Merge pull request #8494 from w3c/woff2-fonts-to-install-as-web-fonts

Add @font-face rules to load TrueType and OpenType test fonts.
diff --git a/.gitignore b/.gitignore
index ad1e88e..fd6a495 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,31 @@
-*#
+# Python
 *.py[co]
+.virtualenv/
+_venv/
+.cache/
+.pytest_cache/
+.tox/
+
+# Node
+node_modules/
+
+# WPT repo stuff
+/MANIFEST.json
+
+testharness_runner.html
+!/testharness_runner.html
+!/tools/wptrunner/wptrunner/testharness_runner.html
+
+_certs
+config.json
+
+# Various OS/editor specific files
+*#
 *.sw[po]
 *~
-MANIFEST.json
 \#*
-_certs
-.virtualenv
-config.json
-node_modules
 scratch
-testharness_runner.html
-webdriver/.idea
+.idea/
 .vscode/
 .DS_Store
 *.rej
-_venv
-webdriver/.cache
diff --git a/.pyup.yml b/.pyup.yml
new file mode 100644
index 0000000..2137f61
--- /dev/null
+++ b/.pyup.yml
@@ -0,0 +1,42 @@
+# search for requirement files
+# default: True
+# allowed: True, False
+search: False
+
+# Specify requirement files by hand, default is empty
+# default: empty
+# allowed: list
+requirements:
+ - tools/wptrunner/requirements_chrome_android.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_edge.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_opera.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_servo.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_safari.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_sauce.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_ie.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_firefox.txt:
+      update: all
+      pin: True
+ - tools/wptrunner/requirements_chrome.txt:
+      update: all
+      pin: True
+ - tools/wpt/requirements.txt:
+      update: all
+      pin: True
diff --git a/.taskcluster.yml b/.taskcluster.yml
new file mode 100644
index 0000000..76f14e2
--- /dev/null
+++ b/.taskcluster.yml
@@ -0,0 +1,889 @@
+# GENERATED FILE DO NOT EDIT
+# To regenerate this file run ./wpt make-tasks
+allowPullRequests: collaborators
+tasks:
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-1, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 1 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-2, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 2 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-3, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 3 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-4, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 4 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-5, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 5 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-6, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 6 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-7, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 7 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-8, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 8 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-9, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 9 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-reftest-10, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome reftest 10 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-wdspec-1, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome wdspec 1 1"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-1, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 1 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-2, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 2 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-3, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 3 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-4, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 4 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-5, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 5 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-6, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 6 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-7, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 7 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-8, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 8 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-9, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 9 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-10, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 10 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-11, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 11 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-12, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 12 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-13, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 13 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-14, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 14 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-chrome-dev-testharness-15, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} chrome-dev &&\n        \
+        \    cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ chrome testharness 15 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-1, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 1 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-2, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 2 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-3, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 3 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-4, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 4 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-5, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 5 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-6, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 6 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-7, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 7 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-8, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 8 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-9, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 9 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-reftest-10, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox reftest 10 10"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-wdspec-1, owner: '{{ event.head.user.email
+      }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox wdspec 1 1"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-1, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 1 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-2, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 2 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-3, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 3 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-4, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 4 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-5, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 5 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-6, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 6 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-7, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 7 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-8, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 8 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-9, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 9 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-10, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 10 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-11, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 11 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-12, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 12 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-13, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 13 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-14, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 14 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+- extra:
+    github:
+      branches: [master]
+      events: [push]
+  metadata: {description: '', name: wpt-firefox-nightly-testharness-15, owner: '{{
+      event.head.user.email }}', source: '{{ event.head.repo.url }}'}
+  payload:
+    artifacts:
+      public/results: {path: /home/test/artifacts, type: directory}
+    command: [/bin/bash, --login, -c, ">-\n            ~/start.sh {{event.head.repo.url}}\
+        \ {{ event.head.repo.branch }} {{event.head.sha}} firefox-nightly &&\n   \
+        \         cd ~/web-platform-tests &&\n            ./tools/ci/ci_taskcluster.sh\
+        \ firefox testharness 15 15"]
+    image: harjgam/web-platform-tests:0.11
+    maxRunTime: 7200
+  provisionerId: '{{ taskcluster.docker.provisionerId }}'
+  workerType: '{{ taskcluster.docker.workerType }}'
+version: 0
diff --git a/.travis.yml b/.travis.yml
index 933183f..6f06b67 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,15 +12,11 @@
     - www2.web-platform.test
     - xn--n8j6ds53lwwkrqhv28a.web-platform.test
     - xn--lve-6lad.web-platform.test
-  jwt:
-    secure: N9lvgkqUPtFlz6Vpa6qTPFhymEsDCsbaCsT64/hj3vlHRxK94r5+ugVJ3zm99zC0q2j1ish8yJC7mN/W4wRfBE4sAwmdxrlowxF1DDGCkaLE9i/GWW92s0fBVGJmXLh8kwNkQ31hMOsaGfHIMpeLFS7Se741te7YqsHIzmBCdQs=
 before_install:
+  # This needs be sourced as it sets various env vars
   - . ./tools/ci/before_install.sh
 install:
   - ./tools/ci/install.sh
-env:  # required at the top-level for allow_failures to work below
-  global:
-    - SAUCE_USERNAME=w3c-ttwf
 matrix:
   fast_finish: true
   include:
@@ -51,7 +47,6 @@
           packages:
             - libnss3-tools
       env:
-        - secure: "YTSXPwI0DyCA1GhYrLT9KMEV6b7QQKuEeaQgeFDP38OTzJ1+cIj3CC4SRNqbnJ/6SJwPGcdqSxLuV8m4e5HFFnyCcQnJe6h8EMsTehZ7W3j/fP9UYrJqYqvGpe3Vj3xblO5pwBYmq7sg3jAmmuCgAgOW6VGf7cRMucrsmFeo7VM="
         - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=firefox:nightly
     - os: linux
       sudo: required
@@ -62,24 +57,11 @@
             - libappindicator1
             - fonts-liberation
       env:
-        - secure: "YTSXPwI0DyCA1GhYrLT9KMEV6b7QQKuEeaQgeFDP38OTzJ1+cIj3CC4SRNqbnJ/6SJwPGcdqSxLuV8m4e5HFFnyCcQnJe6h8EMsTehZ7W3j/fP9UYrJqYqvGpe3Vj3xblO5pwBYmq7sg3jAmmuCgAgOW6VGf7cRMucrsmFeo7VM="
-        - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=chrome:unstable
-    - os: linux
-      python: "2.7"
-      env:
-        - secure: "YTSXPwI0DyCA1GhYrLT9KMEV6b7QQKuEeaQgeFDP38OTzJ1+cIj3CC4SRNqbnJ/6SJwPGcdqSxLuV8m4e5HFFnyCcQnJe6h8EMsTehZ7W3j/fP9UYrJqYqvGpe3Vj3xblO5pwBYmq7sg3jAmmuCgAgOW6VGf7cRMucrsmFeo7VM="
-        - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=sauce:safari:10.0 PLATFORM='macOS 10.12'
-    - os: linux
-      python: "2.7"
-      env:
-        - secure: "YTSXPwI0DyCA1GhYrLT9KMEV6b7QQKuEeaQgeFDP38OTzJ1+cIj3CC4SRNqbnJ/6SJwPGcdqSxLuV8m4e5HFFnyCcQnJe6h8EMsTehZ7W3j/fP9UYrJqYqvGpe3Vj3xblO5pwBYmq7sg3jAmmuCgAgOW6VGf7cRMucrsmFeo7VM="
-        - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=sauce:MicrosoftEdge:14.14393 PLATFORM='Windows 10'
+        - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=chrome:dev
     - python: 2.7
       env: JOB=tools_unittest TOXENV=py27 HYPOTHESIS_PROFILE=ci SCRIPT=tools/ci/ci_tools_unittest.sh
     - python: 3.6
       env: JOB=tools_unittest TOXENV=py36 HYPOTHESIS_PROFILE=ci SCRIPT=tools/ci/ci_tools_unittest.sh
-    - python: pypy-5.4
-      env: JOB=tools_unittest TOXENV=pypy HYPOTHESIS_PROFILE=ci SCRIPT=tools/ci/ci_tools_unittest.sh
     - python: 2.7
       env: JOB=resources_unittest TOXENV=py27 SCRIPT=tools/ci/ci_resources_unittest.sh
     - python: 2.7
@@ -87,20 +69,20 @@
         apt:
           packages:
             - libnss3-tools
-      env: JOB=wpt_integration TOXENV=py27 SCRIPT=tools/ci/ci_wpt.sh
+      env: JOB=wpt_integration TOXENV=py27,py27-flake8 SCRIPT=tools/ci/ci_wpt.sh
+    - os: linux
+      python: "2.7"
+      env: JOB=wptrunner_infrastructure SCRIPT=tools/ci/ci_wptrunner_infrastructure.sh
+      addons:
+        apt:
+          packages:
+            - libnss3-tools
+            - libappindicator1
+            - fonts-liberation
   exclude:
     - env:  # exclude empty env from the top-level above
   allow_failures:
     - env: JOB=build_css SCRIPT=css/build-css-testsuites.sh
-    - env:
-        - secure: "YTSXPwI0DyCA1GhYrLT9KMEV6b7QQKuEeaQgeFDP38OTzJ1+cIj3CC4SRNqbnJ/6SJwPGcdqSxLuV8m4e5HFFnyCcQnJe6h8EMsTehZ7W3j/fP9UYrJqYqvGpe3Vj3xblO5pwBYmq7sg3jAmmuCgAgOW6VGf7cRMucrsmFeo7VM="
-        - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=chrome:unstable
-    - env:
-        - secure: "YTSXPwI0DyCA1GhYrLT9KMEV6b7QQKuEeaQgeFDP38OTzJ1+cIj3CC4SRNqbnJ/6SJwPGcdqSxLuV8m4e5HFFnyCcQnJe6h8EMsTehZ7W3j/fP9UYrJqYqvGpe3Vj3xblO5pwBYmq7sg3jAmmuCgAgOW6VGf7cRMucrsmFeo7VM="
-        - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=sauce:MicrosoftEdge:14.14393 PLATFORM='Windows 10'
-    - env:
-        - secure: "YTSXPwI0DyCA1GhYrLT9KMEV6b7QQKuEeaQgeFDP38OTzJ1+cIj3CC4SRNqbnJ/6SJwPGcdqSxLuV8m4e5HFFnyCcQnJe6h8EMsTehZ7W3j/fP9UYrJqYqvGpe3Vj3xblO5pwBYmq7sg3jAmmuCgAgOW6VGf7cRMucrsmFeo7VM="
-        - JOB=stability SCRIPT=tools/ci/ci_stability.sh PRODUCT=sauce:safari:10.0 PLATFORM='macOS 10.12'
 script:
   - ./tools/ci/run.sh
 cache:
@@ -111,4 +93,3 @@
   email:
     on_success: never
     on_failure: always
-  webhooks: https://pulls.web-platform-tests.org/api/build
diff --git a/.well-known/idp-proxy/OWNERS b/.well-known/idp-proxy/OWNERS
new file mode 100644
index 0000000..79ac31d
--- /dev/null
+++ b/.well-known/idp-proxy/OWNERS
@@ -0,0 +1 @@
+@nils-ohlmeier
diff --git a/2dcontext/OWNERS b/2dcontext/OWNERS
new file mode 100644
index 0000000..3bb6edf
--- /dev/null
+++ b/2dcontext/OWNERS
@@ -0,0 +1,5 @@
+@AmeliaBR
+@annevk
+@kenrussell
+@jdashg
+@fserb
diff --git a/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html b/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
index 5f1c7fa..1621c03 100644
--- a/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
+++ b/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
@@ -8,34 +8,29 @@
 <body class="show_output">
 
 <h1>2d.drawImage.zerocanvas</h1>
-<p class="desc"></p>
+<p class="desc">drawImage with zero-sized canvas as the source shoud throw exception</p>
 
 
 <p class="output">Actual output:</p>
 <canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
-<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
+
 <ul id="d"></ul>
 <script>
-var t = async_test("");
+var t = async_test("drawImage with zero-sized canvas as the source shoud throw exception");
 _addTest(function(canvas, ctx) {
 
-ctx.fillStyle = '#0f0';
-ctx.fillRect(0, 0, 100, 50);
-
 var canvas2 = document.createElement('canvas');
 canvas2.width = 0;
-canvas2.height = 10;
-ctx.drawImage(canvas2, 0, 0);
+canvas2.height = 50;
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(canvas2, 0, 0); });
 
-canvas2.width = 10;
+canvas2.width = 50;
 canvas2.height = 0;
-ctx.drawImage(canvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(canvas2, 0, 0); });
 
 canvas2.width = 0;
 canvas2.height = 0;
-ctx.drawImage(canvas2, 0, 0);
-
-_assertPixelApprox(canvas, 50,25, 0,255,0,255, "50,25", "0,255,0,255", 2);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(canvas2, 0, 0); });
 
 
 });
diff --git a/2dcontext/drawing-paths-to-the-canvas/canvas_focus_drawCustomFocusRing_001.html b/2dcontext/drawing-paths-to-the-canvas/canvas_focus_drawCustomFocusRing_001.html
deleted file mode 100644
index 56d5ebb..0000000
--- a/2dcontext/drawing-paths-to-the-canvas/canvas_focus_drawCustomFocusRing_001.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>canvas drawCustomFocusRing() step1 test</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="author" title="Takeshi Kurosawa" href="mailto:kurosawa-takeshi@mitsue.co.jp">
-    <link rel="help" href="http://www.w3.org/TR/2dcontext/#dom-context-2d-drawcustomfocusring">
-  </head>
-  <body>
-    <h1>Description</h1>
-    <p>This test checks whether drawCustomFocusRing returns false if the element passed as an argument is not focused or is not a descendant of the element with whose context the method is associated.</p>
-    <div id="log"></div>
-    <div>
-      <input type="text" id="text0">
-      <canvas id="canvas"><input type="text" id="text1"></canvas>
-    </div>
-    <script>
-    (function() {
-      test(function() {
-          var canvas = document.getElementById('canvas');
-          var context = canvas.getContext('2d');
-          var text0 = document.getElementById('text0');
-          text0.focus(); // document.activeElement === text0;
-
-          var text1 = document.getElementById('text1');
-          assert_false(context.drawCustomFocusRing(text1));
-      }, 'drawCustomFocusRing must return false for an element that is not focused.');
-
-      test(function() {
-          var canvas = document.getElementById('canvas');
-          var context = canvas.getContext('2d');
-          var text0 = document.getElementById('text0');
-          text0.focus(); // document.activeElement === text0;
-
-          assert_false(context.drawCustomFocusRing(text0));
-      }, 'drawCustomFocusRing must return false for an element that is not a descendant of the canvas element.');
-    })();
-    </script>
-  </body>
-</html>
diff --git a/2dcontext/fill-and-stroke-styles/2d.gradient.object.invalidcolour.html b/2dcontext/fill-and-stroke-styles/2d.gradient.object.invalidcolour.html
index 7687d0b..7ea349e 100644
--- a/2dcontext/fill-and-stroke-styles/2d.gradient.object.invalidcolour.html
+++ b/2dcontext/fill-and-stroke-styles/2d.gradient.object.invalidcolour.html
@@ -21,6 +21,15 @@
 
 var g = ctx.createLinearGradient(0, 0, 100, 0);
 assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, ""); });
+assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)'); });
+assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, 'null'); });
+assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, 'undefined'); });
+assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, null); });
+assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, undefined); });
+
+var g = ctx.createRadialGradient(0, 0, 0, 100, 0, 0);
+assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, ""); });
+assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)'); });
 assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, 'null'); });
 assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, 'undefined'); });
 assert_throws("SYNTAX_ERR", function() { g.addColorStop(0, null); });
diff --git a/2dcontext/imagebitmap/common.js b/2dcontext/imagebitmap/common.js
deleted file mode 100644
index 54cdf6b..0000000
--- a/2dcontext/imagebitmap/common.js
+++ /dev/null
@@ -1,58 +0,0 @@
-function testCanvasDisplayingPattern(canvas)
-{
-    var tolerance = 5; // for creating ImageBitmap from a video, the tolerance needs to be high
-    _assertPixelApprox(canvas, 5,5, 255,0,0,255, "5,5", "255,0,0,255", tolerance);
-    _assertPixelApprox(canvas, 15,5, 0,255,0,255, "15,5", "0,255,0,255", tolerance);
-    _assertPixelApprox(canvas, 5,15, 0,0,255,255, "5,15", "0,0,255,255", tolerance);
-    _assertPixelApprox(canvas, 15,15, 0,0,0,255, "15,15", "0,0,0,255", tolerance);
-}
-
-function testDrawImageBitmap(source)
-{
-    var canvas = document.createElement("canvas");
-    canvas.width = 20;
-    canvas.height = 20;
-    var ctx = canvas.getContext("2d");
-    ctx.clearRect(0, 0, canvas.width, canvas.height);
-    return createImageBitmap(source).then(imageBitmap => {
-        ctx.drawImage(imageBitmap, 0, 0);
-        testCanvasDisplayingPattern(canvas);
-    });
-}
-
-function initializeTestCanvas(testCanvas)
-{
-    testCanvas.width = 20;
-    testCanvas.height = 20;
-    var testCtx = testCanvas.getContext("2d");
-    testCtx.fillStyle = "rgb(255, 0, 0)";
-    testCtx.fillRect(0, 0, 10, 10);
-    testCtx.fillStyle = "rgb(0, 255, 0)";
-    testCtx.fillRect(10, 0, 10, 10);
-    testCtx.fillStyle = "rgb(0, 0, 255)";
-    testCtx.fillRect(0, 10, 10, 10);
-    testCtx.fillStyle = "rgb(0, 0, 0)";
-    testCtx.fillRect(10, 10, 10, 10);
-}
-
-function initializeImageData(imgData, width, height)
-{
-    for (var i = 0; i < width * height * 4; i+=4) {
-        imgData.data[i] = 0;
-        imgData.data[i + 1] = 0;
-        imgData.data[i + 2] = 0;
-        imgData.data[i + 3] = 255; //alpha channel: 255
-    }
-    var halfWidth = width/2;
-    var halfHeight = height/2;
-    // initialize to R, G, B, Black, with each one 10*10 pixels
-    for (var i = 0; i < halfHeight; i++)
-        for (var j = 0; j < halfWidth; j++)
-            imgData.data[i * width * 4 + j * 4] = 255;
-    for (var i = 0; i < halfHeight; i++)
-        for (var j = halfWidth; j < width; j++)
-            imgData.data[i * width * 4 + j * 4 + 1] = 255;
-    for (var i = halfHeight; i < height; i++)
-        for (var j = 0; j < halfWidth; j++)
-            imgData.data[i * width * 4 + j * 4 + 2] = 255;
-}
diff --git a/2dcontext/imagebitmap/common.sub.js b/2dcontext/imagebitmap/common.sub.js
new file mode 100644
index 0000000..7f99396
--- /dev/null
+++ b/2dcontext/imagebitmap/common.sub.js
@@ -0,0 +1,156 @@
+function makeCanvas() {
+    return new Promise(resolve => {
+        var testCanvas = document.createElement("canvas");
+        testCanvas.width = 20;
+        testCanvas.height = 20;
+        var testCtx = testCanvas.getContext("2d");
+        testCtx.fillStyle = "rgb(255, 0, 0)";
+        testCtx.fillRect(0, 0, 10, 10);
+        testCtx.fillStyle = "rgb(0, 255, 0)";
+        testCtx.fillRect(10, 0, 10, 10);
+        testCtx.fillStyle = "rgb(0, 0, 255)";
+        testCtx.fillRect(0, 10, 10, 10);
+        testCtx.fillStyle = "rgb(0, 0, 0)";
+        testCtx.fillRect(10, 10, 10, 10);
+        resolve(testCanvas);
+    });
+}
+
+function makeOffscreenCanvas() {
+    return new Promise(resolve => {
+        let canvas = new OffscreenCanvas(20, 20);
+        var testCtx = canvas.getContext("2d");
+        testCtx.fillStyle = "rgb(255, 0, 0)";
+        testCtx.fillRect(0, 0, 10, 10);
+        testCtx.fillStyle = "rgb(0, 255, 0)";
+        testCtx.fillRect(10, 0, 10, 10);
+        testCtx.fillStyle = "rgb(0, 0, 255)";
+        testCtx.fillRect(0, 10, 10, 10);
+        testCtx.fillStyle = "rgb(0, 0, 0)";
+        testCtx.fillRect(10, 10, 10, 10);
+        resolve(canvas);
+    });
+}
+
+var imageBitmapVideoPromise = new Promise(function(resolve, reject) {
+    var video = document.createElement("video");
+    video.oncanplaythrough = function() {
+        resolve(video);
+    };
+    video.onerror = reject;
+    video.src = getVideoURI("/images/pattern");
+
+    // Prevent WebKit from garbage collecting event handlers.
+    window._video = video;
+});
+
+function makeVideo() {
+    return imageBitmapVideoPromise;
+}
+
+var imageBitmapDataUrlVideoPromise = fetch(getVideoURI("/images/pattern"))
+    .then(response => Promise.all([response.headers.get("Content-Type"), response.arrayBuffer()]))
+    .then(([type, data]) => {
+        return new Promise(function(resolve, reject) {
+            var video = document.createElement("video");
+            video.oncanplaythrough = function() {
+                resolve(video);
+            };
+            video.onerror = reject;
+
+            var encoded = btoa(String.fromCodePoint(...new Uint8Array(data)));
+            var dataUrl = `data:${type};base64,${encoded}`;
+            video.src = dataUrl;
+
+            // Prevent WebKit from garbage collecting event handlers.
+            window._dataVideo = video;
+        });
+    });
+
+function makeDataUrlVideo() {
+    return imageBitmapDataUrlVideoPromise;
+}
+
+function makeMakeHTMLImage(src) {
+    return function() {
+        return new Promise((resolve, reject) => {
+            var img = new Image();
+            img.onload = function() {
+                resolve(img);
+            };
+            img.onerror = reject;
+            img.src = src;
+        });
+    }
+}
+
+function makeMakeSVGImage(src) {
+    return function() {
+        return new Promise((resolve, reject) => {
+            var image = document.createElementNS(NAMESPACES.svg, "image");
+            image.onload = () => resolve(image);
+            image.onerror = reject;
+            image.setAttribute("externalResourcesRequired", "true");
+            image.setAttributeNS(NAMESPACES.xlink, 'xlink:href', src);
+            document.body.appendChild(image);
+        });
+    }
+}
+
+function makeImageData() {
+    return new Promise(function(resolve, reject) {
+        var width = 20, height = 20;
+        var imgData = new ImageData(width, height);
+        for (var i = 0; i < width * height * 4; i += 4) {
+            imgData.data[i] = 0;
+            imgData.data[i + 1] = 0;
+            imgData.data[i + 2] = 0;
+            imgData.data[i + 3] = 255; //alpha channel: 255
+        }
+        var halfWidth = width / 2;
+        var halfHeight = height / 2;
+        // initialize to R, G, B, Black, with each one 10*10 pixels
+        for (var i = 0; i < halfHeight; i++)
+            for (var j = 0; j < halfWidth; j++)
+                imgData.data[i * width * 4 + j * 4] = 255;
+        for (var i = 0; i < halfHeight; i++)
+            for (var j = halfWidth; j < width; j++)
+                imgData.data[i * width * 4 + j * 4 + 1] = 255;
+        for (var i = halfHeight; i < height; i++)
+            for (var j = 0; j < halfWidth; j++)
+                imgData.data[i * width * 4 + j * 4 + 2] = 255;
+        resolve(imgData);
+    });
+}
+
+function makeImageBitmap() {
+    return makeCanvas().then(canvas => {
+        return createImageBitmap(canvas);
+    });
+}
+
+function makeBlob() {
+    return new Promise(function(resolve, reject) {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", '/images/pattern.png');
+        xhr.responseType = 'blob';
+        xhr.send();
+        xhr.onload = function() {
+            resolve(xhr.response);
+        };
+    });
+}
+
+var imageSourceTypes = [
+    { name: 'an HTMLCanvasElement', factory: makeCanvas },
+    { name: 'an HTMLVideoElement',  factory: makeVideo },
+    { name: 'an HTMLVideoElement from a data URL', factory: makeDataUrlVideo },
+    { name: 'a bitmap HTMLImageElement', factory: makeMakeHTMLImage("/images/pattern.png") },
+    { name: 'a vector HTMLImageElement', factory: makeMakeHTMLImage("/images/pattern.svg") },
+    { name: 'a bitmap SVGImageElement', factory: makeMakeSVGImage("/images/pattern.png") },
+    { name: 'a vector SVGImageElement', factory: makeMakeSVGImage("/images/pattern.svg") },
+    { name: 'an OffscreenCanvas',   factory: makeOffscreenCanvas },
+    { name: 'an ImageData',         factory: makeImageData },
+    { name: 'an ImageBitmap',       factory: makeImageBitmap },
+    { name: 'a Blob',               factory: makeBlob },
+];
diff --git a/2dcontext/imagebitmap/createImageBitmap-drawImage.html b/2dcontext/imagebitmap/createImageBitmap-drawImage.html
index 39c5ac1..9146a37 100644
--- a/2dcontext/imagebitmap/createImageBitmap-drawImage.html
+++ b/2dcontext/imagebitmap/createImageBitmap-drawImage.html
@@ -4,72 +4,71 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/common/canvas-tests.js"></script>
-<script src="common.js"></script>
+<script src="/common/media.js"></script>
+<script src="/common/namespaces.js"></script>
+<script src="common.sub.js"></script>
 <link rel="stylesheet" href="/common/canvas-tests.css">
 <body>
 <script>
-(function() {
+function testCanvasDisplayingPattern(canvas, width, height)
+{
+    var tolerance = 10; // for creating ImageBitmap from a video, the tolerance needs to be high
+    const check = (x, y, r, g, b, a) =>
+        _assertPixelApprox(canvas, x,y, r,g,b,a, `${x},${y}`, `${r},${g},${b},${a}`, tolerance);
+    check(1 * width / 4, 1 * height / 4, 255,0,0,255);
+    check(3 * width / 4, 1 * height / 4, 0,255,0,255);
+    check(1 * width / 4, 3 * height / 4, 0,0,255,255);
+    check(3 * width / 4, 3 * height / 4, 0,0,0,255);
+}
+
+function testDrawImageBitmap(source, args = [], { resizeWidth = 20, resizeHeight = 20 } = {})
+{
+    var canvas = document.createElement("canvas");
+    canvas.width = resizeWidth;
+    canvas.height = resizeHeight;
+    var ctx = canvas.getContext("2d");
+    return createImageBitmap(source, ...args).then(imageBitmap => {
+        assert_equals(imageBitmap.width, resizeWidth);
+        assert_equals(imageBitmap.height, resizeHeight);
+        ctx.drawImage(imageBitmap, 0, 0);
+        testCanvasDisplayingPattern(canvas, resizeWidth, resizeHeight);
+    });
+}
+
+for (let { name, factory } of imageSourceTypes) {
     promise_test(function() {
-        return new Promise(function(resolve, reject) {
-            var img = new Image();
-            img.onload = function() { resolve(img); };
-            img.src = "/images/pattern.png";
-        }).then(function(img) {
+        return factory().then(function(img) {
             return testDrawImageBitmap(img);
         });
-    }, "createImageBitmap from a HTMLImageElement, and drawImage on the created ImageBitmap");
+    }, `createImageBitmap from ${name}, and drawImage on the created ImageBitmap`);
 
     promise_test(function() {
-        return new Promise(function(resolve, reject) {
-            var xhr = new XMLHttpRequest();
-            xhr.open("GET", '/images/pattern.png');
-            xhr.responseType = 'blob';
-            xhr.send();
-            xhr.onload = function() {
-                blob = xhr.response;
-                resolve(blob);
-            };
-        }).then(function(blob) {
-            return testDrawImageBitmap(blob);
+        return factory().then(function(img) {
+            const options = { resizeWidth: 10, resizeHeight: 10 };
+            return testDrawImageBitmap(img, [options], options);
         });
-    }, "createImageBitmap from a Blob, and drawImage on the created ImageBitmap");
+    }, `createImageBitmap from ${name} scaled down, and drawImage on the created ImageBitmap`);
 
     promise_test(function() {
-        var testCanvas = document.createElement("canvas");
-        initializeTestCanvas(testCanvas);
-        return testDrawImageBitmap(testCanvas);
-    }, "createImageBitmap from a HTMLCanvasElement, and drawImage on the created ImageBitmap");
-
-    promise_test(function() {
-        var testCanvas = document.createElement("canvas");
-        initializeTestCanvas(testCanvas);
-        return new Promise(function(resolve, reject) {
-            createImageBitmap(testCanvas).then(function(bitmap) {
-                resolve(bitmap);
-            });
-        }).then(function(bitmap) {
-            return testDrawImageBitmap(bitmap);
+        return factory().then(function(img) {
+            const options = { resizeWidth: 40, resizeHeight: 40 };
+            return testDrawImageBitmap(img, [options], options);
         });
-    }, "createImageBitmap from an ImageBitmap, and drawImage on the created ImageBitmap");
+    }, `createImageBitmap from ${name} scaled up, and drawImage on the created ImageBitmap`);
 
     promise_test(function() {
-        var imgData = new ImageData(20, 20);
-        initializeImageData(imgData, 20, 20);
-        return testDrawImageBitmap(imgData);
-    }, "createImageBitmap from an ImageData, and drawImage on the created ImageBitmap");
-
-    promise_test(function() {
-        return new Promise(function(resolve, reject) {
-            var video = document.createElement("video");
-            video.oncanplaythrough = function() {
-                resolve(video);
-            };
-            video.src = "/images/pattern.ogv";
-        }).then(function(video) {
-            return testDrawImageBitmap(video);
+        return factory().then(function(img) {
+            const options = { resizeWidth: 10, resizeHeight: 40 };
+            return testDrawImageBitmap(img, [options], options);
         });
-    }, "createImageBitmap from a HTMLVideoElement, and drawImage on the created ImageBitmap");
-})();
+    }, `createImageBitmap from ${name} resized, and drawImage on the created ImageBitmap`);
+
+    promise_test(function() {
+        return factory().then(function(img) {
+            return testDrawImageBitmap(img, [20, 20, -20, -20]);
+        });
+    }, `createImageBitmap from ${name} with negative sw/sh, and drawImage on the created ImageBitmap`);
+}
 </script>
 </body>
 </html>
diff --git a/2dcontext/imagebitmap/createImageBitmap-invalid-args.html b/2dcontext/imagebitmap/createImageBitmap-invalid-args.html
index a0bcf48..801ec41 100644
--- a/2dcontext/imagebitmap/createImageBitmap-invalid-args.html
+++ b/2dcontext/imagebitmap/createImageBitmap-invalid-args.html
@@ -1,28 +1,11 @@
-<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
-<html>
-<head>
+<!doctype html>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
+<script src="/common/media.js"></script>
+<script src="/common/namespaces.js"></script>
+<script src="common.sub.js"></script>
 <script>
 
-function makeCanvas() {
-  return new Promise(resolve => {
-    let canvas = document.createElement('canvas');
-    canvas.setAttribute('width', '10');
-    canvas.setAttribute('height', '10');
-    resolve(canvas);
-  });
-}
-
-function makeOffscreenCanvas() {
-  return new Promise(resolve => {
-    let canvas = new OffscreenCanvas(10, 10);
-    resolve(canvas);
-  });
-}
-
 function makeOversizedCanvas() {
 
   return new Promise(resolve => {
@@ -40,66 +23,33 @@
   });
 }
 
-function makeVideo() {
-  return new Promise(resolve => {
-    let video = document.createElement('video');
-    video.addEventListener('canplaythrough', resolve.bind(undefined, video), false);
-    video.src = '/media/A4.ogv';
-  });
-}
-
-function makeImage() {
-  return makeCanvas().then(canvas => {
-    let image = new Image();
-    image.src = canvas.toDataURL();
-    return new Promise(resolve => {
-      image.onload = resolve.bind(undefined, image);
-    });
-  });
-}
-
-function makeImageData() {
-  return makeCanvas().then(canvas => {
-    return new Promise(function(resolve, reject) {
-      resolve(canvas.getContext('2d').getImageData(0, 0, 10, 10));
-    });
-  });
-}
-
-function makeImageBitmap() {
-  return makeCanvas().then(canvas => {
-    return createImageBitmap(canvas);
-  });
-}
-
-function makeBlob() {
-  return makeCanvas().then(canvas => {
-    return new Promise(resolve => {
-      canvas.toBlob(resolve);
-    });
-  });
-}
-
 function makeInvalidBlob() {
   return new Promise(resolve => {
     resolve(new Blob()); // Blob with no data cannot be decoded.
   });
 }
 
-imageSourceTypes = [
-  { name: 'HTMLImageElement',  factory: makeImage },
-  { name: 'HTMLVideoElement',  factory: makeVideo },
-  { name: 'HTMLCanvasElement', factory: makeCanvas },
-  { name: 'OffscreenCanvas',   factory: makeOffscreenCanvas },
-  { name: 'ImageData',         factory: makeImageData },
-  { name: 'ImageBitmap',       factory: makeImageBitmap },
-  { name: 'Blob',              factory: makeBlob },
-];
+function makeBrokenImage() {
+  return new Promise((resolve, reject) => {
+    const image = new Image();
+    image.src = "data:,x";
+    image.onload = reject;
+    image.onerror = () => resolve(image);
+  });
+}
+
+function makeAvailableButBrokenImage(path) {
+  return new Promise((resolve, reject) => {
+    const image = new Image();
+    image.src = path;
+    image.onload = () => resolve(image);
+    image.onerror = reject;
+  });
+}
 
 testCases = [
   {
-    description: 'createImageBitmap with a <sourceType> source and sw set to ' +
-        '0 rejects with a RangeError.',
+    description: 'createImageBitmap with a <sourceType> source and sw set to 0',
     promiseTestFunction:
       (source, t) => {
         return promise_rejects(t, new RangeError(),
@@ -107,8 +57,7 @@
       }
   },
   {
-    description: 'createImageBitmap with a <sourceType> source and sh set to ' +
-        '0 rejects with a RangeError.',
+    description: 'createImageBitmap with a <sourceType> source and sh set to 0',
     promiseTestFunction:
       (source, t) => {
         return promise_rejects(t, new RangeError(),
@@ -118,10 +67,11 @@
   {
     // This case is not explicitly documented in the specification for
     // createImageBitmap, but it is expected that internal failures cause
+    // InvalidStateError.
     //
+    // Note: https://bugs.chromium.org/p/chromium/issues/detail?id=799025
     description: 'createImageBitmap with a <sourceType> source and oversized ' +
-        '(unallocatable) crop region rejects with an InvalidStateError ' +
-        'DOMException.',
+        '(unallocatable) crop region',
     promiseTestFunction:
       (source, t) => {
         return promise_rejects(t, new DOMException('', 'InvalidStateError'),
@@ -145,43 +95,95 @@
 
 promise_test( t => {
   return promise_rejects(t, new TypeError(), createImageBitmap(undefined));
-}, "createImageBitmap with undefined image source rejects with a TypeError.");
+}, "createImageBitmap with undefined image source.");
 
 promise_test( t => {
   return promise_rejects(t, new TypeError(), createImageBitmap(null));
-}, "createImageBitmap with null image source rejects with a TypeError.");
+}, "createImageBitmap with null image source.");
 
 promise_test( t => {
-  return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+  var context = document.createElement("canvas").getContext("2d");
+  return promise_rejects(t, new TypeError(), createImageBitmap(context));
+}, "createImageBitmap with CanvasRenderingContext2D image source.");
+
+promise_test( t => {
+  var context = document.createElement("canvas").getContext("webgl");
+  return promise_rejects(t, new TypeError(), createImageBitmap(context));
+}, "createImageBitmap with WebGLRenderingContext image source.");
+
+promise_test( t => {
+  var buffer = new Uint8Array();
+  return promise_rejects(t, new TypeError(), createImageBitmap(buffer));
+}, "createImageBitmap with Uint8Array image source.");
+
+promise_test( t => {
+  var buffer = new ArrayBuffer(8);
+  return promise_rejects(t, new TypeError(), createImageBitmap(buffer));
+}, "createImageBitmap with ArrayBuffer image source.");
+
+promise_test( t => {
+  return promise_rejects(t, "InvalidStateError",
     createImageBitmap(new Image()));
-}, "createImageBitmap with empty image source rejects with a InvalidStateError.");
+}, "createImageBitmap with empty image source.");
 
 promise_test( t => {
-  return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+  return promise_rejects(t, "InvalidStateError",
     createImageBitmap(document.createElement('video')));
-}, "createImageBitmap with empty video source rejects with a InvalidStateError.");
+}, "createImageBitmap with empty video source.");
 
 promise_test( t => {
   return makeOversizedCanvas().then(canvas => {
-    return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+    return promise_rejects(t, "InvalidStateError",
         createImageBitmap(canvas));
   });
-}, "createImageBitmap with an oversized canvas source rejects with a RangeError.");
+}, "createImageBitmap with an oversized canvas source.");
 
 promise_test( t => {
   return makeOversizedOffscreenCanvas().then(offscreenCanvas => {
-    return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+    return promise_rejects(t, "InvalidStateError",
         createImageBitmap(offscreenCanvas));
   });
-}, "createImageBitmap with an invalid OffscreenCanvas source rejects with a RangeError.");
+}, "createImageBitmap with an invalid OffscreenCanvas source.");
 
 promise_test( t => {
   return makeInvalidBlob().then(blob => {
-    return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+    return promise_rejects(t, "InvalidStateError",
         createImageBitmap(blob));
   });
-}, "createImageBitmap with an undecodable blob source rejects with an InvalidStateError.");
+}, "createImageBitmap with an undecodable blob source.");
 
+promise_test( t => {
+  return makeBrokenImage().then(image => {
+    return promise_rejects(t, "InvalidStateError",
+        createImageBitmap(image));
+  });
+}, "createImageBitmap with a broken image source.");
+
+promise_test( t => {
+  return makeAvailableButBrokenImage("/images/broken.png").then(image => {
+    return promise_rejects(t, "InvalidStateError",
+        createImageBitmap(image));
+  });
+}, "createImageBitmap with an available but undecodable image source.");
+
+promise_test( t => {
+  return makeAvailableButBrokenImage("/images/red-zeroheight.svg").then(image => {
+    return promise_rejects(t, "InvalidStateError",
+        createImageBitmap(image));
+  });
+}, "createImageBitmap with an available but zero height image source.");
+
+promise_test( t => {
+  return makeAvailableButBrokenImage("/images/red-zerowidth.svg").then(image => {
+    return promise_rejects(t, "InvalidStateError",
+        createImageBitmap(image));
+  });
+}, "createImageBitmap with an available but zero width image source.");
+
+promise_test( t => {
+  return makeImageBitmap().then(bitmap => {
+    bitmap.close()
+    return promise_rejects(t, "InvalidStateError", createImageBitmap(bitmap));
+  });
+}, "createImageBitmap with a closed ImageBitmap.");
 </script>
-</body>
-</html>
diff --git a/2dcontext/imagebitmap/createImageBitmap-origin.sub.html b/2dcontext/imagebitmap/createImageBitmap-origin.sub.html
new file mode 100644
index 0000000..24848f3
--- /dev/null
+++ b/2dcontext/imagebitmap/createImageBitmap-origin.sub.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>createImageBitmap: origin-clean flag</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script src="/common/namespaces.js"></script>
+<div id=log></div>
+<script>
+const crossOriginImageUrl = "http://{{domains[www1]}}:{{ports[http][0]}}/images/red.png";
+
+function assert_origin_unclean(bitmap) {
+  const context = document.createElement("canvas").getContext("2d");
+  context.drawImage(bitmap, 0, 0);
+  assert_throws("SecurityError", () => {
+    context.getImageData(0, 0, 1, 1);
+  });
+}
+
+function makeImage() {
+  return new Promise((resolve, reject) => {
+    const image = new Image();
+    image.onload = () => resolve(image);
+    image.onerror = reject;
+    image.src = crossOriginImageUrl;
+  });
+}
+
+const arguments = [
+  {
+    name: "cross-origin HTMLImageElement",
+    factory: makeImage,
+  },
+
+  {
+    name: "cross-origin SVGImageElement",
+    factory: () => {
+      return new Promise((resolve, reject) => {
+        const image = document.createElementNS(NAMESPACES.svg, "image");
+        image.onload = () => resolve(image);
+        image.onerror = reject;
+        image.setAttribute("externalResourcesRequired", "true");
+        image.setAttributeNS(NAMESPACES.xlink, 'xlink:href', crossOriginImageUrl);
+        document.body.appendChild(image);
+      });
+    },
+  },
+
+  {
+    name: "cross-origin HTMLVideoElement",
+    factory: () => {
+      return new Promise((resolve, reject) => {
+        const video = document.createElement("video");
+        video.oncanplaythrough = () => resolve(video);
+        video.onerror = reject;
+        video.src = getVideoURI("http://{{domains[www1]}}:{{ports[http][0]}}/media/movie_300");
+      });
+    },
+  },
+
+  {
+    name: "redirected to cross-origin HTMLVideoElement",
+    factory: () => {
+      return new Promise((resolve, reject) => {
+        const video = document.createElement("video");
+        video.oncanplaythrough = () => resolve(video);
+        video.onerror = reject;
+        video.src = "/common/redirect.py?location=" + getVideoURI("http://{{domains[www1]}}:{{ports[http][0]}}/media/movie_300");
+      });
+    },
+  },
+
+  {
+    name: "redirected to same-origin HTMLVideoElement",
+    factory: () => {
+      return new Promise((resolve, reject) => {
+        const video = document.createElement("video");
+        video.oncanplaythrough = () => resolve(video);
+        video.onerror = reject;
+        video.src = "http://{{domains[www1]}}:{{ports[http][0]}}/common/redirect.py?location=" + getVideoURI("http://{{domains[]}}:{{ports[http][0]}}/media/movie_300");
+      });
+    },
+  },
+
+  {
+    name: "unclean HTMLCanvasElement",
+    factory: () => {
+      return makeImage().then(image => {
+        const canvas = document.createElement("canvas");
+        const context = canvas.getContext("2d");
+        context.drawImage(image, 0, 0);
+        return canvas;
+      });
+    },
+  },
+
+  {
+    name: "unclean ImageBitmap",
+    factory: () => {
+      return makeImage().then(createImageBitmap);
+    },
+  },
+];
+
+for (let { name, factory } of arguments) {
+  promise_test(function() {
+    return factory().then(createImageBitmap).then(assert_origin_unclean);
+  }, name);
+}
+</script>
diff --git a/2dcontext/imagebitmap/createImageBitmap-transfer.html b/2dcontext/imagebitmap/createImageBitmap-transfer.html
new file mode 100644
index 0000000..0302634
--- /dev/null
+++ b/2dcontext/imagebitmap/createImageBitmap-transfer.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>createImageBitmap transferring test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script src="/common/namespaces.js"></script>
+<script src="common.sub.js"></script>
+<div id=log></div>
+<script>
+let worker, continuations = {};
+setup(function() {
+    worker = new Worker("transfer-worker.js");
+    worker.addEventListener("message", function(event) {
+        let { name, bitmap } = event.data;
+        if (continuations.hasOwnProperty(name)) {
+            continuations[name](bitmap);
+        }
+    });
+});
+
+for (let { name, factory } of imageSourceTypes) {
+    promise_test(function(t) {
+        return factory().then(createImageBitmap).then(function(bitmap) {
+            assert_equals(bitmap.width, 20);
+            assert_equals(bitmap.height, 20);
+
+            worker.postMessage({ name: t.name, bitmap: bitmap }, [bitmap]);
+
+            assert_equals(bitmap.width, 0);
+            assert_equals(bitmap.height, 0);
+
+            return new Promise(function(resolve) {
+                continuations[t.name] = resolve;
+            });
+        }).then(function(bitmap) {
+            assert_class_string(bitmap, "ImageBitmap");
+            assert_equals(bitmap.width, 20);
+            assert_equals(bitmap.height, 20);
+        });
+    }, `Transfer ImageBitmap created from ${name}`);
+}
+</script>
diff --git a/2dcontext/imagebitmap/transfer-worker.js b/2dcontext/imagebitmap/transfer-worker.js
new file mode 100644
index 0000000..55465a8
--- /dev/null
+++ b/2dcontext/imagebitmap/transfer-worker.js
@@ -0,0 +1,3 @@
+addEventListener('message', evt => {
+    postMessage(evt.data, [evt.data.bitmap]);
+});
diff --git a/2dcontext/tools/gentestutils.py b/2dcontext/tools/gentestutils.py
index 400a27e..9e7f7ad 100644
--- a/2dcontext/tools/gentestutils.py
+++ b/2dcontext/tools/gentestutils.py
@@ -32,6 +32,8 @@
 #
 # * Test the tests, add new ones to Git, remove deleted ones from Git, etc.
 
+from __future__ import print_function
+
 import re
 import codecs
 import time
@@ -191,7 +193,7 @@
                 mapped_name = "%s/%s" % (name_mapping[mn], name)
                 break
         if not mapped_name:
-            print "LIKELY ERROR: %s has no defined target directory mapping" % name
+            print("LIKELY ERROR: %s has no defined target directory mapping" % name)
         if 'manual' in test:
             mapped_name += "-manual"
         return mapped_name
@@ -269,10 +271,10 @@
         test = tests[i]
 
         name = test['name']
-        print "\r(%s)" % name, " "*32, "\t",
+        print("\r(%s)" % name, " "*32, "\t")
 
         if name in used_tests:
-            print "Test %s is defined twice" % name
+            print("Test %s is defined twice" % name)
         used_tests[name] = 1
 
         mapped_name = map_name(name)
@@ -292,14 +294,14 @@
 
         for ref in test.get('testing', []):
             if ref not in spec_ids:
-                print "Test %s uses nonexistent spec point %s" % (name, ref)
+                print("Test %s uses nonexistent spec point %s" % (name, ref))
             spec_refs.setdefault(ref, []).append(name)
 
         if not test.get('testing', []):
-            print "Test %s doesn't refer to any spec points" % name
+            print("Test %s doesn't refer to any spec points" % name)
 
         if test.get('expected', '') == 'green' and re.search(r'@assert pixel .* 0,0,0,0;', test['code']):
-            print "Probable incorrect pixel test in %s" % name
+            print("Probable incorrect pixel test in %s" % name)
 
         code = expand_test_code(test['code'])
 
@@ -312,7 +314,8 @@
             elif expected == 'clear':
                 expected_img = "/images/" + make_flat_image('clear-100x50.png', 100, 50, 0,0,0,0)
             else:
-                if ';' in expected: print "Found semicolon in %s" % name
+                if ';' in expected:
+                    print("Found semicolon in %s" % name)
                 expected = re.sub(r'^size (\d+) (\d+)',
                     r'surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, \1, \2)\ncr = cairo.Context(surface)',
                                   expected)
@@ -394,7 +397,7 @@
                 f = codecs.open('%s/%s%s.worker.js' % (TESTOUTPUTDIR, mapped_name, name_variant), 'w', 'utf-8')
                 f.write(templates['w3cworker'] % template_params)
 
-    print
+    print()
 
 
     def getNodeText(node):
@@ -469,7 +472,7 @@
         for a in spec_assertions:
             # Warn about problems
             if a['id'] not in spec_refs:
-                print "Unused spec statement %s" % a['id']
+                print("Unused spec statement %s" % a['id'])
 
             pattern_text = a['text']
 
@@ -529,11 +532,11 @@
                             continue # discard this match
 
                     if id in matched_assertions:
-                        print "Spec statement %s matches multiple places" % id
+                        print("Spec statement %s matches multiple places" % id)
                     matched_assertions[id] = True
 
                     if m.lastindex != 1:
-                        print "Spec statement %s has incorrect number of match groups" % id
+                        print("Spec statement %s has incorrect number of match groups" % id)
 
                     end = m.end(1)
                     end_node = None
@@ -574,7 +577,7 @@
 
         for s in spec_assertions:
             if s['id'] not in matched_assertions:
-                print "Annotation incomplete: Unmatched spec statement %s" % s['id']
+                print("Annotation incomplete: Unmatched spec statement %s" % s['id'])
 
         # Convert from XHTML back to HTML
         doc.documentElement.removeAttribute('xmlns')
diff --git a/2dcontext/tools/spec.yaml b/2dcontext/tools/spec.yaml
index 278dc95..e57bbc8 100644
--- a/2dcontext/tools/spec.yaml
+++ b/2dcontext/tools/spec.yaml
@@ -586,7 +586,7 @@
     text: "If the image argument is <...> an HTMLVideoElement object whose readyState attribute is either HAVE_NOTHING or HAVE_METADATA<^>, then the implementation *must* return without drawing anything."
   - id: 2d.drawImage.zerocanvas
     previously: [ 10, "dw and dh" ]
-    text: "If the image argument is an HTMLCanvasElement object with either a horizontal dimension or a vertical dimension equal to zero, then the implementation *must* return without drawing anything<^>."
+    text: "If the image argument is an HTMLCanvasElement or an OffscreenCanvas object with either a horizontal dimension or a vertical dimension equal to zero, then the implementation *must* throw an INVALID_STATE_ERR exception<^>."
   - id: 2d.drawImage.zerosource
     text: "If one of the sw or sh arguments is zero<^>, the implementation *must* return without drawing anything."
   - id: 2d.drawImage.paint
diff --git a/2dcontext/tools/tests2d.yaml b/2dcontext/tools/tests2d.yaml
index 63ffe9d..f7b858a 100644
--- a/2dcontext/tools/tests2d.yaml
+++ b/2dcontext/tools/tests2d.yaml
@@ -2092,6 +2092,15 @@
   code: |
     var g = ctx.createLinearGradient(0, 0, 100, 0);
     @assert throws SYNTAX_ERR g.addColorStop(0, "");
+    @assert throws SYNTAX_ERR g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)');
+    @assert throws SYNTAX_ERR g.addColorStop(0, 'null');
+    @assert throws SYNTAX_ERR g.addColorStop(0, 'undefined');
+    @assert throws SYNTAX_ERR g.addColorStop(0, null);
+    @assert throws SYNTAX_ERR g.addColorStop(0, undefined);
+
+    var g = ctx.createRadialGradient(0, 0, 0, 100, 0, 0);
+    @assert throws SYNTAX_ERR g.addColorStop(0, "");
+    @assert throws SYNTAX_ERR g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)');
     @assert throws SYNTAX_ERR g.addColorStop(0, 'null');
     @assert throws SYNTAX_ERR g.addColorStop(0, 'undefined');
     @assert throws SYNTAX_ERR g.addColorStop(0, null);
@@ -8993,27 +9002,22 @@
   expected: green
 
 - name: 2d.drawImage.zerocanvas
+  desc: drawImage with zero-sized canvas as the source shoud throw exception
   testing:
     - 2d.drawImage.zerocanvas
   code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-
     var canvas2 = document.createElement('canvas');
     canvas2.width = 0;
-    canvas2.height = 10;
-    ctx.drawImage(canvas2, 0, 0);
+    canvas2.height = 50;
+    @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0);
 
-    canvas2.width = 10;
+    canvas2.width = 50;
     canvas2.height = 0;
-    ctx.drawImage(canvas2, 0, 0);
+    @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0);
 
     canvas2.width = 0;
     canvas2.height = 0;
-    ctx.drawImage(canvas2, 0, 0);
-
-    @assert pixel 50,25 ==~ 0,255,0,255;
-  expected: green
+    @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0);
 
 - name: 2d.drawImage.svg
   desc: drawImage() of an SVG image
diff --git a/BackgroundSync/OWNERS b/BackgroundSync/OWNERS
new file mode 100644
index 0000000..9cc0eff
--- /dev/null
+++ b/BackgroundSync/OWNERS
@@ -0,0 +1 @@
+@beverloo
diff --git a/BackgroundSync/interfaces.any.js b/BackgroundSync/interfaces.any.js
new file mode 100644
index 0000000..207a0d5
--- /dev/null
+++ b/BackgroundSync/interfaces.any.js
@@ -0,0 +1,21 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// https://wicg.github.io/BackgroundSync/spec/
+
+promise_test(async () => {
+  const idl = await fetch('/interfaces/BackgroundSync.idl').then(r => r.text());
+  const sw = await fetch('/interfaces/ServiceWorker.idl').then(r => r.text());
+  const html = await fetch('/interfaces/html.idl').then(r => r.text());
+  const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+
+  const idlArray = new IdlArray();
+  idlArray.add_idls(idl);
+  idlArray.add_dependency_idls(sw);
+  idlArray.add_dependency_idls(html);
+  idlArray.add_dependency_idls(dom);
+  idlArray.test();
+  done();
+}, 'Background Sync interfaces.');
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d085231..427ec68 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -31,4 +31,4 @@
 Documentation
 -------------
 
-See [web-platform-tests.org](http://web-platform-tests.org/).
+See [web-platform-tests.org](https://web-platform-tests.org/).
diff --git a/FileAPI/BlobURL/support/file_test1.js b/FileAPI/BlobURL/support/file_test1.js
deleted file mode 100644
index 3498358..0000000
--- a/FileAPI/BlobURL/support/file_test1.js
+++ /dev/null
@@ -1 +0,0 @@
-var test_result = 'test1_OK';
\ No newline at end of file
diff --git a/FileAPI/BlobURL/support/file_test3.html b/FileAPI/BlobURL/support/file_test3.html
deleted file mode 100644
index fa234cb..0000000
--- a/FileAPI/BlobURL/support/file_test3.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <title>Test file</title>
-    <style>
-        body {
-            margin: 0;
-        }
-        .block {
-            height: 5000px;
-        }
-    </style>
-    <script>
-        window.test_result = 'test3_OK';
-    </script>
-</head>
-<body>
-    <a id="block1"></a>
-    <div class="block"></div>
-
-    <a id="block2"></a>
-    <div class="block"></div>
-</body>
-</html>
diff --git a/FileAPI/BlobURL/test1-manual.html b/FileAPI/BlobURL/test1-manual.html
deleted file mode 100644
index 8da42cf..0000000
--- a/FileAPI/BlobURL/test1-manual.html
+++ /dev/null
@@ -1,122 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <title>Blob and File reference URL Test(1)</title>
-    <link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#convenienceAPI">
-    <link rel=author title="Breezewish" href="mailto:me@breeswish.org">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-    <form name="upload">
-        <input type="file" id="fileChooser">
-    </form>
-
-    <div>
-        <p>Test steps:</p>
-        <ol>
-            <li>Download the <a href="support/file_test1.js">file</a>.</li>
-            <li>Select the file in the file inputbox to run the test.</li>
-        </ol>
-    </div>
-
-    <div id="log"></div>
-
-    <script>
-
-        var fileChooser = document.querySelector('#fileChooser');
-
-        setup({explicit_done: true});
-        setup({explicit_timeout: true});
-
-        //Run the test when user selects a file
-
-        on_event(fileChooser, 'change', function() {
-
-            var testCount = 10000;
-
-            test(function() {
-
-                var list = [], file = fileChooser.files[0];
-
-                for (var i = 0; i <= testCount; i++) {
-                    list.push(window.URL.createObjectURL(file));
-                }
-
-                list.sort();
-
-                for (var i = 0; i < testCount; i++) {
-                    assert_not_equals(list[i], list[i+1], 'generated Blob URL should be unique');
-                }
-
-            }, 'Check whether generated Blob/File URL is unique (Notice: only generate for ' + testCount + ' times)');
-
-
-            async_test(function(t) {
-
-                var url = URL.createObjectURL(fileChooser.files[0]);
-                var expected_file_content = "var test_result = 'test1_OK';";
-
-                var xhr = new XMLHttpRequest();
-                xhr.open('GET', url, true);
-                xhr.onreadystatechange = t.step_func(function() {
-                    switch (xhr.readyState) {
-                    case xhr.DONE:
-                        assert_equals(xhr.status, 200, 'status code should be 200');
-                        assert_equals(xhr.responseText, expected_file_content);
-                        t.done();
-                        return;
-                    }
-                });
-
-                xhr.send();
-
-            }, 'Check whether Blob/File URL could be used in XHR requests and could get expected data');
-
-            async_test(function(t) {
-
-                var url = URL.createObjectURL(fileChooser.files[0]);
-                var expected_run_result = "test1_OK";
-
-                //expected file content:
-                //   var test_result = 'test1_OK';
-
-                var e = document.createElement('script');
-                e.setAttribute('type', 'text/javascript');
-                e.setAttribute('src', url);
-                e.onload = t.step_func_done(function() {
-                    assert_equals(test_result, expected_run_result);
-                });
-
-                document.body.appendChild(e);
-
-            }, 'Check whether Blob/File URL could be used in tags src like <script>');
-
-            async_test(function(t) {
-
-                var url = URL.createObjectURL(fileChooser.files[0]);
-                URL.revokeObjectURL(url);
-
-                var xhr = new XMLHttpRequest();
-                xhr.open('GET', url, true);
-                xhr.onreadystatechange = t.step_func(function() {
-                    switch (xhr.readyState) {
-                    case xhr.DONE:
-                        assert_equals(xhr.status, 500, 'status code should be 500 if Blob URI is revoked.');
-                        t.done();
-                        return;
-                    }
-                });
-
-                xhr.send();
-
-            }, 'Check whether revokeObjectURL works well');
-
-            done();
-
-        });
-
-    </script>
-</body>
-</html>
diff --git a/FileAPI/BlobURL/test3-manual.html b/FileAPI/BlobURL/test3-manual.html
deleted file mode 100644
index ce020a7..0000000
--- a/FileAPI/BlobURL/test3-manual.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <title>Blob and File reference URL Test(3)</title>
-    <link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#convenienceAPI">
-    <link rel=author title="Breezewish" href="mailto:me@breeswish.org">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-    <form name="upload">
-        <input type="file" id="fileChooser">
-    </form>
-
-    <div>
-        <p>Test steps:</p>
-        <ol>
-            <li>Download the <a href="support/file_test3.html">file</a>.</li>
-            <li>Select the file in the file inputbox and the test will start.</li>
-        </ol>
-    </div>
-
-    <div id="log"></div>
-
-    <script>
-
-        var fileChooser = document.querySelector('#fileChooser');
-
-        setup({explicit_done: true});
-        setup({explicit_timeout: true});
-
-        on_event(fileChooser, 'change', function() {
-
-            async_test(function(t) {
-
-                var url = URL.createObjectURL(fileChooser.files[0]);
-
-                var e = document.createElement('iframe');
-                e.setAttribute('src', url);
-                e.setAttribute('style', 'display:none;');
-                document.body.appendChild(e);
-
-                e.contentWindow.document.body.onload = t.step_func_done(function() {
-                    assert_equals(e.contentWindow.test_result, 'test3_OK');
-                });
-
-            }, 'Check whether the iframe content could be accessed when using Blob/File URL in the same origin.');
-
-            async_test(function(t) {
-
-                var url = URL.createObjectURL(fileChooser.files[0]);
-                url += '#block2';
-
-                var e = document.createElement('iframe');
-                e.setAttribute('src', url);
-                document.body.appendChild(e);
-
-                e.contentWindow.document.body.onload = t.step_func_done(function() {
-                    assert_equals(e.contentWindow.scrollY, 5000);
-                });
-
-            }, 'Check whether the Blob/File URL fragment is implemented.');
-
-            done();
-
-        });
-
-    </script>
-</body>
-</html>
diff --git a/FileAPI/FileReader/test_notreadableerrors-manual.html b/FileAPI/FileReader/test_notreadableerrors-manual.html
new file mode 100644
index 0000000..46d7359
--- /dev/null
+++ b/FileAPI/FileReader/test_notreadableerrors-manual.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileReader NotReadableError Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/FileAPI/#dfn-error-codes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form name="upload">
+  <input type="file" id="fileChooser"><br><input type="button" id="start" value="start">
+</form>
+
+<div>
+  <p>Test steps:</p>
+  <ol>
+    <li>Download the <a href="support/file_test1.txt">file</a>.</li>
+    <li>Select the file in the file inputbox.</li>
+    <li>Delete the file's readable permission.</li>
+    <li>Click the 'start' button.</li>
+  </ol>
+</div>
+
+<script>
+
+  const fileChooser = document.querySelector('#fileChooser');
+
+  setup({explicit_done: true});
+  setup({explicit_timeout: true});
+
+  on_event(document.querySelector('#start'), 'click', () => {
+    async_test(t => {
+      const reader = new FileReader();
+      reader.readAsArrayBuffer(fileChooser.files[0]);
+      reader.onloadend = t.step_func_done(event => {
+        assert_equals(event.target.readyState, FileReader.DONE);
+        assert_equals(reader.error.name, "NotReadableError");
+      });
+    }, 'FileReader.error should be NotReadableError if the file is unreadable');
+    done();
+  });
+
+</script>
+
diff --git a/FileAPI/FileReader/test_securityerrors-manual.html b/FileAPI/FileReader/test_securityerrors-manual.html
new file mode 100644
index 0000000..add93ed
--- /dev/null
+++ b/FileAPI/FileReader/test_securityerrors-manual.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileReader SecurityError Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/FileAPI/#dfn-error-codes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<form name="upload">
+  <input type="file" id="fileChooser"><br><input type="button" id="start" value="start">
+</form>
+
+<div>
+  <p>Test steps:</p>
+  <ol>
+    <li>Select a system sensitive file (e.g. files in /usr/bin, password files,
+        and other native operating system executables) in the file inputbox.</li>
+    <li>Click the 'start' button.</li>
+    </ol>
+</div>
+
+<script>
+
+  const fileChooser = document.querySelector('#fileChooser');
+
+  setup({explicit_done: true});
+  setup({explicit_timeout: true});
+
+  on_event(document.querySelector('#start'), 'click', () => {
+    async_test(t => {
+      const reader = new FileReader();
+      reader.readAsArrayBuffer(fileChooser.files[0]);
+      reader.onloadend = t.step_func_done(event => {
+        assert_equals(event.target.readyState, FileReader.DONE);
+        assert_equals(reader.error.name, "SecurityError");
+      });
+    }, 'FileReader.error should be SECURITY_ERROR if the file is a system sensitive file');
+    done();
+  });
+
+</script>
diff --git a/FileAPI/FileReader/workers.html b/FileAPI/FileReader/workers.html
new file mode 100644
index 0000000..8e114ee
--- /dev/null
+++ b/FileAPI/FileReader/workers.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+async_test(t => {
+  function workerCode() {
+    close();
+    var blob = new Blob([123]);
+    var fr = new FileReader();
+    fr.readAsText(blob);
+    fr.abort()
+    fr.readAsArrayBuffer(blob);
+    postMessage(true);
+  }
+
+  var workerBlob = new Blob([workerCode.toString() + ";workerCode();"], {type:"application/javascript"});
+
+  var w = new Worker(URL.createObjectURL(workerBlob));
+  w.onmessage = function(e) {
+    assert_true(e.data, "FileReader created during worker shutdown.");
+    t.done();
+  }
+}, 'FileReader created after a worker self.close()');
+
+</script>
diff --git a/FileAPI/blob/Blob-XHR-revoke.html b/FileAPI/blob/Blob-XHR-revoke.html
deleted file mode 100644
index a38caaf..0000000
--- a/FileAPI/blob/Blob-XHR-revoke.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!doctype html>
-<title>Revoking blob URL used with XMLHttpRequest</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script>
-async_test(function(t) {
-    var blob = new Blob(["test"]);
-    var url = URL.createObjectURL(blob);
-    var xhr = new XMLHttpRequest();
-    xhr.open("GET", url);
-
-    // Revoke the object URL.  XHR should take a reference to the blob as soon as
-    // it receives it in open(), so the request succeeds even though we revoke the
-    // URL before calling send().
-    URL.revokeObjectURL(url);
-
-    xhr.send();
-
-    xhr.onload = t.step_func_done(function() {
-        assert_equals(xhr.response, "test");
-    })
-    xhr.onerror = t.step_func(function() {
-        assert_unreached("Got unexpected error event");
-    })
-}, "Revoke blob URL after open(), will fetch");
-
-async_test(t => {
-  const blob = new Blob(["test"]),
-        blobURL = URL.createObjectURL(blob),
-        client = new XMLHttpRequest
-  URL.revokeObjectURL(blobURL)
-  client.open("GET", blobURL)
-  client.onload = t.step_func(() => assert_unreached("Got unexpected load event"))
-  client.onerror = t.step_func_done()
-  client.send()
-}, "Revoke blob URL before open(), network error (after send())")
-</script>
diff --git a/FileAPI/blob/Blob-constructor-endings.html b/FileAPI/blob/Blob-constructor-endings.html
new file mode 100644
index 0000000..1dee99f
--- /dev/null
+++ b/FileAPI/blob/Blob-constructor-endings.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Blob constructor: endings option</title>
+<link rel=help href="https://w3c.github.io/FileAPI/#constructorBlob">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Windows platforms use CRLF as the native line ending. All others use LF.
+const crlf = navigator.platform.startsWith('Win');
+const native_ending = crlf ? '\r\n' : '\n';
+
+function readBlobAsPromise(blob) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsText(blob);
+    reader.onload = e => resolve(reader.result);
+    reader.onerror = e => reject(reader.error);
+  });
+}
+
+[
+  'transparent',
+  'native'
+].forEach(value => test(t => {
+  assert_class_string(new Blob([], {endings: value}), 'Blob',
+                      `Constructor should allow "${value}" endings`);
+}, `Valid "endings" value: ${JSON.stringify(value)}`));
+
+[
+  null,
+  '',
+  'invalidEnumValue',
+  'Transparent',
+  'NATIVE',
+  0,
+  {}
+].forEach(value => test(t => {
+  assert_throws(new TypeError(), () => new Blob([], {endings: value}),
+                'Blob constructor should throw');
+}, `Invalid "endings" value: ${JSON.stringify(value)}`));
+
+test(t => {
+  const test_error = {name: 'test'};
+  assert_throws(
+    test_error,
+    () => new Blob([], { get endings() { throw test_error; }}),
+    'Blob constructor should propagate exceptions from "endings" property');
+}, 'Exception propagation from options');
+
+test(t => {
+  let got = false;
+  new Blob([], { get endings() { got = true; } });
+  assert_true(got, 'The "endings" property was accessed during construction.');
+}, 'The "endings" options property is used');
+
+[
+  {name: 'LF', input: '\n', native: native_ending},
+  {name: 'CR', input: '\r', native: native_ending},
+
+  {name: 'CRLF', input: '\r\n', native: native_ending},
+  {name: 'CRCR', input: '\r\r', native: native_ending.repeat(2)},
+  {name: 'LFCR', input: '\n\r', native: native_ending.repeat(2)},
+  {name: 'LFLF', input: '\n\n', native: native_ending.repeat(2)},
+
+  {name: 'CRCRLF', input: '\r\r\n', native: native_ending.repeat(2)},
+  {name: 'CRLFLF', input: '\r\n\n', native: native_ending.repeat(2)},
+  {name: 'CRLFCR', input: '\r\n\r\n', native: native_ending.repeat(2)},
+
+  {name: 'CRLFCRLF', input: '\r\n\r\n', native: native_ending.repeat(2)},
+  {name: 'LFCRLFCR', input: '\n\r\n\r', native: native_ending.repeat(3)},
+
+].forEach(testCase => {
+  promise_test(async t => {
+    const blob = new Blob([testCase.input]);
+    assert_equals(
+      await readBlobAsPromise(blob), testCase.input,
+      'Newlines should not change with endings unspecified');
+  }, `Input ${testCase.name} with endings unspecified`);
+
+  promise_test(async t => {
+    const blob = new Blob([testCase.input], {endings: 'transparent'});
+    assert_equals(
+      await readBlobAsPromise(blob), testCase.input,
+      'Newlines should not change with endings "transparent"');
+  }, `Input ${testCase.name} with endings 'transparent'`);
+
+  promise_test(async t => {
+    const blob = new Blob([testCase.input], {endings: 'native'});
+    assert_equals(
+      await readBlobAsPromise(blob), testCase.native,
+      'Newlines should match the platform with endings "native"');
+  }, `Input ${testCase.name} with endings 'native'`);
+});
+
+promise_test(async t => {
+  const blob = new Blob(['\r', '\n'], {endings: 'native'});
+  const expected = native_ending.repeat(2);
+  assert_equals(
+    await readBlobAsPromise(blob), expected,
+    'CR/LF in adjacent strings should be converted to two platform newlines');
+}, `CR/LF in adjacent input strings`);
+
+</script>
diff --git a/FileAPI/blob/Blob-constructor.html b/FileAPI/blob/Blob-constructor.html
index d8375c2..4d39ed7 100644
--- a/FileAPI/blob/Blob-constructor.html
+++ b/FileAPI/blob/Blob-constructor.html
@@ -74,6 +74,20 @@
   type: "",
   desc: "A plain object with @@iterator should be treated as a sequence for the blobParts argument."
 });
+test(t => {
+  const blob = new Blob({
+    [Symbol.iterator]() {
+      var i = 0;
+      return {next: () => [
+        {done:false, value:'ab'},
+        {done:false, value:'cde'},
+        {done:true}
+      ][i++]
+      };
+    }
+  });
+  assert_equals(blob.size, 5, 'Custom @@iterator should be treated as a sequence');
+}, "A plain object with custom @@iterator should be treated as a sequence for the blobParts argument.");
 test_blob(function() {
   return new Blob({
     [Symbol.iterator]: Array.prototype[Symbol.iterator],
@@ -392,26 +406,20 @@
   desc: "Array with mixed types"
 });
 
-// options argument
 test(function() {
-  new Blob([], { endings: "invalidEnumValue" });
-  new Blob([], { endings: null });
-  new Blob([], { endings: undefined });
-  new Blob([], { endings: 0 });
-  new Blob([], { get endings() { assert_unreached("Should not call getter"); } });
-}, "The 'endings' property should be ignored.");
+  const accessed = [];
+  const stringified = [];
 
-test(function() {
-  assert_throws(test_error, function() {
-    new Blob([], {
-      get type() { throw test_error; }
-    });
+  new Blob([], {
+    get type() { accessed.push('type'); },
+    get endings() { accessed.push('endings'); }
   });
-  assert_throws(test_error, function() {
-    new Blob([], {
-      type: { toString: function() { throw test_error; } }
-    });
+  new Blob([], {
+    type: { toString: () => { stringified.push('type'); return ''; } },
+    endings: { toString: () => { stringified.push('endings'); return 'transparent'; } }
   });
+  assert_array_equals(accessed, ['endings', 'type']);
+  assert_array_equals(stringified, ['endings', 'type']);
 }, "options properties should be accessed in lexicographic order.");
 
 test(function() {
@@ -449,19 +457,16 @@
   });
 });
 
-test_blob(function() {
-  return new Blob(["\na\r\nb\n\rc\r"], { endings: "transparent" });
-}, {
-  expected: "\na\r\nb\n\rc\r",
-  type: "",
-  desc: "Newlines should not change when endings is 'transparent'."
-});
-test_blob(function() {
-  return new Blob(["\na\r\nb\n\rc\r"], { endings: "native" });
-}, {
-  expected: "\na\r\nb\n\rc\r",
-  type: "",
-  desc: "Newlines should not change when endings is 'native'."
+[
+  123,
+  123.4,
+  true,
+  'abc'
+].forEach(arg => {
+  test(t => {
+    assert_throws(new TypeError(), () => new Blob([], arg),
+                  'Blob constructor should throw with invalid property bag');
+  }, `Passing ${JSON.stringify(arg)} for options should throw`);
 });
 
 var type_tests = [
@@ -471,6 +476,7 @@
   [[], 'A', 'a'],
   [[], 'text/html', 'text/html'],
   [[], 'TEXT/HTML', 'text/html'],
+  [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'],
   [[], '\u00E5', ''],
   [[], '\uD801\uDC7E', ''], // U+1047E
   [[], ' image/gif ', ' image/gif '],
diff --git a/FileAPI/blob/Blob-slice-overflow.html b/FileAPI/blob/Blob-slice-overflow.html
index 56891af..74cd83a 100644
--- a/FileAPI/blob/Blob-slice-overflow.html
+++ b/FileAPI/blob/Blob-slice-overflow.html
@@ -16,26 +16,26 @@
 
 test(function() {
   var blob = new Blob([text]);
-  var sliceBlob = blob.slice(-1, 2000);
-  assert_equals(sliceBlob.size, 2000-(2000-1), "Bolb slice size");
+  var sliceBlob = blob.slice(-1, blob.size);
+  assert_equals(sliceBlob.size, 1, "Blob slice size");
 }, "slice start is negative, relativeStart will be max((size + start), 0)");
 
 test(function() {
   var blob = new Blob([text]);
-  var sliceBlob = blob.slice(2001, 2000);
-  assert_equals(sliceBlob.size, 0, "Bolb slice size");
+  var sliceBlob = blob.slice(blob.size + 1, blob.size);
+  assert_equals(sliceBlob.size, 0, "Blob slice size");
 }, "slice start is greater than blob size, relativeStart will be min(start, size)");
 
 test(function() {
   var blob = new Blob([text]);
-  var sliceBlob = blob.slice(1998, -1);
-  assert_equals(sliceBlob.size, (2000-1)-1998, "Bolb slice size");
+  var sliceBlob = blob.slice(blob.size - 2, -1);
+  assert_equals(sliceBlob.size, 1, "Blob slice size");
 }, "slice end is negative, relativeEnd will be max((size + end), 0)");
 
 test(function() {
   var blob = new Blob([text]);
-  var sliceBlob = blob.slice(1998, 2999);
-  assert_equals(sliceBlob.size, 2000-1998, "Bolb slice size");
+  var sliceBlob = blob.slice(blob.size - 2, blob.size + 999);
+  assert_equals(sliceBlob.size, 2, "Blob slice size");
 }, "slice end is greater than blob size, relativeEnd will be min(end, size)");
 
 </script>
diff --git a/FileAPI/file/File-constructor-endings.html b/FileAPI/file/File-constructor-endings.html
new file mode 100644
index 0000000..f0f9090
--- /dev/null
+++ b/FileAPI/file/File-constructor-endings.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>File constructor: endings option</title>
+<link rel=help href="https://w3c.github.io/FileAPI/#file-constructor">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Windows platforms use CRLF as the native line ending. All others use LF.
+const crlf = navigator.platform.startsWith('Win');
+const native_ending = crlf ? '\r\n' : '\n';
+
+function readBlobAsPromise(blob) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsText(blob);
+    reader.onload = e => resolve(reader.result);
+    reader.onerror = e => reject(reader.error);
+  });
+}
+
+[
+  'transparent',
+  'native'
+].forEach(value => test(t => {
+  assert_class_string(new File([], "name", {endings: value}), 'File',
+                      `Constructor should allow "${value}" endings`);
+}, `Valid "endings" value: ${JSON.stringify(value)}`));
+
+[
+  null,
+  '',
+  'invalidEnumValue',
+  'Transparent',
+  'NATIVE',
+  0,
+  {}
+].forEach(value => test(t => {
+  assert_throws(new TypeError(), () => new File([], "name", {endings: value}),
+                'File constructor should throw');
+}, `Invalid "endings" value: ${JSON.stringify(value)}`));
+
+test(t => {
+  const test_error = {name: 'test'};
+  assert_throws(
+    test_error,
+    () => new File([], "name", { get endings() { throw test_error; }}),
+    'File constructor should propagate exceptions from "endings" property');
+}, 'Exception propagation from options');
+
+test(t => {
+  let got = false;
+  new File([], "name", { get endings() { got = true; } });
+  assert_true(got, 'The "endings" property was accessed during construction.');
+}, 'The "endings" options property is used');
+
+[
+  {name: 'LF', input: '\n', native: native_ending},
+  {name: 'CR', input: '\r', native: native_ending},
+
+  {name: 'CRLF', input: '\r\n', native: native_ending},
+  {name: 'CRCR', input: '\r\r', native: native_ending.repeat(2)},
+  {name: 'LFCR', input: '\n\r', native: native_ending.repeat(2)},
+  {name: 'LFLF', input: '\n\n', native: native_ending.repeat(2)},
+
+  {name: 'CRCRLF', input: '\r\r\n', native: native_ending.repeat(2)},
+  {name: 'CRLFLF', input: '\r\n\n', native: native_ending.repeat(2)},
+  {name: 'CRLFCR', input: '\r\n\r\n', native: native_ending.repeat(2)},
+
+  {name: 'CRLFCRLF', input: '\r\n\r\n', native: native_ending.repeat(2)},
+  {name: 'LFCRLFCR', input: '\n\r\n\r', native: native_ending.repeat(3)},
+
+].forEach(testCase => {
+  promise_test(async t => {
+    const file = new File([testCase.input], "name");
+    assert_equals(
+      await readBlobAsPromise(file), testCase.input,
+      'Newlines should not change with endings unspecified');
+  }, `Input ${testCase.name} with endings unspecified`);
+
+  promise_test(async t => {
+    const file = new File([testCase.input], "name", {endings: 'transparent'});
+    assert_equals(
+      await readBlobAsPromise(file), testCase.input,
+      'Newlines should not change with endings "transparent"');
+  }, `Input ${testCase.name} with endings 'transparent'`);
+
+  promise_test(async t => {
+    const file = new File([testCase.input], "name", {endings: 'native'});
+    assert_equals(
+      await readBlobAsPromise(file), testCase.native,
+      'Newlines should match the platform with endings "native"');
+  }, `Input ${testCase.name} with endings 'native'`);
+});
+
+promise_test(async t => {
+  const file = new File(['\r', '\n'], "name", {endings: 'native'});
+  const expected = native_ending.repeat(2);
+  assert_equals(
+    await readBlobAsPromise(file), expected,
+    'CR/LF in adjacent strings should be converted to two platform newlines');
+}, `CR/LF in adjacent input strings`);
+
+</script>
diff --git a/FileAPI/file/File-constructor.html b/FileAPI/file/File-constructor.html
index 97c08b6..646ed99 100644
--- a/FileAPI/file/File-constructor.html
+++ b/FileAPI/file/File-constructor.html
@@ -6,10 +6,20 @@
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
+const to_string_obj = { toString: () => 'a string' };
+const to_string_throws = { toString: () => { throw new Error('expected'); } };
+
 test(function() {
   assert_true("File" in window, "window should have a File property.");
 }, "File interface object exists");
 
+test(t => {
+  assert_throws(new TypeError(), () => new File(),
+                'Bits argument is required');
+  assert_throws(new TypeError(), () => new File([]),
+                'Name argument is required');
+}, 'Required arguments');
+
 function test_first_argument(arg1, expectedSize, testName) {
   test(function() {
     var file = new File(arg1, "dummy");
@@ -22,14 +32,48 @@
   }, testName);
 }
 
+test_first_argument([], 0, "empty fileBits");
 test_first_argument(["bits"], 4, "DOMString fileBits");
 test_first_argument(["𝓽𝓮𝔁𝓽"], 16, "Unicode DOMString fileBits");
+test_first_argument([new String('string object')], 13, "String object fileBits");
 test_first_argument([new Blob()], 0, "Empty Blob fileBits");
 test_first_argument([new Blob(["bits"])], 4, "Blob fileBits");
+test_first_argument([new File([], 'world.txt')], 0, "Empty File fileBits");
+test_first_argument([new File(["bits"], 'world.txt')], 4, "File fileBits");
 test_first_argument([new ArrayBuffer(8)], 8, "ArrayBuffer fileBits");
 test_first_argument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4, "Typed array fileBits");
 test_first_argument(["bits", new Blob(["bits"]), new Blob(), new Uint8Array([0x50, 0x41]),
                      new Uint16Array([0x5353]), new Uint32Array([0x53534150])], 16, "Various fileBits");
+test_first_argument([12], 2, "Number in fileBits");
+test_first_argument([[1,2,3]], 5, "Array in fileBits");
+test_first_argument([{}], 15, "Object in fileBits"); // "[object Object]"
+test_first_argument([document.body], 24, "HTMLBodyElement in fileBits"); // "[object HTMLBodyElement]"
+test_first_argument([to_string_obj], 8, "Object with toString in fileBits");
+test_first_argument({[Symbol.iterator]() {
+  let i = 0;
+  return {next: () => [
+    {done:false, value:'ab'},
+    {done:false, value:'cde'},
+    {done:true}
+  ][i++]};
+}}, 5, 'Custom @@iterator');
+
+[
+  'hello',
+  0,
+  null
+].forEach(arg => {
+  test(t => {
+    assert_throws(new TypeError(), () => new File(arg, 'world.html'),
+                  'Constructor should throw for invalid bits argument');
+  }, `Invalid bits argument: ${JSON.stringify(arg)}`);
+});
+
+test(t => {
+  assert_throws(new Error(), () => new File([to_string_throws], 'name.txt'),
+                'Constructor should propagate exceptions');
+}, 'Bits argument: object that throws');
+
 
 function test_second_argument(arg2, expectedFileName, testName) {
   test(function() {
@@ -41,23 +85,29 @@
 
 test_second_argument("dummy", "dummy", "Using fileName");
 test_second_argument("dummy/foo", "dummy:foo", "Using special character in fileName");
+test_second_argument(null, "null", "Using null fileName");
+test_second_argument(1, "1", "Using number fileName");
+test_second_argument('', '', "Using empty string fileName");
+test_second_argument(document.body, '[object HTMLBodyElement]', "Using object fileName");
 
 // testing the third argument
-test(function() {
-  var file = new File(["bits"], "dummy", { type: "text/plain"});
-  assert_true(file instanceof File);
-  assert_equals(file.type, "text/plain");
-}, "Using type on the File constructor");
-test(function() {
-  var file = new File(["bits"], "dummy", { type: "TEXT/PLAIN"});
-  assert_true(file instanceof File);
-  assert_equals(file.type, "text/plain");
-}, "Using uppercase characters in type");
-test(function() {
-  var file = new File(["bits"], "dummy", { type: "𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫"});
-  assert_true(file instanceof File);
-  assert_equals(file.type, "");
-}, "Using illegal character for type");
+[
+  {type: 'text/plain', expected: 'text/plain'},
+  {type: 'text/plain;charset=UTF-8', expected: 'text/plain;charset=utf-8'},
+  {type: 'TEXT/PLAIN', expected: 'text/plain'},
+  {type: '𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫', expected: ''},
+  {type: 'ascii/nonprintable\u001F', expected: ''},
+  {type: 'ascii/nonprintable\u007F', expected: ''},
+  {type: 'nonascii\u00EE', expected: ''},
+  {type: 'nonascii\u1234', expected: ''},
+  {type: 'nonparsable', expected: 'nonparsable'}
+].forEach(testCase => {
+  test(t => {
+    var file = new File(["bits"], "dummy", { type: testCase.type});
+    assert_true(file instanceof File);
+    assert_equals(file.type, testCase.expected);
+  }, `Using type in File constructor: ${testCase.type}`);
+});
 test(function() {
   var file = new File(["bits"], "dummy", { lastModified: 42 });
   assert_true(file instanceof File);
@@ -68,5 +118,41 @@
   assert_true(file instanceof File);
   assert_equals(file.name, "dummy");
 }, "Misusing name");
+test(function() {
+  var file = new File(["bits"], "dummy", { unknownKey: "value" });
+  assert_true(file instanceof File);
+  assert_equals(file.name, "dummy");
+}, "Unknown properties are ignored");
+
+[
+  123,
+  123.4,
+  true,
+  'abc'
+].forEach(arg => {
+  test(t => {
+    assert_throws(new TypeError(), () => new File(['bits'], 'name.txt', arg),
+                  'Constructor should throw for invalid property bag type');
+  }, `Invalid property bag: ${JSON.stringify(arg)}`);
+});
+
+[
+  null,
+  undefined,
+  [1,2,3],
+  /regex/,
+  function() {}
+].forEach(arg => {
+  test(t => {
+    assert_equals(new File(['bits'], 'name.txt', arg).size, 4,
+                  'Constructor should accept object-ish property bag type');
+  }, `Unusual but valid property bag: ${arg}`);
+});
+
+test(t => {
+  assert_throws(new Error(),
+                () => new File(['bits'], 'name.txt', {type: to_string_throws}),
+                'Constructor should propagate exceptions');
+}, 'Property bag propagates exceptions');
 
 </script>
diff --git a/FileAPI/file/send-file-form-iso-2022-jp.tentative.html b/FileAPI/file/send-file-form-iso-2022-jp.tentative.html
new file mode 100644
index 0000000..421de30
--- /dev/null
+++ b/FileAPI/file/send-file-form-iso-2022-jp.tentative.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload files in ISO-2022-JP form (tentative)</title>
+<!--
+    NOTE: This test is tentative because encoding for filename
+    characters unrepresentable in the form charset is not yet
+    standardized.
+  -->
+<link rel="help"
+      href="https://github.com/whatwg/html/issues/3223">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+      href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+      href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+  fileNameSource: 'ASCII',
+  fileBaseName: 'file-for-upload-in-form.txt',
+  formEncoding: 'ISO-2022-JP',
+  expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'x-user-defined',
+  fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+  formEncoding: 'ISO-2022-JP',
+  expectedEncodedBaseName: (
+      'file-for-upload-in-form-&#63472;&#63379;&#63363;&#63392;.txt'),
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'windows-1252',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'ISO-2022-JP',
+  expectedEncodedBaseName: (
+      'file-for-upload-in-form-&#226;&#732;&#186;&#240;&#376;&#732;&#8218;.txt'),
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'JIS X 0201 and JIS X 0208',
+  fileBaseName: 'file-for-upload-in-form-★星★.txt',
+  formEncoding: 'ISO-2022-JP',
+  expectedEncodedBaseName: 'file-for-upload-in-form-\x1B$B!z@1!z\x1B(B.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'ISO-2022-JP',
+  expectedEncodedBaseName: 'file-for-upload-in-form-&#9786;&#128514;.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+  formEncoding: 'ISO-2022-JP',
+  expectedEncodedBaseName: `file-for-upload-in-form-${
+      kTestFallbackIso2022jp
+  }.txt`,
+});
+
+</script>
diff --git a/FileAPI/file/send-file-form-utf-8.html b/FileAPI/file/send-file-form-utf-8.html
new file mode 100644
index 0000000..03417ba
--- /dev/null
+++ b/FileAPI/file/send-file-form-utf-8.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload files in UTF-8 form</title>
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+      href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+      href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+  fileNameSource: 'ASCII',
+  fileBaseName: 'file-for-upload-in-form.txt',
+  formEncoding: 'UTF-8',
+  expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'x-user-defined',
+  fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+  formEncoding: 'UTF-8',
+  expectedEncodedBaseName: (
+      'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt'),
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'windows-1252',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'UTF-8',
+  expectedEncodedBaseName: 'file-for-upload-in-form-☺😂.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'JIS X 0201 and JIS X 0208',
+  fileBaseName: 'file-for-upload-in-form-★星★.txt',
+  formEncoding: 'UTF-8',
+  expectedEncodedBaseName: 'file-for-upload-in-form-★星★.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'UTF-8',
+  expectedEncodedBaseName: 'file-for-upload-in-form-☺😂.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+  formEncoding: 'UTF-8',
+  expectedEncodedBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+});
+
+</script>
diff --git a/FileAPI/file/send-file-form-windows-1252.tentative.html b/FileAPI/file/send-file-form-windows-1252.tentative.html
new file mode 100644
index 0000000..8e9463f
--- /dev/null
+++ b/FileAPI/file/send-file-form-windows-1252.tentative.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload files in Windows-1252 form (tentative)</title>
+<!--
+    NOTE: This test is tentative because encoding for filename
+    characters unrepresentable in the form charset is not yet
+    standardized.
+  -->
+<link rel="help"
+      href="https://github.com/whatwg/html/issues/3223">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+      href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+      href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+  fileNameSource: 'ASCII',
+  fileBaseName: 'file-for-upload-in-form.txt',
+  formEncoding: 'windows-1252',
+  expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'x-user-defined',
+  fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+  formEncoding: 'windows-1252',
+  expectedEncodedBaseName: (
+      'file-for-upload-in-form-&#63472;&#63379;&#63363;&#63392;.txt'),
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'windows-1252',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'windows-1252',
+  expectedEncodedBaseName: 'file-for-upload-in-form-☺😂.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'JIS X 0201 and JIS X 0208',
+  fileBaseName: 'file-for-upload-in-form-★星★.txt',
+  formEncoding: 'windows-1252',
+  expectedEncodedBaseName: 'file-for-upload-in-form-&#9733;&#26143;&#9733;.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'windows-1252',
+  expectedEncodedBaseName: 'file-for-upload-in-form-&#9786;&#128514;.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+  formEncoding: 'windows-1252',
+  expectedEncodedBaseName: `file-for-upload-in-form-${
+      kTestFallbackWindows1252
+  }.txt`,
+});
+
+</script>
diff --git a/FileAPI/file/send-file-form-x-user-defined.tentative.html b/FileAPI/file/send-file-form-x-user-defined.tentative.html
new file mode 100644
index 0000000..072e3bb
--- /dev/null
+++ b/FileAPI/file/send-file-form-x-user-defined.tentative.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload files in x-user-defined form (tentative)</title>
+<!--
+    NOTE: This test is tentative because encoding for filename
+    characters unrepresentable in the form charset is not yet
+    standardized.
+  -->
+<link rel="help"
+      href="https://github.com/whatwg/html/issues/3223">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+      href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+      href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+  fileNameSource: 'ASCII',
+  fileBaseName: 'file-for-upload-in-form.txt',
+  formEncoding: 'x-user-defined',
+  expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'x-user-defined',
+  fileBaseName: 'file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt',
+  formEncoding: 'x-user-defined',
+  expectedEncodedBaseName: 'file-for-upload-in-form-𓃠.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'windows-1252',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'x-user-defined',
+  expectedEncodedBaseName: ('file-for-upload-in-form-' +
+                            '&#226;&#732;&#186;&#240;&#376;&#732;&#8218;.txt'),
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'JIS X 0201 and JIS X 0208',
+  fileBaseName: 'file-for-upload-in-form-★星★.txt',
+  formEncoding: 'x-user-defined',
+  expectedEncodedBaseName: 'file-for-upload-in-form-&#9733;&#26143;&#9733;.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: 'file-for-upload-in-form-☺😂.txt',
+  formEncoding: 'x-user-defined',
+  expectedEncodedBaseName: 'file-for-upload-in-form-&#9786;&#128514;.txt',
+});
+
+formPostFileUploadTest({
+  fileNameSource: 'Unicode',
+  fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
+  formEncoding: 'x-user-defined',
+  expectedEncodedBaseName: `file-for-upload-in-form-${
+      kTestFallbackXUserDefined
+  }.txt`,
+});
+
+</script>
diff --git a/FileAPI/file/send-file-form.html b/FileAPI/file/send-file-form.html
new file mode 100644
index 0000000..baa8d42
--- /dev/null
+++ b/FileAPI/file/send-file-form.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Upload ASCII-named file in UTF-8 form</title>
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data">
+<link rel="help"
+      href="https://html.spec.whatwg.org/multipage/dnd.html#datatransferitemlist">
+<link rel="help"
+      href="https://w3c.github.io/FileAPI/#file-constructor">
+<link rel="author" title="Benjamin C. Wiley Sittler"
+      href="mailto:bsittler@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/send-file-form-helper.js"></script>
+<script>
+'use strict';
+
+formPostFileUploadTest({
+  fileNameSource: 'ASCII',
+  fileBaseName: 'file-for-upload-in-form.txt',
+  formEncoding: 'UTF-8',
+  expectedEncodedBaseName: 'file-for-upload-in-form.txt',
+});
+
+</script>
diff --git a/FileAPI/historical.https.html b/FileAPI/historical.https.html
index 9d78b56..4f841f1 100644
--- a/FileAPI/historical.https.html
+++ b/FileAPI/historical.https.html
@@ -10,17 +10,20 @@
  <body>
   <div id="log"></div>
   <script>
-    test(function() {
-        assert_false('toNativeLineEndings' in window);
-    }, '"toNativeLineEndings" should not be supported');
+    var removedFromWindow = [
+        'toNativeLineEndings',
+        'FileError',
+        'FileException',
+        'FileHandle',
+        'FileRequest',
+        'MutableFile',
+    ];
 
-    test(function() {
-        assert_false('FileError' in window);
-    }, '"FileError" should not be supported');
-
-    test(function() {
-        assert_false('FileException' in window);
-    }, '"FileException" should not be supported');
+    removedFromWindow.forEach(function(name) {
+        test(function() {
+            assert_false(name in window);
+        }, '"' + name + '" should not be supported');
+    });
 
     test(function() {
         var b = new Blob();
@@ -50,9 +53,13 @@
         assert_false('isClosed' in Blob.prototype, 'isClosed in Blob.prototype');
     }, 'Blob.close() should not be supported');
 
-    // Only add service worker test if service workers are actually supported.
-    if (navigator.serviceWorker)
-      service_worker_test('support/historical-serviceworker.js', 'Service worker test setup');
+    test(() => {
+      const f = new File([], "");
+      assert_false("lastModifiedDate" in f);
+      assert_false("lastModifiedDate" in File.prototype);
+    }, "File's lastModifiedDate should not be supported");
+
+    service_worker_test('support/historical-serviceworker.js', 'Service worker test setup');
   </script>
  </body>
 </html>
diff --git a/FileAPI/idlharness.html b/FileAPI/idlharness.html
index b88a752..0cd604c 100644
--- a/FileAPI/idlharness.html
+++ b/FileAPI/idlharness.html
@@ -25,13 +25,11 @@
     var idl_array = new IdlArray();
 
     var request = new XMLHttpRequest();
-    request.open("GET", "idlharness.idl");
+    request.open("GET", "/interfaces/FileAPI.idl");
     request.send();
     request.onload = function() {
         var idls = request.responseText;
 
-        idl_array.add_untested_idls("[PrimaryGlobal] interface Window { };");
-
         idl_array.add_untested_idls("[Exposed=(Window,Worker)] interface ArrayBuffer {};");
         idl_array.add_untested_idls("interface URL {};");
         idl_array.add_untested_idls("[Exposed=(Window,Worker)] interface EventTarget {};");
diff --git a/FileAPI/idlharness.idl b/FileAPI/idlharness.idl
deleted file mode 100644
index 64aa0d6..0000000
--- a/FileAPI/idlharness.idl
+++ /dev/null
@@ -1,90 +0,0 @@
-// https://w3c.github.io/FileAPI/#idl-index
-
-[Constructor(optional sequence<BlobPart> blobParts, optional BlobPropertyBag options),
-Exposed=(Window,Worker)]
-interface Blob {
-
-  readonly attribute unsigned long long size;
-  readonly attribute DOMString type;
-
-  //slice Blob into byte-ranged chunks
-
-  Blob slice([Clamp] optional long long start,
-            [Clamp] optional long long end,
-            optional DOMString contentType);
-};
-
-dictionary BlobPropertyBag {
-  DOMString type = "";
-};
-
-typedef (BufferSource or Blob or USVString) BlobPart;
-
-[Constructor(sequence<BlobPart> fileBits,
-            [EnsureUTF16] DOMString fileName,
-            optional FilePropertyBag options),
-Exposed=(Window,Worker)]
-interface File : Blob {
-  readonly attribute DOMString name;
-  readonly attribute long long lastModified;
-};
-
-dictionary FilePropertyBag : BlobPropertyBag {
-  long long lastModified;
-};
-
-[Exposed=(Window,Worker)]
-interface FileList {
-  getter File? item(unsigned long index);
-  readonly attribute unsigned long length;
-};
-
-[Constructor, Exposed=(Window,Worker)]
-interface FileReader: EventTarget {
-
-  // async read methods
-  void readAsArrayBuffer(Blob blob);
-  void readAsBinaryString(Blob blob);
-  void readAsText(Blob blob, optional DOMString label);
-  void readAsDataURL(Blob blob);
-
-  void abort();
-
-  // states
-  const unsigned short EMPTY = 0;
-  const unsigned short LOADING = 1;
-  const unsigned short DONE = 2;
-
-
-  readonly attribute unsigned short readyState;
-
-  // File or Blob data
-  readonly attribute (DOMString or ArrayBuffer)? result;
-
-  readonly attribute DOMException? error;
-
-  // event handler content attributes
-  attribute EventHandler onloadstart;
-  attribute EventHandler onprogress;
-  attribute EventHandler onload;
-  attribute EventHandler onabort;
-  attribute EventHandler onerror;
-  attribute EventHandler onloadend;
-
-};
-
-[Constructor, Exposed=Worker]
-interface FileReaderSync {
-  // Synchronously return strings
-
-  ArrayBuffer readAsArrayBuffer(Blob blob);
-  DOMString readAsBinaryString(Blob blob);
-  DOMString readAsText(Blob blob, optional DOMString label);
-  DOMString readAsDataURL(Blob blob);
-};
-
-[Exposed=(Window,DedicatedWorker,SharedWorker)]
-partial interface URL {
-  static DOMString createObjectURL(Blob blob);
-  static void revokeObjectURL(DOMString url);
-};
diff --git a/FileAPI/idlharness.worker.js b/FileAPI/idlharness.worker.js
index 138325b..fdf208d 100644
--- a/FileAPI/idlharness.worker.js
+++ b/FileAPI/idlharness.worker.js
@@ -2,7 +2,7 @@
 importScripts("/resources/WebIDLParser.js", "/resources/idlharness.js");
 
 var request = new XMLHttpRequest();
-request.open("GET", "idlharness.idl");
+request.open("GET", "/interfaces/FileAPI.idl");
 request.send();
 request.onload = function() {
     var idl_array = new IdlArray();
diff --git a/FileAPI/reading-data-section/FileReader-multiple-reads.html b/FileAPI/reading-data-section/FileReader-multiple-reads.html
index ca04f3c..310fa85 100644
--- a/FileAPI/reading-data-section/FileReader-multiple-reads.html
+++ b/FileAPI/reading-data-section/FileReader-multiple-reads.html
@@ -70,4 +70,20 @@
   reader.readAsArrayBuffer(blob_1)
   assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
 }, 'test FileReader no InvalidStateError exception in loadend event handler for readAsArrayBuffer');
+
+async_test(function() {
+  var blob_1 = new Blob([new Uint8Array(0x414141)]);
+  var blob_2 = new Blob(['TEST000000002']);
+  var reader = new FileReader();
+  reader.onloadstart = this.step_func(function() {
+    reader.abort();
+    reader.onloadstart = null;
+    reader.onloadend = this.step_func_done(function() {
+      assert_equals('TEST000000002', reader.result);
+    });
+    reader.readAsText(blob_2);
+  });
+  reader.readAsText(blob_1);
+}, 'test abort and restart in onloadstart event for readAsText');
+
 </script>
diff --git a/FileAPI/reading-data-section/filereader_readAsBinaryString.html b/FileAPI/reading-data-section/filereader_readAsBinaryString.html
new file mode 100644
index 0000000..b550e4d
--- /dev/null
+++ b/FileAPI/reading-data-section/filereader_readAsBinaryString.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileAPI Test: filereader_readAsBinaryString</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/FileAPI/#readAsBinaryString">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+async_test(t => {
+  const blob = new Blob(["σ"]);
+  const reader = new FileReader();
+
+  reader.onload = t.step_func_done(() => {
+    assert_equals(typeof reader.result, "string", "The result is string");
+    assert_equals(reader.result.length, 2, "The result length is 2");
+    assert_equals(reader.result, "\xcf\x83", "The result is \xcf\x83");
+    assert_equals(reader.readyState, reader.DONE);
+  });
+
+  reader.onloadstart = t.step_func(() => {
+    assert_equals(reader.readyState, reader.LOADING);
+  });
+
+  reader.onprogress = t.step_func(() => {
+    assert_equals(reader.readyState, reader.LOADING);
+  });
+
+  reader.readAsBinaryString(blob);
+});
+
+</script>
diff --git a/FileAPI/support/send-file-form-helper.js b/FileAPI/support/send-file-form-helper.js
new file mode 100644
index 0000000..a7522c7
--- /dev/null
+++ b/FileAPI/support/send-file-form-helper.js
@@ -0,0 +1,249 @@
+'use strict';
+
+// Rationale for this particular test character sequence, which is
+// used in filenames and also in file contents:
+//
+// - ABC~ ensures the string starts with something we can read to
+//   ensure it is from the correct source; ~ is used because even
+//   some 1-byte otherwise-ASCII-like parts of ISO-2022-JP
+//   interpret it differently.
+// - ‾¥ are inside a single-byte range of ISO-2022-JP and help
+//   diagnose problems due to filesystem encoding or locale
+// - ≈ is inside IBM437 and helps diagnose problems due to filesystem
+//   encoding or locale
+// - ¤ is inside Latin-1 and helps diagnose problems due to
+//   filesystem encoding or locale; it is also the "simplest" case
+//   needing substitution in ISO-2022-JP
+// - ・ is inside a single-byte range of ISO-2022-JP in some variants
+//   and helps diagnose problems due to filesystem encoding or locale;
+//   on the web it is distinct when decoding but unified when encoding
+// - ・ is inside a double-byte range of ISO-2022-JP and helps
+//   diagnose problems due to filesystem encoding or locale
+// - • is inside Windows-1252 and helps diagnose problems due to
+//   filesystem encoding or locale and also ensures these aren't
+//   accidentally turned into e.g. control codes
+// - ∙ is inside IBM437 and helps diagnose problems due to filesystem
+//   encoding or locale
+// - · is inside Latin-1 and helps diagnose problems due to
+//   filesystem encoding or locale and also ensures HTML named
+//   character references (e.g. &middot;) are not used
+// - ☼ is inside IBM437 shadowing C0 and helps diagnose problems due to
+//   filesystem encoding or locale and also ensures these aren't
+//   accidentally turned into e.g. control codes
+// - ★ is inside ISO-2022-JP on a non-Kanji page and makes correct
+//   output easier to spot
+// - 星 is inside ISO-2022-JP on a Kanji page and makes correct
+//   output easier to spot
+// - 🌟 is outside the BMP and makes incorrect surrogate pair
+//   substitution detectable and ensures substitutions work
+//   correctly immediately after Kanji 2-byte ISO-2022-JP
+// - 星 repeated here ensures the correct codec state is used
+//   after a non-BMP substitution
+// - ★ repeated here also makes correct output easier to spot
+// - ☼ is inside IBM437 shadowing C0 and helps diagnose problems due to
+//   filesystem encoding or locale and also ensures these aren't
+//   accidentally turned into e.g. control codes and also ensures
+//   substitutions work correctly immediately after non-Kanji
+//   2-byte ISO-2022-JP
+// - · is inside Latin-1 and helps diagnose problems due to
+//   filesystem encoding or locale and also ensures HTML named
+//   character references (e.g. &middot;) are not used
+// - ∙ is inside IBM437 and helps diagnose problems due to filesystem
+//   encoding or locale
+// - • is inside Windows-1252 and again helps diagnose problems
+//   due to filesystem encoding or locale
+// - ・ is inside a double-byte range of ISO-2022-JP and helps
+//   diagnose problems due to filesystem encoding or locale
+// - ・ is inside a single-byte range of ISO-2022-JP in some variants
+//   and helps diagnose problems due to filesystem encoding or locale;
+//   on the web it is distinct when decoding but unified when encoding
+// - ¤ is inside Latin-1 and helps diagnose problems due to
+//   filesystem encoding or locale; again it is a "simple"
+//   substitution case
+// - ≈ is inside IBM437 and helps diagnose problems due to filesystem
+//   encoding or locale
+// - ¥‾ are inside a single-byte range of ISO-2022-JP and help
+//   diagnose problems due to filesystem encoding or locale
+// - ~XYZ ensures earlier errors don't lead to misencoding of
+//   simple ASCII
+//
+// Overall the near-symmetry makes common I18N mistakes like
+// off-by-1-after-non-BMP easier to spot. All the characters
+// are also allowed in Windows Unicode filenames.
+const kTestChars = 'ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ';
+
+// NOTE: The expected interpretation of ISO-2022-JP according to
+// https://encoding.spec.whatwg.org/#iso-2022-jp-encoder unifies
+// single-byte and double-byte katakana.
+const kTestFallbackIso2022jp =
+      ('ABC~\x1B(J~\\≈¤\x1B$B!&!&\x1B(B•∙·☼\x1B$B!z@1\x1B(B🌟' +
+       '\x1B$B@1!z\x1B(B☼·∙•\x1B$B!&!&\x1B(B¤≈\x1B(J\\~\x1B(B~XYZ').replace(
+             /[^\0-\x7F]/gu,
+           x => `&#${x.codePointAt(0)};`);
+
+// NOTE: \uFFFD is used here to replace Windows-1252 bytes to match
+// how we will see them in the reflected POST bytes in a frame using
+// UTF-8 byte interpretation. The bytes will actually be intact, but
+// this code cannot tell and does not really care.
+const kTestFallbackWindows1252 =
+      'ABC~‾\xA5≈\xA4・・\x95∙\xB7☼★星🌟星★☼\xB7∙\x95・・\xA4≈\xA5‾~XYZ'.replace(
+            /[^\0-\xFF]/gu,
+          x => `&#${x.codePointAt(0)};`).replace(/[\x80-\xFF]/g, '\uFFFD');
+
+const kTestFallbackXUserDefined =
+      kTestChars.replace(/[^\0-\x7F]/gu, x => `&#${x.codePointAt(0)};`);
+
+// formPostFileUploadTest - verifies multipart upload structure and
+// numeric character reference replacement for filenames, field names,
+// and field values.
+//
+// Uses /fetch/api/resources/echo-content.py to echo the upload
+// POST with UTF-8 byte interpretation, leading to the "UTF-8 goggles"
+// behavior documented below for expectedEncodedBaseName when non-
+// UTF-8-compatible byte sequences appear in the formEncoding-encoded
+// uploaded data.
+//
+// Fields in the parameter object:
+//
+// - fileNameSource: purely explanatory and gives a clue about which
+//   character encoding is the source for the non-7-bit-ASCII parts of
+//   the fileBaseName, or Unicode if no smaller-than-Unicode source
+//   contains all the characters. Used in the test name.
+// - fileBaseName: the not-necessarily-just-7-bit-ASCII file basename
+//   used for the constructed test file. Used in the test name.
+// - formEncoding: the acceptCharset of the form used to submit the
+//   test file. Used in the test name.
+// - expectedEncodedBaseName: the expected formEncoding-encoded
+//   version of fileBaseName with unencodable characters replaced by
+//   numeric character references and non-7-bit-ASCII bytes seen
+//   through UTF-8 goggles; subsequences not interpretable as UTF-8
+//   have each byte represented here by \uFFFD REPLACEMENT CHARACTER.
+const formPostFileUploadTest = ({
+  fileNameSource,
+  fileBaseName,
+  formEncoding,
+  expectedEncodedBaseName,
+}) => {
+  promise_test(async testCase => {
+
+    if (document.readyState !== 'complete') {
+      await new Promise(resolve => addEventListener('load', resolve));
+    }
+
+    const formTargetFrame = Object.assign(document.createElement('iframe'), {
+      name: 'formtargetframe',
+    });
+    document.body.append(formTargetFrame);
+    testCase.add_cleanup(() => {
+      document.body.removeChild(formTargetFrame);
+    });
+
+    const form = Object.assign(document.createElement('form'), {
+      acceptCharset: formEncoding,
+      action: '/fetch/api/resources/echo-content.py',
+      method: 'POST',
+      enctype: 'multipart/form-data',
+      target: formTargetFrame.name,
+    });
+    document.body.append(form);
+    testCase.add_cleanup(() => {
+      document.body.removeChild(form);
+    });
+
+    // Used to verify that the browser agrees with the test about
+    // which form charset is used.
+    form.append(Object.assign(document.createElement('input'), {
+      type: 'hidden',
+      name: '_charset_',
+    }));
+
+    // Used to verify that the browser agrees with the test about
+    // field value replacement and encoding independently of file system
+    // idiosyncracies.
+    form.append(Object.assign(document.createElement('input'), {
+      type: 'hidden',
+      name: 'filename',
+      value: fileBaseName,
+    }));
+
+    // Same, but with name and value reversed to ensure field names
+    // get the same treatment.
+    form.append(Object.assign(document.createElement('input'), {
+      type: 'hidden',
+      name: fileBaseName,
+      value: 'filename',
+    }));
+
+    const fileInput = Object.assign(document.createElement('input'), {
+      type: 'file',
+      name: 'file',
+    });
+    form.append(fileInput);
+
+    // Removes c:\fakepath\ or other pseudofolder and returns just the
+    // final component of filePath; allows both / and \ as segment
+    // delimiters.
+    const baseNameOfFilePath = filePath => filePath.split(/[\/\\]/).pop();
+    await new Promise(resolve => {
+      const dataTransfer = new DataTransfer;
+      dataTransfer.items.add(
+          new File([kTestChars], fileBaseName, {type: 'text/plain'}));
+      fileInput.files = dataTransfer.files;
+      // For historical reasons .value will be prefixed with
+      // c:\fakepath\, but the basename should match the file name
+      // exposed through the newer .files[0].name API. This check
+      // verifies that assumption.
+      assert_equals(
+          fileInput.files[0].name,
+          baseNameOfFilePath(fileInput.value),
+          `The basename of the field's value should match its files[0].name`);
+      form.submit();
+      formTargetFrame.onload = resolve;
+    });
+
+    const formDataText = formTargetFrame.contentDocument.body.textContent;
+    const formDataLines = formDataText.split('\n');
+    if (formDataLines.length && !formDataLines[formDataLines.length - 1]) {
+      --formDataLines.length;
+    }
+    assert_greater_than(
+        formDataLines.length,
+        2,
+        `${fileBaseName}: multipart form data must have at least 3 lines: ${
+             JSON.stringify(formDataText)
+           }`);
+    const boundary = formDataLines[0];
+    assert_equals(
+        formDataLines[formDataLines.length - 1],
+        boundary + '--',
+        `${fileBaseName}: multipart form data must end with ${boundary}--: ${
+             JSON.stringify(formDataText)
+           }`);
+    const expectedText = [
+      boundary,
+      'Content-Disposition: form-data; name="_charset_"',
+      '',
+      formEncoding,
+      boundary,
+      'Content-Disposition: form-data; name="filename"',
+      '',
+      expectedEncodedBaseName,
+      boundary,
+      `Content-Disposition: form-data; name="${expectedEncodedBaseName}"`,
+      '',
+      'filename',
+      boundary,
+      `Content-Disposition: form-data; name="file"; ` +
+          `filename="${expectedEncodedBaseName}"`,
+      'Content-Type: text/plain',
+      '',
+      kTestChars,
+      boundary + '--',
+    ].join('\n');
+    assert_true(
+        formDataText.startsWith(expectedText),
+        `Unexpected multipart-shaped form data received:\n${
+             formDataText
+           }\nExpected:\n${expectedText}`);
+  }, `Upload ${fileBaseName} (${fileNameSource}) in ${formEncoding} form`);
+};
diff --git a/FileAPI/unicode.html b/FileAPI/unicode.html
new file mode 100644
index 0000000..ce3e357
--- /dev/null
+++ b/FileAPI/unicode.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Blob/Unicode interaction: normalization and encoding</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+const OMICRON_WITH_OXIA = '\u1F79'; // NFC normalized to U+3CC
+const CONTAINS_UNPAIRED_SURROGATES = 'abc\uDC00def\uD800ghi';
+const REPLACED = 'abc\uFFFDdef\uFFFDghi';
+
+function readBlobAsPromise(blob) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsText(blob);
+    reader.onload = () => resolve(reader.result);
+    reader.onerror = () => reject(reader.error);
+  });
+}
+
+promise_test(async t => {
+  const blob = new Blob([OMICRON_WITH_OXIA]);
+  const result = await readBlobAsPromise(blob);
+  assert_equals(result, OMICRON_WITH_OXIA, 'String should not be normalized');
+}, 'Test that strings are not NFC normalized by Blob constructor');
+
+promise_test(async t => {
+  const file = new File([OMICRON_WITH_OXIA], 'name');
+  const result = await readBlobAsPromise(file);
+  assert_equals(result, OMICRON_WITH_OXIA, 'String should not be normalized');
+}, 'Test that strings are not NFC normalized by File constructor');
+
+promise_test(async t => {
+  const blob = new Blob([CONTAINS_UNPAIRED_SURROGATES]);
+  const result = await readBlobAsPromise(blob);
+  assert_equals(result, REPLACED, 'Unpaired surrogates should be replaced.');
+}, 'Test that unpaired surrogates are replaced by Blob constructor');
+
+promise_test(async t => {
+  const file = new File([CONTAINS_UNPAIRED_SURROGATES], 'name');
+  const result = await readBlobAsPromise(file);
+  assert_equals(result, REPLACED, 'Unpaired surrogates should be replaced.');
+}, 'Test that unpaired surrogates are replaced by File constructor');
+
+</script>
diff --git a/FileAPI/url/blob-url-in-sandboxed-iframe.html b/FileAPI/url/blob-url-in-sandboxed-iframe.html
deleted file mode 100644
index 7d03249..0000000
--- a/FileAPI/url/blob-url-in-sandboxed-iframe.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>FileAPI Test: Creating Blob URL with Blob</title>
-<link rel="author" title="Victor Costan" href="mailto:pwnall@chromium.org">
-<link rel="help" href="https://w3c.github.io/FileAPI/#originOfBlobURL">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#concept-origin">
-<link rel="help" href="https://url.spec.whatwg.org/#url-parsing">
-<link rel="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<style>
-iframe { width: 10px; height: 10px; }
-</style>
-
-<iframe id="unconstrained-iframe"></iframe>
-<iframe id="sandboxed-iframe" sandbox="allow-scripts"></iframe>
-
-<script id="iframe-srcdoc" language="text/html">
-<!doctype html>
-<script>
-'use strict';
-
-window.onload = () => {
-  const blob = new Blob(['Hello world!']);
-  const blobUrl = URL.createObjectURL(blob);
-
-  fetch(blobUrl).then(response => response.text()).then(text => {
-    window.parent.postMessage({ blobUrl, text }, '*');
-  });
-};
-// The script tag is closed in readBlobFromUrl().
-</script>
-
-<script>
-
-// Carries out the test of minting a Blob URL in an iframe, and reading it back.
-//
-// Returns a promise resolved with an object with properties blobUrl and text
-// (the text read back from the Blob URL).
-function readBlobFromUrl(t, iframeSelector) {
-  return new Promise((resolve, reject) => {
-    window.onmessage = t.step_func((message) => { resolve(message.data); });
-
-    const frame = document.querySelector(iframeSelector);
-    const html = document.querySelector('#iframe-srcdoc').textContent +
-        '<' + '/script>';
-    frame.setAttribute('srcdoc', html);
-  });
-}
-
-promise_test(t => readBlobFromUrl(t, '#unconstrained-iframe').then(data => {
-  assert_true(data.blobUrl.startsWith('blob:'),
-      "The Blob's URL should use the blob: scheme");
-  assert_equals(data.text, 'Hello world!',
-      "The result of reading the Blob's URL should be the Blob's contents");
-}), 'reading a Blob URL in an unconstrained iframe');
-
-promise_test(t => readBlobFromUrl(t, '#sandboxed-iframe').then(data => {
-  assert_true(data.blobUrl.startsWith('blob:'),
-      "The Blob's URL should use the blob: scheme");
-  assert_equals(data.text, 'Hello world!',
-      "The result of reading the Blob's URL should be the Blob's contents");
-}), 'reading a Blob URL in a sandboxed iframe without the same-origin flag');
-
-</script>
diff --git a/FileAPI/url/cross-global-revoke.sub.html b/FileAPI/url/cross-global-revoke.sub.html
new file mode 100644
index 0000000..f39a8be
--- /dev/null
+++ b/FileAPI/url/cross-global-revoke.sub.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+async_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+  const frame = document.createElement('iframe');
+  frame.setAttribute('style', 'display:none;');
+  frame.src = 'resources/revoke-helper.html';
+  document.body.appendChild(frame);
+
+  frame.onload = t.step_func(e => {
+    frame.contentWindow.postMessage({url: url}, '*');
+  });
+
+  self.addEventListener('message', t.step_func(e => {
+    if (e.source !== frame.contentWindow) return;
+    assert_equals(e.data, 'revoked');
+    promise_rejects(t, new TypeError, fetch(url)).then(t.step_func_done());
+  }));
+}, 'It is possible to revoke same-origin blob URLs from different frames.');
+
+async_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+  const worker = new Worker('resources/revoke-helper.js');
+  worker.onmessage = t.step_func(e => {
+    assert_equals(e.data, 'revoked');
+    promise_rejects(t, new TypeError, fetch(url)).then(t.step_func_done());
+  });
+  worker.postMessage({url: url});
+}, 'It is possible to revoke same-origin blob URLs from a different worker global.');
+
+async_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+  const frame = document.createElement('iframe');
+  frame.setAttribute('style', 'display:none;');
+  frame.src = '//{{domains[www1]}}:{{location[port]}}/FileAPI/url/resources/revoke-helper.html';
+  document.body.appendChild(frame);
+
+  frame.onload = t.step_func(e => {
+    frame.contentWindow.postMessage({url: url}, '*');
+  });
+
+  self.addEventListener('message', t.step_func(e => {
+    if (e.source !== frame.contentWindow) return;
+    assert_equals(e.data, 'revoked');
+    fetch(url).then(response => response.text()).then(t.step_func_done(text => {
+      assert_equals(text, blob_contents);
+    }), t.unreached_func('Unexpected promise rejection'));
+  }));
+}, 'It is not possible to revoke cross-origin blob URLs.');
+
+</script>
\ No newline at end of file
diff --git a/FileAPI/url/origin.sub.html b/FileAPI/url/origin.sub.html
deleted file mode 100644
index 56c8fa4..0000000
--- a/FileAPI/url/origin.sub.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>FileAPI Test: Verify origin of Blob URL</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-test(t => {
-  const blob = new Blob(["Test Blob"]);
-  const url = URL.createObjectURL(blob);
-  assert_equals(new URL(url).origin, location.origin);
-  assert_true(url.includes(location.origin));
-  assert_true(url.startsWith('blob:{{location[scheme]}}://'));
-}, 'Verify origin of Blob URI matches our origin');
-
-async_test(t => {
-  const frame = document.createElement('iframe');
-  self.addEventListener('message', t.step_func(e => {
-      if (e.source != frame.contentWindow) return;
-      const url = e.data.url;
-      assert_false(url.includes('天気の良い日'),
-          'Origin should be ascii rather than unicode');
-      assert_equals(new URL(url).origin, e.origin,
-          'Origin of URL should match origin of frame');
-      assert_true(url.startsWith('blob:{{location[scheme]}}://xn--'));
-      t.done();
-    }));
-  frame.src = '{{location[scheme]}}://{{domains[天気の良い日]}}:{{location[port]}}/FileAPI/support/url-origin.html';
-  document.body.appendChild(frame);
-}, 'Verify serialization of non-ascii origin in Blob URLs');
-</script>
diff --git a/FileAPI/url/resources/create-helper.html b/FileAPI/url/resources/create-helper.html
new file mode 100644
index 0000000..fa6cf4e
--- /dev/null
+++ b/FileAPI/url/resources/create-helper.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script>
+self.addEventListener('message', e => {
+  let url = URL.createObjectURL(e.data.blob);
+  e.source.postMessage({url: url}, '*');
+});
+</script>
\ No newline at end of file
diff --git a/FileAPI/url/resources/create-helper.js b/FileAPI/url/resources/create-helper.js
new file mode 100644
index 0000000..e6344f7
--- /dev/null
+++ b/FileAPI/url/resources/create-helper.js
@@ -0,0 +1,4 @@
+self.addEventListener('message', e => {
+  let url = URL.createObjectURL(e.data.blob);
+  self.postMessage({url: url});
+});
diff --git a/FileAPI/url/resources/fetch-tests.js b/FileAPI/url/resources/fetch-tests.js
new file mode 100644
index 0000000..a81ea1e
--- /dev/null
+++ b/FileAPI/url/resources/fetch-tests.js
@@ -0,0 +1,71 @@
+// This method generates a number of tests verifying fetching of blob URLs,
+// allowing the same tests to be used both with fetch() and XMLHttpRequest.
+//
+// |fetch_method| is only used in test names, and should describe the
+// (javascript) method being used by the other two arguments (i.e. 'fetch' or 'XHR').
+//
+// |fetch_should_succeed| is a callback that is called with the Test and a URL.
+// Fetching the URL is expected to succeed. The callback should return a promise
+// resolved with whatever contents were fetched.
+//
+// |fetch_should_fail| similarly is a callback that is called with the Test, a URL
+// to fetch, and optionally a method to use to do the fetch. If no method is
+// specified the callback should use the 'GET' method. Fetching of these URLs is
+// expected to fail, and the callback should return a promise that resolves iff
+// fetching did indeed fail.
+function fetch_tests(fetch_method, fetch_should_succeed, fetch_should_fail) {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+
+  promise_test(t => {
+    const url = URL.createObjectURL(blob);
+
+    return fetch_should_succeed(t, url).then(text => {
+      assert_equals(text, blob_contents);
+    });
+  }, 'Blob URLs can be used in ' + fetch_method);
+
+  promise_test(t => {
+    const url = URL.createObjectURL(blob);
+
+    return fetch_should_succeed(t, url + '#fragment').then(text => {
+      assert_equals(text, blob_contents);
+    });
+  }, fetch_method + ' with a fragment should succeed');
+
+  promise_test(t => {
+    const url = URL.createObjectURL(blob);
+    URL.revokeObjectURL(url);
+
+    return fetch_should_fail(t, url);
+  }, fetch_method + ' of a revoked URL should fail');
+
+  promise_test(t => {
+    const url = URL.createObjectURL(blob);
+    URL.revokeObjectURL(url + '#fragment');
+
+    return fetch_should_succeed(t, url).then(text => {
+      assert_equals(text, blob_contents);
+    });
+  }, 'Only exact matches should revoke URLs, using ' + fetch_method);
+
+  promise_test(t => {
+    const url = URL.createObjectURL(blob);
+
+    return fetch_should_fail(t, url + '?querystring');
+  }, 'Appending a query string should cause ' + fetch_method + ' to fail');
+
+  promise_test(t => {
+    const url = URL.createObjectURL(blob);
+
+    return fetch_should_fail(t, url + '/path');
+  }, 'Appending a path should cause ' + fetch_method + ' to fail');
+
+  for (const method of ['HEAD', 'POST', 'DELETE', 'OPTIONS', 'PUT', 'CUSTOM']) {
+    const url = URL.createObjectURL(blob);
+
+    promise_test(t => {
+      return fetch_should_fail(t, url, method);
+    }, fetch_method + ' with method "' + method + '" should fail');
+  }
+}
\ No newline at end of file
diff --git a/FileAPI/url/resources/revoke-helper.html b/FileAPI/url/resources/revoke-helper.html
new file mode 100644
index 0000000..adf5a01
--- /dev/null
+++ b/FileAPI/url/resources/revoke-helper.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script>
+self.addEventListener('message', e => {
+  URL.revokeObjectURL(e.data.url);
+  e.source.postMessage('revoked', '*');
+});
+</script>
\ No newline at end of file
diff --git a/FileAPI/url/resources/revoke-helper.js b/FileAPI/url/resources/revoke-helper.js
new file mode 100644
index 0000000..28afce7
--- /dev/null
+++ b/FileAPI/url/resources/revoke-helper.js
@@ -0,0 +1,4 @@
+self.addEventListener('message', e => {
+  URL.revokeObjectURL(e.data.url);
+  self.postMessage('revoked');
+});
diff --git a/FileAPI/url/sandboxed-iframe.html b/FileAPI/url/sandboxed-iframe.html
new file mode 100644
index 0000000..3eca08d
--- /dev/null
+++ b/FileAPI/url/sandboxed-iframe.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>FileAPI Test: Verify behavior of Blob URL in unique origins</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="sandboxed-iframe" sandbox="allow-scripts"></iframe>
+
+<script>
+
+const iframe_scripts = [
+  'resources/fetch-tests.js',
+  'url-format.any.js',
+  'url-with-xhr.any.js',
+  'url-with-fetch.any.js',
+  'url-with-tags.window.js',
+];
+
+let html = '<!doctype html>\n<meta charset="utf-8">\n<body>\n';
+html = html + '<script src="/resources/testharness.js"></' + 'script>\n';
+html = html + '<script>setup({"explicit_timeout": true});</' + 'script>\n';
+for (const script of iframe_scripts)
+  html = html + '<script src="' + script + '"></' + 'script>\n';
+
+const frame = document.querySelector('#sandboxed-iframe');
+frame.setAttribute('srcdoc', html);
+frame.setAttribute('style', 'display:none;');
+
+fetch_tests_from_window(frame.contentWindow);
+
+</script>
diff --git a/FileAPI/url/unicode-origin.sub.html b/FileAPI/url/unicode-origin.sub.html
new file mode 100644
index 0000000..2c4921c
--- /dev/null
+++ b/FileAPI/url/unicode-origin.sub.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FileAPI Test: Verify origin of Blob URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+async_test(t => {
+  const frame = document.createElement('iframe');
+  self.addEventListener('message', t.step_func(e => {
+      if (e.source != frame.contentWindow) return;
+      const url = e.data.url;
+      assert_false(url.includes('天気の良い日'),
+          'Origin should be ascii rather than unicode');
+      assert_equals(new URL(url).origin, e.origin,
+          'Origin of URL should match origin of frame');
+      assert_true(url.startsWith('blob:{{location[scheme]}}://xn--'));
+      t.done();
+    }));
+  frame.src = '{{location[scheme]}}://{{domains[天気の良い日]}}:{{location[port]}}/FileAPI/support/url-origin.html';
+  document.body.appendChild(frame);
+}, 'Verify serialization of non-ascii origin in Blob URLs');
+</script>
diff --git a/FileAPI/url/url-format.any.js b/FileAPI/url/url-format.any.js
new file mode 100644
index 0000000..d485bfa
--- /dev/null
+++ b/FileAPI/url/url-format.any.js
@@ -0,0 +1,63 @@
+const blob = new Blob(['test']);
+const file = new File(['test'], 'name');
+
+test(() => {
+  const url_count = 5000;
+  let list = [];
+
+  for (let i = 0; i < url_count; ++i)
+    list.push(URL.createObjectURL(blob));
+
+  list.sort();
+
+  for (let i = 1; i < list.length; ++i)
+    assert_not_equals(list[i], list[i-1], 'generated Blob URLs should be unique');
+}, 'Generated Blob URLs are unique');
+
+test(() => {
+  const url = URL.createObjectURL(blob);
+  assert_equals(typeof url, 'string');
+  assert_true(url.startsWith('blob:'));
+}, 'Blob URL starts with "blob:"');
+
+test(() => {
+  const url = URL.createObjectURL(file);
+  assert_equals(typeof url, 'string');
+  assert_true(url.startsWith('blob:'));
+}, 'Blob URL starts with "blob:" for Files');
+
+test(() => {
+  const url = URL.createObjectURL(blob);
+  assert_equals(new URL(url).origin, location.origin);
+  if (location.origin !== 'null') {
+    assert_true(url.includes(location.origin));
+    assert_true(url.startsWith('blob:' + location.protocol));
+  }
+}, 'Origin of Blob URL matches our origin');
+
+test(() => {
+  const url = URL.createObjectURL(blob);
+  const url_record = new URL(url);
+  assert_equals(url_record.protocol, 'blob:');
+  assert_equals(url_record.origin, location.origin);
+  assert_equals(url_record.host, '', 'host should be an empty string');
+  assert_equals(url_record.port, '', 'port should be an empty string');
+  const uuid_path_re = /\/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+  assert_true(uuid_path_re.test(url_record.pathname), 'Path must end with a valid UUID');
+  if (location.origin !== 'null') {
+    const nested_url = new URL(url_record.pathname);
+    assert_equals(nested_url.origin, location.origin);
+    assert_equals(nested_url.pathname.search(uuid_path_re), 0, 'Path must be a valid UUID');
+    assert_true(url.includes(location.origin));
+    assert_true(url.startsWith('blob:' + location.protocol));
+  }
+}, 'Blob URL parses correctly');
+
+test(() => {
+  const url = URL.createObjectURL(file);
+  assert_equals(new URL(url).origin, location.origin);
+  if (location.origin !== 'null') {
+    assert_true(url.includes(location.origin));
+    assert_true(url.startsWith('blob:' + location.protocol));
+  }
+}, 'Origin of Blob URL matches our origin for Files');
diff --git a/FileAPI/url/url-in-tags-revoke.window.js b/FileAPI/url/url-in-tags-revoke.window.js
new file mode 100644
index 0000000..a564730
--- /dev/null
+++ b/FileAPI/url/url-in-tags-revoke.window.js
@@ -0,0 +1,54 @@
+async_test(t => {
+  const run_result = 'test_frame_OK';
+  const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+    '<script>window.test_result = "' + run_result + '";</script>';
+  const blob = new Blob([blob_contents], {type: 'text/html'});
+  const url = URL.createObjectURL(blob);
+
+  const frame = document.createElement('iframe');
+  frame.setAttribute('src', url);
+  frame.setAttribute('style', 'display:none;');
+  document.body.appendChild(frame);
+  URL.revokeObjectURL(url);
+
+  frame.onload = t.step_func_done(() => {
+    assert_equals(frame.contentWindow.test_result, run_result);
+  });
+}, 'Fetching a blob URL immediately before revoking it works in an iframe.');
+
+async_test(t => {
+  const run_result = 'test_frame_OK';
+  const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+    '<script>window.test_result = "' + run_result + '";</script>';
+  const blob = new Blob([blob_contents], {type: 'text/html'});
+  const url = URL.createObjectURL(blob);
+
+  const frame = document.createElement('iframe');
+  frame.setAttribute('src', '/common/blank.html');
+  frame.setAttribute('style', 'display:none;');
+  document.body.appendChild(frame);
+
+  frame.onload = t.step_func(() => {
+    frame.contentWindow.location = url;
+    URL.revokeObjectURL(url);
+    frame.onload = t.step_func_done(() => {
+      assert_equals(frame.contentWindow.test_result, run_result);
+    });
+  });
+}, 'Fetching a blob URL immediately before revoking it works in an iframe navigation.');
+
+async_test(t => {
+  const run_result = 'test_script_OK';
+  const blob_contents = 'window.script_test_result = "' + run_result + '";';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+
+  const e = document.createElement('script');
+  e.setAttribute('src', url);
+  e.onload = t.step_func_done(() => {
+    assert_equals(window.script_test_result, run_result);
+  });
+
+  document.body.appendChild(e);
+  URL.revokeObjectURL(url);
+}, 'Fetching a blob URL immediately before revoking it works in <script> tags.');
diff --git a/FileAPI/url/url-in-tags.window.js b/FileAPI/url/url-in-tags.window.js
new file mode 100644
index 0000000..f20b359
--- /dev/null
+++ b/FileAPI/url/url-in-tags.window.js
@@ -0,0 +1,48 @@
+async_test(t => {
+  const run_result = 'test_script_OK';
+  const blob_contents = 'window.test_result = "' + run_result + '";';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+
+  const e = document.createElement('script');
+  e.setAttribute('src', url);
+  e.onload = t.step_func_done(() => {
+    assert_equals(window.test_result, run_result);
+  });
+
+  document.body.appendChild(e);
+}, 'Blob URLs can be used in <script> tags');
+
+async_test(t => {
+  const run_result = 'test_frame_OK';
+  const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+    '<script>window.test_result = "' + run_result + '";</script>';
+  const blob = new Blob([blob_contents], {type: 'text/html'});
+  const url = URL.createObjectURL(blob);
+
+  const frame = document.createElement('iframe');
+  frame.setAttribute('src', url);
+  frame.setAttribute('style', 'display:none;');
+  document.body.appendChild(frame);
+
+  frame.onload = t.step_func_done(() => {
+    assert_equals(frame.contentWindow.test_result, run_result);
+  });
+}, 'Blob URLs can be used in iframes, and are treated same origin');
+
+async_test(t => {
+  const blob_contents = '<!doctype html>\n<meta charset="utf-8">\n' +
+    '<style>body { margin: 0; } .block { height: 5000px; }</style>\n' +
+    '<body>\n' +
+    '<a id="block1"></a><div class="block"></div>\n' +
+    '<a id="block2"></a><div class="block"></div>';
+  const blob = new Blob([blob_contents], {type: 'text/html'});
+  const url = URL.createObjectURL(blob);
+
+  const frame = document.createElement('iframe');
+  frame.setAttribute('src', url + '#block2');
+  document.body.appendChild(frame);
+  frame.contentWindow.onscroll = t.step_func_done(() => {
+    assert_equals(frame.contentWindow.scrollY, 5000);
+  });
+}, 'Blob URL fragment is implemented.');
diff --git a/FileAPI/url/url-lifetime.html b/FileAPI/url/url-lifetime.html
new file mode 100644
index 0000000..3c8345c
--- /dev/null
+++ b/FileAPI/url/url-lifetime.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+promise_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const worker = new Worker('resources/create-helper.js');
+  let url;
+  return new Promise(resolve => {
+    worker.onmessage = e => resolve(e.data);
+    worker.postMessage({blob: blob});
+  }).then(data => {
+    url = data.url;
+    let result = fetch(url);
+    worker.terminate();
+    return result;
+  }).then(response => response.text()).then(text => {
+    assert_equals(text, blob_contents);
+    return new Promise(resolve => t.step_timeout(resolve, 100));
+  }).then(() => promise_rejects(t, new TypeError, fetch(url)));
+}, 'Terminating worker revokes its URLs');
+
+promise_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const frame = document.createElement('iframe');
+  frame.setAttribute('style', 'display:none;');
+  frame.src = 'resources/create-helper.html';
+  document.body.appendChild(frame);
+
+  let url;
+  return new Promise(resolve => {
+    frame.onload = t.step_func(e => {
+      resolve(e);
+    });
+  }).then(e => {
+    frame.contentWindow.postMessage({blob: blob}, '*');
+    return new Promise(resolve => {
+      self.addEventListener('message', t.step_func(e => {
+        if (e.source === frame.contentWindow) resolve(e);
+      }));
+    });
+  }).then(e => {
+    url = e.data.url;
+    let fetch_result = fetch(url);
+    document.body.removeChild(frame);
+    return fetch_result;
+  }).then(response => response.text()).then(text => {
+    assert_equals(text, blob_contents);
+    return new Promise(resolve => t.step_timeout(resolve, 100));
+  }).then(() => promise_rejects(t, new TypeError, fetch(url)));
+}, 'Removing an iframe revokes its URLs');
+</script>
\ No newline at end of file
diff --git a/FileAPI/url/url-with-fetch.any.js b/FileAPI/url/url-with-fetch.any.js
new file mode 100644
index 0000000..775d9a0
--- /dev/null
+++ b/FileAPI/url/url-with-fetch.any.js
@@ -0,0 +1,53 @@
+// META: script=resources/fetch-tests.js
+
+function fetch_should_succeed(test, request) {
+  return fetch(request).then(response => response.text());
+}
+
+function fetch_should_fail(test, url, method = 'GET') {
+  return promise_rejects(test, new TypeError, fetch(url, {method: method}));
+}
+
+fetch_tests('fetch', fetch_should_succeed, fetch_should_fail);
+
+promise_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob_type = 'image/png';
+  const blob = new Blob([blob_contents], {type: blob_type});
+  const url = URL.createObjectURL(blob);
+
+  return fetch(url).then(response => {
+    assert_equals(response.headers.get('Content-Type'), blob_type);
+  });
+}, 'fetch should return Content-Type from Blob');
+
+promise_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+  const request = new Request(url);
+
+  // Revoke the object URL.  Request should take a reference to the blob as
+  // soon as it receives it in open(), so the request succeeds even though we
+  // revoke the URL before calling fetch().
+  URL.revokeObjectURL(url);
+
+  return fetch_should_succeed(t, request).then(text => {
+    assert_equals(text, blob_contents);
+  });
+}, 'Revoke blob URL after creating Request, will fetch');
+
+promise_test(function(t) {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+
+  const result = fetch_should_succeed(t, url).then(text => {
+    assert_equals(text, blob_contents);
+  });
+
+  // Revoke the object URL. fetch should have already resolved the blob URL.
+  URL.revokeObjectURL(url);
+
+  return result;
+}, 'Revoke blob URL after calling fetch, fetch should succeed');
diff --git a/FileAPI/url/url-with-xhr.any.js b/FileAPI/url/url-with-xhr.any.js
new file mode 100644
index 0000000..29d8308
--- /dev/null
+++ b/FileAPI/url/url-with-xhr.any.js
@@ -0,0 +1,68 @@
+// META: script=resources/fetch-tests.js
+
+function xhr_should_succeed(test, url) {
+  return new Promise((resolve, reject) => {
+    const xhr = new XMLHttpRequest();
+    xhr.open('GET', url);
+    xhr.onload = test.step_func(() => {
+      assert_equals(xhr.status, 200);
+      assert_equals(xhr.statusText, 'OK');
+      resolve(xhr.response);
+    });
+    xhr.onerror = () => reject('Got unexpected error event');
+    xhr.send();
+  });
+}
+
+function xhr_should_fail(test, url, method = 'GET') {
+  const xhr = new XMLHttpRequest();
+  xhr.open(method, url);
+  const result1 = new Promise((resolve, reject) => {
+    xhr.onload = () => reject('Got unexpected load event');
+    xhr.onerror = resolve;
+  });
+  const result2 = new Promise(resolve => {
+    xhr.onreadystatechange = test.step_func(() => {
+      if (xhr.readyState !== xhr.DONE) return;
+      assert_equals(xhr.status, 0);
+      resolve();
+    });
+  });
+  xhr.send();
+  return Promise.all([result1, result2]);
+}
+
+fetch_tests('XHR', xhr_should_succeed, xhr_should_fail);
+
+async_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob_type = 'image/png';
+  const blob = new Blob([blob_contents], {type: blob_type});
+  const url = URL.createObjectURL(blob);
+  const xhr = new XMLHttpRequest();
+  xhr.open('GET', url);
+  xhr.onloadend = t.step_func_done(() => {
+    assert_equals(xhr.getResponseHeader('Content-Type'), blob_type);
+  });
+  xhr.send();
+}, 'XHR should return Content-Type from Blob');
+
+async_test(t => {
+  const blob_contents = 'test blob contents';
+  const blob = new Blob([blob_contents]);
+  const url = URL.createObjectURL(blob);
+  const xhr = new XMLHttpRequest();
+  xhr.open('GET', url);
+
+  // Revoke the object URL.  XHR should take a reference to the blob as soon as
+  // it receives it in open(), so the request succeeds even though we revoke the
+  // URL before calling send().
+  URL.revokeObjectURL(url);
+
+  xhr.onload = t.step_func_done(() => {
+    assert_equals(xhr.response, blob_contents);
+  });
+  xhr.onerror = t.unreached_func('Got unexpected error event');
+
+  xhr.send();
+}, 'Revoke blob URL after open(), will fetch');
diff --git a/FileAPI/url/url_createobjecturl_blob.html b/FileAPI/url/url_createobjecturl_blob.html
deleted file mode 100644
index 798df08..0000000
--- a/FileAPI/url/url_createobjecturl_blob.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>FileAPI Test: Creating Blob URL with Blob</title>
-<link rel="author" title="Intel" href="http://www.intel.com">
-<link rel="author" title="JunChen Xia" href="mailto:xjconlyme@gmail.com">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script>
-  var blob = new Blob(["Test Blob"]);
-
-  test(function() {
-    var testBlob = window.URL.createObjectURL(blob);
-    assert_equals(typeof testBlob, "string", "Blob URI is typeof string");
-    assert_equals(testBlob.indexOf("blob"), 0, "Blob URI starts with 'blob'");
-  }, "Check if the Blob URI starts with 'blob' using createObjectURL()");
-</script>
-
diff --git a/FileAPI/url/url_createobjecturl_file-manual.html b/FileAPI/url/url_createobjecturl_file-manual.html
index 469606a..7ae3251 100644
--- a/FileAPI/url/url_createobjecturl_file-manual.html
+++ b/FileAPI/url/url_createobjecturl_file-manual.html
@@ -3,9 +3,9 @@
 <title>FileAPI Test: Creating Blob URL with File</title>
 <link rel="author" title="Intel" href="http://www.intel.com">
 <link rel="author" title="JunChen Xia" href="mailto:xjconlyme@gmail.com">
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<meta name="timeout" content="long">
 
 <div>
   <p>Test steps:</p>
diff --git a/FileAPI/url/url_xmlhttprequest.html b/FileAPI/url/url_xmlhttprequest.html
deleted file mode 100644
index 7a86cdd..0000000
--- a/FileAPI/url/url_xmlhttprequest.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>FileAPI Test: Creating Blob URL via XMLHttpRequest</title>
-<link rel="author" title="Intel" href="http://www.intel.com">
-<link rel="author" title="JunChen Xia" href="mailto:xjconlyme@gmail.com">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script>
-  async_test(function () {
-    var http = new XMLHttpRequest();
-    http.open("GET", "/images/blue96x96.png", true);
-    http.responseType = "blob";
-    http.onloadend = this.step_func(function(evt) {
-      var blobURI = window.URL.createObjectURL(http.response);
-      assert_true(http.response instanceof Blob, "XMLHttpRequest returns instanceof Blob");
-      assert_equals(typeof blobURI, "string", "Blob URI is typeof string");
-      assert_equals(blobURI.indexOf("blob"), 0, "Blob URI starts with 'blob'");
-      assert_equals(http.status, 200, "The status is 200");
-      assert_equals(http.statusText, "OK", "The status text is OK when XMLHttpRequest returns correct blob");
-      assert_equals(http.getResponseHeader("Content-Type"), "image/png", "The content type is image/png when set the respnose blob");
-      this.done();
-    });
-    http.send();
-  });
-</script>
-
diff --git a/FileAPI/url/url_xmlhttprequest_img.html b/FileAPI/url/url_xmlhttprequest_img.html
index 7f26633..468dcb0 100644
--- a/FileAPI/url/url_xmlhttprequest_img.html
+++ b/FileAPI/url/url_xmlhttprequest_img.html
@@ -20,9 +20,8 @@
   http.onloadend = function() {
     var fileDisplay = document.querySelector("#fileDisplay");
     fileDisplay.src = window.URL.createObjectURL(http.response);
-    takeScreenshot();
+    fileDisplay.onload = takeScreenshot;
   };
   http.send();
 </script>
 </html>
-
diff --git a/IndexedDB/README.md b/IndexedDB/README.md
index 6b636d5..18f067b 100644
--- a/IndexedDB/README.md
+++ b/IndexedDB/README.md
@@ -1,8 +1,8 @@
 This directory contains the Indexed Database API test suite.
 
-To run the tests in this test suite within a browser, go to: <http://w3c-test.org/IndexedDB/>.
+To run the tests in this test suite within a browser, go to: <https://w3c-test.org/IndexedDB/>.
 
-The latest Editor's Draft of Indexed Database API is: <http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html>.
+The latest Editor's Draft of Indexed Database API is: <https://w3c.github.io/IndexedDB/>.
 
-The latest W3C Technical Report of Indexed Database API is: <http://www.w3.org/TR/IndexedDB/>.
+The latest W3C Technical Report of Indexed Database API is: <https://www.w3.org/TR/IndexedDB/>.
 
diff --git a/IndexedDB/historical.html b/IndexedDB/historical.html
index 73d78c8..8e7097e 100644
--- a/IndexedDB/historical.html
+++ b/IndexedDB/historical.html
@@ -63,4 +63,18 @@
   // Replaced circa May 2012 by a DOMString (later, IDBTransactionMode enum).
   assert_false('VERSION_CHANGE' in IDBTransaction);
 }, '"VERSION_CHANGE" should not be supported on IDBTransaction.');
+
+// Gecko-proprietary interfaces.
+var removedFromWindow = [
+  'IDBFileHandle',
+  'IDBFileRequest',
+  'IDBMutableFile',
+];
+
+removedFromWindow.forEach(function(name) {
+  test(function() {
+    assert_false(name in window);
+  }, '"' + name + '" should not be supported');
+});
+
 </script>
diff --git a/IndexedDB/interfaces.any.js b/IndexedDB/interfaces.any.js
new file mode 100644
index 0000000..f1b2a99
--- /dev/null
+++ b/IndexedDB/interfaces.any.js
@@ -0,0 +1,31 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+promise_test(async t => {
+  const [html, dom, indexeddb] = await Promise.all([
+    '/interfaces/html.idl',
+    '/interfaces/dom.idl',
+    '/interfaces/IndexedDB.idl',
+  ].map(url => fetch(url).then(response => response.text())));
+
+  const idl_array = new IdlArray();
+  idl_array.add_untested_idls(html, { only: ['WindowOrWorkerGlobalScope'] });
+  idl_array.add_untested_idls(dom);
+  idl_array.add_idls(indexeddb);
+  idl_array.add_objects({
+    IDBCursor: [],
+    IDBCursorWithValue: [],
+    IDBDatabase: [],
+    IDBFactory: [self.indexedDB],
+    IDBIndex: [],
+    IDBKeyRange: [IDBKeyRange.only(0)],
+    IDBObjectStore: [],
+    IDBOpenDBRequest: [],
+    IDBRequest: [],
+    IDBTransaction: [],
+    IDBVersionChangeEvent: [new IDBVersionChangeEvent('')],
+    DOMStringList: [],
+  });
+
+  idl_array.test();
+}, 'Test driver');
diff --git a/IndexedDB/interfaces.html b/IndexedDB/interfaces.html
deleted file mode 100644
index 6fb37f8..0000000
--- a/IndexedDB/interfaces.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>IndexedDB IDL tests</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-
-<h1>IndexedDB IDL tests</h1>
-<div id=log></div>
-
-<script>
-"use strict";
-async_test(function(t) {
-  var request = new XMLHttpRequest();
-  request.open("GET", "interfaces.idl");
-  request.send();
-  request.onload = t.step_func(function() {
-    var idlArray = new IdlArray();
-    var idls = request.responseText;
-
-    // https://html.spec.whatwg.org/multipage/browsers.html#window
-    idlArray.add_untested_idls("[PrimaryGlobal] interface Window { };");
-
-    // https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope-mixin
-    idlArray.add_untested_idls(`[NoInterfaceObject, Exposed=(Window,Worker)]
-                                interface WindowOrWorkerGlobalScope {};`);
-    idlArray.add_untested_idls("Window implements WindowOrWorkerGlobalScope;");
-
-    // https://dom.spec.whatwg.org/#interface-event
-    idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface Event { };");
-
-    // https://dom.spec.whatwg.org/#interface-eventtarget
-    idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface EventTarget { };");
-
-    // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist
-    idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface DOMStringList { };");
-
-    // From Indexed DB:
-    idlArray.add_idls(idls);
-
-    idlArray.add_objects({
-      IDBCursor: [],
-      IDBCursorWithValue: [],
-      IDBDatabase: [],
-      IDBFactory: ["window.indexedDB"],
-      IDBIndex: [],
-      IDBKeyRange: ["IDBKeyRange.only(0)"],
-      IDBObjectStore: [],
-      IDBOpenDBRequest: [],
-      IDBRequest: [],
-      IDBTransaction: [],
-      IDBVersionChangeEvent: ["new IDBVersionChangeEvent('foo')"],
-      DOMStringList: [],
-    });
-
-    idlArray.test();
-    t.done();
-  });
-});
-</script>
diff --git a/IndexedDB/interfaces.idl b/IndexedDB/interfaces.idl
deleted file mode 100644
index 3c993c3..0000000
--- a/IndexedDB/interfaces.idl
+++ /dev/null
@@ -1,203 +0,0 @@
-[Exposed=(Window,Worker)]
-interface IDBRequest : EventTarget {
-  readonly attribute any                                        result;
-  readonly attribute DOMException?                              error;
-  readonly attribute (IDBObjectStore or IDBIndex or IDBCursor)? source;
-  readonly attribute IDBTransaction?                            transaction;
-  readonly attribute IDBRequestReadyState                       readyState;
-
-  // Event handlers:
-  attribute EventHandler onsuccess;
-  attribute EventHandler onerror;
-};
-
-enum IDBRequestReadyState {
-  "pending",
-  "done"
-};
-
-[Exposed=(Window,Worker)]
-interface IDBOpenDBRequest : IDBRequest {
-  // Event handlers:
-  attribute EventHandler onblocked;
-  attribute EventHandler onupgradeneeded;
-};
-
-[Exposed=(Window,Worker),
- Constructor(DOMString type, optional IDBVersionChangeEventInit eventInitDict)]
-interface IDBVersionChangeEvent : Event {
-  readonly attribute unsigned long long  oldVersion;
-  readonly attribute unsigned long long? newVersion;
-};
-
-dictionary IDBVersionChangeEventInit : EventInit {
-  unsigned long long  oldVersion = 0;
-  unsigned long long? newVersion = null;
-};
-
-partial interface WindowOrWorkerGlobalScope {
-  [SameObject] readonly attribute IDBFactory indexedDB;
-};
-
-[Exposed=(Window,Worker)]
-interface IDBFactory {
-  IDBOpenDBRequest open(DOMString name,
-                        [EnforceRange] optional unsigned long long version);
-  IDBOpenDBRequest deleteDatabase(DOMString name);
-
-  short cmp(any first, any second);
-};
-
-[Exposed=(Window,Worker)]
-interface IDBDatabase : EventTarget {
-  readonly attribute DOMString          name;
-  readonly attribute unsigned long long version;
-  readonly attribute DOMStringList      objectStoreNames;
-
-  IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames,
-                             optional IDBTransactionMode mode = "readonly");
-  void           close();
-
-  IDBObjectStore createObjectStore(DOMString name,
-                                   optional IDBObjectStoreParameters options);
-  void           deleteObjectStore(DOMString name);
-
-  // Event handlers:
-  attribute EventHandler onabort;
-  attribute EventHandler onclose;
-  attribute EventHandler onerror;
-  attribute EventHandler onversionchange;
-};
-
-dictionary IDBObjectStoreParameters {
-  (DOMString or sequence<DOMString>)? keyPath = null;
-  boolean                             autoIncrement = false;
-};
-
-[Exposed=(Window,Worker)]
-interface IDBObjectStore {
-           attribute DOMString      name;
-  readonly attribute any            keyPath;
-  readonly attribute DOMStringList  indexNames;
-  readonly attribute IDBTransaction transaction;
-  readonly attribute boolean        autoIncrement;
-
-  IDBRequest put(any value, optional any key);
-  IDBRequest add(any value, optional any key);
-  IDBRequest delete(any query);
-  IDBRequest clear();
-  IDBRequest get(any query);
-  IDBRequest getKey(any query);
-  IDBRequest getAll(optional any query,
-                    [EnforceRange] optional unsigned long count);
-  IDBRequest getAllKeys(optional any query,
-                        [EnforceRange] optional unsigned long count);
-  IDBRequest count(optional any query);
-
-  IDBRequest openCursor(optional any query,
-                        optional IDBCursorDirection direction = "next");
-  IDBRequest openKeyCursor(optional any query,
-                           optional IDBCursorDirection direction = "next");
-
-  IDBIndex   index(DOMString name);
-
-  IDBIndex   createIndex(DOMString name,
-                         (DOMString or sequence<DOMString>) keyPath,
-                         optional IDBIndexParameters options);
-  void       deleteIndex(DOMString indexName);
-};
-
-dictionary IDBIndexParameters {
-  boolean unique = false;
-  boolean multiEntry = false;
-};
-
-[Exposed=(Window,Worker)]
-interface IDBIndex {
-           attribute DOMString      name;
-  readonly attribute IDBObjectStore objectStore;
-  readonly attribute any            keyPath;
-  readonly attribute boolean        multiEntry;
-  readonly attribute boolean        unique;
-
-  IDBRequest get(any query);
-  IDBRequest getKey(any query);
-  IDBRequest getAll(optional any query,
-                    [EnforceRange] optional unsigned long count);
-  IDBRequest getAllKeys(optional any query,
-                        [EnforceRange] optional unsigned long count);
-  IDBRequest count(optional any query);
-
-  IDBRequest openCursor(optional any query,
-                        optional IDBCursorDirection direction = "next");
-  IDBRequest openKeyCursor(optional any query,
-                           optional IDBCursorDirection direction = "next");
-};
-
-[Exposed=(Window,Worker)]
-interface IDBKeyRange {
-  readonly attribute any     lower;
-  readonly attribute any     upper;
-  readonly attribute boolean lowerOpen;
-  readonly attribute boolean upperOpen;
-
-  // Static construction methods:
-  static IDBKeyRange only(any value);
-  static IDBKeyRange lowerBound(any lower, optional boolean open = false);
-  static IDBKeyRange upperBound(any upper, optional boolean open = false);
-  static IDBKeyRange bound(any lower,
-                           any upper,
-                           optional boolean lowerOpen = false,
-                           optional boolean upperOpen = false);
-
-  boolean includes(any key);
-};
-
-[Exposed=(Window,Worker)]
-interface IDBCursor {
-  readonly attribute (IDBObjectStore or IDBIndex) source;
-  readonly attribute IDBCursorDirection           direction;
-  readonly attribute any                          key;
-  readonly attribute any                          primaryKey;
-
-  void advance([EnforceRange] unsigned long count);
-  void continue(optional any key);
-  void continuePrimaryKey(any key, any primaryKey);
-
-  IDBRequest update(any value);
-  IDBRequest delete();
-};
-
-enum IDBCursorDirection {
-  "next",
-  "nextunique",
-  "prev",
-  "prevunique"
-};
-
-[Exposed=(Window,Worker)]
-interface IDBCursorWithValue : IDBCursor {
-  readonly attribute any value;
-};
-
-[Exposed=(Window,Worker)]
-interface IDBTransaction : EventTarget {
-  readonly attribute DOMStringList      objectStoreNames;
-  readonly attribute IDBTransactionMode mode;
-  readonly attribute IDBDatabase        db;
-  readonly attribute DOMException       error;
-
-  IDBObjectStore objectStore(DOMString name);
-  void           abort();
-
-  // Event handlers:
-  attribute EventHandler onabort;
-  attribute EventHandler oncomplete;
-  attribute EventHandler onerror;
-};
-
-enum IDBTransactionMode {
-  "readonly",
-  "readwrite",
-  "versionchange"
-};
diff --git a/IndexedDB/interfaces.worker.js b/IndexedDB/interfaces.worker.js
deleted file mode 100644
index 22aa386..0000000
--- a/IndexedDB/interfaces.worker.js
+++ /dev/null
@@ -1,53 +0,0 @@
-"use strict";
-
-importScripts("/resources/testharness.js");
-importScripts("/resources/WebIDLParser.js", "/resources/idlharness.js");
-
-async_test(function(t) {
-  var request = new XMLHttpRequest();
-  request.open("GET", "interfaces.idl");
-  request.send();
-  request.onload = t.step_func(function() {
-    var idlArray = new IdlArray();
-    var idls = request.responseText;
-
-    // https://html.spec.whatwg.org/multipage/workers.html#workerglobalscope
-    idlArray.add_untested_idls("[Exposed=Worker] interface WorkerGlobalScope {};");
-
-    // https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope-mixin
-    idlArray.add_untested_idls(`[NoInterfaceObject, Exposed=(Window,Worker)]
-                              interface WindowOrWorkerGlobalScope {};`);
-    idlArray.add_untested_idls("WorkerGlobalScope implements WindowOrWorkerGlobalScope;");
-
-    // https://dom.spec.whatwg.org/#interface-event
-    idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface Event { };");
-
-    // https://dom.spec.whatwg.org/#interface-eventtarget
-    idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface EventTarget { };");
-
-    // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist
-    idlArray.add_untested_idls("[Exposed=(Window,Worker)] interface DOMStringList { };");
-
-    // From Indexed DB:
-    idlArray.add_idls(idls);
-
-    idlArray.add_objects({
-      IDBCursor: [],
-      IDBCursorWithValue: [],
-      IDBDatabase: [],
-      IDBFactory: ["self.indexedDB"],
-      IDBIndex: [],
-      IDBKeyRange: ["IDBKeyRange.only(0)"],
-      IDBObjectStore: [],
-      IDBOpenDBRequest: [],
-      IDBRequest: [],
-      IDBTransaction: [],
-      IDBVersionChangeEvent: ["new IDBVersionChangeEvent('foo')"],
-      DOMStringList: [],
-    });
-    idlArray.test();
-    t.done();
-  });
-});
-
-done();
diff --git a/IndexedDB/interleaved-cursors-common.js b/IndexedDB/interleaved-cursors-common.js
new file mode 100644
index 0000000..a76ec52
--- /dev/null
+++ b/IndexedDB/interleaved-cursors-common.js
@@ -0,0 +1,188 @@
+// Infrastructure shared by interleaved-cursors-{small,large}.html
+
+// Number of objects that each iterator goes over.
+const itemCount = 10;
+
+// Ratio of small objects to large objects.
+const largeObjectRatio = 5;
+
+// Size of large objects. This should exceed the size of a block in the storage
+// method underlying the browser's IndexedDB implementation. For example, this
+// needs to exceed the LevelDB block size on Chrome, and the SQLite block size
+// on Firefox.
+const largeObjectSize = 48 * 1024;
+
+function objectKey(cursorIndex, itemIndex) {
+  return `${cursorIndex}-key-${itemIndex}`;
+}
+
+function objectValue(cursorIndex, itemIndex) {
+  if ((cursorIndex * itemCount + itemIndex) % largeObjectRatio === 0) {
+    // We use a typed array (as opposed to a string) because IndexedDB
+    // implementations may serialize strings using UTF-8 or UTF-16, yielding
+    // larger IndexedDB entries than we'd expect. It's very unlikely that an
+    // IndexedDB implementation would use anything other than the raw buffer to
+    // serialize a typed array.
+    const buffer = new Uint8Array(largeObjectSize);
+
+    // Some IndexedDB implementations, like LevelDB, compress their data blocks
+    // before storing them to disk. We use a simple 32-bit xorshift PRNG, which
+    // should be sufficient to foil any fast generic-purpose compression scheme.
+
+    // 32-bit xorshift - the seed can't be zero
+    let state = 1000 + (cursorIndex * itemCount + itemIndex);
+
+    for (let i = 0; i < largeObjectSize; ++i) {
+      state ^= state << 13;
+      state ^= state >> 17;
+      state ^= state << 5;
+      buffer[i] = state & 0xff;
+    }
+
+    return buffer;
+  }
+  return [cursorIndex, 'small', itemIndex];
+}
+
+// Writes the objects to be read by one cursor. Returns a promise that resolves
+// when the write completes.
+//
+// We want to avoid creating a large transaction, because that is outside the
+// test's scope, and it's a bad practice. So we break up the writes across
+// multiple transactions. For simplicity, each transaction writes all the
+// objects that will be read by a cursor.
+function writeCursorObjects(database, cursorIndex) {
+  return new Promise((resolve, reject) => {
+    const transaction = database.transaction('cache', 'readwrite');
+    transaction.onabort = () => { reject(transaction.error); };
+
+    const store = transaction.objectStore('cache');
+    for (let i = 0; i < itemCount; ++i) {
+      store.put({
+          key: objectKey(cursorIndex, i), value: objectValue(cursorIndex, i)});
+    }
+    transaction.oncomplete = resolve;
+  });
+}
+
+// Returns a promise that resolves when the store has been populated.
+function populateTestStore(testCase, database, cursorCount) {
+  let promiseChain = Promise.resolve();
+
+  for (let i = 0; i < cursorCount; ++i)
+    promiseChain = promiseChain.then(() => writeCursorObjects(database, i));
+
+  return promiseChain;
+}
+
+// Reads cursors in an interleaved fashion, as shown below.
+//
+// Given N cursors, each of which points to the beginning of a K-item sequence,
+// the following accesses will be made.
+//
+// OC(i)    = open cursor i
+// RD(i, j) = read result of cursor i, which should be at item j
+// CC(i)    = continue cursor i
+// |        = wait for onsuccess on the previous OC or CC
+//
+// OC(1)            | RD(1, 1) OC(2) | RD(2, 1) OC(3) | ... | RD(n-1, 1) CC(n) |
+// RD(n, 1)   CC(1) | RD(1, 2) CC(2) | RD(2, 2) CC(3) | ... | RD(n-1, 2) CC(n) |
+// RD(n, 2)   CC(1) | RD(1, 3) CC(2) | RD(2, 3) CC(3) | ... | RD(n-1, 3) CC(n) |
+// ...
+// RD(n, k-1) CC(1) | RD(1, k) CC(2) | RD(2, k) CC(3) | ... | RD(n-1, k) CC(n) |
+// RD(n, k)           done
+function interleaveCursors(testCase, store, cursorCount) {
+  return new Promise((resolve, reject) => {
+    // The cursors used for iteration are stored here so each cursor's onsuccess
+    // handler can call continue() on the next cursor.
+    const cursors = [];
+
+    // The results of IDBObjectStore.openCursor() calls are stored here so we
+    // we can change the requests' onsuccess handler after every
+    // IDBCursor.continue() call.
+    const requests = [];
+
+    const checkCursorState = (cursorIndex, itemIndex) => {
+      const cursor = cursors[cursorIndex];
+      assert_equals(cursor.key, objectKey(cursorIndex, itemIndex));
+      assert_equals(cursor.value.key, objectKey(cursorIndex, itemIndex));
+      assert_equals(
+          cursor.value.value.join('-'),
+          objectValue(cursorIndex, itemIndex).join('-'));
+    };
+
+    const openCursor = (cursorIndex, callback) => {
+      const request = store.openCursor(
+          IDBKeyRange.lowerBound(objectKey(cursorIndex, 0)));
+      requests[cursorIndex] = request;
+
+      request.onsuccess = testCase.step_func(() => {
+        const cursor = request.result;
+        cursors[cursorIndex] = cursor;
+        checkCursorState(cursorIndex, 0);
+        callback();
+      });
+      request.onerror = event => reject(request.error);
+    };
+
+    const readItemFromCursor = (cursorIndex, itemIndex, callback) => {
+      const request = requests[cursorIndex];
+      request.onsuccess = testCase.step_func(() => {
+        const cursor = request.result;
+        cursors[cursorIndex] = cursor;
+        checkCursorState(cursorIndex, itemIndex);
+        callback();
+      });
+
+      const cursor = cursors[cursorIndex];
+      cursor.continue();
+    };
+
+    // We open all the cursors one at a time, then cycle through the cursors and
+    // call continue() on each of them. This access pattern causes maximal
+    // trashing to an LRU cursor cache. Eviction scheme aside, any cache will
+    // have to evict some cursors, and this access pattern verifies that the
+    // cache correctly restores the state of evicted cursors.
+    const steps = [];
+    for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
+      steps.push(openCursor.bind(null, cursorIndex));
+    for (let itemIndex = 1; itemIndex < itemCount; ++itemIndex) {
+      for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
+        steps.push(readItemFromCursor.bind(null, cursorIndex, itemIndex));
+    }
+
+    const runStep = (stepIndex) => {
+      if (stepIndex === steps.length) {
+        resolve();
+        return;
+      }
+      steps[stepIndex](() => { runStep(stepIndex + 1); });
+    };
+    runStep(0);
+  });
+}
+
+function cursorTest(cursorCount) {
+  promise_test(testCase => {
+    return createDatabase(testCase, (database, transaction) => {
+      const store = database.createObjectStore('cache',
+          { keyPath: 'key', autoIncrement: true });
+    }).then(database => {
+      return populateTestStore(testCase, database, cursorCount).then(
+          () => database);
+    }).then(database => {
+      database.close();
+    }).then(() => {
+      return openDatabase(testCase);
+    }).then(database => {
+      const transaction = database.transaction('cache', 'readonly');
+      transaction.onabort = () => { reject(transaction.error); };
+
+      const store = transaction.objectStore('cache');
+      return interleaveCursors(testCase, store, cursorCount).then(
+          () => database);
+    }).then(database => {
+      database.close();
+    });
+  }, `${cursorCount} cursors`);
+}
diff --git a/IndexedDB/interleaved-cursors-large.html b/IndexedDB/interleaved-cursors-large.html
new file mode 100644
index 0000000..6f4e440
--- /dev/null
+++ b/IndexedDB/interleaved-cursors-large.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>IndexedDB: Interleaved iteration of multiple cursors</title>
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support-promises.js"></script>
+<script src="interleaved-cursors-common.js"></script>
+<script>
+cursorTest(250);
+</script>
diff --git a/IndexedDB/interleaved-cursors-small.html b/IndexedDB/interleaved-cursors-small.html
new file mode 100644
index 0000000..a4c4777
--- /dev/null
+++ b/IndexedDB/interleaved-cursors-small.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>IndexedDB: Interleaved iteration of multiple cursors</title>
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support-promises.js"></script>
+<script src="interleaved-cursors-common.js"></script>
+<script>
+cursorTest(1);
+cursorTest(10);
+cursorTest(100);
+</script>
diff --git a/IndexedDB/interleaved-cursors.html b/IndexedDB/interleaved-cursors.html
deleted file mode 100644
index 3112690..0000000
--- a/IndexedDB/interleaved-cursors.html
+++ /dev/null
@@ -1,196 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>IndexedDB: Interleaved iteration of multiple cursors</title>
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="support-promises.js"></script>
-<script>
-// Number of objects that each iterator goes over.
-const itemCount = 10;
-
-// Ratio of small objects to large objects.
-const largeObjectRatio = 5;
-
-// Size of large objects. This should exceed the size of a block in the storage
-// method underlying the browser's IndexedDB implementation. For example, this
-// needs to exceed the LevelDB block size on Chrome, and the SQLite block size
-// on Firefox.
-const largeObjectSize = 48 * 1024;
-
-function objectKey(cursorIndex, itemIndex) {
-  return `${cursorIndex}-key-${itemIndex}`;
-}
-
-function objectValue(cursorIndex, itemIndex) {
-  if ((cursorIndex * itemCount + itemIndex) % largeObjectRatio === 0) {
-    // We use a typed array (as opposed to a string) because IndexedDB
-    // implementations may serialize strings using UTF-8 or UTF-16, yielding
-    // larger IndexedDB entries than we'd expect. It's very unlikely that an
-    // IndexedDB implementation would use anything other than the raw buffer to
-    // serialize a typed array.
-    const buffer = new Uint8Array(largeObjectSize);
-
-    // Some IndexedDB implementations, like LevelDB, compress their data blocks
-    // before storing them to disk. We use a simple 32-bit xorshift PRNG, which
-    // should be sufficient to foil any fast generic-purpose compression scheme.
-
-    // 32-bit xorshift - the seed can't be zero
-    let state = 1000 + (cursorIndex * itemCount + itemIndex);
-
-    for (let i = 0; i < largeObjectSize; ++i) {
-      state ^= state << 13;
-      state ^= state >> 17;
-      state ^= state << 5;
-      buffer[i] = state & 0xff;
-    }
-
-    return buffer;
-  }
-  return [cursorIndex, 'small', itemIndex];
-}
-
-// Writes the objects to be read by one cursor. Returns a promise that resolves
-// when the write completes.
-//
-// We want to avoid creating a large transaction, because that is outside the
-// test's scope, and it's a bad practice. So we break up the writes across
-// multiple transactions. For simplicity, each transaction writes all the
-// objects that will be read by a cursor.
-function writeCursorObjects(database, cursorIndex) {
-  return new Promise((resolve, reject) => {
-    const transaction = database.transaction('cache', 'readwrite');
-    transaction.onabort = () => { reject(transaction.error); };
-
-    const store = transaction.objectStore('cache');
-    for (let i = 0; i < itemCount; ++i) {
-      store.put({
-          key: objectKey(cursorIndex, i), value: objectValue(cursorIndex, i)});
-    }
-    transaction.oncomplete = resolve;
-  });
-}
-
-// Returns a promise that resolves when the store has been populated.
-function populateTestStore(testCase, database, cursorCount) {
-  let promiseChain = Promise.resolve();
-
-  for (let i = 0; i < cursorCount; ++i)
-    promiseChain = promiseChain.then(() => writeCursorObjects(database, i));
-
-  return promiseChain;
-}
-
-// Reads cursors in an interleaved fashion, as shown below.
-//
-// Given N cursors, each of which points to the beginning of a K-item sequence,
-// the following accesses will be made.
-//
-// OC(i)    = open cursor i
-// RD(i, j) = read result of cursor i, which should be at item j
-// CC(i)    = continue cursor i
-// |        = wait for onsuccess on the previous OC or CC
-//
-// OC(1)            | RD(1, 1) OC(2) | RD(2, 1) OC(3) | ... | RD(n-1, 1) CC(n) |
-// RD(n, 1)   CC(1) | RD(1, 2) CC(2) | RD(2, 2) CC(3) | ... | RD(n-1, 2) CC(n) |
-// RD(n, 2)   CC(1) | RD(1, 3) CC(2) | RD(2, 3) CC(3) | ... | RD(n-1, 3) CC(n) |
-// ...
-// RD(n, k-1) CC(1) | RD(1, k) CC(2) | RD(2, k) CC(3) | ... | RD(n-1, k) CC(n) |
-// RD(n, k)           done
-function interleaveCursors(testCase, store, cursorCount) {
-  return new Promise((resolve, reject) => {
-    // The cursors used for iteration are stored here so each cursor's onsuccess
-    // handler can call continue() on the next cursor.
-    const cursors = [];
-
-    // The results of IDBObjectStore.openCursor() calls are stored here so we
-    // we can change the requests' onsuccess handler after every
-    // IDBCursor.continue() call.
-    const requests = [];
-
-    const checkCursorState = (cursorIndex, itemIndex) => {
-      const cursor = cursors[cursorIndex];
-      assert_equals(cursor.key, objectKey(cursorIndex, itemIndex));
-      assert_equals(cursor.value.key, objectKey(cursorIndex, itemIndex));
-      assert_equals(
-          cursor.value.value.join('-'),
-          objectValue(cursorIndex, itemIndex).join('-'));
-    };
-
-    const openCursor = (cursorIndex, callback) => {
-      const request = store.openCursor(
-          IDBKeyRange.lowerBound(objectKey(cursorIndex, 0)));
-      requests[cursorIndex] = request;
-
-      request.onsuccess = testCase.step_func(() => {
-        const cursor = request.result;
-        cursors[cursorIndex] = cursor;
-        checkCursorState(cursorIndex, 0);
-        callback();
-      });
-      request.onerror = event => reject(request.error);
-    };
-
-    const readItemFromCursor = (cursorIndex, itemIndex, callback) => {
-      const request = requests[cursorIndex];
-      request.onsuccess = testCase.step_func(() => {
-        const cursor = request.result;
-        cursors[cursorIndex] = cursor;
-        checkCursorState(cursorIndex, itemIndex);
-        callback();
-      });
-
-      const cursor = cursors[cursorIndex];
-      cursor.continue();
-    };
-
-    // We open all the cursors one at a time, then cycle through the cursors and
-    // call continue() on each of them. This access pattern causes maximal
-    // trashing to an LRU cursor cache. Eviction scheme aside, any cache will
-    // have to evict some cursors, and this access pattern verifies that the
-    // cache correctly restores the state of evicted cursors.
-    const steps = [];
-    for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
-      steps.push(openCursor.bind(null, cursorIndex));
-    for (let itemIndex = 1; itemIndex < itemCount; ++itemIndex) {
-      for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
-        steps.push(readItemFromCursor.bind(null, cursorIndex, itemIndex));
-    }
-
-    const runStep = (stepIndex) => {
-      if (stepIndex === steps.length) {
-        resolve();
-        return;
-      }
-      steps[stepIndex](() => { runStep(stepIndex + 1); });
-    };
-    runStep(0);
-  });
-}
-
-for (let cursorCount of [1, 10, 100, 500]) {
-  promise_test(testCase => {
-    return createDatabase(testCase, (database, transaction) => {
-      const store = database.createObjectStore('cache',
-          { keyPath: 'key', autoIncrement: true });
-    }).then(database => {
-      return populateTestStore(testCase, database, cursorCount).then(
-          () => database);
-    }).then(database => {
-      database.close();
-    }).then(() => {
-      return openDatabase(testCase);
-    }).then(database => {
-      const transaction = database.transaction('cache', 'readonly');
-      transaction.onabort = () => { reject(transaction.error); };
-
-      const store = transaction.objectStore('cache');
-      return interleaveCursors(testCase, store, cursorCount).then(
-          () => database);
-    }).then(database => {
-      database.close();
-    });
-  }, `${cursorCount} cursors`);
-}
-</script>
diff --git a/IndexedDB/keypath-special-identifiers.htm b/IndexedDB/keypath-special-identifiers.htm
index 331169b..cb64d0b 100644
--- a/IndexedDB/keypath-special-identifiers.htm
+++ b/IndexedDB/keypath-special-identifiers.htm
@@ -38,11 +38,6 @@
     property: 'lastModified',
     instance: new File([''], '', {lastModified: 123}),
   },
-  {
-    type: 'File',
-    property: 'lastModifiedDate',
-    instance: new File([''], '', {lastModified: 123}),
-  },
 ].forEach(function(testcase) {
   indexeddb_test(
     (t, db) => {
diff --git a/IndexedDB/keypath.htm b/IndexedDB/keypath.htm
index b59d614..4985712 100644
--- a/IndexedDB/keypath.htm
+++ b/IndexedDB/keypath.htm
@@ -125,7 +125,7 @@
             "[Blob.length, Blob.type]");
     }
 
-    // File.name and File.lastModifiedDate is not testable automatically
+    // File.name and File.lastModified is not testable automatically
 
     keypath(['name', 'type'],
         [ { name: "orange", type: "fruit" }, { name: "orange", type: ["telecom", "french"] } ],
diff --git a/README.md b/README.md
index b67e208..db5612e 100644
--- a/README.md
+++ b/README.md
@@ -32,17 +32,22 @@
 and read the [Windows Notes](#windows-notes) section below.
 
 To get the tests running, you need to set up the test domains in your
-[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system). The
-following entries are required:
+[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system).
 
+The necessary content can be generated with `./wpt make-hosts-file`; on
+Windows, you will need to preceed the prior command with `python` or
+the path to the Python binary (`python wpt make-hosts-file`).
+
+For example, on most UNIX-like systems, you can setup the hosts file with:
+
+```bash
+./wpt make-hosts-file | sudo tee -a /etc/hosts
 ```
-127.0.0.1   web-platform.test
-127.0.0.1   www.web-platform.test
-127.0.0.1   www1.web-platform.test
-127.0.0.1   www2.web-platform.test
-127.0.0.1   xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1   xn--lve-6lad.web-platform.test
-0.0.0.0     nonexistent-origin.web-platform.test
+
+And on Windows (this must be run in a PowerShell session with Administrator privileges):
+
+```bash
+python wpt make-hosts-file | Out-File %SystemRoot%\System32\drivers\etc\hosts -Encoding ascii -Append
 ```
 
 If you are behind a proxy, you also need to make sure the domains above are
@@ -53,25 +58,38 @@
 ======================
 
 The test server can be started using
+```
+./wpt serve
+```
 
-    ./wpt serve
+**On Windows**: You will need to preceed the prior command with
+`python` or the path to the python binary.
+```bash
+python wpt serve
+```
 
 This will start HTTP servers on two ports and a websockets server on
-one port. By default one web server starts on port 8000 and the other
-ports are randomly-chosen free ports. Tests must be loaded from the
-*first* HTTP server in the output. To change the ports, copy the
-`config.default.json` file to `config.json` and edit the new file,
-replacing the part that reads:
+one port. By default the web servers start on ports 8000 and 8443 and
+the other ports are randomly-chosen free ports. Tests must be loaded
+from the *first* HTTP server in the output. To change the ports,
+create a `config.json` file in the wpt root directory, and add
+port definitions of your choice e.g.:
 
 ```
-"http": [8000, "auto"]
+{
+  "ports": {
+    "http": [1234, "auto"],
+    "https":[5678]
+  }
+}
 ```
 
-to some port of your choice e.g.
+After your `hosts` file is configured, the servers will be locally accessible at:
 
-```
-"http": [1234, "auto"]
-```
+http://web-platform.test:8000/<br>
+https://web-platform.test:8443/ *
+
+\**See [Trusting Root CA](#trusting-root-ca)*
 
 Running Tests Automatically
 ---------------------------
@@ -82,17 +100,20 @@
 test server already running when calling `wpt run`. The basic command
 line syntax is:
 
-```
+```bash
 ./wpt run product [tests]
 ```
 
 **On Windows**: You will need to preceed the prior command with
 `python` or the path to the python binary.
+```bash
+python wpt run product [tests]
+```
 
 where `product` is currently `firefox` or `chrome` and `[tests]` is a
 list of paths to tests. This will attempt to automatically locate a
 browser instance and install required dependencies. The command is
-very configurable; for examaple to specify a particular binary use
+very configurable; for example to specify a particular binary use
 `wpt run --binary=path product`. The full range of options can be see
 with `wpt run --help` and `wpt run --wptrunner-help`.
 
@@ -114,7 +135,7 @@
 
 On other platforms, download the firefox archive and common.tests.zip
 archive for your platform from
-[https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/](Mozilla CI)
+[Mozilla CI](https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/).
 
 Then extract `certutil[.exe]` from the tests.zip package and
 `libnss3[.so|.dll|.dynlib]` and put the former on your path and the latter on
@@ -187,9 +208,13 @@
 <span id="windows-notes">Windows Notes</span>
 =============================================
 
-On Windows `wpt` commands mut bre prefixed with `python` or the path
+On Windows `wpt` commands must be prefixed with `python` or the path
 to the python binary (if `python` is not in your `%PATH%`).
 
+```bash
+python wpt [command]
+```
+
 Alternatively, you may also use
 [Bash on Ubuntu on Windows](https://msdn.microsoft.com/en-us/commandline/wsl/about)
 in the Windows 10 Anniversary Update build, then access your windows
@@ -203,7 +228,7 @@
 ============
 
 By default pregenerated certificates for the web-platform.test domain
-are provided in the repository. If you wish to generate new
+are provided in [`tools/certs`](tools/certs). If you wish to generate new
 certificates for any reason it's possible to use OpenSSL when starting
 the server, or starting a test run, by providing the
 `--ssl-type=openssl` argument to the `wpt serve` or `wpt run`
@@ -234,12 +259,32 @@
 error when you start wptserve.
 
 Finally, set the path value in the server configuration file to the
-default OpenSSL configuration file location. To do this,
-copy `config.default.json` in the web-platform-tests root to `config.json`.
-Then edit the JSON so that the key `ssl/openssl/base_conf_path` has a
-value that is the path to the OpenSSL config file (typically this
-will be `C:\\OpenSSL-Win32\\bin\\openssl.cfg`).
+default OpenSSL configuration file location. To do this create a file
+called `config.json`.  Then add the OpenSSL configuration below,
+ensuring that the key `ssl/openssl/base_conf_path` has a value that is
+the path to the OpenSSL config file (typically this will be
+`C:\\OpenSSL-Win32\\bin\\openssl.cfg`):
 
+```
+{
+  "ssl": {
+    "type": "openssl",
+    "encrypt_after_connect": false,
+    "openssl": {
+      "openssl_binary": "openssl",
+      "base_path: "_certs",
+      "force_regenerate": false,
+      "base_conf_path": "C:\\OpenSSL-Win32\\bin\\openssl.cfg"
+    },
+  },
+}
+```
+
+### Trusting Root CA
+
+To prevent browser SSL warnings when running HTTPS tests locally, the
+web-platform-tests Root CA file `cacert.pem` in [tools/certs](tools/certs)
+must be added as a trusted certificate in your OS/browser.
 
 Publication
 ===========
@@ -328,7 +373,7 @@
 
 For more details, see the [lint-tool documentation][lint-tool].
 
-[lint-tool]: http://web-platform-tests.org/writing-tests/lint-tool.html
+[lint-tool]: https://web-platform-tests.org/writing-tests/lint-tool.html
 
 Adding command-line scripts ("tools" subdirs)
 ---------------------------------------------
@@ -377,6 +422,14 @@
 pushed here without any further review by supplying a link to the
 upstream review.
 
+Search filters to find things to review:
+
+* [Open PRs (excluding vendor exports)](https://github.com/w3c/web-platform-tests/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+-label%3A%22mozilla%3Agecko-sync%22+-label%3A%22chromium-export%22+-label%3A%22webkit-export%22+-label%3A%22servo-export%22)
+* [Reviewed but still open PRs (excluding vendor exports)](https://github.com/w3c/web-platform-tests/pulls?q=is%3Apr+is%3Aopen+-label%3Amozilla%3Agecko-sync+-label%3Achromium-export+-label%3Awebkit-export+-label%3Aservo-export+review%3Aapproved) (Merge? Something left to fix? Ping other reviewer?)
+* [Open PRs without owners](https://github.com/w3c/web-platform-tests/pulls?q=is%3Apr+is%3Aopen+label%3Astatus%3Aneeds-owners)
+* [Open PRs with label `infra` (excluding vendor exports)](https://github.com/w3c/web-platform-tests/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+label%3Ainfra+-label%3A%22mozilla%3Agecko-sync%22+-label%3A%22chromium-export%22+-label%3A%22webkit-export%22+-label%3A%22servo-export%22)
+* [Open PRs with label `docs` (excluding vendor exports)](https://github.com/w3c/web-platform-tests/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+label%3Adocs+-label%3A%22mozilla%3Agecko-sync%22+-label%3A%22chromium-export%22+-label%3A%22webkit-export%22+-label%3A%22servo-export%22)
+
 Getting Involved
 ================
 
@@ -390,11 +443,11 @@
 
 [contributing]: https://github.com/w3c/web-platform-tests/blob/master/CONTRIBUTING.md
 [ircw3org]: https://www.w3.org/wiki/IRC
-[ircarchive]: http://logs.glob.uno/?c=w3%23testing
+[ircarchive]: https://w3.logbot.info/testing
 [mailarchive]: https://lists.w3.org/Archives/Public/public-test-infra/
 
 Documentation
 =============
 
-* [How to write and review tests](http://web-platform-tests.org/)
+* [How to write and review tests](https://web-platform-tests.org/)
 * [Documentation for the wptserve server](http://wptserve.readthedocs.org/en/latest/)
diff --git a/WebCryptoAPI/WebCryptoAPI.idl b/WebCryptoAPI/WebCryptoAPI.idl
deleted file mode 100644
index 132c6d0..0000000
--- a/WebCryptoAPI/WebCryptoAPI.idl
+++ /dev/null
@@ -1,277 +0,0 @@
-[NoInterfaceObject]
-interface GlobalCrypto {
-  readonly attribute Crypto crypto;
-};
-
-//Window implements GlobalCrypto;
-//WorkerGlobalScope implements GlobalCrypto;
-
-[Exposed=(Window,Worker)]
-interface Crypto {
-  readonly attribute SubtleCrypto subtle;
-  ArrayBufferView getRandomValues(ArrayBufferView array);
-};
-
-typedef (object or DOMString) AlgorithmIdentifier;
-
-typedef AlgorithmIdentifier HashAlgorithmIdentifier;
-
-dictionary Algorithm {
-  required DOMString name;
-};
-
-dictionary KeyAlgorithm {
-  required DOMString name;
-};
-
-enum KeyType { "public", "private", "secret" };
-
-enum KeyUsage { "encrypt", "decrypt", "sign", "verify", "deriveKey", "deriveBits", "wrapKey", "unwrapKey" };
-
-[Exposed=(Window,Worker)]
-interface CryptoKey {
-  readonly attribute KeyType type;
-  readonly attribute boolean extractable;
-  readonly attribute object algorithm;
-  readonly attribute object usages;
-};
-
-
-enum KeyFormat { "raw", "spki", "pkcs8", "jwk" };
-
-[Exposed=(Window,Worker)]
-interface SubtleCrypto {
-  Promise<any> encrypt(AlgorithmIdentifier algorithm,
-                       CryptoKey key,
-                       BufferSource data);
-  Promise<any> decrypt(AlgorithmIdentifier algorithm,
-                       CryptoKey key,
-                       BufferSource data);
-  Promise<any> sign(AlgorithmIdentifier algorithm,
-                    CryptoKey key,
-                    BufferSource data);
-  Promise<any> verify(AlgorithmIdentifier algorithm,
-                      CryptoKey key,
-                      BufferSource signature,
-                      BufferSource data);
-  Promise<any> digest(AlgorithmIdentifier algorithm,
-                      BufferSource data);
-
-  Promise<any> generateKey(AlgorithmIdentifier algorithm,
-                          boolean extractable,
-                          sequence<KeyUsage> keyUsages );
-  Promise<any> deriveKey(AlgorithmIdentifier algorithm,
-                         CryptoKey baseKey,
-                         AlgorithmIdentifier derivedKeyType,
-                         boolean extractable,
-                         sequence<KeyUsage> keyUsages );
-  Promise<any> deriveBits(AlgorithmIdentifier algorithm,
-                          CryptoKey baseKey,
-                          unsigned long length);
-
-  Promise<any> importKey(KeyFormat format,
-                         (BufferSource or JsonWebKey) keyData,
-                         AlgorithmIdentifier algorithm,
-                         boolean extractable,
-                         sequence<KeyUsage> keyUsages );
-  Promise<any> exportKey(KeyFormat format, CryptoKey key);
-
-  Promise<any> wrapKey(KeyFormat format,
-                       CryptoKey key,
-                       CryptoKey wrappingKey,
-                       AlgorithmIdentifier wrapAlgorithm);
-  Promise<any> unwrapKey(KeyFormat format,
-                         BufferSource wrappedKey,
-                         CryptoKey unwrappingKey,
-                         AlgorithmIdentifier unwrapAlgorithm,
-                         AlgorithmIdentifier unwrappedKeyAlgorithm,
-                         boolean extractable,
-                         sequence<KeyUsage> keyUsages );
-};
-
-dictionary RsaOtherPrimesInfo {
- // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
-  DOMString r;
-  DOMString d;
-  DOMString t;
-};
-
-dictionary JsonWebKey {
-  // The following fields are defined in Section 3.1 of JSON Web Key
-  DOMString kty;
-  DOMString use;
-  sequence<DOMString> key_ops;
-  DOMString alg;
-
-  // The following fields are defined in JSON Web Key Parameters Registration
-  boolean ext;
-
-  // The following fields are defined in Section 6 of JSON Web Algorithms
-  DOMString crv;
-  DOMString x;
-  DOMString y;
-  DOMString d;
-  DOMString n;
-  DOMString e;
-  DOMString p;
-  DOMString q;
-  DOMString dp;
-  DOMString dq;
-  DOMString qi;
-  sequence<RsaOtherPrimesInfo> oth;
-  DOMString k;
-};
-
-typedef Uint8Array BigInteger;
-
-dictionary CryptoKeyPair {
-  CryptoKey publicKey;
-  CryptoKey privateKey;
-};
-
-dictionary RsaKeyGenParams : Algorithm {
-  // The length, in bits, of the RSA modulus
-  [EnforceRange] required unsigned long modulusLength;
-  // The RSA public exponent
-  required BigInteger publicExponent;
-};
-
-dictionary RsaHashedKeyGenParams : RsaKeyGenParams {
-  // The hash algorithm to use
-  required HashAlgorithmIdentifier hash;
-};
-
-dictionary RsaKeyAlgorithm : KeyAlgorithm {
-  // The length, in bits, of the RSA modulus
-  required unsigned long modulusLength;
-  // The RSA public exponent
-  required BigInteger publicExponent;
-};
-
-dictionary RsaHashedKeyAlgorithm : RsaKeyAlgorithm {
-  // The hash algorithm that is used with this key
-  required KeyAlgorithm hash;
-};
-
-dictionary RsaHashedImportParams {
-  // The hash algorithm to use
-  required HashAlgorithmIdentifier hash;
-};
-
-dictionary RsaPssParams : Algorithm {
-// The desired length of the random salt
-[EnforceRange] required unsigned long saltLength;
-};
-
-dictionary RsaOaepParams : Algorithm {
-// The optional label/application data to associate with the message
-BufferSource label;
-};
-
-dictionary EcdsaParams : Algorithm {
-// The hash algorithm to use
-required HashAlgorithmIdentifier hash;
-};
-
-typedef DOMString NamedCurve;
-
-dictionary EcKeyGenParams : Algorithm {
-// A named curve
-required NamedCurve namedCurve;
-};
-
-dictionary EcKeyAlgorithm : KeyAlgorithm {
-// The named curve that the key uses
-required NamedCurve namedCurve;
-};
-
-dictionary EcKeyImportParams : Algorithm {
-// A named curve
-required NamedCurve namedCurve;
-};
-
-dictionary EcdhKeyDeriveParams : Algorithm {
-// The peer's EC public key.
-required CryptoKey public;
-};
-
-dictionary AesCtrParams : Algorithm {
-// The initial value of the counter block. counter MUST be 16 bytes
-// (the AES block size). The counter bits are the rightmost length
-// bits of the counter block. The rest of the counter block is for
-// the nonce. The counter bits are incremented using the standard
-// incrementing function specified in NIST SP 800-38A Appendix B.1:
-// the counter bits are interpreted as a big-endian integer and
-// incremented by one.
-required BufferSource counter;
-// The length, in bits, of the rightmost part of the counter block
-// that is incremented.
-[EnforceRange] required octet length;
-};
-
-dictionary AesKeyAlgorithm : KeyAlgorithm {
-// The length, in bits, of the key.
-required unsigned short length;
-};
-
-dictionary AesKeyGenParams : Algorithm {
-// The length, in bits, of the key.
-[EnforceRange] required unsigned short length;
-};
-
-dictionary AesDerivedKeyParams : Algorithm {
-// The length, in bits, of the key.
-[EnforceRange] required unsigned short length;
-};
-
-dictionary AesCbcParams : Algorithm {
-// The initialization vector. MUST be 16 bytes.
-required BufferSource iv;
-};
-
-dictionary AesGcmParams : Algorithm {
-// The initialization vector to use. May be up to 2^64-1 bytes long.
-required BufferSource iv;
-// The additional authentication data to include.
-BufferSource additionalData;
-// The desired length of the authentication tag. May be 0 - 128.
-[EnforceRange] octet tagLength;
-};
-
-dictionary HmacImportParams : Algorithm {
-// The inner hash function to use.
-HashAlgorithmIdentifier hash;
-// The length (in bits) of the key.
-[EnforceRange] unsigned long length;
-};
-
-dictionary HmacKeyAlgorithm : KeyAlgorithm {
-// The inner hash function to use.
-required KeyAlgorithm hash;
-// The length (in bits) of the key.
-required unsigned long length;
-};
-
-dictionary HmacKeyGenParams : Algorithm {
-// The inner hash function to use.
-required HashAlgorithmIdentifier hash;
-// The length (in bits) of the key to generate. If unspecified, the
-// recommended length will be used, which is the size of the associated hash function's block
-// size.
-[EnforceRange] unsigned long length;
-};
-
-dictionary HkdfCtrParams : Algorithm {
-// The algorithm to use with HMAC (e.g.: SHA-256)
-required HashAlgorithmIdentifier hash;
-// A bit string that corresponds to the label that identifies the purpose for the derived keying material.
-required BufferSource label;
-// A bit string that corresponds to the context of the key derivation, as described in Section 5 of [NIST SP800-108]
-required BufferSource context;
-};
-
-dictionary Pbkdf2Params : Algorithm {
-required BufferSource salt;
-[EnforceRange] required unsigned long iterations;
-required HashAlgorithmIdentifier hash;
-};
diff --git a/WebCryptoAPI/derive_bits_keys/ecdh_bits.worker.js b/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.worker.js
similarity index 100%
rename from WebCryptoAPI/derive_bits_keys/ecdh_bits.worker.js
rename to WebCryptoAPI/derive_bits_keys/ecdh_bits.https.worker.js
diff --git a/WebCryptoAPI/derive_bits_keys/ecdh_keys.worker.js b/WebCryptoAPI/derive_bits_keys/ecdh_keys.https.worker.js
similarity index 100%
rename from WebCryptoAPI/derive_bits_keys/ecdh_keys.worker.js
rename to WebCryptoAPI/derive_bits_keys/ecdh_keys.https.worker.js
diff --git a/WebCryptoAPI/derive_bits_keys/hkdf.worker.js b/WebCryptoAPI/derive_bits_keys/hkdf.https.worker.js
similarity index 100%
rename from WebCryptoAPI/derive_bits_keys/hkdf.worker.js
rename to WebCryptoAPI/derive_bits_keys/hkdf.https.worker.js
diff --git a/WebCryptoAPI/derive_bits_keys/pbkdf2.worker.js b/WebCryptoAPI/derive_bits_keys/pbkdf2.https.worker.js
similarity index 100%
rename from WebCryptoAPI/derive_bits_keys/pbkdf2.worker.js
rename to WebCryptoAPI/derive_bits_keys/pbkdf2.https.worker.js
diff --git a/WebCryptoAPI/digest/digest.worker.js b/WebCryptoAPI/digest/digest.https.worker.js
similarity index 100%
rename from WebCryptoAPI/digest/digest.worker.js
rename to WebCryptoAPI/digest/digest.https.worker.js
diff --git a/WebCryptoAPI/encrypt_decrypt/aes_cbc.worker.js b/WebCryptoAPI/encrypt_decrypt/aes_cbc.https.worker.js
similarity index 100%
rename from WebCryptoAPI/encrypt_decrypt/aes_cbc.worker.js
rename to WebCryptoAPI/encrypt_decrypt/aes_cbc.https.worker.js
diff --git a/WebCryptoAPI/encrypt_decrypt/aes_ctr.worker.js b/WebCryptoAPI/encrypt_decrypt/aes_ctr.https.worker.js
similarity index 100%
rename from WebCryptoAPI/encrypt_decrypt/aes_ctr.worker.js
rename to WebCryptoAPI/encrypt_decrypt/aes_ctr.https.worker.js
diff --git a/WebCryptoAPI/encrypt_decrypt/aes_gcm.worker.js b/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.worker.js
similarity index 100%
rename from WebCryptoAPI/encrypt_decrypt/aes_gcm.worker.js
rename to WebCryptoAPI/encrypt_decrypt/aes_gcm.https.worker.js
diff --git a/WebCryptoAPI/encrypt_decrypt/rsa.worker.js b/WebCryptoAPI/encrypt_decrypt/rsa.https.worker.js
similarity index 100%
rename from WebCryptoAPI/encrypt_decrypt/rsa.worker.js
rename to WebCryptoAPI/encrypt_decrypt/rsa.https.worker.js
diff --git a/WebCryptoAPI/generateKey/failures_AES-CBC.https.any.js b/WebCryptoAPI/generateKey/failures_AES-CBC.https.any.js
new file mode 100644
index 0000000..19c9fb2
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_AES-CBC.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["AES-CBC"]);
diff --git a/WebCryptoAPI/generateKey/failures_AES-CBC.https.worker.js b/WebCryptoAPI/generateKey/failures_AES-CBC.https.worker.js
deleted file mode 100644
index 7bb3e7f..0000000
--- a/WebCryptoAPI/generateKey/failures_AES-CBC.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["AES-CBC"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_AES-CTR.https.any.js b/WebCryptoAPI/generateKey/failures_AES-CTR.https.any.js
new file mode 100644
index 0000000..2f8a0b3
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_AES-CTR.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["AES-CTR"]);
diff --git a/WebCryptoAPI/generateKey/failures_AES-CTR.https.worker.js b/WebCryptoAPI/generateKey/failures_AES-CTR.https.worker.js
deleted file mode 100644
index f6766b2..0000000
--- a/WebCryptoAPI/generateKey/failures_AES-CTR.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["AES-CTR"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_AES-GCM.https.any.js b/WebCryptoAPI/generateKey/failures_AES-GCM.https.any.js
new file mode 100644
index 0000000..bb0ab46
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_AES-GCM.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["AES-GCM"]);
diff --git a/WebCryptoAPI/generateKey/failures_AES-GCM.https.worker.js b/WebCryptoAPI/generateKey/failures_AES-GCM.https.worker.js
deleted file mode 100644
index 1383e58..0000000
--- a/WebCryptoAPI/generateKey/failures_AES-GCM.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["AES-GCM"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_AES-KW.https.any.js b/WebCryptoAPI/generateKey/failures_AES-KW.https.any.js
new file mode 100644
index 0000000..54d685f
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_AES-KW.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["AES-KW"]);
diff --git a/WebCryptoAPI/generateKey/failures_AES-KW.https.worker.js b/WebCryptoAPI/generateKey/failures_AES-KW.https.worker.js
deleted file mode 100644
index 04f5bb6..0000000
--- a/WebCryptoAPI/generateKey/failures_AES-KW.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["AES-KW"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_ECDH.https.any.js b/WebCryptoAPI/generateKey/failures_ECDH.https.any.js
new file mode 100644
index 0000000..4c13c5a
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_ECDH.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["ECDH"]);
diff --git a/WebCryptoAPI/generateKey/failures_ECDH.https.worker.js b/WebCryptoAPI/generateKey/failures_ECDH.https.worker.js
deleted file mode 100644
index a7e6a72..0000000
--- a/WebCryptoAPI/generateKey/failures_ECDH.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["ECDH"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_ECDSA.https.any.js b/WebCryptoAPI/generateKey/failures_ECDSA.https.any.js
new file mode 100644
index 0000000..74cd480
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_ECDSA.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["ECDSA"]);
diff --git a/WebCryptoAPI/generateKey/failures_ECDSA.https.worker.js b/WebCryptoAPI/generateKey/failures_ECDSA.https.worker.js
deleted file mode 100644
index a1f4711..0000000
--- a/WebCryptoAPI/generateKey/failures_ECDSA.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["ECDSA"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_HMAC.https.any.js b/WebCryptoAPI/generateKey/failures_HMAC.https.any.js
new file mode 100644
index 0000000..708d33b
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_HMAC.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["HMAC"]);
diff --git a/WebCryptoAPI/generateKey/failures_HMAC.https.worker.js b/WebCryptoAPI/generateKey/failures_HMAC.https.worker.js
deleted file mode 100644
index 9dfbbb9..0000000
--- a/WebCryptoAPI/generateKey/failures_HMAC.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["HMAC"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_RSA-OAEP.https.any.js b/WebCryptoAPI/generateKey/failures_RSA-OAEP.https.any.js
new file mode 100644
index 0000000..4ec0ec2
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_RSA-OAEP.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["RSA-OAEP"]);
diff --git a/WebCryptoAPI/generateKey/failures_RSA-OAEP.https.worker.js b/WebCryptoAPI/generateKey/failures_RSA-OAEP.https.worker.js
deleted file mode 100644
index 39de6d6..0000000
--- a/WebCryptoAPI/generateKey/failures_RSA-OAEP.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["RSA-OAEP"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_RSA-PSS.https.any.js b/WebCryptoAPI/generateKey/failures_RSA-PSS.https.any.js
new file mode 100644
index 0000000..557b578
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_RSA-PSS.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["RSA-PSS"]);
diff --git a/WebCryptoAPI/generateKey/failures_RSA-PSS.https.worker.js b/WebCryptoAPI/generateKey/failures_RSA-PSS.https.worker.js
deleted file mode 100644
index 8b1110d..0000000
--- a/WebCryptoAPI/generateKey/failures_RSA-PSS.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["RSA-PSS"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/failures_RSASSA-PKCS1-v1_5.https.any.js b/WebCryptoAPI/generateKey/failures_RSASSA-PKCS1-v1_5.https.any.js
new file mode 100644
index 0000000..45771bf
--- /dev/null
+++ b/WebCryptoAPI/generateKey/failures_RSASSA-PKCS1-v1_5.https.any.js
@@ -0,0 +1,4 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=failures.js
+run_test(["RSASSA-PKCS1-v1_5"]);
diff --git a/WebCryptoAPI/generateKey/failures_RSASSA-PKCS1-v1_5.https.worker.js b/WebCryptoAPI/generateKey/failures_RSASSA-PKCS1-v1_5.https.worker.js
deleted file mode 100644
index 4df0b89..0000000
--- a/WebCryptoAPI/generateKey/failures_RSASSA-PKCS1-v1_5.https.worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("failures.js");
-run_test(["RSASSA-PKCS1-v1_5"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes.js b/WebCryptoAPI/generateKey/successes.js
index 6a74c01..b99e44d 100644
--- a/WebCryptoAPI/generateKey/successes.js
+++ b/WebCryptoAPI/generateKey/successes.js
@@ -70,7 +70,6 @@
         }, testTag + ": generateKey" + parameterString(algorithm, extractable, usages));
     }
 
-
     // Test all valid sets of parameters for successful
     // key generation.
     testVectors.forEach(function(vector) {
@@ -78,7 +77,7 @@
             allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
                 allValidUsages(vector.usages, false, vector.mandatoryUsages).forEach(function(usages) {
                     [false, true].forEach(function(extractable) {
-                        testSuccess(algorithm, extractable, usages, vector.resultType, "Success");
+                        subsetTest(testSuccess, algorithm, extractable, usages, vector.resultType, "Success");
                     });
                 });
             });
diff --git a/WebCryptoAPI/generateKey/successes_AES-CBC.https.any.js b/WebCryptoAPI/generateKey/successes_AES-CBC.https.any.js
new file mode 100644
index 0000000..6feb60e
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_AES-CBC.https.any.js
@@ -0,0 +1,5 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["AES-CBC"]);
diff --git a/WebCryptoAPI/generateKey/successes_AES-CBC.https.worker.js b/WebCryptoAPI/generateKey/successes_AES-CBC.https.worker.js
deleted file mode 100644
index 0710c9e..0000000
--- a/WebCryptoAPI/generateKey/successes_AES-CBC.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["AES-CBC"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_AES-CTR.https.any.js b/WebCryptoAPI/generateKey/successes_AES-CTR.https.any.js
new file mode 100644
index 0000000..7b4f2df
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_AES-CTR.https.any.js
@@ -0,0 +1,5 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["AES-CTR"]);
diff --git a/WebCryptoAPI/generateKey/successes_AES-CTR.https.worker.js b/WebCryptoAPI/generateKey/successes_AES-CTR.https.worker.js
deleted file mode 100644
index 51529f0..0000000
--- a/WebCryptoAPI/generateKey/successes_AES-CTR.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["AES-CTR"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_AES-GCM.https.any.js b/WebCryptoAPI/generateKey/successes_AES-GCM.https.any.js
new file mode 100644
index 0000000..8e7bc92
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_AES-GCM.https.any.js
@@ -0,0 +1,5 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["AES-GCM"]);
diff --git a/WebCryptoAPI/generateKey/successes_AES-GCM.https.worker.js b/WebCryptoAPI/generateKey/successes_AES-GCM.https.worker.js
deleted file mode 100644
index 1f6a1bc..0000000
--- a/WebCryptoAPI/generateKey/successes_AES-GCM.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["AES-GCM"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js b/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js
new file mode 100644
index 0000000..5eb0233
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js
@@ -0,0 +1,5 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["AES-KW"]);
diff --git a/WebCryptoAPI/generateKey/successes_AES-KW.https.worker.js b/WebCryptoAPI/generateKey/successes_AES-KW.https.worker.js
deleted file mode 100644
index 058cf83..0000000
--- a/WebCryptoAPI/generateKey/successes_AES-KW.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["AES-KW"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_ECDH.https.any.js b/WebCryptoAPI/generateKey/successes_ECDH.https.any.js
new file mode 100644
index 0000000..87590d2
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_ECDH.https.any.js
@@ -0,0 +1,5 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["ECDH"]);
diff --git a/WebCryptoAPI/generateKey/successes_ECDH.https.worker.js b/WebCryptoAPI/generateKey/successes_ECDH.https.worker.js
deleted file mode 100644
index a21b7d0..0000000
--- a/WebCryptoAPI/generateKey/successes_ECDH.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["ECDH"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_ECDSA.https.any.js b/WebCryptoAPI/generateKey/successes_ECDSA.https.any.js
new file mode 100644
index 0000000..734d869
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_ECDSA.https.any.js
@@ -0,0 +1,5 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["ECDSA"]);
diff --git a/WebCryptoAPI/generateKey/successes_ECDSA.https.worker.js b/WebCryptoAPI/generateKey/successes_ECDSA.https.worker.js
deleted file mode 100644
index 13f7e35..0000000
--- a/WebCryptoAPI/generateKey/successes_ECDSA.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["ECDSA"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_HMAC.https.any.js b/WebCryptoAPI/generateKey/successes_HMAC.https.any.js
new file mode 100644
index 0000000..bc106f3
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_HMAC.https.any.js
@@ -0,0 +1,5 @@
+// META: timeout=long
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["HMAC"]);
diff --git a/WebCryptoAPI/generateKey/successes_HMAC.https.worker.js b/WebCryptoAPI/generateKey/successes_HMAC.https.worker.js
deleted file mode 100644
index 2b50b90..0000000
--- a/WebCryptoAPI/generateKey/successes_HMAC.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["HMAC"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.any.js b/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.any.js
new file mode 100644
index 0000000..b041228
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.any.js
@@ -0,0 +1,21 @@
+// META: timeout=long
+// META: variant=?1-10
+// META: variant=?11-20
+// META: variant=?21-30
+// META: variant=?31-40
+// META: variant=?41-50
+// META: variant=?51-60
+// META: variant=?61-70
+// META: variant=?71-80
+// META: variant=?81-90
+// META: variant=?91-100
+// META: variant=?101-110
+// META: variant=?111-120
+// META: variant=?121-130
+// META: variant=?131-140
+// META: variant=?141-150
+// META: variant=?151-last
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["RSA-OAEP"]);
diff --git a/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.worker.js b/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.worker.js
deleted file mode 100644
index 4b3538d..0000000
--- a/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["RSA-OAEP"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_RSA-PSS.https.any.js b/WebCryptoAPI/generateKey/successes_RSA-PSS.https.any.js
new file mode 100644
index 0000000..ea91c8f
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_RSA-PSS.https.any.js
@@ -0,0 +1,9 @@
+// META: timeout=long
+// META: variant=?1-10
+// META: variant=?11-20
+// META: variant=?21-30
+// META: variant=?31-last
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["RSA-PSS"]);
diff --git a/WebCryptoAPI/generateKey/successes_RSA-PSS.https.worker.js b/WebCryptoAPI/generateKey/successes_RSA-PSS.https.worker.js
deleted file mode 100644
index 52364f5..0000000
--- a/WebCryptoAPI/generateKey/successes_RSA-PSS.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["RSA-PSS"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js b/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js
new file mode 100644
index 0000000..fc785f9
--- /dev/null
+++ b/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js
@@ -0,0 +1,9 @@
+// META: timeout=long
+// META: variant=?1-10
+// META: variant=?11-20
+// META: variant=?21-30
+// META: variant=?31-last
+// META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
+// META: script=successes.js
+run_test(["RSASSA-PKCS1-v1_5"]);
diff --git a/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.worker.js b/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.worker.js
deleted file mode 100644
index 4c6a02a..0000000
--- a/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.worker.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// META: timeout=long
-importScripts("/resources/testharness.js");
-importScripts("../util/helpers.js");
-importScripts("successes.js");
-
-run_test(["RSASSA-PKCS1-v1_5"]);
-done();
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_aes-cbc.https.html b/WebCryptoAPI/generateKey/test_aes-cbc.https.html
deleted file mode 100644
index a3054a5..0000000
--- a/WebCryptoAPI/generateKey/test_aes-cbc.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test("AES-CBC");
-</script>
diff --git a/WebCryptoAPI/generateKey/test_aes-ctr.https.html b/WebCryptoAPI/generateKey/test_aes-ctr.https.html
deleted file mode 100644
index bb1abf0..0000000
--- a/WebCryptoAPI/generateKey/test_aes-ctr.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test("AES-CTR");
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_AES-CBC.https.html b/WebCryptoAPI/generateKey/test_failures_AES-CBC.https.html
deleted file mode 100644
index 7650908..0000000
--- a/WebCryptoAPI/generateKey/test_failures_AES-CBC.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["AES-CBC"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_AES-CTR.https.html b/WebCryptoAPI/generateKey/test_failures_AES-CTR.https.html
deleted file mode 100644
index f08737c..0000000
--- a/WebCryptoAPI/generateKey/test_failures_AES-CTR.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["AES-CTR"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_AES-GCM.https.html b/WebCryptoAPI/generateKey/test_failures_AES-GCM.https.html
deleted file mode 100644
index 4ef6a77..0000000
--- a/WebCryptoAPI/generateKey/test_failures_AES-GCM.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["AES-GCM"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_AES-KW.https.html b/WebCryptoAPI/generateKey/test_failures_AES-KW.https.html
deleted file mode 100644
index 7cbb4f8..0000000
--- a/WebCryptoAPI/generateKey/test_failures_AES-KW.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["AES-KW"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_ECDH.https.html b/WebCryptoAPI/generateKey/test_failures_ECDH.https.html
deleted file mode 100644
index c606a5b..0000000
--- a/WebCryptoAPI/generateKey/test_failures_ECDH.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["ECDH"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_ECDSA.https.html b/WebCryptoAPI/generateKey/test_failures_ECDSA.https.html
deleted file mode 100644
index 8b742e8..0000000
--- a/WebCryptoAPI/generateKey/test_failures_ECDSA.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["ECDSA"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_HMAC.https.html b/WebCryptoAPI/generateKey/test_failures_HMAC.https.html
deleted file mode 100644
index e037f0d..0000000
--- a/WebCryptoAPI/generateKey/test_failures_HMAC.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["HMAC"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_RSA-OAEP.https.html b/WebCryptoAPI/generateKey/test_failures_RSA-OAEP.https.html
deleted file mode 100644
index ddd63be..0000000
--- a/WebCryptoAPI/generateKey/test_failures_RSA-OAEP.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["RSA-OAEP"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_RSA-PSS.https.html b/WebCryptoAPI/generateKey/test_failures_RSA-PSS.https.html
deleted file mode 100644
index 2814b34..0000000
--- a/WebCryptoAPI/generateKey/test_failures_RSA-PSS.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["RSA-PSS"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_failures_RSASSA-PKCS1-v1_5.https.html b/WebCryptoAPI/generateKey/test_failures_RSASSA-PKCS1-v1_5.https.html
deleted file mode 100644
index 0e28914..0000000
--- a/WebCryptoAPI/generateKey/test_failures_RSASSA-PKCS1-v1_5.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() for Failures</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="failures.js"></script>
-
-<h1>generateKey Tests for Bad Parameters</h1>
-
-<div id="log"></div>
-<script>
-run_test(["RSASSA-PKCS1-v1_5"]);
-</script>
diff --git a/WebCryptoAPI/generateKey/test_successes_AES-CBC.https.html b/WebCryptoAPI/generateKey/test_successes_AES-CBC.https.html
deleted file mode 100644
index af80935..0000000
--- a/WebCryptoAPI/generateKey/test_successes_AES-CBC.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["AES-CBC"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_AES-CTR.https.html b/WebCryptoAPI/generateKey/test_successes_AES-CTR.https.html
deleted file mode 100644
index 3608a4f..0000000
--- a/WebCryptoAPI/generateKey/test_successes_AES-CTR.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["AES-CTR"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_AES-GCM.https.html b/WebCryptoAPI/generateKey/test_successes_AES-GCM.https.html
deleted file mode 100644
index eb7c6b5..0000000
--- a/WebCryptoAPI/generateKey/test_successes_AES-GCM.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["AES-GCM"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_AES-KW.https.html b/WebCryptoAPI/generateKey/test_successes_AES-KW.https.html
deleted file mode 100644
index 9beab20..0000000
--- a/WebCryptoAPI/generateKey/test_successes_AES-KW.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["AES-KW"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_ECDH.https.html b/WebCryptoAPI/generateKey/test_successes_ECDH.https.html
deleted file mode 100644
index 9871f57..0000000
--- a/WebCryptoAPI/generateKey/test_successes_ECDH.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["ECDH"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_ECDSA.https.html b/WebCryptoAPI/generateKey/test_successes_ECDSA.https.html
deleted file mode 100644
index f32a556..0000000
--- a/WebCryptoAPI/generateKey/test_successes_ECDSA.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["ECDSA"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_HMAC.https.html b/WebCryptoAPI/generateKey/test_successes_HMAC.https.html
deleted file mode 100644
index 819b35d..0000000
--- a/WebCryptoAPI/generateKey/test_successes_HMAC.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["HMAC"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_RSA-OAEP.https.html b/WebCryptoAPI/generateKey/test_successes_RSA-OAEP.https.html
deleted file mode 100644
index db58fd6..0000000
--- a/WebCryptoAPI/generateKey/test_successes_RSA-OAEP.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["RSA-OAEP"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_RSA-PSS.https.html b/WebCryptoAPI/generateKey/test_successes_RSA-PSS.https.html
deleted file mode 100644
index d6a3c1b..0000000
--- a/WebCryptoAPI/generateKey/test_successes_RSA-PSS.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["RSA-PSS"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/generateKey/test_successes_RSASSA-PKCS1-v1_5.https.html b/WebCryptoAPI/generateKey/test_successes_RSASSA-PKCS1-v1_5.https.html
deleted file mode 100644
index 3567879..0000000
--- a/WebCryptoAPI/generateKey/test_successes_RSASSA-PKCS1-v1_5.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<meta name="timeout" content="long">
-<title>WebCryptoAPI: generateKey() Successful Calls</title>
-<link rel="author" title="Charles Engelke" href="mailto:w3c@engelke.com">
-<link rel="help" href="https://www.w3.org/TR/WebCryptoAPI/#dfn-SubtleCrypto-method-generateKey">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<script src="/WebCryptoAPI/util/helpers.js"></script>
-<script src="successes.js"></script>
-
-<h1>generateKey Tests for Good Parameters</h1>
-<p>
-    <strong>Warning!</strong> RSA key generation is intrinsically
-    very slow, so the related tests can take up to
-    several minutes to complete, depending on browser!
-</p>
-
-<div id="log"></div>
-<script>
-run_test(["RSASSA-PKCS1-v1_5"]);
-</script>
\ No newline at end of file
diff --git a/WebCryptoAPI/idlharness.html b/WebCryptoAPI/idlharness.html
deleted file mode 100644
index 81e1e04..0000000
--- a/WebCryptoAPI/idlharness.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!doctype html>
-<html>
-<head>
-<meta charset=utf-8>
-<title>IDL check of WebCrypto</title>
-<link rel="help" href="https://w3c.github.io/webcrypto/Overview.html#crypto-interface">
-
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-</head>
-<body>
-
-<h1 class="instructions">Description</h1>
-
-<p class="instructions">This test verifies that the implementations of the WebCrypto API match with its WebIDL definition.</p>
-
-<div id='log'></div>
-
-<script>
-var file_input;
-setup(function() {
-    var idl_array = new IdlArray();
-
-    var request = new XMLHttpRequest();
-    request.open("GET", "WebCryptoAPI.idl");
-    request.send();
-    request.onload = function() {
-        var idls = request.responseText;
-
-        idl_array.add_untested_idls("[PrimaryGlobal] interface Window { };");
-
-        idl_array.add_untested_idls("interface ArrayBuffer {};");
-        idl_array.add_untested_idls("interface ArrayBufferView {};");
-
-        idl_array.add_idls(idls);
-
-        idl_array.add_objects({"Crypto":["crypto"], "SubtleCrypto":["crypto.subtle"]});
-
-        idl_array.test();
-        done();
-  };
-}, {explicit_done: true});
-</script>
diff --git a/WebCryptoAPI/idlharness.https.html b/WebCryptoAPI/idlharness.https.html
index 81e1e04..c4fd0f3 100644
--- a/WebCryptoAPI/idlharness.https.html
+++ b/WebCryptoAPI/idlharness.https.html
@@ -24,16 +24,11 @@
     var idl_array = new IdlArray();
 
     var request = new XMLHttpRequest();
-    request.open("GET", "WebCryptoAPI.idl");
+    request.open("GET", "../interfaces/WebCryptoAPI.idl");
     request.send();
     request.onload = function() {
         var idls = request.responseText;
 
-        idl_array.add_untested_idls("[PrimaryGlobal] interface Window { };");
-
-        idl_array.add_untested_idls("interface ArrayBuffer {};");
-        idl_array.add_untested_idls("interface ArrayBufferView {};");
-
         idl_array.add_idls(idls);
 
         idl_array.add_objects({"Crypto":["crypto"], "SubtleCrypto":["crypto.subtle"]});
diff --git a/WebCryptoAPI/idlharness.https.worker.js b/WebCryptoAPI/idlharness.https.worker.js
new file mode 100644
index 0000000..b4bbdda
--- /dev/null
+++ b/WebCryptoAPI/idlharness.https.worker.js
@@ -0,0 +1,22 @@
+importScripts("/resources/testharness.js");
+importScripts("/resources/WebIDLParser.js", "/resources/idlharness.js");
+
+var request = new XMLHttpRequest();
+request.open("GET", "../interfaces/WebCryptoAPI.idl");
+request.send();
+request.onload = function() {
+    var idl_array = new IdlArray();
+    var idls = request.responseText;
+
+    idl_array.add_untested_idls("[Global] interface Window { };");
+
+    idl_array.add_untested_idls("interface ArrayBuffer {};");
+    idl_array.add_untested_idls("interface ArrayBufferView {};");
+
+    idl_array.add_idls(idls);
+
+    idl_array.add_objects({"Crypto":["crypto"], "SubtleCrypto":["crypto.subtle"]});
+
+    idl_array.test();
+    done();
+};
diff --git a/WebCryptoAPI/idlharness.worker.js b/WebCryptoAPI/idlharness.worker.js
deleted file mode 100644
index ba03301..0000000
--- a/WebCryptoAPI/idlharness.worker.js
+++ /dev/null
@@ -1,22 +0,0 @@
-importScripts("/resources/testharness.js");
-importScripts("/resources/WebIDLParser.js", "/resources/idlharness.js");
-
-var request = new XMLHttpRequest();
-request.open("GET", "WebCryptoAPI.idl");
-request.send();
-request.onload = function() {
-    var idl_array = new IdlArray();
-    var idls = request.responseText;
-
-    idl_array.add_untested_idls("[Global] interface Window { };");
-
-    idl_array.add_untested_idls("interface ArrayBuffer {};");
-    idl_array.add_untested_idls("interface ArrayBufferView {};");
-
-    idl_array.add_idls(idls);
-
-    idl_array.add_objects({"Crypto":["crypto"], "SubtleCrypto":["crypto.subtle"]});
-
-    idl_array.test();
-    done();
-};
diff --git a/WebCryptoAPI/import_export/ec_importKey.worker.js b/WebCryptoAPI/import_export/ec_importKey.https.worker.js
similarity index 100%
rename from WebCryptoAPI/import_export/ec_importKey.worker.js
rename to WebCryptoAPI/import_export/ec_importKey.https.worker.js
diff --git a/WebCryptoAPI/import_export/rsa_importKey.worker.js b/WebCryptoAPI/import_export/rsa_importKey.https.worker.js
similarity index 100%
rename from WebCryptoAPI/import_export/rsa_importKey.worker.js
rename to WebCryptoAPI/import_export/rsa_importKey.https.worker.js
diff --git a/WebCryptoAPI/import_export/symmetric_importKey.worker.js b/WebCryptoAPI/import_export/symmetric_importKey.https.worker.js
similarity index 100%
rename from WebCryptoAPI/import_export/symmetric_importKey.worker.js
rename to WebCryptoAPI/import_export/symmetric_importKey.https.worker.js
diff --git a/WebCryptoAPI/sign_verify/ecdsa.worker.js b/WebCryptoAPI/sign_verify/ecdsa.https.worker.js
similarity index 100%
rename from WebCryptoAPI/sign_verify/ecdsa.worker.js
rename to WebCryptoAPI/sign_verify/ecdsa.https.worker.js
diff --git a/WebCryptoAPI/sign_verify/hmac.worker.js b/WebCryptoAPI/sign_verify/hmac.https.worker.js
similarity index 100%
rename from WebCryptoAPI/sign_verify/hmac.worker.js
rename to WebCryptoAPI/sign_verify/hmac.https.worker.js
diff --git a/WebCryptoAPI/sign_verify/rsa_pkcs.worker.js b/WebCryptoAPI/sign_verify/rsa_pkcs.https.worker.js
similarity index 100%
rename from WebCryptoAPI/sign_verify/rsa_pkcs.worker.js
rename to WebCryptoAPI/sign_verify/rsa_pkcs.https.worker.js
diff --git a/WebCryptoAPI/sign_verify/rsa_pss.worker.js b/WebCryptoAPI/sign_verify/rsa_pss.https.worker.js
similarity index 100%
rename from WebCryptoAPI/sign_verify/rsa_pss.worker.js
rename to WebCryptoAPI/sign_verify/rsa_pss.https.worker.js
diff --git a/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.worker.js b/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.worker.js
similarity index 100%
rename from WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.worker.js
rename to WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.worker.js
diff --git a/WebIDL/ecmascript-binding/default-iterator-object.html b/WebIDL/ecmascript-binding/default-iterator-object.html
new file mode 100644
index 0000000..c7e9188
--- /dev/null
+++ b/WebIDL/ecmascript-binding/default-iterator-object.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Default iterator objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+  const iterator1 = new URLSearchParams()[Symbol.iterator]();
+  const iterator2 = new URLSearchParams().keys();
+  const iterator3 = new URLSearchParams().values();
+  const iterator4 = new URLSearchParams().entries();
+  assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator2));
+  assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator3));
+  assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator4));
+}, "Default iterator objects for an interface have the same prototype");
+
+test(() => {
+  const iterator = new URLSearchParams().entries();
+  assert_equals(Object.prototype.toString.call(iterator), "[object URLSearchParams Iterator]");
+}, "Object.prototype.toString returns correct value");
+
+test(() => {
+  const iterator = new URLSearchParams().entries();
+  assert_equals(iterator[Symbol.toStringTag], "URLSearchParams Iterator");
+  assert_equals(Object.getOwnPropertyDescriptor(iterator, Symbol.toStringTag), undefined);
+}, "@@toStringTag has correct value from prototype");
+</script>
diff --git a/WebIDL/ecmascript-binding/iterator-prototype-object.html b/WebIDL/ecmascript-binding/iterator-prototype-object.html
new file mode 100644
index 0000000..5a935fa
--- /dev/null
+++ b/WebIDL/ecmascript-binding/iterator-prototype-object.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Iterator prototype objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+  const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
+  const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries());
+  assert_equals(Object.getPrototypeOf(iteratorProto), esIteratorPrototype);
+}, "Has %IteratorPrototype% as prototype");
+
+test(() => {
+  const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries());
+  const desc = Object.getOwnPropertyDescriptor(iteratorProto, "next");
+  assert_equals(typeof desc.value, "function");
+  assert_equals(desc.writable, true);
+  assert_equals(desc.enumerable, true);
+  assert_equals(desc.configurable, true);
+}, "next() exists and is writable, enumerable, and configurable");
+
+test(() => {
+  const usp = new URLSearchParams();
+  const iteratorProto = Object.getPrototypeOf(usp.entries());
+
+  assert_throws(new TypeError(), () => {
+    iteratorProto.next();
+  });
+  assert_throws(new TypeError(), () => {
+    iteratorProto.next.call(undefined);
+  });
+  assert_throws(new TypeError(), () => {
+    iteratorProto.next.call(42);
+  });
+  assert_throws(new TypeError(), () => {
+    iteratorProto.next.call(new Headers().entries());
+  });
+}, "next() throws TypeError when called on ineligible receiver");
+
+test(() => {
+  const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries());
+  assert_equals(Object.prototype.toString.call(iteratorProto), "[object URLSearchParams Iterator]");
+}, "Object.prototype.toString returns correct value");
+
+test(() => {
+  const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries());
+  assert_equals(Object.getOwnPropertyDescriptor(iteratorProto, Symbol.toStringTag).value, "URLSearchParams Iterator");
+  // Property attributes have not yet been fully spec'd.
+}, "@@toStringTag has correct value");
+
+test(() => {
+  const iteratorProto1 = Object.getPrototypeOf(new URLSearchParams().entries());
+  const iteratorProto2 = Object.getPrototypeOf(new Headers().entries());
+  assert_not_equals(iteratorProto1, iteratorProto2);
+}, "Is specific to an interface");
+</script>
diff --git a/XMLHttpRequest/abort-after-stop.htm b/XMLHttpRequest/abort-after-stop.htm
deleted file mode 100644
index 24cb5a4..0000000
--- a/XMLHttpRequest/abort-after-stop.htm
+++ /dev/null
@@ -1,30 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>XMLHttpRequest: abort event should fire when stop() method is used</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[3] following::dt[3]/following::dd[1]/p"/>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      var test = async_test();
-      test.step(function() {
-        var client = new XMLHttpRequest();
-        var abortFired = false;
-        client.onabort = test.step_func(function (e) {
-          assert_equals(e.type, 'abort');
-          abortFired = true;
-        });
-        client.open("GET", "resources/delay.py?ms=3000", true);
-        client.send(null);
-        test.step_timeout(() => {
-          assert_equals(abortFired, true);
-          test.done();
-        }, 200);
-        window.stop();
-      });
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/abort-during-open.htm b/XMLHttpRequest/abort-during-open.htm
deleted file mode 100644
index dde94f2..0000000
--- a/XMLHttpRequest/abort-during-open.htm
+++ /dev/null
@@ -1,14 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>XMLHttpRequest: abort() during OPEN</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-abort()-method" data-tested-assertations="following-sibling::ol/li[4] following-sibling::ol/li[5]" />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following-sibling::ol/li[1]" />
-  </head>
-  <body>
-    <div id="log"></div>
-    <script src="abort-during-open.js"></script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/abort-during-open.js b/XMLHttpRequest/abort-during-open.js
deleted file mode 100644
index 26d3f57..0000000
--- a/XMLHttpRequest/abort-during-open.js
+++ /dev/null
@@ -1,18 +0,0 @@
-var test = async_test()
-test.step(function() {
-  var client = new XMLHttpRequest()
-  client.open("GET", "...")
-  client.onreadystatechange = function() {
-    test.step(function() {
-      assert_unreached()
-    })
-  }
-  assert_equals(client.readyState, 1, "before abort()")
-  assert_equals(client.status, 0)
-  assert_equals(client.statusText, "")
-  client.abort()
-  assert_equals(client.readyState, 1, "after abort()")
-  assert_equals(client.status, 0)
-  assert_equals(client.statusText, "")
-})
-test.done()
diff --git a/XMLHttpRequest/abort-during-open.worker.js b/XMLHttpRequest/abort-during-open.worker.js
deleted file mode 100644
index ffb687d..0000000
--- a/XMLHttpRequest/abort-during-open.worker.js
+++ /dev/null
@@ -1,3 +0,0 @@
-importScripts("/resources/testharness.js");
-importScripts("abort-during-open.js");
-done();
diff --git a/XMLHttpRequest/access-control-and-redirects-async-same-origin.htm b/XMLHttpRequest/access-control-and-redirects-async-same-origin.htm
deleted file mode 100644
index 24cc80c..0000000
--- a/XMLHttpRequest/access-control-and-redirects-async-same-origin.htm
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that asynchronous XMLHttpRequests handle redirects according to the CORS standard.</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script>
-    function runTest(test, path, credentials, expectSuccess) {
-      const xhr = new XMLHttpRequest();
-      xhr.withCredentials = credentials;
-      xhr.open("GET", "resources/redirect.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN + path, true);
-
-      xhr.onload = test.step_func_done(function() {
-        assert_true(expectSuccess);
-        assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
-      });
-      xhr.onerror = test.step_func_done(function() {
-        assert_false(expectSuccess);
-        assert_equals(xhr.status, 0);
-      });
-      xhr.send(null);
-    }
-
-    const withoutCredentials = false;
-    const withCredentials = true;
-    const succeeds = true;
-    const fails = false;
-
-    // Test simple same origin requests that receive cross origin redirects.
-
-    // The redirect response passes the access check.
-    async_test(t => {
-      runTest(t, "/XMLHttpRequest/resources/access-control-basic-allow-star.py",
-          withoutCredentials, succeeds)
-    }, "Request without credentials is redirected to a cross-origin response with Access-Control-Allow-Origin=* (with star)");
-
-    // The redirect response fails the access check because credentials were sent.
-    async_test(t => {
-      runTest(t, "/XMLHttpRequest/resources/access-control-basic-allow-star.py",
-          withCredentials, fails)
-    }, "Request with credentials is redirected to a cross-origin response with Access-Control-Allow-Origin=* (with star)");
-
-    // The redirect response passes the access check.
-    async_test(t => {
-      runTest(t, "/XMLHttpRequest/resources/access-control-basic-allow.py",
-          withoutCredentials, succeeds)
-    }, "Request without credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin");
-
-    // The redirect response passes the access check.
-    async_test(t => {
-      runTest(t, "/XMLHttpRequest/resources/access-control-basic-allow.py",
-          withCredentials, succeeds)
-    }, "Request with credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin");
-
-    // forbidding credentials. The redirect response passes the access check.
-    async_test(t => {
-      runTest(t, "/XMLHttpRequest/resources/access-control-basic-allow-no-credentials.py",
-          withoutCredentials, succeeds)
-    }, "Request without credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin (no credentials)");
-
-    // forbidding credentials. The redirect response fails the access check.
-    async_test(t => {
-      runTest(t, "/XMLHttpRequest/resources/access-control-basic-allow-no-credentials.py",
-          withCredentials, fails)
-    }, "Request with credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin (no credentials)");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-and-redirects-async.htm b/XMLHttpRequest/access-control-and-redirects-async.htm
deleted file mode 100644
index bf16c3c..0000000
--- a/XMLHttpRequest/access-control-and-redirects-async.htm
+++ /dev/null
@@ -1,89 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that asynchronous XMLHttpRequests handle redirects according to the CORS standard.</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script>
-    function runTest(test, destination, parameters, customHeader, local, expectSuccess) {
-      const xhr = new XMLHttpRequest();
-      const url = (local ? get_host_info().HTTP_ORIGIN : get_host_info().HTTP_REMOTE_ORIGIN) +
-        "/XMLHttpRequest/resources/redirect-cors.py?location=" + destination + "&" +  parameters;
-
-      xhr.open("GET", url, true);
-
-      if (customHeader)
-        xhr.setRequestHeader("x-test", "test");
-
-      xhr.onload = test.step_func_done(function() {
-        assert_true(expectSuccess);
-        assert_true(xhr.responseText.startsWith("PASS"));
-      });
-      xhr.onerror = test.step_func_done(function() {
-        assert_false(expectSuccess);
-        assert_equals(xhr.status, 0);
-      });
-      xhr.send();
-    }
-
-    const withCustomHeader = true;
-    const withoutCustomHeader = false;
-    const local = true;
-    const remote = false;
-    const succeeds = true;
-    const fails = false;
-
-    // Test simple cross origin requests that receive redirects.
-
-    // The redirect response fails the access check because the redirect lacks a CORS header.
-    async_test(t => {
-      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow-star.py", "",
-          withoutCustomHeader, remote, fails)
-    }, "Request is redirected without CORS headers to a response with Access-Control-Allow-Origin=*");
-
-    // The redirect response passes the access check.
-    async_test(t => {
-      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow-star.py", "allow_origin=true",
-          withoutCustomHeader, remote, succeeds)
-    }, "Request is redirected to a response with Access-Control-Allow-Origin=*");
-
-    // The redirect response fails the access check because user info was sent.
-    async_test(t => {
-      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN.replace("http://", "http://username:password@") +
-          "/XMLHttpRequest/resources/access-control-basic-allow-star.py", "allow_origin=true",
-          withoutCustomHeader, remote, fails)
-    }, "Request with user info is redirected to a response with Access-Control-Allow-Origin=*");
-
-    // The redirect response fails the access check because the URL scheme is unsupported.
-    async_test(t => {
-      runTest(t, "foo://bar.cgi", "allow_origin=true", withoutCustomHeader, remote, fails)
-    }, "Request is redirect to a bad URL");
-
-    // The preflighted redirect response fails the access check because of preflighting.
-    async_test(t => {
-      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow-star.py",
-          "allow_origin=true&redirect_preflight=true", withCustomHeader, remote, fails)
-    }, "Preflighted request is redirected to a response with Access-Control-Allow-Origin=*");
-
-    // The preflighted redirect response fails the access check after successful preflighting.
-    async_test(t => {
-      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow-star.py",
-          "allow_origin=true&allow_header=x-test&redirect_preflight=true",
-          withCustomHeader, remote, fails)
-    }, "Preflighted request is redirected to a response with Access-Control-Allow-Origin=* and header allowed");
-
-    // The same-origin redirect response passes the access check.
-    async_test(t => {
-      runTest(t, get_host_info().HTTP_ORIGIN + "/XMLHttpRequest/resources/pass.txt",
-          "", withCustomHeader, local, succeeds)
-    }, "Request is redirected to a same-origin resource file");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-and-redirects.htm b/XMLHttpRequest/access-control-and-redirects.htm
deleted file mode 100644
index dcdf400..0000000
--- a/XMLHttpRequest/access-control-and-redirects.htm
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that redirects between origins are allowed when access control is involved.</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script>
-    function runSync(test, url)
-    {
-      const xhr = new XMLHttpRequest();
-      xhr.open("GET", url, false);
-      xhr.send();
-      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
-      test.done();
-    }
-    function runAsync(test, url)
-    {
-      const xhr = new XMLHttpRequest();
-      xhr.open("GET", url, true);
-      xhr.onload = test.step_func_done(function() {
-        assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
-      });
-      xhr.onerror = test.unreached_func("Network error");
-      xhr.send();
-      test.done();
-    }
-    test(t => {
-      runSync(t, "resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow.py")
-    }, "Local sync redirect to remote origin");
-    async_test(t => {
-      runAsync(t, "resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow.py")
-    }, "Local async redirect to remote origin");
-    test(t => {
-      runSync(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/redirect-cors.py?location=" + get_host_info().HTTP_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow.py&allow_origin=true")
-    }, "Remote sync redirect to local origin");
-    async_test(t => {
-      runAsync(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/redirect-cors.py?location=" + get_host_info().HTTP_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow.py&allow_origin=true")
-    }, "Remote async redirect to local origin");
-    test(t => {
-      runSync(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow.py&allow_origin=true")
-    }, "Remote sync redirect to same remote origin");
-    async_test(t => {
-      runAsync(t, get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow.py&allow_origin=true")
-    }, "Remote async redirect to same remote origin");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-access-control-origin-header-data-url.htm b/XMLHttpRequest/access-control-basic-allow-access-control-origin-header-data-url.htm
deleted file mode 100644
index d73f56b..0000000
--- a/XMLHttpRequest/access-control-basic-allow-access-control-origin-header-data-url.htm
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that cross-origin access is granted to null-origin embedded iframe</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-const url = get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-origin-header.py";
-async_test(function(test) {
-  window.addEventListener("message", test.step_func(function(evt) {
-    if (evt.data == "ready") {
-      document.getElementById("frame").contentWindow.postMessage(url, "*");
-    } else {
-      assert_equals(evt.data, "PASS: Cross-domain access allowed.\nHTTP_ORIGIN: null");
-      test.done();
-    }
-  }), false);
-}, "Access granted to null-origin iframe");
-    </script>
-    <iframe id="frame" src='data:text/html,
-    <script>
-(function() {
-  parent.postMessage("ready", "*");
-  window.addEventListener("message", function(evt) {
-    try {
-      const url = evt.data;
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", url, false);
-      xhr.send();
-
-      parent.postMessage(xhr.responseText, "*");
-    } catch(e) {
-      parent.postMessage(e.message, "*");
-    }
-  });
-})();
-    </script>'>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-access-control-origin-header.htm b/XMLHttpRequest/access-control-basic-allow-access-control-origin-header.htm
deleted file mode 100644
index 87b7e47..0000000
--- a/XMLHttpRequest/access-control-basic-allow-access-control-origin-header.htm
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test(function(test) {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-origin-header.py", false);
-      xhr.send();
-
-      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\n" +
-          "HTTP_ORIGIN: " + get_host_info().HTTP_ORIGIN);
-      test.done();
-    }, "Access control test with origin header");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-async.htm b/XMLHttpRequest/access-control-basic-allow-async.htm
deleted file mode 100644
index d9e0321..0000000
--- a/XMLHttpRequest/access-control-basic-allow-async.htm
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Testing a basic asynchronous CORS XHR request</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test(function(test) {
-      const xhr = new XMLHttpRequest;
-
-      xhr.onreadystatechange = test.step_func(function() {
-        if (xhr.readyState == xhr.DONE) {
-          assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
-          test.done();
-        }
-      });
-
-      xhr.onerror = test.unreached_func("FAIL: Network error.");
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-allow.py", true);
-      xhr.send();
-    }, "Basic async cross-origin XHR request");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method-async.htm b/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method-async.htm
deleted file mode 100644
index 57721aa..0000000
--- a/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method-async.htm
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests cross-origin async request with non-CORS-safelisted method</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test((test) => {
-      const xhr = new XMLHttpRequest;
-
-      xhr.onload = test.step_func_done(() => {
-        assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
-      });
-
-      xhr.onerror = test.unreached_func("Unexpected error.");
-
-      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-put-allow.py");
-      xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
-      xhr.send("PASS: PUT data received");
-    }, "Allow async PUT request");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method.htm b/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method.htm
deleted file mode 100644
index 08f4111..0000000
--- a/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method.htm
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests cross-origin request with non-CORS-safelisted method</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-put-allow.py", false);
-
-      xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
-
-      xhr.send("PASS: PUT data received");
-
-      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
-    }, "Allow PUT request");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-preflight-cache-invalidation-by-header.htm b/XMLHttpRequest/access-control-basic-allow-preflight-cache-invalidation-by-header.htm
deleted file mode 100644
index 5a1e396..0000000
--- a/XMLHttpRequest/access-control-basic-allow-preflight-cache-invalidation-by-header.htm
+++ /dev/null
@@ -1,48 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Preflight cache should be invalidated in presence of custom header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const uuid = token();
-    const xhr = new XMLHttpRequest;
-
-    async_test(function(test) {
-      xhr.onerror = test.unreached_func("FAIL: Network error.");
-      xhr.onload = test.step_func(function() {
-        // Token reset.  We can start the test now.
-        assert_equals(xhr.responseText, "PASS");
-        firstRequest();
-      });
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/reset-token.py?token=" + uuid, true);
-      xhr.send();
-
-      function firstRequest() {
-        xhr.onload = test.step_func(function() {
-          assert_equals(xhr.responseText, "PASS: First PUT request.");
-          secondRequest();
-        });
-        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
-        xhr.send();
-      }
-
-      function secondRequest() {
-        xhr.onload = test.step_func(function() {
-          assert_equals(xhr.responseText, "PASS: Second OPTIONS request was sent.");
-          test.done();
-        });
-        // Send a header not included in the inital cache.
-        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
-        xhr.setRequestHeader("x-test", "headerValue");
-        xhr.send();
-      }
-    }, "Preflight cache should be invalidated in presence of custom header");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-preflight-cache-invalidation-by-method.htm b/XMLHttpRequest/access-control-basic-allow-preflight-cache-invalidation-by-method.htm
deleted file mode 100644
index 058943e..0000000
--- a/XMLHttpRequest/access-control-basic-allow-preflight-cache-invalidation-by-method.htm
+++ /dev/null
@@ -1,48 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Preflight cache should be invalidated by changed method</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const uuid = token();
-    const xhr = new XMLHttpRequest;
-
-    async_test(function(test) {
-      xhr.onerror = test.unreached_func("FAIL: Network error.");
-      xhr.onload = test.step_func(function() {
-        // Token reset.  We can start the test now.
-        assert_equals(xhr.responseText, "PASS");
-        firstRequest();
-      });
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/reset-token.py?token=" + uuid, true);
-      xhr.send();
-
-      function firstRequest() {
-        xhr.onload = test.step_func(function() {
-          assert_equals(xhr.responseText, "PASS: First PUT request.");
-          secondRequest();
-        });
-        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
-        xhr.send();
-      }
-
-      function secondRequest() {
-        xhr.onload = test.step_func(function() {
-          assert_equals(xhr.responseText, "PASS: Second OPTIONS request was sent.");
-          test.done();
-        });
-        // Send a header not included in the inital cache.
-        xhr.open("XMETHOD", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
-        xhr.send();
-      }
-    }, "Preflight cache should be invalidated by changed method");
-
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-preflight-cache-timeout.htm b/XMLHttpRequest/access-control-basic-allow-preflight-cache-timeout.htm
deleted file mode 100644
index 56702c0..0000000
--- a/XMLHttpRequest/access-control-basic-allow-preflight-cache-timeout.htm
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Preflight cache should be invalidated on timeout</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const uuid = token();
-    let xhr = new XMLHttpRequest;
-
-    async_test(function(test) {
-      xhr.onerror = test.unreached_func("FAIL: Network error.");
-      xhr.onload = test.step_func(function() {
-        // Token reset.  We can start the test now.
-        assert_equals(xhr.responseText, "PASS");
-        firstRequest();
-      });
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/reset-token.py?token=" + uuid, true);
-      xhr.send();
-
-      function firstRequest() {
-        xhr.onload = test.step_func(function() {
-          assert_equals(xhr.responseText, "PASS: First PUT request.");
-          step_timeout(secondRequest, 3000); // 3 seconds
-        });
-        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache-timeout.py?token=" + uuid, true);
-        xhr.send();
-      }
-
-      function secondRequest() {
-        xhr.onload = test.step_func(function() {
-          assert_equals(xhr.responseText, "PASS: Second OPTIONS request was sent.");
-          test.done();
-        });
-        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache-timeout.py?token=" + uuid, true);
-        xhr.send();
-      }
-    }, "Preflight cache should be invalidated on timeout");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-preflight-cache.htm b/XMLHttpRequest/access-control-basic-allow-preflight-cache.htm
deleted file mode 100644
index ad38a6b..0000000
--- a/XMLHttpRequest/access-control-basic-allow-preflight-cache.htm
+++ /dev/null
@@ -1,45 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Preflight cache should allow second request without preflight OPTIONS request</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const uuid = token();
-
-    async_test(function(test) {
-      const xhr = new XMLHttpRequest;
-      xhr.onerror = test.unreached_func("FAIL: Network error.");
-      xhr.onload = test.step_func(function() {
-        // Token reset.  We can start the test now.
-        assert_equals(xhr.responseText, "PASS");
-        firstRequest();
-      });
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/reset-token.py?token=" + uuid, true);
-      xhr.send();
-
-      function firstRequest() {
-        xhr.onload = test.step_func(function() {
-          assert_equals(xhr.responseText, "PASS: First PUT request.");
-          secondRequest();
-        });
-        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache.py?token=" + uuid, true);
-        xhr.send();
-      }
-
-      function secondRequest() {
-        xhr.onload = test.step_func_done(function() {
-          assert_equals(xhr.responseText, "PASS: Second PUT request. Preflight worked.");
-        });
-        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-preflight-cache.py?token=" + uuid, true);
-        xhr.send();
-      }
-    }, "Preflight cache should allow second request");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow-star.htm b/XMLHttpRequest/access-control-basic-allow-star.htm
deleted file mode 100644
index fac25e2..0000000
--- a/XMLHttpRequest/access-control-basic-allow-star.htm
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests "*" setting for Access-Control-Allow-Origin header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const xhr = new XMLHttpRequest;
-
-    test(function(test) {
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-allow-star.py", false);
-
-      xhr.send();
-
-      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
-    }, "Allow star");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-allow.htm b/XMLHttpRequest/access-control-basic-allow.htm
deleted file mode 100644
index f7e67ba..0000000
--- a/XMLHttpRequest/access-control-basic-allow.htm
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests CORS with Access-Control-Allow-Origin header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-allow.py", false);
-
-      xhr.send();
-
-      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
-    }, "Allow basic");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-cors-safelisted-request-headers.htm b/XMLHttpRequest/access-control-basic-cors-safelisted-request-headers.htm
deleted file mode 100644
index 1d570a3..0000000
--- a/XMLHttpRequest/access-control-basic-cors-safelisted-request-headers.htm
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that CORS-safelisted request headers are permitted in cross-origin request</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("POST", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-cors-safelisted-request-headers.py", false);
-
-      xhr.setRequestHeader("Accept", "*");
-      xhr.setRequestHeader("Accept-Language", "ru");
-      xhr.setRequestHeader("Content-Language", "ru");
-      xhr.setRequestHeader("Content-Type", "text/plain");
-      xhr.setRequestHeader("Save-Data", "on");
-
-      xhr.send();
-
-      assert_equals(xhr.responseText,
-          "Accept: *\n" +
-          "Accept-Language: ru\n" +
-          "Content-Language: ru\n" +
-          "Content-Type: text/plain\n");
-    }, "Request with CORS-safelisted headers");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-cors-safelisted-response-headers.htm b/XMLHttpRequest/access-control-basic-cors-safelisted-response-headers.htm
deleted file mode 100644
index c795083..0000000
--- a/XMLHttpRequest/access-control-basic-cors-safelisted-response-headers.htm
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that CORS-safelisted response headers are permitted in cross-origin request</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-whitelist-response-headers.py", false);
-      xhr.send();
-
-      assert_not_equals(xhr.getResponseHeader("cache-control"), null);
-      assert_not_equals(xhr.getResponseHeader("content-language"), null);
-      assert_not_equals(xhr.getResponseHeader("content-type"), null);
-      assert_not_equals(xhr.getResponseHeader("expires"), null);
-      assert_not_equals(xhr.getResponseHeader("last-modified"), null);
-      assert_not_equals(xhr.getResponseHeader("pragma"), null);
-      assert_equals(xhr.getResponseHeader("x-webkit"), null);
-
-      assert_not_equals(xhr.getAllResponseHeaders().match("en"), null);
-      assert_equals(xhr.getAllResponseHeaders().match("foobar"), null);
-    }, "Response with CORS-safelisted headers");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-denied.htm b/XMLHttpRequest/access-control-basic-denied.htm
deleted file mode 100644
index 924bdd5..0000000
--- a/XMLHttpRequest/access-control-basic-denied.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests CORS denying resource without Access-Control-Allow-Origin header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const path = "/XMLHttpRequest/resources/access-control-basic-denied.py";
-
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_ORIGIN + path, false);
-      xhr.send();
-      assert_equals(xhr.status, 200);
-    }, "Same-origin request accepted");
-
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + path, false);
-      try {
-        xhr.send();
-        assert_unreached("Error should occur here");
-      } catch(e) {
-        assert_equals(xhr.status, 0);
-      }
-    }, "Cross-origin request denied");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-get-fail-non-simple.htm b/XMLHttpRequest/access-control-basic-get-fail-non-simple.htm
deleted file mode 100644
index ccd9f7c..0000000
--- a/XMLHttpRequest/access-control-basic-get-fail-non-simple.htm
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests CORS denying preflighted request to resource without CORS headers for OPTIONS</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-options-not-supported.py", false);
-
-      // Non-CORS-safelisted header
-      xhr.setRequestHeader("x-test", "foobar");
-
-      // This fails because the server-side script is not prepared for an OPTIONS request
-      try {
-        xhr.send();
-      } catch(e) {
-        assert_equals(xhr.status, 0);
-        return;
-      }
-      assert_unreached("Preflighted request was not denied.");
-    }, "Preflighted cross-origin request denied");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-non-cors-safelisted-content-type.htm b/XMLHttpRequest/access-control-basic-non-cors-safelisted-content-type.htm
deleted file mode 100644
index bff0cf5..0000000
--- a/XMLHttpRequest/access-control-basic-non-cors-safelisted-content-type.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests cross-origin request with non-CORS-safelisted content type</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(() => {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-put-allow.py", false);
-      xhr.setRequestHeader("Content-Type", "text/plain");
-      xhr.send("PASS: PUT data received");
-
-      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
-
-      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-put-allow.py", false);
-      xhr.setRequestHeader("Content-Type", "application/xml");
-
-      try {
-        xhr.send("FAIL: PUT data received");
-      } catch(e) {
-        assert_equals(xhr.status, 0, "Cross-domain access was denied in 'send'.");
-        return;
-      }
-      assert_unreached("Cross-domain access was not denied in 'send'.");
-    }, "Deny cross-origin request with non-CORS-safelisted content type");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-post-success-no-content-type.htm b/XMLHttpRequest/access-control-basic-post-success-no-content-type.htm
deleted file mode 100644
index 8785a44..0000000
--- a/XMLHttpRequest/access-control-basic-post-success-no-content-type.htm
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that POST requests with text content and no content-type set explicitly don't generate a preflight request.</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test(function(test) {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("POST", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-options-not-supported.py");
-
-      xhr.onerror = test.unreached_func("Network error.");
-
-      xhr.onload = test.step_func_done(function() {
-        assert_equals(xhr.status, 200);
-      });
-
-      xhr.send("Test");
-    }, "POST request with text content and no Content-Type header");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-post-with-non-cors-safelisted-content-type.htm b/XMLHttpRequest/access-control-basic-post-with-non-cors-safelisted-content-type.htm
deleted file mode 100644
index 9867c79..0000000
--- a/XMLHttpRequest/access-control-basic-post-with-non-cors-safelisted-content-type.htm
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Non-CORS-safelisted value in the Content-Type header results in a request preflight</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("POST", get_host_info().HTTP_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-options-not-supported.py", false);
-
-      xhr.setRequestHeader("Content-Type", "application/xml");
-
-      xhr.send();
-
-      assert_equals(xhr.status, 200, "Same-origin access doesn't issue preflight; not denied.");
-    }, "Same-origin request with non-safelisted content type succeeds");
-
-    test(function() {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("POST", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-options-not-supported.py", false);
-
-      xhr.setRequestHeader("Content-Type", "application/xml");
-
-      try {
-        xhr.send();
-      } catch(e) {
-        assert_equals(xhr.status, 0, "Cross-domain access was denied in 'send'.");
-        return;
-      }
-      assert_unreached("Cross-domain access was not denied in 'send'.");
-    }, "CORS request with non-safelisted content type sends preflight and fails");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-basic-preflight-denied.htm b/XMLHttpRequest/access-control-basic-preflight-denied.htm
deleted file mode 100644
index d02bdf0..0000000
--- a/XMLHttpRequest/access-control-basic-preflight-denied.htm
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests async XHR preflight denial due to lack of CORS headers</title>
-    <!--The original test addressed a more specific issue involving caching,
-        but that issue has since been resolved.
-        We maintain this test as a basic test of invalid preflight denial.
-        Please refer to the comment in the following link for more information:
-        https://chromium-review.googlesource.com/c/chromium/src/+/630338#message-0280542b95c9b0f82b121dc373320c04fcaece31
-    -->
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test((test) => {
-      const xhr = new XMLHttpRequest;
-      xhr.onerror = test.step_func_done(() => {
-        assert_equals(xhr.status, 0);
-      });
-
-      xhr.onload = test.unreached_func("Request succeeded unexpectedly");
-
-      xhr.open("FOO", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-basic-denied.py");
-      xhr.send();
-    });
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-async-header-denied.htm b/XMLHttpRequest/access-control-preflight-async-header-denied.htm
deleted file mode 100644
index 84a60eb..0000000
--- a/XMLHttpRequest/access-control-preflight-async-header-denied.htm
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Async request denied at preflight because of non-CORS-safelisted header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const uuid = token();
-    const url = get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-preflight-denied.py?token=" + uuid;
-
-    async_test((test) => {
-      let xhr = new XMLHttpRequest;
-      xhr.open("GET", url + "&command=reset", false);
-      xhr.send();
-
-      xhr = new XMLHttpRequest;
-      xhr.open("GET", url + "&command=header", true);
-      xhr.setRequestHeader("x-test", "foo");
-
-      xhr.onload = test.unreached_func(
-          "Cross-domain access with custom header allowed without throwing exception");
-
-      xhr.onerror = test.step_func_done(() => {
-        xhr = new XMLHttpRequest;
-        xhr.open("GET", url + "&command=complete", false);
-        xhr.send();
-        assert_equals(xhr.responseText, "Request successfully blocked.");
-      });
-
-      xhr.send();
-    }, "Async request denied at preflight");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-async-method-denied.htm b/XMLHttpRequest/access-control-preflight-async-method-denied.htm
deleted file mode 100644
index eb36110..0000000
--- a/XMLHttpRequest/access-control-preflight-async-method-denied.htm
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Async request denied at preflight because of non-CORS-safelisted method</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    const uuid = token();
-    const url = get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-preflight-denied.py?token=" + uuid;
-
-    async_test((test) => {
-      let xhr = new XMLHttpRequest;
-      xhr.open("GET", url + "&command=reset", false);
-      xhr.send();
-
-      xhr = new XMLHttpRequest;
-      xhr.open("DELETE", url + "&command=method", true);
-
-      xhr.onload = test.unreached_func(
-          "Cross-domain access with non-CORS-safelisted method allowed without throwing exception");
-
-      xhr.onerror = test.step_func_done(() => {
-        xhr = new XMLHttpRequest;
-        xhr.open("GET", url + "&command=complete", false);
-        xhr.send();
-        assert_equals(xhr.responseText, "Request successfully blocked.");
-      });
-
-      xhr.send();
-    }, "Async request denied at preflight");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-async-not-supported.htm b/XMLHttpRequest/access-control-preflight-async-not-supported.htm
deleted file mode 100644
index e1607d4..0000000
--- a/XMLHttpRequest/access-control-preflight-async-not-supported.htm
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Async PUT request denied at preflight</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-const uuid = token();
-const url = get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/access-control-preflight-denied.py?token=" + uuid;
-
-async_test((test) => {
-  let xhr = new XMLHttpRequest;
-  xhr.open("GET", url + "&command=reset", false);
-  xhr.send();
-
-  xhr = new XMLHttpRequest;
-  xhr.open("PUT", url, true);
-
-  xhr.onload = test.unreached_func("Cross-domain access allowed unexpectedly.");
-
-  xhr.onerror = test.step_func_done(() => {
-    xhr = new XMLHttpRequest;
-    xhr.open("GET", url + "&command=complete", false);
-    xhr.send();
-    assert_equals(xhr.responseText, "Request successfully blocked.");
-  });
-
-  xhr.send();
-});
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-credential-async.htm b/XMLHttpRequest/access-control-preflight-credential-async.htm
deleted file mode 100644
index d9ccc10..0000000
--- a/XMLHttpRequest/access-control-preflight-credential-async.htm
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests proper handling of cross-origin async request with credentials</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test((test) => {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-auth-basic.py?uid=fooUser",
-          true, "fooUser", "barPass");
-      xhr.withCredentials = true;
-
-      xhr.onerror = test.unreached_func("Unexpected error.");
-
-      xhr.onload = test.step_func_done(() => {
-        assert_equals(xhr.status, 401, "Request raises HTTP 401: Unauthorized error.");
-      });
-
-      xhr.send();
-    }, "CORS async request with URL credentials");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-credential-sync.htm b/XMLHttpRequest/access-control-preflight-credential-sync.htm
deleted file mode 100644
index d0b9901..0000000
--- a/XMLHttpRequest/access-control-preflight-credential-sync.htm
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests proper handling of cross-origin sync request with credentials</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(() => {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-auth-basic.py?uid=fooUser", false, "fooUser", "barPass");
-
-      xhr.withCredentials = true;
-
-      xhr.send();
-
-      assert_equals(xhr.status, 401, "Request raises HTTP 401: Unauthorized error.");
-    }, "CORS sync request with URL credentials");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-headers-async.htm b/XMLHttpRequest/access-control-preflight-headers-async.htm
deleted file mode 100644
index 6e05cfe..0000000
--- a/XMLHttpRequest/access-control-preflight-headers-async.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Test that async CORS requests with custom headers are sent with OPTIONS preflight</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-async_test((test) => {
-  let xhr = new XMLHttpRequest;
-  const uuid = token();
-
-  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/reset-token.py?token=" + uuid, false);
-  xhr.send();
-
-  xhr = new XMLHttpRequest;
-  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/no-custom-header-on-preflight.py?token=" + uuid);
-  xhr.setRequestHeader("x-test", "foobar");
-
-  xhr.onerror = test.unreached_func("Unexpected error");
-
-  xhr.onload = test.step_func_done(() => {
-    assert_equals(xhr.responseText, "PASS");
-  });
-
-  xhr.send();
-}, "Preflighted async request with custom header");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-headers-sync.htm b/XMLHttpRequest/access-control-preflight-headers-sync.htm
deleted file mode 100644
index 85fe446..0000000
--- a/XMLHttpRequest/access-control-preflight-headers-sync.htm
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Test that sync CORS requests with custom headers are not sent with OPTIONS preflight</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    test(function() {
-      let xhr = new XMLHttpRequest;
-      const uuid = token();
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/reset-token.py?token=" + uuid, false);
-      xhr.send();
-
-      xhr = new XMLHttpRequest;
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/no-custom-header-on-preflight.py?token=" + uuid, false);
-      xhr.setRequestHeader("x-test", "foobar");
-      xhr.send();
-      assert_equals(xhr.responseText, "PASS");
-    }, "Preflighted sync request with custom header");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-header-lowercase.htm b/XMLHttpRequest/access-control-preflight-request-header-lowercase.htm
deleted file mode 100644
index d88cac8..0000000
--- a/XMLHttpRequest/access-control-preflight-request-header-lowercase.htm
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Access-Control-Request-Headers values should be lowercase</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test(function(test) {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-preflight-request-header-lowercase.py");
-
-      xhr.setRequestHeader("X-Test", "foobar");
-
-      xhr.onerror = test.unreached_func("Error occurred.");
-
-      xhr.onload = test.step_func_done(function() {
-        assert_equals(xhr.status, 200);
-        assert_equals(xhr.responseText, "PASS");
-      });
-
-      xhr.send();
-    }, "Request with uppercase header set");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-header-sorted.htm b/XMLHttpRequest/access-control-preflight-request-header-sorted.htm
deleted file mode 100644
index 2423b80..0000000
--- a/XMLHttpRequest/access-control-preflight-request-header-sorted.htm
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>Tests that Access-Control-Request-Headers are sorted.</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/common/get-host-info.sub.js"></script>
-</head>
-<body>
-<script>
-async_test((test) => {
-  const xhr = new XMLHttpRequest();
-  const url = get_host_info().HTTP_REMOTE_ORIGIN + '/XMLHttpRequest/resources/access-control-preflight-request-header-sorted.py';
-  xhr.open('GET', url);
-  xhr.setRequestHeader("X-Custom-Test", "foobar");
-  xhr.setRequestHeader("X-Custom-ua", "foobar");
-  xhr.setRequestHeader("X-Custom-V", "foobar");
-  xhr.setRequestHeader("X-Custom-s", "foobar");
-  xhr.setRequestHeader("X-Custom-U", "foobar");
-  xhr.onerror = test.unreached_func('xhr failure');
-  xhr.onload = test.step_func_done(() => {
-    assert_equals(xhr.responseText, 'PASS');
-  });
-  xhr.send();
-});
-</script>
-</body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-headers-origin.htm b/XMLHttpRequest/access-control-preflight-request-headers-origin.htm
deleted file mode 100644
index 67aeda9..0000000
--- a/XMLHttpRequest/access-control-preflight-request-headers-origin.htm
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Test that 'Origin' is not included in Access-Control-Request-Headers in a preflight request</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-async_test((test) => {
-  const xhr = new XMLHttpRequest;
-  const url = get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/access-control-preflight-request-headers-origin.py";
-
-  xhr.open("GET", url);
-  xhr.setRequestHeader("x-pass", "PASS");
-
-  xhr.onerror = test.unreached_func("Unexpected error");
-
-  xhr.onload = test.step_func_done(() => {
-    assert_equals(xhr.responseText, "PASS");
-  });
-
-  xhr.send();
-}, "'Origin' should not be included in CORS Request-Headers");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-invalid-status-301.htm b/XMLHttpRequest/access-control-preflight-request-invalid-status-301.htm
deleted file mode 100644
index 669f7db..0000000
--- a/XMLHttpRequest/access-control-preflight-request-invalid-status-301.htm
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that preflight requests returning invalid 301 status code result in error.</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test((test) => {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py?code=301");
-
-      xhr.setRequestHeader("x-pass", "pass");
-
-      xhr.onerror = test.step_func_done(function() {
-        assert_equals(xhr.status, 0);
-      });
-
-      xhr.onload = test.unreached_func("Invalid 301 response to preflight should result in error.");
-
-      xhr.send();
-    }, "Request with 301 preflight response");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-invalid-status-400.htm b/XMLHttpRequest/access-control-preflight-request-invalid-status-400.htm
deleted file mode 100644
index 3852099..0000000
--- a/XMLHttpRequest/access-control-preflight-request-invalid-status-400.htm
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that preflight requests returning invalid 400 status code result in error.</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test((test) => {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py?code=400");
-
-      xhr.setRequestHeader("x-pass", "pass");
-
-      xhr.onerror = test.step_func_done(function() {
-        assert_equals(xhr.status, 0);
-      });
-
-      xhr.onload = test.unreached_func("Invalid 400 response to preflight should result in error.");
-
-      xhr.send();
-    }, "Request with 400 preflight response");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-invalid-status-501.htm b/XMLHttpRequest/access-control-preflight-request-invalid-status-501.htm
deleted file mode 100644
index 5f3c5e6..0000000
--- a/XMLHttpRequest/access-control-preflight-request-invalid-status-501.htm
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that preflight requests returning invalid 501 status code result in error.</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test((test) => {
-      const xhr = new XMLHttpRequest;
-
-      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py?code=501");
-
-      xhr.setRequestHeader("x-pass", "pass");
-
-      xhr.onerror = test.step_func_done(function() {
-        assert_equals(xhr.status, 0);
-      });
-
-      xhr.onload = test.unreached_func("Invalid 501 response to preflight should result in error.");
-
-      xhr.send();
-    }, "Request with 501 preflight response");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-must-not-contain-cookie.htm b/XMLHttpRequest/access-control-preflight-request-must-not-contain-cookie.htm
deleted file mode 100644
index c7cb5cd..0000000
--- a/XMLHttpRequest/access-control-preflight-request-must-not-contain-cookie.htm
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Preflight request must not contain any cookie header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-    async_test((test) => {
-      function setupCookie() {
-        const xhr = new XMLHttpRequest;
-        // Delete all preexisting cookies and set a cookie named "foo"
-        xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-            "/XMLHttpRequest/resources/access-control-cookie.py?cookie_name=foo");
-        xhr.withCredentials = true;
-        xhr.send();
-        xhr.onerror = test.unreached_func("Unexpected error.");
-        xhr.onload = test.step_func(() => {
-          assert_equals(xhr.status, 200);
-          sendPreflightedRequest();
-        });
-      }
-
-      function sendPreflightedRequest() {
-        const xhr = new XMLHttpRequest;
-        // Request to server-side file fails if cookie is included in preflight
-        xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-          "/XMLHttpRequest/resources/access-control-preflight-request-must-not-contain-cookie.py");
-        xhr.withCredentials = true;
-        xhr.setRequestHeader("X-Proprietary-Header", "foo");
-        xhr.onerror = test.unreached_func("Unexpected error.");
-        xhr.onload = test.step_func(() => {
-          assert_equals(xhr.status, 200);
-          assert_equals(xhr.responseText, "COOKIE");
-          cleanupCookies();
-        });
-        xhr.send();
-      }
-
-      function cleanupCookies() {
-        const xhr = new XMLHttpRequest;
-        // Delete all cookies
-        xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-            "/XMLHttpRequest/resources/access-control-cookie.py");
-        xhr.withCredentials = true;
-        xhr.send();
-        xhr.onerror = test.unreached_func("Unexpected error.");
-        xhr.onload = test.step_func_done(() => {});
-      }
-
-      setupCookie();
-    });
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-sync-header-denied.htm b/XMLHttpRequest/access-control-preflight-sync-header-denied.htm
deleted file mode 100644
index 422c625..0000000
--- a/XMLHttpRequest/access-control-preflight-sync-header-denied.htm
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Sync request denied at preflight because of non-CORS-safelisted header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-const uuid = token();
-const url = get_host_info().HTTP_REMOTE_ORIGIN +
-    "/XMLHttpRequest/resources/access-control-preflight-denied.py?token=" + uuid;
-
-test(() => {
-  let xhr = new XMLHttpRequest;
-  xhr.open("GET", url + "&command=reset", false);
-  xhr.send();
-
-  xhr = new XMLHttpRequest;
-  xhr.open("GET", url + "&command=header", false);
-  xhr.setRequestHeader("x-test", "foo");
-
-  try {
-    xhr.send();
-  } catch(e) {
-    xhr = new XMLHttpRequest;
-    xhr.open("GET", url + "&command=complete", false);
-    xhr.send();
-    assert_equals(xhr.responseText, "Request successfully blocked.");
-    return;
-  }
-
-  assert_unreached("Cross-domain access with custom header allowed without throwing exception");
-}, "Sync request denied at preflight");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-sync-method-denied.htm b/XMLHttpRequest/access-control-preflight-sync-method-denied.htm
deleted file mode 100644
index cd2999a..0000000
--- a/XMLHttpRequest/access-control-preflight-sync-method-denied.htm
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Sync request denied at preflight because of non-CORS-safelisted method</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-const uuid = token();
-const url = get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/access-control-preflight-denied.py?token=" + uuid;
-
-test(() => {
-  let xhr = new XMLHttpRequest;
-  xhr.open("GET", url + "&command=reset", false);
-  xhr.send();
-
-  xhr = new XMLHttpRequest;
-  xhr.open("DELETE", url + "&command=method", false);
-
-  try {
-    xhr.send();
-  } catch(e) {
-    xhr = new XMLHttpRequest;
-    xhr.open("GET", url + "&command=complete", false);
-    xhr.send();
-    assert_equals(xhr.responseText, "Request successfully blocked.");
-    return;
-  }
-
-  assert_unreached("Cross-domain access with non-CORS-safelisted method allowed without throwing exception");
-});
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-preflight-sync-not-supported.htm b/XMLHttpRequest/access-control-preflight-sync-not-supported.htm
deleted file mode 100644
index a0b079e..0000000
--- a/XMLHttpRequest/access-control-preflight-sync-not-supported.htm
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Sync PUT request denied at preflight</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="/common/utils.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-const uuid = token();
-const url = get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/access-control-preflight-denied.py?token=" + uuid;
-
-test(() => {
-  let xhr = new XMLHttpRequest;
-  xhr.open("GET", url + "&command=reset", false);
-  xhr.send();
-
-  xhr = new XMLHttpRequest;
-  xhr.open("PUT", url, false);
-
-  try {
-    xhr.send("");
-  } catch(e) {
-    xhr = new XMLHttpRequest;
-    xhr.open("GET", url + "&command=complete", false);
-    xhr.send();
-    assert_equals(xhr.responseText, "Request successfully blocked.");
-    return;
-  }
-
-  assert_unreached("Cross-domain access allowed without throwing exception");
-});
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-recursive-failed-request.htm b/XMLHttpRequest/access-control-recursive-failed-request.htm
deleted file mode 100644
index e29a34a..0000000
--- a/XMLHttpRequest/access-control-recursive-failed-request.htm
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Recursively repeated CORS requests with failed preflights should never result in unexpected behavior</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-let requestCount = 0;
-const requestMax = 10;
-
-async_test((test) => {
-  function preflightRequest() {
-    const xhr = new XMLHttpRequest;
-
-    xhr.onload = test.unreached_func("Request succeeded unexpectedly.");
-
-    xhr.onerror = test.step_func(() => {
-      assert_equals(xhr.status, 0);
-      if (++requestCount >= requestMax) {
-        test.done();
-        return;
-      }
-      preflightRequest();
-    });
-
-    xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-        "/XMLHttpRequest/resources/access-control-basic-denied.py");
-    xhr.send();
-  }
-
-  preflightRequest();
-});
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-response-with-body-sync.htm b/XMLHttpRequest/access-control-response-with-body-sync.htm
deleted file mode 100644
index 559b7d2..0000000
--- a/XMLHttpRequest/access-control-response-with-body-sync.htm
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests body from CORS preflight response and actual response with sync request</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-test(() => {
-  const xhr = new XMLHttpRequest;
-
-  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN+
-      "/XMLHttpRequest/resources/access-control-allow-with-body.py", false);
-  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-  xhr.send();
-
-  assert_equals(xhr.status, 200);
-  assert_equals(xhr.responseText, "PASS");
-});
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-response-with-body.htm b/XMLHttpRequest/access-control-response-with-body.htm
deleted file mode 100644
index ab89cef..0000000
--- a/XMLHttpRequest/access-control-response-with-body.htm
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that XHR doesn't prepend the body from CORS preflight response to the actual response</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-async_test((test) => {
-  const xhr = new XMLHttpRequest;
-
-  xhr.onerror = test.unreached_func("Unexpected error.");
-
-  xhr.onload = test.step_func_done(() => {
-    assert_equals(xhr.status, 200);
-    assert_equals(xhr.responseText, "PASS");
-  });
-
-  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/access-control-allow-with-body.py");
-  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-  xhr.send();
-});
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-response-with-exposed-headers.htm b/XMLHttpRequest/access-control-response-with-exposed-headers.htm
deleted file mode 100644
index 23cd0fb..0000000
--- a/XMLHttpRequest/access-control-response-with-exposed-headers.htm
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Test CORS response with 'Access-Control-Expose-Headers' header</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-async_test((test) => {
-  const xhr = new XMLHttpRequest;
-
-  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
-      "/XMLHttpRequest/resources/pass.txt?pipe=" +
-      "header(Cache-Control,no-cache)|" +
-      "header(Access-Control-Max-Age,0)|" +
-      "header(Access-Control-Allow-Origin,*)|" +
-      "header(X-foo,BAR)|" +
-      "header(x-test,TEST)|" +
-      "header(Access-Control-Expose-Headers,x-Foo)|",
-      "header(Content-Type,text/html)");
-
-  xhr.onerror = test.unreached_func("Unexpected error");
-
-  xhr.onload = test.step_func_done(() => {
-    assert_equals(xhr.status, 200);
-    assert_equals(xhr.getResponseHeader("X-FOO"), "BAR");
-    assert_equals(xhr.getResponseHeader("x-foo"), "BAR");
-    assert_equals(xhr.getResponseHeader("x-test"), null);
-    assert_equals(xhr.responseText, "PASS\n");
-  });
-
-  xhr.send();
-});
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-sandboxed-iframe-allow-origin-null.htm b/XMLHttpRequest/access-control-sandboxed-iframe-allow-origin-null.htm
deleted file mode 100644
index 5c647df..0000000
--- a/XMLHttpRequest/access-control-sandboxed-iframe-allow-origin-null.htm
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that sandboxed iframe has CORS XHR access to a server that accepts null domain</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-async_test((test) => {
-  window.addEventListener("message", test.step_func((evt) => {
-    if (evt.data === "ready") {
-      document.getElementById("frame").contentWindow.postMessage(
-          get_host_info().HTTP_ORIGIN +
-          "/XMLHttpRequest/resources/pass.txt?pipe=" +
-          "header(Cache-Control,no-store)|" +
-          "header(Content-Type,text/plain)|" +
-          "header(Access-Control-Allow-Credentials,true)|" +
-          "header(Access-Control-Allow-External,true)|" +
-          "header(Access-Control-Allow-Origin,null)", "*");
-    } else {
-      assert_equals(evt.data.trim(), "PASS");
-      test.done();
-    }
-  }), false);
-});
-    </script>
-    <iframe id="frame" sandbox="allow-scripts" src="/XMLHttpRequest/resources/access-control-sandboxed-iframe.html">
-    </iframe>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-sandboxed-iframe-allow.htm b/XMLHttpRequest/access-control-sandboxed-iframe-allow.htm
deleted file mode 100644
index 21413f3..0000000
--- a/XMLHttpRequest/access-control-sandboxed-iframe-allow.htm
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that sandboxed iframe has CORS XHR access to a server that accepts all domains</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-async_test((test) => {
-  window.addEventListener("message", test.step_func((evt) => {
-    if (evt.data === "ready") {
-      document.getElementById("frame").contentWindow.postMessage(
-          get_host_info().HTTP_ORIGIN +
-          "/XMLHttpRequest/resources/pass.txt?pipe=" +
-          "header(Cache-Control,no-store)|" +
-          "header(Content-Type,text/plain)|" +
-          "header(Access-Control-Allow-Credentials,true)|" +
-          "header(Access-Control-Allow-External,true)|" +
-          "header(Access-Control-Allow-Origin,*)", "*");
-    } else {
-      assert_equals(evt.data.trim(), "PASS");
-      test.done();
-    }
-  }), false);
-});
-    </script>
-    <iframe id="frame" sandbox="allow-scripts" src="/XMLHttpRequest/resources/access-control-sandboxed-iframe.html">
-    </iframe>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-sandboxed-iframe-denied-without-wildcard.htm b/XMLHttpRequest/access-control-sandboxed-iframe-denied-without-wildcard.htm
deleted file mode 100644
index 7c375f6..0000000
--- a/XMLHttpRequest/access-control-sandboxed-iframe-denied-without-wildcard.htm
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that sandboxed iframe does not have CORS XHR access to server with "Access-Control-Allow-Origin" set to the original origin</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-const path = "/XMLHttpRequest/resources/pass.txt?pipe=" +
-    "header(Cache-Control,no-store)|" +
-    "header(Content-Type,text/plain)" +
-    "header(Access-Control-Allow-Credentials,true)|" +
-    "header(Access-Control-Allow-Origin," + get_host_info().HTTP_ORIGIN + ")";
-
-async_test((test) => {
-  const xhr = new XMLHttpRequest;
-  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + path);
-  xhr.send();
-  xhr.onerror = test.unreached_func("Unexpected error");
-  xhr.onload = test.step_func_done(() => {
-    assert_equals(xhr.status, 200);
-    assert_equals(xhr.responseText.trim(), "PASS");
-  });
-}, "Check that path exists and is accessible via CORS XHR request");
-
-async_test((test) => {
-  window.addEventListener("message", test.step_func((evt) => {
-    if (evt.data === "ready") {
-      document.getElementById("frame").contentWindow.postMessage(
-          get_host_info().HTTP_REMOTE_ORIGIN + path, "*");
-    } else {
-      assert_equals(evt.data, "Exception thrown. Sandboxed iframe XHR access was denied in 'send'.");
-      test.done();
-    }
-  }), false);
-}, "Sandboxed iframe is denied CORS access to server that allows parent origin");
-    </script>
-    <iframe id="frame" sandbox="allow-scripts" src="/XMLHttpRequest/resources/access-control-sandboxed-iframe.html">
-    </iframe>
-  </body>
-</html>
diff --git a/XMLHttpRequest/access-control-sandboxed-iframe-denied.htm b/XMLHttpRequest/access-control-sandboxed-iframe-denied.htm
deleted file mode 100644
index a87dd7d..0000000
--- a/XMLHttpRequest/access-control-sandboxed-iframe-denied.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Tests that sandboxed iframe does not have CORS XHR access to its server</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-    <script type="text/javascript">
-const path = "/XMLHttpRequest/resources/pass.txt?pipe=" +
-    "header(Cache-Control,no-store)|" +
-    "header(Content-Type,text/plain)";
-
-async_test((test) => {
-  const xhr = new XMLHttpRequest;
-  xhr.open("GET", get_host_info().HTTP_ORIGIN + path);
-  xhr.send();
-  xhr.onerror = test.unreached_func("Unexpected error");
-  xhr.onload = test.step_func_done(() => {
-    assert_equals(xhr.status, 200);
-    assert_equals(xhr.responseText.trim(), "PASS");
-  });
-}, "Check that path exists and is accessible via local XHR request");
-
-async_test((test) => {
-  window.addEventListener("message", test.step_func((evt) => {
-    if (evt.data === "ready") {
-      document.getElementById("frame").contentWindow.postMessage(
-          get_host_info().HTTP_ORIGIN + path, "*");
-    } else {
-      assert_equals(evt.data, "Exception thrown. Sandboxed iframe XHR access was denied in 'send'.");
-      test.done();
-    }
-  }), false);
-}, "Sandboxed iframe is denied access to path");
-    </script>
-    <iframe id="frame" sandbox="allow-scripts" src="/XMLHttpRequest/resources/access-control-sandboxed-iframe.html">
-    </iframe>
-  </body>
-</html>
diff --git a/XMLHttpRequest/allow-lists-starting-with-comma.htm b/XMLHttpRequest/allow-lists-starting-with-comma.htm
deleted file mode 100644
index 4a4e5e2..0000000
--- a/XMLHttpRequest/allow-lists-starting-with-comma.htm
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Allow lists starting with a comma should be parsed correctly</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-  </head>
-  <body>
-  <script type="text/javascript">
-    async_test(function(test) {
-      const client = new XMLHttpRequest();
-      let url = "XMLHttpRequest/resources/access-control-allow-lists.py?headers=,y-lol,x-print,%20,,,y-print&origin=" +
-          get_host_info().HTTP_ORIGIN;
-      client.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + '/' + url, false);
-      client.setRequestHeader('x-print', 'unicorn')
-      client.setRequestHeader('y-print', 'narwhal')
-      // Sending GET request with custom headers
-      assert_equals(client.send(null), undefined);
-      const response = JSON.parse(client.response);
-      assert_equals(response['x-print'], "unicorn");
-      assert_equals(response['y-print'], "narwhal");
-
-      url = "XMLHttpRequest/resources/access-control-allow-lists.py?methods=,,PUT,GET&origin=" +
-          get_host_info().HTTP_ORIGIN;
-      client.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + '/' + url, false);
-      // Sending PUT request
-      assert_equals(client.send(null), undefined);
-      test.done();
-    }, "Allow lists starting with a comma should be parsed correctly");
-  </script>
-  </body>
-  </html>
diff --git a/XMLHttpRequest/event-error-order.sub.html b/XMLHttpRequest/event-error-order.sub.html
deleted file mode 100644
index 9be8b4a..0000000
--- a/XMLHttpRequest/event-error-order.sub.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta name="assert" content="Check the order of events fired when the request has failed.">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="resources/xmlhttprequest-event-order.js"></script>
-    <title>XMLHttpRequest: event - error (order of events)</title>
-</head>
-
-<body>
-    <div id="log"></div>
-
-    <script type="text/javascript">
-        var test = async_test();
-
-        test.step(function()
-        {
-            var xhr = new XMLHttpRequest();
-            prepare_xhr_for_event_order_test(xhr);
-
-            xhr.addEventListener("loadend", function() {
-                test.step(function() {
-                    // no progress events due to CORS failure
-                    assert_xhr_event_order_matches([1, "loadstart(0,0,false)", "upload.loadstart(0,12,true)", 2, 4, "upload.error(0,0,false)", "upload.loadend(0,0,false)", "error(0,0,false)", "loadend(0,0,false)"]);
-                    test.done();
-                });
-            });
-
-            xhr.open("POST", "http://nonexistent-origin.{{host}}:{{ports[http][0]}}", true);
-            xhr.send("Test Message");
-        });
-    </script>
-</body>
-</html>
diff --git a/XMLHttpRequest/event-error.sub.html b/XMLHttpRequest/event-error.sub.html
deleted file mode 100644
index 3171c49..0000000
--- a/XMLHttpRequest/event-error.sub.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>XMLHttpRequest Test: event - error</title>
-<link rel="author" title="Intel" href="http://www.intel.com">
-<meta name="assert" content="Check if event onerror is fired When the request has failed.">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script>
-
-async_test(function (t) {
-  var client = new XMLHttpRequest();
-  client.onerror = t.step_func(function(e) {
-    assert_true(e instanceof ProgressEvent);
-    assert_equals(e.type, "error");
-    t.done();
-  });
-
-  client.open("GET", "http://nonexistent-origin.{{host}}:{{ports[http][0]}}");
-  client.send("null");
-}, document.title);
-
-</script>
diff --git a/XMLHttpRequest/event-upload-progress-crossorigin.htm b/XMLHttpRequest/event-upload-progress-crossorigin.htm
deleted file mode 100644
index 293f7bf..0000000
--- a/XMLHttpRequest/event-upload-progress-crossorigin.htm
+++ /dev/null
@@ -1,33 +0,0 @@
-<!doctype html>
-<html lang=en>
-<meta charset=utf-8>
-<title>XMLHttpRequest: upload progress event for cross-origin requests</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/common/get-host-info.sub.js"></script>
-<div id="log"></div>
-<script>
-const remote = get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/corsenabled.py",
-      redirect = "resources/redirect.py?code=307&location=" + remote;
-
-[remote, redirect].forEach(url => {
-  async_test(test => {
-    const client = new XMLHttpRequest();
-    client.upload.onprogress = test.step_func_done()
-    client.onload = test.unreached_func()
-    client.open("POST", url)
-    client.send("On time: " + url)
-  }, "Upload events registered on time (" + url + ")");
-});
-
-[remote, redirect].forEach(url => {
-  async_test(test => {
-    const client = new XMLHttpRequest();
-    client.onload = test.step_func_done();
-    client.open("POST", url);
-    client.send("Too late: " + url);
-    client.upload.onloadstart = test.unreached_func(); // registered too late
-    client.upload.onprogress = test.unreached_func(); // registered too late
-  }, "Upload events registered too late (" + url + ")");
-});
-</script>
diff --git a/XMLHttpRequest/firing-events-http-content-length.html b/XMLHttpRequest/firing-events-http-content-length.html
deleted file mode 100644
index 6e54852..0000000
--- a/XMLHttpRequest/firing-events-http-content-length.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>ProgressEvent: firing events for HTTP with Content-Length</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#firing-events-using-the-progressevent-interface">
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      var test = async_test();
-
-      test.step(function() {
-        var xhr = new XMLHttpRequest();
-
-        xhr.onprogress = function(pe) {
-          test.step(function() {
-            if(pe.type == "progress") {
-              assert_greater_than_equal(pe.loaded, 0, "loaded");
-              assert_true(pe.lengthComputable, "lengthComputable");
-              assert_equals(pe.total, 1300, "total");
-            }
-          }, "Check lengthComputed, loaded, total when Content-Length is given.");
-        }
-
-        // "loadstart", "error", "abort", "load" tests are out of scope.
-        // They SHOULD be tested in each spec that implement ProgressEvent.
-
-        xhr.onloadend = function(pe) {
-          test.done();
-        }
-        xhr.open("GET", "resources/trickle.py?ms=0&count=100&specifylength=1", true);
-        xhr.send(null);
-      })
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/firing-events-http-no-content-length.html b/XMLHttpRequest/firing-events-http-no-content-length.html
deleted file mode 100644
index 2a4614a..0000000
--- a/XMLHttpRequest/firing-events-http-no-content-length.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>ProgressEvent: firing events for HTTP with no Content-Length</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#firing-events-using-the-progressevent-interface">
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      var test = async_test();
-
-      test.step(function() {
-        var xhr = new XMLHttpRequest();
-
-        xhr.onprogress = function(pe) {
-          test.step(function() {
-            if(pe.type == "progress") {
-              assert_greater_than_equal(pe.loaded, 0, "loaded");
-              assert_false(pe.lengthComputable, "lengthComputable");
-              assert_equals(pe.total, 0, "total");
-            }
-          }, "Check lengthComputed, loaded, total when Content-Length is NOT given.");
-        }
-
-        // "loadstart", "error", "abort", "load" tests are out of scope.
-        // They SHOULD be tested in each spec that implement ProgressEvent.
-
-        xhr.onloadend = function(pe) {
-          test.done();
-        }
-        xhr.open("GET", "resources/trickle.py?ms=0&count=100", true);
-        xhr.send(null);
-      })
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/interfaces.html b/XMLHttpRequest/interfaces.html
deleted file mode 100644
index 56ba552..0000000
--- a/XMLHttpRequest/interfaces.html
+++ /dev/null
@@ -1,136 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>XMLHttpRequest IDL tests</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-
-<h1>XMLHttpRequest IDL tests</h1>
-<div id=log></div>
-
-<script type=text/plain class=untested>
-[TreatNonCallableAsNull]
-callback EventHandlerNonNull = any (Event event);
-typedef EventHandlerNonNull? EventHandler;
-</script>
-<script type=text/plain>
-/*[Exposed=(Window,Worker)]*/
-interface XMLHttpRequestEventTarget : EventTarget {
-  // event handlers
-  attribute EventHandler onloadstart;
-  attribute EventHandler onprogress;
-  attribute EventHandler onabort;
-  attribute EventHandler onerror;
-  attribute EventHandler onload;
-  attribute EventHandler ontimeout;
-  attribute EventHandler onloadend;
-};
-
-/*[Exposed=(Window,Worker)]*/
-interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {
-};
-
-enum XMLHttpRequestResponseType {
-  "",
-  "arraybuffer",
-  "blob",
-  "document",
-  "json",
-  "text"
-};
-
-[Constructor/*,
- Exposed=(Window,Worker)*/]
-interface XMLHttpRequest : XMLHttpRequestEventTarget {
-  // event handler
-  attribute EventHandler onreadystatechange;
-
-  // states
-  const unsigned short UNSENT = 0;
-  const unsigned short OPENED = 1;
-  const unsigned short HEADERS_RECEIVED = 2;
-  const unsigned short LOADING = 3;
-  const unsigned short DONE = 4;
-  readonly attribute unsigned short readyState;
-
-  // request
-  void open(ByteString method, USVString url);
-  void open(ByteString method, USVString url, boolean async, optional USVString? username = null, optional USVString? password = null);
-  void setRequestHeader(ByteString name, ByteString value);
-           attribute unsigned long timeout;
-           attribute boolean withCredentials;
-  readonly attribute XMLHttpRequestUpload upload;
-  void send(optional (Document or BodyInit)? body = null);
-  void abort();
-
-  // response
-  readonly attribute USVString responseURL;
-  readonly attribute unsigned short status;
-  readonly attribute ByteString statusText;
-  ByteString? getResponseHeader(ByteString name);
-  ByteString getAllResponseHeaders();
-  void overrideMimeType(DOMString mime);
-           attribute XMLHttpRequestResponseType responseType;
-  readonly attribute any response;
-  readonly attribute USVString responseText;
-  [Exposed=Window] readonly attribute Document? responseXML;
-};
-
-typedef (File or USVString) FormDataEntryValue;
-
-[Constructor(optional HTMLFormElement form)/*,
- Exposed=(Window,Worker)*/]
-interface FormData {
-  void append(USVString name, Blob value, optional USVString filename);
-  void append(USVString name, USVString value);
-  void delete(USVString name);
-  FormDataEntryValue? get(USVString name);
-  sequence<FormDataEntryValue> getAll(USVString name);
-  boolean has(USVString name);
-  void set(USVString name, Blob value, optional USVString filename);
-  void set(USVString name, USVString value);
-  /*iterable<USVString, FormDataEntryValue>;*/
-};
-
-[Constructor(DOMString type, optional ProgressEventInit eventInitDict)/*,
- Exposed=(Window,Worker)*/]
-interface ProgressEvent : Event {
-  readonly attribute boolean lengthComputable;
-  readonly attribute unsigned long long loaded;
-  readonly attribute unsigned long long total;
-};
-
-dictionary ProgressEventInit : EventInit {
-  boolean lengthComputable = false;
-  unsigned long long loaded = 0;
-  unsigned long long total = 0;
-};
-</script>
-<script>
-"use strict";
-var form = document.createElement("form");
-var idlArray = new IdlArray();
-
-function doTest(domIdl) {
-  idlArray.add_untested_idls(domIdl);
-  [].forEach.call(document.querySelectorAll("script[type=text\\/plain]"), function(node) {
-    if (node.className == "untested") {
-      idlArray.add_untested_idls(node.textContent);
-    } else {
-      idlArray.add_idls(node.textContent);
-    }
-  });
-  idlArray.add_objects({
-    XMLHttpRequest: ['new XMLHttpRequest()'],
-    XMLHttpRequestUpload: ['(new XMLHttpRequest()).upload'],
-    FormData: ['new FormData()', 'new FormData(form)']
-  });
-  idlArray.test();
-}
-
-promise_test(function() {
-  return fetch("/interfaces/dom.idl").then(response => response.text())
-                                     .then(doTest);
-}, "Test driver");
-</script>
diff --git a/XMLHttpRequest/open-url-fragment.htm b/XMLHttpRequest/open-url-fragment.htm
deleted file mode 100644
index 6b3fdeb..0000000
--- a/XMLHttpRequest/open-url-fragment.htm
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>XMLHttpRequest: open() resolving URLs - fragment identifier</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[7]" />
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      test(function() {
-        var client = new XMLHttpRequest()
-        client.open("GET", "folder.txt#foobar", false)
-        client.send(null)
-        assert_equals(client.responseText, "top\n")
-      })
-      test(function() {
-        var client = new XMLHttpRequest()
-        client.open("GET", "resources/requri.py#foobar", false)
-        client.send(null)
-        assert_regexp_match(client.responseText, /XMLHttpRequest\/resources\/requri\.py$/)
-      }, 'make sure fragment is removed from URL before request')
-      test(function() {
-        var client = new XMLHttpRequest()
-        client.open("GET", "resources/requri.py?help=#foobar", false)
-        client.send(null)
-        assert_regexp_match(client.responseText, /XMLHttpRequest\/resources\/requri\.py\?help=$/)
-      }, 'make sure fragment is removed from URL before request (with query string)')
-      test(function() {
-        var client = new XMLHttpRequest()
-        client.open("GET", "resources/requri.py?" +encodeURIComponent("#foobar"), false)
-        client.send(null)
-        assert_regexp_match(client.responseText, /XMLHttpRequest\/resources\/requri\.py\?%23foobar$/)
-      }, 'make sure escaped # is not removed')
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/overridemimetype-blob.html b/XMLHttpRequest/overridemimetype-blob.html
deleted file mode 100644
index 83378a8..0000000
--- a/XMLHttpRequest/overridemimetype-blob.html
+++ /dev/null
@@ -1,65 +0,0 @@
-<!doctype html>
-<title>XMLHttpRequest: overrideMimeType() and responseType = "blob"</title>
-<meta charset="utf-8">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
-<script>
-async_test(t => {
-  const client = new XMLHttpRequest()
-  client.onload = t.step_func_done(() => {
-    assert_equals(client.getResponseHeader("Content-Type"), "")
-    assert_equals(client.response.type, "text/xml")
-  })
-  client.open("GET", "resources/status.py")
-  client.responseType = "blob"
-  client.send()
-}, "Use text/xml as fallback MIME type")
-
-async_test(t => {
-  const client = new XMLHttpRequest()
-  client.onload = t.step_func_done(() => {
-    assert_equals(client.getResponseHeader("Content-Type"), "")
-    assert_equals(client.response.type, "text/xml")
-  })
-  client.open("GET", "resources/status.py?content=thisshouldnotmakeadifferencebutdoes")
-  client.responseType = "blob"
-  client.send()
-}, "Use text/xml as fallback MIME type, 2")
-
-async_test(t => {
-  const client = new XMLHttpRequest()
-  client.onload = t.step_func_done(() => {
-    assert_equals(client.getResponseHeader("Content-Type"), "")
-    assert_equals(client.response.type, "application/octet-stream")
-  })
-  client.open("GET", "resources/status.py")
-  client.responseType = "blob"
-  client.overrideMimeType("bogus")
-  client.send()
-}, "Bogus MIME type should end up as application/octet-stream")
-
-async_test(t => {
-  const client = new XMLHttpRequest()
-  client.onload = t.step_func_done(() => {
-    assert_equals(client.getResponseHeader("Content-Type"), "")
-    assert_equals(client.response.type, "application/octet-stream")
-  })
-  client.open("GET", "resources/status.py")
-  client.responseType = "blob"
-  client.overrideMimeType("text/xml;charset=†")
-  client.send()
-}, "Bogus MIME type should end up as application/octet-stream, 2")
-
-async_test(t => {
-  const client = new XMLHttpRequest()
-  client.onload = t.step_func_done(() => {
-    assert_equals(client.getResponseHeader("Content-Type"), "")
-    assert_equals(client.response.type, "hi/x")
-  })
-  client.open("GET", "resources/status.py")
-  client.responseType = "blob"
-  client.overrideMimeType("HI/x;test=test")
-  client.send()
-}, "Valid MIME types need to be normalized")
-</script>
diff --git a/XMLHttpRequest/resources/authentication.py b/XMLHttpRequest/resources/authentication.py
deleted file mode 100644
index 4f65fa2..0000000
--- a/XMLHttpRequest/resources/authentication.py
+++ /dev/null
@@ -1,32 +0,0 @@
-def main(request, response):
-    if "logout" in request.GET:
-        return ((401, "Unauthorized"),
-                [("WWW-Authenticate", 'Basic realm="test"')],
-                "Logged out, hopefully")
-
-    session_user = request.auth.username
-    session_pass = request.auth.password
-    expected_user_name = request.headers.get("X-User", None)
-
-    token = expected_user_name
-    if session_user is None and session_pass is None:
-        if token is not None and request.server.stash.take(token) is not None:
-            return 'FAIL (did not authorize)'
-        else:
-            if token is not None:
-                request.server.stash.put(token, "1")
-            status = (401, 'Unauthorized')
-            headers = [('WWW-Authenticate', 'Basic realm="test"'),
-                       ('XHR-USER', expected_user_name),
-                       ('SES-USER', session_user)]
-            return status, headers, 'FAIL (should be transparent)'
-    else:
-        if request.server.stash.take(token) == "1":
-            challenge = "DID"
-        else:
-            challenge = "DID-NOT"
-        headers = [('XHR-USER', expected_user_name),
-                   ('SES-USER', session_user),
-                   ("X-challenge", challenge)]
-        return headers, session_user + "\n" + session_pass;
-
diff --git a/XMLHttpRequest/responsetext-decoding.htm b/XMLHttpRequest/responsetext-decoding.htm
deleted file mode 100644
index c7e3783..0000000
--- a/XMLHttpRequest/responsetext-decoding.htm
+++ /dev/null
@@ -1,92 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>XMLHttpRequest: responseText decoding</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      function create_html(content) {
-        return "<!doctype html><meta charset=windows-1252><x>" + content + "</x>";
-      }
-      function create_encoded_html(encoded_content) {
-        return encodeURIComponent("<!doctype html><meta charset=windows-1252><x>") + encoded_content + encodeURIComponent("<\/x>");
-      }
-      function create_xml(content) {
-        return "<?xml version='1.0' encoding='windows-1252'?><x>" + content + "</x>";
-      }
-      function create_encoded_xml(encoded_content) {
-        return encodeURIComponent("<?xml version='1.0' encoding='windows-1252'?><x>") + encoded_content + encodeURIComponent("<\/x>");
-      }
-      function request(type, input, output, responseType) {
-        async_test((test) => {
-          const client = new XMLHttpRequest();
-          if (responseType !== undefined) {
-            client.responseType = responseType;
-          }
-          client.open("GET", "resources/status.py?content=" + input + "&type=" + encodeURIComponent(type), true);
-          client.onload = test.step_func_done(() => {
-            assert_equals(client.responseText, output);
-          })
-          client.send(null);
-        }, document.title + " (" + type + " " + input + " " + (responseType ? " " + responseType : "empty") + ")");
-      }
-
-      const encoded_content = "%e6%a9%9f";
-      const decoded_as_windows_1252 = "\u00e6\u00a9\u0178";
-      const decoded_as_utf_8 = "\u6a5f";
-      const encoded_xml = create_encoded_xml(encoded_content);
-      const encoded_html = create_encoded_html(encoded_content);
-      const xml_decoded_as_windows_1252 = create_xml(decoded_as_windows_1252);
-      const xml_decoded_as_utf_8 = create_xml(decoded_as_utf_8);
-      const html_decoded_as_windows_1252 = create_html(decoded_as_windows_1252);
-      const html_decoded_as_utf_8 = create_html(decoded_as_utf_8);
-
-      // "default" response type
-      // An XML-ish response is sniffed.
-      request("application/xml", encoded_xml, xml_decoded_as_windows_1252);
-      // An HTML-ish response isn't sniffed.
-      request("text/html", encoded_html, html_decoded_as_utf_8);
-      request("application/xml;charset=utf-8", encoded_xml, xml_decoded_as_utf_8);
-      request("application/xml;charset=windows-1252", encoded_xml, xml_decoded_as_windows_1252);
-      request("text/html;charset=utf-8", encoded_html, html_decoded_as_utf_8);
-      request("text/html;charset=windows-1252", encoded_html, html_decoded_as_windows_1252);
-      request("text/plain;charset=windows-1252", "%FF", "\u00FF");
-      request("text/plain", "%FF", "\uFFFD");
-      request("text/plain", "%FE%FF", "");
-      request("text/plain", "%FE%FF%FE%FF", "\uFEFF");
-      request("text/plain", "%EF%BB%BF", "");
-      request("text/plain", "%EF%BB%BF%EF%BB%BF", "\uFEFF");
-      request("text/plain", "%C2", "\uFFFD");
-      request("text/xml", "%FE%FF", "");
-      request("text/xml", "%FE%FF%FE%FF", "\uFEFF");
-      request("text/xml", "%EF%BB%BF", "");
-      request("text/xml", "%EF%BB%BF%EF%BB%BF", "\uFEFF");
-      request("text/plain", "%E3%81%B2", "\u3072");
-
-      // "text" response type
-      // An XML-ish response isn't sniffed.
-      request("application/xml", encoded_xml, xml_decoded_as_utf_8, "text");
-      // An HTML-ish response isn't sniffed.
-      request("text/html", encoded_html, html_decoded_as_utf_8, "text");
-      request("application/xml;charset=utf-8", encoded_xml, xml_decoded_as_utf_8, "text");
-      request("application/xml;charset=windows-1252", encoded_xml, xml_decoded_as_windows_1252, "text");
-      request("text/html;charset=utf-8", encoded_html, html_decoded_as_utf_8, "text");
-      request("text/html;charset=windows-1252", encoded_html, html_decoded_as_windows_1252, "text");
-      request("text/plain;charset=windows-1252", "%FF", "\u00FF", "text");
-      request("text/plain", "%FF", "\uFFFD", "text");
-      request("text/plain", "%FE%FF", "", "text");
-      request("text/plain", "%FE%FF%FE%FF", "\uFEFF", "text");
-      request("text/plain", "%EF%BB%BF", "", "text");
-      request("text/plain", "%EF%BB%BF%EF%BB%BF", "\uFEFF", "text");
-      request("text/plain", "%C2", "\uFFFD", "text");
-      request("text/xml", "%FE%FF", "", "text");
-      request("text/xml", "%FE%FF%FE%FF", "\uFEFF", "text");
-      request("text/xml", "%EF%BB%BF", "", "text");
-      request("text/xml", "%EF%BB%BF%EF%BB%BF", "\uFEFF", "text");
-      request("text/plain", "%E3%81%B2", "\u3072", "text");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/responsexml-document-properties.htm b/XMLHttpRequest/responsexml-document-properties.htm
deleted file mode 100644
index 30bf7d0..0000000
--- a/XMLHttpRequest/responsexml-document-properties.htm
+++ /dev/null
@@ -1,106 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>XMLHttpRequest: responseXML document properties</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      var timePreXHR = Math.floor(new Date().getTime(new Date().getTime() - 3000) / 1000); // three seconds ago, in case there's clock drift
-      var client = new XMLHttpRequest()
-      client.open("GET", "resources/well-formed.xml", false)
-      client.send(null)
-      var responseURL = new URL('resources/well-formed.xml', location.href).href
-      var expected = {
-        domain:undefined,
-        URL:responseURL,
-        documentURI:responseURL,
-        baseURI:responseURL,
-        referrer:'',
-        title:'',
-        contentType:'application/xml',
-        readyState:'complete',
-        location:null,
-        defaultView:null,
-        body:undefined,
-        images: undefined,
-        doctype:null,
-        forms:undefined,
-        all:undefined,
-        links: undefined,
-        cookie:''
-      }
-
-      for (var name in expected) {
-        runTest(name, expected[name])
-      }
-
-      function runTest(name, value){
-        test(function(){
-          assert_equals(client.responseXML[name], value)
-        }, name)
-      }
-
-      async_test(t => {
-        const client = new XMLHttpRequest();
-        client.open("GET", "resources/redirect.py?location=well-formed.xml");
-        client.send();
-        client.onload = t.step_func_done(() => {
-          assert_equals(client.responseXML.URL, responseURL);
-          assert_equals(client.responseXML.baseURI, responseURL);
-        });
-      }, "Test document URL properties after redirect");
-
-      async_test(t => {
-        const client = new XMLHttpRequest();
-        client.open("GET", "resources/redirect.py?location=base.xml");
-        client.send();
-        client.onload = t.step_func_done(() => {
-          const localResponseURL = new URL('resources/base.xml', location.href).href;
-          assert_equals(client.responseXML.URL, localResponseURL);
-          assert_equals(client.responseXML.baseURI, 'https://example.com/');
-          client.responseXML.documentElement.remove();
-          assert_equals(client.responseXML.baseURI, localResponseURL);
-          const newBase = document.createElement("base"),
-                newBaseURL = "https://elsewhere.example/";
-          newBase.href = "https://elsewhere.example/";
-          client.responseXML.appendChild(newBase);
-          assert_equals(client.responseXML.baseURI, newBaseURL);
-          newBase.remove();
-          document.head.appendChild(newBase);
-          assert_equals(client.responseXML.baseURI, localResponseURL);
-          newBase.remove();
-        });
-      }, "Test document URL properties of document with <base> after redirect");
-
-      test(function() {
-        var lastModified = Math.floor(new Date(client.responseXML.lastModified).getTime() / 1000);
-        var now = Math.floor(new Date().getTime(new Date().getTime() + 3000) / 1000); // three seconds from now, in case there's clock drift
-        assert_greater_than_equal(lastModified, timePreXHR);
-        assert_less_than_equal(lastModified, now);
-      }, 'lastModified set to time of response if no HTTP header provided')
-
-      test(function() {
-        var client2 = new XMLHttpRequest()
-        client2.open("GET", "resources/last-modified.py", false)
-        client2.send(null)
-        assert_equals((new Date(client2.getResponseHeader('Last-Modified'))).getTime(), (new Date(client2.responseXML.lastModified)).getTime())
-      }, 'lastModified set to related HTTP header if provided')
-
-      test(function() {
-        client.responseXML.cookie = "thisshouldbeignored"
-        assert_equals(client.responseXML.cookie, "")
-      }, 'cookie (after setting it)')
-
-      test(function() {
-        assert_equals(typeof(client.responseXML.styleSheets), "object")
-      }, 'styleSheets')
-
-      test(function() {
-        assert_equals(typeof(client.responseXML.implementation), "object")
-      }, 'implementation')
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/security-consideration.sub.html b/XMLHttpRequest/security-consideration.sub.html
deleted file mode 100644
index 5eb7110..0000000
--- a/XMLHttpRequest/security-consideration.sub.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>ProgressEvent: security consideration</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#security-considerations" data-tested-assertations="/following-sibling::p" />
-    <link rel="help" href="https://fetch.spec.whatwg.org/#http-fetch" data-tested-assertations="/following-sibling::ol[1]/li[3]/ol[1]/li[6]" />
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      async_test(function() {
-        var xhr = new XMLHttpRequest();
-
-        xhr.onprogress = this.unreached_func("MUST NOT dispatch progress event.");
-        xhr.onload = this.unreached_func("MUST NOT dispatch load event.");
-        xhr.onerror = this.step_func(function(pe) {
-          assert_equals(pe.type, "error");
-          assert_equals(pe.loaded, 0, "loaded is zero.");
-          assert_false(pe.lengthComputable, "lengthComputable is false.");
-          assert_equals(pe.total, 0, "total is zero.");
-        });
-        xhr.onloadend = this.step_func(function(pe) {
-          assert_equals(pe.type, "loadend");
-          assert_equals(pe.loaded, 0, "loaded is zero.");
-          assert_false(pe.lengthComputable, "lengthComputable is false.");
-          assert_equals(pe.total, 0, "total is zero.");
-          this.done();
-        });
-        xhr.open("GET", "http://{{host}}:{{ports[http][1]}}/XMLHttpRequest/resources/img.jpg", true);
-        xhr.send(null);
-      })
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/send-after-setting-document-domain.htm b/XMLHttpRequest/send-after-setting-document-domain.htm
deleted file mode 100644
index 30b6c71..0000000
--- a/XMLHttpRequest/send-after-setting-document-domain.htm
+++ /dev/null
@@ -1,39 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>XMLHttpRequest: send() with document.domain set</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[2]/ol[1]/li[3]" />
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      var test_base_url = location.protocol+'//www2.'+location.host+"/XMLHttpRequest/resources/",
-          test_windows = [
-            window.open(test_base_url + "send-after-setting-document-domain-window-1.htm"),
-            window.open(test_base_url + "send-after-setting-document-domain-window-2.htm"),
-          ],
-          num_tests_left = test_windows.length;
-
-      async_test(function(wrapper_test) {
-        window.addEventListener("message", function(evt) {
-          // run a shadow test that just forwards the results
-          async_test(function(test) {
-            assert_true(evt.data.passed, evt.data.message);
-            test.done();
-          }, evt.data.name);
-
-          // after last result comes in, close all test
-          // windows and complete the wrapper test.
-          if (--num_tests_left == 0) {
-            for (var i=0; i<test_windows.length; ++i) {
-              test_windows[i].close();
-            }
-            wrapper_test.done();
-          }
-        }, false);
-      }, "All tests ran");
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/send-authentication-competing-names-passwords.htm b/XMLHttpRequest/send-authentication-competing-names-passwords.htm
deleted file mode 100644
index d58d9e0..0000000
--- a/XMLHttpRequest/send-authentication-competing-names-passwords.htm
+++ /dev/null
@@ -1,54 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>XMLHttpRequest: send() - "Basic" authenticated requests with competing user name/password options</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/common/utils.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[9]/ol[1]/li[1] following::ol[1]/li[9]/ol[1]/li[2]" />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::code[contains(@title,'http-authorization')]/.." />  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      function request(user1, pass1, user2, pass2, name) {
-        // user1, pass1 will if given become userinfo part of URL
-        // user2, pass2 will if given be passed to open() call
-        test(function() {
-          var client = new XMLHttpRequest(),
-              urlstart = "", userwin, passwin
-          // if user2 is set, winning user name and password is 2
-          if(user2)
-            userwin = user2, passwin = pass2
-          // if user1 is set, and user2 is not set, user1 and pass1 win
-          if(user1 && ! user2)
-            userwin = user1, passwin = pass1
-          // if neither user name is set, pass 2 wins (there will be no userinfo in URL)
-          if (!(user1 || user2))
-            passwin = pass2
-          if(user1) { // should add userinfo to URL (there is no way to create userinfo part of URL with only password in)
-            urlstart = "http://" + user1
-            if(pass1)
-              urlstart += ":" + pass1
-            urlstart += "@" + location.host + location.pathname.replace(/\/[^\/]*$/, '/')
-          }
-          client.open("GET", urlstart + "resources/authentication.py", false, user2, pass2)
-          client.setRequestHeader("x-user", userwin)
-          client.send(null)
-          assert_true(client.responseText == ((userwin||'') + "\n" + (passwin||'')), 'responseText should contain the right user and password')
-
-          // We want to send multiple requests to the same realm here, so we try to make the UA forget its (cached) credentials between each test..
-          // forcing a 401 response to (hopefully) "log out"
-          // NOTE: This is commented out because it causes authentication prompts while running the test
-          //client.open('GET', "resources/authentication.py?logout=1", false)
-          //client.send()
-        }, document.title+' '+name)
-      }
-      request(null, null, token(), token(), 'user/pass in open() call')
-      request(null, null, token(), token(), 'another user/pass in open() call - must override cached credentials from previous test')
-      request("userinfo-user", "userinfo-pass", token(), token(), 'user/pass both in URL userinfo AND open() call - expexted that open() wins')
-      request(token(), token(), null, null, 'user/pass *only* in URL userinfo')
-      request(token(), null, null, token(), 'user name in URL userinfo, password in open() call: user name wins and password is thrown away')
-      request("1", token(), token(), null, 'user name and password in URL userinfo, only user name in open() call: user name in open() wins')
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/send-content-type-charset.htm b/XMLHttpRequest/send-content-type-charset.htm
deleted file mode 100644
index 9e93279..0000000
--- a/XMLHttpRequest/send-content-type-charset.htm
+++ /dev/null
@@ -1,83 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>XMLHttpRequest: send() - charset parameter of Content-Type</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::ol[1]/li[4]/p/code[contains(text(),'Content-Type')]/.. following::ol[1]/li[4]/p/code[contains(text(),'Content-Type')]/../following-sibling::p" />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#dom-XMLHttpRequest-send-a-string" data-tested-assertations="following::p[2]" />
-  </head>
-  <body>
-    <div id="log"></div>
-    <script>
-      function request(input, output, title) {
-        title = title || document.title + ' - ' + input;
-        test(function() {
-        var client = new XMLHttpRequest()
-        client.open("POST", "resources/content.py", false)
-        if(input)
-          client.setRequestHeader("Content-Type", input)
-        client.send("TEST")
-        assert_equals(client.responseText, "TEST")
-        assert_equals(client.getResponseHeader("x-request-content-type"), output)
-        }, title)
-      }
-
-      request(
-        "text; charset=ascii",
-        "text; charset=ascii",
-        "header with invalid MIME type is not changed"
-      )
-      request(
-        "charset=ascii",
-        "charset=ascii",
-        "known charset but bogus header - missing MIME type"
-      )
-      request(
-        "charset=bogus",
-        "charset=bogus",
-        "bogus charset and bogus header - missing MIME type"
-      )
-      request(
-        "text/plain;charset=utf-8",
-        "text/plain;charset=utf-8",
-        "Correct text/plain MIME with charset"
-      )
-      request(
-        "text/x-pink-unicorn",
-        "text/x-pink-unicorn",
-        "If no charset= param is given, implementation should not add one - unknown MIME"
-      )
-      request(
-        "text/plain",
-        "text/plain",
-        "If no charset= param is given, implementation should not add one - known MIME"
-      )
-      request(
-        "text/x-thepiano;charset= waddup",
-        "text/x-thepiano;charset=UTF-8",
-        "charset given but wrong, fix it (unknown MIME, bogus charset)"
-      )
-      request(
-        "text/plain;charset=utf-8;charset=waddup",
-        "text/plain;charset=utf-8;charset=UTF-8",
-        "charset given but wrong, fix it (known MIME, bogus charset)"
-      )
-      request(
-        "text/plain;charset=shift-jis",
-        "text/plain;charset=UTF-8",
-        "charset given but wrong, fix it (known MIME, actual charset)"
-      )
-      request(
-        "text/x-pink-unicorn; charset=windows-1252; charset=bogus; notrelated; charset=ascii",
-        "text/x-pink-unicorn; charset=UTF-8; charset=UTF-8; notrelated; charset=UTF-8",
-        "If multiple charset parameters are given, all should be rewritten"
-      )
-      request(
-        null,
-        "text/plain;charset=UTF-8",
-        "No content type set, give MIME and charset"
-      )
-    </script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/send-network-error-async-events.sub.htm b/XMLHttpRequest/send-network-error-async-events.sub.htm
deleted file mode 100644
index c803efa..0000000
--- a/XMLHttpRequest/send-network-error-async-events.sub.htm
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onerror" data-tested-assertations="../.." />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::ol[1]/li[9]/ol/li[2] following::ol[1]/li[9]/ol/li[3]" />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[4] following::dd[4]/p" />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#network-error" data-tested-assertations=".." />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#request-error" data-tested-assertations="following::ol[1]/li[4] following::ol[1]/li[6] following::ol[1]/li[7] following::ol[1]/li[7]/ol/li[3] following::ol[1]/li[7]/ol/li[4] following::ol[1]/li[9] following::ol[1]/li[10]" />
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <title>XMLHttpRequest: The send() method: Fire a progress event named error when Network error happens (synchronous flag is unset)</title>
-</head>
-
-<body>
-    <div id="log"></div>
-
-    <script type="text/javascript">
-        var test = async_test();
-
-        test.step(function(){
-            var xhr = new XMLHttpRequest();
-            var expect =  ["loadstart", "upload.loadstart", 4, "upload.error", "upload.loadend", "error", "loadend"];
-            var actual = [];
-
-            xhr.onreadystatechange = test.step_func(() => {
-                if (xhr.readyState == 4) {
-                    actual.push(xhr.readyState);
-                }
-            });
-
-            xhr.onloadstart        = test.step_func(e => { actual.push(e.type); })
-            xhr.onloadend          = test.step_func_done(e => {
-                actual.push(e.type);
-                assert_array_equals(actual, expect);
-            })
-            xhr.onerror            = test.step_func(e => { actual.push(e.type); })
-
-            xhr.upload.onloadstart = test.step_func(e => { actual.push("upload." + e.type); })
-            xhr.upload.onloadend   = test.step_func(e => { actual.push("upload." + e.type); })
-            xhr.upload.onerror     = test.step_func(e => { actual.push("upload." + e.type); })
-
-            xhr.open("POST", "http://nonexistent-origin.{{host}}:{{ports[http][0]}}", true);
-            xhr.send("Test Message");
-        });
-    </script>
-</body>
-</html>
diff --git a/XMLHttpRequest/send-network-error-sync-events.sub.htm b/XMLHttpRequest/send-network-error-sync-events.sub.htm
deleted file mode 100644
index 0ae9406..0000000
--- a/XMLHttpRequest/send-network-error-sync-events.sub.htm
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[4] following::dd[4]/p" />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#network-error" data-tested-assertations=".." />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#request-error" data-tested-assertations="following::ol[1]/li[4] following::ol[1]/li[5]" />
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <title>XMLHttpRequest: The send() method: Throw a "throw an "NetworkError" exception when Network error happens (synchronous flag is set)</title>
-</head>
-
-<body>
-    <div id="log"></div>
-
-    <script type="text/javascript">
-        test(function()
-        {
-            var xhr = new XMLHttpRequest();
-
-            xhr.open("POST", "http://nonexistent-origin.{{host}}}:{{ports[http][0]}}", false);
-
-            assert_throws("NetworkError", function()
-            {
-                xhr.send("Test Message");
-            });
-            assert_equals(xhr.readyState, 4)
-
-            xhr.open("GET", "data:text/html;charset=utf-8;base64,PT0NUWVBFIGh0bWw%2BDQo8", false);
-
-            assert_throws("NetworkError", function()
-            {
-                xhr.send("Test Message");
-            });
-            assert_equals(xhr.readyState, 4)
-
-        });
-    </script>
-</body>
-</html>
diff --git a/XMLHttpRequest/send-redirect-post-upload.htm b/XMLHttpRequest/send-redirect-post-upload.htm
deleted file mode 100644
index 37a90d4..0000000
--- a/XMLHttpRequest/send-redirect-post-upload.htm
+++ /dev/null
@@ -1,124 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onprogress" data-tested-assertations="../.." />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-progress" data-tested-assertations="../.." />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::dt[@id="dom-xmlhttprequest-send-bodyinit"]/following::dd[1]/p[2] following::ol[1]/li[9]//li[1] following::ol[1]/li[9]//li[2]" />
-    <link rel="help" href="https://fetch.spec.whatwg.org/#http-fetch" data-tested-assertations="following::ol[1]/li[6]/dl/dd[1]//dd[3]" />
-    <link rel="help" href="https://fetch.spec.whatwg.org/#concept-http-redirect-fetch" data-tested-assertations="following::li[16]" />
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <title>XMLHttpRequest: The send() method: POSTing to URL that redirects</title>
-</head>
-
-<body>
-    <div id="log"></div>
-
-    <script type="text/javascript">
-    function testRedirectPost(code, shouldResendPost) {
-        var test = async_test(document.title + " (" + code + ")");
-        var actual = [];
-        // We check upload.onprogress with a boolean because it *might* fire more than once
-        var progressFiredReadyState1 = false;
-
-        var expectedHeaders, expectedEvents;
-
-        // 307 redirects should resend the POST data, and events and headers will be a little different..
-        if(shouldResendPost) {
-            expectedHeaders = {
-                "X-Request-Content-Length": "11988",
-                "X-Request-Content-Type": "text/plain;charset=UTF-8",
-                "X-Request-Method": "POST",
-                "X-Request-Query": "NO",
-                "Content-Length": "11988"
-            }
-            expectedEvents = [
-                "xhr onreadystatechange 1",
-                "xhr loadstart 1",
-                "upload loadstart 1",
-                "upload loadend 1",
-                "xhr onreadystatechange 2",
-                "xhr onreadystatechange 3",
-                "xhr onreadystatechange 4",
-                "xhr load 4",
-                "xhr loadend 4"
-            ];
-        } else {
-            // setting the right expectations for POST resent as GET without request body
-            expectedHeaders = {
-                "X-Request-Content-Length": "NO",
-                "X-Request-Content-Type": "NO",
-                "X-Request-Method": "GET",
-                "X-Request-Query": "NO"
-            }
-            expectedEvents = [
-                "xhr onreadystatechange 1",
-                "xhr loadstart 1",
-                "upload loadstart 1",
-                "upload loadend 1",
-                "xhr onreadystatechange 2",
-                /* we expect no onreadystatechange readyState=3 event because there is no loading content */
-                "xhr onreadystatechange 4",
-                "xhr load 4",
-                "xhr loadend 4"
-            ];
-        }
-        test.step(function()
-        {
-            var xhr = new XMLHttpRequest();
-
-            xhr.upload.onloadstart = test.step_func(function(e) {
-                actual.push("upload loadstart " + xhr.readyState);
-            });
-            xhr.upload.onprogress = test.step_func(function(e) {
-                // events every 50ms, one final when uploading is done
-                if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
-                    assert_equals(xhr.status, 200, "JS never gets to see the 30x status code");
-                }
-                progressFiredReadyState1 = xhr.readyState === xhr.OPENED;
-            });
-            xhr.upload.onloadend = test.step_func(function() {
-                actual.push("upload loadend " + xhr.readyState);
-            });
-            xhr.onloadstart = test.step_func(function() {
-                actual.push("xhr loadstart " + xhr.readyState);
-            });
-            xhr.onreadystatechange = test.step_func(function() {
-                if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
-                    assert_equals(xhr.status, 200, "JS never gets to see the 30x status code");
-                }
-                actual.push("xhr onreadystatechange " + xhr.readyState);
-            });
-            xhr.onload = test.step_func(function(e)
-            {
-                actual.push("xhr load " + xhr.readyState);
-            });
-            xhr.onloadend = test.step_func(function(e)
-            {
-                actual.push("xhr loadend " + xhr.readyState);
-
-                assert_true(progressFiredReadyState1, "One progress event should fire on xhr.upload when readyState is 1");
-
-                // Headers will tell us if data was sent when expected
-                for(var header in expectedHeaders) {
-                    assert_equals(xhr.getResponseHeader(header), expectedHeaders[header], header);
-                }
-
-                assert_array_equals(actual, expectedEvents, "events firing in expected order and states");
-                test.done();
-            });
-
-            xhr.open("POST", "./resources/redirect.py?location=content.py&code=" + code, true);
-            xhr.send((new Array(1000)).join("Test Message"));
-        });
-    }
-
-
-    testRedirectPost(301, false);
-    testRedirectPost(302, false);
-    testRedirectPost(303, false);
-    testRedirectPost(307, true);
-
-    </script>
-</body>
-</html>
diff --git a/XMLHttpRequest/send-send.htm b/XMLHttpRequest/send-send.htm
deleted file mode 100644
index cbcbdb4..0000000
--- a/XMLHttpRequest/send-send.htm
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>XMLHttpRequest: send() - send()</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::ol/li[2]" />
-  </head>
-  <body>
-    <div id="log"></div>
-    <script src="send-send.js"></script>
-  </body>
-</html>
diff --git a/XMLHttpRequest/send-send.js b/XMLHttpRequest/send-send.js
deleted file mode 100644
index 2e7fe86..0000000
--- a/XMLHttpRequest/send-send.js
+++ /dev/null
@@ -1,7 +0,0 @@
-test(function() {
-  var client = new XMLHttpRequest()
-  client.open("GET", "resources/well-formed.xml")
-  client.send(null)
-  assert_throws("InvalidStateError", function() { client.send(null) })
-  client.abort()
-})
diff --git a/XMLHttpRequest/send-send.worker.js b/XMLHttpRequest/send-send.worker.js
deleted file mode 100644
index 9d34ce6..0000000
--- a/XMLHttpRequest/send-send.worker.js
+++ /dev/null
@@ -1,3 +0,0 @@
-importScripts("/resources/testharness.js");
-importScripts("send-send.js");
-done();
diff --git a/XMLHttpRequest/send-timeout-events.htm b/XMLHttpRequest/send-timeout-events.htm
deleted file mode 100644
index 6aea627..0000000
--- a/XMLHttpRequest/send-timeout-events.htm
+++ /dev/null
@@ -1,76 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <title>XMLHttpRequest: The send() method: timeout is not 0 </title>
-    <link rel="help" href="https://xhr.spec.whatwg.org/#the-timeout-attribute" data-tested-assertations="following::ol[1]/li[2]" />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[5] following::a[contains(@href,'#timeout-error')]/.." />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#timeout-error" data-tested-assertations=".." />
-    <link rel="help" href="https://xhr.spec.whatwg.org/#request-error" data-tested-assertations="following::ol[1]/li[4] following::ol[1]/li[6] following::ol[1]/li[7]/ol/li[3] following::ol[1]/li[7]/ol/li[4] following::ol[1]/li[9] following::ol[1]/li[10]" />
-</head>
-
-<body>
-    <div id="log"></div>
-
-    <script type="text/javascript">
-        var test = async_test();
-
-        test.step(function()
-        {
-            var xhr = new XMLHttpRequest();
-            var expect = [4, "", "upload.timeout", "upload.loadend", "timeout", "loadend"];
-            var actual = [];
-
-            xhr.onreadystatechange = test.step_func(function()
-            {
-                if (xhr.readyState == 4)
-                {
-                    actual.push(xhr.readyState, xhr.response);
-                }
-            });
-
-            xhr.onloadend = test.step_func_done(function(e)
-            {
-                assert_equals(e.loaded, 0);
-                assert_equals(e.total, 0);
-                actual.push(e.type);
-                assert_array_equals(actual, expect);
-            });
-
-            xhr.ontimeout = test.step_func(function(e)
-            {
-                assert_equals(e.loaded, 0);
-                assert_equals(e.total, 0);
-                actual.push(e.type);
-            });
-
-
-            xhr.upload.onloadend = test.step_func(function(e)
-            {
-                assert_equals(e.loaded, 0);
-                assert_equals(e.total, 0);
-                actual.push("upload." + e.type);
-            });
-
-            xhr.upload.ontimeout = test.step_func(function(e)
-            {
-                assert_equals(e.loaded, 0);
-                assert_equals(e.total, 0);
-                actual.push("upload." + e.type);
-            });
-
-
-            var content = "";
-            for (var i = 0; i < 121026; i++)
-            {
-                content += "[" + i + "]";
-            }
-
-            xhr.open("POST", "./resources/trickle.py", true);
-            xhr.timeout = 1;
-            xhr.send(content);
-        });
-    </script>
-</body>
-</html>
diff --git a/XMLHttpRequest/timeout-multiple-fetches.html b/XMLHttpRequest/timeout-multiple-fetches.html
deleted file mode 100644
index d2ab4d2..0000000
--- a/XMLHttpRequest/timeout-multiple-fetches.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!doctype html>
-<title>XMLHttpRequest: timeout, redirects, and CORS preflights</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src=/common/get-host-info.sub.js></script>
-<div id=log></div>
-<script>
-async_test(t => {
-  const client = new XMLHttpRequest
-  client.open("GET", "resources/redirect.py?delay=500&location=delay.py") // 500 + 500 = 1000
-  client.timeout = 1000
-  client.send()
-  client.ontimeout = t.step_func_done(() => {
-    assert_equals(client.readyState, 4)
-  })
-  client.onload = t.unreached_func("load event fired")
-}, "Redirects should not reset the timer")
-
-async_test(t => {
-  const client = new XMLHttpRequest
-  client.open("YO", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/delay.py")
-  client.timeout = 1000
-  client.send()
-  client.ontimeout = t.step_func_done(() => {
-    assert_equals(client.readyState, 4)
-  })
-  client.onload = t.unreached_func("load event fired")
-}, "CORS preflights should not reset the timer")
-</script>
diff --git a/XMLHttpRequest/xmlhttprequest-sync-default-feature-policy.sub.html b/XMLHttpRequest/xmlhttprequest-sync-default-feature-policy.sub.html
deleted file mode 100644
index ade3437..0000000
--- a/XMLHttpRequest/xmlhttprequest-sync-default-feature-policy.sub.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<body>
-  <meta charset="utf-8">
-  <title>Synchronous XMLHttpRequest Feature Policy Test</title>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <script src=/feature-policy/resources/featurepolicy.js></script>
-  <script src=util/utils.js></script>
-  <script>
-  'use strict';
-  run_all_fp_tests_allow_all(
-      'http://{{domains[www]}}:{{ports[http][0]}}',
-      'sync-xhr',
-      'InvalidAccessError: Failed to execute \'open\' on \'XMLHttpRequest\': ' +
-      'Synchronous requests are disabled by Feature Policy.',
-      () => {
-        return new Promise((resolve, reject) => {
-          try {
-            var xhr = new XMLHttpRequest();
-            xhr.open("GET", "data:,", false);
-            xhr.send();
-            resolve();
-          } catch(e) {
-            reject(e);
-          }
-       });
-      });
-  </script>
-</body>
diff --git a/accelerometer/Accelerometer-disabled-by-feature-policy.https.html b/accelerometer/Accelerometer-disabled-by-feature-policy.https.html
new file mode 100644
index 0000000..041ddc4
--- /dev/null
+++ b/accelerometer/Accelerometer-disabled-by-feature-policy.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<title>Accelerometer Feature Policy Test: Disabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_disabled('Accelerometer');
+run_fp_tests_disabled('LinearAccelerationSensor');
+run_fp_tests_disabled('GravitySensor');
+</script>
+</body>
diff --git a/accelerometer/Accelerometer-disabled-by-feature-policy.https.html.headers b/accelerometer/Accelerometer-disabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..beea042
--- /dev/null
+++ b/accelerometer/Accelerometer-disabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer 'none'
diff --git a/accelerometer/Accelerometer-enabled-by-feature-policy-attribute-redirect-on-load.https.html b/accelerometer/Accelerometer-enabled-by-feature-policy-attribute-redirect-on-load.https.html
new file mode 100644
index 0000000..53f6960
--- /dev/null
+++ b/accelerometer/Accelerometer-enabled-by-feature-policy-attribute-redirect-on-load.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<title>Accelerometer Feature Policy Test: Enabled by attribute redirect on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute_redirect_on_load('Accelerometer');
+run_fp_tests_enabled_by_attribute_redirect_on_load('LinearAccelerationSensor');
+run_fp_tests_enabled_by_attribute_redirect_on_load('GravitySensor');
+</script>
+</body>
diff --git a/accelerometer/Accelerometer-enabled-by-feature-policy-attribute.https.html b/accelerometer/Accelerometer-enabled-by-feature-policy-attribute.https.html
new file mode 100644
index 0000000..3dd49d3
--- /dev/null
+++ b/accelerometer/Accelerometer-enabled-by-feature-policy-attribute.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<title>Accelerometer Feature Policy Test: Enabled by attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute('Accelerometer');
+run_fp_tests_enabled_by_attribute('LinearAccelerationSensor');
+run_fp_tests_enabled_by_attribute('GravitySensor');
+</script>
+</body>
diff --git a/accelerometer/Accelerometer-enabled-by-feature-policy.https.html b/accelerometer/Accelerometer-enabled-by-feature-policy.https.html
new file mode 100644
index 0000000..f5d0897
--- /dev/null
+++ b/accelerometer/Accelerometer-enabled-by-feature-policy.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<title>Accelerometer Feature Policy Test: Enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled('Accelerometer');
+run_fp_tests_enabled('LinearAccelerationSensor');
+run_fp_tests_enabled('GravitySensor');
+</script>
+</body>
diff --git a/accelerometer/Accelerometer-enabled-by-feature-policy.https.html.headers b/accelerometer/Accelerometer-enabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..5df2754
--- /dev/null
+++ b/accelerometer/Accelerometer-enabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer *
diff --git a/accelerometer/Accelerometer-enabled-on-self-origin-by-feature-policy.https.html b/accelerometer/Accelerometer-enabled-on-self-origin-by-feature-policy.https.html
new file mode 100644
index 0000000..2074562
--- /dev/null
+++ b/accelerometer/Accelerometer-enabled-on-self-origin-by-feature-policy.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<title>Accelerometer Feature Policy Test: Enabled on self origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_on_self_origin('Accelerometer');
+run_fp_tests_enabled_on_self_origin('LinearAccelerationSensor');
+run_fp_tests_enabled_on_self_origin('GravitySensor');
+</script>
+</body>
diff --git a/accelerometer/Accelerometer-enabled-on-self-origin-by-feature-policy.https.html.headers b/accelerometer/Accelerometer-enabled-on-self-origin-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..cdefed8
--- /dev/null
+++ b/accelerometer/Accelerometer-enabled-on-self-origin-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer 'self'
diff --git a/accelerometer/Accelerometer.https.html b/accelerometer/Accelerometer.https.html
index 5835557..b601c61 100644
--- a/accelerometer/Accelerometer.https.html
+++ b/accelerometer/Accelerometer.https.html
@@ -9,6 +9,8 @@
 <div id="log"></div>
 <script>
 
-runGenericSensorTests(Accelerometer);
+runGenericSensorTests('Accelerometer');
+runGenericSensorTests('GravitySensor');
+runGenericSensorTests('LinearAccelerationSensor');
 
 </script>
diff --git a/accelerometer/Accelerometer_insecure_context.html b/accelerometer/Accelerometer_insecure_context.html
index d55a62f..ff1f083 100644
--- a/accelerometer/Accelerometer_insecure_context.html
+++ b/accelerometer/Accelerometer_insecure_context.html
@@ -7,7 +7,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/generic-sensor/generic-sensor-tests.js"></script>
 <div id="log"></div>
-<h2>Precondition</h2>
+<h2>Note:</h2>
 <ol>
   <li>
     Run test in an insecure context, e.g. http://example.com/.
@@ -16,5 +16,7 @@
 <script>
 
 runGenericSensorInsecureContext("Accelerometer");
+runGenericSensorInsecureContext("GravitySensor");
+runGenericSensorInsecureContext("LinearAccelerationSensor");
 
 </script>
diff --git a/accelerometer/Accelerometer_onerror-manual.https.html b/accelerometer/Accelerometer_onerror-manual.https.html
index a3e8150..dec2188 100644
--- a/accelerometer/Accelerometer_onerror-manual.https.html
+++ b/accelerometer/Accelerometer_onerror-manual.https.html
@@ -15,6 +15,8 @@
 </ol>
 <script>
 
-runGenericSensorOnerror(Accelerometer);
+runGenericSensorOnerror('Accelerometer');
+runGenericSensorOnerror('GravitySensor');
+runGenericSensorOnerror('LinearAccelerationSensor');
 
 </script>
diff --git a/accelerometer/LinearAccelerationSensor-shake-threshold-manual.https.html b/accelerometer/LinearAccelerationSensor-shake-threshold-manual.https.html
new file mode 100644
index 0000000..c0746e5
--- /dev/null
+++ b/accelerometer/LinearAccelerationSensor-shake-threshold-manual.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>LinearAccelerationSensor Shake Threshold Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/accelerometer/#examples">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p class="instructions">
+  Shake gesture along x axis of the device.
+</p>
+<script>
+
+setup({explicit_timeout: true});
+
+async_test(t => {
+  const shakeThreshold = 25;
+  const sensor = new LinearAccelerationSensor({frequency: 60});
+
+  sensor.onreading = t.step_func(() => {
+    if (sensor.x > shakeThreshold) {
+      t.done();
+    }
+  });
+
+  sensor.onerror = t.step_func_done(event => {
+    assert_unreached(event.error.name + ":" + event.error.message);
+  });
+
+  sensor.start();
+}, "Test that when shaking gesture along x axis of the device, the shake threshold can be greater than 25");
+
+</script>
diff --git a/accelerometer/idlharness.https.html b/accelerometer/idlharness.https.html
index 78a9571..1f32be9 100644
--- a/accelerometer/idlharness.https.html
+++ b/accelerometer/idlharness.https.html
@@ -15,7 +15,7 @@
   const idl_array = new IdlArray();
   idl_array.add_untested_idls(dom);
   idl_array.add_untested_idls('interface EventHandler {};');
-  idl_array.add_idls(generic_sensor, { only: ['Sensor'] });
+  idl_array.add_idls(generic_sensor, { only: ['Sensor', 'SensorOptions'] });
   idl_array.add_idls(accelerometer);
   idl_array.add_objects({
     Accelerometer: ['new Accelerometer();'],
@@ -32,7 +32,7 @@
 promise_test(() => {
   return Promise.all([
     "/interfaces/dom.idl",
-    "/interfaces/generic-sensor.idl",
+    "/interfaces/sensors.idl",
     "/interfaces/accelerometer.idl",
   ].map(fetchText)).then(doTest);
 }, "Test IDL implementation of Accelerometer Sensor");
diff --git a/accname/OWNERS b/accname/OWNERS
new file mode 100644
index 0000000..2a4c47e
--- /dev/null
+++ b/accname/OWNERS
@@ -0,0 +1,2 @@
+@halindrome
+@joanmarie
diff --git a/accname/description_1.0_combobox-focusable-manual.html b/accname/description_1.0_combobox-focusable-manual.html
new file mode 100644
index 0000000..c0d54d6
--- /dev/null
+++ b/accname/description_1.0_combobox-focusable-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description 1.0 combobox-focusable</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  ""
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  ""
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  ""
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  ""
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description 1.0 combobox-focusable"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description 1.0 combobox-focusable.</p>
+    <div id="test" role="combobox" tabindex="0" title="Choose your language.">
+    <span> English </span>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_from_content_of_describedby_element-manual.html b/accname/description_from_content_of_describedby_element-manual.html
new file mode 100644
index 0000000..2911d1d
--- /dev/null
+++ b/accname/description_from_content_of_describedby_element-manual.html
@@ -0,0 +1,94 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description from content of describedby element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description from content of describedby element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description from content of describedby element.</p>
+    <style>
+    .hidden { display: none; }
+  </style>
+  <input id="test" type="text" aria-label="Important stuff" aria-describedby="descId" />
+  <div>
+    <div id="descId">
+      <span aria-hidden="true"><i> Hello, </i></span>
+      <span>My</span> name is
+      <div><img src="file.jpg" title="Bryan" alt="" role="presentation" /></div>
+      <span role="presentation" aria-label="Eli">
+        <span aria-label="Garaventa">Zambino</span>
+      </span>
+      <span>the weird.</span>
+      (QED)
+      <span class="hidden"><i><b>and don't you forget it.</b></i></span>
+      <table>
+        <tr>
+          <td>Where</td>
+          <td style="visibility:hidden;"><div>in</div></td>
+          <td><div style="display:none;">the world</div></td>
+          <td>are my marbles?</td>
+        </tr>
+      </table>
+    </div>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_link-with-label-manual.html b/accname/description_link-with-label-manual.html
new file mode 100644
index 0000000..09c32db
--- /dev/null
+++ b/accname/description_link-with-label-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description link-with-label</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "San Francisco"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "San Francisco"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "San Francisco"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "San Francisco"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description link-with-label"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description link-with-label.</p>
+    <a id="test" href="#" aria-label="California" title="San Francisco" >United States</a>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_557-manual.html b/accname/description_test_case_557-manual.html
new file mode 100644
index 0000000..1294baf
--- /dev/null
+++ b/accname/description_test_case_557-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 557</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "t"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "t"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "t"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "t"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 557"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 557.</p>
+    <img id="test" src="foo.jpg" aria-label="1" alt="a" title="t"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_664-manual.html b/accname/description_test_case_664-manual.html
new file mode 100644
index 0000000..f4e8136
--- /dev/null
+++ b/accname/description_test_case_664-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 664</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 664"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 664.</p>
+    <div>
+    <img id="test" aria-describedby="ID1" src="test.png">
+  </div>
+  <div id="ID1">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_665-manual.html b/accname/description_test_case_665-manual.html
new file mode 100644
index 0000000..e97a41d
--- /dev/null
+++ b/accname/description_test_case_665-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 665</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 665"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 665.</p>
+    <div>
+    <img id="test" aria-describedby="ID1" src="test.png">
+  </div>
+  <div id="ID1" style="display:none">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_666-manual.html b/accname/description_test_case_666-manual.html
new file mode 100644
index 0000000..378979c
--- /dev/null
+++ b/accname/description_test_case_666-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 666</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 666"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 666.</p>
+    <div>
+    <img id="test" aria-describedby="ID1" src="test.png">
+  </div>
+  <div id="ID1" role="presentation">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_772-manual.html b/accname/description_test_case_772-manual.html
new file mode 100644
index 0000000..efc5868
--- /dev/null
+++ b/accname/description_test_case_772-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 772</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 772"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 772.</p>
+    <img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
+  <div id="t1">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_773-manual.html b/accname/description_test_case_773-manual.html
new file mode 100644
index 0000000..ea004cf
--- /dev/null
+++ b/accname/description_test_case_773-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 773</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 773"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 773.</p>
+    <img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
+  <div id="t1" style="display:none">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_774-manual.html b/accname/description_test_case_774-manual.html
new file mode 100644
index 0000000..6fafc81
--- /dev/null
+++ b/accname/description_test_case_774-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 774</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 774"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 774.</p>
+    <img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
+  <span id="t1" role="presentation">foo</span>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_838-manual.html b/accname/description_test_case_838-manual.html
new file mode 100644
index 0000000..df4210a
--- /dev/null
+++ b/accname/description_test_case_838-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case 838</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case 838"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case 838.</p>
+    <img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
+  <div id="t1" style="visibility:hidden">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_broken_reference-manual.html b/accname/description_test_case_broken_reference-manual.html
new file mode 100644
index 0000000..7248496
--- /dev/null
+++ b/accname/description_test_case_broken_reference-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case broken reference</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  ""
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  ""
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  ""
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  ""
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case broken reference"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case broken reference.</p>
+    <img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_test_case_one_valid_reference-manual.html b/accname/description_test_case_one_valid_reference-manual.html
new file mode 100644
index 0000000..bce5ce6
--- /dev/null
+++ b/accname/description_test_case_one_valid_reference-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description test case one valid reference</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description test case one valid reference"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description test case one valid reference.</p>
+    <img src="foo.jpg" id="test" alt="test" aria-describedby="t1 t2 t3">
+  <div id="t2">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/description_title-same-element-manual.html b/accname/description_title-same-element-manual.html
new file mode 100644
index 0000000..8c926e3
--- /dev/null
+++ b/accname/description_title-same-element-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Description title-same-element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "description",
+                  "is",
+                  "Description"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXHelp",
+                  "is",
+                  "Description"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accDescription",
+                  "is",
+                  "Description"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Description",
+                  "is",
+                  "Description"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Description title-same-element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Description title-same-element.</p>
+    <div><input aria-label="Name" id="test" title="Title" aria-describedby="ID1" type="text"></div>
+  <div id="ID1">Description</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/foo.jpg b/accname/foo.jpg
new file mode 100644
index 0000000..7d11a4f
--- /dev/null
+++ b/accname/foo.jpg
Binary files differ
diff --git a/accname/name_1.0_combobox-focusable-alternative-manual.html b/accname/name_1.0_combobox-focusable-alternative-manual.html
new file mode 100644
index 0000000..454be9e
--- /dev/null
+++ b/accname/name_1.0_combobox-focusable-alternative-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name 1.0 combobox-focusable-alternative</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Choose your language"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Choose your language"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Choose your language"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Choose your language"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name 1.0 combobox-focusable-alternative"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name 1.0 combobox-focusable-alternative.</p>
+    <input id="test" role="combobox" type="text" title="Choose your language" value="English">
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_1.0_combobox-focusable-manual.html b/accname/name_1.0_combobox-focusable-manual.html
new file mode 100644
index 0000000..bb7a031
--- /dev/null
+++ b/accname/name_1.0_combobox-focusable-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name 1.0 combobox-focusable</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Choose your language."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Choose your language."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Choose your language."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Choose your language."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name 1.0 combobox-focusable"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name 1.0 combobox-focusable.</p>
+    <div id="test" role="combobox" tabindex="0" title="Choose your language.">
+    <span> English </span>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-embedded-combobox-manual.html b/accname/name_checkbox-label-embedded-combobox-manual.html
new file mode 100644
index 0000000..24cbb70
--- /dev/null
+++ b/accname/name_checkbox-label-embedded-combobox-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-embedded-combobox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-embedded-combobox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-embedded-combobox.</p>
+    <input type="checkbox" id="test" />
+  <label for="test">Flash the screen
+    <div role="combobox">
+      <div role="textbox"></div>
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="true">1</li>
+    <li role="option">2</li>
+    <li role="option">3</li>
+      </ul>
+    </div>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-embedded-listbox-manual.html b/accname/name_checkbox-label-embedded-listbox-manual.html
new file mode 100644
index 0000000..a7bbe4c
--- /dev/null
+++ b/accname/name_checkbox-label-embedded-listbox-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-embedded-listbox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-embedded-listbox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-embedded-listbox.</p>
+    <input type="checkbox" id="test" />
+  <label for="test">Flash the screen
+    <ul role="listbox" style="list-style-type: none;">
+      <li role="option" aria-selected="true">1</li>
+      <li role="option">2</li>
+      <li role="option">3</li>
+    </ul>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-embedded-menu-manual.html b/accname/name_checkbox-label-embedded-menu-manual.html
new file mode 100644
index 0000000..1c18b98
--- /dev/null
+++ b/accname/name_checkbox-label-embedded-menu-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-embedded-menu</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-embedded-menu"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-embedded-menu.</p>
+    <input type="checkbox" id="test" />
+  <label for="test">Flash the screen
+    <span role="menu">
+      <span role="menuitem" aria-selected="true">1</span>
+        <span role="menuitem" hidden>2</span>
+    <span role="menuitem" hidden>3</span>
+      </span>
+      times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-embedded-select-manual.html b/accname/name_checkbox-label-embedded-select-manual.html
new file mode 100644
index 0000000..06c41e0
--- /dev/null
+++ b/accname/name_checkbox-label-embedded-select-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-embedded-select</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-embedded-select"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-embedded-select.</p>
+    <input type="checkbox" id="test" />
+  <label for="test">Flash the screen
+    <select size="1">
+      <option selected="selected">1</option>
+      <option>2</option>
+      <option>3</option>
+    </select>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-embedded-slider-manual.html b/accname/name_checkbox-label-embedded-slider-manual.html
new file mode 100644
index 0000000..48741d3
--- /dev/null
+++ b/accname/name_checkbox-label-embedded-slider-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-embedded-slider</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-embedded-slider"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-embedded-slider.</p>
+    <input type="checkbox" id="test" />
+  <label for="test">foo <input role="slider" type="range" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-embedded-spinbutton-manual.html b/accname/name_checkbox-label-embedded-spinbutton-manual.html
new file mode 100644
index 0000000..d5cf7a5
--- /dev/null
+++ b/accname/name_checkbox-label-embedded-spinbutton-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-embedded-spinbutton</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-embedded-spinbutton"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-embedded-spinbutton.</p>
+    <input type="checkbox" id="test" />
+  <label for="test">foo <input role="spinbutton" type="number" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-embedded-textbox-manual.html b/accname/name_checkbox-label-embedded-textbox-manual.html
new file mode 100644
index 0000000..2d8642b
--- /dev/null
+++ b/accname/name_checkbox-label-embedded-textbox-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-embedded-textbox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-embedded-textbox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-embedded-textbox.</p>
+    <input type="checkbox" id="test" />
+  <label for="test">Flash the screen
+    <div role="textbox" contenteditable>1</div>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-multiple-label-alternative-manual.html b/accname/name_checkbox-label-multiple-label-alternative-manual.html
new file mode 100644
index 0000000..9f446aa
--- /dev/null
+++ b/accname/name_checkbox-label-multiple-label-alternative-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-multiple-label-alternative</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "a test This is"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "a test This is"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "a test This is"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "a test This is"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-multiple-label-alternative"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-multiple-label-alternative.</p>
+    <label for="test">a test</label>
+  <label>This <input type="checkbox" id="test" /> is</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-label-multiple-label-manual.html b/accname/name_checkbox-label-multiple-label-manual.html
new file mode 100644
index 0000000..54c218c
--- /dev/null
+++ b/accname/name_checkbox-label-multiple-label-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-label-multiple-label</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "This is a test"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "This is a test"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "This is a test"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "This is a test"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-label-multiple-label"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-label-multiple-label.</p>
+    <label>This <input type="checkbox" id="test" /> is</label>
+  <label for="test">a test</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_checkbox-title-manual.html b/accname/name_checkbox-title-manual.html
new file mode 100644
index 0000000..83b5a8c
--- /dev/null
+++ b/accname/name_checkbox-title-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name checkbox-title</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name checkbox-title"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name checkbox-title.</p>
+    <input type="checkbox" id="test" title="foo" />
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-embedded-combobox-manual.html b/accname/name_file-label-embedded-combobox-manual.html
new file mode 100644
index 0000000..06adca7
--- /dev/null
+++ b/accname/name_file-label-embedded-combobox-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-embedded-combobox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-embedded-combobox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-embedded-combobox.</p>
+    <input type="file" id="test" />
+  <label for="test">Flash the screen
+    <div role="combobox">
+      <div role="textbox"></div>
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="true">1 </li>
+    <li role="option">2 </li>
+    <li role="option">3 </li>
+      </ul>
+    </div>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-embedded-menu-manual.html b/accname/name_file-label-embedded-menu-manual.html
new file mode 100644
index 0000000..de6bcba
--- /dev/null
+++ b/accname/name_file-label-embedded-menu-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-embedded-menu</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-embedded-menu"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-embedded-menu.</p>
+    <input type="file" id="test" />
+  <label for="test">Flash the screen
+    <span role="menu">
+      <span role="menuitem" aria-selected="true">1</span>
+      <span role="menuitem" hidden>2</span>
+      <span role="menuitem" hidden>3</span>
+    </span>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-embedded-select-manual.html b/accname/name_file-label-embedded-select-manual.html
new file mode 100644
index 0000000..117e96c
--- /dev/null
+++ b/accname/name_file-label-embedded-select-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-embedded-select</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-embedded-select"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-embedded-select.</p>
+    <input type="file" id="test" />
+  <label for="test">Flash the screen
+    <select size="1">
+      <option selected="selected">1</option>
+      <option>2</option>
+      <option>3</option>
+    </select>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-embedded-slider-manual.html b/accname/name_file-label-embedded-slider-manual.html
new file mode 100644
index 0000000..22a9057
--- /dev/null
+++ b/accname/name_file-label-embedded-slider-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-embedded-slider</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-embedded-slider"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-embedded-slider.</p>
+    <input type="file" id="test" />
+  <label for="test">foo <input role="slider" type="range" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-embedded-spinbutton-manual.html b/accname/name_file-label-embedded-spinbutton-manual.html
new file mode 100644
index 0000000..1432e2f
--- /dev/null
+++ b/accname/name_file-label-embedded-spinbutton-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-embedded-spinbutton</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-embedded-spinbutton"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-embedded-spinbutton.</p>
+    <input type="file" id="test" />
+  <label for="test">foo <input role="spinbutton" type="number" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-inline-block-elements-manual.html b/accname/name_file-label-inline-block-elements-manual.html
new file mode 100644
index 0000000..c2c1659
--- /dev/null
+++ b/accname/name_file-label-inline-block-elements-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-inline-block-elements</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "What is your name?"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "What is your name?"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "What is your name?"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "What is your name?"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-inline-block-elements"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-inline-block-elements.</p>
+    <input type="file" id="test" />
+  <label for="test">W<i>h<b>a</b></i>t<br>is<div>your<div>name<b>?</b></div></div></label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-inline-block-styles-manual.html b/accname/name_file-label-inline-block-styles-manual.html
new file mode 100644
index 0000000..ecf6ed1
--- /dev/null
+++ b/accname/name_file-label-inline-block-styles-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-inline-block-styles</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "This is a test."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "This is a test."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "This is a test."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "This is a test."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-inline-block-styles"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-inline-block-styles.</p>
+    <style>
+    label:before { content: "This"; display: block; }
+    label:after { content: "."; }
+  </style>
+  <label for="test">is a test</label>
+  <input type="text" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-inline-hidden-elements-manual.html b/accname/name_file-label-inline-hidden-elements-manual.html
new file mode 100644
index 0000000..c982b42
--- /dev/null
+++ b/accname/name_file-label-inline-hidden-elements-manual.html
@@ -0,0 +1,80 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-inline-hidden-elements</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "2 4 6 8 10"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "2 4 6 8 10"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "2 4 6 8 10"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "2 4 6 8 10"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-inline-hidden-elements"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-inline-hidden-elements.</p>
+    <style>
+    .hidden { display: none; }
+  </style>
+  <input type="file" id="test" />
+  <label for="test">
+    <span class="hidden">1</span><span>2</span>
+    <span style="visibility: hidden;">3</span><span>4</span>
+    <span hidden>5</span><span>6</span>
+    <span aria-hidden="true">7</span><span>8</span>
+    <span aria-hidden="false" class="hidden">9</span><span>10</span>
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-owned-combobox-manual.html b/accname/name_file-label-owned-combobox-manual.html
new file mode 100644
index 0000000..79a6917
--- /dev/null
+++ b/accname/name_file-label-owned-combobox-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-owned-combobox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-owned-combobox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-owned-combobox.</p>
+    <input type="file" id="test" />
+  <label for="test">Flash <span aria-owns="id1">the screen</span> times.</label>
+  <div id="id1">
+    <div role="combobox">
+      <div role="textbox"></div>
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="true">1 </li>
+    <li role="option">2 </li>
+    <li role="option">3 </li>
+      </ul>
+    </div>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-label-owned-combobox-owned-listbox-manual.html b/accname/name_file-label-owned-combobox-owned-listbox-manual.html
new file mode 100644
index 0000000..4bbc6f3
--- /dev/null
+++ b/accname/name_file-label-owned-combobox-owned-listbox-manual.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-label-owned-combobox-owned-listbox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 2 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 2 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 2 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 2 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-label-owned-combobox-owned-listbox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-label-owned-combobox-owned-listbox.</p>
+    <input type="file" id="test" />
+  <label for="test">Flash <span aria-owns="id1">the screen</span> times.</label>
+  <div>
+    <div id="id1" role="combobox" aria-owns="id2">
+      <div role="textbox"></div>
+    </div>
+  </div>
+  <div>
+    <ul id="id2" role="listbox" style="list-style-type: none;">
+      <li role="option" >1 </li>
+      <li role="option" aria-selected="true">2 </li>
+      <li role="option">3 </li>
+    </ul>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_file-title-manual.html b/accname/name_file-title-manual.html
new file mode 100644
index 0000000..77b7bf6
--- /dev/null
+++ b/accname/name_file-title-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name file-title</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name file-title"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name file-title.</p>
+    <input type="file" id="test" title="foo" />
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_from_content-manual.html b/accname/name_from_content-manual.html
new file mode 100644
index 0000000..f7e2491
--- /dev/null
+++ b/accname/name_from_content-manual.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name from content</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name from content"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name from content.</p>
+    <style>
+    .hidden { display: none; }
+  </style>
+  <div id="test" role="link" tabindex="0">
+    <span aria-hidden="true"><i> Hello, </i></span>
+    <span>My</span> name is
+    <div><img src="file.jpg" title="Bryan" alt="" role="presentation" /></div>
+    <span role="presentation" aria-label="Eli">
+      <span aria-label="Garaventa">Zambino</span>
+    </span>
+    <span>the weird.</span>
+    (QED)
+    <span class="hidden"><i><b>and don't you forget it.</b></i></span>
+    <table>
+      <tr>
+        <td>Where</td>
+        <td style="visibility:hidden;"><div>in</div></td>
+        <td><div style="display:none;">the world</div></td>
+        <td>are my marbles?</td>
+      </tr>
+    </table>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_from_content_of_label-manual.html b/accname/name_from_content_of_label-manual.html
new file mode 100644
index 0000000..3eb5b77
--- /dev/null
+++ b/accname/name_from_content_of_label-manual.html
@@ -0,0 +1,92 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name from content of label</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name from content of label"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name from content of label.</p>
+    <style>
+    .hidden { display: none; }
+  </style>
+  <input type="text" id="test" />
+  <label for="test" id="label">
+    <span aria-hidden="true"><i> Hello, </i></span>
+    <span>My</span> name is
+    <div><img src="file.jpg" title="Bryan" alt="" role="presentation" /></div>
+    <span role="presentation" aria-label="Eli">
+      <span aria-label="Garaventa">Zambino</span>
+   </span>
+   <span>the weird.</span>
+   (QED)
+   <span class="hidden"><i><b>and don't you forget it.</b></i></span>
+   <table>
+     <tr>
+       <td>Where</td>
+       <td style="visibility:hidden;"><div>in</div></td>
+       <td><div style="display:none;">the world</div></td>
+       <td>are my marbles?</td>
+    </tr>
+   </table>
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_from_content_of_labelledby_element-manual.html b/accname/name_from_content_of_labelledby_element-manual.html
new file mode 100644
index 0000000..a60a8c0
--- /dev/null
+++ b/accname/name_from_content_of_labelledby_element-manual.html
@@ -0,0 +1,92 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name from content of labelledby element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "My name is Eli the weird. (QED) Where are my marbles?"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name from content of labelledby element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name from content of labelledby element.</p>
+    <style>
+    .hidden { display: none; }
+  </style>
+  <input id="test" type="text" aria-labelledby="lblId" />
+  <div id="lblId" >
+    <span aria-hidden="true"><i> Hello, </i></span>
+    <span>My</span> name is
+    <div><img src="file.jpg" title="Bryan" alt="" role="presentation" /></div>
+    <span role="presentation" aria-label="Eli">
+      <span aria-label="Garaventa">Zambino</span>
+    </span>
+    <span>the weird.</span>
+    (QED)
+    <span class="hidden"><i><b>and don't you forget it.</b></i></span>
+    <table>
+      <tr>
+        <td>Where</td>
+        <td style="visibility:hidden;"><div>in</div></td>
+        <td><div style="display:none;">the world</div></td>
+        <td>are my marbles?</td>
+      </tr>
+    </table>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_from_content_of_labelledby_elements_one_of_which_is_hidden-manual.html b/accname/name_from_content_of_labelledby_elements_one_of_which_is_hidden-manual.html
new file mode 100644
index 0000000..8793930
--- /dev/null
+++ b/accname/name_from_content_of_labelledby_elements_one_of_which_is_hidden-manual.html
@@ -0,0 +1,102 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name from content of labelledby elements one of which is hidden</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Important stuff"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Important stuff"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Important stuff"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Important stuff"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name from content of labelledby elements one of which is hidden"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name from content of labelledby elements one of which is hidden.</p>
+    <style>
+    .hidden { display: none; }
+  </style>
+  <div>
+    <input id="test" type="text" aria-labelledby="lbl1 lbl2" aria-describedby="descId" />
+    <span>
+      <span aria-hidden="true" id="lbl1">Important</span>
+      <span class="hidden">
+        <span aria-hidden="true" id="lbl2">stuff</span>
+      </span>
+    </span>
+  </div>
+  <div class="hidden">
+    <div id="descId">
+      <span aria-hidden="true"><i> Hello, </i></span>
+      <span>My</span> name is
+      <div><img src="file.jpg" title="Bryan" alt="" role="presentation" /></div>
+      <span role="presentation" aria-label="Eli">
+        <span aria-label="Garaventa">Zambino</span>
+      </span>
+      <span>the weird.</span>
+      (QED)
+      <span class="hidden"><i><b>and don't you forget it.</b></i></span>
+      <table>
+        <tr>
+          <td>Where</td>
+          <td style="visibility:hidden;"><div>in</div></td>
+          <td><div style="display:none;">the world</div></td>
+          <td>are my marbles?</td>
+        </tr>
+      </table>
+    </div>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_heading-combobox-focusable-alternative-manual.html b/accname/name_heading-combobox-focusable-alternative-manual.html
new file mode 100644
index 0000000..c1540b1
--- /dev/null
+++ b/accname/name_heading-combobox-focusable-alternative-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name  heading-combobox-focusable-alternative</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Country of origin: United States"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Country of origin: United States"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Country of origin: United States"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Country of origin: United States"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name  heading-combobox-focusable-alternative"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name  heading-combobox-focusable-alternative.</p>
+    <h2 id="test">
+  Country of origin:
+  <input role="combobox" type="text" title="Choose your country." value="United States">
+  </h2>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_image-title-manual.html b/accname/name_image-title-manual.html
new file mode 100644
index 0000000..43ce9be
--- /dev/null
+++ b/accname/name_image-title-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name image-title</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name image-title"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name image-title.</p>
+    <input type="image" src="test.png" id="test" title="foo" />
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_link-mixed-content-manual.html b/accname/name_link-mixed-content-manual.html
new file mode 100644
index 0000000..f089ebd
--- /dev/null
+++ b/accname/name_link-mixed-content-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name link-mixed-content</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "My name is Eli the weird. (QED)"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "My name is Eli the weird. (QED)"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "My name is Eli the weird. (QED)"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "My name is Eli the weird. (QED)"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name link-mixed-content"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name link-mixed-content.</p>
+    <style>
+    .hidden { display: none; }
+  </style>
+  <div id="test" role="link" tabindex="0">
+    <span aria-hidden="true"><i> Hello, </i></span>
+    <span>My</span> name is
+    <div><img src="file.jpg" title="Bryan" alt="" role="presentation" /></div>
+    <span role="presentation" aria-label="Eli"><span aria-label="Garaventa">Zambino</span></span>
+    <span>the weird.</span>
+    (QED)
+    <span class="hidden"><i><b>and don't you forget it.</b></i></span>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_link-with-label-manual.html b/accname/name_link-with-label-manual.html
new file mode 100644
index 0000000..e8f1e3f
--- /dev/null
+++ b/accname/name_link-with-label-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name link-with-label</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "California"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "California"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "California"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "California"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name link-with-label"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name link-with-label.</p>
+    <a id="test" href="#" aria-label="California" title="San Francisco" >United States</a>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_password-label-embedded-combobox-manual.html b/accname/name_password-label-embedded-combobox-manual.html
new file mode 100644
index 0000000..39c8005
--- /dev/null
+++ b/accname/name_password-label-embedded-combobox-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name password-label-embedded-combobox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name password-label-embedded-combobox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name password-label-embedded-combobox.</p>
+    <input type="password" id="test" />
+  <label for="test">Flash the screen
+    <div role="combobox">
+      <div role="textbox"></div>
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="true">1</li>
+    <li role="option">2</li>
+    <li role="option">3</li>
+      </ul>
+    </div>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_password-label-embedded-menu-manual.html b/accname/name_password-label-embedded-menu-manual.html
new file mode 100644
index 0000000..d6ad649
--- /dev/null
+++ b/accname/name_password-label-embedded-menu-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name password-label-embedded-menu</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name password-label-embedded-menu"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name password-label-embedded-menu.</p>
+    <input type="password" id="test" />
+  <label for="test">Flash the screen
+    <span role="menu">
+      <span role="menuitem" aria-selected="true">1</span>
+      <span role="menuitem" hidden>2</span>
+      <span role="menuitem" hidden>3</span>
+    </span>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_password-label-embedded-select-manual.html b/accname/name_password-label-embedded-select-manual.html
new file mode 100644
index 0000000..f4b96e2
--- /dev/null
+++ b/accname/name_password-label-embedded-select-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name password-label-embedded-select</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name password-label-embedded-select"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name password-label-embedded-select.</p>
+    <input type="password" id="test" />
+  <label for="test">Flash the screen
+    <select size="1">
+      <option selected="selected">1</option>
+      <option>2</option>
+      <option>3</option>
+    </select>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_password-label-embedded-slider-manual.html b/accname/name_password-label-embedded-slider-manual.html
new file mode 100644
index 0000000..7701b8b
--- /dev/null
+++ b/accname/name_password-label-embedded-slider-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name password-label-embedded-slider</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name password-label-embedded-slider"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name password-label-embedded-slider.</p>
+    <input type="password" id="test" />
+  <label for="test">foo <input role="slider" type="range" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_password-label-embedded-spinbutton-manual.html b/accname/name_password-label-embedded-spinbutton-manual.html
new file mode 100644
index 0000000..67cd55d
--- /dev/null
+++ b/accname/name_password-label-embedded-spinbutton-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name password-label-embedded-spinbutton</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name password-label-embedded-spinbutton"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name password-label-embedded-spinbutton.</p>
+    <input type="password" id="test" />
+  <label for="test">foo <input role="spinbutton" type="number" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_password-title-manual.html b/accname/name_password-title-manual.html
new file mode 100644
index 0000000..3f3eb41
--- /dev/null
+++ b/accname/name_password-title-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name password-title</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name password-title"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name password-title.</p>
+    <input type="password" id="test" title="foo" />
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_radio-label-embedded-combobox-manual.html b/accname/name_radio-label-embedded-combobox-manual.html
new file mode 100644
index 0000000..3facb5e
--- /dev/null
+++ b/accname/name_radio-label-embedded-combobox-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name radio-label-embedded-combobox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name radio-label-embedded-combobox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name radio-label-embedded-combobox.</p>
+    <input type="radio" id="test" />
+  <label for="test">Flash the screen
+    <div role="combobox">
+      <div role="textbox"></div>
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="true">1</li>
+    <li role="option">2</li>
+    <li role="option">3</li>
+      </ul>
+    </div>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_radio-label-embedded-menu-manual.html b/accname/name_radio-label-embedded-menu-manual.html
new file mode 100644
index 0000000..3752b1f
--- /dev/null
+++ b/accname/name_radio-label-embedded-menu-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name radio-label-embedded-menu</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name radio-label-embedded-menu"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name radio-label-embedded-menu.</p>
+    <input type="radio" id="test" />
+  <label for="test">Flash the screen
+    <span role="menu">
+      <span role="menuitem" aria-selected="true">1</span>
+      <span role="menuitem" hidden>2</span>
+      <span role="menuitem" hidden>3</span>
+    </span>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_radio-label-embedded-select-manual.html b/accname/name_radio-label-embedded-select-manual.html
new file mode 100644
index 0000000..6f632f9
--- /dev/null
+++ b/accname/name_radio-label-embedded-select-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name radio-label-embedded-select</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name radio-label-embedded-select"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name radio-label-embedded-select.</p>
+    <input type="radio" id="test" />
+  <label for="test">Flash the screen
+    <select size="1">
+      <option selected="selected">1</option>
+      <option>2</option>
+      <option>3</option>
+    </select>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_radio-label-embedded-slider-manual.html b/accname/name_radio-label-embedded-slider-manual.html
new file mode 100644
index 0000000..5c82a06
--- /dev/null
+++ b/accname/name_radio-label-embedded-slider-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name radio-label-embedded-slider</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name radio-label-embedded-slider"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name radio-label-embedded-slider.</p>
+    <input type="radio" id="test" />
+  <label for="test">foo <input role="slider" type="range" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_radio-label-embedded-spinbutton-manual.html b/accname/name_radio-label-embedded-spinbutton-manual.html
new file mode 100644
index 0000000..ab4fdcc
--- /dev/null
+++ b/accname/name_radio-label-embedded-spinbutton-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name radio-label-embedded-spinbutton</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name radio-label-embedded-spinbutton"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name radio-label-embedded-spinbutton.</p>
+    <input type="radio" id="test" />
+  <label for="test">foo <input role="spinbutton"  type="number" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_radio-title-manual.html b/accname/name_radio-title-manual.html
new file mode 100644
index 0000000..cb6f065
--- /dev/null
+++ b/accname/name_radio-title-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name radio-title</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name radio-title"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name radio-title.</p>
+    <input type="radio" id="test" title="foo" />
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_539-manual.html b/accname/name_test_case_539-manual.html
new file mode 100644
index 0000000..659740b
--- /dev/null
+++ b/accname/name_test_case_539-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 539</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Rich"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Rich"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Rich"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Rich"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 539"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 539.</p>
+    <input type="button" aria-label="Rich" id="test">
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_540-manual.html b/accname/name_test_case_540-manual.html
new file mode 100644
index 0000000..c187c37
--- /dev/null
+++ b/accname/name_test_case_540-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 540</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Rich's button"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Rich's button"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Rich's button"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Rich's button"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 540"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 540.</p>
+    <div id="ID1">Rich's button</div>
+  <input type="button" aria-labelledby="ID1" id="test">
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_541-manual.html b/accname/name_test_case_541-manual.html
new file mode 100644
index 0000000..1940544
--- /dev/null
+++ b/accname/name_test_case_541-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 541</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Rich's button"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Rich's button"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Rich's button"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Rich's button"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 541"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 541.</p>
+    <div id="ID1">Rich's button</div>
+  <input type="button" aria-label="bar" aria-labelledby="ID1" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_543-manual.html b/accname/name_test_case_543-manual.html
new file mode 100644
index 0000000..7908bf6
--- /dev/null
+++ b/accname/name_test_case_543-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 543</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Reset"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Reset"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Reset"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Reset"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 543"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 543.</p>
+    <input type="reset" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_544-manual.html b/accname/name_test_case_544-manual.html
new file mode 100644
index 0000000..1b7ba37
--- /dev/null
+++ b/accname/name_test_case_544-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 544</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 544"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 544.</p>
+    <input type="button" id="test" value="foo"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_545-manual.html b/accname/name_test_case_545-manual.html
new file mode 100644
index 0000000..1909804
--- /dev/null
+++ b/accname/name_test_case_545-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 545</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 545"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 545.</p>
+    <input src="baz.html" type="image" id="test" alt="foo"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_546-manual.html b/accname/name_test_case_546-manual.html
new file mode 100644
index 0000000..f40aa2f
--- /dev/null
+++ b/accname/name_test_case_546-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 546</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "States:"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 546"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 546.</p>
+    <label for="test">States:</label>
+  <input type="text" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_547-manual.html b/accname/name_test_case_547-manual.html
new file mode 100644
index 0000000..b7a977b
--- /dev/null
+++ b/accname/name_test_case_547-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 547</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo David"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 547"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 547.</p>
+    <label for="test">
+  foo
+  <input type="text" value="David"/>
+  </label>
+  <input type="text" id="test" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_548-manual.html b/accname/name_test_case_548-manual.html
new file mode 100644
index 0000000..d63ff4a
--- /dev/null
+++ b/accname/name_test_case_548-manual.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 548</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 548"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 548.</p>
+    <label for="test">
+  crazy
+    <select name="member" size="1" role="menu" tabindex="0">
+      <option role="menuitem" value="beard" selected="true">clown</option>
+      <option role="menuitem" value="scuba">rich</option>
+    </select>
+  </label>
+  <input type="text" id="test" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_549-manual.html b/accname/name_test_case_549-manual.html
new file mode 100644
index 0000000..c729c1f
--- /dev/null
+++ b/accname/name_test_case_549-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 549</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 549"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 549.</p>
+    <label for="test">
+    crazy
+     <div role="spinbutton" aria-valuetext="Monday" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+     </div>
+  </label>
+  <input type="text" id="test" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_550-manual.html b/accname/name_test_case_550-manual.html
new file mode 100644
index 0000000..de8a635
--- /dev/null
+++ b/accname/name_test_case_550-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 550</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy 4"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 550"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 550.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="text" id="test" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_551-manual.html b/accname/name_test_case_551-manual.html
new file mode 100644
index 0000000..65e284f
--- /dev/null
+++ b/accname/name_test_case_551-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 551</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 551"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 551.</p>
+    <input type="text" id="test" title="crazy" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_552-manual.html b/accname/name_test_case_552-manual.html
new file mode 100644
index 0000000..a48aaa3
--- /dev/null
+++ b/accname/name_test_case_552-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 552</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 552"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 552.</p>
+    <style>
+    label:before { content:"fancy "; }
+  </style>
+  <label for="test">fruit</label>
+  <input type="text" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_553-manual.html b/accname/name_test_case_553-manual.html
new file mode 100644
index 0000000..def83ed
--- /dev/null
+++ b/accname/name_test_case_553-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 553</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "test content"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "test content"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "test content"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "test content"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 553"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 553.</p>
+    <style type="text/css">
+    [data-after]:after { content: attr(data-after); }
+  </style>
+  <label for="test" data-after="test content"></label>
+  <input type="text" id="test">
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_556-manual.html b/accname/name_test_case_556-manual.html
new file mode 100644
index 0000000..5923db2
--- /dev/null
+++ b/accname/name_test_case_556-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 556</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "1"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "1"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "1"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "1"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 556"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 556.</p>
+    <img id="test" src="foo.jpg" aria-label="1"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_557-manual.html b/accname/name_test_case_557-manual.html
new file mode 100644
index 0000000..1a81ffc
--- /dev/null
+++ b/accname/name_test_case_557-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 557</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "1"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "1"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "1"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "1"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 557"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 557.</p>
+    <img id="test" src="foo.jpg" aria-label="1" alt="a" title="t"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_558-manual.html b/accname/name_test_case_558-manual.html
new file mode 100644
index 0000000..3e5b448
--- /dev/null
+++ b/accname/name_test_case_558-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 558</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  ""
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  ""
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  ""
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  ""
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 558"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 558.</p>
+    <input type="text" value="peanuts" id="test">
+  <img aria-labelledby="test" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_559-manual.html b/accname/name_test_case_559-manual.html
new file mode 100644
index 0000000..00cc7d1
--- /dev/null
+++ b/accname/name_test_case_559-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 559</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  ""
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  ""
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  ""
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  ""
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 559"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 559.</p>
+    <img id="test" aria-labelledby="test" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_560-manual.html b/accname/name_test_case_560-manual.html
new file mode 100644
index 0000000..05bf8d4
--- /dev/null
+++ b/accname/name_test_case_560-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 560</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  ""
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  ""
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  ""
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  ""
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 560"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 560.</p>
+    <input type="text" value="peanuts" id="test">
+  <img aria-labelledby="test" aria-label="1" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_561-manual.html b/accname/name_test_case_561-manual.html
new file mode 100644
index 0000000..5131744
--- /dev/null
+++ b/accname/name_test_case_561-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 561</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "1"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "1"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "1"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "1"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 561"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 561.</p>
+    <img id="test" aria-labelledby="test" aria-label="1" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_562-manual.html b/accname/name_test_case_562-manual.html
new file mode 100644
index 0000000..f718b35
--- /dev/null
+++ b/accname/name_test_case_562-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 562</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "peanuts popcorn apple jacks"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "peanuts popcorn apple jacks"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "peanuts popcorn apple jacks"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "peanuts popcorn apple jacks"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 562"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 562.</p>
+    <input type="text" value="peanuts" id="ID1">
+  <input type="text" value="popcorn" id="ID2">
+  <input type="text" value="apple jacks" id="ID3">
+  <img aria-labelledby="ID1 ID2 ID3" id="test" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_563-manual.html b/accname/name_test_case_563-manual.html
new file mode 100644
index 0000000..5214fb9
--- /dev/null
+++ b/accname/name_test_case_563-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 563</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "l peanuts"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "l peanuts"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "l peanuts"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "l peanuts"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 563"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 563.</p>
+    <input type="text" value="peanuts" id="ID1">
+  <img id="test" aria-label="l" aria-labelledby="test ID1" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_564-manual.html b/accname/name_test_case_564-manual.html
new file mode 100644
index 0000000..bac49ad
--- /dev/null
+++ b/accname/name_test_case_564-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 564</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "l peanuts popcorn"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "l peanuts popcorn"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "l peanuts popcorn"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "l peanuts popcorn"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 564"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 564.</p>
+    <input type="text" value="peanuts" id="ID1">
+  <input type="text" value="popcorn" id="ID2">
+  <img id="test" aria-label="l" aria-labelledby="test ID1 ID2" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_565-manual.html b/accname/name_test_case_565-manual.html
new file mode 100644
index 0000000..749d24d
--- /dev/null
+++ b/accname/name_test_case_565-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 565</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "l peanuts popcorn apple jacks"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "l peanuts popcorn apple jacks"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "l peanuts popcorn apple jacks"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "l peanuts popcorn apple jacks"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 565"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 565.</p>
+    <input type="text" value="peanuts" id="ID1">
+  <input type="text" value="popcorn" id="ID2">
+  <input type="text" value="apple jacks" id="ID3">
+  <img id="test" aria-label="l" aria-labelledby="test ID1 ID2 ID3" alt= "a" title="t" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_566-manual.html b/accname/name_test_case_566-manual.html
new file mode 100644
index 0000000..5ffef66
--- /dev/null
+++ b/accname/name_test_case_566-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 566</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "t peanuts popcorn apple jacks"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "t peanuts popcorn apple jacks"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "t peanuts popcorn apple jacks"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "t peanuts popcorn apple jacks"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 566"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 566.</p>
+    <input type="text" value="peanuts" id="ID1">
+  <input type="text" value="popcorn" id="ID2">
+  <input type="text" value="apple jacks" id="ID3">
+  <img id="test" aria-label="" aria-labelledby="test ID1 ID2 ID3" alt="" title="t" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_596-manual.html b/accname/name_test_case_596-manual.html
new file mode 100644
index 0000000..821b96b
--- /dev/null
+++ b/accname/name_test_case_596-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 596</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "bar"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 596"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 596.</p>
+    <div id="test" aria-labelledby="ID1">foo</div>
+  <span id="ID1">bar</span>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_597-manual.html b/accname/name_test_case_597-manual.html
new file mode 100644
index 0000000..ed3e90f
--- /dev/null
+++ b/accname/name_test_case_597-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 597</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Tag"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 597"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 597.</p>
+    <div id="test" aria-label="Tag">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_598-manual.html b/accname/name_test_case_598-manual.html
new file mode 100644
index 0000000..89a0f89
--- /dev/null
+++ b/accname/name_test_case_598-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 598</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "bar"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 598"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 598.</p>
+    <div id="test" aria-labelledby="ID1" aria-label="Tag">foo</div>
+  <span id="ID1">bar</span>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_599-manual.html b/accname/name_test_case_599-manual.html
new file mode 100644
index 0000000..9f186a6
--- /dev/null
+++ b/accname/name_test_case_599-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 599</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 599"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 599.</p>
+    <div id="test" aria-labelledby="ID0 ID1" aria-label="Tag">foo</div>
+  <span id="ID0">bar</span>
+  <span id="ID1">baz</span>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_600-manual.html b/accname/name_test_case_600-manual.html
new file mode 100644
index 0000000..091c8cb
--- /dev/null
+++ b/accname/name_test_case_600-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 600</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  ""
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  ""
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  ""
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  ""
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 600"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 600.</p>
+    <div id="test">Div with text</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_601-manual.html b/accname/name_test_case_601-manual.html
new file mode 100644
index 0000000..8a87591
--- /dev/null
+++ b/accname/name_test_case_601-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 601</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 601"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 601.</p>
+    <div id="test" role="button">foo</div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_602-manual.html b/accname/name_test_case_602-manual.html
new file mode 100644
index 0000000..71da6f7
--- /dev/null
+++ b/accname/name_test_case_602-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 602</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Tag"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 602"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 602.</p>
+    <div id="test" role="button" title="Tag" style="outline:medium solid black; width:2em; height:1em;">
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_603-manual.html b/accname/name_test_case_603-manual.html
new file mode 100644
index 0000000..5496f50
--- /dev/null
+++ b/accname/name_test_case_603-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 603</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 603"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 603.</p>
+    <div id="ID1">foo</div>
+  <a id="test" href="test.html" aria-labelledby="ID1">bar</a>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_604-manual.html b/accname/name_test_case_604-manual.html
new file mode 100644
index 0000000..db0831c
--- /dev/null
+++ b/accname/name_test_case_604-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 604</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Tag"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 604"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 604.</p>
+    <a id="test" href="test.html" aria-label="Tag">ABC</a>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_605-manual.html b/accname/name_test_case_605-manual.html
new file mode 100644
index 0000000..ec6c5a7
--- /dev/null
+++ b/accname/name_test_case_605-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 605</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "bar"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "bar"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 605"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 605.</p>
+    <a href="test.html" id="test" aria-labelledby="ID1" aria-label="Tag">foo</a>
+  <p id="ID1">bar</p>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_606-manual.html b/accname/name_test_case_606-manual.html
new file mode 100644
index 0000000..8bcdefa
--- /dev/null
+++ b/accname/name_test_case_606-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 606</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Tag foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Tag foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Tag foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Tag foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 606"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 606.</p>
+    <a href="test.html" id="test" aria-labelledby="test ID1" aria-label="Tag"></a>
+  <p id="ID1">foo</p>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_607-manual.html b/accname/name_test_case_607-manual.html
new file mode 100644
index 0000000..8737c86
--- /dev/null
+++ b/accname/name_test_case_607-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 607</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "ABC"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "ABC"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "ABC"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "ABC"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 607"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 607.</p>
+    <a href="test.html" id="test">ABC</a>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_608-manual.html b/accname/name_test_case_608-manual.html
new file mode 100644
index 0000000..4380f40
--- /dev/null
+++ b/accname/name_test_case_608-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 608</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Tag"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Tag"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 608"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 608.</p>
+    <a href="test.html" id="test" title="Tag"></a>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_609-manual.html b/accname/name_test_case_609-manual.html
new file mode 100644
index 0000000..d408962
--- /dev/null
+++ b/accname/name_test_case_609-manual.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 609</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 609"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 609.</p>
+    <input id="test" type="text" aria-labelledby="ID1 ID2 ID3">
+  <p id="ID1">foo</p>
+  <p id="ID2">bar</p>
+  <p id="ID3">baz</p>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_610-manual.html b/accname/name_test_case_610-manual.html
new file mode 100644
index 0000000..fc8bcf2
--- /dev/null
+++ b/accname/name_test_case_610-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 610</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 610"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 610.</p>
+    <input id="test" type="text" aria-label="bar" aria-labelledby="ID1 test">
+  <div id="ID1">foo</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_611-manual.html b/accname/name_test_case_611-manual.html
new file mode 100644
index 0000000..2ea0860
--- /dev/null
+++ b/accname/name_test_case_611-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 611</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 611"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 611.</p>
+    <input id="test" type="text"/>
+  <label for="test">foo</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_612-manual.html b/accname/name_test_case_612-manual.html
new file mode 100644
index 0000000..9faa922
--- /dev/null
+++ b/accname/name_test_case_612-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 612</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 612"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 612.</p>
+    <input type="password" id="test">
+  <label for="test">foo</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_613-manual.html b/accname/name_test_case_613-manual.html
new file mode 100644
index 0000000..d8a27c3
--- /dev/null
+++ b/accname/name_test_case_613-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 613</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 613"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 613.</p>
+    <input type="checkbox" id="test">
+  <label for="test">foo</label></body>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_614-manual.html b/accname/name_test_case_614-manual.html
new file mode 100644
index 0000000..b2323aa
--- /dev/null
+++ b/accname/name_test_case_614-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 614</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 614"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 614.</p>
+    <input type="radio" id="test">
+  <label for="test">foo</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_615-manual.html b/accname/name_test_case_615-manual.html
new file mode 100644
index 0000000..e1d53f4
--- /dev/null
+++ b/accname/name_test_case_615-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 615</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 615"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 615.</p>
+    <input type="file" id="test">
+  <label for="test">foo</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_616-manual.html b/accname/name_test_case_616-manual.html
new file mode 100644
index 0000000..310bfca
--- /dev/null
+++ b/accname/name_test_case_616-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 616</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 616"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 616.</p>
+    <input type="image" id="test">
+  <label for="test">foo</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_617-manual.html b/accname/name_test_case_617-manual.html
new file mode 100644
index 0000000..2927063
--- /dev/null
+++ b/accname/name_test_case_617-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 617</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 617"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 617.</p>
+    <input type="checkbox" id="test">
+  <label for="test">foo<input type="text" value="bar">baz</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_618-manual.html b/accname/name_test_case_618-manual.html
new file mode 100644
index 0000000..be74e08
--- /dev/null
+++ b/accname/name_test_case_618-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 618</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 618"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 618.</p>
+    <input type="text" id="test">
+  <label for="test">foo<input type="text" value="bar">baz</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_619-manual.html b/accname/name_test_case_619-manual.html
new file mode 100644
index 0000000..8bd0826
--- /dev/null
+++ b/accname/name_test_case_619-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 619</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 619"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 619.</p>
+    <input type="password" id="test">
+  <label for="test">foo<input type="text" value="bar">baz</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_620-manual.html b/accname/name_test_case_620-manual.html
new file mode 100644
index 0000000..855d0cc
--- /dev/null
+++ b/accname/name_test_case_620-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 620</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 620"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 620.</p>
+    <input type="radio" id="test">
+  <label for="test">foo<input type="text" value="bar">baz</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_621-manual.html b/accname/name_test_case_621-manual.html
new file mode 100644
index 0000000..bb5780a
--- /dev/null
+++ b/accname/name_test_case_621-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 621</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 621"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 621.</p>
+    <input type="file" id="test">
+  <label for="test">foo <input type="text" value="bar"> baz</label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_659-manual.html b/accname/name_test_case_659-manual.html
new file mode 100644
index 0000000..1332c30
--- /dev/null
+++ b/accname/name_test_case_659-manual.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 659</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 659"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 659.</p>
+    <style type="text/css">
+    label:before { content: "foo"; }
+    label:after { content: "baz"; }
+  </style>
+  <form>
+    <label for="test" title="bar"><input id="test" type="text" name="test" title="bar"></label>
+  </form>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_660-manual.html b/accname/name_test_case_660-manual.html
new file mode 100644
index 0000000..66b3663
--- /dev/null
+++ b/accname/name_test_case_660-manual.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 660</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 660"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 660.</p>
+    <style type="text/css">
+    label:before { content: "foo"; }
+    label:after { content: "baz"; }
+  </style>
+  <form>
+    <label for="test" title="bar"><input id="test" type="password" name="test" title="bar"></label>
+  </form>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_661-manual.html b/accname/name_test_case_661-manual.html
new file mode 100644
index 0000000..e1f25ba
--- /dev/null
+++ b/accname/name_test_case_661-manual.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 661</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 661"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 661.</p>
+    <style type="text/css">
+    label:before { content: "foo"; }
+    label:after { content: "baz"; }
+  </style>
+  <form>
+    <label for="test"><input id="test" type="checkbox" name="test" title=" bar "></label>
+  </form>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_662-manual.html b/accname/name_test_case_662-manual.html
new file mode 100644
index 0000000..4d69b0f
--- /dev/null
+++ b/accname/name_test_case_662-manual.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 662</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 662"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 662.</p>
+    <style type="text/css">
+    label:before { content: "foo"; }
+    label:after { content: "baz"; }
+  </style>
+  <form>
+    <label for="test"><input id="test" type="radio" name="test" title=" bar "></label>
+  </form>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_663a-manual.html b/accname/name_test_case_663a-manual.html
new file mode 100644
index 0000000..95ec2f8
--- /dev/null
+++ b/accname/name_test_case_663a-manual.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 663a</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo bar baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo bar baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 663a"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 663a.</p>
+    <style type="text/css">
+    label:before { content: "foo"; }
+    label:after { content: "baz"; }
+  </style>
+  <form>
+    <label for="test"><input id="test" type="image" src="foo.jpg" name="test" title="bar"></label>
+  </form>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_721-manual.html b/accname/name_test_case_721-manual.html
new file mode 100644
index 0000000..90e1911
--- /dev/null
+++ b/accname/name_test_case_721-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 721</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "States:"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 721"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 721.</p>
+    <label for="test">States:</label>
+  <input type="password" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_723-manual.html b/accname/name_test_case_723-manual.html
new file mode 100644
index 0000000..84cd655
--- /dev/null
+++ b/accname/name_test_case_723-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 723</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "States:"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 723"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 723.</p>
+    <label for="test">States:</label>
+  <input type="checkbox" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_724-manual.html b/accname/name_test_case_724-manual.html
new file mode 100644
index 0000000..69d8d70
--- /dev/null
+++ b/accname/name_test_case_724-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 724</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "States:"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 724"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 724.</p>
+    <label for="test">States:</label>
+  <input type="radio" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_725-manual.html b/accname/name_test_case_725-manual.html
new file mode 100644
index 0000000..9d1b867
--- /dev/null
+++ b/accname/name_test_case_725-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 725</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "File:"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "File:"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "File:"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "File:"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 725"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 725.</p>
+    <label for="test">File:</label>
+  <input type="file" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_726-manual.html b/accname/name_test_case_726-manual.html
new file mode 100644
index 0000000..0faf0f2
--- /dev/null
+++ b/accname/name_test_case_726-manual.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 726</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "States:"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "States:"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 726"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 726.</p>
+    <label for="test">States:</label>
+  <input type="image" id="test" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_727-manual.html b/accname/name_test_case_727-manual.html
new file mode 100644
index 0000000..d009ae7
--- /dev/null
+++ b/accname/name_test_case_727-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 727</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo David"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 727"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 727.</p>
+    <label for="test">
+    foo
+    <input type="text" value="David"/>
+  </label>
+  <input type="password" id="test" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_728-manual.html b/accname/name_test_case_728-manual.html
new file mode 100644
index 0000000..f6a14e7
--- /dev/null
+++ b/accname/name_test_case_728-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 728</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo David"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 728"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 728.</p>
+    <label for="test">
+    foo
+    <input type="text" value="David"/>
+  </label>
+  <input type="checkbox" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_729-manual.html b/accname/name_test_case_729-manual.html
new file mode 100644
index 0000000..f9b5e15
--- /dev/null
+++ b/accname/name_test_case_729-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 729</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo David"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 729"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 729.</p>
+    <label for="test">
+    foo
+    <input type="text" value="David"/>
+  </label>
+  <input type="radio" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_730-manual.html b/accname/name_test_case_730-manual.html
new file mode 100644
index 0000000..b213840
--- /dev/null
+++ b/accname/name_test_case_730-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 730</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo David"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 730"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 730.</p>
+    <label for="test">
+    foo
+    <input type="text" value="David"/>
+  </label>
+  <input type="file" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_731-manual.html b/accname/name_test_case_731-manual.html
new file mode 100644
index 0000000..50f4a5b
--- /dev/null
+++ b/accname/name_test_case_731-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 731</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo David"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo David"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 731"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 731.</p>
+    <label for="test">
+    foo
+    <input type="text" value="David"/>
+  </label>
+  <input type="image" id="test" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_733-manual.html b/accname/name_test_case_733-manual.html
new file mode 100644
index 0000000..a9d3aaa
--- /dev/null
+++ b/accname/name_test_case_733-manual.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 733</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 733"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 733.</p>
+    <label for="test">
+    crazy
+    <select name="member" size="1" role="menu" tabindex="0">
+      <option role="menuitem" value="beard" selected="true">clown</option>
+      <option role="menuitem" value="scuba">rich</option>
+    </select>
+  </label>
+  <input type="password" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_734-manual.html b/accname/name_test_case_734-manual.html
new file mode 100644
index 0000000..fbf5a0c
--- /dev/null
+++ b/accname/name_test_case_734-manual.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 734</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 734"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 734.</p>
+    <label for="test">
+    crazy
+    <select name="member" size="1" role="menu" tabindex="0">
+      <option role="menuitem" value="beard" selected="true">clown</option>
+      <option role="menuitem" value="scuba">rich</option>
+    </select>
+  </label>
+  <input type="checkbox" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_735-manual.html b/accname/name_test_case_735-manual.html
new file mode 100644
index 0000000..955af0f
--- /dev/null
+++ b/accname/name_test_case_735-manual.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 735</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 735"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 735.</p>
+    <label for="test">
+    crazy
+    <select name="member" size="1" role="menu" tabindex="0">
+      <option role="menuitem" value="beard" selected="true">clown</option>
+      <option role="menuitem" value="scuba">rich</option>
+    </select>
+  </label>
+  <input type="radio" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_736-manual.html b/accname/name_test_case_736-manual.html
new file mode 100644
index 0000000..2518ff3
--- /dev/null
+++ b/accname/name_test_case_736-manual.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 736</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 736"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 736.</p>
+    <label for="test">
+    crazy
+    <select name="member" size="1" role="menu" tabindex="0">
+      <option role="menuitem" value="beard" selected="true">clown</option>
+      <option role="menuitem" value="scuba">rich</option>
+    </select>
+  </label>
+  <input type="file" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_737-manual.html b/accname/name_test_case_737-manual.html
new file mode 100644
index 0000000..a550114
--- /dev/null
+++ b/accname/name_test_case_737-manual.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 737</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 737"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 737.</p>
+    <label for="test">
+    crazy
+    <select name="member" size="1" role="menu" tabindex="0">
+      <option role="menuitem" value="beard" selected="true">clown</option>
+      <option role="menuitem" value="scuba">rich</option>
+    </select>
+  </label>
+  <input type="image" id="test" src="foo.jpg"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_738-manual.html b/accname/name_test_case_738-manual.html
new file mode 100644
index 0000000..ddf6210
--- /dev/null
+++ b/accname/name_test_case_738-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 738</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 738"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 738.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuetext="Monday" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="password" value="baz" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_739-manual.html b/accname/name_test_case_739-manual.html
new file mode 100644
index 0000000..241c0fb
--- /dev/null
+++ b/accname/name_test_case_739-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 739</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 739"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 739.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuetext="Monday" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="checkbox" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_740-manual.html b/accname/name_test_case_740-manual.html
new file mode 100644
index 0000000..f13ac6b
--- /dev/null
+++ b/accname/name_test_case_740-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 740</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 740"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 740.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuetext="Monday" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="radio" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_741-manual.html b/accname/name_test_case_741-manual.html
new file mode 100644
index 0000000..9b0b4bf
--- /dev/null
+++ b/accname/name_test_case_741-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 741</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 741"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 741.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuetext="Monday" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="file" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_742-manual.html b/accname/name_test_case_742-manual.html
new file mode 100644
index 0000000..e3fa6ad
--- /dev/null
+++ b/accname/name_test_case_742-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 742</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy Monday"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy Monday"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 742"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 742.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuetext="Monday" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="image" src="foo.jpg" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_743-manual.html b/accname/name_test_case_743-manual.html
new file mode 100644
index 0000000..b226f00
--- /dev/null
+++ b/accname/name_test_case_743-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 743</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy 4"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 743"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 743.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="password" id="test" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_744-manual.html b/accname/name_test_case_744-manual.html
new file mode 100644
index 0000000..44a70f7
--- /dev/null
+++ b/accname/name_test_case_744-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 744</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy 4"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 744"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 744.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="checkbox" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_745-manual.html b/accname/name_test_case_745-manual.html
new file mode 100644
index 0000000..e54efda
--- /dev/null
+++ b/accname/name_test_case_745-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 745</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy 4"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 745"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 745.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="radio" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_746-manual.html b/accname/name_test_case_746-manual.html
new file mode 100644
index 0000000..132fe30
--- /dev/null
+++ b/accname/name_test_case_746-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 746</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy 4"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 746"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 746.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="file" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_747-manual.html b/accname/name_test_case_747-manual.html
new file mode 100644
index 0000000..be56f5b
--- /dev/null
+++ b/accname/name_test_case_747-manual.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 747</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy 4"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy 4"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 747"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 747.</p>
+    <label for="test">
+    crazy
+    <div role="spinbutton" aria-valuemin="1" aria-valuemax="7" aria-valuenow="4">
+    </div>
+  </label>
+  <input type="image" src="foo.jpg" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_748-manual.html b/accname/name_test_case_748-manual.html
new file mode 100644
index 0000000..ad75819
--- /dev/null
+++ b/accname/name_test_case_748-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 748</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 748"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 748.</p>
+    <input type="password" id="test" title="crazy" value="baz"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_749-manual.html b/accname/name_test_case_749-manual.html
new file mode 100644
index 0000000..0ccaca7
--- /dev/null
+++ b/accname/name_test_case_749-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 749</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 749"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 749.</p>
+    <input type="checkbox" id="test" title="crazy"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_750-manual.html b/accname/name_test_case_750-manual.html
new file mode 100644
index 0000000..19f1243
--- /dev/null
+++ b/accname/name_test_case_750-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 750</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 750"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 750.</p>
+    <input type="radio" id="test" title="crazy"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_751-manual.html b/accname/name_test_case_751-manual.html
new file mode 100644
index 0000000..8f44ef9
--- /dev/null
+++ b/accname/name_test_case_751-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 751</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 751"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 751.</p>
+    <input type="file" id="test" title="crazy"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_752-manual.html b/accname/name_test_case_752-manual.html
new file mode 100644
index 0000000..6d2593b
--- /dev/null
+++ b/accname/name_test_case_752-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 752</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "crazy"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "crazy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 752"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 752.</p>
+    <input type="image" src="foo.jpg" id="test" title="crazy"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_753-manual.html b/accname/name_test_case_753-manual.html
new file mode 100644
index 0000000..82ee27c
--- /dev/null
+++ b/accname/name_test_case_753-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 753</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 753"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 753.</p>
+    <style>
+    label:before { content:"fancy "; }
+  </style>
+  <label for="test">fruit</label>
+  <input type="password" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_754-manual.html b/accname/name_test_case_754-manual.html
new file mode 100644
index 0000000..06d56ee
--- /dev/null
+++ b/accname/name_test_case_754-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 754</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 754"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 754.</p>
+    <style>
+    label:before { content:"fancy "; }
+  </style>
+  <label for="test">fruit</label>
+  <input type="checkbox" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_755-manual.html b/accname/name_test_case_755-manual.html
new file mode 100644
index 0000000..e621db7
--- /dev/null
+++ b/accname/name_test_case_755-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 755</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 755"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 755.</p>
+    <style>
+    label:before { content:"fancy "; }
+  </style>
+  <label for="test">fruit</label>
+  <input type="radio" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_756-manual.html b/accname/name_test_case_756-manual.html
new file mode 100644
index 0000000..70394d2
--- /dev/null
+++ b/accname/name_test_case_756-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 756</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 756"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 756.</p>
+    <style>
+    label:before { content:"fancy "; }
+  </style>
+  <label for="test">fruit</label>
+  <input type="file" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_757-manual.html b/accname/name_test_case_757-manual.html
new file mode 100644
index 0000000..4e7fd81
--- /dev/null
+++ b/accname/name_test_case_757-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 757</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 757"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 757.</p>
+    <style>
+    label:before { content:"fancy "; }
+  </style>
+  <label for="test">fruit</label>
+  <input type="image" src="foo.jpg" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_758-manual.html b/accname/name_test_case_758-manual.html
new file mode 100644
index 0000000..0381f1b
--- /dev/null
+++ b/accname/name_test_case_758-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 758</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 758"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 758.</p>
+    <style>
+    label:after { content:" fruit"; }
+  </style>
+  <label for="test">fancy</label>
+  <input type="password" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_759-manual.html b/accname/name_test_case_759-manual.html
new file mode 100644
index 0000000..a9b2d2f
--- /dev/null
+++ b/accname/name_test_case_759-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 759</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 759"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 759.</p>
+    <style>
+    label:after { content:" fruit"; }
+  </style>
+  <label for="test">fancy</label>
+  <input type="checkbox" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_760-manual.html b/accname/name_test_case_760-manual.html
new file mode 100644
index 0000000..bab3c19
--- /dev/null
+++ b/accname/name_test_case_760-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 760</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 760"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 760.</p>
+    <style>
+    label:after { content:" fruit"; }
+  </style>
+  <label for="test">fancy</label>
+  <input type="radio" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_761-manual.html b/accname/name_test_case_761-manual.html
new file mode 100644
index 0000000..7848eea
--- /dev/null
+++ b/accname/name_test_case_761-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 761</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fruitfancy"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 761"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 761.</p>
+    <style>
+    label:after { content:" fruit"; }
+  </style>
+  <label for="test">fancy</label>
+  <input type="file" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_test_case_762-manual.html b/accname/name_test_case_762-manual.html
new file mode 100644
index 0000000..d0d04e3
--- /dev/null
+++ b/accname/name_test_case_762-manual.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name test case 762</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "fancy fruit"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "fancy fruit"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name test case 762"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name test case 762.</p>
+    <style>
+    label:after { content:" fruit"; }
+  </style>
+  <label for="test">fancy</label>
+  <input type="image" src="foo.jpg" id="test"/>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_text-label-embedded-combobox-manual.html b/accname/name_text-label-embedded-combobox-manual.html
new file mode 100644
index 0000000..a32b1c2
--- /dev/null
+++ b/accname/name_text-label-embedded-combobox-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name text-label-embedded-combobox</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name text-label-embedded-combobox"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name text-label-embedded-combobox.</p>
+    <input type="text" id="test" />
+  <label for="test">Flash the screen
+    <div role="combobox">
+      <div role="textbox"></div>
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="true">1</li>
+    <li role="option">2</li>
+    <li role="option">3</li>
+      </ul>
+    </div>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_text-label-embedded-menu-manual.html b/accname/name_text-label-embedded-menu-manual.html
new file mode 100644
index 0000000..d6fddc6
--- /dev/null
+++ b/accname/name_text-label-embedded-menu-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name text-label-embedded-menu</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name text-label-embedded-menu"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name text-label-embedded-menu.</p>
+    <input type="text" id="test" />
+  <label for="test">Flash the screen
+    <span role="menu">
+      <span role="menuitem" aria-selected="true">1</span>
+      <span role="menuitem" hidden>2</span>
+      <span role="menuitem" hidden>3</span>
+    </span>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_text-label-embedded-select-manual.html b/accname/name_text-label-embedded-select-manual.html
new file mode 100644
index 0000000..6a0f6e1
--- /dev/null
+++ b/accname/name_text-label-embedded-select-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name text-label-embedded-select</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "Flash the screen 1 times."
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name text-label-embedded-select"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name text-label-embedded-select.</p>
+    <input type="text" id="test" />
+  <label for="test">Flash the screen
+    <select size="1">
+      <option selected="selected">1</option>
+      <option>2</option>
+      <option>3</option>
+    </select>
+    times.
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_text-label-embedded-slider-manual.html b/accname/name_text-label-embedded-slider-manual.html
new file mode 100644
index 0000000..caf9124
--- /dev/null
+++ b/accname/name_text-label-embedded-slider-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name text-label-embedded-slider</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name text-label-embedded-slider"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name text-label-embedded-slider.</p>
+    <input type="text" id="test" />
+  <label for="test">foo <input role="slider" type="range" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_text-label-embedded-spinbutton-manual.html b/accname/name_text-label-embedded-spinbutton-manual.html
new file mode 100644
index 0000000..8e1b1e2
--- /dev/null
+++ b/accname/name_text-label-embedded-spinbutton-manual.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name text-label-embedded-spinbutton</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo 5 baz"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name text-label-embedded-spinbutton"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name text-label-embedded-spinbutton.</p>
+    <input type="text" id="test" />
+  <label for="test">foo <input role="spinbutton" type="number" value="5" min="1" max="10" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10"> baz
+  </label>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/name_text-title-manual.html b/accname/name_text-title-manual.html
new file mode 100644
index 0000000..720a94c
--- /dev/null
+++ b/accname/name_text-title-manual.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Name text-title</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "name",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXDescription",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "accName",
+                  "is",
+                  "foo"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "Name",
+                  "is",
+                  "foo"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "Name text-title"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for Name text-title.</p>
+    <input type="text" id="test" title="foo" />
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/accname/test.png b/accname/test.png
new file mode 100644
index 0000000..85cfa35
--- /dev/null
+++ b/accname/test.png
Binary files differ
diff --git a/acid/OWNERS b/acid/OWNERS
new file mode 100644
index 0000000..fd31fb2
--- /dev/null
+++ b/acid/OWNERS
@@ -0,0 +1 @@
+@Ms2ger
diff --git a/acid/acid3/numbered-tests.html b/acid/acid3/numbered-tests.html
new file mode 100644
index 0000000..42d3324
--- /dev/null
+++ b/acid/acid3/numbered-tests.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>Acid3 numbered tests</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var tests = undefined;
+
+function gotMessage(e) {
+  var m = e.data;
+  if (tests === undefined && "num_tests" in m) {
+    tests = [];
+    for (var i = 0; i < m.num_tests; i++) {
+      tests.push(async_test("Test " + i));
+    }
+  } else if ("result" in m) {
+    var test = m.test;
+    var passed = m.result === "pass";
+    var message = m.message;
+    tests[test].step(function() {
+      assert_true(passed, message);
+    });
+    tests[test].done();
+  }
+}
+window.addEventListener("message", gotMessage, false);
+</script>
+<iframe src="test.html"></iframe>
diff --git a/acid/acid3/reference.sub.html b/acid/acid3/reference.sub.html
index 3109613..974bee1 100755
--- a/acid/acid3/reference.sub.html
+++ b/acid/acid3/reference.sub.html
@@ -1,7 +1,7 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
 <html>
  <title>The Acid3 Test (Reference Rendering)</title>
- <link rel="icon" href="http://nonexistent-origin.{{host}}">
+ <link rel="icon" href="http://nonexistent.{{host}}">
  <style type="text/css">
   html { margin: 0; padding: 0; }
   body { background: #c0c0c0 url(reference.png) top left no-repeat; margin: 0; padding: 0; }
diff --git a/acid/acid3/test.html b/acid/acid3/test.html
index 1ac8da6..0c47530 100644
--- a/acid/acid3/test.html
+++ b/acid/acid3/test.html
@@ -3419,6 +3419,7 @@
     }
 
   ];
+  window.parent.postMessage({num_tests: tests.length}, "*");
   var log = '';
   var delay = 10;
   var score = 0, index = 0, retry = 0, errors = 0;
@@ -3456,6 +3457,7 @@
         } else {
           fail("no error message");
         }
+        window.parent.postMessage({test: index, result: "pass"}, "*");
       } catch (e) {
         var s;
         if (e.message)
@@ -3464,6 +3466,7 @@
           s = e;
         errors += 1;
         log += "Test " + zeroPaddedIndex + " failed: " + s + "\n";
+        window.parent.postMessage({test: index, result: "fail", message: s}, "*");
       };
       retry = 0;
       index += 1;
diff --git a/ambient-light/AmbientLightSensor-disabled-by-feature-policy.https.html b/ambient-light/AmbientLightSensor-disabled-by-feature-policy.https.html
new file mode 100644
index 0000000..3525320
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-disabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AmbientLightSensor Feature Policy Test: Disabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_disabled(AmbientLightSensor);
+</script>
+</body>
diff --git a/ambient-light/AmbientLightSensor-disabled-by-feature-policy.https.html.headers b/ambient-light/AmbientLightSensor-disabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..b8073a8
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-disabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: ambient-light-sensor 'none'
diff --git a/ambient-light/AmbientLightSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html b/ambient-light/AmbientLightSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
new file mode 100644
index 0000000..84bee55
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AmbientLightSensor Feature Policy Test: Enabled by attribute redirect on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute_redirect_on_load(AmbientLightSensor);
+</script>
+</body>
diff --git a/ambient-light/AmbientLightSensor-enabled-by-feature-policy-attribute.https.html b/ambient-light/AmbientLightSensor-enabled-by-feature-policy-attribute.https.html
new file mode 100644
index 0000000..a981012
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-enabled-by-feature-policy-attribute.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AmbientLightSensor Feature Policy Test: Enabled by attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute(AmbientLightSensor);
+</script>
+</body>
diff --git a/ambient-light/AmbientLightSensor-enabled-by-feature-policy.https.html b/ambient-light/AmbientLightSensor-enabled-by-feature-policy.https.html
new file mode 100644
index 0000000..e4ce256
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-enabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AmbientLightSensor Feature Policy Test: Enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled(AmbientLightSensor);
+</script>
+</body>
diff --git a/ambient-light/AmbientLightSensor-enabled-by-feature-policy.https.html.headers b/ambient-light/AmbientLightSensor-enabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..f037f3f
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-enabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: ambient-light-sensor *
diff --git a/ambient-light/AmbientLightSensor-enabled-on-self-origin-by-feature-policy.https.html b/ambient-light/AmbientLightSensor-enabled-on-self-origin-by-feature-policy.https.html
new file mode 100644
index 0000000..bb2c4a6
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-enabled-on-self-origin-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AmbientLightSensor Feature Policy Test: Enabled on self origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_on_self_origin(AmbientLightSensor);
+</script>
+</body>
diff --git a/ambient-light/AmbientLightSensor-enabled-on-self-origin-by-feature-policy.https.html.headers b/ambient-light/AmbientLightSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..1d22667
--- /dev/null
+++ b/ambient-light/AmbientLightSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: ambient-light-sensor 'self'
diff --git a/ambient-light/idlharness.https.html b/ambient-light/idlharness.https.html
index 732271a..962a8f7 100644
--- a/ambient-light/idlharness.https.html
+++ b/ambient-light/idlharness.https.html
@@ -30,7 +30,7 @@
 promise_test(() => {
   return Promise.all([
     "/interfaces/dom.idl",
-    "/interfaces/generic-sensor.idl",
+    "/interfaces/sensors.idl",
     "/interfaces/ambient-light.idl",
   ].map(fetchText)).then(doTest);
 }, "Test IDL implementation of Ambient Light Sensor");
diff --git a/annotation-model/annotations/annotationMusts-manual.html b/annotation-model/annotations/annotationMusts-manual.html
index adfcca8..6116e4e 100644
--- a/annotation-model/annotations/annotationMusts-manual.html
+++ b/annotation-model/annotations/annotationMusts-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>A single ANNOTATION has all required keys and all annotation keys used meet required value constraints</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/annotations/annotationOptionals-manual.html b/annotation-model/annotations/annotationOptionals-manual.html
index 65be15a..5d73034 100644
--- a/annotation-model/annotations/annotationOptionals-manual.html
+++ b/annotation-model/annotations/annotationOptionals-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>Annotation implements optional keys and meets optional key value constraints</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/annotations/annotationsAgentOptionals-manual.html b/annotation-model/annotations/annotationsAgentOptionals-manual.html
index b187008..3aadc21 100644
--- a/annotation-model/annotations/annotationsAgentOptionals-manual.html
+++ b/annotation-model/annotations/annotationsAgentOptionals-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>Annotation implements optional keys and meets optional key value constraints for Creator and Generator Agents</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/collections/collectionMusts-manual.html b/annotation-model/collections/collectionMusts-manual.html
index d1ff444..a023956 100644
--- a/annotation-model/collections/collectionMusts-manual.html
+++ b/annotation-model/collections/collectionMusts-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>A single Annotation Collection has all required keys and all collection keys used meet required value constraints</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/collections/collectionOptionals-manual.html b/annotation-model/collections/collectionOptionals-manual.html
index ce489fc..24df82d 100644
--- a/annotation-model/collections/collectionOptionals-manual.html
+++ b/annotation-model/collections/collectionOptionals-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>A single Annotation Collection implements optional keys and meets optional key value constraints</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/collections/pages/pageMusts-manual.html b/annotation-model/collections/pages/pageMusts-manual.html
index 843faaf..4422bba 100644
--- a/annotation-model/collections/pages/pageMusts-manual.html
+++ b/annotation-model/collections/pages/pageMusts-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>A single Annotation Page has all required keys and all page keys present meet required value constraints</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/collections/pages/pageOptionals-manual.html b/annotation-model/collections/pages/pageOptionals-manual.html
index e68f2d1..95f66bb 100644
--- a/annotation-model/collections/pages/pageOptionals-manual.html
+++ b/annotation-model/collections/pages/pageOptionals-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>A single Annotation Page implements optional keys and meets optional key value constraints</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/tools/make_tests.py b/annotation-model/tools/make_tests.py
index 3ff46de..615d8b6 100644
--- a/annotation-model/tools/make_tests.py
+++ b/annotation-model/tools/make_tests.py
@@ -4,6 +4,7 @@
 # This tool creates .html test files for the WPT harness from corresponding .test
 # files that it finds in the tree for this test collection.
 
+from __future__ import print_function
 
 import re
 import time
@@ -40,7 +41,7 @@
     try:
       testJSON = json.load(open(theFile, "r"))
     except ValueError as e:
-      print "parse of " + theFile + " failed: " + e[0]
+      print("parse of " + theFile + " failed: " + e[0])
     else:
       theFile = re.sub("\.\./", "", theFile)
       defList.append(theFile)
@@ -65,7 +66,7 @@
     try:
       testJSON = json.load(open(theFile, "r"))
     except ValueError as e:
-      print "parse of " + theFile + " failed: " + e[0]
+      print("parse of " + theFile + " failed: " + e[0])
     else:
       try:
         testType = testJSON['testType']
diff --git a/annotation-model/tools/template_js b/annotation-model/tools/template_js
index 4be9fc8..29b76b3 100644
--- a/annotation-model/tools/template_js
+++ b/annotation-model/tools/template_js
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>{{TESTTITLE}}</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-model/tools/template_manual b/annotation-model/tools/template_manual
index d7c3315..14b3b9a 100644
--- a/annotation-model/tools/template_manual
+++ b/annotation-model/tools/template_manual
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>{{TESTTITLE}}</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/annotation-model/scripts/ajv.min.js"></script>
diff --git a/annotation-protocol/tools/protocol-server.py b/annotation-protocol/tools/protocol-server.py
index cbfc403..6fcbe92 100755
--- a/annotation-protocol/tools/protocol-server.py
+++ b/annotation-protocol/tools/protocol-server.py
@@ -9,6 +9,8 @@
 #
 # for license information, see http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
 
+from __future__ import print_function
+
 import os
 import sys
 
@@ -345,8 +347,8 @@
     newID = incoming['id']
     key = os.path.basename(newID)
 
-    print "post:" + newID
-    print "post:" + key
+    print("post:" + newID)
+    print("post:" + key)
 
     tempAnnotations[key] = dump_json(incoming)
 
@@ -366,8 +368,8 @@
     newID = incoming['id']
     key = os.path.basename(newID)
 
-    print "put:" + newID
-    print "put:" + key
+    print("put:" + newID)
+    print("put:" + key)
 
     tempAnnotations[key] = dump_json(incoming)
 
@@ -402,9 +404,9 @@
         response.content = 'Not Found'
 
 if __name__ == '__main__':
-    print 'http://' + myhost + ':{0}/'.format(port)
-    print 'container URI is http://' + myhost + ':{0}/'.format(port) + "/annotations/"
-    print 'example annotation URI is http://' + myhost + ':{0}/'.format(port) + "/annotations/anno1.json"
+    print('http://' + myhost + ':{0}/'.format(port))
+    print('container URI is http://' + myhost + ':{0}/'.format(port) + "/annotations/")
+    print('example annotation URI is http://' + myhost + ':{0}/'.format(port) + "/annotations/anno1.json")
 
     routes = [
         ("GET", "", wptserve.handlers.file_handler),
diff --git a/annotation-vocab/01-validJSON-LD-manual.html b/annotation-vocab/01-validJSON-LD-manual.html
index 88a4ccf..3d6623a 100644
--- a/annotation-vocab/01-validJSON-LD-manual.html
+++ b/annotation-vocab/01-validJSON-LD-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>Ensure Vocabulary JSON-LD is valid</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
diff --git a/annotation-vocab/02-context-to-triples-manual.html b/annotation-vocab/02-context-to-triples-manual.html
index b9d6603..bfe030d 100644
--- a/annotation-vocab/02-context-to-triples-manual.html
+++ b/annotation-vocab/02-context-to-triples-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>JSON-LD context document can be used to convert JSON-LD serialized Annotations into RDF triples</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
diff --git a/annotation-vocab/03-graphs-are-isomorphic-manual.html b/annotation-vocab/03-graphs-are-isomorphic-manual.html
index 5061c32..ed6869c 100644
--- a/annotation-vocab/03-graphs-are-isomorphic-manual.html
+++ b/annotation-vocab/03-graphs-are-isomorphic-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>Graphs produced are isomorphic</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
diff --git a/annotation-vocab/04-graphs-convert-lossless-manual.html b/annotation-vocab/04-graphs-convert-lossless-manual.html
index 95ebfd6..05e7fb7 100644
--- a/annotation-vocab/04-graphs-convert-lossless-manual.html
+++ b/annotation-vocab/04-graphs-convert-lossless-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>Graphs produced can be converted back into JSON-LD with no loss</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
diff --git a/annotation-vocab/05-ontology-parsed-as-valid-manual.html b/annotation-vocab/05-ontology-parsed-as-valid-manual.html
index 331eb23..e1e41e6 100644
--- a/annotation-vocab/05-ontology-parsed-as-valid-manual.html
+++ b/annotation-vocab/05-ontology-parsed-as-valid-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>Ontology can be parsed without errors</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
diff --git a/annotation-vocab/06-ontology-internally-consistent-manual.html b/annotation-vocab/06-ontology-internally-consistent-manual.html
index 84aa433..de9b7d0 100644
--- a/annotation-vocab/06-ontology-internally-consistent-manual.html
+++ b/annotation-vocab/06-ontology-internally-consistent-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
 <title>Ontology is internally consistent</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
diff --git a/annotation-vocab/tools/vocab_tester.py b/annotation-vocab/tools/vocab_tester.py
index 873377c..7d9cc5b 100644
--- a/annotation-vocab/tools/vocab_tester.py
+++ b/annotation-vocab/tools/vocab_tester.py
@@ -3,6 +3,8 @@
 # License: Apache2
 # Last Modified: 2016-09-02
 
+from __future__ import print_function
+
 import json
 from rdflib import ConjunctiveGraph, URIRef
 from pyld import jsonld
@@ -164,14 +166,14 @@
 try:
     expanded = expand(example_js, context_js)
 except:
-    print "Context is invalid, failed Test 1"
+    print("Context is invalid, failed Test 1")
 
 
 # Test2: JSON-LD context document can be used to convert JSON-LD serialized Annotations into RDF triples.
 try:
     jsonld_nq = to_rdf(example_js, {"base": "http://example.org/", "format": "application/nquads"})
 except:
-    print "Cannot use context to convert JSON-LD to NQuads"
+    print("Cannot use context to convert JSON-LD to NQuads")
 
 
 # Test3: Graphs produced are isomorphic
@@ -183,7 +185,7 @@
     assert(len(rl_g.store) == len(js_g.store))
     assert(rl_g.isomorphic(js_g))
 except:
-    print "Different triples from two parsers, or non-isomorphic graphs"
+    print("Different triples from two parsers, or non-isomorphic graphs")
 
 
 # Test4: The graphs produced can be converted back into JSON-LD without loss of information
@@ -192,7 +194,7 @@
     js2 = validator.compact_and_clean(js)
     assert(js2 == example_js)
 except:
-    print "Failed to recompact parsed data"
+    print("Failed to recompact parsed data")
     raise
 
 
@@ -228,13 +230,13 @@
     for r in ranges:
         if not r in classes and not str(r).startswith("http://www.w3.org/2001/XMLSchema#") and \
             not r == rdfsresource:
-            print "Found inconsistent property: %s has unknown range" % p
+            print("Found inconsistent property: %s has unknown range" % p)
 
 for c in classes:
     parents = list(g.objects(c, rdfssco))
     for p in parents:
         if not p in classes and not p in otherClasses:
-            print "Found inconsistent class: %s has unknown superClass" % c
+            print("Found inconsistent class: %s has unknown superClass" % c)
 
 
-print "Done."
+print("Done.")
diff --git a/apng/OWNERS b/apng/OWNERS
new file mode 100644
index 0000000..a2f0669
--- /dev/null
+++ b/apng/OWNERS
@@ -0,0 +1,3 @@
+@stuartparmenter
+@svgeesus
+@leonscroggins
diff --git a/apng/supported-in-source-type.html b/apng/supported-in-source-type.html
new file mode 100644
index 0000000..0a4d232
--- /dev/null
+++ b/apng/supported-in-source-type.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Animated PNG MIME type (image/apng) is recognized by &lt;source type></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function resolveUrl(relativeUrl) {
+  var a = document.createElement('a');
+  a.href = relativeUrl;
+  return a.href;
+}
+
+async_test(t => {
+  window.onload = t.step_func_done(() => {
+    let image = document.querySelector('img');
+    let apngSrc = document.querySelector('source');
+    assert_equals(image.currentSrc, resolveUrl(apngSrc.srcset));
+  });
+});
+</script>
+<picture>
+  <source srcset="/images/anim-gr.png" type="image/apng">
+  <img src="/images/anim-gr.gif" style="visibility: hidden">
+</picture>
diff --git a/async-local-storage/storage-smoke-test.https.tentative.html b/async-local-storage/storage-smoke-test.https.tentative.html
new file mode 100644
index 0000000..b4d66da
--- /dev/null
+++ b/async-local-storage/storage-smoke-test.https.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async local storage storage export smoke test</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module">
+import { storage } from "std:async-local-storage";
+
+test(() => {
+  const { backingStore } = storage;
+  assert_array_equals(Object.keys(backingStore), ["database", "store", "version"]);
+  assert_own_property(backingStore, "database");
+  assert_own_property(backingStore, "store");
+  assert_own_property(backingStore, "version");
+  assert_equals(Object.getPrototypeOf(backingStore), Object.prototype);
+
+  assert_equals(backingStore.database, "async-local-storage:default");
+  assert_equals(backingStore.store, "store");
+  assert_equals(backingStore.version, 1);
+}, "backingStore returns the correct object");
+
+promise_test(async (t) => {
+  t.add_cleanup(async () => {
+    await storage.clear();
+  });
+
+  assert_equals(await storage.set("key", "value"), undefined);
+
+  assert_equals(await storage.get("key"), "value");
+  assert_equals(await storage.has("key"), true);
+  assert_array_equals(await storage.keys(), ["key"]);
+  assert_array_equals(await storage.values(), ["value"]);
+
+  const entries = await storage.entries();
+  assert_true(Array.isArray(entries));
+  assert_equals(entries.length, 1);
+  assert_array_equals(entries[0], ["key", "value"]);
+
+  assert_equals(await storage.delete("key"), undefined);
+
+  assert_equals(await storage.get("key"), undefined);
+  assert_equals(await storage.has("key"), false);
+}, "storage methods work, at least for one entry with string key and value");
+</script>
diff --git a/audio-output/OWNERS b/audio-output/OWNERS
new file mode 100644
index 0000000..4a5c2a8
--- /dev/null
+++ b/audio-output/OWNERS
@@ -0,0 +1 @@
+@guidou
diff --git a/background-fetch/interfaces.html b/background-fetch/interfaces.html
index 0cd200f..51e320e 100644
--- a/background-fetch/interfaces.html
+++ b/background-fetch/interfaces.html
@@ -12,15 +12,17 @@
 <script>
 'use strict';
 
-promise_test(function() {
-  return fetch('interfaces.idl')
-    .then(response => response.text())
-    .then(idls => {
-      var idlArray = new IdlArray();
-      idlArray.add_untested_idls('interface ServiceWorkerRegistration {};');
-      idlArray.add_untested_idls('[Exposed=ServiceWorker] interface ServiceWorkerGlobalScope {};');
-      idlArray.add_idls(idls);
-      idlArray.test();
-    });
+promise_test(async function () {
+  const idls = await fetch('/interfaces/background-fetch.idl').then(r => r.text());
+  const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+
+  var idlArray = new IdlArray();
+  idlArray.add_untested_idls('interface ServiceWorkerRegistration {};');
+  idlArray.add_untested_idls('[Exposed=ServiceWorker] interface ServiceWorkerGlobalScope {};');
+  idlArray.add_untested_idls('interface ExtendableEvent{};');
+  idlArray.add_untested_idls('dictionary ExtendableEventInit{};');
+  idlArray.add_untested_idls(dom, { only: ['EventTarget'] });
+  idlArray.add_idls(idls);
+  idlArray.test();
 }, 'Exposed interfaces in a Document.');
 </script>
diff --git a/background-fetch/interfaces.worker.js b/background-fetch/interfaces.worker.js
index a5fc4ed..0760ffd 100644
--- a/background-fetch/interfaces.worker.js
+++ b/background-fetch/interfaces.worker.js
@@ -3,16 +3,18 @@
 importScripts('/resources/testharness.js');
 importScripts('/resources/WebIDLParser.js', '/resources/idlharness.js');
 
-promise_test(function() {
-  return fetch('interfaces.idl')
-    .then(response => response.text())
-    .then(idls => {
-      var idlArray = new IdlArray();
-      idlArray.add_untested_idls('interface ServiceWorkerRegistration {};');
-      idlArray.add_untested_idls('[Exposed=ServiceWorker] interface ServiceWorkerGlobalScope {};');
-      idlArray.add_idls(idls);
-      idlArray.test();
-    });
+promise_test(async function() {
+  const idls = await fetch('/interfaces/background-fetch.idl').then(r => r.text());
+  const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+
+  var idlArray = new IdlArray();
+  idlArray.add_untested_idls('interface ServiceWorkerRegistration {};');
+  idlArray.add_untested_idls('[SecureContext, Exposed = (Window, Worker)] interface ServiceWorkerGlobalScope {};');
+  idlArray.add_untested_idls('interface ExtendableEvent{};');
+  idlArray.add_untested_idls('dictionary ExtendableEventInit{};');
+  idlArray.add_untested_idls(dom, { only: ['EventTarget'] });
+  idlArray.add_idls(idls);
+  idlArray.test();
 }, 'Exposed interfaces in a Service Worker.');
 
 done();
diff --git a/beacon/OWNERS b/beacon/OWNERS
new file mode 100644
index 0000000..719eb69
--- /dev/null
+++ b/beacon/OWNERS
@@ -0,0 +1,2 @@
+@toddreifsteck
+@igrigorik
diff --git a/beacon/beacon-common.sub.js b/beacon/beacon-common.sub.js
index de3846b..0a36283 100644
--- a/beacon/beacon-common.sub.js
+++ b/beacon/beacon-common.sub.js
@@ -39,6 +39,10 @@
 var smallFormDataTest = { id: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) };
 var mediumFormDataTest = { id: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) };
 var largeFormDataTest = { id: "LargeFormData", data: CreateFormDataFromPayload(largePayload) };
+var smallSafeContentTypeEncodedTest = { id: "SmallSafeContentTypeEncoded", data: new Blob([smallPayload], { type: 'application/x-www-form-urlencoded' }) };
+var smallSafeContentTypeFormTest = { id: "SmallSafeContentTypeForm", data: new FormData() };
+var smallSafeContentTypeTextTest = { id: "SmallSafeContentTypeText", data: new Blob([smallPayload], { type: 'text/plain' }) };
+var smallCORSContentTypeTextTest = { id: "SmallCORSContentTypeText", data: new Blob([smallPayload], { type: 'text/html' }) };
 // We don't test maxFormData because the extra multipart separators make it difficult to
 // calculate a maxPayload.
 
@@ -52,12 +56,15 @@
 var bufferSourceMaxTest = [maxBufferSourceTest];
 var formDataTests = [emptyFormDataTest, smallFormDataTest, mediumFormDataTest, largeFormDataTest];
 var formDataMaxTest = [largeFormDataTest];
-var allTests = [].concat(stringTests, stringMaxTest, blobTests, blobMaxTest, bufferSourceTests, bufferSourceMaxTest, formDataTests, formDataMaxTest);
+var contentTypeTests = [smallSafeContentTypeEncodedTest,smallSafeContentTypeFormTest,smallSafeContentTypeTextTest,smallCORSContentTypeTextTest];
+var allTests = [].concat(stringTests, stringMaxTest, blobTests, blobMaxTest, bufferSourceTests, bufferSourceMaxTest, formDataTests, formDataMaxTest, contentTypeTests);
 
 // This special cross section of test cases is meant to provide a slimmer but reasonably-
 // representative set of tests for parameterization across variables (e.g. redirect codes,
 // cors modes, etc.)
-var sampleTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, smallBlobTest, smallBufferSourceTest, smallFormDataTest];
+var sampleTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, smallBlobTest, smallBufferSourceTest, smallFormDataTest, smallSafeContentTypeEncodedTest, smallSafeContentTypeFormTest, smallSafeContentTypeTextTest];
+
+var preflightTests = [smallCORSContentTypeTextTest];
 
 // Build a test lookup table, which is useful when instructing a web worker or an iframe
 // to run a test, so that we don't have to marshal the entire test case across a process boundary.
diff --git a/beacon/beacon-cors.sub.window.js b/beacon/beacon-cors.sub.window.js
index ddfdfbf..f4bccf1 100644
--- a/beacon/beacon-cors.sub.window.js
+++ b/beacon/beacon-cors.sub.window.js
@@ -38,4 +38,30 @@
     runTests(sampleTests);
 });
 
+// Now test a cross-origin request that doesn't use a safelisted Content-Type and ensure
+// we are applying the proper restrictions. Since a non-safelisted Content-Type request
+// header is used there should be a preflight/options request and we should only succeed
+// send the payload if the proper CORS headers are used.
+{
+    // Implement the self.buildId extension to identify the parameterized
+    // test in the report.
+    self.buildId = function (baseId) {
+        return `${baseId}-PREFLIGHT-ALLOW`;
+    };
+
+    // Implement the self.buildBaseUrl and self.buildTargetUrl extensions
+    // to change the target URL to use a cross-origin domain name.
+    self.buildBaseUrl = function (baseUrl) {
+        return "http://{{domains[www]}}:{{ports[http][0]}}";
+    };
+
+    // Implement the self.buildTargetUrl extension to append a directive
+    // to the handler, that it should return CORS headers for the preflight we expect.
+    self.buildTargetUrl = function (targetUrl) {
+        return `${targetUrl}&origin=http://{{host}}:{{ports[http][0]}}&credentials=true&preflightExpected=true`;
+    }
+
+    runTests(preflightTests);
+}
+
 done();
\ No newline at end of file
diff --git a/beacon/beacon-error.window.js b/beacon/beacon-error.window.js
index 9c037ba..b53353a 100644
--- a/beacon/beacon-error.window.js
+++ b/beacon/beacon-error.window.js
@@ -24,16 +24,25 @@
 }, "Verify calling 'navigator.sendBeacon()' with a URL that is not a http(s) scheme throws an exception.");
 
 // We'll validate that we can send one beacon that uses our entire Quota and then fail to send one that is just one char.
-test(function () {
-    var destinationURL = "/fetch/api/resources/trickle.py?count=1&ms=1000";
-
-    var firstSuccess = navigator.sendBeacon(destinationURL, maxPayload);
-    assert_true(firstSuccess, "calling 'navigator.sendBeacon()' with our max payload size should succeed.");
+promise_test(async () => {
+    function wait(ms) {
+        return new Promise(res => step_timeout(res, ms));
+    }
+    const url = '/fetch/api/resources/trickle.py?count=1&ms=0';
+    assert_true(navigator.sendBeacon(url, maxPayload),
+                "calling 'navigator.sendBeacon()' with our max payload size should succeed.");
 
     // Now we'll send just one character.
-    var secondSuccess = navigator.sendBeacon(destinationURL, "1");
-    assert_false(secondSuccess, "calling 'navigator.sendBeacon()' with just one char should fail while our Quota is used up.");
+    assert_false(navigator.sendBeacon(url, '1'),
+                 "calling 'navigator.sendBeacon()' with just one char should fail while our Quota is used up.");
 
-}, "Verify calling 'navigator.sendBeacon()' with a small payload fails while Quota is completely utilized.");
+    for (let i = 0; i < 20; ++i) {
+        await wait(100);
+        if (navigator.sendBeacon(url, maxPayload)) {
+           return;
+        }
+    }
+    assert_unreached('The quota should recover after fetching.');
+}, "Verify the behavior after the quota is exhausted.");
 
 done();
diff --git a/beacon/headers/header-content-type.html b/beacon/headers/header-content-type.html
index 2d23adf..e2f2705 100644
--- a/beacon/headers/header-content-type.html
+++ b/beacon/headers/header-content-type.html
@@ -10,36 +10,30 @@
     <script src="/common/utils.js"></script>
     <script src="/common/get-host-info.sub.js"></script>
     <script>
-var RESOURCES_DIR = "/beacon/resources/";
-
-function pollResult(test, id) {
-  var checkUrl = RESOURCES_DIR + "content-type.py?cmd=get&id=" + id;
-
-  return new Promise(resolve => {
-    step_timeout(test.step_func(() => {
-      fetch(checkUrl).then(response => {
-        response.text().then(body => {
-          resolve(body);
-        });
-      });
-    }), 1000);
-  });
-}
+const RESOURCES_DIR = "/beacon/resources/";
 
 function testContentTypeHeader(what, contentType, title) {
-  var testBase = RESOURCES_DIR;
-  var id = self.token();
-  var testUrl = testBase + "content-type.py?cmd=put&id=" + id;
-
-  promise_test(function(test) {
+  function wait(ms) {
+    return new Promise(resolve => step_timeout(resolve, ms));
+  }
+  promise_test(async t => {
+    const id = self.token();
+    const testUrl = new Request(RESOURCES_DIR + "content-type.py?cmd=put&id=" + id).url;
+    assert_equals(performance.getEntriesByName(testUrl).length, 0);
     assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
-    return pollResult(test, id) .then(result => {
-      if (contentType == "multipart/form-data") {
-        assert_true(result.startsWith(contentType), "Correct Content-Type header result");
-      } else {
-        assert_equals(result, contentType, "Correct Content-Type header result");
-      }
-    });
+
+    do {
+      await wait(50);
+    } while (performance.getEntriesByName(testUrl).length === 0);
+    assert_equals(performance.getEntriesByName(testUrl).length, 1);
+    const checkUrl = RESOURCES_DIR + "content-type.py?cmd=get&id=" + id;
+    const response = await fetch(checkUrl);
+    const text = await response.text();
+    if (contentType === "multipart/form-data") {
+      assert_true(text.startsWith(contentType), "Correct Content-Type header result");
+    } else {
+      assert_equals(text, contentType, "Correct Content-Type header result");
+    }
   }, "Test content-type header for a body " + title);
 }
 
diff --git a/beacon/resources/beacon.py b/beacon/resources/beacon.py
index afa522a..5f2553d 100644
--- a/beacon/resources/beacon.py
+++ b/beacon/resources/beacon.py
@@ -60,40 +60,52 @@
         # with the unique session id, in order to retrieve a range of results
         # later knowing the index range.
         test_idx = request.GET.first("tidx")
-
-        test_data_key = build_stash_key(session_id, test_idx)
         test_data = { "id": test_id, "error": None }
 
-        payload = ""
-        if "Content-Type" in request.headers and \
-           "form-data" in request.headers["Content-Type"]:
-            if "payload" in request.POST:
-                # The payload was sent as a FormData.
-                payload = request.POST.first("payload")
+        # Only store the actual POST requests, not any preflight/OPTIONS requests we may get.
+        if request.method == "POST":
+            test_data_key = build_stash_key(session_id, test_idx)
+
+            payload = ""
+            if "Content-Type" in request.headers and \
+               "form-data" in request.headers["Content-Type"]:
+                if "payload" in request.POST:
+                    # The payload was sent as a FormData.
+                    payload = request.POST.first("payload")
+                else:
+                    # A FormData was sent with an empty payload.
+                    pass
             else:
-                # A FormData was sent with an empty payload.
-                pass
-        else:
-            # The payload was sent as either a string, Blob, or BufferSource.
-            payload = request.body
+                # The payload was sent as either a string, Blob, or BufferSource.
+                payload = request.body
 
-        payload_parts = filter(None, payload.split(":"))
-        if len(payload_parts) > 0:
-            payload_size = int(payload_parts[0])
+            payload_parts = filter(None, payload.split(":"))
+            if len(payload_parts) > 0:
+                payload_size = int(payload_parts[0])
 
-            # Confirm the payload size sent matches with the number of characters sent.
-            if payload_size != len(payload_parts[1]):
-                test_data["error"] = "expected %d characters but got %d" % (payload_size, len(payload_parts[1]))
+                # Confirm the payload size sent matches with the number of characters sent.
+                if payload_size != len(payload_parts[1]):
+                    test_data["error"] = "expected %d characters but got %d" % (payload_size, len(payload_parts[1]))
+                else:
+                    # Confirm the payload contains the correct characters.
+                    for i in range(0, payload_size):
+                        if payload_parts[1][i] != "*":
+                            test_data["error"] = "expected '*' at index %d but got '%s''" % (i, payload_parts[1][i])
+                            break
+
+            # Store the result in the stash so that it can be retrieved
+            # later with a 'stat' command.
+            request.server.stash.put(test_data_key, test_data)
+        elif request.method == "OPTIONS":
+            # If we expect a preflight, then add the cors headers we expect, otherwise log an error as we shouldn't
+            # send a preflight for all requests.
+            if "preflightExpected" in request.GET:
+                response.headers.set("Access-Control-Allow-Headers", "content-type")
+                response.headers.set("Access-Control-Allow-Methods", "POST")
             else:
-                # Confirm the payload contains the correct characters.
-                for i in range(0, payload_size):
-                    if payload_parts[1][i] != "*":
-                        test_data["error"] = "expected '*' at index %d but got '%s''" % (i, payload_parts[1][i])
-                        break
-
-        # Store the result in the stash so that it can be retrieved
-        # later with a 'stat' command.
-        request.server.stash.put(test_data_key, test_data)
+                test_data_key = build_stash_key(session_id, test_idx)
+                test_data["error"] = "Preflight not expected."
+                request.server.stash.put(test_data_key, test_data)
     elif command == "stat":
         test_idx_min = int(request.GET.first("tidx_min"))
         test_idx_max = int(request.GET.first("tidx_max"))
diff --git a/bluetooth/README.md b/bluetooth/README.md
new file mode 100644
index 0000000..2d0e9ab
--- /dev/null
+++ b/bluetooth/README.md
@@ -0,0 +1,61 @@
+# Web Bluetooth Testing
+
+Web Bluetooth testing relies on the [Web Bluetooth Testing API] which must be
+provided by browsers under test.
+
+In this test suite `resources/bluetooth-helpers.js` detects and triggers
+the API to be loaded as needed.
+
+The Chromium implementation is provided by
+`../resources/chromium/web-bluetooth-test.js`.
+
+The Chromium implementation is not included in stable Chrome builds since it
+would add too much to the binary size. On Chromium infrastructure, it is run
+using the `content_shell` executable.
+
+In the future, Chromium `src/device/bluetooth` may be refactored into a Mojo
+service. At this point, it would be possible to add the necessary testing hooks
+into stable Chrome without substantially increasing the binary size, similar to
+WebUSB.
+
+These bluetooth tests are upstreamed here because other browsers can reuse them
+by implementing the [Web Bluetooth Testing API], even if only on their internal
+infrastructure.
+
+[Web Bluetooth Testing API]: https://docs.google.com/document/d/1Nhv_oVDCodd1pEH_jj9k8gF4rPGb_84VYaZ9IG8M_WY/
+
+# Generated gen-* files from generator.py
+
+`generator.py` builds `gen-*.html` tests using templates in
+`script-tests/*/*.js`.
+
+The subdirectory structure in `bluetooth/script-test/*` is recreated into
+`bluetooth/*`.  The generator expands each CALL function from templates
+into new leaf directories and files.
+
+Example:
+
+`script-tests/server/get-same-object.js` contains:
+
+```
+gattServer.CALLS([
+        getPrimaryService('heart_rate')|
+        getPrimaryServices()|
+        getPrimaryServices('heart_rate')[UUID]]),
+```
+
+Generating:
+
+```
+server/getPrimaryService/gen-get-same-object.html
+server/getPrimaryServices/gen-get-same-object.html
+server/getPrimaryServices/gen-get-same-object-with-uuid.html
+```
+
+Usage:
+
+```
+$ python generate.py
+```
+
+More details documented in `generate.py`.
diff --git a/bluetooth/characteristic/characteristicProperties.https.html b/bluetooth/characteristic/characteristicProperties.https.html
new file mode 100644
index 0000000..d20d435
--- /dev/null
+++ b/bluetooth/characteristic/characteristicProperties.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'HeartRate device properties';
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => Promise.all([
+      service.getCharacteristic('temperature_measurement'),
+      service.getCharacteristic('measurement_interval')]))
+    .then(([temperature_measurement, measurement_interval]) => {
+      let tm_expected_properties =
+          new TestCharacteristicProperties(['indicate']);
+      assert_properties_equal(temperature_measurement.properties,
+          tm_expected_properties);
+
+      let mi_expected_properties =
+          new TestCharacteristicProperties(['read', 'write', 'indicate']);
+      assert_properties_equal(measurement_interval.properties,
+          mi_expected_properties);
+    }), test_desc);
+</script>
diff --git a/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.html b/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.html
new file mode 100644
index 0000000..02aaecc
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+                                  'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+    .then(() => characteristic.getDescriptor(user_description.name))
+    .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+    .then(() => fake_characteristic.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+      characteristic.getDescriptor(user_description.name), expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.html b/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.html
new file mode 100644
index 0000000..52b39c1
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.html
@@ -0,0 +1,39 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getDescriptor should return the same object.';
+let characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic} = _))
+    .then(() => Promise.all([
+      characteristic.getDescriptor(user_description.alias),
+      characteristic.getDescriptor(user_description.name),
+      characteristic.getDescriptor(user_description.uuid)
+    ]))
+    .then(descriptors_arrays => {
+      assert_true(descriptors_arrays.length > 0)
+
+      // Convert to arrays if necessary.
+      for (let i = 0; i < descriptors_arrays.length; i++) {
+        descriptors_arrays[i] = [].concat(descriptors_arrays[i]);
+      }
+
+      for (let i = 1; i < descriptors_arrays.length; i++) {
+        assert_equals(descriptors_arrays[0].length,
+            descriptors_arrays[i].length);
+      }
+
+      let base_set = new Set(descriptors_arrays[0]);
+      for (let descriptors of descriptors_arrays) {
+        descriptors.forEach(descriptor => assert_true(base_set.has(descriptor)));
+      }
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.html b/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.html
new file mode 100644
index 0000000..bfb4c8d
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.html
@@ -0,0 +1,27 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+// TODO(https://crbug.com/672127) Use this test case to test the rest of
+// characteristic functions.
+'use strict';
+const test_desc = 'Service is removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.getDescriptor(user_description.name),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.html b/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.html
new file mode 100644
index 0000000..c00d4cf
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+                                  'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+    .then(() => characteristic.getDescriptor(user_description.name))
+    .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+    .then(() => fake_characteristic.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+      characteristic.getDescriptors(user_description.name), expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.html b/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.html
new file mode 100644
index 0000000..a29548f
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+                                  'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+    .then(() => characteristic.getDescriptor(user_description.name))
+    .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+    .then(() => fake_characteristic.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+      characteristic.getDescriptors(), expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.html b/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.html
new file mode 100644
index 0000000..20df1d0
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.html
@@ -0,0 +1,39 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getDescriptors should return the same object.';
+let characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic} = _))
+    .then(() => Promise.all([
+      characteristic.getDescriptors(user_description.alias),
+      characteristic.getDescriptors(user_description.name),
+      characteristic.getDescriptors(user_description.uuid)
+    ]))
+    .then(descriptors_arrays => {
+      assert_true(descriptors_arrays.length > 0)
+
+      // Convert to arrays if necessary.
+      for (let i = 0; i < descriptors_arrays.length; i++) {
+        descriptors_arrays[i] = [].concat(descriptors_arrays[i]);
+      }
+
+      for (let i = 1; i < descriptors_arrays.length; i++) {
+        assert_equals(descriptors_arrays[0].length,
+            descriptors_arrays[i].length);
+      }
+
+      let base_set = new Set(descriptors_arrays[0]);
+      for (let descriptors of descriptors_arrays) {
+        descriptors.forEach(descriptor => assert_true(base_set.has(descriptor)));
+      }
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.html b/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.html
new file mode 100644
index 0000000..de83400
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.html
@@ -0,0 +1,27 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+// TODO(https://crbug.com/672127) Use this test case to test the rest of
+// characteristic functions.
+'use strict';
+const test_desc = 'Service is removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.getDescriptors(user_description.uuid),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.html b/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.html
new file mode 100644
index 0000000..c9056dd
--- /dev/null
+++ b/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.html
@@ -0,0 +1,27 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+// TODO(https://crbug.com/672127) Use this test case to test the rest of
+// characteristic functions.
+'use strict';
+const test_desc = 'Service is removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.getDescriptors(user_description.name),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/notifications/characteristic-is-removed.https.html b/bluetooth/characteristic/notifications/characteristic-is-removed.https.html
new file mode 100644
index 0000000..35f5ee9
--- /dev/null
+++ b/bluetooth/characteristic/notifications/characteristic-is-removed.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic is removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() => fake_characteristic.remove())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.startNotifications(),
+        expected,
+        'Characteristic got removed.')),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/notifications/service-is-removed.https.html b/bluetooth/characteristic/notifications/service-is-removed.https.html
new file mode 100644
index 0000000..bf389ce
--- /dev/null
+++ b/bluetooth/characteristic/notifications/service-is-removed.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service is removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.startNotifications(),
+        expected,
+        'Service got removed.')),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.html b/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.html
new file mode 100644
index 0000000..c0d4328
--- /dev/null
+++ b/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Add multiple event listeners then readValue().';
+let characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() => fake_characteristic.setNextReadResponse(GATT_SUCCESS, [0,1,2]))
+    .then(() => assert_promise_resolves_after_event(
+        characteristic,
+        'readValue',
+        'characteristicvaluechanged',
+        3 /* attach 3 listeners */))
+    .then(results => {
+      let read_value = new Uint8Array(results[0].buffer);
+      let event_values = results.slice(1).map(v => new Uint8Array(v.buffer));
+      for (let event_value of event_values) {
+        assert_equals(event_value.buffer, read_value.buffer);
+        assert_array_equals(event_value, read_value);
+      }
+    }), test_desc);
+</script>
diff --git a/bluetooth/characteristic/readValue/characteristic-is-removed.https.html b/bluetooth/characteristic/readValue/characteristic-is-removed.https.html
new file mode 100644
index 0000000..3c963c3
--- /dev/null
+++ b/bluetooth/characteristic/readValue/characteristic-is-removed.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() => fake_characteristic.remove())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.readValue(),
+        expected,
+        'Characteristic got removed.')),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/readValue/event-is-fired.https.html b/bluetooth/characteristic/readValue/event-is-fired.https.html
new file mode 100644
index 0000000..a217bdd
--- /dev/null
+++ b/bluetooth/characteristic/readValue/event-is-fired.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Reading a characteristic should fire an event.';
+let characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() => fake_characteristic.setNextReadResponse(
+          GATT_SUCCESS, [0, 1, 2]))
+    .then(() => assert_promise_resolves_after_event(
+         characteristic,
+         'readValue',
+         'characteristicvaluechanged'))
+    .then(results => new Promise(resolve => {
+        let read_value = new Uint8Array(results[0].buffer);
+        let event_value = new Uint8Array(results[1].buffer);
+        assert_equals(event_value.buffer, read_value.buffer);
+        assert_array_equals(event_value, read_value);
+        resolve();
+    })), test_desc);
+</script>
diff --git a/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.html b/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.html
new file mode 100644
index 0000000..a80bccf
--- /dev/null
+++ b/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+                                  'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+    .then(() => characteristic.getDescriptor(user_description.name))
+    .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+    .then(() => fake_characteristic.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+      characteristic.readValue(), expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/readValue/read-succeeds.https.html b/bluetooth/characteristic/readValue/read-succeeds.https.html
new file mode 100644
index 0000000..4dca3ed
--- /dev/null
+++ b/bluetooth/characteristic/readValue/read-succeeds.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A read request succeeds and returns the characteristic\'s ' +
+    'value.';
+const EXPECTED_VALUE = [0, 1, 2];
+
+let characteristic, fake_characteristic;
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() =>  fake_characteristic.setNextReadResponse(
+        GATT_SUCCESS,
+        EXPECTED_VALUE))
+    .then(() => characteristic.readValue())
+    .then(value => assert_array_equals(
+        new Uint8Array(value.buffer),
+        EXPECTED_VALUE)),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/readValue/read-updates-value.https.html b/bluetooth/characteristic/readValue/read-updates-value.https.html
new file mode 100644
index 0000000..11bb939
--- /dev/null
+++ b/bluetooth/characteristic/readValue/read-updates-value.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Succesful read should update characteristic.value';
+const EXPECTED_VALUE = [0, 1, 2];
+let characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() => assert_equals(characteristic.value, null))
+    .then(() => fake_characteristic.setNextReadResponse(
+          GATT_SUCCESS, EXPECTED_VALUE))
+    .then(() => characteristic.readValue())
+    .then(() => assert_array_equals(
+          new Uint8Array(characteristic.value.buffer),
+          EXPECTED_VALUE)),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/readValue/service-is-removed.https.html b/bluetooth/characteristic/readValue/service-is-removed.https.html
new file mode 100644
index 0000000..48f8127
--- /dev/null
+++ b/bluetooth/characteristic/readValue/service-is-removed.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.readValue(),
+        expected,
+        'Service got removed.')),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/service-same-from-2-characteristics.https.html b/bluetooth/characteristic/service-same-from-2-characteristics.https.html
new file mode 100644
index 0000000..aa156b2
--- /dev/null
+++ b/bluetooth/characteristic/service-same-from-2-characteristics.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Same parent service returned from multiple characteristics.';
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => Promise.all([
+      service.getCharacteristic('measurement_interval'),
+      service.getCharacteristic('temperature_measurement')
+    ]))
+    .then(characteristics =>
+        assert_equals(characteristics[0].service, characteristics[1].service)),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/service-same-object.https.html b/bluetooth/characteristic/service-same-object.https.html
new file mode 100644
index 0000000..ee96121
--- /dev/null
+++ b/bluetooth/characteristic/service-same-object.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = '[SameObject] test for BluetoothRemoteGATTCharacteristic ' +
+  'service.';
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(({characteristic}) =>
+        assert_equals(characteristic.service, characteristic.service)),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.html b/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.html
new file mode 100644
index 0000000..d9a9594
--- /dev/null
+++ b/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+                                  'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+    .then(() => characteristic.getDescriptor(user_description.name))
+    .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+    .then(() => fake_characteristic.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+      characteristic.startNotifications(), expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/writeValue/characteristic-is-removed.https.html b/bluetooth/characteristic/writeValue/characteristic-is-removed.https.html
new file mode 100644
index 0000000..f79c219
--- /dev/null
+++ b/bluetooth/characteristic/writeValue/characteristic-is-removed.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() => fake_characteristic.remove())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.writeValue(new ArrayBuffer(1 /* length */)),
+        expected,
+        'Characteristic got removed.')),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.html b/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.html
new file mode 100644
index 0000000..1652070
--- /dev/null
+++ b/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+                                  'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+    .then(() => characteristic.getDescriptor(user_description.name))
+    .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+    .then(() => fake_characteristic.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+      characteristic.writeValue(new Uint8Array(1)), expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/characteristic/writeValue/service-is-removed.https.html b/bluetooth/characteristic/writeValue/service-is-removed.https.html
new file mode 100644
index 0000000..2b43bfb
--- /dev/null
+++ b/bluetooth/characteristic/writeValue/service-is-removed.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.writeValue(new ArrayBuffer(1 /* length */)),
+        expected,
+        'Service got removed.')),
+    test_desc);
+</script>
diff --git a/bluetooth/characteristic/writeValue/write-succeeds.https.html b/bluetooth/characteristic/writeValue/write-succeeds.https.html
new file mode 100644
index 0000000..f2cf577
--- /dev/null
+++ b/bluetooth/characteristic/writeValue/write-succeeds.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A regular write request to a writable characteristic ' +
+    'should succeed.';
+let typed_array = Uint8Array.of(1, 2);
+let array_buffer = Uint8Array.of(3, 4).buffer;
+let data_view = new DataView(new ArrayBuffer(2));
+let characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_characteristic} = _))
+    .then(() => new Promise(resolve => {
+        data_view.setUint8(0, 5);
+        data_view.setUint8(1, 6);
+        resolve();
+    }))
+    .then(() => fake_characteristic.getLastWrittenValue())
+    .then(last_value => assert_true(last_value === null))
+
+    .then(() => fake_characteristic.setNextWriteResponse(GATT_SUCCESS))
+    .then(() => characteristic.writeValue(typed_array))
+    .then(() => fake_characteristic.getLastWrittenValue())
+    .then(last_value => assert_array_equals(last_value, [1, 2]))
+
+    .then(() => fake_characteristic.setNextWriteResponse(GATT_SUCCESS))
+    .then(() => characteristic.writeValue(array_buffer))
+    .then(() => fake_characteristic.getLastWrittenValue())
+    .then(last_value => assert_array_equals(last_value, [3, 4]))
+
+    .then(() => fake_characteristic.setNextWriteResponse(GATT_SUCCESS))
+    .then(() => characteristic.writeValue(data_view))
+    .then(() => fake_characteristic.getLastWrittenValue())
+    .then(last_value => assert_array_equals(last_value, [5, 6])),
+    test_desc);
+</script>
diff --git a/bluetooth/descriptor/readValue/gen-service-is-removed.https.html b/bluetooth/descriptor/readValue/gen-service-is-removed.https.html
new file mode 100644
index 0000000..e453c80
--- /dev/null
+++ b/bluetooth/descriptor/readValue/gen-service-is-removed.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let descriptor, fake_peripheral, fake_service;
+
+bluetooth_test(() => getUserDescriptionDescriptor()
+    .then(_ => ({descriptor, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        descriptor.readValue(),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/descriptor/readValue/read-succeeds.https.html b/bluetooth/descriptor/readValue/read-succeeds.https.html
new file mode 100644
index 0000000..35ff057
--- /dev/null
+++ b/bluetooth/descriptor/readValue/read-succeeds.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A read request succeeds and returns the descriptor\'s value.';
+const EXPECTED_VALUE = [0, 1, 2];
+let descriptor, fake_descriptor;
+
+bluetooth_test(() => getUserDescriptionDescriptor()
+    .then(_ => ({descriptor, fake_descriptor} = _))
+    .then(() =>
+        fake_descriptor.setNextReadResponse(GATT_SUCCESS, EXPECTED_VALUE))
+    .then(() => descriptor.readValue())
+    .then(value => assert_array_equals(Array.from(new Uint8Array(
+        value.buffer)), EXPECTED_VALUE)),
+    test_desc);
+</script>
diff --git a/bluetooth/descriptor/writeValue/gen-service-is-removed.https.html b/bluetooth/descriptor/writeValue/gen-service-is-removed.https.html
new file mode 100644
index 0000000..acd56c3
--- /dev/null
+++ b/bluetooth/descriptor/writeValue/gen-service-is-removed.https.html
@@ -0,0 +1,25 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let descriptor, fake_peripheral, fake_service;
+
+bluetooth_test(() => getUserDescriptionDescriptor()
+    .then(_ => ({descriptor, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        descriptor.writeValue(new ArrayBuffer(1 /* length */)),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/device/gattserverdisconnected-event/disconnected.https.html b/bluetooth/device/gattserverdisconnected-event/disconnected.https.html
new file mode 100644
index 0000000..c406e50
--- /dev/null
+++ b/bluetooth/device/gattserverdisconnected-event/disconnected.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A device disconnecting while connected should fire the ' +
+    'gattserverdisconnected event.';
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+  .then(({device, fake_peripheral}) => {
+    fake_peripheral.simulateGATTDisconnection();
+    return eventPromise(device, 'gattserverdisconnected');
+  })
+  .then(e => assert_true(e.bubbles)),
+  test_desc);
+</script>
diff --git a/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.html b/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.html
new file mode 100644
index 0000000..8208231
--- /dev/null
+++ b/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A device disconnecting after the BluetoothDevice object ' +
+    'has been GC\'ed should not access freed memory.';
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+  .then(({fake_peripheral}) => {
+    // 1. Disconnect.
+    fake_peripheral.simulateGATTDisconnection();
+    // 2. Run garbage collection.
+    fake_peripheral = undefined;
+    runGarbageCollection();
+  })
+  // 3. Wait 50ms after the GC runs for the disconnection event to come back.
+  // There's nothing to assert other than that only valid memory is used.
+  .then(() => new Promise(resolve => step_timeout(resolve, 50))),
+  test_desc);
+</script>
diff --git a/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.html b/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.html
new file mode 100644
index 0000000..92be9c3
--- /dev/null
+++ b/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'If a site disconnects from a device while the platform is ' +
+    'disconnecting that device, only one gattserverdisconnected event should ' +
+    'fire.';
+let device, fake_peripheral;
+let num_events = 0;
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+  .then(_ => ({device, fake_peripheral} = _))
+  // 1. Listen for disconnections.
+  .then(() =>
+      device.addEventListener('gattserverdisconnected', () => num_events++))
+  // 2. Disconnect several times.
+  .then(() => Promise.all([
+    eventPromise(device, 'gattserverdisconnected'),
+    fake_peripheral.simulateGATTDisconnection(),
+    device.gatt.disconnect(),
+    device.gatt.disconnect(),
+  ]))
+  // 3. Wait to catch disconnect events.
+  .then(() => new Promise(resolve => step_timeout(resolve, 50)))
+  // 4. Ensure there is exactly 1 disconnection recorded.
+  .then(() => assert_equals(num_events, 1)),
+  test_desc);
+</script>
diff --git a/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.html b/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.html
new file mode 100644
index 0000000..6d0c8e2
--- /dev/null
+++ b/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+let test_desc = 'A device that reconnects during the gattserverdisconnected ' +
+    'event should still receive gattserverdisconnected events after ' +
+    're-connection.';
+let device, fake_peripheral;
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+  .then(_ => ({device, fake_peripheral} = _))
+  // 1. Disconnect.
+  .then(() => new Promise(resolve => {
+    fake_peripheral.simulateGATTDisconnection();
+    device.addEventListener(
+        'gattserverdisconnected', function onDisconnected() {
+      device.removeEventListener('gattserverdisconnected', onDisconnected);
+      // 2. Reconnect.
+      fake_peripheral.setNextGATTConnectionResponse({
+        code: HCI_SUCCESS,
+      })
+        .then(() => device.gatt.connect())
+        .then(() => resolve());
+    });
+  }))
+  // 3. Disconnect after reconnecting.
+  .then(() => {
+    fake_peripheral.simulateGATTDisconnection();
+    return eventPromise(device, 'gattserverdisconnected')
+  }), test_desc);
+</script>
diff --git a/bluetooth/generate.py b/bluetooth/generate.py
new file mode 100644
index 0000000..034ca22
--- /dev/null
+++ b/bluetooth/generate.py
@@ -0,0 +1,189 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# TODO(509038): Delete the file in LayoutTests/bluetooth after all the script
+# tests have been migrated to this directory.
+"""Generator script for Web Bluetooth LayoutTests.
+
+For each script-tests/X.js creates the following test files depending on the
+contents of X.js
+- getPrimaryService/X.html
+- getPrimaryServices/X.html
+- getPrimaryServices/X-with-uuid.html
+
+script-tests/X.js files should contain "CALLS([variation1 | variation2 | ...])"
+tokens that indicate what files to generate. Each variation in CALLS([...])
+should corresponds to a js function call and its arguments. Additionally a
+variation can end in [UUID] to indicate that the generated file's name should
+have the -with-uuid suffix.
+
+The PREVIOUS_CALL token will be replaced with the function that replaced CALLS.
+
+The FUNCTION_NAME token will be replaced with the name of the function that
+replaced CALLS.
+
+For example, for the following template file:
+
+// script-tests/example.js
+promise_test(() => {
+    return navigator.bluetooth.requestDevice(...)
+        .then(device => device.gatt.CALLS([
+            getPrimaryService('heart_rate')|
+            getPrimaryServices('heart_rate')[UUID]]))
+        .then(device => device.gatt.PREVIOUS_CALL);
+}, 'example test for FUNCTION_NAME');
+
+this script will generate:
+
+// getPrimaryService/example.html
+promise_test(() => {
+    return navigator.bluetooth.requestDevice(...)
+        .then(device => device.gatt.getPrimaryService('heart_rate'))
+        .then(device => device.gatt.getPrimaryService('heart_rate'));
+}, 'example test for getPrimaryService');
+
+// getPrimaryServices/example-with-uuid.html
+promise_test(() => {
+    return navigator.bluetooth.requestDevice(...)
+        .then(device => device.gatt.getPrimaryServices('heart_rate'))
+        .then(device => device.gatt.getPrimaryServices('heart_rate'));
+}, 'example test for getPrimaryServices');
+
+Run
+$ python //third_party/WebKit/LayoutTests/bluetooth/generate.py
+and commit the generated files.
+"""
+
+import fnmatch
+import os
+import re
+import sys
+import logging
+
+TEMPLATES_DIR = 'script-tests'
+
+
+class GeneratedTest:
+
+    def __init__(self, data, path, template):
+        self.data = data
+        self.path = path
+        self.template = template
+
+
+def GetGeneratedTests():
+    """Yields a GeneratedTest for each call in templates in script-tests."""
+    bluetooth_tests_dir = os.path.dirname(os.path.realpath(__file__))
+
+    # Read Base Test Template.
+    base_template_file_handle = open(
+        os.path.join(
+            bluetooth_tests_dir,
+            TEMPLATES_DIR,
+            'base_test_html.template'
+        ), 'r')
+    base_template_file_data = base_template_file_handle.read().decode('utf-8')
+    base_template_file_handle.close()
+
+    # Get Templates.
+
+    template_path = os.path.join(bluetooth_tests_dir, TEMPLATES_DIR)
+
+    available_templates = []
+    for root, _, files in os.walk(template_path):
+        for template in files:
+            if template.endswith('.js'):
+                available_templates.append(os.path.join(root, template))
+
+    # Generate Test Files
+    for template in available_templates:
+        # Read template
+        template_file_handle = open(template, 'r')
+        template_file_data = template_file_handle.read().decode('utf-8')
+        template_file_handle.close()
+
+        template_name = os.path.splitext(os.path.basename(template))[0]
+
+        # Find function names in multiline pattern: CALLS( [ function_name,function_name2[UUID] ])
+        result = re.search(
+            r'CALLS\(' + # CALLS(
+            r'[^\[]*' +  # Any characters not [, allowing for new lines.
+            r'\[' +      # [
+            r'(.*?)' +   # group matching: function_name(), function_name2[UUID]
+            r'\]\)',     # adjacent closing characters: ])
+            template_file_data, re.MULTILINE | re.DOTALL)
+
+        if result is None:
+            raise Exception('Template must contain \'CALLS\' tokens')
+
+        new_test_file_data = base_template_file_data.replace('TEST',
+            template_file_data)
+        # Replace CALLS([...]) with CALLS so that we don't have to replace the
+        # CALLS([...]) for every new test file.
+        new_test_file_data = new_test_file_data.replace(result.group(), 'CALLS')
+
+        # Replace 'PREVIOUS_CALL' with 'CALLS' so that we can replace it while
+        # replacing CALLS.
+        new_test_file_data = new_test_file_data.replace('PREVIOUS_CALL', 'CALLS')
+
+        for call in result.group(1).split('|'):
+            # Parse call
+            call = call.strip()
+            function_name, args, uuid_suffix = re.search(r'(.*?)\((.*)\)(\[UUID\])?', call).groups()
+
+            # Replace template tokens
+            call_test_file_data = new_test_file_data
+            call_test_file_data = call_test_file_data.replace('CALLS', '{}({})'.format(function_name, args))
+            call_test_file_data = call_test_file_data.replace('FUNCTION_NAME', function_name)
+
+            # Get test file name
+            group_dir = os.path.basename(os.path.abspath(os.path.join(template, os.pardir)))
+
+            call_test_file_name = 'gen-{}{}.https.html'.format(template_name, '-with-uuid' if uuid_suffix else '')
+            call_test_file_path = os.path.join(bluetooth_tests_dir, group_dir, function_name, call_test_file_name)
+
+            yield GeneratedTest(call_test_file_data, call_test_file_path, template)
+
+def main():
+    logging.basicConfig(level=logging.INFO)
+    previous_generated_files = set()
+    current_path = os.path.dirname(os.path.realpath(__file__))
+    for root, _, filenames in os.walk(current_path):
+        for filename in fnmatch.filter(filenames, 'gen-*.https.html'):
+            previous_generated_files.add(os.path.join(root, filename))
+
+    generated_files = set()
+    for generated_test in GetGeneratedTests():
+        prev_len = len(generated_files)
+        generated_files.add(generated_test.path)
+        if prev_len == len(generated_files):
+            logging.info('Generated the same test twice for template:\n%s',
+                       generated_test.template)
+
+        # Create or open test file
+        directory = os.path.dirname(generated_test.path)
+        if not os.path.exists(directory):
+            os.makedirs(directory)
+        test_file_handle = open(generated_test.path, 'wb')
+
+        # Write contents
+        test_file_handle.write(generated_test.data.encode('utf-8'))
+        test_file_handle.close()
+
+    new_generated_files = generated_files - previous_generated_files
+    if len(new_generated_files) != 0:
+        logging.info('Newly generated tests:')
+        for generated_file in new_generated_files:
+              logging.info(generated_file)
+
+    obsolete_files = previous_generated_files - generated_files
+    if len(obsolete_files) != 0:
+        logging.warning('The following files might be obsolete:')
+        for generated_file in obsolete_files:
+            logging.warning(generated_file)
+
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/bluetooth/generate_test.py b/bluetooth/generate_test.py
new file mode 100755
index 0000000..881f7db
--- /dev/null
+++ b/bluetooth/generate_test.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# TODO(50903): Delete the file in LayoutTests/bluetooth after all the tests have
+# been migrated to this directory.
+"""Test that the set of gen-* files is the same as the generated files."""
+
+import fnmatch
+import os
+import sys
+import generate
+import logging
+
+UPDATE_TIP = 'To update the generated tests, run:\n' \
+             '$ python third_party/WebKit/LayoutTests/bluetooth/generate.py'
+
+
+def main():
+  logging.basicConfig(level=logging.INFO)
+  logging.info(UPDATE_TIP)
+  generated_files = set()
+  # Tests data in gen-* files is the same as the data generated.
+  for generated_test in generate.GetGeneratedTests():
+    generated_files.add(generated_test.path)
+    try:
+      with open(generated_test.path, 'r') as f:
+        data = f.read().decode('utf-8')
+        if data != generated_test.data:
+          logging.error('%s does not match template', generated_test.path)
+          return -1
+    except IOError, e:
+      if e.errno == 2:
+        logging.error('Missing generated test:\n%s\nFor template:\n%s',
+                     generated_test.path,
+                     generated_test.template)
+        return -1
+
+  # Tests that there are no obsolete generated files.
+  previous_generated_files = set()
+  current_path = os.path.dirname(os.path.realpath(__file__))
+  for root, _, filenames in os.walk(current_path):
+    for filename in fnmatch.filter(filenames, 'gen-*.https.html'):
+      previous_generated_files.add(os.path.join(root, filename))
+
+  if previous_generated_files != generated_files:
+    logging.error('There are extra generated tests. Please remove them.')
+    for test_path in previous_generated_files - generated_files:
+      logging.error('%s', test_path)
+    return -1
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/bluetooth/idl-Bluetooth.html b/bluetooth/idl-Bluetooth.html
deleted file mode 100644
index b0263f9..0000000
--- a/bluetooth/idl-Bluetooth.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<title>Bluetooth interface</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-test(() => {
-  assert_throws(new TypeError(), () => new Bluetooth(),
-                'the constructor should not be callable with "new"');
-  assert_throws(new TypeError(), () => Bluetooth(),
-                'the constructor should not be callable');
-
-  // Bluetooth implements BluetoothDiscovery;
-  assert_true('requestDevice' in navigator.bluetooth);
-  assert_equals(navigator.bluetooth.requestDevice.length, 0);
-}, 'Bluetooth IDL test');
-
-</script>
diff --git a/bluetooth/idl/idl-Bluetooth.https.html b/bluetooth/idl/idl-Bluetooth.https.html
new file mode 100644
index 0000000..2835236
--- /dev/null
+++ b/bluetooth/idl/idl-Bluetooth.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Bluetooth interface</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+const test_desc = 'Bluetooth IDL test';
+
+test(() => {
+  assert_throws(new TypeError(), () => new Bluetooth(),
+                'the constructor should not be callable with "new"');
+  assert_throws(new TypeError(), () => Bluetooth(),
+                'the constructor should not be callable');
+
+  // Bluetooth implements BluetoothDiscovery;
+  assert_true('requestDevice' in navigator.bluetooth);
+  assert_equals(navigator.bluetooth.requestDevice.length, 0);
+}, test_desc);
+</script>
diff --git a/bluetooth/idl/idl-BluetoothDevice.https.html b/bluetooth/idl/idl-BluetoothDevice.https.html
new file mode 100644
index 0000000..631d9dd
--- /dev/null
+++ b/bluetooth/idl/idl-BluetoothDevice.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc_idl = 'BluetoothDevice IDL test.';
+
+test(() => {
+  assert_throws(new TypeError(), () => new BluetoothDevice(),
+      'the constructor should not be callable with "new"');
+  assert_throws(new TypeError(), () => BluetoothDevice(),
+      'the constructor should not be callable');
+}, test_desc_idl);
+
+const test_desc_attr = 'BluetoothDevice attributes.';
+let device;
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+  .then(({device}) => {
+    assert_equals(device.constructor.name, 'BluetoothDevice');
+    var old_device_id = device.id;
+    assert_throws(new TypeError(), () => device.id = 'overwritten',
+        'the device id should not be writable');
+    assert_throws(new TypeError(), () => device.name = 'overwritten',
+        'the device name should not be writable');
+    assert_equals(device.id, old_device_id);
+    assert_equals(device.name, 'Health Thermometer');
+  }), test_desc_attr);
+</script>
diff --git a/bluetooth/idl/idl-BluetoothUUID.html b/bluetooth/idl/idl-BluetoothUUID.html
new file mode 100644
index 0000000..efebb15
--- /dev/null
+++ b/bluetooth/idl/idl-BluetoothUUID.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict'
+
+var base_uuid = '00000000-0000-1000-8000-00805f9b34fb'
+
+test(() => {
+  let base_alias = 0x0
+  assert_equals(BluetoothUUID.getService(base_alias), base_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(base_alias), base_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(base_alias), base_uuid);
+}, '0x0 should produce valid UUID.');
+
+test(() => {
+  assert_equals(BluetoothUUID.getService(NaN), base_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(NaN), base_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(NaN), base_uuid);
+}, 'NaN returns basic uuid');
+
+test(() => {
+  let max_uuid =  'ffffffff-0000-1000-8000-00805f9b34fb';
+  let nine_digits =     0xfffffffff;
+  let thirteen_digits = 0xfffffffffffff;
+  let fourteen_digits = 0xffffffffffffff;
+  assert_equals(BluetoothUUID.getService(nine_digits), max_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(nine_digits), max_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(nine_digits), max_uuid);
+  assert_equals(BluetoothUUID.getService(thirteen_digits), max_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(thirteen_digits), max_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(thirteen_digits), max_uuid);
+  assert_equals(BluetoothUUID.getService(fourteen_digits), base_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(fourteen_digits), base_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(fourteen_digits), base_uuid);
+}, 'Values between 0xfffffffff (8 digits) and 0xffffffffffffff (14 digits)' +
+   'should return max UUID');
+
+test(() => {
+  assert_equals(BluetoothUUID.getService(Infinity), base_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(Infinity), base_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(Infinity), base_uuid);
+}, 'Infinity returns base UUID');
+
+test(() => {
+  let deadbeef_alias = 0xDEADBEEF;
+  let deadbeef_uuid = 'deadbeef-0000-1000-8000-00805f9b34fb';
+  assert_equals(BluetoothUUID.getService(deadbeef_alias), deadbeef_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(deadbeef_alias), deadbeef_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(deadbeef_alias), deadbeef_uuid);
+}, '0xdeadbeef should produce valid UUID.');
+
+test(() => {
+  let adeadbeef_alias = 0xADEADBEEF;
+  let adeadbeef_uuid = 'deadbeef-0000-1000-8000-00805f9b34fb';
+  assert_equals(BluetoothUUID.getService(adeadbeef_alias), adeadbeef_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(adeadbeef_alias), adeadbeef_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(adeadbeef_alias), adeadbeef_uuid);
+}, 'Only first 32bits should be used.');
+
+test(() => {
+  let basic_uuid = '1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d';
+  assert_equals(BluetoothUUID.getService(basic_uuid), basic_uuid);
+  assert_equals(BluetoothUUID.getCharacteristic(basic_uuid), basic_uuid);
+  assert_equals(BluetoothUUID.getDescriptor(basic_uuid), basic_uuid);
+}, 'A valid UUID String should return the same UUID.');
+
+test(() => {
+  let all_caps_uuid = '1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D';
+  assert_throws(TypeError(), () => BluetoothUUID.getService(all_caps_uuid));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(all_caps_uuid));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(all_caps_uuid));
+}, 'A UUID String with uppercase letters is an invalid UUID.');
+
+test(() => {
+  let string_alias = 'deadbeef';
+  assert_throws(TypeError(), () => BluetoothUUID.getService(string_alias));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(string_alias));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(string_alias));
+}, 'A 32bit *String* alias is invalid.');
+
+test(() => {
+  let invalid_character_uuid = '0000000g-0000-1000-8000-00805f9b34fb';
+  assert_throws(TypeError(), () => BluetoothUUID.getService(invalid_character_uuid));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(invalid_character_uuid));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(invalid_character_uuid));
+}, 'A UUID with invalid characters is an invalid UUID.');
+
+test(() => {
+  assert_equals(BluetoothUUID.getService('alert_notification'),
+                '00001811-0000-1000-8000-00805f9b34fb');
+  assert_equals(BluetoothUUID.getCharacteristic('aerobic_heart_rate_lower_limit'),
+                '00002a7e-0000-1000-8000-00805f9b34fb');
+  assert_equals(BluetoothUUID.getDescriptor('gatt.characteristic_extended_properties'),
+                '00002900-0000-1000-8000-00805f9b34fb');
+}, 'A valid UUID from a name.');
+
+test(() => {
+  assert_throws(TypeError(), () => {
+    BluetoothUUID.getService('aerobic_heart_rate_lower_limit');
+  });
+  assert_throws(TypeError(), () => {
+    BluetoothUUID.getService('gatt.characteristic_extended_properties');
+  });
+  assert_throws(TypeError(), () => {
+    BluetoothUUID.getCharacteristic('alert_notification');
+  });
+  assert_throws(TypeError(), () => {
+    BluetoothUUID.getCharacteristic('gatt.characteristic_extended_properties');
+  });
+  assert_throws(TypeError(), () => {
+    BluetoothUUID.getDescriptor('alert_notification');
+  });
+  assert_throws(TypeError(), () => {
+    BluetoothUUID.getDescriptor('aerobic_heart_rate_lower_limit');
+  });
+}, 'Make sure attributes don\'t share a map');
+
+test(() => {
+  let wrong_name = 'wrong_name';
+  assert_throws(TypeError(), () => BluetoothUUID.getService(wrong_name));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(wrong_name));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(wrong_name));
+}, 'Invalid Descriptor name');
+
+test(() => {
+  let object = {};
+  let array = [];
+  let func = () => {};
+
+  // cannonicalUUID
+  assert_throws(new TypeError, () => BluetoothUUID.canonicalUUID(object));
+  // [] converts to '', which converts to 0 before the range check.
+  assert_equals(BluetoothUUID.canonicalUUID(array), base_uuid);
+  assert_throws(new TypeError, () => BluetoothUUID.canonicalUUID(func));
+  assert_throws(new TypeError, () => BluetoothUUID.canonicalUUID(undefined));
+  assert_equals(BluetoothUUID.canonicalUUID(null), base_uuid);
+  assert_equals(BluetoothUUID.canonicalUUID(false), base_uuid);
+  assert_equals(BluetoothUUID.canonicalUUID(true), BluetoothUUID.canonicalUUID(1));
+  assert_throws(new TypeError, () => BluetoothUUID.canonicalUUID(NaN));
+
+  // getService
+  assert_throws(TypeError(), () => BluetoothUUID.getService(object));
+  assert_throws(TypeError(), () => BluetoothUUID.getService(array));
+  assert_throws(TypeError(), () => BluetoothUUID.getService(func));
+  assert_throws(TypeError(), () => BluetoothUUID.getService(undefined));
+  assert_throws(TypeError(), () => BluetoothUUID.getService(null));
+  assert_throws(TypeError(), () => BluetoothUUID.getService(false));
+
+  // getCharacteristic
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(object));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(array));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(func));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(undefined));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(null));
+  assert_throws(TypeError(), () => BluetoothUUID.getCharacteristic(false));
+
+  // getDescriptor
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(object));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(array));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(func));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(undefined));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(null));
+  assert_throws(TypeError(), () => BluetoothUUID.getDescriptor(false));
+}, 'Non-number and non-strings');
+</script>
diff --git a/bluetooth/idl/idl-NavigatorBluetooth.html b/bluetooth/idl/idl-NavigatorBluetooth.html
new file mode 100644
index 0000000..b8649f1
--- /dev/null
+++ b/bluetooth/idl/idl-NavigatorBluetooth.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+const test_desc = '[SameObject] test for navigator.bluetooth';
+
+test(() => {
+  assert_true('bluetooth' in navigator,
+              'navigator.bluetooth exists.');
+}, 'navigator.bluetooth IDL test');
+
+test(() => {
+  assert_equals(navigator.bluetooth, navigator.bluetooth);
+}, test_desc);
+</script>
diff --git a/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.html b/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.html
new file mode 100644
index 0000000..033570d
--- /dev/null
+++ b/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Device with empty name and no UUIDs nearby. Should be ' +
+    'found if acceptAllDevices is true.';
+
+bluetooth_test(() => setUpPreconnectedDevice({name: ''})
+    .then(() => requestDeviceWithTrustedClick({acceptAllDevices: true}))
+    .then(device => assert_equals(device.name, '')),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.html b/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.html
new file mode 100644
index 0000000..d990dbf
--- /dev/null
+++ b/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+const test_desc = 'A device with name and no UUIDs nearby. Should be found if ' +
+   'acceptAllDevices is true.';
+const name = 'LE Device';
+
+bluetooth_test(() => setUpPreconnectedDevice({name: name})
+    .then(() => requestDeviceWithTrustedClick({acceptAllDevices: true}))
+    .then(device => assert_equals(device.name, name)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.html b/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.html
new file mode 100644
index 0000000..bd9e586
--- /dev/null
+++ b/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'requestDevice called with acceptAllDevices: true and ' +
+    'with no optionalServices. Should not get access to any services.';
+const expected = new DOMException(
+    'Origin is not allowed to access any service. ' +
+    'Tip: Add the service UUID to \'optionalServices\' in ' +
+    'requestDevice() options. https://goo.gl/HxfxSQ',
+    'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true})
+    .then(({device}) => assert_promise_rejects_with_message(
+      device.gatt.getPrimaryServices(),
+      expected)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.html b/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.html
new file mode 100644
index 0000000..3c0f4c1
--- /dev/null
+++ b/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'requestDevice called with acceptAllDevices: true and with ' +
+   'optionalServices. Should get access to services.';
+
+bluetooth_test(() => getTwoHealthThermometerServicesDevice()
+    .then(() => requestDeviceWithTrustedClick({
+      acceptAllDevices: true,
+      optionalServices: ['health_thermometer']
+    }))
+    .then(device => device.gatt.connect())
+    .then(gattServer => gattServer.getPrimaryServices())
+    .then(services => {
+      assert_equals(services.length, 2);
+      services.forEach(service => {
+        assert_equals(service.uuid,
+          BluetoothUUID.getService('health_thermometer'));
+      });
+    }), test_desc);
+</script>
diff --git a/bluetooth/requestDevice/blocklisted-service-in-filter.https.html b/bluetooth/requestDevice/blocklisted-service-in-filter.https.html
new file mode 100644
index 0000000..253e311
--- /dev/null
+++ b/bluetooth/requestDevice/blocklisted-service-in-filter.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Reject with SecurityError if requesting a blocklisted ' +
+    'service.';
+const expected = new DOMException(
+    'requestDevice() called with a filter containing a blocklisted UUID. ' +
+    'https://goo.gl/4NeimX',
+    'SecurityError');
+
+bluetooth_test(() => setUpPreconnectedDevice({
+  knownServiceUUIDs: ['human_interface_device']
+})
+    .then(() => assert_promise_rejects_with_message(
+        requestDeviceWithTrustedClick({
+          filters: [{services: ['human_interface_device']}]
+        }),
+        expected, 'Requesting blocklisted service rejects.')),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.html b/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.html
new file mode 100644
index 0000000..7189b64
--- /dev/null
+++ b/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Blocklisted UUID in optionalServices is removed and ' +
+    'access not granted.';
+const expected = new DOMException('Origin is not allowed to access the ' +
+    'service. Tip: Add the service UUID to \'optionalServices\' in ' +
+    'requestDevice() options. https://goo.gl/HxfxSQ',
+    'SecurityError');
+let device, fake_peripheral;
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice({
+  filters: [{services: ['health_thermometer']}],
+  optionalServices: ['human_interface_device']
+})
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() =>
+        fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.connect())
+    .then(() => Promise.all([
+      assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('human_interface_device'),
+        expected, 'Blocklisted service not accessible.'),
+      assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('human_interface_device'),
+        expected, 'Blocklisted services not accessible.')])),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.html b/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.html
new file mode 100644
index 0000000..ba8a090
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A device name between 29 and 248 bytes is valid.';
+const DEVICE_NAME = 'a_device_name_that_is_longer_than_29_bytes_but_' +
+    'shorter_than_248_bytes';
+
+bluetooth_test(() => setUpPreconnectedDevice({name: DEVICE_NAME})
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{name: DEVICE_NAME}]
+    }))
+    .then(device => assert_equals(device.name, DEVICE_NAME)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.html b/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.html
new file mode 100644
index 0000000..eed7b87
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A filter must restrict the devices in some way.';
+const expected = new TypeError();
+
+bluetooth_test(() => assert_promise_rejects_with_message(
+    requestDeviceWithTrustedClick({filters: [{}]}),
+    expected),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.html b/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.html
new file mode 100644
index 0000000..fbbd6da
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'An empty |filters| member should result in a TypeError';
+const expected = new DOMException('Failed to execute \'requestDevice\' on ' +
+    '\'Bluetooth\': \'filters\' member must be non-empty to find any devices.',
+    new TypeError());
+
+bluetooth_test(() => assert_promise_rejects_with_message(
+    requestDeviceWithTrustedClick({filters: []}),
+    expected),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.html b/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.html
new file mode 100644
index 0000000..aa6c830
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'requestDevice with empty namePrefix. ' +
+    'Should reject with TypeError.';
+const expected = new TypeError();
+const test_specs = [{
+  filters: [{ namePrefix: ''}]
+}, {
+  filters: [{ namePrefix: '', name: 'Name'}]
+}, {
+  filters: [{ namePrefix: '', services: ['heart_rate']}]
+}, {
+  filters: [{ namePrefix: '', name: 'Name', services: ['heart_rate']}]
+}, {
+  filters: [{ namePrefix: ''}],
+  optionalServices: ['heart_rate']
+}, {
+  filters: [{ namePrefix: '', name: 'Name'}],
+  optionalServices: ['heart_rate']
+}, {
+  filters: [{ namePrefix: '', services: ['heart_rate']}],
+  optionalServices: ['heart_rate']
+}, {
+  filters: [{ namePrefix: '', name: 'Name', services: ['heart_rate']}],
+  optionalServices: ['heart_rate']
+}];
+
+bluetooth_test(() => {
+    let test_promises = Promise.resolve();
+    test_specs.forEach(args => {
+      test_promises = test_promises
+          .then(() => assert_promise_rejects_with_message(
+              requestDeviceWithTrustedClick(args),
+              expected));
+    });
+    return test_promises;
+  }, test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.html b/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.html
new file mode 100644
index 0000000..6cb923a
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Services member must contain at least one service.';
+const expected = new TypeError();
+
+bluetooth_test(() => {
+    let test_promises = Promise.resolve();
+    generateRequestDeviceArgsWithServices([]).forEach(args => {
+      test_promises = test_promises.then(() =>
+          assert_promise_rejects_with_message(
+              requestDeviceWithTrustedClick(args),
+              expected,
+              'Services member must contain at least one service'))
+    });
+    return test_promises;
+  }, test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.html b/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.html
new file mode 100644
index 0000000..ab20732
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = "RequestDeviceOptions should have exactly one of " +
+    "'filters' or 'acceptAllDevices:true'. Reject with TypeError if not.";
+const expected = new DOMException(
+    "Failed to execute 'requestDevice' on 'Bluetooth': " +
+    "Either 'filters' should be present or " +
+    "'acceptAllDevices' should be true, but not both.",
+    new TypeError());
+const test_specs = [
+  {},
+  {optionalServices: ['heart_rate']},
+  {filters: [], acceptAllDevices: true},
+  {filters: [], acceptAllDevices: true, optionalServices: ['heart_rate']}
+];
+
+bluetooth_test(() => {
+    let test_promises = Promise.resolve();
+    test_specs.forEach(args => {
+        test_promises = test_promises
+            .then(() => assert_promise_rejects_with_message(
+                requestDeviceWithTrustedClick(args),
+                expected))
+    });
+    return test_promises;
+  }, test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.html
new file mode 100644
index 0000000..2f2df74
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Unicode string with utf8 representation longer than 248 ' +
+    'bytes in \'name\' must throw TypeError.';
+const expected = new DOMException(
+    "Failed to execute 'requestDevice' on 'Bluetooth': " +
+    "A device name can't be longer than 248 bytes.",
+    new TypeError());
+// \u2764's UTF-8 respresentation is 3 bytes long.
+// 83 chars * 3 bytes/char = 249 bytes
+const unicode_name = '\u2764'.repeat(83);
+
+bluetooth_test(() => assert_promise_rejects_with_message(
+    requestDeviceWithTrustedClick({filters: [{name: unicode_name}]}),
+    expected),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.html
new file mode 100644
index 0000000..dcb6c40
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A device name longer than 248 must reject.';
+const expected = new DOMException(
+    "Failed to execute 'requestDevice' on 'Bluetooth': A device " +
+    "name can't be longer than 248 bytes.",
+    new TypeError());
+const name_too_long = 'a'.repeat(249);
+
+bluetooth_test(() => assert_promise_rejects_with_message(
+    requestDeviceWithTrustedClick({filters: [{name: name_too_long}]}),
+    expected,
+    'Device name longer than 248'),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.html
new file mode 100644
index 0000000..fe1f919
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Unicode string with utf8 representation longer than 248 ' +
+    'bytes in \'namePrefix\' must throw NotFoundError.';
+const expected = new DOMException(
+    "Failed to execute 'requestDevice' on 'Bluetooth': " +
+    "A device name can't be longer than 248 bytes.",
+    new TypeError());
+// \u2764's UTF-8 respresentation is 3 bytes long.
+// 83 chars * 3 bytes/char = 249 bytes
+const unicode_name = '\u2764'.repeat(83);
+
+bluetooth_test(() => assert_promise_rejects_with_message(
+    requestDeviceWithTrustedClick({filters: [{namePrefix: unicode_name}]}),
+    expected),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.html
new file mode 100644
index 0000000..ab3a6ba
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A device name prefix longer than 248 must reject.';
+const expected = new DOMException(
+    "Failed to execute 'requestDevice' on 'Bluetooth': A device " +
+    "name can't be longer than 248 bytes.",
+    new TypeError());
+const name_too_long = 'a'.repeat(249);
+
+bluetooth_test(() => assert_promise_rejects_with_message(
+    requestDeviceWithTrustedClick({filters: [{namePrefix: name_too_long}]}),
+    expected,
+    'Device name longer than 248'),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.html
new file mode 100644
index 0000000..40df9d0
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A unicode device name of 248 bytes is valid.';
+// \u00A1's UTF-8 respresentation is 2 bytes long.
+// 124 chars * 2 bytes/char = 248 bytes
+const DEVICE_NAME = '\u00A1'.repeat(124);
+
+bluetooth_test(() => setUpPreconnectedDevice({name: DEVICE_NAME})
+    .then(() => requestDeviceWithTrustedClick({ filters: [{name: DEVICE_NAME}]}))
+    .then(device => assert_equals(device.name, DEVICE_NAME)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.html
new file mode 100644
index 0000000..d1759d6
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A device name of 248 bytes is valid.';
+const DEVICE_NAME = 'a'.repeat(248);
+
+bluetooth_test(() => setUpPreconnectedDevice({name: DEVICE_NAME})
+    .then(() => requestDeviceWithTrustedClick({ filters: [{name: DEVICE_NAME}]}))
+    .then(device => assert_equals(device.name, DEVICE_NAME)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.html
new file mode 100644
index 0000000..8e86844
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A unicode device namePrefix of 248 bytes is valid.';
+// \u00A1's UTF-8 respresentation is 2 bytes long.
+// 124 chars * 2 bytes/char = 248 bytes
+const DEVICE_NAME = '\u00A1'.repeat(124);
+
+bluetooth_test(() => setUpPreconnectedDevice({name: DEVICE_NAME})
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{namePrefix: DEVICE_NAME}]
+    }))
+    .then(device => assert_equals(device.name, DEVICE_NAME)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.html b/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.html
new file mode 100644
index 0000000..01ed22e
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A device namePrefix of 248 bytes is valid.';
+const DEVICE_NAME = 'a'.repeat(248);
+
+bluetooth_test(() => setUpPreconnectedDevice({name: DEVICE_NAME})
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{namePrefix: DEVICE_NAME}]
+    }))
+    .then(device => assert_equals(device.name, DEVICE_NAME)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/no-arguments.https.html b/bluetooth/requestDevice/canonicalizeFilter/no-arguments.https.html
new file mode 100644
index 0000000..e6337a5
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/no-arguments.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'requestDevice() requires an argument.';
+const expected = new TypeError();
+
+promise_test(() => assert_promise_rejects_with_message(
+    requestDeviceWithTrustedClick(),
+    expected),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.html b/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.html
new file mode 100644
index 0000000..08038b9
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'A name containing unicode characters whose utf8 length ' +
+    'is less than 30 must not throw an error.';
+// \u2764's UTF-8 representation is 3 bytes long.
+// 9 chars * 3 bytes/char = 27 bytes
+const valid_unicode_name = '\u2764'.repeat(9);
+
+bluetooth_test(() => setUpPreconnectedDevice({name: valid_unicode_name})
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{name: valid_unicode_name}]
+    }))
+    .then(device => assert_equals(device.name, valid_unicode_name)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.html b/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.html
new file mode 100644
index 0000000..fc44482
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc =  'A namePrefix containing unicode characters whose utf8 ' +
+    'length is less than 30 must not throw an error.';
+// \u2764's UTF-8 representation is 3 bytes long.
+// 9 chars * 3 bytes/char = 27 bytes
+const valid_unicode_name = '\u2764'.repeat(9);
+
+bluetooth_test(() => setUpPreconnectedDevice({name: valid_unicode_name})
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{namePrefix: valid_unicode_name}]
+    }))
+    .then(device => assert_equals(device.name, valid_unicode_name)),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.html b/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.html
new file mode 100644
index 0000000..15cf902
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Invalid optional service must reject the promise.';
+const expected = new TypeError();
+const test_specs = [{
+  optionalServices: ['wrong_service'],
+  filters: [{services: ['heart_rate']}]
+}, {
+  optionalServices: ['wrong_service'],
+  filters: [{ services: ['heart_rate'], name: 'Name'}]
+}, {
+  optionalServices: ['wrong_service'],
+  filters: [{ services: ['heart_rate'], namePrefix: 'Pre'}]
+}, {
+  optionalServices: ['wrong_service'],
+  filters: [{ services: ['heart_rate'], name: 'Name', namePrefix: 'Pre'}]
+}, {
+  optionalServices: ['wrong_service'],
+  filters: [{ name: 'Name'}]
+}, {
+  optionalServices: ['wrong_service'],
+  filters: [{ name: 'Name', namePrefix: 'Pre'}]
+}, {
+  optionalServices: ['wrong_service'],
+  filters: [{ namePrefix: 'Pre'}]
+}];
+
+bluetooth_test(() => {
+    let test_promises = Promise.resolve();
+    test_specs.forEach(args => {
+        test_promises =
+            test_promises.then(() => assert_promise_rejects_with_message(
+                requestDeviceWithTrustedClick(args),
+                expected));
+    });
+    return test_promises;
+  }, test_desc);
+</script>
diff --git a/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.html b/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.html
new file mode 100644
index 0000000..5d9b245
--- /dev/null
+++ b/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Invalid service must reject the promise.';
+const expected = new TypeError();
+
+bluetooth_test(() => {
+    let test_promises = Promise.resolve();
+    generateRequestDeviceArgsWithServices(['wrong_service']).forEach(args => {
+        test_promises = test_promises.then(() =>
+            assert_promise_rejects_with_message(
+              requestDeviceWithTrustedClick(args),
+              expected));
+    });
+    return test_promises;
+  }, test_desc);
+</script>
diff --git a/bluetooth/requestDevice/cross-origin-iframe.sub.https.html b/bluetooth/requestDevice/cross-origin-iframe.sub.https.html
new file mode 100644
index 0000000..e97991d
--- /dev/null
+++ b/bluetooth/requestDevice/cross-origin-iframe.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<body>
+<script>
+'use strict';
+const test_desc = 'Request device from a unique origin. ' +
+    'Should reject with SecurityError.';
+const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    '/bluetooth/resources/health-thermometer-iframe.html'
+let iframe = document.createElement('iframe');
+
+bluetooth_test(() => setUpHealthThermometerDevice()
+    // 1. Load the iframe.
+    .then(() => new Promise(resolve => {
+      iframe.src = cross_origin_src;
+      document.body.appendChild(iframe);
+      iframe.addEventListener('load', resolve);
+    }))
+    // 2. Request the device from the iframe.
+    .then(() => new Promise(resolve => {
+      callWithTrustedClick(() => {
+        iframe.contentWindow.postMessage({
+          type: 'RequestDevice'
+        }, '*');
+      });
+
+      window.onmessage = messageEvent => {
+        assert_equals(messageEvent.data, 'SecurityError: requestDevice() ' +
+            'called from cross-origin iframe.');
+        resolve();
+      }
+    })), test_desc);
+</script>
+</body>
diff --git a/bluetooth/requestDevice/discovery-succeeds.https.html b/bluetooth/requestDevice/discovery-succeeds.https.html
new file mode 100644
index 0000000..2121944
--- /dev/null
+++ b/bluetooth/requestDevice/discovery-succeeds.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Discover a device using alias, name, or UUID.';
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+    // Chrome will always close the previous chooser in the process of handling
+    // a user gesture for the next request, so these need to be done
+    // sequentially.
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{services: [health_thermometer.alias]}]
+    }))
+    .then(device => assert_equals(device.constructor.name, 'BluetoothDevice'))
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{services: [health_thermometer.name]}]
+    }))
+    .then(device => assert_equals(device.constructor.name, 'BluetoothDevice'))
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{services: [health_thermometer.uuid]}]
+    }))
+    .then(device => assert_equals(device.constructor.name, 'BluetoothDevice')),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/doesnt-consume-user-gesture.https.html b/bluetooth/requestDevice/doesnt-consume-user-gesture.https.html
new file mode 100644
index 0000000..3394a59
--- /dev/null
+++ b/bluetooth/requestDevice/doesnt-consume-user-gesture.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'requestDevice calls do not consume user gestures.';
+
+bluetooth_test(() => setUpHealthThermometerAndHeartRateDevices()
+    .then(() => callWithTrustedClick(() => {
+      let first = navigator.bluetooth.requestDevice({
+        filters: [{services: ['heart_rate']}]});
+      let second = navigator.bluetooth.requestDevice({
+        filters: [{services: ['heart_rate']}]});
+      return Promise.all([
+        first.then(device => assert_equals(
+          device.constructor.name, 'BluetoothDevice')),
+        second.then(device => assert_equals(
+          device.constructor.name, 'BluetoothDevice')),
+      ]);
+    })), test_desc);
+</script>
diff --git a/bluetooth/requestDevice/filter-matches.https.html b/bluetooth/requestDevice/filter-matches.https.html
new file mode 100644
index 0000000..3f62d39
--- /dev/null
+++ b/bluetooth/requestDevice/filter-matches.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Matches a filter if all present members match.';
+let matching_services = [health_thermometer.uuid];
+let matching_name = 'Health Thermometer';
+let matching_namePrefix = 'Health';
+
+let test_specs = [{
+  filters: [{
+    services: matching_services,
+  }]
+}, {
+  filters: [{
+    services: matching_services,
+    name: matching_name,
+  }]
+}, {
+  filters: [{
+    services: matching_services,
+    namePrefix: matching_namePrefix
+  }]
+}, {
+  filters: [{
+    name: matching_name,
+  }],
+  optionalServices: matching_services
+}, {
+  filters: [{
+    name: matching_name,
+    namePrefix: matching_namePrefix
+  }],
+  optionalServices: matching_services
+}, {
+  filters: [{
+    namePrefix: matching_namePrefix
+  }],
+  optionalServices: matching_services
+}, {
+  filters: [{
+    services: matching_services,
+    name: matching_name,
+    namePrefix: matching_namePrefix
+  }]
+}];
+
+bluetooth_test(() => setUpHealthThermometerDevice()
+    .then(() => {
+      let test_promises = Promise.resolve();
+      test_specs.forEach(args => {
+        test_promises = test_promises
+            .then(() => requestDeviceWithTrustedClick(args))
+            .then(device => {
+              // We always have access to the services in matching_services
+              // because we include them in a filter or in optionalServices.
+              assert_equals(device.name, matching_name);
+              assert_true(device.name.startsWith(matching_namePrefix));
+            });
+      });
+      return test_promises;
+    }), test_desc);
+</script>
diff --git a/bluetooth/requestDevice/le-not-supported.https.html b/bluetooth/requestDevice/le-not-supported.https.html
new file mode 100644
index 0000000..4a37525
--- /dev/null
+++ b/bluetooth/requestDevice/le-not-supported.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Reject with NotFoundError if Bluetooth is not supported.';
+const expected = new DOMException('Bluetooth Low Energy not available.',
+    'NotFoundError');
+
+bluetooth_test(() => navigator.bluetooth.test.setLESupported(false)
+    .then(() => assert_promise_rejects_with_message(
+        requestDeviceWithTrustedClick({acceptAllDevices: true}),
+        expected, 'Bluetooth Low Energy is not supported.')),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.html b/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.html
new file mode 100644
index 0000000..ee9b912
--- /dev/null
+++ b/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'An empty name device can be obtained by empty name filter.'
+
+bluetooth_test(() => setUpPreconnectedDevice({name: ''})
+    .then(() => requestDeviceWithTrustedClick({filters: [{name: ''}]}))
+    .then(device => assert_equals(device.name, '')),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/not-processing-user-gesture.https.html b/bluetooth/requestDevice/not-processing-user-gesture.https.html
new file mode 100644
index 0000000..1781b7b
--- /dev/null
+++ b/bluetooth/requestDevice/not-processing-user-gesture.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Requires a user gesture.';
+const expected = new DOMException(
+    'Must be handling a user gesture to show a permission request.',
+    'SecurityError');
+
+bluetooth_test(() => setUpHealthThermometerAndHeartRateDevices()
+    .then(() => assert_promise_rejects_with_message(
+        navigator.bluetooth.requestDevice({filters:[{services:['heart_rate']}]}),
+        expected, 'User gesture is required')),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/radio-not-present.https.html b/bluetooth/requestDevice/radio-not-present.https.html
new file mode 100644
index 0000000..a66bcf9
--- /dev/null
+++ b/bluetooth/requestDevice/radio-not-present.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Reject with NotFoundError if there is no BT radio present.';
+const expected = new DOMException('Bluetooth adapter not available.',
+    'NotFoundError');
+
+bluetooth_test(() => navigator.bluetooth.test.simulateCentral({state: 'absent'})
+    .then(() => assert_promise_rejects_with_message(
+        requestDeviceWithTrustedClick({
+          filters: [{services: ['generic_access']}]
+        }),
+        expected, 'Bluetooth adapter is not present.')),
+    test_desc);
+</script>
diff --git a/bluetooth/requestDevice/request-from-iframe.https.html b/bluetooth/requestDevice/request-from-iframe.https.html
new file mode 100644
index 0000000..01590ea
--- /dev/null
+++ b/bluetooth/requestDevice/request-from-iframe.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Concurrent requestDevice calls in iframes work.';
+const iframes = [];
+for (let i = 0; i < 5; i++) {
+  iframes.push(document.createElement('iframe'));
+}
+
+bluetooth_test(() => setUpHealthThermometerAndHeartRateDevices()
+    // 1. Load the iframes.
+    .then(() => {
+      let promises = [];
+      for (let iframe of iframes) {
+        iframe.src = '/bluetooth/resources/health-thermometer-iframe.html';
+        document.body.appendChild(iframe);
+        promises.push(new Promise(resolve =>
+            iframe.addEventListener('load', resolve)));
+      }
+      return Promise.all(promises);
+    })
+    // 2. Request the device from the iframes.
+    .then(() => new Promise(async (resolve) => {
+      let numMessages = 0;
+      window.onmessage = messageEvent => {
+        assert_equals(messageEvent.data, 'Success');
+        if (++numMessages === iframes.length) {
+          resolve();
+        }
+      }
+
+      for (let iframe of iframes) {
+        await callWithTrustedClick(() => iframe.contentWindow.postMessage({
+            type: 'RequestDevice'
+        }, '*'));
+      }
+    })), test_desc);
+</script>
diff --git a/bluetooth/requestDevice/request-from-sandboxed-iframe.https.html b/bluetooth/requestDevice/request-from-sandboxed-iframe.https.html
new file mode 100644
index 0000000..8079830
--- /dev/null
+++ b/bluetooth/requestDevice/request-from-sandboxed-iframe.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<body>
+<script>
+'use strict';
+const test_desc = 'Request device from a unique origin. ' +
+    'Should reject with SecurityError.';
+const expected = 'SecurityError: requestDevice() called from cross-origin ' +
+    'iframe.';
+
+let iframe = document.createElement('iframe');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+    // 1. Load the iframe.
+    .then(() => new Promise(resolve => {
+      iframe.sandbox.add('allow-scripts');
+      iframe.src = '/bluetooth/resources/health-thermometer-iframe.html';
+      document.body.appendChild(iframe);
+      iframe.addEventListener('load', resolve);
+    }))
+    // 2. Request the device from the iframe.
+    .then(() => new Promise(resolve => {
+      callWithTrustedClick(() => {
+        iframe.contentWindow.postMessage({
+          type: 'RequestDevice'
+        }, '*');
+      });
+
+      window.onmessage = messageEvent => {
+        assert_equals(messageEvent.data, expected);
+        resolve();
+      }
+    })), test_desc);
+</script>
+</body>
diff --git a/bluetooth/requestDevice/same-device.https.html b/bluetooth/requestDevice/same-device.https.html
new file mode 100644
index 0000000..a83cf70
--- /dev/null
+++ b/bluetooth/requestDevice/same-device.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Returned device should always be the same.';
+let devices = [];
+let push = device => devices.push(device);
+
+bluetooth_test(() => setUpHealthThermometerAndHeartRateDevices()
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{services: [heart_rate.alias]}]
+    }))
+    .then(push)
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{services: [heart_rate.name]}]
+    }))
+    .then(push)
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{services: [heart_rate.uuid]}]
+    }))
+    .then(push)
+    .then(() => {
+      assert_equals(devices[0], devices[1]);
+      assert_equals(devices[1], devices[2]);
+    }), test_desc);
+</script>
diff --git a/bluetooth/requestDevice/single-filter-single-service.https.html b/bluetooth/requestDevice/single-filter-single-service.https.html
new file mode 100644
index 0000000..8735eb3
--- /dev/null
+++ b/bluetooth/requestDevice/single-filter-single-service.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Simple filter selects matching device.';
+
+bluetooth_test(() => setUpHealthThermometerAndHeartRateDevices()
+    .then(() => requestDeviceWithTrustedClick({
+      filters: [{services: ['health_thermometer']}]
+    }))
+    .then(device => assert_equals(device.name, 'Health Thermometer')),
+    test_desc);
+</script>
diff --git a/bluetooth/resources/bluetooth-helpers.js b/bluetooth/resources/bluetooth-helpers.js
new file mode 100644
index 0000000..725e56d
--- /dev/null
+++ b/bluetooth/resources/bluetooth-helpers.js
@@ -0,0 +1,1002 @@
+'use strict';
+
+function loadScript(path) {
+  let script = document.createElement('script');
+  let promise = new Promise(resolve => script.onload = resolve);
+  script.src = path;
+  script.async = false;
+  document.head.appendChild(script);
+  return promise;
+}
+
+function loadScripts(paths) {
+  let chain = Promise.resolve();
+  for (let path of paths) {
+    chain = chain.then(() => loadScript(path));
+  }
+  return chain;
+}
+
+function performChromiumSetup() {
+  // Make sure we are actually on Chromium.
+  if (!Mojo) {
+    return;
+  }
+
+  // Load the Chromium-specific resources.
+  let prefix = '/resources/chromium';
+  let extra = [];
+  if (window.location.pathname.includes('/LayoutTests/')) {
+    let root = window.location.pathname.match(/.*LayoutTests/);
+    prefix = `${root}/external/wpt/resources/chromium`;
+    extra = [
+      `${root}/resources/bluetooth/bluetooth-fake-adapter.js`,
+    ];
+  } else if (window.location.pathname.startsWith('/bluetooth/https/')) {
+    extra = [
+      '/js-test-resources/bluetooth/bluetooth-fake-adapter.js',
+    ];
+  }
+  return loadScripts([
+    `${prefix}/mojo_bindings.js`,
+    `${prefix}/mojo_layouttest_test.mojom.js`,
+    `${prefix}/uuid.mojom.js`,
+    `${prefix}/fake_bluetooth.mojom.js`,
+    `${prefix}/fake_bluetooth_chooser.mojom.js`,
+    `${prefix}/web-bluetooth-test.js`,
+  ].concat(extra))
+      // Call setBluetoothFakeAdapter() to clean up any fake adapters left over
+      // by legacy tests.
+      // Legacy tests that use setBluetoothFakeAdapter() sometimes fail to clean
+      // their fake adapter. This is not a problem for these tests because the
+      // next setBluetoothFakeAdapter() will clean it up anyway but it is a
+      // problem for the new tests that do not use setBluetoothFakeAdapter().
+      // TODO(crbug.com/569709): Remove once setBluetoothFakeAdapter is no
+      // longer used.
+      .then(() => typeof setBluetoothFakeAdapter === 'undefined' ?
+          undefined : setBluetoothFakeAdapter(''));
+}
+
+
+// These tests rely on the User Agent providing an implementation of the
+// Web Bluetooth Testing API.
+// https://docs.google.com/document/d/1Nhv_oVDCodd1pEH_jj9k8gF4rPGb_84VYaZ9IG8M_WY/edit?ts=59b6d823#heading=h.7nki9mck5t64
+function bluetooth_test(func, name, properties) {
+  Promise.resolve()
+    .then(() => promise_test(t => Promise.resolve()
+      // Trigger Chromium-specific setup.
+      .then(performChromiumSetup)
+      .then(() => func(t))
+      .then(() => navigator.bluetooth.test.allResponsesConsumed())
+      .then(consumed => assert_true(consumed)), name, properties));
+}
+
+// HCI Error Codes. Used for simulateGATT[Dis]ConnectionResponse.
+// For a complete list of possible error codes see
+// BT 4.2 Vol 2 Part D 1.3 List Of Error Codes.
+const HCI_SUCCESS = 0x0000;
+const HCI_CONNECTION_TIMEOUT = 0x0008;
+
+// GATT Error codes. Used for GATT operations responses.
+// BT 4.2 Vol 3 Part F 3.4.1.1 Error Response
+const GATT_SUCCESS        = 0x0000;
+const GATT_INVALID_HANDLE = 0x0001;
+
+// Bluetooth UUID constants:
+// Services:
+var blocklist_test_service_uuid = "611c954a-263b-4f4a-aab6-01ddb953f985";
+var request_disconnection_service_uuid = "01d7d889-7451-419f-aeb8-d65e7b9277af";
+// Characteristics:
+var blocklist_exclude_reads_characteristic_uuid =
+  "bad1c9a2-9a5b-4015-8b60-1579bbbf2135";
+var request_disconnection_characteristic_uuid =
+  "01d7d88a-7451-419f-aeb8-d65e7b9277af";
+// Descriptors:
+var blocklist_test_descriptor_uuid = "bad2ddcf-60db-45cd-bef9-fd72b153cf7c";
+var blocklist_exclude_reads_descriptor_uuid =
+    "bad3ec61-3cc3-4954-9702-7977df514114";
+
+// Sometimes we need to test that using either the name, alias, or UUID
+// produces the same result. The following objects help us do that.
+var generic_access = {
+  alias: 0x1800,
+  name: 'generic_access',
+  uuid: '00001800-0000-1000-8000-00805f9b34fb'
+};
+var device_name = {
+  alias: 0x2a00,
+  name: 'gap.device_name',
+  uuid: '00002a00-0000-1000-8000-00805f9b34fb'
+};
+var reconnection_address = {
+  alias: 0x2a03,
+  name: 'gap.reconnection_address',
+  uuid: '00002a03-0000-1000-8000-00805f9b34fb'
+};
+var heart_rate = {
+  alias: 0x180d,
+  name: 'heart_rate',
+  uuid: '0000180d-0000-1000-8000-00805f9b34fb'
+};
+var health_thermometer = {
+  alias: 0x1809,
+  name: 'health_thermometer',
+  uuid: '00001809-0000-1000-8000-00805f9b34fb'
+};
+var body_sensor_location = {
+  alias: 0x2a38,
+  name: 'body_sensor_location',
+  uuid: '00002a38-0000-1000-8000-00805f9b34fb'
+};
+var glucose = {
+  alias: 0x1808,
+  name: 'glucose',
+  uuid: '00001808-0000-1000-8000-00805f9b34fb'
+};
+var battery_service = {
+  alias: 0x180f,
+  name: 'battery_service',
+  uuid: '0000180f-0000-1000-8000-00805f9b34fb'
+};
+var battery_level = {
+  alias: 0x2A19,
+  name: 'battery_level',
+  uuid: '00002a19-0000-1000-8000-00805f9b34fb'
+};
+var user_description = {
+  alias: 0x2901,
+  name: 'gatt.characteristic_user_description',
+  uuid: '00002901-0000-1000-8000-00805f9b34fb'
+};
+var client_characteristic_configuration = {
+  alias: 0x2902,
+  name: 'gatt.client_characteristic_configuration',
+  uuid: '00002902-0000-1000-8000-00805f9b34fb'
+};
+var measurement_interval = {
+  alias: 0x2a21,
+  name: 'measurement_interval',
+  uuid: '00002a21-0000-1000-8000-00805f9b34fb'
+};
+
+// The following tests make sure the Web Bluetooth implementation
+// responds correctly to the different types of errors the
+// underlying platform might return for GATT operations.
+
+// Each browser should map these characteristics to specific code paths
+// that result in different errors thus increasing code coverage
+// when testing. Therefore some of these characteristics might not be useful
+// for all browsers.
+//
+// TODO(ortuno): According to the testing spec errorUUID(0x101) to
+// errorUUID(0x1ff) should be use for the uuids of the characteristics.
+var gatt_errors_tests = [{
+  testName: 'GATT Error: Unknown.',
+  uuid: errorUUID(0xA1),
+  error: new DOMException(
+      'GATT Error Unknown.',
+      'NotSupportedError')
+}, {
+  testName: 'GATT Error: Failed.',
+  uuid: errorUUID(0xA2),
+  error: new DOMException(
+      'GATT operation failed for unknown reason.',
+      'NotSupportedError')
+}, {
+  testName: 'GATT Error: In Progress.',
+  uuid: errorUUID(0xA3),
+  error: new DOMException(
+      'GATT operation already in progress.',
+      'NetworkError')
+}, {
+  testName: 'GATT Error: Invalid Length.',
+  uuid: errorUUID(0xA4),
+  error: new DOMException(
+      'GATT Error: invalid attribute length.',
+      'InvalidModificationError')
+}, {
+  testName: 'GATT Error: Not Permitted.',
+  uuid: errorUUID(0xA5),
+  error: new DOMException(
+      'GATT operation not permitted.',
+      'NotSupportedError')
+}, {
+  testName: 'GATT Error: Not Authorized.',
+  uuid: errorUUID(0xA6),
+  error: new DOMException(
+      'GATT operation not authorized.',
+      'SecurityError')
+}, {
+  testName: 'GATT Error: Not Paired.',
+  uuid: errorUUID(0xA7),
+  // TODO(ortuno): Change to InsufficientAuthenticationError or similiar
+  // once https://github.com/WebBluetoothCG/web-bluetooth/issues/137 is
+  // resolved.
+  error: new DOMException(
+      'GATT Error: Not paired.',
+      'NetworkError')
+}, {
+  testName: 'GATT Error: Not Supported.',
+  uuid: errorUUID(0xA8),
+  error: new DOMException(
+      'GATT Error: Not supported.',
+      'NotSupportedError')
+}];
+
+// Waits until the document has finished loading.
+function waitForDocumentReady() {
+  return new Promise(resolve => {
+    if (document.readyState === 'complete') {
+      resolve();
+    }
+
+    window.addEventListener('load', () => {
+      resolve();
+    }, {once: true});
+  });
+}
+
+function callWithTrustedClick(callback) {
+  return waitForDocumentReady()
+    .then(() => new Promise(resolve => {
+      let button = document.createElement('button');
+      button.textContent = 'click to continue test';
+      button.style.display = 'block';
+      button.style.fontSize = '20px';
+      button.style.padding = '10px';
+      button.onclick = () => {
+        document.body.removeChild(button);
+        resolve(callback());
+      };
+      document.body.appendChild(button);
+      test_driver.click(button);
+    }));
+}
+
+// Calls requestDevice() in a context that's 'allowed to show a popup'.
+function requestDeviceWithTrustedClick() {
+  let args = arguments;
+  return callWithTrustedClick(
+      () => navigator.bluetooth.requestDevice.apply(navigator.bluetooth, args));
+}
+
+// errorUUID(alias) returns a UUID with the top 32 bits of
+// '00000000-97e5-4cd7-b9f1-f5a427670c59' replaced with the bits of |alias|.
+// For example, errorUUID(0xDEADBEEF) returns
+// 'deadbeef-97e5-4cd7-b9f1-f5a427670c59'. The bottom 96 bits of error UUIDs
+// were generated as a type 4 (random) UUID.
+function errorUUID(uuidAlias) {
+  // Make the number positive.
+  uuidAlias >>>= 0;
+  // Append the alias as a hex number.
+  var strAlias = '0000000' + uuidAlias.toString(16);
+  // Get last 8 digits of strAlias.
+  strAlias = strAlias.substr(-8);
+  // Append Base Error UUID
+  return strAlias + '-97e5-4cd7-b9f1-f5a427670c59';
+}
+
+// Function to test that a promise rejects with the expected error type and
+// message.
+function assert_promise_rejects_with_message(promise, expected, description) {
+  return promise.then(() => {
+    assert_unreached('Promise should have rejected: ' + description);
+  }, error => {
+    assert_equals(error.name, expected.name, 'Unexpected Error Name:');
+    if (expected.message) {
+      assert_equals(error.message, expected.message, 'Unexpected Error Message:');
+    }
+  });
+}
+
+function runGarbageCollection()
+{
+  // Run gc() as a promise.
+  return new Promise(
+      function(resolve, reject) {
+        GCController.collect();
+        step_timeout(resolve, 0);
+      });
+}
+
+function eventPromise(target, type, options) {
+  return new Promise(resolve => {
+    let wrapper = function(event) {
+      target.removeEventListener(type, wrapper);
+      resolve(event);
+    };
+    target.addEventListener(type, wrapper, options);
+  });
+}
+
+// Helper function to assert that events are fired and a promise resolved
+// in the correct order.
+// 'event' should be passed as |should_be_first| to indicate that the events
+// should be fired first, otherwise 'promiseresolved' should be passed.
+// Attaches |num_listeners| |event| listeners to |object|. If all events have
+// been fired and the promise resolved in the correct order, returns a promise
+// that fulfills with the result of |object|.|func()| and |event.target.value|
+// of each of event listeners. Otherwise throws an error.
+function assert_promise_event_order_(should_be_first, object, func, event, num_listeners) {
+  let order = [];
+  let event_promises = [];
+  for (let i = 0; i < num_listeners; i++) {
+    event_promises.push(new Promise(resolve => {
+      let event_listener = (e) => {
+        object.removeEventListener(event, event_listener);
+        order.push('event');
+        resolve(e.target.value);
+      };
+      object.addEventListener(event, event_listener);
+    }));
+  }
+
+  let func_promise = object[func]().then(result => {
+    order.push('promiseresolved');
+    return result;
+  });
+
+  return Promise.all([func_promise, ...event_promises])
+    .then((result) => {
+      if (should_be_first !== order[0]) {
+        throw should_be_first === 'promiseresolved' ?
+                      `'${event}' was fired before promise resolved.` :
+                      `Promise resolved before '${event}' was fired.`;
+      }
+
+      if (order[0] !== 'promiseresolved' &&
+          order[order.length - 1] !== 'promiseresolved') {
+        throw 'Promise resolved in between event listeners.';
+      }
+
+      return result;
+    });
+}
+
+// See assert_promise_event_order_ above.
+function assert_promise_resolves_before_event(
+  object, func, event, num_listeners=1) {
+  return assert_promise_event_order_(
+    'promiseresolved', object, func, event, num_listeners);
+}
+
+// See assert_promise_event_order_ above.
+function assert_promise_resolves_after_event(
+  object, func, event, num_listeners=1) {
+  return assert_promise_event_order_(
+    'event', object, func, event, num_listeners);
+}
+
+// Returns a promise that resolves after 100ms unless
+// the the event is fired on the object in which case
+// the promise rejects.
+function assert_no_events(object, event_name) {
+  return new Promise((resolve, reject) => {
+    let event_listener = (e) => {
+      object.removeEventListener(event_name, event_listener);
+      assert_unreached('Object should not fire an event.');
+    };
+    object.addEventListener(event_name, event_listener);
+    // TODO: Remove timeout.
+    // http://crbug.com/543884
+    step_timeout(() => {
+      object.removeEventListener(event_name, event_listener);
+      resolve();
+    }, 100);
+  });
+}
+
+class TestCharacteristicProperties {
+  // |properties| is an array of strings for property bits to be set
+  // as true.
+  constructor(properties) {
+    this.broadcast                 = false;
+    this.read                      = false;
+    this.writeWithoutResponse      = false;
+    this.write                     = false;
+    this.notify                    = false;
+    this.indicate                  = false;
+    this.authenticatedSignedWrites = false;
+    this.reliableWrite             = false;
+    this.writableAuxiliaries       = false;
+
+    properties.forEach(val => {
+      if (this.hasOwnProperty(val))
+        this[val] = true;
+      else
+        throw `Invalid member '${val}'`;
+    });
+  }
+}
+
+function assert_properties_equal(properties, expected_properties) {
+  for (let key in expected_properties) {
+    assert_equals(properties[key], expected_properties[key]);
+  }
+}
+
+class EventCatcher {
+  constructor(object, event) {
+    this.eventFired = false;
+    let event_listener = () => {
+      object.removeEventListener(event, event_listener);
+      this.eventFired = true;
+    };
+    object.addEventListener(event, event_listener);
+  }
+}
+
+// Returns a function that when called returns a promise that resolves when
+// the device has disconnected. Example:
+// device.gatt.connect()
+//   .then(gatt => get_request_disconnection(gatt))
+//   .then(requestDisconnection => requestDisconnection())
+//   .then(() => // device is now disconnected)
+function get_request_disconnection(gattServer) {
+  return gattServer.getPrimaryService(request_disconnection_service_uuid)
+    .then(service => service.getCharacteristic(request_disconnection_characteristic_uuid))
+    .then(characteristic => {
+      return () => assert_promise_rejects_with_message(
+        characteristic.writeValue(new Uint8Array([0])),
+        new DOMException(
+          'GATT Server is disconnected. Cannot perform GATT operations. ' +
+          '(Re)connect first with `device.gatt.connect`.',
+          'NetworkError'));
+    });
+}
+
+function generateRequestDeviceArgsWithServices(services = ['heart_rate']) {
+  return [{
+    filters: [{ services: services }]
+  }, {
+    filters: [{ services: services, name: 'Name' }]
+  }, {
+    filters: [{ services: services, namePrefix: 'Pre' }]
+  }, {
+    filters: [{ services: services, name: 'Name', namePrefix: 'Pre' }]
+  }, {
+    filters: [{ services: services }],
+    optionalServices: ['heart_rate']
+  }, {
+    filters: [{ services: services, name: 'Name' }],
+    optionalServices: ['heart_rate']
+  }, {
+    filters: [{ services: services, namePrefix: 'Pre' }],
+    optionalServices: ['heart_rate']
+  }, {
+    filters: [{ services: services, name: 'Name', namePrefix: 'Pre' }],
+    optionalServices: ['heart_rate']
+  }];
+}
+
+// Causes |fake_peripheral| to disconnect and returns a promise that resolves
+// once `gattserverdisconnected` has been fired on |device|.
+function simulateGATTDisconnectionAndWait(device, fake_peripheral) {
+  return Promise.all([
+    eventPromise(device, 'gattserverdisconnected'),
+    fake_peripheral.simulateGATTDisconnection(),
+  ]);
+}
+
+// Simulates a pre-connected device with |address|, |name| and
+// |knownServiceUUIDs|.
+function setUpPreconnectedDevice({
+  address = '00:00:00:00:00:00', name = 'LE Device', knownServiceUUIDs = []}) {
+  return navigator.bluetooth.test.simulateCentral({state: 'powered-on'})
+    .then(fake_central => fake_central.simulatePreconnectedPeripheral({
+      address: address,
+      name: name,
+      knownServiceUUIDs: knownServiceUUIDs,
+    }));
+}
+
+// Returns a FakePeripheral that corresponds to a simulated pre-connected device
+// called 'Health Thermometer'. The device has two known serviceUUIDs:
+// 'generic_access' and 'health_thermometer'.
+function setUpHealthThermometerDevice() {
+  return setUpPreconnectedDevice({
+    address: '09:09:09:09:09:09',
+    name: 'Health Thermometer',
+    knownServiceUUIDs: ['generic_access', 'health_thermometer'],
+  });
+}
+
+// Returns an array containing two FakePeripherals corresponding
+// to the simulated devices.
+function setUpHealthThermometerAndHeartRateDevices() {
+  return navigator.bluetooth.test.simulateCentral({state: 'powered-on'})
+   .then(fake_central => Promise.all([
+     fake_central.simulatePreconnectedPeripheral({
+       address: '09:09:09:09:09:09',
+       name: 'Health Thermometer',
+       knownServiceUUIDs: ['generic_access', 'health_thermometer'],
+     }),
+     fake_central.simulatePreconnectedPeripheral({
+       address: '08:08:08:08:08:08',
+       name: 'Heart Rate',
+       knownServiceUUIDs: ['generic_access', 'heart_rate'],
+     })]));
+}
+
+// Returns the same fake peripheral as setUpHealthThermometerDevice() except
+// that connecting to the peripheral will succeed.
+function setUpConnectableHealthThermometerDevice() {
+  let fake_peripheral;
+  return setUpHealthThermometerDevice()
+    .then(_ => fake_peripheral = _)
+    .then(() => fake_peripheral.setNextGATTConnectionResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => fake_peripheral);
+}
+
+// Returns an object containing a BluetoothDevice discovered using |options|,
+// its corresponding FakePeripheral and FakeRemoteGATTServices.
+// The simulated device is called 'Health Thermometer' it has two known service
+// UUIDs: 'generic_access' and 'health_thermometer' which correspond to two
+// services with the same UUIDs. The 'health thermometer' service contains three
+// characteristics:
+//  - 'temperature_measurement' (indicate),
+//  - 'temperature_type' (read),
+//  - 'measurement_interval' (read, write, indicate)
+// The 'measurement_interval' characteristic contains a
+// 'gatt.client_characteristic_configuration' descriptor and a
+// 'characteristic_user_description' descriptor.
+// The device has been connected to and its attributes are ready to be
+// discovered.
+function getHealthThermometerDevice(options) {
+  let result;
+  return getConnectedHealthThermometerDevice(options)
+    .then(_ => result = _)
+    .then(() => result.fake_peripheral.setNextGATTDiscoveryResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => result);
+}
+
+// Similar to getHealthThermometerDevice except that the peripheral has
+// two 'health_thermometer' services.
+function getTwoHealthThermometerServicesDevice(options) {
+  let device;
+  let fake_peripheral;
+  let fake_generic_access;
+  let fake_health_thermometer1;
+  let fake_health_thermometer2;
+
+  return getConnectedHealthThermometerDevice(options)
+    .then(result => {
+      ({
+        device,
+        fake_peripheral,
+        fake_generic_access,
+        fake_health_thermometer: fake_health_thermometer1,
+      } = result);
+    })
+    .then(() => fake_peripheral.addFakeService({uuid: 'health_thermometer'}))
+    .then(s => fake_health_thermometer2 = s)
+    .then(() => fake_peripheral.setNextGATTDiscoveryResponse({
+      code: HCI_SUCCESS}))
+    .then(() => ({
+      device: device,
+      fake_peripheral: fake_peripheral,
+      fake_generic_access: fake_generic_access,
+      fake_health_thermometer1: fake_health_thermometer1,
+      fake_health_thermometer2: fake_health_thermometer2
+    }));
+}
+
+// Returns an object containing a Health Thermometer BluetoothRemoteGattService
+// and its corresponding FakeRemoteGATTService.
+function getHealthThermometerService() {
+  let result;
+  return getHealthThermometerDevice()
+    .then(r => result = r)
+    .then(() => result.device.gatt.getPrimaryService('health_thermometer'))
+    .then(service => Object.assign(result, {
+      service,
+      fake_service: result.fake_health_thermometer,
+    }));
+}
+
+// Returns an object containing a Measurement Interval
+// BluetoothRemoteGATTCharacteristic and its corresponding
+// FakeRemoteGATTCharacteristic.
+function getMeasurementIntervalCharacteristic() {
+  let result;
+  return getHealthThermometerService()
+    .then(r => result = r)
+    .then(() => result.service.getCharacteristic('measurement_interval'))
+    .then(characteristic => Object.assign(result, {
+      characteristic,
+      fake_characteristic: result.fake_measurement_interval,
+    }));
+}
+
+function getUserDescriptionDescriptor() {
+  let result;
+  return getMeasurementIntervalCharacteristic()
+    .then(r => result = r)
+    .then(() => result.characteristic.getDescriptor(
+        'gatt.characteristic_user_description'))
+    .then(descriptor => Object.assign(result, {
+      descriptor,
+      fake_descriptor: result.fake_user_description,
+    }));
+}
+
+// Populates a fake_peripheral with various fakes appropriate for a health
+// thermometer.  This resolves to an associative array composed of the fakes,
+// including the |fake_peripheral|.
+function populateHealthThermometerFakes(fake_peripheral) {
+  let fake_generic_access, fake_health_thermometer, fake_measurement_interval,
+      fake_user_description, fake_cccd, fake_temperature_measurement,
+      fake_temperature_type;
+  return fake_peripheral.addFakeService({uuid: 'generic_access'})
+    .then(_ => fake_generic_access = _)
+    .then(() => fake_peripheral.addFakeService({
+        uuid: 'health_thermometer',
+    }))
+    .then(_ => fake_health_thermometer = _)
+    .then(() => fake_health_thermometer.addFakeCharacteristic({
+      uuid: 'measurement_interval',
+      properties: ['read', 'write', 'indicate'],
+    }))
+    .then(_ => fake_measurement_interval = _)
+    .then(() => fake_measurement_interval.addFakeDescriptor({
+      uuid: 'gatt.characteristic_user_description',
+    }))
+    .then(_ => fake_user_description = _)
+    .then(() => fake_measurement_interval.addFakeDescriptor({
+      uuid: 'gatt.client_characteristic_configuration',
+    }))
+    .then(_ => fake_cccd = _)
+    .then(() => fake_health_thermometer.addFakeCharacteristic({
+      uuid: 'temperature_measurement',
+      properties: ['indicate'],
+    }))
+    .then(_ => fake_temperature_measurement = _)
+    .then(() => fake_health_thermometer.addFakeCharacteristic({
+      uuid: 'temperature_type',
+      properties: ['read'],
+    }))
+    .then(_ => fake_temperature_type = _)
+    .then(() => ({
+      fake_peripheral,
+      fake_generic_access,
+      fake_health_thermometer,
+      fake_measurement_interval,
+      fake_cccd,
+      fake_user_description,
+      fake_temperature_measurement,
+      fake_temperature_type,
+    }));
+}
+
+// Similar to getHealthThermometerDevice except the GATT discovery
+// response has not been set yet so more attributes can still be added.
+function getConnectedHealthThermometerDevice(options) {
+  let device, fake_peripheral, fakes;
+  return getDiscoveredHealthThermometerDevice(options)
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() => fake_peripheral.setNextGATTConnectionResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => populateHealthThermometerFakes(fake_peripheral))
+    .then(_ => fakes = _)
+    .then(() => device.gatt.connect())
+    .then(() => Object.assign({device}, fakes));
+}
+
+// Returns an object containing a BluetoothDevice discovered using |options|,
+// its corresponding FakePeripheral and FakeRemoteGATTServices.
+// The simulated device is called 'Blocklist Device' and it has one known
+// service UUIDs |blocklist_test_service_uuid| which
+// correspond to a service with the same UUID. The
+// |blocklist_test_service_uuid| service contains two characteristics:
+//   - |blocklist_exclude_reads_characteristic_uuid| (read, write)
+//   - 'gap.peripheral_privacy_flag' (read, write)
+// The 'gap.peripheral_privacy_flag' characteristic contains three descriptors:
+//   - |blocklist_test_descriptor_uuid|
+//   - |blocklist_exclude_reads_descriptor_uuid|
+//   - 'gatt.client_characteristic_configuration'
+// These are special UUIDs that have been added to the blocklist found at
+// https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt
+// There are also test UUIDs that have been added to the test environment which
+// other implementations should add as test UUIDs as well.
+// The device has been connected to and its attributes are ready to be
+// discovered.
+function getBlocklistDevice(
+    options = {filters: [{services: [blocklist_test_service_uuid]}]}) {
+  let device, fake_peripheral, fake_blocklist_test_service,
+      fake_blocklist_exclude_reads_characteristic,
+      fake_blocklist_exclude_writes_characteristic,
+      fake_blocklist_descriptor,
+      fake_blocklist_exclude_reads_descriptor,
+      fake_blocklist_exclude_writes_descriptor;
+  return setUpPreconnectedDevice({
+    address: '11:11:11:11:11:11',
+    name: 'Blocklist Device',
+    knownServiceUUIDs: ['generic_access', blocklist_test_service_uuid],
+  })
+      .then(_ => fake_peripheral = _)
+      .then(() => requestDeviceWithTrustedClick(options))
+      .then(_ => device = _)
+      .then(() => fake_peripheral.setNextGATTConnectionResponse({
+        code: HCI_SUCCESS,
+      }))
+      .then(() => device.gatt.connect())
+      .then(() => fake_peripheral.addFakeService({
+        uuid: blocklist_test_service_uuid,
+      }))
+      .then(_ => fake_blocklist_test_service = _)
+      .then(() => fake_blocklist_test_service.addFakeCharacteristic({
+        uuid: blocklist_exclude_reads_characteristic_uuid,
+        properties: ['read', 'write'],
+      }))
+      .then(_ => fake_blocklist_exclude_reads_characteristic = _)
+      .then(() => fake_blocklist_test_service.addFakeCharacteristic({
+        uuid: 'gap.peripheral_privacy_flag',
+        properties: ['read', 'write'],
+      }))
+      .then(_ => fake_blocklist_exclude_writes_characteristic = _)
+      .then(() => fake_blocklist_exclude_writes_characteristic
+          .addFakeDescriptor({uuid: blocklist_test_descriptor_uuid}))
+      .then(_ => fake_blocklist_descriptor = _)
+      .then(() => fake_blocklist_exclude_writes_characteristic
+          .addFakeDescriptor({uuid: blocklist_exclude_reads_descriptor_uuid}))
+      .then(_ => fake_blocklist_exclude_reads_descriptor = _)
+      .then(() => fake_blocklist_exclude_writes_characteristic
+          .addFakeDescriptor({
+            uuid: 'gatt.client_characteristic_configuration'
+          }))
+      .then(_ => fake_blocklist_exclude_writes_descriptor = _)
+      .then(() => fake_peripheral.setNextGATTDiscoveryResponse({
+        code: HCI_SUCCESS,
+      }))
+      .then(() => ({
+        device,
+        fake_peripheral,
+        fake_blocklist_test_service,
+        fake_blocklist_exclude_reads_characteristic,
+        fake_blocklist_exclude_writes_characteristic,
+        fake_blocklist_descriptor,
+        fake_blocklist_exclude_reads_descriptor,
+        fake_blocklist_exclude_writes_descriptor,
+      }));
+}
+
+// Returns an object containing a Blocklist Test BluetoothRemoveGattService and
+// its corresponding FakeRemoteGATTService.
+function getBlocklistTestService() {
+  let result;
+  return getBlocklistDevice()
+      .then(_ => result = _)
+      .then(() =>
+          result.device.gatt.getPrimaryService(blocklist_test_service_uuid))
+      .then(service => Object.assign(result, {
+        service,
+        fake_service: result.fake_blocklist_test_service,
+      }));
+}
+
+// Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic
+// that excludes reads and its corresponding FakeRemoteGATTCharacteristic.
+function getBlocklistExcludeReadsCharacteristic() {
+  let result, fake_characteristic;
+  return getBlocklistTestService()
+      .then(_ => result = _)
+      .then(() => result.service.getCharacteristic(
+          blocklist_exclude_reads_characteristic_uuid))
+      .then(characteristic =>
+          Object.assign(
+              result, {
+                characteristic,
+                fake_characteristic:
+                    result.fake_blocklist_exclude_reads_characteristic
+              }));
+}
+
+// Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic
+// that excludes writes and its corresponding FakeRemoteGATTCharacteristic.
+function getBlocklistExcludeWritesCharacteristic() {
+  let result, fake_characteristic;
+  return getBlocklistTestService()
+      .then(_ => result = _)
+      .then(() => result.service.getCharacteristic(
+          'gap.peripheral_privacy_flag'))
+      .then(characteristic =>
+          Object.assign(
+              result, {
+                characteristic,
+                fake_characteristic:
+                    result.fake_blocklist_exclude_writes_characteristic
+              }));
+}
+
+// Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that
+// excludes reads and its corresponding FakeRemoteGATTDescriptor.
+function getBlocklistExcludeReadsDescriptor() {
+  let result;
+  return getBlocklistExcludeWritesCharacteristic()
+      .then(_ => result = _)
+      .then(() => result.characteristic.getDescriptor(
+          blocklist_exclude_reads_descriptor_uuid))
+      .then(descriptor => Object.assign(
+          result, {
+            descriptor,
+            fake_descriptor: result.fake_blocklist_exclude_reads_descriptor
+          }));
+}
+
+// Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that
+// excludes writes and its corresponding FakeRemoteGATTDescriptor.
+function getBlocklistExcludeWritesDescriptor() {
+  let result;
+  return getBlocklistExcludeWritesCharacteristic()
+      .then(_ => result = _)
+      .then(() => result.characteristic.getDescriptor(
+          'gatt.client_characteristic_configuration'))
+      .then(descriptor => Object.assign(
+          result, {
+            descriptor: descriptor,
+            fake_descriptor: result.fake_blocklist_exclude_writes_descriptor,
+          }));
+}
+
+// Returns the same device and fake peripheral as getHealthThermometerDevice()
+// after another frame (an iframe we insert) discovered the device,
+// connected to it and discovered its services.
+function getHealthThermometerDeviceWithServicesDiscovered(options) {
+  let device, fake_peripheral, fakes;
+  let iframe = document.createElement('iframe');
+  return setUpConnectableHealthThermometerDevice()
+    .then(_ => fake_peripheral = _)
+    .then(() => populateHealthThermometerFakes(fake_peripheral))
+    .then(_ => fakes = _)
+    .then(() => fake_peripheral.setNextGATTDiscoveryResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => new Promise(resolve => {
+      let src = '/bluetooth/resources/health-thermometer-iframe.html';
+      // TODO(509038): Can be removed once LayoutTests/bluetooth/* that use
+      // health-thermometer-iframe.html have been moved to
+      // LayoutTests/external/wpt/bluetooth/*
+      if (window.location.pathname.includes('/LayoutTests/')) {
+        src = '../../../external/wpt/bluetooth/resources/health-thermometer-iframe.html';
+      }
+      iframe.src = src;
+      document.body.appendChild(iframe);
+      iframe.addEventListener('load', resolve);
+    }))
+    .then(() => new Promise((resolve, reject) => {
+      callWithTrustedClick(() => {
+        iframe.contentWindow.postMessage({
+          type: 'DiscoverServices',
+          options: options
+        }, '*');
+      });
+
+      function messageHandler(messageEvent) {
+        if (messageEvent.data == 'DiscoveryComplete') {
+          window.removeEventListener('message', messageHandler);
+          resolve();
+        } else {
+          reject(new Error(`Unexpected message: ${messageEvent.data}`));
+        }
+      }
+      window.addEventListener('message', messageHandler);
+    }))
+    .then(() => requestDeviceWithTrustedClick(options))
+    .then(_ => device = _)
+    .then(device => device.gatt.connect())
+    .then(_ => Object.assign({device}, fakes));
+}
+
+// Similar to getHealthThermometerDevice() except the device has no services,
+// characteristics, or descriptors.
+function getEmptyHealthThermometerDevice(options) {
+  return getDiscoveredHealthThermometerDevice(options)
+    .then(({device, fake_peripheral}) => {
+      return fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS})
+        .then(() => device.gatt.connect())
+        .then(() => fake_peripheral.setNextGATTDiscoveryResponse({
+          code: HCI_SUCCESS}))
+        .then(() => ({
+          device: device,
+          fake_peripheral: fake_peripheral
+        }));
+    });
+}
+
+// Similar to getHealthThermometerService() except the service has no
+// characteristics or included services.
+function getEmptyHealthThermometerService(options) {
+  let device;
+  let fake_peripheral;
+  let fake_health_thermometer;
+  return getDiscoveredHealthThermometerDevice(options)
+    .then(result => ({device, fake_peripheral} = result))
+    .then(() => fake_peripheral.setNextGATTConnectionResponse({
+      code: HCI_SUCCESS}))
+    .then(() => device.gatt.connect())
+    .then(() => fake_peripheral.addFakeService({uuid: 'health_thermometer'}))
+    .then(s => fake_health_thermometer = s)
+    .then(() => fake_peripheral.setNextGATTDiscoveryResponse({
+      code: HCI_SUCCESS}))
+    .then(() => device.gatt.getPrimaryService('health_thermometer'))
+    .then(service => ({
+      service: service,
+      fake_health_thermometer: fake_health_thermometer,
+    }));
+}
+
+// Returns a BluetoothDevice discovered using |options| and its
+// corresponding FakePeripheral.
+// The simulated device is called 'HID Device' it has three known service
+// UUIDs: 'generic_access', 'device_information', 'human_interface_device'.
+// The primary service with 'device_information' UUID has a characteristics
+// with UUID 'serial_number_string'. The device has been connected to and its
+// attributes are ready to be discovered.
+function getHIDDevice(options) {
+  let device, fake_peripheral;
+  return getConnectedHIDDevice(options)
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() => fake_peripheral.setNextGATTDiscoveryResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => ({device, fake_peripheral}));
+}
+
+// Similar to getHealthThermometerDevice except the GATT discovery
+// response has not been set yet so more attributes can still be added.
+// TODO(crbug.com/719816): Add descriptors.
+function getConnectedHIDDevice(options) {
+  let device, fake_peripheral;
+  return setUpPreconnectedDevice({
+      address: '10:10:10:10:10:10',
+      name: 'HID Device',
+      knownServiceUUIDs: [
+        'generic_access',
+        'device_information',
+        'human_interface_device',
+      ],
+    })
+    .then(_ => (fake_peripheral = _))
+    .then(() => requestDeviceWithTrustedClick(options))
+    .then(_ => (device = _))
+    .then(() => fake_peripheral.setNextGATTConnectionResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => device.gatt.connect())
+    .then(() => fake_peripheral.addFakeService({
+      uuid: 'generic_access',
+    }))
+    .then(() => fake_peripheral.addFakeService({
+      uuid: 'device_information',
+    }))
+    // Blocklisted Characteristic:
+    // https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt
+    .then(dev_info => dev_info.addFakeCharacteristic({
+      uuid: 'serial_number_string',
+      properties: ['read'],
+    }))
+    .then(() => fake_peripheral.addFakeService({
+      uuid: 'human_interface_device',
+    }))
+    .then(() => ({device, fake_peripheral}));
+}
+
+// Similar to getHealthThermometerDevice() except the device
+// is not connected and thus its services have not been
+// discovered.
+function getDiscoveredHealthThermometerDevice(
+  options = {filters: [{services: ['health_thermometer']}]}) {
+  return setUpHealthThermometerDevice()
+  .then(fake_peripheral => {
+    return requestDeviceWithTrustedClick(options)
+      .then(device => ({
+        device: device,
+        fake_peripheral: fake_peripheral
+      }));
+  });
+}
diff --git a/bluetooth/resources/health-thermometer-iframe.html b/bluetooth/resources/health-thermometer-iframe.html
new file mode 100644
index 0000000..5e24f62
--- /dev/null
+++ b/bluetooth/resources/health-thermometer-iframe.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<script>
+let device, gatt;
+
+function requestDeviceWithOptionsAndConnect(options) {
+  return navigator.bluetooth.requestDevice(options)
+      .then(device => device.gatt.connect());
+}
+
+window.addEventListener('message', (messageEvent) => {
+  switch (messageEvent.data.type) {
+    case 'RequestDevice':
+      navigator.bluetooth.requestDevice({
+        filters: [{services: ['generic_access']}]
+      })
+          .then(device => {
+            if (device.constructor.name === 'BluetoothDevice') {
+              parent.postMessage('Success', '*');
+            } else {
+              parent.postMessage(
+                  `FAIL: requestDevice in iframe returned ${device.name}`, '*');
+            }}).catch(err => parent.postMessage(`${err.name}: ${err.message}`,
+                '*'));
+      break;
+    case 'RequestAndConnect':
+      requestDeviceWithOptionsAndConnect(messageEvent.data.options)
+          .then(_ => {
+            gatt = _;
+            device = gatt.device;
+            parent.postMessage('Connected', '*');
+          }).catch(err => {
+            parent.postMessage(`FAIL: ${err}`, '*');
+          });
+      break;
+    case 'DiscoverServices':
+      requestDeviceWithOptionsAndConnect(messageEvent.data.options)
+          .then(gatt => gatt.getPrimaryServices())
+          .then(() => parent.postMessage('DiscoveryComplete', '*'))
+          .catch(err => {
+            parent.postMessage(`FAIL: ${err}`, '*');
+          });
+      break;
+    case 'GetService':
+      if (typeof gatt === 'undefined') {
+        parent.postMessage('FAIL: no GATT server', '*');
+        break;
+      }
+      gatt.getPrimaryService(messageEvent.data.options)
+          .then(() => parent.postMessage('ServiceReceived', '*'))
+          .catch(err => parent.postMessage(`FAIL: ${err}`, '*'));
+      break;
+    default:
+      parent.postMessage(`FAIL: Bad message type: ${messageEvent.data.type}`,
+          '*');
+  }
+});
+</script>
diff --git a/bluetooth/script-tests/base_test_html.template b/bluetooth/script-tests/base_test_html.template
new file mode 100644
index 0000000..714333c
--- /dev/null
+++ b/bluetooth/script-tests/base_test_html.template
@@ -0,0 +1,10 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+TEST
+</script>
diff --git a/bluetooth/script-tests/characteristic/characteristic-is-removed.js b/bluetooth/script-tests/characteristic/characteristic-is-removed.js
new file mode 100644
index 0000000..8ff747c
--- /dev/null
+++ b/bluetooth/script-tests/characteristic/characteristic-is-removed.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+                                  'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+    .then(() => characteristic.getDescriptor(user_description.name))
+    .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+    .then(() => fake_characteristic.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+      characteristic.CALLS([
+        getDescriptor(user_description.name)|
+        getDescriptors(user_description.name)[UUID]|
+        getDescriptors()|
+        readValue()|
+        writeValue(new Uint8Array(1))|
+        startNotifications()
+      ]), expected)),
+    test_desc);
diff --git a/bluetooth/script-tests/characteristic/descriptor-get-same-object.js b/bluetooth/script-tests/characteristic/descriptor-get-same-object.js
new file mode 100644
index 0000000..4e6bc35
--- /dev/null
+++ b/bluetooth/script-tests/characteristic/descriptor-get-same-object.js
@@ -0,0 +1,32 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME should return the same object.';
+let characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic} = _))
+    .then(() => Promise.all([
+      characteristic.CALLS([
+        getDescriptor(user_description.alias)|
+        getDescriptors(user_description.alias)
+      ]),
+      characteristic.FUNCTION_NAME(user_description.name),
+      characteristic.FUNCTION_NAME(user_description.uuid)
+    ]))
+    .then(descriptors_arrays => {
+      assert_true(descriptors_arrays.length > 0)
+
+      // Convert to arrays if necessary.
+      for (let i = 0; i < descriptors_arrays.length; i++) {
+        descriptors_arrays[i] = [].concat(descriptors_arrays[i]);
+      }
+
+      for (let i = 1; i < descriptors_arrays.length; i++) {
+        assert_equals(descriptors_arrays[0].length,
+            descriptors_arrays[i].length);
+      }
+
+      let base_set = new Set(descriptors_arrays[0]);
+      for (let descriptors of descriptors_arrays) {
+        descriptors.forEach(descriptor => assert_true(base_set.has(descriptor)));
+      }
+    }), test_desc);
diff --git a/bluetooth/script-tests/characteristic/service-is-removed.js b/bluetooth/script-tests/characteristic/service-is-removed.js
new file mode 100644
index 0000000..2f58240
--- /dev/null
+++ b/bluetooth/script-tests/characteristic/service-is-removed.js
@@ -0,0 +1,20 @@
+// TODO(https://crbug.com/672127) Use this test case to test the rest of
+// characteristic functions.
+'use strict';
+const test_desc = 'Service is removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+    .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        characteristic.CALLS([
+          getDescriptor(user_description.name)|
+          getDescriptors(user_description.uuid)[UUID]|
+          getDescriptors(user_description.name)]),
+        expected,
+        'Service got removed.')),
+    test_desc);
diff --git a/bluetooth/script-tests/descriptor/service-is-removed.js b/bluetooth/script-tests/descriptor/service-is-removed.js
new file mode 100644
index 0000000..5373364
--- /dev/null
+++ b/bluetooth/script-tests/descriptor/service-is-removed.js
@@ -0,0 +1,18 @@
+'use strict';
+const test_desc = 'Service gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let descriptor, fake_peripheral, fake_service;
+
+bluetooth_test(() => getUserDescriptionDescriptor()
+    .then(_ => ({descriptor, fake_peripheral, fake_service} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        descriptor.CALLS([
+          readValue()|
+          writeValue(new ArrayBuffer(1 /* length */))
+        ]),
+        expected,
+        'Service got removed.')),
+    test_desc);
diff --git a/bluetooth/script-tests/server/disconnect-called-before.js b/bluetooth/script-tests/server/disconnect-called-before.js
new file mode 100644
index 0000000..57704ee
--- /dev/null
+++ b/bluetooth/script-tests/server/disconnect-called-before.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'disconnect() called before FUNCTION_NAME. ' +
+    'Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.disconnect())
+    .then(() => assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('health_thermometer')|
+          getPrimaryServices()|
+          getPrimaryServices('health_thermometer')[UUID]]),
+        expected)),
+    test_desc);
diff --git a/bluetooth/script-tests/server/disconnect-called-during-error.js b/bluetooth/script-tests/server/disconnect-called-during-error.js
new file mode 100644
index 0000000..edabb07
--- /dev/null
+++ b/bluetooth/script-tests/server/disconnect-called-during-error.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'disconnect() called during a FUNCTION_NAME ' +
+    'call that fails. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.', 'NetworkError');
+let device;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(_ => ({device} = _))
+    .then(() => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('health_thermometer')|
+          getPrimaryServices()|
+          getPrimaryServices('health_thermometer')[UUID]
+        ]),
+        expected)
+      device.gatt.disconnect();
+      return promise;
+    }),
+    test_desc);
diff --git a/bluetooth/script-tests/server/disconnect-called-during-success.js b/bluetooth/script-tests/server/disconnect-called-during-success.js
new file mode 100644
index 0000000..84157a0
--- /dev/null
+++ b/bluetooth/script-tests/server/disconnect-called-during-success.js
@@ -0,0 +1,23 @@
+'use strict';
+const test_desc = 'disconnect() called during a FUNCTION_NAME call that ' +
+    'succeeds. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+     'NetworkError');
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('health_thermometer')|
+          getPrimaryServices()|
+          getPrimaryServices('health_thermometer')[UUID]
+        ]),
+        expected);
+      device.gatt.disconnect();
+      return promise;
+    }), test_desc);
diff --git a/bluetooth/script-tests/server/disconnect-invalidates-objects.js b/bluetooth/script-tests/server/disconnect-invalidates-objects.js
new file mode 100644
index 0000000..995fda3
--- /dev/null
+++ b/bluetooth/script-tests/server/disconnect-invalidates-objects.js
@@ -0,0 +1,39 @@
+'use strict';
+const test_desc = 'Calls on services after we disconnect and connect again. '+
+   'Should reject with InvalidStateError.';
+let device, services;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.CALLS([
+      getPrimaryService('health_thermometer')|
+      getPrimaryServices()|
+      getPrimaryServices('health_thermometer')[UUID]]))
+    // Convert to array if necessary.
+    .then(s => services = [].concat(s))
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => {
+      let promises = Promise.resolve();
+      for (let service of services) {
+        let error = new DOMException(
+          `Service with UUID ${service.uuid} is no longer valid. Remember ` +
+          `to retrieve the service again after reconnecting.`,
+          'InvalidStateError');
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristic('measurement_interval'),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics(),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics('measurement_interval'),
+                error));
+      }
+      return promises;
+    }), test_desc);
diff --git a/bluetooth/script-tests/server/disconnected-device.js b/bluetooth/script-tests/server/disconnected-device.js
new file mode 100644
index 0000000..2b60116
--- /dev/null
+++ b/bluetooth/script-tests/server/disconnected-device.js
@@ -0,0 +1,20 @@
+'use strict';
+const test_desc = 'FUNCTION_NAME called before connecting. Reject with ' +
+    'NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('health_thermometer')|
+          getPrimaryServices()|
+          getPrimaryServices('health_thermometer')[UUID]
+        ]),
+        expected)),
+    test_desc);
diff --git a/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js b/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js
new file mode 100644
index 0000000..e9e9723
--- /dev/null
+++ b/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js
@@ -0,0 +1,25 @@
+'use strict';
+const test_desc = 'Request for absent service without permission. Should ' +
+    'Reject with SecurityError even if services have been discovered already.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+    'SecurityError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(_ => ({device} = _))
+    .then(() => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.CALLS([
+            getPrimaryService(glucose.alias)|
+            getPrimaryServices(glucose.alias)[UUID]
+          ]), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.FUNCTION_NAME(glucose.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.FUNCTION_NAME(glucose.uuid), expected)])),
+    test_desc);
diff --git a/bluetooth/script-tests/server/discovery-complete-service-not-found.js b/bluetooth/script-tests/server/discovery-complete-service-not-found.js
new file mode 100644
index 0000000..6b745d7
--- /dev/null
+++ b/bluetooth/script-tests/server/discovery-complete-service-not-found.js
@@ -0,0 +1,16 @@
+'use strict';
+const test_desc = 'Request for absent service. Must reject with ' +
+    'NotFoundError even when the services have previously been discovered.';
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['glucose']})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('glucose')|
+          getPrimaryServices('glucose')[UUID]
+        ]),
+        new DOMException(
+            `No Services matching UUID ${glucose.uuid} found in Device.`,
+            'NotFoundError'))),
+    test_desc);
diff --git a/bluetooth/script-tests/server/garbage-collection-ran-during-error.js b/bluetooth/script-tests/server/garbage-collection-ran-during-error.js
new file mode 100644
index 0000000..35e838e
--- /dev/null
+++ b/bluetooth/script-tests/server/garbage-collection-ran-during-error.js
@@ -0,0 +1,25 @@
+'use strict';
+const test_desc = 'Garbage Collection ran during a FUNCTION_NAME ' +
+    'call that failed. Should not crash.'
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' +
+    'with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.CALLS([
+            getPrimaryService('health_thermometer')|
+            getPrimaryServices()|
+            getPrimaryServices('health_thermometer')[UUID]
+          ]),
+          expected);
+      // Disconnect called to clear attributeInstanceMap and allow the
+      // object to get garbage collected.
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
diff --git a/bluetooth/script-tests/server/garbage-collection-ran-during-success.js b/bluetooth/script-tests/server/garbage-collection-ran-during-success.js
new file mode 100644
index 0000000..7976bbd
--- /dev/null
+++ b/bluetooth/script-tests/server/garbage-collection-ran-during-success.js
@@ -0,0 +1,24 @@
+'use strict';
+const test_desc = 'Garbage Collection ran during a FUNCTION_NAME call that ' +
+    'succeeds. Should not crash.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.CALLS([
+            getPrimaryService('health_thermometer') |
+            getPrimaryServices() |
+            getPrimaryServices('health_thermometer')[UUID]]),
+          expected);
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
diff --git a/bluetooth/script-tests/server/get-different-service-after-reconnection.js b/bluetooth/script-tests/server/get-different-service-after-reconnection.js
new file mode 100644
index 0000000..e72128a
--- /dev/null
+++ b/bluetooth/script-tests/server/get-different-service-after-reconnection.js
@@ -0,0 +1,35 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME after a disconnection should return ' +
+    'a different object.';
+let device, services_first_connection, services_second_connection;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.CALLS([
+      getPrimaryService('health_thermometer')|
+      getPrimaryServices()|
+      getPrimaryServices('health_thermometer')[UUID]]))
+    .then(services => services_first_connection = services)
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => device.gatt.PREVIOUS_CALL)
+    .then(services => services_second_connection = services)
+    .then(() => {
+      // Convert to arrays if necessary.
+      services_first_connection = [].concat(services_first_connection);
+      services_second_connection = [].concat(services_second_connection);
+
+      assert_equals(services_first_connection.length,
+          services_second_connection.length);
+
+      let first_connection_set = new Set(services_first_connection);
+      let second_connection_set = new Set(services_second_connection);
+
+      // The two sets should be disjoint.
+      let common_services = services_first_connection.filter(
+          val => second_connection_set.has(val));
+      assert_equals(common_services.length, 0);
+    }), test_desc);
diff --git a/bluetooth/script-tests/server/get-same-object.js b/bluetooth/script-tests/server/get-same-object.js
new file mode 100644
index 0000000..3b3bdd1
--- /dev/null
+++ b/bluetooth/script-tests/server/get-same-object.js
@@ -0,0 +1,33 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME should return the same object.';
+let device;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']})
+    .then(({device}) => Promise.all([
+      device.gatt.CALLS([
+        getPrimaryService('health_thermometer')|
+        getPrimaryServices()|
+        getPrimaryServices('health_thermometer')[UUID]]),
+      device.gatt.PREVIOUS_CALL]))
+    .then(([services_first_call, services_second_call]) => {
+      // Convert to arrays if necessary.
+      services_first_call = [].concat(services_first_call);
+      services_second_call = [].concat(services_second_call);
+
+      assert_equals(services_first_call.length, services_second_call.length);
+
+      let first_call_set = new Set(services_first_call);
+      assert_equals(services_first_call.length, first_call_set.size);
+      let second_call_set = new Set(services_second_call);
+      assert_equals(services_second_call.length, second_call_set.size);
+
+      services_first_call.forEach(service => {
+        assert_true(second_call_set.has(service))
+      });
+
+      services_second_call.forEach(service => {
+        assert_true(first_call_set.has(service));
+      });
+    }), test_desc);
diff --git a/bluetooth/script-tests/server/invalid-service-name.js b/bluetooth/script-tests/server/invalid-service-name.js
new file mode 100644
index 0000000..52cbb24
--- /dev/null
+++ b/bluetooth/script-tests/server/invalid-service-name.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'Wrong Service name. Reject with TypeError.';
+const expected = new DOMException(
+    "Failed to execute 'FUNCTION_NAME' on " +
+    "'BluetoothRemoteGATTServer': Invalid Service name: " +
+    "'wrong_name'. It must be a valid UUID alias (e.g. 0x1234), " +
+    "UUID (lowercase hex characters e.g. " +
+    "'00001234-0000-1000-8000-00805f9b34fb'), " +
+    "or recognized standard name from " +
+    "https://www.bluetooth.com/specifications/gatt/services" +
+    " e.g. 'alert_notification'.",
+    'TypeError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('wrong_name')|
+          getPrimaryServices('wrong_name')
+        ]),
+        expected,
+        'Wrong Service name passed.')),
+    test_desc);
diff --git a/bluetooth/script-tests/server/no-permission-absent-service.js b/bluetooth/script-tests/server/no-permission-absent-service.js
new file mode 100644
index 0000000..200dab3
--- /dev/null
+++ b/bluetooth/script-tests/server/no-permission-absent-service.js
@@ -0,0 +1,23 @@
+'use strict';
+const test_desc = 'Request for absent service without permission. ' +
+    'Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+    'to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+    'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.CALLS([
+            getPrimaryService(glucose.alias)|
+            getPrimaryServices(glucose.alias)[UUID]
+          ]), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.FUNCTION_NAME(glucose.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.FUNCTION_NAME(glucose.uuid), expected)])),
+    test_desc);
diff --git a/bluetooth/script-tests/server/no-permission-for-any-service.js b/bluetooth/script-tests/server/no-permission-for-any-service.js
new file mode 100644
index 0000000..60e3ef0
--- /dev/null
+++ b/bluetooth/script-tests/server/no-permission-for-any-service.js
@@ -0,0 +1,17 @@
+'use strict';
+const test_desc = 'Request for present service without permission to access ' +
+    'any service. Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access any service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+     'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('heart_rate')|
+          getPrimaryServices()|
+          getPrimaryServices('heart_rate')[UUID]]),
+        expected)),
+    test_desc);
diff --git a/bluetooth/script-tests/server/no-permission-present-service.js b/bluetooth/script-tests/server/no-permission-present-service.js
new file mode 100644
index 0000000..3257410
--- /dev/null
+++ b/bluetooth/script-tests/server/no-permission-present-service.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'Request for present service without permission. ' +
+    'Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+    'to \'optionalServices\' in requestDevice() options. https://goo.gl/HxfxSQ',
+    'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.CALLS([
+            getPrimaryService(generic_access.alias)|
+            getPrimaryServices(generic_access.alias)[UUID]
+          ]), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.FUNCTION_NAME(generic_access.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.FUNCTION_NAME(generic_access.uuid), expected)])),
+    test_desc);
diff --git a/bluetooth/script-tests/server/service-not-found.js b/bluetooth/script-tests/server/service-not-found.js
new file mode 100644
index 0000000..0fd2dac
--- /dev/null
+++ b/bluetooth/script-tests/server/service-not-found.js
@@ -0,0 +1,16 @@
+'use strict';
+const test_desc = 'Request for absent service. Reject with NotFoundError.';
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['glucose']
+    })
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.CALLS([
+          getPrimaryService('glucose')|
+          getPrimaryServices('glucose')[UUID]
+        ]),
+        new DOMException(
+            `No Services matching UUID ${glucose.uuid} found in Device.`,
+            'NotFoundError'))),
+    test_desc);
diff --git a/bluetooth/script-tests/service/blocklisted-characteristic.js b/bluetooth/script-tests/service/blocklisted-characteristic.js
new file mode 100644
index 0000000..b26f039
--- /dev/null
+++ b/bluetooth/script-tests/service/blocklisted-characteristic.js
@@ -0,0 +1,19 @@
+'use strict';
+const test_desc = 'Serial Number String characteristic is blocklisted. ' +
+    'Should reject with SecurityError.';
+const expected = new DOMException(
+    'getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX',
+    'SecurityError');
+
+bluetooth_test(() => getHIDDevice({
+  filters: [{services: ['device_information']}]
+})
+    .then(({device}) => device.gatt.getPrimaryService('device_information'))
+    .then(service => assert_promise_rejects_with_message(
+        service.CALLS([
+          getCharacteristic('serial_number_string')|
+          getCharacteristics('serial_number_string')[UUID]
+        ]),
+        expected,
+        'Serial Number String characteristic is blocklisted.')),
+    test_desc);
diff --git a/bluetooth/script-tests/service/characteristic-not-found.js b/bluetooth/script-tests/service/characteristic-not-found.js
new file mode 100644
index 0000000..366e046
--- /dev/null
+++ b/bluetooth/script-tests/service/characteristic-not-found.js
@@ -0,0 +1,15 @@
+'use strict';
+const test_desc = 'Request for absent characteristics with UUID. ' +
+    'Reject with NotFoundError.';
+
+bluetooth_test(() => getEmptyHealthThermometerService()
+    .then(({service}) => assert_promise_rejects_with_message(
+        service.CALLS([
+          getCharacteristic('battery_level')|
+          getCharacteristics('battery_level')[UUID]
+        ]),
+        new DOMException(
+            `No Characteristics matching UUID ${battery_level.uuid} found ` +
+            `in Service with UUID ${health_thermometer.uuid}.`,
+            'NotFoundError'))),
+    test_desc);
diff --git a/bluetooth/script-tests/service/garbage-collection-ran-during-error.js b/bluetooth/script-tests/service/garbage-collection-ran-during-error.js
new file mode 100644
index 0000000..33c6109
--- /dev/null
+++ b/bluetooth/script-tests/service/garbage-collection-ran-during-error.js
@@ -0,0 +1,24 @@
+'use strict';
+const test_desc = 'Garbage Collection ran during FUNCTION_NAME ' +
+    'call that fails. Should not crash';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => {
+      promise = assert_promise_rejects_with_message(
+          service.CALLS([
+            getCharacteristic('measurement_interval')|
+            getCharacteristics()|
+            getCharacteristics('measurement_interval')[UUID]
+          ]), expected);
+      // Disconnect called to clear attributeInstanceMap and allow the object to
+      // get garbage collected.
+      service.device.gatt.disconnect();
+    })
+    .then(runGarbageCollection)
+    .then(() => promise),
+    test_desc);
diff --git a/bluetooth/script-tests/service/get-same-object.js b/bluetooth/script-tests/service/get-same-object.js
new file mode 100644
index 0000000..db9d740
--- /dev/null
+++ b/bluetooth/script-tests/service/get-same-object.js
@@ -0,0 +1,24 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME should return the same object.';
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => Promise.all([
+      service.CALLS([
+        getCharacteristic('measurement_interval')|
+        getCharacteristics()|
+        getCharacteristics('measurement_interval')[UUID]]),
+      service.PREVIOUS_CALL]))
+    .then(([characteristics_first_call, characteristics_second_call]) => {
+      // Convert to arrays if necessary.
+      characteristics_first_call = [].concat(characteristics_first_call);
+      characteristics_second_call = [].concat(characteristics_second_call);
+
+      let first_call_set = new Set(characteristics_first_call);
+      assert_equals(characteristics_first_call.length, first_call_set.size);
+      let second_call_set = new Set(characteristics_second_call);
+      assert_equals(characteristics_second_call.length, second_call_set.size);
+
+      characteristics_first_call.forEach(characteristic => {
+        assert_true(second_call_set.has(characteristic));
+      });
+    }), test_desc);
diff --git a/bluetooth/script-tests/service/invalid-characteristic-name.js b/bluetooth/script-tests/service/invalid-characteristic-name.js
new file mode 100644
index 0000000..74cba7e
--- /dev/null
+++ b/bluetooth/script-tests/service/invalid-characteristic-name.js
@@ -0,0 +1,23 @@
+'use strict';
+const test_desc = 'Wrong Characteristic name. Reject with TypeError.';
+const expected = new DOMException(
+    "Failed to execute 'FUNCTION_NAME' on " +
+    "'BluetoothRemoteGATTService': Invalid Characteristic name: " +
+    "'wrong_name'. " +
+    "It must be a valid UUID alias (e.g. 0x1234), " +
+    "UUID (lowercase hex characters e.g. " +
+    "'00001234-0000-1000-8000-00805f9b34fb'), " +
+    "or recognized standard name from " +
+    "https://www.bluetooth.com/specifications/gatt/characteristics" +
+    " e.g. 'aerobic_heart_rate_lower_limit'.",
+    'TypeError');
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => assert_promise_rejects_with_message(
+        service.CALLS([
+          getCharacteristic('wrong_name')|
+          getCharacteristics('wrong_name')
+        ]),
+        expected,
+        'Wrong Characteristic name passed.')),
+    test_desc);
diff --git a/bluetooth/script-tests/service/reconnect-during.js b/bluetooth/script-tests/service/reconnect-during.js
new file mode 100644
index 0000000..cc71547
--- /dev/null
+++ b/bluetooth/script-tests/service/reconnect-during.js
@@ -0,0 +1,36 @@
+'use strict';
+const test_desc = 'disconnect() and connect() called during ' +
+    'FUNCTION_NAME. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+  filters: [{services: [health_thermometer.name]}],
+})
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService(health_thermometer.name))
+    .then(service => Promise.all([
+      // 1. Make a call to service.FUNCTION_NAME, while the service is still
+      // valid.
+      assert_promise_rejects_with_message(service.CALLS([
+        getCharacteristic(measurement_interval.name)|
+        getCharacteristics()|
+        getCharacteristics(measurement_interval.name)[UUID]
+      ]), expected),
+
+      // 2. disconnect() and connect before the initial call completes.
+      // This is accomplished by making the calls without waiting for the
+      // earlier promises to resolve.
+      // connect() guarantees on OS-level connection, but disconnect()
+      // only disconnects the current instance.
+      // getHealthThermometerDeviceWithServicesDiscovered holds another
+      // connection in an iframe, so disconnect() and connect() are certain to
+      // reconnect.  However, disconnect() will invalidate the service object so
+      // the subsequent calls made to it will fail, even after reconnecting.
+      device.gatt.disconnect(),
+      device.gatt.connect()
+    ])),
+    test_desc);
diff --git a/bluetooth/script-tests/service/service-is-removed.js b/bluetooth/script-tests/service/service-is-removed.js
new file mode 100644
index 0000000..aaf0f14
--- /dev/null
+++ b/bluetooth/script-tests/service/service-is-removed.js
@@ -0,0 +1,20 @@
+'use strict';
+const test_desc = 'Service is removed before FUNCTION_NAME call. ' +
+    'Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let service, fake_service, fake_peripheral;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(_ => ({service, fake_service, fake_peripheral} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        service.CALLS([
+          getCharacteristic('measurement_interval')|
+          getCharacteristics()|
+          getCharacteristics('measurement_interval')[UUID]
+        ]),
+        expected,
+        'Service got removed.')),
+    test_desc);
diff --git a/bluetooth/server/connect/connection-succeeds.https.html b/bluetooth/server/connect/connection-succeeds.https.html
new file mode 100644
index 0000000..aebd681
--- /dev/null
+++ b/bluetooth/server/connect/connection-succeeds.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Device will connect';
+let device, fake_peripheral;
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() =>
+      fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.connect())
+    .then(gatt => assert_true(gatt.connected)),
+    test_desc);
+</script>
diff --git a/bluetooth/server/connect/garbage-collection-ran-during-success.https.html b/bluetooth/server/connect/garbage-collection-ran-during-success.https.html
new file mode 100644
index 0000000..f70befc
--- /dev/null
+++ b/bluetooth/server/connect/garbage-collection-ran-during-success.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during a connect call that ' +
+    'succeeds. Should not crash.';
+let device, fake_peripheral;
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() =>
+      fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}))
+    // Don't return the promise and let |device| go out of scope
+    // so that it gets garbage collected.
+    .then(() => device.gatt.connect())
+    .then(runGarbageCollection),
+    test_desc);
+</script>
diff --git a/bluetooth/server/connect/get-same-gatt-server.https.html b/bluetooth/server/connect/get-same-gatt-server.https.html
new file mode 100644
index 0000000..c3e3553
--- /dev/null
+++ b/bluetooth/server/connect/get-same-gatt-server.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Multiple connects should return the same gatt object.';
+let device, fake_peripheral;
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() =>
+      fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.connect())
+    // No second response is necessary because an ATT Bearer
+    // already exists from the first connection.
+    // See https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
+    // step 5.1.
+    .then(gatt1 => device.gatt.connect()
+        .then(gatt2 => [gatt1, gatt2]))
+    .then(([gatt1, gatt2]) => assert_equals(gatt1, gatt2)),
+    test_desc);
+</script>
diff --git a/bluetooth/server/device-same-object.https.html b/bluetooth/server/device-same-object.https.html
new file mode 100644
index 0000000..cfd4bc5
--- /dev/null
+++ b/bluetooth/server/device-same-object.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = '[SameObject] test for BluetoothRemoteGATTServer\'s device.';
+let device, fake_peripheral;
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() =>
+      fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.connect())
+    .then(gatt => assert_equals(gatt.device, gatt.device)),
+    test_desc);
+</script>
diff --git a/bluetooth/server/disconnect/connect-disconnect-twice.https.html b/bluetooth/server/disconnect/connect-disconnect-twice.https.html
new file mode 100644
index 0000000..e0d8439
--- /dev/null
+++ b/bluetooth/server/disconnect/connect-disconnect-twice.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Connect + Disconnect twice still results in ' +
+  '\'connected\' being false.';
+let device, fake_peripheral;
+
+// TODO(569716): Test that the disconnect signal was sent to the device.
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() => fake_peripheral.setNextGATTConnectionResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => device.gatt.connect()
+    .then(gattServer => {
+      gattServer.disconnect();
+      assert_false(gattServer.connected);
+    })
+    .then(() => device.gatt.connect())
+    .then(gattServer => {
+      gattServer.disconnect();
+      assert_false(gattServer.connected);
+    })), test_desc);
+</script>
diff --git a/bluetooth/server/disconnect/detach-gc.https.html b/bluetooth/server/disconnect/detach-gc.https.html
new file mode 100644
index 0000000..8b1459d
--- /dev/null
+++ b/bluetooth/server/disconnect/detach-gc.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<body>
+<script>
+'use strict';
+const test_desc = 'Detach frame then garbage collect. We shouldn\'t crash.';
+let iframe = document.createElement('iframe');
+
+bluetooth_test(() => setUpConnectableHealthThermometerDevice()
+    // 1. Load the iframe.
+    .then(() => new Promise(resolve => {
+      iframe.src = '/bluetooth/resources/health-thermometer-iframe.html';
+      document.body.appendChild(iframe);
+      iframe.addEventListener('load', resolve);
+    }))
+    // 2. Connect device, detach the iframe, and run garbage collection.
+    .then(() => new Promise(resolve => {
+      callWithTrustedClick(() => {
+        iframe.contentWindow.postMessage({
+          type: 'RequestAndConnect',
+          options: {filters: [{services: ['health_thermometer']}]}
+        }, '*');
+      });
+
+      window.onmessage = messageEvent => {
+        assert_equals(messageEvent.data, 'Connected');
+        iframe.remove();
+        runGarbageCollection().then(resolve);
+      }
+    })), test_desc)
+</script>
+</body>
diff --git a/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.html b/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.html
new file mode 100644
index 0000000..5dada7a
--- /dev/null
+++ b/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calling disconnect twice in a row still results in ' +
+  '\'connected\' being false.';
+let device, fake_peripheral;
+
+// TODO(569716): Test that the disconnect signal was sent to the device.
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() => fake_peripheral.setNextGATTConnectionResponse({
+      code: HCI_SUCCESS,
+    }))
+    .then(() => device.gatt.connect())
+    .then(gattServer => {
+      gattServer.disconnect();
+      assert_false(gattServer.connected);
+      gattServer.disconnect();
+      assert_false(gattServer.connected);
+    }), test_desc);
+</script>
diff --git a/bluetooth/server/disconnect/gc-detach.https.html b/bluetooth/server/disconnect/gc-detach.https.html
new file mode 100644
index 0000000..04ccede
--- /dev/null
+++ b/bluetooth/server/disconnect/gc-detach.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<body>
+<script>
+'use strict';
+const test_desc = 'Garbage collect then detach frame. We shouldn\'t crash.';
+let iframe = document.createElement('iframe');
+
+bluetooth_test(() => setUpConnectableHealthThermometerDevice()
+    // 1. Load the iframe.
+    .then((f) => new Promise(resolve => {
+      iframe.src = '/bluetooth/resources/health-thermometer-iframe.html';
+      document.body.appendChild(iframe);
+      iframe.addEventListener('load', resolve);
+    }))
+    // 2. Connect device, run garbage collection, and detach iframe.
+    .then(() => new Promise(resolve => {
+      callWithTrustedClick(() => {
+        iframe.contentWindow.postMessage({
+          type: 'RequestAndConnect',
+          options: {filters: [{services: ['health_thermometer']}]}
+        }, '*');
+      });
+
+      window.onmessage = messageEvent => {
+        assert_equals(messageEvent.data, 'Connected');
+        runGarbageCollection().then(() => {
+          iframe.remove();
+          resolve();
+        });
+      }
+    })), test_desc)
+</script>
+</body>
diff --git a/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.html b/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.html
new file mode 100644
index 0000000..af00c66
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called before getPrimaryService. ' +
+    'Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.disconnect())
+    .then(() => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('health_thermometer'),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.html b/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.html
new file mode 100644
index 0000000..6656ff1
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.html
@@ -0,0 +1,28 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called during a getPrimaryService ' +
+    'call that fails. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.', 'NetworkError');
+let device;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(_ => ({device} = _))
+    .then(() => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('health_thermometer'),
+        expected)
+      device.gatt.disconnect();
+      return promise;
+    }),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.html b/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.html
new file mode 100644
index 0000000..760be97
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called during a getPrimaryService call that ' +
+    'succeeds. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+     'NetworkError');
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('health_thermometer'),
+        expected);
+      device.gatt.disconnect();
+      return promise;
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.html b/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.html
new file mode 100644
index 0000000..4c3c577
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.html
@@ -0,0 +1,46 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls on services after we disconnect and connect again. '+
+   'Should reject with InvalidStateError.';
+let device, services;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService('health_thermometer'))
+    // Convert to array if necessary.
+    .then(s => services = [].concat(s))
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => {
+      let promises = Promise.resolve();
+      for (let service of services) {
+        let error = new DOMException(
+          `Service with UUID ${service.uuid} is no longer valid. Remember ` +
+          `to retrieve the service again after reconnecting.`,
+          'InvalidStateError');
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristic('measurement_interval'),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics(),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics('measurement_interval'),
+                error));
+      }
+      return promises;
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-disconnected-device.https.html b/bluetooth/server/getPrimaryService/gen-disconnected-device.https.html
new file mode 100644
index 0000000..abfd1a9
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-disconnected-device.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'getPrimaryService called before connecting. Reject with ' +
+    'NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('health_thermometer'),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.html b/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.html
new file mode 100644
index 0000000..098eec4
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.html
@@ -0,0 +1,32 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service without permission. Should ' +
+    'Reject with SecurityError even if services have been discovered already.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+    'SecurityError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(_ => ({device} = _))
+    .then(() => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(glucose.alias), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(glucose.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(glucose.uuid), expected)])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.html b/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.html
new file mode 100644
index 0000000..233a45ad
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.html
@@ -0,0 +1,23 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service. Must reject with ' +
+    'NotFoundError even when the services have previously been discovered.';
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['glucose']})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('glucose'),
+        new DOMException(
+            `No Services matching UUID ${glucose.uuid} found in Device.`,
+            'NotFoundError'))),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.html b/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.html
new file mode 100644
index 0000000..ab93d33
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during a getPrimaryService ' +
+    'call that failed. Should not crash.'
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' +
+    'with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService('health_thermometer'),
+          expected);
+      // Disconnect called to clear attributeInstanceMap and allow the
+      // object to get garbage collected.
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.html b/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.html
new file mode 100644
index 0000000..647aa50
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during a getPrimaryService call that ' +
+    'succeeds. Should not crash.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService('health_thermometer'),
+          expected);
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.html b/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.html
new file mode 100644
index 0000000..f09e4e6
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.html
@@ -0,0 +1,42 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getPrimaryService after a disconnection should return ' +
+    'a different object.';
+let device, services_first_connection, services_second_connection;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService('health_thermometer'))
+    .then(services => services_first_connection = services)
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => device.gatt.getPrimaryService('health_thermometer'))
+    .then(services => services_second_connection = services)
+    .then(() => {
+      // Convert to arrays if necessary.
+      services_first_connection = [].concat(services_first_connection);
+      services_second_connection = [].concat(services_second_connection);
+
+      assert_equals(services_first_connection.length,
+          services_second_connection.length);
+
+      let first_connection_set = new Set(services_first_connection);
+      let second_connection_set = new Set(services_second_connection);
+
+      // The two sets should be disjoint.
+      let common_services = services_first_connection.filter(
+          val => second_connection_set.has(val));
+      assert_equals(common_services.length, 0);
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-get-same-object.https.html b/bluetooth/server/getPrimaryService/gen-get-same-object.https.html
new file mode 100644
index 0000000..35893d2
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-get-same-object.https.html
@@ -0,0 +1,40 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getPrimaryService should return the same object.';
+let device;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']})
+    .then(({device}) => Promise.all([
+      device.gatt.getPrimaryService('health_thermometer'),
+      device.gatt.getPrimaryService('health_thermometer')]))
+    .then(([services_first_call, services_second_call]) => {
+      // Convert to arrays if necessary.
+      services_first_call = [].concat(services_first_call);
+      services_second_call = [].concat(services_second_call);
+
+      assert_equals(services_first_call.length, services_second_call.length);
+
+      let first_call_set = new Set(services_first_call);
+      assert_equals(services_first_call.length, first_call_set.size);
+      let second_call_set = new Set(services_second_call);
+      assert_equals(services_second_call.length, second_call_set.size);
+
+      services_first_call.forEach(service => {
+        assert_true(second_call_set.has(service))
+      });
+
+      services_second_call.forEach(service => {
+        assert_true(first_call_set.has(service));
+      });
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.html b/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.html
new file mode 100644
index 0000000..40777ee
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Wrong Service name. Reject with TypeError.';
+const expected = new DOMException(
+    "Failed to execute 'getPrimaryService' on " +
+    "'BluetoothRemoteGATTServer': Invalid Service name: " +
+    "'wrong_name'. It must be a valid UUID alias (e.g. 0x1234), " +
+    "UUID (lowercase hex characters e.g. " +
+    "'00001234-0000-1000-8000-00805f9b34fb'), " +
+    "or recognized standard name from " +
+    "https://www.bluetooth.com/specifications/gatt/services" +
+    " e.g. 'alert_notification'.",
+    'TypeError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('wrong_name'),
+        expected,
+        'Wrong Service name passed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.html b/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.html
new file mode 100644
index 0000000..7883ef3
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.html
@@ -0,0 +1,30 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service without permission. ' +
+    'Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+    'to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+    'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(glucose.alias), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(glucose.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(glucose.uuid), expected)])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.html b/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.html
new file mode 100644
index 0000000..566a9fb
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.html
@@ -0,0 +1,24 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for present service without permission to access ' +
+    'any service. Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access any service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+     'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('heart_rate'),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.html b/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.html
new file mode 100644
index 0000000..c9ce1b8
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for present service without permission. ' +
+    'Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+    'to \'optionalServices\' in requestDevice() options. https://goo.gl/HxfxSQ',
+    'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(generic_access.alias), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(generic_access.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryService(generic_access.uuid), expected)])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/gen-service-not-found.https.html b/bluetooth/server/getPrimaryService/gen-service-not-found.https.html
new file mode 100644
index 0000000..aca14c5
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/gen-service-not-found.https.html
@@ -0,0 +1,23 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service. Reject with NotFoundError.';
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['glucose']
+    })
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('glucose'),
+        new DOMException(
+            `No Services matching UUID ${glucose.uuid} found in Device.`,
+            'NotFoundError'))),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryService/service-found.https.html b/bluetooth/server/getPrimaryService/service-found.https.html
new file mode 100644
index 0000000..6fccff1
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/service-found.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for service. Should return right service';
+let device;
+
+bluetooth_test(() => getHealthThermometerDevice({
+  filters: [{services: ['health_thermometer']}],
+  optionalServices: ['generic_access']
+})
+    .then(_ => ({device} = _))
+    .then(() => Promise.all([
+      device.gatt.getPrimaryService(generic_access.alias),
+      device.gatt.getPrimaryService(generic_access.name),
+      device.gatt.getPrimaryService(generic_access.uuid)]))
+    .then(services => {
+      services.forEach(service => {
+        assert_equals(service.uuid, generic_access.uuid,
+            'Service UUID should be the same as requested UUID.');
+        assert_true(service.isPrimary,
+            'getPrimaryService should return a primary service.');
+        assert_equals(service.device, device,
+            'Service device should be the same as device.');
+      })
+    }), test_desc);
+</script>
diff --git a/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.html b/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.html
new file mode 100644
index 0000000..eb20225
--- /dev/null
+++ b/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+"use strict";
+const test_desc = 'Two iframes in the same origin should be able to access ' +
+    'each other\'s services';
+
+const iframe1 = document.createElement('iframe');
+const iframe2 = document.createElement('iframe');
+
+function add_iframe(iframe) {
+  let promise = new Promise(resolve => iframe.addEventListener('load', resolve));
+  iframe.src = '/bluetooth/resources/health-thermometer-iframe.html';
+  document.body.appendChild(iframe);
+  return promise;
+}
+
+function send_message(iframe, command, arg, assert_func) {
+  let promise = new Promise((resolve, reject) => {
+    window.addEventListener('message', (messageEvent) => {
+      try {
+        assert_func(messageEvent.data);
+      } catch (e) {
+        reject(e);
+      }
+      resolve();
+    }, { once: true });
+  });
+  if (command === 'RequestAndConnect') {
+    arg = {filters: [{services: [arg]}]};
+  }
+  callWithTrustedClick(() => iframe.contentWindow.postMessage({
+      type: command,
+      options: arg,
+    }, '*'));
+  return promise;
+}
+
+bluetooth_test(() => getHealthThermometerDevice()
+    // 1. Add the first iframe.
+    .then(() => add_iframe(iframe1))
+    // 2. Connect with the first iframe, requesting the health thermometer
+    // service.
+    .then(() => send_message(iframe1, 'RequestAndConnect', 'health_thermometer',
+        msg => assert_equals(msg, 'Connected')))
+    // 3. Access the health thermometer service with the first iframe
+    // (successfully).
+    .then(() => send_message(iframe1, 'GetService', 'health_thermometer',
+        msg => assert_equals(msg, 'ServiceReceived')))
+    // 4. Access the generic access service with the first iframe
+    // (unsuccessfully).
+    .then(() => send_message(iframe1, 'GetService', 'generic_access', msg => {
+        let split_msg = msg.split(': ');
+        assert_equals(split_msg[0], 'FAIL');
+        assert_equals(split_msg[1], 'SecurityError');
+    }))
+    // 5. Add the second iframe.
+    .then(() => add_iframe(iframe2))
+    // 6. Connect with the second iframe, requesting the generic access service.
+    .then(() => send_message(iframe2, 'RequestAndConnect', 'generic_access',
+        msg => assert_equals(msg, 'Connected')))
+    // 7. Access the health thermometer service with the second iframe
+    // (successfully).  Both iframes should have access to both services at this
+    // point since they have the same origin.
+    .then(() => send_message(iframe2, 'GetService', 'health_thermometer',
+        msg => assert_equals(msg, 'ServiceReceived')))
+    // 8. Access the generic access service with the second iframe
+    // (unsuccessfully).
+    .then(() => send_message(iframe2, 'GetService', 'generic_access',
+        msg => assert_equals(msg, 'ServiceReceived')))
+    // 9. Access the generic access service with the first iframe
+    // (successfully).
+    .then(() => send_message(iframe1, 'GetService', 'generic_access',
+        msg => assert_equals(msg, 'ServiceReceived'))),
+    test_desc);
+</script>
diff --git a/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.html b/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.html
new file mode 100644
index 0000000..6ed24fd
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for services. Does not return blocklisted service.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ', 'SecurityError');
+
+bluetooth_test(() => getConnectedHIDDevice({
+  filters: [{services: ['device_information']}],
+  optionalServices: ['human_interface_device']
+})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('human_interface_device'),
+        expected)),
+    test_desc);
+</script>
diff --git a/bluetooth/server/getPrimaryServices/blocklisted-services.https.html b/bluetooth/server/getPrimaryServices/blocklisted-services.https.html
new file mode 100644
index 0000000..2a34e0b
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/blocklisted-services.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for services. Does not return blocklisted service.';
+
+bluetooth_test(() => getHIDDevice({
+  filters: [{services: ['device_information']}],
+  optionalServices: ['generic_access', 'human_interface_device']
+})
+    .then(({device}) => device.gatt.getPrimaryServices())
+    .then(services => {
+      assert_equals(services.length, 2);
+      let uuid_set = new Set(services.map(s => s.uuid));
+
+      assert_equals(uuid_set.size, 2);
+      assert_true(uuid_set.has(BluetoothUUID.getService('generic_access')));
+      assert_true(uuid_set.has(BluetoothUUID.getService('device_information')));
+      assert_false(
+          uuid_set.has(BluetoothUUID.getService('human_interface_device')));
+    }), test_desc);
+</script>
diff --git a/bluetooth/server/getPrimaryServices/correct-services.https.html b/bluetooth/server/getPrimaryServices/correct-services.https.html
new file mode 100644
index 0000000..9fffbd5
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/correct-services.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Find correct services with UUID.';
+let device, fake_peripheral;
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+  filters: [{services: ['health_thermometer']}]
+})
+    .then(_ => ({device, fake_peripheral} = _))
+    .then(() => fake_peripheral.addFakeService({uuid: 'health_thermometer'}))
+    .then(fake_service => Promise.all([
+      fake_service.addFakeCharacteristic({
+        uuid: 'temperature_measurement', properties: ['indicate']}),
+      fake_service.addFakeCharacteristic({
+        uuid: 'temperature_measurement', properties: ['indicate']})
+    ]))
+    .then(() => fake_peripheral.setNextGATTDiscoveryResponse({code:HCI_SUCCESS}))
+    .then(() => device.gatt.getPrimaryServices('health_thermometer'))
+    .then(services => Promise.all([
+      services[0].getCharacteristics(),
+      services[1].getCharacteristics()]))
+    .then(([characteristics1, characteristics2]) => {
+      if (characteristics1.length === 2)
+        assert_equals(characteristics2.length, 3);
+      else if (characteristics2.length === 2)
+        assert_equals(characteristics1.length, 3);
+      else
+        assert_unreached('Invalid lengths.');
+    }), test_desc);
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.html
new file mode 100644
index 0000000..4449d2e
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called before getPrimaryServices. ' +
+    'Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.disconnect())
+    .then(() => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('health_thermometer'),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.html
new file mode 100644
index 0000000..3bf4ed6
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called before getPrimaryServices. ' +
+    'Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.disconnect())
+    .then(() => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices(),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.html
new file mode 100644
index 0000000..d7cb437
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.html
@@ -0,0 +1,28 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called during a getPrimaryServices ' +
+    'call that fails. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.', 'NetworkError');
+let device;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(_ => ({device} = _))
+    .then(() => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('health_thermometer'),
+        expected)
+      device.gatt.disconnect();
+      return promise;
+    }),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.html
new file mode 100644
index 0000000..27440af
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.html
@@ -0,0 +1,28 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called during a getPrimaryServices ' +
+    'call that fails. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.', 'NetworkError');
+let device;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(_ => ({device} = _))
+    .then(() => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices(),
+        expected)
+      device.gatt.disconnect();
+      return promise;
+    }),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.html
new file mode 100644
index 0000000..7b9955a
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called during a getPrimaryServices call that ' +
+    'succeeds. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+     'NetworkError');
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('health_thermometer'),
+        expected);
+      device.gatt.disconnect();
+      return promise;
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.html
new file mode 100644
index 0000000..4b09bce
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() called during a getPrimaryServices call that ' +
+    'succeeds. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+     'NetworkError');
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => {
+      let promise = assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices(),
+        expected);
+      device.gatt.disconnect();
+      return promise;
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.html
new file mode 100644
index 0000000..2ef6b59
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.html
@@ -0,0 +1,46 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls on services after we disconnect and connect again. '+
+   'Should reject with InvalidStateError.';
+let device, services;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryServices('health_thermometer'))
+    // Convert to array if necessary.
+    .then(s => services = [].concat(s))
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => {
+      let promises = Promise.resolve();
+      for (let service of services) {
+        let error = new DOMException(
+          `Service with UUID ${service.uuid} is no longer valid. Remember ` +
+          `to retrieve the service again after reconnecting.`,
+          'InvalidStateError');
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristic('measurement_interval'),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics(),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics('measurement_interval'),
+                error));
+      }
+      return promises;
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.html b/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.html
new file mode 100644
index 0000000..bc21ea1
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.html
@@ -0,0 +1,46 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls on services after we disconnect and connect again. '+
+   'Should reject with InvalidStateError.';
+let device, services;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryServices())
+    // Convert to array if necessary.
+    .then(s => services = [].concat(s))
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => {
+      let promises = Promise.resolve();
+      for (let service of services) {
+        let error = new DOMException(
+          `Service with UUID ${service.uuid} is no longer valid. Remember ` +
+          `to retrieve the service again after reconnecting.`,
+          'InvalidStateError');
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristic('measurement_interval'),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics(),
+                error));
+        promises = promises.then(() =>
+            assert_promise_rejects_with_message(
+                service.getCharacteristics('measurement_interval'),
+                error));
+      }
+      return promises;
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.html
new file mode 100644
index 0000000..0a87bce
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'getPrimaryServices called before connecting. Reject with ' +
+    'NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('health_thermometer'),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.html b/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.html
new file mode 100644
index 0000000..dbc4428
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'getPrimaryServices called before connecting. Reject with ' +
+    'NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+    'first with `device.gatt.connect`.',
+    'NetworkError');
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices(),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.html
new file mode 100644
index 0000000..460b94d
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.html
@@ -0,0 +1,32 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service without permission. Should ' +
+    'Reject with SecurityError even if services have been discovered already.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+    'SecurityError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(_ => ({device} = _))
+    .then(() => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(glucose.alias), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(glucose.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(glucose.uuid), expected)])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.html
new file mode 100644
index 0000000..38e0d45
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.html
@@ -0,0 +1,23 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service. Must reject with ' +
+    'NotFoundError even when the services have previously been discovered.';
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['glucose']})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('glucose'),
+        new DOMException(
+            `No Services matching UUID ${glucose.uuid} found in Device.`,
+            'NotFoundError'))),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.html
new file mode 100644
index 0000000..31ab862
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during a getPrimaryServices ' +
+    'call that failed. Should not crash.'
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' +
+    'with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices('health_thermometer'),
+          expected);
+      // Disconnect called to clear attributeInstanceMap and allow the
+      // object to get garbage collected.
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.html b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.html
new file mode 100644
index 0000000..a79060a
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during a getPrimaryServices ' +
+    'call that failed. Should not crash.'
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' +
+    'with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(),
+          expected);
+      // Disconnect called to clear attributeInstanceMap and allow the
+      // object to get garbage collected.
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.html
new file mode 100644
index 0000000..b6f0e4a
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during a getPrimaryServices call that ' +
+    'succeeds. Should not crash.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices('health_thermometer'),
+          expected);
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.html b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.html
new file mode 100644
index 0000000..baf7865
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during a getPrimaryServices call that ' +
+    'succeeds. Should not crash.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve services. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => {
+      promise = assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(),
+          expected);
+      device.gatt.disconnect();
+      return runGarbageCollection();
+    })
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.html
new file mode 100644
index 0000000..b87a24a
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.html
@@ -0,0 +1,42 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getPrimaryServices after a disconnection should return ' +
+    'a different object.';
+let device, services_first_connection, services_second_connection;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryServices('health_thermometer'))
+    .then(services => services_first_connection = services)
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => device.gatt.getPrimaryServices('health_thermometer'))
+    .then(services => services_second_connection = services)
+    .then(() => {
+      // Convert to arrays if necessary.
+      services_first_connection = [].concat(services_first_connection);
+      services_second_connection = [].concat(services_second_connection);
+
+      assert_equals(services_first_connection.length,
+          services_second_connection.length);
+
+      let first_connection_set = new Set(services_first_connection);
+      let second_connection_set = new Set(services_second_connection);
+
+      // The two sets should be disjoint.
+      let common_services = services_first_connection.filter(
+          val => second_connection_set.has(val));
+      assert_equals(common_services.length, 0);
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.html b/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.html
new file mode 100644
index 0000000..9ed3c50
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.html
@@ -0,0 +1,42 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getPrimaryServices after a disconnection should return ' +
+    'a different object.';
+let device, services_first_connection, services_second_connection;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']
+    })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryServices())
+    .then(services => services_first_connection = services)
+    .then(() => device.gatt.disconnect())
+    .then(() => device.gatt.connect())
+    .then(() => device.gatt.getPrimaryServices())
+    .then(services => services_second_connection = services)
+    .then(() => {
+      // Convert to arrays if necessary.
+      services_first_connection = [].concat(services_first_connection);
+      services_second_connection = [].concat(services_second_connection);
+
+      assert_equals(services_first_connection.length,
+          services_second_connection.length);
+
+      let first_connection_set = new Set(services_first_connection);
+      let second_connection_set = new Set(services_second_connection);
+
+      // The two sets should be disjoint.
+      let common_services = services_first_connection.filter(
+          val => second_connection_set.has(val));
+      assert_equals(common_services.length, 0);
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.html
new file mode 100644
index 0000000..9d213d5
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.html
@@ -0,0 +1,40 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getPrimaryServices should return the same object.';
+let device;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']})
+    .then(({device}) => Promise.all([
+      device.gatt.getPrimaryServices('health_thermometer'),
+      device.gatt.getPrimaryServices('health_thermometer')]))
+    .then(([services_first_call, services_second_call]) => {
+      // Convert to arrays if necessary.
+      services_first_call = [].concat(services_first_call);
+      services_second_call = [].concat(services_second_call);
+
+      assert_equals(services_first_call.length, services_second_call.length);
+
+      let first_call_set = new Set(services_first_call);
+      assert_equals(services_first_call.length, first_call_set.size);
+      let second_call_set = new Set(services_second_call);
+      assert_equals(services_second_call.length, second_call_set.size);
+
+      services_first_call.forEach(service => {
+        assert_true(second_call_set.has(service))
+      });
+
+      services_second_call.forEach(service => {
+        assert_true(first_call_set.has(service));
+      });
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-get-same-object.https.html b/bluetooth/server/getPrimaryServices/gen-get-same-object.https.html
new file mode 100644
index 0000000..258bda0
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-get-same-object.https.html
@@ -0,0 +1,40 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getPrimaryServices should return the same object.';
+let device;
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['generic_access']})
+    .then(({device}) => Promise.all([
+      device.gatt.getPrimaryServices(),
+      device.gatt.getPrimaryServices()]))
+    .then(([services_first_call, services_second_call]) => {
+      // Convert to arrays if necessary.
+      services_first_call = [].concat(services_first_call);
+      services_second_call = [].concat(services_second_call);
+
+      assert_equals(services_first_call.length, services_second_call.length);
+
+      let first_call_set = new Set(services_first_call);
+      assert_equals(services_first_call.length, first_call_set.size);
+      let second_call_set = new Set(services_second_call);
+      assert_equals(services_second_call.length, second_call_set.size);
+
+      services_first_call.forEach(service => {
+        assert_true(second_call_set.has(service))
+      });
+
+      services_second_call.forEach(service => {
+        assert_true(first_call_set.has(service));
+      });
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.html b/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.html
new file mode 100644
index 0000000..969ad61
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Wrong Service name. Reject with TypeError.';
+const expected = new DOMException(
+    "Failed to execute 'getPrimaryServices' on " +
+    "'BluetoothRemoteGATTServer': Invalid Service name: " +
+    "'wrong_name'. It must be a valid UUID alias (e.g. 0x1234), " +
+    "UUID (lowercase hex characters e.g. " +
+    "'00001234-0000-1000-8000-00805f9b34fb'), " +
+    "or recognized standard name from " +
+    "https://www.bluetooth.com/specifications/gatt/services" +
+    " e.g. 'alert_notification'.",
+    'TypeError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('wrong_name'),
+        expected,
+        'Wrong Service name passed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.html
new file mode 100644
index 0000000..696b1f9
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.html
@@ -0,0 +1,30 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service without permission. ' +
+    'Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+    'to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+    'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(glucose.alias), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(glucose.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(glucose.uuid), expected)])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.html
new file mode 100644
index 0000000..41bbf6e
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.html
@@ -0,0 +1,24 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for present service without permission to access ' +
+    'any service. Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access any service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+     'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('heart_rate'),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.html b/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.html
new file mode 100644
index 0000000..58bea88
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.html
@@ -0,0 +1,24 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for present service without permission to access ' +
+    'any service. Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access any service. Tip: Add the service ' +
+    'UUID to \'optionalServices\' in requestDevice() options. ' +
+    'https://goo.gl/HxfxSQ',
+     'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true})
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices(),
+        expected)),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.html
new file mode 100644
index 0000000..4f8254a
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.html
@@ -0,0 +1,29 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for present service without permission. ' +
+    'Reject with SecurityError.';
+const expected = new DOMException(
+    'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+    'to \'optionalServices\' in requestDevice() options. https://goo.gl/HxfxSQ',
+    'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}]
+    })
+    .then(({device}) => Promise.all([
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(generic_access.alias), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(generic_access.name), expected),
+      assert_promise_rejects_with_message(
+          device.gatt.getPrimaryServices(generic_access.uuid), expected)])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.html b/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.html
new file mode 100644
index 0000000..cb5e094
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.html
@@ -0,0 +1,23 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent service. Reject with NotFoundError.';
+
+bluetooth_test(() => getHealthThermometerDevice({
+      filters: [{services: ['health_thermometer']}],
+      optionalServices: ['glucose']
+    })
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('glucose'),
+        new DOMException(
+            `No Services matching UUID ${glucose.uuid} found in Device.`,
+            'NotFoundError'))),
+    test_desc);
+
+</script>
diff --git a/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.html b/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.html
new file mode 100644
index 0000000..6face0a
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for services. Should return right number of ' +
+    'services.';
+
+bluetooth_test(() => getTwoHealthThermometerServicesDevice({
+  filters: [{services: ['health_thermometer']}]
+})
+    .then(({device}) => Promise.all([
+      device.gatt.getPrimaryServices(health_thermometer.alias),
+      device.gatt.getPrimaryServices(health_thermometer.name),
+      device.gatt.getPrimaryServices(health_thermometer.uuid)]))
+    .then(services_arrays => services_arrays.forEach(services => {
+        assert_equals(services.length, 2);
+        services.forEach(service => {
+          assert_equals(service.uuid,
+              BluetoothUUID.getService('health_thermometer'));
+          assert_true(service.isPrimary);
+        });
+    })), test_desc);
+</script>
diff --git a/bluetooth/server/getPrimaryServices/services-found.https.html b/bluetooth/server/getPrimaryServices/services-found.https.html
new file mode 100644
index 0000000..6edb111
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/services-found.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Find all services in a device.';
+
+bluetooth_test(() => getTwoHealthThermometerServicesDevice({
+  filters: [{services: ['health_thermometer']}],
+  optionalServices: ['generic_access']
+})
+    .then(({device}) => device.gatt.getPrimaryServices())
+    .then(services => {
+      // Expect three service instances.
+      assert_equals(services.length, 3);
+      services.forEach(s => assert_true(s.isPrimary));
+
+      let uuid_set = new Set(services.map(s => s.uuid));
+      // Two of the expected services are 'health_thermometer', so
+      // only 2 unique UUIDs.
+      assert_equals(uuid_set.size, 2);
+
+      assert_true(uuid_set.has(BluetoothUUID.getService('generic_access')));
+      assert_true(uuid_set.has(BluetoothUUID.getService('health_thermometer')));
+    }), test_desc);
+</script>
diff --git a/bluetooth/server/getPrimaryServices/services-not-found.https.html b/bluetooth/server/getPrimaryServices/services-not-found.https.html
new file mode 100644
index 0000000..2632ec6
--- /dev/null
+++ b/bluetooth/server/getPrimaryServices/services-not-found.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for services in a device with no services. Reject ' +
+    'with NotFoundError.';
+const expected = new DOMException('No Services found in device.',
+    'NotFoundError');
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+    .then(({device}) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices(),
+        expected)),
+    test_desc);
+</script>
diff --git a/bluetooth/service/device-same-from-2-services.https.html b/bluetooth/service/device-same-from-2-services.https.html
new file mode 100644
index 0000000..3665493
--- /dev/null
+++ b/bluetooth/service/device-same-from-2-services.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Same parent device returned from multiple services.';
+
+bluetooth_test(() => getTwoHealthThermometerServicesDevice({
+  filters: [{services: ['health_thermometer']}]
+})
+    .then(({device}) => device.gatt.getPrimaryServices('health_thermometer'))
+    .then(([service1, service2]) =>
+        assert_equals(service1.device, service2.device)),
+    test_desc);
+</script>
diff --git a/bluetooth/service/device-same-object.https.html b/bluetooth/service/device-same-object.https.html
new file mode 100644
index 0000000..f43e9f0
--- /dev/null
+++ b/bluetooth/service/device-same-object.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = '[SameObject] test for BluetoothRemoteGATTService device.';
+
+bluetooth_test(() => getHealthThermometerDevice({
+  filters: [{services: ['health_thermometer']}]
+})
+    .then(({device}) => device.gatt.getPrimaryService('health_thermometer'))
+    .then(service => assert_equals(service.device, service.device)),
+    test_desc);
+</script>
diff --git a/bluetooth/service/getCharacteristic/characteristic-found.https.html b/bluetooth/service/getCharacteristic/characteristic-found.https.html
new file mode 100644
index 0000000..9746f4b
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/characteristic-found.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for characteristic. Should return right ' +
+    'characteristic.';
+let device, service;
+
+bluetooth_test(() => getHealthThermometerDevice()
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService('health_thermometer'))
+    .then(_ => service = _)
+    .then(() => Promise.all([
+        service.getCharacteristic(measurement_interval.alias),
+        service.getCharacteristic(measurement_interval.name),
+        service.getCharacteristic(measurement_interval.uuid)]))
+    .then(characteristics => characteristics.forEach(characteristic => {
+      assert_equals(
+          characteristic.uuid, measurement_interval.uuid,
+          'Characteristic UUID should be the same as requested UUID.');
+      assert_equals(
+          characteristic.service, service,
+          'Characteristic service should be the same as service.');
+    })), test_desc);
+</script>
diff --git a/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.html b/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.html
new file mode 100644
index 0000000..9e42a46
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Serial Number String characteristic is blocklisted. ' +
+    'Should reject with SecurityError.';
+const expected = new DOMException(
+    'getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX',
+    'SecurityError');
+
+bluetooth_test(() => getHIDDevice({
+  filters: [{services: ['device_information']}]
+})
+    .then(({device}) => device.gatt.getPrimaryService('device_information'))
+    .then(service => assert_promise_rejects_with_message(
+        service.getCharacteristic('serial_number_string'),
+        expected,
+        'Serial Number String characteristic is blocklisted.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.html b/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.html
new file mode 100644
index 0000000..5fc3ec4
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.html
@@ -0,0 +1,22 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent characteristics with UUID. ' +
+    'Reject with NotFoundError.';
+
+bluetooth_test(() => getEmptyHealthThermometerService()
+    .then(({service}) => assert_promise_rejects_with_message(
+        service.getCharacteristic('battery_level'),
+        new DOMException(
+            `No Characteristics matching UUID ${battery_level.uuid} found ` +
+            `in Service with UUID ${health_thermometer.uuid}.`,
+            'NotFoundError'))),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.html b/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.html
new file mode 100644
index 0000000..0b81768
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.html
@@ -0,0 +1,30 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during getCharacteristic ' +
+    'call that fails. Should not crash';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => {
+      promise = assert_promise_rejects_with_message(
+          service.getCharacteristic('measurement_interval'), expected);
+      // Disconnect called to clear attributeInstanceMap and allow the object to
+      // get garbage collected.
+      service.device.gatt.disconnect();
+    })
+    .then(runGarbageCollection)
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristic/gen-get-same-object.https.html b/bluetooth/service/getCharacteristic/gen-get-same-object.https.html
new file mode 100644
index 0000000..e8fc654
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/gen-get-same-object.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getCharacteristic should return the same object.';
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => Promise.all([
+      service.getCharacteristic('measurement_interval'),
+      service.getCharacteristic('measurement_interval')]))
+    .then(([characteristics_first_call, characteristics_second_call]) => {
+      // Convert to arrays if necessary.
+      characteristics_first_call = [].concat(characteristics_first_call);
+      characteristics_second_call = [].concat(characteristics_second_call);
+
+      let first_call_set = new Set(characteristics_first_call);
+      assert_equals(characteristics_first_call.length, first_call_set.size);
+      let second_call_set = new Set(characteristics_second_call);
+      assert_equals(characteristics_second_call.length, second_call_set.size);
+
+      characteristics_first_call.forEach(characteristic => {
+        assert_true(second_call_set.has(characteristic));
+      });
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.html b/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.html
new file mode 100644
index 0000000..08c1352
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.html
@@ -0,0 +1,30 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Wrong Characteristic name. Reject with TypeError.';
+const expected = new DOMException(
+    "Failed to execute 'getCharacteristic' on " +
+    "'BluetoothRemoteGATTService': Invalid Characteristic name: " +
+    "'wrong_name'. " +
+    "It must be a valid UUID alias (e.g. 0x1234), " +
+    "UUID (lowercase hex characters e.g. " +
+    "'00001234-0000-1000-8000-00805f9b34fb'), " +
+    "or recognized standard name from " +
+    "https://www.bluetooth.com/specifications/gatt/characteristics" +
+    " e.g. 'aerobic_heart_rate_lower_limit'.",
+    'TypeError');
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => assert_promise_rejects_with_message(
+        service.getCharacteristic('wrong_name'),
+        expected,
+        'Wrong Characteristic name passed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristic/gen-reconnect-during.https.html b/bluetooth/service/getCharacteristic/gen-reconnect-during.https.html
new file mode 100644
index 0000000..2fe9cd0
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/gen-reconnect-during.https.html
@@ -0,0 +1,42 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() and connect() called during ' +
+    'getCharacteristic. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+  filters: [{services: [health_thermometer.name]}],
+})
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService(health_thermometer.name))
+    .then(service => Promise.all([
+      // 1. Make a call to service.getCharacteristic, while the service is still
+      // valid.
+      assert_promise_rejects_with_message(service.getCharacteristic(measurement_interval.name), expected),
+
+      // 2. disconnect() and connect before the initial call completes.
+      // This is accomplished by making the calls without waiting for the
+      // earlier promises to resolve.
+      // connect() guarantees on OS-level connection, but disconnect()
+      // only disconnects the current instance.
+      // getHealthThermometerDeviceWithServicesDiscovered holds another
+      // connection in an iframe, so disconnect() and connect() are certain to
+      // reconnect.  However, disconnect() will invalidate the service object so
+      // the subsequent calls made to it will fail, even after reconnecting.
+      device.gatt.disconnect(),
+      device.gatt.connect()
+    ])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristic/gen-service-is-removed.https.html b/bluetooth/service/getCharacteristic/gen-service-is-removed.https.html
new file mode 100644
index 0000000..391c49a
--- /dev/null
+++ b/bluetooth/service/getCharacteristic/gen-service-is-removed.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service is removed before getCharacteristic call. ' +
+    'Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let service, fake_service, fake_peripheral;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(_ => ({service, fake_service, fake_peripheral} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        service.getCharacteristic('measurement_interval'),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.html b/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.html
new file mode 100644
index 0000000..5620f2c
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'The Device Information service is composed of blocklisted ' +
+    'characteristics so we shouldn\'t find any.';
+const expected = new DOMException('No Characteristics found in service.',
+    'NotFoundError');
+let device;
+
+bluetooth_test(() => getHIDDevice({
+    filters: [{services: ['device_information']}]
+  })
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService('device_information'))
+    .then(service => assert_promise_rejects_with_message(
+      service.getCharacteristics(),
+      expected)),
+    test_desc);
+</script>
diff --git a/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.html b/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.html
new file mode 100644
index 0000000..9139ae1
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Find characteristics with UUID in service.';
+let device, fake_peripheral;
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _ ))
+    // Setup a device with two measurement intervals.
+    .then(() =>
+      fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.connect())
+    .then(() => fake_peripheral.addFakeService({uuid: 'health_thermometer'}))
+    .then(fake_health_thermometer => Promise.all([
+        fake_health_thermometer.addFakeCharacteristic({
+          uuid: 'measurement_interval',
+          properties: ['read', 'write', 'indicate']}),
+        fake_health_thermometer.addFakeCharacteristic({
+          uuid: 'measurement_interval',
+          properties: ['read', 'write', 'indicate']}),
+        fake_health_thermometer.addFakeCharacteristic({
+          uuid: 'temperature_measurement',
+          properties: ['indicate']})
+    ]))
+    .then(() =>
+      fake_peripheral.setNextGATTDiscoveryResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.getPrimaryService('health_thermometer'))
+    // Actual test starts.
+    .then(service => Promise.all([
+        service.getCharacteristics(measurement_interval.alias),
+        service.getCharacteristics(measurement_interval.name),
+        service.getCharacteristics(measurement_interval.uuid)]))
+    .then(characteristics_arrays => characteristics_arrays.forEach(
+        characteristics => {
+          assert_equals(characteristics.length, 2);
+          assert_equals(characteristics[0].uuid, measurement_interval.uuid);
+          assert_equals(characteristics[1].uuid, measurement_interval.uuid);
+    })), test_desc);
+</script>
diff --git a/bluetooth/service/getCharacteristics/characteristics-found.https.html b/bluetooth/service/getCharacteristics/characteristics-found.https.html
new file mode 100644
index 0000000..952e3e6
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/characteristics-found.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Find all characteristics in a service.';
+let device, fake_peripheral;
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice()
+    .then(_ => ({device, fake_peripheral} = _))
+    // Setup a device with two measurement intervals.
+    .then(() =>
+      fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.connect())
+    .then(() => fake_peripheral.addFakeService({uuid: 'health_thermometer'}))
+    .then(fake_health_thermometer => Promise.all([
+        fake_health_thermometer.addFakeCharacteristic({
+          uuid: 'measurement_interval',
+          properties: ['read', 'write', 'indicate']}),
+        fake_health_thermometer.addFakeCharacteristic({
+          uuid: 'measurement_interval',
+          properties: ['read', 'write', 'indicate']}),
+        fake_health_thermometer.addFakeCharacteristic({
+          uuid: 'temperature_measurement',
+          properties: ['indicate']})
+    ]))
+    .then(() =>
+      fake_peripheral.setNextGATTDiscoveryResponse({code: HCI_SUCCESS}))
+    .then(() => device.gatt.getPrimaryService('health_thermometer'))
+    // Actual test starts.
+    .then(service => service.getCharacteristics())
+    .then(characteristics => {
+      // Expect three characteristic instances.
+      assert_equals(characteristics.length, 3);
+
+      let uuid_set = new Set(characteristics.map(c => c.uuid));
+      // Two of the expected characteristics are 'measurement_interval',
+      // so only 2 unique UUID.
+      assert_equals(uuid_set.size, 2);
+      assert_true(uuid_set.has(BluetoothUUID.getCharacteristic(
+        'measurement_interval')));
+      assert_true(uuid_set.has(BluetoothUUID.getCharacteristic(
+        'temperature_measurement')));
+    }), test_desc);
+</script>
diff --git a/bluetooth/service/getCharacteristics/characteristics-not-found.https.html b/bluetooth/service/getCharacteristics/characteristics-not-found.https.html
new file mode 100644
index 0000000..6401740
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/characteristics-not-found.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent characteristics. Reject with ' +
+    'NotFoundError.';
+const expected = new DOMException('No Characteristics found in service.',
+    'NotFoundError');
+let service;
+
+bluetooth_test(() => getEmptyHealthThermometerService()
+    .then(_ => ({service} = _))
+    .then(() => assert_promise_rejects_with_message(
+      service.getCharacteristics(), expected)),
+    test_desc);
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.html b/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.html
new file mode 100644
index 0000000..eeaa5b3
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Serial Number String characteristic is blocklisted. ' +
+    'Should reject with SecurityError.';
+const expected = new DOMException(
+    'getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX',
+    'SecurityError');
+
+bluetooth_test(() => getHIDDevice({
+  filters: [{services: ['device_information']}]
+})
+    .then(({device}) => device.gatt.getPrimaryService('device_information'))
+    .then(service => assert_promise_rejects_with_message(
+        service.getCharacteristics('serial_number_string'),
+        expected,
+        'Serial Number String characteristic is blocklisted.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.html b/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.html
new file mode 100644
index 0000000..7f32b30
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.html
@@ -0,0 +1,22 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Request for absent characteristics with UUID. ' +
+    'Reject with NotFoundError.';
+
+bluetooth_test(() => getEmptyHealthThermometerService()
+    .then(({service}) => assert_promise_rejects_with_message(
+        service.getCharacteristics('battery_level'),
+        new DOMException(
+            `No Characteristics matching UUID ${battery_level.uuid} found ` +
+            `in Service with UUID ${health_thermometer.uuid}.`,
+            'NotFoundError'))),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.html b/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.html
new file mode 100644
index 0000000..36ef29b
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.html
@@ -0,0 +1,30 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during getCharacteristics ' +
+    'call that fails. Should not crash';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => {
+      promise = assert_promise_rejects_with_message(
+          service.getCharacteristics('measurement_interval'), expected);
+      // Disconnect called to clear attributeInstanceMap and allow the object to
+      // get garbage collected.
+      service.device.gatt.disconnect();
+    })
+    .then(runGarbageCollection)
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.html b/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.html
new file mode 100644
index 0000000..5768ff4
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.html
@@ -0,0 +1,30 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Garbage Collection ran during getCharacteristics ' +
+    'call that fails. Should not crash';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => {
+      promise = assert_promise_rejects_with_message(
+          service.getCharacteristics(), expected);
+      // Disconnect called to clear attributeInstanceMap and allow the object to
+      // get garbage collected.
+      service.device.gatt.disconnect();
+    })
+    .then(runGarbageCollection)
+    .then(() => promise),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.html b/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.html
new file mode 100644
index 0000000..917d84d
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getCharacteristics should return the same object.';
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => Promise.all([
+      service.getCharacteristics('measurement_interval'),
+      service.getCharacteristics('measurement_interval')]))
+    .then(([characteristics_first_call, characteristics_second_call]) => {
+      // Convert to arrays if necessary.
+      characteristics_first_call = [].concat(characteristics_first_call);
+      characteristics_second_call = [].concat(characteristics_second_call);
+
+      let first_call_set = new Set(characteristics_first_call);
+      assert_equals(characteristics_first_call.length, first_call_set.size);
+      let second_call_set = new Set(characteristics_second_call);
+      assert_equals(characteristics_second_call.length, second_call_set.size);
+
+      characteristics_first_call.forEach(characteristic => {
+        assert_true(second_call_set.has(characteristic));
+      });
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-get-same-object.https.html b/bluetooth/service/getCharacteristics/gen-get-same-object.https.html
new file mode 100644
index 0000000..ebdd803
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-get-same-object.https.html
@@ -0,0 +1,31 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Calls to getCharacteristics should return the same object.';
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => Promise.all([
+      service.getCharacteristics(),
+      service.getCharacteristics()]))
+    .then(([characteristics_first_call, characteristics_second_call]) => {
+      // Convert to arrays if necessary.
+      characteristics_first_call = [].concat(characteristics_first_call);
+      characteristics_second_call = [].concat(characteristics_second_call);
+
+      let first_call_set = new Set(characteristics_first_call);
+      assert_equals(characteristics_first_call.length, first_call_set.size);
+      let second_call_set = new Set(characteristics_second_call);
+      assert_equals(characteristics_second_call.length, second_call_set.size);
+
+      characteristics_first_call.forEach(characteristic => {
+        assert_true(second_call_set.has(characteristic));
+      });
+    }), test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.html b/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.html
new file mode 100644
index 0000000..0bcb25c
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.html
@@ -0,0 +1,30 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Wrong Characteristic name. Reject with TypeError.';
+const expected = new DOMException(
+    "Failed to execute 'getCharacteristics' on " +
+    "'BluetoothRemoteGATTService': Invalid Characteristic name: " +
+    "'wrong_name'. " +
+    "It must be a valid UUID alias (e.g. 0x1234), " +
+    "UUID (lowercase hex characters e.g. " +
+    "'00001234-0000-1000-8000-00805f9b34fb'), " +
+    "or recognized standard name from " +
+    "https://www.bluetooth.com/specifications/gatt/characteristics" +
+    " e.g. 'aerobic_heart_rate_lower_limit'.",
+    'TypeError');
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(({service}) => assert_promise_rejects_with_message(
+        service.getCharacteristics('wrong_name'),
+        expected,
+        'Wrong Characteristic name passed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.html b/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.html
new file mode 100644
index 0000000..4226bcb
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.html
@@ -0,0 +1,42 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() and connect() called during ' +
+    'getCharacteristics. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+  filters: [{services: [health_thermometer.name]}],
+})
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService(health_thermometer.name))
+    .then(service => Promise.all([
+      // 1. Make a call to service.getCharacteristics, while the service is still
+      // valid.
+      assert_promise_rejects_with_message(service.getCharacteristics(measurement_interval.name), expected),
+
+      // 2. disconnect() and connect before the initial call completes.
+      // This is accomplished by making the calls without waiting for the
+      // earlier promises to resolve.
+      // connect() guarantees on OS-level connection, but disconnect()
+      // only disconnects the current instance.
+      // getHealthThermometerDeviceWithServicesDiscovered holds another
+      // connection in an iframe, so disconnect() and connect() are certain to
+      // reconnect.  However, disconnect() will invalidate the service object so
+      // the subsequent calls made to it will fail, even after reconnecting.
+      device.gatt.disconnect(),
+      device.gatt.connect()
+    ])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-reconnect-during.https.html b/bluetooth/service/getCharacteristics/gen-reconnect-during.https.html
new file mode 100644
index 0000000..9157275
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-reconnect-during.https.html
@@ -0,0 +1,42 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'disconnect() and connect() called during ' +
+    'getCharacteristics. Reject with NetworkError.';
+const expected = new DOMException(
+    'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+    '(Re)connect first with `device.gatt.connect`.',
+    'NetworkError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+  filters: [{services: [health_thermometer.name]}],
+})
+    .then(_ => ({device} = _))
+    .then(() => device.gatt.getPrimaryService(health_thermometer.name))
+    .then(service => Promise.all([
+      // 1. Make a call to service.getCharacteristics, while the service is still
+      // valid.
+      assert_promise_rejects_with_message(service.getCharacteristics(), expected),
+
+      // 2. disconnect() and connect before the initial call completes.
+      // This is accomplished by making the calls without waiting for the
+      // earlier promises to resolve.
+      // connect() guarantees on OS-level connection, but disconnect()
+      // only disconnects the current instance.
+      // getHealthThermometerDeviceWithServicesDiscovered holds another
+      // connection in an iframe, so disconnect() and connect() are certain to
+      // reconnect.  However, disconnect() will invalidate the service object so
+      // the subsequent calls made to it will fail, even after reconnecting.
+      device.gatt.disconnect(),
+      device.gatt.connect()
+    ])),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.html b/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.html
new file mode 100644
index 0000000..62caaad
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service is removed before getCharacteristics call. ' +
+    'Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let service, fake_service, fake_peripheral;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(_ => ({service, fake_service, fake_peripheral} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        service.getCharacteristics('measurement_interval'),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/bluetooth/service/getCharacteristics/gen-service-is-removed.https.html b/bluetooth/service/getCharacteristics/gen-service-is-removed.https.html
new file mode 100644
index 0000000..4868127
--- /dev/null
+++ b/bluetooth/service/getCharacteristics/gen-service-is-removed.https.html
@@ -0,0 +1,26 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/bluetooth/resources/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+const test_desc = 'Service is removed before getCharacteristics call. ' +
+    'Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+    'InvalidStateError');
+let service, fake_service, fake_peripheral;
+
+bluetooth_test(() => getHealthThermometerService()
+    .then(_ => ({service, fake_service, fake_peripheral} = _))
+    .then(() => fake_service.remove())
+    .then(() => fake_peripheral.simulateGATTServicesChanged())
+    .then(() => assert_promise_rejects_with_message(
+        service.getCharacteristics(),
+        expected,
+        'Service got removed.')),
+    test_desc);
+
+</script>
diff --git a/budget-api/OWNERS b/budget-api/OWNERS
new file mode 100644
index 0000000..9cc0eff
--- /dev/null
+++ b/budget-api/OWNERS
@@ -0,0 +1 @@
+@beverloo
diff --git a/budget-api/interfaces.any.js b/budget-api/interfaces.any.js
new file mode 100644
index 0000000..84e8874
--- /dev/null
+++ b/budget-api/interfaces.any.js
@@ -0,0 +1,24 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// See https://wicg.github.io/budget-api/
+
+promise_test(async () => {
+  const html = await fetch('/interfaces/html.idl').then(r => r.text());
+  const workers = await fetch('/interfaces/dedicated-workers.idl').then(r => r.text());
+  const idl = await fetch('/interfaces/budget-api.idl').then(r => r.text());
+
+  const idlArray = new IdlArray();
+  idlArray.add_untested_idls(html, { only: [
+    'Navigator',
+    'NavigatorContentUtils',
+    'NavigatorCookies',
+    'NavigatorPlugins',
+  ] });
+  idlArray.add_untested_idls(workers);
+  idlArray.add_idls(idl);
+  idlArray.test();
+  done();
+}, 'budget-api interfaces.');
diff --git a/check_stability.ini b/check_stability.ini
index f5ec030..b939328 100644
--- a/check_stability.ini
+++ b/check_stability.ini
@@ -6,5 +6,4 @@
 # Exhaustively validating such changes is highly resource intensive
 # (particularly in terms of execution time), making it impractical in most
 # cases.
-ignore_changes: resources
-results_url: https://pulls.web-platform-tests.org/api/stability
+ignore_changes: resources/**
diff --git a/clear-site-data/OWNERS b/clear-site-data/OWNERS
new file mode 100644
index 0000000..fa38108
--- /dev/null
+++ b/clear-site-data/OWNERS
@@ -0,0 +1,2 @@
+@mikewest
+@msramek
diff --git a/client-hints/OWNERS b/client-hints/OWNERS
new file mode 100644
index 0000000..1bcab3b
--- /dev/null
+++ b/client-hints/OWNERS
@@ -0,0 +1,3 @@
+@igrigorik
+@yoavweiss
+@tarunban
diff --git a/client-hints/accept_ch.http.html b/client-hints/accept_ch.http.html
new file mode 100644
index 0000000..50d7764
--- /dev/null
+++ b/client-hints/accept_ch.http.html
@@ -0,0 +1,35 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+// When the response for the HTML file contains "Accept-CH" in the response
+// headers, then the browser should not attach the specified client hints in
+// the HTTP request headers if the response was delivered by an insecure HTTP
+// server. Test this functionality by fetching an XHR from this page hosted on
+// an insecure HTTP server. The response headers for this page include
+// "Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect".
+//
+// echo_client_hints_received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+  promise_test(t => {
+  return fetch("/client-hints/echo_client_hints_received.py").then(r => {
+    assert_equals(r.status, 200)
+    // Verify that the browser does not include client hints in the headers
+    // when fetching the XHR from an insecure HTTP server.
+    assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+    assert_false(r.headers.has("dpr-received"), "dpr-received");
+    assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+    assert_false(r.headers.has("rtt-received"), "rtt-received");
+    assert_false(r.headers.has("downlink-received"), "downlink-received");
+    assert_false(r.headers.has("ect-received"), "ect-received");
+  });
+}, "Accept-CH header test");
+
+</script>
+
+</body>
+</html>
diff --git a/client-hints/accept_ch.http.html.headers b/client-hints/accept_ch.http.html.headers
new file mode 100644
index 0000000..4f3a57b
--- /dev/null
+++ b/client-hints/accept_ch.http.html.headers
@@ -0,0 +1 @@
+Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect
\ No newline at end of file
diff --git a/client-hints/accept_ch.sub.https.html b/client-hints/accept_ch.sub.https.html
new file mode 100644
index 0000000..78c7ad0
--- /dev/null
+++ b/client-hints/accept_ch.sub.https.html
@@ -0,0 +1,62 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+// If the response for the HTML file contains "Accept-CH" in the response
+// headers, then the browser should attach the specified client hints in the
+// HTTP request headers depending on whether the resource is being fetched from
+// the same origin or a different origin. Test this functionality by fetching
+// same-origin and cross-origin resources from this page. The response headers
+// for this page include "Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect".
+//
+// echo_client_hints_received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+  return fetch("https://{{domains[]}}:{{ports[https][0]}}/client-hints/echo_client_hints_received.py", {"mode": "no-cors"}).then(r => {
+    assert_equals(r.status, 200)
+    // Verify that the browser includes client hints in the headers for a
+    // same-origin fetch.
+    assert_true(r.headers.has("device-memory-received"), "device-memory-received");
+    assert_true(r.headers.has("dpr-received"), "dpr-received");
+    assert_true(r.headers.has("viewport-width-received"), "viewport-width-received");
+
+    assert_true(r.headers.has("rtt-received"), "rtt-received");
+    var rtt = parseInt(r.headers.get("rtt-received"));
+    assert_greater_than_equal(rtt, 0);
+    assert_less_than_equal(rtt, 3000);
+    assert_equals(rtt % 50, 0, 'rtt must be a multiple of 50 msec');
+
+    assert_true(r.headers.has("downlink-received"), "downlink-received");
+    var downlinkKbps  = r.headers.get("downlink-received") * 1000;
+    assert_greater_than_equal(downlinkKbps, 0);
+    assert_less_than_equal(downlinkKbps, 10000);
+
+    assert_in_array(r.headers.get("ect-received"), ["slow-2g", "2g",
+          "3g", "4g"], 'ect-received is unexpected');
+  });
+}, "Accept-CH header test");
+
+promise_test(t => {
+  return fetch("https://{{domains[www]}}:{{ports[https][0]}}/client-hints/echo_client_hints_received.py").then(r => {
+    assert_equals(r.status, 200)
+    // Verify that the browser does not include client hints in the headers
+    // for a cross-origin fetch.
+    assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+    assert_false(r.headers.has("dpr-received"), "dpr-received");
+    assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+    assert_false(r.headers.has("rtt-received"), "rtt-received");
+    assert_false(r.headers.has("downlink-received"), "downlink-received");
+    assert_false(r.headers.has("ect-received"), "ect-received");
+  });
+}, "Cross-Origin Accept-CH header test");
+
+
+
+</script>
+
+</body>
+</html>
diff --git a/client-hints/accept_ch.sub.https.html.headers b/client-hints/accept_ch.sub.https.html.headers
new file mode 100644
index 0000000..4f3a57b
--- /dev/null
+++ b/client-hints/accept_ch.sub.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect
\ No newline at end of file
diff --git a/client-hints/accept_ch_malformed_header.https.html b/client-hints/accept_ch_malformed_header.https.html
new file mode 100644
index 0000000..70ccab8
--- /dev/null
+++ b/client-hints/accept_ch_malformed_header.https.html
@@ -0,0 +1,26 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+promise_test(t => {
+  return fetch("/client-hints/echo_client_hints_received.py").then(r => {
+    assert_equals(r.status, 200)
+    // Verify that the browser does not include client hints in the headers
+    // since Accept-CH is malformed (includes whitespace between attributes
+    // instead of comma).
+    assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+    assert_false(r.headers.has("dpr-received"), "dpr-received");
+    assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+    assert_false(r.headers.has("rtt-received"), "rtt-received");
+    assert_false(r.headers.has("downlink-received"), "downlink-received");
+    assert_false(r.headers.has("ect-received"), "ect-received");
+  });
+}, "Accept-CH header test");
+
+</script>
+
+</body>
+</html>
diff --git a/client-hints/accept_ch_malformed_header.https.html.headers b/client-hints/accept_ch_malformed_header.https.html.headers
new file mode 100644
index 0000000..6d23758
--- /dev/null
+++ b/client-hints/accept_ch_malformed_header.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: device-memory dpr
\ No newline at end of file
diff --git a/client-hints/echo_client_hints_received.py b/client-hints/echo_client_hints_received.py
new file mode 100644
index 0000000..8f2ccaa
--- /dev/null
+++ b/client-hints/echo_client_hints_received.py
@@ -0,0 +1,20 @@
+def main(request, response):
+    """
+    Simple handler that sets a response header based on which client hint
+    request headers were received.
+    """
+
+    response.headers.append("Access-Control-Allow-Origin", "*")
+
+    if "device-memory" in request.headers:
+            response.headers.set("device-memory-received", request.headers.get("device-memory"))
+    if "dpr" in request.headers:
+            response.headers.set("dpr-received", request.headers.get("dpr"))
+    if "viewport-width" in request.headers:
+            response.headers.set("viewport-width-received", request.headers.get("viewport-width"))
+    if "rtt" in request.headers:
+            response.headers.set("rtt-received", request.headers.get("rtt"))
+    if "downlink" in request.headers:
+            response.headers.set("downlink-received", request.headers.get("downlink"))
+    if "ect" in request.headers:
+            response.headers.set("ect-received", request.headers.get("ect"))
diff --git a/clipboard-apis/async-interfaces.https.html b/clipboard-apis/async-interfaces.https.html
index a82e39f..1f523e5 100644
--- a/clipboard-apis/async-interfaces.https.html
+++ b/clipboard-apis/async-interfaces.https.html
@@ -9,16 +9,17 @@
 <script>
 'use strict';
 
-function doTest(idls) {
+function doTest(idl, dom) {
   var idl_array = new IdlArray();
   idl_array.add_untested_idls('interface Navigator {};');
   idl_array.add_untested_idls('interface EventTarget {};');
-  for (let idl of idls) {
-    idl_array.add_idls(idl);
-  }
+  idl_array.add_untested_idls('dictionary PermissionDescriptor {};');
+  idl_array.add_untested_idls(dom, { only: ['Event', 'EventInit'] });
+  idl_array.add_idls(idl);
   idl_array.add_objects({
     Navigator: ['navigator'],
     Clipboard: ['navigator.clipboard'],
+    ClipboardEvent: ['new ClipboardEvent("x")'],
   });
   idl_array.test();
 };
@@ -28,7 +29,11 @@
 }
 
 promise_test(() => {
-  return Promise.all(["/interfaces/clipboard.idl"].map(fetchText))
-    .then(doTest);
+  return Promise.all(
+    [
+      "/interfaces/clipboard-apis.idl",
+      "/interfaces/dom.idl",
+    ].map(fetchText))
+    .then(([idl, dom]) => doTest(idl, dom));
 }, "Test driver");
 </script>
diff --git a/clipboard-apis/clipboard-events-synthetic.html b/clipboard-apis/clipboard-events-synthetic.html
new file mode 100644
index 0000000..00f32ae
--- /dev/null
+++ b/clipboard-apis/clipboard-events-synthetic.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>synthetic clipboard events should not be composed</title>
+<link rel="help" href="https://w3c.github.io/clipboard-apis/#clipboard-event-copy">
+<link rel="help" href="https://w3c.github.io/clipboard-apis/#clipboard-event-cut">
+<link rel="help" href="https://w3c.github.io/clipboard-apis/#clipboard-event-paste">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+const EVENTS = [ 'copy', 'cut', 'paste' ];
+
+EVENTS.forEach(name => {
+  function testEvent(name, init, composed_flag_expectation, name) {
+    async_test(function(test) {
+      document.addEventListener(name, test.step_func_done((e) => {
+        assert_false(e.isTrusted, `synthetic ${name} event is untrusted`);
+        assert_equals(e.composed, composed_flag_expectation,
+                      `composed flag should be ${composed_flag_expectation}`);
+      }));
+      let event = new ClipboardEvent(name, init);
+      document.dispatchEvent(event);
+    }, name);
+  }
+
+  testEvent(name, { bubbles: true, cancellable: true}, false,
+            `Unspecified synthetic ${name} event should not be composed.`);
+  testEvent(name, { bubbles: true, cancelable: true, composed: true }, true,
+            `Synthetic ${name} event can be explicitly composed.`);
+  testEvent(name, { bubbles: true, cancelable: true, composed: false }, false,
+            `Synthetic ${name} event can be explicitly uncomposed.`);
+});
+</script>
diff --git a/clipboard-apis/copy-event-manual.html b/clipboard-apis/copy-event-manual.html
new file mode 100644
index 0000000..e4cf337
--- /dev/null
+++ b/clipboard-apis/copy-event-manual.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>The copy event</title>
+<link rel="help" href="https://w3c.github.io/clipboard-apis/#clipboard-event-copy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<p>Select and copy any part of this text to continue.
+<script>
+setup({explicit_timeout: true});
+async_test(t => {
+  document.oncopy = t.step_func_done(event => {
+    // Nothing can be asserted about the event target until
+    // https://github.com/w3c/clipboard-apis/issues/70 is resolved.
+    // assert_equals(event.target, document.body, "event.target");
+    assert_true(event.isTrusted, "event.isTrusted");
+    assert_true(event.composed, "event.composed");
+  });
+});
+</script>
diff --git a/clipboard-apis/cut-event-manual.html b/clipboard-apis/cut-event-manual.html
new file mode 100644
index 0000000..abef6f9
--- /dev/null
+++ b/clipboard-apis/cut-event-manual.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>The cut event</title>
+<link rel="help" href="https://w3c.github.io/clipboard-apis/#clipboard-event-cut">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<input value="Select and cut any part of this text to continue" size="100">
+<script>
+setup({explicit_timeout: true});
+async_test(t => {
+  document.oncut = t.step_func_done(event => {
+    // Nothing can be asserted about the event target until
+    // https://github.com/w3c/clipboard-apis/issues/70 is resolved.
+    // assert_equals(event.target, document.body, "event.target");
+    assert_true(event.isTrusted, "event.isTrusted");
+    assert_true(event.composed, "event.composed");
+  });
+});
+</script>
diff --git a/clipboard-apis/paste-event-manual.html b/clipboard-apis/paste-event-manual.html
new file mode 100644
index 0000000..4131a41
--- /dev/null
+++ b/clipboard-apis/paste-event-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>The paste event</title>
+<link rel="help" href="https://w3c.github.io/clipboard-apis/#clipboard-event-paste">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<input placeholder="Paste any text here to continue" size="100">
+<p>Some pre-selected text to copy for convenience</p>
+<script>
+setup({explicit_timeout: true});
+async_test(t => {
+  getSelection().selectAllChildren(document.querySelector("p"));
+  document.onpaste = t.step_func_done(event => {
+    // Nothing can be asserted about the event target until
+    // https://github.com/w3c/clipboard-apis/issues/70 is resolved.
+    // assert_equals(event.target, document.body, "event.target");
+    assert_true(event.isTrusted, "event.isTrusted");
+    assert_true(event.composed, "event.composed");
+  });
+});
+</script>
diff --git a/common/arrays.js b/common/arrays.js
new file mode 100644
index 0000000..49431dd
--- /dev/null
+++ b/common/arrays.js
@@ -0,0 +1,16 @@
+// Returns true if the given arrays are equal. Optionally can pass an equality function.
+export function areArraysEqual(a, b, equalityFunction = (c, d) => { return c === d; }) {
+  try {
+    if (a.length !== b.length)
+      return false;
+
+    for (let i = 0; i < a.length; i++) {
+      if (!equalityFunction(a[i], b[i]))
+        return false;
+    }
+  } catch (ex) {
+    return false;
+  }
+
+  return true;
+}
diff --git a/common/css-paint-tests.js b/common/css-paint-tests.js
deleted file mode 100644
index cd57332..0000000
--- a/common/css-paint-tests.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// To make sure that we take the snapshot at the right time, we do double
-// requestAnimationFrame. In the second frame, we take a screenshot, that makes
-// sure that we already have a full frame.
-function importPaintWorkletAndTerminateTestAfterAsyncPaint(code) {
-    if (typeof CSS.paintWorklet == "undefined") {
-        takeScreenshot();
-    } else {
-        var blob = new Blob([code], {type: 'text/javascript'});
-        CSS.paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
-            requestAnimationFrame(function() {
-                requestAnimationFrame(function() {
-                    takeScreenshot();
-                });
-            });
-        });
-    }
-}
-
diff --git a/common/media.js b/common/media.js
index 6bddea5..7cea1ac 100644
--- a/common/media.js
+++ b/common/media.js
@@ -33,3 +33,14 @@
 
     return base + extension;
 }
+
+function getMediaContentType(url) {
+    var extension = new URL(url, location).pathname.split(".").pop();
+    var map = {
+        "mp4": "video/mp4",
+        "ogv": "video/ogg",
+        "mp3": "audio/mp3",
+        "oga": "audio/ogg",
+    };
+    return map[extension];
+}
diff --git a/common/namespaces.js b/common/namespaces.js
new file mode 100644
index 0000000..741bdc1
--- /dev/null
+++ b/common/namespaces.js
@@ -0,0 +1,4 @@
+var NAMESPACES = {
+  "svg": "http://www.w3.org/2000/svg",
+  "xlink": "http://www.w3.org/1999/xlink",
+};
diff --git a/common/subset-tests.js b/common/subset-tests.js
new file mode 100644
index 0000000..1810320
--- /dev/null
+++ b/common/subset-tests.js
@@ -0,0 +1,33 @@
+// Only test a subset of tests with, e.g., ?1-10 in the URL.
+// Can be used together with <meta name="variant" content="...">
+// Sample usage:
+// for (const test of tests) {
+//   subsetTest(async_test, test.fn, test.name);
+// }
+(function() {
+  var subTestStart = 0;
+  var subTestEnd = Infinity;
+  var match;
+  if (location.search) {
+      match = /(?:^\?|&)(\d+)-(\d+|last)(?:&|$)/.exec(location.search);
+      if (match) {
+        subTestStart = parseInt(match[1], 10);
+        if (match[2] !== "last") {
+            subTestEnd = parseInt(match[2], 10);
+        }
+      }
+  }
+  function shouldRunSubTest(currentSubTest) {
+    return currentSubTest >= subTestStart && currentSubTest <= subTestEnd;
+  }
+  var currentSubTest = 0;
+  function subsetTest(testFunc, ...args) {
+    currentSubTest++;
+    if (shouldRunSubTest(currentSubTest)) {
+      return testFunc(...args);
+    }
+    return null;
+  }
+  self.shouldRunSubTest = shouldRunSubTest;
+  self.subsetTest = subsetTest;
+})();
diff --git a/common/worklet-reftest.js b/common/worklet-reftest.js
new file mode 100644
index 0000000..abdda5b
--- /dev/null
+++ b/common/worklet-reftest.js
@@ -0,0 +1,33 @@
+// Imports code into a worklet. E.g.
+//
+// importWorklet(CSS.paintWorklet, {url: 'script.js'});
+// importWorklet(CSS.paintWorklet, '/* javascript string */');
+function importWorklet(worklet, code) {
+    let url;
+    if (typeof code === 'object') {
+      url = code.url;
+    } else {
+      const blob = new Blob([code], {type: 'text/javascript'});
+      url = URL.createObjectURL(blob);
+    }
+
+    return worklet.addModule(url);
+}
+
+// To make sure that we take the snapshot at the right time, we do double
+// requestAnimationFrame. In the second frame, we take a screenshot, that makes
+// sure that we already have a full frame.
+async function importWorkletAndTerminateTestAfterAsyncPaint(worklet, code) {
+    if (typeof worklet === 'undefined') {
+        takeScreenshot();
+        return;
+    }
+
+    await importWorklet(worklet, code);
+
+    requestAnimationFrame(function() {
+        requestAnimationFrame(function() {
+            takeScreenshot();
+        });
+    });
+}
diff --git a/compat/css-style-declaration-alias-enumeration.html b/compat/css-style-declaration-alias-enumeration.html
new file mode 100644
index 0000000..4608af9
--- /dev/null
+++ b/compat/css-style-declaration-alias-enumeration.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Aliases enumerated on CSSStyleDeclaration</title>
+<link rel="author" title="Anders Hartvoll Ruud" href="andruud@chromium.org">
+<link rel="help" href="https://compat.spec.whatwg.org/#css-simple-aliases">
+<meta name="assert" content="This test verifies that aliases are enumerated on CSSStyleDeclaration" />
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+</body>
+<script>
+
+// Note: This is an arbitrarily chosen simple alias from the spec.
+const ALIAS_SAMPLE = 'webkitTransform';
+
+function find_alias(alias) {
+  for (let prop in document.body.style) {
+    if (prop == alias) {
+      return true;
+    }
+  }
+  return false;
+}
+
+test(() => {
+  assert_true(find_alias(ALIAS_SAMPLE), `Alias ${ALIAS_SAMPLE} seen`);
+});
+</script>
diff --git a/compat/interfaces.any.js b/compat/interfaces.any.js
new file mode 100644
index 0000000..1b87c90
--- /dev/null
+++ b/compat/interfaces.any.js
@@ -0,0 +1,17 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// https://compat.spec.whatwg.org/
+
+promise_test(async () => {
+  const idl = await fetch('/interfaces/compat.idl').then(r => r.text());
+  const idlArray = new IdlArray();
+  idlArray.add_untested_idls('interface Window {};');
+  idlArray.add_untested_idls('interface EventTarget{};');
+  idlArray.add_untested_idls('interface HTMLBodyElement{};');
+  idlArray.add_idls(idl);
+  idlArray.test();
+  done();
+}, 'compat interfaces.');
diff --git a/compat/webkit-appearance.tentative.html b/compat/webkit-appearance.tentative.html
new file mode 100644
index 0000000..8c0273d
--- /dev/null
+++ b/compat/webkit-appearance.tentative.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<title>-webkit-appearance support</title>
+<!-- There is no spec for -webkit-appearance. It is supported in Opera, Safari,
+     Chrome, and Edge. Firefox has expressed intent to support it. -->
+<link rel="help" href="https://github.com/whatwg/compat/issues/6">
+<meta name="assert" content="This test checks for support of the -webkit-appearance CSS attribute." />
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="tester"></div>
+
+<script>
+const WEBKIT_APPEARANCE_VALUES = [
+  'none',
+  'checkbox',
+  'radio',
+  'push-button',
+  'square-button',
+  'button',
+  'button-bevel',
+  'inner-spin-button',
+  'listbox',
+  'listitem',
+  'media-enter-fullscreen-button',
+  'media-exit-fullscreen-button',
+  'media-mute-button',
+  'media-play-button',
+  'media-overlay-play-button',
+  'media-toggle-closed-captions-button',
+  'media-slider',
+  'media-sliderthumb',
+  'media-volume-slider-container',
+  'media-volume-slider',
+  'media-volume-sliderthumb',
+  'media-controls-background',
+  'media-controls-fullscreen-background',
+  'media-current-time-display',
+  'media-time-remaining-display',
+  'menulist',
+  'menulist-button',
+  'menulist-text',
+  'menulist-textfield',
+  'meter',
+  'progress-bar',
+  'progress-bar-value',
+  'slider-horizontal',
+  'slider-vertical',
+  'sliderthumb-horizontal',
+  'sliderthumb-vertical',
+  'caret',
+  'searchfield',
+  'searchfield-cancel-button',
+  'textfield',
+  'textarea',
+];
+
+for (const appearance_value of WEBKIT_APPEARANCE_VALUES) {
+  test(() => {
+    const div = document.getElementById('tester');
+    div.style = `-webkit-appearance: ${appearance_value}`;
+    assert_equals(getComputedStyle(div).webkitAppearance, appearance_value);
+  });
+}
+</script>
diff --git a/compat/webkit-background-origin-text-ref.html b/compat/webkit-background-origin-text-ref.html
new file mode 100644
index 0000000..d1f1838
--- /dev/null
+++ b/compat/webkit-background-origin-text-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>webkit-background-origin should not accept text as value</title>
+<link rel="author" title="Rob Buis" href="rob.buis@chromium.org">
+<p>There should be a green square below and no red.</p>
+<div style="width: 100px; height: 100px; background-color: green"></div>
diff --git a/compat/webkit-background-origin-text.html b/compat/webkit-background-origin-text.html
new file mode 100644
index 0000000..06e7283
--- /dev/null
+++ b/compat/webkit-background-origin-text.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>webkit-background-origin should not accept text as value</title>
+<link rel="author" title="Rob Buis" href="rob.buis@chromium.org">
+<link rel="match" href="webkit-background-origin-text-ref.html">
+<style>
+#target {
+  width: 0px;
+  background-image: linear-gradient(green, green 50%, red 50%, red);
+  background-size: 200px 200px;
+  background-origin: border-box;
+  -webkit-background-origin: text;
+  padding: 25px;
+  border: 25px solid transparent;
+}
+</style>
+<p>There should be a green square below and no red.</p>
+<div id="target"></div>
diff --git a/config.default.json b/config.default.json
deleted file mode 100644
index a55b17c..0000000
--- a/config.default.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{"host": "web-platform.test",
- "doc_root": null,
- "ws_doc_root": null,
- "external_host": null,
- "ports":{"http":[8000, "auto"],
-          "https":[8443],
-          "ws":["auto"],
-          "wss":["auto"]},
- "check_subdomains": true,
- "log_level":"debug",
- "bind_hostname": true,
- "ssl": {"type": "pregenerated",
-         "encrypt_after_connect": false,
-         "openssl": {
-             "openssl_binary": "openssl",
-             "base_path": "_certs",
-             "force_regenerate": false,
-             "base_conf_path": null
-         },
-         "pregenerated": {
-             "host_key_path": "tools/certs/web-platform.test.key",
-             "host_cert_path": "tools/certs/web-platform.test.pem"
-         },
-         "none": {}
-        },
- "aliases": []
-}
diff --git a/conformance-checkers/Makefile b/conformance-checkers/Makefile
index 161c8db..879b023 100644
--- a/conformance-checkers/Makefile
+++ b/conformance-checkers/Makefile
@@ -7,7 +7,7 @@
 EXPANDFLAGS=
 GIT=git
 GITFLAGS=
-PYTHON=python
+PYTHON=python3
 PYTHONFLAGS=
 VNU_TEST_REPO=git@github.com:validator/tests.git
 ITS_REPO=git@github.com:w3c/its-2.0-testsuite-inputdata.git
@@ -24,7 +24,7 @@
 	    | $(EXPAND) $(EXPANDFLAGS) > $@
 
 messages.json: .FORCE
-	$(PYTHON) $(PYTHONFLAGS) -mjson.tool $@ > $@.tmp
+	$(PYTHON) $(PYTHONFLAGS) -mjson.tool --sort-keys $@ > $@.tmp
 	mv $@.tmp $@
 
 push:
diff --git a/conformance-checkers/html-aria/_functional/tree/ariatree.html b/conformance-checkers/html-aria/_functional/tree/ariatree.html
index d2d57e5..d915c55 100644
--- a/conformance-checkers/html-aria/_functional/tree/ariatree.html
+++ b/conformance-checkers/html-aria/_functional/tree/ariatree.html
@@ -4,9 +4,9 @@
 	<title>ARIA Tree Example</title>
 	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
 	<link rel="stylesheet" href="./css/treesimple.css" type="text/css">
-	<script type="text/javascript" src="./js/prototype.js"></script>
-	<script type="text/javascript" src="./js/aria.js"></script>
-	<script type="text/javascript" src="./js/init.js"></script>
+	<script src="./js/prototype.js"></script>
+	<script src="./js/aria.js"></script>
+	<script src="./js/init.js"></script>
 </head>
 <body>
 	
diff --git a/conformance-checkers/html-aria/_functional/tree/ariatree2.html b/conformance-checkers/html-aria/_functional/tree/ariatree2.html
index b6d809c..3efe246 100644
--- a/conformance-checkers/html-aria/_functional/tree/ariatree2.html
+++ b/conformance-checkers/html-aria/_functional/tree/ariatree2.html
@@ -4,9 +4,9 @@
 	<title>ARIA Tree Example</title>
 	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
 	<link rel="stylesheet" href="./css/treebox.css" type="text/css">
-	<script type="text/javascript" src="./js/prototype.js"></script>
-	<script type="text/javascript" src="./js/aria.js"></script>
-	<script type="text/javascript" src="./js/init.js"></script>
+	<script src="./js/prototype.js"></script>
+	<script src="./js/aria.js"></script>
+	<script src="./js/init.js"></script>
 </head>
 <body>
 	
diff --git a/conformance-checkers/html-aria/accessible-name-updates/673.html b/conformance-checkers/html-aria/accessible-name-updates/673.html
index d24cd6b..3810051 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/673.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/673.html
@@ -15,7 +15,7 @@
       After the onload event completes a child DOM text node is added to the element with 
       the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
      function addTextNode() {
        var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/674.html b/conformance-checkers/html-aria/accessible-name-updates/674.html
index c031ec9..52269a5 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/674.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/674.html
@@ -15,7 +15,7 @@
       After the onload event completes a child DOM text node is added to the element 
       with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
      function addTextNode() {
        var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/675.html b/conformance-checkers/html-aria/accessible-name-updates/675.html
index c762fcf..1c778dd 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/675.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/675.html
@@ -16,7 +16,7 @@
       value 'assertive' is added to the element and a child DOM text 
       node is added to the element.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
      function addLiveRegion() {
        var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/676.html b/conformance-checkers/html-aria/accessible-name-updates/676.html
index 0e193cb..117fc5c 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/676.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/676.html
@@ -16,7 +16,7 @@
       value 'assertive' is added to the document and a child element and text 
       node is added to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function addElement() {
         var node = document.getElementById('TEST_ID');
diff --git a/conformance-checkers/html-aria/accessible-name-updates/677.html b/conformance-checkers/html-aria/accessible-name-updates/677.html
index e93361c..9a6b488 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/677.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/677.html
@@ -17,7 +17,7 @@
       After the onload event completes a child DOM element node with text content has the 
       CSS dsiplay property changed to display="block".</p>
     
-    <script type="text/javascript">  
+    <script>  
   
      function showElement() {
        var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/678.html b/conformance-checkers/html-aria/accessible-name-updates/678.html
index 68699e3..6479eca 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/678.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/678.html
@@ -18,7 +18,7 @@
       a child DOM element node with text content has the CSS dsiplay property 
       changed to display="block".</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function showElement() {
         var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/679.html b/conformance-checkers/html-aria/accessible-name-updates/679.html
index 6bb8b2d..0f74596 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/679.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/679.html
@@ -20,7 +20,7 @@
       After the onload event completes a child DOM element node with text content has the 
       CSS dsiplay property changed to visibility="visible".</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function showElement() {
         var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/680.html b/conformance-checkers/html-aria/accessible-name-updates/680.html
index ef774fd..9a64936 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/680.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/680.html
@@ -19,7 +19,7 @@
       After the onload event completes a child DOM element node with text content has the CSS dsiplay 
       property changed to visibility="visible".</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function showElement() {
         var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/681.html b/conformance-checkers/html-aria/accessible-name-updates/681.html
index fd8befe..e8a6026 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/681.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/681.html
@@ -15,7 +15,7 @@
       event with the value="polite". After the onload event completes a child DOM text node is added 
       to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function addChildTextNode() {
         var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/682.html b/conformance-checkers/html-aria/accessible-name-updates/682.html
index b8d90ce..85d744d 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/682.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/682.html
@@ -15,7 +15,7 @@
       event with the value="assertive". After the onload event completes a child DOM text node is 
       added to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function addChildTextNode() {
         var node = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/683.html b/conformance-checkers/html-aria/accessible-name-updates/683.html
index ca45f2b..92bde34 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/683.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/683.html
@@ -15,7 +15,7 @@
       event with the value="polite". After the onload event completes a child DOM element node is 
       added to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function addChildElementNode() {
         var node         = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/684.html b/conformance-checkers/html-aria/accessible-name-updates/684.html
index 2abea5c..c77062c 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/684.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/684.html
@@ -15,7 +15,7 @@
       the onload event with the value="assertive". After the onload event completes a 
       child DOM element node is added to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
   
       function addChildElementNode() {
         var node         = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/685.html b/conformance-checkers/html-aria/accessible-name-updates/685.html
index 90cb71f..f099039 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/685.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/685.html
@@ -16,7 +16,7 @@
       that is hidden using CSS display="none". After the onload event completes a child DOM element 
       node with text content has the CSS display property changed to display="block".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_3');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/686.html b/conformance-checkers/html-aria/accessible-name-updates/686.html
index 9cf141d..ed55007 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/686.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/686.html
@@ -16,7 +16,7 @@
       that is hidden using CSS display="none". After the onload event completes a child DOM element 
       node with text content has the CSS display property changed to display="block".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_3');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/687.html b/conformance-checkers/html-aria/accessible-name-updates/687.html
index 2294b4c..9503adb 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/687.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/687.html
@@ -17,7 +17,7 @@
       event completes a child DOM element node with text content has the CSS display property 
       changed to visibility="visible".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_3');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/688.html b/conformance-checkers/html-aria/accessible-name-updates/688.html
index cc488b8..c21cf27 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/688.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/688.html
@@ -17,7 +17,7 @@
       After the onload event completes a child DOM element node with text content 
       has the CSS display property changed to visibility="visible".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_3');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/689.html b/conformance-checkers/html-aria/accessible-name-updates/689.html
index 714b34b..98f20d4 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/689.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/689.html
@@ -15,7 +15,7 @@
       onload event completes with the value="polite". After the aria-live attribute is added, 
       a child DOM text node is added to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function addChildTextNode() {
         var node         = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/690.html b/conformance-checkers/html-aria/accessible-name-updates/690.html
index 3f189e5..d3fb88c 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/690.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/690.html
@@ -15,7 +15,7 @@
       the onload event completes with the value="assertive". After the aria-live attribute 
       is added, a child DOM text node is added to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function addChildTextNode() {
         var node         = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/691.html b/conformance-checkers/html-aria/accessible-name-updates/691.html
index 85bd8c9..4cb1ab7 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/691.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/691.html
@@ -16,7 +16,7 @@
       attribute is added, a child DOM element node is added to the element with 
       the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function addChildElementNode() {
         var node         = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/692.html b/conformance-checkers/html-aria/accessible-name-updates/692.html
index 13c16d6..7f6f3f9 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/692.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/692.html
@@ -15,7 +15,7 @@
       the onload event completes with the value="assertive". After the aria-live attribute 
       is added, a child DOM element node is added to the element with the aria-live attribute.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function addChildElementNode() {
         var node         = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/693.html b/conformance-checkers/html-aria/accessible-name-updates/693.html
index 8914d7e..9695f25 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/693.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/693.html
@@ -17,7 +17,7 @@
       aria-live attribute is added, the child DOM element node with text content has the 
       CSS display property changed to display="block".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/694.html b/conformance-checkers/html-aria/accessible-name-updates/694.html
index 49ee64c..392e7d2 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/694.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/694.html
@@ -17,7 +17,7 @@
       the aria-live attribute is added, the child DOM element node with text content has 
       the CSS display property changed to display="block".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_3');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/695.html b/conformance-checkers/html-aria/accessible-name-updates/695.html
index b2550c6..46c1666 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/695.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/695.html
@@ -17,7 +17,7 @@
       visibility="hidden". After the aria-live attribute is added, the child DOM 
       element node with text content has the CSS display property changed to visibility="visible".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_3');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/696.html b/conformance-checkers/html-aria/accessible-name-updates/696.html
index a577b1d..b66a721 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/696.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/696.html
@@ -18,7 +18,7 @@
       the aria-live attribute is added, the child DOM element node with text content has 
       the CSS display property changed to visibility="visible".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function showElement() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/697.html b/conformance-checkers/html-aria/accessible-name-updates/697.html
index eed947c..c229e74 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/697.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/697.html
@@ -16,7 +16,7 @@
       has a child DOM text node with content. After the onload event completes the child 
       DOM text node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/698.html b/conformance-checkers/html-aria/accessible-name-updates/698.html
index 3e90818..3d96ba9 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/698.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/698.html
@@ -16,7 +16,7 @@
       has a child DOM text node with content. After the onload event completes the child DOM 
       text node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/699.html b/conformance-checkers/html-aria/accessible-name-updates/699.html
index f306cb1..adc1c79 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/699.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/699.html
@@ -16,7 +16,7 @@
       has a child DOM element node that contains text content. After the onload event completes 
       the child DOM element node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/700.html b/conformance-checkers/html-aria/accessible-name-updates/700.html
index de12aff..4f0af8c 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/700.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/700.html
@@ -16,7 +16,7 @@
       value="assertive" has a child DOM element node that contains text content. 
       After the onload event completes the child DOM element node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/701.html b/conformance-checkers/html-aria/accessible-name-updates/701.html
index 942cab3..755b226 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/701.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/701.html
@@ -17,7 +17,7 @@
       content. After the onload event completes the CSS 'display' property 
       of the child DOM element node is changed to display="none".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/702.html b/conformance-checkers/html-aria/accessible-name-updates/702.html
index f46eb1d..e13b82e 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/702.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/702.html
@@ -16,7 +16,7 @@
       has a child DOM element node that contains text content. After the onload event completes 
       the CSS 'display' property of the child DOM element node is changed to display="none".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/703.html b/conformance-checkers/html-aria/accessible-name-updates/703.html
index 661eed2..e0a4ed5 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/703.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/703.html
@@ -17,7 +17,7 @@
       completes the CSS 'visibility' property of the child DOM element node is changed 
       to visibility="hidden".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/704.html b/conformance-checkers/html-aria/accessible-name-updates/704.html
index bb68b58..c2294a5 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/704.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/704.html
@@ -16,7 +16,7 @@
       has a child DOM element node that contains text content. After the onload event completes 
       the CSS 'visibility' property of the child DOM element node is changed to visibility="hidden".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/705.html b/conformance-checkers/html-aria/accessible-name-updates/705.html
index d1f8cb8..3ac9831 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/705.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/705.html
@@ -16,7 +16,7 @@
       text node with content. After the onload event completes the child DOM text 
       node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/706.html b/conformance-checkers/html-aria/accessible-name-updates/706.html
index 80f605d..edcc7a9 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/706.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/706.html
@@ -15,7 +15,7 @@
       event with the value="assertive" and the element has a child DOM text node with content. After 
       the onload event completes the child DOM text node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/707.html b/conformance-checkers/html-aria/accessible-name-updates/707.html
index 8490112..ed0de5f 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/707.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/707.html
@@ -16,7 +16,7 @@
       DOM element node that contains text content. After the onload event completes 
       the child DOM element node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/708.html b/conformance-checkers/html-aria/accessible-name-updates/708.html
index e1bfcd1..796b729 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/708.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/708.html
@@ -16,7 +16,7 @@
       node that contains text content. After the onload event completes the child DOM 
       element node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
 
       function deleteChildNodes() {
diff --git a/conformance-checkers/html-aria/accessible-name-updates/709.html b/conformance-checkers/html-aria/accessible-name-updates/709.html
index 321b852..3306f7e 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/709.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/709.html
@@ -16,7 +16,7 @@
       that contains text content. After the onload event completes the CSS 'display' 
       property of the child DOM element node is changed to display="none".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
 
       function hideElement() {
diff --git a/conformance-checkers/html-aria/accessible-name-updates/710.html b/conformance-checkers/html-aria/accessible-name-updates/710.html
index 872643f..dd947f2 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/710.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/710.html
@@ -16,7 +16,7 @@
       node that contains text content. After the onload event completes the CSS 'display' 
       property of the child DOM element node is changed to display="none".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
 
       function hideElement() {
diff --git a/conformance-checkers/html-aria/accessible-name-updates/711.html b/conformance-checkers/html-aria/accessible-name-updates/711.html
index 7b4375c..af409ce 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/711.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/711.html
@@ -17,7 +17,7 @@
       the CSS 'visibility' property of the child DOM element node is changed 
       to visibility="hidden".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
 
       function hideElement() {
diff --git a/conformance-checkers/html-aria/accessible-name-updates/712.html b/conformance-checkers/html-aria/accessible-name-updates/712.html
index 3c6d69d..11dd2f2 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/712.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/712.html
@@ -17,7 +17,7 @@
       event completes the CSS 'visibility' property of the child DOM element 
       node is changed to visibility="hidden".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
 
       function hideElement() {
diff --git a/conformance-checkers/html-aria/accessible-name-updates/713.html b/conformance-checkers/html-aria/accessible-name-updates/713.html
index 08f8a5b..24387f9 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/713.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/713.html
@@ -16,7 +16,7 @@
       the element has a child DOM text node with content. After the 
       aria-live attribute is added, the child DOM text node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
 
       function deleteChildNodes() {
diff --git a/conformance-checkers/html-aria/accessible-name-updates/714.html b/conformance-checkers/html-aria/accessible-name-updates/714.html
index 56a63f8..b2d4f12 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/714.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/714.html
@@ -16,7 +16,7 @@
       and the element has a child DOM text node with content. After the 
       aria-live attribute is added, the child DOM text node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/715.html b/conformance-checkers/html-aria/accessible-name-updates/715.html
index 82f82b0..00b26f5 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/715.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/715.html
@@ -16,7 +16,7 @@
       a child DOM element node that contains text content. After the aria-live 
       attribute is added, the child DOM element node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/716.html b/conformance-checkers/html-aria/accessible-name-updates/716.html
index 3a98fad..80d2b85 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/716.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/716.html
@@ -17,7 +17,7 @@
       a child DOM element node that contains text content. After the aria-live 
       attribute is added, the child DOM element node is deleted.</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function deleteChildNodes() {
         var node  = document.getElementById('TEST_ID_1');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/717.html b/conformance-checkers/html-aria/accessible-name-updates/717.html
index e4e65c3..aaed8c0 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/717.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/717.html
@@ -17,7 +17,7 @@
       attribute is added, the CSS 'display' property of the child DOM element 
       node is changed to display="none".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/718.html b/conformance-checkers/html-aria/accessible-name-updates/718.html
index e662390..f6e9a2b 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/718.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/718.html
@@ -17,7 +17,7 @@
       attribute is added, the CSS 'display' property of the child DOM element 
       node is changed to display="none".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/719.html b/conformance-checkers/html-aria/accessible-name-updates/719.html
index 4fd10b7..1f132e6 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/719.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/719.html
@@ -17,7 +17,7 @@
       After the aria-live attribute is added, the CSS 'visibility' property 
       of the child DOM element node is changed to visibility="hidden".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/accessible-name-updates/720.html b/conformance-checkers/html-aria/accessible-name-updates/720.html
index 22ab76a..23564ea 100644
--- a/conformance-checkers/html-aria/accessible-name-updates/720.html
+++ b/conformance-checkers/html-aria/accessible-name-updates/720.html
@@ -17,7 +17,7 @@
       After the aria-live attribute is added, the CSS 'visibility' property 
       of the child DOM element node is changed to visibility="hidden".</p>
     
-    <script type="text/javascript">  
+    <script>  
 
       function hideElement() {
         var node  = document.getElementById('TEST_ID_2');  
diff --git a/conformance-checkers/html-aria/author-requirements/567.html b/conformance-checkers/html-aria/author-requirements/567.html
index ce4c8d4..3800104 100644
--- a/conformance-checkers/html-aria/author-requirements/567.html
+++ b/conformance-checkers/html-aria/author-requirements/567.html
@@ -3,7 +3,7 @@
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>A &lt;div&gt; with role "button" followed by a modification to the value of "checkbox".</title>
-    <script type="text/javascript">
+    <script>
       function changeRole (/* Element */ element, /* String */ newRole) {
           element.setAttribute ('role', newRole);
           element.innerHTML = "This &lt;div&gt; has role '" + newRole + "'";
diff --git a/conformance-checkers/html-aria/author-requirements/568.html b/conformance-checkers/html-aria/author-requirements/568.html
index d9f06dc..d290ada 100644
--- a/conformance-checkers/html-aria/author-requirements/568.html
+++ b/conformance-checkers/html-aria/author-requirements/568.html
@@ -3,7 +3,7 @@
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>A &lt;div&gt; with role "button" followed by removal of the element and its children and an insertion of a new div having role="checkbox"</title>
-    <script type="text/javascript">
+    <script>
       function changeRoleByRemoval (/* Element */ element, /* String */ newRole) {
           var parent = element.parentNode;
           if (parent) {
diff --git a/conformance-checkers/html-aria/author-requirements/571-haswarn.html b/conformance-checkers/html-aria/author-requirements/571-haswarn.html
index 41b8867..92130fb 100644
--- a/conformance-checkers/html-aria/author-requirements/571-haswarn.html
+++ b/conformance-checkers/html-aria/author-requirements/571-haswarn.html
@@ -2,10 +2,10 @@
 <html><head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>In a &lt;div&gt; element with role="combobox" and aria-autocomplete="none", change values of the combobox by typing.</title>
-    <style type="text/css">
+    <style>
       .hasFocus { border: 2px solid red; }
     </style>
-    <script type="text/javascript">
+    <script>
       var DEL = 8;
       var BACK_SPACE = 72;
       var comboInfo = {};
diff --git a/conformance-checkers/html-aria/author-requirements/572-haswarn.html b/conformance-checkers/html-aria/author-requirements/572-haswarn.html
index a317a1c..358d14a 100644
--- a/conformance-checkers/html-aria/author-requirements/572-haswarn.html
+++ b/conformance-checkers/html-aria/author-requirements/572-haswarn.html
@@ -3,10 +3,10 @@
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>In a &lt;div&gt; element with role="combobox" and aria-autocomplete="inline", change values of the combobox by adjusting the up and down arrow keys with focus on the textfield in the combobox.</title>
-    <style type="text/css">
+    <style>
       .hasFocus { border: 2px solid red; }
     </style>
-    <script type="text/javascript">
+    <script>
       var UP = 38;
       var DOWN = 40;
       comboInfo = {};
diff --git a/conformance-checkers/html-aria/author-requirements/573-haswarn.html b/conformance-checkers/html-aria/author-requirements/573-haswarn.html
index 388282c..195dc7c 100644
--- a/conformance-checkers/html-aria/author-requirements/573-haswarn.html
+++ b/conformance-checkers/html-aria/author-requirements/573-haswarn.html
@@ -3,10 +3,10 @@
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>In a &lt;div&gt; element with role="combobox" and aria-autocomplete="both", change values of the combobox by adjusting the up and down arrow keys with focus on the textfield in the combobox.</title>
-    <style type="text/css">
+    <style>
       .hasFocus { border: 2px solid red; }
     </style>
-    <script type="text/javascript">
+    <script>
       var UP = 38;
       var DOWN = 40;
       comboInfo = {};
diff --git a/conformance-checkers/html-aria/author-requirements/580.html b/conformance-checkers/html-aria/author-requirements/580.html
index 5445050..14a2c11 100644
--- a/conformance-checkers/html-aria/author-requirements/580.html
+++ b/conformance-checkers/html-aria/author-requirements/580.html
@@ -3,7 +3,7 @@
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>A div element with style="display:none" has aria-hidden="true", and then script sets style to "display: block".</title>
-    <script type="text/javascript">
+    <script>
       function replaceStyle (/* Element */ element, /* String */ selector, /* String */ newValue) {
           element.setAttribute ('style', selector + ':' + newValue);
           if (newValue == 'block')
diff --git a/conformance-checkers/html-aria/live-events/test-case-live-event-1.html b/conformance-checkers/html-aria/live-events/test-case-live-event-1.html
index a04c24a..58f8605 100644
--- a/conformance-checkers/html-aria/live-events/test-case-live-event-1.html
+++ b/conformance-checkers/html-aria/live-events/test-case-live-event-1.html
@@ -3,7 +3,7 @@
   <head>
     <title>ARIA 1.0 Live Region Event Test Case: Add text content</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <style type="text/css">
+    <style>
       div#TEST_ID {
         margin: 0.5em; 
         padding: 0.25em; 
@@ -42,7 +42,7 @@
     </dl>
     
     
-    <script type="text/javascript">  
+    <script>  
 
       function tryAgain() {
         var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/live-events/test-case-live-event-2.html b/conformance-checkers/html-aria/live-events/test-case-live-event-2.html
index 39cf43d..180ff4c 100644
--- a/conformance-checkers/html-aria/live-events/test-case-live-event-2.html
+++ b/conformance-checkers/html-aria/live-events/test-case-live-event-2.html
@@ -3,7 +3,7 @@
   <head>
     <title>ARIA 1.0 Live Region Event Test Case: Delete text content</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <style type="text/css">
+    <style>
       div#TEST_ID {
         margin: 0.5em; 
         padding: 0.25em; 
@@ -44,7 +44,7 @@
     </dl>
     
     
-    <script type="text/javascript">  
+    <script>  
 
       function tryAgain() {
         addText()
diff --git a/conformance-checkers/html-aria/live-events/test-case-live-event-3.html b/conformance-checkers/html-aria/live-events/test-case-live-event-3.html
index 8e49ee3..8bd066d 100644
--- a/conformance-checkers/html-aria/live-events/test-case-live-event-3.html
+++ b/conformance-checkers/html-aria/live-events/test-case-live-event-3.html
@@ -3,7 +3,7 @@
   <head>
     <title>ARIA 1.0 Live Region Event Test Case: Change text content</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <style type="text/css">
+    <style>
       div#TEST_ID {
         margin: 0.5em; 
         padding: 0.25em; 
@@ -44,7 +44,7 @@
     </dl>
     
     
-    <script type="text/javascript">  
+    <script>  
 
       function tryAgain() {
         changeText('TEST TEXT 1')
diff --git a/conformance-checkers/html-aria/live-events/test-case-live-event-4.html b/conformance-checkers/html-aria/live-events/test-case-live-event-4.html
index a5233f3..25609e9 100644
--- a/conformance-checkers/html-aria/live-events/test-case-live-event-4.html
+++ b/conformance-checkers/html-aria/live-events/test-case-live-event-4.html
@@ -3,7 +3,7 @@
   <head>
     <title>ARIA 1.0 Live Region Event Test Case: Add text content (ALERT role)</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <style type="text/css">
+    <style>
       div#TEST_ID {
         margin: 0.5em; 
         padding: 0.25em; 
@@ -42,7 +42,7 @@
     </dl>
     
     
-    <script type="text/javascript">  
+    <script>  
 
       function tryAgain() {
         var node = document.getElementById('TEST_ID');  
diff --git a/conformance-checkers/html-aria/live-events/test-case-live-event-5.html b/conformance-checkers/html-aria/live-events/test-case-live-event-5.html
index 914b282..4ca9d33 100644
--- a/conformance-checkers/html-aria/live-events/test-case-live-event-5.html
+++ b/conformance-checkers/html-aria/live-events/test-case-live-event-5.html
@@ -3,7 +3,7 @@
   <head>
     <title>ARIA 1.0 Live Region Event Test Case: Change text content (LOG role)</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <style type="text/css">
+    <style>
       div#TEST_ID {
         margin: 0.5em; 
         padding: 0.25em; 
@@ -44,7 +44,7 @@
     </dl>
     
     
-    <script type="text/javascript">  
+    <script>  
 
       function tryAgain() {
         changeText('LOG ROLE TEST TEXT 1')
diff --git a/conformance-checkers/html-aria/live-events/test-case-live-event-6.html b/conformance-checkers/html-aria/live-events/test-case-live-event-6.html
index 8be8f43..7fb2a07 100644
--- a/conformance-checkers/html-aria/live-events/test-case-live-event-6.html
+++ b/conformance-checkers/html-aria/live-events/test-case-live-event-6.html
@@ -3,7 +3,7 @@
   <head>
     <title>ARIA 1.0 Live Region Event Test Case: Delete text content (STATUS role)</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <style type="text/css">
+    <style>
       div#TEST_ID {
         margin: 0.5em; 
         padding: 0.25em; 
@@ -44,7 +44,7 @@
     </dl>
     
     
-    <script type="text/javascript">  
+    <script>  
 
       function tryAgain() {
         addText()
diff --git a/conformance-checkers/html-aria/name-computation-input/659.html b/conformance-checkers/html-aria/name-computation-input/659.html
index 5e9b439..c7700a8 100644
--- a/conformance-checkers/html-aria/name-computation-input/659.html
+++ b/conformance-checkers/html-aria/name-computation-input/659.html
@@ -3,7 +3,7 @@
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <title>Text input with @title and :before and :after content rule</title>
-      <style type="text/css">
+      <style>
         label:before { content: "foo"; }
         label:after { content: "baz"; }
       </style>
diff --git a/conformance-checkers/html-aria/name-computation-input/660.html b/conformance-checkers/html-aria/name-computation-input/660.html
index e1d5f94..7fd0d26 100644
--- a/conformance-checkers/html-aria/name-computation-input/660.html
+++ b/conformance-checkers/html-aria/name-computation-input/660.html
@@ -3,7 +3,7 @@
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <title>Text input with @title and :before and :after content rule</title>
-         <style type="text/css">
+         <style>
         label:before { content: "foo "; }
         label:after { content: " baz"; }
       </style>
diff --git a/conformance-checkers/html-aria/name-computation-input/661.html b/conformance-checkers/html-aria/name-computation-input/661.html
index 95a3d46..7cb28dc 100644
--- a/conformance-checkers/html-aria/name-computation-input/661.html
+++ b/conformance-checkers/html-aria/name-computation-input/661.html
@@ -3,7 +3,7 @@
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <title>Text input with @title and :before and :after content rule</title>
-         <style type="text/css">
+         <style>
         label:before { content: "foo"; }
         label:after { content: "baz"; }
       </style>
diff --git a/conformance-checkers/html-aria/name-computation-input/662.html b/conformance-checkers/html-aria/name-computation-input/662.html
index 3bca088..a857a15 100644
--- a/conformance-checkers/html-aria/name-computation-input/662.html
+++ b/conformance-checkers/html-aria/name-computation-input/662.html
@@ -3,7 +3,7 @@
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <title>Text input with @title and :before and :after content rule</title>
-         <style type="text/css">
+         <style>
         label:before { content: "foo"; }
         label:after { content: "baz"; }
       </style>
diff --git a/conformance-checkers/html-aria/name-computation-input/663.html b/conformance-checkers/html-aria/name-computation-input/663.html
index ae70b85..01c5604 100644
--- a/conformance-checkers/html-aria/name-computation-input/663.html
+++ b/conformance-checkers/html-aria/name-computation-input/663.html
@@ -3,7 +3,7 @@
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <title>Text input with @title and :before and :after content rule</title>
-         <style type="text/css">
+         <style>
         label:before { content: "foo "; }
         label:after { content: " baz"; }
       </style>
diff --git a/conformance-checkers/html-aria/roles-plain-concrete/roles-plain-concrete-listbox-parent-combobox.html b/conformance-checkers/html-aria/roles-plain-concrete/roles-plain-concrete-listbox-parent-combobox.html
index e2e0135..43e137f 100644
--- a/conformance-checkers/html-aria/roles-plain-concrete/roles-plain-concrete-listbox-parent-combobox.html
+++ b/conformance-checkers/html-aria/roles-plain-concrete/roles-plain-concrete-listbox-parent-combobox.html
@@ -3,7 +3,7 @@
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <title>An element with id=test and role=listbox, which is owned by a combobox</title>
-      <style type="text/css">
+      <style>
           ul#test {
             list-style-type: none;
             width: 5em;
@@ -12,9 +12,9 @@
             margin-top: 0;
             margin-left: 1em;
           }
-          
+
           ul#test > li {
-            margin-left -10em;
+            margin-left: -10em;
           }
       </style>
    </head>
diff --git a/conformance-checkers/html-aria/selected-state/670.html b/conformance-checkers/html-aria/selected-state/670.html
index 594bf04..de95a2e 100644
--- a/conformance-checkers/html-aria/selected-state/670.html
+++ b/conformance-checkers/html-aria/selected-state/670.html
@@ -17,7 +17,7 @@
       role "listbox", and the value of the "aria-selected" attribute is equal 
       to "true".</p>
     
-    <script type="text/javascript">  
+    <script>  
       
      function setFocus() {
        var node = document.getElementById('ID_OPTION');  
diff --git a/conformance-checkers/html-aria/selected-state/671.html b/conformance-checkers/html-aria/selected-state/671.html
index 2c95a4f..4d36b22 100644
--- a/conformance-checkers/html-aria/selected-state/671.html
+++ b/conformance-checkers/html-aria/selected-state/671.html
@@ -18,7 +18,7 @@
       to "true" or the attribute is undefined.</p>
     
     
-    <script type="text/javascript">  
+    <script>  
       
      function setFocus() {
        var test_node = document.getElementById('ID_OPTION');  
diff --git a/conformance-checkers/html-aria/selected-state/672.html b/conformance-checkers/html-aria/selected-state/672.html
index 461d343..962e6b9 100644
--- a/conformance-checkers/html-aria/selected-state/672.html
+++ b/conformance-checkers/html-aria/selected-state/672.html
@@ -15,7 +15,7 @@
     <p>For an element with role "option" which is a child of an element with role "listbox", 
       and the value of the "aria-selected" attribute changes (from "true", or to "true").</p>
     
-    <script type="text/javascript">  
+    <script>  
       function changeSelectedState() {
         var test_node = document.getElementById('TEST_ID');  
         var state = test_node.getAttribute('aria-selected');
diff --git a/conformance-checkers/html-aria/setsize-posinset-level/setsize-posinset-level-1.html b/conformance-checkers/html-aria/setsize-posinset-level/setsize-posinset-level-1.html
index 92bf0f4..04786b8 100644
--- a/conformance-checkers/html-aria/setsize-posinset-level/setsize-posinset-level-1.html
+++ b/conformance-checkers/html-aria/setsize-posinset-level/setsize-posinset-level-1.html
@@ -71,7 +71,7 @@
       </ul>
       
     
-    <script type="text/javascript">  
+    <script>  
       
      function setFocus() {
        var node = document.getElementById('ID_TARGET');  
diff --git a/conformance-checkers/html-aria/stability-of-dom/669.html b/conformance-checkers/html-aria/stability-of-dom/669.html
index 6ad0f03..2f2e64f 100644
--- a/conformance-checkers/html-aria/stability-of-dom/669.html
+++ b/conformance-checkers/html-aria/stability-of-dom/669.html
@@ -17,7 +17,7 @@
       and aria-activedescendant set to the id of the first option for which an 
       assistive technology or API test tool requests to move focus to option 2.</p>
     
-    <script type="text/javascript">  
+    <script>  
       
      function setFocus() {
        var node = document.getElementById('ID_LISTBOX');  
diff --git a/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-A.html b/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-A.html
index ab66f05..3ba81f2 100644
--- a/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-A.html
+++ b/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-A.html
@@ -35,7 +35,7 @@
     <h3>AXAPI</h3>
     <p>AXSelected:Yes on option 2 and 3 and AXSelected:No on option 1 and 4</p>
     
-    <script type="text/javascript">  
+    <script>  
       
      function setFocus() {
        var node = document.getElementById('ID_TARGET');  
diff --git a/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-B.html b/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-B.html
index 7ae3d88..6d1dcda 100644
--- a/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-B.html
+++ b/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-B.html
@@ -35,7 +35,7 @@
     <h3>AXAPI</h3>
     <p>AXSelected:Yes on option 2 and 3 and AXSelected:No on option 1 and 4</p>
     
-    <script type="text/javascript">  
+    <script>  
       
      function clearSelection() {
        myVar=setTimeout(function(){var node = document.getElementById('ID_TARGET');node.setAttribute("aria-selected","false");} ,3000)       
diff --git a/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-C.html b/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-C.html
index 01f8ccf..88eb11b 100644
--- a/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-C.html
+++ b/conformance-checkers/html-aria/testcases-multiselectable/testcase-listbox-multiselectable-C.html
@@ -2,7 +2,7 @@
 <html><head>
     <title>ARIA 1.0 Test Case: Listbox role with multiseclect</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-    <style type="text/css">
+    <style>
       [aria-selected="true"] { font-weight: bold; }
     </style>
   </head>
@@ -39,7 +39,7 @@
     <h3>AXAPI</h3>
     <p>AXSelected:Yes on option 2 and 3 and AXSelected:No on option 1 and 4</p>
     
-    <script type="text/javascript">  
+    <script>  
 /*      
      function setSelection() {
        var node = document.getElementById('ID_TARGET');  
diff --git a/conformance-checkers/html-aria/testcases-multiselectable/testcase-multiselectable-D.html b/conformance-checkers/html-aria/testcases-multiselectable/testcase-multiselectable-D.html
index cc13b5c..290430a 100644
--- a/conformance-checkers/html-aria/testcases-multiselectable/testcase-multiselectable-D.html
+++ b/conformance-checkers/html-aria/testcases-multiselectable/testcase-multiselectable-D.html
@@ -2,7 +2,7 @@
 <html><head>
     <title>ARIA 1.0 Test Case: Listbox role with multiseclect</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-    <style type="text/css">
+    <style>
       div[aria-selected="true"] {color:white;background-color:black;}
     </style>
   </head>
@@ -15,7 +15,7 @@
       <div role="option" aria-selected="false" id="ID3" onmousedown="handle2(event)">Option 3 (test for shift click selected)</div> 
     </div>
     
-    <script type="text/javascript">  
+    <script>  
      function handle1(e) {
         var node=document.getElementById('ID1');
         node.focus();
diff --git a/conformance-checkers/html-aria/testcases-multiselectable/testcase-tree-multiselectable-C.html b/conformance-checkers/html-aria/testcases-multiselectable/testcase-tree-multiselectable-C.html
index eeb46a0..da97b29 100644
--- a/conformance-checkers/html-aria/testcases-multiselectable/testcase-tree-multiselectable-C.html
+++ b/conformance-checkers/html-aria/testcases-multiselectable/testcase-tree-multiselectable-C.html
@@ -30,7 +30,7 @@
       </div>
     </div>
     
-    <script type="text/javascript">  
+    <script>  
       
      function setFocus() {
        var node = document.getElementById('ID_TARGET');  
diff --git a/conformance-checkers/html-its/locqualityissue/html/locqualityissue4html.html b/conformance-checkers/html-its/locqualityissue/html/locqualityissue4html.html
index 82d2056..110585e 100644
--- a/conformance-checkers/html-its/locqualityissue/html/locqualityissue4html.html
+++ b/conformance-checkers/html-its/locqualityissue/html/locqualityissue4html.html
@@ -3,7 +3,7 @@
   <head>
     <meta charset="utf-8"/>
     <title>Telharmonium 1897</title>
-    <style type="text/css">
+    <style>
          [its-loc-quality-issue-type]{
            background-color:yellow;
            margin:2px;
diff --git a/conformance-checkers/html-its/locqualityissue/html/locqualityissue5html.html b/conformance-checkers/html-its/locqualityissue/html/locqualityissue5html.html
index 951cfdd..7eb28af 100644
--- a/conformance-checkers/html-its/locqualityissue/html/locqualityissue5html.html
+++ b/conformance-checkers/html-its/locqualityissue/html/locqualityissue5html.html
@@ -3,7 +3,7 @@
   <head>
     <meta charset="utf-8"/>
     <title>Telharmonium 1897</title>
-    <style type="text/css">
+    <style>
          [its-loc-quality-issue-type]{
            background-color:yellow;
            margin:2px;
diff --git a/conformance-checkers/html-its/locqualityissue/html/locqualityissue6html.html b/conformance-checkers/html-its/locqualityissue/html/locqualityissue6html.html
index ac35ca5..9658ece 100644
--- a/conformance-checkers/html-its/locqualityissue/html/locqualityissue6html.html
+++ b/conformance-checkers/html-its/locqualityissue/html/locqualityissue6html.html
@@ -3,7 +3,7 @@
   <head>
     <meta charset="utf-8"/>
     <title>Telharmonium 1897</title>
-    <style type="text/css">
+    <style>
          [its-loc-quality-issue-type]{
            background-color:yellow;
            margin:2px;
diff --git a/conformance-checkers/html-its/locqualityissue/html/locqualityissue7html.html b/conformance-checkers/html-its/locqualityissue/html/locqualityissue7html.html
index 01beb02..928c65b 100644
--- a/conformance-checkers/html-its/locqualityissue/html/locqualityissue7html.html
+++ b/conformance-checkers/html-its/locqualityissue/html/locqualityissue7html.html
@@ -11,7 +11,7 @@
           locQualityIssueSeverity="50"/>
       </its:locQualityIssues>
     </script>
-    <style type="text/css">.qaissue { background-color: yellow; } </style>
+    <style>.qaissue { background-color: yellow; } </style>
   </head>
   <body>
     <p>
diff --git a/conformance-checkers/html-svg/animate-elem-33-t-isvalid.html b/conformance-checkers/html-svg/animate-elem-33-t-isvalid.html
index b9a4600..f8b1d07 100644
--- a/conformance-checkers/html-svg/animate-elem-33-t-isvalid.html
+++ b/conformance-checkers/html-svg/animate-elem-33-t-isvalid.html
@@ -66,7 +66,7 @@
       </circle>
     </g>
     <!-- THIRD TEST-->
-    <!-- The lenght of the lines in the polyline (motionpath) are 100,50 and 100.
+    <!-- The length of the lines in the polyline (motionpath) are 100,50 and 100.
 		 The animated circle starts at the midpoint of one of the "legs" and also pass 
 		 this point at time 1.4 since 
 		 1.4 = 4*(0.25) + (4*(0.75-0.25))*(1/5).
diff --git a/conformance-checkers/html-svg/animate-elem-90-b-isvalid.html b/conformance-checkers/html-svg/animate-elem-90-b-isvalid.html
index 6141e62..40e7fac 100644
--- a/conformance-checkers/html-svg/animate-elem-90-b-isvalid.html
+++ b/conformance-checkers/html-svg/animate-elem-90-b-isvalid.html
@@ -26,7 +26,7 @@
         <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>
       </font-face-src>
     </font-face>
-    <style type="text/css">
+    <style>
 	.start {visibility: hidden }
 	.midway {visibility: visible; fill: rgb(0,0,255); }
 	#test-body-content .final {fill: rgb(128,0,0); }
diff --git a/conformance-checkers/html-svg/animate-elem-91-t-isvalid.html b/conformance-checkers/html-svg/animate-elem-91-t-isvalid.html
index fe95c82..8815c7b 100644
--- a/conformance-checkers/html-svg/animate-elem-91-t-isvalid.html
+++ b/conformance-checkers/html-svg/animate-elem-91-t-isvalid.html
@@ -51,7 +51,7 @@
 
       <!-- Sub-test 1: class on <rect> -->
       <g transform="translate(0,110)">
-        <style type="text/css">.on { fill: rgb(204,0,102) } .off { fill: #ccc }</style>
+        <style>.on { fill: rgb(204,0,102) } .off { fill: #ccc }</style>
         <text x="30" y="12">class</text>
         <rect x="200" width="12" height="12" stroke="black" class="on">
           <animate attributeName="class" to="off" begin="2s" dur="2s" fill="freeze"/>
diff --git a/conformance-checkers/html-svg/filters-turb-02-f-isvalid.html b/conformance-checkers/html-svg/filters-turb-02-f-isvalid.html
index 203f6a7..4230673 100644
--- a/conformance-checkers/html-svg/filters-turb-02-f-isvalid.html
+++ b/conformance-checkers/html-svg/filters-turb-02-f-isvalid.html
@@ -72,7 +72,7 @@
         <feTurbulence seed="-2.6" baseFrequency="0.01" type="turbulence"/>
       </filter>
             
-      <style type="text/css">
+      <style>
         #subtests text { fill: black }
       </style>
     </defs>
diff --git a/conformance-checkers/html-svg/fonts-elem-03-b-isvalid.html b/conformance-checkers/html-svg/fonts-elem-03-b-isvalid.html
index e8e47d4..3fc08bc 100644
--- a/conformance-checkers/html-svg/fonts-elem-03-b-isvalid.html
+++ b/conformance-checkers/html-svg/fonts-elem-03-b-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
-      <!--style type="text/css">
+      <!--style>
      			 <![CDATA[
 		        @font-face {
 			          font-family: 'TestComic'; 
diff --git a/conformance-checkers/html-svg/fonts-elem-04-b-isvalid.html b/conformance-checkers/html-svg/fonts-elem-04-b-isvalid.html
index 543abe3..c14bcc5 100644
--- a/conformance-checkers/html-svg/fonts-elem-04-b-isvalid.html
+++ b/conformance-checkers/html-svg/fonts-elem-04-b-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
-      <style type="text/css">
+      <style>
 
         @font-face {
         font-family: 'TestComic';
diff --git a/conformance-checkers/html-svg/fonts-elem-07-b-isvalid.html b/conformance-checkers/html-svg/fonts-elem-07-b-isvalid.html
index 8bb396f..bdaa26f 100644
--- a/conformance-checkers/html-svg/fonts-elem-07-b-isvalid.html
+++ b/conformance-checkers/html-svg/fonts-elem-07-b-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
-      <style type="text/css">
+      <style>
         @font-face {
         font-family: 'TestComic';
         font-weight: normal;
diff --git a/conformance-checkers/html-svg/interact-pevents-10-f-isvalid.html b/conformance-checkers/html-svg/interact-pevents-10-f-isvalid.html
index 9aec374..553f1a3 100644
--- a/conformance-checkers/html-svg/interact-pevents-10-f-isvalid.html
+++ b/conformance-checkers/html-svg/interact-pevents-10-f-isvalid.html
@@ -35,7 +35,7 @@
         <circle onclick="failTest()" cx="250" cy="200" r="50" />
     </g>
     <text id="failText" visibility="hidden" x="100" y="100" font-size="80" fill="red">FAIL</text>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
       try
       {
           function failTest()
diff --git a/conformance-checkers/html-svg/interact-pointer-04-f-isvalid.html b/conformance-checkers/html-svg/interact-pointer-04-f-isvalid.html
index 499797d..8e9660b 100644
--- a/conformance-checkers/html-svg/interact-pointer-04-f-isvalid.html
+++ b/conformance-checkers/html-svg/interact-pointer-04-f-isvalid.html
@@ -35,7 +35,7 @@
         <rect x="0" y="0" width="100" height="100" fill="black" />
     </mask>
     <rect x="200" y="0" width="100" height="100" fill="#dddddd" mask="url(#opacityMask)" onmouseover="testOpacity()" />
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             function test(testElement)
diff --git a/conformance-checkers/html-svg/masking-mask-01-b-isvalid.html b/conformance-checkers/html-svg/masking-mask-01-b-isvalid.html
index 23590f2..d23f6a1 100644
--- a/conformance-checkers/html-svg/masking-mask-01-b-isvalid.html
+++ b/conformance-checkers/html-svg/masking-mask-01-b-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-  <style type="text/css">
+  <style>
   @font-face {
      font-family: larabie-anglepoise;
      src: url(woffs/anglepoi.woff) format("woff");
diff --git a/conformance-checkers/html-svg/masking-path-12-f-isvalid.html b/conformance-checkers/html-svg/masking-path-12-f-isvalid.html
index 6cf14af..8ea5703 100644
--- a/conformance-checkers/html-svg/masking-path-12-f-isvalid.html
+++ b/conformance-checkers/html-svg/masking-path-12-f-isvalid.html
@@ -37,7 +37,7 @@
     <rect id="pass" x="200" y="100" width="50" height="50" fill="green"/>
     <rect id="fail" x="200" y="100" width="50" height="50" fill="red"/>
     
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var test = document.getElementById("testClip");
diff --git a/conformance-checkers/html-svg/painting-marker-03-f-isvalid.html b/conformance-checkers/html-svg/painting-marker-03-f-isvalid.html
index 0c73e6d..27d1596 100644
--- a/conformance-checkers/html-svg/painting-marker-03-f-isvalid.html
+++ b/conformance-checkers/html-svg/painting-marker-03-f-isvalid.html
@@ -35,7 +35,7 @@
       <marker id="marker2" markerUnits="strokeWidth" refX="100" refY="100" markerWidth="15" markerHeight="15" viewBox="0 0 200 200">
         <rect width="200" height="200" fill="red" stroke="none"/>
       </marker>
-      <style type="text/css">
+      <style>
         #markme { marker: url(#marker1) }
       </style>
     </defs>
diff --git a/conformance-checkers/html-svg/painting-marker-05-f-isvalid.html b/conformance-checkers/html-svg/painting-marker-05-f-isvalid.html
index 0180187..08a85a7 100644
--- a/conformance-checkers/html-svg/painting-marker-05-f-isvalid.html
+++ b/conformance-checkers/html-svg/painting-marker-05-f-isvalid.html
@@ -110,7 +110,7 @@
       </marker>
     </defs>
 
-    <style type="text/css">
+    <style>
       .testpaths {
       fill:none;
       stroke:black;
diff --git a/conformance-checkers/html-svg/painting-marker-properties-01-f-isvalid.html b/conformance-checkers/html-svg/painting-marker-properties-01-f-isvalid.html
index 6781359..52a4b4a 100644
--- a/conformance-checkers/html-svg/painting-marker-properties-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/painting-marker-properties-01-f-isvalid.html
@@ -25,7 +25,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css"><![CDATA[
+    <style><![CDATA[
         .start
         {
             marker-start: url(#markerTest);
diff --git a/conformance-checkers/html-svg/pservers-grad-08-b-isvalid.html b/conformance-checkers/html-svg/pservers-grad-08-b-isvalid.html
index 561f444..8afbbc2 100644
--- a/conformance-checkers/html-svg/pservers-grad-08-b-isvalid.html
+++ b/conformance-checkers/html-svg/pservers-grad-08-b-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
 
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-  <style type="text/css">
+  <style>
     @font-face {
       font-family: Blocky;
       src: url(woffs/Blocky.woff) format("woff");
diff --git a/conformance-checkers/html-svg/render-elems-06-t-isvalid.html b/conformance-checkers/html-svg/render-elems-06-t-isvalid.html
index fcbebe4..f48ade4 100644
--- a/conformance-checkers/html-svg/render-elems-06-t-isvalid.html
+++ b/conformance-checkers/html-svg/render-elems-06-t-isvalid.html
@@ -34,7 +34,7 @@
           <font-face-uri xlink:href="../resources/Blocky.svg#Blocky"/>
         </font-face-src>
       </font-face>
-       <style type="text/css">
+       <style>
           @font-face {
              font-family: BlockyWoff;
              src: url(woffs/Blocky.woff) format("woff");
diff --git a/conformance-checkers/html-svg/render-elems-07-t-isvalid.html b/conformance-checkers/html-svg/render-elems-07-t-isvalid.html
index a4a0cf5..bfb7317 100644
--- a/conformance-checkers/html-svg/render-elems-07-t-isvalid.html
+++ b/conformance-checkers/html-svg/render-elems-07-t-isvalid.html
@@ -34,7 +34,7 @@
           <font-face-uri xlink:href="../resources/Blocky.svg#Blocky"/>
         </font-face-src>
       </font-face>
-       <style type="text/css">
+       <style>
           @font-face {
              font-family: BlockyWoff;
              src: url(woffs/Blocky.woff) format("woff");
diff --git a/conformance-checkers/html-svg/render-elems-08-t-isvalid.html b/conformance-checkers/html-svg/render-elems-08-t-isvalid.html
index 46f409c..9411b42 100644
--- a/conformance-checkers/html-svg/render-elems-08-t-isvalid.html
+++ b/conformance-checkers/html-svg/render-elems-08-t-isvalid.html
@@ -34,7 +34,7 @@
           <font-face-uri xlink:href="../resources/Blocky.svg#Blocky"/>
         </font-face-src>
       </font-face>
-       <style type="text/css">
+       <style>
           @font-face {
              font-family: BlockyWoff;
              src: url(woffs/Blocky.woff) format("woff");
diff --git a/conformance-checkers/html-svg/render-groups-01-b-isvalid.html b/conformance-checkers/html-svg/render-groups-01-b-isvalid.html
index a81e5c5..3aa02dc 100644
--- a/conformance-checkers/html-svg/render-groups-01-b-isvalid.html
+++ b/conformance-checkers/html-svg/render-groups-01-b-isvalid.html
@@ -38,7 +38,7 @@
         <glyph unicode="G" glyph-name="G" horiz-adv-x="367" d="M355 1H18V564H355V420H125V144H248V211H156V355H355V1Z"/>
         <hkern g1="V" g2="G" k="-40"/>
       </font>
-      <style type="text/css">
+      <style>
         @font-face {
           font-family: anglepoise;
           src: url(woffs/anglepoi.woff) format("woff");
diff --git a/conformance-checkers/html-svg/render-groups-03-t-isvalid.html b/conformance-checkers/html-svg/render-groups-03-t-isvalid.html
index 937a76c..257e005 100644
--- a/conformance-checkers/html-svg/render-groups-03-t-isvalid.html
+++ b/conformance-checkers/html-svg/render-groups-03-t-isvalid.html
@@ -38,7 +38,7 @@
         <glyph unicode="G" glyph-name="G" horiz-adv-x="367" d="M355 1H18V564H355V420H125V144H248V211H156V355H355V1Z"/>
         <hkern g1="V" g2="G" k="-40"/>
       </font>
-      <style type="text/css">
+      <style>
         @font-face {
           font-family: anglepoise;
           src: url(woffs/anglepoi.woff) format("woff");
diff --git a/conformance-checkers/html-svg/struct-dom-13-f-isvalid.html b/conformance-checkers/html-svg/struct-dom-13-f-isvalid.html
index 3d1a44d..96cf66b 100644
--- a/conformance-checkers/html-svg/struct-dom-13-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-dom-13-f-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 	<defs>
-		<style type="text/css">
+		<style>
 			.passed { fill: lime }
 			.failed { fill: red }
 			.result { font: 9px monospace; fill: black }
diff --git a/conformance-checkers/html-svg/struct-dom-16-f-isvalid.html b/conformance-checkers/html-svg/struct-dom-16-f-isvalid.html
index e16c3a5..4d132e4 100644
--- a/conformance-checkers/html-svg/struct-dom-16-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-dom-16-f-isvalid.html
@@ -31,7 +31,7 @@
     <svg id="testSVG" />
     <text id="failText" x="350" y="350" font-size="50" fill="red">FAIL</text>
     <text id="passText" x="350" y="300" font-size="50" display="none">PASS</text>
-    <script type="text/javascript">    
+    <script>    
       <![CDATA[
         try
         {
diff --git a/conformance-checkers/html-svg/struct-dom-17-f-novalid.html b/conformance-checkers/html-svg/struct-dom-17-f-novalid.html
index f24a537..066e281 100644
--- a/conformance-checkers/html-svg/struct-dom-17-f-novalid.html
+++ b/conformance-checkers/html-svg/struct-dom-17-f-novalid.html
@@ -37,7 +37,7 @@
       <text id="failText" fill="red">FAIL</text>
       <text id="passText" fill="green" display="none">PASS</text>
     </g>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var passedCount = 0;
diff --git a/conformance-checkers/html-svg/struct-dom-18-f-isvalid.html b/conformance-checkers/html-svg/struct-dom-18-f-isvalid.html
index 89c2953..e6b23e2 100644
--- a/conformance-checkers/html-svg/struct-dom-18-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-dom-18-f-isvalid.html
@@ -39,7 +39,7 @@
         <text x="100" y="100" fill="black" id="testText">Filler text</text>
     </svg>
     <text id="failText" x="350" y="350" font-size="50" fill="red">FAIL</text>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var passedCount = 0;
diff --git a/conformance-checkers/html-svg/struct-dom-19-f-novalid.html b/conformance-checkers/html-svg/struct-dom-19-f-novalid.html
index 1dabbec..a09f040 100644
--- a/conformance-checkers/html-svg/struct-dom-19-f-novalid.html
+++ b/conformance-checkers/html-svg/struct-dom-19-f-novalid.html
@@ -33,7 +33,7 @@
       <text id="failText" fill="red">FAIL</text>
       <text id="passText" fill="green" display="none">PASS</text>
     </g>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var passedCount = 0;
diff --git a/conformance-checkers/html-svg/struct-svg-01-f-isvalid.html b/conformance-checkers/html-svg/struct-svg-01-f-isvalid.html
index 24f927f..db9226a 100644
--- a/conformance-checkers/html-svg/struct-svg-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-svg-01-f-isvalid.html
@@ -45,7 +45,7 @@
 
     <text id="failText" x="350" y="350" font-size="50" fill="red">FAIL</text>
 
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var testSVG = document.getElementById("testSVG");
diff --git a/conformance-checkers/html-svg/struct-svg-02-f-isvalid.html b/conformance-checkers/html-svg/struct-svg-02-f-isvalid.html
index b5a4b3f..27753f5 100644
--- a/conformance-checkers/html-svg/struct-svg-02-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-svg-02-f-isvalid.html
@@ -42,7 +42,7 @@
     <!-- Cover the whole testframe with a simple visual result -->
     <rect id="status" width="100%" height="100%" fill="none"/>
     
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
       var passes = 0;
       var ypos = 50;
 
diff --git a/conformance-checkers/html-svg/struct-use-10-f-isvalid.html b/conformance-checkers/html-svg/struct-use-10-f-isvalid.html
index f2da4cd..f477b90 100644
--- a/conformance-checkers/html-svg/struct-use-10-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-use-10-f-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css">
+    <style>
 
       /* low specificity */
       defs &gt; rect  {
diff --git a/conformance-checkers/html-svg/struct-use-11-f-novalid.html b/conformance-checkers/html-svg/struct-use-11-f-novalid.html
index ad303ba..4123aa9 100644
--- a/conformance-checkers/html-svg/struct-use-11-f-novalid.html
+++ b/conformance-checkers/html-svg/struct-use-11-f-novalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-    <style type="text/css"><![CDATA[
+    <style><![CDATA[
         #testId.test1
         {
             fill: blue;
diff --git a/conformance-checkers/html-svg/struct-use-14-f-isvalid.html b/conformance-checkers/html-svg/struct-use-14-f-isvalid.html
index 6812e60..1b634d4 100644
--- a/conformance-checkers/html-svg/struct-use-14-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-use-14-f-isvalid.html
@@ -33,7 +33,7 @@
     </defs>
     <rect width="100" height="100" fill="red"/>
     <use xlink:href="#pass" />
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var test = document.getElementById("test");
diff --git a/conformance-checkers/html-svg/struct-use-15-f-isvalid.html b/conformance-checkers/html-svg/struct-use-15-f-isvalid.html
index 2dd2cea..afcef4d 100644
--- a/conformance-checkers/html-svg/struct-use-15-f-isvalid.html
+++ b/conformance-checkers/html-svg/struct-use-15-f-isvalid.html
@@ -34,7 +34,7 @@
         <use xlink:href="#testG" id="testUse1" />
     </defs>
     <use xlink:href="#testUse1" />
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var testG = document.getElementById("testG");
diff --git a/conformance-checkers/html-svg/styling-class-01-f-isvalid.html b/conformance-checkers/html-svg/styling-class-01-f-isvalid.html
index 923205d..e680f21 100644
--- a/conformance-checkers/html-svg/styling-class-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/styling-class-01-f-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-    <style type="text/css"><![CDATA[
+    <style><![CDATA[
         .testClass
         {
             fill: blue;
diff --git a/conformance-checkers/html-svg/styling-css-01-b-isvalid.html b/conformance-checkers/html-svg/styling-css-01-b-isvalid.html
index e5e0ab3..a8d14dd 100644
--- a/conformance-checkers/html-svg/styling-css-01-b-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-01-b-isvalid.html
@@ -30,7 +30,7 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
       <defs>
-        <style type="text/css">
+        <style>
           rect { fill: green }
           .warning { fill: green }
           .bar {fill: green}
diff --git a/conformance-checkers/html-svg/styling-css-02-b-isvalid.html b/conformance-checkers/html-svg/styling-css-02-b-isvalid.html
index 6845ede..a0dab05 100644
--- a/conformance-checkers/html-svg/styling-css-02-b-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-02-b-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
-      <style type="text/css">
+      <style>
         #one, #two { fill: green }
         [transform="scale(2)"] { fill: green }
         #x [points] { fill: green }
diff --git a/conformance-checkers/html-svg/styling-css-03-b-isvalid.html b/conformance-checkers/html-svg/styling-css-03-b-isvalid.html
index 2bc98ea..1bb7a53 100644
--- a/conformance-checkers/html-svg/styling-css-03-b-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-03-b-isvalid.html
@@ -30,7 +30,7 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
     <defs>
-      <style type="text/css">
+      <style>
         .mummy {fill: green }                                       /* least specific */
         .mummy rect { fill: red}                      /* more specific, make rect red */
         .mummy &gt; .thischild { fill: green }     /* even more specific,  rect green */
diff --git a/conformance-checkers/html-svg/styling-css-04-f-isvalid.html b/conformance-checkers/html-svg/styling-css-04-f-isvalid.html
index e9aeb2e..11f7111 100644
--- a/conformance-checkers/html-svg/styling-css-04-f-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-04-f-isvalid.html
@@ -31,7 +31,7 @@
     <g fill="white">
       <text x="240" y="35" font-size="20" text-anchor="middle">CSS selector test</text>
       <defs>
-        <style type="text/css">
+        <style>
           * {stroke:red; stroke-width:1;} /* 0 */
           text {stroke:none; fill:black;} /* 1 */
           rect {stroke:black; stroke-dasharray:none;} /* 1 */
diff --git a/conformance-checkers/html-svg/styling-css-05-b-isvalid.html b/conformance-checkers/html-svg/styling-css-05-b-isvalid.html
index 9b83184..74c7d79 100644
--- a/conformance-checkers/html-svg/styling-css-05-b-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-05-b-isvalid.html
@@ -33,7 +33,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css">
+    <style>
       :lang(en) { fill: green}
       :lang(fr) { fill: blue }
       :lang(fr-ca) {font-style: italic}
diff --git a/conformance-checkers/html-svg/styling-css-06-b-isvalid.html b/conformance-checkers/html-svg/styling-css-06-b-isvalid.html
index 2767329..1264581 100644
--- a/conformance-checkers/html-svg/styling-css-06-b-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-06-b-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css">
+    <style>
       svg:hover {fill: none}
       text {fill: black}
       :link {fill: #33F}
diff --git a/conformance-checkers/html-svg/styling-css-08-f-isvalid.html b/conformance-checkers/html-svg/styling-css-08-f-isvalid.html
index ab31884..bbdc60d 100644
--- a/conformance-checkers/html-svg/styling-css-08-f-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-08-f-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-    <style type="text/css"><![CDATA[
+    <style><![CDATA[
         path
         {
             visibility: hidden;
diff --git a/conformance-checkers/html-svg/styling-css-09-f-isvalid.html b/conformance-checkers/html-svg/styling-css-09-f-isvalid.html
index f7e053e..3948d56 100644
--- a/conformance-checkers/html-svg/styling-css-09-f-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-09-f-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-size="18">
-    <style type="text/css"><![CDATA[
+    <style><![CDATA[
         @import url("../images/selector-types-visibility-hidden.css");]]>
     </style>
     <path class="reference" d="M 50 125 L 150 125 L 100 75 z" fill="blue" visibility="hidden"/>
diff --git a/conformance-checkers/html-svg/styling-css-10-f-isvalid.html b/conformance-checkers/html-svg/styling-css-10-f-isvalid.html
index 1f2f2ff..e1c9b20 100644
--- a/conformance-checkers/html-svg/styling-css-10-f-isvalid.html
+++ b/conformance-checkers/html-svg/styling-css-10-f-isvalid.html
@@ -36,7 +36,7 @@
     <circle id="c" fill="blue" cx="140" cy="220" r="50"/>
     <circle id="d" fill="blue" cx="340" cy="220" r="50"/>
 
-    <style type="text/css">
+    <style>
         @import url("../images/case-insensitivity.css");
         #c {fill: red }
         #c {FiLl: oRaNgE }
diff --git a/conformance-checkers/html-svg/styling-pres-04-f-isvalid.html b/conformance-checkers/html-svg/styling-pres-04-f-isvalid.html
index 6ddcdb2..ef28674 100644
--- a/conformance-checkers/html-svg/styling-pres-04-f-isvalid.html
+++ b/conformance-checkers/html-svg/styling-pres-04-f-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-    <style type="text/css"><![CDATA[
+    <style><![CDATA[
         path
         {
             fill: green;
diff --git a/conformance-checkers/html-svg/styling-pres-05-f-isvalid.html b/conformance-checkers/html-svg/styling-pres-05-f-isvalid.html
index 226dc0d..a0846c0 100644
--- a/conformance-checkers/html-svg/styling-pres-05-f-isvalid.html
+++ b/conformance-checkers/html-svg/styling-pres-05-f-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-    <style type="text/css"><![CDATA[
+    <style><![CDATA[
         @import url("../images/selector-types-fill-green.css");]]>
     </style>
 
diff --git a/conformance-checkers/html-svg/text-align-08-b-isvalid.html b/conformance-checkers/html-svg/text-align-08-b-isvalid.html
index d945993..48ee3df 100644
--- a/conformance-checkers/html-svg/text-align-08-b-isvalid.html
+++ b/conformance-checkers/html-svg/text-align-08-b-isvalid.html
@@ -35,7 +35,7 @@
       </font-face-src>
     </font-face>
   </defs>
-  <style type="text/css">
+  <style>
   @font-face {
     font-family: Tribase ;
     src: url(woffs/Tribase.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-dom-02-f-isvalid.html b/conformance-checkers/html-svg/text-dom-02-f-isvalid.html
index da0d722..d70ffc6 100644
--- a/conformance-checkers/html-svg/text-dom-02-f-isvalid.html
+++ b/conformance-checkers/html-svg/text-dom-02-f-isvalid.html
@@ -35,7 +35,7 @@
           <font-face-uri xlink:href="../resources/Plane1/PlaneOne.svg#SPlaneOne"/>
         </font-face-src>
     </font-face>
-    <style type="text/css">
+    <style>
       @font-face {
         font-family: PlaneOne;
         src: url(woffs/PlaneOne.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-dom-05-f-isvalid.html b/conformance-checkers/html-svg/text-dom-05-f-isvalid.html
index af84356..5849399 100644
--- a/conformance-checkers/html-svg/text-dom-05-f-isvalid.html
+++ b/conformance-checkers/html-svg/text-dom-05-f-isvalid.html
@@ -35,7 +35,7 @@
           <font-face-uri xlink:href="../resources/Plane1/PlaneOne.svg#SPlaneOne"/>
         </font-face-src>
     </font-face>
-    <style type="text/css">
+    <style>
       @font-face {
         font-family: PlaneOne;
         src: url(woffs/PlaneOne.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-fonts-204-t-isvalid.html b/conformance-checkers/html-svg/text-fonts-204-t-isvalid.html
index 7a4b1d6..26f5f71 100644
--- a/conformance-checkers/html-svg/text-fonts-204-t-isvalid.html
+++ b/conformance-checkers/html-svg/text-fonts-204-t-isvalid.html
@@ -31,7 +31,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
   <defs>
-    <style type="text/css">
+    <style>
       @font-face {
       font-family: ZC;
       unicode-range: U+0-7F;
diff --git a/conformance-checkers/html-svg/text-intro-06-t-isvalid.html b/conformance-checkers/html-svg/text-intro-06-t-isvalid.html
index 48b50d8..033de9d 100644
--- a/conformance-checkers/html-svg/text-intro-06-t-isvalid.html
+++ b/conformance-checkers/html-svg/text-intro-06-t-isvalid.html
@@ -20,7 +20,7 @@
   <!--======================================================================-->
   
   <title id="test-title">$RCSfile: text-intro-06-t.svg,v $</title>
-  <style type="text/css">
+  <style>
     @font-face {
       font-family: Scheherezade;
       src: url(woffs/ScheherazadeRegOT.woff) format("woff")
diff --git a/conformance-checkers/html-svg/text-intro-09-b-isvalid.html b/conformance-checkers/html-svg/text-intro-09-b-isvalid.html
index d6c6f8d..1b500ec 100644
--- a/conformance-checkers/html-svg/text-intro-09-b-isvalid.html
+++ b/conformance-checkers/html-svg/text-intro-09-b-isvalid.html
@@ -21,7 +21,7 @@
   
   <title id="test-title">$RCSfile: text-intro-09-b.svg,v $</title>
   <defs>
-    <style type="text/css">
+    <style>
   @font-face {
       font-family: Ezra SIL SR;
       src: url(woffs/EzraSILSR.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-intro-12-t-isvalid.html b/conformance-checkers/html-svg/text-intro-12-t-isvalid.html
index a2d65e1..30203c9 100644
--- a/conformance-checkers/html-svg/text-intro-12-t-isvalid.html
+++ b/conformance-checkers/html-svg/text-intro-12-t-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css">
+    <style>
       @font-face {
         font-family: Scheherazade;
         src: url(woffs/ScheherazadeRegOT.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-path-02-b-isvalid.html b/conformance-checkers/html-svg/text-path-02-b-isvalid.html
index 48dea56..5a0ca93 100644
--- a/conformance-checkers/html-svg/text-path-02-b-isvalid.html
+++ b/conformance-checkers/html-svg/text-path-02-b-isvalid.html
@@ -29,7 +29,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII, FreeSansWoff, sans-serif" font-size="18">
     <defs>
-      <style type="text/css">
+      <style>
       @font-face {
         font-family: FreeSansWoff;
         src: url(woffs/FreeSans.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-text-04-t-isvalid.html b/conformance-checkers/html-svg/text-text-04-t-isvalid.html
index ab06f0f..91319e5 100644
--- a/conformance-checkers/html-svg/text-text-04-t-isvalid.html
+++ b/conformance-checkers/html-svg/text-text-04-t-isvalid.html
@@ -20,7 +20,7 @@
   <!--======================================================================-->
   
   <title id="test-title">$RCSfile: text-text-04-t.svg,v $</title>
-  <style type="text/css">
+  <style>
   @font-face {
     font-family: embeded;
     src: url(woffs/embeded-text-text-04.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-text-05-t-isvalid.html b/conformance-checkers/html-svg/text-text-05-t-isvalid.html
index 9dd6b49..8575bf4 100644
--- a/conformance-checkers/html-svg/text-text-05-t-isvalid.html
+++ b/conformance-checkers/html-svg/text-text-05-t-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css">
+    <style>
   @font-face {
     font-family: embeded;
     src: url(woffs/embeded-text-text-05.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-text-06-t-isvalid.html b/conformance-checkers/html-svg/text-text-06-t-isvalid.html
index 823cbb6..0d97c01 100644
--- a/conformance-checkers/html-svg/text-text-06-t-isvalid.html
+++ b/conformance-checkers/html-svg/text-text-06-t-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css">
+    <style>
   @font-face {
     font-family: embeded;
     src: url(woffs/embeded-text-text-06.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-text-12-t-isvalid.html b/conformance-checkers/html-svg/text-text-12-t-isvalid.html
index d28fd07..c92eb2e 100644
--- a/conformance-checkers/html-svg/text-text-12-t-isvalid.html
+++ b/conformance-checkers/html-svg/text-text-12-t-isvalid.html
@@ -28,7 +28,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    <style type="text/css">
+    <style>
   @font-face {
     font-family: embeded;
     src: url(woffs/embeded-text-text-05.woff) format("woff");
diff --git a/conformance-checkers/html-svg/text-tselect-03-f-isvalid.html b/conformance-checkers/html-svg/text-tselect-03-f-isvalid.html
index 6ce8758..b59de9d 100644
--- a/conformance-checkers/html-svg/text-tselect-03-f-isvalid.html
+++ b/conformance-checkers/html-svg/text-tselect-03-f-isvalid.html
@@ -21,7 +21,7 @@
   
   <title id="test-title">$RCSfile: text-tselect-03-f.svg,v $</title>
   <defs>
-    <style type="text/css">
+    <style>
 	@font-face {
 	    font-family: Ezra SIL SR;
 	    src: url(woffs/EzraSILSR.woff) format("woff");
diff --git a/conformance-checkers/html-svg/types-dom-svgfittoviewbox-01-f-isvalid.html b/conformance-checkers/html-svg/types-dom-svgfittoviewbox-01-f-isvalid.html
index a4ec2b6..e158b9d 100644
--- a/conformance-checkers/html-svg/types-dom-svgfittoviewbox-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/types-dom-svgfittoviewbox-01-f-isvalid.html
@@ -31,7 +31,7 @@
     <svg id="testSvg" viewBox="0 0 300 200" preserveAspectRatio="xMinYMin slice" />
     <text id="failText" x="100" y="100" font-size="80" fill="red">FAIL</text>
     <text id="passText" x="350" y="300" font-size="50" display="none">PASS</text>
-    <script type="text/javascript">    
+    <script>    
       <![CDATA[
  
         try
diff --git a/conformance-checkers/html-svg/types-dom-svglengthlist-01-f-isvalid.html b/conformance-checkers/html-svg/types-dom-svglengthlist-01-f-isvalid.html
index 1b0470c..a6638a7 100644
--- a/conformance-checkers/html-svg/types-dom-svglengthlist-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/types-dom-svglengthlist-01-f-isvalid.html
@@ -31,7 +31,7 @@
       <text id="failText" fill="red">FAIL</text>
       <text id="passText" fill="green" display="none">PASS</text>
     </g>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var testElement = document.getElementById("test");
diff --git a/conformance-checkers/html-svg/types-dom-svgnumberlist-01-f-isvalid.html b/conformance-checkers/html-svg/types-dom-svgnumberlist-01-f-isvalid.html
index 7dbda06..7590ecb 100644
--- a/conformance-checkers/html-svg/types-dom-svgnumberlist-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/types-dom-svgnumberlist-01-f-isvalid.html
@@ -31,7 +31,7 @@
       <text id="failText" fill="red">FAIL</text>
       <text id="passText" fill="green" display="none">PASS</text>
     </g>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var testElement = document.getElementById("test");
diff --git a/conformance-checkers/html-svg/types-dom-svgstringlist-01-f-isvalid.html b/conformance-checkers/html-svg/types-dom-svgstringlist-01-f-isvalid.html
index f1ad116..0118e06 100644
--- a/conformance-checkers/html-svg/types-dom-svgstringlist-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/types-dom-svgstringlist-01-f-isvalid.html
@@ -31,7 +31,7 @@
       <text id="failText" fill="red">FAIL</text>
       <text id="passText" fill="green" display="none">PASS</text>
     </g>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var testElement = document.getElementById("test");
diff --git a/conformance-checkers/html-svg/types-dom-svgtransformable-01-f-isvalid.html b/conformance-checkers/html-svg/types-dom-svgtransformable-01-f-isvalid.html
index c9ba4eb..2e52606 100644
--- a/conformance-checkers/html-svg/types-dom-svgtransformable-01-f-isvalid.html
+++ b/conformance-checkers/html-svg/types-dom-svgtransformable-01-f-isvalid.html
@@ -31,7 +31,7 @@
       <text id="failText" fill="red">FAIL</text>
       <text id="passText" fill="green" display="none">PASS</text>
     </g>
-    <script type="text/javascript"><![CDATA[
+    <script><![CDATA[
         try
         {
             var testElement = document.getElementById("test");
diff --git a/conformance-checkers/html/elements/a/href/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/a/href/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index e818ba7..0000000
--- a/conformance-checkers/html/elements/a/href/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid href: scheme-javascript-no-slash-malformed</title>
-<a href="javascript:example.com/"></a>
diff --git a/conformance-checkers/html/elements/area/href/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/area/href/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index bb12e65..0000000
--- a/conformance-checkers/html/elements/area/href/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid href: scheme-javascript-no-slash-malformed</title>
-<map name=foo><area href="javascript:example.com/" alt></map>
diff --git a/conformance-checkers/html/elements/audio/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/audio/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 7d37b16..0000000
--- a/conformance-checkers/html/elements/audio/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<audio src="javascript:example.com/"></audio>
diff --git a/conformance-checkers/html/elements/base/href/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/base/href/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 16b4187..0000000
--- a/conformance-checkers/html/elements/base/href/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid href: scheme-javascript-no-slash-malformed</title>
-<base href="javascript:example.com/">
diff --git a/conformance-checkers/html/elements/blockquote/cite/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/blockquote/cite/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 71df3f3..0000000
--- a/conformance-checkers/html/elements/blockquote/cite/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid cite: scheme-javascript-no-slash-malformed</title>
-<blockquote cite="javascript:example.com/"></blockquote>
diff --git a/conformance-checkers/html/elements/button/formaction/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/button/formaction/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 74c1cbc..0000000
--- a/conformance-checkers/html/elements/button/formaction/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid formaction: scheme-javascript-no-slash-malformed</title>
-<button formaction="javascript:example.com/"></button>
diff --git a/conformance-checkers/html/elements/del/cite/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/del/cite/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 4328b02..0000000
--- a/conformance-checkers/html/elements/del/cite/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid cite: scheme-javascript-no-slash-malformed</title>
-<del cite="javascript:example.com/"></del>
diff --git a/conformance-checkers/html/elements/embed/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/embed/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 52aff9f..0000000
--- a/conformance-checkers/html/elements/embed/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<embed src="javascript:example.com/">
diff --git a/conformance-checkers/html/elements/form/action/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/form/action/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index bc66953..0000000
--- a/conformance-checkers/html/elements/form/action/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid action: scheme-javascript-no-slash-malformed</title>
-<form action="javascript:example.com/"></form>
diff --git a/conformance-checkers/html/elements/iframe/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/iframe/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 6424e83..0000000
--- a/conformance-checkers/html/elements/iframe/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<iframe src="javascript:example.com/"></iframe>
diff --git a/conformance-checkers/html/elements/img/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/img/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 00a8c88..0000000
--- a/conformance-checkers/html/elements/img/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<img src="javascript:example.com/" alt>
diff --git a/conformance-checkers/html/elements/input/type-image-formaction/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/input/type-image-formaction/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 727da48..0000000
--- a/conformance-checkers/html/elements/input/type-image-formaction/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid formaction: scheme-javascript-no-slash-malformed</title>
-<input type=image alt="foo" formaction="javascript:example.com/">
diff --git a/conformance-checkers/html/elements/input/type-image-src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/input/type-image-src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index f22ebf5..0000000
--- a/conformance-checkers/html/elements/input/type-image-src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<input type=image alt="foo" src="javascript:example.com/">
diff --git a/conformance-checkers/html/elements/input/type-submit-formaction/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/input/type-submit-formaction/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index fe2c0ce..0000000
--- a/conformance-checkers/html/elements/input/type-submit-formaction/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid formaction: scheme-javascript-no-slash-malformed</title>
-<input type=submit formaction="javascript:example.com/">
diff --git a/conformance-checkers/html/elements/input/type-url-value/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/input/type-url-value/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 73c7e6a..0000000
--- a/conformance-checkers/html/elements/input/type-url-value/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid value attribute: scheme-javascript-no-slash-malformed</title>
-<input type=url value="javascript:example.com/">
diff --git a/conformance-checkers/html/elements/ins/cite/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/ins/cite/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 267f113..0000000
--- a/conformance-checkers/html/elements/ins/cite/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid cite: scheme-javascript-no-slash-malformed</title>
-<ins cite="javascript:example.com/"></ins>
diff --git a/conformance-checkers/html/elements/link/href/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/link/href/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 6cf49ca..0000000
--- a/conformance-checkers/html/elements/link/href/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid href: scheme-javascript-no-slash-malformed</title>
-<link href="javascript:example.com/" rel=help>
diff --git a/conformance-checkers/html/elements/object/data/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/object/data/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index aa27796..0000000
--- a/conformance-checkers/html/elements/object/data/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid data: scheme-javascript-no-slash-malformed</title>
-<object data="javascript:example.com/"></object>
diff --git a/conformance-checkers/html/elements/q/cite/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/q/cite/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index a142b00..0000000
--- a/conformance-checkers/html/elements/q/cite/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid cite: scheme-javascript-no-slash-malformed</title>
-<q cite="javascript:example.com/"></q>
diff --git a/conformance-checkers/html/elements/script/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/script/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 6e85d80..0000000
--- a/conformance-checkers/html/elements/script/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<script src="javascript:example.com/"></script>
diff --git a/conformance-checkers/html/elements/script/streams-demo-append-child-isvalid.html b/conformance-checkers/html/elements/script/streams-demo-append-child-isvalid.html
new file mode 100644
index 0000000..d2fdf78
--- /dev/null
+++ b/conformance-checkers/html/elements/script/streams-demo-append-child-isvalid.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Append child writable stream demo</title>
+
+<script src="transforms/transform-stream-polyfill.js"></script>
+<script src="transforms/text-encode-transform.js"></script>
+<script src="transforms/parse-json.js"></script>
+<script src="transforms/split-stream.js"></script>
+<script src="resources/highlight.pack.js"></script>
+<script src="resources/web-animations.min.js"></script>
+<script src="tags/view-source.js"></script>
+
+<link rel=stylesheet href="resources/common.css">
+<link rel=stylesheet href="resources/commits.css">
+
+<h1>Append child writable stream demo</h1>
+<a id=back-to-index href=index.html>Back to demo index</a>
+<view-source></view-source>
+
+<div id=buttons>
+  <button id="load">Load JSON stream</button>
+  <button id="reset">Reset</button>
+</div>
+
+<div id=target></div>
+
+<script>
+'use strict';
+const loadButton = document.querySelector('#load');
+const resetButton = document.querySelector('#reset');
+const targetDiv = document.querySelector('#target');
+const FIELDS =  ['Hash', 'Date', 'Author', 'Subject'];
+const FIELDS_LOWERCASE = FIELDS.map(string => string.toLowerCase());
+
+function createTable(parentElement) {
+  const table = document.createElement('table');
+  table.id = 'commits';
+  const tr = document.createElement('tr');
+  for (const heading of FIELDS) {
+    const th = document.createElement('th');
+    th.textContent = heading;
+    tr.appendChild(th);
+  }
+  table.appendChild(tr);
+  parentElement.appendChild(table);
+  return table;
+}
+
+// BEGIN SOURCE TO VIEW
+function appendChildWritableStream(parentNode) {
+  return new WritableStream({
+    write(chunk) {
+      parentNode.appendChild(chunk);
+    }
+  });
+}
+
+async function fetchThenJSONToDOM() {
+  const jsonToElementTransform = new TransformStream({
+    transform(chunk, controller) {
+      const tr = document.createElement('tr');
+      for (const cell of FIELDS_LOWERCASE) {
+        const td = document.createElement('td');
+        td.textContent = chunk[cell];
+        tr.appendChild(td);
+      }
+      controller.enqueue(tr);
+    }
+  });
+
+  const response = await fetch('data/commits.json',
+                               {
+                                 mode: 'same-origin',
+                                 headers: {
+                                   'Cache-Control': 'no-cache, no-store'
+                                 }
+                               });
+
+  const table = createTable(targetDiv);
+  return response.body
+    .pipeThrough(new TextDecoder())
+    .pipeThrough(splitStream('\n'))
+    .pipeThrough(parseJSON())
+    .pipeThrough(jsonToElementTransform)
+    .pipeTo(appendChildWritableStream(table));
+}
+// END SOURCE TO VIEW
+
+loadButton.onclick = () => {
+  loadButton.disabled = true;
+  setTimeout(fetchThenJSONToDOM, 0);
+};
+
+resetButton.onclick = () => {
+  targetDiv.innerHTML = '';
+  loadButton.disabled = false;
+};
+</script>
diff --git a/conformance-checkers/html/elements/script/streams-demo-streaming-element-backpressure-isvalid.html b/conformance-checkers/html/elements/script/streams-demo-streaming-element-backpressure-isvalid.html
new file mode 100644
index 0000000..35bf133
--- /dev/null
+++ b/conformance-checkers/html/elements/script/streams-demo-streaming-element-backpressure-isvalid.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Streaming element with backpressure demo</title>
+
+<script src="transforms/transform-stream-polyfill.js"></script>
+<script src="transforms/text-encode-transform.js"></script>
+<script src="tags/streaming-element-backpressure.js"></script>
+<script src="resources/highlight.pack.js"></script>
+<script src="resources/web-animations.min.js"></script>
+<script src="tags/view-source.js"></script>
+
+<link rel=stylesheet href="resources/common.css">
+<link rel=stylesheet href="resources/jank-meter.css">
+<link rel=stylesheet href="resources/commits.css">
+
+<h1>Streaming element with backpressure demo</h1>
+<a id=back-to-index href=index.html>Back to demo index</a>
+<view-source></view-source>
+
+<div id=buttons>
+  <button id="load">Load HTML stream, applying backpressure</button>
+  <button id="reset">Reset</button>
+</div>
+
+<div id=jank-meter>JANK METER</div>
+
+<streaming-element-backpressure id=target></streaming-element-backpressure>
+
+<script>
+'use strict';
+const loadButton = document.querySelector('#load');
+const resetButton = document.querySelector('#reset');
+const targetDiv = document.querySelector('#target');
+
+loadButton.onclick = async () => {
+  loadButton.disabled = true;
+  fetchDirectlyIntoDOM();
+};
+
+// BEGIN SOURCE TO VIEW
+async function fetchDirectlyIntoDOM() {
+  const response = await fetch('data/commits.include',
+                             {
+                               mode: 'same-origin',
+                               headers: {
+                                 'Cache-Control': 'no-cache, no-store'
+                               }
+                             });
+
+  await response.body
+      .pipeThrough(new TextDecoder())
+      .pipeTo(targetDiv.writable);
+}
+// END SOURCE TO VIEW
+
+resetButton.onclick = () => {
+  targetDiv.reset();
+  loadButton.disabled = false;
+};
+</script>
diff --git a/conformance-checkers/html/elements/script/streams-demo-streaming-element-isvalid.html b/conformance-checkers/html/elements/script/streams-demo-streaming-element-isvalid.html
new file mode 100644
index 0000000..c9c70d5
--- /dev/null
+++ b/conformance-checkers/html/elements/script/streams-demo-streaming-element-isvalid.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Streaming element demo</title>
+
+<script src="transforms/transform-stream-polyfill.js"></script>
+<script src="transforms/text-encode-transform.js"></script>
+<script src="tags/streaming-element.js"></script>
+<script src="resources/highlight.pack.js"></script>
+<script src="resources/web-animations.min.js"></script>
+<script src="tags/view-source.js"></script>
+
+<link rel=stylesheet href="resources/common.css">
+<link rel=stylesheet href="resources/jank-meter.css">
+<link rel=stylesheet href="resources/commits.css">
+
+<h1>Streaming element demo</h1>
+<a id=back-to-index href=index.html>Back to demo index</a>
+<view-source></view-source>
+
+<div id=buttons>
+  <button id="load">Load HTML stream</button>
+  <button id="reset">Reset</button>
+</div>
+
+<div id=jank-meter>JANK METER</div>
+
+<streaming-element id=target></streaming-element>
+
+<script>
+'use strict';
+const loadButton = document.querySelector('#load');
+const resetButton = document.querySelector('#reset');
+const targetDiv = document.querySelector('#target');
+
+// BEGIN SOURCE TO VIEW
+async function streamDirectlyIntoDOM() {
+  const response = await fetch('data/commits.include',
+                               {
+                                 mode: 'same-origin',
+                                 headers: {
+                                   'Cache-Control': 'no-cache, no-store'
+                                 }
+                               });
+
+  await response.body
+      .pipeThrough(new TextDecoder())
+      .pipeTo(targetDiv.writable);
+}
+
+loadButton.onclick = () => {
+  loadButton.disabled = true;
+  streamDirectlyIntoDOM();
+};
+// END SOURCE TO VIEW
+
+resetButton.onclick = () => {
+  targetDiv.reset();
+  loadButton.disabled = false;
+};
+</script>
diff --git a/conformance-checkers/html/elements/source/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/source/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 6516b42..0000000
--- a/conformance-checkers/html/elements/source/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<video><source src="javascript:example.com/"></video>
diff --git a/conformance-checkers/html/elements/style/html-spec-comms-isvalid.html b/conformance-checkers/html/elements/style/html-spec-comms-isvalid.html
new file mode 100644
index 0000000..69f70d5
--- /dev/null
+++ b/conformance-checkers/html/elements/style/html-spec-comms-isvalid.html
@@ -0,0 +1,276 @@
+<!DOCTYPE html><html class=split lang=en-US-x-hixie><script src=/link-fixup.js defer=""></script><meta charset=utf-8><meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name=viewport><title>HTML Standard</title><meta content=#3c790a name=theme-color><link rel=stylesheet href=https://resources.whatwg.org/standard.css><link rel=icon href=https://resources.whatwg.org/logo.svg><script>
+   function toggleStatus(div) {
+     div.parentNode.classList.toggle('wrapped');
+   }
+  </script><style>
+   .status { min-height: 0.6em; font: 1em sans-serif; width: 9em; padding: 0.3em; position: absolute; z-index: 8; right: 0.3em; background: #EEE; color: black; box-shadow: 0 0 3px #999; overflow: hidden; margin: -2em 0 0 0; border-collapse: initial; border-spacing: initial; }
+   .status:hover { z-index: 9; }
+   .status:focus-within { z-index: 9; }
+   .status.wrapped > :not(input) { display: none; }
+   .status > input { position: absolute; left: 0; top: 0; width: 1em; height: 1em; border: none; background: transparent; padding: 0; margin: 0; }
+   .status > p { font-size: 0.6em; margin: 0; padding: 0; }
+   .status > p + p { padding-top: 0.5em; }
+   .status > p > strong { margin-left: 1.5em; }
+   .status > .support { display: block; }
+   .status > .support > span { padding: 0.2em 0; display: block; display: table; }
+   .status > .support > span.partial { color: #666666; filter: grayscale(50%); }
+   .status > .support > span.no { color: #CCCCCC; filter: grayscale(100%); }
+   .status > .support > span.no::before { opacity: 0.5; }
+   .status > .support > span:first-of-type { padding-top: 0.5em; }
+   .status > .support > span > span { padding: 0 0.5em; display: table-cell; vertical-align: top; }
+   .status > .support > span > span:first-child { width: 100%; }
+   .status > .support > span > span:last-child { width: 100%; white-space: pre; padding: 0; }
+   .status > .support > span::before { content: ' '; display: table-cell; min-width: 1.5em; height: 1.5em; background: no-repeat center center; background-size: contain; text-align: right; font-size: 0.75em; font-weight: bold; }
+   .status > .support > .and_chr::before { background-image: url(https://resources.whatwg.org/browser-logos/chrome.svg); }
+   .status > .support > .and_ff::before { background-image: url(https://resources.whatwg.org/browser-logos/firefox.png); }
+   .status > .support > .and_uc::before { background-image: url(https://resources.whatwg.org/browser-logos/uc.png); } /* UC Browser for Android */
+   .status > .support > .android::before { background-image: url(https://resources.whatwg.org/browser-logos/android.svg); }
+   .status > .support > .bb::before { background-image: url(https://resources.whatwg.org/browser-logos/bb.jpg); } /* Blackberry Browser */
+   .status > .support > .chrome::before { background-image: url(https://resources.whatwg.org/browser-logos/chrome.svg); }
+   .status > .support > .edge::before { background-image: url(https://resources.whatwg.org/browser-logos/edge.svg); }
+   .status > .support > .firefox::before { background-image: url(https://resources.whatwg.org/browser-logos/firefox.png); }
+   .status > .support > .ie::before { background-image: url(https://resources.whatwg.org/browser-logos/ie.png); }
+   .status > .support > .ie_mob::before { background-image: url(https://resources.whatwg.org/browser-logos/ie-mobile.svg); }
+   .status > .support > .ios_saf::before { background-image: url(https://resources.whatwg.org/browser-logos/safari-ios.svg); }
+   .status > .support > .op_mini::before { background-image: url(https://resources.whatwg.org/browser-logos/opera-mini.png); }
+   .status > .support > .op_mob::before { background-image: url(https://resources.whatwg.org/browser-logos/opera.png); }
+   .status > .support > .opera::before { background-image: url(https://resources.whatwg.org/browser-logos/opera.png); }
+   .status > .support > .safari::before { background-image: url(https://resources.whatwg.org/browser-logos/safari.png); }
+   .status > .support > .samsung::before { background-image: url(https://resources.whatwg.org/browser-logos/samsung.png); }
+   .status > .caniuse { text-align: right; font-style: italic; width: 100%; }
+   .status > .caniuse + p { margin-top: 0.5em; border-top: 1px solid silver; }
+
+   @media (max-width: 767px) {
+     .status { right: -9em; }
+   }
+  </style><style>
+   [hidden] { display: none; }
+
+   .bad, .bad *:not(.X\58X) { color: gray; border-color: gray; background: transparent; }
+
+   .fingerprint { position: absolute; right: 0; z-index: 5; }
+   @media (max-width: 767px) {
+     .fingerprint { max-width: 35px; }
+   }
+
+   .applies .yes, .yesno .yes { background: yellow; }
+   .yesno .yes, .yesno .no { text-align: center; }
+
+   .applies thead th > * { display: block; }
+   .applies thead code { display: block; }
+   .applies td { text-align: center; }
+
+   .matrix, .matrix td { border: hidden; text-align: right; }
+   .matrix { margin-left: 2em; }
+
+   .vertical-summary-table tr > th[rowspan="2"]:first-child + th,
+   .vertical-summary-table tr > td[rowspan="2"]:first-child + td { border-bottom: hidden; }
+
+   .dice-example { border-collapse: collapse; border-style: hidden solid solid hidden; border-width: thin; margin-left: 3em; }
+   .dice-example caption { width: 30em; font-size: smaller; font-style: italic; padding: 0.75em 0; text-align: left; }
+   .dice-example td, .dice-example th { border: solid thin; width: 1.35em; height: 1.05em; text-align: center; padding: 0; }
+
+   td.eg { border-width: thin; text-align: center; }
+
+   #table-example-1 { border: solid thin; border-collapse: collapse; margin-left: 3em; }
+   #table-example-1 caption { padding-bottom: 0.5em; }
+   #table-example-1 thead, #table-example-1 tbody { border: none; }
+   #table-example-1 th, #table-example-1 td { border: solid thin; }
+   #table-example-1 th { font-weight: normal; }
+   #table-example-1 td { border-style: none solid; vertical-align: top; }
+   #table-example-1 th { padding: 0.5em; vertical-align: middle; text-align: center; }
+   #table-example-1 tbody tr:first-child td { padding-top: 0.5em; }
+   #table-example-1 tbody tr:last-child td { padding-bottom: 1.5em; }
+   #table-example-1 tbody td:first-child { padding-left: 2.5em; padding-right: 0; width: 9em; }
+   #table-example-1 tbody td:first-child::after { content: leader(". "); }
+   #table-example-1 tbody td { padding-left: 2em; padding-right: 2em; }
+   #table-example-1 tbody td:first-child + td { width: 10em; }
+   #table-example-1 tbody td:first-child + td ~ td { width: 2.5em; }
+   #table-example-1 tbody td:first-child + td + td + td ~ td { width: 1.25em; }
+
+   .apple-table-examples { border: none; border-collapse: separate; border-spacing: 1.5em 0em; width: 40em; margin-left: 3em; }
+   .apple-table-examples * { font-family: "Times", serif; }
+   .apple-table-examples td, .apple-table-examples th { border: none; white-space: nowrap; padding-top: 0; padding-bottom: 0; }
+   .apple-table-examples tbody th:first-child { border-left: none; width: 100%; }
+   .apple-table-examples thead th:first-child ~ th { font-size: smaller; font-weight: bolder; border-bottom: solid 2px; text-align: center; }
+   .apple-table-examples tbody th::after, .apple-table-examples tfoot th::after { content: leader(". ") }
+   .apple-table-examples tbody th, .apple-table-examples tfoot th { font: inherit; text-align: left; }
+   .apple-table-examples td { text-align: right; vertical-align: top; }
+   .apple-table-examples.e1 tbody tr:last-child td { border-bottom: solid 1px; }
+   .apple-table-examples.e1 tbody + tbody tr:last-child td { border-bottom: double 3px; }
+   .apple-table-examples.e2 th[scope=row] { padding-left: 1em; }
+   .apple-table-examples sup { line-height: 0; }
+
+   .three-column-nowrap tr > td:first-child,
+   .three-column-nowrap tr > td:first-child + td,
+   .three-column-nowrap tr > td:first-child + td + td { white-space: nowrap; }
+
+   .details-example img { vertical-align: top; }
+
+   .parse-error-table td > p:first-child { margin-top: 0; }
+
+   #named-character-references-table {
+     white-space: nowrap;
+     font-size: 0.6em;
+     column-width: 30em;
+     column-gap: 1em;
+     -moz-column-width: 30em;
+     -moz-column-gap: 1em;
+     -webkit-column-width: 30em;
+     -webkit-column-gap: 1em;
+   }
+   #named-character-references-table > table > tbody > tr > td:first-child + td,
+   #named-character-references-table > table > tbody > tr > td:last-child { text-align: center; }
+   #named-character-references-table > table > tbody > tr > td:last-child:hover > span { position: absolute; top: auto; left: auto; margin-left: 0.5em; line-height: 1.2; font-size: 5em; border: outset; padding: 0.25em 0.5em; background: white; width: 1.25em; height: auto; text-align: center; }
+   #named-character-references-table > table > tbody > tr#entity-CounterClockwiseContourIntegral > td:first-child { font-size: 0.5em; }
+
+   .glyph.control { color: red; }
+
+   #table-example-1 * { font-family: "Essays1743", serif; line-height: 1.01em; }
+   @font-face {
+     font-family: 'Essays1743';
+     src: url('/fonts/Essays1743.ttf');
+   }
+   @font-face {
+     font-family: 'Essays1743';
+     font-weight: bold;
+     src: url('/fonts/Essays1743-Bold.ttf');
+   }
+   @font-face {
+     font-family: 'Essays1743';
+     font-style: italic;
+     src: url('/fonts/Essays1743-Italic.ttf');
+   }
+   @font-face {
+     font-family: 'Essays1743';
+     font-style: italic;
+     font-weight: bold;
+     src: url('/fonts/Essays1743-BoldItalic.ttf');
+   }
+
+   @media (max-width: 767px) {
+     #abstractimg { width: 100%; }
+   }
+   #abstractimg, #abstractimg text { font: inherit; }
+   #abstractimg rect { fill: #424242; }
+   #abstractimg text { fill: #ffffff; font-size: 18px }
+   #abstractimg .top { word-spacing: 50px; text-anchor: middle; }
+   #abstractimg .left, #abstractimg .bottom { word-spacing: 12px; }
+   #abstractimg .right { font-size: 25px; }
+  </style><body>
+  
+  <script async="" src=/html-dfn.js></script>
+  <script data-file-issue-url=https://github.com/whatwg/html/issues/new src=https://resources.whatwg.org/file-issue.js async=""></script>
+  <header id=head class="head with-buttons">
+   <a href=https://whatwg.org/ class=logo><img alt=WHATWG src=https://resources.whatwg.org/logo.svg width=100 height=100></a>
+   <h1 class=allcaps>HTML</h1><h2 id=living-standard class="no-num no-toc">Living Standard — Last Updated <span class=pubdate>9 December 2017</span></h2>
+   
+   
+
+   
+  </header>
+
+  
+
+  
+  
+
+  
+  
+
+  
+
+  <nav><a href=imagebitmap-and-animations.html>← 8.8 Images</a> — <a href=./>Table of Contents</a> — <a href=server-sent-events.html>9.2 Server-sent events →</a></nav><ol class=toc><li id=toc-comms><a href=comms.html#comms><span class=secno>9</span> Communication</a><ol><li><a href=comms.html#the-messageevent-interface><span class=secno>9.1</span> The <code>MessageEvent</code> interface</a></ol></ol><h2 id=comms><span class=secno>9</span> Communication<a href=#comms class=self-link></a></h2>
+
+  <h3 id=the-messageevent-interface><span class=secno>9.1</span> The <code id=the-messageevent-interface:messageevent><a href=#messageevent>MessageEvent</a></code> interface<a href=#the-messageevent-interface class=self-link></a></h3>
+
+  <p>Messages in <a id=the-messageevent-interface:server-sent-events href=server-sent-events.html#server-sent-events>server-sent events</a>, <a id=the-messageevent-interface:network href=web-sockets.html#network>Web sockets</a>, <a id=the-messageevent-interface:web-messaging href=web-messaging.html#web-messaging>cross-document
+  messaging</a>, <a id=the-messageevent-interface:channel-messaging href=web-messaging.html#channel-messaging>channel messaging</a>, and <a id=the-messageevent-interface:broadcasting-to-other-browsing-contexts href=web-messaging.html#broadcasting-to-other-browsing-contexts>broadcast channels</a> use the
+  <code id=the-messageevent-interface:messageevent-2><a href=#messageevent>MessageEvent</a></code> interface for their <code id=the-messageevent-interface:event-message><a href=indices.html#event-message>message</a></code>
+  events:</p>
+
+  <pre class=idl>[Constructor(DOMString type, optional <a href=#messageeventinit id=the-messageevent-interface:messageeventinit>MessageEventInit</a> eventInitDict), Exposed=(Window,Worker,AudioWorklet)]
+interface <dfn id=messageevent>MessageEvent</dfn> : <a id=the-messageevent-interface:event href=https://dom.spec.whatwg.org/#interface-event data-x-internal=event>Event</a> {
+  readonly attribute any <a href=#dom-messageevent-data id=the-messageevent-interface:dom-messageevent-data>data</a>;
+  readonly attribute USVString <a href=#dom-messageevent-origin id=the-messageevent-interface:dom-messageevent-origin>origin</a>;
+  readonly attribute DOMString <a href=#dom-messageevent-lasteventid id=the-messageevent-interface:dom-messageevent-lasteventid>lastEventId</a>;
+  readonly attribute <a href=#messageeventsource id=the-messageevent-interface:messageeventsource>MessageEventSource</a>? <a href=#dom-messageevent-source id=the-messageevent-interface:dom-messageevent-source>source</a>;
+  readonly attribute FrozenArray&lt;<a id=the-messageevent-interface:messageport href=web-messaging.html#messageport>MessagePort</a>> <a href=#dom-messageevent-ports id=the-messageevent-interface:dom-messageevent-ports>ports</a>;
+
+  void <a href=#dom-messageevent-initmessageevent id=the-messageevent-interface:dom-messageevent-initmessageevent>initMessageEvent</a>(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional any data = null, optional USVString origin = "", optional DOMString lastEventId = "", optional <a href=#messageeventsource id=the-messageevent-interface:messageeventsource-2>MessageEventSource</a>? source = null, optional sequence&lt;<a id=the-messageevent-interface:messageport-2 href=web-messaging.html#messageport>MessagePort</a>> ports = []);
+};
+
+dictionary <dfn id=messageeventinit>MessageEventInit</dfn> : <a id=the-messageevent-interface:eventinit href=https://dom.spec.whatwg.org/#dictdef-eventinit data-x-internal=eventinit>EventInit</a> {
+  any data = null;
+  USVString origin = "";
+  DOMString lastEventId = "";
+  <a href=#messageeventsource id=the-messageevent-interface:messageeventsource-3>MessageEventSource</a>? source = null;
+  sequence&lt;<a id=the-messageevent-interface:messageport-3 href=web-messaging.html#messageport>MessagePort</a>> ports = [];
+};
+
+typedef (<a id=the-messageevent-interface:windowproxy href=window-object.html#windowproxy>WindowProxy</a> or <a id=the-messageevent-interface:messageport-4 href=web-messaging.html#messageport>MessagePort</a> or <a id=the-messageevent-interface:serviceworker href=https://w3c.github.io/ServiceWorker/#serviceworker data-x-internal=serviceworker>ServiceWorker</a>) <dfn id=messageeventsource>MessageEventSource</dfn>;</pre>
+
+  <dl class=domintro><dt><var>event</var> . <code id=dom-messageevent-data-dev><a href=#dom-messageevent-data>data</a></code><dd>
+
+    <p>Returns the data of the message.</p>
+
+   <dt><var>event</var> . <code id=dom-messageevent-origin-dev><a href=#dom-messageevent-origin>origin</a></code><dd>
+
+    <p>Returns the origin of the message, for <a id=the-messageevent-interface:server-sent-events-2 href=server-sent-events.html#server-sent-events>server-sent events</a> and
+    <a id=the-messageevent-interface:web-messaging-2 href=web-messaging.html#web-messaging>cross-document messaging</a>.</p>
+
+   <dt><var>event</var> . <code id=dom-messageevent-lasteventid-dev><a href=#dom-messageevent-lasteventid>lastEventId</a></code><dd>
+
+    <p>Returns the <a href=server-sent-events.html#concept-event-stream-last-event-id id=the-messageevent-interface:concept-event-stream-last-event-id>last event ID string</a>, for
+    <a id=the-messageevent-interface:server-sent-events-3 href=server-sent-events.html#server-sent-events>server-sent events</a>.</p>
+
+   <dt><var>event</var> . <code id=dom-messageevent-source-dev><a href=#dom-messageevent-source>source</a></code><dd>
+
+    <p>Returns the <code id=the-messageevent-interface:windowproxy-2><a href=window-object.html#windowproxy>WindowProxy</a></code> of the source window, for <a id=the-messageevent-interface:web-messaging-3 href=web-messaging.html#web-messaging>cross-document
+    messaging</a>, and the <code id=the-messageevent-interface:messageport-5><a href=web-messaging.html#messageport>MessagePort</a></code> being attached, in the <code id=the-messageevent-interface:event-workerglobalscope-connect><a href=indices.html#event-workerglobalscope-connect>connect</a></code> event fired at
+    <code id=the-messageevent-interface:sharedworkerglobalscope><a href=workers.html#sharedworkerglobalscope>SharedWorkerGlobalScope</a></code> objects.</p>
+
+   <dt><var>event</var> . <code id=dom-messageevent-ports-dev><a href=#dom-messageevent-ports>ports</a></code><dd>
+
+    <p>Returns the <code id=the-messageevent-interface:messageport-6><a href=web-messaging.html#messageport>MessagePort</a></code> array sent with the message, for <a id=the-messageevent-interface:web-messaging-4 href=web-messaging.html#web-messaging>cross-document
+    messaging</a> and <a id=the-messageevent-interface:channel-messaging-2 href=web-messaging.html#channel-messaging>channel messaging</a>.</p>
+
+   </dl>
+
+  
+
+  <p>The <dfn id=dom-messageevent-data><code>data</code></dfn> attribute must return the value
+  it was initialized to. It represents the message being sent.</p>
+
+  <p>The <dfn id=dom-messageevent-origin><code>origin</code></dfn> attribute must return the
+  value it was initialized to. It represents, in <a id=the-messageevent-interface:server-sent-events-4 href=server-sent-events.html#server-sent-events>server-sent events</a> and
+  <a id=the-messageevent-interface:web-messaging-5 href=web-messaging.html#web-messaging>cross-document messaging</a>, the <a id=the-messageevent-interface:concept-origin href=origin.html#concept-origin>origin</a> of the document that sent the
+  message (typically the scheme, hostname, and port of the document, but not its path or <a href=https://url.spec.whatwg.org/#concept-url-fragment id=the-messageevent-interface:concept-url-fragment data-x-internal=concept-url-fragment>fragment</a>).</p>
+
+  <p>The <dfn id=dom-messageevent-lasteventid><code>lastEventId</code></dfn> attribute must
+  return the value it was initialized to. It represents, in <a id=the-messageevent-interface:server-sent-events-5 href=server-sent-events.html#server-sent-events>server-sent events</a>, the
+  <a href=server-sent-events.html#concept-event-stream-last-event-id id=the-messageevent-interface:concept-event-stream-last-event-id-2>last event ID string</a> of the event
+  source.</p>
+
+  <p>The <dfn id=dom-messageevent-source><code>source</code></dfn> attribute must return the
+  value it was initialized to. It represents, in <a id=the-messageevent-interface:web-messaging-6 href=web-messaging.html#web-messaging>cross-document messaging</a>, the
+  <code id=the-messageevent-interface:windowproxy-3><a href=window-object.html#windowproxy>WindowProxy</a></code> of the <a id=the-messageevent-interface:browsing-context href=browsers.html#browsing-context>browsing context</a> of the <code id=the-messageevent-interface:window><a href=window-object.html#window>Window</a></code> object
+  from which the message came; and in the <code id=the-messageevent-interface:event-workerglobalscope-connect-2><a href=indices.html#event-workerglobalscope-connect>connect</a></code> events used by <a href=workers.html#sharedworkerglobalscope id=the-messageevent-interface:sharedworkerglobalscope-2>shared workers</a>, the newly connecting
+  <code id=the-messageevent-interface:messageport-7><a href=web-messaging.html#messageport>MessagePort</a></code>.</p>
+
+  <p>The <dfn id=dom-messageevent-ports><code>ports</code></dfn> attribute must return the
+  value it was initialized to. It represents, in <a id=the-messageevent-interface:web-messaging-7 href=web-messaging.html#web-messaging>cross-document messaging</a> and
+  <a id=the-messageevent-interface:channel-messaging-3 href=web-messaging.html#channel-messaging>channel messaging</a>, the <code id=the-messageevent-interface:messageport-8><a href=web-messaging.html#messageport>MessagePort</a></code> array being sent.</p>
+
+  
+  <p>The <dfn id=dom-messageevent-initmessageevent><code>initMessageEvent()</code></dfn>
+  method must initialize the event in a manner analogous to the similarly-named <code id=the-messageevent-interface:dom-event-initevent><a data-x-internal=dom-event-initevent href=https://dom.spec.whatwg.org/#dom-event-initevent>initEvent()</a></code> method. <a href=references.html#refsDOM>[DOM]</a></p>
+
+  
+
+  <p class=note>Various APIs (e.g., <code id=the-messageevent-interface:websocket><a href=web-sockets.html#websocket>WebSocket</a></code>, <code id=the-messageevent-interface:eventsource><a href=server-sent-events.html#eventsource>EventSource</a></code>) use the
+  <code id=the-messageevent-interface:messageevent-3><a href=#messageevent>MessageEvent</a></code> interface for their <code id=the-messageevent-interface:event-message-2><a href=indices.html#event-message>message</a></code> event
+  without using the <code id=the-messageevent-interface:messageport-9><a href=web-messaging.html#messageport>MessagePort</a></code> API.</p>
+
+
+  <nav><a href=imagebitmap-and-animations.html>← 8.8 Images</a> — <a href=./>Table of Contents</a> — <a href=server-sent-events.html>9.2 Server-sent events →</a></nav>
diff --git a/conformance-checkers/html/elements/style/model-isvalid.html b/conformance-checkers/html/elements/style/model-isvalid.html
index f0dd1c2..e55c3e9 100644
--- a/conformance-checkers/html/elements/style/model-isvalid.html
+++ b/conformance-checkers/html/elements/style/model-isvalid.html
@@ -4,15 +4,12 @@
   <style>
 <!--
 
-Something or other
+.hasFocus { border: 2px solid red; }
 
 -->
   </style>
   <meta charset=utf-8>
   <title>&lt;STYLE&gt;s</title>
-  <style type="application/vnd.nonsense" title="My Style">
-    Something or other
-  </style>
 </head>
 <body>
 
diff --git a/conformance-checkers/html/elements/style/type-novalid.html b/conformance-checkers/html/elements/style/type-novalid.html
new file mode 100644
index 0000000..f977b17
--- /dev/null
+++ b/conformance-checkers/html/elements/style/type-novalid.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset=utf-8>
+  <title>&lt;STYLE&gt;s</title>
+  <style type="application/vnd.nonsense" title="My Style">
+    Something or other
+  </style>
+</head>
+<body>
+
+</body>
+</html>
diff --git a/conformance-checkers/html/elements/track/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/track/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 13a633d..0000000
--- a/conformance-checkers/html/elements/track/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<video><track src="javascript:example.com/"></video>
diff --git a/conformance-checkers/html/elements/video/poster/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/video/poster/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index af5c83d..0000000
--- a/conformance-checkers/html/elements/video/poster/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid poster: scheme-javascript-no-slash-malformed</title>
-<video poster="javascript:example.com/"></video>
diff --git a/conformance-checkers/html/elements/video/src/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/elements/video/src/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index adc7a87..0000000
--- a/conformance-checkers/html/elements/video/src/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid src: scheme-javascript-no-slash-malformed</title>
-<video src="javascript:example.com/"></video>
diff --git a/conformance-checkers/html/microdata/itemid/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/microdata/itemid/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 3bf1c65..0000000
--- a/conformance-checkers/html/microdata/itemid/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid itemid: scheme-javascript-no-slash-malformed</title>
-<div itemid="javascript:example.com/" itemtype="http://foo" itemscope></div>
diff --git a/conformance-checkers/html/microdata/itemtype/scheme-javascript-no-slash-malformed-novalid.html b/conformance-checkers/html/microdata/itemtype/scheme-javascript-no-slash-malformed-novalid.html
deleted file mode 100644
index 5110c6d..0000000
--- a/conformance-checkers/html/microdata/itemtype/scheme-javascript-no-slash-malformed-novalid.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>invalid itemtype: scheme-javascript-no-slash-malformed</title>
-<div itemtype="javascript:example.com/" itemscope></div>
diff --git a/conformance-checkers/index.html b/conformance-checkers/index.html
index 2dadc40..fbf944a 100644
--- a/conformance-checkers/index.html
+++ b/conformance-checkers/index.html
@@ -14,7 +14,7 @@
 
 <p>Curious committers should see the makefile.
 
-<style type="text/css">
+<style>
 html {
   background: #DDE5D9 url(data:image/gif;base64,R0lGODlhBAAEAIAAANra2v///yH5BAAAAAAALAAAAAAEAAQAAAIFTGB4xlcAOw==) repeat 0 0;
   font-family: "Lucida Sans Unicode", "Lucida Sans", verdana, arial, helvetica;
diff --git a/conformance-checkers/messages.json b/conformance-checkers/messages.json
index 7c363b3..1bdb90d 100644
--- a/conformance-checkers/messages.json
+++ b/conformance-checkers/messages.json
@@ -144,7 +144,6 @@
     "html/elements/a/href/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201chref\u201d on element \u201ca\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/a/href/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201chref\u201d on element \u201ca\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/a/href/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201chref\u201d on element \u201ca\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/a/href/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201chref\u201d on element \u201ca\u201d: Bad URL: syntax error",
     "html/elements/a/href/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201ca\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/a/href/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201ca\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/a/href/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201chref\u201d on element \u201ca\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -214,7 +213,6 @@
     "html/elements/area/href/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201chref\u201d on element \u201carea\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/area/href/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201chref\u201d on element \u201carea\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/area/href/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201chref\u201d on element \u201carea\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/area/href/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201chref\u201d on element \u201carea\u201d: Bad URL: syntax error",
     "html/elements/area/href/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201carea\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/area/href/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201carea\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/area/href/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201chref\u201d on element \u201carea\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -284,7 +282,6 @@
     "html/elements/audio/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201caudio\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/audio/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201caudio\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/audio/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201caudio\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/audio/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201caudio\u201d: Bad URL: syntax error",
     "html/elements/audio/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201caudio\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/audio/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201caudio\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/audio/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201caudio\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -350,7 +347,6 @@
     "html/elements/base/href/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201chref\u201d on element \u201cbase\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/base/href/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201chref\u201d on element \u201cbase\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/base/href/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201chref\u201d on element \u201cbase\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/base/href/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201chref\u201d on element \u201cbase\u201d: Bad URL: syntax error",
     "html/elements/base/href/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201cbase\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/base/href/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201cbase\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/base/href/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201chref\u201d on element \u201cbase\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -416,7 +412,6 @@
     "html/elements/blockquote/cite/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cblockquote\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/blockquote/cite/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201ccite\u201d on element \u201cblockquote\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/blockquote/cite/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cblockquote\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/blockquote/cite/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201ccite\u201d on element \u201cblockquote\u201d: Bad URL: syntax error",
     "html/elements/blockquote/cite/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cblockquote\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/blockquote/cite/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cblockquote\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/blockquote/cite/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201ccite\u201d on element \u201cblockquote\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -485,7 +480,6 @@
     "html/elements/button/formaction/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201cformaction\u201d on element \u201cbutton\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/button/formaction/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201cformaction\u201d on element \u201cbutton\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/button/formaction/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201cformaction\u201d on element \u201cbutton\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/button/formaction/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201cformaction\u201d on element \u201cbutton\u201d: Bad URL: syntax error",
     "html/elements/button/formaction/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cformaction\u201d on element \u201cbutton\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/button/formaction/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cformaction\u201d on element \u201cbutton\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/button/formaction/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201cformaction\u201d on element \u201cbutton\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -553,7 +547,6 @@
     "html/elements/del/cite/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cdel\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/del/cite/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201ccite\u201d on element \u201cdel\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/del/cite/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cdel\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/del/cite/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201ccite\u201d on element \u201cdel\u201d: Bad URL: syntax error",
     "html/elements/del/cite/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cdel\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/del/cite/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cdel\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/del/cite/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201ccite\u201d on element \u201cdel\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -749,7 +742,6 @@
     "html/elements/embed/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cembed\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/embed/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201cembed\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/embed/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cembed\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/embed/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201cembed\u201d: Bad URL: syntax error",
     "html/elements/embed/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cembed\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/embed/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cembed\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/embed/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201cembed\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -820,7 +812,6 @@
     "html/elements/form/action/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201caction\u201d on element \u201cform\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/form/action/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201caction\u201d on element \u201cform\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/form/action/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201caction\u201d on element \u201cform\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/form/action/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201caction\u201d on element \u201cform\u201d: Bad URL: syntax error",
     "html/elements/form/action/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201caction\u201d on element \u201cform\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/form/action/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201caction\u201d on element \u201cform\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/form/action/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201caction\u201d on element \u201cform\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -898,7 +889,6 @@
     "html/elements/iframe/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201ciframe\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/iframe/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201ciframe\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/iframe/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201ciframe\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/iframe/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201ciframe\u201d: Bad URL: syntax error",
     "html/elements/iframe/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201ciframe\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/iframe/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201ciframe\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/iframe/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201ciframe\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -966,7 +956,6 @@
     "html/elements/img/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cimg\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/img/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201cimg\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/img/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cimg\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/img/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201cimg\u201d: Bad URL: syntax error",
     "html/elements/img/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cimg\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/img/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cimg\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/img/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201cimg\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -979,8 +968,8 @@
     "html/elements/img/usemap-bad-value-novalid.html": "Bad value \u201c#\u201d for attribute \u201cusemap\u201d on element \u201cimg\u201d: Bad hash-name reference: A hash-name reference must have at least one character after \u201c#\u201d.",
     "html/elements/img/width-height-negative-novalid.html": "Bad value \u201c-1\u201d for attribute \u201cwidth\u201d on element \u201cimg\u201d: Bad non-negative integer: Expected a digit but saw \u201c-\u201d instead.",
     "html/elements/input/list-novalid.html": "The \u201clist\u201d attribute of the \u201cinput\u201d element must refer to a \u201cdatalist\u201d element.",
-    "html/elements/input/pattern-asterisk-novalid.html": "Bad value \u201c*\u201d for attribute \u201cpattern\u201d on element \u201cinput\u201d: Bad pattern: Invalid quantifier *",
-    "html/elements/input/pattern-paren-novalid.html": "Bad value \u201c(\u201d for attribute \u201cpattern\u201d on element \u201cinput\u201d: Bad pattern: Unterminated parenthetical ",
+    "html/elements/input/pattern-asterisk-novalid.html": "Bad value \u201c*\u201d for attribute \u201cpattern\u201d on element \u201cinput\u201d: Bad pattern: Dangling meta character '*' near index 0",
+    "html/elements/input/pattern-paren-novalid.html": "Bad value \u201c(\u201d for attribute \u201cpattern\u201d on element \u201cinput\u201d: Bad pattern: Unclosed group near index 1",
     "html/elements/input/type-image-formaction-empty-novalid.html": "Bad value \u201c\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Must be non-empty.",
     "html/elements/input/type-image-formaction-whitespace-only-novalid.html": "Bad value \u201c\t \n\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Must be non-empty.",
     "html/elements/input/type-image-formaction/fragment-backslash-novalid.html": "Bad value \u201c#\\\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Illegal character in fragment: \u201c\\\u201d is not allowed.",
@@ -1038,7 +1027,6 @@
     "html/elements/input/type-image-formaction/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/input/type-image-formaction/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/input/type-image-formaction/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/input/type-image-formaction/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: syntax error",
     "html/elements/input/type-image-formaction/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/input/type-image-formaction/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/input/type-image-formaction/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1105,7 +1093,6 @@
     "html/elements/input/type-image-src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/input/type-image-src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/input/type-image-src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/input/type-image-src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201cinput\u201d: Bad URL: syntax error",
     "html/elements/input/type-image-src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/input/type-image-src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/input/type-image-src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1172,7 +1159,6 @@
     "html/elements/input/type-submit-formaction/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/input/type-submit-formaction/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/input/type-submit-formaction/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/input/type-submit-formaction/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: syntax error",
     "html/elements/input/type-submit-formaction/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/input/type-submit-formaction/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/input/type-submit-formaction/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201cformaction\u201d on element \u201cinput\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1246,7 +1232,6 @@
     "html/elements/input/type-url-value/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201cvalue\u201d on element \u201cinput\u201d: Bad absolute URL: Expected a slash (\"/\").",
     "html/elements/input/type-url-value/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201cvalue\u201d on element \u201cinput\u201d: Bad absolute URL: Expected a slash (\"/\").",
     "html/elements/input/type-url-value/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201cvalue\u201d on element \u201cinput\u201d: Bad absolute URL: Expected a slash (\"/\").",
-    "html/elements/input/type-url-value/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201cvalue\u201d on element \u201cinput\u201d: Bad absolute URL: syntax error",
     "html/elements/input/type-url-value/scheme-schemeless-relative-novalid.html": "Bad value \u201c//foo/bar\u201d for attribute \u201cvalue\u201d on element \u201cinput\u201d: Bad absolute URL: The string \u201c//foo/bar\u201d is not an absolute URL.",
     "html/elements/input/type-url-value/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cvalue\u201d on element \u201cinput\u201d: Bad absolute URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/input/type-url-value/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cvalue\u201d on element \u201cinput\u201d: Bad absolute URL: Illegal character in scheme data: line break is not allowed.",
@@ -1312,7 +1297,6 @@
     "html/elements/ins/cite/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cins\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/ins/cite/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201ccite\u201d on element \u201cins\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/ins/cite/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cins\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/ins/cite/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201ccite\u201d on element \u201cins\u201d: Bad URL: syntax error",
     "html/elements/ins/cite/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cins\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/ins/cite/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cins\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/ins/cite/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201ccite\u201d on element \u201cins\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1485,7 +1469,6 @@
     "html/elements/link/href/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201chref\u201d on element \u201clink\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/link/href/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201chref\u201d on element \u201clink\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/link/href/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201chref\u201d on element \u201clink\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/link/href/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201chref\u201d on element \u201clink\u201d: Bad URL: syntax error",
     "html/elements/link/href/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201clink\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/link/href/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201chref\u201d on element \u201clink\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/link/href/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201chref\u201d on element \u201clink\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1554,7 +1537,6 @@
     "html/elements/object/data/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201cdata\u201d on element \u201cobject\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/object/data/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201cdata\u201d on element \u201cobject\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/object/data/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201cdata\u201d on element \u201cobject\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/object/data/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201cdata\u201d on element \u201cobject\u201d: Bad URL: syntax error",
     "html/elements/object/data/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cdata\u201d on element \u201cobject\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/object/data/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cdata\u201d on element \u201cobject\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/object/data/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201cdata\u201d on element \u201cobject\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1822,7 +1804,6 @@
     "html/elements/q/cite/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cq\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/q/cite/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201ccite\u201d on element \u201cq\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/q/cite/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201ccite\u201d on element \u201cq\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/q/cite/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201ccite\u201d on element \u201cq\u201d: Bad URL: syntax error",
     "html/elements/q/cite/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cq\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/q/cite/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201ccite\u201d on element \u201cq\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/q/cite/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201ccite\u201d on element \u201cq\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1894,7 +1875,6 @@
     "html/elements/script/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cscript\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/script/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201cscript\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/script/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cscript\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/script/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201cscript\u201d: Bad URL: syntax error",
     "html/elements/script/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cscript\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/script/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cscript\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/script/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201cscript\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1962,7 +1942,6 @@
     "html/elements/source/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201csource\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/source/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201csource\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/source/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201csource\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/source/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201csource\u201d: Bad URL: syntax error",
     "html/elements/source/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201csource\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/source/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201csource\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/source/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201csource\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -1980,6 +1959,7 @@
     "html/elements/style/scoped-model-novalid.html": "Element \u201cstyle\u201d not allowed as child of element \u201cdiv\u201d in this context. (Suppressing further errors from this subtree.)",
     "html/elements/style/scoped-multiple-adjacent-novalid.html": "Element \u201cstyle\u201d not allowed as child of element \u201cdiv\u201d in this context. (Suppressing further errors from this subtree.)",
     "html/elements/style/scoped-novalid.html": "Element \u201cstyle\u201d not allowed as child of element \u201cdiv\u201d in this context. (Suppressing further errors from this subtree.)",
+    "html/elements/style/type-novalid.html": " The only allowed value for the \u201ctype\u201d attribute for the \u201cstyle\u201d element is \u201ctext/css\u201d (with no parameters). (But the attribute is not needed and should be omitted altogether.)",
     "html/elements/sub/model-novalid.html": "End tag \u201cp\u201d implied, but there were open elements.",
     "html/elements/sup/model-novalid.html": "End tag \u201cp\u201d implied, but there were open elements.",
     "html/elements/table/integrity/Alexis_of_Russia-novalid.html": "Bad value \u201ccopyright\u201d for attribute \u201crel\u201d on element \u201clink\u201d: Bad list of link-type keywords: The keyword \u201ccopyright\u201d for the \u201crel\u201d attribute should not be used. Consider using \u201clicense\u201d instead.",
@@ -2048,7 +2028,6 @@
     "html/elements/track/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201ctrack\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/track/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201ctrack\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/track/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201ctrack\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/track/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201ctrack\u201d: Bad URL: syntax error",
     "html/elements/track/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201ctrack\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/track/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201ctrack\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/track/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201ctrack\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -2118,7 +2097,6 @@
     "html/elements/video/poster/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201cposter\u201d on element \u201cvideo\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/video/poster/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201cposter\u201d on element \u201cvideo\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/video/poster/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201cposter\u201d on element \u201cvideo\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/video/poster/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201cposter\u201d on element \u201cvideo\u201d: Bad URL: syntax error",
     "html/elements/video/poster/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cposter\u201d on element \u201cvideo\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/video/poster/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201cposter\u201d on element \u201cvideo\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/video/poster/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201cposter\u201d on element \u201cvideo\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -2183,7 +2161,6 @@
     "html/elements/video/src/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cvideo\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/video/src/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201csrc\u201d on element \u201cvideo\u201d: Bad URL: Expected a slash (\"/\").",
     "html/elements/video/src/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201csrc\u201d on element \u201cvideo\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/elements/video/src/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201csrc\u201d on element \u201cvideo\u201d: Bad URL: syntax error",
     "html/elements/video/src/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cvideo\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/video/src/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201csrc\u201d on element \u201cvideo\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/elements/video/src/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201csrc\u201d on element \u201cvideo\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -2271,7 +2248,6 @@
     "html/microdata/itemid/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201citemid\u201d on element \u201cdiv\u201d: Bad URL: Expected a slash (\"/\").",
     "html/microdata/itemid/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201citemid\u201d on element \u201cdiv\u201d: Bad URL: Expected a slash (\"/\").",
     "html/microdata/itemid/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201citemid\u201d on element \u201cdiv\u201d: Bad URL: Expected a slash (\"/\").",
-    "html/microdata/itemid/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201citemid\u201d on element \u201cdiv\u201d: Bad URL: syntax error",
     "html/microdata/itemid/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201citemid\u201d on element \u201cdiv\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/microdata/itemid/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201citemid\u201d on element \u201cdiv\u201d: Bad URL: Illegal character in scheme data: line break is not allowed.",
     "html/microdata/itemid/scheme-trailing-space-novalid.html": "Bad value \u201ca: foo.com\u201d for attribute \u201citemid\u201d on element \u201cdiv\u201d: Bad URL: Illegal character in scheme data: space is not allowed.",
@@ -2343,7 +2319,6 @@
     "html/microdata/itemtype/scheme-http-single-slash-novalid.html": "Bad value \u201chttp:/example.com/\u201d for attribute \u201citemtype\u201d on element \u201cdiv\u201d: Bad absolute URL: Expected a slash (\"/\").",
     "html/microdata/itemtype/scheme-https-no-slash-novalid.html": "Bad value \u201chttps:example.com/\u201d for attribute \u201citemtype\u201d on element \u201cdiv\u201d: Bad absolute URL: Expected a slash (\"/\").",
     "html/microdata/itemtype/scheme-https-single-slash-novalid.html": "Bad value \u201chttps:/example.com/\u201d for attribute \u201citemtype\u201d on element \u201cdiv\u201d: Bad absolute URL: Expected a slash (\"/\").",
-    "html/microdata/itemtype/scheme-javascript-no-slash-malformed-novalid.html": "Bad value \u201cjavascript:example.com/\u201d for attribute \u201citemtype\u201d on element \u201cdiv\u201d: Bad absolute URL: syntax error",
     "html/microdata/itemtype/scheme-schemeless-relative-novalid.html": "Bad value \u201c//foo/bar\u201d for attribute \u201citemtype\u201d on element \u201cdiv\u201d: Bad absolute URL: The string \u201c//foo/bar\u201d is not an absolute URL.",
     "html/microdata/itemtype/scheme-trailing-cr-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201citemtype\u201d on element \u201cdiv\u201d: Bad absolute URL: The string \u201cfoo.com\u201d is not an absolute URL.",
     "html/microdata/itemtype/scheme-trailing-newline-novalid.html": "Bad value \u201ca:\nfoo.com\u201d for attribute \u201citemtype\u201d on element \u201cdiv\u201d: Bad absolute URL: The string \u201cfoo.com\u201d is not an absolute URL.",
diff --git a/conformance-checkers/tools/build-svg-tests.py b/conformance-checkers/tools/build-svg-tests.py
index 3986a8d..dd3aa38 100644
--- a/conformance-checkers/tools/build-svg-tests.py
+++ b/conformance-checkers/tools/build-svg-tests.py
@@ -20,6 +20,8 @@
 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
+from __future__ import print_function
+
 """build_svg_tests.py.
 
 This script builds a set of SVG-in-HTML test files for the Nu Html Checker
@@ -218,7 +220,7 @@
     if status == "Valid":
         return True
 
-    print "    ('%s', 'DTD %s')," % (filename, status)
+    print("    ('%s', 'DTD %s')," % (filename, status))
     return False
 
 
@@ -234,11 +236,11 @@
     try:
         opts, args = getopt.getopt(sys.argv[1:],"",["svgdir=","outdir="])
     except getopt.GetoptError:
-        print 'build-svg-tests.py --svgdir <indir> --outdir <outdir>'
+        print('build-svg-tests.py --svgdir <indir> --outdir <outdir>')
         sys.exit(2)
 
     for opt, arg in opts:
-        print opt, arg
+        print(opt, arg)
         if opt in ("-s", "--svgdir"):
             svgdirectory = arg
         elif opt in ("-o", "--outdir"):
diff --git a/conformance-checkers/tools/url.py b/conformance-checkers/tools/url.py
index 1f59c02..df31eab 100644
--- a/conformance-checkers/tools/url.py
+++ b/conformance-checkers/tools/url.py
@@ -17,7 +17,6 @@
     "scheme-data-single-slash": "data:/example.com/",
     "scheme-ftp-no-slash": "ftp:example.com/",
     "scheme-https-no-slash": "https:example.com/",
-    "scheme-javascript-no-slash-malformed": "javascript:example.com/",
     "userinfo-password-bad-chars": "http://&a:foo(b]c@d:2/",
     "userinfo-username-contains-at-sign": "http://::@c@d:2",
     "userinfo-backslash": "http://a\\b:c\\d@foo.com",
diff --git a/console/console-count-label-conversion.any.js b/console/console-count-label-conversion.any.js
deleted file mode 100644
index ee37436..0000000
--- a/console/console-count-label-conversion.any.js
+++ /dev/null
@@ -1,24 +0,0 @@
-"use strict";
-// https://console.spec.whatwg.org/#count
-
-test(() => {
-  let countLabelToStringCalled = false;
-
-  console.count({
-    toString() {
-      countLabelToStringCalled = true;
-    }
-  });
-
-  assert_true(countLabelToStringCalled, "toString() must be called on count()'s label when label is an object");
-}, "console.count()'s label gets converted to string via label.toString() when label is an object");
-
-test(() => {
-  assert_throws({name: "Error"}, () => {
-    console.count({
-      toString() {
-        throw new Error("conversion error");
-      }
-    });
-  }, "count() must re-throw any exceptions thrown by label.toString() conversion");
-}, "console.count() throws exceptions generated by erroneous label.toString() conversion");
diff --git a/console/console-countReset-logging-manual.html b/console/console-countReset-logging-manual.html
new file mode 100644
index 0000000..e2ed2dd
--- /dev/null
+++ b/console/console-countReset-logging-manual.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Console Count Reset - Logging Manual Test</title>
+<meta name="author" title="Dominic Farolino" href="mailto:domfarolino@gmail.com">
+<meta name="assert" content="Console countReset method">
+<link rel="help" href="https://console.spec.whatwg.org/#countReset">
+</head>
+<body>
+<p>Open the console inside the developer tools. It should contain lines whose contents are:</p>
+<p><code>default: 1</code></p>
+<p><code>default: 1</code></p>
+<p><code>default: 1</code></p>
+<p><code>default: 1</code></p>
+<p><code>default: 1</code></p>
+<p><code>default: 1</code></p>
+<p><code>default: 1</code></p>
+<p><code>default: 1</code></p>
+<p><code>a label: 1</code></p>
+<p><code>a label: 1</code></p>
+<p style="color:grey;">[some warning message indicating that a count for label "b" does not exist]</p>
+
+<script>
+console.count();
+console.countReset();
+console.count();
+
+console.count(undefined);
+console.countReset(undefined);
+console.count(undefined);
+
+console.count("default");
+console.countReset("default");
+console.count("default");
+
+console.count({toString() {return "default"}});
+console.countReset({toString() {return "default"}});
+console.count({toString() {return "default"}});
+
+console.count("a label");
+console.countReset();
+console.count("a label");
+
+console.countReset("b"); // should produce a warning
+</script>
+</body>
+</html>
diff --git a/console/console-counting-label-conversion.any.js b/console/console-counting-label-conversion.any.js
new file mode 100644
index 0000000..bbc77f0
--- /dev/null
+++ b/console/console-counting-label-conversion.any.js
@@ -0,0 +1,55 @@
+"use strict";
+// https://console.spec.whatwg.org/#count
+// https://console.spec.whatwg.org/#countreset
+
+// TODO(domfarolino): make this a link to
+// https://console.spec.whatwg/org/#counting pending
+// the resolution of https://github.com/whatwg/console/issues/135
+
+// TODO(domfarolino): DRY up the label conversion tests for count/countReset/time/timeEnd
+// by probably making a helper function that passes in the console method to perform the
+// conversion with so we're not duplicating everything
+
+test(() => {
+  let countLabelToStringCalled = false;
+
+  console.count({
+    toString() {
+      countLabelToStringCalled = true;
+    }
+  });
+
+  assert_true(countLabelToStringCalled, "toString() must be called on count()'s label when label is an object");
+}, "console.count()'s label gets converted to string via label.toString() when label is an object");
+
+test(() => {
+  assert_throws({name: "Error"}, () => {
+    console.count({
+      toString() {
+        throw new Error("conversion error");
+      }
+    });
+  }, "count() must re-throw any exceptions thrown by label.toString() conversion");
+}, "console.count() throws exceptions generated by erroneous label.toString() conversion");
+
+test(() => {
+  let countLabelToStringCalled = false;
+
+  console.countReset({
+    toString() {
+      countLabelToStringCalled = true;
+    }
+  });
+
+  assert_true(countLabelToStringCalled, "toString() must be called on countReset()'s label when label is an object");
+}, "console.countReset()'s label gets converted to string via label.toString() when label is an object");
+
+test(() => {
+  assert_throws({name: "Error"}, () => {
+    console.countReset({
+      toString() {
+        throw new Error("conversion error");
+      }
+    });
+  }, "countReset() must re-throw any exceptions thrown by label.toString() conversion");
+}, "console.countReset() throws exceptions generated by erroneous label.toString() conversion");
diff --git a/console/console-format-specifier-symbol-manual.html b/console/console-format-specifier-symbol-manual.html
new file mode 100644
index 0000000..47a981f
--- /dev/null
+++ b/console/console-format-specifier-symbol-manual.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Console Format Specifiers on Symbols</title>
+<meta name="author" title="Dominic Farolino" href="mailto:domfarolino@gmail.com">
+<meta name="assert" content="Console format specifiers on Symbols">
+<link rel="help" href="https://console.spec.whatwg.org/#formatter">
+</head>
+<body>
+<p>Open the console inside the developer tools. It should contain five lines, each of which are:</p>
+<p><code>Symbol(description)</code></p>
+
+<script>
+console.log("%s", Symbol.for("description"));
+console.dirxml("%s", Symbol.for("description"));
+console.trace("%s", Symbol.for("description"));
+console.group("%s", Symbol.for("description"));
+console.groupCollapsed("%s", Symbol.for("description"));
+</script>
+</body>
+</html>
diff --git a/console/console-tests-historical.any.js b/console/console-tests-historical.any.js
new file mode 100644
index 0000000..4c4d4c2
--- /dev/null
+++ b/console/console-tests-historical.any.js
@@ -0,0 +1,19 @@
+/**
+ * These tests assert the non-existence of certain
+ * legacy Console methods that are not included in
+ * the specification: http://console.spec.whatwg.org/
+ */
+
+"use strict";
+
+test(() => {
+  assert_equals(console.timeline, undefined, "console.timeline should be undefined");
+}, "'timeline' function should not exist on the console object");
+
+test(() => {
+  assert_equals(console.timelineEnd, undefined, "console.timelineEnd should be undefined");
+}, "'timelineEnd' function should not exist on the console object");
+
+test(() => {
+  assert_equals(console.markTimeline, undefined, "console.markTimeline should be undefined");
+}, "'markTimeline' function should not exist on the console object");
diff --git a/console/console-timeline-timelineEnd-historical.any.js b/console/console-timeline-timelineEnd-historical.any.js
deleted file mode 100644
index 038c715..0000000
--- a/console/console-timeline-timelineEnd-historical.any.js
+++ /dev/null
@@ -1,9 +0,0 @@
-"use strict";
-
-test(() => {
-  assert_equals(console.timeline, undefined, "console.timeline should be undefined");
-}, "'timeline' function should not exist on the console object");
-
-test(() => {
-  assert_equals(console.timelineEnd, undefined, "console.timelineEnd should be undefined");
-}, "'timelineEnd' function should not exist on the console object");
\ No newline at end of file
diff --git a/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html b/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html
index 6f59016..6a0f0a1 100644
--- a/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html
+++ b/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html
@@ -5,7 +5,7 @@
   <script src='/resources/testharness.js'></script>
   <script src='/resources/testharnessreport.js'></script>
   <!-- if base is used for resolving the URL to report to then we will not get a report -->
-  <base href="http://nonexistent-origin.web-platform.test">
+  <base href="http://nonexistent.web-platform.test">
 </head>
 <body>
   <script>
diff --git a/content-security-policy/embedded-enforcement/required-csp-header-cascade.html b/content-security-policy/embedded-enforcement/required-csp-header-cascade.html
new file mode 100644
index 0000000..f130b17
--- /dev/null
+++ b/content-security-policy/embedded-enforcement/required-csp-header-cascade.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Embedded Enforcement: Sec-Required-CSP header.</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="support/testharness-helper.sub.js"></script>
+</head>
+<body>
+  <script>
+    var tests = [
+      { "name": "Test same policy for both iframes",
+        "csp1": "script-src 'unsafe-inline';",
+        "csp2": "script-src 'unsafe-inline';",
+        "expected1": "script-src 'unsafe-inline';",
+        "expected2": "script-src 'unsafe-inline';"},
+      { "name": "Test more restrictive policy on second iframe",
+        "csp1": "script-src 'unsafe-inline';",
+        "csp2": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected1": "script-src 'unsafe-inline';",
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+      { "name": "Test less restrictive policy on second iframe",
+        "csp1": "script-src 'unsafe-inline'; style-src 'self';",
+        "csp2": "script-src 'unsafe-inline';",
+        "expected1": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+      { "name": "Test no policy on second iframe",
+        "csp1": "script-src 'unsafe-inline'; style-src 'self';",
+        "csp2": "",
+        "expected1": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+      { "name": "Test no policy on first iframe",
+        "csp1": "",
+        "csp2": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected1": null,
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+      { "name": "Test invalid policy on first iframe (bad directive)",
+        "csp1": "default-src http://example.com; invalid-policy-name http://example.com",
+        "csp2": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected1": null,
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+      { "name": "Test invalid policy on first iframe (report directive)",
+        "csp1": "script-src 'unsafe-inline'; report-uri resources/dummy-report.php",
+        "csp2": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected1": null,
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+      { "name": "Test invalid policy on second iframe (bad directive)",
+        "csp1": "script-src 'unsafe-inline'; style-src 'self';",
+        "csp2": "default-src http://example.com; invalid-policy-name http://example.com",
+        "expected1": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+      { "name": "Test invalid policy on second iframe (report directive)",
+        "csp1": "script-src 'unsafe-inline'; style-src 'self';",
+        "csp2": "script-src 'unsafe-inline'; report-uri resources/dummy-report.php",
+        "expected1": "script-src 'unsafe-inline'; style-src 'self';",
+        "expected2": "script-src 'unsafe-inline'; style-src 'self';"},
+    ];
+
+    tests.forEach(test => {
+      async_test(t =>  {
+        var url = generateURLStringWithSecondIframeParams(Host.SAME_ORIGIN, PolicyHeader.REQUIRED_CSP, test.csp2);
+        assert_required_csp(t, url, test.csp1, [test.expected1, test.expected2]);
+      }, "Test same origin: " + test.name);
+    });
+  </script>
+</body>
+</html>
diff --git a/content-security-policy/embedded-enforcement/required_csp-header.html b/content-security-policy/embedded-enforcement/required_csp-header.html
index 3d8e337..510c25b 100644
--- a/content-security-policy/embedded-enforcement/required_csp-header.html
+++ b/content-security-policy/embedded-enforcement/required_csp-header.html
@@ -50,31 +50,33 @@
       { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - report-uri present",
         "csp": "script-src 'unsafe-inline'; report-uri resources/dummy-report.php",
         "expected": null },
-      // TODO(andypaicu): when `report-to` is implemented, add tests here.
+      { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - report-to present",
+        "csp": "script-src 'unsafe-inline'; report-to resources/dummy-report.php",
+        "expected": null },
     ];
 
     tests.forEach(test => {
       async_test(t =>  {
         var url = generateURLString(Host.SAME_ORIGIN, PolicyHeader.REQUIRED_CSP);
-        assert_required_csp(t, url, test.csp, test.expected);
+        assert_required_csp(t, url, test.csp, [test.expected]);
       }, "Test same origin: " + test.name);
 
       async_test(t => {
         var url = generateURLString(Host.SAME_ORIGIN, PolicyHeader.REQUIRED_CSP);
         var redirect_url = generateRedirect(Host.SAME_ORIGIN, url);
-        assert_required_csp(t, redirect_url, test.csp, test.expected);
+        assert_required_csp(t, redirect_url, test.csp, [test.expected]);
       }, "Test same origin redirect: " + test.name);
 
       async_test(t => {
         var url = generateURLString(Host.SAME_ORIGIN, PolicyHeader.REQUIRED_CSP);
         var redirect_url = generateRedirect(Host.CROSS_ORIGIN, url);
-        assert_required_csp(t, redirect_url, test.csp, test.expected);
+        assert_required_csp(t, redirect_url, test.csp, [test.expected]);
       }, "Test cross origin redirect: " + test.name);
 
       async_test(t => {
         var url = generateURLString(Host.CROSS_ORIGIN, PolicyHeader.REQUIRED_CSP);
         var redirect_url = generateRedirect(Host.CROSS_ORIGIN, url);
-        assert_required_csp(t, redirect_url, test.csp, test.expected);
+        assert_required_csp(t, redirect_url, test.csp, [test.expected]);
       }, "Test cross origin redirect of cross origin iframe: " + test.name);
 
       async_test(t => {
diff --git a/content-security-policy/embedded-enforcement/support/echo-required-csp.py b/content-security-policy/embedded-enforcement/support/echo-required-csp.py
index 165ba9a..930a1f6 100644
--- a/content-security-policy/embedded-enforcement/support/echo-required-csp.py
+++ b/content-security-policy/embedded-enforcement/support/echo-required-csp.py
@@ -3,13 +3,37 @@
     header = request.headers.get("Sec-Required-CSP");
     message = {}
     message['required_csp'] = header if header else None
+    second_level_iframe_code = ""
+    if "include_second_level_iframe" in request.GET:
+       if "second_level_iframe_csp" in request.GET and request.GET["second_level_iframe_csp"] <> "":
+         second_level_iframe_code = '''<script>
+            var i2 = document.createElement('iframe');
+            i2.src = 'echo-required-csp.py';
+            i2.csp = "{0}";
+            document.body.appendChild(i2);
+            </script>'''.format(request.GET["second_level_iframe_csp"])
+       else:
+         second_level_iframe_code = '''<script>
+            var i2 = document.createElement('iframe');
+            i2.src = 'echo-required-csp.py';
+            document.body.appendChild(i2);
+            </script>'''
+
     return [("Content-Type", "text/html"), ("Allow-CSP-From", "*")], '''
 <!DOCTYPE html>
 <html>
 <head>
+    <!--{2}-->
     <script>
+      window.addEventListener('message', function(e) {{
+        window.parent.postMessage(e.data, '*');
+      }});
+
       window.parent.postMessage({0}, '*');
     </script>
 </head>
+<body>
+{1}
+</body>
 </html>
-'''.format(json.dumps(message))
+'''.format(json.dumps(message), second_level_iframe_code, str(request.headers))
diff --git a/content-security-policy/embedded-enforcement/support/testharness-helper.sub.js b/content-security-policy/embedded-enforcement/support/testharness-helper.sub.js
index f6a64b0..127a94b 100644
--- a/content-security-policy/embedded-enforcement/support/testharness-helper.sub.js
+++ b/content-security-policy/embedded-enforcement/support/testharness-helper.sub.js
@@ -34,16 +34,25 @@
   return url.toString();
 }
 
-function generateURL(host, path) {
+function generateURL(host, path, include_second_level_iframe, second_level_iframe_csp) {
   var url = new URL("http://{{host}}:{{ports[http][0]}}/content-security-policy/embedded-enforcement/support/");
   url.hostname = host == Host.SAME_ORIGIN ? "{{host}}" : "{{domains[天気の良い日]}}";
   url.pathname += path;
+  if (include_second_level_iframe) {
+    url.searchParams.append("include_second_level_iframe", "");
+    if (second_level_iframe_csp)
+      url.searchParams.append("second_level_iframe_csp", second_level_iframe_csp);
+  }
 
   return url;
 }
 
 function generateURLString(host, path) {
-  return generateURL(host, path).toString();
+  return generateURL(host, path, false, "").toString();
+}
+
+function generateURLStringWithSecondIframeParams(host, path, second_level_iframe_csp) {
+  return generateURL(host, path, true, second_level_iframe_csp).toString();
 }
 
 function generateRedirect(host, target) {
@@ -77,8 +86,13 @@
   window.addEventListener('message', t.step_func(e => {
     if (e.source != i.contentWindow || !('required_csp' in e.data))
       return;
-    assert_equals(e.data['required_csp'], expected);
-    t.done();
+
+    if (expected.indexOf(e.data['required_csp']) == -1)
+      assert_unreached('Child iframes have unexpected csp:"' + e.data['required_csp'] + '"');
+
+    expected.splice(expected.indexOf(e.data['required_csp']), 1);
+    if (expected.length == 0)
+      t.done();
   }));
 
   document.body.appendChild(i);
@@ -104,7 +118,7 @@
     window.onmessage = function (e) {
       if (e.source != i.contentWindow)
           return;
-      t.unreached_func('No message should be sent from the frame.');
+      t.assert_unreached('No message should be sent from the frame.');
     }
     i.onload = t.step_func(function () {
       // Delay the check until after the postMessage has a chance to execute.
@@ -124,12 +138,18 @@
       t.done();
     }));
   } else {
-    // Assert iframe loads.
+    // Assert iframe loads.  Wait for both the load event and the postMessage.
+    window.addEventListener('message', t.step_func(e => {
+      if (e.source != i.contentWindow)
+        return;
+      assert_true(loaded[urlId]);
+      if (i.onloadReceived)
+        t.done();
+    }));
     i.onload = t.step_func(function () {
-      // Delay the check until after the postMessage has a chance to execute.
-      setTimeout(t.step_func_done(function () {
-        assert_true(loaded[urlId]);
-      }), 1);
+      if (loaded[urlId])
+        t.done();
+      i.onloadReceived = true;
     });
   }
   document.body.appendChild(i);
diff --git a/content-security-policy/form-action/form-action-self-allowed-target-blank.html b/content-security-policy/form-action/form-action-self-allowed-target-blank.html
new file mode 100644
index 0000000..673174c
--- /dev/null
+++ b/content-security-policy/form-action/form-action-self-allowed-target-blank.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta http-equiv="Content-Security-Policy" content="form-action 'self'">
+</head>
+
+<body>
+  <form action='/content-security-policy/support/postmessage-pass-to-opener.html'
+        id='form_id'
+        target="_blank">
+  </form>
+
+  <p>
+    Test that "form-action 'self'" works correctly when the form uses
+    target="_blank". If this test passes, a new window must open after pressing
+    "submit".
+  </p>
+</body>
+
+<script>
+  async_test(t => {
+    document.addEventListener('securitypolicyviolation', function(e) {
+      t.unreached_func("Form submission was blocked.");
+    });
+
+    window.addEventListener('message', function(event) {
+      t.done();
+    })
+
+    window.addEventListener("load", function() {
+      document.getElementById("form_id").submit();
+    });
+  }, "The form submission should not be blocked by the iframe's CSP.");
+</script>
+
+</html>
diff --git a/content-security-policy/form-action/form-action-src-allowed-target-blank.sub.html b/content-security-policy/form-action/form-action-src-allowed-target-blank.sub.html
new file mode 100644
index 0000000..46747b7
--- /dev/null
+++ b/content-security-policy/form-action/form-action-src-allowed-target-blank.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>form-action-src-redirect-allowed-target-blank</title>
+  <meta http-equiv="Content-Security-Policy" content="form-action 'self'">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
+    function OnDocumentLoaded() {
+      let test = async_test("form submission targetting _blank allowed after a redirect");
+      window.addEventListener("message", function(event) {
+        if (event.data == "DocumentNotBlocked") {
+          event.source.close();
+          test.done();
+        }
+      });
+
+      let form = document.getElementById("form");
+      form.action =
+        "/content-security-policy/form-action/support/post-message-to-opener.sub.html";
+
+      let submit = document.getElementById("submit");
+      submit.click();
+    }
+  </script>
+</head>
+<body onload="OnDocumentLoaded();">
+  <form id="form" method="GET" target="_blank">
+    <input type="hidden" name="message" value="DocumentNotBlocked">
+    <input type="submit" id="submit">
+  </form>
+</body>
+</html>
+
diff --git a/content-security-policy/form-action/form-action-src-allowed-target-frame.sub.html b/content-security-policy/form-action/form-action-src-allowed-target-frame.sub.html
new file mode 100644
index 0000000..81921d3
--- /dev/null
+++ b/content-security-policy/form-action/form-action-src-allowed-target-frame.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>form-action-src-allowed-target-frame</title>
+  <meta http-equiv="Content-Security-Policy" content="form-action 'self'">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
+    function OnDocumentLoaded() {
+      let test = async_test("form submission targetting a frame allowed");
+      window.addEventListener("message", function(event) {
+        if (event.data == "DocumentNotBlocked") {
+          test.done();
+        }
+      });
+
+      let form = document.getElementById("form");
+      form.action =
+        "/content-security-policy/form-action/support/post-message-to-parent.sub.html";
+
+      let submit = document.getElementById("submit");
+      submit.click();
+    }
+  </script>
+</head>
+<body onload="OnDocumentLoaded();">
+  <form id="form" method="GET" target="frame">
+    <input type="hidden" name="message" value="DocumentNotBlocked">
+    <input type="submit" id="submit">
+  </form>
+  <iframe name="frame"></iframe>
+</body>
+</html>
+
diff --git a/content-security-policy/form-action/form-action-src-redirect-allowed-target-blank.sub.html b/content-security-policy/form-action/form-action-src-redirect-allowed-target-blank.sub.html
new file mode 100644
index 0000000..41c68b6
--- /dev/null
+++ b/content-security-policy/form-action/form-action-src-redirect-allowed-target-blank.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>form-action-src-redirect-allowed-target-blank</title>
+  <meta http-equiv="Content-Security-Policy" content="form-action 'self'">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
+    function OnDocumentLoaded() {
+      let test = async_test("form submission targetting _blank allowed after a redirect");
+      window.addEventListener("message", function(event) {
+        if (event.data == "DocumentNotBlocked") {
+          event.source.close();
+          test.done();
+        }
+      });
+
+      let form = document.getElementById("form");
+      let final_url = "/content-security-policy/form-action/support/post-message-to-opener.sub.html?message=DocumentNotBlocked";
+      let redirect_url = "/common/redirect.py?location=";
+      form.action = redirect_url + encodeURIComponent(final_url);
+
+      let submit = document.getElementById("submit");
+      submit.click();
+    }
+  </script>
+</head>
+<body onload="OnDocumentLoaded();">
+  <form id="form" method="POST" target="_blank">
+    <input type="submit" id="submit">
+  </form>
+</body>
+</html>
+
diff --git a/content-security-policy/form-action/form-action-src-redirect-allowed-target-frame.sub.html b/content-security-policy/form-action/form-action-src-redirect-allowed-target-frame.sub.html
new file mode 100644
index 0000000..6afd445
--- /dev/null
+++ b/content-security-policy/form-action/form-action-src-redirect-allowed-target-frame.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>form-action-src-redirect-allowed-target-frame</title>
+  <meta http-equiv="Content-Security-Policy" content="form-action 'self'">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
+    function OnDocumentLoaded() {
+      let test = async_test("form submission targetting a frame allowed after a redirect");
+      window.addEventListener("message", function(event) {
+        if (event.data == "DocumentNotBlocked") {
+          test.done();
+        }
+      });
+
+      let form = document.getElementById("form");
+      let final_url = "/content-security-policy/form-action/support/post-message-to-parent.sub.html?message=DocumentNotBlocked";
+      let redirect_url = "/common/redirect.py?location=";
+      form.action = redirect_url + encodeURIComponent(final_url);
+
+      let submit = document.getElementById("submit");
+      submit.click();
+    }
+  </script>
+</head>
+<body onload="OnDocumentLoaded();">
+  <form id="form" method="POST" target="frame">
+    <input type="submit" id="submit">
+  </form>
+  <iframe name="frame"></iframe>
+</body>
+</html>
+
diff --git a/content-security-policy/form-action/support/post-message-to-opener.sub.html b/content-security-policy/form-action/support/post-message-to-opener.sub.html
new file mode 100644
index 0000000..0348139
--- /dev/null
+++ b/content-security-policy/form-action/support/post-message-to-opener.sub.html
@@ -0,0 +1,3 @@
+<script>
+  opener.postMessage("{{GET[message]}}", "*");
+</script>
diff --git a/content-security-policy/form-action/support/post-message-to-parent.sub.html b/content-security-policy/form-action/support/post-message-to-parent.sub.html
new file mode 100644
index 0000000..63e464b
--- /dev/null
+++ b/content-security-policy/form-action/support/post-message-to-parent.sub.html
@@ -0,0 +1,3 @@
+<script>
+  parent.postMessage("{{GET[message]}}", "*");
+</script>
diff --git a/content-security-policy/frame-ancestors/frame-ancestors-overrides-xfo.html b/content-security-policy/frame-ancestors/frame-ancestors-overrides-xfo.html
index 8365466..4b7b099 100644
--- a/content-security-policy/frame-ancestors/frame-ancestors-overrides-xfo.html
+++ b/content-security-policy/frame-ancestors/frame-ancestors-overrides-xfo.html
@@ -10,7 +10,7 @@
             var i = document.createElement('iframe');
             i.src = "support/frame-ancestors-and-x-frame-options.sub.html?policy='self'&xfo=DENY";
             i.onload = t.step_func_done(function () {
-                assert_equals(i.contentDocument.origin, document.origin, "The same-origin page loaded.");
+                assert_equals(i.contentWindow.origin, window.origin, "The same-origin page loaded.");
             });
             document.body.appendChild(i);
         }, "A 'frame-ancestors' CSP directive overrides an 'x-frame-options' header which would block the page.");
@@ -19,10 +19,7 @@
             var i = document.createElement('iframe');
             i.src = "support/frame-ancestors-and-x-frame-options.sub.html?policy=other-origin.com&xfo=SAMEORIGIN";
             i.onload = t.step_func_done(function () {
-                assert_throws(
-                    "SecurityError",
-                    function () { i.contentDocument.origin },
-                    "The same-origin page was blocked and sandboxed.");
+                assert_equals(i.contentDocument, null);
             });
             document.body.appendChild(i);
         }, "A 'frame-ancestors' CSP directive overrides an 'x-frame-options' header which would allow the page.");
diff --git a/content-security-policy/inheritance/inherited-csp-list-modifications-are-local.html b/content-security-policy/inheritance/inherited-csp-list-modifications-are-local.html
new file mode 100644
index 0000000..c473b3f
--- /dev/null
+++ b/content-security-policy/inheritance/inherited-csp-list-modifications-are-local.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<head>
+  <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline' 'self'">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <!-- Tests that mutations inside a context that inherits a copy of the CSP list
+       does not affect the parent context -->
+</head>
+<body>
+  <script>
+    var t1 = async_test("Test that parent document image loads");
+    var t2 = async_test("Test that embedded iframe document image does not load");
+    var t3 = async_test("Test that spv event is fired");
+
+    window.onmessage = function(e) {
+      if (e.data.type == 'spv') {
+        t3.step(function() {
+          assert_equals(e.data.violatedDirective, "img-src");
+          t3.done();
+        });
+      } else if (e.data.type == 'imgload') {
+        var img = document.createElement('img');
+        img.src = "../support/pass.png";
+        img.onload = function() { t1.done(); };
+        img.onerror = t1.unreached_func('Should have loaded the image');
+        document.body.appendChild(img);
+
+        t2.step(function() {
+          assert_false(e.data.loaded, "Should not have loaded image inside the frame because of its CSP");
+          t2.done();
+        });
+      }
+    }
+
+    var srcdoc = ['<meta http-equiv="Content-Security-Policy" content="img-src \'none\'">',
+                  '<script>',
+                  ' window.addEventListener("securitypolicyviolation", function(e) {',
+                  '  window.top.postMessage({type: "spv", violatedDirective: e.violatedDirective}, "*");',
+                  ' });',
+                  '</scr' + 'ipt>',
+                  '<img src="../support/fail.png"',
+                  '  onload="window.top.postMessage({type: \'imgload\', loaded: true}, \'*\')"',
+                  '  onerror="window.top.postMessage({type: \'imgload\', loaded: false}, \'*\')">'].join('\n');
+    var i = document.createElement('iframe');
+    i.srcdoc = srcdoc;
+    document.body.appendChild(i);
+  </script>
+</body>
+</html>
diff --git a/content-security-policy/navigate-to/anchor-navigation-always-allowed.html b/content-security-policy/navigate-to/anchor-navigation-always-allowed.html
new file mode 100644
index 0000000..1cf01a7
--- /dev/null
+++ b/content-security-policy/navigate-to/anchor-navigation-always-allowed.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+
+<a name="anchor"></a>
+
+<script>
+  var t = async_test("Test that anchor navigation is allowed regardless of the `navigate-to` directive");
+  
+  window.addEventListener('securitypolicyviolation', t.unreached_func("Should not have triggered any violation"));
+  
+  try {
+    window.location.hash = "anchor";
+    t.done();
+  } catch(ex) {}
+</script>
+
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/anchor-navigation-always-allowed.html.headers b/content-security-policy/navigate-to/anchor-navigation-always-allowed.html.headers
new file mode 100644
index 0000000..739a2ce
--- /dev/null
+++ b/content-security-policy/navigate-to/anchor-navigation-always-allowed.html.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: navigate-to 'none'
diff --git a/content-security-policy/navigate-to/child-navigates-parent-allowed.html b/content-security-policy/navigate-to/child-navigates-parent-allowed.html
new file mode 100644
index 0000000..6c05478
--- /dev/null
+++ b/content-security-policy/navigate-to/child-navigates-parent-allowed.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+
+<head>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child can navigate the parent because the relevant policy belongs to the navigation initiator (in this case the child)");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+
+<iframe srcdoc="<iframe src='support/navigate_parent.sub.html?csp=navigate-to%20%27self%27'>">
+
+</body>
diff --git a/content-security-policy/navigate-to/child-navigates-parent-allowed.html.headers b/content-security-policy/navigate-to/child-navigates-parent-allowed.html.headers
new file mode 100644
index 0000000..aced1c6
--- /dev/null
+++ b/content-security-policy/navigate-to/child-navigates-parent-allowed.html.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: navigate-to 'self' support/navigate_parent.sub.html
diff --git a/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html b/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html
new file mode 100644
index 0000000..2acf417
--- /dev/null
+++ b/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe srcdoc="<iframe src='support/navigate_parent.sub.html?csp=navigate-to%20%27none%27&report_id={{$id:uuid()}}'>"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27none%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html.headers b/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html.headers
new file mode 100644
index 0000000..9cb770b
--- /dev/null
+++ b/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: navigate-to 'self'
diff --git a/content-security-policy/navigate-to/form-action/form-action-allows-navigate-to-allows.html b/content-security-policy/navigate-to/form-action/form-action-allows-navigate-to-allows.html
new file mode 100644
index 0000000..452b881
--- /dev/null
+++ b/content-security-policy/navigate-to/form-action/form-action-allows-navigate-to-allows.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that form-action overrides navigate-to when present.");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="../support/form_action_navigation.sub.html?csp=navigate-to%20%27self%27%3B%20form-action%20%27self%27%3B&action=post_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-action/form-action-allows-navigate-to-blocks.html b/content-security-policy/navigate-to/form-action/form-action-allows-navigate-to-blocks.html
new file mode 100644
index 0000000..44dcc51
--- /dev/null
+++ b/content-security-policy/navigate-to/form-action/form-action-allows-navigate-to-blocks.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that form-action overrides navigate-to when present.");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="../support/form_action_navigation.sub.html?csp=navigate-to%20%27none%27%3B%20form-action%20%27self%27%3B&action=post_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-action/form-action-blocks-navigate-to-allows.html b/content-security-policy/navigate-to/form-action/form-action-blocks-navigate-to-allows.html
new file mode 100644
index 0000000..5e6557d
--- /dev/null
+++ b/content-security-policy/navigate-to/form-action/form-action-blocks-navigate-to-allows.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that form-action overrides navigate-to when present.");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'fail');
+    assert_equals(e.data.violatedDirective, 'form-action');
+  });
+</script>
+<iframe src="../support/form_action_navigation.sub.html?csp=navigate-to%20%27self%27%3B%20form-action%20%27none%27%3B&action=post_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-action/form-action-blocks-navigate-to-blocks.html b/content-security-policy/navigate-to/form-action/form-action-blocks-navigate-to-blocks.html
new file mode 100644
index 0000000..9f9c803
--- /dev/null
+++ b/content-security-policy/navigate-to/form-action/form-action-blocks-navigate-to-blocks.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that form-action overrides navigate-to when present.");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'fail');
+    assert_equals(e.data.violatedDirective, 'form-action');
+  });
+</script>
+<iframe src="../support/form_action_navigation.sub.html?csp=navigate-to%20%27none%27%3B%20form-action%20%27none%27%3B&action=post_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-allowed.html b/content-security-policy/navigate-to/form-allowed.html
new file mode 100644
index 0000000..aa38d89
--- /dev/null
+++ b/content-security-policy/navigate-to/form-allowed.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/form_action_navigation.sub.html?csp=navigate-to%20%27self%27&action=post_message_to_frame_owner.html"></iframe>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-blocked.sub.html b/content-security-policy/navigate-to/form-blocked.sub.html
new file mode 100644
index 0000000..beb2221
--- /dev/null
+++ b/content-security-policy/navigate-to/form-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/form_action_navigation.sub.html?csp=navigate-to%20%27none%27&report_id={{$id:uuid()}}&action=post_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27none%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-cross-origin-allowed.sub.html b/content-security-policy/navigate-to/form-cross-origin-allowed.sub.html
new file mode 100644
index 0000000..4d0ddc3
--- /dev/null
+++ b/content-security-policy/navigate-to/form-cross-origin-allowed.sub.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/form_action_navigation.sub.html?csp=navigate-to%20http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}&action=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html"></iframe>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-cross-origin-blocked.sub.html b/content-security-policy/navigate-to/form-cross-origin-blocked.sub.html
new file mode 100644
index 0000000..49fe958
--- /dev/null
+++ b/content-security-policy/navigate-to/form-cross-origin-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/form_action_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&action=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-redirected-allowed.html b/content-security-policy/navigate-to/form-redirected-allowed.html
new file mode 100644
index 0000000..129b719
--- /dev/null
+++ b/content-security-policy/navigate-to/form-redirected-allowed.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/form_action_navigation.sub.html?csp=navigate-to%20%27self%27&action=redirect_to_post_message_to_frame_owner.py%3Flocation%3Dpost_message_to_frame_owner.html"></iframe>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/form-redirected-blocked.sub.html b/content-security-policy/navigate-to/form-redirected-blocked.sub.html
new file mode 100644
index 0000000..6adc9ef
--- /dev/null
+++ b/content-security-policy/navigate-to/form-redirected-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/form_action_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&action=redirect_to_post_message_to_frame_owner.py%3Flocation%3Dhttp%3A%2F%2F{{domains[www1]}}%3A{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/href-location-allowed.html b/content-security-policy/navigate-to/href-location-allowed.html
new file mode 100644
index 0000000..16e11e0
--- /dev/null
+++ b/content-security-policy/navigate-to/href-location-allowed.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+
+  window.open("support/href_location_navigation.sub.html?csp=navigate-to%20%27self%27&target=post_message_to_frame_owner.html", "_blank");
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/href-location-blocked.sub.html b/content-security-policy/navigate-to/href-location-blocked.sub.html
new file mode 100644
index 0000000..6aef250
--- /dev/null
+++ b/content-security-policy/navigate-to/href-location-blocked.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  window.open("support/href_location_navigation.sub.html?csp=navigate-to%20%27none%27&report_id={{$id:uuid()}}&target=post_message_to_frame_owner.html", "_blank");
+</script>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27none%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/href-location-cross-origin-allowed.sub.html b/content-security-policy/navigate-to/href-location-cross-origin-allowed.sub.html
new file mode 100644
index 0000000..a9396fc
--- /dev/null
+++ b/content-security-policy/navigate-to/href-location-cross-origin-allowed.sub.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+
+  window.open("support/href_location_navigation.sub.html?csp=navigate-to%20http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}&target=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html", "_blank");
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/href-location-cross-origin-blocked.sub.html b/content-security-policy/navigate-to/href-location-cross-origin-blocked.sub.html
new file mode 100644
index 0000000..894719b
--- /dev/null
+++ b/content-security-policy/navigate-to/href-location-cross-origin-blocked.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  window.open("support/href_location_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&target=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html", "_blank");
+</script>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/href-location-redirected-allowed.html b/content-security-policy/navigate-to/href-location-redirected-allowed.html
new file mode 100644
index 0000000..4dbfa7a
--- /dev/null
+++ b/content-security-policy/navigate-to/href-location-redirected-allowed.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+
+  window.open("support/href_location_navigation.sub.html?csp=navigate-to%20%27self%27&target=redirect_to_post_message_to_frame_owner.py", "_blank");
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/href-location-redirected-blocked.sub.html b/content-security-policy/navigate-to/href-location-redirected-blocked.sub.html
new file mode 100644
index 0000000..b91c473
--- /dev/null
+++ b/content-security-policy/navigate-to/href-location-redirected-blocked.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  window.open("support/href_location_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&target=redirect_to_post_message_to_frame_owner.py%3Flocation%3Dhttp%3A%2F%2F{{domains[www1]}}%3A{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html", "_blank");
+</script>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/link-click-allowed.html b/content-security-policy/navigate-to/link-click-allowed.html
new file mode 100644
index 0000000..667a3a5
--- /dev/null
+++ b/content-security-policy/navigate-to/link-click-allowed.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/link_click_navigation.sub.html?csp=navigate-to%20%27self%27&target=post_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/link-click-blocked.sub.html b/content-security-policy/navigate-to/link-click-blocked.sub.html
new file mode 100644
index 0000000..0ad9826
--- /dev/null
+++ b/content-security-policy/navigate-to/link-click-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/link_click_navigation.sub.html?csp=navigate-to%20%27none%27&report_id={{$id:uuid()}}&target=post_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27none%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/link-click-cross-origin-allowed.sub.html b/content-security-policy/navigate-to/link-click-cross-origin-allowed.sub.html
new file mode 100644
index 0000000..2f9f0e2
--- /dev/null
+++ b/content-security-policy/navigate-to/link-click-cross-origin-allowed.sub.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/link_click_navigation.sub.html?csp=navigate-to%20http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}&target=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/link-click-cross-origin-blocked.sub.html b/content-security-policy/navigate-to/link-click-cross-origin-blocked.sub.html
new file mode 100644
index 0000000..970f5a2
--- /dev/null
+++ b/content-security-policy/navigate-to/link-click-cross-origin-blocked.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+
+<head>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/link_click_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&target=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/link-click-redirected-allowed.html b/content-security-policy/navigate-to/link-click-redirected-allowed.html
new file mode 100644
index 0000000..81c4f4e
--- /dev/null
+++ b/content-security-policy/navigate-to/link-click-redirected-allowed.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<head>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/link_click_navigation.sub.html?csp=navigate-to%20%27self%27&target=redirect_to_post_message_to_frame_owner.py">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/link-click-redirected-blocked.sub.html b/content-security-policy/navigate-to/link-click-redirected-blocked.sub.html
new file mode 100644
index 0000000..bb61126
--- /dev/null
+++ b/content-security-policy/navigate-to/link-click-redirected-blocked.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+
+<head>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/link_click_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&target=redirect_to_post_message_to_frame_owner.py%3Flocation%3Dhttp%3A%2F%2F{{domains[www1]}}%3A{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/meta-refresh-allowed.html b/content-security-policy/navigate-to/meta-refresh-allowed.html
new file mode 100644
index 0000000..eeaefc4
--- /dev/null
+++ b/content-security-policy/navigate-to/meta-refresh-allowed.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/meta_refresh_navigation.sub.html?csp=navigate-to%20%27self%27&target=post_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/meta-refresh-blocked.sub.html b/content-security-policy/navigate-to/meta-refresh-blocked.sub.html
new file mode 100644
index 0000000..d4d52a5
--- /dev/null
+++ b/content-security-policy/navigate-to/meta-refresh-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/meta_refresh_navigation.sub.html?csp=navigate-to%20%27none%27&report_id={{$id:uuid()}}&target=post_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27none%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/meta-refresh-cross-origin-allowed.sub.html b/content-security-policy/navigate-to/meta-refresh-cross-origin-allowed.sub.html
new file mode 100644
index 0000000..39e887e
--- /dev/null
+++ b/content-security-policy/navigate-to/meta-refresh-cross-origin-allowed.sub.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/meta_refresh_navigation.sub.html?csp=navigate-to%20http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}&target=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/meta-refresh-cross-origin-blocked.sub.html b/content-security-policy/navigate-to/meta-refresh-cross-origin-blocked.sub.html
new file mode 100644
index 0000000..2118b27
--- /dev/null
+++ b/content-security-policy/navigate-to/meta-refresh-cross-origin-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/meta_refresh_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&target=http%3A%2F%2F{{domains[www1]}}:{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/meta-refresh-redirected-allowed.html b/content-security-policy/navigate-to/meta-refresh-redirected-allowed.html
new file mode 100644
index 0000000..de756bc
--- /dev/null
+++ b/content-security-policy/navigate-to/meta-refresh-redirected-allowed.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the child iframe navigation is allowed");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+</script>
+<iframe src="support/meta_refresh_navigation.sub.html?csp=navigate-to%20%27self%27&target=redirect_to_post_message_to_frame_owner.py">
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/meta-refresh-redirected-blocked.sub.html b/content-security-policy/navigate-to/meta-refresh-redirected-blocked.sub.html
new file mode 100644
index 0000000..55e3295
--- /dev/null
+++ b/content-security-policy/navigate-to/meta-refresh-redirected-blocked.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<iframe src="support/meta_refresh_navigation.sub.html?csp=navigate-to%20%27self%27&report_id={{$id:uuid()}}&target=redirect_to_post_message_to_frame_owner.py%3Flocation%3Dhttp%3A%2F%2F{{domains[www1]}}%3A{{ports[http][0]}}%2Fcontent-security-policy%2Fnavigate-to%2Fsupport%2Fpost_message_to_frame_owner.html"></iframe>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20%27self%27&reportID={{$id}}'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/parent-navigates-child-allowed.html b/content-security-policy/navigate-to/parent-navigates-child-allowed.html
new file mode 100644
index 0000000..e5455ce
--- /dev/null
+++ b/content-security-policy/navigate-to/parent-navigates-child-allowed.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var t = async_test("Test that the parent can navigate the child because the relevant policy belongs to the navigation initiator (in this case the parent)");
+  window.onmessage = t.step_func_done(function(e) {
+    assert_equals(e.data.result, 'success');
+  });
+  window.addEventListener('securitypolicyviolation', t.unreached_func("Should not have triggered a policy violation"));
+
+  var i = document.createElement('iframe');
+  var src_changed = false;
+  i.onload = function() {
+    if (src_changed) return;
+    src_changed = true;
+    i.src = "support/post_message_to_frame_owner.html";
+  }
+  i.src = "support/wait_for_navigation.html?csp=navigate-to%20%none%27";
+  document.body.appendChild(i);
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/parent-navigates-child-allowed.html.headers b/content-security-policy/navigate-to/parent-navigates-child-allowed.html.headers
new file mode 100644
index 0000000..9cb770b
--- /dev/null
+++ b/content-security-policy/navigate-to/parent-navigates-child-allowed.html.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: navigate-to 'self'
diff --git a/content-security-policy/navigate-to/parent-navigates-child-blocked.html b/content-security-policy/navigate-to/parent-navigates-child-blocked.html
new file mode 100644
index 0000000..fc549b4
--- /dev/null
+++ b/content-security-policy/navigate-to/parent-navigates-child-blocked.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  var i = document.createElement('iframe');
+  var src_changed = false;
+  i.onload = function() {
+    if (src_changed) return;
+    src_changed = true;
+    i.src = "support/post_message_to_frame_owner.html";
+  }
+  i.src = "support/wait_for_navigation.html?csp=navigate-to%20%27self%27";
+  document.body.appendChild(i);
+</script>
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=navigate-to%20support%2Fwait_for_navigation.html'></script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/parent-navigates-child-blocked.html.sub.headers b/content-security-policy/navigate-to/parent-navigates-child-blocked.html.sub.headers
new file mode 100644
index 0000000..6784a56
--- /dev/null
+++ b/content-security-policy/navigate-to/parent-navigates-child-blocked.html.sub.headers
@@ -0,0 +1,5 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Set-Cookie: parent-navigates-child-blocked={{$id:uuid()}}; Path=/content-security-policy/navigate-to/
+Content-Security-Policy: navigate-to support/wait_for_navigation.html; report-uri ../support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/navigate-to/support/form_action_navigation.sub.html b/content-security-policy/navigate-to/support/form_action_navigation.sub.html
new file mode 100644
index 0000000..3e3e2af
--- /dev/null
+++ b/content-security-policy/navigate-to/support/form_action_navigation.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <script>
+    window.addEventListener('securitypolicyviolation', function(e) {
+      top.postMessage({result: 'fail', violatedDirective: e.violatedDirective}, '*');
+    });
+  </script>
+</head>
+
+<body>
+<form action='{{GET[action]}}' target='_self' id='form'>
+  <input type="text" name="dummy">
+  <div id="form-div"></div>
+</form>
+
+<script>
+ try {
+  url = new URL("{{GET[action]}}", location.href);
+  for (var p of url.searchParams) {
+    var elem = document.createElement('input');
+    elem.type = 'text';
+    elem.name = p[0];
+    elem.value = p[1];
+    document.getElementById('form-div').appendChild(elem);
+  }
+ } catch(ex) {}
+ 
+ document.getElementById('form').submit();
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/form_action_navigation.sub.html.sub.headers b/content-security-policy/navigate-to/support/form_action_navigation.sub.html.sub.headers
new file mode 100644
index 0000000..9c572a9
--- /dev/null
+++ b/content-security-policy/navigate-to/support/form_action_navigation.sub.html.sub.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}}
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/href_location_navigation.sub.html b/content-security-policy/navigate-to/support/href_location_navigation.sub.html
new file mode 100644
index 0000000..c577e93
--- /dev/null
+++ b/content-security-policy/navigate-to/support/href_location_navigation.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+  try {
+    location.href = "{{GET[target]}}";
+  } catch(ex) {}
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/href_location_navigation.sub.html.sub.headers b/content-security-policy/navigate-to/support/href_location_navigation.sub.html.sub.headers
new file mode 100644
index 0000000..d01e267
--- /dev/null
+++ b/content-security-policy/navigate-to/support/href_location_navigation.sub.html.sub.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}}
diff --git a/content-security-policy/navigate-to/support/link_click_navigation.sub.html b/content-security-policy/navigate-to/support/link_click_navigation.sub.html
new file mode 100644
index 0000000..f1b4242
--- /dev/null
+++ b/content-security-policy/navigate-to/support/link_click_navigation.sub.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<a href="{{GET[target]}}" id="link">dummy link</a>
+<script>
+  document.getElementById('link').click();
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/link_click_navigation.sub.html.sub.headers b/content-security-policy/navigate-to/support/link_click_navigation.sub.html.sub.headers
new file mode 100644
index 0000000..d01e267
--- /dev/null
+++ b/content-security-policy/navigate-to/support/link_click_navigation.sub.html.sub.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}}
diff --git a/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html b/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html
new file mode 100644
index 0000000..64bae27
--- /dev/null
+++ b/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <script>
+    window.addEventListener('securitypolicyviolation', function(e) {
+      top.postMessage({result: 'fail', violatedDirective: e.violatedDirective}, '*');
+    });
+  </script>
+
+  <meta http-equiv="refresh" content="0; url={{GET[target]}}">
+</head>
+
+<body>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html.sub.headers b/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html.sub.headers
new file mode 100644
index 0000000..d01e267
--- /dev/null
+++ b/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html.sub.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}}
diff --git a/content-security-policy/navigate-to/support/navigate_parent.sub.html b/content-security-policy/navigate-to/support/navigate_parent.sub.html
new file mode 100644
index 0000000..a84c9c6
--- /dev/null
+++ b/content-security-policy/navigate-to/support/navigate_parent.sub.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <script>
+    window.addEventListener('securitypolicyviolation', function(e) {
+      top.postMessage({result: 'fail', violatedDirective: e.violatedDirective}, '*');
+    });
+  </script>
+</head>
+
+<body>
+<a href="post_message_to_frame_owner.html" id="link" target="_parent">dummy link</a>
+<script>
+  document.getElementById('link').click();
+</script>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/navigate_parent.sub.html.sub.headers b/content-security-policy/navigate-to/support/navigate_parent.sub.html.sub.headers
new file mode 100644
index 0000000..d01e267
--- /dev/null
+++ b/content-security-policy/navigate-to/support/navigate_parent.sub.html.sub.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}}
diff --git a/content-security-policy/navigate-to/support/post_message_to_frame_owner.html b/content-security-policy/navigate-to/support/post_message_to_frame_owner.html
new file mode 100644
index 0000000..c25e49d
--- /dev/null
+++ b/content-security-policy/navigate-to/support/post_message_to_frame_owner.html
@@ -0,0 +1,6 @@
+<script>
+  if (window.opener)
+    window.opener.postMessage({result: 'success'}, '*');
+  else
+    top.postMessage({result: 'success'}, '*');
+</script>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/redirect_to_post_message_to_frame_owner.py b/content-security-policy/navigate-to/support/redirect_to_post_message_to_frame_owner.py
new file mode 100644
index 0000000..652f9a5
--- /dev/null
+++ b/content-security-policy/navigate-to/support/redirect_to_post_message_to_frame_owner.py
@@ -0,0 +1,6 @@
+def main(request, response):
+    response.status = 302
+    if "location" in request.GET:
+        response.headers.set("Location", request.GET["location"])
+    else:
+        response.headers.set("Location", "post_message_to_frame_owner.html")
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/wait_for_navigation.html b/content-security-policy/navigate-to/support/wait_for_navigation.html
new file mode 100644
index 0000000..2450ff1
--- /dev/null
+++ b/content-security-policy/navigate-to/support/wait_for_navigation.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <script>
+    window.addEventListener('securitypolicyviolation', function(e) {
+      top.postMessage({result: 'fail', violatedDirective: e.violatedDirective}, '*');
+    });
+  </script>
+</head>
+
+<body>
+</body>
\ No newline at end of file
diff --git a/content-security-policy/navigate-to/support/wait_for_navigation.html.sub.headers b/content-security-policy/navigate-to/support/wait_for_navigation.html.sub.headers
new file mode 100644
index 0000000..d3c635b
--- /dev/null
+++ b/content-security-policy/navigate-to/support/wait_for_navigation.html.sub.headers
@@ -0,0 +1,4 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Security-Policy: {{GET[csp]}}
diff --git a/content-security-policy/prefetch-src/prefetch-allowed.html b/content-security-policy/prefetch-src/prefetch-allowed.html
new file mode 100644
index 0000000..95177c1
--- /dev/null
+++ b/content-security-policy/prefetch-src/prefetch-allowed.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='/content-security-policy/support/testharness-helper.js'></script>
+  <script src='/content-security-policy/support/prefetch-helper.js'></script>
+  <script>
+    async_test(t => {
+      var win = window.open('/content-security-policy/support/' +
+                            'file-prefetch-allowed.html');
+      win.addEventListener('load', function () {
+        // Cache control headers are added,since they are needed 
+        // to enable prefetching.
+        let url = '/content-security-policy/support/pass.png' +
+                                  '?pipe=header(Cache-Control, max-age=604800)';
+
+        // Link element is created on the new opened window.
+        let link = win.document.createElement('link');
+        link.rel = 'prefetch';
+        link.href = url;
+        assert_link_prefetches(t, link);
+        win.close();
+      }, false);
+    }, 'Prefetch succeeds when allowed by prefetch-src');
+  </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/content-security-policy/prefetch-src/prefetch-blocked.html b/content-security-policy/prefetch-src/prefetch-blocked.html
new file mode 100644
index 0000000..890a65f
--- /dev/null
+++ b/content-security-policy/prefetch-src/prefetch-blocked.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-Security-Policy" content="prefetch-src 'none';">
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='/content-security-policy/support/testharness-helper.js'></script>
+  <script src='/content-security-policy/support/prefetch-helper.js'></script>
+  <script>
+    async_test(t => {
+      let url = window.origin + '/content-security-policy/support/fail.png';
+
+      let link = document.createElement('link');
+      link.rel = 'prefetch';
+      link.href = url;
+
+      assert_link_does_not_prefetch(t, link);
+    }, "Blocked prefetch generates report.");
+  </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/content-security-policy/prefetch-src/prefetch-header-allowed.html b/content-security-policy/prefetch-src/prefetch-header-allowed.html
new file mode 100644
index 0000000..dd8071b
--- /dev/null
+++ b/content-security-policy/prefetch-src/prefetch-header-allowed.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <!-- Headers:
+    Content-Security-Policy: prefetch-src 'self'
+    Link: </content-security-policy/support/pass.png>;rel=prefetch
+  -->
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='/content-security-policy/support/testharness-helper.js'></script>
+  <script src='/content-security-policy/support/prefetch-helper.js'></script>
+  <script>
+    async_test(t => {
+      let url = window.origin + '/content-security-policy/support/pass.png';
+      assert_no_csp_event_for_url(t, url);
+
+      waitUntilResourceDownloaded(url)
+        .then(t.step_func_done()); 
+    }, 'Prefetch via `Link` header succeeds when allowed by prefetch-src');
+  </script>
+</head>
+<body>
+</body>
+</html>
+
diff --git a/content-security-policy/prefetch-src/prefetch-header-allowed.html.headers b/content-security-policy/prefetch-src/prefetch-header-allowed.html.headers
new file mode 100644
index 0000000..2b1d42a
--- /dev/null
+++ b/content-security-policy/prefetch-src/prefetch-header-allowed.html.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: prefetch-src 'self'
+Link: </content-security-policy/support/pass.png>;rel=prefetch
diff --git a/content-security-policy/prefetch-src/prefetch-header-blocked.html b/content-security-policy/prefetch-src/prefetch-header-blocked.html
new file mode 100644
index 0000000..382c99a
--- /dev/null
+++ b/content-security-policy/prefetch-src/prefetch-header-blocked.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-Security-Policy" content="prefetch-src 'none'">
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='/content-security-policy/support/testharness-helper.js'></script>
+  <script src='/content-security-policy/support/prefetch-helper.js'></script>
+  <script>
+    async_test(t => {
+      let url = window.origin + '/content-security-policy/support/fail.png';
+      waitUntilCSPEventForURL(t, url)
+        .then(t.step_func_done(e => {
+          assert_equals(e.violatedDirective, 'prefetch-src');
+          assert_resource_not_downloaded(t, url);
+        }));
+
+      // Load a stylesheet that tries to trigger a prefetch:
+      let link = document.createElement('link');
+      link.rel = 'stylesheet';
+      link.href = '/content-security-policy/support/prefetch-subresource.css';
+      document.head.appendChild(link);
+    }, 'Prefetch via `Link` header succeeds when allowed by prefetch-src');
+  </script>
+</head>
+<body>
+</body>
+</html>
+
+
diff --git a/content-security-policy/reporting/reporting-api-doesnt-send-reports-without-violation.https.sub.html b/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html
similarity index 100%
rename from content-security-policy/reporting/reporting-api-doesnt-send-reports-without-violation.https.sub.html
rename to content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html
diff --git a/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers b/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers
new file mode 100644
index 0000000..011c1b0
--- /dev/null
+++ b/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: reporting-api-doesnt-send-reports-without-violation={{$id:uuid()}}; Path=/content-security-policy/reporting-api
+Report-To: { "group": "csp-group", "max-age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] }
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'self'; report-to csp-group
diff --git a/content-security-policy/reporting/reporting-api-report-only-sends-reports-on-violation.https.sub.html b/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html
similarity index 100%
rename from content-security-policy/reporting/reporting-api-report-only-sends-reports-on-violation.https.sub.html
rename to content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html
diff --git a/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers b/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers
new file mode 100644
index 0000000..948b740
--- /dev/null
+++ b/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: reporting-api-report-only-sends-reports-on-violation={{$id:uuid()}}; Path=/content-security-policy/reporting-api
+Report-To: { "group": "csp-group", "max-age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] }
+Content-Security-Policy-Report-Only: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group
diff --git a/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-1.https.sub.html b/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html
similarity index 100%
rename from content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-1.https.sub.html
rename to content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html
diff --git a/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers b/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers
new file mode 100644
index 0000000..55bf5b7
--- /dev/null
+++ b/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: reporting-api-report-to-overrides-report-uri-1={{$id:uuid()}}; Path=/content-security-policy/reporting-api
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-uri "/content-security-policy/support/report.py?op=put&reportID={{$id}}"; report-to csp-group
+Report-To: { "group": "csp-group", "max-age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id:uuid()}}" }] }
diff --git a/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-2.https.sub.html b/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html
similarity index 100%
rename from content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-2.https.sub.html
rename to content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html
diff --git a/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers b/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers
new file mode 100644
index 0000000..ecef63f
--- /dev/null
+++ b/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: reporting-api-report-to-overrides-report-uri-2={{$id:uuid()}}; Path=/content-security-policy/reporting-api
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group; report-uri "/content-security-policy/support/report.py?op=put&reportID={{$id}}"
+Report-To: { "group": "csp-group", "max-age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id:uuid()}}" }] }
diff --git a/content-security-policy/reporting/reporting-api-sends-reports-on-violation.https.sub.html b/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html
similarity index 100%
rename from content-security-policy/reporting/reporting-api-sends-reports-on-violation.https.sub.html
rename to content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html
diff --git a/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers b/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers
new file mode 100644
index 0000000..4567e31
--- /dev/null
+++ b/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: reporting-api-sends-reports-on-violation={{$id:uuid()}}; Path=/content-security-policy/reporting-api
+Report-To: { "group": "csp-group", "max-age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] }
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group
diff --git a/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html b/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html
new file mode 100644
index 0000000..d633e3b
--- /dev/null
+++ b/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that reports using the report-api service are sent when there's a violation</title>
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+</head>
+<body>
+  <script>
+    async_test(function(t2) {
+      window.addEventListener("securitypolicyviolation", t2.step_func(function(e) {
+        assert_equals(e.blockedURI, "{{location[scheme]}}://{{location[host]}}/content-security-policy/support/fail.html");
+        assert_equals(e.violatedDirective, "frame-src");
+        t2.done();
+      }));
+    }, "Event is fired");
+  </script>
+  <iframe src="../support/fail.html"></iframe>
+
+  <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=frame-src%20%27none%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html.sub.headers b/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html.sub.headers
new file mode 100644
index 0000000..a0fd2e9
--- /dev/null
+++ b/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Set-Cookie: reporting-api-works-on-frame-src={{$id:uuid()}}; Path=/content-security-policy/reporting-api
+Report-To: { "group": "csp-group", "max-age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] }
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; frame-src 'none'; report-to csp-group
diff --git a/content-security-policy/reporting/multiple-report-policies.html b/content-security-policy/reporting/multiple-report-policies.html
new file mode 100644
index 0000000..204c1f3
--- /dev/null
+++ b/content-security-policy/reporting/multiple-report-policies.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>When multiple report-uri endpoints for multiple policies are specified, each gets a report</title>
+    <!-- CSP headers
+Content-Security-Policy-Report-Only: img-src http://* https://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
+
+Content-Security-Policy-Report-Only: img-src http://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
+-->
+</head>
+<body>
+    <img src="ftp://blah.test" />
+
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20http%3A%2F%2F%2A%20https%3A%2F%2F%2A&testName=1-Violation%20report%20status%20OK'></script>
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20http%3A%2F%2F%2A&reportCookieName=multiple-report-policies-2&testName=2-Violation%20report%20status%20OK'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/multiple-report-policies.html.sub.headers b/content-security-policy/reporting/multiple-report-policies.html.sub.headers
new file mode 100644
index 0000000..b7affbe
--- /dev/null
+++ b/content-security-policy/reporting/multiple-report-policies.html.sub.headers
@@ -0,0 +1,8 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: multiple-report-policies={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy-Report-Only: img-src http://* https://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
+Set-Cookie: multiple-report-policies-2={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy-Report-Only: img-src http://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-and-enforce.html b/content-security-policy/reporting/report-and-enforce.html
new file mode 100644
index 0000000..910df20
--- /dev/null
+++ b/content-security-policy/reporting/report-and-enforce.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Reporting and enforcing policies can be different</title>
+    <!-- CSP headers
+Content-Security-Policy: img-src 'none'; style-src *; script-src 'self' 'unsafe-inline'
+
+Content-Security-Policy-Report-Only: img-src *; style-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
+-->
+</head>
+<body>
+    <script>
+        var img_test = async_test("The image should be blocked");
+        var sheet_test = async_test("The stylesheet should load");
+        <!-- This image should be blocked, but should not generate a report-->
+        var i = document.createElement('img');
+        i.onerror = img_test.step_func_done();
+        i.onload = img_test.unreached_func("Should not have loaded the img");
+        i.src = "../support/fail.png";
+        document.body.appendChild(i);
+        <!-- This font should be loaded but should generate a report-->
+        var s = document.createElement('link');
+        s.onerror = sheet_test.unreached_func("Should have loaded the font");
+        s.onload = sheet_test.step_func_done();
+        s.type = "text/css";
+        s.rel="stylesheet";
+        s.href = "../support/fonts.css";
+        document.body.appendChild(s);
+    </script>
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=style-src%20%27none%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-and-enforce.html.sub.headers b/content-security-policy/reporting/report-and-enforce.html.sub.headers
new file mode 100644
index 0000000..5d4c5dc
--- /dev/null
+++ b/content-security-policy/reporting/report-and-enforce.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-and-enforce={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: img-src 'none'; style-src *; script-src 'self' 'unsafe-inline'
+Content-Security-Policy-Report-Only: img-src *; style-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/reporting/report-blocked-data-uri.html b/content-security-policy/reporting/report-blocked-data-uri.html
new file mode 100644
index 0000000..5a75ea1
--- /dev/null
+++ b/content-security-policy/reporting/report-blocked-data-uri.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Data-uri images are reported correctly</title>
+    <!-- CSP headers
+Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
+-->
+</head>
+<body>
+    <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-blocked-data-uri.html.sub.headers b/content-security-policy/reporting/report-blocked-data-uri.html.sub.headers
new file mode 100644
index 0000000..52e2f16
--- /dev/null
+++ b/content-security-policy/reporting/report-blocked-data-uri.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-blocked-data-uri={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html b/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html
new file mode 100644
index 0000000..9d56cdb
--- /dev/null
+++ b/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Cross-origin images are reported correctly</title>
+    <!-- CSP headers
+Content-Security-Policy: script-src 'self' 'unsafe-inline'
+Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID=$id
+-->
+</head>
+<body>
+  <img src="http://{{domains[www1]}}:{{ports[http][0]}}/content-security-policy/support/pass.png">
+  <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html.sub.headers b/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html.sub.headers
new file mode 100644
index 0000000..ef5073b
--- /dev/null
+++ b/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-blocked-uri-cross-origin={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: script-src 'self' 'unsafe-inline'
+Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-blocked-uri.html b/content-security-policy/reporting/report-blocked-uri.html
new file mode 100644
index 0000000..5eb9f29
--- /dev/null
+++ b/content-security-policy/reporting/report-blocked-uri.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Blocked relative images are reported correctly</title>
+    <!-- CSP headers
+Content-Security-Policy: script-src 'self' 'unsafe-inline'
+Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
+-->
+</head>
+<body>
+    <img src="../support/pass.png">
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-blocked-uri.html.sub.headers b/content-security-policy/reporting/report-blocked-uri.html.sub.headers
new file mode 100644
index 0000000..6f4b37e
--- /dev/null
+++ b/content-security-policy/reporting/report-blocked-uri.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-blocked-uri={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: script-src 'self' 'unsafe-inline'
+Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html b/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html
new file mode 100644
index 0000000..2922894
--- /dev/null
+++ b/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Cookies are not sent on cross origin violation reports</title>
+    <!-- CSP headers
+         Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri http://{{domains[www1]}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID=$id
+         -->
+</head>
+<body>
+<script>
+  var test = async_test("Image should not load");
+  fetch(
+    "/cookies/resources/set-cookie.py?name=cspViolationReportCookie1&path=" + encodeURIComponent("{{domains[www1]}}:{{ports[http][0]}}/"),
+    {mode: 'no-cors', credentials: 'include'})
+  .then(() => {
+    // This image will generate a CSP violation report.
+    const img = new Image();
+    img.onerror = test.step_func_done();
+    img.onload = test.unreached_func("Should not have loaded the image");
+
+    img.src = "../support/fail.png";
+    document.body.appendChild(img);
+  });
+</script>
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27&noCookies=true'></script>
+
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html.sub.headers b/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html.sub.headers
new file mode 100644
index 0000000..cb1acfc
--- /dev/null
+++ b/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-cross-origin-no-cookies={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri http://{{domains[www1]}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-multiple-violations-01.html b/content-security-policy/reporting/report-multiple-violations-01.html
new file mode 100644
index 0000000..7a92f1b
--- /dev/null
+++ b/content-security-policy/reporting/report-multiple-violations-01.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Test multiple violations cause multiple reports</title>
+    <!-- CSP headers
+         Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
+         -->
+</head>
+<body>
+    <img src="../support/pass.png">
+    <img src="../support/pass2.png">
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27&reportCount=2'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-multiple-violations-01.html.sub.headers b/content-security-policy/reporting/report-multiple-violations-01.html.sub.headers
new file mode 100644
index 0000000..904e2c6
--- /dev/null
+++ b/content-security-policy/reporting/report-multiple-violations-01.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-multiple-violations-01={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-multiple-violations-02.html b/content-security-policy/reporting/report-multiple-violations-02.html
new file mode 100644
index 0000000..d9f7da3
--- /dev/null
+++ b/content-security-policy/reporting/report-multiple-violations-02.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>This tests that multiple violations on a page trigger multiple reports
+        if and only if the violations are distinct.</title>
+    <!-- CSP headers
+         Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}}
+         -->
+</head>
+<body>
+  <script>
+    for (var i = 0; i<5; i++)
+      setTimeout("document.body.innerHTML += ('<p>PASS: setTimeout #" + i + " executed.');", 0);
+  </script>
+  <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27unsafe-inline%27%20%27self%27&reportCount=1'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-multiple-violations-02.html.sub.headers b/content-security-policy/reporting/report-multiple-violations-02.html.sub.headers
new file mode 100644
index 0000000..e7bf84b
--- /dev/null
+++ b/content-security-policy/reporting/report-multiple-violations-02.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-multiple-violations-02={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/reporting/report-only-in-meta.sub.html b/content-security-policy/reporting/report-only-in-meta.sub.html
new file mode 100644
index 0000000..d4e7b6f
--- /dev/null
+++ b/content-security-policy/reporting/report-only-in-meta.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Report-only policy not allowed in meta tag</title>
+    <!-- CSP headers
+         Content-Security-Policy: script-src 'unsafe-inline' 'self'
+         -->
+    <!-- since we try to set the report-uri in the meta tag, we have to set the cookie with the reportID in here instead of in the headers file -->
+    <meta http-equiv="Content-Security-Policy-Report-Only" content="img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id:uuid()}}">
+</head>
+<body>
+  <script>
+    var test = async_test("Image should load");
+
+    <!-- Set cookie for checking if the report exists
+      -->
+    fetch(
+      "support/set-cookie.py?name=report-only-in-meta&value={{$id}}&path=" + encodeURIComponent("/content-security-policy/reporting/"),
+      {mode: 'no-cors', credentials: 'include'})
+    .then(() => {
+      const img = new Image();
+      img.onload = test.step_func_done();
+      img.onerror = test.unreached_func("Should have loaded the image");
+
+      img.src = "../support/pass.png";
+      document.body.appendChild(img);
+
+      <!-- this needs to be done after setting the cookie so we do it here -->
+      const script = document.createElement('script');
+      script.async = true;
+      script.defer = true;
+      script.src = '../support/checkReport.sub.js?reportExists=false'
+      document.body.appendChild(script);
+    });
+  </script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-only-in-meta.sub.html.sub.headers b/content-security-policy/reporting/report-only-in-meta.sub.html.sub.headers
new file mode 100644
index 0000000..b56292b
--- /dev/null
+++ b/content-security-policy/reporting/report-only-in-meta.sub.html.sub.headers
@@ -0,0 +1,5 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Content-Security-Policy: script-src 'unsafe-inline' 'self'
diff --git a/content-security-policy/reporting/report-original-url.sub.html b/content-security-policy/reporting/report-original-url.sub.html
new file mode 100644
index 0000000..45c1aeb
--- /dev/null
+++ b/content-security-policy/reporting/report-original-url.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <!-- CSP headers
+       Content-Security-Policy: img-src {{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}; script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID=$id
+       -->
+</head>
+<body>
+<script>
+function createListener(expectedURL, test) {
+    var listener = test.step_func(e => {
+        if (e.blockedURI == expectedURL) {
+            document.removeEventListener('securitypolicyviolation', listener);
+            test.done();
+        }
+    });
+    document.addEventListener('securitypolicyviolation', listener);
+}
+
+async_test(t => {
+    var i = document.createElement('img');
+    createListener("{{location[scheme]}}://{{location[host]}}/content-security-policy/support/fail.png?t=1", t);
+    i.src = "{{location[scheme]}}://{{location[host]}}/content-security-policy/support/fail.png?t=1";
+}, "Direct block, same-origin = full URL in report");
+
+async_test(t => {
+    var i = document.createElement('img');
+    createListener("{{location[scheme]}}://{{domains[www2]}}:{{ports[http][0]}}/content-security-policy/support/fail.png?t=2", t);
+    i.src = "{{location[scheme]}}://{{domains[www2]}}:{{ports[http][0]}}/content-security-policy/support/fail.png?t=2";
+}, "Direct block, cross-origin = full URL in report");
+
+async_test(t => {
+    var i = document.createElement('img');
+    var url = "{{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}/common/redirect.py?location=" + encodeURIComponent("{{location[scheme]}}://{{location[host]}}/content-security-policy/support/fail.png?t=3");
+    createListener("{{location[scheme]}}://{{location[host]}}/content-security-policy/support/fail.png?t=3", t);
+    i.src = url;
+}, "Block after redirect, same-origin = original URL in report");
+
+async_test(t => {
+    var i = document.createElement('img');
+    var url = "{{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}/common/redirect.py?location=" + encodeURIComponent("{{location[scheme]}}://{{domains[www2]}}:{{ports[http][0]}}/content-security-policy/support/fail.png?t=4");
+    createListener("{{location[scheme]}}://{{domains[www2]}}:{{ports[http][0]}}", t);
+    i.src = url;
+}, "Block after redirect, cross-origin = original URL in report");
+</script>
+
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src {{location[scheme]}}%3A%2F%2F{{domains[www1]}}%3A{{ports[http][0]}}'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-original-url.sub.html.sub.headers b/content-security-policy/reporting/report-original-url.sub.html.sub.headers
new file mode 100644
index 0000000..1031a7a
--- /dev/null
+++ b/content-security-policy/reporting/report-original-url.sub.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-original-url={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: img-src {{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}; script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/reporting/report-same-origin-with-cookies.html b/content-security-policy/reporting/report-same-origin-with-cookies.html
new file mode 100644
index 0000000..515996c
--- /dev/null
+++ b/content-security-policy/reporting/report-same-origin-with-cookies.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Cookies are sent on same origin violation reports</title>
+    <!-- CSP headers
+         Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /content-security-policy/support/report.py?op=put&reportID={{$id}}
+         -->
+</head>
+<body>
+<script>
+  var test = async_test("Image should not load");
+  fetch(
+    "/cookies/resources/set-cookie.py?name=cspViolationReportCookie2&path=" + encodeURIComponent("/"),
+    {mode: 'no-cors', credentials: 'include'})
+  .then(() => {
+    // This image will generate a CSP violation report.
+    const img = new Image();
+    img.onerror = test.step_func_done();
+    img.onload = test.unreached_func("Should not have loaded the image");
+
+    img.src = "../support/fail.png";
+    document.body.appendChild(img);
+  });
+</script>
+<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27&cookiePresent=cspViolationReportCookie2'></script>
+
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-same-origin-with-cookies.html.sub.headers b/content-security-policy/reporting/report-same-origin-with-cookies.html.sub.headers
new file mode 100644
index 0000000..356439d
--- /dev/null
+++ b/content-security-policy/reporting/report-same-origin-with-cookies.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-same-origin-with-cookies={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /content-security-policy/support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/reporting/report-strips-fragment.html b/content-security-policy/reporting/report-strips-fragment.html
new file mode 100644
index 0000000..4ecfa84
--- /dev/null
+++ b/content-security-policy/reporting/report-strips-fragment.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/content-security-policy/support/testharness-helper.js"></script>
+  <meta http-equiv="Content-Security-Policy" content="img-src 'none'">
+</head>
+<body>
+  <script>
+  async_test(t => {
+    waitUntilCSPEventForURL(t, "https://evil.com/img.png")
+      .then(t.step_func_done(e => {
+        var u = new URL(e.documentURI);
+        assert_equals(u.hash, "");
+      }));
+
+    window.location.hash = "should-not-appear-in-report";
+
+    var i = document.createElement("img");
+    i.src = "https://evil.com/img.png#boo";
+  }, "Reported document URI does not contain fragments.");
+  </script>
+</body>
diff --git a/content-security-policy/reporting/report-uri-effective-directive.html b/content-security-policy/reporting/report-uri-effective-directive.html
new file mode 100644
index 0000000..1d959fd
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-effective-directive.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Violation report is sent if violation occurs.</title>
+    <!-- CSP headers
+         Content-Security-Policy: default-src 'self'; report-uri ../support/report.py?op=put&reportID={{$id}}
+         -->
+</head>
+<body>
+    <script>
+        // This script block will trigger a violation report.
+        alert('FAIL');
+    </script>
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27self%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-uri-effective-directive.html.sub.headers b/content-security-policy/reporting/report-uri-effective-directive.html.sub.headers
new file mode 100644
index 0000000..1fb9842
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-effective-directive.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-uri-effective-directive={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: default-src 'self'; report-uri ../support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/reporting/report-uri-from-child-frame.html b/content-security-policy/reporting/report-uri-from-child-frame.html
new file mode 100644
index 0000000..92b1e1b
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-from-child-frame.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Reporting works in child iframes.</title>
+    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-abc'">
+</head>
+<body>
+    <script nonce="abc">
+      var t1 = async_test("Check that we received a message from the child frame");
+
+      window.onmessage = function(e) {
+        if (e.data == 'cookie set') {
+          var s = document.createElement('script');
+          s.async = true;
+          s.defer = true;
+          s.src = '../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27self%27%20%27nonce-abc%27&reportCookieName=generate-csp-report';
+          document.body.appendChild(s);
+
+          t1.done();
+        }
+      }
+    </script>
+    <iframe src="support/generate-csp-report.html"/>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-uri-from-inline-javascript.html b/content-security-policy/reporting/report-uri-from-inline-javascript.html
new file mode 100644
index 0000000..ca65c90
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-from-inline-javascript.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Violation report is sent from inline javascript.</title>
+    <!-- CSP headers
+         Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
+         -->
+</head>
+<body>
+    <script>
+        // This script block will trigger a violation report.
+        var i = document.createElement('img');
+        i.src = '/security/resources/abe.png';
+        document.body.appendChild(i);
+    </script>
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-uri-from-inline-javascript.html.sub.headers b/content-security-policy/reporting/report-uri-from-inline-javascript.html.sub.headers
new file mode 100644
index 0000000..07239a0
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-from-inline-javascript.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-uri-from-inline-javascript={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-uri-from-javascript.html b/content-security-policy/reporting/report-uri-from-javascript.html
new file mode 100644
index 0000000..354c696
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-from-javascript.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Violation report is sent from javascript resource.</title>
+    <!-- CSP headers
+         Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
+         -->
+</head>
+<body>
+    <script src="../support/inject-image.js"></script>
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20%27none%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-uri-from-javascript.html.sub.headers b/content-security-policy/reporting/report-uri-from-javascript.html.sub.headers
new file mode 100644
index 0000000..9443226
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-from-javascript.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-uri-from-javascript={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/reporting/report-uri-multiple-reversed.html b/content-security-policy/reporting/report-uri-multiple-reversed.html
new file mode 100644
index 0000000..499d119
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-multiple-reversed.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Content-Security-Policy-Report-Only violation report is sent even when resource is blocked by actual policy.</title>
+    <!-- CSP headers
+         Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}}
+         Content-Security-Policy: img-src http://*
+         -->
+</head>
+<body>
+    <img src="ftp://blah.test" />
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20http%3A%2F%2F%2A'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-uri-multiple-reversed.html.sub.headers b/content-security-policy/reporting/report-uri-multiple-reversed.html.sub.headers
new file mode 100644
index 0000000..0c12887
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-multiple-reversed.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-uri-multiple-reversed={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}}
+Content-Security-Policy: img-src http://*
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-uri-multiple.html b/content-security-policy/reporting/report-uri-multiple.html
new file mode 100644
index 0000000..268da91
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-multiple.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Content-Security-Policy-Report-Only violation report is sent even when resource is blocked by actual policy.</title>
+    <!-- CSP headers
+         Content-Security-Policy: img-src http://*
+         Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}}
+      -->
+</head>
+<body>
+    <img src="ftp://blah.test" />
+    <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=img-src%20http%3A%2F%2F%2A'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-uri-multiple.html.sub.headers b/content-security-policy/reporting/report-uri-multiple.html.sub.headers
new file mode 100644
index 0000000..d78c8e5
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-multiple.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-uri-multiple={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: img-src http://*
+Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/report-uri-scheme-relative.html b/content-security-policy/reporting/report-uri-scheme-relative.html
new file mode 100644
index 0000000..e64d797
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-scheme-relative.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>Relative scheme URIs are accepted as the report-uri.</title>
+    <!-- CSP headers
+         Content-Security-Policy: script-src 'self'; report-uri //{{location[host]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}
+         -->
+</head>
+<body>
+  <script>
+    // This script block will trigger a violation report.
+    alert('FAIL');
+  </script>
+  <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27self%27'></script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/report-uri-scheme-relative.html.sub.headers b/content-security-policy/reporting/report-uri-scheme-relative.html.sub.headers
new file mode 100644
index 0000000..74f263d
--- /dev/null
+++ b/content-security-policy/reporting/report-uri-scheme-relative.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: report-uri-scheme-relative={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: script-src 'self'; report-uri //{{location[host]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/content-security-policy/reporting/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers b/content-security-policy/reporting/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers
deleted file mode 100644
index 9a02581..0000000
--- a/content-security-policy/reporting/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers
+++ /dev/null
@@ -1,7 +0,0 @@
-Expires: Mon, 26 Jul 1997 05:00:00 GMT
-Cache-Control: no-store, no-cache, must-revalidate
-Cache-Control: post-check=0, pre-check=0, false
-Pragma: no-cache
-Set-Cookie: reporting-api-doesnt-send-reports-without-violation={{$id:uuid()}}; Path=/content-security-policy/reporting
-Report-To: { "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}", "group": "csp-group", "max-age": 10886400 }
-Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'self'; report-to csp-group
diff --git a/content-security-policy/reporting/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers b/content-security-policy/reporting/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers
deleted file mode 100644
index 990cfaf..0000000
--- a/content-security-policy/reporting/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers
+++ /dev/null
@@ -1,7 +0,0 @@
-Expires: Mon, 26 Jul 1997 05:00:00 GMT
-Cache-Control: no-store, no-cache, must-revalidate
-Cache-Control: post-check=0, pre-check=0, false
-Pragma: no-cache
-Set-Cookie: reporting-api-report-only-sends-reports-on-violation={{$id:uuid()}}; Path=/content-security-policy/reporting
-Report-To: { "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}", "group": "csp-group", "max-age": 10886400 }
-Content-Security-Policy-Report-Only: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group
diff --git a/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers b/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers
deleted file mode 100644
index c696384..0000000
--- a/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers
+++ /dev/null
@@ -1,7 +0,0 @@
-Expires: Mon, 26 Jul 1997 05:00:00 GMT
-Cache-Control: no-store, no-cache, must-revalidate
-Cache-Control: post-check=0, pre-check=0, false
-Pragma: no-cache
-Set-Cookie: reporting-api-report-to-overrides-report-uri-1={{$id:uuid()}}; Path=/content-security-policy/reporting
-Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-uri "/content-security-policy/support/report.py?op=put&reportID={{$id}}"; report-to csp-group
-Report-To: { "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id:uuid()}}", "group": "csp-group", "max-age": 10886400 }
diff --git a/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers b/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers
deleted file mode 100644
index 2ac676b..0000000
--- a/content-security-policy/reporting/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers
+++ /dev/null
@@ -1,7 +0,0 @@
-Expires: Mon, 26 Jul 1997 05:00:00 GMT
-Cache-Control: no-store, no-cache, must-revalidate
-Cache-Control: post-check=0, pre-check=0, false
-Pragma: no-cache
-Set-Cookie: reporting-api-report-to-overrides-report-uri-2={{$id:uuid()}}; Path=/content-security-policy/reporting
-Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group; report-uri "/content-security-policy/support/report.py?op=put&reportID={{$id}}"
-Report-To: { "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id:uuid()}}", "group": "csp-group", "max-age": 10886400 }
diff --git a/content-security-policy/reporting/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers b/content-security-policy/reporting/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers
deleted file mode 100644
index ff4b9e6..0000000
--- a/content-security-policy/reporting/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers
+++ /dev/null
@@ -1,7 +0,0 @@
-Expires: Mon, 26 Jul 1997 05:00:00 GMT
-Cache-Control: no-store, no-cache, must-revalidate
-Cache-Control: post-check=0, pre-check=0, false
-Pragma: no-cache
-Set-Cookie: reporting-api-sends-reports-on-violation={{$id:uuid()}}; Path=/content-security-policy/reporting
-Report-To: { "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}", "group": "csp-group", "max-age": 10886400 }
-Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group
diff --git a/content-security-policy/reporting/securitypolicyviolation-idl.html b/content-security-policy/reporting/securitypolicyviolation-idl.html
deleted file mode 100644
index e6b1e0d..0000000
--- a/content-security-policy/reporting/securitypolicyviolation-idl.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>SecurityPolicyViolationEvent IDL Tests</title>
-<link rel="author" title="Louay Bassbouss" href="http://www.fokus.fraunhofer.de">
-<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-user-agent">
-
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-
-<script id="idl" type="text/plain">
-[Constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict)]
-interface SecurityPolicyViolationEvent : Event {
-    readonly    attribute DOMString      documentURI;
-    readonly    attribute DOMString      referrer;
-    readonly    attribute DOMString      blockedURI;
-    readonly    attribute DOMString      violatedDirective;
-    readonly    attribute DOMString      effectiveDirective;
-    readonly    attribute DOMString      originalPolicy;
-    readonly    attribute DOMString      disposition;
-    readonly    attribute DOMString      sourceFile;
-    readonly    attribute unsigned short statusCode;
-    readonly    attribute long           lineNumber;
-    readonly    attribute long           columnNumber;
-};
-
-dictionary SecurityPolicyViolationEventInit : EventInit {
-    DOMString      documentURI;
-    DOMString      referrer;
-    DOMString      blockedURI;
-    DOMString      violatedDirective;
-    DOMString      effectiveDirective;
-    DOMString      originalPolicy;
-    DOMString      disposition;
-    DOMString      sourceFile;
-    unsigned short statusCode;
-    long           lineNumber;
-    long           columnNumber;
-};
-</script>
-<script>
-function do_test(dom_idl) {
-    var idl_array = new IdlArray();
-    idl_array.add_untested_idls(dom_idl);
-    var idls = document.getElementById('idl').textContent;
-    idl_array.add_idls(idls);
-
-    window.event_to_test = new SecurityPolicyViolationEvent({});
-
-    idl_array.add_objects({
-        SecurityPolicyViolationEvent: ['event_to_test']
-    });
-    idl_array.test();
-}
-
-promise_test(function() {
-    return fetch("/interfaces/dom.idl").then(response => response.text())
-                                       .then(do_test);
-}, "Test driver");
-</script>
diff --git a/content-security-policy/reporting/support/generate-csp-report.html b/content-security-policy/reporting/support/generate-csp-report.html
new file mode 100644
index 0000000..c2024c0
--- /dev/null
+++ b/content-security-policy/reporting/support/generate-csp-report.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+    <script nonce='abc'>
+      top.postMessage('cookie set', '*');
+    </script>
+    <script>
+        // This script block will trigger a violation report.
+        alert('FAIL');
+    </script>
+</body>
+</html>
diff --git a/content-security-policy/reporting/support/generate-csp-report.html.sub.headers b/content-security-policy/reporting/support/generate-csp-report.html.sub.headers
new file mode 100644
index 0000000..6d1eedb
--- /dev/null
+++ b/content-security-policy/reporting/support/generate-csp-report.html.sub.headers
@@ -0,0 +1,6 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: generate-csp-report={{$id:uuid()}}; Path=/content-security-policy/reporting/
+Content-Security-Policy: script-src 'self' 'nonce-abc'; report-uri ../../support/report.py?op=put&reportID={{$id}}
diff --git a/content-security-policy/reporting/support/set-cookie.py b/content-security-policy/reporting/support/set-cookie.py
new file mode 100644
index 0000000..7f321a5
--- /dev/null
+++ b/content-security-policy/reporting/support/set-cookie.py
@@ -0,0 +1,28 @@
+import sys
+import urlparse
+
+def main(request, response):
+    """
+    Returns cookie name and path from query params in a Set-Cookie header.
+
+    e.g.
+
+    > GET /cookies/resources/set-cookie.py?name=match-slash&path=%2F HTTP/1.1
+    > Host: localhost:8000
+    > User-Agent: curl/7.43.0
+    > Accept: */*
+    >
+    < HTTP/1.1 200 OK
+    < Content-Type: application/json
+    < Set-Cookie: match-slash=1; Path=/; Expires=Wed, 09 Jun 2021 10:18:14 GMT
+    < Server: BaseHTTP/0.3 Python/2.7.12
+    < Date: Tue, 04 Oct 2016 18:16:06 GMT
+    < Content-Length: 80
+    """
+    params = urlparse.parse_qs(request.url_parts.query)
+    headers = [
+        ("Content-Type", "application/json"),
+        ("Set-Cookie", "{name[0]}={value[0]}; Path={path[0]}; Expires=Wed, 09 Jun 2021 10:18:14 GMT".format(**params))
+    ]
+    body = "{}"
+    return headers, body
diff --git a/content-security-policy/script-src/nonce-enforce-blocked.html b/content-security-policy/script-src/nonce-enforce-blocked.html
new file mode 100644
index 0000000..25343a5
--- /dev/null
+++ b/content-security-policy/script-src/nonce-enforce-blocked.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-abc'">
+<script src="/resources/testharness.js" nonce="abc"></script>
+<script src="/resources/testharnessreport.js" nonce="abc"></script>
+<script nonce="abc">
+    var t = async_test("Unnonced scripts generate reports.");
+    var events = 0;
+    var firstLine = 38;
+    var expectations = {}
+    expectations[firstLine] = true;
+    expectations[firstLine + 3] = true;
+    expectations[firstLine + 6] = true;
+    expectations[firstLine + 9] = true;
+    expectations[firstLine + 12] = true;
+    expectations[firstLine + 15] = true;
+    expectations[firstLine + 18] = true;
+    expectations["/content-security-policy/support/nonce-should-be-blocked.js?1"] = true;
+    expectations["/content-security-policy/support/nonce-should-be-blocked.js?2"] = true;
+    expectations["/content-security-policy/support/nonce-should-be-blocked.js?3"] = true;
+    expectations["/content-security-policy/support/nonce-should-be-blocked.js?4"] = true;
+    expectations["/content-security-policy/support/nonce-should-be-blocked.js?5"] = true;
+
+    document.addEventListener('securitypolicyviolation', t.step_func(e => {
+        if (e.lineNumber) {
+            // Verify that the line is expected, then clear the expectation:
+            assert_true(expectations[e.lineNumber], "Line number: " + e.lineNumber);
+            assert_equals(e.blockedURI, "inline");
+        } else {
+            // Otherwise, verify that the URL is expected, then clear the expectation:
+            var url = new URL(e.blockedURI);
+            assert_true(expectations[url.pathname + url.search], "URL: " + e.blockedURI);
+        }
+        events++;
+        if (events == 12)
+          t.done();
+    }));
+</script>
+<script>
+    t.unreached_func("No nonce, no execution.")();
+</script>
+<script nonce="xyz">
+    t.unreached_func("Bad nonce, no execution.")();
+</script>
+<script <script nonce="abc">
+    t.unreached_func("'<script' attribute, no execution.")();
+</script>
+<script attribute<script nonce="abc">
+    t.unreached_func("'attribute<script', no execution.")();
+</script>
+<script attribute=<script nonce="abc">
+    t.unreached_func("'<script' value, no execution.")();
+</script>
+<script attribute=value<script nonce="abc">
+    t.unreached_func("'value<script', no execution.")();
+</script>
+<script attribute="" attribute=<style nonce="abc">
+    t.unreached_func("Duplicate attribute, no execution.")();
+</script>
+<script src="../support/nonce-should-be-blocked.js?1" <script nonce="abc"></script>
+<script src="../support/nonce-should-be-blocked.js?2" attribute=<script nonce="abc"></script>
+<script src="../support/nonce-should-be-blocked.js?3" <style nonce="abc"></script>
+<script src="../support/nonce-should-be-blocked.js?4" attribute=<style nonce="abc"></script>
+<script src="../support/nonce-should-be-blocked.js?5" attribute=<style nonce="abc"></script>
diff --git a/content-security-policy/securitypolicyviolation/idl.html b/content-security-policy/securitypolicyviolation/idl.html
index 17f492e..1849abc 100644
--- a/content-security-policy/securitypolicyviolation/idl.html
+++ b/content-security-policy/securitypolicyviolation/idl.html
@@ -39,11 +39,16 @@
   };
 </script>
 <script>
-    var idl_array = new IdlArray();
+  promise_test(async function() {
+    const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+
+    const idl_array = new IdlArray();
     idl_array.add_untested_idls(document.querySelector('#untested').textContent);
+    idl_array.add_untested_idls(dom, { only: ['Event', 'EventInit'] });
     idl_array.add_idls(document.querySelector('#tested').textContent);
     idl_array.add_objects({
-      SecurityPolicyViolationEvent: ['new SecurityPolicyViolationEvent({})']      
+      SecurityPolicyViolationEvent: ['new SecurityPolicyViolationEvent({})']
     });
     idl_array.test();
+  })
 </script>
diff --git a/content-security-policy/style-src/style-src-imported-style-blocked.html b/content-security-policy/style-src/style-src-imported-style-blocked.html
index 1906611..a008880 100644
--- a/content-security-policy/style-src/style-src-imported-style-blocked.html
+++ b/content-security-policy/style-src/style-src-imported-style-blocked.html
@@ -12,8 +12,13 @@
       document.addEventListener("securitypolicyviolation", t_spv.step_func_done(function(e) {
         assert_equals("style-src", e.violatedDirective);
       }));
+
+      var l = document.createElement("link");
+      l.setAttribute("href", "/content-security-policy/style-src/resources/style-src-import.sub.css");
+      l.setAttribute("rel", "stylesheet");
+      l.setAttribute("type", "text/css");
+      document.head.appendChild(l);
     </script>
-    <link href="/content-security-policy/style-src/resources/style-src-import.sub.css" rel=stylesheet type=text/css>
 </head>
 <body>
     <div id='log'></div>
diff --git a/content-security-policy/support/checkReport.sub.js b/content-security-policy/support/checkReport.sub.js
index ba179d6..1ae7d09 100644
--- a/content-security-policy/support/checkReport.sub.js
+++ b/content-security-policy/support/checkReport.sub.js
@@ -13,29 +13,39 @@
   var reportValue  = "{{GET[reportValue]}}";
   var reportExists = "{{GET[reportExists]}}";
   var noCookies = "{{GET[noCookies]}}";
+  var reportCookieName = "{{GET[reportCookieName]}}"
+  var testName = "{{GET[testName]}}"
+  var cookiePresent = "{{GET[cookiePresent]}}"
+  var reportCount = "{{GET[reportCount]}}"
 
   var location = window.location;
-  var thisTestName = location.pathname.split('/')[location.pathname.split('/').length - 1].split('.')[0];
+  if (reportCookieName == "") {
+    // fallback on test file name if cookie name not specified
+    reportCookieName = location.pathname.split('/')[location.pathname.split('/').length - 1].split('.')[0];
+  }
 
-  var reportID = "";
+  var reportID = "{{GET[reportID]}}";
 
-  var cookies = document.cookie.split(';');
-  for (var i = 0; i < cookies.length; i++) {
-    var cookieName = cookies[i].split('=')[0].trim();
-    var cookieValue = cookies[i].split('=')[1].trim();
+  if (reportID == "") {
+    var cookies = document.cookie.split(';');
+    for (var i = 0; i < cookies.length; i++) {
+      var cookieName = cookies[i].split('=')[0].trim();
+      var cookieValue = cookies[i].split('=')[1].trim();
 
-    if (cookieName == thisTestName) {
-      reportID = cookieValue;
-      var cookieToDelete = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + document.location.pathname.substring(0, document.location.pathname.lastIndexOf('/') + 1);
-      document.cookie = cookieToDelete;
-      break;
+      if (cookieName == reportCookieName) {
+        reportID = cookieValue;
+        var cookieToDelete = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + document.location.pathname.substring(0, document.location.pathname.lastIndexOf('/') + 1);
+        document.cookie = cookieToDelete;
+        break;
+      }
     }
   }
 
   var timeout = document.querySelector("meta[name=timeout][content=long]") ? 50 : 5;
-  var reportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=take&timeout=" + timeout + "&reportID=" + reportID;
+  var reportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=retrieve_report&timeout=" + timeout + "&reportID=" + reportID;
 
-  var reportTest = async_test("Violation report status OK.");
+  if (testName == "") testName = "Violation report status OK.";
+  var reportTest = async_test(testName);
 
   function assert_field_value(field, value, field_name) {
     assert_true(field.indexOf(value.split(" ")[0]) != -1,
@@ -77,17 +87,38 @@
     report.send();
   });
 
-  if (noCookies) {
-      var cookieTest = async_test("No cookies sent with report.");
+  if (noCookies || cookiePresent) {
+      var cookieTest = async_test("Test report cookies.");
       var cookieReport = new XMLHttpRequest();
       cookieReport.onload = cookieTest.step_func(function () {
-          var data = JSON.parse(cookieReport.responseText);
-          assert_equals(data.reportCookies, "None");
-          cookieTest.done();
+        var data = JSON.parse(cookieReport.responseText);
+        if (noCookies) {
+          assert_equals(data.reportCookies, "None", "Report should not contain any cookies");
+        }
+
+        if (cookiePresent) {
+          assert_true(data.reportCookies.hasOwnProperty(cookiePresent), "Report should contain cookie: " + cookiePresent);
+        }
+        cookieTest.done();
       });
-      var cReportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=cookies&timeout=" + timeout + "&reportID=" + reportID;
+      var cReportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=retrieve_cookies&timeout=" + timeout + "&reportID=" + reportID;
       cookieReport.open("GET", cReportLocation, true);
       cookieReport.send();
-  };
+  }
+
+  if (reportCount != "") {
+      var reportCountTest = async_test("Test number of sent reports.");
+      var reportCountReport = new XMLHttpRequest();
+      reportCountReport.onload = reportCountTest.step_func(function () {
+        var data = JSON.parse(reportCountReport.responseText);
+
+        assert_equals(data.report_count, reportCount, "Report count was not what was expected.");
+
+        reportCountTest.done();
+      });
+      var cReportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=retrieve_count&timeout=" + timeout + "&reportID=" + reportID;
+      reportCountReport.open("GET", cReportLocation, true);
+      reportCountReport.send();
+  }
 
 })();
diff --git a/content-security-policy/support/dedicated-worker-helper.js b/content-security-policy/support/dedicated-worker-helper.js
new file mode 100644
index 0000000..c1ed208
--- /dev/null
+++ b/content-security-policy/support/dedicated-worker-helper.js
@@ -0,0 +1,2 @@
+var url = new URL("../support/ping.js", document.baseURI).toString();
+assert_worker_is_loaded(url, document.getElementById("foo").getAttribute("data-desc-fallback"));
\ No newline at end of file
diff --git a/content-security-policy/support/file-prefetch-allowed.html b/content-security-policy/support/file-prefetch-allowed.html
new file mode 100644
index 0000000..bd60d26
--- /dev/null
+++ b/content-security-policy/support/file-prefetch-allowed.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <!-- CSP directive 'prefetch-src' is not supported via meta tag though -->
+  <meta http-equiv="Content-Security-Policy" content="prefetch-src 'self'">
+</head>
+<body>
+</body>
+</html>
diff --git a/content-security-policy/support/inject-image.js b/content-security-policy/support/inject-image.js
index cc5b600..a10d50a 100644
--- a/content-security-policy/support/inject-image.js
+++ b/content-security-policy/support/inject-image.js
@@ -2,4 +2,3 @@
 var i = document.createElement('img');
 i.src = '/content-security-policy/support/fail.png';
 document.body.appendChild(i);
-log("TEST COMPLETE");
\ No newline at end of file
diff --git a/content-security-policy/support/nonce-should-be-blocked.js b/content-security-policy/support/nonce-should-be-blocked.js
new file mode 100644
index 0000000..501f7a9
--- /dev/null
+++ b/content-security-policy/support/nonce-should-be-blocked.js
@@ -0,0 +1 @@
+t.unreached_func(document.currentScript.getAttribute('src') + " should not execute.")();
diff --git a/content-security-policy/support/pass2.png b/content-security-policy/support/pass2.png
new file mode 100644
index 0000000..2fa1e0a
--- /dev/null
+++ b/content-security-policy/support/pass2.png
Binary files differ
diff --git a/content-security-policy/support/postmessage-pass-to-opener.html b/content-security-policy/support/postmessage-pass-to-opener.html
new file mode 100644
index 0000000..e1bdf71
--- /dev/null
+++ b/content-security-policy/support/postmessage-pass-to-opener.html
@@ -0,0 +1,3 @@
+<script>
+  window.top.opener.postMessage('PASS', '*');
+</script>
diff --git a/content-security-policy/support/prefetch-helper.js b/content-security-policy/support/prefetch-helper.js
new file mode 100644
index 0000000..db6d875
--- /dev/null
+++ b/content-security-policy/support/prefetch-helper.js
@@ -0,0 +1,65 @@
+test(t => {
+  assert_true(document.createElement('link').relList.supports('prefetch'));
+}, "Browser supports prefetch.");
+
+test(t => {
+  assert_true(!!window.PerformanceResourceTiming);
+}, "Browser supports performance APIs.");
+
+async function waitUntilResourceDownloaded(url) {
+  await new Promise((resolve, reject) => {
+    if (performance.getEntriesByName(url).length >= 1)
+      resolve();
+
+    let observer = new PerformanceObserver(list => {
+      list.getEntries().forEach(entry => {
+        if (entry.name == url) {
+          resolve();
+        }
+      });
+    });
+  });
+}
+
+async function assert_resource_not_downloaded(test, url) {
+  if (performance.getEntriesByName(url).length >= 1) {
+    (test.unreached_func(`'${url}' should not have downloaded.`))();
+  }
+}
+
+function assert_link_prefetches(test, link) {
+  assert_no_csp_event_for_url(test, link.href);
+
+  link.onerror = test.unreached_func('onerror should not fire.');
+
+  // Test is finished when either the `load` event fires, or we get a performance
+  // entry showing that the resource loaded successfully.
+  link.onload = test.step_func(test.step_func_done());
+  waitUntilResourceDownloaded(link.href).then(test.step_func_done());
+
+  document.head.appendChild(link);
+}
+
+function assert_link_does_not_prefetch(test, link) {
+  let cspEvent = false;
+  let errorEvent = false;
+
+  waitUntilCSPEventForURL(test, link.href)
+      .then(test.step_func(e => {
+        cspEvent = true;
+        assert_equals(e.violatedDirective, "prefetch-src");
+        assert_equals(e.effectiveDirective, "prefetch-src");
+
+        if (errorEvent)
+          test.done();
+      }));
+
+  link.onerror = test.step_func(e => {
+    errorEvent = true;
+    if (cspEvent)
+      test.done();
+  });
+  link.onload = test.unreached_func('onload should not fire.');
+
+  document.head.appendChild(link);
+}
diff --git a/content-security-policy/support/prefetch-subresource.css b/content-security-policy/support/prefetch-subresource.css
new file mode 100644
index 0000000..4c4fa46
--- /dev/null
+++ b/content-security-policy/support/prefetch-subresource.css
@@ -0,0 +1,3 @@
+/* This CSS file sends some headers:
+ *     Link: </content-security-policy/support/fail.png>;rel=prefetch
+ */
diff --git a/content-security-policy/support/prefetch-subresource.css.headers b/content-security-policy/support/prefetch-subresource.css.headers
new file mode 100644
index 0000000..eaf7b16
--- /dev/null
+++ b/content-security-policy/support/prefetch-subresource.css.headers
@@ -0,0 +1 @@
+Link: </content-security-policy/support/fail.png>;rel=prefetch
diff --git a/content-security-policy/support/report.py b/content-security-policy/support/report.py
index 193315f..3b249f3 100644
--- a/content-security-policy/support/report.py
+++ b/content-security-policy/support/report.py
@@ -2,33 +2,61 @@
 import json
 import re
 
+def retrieve_from_stash(request, key, timeout, default_value):
+  t0 = time.time()
+  while time.time() - t0 < timeout:
+    time.sleep(0.5)
+    value = request.server.stash.take(key=key)
+    if value is not None:
+      return value
+
+  return default_value
+
 def main(request, response):
-    op = request.GET.first("op");
-    key = request.GET.first("reportID")
+  op = request.GET.first("op");
+  key = request.GET.first("reportID")
+  cookie_key = re.sub('^....', 'cccc', key)
+  count_key = re.sub('^....', 'dddd', key)
 
-    if op == "take":
-        timeout = float(request.GET.first("timeout"))
-        t0 = time.time()
-        while time.time() - t0 < timeout:
-            time.sleep(0.5)
-            value = request.server.stash.take(key=key)
-            if value is not None:
-                return [("Content-Type", "application/json")], value
+  try:
+    timeout = request.GET.first("timeout")
+  except:
+    timeout = 0.5
+  timeout = float(timeout)
 
-        return [("Content-Type", "application/json")], json.dumps({'error': 'No such report.' , 'guid' : key})
+  if op == "retrieve_report":
+    return [("Content-Type", "application/json")], retrieve_from_stash(request, key, timeout, json.dumps({'error': 'No such report.' , 'guid' : key}))
 
-    if op == "cookies":
-        cval = request.server.stash.take(key=re.sub('^...', 'ccc', key))
-        if cval is None:
-            cval = "\"None\""
+  if op == "retrieve_cookies":
+    return [("Content-Type", "application/json")], "{ \"reportCookies\" : " + str(retrieve_from_stash(request, cookie_key, timeout, "\"None\"")) + "}"
 
-        return [("Content-Type", "application/json")], "{ \"reportCookies\" : " + cval + "}"
+  if op == "retrieve_count":
+    return [("Content-Type", "application/json")], json.dumps({'report_count': str(retrieve_from_stash(request, count_key, timeout, 0))})
 
-    if hasattr(request, 'Cookies'):
-        request.server.stash.put(key=re.sub('^...', 'ccc', key), value=request.Cookies)
+  # save cookies
+  if hasattr(request, 'cookies') and len(request.cookies.keys()) > 0:
+    # convert everything into strings and dump it into a dict so it can be jsoned
+    temp_cookies_dict = {}
+    for dict_key in request.cookies.keys():
+      temp_cookies_dict[str(dict_key)] = str(request.cookies.get_list(dict_key))
+    with request.server.stash.lock:
+      request.server.stash.take(key=cookie_key)
+      request.server.stash.put(key=cookie_key, value=json.dumps(temp_cookies_dict))
 
-    report = request.body
-    report.rstrip()
+  # save latest report
+  report = request.body
+  report.rstrip()
+  with request.server.stash.lock:
     request.server.stash.take(key=key)
     request.server.stash.put(key=key, value=report)
-    return [("Content-Type", "text/plain")], "Recorded report " + report
+
+  with request.server.stash.lock:
+    # increment report count
+    count = request.server.stash.take(key=count_key)
+    if count is None:
+      count = 0
+    count += 1
+    request.server.stash.put(key=count_key, value=count)
+
+  # return acknowledgement report
+  return [("Content-Type", "text/plain")], "Recorded report " + report
diff --git a/content-security-policy/support/service-worker-helper.js b/content-security-policy/support/service-worker-helper.js
new file mode 100644
index 0000000..4e8f8b5
--- /dev/null
+++ b/content-security-policy/support/service-worker-helper.js
@@ -0,0 +1,2 @@
+var url = new URL("../support/ping.js", document.baseURI).toString();
+assert_service_worker_is_loaded(url, document.getElementById("foo").getAttribute("data-desc-fallback"));
\ No newline at end of file
diff --git a/content-security-policy/support/shared-worker-helper.js b/content-security-policy/support/shared-worker-helper.js
new file mode 100644
index 0000000..d0637ec
--- /dev/null
+++ b/content-security-policy/support/shared-worker-helper.js
@@ -0,0 +1,2 @@
+var url = new URL("../support/ping.js", document.baseURI).toString();
+assert_shared_worker_is_loaded(url, document.getElementById("foo").getAttribute("data-desc-fallback"));
\ No newline at end of file
diff --git a/content-security-policy/svg/including.sub.svg b/content-security-policy/svg/including.sub.svg
index 99b416b..51215d9 100644
--- a/content-security-policy/svg/including.sub.svg
+++ b/content-security-policy/svg/including.sub.svg
@@ -2,7 +2,8 @@
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
   "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="6cm" height="5cm" viewBox="0 0 600 500"
-     xmlns="http://www.w3.org/2000/svg" version="1.1">
+     xmlns="http://www.w3.org/2000/svg" version="1.1"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
   <desc>using SVG as a resource doc should apply this doc's CSP</desc>
 
   <use xlink:href="scripted.svg#postmessagescript" />
diff --git a/content-security-policy/worker-src/dedicated-worker-src-child-fallback.sub.html b/content-security-policy/worker-src/dedicated-worker-src-child-fallback.sub.html
new file mode 100644
index 0000000..9c37dfb
--- /dev/null
+++ b/content-security-policy/worker-src/dedicated-worker-src-child-fallback.sub.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for dedicated worker allowed by child-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<!-- Ideally we would use "script-src 'none'" alone but we have to whitelist the actual script that spawns the workers, hence the nonce.-->
+<meta http-equiv="Content-Security-Policy" content="child-src 'self'; script-src 'none' 'nonce-foo'; default-src 'none'; ">
+<script src="../support/dedicated-worker-helper.js" nonce="foo" id="foo" data-desc-fallback="Same-origin dedicated worker allowed by child-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/dedicated-worker-src-default-fallback.sub.html b/content-security-policy/worker-src/dedicated-worker-src-default-fallback.sub.html
new file mode 100644
index 0000000..5bded3f
--- /dev/null
+++ b/content-security-policy/worker-src/dedicated-worker-src-default-fallback.sub.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for dedicated worker allowed by default-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
+<script src="../support/dedicated-worker-helper.js" id="foo" data-desc-fallback="Same-origin dedicated worker allowed by default-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/dedicated-worker-src-script-fallback.sub.html b/content-security-policy/worker-src/dedicated-worker-src-script-fallback.sub.html
new file mode 100644
index 0000000..ca92207
--- /dev/null
+++ b/content-security-policy/worker-src/dedicated-worker-src-script-fallback.sub.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for dedicated worker allowed by script-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self'; default-src 'none'; ">
+<script src="../support/dedicated-worker-helper.js" id="foo" data-desc-fallback="Same-origin dedicated worker allowed by script-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/dedicated-worker-src-self-fallback.sub.html b/content-security-policy/worker-src/dedicated-worker-src-self-fallback.sub.html
new file mode 100644
index 0000000..06e79db
--- /dev/null
+++ b/content-security-policy/worker-src/dedicated-worker-src-self-fallback.sub.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for dedicated worker allowed by worker-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<!-- Ideally we would use "script-src 'none'" alone but we have to whitelist the actual script that spawns the workers, hence the nonce.-->
+<meta http-equiv="Content-Security-Policy" content="worker-src 'self'; child-src 'none'; script-src 'none' 'nonce-foo'; default-src 'none'; ">
+<script src="../support/dedicated-worker-helper.js" nonce="foo" id="foo" data-desc-fallback="Same-origin dedicated worker allowed by worker-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/service-worker-src-child-fallback.https.sub.html b/content-security-policy/worker-src/service-worker-src-child-fallback.https.sub.html
new file mode 100644
index 0000000..0053b10
--- /dev/null
+++ b/content-security-policy/worker-src/service-worker-src-child-fallback.https.sub.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for service worker allowed by child-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<!-- Ideally we would use "script-src 'none'" alone but we have to whitelist the actual script that spawns the workers, hence the nonce.-->
+<meta http-equiv="Content-Security-Policy" content="child-src 'self'; script-src 'none' 'nonce-foo'; default-src 'none'; ">
+<script src="../support/service-worker-helper.js" nonce="foo" id="foo" data-desc-fallback="Same-origin service worker allowed by child-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/service-worker-src-default-fallback.https.sub.html b/content-security-policy/worker-src/service-worker-src-default-fallback.https.sub.html
new file mode 100644
index 0000000..f9df743
--- /dev/null
+++ b/content-security-policy/worker-src/service-worker-src-default-fallback.https.sub.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for service worker allowed by default-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
+<script src="../support/service-worker-helper.js" id="foo" data-desc-fallback="Same-origin service worker allowed by default-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/service-worker-src-script-fallback.https.sub.html b/content-security-policy/worker-src/service-worker-src-script-fallback.https.sub.html
new file mode 100644
index 0000000..ce03f24
--- /dev/null
+++ b/content-security-policy/worker-src/service-worker-src-script-fallback.https.sub.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for service worker allowed by script-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self'; default-src 'none'; ">
+<script src="../support/service-worker-helper.js" id="foo" data-desc-fallback="Same-origin service worker allowed by script-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/service-worker-src-self-fallback.https.sub.html b/content-security-policy/worker-src/service-worker-src-self-fallback.https.sub.html
new file mode 100644
index 0000000..58bc8cd
--- /dev/null
+++ b/content-security-policy/worker-src/service-worker-src-self-fallback.https.sub.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for service worker allowed by worker-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<!-- Ideally we would use "script-src 'none'" alone but we have to whitelist the actual script that spawns the workers, hence the nonce.-->
+<meta http-equiv="Content-Security-Policy" content="worker-src 'self'; child-src 'none'; script-src 'none' 'nonce-foo'; default-src 'none'; ">
+<script src="../support/service-worker-helper.js" nonce="foo" id="foo" data-desc-fallback="Same-origin service worker allowed by worker-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/shared-worker-src-child-fallback.sub.html b/content-security-policy/worker-src/shared-worker-src-child-fallback.sub.html
new file mode 100644
index 0000000..5351085
--- /dev/null
+++ b/content-security-policy/worker-src/shared-worker-src-child-fallback.sub.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for shared worker allowed by child-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<!-- Ideally we would use "script-src 'none'" alone but we have to whitelist the actual script that spawns the workers, hence the nonce.-->
+<meta http-equiv="Content-Security-Policy" content="child-src 'self'; script-src 'none' 'nonce-foo'; default-src 'none'; ">
+<script src="../support/shared-worker-helper.js" nonce="foo" id="foo" data-desc-fallback="Same-origin shared worker allowed by child-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/shared-worker-src-default-fallback.sub.html b/content-security-policy/worker-src/shared-worker-src-default-fallback.sub.html
new file mode 100644
index 0000000..4a07db7
--- /dev/null
+++ b/content-security-policy/worker-src/shared-worker-src-default-fallback.sub.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for shared worker allowed by default-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
+<script src="../support/shared-worker-helper.js" id="foo" data-desc-fallback="Same-origin shared worker allowed by default-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/shared-worker-src-script-fallback.sub.html b/content-security-policy/worker-src/shared-worker-src-script-fallback.sub.html
new file mode 100644
index 0000000..0a854da
--- /dev/null
+++ b/content-security-policy/worker-src/shared-worker-src-script-fallback.sub.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for shared worker allowed by script-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self'; default-src 'none'; ">
+<script src="../support/shared-worker-helper.js" id="foo" data-desc-fallback="Same-origin shared worker allowed by script-src 'self'."></script>
\ No newline at end of file
diff --git a/content-security-policy/worker-src/shared-worker-src-self-fallback.sub.html b/content-security-policy/worker-src/shared-worker-src-self-fallback.sub.html
new file mode 100644
index 0000000..353a3a0
--- /dev/null
+++ b/content-security-policy/worker-src/shared-worker-src-self-fallback.sub.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Web platform test for shared worker allowed by worker-src self</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../support/testharness-helper.js"></script>
+<!-- Ideally we would use "script-src 'none'" alone but we have to whitelist the actual script that spawns the workers, hence the nonce.-->
+<meta http-equiv="Content-Security-Policy" content="worker-src 'self'; child-src 'none'; script-src 'none' 'nonce-foo'; default-src 'none'; ">
+<script src="../support/shared-worker-helper.js" nonce="foo" id="foo" data-desc-fallback="Same-origin shared worker allowed by worker-src 'self'."></script>
\ No newline at end of file
diff --git a/cookie-store/README.md b/cookie-store/README.md
index 4ba55f7..c406dcc 100644
--- a/cookie-store/README.md
+++ b/cookie-store/README.md
@@ -1,2 +1,28 @@
 This directory contains tests for the
 [Async Cookies API](https://github.com/WICG/cookie-store).
+
+## Note on cookie naming conventions
+
+A simple origin cookie is a cookie named with the `__Host-` prefix
+which is always secure-flagged, always implicit-domain, always
+`/`-scoped, and hence always unambiguous in the cookie jar serialization
+and origin-scoped. It can be treated as a simple key/value pair.
+
+`"LEGACY"` in a cookie name here means it is an old-style unprefixed
+cookie name, so you can't tell e.g. whether it is Secure-flagged or
+`/`-pathed just by looking at it, and its flags, domain and path may
+vary even in a single cookie jar serialization leading to apparent
+duplicate entries, ambiguities, and complexity (i.e. it cannot be
+treated as a simple key/value pair.)
+
+Cookie names used in the tests are intended to be
+realistic. Traditional session cookie names are typically
+all-upper-case for broad framework compatibility. The more modern
+`"__Host-"` prefix has only one allowed casing. An expected upgrade
+path from traditional "legacy" cookie names to simple origin cookie
+names is simply to prefix the traditional name with the `"__Host-"`
+prefix.
+
+Many of the used cookie names are non-ASCII to ensure
+straightforward internationalization is possible at every API surface.
+These work in many modern browsers, though not yet all of them.
diff --git a/cookie-store/cookieStore_delete_arguments.tentative.html b/cookie-store/cookieStore_delete_arguments.tentative.html
deleted file mode 100644
index 962f13a..0000000
--- a/cookie-store/cookieStore_delete_arguments.tentative.html
+++ /dev/null
@@ -1,181 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.delete() arguments and options</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await cookieStore.delete('cookie-name');
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-}, 'cookieStore.delete with positional name');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await cookieStore.delete({ name: 'cookie-name' });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.delete with name in options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
-      'cookie-name', { name: 'cookie-name' }));
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.delete with name in both positional arguments and options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
-      'cookie-name', { value: 'cookie-value' }));
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.delete with value in options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
-  const tenYearsAgo = Date.now() - tenYears;
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
-      'cookie-name', { expires: tenYearsAgo }));
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.delete with expires in options');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentDomain = currentUrl.hostname;
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { domain: currentDomain });
-
-  await cookieStore.delete('cookie-name', { domain: currentDomain });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { domain: currentDomain })
-  });
-}, 'cookieStore.delete with domain set to the current hostname');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentDomain = currentUrl.hostname;
-  const subDomain = `sub.${currentDomain}`;
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { domain: currentDomain });
-
-  await cookieStore.delete('cookie-name', { domain: subDomain });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { domain: currentDomain })
-  });
-}, 'cookieStore.delete with domain set to a subdomain of the current hostname');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentDomain = currentUrl.hostname;
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { domain: currentDomain });
-
-  await cookieStore.delete({ name: 'cookie-name', domain: currentDomain });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { domain: currentDomain })
-  });
-}, 'cookieStore.delete with name in options and domain set to the current ' +
-   'hostname');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentDomain = currentUrl.hostname;
-  const subDomain = `sub.${currentDomain}`;
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { domain: currentDomain });
-
-  await cookieStore.delete({ name: 'cookie-name', domain: subDomain });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { domain: currentDomain })
-  });
-}, 'cookieStore.delete with name in options and domain set to a subdomain of ' +
-   'the current hostname');
-
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentPath = currentUrl.pathname;
-  const currentDirectory =
-      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { path: currentDirectory });
-
-  await cookieStore.delete('cookie-name', { path: currentDirectory });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { path: currentDirectory })
-  });
-}, 'cookieStore.delete with path set to the current directory');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentPath = currentUrl.pathname;
-  const currentDirectory =
-      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
-  const subDirectory = currentDirectory + "subdir/";
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { path: currentDirectory });
-
-  await cookieStore.delete('cookie-name', { path: subDirectory });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { path: currentDirectory })
-  });
-}, 'cookieStore.delete with path set to subdirectory of the current directory');
-
-</script>
diff --git a/cookie-store/cookieStore_delete_arguments.tentative.window.js b/cookie-store/cookieStore_delete_arguments.tentative.window.js
new file mode 100644
index 0000000..8f3f6f5
--- /dev/null
+++ b/cookie-store/cookieStore_delete_arguments.tentative.window.js
@@ -0,0 +1,171 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await cookieStore.delete('cookie-name');
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+}, 'cookieStore.delete with positional name');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await cookieStore.delete({ name: 'cookie-name' });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.delete with name in options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
+      'cookie-name', { name: 'cookie-name' }));
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.delete with name in both positional arguments and options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
+      'cookie-name', { value: 'cookie-value' }));
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.delete with value in options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
+  const tenYearsAgo = Date.now() - tenYears;
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
+      'cookie-name', { expires: tenYearsAgo }));
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.delete with expires in options');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentDomain = currentUrl.hostname;
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { domain: currentDomain });
+
+  await cookieStore.delete('cookie-name', { domain: currentDomain });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { domain: currentDomain })
+  });
+}, 'cookieStore.delete with domain set to the current hostname');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentDomain = currentUrl.hostname;
+  const subDomain = `sub.${currentDomain}`;
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { domain: currentDomain });
+
+  await cookieStore.delete('cookie-name', { domain: subDomain });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { domain: currentDomain })
+  });
+}, 'cookieStore.delete with domain set to a subdomain of the current hostname');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentDomain = currentUrl.hostname;
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { domain: currentDomain });
+
+  await cookieStore.delete({ name: 'cookie-name', domain: currentDomain });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { domain: currentDomain })
+  });
+}, 'cookieStore.delete with name in options and domain set to the current ' +
+   'hostname');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentDomain = currentUrl.hostname;
+  const subDomain = `sub.${currentDomain}`;
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { domain: currentDomain });
+
+  await cookieStore.delete({ name: 'cookie-name', domain: subDomain });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { domain: currentDomain })
+  });
+}, 'cookieStore.delete with name in options and domain set to a subdomain of ' +
+   'the current hostname');
+
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentPath = currentUrl.pathname;
+  const currentDirectory =
+      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { path: currentDirectory });
+
+  await cookieStore.delete('cookie-name', { path: currentDirectory });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { path: currentDirectory })
+  });
+}, 'cookieStore.delete with path set to the current directory');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentPath = currentUrl.pathname;
+  const currentDirectory =
+      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
+  const subDirectory = currentDirectory + "subdir/";
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { path: currentDirectory });
+
+  await cookieStore.delete('cookie-name', { path: subDirectory });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { path: currentDirectory })
+  });
+}, 'cookieStore.delete with path set to subdirectory of the current directory');
diff --git a/cookie-store/cookieStore_delete_basic.tentative.window.js b/cookie-store/cookieStore_delete_basic.tentative.window.js
new file mode 100644
index 0000000..8a3f7ed
--- /dev/null
+++ b/cookie-store/cookieStore_delete_basic.tentative.window.js
@@ -0,0 +1,10 @@
+'use strict';
+
+promise_test(async testCase => {
+  const p = cookieStore.delete('cookie-name');
+  assert_true(p instanceof Promise,
+              'cookieStore.delete() returns a promise');
+  const result = await p;
+  assert_equals(result, undefined,
+                'cookieStore.delete() promise resolves to undefined');
+}, 'cookieStore.delete return type is Promise<void>');
diff --git a/cookie-store/cookieStore_event_arguments.tenative.window.js b/cookie-store/cookieStore_event_arguments.tenative.window.js
new file mode 100644
index 0000000..bcb698e
--- /dev/null
+++ b/cookie-store/cookieStore_event_arguments.tenative.window.js
@@ -0,0 +1,65 @@
+'use strict';
+
+test(() => {
+  const event = new CookieChangeEvent('change');
+  assert_true(event instanceof CookieChangeEvent);
+  assert_equals(event.type, 'change');
+  assert_equals(event.changed.length, 0);
+  assert_equals(event.deleted.length, 0);
+}, 'CookieChangeEvent construction with default arguments');
+
+test(() => {
+  const event = new CookieChangeEvent('change', {
+    changed: [
+      { name: 'changed-name1', value: 'changed-value1' },
+      { name: 'changed-name2', value: 'changed-value2' },
+    ],
+  });
+  assert_true(event instanceof CookieChangeEvent);
+  assert_equals(event.type, 'change');
+  assert_equals(event.changed.length, 2);
+  assert_equals(event.changed[0].name, 'changed-name1');
+  assert_equals(event.changed[0].value, 'changed-value1');
+  assert_equals(event.changed[1].name, 'changed-name2');
+  assert_equals(event.changed[1].value, 'changed-value2');
+  assert_equals(event.deleted.length, 0);
+}, 'CookieChangeEvent construction with changed cookie list');
+
+test(() => {
+  const event = new CookieChangeEvent('change', {
+    deleted: [
+      { name: 'deleted-name1', value: 'deleted-value1' },
+      { name: 'deleted-name2', value: 'deleted-value2' },
+    ],
+  });
+  assert_true(event instanceof CookieChangeEvent);
+  assert_equals(event.type, 'change');
+  assert_equals(event.changed.length, 0);
+  assert_equals(event.deleted.length, 2);
+  assert_equals(event.deleted[0].name, 'deleted-name1');
+  assert_equals(event.deleted[0].value, 'deleted-value1');
+  assert_equals(event.deleted[1].name, 'deleted-name2');
+  assert_equals(event.deleted[1].value, 'deleted-value2');
+}, 'CookieChangeEvent construction with deleted cookie list');
+
+test(() => {
+  const event = new CookieChangeEvent('change', {
+    changed: [
+      { name: 'changed-name1', value: 'changed-value1' },
+      { name: 'changed-name2', value: 'changed-value2' },
+    ],
+    deleted: [
+      { name: 'deleted-name1', value: 'deleted-value1' },
+    ],
+  });
+  assert_true(event instanceof CookieChangeEvent);
+  assert_equals(event.type, 'change');
+  assert_equals(event.changed.length, 2);
+  assert_equals(event.changed[0].name, 'changed-name1');
+  assert_equals(event.changed[0].value, 'changed-value1');
+  assert_equals(event.changed[1].name, 'changed-name2');
+  assert_equals(event.changed[1].value, 'changed-value2');
+  assert_equals(event.deleted.length, 1);
+  assert_equals(event.deleted[0].name, 'deleted-name1');
+  assert_equals(event.deleted[0].value, 'deleted-value1');
+}, 'CookieChangeEvent construction with changed and deleted cookie lists');
\ No newline at end of file
diff --git a/cookie-store/cookieStore_event_basic.tentative.window.js b/cookie-store/cookieStore_event_basic.tentative.window.js
new file mode 100644
index 0000000..306fc1e
--- /dev/null
+++ b/cookie-store/cookieStore_event_basic.tentative.window.js
@@ -0,0 +1,29 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  const eventPromise = new Promise((resolve) => {
+    cookieStore.onchange = resolve;
+  });
+
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const event = await eventPromise;
+  assert_true(event instanceof CookieChangeEvent);
+  assert_equals(event.type, 'change');
+  assert_equals(event.changed.length, 1);
+  assert_equals(event.changed[0].name, 'cookie-name');
+  assert_equals(event.changed[0].value, 'cookie-value');
+  assert_equals(event.deleted.length, 0);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore fires change event for cookie set by cookieStore.set()');
diff --git a/cookie-store/cookieStore_event_delete.tenative.window.js b/cookie-store/cookieStore_event_delete.tenative.window.js
new file mode 100644
index 0000000..de54449
--- /dev/null
+++ b/cookie-store/cookieStore_event_delete.tenative.window.js
@@ -0,0 +1,31 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const eventPromise = new Promise((resolve) => {
+    cookieStore.onchange = resolve;
+  });
+  await cookieStore.delete('cookie-name');
+  const event = await eventPromise;
+  assert_true(event instanceof CookieChangeEvent);
+  assert_equals(event.type, 'change');
+  assert_equals(event.deleted.length, 1);
+  assert_equals(event.deleted[0].name, 'cookie-name');
+  assert_equals(
+      event.deleted[0].value, undefined,
+      'Cookie change events for deletions should not have cookie values');
+  assert_equals(event.changed.length, 0);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore fires change event for cookie deleted by cookieStore.delete()');
\ No newline at end of file
diff --git a/cookie-store/cookieStore_event_overwrite.tentative.window.js b/cookie-store/cookieStore_event_overwrite.tentative.window.js
new file mode 100644
index 0000000..8e8c9c1
--- /dev/null
+++ b/cookie-store/cookieStore_event_overwrite.tentative.window.js
@@ -0,0 +1,31 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const eventPromise = new Promise((resolve) => {
+    cookieStore.onchange = resolve;
+  });
+
+  await cookieStore.set('cookie-name', 'new-cookie-value');
+
+  const event = await eventPromise;
+  assert_true(event instanceof CookieChangeEvent);
+  assert_equals(event.type, 'change');
+  assert_equals(event.changed.length, 1);
+  assert_equals(event.changed[0].name, 'cookie-name');
+  assert_equals(event.changed[0].value, 'new-cookie-value');
+  assert_equals(event.deleted.length, 0);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore fires change event for cookie overwritten by cookieStore.set()');
diff --git a/cookie-store/cookieStore_getAll_arguments.tentative.html b/cookie-store/cookieStore_getAll_arguments.tentative.html
deleted file mode 100644
index 3066a99..0000000
--- a/cookie-store/cookieStore_getAll_arguments.tentative.html
+++ /dev/null
@@ -1,129 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.getAll() arguments and options</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.set('cookie-name-2', 'cookie-value-2');
-
-  const cookies = await cookieStore.getAll();
-  cookies.sort((a, b) => a.name.localeCompare(b.name));
-  assert_equals(cookies.length, 2);
-  assert_equals(cookies[0].name, 'cookie-name');
-  assert_equals(cookies[0].value, 'cookie-value');
-  assert_equals(cookies[1].name, 'cookie-name-2');
-  assert_equals(cookies[1].value, 'cookie-value-2');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
-}, 'cookieStore.getAll with no arguments');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.set('cookie-name-2', 'cookie-value-2');
-
-  const cookies = await cookieStore.getAll('cookie-name');
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name');
-  assert_equals(cookies[0].value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
-}, 'cookieStore.getAll with positional name');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.set('cookie-name-2', 'cookie-value-2');
-
-  const cookies = await cookieStore.getAll({ name: 'cookie-name' });
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name');
-  assert_equals(cookies[0].value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
-}, 'cookieStore.getAll with name in options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.set('cookie-name-2', 'cookie-value-2');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.get(
-      'cookie-name', { name: 'cookie-name' }));
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
-}, 'cookieStore.getAll with name in both positional arguments and options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const cookies = await cookieStore.getAll(
-      'cookie-name', { matchType: 'equals' });
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name');
-  assert_equals(cookies[0].value, 'cookie-value');
-
-  const no_cookies = await cookieStore.getAll(
-      'cookie-na', { matchType: 'equals' });
-  assert_equals(no_cookies.length, 0);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.getAll with matchType explicitly set to equals');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.set('cookie-name-2', 'cookie-value-2');
-
-  const cookies = await cookieStore.getAll(
-      'cookie-name-', { matchType: 'startsWith' });
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name-2');
-  assert_equals(cookies[0].value, 'cookie-value-2');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
-}, 'cookieStore.getAll with matchType set to startsWith');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.set('cookie-name-2', 'cookie-value-2');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.getAll(
-      'cookie-name', { matchType: 'invalid' }));
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
-}, 'cookieStore.getAll with invalid matchType');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.set('cookie-name-2', 'cookie-value-2');
-
-  const cookies = await cookieStore.getAll(
-      { matchType: 'startsWith', name: 'cookie-name-' });
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name-2');
-  assert_equals(cookies[0].value, 'cookie-value-2');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
-}, 'cookieStore.getAll with matchType set to startsWith and name in options');
-
-</script>
diff --git a/cookie-store/cookieStore_getAll_arguments.tentative.window.js b/cookie-store/cookieStore_getAll_arguments.tentative.window.js
new file mode 100644
index 0000000..650d6eb
--- /dev/null
+++ b/cookie-store/cookieStore_getAll_arguments.tentative.window.js
@@ -0,0 +1,119 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.set('cookie-name-2', 'cookie-value-2');
+
+  const cookies = await cookieStore.getAll();
+  cookies.sort((a, b) => a.name.localeCompare(b.name));
+  assert_equals(cookies.length, 2);
+  assert_equals(cookies[0].name, 'cookie-name');
+  assert_equals(cookies[0].value, 'cookie-value');
+  assert_equals(cookies[1].name, 'cookie-name-2');
+  assert_equals(cookies[1].value, 'cookie-value-2');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
+}, 'cookieStore.getAll with no arguments');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.set('cookie-name-2', 'cookie-value-2');
+
+  const cookies = await cookieStore.getAll('cookie-name');
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name');
+  assert_equals(cookies[0].value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
+}, 'cookieStore.getAll with positional name');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.set('cookie-name-2', 'cookie-value-2');
+
+  const cookies = await cookieStore.getAll({ name: 'cookie-name' });
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name');
+  assert_equals(cookies[0].value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
+}, 'cookieStore.getAll with name in options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.set('cookie-name-2', 'cookie-value-2');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.get(
+      'cookie-name', { name: 'cookie-name' }));
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
+}, 'cookieStore.getAll with name in both positional arguments and options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const cookies = await cookieStore.getAll(
+      'cookie-name', { matchType: 'equals' });
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name');
+  assert_equals(cookies[0].value, 'cookie-value');
+
+  const no_cookies = await cookieStore.getAll(
+      'cookie-na', { matchType: 'equals' });
+  assert_equals(no_cookies.length, 0);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.getAll with matchType explicitly set to equals');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.set('cookie-name-2', 'cookie-value-2');
+
+  const cookies = await cookieStore.getAll(
+      'cookie-name-', { matchType: 'startsWith' });
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name-2');
+  assert_equals(cookies[0].value, 'cookie-value-2');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
+}, 'cookieStore.getAll with matchType set to startsWith');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.set('cookie-name-2', 'cookie-value-2');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.getAll(
+      'cookie-name', { matchType: 'invalid' }));
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
+}, 'cookieStore.getAll with invalid matchType');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.set('cookie-name-2', 'cookie-value-2');
+
+  const cookies = await cookieStore.getAll(
+      { matchType: 'startsWith', name: 'cookie-name-' });
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name-2');
+  assert_equals(cookies[0].value, 'cookie-value-2');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name-2'));
+}, 'cookieStore.getAll with matchType set to startsWith and name in options');
diff --git a/cookie-store/cookieStore_getAll_set_basic.tentative.window.js b/cookie-store/cookieStore_getAll_set_basic.tentative.window.js
new file mode 100644
index 0000000..a70c119
--- /dev/null
+++ b/cookie-store/cookieStore_getAll_set_basic.tentative.window.js
@@ -0,0 +1,22 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  const cookies = await cookieStore.getAll('cookie-name');
+
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name');
+  assert_equals(cookies[0].value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.getAll returns the cookie written by cookieStore.set');
diff --git a/cookie-store/cookieStore_get_arguments.tentative.html b/cookie-store/cookieStore_get_arguments.tentative.html
deleted file mode 100644
index 469cf4b..0000000
--- a/cookie-store/cookieStore_get_arguments.tentative.html
+++ /dev/null
@@ -1,96 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.get() arguments and options</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get with positional name');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const cookie = await cookieStore.get({ name: 'cookie-name' });
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get with name in options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.get(
-      'cookie-name', { name: 'cookie-name' }));
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get with name in both positional arguments and options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const cookie = await cookieStore.get(
-      'cookie-name', { matchType: 'equals' });
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  const no_cookie = await cookieStore.get(
-      'cookie-na', { matchType: 'equals' });
-  assert_equals(no_cookie, null);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get with matchType explicitly set to equals');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const cookie = await cookieStore.get(
-      'cookie-na', { matchType: 'startsWith' });
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get with matchType set to startsWith');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.get(
-      'cookie-name', { matchType: 'invalid' }));
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get with invalid matchType');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const cookie = await cookieStore.get(
-      { matchType: 'startsWith', name: 'cookie-na' });
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get with matchType set to startsWith and name in options');
-
-</script>
diff --git a/cookie-store/cookieStore_get_arguments.tentative.window.js b/cookie-store/cookieStore_get_arguments.tentative.window.js
new file mode 100644
index 0000000..f50bb51
--- /dev/null
+++ b/cookie-store/cookieStore_get_arguments.tentative.window.js
@@ -0,0 +1,86 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get with positional name');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const cookie = await cookieStore.get({ name: 'cookie-name' });
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get with name in options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.get(
+      'cookie-name', { name: 'cookie-name' }));
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get with name in both positional arguments and options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const cookie = await cookieStore.get(
+      'cookie-name', { matchType: 'equals' });
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  const no_cookie = await cookieStore.get(
+      'cookie-na', { matchType: 'equals' });
+  assert_equals(no_cookie, null);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get with matchType explicitly set to equals');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const cookie = await cookieStore.get(
+      'cookie-na', { matchType: 'startsWith' });
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get with matchType set to startsWith');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.get(
+      'cookie-name', { matchType: 'invalid' }));
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get with invalid matchType');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const cookie = await cookieStore.get(
+      { matchType: 'startsWith', name: 'cookie-na' });
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get with matchType set to startsWith and name in options');
diff --git a/cookie-store/cookieStore_get_delete_basic.tentative.window.js b/cookie-store/cookieStore_get_delete_basic.tentative.window.js
new file mode 100644
index 0000000..e039d81
--- /dev/null
+++ b/cookie-store/cookieStore_get_delete_basic.tentative.window.js
@@ -0,0 +1,20 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.delete('cookie-name');
+  const cookie = await cookieStore.get();
+  assert_equals(cookie, null);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get returns null for a cookie deleted by cookieStore.delete');
\ No newline at end of file
diff --git a/cookie-store/cookieStore_get_set_basic.tentative.window.js b/cookie-store/cookieStore_get_set_basic.tentative.window.js
new file mode 100644
index 0000000..1fa834a
--- /dev/null
+++ b/cookie-store/cookieStore_get_set_basic.tentative.window.js
@@ -0,0 +1,21 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  const cookie = await cookieStore.get('cookie-name');
+
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.get returns the cookie written by cookieStore.set');
diff --git a/cookie-store/cookieStore_has_arguments.tentative.html b/cookie-store/cookieStore_has_arguments.tentative.html
deleted file mode 100644
index c5d8d0b..0000000
--- a/cookie-store/cookieStore_has_arguments.tentative.html
+++ /dev/null
@@ -1,102 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.has() arguments and options</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.delete('cookie-name-2');
-
-  const has_cookie = await cookieStore.has('cookie-name');
-  assert_equals(has_cookie, true);
-  const has_cookie2 = await cookieStore.has('cookie-name-2');
-  assert_equals(has_cookie2, false);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has with positional name');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.delete('cookie-name-2');
-
-  const has_cookie = await cookieStore.has({ name: 'cookie-name' });
-  assert_equals(has_cookie, true);
-  const has_cookie2 = await cookieStore.has({ name: 'cookie-name-2' });
-  assert_equals(has_cookie2, false);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has with name in options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.has(
-      'cookie-name', { name: 'cookie-name' }));
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has with name in both positional arguments and options');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const has_cookie = await cookieStore.has(
-      'cookie-na', { matchType: 'equals' });
-  assert_equals(has_cookie, false);
-  const has_cookie2 = await cookieStore.has(
-      'cookie-name', { matchType: 'equals' });
-  assert_equals(has_cookie2, true);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has with matchType explicitly set to equals');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const has_cookie = await cookieStore.has(
-      'cookie-na', { matchType: 'startsWith' });
-  assert_equals(has_cookie, true);
-  const has_cookie2 = await cookieStore.has(
-      'cookie-name-', { matchType: 'startsWith' });
-  assert_equals(has_cookie2, false);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has with matchType set to startsWith');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.has(
-      'cookie-name', { matchType: 'invalid' }));
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has with invalid matchType');
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const has_cookie = await cookieStore.has(
-      { matchType: 'startsWith', name: 'cookie-na' });
-  assert_equals(has_cookie, true);
-  const has_cookie2 = await cookieStore.has(
-      { matchType: 'startsWith', name: 'cookie-name-' });
-  assert_equals(has_cookie2, false);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has with matchType set to startsWith and name in options');
-
-</script>
diff --git a/cookie-store/cookieStore_has_arguments.tentative.window.js b/cookie-store/cookieStore_has_arguments.tentative.window.js
new file mode 100644
index 0000000..443341e
--- /dev/null
+++ b/cookie-store/cookieStore_has_arguments.tentative.window.js
@@ -0,0 +1,92 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.delete('cookie-name-2');
+
+  const has_cookie = await cookieStore.has('cookie-name');
+  assert_equals(has_cookie, true);
+  const has_cookie2 = await cookieStore.has('cookie-name-2');
+  assert_equals(has_cookie2, false);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has with positional name');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  await cookieStore.delete('cookie-name-2');
+
+  const has_cookie = await cookieStore.has({ name: 'cookie-name' });
+  assert_equals(has_cookie, true);
+  const has_cookie2 = await cookieStore.has({ name: 'cookie-name-2' });
+  assert_equals(has_cookie2, false);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has with name in options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.has(
+      'cookie-name', { name: 'cookie-name' }));
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has with name in both positional arguments and options');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const has_cookie = await cookieStore.has(
+      'cookie-na', { matchType: 'equals' });
+  assert_equals(has_cookie, false);
+  const has_cookie2 = await cookieStore.has(
+      'cookie-name', { matchType: 'equals' });
+  assert_equals(has_cookie2, true);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has with matchType explicitly set to equals');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const has_cookie = await cookieStore.has(
+      'cookie-na', { matchType: 'startsWith' });
+  assert_equals(has_cookie, true);
+  const has_cookie2 = await cookieStore.has(
+      'cookie-name-', { matchType: 'startsWith' });
+  assert_equals(has_cookie2, false);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has with matchType set to startsWith');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.has(
+      'cookie-name', { matchType: 'invalid' }));
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has with invalid matchType');
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const has_cookie = await cookieStore.has(
+      { matchType: 'startsWith', name: 'cookie-na' });
+  assert_equals(has_cookie, true);
+  const has_cookie2 = await cookieStore.has(
+      { matchType: 'startsWith', name: 'cookie-name-' });
+  assert_equals(has_cookie2, false);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has with matchType set to startsWith and name in options');
diff --git a/cookie-store/cookieStore_has_basic.tentative.window.js b/cookie-store/cookieStore_has_basic.tentative.window.js
new file mode 100644
index 0000000..0774a71
--- /dev/null
+++ b/cookie-store/cookieStore_has_basic.tentative.window.js
@@ -0,0 +1,25 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.set('cookie-name', 'cookie-value');
+  assert_equals(await cookieStore.has('cookie-name'), true);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has returns true for cookie set by cookieStore.set()');
+
+promise_test(async testCase => {
+  await cookieStore.delete('cookie-name');
+  assert_equals(await cookieStore.has('cookie-name'), false);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.has returns false for cookie deleted by cookieStore.delete()');
diff --git a/cookie-store/cookieStore_set_arguments.tentative.html b/cookie-store/cookieStore_set_arguments.tentative.html
deleted file mode 100644
index 7a16162..0000000
--- a/cookie-store/cookieStore_set_arguments.tentative.html
+++ /dev/null
@@ -1,225 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.set() arguments and options</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set('cookie-name', 'cookie-value');
-
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with positional name and value');
-
-promise_test(async testCase => {
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set({ name: 'cookie-name', value: 'cookie-value' });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with name and value in options');
-
-promise_test(async testCase => {
-  await cookieStore.delete('cookie-name');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.set(
-      'cookie-name', 'cookie-value', { name: 'cookie-name' }));
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with name in both positional arguments and options');
-
-promise_test(async testCase => {
-  await cookieStore.delete('cookie-name');
-
-  await promise_rejects(testCase, new TypeError(), cookieStore.set(
-      'cookie-name', 'cookie-value', { value: 'cookie-value' }));
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with value in both positional arguments and options');
-
-promise_test(async testCase => {
-  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
-  const tenYearsFromNow = Date.now() + tenYears;
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { expires: tenYearsFromNow });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with expires in the future');
-
-promise_test(async testCase => {
-  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
-  const tenYearsAgo = Date.now() - tenYears;
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { expires: tenYearsAgo });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with expires in the past');
-
-promise_test(async testCase => {
-  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
-  const tenYearsFromNow = Date.now() + tenYears;
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set(
-      { name: 'cookie-name', value: 'cookie-value', expires: tenYearsFromNow });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with name and value in options and expires in the future');
-
-promise_test(async testCase => {
-  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
-  const tenYearsAgo = Date.now() - tenYears;
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set(
-      { name: 'cookie-name', value: 'cookie-value', expires: tenYearsAgo });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.set with name and value in options and expires in the past');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentDomain = currentUrl.hostname;
-  await cookieStore.delete('cookie-name', { domain: currentDomain });
-
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { domain: currentDomain });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { domain: currentDomain });
-  });
-}, 'cookieStore.set with domain set to the current hostname');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentDomain = currentUrl.hostname;
-  const subDomain = `sub.${currentDomain}`;
-  await cookieStore.delete('cookie-name', { domain: currentDomain });
-  await cookieStore.delete('cookie-name', { domain: subDomain });
-
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { domain: subDomain });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { domain: subDomain });
-  });
-}, 'cookieStore.set with domain set to a subdomain of the current hostname');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentDomain = currentUrl.hostname;
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set('cookie-name', 'cookie-old-value');
-  await cookieStore.set(
-      'cookie-name', 'cookie-new-value', { domain: currentDomain });
-
-  const cookies = await cookieStore.getAll('cookie-name');
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name');
-  assert_equals(cookies[0].value, 'cookie-new-value');
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name');
-    await cookieStore.delete('cookie-name', { domain: currentDomain });
-  });
-}, 'cookieStore.set default domain is current hostname');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentPath = currentUrl.pathname;
-  const currentDirectory =
-      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
-  await cookieStore.delete('cookie-name', { path: currentDirectory });
-
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { path: currentDirectory });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { path: currentDirectory });
-  });
-}, 'cookieStore.set with path set to the current directory');
-
-promise_test(async testCase => {
-  const currentUrl = new URL(window.location.href);
-  const currentPath = currentUrl.pathname;
-  const currentDirectory =
-      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
-  const subDirectory = currentDirectory + "subdir/";
-  await cookieStore.delete('cookie-name', { path: currentDirectory });
-  await cookieStore.delete('cookie-name', { path: subDirectory });
-
-  await cookieStore.set(
-      'cookie-name', 'cookie-value', { path: subDirectory });
-  const cookie = await cookieStore.get('cookie-name');
-  assert_equals(cookie, null);
-
-  await async_cleanup(async () => {
-    await cookieStore.delete('cookie-name', { path: subDirectory });
-  });
-}, 'cookieStore.set with path set to a subdirectory of the current directory');
-
-promise_test(async testCase => {
-  await cookieStore.delete('cookie-name');
-
-  await cookieStore.set('cookie-name', 'cookie-old-value');
-  await cookieStore.set('cookie-name', 'cookie-new-value', { path: '/' });
-
-  const cookies = await cookieStore.getAll('cookie-name');
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name');
-  assert_equals(cookies[0].value, 'cookie-new-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-  await async_cleanup(() => cookieStore.delete('cookie-name', { path: '/' }));
-}, 'cookieStore.set default path is /');
-
-</script>
diff --git a/cookie-store/cookieStore_set_arguments.tentative.window.js b/cookie-store/cookieStore_set_arguments.tentative.window.js
new file mode 100644
index 0000000..c256430
--- /dev/null
+++ b/cookie-store/cookieStore_set_arguments.tentative.window.js
@@ -0,0 +1,215 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with positional name and value');
+
+promise_test(async testCase => {
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set({ name: 'cookie-name', value: 'cookie-value' });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with name and value in options');
+
+promise_test(async testCase => {
+  await cookieStore.delete('cookie-name');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.set(
+      'cookie-name', 'cookie-value', { name: 'cookie-name' }));
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with name in both positional arguments and options');
+
+promise_test(async testCase => {
+  await cookieStore.delete('cookie-name');
+
+  await promise_rejects(testCase, new TypeError(), cookieStore.set(
+      'cookie-name', 'cookie-value', { value: 'cookie-value' }));
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with value in both positional arguments and options');
+
+promise_test(async testCase => {
+  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
+  const tenYearsFromNow = Date.now() + tenYears;
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { expires: tenYearsFromNow });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with expires in the future');
+
+promise_test(async testCase => {
+  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
+  const tenYearsAgo = Date.now() - tenYears;
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { expires: tenYearsAgo });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with expires in the past');
+
+promise_test(async testCase => {
+  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
+  const tenYearsFromNow = Date.now() + tenYears;
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set(
+      { name: 'cookie-name', value: 'cookie-value', expires: tenYearsFromNow });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with name and value in options and expires in the future');
+
+promise_test(async testCase => {
+  const tenYears = 10 * 365 * 24 * 60 * 60 * 1000;
+  const tenYearsAgo = Date.now() - tenYears;
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set(
+      { name: 'cookie-name', value: 'cookie-value', expires: tenYearsAgo });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with name and value in options and expires in the past');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentDomain = currentUrl.hostname;
+  await cookieStore.delete('cookie-name', { domain: currentDomain });
+
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { domain: currentDomain });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { domain: currentDomain });
+  });
+}, 'cookieStore.set with domain set to the current hostname');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentDomain = currentUrl.hostname;
+  const subDomain = `sub.${currentDomain}`;
+  await cookieStore.delete('cookie-name', { domain: currentDomain });
+  await cookieStore.delete('cookie-name', { domain: subDomain });
+
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { domain: subDomain });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { domain: subDomain });
+  });
+}, 'cookieStore.set with domain set to a subdomain of the current hostname');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentDomain = currentUrl.hostname;
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set('cookie-name', 'cookie-old-value');
+  await cookieStore.set(
+      'cookie-name', 'cookie-new-value', { domain: currentDomain });
+
+  const cookies = await cookieStore.getAll('cookie-name');
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name');
+  assert_equals(cookies[0].value, 'cookie-new-value');
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name');
+    await cookieStore.delete('cookie-name', { domain: currentDomain });
+  });
+}, 'cookieStore.set default domain is current hostname');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentPath = currentUrl.pathname;
+  const currentDirectory =
+      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
+  await cookieStore.delete('cookie-name', { path: currentDirectory });
+
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { path: currentDirectory });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { path: currentDirectory });
+  });
+}, 'cookieStore.set with path set to the current directory');
+
+promise_test(async testCase => {
+  const currentUrl = new URL(self.location.href);
+  const currentPath = currentUrl.pathname;
+  const currentDirectory =
+      currentPath.substr(0, currentPath.lastIndexOf('/') + 1);
+  const subDirectory = currentDirectory + "subdir/";
+  await cookieStore.delete('cookie-name', { path: currentDirectory });
+  await cookieStore.delete('cookie-name', { path: subDirectory });
+
+  await cookieStore.set(
+      'cookie-name', 'cookie-value', { path: subDirectory });
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie, null);
+
+  await async_cleanup(async () => {
+    await cookieStore.delete('cookie-name', { path: subDirectory });
+  });
+}, 'cookieStore.set with path set to a subdirectory of the current directory');
+
+promise_test(async testCase => {
+  await cookieStore.delete('cookie-name');
+
+  await cookieStore.set('cookie-name', 'cookie-old-value');
+  await cookieStore.set('cookie-name', 'cookie-new-value', { path: '/' });
+
+  const cookies = await cookieStore.getAll('cookie-name');
+  assert_equals(cookies.length, 1);
+  assert_equals(cookies[0].name, 'cookie-name');
+  assert_equals(cookies[0].value, 'cookie-new-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+  await async_cleanup(() => cookieStore.delete('cookie-name', { path: '/' }));
+}, 'cookieStore.set default path is /');
diff --git a/cookie-store/cookieStore_set_expires_option.tentative.window.js b/cookie-store/cookieStore_set_expires_option.tentative.window.js
new file mode 100644
index 0000000..fdbe2d9
--- /dev/null
+++ b/cookie-store/cookieStore_set_expires_option.tentative.window.js
@@ -0,0 +1,59 @@
+'use strict';
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+promise_test(async testCase => {
+  const inTwentyFourHours = new Date(Date.now() + 24 * 60 * 60 * 1000);
+
+  assert_equals(
+    await cookieStore.set(
+      'cookie-name', 'cookie-value', { expires: inTwentyFourHours }),
+    undefined);
+
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with expires option: Date object');
+
+promise_test(async testCase => {
+  const inTwentyFourHours = Date.now() + 24 * 60 * 60 * 1000;
+
+  assert_equals(
+    await cookieStore.set(
+      'cookie-name', 'cookie-value', { expires: inTwentyFourHours }),
+    undefined);
+
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with expires option: milliseconds since epoch object');
+
+promise_test(async testCase => {
+  const year = (new Date()).getUTCFullYear() + 1;
+  const date = new Date('07 Jun ' + year + ' 07:07:07 UTC');
+  const day = ('Sun Mon Tue Wed Thu Fri Sat'.split(' '))[date.getUTCDay()];
+  const nextJune = `${day}, 07 Jun ${year} + ' 07:07:07 GMT`;
+
+  assert_equals(
+    await cookieStore.set(
+      'cookie-name', 'cookie-value', { expires: nextJune }),
+    undefined);
+
+  const cookie = await cookieStore.get('cookie-name');
+  assert_equals(cookie.name, 'cookie-name');
+  assert_equals(cookie.value, 'cookie-value');
+
+  await async_cleanup(() => cookieStore.delete('cookie-name'));
+}, 'cookieStore.set with expires option: HTTP date string');
diff --git a/cookie-store/cookieStore_special_names.tentative.html b/cookie-store/cookieStore_special_names.tentative.html
index 3a80f83..9859e10 100644
--- a/cookie-store/cookieStore_special_names.tentative.html
+++ b/cookie-store/cookieStore_special_names.tentative.html
@@ -1,6 +1,6 @@
 <!doctype html>
 <meta charset="utf-8">
-<title>Async Cookies: cookieStore handles special cookie names correctly</title>
+<title>Cookie Store: cookieStore handles special cookie names correctly</title>
 <link rel="help" href="https://github.com/WICG/cookie-store">
 <link rel="author" href="pwnall@chromium.org" title="Victor Costan">
 <script src="/resources/testharness.js"></script>
@@ -8,28 +8,64 @@
 <script>
 'use strict';
 
-promise_test(async testCase => {
-  await promise_rejects(testCase, new TypeError(), cookieStore.set(
-      '__Secure-cookie-name', 'secure-cookie-value'));
+['__Secure-', '__Host-'].forEach(prefix => {
+  promise_test(async testCase => {
+    await promise_rejects(
+      testCase, new TypeError(),
+      cookieStore.set(`${prefix}cookie-name`, `secure-cookie-value`),
+      `Setting ${prefix} cookies should fail in non-secure contexts`);
 
-  try { await cookieStore.delete('__Secure-cookie-name'); } catch (e) {}
-}, 'cookieStore.set with __Secure- name on insecure origin');
+    try { await cookieStore.delete(`${prefix}cookie-name`); } catch (e) {}
+  }, `cookieStore.set with ${prefix} name on non-secure origin`);
 
-promise_test(async testCase => {
-  await promise_rejects(testCase, new TypeError(), cookieStore.set(
-      '__Host-cookie-name', 'host-cookie-value'));
+  promise_test(async testCase => {
+    await promise_rejects(
+      testCase, new TypeError(),
+      cookieStore.set(
+        `${prefix}cookie-name`, `secure-cookie-value`, {
+          expires: Date.now() - (24 * 60 * 60 * 1000)
+        }),
+      `Setting expired ${prefix} cookies should fail in non-secure contexts`);
 
-  try { await cookieStore.delete('__Host-cookie-name'); } catch (e) {}
-}, 'cookieStore.set with __Host- name on insecure origin');
+    try { await cookieStore.delete(`${prefix}cookie-name`); } catch (e) {}
+  }, `cookieStore.set of expired ${prefix} cookie on non-secure origin`);
 
-promise_test(async testCase => {
-  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
-      '__Secure-cookie-name', 'secure-cookie-value'));
-}, 'cookieStore.delete with __Secure- name on insecure origin');
+  promise_test(async testCase => {
+    assert_equals(
+      await cookieStore.get(`${prefix}cookie-name`),
+      null,
+      'get with ${prefix} prefix should not reject');
+    assert_equals(
+      await cookieStore.get({name: `${prefix}cookie-name`}),
+      null,
+      'get with ${prefix} prefix name option should not reject');
+    assert_equals(
+      await cookieStore.get({name: prefix, matchType: 'startsWith'}),
+      null,
+      'get with ${prefix} name and startsWith options should not reject');
+  }, `cookieStore.get with ${prefix} name on non-secure origin`);
 
-promise_test(async testCase => {
-  await promise_rejects(testCase, new TypeError(), cookieStore.delete(
-      '__Host-cookie-name', 'host-cookie-value'));
-}, 'cookieStore.delete with __Host- name on insecure origin');
+  promise_test(async testCase => {
+    assert_array_equals(
+      await cookieStore.getAll(`${prefix}cookie-name`),
+      [],
+      'getAll with ${prefix} prefix should not reject');
+    assert_array_equals(
+      await cookieStore.getAll({name: `${prefix}cookie-name`}),
+      [],
+      'getAll with ${prefix} prefix name option should not reject');
+    assert_array_equals(
+      await cookieStore.getAll({name: prefix, matchType: 'startsWith'}),
+      [],
+      'getAll with ${prefix} name and startsWith options should not reject');
+  }, `cookieStore.getAll with ${prefix} name on non-secure origin`);
+
+  promise_test(async testCase => {
+    await promise_rejects(
+      testCase, new TypeError(),
+      cookieStore.delete(`${prefix}cookie-name`, `host-cookie-value`),
+      `Deleting ${prefix} cookies should fail in non-secure contexts`);
+  }, `cookieStore.delete with ${prefix} name on non-secure origin`);
+});
 
 </script>
diff --git a/cookie-store/cookieStore_special_names.tentative.https.html b/cookie-store/cookieStore_special_names.tentative.https.html
new file mode 100644
index 0000000..0d1be12
--- /dev/null
+++ b/cookie-store/cookieStore_special_names.tentative.https.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Cookie Store: cookieStore handles special cookie names correctly (secure context)</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+['__Secure-', '__Host-'].forEach(prefix => {
+  promise_test(async testCase => {
+    await cookieStore.set(`${prefix}cookie-name`, `secure-cookie-value`);
+    assert_equals(
+      (await cookieStore.get(`${prefix}cookie-name`)).value,
+      'secure-cookie-value',
+      `Setting ${prefix} cookies should not fail in secure context`);
+
+    try { await cookieStore.delete(`${prefix}cookie-name`); } catch (e) {}
+  }, `cookieStore.set with ${prefix} name on secure origin`);
+
+  promise_test(async testCase => {
+    // This test is for symmetry with the non-secure case. In non-secure
+    // contexts, the set() should fail even if the expiration date makes
+    // the operation a no-op.
+    await cookieStore.set(
+      `${prefix}cookie-name`, `secure-cookie-value`, {
+        expires: Date.now() - (24 * 60 * 60 * 1000)
+      });
+    assert_equals(await cookieStore.get(`${prefix}cookie-name`), null);
+    try { await cookieStore.delete(`${prefix}cookie-name`); } catch (e) {}
+  }, `cookieStore.set of expired ${prefix} cookie name on secure origin`);
+
+  promise_test(async testCase => {
+    assert_equals(
+      await cookieStore.delete(`${prefix}cookie-name`), undefined,
+      `Deleting ${prefix} cookies should not fail in secure context`);
+  }, `cookieStore.delete with ${prefix} name on secure origin`);
+});
+
+</script>
diff --git a/cookie-store/cookie_store_tests.tentative.html b/cookie-store/cookie_store_tests.tentative.html
deleted file mode 100644
index 39962c7..0000000
--- a/cookie-store/cookie_store_tests.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Basic tests for cookieStore</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite();
-</script>
diff --git a/cookie-store/cookie_store_tests.tentative.https.html b/cookie-store/cookie_store_tests.tentative.https.html
deleted file mode 100644
index 3bf592e..0000000
--- a/cookie-store/cookie_store_tests.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Basic tests for cookieStore (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite();
-</script>
diff --git a/cookie-store/cookie_store_tests_static.tentative.html b/cookie-store/cookie_store_tests_static.tentative.html
deleted file mode 100644
index 6f0a3c9..0000000
--- a/cookie-store/cookie_store_tests_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Basic tests for cookieStore (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite();
-</script>
diff --git a/cookie-store/cookie_store_tests_static.tentative.https.html b/cookie-store/cookie_store_tests_static.tentative.https.html
deleted file mode 100644
index 08784f8..0000000
--- a/cookie-store/cookie_store_tests_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Basic tests for cookieStore (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite();
-</script>
diff --git a/cookie-store/delete_cookies.tentative.html b/cookie-store/delete_cookies.tentative.html
deleted file mode 100644
index 786885a..0000000
--- a/cookie-store/delete_cookies.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: delete cookies</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDeleteCookies'});
-</script>
diff --git a/cookie-store/delete_cookies.tentative.https.html b/cookie-store/delete_cookies.tentative.https.html
deleted file mode 100644
index accb4d2..0000000
--- a/cookie-store/delete_cookies.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: delete cookies (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDeleteCookies'});
-</script>
diff --git a/cookie-store/delete_cookies_static.tentative.html b/cookie-store/delete_cookies_static.tentative.html
deleted file mode 100644
index 0ceb517f..0000000
--- a/cookie-store/delete_cookies_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: delete cookies (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDeleteCookies'});
-</script>
diff --git a/cookie-store/delete_cookies_static.tentative.https.html b/cookie-store/delete_cookies_static.tentative.https.html
deleted file mode 100644
index a022c28..0000000
--- a/cookie-store/delete_cookies_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: delete cookies (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDeleteCookies'});
-</script>
diff --git a/cookie-store/document_cookie.tentative.html b/cookie-store/document_cookie.tentative.html
index 743f4ef..f5528d2 100644
--- a/cookie-store/document_cookie.tentative.html
+++ b/cookie-store/document_cookie.tentative.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: document.cookie</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDocumentCookie'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/document_cookie.js"></script>
diff --git a/cookie-store/document_cookie.tentative.https.html b/cookie-store/document_cookie.tentative.https.html
index e19b841..63ddd9f 100644
--- a/cookie-store/document_cookie.tentative.https.html
+++ b/cookie-store/document_cookie.tentative.https.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: document.cookie (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDocumentCookie'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/document_cookie.js"></script>
diff --git a/cookie-store/document_cookie_static.tentative.html b/cookie-store/document_cookie_static.tentative.html
deleted file mode 100644
index 8f671dd..0000000
--- a/cookie-store/document_cookie_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: document.cookie (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDocumentCookie'});
-</script>
diff --git a/cookie-store/document_cookie_static.tentative.https.html b/cookie-store/document_cookie_static.tentative.https.html
deleted file mode 100644
index 065af2a..0000000
--- a/cookie-store/document_cookie_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: document.cookie (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testDocumentCookie'});
-</script>
diff --git a/cookie-store/document_getAll_set.tentative.html b/cookie-store/document_getAll_set.tentative.html
deleted file mode 100644
index 05a794b..0000000
--- a/cookie-store/document_getAll_set.tentative.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.getAll() sees cookieStore.set() cookie</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  const cookies = await cookieStore.getAll('cookie-name');
-
-  assert_equals(cookies.length, 1);
-  assert_equals(cookies[0].name, 'cookie-name');
-  assert_equals(cookies[0].value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.getAll returns the cookie written by cookieStore.set');
-
-</script>
diff --git a/cookie-store/document_get_delete.tentative.html b/cookie-store/document_get_delete.tentative.html
deleted file mode 100644
index ebb2c8f..0000000
--- a/cookie-store/document_get_delete.tentative.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.delete() impacts cookieStore.get()</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  await cookieStore.delete('cookie-name');
-  const cookie = await cookieStore.get();
-  assert_equals(cookie, null);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get returns null for a cookie deleted by cookieStore.delete');
-
-</script>
diff --git a/cookie-store/document_get_set.tentative.html b/cookie-store/document_get_set.tentative.html
deleted file mode 100644
index e90d0e5..0000000
--- a/cookie-store/document_get_set.tentative.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.get() sees cookieStore.set() cookie</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  const cookie = await cookieStore.get('cookie-name');
-
-  assert_equals(cookie.name, 'cookie-name');
-  assert_equals(cookie.value, 'cookie-value');
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.get returns the cookie written by cookieStore.set');
-
-</script>
diff --git a/cookie-store/document_has.tentative.html b/cookie-store/document_has.tentative.html
deleted file mode 100644
index 9c2eb5b..0000000
--- a/cookie-store/document_has.tentative.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Async Cookies: cookieStore.has()</title>
-<link rel="help" href="https://github.com/WICG/cookie-store">
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-// Workaround because add_cleanup doesn't support async functions yet.
-// See https://github.com/w3c/web-platform-tests/issues/6075
-async function async_cleanup(cleanup_function) {
-  try {
-    await cleanup_function();
-  } catch (e) {
-    // Errors in cleanup functions shouldn't result in test failures.
-  }
-}
-
-promise_test(async testCase => {
-  await cookieStore.set('cookie-name', 'cookie-value');
-  assert_equals(await cookieStore.has('cookie-name'), true);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has returns true for cookie set by cookieStore.set()');
-
-promise_test(async testCase => {
-  await cookieStore.delete('cookie-name');
-  assert_equals(await cookieStore.has('cookie-name'), false);
-
-  await async_cleanup(() => cookieStore.delete('cookie-name'));
-}, 'cookieStore.has returns false for cookie deleted by cookieStore.delete()');
-
-</script>
diff --git a/cookie-store/expiration.tentative.html b/cookie-store/expiration.tentative.html
deleted file mode 100644
index 47a628d..0000000
--- a/cookie-store/expiration.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: expiration</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testExpiration'});
-</script>
diff --git a/cookie-store/expiration.tentative.https.html b/cookie-store/expiration.tentative.https.html
deleted file mode 100644
index e4db1a6..0000000
--- a/cookie-store/expiration.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: expiration (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testExpiration'});
-</script>
diff --git a/cookie-store/expiration_static.tentative.html b/cookie-store/expiration_static.tentative.html
deleted file mode 100644
index f9c6018..0000000
--- a/cookie-store/expiration_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: expiration (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testExpiration'});
-</script>
diff --git a/cookie-store/expiration_static.tentative.https.html b/cookie-store/expiration_static.tentative.https.html
deleted file mode 100644
index b688768..0000000
--- a/cookie-store/expiration_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: expiration (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testExpiration'});
-</script>
diff --git a/cookie-store/get_set_get_all.tentative.html b/cookie-store/get_set_get_all.tentative.html
deleted file mode 100644
index 2574ade..0000000
--- a/cookie-store/get_set_get_all.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: get, set, getAll</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testGetSetGetAll'});
-</script>
diff --git a/cookie-store/get_set_get_all.tentative.https.html b/cookie-store/get_set_get_all.tentative.https.html
deleted file mode 100644
index 5a52fdd..0000000
--- a/cookie-store/get_set_get_all.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: get, set, getAll (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testGetSetGetAll'});
-</script>
diff --git a/cookie-store/get_set_get_all_static.tentative.html b/cookie-store/get_set_get_all_static.tentative.html
deleted file mode 100644
index 0892179..0000000
--- a/cookie-store/get_set_get_all_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: get, set, getAll (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testGetSetGetAll'});
-</script>
diff --git a/cookie-store/get_set_get_all_static.tentative.https.html b/cookie-store/get_set_get_all_static.tentative.https.html
deleted file mode 100644
index 6e44c0d..0000000
--- a/cookie-store/get_set_get_all_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: get, set, getAll (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testGetSetGetAll'});
-</script>
diff --git a/cookie-store/http_cookie_and_set_cookie_headers.tentative.html b/cookie-store/http_cookie_and_set_cookie_headers.tentative.html
index 8784885..4370350 100644
--- a/cookie-store/http_cookie_and_set_cookie_headers.tentative.html
+++ b/cookie-store/http_cookie_and_set_cookie_headers.tentative.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: HTTP Cookie and Set-Cookie headers</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testHttpCookieAndSetCookieHeaders'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/http_cookie_and_set_cookie_headers.js"></script>
diff --git a/cookie-store/http_cookie_and_set_cookie_headers.tentative.https.html b/cookie-store/http_cookie_and_set_cookie_headers.tentative.https.html
index 9b05779..0c3d8b1 100644
--- a/cookie-store/http_cookie_and_set_cookie_headers.tentative.https.html
+++ b/cookie-store/http_cookie_and_set_cookie_headers.tentative.https.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: HTTP Cookie and Set-Cookie headers (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testHttpCookieAndSetCookieHeaders'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/http_cookie_and_set_cookie_headers.js"></script>
diff --git a/cookie-store/httponly_cookies.window.js b/cookie-store/httponly_cookies.window.js
new file mode 100644
index 0000000..01239ae
--- /dev/null
+++ b/cookie-store/httponly_cookies.window.js
@@ -0,0 +1,47 @@
+// META: script=resources/cookie-test-helpers.js
+
+'use strict';
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringHttp('HTTPONLY-cookie=value; path=/; httponly');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'HttpOnly cookie we wrote using HTTP in cookie jar' +
+        ' is invisible to script');
+  assert_equals(
+      await getCookieStringHttp(),
+      'HTTPONLY-cookie=value',
+    'HttpOnly cookie we wrote using HTTP in HTTP cookie jar');
+
+  await setCookieStringHttp('HTTPONLY-cookie=new-value; path=/; httponly');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'HttpOnly cookie we overwrote using HTTP in cookie jar' +
+        ' is invisible to script');
+  assert_equals(
+      await getCookieStringHttp(),
+      'HTTPONLY-cookie=new-value',
+    'HttpOnly cookie we overwrote using HTTP in HTTP cookie jar');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringHttp(
+      'HTTPONLY-cookie=DELETED; path=/; max-age=0; httponly');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'Empty cookie jar after HTTP cookie-clearing using max-age=0');
+  assert_equals(
+      await getCookieStringHttp(),
+      undefined,
+      'Empty HTTP cookie jar after HTTP cookie-clearing using max-age=0');
+
+  // HTTPONLY cookie changes should not have been observed; perform
+  // a dummy change to verify that nothing else was queued up.
+  await cookieStore.set('TEST', 'dummy');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'TEST', value: 'dummy'}]},
+    'HttpOnly cookie deletion was not observed');
+}, 'HttpOnly cookies are not observed');
diff --git a/cookie-store/idlharness.tentative.html b/cookie-store/idlharness.tentative.html
new file mode 100644
index 0000000..9835427
--- /dev/null
+++ b/cookie-store/idlharness.tentative.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: IDL tests</title>
+<link rel="help" href="https://github.com/WICG/cookie-store"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const urls = [
+    '/interfaces/uievents.idl',
+    '/interfaces/dom.idl',
+    '/interfaces/html.idl',
+    '/interfaces/cookie-store.idl'
+  ];
+  const [uievents, dom, html, cookie_store] = await Promise.all(
+    urls.map(url => fetch(url).then(r => r.text())));
+
+  const idl_array = new IdlArray();
+
+  // Dependencies of HTML
+  idl_array.add_untested_idls(dom, { only: [
+    'Event',
+    'EventInit',
+    'EventTarget',
+    'HTMLCollection',
+    'NodeList',
+  ] });
+  idl_array.add_untested_idls('interface Document {};');
+  idl_array.add_untested_idls('interface Element {};');
+  idl_array.add_untested_idls('interface LinkStyle {};');
+  idl_array.add_untested_idls('interface SVGElement {};');
+  idl_array.add_untested_idls(html);
+  idl_array.add_untested_idls(uievents, { only: [
+    'UIEvent',
+    'UIEventInit',
+    'MouseEvent',
+    'MouseEventInit',
+    'EventModifierInit',
+  ] });
+
+  idl_array.add_untested_idls(
+    `dictionary ExtendableEventInit {};`);
+  idl_array.add_untested_idls(
+    `[Global=ExtendableEvent, Exposed=ServiceWorker]
+     interface ExtendableEvent : Event {};`);
+  idl_array.add_untested_idls(
+    `[Global=ServiceWorker, Exposed=ServiceWorker]
+     interface ServiceWorkerGlobalScope {};`);
+
+  idl_array.add_idls(cookie_store);
+
+  idl_array.add_objects({
+    CookieStore: [self.cookieStore],
+    CookieChangeEvent: [new CookieChangeEvent('change')],
+  });
+  idl_array.test();
+}, 'Interface test');
+</script>
diff --git a/cookie-store/idlharness_serviceworker.js b/cookie-store/idlharness_serviceworker.js
new file mode 100644
index 0000000..ba80413
--- /dev/null
+++ b/cookie-store/idlharness_serviceworker.js
@@ -0,0 +1,45 @@
+self.GLOBAL = {
+  isWindow: function() { return false; },
+  isWorker: function() { return true; },
+};
+importScripts('/resources/testharness.js',
+              '/resources/WebIDLParser.js',
+              '/resources/idlharness.js');
+
+promise_test(async t => {
+  const urls = ['/interfaces/cookie-store.idl'];
+  const [cookie_store] = await Promise.all(
+    urls.map(url => fetch(url).then(response => response.text())));
+
+  const idl_array = new IdlArray();
+
+  idl_array.add_untested_idls(
+    `[Global=Event, Exposed=ServiceWorker]
+     interface Event {};`);
+  idl_array.add_untested_idls(
+    `[Global=ExtendableEvent, Exposed=ServiceWorker]
+     interface ExtendableEvent : Event {};`);
+  idl_array.add_untested_idls('dictionary EventHandler {};');
+  idl_array.add_untested_idls('dictionary EventInit {};');
+  idl_array.add_untested_idls('dictionary ExtendableEventInit {};');
+  idl_array.add_untested_idls(
+    `[Global=EventTarget, Exposed=ServiceWorker]
+     interface EventTarget {};`);
+  idl_array.add_untested_idls(
+    `[Global=ServiceWorker, Exposed=ServiceWorker]
+     interface ServiceWorkerGlobalScope {};`);
+  idl_array.add_untested_idls(
+    `[Global=Window, Exposed=Window]
+     interface Window {};`);
+
+  idl_array.add_idls(cookie_store);
+
+  idl_array.add_objects({
+    CookieStore: [self.cookieStore],
+    ExtendableCookieChangeEvent: [
+        new ExtendableCookieChangeEvent('cookiechange')],
+  });
+  idl_array.test();
+}, 'Interface test');
+
+done();
diff --git a/cookie-store/idlharness_serviceworker.tentative.https.html b/cookie-store/idlharness_serviceworker.tentative.https.html
new file mode 100644
index 0000000..845a9fd
--- /dev/null
+++ b/cookie-store/idlharness_serviceworker.tentative.https.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: ServiceWorker IDL tests</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+(async () => {
+  const scope = 'does/not/exist';
+
+  let registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+  registration = await navigator.serviceWorker.register(
+      'idlharness_serviceworker.js', {scope});
+
+  fetch_tests_from_worker(registration.installing);
+})();
+</script>
diff --git a/cookie-store/meta_http_equiv_set_cookie.tentative.html b/cookie-store/meta_http_equiv_set_cookie.tentative.html
deleted file mode 100644
index 978aa8b..0000000
--- a/cookie-store/meta_http_equiv_set_cookie.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: <title>Async Cookies: document.cookie</title>lt;Meta Http-Equiv="Set-Cookie" ... <title>Async Cookies: document.cookie</title>gt;</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testMetaHttpEquivSetCookie'});
-</script>
diff --git a/cookie-store/meta_http_equiv_set_cookie.tentative.https.html b/cookie-store/meta_http_equiv_set_cookie.tentative.https.html
deleted file mode 100644
index 38192b1..0000000
--- a/cookie-store/meta_http_equiv_set_cookie.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: <title>Async Cookies: document.cookie</title>lt;Meta Http-Equiv="Set-Cookie" ... <title>Async Cookies: document.cookie</title>gt; (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testMetaHttpEquivSetCookie'});
-</script>
diff --git a/cookie-store/meta_http_equiv_set_cookie_static.tentative.html b/cookie-store/meta_http_equiv_set_cookie_static.tentative.html
deleted file mode 100644
index a7063e6..0000000
--- a/cookie-store/meta_http_equiv_set_cookie_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: <title>Async Cookies: document.cookie</title>lt;Meta Http-Equiv="Set-Cookie" ... <title>Async Cookies: document.cookie</title>gt; (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testMetaHttpEquivSetCookie'});
-</script>
diff --git a/cookie-store/meta_http_equiv_set_cookie_static.tentative.https.html b/cookie-store/meta_http_equiv_set_cookie_static.tentative.https.html
deleted file mode 100644
index 830b927..0000000
--- a/cookie-store/meta_http_equiv_set_cookie_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: <title>Async Cookies: document.cookie</title>lt;Meta Http-Equiv="Set-Cookie" ... <title>Async Cookies: document.cookie</title>gt; (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testMetaHttpEquivSetCookie'});
-</script>
diff --git a/cookie-store/no_name_and_no_value.tentative.html b/cookie-store/no_name_and_no_value.tentative.html
index 25bbb82..79923b5 100644
--- a/cookie-store/no_name_and_no_value.tentative.html
+++ b/cookie-store/no_name_and_no_value.tentative.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: Test No Name and No Value</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameAndNoValue'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/no_name_and_no_value.js"></script>
diff --git a/cookie-store/no_name_and_no_value.tentative.https.html b/cookie-store/no_name_and_no_value.tentative.https.html
index 3f25544..0f15dafa 100644
--- a/cookie-store/no_name_and_no_value.tentative.https.html
+++ b/cookie-store/no_name_and_no_value.tentative.https.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: Test No Name and No Value (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameAndNoValue'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/no_name_and_no_value.js"></script>
diff --git a/cookie-store/no_name_and_no_value_static.tentative.html b/cookie-store/no_name_and_no_value_static.tentative.html
deleted file mode 100644
index d4a66b0..0000000
--- a/cookie-store/no_name_and_no_value_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test No Name and No Value (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameAndNoValue'});
-</script>
diff --git a/cookie-store/no_name_and_no_value_static.tentative.https.html b/cookie-store/no_name_and_no_value_static.tentative.https.html
deleted file mode 100644
index 46e0bf9..0000000
--- a/cookie-store/no_name_and_no_value_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test No Name and No Value (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameAndNoValue'});
-</script>
diff --git a/cookie-store/no_name_equals_in_value.tentative.html b/cookie-store/no_name_equals_in_value.tentative.html
index 849af3c..1759703 100644
--- a/cookie-store/no_name_equals_in_value.tentative.html
+++ b/cookie-store/no_name_equals_in_value.tentative.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: Test No Name, '=' in Value</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameEqualsInValue'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/no_name_equals_in_value.js"></script>
diff --git a/cookie-store/no_name_equals_in_value.tentative.https.html b/cookie-store/no_name_equals_in_value.tentative.https.html
index d352768..de77455 100644
--- a/cookie-store/no_name_equals_in_value.tentative.https.html
+++ b/cookie-store/no_name_equals_in_value.tentative.https.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: Test No Name, '=' in Value (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameEqualsInValue'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/no_name_equals_in_value.js"></script>
diff --git a/cookie-store/no_name_equals_in_value_static.tentative.html b/cookie-store/no_name_equals_in_value_static.tentative.html
deleted file mode 100644
index 02fd5f7..0000000
--- a/cookie-store/no_name_equals_in_value_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test No Name, '=' in Value (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameEqualsInValue'});
-</script>
diff --git a/cookie-store/no_name_equals_in_value_static.tentative.https.html b/cookie-store/no_name_equals_in_value_static.tentative.https.html
deleted file mode 100644
index a3d9ec0..0000000
--- a/cookie-store/no_name_equals_in_value_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test No Name, '=' in Value (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameEqualsInValue'});
-</script>
diff --git a/cookie-store/no_name_multiple_values.tentative.html b/cookie-store/no_name_multiple_values.tentative.html
index e4f208c..755d106 100644
--- a/cookie-store/no_name_multiple_values.tentative.html
+++ b/cookie-store/no_name_multiple_values.tentative.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: Test No Name, Multiple Values</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameMultipleValues'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/no_name_multiple_values.js"></script>
diff --git a/cookie-store/no_name_multiple_values.tentative.https.html b/cookie-store/no_name_multiple_values.tentative.https.html
index a3ff0ce..29cde81 100644
--- a/cookie-store/no_name_multiple_values.tentative.https.html
+++ b/cookie-store/no_name_multiple_values.tentative.https.html
@@ -1,14 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<meta name="timeout" content="long">
 <title>Async Cookies: Test No Name, Multiple Values (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
+<meta name="help" href="https://github.com/WICG/cookie-store/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameMultipleValues'});
-</script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/no_name_multiple_values.js"></script>
diff --git a/cookie-store/no_name_multiple_values_static.tentative.html b/cookie-store/no_name_multiple_values_static.tentative.html
deleted file mode 100644
index 8e61be6..0000000
--- a/cookie-store/no_name_multiple_values_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test No Name, Multiple Values (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameMultipleValues'});
-</script>
diff --git a/cookie-store/no_name_multiple_values_static.tentative.https.html b/cookie-store/no_name_multiple_values_static.tentative.https.html
deleted file mode 100644
index 6d2a684..0000000
--- a/cookie-store/no_name_multiple_values_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test No Name, Multiple Values (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testNoNameMultipleValues'});
-</script>
diff --git a/cookie-store/observation.tentative.html b/cookie-store/observation.tentative.html
deleted file mode 100644
index d2c9e6a8..0000000
--- a/cookie-store/observation.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test Observation</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testObservation'});
-</script>
diff --git a/cookie-store/observation.tentative.https.html b/cookie-store/observation.tentative.https.html
deleted file mode 100644
index 11ee8e4..0000000
--- a/cookie-store/observation.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test Observation (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testObservation'});
-</script>
diff --git a/cookie-store/observation_static.tentative.html b/cookie-store/observation_static.tentative.html
deleted file mode 100644
index 422ab39..0000000
--- a/cookie-store/observation_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test Observation (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testObservation'});
-</script>
diff --git a/cookie-store/observation_static.tentative.https.html b/cookie-store/observation_static.tentative.https.html
deleted file mode 100644
index 323dc86..0000000
--- a/cookie-store/observation_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: Test Observation (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testObservation'});
-</script>
diff --git a/cookie-store/one_simple_origin_cookie.tentative.html b/cookie-store/one_simple_origin_cookie.tentative.html
deleted file mode 100644
index 1ca217e..0000000
--- a/cookie-store/one_simple_origin_cookie.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: One simple origin cookie</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testOneSimpleOriginCookie'});
-</script>
diff --git a/cookie-store/one_simple_origin_cookie.tentative.https.html b/cookie-store/one_simple_origin_cookie.tentative.https.html
deleted file mode 100644
index f27f7f4..0000000
--- a/cookie-store/one_simple_origin_cookie.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: One simple origin cookie (HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testOneSimpleOriginCookie'});
-</script>
diff --git a/cookie-store/one_simple_origin_cookie_static.tentative.html b/cookie-store/one_simple_origin_cookie_static.tentative.html
deleted file mode 100644
index 88bd8ba..0000000
--- a/cookie-store/one_simple_origin_cookie_static.tentative.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: One simple origin cookie (Static)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testOneSimpleOriginCookie'});
-</script>
diff --git a/cookie-store/one_simple_origin_cookie_static.tentative.https.html b/cookie-store/one_simple_origin_cookie_static.tentative.https.html
deleted file mode 100644
index f53d81a..0000000
--- a/cookie-store/one_simple_origin_cookie_static.tentative.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>Async Cookies: One simple origin cookie (Static; HTTPS)</title>
-<meta name="help" href="https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/testharness-helpers.js"></script>
-<script src="resources/cookie-store-tests.js"></script>
-<script>
-'use strict';
-
-suite({testName: 'testOneSimpleOriginCookie'});
-</script>
diff --git a/cookie-store/ordering.tentative.https.html b/cookie-store/ordering.tentative.https.html
new file mode 100644
index 0000000..1873ed4
--- /dev/null
+++ b/cookie-store/ordering.tentative.https.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Async Cookies: ordering (HTTPS)</title>
+<meta name="help" href="https://github.com/WICG/cookie-store/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/cookie-test-helpers.js"></script>
+<script src="resources/ordering.js"></script>
diff --git a/cookie-store/resources/cookie-store-tests.js b/cookie-store/resources/cookie-store-tests.js
deleted file mode 100644
index 413dee9..0000000
--- a/cookie-store/resources/cookie-store-tests.js
+++ /dev/null
@@ -1,1114 +0,0 @@
-'use strict';
-
-// Buffered exceptions re-thrown at end of suite
-let savedExceptions = [];
-
-// Observer-based document.cookie simulator
-let observer;
-let observationLog = [];
-let observedStore = [];
-
-// Note on cookie naming conventions:
-//
-// A simple origin cookie is a cookie named with the __Host- prefix
-// which is always secure-flagged, always implicit-domain, always
-// /-scoped, and hence always unambiguous in the cookie jar serialization
-// and origin-scoped. It can be treated as a simple key/value pair.
-//
-// "LEGACY" in a cookie name here means it is an old-style unprefixed
-// cookie name, so you can't tell e.g. whether it is Secure-flagged or
-// /-pathed just by looking at it, and its flags, domain and path may
-// vary even in a single cookie jar serialization leading to apparent
-// duplicate entries, ambiguities, and complexity (it cannot be
-// treated as a simple key/value pair.)
-//
-// Cookie names used in the tests are intended to be
-// realistic. Traditional session cookie names are typically
-// all-upper-case for broad framework compatibility. The more modern
-// "__Host-" prefix has only one allowed casing. An expected upgrade
-// path from traditional "legacy" cookie names to simple origin cookie
-// names is simply to prefix the traditional name with the "__Host-"
-// prefix.
-//
-// Many of the used cookie names are non-ASCII to ensure
-// straightforward internationalization is possible at every API surface.
-// These work in many modern browsers, though not yet all of them.
-
-// Approximate async observer-based equivalent to the document.cookie
-// getter but with important differences: an empty cookie jar returns
-// undefined. Introduces unfortunate but apparently unavoidable delays
-// to ensure the observer has time to run.
-//
-// Timeouts here are intended to give observers enough time to sense
-// a change. It can't be changed to wait indefinitely as it is
-// sometimes used to verify observers were not notified of any
-// change.
-const getCookieStringObserved = opt_name => {
-  // Run later to ensure the cookie scanner (which runs one task
-  // later, at least in the polyfill) has a chance.
-  //
-  // We cannot use the s\u0065tTimeout identifier unescaped inside WPT
-  // tests (the linter does not allow it.) However we need an actual
-  // delay to allow batched observers to fire.
-  const initialLength = observationLog.length;
-  return (async () => {
-    assert_not_equals(observer, undefined, 'observer should not be undefined');
-    await new Promise(resolve => s\u0065tTimeout(resolve));
-    const lengthAfterImplicit0msSetTimeout = observationLog.length;
-    if (lengthAfterImplicit0msSetTimeout === initialLength) {
-      await new Promise(resolve => s\u0065tTimeout(resolve, 4));
-      const lengthAfter4msSetTimeout = observationLog.length;
-      if (lengthAfter4msSetTimeout === initialLength) {
-        let lengthAfterRequestAnimationFrame = lengthAfter4msSetTimeout;
-        if (typeof requestAnimationFrame !== 'undefined') {
-          await new Promise(resolve => requestAnimationFrame(resolve));
-          lengthAfterRequestAnimationFrame = observationLog.length;
-        }
-        if (lengthAfterRequestAnimationFrame === initialLength) {
-          await new Promise(
-              resolve => s\u0065tTimeout(resolve, kExtraObserverDelay));
-        }
-      }
-    }
-    let filtered = observedStore;
-    if (opt_name != null) filtered = filtered.filter(
-        cookie => cookie.name === opt_name);
-    return cookieString(filtered);
-  })();
-};
-
-const assertEmptyCookieJar = async (testCase, messageSuffix) => {
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'No cookies ' + messageSuffix);
-  if (!kIsStatic) assert_equals(
-      await getCookieStringHttp(),
-      undefined,
-      'No HTTP cookies ' + messageSuffix);
-  if (kHasDocument) assert_equals(
-      await getCookieStringDocument(),
-      undefined,
-      'No document.cookie cookies ' + messageSuffix);
-};
-
-const suite = ({testName = undefined} = {}) => {
-  promise_test(async testCase => {
-    testOverride = testName;
-    observer = undefined;
-    observationLog.length = 0;
-    observedStore.length = 0;
-    savedExceptions.length = 0;
-    // Start with a clean slate.
-    //
-    // Attempt testDeleteCookies first too, since otherwise an earlier
-    // failed test can cause all subsequent tests to fail.
-    await testDeleteCookies(testCase);
-    await assertEmptyCookieJar(testCase, 'at start of test');
-    let unfinished = true;
-    try {
-      if (includeTest('testObservation')) {
-        observer = await testObservation();
-        assert_equals(
-            await getCookieStringObserved(),
-            undefined,
-            'No observed cookies at start of test');
-      }
-      // These use the same cookie names and so cannot run interleaved
-      if (includeTest('testNoNameAndNoValue')) await testNoNameAndNoValue();
-      if (includeTest('testNoNameMultipleValues')) {
-        await testNoNameMultipleValues();
-      }
-      if (includeTest('testNoNameEqualsInValue')) {
-        await testNoNameEqualsInValue();
-      }
-      if (includeTest('testMetaHttpEquivSetCookie')) {
-        await testMetaHttpEquivSetCookie();
-      }
-      if (includeTest('testDocumentCookie', !kHasDocument)) {
-        await testDocumentCookie();
-      }
-      if (includeTest('testHttpCookieAndSetCookieHeaders', kIsStatic)) {
-        await testHttpCookieAndSetCookieHeaders();
-      }
-      if (includeTest('testGetSetGetAll')) {
-        await testGetSetGetAll();
-      }
-      if (includeTest('testOneSimpleOriginCookie')) {
-        await testOneSimpleOriginCookie(testCase);
-      }
-      if (includeTest('testExpiration')) {
-        await testExpiration(testCase);
-      }
-      await promise_rejects_when_unsecured(
-          testCase,
-          new TypeError(),
-          testThreeSimpleOriginSessionCookiesSetSequentially(),
-          '__Host- cookies only writable from secure contexts' +
-            ' (testThreeSimpleOriginSessionCookiesSetSequentially)');
-      await promise_rejects_when_unsecured(
-          testCase,
-          new TypeError(),
-          testThreeSimpleOriginSessionCookiesSetNonsequentially(),
-          '__Host- cookies only writable from secure contexts' +
-            ' (testThreeSimpleOriginSessionCookiesSetNonsequentially)');
-      await promise_rejects_when_unsecured(
-          testCase,
-          new TypeError(),
-          setExpiredSecureCookieWithDomainPathAndFallbackValue(),
-          'Secure cookies only writable from secure contexts' +
-            ' (setExpiredSecureCookieWithDomainPathAndFallbackValue)');
-      await promise_rejects_when_unsecured(
-          testCase,
-          new TypeError(),
-          deleteSimpleOriginCookie(),
-          '__Host- cookies only writable from secure contexts' +
-            ' (deleteSimpleOriginCookie)');
-      await promise_rejects_when_unsecured(
-          testCase,
-          new TypeError(),
-          deleteSecureCookieWithDomainAndPath(),
-          'Secure cookies only writable from secure contexts' +
-            ' (deleteSecureCookieWithDomainAndPath)');
-      if (kIsUnsecured) {
-        assert_equals(
-            await getCookieString(),
-            includeTest('testGetSetGetAll') ? 'TEST=value' : undefined,
-            (includeTest('testGetSetGetAll') ?
-             'Only one unsecured cookie' :
-             'No unsecured cookies') +
-              ' before testDeleteCookies at end of test');
-        if (observer) assert_equals(
-            await getCookieStringObserved(),
-            includeTest('testGetSetGetAll') ? 'TEST=value' : undefined,
-            (includeTest('testGetSetGetAll') ?
-             'Only one observed unsecured cookie' :
-             'No observed unsecured cookies') +
-              ' before testDeleteCookies at end of test');
-      } else {
-        assert_equals(
-            await getCookieString(),
-            (includeTest('testGetSetGetAll') ? 'TEST=value; ' : '') +
-              '__Host-1🍪=🔵cookie-value1🔴; ' +
-              '__Host-2🌟=🌠cookie-value2🌠; ' +
-              '__Host-3🌱=🔶cookie-value3🔷; ' +
-              '__Host-unordered1🍪=🔵unordered-cookie-value1🔴; ' +
-              '__Host-unordered2🌟=🌠unordered-cookie-value2🌠; ' +
-              '__Host-unordered3🌱=🔶unordered-cookie-value3🔷',
-            'All residual cookies before testDeleteCookies at end of test');
-        if (observer) assert_equals(
-            await getCookieStringObserved(),
-            (includeTest('testGetSetGetAll') ? 'TEST=value; ' : '') +
-              '__Host-1🍪=🔵cookie-value1🔴; ' +
-              '__Host-2🌟=🌠cookie-value2🌠; ' +
-              '__Host-3🌱=🔶cookie-value3🔷; ' +
-              '__Host-unordered1🍪=🔵unordered-cookie-value1🔴; ' +
-              '__Host-unordered2🌟=🌠unordered-cookie-value2🌠; ' +
-              '__Host-unordered3🌱=🔶unordered-cookie-value3🔷',
-            'All residual observed cookies before testDeleteCookies ' +
-              'at end of test');
-      }
-      if (kIsUnsecured) {
-        if (!kIsStatic) assert_equals(
-            await getCookieStringHttp(),
-            includeTest('testGetSetGetAll') ? 'TEST=value' : undefined,
-            (includeTest('testGetSetGetAll') ?
-             'Only one unsecured HTTP cookie' :
-             'No unsecured HTTP cookies') +
-              ' before testDeleteCookies at end of test');
-      } else {
-        if (!kIsStatic) assert_equals(
-            await getCookieStringHttp(),
-            (includeTest('testGetSetGetAll') ? 'TEST=value; ' : '') +
-              '__Host-1🍪=🔵cookie-value1🔴; ' +
-              '__Host-2🌟=🌠cookie-value2🌠; ' +
-              '__Host-3🌱=🔶cookie-value3🔷; ' +
-              '__Host-unordered1🍪=🔵unordered-cookie-value1🔴; ' +
-              '__Host-unordered2🌟=🌠unordered-cookie-value2🌠; ' +
-              '__Host-unordered3🌱=🔶unordered-cookie-value3🔷',
-            'All residual HTTP cookies before testDeleteCookies ' +
-              'at end of test');
-      }
-      if (kIsUnsecured) {
-        if (kHasDocument) assert_equals(
-            await getCookieStringDocument(),
-            includeTest('testGetSetGetAll') ? 'TEST=value' : undefined,
-            (includeTest('testGetSetGetAll') ?
-             'Only one unsecured document.cookie cookie' :
-             'No unsecured document.cookie cookies') +
-              ' before testDeleteCookies at end of test');
-      } else {
-        if (kHasDocument) assert_equals(
-            await getCookieStringDocument(),
-            (includeTest('testGetSetGetAll') ? 'TEST=value; ' : '') +
-              '__Host-1🍪=🔵cookie-value1🔴; ' +
-              '__Host-2🌟=🌠cookie-value2🌠; ' +
-              '__Host-3🌱=🔶cookie-value3🔷; ' +
-              '__Host-unordered1🍪=🔵unordered-cookie-value1🔴; ' +
-              '__Host-unordered2🌟=🌠unordered-cookie-value2🌠; ' +
-              '__Host-unordered3🌱=🔶unordered-cookie-value3🔷',
-            'All residual document.cookie cookies before testDeleteCookies ' +
-              'at end of test');
-      }
-      unfinished = false;
-      assert_equals(
-          savedExceptions.length,
-          0,
-          'Found saved exceptions: ' + savedExceptions);
-    } finally {
-      try {
-        await testDeleteCookies(testCase);
-        if (observer) observer.disconnect();
-        await assertEmptyCookieJar(testCase, 'at end of test');
-      } catch (e) {
-        // only re-throw testDeleteCookies failures if finished to avoid masking
-        // earlier failures
-        if (!unfinished) throw e;
-      }
-    }
-  }, 'Cookie Store Tests (' + (testName || 'all') + ')');
-};
-
-
-// Try to clean up cookies and observers used by tests. Also
-// verifies delete() behavior for secure contexts and unsecured
-// contexts.
-//
-// Parameters:
-// - testCase: (TestCase) Context in which the testDeleteCookies is run.
-const testDeleteCookies = async testCase => {
-  await cookieStore.delete('');
-  await cookieStore.delete('TEST');
-  await cookieStore.delete('META-🍪');
-  await cookieStore.delete('DOCUMENT-🍪');
-  await cookieStore.delete('HTTP-🍪');
-  if (!kIsStatic) await setCookieStringHttp(
-      'HTTPONLY-🍪=DELETED; path=/; max-age=0; httponly');
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      cookieStore.delete('__Host-COOKIENAME'));
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      cookieStore.delete('__Host-1🍪'));
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      cookieStore.delete('__Host-2🌟'));
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      cookieStore.delete('__Host-3🌱'));
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      cookieStore.delete('__Host-unordered1🍪'));
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      cookieStore.delete('__Host-unordered2🌟'));
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      cookieStore.delete('__Host-unordered3🌱'));
-};
-
-// Helper to verify first-of-name get using async/await.
-//
-// Returns the first script-visible value of the __Host-COOKIENAME cookie or
-// undefined if no matching cookies are script-visible.
-let getOneSimpleOriginCookie = async () => {
-  let cookie = await cookieStore.get('__Host-COOKIENAME');
-  if (!cookie) return undefined;
-  return cookie.value;
-};
-
-// Returns the number of script-visible cookies whose names start with
-// __Host-COOKIEN
-let countMatchingSimpleOriginCookies = async () => {
-  let cookieList = await cookieStore.getAll({
-    name: '__Host-COOKIEN',
-    matchType: 'startsWith'
-  });
-  return cookieList.length;
-};
-
-// Set the secure implicit-domain cookie __Host-COOKIENAME with value
-// cookie-value on path / and session duration.
-let setOneSimpleOriginSessionCookie = async () => {
-  await cookieStore.set('__Host-COOKIENAME', 'cookie-value');
-};
-
-// Set the secure example.org-domain cookie __Secure-COOKIENAME with
-// value cookie-value on path /cgi-bin/ and 24 hour duration; domain
-// and path will be rewritten below.
-//
-// This uses a Date object for expiration.
-let setOneDaySecureCookieWithDate = async () => {
-  // one day ahead, ignoring a possible leap-second
-  let inTwentyFourHours = new Date(Date.now() + 24 * 60 * 60 * 1000);
-  await cookieStore.set('__Secure-COOKIENAME', 'cookie-value', {
-    path: '/cgi-bin/',
-    expires: inTwentyFourHours,
-    secure: true,
-    domain: 'example.org'
-  });
-};
-
-// Set the unsecured example.org-domain cookie LEGACYCOOKIENAME with
-// value cookie-value on path /cgi-bin/ and 24 hour duration; domain
-// and path will be rewritten below.
-//
-// This uses milliseconds since the start of the Unix epoch for
-// expiration.
-let setOneDayUnsecuredCookieWithMillisecondsSinceEpoch = async () => {
-  // one day ahead, ignoring a possible leap-second
-  let inTwentyFourHours = Date.now() + 24 * 60 * 60 * 1000;
-  await cookieStore.set('LEGACYCOOKIENAME', 'cookie-value', {
-    path: '/cgi-bin/',
-    expires: inTwentyFourHours,
-    secure: false,
-    domain: 'example.org'
-  });
-};
-
-// Delete the cookie written by
-// setOneDayUnsecuredCookieWithMillisecondsSinceEpoch.
-let deleteUnsecuredCookieWithDomainAndPath = async () => {
-  await cookieStore.delete('LEGACYCOOKIENAME', {
-    path: '/cgi-bin/',
-    secure: false,
-    domain: 'example.org'
-  });
-};
-
-
-// Set the secured example.org-domain cookie __Secure-COOKIENAME with
-// value cookie-value on path /cgi-bin/ and expiration in June of next
-// year; domain and path will be rewritten below.
-//
-// This uses an HTTP-style date string for expiration.
-let setSecureCookieWithHttpLikeExpirationString = async () => {
-  const year = (new Date()).getUTCFullYear() + 1;
-  const date = new Date('07 Jun ' + year + ' 07:07:07 UTC');
-  const day = ('Sun Mon Tue Wed Thu Fri Sat'.split(' '))[date.getUTCDay()];
-  await cookieStore.set('__Secure-COOKIENAME', 'cookie-value', {
-    path: '/cgi-bin/',
-    expires: day + ', 07 Jun ' + year + ' 07:07:07 GMT',
-    secure: true,
-    domain: 'example.org'
-  });
-};
-
-// Set three simple origin session cookies sequentially and ensure
-// they all end up in the cookie jar in order.
-let testThreeSimpleOriginSessionCookiesSetSequentially = async () => {
-  await cookieStore.set('__Host-1🍪', '🔵cookie-value1🔴');
-  await cookieStore.set('__Host-2🌟', '🌠cookie-value2🌠');
-  await cookieStore.set('__Host-3🌱', '🔶cookie-value3🔷');
-  // NOTE: this assumes no concurrent writes from elsewhere; it also
-  // uses three separate cookie jar read operations where a single getAll
-  // would be more efficient, but this way the CookieStore does the filtering
-  // for us.
-  let matchingValues = await Promise.all([ '1🍪', '2🌟', '3🌱' ].map(
-      async suffix => (await cookieStore.get('__Host-' + suffix)).value));
-  let actual = matchingValues.join(';');
-  let expected = '🔵cookie-value1🔴;🌠cookie-value2🌠;🔶cookie-value3🔷';
-  if (actual !== expected) throw new Error(
-      'Expected ' + JSON.stringify(expected) +
-        ' but got ' + JSON.stringify(actual));
-};
-
-// Set three simple origin session cookies in undefined order using
-// Promise.all and ensure they all end up in the cookie jar in any
-// order.
-let testThreeSimpleOriginSessionCookiesSetNonsequentially = async () => {
-  await Promise.all([
-    cookieStore.set('__Host-unordered1🍪', '🔵unordered-cookie-value1🔴'),
-    cookieStore.set('__Host-unordered2🌟', '🌠unordered-cookie-value2🌠'),
-    cookieStore.set('__Host-unordered3🌱', '🔶unordered-cookie-value3🔷')
-  ]);
-  // NOTE: this assumes no concurrent writes from elsewhere; it also
-  // uses three separate cookie jar read operations where a single getAll
-  // would be more efficient, but this way the CookieStore does the filtering
-  // for us and we do not need to sort.
-  let matchingCookies = await Promise.all([ '1🍪', '2🌟', '3🌱' ].map(
-      suffix => cookieStore.get('__Host-unordered' + suffix)));
-  let actual = matchingCookies.map(({ value }) => value).join(';');
-  let expected =
-      '🔵unordered-cookie-value1🔴;' +
-      '🌠unordered-cookie-value2🌠;' +
-      '🔶unordered-cookie-value3🔷';
-  if (actual !== expected) throw new Error(
-      'Expected ' + JSON.stringify(expected) +
-        ' but got ' + JSON.stringify(actual));
-};
-
-// Set an already-expired cookie.
-let setExpiredSecureCookieWithDomainPathAndFallbackValue = async () => {
-  let theVeryRecentPast = Date.now();
-  let expiredCookieSentinelValue = 'EXPIRED';
-  await cookieStore.set('__Secure-COOKIENAME', expiredCookieSentinelValue, {
-    path: '/cgi-bin/',
-    expires: theVeryRecentPast,
-    secure: true,
-    domain: 'example.org'
-  });
-};
-
-// Delete the __Host-COOKIENAME cookie created above.
-let deleteSimpleOriginCookie = async () => {
-  await cookieStore.delete('__Host-COOKIENAME');
-};
-
-// Delete the __Secure-COOKIENAME cookie created above.
-let deleteSecureCookieWithDomainAndPath = async () => {
-  await cookieStore.delete('__Secure-COOKIENAME', {
-    path: '/cgi-bin/',
-    domain: 'example.org',
-    secure: true
-  });
-};
-
-// Test for CookieObserver. Used in implementation of async observer-based
-// document.cookie simulator. This is passed to the Promise constructor after
-// rewriting.
-let testObservation_ = (resolve, reject) => {
-  // This will get invoked (asynchronously) shortly after the
-  // observe(...) call to provide an initial snapshot; in that case
-  // the length of cookieChanges may be 0, indicating no matching
-  // script-visible cookies for any URL+cookieStore currently
-  // observed. The CookieObserver instance is passed as the second
-  // parameter to allow additional calls to observe or disconnect.
-  let callback = (cookieChanges, observer) => {
-    var logEntry = [];
-    observationLog.push(logEntry);
-    const cookieChangesStrings = changes => changes.map(
-        ({type, name, value, index}) => cookieString(Object.assign(
-            new Array(observedStore.length),
-            {[index]: {
-              name: ((type === 'visible') ? '+' : '-') + name,
-              value: value
-            }})));
-    logEntry.push(['before', cookieString(observedStore)]);
-    logEntry.push(['changes', cookieChangesStrings(cookieChanges)]);
-    const newObservedStore = observedStore.slice(0);
-    try {
-      const insertions = [], deletions = [];
-      cookieChanges.forEach(({
-        cookieStore,
-        type,
-        url,
-        name,
-        value,
-        index,
-        all
-      }) => {
-        switch (type) {
-          case 'visible':
-            // Creation or modification (e.g. change in value, or
-            // removal of HttpOnly), or appearance to script due to
-            // change in policy or permissions
-            insertions.push([index, {name: name, value: value}]);
-            break;
-          case 'hidden':
-            // Deletion/expiration or disappearance (e.g. due to
-            // modification adding HttpOnly), or disappearance from
-            // script due to change in policy or permissions
-            assert_object_equals(
-                {name: name, value: value},
-                observedStore[index],
-                'Hidden cookie at index ' + index +
-                  ' was not the expected one: ' + JSON.stringify({
-                    got: {name: name, value: value},
-                    expected: observedStore[index]
-                  }));
-            deletions.push(index);
-            break;
-          default:
-            savedExceptions.push('Unexpected CookieChange type ' + type);
-            if (reject) reject(savedExceptions[savedExceptions.length - 1]);
-            throw savedExceptions[savedExceptions.length - 1];
-        }
-      });
-      deletions.sort((a, b) => b - a).forEach(
-          index => newObservedStore.splice(index, 1));
-      let bias = 0;
-      insertions.sort(([a], [b]) => a - b).forEach(([ index, cookie ]) => {
-        if (newObservedStore[index + bias] !== undefined) {
-          newObservedStore.splice(index, 0, cookie);
-          --bias;
-        } else {
-          newObservedStore[index] = cookie;
-        }
-      });
-      observedStore = newObservedStore.filter(entry => entry !== undefined);
-      logEntry.push(['after', cookieString(observedStore)]);
-      const reported =
-            cookieChanges && cookieChanges.length ?
-            cookieChanges[cookieChanges.length - 1].all :
-            [];
-      assert_equals(
-          cookieString(reported),
-          cookieString(observedStore),
-          'Mismatch between observed store and reported store.' +
-            '\n observed:\n ' + cookieString(observedStore) +
-            '\n reported:\n ' + cookieString(reported) +
-            '\n log:\n ' + observationLog.map(JSON.stringify).join('\n '));
-    } catch (e) {
-      logEntry.push([' *** ⚠ *** ERROR: EXCEPTION THROWN *** ⚠ *** ']);
-      savedExceptions.push('Exception in observer');
-      savedExceptions.push(e);
-      if (reject) reject(e);
-      throw e;
-    }
-    // Resolve promise after first callback
-    if (resolve) resolve(observer);
-    resolve = null;
-    reject = null;
-  };
-  CookieObserver.startTimer_ = (handler, ignoredDelay) => {
-    var timer = {shouldRun: true, fingerPrint: Math.random()};
-    new Promise(resolve => s\u0065tTimeout(resolve)).then(() => {
-      if (!timer.shouldRun) return;
-      CookieObserver.stopTimer_(timer);
-      handler();
-    });
-    return timer;
-  };
-  CookieObserver.stopTimer_ = timer => {
-    timer.shouldRun = false;
-  };
-  let observer = new CookieObserver(callback);
-  // If null or omitted this defaults to location.pathname up to and
-  // including the final '/' in a document context, or worker scope up
-  // to and including the final '/' in a service worker context.
-  let url = (location.pathname).replace(/[^\/]+$/, '');
-  // If null or omitted this defaults to interest in all
-  // script-visible cookies.
-  let interests = [
-    // Interested in all secure cookies named '__Secure-COOKIENAME';
-    // the default matchType is 'equals' at the given URL.
-    { name: '__Secure-COOKIENAME', url: url },
-    // Interested in all simple origin cookies named like
-    // /^__Host-COOKIEN.*$/ at the default URL.
-    { name: '__Host-COOKIEN', matchType: 'startsWith' },
-    // Interested in all simple origin cookies named '__Host-1🍪'
-    // at the default URL.
-    { name: '__Host-1🍪' },
-    // Interested in all cookies named 'OLDCOOKIENAME' at the given URL.
-    { name: 'OLDCOOKIENAME', matchType: 'equals', url: url },
-    // Interested in all simple origin cookies named like
-    // /^__Host-AUTHTOKEN.*$/ at the given URL.
-    { name: '__Host-AUTHTOKEN', matchType: 'startsWith', url: url + 'auth/' }
-  ];
-  observer.observe(cookieStore, interests);
-  // Default interest: all script-visible changes, default URL
-  observer.observe(cookieStore);
-};
-
-// Rewrite testObservation_ to use a path we are allowed to see from a
-// document context.
-//
-// FIXME: remove this once ServiceWorker support is implemented and
-// path observation can actually be verified at a sub-path.
-if (kHasDocument) {
-  testObservation_ = eval(String(testObservation_).split('auth/').join('auth'));
-}
-
-// Wrap testObservation_ to work as a promise.
-const testObservation = () => new Promise(testObservation_);
-
-// Verify behavior of no-name and no-value cookies.
-let testNoNameAndNoValue = async () => {
-  await cookieStore.set('', 'first-value');
-  let actual1 =
-      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
-  let expected1 = 'first-value';
-  if (actual1 !== expected1) throw new Error(
-      'Expected ' + JSON.stringify(expected1) +
-        ' but got ' + JSON.stringify(actual1));
-  await cookieStore.set('', '');
-  let actual2 =
-      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
-  let expected2 = '';
-  if (actual2 !== expected2) throw new Error(
-      'Expected ' + JSON.stringify(expected) +
-        ' but got ' + JSON.stringify(actual));
-  await cookieStore.delete('');
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'Empty cookie jar after testNoNameAndNoValue');
-  if (!kIsStatic) assert_equals(
-      await getCookieStringHttp(),
-      undefined,
-      'Empty HTTP cookie jar after testNoNameAndNoValue');
-  if (kHasDocument) assert_equals(
-      await getCookieStringDocument(),
-      undefined,
-      'Empty document.cookie cookie jar after testNoNameAndNoValue');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      undefined,
-      'Empty observed cookie jar after testNoNameAndNoValue');
-};
-
-// Verify behavior of multiple no-name cookies.
-let testNoNameMultipleValues = async () => {
-  await cookieStore.set('', 'first-value');
-  let actual1 =
-      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
-  let expected1 = 'first-value';
-  if (actual1 !== expected1) throw new Error(
-      'Expected ' + JSON.stringify(expected1) +
-        ' but got ' + JSON.stringify(actual1));
-  await cookieStore.set('', 'second-value');
-  let actual2 =
-      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
-  let expected2 = 'second-value';
-  if (actual2 !== expected2) throw new Error(
-      'Expected ' + JSON.stringify(expected2) +
-        ' but got ' + JSON.stringify(actual2));
-  await cookieStore.delete('');
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'Empty cookie jar after testNoNameMultipleValues');
-  if (!kIsStatic) assert_equals(
-      await getCookieStringHttp(),
-      undefined,
-      'Empty HTTP cookie jar after testNoNameMultipleValues');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      undefined,
-      'Empty observed cookie jar after testNoNameMultipleValues');
-};
-
-// Verify that attempting to set a cookie with no name and with '=' in
-// the value does not work.
-let testNoNameEqualsInValue = async () => {
-  await cookieStore.set('', 'first-value');
-  let actual1 =
-      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
-  let expected1 = 'first-value';
-  if (actual1 !== expected1) throw new Error(
-      'Expected ' + JSON.stringify(expected1) +
-        ' but got ' + JSON.stringify(actual1));
-  try {
-    await cookieStore.set('', 'suspicious-value=resembles-name-and-value');
-  } catch (expectedError) {
-    let actual2 =
-        (await cookieStore.getAll('')).map(({ value }) => value).join(';');
-    let expected2 = 'first-value';
-    if (actual2 !== expected2) throw new Error(
-        'Expected ' + JSON.stringify(expected2) +
-          ' but got ' + JSON.stringify(actual2));
-    assert_equals(
-        await getCookieString(),
-        'first-value',
-        'Earlier cookie jar after rejected part of testNoNameEqualsInValue');
-    await cookieStore.delete('');
-    assert_equals(
-        await getCookieString(),
-        undefined,
-        'Empty cookie jar after cleanup in testNoNameEqualsInValue');
-    if (!kIsStatic) assert_equals(
-        await getCookieStringHttp(),
-        undefined,
-        'Empty HTTP cookie jar after cleanup in testNoNameEqualsInValue');
-    if (observer) assert_equals(
-        await getCookieStringObserved(),
-        undefined,
-        'Empty observed cookie jar after cleanup in testNoNameEqualsInValue');
-    return;
-  }
-  throw new Error(
-      'Expected promise rejection' +
-        ' when setting a cookie with no name and "=" in value');
-};
-
-// https://github.com/whatwg/html/issues/3076#issuecomment-332920132
-// proposes to remove <meta http-equiv="set-cookie" ... > but it is
-// not yet an accepted part of the HTML spec.
-//
-// Until the feature is gone, it interacts with other cookie APIs,
-// including this one.
-//
-// When kMetaHttpEquivSetCookieIsGone is set, verify that <meta
-// http-equiv="set-cookie" ... > no longer works. Otherwise, verify
-// its interoperability with other APIs.
-let testMetaHttpEquivSetCookie = async () => {
-  await setCookieStringMeta('META-🍪=🔵; path=/');
-  if (kMetaHttpEquivSetCookieIsGone) {
-    assert_equals(
-        await getCookieString(),
-        undefined,
-        'Empty cookie jar after no-longer-supported' +
-          ' <meta http-equiv="set-cookie" ... >');
-    if (!kIsStatic) assert_equals(
-        await getCookieStringHttp(),
-        undefined,
-        'Empty HTTP cookie jar after no-longer-supported' +
-          ' <meta http-equiv="set-cookie" ... >');
-    if (observer) assert_equals(
-        await getCookieStringObserved(),
-        undefined,
-        'Empty observed cookie jar after no-longer-supported' +
-          ' <meta http-equiv="set-cookie" ... >');
-  } else {
-    assert_equals(
-        await getCookieString(),
-        'META-🍪=🔵',
-        'Cookie we wrote using' +
-          ' <meta http-equiv="set-cookie" ... > in cookie jar');
-    if (!kIsStatic) assert_equals(
-        await getCookieStringHttp(),
-        'META-🍪=🔵',
-        'Cookie we wrote using' +
-          ' <meta http-equiv="set-cookie" ... > in HTTP cookie jar');
-    if (observer) assert_equals(
-        await getCookieStringObserved(),
-        'META-🍪=🔵',
-        'Cookie we wrote using' +
-          ' <meta http-equiv="set-cookie" ... > in observed cookie jar');
-    await setCookieStringMeta('META-🍪=DELETED; path=/; max-age=0');
-    assert_equals(
-        await getCookieString(),
-        undefined,
-        'Empty cookie jar after <meta http-equiv="set-cookie" ... >' +
-          ' cookie-clearing using max-age=0');
-    if (!kIsStatic) assert_equals(
-        await getCookieStringHttp(),
-        undefined,
-        'Empty HTTP cookie jar after <meta http-equiv="set-cookie" ... >' +
-          ' cookie-clearing using max-age=0');
-    if (observer) assert_equals(
-        await getCookieStringObserved(),
-        undefined,
-        'Empty observed cookie jar after <meta http-equiv="set-cookie" ... >' +
-          ' cookie-clearing using max-age=0');
-  }
-};
-
-// Verify interoperability of document.cookie with other APIs.
-let testDocumentCookie = async () => {
-  await setCookieStringDocument('DOCUMENT-🍪=🔵; path=/');
-  assert_equals(
-      await getCookieString(),
-      'DOCUMENT-🍪=🔵',
-      'Cookie we wrote using document.cookie in cookie jar');
-  if (!kIsStatic) assert_equals(
-      await getCookieStringHttp(),
-      'DOCUMENT-🍪=🔵',
-      'Cookie we wrote using document.cookie in HTTP cookie jar');
-  assert_equals(
-      await getCookieStringDocument(),
-      'DOCUMENT-🍪=🔵',
-      'Cookie we wrote using document.cookie in document.cookie');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      'DOCUMENT-🍪=🔵',
-      'Cookie we wrote using document.cookie in observed cookie jar');
-  await setCookieStringDocument('DOCUMENT-🍪=DELETED; path=/; max-age=0');
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'Empty cookie jar after document.cookie' +
-        ' cookie-clearing using max-age=0');
-  if (!kIsStatic) assert_equals(
-      await getCookieStringHttp(),
-      undefined,
-      'Empty HTTP cookie jar after document.cookie' +
-        ' cookie-clearing using max-age=0');
-  assert_equals(
-      await getCookieStringDocument(),
-      undefined,
-      'Empty document.cookie cookie jar after document.cookie' +
-        ' cookie-clearing using max-age=0');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      undefined,
-      'Empty observed cookie jar after document.cookie cookie-clearing' +
-        ' using max-age=0');
-};
-
-// Verify interoperability of HTTP Set-Cookie: with other APIs.
-let testHttpCookieAndSetCookieHeaders = async () => {
-  await setCookieStringHttp('HTTP-🍪=🔵; path=/');
-  assert_equals(
-      await getCookieString(),
-      'HTTP-🍪=🔵',
-      'Cookie we wrote using HTTP in cookie jar');
-  assert_equals(
-      await getCookieStringHttp(),
-      'HTTP-🍪=🔵',
-      'Cookie we wrote using HTTP in HTTP cookie jar');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      'HTTP-🍪=🔵',
-      'Cookie we wrote using HTTP in observed cookie jar');
-  await setCookieStringHttp('HTTP-🍪=DELETED; path=/; max-age=0');
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'Empty cookie jar after HTTP cookie-clearing using max-age=0');
-  assert_equals(
-      await getCookieStringHttp(),
-      undefined,
-      'Empty HTTP cookie jar after HTTP cookie-clearing using max-age=0');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      undefined,
-      'Empty observed cookie jar after HTTP cookie-clearing' +
-        ' using max-age=0');
-  await setCookieStringHttp('HTTPONLY-🍪=🔵; path=/; httponly');
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'HttpOnly cookie we wrote using HTTP in cookie jar' +
-        ' is invisible to script');
-  assert_equals(
-      await getCookieStringHttp(),
-      'HTTPONLY-🍪=🔵',
-      'HttpOnly cookie we wrote using HTTP in HTTP cookie jar');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      undefined,
-      'HttpOnly cookie we wrote using HTTP is invisible to observer');
-  await setCookieStringHttp(
-      'HTTPONLY-🍪=DELETED; path=/; max-age=0; httponly');
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'Empty cookie jar after HTTP cookie-clearing using max-age=0');
-  assert_equals(
-      await getCookieStringHttp(),
-      undefined,
-      'Empty HTTP cookie jar after HTTP cookie-clearing using max-age=0');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      undefined,
-      'Empty observed cookie jar after HTTP cookie-clearing' +
-        ' using max-age=0');
-  // Non-UTF-8 byte sequences cause the Set-Cookie to be dropped.
-  await setCookieBinaryHttp(
-      unescape(encodeURIComponent('HTTP-🍪=🔵')) + '\xef\xbf\xbd; path=/');
-  assert_equals(
-      await getCookieString(),
-      'HTTP-🍪=🔵\ufffd',
-      'Binary cookie we wrote using HTTP in cookie jar');
-  assert_equals(
-      await getCookieStringHttp(),
-      'HTTP-🍪=🔵\ufffd',
-      'Binary cookie we wrote using HTTP in HTTP cookie jar');
-  assert_equals(
-      decodeURIComponent(escape(await getCookieBinaryHttp())),
-      'HTTP-🍪=🔵\ufffd',
-      'Binary cookie we wrote in binary HTTP cookie jar');
-  assert_equals(
-      await getCookieBinaryHttp(),
-      unescape(encodeURIComponent('HTTP-🍪=🔵')) + '\xef\xbf\xbd',
-      'Binary cookie we wrote in binary HTTP cookie jar');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      'HTTP-🍪=🔵\ufffd',
-      'Binary cookie we wrote using HTTP in observed cookie jar');
-  await setCookieBinaryHttp(
-      unescape(encodeURIComponent('HTTP-🍪=DELETED; path=/; max-age=0')));
-  assert_equals(
-      await getCookieString(),
-      undefined,
-      'Empty cookie jar after binary HTTP cookie-clearing using max-age=0');
-  assert_equals(
-      await getCookieStringHttp(),
-      undefined,
-      'Empty HTTP cookie jar after' +
-        ' binary HTTP cookie-clearing using max-age=0');
-  assert_equals(
-      await getCookieBinaryHttp(),
-      undefined,
-      'Empty binary HTTP cookie jar after' +
-        ' binary HTTP cookie-clearing using max-age=0');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      undefined,
-      'Empty observed cookie jar after binary HTTP cookie-clearing' +
-        ' using max-age=0');
-};
-
-const testGetSetGetAll = async () => {
-  await cookieStore.set('TEST', 'value0');
-  assert_equals(
-      await getCookieString(),
-      'TEST=value0',
-      'Cookie jar contains only cookie we set');
-  if (!kIsStatic) assert_equals(
-      await getCookieStringHttp(),
-      'TEST=value0',
-      'HTTP cookie jar contains only cookie we set');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      'TEST=value0',
-      'Observed cookie jar contains only cookie we set');
-  await cookieStore.set('TEST', 'value');
-  assert_equals(
-      await getCookieString(),
-      'TEST=value',
-      'Cookie jar contains only cookie we overwrote');
-  if (!kIsStatic) assert_equals(
-      await getCookieStringHttp(),
-      'TEST=value',
-      'HTTP cookie jar contains only cookie we overwrote');
-  if (observer) assert_equals(
-      await getCookieStringObserved(),
-      'TEST=value',
-      'Observed cookie jar contains only cookie we overwrote');
-  let allCookies = await cookieStore.getAll();
-  assert_equals(
-      allCookies[0].name,
-      'TEST',
-      'First entry in allCookies should be named TEST');
-  assert_equals(
-      allCookies[0].value,
-      'value',
-      'First entry in allCookies should have value "value"');
-  assert_equals(
-      allCookies.length,
-      1,
-      'Only one cookie should exist in allCookies');
-  let firstCookie = await cookieStore.get();
-  assert_equals(
-      firstCookie.name,
-      'TEST',
-      'First cookie should be named TEST');
-  assert_equals(
-      firstCookie.value,
-      'value',
-      'First cookie should have value "value"');
-  let allCookies_TEST = await cookieStore.getAll('TEST');
-  assert_equals(
-      allCookies_TEST[0].name,
-      'TEST',
-      'First entry in allCookies_TEST should be named TEST');
-  assert_equals(
-      allCookies_TEST[0].value,
-      'value',
-      'First entry in allCookies_TEST should have value "value"');
-  assert_equals(
-      allCookies_TEST.length,
-      1,
-      'Only one cookie should exist in allCookies_TEST');
-  let firstCookie_TEST = await cookieStore.get('TEST');
-  assert_equals(
-      firstCookie_TEST.name,
-      'TEST',
-      'First TEST cookie should be named TEST');
-  assert_equals(
-      firstCookie_TEST.value,
-      'value',
-      'First TEST cookie should have value "value"');
-};
-
-const testOneSimpleOriginCookie = async testCase => {
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      setOneSimpleOriginSessionCookie(),
-      '__Host- prefix only writable from' +
-        ' secure contexts (setOneSimpleOriginSessionCookie)');
-  if (!kIsUnsecured) {
-    assert_equals(
-        await getOneSimpleOriginCookie(),
-        'cookie-value',
-        '__Host-COOKIENAME cookie should be found' +
-          ' in a secure context (getOneSimpleOriginCookie)');
-  } else {
-    assert_equals(
-        await getOneSimpleOriginCookie(),
-        undefined,
-        '__Host-COOKIENAME cookie should not be found' +
-          ' in an unsecured context (getOneSimpleOriginCookie)');
-  }
-  if (kIsUnsecured) {
-    assert_equals(
-        await countMatchingSimpleOriginCookies(),
-        0,
-        'No __Host-COOKIEN* cookies should be found' +
-          ' in an unsecured context (countMatchingSimpleOriginCookies)');
-  } else {
-    assert_equals(
-        await countMatchingSimpleOriginCookies(),
-        1,
-        'One __Host-COOKIEN* cookie should be found' +
-          ' in a secure context (countMatchingSimpleOriginCookies)');
-  }
-};
-
-const testExpiration = async testCase => {
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      setOneDaySecureCookieWithDate(),
-      'Secure cookies only writable' +
-        ' from secure contexts (setOneDaySecureCookieWithDate)');
-  await setOneDayUnsecuredCookieWithMillisecondsSinceEpoch();
-  assert_equals(
-      await getCookieString('LEGACYCOOKIENAME'),
-      'LEGACYCOOKIENAME=cookie-value',
-      'Ensure unsecured cookie we set is visible');
-  if (observer) assert_equals(
-      await getCookieStringObserved('LEGACYCOOKIENAME'),
-      'LEGACYCOOKIENAME=cookie-value',
-      'Ensure unsecured cookie we set is visible to observer');
-  await deleteUnsecuredCookieWithDomainAndPath();
-  await promise_rejects_when_unsecured(
-      testCase,
-      new TypeError(),
-      setSecureCookieWithHttpLikeExpirationString(),
-      'Secure cookies only writable from secure contexts' +
-        ' (setSecureCookieWithHttpLikeExpirationString)');
-};
-
-// Rewrite domain and path in affected cases to match current test
-// domain and directory.
-//
-// FIXME: remove these once ServiceWorker support and cross-domain
-// testing are added and full domain and path coverage is possible.
-setOneDaySecureCookieWithDate =
-    eval(String(setOneDaySecureCookieWithDate).split(
-        '/cgi-bin/').join(location.pathname.replace(/[^/]+$/, '')));
-setOneDaySecureCookieWithDate =
-    eval(String(setOneDaySecureCookieWithDate).split(
-        'example.org').join(location.hostname));
-setOneDayUnsecuredCookieWithMillisecondsSinceEpoch =
-    eval(String(setOneDayUnsecuredCookieWithMillisecondsSinceEpoch).split(
-        '/cgi-bin/').join(location.pathname.replace(/[^/]+$/, '')));
-setOneDayUnsecuredCookieWithMillisecondsSinceEpoch =
-    eval(String(setOneDayUnsecuredCookieWithMillisecondsSinceEpoch).split(
-        'example.org').join(location.hostname));
-deleteUnsecuredCookieWithDomainAndPath =
-    eval(String(deleteUnsecuredCookieWithDomainAndPath).split(
-        '/cgi-bin/').join(location.pathname.replace(/[^/]+$/, '')));
-deleteUnsecuredCookieWithDomainAndPath =
-    eval(String(deleteUnsecuredCookieWithDomainAndPath).split(
-        'example.org').join(location.hostname));
-setSecureCookieWithHttpLikeExpirationString =
-    eval(String(setSecureCookieWithHttpLikeExpirationString).split(
-        '/cgi-bin/').join(location.pathname.replace(/[^/]+$/, '')));
-setSecureCookieWithHttpLikeExpirationString =
-    eval(String(setSecureCookieWithHttpLikeExpirationString).split(
-        'example.org').join(location.hostname));
-setExpiredSecureCookieWithDomainPathAndFallbackValue =
-    eval(String(setExpiredSecureCookieWithDomainPathAndFallbackValue).split(
-        '/cgi-bin/').join(location.pathname.replace(/[^/]+$/, '')));
-setExpiredSecureCookieWithDomainPathAndFallbackValue =
-    eval(String(setExpiredSecureCookieWithDomainPathAndFallbackValue).split(
-        'example.org').join(location.hostname));
-deleteSecureCookieWithDomainAndPath =
-    eval(String(deleteSecureCookieWithDomainAndPath).split(
-        '/cgi-bin/').join(location.pathname.replace(/[^/]+$/, '')));
-deleteSecureCookieWithDomainAndPath =
-    eval(String(deleteSecureCookieWithDomainAndPath).split(
-        'example.org').join(location.hostname));
diff --git a/cookie-store/resources/cookie-test-helpers.js b/cookie-store/resources/cookie-test-helpers.js
new file mode 100644
index 0000000..178947a
--- /dev/null
+++ b/cookie-store/resources/cookie-test-helpers.js
@@ -0,0 +1,226 @@
+'use strict';
+
+// TODO(jsbell): Once ServiceWorker is supported, add arbitrary path coverage.
+const kPath = location.pathname.replace(/[^/]+$/, '');
+
+// True when running in a document context as opposed to a worker context
+const kHasDocument = typeof document !== 'undefined';
+
+// True when running on unsecured 'http:' rather than secured 'https:'.
+const kIsUnsecured = location.protocol !== 'https:';
+
+const kCookieHelperCgi = 'resources/cookie_helper.py';
+
+// Approximate async equivalent to the document.cookie getter but with
+// important differences: optional additional getAll arguments are
+// forwarded, and an empty cookie jar returns undefined.
+//
+// This is intended primarily for verification against expected cookie
+// jar contents. It should produce more readable messages using
+// assert_equals in failing cases than assert_object_equals would
+// using parsed cookie jar contents and also allows expectations to be
+// written more compactly.
+async function getCookieString(...args) {
+  const cookies = await cookieStore.getAll(...args);
+  return cookies.length
+    ? cookies.map(({name, value}) =>
+                  (name ? (name + '=') : '') + value).join('; ')
+    : undefined;
+}
+
+// Approximate async equivalent to the document.cookie getter but from
+// the server's point of view. Returns UTF-8 interpretation. Allows
+// sub-path to be specified.
+//
+// Unlike document.cookie, this returns undefined when no cookies are
+// present.
+async function getCookieStringHttp(extraPath = null) {
+  const url =
+        kCookieHelperCgi + ((extraPath == null) ? '' : ('/' + extraPath));
+  const response = await fetch(url, { credentials: 'include' });
+  const text = await response.text();
+  assert_equals(
+      response.ok,
+      true,
+      'CGI should have succeeded in getCookieStringHttp\n' + text);
+  assert_equals(
+      response.headers.get('content-type'),
+      'text/plain; charset=utf-8',
+      'CGI did not return UTF-8 text in getCookieStringHttp');
+  if (text === '')
+    return undefined;
+  assert_equals(
+      text.indexOf('cookie='),
+      0,
+      'CGI response did not begin with "cookie=" and was not empty: ' + text);
+  return decodeURIComponent(text.replace(/^cookie=/, ''));
+}
+
+// Approximate async equivalent to the document.cookie getter but from
+// the server's point of view. Returns binary string
+// interpretation. Allows sub-path to be specified.
+//
+// Unlike document.cookie, this returns undefined when no cookies are
+// present.
+async function getCookieBinaryHttp(extraPath = null) {
+  const url =
+        kCookieHelperCgi +
+        ((extraPath == null) ?
+         '' :
+         ('/' + extraPath)) + '?charset=iso-8859-1';
+  const response = await fetch(url, { credentials: 'include' });
+  const text = await response.text();
+  assert_equals(
+      response.ok,
+      true,
+      'CGI should have succeeded in getCookieBinaryHttp\n' + text);
+  assert_equals(
+      response.headers.get('content-type'),
+      'text/plain; charset=iso-8859-1',
+      'CGI did not return ISO 8859-1 text in getCookieBinaryHttp');
+  if (text === '')
+    return undefined;
+  assert_equals(
+      text.indexOf('cookie='),
+      0,
+      'CGI response did not begin with "cookie=" and was not empty: ' + text);
+  return unescape(text.replace(/^cookie=/, ''));
+}
+
+// Approximate async equivalent to the document.cookie setter but from
+// the server's point of view.
+async function setCookieStringHttp(setCookie) {
+  const encodedSetCookie = encodeURIComponent(setCookie);
+  const url = kCookieHelperCgi;
+  const headers = new Headers();
+  headers.set(
+      'content-type',
+      'application/x-www-form-urlencoded; charset=utf-8');
+  const response = await fetch(
+      url,
+      {
+        credentials: 'include',
+        method: 'POST',
+        headers: headers,
+        body: 'set-cookie=' + encodedSetCookie,
+      });
+  const text = await response.text();
+  assert_equals(
+      response.ok,
+      true,
+      'CGI should have succeeded in setCookieStringHttp set-cookie: ' +
+        setCookie + '\n' + text);
+  assert_equals(
+      response.headers.get('content-type'),
+      'text/plain; charset=utf-8',
+      'CGI did not return UTF-8 text in setCookieStringHttp');
+  assert_equals(
+      text,
+      'set-cookie=' + encodedSetCookie,
+      'CGI did not faithfully echo the set-cookie value');
+}
+
+// Approximate async equivalent to the document.cookie setter but from
+// the server's point of view. This version sets a binary cookie rather
+// than a UTF-8 one.
+async function setCookieBinaryHttp(setCookie) {
+  const encodedSetCookie = escape(setCookie).split('/').join('%2F');
+  const url = kCookieHelperCgi + '?charset=iso-8859-1';
+  const headers = new Headers();
+  headers.set(
+      'content-type',
+      'application/x-www-form-urlencoded; charset=iso-8859-1');
+  const response = await fetch(url, {
+    credentials: 'include',
+    method: 'POST',
+    headers: headers,
+    body: 'set-cookie=' + encodedSetCookie
+  });
+  const text = await response.text();
+  assert_equals(
+      response.ok,
+      true,
+      'CGI should have succeeded in setCookieBinaryHttp set-cookie: ' +
+        setCookie + '\n' + text);
+  assert_equals(
+      response.headers.get('content-type'),
+      'text/plain; charset=iso-8859-1',
+      'CGI did not return Latin-1 text in setCookieBinaryHttp');
+  assert_equals(
+      text,
+      'set-cookie=' + encodedSetCookie,
+      'CGI did not faithfully echo the set-cookie value');
+}
+
+// Async document.cookie getter; converts '' to undefined which loses
+// information in the edge case where a single ''-valued anonymous
+// cookie is visible.
+async function getCookieStringDocument() {
+  if (!kHasDocument)
+    throw 'document.cookie not available in this context';
+  return String(document.cookie || '') || undefined;
+}
+
+// Async document.cookie setter
+async function setCookieStringDocument(setCookie) {
+  if (!kHasDocument)
+    throw 'document.cookie not available in this context';
+  document.cookie = setCookie;
+}
+
+// Observe the next 'change' event on the cookieStore. Typical usage:
+//
+//   const eventPromise = observeNextCookieChangeEvent();
+//   await /* something that modifies cookies */
+//   await verifyCookieChangeEvent(
+//     eventPromise, {changed: [{name: 'name', value: 'value'}]});
+//
+function observeNextCookieChangeEvent() {
+  return new Promise(resolve => {
+    cookieStore.addEventListener('change', e => resolve(e), {once: true});
+  });
+}
+
+async function verifyCookieChangeEvent(eventPromise, expected, description) {
+  description = description ? description + ': ' : '';
+  expected = Object.assign({changed:[], deleted:[]}, expected);
+  const event = await eventPromise;
+  assert_equals(event.changed.length, expected.changed.length,
+               description + 'number of changed cookies');
+  for (let i = 0; i < event.changed.length; ++i) {
+    assert_equals(event.changed[i].name, expected.changed[i].name,
+                 description + 'changed cookie name');
+    assert_equals(event.changed[i].value, expected.changed[i].value,
+                 description + 'changed cookie value');
+  }
+  assert_equals(event.deleted.length, expected.deleted.length,
+               description + 'number of deleted cookies');
+  for (let i = 0; i < event.deleted.length; ++i) {
+    assert_equals(event.deleted[i].name, expected.deleted[i].name,
+                 description + 'deleted cookie name');
+    assert_equals(event.deleted[i].value, expected.deleted[i].value,
+                 description + 'deleted cookie value');
+  }
+}
+
+// Helper function for promise_test with cookies; cookies
+// named in these tests are cleared before/after the test
+// body function is executed.
+async function cookie_test(func, description) {
+
+  // Wipe cookies used by tests before and after the test.
+  async function deleteAllCookies() {
+    (await cookieStore.getAll()).forEach(({name, value}) => {
+      cookieStore.delete(name);
+    });
+  }
+
+  return promise_test(async t => {
+    await deleteAllCookies();
+    try {
+      return await func(t);
+    } finally {
+      await deleteAllCookies();
+    }
+  }, description);
+}
diff --git a/cookie-store/resources/document_cookie.js b/cookie-store/resources/document_cookie.js
new file mode 100644
index 0000000..980347f
--- /dev/null
+++ b/cookie-store/resources/document_cookie.js
@@ -0,0 +1,157 @@
+'use strict';
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringDocument('DOCUMENT-cookie=value; path=/');
+  assert_equals(
+      await getCookieString(),
+      'DOCUMENT-cookie=value',
+      'Cookie we wrote using document.cookie in cookie jar');
+  assert_equals(
+    await getCookieStringHttp(),
+    'DOCUMENT-cookie=value',
+    'Cookie we wrote using document.cookie in HTTP cookie jar');
+  assert_equals(
+      await getCookieStringDocument(),
+      'DOCUMENT-cookie=value',
+      'Cookie we wrote using document.cookie in document.cookie');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'DOCUMENT-cookie', value: 'value'}]},
+      'Cookie we wrote using document.cookie is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringDocument('DOCUMENT-cookie=new-value; path=/');
+  assert_equals(
+      await getCookieString(),
+      'DOCUMENT-cookie=new-value',
+      'Cookie we overwrote using document.cookie in cookie jar');
+  assert_equals(
+    await getCookieStringHttp(),
+    'DOCUMENT-cookie=new-value',
+    'Cookie we overwrote using document.cookie in HTTP cookie jar');
+  assert_equals(
+      await getCookieStringDocument(),
+      'DOCUMENT-cookie=new-value',
+      'Cookie we overwrote using document.cookie in document.cookie');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'DOCUMENT-cookie', value: 'new-value'}]},
+      'Cookie we overwrote using document.cookie is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringDocument('DOCUMENT-cookie=DELETED; path=/; max-age=0');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'Empty cookie jar after document.cookie' +
+        ' cookie-clearing using max-age=0');
+  assert_equals(
+    await getCookieStringHttp(),
+    undefined,
+    'Empty HTTP cookie jar after document.cookie' +
+        ' cookie-clearing using max-age=0');
+  assert_equals(
+      await getCookieStringDocument(),
+      undefined,
+      'Empty document.cookie cookie jar after document.cookie' +
+        ' cookie-clearing using max-age=0');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: 'DOCUMENT-cookie'}]},
+      'Deletion observed after document.cookie cookie-clearing' +
+        ' using max-age=0');
+}, 'document.cookie set/overwrite/delete observed by CookieStore');
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('DOCUMENT-cookie', 'value');
+  assert_equals(
+      await getCookieString(),
+      'DOCUMENT-cookie=value',
+      'Cookie we wrote using CookieStore in cookie jar');
+  assert_equals(
+    await getCookieStringHttp(),
+    'DOCUMENT-cookie=value',
+    'Cookie we wrote using CookieStore in HTTP cookie jar');
+  assert_equals(
+      await getCookieStringDocument(),
+      'DOCUMENT-cookie=value',
+      'Cookie we wrote using CookieStore in document.cookie');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'DOCUMENT-cookie', value: 'value'}]},
+      'Cookie we wrote using CookieStore is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('DOCUMENT-cookie', 'new-value');
+  assert_equals(
+      await getCookieString(),
+      'DOCUMENT-cookie=new-value',
+      'Cookie we overwrote using CookieStore in cookie jar');
+  assert_equals(
+    await getCookieStringHttp(),
+    'DOCUMENT-cookie=new-value',
+    'Cookie we overwrote using CookieStore in HTTP cookie jar');
+  assert_equals(
+      await getCookieStringDocument(),
+      'DOCUMENT-cookie=new-value',
+      'Cookie we overwrote using CookieStore in document.cookie');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'DOCUMENT-cookie', value: 'new-value'}]},
+      'Cookie we overwrote using CookieStore is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.delete('DOCUMENT-cookie');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'Empty cookie jar after CookieStore delete');
+  assert_equals(
+    await getCookieStringHttp(),
+    undefined,
+    'Empty HTTP cookie jar after CookieStore delete');
+  assert_equals(
+      await getCookieStringDocument(),
+      undefined,
+      'Empty document.cookie cookie jar after CookieStore delete');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: 'DOCUMENT-cookie'}]},
+      'Deletion observed after CookieStore delete');
+}, 'CookieStore set/overwrite/delete observed by document.cookie');
+
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringDocument('DOCUMENT-🍪=🔵; path=/');
+  assert_equals(
+      await getCookieString(),
+      'DOCUMENT-🍪=🔵',
+      'Cookie we wrote using document.cookie in cookie jar');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'DOCUMENT-🍪', value: '🔵'}]},
+      'Cookie we wrote using document.cookie is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringDocument('DOCUMENT-🍪=DELETED; path=/; max-age=0');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'Empty cookie jar after document.cookie' +
+        ' cookie-clearing using max-age=0');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: 'DOCUMENT-🍪'}]},
+      'Deletion observed after document.cookie cookie-clearing' +
+        ' using max-age=0');
+}, 'CookieStore agrees with document.cookie on encoding non-ASCII cookies');
+
+
+cookie_test(async t => {
+  await cookieStore.set('DOCUMENT-🍪', '🔵');
+  assert_equals(
+      await getCookieStringDocument(),
+      'DOCUMENT-🍪=🔵',
+      'Cookie we wrote using CookieStore in document.cookie');
+
+  await cookieStore.delete('DOCUMENT-🍪');
+  assert_equals(
+      await getCookieStringDocument(),
+      undefined,
+      'Empty cookie jar after CookieStore delete');
+}, 'document.cookie agrees with CookieStore on encoding non-ASCII cookies');
diff --git a/cookie-store/resources/http_cookie_and_set_cookie_headers.js b/cookie-store/resources/http_cookie_and_set_cookie_headers.js
new file mode 100644
index 0000000..0fcf6b6
--- /dev/null
+++ b/cookie-store/resources/http_cookie_and_set_cookie_headers.js
@@ -0,0 +1,203 @@
+'use strict';
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringHttp('HTTP-cookie=value; path=/');
+  assert_equals(
+      await getCookieString(),
+      'HTTP-cookie=value',
+      'Cookie we wrote using HTTP in cookie jar');
+  assert_equals(
+      await getCookieStringHttp(),
+      'HTTP-cookie=value',
+      'Cookie we wrote using HTTP in HTTP cookie jar');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'HTTP-cookie', value: 'value'}]},
+    'Cookie we wrote using HTTP is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringHttp('HTTP-cookie=new-value; path=/');
+  assert_equals(
+      await getCookieString(),
+      'HTTP-cookie=new-value',
+      'Cookie we overwrote using HTTP in cookie jar');
+  assert_equals(
+      await getCookieStringHttp(),
+      'HTTP-cookie=new-value',
+      'Cookie we overwrote using HTTP in HTTP cookie jar');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'HTTP-cookie', value: 'new-value'}]},
+    'Cookie we overwrote using HTTP is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringHttp('HTTP-cookie=DELETED; path=/; max-age=0');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'Empty cookie jar after HTTP cookie-clearing using max-age=0');
+  assert_equals(
+      await getCookieStringHttp(),
+      undefined,
+      'Empty HTTP cookie jar after HTTP cookie-clearing using max-age=0');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: 'HTTP-cookie'}]},
+    'Deletion observed after HTTP cookie-clearing using max-age=0');
+}, 'HTTP set/overwrite/delete observed in CookieStore');
+
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringHttp('HTTP-🍪=🔵; path=/');
+  assert_equals(
+      await getCookieString(),
+      'HTTP-🍪=🔵',
+      'Cookie we wrote using HTTP in cookie jar');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'HTTP-🍪', value: '🔵'}]},
+    'Cookie we wrote using HTTP is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieStringHttp('HTTP-🍪=DELETED; path=/; max-age=0');
+  assert_equals(
+      await getCookieString(),
+      undefined,
+    'Empty cookie jar after HTTP cookie-clearing using max-age=0');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: 'HTTP-🍪'}]},
+    'Deletion observed after HTTP cookie-clearing using max-age=0');
+
+}, 'CookieStore agreed with HTTP headers agree on encoding non-ASCII cookies');
+
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('TEST', 'value0');
+  assert_equals(
+    await getCookieString(),
+    'TEST=value0',
+    'Cookie jar contains only cookie we set');
+  assert_equals(
+    await getCookieStringHttp(),
+    'TEST=value0',
+    'HTTP cookie jar contains only cookie we set');
+  await verifyCookieChangeEvent(
+    eventPromise,
+    {changed: [{name: 'TEST', value: 'value0'}]},
+    'Observed value that was set');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('TEST', 'value');
+  assert_equals(
+    await getCookieString(),
+    'TEST=value',
+    'Cookie jar contains only cookie we set');
+  assert_equals(
+    await getCookieStringHttp(),
+    'TEST=value',
+    'HTTP cookie jar contains only cookie we set');
+  await verifyCookieChangeEvent(
+    eventPromise,
+    {changed: [{name: 'TEST', value: 'value'}]},
+    'Observed value that was overwritten');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.delete('TEST');
+  assert_equals(
+    await getCookieString(),
+    undefined,
+    'Cookie jar does not contain cookie we deleted');
+  assert_equals(
+    await getCookieStringHttp(),
+    undefined,
+    'HTTP cookie jar does not contain cookie we deleted');
+  await verifyCookieChangeEvent(
+    eventPromise,
+    {deleted: [{name: 'TEST'}]},
+    'Observed cookie that was deleted');
+}, 'CookieStore set/overwrite/delete observed in HTTP headers');
+
+
+cookie_test(async t => {
+  await cookieStore.set('🍪', '🔵');
+  assert_equals(
+    await getCookieStringHttp(),
+    '🍪=🔵',
+    'HTTP cookie jar contains only cookie we set');
+
+  await cookieStore.delete('🍪');
+  assert_equals(
+    await getCookieStringHttp(),
+    undefined,
+    'HTTP cookie jar does not contain cookie we deleted');
+}, 'HTTP headers agreed with CookieStore on encoding non-ASCII cookies');
+
+
+cookie_test(async t => {
+  // Non-UTF-8 byte sequences cause the Set-Cookie to be dropped.
+  let eventPromise = observeNextCookieChangeEvent();
+  await setCookieBinaryHttp(
+      unescape(encodeURIComponent('HTTP-cookie=value')) + '\xef\xbf\xbd; path=/');
+  assert_equals(
+      await getCookieString(),
+      'HTTP-cookie=value\ufffd',
+      'Binary cookie we wrote using HTTP in cookie jar');
+  assert_equals(
+      await getCookieStringHttp(),
+      'HTTP-cookie=value\ufffd',
+      'Binary cookie we wrote using HTTP in HTTP cookie jar');
+  assert_equals(
+      decodeURIComponent(escape(await getCookieBinaryHttp())),
+      'HTTP-cookie=value\ufffd',
+      'Binary cookie we wrote in binary HTTP cookie jar');
+  assert_equals(
+      await getCookieBinaryHttp(),
+      unescape(encodeURIComponent('HTTP-cookie=value')) + '\xef\xbf\xbd',
+      'Binary cookie we wrote in binary HTTP cookie jar');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'HTTP-cookie', value: 'value\ufffd'}]},
+    'Binary cookie we wrote using HTTP is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieBinaryHttp(
+      unescape(encodeURIComponent('HTTP-cookie=new-value')) + '\xef\xbf\xbd; path=/');
+  assert_equals(
+      await getCookieString(),
+      'HTTP-cookie=new-value\ufffd',
+      'Binary cookie we overwrote using HTTP in cookie jar');
+  assert_equals(
+      await getCookieStringHttp(),
+      'HTTP-cookie=new-value\ufffd',
+      'Binary cookie we overwrote using HTTP in HTTP cookie jar');
+  assert_equals(
+      decodeURIComponent(escape(await getCookieBinaryHttp())),
+      'HTTP-cookie=new-value\ufffd',
+      'Binary cookie we overwrote in binary HTTP cookie jar');
+  assert_equals(
+      await getCookieBinaryHttp(),
+      unescape(encodeURIComponent('HTTP-cookie=new-value')) + '\xef\xbf\xbd',
+      'Binary cookie we overwrote in binary HTTP cookie jar');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: 'HTTP-cookie', value: 'new-value\ufffd'}]},
+    'Binary cookie we overwrote using HTTP is observed');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await setCookieBinaryHttp(
+      unescape(encodeURIComponent('HTTP-cookie=DELETED; path=/; max-age=0')));
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'Empty cookie jar after binary HTTP cookie-clearing using max-age=0');
+  assert_equals(
+      await getCookieStringHttp(),
+      undefined,
+      'Empty HTTP cookie jar after' +
+        ' binary HTTP cookie-clearing using max-age=0');
+  assert_equals(
+      await getCookieBinaryHttp(),
+      undefined,
+      'Empty binary HTTP cookie jar after' +
+        ' binary HTTP cookie-clearing using max-age=0');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: 'HTTP-cookie'}]},
+    'Deletion observed after binary HTTP cookie-clearing using max-age=0');
+}, 'Binary HTTP set/overwrite/delete observed in CookieStore');
diff --git a/cookie-store/resources/no_name_and_no_value.js b/cookie-store/resources/no_name_and_no_value.js
new file mode 100644
index 0000000..e0c11a6
--- /dev/null
+++ b/cookie-store/resources/no_name_and_no_value.js
@@ -0,0 +1,45 @@
+'use strict';
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('', 'first-value');
+  const actual1 =
+      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
+  const expected1 = 'first-value';
+  assert_equals(actual1, expected1);
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: '', value: 'first-value'}]},
+    'Observed no-name change');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('', '');
+  const actual2 =
+      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
+  const expected2 = '';
+  assert_equals(actual2, expected2);
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: '', value: ''}]},
+    'Observed no-name change');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.delete('');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: ''}]},
+    'Observed no-name deletion');
+
+  assert_equals(
+    await getCookieString(),
+      undefined,
+      'Empty cookie jar');
+  assert_equals(
+    await getCookieStringHttp(),
+    undefined,
+    'Empty HTTP cookie jar');
+  if (kHasDocument) {
+    assert_equals(
+      await getCookieStringDocument(),
+      undefined,
+      'Empty document.cookie cookie jar');
+  }
+
+}, 'Verify behavior of no-name and no-value cookies.');
diff --git a/cookie-store/resources/no_name_equals_in_value.js b/cookie-store/resources/no_name_equals_in_value.js
new file mode 100644
index 0000000..b6cfab5
--- /dev/null
+++ b/cookie-store/resources/no_name_equals_in_value.js
@@ -0,0 +1,41 @@
+'use strict';
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('', 'first-value');
+  assert_equals(
+    (await cookieStore.getAll('')).map(({ value }) => value).join(';'),
+    'first-value',
+    'Cookie with no name and normal value should have been set');
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: '', value: 'first-value'}]},
+    'Observed no-name change');
+
+  await promise_rejects(
+    t,
+    new TypeError(),
+    cookieStore.set('', 'suspicious-value=resembles-name-and-value'),
+    'Expected promise rejection when setting a cookie with' +
+      ' no name and "=" in value (via arguments)');
+
+  await promise_rejects(
+    t,
+    new TypeError(),
+    cookieStore.set(
+      {name: '', value: 'suspicious-value=resembles-name-and-value'}),
+    'Expected promise rejection when setting a cookie with' +
+      ' no name and "=" in value (via options)');
+
+  assert_equals(
+    (await cookieStore.getAll('')).map(({ value }) => value).join(';'),
+    'first-value',
+    'Cookie with no name should still have previous value');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.delete('');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: ''}]},
+    'Observed no-name deletion');
+
+}, "Verify that attempting to set a cookie with no name and with '=' in" +
+             " the value does not work.");
diff --git a/cookie-store/resources/no_name_multiple_values.js b/cookie-store/resources/no_name_multiple_values.js
new file mode 100644
index 0000000..51ea528
--- /dev/null
+++ b/cookie-store/resources/no_name_multiple_values.js
@@ -0,0 +1,38 @@
+'use strict';
+
+cookie_test(async t => {
+  let eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('', 'first-value');
+  let actual1 =
+      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
+  let expected1 = 'first-value';
+  assert_equals(actual1, expected1);
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: '', value: 'first-value'}]},
+    'Observed no-name change');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.set('', 'second-value');
+  let actual2 =
+      (await cookieStore.getAll('')).map(({ value }) => value).join(';');
+  let expected2 = 'second-value';
+  assert_equals(actual2, expected2);
+  await verifyCookieChangeEvent(
+    eventPromise, {changed: [{name: '', value: 'second-value'}]},
+    'Observed no-name change');
+
+  eventPromise = observeNextCookieChangeEvent();
+  await cookieStore.delete('');
+  await verifyCookieChangeEvent(
+    eventPromise, {deleted: [{name: ''}]},
+    'Observed no-name change');
+
+  assert_equals(
+      await getCookieString(),
+      undefined,
+      'Empty cookie jar after testNoNameMultipleValues');
+  assert_equals(
+    await getCookieStringHttp(),
+    undefined,
+    'Empty HTTP cookie jar after testNoNameMultipleValues');
+}, 'Verify behavior of multiple no-name cookies');
diff --git a/cookie-store/resources/ordering.js b/cookie-store/resources/ordering.js
new file mode 100644
index 0000000..86babd9
--- /dev/null
+++ b/cookie-store/resources/ordering.js
@@ -0,0 +1,39 @@
+'use strict';
+
+cookie_test(async t => {
+  await cookieStore.set('ordered-1', 'cookie-value1');
+  await cookieStore.set('ordered-2', 'cookie-value2');
+  await cookieStore.set('ordered-3', 'cookie-value3');
+  // NOTE: this assumes no concurrent writes from elsewhere; it also
+  // uses three separate cookie jar read operations where a single getAll
+  // would be more efficient, but this way the CookieStore does the filtering
+  // for us.
+  const matchingValues = await Promise.all(['1', '2', '3'].map(
+      async suffix => (await cookieStore.get('ordered-' + suffix)).value));
+  const actual = matchingValues.join(';');
+  const expected = 'cookie-value1;cookie-value2;cookie-value3';
+  assert_equals(actual, expected);
+}, 'Set three simple origin session cookies sequentially and ensure ' +
+            'they all end up in the cookie jar in order.');
+
+cookie_test(async t => {
+  await Promise.all([
+    cookieStore.set('ordered-unordered1', 'unordered-cookie-value1'),
+    cookieStore.set('ordered-unordered2', 'unordered-cookie-value2'),
+    cookieStore.set('ordered-unordered3', 'unordered-cookie-value3')
+  ]);
+  // NOTE: this assumes no concurrent writes from elsewhere; it also
+  // uses three separate cookie jar read operations where a single getAll
+  // would be more efficient, but this way the CookieStore does the filtering
+  // for us and we do not need to sort.
+  const matchingCookies = await Promise.all(['1', '2', '3'].map(
+    suffix => cookieStore.get('ordered-unordered' + suffix)));
+  const actual = matchingCookies.map(({ value }) => value).join(';');
+  const expected =
+      'unordered-cookie-value1;' +
+      'unordered-cookie-value2;' +
+      'unordered-cookie-value3';
+  assert_equals(actual, expected);
+}, 'Set three simple origin session cookies in undefined order using ' +
+            'Promise.all and ensure they all end up in the cookie jar in any ' +
+            'order. ');
diff --git a/cookie-store/resources/testharness-helpers.js b/cookie-store/resources/testharness-helpers.js
deleted file mode 100644
index 7e644c7..0000000
--- a/cookie-store/resources/testharness-helpers.js
+++ /dev/null
@@ -1,286 +0,0 @@
-'use strict';
-
-// Length of final setTimeout when observer callback has not fired.
-//
-const kExtraObserverDelay = 0;  // For builtin implementation
-
-// NOTE: A polyfill was used for pre-implementation testing. To revive
-// it uncomment these and comment out the preceding line:
-//
-// const kExtraObserverDelay = 200;  // Polyfill when not running on battery
-// // const kExtraObserverDelay = 5000;  // ... when running on battery
-// document.open();
-// document.write(`
-//   <script>delete cookieStore</script>
-//   <script src="https://wicg.github.io/cookie-store/cookies.js">
-//   </script>
-// `);
-// document.close()
-
-// See https://github.com/whatwg/html/pull/3011#issuecomment-331187136
-// and https://www.chromestatus.com/feature/6170540112871424
-const kMetaHttpEquivSetCookieIsGone = true;
-
-// True when running in a document context as opposed to a worker context
-const kHasDocument = typeof document !== 'undefined';
-
-// Override for named test inclusion. Set by suite().
-let testOverride = undefined;
-
-// Determines whether the named test should be included in this run of the
-// suite. Only usable in a test runner context as this uses assert_equals.
-//
-// Parameters:
-//
-// - testName: (string) test name; must be an identifier starting with 'test'
-// - opt_excludeFromAll: (optional; boolean) if true, explicit or implicit
-//   #...&test=all (which is the default) will not activate this test.
-const includeTest = (testName, opt_excludeFromAll) => {
-  assert_equals(!!testName.match(/^test\w+/), true, 'includeTest: ' + testName);
-  assert_equals(typeof eval(testName), 'function', 'includeTest: ' + testName);
-  let testParams =
-        (location.hash || '#').substr(1).split('&').filter(
-            x => x.match(/^test=/)).map(x => decodeURIComponent(x));
-  if (!testParams.length) {
-    testParams = ['test=all'];
-    if (testOverride !== undefined) {
-      testParams = ['test=' + testOverride];
-    }
-  }
-  const filterSet =
-        testParams.map(x => x.split('=', 2)[1]).join(',').split(',').reduce(
-            (set, name) => Object.assign(set, {[name]: true}), {});
-  for (let name in filterSet) {
-    if (name === 'all' || !filterSet.hasOwnProperty(name)) continue;
-    assert_equals(!!name.match(/^test\w+/), true, '#test=' + testName);
-    assert_equals(typeof eval(name), 'function', '#test=' + testName);
-  }
-  return (filterSet.all && !opt_excludeFromAll) ||
-      filterSet.hasOwnProperty(testName) && filterSet[testName];
-}
-
-// True when running on unsecured 'http:' rather than secured 'https:'.
-const kIsUnsecured = location.protocol !== 'https:';
-
-// True when no CGI/no active wptserve handlers should be used.
-const kIsStatic = !!((location.hash || '#').match(/(^#|&)static=true(&|$)/) ||
-                     location.pathname.match(/_static\./));
-
-const kCookieHelperCgi = 'resources/cookie_helper.py';
-
-// Async wrapper for an async function or promise that is expected
-// reject in an unsecured (non-https:) context and work in a secured
-// (https:) context.
-//
-// Parameters:
-//
-// - testCase: (TestCase) test case context
-// - code: (Error class or number) expected rejection type in unsecured context
-// - promise: (thenable) test code
-// - message: (optional; string) message to forward to promise_rejects in
-//   unsecured context
-const promise_rejects_when_unsecured = async (
-    testCase,
-    code,
-    promise,
-    message = 'Feature unavailable from unsecured contexts'
-) => {
-  if (kIsUnsecured) await promise_rejects(testCase, code, promise, message);
-  else await promise;
-};
-
-// Converts a list of cookie records {name, value} to [name=]value; ... as
-// seen in Cookie: and document.cookie.
-//
-// Parameters:
-// - cookies: (array of {name, value}) records to convert
-//
-// Returns a string serializing the records, or undefined if no records were
-// given.
-const cookieString = cookies => cookies.length ? cookies.map((
-    {name, value}) => (name ? (name + '=') : '') + value).join('; ') :
-      undefined;
-
-// Approximate async equivalent to the document.cookie getter but with
-// important differences: optional additional getAll arguments are
-// forwarded, and an empty cookie jar returns undefined.
-//
-// This is intended primarily for verification against expected cookie
-// jar contents. It should produce more readable messages using
-// assert_equals in failing cases than assert_object_equals would
-// using parsed cookie jar contents and also allows expectations to be
-// written more compactly.
-const getCookieString = async (...args) => {
-  return cookieString(await cookieStore.getAll(...args));
-}
-
-// Approximate async equivalent to the document.cookie getter but from
-// the server's point of view. Returns UTF-8 interpretation. Allows
-// sub-path to be specified.
-//
-// Unlike document.cookie, this returns undefined when no cookies are
-// present.
-const getCookieStringHttp = async (extraPath = null) => {
-  if (kIsStatic) throw 'CGI not available in static HTML test';
-  const url =
-        kCookieHelperCgi + ((extraPath == null) ? '' : ('/' + extraPath));
-  const response = await fetch(url, { credentials: 'include' });
-  const text = await response.text();
-  assert_equals(
-      response.ok,
-      true,
-      'CGI should have succeeded in getCookieStringHttp\n' + text);
-  assert_equals(
-      response.headers.get('content-type'),
-      'text/plain; charset=utf-8',
-      'CGI did not return UTF-8 text in getCookieStringHttp');
-  if (text === '') return undefined;
-  assert_equals(
-      text.indexOf('cookie='),
-      0,
-      'CGI response did not begin with "cookie=" and was not empty: ' + text);
-  return decodeURIComponent(text.replace(/^cookie=/, ''));
-}
-
-// Approximate async equivalent to the document.cookie getter but from
-// the server's point of view. Returns binary string
-// interpretation. Allows sub-path to be specified.
-//
-// Unlike document.cookie, this returns undefined when no cookies are
-// present.
-const getCookieBinaryHttp = async (extraPath = null) => {
-  if (kIsStatic) throw 'CGI not available in static HTML test';
-  const url =
-        kCookieHelperCgi +
-        ((extraPath == null) ?
-         '' :
-         ('/' + extraPath)) + '?charset=iso-8859-1';
-  const response = await fetch(url, { credentials: 'include' });
-  const text = await response.text();
-  assert_equals(
-      response.ok,
-      true,
-      'CGI should have succeeded in getCookieBinaryHttp\n' + text);
-  assert_equals(
-      response.headers.get('content-type'),
-      'text/plain; charset=iso-8859-1',
-      'CGI did not return ISO 8859-1 text in getCookieBinaryHttp');
-  if (text === '') return undefined;
-  assert_equals(
-      text.indexOf('cookie='),
-      0,
-      'CGI response did not begin with "cookie=" and was not empty: ' + text);
-  return unescape(text.replace(/^cookie=/, ''));
-}
-
-// Approximate async equivalent to the document.cookie setter but from
-// the server's point of view.
-const setCookieStringHttp = async setCookie => {
-  if (kIsStatic) throw 'CGI not available in static HTML test';
-  const encodedSetCookie = encodeURIComponent(setCookie);
-  const url = kCookieHelperCgi;
-  const headers = new Headers();
-  headers.set(
-      'content-type',
-      'application/x-www-form-urlencoded; charset=utf-8');
-  const response = await fetch(
-      url,
-      {
-        credentials: 'include',
-        method: 'POST',
-        headers: headers,
-        body: 'set-cookie=' + encodedSetCookie,
-      });
-  const text = await response.text();
-  assert_equals(
-      response.ok,
-      true,
-      'CGI should have succeeded in setCookieStringHttp set-cookie: ' +
-        setCookie + '\n' + text);
-  assert_equals(
-      response.headers.get('content-type'),
-      'text/plain; charset=utf-8',
-      'CGI did not return UTF-8 text in setCookieStringHttp');
-  assert_equals(
-      text,
-      'set-cookie=' + encodedSetCookie,
-      'CGI did not faithfully echo the set-cookie value');
-};
-
-// Approximate async equivalent to the document.cookie setter but from
-// the server's point of view. This version sets a binary cookie rather
-// than a UTF-8 one.
-const setCookieBinaryHttp = async setCookie => {
-  if (kIsStatic) throw 'CGI not available in static HTML test';
-  const encodedSetCookie = escape(setCookie).split('/').join('%2F');
-  const url = kCookieHelperCgi + '?charset=iso-8859-1';
-  const headers = new Headers();
-  headers.set(
-      'content-type',
-      'application/x-www-form-urlencoded; charset=iso-8859-1');
-  const response = await fetch(url, {
-    credentials: 'include',
-    method: 'POST',
-    headers: headers,
-    body: 'set-cookie=' + encodedSetCookie
-  });
-  const text = await response.text();
-  assert_equals(
-      response.ok,
-      true,
-      'CGI should have succeeded in setCookieBinaryHttp set-cookie: ' +
-        setCookie + '\n' + text);
-  assert_equals(
-      response.headers.get('content-type'),
-      'text/plain; charset=iso-8859-1',
-      'CGI did not return Latin-1 text in setCookieBinaryHttp');
-  assert_equals(
-      text,
-      'set-cookie=' + encodedSetCookie,
-      'CGI did not faithfully echo the set-cookie value');
-};
-
-// Approximate async equivalent to the document.cookie setter but using
-// <meta http-equiv="set-cookie" content="..."> written into a temporary
-// IFRAME. Merely appending the node to HEAD works in some browsers (e.g.
-// Chromium) but not others (e.g. Firefox.)
-const setCookieStringMeta = async setCookie => {
-  if (document.readyState !== 'complete') {
-    await new Promise(resolve => addEventListener('load', resolve, true));
-  }
-  const meta = Object.assign(document.createElement('meta'), {
-    httpEquiv: 'set-cookie',
-    content: setCookie
-  });
-  const ifr = document.createElement('iframe');
-  await new Promise(resolve => document.body.appendChild(Object.assign(
-      ifr,
-      {
-        onload: resolve
-      })));
-  try {
-    ifr.contentWindow.document.open('text/html; charset=utf-8');
-    ifr.contentWindow.document.write([
-      '<!DOCTYPE html>',
-      '<meta charset="utf-8">',
-      meta.outerHTML
-    ].join('\r\n'));
-    ifr.contentWindow.document.close();
-  } finally {
-    if (ifr.parentNode) ifr.parentNode.removeChild(ifr);
-  }
-};
-
-// Async document.cookie getter; converts '' to undefined which loses
-// information in the edge case where a single ''-valued anonymous
-// cookie is visible.
-const getCookieStringDocument = async () => {
-  if (!kHasDocument) throw 'document.cookie not available in this context';
-  return String(document.cookie || '') || undefined;
-};
-
-// Async document.cookie setter
-const setCookieStringDocument = async setCookie => {
-  if (!kHasDocument) throw 'document.cookie not available in this context';
-  document.cookie = setCookie;
-};
diff --git a/cookie-store/serviceworker_cookieStore_arguments.js b/cookie-store/serviceworker_cookieStore_arguments.js
new file mode 100644
index 0000000..0ffe6f8
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_arguments.js
@@ -0,0 +1,14 @@
+self.GLOBAL = {
+  isWindow: function() { return false; },
+  isWorker: function() { return true; },
+};
+importScripts("/resources/testharness.js");
+
+importScripts(
+    "cookieStore_delete_arguments.tentative.window.js",
+    "cookieStore_get_arguments.tentative.window.js",
+    "cookieStore_getAll_arguments.tentative.window.js",
+    "cookieStore_has_arguments.tentative.window.js",
+    "cookieStore_set_arguments.tentative.window.js");
+
+done();
diff --git a/cookie-store/serviceworker_cookieStore_arguments.tentative.https.html b/cookie-store/serviceworker_cookieStore_arguments.tentative.https.html
new file mode 100644
index 0000000..06c9501
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_arguments.tentative.https.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: cookieStore API argument handling in ServiceWorker</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+(async () => {
+  const scope = 'does/not/exist';
+
+  let registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+  registration = await navigator.serviceWorker.register(
+      'serviceworker_cookieStore_arguments.js', {scope});
+
+  fetch_tests_from_worker(registration.installing);
+})();
+</script>
diff --git a/cookie-store/serviceworker_cookieStore_basic.js b/cookie-store/serviceworker_cookieStore_basic.js
new file mode 100644
index 0000000..0d8039f
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_basic.js
@@ -0,0 +1,13 @@
+self.GLOBAL = {
+  isWindow: function() { return false; },
+  isWorker: function() { return true; },
+};
+importScripts("/resources/testharness.js");
+
+importScripts(
+    "cookieStore_get_delete_basic.tentative.window.js",
+    "cookieStore_get_set_basic.tentative.window.js",
+    "cookieStore_getAll_set_basic.tentative.window.js",
+    "cookieStore_has_basic.tentative.window.js");
+
+done();
diff --git a/cookie-store/serviceworker_cookieStore_basic.tentative.https.html b/cookie-store/serviceworker_cookieStore_basic.tentative.https.html
new file mode 100644
index 0000000..d4385d6
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_basic.tentative.https.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: cookieStore basic API in ServiceWorker</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+(async () => {
+  const scope = 'does/not/exist';
+
+  let registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+  registration = await navigator.serviceWorker.register(
+      'serviceworker_cookieStore_basic.js', {scope});
+
+  fetch_tests_from_worker(registration.installing);
+})();
+</script>
diff --git a/cookie-store/serviceworker_cookieStore_subscriptions.js b/cookie-store/serviceworker_cookieStore_subscriptions.js
new file mode 100644
index 0000000..3f1b0ff
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_subscriptions.js
@@ -0,0 +1,113 @@
+self.GLOBAL = {
+  isWindow: function() { return false; },
+  isWorker: function() { return true; },
+};
+importScripts("/resources/testharness.js");
+
+self.addEventListener('install', (event) => {
+  event.waitUntil((async () => {
+    // The subscribeToChanges calls are not done in parallel on purpose. Having
+    // multiple in-flight requests introduces failure modes aside from the
+    // cookie change logic that this test aims to cover.
+    await cookieStore.subscribeToChanges([
+      { name: 'cookie-name1', matchType: 'equals', url: '/scope/path1' }]);
+    await cookieStore.subscribeToChanges([
+      { },  // Test the default values for subscription properties.
+      { name: 'cookie-prefix', matchType: 'startsWith' },
+    ]);
+  })());
+});
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+// Resolves when the service worker receives the 'activate' event.
+const kServiceWorkerActivatedPromise = new Promise(resolve => {
+  self.addEventListener('activate', event => { resolve(); });
+});
+
+// sort() comparator that uses the < operator.
+//
+// This is intended to be used for sorting strings. Using < is preferred to
+// localeCompare() because the latter has some implementation-dependent
+// behavior.
+function CompareStrings(a, b) {
+  return a < b ? -1 : (b < a ? 1 : 0);
+}
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  const subscriptions = await cookieStore.getChangeSubscriptions();
+  assert_equals(subscriptions.length, 3);
+
+  subscriptions.sort((a, b) => CompareStrings(`${a.name}`, `${b.name}`));
+
+  assert_equals(subscriptions[0].name, 'cookie-name1');
+  assert_equals('equals', subscriptions[0].matchType);
+
+  assert_equals(subscriptions[1].name, 'cookie-prefix');
+  assert_equals('startsWith', subscriptions[1].matchType);
+
+  assert_false('name' in subscriptions[2]);
+  assert_equals('startsWith', subscriptions[2].matchType);
+}, 'getChangeSubscriptions returns subscriptions passed to subscribeToChanges');
+
+promise_test(async testCase => {
+  promise_rejects(
+      testCase, new TypeError(),
+      cookieStore.subscribeToChanges([{ name: 'cookie-name2' }]));
+}, 'subscribeToChanges rejects when called outside the install handler');
+
+
+// Accumulates cookiechange events dispatched to the service worker.
+let g_cookie_changes = [];
+
+// Resolved when a cookiechange event is received. Rearmed by
+// ResetCookieChangeReceivedPromise().
+let g_cookie_change_received_promise = null;
+let g_cookie_change_received_promise_resolver = null;
+self.addEventListener('cookiechange', (event) => {
+  g_cookie_changes.push(event);
+  if (g_cookie_change_received_promise_resolver)
+    g_cookie_change_received_promise_resolver();
+});
+function RearmCookieChangeReceivedPromise() {
+  g_cookie_change_received_promise = new Promise((resolve) => {
+    g_cookie_change_received_promise_resolver = resolve;
+  });
+}
+RearmCookieChangeReceivedPromise();
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await g_cookie_change_received_promise;
+
+  assert_equals(g_cookie_changes.length, 1);
+  const event = g_cookie_changes[0]
+  assert_equals(event.type, 'cookiechange');
+  assert_equals(event.changed.length, 1);
+  assert_equals(event.changed[0].name, 'cookie-name');
+  assert_equals(event.changed[0].value, 'cookie-value');
+  assert_equals(event.deleted.length, 0);
+  assert_true(event instanceof ExtendableCookieChangeEvent);
+  assert_true(event instanceof ExtendableEvent);
+
+  await async_cleanup(() => {
+    cookieStore.delete('cookie-name');
+    g_cookie_changes = [];
+    RearmCookieChangeReceivedPromise();
+  });
+}, 'cookiechange dispatched with cookie change that matches subscription');
+
+done();
diff --git a/cookie-store/serviceworker_cookieStore_subscriptions.tentative.https.html b/cookie-store/serviceworker_cookieStore_subscriptions.tentative.https.html
new file mode 100644
index 0000000..723de23
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_subscriptions.tentative.https.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: cookie change events in ServiceWorker</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+(async () => {
+  const scope = 'scope';
+
+  let registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+  registration = await navigator.serviceWorker.register(
+      'serviceworker_cookieStore_subscriptions.js', {scope});
+
+  fetch_tests_from_worker(registration.installing);
+})();
+</script>
diff --git a/cookie-store/serviceworker_cookieStore_subscriptions_basic.js b/cookie-store/serviceworker_cookieStore_subscriptions_basic.js
new file mode 100644
index 0000000..68edc0e
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_subscriptions_basic.js
@@ -0,0 +1,63 @@
+self.GLOBAL = {
+  isWindow: function() { return false; },
+  isWorker: function() { return true; },
+};
+importScripts("/resources/testharness.js");
+
+self.addEventListener('install', (event) => {
+  event.waitUntil((async () => {
+    cookieStore.subscribeToChanges([
+      { name: 'cookie-name', matchType: 'equals', url: '/scope/path' }]);
+  })());
+});
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+// Resolves when the service worker receives the 'activate' event.
+const kServiceWorkerActivatedPromise = new Promise(resolve => {
+  self.addEventListener('activate', event => { resolve(); });
+});
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  const subscriptions = await cookieStore.getChangeSubscriptions();
+  assert_equals(subscriptions.length, 1);
+
+  assert_equals(subscriptions[0].name, 'cookie-name');
+  assert_equals('equals', subscriptions[0].matchType);
+}, 'getChangeSubscriptions returns a subscription passed to subscribeToChanges');
+
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  cookie_change_received_promise = new Promise((resolve) => {
+    self.addEventListener('cookiechange', (event) => {
+      resolve(event);
+    });
+  });
+
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const event = await cookie_change_received_promise;
+  assert_equals(event.type, 'cookiechange');
+  assert_equals(event.changed.length, 1);
+  assert_equals(event.changed[0].name, 'cookie-name');
+  assert_equals(event.changed[0].value, 'cookie-value');
+  assert_equals(event.deleted.length, 0);
+  assert_true(event instanceof ExtendableCookieChangeEvent);
+  assert_true(event instanceof ExtendableEvent);
+
+  await async_cleanup(() => { cookieStore.delete('cookie-name'); });
+}, 'cookiechange dispatched with cookie change that matches subscription');
+
+done();
diff --git a/cookie-store/serviceworker_cookieStore_subscriptions_basic.tentative.https.html b/cookie-store/serviceworker_cookieStore_subscriptions_basic.tentative.https.html
new file mode 100644
index 0000000..525c3b3
--- /dev/null
+++ b/cookie-store/serviceworker_cookieStore_subscriptions_basic.tentative.https.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: cookie change events in ServiceWorker</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+(async () => {
+  const scope = 'scope';
+
+  let registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+  registration = await navigator.serviceWorker.register(
+      'serviceworker_cookieStore_subscriptions_basic.js', {scope});
+
+  fetch_tests_from_worker(registration.installing);
+})();
+</script>
diff --git a/cookies/http-state/attribute-tests.html b/cookies/http-state/attribute-tests.html
new file mode 100644
index 0000000..6e7d5fe
--- /dev/null
+++ b/cookies/http-state/attribute-tests.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Tests cookie attribute functionality</title>
+    <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8">
+
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/cookie-http-state-template.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      setup({ explicit_timeout: true });
+
+      const TEST_CASES = [
+        {file: "attribute0001", name: "Ignore cookie for Secure attribute."},
+        {file: "attribute0002", name: "Ignore cookie for seCURe attribute."},
+        {file: "attribute0003", name: "Set cookie for \"Secure\" attribute."},
+        {file: "attribute0004", name: "Ignore cookie for for Secure= attribute."},
+        {file: "attribute0005", name: "Ignore cookie for Secure=aaaa"},
+        {file: "attribute0006", name: "Set cookie for Secure qux"},
+        {file: "attribute0007", name: "Ignore cookie for Secure space equals."},
+        {file: "attribute0008", name: "Ignore cookie for Secure equals space"},
+        {file: "attribute0009", name: "Ignore cookie for Secure separated."},
+        {file: "attribute0010", name: "Ignore cookie for Secure separated v2."},
+        {file: "attribute0011", name: "Ignore cookie for Secure separated v2."},
+        {file: "attribute0012", name: "Ignore cookie for spaced Secure"},
+        {file: "attribute0013", name: "Ignore cookie for space Secure with ;."},
+        {file: "attribute0014", name: "Set cookie for Path."},
+        {file: "attribute0015", name: "Set cookie for Path=."},
+        {file: "attribute0016", name: "Set cookie for Path=/."},
+        {file: "attribute0017", name: "Ignore cookie for invalid path."},
+        {file: "attribute0018", name: "Ignore cookie for spaced invalid path."},
+        {file: "attribute0019", name: "Ignore cookie for spaced invalid path v2."},
+        {file: "attribute0020", name: "Ignore cookie for invalid path and attribute."},
+        {file: "attribute0021", name: "Ignore cookie for invalid and root path."},
+        {file: "attribute0022", name: "Set cookie for root and invalid path."},
+        {file: "attribute0023", name: "Set cookie for invalid and sane path."},
+        {file: "attribute0024", name: "Ignore cookie for sane and invalid path."},
+        {file: "attribute0025", name: "Ignore cookie for invalid + Secure."},
+        {file: "attribute0026", name: "Ignore cookie for quoted invalid attribute."},
+      ];
+
+      for (const i in TEST_CASES) {
+        const t = TEST_CASES[i];
+        promise_test(createCookieTest(t.file),
+                     t.file + " - " + t.name,
+                     { timeout: 3000 });
+      }
+
+    </script>
+  </body>
+</html>
diff --git a/cookies/http-state/general-tests.html b/cookies/http-state/general-tests.html
new file mode 100644
index 0000000..169984d
--- /dev/null
+++ b/cookies/http-state/general-tests.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Tests basic cookie setting functionality</title>
+    <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8">
+
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/cookie-http-state-template.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      setup({ explicit_timeout: true });
+
+      const TEST_CASES = [
+        {file: "0001", name: "Set cookie."},
+        {file: "0002", name: "Set cookie with future expiration."},
+        {file: "0003", name: "Set expired cookie along with valid cookie."},
+        {file: "0004", name: "Ignore cookie without key."},
+        {file: "0005", name: "Set cookie with age."},
+        {file: "0006", name: "Set no cookie with max-age=0."},
+        {file: "0007", name: "Set cookie with version=1."},
+        {file: "0008", name: "Set cookie with version=1000."},
+        {file: "0009", name: "Set cookie with custom value."},
+        // TODO(fhorschig): Could 0010 break when run on a HTTPS try server?
+        {file: "0010", name: "Dont accept 'secure' cookies over http."},
+        {file: "0011", name: "Ignore separators in cookie values."},
+        {file: "0012", name: "Ignore values with separators and without ';'."},
+        {file: "0013", name: "Use last value for cookies with identical keys."},
+        {file: "0014", name: "Keep alphabetic key order."},
+        {file: "0015", name: "Keep alphabetic single-char key order."},
+        {file: "0016", name: "Keep non-alphabetic key order."},
+        {file: "0017", name: "Keep order if comma-separated."},
+        {file: "0018", name: "Ignore keys after semicolon."},
+        {file: "0019", name: "Ignore attributes after semicolon."},
+        {file: "0020", name: "Ignore cookies without key and value."},
+        {file: "0021", name: "Ignore cookie without key in all 'Set-Cookie'."},
+        {file: "0022", name: "Set cookie without value in all 'Set-Cookie'."},
+        {file: "0023", name: "Ignore cookies without '=' in all 'Set-Cookie'."},
+        {file: "0024", name: "Ignore malformed cookies in all 'Set-Cookie'."},
+        {file: "0025", name: "Ignore cookies with ';' in all 'Set-Cookie'."},
+        {file: "0026", name: "Ignore malformed cookies in all 'Set-Cookie' v2."},
+        {file: "0027", name: "Ignore malformed cookies in all 'Set-Cookie' v3."},
+        // TODO(fhorschig): Ask about 0028's expectations ... should be empty?
+        {file: "0028", name: "[INVALID EXPECTATION] Ignore malformed cookies in all 'Set-Cookie' v4."},
+      ];
+
+      for (const i in TEST_CASES) {
+        const t = TEST_CASES[i];
+        promise_test(createCookieTest(t.file),
+                     t.file + " - " + t.name,
+                     { timeout: 3000 });
+      }
+
+    </script>
+  </body>
+</html>
diff --git a/cookies/http-state/resources/cookie-http-state-template.js b/cookies/http-state/resources/cookie-http-state-template.js
new file mode 100644
index 0000000..470d46e
--- /dev/null
+++ b/cookies/http-state/resources/cookie-http-state-template.js
@@ -0,0 +1,85 @@
+const SERVER_LOCATION = "resources";
+const SERVER_SCRIPT = SERVER_LOCATION + "/cookie-setter.py";
+
+function stripPrefixAndWhitespace(cookie_text) {
+  return cookie_text.replace(/^Cookie: /, '').replace(/^\s+|\s+$/g, '');
+}
+
+function getLocalResourcesPath() {
+  let replace = "(" + SERVER_LOCATION + "\/)*";  // Redundant location.
+  replace += "[^\/]*$";  // Everything after the last "/".
+  return location.pathname.replace(new RegExp(replace), "") + SERVER_LOCATION;
+}
+
+function getAbsoluteServerLocation() {
+  // Replace the server location and everything coming after it ...
+  let replace = SERVER_LOCATION + ".*$";
+  // ... with the Server script (which includes the server location).
+  return getLocalResourcesPath().replace(new RegExp(replace),'')+ SERVER_SCRIPT;
+}
+
+function expireCookie(name, expiry_date, path) {
+  name = name || "";
+  expiry_date = expiry_date || "Thu, 01 Jan 1970 00:00:00 UTC";
+  path = path || getLocalResourcesPath();
+  document.cookie = name + "=; expires=" + expiry_date + "; path=" + path + ";";
+}
+
+function CookieManager() {
+  this.initial_cookies = [];
+}
+
+CookieManager.prototype.parse = document_cookies => {
+  this.initial_cookies = [];
+  document_cookies = document_cookies.replace(/^Cookie: /, '');
+  if (document_cookies != "") {
+    this.initial_cookies = document_cookies.split(/\s*;\s*/);
+  }
+}
+
+CookieManager.prototype.diffWith = document_cookies => {
+  this.actual_cookies = document_cookies;
+  for (let i in initial_cookies) {
+    let no_spaces_cookie_regex =
+        new RegExp(/\s*[\;]*\s/.source + initial_cookies[i]);
+    this.actual_cookies = actual_cookies.replace(no_spaces_cookie_regex, '');
+  }
+  return this.actual_cookies;
+}
+
+CookieManager.prototype.resetCookies = () => {
+  expireCookie(/*name=*/"");  // If a cookie without keys was accepted, drop it.
+  if (this.actual_cookies == "") {
+    return;  // There is nothing to reset here.
+  }
+  let cookies_to_delete = this.actual_cookies.split(/\s*;\s*/)
+  for (let i in cookies_to_delete){
+    expireCookie(cookies_to_delete[i].replace(/=.*$/, ""));
+    // Drop cookies with same name that were set to the root path which happens
+    // for example due to "0010" still failing.
+    expireCookie(cookies_to_delete[i].replace(/=.*$/, ""),
+                 /*expiry_date=*/null,
+                 /*path=*/'/');
+  }
+}
+
+function createCookieTest(file) {
+  return t => {
+    const iframe = document.createElement('iframe');
+    document.body.appendChild(iframe);
+    let diff_tool = new CookieManager();
+    t.add_cleanup(diff_tool.resetCookies);
+    return new Promise((resolve, reject) => {
+      diff_tool.parse(document.cookie);
+      window.addEventListener("message", t.step_func(e => {
+        assert_true(!!e.data, "Message contains data")
+        resolve(e.data);
+      }));
+      iframe.src = getAbsoluteServerLocation() + "?file=" + file;
+    }).then((response) => {
+      let actual_cookies = diff_tool.diffWith(response.cookies);
+      let expected_cookies = stripPrefixAndWhitespace(response.expectation);
+      assert_equals(actual_cookies, expected_cookies);
+    });
+  }
+};
diff --git a/cookies/http-state/resources/cookie-setter.py b/cookies/http-state/resources/cookie-setter.py
new file mode 100644
index 0000000..0418f4b
--- /dev/null
+++ b/cookies/http-state/resources/cookie-setter.py
@@ -0,0 +1,54 @@
+from os import path;
+
+
+SETUP_FILE_TEMPLATE = "{}-test"
+EXPECTATION_FILE_TEMPLATE = "{}-expected"
+EXPECTATION_HTML_SCAFFOLD = "iframe-expectation-doc.html.py-str"
+DEBUGGING_HTML_SCAFFOLD = "debugging-single-test.html.py-str"
+DEFAULT_RESOURCE_DIR = path.join("cookies", "http-state", "resources")
+DEFAULT_TEST_DIR = "test-files"
+
+
+def dump_file(directory, filename):
+  return open(path.join(directory, filename), "r").read()
+
+
+class CookieTestResponse(object):
+  def __init__(self, file, root):
+    super(CookieTestResponse, self).__init__()
+    self.__test_file = SETUP_FILE_TEMPLATE.format(file)
+    self.__expectation_file = EXPECTATION_FILE_TEMPLATE.format(file)
+    self.__resources_dir = path.join(root, DEFAULT_RESOURCE_DIR)
+    self.__test_files_dir = path.join(self.__resources_dir, DEFAULT_TEST_DIR)
+
+  def cookie_setting_header(self):
+    return dump_file(self.__test_files_dir, self.__test_file)
+
+  def body_with_expectation(self):
+    html_frame = dump_file(self.__resources_dir, EXPECTATION_HTML_SCAFFOLD)
+    expected_data = dump_file(self.__test_files_dir, self.__expectation_file);
+    return html_frame.format(**{'data' : expected_data})
+
+
+def main(request, response):
+  if "debug" in request.GET:
+    response.writer.write_status(200)
+    response.writer.end_headers()
+    html_frame = dump_file(path.join(request.doc_root, DEFAULT_RESOURCE_DIR),
+                           DEBUGGING_HTML_SCAFFOLD)
+    test_file = html_frame % (request.GET['debug'])
+    response.writer.write_content(test_file)
+    return;
+
+  if not "file" in request.GET:
+    response.writer.write_status(404)
+    response.writer.end_headers()
+    response.writer.write_content("The 'file' parameter is missing!")
+    return;
+
+  cookie_response = CookieTestResponse(request.GET['file'], request.doc_root)
+
+  response.writer.write_status(200)
+  response.writer.write(cookie_response.cookie_setting_header())
+  response.writer.end_headers()
+  response.writer.write_content(cookie_response.body_with_expectation())
diff --git a/cookies/http-state/resources/debugging-single-test.html.py-str b/cookies/http-state/resources/debugging-single-test.html.py-str
new file mode 100644
index 0000000..d34ff87
--- /dev/null
+++ b/cookies/http-state/resources/debugging-single-test.html.py-str
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Debug single test</title>
+    <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8">
+
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="cookie-http-state-template.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      setup({ explicit_timeout: true });
+
+      promise_test(createCookieTest("%s"), "DEBUG", { timeout: 3000 });
+
+    </script>
+  </body>
+</html>
diff --git a/cookies/http-state/resources/iframe-content-pushing.js b/cookies/http-state/resources/iframe-content-pushing.js
new file mode 100644
index 0000000..99ede2a
--- /dev/null
+++ b/cookies/http-state/resources/iframe-content-pushing.js
@@ -0,0 +1,4 @@
+window.top.postMessage({
+  "cookies": document.cookie,
+  "expectation": document.querySelector('#data').innerText
+}, "*");
diff --git a/cookies/http-state/resources/iframe-expectation-doc.html.py-str b/cookies/http-state/resources/iframe-expectation-doc.html.py-str
new file mode 100644
index 0000000..f2cf313
--- /dev/null
+++ b/cookies/http-state/resources/iframe-expectation-doc.html.py-str
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Cookie Test Expectation Document</title>
+  </head>
+  <body>
+    <div id="data">{data}</div>
+    <script src="iframe-content-pushing.js"></script>
+  </body>
+</html>
diff --git a/cookies/http-state/resources/test-files/0001-expected b/cookies/http-state/resources/test-files/0001-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0001-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0001-test b/cookies/http-state/resources/test-files/0001-test
new file mode 100644
index 0000000..bbeb77a
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0001-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0002-expected b/cookies/http-state/resources/test-files/0002-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0002-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0002-test b/cookies/http-state/resources/test-files/0002-test
new file mode 100644
index 0000000..a3eaff0
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0002-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Expires=Fri, 07 Aug 2019 08:04:19 GMT
diff --git a/cookies/http-state/resources/test-files/0003-expected b/cookies/http-state/resources/test-files/0003-expected
new file mode 100644
index 0000000..78e79c2
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0003-expected
@@ -0,0 +1 @@
+Cookie: foo2=bar2
diff --git a/cookies/http-state/resources/test-files/0003-test b/cookies/http-state/resources/test-files/0003-test
new file mode 100644
index 0000000..62fa172
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0003-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo=bar; Expires=Fri, 07 Aug 2007 08:04:19 GMT
+Set-Cookie: foo2=bar2; Expires=Fri, 07 Aug 2027 08:04:19 GMT
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0004-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0004-expected
diff --git a/cookies/http-state/resources/test-files/0004-test b/cookies/http-state/resources/test-files/0004-test
new file mode 100644
index 0000000..190260d
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0004-test
@@ -0,0 +1 @@
+Set-Cookie: foo
diff --git a/cookies/http-state/resources/test-files/0005-expected b/cookies/http-state/resources/test-files/0005-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0005-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0005-test b/cookies/http-state/resources/test-files/0005-test
new file mode 100644
index 0000000..4de576a
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0005-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; max-age=10000;
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0006-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0006-expected
diff --git a/cookies/http-state/resources/test-files/0006-test b/cookies/http-state/resources/test-files/0006-test
new file mode 100644
index 0000000..83e41ec
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0006-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; max-age=0;
diff --git a/cookies/http-state/resources/test-files/0007-expected b/cookies/http-state/resources/test-files/0007-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0007-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0007-test b/cookies/http-state/resources/test-files/0007-test
new file mode 100644
index 0000000..af7b672
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0007-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; version=1;
diff --git a/cookies/http-state/resources/test-files/0008-expected b/cookies/http-state/resources/test-files/0008-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0008-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0008-test b/cookies/http-state/resources/test-files/0008-test
new file mode 100644
index 0000000..02baa00
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0008-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; version=1000;
diff --git a/cookies/http-state/resources/test-files/0009-expected b/cookies/http-state/resources/test-files/0009-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0009-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0009-test b/cookies/http-state/resources/test-files/0009-test
new file mode 100644
index 0000000..83c83fe
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0009-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; customvalue=1000;
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0010-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0010-expected
diff --git a/cookies/http-state/resources/test-files/0010-test b/cookies/http-state/resources/test-files/0010-test
new file mode 100644
index 0000000..1408056
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0010-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; secure;
diff --git a/cookies/http-state/resources/test-files/0011-expected b/cookies/http-state/resources/test-files/0011-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0011-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0011-test b/cookies/http-state/resources/test-files/0011-test
new file mode 100644
index 0000000..ad11eab
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0011-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; customvalue="1000 or more";
diff --git a/cookies/http-state/resources/test-files/0012-expected b/cookies/http-state/resources/test-files/0012-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0012-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/0012-test b/cookies/http-state/resources/test-files/0012-test
new file mode 100644
index 0000000..cf9e712
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0012-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; customvalue="no trailing semicolon"
diff --git a/cookies/http-state/resources/test-files/0013-expected b/cookies/http-state/resources/test-files/0013-expected
new file mode 100644
index 0000000..266392c
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0013-expected
@@ -0,0 +1 @@
+Cookie: foo=qux
diff --git a/cookies/http-state/resources/test-files/0013-test b/cookies/http-state/resources/test-files/0013-test
new file mode 100644
index 0000000..f3c30ab
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0013-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo=bar
+Set-Cookie: foo=qux
diff --git a/cookies/http-state/resources/test-files/0014-expected b/cookies/http-state/resources/test-files/0014-expected
new file mode 100644
index 0000000..4b162fe
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0014-expected
@@ -0,0 +1 @@
+Cookie: foo1=bar; foo2=qux
diff --git a/cookies/http-state/resources/test-files/0014-test b/cookies/http-state/resources/test-files/0014-test
new file mode 100644
index 0000000..6bacc67
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0014-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo1=bar
+Set-Cookie: foo2=qux
diff --git a/cookies/http-state/resources/test-files/0015-expected b/cookies/http-state/resources/test-files/0015-expected
new file mode 100644
index 0000000..036ac4a
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0015-expected
@@ -0,0 +1 @@
+Cookie: a=b; z=y
diff --git a/cookies/http-state/resources/test-files/0015-test b/cookies/http-state/resources/test-files/0015-test
new file mode 100644
index 0000000..b9a3125
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0015-test
@@ -0,0 +1,2 @@
+Set-Cookie: a=b
+Set-Cookie: z=y
diff --git a/cookies/http-state/resources/test-files/0016-expected b/cookies/http-state/resources/test-files/0016-expected
new file mode 100644
index 0000000..5f1a6fd
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0016-expected
@@ -0,0 +1 @@
+Cookie: z=y; a=b
diff --git a/cookies/http-state/resources/test-files/0016-test b/cookies/http-state/resources/test-files/0016-test
new file mode 100644
index 0000000..5565928
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0016-test
@@ -0,0 +1,2 @@
+Set-Cookie: z=y
+Set-Cookie: a=b
diff --git a/cookies/http-state/resources/test-files/0017-expected b/cookies/http-state/resources/test-files/0017-expected
new file mode 100644
index 0000000..f3c269c
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0017-expected
@@ -0,0 +1,2 @@
+Cookie: z=y, a=b
+
diff --git a/cookies/http-state/resources/test-files/0017-test b/cookies/http-state/resources/test-files/0017-test
new file mode 100644
index 0000000..b8984fe
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0017-test
@@ -0,0 +1 @@
+Set-Cookie: z=y, a=b
diff --git a/cookies/http-state/resources/test-files/0018-expected b/cookies/http-state/resources/test-files/0018-expected
new file mode 100644
index 0000000..0b54935
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0018-expected
@@ -0,0 +1,2 @@
+Cookie: z=y
+
diff --git a/cookies/http-state/resources/test-files/0018-test b/cookies/http-state/resources/test-files/0018-test
new file mode 100644
index 0000000..630bf9b
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0018-test
@@ -0,0 +1 @@
+Set-Cookie: z=y; foo=bar, a=b
diff --git a/cookies/http-state/resources/test-files/0019-expected b/cookies/http-state/resources/test-files/0019-expected
new file mode 100644
index 0000000..ecc95263
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0019-expected
@@ -0,0 +1 @@
+Cookie: foo=b
diff --git a/cookies/http-state/resources/test-files/0019-test b/cookies/http-state/resources/test-files/0019-test
new file mode 100644
index 0000000..352123e
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0019-test
@@ -0,0 +1 @@
+Set-Cookie: foo=b;max-age=3600, c=d;path=/
diff --git a/cookies/http-state/resources/test-files/0020-expected b/cookies/http-state/resources/test-files/0020-expected
new file mode 100644
index 0000000..a43cb00
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0020-expected
@@ -0,0 +1 @@
+Cookie: a=b; c=d
diff --git a/cookies/http-state/resources/test-files/0020-test b/cookies/http-state/resources/test-files/0020-test
new file mode 100644
index 0000000..9b35947
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0020-test
@@ -0,0 +1,3 @@
+Set-Cookie: a=b
+Set-Cookie: =
+Set-Cookie: c=d
diff --git a/cookies/http-state/resources/test-files/0021-expected b/cookies/http-state/resources/test-files/0021-expected
new file mode 100644
index 0000000..a43cb00
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0021-expected
@@ -0,0 +1 @@
+Cookie: a=b; c=d
diff --git a/cookies/http-state/resources/test-files/0021-test b/cookies/http-state/resources/test-files/0021-test
new file mode 100644
index 0000000..5781fe3
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0021-test
@@ -0,0 +1,3 @@
+Set-Cookie: a=b
+Set-Cookie: =x
+Set-Cookie: c=d
diff --git a/cookies/http-state/resources/test-files/0022-expected b/cookies/http-state/resources/test-files/0022-expected
new file mode 100644
index 0000000..5632bec
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0022-expected
@@ -0,0 +1 @@
+Cookie: a=b; x=; c=d
diff --git a/cookies/http-state/resources/test-files/0022-test b/cookies/http-state/resources/test-files/0022-test
new file mode 100644
index 0000000..70b9abf
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0022-test
@@ -0,0 +1,3 @@
+Set-Cookie: a=b
+Set-Cookie: x=
+Set-Cookie: c=d
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0023-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0023-expected
diff --git a/cookies/http-state/resources/test-files/0023-test b/cookies/http-state/resources/test-files/0023-test
new file mode 100644
index 0000000..609c668
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0023-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo
+Set-Cookie:
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0024-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0024-expected
diff --git a/cookies/http-state/resources/test-files/0024-test b/cookies/http-state/resources/test-files/0024-test
new file mode 100644
index 0000000..31dd69f
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0024-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo
+Set-Cookie: =
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0025-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0025-expected
diff --git a/cookies/http-state/resources/test-files/0025-test b/cookies/http-state/resources/test-files/0025-test
new file mode 100644
index 0000000..09f51a8
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0025-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo
+Set-Cookie: ; bar
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0026-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0026-expected
diff --git a/cookies/http-state/resources/test-files/0026-test b/cookies/http-state/resources/test-files/0026-test
new file mode 100644
index 0000000..609c668
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0026-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo
+Set-Cookie:
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/0027-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/0027-expected
diff --git a/cookies/http-state/resources/test-files/0027-test b/cookies/http-state/resources/test-files/0027-test
new file mode 100644
index 0000000..f7f6d26
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0027-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo
+Set-Cookie: bar
diff --git a/cookies/http-state/resources/test-files/0028-expected b/cookies/http-state/resources/test-files/0028-expected
new file mode 100644
index 0000000..609c668
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0028-expected
@@ -0,0 +1,2 @@
+Set-Cookie: foo
+Set-Cookie:
diff --git a/cookies/http-state/resources/test-files/0028-test b/cookies/http-state/resources/test-files/0028-test
new file mode 100644
index 0000000..609c668
--- /dev/null
+++ b/cookies/http-state/resources/test-files/0028-test
@@ -0,0 +1,2 @@
+Set-Cookie: foo
+Set-Cookie:
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0001-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0001-expected
diff --git a/cookies/http-state/resources/test-files/attribute0001-test b/cookies/http-state/resources/test-files/attribute0001-test
new file mode 100644
index 0000000..6199f78
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0001-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0002-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0002-expected
diff --git a/cookies/http-state/resources/test-files/attribute0002-test b/cookies/http-state/resources/test-files/attribute0002-test
new file mode 100644
index 0000000..047a24d
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0002-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; seCURe
diff --git a/cookies/http-state/resources/test-files/attribute0003-expected b/cookies/http-state/resources/test-files/attribute0003-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0003-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/attribute0003-test b/cookies/http-state/resources/test-files/attribute0003-test
new file mode 100644
index 0000000..c944bac
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0003-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; "Secure"
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0004-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0004-expected
diff --git a/cookies/http-state/resources/test-files/attribute0004-test b/cookies/http-state/resources/test-files/attribute0004-test
new file mode 100644
index 0000000..bcfaa7d8
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0004-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure=
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0005-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0005-expected
diff --git a/cookies/http-state/resources/test-files/attribute0005-test b/cookies/http-state/resources/test-files/attribute0005-test
new file mode 100644
index 0000000..1671087
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0005-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure=aaaa
diff --git a/cookies/http-state/resources/test-files/attribute0006-expected b/cookies/http-state/resources/test-files/attribute0006-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0006-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/attribute0006-test b/cookies/http-state/resources/test-files/attribute0006-test
new file mode 100644
index 0000000..39d7589
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0006-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0007-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0007-expected
diff --git a/cookies/http-state/resources/test-files/attribute0007-test b/cookies/http-state/resources/test-files/attribute0007-test
new file mode 100644
index 0000000..f75f46a
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0007-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure =aaaaa
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0008-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0008-expected
diff --git a/cookies/http-state/resources/test-files/attribute0008-test b/cookies/http-state/resources/test-files/attribute0008-test
new file mode 100644
index 0000000..be45b3a
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0008-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure= aaaaa
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0009-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0009-expected
diff --git a/cookies/http-state/resources/test-files/attribute0009-test b/cookies/http-state/resources/test-files/attribute0009-test
new file mode 100644
index 0000000..1a44c22
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0009-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure; qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0010-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0010-expected
diff --git a/cookies/http-state/resources/test-files/attribute0010-test b/cookies/http-state/resources/test-files/attribute0010-test
new file mode 100644
index 0000000..ca62200
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0010-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure;qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0011-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0011-expected
diff --git a/cookies/http-state/resources/test-files/attribute0011-test b/cookies/http-state/resources/test-files/attribute0011-test
new file mode 100644
index 0000000..3bd4c2e
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0011-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Secure    ; qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0012-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0012-expected
diff --git a/cookies/http-state/resources/test-files/attribute0012-test b/cookies/http-state/resources/test-files/attribute0012-test
new file mode 100644
index 0000000..6e7b816
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0012-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar;                Secure
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0013-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0013-expected
diff --git a/cookies/http-state/resources/test-files/attribute0013-test b/cookies/http-state/resources/test-files/attribute0013-test
new file mode 100644
index 0000000..f4814e4
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0013-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar;       Secure     ;
diff --git a/cookies/http-state/resources/test-files/attribute0014-expected b/cookies/http-state/resources/test-files/attribute0014-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0014-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/attribute0014-test b/cookies/http-state/resources/test-files/attribute0014-test
new file mode 100644
index 0000000..ef88896
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0014-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path
diff --git a/cookies/http-state/resources/test-files/attribute0015-expected b/cookies/http-state/resources/test-files/attribute0015-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0015-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/attribute0015-test b/cookies/http-state/resources/test-files/attribute0015-test
new file mode 100644
index 0000000..cea7060
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0015-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=
diff --git a/cookies/http-state/resources/test-files/attribute0016-expected b/cookies/http-state/resources/test-files/attribute0016-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0016-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/attribute0016-test b/cookies/http-state/resources/test-files/attribute0016-test
new file mode 100644
index 0000000..9a5b591
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0016-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=/
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0017-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0017-expected
diff --git a/cookies/http-state/resources/test-files/attribute0017-test b/cookies/http-state/resources/test-files/attribute0017-test
new file mode 100644
index 0000000..a6aeeb3
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0017-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=/qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0018-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0018-expected
diff --git a/cookies/http-state/resources/test-files/attribute0018-test b/cookies/http-state/resources/test-files/attribute0018-test
new file mode 100644
index 0000000..f912201
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0018-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path    =/qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0019-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0019-expected
diff --git a/cookies/http-state/resources/test-files/attribute0019-test b/cookies/http-state/resources/test-files/attribute0019-test
new file mode 100644
index 0000000..a424c6e
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0019-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=    /qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0020-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0020-expected
diff --git a/cookies/http-state/resources/test-files/attribute0020-test b/cookies/http-state/resources/test-files/attribute0020-test
new file mode 100644
index 0000000..367d2a1
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0020-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=/qux      ; taz
diff --git a/cookies/http-state/resources/test-files/attribute0021-expected b/cookies/http-state/resources/test-files/attribute0021-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0021-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/attribute0021-test b/cookies/http-state/resources/test-files/attribute0021-test
new file mode 100644
index 0000000..bb76deb
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0021-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=/qux; Path=/
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0022-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0022-expected
diff --git a/cookies/http-state/resources/test-files/attribute0022-test b/cookies/http-state/resources/test-files/attribute0022-test
new file mode 100644
index 0000000..ac79c0f
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0022-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=/; Path=/qux
diff --git a/cookies/http-state/resources/test-files/attribute0023-expected b/cookies/http-state/resources/test-files/attribute0023-expected
new file mode 100644
index 0000000..b14d4f6
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0023-expected
@@ -0,0 +1 @@
+Cookie: foo=bar
diff --git a/cookies/http-state/resources/test-files/attribute0023-test b/cookies/http-state/resources/test-files/attribute0023-test
new file mode 100644
index 0000000..97f2ac3
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0023-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=/qux; Path=/cookie-parser-result
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0024-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0024-expected
diff --git a/cookies/http-state/resources/test-files/attribute0024-test b/cookies/http-state/resources/test-files/attribute0024-test
new file mode 100644
index 0000000..cb041c5
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0024-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path=/cookie-parser-result; Path=/qux
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0025-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0025-expected
diff --git a/cookies/http-state/resources/test-files/attribute0025-test b/cookies/http-state/resources/test-files/attribute0025-test
new file mode 100644
index 0000000..c430943
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0025-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; qux; Secure
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/cookies/http-state/resources/test-files/attribute0026-expected
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to cookies/http-state/resources/test-files/attribute0026-expected
diff --git a/cookies/http-state/resources/test-files/attribute0026-test b/cookies/http-state/resources/test-files/attribute0026-test
new file mode 100644
index 0000000..7f68322
--- /dev/null
+++ b/cookies/http-state/resources/test-files/attribute0026-test
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; qux="aaa;bbb"; Secure
diff --git a/cookies/meta-blocked.html b/cookies/meta-blocked.html
new file mode 100644
index 0000000..1b86e65
--- /dev/null
+++ b/cookies/meta-blocked.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<head>
+  <meta http-equiv="set-cookie" content="meta-set-cookie=1">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+  <script>
+    test(t => {
+      assert_equals(document.cookie.indexOf('meta-set-cookie'), -1);
+    }, "Cookie is not set from `<meta>`.");
+  </script>
+</body>
diff --git a/cookies/path/echo-cookie.html b/cookies/path/echo-cookie.html
deleted file mode 100644
index dd515a8..0000000
--- a/cookies/path/echo-cookie.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <meta charset=utf-8>
-  <title>helper iframe for matching cookie path tests</title>
-  <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
-</head>
-<body>
-<script>
-window.setCookie = function (name, path) {
-  document.cookie = name + '=1; path = ' + path + ';';
-}
-window.fetchCookieThen = function (name, path) {
-  return fetch("/cookies/resources/set-cookie.py?name=" + encodeURIComponent(name) + "&path=" + encodeURIComponent(path));
-};
-window.isCookieSet = function (name, path) {
-  return document.cookie.match(name + '=1');
-};
-window.expireCookie = function (name, path) {
-  document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=' + path + ';';
-};
-</script>
-</body>
-</html>
diff --git a/cookies/path/match.html b/cookies/path/match.html
index c3f2f8e..54026ef 100644
--- a/cookies/path/match.html
+++ b/cookies/path/match.html
@@ -16,32 +16,32 @@
 var body = document.getElementsByTagName('body')[0];
 var createIframeThen = function (callback) {
   var iframe = document.createElement('iframe');
-  iframe.src = "echo-cookie.html";
+  iframe.src = "/cookies/resources/echo-cookie.html";
   body.appendChild(iframe);
   iframe.onload = callback;
   return iframe;
 };
 var testCookiePathFromDOM = function (testCase, test) {
   var iframe = createIframeThen(test.step_func(function () {
-    iframe.contentWindow.setCookie(testCase.name, testCase.path);
-    var cookieSet = iframe.contentWindow.isCookieSet(testCase.name, testCase.path);
+    iframe.contentWindow.setCookie('dom-' + testCase.name, testCase.path);
+    var cookieSet = iframe.contentWindow.isCookieSet('dom-' + testCase.name, testCase.path);
     if (testCase.match === false) {
       assert_equals(cookieSet, null);
     } else {
       assert_not_equals(cookieSet, null);
     }
 
-    iframe.contentWindow.expireCookie(testCase.name, testCase.path);
+    iframe.contentWindow.expireCookie('dom-' + testCase.name, testCase.path);
     test.done();
   }));
 };
 var testCookiePathFromHeader = function (testCase, test) {
   var iframe = createIframeThen(test.step_func(function () {
-    iframe.contentWindow.fetchCookieThen(testCase.name, testCase.path).then(test.step_func(function (response) {
+    iframe.contentWindow.fetchCookieThen('header-' + testCase.name, testCase.path).then(test.step_func(function (response) {
       assert_true(response.ok);
 
-      var cookieSet = iframe.contentWindow.isCookieSet(testCase.name, testCase.path);
-      iframe.contentWindow.expireCookie(testCase.name, testCase.path);
+      var cookieSet = iframe.contentWindow.isCookieSet('header-' + testCase.name, testCase.path);
+      iframe.contentWindow.expireCookie('header-' + testCase.name, testCase.path);
       if (testCase.match === false) {
         assert_equals(cookieSet, null);
       } else {
@@ -70,7 +70,7 @@
   "path": "/cookies/",
 }, {
   "name": "match-exact-page",
-  "path": "/cookies/path/echo-cookie.html",
+  "path": "/cookies/resources/echo-cookie.html",
 }, {
   "name": "no-match",
   "path": "/cook",
@@ -82,9 +82,9 @@
 }];
 
 var domTests = tests.map(function (testCase) {
-  var testName = "`document.cookie` on /cookies/path/echo-cookie.html sets cookie with path: " + testCase.path;
+  var testName = "`document.cookie` on /cookies/resources/echo-cookie.html sets cookie with path: " + testCase.path;
   if (testCase.match === false) {
-    testName = "`document.cookie` on /cookies/path/echo-cookie.html DOES NOT set cookie for path: " + testCase.path;
+    testName = "`document.cookie` on /cookies/resources/echo-cookie.html DOES NOT set cookie for path: " + testCase.path;
   }
   return [
     testName,
@@ -95,9 +95,9 @@
 });
 
 var headerTests = tests.map(function (testCase) {
-  var testName = "`Set-Cookie` on /cookies/path/echo-cookie.html sets cookie with path: " + testCase.path;
+  var testName = "`Set-Cookie` on /cookies/resources/echo-cookie.html sets cookie with path: " + testCase.path;
   if (testCase.match === false) {
-    testName = "`Set-Cookie` on /cookies/path/echo-cookie.html DOES NOT set cookie for path: " + testCase.path;
+    testName = "`Set-Cookie` on /cookies/resources/echo-cookie.html DOES NOT set cookie for path: " + testCase.path;
   }
   return [
     testName,
diff --git a/cookies/prefix/__host.document-cookie.non-secure.html b/cookies/prefix/__host.document-cookie.non-secure.html
new file mode 100644
index 0000000..e1a272a
--- /dev/null
+++ b/cookies/prefix/__host.document-cookie.non-secure.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+    // Without 'secure'
+    set_prefixed_cookie_via_dom_test({
+      prefix: "__Host-",
+      params: "Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Host: Non-secure origin: 'Path=/;" + extraParams + "'"
+    });
+
+    // With 'secure'
+    set_prefixed_cookie_via_dom_test({
+      prefix: "__Host-",
+      params: "Secure; Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Host: Non-secure origin: 'Secure; Path=/;" + extraParams + "'"
+    });
+  });
+
+  set_prefixed_cookie_via_dom_test({
+    prefix: "__Host-",
+    params: "Secure; Path=/cookies/resources/list.py",
+    shouldExistInDOM: false,
+    shouldExistViaHTTP: false,
+    title: "__Host: Non-secure origin: 'Secure; Path=/cookies/resources/list.py'"
+  });
+</script>
diff --git a/cookies/prefix/__host.http.non-secure.html b/cookies/prefix/__host.http.non-secure.html
new file mode 100644
index 0000000..3ce5873
--- /dev/null
+++ b/cookies/prefix/__host.http.non-secure.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+    // Without 'secure'
+    set_prefixed_cookie_via_http_test({
+      prefix: "__Host-",
+      params: "Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Host: Non-secure origin: 'Path=/;" + extraParams + "'"
+    });
+
+    // With 'secure'
+    set_prefixed_cookie_via_http_test({
+      prefix: "__Host-",
+      params: "Secure; Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Host: Non-secure origin: 'Secure; Path=/;" + extraParams + "'"
+    });
+  });
+
+  set_prefixed_cookie_via_http_test({
+    prefix: "__Host-",
+    params: "Secure; Path=/cookies/resources/list.py",
+    shouldExistInDOM: false,
+    shouldExistViaHTTP: false,
+    title: "__Host: Non-secure origin: 'Secure; Path=/cookies/resources/list.py'"
+  });
+</script>
+
diff --git a/cookies/prefix/__secure.document-cookie.non-secure.html b/cookies/prefix/__secure.document-cookie.non-secure.html
new file mode 100644
index 0000000..bf898f4
--- /dev/null
+++ b/cookies/prefix/__secure.document-cookie.non-secure.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+    // Without 'secure'
+    set_prefixed_cookie_via_dom_test({
+      prefix: "__Secure-",
+      params: "Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Secure: Non-secure origin: 'Path=/;" + extraParams + "'"
+    });
+
+    // With 'secure'
+    set_prefixed_cookie_via_dom_test({
+      prefix: "__Secure-",
+      params: "Secure; Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Secure: Non-secure origin: 'Secure; Path=/;" + extraParams + "'"
+    });
+  });
+</script>
diff --git a/cookies/prefix/__secure.http.non-secure.html b/cookies/prefix/__secure.http.non-secure.html
new file mode 100644
index 0000000..af844a9
--- /dev/null
+++ b/cookies/prefix/__secure.http.non-secure.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+    // Without 'secure'
+    set_prefixed_cookie_via_http_test({
+      prefix: "__Secure-",
+      params: "Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Secure: Non-secure origin: 'Path=/;" + extraParams + "'"
+    });
+
+    // With 'secure'
+    set_prefixed_cookie_via_http_test({
+      prefix: "__Secure-",
+      params: "Secure; Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Secure: Non-secure origin: 'Secure; Path=/;" + extraParams + "'"
+    });
+  });
+</script>
diff --git a/cookies/prefix/__secure.http.secure.html b/cookies/prefix/__secure.http.secure.html
new file mode 100644
index 0000000..4b413e9
--- /dev/null
+++ b/cookies/prefix/__secure.http.secure.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  ["", "domain="+CROSS_SITE_HOST, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+    // Without 'secure'
+    set_prefixed_cookie_via_http_test({
+      origin: SECURE_CROSS_SITE_ORIGIN,
+      prefix: "__Secure-",
+      params: "Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: false,
+      title: "__Secure: secure origin: 'Path=/;" + extraParams + "'"
+    });
+
+    // With 'secure'
+    set_prefixed_cookie_via_http_test({
+      origin: SECURE_CROSS_SITE_ORIGIN,
+      prefix: "__Secure-",
+      params: "Secure;Path=/;" + extraParams,
+      shouldExistInDOM: false,
+      shouldExistViaHTTP: true,
+      title: "__Secure: secure origin: 'Secure;Path=/;" + extraParams + "'"
+    });
+  });
+</script>
diff --git a/cookies/prefix/document-cookie.non-secure.html b/cookies/prefix/document-cookie.non-secure.html
new file mode 100644
index 0000000..bc6832b
--- /dev/null
+++ b/cookies/prefix/document-cookie.non-secure.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(prefix, params, shouldExistInDOM, shouldExistViaHTTP, title) {
+    promise_test(t => {
+      var name = prefix + "prefixtestcookie";
+      erase_cookie_from_js(name);
+      var value = "" + Math.random();
+      document.cookie = name + "=" + value + ";" + params;
+
+      assert_dom_cookie(name, value, shouldExistInDOM);
+
+      return credFetch("/cookies/rfx6265bis/resources/list.py")
+        .then(r => r.json())
+        .then(cookies => assert_equals(cookies[name], shouldExistViaHTTP ? value : undefined));
+    }, title);
+  }
+
+  // No prefix
+  create_test("", "path=/", true, true, "No prefix, root path, no special behavior");
+  create_test("", "path=/;domain=" + document.location.hostname, true, true, "No prefix, domain, no special behavior");
+
+  // `__Secure-` Prefix
+  ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(params => {
+    create_test("__Secure-", "Path=/;" + params, false, false, "__Secure: Non-secure origin: 'Path=/;" + params + "'");
+    create_test("__Secure-", "Secure; Path=/;" + params, false, false, "__Secure: Non-secure origin: 'Secure; Path=/;" + params + "'");
+  });
+
+  // `__Host-` Prefix
+  ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(params => {
+    create_test("__Secure-", "Path=/;" + params, false, false, "__Host: Non-secure origin: 'Path=/; " + params + "'");
+    create_test("__Secure-", "Secure; Path=/;" + params, false, false, "__Host: Non-secure origin: 'Secure; Path=/; " + params + "'");
+  });
+  create_test("__Secure-", "Path=/cookies/resources/list.py;Secure", false, false, "__Host: Non-secure origin: 'Path=/cookies/resources/list.py;Secure'");
+</script>
diff --git a/cookies/resources/cookie-helper.sub.js b/cookies/resources/cookie-helper.sub.js
new file mode 100644
index 0000000..852fbb6
--- /dev/null
+++ b/cookies/resources/cookie-helper.sub.js
@@ -0,0 +1,206 @@
+// Set up exciting global variables for cookie tests.
+(_ => {
+  var HOST = "{{host}}";
+  var SECURE_PORT = ":{{ports[https][0]}}";
+  var PORT = ":{{ports[http][0]}}";
+  var CROSS_ORIGIN_HOST = "{{hosts[alt][]}}";
+  var SECURE_CROSS_ORIGIN_HOST = "{{hosts[alt][]}}";
+
+  //For secure cookie verification
+  window.SECURE_ORIGIN = "https://" + HOST + SECURE_PORT;
+  window.INSECURE_ORIGIN = "http://" + HOST + PORT;
+
+  //standard references
+  window.ORIGIN = "http://" + HOST + PORT;
+  window.WWW_ORIGIN = "http://{{domains[www]}}" + PORT;
+  window.SUBDOMAIN_ORIGIN = "http://{{domains[www1]}}" + PORT;
+  window.CROSS_SITE_ORIGIN = "http://" + CROSS_ORIGIN_HOST + PORT;
+  window.SECURE_CROSS_SITE_ORIGIN = "https://" + SECURE_CROSS_ORIGIN_HOST + SECURE_PORT;
+  window.CROSS_SITE_HOST = SECURE_CROSS_ORIGIN_HOST;
+
+  // Set the global cookie name.
+  window.HTTP_COOKIE = "cookie_via_http";
+
+  // If we're not on |HOST|, move ourselves there:
+  if (window.location.hostname != HOST)
+    window.location.hostname = HOST;
+})();
+
+// A tiny helper which returns the result of fetching |url| with credentials.
+function credFetch(url) {
+  return fetch(url, {"credentials": "include"});
+}
+
+// Returns a URL on |origin| which redirects to a given absolute URL.
+function redirectTo(origin, url) {
+  return origin + "/cookies/resources/redirectWithCORSHeaders.py?status=307&location=" + encodeURIComponent(url);
+}
+
+// Asserts that `document.cookie` contains or does not contain (according to
+// the value of |present|) a cookie named |name| with a value of |value|.
+function assert_dom_cookie(name, value, present) {
+  var re = new RegExp("(?:^|; )" + name + "=" + value + "(?:$|;)");
+  assert_equals(re.test(document.cookie), present, "`" + name + "=" + value + "` in `document.cookie`");
+}
+
+function assert_cookie(origin, obj, name, value, present) {
+  assert_equals(obj[name], present ? value : undefined, "`" + name + "=" + value + "` in request to `" + origin + "`.");
+}
+
+// Remove the cookie named |name| from |origin|, then set it on |origin| anew.
+// If |origin| matches `document.origin`, also assert (via `document.cookie`) that
+// the cookie was correctly removed and reset.
+function create_cookie(origin, name, value, extras) {
+  alert("Create_cookie: " + origin + "/cookies/resources/drop.py?name=" + name);
+  return credFetch(origin + "/cookies/resources/drop.py?name=" + name)
+    .then(_ => {
+      if (origin == document.origin)
+        assert_dom_cookie(name, value, false);
+    })
+    .then(_ => {
+      return credFetch(origin + "/cookies/resources/set.py?" + name + "=" + value + ";path=/;" + extras)
+        .then(_ => {
+          if (origin == document.origin)
+            assert_dom_cookie(name, value, true);
+        });
+    });
+}
+
+//
+// Prefix-specific test helpers
+//
+function set_prefixed_cookie_via_dom_test(options) {
+  promise_test(t => {
+    var name = options.prefix + "prefixtestcookie";
+    erase_cookie_from_js(name);
+    var value = "" + Math.random();
+    document.cookie = name + "=" + value + ";" + options.params;
+
+    assert_dom_cookie(name, value, options.shouldExistInDOM);
+
+    return credFetch("/cookies/resources/list.py")
+      .then(r => r.json())
+      .then(cookies => assert_equals(cookies[name], options.shouldExistViaHTTP ? value : undefined));
+  }, options.title);
+}
+
+function set_prefixed_cookie_via_http_test(options) {
+  promise_test(t => {
+    var postDelete = _ => {
+      var value = "" + Math.random();
+      return credFetch(options.origin + "/cookies/resources/set.py?" + name + "=" + value + ";" + options.params)
+        .then(_ => credFetch(options.origin + "/cookies/resources/list.py"))
+        .then(r => r.json())
+        .then(cookies => assert_equals(cookies[name], options.shouldExistViaHTTP ? value : undefined));
+    };
+
+    var name = options.prefix + "prefixtestcookie";
+    if (!options.origin) {
+      options.origin = document.origin;
+      erase_cookie_from_js(name);
+      return postDelete;
+    } else {
+      return credFetch(options.origin + "/cookies/resources/drop.py?name=" + name)
+        .then(_ => postDelete());
+    }
+  }, options.title);
+}
+
+//
+// SameSite-specific test helpers:
+//
+
+window.SameSiteStatus = {
+  CROSS_SITE: "cross-site",
+  LAX: "lax",
+  STRICT: "strict"
+};
+
+// Reset SameSite test cookies on |origin|. If |origin| matches `document.origin`, assert
+// (via `document.cookie`) that they were properly removed and reset.
+function resetSameSiteCookies(origin, value) {
+  return credFetch(origin + "/cookies/resources/dropSameSite.py")
+    .then(_ => {
+      if (origin == document.origin) {
+        assert_dom_cookie("samesite_strict", value, false);
+        assert_dom_cookie("samesite_lax", value, false);
+        assert_dom_cookie("samesite_none", value, false);
+      }
+    })
+    .then(_ => {
+      return credFetch(origin + "/cookies/resources/setSameSite.py?" + value)
+        .then(_ => {
+          if (origin == document.origin) {
+            assert_dom_cookie("samesite_strict", value, true);
+            assert_dom_cookie("samesite_lax", value, true);
+            assert_dom_cookie("samesite_none", value, true);
+          }
+        })
+    })
+}
+
+// Given an |expectedStatus| and |expectedValue|, assert the |cookies| contains the
+// proper set of cookie names and values.
+function verifySameSiteCookieState(expectedStatus, expectedValue, cookies) {
+    assert_equals(cookies["samesite_none"], expectedValue, "Non-SameSite cookies are always sent.");
+    if (expectedStatus == SameSiteStatus.CROSS_SITE) {
+      assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not sent with cross-site requests.");
+      assert_not_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are not sent with cross-site requests.");
+    } else if (expectedStatus == SameSiteStatus.LAX) {
+      assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not sent with lax requests.");
+      assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are sent with lax requests.");
+    } else if (expectedStatus == SameSiteStatus.STRICT) {
+      assert_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are sent with strict requests.");
+      assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are sent with strict requests.");
+    }
+}
+
+//
+// LeaveSecureCookiesAlone-specific test helpers:
+//
+
+window.SecureStatus = {
+  INSECURE_COOKIE_ONLY: "1",
+  BOTH_COOKIES: "2",
+};
+
+//Reset SameSite test cookies on |origin|. If |origin| matches `document.origin`, assert
+//(via `document.cookie`) that they were properly removed and reset.
+function resetSecureCookies(origin, value) {
+return credFetch(origin + "/cookies/resources/dropSecure.py")
+ .then(_ => {
+   if (origin == document.origin) {
+     assert_dom_cookie("alone_secure", value, false);
+     assert_dom_cookie("alone_insecure", value, false);
+   }
+ })
+ .then(_ => {
+     return credFetch(origin + "/cookie/resources/setSecure.py?" + value)
+ })
+}
+
+//
+// DOM based cookie manipulation API's
+//
+
+// borrowed from http://www.quirksmode.org/js/cookies.html
+function create_cookie_from_js(name, value, days, secure_flag) {
+  if (days) {
+    var date = new Date();
+    date.setTime(date.getTime()+(days*24*60*60*1000));
+    var expires = "; expires="+date.toGMTString();
+  }
+  else var expires = "";
+
+  var secure = "";
+  if (secure_flag == true) {
+    secure = "secure; ";
+  }
+  document.cookie = name+"="+value+expires+"; "+secure+"path=/";
+}
+
+// erase cookie value and set for expiration
+function erase_cookie_from_js(name) {
+  create_cookie_from_js(name,"",-1);
+  assert_dom_cookie(name, "", false);
+}
diff --git a/cookies/resources/drop.py b/cookies/resources/drop.py
new file mode 100644
index 0000000..7491dad
--- /dev/null
+++ b/cookies/resources/drop.py
@@ -0,0 +1,15 @@
+from helpers import makeDropCookie, readParameter, setNoCacheAndCORSHeaders
+
+def main(request, response):
+    """Respond to `/cookie/drop?name={name}` by expiring the cookie named `{name}`."""
+    headers = setNoCacheAndCORSHeaders(request, response)
+    try:
+        # Expire the named cookie, and return a JSON-encoded success code.
+        name = readParameter(request, paramName="name", requireValue=True)
+        scheme = request.url_parts.scheme
+        headers.append(makeDropCookie(name,  "https" == scheme))
+        return headers, '{"success": true}'
+    except:
+        return 500, headers, '{"error" : "Empty or missing name parameter."}'
+
+
diff --git a/cookies/resources/dropSameSite.py b/cookies/resources/dropSameSite.py
new file mode 100644
index 0000000..803dbeb
--- /dev/null
+++ b/cookies/resources/dropSameSite.py
@@ -0,0 +1,12 @@
+from helpers import makeDropCookie, readParameter, setNoCacheAndCORSHeaders
+
+def main(request, response):
+    """Respond to `/cookie/same-site/resources/dropSameSite.py by dropping the
+    three cookies set by setSameSiteCookies.py"""
+    headers = setNoCacheAndCORSHeaders(request, response)
+
+    # Expire the cookies, and return a JSON-encoded success code.
+    headers.append(makeDropCookie("samesite_strict", False))
+    headers.append(makeDropCookie("samesite_lax", False))
+    headers.append(makeDropCookie("samesite_none", False))
+    return headers, '{"success": true}'
diff --git a/cookies/resources/dropSecure.py b/cookies/resources/dropSecure.py
new file mode 100644
index 0000000..f95e9a9
--- /dev/null
+++ b/cookies/resources/dropSecure.py
@@ -0,0 +1,11 @@
+from helpers import makeDropCookie, readParameter, setNoCacheAndCORSHeaders
+
+def main(request, response):
+    """Respond to `/cookie/drop/secure` by dropping the two cookie set by
+    `setSecureTestCookies()`"""
+    headers = setNoCacheAndCORSHeaders(request, response)
+
+    # Expire the cookies, and return a JSON-encoded success code.
+    headers.append(makeDropCookie("alone_secure", False))
+    headers.append(makeDropCookie("alone_insecure", False))
+    return headers, '{"success": true}'
diff --git a/cookies/resources/echo-cookie.html b/cookies/resources/echo-cookie.html
new file mode 100644
index 0000000..a715b8b
--- /dev/null
+++ b/cookies/resources/echo-cookie.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+  <meta charset=utf-8>
+  <title>helper iframe for matching cookie path tests</title>
+  <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+</head>
+<body>
+<script>
+window.setCookie = function (name, path) {
+  document.cookie = name + '=1; Path=' + path + ';';
+}
+window.fetchCookieThen = function (name, path) {
+  return fetch("/cookies/resources/set-cookie.py?name=" + encodeURIComponent(name) + "&path=" + encodeURIComponent(path), {'credentials': 'include'});
+};
+window.isCookieSet = function (name, path) {
+  return document.cookie.match(name + '=1');
+};
+window.expireCookie = function (name, path) {
+  document.cookie = name + '=0; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=' + path + ';';
+};
+</script>
+</body>
+</html>
diff --git a/cookies/resources/helpers.py b/cookies/resources/helpers.py
new file mode 100644
index 0000000..145f2fe
--- /dev/null
+++ b/cookies/resources/helpers.py
@@ -0,0 +1,55 @@
+import urlparse
+
+def setNoCacheAndCORSHeaders(request, response):
+    """Set Cache-Control, CORS and Content-Type headers appropriate for the cookie tests."""
+    headers = [("Content-Type", "application/json"),
+               ("Access-Control-Allow-Credentials", "true")]
+
+    origin = "*"
+    if "origin" in request.headers:
+        origin = request.headers["origin"]
+
+    headers.append(("Access-Control-Allow-Origin", origin))
+    #headers.append(("Access-Control-Allow-Credentials", "true"))
+    headers.append(("Cache-Control", "no-cache"))
+    headers.append(("Expires", "Fri, 01 Jan 1990 00:00:00 GMT"))
+
+    return headers
+
+def makeCookieHeader(name, value, otherAttrs):
+    """Make a Set-Cookie header for a cookie with the name, value and attributes provided."""
+    def makeAV(a, v):
+        if None == v or "" == v:
+            return a
+        return "%s=%s" % (a, v)
+
+    # ensure cookie name is always first
+    attrs = ["%s=%s" % (name, value)]
+    attrs.extend(makeAV(a, v) for (a,v) in otherAttrs.iteritems())
+    return ("Set-Cookie", "; ".join(attrs))
+
+def makeDropCookie(name, secure):
+    attrs = {"MaxAge": 0, "path": "/"}
+    if secure:
+        attrs["secure"] = ""
+    return makeCookieHeader(name, "", attrs)
+
+def readParameter(request, paramName, requireValue):
+    """Read a parameter from the request. Raise if requireValue is set and the
+    parameter has an empty value or is not present."""
+    params = urlparse.parse_qs(request.url_parts.query)
+    param = params[paramName][0].strip()
+    if len(param) == 0:
+        raise Exception("Empty or missing name parameter.")
+    return param
+
+def readCookies(request):
+    """Read the cookies from the client present in the request."""
+    cookies = {}
+    for key in request.cookies:
+        for cookie in request.cookies.get_list(key):
+            # do we care we'll clobber cookies here? If so, do we
+            # need to modify the test to take cookie names and value lists?
+            cookies[key] = cookie.value
+    return cookies
+
diff --git a/cookies/resources/imgIfMatch.py b/cookies/resources/imgIfMatch.py
new file mode 100644
index 0000000..0866441
--- /dev/null
+++ b/cookies/resources/imgIfMatch.py
@@ -0,0 +1,16 @@
+import helpers
+
+def main(request, response):
+    """Respond to `/cookie/imgIfMatch?name={name}&value={value}` with a 404 if
+       the cookie isn't present, and a transparent GIF otherwise."""
+    headers = helpers.setNoCacheAndCORSHeaders(request, response)
+    name = helpers.readParameter(request, paramName="name", requireValue=True)
+    value = helpers.readParameter(request, paramName="value", requireValue=True)
+    cookiesWithMatchingNames = request.cookies.get_list(name)
+    for cookie in cookiesWithMatchingNames:
+        if cookie.value == value:
+            # From https://github.com/mathiasbynens/small/blob/master/gif-transparent.gif
+            headers.append(("Content-Type","image/gif"))
+            gif = "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xFF\xFF\xFF\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B"
+            return headers, gif
+    return 500, headers, '{"error": {"message": "The cookie\'s value did not match the given value."}}'
diff --git a/cookies/resources/list.py b/cookies/resources/list.py
new file mode 100644
index 0000000..3fe7dd6
--- /dev/null
+++ b/cookies/resources/list.py
@@ -0,0 +1,7 @@
+import json
+import helpers
+
+def main(request, response):
+    headers = helpers.setNoCacheAndCORSHeaders(request, response)
+    cookies = helpers.readCookies(request)
+    return headers, json.dumps(cookies)
diff --git a/cookies/resources/postToParent.py b/cookies/resources/postToParent.py
new file mode 100644
index 0000000..68e85d3
--- /dev/null
+++ b/cookies/resources/postToParent.py
@@ -0,0 +1,27 @@
+import json
+import helpers
+
+def main(request, response):
+    headers = helpers.setNoCacheAndCORSHeaders(request, response)
+    cookies = helpers.readCookies(request)
+    headers.append(("Content-Type", "text/html; charset=utf-8"))
+
+    tmpl = """
+<!DOCTYPE html>
+<script>
+  var data = %s;
+
+  if (window.parent != window)
+    window.parent.postMessage(data, "*");
+
+  if (window.opener)
+    window.opener.postMessage(data, "*");
+
+  window.addEventListener("message", e => {
+    console.log(e);
+    if (e.data == "reload")
+      window.location.reload();
+  });
+</script>
+"""
+    return headers, tmpl % json.dumps(cookies)
diff --git a/cookies/resources/redirectWithCORSHeaders.py b/cookies/resources/redirectWithCORSHeaders.py
new file mode 100644
index 0000000..89ca1af
--- /dev/null
+++ b/cookies/resources/redirectWithCORSHeaders.py
@@ -0,0 +1,22 @@
+from helpers import setNoCacheAndCORSHeaders
+
+def main(request, response):
+    """Simple handler that causes redirection.
+
+    The request should typically have two query parameters:
+    status - The status to use for the redirection. Defaults to 302.
+    location - The resource to redirect to.
+    """
+    status = 302
+    if "status" in request.GET:
+        try:
+            status = int(request.GET.first("status"))
+        except ValueError:
+            pass
+    headers = setNoCacheAndCORSHeaders(request, response)
+
+    location = request.GET.first("location")
+
+    headers.append(("Location", location))
+
+    return status, headers, ""
diff --git a/cookies/resources/set.py b/cookies/resources/set.py
new file mode 100644
index 0000000..abfb8c8
--- /dev/null
+++ b/cookies/resources/set.py
@@ -0,0 +1,7 @@
+import helpers
+
+def main(request, response):
+    """Respond to `/cookie/set?{cookie}` by echoing `{cookie}` as a `Set-Cookie` header."""
+    headers = helpers.setNoCacheAndCORSHeaders(request, response)
+    headers.append(("Set-Cookie", request.url_parts.query))
+    return headers, '{"success": true}'
diff --git a/cookies/resources/setSameSite.py b/cookies/resources/setSameSite.py
new file mode 100644
index 0000000..8ae1776
--- /dev/null
+++ b/cookies/resources/setSameSite.py
@@ -0,0 +1,14 @@
+from helpers import makeCookieHeader, readParameter, setNoCacheAndCORSHeaders
+
+def main(request, response):
+    """Respond to `/cookie/set/samesite?{value}` by setting three cookies:
+    1. `samesite_strict={value};SameSite=Strict;path=/`
+    2. `samesite_lax={value};SameSite=Lax;path=/`
+    3. `samesite_none={value};path=/`"""
+    headers = setNoCacheAndCORSHeaders(request, response)
+    value = request.url_parts.query
+
+    headers.append(makeCookieHeader("samesite_strict", value, {"SameSite":"Strict","path":"/"}))
+    headers.append(makeCookieHeader("samesite_lax", value, {"SameSite":"Lax","path":"/"}))
+    headers.append(makeCookieHeader("samesite_none", value, {"path":"/"}))
+    return headers, '{"success": true}'
diff --git a/cookies/resources/setSecure.py b/cookies/resources/setSecure.py
new file mode 100644
index 0000000..c8ec017
--- /dev/null
+++ b/cookies/resources/setSecure.py
@@ -0,0 +1,12 @@
+from helpers import makeCookieHeader, readParameter, setNoCacheAndCORSHeaders
+
+def main(request, response):
+    """Respond to `/cookie/set/secure?{value}` by setting two cookies:
+    alone_secure={value};secure;path=/`
+    alone_insecure={value};path=/"""
+    headers = setNoCacheAndCORSHeaders(request, response)
+    value = request.url_parts.query
+
+    headers.append(makeCookieHeader("alone_secure", value, {"secure": "","path": "/"}))
+    headers.append(makeCookieHeader("alone_insecure", value, {"path": "/"}))
+    return headers, '{"success": true}'
diff --git a/cookies/samesite/fetch.html b/cookies/samesite/fetch.html
new file mode 100644
index 0000000..734462a
--- /dev/null
+++ b/cookies/samesite/fetch.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return credFetch(target + "/cookies/resources/list.py")
+
+            .then(r => r.json())
+            .then(cookies => verifySameSiteCookieState(expectedStatus, value, cookies));
+        });
+    }, title);
+  }
+
+  // No redirect:
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Same-host fetches are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain fetches are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site fetches are cross-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(ORIGIN, redirectTo(ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host fetches are strictly same-site");
+  create_test(ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host fetches are strictly same-site");
+  create_test(ORIGIN, redirectTo(CROSS_SITE_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to same-host fetches are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain fetches are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain fetches are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to subdomain fetches are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to cross-site:
+  create_test(CROSS_SITE_ORIGIN, redirectTo(ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site fetches are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site fetches are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site fetches are cross-site");
+</script>
diff --git a/cookies/samesite/form-get-blank-reload.html b/cookies/samesite/form-get-blank-reload.html
new file mode 100644
index 0000000..09f3ee9
--- /dev/null
+++ b/cookies/samesite/form-get-blank-reload.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var f = document.createElement('form');
+            f.action = target + "/cookies/resources/postToParent.py";
+            f.target = "_blank";
+            f.method = "GET";
+
+            // If |target| contains a `redir` parameter, extract it, and add it
+            // to the form so it doesn't get dropped in the submission.
+            var url = new URL(f.action);
+            if (url.pathname = "/cookies/rfc6265/resources/redirectWithCORSHeaders.py") {
+              var i = document.createElement("input");
+              i.name = "location";
+              i.value = url.searchParams.get("location");
+              i.type = "hidden";
+              f.appendChild(i);
+            }
+            var reloaded = false;
+            var msgHandler = e => {
+              try {
+                verifySameSiteCookieState(expectedStatus, value, e.data);
+              } catch (e) {
+                reject(e);
+              }
+
+              if (reloaded) {
+                window.removeEventListener("message", msgHandler);
+                e.source.close();
+                resolve("Popup received the cookie.");
+              } else {
+                reloaded = true;
+                e.source.postMessage("reload", "*");
+              }
+            };
+            window.addEventListener("message", msgHandler);
+            document.body.appendChild(f);
+
+            f.submit();
+          });
+        });
+    }, title);
+  }
+
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Reloaded same-host top-level form GETs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Reloaded subdomain top-level form GETs are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Reloaded cross-site top-level form GETs are laxly same-site");
+</script>
diff --git a/cookies/samesite/form-get-blank.html b/cookies/samesite/form-get-blank.html
new file mode 100644
index 0000000..a86f34b
--- /dev/null
+++ b/cookies/samesite/form-get-blank.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var f = document.createElement('form');
+            f.action = target + "/cookies/resources/postToParent.py";
+            f.target = "_blank";
+            f.method = "GET";
+
+            // If |target| contains a `redir` parameter, extract it, and add it
+            // to the form so it doesn't get dropped in the submission.
+            var url = new URL(f.action);
+            if (url.pathname == "/cookies/resources/redirectWithCORSHeaders.py") {
+              var i = document.createElement("input");
+              i.name = "location";
+              i.type="hidden";
+              i.value = url.searchParams.get("location");
+              f.appendChild(i);
+            }
+
+            var msgHandler = e => {
+              window.removeEventListener("message", msgHandler);
+              e.source.close();
+              try {
+                verifySameSiteCookieState(expectedStatus, value, e.data);
+                resolve("Popup received the cookie.");
+              } catch (e) {
+                reject(e);
+              }
+            };
+            window.addEventListener("message", msgHandler);
+            document.body.appendChild(f);
+            f.submit();
+          });
+        });
+    }, title);
+  }
+
+  // No redirect:
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Same-host top-level form GETs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain top-level form GETs are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Cross-site top-level form GETs are laxly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(ORIGIN, redirectTo(ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host top-level form GETs are strictly same-site");
+  create_test(ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host top-level form GETs are strictly same-site");
+  create_test(ORIGIN, redirectTo(CROSS_SITE_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to same-host top-level form GETs are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain top-level form GETs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain top-level form GETs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to subdomain top-level form GETs are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to cross-site:
+  create_test(CROSS_SITE_ORIGIN, redirectTo(ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Same-host redirecting to cross-site top-level form GETs are laxly same-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Subdomain redirecting to cross-site top-level form GETs are laxly same-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to cross-site top-level form GETs are laxly same-site");
+</script>
diff --git a/cookies/samesite/form-post-blank-reload.html b/cookies/samesite/form-post-blank-reload.html
new file mode 100644
index 0000000..f9449bf
--- /dev/null
+++ b/cookies/samesite/form-post-blank-reload.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var f = document.createElement('form');
+            f.action = target + "/cookies/resources/postToParent.py";
+            f.target = "_blank";
+            f.method = "POST";
+
+            var reloaded = false;
+            var msgHandler = e => {
+              try {
+                verifySameSiteCookieState(expectedStatus, value, e.data);
+              } catch (e) {
+                reject(e);
+              }
+
+              if (reloaded) {
+                window.removeEventListener("message", msgHandler);
+                e.source.close();
+                resolve("Popup received the cookie.");
+              } else {
+                reloaded = true;
+                e.source.postMessage("reload", "*");
+              }
+            };
+            window.addEventListener("message", msgHandler);
+
+            document.body.appendChild(f);
+            f.submit();
+          });
+        });
+    }, title);
+  }
+
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Reloaded same-host top-level form POSTs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Reloaded subdomain top-level form POSTs are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Reloaded cross-site top-level form POSTs are not same-site");
+</script>
diff --git a/cookies/samesite/form-post-blank.html b/cookies/samesite/form-post-blank.html
new file mode 100644
index 0000000..115c6a1
--- /dev/null
+++ b/cookies/samesite/form-post-blank.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var f = document.createElement('form');
+            f.action = target + "/cookies/resources/postToParent.py";
+            f.target = "_blank";
+            f.method = "POST";
+
+            var msgHandler = e => {
+              window.removeEventListener("message", msgHandler);
+              e.source.close();
+              try {
+                verifySameSiteCookieState(expectedStatus, value, e.data);
+                resolve("Popup received the cookie.");
+              } catch (e) {
+                reject(e);
+              }
+            };
+            window.addEventListener("message", msgHandler);
+            document.body.appendChild(f);
+            f.submit();
+          });
+        });
+    }, title);
+  }
+
+  // No redirect:
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Same-host top-level form POSTs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain top-level form POSTs are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site top-level form POSTs are cross-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(ORIGIN, redirectTo(ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host top-level form POSTs are strictly same-site");
+  create_test(ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host top-level form POSTs are strictly same-site");
+  create_test(ORIGIN, redirectTo(CROSS_SITE_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to same-host top-level form POSTs are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain top-level form POSTs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain top-level form POSTs are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to subdomain top-level form POSTs are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to cross-site:
+  create_test(CROSS_SITE_ORIGIN, redirectTo(ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site top-level form POSTs are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site top-level form POSTs are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site top-level form POSTs are cross-site");
+</script>
diff --git a/cookies/samesite/iframe-reload.html b/cookies/samesite/iframe-reload.html
new file mode 100644
index 0000000..759fc7b
--- /dev/null
+++ b/cookies/samesite/iframe-reload.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body -->
+<body>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var iframe = document.createElement("iframe");
+            iframe.onerror = _ => reject("IFrame could not be loaded.");
+
+            var reloaded = false;
+            var msgHandler = e => {
+              try {
+                verifySameSiteCookieState(expectedStatus, value, e.data);
+              } catch (e) {
+                reject(e);
+              }
+
+              if (reloaded) {
+                window.removeEventListener("message", msgHandler);
+                document.body.removeChild(iframe);
+                resolve("IFrame received the cookie.");
+              } else {
+                reloaded = true;
+                e.source.postMessage("reload", "*");
+              }
+            };
+            window.addEventListener("message", msgHandler);
+
+            iframe.src = target + "/cookies/resources/postToParent.py";
+            document.body.appendChild(iframe);
+          });
+        });
+    }, title);
+  }
+
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Reloaded same-host fetches are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Reloaded subdomain fetches are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Reloaded cross-site fetches are cross-site");
+</script>
diff --git a/cookies/samesite/iframe.html b/cookies/samesite/iframe.html
new file mode 100644
index 0000000..38a7701
--- /dev/null
+++ b/cookies/samesite/iframe.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body -->
+<body>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var iframe = document.createElement("iframe");
+            iframe.onerror = _ => reject("IFrame could not be loaded.");
+
+            var msgHandler = e => {
+              if (e.source == iframe.contentWindow) {
+                // Cleanup, then verify cookie state:
+                document.body.removeChild(iframe);
+                window.removeEventListener("message", msgHandler);
+                try {
+                  verifySameSiteCookieState(expectedStatus, value, e.data);
+                  resolve();
+                } catch(e) {
+                  reject(e);
+                }
+              }
+            };
+            window.addEventListener("message", msgHandler);
+
+            iframe.src = target + "/cookies/resources/postToParent.py";
+            document.body.appendChild(iframe);
+          });
+        });
+    }, title);
+  }
+
+  // No redirect:
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Same-host fetches are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain fetches are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site fetches are cross-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(ORIGIN, redirectTo(ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host fetches are strictly same-site");
+  create_test(ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host fetches are strictly same-site");
+  create_test(ORIGIN, redirectTo(CROSS_SITE_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to same-host fetches are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain fetches are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain fetches are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to subdomain fetches are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to cross-site:
+  create_test(CROSS_SITE_ORIGIN, redirectTo(ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site fetches are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site fetches are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site fetches are cross-site");
+</script>
diff --git a/cookies/samesite/img.html b/cookies/samesite/img.html
new file mode 100644
index 0000000..b1b0434
--- /dev/null
+++ b/cookies/samesite/img.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function assert_cookie_present(origin, name, value) {
+    return new Promise((resolve, reject) => {
+      var img = document.createElement("img");
+      img.onload = _ => resolve("'" + name + "=" + value + "' present on " + origin);
+      img.onerror = _ => reject("'" + name + "=" + value + "' not present on " + origin);
+
+      // We need to URL encode the destination path/query if we're redirecting:
+      if (origin.match(/\/redir/))
+        img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
+      else
+        img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
+    });
+  }
+
+  function assert_cookie_absent(origin, name, value) {
+    return new Promise((resolve, reject) => {
+      var img = document.createElement("img");
+      img.onload = _ => reject("'" + name + "=" + value + "' present on " + origin);
+      img.onerror = _ => resolve("'" + name + "=" + value + "' not present on " + origin);
+
+      // We need to URL encode the destination path/query if we're redirecting:
+      if (origin.match(/\/redir/))
+        img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
+      else
+        img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
+    });
+  }
+
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return Promise.all([
+            assert_cookie_present(target, "samesite_none", value),
+            expectedStatus == SameSiteStatus.STRICT ?
+              assert_cookie_present(target, "samesite_strict", value) :
+              assert_cookie_absent(target, "samesite_strict", value),
+            expectedStatus == SameSiteStatus.CROSS_SITE ?
+              assert_cookie_absent(target, "samesite_lax", value) :
+              assert_cookie_present(target, "samesite_lax", value)
+          ]);
+        });
+    }, title);
+  }
+
+  // No redirect:
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Same-host images are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain images are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site images are cross-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(ORIGIN, redirectTo(ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host images are strictly same-site");
+  create_test(ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host images are strictly same-site");
+  create_test(ORIGIN, redirectTo(CROSS_SITE_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to same-host images are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain images are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain images are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to subdomain images are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to cross-site:
+  create_test(CROSS_SITE_ORIGIN, redirectTo(ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site images are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site images are cross-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site images are cross-site");
+</script>
diff --git a/cookies/samesite/window-open-reload.html b/cookies/samesite/window-open-reload.html
new file mode 100644
index 0000000..b37cff8
--- /dev/null
+++ b/cookies/samesite/window-open-reload.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var w = window.open(origin + "/cookies/resources/postToParent.py");
+
+            var reloaded = false;
+            var msgHandler = e => {
+              try {
+                verifySameSiteCookieState(expectedStatus, value, e.data);
+              } catch (e) {
+                reject(e);
+              }
+
+              if (reloaded) {
+                window.removeEventListener("message", msgHandler);
+                w.close();
+                resolve("Popup received the cookie.");
+              } else {
+                reloaded = true;
+                w.postMessage("reload", "*");
+              }
+            };
+            window.addEventListener("message", msgHandler);
+
+            if (!w)
+              reject("Popup could not be opened (did you whitelist the test site in your popup blocker?).");
+          });
+        });
+    }, title);
+  }
+
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Reloaded same-host auxiliary navigations are strictly same-site.");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Reloaded subdomain auxiliary navigations are strictly same-site.");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Reloaded ross-site auxiliary navigations are laxly same-site");
+</script>
diff --git a/cookies/samesite/window-open.html b/cookies/samesite/window-open.html
new file mode 100644
index 0000000..1aa8e5e
--- /dev/null
+++ b/cookies/samesite/window-open.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSameSiteCookies(origin, value)
+        .then(_ => {
+          return new Promise((resolve, reject) => {
+            var w = window.open(origin + "/cookies/resources/postToParent.py");
+
+            var msgHandler = e => {
+              window.removeEventListener("message", msgHandler);
+              w.close();
+              try {
+                verifySameSiteCookieState(expectedStatus, value, e.data);
+                resolve("Popup received the cookie.");
+              } catch (e) {
+                reject(e);
+              }
+            };
+            window.addEventListener("message", msgHandler);
+
+            if (!w)
+              reject("Popup could not be opened (did you whitelist the test site in your popup blocker?).");
+          });
+        });
+    }, title);
+  }
+
+  // No redirect:
+  create_test(ORIGIN, ORIGIN, SameSiteStatus.STRICT, "Same-host auxiliary navigations are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain auxiliary navigations are strictly same-site");
+  create_test(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Cross-site auxiliary navigations are laxly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(ORIGIN, redirectTo(ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host auxiliary navigations are strictly same-site");
+  create_test(ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host auxiliary navigations are strictly same-site");
+  create_test(ORIGIN, redirectTo(CROSS_SITE_ORIGIN, ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to same-host auxiliary navigations are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to same-host:
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain auxiliary navigations are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain auxiliary navigations are strictly same-site");
+  create_test(SUBDOMAIN_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Cross-site redirecting to subdomain auxiliary navigations are strictly same-site");
+
+  // Redirect from {same-host,subdomain,cross-site} to cross-site:
+  create_test(CROSS_SITE_ORIGIN, redirectTo(ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Same-host redirecting to cross-site auxiliary navigations are laxly same-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(SUBDOMAIN_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Subdomain redirecting to cross-site auxiliary navigations are laxly same-site");
+  create_test(CROSS_SITE_ORIGIN, redirectTo(CROSS_SITE_ORIGIN, CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to cross-site auxiliary navigations are laxly same-site");
+</script>
diff --git a/cookies/secure/cookie-forcing.html b/cookies/secure/cookie-forcing.html
new file mode 100644
index 0000000..3ea59e1
--- /dev/null
+++ b/cookies/secure/cookie-forcing.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  function cookie_force_test(secure_origin, secure_cookie, present, title) {
+    var counter = 0;
+    promise_test(t => {
+
+      var testCookieValue = "" + Math.random();
+      var markerCookieName = "marker";
+      var markerCookieValue = "markerVal";
+
+      var brakes = 5000; //limit cookie setting limit in case browers are magic
+
+      // Set an initial cookie as a marker
+      create_cookie_from_js(markerCookieName, markerCookieValue, 10, secure_cookie);
+      //TODO we cant trust document.cookie to set secure cookies. Need a round trip to a secure origin.
+      assert_dom_cookie(markerCookieName, markerCookieValue, true);
+
+      // Set new cookies until marker is gone
+      try {
+        for (i = 0; i < brakes; i++) {
+          create_cookie_from_js(markerCookieName + counter++, markerCookieValue, 10, secure_cookie);
+          assert_dom_cookie(markerCookieName, markerCookieValue, true);
+        }
+      } catch(err) {
+        //shame on me, just fiddling for now
+      }
+
+      assert_dom_cookie(markerCookieName, markerCookieValue, present);
+
+      if (present == false) {
+        alert("It took " + counter + " cookies to force out the marker cookie");
+      } else {
+        alert("Even after " + counter + " cookies the marker cookie was not forced out. Try incresing the current limit of " + brakes);
+      }
+
+    }, title);
+  }
+
+
+  //actual tests to verify that non-secure origins should "leave secure cookies alone"
+  cookie_force_test(false, false, false, "non-secure origins should be able to force out insecure cookies.");
+
+</script>
diff --git a/cookies/secure/create-cookie-http.html b/cookies/secure/create-cookie-http.html
new file mode 100644
index 0000000..425f66f
--- /dev/null
+++ b/cookies/secure/create-cookie-http.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+  //origin is who sets the cookie
+  //target is who tries to send cookie
+  function create_test(origin, target, expectedStatus, title) {
+    promise_test(t => {
+      var value = "" + Math.random();
+      return resetSecureCookies(origin, value)
+        .then(_ => {
+          return credFetch(target + "/cookies/resources/list.py")
+            .then(r => r.json())
+            .then(cookies => verifySecureCookieState(expectedStatus, value, cookies));
+        });
+    }, title);
+  }
+
+  //Given an |expectedStatus| and |expectedValue|, assert the |cookies| contains the
+  //proper set of cookie names and values.
+  function verifySecureCookieState(expectedStatus, expectedValue, cookies) {
+    assert_equals(cookies["alone_insecure"], expectedValue, "Insecure cookies are always present");
+    if (expectedStatus == SecureStatus.INSECURE_COOKIE_ONLY) {
+    	assert_equals(cookies["alone_secure"], undefined, "Secure cookies are not present");
+    } else if (expectedStatus == SecureStatus.BOTH_COOKIES) {
+    	assert_equals(cookies["alone_secure"], expectedValue, "Secure cookies are present");
+    }
+  }
+
+  //cookies set by insecure origins
+  create_test(INSECURE_ORIGIN, INSECURE_ORIGIN, SecureStatus.INSECURE_COOKIE_ONLY, "Secure cookies cannot be set by insecure origins");
+  //create_test(INSECURE_ORIGIN, SECURE_ORIGIN, SecureStatus.INSECURE_COOKIE_ONLY, "Secure cookies cannot be set by insecure origins, even if read from a secure origin");
+
+  //This test should set the secure cookie right but not be able to read it from the secure origin
+  //create_test(SECURE_ORIGIN, INSECURE_ORIGIN, SecureStatus.INSECURE_COOKIE_ONLY, "Secure cookies should not be read by insecure origins");
+  //create_test(SECURE_ORIGIN, SECURE_ORIGIN, SecureStatus.BOTH_COOKIES, "Secure cookies should be set and read by secure domains")
+</script>
diff --git a/cookies/secure/set-from-ws.https.sub.html b/cookies/secure/set-from-ws.sub.html
similarity index 100%
rename from cookies/secure/set-from-ws.https.sub.html
rename to cookies/secure/set-from-ws.sub.html
diff --git a/core-aam/alert-manual.html b/core-aam/alert-manual.html
index 938da43..0177ffb 100644
--- a/core-aam/alert-manual.html
+++ b/core-aam/alert-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>alert</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/alertdialog-manual.html b/core-aam/alertdialog-manual.html
index 98d3b1a..eacdde3 100644
--- a/core-aam/alertdialog-manual.html
+++ b/core-aam/alertdialog-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>alertdialog</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/application-manual.html b/core-aam/application-manual.html
index 3d22162..1fe0577 100644
--- a/core-aam/application-manual.html
+++ b/core-aam/application-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>application</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-atomic_false-manual.html b/core-aam/aria-atomic_false-manual.html
index 61b7b00..d1ab0b6 100644
--- a/core-aam/aria-atomic_false-manual.html
+++ b/core-aam/aria-atomic_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-atomic=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-atomic_true-manual.html b/core-aam/aria-atomic_true-manual.html
index 9462c0a..74353a6 100644
--- a/core-aam/aria-atomic_true-manual.html
+++ b/core-aam/aria-atomic_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-atomic=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-autocomplete_both-manual.html b/core-aam/aria-autocomplete_both-manual.html
index 6fea230..9b02846 100644
--- a/core-aam/aria-autocomplete_both-manual.html
+++ b/core-aam/aria-autocomplete_both-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-autocomplete=both</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-autocomplete_inline-manual.html b/core-aam/aria-autocomplete_inline-manual.html
index 32e45dd..e4b985d 100644
--- a/core-aam/aria-autocomplete_inline-manual.html
+++ b/core-aam/aria-autocomplete_inline-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-autocomplete=inline</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-autocomplete_list-manual.html b/core-aam/aria-autocomplete_list-manual.html
index b74f396..4ba6d68 100644
--- a/core-aam/aria-autocomplete_list-manual.html
+++ b/core-aam/aria-autocomplete_list-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-autocomplete=list</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-busy_false-manual.html b/core-aam/aria-busy_false-manual.html
index 0601b60..70a2508 100644
--- a/core-aam/aria-busy_false-manual.html
+++ b/core-aam/aria-busy_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-busy=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-busy_true-manual.html b/core-aam/aria-busy_true-manual.html
index 1d0781c..ce5a541 100644
--- a/core-aam/aria-busy_true-manual.html
+++ b/core-aam/aria-busy_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-busy=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-busy_value_changes-manual.html b/core-aam/aria-busy_value_changes-manual.html
index d39fd2f..ff7c679 100644
--- a/core-aam/aria-busy_value_changes-manual.html
+++ b/core-aam/aria-busy_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-busy value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-checked_false_on_checkbox-manual.html b/core-aam/aria-checked_false_on_checkbox-manual.html
index 657e4b0..e508c5b 100644
--- a/core-aam/aria-checked_false_on_checkbox-manual.html
+++ b/core-aam/aria-checked_false_on_checkbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-checked=false on checkbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-checked_false_on_menuitemradio-manual.html b/core-aam/aria-checked_false_on_menuitemradio-manual.html
index 3b9cced..a977d4c 100644
--- a/core-aam/aria-checked_false_on_menuitemradio-manual.html
+++ b/core-aam/aria-checked_false_on_menuitemradio-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-checked=false on menuitemradio</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-checked_mixed-manual.html b/core-aam/aria-checked_mixed-manual.html
index cb876d7..c685b8b 100644
--- a/core-aam/aria-checked_mixed-manual.html
+++ b/core-aam/aria-checked_mixed-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-checked=mixed</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-checked_true_on_checkbox-manual.html b/core-aam/aria-checked_true_on_checkbox-manual.html
index 72c45db..440731c 100644
--- a/core-aam/aria-checked_true_on_checkbox-manual.html
+++ b/core-aam/aria-checked_true_on_checkbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-checked=true on checkbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-checked_true_on_menuitemradio-manual.html b/core-aam/aria-checked_true_on_menuitemradio-manual.html
index 224bc09..0d22a08 100644
--- a/core-aam/aria-checked_true_on_menuitemradio-manual.html
+++ b/core-aam/aria-checked_true_on_menuitemradio-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-checked=true on menuitemradio</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-checked_value_changes-manual.html b/core-aam/aria-checked_value_changes-manual.html
index e3fb333..f90ac2f 100644
--- a/core-aam/aria-checked_value_changes-manual.html
+++ b/core-aam/aria-checked_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-checked value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -59,7 +58,7 @@
                   "event",
                   "type",
                   "is",
-                  "TBD"
+                  "TogglePattern.ToggleStateProperty"
                ]
             ]
          },
@@ -111,7 +110,7 @@
                   "event",
                   "type",
                   "is",
-                  "TBD"
+                  "TogglePattern.ToggleStateProperty"
                ]
             ]
          },
diff --git a/core-aam/aria-colcount_new-manual.html b/core-aam/aria-colcount_new-manual.html
index 60b797b..9ab005f 100644
--- a/core-aam/aria-colcount_new-manual.html
+++ b/core-aam/aria-colcount_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-colcount NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-colindex_new-manual.html b/core-aam/aria-colindex_new-manual.html
index 3a0cead..0f48509 100644
--- a/core-aam/aria-colindex_new-manual.html
+++ b/core-aam/aria-colindex_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-colindex NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-colspan_new-manual.html b/core-aam/aria-colspan_new-manual.html
index d9148f6..bd99cde 100644
--- a/core-aam/aria-colspan_new-manual.html
+++ b/core-aam/aria-colspan_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-colspan NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-controls-manual.html b/core-aam/aria-controls-manual.html
index 29355eb..e903d21 100644
--- a/core-aam/aria-controls-manual.html
+++ b/core-aam/aria-controls-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-controls</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-current_value_changes-manual.html b/core-aam/aria-current_value_changes-manual.html
index 57c65cb..14a5aae 100644
--- a/core-aam/aria-current_value_changes-manual.html
+++ b/core-aam/aria-current_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-current_with_non-false_allowed_value_new-manual.html b/core-aam/aria-current_with_non-false_allowed_value_new-manual.html
index 23f7926..da9e2be 100644
--- a/core-aam/aria-current_with_non-false_allowed_value_new-manual.html
+++ b/core-aam/aria-current_with_non-false_allowed_value_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with non-false allowed value NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-current_with_unrecognized_value_new-manual.html b/core-aam/aria-current_with_unrecognized_value_new-manual.html
index ad1e7f6..a01f730 100644
--- a/core-aam/aria-current_with_unrecognized_value_new-manual.html
+++ b/core-aam/aria-current_with_unrecognized_value_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with unrecognized value NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-describedby-manual.html b/core-aam/aria-describedby-manual.html
index 473b555..4cffe6f 100644
--- a/core-aam/aria-describedby-manual.html
+++ b/core-aam/aria-describedby-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-describedby</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-details_new-manual.html b/core-aam/aria-details_new-manual.html
index cbce3c9..1b182fc 100644
--- a/core-aam/aria-details_new-manual.html
+++ b/core-aam/aria-details_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-details NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-disabled_false-manual.html b/core-aam/aria-disabled_false-manual.html
index 078271c..e755d8e 100644
--- a/core-aam/aria-disabled_false-manual.html
+++ b/core-aam/aria-disabled_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-disabled=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-disabled_true-manual.html b/core-aam/aria-disabled_true-manual.html
index bc469f7..f373faf 100644
--- a/core-aam/aria-disabled_true-manual.html
+++ b/core-aam/aria-disabled_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-disabled=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-disabled_value_changes-manual.html b/core-aam/aria-disabled_value_changes-manual.html
index 9324934..41236f1 100644
--- a/core-aam/aria-disabled_value_changes-manual.html
+++ b/core-aam/aria-disabled_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-disabled value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-dropeffect_copy-manual.html b/core-aam/aria-dropeffect_copy-manual.html
index 8848d17..6122d12 100644
--- a/core-aam/aria-dropeffect_copy-manual.html
+++ b/core-aam/aria-dropeffect_copy-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-dropeffect=copy</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-dropeffect_execute-manual.html b/core-aam/aria-dropeffect_execute-manual.html
index ec3432b..54578f7 100644
--- a/core-aam/aria-dropeffect_execute-manual.html
+++ b/core-aam/aria-dropeffect_execute-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-dropeffect=execute</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-dropeffect_link-manual.html b/core-aam/aria-dropeffect_link-manual.html
index 2dbbd17..1dd86a3 100644
--- a/core-aam/aria-dropeffect_link-manual.html
+++ b/core-aam/aria-dropeffect_link-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-dropeffect=link</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-dropeffect_move-manual.html b/core-aam/aria-dropeffect_move-manual.html
index 332b838..eeee18e 100644
--- a/core-aam/aria-dropeffect_move-manual.html
+++ b/core-aam/aria-dropeffect_move-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-dropeffect=move</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-dropeffect_none-manual.html b/core-aam/aria-dropeffect_none-manual.html
index 41cd080..344349c 100644
--- a/core-aam/aria-dropeffect_none-manual.html
+++ b/core-aam/aria-dropeffect_none-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-dropeffect=none</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-dropeffect_popup-manual.html b/core-aam/aria-dropeffect_popup-manual.html
index 8f1b84f..2e00ae2 100644
--- a/core-aam/aria-dropeffect_popup-manual.html
+++ b/core-aam/aria-dropeffect_popup-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-dropeffect=popup</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-dropeffect_value_changes-manual.html b/core-aam/aria-dropeffect_value_changes-manual.html
index 9302b3b..6e623c0 100644
--- a/core-aam/aria-dropeffect_value_changes-manual.html
+++ b/core-aam/aria-dropeffect_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-dropeffect value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-errormessage-manual.html b/core-aam/aria-errormessage-manual.html
index 128d93f..fada768 100644
--- a/core-aam/aria-errormessage-manual.html
+++ b/core-aam/aria-errormessage-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-errormessage</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-expanded_false-manual.html b/core-aam/aria-expanded_false-manual.html
index c0b8bd6..2560998 100644
--- a/core-aam/aria-expanded_false-manual.html
+++ b/core-aam/aria-expanded_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-expanded=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-expanded_true-manual.html b/core-aam/aria-expanded_true-manual.html
index 0cbdffa..30f0c6a 100644
--- a/core-aam/aria-expanded_true-manual.html
+++ b/core-aam/aria-expanded_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-expanded=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-expanded_value_changes-manual.html b/core-aam/aria-expanded_value_changes-manual.html
index 873094b..f6767b1 100644
--- a/core-aam/aria-expanded_value_changes-manual.html
+++ b/core-aam/aria-expanded_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-expanded value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -65,7 +64,7 @@
                   "event",
                   "type",
                   "is",
-                  "TBD"
+                  "ExpandCollapsePattern.ExpandCollapseStateProperty"
                ]
             ]
          },
@@ -123,7 +122,7 @@
                   "event",
                   "type",
                   "is",
-                  "TBD"
+                  "ExpandCollapsePattern.ExpandCollapseStateProperty"
                ]
             ]
          },
diff --git a/core-aam/aria-flowto-manual.html b/core-aam/aria-flowto-manual.html
index f8e55a4..0902551 100644
--- a/core-aam/aria-flowto-manual.html
+++ b/core-aam/aria-flowto-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-flowto</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -36,7 +35,7 @@
             "IAccessible2" : [
                [
                   "relation",
-                  "IA2_RELATION_FLOW_TO",
+                  "IA2_RELATION_FLOWS_TO",
                   "is",
                   "[next]"
                ]
@@ -67,7 +66,7 @@
             "IAccessible2" : [
                [
                   "relation",
-                  "IA2_RELATION_FLOW_FROM",
+                  "IA2_RELATION_FLOWS_FROM",
                   "is",
                   "[test]"
                ]
diff --git a/core-aam/aria-grabbed_false-manual.html b/core-aam/aria-grabbed_false-manual.html
index 006914e..7b6404c 100644
--- a/core-aam/aria-grabbed_false-manual.html
+++ b/core-aam/aria-grabbed_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-grabbed=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-grabbed_true-manual.html b/core-aam/aria-grabbed_true-manual.html
index aaec8e1..5ef11ce 100644
--- a/core-aam/aria-grabbed_true-manual.html
+++ b/core-aam/aria-grabbed_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-grabbed=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-grabbed_value_changes-manual.html b/core-aam/aria-grabbed_value_changes-manual.html
index 645d80d..795a0e8 100644
--- a/core-aam/aria-grabbed_value_changes-manual.html
+++ b/core-aam/aria-grabbed_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-grabbed value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-haspopup_dialog_new-manual.html b/core-aam/aria-haspopup_dialog_new-manual.html
index 747113f..e2111a8 100644
--- a/core-aam/aria-haspopup_dialog_new-manual.html
+++ b/core-aam/aria-haspopup_dialog_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-haspopup=dialog NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-haspopup_false-manual.html b/core-aam/aria-haspopup_false-manual.html
index 2eb81ee..50b0fad 100644
--- a/core-aam/aria-haspopup_false-manual.html
+++ b/core-aam/aria-haspopup_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-haspopup=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-haspopup_listbox_new-manual.html b/core-aam/aria-haspopup_listbox_new-manual.html
index 4311592..70d1268 100644
--- a/core-aam/aria-haspopup_listbox_new-manual.html
+++ b/core-aam/aria-haspopup_listbox_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-haspopup=listbox NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-haspopup_menu_new-manual.html b/core-aam/aria-haspopup_menu_new-manual.html
index 966a5bc..95fde14 100644
--- a/core-aam/aria-haspopup_menu_new-manual.html
+++ b/core-aam/aria-haspopup_menu_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-haspopup=menu NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-haspopup_tree_new-manual.html b/core-aam/aria-haspopup_tree_new-manual.html
index 7a048fb..cc3e33c 100644
--- a/core-aam/aria-haspopup_tree_new-manual.html
+++ b/core-aam/aria-haspopup_tree_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-haspopup=tree NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-haspopup_true-manual.html b/core-aam/aria-haspopup_true-manual.html
index 8ee14bf..57b5ad4 100644
--- a/core-aam/aria-haspopup_true-manual.html
+++ b/core-aam/aria-haspopup_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-haspopup=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-hidden_true-manual.html b/core-aam/aria-hidden_true-manual.html
index f9ffcf9..fd597f3 100644
--- a/core-aam/aria-hidden_true-manual.html
+++ b/core-aam/aria-hidden_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-hidden=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-hidden_true_when_element_is_focused_or_fires_event_new-manual.html b/core-aam/aria-hidden_true_when_element_is_focused_or_fires_event_new-manual.html
index 81f19f1..4d47e89 100644
--- a/core-aam/aria-hidden_true_when_element_is_focused_or_fires_event_new-manual.html
+++ b/core-aam/aria-hidden_true_when_element_is_focused_or_fires_event_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-hidden=true when element is focused or fires event NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-hidden_value_changes-manual.html b/core-aam/aria-hidden_value_changes-manual.html
index 445a942..aa43a9a 100644
--- a/core-aam/aria-hidden_value_changes-manual.html
+++ b/core-aam/aria-hidden_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-hidden value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-invalid_false-manual.html b/core-aam/aria-invalid_false-manual.html
index c7fbb2c..86c43bb 100644
--- a/core-aam/aria-invalid_false-manual.html
+++ b/core-aam/aria-invalid_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-invalid=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-invalid_grammar-manual.html b/core-aam/aria-invalid_grammar-manual.html
index 60bc5d7..ee8be2d 100644
--- a/core-aam/aria-invalid_grammar-manual.html
+++ b/core-aam/aria-invalid_grammar-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-invalid=grammar</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-invalid_spelling-manual.html b/core-aam/aria-invalid_spelling-manual.html
index a176221..53e9f98 100644
--- a/core-aam/aria-invalid_spelling-manual.html
+++ b/core-aam/aria-invalid_spelling-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-invalid=spelling</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-invalid_true-manual.html b/core-aam/aria-invalid_true-manual.html
index 196af9b..84b966f 100644
--- a/core-aam/aria-invalid_true-manual.html
+++ b/core-aam/aria-invalid_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-invalid=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-invalid_value_changes-manual.html b/core-aam/aria-invalid_value_changes-manual.html
index 96b3326..c34109f 100644
--- a/core-aam/aria-invalid_value_changes-manual.html
+++ b/core-aam/aria-invalid_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-invalid value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-invalid_with_unrecognized_value_new-manual.html b/core-aam/aria-invalid_with_unrecognized_value_new-manual.html
index dd10baf..38a4330 100644
--- a/core-aam/aria-invalid_with_unrecognized_value_new-manual.html
+++ b/core-aam/aria-invalid_with_unrecognized_value_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-invalid with unrecognized value NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-keyshortcuts_new-manual.html b/core-aam/aria-keyshortcuts_new-manual.html
index f8dbdb0..47f18e6 100644
--- a/core-aam/aria-keyshortcuts_new-manual.html
+++ b/core-aam/aria-keyshortcuts_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-keyshortcuts NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-label-manual.html b/core-aam/aria-label-manual.html
index 00c7e1e..ec3fa25 100644
--- a/core-aam/aria-label-manual.html
+++ b/core-aam/aria-label-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-label</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-labelledby-manual.html b/core-aam/aria-labelledby-manual.html
index 557bba9..8e14469 100644
--- a/core-aam/aria-labelledby-manual.html
+++ b/core-aam/aria-labelledby-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-labelledby</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-level_on_heading-manual.html b/core-aam/aria-level_on_heading-manual.html
index 950c406..92afe8e 100644
--- a/core-aam/aria-level_on_heading-manual.html
+++ b/core-aam/aria-level_on_heading-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-level on heading</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-level_on_non-heading-manual.html b/core-aam/aria-level_on_non-heading-manual.html
index 2698040..0dbcf0e 100644
--- a/core-aam/aria-level_on_non-heading-manual.html
+++ b/core-aam/aria-level_on_non-heading-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-level on non-heading</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-live_assertive-manual.html b/core-aam/aria-live_assertive-manual.html
index 3e6ebae..1f46482 100644
--- a/core-aam/aria-live_assertive-manual.html
+++ b/core-aam/aria-live_assertive-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-live=assertive</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-live_off-manual.html b/core-aam/aria-live_off-manual.html
index 958ecad..6badb3f 100644
--- a/core-aam/aria-live_off-manual.html
+++ b/core-aam/aria-live_off-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-live=off</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-live_polite-manual.html b/core-aam/aria-live_polite-manual.html
index 95d672b..77d2563 100644
--- a/core-aam/aria-live_polite-manual.html
+++ b/core-aam/aria-live_polite-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-live=polite</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-modal_false_new-manual.html b/core-aam/aria-modal_false_new-manual.html
index f32b5c1..b1e4817 100644
--- a/core-aam/aria-modal_false_new-manual.html
+++ b/core-aam/aria-modal_false_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-modal=false NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-modal_true_new-manual.html b/core-aam/aria-modal_true_new-manual.html
index 5eb8e89..39ff6a6 100644
--- a/core-aam/aria-modal_true_new-manual.html
+++ b/core-aam/aria-modal_true_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-modal=true NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-multiline_false-manual.html b/core-aam/aria-multiline_false-manual.html
index cf4750e..32f384c 100644
--- a/core-aam/aria-multiline_false-manual.html
+++ b/core-aam/aria-multiline_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-multiline=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-multiline_true-manual.html b/core-aam/aria-multiline_true-manual.html
index d3af0a3..92a5a3f 100644
--- a/core-aam/aria-multiline_true-manual.html
+++ b/core-aam/aria-multiline_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-multiline=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-multiselectable_false-manual.html b/core-aam/aria-multiselectable_false-manual.html
index d8ef9a3..724ea0f 100644
--- a/core-aam/aria-multiselectable_false-manual.html
+++ b/core-aam/aria-multiselectable_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-multiselectable=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-multiselectable_true-manual.html b/core-aam/aria-multiselectable_true-manual.html
index a87cf52..75dbe2b 100644
--- a/core-aam/aria-multiselectable_true-manual.html
+++ b/core-aam/aria-multiselectable_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-multiselectable=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-orientation_horizontal-manual.html b/core-aam/aria-orientation_horizontal-manual.html
index e12292d..903b4a5 100644
--- a/core-aam/aria-orientation_horizontal-manual.html
+++ b/core-aam/aria-orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-orientation=horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-orientation_undefined_new-manual.html b/core-aam/aria-orientation_undefined_new-manual.html
index 2fa22c9..63321cf 100644
--- a/core-aam/aria-orientation_undefined_new-manual.html
+++ b/core-aam/aria-orientation_undefined_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-orientation=undefined NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-orientation_vertical-manual.html b/core-aam/aria-orientation_vertical-manual.html
index bf6f41d..53a4e49 100644
--- a/core-aam/aria-orientation_vertical-manual.html
+++ b/core-aam/aria-orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-orientation=vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-owns_may_need_manual_verification-manual.html b/core-aam/aria-owns_may_need_manual_verification-manual.html
index 9510432..3a02f4a 100644
--- a/core-aam/aria-owns_may_need_manual_verification-manual.html
+++ b/core-aam/aria-owns_may_need_manual_verification-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-owns may need manual verification</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -44,15 +43,9 @@
             "UIA" : [
                [
                   "property",
-                  "TBD",
+                  "Children",
                   "is",
-                  "owned1"
-               ],
-               [
-                  "property",
-                  "TBD",
-                  "is",
-                  "owned2"
+                  "[owned1, owned2]"
                ]
             ]
          },
diff --git a/core-aam/aria-placeholder_new-manual.html b/core-aam/aria-placeholder_new-manual.html
index 69f8cf9..253eed0 100644
--- a/core-aam/aria-placeholder_new-manual.html
+++ b/core-aam/aria-placeholder_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-placeholder NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-posinset-manual.html b/core-aam/aria-posinset-manual.html
index 14d46b0..d72b97f 100644
--- a/core-aam/aria-posinset-manual.html
+++ b/core-aam/aria-posinset-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-posinset</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-pressed_false-manual.html b/core-aam/aria-pressed_false-manual.html
index 4d2c9dc..3087897 100644
--- a/core-aam/aria-pressed_false-manual.html
+++ b/core-aam/aria-pressed_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-pressed=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-pressed_mixed-manual.html b/core-aam/aria-pressed_mixed-manual.html
index 09ed8c8..1bc5083 100644
--- a/core-aam/aria-pressed_mixed-manual.html
+++ b/core-aam/aria-pressed_mixed-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-pressed=mixed</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-pressed_true-manual.html b/core-aam/aria-pressed_true-manual.html
index 3345977..532c7f3 100644
--- a/core-aam/aria-pressed_true-manual.html
+++ b/core-aam/aria-pressed_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-pressed=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-pressed_value_changes-manual.html b/core-aam/aria-pressed_value_changes-manual.html
index e8c88b5..82f12b9 100644
--- a/core-aam/aria-pressed_value_changes-manual.html
+++ b/core-aam/aria-pressed_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-pressed value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-readonly_false-manual.html b/core-aam/aria-readonly_false-manual.html
index 44675b6..7be1a70 100644
--- a/core-aam/aria-readonly_false-manual.html
+++ b/core-aam/aria-readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-readonly=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-readonly_is_unspecified_on_gridcell_new-manual.html b/core-aam/aria-readonly_is_unspecified_on_gridcell_new-manual.html
index 721a664..fafa8ab 100644
--- a/core-aam/aria-readonly_is_unspecified_on_gridcell_new-manual.html
+++ b/core-aam/aria-readonly_is_unspecified_on_gridcell_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-readonly is unspecified on gridcell NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-readonly_true_on_checkbox-manual.html b/core-aam/aria-readonly_true_on_checkbox-manual.html
index 8b01a20..d5999e1 100644
--- a/core-aam/aria-readonly_true_on_checkbox-manual.html
+++ b/core-aam/aria-readonly_true_on_checkbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-readonly=true on checkbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-readonly_true_on_radiogroup-manual.html b/core-aam/aria-readonly_true_on_radiogroup-manual.html
index bb4fbc4..ef97697 100644
--- a/core-aam/aria-readonly_true_on_radiogroup-manual.html
+++ b/core-aam/aria-readonly_true_on_radiogroup-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-readonly=true on radiogroup</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-readonly_true_on_textbox-manual.html b/core-aam/aria-readonly_true_on_textbox-manual.html
index 3b96631..0dabdd8 100644
--- a/core-aam/aria-readonly_true_on_textbox-manual.html
+++ b/core-aam/aria-readonly_true_on_textbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-readonly=true on textbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-readonly_value_changes-manual.html b/core-aam/aria-readonly_value_changes-manual.html
index d9cac76..8296d59 100644
--- a/core-aam/aria-readonly_value_changes-manual.html
+++ b/core-aam/aria-readonly_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-readonly value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-relevant-manual.html b/core-aam/aria-relevant-manual.html
index be0d292..c9ad1ac 100644
--- a/core-aam/aria-relevant-manual.html
+++ b/core-aam/aria-relevant-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-relevant</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-required_true-manual.html b/core-aam/aria-required_true-manual.html
index 052395b..6b44ba4 100644
--- a/core-aam/aria-required_true-manual.html
+++ b/core-aam/aria-required_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-required=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-required_value_changes-manual.html b/core-aam/aria-required_value_changes-manual.html
index c38eb91..588b6cf 100644
--- a/core-aam/aria-required_value_changes-manual.html
+++ b/core-aam/aria-required_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-required value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-roledescription_is_empty_or_whitespace_characters_new-manual.html b/core-aam/aria-roledescription_is_empty_or_whitespace_characters_new-manual.html
index 0703399..a146dff 100644
--- a/core-aam/aria-roledescription_is_empty_or_whitespace_characters_new-manual.html
+++ b/core-aam/aria-roledescription_is_empty_or_whitespace_characters_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-roledescription is empty or whitespace characters NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -28,7 +27,7 @@
             "UIA" : [
                [
                   "property",
-                  "Localized Control Type",
+                  "Localized ControlType",
                   "is",
                   "Group"
                ]
diff --git a/core-aam/aria-roledescription_new-manual.html b/core-aam/aria-roledescription_new-manual.html
index b137f4e..48fc75e 100644
--- a/core-aam/aria-roledescription_new-manual.html
+++ b/core-aam/aria-roledescription_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-roledescription NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-rowcount_new-manual.html b/core-aam/aria-rowcount_new-manual.html
index 12ab12a..8be77d6 100644
--- a/core-aam/aria-rowcount_new-manual.html
+++ b/core-aam/aria-rowcount_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-rowcount NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-rowindex_new-manual.html b/core-aam/aria-rowindex_new-manual.html
index f3310e9..c89a5f7 100644
--- a/core-aam/aria-rowindex_new-manual.html
+++ b/core-aam/aria-rowindex_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-rowindex NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-rowspan_new-manual.html b/core-aam/aria-rowspan_new-manual.html
index 199cccd..65b73a0 100644
--- a/core-aam/aria-rowspan_new-manual.html
+++ b/core-aam/aria-rowspan_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-rowspan NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-selected_false-manual.html b/core-aam/aria-selected_false-manual.html
index 1d8d88f..b948ace 100644
--- a/core-aam/aria-selected_false-manual.html
+++ b/core-aam/aria-selected_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-selected=false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-selected_true-manual.html b/core-aam/aria-selected_true-manual.html
index 64c609a..a40ba05 100644
--- a/core-aam/aria-selected_true-manual.html
+++ b/core-aam/aria-selected_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-selected=true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-setsize_-1_new-manual.html b/core-aam/aria-setsize_-1_new-manual.html
index 35e7d85..aed5659 100644
--- a/core-aam/aria-setsize_-1_new-manual.html
+++ b/core-aam/aria-setsize_-1_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-setsize=-1 NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-setsize_3-manual.html b/core-aam/aria-setsize_3-manual.html
index 5473072..643821b 100644
--- a/core-aam/aria-setsize_3-manual.html
+++ b/core-aam/aria-setsize_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-setsize=3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-sort_ascending-manual.html b/core-aam/aria-sort_ascending-manual.html
index 9d13def..7194ba0 100644
--- a/core-aam/aria-sort_ascending-manual.html
+++ b/core-aam/aria-sort_ascending-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-sort=ascending</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-sort_descending-manual.html b/core-aam/aria-sort_descending-manual.html
index 423dc9b..8bf419f 100644
--- a/core-aam/aria-sort_descending-manual.html
+++ b/core-aam/aria-sort_descending-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-sort=descending</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-sort_none-manual.html b/core-aam/aria-sort_none-manual.html
index cbc0c87..7969c8a 100644
--- a/core-aam/aria-sort_none-manual.html
+++ b/core-aam/aria-sort_none-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-sort=none</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-sort_other-manual.html b/core-aam/aria-sort_other-manual.html
index bd7d092..81dc355 100644
--- a/core-aam/aria-sort_other-manual.html
+++ b/core-aam/aria-sort_other-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-sort=other</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-valuemax-manual.html b/core-aam/aria-valuemax-manual.html
index f92332c..3369b57 100644
--- a/core-aam/aria-valuemax-manual.html
+++ b/core-aam/aria-valuemax-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-valuemax</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-valuemin-manual.html b/core-aam/aria-valuemin-manual.html
index 2f5fd07..4f92726 100644
--- a/core-aam/aria-valuemin-manual.html
+++ b/core-aam/aria-valuemin-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-valuemin</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-valuenow-manual.html b/core-aam/aria-valuenow-manual.html
index 5aa9b94..183feaf 100644
--- a/core-aam/aria-valuenow-manual.html
+++ b/core-aam/aria-valuenow-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-valuenow</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-valuenow_value_changes-manual.html b/core-aam/aria-valuenow_value_changes-manual.html
index ad3d186..36f9ec5 100644
--- a/core-aam/aria-valuenow_value_changes-manual.html
+++ b/core-aam/aria-valuenow_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-valuenow value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -53,7 +52,7 @@
                   "event",
                   "type",
                   "is",
-                  "TBD"
+                  "ValuePattern.ValueProperty"
                ]
             ]
          },
diff --git a/core-aam/aria-valuetext-manual.html b/core-aam/aria-valuetext-manual.html
index 031fc60..364a9fb 100644
--- a/core-aam/aria-valuetext-manual.html
+++ b/core-aam/aria-valuetext-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-valuetext</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/aria-valuetext_value_changes-manual.html b/core-aam/aria-valuetext_value_changes-manual.html
index bf60171..5362ee4 100644
--- a/core-aam/aria-valuetext_value_changes-manual.html
+++ b/core-aam/aria-valuetext_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-valuetext value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/article-manual.html b/core-aam/article-manual.html
index f60a7bd..413f7ca 100644
--- a/core-aam/article-manual.html
+++ b/core-aam/article-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>article</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/banner-manual.html b/core-aam/banner-manual.html
index 4c8ad33..634ac11 100644
--- a/core-aam/banner-manual.html
+++ b/core-aam/banner-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>banner</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/button_with_aria-haspopup_dialog_new-manual.html b/core-aam/button_with_aria-haspopup_dialog_new-manual.html
index 7ef0a2e..f68e351 100644
--- a/core-aam/button_with_aria-haspopup_dialog_new-manual.html
+++ b/core-aam/button_with_aria-haspopup_dialog_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button with aria-haspopup="dialog" NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/button_with_aria-haspopup_true-manual.html b/core-aam/button_with_aria-haspopup_true-manual.html
index 682179a..c21d0e9 100644
--- a/core-aam/button_with_aria-haspopup_true-manual.html
+++ b/core-aam/button_with_aria-haspopup_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button with aria-haspopup="true"</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/button_with_default_values_for_aria-pressed_and_aria-haspopup-manual.html b/core-aam/button_with_default_values_for_aria-pressed_and_aria-haspopup-manual.html
index b36bb46..fe9ca7b 100644
--- a/core-aam/button_with_default_values_for_aria-pressed_and_aria-haspopup-manual.html
+++ b/core-aam/button_with_default_values_for_aria-pressed_and_aria-haspopup-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button with default values for aria-pressed and aria-haspopup</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/button_with_defined_value_for_aria-pressed-manual.html b/core-aam/button_with_defined_value_for_aria-pressed-manual.html
index 20c9287..6088fcb 100644
--- a/core-aam/button_with_defined_value_for_aria-pressed-manual.html
+++ b/core-aam/button_with_defined_value_for_aria-pressed-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button with defined value for aria-pressed</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/cell_new-manual.html b/core-aam/cell_new-manual.html
index c6992e8..8ff93f1 100644
--- a/core-aam/cell_new-manual.html
+++ b/core-aam/cell_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/checkbox-manual.html b/core-aam/checkbox-manual.html
index f92ab48..e9db3f3 100644
--- a/core-aam/checkbox-manual.html
+++ b/core-aam/checkbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>checkbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/columnheader-manual.html b/core-aam/columnheader-manual.html
index 017ebe9..f4ce817 100644
--- a/core-aam/columnheader-manual.html
+++ b/core-aam/columnheader-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/combobox-manual.html b/core-aam/combobox-manual.html
index 9d5b08c..44a1124 100644
--- a/core-aam/combobox-manual.html
+++ b/core-aam/combobox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/complementary-manual.html b/core-aam/complementary-manual.html
index a7e79c2..db9b8ac 100644
--- a/core-aam/complementary-manual.html
+++ b/core-aam/complementary-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>complementary</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/contentinfo-manual.html b/core-aam/contentinfo-manual.html
index 6288c8f..50b5269 100644
--- a/core-aam/contentinfo-manual.html
+++ b/core-aam/contentinfo-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>contentinfo</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/definition-manual.html b/core-aam/definition-manual.html
index 5564542..4997caa 100644
--- a/core-aam/definition-manual.html
+++ b/core-aam/definition-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>definition</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/dialog-manual.html b/core-aam/dialog-manual.html
index f7aa26c..24dd180 100644
--- a/core-aam/dialog-manual.html
+++ b/core-aam/dialog-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>dialog</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/directory-manual.html b/core-aam/directory-manual.html
index 1dcc0ed..6c9b46b 100644
--- a/core-aam/directory-manual.html
+++ b/core-aam/directory-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>directory</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/document-manual.html b/core-aam/document-manual.html
index 6c37547..5b9c275 100644
--- a/core-aam/document-manual.html
+++ b/core-aam/document-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>document</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_element_hidden_with_css_display_none-manual.html b/core-aam/exclude_element_hidden_with_css_display_none-manual.html
index 2c6a355..5cae26e 100644
--- a/core-aam/exclude_element_hidden_with_css_display_none-manual.html
+++ b/core-aam/exclude_element_hidden_with_css_display_none-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude element hidden with CSS display:none</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_element_hidden_with_css_visibility_hidden-manual.html b/core-aam/exclude_element_hidden_with_css_visibility_hidden-manual.html
index 0ebf066..4bfa6cf 100644
--- a/core-aam/exclude_element_hidden_with_css_visibility_hidden-manual.html
+++ b/core-aam/exclude_element_hidden_with_css_visibility_hidden-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude element hidden with CSS visibility:hidden</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_element_hidden_with_html5_hidden-manual.html b/core-aam/exclude_element_hidden_with_html5_hidden-manual.html
index 6d37231..c339471 100644
--- a/core-aam/exclude_element_hidden_with_html5_hidden-manual.html
+++ b/core-aam/exclude_element_hidden_with_html5_hidden-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude element hidden with HTML5 hidden</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_button-manual.html b/core-aam/exclude_presentational_children_of_button-manual.html
index 27c218e..63ea334 100644
--- a/core-aam/exclude_presentational_children_of_button-manual.html
+++ b/core-aam/exclude_presentational_children_of_button-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of button</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_checkbox_new-manual.html b/core-aam/exclude_presentational_children_of_checkbox_new-manual.html
index 0bea0e6..83997f4 100644
--- a/core-aam/exclude_presentational_children_of_checkbox_new-manual.html
+++ b/core-aam/exclude_presentational_children_of_checkbox_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of checkbox NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_img-manual.html b/core-aam/exclude_presentational_children_of_img-manual.html
index 24c9582..85c19ad 100644
--- a/core-aam/exclude_presentational_children_of_img-manual.html
+++ b/core-aam/exclude_presentational_children_of_img-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of img</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_math-manual.html b/core-aam/exclude_presentational_children_of_math-manual.html
index e2b86bc..cf93519 100644
--- a/core-aam/exclude_presentational_children_of_math-manual.html
+++ b/core-aam/exclude_presentational_children_of_math-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of math</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_menuitemcheckbox_new-manual.html b/core-aam/exclude_presentational_children_of_menuitemcheckbox_new-manual.html
index 3c69d07..e43f614 100644
--- a/core-aam/exclude_presentational_children_of_menuitemcheckbox_new-manual.html
+++ b/core-aam/exclude_presentational_children_of_menuitemcheckbox_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of menuitemcheckbox NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_menuitemradio_new-manual.html b/core-aam/exclude_presentational_children_of_menuitemradio_new-manual.html
index 78b408c..083af60 100644
--- a/core-aam/exclude_presentational_children_of_menuitemradio_new-manual.html
+++ b/core-aam/exclude_presentational_children_of_menuitemradio_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of menuitemradio NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_option_new-manual.html b/core-aam/exclude_presentational_children_of_option_new-manual.html
index e6aafc7..f880add 100644
--- a/core-aam/exclude_presentational_children_of_option_new-manual.html
+++ b/core-aam/exclude_presentational_children_of_option_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of option NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_progressbar-manual.html b/core-aam/exclude_presentational_children_of_progressbar-manual.html
index 553d0cf..02ade36 100644
--- a/core-aam/exclude_presentational_children_of_progressbar-manual.html
+++ b/core-aam/exclude_presentational_children_of_progressbar-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of progressbar</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_radio_new-manual.html b/core-aam/exclude_presentational_children_of_radio_new-manual.html
index 856b1e8..05e4086 100644
--- a/core-aam/exclude_presentational_children_of_radio_new-manual.html
+++ b/core-aam/exclude_presentational_children_of_radio_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of radio NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_scrollbar-manual.html b/core-aam/exclude_presentational_children_of_scrollbar-manual.html
index 71d5ca5..e6d2b22 100644
--- a/core-aam/exclude_presentational_children_of_scrollbar-manual.html
+++ b/core-aam/exclude_presentational_children_of_scrollbar-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of scrollbar</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_separator-manual.html b/core-aam/exclude_presentational_children_of_separator-manual.html
index a5ce334..d1c924e 100644
--- a/core-aam/exclude_presentational_children_of_separator-manual.html
+++ b/core-aam/exclude_presentational_children_of_separator-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of separator</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_slider-manual.html b/core-aam/exclude_presentational_children_of_slider-manual.html
index 5963765..2c85f9d 100644
--- a/core-aam/exclude_presentational_children_of_slider-manual.html
+++ b/core-aam/exclude_presentational_children_of_slider-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of slider</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_switch_new-manual.html b/core-aam/exclude_presentational_children_of_switch_new-manual.html
index 89db6be..489519b 100644
--- a/core-aam/exclude_presentational_children_of_switch_new-manual.html
+++ b/core-aam/exclude_presentational_children_of_switch_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of switch NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/exclude_presentational_children_of_tab_new-manual.html b/core-aam/exclude_presentational_children_of_tab_new-manual.html
index 5424288..0ec68c6 100644
--- a/core-aam/exclude_presentational_children_of_tab_new-manual.html
+++ b/core-aam/exclude_presentational_children_of_tab_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Exclude presentational children of tab NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/feed_new-manual.html b/core-aam/feed_new-manual.html
index 71f8c90..f00a737 100644
--- a/core-aam/feed_new-manual.html
+++ b/core-aam/feed_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>feed NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/figure_new-manual.html b/core-aam/figure_new-manual.html
index c2bbd39..ce3dfef 100644
--- a/core-aam/figure_new-manual.html
+++ b/core-aam/figure_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>figure NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/form-manual.html b/core-aam/form-manual.html
index 0664713..64969c7 100644
--- a/core-aam/form-manual.html
+++ b/core-aam/form-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>form</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/grid-manual.html b/core-aam/grid-manual.html
index 829f3d8..8754bb2 100644
--- a/core-aam/grid-manual.html
+++ b/core-aam/grid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/gridcell-manual.html b/core-aam/gridcell-manual.html
index c698032..ae147a9 100644
--- a/core-aam/gridcell-manual.html
+++ b/core-aam/gridcell-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>gridcell</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/group-manual.html b/core-aam/group-manual.html
index da82517..06e2e58 100644
--- a/core-aam/group-manual.html
+++ b/core-aam/group-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>group</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/heading-manual.html b/core-aam/heading-manual.html
index d68bb53..894e6a0 100644
--- a/core-aam/heading-manual.html
+++ b/core-aam/heading-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>heading</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/img-manual.html b/core-aam/img-manual.html
index baa4f17..bcb8118 100644
--- a/core-aam/img-manual.html
+++ b/core-aam/img-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>img</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_referenced_by_global_aria-controls-manual.html b/core-aam/include_element_referenced_by_global_aria-controls-manual.html
index 127fcb7..7eebef4 100644
--- a/core-aam/include_element_referenced_by_global_aria-controls-manual.html
+++ b/core-aam/include_element_referenced_by_global_aria-controls-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element referenced by global aria-controls</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_referenced_by_global_aria-describedby-manual.html b/core-aam/include_element_referenced_by_global_aria-describedby-manual.html
index 9cf1862..55be378 100644
--- a/core-aam/include_element_referenced_by_global_aria-describedby-manual.html
+++ b/core-aam/include_element_referenced_by_global_aria-describedby-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element referenced by global aria-describedby</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_referenced_by_global_aria-details_new-manual.html b/core-aam/include_element_referenced_by_global_aria-details_new-manual.html
index 397ded1..ec8cd09 100644
--- a/core-aam/include_element_referenced_by_global_aria-details_new-manual.html
+++ b/core-aam/include_element_referenced_by_global_aria-details_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element referenced by global aria-details NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_referenced_by_global_aria-errormessage_new-manual.html b/core-aam/include_element_referenced_by_global_aria-errormessage_new-manual.html
index f279cdd..67d9beb 100644
--- a/core-aam/include_element_referenced_by_global_aria-errormessage_new-manual.html
+++ b/core-aam/include_element_referenced_by_global_aria-errormessage_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element referenced by global aria-errormessage NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_referenced_by_global_aria-flowto-manual.html b/core-aam/include_element_referenced_by_global_aria-flowto-manual.html
index 173d702..9847a81 100644
--- a/core-aam/include_element_referenced_by_global_aria-flowto-manual.html
+++ b/core-aam/include_element_referenced_by_global_aria-flowto-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element referenced by global aria-flowto</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_referenced_by_global_aria-labelledby-manual.html b/core-aam/include_element_referenced_by_global_aria-labelledby-manual.html
index 86a09cb..c7cdd0e 100644
--- a/core-aam/include_element_referenced_by_global_aria-labelledby-manual.html
+++ b/core-aam/include_element_referenced_by_global_aria-labelledby-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element referenced by global aria-labelledby</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_referenced_by_global_aria-owns-manual.html b/core-aam/include_element_referenced_by_global_aria-owns-manual.html
index baf2c17..540690d 100644
--- a/core-aam/include_element_referenced_by_global_aria-owns-manual.html
+++ b/core-aam/include_element_referenced_by_global_aria-owns-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element referenced by global aria-owns</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_that_is_focusable-manual.html b/core-aam/include_element_that_is_focusable-manual.html
index c3e9540..88f8c1d 100644
--- a/core-aam/include_element_that_is_focusable-manual.html
+++ b/core-aam/include_element_that_is_focusable-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element that is focusable</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/include_element_with_id_inside_element_with_aria-activedescendant-manual.html b/core-aam/include_element_with_id_inside_element_with_aria-activedescendant-manual.html
index cbae28e..fc0b654 100644
--- a/core-aam/include_element_with_id_inside_element_with_aria-activedescendant-manual.html
+++ b/core-aam/include_element_with_id_inside_element_with_aria-activedescendant-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>Include element with ID inside element with aria-activedescendant</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/link-manual.html b/core-aam/link-manual.html
index d83d0cf..a5ff107 100644
--- a/core-aam/link-manual.html
+++ b/core-aam/link-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>link</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/list-manual.html b/core-aam/list-manual.html
index 81e8b2d..aac0d20 100644
--- a/core-aam/list-manual.html
+++ b/core-aam/list-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>list</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/listbox_not_owned_by_or_child_of_combobox-manual.html b/core-aam/listbox_not_owned_by_or_child_of_combobox-manual.html
index 58b3021..8e1db61 100644
--- a/core-aam/listbox_not_owned_by_or_child_of_combobox-manual.html
+++ b/core-aam/listbox_not_owned_by_or_child_of_combobox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox not owned by or child of combobox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/listbox_owned_by_or_child_of_combobox-manual.html b/core-aam/listbox_owned_by_or_child_of_combobox-manual.html
index 62a233a..7cabaa9 100644
--- a/core-aam/listbox_owned_by_or_child_of_combobox-manual.html
+++ b/core-aam/listbox_owned_by_or_child_of_combobox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox owned by or child of combobox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/listitem-manual.html b/core-aam/listitem-manual.html
index 14dbec9..8b11eb4 100644
--- a/core-aam/listitem-manual.html
+++ b/core-aam/listitem-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listitem</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/log-manual.html b/core-aam/log-manual.html
index d997b95..a2156e2 100644
--- a/core-aam/log-manual.html
+++ b/core-aam/log-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>log</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/main-manual.html b/core-aam/main-manual.html
index 8d8e6ff..2ec25f1 100644
--- a/core-aam/main-manual.html
+++ b/core-aam/main-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>main</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/marquee-manual.html b/core-aam/marquee-manual.html
index 064c8d2..522887b 100644
--- a/core-aam/marquee-manual.html
+++ b/core-aam/marquee-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>marquee</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/math-manual.html b/core-aam/math-manual.html
index 5fc9672..f15e8f8 100644
--- a/core-aam/math-manual.html
+++ b/core-aam/math-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>math</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/menu-manual.html b/core-aam/menu-manual.html
index 0407d3a..43ec797 100644
--- a/core-aam/menu-manual.html
+++ b/core-aam/menu-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menu</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/menu_child_of_menu_item-manual.html b/core-aam/menu_child_of_menu_item-manual.html
index 723d2df..d3d37ce 100644
--- a/core-aam/menu_child_of_menu_item-manual.html
+++ b/core-aam/menu_child_of_menu_item-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menu child of menu item</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/menubar-manual.html b/core-aam/menubar-manual.html
index 88f3238..878f1da 100644
--- a/core-aam/menubar-manual.html
+++ b/core-aam/menubar-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menubar</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/menuitem_not_owned_by_or_child_of_group-manual.html b/core-aam/menuitem_not_owned_by_or_child_of_group-manual.html
index f42d81f..8f1cfbf 100644
--- a/core-aam/menuitem_not_owned_by_or_child_of_group-manual.html
+++ b/core-aam/menuitem_not_owned_by_or_child_of_group-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitem not owned by or child of group</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/menuitem_owned_by_or_child_of_group-manual.html b/core-aam/menuitem_owned_by_or_child_of_group-manual.html
index 11d216f..c5d4c41 100644
--- a/core-aam/menuitem_owned_by_or_child_of_group-manual.html
+++ b/core-aam/menuitem_owned_by_or_child_of_group-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitem owned by or child of group</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/menuitemcheckbox-manual.html b/core-aam/menuitemcheckbox-manual.html
index 1e13e5a..6aa5128 100644
--- a/core-aam/menuitemcheckbox-manual.html
+++ b/core-aam/menuitemcheckbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemcheckbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/menuitemradio-manual.html b/core-aam/menuitemradio-manual.html
index 6d08321..7fa58d6 100644
--- a/core-aam/menuitemradio-manual.html
+++ b/core-aam/menuitemradio-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemradio</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/navigation-manual.html b/core-aam/navigation-manual.html
index 2399482..1894119 100644
--- a/core-aam/navigation-manual.html
+++ b/core-aam/navigation-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>navigation</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/none_new-manual.html b/core-aam/none_new-manual.html
index deef5fb..afde9b9 100644
--- a/core-aam/none_new-manual.html
+++ b/core-aam/none_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>none NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/none_used_on_element_that_is_focused_or_fires_event-manual.html b/core-aam/none_used_on_element_that_is_focused_or_fires_event-manual.html
index 06a3b07..9b77057 100644
--- a/core-aam/none_used_on_element_that_is_focused_or_fires_event-manual.html
+++ b/core-aam/none_used_on_element_that_is_focused_or_fires_event-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>none used on element that is focused or fires event</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/none_used_on_table_element_with_td_children_new-manual.html b/core-aam/none_used_on_table_element_with_td_children_new-manual.html
index 9004348..94200b8 100644
--- a/core-aam/none_used_on_table_element_with_td_children_new-manual.html
+++ b/core-aam/none_used_on_table_element_with_td_children_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>none used on table element with td children NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/none_used_on_ul_element_with_li_children_new-manual.html b/core-aam/none_used_on_ul_element_with_li_children_new-manual.html
index c6fdaf7..ac5a04a 100644
--- a/core-aam/none_used_on_ul_element_with_li_children_new-manual.html
+++ b/core-aam/none_used_on_ul_element_with_li_children_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>none used on ul element with li children NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/note-manual.html b/core-aam/note-manual.html
index 37504d7..8959e08 100644
--- a/core-aam/note-manual.html
+++ b/core-aam/note-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>note</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/option_inside_combobox-manual.html b/core-aam/option_inside_combobox-manual.html
index e692d9f..3023051 100644
--- a/core-aam/option_inside_combobox-manual.html
+++ b/core-aam/option_inside_combobox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>option inside combobox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/option_not_inside_combobox-manual.html b/core-aam/option_not_inside_combobox-manual.html
index 15f966b..cf92054 100644
--- a/core-aam/option_not_inside_combobox-manual.html
+++ b/core-aam/option_not_inside_combobox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>option not inside combobox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/presentation-manual.html b/core-aam/presentation-manual.html
index 4abbe83..c0cfa82 100644
--- a/core-aam/presentation-manual.html
+++ b/core-aam/presentation-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>presentation</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/presentation_used_on_element_that_is_focused_or_fires_event-manual.html b/core-aam/presentation_used_on_element_that_is_focused_or_fires_event-manual.html
index f7021f8..ac8c1ba 100644
--- a/core-aam/presentation_used_on_element_that_is_focused_or_fires_event-manual.html
+++ b/core-aam/presentation_used_on_element_that_is_focused_or_fires_event-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>presentation used on element that is focused or fires event</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/presentation_used_on_table_element_with_td_children_new-manual.html b/core-aam/presentation_used_on_table_element_with_td_children_new-manual.html
index cd0b19d..0f7134d 100644
--- a/core-aam/presentation_used_on_table_element_with_td_children_new-manual.html
+++ b/core-aam/presentation_used_on_table_element_with_td_children_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>presentation used on table element with td children NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/presentation_used_on_ul_element_with_li_children_new-manual.html b/core-aam/presentation_used_on_ul_element_with_li_children_new-manual.html
index b00e183..c273571 100644
--- a/core-aam/presentation_used_on_ul_element_with_li_children_new-manual.html
+++ b/core-aam/presentation_used_on_ul_element_with_li_children_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>presentation used on ul element with li children NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/progressbar-manual.html b/core-aam/progressbar-manual.html
index a336fa0..f3b402b 100644
--- a/core-aam/progressbar-manual.html
+++ b/core-aam/progressbar-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>progressbar</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -62,7 +61,7 @@
                   "property",
                   "interfaces",
                   "contains",
-                  "IAcesssibleValue"
+                  "IAccessibleValue"
                ]
             ],
             "MSAA" : [
diff --git a/core-aam/radio-manual.html b/core-aam/radio-manual.html
index 9f191a4..3d5e660 100644
--- a/core-aam/radio-manual.html
+++ b/core-aam/radio-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radio</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/radiogroup-manual.html b/core-aam/radiogroup-manual.html
index b91cf46..f217fb1 100644
--- a/core-aam/radiogroup-manual.html
+++ b/core-aam/radiogroup-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radiogroup</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/region_with_an_accessible_name_new-manual.html b/core-aam/region_with_an_accessible_name_new-manual.html
index f09e4ff..daaa5cb 100644
--- a/core-aam/region_with_an_accessible_name_new-manual.html
+++ b/core-aam/region_with_an_accessible_name_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>region with an accessible name NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/region_without_an_accessible_name_new-manual.html b/core-aam/region_without_an_accessible_name_new-manual.html
index 05ca5e7..9a19fb8 100644
--- a/core-aam/region_without_an_accessible_name_new-manual.html
+++ b/core-aam/region_without_an_accessible_name_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>region without an accessible name NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -56,7 +55,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Group"
                ]
diff --git a/core-aam/row_inside_treegrid-manual.html b/core-aam/row_inside_treegrid-manual.html
index 8add16b..2a708c5 100644
--- a/core-aam/row_inside_treegrid-manual.html
+++ b/core-aam/row_inside_treegrid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>row inside treegrid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/row_not_inside_treegrid-manual.html b/core-aam/row_not_inside_treegrid-manual.html
index eb0cdff..c6ec4f4 100644
--- a/core-aam/row_not_inside_treegrid-manual.html
+++ b/core-aam/row_not_inside_treegrid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>row not inside treegrid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/rowgroup-manual.html b/core-aam/rowgroup-manual.html
index 556647f..83efba8 100644
--- a/core-aam/rowgroup-manual.html
+++ b/core-aam/rowgroup-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowgroup</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/rowheader-manual.html b/core-aam/rowheader-manual.html
index 324d92c..7705ead 100644
--- a/core-aam/rowheader-manual.html
+++ b/core-aam/rowheader-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowheader</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/scrollbar-manual.html b/core-aam/scrollbar-manual.html
index 3b4b5ff..9dc61e5 100644
--- a/core-aam/scrollbar-manual.html
+++ b/core-aam/scrollbar-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>scrollbar</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -56,7 +55,7 @@
                   "property",
                   "interfaces",
                   "contains",
-                  "IAcesssibleValue"
+                  "IAccessibleValue"
                ]
             ],
             "MSAA" : [
diff --git a/core-aam/search-manual.html b/core-aam/search-manual.html
index dee7fb3..067ebfd 100644
--- a/core-aam/search-manual.html
+++ b/core-aam/search-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>search</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/searchbox_new-manual.html b/core-aam/searchbox_new-manual.html
index ba59e73..34dd391 100644
--- a/core-aam/searchbox_new-manual.html
+++ b/core-aam/searchbox_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/separator_focusable_new-manual.html b/core-aam/separator_focusable_new-manual.html
index d94c06d..9afc08f 100644
--- a/core-aam/separator_focusable_new-manual.html
+++ b/core-aam/separator_focusable_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator focusable NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/separator_non-focusable-manual.html b/core-aam/separator_non-focusable-manual.html
index 78c21ae..aeee369 100644
--- a/core-aam/separator_non-focusable-manual.html
+++ b/core-aam/separator_non-focusable-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator non-focusable</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/slider-manual.html b/core-aam/slider-manual.html
index d0613e6..7adea5a 100644
--- a/core-aam/slider-manual.html
+++ b/core-aam/slider-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>slider</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -56,7 +55,7 @@
                   "property",
                   "interfaces",
                   "contains",
-                  "IAcesssibleValue"
+                  "IAccessibleValue"
                ]
             ],
             "MSAA" : [
diff --git a/core-aam/spinbutton-manual.html b/core-aam/spinbutton-manual.html
index 80f3f24..e61f4ea 100644
--- a/core-aam/spinbutton-manual.html
+++ b/core-aam/spinbutton-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>spinbutton</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -56,7 +55,7 @@
                   "property",
                   "interfaces",
                   "contains",
-                  "IAcesssibleValue"
+                  "IAccessibleValue"
                ]
             ],
             "MSAA" : [
diff --git a/core-aam/status-manual.html b/core-aam/status-manual.html
index 2580364..a4f5359 100644
--- a/core-aam/status-manual.html
+++ b/core-aam/status-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>status</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/switch_new-manual.html b/core-aam/switch_new-manual.html
index b7c6d33..f57f992 100644
--- a/core-aam/switch_new-manual.html
+++ b/core-aam/switch_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/tab-manual.html b/core-aam/tab-manual.html
index 7b749e8..625cced 100644
--- a/core-aam/tab-manual.html
+++ b/core-aam/tab-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tab</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/table_new-manual.html b/core-aam/table_new-manual.html
index 463c470..97bd0a8 100644
--- a/core-aam/table_new-manual.html
+++ b/core-aam/table_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>table NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/tablist-manual.html b/core-aam/tablist-manual.html
index 71c7f3f..93b8e17 100644
--- a/core-aam/tablist-manual.html
+++ b/core-aam/tablist-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tablist</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/tabpanel-manual.html b/core-aam/tabpanel-manual.html
index dd00381..6841778 100644
--- a/core-aam/tabpanel-manual.html
+++ b/core-aam/tabpanel-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tabpanel</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/term_new-manual.html b/core-aam/term_new-manual.html
index a09cc8c..d369be9 100644
--- a/core-aam/term_new-manual.html
+++ b/core-aam/term_new-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>term NEW</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/textbox_when_aria-multiline_is_false-manual.html b/core-aam/textbox_when_aria-multiline_is_false-manual.html
index 662dfab..f4c6aa1 100644
--- a/core-aam/textbox_when_aria-multiline_is_false-manual.html
+++ b/core-aam/textbox_when_aria-multiline_is_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>textbox when aria-multiline is false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/textbox_when_aria-multiline_is_true-manual.html b/core-aam/textbox_when_aria-multiline_is_true-manual.html
index 766a886..e56910e 100644
--- a/core-aam/textbox_when_aria-multiline_is_true-manual.html
+++ b/core-aam/textbox_when_aria-multiline_is_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>textbox when aria-multiline is true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/timer-manual.html b/core-aam/timer-manual.html
index bea8052..8a1f93d 100644
--- a/core-aam/timer-manual.html
+++ b/core-aam/timer-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>timer</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/toolbar-manual.html b/core-aam/toolbar-manual.html
index b0e8a46..c93e531 100644
--- a/core-aam/toolbar-manual.html
+++ b/core-aam/toolbar-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>toolbar</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/tooltip-manual.html b/core-aam/tooltip-manual.html
index 066f30d..b815d38 100644
--- a/core-aam/tooltip-manual.html
+++ b/core-aam/tooltip-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tooltip</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/tree-manual.html b/core-aam/tree-manual.html
index ba347dc..5a15b46 100644
--- a/core-aam/tree-manual.html
+++ b/core-aam/tree-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tree</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/treegrid-manual.html b/core-aam/treegrid-manual.html
index 44440b4..2d144aa 100644
--- a/core-aam/treegrid-manual.html
+++ b/core-aam/treegrid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treegrid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/core-aam/treeitem-manual.html b/core-aam/treeitem-manual.html
index 64d738b..1b16545 100644
--- a/core-aam/treeitem-manual.html
+++ b/core-aam/treeitem-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treeitem</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/cors/README.md b/cors/README.md
index b239462..aa5ff90 100644
--- a/cors/README.md
+++ b/cors/README.md
@@ -5,4 +5,4 @@
 More CORS tests can be found in
 
 * /fetch
-* /XMLHttpRequest
+* /xhr
diff --git a/cors/client-hint-request-headers.htm b/cors/client-hint-request-headers.htm
new file mode 100644
index 0000000..ee6c7ea
--- /dev/null
+++ b/cors/client-hint-request-headers.htm
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CORS - client hint request headers - Access-Control-Allow-Headers</title>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=support.js?pipe=sub></script>
+
+<h1>Request headers</h1>
+<div id=log></div>
+<script>
+
+test(function() {
+    var client = new XMLHttpRequest()
+    client.open('GET', CROSSDOMAIN + 'resources/cors-makeheader.py?headers=x-print,', false)
+    client.setRequestHeader('x-print', 'unicorn')
+    client.setRequestHeader('content-type', 'text/plain')
+    client.setRequestHeader('accept', 'test')
+    client.setRequestHeader('accept-language', 'nn')
+    client.setRequestHeader('content-language', 'nn')
+    client.setRequestHeader('save-data', 'on')
+    client.setRequestHeader('device-memory', '1.0')
+    client.setRequestHeader('dpr', '2.0')
+    client.setRequestHeader('width', '35')
+    client.setRequestHeader('viewport-width', '42')
+    client.send(null)
+
+    const res = JSON.parse(client.response)
+    assert_equals(res['x-print'], 'unicorn')
+    assert_equals(res['content-type'], 'text/plain')
+    assert_equals(res['accept'], 'test')
+    assert_equals(res['accept-language'], 'nn')
+    assert_equals(res['content-language'], 'nn')
+    assert_equals(res['save-data'], 'on')
+    assert_equals(res['device-memory'], '1.0')
+    assert_equals(res['dpr'], '2.0')
+    assert_equals(res['width'], '35')
+    assert_equals(res['viewport-width'], '42')
+}, 'Client hint headers are simple headers')
+
+test(function() {
+    var client = new XMLHttpRequest()
+    client.open('GET', CROSSDOMAIN + 'resources/cors-makeheader.py?headers=x-print', false)
+    client.setRequestHeader('x-print', 'unicorn')
+    client.setRequestHeader('y-print', 'unicorn')
+    assert_throws("NetworkError", function() { client.send(null) })
+}, 'Unspecified request headers are disallowed')
+
+test(function() {
+    var client = new XMLHttpRequest()
+    client.open('GET', CROSSDOMAIN + 'resources/cors-makeheader.py?headers=x-print', false)
+    client.setRequestHeader('device-memory', '')
+    assert_throws("NetworkError", function() { client.send(null) })
+}, 'Unextractable device-memory client hint header is disallowed')
+
+test(function() {
+    var client = new XMLHttpRequest()
+    client.open('GET', CROSSDOMAIN + 'resources/cors-makeheader.py?headers=x-print', false)
+    client.setRequestHeader('dpr', '')
+    assert_throws("NetworkError", function() { client.send(null) })
+}, 'Unextractable DPR client hint header is disallowed')
+
+test(function() {
+    var client = new XMLHttpRequest()
+    client.open('GET', CROSSDOMAIN + 'resources/cors-makeheader.py?headers=x-print', false)
+    client.setRequestHeader('width', '')
+    assert_throws("NetworkError", function() { client.send(null) })
+}, 'Unextractable width client hint header is disallowed')
+
+test(function() {
+    var client = new XMLHttpRequest()
+    client.open('GET', CROSSDOMAIN + 'resources/cors-makeheader.py?headers=x-print', false)
+    client.setRequestHeader('viewport-width', '')
+    assert_throws("NetworkError", function() { client.send(null) })
+}, 'Unextractable viewport-width client hint header is disallowed')
+
+</script>
diff --git a/cors/image-tainting-in-cross-origin-iframe.sub.html b/cors/image-tainting-in-cross-origin-iframe.sub.html
new file mode 100644
index 0000000..0cd8775
--- /dev/null
+++ b/cors/image-tainting-in-cross-origin-iframe.sub.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+</body>
+<script>
+async_test(t => {
+  const img = document.createElement('img');
+  img.onload = t.step_func(() => {
+    const iframe = document.createElement('iframe');
+    window.onmessage = t.step_func_done(e => {
+      assert_equals(e.data, 'DONE');
+    });
+    iframe.src = 'http://{{domains[www1]}}:{{ports[http][0]}}/cors/resources/image-tainting-checker.sub.html';
+    document.body.appendChild(iframe);
+  });
+  img.src = '/images/blue-png-cachable.py';
+  document.body.appendChild(img);
+}, 'An image resource that is same-origin to the top-level frame loaded in ' +
+  'the frame is not treated as same-origin for an iframe that is ' +
+  'cross-origin to the top-level frame, and therefore a canvas where the ' +
+  'image is drawn gets tainted.');
+</script>
diff --git a/cors/resources/image-tainting-checker.sub.html b/cors/resources/image-tainting-checker.sub.html
new file mode 100644
index 0000000..59de9e7
--- /dev/null
+++ b/cors/resources/image-tainting-checker.sub.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<body>
+<canvas id="canvas"></canvas>
+<script>
+// Used by image-tainting-in-cross-origin-iframe.sub.html to check that an
+// image resource loaded by the top level frame that is same-origin to the
+// frame isn't treated as a same-origin resource in a cross-origin iframe.
+const canvas = document.getElementById('canvas');
+const ctx = canvas.getContext('2d');
+const img = new Image();
+img.src = 'http://{{host}}:{{ports[http][0]}}/images/blue-png-cachable.py';
+img.onload = () => {
+  ctx.drawImage(img, 0, 0);
+  try {
+    ctx.getImageData(0, 0, 1, 1);
+    parent.postMessage('FAIL: getImageData() didn\'t throw', '*');
+  } catch (e) {
+    parent.postMessage('DONE', '*');
+  }
+};
+</script>
+</body>
diff --git a/cors/resources/status.py b/cors/resources/status.py
index 8d441f0..d223783 100644
--- a/cors/resources/status.py
+++ b/cors/resources/status.py
@@ -24,7 +24,7 @@
     if request.method == "OPTIONS" and "preflight" in request.GET:
         try:
             code = int(request.GET.first('preflight'))
-        except KeyError, ValueError:
+        except KeyError:
             pass
 
     status = code, text
diff --git a/cors/simple-requests.htm b/cors/simple-requests.htm
index 441a8c1..77ed8ee 100644
--- a/cors/simple-requests.htm
+++ b/cors/simple-requests.htm
@@ -61,10 +61,17 @@
                         'content-type': 'text/plain; parameter=whatever'
                      })
 
+check_simple_headers({
+                        'save-data': 'on',
+                        'device-memory': '2.0',
+                        'dpr': '3.0',
+                        'width': '1200',
+                        'viewport-width': '1300'
+                     })
+
 check_simple('Get', {'content-type': 'text/plain; parameter=extra_bonus'})
 check_simple('post', {'content-type': 'text/plain'})
 
-
 /* Extra async test */
 
 var simple_async = async_test("Check simple headers (async)")
diff --git a/credential-management/OWNERS b/credential-management/OWNERS
new file mode 100644
index 0000000..12f907c
--- /dev/null
+++ b/credential-management/OWNERS
@@ -0,0 +1 @@
+@mikewest
diff --git a/credential-management/require_securecontext.html b/credential-management/require_securecontext.html
new file mode 100644
index 0000000..b1f3103
--- /dev/null
+++ b/credential-management/require_securecontext.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that Credential Management requires secure contexts</title>
+<link rel="help" href="https://w3c.github.io/webappsec-credential-management/#idl-index">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+"use strict";
+  test(() => {
+    assert_false(isSecureContext);
+    assert_false('credentials' in navigator);
+  }, "Credential Management must not be accessible in insecure contexts");
+</script>
diff --git a/css/.gitignore b/css/.gitignore
index 20072eb..3ed165f 100644
--- a/css/.gitignore
+++ b/css/.gitignore
@@ -2,6 +2,7 @@
 dist_last
 build-temp
 tools/cache
+tools/_virtualenv
 *.xcodeproj
 *.DS_Store
 *.pyc
diff --git a/css/CSS2/abspos/table-caption-is-containing-block-001.html b/css/CSS2/abspos/table-caption-is-containing-block-001.html
new file mode 100644
index 0000000..32ac681
--- /dev/null
+++ b/css/CSS2/abspos/table-caption-is-containing-block-001.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/tables.html#model">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="flags" content="" />
+<meta name="assert" content="relpos caption is containing block for abspos child" />
+<title>
+Captions and abspos descendants
+</title>
+
+<style>
+caption, div {
+  height:100px;
+  width:50px;
+  background:green;
+}
+#redSquare {
+  height: 100px;
+  width: 100px;
+  background-color: red;
+  position: absolute;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="redSquare"></div>
+
+<table>
+  <caption style="position:relative">
+    <div style="position:absolute; left:50px;">
+    </div>
+  </caption>
+</table>
diff --git a/css/CSS2/abspos/table-caption-passes-abspos-up-001.html b/css/CSS2/abspos/table-caption-passes-abspos-up-001.html
new file mode 100644
index 0000000..51b0d69
--- /dev/null
+++ b/css/CSS2/abspos/table-caption-passes-abspos-up-001.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/tables.html#model">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="flags" content="" />
+<meta name="assert" content="non-containing block caption passes abspos child to its parent" />
+<title>
+Captions and abspos descendants
+</title>
+
+<style>
+caption, div {
+  height:100px;
+  width:50px;
+  background:green;
+}
+#redSquare {
+  height: 100px;
+  width: 100px;
+  background-color: red;
+  position: absolute;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="redSquare"></div>
+
+<table style="position:relative">
+  <caption>
+    <div style="position:absolute; left:50px;">
+    </div>
+  </caption>
+</table>
diff --git a/css/CSS2/backgrounds/background-attachment-004.xht b/css/CSS2/backgrounds/background-attachment-004.xht
index 9bf2fd0..d738fc8 100644
--- a/css/CSS2/backgrounds/background-attachment-004.xht
+++ b/css/CSS2/backgrounds/background-attachment-004.xht
@@ -8,8 +8,8 @@
   <meta name="flags" content="interact"/>
   <style type="text/css">
     * { background: transparent; }
-    body { background: transparent url(http://www.hixie.ch/resources/images/astrophy/128) no-repeat fixed center; }
-    .a   { background: transparent url(http://www.hixie.ch/resources/images/astrophy/830) no-repeat fixed center; height: 100px; }
+    body { background: transparent url(support/cat-128.png) no-repeat fixed center; }
+    .a   { background: transparent url(support/cat-830.png) no-repeat fixed center; height: 100px; }
     pre { font: 100 24px/2 Arial, sans-serif; letter-spacing: 1em; word-spacing: 1.2em; margin: 1em; }
   </style>
  </head>
diff --git a/css/CSS2/backgrounds/background-color-030.xht b/css/CSS2/backgrounds/background-color-030.xht
index 767cb1f..050ad81 100644
--- a/css/CSS2/backgrounds/background-color-030.xht
+++ b/css/CSS2/backgrounds/background-color-030.xht
@@ -13,8 +13,8 @@
         <style type="text/css">
             div
             {
-                height: 1in;
-                width: 1in;
+                height: 100px;
+                width: 100px;
             }
             #wrapper
             {
diff --git a/css/CSS2/backgrounds/background-color-174.xht b/css/CSS2/backgrounds/background-color-174.xht
index 8dcd75e..2f8c892 100644
--- a/css/CSS2/backgrounds/background-color-174.xht
+++ b/css/CSS2/backgrounds/background-color-174.xht
@@ -13,8 +13,8 @@
         <style type="text/css">
             div
             {
-                height: 1in;
-                width: 1in;
+                height: 100px;
+                width: 100px;
             }
             #wrapper
             {
diff --git a/css/CSS2/backgrounds/background-image-002.xht b/css/CSS2/backgrounds/background-image-002.xht
index 89d6cb2..285d7ef 100644
--- a/css/CSS2/backgrounds/background-image-002.xht
+++ b/css/CSS2/backgrounds/background-image-002.xht
@@ -14,8 +14,8 @@
             div
             {
                 background-image: url("support/green_box.png");
-                height: 1in;
-                width: 1in;
+                height: 100px;
+                width: 100px;
             }
         </style>
     </head>
diff --git a/css/CSS2/backgrounds/support/cat-128.png b/css/CSS2/backgrounds/support/cat-128.png
new file mode 100644
index 0000000..a7b61bc
--- /dev/null
+++ b/css/CSS2/backgrounds/support/cat-128.png
Binary files differ
diff --git a/css/CSS2/backgrounds/support/cat-830.png b/css/CSS2/backgrounds/support/cat-830.png
new file mode 100644
index 0000000..34c4f5b
--- /dev/null
+++ b/css/CSS2/backgrounds/support/cat-830.png
Binary files differ
diff --git a/css/CSS2/cascade-import/cascade-import-002.xht b/css/CSS2/cascade-import/cascade-import-002.xht
index 6a2c70f..14e7156 100644
--- a/css/CSS2/cascade-import/cascade-import-002.xht
+++ b/css/CSS2/cascade-import/cascade-import-002.xht
@@ -15,7 +15,6 @@
   <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#cascading"/>
   <meta name="flags" content="http" />
 
-  <meta http-equiv="Link" content='&lt;support/cascade-import-002h.css>; rel="stylesheet"'/>
   <link rel="stylesheet" href="support/cascade-import-002j.css"/>
   <style type="text/css">
    @import url(support/cascade-import-002k.css);
@@ -28,7 +27,6 @@
    .r { color: red; }
   </style>
   <link rel="stylesheet" href="support/cascade-import-002p.css"/>
-  <meta http-equiv="Link" content='&lt;support/cascade-import-002r.css>; rel="stylesheet"'/>
  </head>
  <body>
   <div class="a"> This line should be green. (a) </div>
@@ -37,8 +35,6 @@
   <div class="d"> This line should be green. (d) </div>
   <div class="e"> This line should be green. (e) </div>
   <div class="f"> This line should be green. (f) </div>
-  <div class="g"> This line should be green. (g) </div>
-  <div class="h"> This line should be green. (h) </div>
   <div class="i"> This line should be green. (i) </div>
   <div class="j"> This line should be green. (j) </div>
   <div class="k"> This line should be green. (k) </div>
@@ -47,8 +43,6 @@
   <div class="n"> This line should be green. (n) </div>
   <div class="o"> This line should be green. (o) </div>
   <div class="p"> This line should be green. (p) </div>
-  <div class="q"> This line should be green. (q) </div>
-  <div class="r"> This line should be green. (r) </div>
   <!--
    The cascade is as follows:
       HTTP Link: header
@@ -63,10 +57,6 @@
        @import
    e    Rule
    f   Rule
-      <meta http-equiv="Link">
-       @import
-   g    Rule
-   h   Rule
       <link rel="stylesheet">
        @import
    i    Rule
@@ -84,10 +74,6 @@
    n      Rule
    o    Rule
    p   Rule
-      <meta http-equiv="Link">
-       @import
-   q    Rule
-   r   Rule
   -->
 
 
diff --git a/css/CSS2/cascade-import/support/cascade-import-002g.css b/css/CSS2/cascade-import/support/cascade-import-002g.css
deleted file mode 100644
index 3f8203e..0000000
--- a/css/CSS2/cascade-import/support/cascade-import-002g.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.g { color: green; }
-.h { color: red; }
-.i { color: red; }
-.j { color: red; }
-.k { color: red; }
-.l { color: red; }
-.m { color: red; }
-.n { color: red; }
-.o { color: red; }
-.p { color: red; }
-.q { color: red; }
-.r { color: red; }
diff --git a/css/CSS2/cascade-import/support/cascade-import-002h.css b/css/CSS2/cascade-import/support/cascade-import-002h.css
deleted file mode 100644
index 7dabe99..0000000
--- a/css/CSS2/cascade-import/support/cascade-import-002h.css
+++ /dev/null
@@ -1,12 +0,0 @@
-@import url('cascade-import-002g.css');
-.h { color: green; }
-.i { color: red; }
-.j { color: red; }
-.k { color: red; }
-.l { color: red; }
-.m { color: red; }
-.n { color: red; }
-.o { color: red; }
-.p { color: red; }
-.q { color: red; }
-.r { color: red; }
diff --git a/css/CSS2/cascade-import/support/cascade-import-002q.css b/css/CSS2/cascade-import/support/cascade-import-002q.css
deleted file mode 100644
index a778423..0000000
--- a/css/CSS2/cascade-import/support/cascade-import-002q.css
+++ /dev/null
@@ -1,2 +0,0 @@
-.q { color: green; }
-.r { color: red; }
diff --git a/css/CSS2/cascade-import/support/cascade-import-002r.css b/css/CSS2/cascade-import/support/cascade-import-002r.css
deleted file mode 100644
index a3faacf..0000000
--- a/css/CSS2/cascade-import/support/cascade-import-002r.css
+++ /dev/null
@@ -1,2 +0,0 @@
-@import url(cascade-import-002q.css);
-.r { color: green; }
diff --git a/css/CSS2/colors/color-030.xht b/css/CSS2/colors/color-030.xht
deleted file mode 100644
index bec9327..0000000
--- a/css/CSS2/colors/color-030.xht
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-    <head>
-        <title>CSS Test: Color set to hex with three digits with the maximum plus one value of #1000</title>
-        <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
-        <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-04-08 -->
-        <link rel="help" href="http://www.w3.org/TR/CSS21/colors.html#propdef-color" />
-        <link rel="help" href="http://www.w3.org/TR/CSS21/colors.html#colors" />
-        <link rel="match" href="color-002-ref.xht" />
-        <meta name="flags" content="ahem invalid" />
-        <meta name="assert" content="The 'color' set to '#1000' falls back to the initial value and  renders the correct foreground color of an element's text content." />
-        <style type="text/css">
-            #test
-            {
-                color: #1000;
-                font: 100px/1 Ahem;
-                margin-bottom: 10px;
-            }
-            #reference
-            {
-                height: 100px;
-                width: 100px;
-                background-color: #000000;
-            }
-        </style>
-    </head>
-    <body>
-        <p>Test passes if there are 2 squares with the <strong>same color</strong>.</p>
-        <div id="test">X</div>
-        <div id="reference"></div>
-    </body>
-</html>
\ No newline at end of file
diff --git a/css/CSS2/colors/color-083-ref.xht b/css/CSS2/colors/color-083-ref.xht
index 65f5c6f..5417ef0 100644
--- a/css/CSS2/colors/color-083-ref.xht
+++ b/css/CSS2/colors/color-083-ref.xht
@@ -30,7 +30,6 @@
   </div>
 
   <div>
-    <br />
     <img alt="Image download support must be enabled" src="support/800000_color.png" />
   </div>
 
diff --git a/css/CSS2/floats-clear/adjoining-float-before-clearance.html b/css/CSS2/floats-clear/adjoining-float-before-clearance.html
new file mode 100644
index 0000000..13041a2
--- /dev/null
+++ b/css/CSS2/floats-clear/adjoining-float-before-clearance.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Float that would be adjoining if there were no clearance</title>
+<meta name="assert" content="If the clearance candidate would pull a float down with it (due to margin collapsing) if there were no clearance, clearance needs to be inserted to separate the two, so that the block can go past the float. No matter how large the margin is, it should still be just below the float.">
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; background:red;">
+  <div>
+    <div style="float:left; width:100px; height:50px; background:green;"></div>
+  </div>
+  <div style="margin-top:400px; clear:left; height:50px; background:green;"></div>
+</div>
diff --git a/css/CSS2/floats-clear/adjoining-float-new-fc.html b/css/CSS2/floats-clear/adjoining-float-new-fc.html
new file mode 100644
index 0000000..587bbf5
--- /dev/null
+++ b/css/CSS2/floats-clear/adjoining-float-new-fc.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>New formatting context next to adjoining float</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="help" href="https://www.w3.org/TR/CSS22/box.html#collapsing-margins" title="8.3.1 Collapsing margins">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; overflow:hidden; background:red;">
+  <div>
+    <div style="float:left; width:100px; height:50px; background:green;"></div>
+    <div style="margin-top:300px; clear:left; overflow:hidden; width:100px; height:50px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/clear-after-top-margin.html b/css/CSS2/floats-clear/clear-after-top-margin.html
new file mode 100644
index 0000000..95b4456
--- /dev/null
+++ b/css/CSS2/floats-clear/clear-after-top-margin.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Clearance inside block with top margin</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="help" href="https://www.w3.org/TR/CSS22/box.html#collapsing-margins" title="8.3.1 Collapsing margins">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="float:right; width:100px; height:100px;"></div>
+<!-- Using padding to prevent the child margin from pulling the block down. -->
+<div style="padding-top:10px; width:100px; background:green;">
+  <div style="margin-top:80px;">
+    <div style="clear:right;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/clear-applies-to-008-ref.xht b/css/CSS2/floats-clear/clear-applies-to-008-ref.xht
index 6a17a53..e947a1a 100644
--- a/css/CSS2/floats-clear/clear-applies-to-008-ref.xht
+++ b/css/CSS2/floats-clear/clear-applies-to-008-ref.xht
@@ -9,6 +9,8 @@
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
 
   <style type="text/css"><![CDATA[
+  /* Disable kerning because kerning may differ for different node tree. */
+  html { font-kerning: none; font-feature-settings: "kern" off; }
   body {margin-bottom: 0px;}
 
   div {height: 10em;}
@@ -23,4 +25,4 @@
   <div>PASS</div>
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/floats-clear/clear-applies-to-008.xht b/css/CSS2/floats-clear/clear-applies-to-008.xht
index 59a718a..1ff09c6 100644
--- a/css/CSS2/floats-clear/clear-applies-to-008.xht
+++ b/css/CSS2/floats-clear/clear-applies-to-008.xht
@@ -11,6 +11,8 @@
 
   <meta name="assert" content="The 'clear' property does not apply to elements with a display of 'inline'." />
   <style type="text/css">
+   /* Disable kerning because kerning may differ for different node tree. */
+   html { font-kerning: none; font-feature-settings: "kern" off; }
    .float { float: left; height: 10em; }
    .clear { clear: left; }
   </style>
diff --git a/css/CSS2/floats-clear/clear-applies-to-012.xht b/css/CSS2/floats-clear/clear-applies-to-012.xht
index fb76e91..ecff648 100644
--- a/css/CSS2/floats-clear/clear-applies-to-012.xht
+++ b/css/CSS2/floats-clear/clear-applies-to-012.xht
@@ -42,8 +42,8 @@
         <p>Test passes if there is a filled blue square to the right of this text.</p>
 
         <div>
-				    <span class="block-descendant">a</span>
-				    <span class="block-descendant">b</span>
+				    <span class="block-descendant">&nbsp;a</span>
+				    <span class="block-descendant">&nbsp;b</span>
         </div>
 
     </body>
diff --git a/css/CSS2/floats-clear/clear-on-child-with-margins.html b/css/CSS2/floats-clear/clear-on-child-with-margins.html
new file mode 100644
index 0000000..f65f314
--- /dev/null
+++ b/css/CSS2/floats-clear/clear-on-child-with-margins.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Child of block with clear</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="position:relative; top:-20px;">
+  <div style="float:left; margin-top:20px; width:50px; height:50px; background:green;"></div>
+  <div style="padding-top:1px;">
+    <div style="margin-top:19px; width:100px; background:green;">
+      <div style="clear:left; margin-top:25px; width:100px; height:50px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/clear-on-parent-and-child.html b/css/CSS2/floats-clear/clear-on-parent-and-child.html
new file mode 100644
index 0000000..d7dc26f
--- /dev/null
+++ b/css/CSS2/floats-clear/clear-on-parent-and-child.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Child of block with clear</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="container" style="overflow:hidden; width:200px; height:200px; background:red;">
+  <div style="float:left; width:10px; height:50px;"></div>
+  <div style="float:right; width:10px; height:100px;"></div>
+  <div style="clear:right;">
+    <div style="clear:left; height:200px; background:green;"></div>
+  </div>
+  <div style="height:10000px;"></div>
+</div>
+<script>
+  document.getElementById("container").scrollTop = 100;
+</script>
diff --git a/css/CSS2/floats-clear/clear-on-parent-with-margins-no-clearance.html b/css/CSS2/floats-clear/clear-on-parent-with-margins-no-clearance.html
new file mode 100644
index 0000000..f3d3c18
--- /dev/null
+++ b/css/CSS2/floats-clear/clear-on-parent-with-margins-no-clearance.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Child of block with clear</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="border-top:1px solid white; position:relative; z-index:-1; top:-51px; width:100px; background:green;">
+  <div style="float:left; width:100px; height:50px; background:white;"></div>
+  <div style="clear:left; margin-top:25px;">
+    <div style="height:50px; margin-top:150px; background:white;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/clear-on-parent-with-margins.html b/css/CSS2/floats-clear/clear-on-parent-with-margins.html
new file mode 100644
index 0000000..9b331d5
--- /dev/null
+++ b/css/CSS2/floats-clear/clear-on-parent-with-margins.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Child of block with clear</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:200px; height:200px; background:red;">
+  <div style="float:left; width:200px; height:100px; background:green;"></div>
+  <div style="clear:left; margin-top:100px;">
+    <div style="height:100px; margin-top:-1000px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/clear-on-parent.html b/css/CSS2/floats-clear/clear-on-parent.html
new file mode 100644
index 0000000..755cd78
--- /dev/null
+++ b/css/CSS2/floats-clear/clear-on-parent.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Child of block with clear</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:200px; height:200px; background:red;">
+  <div style="float:left; width:200px; height:100px; background:green;"></div>
+  <div style="clear:left;">
+    <div style="height:100px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/clear-with-top-margin-after-cleared-empty-block.html b/css/CSS2/floats-clear/clear-with-top-margin-after-cleared-empty-block.html
new file mode 100644
index 0000000..5d0fba7
--- /dev/null
+++ b/css/CSS2/floats-clear/clear-with-top-margin-after-cleared-empty-block.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Clearance on empty block followed by block with large margin and 'clear'</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="help" href="https://www.w3.org/TR/CSS22/box.html#collapsing-margins" title="8.3.1 Collapsing margins">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="position:relative; width:100px; border-top:1px solid white; top:-21px; z-index:-1; background:green;">
+  <div style="float:left; width:100px; height:20px; background:white;"></div>
+  <div style="clear:both;"></div>
+  <div style="clear:both; margin-top:100px; height:20px; background:white;"></div>
+</div>
diff --git a/css/CSS2/floats-clear/float-applies-to-008-ref.xht b/css/CSS2/floats-clear/float-applies-to-008-ref.xht
index 1d7dde1..addfe5e 100644
--- a/css/CSS2/floats-clear/float-applies-to-008-ref.xht
+++ b/css/CSS2/floats-clear/float-applies-to-008-ref.xht
@@ -10,17 +10,15 @@
 
   <style type="text/css"><![CDATA[
   div {text-align: right;}
-
-  span {background-color: blue;}
   ]]></style>
 
  </head>
 
  <body>
 
-  <p>Test passes if there is a short blue stripe on the right side of the page.</p>
+  <p>Test passes if Filler Text can be seen on the right side of the page.</p>
 
-  <div><span>Filler Text</span></div>
+  <div>Filler Text</div>
 
  </body>
 </html>
diff --git a/css/CSS2/floats-clear/float-applies-to-008.xht b/css/CSS2/floats-clear/float-applies-to-008.xht
index 4c78d57..0405348 100644
--- a/css/CSS2/floats-clear/float-applies-to-008.xht
+++ b/css/CSS2/floats-clear/float-applies-to-008.xht
@@ -1,26 +1,25 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
-    <head>
-        <title>CSS Test: Float applied to element with 'display' set to inline</title>
-        <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
-        <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2012-12-14 -->
-        <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#propdef-float" />
-        <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#float-position" />
-		<link rel="match" href="float-applies-to-008-ref.xht" />
+  <head>
+    <title>CSS Test: Float applied to element with 'display' set to inline</title>
+    <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+    <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2012-12-14 -->
+    <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#propdef-float" />
+    <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#float-position" />
+    <link rel="match" href="float-applies-to-008-ref.xht" />
 
-        <meta name="flags" content="" />
-        <meta name="assert" content="The 'float' property applies to elements with a display of inline." />
-        <style type="text/css">
-            div
-            {
-                background: blue;
-                display: inline;
-                float: right;
-            }
-        </style>
-    </head>
-    <body>
-        <p>Test passes if there is a short blue stripe on the right side of the page.</p>
-        <div>Filler Text</div>
-    </body>
+    <meta name="flags" content="" />
+    <meta name="assert" content="The 'float' property applies to elements with a display of inline." />
+    <style type="text/css">
+      div
+      {
+        display: inline;
+        float: right;
+      }
+    </style>
+  </head>
+  <body>
+    <p>Test passes if Filler Text can be seen on the right side of the page.</p>
+    <div>Filler Text</div>
+  </body>
 </html>
diff --git a/css/CSS2/floats-clear/float-applies-to-012.xht b/css/CSS2/floats-clear/float-applies-to-012.xht
index 9650741..21bc2dc 100644
--- a/css/CSS2/floats-clear/float-applies-to-012.xht
+++ b/css/CSS2/floats-clear/float-applies-to-012.xht
@@ -1,41 +1,41 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
-    <head>
-        <title>CSS Test: Float applied to element with 'display' set to inline-block</title>
-        <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
-        <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2012-12-14 -->
-        <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#propdef-float" />
-        <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#float-position" />
-        <link rel="match" href="../reference/float-applies-to-001-ref.xht" />
+  <head>
+    <title>CSS Test: Float applied to element with 'display' set to inline-block</title>
+    <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
+    <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2012-12-14 -->
+    <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#propdef-float" />
+    <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#float-position" />
+    <link rel="match" href="../reference/float-applies-to-001-ref.xht" />
 
-        <meta name="flags" content="" />
-        <meta name="assert" content="The 'float' property applies to elements with a display of inline-block." />
-        <style type="text/css">
-            span#inline-block
-            {
-                background: blue;
-                display: inline-block;
-                float: right;
-                height: 1in;
-                width: 1in;
-            }
+    <meta name="flags" content="" />
+    <meta name="assert" content="The 'float' property applies to elements with a display of inline-block." />
+    <style type="text/css">
+      span#inline-block
+      {
+        background: blue;
+        display: inline-block;
+        float: right;
+        height: 1in;
+        width: 1in;
+      }
 
-            span.block-descendant
-            {
-                color: blue;
-                display: block;
-            }
-        </style>
-    </head>
-    <body>
-        <p>Test passes if there is a filled blue square on the right side of the page.</p>
+      span.block-descendant
+      {
+        color: blue;
+        display: block;
+      }
+    </style>
+  </head>
+  <body>
+    <p>Test passes if there is a filled blue square on the right side of the page.</p>
 
-        <div>
-            <span id="inline-block">
-				        <span class="block-descendant">a</span>
-				        <span class="block-descendant">b</span>
-        	  </span>
-        </div>
+    <div>
+      <span id="inline-block">
+	<span class="block-descendant">&nbsp;a</span>
+	<span class="block-descendant">&nbsp;b</span>
+      </span>
+    </div>
 
-    </body>
+  </body>
 </html>
diff --git a/css/CSS2/floats-clear/floats-028-ref.xht b/css/CSS2/floats-clear/floats-028-ref.xht
index e6fd0ff..a975a6b 100644
--- a/css/CSS2/floats-clear/floats-028-ref.xht
+++ b/css/CSS2/floats-clear/floats-028-ref.xht
@@ -8,22 +8,20 @@
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
 
   <style type="text/css"><![CDATA[
-  div#wrapper
-  {
-  border: black solid 5px;
-  bottom: 17px;
-  height: 192px;
-  left: 24px;
-  position: relative;
-  width: 192px;
-  }
+    div#wrapper {
+      border: solid 5px black;
+      height: 2in;
+      margin: 0.25in;
+      position: absolute;
+      top: 10px;
+      width: 2in;
+   }
 
-  div > div
-  {
-  background-color: blue;
-  height: 120px;
-  width: 120px;
-  }
+   div > div {
+     background-color: blue;
+     height: 120px;
+     width: 120px;
+   }
 
   ]]></style>
 
@@ -38,4 +36,4 @@
   </div>
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/floats-clear/floats-028.xht b/css/CSS2/floats-clear/floats-028.xht
index 39006ea..8dd14fb 100644
--- a/css/CSS2/floats-clear/floats-028.xht
+++ b/css/CSS2/floats-clear/floats-028.xht
@@ -33,4 +33,4 @@
             <span>Filler Text</span>
         </div>
     </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/floats-clear/floats-036-ref.xht b/css/CSS2/floats-clear/floats-036-ref.xht
index 0c54201..0de561a 100644
--- a/css/CSS2/floats-clear/floats-036-ref.xht
+++ b/css/CSS2/floats-clear/floats-036-ref.xht
@@ -6,6 +6,10 @@
 
   <title>CSS Reftest Reference</title>
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
+  <style>
+    /* Disable kerning because kerning may differ for different node tree. */
+    html { font-kerning: none; font-feature-settings: "kern" off; }
+  </style>
 
  </head>
 
@@ -16,4 +20,4 @@
   <div>Filler TextFiller Text</div>
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/floats-clear/floats-036.xht b/css/CSS2/floats-clear/floats-036.xht
index de47a27..c0a91a8 100644
--- a/css/CSS2/floats-clear/floats-036.xht
+++ b/css/CSS2/floats-clear/floats-036.xht
@@ -9,6 +9,8 @@
         <meta name="flags" content="" />
         <meta name="assert" content="A floated elements top edge will be aligned with the top of a line box." />
         <style type="text/css">
+            /* Disable kerning because kerning may differ for different node tree. */
+            html { font-kerning: none; font-feature-settings: "kern" off; }
             div
             {
                 width: 5in;
@@ -26,4 +28,4 @@
             <span id="span1">Filler Text</span>
         </div>
     </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/floats-clear/floats-143-ref.xht b/css/CSS2/floats-clear/floats-143-ref.xht
index 49d1fe5..2fcdc13 100644
--- a/css/CSS2/floats-clear/floats-143-ref.xht
+++ b/css/CSS2/floats-clear/floats-143-ref.xht
@@ -9,6 +9,8 @@
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
 
   <style type="text/css"><![CDATA[
+  /* Disable kerning because kerning may differ for different node tree. */
+  html { font-kerning: none; font-feature-settings: "kern" off; }
   div
   {
   background-color: green;
diff --git a/css/CSS2/floats-clear/floats-143.xht b/css/CSS2/floats-clear/floats-143.xht
index 84acecc..6eb7e71 100644
--- a/css/CSS2/floats-clear/floats-143.xht
+++ b/css/CSS2/floats-clear/floats-143.xht
@@ -9,6 +9,8 @@
   <link rel="match" href="floats-143-ref.xht" />
 
   <style type="text/css">
+   /* Disable kerning because kerning may differ for different node tree. */
+   html { font-kerning: none; font-feature-settings: "kern" off; }
    .float { float: left; background: red; padding: 0; margin: 0; list-style: none; }
    .text { font: 900 2em/1 sans-serif; background: green; color: white; }
   </style>
diff --git a/css/CSS2/floats-clear/negative-clearance-after-adjoining-float.html b/css/CSS2/floats-clear/negative-clearance-after-adjoining-float.html
new file mode 100644
index 0000000..698f380
--- /dev/null
+++ b/css/CSS2/floats-clear/negative-clearance-after-adjoining-float.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Negative clearance between adjoining float and child with top margin</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; background:red;">
+  <div style="float:left; width:100px; height:50px; background:green;"></div>
+  <div style="clear:left; margin-top:200px;"></div>
+</div>
+<div style="width:100px; height:50px; background:green;"></div>
diff --git a/css/CSS2/floats-clear/negative-clearance-after-bottom-margin.html b/css/CSS2/floats-clear/negative-clearance-after-bottom-margin.html
new file mode 100644
index 0000000..fb06639
--- /dev/null
+++ b/css/CSS2/floats-clear/negative-clearance-after-bottom-margin.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Negative clearance between bottom margin and cleared child with top margin</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="position:relative; top:-50px; z-index:-1;">
+  <div style="float:left; width:50px; height:50px; border-top:50px solid white; background:green;"></div>
+  <div style="padding-top:1px;">
+    <div style="width:100px; background:green;">
+      <div style="margin-bottom:49px;"></div>
+      <div style="clear:left; margin-top:98px;"></div>
+    </div>
+    <div style="width:100px; height:50px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/nested-clearance-new-formatting-context.html b/css/CSS2/floats-clear/nested-clearance-new-formatting-context.html
new file mode 100644
index 0000000..5e43b25
--- /dev/null
+++ b/css/CSS2/floats-clear/nested-clearance-new-formatting-context.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Nested clearance on new formatting context</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:200px; height:200px; background:red;">
+  <div style="width:500px;">
+    <div style="float:left; width:200px; height:100px; background:green;"></div>
+    <div style="clear:left; margin-top:40px;">
+      <div style="float:left; width:200px; height:80px; background:green;"></div>
+      <div style="overflow:hidden; clear:left; margin-top:50px; width:200px; height:20px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/no-clearance-adjoining-opposite-float.html b/css/CSS2/floats-clear/no-clearance-adjoining-opposite-float.html
new file mode 100644
index 0000000..01a457e
--- /dev/null
+++ b/css/CSS2/floats-clear/no-clearance-adjoining-opposite-float.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>No clearance due to large top margin that takes us past the float</title>
+<meta name="assert" content="Check that we don't apply clearance when the top margin of the clearance candidate takes it past the relevant float, and that a preceding empty block with a right float that's adjoining to the clearance candidate doesn't cause confusion">
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/nothing.html">
+<p>There should be nothing below.</p>
+<div style="float:left; width:10px; height:100px;"></div>
+<div>
+  <div>
+    <div style="float:right; width:10px; height:200px;"></div>
+  </div>
+  <div style="background:red;">
+    <div style="margin-top:150px; clear:left; background:hotpink;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/no-clearance-due-to-large-margin-after-left-right.html b/css/CSS2/floats-clear/no-clearance-due-to-large-margin-after-left-right.html
new file mode 100644
index 0000000..bc459d0
--- /dev/null
+++ b/css/CSS2/floats-clear/no-clearance-due-to-large-margin-after-left-right.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>No clearance due to large top margin inside clear:right inside clear:left that takes us past the floats</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="float:left; width:50px; height:50px;"></div>
+<div style="float:right; width:50px; height:100px;"></div>
+<div style="width:100px; height:100px; background:green;">
+  <div style="height:15px; margin-bottom:20px;"></div>
+  <div style="background:red;">
+    <div style="clear:left; background:red;">
+      <div style="clear:right; background:red;">
+        <div style="margin-top:185px; background:red;"></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/no-clearance-due-to-large-margin.html b/css/CSS2/floats-clear/no-clearance-due-to-large-margin.html
new file mode 100644
index 0000000..cabb1ac
--- /dev/null
+++ b/css/CSS2/floats-clear/no-clearance-due-to-large-margin.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>No clearance due to large top margin that takes us past the float</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="float:left; width:100px; height:100px; background:green;"></div>
+<div style="padding-top:1px;">
+  <div style="background:red;">
+    <div style="clear:left; background:white; margin-top:150px;"><br></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/second-float-inside-empty-cleared-block-after-margin.html b/css/CSS2/floats-clear/second-float-inside-empty-cleared-block-after-margin.html
new file mode 100644
index 0000000..ee8bebc
--- /dev/null
+++ b/css/CSS2/floats-clear/second-float-inside-empty-cleared-block-after-margin.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Float separated from float inside empty cleared block, margin before clearance</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="help" href="https://www.w3.org/TR/CSS22/box.html#collapsing-margins" title="8.3.1 Collapsing margins">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="float:left; width:100px; height:50px; background:green;"></div>
+<div style="height:1px;"></div>
+<div style="margin-top:20px;">
+  <div style="clear:left;">
+    <div style="float:left; width:100px; height:50px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats-clear/second-float-inside-empty-cleared-block.html b/css/CSS2/floats-clear/second-float-inside-empty-cleared-block.html
new file mode 100644
index 0000000..a383e9a
--- /dev/null
+++ b/css/CSS2/floats-clear/second-float-inside-empty-cleared-block.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Float separated from float inside empty cleared block</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="help" href="https://www.w3.org/TR/CSS22/box.html#collapsing-margins" title="8.3.1 Collapsing margins">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="float:left; width:100px; height:50px; background:green;"></div>
+<div style="height:1px;"></div>
+<div>
+  <div style="clear:left;">
+    <div style="float:left; width:100px; height:50px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats/floated-table-wider-than-specified.html b/css/CSS2/floats/floated-table-wider-than-specified.html
new file mode 100644
index 0000000..f93d50e
--- /dev/null
+++ b/css/CSS2/floats/floated-table-wider-than-specified.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Floated table that becomes wider than its specified width, due to wide contents</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#float-width" title="10.3.5 Floating, non-replaced elements">
+<link rel="help" href="https://www.w3.org/TR/CSS22/tables.html#auto-table-layout" title="17.5.2.2 Automatic table layout">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:200px; height:200px; background:red;">
+  <div style="width:300px;">
+    <div style="float:left; width:200px; height:100px; margin-right:10px; background:green;"></div>
+    <div style="float:left; display:table; width:50px; height:10px; background:green;">
+      <div style="width:200px; height:100px;"></div>
+    </div>
+  </div>
+</div>
diff --git a/css/CSS2/floats/floats-in-table-caption-001-ref.html b/css/CSS2/floats/floats-in-table-caption-001-ref.html
new file mode 100644
index 0000000..bf02b99
--- /dev/null
+++ b/css/CSS2/floats/floats-in-table-caption-001-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/tables.html#model">
+<meta name="flags" content="" />
+<title>
+Caption block containers are rendered same as normal block boxes
+</title>
+
+<p>
+The words floated and inline should be legible below, with inline appearing just
+to the right of the border surrounding floated.
+</p>
+
+<div style="float:left; border: 1px solid black;">
+  floated
+</div>
+inline
diff --git a/css/CSS2/floats/floats-in-table-caption-001.html b/css/CSS2/floats/floats-in-table-caption-001.html
new file mode 100644
index 0000000..2bc9850
--- /dev/null
+++ b/css/CSS2/floats/floats-in-table-caption-001.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/tables.html#model">
+<link rel="match" href="floats-in-table-caption-001-ref.html">
+<meta name="flags" content="" />
+<meta name="assert" content="Basic floats are honored inside a caption" />
+<title>
+Caption block containers are rendered same as normal block boxes
+</title>
+
+<p>
+The words floated and inline should be legible below, with inline appearing just
+to the right of the border surrounding floated.
+</p>
+
+<table>
+  <!-- 200px allows floated and inline to not wrap -->
+  <caption style="text-align:left; width:200px">
+    <div style="float:left; border: 1px solid black;">
+      floated
+    </div>
+    inline
+  </caption>
+</table>
diff --git a/css/CSS2/floats/intrinsic-size-float-and-line.html b/css/CSS2/floats/intrinsic-size-float-and-line.html
new file mode 100644
index 0000000..0603124
--- /dev/null
+++ b/css/CSS2/floats/intrinsic-size-float-and-line.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Put float next to line in shrink-to-fit container</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#float-width" title="10.3.5 Floating, non-replaced elements">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="float:left; min-width:150px; background:red;">
+  <div style="float:right; width:100px; height:200px; background:green;"></div>
+  <div style="vertical-align:top; display:inline-block; width:100px; height:200px; background:green;"></div>
+</div>
diff --git a/css/CSS2/floats/new-fc-beside-adjoining-float-2.html b/css/CSS2/floats/new-fc-beside-adjoining-float-2.html
new file mode 100644
index 0000000..2d27a28
--- /dev/null
+++ b/css/CSS2/floats/new-fc-beside-adjoining-float-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>A new formatting context that fits beside an adjoining float, and thus pulls down the float with its top margin</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#bfc-next-to-float" title="9.5 Floats">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<meta name="assert" content="The float is adjoining with the box that establishes a new formatting context when it fits beside it, and will therefore be affected by its margin">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="overflow:hidden; width:200px; height:200px; background:red;">
+  <div style="margin-top:190px;">
+    <div>
+      <div style="float:left; width:100px; height:200px; background:green;"></div>
+    </div>
+    <div style="margin-top:-190px; overflow:hidden; width:100px; height:200px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats/new-fc-beside-adjoining-float.html b/css/CSS2/floats/new-fc-beside-adjoining-float.html
new file mode 100644
index 0000000..91adbfc
--- /dev/null
+++ b/css/CSS2/floats/new-fc-beside-adjoining-float.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>A new formatting context that fits beside an adjoining float, and thus pulls down the float with its top margin</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#bfc-next-to-float" title="9.5 Floats">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<meta name="assert" content="The float is adjoining with the box that establishes a new formatting context when it fits beside it, and will therefore be affected by its margin">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="overflow:hidden; width:200px; background:green;">
+  <div style="width:300px; margin-top:50px; background:red;">
+    <div>
+      <div style="float:left; width:200px; height:10px; background:green;"></div>
+    </div>
+    <div style="margin-top:190px; overflow:hidden; width:100px; height:10px; background:red;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats/new-fc-separates-from-float-2.html b/css/CSS2/floats/new-fc-separates-from-float-2.html
new file mode 100644
index 0000000..fd0deab
--- /dev/null
+++ b/css/CSS2/floats/new-fc-separates-from-float-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>A new formatting context that doesn't fit beside a float make the float non-adjoining</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#bfc-next-to-float" title="9.5 Floats">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<meta name="assert" content="Although the 'clear' property isn't specified in this test, a new formatting context that doesn't fit below a float that would otherwise be adjoining will need to separate its margin from the float, so that it doesn't affect the float. This is very similar to clearance.">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="overflow:hidden; width:200px; height:200px; background:red;">
+  <div style="margin-top:-50px;">
+    <div>
+      <div style="float:left; width:200px; height:150px; background:green;"></div>
+    </div>
+    <div style="margin-top:12345px; overflow:hidden; width:200px; height:100px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats/new-fc-separates-from-float.html b/css/CSS2/floats/new-fc-separates-from-float.html
new file mode 100644
index 0000000..89ee751
--- /dev/null
+++ b/css/CSS2/floats/new-fc-separates-from-float.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>A new formatting context that doesn't fit beside a float make the float non-adjoining</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#bfc-next-to-float" title="9.5 Floats">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<meta name="assert" content="Although the 'clear' property isn't specified in this test, a new formatting context that doesn't fit below a float that would otherwise be adjoining will need to separate its margin from the float, so that it doesn't affect the float. This is very similar to clearance.">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="overflow:hidden; width:200px; background:red;">
+  <div>
+    <div>
+      <div style="float:left; width:200px; height:200px; background:green;"></div>
+    </div>
+    <div style="margin-top:200px; overflow:hidden; width:200px; height:1px; background:white;"></div>
+  </div>
+</div>
diff --git a/css/CSS2/floats/zero-space-between-floats-001.html b/css/CSS2/floats/zero-space-between-floats-001.html
new file mode 100644
index 0000000..a779601
--- /dev/null
+++ b/css/CSS2/floats/zero-space-between-floats-001.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>A zero-width new formatting context should fit in a zero-width layout opportunity between floats</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#float-position" title="9.5.1 Positioning the float: the 'float' property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<div id="container" style="position:relative; width:200px;">
+  <div style="float:left; width:100px; height:200px;"></div>
+  <div style="float:right; width:100px; height:200px;"></div>
+  <div data-offset-x="100" data-offset-y="0" style="overflow:hidden; width:0; height:200px;"></div>
+</div>
+<script>
+  checkLayout("#container");
+</script>
diff --git a/css/CSS2/floats/zero-space-between-floats-002.html b/css/CSS2/floats/zero-space-between-floats-002.html
new file mode 100644
index 0000000..f8eb591
--- /dev/null
+++ b/css/CSS2/floats/zero-space-between-floats-002.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>A zero-width new formatting context should fit in a zero-width layout opportunity between floats, above a 100% wide float</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#float-position" title="9.5.1 Positioning the float: the 'float' property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<div id="container" style="position:relative; width:200px;">
+  <div style="float:left; width:100px; height:200px;"></div>
+  <div style="float:right; width:100px; height:200px;"></div>
+  <div style="float:right; width:100%; height:10px;"></div>
+  <div data-offset-x="100" data-offset-y="0" style="overflow:hidden; width:0; height:200px;"></div>
+</div>
+<script>
+  checkLayout("#container");
+</script>
diff --git a/css/CSS2/floats/zero-space-between-floats-003.html b/css/CSS2/floats/zero-space-between-floats-003.html
new file mode 100644
index 0000000..3ddbb87
--- /dev/null
+++ b/css/CSS2/floats/zero-space-between-floats-003.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Zero-width new formatting context with clearance</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#float-position" title="9.5.1 Positioning the float: the 'float' property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<div id="container" style="position:relative; width:200px;">
+  <div style="float:left; width:100px; height:100px;"></div>
+  <div style="float:right; width:100px; height:100px;"></div>
+  <div style="float:right; width:100%; height:100px;"></div>
+  <div data-offset-x="0" data-offset-y="100" style="overflow:hidden; clear:left; width:0;"></div>
+</div>
+<script>
+  checkLayout("#container");
+</script>
diff --git a/css/CSS2/floats/zero-space-between-floats-004.html b/css/CSS2/floats/zero-space-between-floats-004.html
new file mode 100644
index 0000000..4ac426f
--- /dev/null
+++ b/css/CSS2/floats/zero-space-between-floats-004.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Zero-width new formatting context with clearance</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#float-position" title="9.5.1 Positioning the float: the 'float' property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<div id="container" style="position:relative; width:200px;">
+  <div style="float:left; width:100px; height:100px;"></div>
+  <div style="float:right; width:100px; height:100px;"></div>
+  <div style="float:right; width:100%; height:100px;"></div>
+  <div data-offset-x="0" data-offset-y="200" style="overflow:hidden; clear:right; width:0;"></div>
+</div>
+<script>
+  checkLayout("#container");
+</script>
diff --git a/css/CSS2/floats/zero-width-floats.html b/css/CSS2/floats/zero-width-floats.html
new file mode 100644
index 0000000..affecab
--- /dev/null
+++ b/css/CSS2/floats/zero-width-floats.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Zero width floats</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#float-width" title="10.3.5 Floating, non-replaced elements">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="margin-left:50px; width:100px;">
+  <div style="float:left; width:0; height:50px;"></div>
+  <div style="float:right; clear:left; width:0; height:150px;"></div>
+  <div style="overflow:hidden; margin:0 -50px; height:100px; background:green;"></div>
+</div>
+<div style="position:relative; top:-100px; overflow:hidden; clear:both; width:200px; height:100px; background:green;"></div>
diff --git a/css/CSS2/fonts/font-148-ref.xht b/css/CSS2/fonts/font-148-ref.xht
new file mode 100644
index 0000000..22a8e1a
--- /dev/null
+++ b/css/CSS2/fonts/font-148-ref.xht
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Ondřej Žára" href="https://ondras.zarovi.cz/" />
+
+<style type="text/css">
+    div {
+        font: 10px sans-serif;
+    }
+    span {
+        font: 100px sans-serif;
+    }
+</style>
+
+ </head>
+
+    <body>
+        <p>Test passes if letters "def" below are larger than "abc" and "ghi".</p>
+        <div>abc<span class="test">def</span>ghi</div>
+    </body>
+</html>
\ No newline at end of file
diff --git a/css/CSS2/fonts/font-148.xht b/css/CSS2/fonts/font-148.xht
new file mode 100644
index 0000000..972aa1a
--- /dev/null
+++ b/css/CSS2/fonts/font-148.xht
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <title>CSS Test: Font shorthand using calc() value for font-size</title>
+        <link rel="author" title="Ondřej Žára" href="https://ondras.zarovi.cz/" />
+        <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-prop" />
+        <link rel="help" href="https://www.w3.org/TR/css-values-3/#calc-notation" />
+        <link rel="match" href="font-148-ref.xht"/>
+        <meta name="flags" content="" />
+        <meta name="assert" content="The 'font' shorthand property accepts and sets font-variant, font-size and font-family." />
+        <style type="text/css">
+            div {
+                font: 10px sans-serif;
+            }
+            span {
+                font: calc(10 * 10px) sans-serif;
+            }
+        </style>
+    </head>
+    <body>
+        <p>Test passes if letters "def" below are larger than "abc" and "ghi".</p>
+        <div>abc<span class="test">def</span>ghi</div>
+    </body>
+</html>
diff --git a/css/CSS2/fonts/font-weight-100-ref.html b/css/CSS2/fonts/font-weight-100-ref.html
new file mode 100644
index 0000000..a3bd0d2
--- /dev/null
+++ b/css/CSS2/fonts/font-weight-100-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<link rel="author" title="Mike Bremford" href="http://bfo.com">
+<style>
+div { font-weight: 100 }
+</style>
+<body>
+  <p>Test passes if the lines of "Filler Text" below match.</p>
+  <div>Filler Text</div>
+  <div>Filler Text</div>
+</body>
diff --git a/css/CSS2/fonts/font-weight-900-ref.html b/css/CSS2/fonts/font-weight-900-ref.html
new file mode 100644
index 0000000..cc3ba5d
--- /dev/null
+++ b/css/CSS2/fonts/font-weight-900-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<link rel="author" title="Mike Bremford" href="http://bfo.com">
+<style>
+div { font-weight: 900 }
+</style>
+<body>
+  <p>Test passes if the lines of "Filler Text" below match.</p>
+  <div>Filler Text</div>
+  <div>Filler Text</div>
+</body>
diff --git a/css/CSS2/fonts/font-weight-rule-005.xht b/css/CSS2/fonts/font-weight-rule-005.xht
index 742af3d..d7213e3 100644
--- a/css/CSS2/fonts/font-weight-rule-005.xht
+++ b/css/CSS2/fonts/font-weight-rule-005.xht
@@ -5,7 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/fonts.html#font-boldness" />
         <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-weight-prop" />
-        <link rel="match" href="font-weight-bold-ref.html" />
+        <link rel="match" href="font-weight-900-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'font-weight' property set to 'bolder' does not increase the value of font weight when the parent values is already set at 900." />
         <style type="text/css">
diff --git a/css/CSS2/fonts/font-weight-rule-007.xht b/css/CSS2/fonts/font-weight-rule-007.xht
index 8c3b8e3..cf51b12 100644
--- a/css/CSS2/fonts/font-weight-rule-007.xht
+++ b/css/CSS2/fonts/font-weight-rule-007.xht
@@ -5,7 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/fonts.html#font-boldness" />
         <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-weight-prop" />
-        <link rel="match" href="font-weight-normal-ref.html" />
+        <link rel="match" href="font-weight-100-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="A font weight of 'lighter' selects the next lighter weight compared to its parent's weight." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/after-inheritable-001-ref.html b/css/CSS2/generated-content/after-inheritable-001-ref.html
new file mode 100644
index 0000000..77aa2db
--- /dev/null
+++ b/css/CSS2/generated-content/after-inheritable-001-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: solid;
+        color: green;
+        text-align: center;
+    }
+</style>
+<body>
+    <p>Test passes if the words "PASS PASS" below are green and the words are centered within the box below.</p>
+    <div>PASS PASS</div>
+</body>
diff --git a/css/CSS2/generated-content/after-inheritable-001.xht b/css/CSS2/generated-content/after-inheritable-001.xht
index 273defe..aa1b490 100644
--- a/css/CSS2/generated-content/after-inheritable-001.xht
+++ b/css/CSS2/generated-content/after-inheritable-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Pseudo-element ':after' inherits inheritable values</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content" />
+        <link rel="match" href="after-inheritable-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The pseudo-element ':after' generated content inherits any inheritable properties from the element." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/after-inheritable-002-ref.html b/css/CSS2/generated-content/after-inheritable-002-ref.html
new file mode 100644
index 0000000..541589b
--- /dev/null
+++ b/css/CSS2/generated-content/after-inheritable-002-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 15px solid blue;
+        color: green;
+    }
+    a {
+    	border-color: orange;
+        border-style: solid;
+    }
+</style>
+<body>
+    <p>Test passes if the words "PASS PASS" are green, they are contained within an orange box with thinner lines than the blue box.</p>
+    <div><a>PASS PASS</a></div>
+</body>
diff --git a/css/CSS2/generated-content/after-inheritable-002.xht b/css/CSS2/generated-content/after-inheritable-002.xht
index 8273293..dd56d04 100644
--- a/css/CSS2/generated-content/after-inheritable-002.xht
+++ b/css/CSS2/generated-content/after-inheritable-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Pseudo-element ':after' does not inherit non-inheritable values</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content" />
+        <link rel="match" href="after-inheritable-002-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Non-inherited properties apply their initial value when applying to ':after'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/after-location-001-ref.html b/css/CSS2/generated-content/after-location-001-ref.html
new file mode 100644
index 0000000..5b4f25f
--- /dev/null
+++ b/css/CSS2/generated-content/after-location-001-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>Test passes if the words "PASS PASS" appear below and are to the right of the arrow.</p>
+    <div>--&gt;PASS PASS</div>
+</body>
diff --git a/css/CSS2/generated-content/after-location-001.xht b/css/CSS2/generated-content/after-location-001.xht
index fa80f55..4b89e88 100644
--- a/css/CSS2/generated-content/after-location-001.xht
+++ b/css/CSS2/generated-content/after-location-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: After applies after text</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content" />
+        <link rel="match" href="after-location-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="After places the generated content after the element content." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/before-after-002.xht b/css/CSS2/generated-content/before-after-002.xht
index 127b6a8..b99ca9e 100644
--- a/css/CSS2/generated-content/before-after-002.xht
+++ b/css/CSS2/generated-content/before-after-002.xht
@@ -4,12 +4,13 @@
         <title>CSS Test: Before, after is included in formatting changes</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Generated content is included in any formatting changes made to an element." />
         <style type="text/css">
             div
             {
-                border: solid;
+                border: 2px solid black;
                 color: green;
             }
             div:before
@@ -23,7 +24,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if the words "PASS PASS" appear in green inside the box.</p>
+        <p>Test passes if the words "PASS PASS" appear in the box below.</p>
         <div></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/before-inheritable-001.xht b/css/CSS2/generated-content/before-inheritable-001.xht
index 82153e2..f240711 100644
--- a/css/CSS2/generated-content/before-inheritable-001.xht
+++ b/css/CSS2/generated-content/before-inheritable-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Pseudo-element ':before' inherits inheritable values</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content" />
+        <link rel="match" href="after-inheritable-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The pseudo-element ':before' generated content inherits any inheritable properties from the element." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/before-inheritable-002.xht b/css/CSS2/generated-content/before-inheritable-002.xht
index ff8ae02..ad422bf 100644
--- a/css/CSS2/generated-content/before-inheritable-002.xht
+++ b/css/CSS2/generated-content/before-inheritable-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Pseudo-element ':before' does not inherit non-inheritable values</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content" />
+        <link rel="match" href="after-inheritable-002-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Non-inherited properties apply the initial value when applying to ':before'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/before-location-001-ref.html b/css/CSS2/generated-content/before-location-001-ref.html
new file mode 100644
index 0000000..d22138a
--- /dev/null
+++ b/css/CSS2/generated-content/before-location-001-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>Test passes if the words "PASS PASS" appear below and are to the left of the arrow.</p>
+    <div>PASS PASS&lt;--</div>
+</body>
diff --git a/css/CSS2/generated-content/before-location-001.xht b/css/CSS2/generated-content/before-location-001.xht
index 222d2e8..6999ef6 100644
--- a/css/CSS2/generated-content/before-location-001.xht
+++ b/css/CSS2/generated-content/before-location-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Before applies before text</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content" />
+        <link rel="match" href="before-location-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Before places generated content before the element content." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/bidi-generated-content-001-ref.html b/css/CSS2/generated-content/bidi-generated-content-001-ref.html
new file mode 100644
index 0000000..9ff414a
--- /dev/null
+++ b/css/CSS2/generated-content/bidi-generated-content-001-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>The two lines below should be identical:</p>
+    <p>This sentence should be readable</p>
+    <p>This sentence should be readable</p>
+</body>
diff --git a/css/CSS2/generated-content/bidi-generated-content-001.xht b/css/CSS2/generated-content/bidi-generated-content-001.xht
index 3e2e6f6..11e1062 100644
--- a/css/CSS2/generated-content/bidi-generated-content-001.xht
+++ b/css/CSS2/generated-content/bidi-generated-content-001.xht
@@ -5,6 +5,7 @@
     <link rel="author" title="Eira Monstad, Opera Software ASA" href="mailto:public-testsuites@opera.com"/>
     <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content"/>
     <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#direction"/>
+    <link rel="match" href="bidi-generated-content-001-ref.html" />
     <meta name="assert" content="A right-to-left override should be applied when u+202E is inserted through the content property"/>
     <style type="text/css"><![CDATA[
       .force:before {
diff --git a/css/CSS2/generated-content/bidi-generated-content-002-ref.html b/css/CSS2/generated-content/bidi-generated-content-002-ref.html
new file mode 100644
index 0000000..a7eaddf
--- /dev/null
+++ b/css/CSS2/generated-content/bidi-generated-content-002-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>The two lines below should be identical:</p>
+    <p>ab c d</p>
+    <p>ab c d</p>
+</body>
diff --git a/css/CSS2/generated-content/bidi-generated-content-002.xht b/css/CSS2/generated-content/bidi-generated-content-002.xht
index b10e641..b9dd46d 100644
--- a/css/CSS2/generated-content/bidi-generated-content-002.xht
+++ b/css/CSS2/generated-content/bidi-generated-content-002.xht
@@ -5,6 +5,7 @@
     <link rel="author" title="Eira Monstad, Opera Software ASA" href="mailto:public-testsuites@opera.com"/>
     <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#direction"/>
     <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content"/>
+    <link rel="match" href="bidi-generated-content-002-ref.html" />
     <meta name="flags" content=""/>
     <meta name="assert" content="A left-to-right override should be correctly applied when inserted through the content property"/>
     <style type="text/css"><![CDATA[
diff --git a/css/CSS2/generated-content/content-001-ref.html b/css/CSS2/generated-content/content-001-ref.html
new file mode 100644
index 0000000..b55f5fe
--- /dev/null
+++ b/css/CSS2/generated-content/content-001-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is no red visible on the page.</p>
+    <div></div>
+</body>
diff --git a/css/CSS2/generated-content/content-001.xht b/css/CSS2/generated-content/content-001.xht
index efd40d2..447d8da 100644
--- a/css/CSS2/generated-content/content-001.xht
+++ b/css/CSS2/generated-content/content-001.xht
@@ -5,7 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
-        <link rel="match" href="../reference/no-red-on-blank-page-ref.xht"/>
+        <link rel="match" href="content-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a value of 'none'." />
         <style type="text/css">
@@ -15,7 +15,7 @@
                 content: none;
                 color: red;
             }
-            #div1
+            div
             {
                 border: 2px solid black;
                 height: 30px;
diff --git a/css/CSS2/generated-content/content-002.xht b/css/CSS2/generated-content/content-002.xht
index b6ae000..27c4a08 100644
--- a/css/CSS2/generated-content/content-002.xht
+++ b/css/CSS2/generated-content/content-002.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a value of 'normal'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-003-ref.html b/css/CSS2/generated-content/content-003-ref.html
new file mode 100644
index 0000000..83017ff
--- /dev/null
+++ b/css/CSS2/generated-content/content-003-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the words "PASS PASS" appear in the box below.</p>
+    <div>PASS PASS</div>
+</body>
diff --git a/css/CSS2/generated-content/content-003.xht b/css/CSS2/generated-content/content-003.xht
index 9394d90..93f016a 100644
--- a/css/CSS2/generated-content/content-003.xht
+++ b/css/CSS2/generated-content/content-003.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a string as a value." />
         <style type="text/css">
@@ -16,12 +17,11 @@
             div
             {
                 border: 2px solid black;
-                height: 30px;
             }
         </style>
     </head>
     <body>
-        <p>Test passes if the words "PASS PASS" are in the box below.</p>
+        <p>Test passes if the words "PASS PASS" appear in the box below.</p>
         <div></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-004.xht b/css/CSS2/generated-content/content-004.xht
index 7a2f183..1388f76 100644
--- a/css/CSS2/generated-content/content-004.xht
+++ b/css/CSS2/generated-content/content-004.xht
@@ -5,17 +5,18 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="../../reference/ref-filled-green-100px-square-only.html" />
         <meta name="flags" content="image" />
         <meta name="assert" content="The 'content' property properly handles the 'url()' function as a value." />
         <style type="text/css">
             div:before
             {
-                content: url('support/green15x15.png');
+                content: url('../support/green_box.png');
             }
         </style>
     </head>
     <body>
-        <p>Test passes if there is a green box below.</p>
+        <p>Test passes if there is a filled green square.</p>
         <div></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-005-ref.html b/css/CSS2/generated-content/content-005-ref.html
new file mode 100644
index 0000000..9947695
--- /dev/null
+++ b/css/CSS2/generated-content/content-005-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a zero "0" in the box below.</p>
+    <div>0</div>
+</body>
diff --git a/css/CSS2/generated-content/content-005.xht b/css/CSS2/generated-content/content-005.xht
index 7accf92..3036214 100644
--- a/css/CSS2/generated-content/content-005.xht
+++ b/css/CSS2/generated-content/content-005.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-005-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function as a value." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-006-ref.html b/css/CSS2/generated-content/content-006-ref.html
new file mode 100644
index 0000000..1a59725
--- /dev/null
+++ b/css/CSS2/generated-content/content-006-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a bullet (&#x2022;) in the box below.</p>
+    <div>&#x2022;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-006.xht b/css/CSS2/generated-content/content-006.xht
index bd4584e..297e275 100644
--- a/css/CSS2/generated-content/content-006.xht
+++ b/css/CSS2/generated-content/content-006.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-006-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-007-ref.html b/css/CSS2/generated-content/content-007-ref.html
new file mode 100644
index 0000000..7e50aaa
--- /dev/null
+++ b/css/CSS2/generated-content/content-007-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a bullet (&#x25E6;) in the box below.</p>
+    <div>&#x25E6;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-007.xht b/css/CSS2/generated-content/content-007.xht
index 9573188..c4449d1 100644
--- a/css/CSS2/generated-content/content-007.xht
+++ b/css/CSS2/generated-content/content-007.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-007-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-009.xht b/css/CSS2/generated-content/content-009.xht
index 743d338..a245954 100644
--- a/css/CSS2/generated-content/content-009.xht
+++ b/css/CSS2/generated-content/content-009.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-005-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-010-ref.html b/css/CSS2/generated-content/content-010-ref.html
new file mode 100644
index 0000000..85979c5
--- /dev/null
+++ b/css/CSS2/generated-content/content-010-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there are double zeros "00" in the box below.</p>
+    <div>00</div>
+</body>
diff --git a/css/CSS2/generated-content/content-010.xht b/css/CSS2/generated-content/content-010.xht
index ac8550c..f0db8df 100644
--- a/css/CSS2/generated-content/content-010.xht
+++ b/css/CSS2/generated-content/content-010.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-010-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
@@ -20,7 +21,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if there is are double zeros "00" in the box below.</p>
+        <p>Test passes if there are double zeros "00" in the box below.</p>
         <div></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-011-ref.html b/css/CSS2/generated-content/content-011-ref.html
new file mode 100644
index 0000000..8bc5f31
--- /dev/null
+++ b/css/CSS2/generated-content/content-011-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a letter "i" in the box below.</p>
+    <div>i</div>
+</body>
diff --git a/css/CSS2/generated-content/content-011.xht b/css/CSS2/generated-content/content-011.xht
index b5f8932..8bd626a 100644
--- a/css/CSS2/generated-content/content-011.xht
+++ b/css/CSS2/generated-content/content-011.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-011-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-012-ref.html b/css/CSS2/generated-content/content-012-ref.html
new file mode 100644
index 0000000..e628c5f
--- /dev/null
+++ b/css/CSS2/generated-content/content-012-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a letter "I" in the box below.</p>
+    <div>I</div>
+</body>
diff --git a/css/CSS2/generated-content/content-012.xht b/css/CSS2/generated-content/content-012.xht
index 6ba353f..1b57d03 100644
--- a/css/CSS2/generated-content/content-012.xht
+++ b/css/CSS2/generated-content/content-012.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-012-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-013-ref.html b/css/CSS2/generated-content/content-013-ref.html
new file mode 100644
index 0000000..dd22b26
--- /dev/null
+++ b/css/CSS2/generated-content/content-013-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a greek letter "&#x03B1;" in the box below.</p>
+    <div>&#x03B1;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-013.xht b/css/CSS2/generated-content/content-013.xht
index 753dcc2..9ac30e5 100644
--- a/css/CSS2/generated-content/content-013.xht
+++ b/css/CSS2/generated-content/content-013.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-013-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-014-ref.html b/css/CSS2/generated-content/content-014-ref.html
new file mode 100644
index 0000000..a3b3167
--- /dev/null
+++ b/css/CSS2/generated-content/content-014-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a letter "a" in the box below.</p>
+    <div>a</div>
+</body>
diff --git a/css/CSS2/generated-content/content-014.xht b/css/CSS2/generated-content/content-014.xht
index f7fe2d3..879b4dd 100644
--- a/css/CSS2/generated-content/content-014.xht
+++ b/css/CSS2/generated-content/content-014.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-014-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-015-ref.html b/css/CSS2/generated-content/content-015-ref.html
new file mode 100644
index 0000000..f452a20
--- /dev/null
+++ b/css/CSS2/generated-content/content-015-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a letter "A" in the box below.</p>
+    <div>A</div>
+</body>
diff --git a/css/CSS2/generated-content/content-015.xht b/css/CSS2/generated-content/content-015.xht
index 843350b..089496d 100644
--- a/css/CSS2/generated-content/content-015.xht
+++ b/css/CSS2/generated-content/content-015.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-015-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-016-ref.html b/css/CSS2/generated-content/content-016-ref.html
new file mode 100644
index 0000000..023c98d
--- /dev/null
+++ b/css/CSS2/generated-content/content-016-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is an Armenian character "&#x0531;" below.</p>
+    <div>&#x0531;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-016.xht b/css/CSS2/generated-content/content-016.xht
index 2ff6f83..75d8bb7 100644
--- a/css/CSS2/generated-content/content-016.xht
+++ b/css/CSS2/generated-content/content-016.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-016-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
@@ -21,7 +22,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if there is an Armenian character "&#x0561;" or "&#x0531;" below.</p>
+        <p>Test passes if there is an Armenian character "&#x0531;" below.</p>
         <div></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-017-ref.html b/css/CSS2/generated-content/content-017-ref.html
new file mode 100644
index 0000000..25d69f2
--- /dev/null
+++ b/css/CSS2/generated-content/content-017-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a Georgian character "&#x10d0;" in the box below.</p>
+    <div>&#x10d0;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-017.xht b/css/CSS2/generated-content/content-017.xht
index f887d2c..adf5b89 100644
--- a/css/CSS2/generated-content/content-017.xht
+++ b/css/CSS2/generated-content/content-017.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-017-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-018.xht b/css/CSS2/generated-content/content-018.xht
index bf42051..039987a 100644
--- a/css/CSS2/generated-content/content-018.xht
+++ b/css/CSS2/generated-content/content-018.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-014-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-019.xht b/css/CSS2/generated-content/content-019.xht
index 7f85f67..2d2b21c 100644
--- a/css/CSS2/generated-content/content-019.xht
+++ b/css/CSS2/generated-content/content-019.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-015-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-020.xht b/css/CSS2/generated-content/content-020.xht
index 656d1b5..7328cb5 100644
--- a/css/CSS2/generated-content/content-020.xht
+++ b/css/CSS2/generated-content/content-020.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counter()' function with a list-style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-021-ref.html b/css/CSS2/generated-content/content-021-ref.html
new file mode 100644
index 0000000..5e9cd46
--- /dev/null
+++ b/css/CSS2/generated-content/content-021-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if the numbers "0" and "0.0" are in the box below.</p>
+    <div>0<br>0.0</div>
+</body>
diff --git a/css/CSS2/generated-content/content-021.xht b/css/CSS2/generated-content/content-021.xht
index b003a29..cbe53ed 100644
--- a/css/CSS2/generated-content/content-021.xht
+++ b/css/CSS2/generated-content/content-021.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-021-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string value." />
         <style type="text/css">
@@ -20,7 +21,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if there are the numbers "0" and "0.0" in the box below.</p>
+        <p>Test passes if the numbers "0" and "0.0" are in the box below.</p>
         <div id="div1">
             <div></div>
         </div>
diff --git a/css/CSS2/generated-content/content-022-ref.html b/css/CSS2/generated-content/content-022-ref.html
new file mode 100644
index 0000000..a7087f7
--- /dev/null
+++ b/css/CSS2/generated-content/content-022-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are bullets "&#x2022;" and "&#x2022;.&#x2022;" in the box below.</p>
+    <div>&#x2022;<br>&#x2022;.&#x2022;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-022.xht b/css/CSS2/generated-content/content-022.xht
index 11ac0ed..c5adb66 100644
--- a/css/CSS2/generated-content/content-022.xht
+++ b/css/CSS2/generated-content/content-022.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-022-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-023-ref.html b/css/CSS2/generated-content/content-023-ref.html
new file mode 100644
index 0000000..c6f8a19
--- /dev/null
+++ b/css/CSS2/generated-content/content-023-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are circles "&#x25E6;" and "&#x25E6;.&#x25E6;" in the box below.</p>
+    <div>&#x25E6;<br>&#x25E6;.&#x25E6;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-023.xht b/css/CSS2/generated-content/content-023.xht
index 6168a5e..319f04c 100644
--- a/css/CSS2/generated-content/content-023.xht
+++ b/css/CSS2/generated-content/content-023.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-023-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-025.xht b/css/CSS2/generated-content/content-025.xht
index 38e429f..fdc8229 100644
--- a/css/CSS2/generated-content/content-025.xht
+++ b/css/CSS2/generated-content/content-025.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-021-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
@@ -20,7 +21,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if there are numbers "0" and "0.0" in the box below.</p>
+        <p>Test passes if the numbers "0" and "0.0" are in the box below.</p>
         <div id="div1">
             <div></div>
         </div>
diff --git a/css/CSS2/generated-content/content-026-ref.html b/css/CSS2/generated-content/content-026-ref.html
new file mode 100644
index 0000000..c884052
--- /dev/null
+++ b/css/CSS2/generated-content/content-026-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are numbers "00" and "00.00" in the box below.</p>
+    <div>00<br>00.00</div>
+</body>
diff --git a/css/CSS2/generated-content/content-026.xht b/css/CSS2/generated-content/content-026.xht
index a822292..54642cd 100644
--- a/css/CSS2/generated-content/content-026.xht
+++ b/css/CSS2/generated-content/content-026.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-026-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-027-ref.html b/css/CSS2/generated-content/content-027-ref.html
new file mode 100644
index 0000000..eb09da6
--- /dev/null
+++ b/css/CSS2/generated-content/content-027-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are letters "i" and "i.i" in the box below.</p>
+    <div>i<br>i.i</div>
+</body>
diff --git a/css/CSS2/generated-content/content-027.xht b/css/CSS2/generated-content/content-027.xht
index b2bc7c0..8ee1644 100644
--- a/css/CSS2/generated-content/content-027.xht
+++ b/css/CSS2/generated-content/content-027.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-027-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-028-ref.html b/css/CSS2/generated-content/content-028-ref.html
new file mode 100644
index 0000000..78883ad
--- /dev/null
+++ b/css/CSS2/generated-content/content-028-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are letters "I" and "I.I" in the box below.</p>
+    <div>I<br>I.I</div>
+</body>
diff --git a/css/CSS2/generated-content/content-028.xht b/css/CSS2/generated-content/content-028.xht
index 1086c5e..fb36942 100644
--- a/css/CSS2/generated-content/content-028.xht
+++ b/css/CSS2/generated-content/content-028.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-028-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-029-ref.html b/css/CSS2/generated-content/content-029-ref.html
new file mode 100644
index 0000000..7e8e6c6
--- /dev/null
+++ b/css/CSS2/generated-content/content-029-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are greek letters "&#x03B1;" and "&#x03B1;.&#x03B1;" in the box below.</p>
+    <div>&#x03B1;<br>&#x03B1;.&#x03B1;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-029.xht b/css/CSS2/generated-content/content-029.xht
index eb8b862..d49a216 100644
--- a/css/CSS2/generated-content/content-029.xht
+++ b/css/CSS2/generated-content/content-029.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-029-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-030-ref.html b/css/CSS2/generated-content/content-030-ref.html
new file mode 100644
index 0000000..2a7a6c2
--- /dev/null
+++ b/css/CSS2/generated-content/content-030-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are letters "a" and "a.a" in the box below.</p>
+    <div>a<br>a.a</div>
+</body>
diff --git a/css/CSS2/generated-content/content-030.xht b/css/CSS2/generated-content/content-030.xht
index 30fdae9..619ae0c 100644
--- a/css/CSS2/generated-content/content-030.xht
+++ b/css/CSS2/generated-content/content-030.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-030-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-031-ref.html b/css/CSS2/generated-content/content-031-ref.html
new file mode 100644
index 0000000..d97b57b
--- /dev/null
+++ b/css/CSS2/generated-content/content-031-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are letters "A" and "A.A" in the box below.</p>
+    <div>A<br>A.A</div>
+</body>
diff --git a/css/CSS2/generated-content/content-031.xht b/css/CSS2/generated-content/content-031.xht
index 2771e04..305c724 100644
--- a/css/CSS2/generated-content/content-031.xht
+++ b/css/CSS2/generated-content/content-031.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-031-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-032-ref.html b/css/CSS2/generated-content/content-032-ref.html
new file mode 100644
index 0000000..55ea7be
--- /dev/null
+++ b/css/CSS2/generated-content/content-032-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are Armenian characters "&#x0531;" and "&#x0531;.&#x0531;" in the box below.</p>
+    <div>&#x0531;<br>&#x0531;.&#x0531;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-032.xht b/css/CSS2/generated-content/content-032.xht
index 998223d..4e74b31 100644
--- a/css/CSS2/generated-content/content-032.xht
+++ b/css/CSS2/generated-content/content-032.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-032-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
@@ -21,7 +22,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if there are Armenian characters "&#x0561;" and "&#x0561;.&#x0561;" or "&#x0531;" and "&#x0531;.&#x0531;" in the box below.</p>
+        <p>Test passes if there are Armenian characters "&#x0531;" and "&#x0531;.&#x0531;" in the box below.</p>
         <div id="div1">
             <div></div>
         </div>
diff --git a/css/CSS2/generated-content/content-033-ref.html b/css/CSS2/generated-content/content-033-ref.html
new file mode 100644
index 0000000..fc0aa9e
--- /dev/null
+++ b/css/CSS2/generated-content/content-033-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if there are Georgian characters "&#x10d0;" and "&#x10d0;.&#x10d0;" in the box below.</p>
+    <div>&#x10d0;<br>&#x10d0;.&#x10d0;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-033.xht b/css/CSS2/generated-content/content-033.xht
index f915c60..2d41a9f 100644
--- a/css/CSS2/generated-content/content-033.xht
+++ b/css/CSS2/generated-content/content-033.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-033-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-034.xht b/css/CSS2/generated-content/content-034.xht
index d3c9685..a49776e 100644
--- a/css/CSS2/generated-content/content-034.xht
+++ b/css/CSS2/generated-content/content-034.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-030-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-035.xht b/css/CSS2/generated-content/content-035.xht
index 53d126e..4595b49 100644
--- a/css/CSS2/generated-content/content-035.xht
+++ b/css/CSS2/generated-content/content-035.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-031-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-036.xht b/css/CSS2/generated-content/content-036.xht
index f3c8a44..97808a6 100644
--- a/css/CSS2/generated-content/content-036.xht
+++ b/css/CSS2/generated-content/content-036.xht
@@ -5,7 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
-        <link rel="match" href="../reference/no-red-on-blank-page-ref.xht"/>
+        <link rel="match" href="content-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'counters()' function with a string and a list style." />
         <style type="text/css">
@@ -16,9 +16,10 @@
                 counter-reset: test;
                 color: red;
             }
-            #div1
+            div
             {
                 border: 2px solid black;
+                height: 30px;
             }
         </style>
     </head>
diff --git a/css/CSS2/generated-content/content-037-ref.html b/css/CSS2/generated-content/content-037-ref.html
new file mode 100644
index 0000000..39b5ba9
--- /dev/null
+++ b/css/CSS2/generated-content/content-037-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the words "PASS PASS" appear in the box below.</p>
+    <table>
+        <tr>
+            <td>PASS PASS</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-037.xht b/css/CSS2/generated-content/content-037.xht
index d765225..f0f45e1 100644
--- a/css/CSS2/generated-content/content-037.xht
+++ b/css/CSS2/generated-content/content-037.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-037-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'abbr'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-038.xht b/css/CSS2/generated-content/content-038.xht
index c975fd2..ccb9ab4 100644
--- a/css/CSS2/generated-content/content-038.xht
+++ b/css/CSS2/generated-content/content-038.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'accept-charset'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-039.xht b/css/CSS2/generated-content/content-039.xht
index f04ede1..7b333ce 100644
--- a/css/CSS2/generated-content/content-039.xht
+++ b/css/CSS2/generated-content/content-039.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'accept'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-040-ref.html b/css/CSS2/generated-content/content-040-ref.html
new file mode 100644
index 0000000..b5f7d0d
--- /dev/null
+++ b/css/CSS2/generated-content/content-040-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the letter "P" appears in the box below.</p>
+    <div>P</div>
+</body>
diff --git a/css/CSS2/generated-content/content-040.xht b/css/CSS2/generated-content/content-040.xht
index ec011ec..79791c9 100644
--- a/css/CSS2/generated-content/content-040.xht
+++ b/css/CSS2/generated-content/content-040.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-040-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'accesskey'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(accesskey);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-041-ref.html b/css/CSS2/generated-content/content-041-ref.html
new file mode 100644
index 0000000..c3868b3
--- /dev/null
+++ b/css/CSS2/generated-content/content-041-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "PASS" appears in the box below.</p>
+    <div>PASS</div>
+</body>
diff --git a/css/CSS2/generated-content/content-041.xht b/css/CSS2/generated-content/content-041.xht
index 8bdf075..9f77450 100644
--- a/css/CSS2/generated-content/content-041.xht
+++ b/css/CSS2/generated-content/content-041.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-041-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'action'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-042-ref.html b/css/CSS2/generated-content/content-042-ref.html
new file mode 100644
index 0000000..3a3c43e
--- /dev/null
+++ b/css/CSS2/generated-content/content-042-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the words "center" appear in the box below.</p>
+    <div align="center">center</div>
+</body>
diff --git a/css/CSS2/generated-content/content-042.xht b/css/CSS2/generated-content/content-042.xht
index 2b8d006..0f927cc 100644
--- a/css/CSS2/generated-content/content-042.xht
+++ b/css/CSS2/generated-content/content-042.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-042-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'align'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-043-ref.html b/css/CSS2/generated-content/content-043-ref.html
new file mode 100644
index 0000000..42f7ab0
--- /dev/null
+++ b/css/CSS2/generated-content/content-043-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        color: green;
+    }
+</style>
+<body>
+	<div>#ffff00</div>
+    <p>Test passes if there is the text "#ffff00" above.</p>
+</body>
diff --git a/css/CSS2/generated-content/content-043.xht b/css/CSS2/generated-content/content-043.xht
index 5d93f31..3ecf847 100644
--- a/css/CSS2/generated-content/content-043.xht
+++ b/css/CSS2/generated-content/content-043.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-043-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'alink'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-046.xht b/css/CSS2/generated-content/content-046.xht
index 55b230e..b376e3a 100644
--- a/css/CSS2/generated-content/content-046.xht
+++ b/css/CSS2/generated-content/content-046.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-037-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'axis'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-047-ref.html b/css/CSS2/generated-content/content-047-ref.html
new file mode 100644
index 0000000..317170f
--- /dev/null
+++ b/css/CSS2/generated-content/content-047-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    body {
+        margin: 0px;
+    }
+    .test {
+        color: green;
+    }
+    p {
+        margin-left: 8px;
+    }
+</style>
+<body>
+    <div class="test">PASS PASS</div>
+    <p>Test passes if the words "PASS PASS" appear above this text.</p>
+</body>
diff --git a/css/CSS2/generated-content/content-047.xht b/css/CSS2/generated-content/content-047.xht
index 3c8ef21..2b575f8 100644
--- a/css/CSS2/generated-content/content-047.xht
+++ b/css/CSS2/generated-content/content-047.xht
@@ -5,17 +5,23 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'background'." />
         <style type="text/css">
-            body:before
-            {
+            body:before {
                 content: attr(background);
                 color: green;
             }
+            body {
+                margin: 0px;
+            }
+            p {
+                margin-left: 8px;
+            }
         </style>
     </head>
     <body background="PASS PASS">
-        <p>Test passes if only the words "PASS PASS" appear above.</p>
+        <p>Test passes if the words "PASS PASS" appear above this text.</p>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-048-ref.html b/css/CSS2/generated-content/content-048-ref.html
new file mode 100644
index 0000000..e871e3b
--- /dev/null
+++ b/css/CSS2/generated-content/content-048-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+	body {
+		background: #ffff00;
+	}
+    div {
+        color: green;
+    }
+</style>
+<body>
+	<div>#ffff00</div>
+    <p>Test passes if there is the text "#ffff00" above.</p>
+</body>
diff --git a/css/CSS2/generated-content/content-048.xht b/css/CSS2/generated-content/content-048.xht
index fb906d6..9a1e66f 100644
--- a/css/CSS2/generated-content/content-048.xht
+++ b/css/CSS2/generated-content/content-048.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-048-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'bgcolor'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-050-ref.html b/css/CSS2/generated-content/content-050-ref.html
new file mode 100644
index 0000000..bf0b0a8
--- /dev/null
+++ b/css/CSS2/generated-content/content-050-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+	table:before {
+		content: "1";
+	}
+    table {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+	<p>Test passes if the number "1" appears in the box below.</p>
+	<table>
+	    <tr>
+	        <td></td>
+	    </tr>
+	</table>
+</body>
diff --git a/css/CSS2/generated-content/content-050.xht b/css/CSS2/generated-content/content-050.xht
index 89d2841..623ce5a 100644
--- a/css/CSS2/generated-content/content-050.xht
+++ b/css/CSS2/generated-content/content-050.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-050-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'cellpadding'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-052-ref.html b/css/CSS2/generated-content/content-052-ref.html
new file mode 100644
index 0000000..630898c
--- /dev/null
+++ b/css/CSS2/generated-content/content-052-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if the letter "A" appears in the box below.</p>
+    <table>
+        <tr>
+            <td>A</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-052.xht b/css/CSS2/generated-content/content-052.xht
index 4b1ed88..e155c23 100644
--- a/css/CSS2/generated-content/content-052.xht
+++ b/css/CSS2/generated-content/content-052.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-052-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'char'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-053-ref.html b/css/CSS2/generated-content/content-053-ref.html
new file mode 100644
index 0000000..6f639b1
--- /dev/null
+++ b/css/CSS2/generated-content/content-053-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if the number "1" appears in the box below.</p>
+    <table>
+        <tr>
+            <td>1</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-053.xht b/css/CSS2/generated-content/content-053.xht
index 0cc6b7c..0c85ab6 100644
--- a/css/CSS2/generated-content/content-053.xht
+++ b/css/CSS2/generated-content/content-053.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-053-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'charoff'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-054.xht b/css/CSS2/generated-content/content-054.xht
index befa87b..736453d 100644
--- a/css/CSS2/generated-content/content-054.xht
+++ b/css/CSS2/generated-content/content-054.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'charset'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(charset);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-056.xht b/css/CSS2/generated-content/content-056.xht
index d36a841..c2c25fa 100644
--- a/css/CSS2/generated-content/content-056.xht
+++ b/css/CSS2/generated-content/content-056.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'cite'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(cite);
                 color: green;
             }
-            q
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-057.xht b/css/CSS2/generated-content/content-057.xht
index 0a39b55..38be14e 100644
--- a/css/CSS2/generated-content/content-057.xht
+++ b/css/CSS2/generated-content/content-057.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'class'." />
         <style type="text/css">
@@ -20,7 +21,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if the words "PASS PASS" appears in the box below.</p>
+        <p>Test passes if the words "PASS PASS" appear in the box below.</p>
         <div class="PASS PASS"></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-063-ref.html b/css/CSS2/generated-content/content-063-ref.html
new file mode 100644
index 0000000..84a732b
--- /dev/null
+++ b/css/CSS2/generated-content/content-063-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "green" appears in the box below.</p>
+    <div>green</div>
+</body>
diff --git a/css/CSS2/generated-content/content-063.xht b/css/CSS2/generated-content/content-063.xht
index 5ce5a7a..a5fdada 100644
--- a/css/CSS2/generated-content/content-063.xht
+++ b/css/CSS2/generated-content/content-063.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-063-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'color'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(color);
                 color: green;
             }
-            font
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-065.xht b/css/CSS2/generated-content/content-065.xht
index 9d485a0..46164b4 100644
--- a/css/CSS2/generated-content/content-065.xht
+++ b/css/CSS2/generated-content/content-065.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-053-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'colspan'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-067.xht b/css/CSS2/generated-content/content-067.xht
index 42b6416..513a8fa 100644
--- a/css/CSS2/generated-content/content-067.xht
+++ b/css/CSS2/generated-content/content-067.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'content'." />
         <meta content="PASS PASS" />
diff --git a/css/CSS2/generated-content/content-068-ref.html b/css/CSS2/generated-content/content-068-ref.html
new file mode 100644
index 0000000..e3c1796
--- /dev/null
+++ b/css/CSS2/generated-content/content-068-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the number "10" appears in the box below.</p>
+    <div>10</div>
+</body>
diff --git a/css/CSS2/generated-content/content-068.xht b/css/CSS2/generated-content/content-068.xht
index c9eb114..de2a9dc 100644
--- a/css/CSS2/generated-content/content-068.xht
+++ b/css/CSS2/generated-content/content-068.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-068-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'coords'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(coords);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-070-ref.html b/css/CSS2/generated-content/content-070-ref.html
new file mode 100644
index 0000000..2995930
--- /dev/null
+++ b/css/CSS2/generated-content/content-070-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    ins {
+        border: 2px solid black;
+        color: green;
+        text-decoration-color: black;
+    }
+</style>
+<body>
+    <p>Test passes if the text "2000-01-01T00:00:00-08:00" appears in the box below.</p>
+    <ins>2000-01-01T00:00:00-08:00</ins>
+</body>
diff --git a/css/CSS2/generated-content/content-070.xht b/css/CSS2/generated-content/content-070.xht
index 9b162fb..97aa0a2 100644
--- a/css/CSS2/generated-content/content-070.xht
+++ b/css/CSS2/generated-content/content-070.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-070-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'datetime'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-072-ref.html b/css/CSS2/generated-content/content-072-ref.html
new file mode 100644
index 0000000..c8bc326
--- /dev/null
+++ b/css/CSS2/generated-content/content-072-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "defer" appears in the box below.</p>
+    <div>defer</div>
+</body>
diff --git a/css/CSS2/generated-content/content-072.xht b/css/CSS2/generated-content/content-072.xht
index 8fd060e..755a6d5 100644
--- a/css/CSS2/generated-content/content-072.xht
+++ b/css/CSS2/generated-content/content-072.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-072-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'defer'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-073-ref.html b/css/CSS2/generated-content/content-073-ref.html
new file mode 100644
index 0000000..41963db
--- /dev/null
+++ b/css/CSS2/generated-content/content-073-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the letters "ltr" appear in the box below.</p>
+    <div>ltr</div>
+</body>
diff --git a/css/CSS2/generated-content/content-073.xht b/css/CSS2/generated-content/content-073.xht
index d8e0b88..8a3c396 100644
--- a/css/CSS2/generated-content/content-073.xht
+++ b/css/CSS2/generated-content/content-073.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-073-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'dir'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-075-ref.html b/css/CSS2/generated-content/content-075-ref.html
new file mode 100644
index 0000000..2c0b63d
--- /dev/null
+++ b/css/CSS2/generated-content/content-075-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the text "multipart/form-data" appear in the box below.</p>
+    <div>multipart/form-data</div>
+</body>
diff --git a/css/CSS2/generated-content/content-075.xht b/css/CSS2/generated-content/content-075.xht
index 222528c..4f7a685 100644
--- a/css/CSS2/generated-content/content-075.xht
+++ b/css/CSS2/generated-content/content-075.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-075-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'enctype'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-076.xht b/css/CSS2/generated-content/content-076.xht
index d694d53..bb44002 100644
--- a/css/CSS2/generated-content/content-076.xht
+++ b/css/CSS2/generated-content/content-076.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'face'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(face);
                 color: green;
             }
-            font
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-077.xht b/css/CSS2/generated-content/content-077.xht
index 115a9ea..10a8523 100644
--- a/css/CSS2/generated-content/content-077.xht
+++ b/css/CSS2/generated-content/content-077.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-041-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'for'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(for);
                 color: green;
             }
-            label
+            .box
             {
                 border: 2px solid black;
             }
@@ -21,7 +22,7 @@
     </head>
     <body>
         <p>Test passes if the word "PASS" appears in the box below.</p>
-        <div>
+        <div class="box">
             <label for="PASS"></label>
             <div id="PASS"></div>
         </div>
diff --git a/css/CSS2/generated-content/content-080-ref.html b/css/CSS2/generated-content/content-080-ref.html
new file mode 100644
index 0000000..6f5454c
--- /dev/null
+++ b/css/CSS2/generated-content/content-080-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "PASS" appears in the box below.</p>
+    <table>
+        <tr>
+            <td>PASS</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-080.xht b/css/CSS2/generated-content/content-080.xht
index 3936e7d..7dbe2e4 100644
--- a/css/CSS2/generated-content/content-080.xht
+++ b/css/CSS2/generated-content/content-080.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-080-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'headers'." />
         <style type="text/css">
@@ -23,10 +24,10 @@
         <p>Test passes if the word "PASS" appears in the box below.</p>
         <table>
             <tr>
-                <th id="PASS"></th>
+                <td headers="PASS"></td>
             </tr>
             <tr>
-                <td headers="PASS"></td>
+                <th id="PASS"></th>
             </tr>
         </table>
     </body>
diff --git a/css/CSS2/generated-content/content-081-ref.html b/css/CSS2/generated-content/content-081-ref.html
new file mode 100644
index 0000000..7da539a
--- /dev/null
+++ b/css/CSS2/generated-content/content-081-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if the number "10" appears in the box below.</p>
+    <table>
+        <tr>
+            <td>10</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-081.xht b/css/CSS2/generated-content/content-081.xht
index 21cd304..937a691 100644
--- a/css/CSS2/generated-content/content-081.xht
+++ b/css/CSS2/generated-content/content-081.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-081-ref.html" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'height'." />
         <style type="text/css">
             td:before
diff --git a/css/CSS2/generated-content/content-082-ref.html b/css/CSS2/generated-content/content-082-ref.html
new file mode 100644
index 0000000..db3242b
--- /dev/null
+++ b/css/CSS2/generated-content/content-082-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the character "#" appears in the box below.</p>
+    <a href="#"><div>#</div></a>
+</body>
diff --git a/css/CSS2/generated-content/content-082.xht b/css/CSS2/generated-content/content-082.xht
index 3713792..9c51a98 100644
--- a/css/CSS2/generated-content/content-082.xht
+++ b/css/CSS2/generated-content/content-082.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-082-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'href'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(href);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-083-ref.html b/css/CSS2/generated-content/content-083-ref.html
new file mode 100644
index 0000000..375572b
--- /dev/null
+++ b/css/CSS2/generated-content/content-083-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the letters "aa" appear in the box below.</p>
+    <div>aa</div>
+</body>
diff --git a/css/CSS2/generated-content/content-083.xht b/css/CSS2/generated-content/content-083.xht
index 63f7388..833a4b6 100644
--- a/css/CSS2/generated-content/content-083.xht
+++ b/css/CSS2/generated-content/content-083.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-083-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'hreflang'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(hreflang);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-085.xht b/css/CSS2/generated-content/content-085.xht
index fadf73b..dd10cbb 100644
--- a/css/CSS2/generated-content/content-085.xht
+++ b/css/CSS2/generated-content/content-085.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property correctly handles the 'attr()' function when calling the attribute 'http-equiv'." />
         <meta content="" http-equiv="PASS PASS" />
diff --git a/css/CSS2/generated-content/content-086.xht b/css/CSS2/generated-content/content-086.xht
index b790bed..0f226f5 100644
--- a/css/CSS2/generated-content/content-086.xht
+++ b/css/CSS2/generated-content/content-086.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-041-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'id'." />
         <style type="text/css">
@@ -20,7 +21,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if the word "PASS" appear in the box below.</p>
+        <p>Test passes if the word "PASS" appears in the box below.</p>
         <div id="PASS"></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-089-ref.html b/css/CSS2/generated-content/content-089-ref.html
new file mode 100644
index 0000000..375572b
--- /dev/null
+++ b/css/CSS2/generated-content/content-089-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the letters "aa" appear in the box below.</p>
+    <div>aa</div>
+</body>
diff --git a/css/CSS2/generated-content/content-089.xht b/css/CSS2/generated-content/content-089.xht
index 51ad578..cd9d500 100644
--- a/css/CSS2/generated-content/content-089.xht
+++ b/css/CSS2/generated-content/content-089.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-089-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'lang'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-090-ref.html b/css/CSS2/generated-content/content-090-ref.html
new file mode 100644
index 0000000..a8d8619
--- /dev/null
+++ b/css/CSS2/generated-content/content-090-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "ecmascript" appears in the box below.</p>
+    <div>ecmascript</div>
+</body>
diff --git a/css/CSS2/generated-content/content-090.xht b/css/CSS2/generated-content/content-090.xht
index d4f15c5..982023d 100644
--- a/css/CSS2/generated-content/content-090.xht
+++ b/css/CSS2/generated-content/content-090.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-090-ref.html" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'language'." />
         <style type="text/css">
             script:before
diff --git a/css/CSS2/generated-content/content-091-ref.html b/css/CSS2/generated-content/content-091-ref.html
new file mode 100644
index 0000000..b9cb6dd
--- /dev/null
+++ b/css/CSS2/generated-content/content-091-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        color: green;
+    }
+</style>
+<body>
+    <div>green</div>
+    <p>Test passes if the word "green" appears above.</p>
+</body>
diff --git a/css/CSS2/generated-content/content-091.xht b/css/CSS2/generated-content/content-091.xht
index cb6a8de..1c1b352 100644
--- a/css/CSS2/generated-content/content-091.xht
+++ b/css/CSS2/generated-content/content-091.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-091-ref.html" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'link'." />
         <style type="text/css">
             body:before
diff --git a/css/CSS2/generated-content/content-096-ref.html b/css/CSS2/generated-content/content-096-ref.html
new file mode 100644
index 0000000..9518fe0
--- /dev/null
+++ b/css/CSS2/generated-content/content-096-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    body {
+        margin: 0px;
+    }
+    .test {
+        color: green;
+    }
+    p {
+        margin-left: 8px;
+    }
+</style>
+<body>
+    <div class="test">all</div>
+    <p>Test passes if the word "all" appears above this text.</p>
+</body>
diff --git a/css/CSS2/generated-content/content-096.xht b/css/CSS2/generated-content/content-096.xht
index 24dbcdd..a968386 100644
--- a/css/CSS2/generated-content/content-096.xht
+++ b/css/CSS2/generated-content/content-096.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-096-ref.html" />
         <link media="all" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'media'." />
diff --git a/css/CSS2/generated-content/content-097-ref.html b/css/CSS2/generated-content/content-097-ref.html
new file mode 100644
index 0000000..d68b795
--- /dev/null
+++ b/css/CSS2/generated-content/content-097-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "get" appears in the box below.</p>
+    <div>get</div>
+</body>
diff --git a/css/CSS2/generated-content/content-097.xht b/css/CSS2/generated-content/content-097.xht
index f6de010..a95a303 100644
--- a/css/CSS2/generated-content/content-097.xht
+++ b/css/CSS2/generated-content/content-097.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-097-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'method'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-099.xht b/css/CSS2/generated-content/content-099.xht
index 60549d8..05bd846 100644
--- a/css/CSS2/generated-content/content-099.xht
+++ b/css/CSS2/generated-content/content-099.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-041-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'name'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(name);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-100-ref.html b/css/CSS2/generated-content/content-100-ref.html
new file mode 100644
index 0000000..91b01ce
--- /dev/null
+++ b/css/CSS2/generated-content/content-100-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "nohref" appears in the box below.</p>
+    <div>nohref</div>
+</body>
diff --git a/css/CSS2/generated-content/content-100.xht b/css/CSS2/generated-content/content-100.xht
index e928b35..f067992 100644
--- a/css/CSS2/generated-content/content-100.xht
+++ b/css/CSS2/generated-content/content-100.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-100-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'nohref'." />
         <style type="text/css">
@@ -21,7 +22,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if the word "nohref" appear in the box below.</p>
+        <p>Test passes if the word "nohref" appears in the box below.</p>
         <div>
             <map id="test">
                 <area alt="" nohref="nohref" />
diff --git a/css/CSS2/generated-content/content-103-ref.html b/css/CSS2/generated-content/content-103-ref.html
new file mode 100644
index 0000000..d538ab4
--- /dev/null
+++ b/css/CSS2/generated-content/content-103-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if the word "nowrap" appears in the box below.</p>
+    <table>
+        <tr>
+            <td>nowrap</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-103.xht b/css/CSS2/generated-content/content-103.xht
index 9b22daa..b8380e4 100644
--- a/css/CSS2/generated-content/content-103.xht
+++ b/css/CSS2/generated-content/content-103.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-103-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'nowrap'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-105.xht b/css/CSS2/generated-content/content-105.xht
index 18d0974..25eb779 100644
--- a/css/CSS2/generated-content/content-105.xht
+++ b/css/CSS2/generated-content/content-105.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="../../reference/only_pass_parens_semicolon.html" />
         <meta name="flags" content="dom" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'onblur'." />
         <meta http-equiv="Content-Script-Type" content="text/javascript" />
@@ -14,7 +15,7 @@
                 content: attr(onblur);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-109.xht b/css/CSS2/generated-content/content-109.xht
index b9e01f2..030d38d 100644
--- a/css/CSS2/generated-content/content-109.xht
+++ b/css/CSS2/generated-content/content-109.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="../../reference/only_pass_parens_semicolon.html" />
         <meta name="flags" content="dom" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'onfocus'." />
         <meta http-equiv="Content-Script-Type" content="text/javascript" />
@@ -14,7 +15,7 @@
                 content: attr(onfocus);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-113-ref.html b/css/CSS2/generated-content/content-113-ref.html
new file mode 100644
index 0000000..c17bdd7
--- /dev/null
+++ b/css/CSS2/generated-content/content-113-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        color: green;
+    }
+</style>
+<body>
+    <div>PASS();</div>
+    <p>Test passes if only the word "PASS();" appears above. Fail if there is any other additional text.</p>
+</body>
diff --git a/css/CSS2/generated-content/content-113.xht b/css/CSS2/generated-content/content-113.xht
index 7de819e..559b795 100644
--- a/css/CSS2/generated-content/content-113.xht
+++ b/css/CSS2/generated-content/content-113.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-113-ref.html" />
         <meta name="flags" content="dom" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'onload'." />
         <meta http-equiv="Content-Script-Type" content="text/javascript" />
diff --git a/css/CSS2/generated-content/content-122.xht b/css/CSS2/generated-content/content-122.xht
index 3cf97e3..a32ce89 100644
--- a/css/CSS2/generated-content/content-122.xht
+++ b/css/CSS2/generated-content/content-122.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-113-ref.html" />
         <meta name="flags" content="dom" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'onunload'." />
         <meta http-equiv="Content-Script-Type" content="text/javascript" />
diff --git a/css/CSS2/generated-content/content-123.xht b/css/CSS2/generated-content/content-123.xht
index 76d726d..94399d1 100644
--- a/css/CSS2/generated-content/content-123.xht
+++ b/css/CSS2/generated-content/content-123.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'profile'." />
         <style type="text/css">
@@ -20,6 +21,6 @@
         </style>
     </head>
     <body>
-        <p>Test passes if only the words "PASS PASS" appear above.</p>
+        <p>Test passes if the words "PASS PASS" appear above this text.</p>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-126-ref.html b/css/CSS2/generated-content/content-126-ref.html
new file mode 100644
index 0000000..74f46da
--- /dev/null
+++ b/css/CSS2/generated-content/content-126-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if only the word "Alternate" appears in the box below.</p>
+    <div>Alternate</div>
+</body>
diff --git a/css/CSS2/generated-content/content-126.xht b/css/CSS2/generated-content/content-126.xht
index a4273a8..6157e81 100644
--- a/css/CSS2/generated-content/content-126.xht
+++ b/css/CSS2/generated-content/content-126.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-126-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'rel'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(rel);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-127.xht b/css/CSS2/generated-content/content-127.xht
index bc3f7e6..a9b0448 100644
--- a/css/CSS2/generated-content/content-127.xht
+++ b/css/CSS2/generated-content/content-127.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-126-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'rev'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(rev);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-129.xht b/css/CSS2/generated-content/content-129.xht
index 648f520..df6691b 100644
--- a/css/CSS2/generated-content/content-129.xht
+++ b/css/CSS2/generated-content/content-129.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-053-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'rowspan'." />
         <style type="text/css">
@@ -20,7 +21,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if only the number "1" appears in the box below.</p>
+        <p>Test passes if the number "1" appears in the box below.</p>
         <table>
             <tr>
                 <td rowspan="1"></td>
diff --git a/css/CSS2/generated-content/content-131.xht b/css/CSS2/generated-content/content-131.xht
index 3e7c530..ad6d628 100644
--- a/css/CSS2/generated-content/content-131.xht
+++ b/css/CSS2/generated-content/content-131.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'scheme'." />
         <meta content="" scheme="PASS PASS" />
@@ -21,6 +22,6 @@
         </style>
     </head>
     <body>
-        <p>Test passes if only the words "PASS PASS" appear above this text.</p>
+        <p>Test passes if the words "PASS PASS" appear above this text.</p>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-132-ref.html b/css/CSS2/generated-content/content-132-ref.html
new file mode 100644
index 0000000..6c1736b
--- /dev/null
+++ b/css/CSS2/generated-content/content-132-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if only the word "col" appears in the box below.</p>
+    <table>
+        <tr>
+            <td>col</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-132.xht b/css/CSS2/generated-content/content-132.xht
index da7701e..389966d 100644
--- a/css/CSS2/generated-content/content-132.xht
+++ b/css/CSS2/generated-content/content-132.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-132-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'scope'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-135-ref.html b/css/CSS2/generated-content/content-135-ref.html
new file mode 100644
index 0000000..c1be10b
--- /dev/null
+++ b/css/CSS2/generated-content/content-135-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if only the word "circle" appears in the box below.</p>
+    <div>circle</div>
+</body>
diff --git a/css/CSS2/generated-content/content-135.xht b/css/CSS2/generated-content/content-135.xht
index 9fdce20..3acddbd 100644
--- a/css/CSS2/generated-content/content-135.xht
+++ b/css/CSS2/generated-content/content-135.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-135-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'shape'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(shape);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-136-ref.html b/css/CSS2/generated-content/content-136-ref.html
new file mode 100644
index 0000000..2ec806a
--- /dev/null
+++ b/css/CSS2/generated-content/content-136-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    font {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if the number "5" appears in the box below.</p>
+    <div><font size="5">5</font></div>
+</body>
diff --git a/css/CSS2/generated-content/content-136.xht b/css/CSS2/generated-content/content-136.xht
index c4d9531..97b3691 100644
--- a/css/CSS2/generated-content/content-136.xht
+++ b/css/CSS2/generated-content/content-136.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-136-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'size'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-138.xht b/css/CSS2/generated-content/content-138.xht
index 836d5fb..21e1c16 100644
--- a/css/CSS2/generated-content/content-138.xht
+++ b/css/CSS2/generated-content/content-138.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'src'." />
         <style type="text/css">
@@ -21,7 +22,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if only the words "PASS PASS" appear in the box below.</p>
+        <p>Test passes if the words "PASS PASS" appear in the box below.</p>
         <script src="PASS PASS" type="text/javascript"></script>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-141-ref.html b/css/CSS2/generated-content/content-141-ref.html
new file mode 100644
index 0000000..9a5d248
--- /dev/null
+++ b/css/CSS2/generated-content/content-141-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the words "color: green;" appear in the box below.</p>
+    <div>color: green;</div>
+</body>
diff --git a/css/CSS2/generated-content/content-141.xht b/css/CSS2/generated-content/content-141.xht
index 9f0b207..1a384c0 100644
--- a/css/CSS2/generated-content/content-141.xht
+++ b/css/CSS2/generated-content/content-141.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-141-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'style'." />
         <meta http-equiv="Content-Style-Type" content="text/css" />
diff --git a/css/CSS2/generated-content/content-143-ref.html b/css/CSS2/generated-content/content-143-ref.html
new file mode 100644
index 0000000..6a4e3dc
--- /dev/null
+++ b/css/CSS2/generated-content/content-143-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the number "5" appears in the box below.</p>
+    <div>5</div>
+</body>
diff --git a/css/CSS2/generated-content/content-143.xht b/css/CSS2/generated-content/content-143.xht
index 001b572..ff6b235 100644
--- a/css/CSS2/generated-content/content-143.xht
+++ b/css/CSS2/generated-content/content-143.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-143-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'tabindex'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(tabindex);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-144-ref.html b/css/CSS2/generated-content/content-144-ref.html
new file mode 100644
index 0000000..f89a44c
--- /dev/null
+++ b/css/CSS2/generated-content/content-144-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the word "_blank" appears in the box below.</p>
+    <div>_blank</div>
+</body>
diff --git a/css/CSS2/generated-content/content-144.xht b/css/CSS2/generated-content/content-144.xht
index 71be8cd..3ca997a 100644
--- a/css/CSS2/generated-content/content-144.xht
+++ b/css/CSS2/generated-content/content-144.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-144-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'target'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(target);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-145.xht b/css/CSS2/generated-content/content-145.xht
index 1632ff1..e36d20f 100644
--- a/css/CSS2/generated-content/content-145.xht
+++ b/css/CSS2/generated-content/content-145.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-091-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'text'." />
         <style type="text/css">
@@ -19,6 +20,6 @@
         </style>
     </head>
     <body text="green">
-        <p>Test passes if there is only the word "green" above.</p>
+        <p>Test passes if the word "green" appears above.</p>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-146.xht b/css/CSS2/generated-content/content-146.xht
index 3c2d4fc..fdc5166 100644
--- a/css/CSS2/generated-content/content-146.xht
+++ b/css/CSS2/generated-content/content-146.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'title'." />
         <style type="text/css">
@@ -13,7 +14,7 @@
                 content: attr(title);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
diff --git a/css/CSS2/generated-content/content-147-ref.html b/css/CSS2/generated-content/content-147-ref.html
new file mode 100644
index 0000000..8c05613
--- /dev/null
+++ b/css/CSS2/generated-content/content-147-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the words "text/plain" appear in the box below.</p>
+    <div>text/plain</div>
+</body>
diff --git a/css/CSS2/generated-content/content-147.xht b/css/CSS2/generated-content/content-147.xht
index a1dc049..12db407 100644
--- a/css/CSS2/generated-content/content-147.xht
+++ b/css/CSS2/generated-content/content-147.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-147-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'type'." />
         <style type="text/css">
@@ -13,14 +14,14 @@
                 content: attr(type);
                 color: green;
             }
-            a
+            div
             {
                 border: 2px solid black;
             }
         </style>
     </head>
     <body>
-        <p>Test passes if the words "text/plain"" appear in the box below.</p>
+        <p>Test passes if the words "text/plain" appear in the box below.</p>
         <div>
             <a type="text/plain"></a>
         </div>
diff --git a/css/CSS2/generated-content/content-149-ref.html b/css/CSS2/generated-content/content-149-ref.html
new file mode 100644
index 0000000..55641cc
--- /dev/null
+++ b/css/CSS2/generated-content/content-149-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    td {
+        color: green;
+        border: 2px solid black;
+    }
+</style>
+<body>
+    <p>Test passes if only the word "baseline" appears in the box below.</p>
+    <table>
+        <tr>
+            <td>baseline</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/CSS2/generated-content/content-149.xht b/css/CSS2/generated-content/content-149.xht
index 2685e6c..ab98d48 100644
--- a/css/CSS2/generated-content/content-149.xht
+++ b/css/CSS2/generated-content/content-149.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-149-ref.html" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'valign'." />
         <style type="text/css">
             td:before
diff --git a/css/CSS2/generated-content/content-150-ref.html b/css/CSS2/generated-content/content-150-ref.html
new file mode 100644
index 0000000..2a24d70
--- /dev/null
+++ b/css/CSS2/generated-content/content-150-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    li {
+        color: green;
+        border: 2px solid black;
+        display: block;
+    }
+</style>
+<body>
+    <p>Test passes if only the number "1" appears in the box below.</p>
+    <ol>
+        <li>1</li>
+    </ol>
+</body>
diff --git a/css/CSS2/generated-content/content-150.xht b/css/CSS2/generated-content/content-150.xht
index a82d07d..258fb99 100644
--- a/css/CSS2/generated-content/content-150.xht
+++ b/css/CSS2/generated-content/content-150.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-150-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'value'." />
         <style type="text/css">
@@ -21,7 +22,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if only the number "1" appear in the box below.</p>
+        <p>Test passes if only the number "1" appears in the box below.</p>
         <ol>
             <li value="1"></li>
         </ol>
diff --git a/css/CSS2/generated-content/content-152.xht b/css/CSS2/generated-content/content-152.xht
index 7da46d4..78965fb 100644
--- a/css/CSS2/generated-content/content-152.xht
+++ b/css/CSS2/generated-content/content-152.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'version'." />
         <style type="text/css">
@@ -16,6 +17,6 @@
         </style>
     </head>
     <body>
-        <p>Test passes if the words "PASS PASS" appear above.</p>
+        <p>Test passes if the words "PASS PASS" appear above this text.</p>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-153.xht b/css/CSS2/generated-content/content-153.xht
index 27e18de..36c0344 100644
--- a/css/CSS2/generated-content/content-153.xht
+++ b/css/CSS2/generated-content/content-153.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-091-ref.html" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'vlink'." />
         <style type="text/css">
             body:before
diff --git a/css/CSS2/generated-content/content-155-ref.html b/css/CSS2/generated-content/content-155-ref.html
new file mode 100644
index 0000000..373e467
--- /dev/null
+++ b/css/CSS2/generated-content/content-155-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    pre {
+        border: 2px solid black;
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the number "10" appears in the box below.</p>
+    <pre>10</pre>
+</body>
diff --git a/css/CSS2/generated-content/content-155.xht b/css/CSS2/generated-content/content-155.xht
index 8bf60b1..7821cad 100644
--- a/css/CSS2/generated-content/content-155.xht
+++ b/css/CSS2/generated-content/content-155.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-155-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles the 'attr()' function when calling the attribute 'width'." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-156-ref.html b/css/CSS2/generated-content/content-156-ref.html
new file mode 100644
index 0000000..1dab980
--- /dev/null
+++ b/css/CSS2/generated-content/content-156-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid blue;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a quote (") in the blue box below.</p>
+    <div>"</div>
+</body>
diff --git a/css/CSS2/generated-content/content-156.xht b/css/CSS2/generated-content/content-156.xht
index c190e1b..6dcdee9 100644
--- a/css/CSS2/generated-content/content-156.xht
+++ b/css/CSS2/generated-content/content-156.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-156-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles an 'open-quote' value." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-157.xht b/css/CSS2/generated-content/content-157.xht
index 783a7dc..f42ee59 100644
--- a/css/CSS2/generated-content/content-157.xht
+++ b/css/CSS2/generated-content/content-157.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-156-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'close-quote' value." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-158-ref.html b/css/CSS2/generated-content/content-158-ref.html
new file mode 100644
index 0000000..f54324d
--- /dev/null
+++ b/css/CSS2/generated-content/content-158-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid blue;
+        height: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if there is a single quote (') in the blue box below.</p>
+    <div>'</div>
+</body>
diff --git a/css/CSS2/generated-content/content-158.xht b/css/CSS2/generated-content/content-158.xht
index 84cf084..d52e18b 100644
--- a/css/CSS2/generated-content/content-158.xht
+++ b/css/CSS2/generated-content/content-158.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-158-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'no-open-quote' value." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-159-ref.html b/css/CSS2/generated-content/content-159-ref.html
new file mode 100644
index 0000000..bd17b1f
--- /dev/null
+++ b/css/CSS2/generated-content/content-159-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: 2px solid blue;
+    }
+</style>
+<body>
+    <p>Test passes if there are 3 quotes (" ' ") in the blue box.</p>
+    <div>" ' "</div>
+</body>
diff --git a/css/CSS2/generated-content/content-159.xht b/css/CSS2/generated-content/content-159.xht
index e0e65a4..de8ec37 100644
--- a/css/CSS2/generated-content/content-159.xht
+++ b/css/CSS2/generated-content/content-159.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-159-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'content' property properly handles a 'no-close-quote' value." />
         <style type="text/css">
@@ -24,7 +25,7 @@
             {
                 content: no-close-quote;
             }
-            #span1
+            div
             {
                 border: 2px solid blue;
             }
diff --git a/css/CSS2/generated-content/content-attr-001.xht b/css/CSS2/generated-content/content-attr-001.xht
index 14a7d4b..636d7ba 100644
--- a/css/CSS2/generated-content/content-attr-001.xht
+++ b/css/CSS2/generated-content/content-attr-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Content property missing attr(x)</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="../reference/ref-nothing-below.xht" />
         <meta name="flags" content="" />
         <meta name="assert" content="If attribute (x) does not exist then an empty string is returned for the attr(x) value." />
         <style type="text/css">
@@ -14,7 +15,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if there is nothing displayed below.</p>
+        <p>Test passes if there is nothing below.</p>
         <div></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-attr-case-001.html b/css/CSS2/generated-content/content-attr-case-001.html
index 21361c3..2ba49f9 100644
--- a/css/CSS2/generated-content/content-attr-case-001.html
+++ b/css/CSS2/generated-content/content-attr-case-001.html
@@ -4,12 +4,12 @@
         <title>CSS Test: Content property attr(x) case sensitivity</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/">
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content">
+        <link rel="match" href="../../reference/pass_if_pass_below.html" />
         <meta name="flags" content="HTMLonly">
         <meta name="assert" content="The attr(x) function selects the attribute even when case does not match.">
         <style type="text/css">
             div:before
             {
-                color: green;
                 content: attr(Title);
             }
         </style>
diff --git a/css/CSS2/generated-content/content-attr-case-002.xht b/css/CSS2/generated-content/content-attr-case-002.xht
index 46632f1..d3126ba 100644
--- a/css/CSS2/generated-content/content-attr-case-002.xht
+++ b/css/CSS2/generated-content/content-attr-case-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: content attr(x) case sensitivity</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="../reference/no-red-on-blank-page-ref.xht" />
         <meta name="flags" content="nonHTML" />
         <meta name="assert" content="Verify in XHTML that attr(x) does not select the attribute when the case does not match" />
         <style type="text/css">
@@ -16,7 +17,7 @@
         </style>
     </head>
     <body>
-        <div>Test Passes if there is no red visible on the page.</div>
+        <p>Test passes if there is no red visible on the page.</p>
         <div id="test" title="FAIL"></div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/generated-content/content-auto-reset-001-ref.html b/css/CSS2/generated-content/content-auto-reset-001-ref.html
new file mode 100644
index 0000000..3a2e1d7
--- /dev/null
+++ b/css/CSS2/generated-content/content-auto-reset-001-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>Test passes if there is the number '0' below.</p>
+    <div>0</div>
+</body>
diff --git a/css/CSS2/generated-content/content-auto-reset-001.xht b/css/CSS2/generated-content/content-auto-reset-001.xht
index c647603..6fc8adb 100644
--- a/css/CSS2/generated-content/content-auto-reset-001.xht
+++ b/css/CSS2/generated-content/content-auto-reset-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Content property on out of scope counter</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-auto-reset-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="If content refers to a counter that is not in scope, it is assumed that a counter-reset has occurred and the counter is reset to zero." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-newline-001-ref.html b/css/CSS2/generated-content/content-newline-001-ref.html
new file mode 100644
index 0000000..22d7794
--- /dev/null
+++ b/css/CSS2/generated-content/content-newline-001-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        height: 100px;
+        width: 200px;
+    }
+</style>
+<body>
+    <p>Test passes if there are exactly two lines of text below.</p>
+    <div>This text<br />should be on two lines.</div>
+</body>
diff --git a/css/CSS2/generated-content/content-newline-001.xht b/css/CSS2/generated-content/content-newline-001.xht
index 5823940..2e1a3bd 100644
--- a/css/CSS2/generated-content/content-newline-001.xht
+++ b/css/CSS2/generated-content/content-newline-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Content property \A creates newline</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-newline-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="A '\A' creates a newline for strings in the content property." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-white-space-001-ref.html b/css/CSS2/generated-content/content-white-space-001-ref.html
new file mode 100644
index 0000000..cbc297e
--- /dev/null
+++ b/css/CSS2/generated-content/content-white-space-001-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: solid green;
+        width: 25em;
+    }
+    #test {
+    	border: solid blue;
+    }
+</style>
+<body>
+    <p>Test passes if the text in the green box and the blue box have the same spacing between words and the lines wrap at the same point.</p>
+    <div>This text<br />should be on two lines.</div>
+    <div id="test">This text<br />should be on two lines.</div>
+</body>
diff --git a/css/CSS2/generated-content/content-white-space-001.xht b/css/CSS2/generated-content/content-white-space-001.xht
index 2fe179f..9269871 100644
--- a/css/CSS2/generated-content/content-white-space-001.xht
+++ b/css/CSS2/generated-content/content-white-space-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Content property and white-space: pre-line</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-white-space-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="'white-space: pre-line' applies to generated string content." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-white-space-002-ref.html b/css/CSS2/generated-content/content-white-space-002-ref.html
new file mode 100644
index 0000000..62b54e2
--- /dev/null
+++ b/css/CSS2/generated-content/content-white-space-002-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: solid silver;
+        margin: 0.5em;
+        padding: 0.2em;
+        color: blue;
+        white-space: pre;
+    }
+</style>
+<body>
+    <p>Test passes if the contents of the two silver boxes are identical.</p>
+    <div>This text
+        should be on
+        four
+        lines.</div>
+    <div>This text
+        should be on
+        four
+        lines.</div>
+</body>
diff --git a/css/CSS2/generated-content/content-white-space-002.xht b/css/CSS2/generated-content/content-white-space-002.xht
index 131489e..52294d2 100644
--- a/css/CSS2/generated-content/content-white-space-002.xht
+++ b/css/CSS2/generated-content/content-white-space-002.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact" />
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-white-space-002-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="'white-space: pre' applies to generated string content." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-white-space-003-ref.html b/css/CSS2/generated-content/content-white-space-003-ref.html
new file mode 100644
index 0000000..283bd81
--- /dev/null
+++ b/css/CSS2/generated-content/content-white-space-003-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: solid silver;
+        margin: 0.5em;
+        padding: 0.2em;
+        color: blue;
+    }
+</style>
+<body>
+    <p>Test passes if the contents of the two silver boxes are identical.</p>
+    <div>This text should be on one line.</div>
+    <div>This text should be on one line.</div>
+</body>
diff --git a/css/CSS2/generated-content/content-white-space-003.xht b/css/CSS2/generated-content/content-white-space-003.xht
index dc72283..83fd21d 100644
--- a/css/CSS2/generated-content/content-white-space-003.xht
+++ b/css/CSS2/generated-content/content-white-space-003.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact" />
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-white-space-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="'white-space: nowrap' applies to generated string content." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/content-white-space-004-ref.html b/css/CSS2/generated-content/content-white-space-004-ref.html
new file mode 100644
index 0000000..5c5fa20
--- /dev/null
+++ b/css/CSS2/generated-content/content-white-space-004-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        border: solid silver;
+        margin: 0.5em;
+        padding: 0.2em;
+        color: blue;
+        width: 10em;
+    }
+</style>
+<body>
+    <p>Test passes if the contents of the two silver boxes are identical.</p>
+    <div>This text should wrap normally.</div>
+    <div>This text should wrap normally.</div>
+</body>
diff --git a/css/CSS2/generated-content/content-white-space-004.xht b/css/CSS2/generated-content/content-white-space-004.xht
index 8356184..34c1862 100644
--- a/css/CSS2/generated-content/content-white-space-004.xht
+++ b/css/CSS2/generated-content/content-white-space-004.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact" />
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#content" />
+        <link rel="match" href="content-white-space-004-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="'white-space: normal' applies to generated string content." />
         <style type="text/css">
diff --git a/css/CSS2/generated-content/counters-hidden-000-ref.html b/css/CSS2/generated-content/counters-hidden-000-ref.html
new file mode 100644
index 0000000..db63888
--- /dev/null
+++ b/css/CSS2/generated-content/counters-hidden-000-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>The following should be identical:</p>
+    <div>0</div>
+    <div>0</div>
+</body>
diff --git a/css/CSS2/generated-content/counters-hidden-000.xht b/css/CSS2/generated-content/counters-hidden-000.xht
index 0cb724c..a3c6248 100644
--- a/css/CSS2/generated-content/counters-hidden-000.xht
+++ b/css/CSS2/generated-content/counters-hidden-000.xht
@@ -7,6 +7,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#counter"/>
+  <link rel="match" href="counters-hidden-000-ref.html" />
   <style type="text/css">
 
   body { white-space: nowrap; }
diff --git a/css/CSS2/generated-content/counters-hidden-001.xht b/css/CSS2/generated-content/counters-hidden-001.xht
index 7c2be2d..b872609 100644
--- a/css/CSS2/generated-content/counters-hidden-001.xht
+++ b/css/CSS2/generated-content/counters-hidden-001.xht
@@ -7,6 +7,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#counter"/>
+  <link rel="match" href="counters-hidden-000-ref.html" />
   <style type="text/css">
 
   body { white-space: nowrap; }
diff --git a/css/CSS2/generated-content/counters-hidden-002-ref.html b/css/CSS2/generated-content/counters-hidden-002-ref.html
new file mode 100644
index 0000000..ce3c9fc
--- /dev/null
+++ b/css/CSS2/generated-content/counters-hidden-002-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>The following should be identical:</p>
+    <div>1</div>
+    <div>1</div>
+</body>
diff --git a/css/CSS2/generated-content/counters-hidden-002.xht b/css/CSS2/generated-content/counters-hidden-002.xht
index 12aaa25..3253f3e 100644
--- a/css/CSS2/generated-content/counters-hidden-002.xht
+++ b/css/CSS2/generated-content/counters-hidden-002.xht
@@ -7,6 +7,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#counter"/>
+  <link rel="match" href="counters-hidden-002-ref.html" />
   <style type="text/css">
 
   body { white-space: nowrap; }
diff --git a/css/CSS2/generated-content/counters-multi-000-ref.html b/css/CSS2/generated-content/counters-multi-000-ref.html
new file mode 100644
index 0000000..09c607f
--- /dev/null
+++ b/css/CSS2/generated-content/counters-multi-000-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>The following should be identical:</p>
+    <div>12</div>
+    <div>12</div>
+</body>
diff --git a/css/CSS2/generated-content/counters-multi-000.xht b/css/CSS2/generated-content/counters-multi-000.xht
index 23eeff4..1c9bab5 100644
--- a/css/CSS2/generated-content/counters-multi-000.xht
+++ b/css/CSS2/generated-content/counters-multi-000.xht
@@ -6,6 +6,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#counter"/>
+  <link rel="match" href="counters-multi-000-ref.html" />
   <style type="text/css">
 
   body { white-space: nowrap; }
diff --git a/css/CSS2/generated-content/counters-multi-001.xht b/css/CSS2/generated-content/counters-multi-001.xht
index 743abce..a67d6ce 100644
--- a/css/CSS2/generated-content/counters-multi-001.xht
+++ b/css/CSS2/generated-content/counters-multi-001.xht
@@ -6,6 +6,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#counter"/>
+  <link rel="match" href="counters-multi-000-ref.html" />
   <style type="text/css">
 
   body { white-space: nowrap; }
diff --git a/css/CSS2/generated-content/counters-order-000-ref.html b/css/CSS2/generated-content/counters-order-000-ref.html
new file mode 100644
index 0000000..341484c
--- /dev/null
+++ b/css/CSS2/generated-content/counters-order-000-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>The following should be identical:</p>
+    <div>0 1 2 3 4 4 4 4 8 9 10 11 12 12 12 12</div>
+    <div>0 1 2 3 4 4 4 4 8 9 10 11 12 12 12 12</div>
+</body>
diff --git a/css/CSS2/generated-content/counters-order-000.xht b/css/CSS2/generated-content/counters-order-000.xht
index ce1d154..7fd5bb6 100644
--- a/css/CSS2/generated-content/counters-order-000.xht
+++ b/css/CSS2/generated-content/counters-order-000.xht
@@ -6,6 +6,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#counter"/>
+  <link rel="match" href="counters-order-000-ref.html" />
   <style type="text/css">
 
   body { white-space: nowrap; }
diff --git a/css/CSS2/generated-content/counters-root-000-ref.html b/css/CSS2/generated-content/counters-root-000-ref.html
new file mode 100644
index 0000000..ecf2645
--- /dev/null
+++ b/css/CSS2/generated-content/counters-root-000-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>The following should be identical:</p>
+    <div>4.8</div>
+    <div>4.8</div>
+</body>
diff --git a/css/CSS2/generated-content/counters-root-000.xht b/css/CSS2/generated-content/counters-root-000.xht
index e8fa6e7..d588241 100644
--- a/css/CSS2/generated-content/counters-root-000.xht
+++ b/css/CSS2/generated-content/counters-root-000.xht
@@ -6,6 +6,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-content"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#counter"/>
+  <link rel="match" href="counters-root-000-ref.html" />
   <style type="text/css">
 
   body { white-space: nowrap; }
diff --git a/css/CSS2/linebox/fractional-line-height.html b/css/CSS2/linebox/fractional-line-height.html
new file mode 100644
index 0000000..0b74807
--- /dev/null
+++ b/css/CSS2/linebox/fractional-line-height.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Container height calculation with fractional line height and fractional child height</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#line-height" title="10.8 Line height calculations: the 'line-height' and 'vertical-align' properties">
+<link rel="match" href="../../reference/nothing.html">
+<style>
+  .container { float:left; overflow:auto; width:50px; }
+  .container > span { display:inline-block; width:10px; }
+</style>
+<p>There should be nothing below.</p>
+
+<!-- None of these should not trigger a vertical scrollbar, because the height
+     of the overflow:auto container is auto, so it should make room for
+     whatever's inside. -->
+
+<div class="container" style="line-height:19.75px;">
+  <span style="height:100.25px;"></span>
+</div>
+
+<div class="container" style="line-height:19.75px;">
+  <span style="height:100.75px;"></span>
+</div>
+
+<div class="container" style="line-height:19.25px;">
+  <span style="height:100.25px;"></span>
+</div>
+
+<div class="container" style="line-height:19.25px;">
+  <span style="height:100.75px;"></span>
+</div>
diff --git a/css/CSS2/linebox/line-height-oof-descendants-001-ref.html b/css/CSS2/linebox/line-height-oof-descendants-001-ref.html
new file mode 100644
index 0000000..c338a1b
--- /dev/null
+++ b/css/CSS2/linebox/line-height-oof-descendants-001-ref.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+p {
+  font-size: 20px;
+  line-height: 0;
+}
+</style>
+<p>Some paragraph</p>
+<p>Some paragraph</p>
+<p>Some paragraph</p>
+<p>Some other paragraph</p>
diff --git a/css/CSS2/linebox/line-height-oof-descendants-001.html b/css/CSS2/linebox/line-height-oof-descendants-001.html
new file mode 100644
index 0000000..6663b19
--- /dev/null
+++ b/css/CSS2/linebox/line-height-oof-descendants-001.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: line-height is not affected by out-of-flow descendants</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://www.w3.org/TR/CSS21/visudet.html#line-height">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1426760">
+<link rel="match" href="line-height-oof-descendants-001-ref.html">
+<style>
+p {
+  font-size: 20px;
+  line-height: 0;
+}
+</style>
+<p><span style="position: absolute;"></span>Some paragraph</p>
+<p><span style="float: left;"></span>Some paragraph</p>
+<p><span style="position: fixed;"></span>Some paragraph</p>
+<p>Some other paragraph</p>
diff --git a/css/CSS2/lists/counter-increment-014-ref.html b/css/CSS2/lists/counter-increment-014-ref.html
new file mode 100644
index 0000000..c436edd
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-014-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the number '2' appears below.</p>
+    <div>2</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-014.xht b/css/CSS2/lists/counter-increment-014.xht
index 5d564ad..96149c3 100644
--- a/css/CSS2/lists/counter-increment-014.xht
+++ b/css/CSS2/lists/counter-increment-014.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-014-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with only an identifier, grouped twice." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-021-ref.html b/css/CSS2/lists/counter-increment-021-ref.html
new file mode 100644
index 0000000..39f5154
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-021-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the number '20' appears below.</p>
+    <div>20</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-021.xht b/css/CSS2/lists/counter-increment-021.xht
index f8f69ca..5bcdda6 100644
--- a/css/CSS2/lists/counter-increment-021.xht
+++ b/css/CSS2/lists/counter-increment-021.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-021-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with an identifier and an integer value, grouped twice." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-022.xht b/css/CSS2/lists/counter-increment-022.xht
index 3c20580..99d44f3 100644
--- a/css/CSS2/lists/counter-increment-022.xht
+++ b/css/CSS2/lists/counter-increment-022.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-021-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with an identifier and a positive integer value, grouped twice." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-027-ref.html b/css/CSS2/lists/counter-increment-027-ref.html
new file mode 100644
index 0000000..232149e
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-027-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the number '3' appears below.</p>
+    <div>3</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-027.xht b/css/CSS2/lists/counter-increment-027.xht
index 25b9353..4bfc100 100644
--- a/css/CSS2/lists/counter-increment-027.xht
+++ b/css/CSS2/lists/counter-increment-027.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-027-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with only an identifier, grouped three times." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-034-ref.html b/css/CSS2/lists/counter-increment-034-ref.html
new file mode 100644
index 0000000..7acce36
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-034-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the number '30' appears below.</p>
+    <div>30</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-034.xht b/css/CSS2/lists/counter-increment-034.xht
index cc433a2..514077b 100644
--- a/css/CSS2/lists/counter-increment-034.xht
+++ b/css/CSS2/lists/counter-increment-034.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-034-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with an identifier and an integer value, grouped three times." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-035.xht b/css/CSS2/lists/counter-increment-035.xht
index b643245..e0d9abd 100644
--- a/css/CSS2/lists/counter-increment-035.xht
+++ b/css/CSS2/lists/counter-increment-035.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-034-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with an identifier and a positive integer value, grouped three times." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-040-ref.html b/css/CSS2/lists/counter-increment-040-ref.html
new file mode 100644
index 0000000..2e6a60c
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-040-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the number '32' appears below.</p>
+    <div>32</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-040.xht b/css/CSS2/lists/counter-increment-040.xht
index 589eaf6..b035b30 100644
--- a/css/CSS2/lists/counter-increment-040.xht
+++ b/css/CSS2/lists/counter-increment-040.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-040-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with only an identifier, grouped thirty-two times." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-047-ref.html b/css/CSS2/lists/counter-increment-047-ref.html
new file mode 100644
index 0000000..2b5762b
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-047-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the number '320' appears below.</p>
+    <div>320</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-047.xht b/css/CSS2/lists/counter-increment-047.xht
index 46bd419..505832a 100644
--- a/css/CSS2/lists/counter-increment-047.xht
+++ b/css/CSS2/lists/counter-increment-047.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with an identifier and an integer value, grouped thirty-two times." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-048.xht b/css/CSS2/lists/counter-increment-048.xht
index c87105a..bca1813 100644
--- a/css/CSS2/lists/counter-increment-048.xht
+++ b/css/CSS2/lists/counter-increment-048.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-047-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with an identifier and a positive integer value, grouped thirty-two times." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-053-ref.html b/css/CSS2/lists/counter-increment-053-ref.html
new file mode 100644
index 0000000..caa2f38
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-053-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the numbers '5 5 10' appear below in the same order.</p>
+    <div>5</div>
+    <div>5</div>
+    <div>10</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-053.xht b/css/CSS2/lists/counter-increment-053.xht
index 29928a4..65a0820 100644
--- a/css/CSS2/lists/counter-increment-053.xht
+++ b/css/CSS2/lists/counter-increment-053.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-053-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The counter-increment set to 'none' does not increment counter." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-054-ref.html b/css/CSS2/lists/counter-increment-054-ref.html
new file mode 100644
index 0000000..639206a
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-054-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the numbers '5 10' appear below in the same order.</p>
+    <div>5</div>
+    <div>10</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-054.xht b/css/CSS2/lists/counter-increment-054.xht
index 979b933..adc23fa 100644
--- a/css/CSS2/lists/counter-increment-054.xht
+++ b/css/CSS2/lists/counter-increment-054.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-054-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The counter-increment with the value inherit specified." />
         <style type="text/css">
diff --git a/css/CSS2/lists/counter-increment-055-ref.html b/css/CSS2/lists/counter-increment-055-ref.html
new file mode 100644
index 0000000..2f3532e
--- /dev/null
+++ b/css/CSS2/lists/counter-increment-055-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 30px;
+    }
+</style>
+<body>
+    <p>Test passes if the number '1' appears below.</p>
+    <div>1</div>
+</body>
diff --git a/css/CSS2/lists/counter-increment-055.xht b/css/CSS2/lists/counter-increment-055.xht
index 91884a0..025b2c9 100644
--- a/css/CSS2/lists/counter-increment-055.xht
+++ b/css/CSS2/lists/counter-increment-055.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#counters" />
+        <link rel="match" href="counter-increment-055-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'counter-increment' property with only an identifier." />
         <style type="text/css">
diff --git a/css/CSS2/normal-flow/inline-table-002-ref.xht b/css/CSS2/normal-flow/inline-table-002-ref.xht
index c333d3a..42a4110 100644
--- a/css/CSS2/normal-flow/inline-table-002-ref.xht
+++ b/css/CSS2/normal-flow/inline-table-002-ref.xht
@@ -4,7 +4,5 @@
 <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 </head>
 <body>
-<p>abcde</p>
-
-
-</body></html>
\ No newline at end of file
+<p>a<span style="display:inline-table;">bcd</span>e</p>
+</body></html>
diff --git a/css/CSS2/normal-flow/inline-table-valign-001-ref.xht b/css/CSS2/normal-flow/inline-table-valign-001-ref.xht
index 243ad0f..07d9f5e 100644
--- a/css/CSS2/normal-flow/inline-table-valign-001-ref.xht
+++ b/css/CSS2/normal-flow/inline-table-valign-001-ref.xht
@@ -4,8 +4,6 @@
 <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 <style type="text/css">
 span#table { display: inline-table; }
-span#rowgroup { display: table-row-group; }
-span#row { display: table-row; }
 span#cell { display: table-cell; }
 span#table, span#rowgroup, span#row, span#cell {
   border: 4px solid white;
@@ -19,8 +17,11 @@
 </head>
 <body>
 <table border=""><tbody><tr><td>
-<p><span id="table"><span id="rowgroup"><span id="row"><span id="cell">abcde<span id="block">x</span></span></span></span></span></p>
+        <p>
+          a<span id="table"><span id="cell">bcd</span></span>e
+          <span id="block">x</span>
+        </p>
 </td></tr></tbody></table>
 
 
-</body></html>
\ No newline at end of file
+</body></html>
diff --git a/css/CSS2/normal-flow/margin-collapse-through-zero-height-block.html b/css/CSS2/normal-flow/margin-collapse-through-zero-height-block.html
new file mode 100644
index 0000000..471a4c7
--- /dev/null
+++ b/css/CSS2/normal-flow/margin-collapse-through-zero-height-block.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Collapse bottom margin from previous sibling through zero height block to next sibling</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/box.html#collapsing-margins" title="8.3.1 Collapsing margins">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="overflow:hidden; width:200px; height:400px; background:green;">
+  <div style="margin-bottom:200px;"></div>
+  <div style="height:0;"></div>
+  <div style="height:200px; margin-top:100px; background:white;"></div>
+  <div style="height:200px; background:red;"></div>
+</div>
diff --git a/css/CSS2/normal-flow/margin-collapsing-in-table-caption-001.html b/css/CSS2/normal-flow/margin-collapsing-in-table-caption-001.html
new file mode 100644
index 0000000..c06d737
--- /dev/null
+++ b/css/CSS2/normal-flow/margin-collapsing-in-table-caption-001.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/tables.html#model">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<meta name="flags" content="" />
+<meta name="assert" content="margins between sibling blocks are collapsed inside caption" />
+<title>
+Caption block containers are rendered same as normal block boxes
+</title>
+
+<style>
+div {
+  margin: 10px;
+  height: 35px;
+}
+</style>
+
+<p>Test passes if there is a filled green square.</p>
+
+<table>
+  <caption style="width:100px; background:green">
+    <div></div>
+    <div></div>
+  </caption>
+</table>
diff --git a/css/CSS2/normal-flow/margin-collapsing-in-table-caption-002.html b/css/CSS2/normal-flow/margin-collapsing-in-table-caption-002.html
new file mode 100644
index 0000000..1890330
--- /dev/null
+++ b/css/CSS2/normal-flow/margin-collapsing-in-table-caption-002.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/tables.html#model">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<meta name="flags" content="" />
+<meta name="assert" content="margins between parent and child block collapse inside caption" />
+<title>
+Caption block containers are rendered same as normal block boxes
+</title>
+
+<style>
+div {
+  margin-top: 100px;
+}
+</style>
+
+<p>Test passes if there is a filled green square.</p>
+
+<table>
+  <caption style="width:100px; background:green">
+    <div>
+      <div></div>
+    </div>
+  </caption>
+</table>
diff --git a/css/CSS2/normal-flow/unresolvable-max-height.html b/css/CSS2/normal-flow/unresolvable-max-height.html
new file mode 100644
index 0000000..8a2fbb2
--- /dev/null
+++ b/css/CSS2/normal-flow/unresolvable-max-height.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Unresolvable percentage min-height</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#min-max-heights" title="10.7 Minimum and maximum heights: 'min-height' and 'max-height'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<div id="container" style="width:100px; height:30000px; max-height:100%;" data-expected-height="30000">
+  <div style="height:12345px;"></div>
+</div>
+<script>
+  checkLayout("#container");
+</script>
diff --git a/css/CSS2/normal-flow/unresolvable-min-height.html b/css/CSS2/normal-flow/unresolvable-min-height.html
new file mode 100644
index 0000000..5201edd
--- /dev/null
+++ b/css/CSS2/normal-flow/unresolvable-min-height.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Unresolvable percentage min-height</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#min-max-heights" title="10.7 Minimum and maximum heights: 'min-height' and 'max-height'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<p>There should be a blue square below.</p>
+<div id="container" style="width:100px; max-height:100px; min-height:100%; background:blue;" data-expected-height="100">
+  <div style="height:300px;"></div>
+</div>
+<script>
+  checkLayout("#container");
+</script>
diff --git a/css/CSS2/normal-flow/width-inherit-001.xht b/css/CSS2/normal-flow/width-inherit-001.xht
index 18583f4..c958d8b 100644
--- a/css/CSS2/normal-flow/width-inherit-001.xht
+++ b/css/CSS2/normal-flow/width-inherit-001.xht
@@ -23,6 +23,8 @@
       background: orange;
     }
     .control {
+      position: absolute;
+      top: 82px;
       width: 9.375em;
       height: 1.875em;
       background: blue;
diff --git a/css/CSS2/positioning/abspos-011-ref.xht b/css/CSS2/positioning/abspos-011-ref.xht
index 0e421c4..e14b766 100644
--- a/css/CSS2/positioning/abspos-011-ref.xht
+++ b/css/CSS2/positioning/abspos-011-ref.xht
@@ -32,7 +32,7 @@
 
  <body>
 
-  <div id="first">____ ____</div>
+  <div id="first">&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;</div>
 
   <div id="second">FAIL PASS</div>
 
diff --git a/css/CSS2/positioning/abspos-011.xht b/css/CSS2/positioning/abspos-011.xht
index 111795b..1900af4 100644
--- a/css/CSS2/positioning/abspos-011.xht
+++ b/css/CSS2/positioning/abspos-011.xht
@@ -16,9 +16,9 @@
   </style>
  </head>
  <body>
-  <p>FAIL ____</p>
-  <p>#___ P___</p>
-  <p>_##_ _A__</p>
-  <p>___# __SS</p>
+  <p>FAIL &nbsp;&nbsp;&nbsp;&nbsp;</p>
+  <p>#&nbsp;&nbsp;&nbsp; P&nbsp;&nbsp;&nbsp;</p>
+  <p>&nbsp;##&nbsp; &nbsp;A&nbsp;&nbsp;</p>
+  <p>&nbsp;&nbsp;&nbsp;# &nbsp;&nbsp;SS</p>
 </body>
 </html>
diff --git a/css/CSS2/positioning/abspos-012.xht b/css/CSS2/positioning/abspos-012.xht
index 8aacf30..03b47f9 100644
--- a/css/CSS2/positioning/abspos-012.xht
+++ b/css/CSS2/positioning/abspos-012.xht
@@ -18,10 +18,10 @@
  </head>
  <body>
   <div>
-   <p>FAIL ____</p>
-   <p>#___ P___</p>
-   <p>_##_ _A__</p>
-   <p>___# __SS</p>
+   <p>FAIL &nbsp;&nbsp;&nbsp;&nbsp;</p>
+   <p>#&nbsp;&nbsp;&nbsp; P&nbsp;&nbsp;&nbsp;</p>
+   <p>&nbsp;##&nbsp; &nbsp;A&nbsp;&nbsp;</p>
+   <p>&nbsp;&nbsp;&nbsp;# &nbsp;&nbsp;SS</p>
   </div>
 
 
diff --git a/css/CSS2/positioning/abspos-027.xht b/css/CSS2/positioning/abspos-027.xht
index 20fcb2a..f9fecbe 100644
--- a/css/CSS2/positioning/abspos-027.xht
+++ b/css/CSS2/positioning/abspos-027.xht
@@ -9,7 +9,7 @@
   <link rel="match" href="../reference/ref-if-there-is-no-red.xht" />
 
   <style type="text/css">
-  body {margin-top: 1em; font: 1em/1.25 serif;}
+  body {margin-top: 1em; }
 
    .test { position: absolute; background: red; }
    table { background: white; color: black; margin: auto; border-spacing: 0px; }
diff --git a/css/CSS2/positioning/auto-position-rtl-child-viewport-scrollbar-ref.html b/css/CSS2/positioning/auto-position-rtl-child-viewport-scrollbar-ref.html
new file mode 100644
index 0000000..99d7c74
--- /dev/null
+++ b/css/CSS2/positioning/auto-position-rtl-child-viewport-scrollbar-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<style>body { overflow:scroll; }</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:green;"></div>
diff --git a/css/CSS2/positioning/auto-position-rtl-child-viewport-scrollbar.html b/css/CSS2/positioning/auto-position-rtl-child-viewport-scrollbar.html
new file mode 100644
index 0000000..80daf1b
--- /dev/null
+++ b/css/CSS2/positioning/auto-position-rtl-child-viewport-scrollbar.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Auto-positioned absolutely positioned in static RTL parent, scrollbars on viewport</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
+<link rel="match" href="auto-position-rtl-child-viewport-scrollbar-ref.html">
+<style>body { overflow:scroll; }</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="direction:rtl; width:80px; height:80px; border:10px solid green; background:red;">
+  <div style="position:absolute; width:80px; height:80px; background:green;"></div>
+</div>
diff --git a/css/CSS2/positioning/position-relative-031-ref.xht b/css/CSS2/positioning/position-relative-031-ref.xht
index 8e5300f..841ed97 100644
--- a/css/CSS2/positioning/position-relative-031-ref.xht
+++ b/css/CSS2/positioning/position-relative-031-ref.xht
@@ -8,6 +8,10 @@
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
 
+  <style>
+    /* Disable kerning because kerning may differ for different node tree. */
+    html { font-kerning: none; font-feature-settings: "kern" off; }
+  </style>
  </head>
 
  <body>
@@ -17,4 +21,4 @@
   <div>Filler Text Filler Text Filler Text Filler Text Filler Text</div>
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/positioning/position-relative-031.xht b/css/CSS2/positioning/position-relative-031.xht
index 235260b..11f5375 100644
--- a/css/CSS2/positioning/position-relative-031.xht
+++ b/css/CSS2/positioning/position-relative-031.xht
@@ -10,6 +10,8 @@
         <meta name="flags" content="" />
         <meta name="assert" content="Relatively positioned element with auto positioning render as though they were positioned in flow." />
         <style type="text/css">
+            /* Disable kerning because kerning may differ for different node tree. */
+            html { font-kerning: none; font-feature-settings: "kern" off; }
             div
             {
                 width: 5in;
@@ -26,4 +28,4 @@
             <span>Filler Text </span><span id="span1">Filler Text</span> Filler Text<span> Filler Text </span>Filler Text
         </div>
     </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/positioning/positioning-float-001-ref.xht b/css/CSS2/positioning/positioning-float-001-ref.xht
index 5655e5f..aaa2ce8 100644
--- a/css/CSS2/positioning/positioning-float-001-ref.xht
+++ b/css/CSS2/positioning/positioning-float-001-ref.xht
@@ -9,6 +9,9 @@
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
 
   <style type="text/css"><![CDATA[
+  /* Disable kerning because kerning may differ for different node tree. */
+  html { font-kerning: none; font-feature-settings: "kern" off; }
+
   div {font-size: 3em;}
   ]]></style>
 
diff --git a/css/CSS2/positioning/positioning-float-001.xht b/css/CSS2/positioning/positioning-float-001.xht
index 0b0ee55..ababe89 100644
--- a/css/CSS2/positioning/positioning-float-001.xht
+++ b/css/CSS2/positioning/positioning-float-001.xht
@@ -9,6 +9,9 @@
         <meta name="flags" content="" />
         <meta name="assert" content="Floating a box will first lay out the box then shift it to the left." />
         <style type="text/css">
+            /* Disable kerning because kerning may differ for different node tree. */
+            html { font-kerning: none; font-feature-settings: "kern" off; }
+
             div
             {
                 font-size: 3em;
@@ -24,4 +27,4 @@
         <p>Test passes if there is the word "P A S S".</p>
         <div>S <span>P A&nbsp;</span>S</div>
     </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/CSS2/selectors/class-selector-001.xht b/css/CSS2/selectors/class-selector-001.xht
index 43179e6..825ab5a 100644
--- a/css/CSS2/selectors/class-selector-001.xht
+++ b/css/CSS2/selectors/class-selector-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Class selectors with only a partial match of 'class' attribute</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+        <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
         <link rel="match" href="../reference/no-red-filler-text-ref.xht"/>
         <meta name="flags" content="" />
         <meta name="assert" content="Class selector need to match any or all of the values in the 'class' attribute." />
diff --git a/css/CSS2/selectors/class-selector-002.xht b/css/CSS2/selectors/class-selector-002.xht
index 7005634..1aac525 100644
--- a/css/CSS2/selectors/class-selector-002.xht
+++ b/css/CSS2/selectors/class-selector-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Class selectors with a type</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+        <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
         <link rel="match" href="../reference/filler-text-below-green.xht"/>
         <meta name="flags" content="" />
         <meta name="assert" content="Class selector with a type, match the appropriate element." />
diff --git a/css/CSS2/selectors/class-selector-003.xht b/css/CSS2/selectors/class-selector-003.xht
index 9ed65d0..f097166 100644
--- a/css/CSS2/selectors/class-selector-003.xht
+++ b/css/CSS2/selectors/class-selector-003.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Class selectors without a type</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+        <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
         <link rel="match" href="../reference/filler-text-below-green.xht"/>
         <meta name="flags" content="" />
         <meta name="assert" content="Class selector without a type, match the appropriate element." />
diff --git a/css/CSS2/selectors/class-selector-004.xht b/css/CSS2/selectors/class-selector-004.xht
index 29b2261..c7faefb 100644
--- a/css/CSS2/selectors/class-selector-004.xht
+++ b/css/CSS2/selectors/class-selector-004.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Invalid class selectors with a space between the "." and the class name</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+        <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
         <link rel="match" href="../reference/no-red-filler-text-ref.xht"/>
         <meta name="flags" content="invalid" />
         <meta name="assert" content="The attribute value of a class selector followed by white space is invalid." />
diff --git a/css/CSS2/selectors/class-selector-005.xht b/css/CSS2/selectors/class-selector-005.xht
index ee809f3..a62adae 100644
--- a/css/CSS2/selectors/class-selector-005.xht
+++ b/css/CSS2/selectors/class-selector-005.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Invalid class selectors with a carriage return between the "." and the class name</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+        <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
         <link rel="match" href="../reference/no-red-filler-text-ref.xht"/>
         <meta name="flags" content="invalid" />
         <meta name="assert" content="The attribute value of a class selector followed by white space is invalid." />
diff --git a/css/CSS2/selectors/class-selector-006.xht b/css/CSS2/selectors/class-selector-006.xht
index 2010fb8..c55443a 100644
--- a/css/CSS2/selectors/class-selector-006.xht
+++ b/css/CSS2/selectors/class-selector-006.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Multiple class selectors</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+        <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
         <link rel="match" href="../reference/filler-text-below-green.xht"/>
         <meta name="flags" content="" />
         <meta name="assert" content="All class names in the selector need to match all of the 'class' attribute values." />
diff --git a/css/CSS2/selectors/class-selector-007.xht b/css/CSS2/selectors/class-selector-007.xht
index e336bf4..c27f3f2 100644
--- a/css/CSS2/selectors/class-selector-007.xht
+++ b/css/CSS2/selectors/class-selector-007.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Multiple class selectors and missing class values</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+        <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
         <link rel="match" href="../reference/no-red-filler-text-ref.xht"/>
         <meta name="flags" content="" />
         <meta name="assert" content="All class names in the selector need to match all of the 'class' attribute values." />
diff --git a/css/CSS2/selectors/class-selector-008.xht b/css/CSS2/selectors/class-selector-008.xht
index 375a6d6..b4ceabb 100644
--- a/css/CSS2/selectors/class-selector-008.xht
+++ b/css/CSS2/selectors/class-selector-008.xht
@@ -5,6 +5,7 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/class/001.html" type="text/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+  <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
   <link rel="match" href="../reference/ref-this-text-should-be-green.xht" />
   <style type="text/css">
    .teST { color: green; }
diff --git a/css/CSS2/selectors/class-selector-009.xht b/css/CSS2/selectors/class-selector-009.xht
index 29baaa7..cba3e08 100644
--- a/css/CSS2/selectors/class-selector-009.xht
+++ b/css/CSS2/selectors/class-selector-009.xht
@@ -5,13 +5,15 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/class/002.html" type="text/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+  <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
+  <link rel="match" href="../reference/ref-green-background.xht" />
   <style type="text/css">
    p { background: green; color: white; }
    .fail.test { background: red; color: yellow; }
   </style>
  </head>
  <body>
-  <p class="pass test">This line should be green.</p>
+  <p class="pass test">This should have a green background.</p>
  </body>
 </html>
 
diff --git a/css/CSS2/selectors/class-selector-010.xht b/css/CSS2/selectors/class-selector-010.xht
index 99f3ebc..e0fd5bc 100644
--- a/css/CSS2/selectors/class-selector-010.xht
+++ b/css/CSS2/selectors/class-selector-010.xht
@@ -5,13 +5,15 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/class/003.html" type="text/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+  <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
+  <link rel="match" href="../reference/ref-green-background.xht" />
   <style type="text/css">
    p { background: red; color: yellow; }
    .pass.test { background: green; color: white; }
   </style>
  </head>
  <body>
-  <p class="pass test">This line should be green.</p>
+  <p class="pass test">This should have a green background.</p>
  </body>
 </html>
 
diff --git a/css/CSS2/selectors/class-selector-011.xht b/css/CSS2/selectors/class-selector-011.xht
index 2af242b..ac49276 100644
--- a/css/CSS2/selectors/class-selector-011.xht
+++ b/css/CSS2/selectors/class-selector-011.xht
@@ -5,13 +5,15 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/class/004.html" type="text/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+  <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
+  <link rel="match" href="../reference/ref-green-background.xht" />
   <style type="text/css">
    p { background: red; color: yellow; }
    .pass { background: green; color: white; }
   </style>
  </head>
  <body>
-  <p class="pass test">This line should be green.</p>
+  <p class="pass test">This should have a green background.</p>
  </body>
 </html>
 
diff --git a/css/CSS2/selectors/class-selector-012-ref.html b/css/CSS2/selectors/class-selector-012-ref.html
new file mode 100644
index 0000000..52f9a3a
--- /dev/null
+++ b/css/CSS2/selectors/class-selector-012-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    p {
+        color: white;
+        background: green;
+    }
+</style>
+<body>
+    <p>This line should be green.</p>
+    <p>This line should be green.</p>
+    <p>This line should be green.</p>
+    <p>This line should be green.</p>
+    <p>This line should be green.</p>
+    <p>This line should be green.</p>
+    <p>This line should be green.</p>
+    <p>This line should be green.</p>
+</body>
+</html>
diff --git a/css/CSS2/selectors/class-selector-012.xht b/css/CSS2/selectors/class-selector-012.xht
index b822f1b..ea57e2b 100644
--- a/css/CSS2/selectors/class-selector-012.xht
+++ b/css/CSS2/selectors/class-selector-012.xht
@@ -5,6 +5,8 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/class/005.html" type="text/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#class-html" />
+  <link rel="help" href="https://www.w3.org/TR/selectors/#class-html" />
+  <link rel="match" href="class-selector-012-ref.html" />
   <style type="text/css">
    p { background: red; color: yellow; }
    .test { background: green; color: white; }
diff --git a/css/CSS2/selectors/first-child-selector-001-ref.html b/css/CSS2/selectors/first-child-selector-001-ref.html
new file mode 100644
index 0000000..8aa67fa
--- /dev/null
+++ b/css/CSS2/selectors/first-child-selector-001-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    .green {
+        color: green;
+    }
+</style>
+<body>
+    <div class="green">Filler Text</div>
+    <div>Filler Text</div>
+    <p>Test passes if the first "Filler Text" above is green and the second one is black.</p>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-child-selector-001.xht b/css/CSS2/selectors/first-child-selector-001.xht
index e8d4e66..9f60cdc 100644
--- a/css/CSS2/selectors/first-child-selector-001.xht
+++ b/css/CSS2/selectors/first-child-selector-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-child pseudo-class</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-child" />
+        <link rel="match" href="first-child-selector-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="First-child pseudo-class matches only the first element of its type." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-child-selector-002-ref.html b/css/CSS2/selectors/first-child-selector-002-ref.html
new file mode 100644
index 0000000..c2ee2e7
--- /dev/null
+++ b/css/CSS2/selectors/first-child-selector-002-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    .green {
+        color: green;
+    }
+</style>
+<body>
+    Filler Text
+    <div class="green">Filler Text</div>
+    <div>Filler Text</div>
+    <p>Test passes if the second line of "Filler Text" above is green and the first and third lines are black.</p>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-child-selector-002.xht b/css/CSS2/selectors/first-child-selector-002.xht
index 588d8f93..d2c6512 100644
--- a/css/CSS2/selectors/first-child-selector-002.xht
+++ b/css/CSS2/selectors/first-child-selector-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-child pseudo-class with text node</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-child" />
+        <link rel="match" href="first-child-selector-002-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="First-child pseudo-class matches only the first element of its type ignoring text nodes." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-child-selector-003.xht b/css/CSS2/selectors/first-child-selector-003.xht
index c779c8b..a02e002 100644
--- a/css/CSS2/selectors/first-child-selector-003.xht
+++ b/css/CSS2/selectors/first-child-selector-003.xht
@@ -5,12 +5,13 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/child/001.xml" type="application/xhtml+xml"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-child" />
+  <link rel="match" href="../reference/ref-this-text-should-be-green.xht" />
   <style type="text/css">
-   html { color: green; }
-   html:first-child { color: red ! important; }
+   html { color: red; }
+   :root:first-child { color: green; }
   </style>
  </head>
  <body>
-  <p>This should be green.</p>
+  <p>This text should be green.</p>
  </body>
 </html>
diff --git a/css/CSS2/selectors/first-letter-selector-000-ref.html b/css/CSS2/selectors/first-letter-selector-000-ref.html
new file mode 100644
index 0000000..ea97ba2
--- /dev/null
+++ b/css/CSS2/selectors/first-letter-selector-000-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    div {
+        color: green;
+        font: 2em sans-serif;
+    }
+</style>
+<body>
+    <p>This next line should be green:</p>
+    <div>TESTING</div>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-letter-selector-000.xht b/css/CSS2/selectors/first-letter-selector-000.xht
index 1b2b6ff..0fdef18 100644
--- a/css/CSS2/selectors/first-letter-selector-000.xht
+++ b/css/CSS2/selectors/first-letter-selector-000.xht
@@ -5,6 +5,7 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/first-letter/001.xml" type="application/xhtml+xml"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+  <link rel="match" href="first-letter-selector-000-ref.html" />
   <style type="text/css">
    .test { float: left; font: 2em sans-serif; }
    .test:first-letter { color: green; }
diff --git a/css/CSS2/selectors/first-letter-selector-001-ref.html b/css/CSS2/selectors/first-letter-selector-001-ref.html
new file mode 100644
index 0000000..625c2f5
--- /dev/null
+++ b/css/CSS2/selectors/first-letter-selector-001-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    span {
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the letter "F" below is green.</p>
+    <div><span>F</span>iller Text</div>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-letter-selector-001.xht b/css/CSS2/selectors/first-letter-selector-001.xht
index c386e3e..7ae5c3a 100644
--- a/css/CSS2/selectors/first-letter-selector-001.xht
+++ b/css/CSS2/selectors/first-letter-selector-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter pseudo-element</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The :first-letter pseudo-element matches the first letter in a given element." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-letter-selector-002-ref.html b/css/CSS2/selectors/first-letter-selector-002-ref.html
new file mode 100644
index 0000000..39d4045
--- /dev/null
+++ b/css/CSS2/selectors/first-letter-selector-002-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<body>
+    <p>Test passes if there is no red visible on the page.</p>
+    <div><img alt="15x15 blue box" src="support/blue15x15.png" />Filler Text</div>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-letter-selector-002.xht b/css/CSS2/selectors/first-letter-selector-002.xht
index 3c55685..982c64d 100644
--- a/css/CSS2/selectors/first-letter-selector-002.xht
+++ b/css/CSS2/selectors/first-letter-selector-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter pseudo-element with image missing alt text</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-002-ref.html" />
         <meta name="flags" content="image" />
         <meta name="assert" content="If there is preceding content (an image) or alt text the :first-letter pseudo-element does not match the first letter." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-letter-selector-003-ref.html b/css/CSS2/selectors/first-letter-selector-003-ref.html
new file mode 100644
index 0000000..a818e1a
--- /dev/null
+++ b/css/CSS2/selectors/first-letter-selector-003-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    span {
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the number "4" below is green.</p>
+    <div><span>4</span>2 Filler Text</div>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-letter-selector-003.xht b/css/CSS2/selectors/first-letter-selector-003.xht
index 42ea445..876f2ef 100644
--- a/css/CSS2/selectors/first-letter-selector-003.xht
+++ b/css/CSS2/selectors/first-letter-selector-003.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter as a digit</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The ':first-letter' applies if the first letter is a digit." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-letter-selector-004.xht b/css/CSS2/selectors/first-letter-selector-004.xht
index 9ac9587..e350c74 100644
--- a/css/CSS2/selectors/first-letter-selector-004.xht
+++ b/css/CSS2/selectors/first-letter-selector-004.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter and :before</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="If an element has ':before' or ':after' content, the ':first-letter applies to the first letter of the element including that content." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-letter-selector-005-ref.html b/css/CSS2/selectors/first-letter-selector-005-ref.html
new file mode 100644
index 0000000..d64537c
--- /dev/null
+++ b/css/CSS2/selectors/first-letter-selector-005-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<body>
+    <p>Test passes if there is no red visible on the page.</p>
+    <div><br />Filler Text</div>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-letter-selector-005.xht b/css/CSS2/selectors/first-letter-selector-005.xht
index 3d0a622..7f0c7dc 100644
--- a/css/CSS2/selectors/first-letter-selector-005.xht
+++ b/css/CSS2/selectors/first-letter-selector-005.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter with leading line break</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-005-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The first letter occurs on the first formatted line." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-letter-selector-007-ref.html b/css/CSS2/selectors/first-letter-selector-007-ref.html
new file mode 100644
index 0000000..4c2801b
--- /dev/null
+++ b/css/CSS2/selectors/first-letter-selector-007-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    span {
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the letter "F" below is green.</p>
+    <table>
+        <tr>
+            <td><span>F</span>iller Text</td>
+        </tr>
+    </table>
+</body>
+</html>
diff --git a/css/CSS2/selectors/first-letter-selector-007.xht b/css/CSS2/selectors/first-letter-selector-007.xht
index 45b7bb0..2f19c5d 100644
--- a/css/CSS2/selectors/first-letter-selector-007.xht
+++ b/css/CSS2/selectors/first-letter-selector-007.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter with table cell elements</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-007-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The :first-letter pseudo-element applies to table-cells." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-letter-selector-008.xht b/css/CSS2/selectors/first-letter-selector-008.xht
index 8380838..e7c9b66 100644
--- a/css/CSS2/selectors/first-letter-selector-008.xht
+++ b/css/CSS2/selectors/first-letter-selector-008.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter with inline-block elements</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The first-letter pseudo-element applies to inline-block elements." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-letter-selector-019.xht b/css/CSS2/selectors/first-letter-selector-019.xht
index 228308a..115d716 100644
--- a/css/CSS2/selectors/first-letter-selector-019.xht
+++ b/css/CSS2/selectors/first-letter-selector-019.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter in descendents</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-letter" />
+        <link rel="match" href="first-letter-selector-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="A user agent acts as if the fictional start tag of the first-letter pseudo-element is just before the first text of the element, even if that first text is in a descendant." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/first-line-pseudo-017.xht b/css/CSS2/selectors/first-line-pseudo-017.xht
index f6e9033..9f43eca 100644
--- a/css/CSS2/selectors/first-line-pseudo-017.xht
+++ b/css/CSS2/selectors/first-line-pseudo-017.xht
@@ -12,6 +12,6 @@
   </style>
  </head>
  <body>
-  <p><span>This sentence should have a green background.</span></p>
+  <p><span>This should have a green background.</span></p>
  </body>
 </html>
diff --git a/css/CSS2/selectors/first-line-pseudo-019.xht b/css/CSS2/selectors/first-line-pseudo-019.xht
index c1bb6cd..3c29e03 100644
--- a/css/CSS2/selectors/first-line-pseudo-019.xht
+++ b/css/CSS2/selectors/first-line-pseudo-019.xht
@@ -6,11 +6,12 @@
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/first-line/003.xml" type="application/xhtml+xml"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/first-line/003.html" type="type/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo" />
+  <link rel="match" href="../reference/ref-this-text-should-be-green.xht" />
   <style type="text/css">
    :first-line { color: green; }
   </style>
  </head>
  <body>
-  <p>This line should be green.</p>
+  <p>This text should be green.</p>
  </body>
 </html>
diff --git a/css/CSS2/selectors/first-line-pseudo-020.xht b/css/CSS2/selectors/first-line-pseudo-020.xht
index 63acd1c..d0c8ddc 100644
--- a/css/CSS2/selectors/first-line-pseudo-020.xht
+++ b/css/CSS2/selectors/first-line-pseudo-020.xht
@@ -6,11 +6,12 @@
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/first-line/004.xml" type="application/xhtml+xml"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/first-line/004.html" type="type/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo" />
+  <link rel="match" href="../reference/ref-this-text-should-be-green.xht" />
   <style type="text/css">
    *:first-line { color: green; }
   </style>
  </head>
  <body>
-  <p>This line should be green.</p>
+  <p>This text should be green.</p>
  </body>
 </html>
diff --git a/css/CSS2/selectors/first-line-pseudo-021.xht b/css/CSS2/selectors/first-line-pseudo-021.xht
index af425a0..d071fc0 100644
--- a/css/CSS2/selectors/first-line-pseudo-021.xht
+++ b/css/CSS2/selectors/first-line-pseudo-021.xht
@@ -5,6 +5,7 @@
   <link rel="author" title="Ian Hickson" href="mailto:ian@hixie.ch"/>
   <link rel="alternate" href="http://www.hixie.ch/tests/adhoc/css/selectors/first-line/005.html" type="type/html"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo" />
+  <link rel="match" href="../reference/ref-this-text-should-be-green.xht" />
   <style type="text/css">
    p { color: red; }
    p:first-line { color: green; }
@@ -12,6 +13,6 @@
   </style>
  </head>
  <body>
-  <p><span>This line should be green.</span></p>
+  <p><span>This text should be green.</span></p>
  </body>
 </html>
diff --git a/css/CSS2/selectors/first-line-selector-010.xht b/css/CSS2/selectors/first-line-selector-010.xht
index b64f927..bbfa652 100644
--- a/css/CSS2/selectors/first-line-selector-010.xht
+++ b/css/CSS2/selectors/first-line-selector-010.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-line after a BR</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo" />
+        <link rel="match" href="first-letter-selector-005-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The :first-line pseudo-element start tag is inserted right after the start tag of the block element." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/id-selector-002.xht b/css/CSS2/selectors/id-selector-002.xht
index 315b916..8d82b3e 100644
--- a/css/CSS2/selectors/id-selector-002.xht
+++ b/css/CSS2/selectors/id-selector-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: ID selector syntax - Begins with hyphen</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#id-selectors" />
+        <link rel="match" href="../reference/filler-text-below-green.xht" />
         <meta name="flags" content="" />
         <meta name="assert" content="Identifier selectors starting with a hyphen are valid." />
         <style type="text/css">
@@ -18,7 +19,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if there is no red visible on the page.</p>
+        <p>Test passes if the "Filler Text" below is green.</p>
         <div id="-div1">Filler Text</div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/selectors/lang-selector-004-ref.html b/css/CSS2/selectors/lang-selector-004-ref.html
new file mode 100644
index 0000000..6f57dfc
--- /dev/null
+++ b/css/CSS2/selectors/lang-selector-004-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<style>
+    div {
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the "Filler Text" below is green.</p>
+    <div>Filler Text
+        <p>Filler Text</p>
+    </div>
+</body>
+</html>
diff --git a/css/CSS2/selectors/lang-selector-004.xht b/css/CSS2/selectors/lang-selector-004.xht
index 4b8ec89..d15c8eb 100644
--- a/css/CSS2/selectors/lang-selector-004.xht
+++ b/css/CSS2/selectors/lang-selector-004.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Inherited lang attribute selected</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#lang" />
+        <link rel="match" href="lang-selector-004-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Lang attribute is inherited and lang selector works on children." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/lang-selector-005.xht b/css/CSS2/selectors/lang-selector-005.xht
index f149d7e..916028c 100644
--- a/css/CSS2/selectors/lang-selector-005.xht
+++ b/css/CSS2/selectors/lang-selector-005.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Lang selector and document language set via server's content-language</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#lang" />
+        <link rel="match" href="../reference/filler-text-below-green.xht" />
         <meta name="flags" content="http" />
         <meta name="assert" content="Lang attribute is selectable when specified by HTTP header." />
         <style type="text/css">
@@ -18,7 +19,6 @@
         </style>
     </head>
     <body>
-        <p id="prerequisite">PREREQUISITE: Set the page "content-language" header to "fr" (French).</p>
         <p>Test passes if the "Filler Text" below is green.</p>
         <div>Filler Text</div>
     </body>
diff --git a/css/CSS2/selectors/lang-selector-006.xht b/css/CSS2/selectors/lang-selector-006.xht
index 244639c..e647695 100644
--- a/css/CSS2/selectors/lang-selector-006.xht
+++ b/css/CSS2/selectors/lang-selector-006.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Lang selector and document language set via meta tag</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#lang" />
+        <link rel="match" href="../reference/filler-text-below-green.xht" />
         <meta name="flags" content="" />
         <meta http-equiv="content-language" content="fr" />
         <meta name="assert" content="Lang attribute is selectable when specified in a meta tag." />
diff --git a/css/CSS2/selectors/pseudo-006.xht b/css/CSS2/selectors/pseudo-006.xht
index be667b0..d0bd9f9 100644
--- a/css/CSS2/selectors/pseudo-006.xht
+++ b/css/CSS2/selectors/pseudo-006.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-letter pseudo-element case sensitivity</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo" />
+        <link rel="match" href="first-letter-selector-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="First-letter is case-insensitive." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/pseudo-007.xht b/css/CSS2/selectors/pseudo-007.xht
index 1daca91..e007709 100644
--- a/css/CSS2/selectors/pseudo-007.xht
+++ b/css/CSS2/selectors/pseudo-007.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: First-child pseudo-element case sensitivity</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo" />
+        <link rel="match" href="universal-selector-002-ref.xht" />
         <meta name="flags" content="" />
         <meta name="assert" content="First-child is case-insensitive." />
         <style type="text/css">
diff --git a/css/CSS2/selectors/pseudo-008-ref.html b/css/CSS2/selectors/pseudo-008-ref.html
new file mode 100644
index 0000000..8297dc3
--- /dev/null
+++ b/css/CSS2/selectors/pseudo-008-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Reftest Reference</title>
+<body>
+    <p>Test passes if the words "Filler Text" are below.</p>
+    <div>Filler Text</div>
+</body>
+</html>
diff --git a/css/CSS2/selectors/pseudo-008.xht b/css/CSS2/selectors/pseudo-008.xht
index 41c009e..be85608 100644
--- a/css/CSS2/selectors/pseudo-008.xht
+++ b/css/CSS2/selectors/pseudo-008.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: After and before case sensitivity</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/selector.html#first-line-pseudo" />
+        <link rel="match" href="pseudo-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="After and before are case-insensitive." />
         <style type="text/css">
diff --git a/css/CSS2/syntax/at-charset-004.xht b/css/CSS2/syntax/at-charset-004.xht
index 7789f48..6d23331 100644
--- a/css/CSS2/syntax/at-charset-004.xht
+++ b/css/CSS2/syntax/at-charset-004.xht
@@ -8,7 +8,7 @@
         <meta name="flags" content="http" />
         <meta name="assert" content="The HTTP charset setting overrides the referring document's code page." />
         <style type="text/css">
-            @import "support/at-charset-004.css";
+            @import "support/at-charset-003.css";
             div
             {
                 color: red;
diff --git a/css/CSS2/syntax/at-charset-005.xht b/css/CSS2/syntax/at-charset-005.xht
index ba952a6..d76f64f 100644
--- a/css/CSS2/syntax/at-charset-005.xht
+++ b/css/CSS2/syntax/at-charset-005.xht
@@ -9,7 +9,7 @@
         <meta name="assert" content="The HTTP charset setting overrides the charset designation in the referring document's meta tag." />
         <meta http-equiv="Content-Type" content="text/html; windows-1252" />
         <style type="text/css">
-            @import "support/at-charset-005.css";
+            @import "support/at-charset-003.css";
             div
             {
                 color: red;
diff --git a/css/CSS2/syntax/at-charset-006.xht b/css/CSS2/syntax/at-charset-006.xht
index beba4fc..d78b378 100644
--- a/css/CSS2/syntax/at-charset-006.xht
+++ b/css/CSS2/syntax/at-charset-006.xht
@@ -8,7 +8,7 @@
         <meta name="flags" content="http" />
         <meta name="assert" content="The HTTP charset setting overrides the charset of referring document." />
         <style type="text/css">
-            @import "support/at-charset-006.css";
+            @import "support/at-charset-003.css";
             div
             {
                 color: red;
diff --git a/css/CSS2/syntax/characters-0080-009F-001-ref.xht b/css/CSS2/syntax/characters-0080-009F-001-ref.xht
index 7f285bc..f3aeb99 100644
--- a/css/CSS2/syntax/characters-0080-009F-001-ref.xht
+++ b/css/CSS2/syntax/characters-0080-009F-001-ref.xht
@@ -4,7 +4,7 @@
  <head>
   <title>CSS Test: Valid control characters in identifiers</title>
 
-  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org/" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
 
   <style type="text/css">
     div p::before {content: counter(item, lower-latin) ". "}
diff --git a/css/CSS2/syntax/characters-0080-009F-001.xht b/css/CSS2/syntax/characters-0080-009F-001.xht
index e6da937..40a7ad9 100644
--- a/css/CSS2/syntax/characters-0080-009F-001.xht
+++ b/css/CSS2/syntax/characters-0080-009F-001.xht
@@ -8,7 +8,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.4.1.3d" />
   <link rel="match" href="characters-0080-009F-001-ref.xht" />
   <meta name="assert" content="identifiers [...] can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+0080 and higher, [...]" />
-  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org/" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
 
   <style type="text/css">
     /* Warning: the next lines contain control characters \201..\237 */
diff --git a/css/CSS2/syntax/comments-005.xht b/css/CSS2/syntax/comments-005.xht
index a7f22b21..c192689 100644
--- a/css/CSS2/syntax/comments-005.xht
+++ b/css/CSS2/syntax/comments-005.xht
@@ -16,7 +16,7 @@
         </style>
     </head>
     <body>
-        <p>Test passes if "Filler Text" is green.</p>
+        <p>Test passes if the "Filler Text" below is green.</p>
         <div>Filler Text</div>
     </body>
 </html>
\ No newline at end of file
diff --git a/css/CSS2/syntax/escaped-url-001-ref.xht b/css/CSS2/syntax/escaped-url-001-ref.xht
index b6370d7..2df64b5 100644
--- a/css/CSS2/syntax/escaped-url-001-ref.xht
+++ b/css/CSS2/syntax/escaped-url-001-ref.xht
@@ -4,7 +4,7 @@
  <head>
   <title>CSS Test: Escapes in a URI token</title>
 
-  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org/" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
 
   <style type="text/css">
     div {width: 5em; height: 1em; background: url(support/1x1-green.png)}
diff --git a/css/CSS2/syntax/escaped-url-001.xht b/css/CSS2/syntax/escaped-url-001.xht
index f2e0e46..caaa644 100644
--- a/css/CSS2/syntax/escaped-url-001.xht
+++ b/css/CSS2/syntax/escaped-url-001.xht
@@ -9,7 +9,7 @@
   <link rel="match" href="escaped-url-001-ref.xht" />
   <meta name="assert" content="URI   {U}{R}{L}\({w}{string}{w}\)|
  {U}{R}{L}\({w}([!#$%&amp;*-\[\]-~]|{nonascii}|{escape})*{w}\)" />
-  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org/" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
 
   <style type="text/css">
     div {background: red; width: 5em; height: 1em}
diff --git a/css/CSS2/syntax/ident-003.xht b/css/CSS2/syntax/ident-003.xht
index 3002a97..e09d970 100644
--- a/css/CSS2/syntax/ident-003.xht
+++ b/css/CSS2/syntax/ident-003.xht
@@ -5,7 +5,7 @@
   <link rel="author" title="L. David Baron" href="https://dbaron.org/"/>
   <link rel="author" title="Ray Kiddy" href="mailto:ray@ganymede.org"/>
   <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#characters"/>
-  <link rel="match" href="ident-000-ref.xht"/>
+  <link rel="match" href="ident-003-ref.xht"/>
   <meta name="flags" content="invalid"/>
   <style type="text/css">
 
diff --git a/css/CSS2/syntax/ident-020.xht b/css/CSS2/syntax/ident-020.xht
deleted file mode 100644
index d6cb0c9..0000000
--- a/css/CSS2/syntax/ident-020.xht
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Test: Invalid Identifiers (starting with double -) as Classes and IDs</title>
-  <link rel="author" title="L. David Baron" href="https://dbaron.org/"/>
-  <link rel="help" href="http://www.w3.org/TR/CSS21/syndata.html#characters"/>
-  <link rel="match" href="ident-000-ref.xht"/>
-
-  <meta name="flags" content="invalid" />
-  <style type="text/css">
-
-  .one { color: green; background: white; }
-  .--ident, .one { color: red; }
-
-  .two { color: green; background: white; }
-  #--ident, .two { color: red; }
-
-  </style>
-
- </head>
- <body>
-
- <p class="one">This should be green.</p>
- <p class="two">This should be green.</p>
-
- </body>
-</html>
diff --git a/css/CSS2/syntax/support/.htaccess b/css/CSS2/syntax/support/.htaccess
deleted file mode 100644
index 7760371..0000000
--- a/css/CSS2/syntax/support/.htaccess
+++ /dev/null
@@ -1,40 +0,0 @@
-<Files ~ "^plaintext.css$">
-ForceType text/plain
-</Files>
-
-<files character-encoding-031.css>
-AddCharset iso-8859-1 .css
-</files>
-
-<files character-encoding-032.css>
-AddCharset iso-8859-11 .css
-</files>
-
-<files character-encoding-033.css>
-AddCharset iso-8859-5 .css
-</files>
-
-<files character-encoding-034.css>
-AddCharset iso-8859-6 .css
-</files>
-
-<files character-encoding-035.css>
-AddCharset iso-8859-7 .css
-</files>
-
-<files character-encoding-036.css>
-AddCharset iso-8859-8 .css
-</files>
-
-<files character-encoding-037.css>
-AddCharset koi8-r .css
-</files>
-
-<files ~ '^at-charset-07[1234567]\.css$'>
-RemoveCharset .css
-</files>
-
-<files character-encoding-041.css>
-AddCharset utf-16be .css
-</files>
-
diff --git a/css/CSS2/syntax/support/at-charset-001.css.headers b/css/CSS2/syntax/support/at-charset-001.css.headers
new file mode 100644
index 0000000..b2d94d0
--- /dev/null
+++ b/css/CSS2/syntax/support/at-charset-001.css.headers
@@ -0,0 +1 @@
+Content-Type: text/css; charset=Shift_JIS
diff --git a/css/CSS2/syntax/support/at-charset-002.css.headers b/css/CSS2/syntax/support/at-charset-002.css.headers
new file mode 100644
index 0000000..b2d94d0
--- /dev/null
+++ b/css/CSS2/syntax/support/at-charset-002.css.headers
@@ -0,0 +1 @@
+Content-Type: text/css; charset=Shift_JIS
diff --git a/css/CSS2/syntax/support/at-charset-003.css.headers b/css/CSS2/syntax/support/at-charset-003.css.headers
new file mode 100644
index 0000000..b2d94d0
--- /dev/null
+++ b/css/CSS2/syntax/support/at-charset-003.css.headers
@@ -0,0 +1 @@
+Content-Type: text/css; charset=Shift_JIS
diff --git a/css/CSS2/syntax/support/at-charset-004.css b/css/CSS2/syntax/support/at-charset-004.css
deleted file mode 100644
index a9b14d8..0000000
--- a/css/CSS2/syntax/support/at-charset-004.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.•½˜a, #div2
-{
-    color: green;
-}
\ No newline at end of file
diff --git a/css/CSS2/syntax/support/at-charset-005.css b/css/CSS2/syntax/support/at-charset-005.css
deleted file mode 100644
index a9b14d8..0000000
--- a/css/CSS2/syntax/support/at-charset-005.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.•½˜a, #div2
-{
-    color: green;
-}
\ No newline at end of file
diff --git a/css/CSS2/syntax/support/at-charset-006.css b/css/CSS2/syntax/support/at-charset-006.css
deleted file mode 100644
index a9b14d8..0000000
--- a/css/CSS2/syntax/support/at-charset-006.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.•½˜a, #div2
-{
-    color: green;
-}
\ No newline at end of file
diff --git a/css/CSS2/syntax/support/plaintext.css.headers b/css/CSS2/syntax/support/plaintext.css.headers
new file mode 100644
index 0000000..a1f9e38
--- /dev/null
+++ b/css/CSS2/syntax/support/plaintext.css.headers
@@ -0,0 +1 @@
+Content-Type: text/plain
diff --git a/css/CSS2/tables/caption-side-applies-to-001-ref.html b/css/CSS2/tables/caption-side-applies-to-001-ref.html
new file mode 100644
index 0000000..117b41c
--- /dev/null
+++ b/css/CSS2/tables/caption-side-applies-to-001-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p>Test passes if the words "Filler Text" below are all on the same line.</p>
+    <div>Filler Text Filler Text Filler Text</div>
+</body>
diff --git a/css/CSS2/tables/caption-side-applies-to-001.xht b/css/CSS2/tables/caption-side-applies-to-001.xht
index d228b35..d575df6 100644
--- a/css/CSS2/tables/caption-side-applies-to-001.xht
+++ b/css/CSS2/tables/caption-side-applies-to-001.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: inline' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-002-ref.html b/css/CSS2/tables/caption-side-applies-to-002-ref.html
new file mode 100644
index 0000000..e60406b
--- /dev/null
+++ b/css/CSS2/tables/caption-side-applies-to-002-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    .blue {
+        background: blue;
+    }
+</style>
+<body>
+    <p>Test passes if there are three lines of "Filler Text" below and the middle line has a blue background.</p>
+    <div>Filler Text</div>
+    <div class="blue">Filler Text</div>
+    <div>Filler Text</div>
+</body>
diff --git a/css/CSS2/tables/caption-side-applies-to-002.xht b/css/CSS2/tables/caption-side-applies-to-002.xht
index 4e8334e..bc7fd55 100644
--- a/css/CSS2/tables/caption-side-applies-to-002.xht
+++ b/css/CSS2/tables/caption-side-applies-to-002.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-002-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: block' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-003-ref.html b/css/CSS2/tables/caption-side-applies-to-003-ref.html
new file mode 100644
index 0000000..87bda4a
--- /dev/null
+++ b/css/CSS2/tables/caption-side-applies-to-003-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        margin-left: 2em;
+    }
+    .orange {
+        background: orange;
+        display: list-item;
+    }
+</style>
+<body>
+    <p>Test passes if there are three lines of "Filler Text" below and if the middle "Filler Text" has a orange background and a marker bullet on its left-hand side. The marker bullet should not have an orange background.</p>
+    <div>Filler Text</div>
+    <div class="orange">Filler Text</div>
+    <div>Filler Text</div>
+</body>
diff --git a/css/CSS2/tables/caption-side-applies-to-003.xht b/css/CSS2/tables/caption-side-applies-to-003.xht
index 032e0c3..546dfd6 100644
--- a/css/CSS2/tables/caption-side-applies-to-003.xht
+++ b/css/CSS2/tables/caption-side-applies-to-003.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: list-item' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-005.xht b/css/CSS2/tables/caption-side-applies-to-005.xht
index aa2a9af..8fbc059 100644
--- a/css/CSS2/tables/caption-side-applies-to-005.xht
+++ b/css/CSS2/tables/caption-side-applies-to-005.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: inline-block' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-006-ref.html b/css/CSS2/tables/caption-side-applies-to-006-ref.html
new file mode 100644
index 0000000..3426468
--- /dev/null
+++ b/css/CSS2/tables/caption-side-applies-to-006-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    .box {
+        background: blue;
+        height: 1in;
+        width: 2in;
+    }
+</style>
+<body>
+    <p>Test passes if the "Filler Text" below is below the box.</p>
+    <div class="box"></div>
+    <div>Filler Text</div>
+</body>
diff --git a/css/CSS2/tables/caption-side-applies-to-006.xht b/css/CSS2/tables/caption-side-applies-to-006.xht
index 28bdfdb..917f13e 100644
--- a/css/CSS2/tables/caption-side-applies-to-006.xht
+++ b/css/CSS2/tables/caption-side-applies-to-006.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-006-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-007.xht b/css/CSS2/tables/caption-side-applies-to-007.xht
index 607dfe7..939967e 100644
--- a/css/CSS2/tables/caption-side-applies-to-007.xht
+++ b/css/CSS2/tables/caption-side-applies-to-007.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-006-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: inline-table' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-008-ref.html b/css/CSS2/tables/caption-side-applies-to-008-ref.html
new file mode 100644
index 0000000..7e2af45
--- /dev/null
+++ b/css/CSS2/tables/caption-side-applies-to-008-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    .box {
+        background: blue;
+        height: 1in;
+        width: 2in;
+    }
+</style>
+<body>
+    <p>Test passes if the "Filler Text" below is above the box.</p>
+    <div>Filler Text</div>
+    <div class="box"></div>
+</body>
diff --git a/css/CSS2/tables/caption-side-applies-to-008.xht b/css/CSS2/tables/caption-side-applies-to-008.xht
index d6fe5e4..c7b3644 100644
--- a/css/CSS2/tables/caption-side-applies-to-008.xht
+++ b/css/CSS2/tables/caption-side-applies-to-008.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table-row-group' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-009.xht b/css/CSS2/tables/caption-side-applies-to-009.xht
index d45d936..de99f44 100644
--- a/css/CSS2/tables/caption-side-applies-to-009.xht
+++ b/css/CSS2/tables/caption-side-applies-to-009.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table-header-group' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-010.xht b/css/CSS2/tables/caption-side-applies-to-010.xht
index 75afc68..b37aa94 100644
--- a/css/CSS2/tables/caption-side-applies-to-010.xht
+++ b/css/CSS2/tables/caption-side-applies-to-010.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table-footer-group' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-011.xht b/css/CSS2/tables/caption-side-applies-to-011.xht
index 3db6e29..6f9f53b 100644
--- a/css/CSS2/tables/caption-side-applies-to-011.xht
+++ b/css/CSS2/tables/caption-side-applies-to-011.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table-column' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-012.xht b/css/CSS2/tables/caption-side-applies-to-012.xht
index a1cb7c0..1a65da4 100644
--- a/css/CSS2/tables/caption-side-applies-to-012.xht
+++ b/css/CSS2/tables/caption-side-applies-to-012.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table-row' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-013.xht b/css/CSS2/tables/caption-side-applies-to-013.xht
index 3e4353e..26360ab 100644
--- a/css/CSS2/tables/caption-side-applies-to-013.xht
+++ b/css/CSS2/tables/caption-side-applies-to-013.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table-column-group' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-014.xht b/css/CSS2/tables/caption-side-applies-to-014.xht
index cd89de7..f55d8b7 100644
--- a/css/CSS2/tables/caption-side-applies-to-014.xht
+++ b/css/CSS2/tables/caption-side-applies-to-014.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-008-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side does not apply to 'display: table-cell' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-015.xht b/css/CSS2/tables/caption-side-applies-to-015.xht
index f26658c..414db63 100644
--- a/css/CSS2/tables/caption-side-applies-to-015.xht
+++ b/css/CSS2/tables/caption-side-applies-to-015.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-006-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side applies to 'display: table-caption' elements." />
         <style type="text/css">
diff --git a/css/CSS2/tables/caption-side-applies-to-017-ref.html b/css/CSS2/tables/caption-side-applies-to-017-ref.html
new file mode 100644
index 0000000..4a19d9c
--- /dev/null
+++ b/css/CSS2/tables/caption-side-applies-to-017-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        background: blue;
+        width: 2in;
+    }
+    .box {
+        height: 1in;
+    }
+</style>
+<body>
+    <p>Test passes if the "Filler Text" below is inside the box.</p>
+    <div>Filler Text</div>
+    <div class="box"></div>
+</body>
diff --git a/css/CSS2/tables/caption-side-applies-to-017.xht b/css/CSS2/tables/caption-side-applies-to-017.xht
index 4ebbe1d..20f7e76 100644
--- a/css/CSS2/tables/caption-side-applies-to-017.xht
+++ b/css/CSS2/tables/caption-side-applies-to-017.xht
@@ -5,6 +5,7 @@
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#propdef-caption-side" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#caption-position" />
+        <link rel="match" href="caption-side-applies-to-017-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Caption-side applies to 'display: inherit' elements which do not inherit the value of 'table-caption'." />
         <style type="text/css">
diff --git a/css/CSS2/tables/collapsing-border-model-001-ref.html b/css/CSS2/tables/collapsing-border-model-001-ref.html
new file mode 100644
index 0000000..38308df
--- /dev/null
+++ b/css/CSS2/tables/collapsing-border-model-001-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        height: 120px;
+        width: 220px;
+    }
+    .orange {
+        background: orange;
+    }
+    .blue {
+        background: blue;
+    }
+</style>
+<body>
+    <p>Test passes if the orange and blue boxes below are the same width.</p>
+    <div class="orange"></div>
+    <div class="blue"></div>
+</body>
diff --git a/css/CSS2/tables/collapsing-border-model-001.xht b/css/CSS2/tables/collapsing-border-model-001.xht
index 90e706e..4501b584 100644
--- a/css/CSS2/tables/collapsing-border-model-001.xht
+++ b/css/CSS2/tables/collapsing-border-model-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Collapsing borders model row width equation (auto layout)</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders" />
+        <link rel="match" href="collapsing-border-model-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The user agent adheres to the collapsing border model row width equation in auto table layout." />
         <style type="text/css">
@@ -22,7 +23,7 @@
             #div1
             {
                 background: orange;
-                height: 110px;
+                height: 120px;
                 width: 220px;
             }
         </style>
diff --git a/css/CSS2/tables/collapsing-border-model-003-ref.html b/css/CSS2/tables/collapsing-border-model-003-ref.html
new file mode 100644
index 0000000..c9c30c2
--- /dev/null
+++ b/css/CSS2/tables/collapsing-border-model-003-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        height: 1.5in;
+        width: 100px;
+    }
+    .orange {
+        background: orange;
+    }
+    .blue {
+        background: blue;
+        width: 50px;
+        float: right;
+    }
+</style>
+<body>
+    <p>Test passes if the orange and blue boxes below are the same height.</p>
+    <div class="orange"><div class="blue"></div></div>
+</body>
diff --git a/css/CSS2/tables/collapsing-border-model-003.xht b/css/CSS2/tables/collapsing-border-model-003.xht
index 9a0b5f0..8616e69 100644
--- a/css/CSS2/tables/collapsing-border-model-003.xht
+++ b/css/CSS2/tables/collapsing-border-model-003.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Top table border width under collapsing borders model</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders" />
+        <link rel="match" href="collapsing-border-model-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The top border width of the table is half of the maximum collapsed top border width." />
         <style type="text/css">
diff --git a/css/CSS2/tables/collapsing-border-model-004.xht b/css/CSS2/tables/collapsing-border-model-004.xht
index 41e158f..fa7c3c9 100644
--- a/css/CSS2/tables/collapsing-border-model-004.xht
+++ b/css/CSS2/tables/collapsing-border-model-004.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Tables under the collapsing borders model don't have padding</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders" />
+        <link rel="match" href="collapsing-border-model-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="Padding doesn't apply to a table under the collapsing border model." />
         <style type="text/css">
@@ -23,7 +24,7 @@
             #div1
             {
                 background: orange;
-                height: 110px;
+                height: 120px;
                 width: 220px;
             }
         </style>
diff --git a/css/CSS2/tables/collapsing-border-model-007.xht b/css/CSS2/tables/collapsing-border-model-007.xht
index d48fdff..6fe4742 100644
--- a/css/CSS2/tables/collapsing-border-model-007.xht
+++ b/css/CSS2/tables/collapsing-border-model-007.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Left table border width under collapsing borders model</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders" />
+        <link rel="match" href="collapsing-border-model-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The left border width of the table is half of the first cell's collapsed left border width." />
         <style type="text/css">
@@ -14,21 +15,22 @@
             }
             col
             {
-                width: 1in;
+                width: 165px;
             }
             td, #div1
             {
-                height: 1in;
+                height: 120px;
             }
             td
             {
-                border-left: 1in solid blue;
+                width: 110px;
+                border-left: 110px solid blue;
                 padding: 0;
             }
             #div1
             {
                 background: orange;
-                width: 1.5in;
+                width: 220px;
             }
         </style>
     </head>
diff --git a/css/CSS2/tables/collapsing-border-model-008.xht b/css/CSS2/tables/collapsing-border-model-008.xht
index 6574658..23a4b31 100644
--- a/css/CSS2/tables/collapsing-border-model-008.xht
+++ b/css/CSS2/tables/collapsing-border-model-008.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Right table border width under collapsing borders model</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders" />
+        <link rel="match" href="collapsing-border-model-001-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The right border width of the table is half of the collapsed right border width of the last cell of the first row." />
         <style type="text/css">
@@ -14,21 +15,22 @@
             }
             col
             {
-                width: 1in;
+                width: 165px;
             }
             td, #div1
             {
-                height: 1in;
+                height: 120px;
             }
             td
             {
-                border-right: 1in solid blue;
+                width: 110px;
+                border-right: 110px solid blue;
                 padding: 0;
             }
             #div1
             {
                 background: orange;
-                width: 1.5in;
+                width: 220px;
             }
         </style>
     </head>
diff --git a/css/CSS2/tables/collapsing-border-model-009.xht b/css/CSS2/tables/collapsing-border-model-009.xht
index dfd35f2..6f0b864 100644
--- a/css/CSS2/tables/collapsing-border-model-009.xht
+++ b/css/CSS2/tables/collapsing-border-model-009.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Bottom table border width under collapsing borders model</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders" />
+        <link rel="match" href="collapsing-border-model-003-ref.html" />
         <meta name="flags" content="" />
         <meta name="assert" content="The bottom border width of the table is half of the maximum collapsed bottom border width." />
         <style type="text/css">
diff --git a/css/CSS2/tables/row-visibility-001.xht b/css/CSS2/tables/row-visibility-001.xht
index 010318e..4f91f23 100644
--- a/css/CSS2/tables/row-visibility-001.xht
+++ b/css/CSS2/tables/row-visibility-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Row and the 'visibility' property</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#dynamic-effects" />
+        <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
         <meta name="flags" content="" />
         <meta name="assert" content="A 'visibility' value of 'collapse' applies to table rows." />
         <style type="text/css">
@@ -27,13 +28,13 @@
             .cell
             {
                 display: table-cell;
-                height: 1in;
-                width: 1in;
+                height: 100px;
+                width: 100px;
             }
         </style>
     </head>
     <body>
-        <p>Test passes if there is no red visible on the page.</p>
+        <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
         <div id="table">
             <div id="row1">
                 <div class="cell"></div>
diff --git a/css/CSS2/tables/row-visibility-002.xht b/css/CSS2/tables/row-visibility-002.xht
index 5242ecd..5b6309c 100644
--- a/css/CSS2/tables/row-visibility-002.xht
+++ b/css/CSS2/tables/row-visibility-002.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Row group and the 'visibility' property</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#dynamic-effects" />
+        <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
         <meta name="flags" content="" />
         <meta name="assert" content="The 'visibility' value of 'collapse' applies to table row groups." />
         <style type="text/css">
@@ -31,13 +32,13 @@
             .cell
             {
                 display: table-cell;
-                height: 1in;
-                width: 1in;
+                height: 100px;
+                width: 100px;
             }
         </style>
     </head>
     <body>
-        <p>Test passes if there is no red visible on the page.</p>
+        <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
         <div id="table">
             <div id="rowgroup1">
                 <div id="row">
diff --git a/css/CSS2/tables/table-cell-001.xht b/css/CSS2/tables/table-cell-001.xht
index 3f09574..3b64c7a 100644
--- a/css/CSS2/tables/table-cell-001.xht
+++ b/css/CSS2/tables/table-cell-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Table-cell</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#table-display" />
+        <link rel="match" href="../reference/ref-filled-black-96px-square.xht" />
         <meta name="flags" content="" />
         <meta name="assert" content="An element with 'display: table-cell' is rendered as if it were a table cell." />
         <style type="text/css">
@@ -19,13 +20,13 @@
             {
                 background: black;
                 display: table-cell;
-                height: 2em;
-                width: 2em;
+                height: 48px;
+                width: 48px;
             }
         </style>
     </head>
     <body>
-        <p>Test passes if there is a square below.</p>
+        <p>Test passes if there is a filled black square.</p>
         <div class="table">
            <div class="tr">
                 <div class="td"></div>
diff --git a/css/CSS2/text/text-indent-on-blank-line-rtl-left-align-ref.html b/css/CSS2/text/text-indent-on-blank-line-rtl-left-align-ref.html
new file mode 100644
index 0000000..15a15b3
--- /dev/null
+++ b/css/CSS2/text/text-indent-on-blank-line-rtl-left-align-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<p>There should be a hotpink square in the bottom left corner of a larger blue square.</p>
+<div style="width:200px; background:blue;">
+  <div style="height:100px;"></div>
+  <div style="width:100px; height:100px; background:hotpink;"></div>
+</div>
diff --git a/css/CSS2/text/text-indent-on-blank-line-rtl-left-align.html b/css/CSS2/text/text-indent-on-blank-line-rtl-left-align.html
new file mode 100644
index 0000000..232065c
--- /dev/null
+++ b/css/CSS2/text/text-indent-on-blank-line-rtl-left-align.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Text-indent on blank line in RTL container with text-align:left</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/text.html#propdef-text-indent" title="16.1 Indentation: the 'text-indent' property">
+<link rel="match" href="text-indent-on-blank-line-rtl-left-align-ref.html">
+<p>There should be a hotpink square in the bottom left corner of a larger blue square.</p>
+<div style="text-align:left; direction:rtl; text-indent:300px; line-height:100px; width:200px; background:blue;">
+  <br>
+  <div style="vertical-align:bottom; display:inline-block; width:100px; height:100px; background:hotpink;"></div>
+</div>
diff --git a/css/CSS2/values/color-000-ref.xht b/css/CSS2/values/color-000-ref.xht
index 7dce9fe..9d46303 100644
--- a/css/CSS2/values/color-000-ref.xht
+++ b/css/CSS2/values/color-000-ref.xht
@@ -18,7 +18,5 @@
 <p>This should be green</p>
 <p>This should be green</p>
 <p>This should be green</p>
-<p>This should be green</p>
-<p>This should be green</p>
 </body>
 </html>
diff --git a/css/CSS2/values/color-000.xht b/css/CSS2/values/color-000.xht
index 34d9131..ea2e3f5 100644
--- a/css/CSS2/values/color-000.xht
+++ b/css/CSS2/values/color-000.xht
@@ -16,11 +16,9 @@
     p#percentpercentnum { color: rgb(100%, 0%, 0) }
     p#percentnumpercent { color: rgb(100%, 0, 0%) }
     p#numpercentnum { color: rgb(255, 0%, 0); }
-    p#floatnumnum { color: rgb(255.0, 0, 0) }
-    p#numfloatnum { color: rgb(0, 128.0, 0) }
     p#numnumnum { color: rgb(0, 128, 0) }
     p#percentpercentpercent1 { color: rgb(0%, 50%, 0%) }
-    p#percentpercentpercent2 { color: rgb(0%, 49.99%, 0%) }
+    p#percentpercentpercent2 { color: rgb(0%, 50.01%, 0%) }
   </style>
  </head>
  <body>
@@ -29,8 +27,6 @@
   <p class="incorrect" id="percentpercentnum">This should be green</p>
   <p class="incorrect" id="percentnumpercent">This should be green</p>
   <p class="incorrect" id="numpercentnum">This should be green</p>
-  <p class="incorrect" id="floatnumnum">This should be green</p>
-  <p class="incorrect" id="numfloatnum">This should be green</p>
 
   <p class="correct" id="numnumnum">This should be green</p>
   <p class="correct" id="percentpercentpercent1">This should be green</p>
diff --git a/css/CSS2/visudet/content-height-001.html b/css/CSS2/visudet/content-height-001.html
new file mode 100644
index 0000000..54fc8da
--- /dev/null
+++ b/css/CSS2/visudet/content-height-001.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 inline level box content height test</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/content-height-001-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of the content area of an inline-level box does not depend on the value of the line-height property">
+<style>
+
+div { font-size: 50px; display: inline-block; color: transparent; }
+
+span { background: blue; }
+
+div { line-height: 200px; }
+div:nth-of-type(2) { line-height: 30px; }
+div:nth-of-type(3) { line-height: normal; }
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<div><span>aa</span></div><div><span>aa</span></div><div><span>aa</span></div>
diff --git a/css/CSS2/visudet/content-height-002.html b/css/CSS2/visudet/content-height-002.html
new file mode 100644
index 0000000..a8e481b
--- /dev/null
+++ b/css/CSS2/visudet/content-height-002.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 inline level box content height test</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/content-height-002-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of the content area of an inline-level box does not depend on the value of the line-height property,
+                             even when fallback fonts are used">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061, U+0020;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062, U+0020;
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+  color: transparent;
+  font-family: high-a-only, deep-b-only;
+}
+
+span { background: blue; }
+
+div { line-height: 200px; }
+div:nth-of-type(2) { line-height: 30px; }
+div:nth-of-type(3) { line-height: normal; }
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<div><span>ab</span></div><div><span>ab</span></div><div><span>ab</span></div>
diff --git a/css/CSS2/visudet/content-height-003.html b/css/CSS2/visudet/content-height-003.html
new file mode 100644
index 0000000..57547d2
--- /dev/null
+++ b/css/CSS2/visudet/content-height-003.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 inline level box content height test</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/content-height-003-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of the content area of an inline-level box does not depend on the value of the line-height property,
+                             even when fallback fonts are used at the exclusion of the first available font">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061, U+0020;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062, U+0020;
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+  color: transparent;
+  font-family: high-a-only, deep-b-only;
+}
+
+span { background: blue; }
+
+div { line-height: 200px; }
+div:nth-of-type(2) { line-height: 30px; }
+div:nth-of-type(3) { line-height: normal; }
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<div><span>bb</span></div><div><span>bb</span></div><div><span>bb</span></div>
diff --git a/css/CSS2/visudet/content-height-004.html b/css/CSS2/visudet/content-height-004.html
new file mode 100644
index 0000000..de6fd6f
--- /dev/null
+++ b/css/CSS2/visudet/content-height-004.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 inline level box content height test</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/content-height-004-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of the content area of an inline-level does not depend on fallback fonts
+                             regardless of whether they are used on not.">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061, U+0020;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062, U+0020;
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+  color: transparent;
+  font-family: high-a-only, deep-b-only;
+}
+
+span { background: blue; }
+
+div { }
+div:nth-of-type(2) { }
+div:nth-of-type(3) { }
+div:nth-of-type(4) { font-family: high-a-only }
+aside {
+  max-width: 300px;
+  overflow: hidden;
+  white-space: pre;
+}
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<aside>
+<div><span>aa</span></div><div><span>bb</span></div><div><span>ab</span></div><div><span>aaa</span></div>
+</aside>
diff --git a/css/CSS2/visudet/content-height-005.html b/css/CSS2/visudet/content-height-005.html
new file mode 100644
index 0000000..6cd8966
--- /dev/null
+++ b/css/CSS2/visudet/content-height-005.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 inline level box content height test</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="mismatch" href="reference/content-height-005-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of the content area of an inline-level depends on the primary font">
+<style>
+@font-face {
+  font-family: 'high';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+}
+@font-face {
+  font-family: 'deep';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+}
+
+span {
+  padding-left: 1em;
+  color: black;
+  border-top: solid 1px;
+  border-bottom: solid 1px;
+}
+
+div:nth-of-type(1) { font-family: deep; }
+div:nth-of-type(2) { font-family: high; margin-left: -1em; }
+</style>
+
+<p>Test passes if there are <strong>more than 2</strong> lines below.
+
+<div><span></span></div><div><span></span></div>
diff --git a/css/CSS2/visudet/inline-block-baseline-001-ref.xht b/css/CSS2/visudet/inline-block-baseline-001-ref.xht
new file mode 100644
index 0000000..f2dddb2
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-001-ref.xht
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     All the <span>words</span> are aligned on the same baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-001.xht b/css/CSS2/visudet/inline-block-baseline-001.xht
new file mode 100644
index 0000000..f756823
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-001.xht
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an 'inline-block’ whose ‘overflow’ property has a computed value of ‘visible’ is the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case the baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-001-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: visible}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     All the <span>words</span> are aligned on the same baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-002.xht b/css/CSS2/visudet/inline-block-baseline-002.xht
new file mode 100644
index 0000000..c1f6996
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-002.xht
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an 'inline-block’ whose ‘overflow’ property has a computed value of ‘visible’ is the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case the baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-001-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: visible; line-height: 1}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     All the <span>&nbsp;<br />words</span> are aligned on the same baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-003.xht b/css/CSS2/visudet/inline-block-baseline-003.xht
new file mode 100644
index 0000000..b499e09
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-003.xht
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an inline-block whose ‘overflow’ property has a computed value not equal to ‘visible’ is the higher of either its bottom margin edge or the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case its baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-001-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: auto;
+    /* The last line box of the inline-block is above its bottom margin edge */
+    line-height: 1; height: 2em; margin-bottom: 0.2em}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     All the <span>words</span> are aligned on the same baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-004.xht b/css/CSS2/visudet/inline-block-baseline-004.xht
new file mode 100644
index 0000000..cdf6c55
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-004.xht
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an inline-block whose ‘overflow’ property has a computed value not equal to ‘visible’ is the higher of either its bottom margin edge or the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case its baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-001-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: hidden;
+    /* The last line box of the inline-block is above its bottom margin edge */
+    line-height: 1; height: 2em; margin-bottom: 0.2em}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     All the <span>words</span> are aligned on the same baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-005.xht b/css/CSS2/visudet/inline-block-baseline-005.xht
new file mode 100644
index 0000000..1ef69f6
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-005.xht
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an inline-block whose ‘overflow’ property has a computed value not equal to ‘visible’ is the higher of either its bottom margin edge or the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case its baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-001-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: auto;
+    /* The last line box of the inline-block is above its bottom margin edge */
+    line-height: 1; height: 2.5em; margin-bottom: 0.2em}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     All the <span>&nbsp;<br />words</span> are aligned on the same baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-006.xht b/css/CSS2/visudet/inline-block-baseline-006.xht
new file mode 100644
index 0000000..9f9d051
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-006.xht
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an inline-block whose ‘overflow’ property has a computed value not equal to ‘visible’ is the higher of either its bottom margin edge or the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case its baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-001-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: hidden;
+    /* The last line box of the inline-block is above its bottom margin edge */
+    line-height: 1; height: 2.5em; margin-bottom: 0.2em}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     All the <span>&nbsp;<br />words</span> are aligned on the same baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-010-ref.xht b/css/CSS2/visudet/inline-block-baseline-010-ref.xht
new file mode 100644
index 0000000..81ffd5e
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-010-ref.xht
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  img {height: 1em; width: 1em}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     A blue square
+     <img src="support/swatch-blue.png" alt="[blue square]" />
+     sits on the baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-010.xht b/css/CSS2/visudet/inline-block-baseline-010.xht
new file mode 100644
index 0000000..600cdd7
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-010.xht
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an 'inline-block’ whose ‘overflow’ property has a computed value of ‘visible’ is the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case the baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-010-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: visible;
+    /* This inline-block has no line boxes, it's aligned at the bottom margin */
+    height: 1em; width: 1em; background: blue}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     A blue square
+     <span></span>
+     sits on the baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-011.xht b/css/CSS2/visudet/inline-block-baseline-011.xht
new file mode 100644
index 0000000..157420d
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-011.xht
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an 'inline-block’ whose ‘overflow’ property has a computed value of ‘visible’ is the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case the baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-010-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: visible;
+    /* This inline-block has no line boxes, it's aligned at the bottom margin */
+    vertical-align: -0.5em; margin-bottom: 0.5em;
+    height: 1em; width: 1em; background: blue}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     A blue square
+     <span></span>
+     sits on the baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-012.xht b/css/CSS2/visudet/inline-block-baseline-012.xht
new file mode 100644
index 0000000..ca37545
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-012.xht
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an inline-block whose ‘overflow’ property has a computed value not equal to ‘visible’ is the higher of either its bottom margin edge or the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case its baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-010-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: auto;
+    /* This inline-block has no line boxes, it's aligned at the bottom margin */
+    height: 1em; width: 1em; background: blue}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     A blue square
+     <span></span>
+     sits on the baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-013.xht b/css/CSS2/visudet/inline-block-baseline-013.xht
new file mode 100644
index 0000000..4dcaad4
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-013.xht
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an inline-block whose ‘overflow’ property has a computed value not equal to ‘visible’ is the higher of either its bottom margin edge or the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case its baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-010-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: hidden;
+    /* This inline-block has no line boxes, it's aligned at the bottom margin */
+    height: 1em; width: 1em; background: blue}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     A blue square
+     <span></span>
+     sits on the baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/inline-block-baseline-014.xht b/css/CSS2/visudet/inline-block-baseline-014.xht
new file mode 100644
index 0000000..b087caa
--- /dev/null
+++ b/css/CSS2/visudet/inline-block-baseline-014.xht
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+  <title>Vertical-align: baseline of an inline-block depends on 'overflow'</title>
+
+  <link rel="help" href="http://www.w3.org/TR/CSS22/visudet.html#leading" title="10.8.1 Leading and half-leading" />
+  <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.10.8.1" title="C.1 Changes since the Recommendation of 7 June 2011" />
+  <meta name="assert" content="The baseline of an inline-block whose ‘overflow’ property has a computed value not equal to ‘visible’ is the higher of either its bottom margin edge or the baseline of its last line box in the normal flow, unless it has no in-flow line boxes, in which case its baseline is the bottom margin edge." />
+  <link rel="match" href="inline-block-baseline-010-ref.xht" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
+
+  <meta name="flags" content="" />
+
+  <style type="text/css"><![CDATA[
+  body {background: white; color: black; font-size: 15px}
+  p {white-space: nowrap; line-height: 5}
+  span {display: inline-block; overflow: hidden;
+    /* This inline-block has no line boxes, it's aligned at the bottom margin */
+    vertical-align: -0.5em; margin-bottom: 0.5em;
+    height: 1em; width: 1em; background: blue}
+  ]]></style>
+ </head>
+
+ <body>
+   <p>
+     A blue square
+     <span></span>
+     sits on the baseline.
+   </p>
+ </body>
+</html>
diff --git a/css/CSS2/visudet/line-height-201.html b/css/CSS2/visudet/line-height-201.html
new file mode 100644
index 0000000..fd65ac9
--- /dev/null
+++ b/css/CSS2/visudet/line-height-201.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 Line height test: explicit sizing</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/line-height-201-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="non-normal values of line-height result in the height of the inline-level box to be exactly the specified dimention,
+                             even when fallback fonts with metrics different from the first available font one are used.">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0020, U+0061;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062;
+}
+
+#red { /* red to be uncovered if the test divs aren't tall enough */
+  position: absolute;
+  background: red;
+  width: 100px;
+  height: 100px;
+}
+
+div:not(#red) {
+  position: absolute;
+  width: 50px;
+  line-height: 100px;
+  font-family: high-a-only, deep-b-only;
+  background: url("support/1x1-green.png") 0 0 / 50px 100px no-repeat, red; /* gets red if the div is too large */
+  color: transparent;
+}
+#test {
+  font-size: 20px; /* With a font-size smaller than the line-height */
+}
+#test2 {
+  margin-left: 50px;
+  font-size: 150px; /* With a font-size larger than the line-height */
+}
+</style>
+
+<p>Test passes if there is a <strong>green square</strong> and <strong>no red</strong> below.
+
+<div id=red></div>
+
+<div id=test>ab</div>
+<div id=test2>ab</div>
diff --git a/css/CSS2/visudet/line-height-202.html b/css/CSS2/visudet/line-height-202.html
new file mode 100644
index 0000000..4d91b53
--- /dev/null
+++ b/css/CSS2/visudet/line-height-202.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 Line height test: baseline position when explicit sizing</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/line-height-202-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The position of the baseline in an inline-level box whose height is determined by a non-normal value of line-height
+                             does not depend on fonts other than the first available font">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0020, U+0061;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062;
+}
+
+div {
+  position: absolute;
+  line-height: 100px;
+  font-size: 100px;
+  width: 300px; /* plenty of room for the (invisible) text */
+  text-align: right;
+  color: transparent;
+}
+span { /* visible thing aligned to the baseline, and small enough to not influence its position */
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+
+}
+
+/* white #h is on top of red #hd,
+   and the presence of a fallback font in #hd should not influence the position of the baseline,
+   so #h should completely cover #hd,
+   and no red should be visible. */
+#hd { font-family: high-a-only, deep-b-only; }
+#hd span { background: red; }
+
+#h { font-family: high-a-only; }
+#h span { background: white; }
+
+</style>
+
+<p>Test passes if there is <strong>no red</strong> below.
+
+<div id=hd>ab<span></span></div>
+<div id=h>aa<span></span></div>
diff --git a/css/CSS2/visudet/line-height-203.html b/css/CSS2/visudet/line-height-203.html
new file mode 100644
index 0000000..cdf0dd7
--- /dev/null
+++ b/css/CSS2/visudet/line-height-203.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 Line height test: baseline position with explicit sizing</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="mismatch" href="reference/line-height-203-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The position of the baseline in an inline-level box whose height is determined by a non-normal value of line-height
+                             does depend on the first available font.">
+<style>
+@font-face {
+  font-family: 'high';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+}
+@font-face {
+  font-family: 'deep';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  position: absolute;
+  line-height: 100px;
+  font-size: 100px;
+  width: 300px; /* plenty of room for the (invisible) text */
+  text-align: right;
+  color: transparent;
+}
+span { /* visible thing aligned to the baseline, and small enough to not influence its position */
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+}
+
+/* white #h is on top of green #d,
+   but as they have different primary fonts,
+   their baselines should not line up and green #d should be visible.*/
+#d { font-family: deep; }
+#d span { background: green; }
+
+#h { font-family: high; }
+#h span { background: white; }
+
+</style>
+
+<p>Test passes if there is a small green rectangle or square below.
+
+<div id=d>aa<span></span></div>
+<div id=h>aa<span></span></div>
diff --git a/css/CSS2/visudet/line-height-204.html b/css/CSS2/visudet/line-height-204.html
new file mode 100644
index 0000000..5f1e970
--- /dev/null
+++ b/css/CSS2/visudet/line-height-204.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS2 Line height test: baseline position, normal sizing vs explicit sizing</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/line-height-202-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The position of the baseline in an inline-level box whose line-height is normal
+                             and the position of the baseline in an inline-level box whose line-height is set to a non normal value resulting in the same height
+                             are the same,
+                             assuming only the first available font is used.">
+<style>
+@font-face {
+  font-family: 'high';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+}
+
+div {
+  position: absolute;
+  font-size: 100px;
+  width: 2em; /* plenty of room for the (invisible) text */
+  text-align: right;
+  color: transparent;
+}
+span { /* visible thing aligned to the baseline, and small enough to not influence its position */
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+}
+
+/* white #lh-auto is on top of red #lh-manual,
+   and as their baselines should line up
+   #lh-manual should be fully covered,
+   and no red should be visible */
+#lh-manual { font-family: high; }
+#lh-manual span { background: red; }
+
+#lh-auto { font-family: high; line-height: normal; }
+#lh-auto span { background: white; }
+
+</style>
+
+<body onload="measure()">
+<p>Test passes if there is <strong>no red</strong> below.
+
+<div id=lh-manual>a<span></span></div>
+<div id=lh-auto>a<span></span></div>
+
+<script>
+function measure() { /* let layout complete first, so that we can measure things */
+  var lha = document.getElementById("lh-auto");
+  var lhm = document.getElementById("lh-manual")
+  var h = window.getComputedStyle(lha).height; /*getting the used-value of line-height by proxy */
+  lhm.style.lineHeight = h;
+  document.documentElement.className = "";
+}
+</script>
diff --git a/css/CSS2/visudet/line-height-205.html b/css/CSS2/visudet/line-height-205.html
new file mode 100644
index 0000000..e07eb73
--- /dev/null
+++ b/css/CSS2/visudet/line-height-205.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 Line height test: normal sizing</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="match" href="reference/line-height-202-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of an inline-level box whose line-height is normal and uses both the first available font and fallback fonts
+                             is the same as the union of
+                             baseline-aligned adjacent inline-level boxes, each using the various fonts as their primary one.">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0020, U+0061;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0020, U+0062;
+}
+
+div {
+  position: absolute;
+  line-height: normal;
+  font-size: 100px;
+  color: transparent;
+}
+
+.h { font-family: high-a-only; }
+.d { font-family: deep-b-only; }
+.hd { font-family: high-a-only, deep-b-only; }
+.white { background: white; }
+.red { background: red; }
+.shift { margin-left: 300px; }
+</style>
+
+<p>Test passes if there is <strong>no red</strong> below.
+
+<!-- Check if the hd div is larger-->
+
+<div class="hd red">ab</div>
+<div class=white><span class=h>a</span><span class=d>b</span></div>
+
+<!-- Same thing, reversed, to check if the hd dix is smaller -->
+
+<div class="red shift"><span class=h>a</span><span class=d>b</span></div>
+<div class="hd white shift">ab</div>
+
diff --git a/css/CSS2/visudet/line-height-206.html b/css/CSS2/visudet/line-height-206.html
new file mode 100644
index 0000000..acf8f42
--- /dev/null
+++ b/css/CSS2/visudet/line-height-206.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 Line height test: normal sizing with fallback fonts</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#line-height">
+<link rel="mismatch" href="reference/line-height-206-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of an inline-level box whose line-height is normal and which only uses glyphs from the fallback font
+                             must still take the strut from the first available font into account.">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061, U+0020;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062, U+0020;
+}
+
+div {
+  position: absolute;
+  line-height: normal;
+  font-size: 100px;
+  color: transparent;
+  border: solid black 1px;
+}
+
+.h { font-family: high-a-only; }
+.dh { font-family: deep-b-only, high-a-only; }
+</style>
+
+<p>There should be two stacked rectangles below: a tall one above a short one, both the same width, sharing the edge at which they touch.
+
+<!-- Both divs show the same content with the same font,
+     but the first div has an unused first available font
+     while the second one does not.
+     As the height calculation takes the strut into account,
+     these two boxes should have different heights -->
+<div class="dh">a</div>
+<div class="h">a</div>
diff --git a/css/CSS2/visudet/reference/content-height-001-ref.html b/css/CSS2/visudet/reference/content-height-001-ref.html
new file mode 100644
index 0000000..d6ca716
--- /dev/null
+++ b/css/CSS2/visudet/reference/content-height-001-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+
+div { font-size: 50px; display: inline-block; color: transparent; }
+
+span { background: blue; }
+
+div { line-height: 200px; }
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<div><span>aa</span></div><div><span>aa</span></div><div><span>aa</span></div>
diff --git a/css/CSS2/visudet/reference/content-height-002-ref.html b/css/CSS2/visudet/reference/content-height-002-ref.html
new file mode 100644
index 0000000..1222f0c
--- /dev/null
+++ b/css/CSS2/visudet/reference/content-height-002-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062;
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+  color: transparent;
+  font-family: high-a-only, deep-b-only;
+}
+
+span { background: blue; }
+
+div { line-height: 200px; }
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<div><span>ab</span></div><div><span>ab</span></div><div><span>ab</span></div>
diff --git a/css/CSS2/visudet/reference/content-height-003-ref.html b/css/CSS2/visudet/reference/content-height-003-ref.html
new file mode 100644
index 0000000..fb27077
--- /dev/null
+++ b/css/CSS2/visudet/reference/content-height-003-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061;
+}
+@font-face {
+  font-family: 'deep-b-only';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062;
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+  color: transparent;
+  font-family: high-a-only, deep-b-only;
+}
+
+span { background: blue; }
+
+div { line-height: 200px; }
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<div><span>bb</span></div><div><span>bb</span></div><div><span>bb</span></div>
diff --git a/css/CSS2/visudet/reference/content-height-004-ref.html b/css/CSS2/visudet/reference/content-height-004-ref.html
new file mode 100644
index 0000000..21b8982
--- /dev/null
+++ b/css/CSS2/visudet/reference/content-height-004-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS2 Line height test: explicit sizing</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061;
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+  color: transparent;
+}
+
+span { background: blue; }
+
+div { font-family: high-a-only }
+aside {
+  max-width: 300px;
+  overflow: hidden;
+  white-space: pre;
+}
+</style>
+
+<p>Test passes if the blue shape below is a rectangle, but not some other polygon.
+
+<aside>
+<div><span>aaaaaaaaa</span></div>
+</aside>
diff --git a/css/CSS2/visudet/reference/content-height-005-ref.html b/css/CSS2/visudet/reference/content-height-005-ref.html
new file mode 100644
index 0000000..b68f8a9
--- /dev/null
+++ b/css/CSS2/visudet/reference/content-height-005-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+
+div {
+  font-size: 50px;
+  display: inline-block;
+}
+
+span {
+  padding-left: 1em;
+  color: black;
+  border-top: solid 1px;
+  border-bottom: solid 1px;
+}
+
+</style>
+
+<p>Test passes if there are <strong>more than 2</strong> lines below.
+
+<div><span></span></div>
diff --git a/css/CSS2/visudet/reference/line-height-201-ref.html b/css/CSS2/visudet/reference/line-height-201-ref.html
new file mode 100644
index 0000000..3564bf7
--- /dev/null
+++ b/css/CSS2/visudet/reference/line-height-201-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+div {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+
+<p>Test passes if there is a <strong>green square</strong> and <strong>no red</strong> below.
+
+<div></div>
diff --git a/css/CSS2/visudet/reference/line-height-202-ref.html b/css/CSS2/visudet/reference/line-height-202-ref.html
new file mode 100644
index 0000000..2b77a6b
--- /dev/null
+++ b/css/CSS2/visudet/reference/line-height-202-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>Test passes if there is <strong>no red</strong> below.
diff --git a/css/CSS2/visudet/reference/line-height-203-ref.html b/css/CSS2/visudet/reference/line-height-203-ref.html
new file mode 100644
index 0000000..ec605c7
--- /dev/null
+++ b/css/CSS2/visudet/reference/line-height-203-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>Test passes if there is a small green rectangle or square below.
diff --git a/css/CSS2/visudet/reference/line-height-206-ref.html b/css/CSS2/visudet/reference/line-height-206-ref.html
new file mode 100644
index 0000000..8ddf2b4
--- /dev/null
+++ b/css/CSS2/visudet/reference/line-height-206-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+@font-face {
+  font-family: 'high-a-only';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061;
+}
+
+div {
+  position: absolute;
+  line-height: normal;
+  font-size: 100px;
+  color: transparent;
+  border: solid black 1px;
+  font-family: high-a-only;
+}
+</style>
+
+<p>There should be two stacked rectangles below: a tall one above a short one, both the same width, sharing the edge at which they touch.
+
+<div>a</div>
diff --git a/css/CSS2/visudet/support/1x1-green.png b/css/CSS2/visudet/support/1x1-green.png
new file mode 100644
index 0000000..b98ca0b
--- /dev/null
+++ b/css/CSS2/visudet/support/1x1-green.png
Binary files differ
diff --git a/css/CSS2/visufx/overflow-applies-to-001-ref.xht b/css/CSS2/visufx/overflow-applies-to-001-ref.xht
index 2a89acf..a8c54ab 100644
--- a/css/CSS2/visufx/overflow-applies-to-001-ref.xht
+++ b/css/CSS2/visufx/overflow-applies-to-001-ref.xht
@@ -3,7 +3,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
   <title>CSS Reference File</title>
-  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org/" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
 
   <style type="text/css">
   #container {width: 20em; background: green; text-align: center;
diff --git a/css/CSS2/visuren/inline-formatting-context-001-ref.xht b/css/CSS2/visuren/inline-formatting-context-001-ref.xht
index 2090c11..42f12fe 100644
--- a/css/CSS2/visuren/inline-formatting-context-001-ref.xht
+++ b/css/CSS2/visuren/inline-formatting-context-001-ref.xht
@@ -3,7 +3,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
   <title>CSS Reference File</title>
-  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org/" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
 
   <style type="text/css"><![CDATA[
   #block {display: block; min-width: 25em}
diff --git a/css/CSS2/visuren/inline-formatting-context-001.xht b/css/CSS2/visuren/inline-formatting-context-001.xht
index 43cec46..e6917a2 100644
--- a/css/CSS2/visuren/inline-formatting-context-001.xht
+++ b/css/CSS2/visuren/inline-formatting-context-001.xht
@@ -7,7 +7,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS22/visuren.html#inline-formatting" />
   <link rel="help" href="http://www.w3.org/TR/CSS22/changes.html#s.9.4.2" />
   <link rel="match" href="inline-formatting-context-001-ref.xht" />
-  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org/" />
+  <link rel="author" title="Bert Bos" href="mailto:bert@w3.org" />
   <meta name="assert" content="An inline formatting context is established by a block container box that contains no block-level boxes. In an inline formatting context, boxes are laid out horizontally, one after the other, beginning at the top of a containing block." />
 
   <style type="text/css"><![CDATA[
diff --git a/css/CSS2/zorder/z-index-020-ref2.xht b/css/CSS2/zorder/z-index-020-ref2.xht
new file mode 100644
index 0000000..936116c
--- /dev/null
+++ b/css/CSS2/zorder/z-index-020-ref2.xht
@@ -0,0 +1,102 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <title>CSS Reference: z-index (option B)</title>
+    <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"/>
+    <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
+    <meta name="flags" content="" />
+<style type="text/css">
+.container {
+  z-index:0;
+  position: relative;
+  height: 200px;
+  width: 200px;
+  font-size: 0;
+  line-height: 0;
+  background: silver;
+  border: solid white;
+}
+.container div {
+  height: 80px;
+  width: 80px;
+  padding: 10px;
+}
+
+.control .outline {
+  border: solid fuchsia 5px;
+  width: 110px;
+  height: 85px;
+  padding: 0;
+}
+
+.outline.c1 {
+  margin: 30px 5px 100px;
+}
+.outline.c2 {
+  padding: 0;
+  margin: -20px -45px;
+}
+.outline.c2 > div {
+  margin: -20px 5px 0;
+}
+
+.control div {
+  margin-left: -35px;
+  margin-top: -35px;
+}
+.control > div {
+  margin-left: auto;
+  margin-top: 100px;
+}
+</style>
+</head>
+<body>
+
+<div class="control container">
+  <div style="background: navy">
+    <div style="background: blue">
+       <div style="background: aqua">
+         <div class="outline c2">
+           <div style="background: lime">
+             <div style="background: teal">
+             </div>
+           </div>
+         </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<p>The pattern above must match one of the two patterns below.</p>
+
+<div class="control container">
+  <div style="background: navy">
+    <div style="background: blue">
+       <div style="background: aqua">
+         <div style="background: lime">
+           <div style="background: teal">
+             <div class="outline c1">
+             </div>
+           </div>
+         </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="control container">
+  <div style="background: navy">
+    <div style="background: blue">
+       <div style="background: aqua">
+         <div class="outline c2">
+           <div style="background: lime">
+             <div style="background: teal">
+             </div>
+           </div>
+         </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+</body></html>
diff --git a/css/CSS2/zorder/z-index-020.xht b/css/CSS2/zorder/z-index-020.xht
index c50cc59..d882d85 100644
--- a/css/CSS2/zorder/z-index-020.xht
+++ b/css/CSS2/zorder/z-index-020.xht
@@ -7,6 +7,7 @@
     <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
     <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#z-index" />
     <link rel="match" href="z-index-020-ref.xht"/>
+    <link rel="match" href="z-index-020-ref2.xht"/>
     <meta name="flags" content="" />
 <style type="text/css">
 .container {
diff --git a/css/README.md b/css/README.md
index 4967f8c..959bab6 100644
--- a/css/README.md
+++ b/css/README.md
@@ -67,5 +67,5 @@
 
 
 [harness]: https://test.csswg.org/harness/
-[spec-link]: http://web-platform-tests.org/writing-tests/css-metadata.html#specification-links
+[spec-link]: https://web-platform-tests.org/writing-tests/css-metadata.html#specification-links
 [hg-patch-to-git-patch]: https://raw.githubusercontent.com/mozilla/moz-git-tools/master/hg-patch-to-git-patch
diff --git a/css/build-css-testsuites.sh b/css/build-css-testsuites.sh
index cbe38b7..b72c486 100755
--- a/css/build-css-testsuites.sh
+++ b/css/build-css-testsuites.sh
@@ -2,11 +2,11 @@
 set -ex
 
 SCRIPT_DIR=$(dirname $(readlink -f "$0"))
-WPT_ROOT=$(readlink -f $SCRIPT_DIR/../..)
+WPT_ROOT=$(readlink -f $SCRIPT_DIR/..)
 cd $WPT_ROOT
 
 main() {
-    cd css
+    cd $WPT_ROOT/css
 
     if [ -z $VENV ]; then
         VENV=tools/_virtualenv
@@ -41,17 +41,14 @@
     # Install dependencies
     $VENV/bin/pip install -r requirements.txt
 
-    # Fetch hg submodules if they're not there
-    if [ ! -d tools/apiclient ]; then
-        $VENV/bin/hg clone https://hg.csswg.org/dev/apiclient tools/apiclient
-    fi
-
-    if [ ! -d tools/w3ctestlib ]; then
-        $VENV/bin/hg clone https://hg.csswg.org/dev/w3ctestlib tools/w3ctestlib
+    # Error if submodules are not there
+    if [ ! -d tools/apiclient -o ! -d tools/w3ctestlib ]; then
+        echo 'Please run `git submodule update --init --recursive`'
+        exit 1
     fi
 
     # Run the build script
     $VENV/bin/python tools/build.py "$@"
 }
 
-main
+main "$@"
diff --git a/css/compositing/mix-blend-mode/mix-blend-mode-transition.html b/css/compositing/mix-blend-mode/mix-blend-mode-transition.html
deleted file mode 100644
index e3d3501..0000000
--- a/css/compositing/mix-blend-mode/mix-blend-mode-transition.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>CSS Test: Blended element with transition</title>
-        <link rel="author" title="Mihai Tica" href="mailto:mitica@adobe.com">
-        <link rel="help" href="https://drafts.fxtf.org/compositing-1/#mix-blend-mode">
-        <meta name="assert" content="Test checks that an element having a transition applied on opacity blends with the parent element.">
-        <meta name="flags" content="dom"/>
-        <link rel="reviewer" title="Rik Cabanier" href="mailto:cabanier@adobe.com">
-        <link rel="reviewer" title="Mirela Budaes" href="mailto:mbudaes@adobe.com">
-        <link rel="match" href="reference/mix-blend-mode-transition-ref.html">
-        <style type="text/css">
-            div {
-                width: 100px;
-                height: 100px;
-                background: #FF0;
-            }
-
-            #blender {
-                background: #F00;
-                mix-blend-mode: difference;
-                transition: opacity 1s ease;
-            }
-
-            .opaqueBox {
-                opacity: 0.4;
-            }
-        </style>
-    </head>
-    <body>
-        <p>Test passes if you can see a fading green rectangle.</p>
-        <div><div id="blender"></div></div>
-        <script type="text/javascript">
-            setInterval(function(){ document.getElementById('blender').className = 'opaqueBox'; }, 100);
-        </script>
-    </body>
-</html>
diff --git a/css/compositing/mix-blend-mode/reference/mix-blend-mode-transition-ref.html b/css/compositing/mix-blend-mode/reference/mix-blend-mode-transition-ref.html
deleted file mode 100644
index bb7d020..0000000
--- a/css/compositing/mix-blend-mode/reference/mix-blend-mode-transition-ref.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>CSS Test: Blended element with transition</title>
-        <link rel="author" title="Mihai Tica" href="mailto:mitica@adobe.com">
-        <meta name="flags" content="dom"/>
-        <style type="text/css">
-            div {
-                width: 100px;
-                height: 100px;
-            }
-
-            #blender {
-                background: #0F0;
-                transition: opacity 1s ease;
-            }
-
-            .opaqueBox {
-                opacity: 0.4;
-            }
-        </style>
-    </head>
-    <body>
-        <p>Test passes if you can see a fading green rectangle.</p>
-        <div style="background: #FF0;"><div id="blender"></div></div>
-        <script type="text/javascript">
-            setInterval(function(){ document.getElementById('blender').className = 'opaqueBox'; }, 100);
-        </script>
-    </body>
-</html>
diff --git a/css/css-align/OWNERS b/css/css-align/OWNERS
new file mode 100644
index 0000000..e9c4f6b
--- /dev/null
+++ b/css/css-align/OWNERS
@@ -0,0 +1,2 @@
+@dholbert
+@emilio
diff --git a/css/css-align/content-distribution/parse-align-content-001.html b/css/css-align/content-distribution/parse-align-content-001.html
new file mode 100644
index 0000000..c5cd425
--- /dev/null
+++ b/css/css-align/content-distribution/parse-align-content-001.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distribution: align-content - setting values via CSS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-content" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<link rel="stylesheet" href="../../support/alignment.css" >
+<meta name="assert" content="Check that the computed value is the specified value and the JS value is empty."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    let classes = Object.assign({"Normal":"normal"}, contentPositionClasses, distributionClasses,
+                                baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        element = document.createElement("div");
+        element.className = "alignContent" + key;
+        document.body.appendChild(element);
+        test(function() {
+            if (specifiedValue === "first baseline")
+                checkValues(element, "alignContent", "align-content", "", "baseline");
+            else
+                checkValues(element, "alignContent", "align-content", "", specifiedValue);
+        }, "Checking align-content: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/content-distribution/parse-align-content-002.html b/css/css-align/content-distribution/parse-align-content-002.html
new file mode 100644
index 0000000..5d20062
--- /dev/null
+++ b/css/css-align/content-distribution/parse-align-content-002.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distribution: align-content - 'initial' value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-content" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial-values" />
+<meta name="assert" content="Check the 'initial' value in diferent scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+container = document.createElement("div");
+element = document.createElement("div");
+container.appendChild(element);
+document.body.appendChild(container);
+
+test(function() {
+    element = document.createElement("div");
+    document.body.appendChild(element);
+    checkValues(element, "alignContent", "align-content", "", "normal");
+}, "Test 'initial' value when nothing is specified");
+
+test(function() {
+    container.style.display = "";
+    checkInitialValues(element, "alignContent", "align-content", "center", "normal");
+}, "Test align-content: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    checkInitialValues(element, "alignContent", "align-content", "safe start", "normal");
+}, "Test grid items align-content: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    checkInitialValues(element, "alignContent", "align-content", "unsafe end", "normal");
+}, "Test flex items align-content: 'initial'");
+
+test(function() {
+    container.style.display = "";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignContent", "align-content", "start", "normal");
+}, "Test absolute positioned elements align-content: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignContent", "align-content", "end", "normal");
+}, "Test absolute positioned grid items align-content: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignContent", "align-content", "end", "normal");
+}, "Test absolute positioned flex items align-content: 'initial'");
+</script>
diff --git a/css/css-align/content-distribution/parse-align-content-003.html b/css/css-align/content-distribution/parse-align-content-003.html
new file mode 100644
index 0000000..b0da358
--- /dev/null
+++ b/css/css-align/content-distribution/parse-align-content-003.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Disrtribution: align-content - setting values via JS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-content" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<meta name="assert" content="Check that the computed value is the specified value and the same than the JS value."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let classes = Object.assign({"Normal":"normal"}, contentPositionClasses, distributionClasses,
+                                baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        test(function() {
+            element.style.alignContent = "";
+            element.style.alignContent = specifiedValue;
+            if (specifiedValue === "first baseline")
+                checkValues(element, "alignContent", "align-content", "baseline", "baseline");
+            else
+                checkValues(element, "alignContent", "align-content",  specifiedValue, specifiedValue);
+        }, "Checking align-content: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/content-distribution/parse-align-content-004.html b/css/css-align/content-distribution/parse-align-content-004.html
new file mode 100644
index 0000000..6a6f5b9
--- /dev/null
+++ b/css/css-align/content-distribution/parse-align-content-004.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distribution: align-content - invalid values</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-content" />
+<meta name="assert" content="Check bad combinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let values = ["auto", "legacy", "self-start", "left", "safe right"].concat(invalidPositionValues, invalidDistributionValues);
+
+    values.forEach(function(value) {
+        test(function() {
+            checkBadValues(element, "alignContent", "align-content",  value);
+        }, "Checking invalid combination - align-content: " + value);
+    });
+</script>
diff --git a/css/css-align/content-distribution/parse-align-content-005.html b/css/css-align/content-distribution/parse-align-content-005.html
new file mode 100644
index 0000000..fd4fa3c
--- /dev/null
+++ b/css/css-align/content-distribution/parse-align-content-005.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distibution: align-content - inherit value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-content" />
+<meta name="assert" content="Check bad cobinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+    checkInheritValues("alignContent", "align-content", "end");
+}, "Test the value 'inherit' overrides current value ('end')");
+test(function() {
+    checkInheritValues("alignContent", "align-content", "safe start");
+}, "Test the value 'inherit' overrides current value ('safe start')");
+test(function() {
+    checkInheritValues("alignContent", "align-content", "unsafe center");
+}, "Test the value 'inherit' overrides current value ('unsafe center')");
+</script>
diff --git a/css/css-align/content-distribution/parse-justify-content-001.html b/css/css-align/content-distribution/parse-justify-content-001.html
new file mode 100644
index 0000000..df06ca9
--- /dev/null
+++ b/css/css-align/content-distribution/parse-justify-content-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distribution: justify-content - setting values via CSS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-content" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<link rel="stylesheet" href="../../support/alignment.css" >
+<meta name="assert" content="Check that the computed value is the specified value and the JS value is empty."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    let classes = Object.assign({"Normal":"normal", "Left":"left", "Right":"right"}, contentPositionClasses,
+                                distributionClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        element = document.createElement("div");
+        element.className = "justifyContent" + key;
+        document.body.appendChild(element);
+        test(function() {
+            checkValues(element, "justifyContent", "justify-content", "", specifiedValue);
+        }, "Checking justify-content: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/content-distribution/parse-justify-content-002.html b/css/css-align/content-distribution/parse-justify-content-002.html
new file mode 100644
index 0000000..3f48a2a
--- /dev/null
+++ b/css/css-align/content-distribution/parse-justify-content-002.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distribution: justify-content - 'initial' value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-content" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial-values" />
+<meta name="assert" content="Check the 'initial' value in diferent scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+container = document.createElement("div");
+element = document.createElement("div");
+container.appendChild(element);
+document.body.appendChild(container);
+
+test(function() {
+    element = document.createElement("div");
+    document.body.appendChild(element);
+    checkValues(element, "justifyContent", "justify-content", "", "normal");
+}, "Test 'initial' value when nothing is specified");
+
+test(function() {
+    container.style.display = "";
+    checkInitialValues(element, "justifyContent", "justify-content", "center", "normal");
+}, "Test justify-content: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    checkInitialValues(element, "justifyContent", "justify-content", "safe start", "normal");
+}, "Test grid items justify-content: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    checkInitialValues(element, "justifyContent", "justify-content", "unsafe end", "normal");
+}, "Test flex items justify-content: 'initial'");
+
+test(function() {
+    container.style.display = "";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifyContent", "justify-content", "start", "normal");
+}, "Test absolute positioned elements justify-content: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifyContent", "justify-content", "end", "normal");
+}, "Test absolute positioned grid items justify-content: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifyContent", "justify-content", "end", "normal");
+}, "Test absolute positioned flex items justify-content: 'initial'");
+</script>
diff --git a/css/css-align/content-distribution/parse-justify-content-003.html b/css/css-align/content-distribution/parse-justify-content-003.html
new file mode 100644
index 0000000..0dea6e5
--- /dev/null
+++ b/css/css-align/content-distribution/parse-justify-content-003.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Disrtribution: justify-content - setting values via JS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-content" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<meta name="assert" content="Check that the computed value is the specified value and the same than the JS value."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let classes = Object.assign({"Normal":"normal", "Left":"left", "Right":"right"}, contentPositionClasses,
+                                distributionClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        test(function() {
+            element.style.justifyContent = "";
+            element.style.justifyContent = specifiedValue;
+            checkValues(element, "justifyContent", "justify-content",  specifiedValue, specifiedValue);
+        }, "Checking justify-content: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/content-distribution/parse-justify-content-004.html b/css/css-align/content-distribution/parse-justify-content-004.html
new file mode 100644
index 0000000..22e78f8
--- /dev/null
+++ b/css/css-align/content-distribution/parse-justify-content-004.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distribution: justify-content - invalid values</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-content" />
+<meta name="assert" content="Check bad combinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let values = ["auto", "legacy", "self-start", "baseline", "first baseline", "last baseline"].concat(invalidPositionValues, invalidDistributionValues);
+
+    values.forEach(function(value) {
+        test(function() {
+            checkBadValues(element, "justifyContent", "justify-content",  value);
+        }, "Checking invalid combination - justify-content: " + value);
+    });
+</script>
diff --git a/css/css-align/content-distribution/parse-justify-content-005.html b/css/css-align/content-distribution/parse-justify-content-005.html
new file mode 100644
index 0000000..32949e8
--- /dev/null
+++ b/css/css-align/content-distribution/parse-justify-content-005.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Content Distibution: justify-content - inherit value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-content" />
+<meta name="assert" content="Check bad cobinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+    checkInheritValues("justifyContent", "justify-content", "end");
+}, "Test the value 'inherit' overrides current value ('end')");
+test(function() {
+    checkInheritValues("justifyContent", "justify-content", "safe left");
+}, "Test the value 'inherit' overrides current value ('safe left')");
+test(function() {
+    checkInheritValues("justifyContent", "justify-content", "unsafe center");
+}, "Test the value 'inherit' overrides current value ('unsafe center')");
+</script>
diff --git a/css/css-align/content-distribution/place-content-shorthand-001.html b/css/css-align/content-distribution/place-content-shorthand-001.html
index 6e62621..1506333 100644
--- a/css/css-align/content-distribution/place-content-shorthand-001.html
+++ b/css/css-align/content-distribution/place-content-shorthand-001.html
@@ -5,13 +5,14 @@
 <meta name="assert" content="Check that setting a single value to place-content expands to such value set in both 'align-content' and 'justify-content'." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["normal"].concat(contentPositionValues, distributionValues, baselineValues);
-    values.forEach(function(value) {
+    let classes = Object.assign({"Normal":"normal"}, contentPositionClasses, distributionClasses, overflowClasses);
+    for (var key in classes) {
+        let value = classes[key];
         test(function() {
             checkPlaceShorhandLonghands("place-content", "align-content", "justify-content", value);
         }, "Checking place-content: " + value);
-    });
+    }
 </script>
diff --git a/css/css-align/content-distribution/place-content-shorthand-002.html b/css/css-align/content-distribution/place-content-shorthand-002.html
index 90fbb60..589feff 100644
--- a/css/css-align/content-distribution/place-content-shorthand-002.html
+++ b/css/css-align/content-distribution/place-content-shorthand-002.html
@@ -5,15 +5,20 @@
 <meta name="assert" content="Check that setting two values to place-content sets the first one to 'align-content' and the second one to 'justify-content'." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["normal"].concat(contentPositionValues, distributionValues, baselineValues);
-    values.forEach(function(alignValue) {
-        values.forEach(function(justifyValue) {
-           test(function() {
-               checkPlaceShorhandLonghands("place-content", "align-content", "justify-content", alignValue, justifyValue);
-           }, "Checking place-content: " + alignValue + " " + justifyValue);
-        });
-    });
+    let classes = Object.assign({"Normal":"normal"}, contentPositionClasses, distributionClasses, baselineClasses,
+                                overflowClasses);
+    for (var key1 in classes) {
+        let alignValue = classes[key1];
+        let classes2 = Object.assign({"Normal":"normal", "Left":"left", "Right":"right"}, contentPositionClasses,
+                                     distributionClasses);
+        for (var key2 in classes2) {
+            let justifyValue = classes2[key2];
+            test(function() {
+                checkPlaceShorhandLonghands("place-content", "align-content", "justify-content", alignValue, justifyValue);
+            }, "Checking place-content: " + alignValue + " " + justifyValue);
+        }
+    }
 </script>
diff --git a/css/css-align/content-distribution/place-content-shorthand-004.html b/css/css-align/content-distribution/place-content-shorthand-004.html
index 8a1db87..f437410 100644
--- a/css/css-align/content-distribution/place-content-shorthand-004.html
+++ b/css/css-align/content-distribution/place-content-shorthand-004.html
@@ -5,7 +5,7 @@
 <meta name="assert" content="Check that place-content's invalid values are properly detected at parsing time." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
     function checkInvalidValues(value)
@@ -14,27 +14,39 @@
     }
 
     test(function() {
-        checkInvalidValues("center safe")
-        checkInvalidValues("true center")
-    }, "Verify overflow keywords are invalid");
-
-    test(function() {
         checkInvalidValues("center space-between start")
     }, "Verify fallback values are invalid");
 
     test(function() {
-        checkInvalidValues("10px left")
-        checkInvalidValues("right 10%")
+        checkInvalidValues("left")
+        checkInvalidValues("left start")
+        checkInvalidValues("right center")
+    }, "Verify 'left' and 'right' values are invalid for block/cross axis alignment");
+
+    test(function() {
+        checkInvalidValues("start baseline")
+        checkInvalidValues("end last baseline")
+    }, "Verify <baseline-position> values are invalid for the justify-content property");
+
+    test(function() {
+        checkInvalidValues("10px end")
+        checkInvalidValues("start 10%")
     }, "Verify numeric values are invalid");
 
     test(function() {
         checkInvalidValues("auto")
         checkInvalidValues("auto right")
         checkInvalidValues("auto auto")
-        checkInvalidValues("left auto")
+        checkInvalidValues("start auto")
     }, "Verify 'auto' values are invalid");
 
     test(function() {
+        checkInvalidValues("self-start")
+        checkInvalidValues("center self-end")
+        checkInvalidValues("self-end start")
+    }, "Verify self-position values are invalid");
+
+    test(function() {
         checkInvalidValues("")
     }, "Verify empty declaration is invalid");
 </script>
diff --git a/css/css-align/content-distribution/place-content-shorthand-006.html b/css/css-align/content-distribution/place-content-shorthand-006.html
index aa99621..2a5056d 100644
--- a/css/css-align/content-distribution/place-content-shorthand-006.html
+++ b/css/css-align/content-distribution/place-content-shorthand-006.html
@@ -5,17 +5,20 @@
 <meta name="assert" content="Check the place-content's 'specified' and 'resolved' values serialization." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <div id="test"></div>
 <script>
-    var values = ["normal"].concat(contentPositionValues, distributionValues, baselineValues);
-    values.forEach(function(alignValue) {
-        [""].concat(values).forEach(function(justifyValue) {
+    let classes = Object.assign({"Normal":"normal"}, contentPositionClasses, distributionClasses, baselineClasses);
+    for (var key1 in classes) {
+        let alignValue = classes[key1];
+        let classes2 = Object.assign({"Normal":"normal", "Left":"left", "Right":"right"}, contentPositionClasses, distributionClasses);
+        for (var key2 in classes2) {
+            let justifyValue = classes2[key2];
             var value = (alignValue + " " + justifyValue).trim();
             test(function() {
-                checkPlaceShorhand("place-content", alignValue, justifyValue)
+                checkPlaceShorhand("place-content", value, alignValue, justifyValue)
             }, "Checking place-content: " + value);
-        });
-    });
+        }
+    }
 </script>
diff --git a/css/css-align/content-distribution/place-content-shorthand-007-ref.html b/css/css-align/content-distribution/place-content-shorthand-007-ref.html
new file mode 100644
index 0000000..67727f4
--- /dev/null
+++ b/css/css-align/content-distribution/place-content-shorthand-007-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+div {
+  width: 400px;
+  height: 400px;
+  background: blue;
+  position: relative;
+}
+span {
+  background: green;
+  width: 200px;
+  height: 200px;
+  position: absolute;
+  bottom: 0;
+  left: 100px;
+}
+</style>
+Should see a green square centered and at the bottom of the blue square.
+<div><span></span></div>
diff --git a/css/css-align/content-distribution/place-content-shorthand-007.html b/css/css-align/content-distribution/place-content-shorthand-007.html
new file mode 100644
index 0000000..be954c9
--- /dev/null
+++ b/css/css-align/content-distribution/place-content-shorthand-007.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment: place-content shorthand with fallback</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-align/#propdef-place-content">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430622">
+<link rel="match" href="place-content-shorthand-007-ref.html">
+<style>
+div {
+  display: grid;
+  grid: 200px / 200px;
+  width: 400px;
+  height: 400px;
+  background: blue;
+  place-content: end space-evenly;
+}
+span {
+  background: green;
+}
+</style>
+Should see a green square centered and at the bottom of the blue square.
+<div><span></span></div>
diff --git a/css/css-align/default-alignment/parse-align-items-001.html b/css/css-align/default-alignment/parse-align-items-001.html
new file mode 100644
index 0000000..4a7d1b8
--- /dev/null
+++ b/css/css-align/default-alignment/parse-align-items-001.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: align-items - setting values via CSS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-items" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<link rel="stylesheet" href="../../support/alignment.css" >
+<meta name="assert" content="Check that the computed value is the specified value and the JS value is empty."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    let classes = Object.assign({"Normal":"normal", "Stretch":"stretch"}, selfPositionClasses,
+                                baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        element = document.createElement("div");
+        element.className = "alignItems" + key;
+        document.body.appendChild(element);
+        test(function() {
+            if (specifiedValue === "first baseline")
+                checkValues(element, "alignItems", "align-items", "", "baseline");
+            else
+                checkValues(element, "alignItems", "align-items", "", specifiedValue);
+        }, "Checking align-items: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/default-alignment/parse-align-items-002.html b/css/css-align/default-alignment/parse-align-items-002.html
new file mode 100644
index 0000000..79b1d60
--- /dev/null
+++ b/css/css-align/default-alignment/parse-align-items-002.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: align-items - 'initial' value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-items" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial-values" />
+<meta name="assert" content="Check the 'initial' value in diferent scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+container = document.createElement("div");
+element = document.createElement("div");
+container.appendChild(element);
+document.body.appendChild(container);
+
+test(function() {
+    element = document.createElement("div");
+    document.body.appendChild(element);
+    checkValues(element, "alignItems", "align-items", "", "normal");
+}, "Test 'initial' value when nothing is specified");
+
+test(function() {
+    container.style.display = "";
+    checkInitialValues(element, "alignItems", "align-items", "center", "normal");
+}, "Test align-items: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    checkInitialValues(element, "alignItems", "align-items", "safe start", "normal");
+}, "Test grid items align-items: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    checkInitialValues(element, "alignItems", "align-items", "unsafe end", "normal");
+}, "Test flex items align-items: 'initial'");
+
+test(function() {
+    container.style.display = "";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignItems", "align-items", "start", "normal");
+}, "Test absolute positioned elements align-items: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignItems", "align-items", "end", "normal");
+}, "Test absolute positioned grid items align-items: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignItems", "align-items", "end", "normal");
+}, "Test absolute positioned flex items align-items: 'initial'");
+</script>
diff --git a/css/css-align/default-alignment/parse-align-items-003.html b/css/css-align/default-alignment/parse-align-items-003.html
new file mode 100644
index 0000000..aa6b295
--- /dev/null
+++ b/css/css-align/default-alignment/parse-align-items-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: align-items - setting values via JS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-items" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<meta name="assert" content="Check that the computed value is the specified value and the same than the JS value."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let classes = Object.assign({"Normal":"normal", "Stretch":"stretch"}, selfPositionClasses,
+                                baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        test(function() {
+            element.style.alignItems = "";
+            element.style.alignItems = specifiedValue;
+            if (specifiedValue === "first baseline")
+                checkValues(element, "alignItems", "align-items", "baseline", "baseline");
+            else
+                checkValues(element, "alignItems", "align-items",  specifiedValue, specifiedValue);
+        }, "Checking align-items: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/default-alignment/parse-align-items-004.html b/css/css-align/default-alignment/parse-align-items-004.html
new file mode 100644
index 0000000..2cd306a
--- /dev/null
+++ b/css/css-align/default-alignment/parse-align-items-004.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: align-items - invalid values</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-items" />
+<meta name="assert" content="Check bad combinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let values = ["auto", "legacy", "space-around", "left", "safe right"].concat(invalidPositionValues);
+
+    values.forEach(function(value) {
+        test(function() {
+            checkBadValues(element, "alignItems", "align-items",  value);
+        }, "Checking invalid combination - align-items: " + value);
+    });
+</script>
diff --git a/css/css-align/default-alignment/parse-align-items-005.html b/css/css-align/default-alignment/parse-align-items-005.html
new file mode 100644
index 0000000..6dc0ad6
--- /dev/null
+++ b/css/css-align/default-alignment/parse-align-items-005.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: align-items - inherit value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-items" />
+<meta name="assert" content="Check bad cobinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+    checkInheritValues("alignItems", "align-items", "end");
+}, "Test the value 'inherit' overrides current value ('end')");
+test(function() {
+    checkInheritValues("alignItems", "align-items", "safe start");
+}, "Test the value 'inherit' overrides current value ('safe start')");
+test(function() {
+    checkInheritValues("alignItems", "align-items", "unsafe center");
+}, "Test the value 'inherit' overrides current value ('unsafe center')");
+</script>
diff --git a/css/css-align/default-alignment/parse-justify-items-001.html b/css/css-align/default-alignment/parse-justify-items-001.html
new file mode 100644
index 0000000..b2edd2a
--- /dev/null
+++ b/css/css-align/default-alignment/parse-justify-items-001.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: justify-items - setting values via CSS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-items" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<link rel="stylesheet" href="../../support/alignment.css" >
+<meta name="assert" content="Check that the computed value is the specified value and the JS value is empty."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    let classes = Object.assign({"Normal":"normal", "Stretch":"stretch", "Left":"left", "Right":"right"},
+                                selfPositionClasses, baselineClasses, overflowClasses, legacyClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        element = document.createElement("div");
+        element.className = "justifyItems" + key;
+        document.body.appendChild(element);
+        test(function() {
+            if (specifiedValue === "first baseline")
+                checkValues(element, "justifyItems", "justify-items", "", "baseline");
+            else
+                checkValues(element, "justifyItems", "justify-items", "", specifiedValue);
+        }, "Checking justify-items: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/default-alignment/parse-justify-items-002.html b/css/css-align/default-alignment/parse-justify-items-002.html
new file mode 100644
index 0000000..1806fa2
--- /dev/null
+++ b/css/css-align/default-alignment/parse-justify-items-002.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: justify-items - 'initial' value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-items" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial-values" />
+<meta name="assert" content="Check the 'initial' value in diferent scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+container = document.createElement("div");
+element = document.createElement("div");
+container.appendChild(element);
+document.body.appendChild(container);
+
+test(function() {
+    element = document.createElement("div");
+    document.body.appendChild(element);
+    checkValues(element, "justifyItems", "justify-items", "", "normal");
+}, "Test 'initial' value when nothing is specified");
+
+test(function() {
+    container.style.display = "";
+    checkInitialValues(element, "justifyItems", "justify-items", "center", "normal");
+}, "Test justify-items: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    checkInitialValues(element, "justifyItems", "justify-items", "safe start", "normal");
+}, "Test grid items justify-items: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    checkInitialValues(element, "justifyItems", "justify-items", "unsafe end", "normal");
+}, "Test flex items justify-items: 'initial'");
+
+test(function() {
+    container.style.display = "";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifyItems", "justify-items", "start", "normal");
+}, "Test absolute positioned elements justify-items: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifyItems", "justify-items", "end", "normal");
+}, "Test absolute positioned grid items justify-items: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifyItems", "justify-items", "end", "normal");
+}, "Test absolute positioned flex items justify-items: 'initial'");
+</script>
diff --git a/css/css-align/default-alignment/parse-justify-items-003.html b/css/css-align/default-alignment/parse-justify-items-003.html
new file mode 100644
index 0000000..6da7d76
--- /dev/null
+++ b/css/css-align/default-alignment/parse-justify-items-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: justify-items - setting values via JS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-items" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<meta name="assert" content="Check that the computed value is the specified value and the same than the JS value."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let classes = Object.assign({"Normal":"normal", "Stretch":"stretch", "Left":"left", "Right":"right"},
+                                selfPositionClasses, baselineClasses, overflowClasses, legacyClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        test(function() {
+            element.style.justifyItems = "";
+            element.style.justifyItems = specifiedValue;
+            if (specifiedValue === "first baseline")
+                checkValues(element, "justifyItems", "justify-items", "baseline", "baseline");
+            else
+                checkValues(element, "justifyItems", "justify-items",  specifiedValue, specifiedValue);
+        }, "Checking justify-items: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/default-alignment/parse-justify-items-004.html b/css/css-align/default-alignment/parse-justify-items-004.html
new file mode 100644
index 0000000..70e80b2
--- /dev/null
+++ b/css/css-align/default-alignment/parse-justify-items-004.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: justify-items - invalid values</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-items" />
+<meta name="assert" content="Check bad combinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let values = ["auto", "space-around"].concat(invalidPositionValues, invalidLegacyValues);
+
+    values.forEach(function(value) {
+        test(function() {
+            checkBadValues(element, "justifyItems", "justify-items",  value);
+        }, "Checking invalid combination - justify-items: " + value);
+    });
+
+</script>
diff --git a/css/css-align/default-alignment/parse-justify-items-005.html b/css/css-align/default-alignment/parse-justify-items-005.html
new file mode 100644
index 0000000..b4814da
--- /dev/null
+++ b/css/css-align/default-alignment/parse-justify-items-005.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: justify-items - inherit value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-items" />
+<meta name="assert" content="Check the 'inherit' value in different scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+    checkInheritValues("justifyItems", "justify-items", "end");
+}, "Test the value 'inherit' overrides current value ('end')");
+test(function() {
+    checkInheritValues("justifyItems", "justify-items", "safe left");
+}, "Test the value 'inherit' overrides current value ('safe left')");
+test(function() {
+    checkInheritValues("justifyItems", "justify-items", "unsafe center");
+}, "Test the value 'inherit' overrides current value ('unsafe center')");
+</script>
diff --git a/css/css-align/default-alignment/parse-justify-items-006.html b/css/css-align/default-alignment/parse-justify-items-006.html
new file mode 100644
index 0000000..f64b52e
--- /dev/null
+++ b/css/css-align/default-alignment/parse-justify-items-006.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Default-Alignment: justify-items - use of the 'legacy' keyword</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#default-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-items" />
+<meta name="assert" content="Check the use of the 'legacy' keyword in different scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+    checkLegacyValues("justifyItems", "justify-items", "legacy left");
+}, "Test the value justify-items: legacy left");
+test(function() {
+    checkLegacyValues("justifyItems", "justify-items", "legacy center");
+}, "Test the value justify-items: legacy center");
+test(function() {
+    checkLegacyValues("justifyItems", "justify-items", "legacy right");
+}, "Test the value justify-items: legacy right");
+</script>
diff --git a/css/css-align/default-alignment/place-items-shorthand-001.html b/css/css-align/default-alignment/place-items-shorthand-001.html
index 8e1ac7a..5b63c87 100644
--- a/css/css-align/default-alignment/place-items-shorthand-001.html
+++ b/css/css-align/default-alignment/place-items-shorthand-001.html
@@ -5,13 +5,15 @@
 <meta name="assert" content="Check that setting a single value to place-items expands to such value set in both 'align-items' and 'justify-items'." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["normal", "stretch"].concat(selfPositionValues, baselineValues);
-    values.forEach(function(value) {
+    let classes = Object.assign({"Normal":"normal", "Stretch":"stretch"}, selfPositionClasses, baselineClasses,
+                                overflowClasses);
+    for (var key in classes) {
+        let value = classes[key];
         test(function() {
             checkPlaceShorhandLonghands("place-items", "align-items", "justify-items", value);
         }, "Checking place-items: " + value);
-    });
+    }
 </script>
diff --git a/css/css-align/default-alignment/place-items-shorthand-002.html b/css/css-align/default-alignment/place-items-shorthand-002.html
index 1608b70..3a075b2 100644
--- a/css/css-align/default-alignment/place-items-shorthand-002.html
+++ b/css/css-align/default-alignment/place-items-shorthand-002.html
@@ -5,15 +5,19 @@
 <meta name="assert" content="Check that setting two values to place-items sets the first one to 'align-items' and the second one to 'justify-items'." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["normal", "stretch"].concat(selfPositionValues, baselineValues);
-    values.forEach(function(alignValue) {
-       ["auto"].concat(values).forEach(function(justifyValue) {
+    let classes = Object.assign({"Normal":"normal", "Stretch":"stretch"}, selfPositionClasses, baselineClasses,
+                                overflowClasses);
+    for (var key1 in classes) {
+        let alignValue = classes[key1];
+        let classes2 = Object.assign({"Left":"left", "Right":"right"}, legacyClasses, classes);
+        for (var key2 in classes2) {
+           let justifyValue = classes2[key2];
            test(function() {
                checkPlaceShorhandLonghands("place-items", "align-items", "justify-items", alignValue, justifyValue);
            }, "Checking place-items: " + alignValue + " " + justifyValue);
-        });
-    });
+        }
+    }
 </script>
diff --git a/css/css-align/default-alignment/place-items-shorthand-004.html b/css/css-align/default-alignment/place-items-shorthand-004.html
index 07f23ec..f0d245c 100644
--- a/css/css-align/default-alignment/place-items-shorthand-004.html
+++ b/css/css-align/default-alignment/place-items-shorthand-004.html
@@ -5,7 +5,7 @@
 <meta name="assert" content="Check that place-items's invalid values are properly detected at parsing time." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
     function checkInvalidValues(value)
@@ -14,13 +14,14 @@
     }
 
     test(function() {
-        checkInvalidValues("center safe")
-        checkInvalidValues("true center")
-    }, "Verify overflow keywords are invalid");
+        checkInvalidValues("center end start")
+    }, "Verify fallback values are invalid");
 
     test(function() {
-        checkInvalidValues("center space-between start")
-    }, "Verify fallback values are invalid");
+        checkInvalidValues("left")
+        checkInvalidValues("left start")
+        checkInvalidValues("right center")
+    }, "Verify 'left' and 'right' values are invalid for block/cross axis alignment");
 
     test(function() {
         checkInvalidValues("10px left")
@@ -28,12 +29,6 @@
     }, "Verify numeric values are invalid");
 
     test(function() {
-       checkInvalidValues("auto")
-       checkInvalidValues("auto right")
-       checkInvalidValues("auto auto")
-    }, "Verify 'auto' value is invalid as first longhand value.");
-
-    test(function() {
         checkInvalidValues("")
     }, "Verify empty declaration is invalid");
 </script>
diff --git a/css/css-align/default-alignment/place-items-shorthand-006.html b/css/css-align/default-alignment/place-items-shorthand-006.html
index 4af3ad5..4c55b10 100644
--- a/css/css-align/default-alignment/place-items-shorthand-006.html
+++ b/css/css-align/default-alignment/place-items-shorthand-006.html
@@ -5,16 +5,19 @@
 <meta name="assert" content="Check the place-items's 'specified' and 'resolved' values serialization." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["normal", "stretch"].concat(selfPositionValues, baselineValues);
-    values.forEach(function(alignValue) {
-        [""].concat(values).forEach(function(justifyValue) {
+    let classes = Object.assign({"Normal":"normal", "Stretch":"stretch"}, selfPositionClasses, baselineClasses);
+    for (var key1 in classes) {
+        let alignValue = classes[key1];
+        let classes2 = Object.assign({"Left":"left", "Right":"right"}, classes);
+        for (var key2 in classes2) {
+            let justifyValue = classes2[key2];
             var value = (alignValue + " " + justifyValue).trim();
             test(function() {
-                checkPlaceShorhand("place-items", alignValue, justifyValue)
+                checkPlaceShorhand("place-items", value, alignValue, justifyValue)
             }, "Checking place-items: " + value);
-        });
-    });
+        }
+    }
 </script>
diff --git a/css/css-align/default-alignment/shorthand-serialization-001.html b/css/css-align/default-alignment/shorthand-serialization-001.html
new file mode 100644
index 0000000..730b2ad
--- /dev/null
+++ b/css/css-align/default-alignment/shorthand-serialization-001.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset=utf-8>
+  <title>Test serialization of CSS Align shorthand properties</title>
+  <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+  <link rel="help" href="https://drafts.csswg.org/css-align/">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+<script>
+
+var initial_values = {
+    alignContent: "normal",
+    alignItems: "normal",
+    alignSelf: "auto",
+    justifyContent: "normal",
+    justifyItems: "legacy",
+    justifySelf: "auto",
+};
+
+var place_content_test_cases = [
+    {
+        alignContent: "center",
+        shorthand: "center normal",
+    },
+    {
+        alignContent: "baseline safe right",
+        shorthand: "",
+    },
+    {
+        justifyContent: "safe start",
+        shorthand: "normal safe start",
+    },
+    {
+        justifyContent: "unsafe start",
+        shorthand: ["normal start", "normal unsafe start"],
+    },
+    {
+        justifyContent: "space-evenly start",
+        shorthand: "",
+    },
+    {
+        alignContent: "start",
+        justifyContent: "end",
+        shorthand: "start end",
+    },
+];
+
+var place_items_test_cases = [
+    {
+        alignItems: "center",
+        shorthand: "center legacy",
+    },
+    {
+        alignItems: "baseline",
+        shorthand: "baseline legacy",
+    },
+    {
+        justifyItems: "safe start",
+        shorthand: "normal safe start",
+    },
+    {
+        justifyItems: "unsafe start",
+        shorthand: ["normal start", "normal unsafe start"],
+    },
+    {
+        justifyItems: "stretch",
+        shorthand: "normal stretch",
+    },
+    {
+        justifyItems: "left legacy",
+        shorthand: "normal legacy left",
+    },
+    {
+        alignItems: "stretch",
+        justifyItems: "end",
+        shorthand: "stretch end",
+    },
+];
+
+var place_self_test_cases = [
+    {
+        alignSelf: "self-end safe",
+        shorthand: "",
+    },
+    {
+        justifySelf: "unsafe start",
+        shorthand: ["auto start", "auto unsafe start"],
+    },
+    {
+        justifySelf: "last baseline start",
+        shorthand: "",
+    },
+    {
+        alignSelf: "baseline",
+        justifySelf: "last baseline",
+        shorthand: "baseline last baseline",
+    },
+];
+
+function run_tests(test_cases, shorthand, subproperties) {
+    test_cases.forEach(function(test_case) {
+        test(function() {
+            var element = document.createElement('div');
+            document.body.appendChild(element);
+            subproperties.forEach(function(longhand) {
+                element.style[longhand] = test_case[longhand] ||
+                                          initial_values[longhand];
+            });
+            if (Array.isArray(test_case.shorthand)) {
+              assert_in_array(element.style[shorthand], test_case.shorthand);
+            } else {
+              assert_equals(element.style[shorthand], test_case.shorthand);
+            }
+        }, "test shorthand serialization " + JSON.stringify(test_case));
+    });
+}
+
+run_tests(place_content_test_cases, "placeContent", [
+    "alignContent", "justifyContent"]);
+run_tests(place_items_test_cases, "placeItems", [
+    "alignItems", "justifyItems"]);
+run_tests(place_self_test_cases, "placeSelf", [
+    "alignSelf", "justifySelf"]);
+
+</script>
+</body>
+</html>
diff --git a/css/css-align/gaps/column-gap-animation-001.html b/css/css-align/gaps/column-gap-animation-001.html
new file mode 100644
index 0000000..03c0624
--- /dev/null
+++ b/css/css-align/gaps/column-gap-animation-001.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: column-gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that column-gap property is interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes column-gap-0-to-100 {
+    from { column-gap: 0px; }
+    to { column-gap: 100px; }
+  }
+
+  #target {
+    animation-name: column-gap-0-to-100;
+    animation-timing-function: linear;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).columnGap, "50px");
+      }, "column-gap is interpolable");
+  </script>
+</body>
+
diff --git a/css/css-align/gaps/column-gap-animation-002.html b/css/css-align/gaps/column-gap-animation-002.html
new file mode 100644
index 0000000..133b98a
--- /dev/null
+++ b/css/css-align/gaps/column-gap-animation-002.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: column-gap normal test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that 'normal' value for column-gap property is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes column-gap-normal-to-100 {
+    from { column-gap: normal; }
+    to { column-gap: 100px; }
+  }
+
+  #target {
+    animation-name: column-gap-normal-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).columnGap, "100px");
+      }, "column-gap: normal is not interpolable");
+  </script>
+</body>
diff --git a/css/css-align/gaps/column-gap-animation-003.html b/css/css-align/gaps/column-gap-animation-003.html
new file mode 100644
index 0000000..ef8f0e5
--- /dev/null
+++ b/css/css-align/gaps/column-gap-animation-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: Default column-gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that the default value for column-gap property, which is 'normal', is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes column-gap-to-100 {
+    to { column-gap: 100px; }
+  }
+
+  #target {
+    animation-name: column-gap-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).columnGap, "100px");
+      }, "Default column-gap is not interpolable");
+  </script>
+</body>
diff --git a/css/css-align/gaps/column-gap-parsing-001.html b/css/css-align/gaps/column-gap-parsing-001.html
new file mode 100644
index 0000000..a0a92a9
--- /dev/null
+++ b/css/css-align/gaps/column-gap-parsing-001.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: column-gap parsing</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .columnGapPx { column-gap: 12px; }
+  #columnGapEm { column-gap: 2em; font: 10px/1 Monospace; }
+  #columnGapVw { column-gap: 2vw; }
+  #columnGapPercent { column-gap: 15%; }
+  #columnGapCalc { column-gap: calc(10px + 4px); }
+  #columnGapCalcFixedPercent { column-gap: calc(5px + 10%); }
+  .columnGapInitial { column-gap: initial; }
+  .columnGapInherit { column-gap: inherit; }
+
+  #invalidColumnGapNegative { column-gap: -10px; }
+  #invalidColumnGapMaxContent { column-gap: max-content; }
+  #invalidColumnGapNone { column-gap: none; }
+  #invalidColumnGapMultiple { column-gap: 10px 1px; }
+  #invalidColumnGapAngle { column-gap: 3rad; }
+  #invalidColumnGapResolution { column-gap: 2dpi; }
+  #invalidColumnGapTime { column-gap: 200ms; }
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id="default"></div>
+  <div id="columnGapPx" class="columnGapPx"></div>
+  <div id="columnGapEm"></div>
+  <div id="columnGapVw"></div>
+  <div id="columnGapPercent"></div>
+  <div id="columnGapCalc"></div>
+  <div id="columnGapCalcFixedPercent"></div>
+  <div id="columnGapInitial" class="columnGapInitial"></div>
+  <div class="columnGapPx">
+    <div id="columnGapInitialPx" class="columnGapInitial"></div>
+  </div>
+  <div id="columnGapInherit" class="columnGapInherit"></div>
+  <div class="columnGapPx">
+    <div id="columnGapInheritPx" class="columnGapInherit"></div>
+  </div>
+
+  <div id="invalidColumnGapNegative"></div>
+  <div id="invalidColumnGapMaxContent"></div>
+  <div id="invalidColumnGapNone"></div>
+  <div id="invalidColumnGapMultiple"></div>
+  <div id="invalidColumnGapAngle"></div>
+  <div id="invalidColumnGapResolution"></div>
+  <div id="invalidColumnGapTime"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("default");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Default column-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("columnGapPx");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "column-gap accepts pixels");
+    test(
+      function(){
+        var target = document.getElementById("columnGapEm");
+        assert_equals(getComputedStyle(target).columnGap, "20px");
+      }, "column-gap accepts em");
+    test(
+      function(){
+        var target = document.getElementById("columnGapVw");
+        // The columnGap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).columnGap, "normal");
+      }, "column-gap accepts vw");
+    test(
+      function(){
+        var target = document.getElementById("columnGapPercent");
+        assert_equals(getComputedStyle(target).columnGap, "15%");
+      }, "column-gap accepts percentage");
+    test(
+      function(){
+        var target = document.getElementById("columnGapCalc");
+        assert_equals(getComputedStyle(target).columnGap, "14px");
+      }, "column-gap accepts calc()");
+    test(
+      function(){
+        var target = document.getElementById("columnGapCalcFixedPercent");
+        assert_equals(getComputedStyle(target).columnGap, "calc(5px + 10%)");
+      }, "column-gap accepts calc() mixing fixed and percentage values");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInitial");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial column-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInitialPx");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial column-gap is 'normal' 2");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInherit");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial inherited column-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInheritPx");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "column-gap is inheritable");
+
+
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapNegative");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Negative column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapMaxContent");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'max-content' column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapNone");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'none' column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapMultiple");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "column-gap with multiple values is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapAngle");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Angle column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapResolution");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Resolution column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapTime");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Time column-gap is invalid");
+  </script>
+</body>
+
+
diff --git a/css/css-align/gaps/gap-animation-001.html b/css/css-align/gaps/gap-animation-001.html
new file mode 100644
index 0000000..881bb18
--- /dev/null
+++ b/css/css-align/gaps/gap-animation-001.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that gap property is interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes gap-0-to-100 {
+    from { gap: 0px; }
+    to { gap: 100px; }
+  }
+
+  #target {
+    animation-name: gap-0-to-100;
+    animation-timing-function: linear;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).gap, "50px 50px");
+        assert_equals(getComputedStyle(target).rowGap, "50px");
+        assert_equals(getComputedStyle(target).columnGap, "50px");
+      }, "gap is interpolable");
+  </script>
+</body>
+
diff --git a/css/css-align/gaps/gap-animation-002.html b/css/css-align/gaps/gap-animation-002.html
new file mode 100644
index 0000000..44d8c70
--- /dev/null
+++ b/css/css-align/gaps/gap-animation-002.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: gap normal test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that 'normal' value for gap property is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes gap-normal-to-100 {
+    from { gap: normal; }
+    to { gap: 100px; }
+  }
+
+  #target {
+    animation-name: gap-normal-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).gap, "100px 100px");
+        assert_equals(getComputedStyle(target).rowGap, "100px");
+        assert_equals(getComputedStyle(target).columnGap, "100px");
+      }, "gap: normal is not interpolable");
+  </script>
+</body>
diff --git a/css/css-align/gaps/gap-animation-003.html b/css/css-align/gaps/gap-animation-003.html
new file mode 100644
index 0000000..c047946
--- /dev/null
+++ b/css/css-align/gaps/gap-animation-003.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: Default gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that the default value for gap property, which is 'normal', is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes gap-to-100 {
+    to { gap: 100px; }
+  }
+
+  #target {
+    animation-name: gap-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).gap, "100px 100px");
+        assert_equals(getComputedStyle(target).rowGap, "100px");
+        assert_equals(getComputedStyle(target).columnGap, "100px");
+      }, "Default gap is not interpolable");
+  </script>
+</body>
diff --git a/css/css-align/gaps/gap-animation-004.html b/css/css-align/gaps/gap-animation-004.html
new file mode 100644
index 0000000..8b6fbe4
--- /dev/null
+++ b/css/css-align/gaps/gap-animation-004.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that gap property is interpolable for each longhand (row-gap and column-gap) independently.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes gap-100-200-to-200-400 {
+    from { gap: 100px 200px; }
+    to { gap: 200px 400px; }
+  }
+
+  #target {
+    animation-name: gap-100-200-to-200-400;
+    animation-timing-function: linear;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).gap, "150px 300px");
+        assert_equals(getComputedStyle(target).rowGap, "150px");
+        assert_equals(getComputedStyle(target).columnGap, "300px");
+      }, "gap is interpolable");
+  </script>
+</body>
diff --git a/css/css-align/gaps/gap-normal-computed-001.html b/css/css-align/gaps/gap-normal-computed-001.html
new file mode 100644
index 0000000..45b82ac
--- /dev/null
+++ b/css/css-align/gaps/gap-normal-computed-001.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: computed value of normal on *-gap properties</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<meta assert="The computed value of [row-|column-]?gap is normal for all elements it applies to. Checking explicitely because earlier version of the spec called for 0px in some cases.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#col,
+#grid,
+#flex {
+  /* Not using the shorthand because that's not what we're interested in,
+     and there are implementations that support column-gap without supporting the shorthand */
+  colum-gap: normal;
+  row-gap: normal;
+  float: right; /* for shrinkwrap*/
+}
+#col {
+  column-count: 2;
+  column-width: 50px;
+}
+#grid {
+  display: grid;
+  grid-template-columns: 50px 50px;
+  grid-template-rows: 50px 50px;
+}
+#flex {
+  display: flex;
+}
+#flex * { width: 50px; height: 50px;}
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id=col></div>
+  <div id=grid></div>
+  <div id=flex><span></span><span></span></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("col");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "colum-gap:normal computes to normal on multicol elements");
+    test(
+      function(){
+        var target = document.getElementById("col");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "row-gap:normal computes to normal on multicol elements");
+    test(
+      function(){
+        var target = document.getElementById("grid");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "colum-gap:normal computes to normal on grid");
+    test(
+      function(){
+        var target = document.getElementById("grid");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "row-gap:normal computes to normal on grid");
+    test(
+      function(){
+        var target = document.getElementById("flex");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "colum-gap:normal (main axis) computes to normal on flexbox");
+    test(
+      function(){
+        var target = document.getElementById("flex");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "row-gap:normal (cross axis) computes to normal on flexbox");
+  </script>
+</body>
diff --git a/css/css-align/gaps/gap-normal-used-001.html b/css/css-align/gaps/gap-normal-used-001.html
new file mode 100644
index 0000000..3d7ab10
--- /dev/null
+++ b/css/css-align/gaps/gap-normal-used-001.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: used value of *-gap:normal on grid</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<meta assert="The used value of row-gap and column-gap normal for grids is 0">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" />
+<style>
+#grid {
+  colum-gap: normal;
+  row-gap: normal;
+  display: grid;
+  grid-template-columns: 50px 50px;
+  grid-template-rows: 50px 50px;
+
+  position: absolute;
+}
+#grid * { background: green; }
+#red {
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+<div id=grid><span></span><span></span><span></span><span></span></div>
+<div id=red></div>
diff --git a/css/css-align/gaps/gap-normal-used-002.html b/css/css-align/gaps/gap-normal-used-002.html
new file mode 100644
index 0000000..0b40646
--- /dev/null
+++ b/css/css-align/gaps/gap-normal-used-002.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: used value of *-gap:normal on flexbox</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<meta assert="The used value row-gap:normal and column:normal normal is 0px in flexbox">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" />
+<style>
+#flex {
+  colum-gap: normal;
+  row-gap: normal;
+  display: flex;
+  flex-flow: wrap;
+  max-width: 145px; /* more than 100, less than 150, to force wrapping and get 2 items per line*/
+
+  position: absolute;
+}
+#flex * {
+  width: 50px;
+  height: 50px;
+  background: green
+}
+#red {
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+<div id=flex><span></span><span></span><span></span><span></span></div>
+<div id=red></div>
diff --git a/css/css-align/gaps/gap-parsing-001.html b/css/css-align/gaps/gap-parsing-001.html
new file mode 100644
index 0000000..0a2a9bd
--- /dev/null
+++ b/css/css-align/gaps/gap-parsing-001.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: gap shorthand parsing</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#row-row-gap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .gapPx { gap: 12px; }
+  #gapPxTwo { gap: 12px 8px; }
+  #gapPxPercent { gap: 12px 10%; }
+  #gapEm { gap: 2em; font: 10px/1 Monospace; }
+  #gapEmTwo { gap: 2em 4em; font: 10px/1 Monospace; }
+  #gapVw { gap: 2vw; }
+  #gapVwTwo { gap: 2vw 1vh; }
+  #gapPercent { gap: 15%; }
+  #gapPercentTwo { gap: 15% 10%; }
+  #gapCalc { gap: calc(10px + 4px); }
+  #gapCalcFixedPercent { gap: calc(5px + 10%); }
+  #gapCalcTwo { gap: calc(10px + 4px) calc(20px - 8px); }
+  .gapInitial { gap: initial; }
+  .gapInherit { gap: inherit; }
+
+  #invalidGapNegative { gap: -10px; }
+  #invalidGapMaxContent { gap: max-content; }
+  #invalidGapNone { gap: none; }
+  #invalidGapAngle { gap: 3rad; }
+  #invalidGapResolution { gap: 2dpi; }
+  #invalidGapTime { gap: 200ms; }
+  #invalidGapThree { gap: 10px 1px 5px; }
+  #invalidGapSlash { gap: 10px / 5px; }
+  #invalidGapOneWrong { gap: 10px -5px; }
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id="default"></div>
+  <div id="gapPx" class="gapPx"></div>
+  <div id="gapPxTwo"></div>
+  <div id="gapPxPercent"></div>
+  <div id="gapEm"></div>
+  <div id="gapEmTwo"></div>
+  <div id="gapVw"></div>
+  <div id="gapVwTwo"></div>
+  <div id="gapPercent"></div>
+  <div id="gapPercentTwo"></div>
+  <div id="gapCalc"></div>
+  <div id="gapCalcFixedPercent"></div>
+  <div id="gapCalcTwo"></div>
+  <div id="gapInitial" class="gapInitial"></div>
+  <div class="gapPx">
+    <div id="gapInitialPx" class="gapInitial"></div>
+  </div>
+  <div id="gapInherit" class="gapInherit"></div>
+  <div class="gapPx">
+    <div id="gapInheritPx" class="gapInherit"></div>
+  </div>
+
+  <div id="invalidGapNegative"></div>
+  <div id="invalidGapMaxContent"></div>
+  <div id="invalidGapNone"></div>
+  <div id="invalidGapAngle"></div>
+  <div id="invalidGapResolution"></div>
+  <div id="invalidGapTime"></div>
+  <div id="invalidGapThree"></div>
+  <div id="invalidGapSlash"></div>
+  <div id="invalidGapOneWrong"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("default");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Default gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("gapPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "gap accepts pixels");
+    test(
+      function(){
+        var target = document.getElementById("gapPxTwo");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "8px");
+      }, "gap accepts pixels 2");
+    test(
+      function(){
+        var target = document.getElementById("gapPxPercent");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "10%");
+      }, "gap accepts pixels combined with percentage");
+    test(
+      function(){
+        var target = document.getElementById("gapEm");
+        assert_equals(getComputedStyle(target).rowGap, "20px");
+        assert_equals(getComputedStyle(target).columnGap, "20px");
+      }, "gap accepts em");
+    test(
+      function(){
+        var target = document.getElementById("gapEmTwo");
+        assert_equals(getComputedStyle(target).rowGap, "20px");
+        assert_equals(getComputedStyle(target).columnGap, "40px");
+      }, "gap accepts em 2");
+    test(
+      function(){
+        var target = document.getElementById("gapVw");
+        // The gap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).rowGap, "normal");
+        assert_not_equals(getComputedStyle(target).columnGap, "normal");
+      }, "gap accepts vw");
+    test(
+      function(){
+        var target = document.getElementById("gapVwTwo");
+        // The gap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).rowGap, "normal");
+        assert_not_equals(getComputedStyle(target).columnGap, "normal");
+      }, "gap accepts vw and vh");
+    test(
+      function(){
+        var target = document.getElementById("gapPercent");
+        assert_equals(getComputedStyle(target).rowGap, "15%");
+        assert_equals(getComputedStyle(target).columnGap, "15%");
+      }, "gap accepts percentage");
+    test(
+      function(){
+        var target = document.getElementById("gapPercentTwo");
+        assert_equals(getComputedStyle(target).rowGap, "15%");
+        assert_equals(getComputedStyle(target).columnGap, "10%");
+      }, "gap accepts percentage 2");
+    test(
+      function(){
+        var target = document.getElementById("gapCalc");
+        assert_equals(getComputedStyle(target).rowGap, "14px");
+        assert_equals(getComputedStyle(target).columnGap, "14px");
+      }, "gap accepts calc()");
+    test(
+      function(){
+        var target = document.getElementById("gapCalcFixedPercent");
+        assert_equals(getComputedStyle(target).rowGap, "calc(5px + 10%)");
+        assert_equals(getComputedStyle(target).columnGap, "calc(5px + 10%)");
+      }, "gap accepts calc() mixing fixed and percentage values");
+    test(
+      function(){
+        var target = document.getElementById("gapCalcTwo");
+        assert_equals(getComputedStyle(target).rowGap, "14px");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "gap accepts calc() 2");
+    test(
+      function(){
+        var target = document.getElementById("gapInitial");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("gapInitialPx");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial gap is 'normal' 2");
+    test(
+      function(){
+        var target = document.getElementById("gapInherit");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial inherited gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("gapInheritPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "gap is inheritable");
+
+    test(
+      function(){
+        var target = document.getElementById("invalidGapNegative");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Negative gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapMaxContent");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'max-content' gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapNone");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'none' gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapAngle");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Angle gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapResolution");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Resolution gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapTime");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Time gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapThree");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "gap with three values is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapSlash");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "gap with slash is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGapOneWrong");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "gap with one wrong value is invalid");
+  </script>
+</body>
+
+
diff --git a/css/css-align/gaps/grid-column-gap-parsing-001.html b/css/css-align/gaps/grid-column-gap-parsing-001.html
new file mode 100644
index 0000000..66d8199
--- /dev/null
+++ b/css/css-align/gaps/grid-column-gap-parsing-001.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: grid-column-gap parsing</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#gap-legacy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .columnGapPx { grid-column-gap: 12px; }
+  #columnGapEm { grid-column-gap: 2em; font: 10px/1 Monospace; }
+  #columnGapVw { grid-column-gap: 2vw; }
+  #columnGapPercent { grid-column-gap: 15%; }
+  #columnGapCalc { grid-column-gap: calc(10px + 4px); }
+  #columnGapCalcFixedPercent { grid-column-gap: calc(5px + 10%); }
+  .columnGapInitial { grid-column-gap: initial; }
+  .columnGapInherit { grid-column-gap: inherit; }
+
+  #invalidColumnGapNegative { grid-column-gap: -10px; }
+  #invalidColumnGapMaxContent { grid-column-gap: max-content; }
+  #invalidColumnGapNone { grid-column-gap: none; }
+  #invalidColumnGapMultiple { grid-column-gap: 10px 1px; }
+  #invalidColumnGapAngle { grid-column-gap: 3rad; }
+  #invalidColumnGapResolution { grid-column-gap: 2dpi; }
+  #invalidColumnGapTime { grid-column-gap: 200ms; }
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id="default"></div>
+  <div id="columnGapPx" class="columnGapPx"></div>
+  <div id="columnGapEm"></div>
+  <div id="columnGapVw"></div>
+  <div id="columnGapPercent"></div>
+  <div id="columnGapCalc"></div>
+  <div id="columnGapCalcFixedPercent"></div>
+  <div id="columnGapInitial" class="columnGapInitial"></div>
+  <div class="columnGapPx">
+    <div id="columnGapInitialPx" class="columnGapInitial"></div>
+  </div>
+  <div id="columnGapInherit" class="columnGapInherit"></div>
+  <div class="columnGapPx">
+    <div id="columnGapInheritPx" class="columnGapInherit"></div>
+  </div>
+
+  <div id="invalidColumnGapNegative"></div>
+  <div id="invalidColumnGapMaxContent"></div>
+  <div id="invalidColumnGapNone"></div>
+  <div id="invalidColumnGapMultiple"></div>
+  <div id="invalidColumnGapAngle"></div>
+  <div id="invalidColumnGapResolution"></div>
+  <div id="invalidColumnGapTime"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("default");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Default grid-column-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("columnGapPx");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "grid-column-gap accepts pixels");
+    test(
+      function(){
+        var target = document.getElementById("columnGapEm");
+        assert_equals(getComputedStyle(target).columnGap, "20px");
+      }, "grid-column-gap accepts em");
+    test(
+      function(){
+        var target = document.getElementById("columnGapVw");
+        // The columnGap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).columnGap, "normal");
+      }, "grid-column-gap accepts vw");
+    test(
+      function(){
+        var target = document.getElementById("columnGapPercent");
+        assert_equals(getComputedStyle(target).columnGap, "15%");
+      }, "grid-column-gap accepts percentage");
+    test(
+      function(){
+        var target = document.getElementById("columnGapCalc");
+        assert_equals(getComputedStyle(target).columnGap, "14px");
+      }, "grid-column-gap accepts calc()");
+    test(
+      function(){
+        var target = document.getElementById("columnGapCalcFixedPercent");
+        assert_equals(getComputedStyle(target).columnGap, "calc(5px + 10%)");
+      }, "grid-column-gap accepts calc() mixing fixed and percentage values");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInitial");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial grid-column-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInitialPx");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial grid-column-gap is 'normal' 2");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInherit");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial inherited grid-column-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("columnGapInheritPx");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "grid-column-gap is inheritable");
+
+
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapNegative");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Negative grid-column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapMaxContent");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'max-content' grid-column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapNone");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'none' grid-column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapMultiple");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "grid-column-gap with multiple values is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapAngle");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Angle grid-column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapResolution");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Resolution grid-column-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidColumnGapTime");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Time grid-column-gap is invalid");
+  </script>
+</body>
+
+
diff --git a/css/css-align/gaps/grid-gap-parsing-001.html b/css/css-align/gaps/grid-gap-parsing-001.html
new file mode 100644
index 0000000..aa43f77
--- /dev/null
+++ b/css/css-align/gaps/grid-gap-parsing-001.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: gap shorthand parsing</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#gap-legacy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .gapPx { grid-gap: 12px; }
+  #gapPxTwo { grid-gap: 12px 8px; }
+  #gapPxPercent { grid-gap: 12px 10%; }
+  #gapEm { grid-gap: 2em; font: 10px/1 Monospace; }
+  #gapEmTwo { grid-gap: 2em 4em; font: 10px/1 Monospace; }
+  #gapVw { grid-gap: 2vw; }
+  #gapVwTwo { grid-gap: 2vw 1vh; }
+  #gapPercent { grid-gap: 15%; }
+  #gapPercentTwo { grid-gap: 15% 10%; }
+  #gapCalc { grid-gap: calc(10px + 4px); }
+  #gapCalcFixedPercent { grid-gap: calc(5px + 10%); }
+  #gapCalcTwo { grid-gap: calc(10px + 4px) calc(20px - 8px); }
+  .gapInitial { grid-gap: initial; }
+  .gapInherit { grid-gap: inherit; }
+
+  #invalidGridGapNegative { grid-gap: -10px; }
+  #invalidGridGapMaxContent { grid-gap: max-content; }
+  #invalidGridGapNone { grid-gap: none; }
+  #invalidGridGapAngle { grid-gap: 3rad; }
+  #invalidGridGapResolution { grid-gap: 2dpi; }
+  #invalidGridGapTime { grid-gap: 200ms; }
+  #invalidGridGapThree { grid-gap: 10px 1px 5px; }
+  #invalidGridGapSlash { grid-gap: 10px / 5px; }
+  #invalidGridGapOneWrong { grid-gap: 10px -5px; }
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id="default"></div>
+  <div id="gapPx" class="gapPx"></div>
+  <div id="gapPxTwo"></div>
+  <div id="gapPxPercent"></div>
+  <div id="gapEm"></div>
+  <div id="gapEmTwo"></div>
+  <div id="gapVw"></div>
+  <div id="gapVwTwo"></div>
+  <div id="gapPercent"></div>
+  <div id="gapPercentTwo"></div>
+  <div id="gapCalc"></div>
+  <div id="gapCalcFixedPercent"></div>
+  <div id="gapCalcTwo"></div>
+  <div id="gapInitial" class="gapInitial"></div>
+  <div class="gapPx">
+    <div id="gapInitialPx" class="gapInitial"></div>
+  </div>
+  <div id="gapInherit" class="gapInherit"></div>
+  <div class="gapPx">
+    <div id="gapInheritPx" class="gapInherit"></div>
+  </div>
+
+  <div id="invalidGridGapNegative"></div>
+  <div id="invalidGridGapMaxContent"></div>
+  <div id="invalidGridGapNone"></div>
+  <div id="invalidGridGapAngle"></div>
+  <div id="invalidGridGapResolution"></div>
+  <div id="invalidGridGapTime"></div>
+  <div id="invalidGridGapThree"></div>
+  <div id="invalidGridGapSlash"></div>
+  <div id="invalidGridGapOneWrong"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("default");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Default grid-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("gapPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "grid-gap accepts pixels");
+    test(
+      function(){
+        var target = document.getElementById("gapPxTwo");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "8px");
+      }, "grid-gap accepts pixels 2");
+    test(
+      function(){
+        var target = document.getElementById("gapPxPercent");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "10%");
+      }, "grid-gap accepts pixels combined with percentage");
+    test(
+      function(){
+        var target = document.getElementById("gapEm");
+        assert_equals(getComputedStyle(target).rowGap, "20px");
+        assert_equals(getComputedStyle(target).columnGap, "20px");
+      }, "grid-gap accepts em");
+    test(
+      function(){
+        var target = document.getElementById("gapEmTwo");
+        assert_equals(getComputedStyle(target).rowGap, "20px");
+        assert_equals(getComputedStyle(target).columnGap, "40px");
+      }, "grid-gap accepts em 2");
+    test(
+      function(){
+        var target = document.getElementById("gapVw");
+        // The gap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).rowGap, "normal");
+        assert_not_equals(getComputedStyle(target).columnGap, "normal");
+      }, "grid-gap accepts vw");
+    test(
+      function(){
+        var target = document.getElementById("gapVwTwo");
+        // The gap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).rowGap, "normal");
+        assert_not_equals(getComputedStyle(target).columnGap, "normal");
+      }, "grid-gap accepts vw and vh");
+    test(
+      function(){
+        var target = document.getElementById("gapPercent");
+        assert_equals(getComputedStyle(target).rowGap, "15%");
+        assert_equals(getComputedStyle(target).columnGap, "15%");
+      }, "grid-gap accepts percentage");
+    test(
+      function(){
+        var target = document.getElementById("gapPercentTwo");
+        assert_equals(getComputedStyle(target).rowGap, "15%");
+        assert_equals(getComputedStyle(target).columnGap, "10%");
+      }, "grid-gap accepts percentage 2");
+    test(
+      function(){
+        var target = document.getElementById("gapCalc");
+        assert_equals(getComputedStyle(target).rowGap, "14px");
+        assert_equals(getComputedStyle(target).columnGap, "14px");
+      }, "grid-gap accepts calc()");
+    test(
+      function(){
+        var target = document.getElementById("gapCalcFixedPercent");
+        assert_equals(getComputedStyle(target).rowGap, "calc(5px + 10%)");
+        assert_equals(getComputedStyle(target).columnGap, "calc(5px + 10%)");
+      }, "grid-gap accepts calc() mixing fixed and percentage values");
+    test(
+      function(){
+        var target = document.getElementById("gapCalcTwo");
+        assert_equals(getComputedStyle(target).rowGap, "14px");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "grid-gap accepts calc() 2");
+    test(
+      function(){
+        var target = document.getElementById("gapInitial");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial grid-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("gapInitialPx");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial grid-gap is 'normal' 2");
+    test(
+      function(){
+        var target = document.getElementById("gapInherit");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Initial inherited grid-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("gapInheritPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+        assert_equals(getComputedStyle(target).columnGap, "12px");
+      }, "grid-gap is inheritable");
+
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapNegative");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Negative grid-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapMaxContent");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'max-content' grid-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapNone");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "'none' grid-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapAngle");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Angle grid-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapResolution");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Resolution grid-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapTime");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "Time grid-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapThree");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "grid-gap with three values is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapSlash");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "grid-gap with slash is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidGridGapOneWrong");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+        assert_equals(getComputedStyle(target).columnGap, "normal");
+      }, "grid-gap with one wrong value is invalid");
+  </script>
+</body>
+
+
diff --git a/css/css-align/gaps/grid-row-gap-parsing-001.html b/css/css-align/gaps/grid-row-gap-parsing-001.html
new file mode 100644
index 0000000..e394ea1
--- /dev/null
+++ b/css/css-align/gaps/grid-row-gap-parsing-001.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: grid-row-gap parsing</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#gap-legacy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .rowGapPx { grid-row-gap: 12px; }
+  #rowGapEm { grid-row-gap: 2em; font: 10px/1 Monospace; }
+  #rowGapVw { grid-row-gap: 2vw; }
+  #rowGapPercent { grid-row-gap: 15%; }
+  #rowGapCalc { grid-row-gap: calc(10px + 4px); }
+  #rowGapCalcFixedPercent { grid-row-gap: calc(5px + 10%); }
+  .rowGapInitial { grid-row-gap: initial; }
+  .rowGapInherit { grid-row-gap: inherit; }
+
+  #invalidRowGapNegative { grid-row-gap: -10px; }
+  #invalidRowGapMaxContent { grid-row-gap: max-content; }
+  #invalidRowGapNone { grid-row-gap: none; }
+  #invalidRowGapMultiple { grid-row-gap: 10px 1px; }
+  #invalidRowGapAngle { grid-row-gap: 3rad; }
+  #invalidRowGapResolution { grid-row-gap: 2dpi; }
+  #invalidRowGapTime { grid-row-gap: 200ms; }
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id="default"></div>
+  <div id="rowGapPx" class="rowGapPx"></div>
+  <div id="rowGapEm"></div>
+  <div id="rowGapVw"></div>
+  <div id="rowGapPercent"></div>
+  <div id="rowGapCalc"></div>
+  <div id="rowGapCalcFixedPercent"></div>
+  <div id="rowGapInitial" class="rowGapInitial"></div>
+  <div class="rowGapPx">
+    <div id="rowGapInitialPx" class="rowGapInitial"></div>
+  </div>
+  <div id="rowGapInherit" class="rowGapInherit"></div>
+  <div class="rowGapPx">
+    <div id="rowGapInheritPx" class="rowGapInherit"></div>
+  </div>
+
+  <div id="invalidRowGapNegative"></div>
+  <div id="invalidRowGapMaxContent"></div>
+  <div id="invalidRowGapNone"></div>
+  <div id="invalidRowGapMultiple"></div>
+  <div id="invalidRowGapAngle"></div>
+  <div id="invalidRowGapResolution"></div>
+  <div id="invalidRowGapTime"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("default");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Default grid-row-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("rowGapPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+      }, "grid-row-gap accepts pixels");
+    test(
+      function(){
+        var target = document.getElementById("rowGapEm");
+        assert_equals(getComputedStyle(target).rowGap, "20px");
+      }, "grid-row-gap accepts em");
+    test(
+      function(){
+        var target = document.getElementById("rowGapVw");
+        // The rowGap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).rowGap, "normal");
+      }, "grid-row-gap accepts vw");
+    test(
+      function(){
+        var target = document.getElementById("rowGapPercent");
+        assert_equals(getComputedStyle(target).rowGap, "15%");
+      }, "grid-row-gap accepts percentage");
+    test(
+      function(){
+        var target = document.getElementById("rowGapCalc");
+        assert_equals(getComputedStyle(target).rowGap, "14px");
+      }, "grid-row-gap accepts calc()");
+    test(
+      function(){
+        var target = document.getElementById("rowGapCalcFixedPercent");
+        assert_equals(getComputedStyle(target).rowGap, "calc(5px + 10%)");
+      }, "grid-row-gap accepts calc() mixing fixed and percentage values");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInitial");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Initial grid-row-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInitialPx");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Initial grid-row-gap is 'normal' 2");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInherit");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Initial inherited grid-row-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInheritPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+      }, "grid-row-gap is inheritable");
+
+
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapNegative");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Negative grid-row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapMaxContent");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "'max-content' grid-row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapNone");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "'none' grid-row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapMultiple");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "grid-row-gap with multiple values is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapAngle");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Angle grid-row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapResolution");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Resolution grid-row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapTime");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Time grid-row-gap is invalid");
+  </script>
+</body>
+
+
diff --git a/css/css-align/gaps/row-gap-animation-001.html b/css/css-align/gaps/row-gap-animation-001.html
new file mode 100644
index 0000000..da4a235
--- /dev/null
+++ b/css/css-align/gaps/row-gap-animation-001.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: row-gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that row-gap property is interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes row-gap-0-to-100 {
+    from { row-gap: 0px; }
+    to { row-gap: 100px; }
+  }
+
+  #target {
+    animation-name: row-gap-0-to-100;
+    animation-timing-function: linear;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).rowGap, "50px");
+      }, "row-gap is interpolable");
+  </script>
+</body>
+
diff --git a/css/css-align/gaps/row-gap-animation-002.html b/css/css-align/gaps/row-gap-animation-002.html
new file mode 100644
index 0000000..330e105
--- /dev/null
+++ b/css/css-align/gaps/row-gap-animation-002.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: row-gap normal test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that 'normal' value for row-gap property is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes row-gap-normal-to-100 {
+    from { row-gap: normal; }
+    to { row-gap: 100px; }
+  }
+
+  #target {
+    animation-name: row-gap-normal-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).rowGap, "100px");
+      }, "row-gap: normal is not interpolable");
+  </script>
+</body>
diff --git a/css/css-align/gaps/row-gap-animation-003.html b/css/css-align/gaps/row-gap-animation-003.html
new file mode 100644
index 0000000..574e3ff
--- /dev/null
+++ b/css/css-align/gaps/row-gap-animation-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: Default row-gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<link rel="help" href="https://www.w3.org/TR/css-animations-1/#keyframes">
+<meta name="assert" content="This test checks that the default value for row-gap property, which is 'normal', is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes row-gap-to-100 {
+    to { row-gap: 100px; }
+  }
+
+  #target {
+    animation-name: row-gap-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="target"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("target");
+        assert_equals(getComputedStyle(target).rowGap, "100px");
+      }, "Default row-gap is not interpolable");
+  </script>
+</body>
diff --git a/css/css-align/gaps/row-gap-parsing-001.html b/css/css-align/gaps/row-gap-parsing-001.html
new file mode 100644
index 0000000..71b971d
--- /dev/null
+++ b/css/css-align/gaps/row-gap-parsing-001.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: row-gap parsing</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#column-row-gap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .rowGapPx { row-gap: 12px; }
+  #rowGapEm { row-gap: 2em; font: 10px/1 Monospace; }
+  #rowGapVw { row-gap: 2vw; }
+  #rowGapPercent { row-gap: 15%; }
+  #rowGapCalc { row-gap: calc(10px + 4px); }
+  #rowGapCalcFixedPercent { row-gap: calc(5px + 10%); }
+  .rowGapInitial { row-gap: initial; }
+  .rowGapInherit { row-gap: inherit; }
+
+  #invalidRowGapNegative { row-gap: -10px; }
+  #invalidRowGapMaxContent { row-gap: max-content; }
+  #invalidRowGapNone { row-gap: none; }
+  #invalidRowGapMultiple { row-gap: 10px 1px; }
+  #invalidRowGapAngle { row-gap: 3rad; }
+  #invalidRowGapResolution { row-gap: 2dpi; }
+  #invalidRowGapTime { row-gap: 200ms; }
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id="default"></div>
+  <div id="rowGapPx" class="rowGapPx"></div>
+  <div id="rowGapEm"></div>
+  <div id="rowGapVw"></div>
+  <div id="rowGapPercent"></div>
+  <div id="rowGapCalc"></div>
+  <div id="rowGapCalcFixedPercent"></div>
+  <div id="rowGapInitial" class="rowGapInitial"></div>
+  <div class="rowGapPx">
+    <div id="rowGapInitialPx" class="rowGapInitial"></div>
+  </div>
+  <div id="rowGapInherit" class="rowGapInherit"></div>
+  <div class="rowGapPx">
+    <div id="rowGapInheritPx" class="rowGapInherit"></div>
+  </div>
+
+  <div id="invalidRowGapNegative"></div>
+  <div id="invalidRowGapMaxContent"></div>
+  <div id="invalidRowGapNone"></div>
+  <div id="invalidRowGapMultiple"></div>
+  <div id="invalidRowGapAngle"></div>
+  <div id="invalidRowGapResolution"></div>
+  <div id="invalidRowGapTime"></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("default");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Default row-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("rowGapPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+      }, "row-gap accepts pixels");
+    test(
+      function(){
+        var target = document.getElementById("rowGapEm");
+        assert_equals(getComputedStyle(target).rowGap, "20px");
+      }, "row-gap accepts em");
+    test(
+      function(){
+        var target = document.getElementById("rowGapVw");
+        // The rowGap size would depend on the viewport width, so to make the test pass
+        // in any window size we just check it's not "normal".
+        assert_not_equals(getComputedStyle(target).rowGap, "normal");
+      }, "row-gap accepts vw");
+    test(
+      function(){
+        var target = document.getElementById("rowGapPercent");
+        assert_equals(getComputedStyle(target).rowGap, "15%");
+      }, "row-gap accepts percentage");
+    test(
+      function(){
+        var target = document.getElementById("rowGapCalc");
+        assert_equals(getComputedStyle(target).rowGap, "14px");
+      }, "row-gap accepts calc()");
+    test(
+      function(){
+        var target = document.getElementById("rowGapCalcFixedPercent");
+        assert_equals(getComputedStyle(target).rowGap, "calc(5px + 10%)");
+      }, "row-gap accepts calc() mixing fixed and percentage values");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInitial");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Initial row-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInitialPx");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Initial row-gap is 'normal' 2");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInherit");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Initial inherited row-gap is 'normal'");
+    test(
+      function(){
+        var target = document.getElementById("rowGapInheritPx");
+        assert_equals(getComputedStyle(target).rowGap, "12px");
+      }, "row-gap is inheritable");
+
+
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapNegative");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Negative row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapMaxContent");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "'max-content' row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapNone");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "'none' row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapMultiple");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "row-gap with multiple values is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapAngle");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Angle row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapResolution");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Resolution row-gap is invalid");
+    test(
+      function(){
+        var target = document.getElementById("invalidRowGapTime");
+        assert_equals(getComputedStyle(target).rowGap, "normal");
+      }, "Time row-gap is invalid");
+  </script>
+</body>
+
+
diff --git a/css/css-align/resources/alignment-parsing-utils.js b/css/css-align/resources/alignment-parsing-utils.js
index 9084624..f549bed 100644
--- a/css/css-align/resources/alignment-parsing-utils.js
+++ b/css/css-align/resources/alignment-parsing-utils.js
@@ -1,28 +1,43 @@
-var selfPositionValues = [ "start", "end", "self-start", "self-end", "left", "right", "center", "flex-start", "flex-end"];
-var contentPositionValues = [ "start", "end", "left", "right", "center", "flex-start", "flex-end"];
-var distributionValues = [ "stretch", "space-around", "space-between", "space-evenly"];
-var baselineValues = [ "baseline", "first baseline", "last baseline"];
+var selfPositionClasses = {"Start":"start", "End":"end", "SelfStart":"self-start", "SelfEnd":"self-end", "Center":"center", "FlexStart":"flex-start", "FlexEnd":"flex-end"};
+var contentPositionClasses = {"Start":"start", "End":"end", "Center":"center", "FlexStart":"flex-start", "FlexEnd":"flex-end"};
+var distributionClasses = {"Stretch":"stretch", "SpaceAround":"space-around", "SpaceBetween":"space-between", "SpaceEvenly":"space-evenly"};
+var baselineClasses = {"Baseline":"baseline", "FirstBaseline":"first baseline", "LastBaseline":"last baseline"};
+var overflowClasses = {"SafeFlexEnd":"safe flex-end", "UnsafeEnd":"unsafe end", "SafeEnd":"safe end", "UnsafeFlexStart":"unsafe flex-start", "SafeCenter":"safe center"};
+var legacyClasses = {"LegacyLeft":"legacy left", "LegacyCenter":"legacy center", "LegacyRight":"legacy right"};
 
-function checkPlaceShorhand(shorthand, alignValue, justifyValue)
+var invalidPositionValues = ["auto safe", "auto left", "normal unsafe", "normal stretch", "baseline normal",
+                             "baseline center", "first baseline center", "last baseline center", "baseline last",
+                             "baseline first", "stretch unsafe", "stretch right", "unsafe unsafe", "unsafe safe",
+                             "center start", "unsafe stretch", "safe stretch", "baseline safe", "unsafe baseline",
+                             "unsafe safe left", "unsafe left safe", "left safe unsafe safe", "start safe", "safe"];
+var invalidLegacyValues = ["legacy start", "legacy end", "legacy right unsafe", "legacy auto", "legacy stretch",
+                           "legacy left right"];
+var invalidDistributionValues = ["space-between left", "space-around center", "space-evenly right",
+                                 "stretch safe start", "space-around unsafe", "space-evenly safe flex-start",
+                                 "space-between safe", "space-between stretch", "stretch start",
+                                 "stretch baseline", "first baseline space-around"];
+
+function checkPlaceShorhand(shorthand, shorthandValue, alignValue, justifyValue)
 {
     var div = document.createElement("div");
-    var specifiedValue = (alignValue + " " + justifyValue).trim();
-    div.style[shorthand] = specifiedValue;
+    div.style[shorthand] = shorthandValue;
     document.body.appendChild(div);
 
-    if (alignValue === justifyValue)
-        specifiedValue = alignValue;
-
-    var resolvedValue = getComputedStyle(div).getPropertyValue(shorthand);
     if (alignValue === "first baseline")
         alignValue = "baseline";
     if (justifyValue === "first baseline")
         justifyValue = "baseline";
     if (justifyValue === "")
         justifyValue = alignValue;
-    var expectedResolvedValue = (alignValue + " " + justifyValue).trim()
 
-    assert_equals(div.style[shorthand], specifiedValue, shorthand + " specified value");
+    let specifiedValue = (alignValue + " " + justifyValue).trim();
+    if (alignValue === justifyValue)
+        specifiedValue = alignValue;
+
+    var resolvedValue = getComputedStyle(div).getPropertyValue(shorthand);
+    var expectedResolvedValue = (alignValue + " " + justifyValue).trim();
+
+    assert_equals(div.style[shorthand], specifiedValue, shorthandValue + " specified value");
     // FIXME: We need https://github.com/w3c/csswg-drafts/issues/1041 to clarify which
     // value is expected for the shorthand's 'resolved value".
     assert_in_array(resolvedValue, ["", expectedResolvedValue], shorthand + " resolved value");
@@ -33,6 +48,10 @@
     var div = document.createElement("div");
     div.setAttribute("style", shorthand + ": " + alignValue + " " + justifyValue);
     document.body.appendChild(div);
+    if (alignValue === "first baseline")
+        alignValue = "baseline";
+    if (justifyValue === "first baseline")
+        justifyValue = "baseline";
     if (justifyValue === "")
         justifyValue = alignValue;
     assert_equals(div.style[alignLonghand],
@@ -52,3 +71,60 @@
     assert_equals(div.style[justifyLonghand],
                   "end", justifyLonghand + " expanded value");
 }
+
+function checkValues(element, property, propertyID, value, computedValue)
+{
+    window.element = element;
+    var elementID = element.id || "element";
+    assert_equals(eval('element.style.' + property), value, propertyID + ' specified value is not what it should.');
+    assert_equals(eval("window.getComputedStyle(" + elementID + ", '').getPropertyValue('" + propertyID + "')"), computedValue, propertyID + " computed style is not what is should.");
+}
+
+function checkBadValues(element, property, propertyID, value)
+{
+    var elementID = element.id || "element";
+    element.style[property] = "";
+    var initialValue = eval("window.getComputedStyle(" + elementID + " , '').getPropertyValue('" + propertyID + "')");
+    element.style[property] = value;
+    checkValues(element, property, propertyID, "", initialValue);
+}
+
+function checkInitialValues(element, property, propertyID, value, initial)
+{
+    element.style[property] = value;
+    checkValues(element, property, propertyID, value, value);
+    element.style[property] = "initial";
+    checkValues(element, property, propertyID, "initial", initial);
+}
+
+function checkInheritValues(property, propertyID, value)
+{
+    var parentElement = document.createElement("div");
+    document.body.appendChild(parentElement);
+    parentElement.style[property] = value;
+    checkValues(parentElement, property, propertyID, value, value);
+
+    var element = document.createElement("div");
+    parentElement.appendChild(element);
+    element.style[property] = "inherit";
+    checkValues(element, property, propertyID, "inherit", value);
+}
+
+function checkLegacyValues(property, propertyID, value)
+{
+    var parentElement = document.createElement("div");
+    document.body.appendChild(parentElement);
+    parentElement.style[property] = value;
+    checkValues(parentElement, property, propertyID, value, value);
+
+    var element = document.createElement("div");
+    parentElement.appendChild(element);
+    checkValues(element, property, propertyID, "", value);
+}
+
+function checkSupportedValues(elementID, property)
+{
+    var value = eval("window.getComputedStyle(" + elementID + " , '').getPropertyValue('" + property + "')");
+    var value1 = eval("window.getComputedStyle(" + elementID + " , '')");
+    shouldBeTrue("CSS.supports('" + property + "', '" + value + "')");
+}
diff --git a/css/css-align/self-alignment/parse-align-self-001.html b/css/css-align/self-alignment/parse-align-self-001.html
new file mode 100644
index 0000000..b19372d
--- /dev/null
+++ b/css/css-align/self-alignment/parse-align-self-001.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: align-self - setting values via CSS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<link rel="stylesheet" href="../../support/alignment.css" >
+<meta name="assert" content="Check that the computed value is the specified value and the JS value is empty."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    let classes = Object.assign({"Auto":"auto", "Normal":"normal", "Stretch":"stretch"}, selfPositionClasses,
+                                baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        element = document.createElement("div");
+        element.className = "alignSelf" + key;
+        document.body.appendChild(element);
+        test(function() {
+            if (specifiedValue === "first baseline")
+                checkValues(element, "alignSelf", "align-self", "", "baseline");
+            else
+                checkValues(element, "alignSelf", "align-self", "", specifiedValue);
+        }, "Checking align-self: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/self-alignment/parse-align-self-002.html b/css/css-align/self-alignment/parse-align-self-002.html
new file mode 100644
index 0000000..31af780
--- /dev/null
+++ b/css/css-align/self-alignment/parse-align-self-002.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: align-self - 'initial' value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial-values" />
+<meta name="assert" content="Check the 'initial' value in diferent scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+container = document.createElement("div");
+element = document.createElement("div");
+container.appendChild(element);
+document.body.appendChild(container);
+
+test(function() {
+    element = document.createElement("div");
+    document.body.appendChild(element);
+    checkValues(element, "alignSelf", "align-self", "", "auto");
+}, "Test 'initial' value when nothing is specified");
+
+test(function() {
+    container.style.display = "";
+    checkInitialValues(element, "alignSelf", "align-self", "center", "auto");
+}, "Test align-self: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    checkInitialValues(element, "alignSelf", "align-self", "safe start", "auto");
+}, "Test grid items align-self: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    checkInitialValues(element, "alignSelf", "align-self", "unsafe end", "auto");
+}, "Test flex items align-self: 'initial'");
+
+test(function() {
+    container.style.display = "";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignSelf", "align-self", "start", "auto");
+}, "Test absolute positioned elements align-self: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignSelf", "align-self", "end", "auto");
+}, "Test absolute positioned grid items align-self: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    element.style.position = "absolute";
+    checkInitialValues(element, "alignSelf", "align-self", "end", "auto");
+}, "Test absolute positioned flex items align-self: 'initial'");
+</script>
diff --git a/css/css-align/self-alignment/parse-align-self-003.html b/css/css-align/self-alignment/parse-align-self-003.html
new file mode 100644
index 0000000..a2f3351
--- /dev/null
+++ b/css/css-align/self-alignment/parse-align-self-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: align-self - setting values via JS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<meta name="assert" content="Check that the computed value is the specified value and the same than the JS value."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let classes = Object.assign({"Auto":"auto", "Normal":"normal", "Stretch":"stretch"}, selfPositionClasses,
+                                baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        test(function() {
+            element.style.alignSelf = "";
+            element.style.alignSelf = specifiedValue;
+            if (specifiedValue === "first baseline")
+                checkValues(element, "alignSelf", "align-self", "baseline", "baseline");
+            else
+                checkValues(element, "alignSelf", "align-self",  specifiedValue, specifiedValue);
+        }, "Checking align-self: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/self-alignment/parse-align-self-004.html b/css/css-align/self-alignment/parse-align-self-004.html
new file mode 100644
index 0000000..3549947
--- /dev/null
+++ b/css/css-align/self-alignment/parse-align-self-004.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: align-self - invalid values</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self" />
+<meta name="assert" content="Check bad combinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let values = ["legacy", "space-around", "left", "safe right"].concat(invalidPositionValues);
+
+    values.forEach(function(value) {
+        test(function() {
+            checkBadValues(element, "alignSelf", "align-self",  value);
+        }, "Checking invalid combination - align-self: " + value);
+    });
+</script>
diff --git a/css/css-align/self-alignment/parse-align-self-005.html b/css/css-align/self-alignment/parse-align-self-005.html
new file mode 100644
index 0000000..37e15f8
--- /dev/null
+++ b/css/css-align/self-alignment/parse-align-self-005.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: align-self - inherit value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self" />
+<meta name="assert" content="Check bad cobinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+    checkInheritValues("alignSelf", "align-self", "end");
+}, "Test the value 'inherit' overrides current value ('end')");
+test(function() {
+    checkInheritValues("alignSelf", "align-self", "safe start");
+}, "Test the value 'inherit' overrides current value ('safe start')");
+test(function() {
+    checkInheritValues("alignSelf", "align-self", "unsafe center");
+}, "Test the value 'inherit' overrides current value ('unsafe center')");
+</script>
diff --git a/css/css-align/self-alignment/parse-justify-self-001.html b/css/css-align/self-alignment/parse-justify-self-001.html
new file mode 100644
index 0000000..4b321d5
--- /dev/null
+++ b/css/css-align/self-alignment/parse-justify-self-001.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: justify-self - setting values via CSS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<link rel="stylesheet" href="../../support/alignment.css" >
+<meta name="assert" content="Check that the computed value is the specified value and the JS value is empty."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    let classes = Object.assign({"Auto":"auto", "Normal":"normal", "Stretch":"stretch", "Left":"left",
+                                 "Right":"right"}, selfPositionClasses, baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        element = document.createElement("div");
+        element.className = "justifySelf" + key;
+        document.body.appendChild(element);
+        test(function() {
+            let value = specifiedValue;
+            if (specifiedValue === "first baseline")
+                checkValues(element, "justifySelf", "justify-self", "", "baseline");
+            else
+                checkValues(element, "justifySelf", "justify-self", "", value);
+        }, "Checking justify-self: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/self-alignment/parse-justify-self-002.html b/css/css-align/self-alignment/parse-justify-self-002.html
new file mode 100644
index 0000000..45403c7
--- /dev/null
+++ b/css/css-align/self-alignment/parse-justify-self-002.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: justify-self - 'initial' value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial-values" />
+<meta name="assert" content="Check the 'initial' value in diferent scenarios."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+container = document.createElement("div");
+element = document.createElement("div");
+container.appendChild(element);
+document.body.appendChild(container);
+
+test(function() {
+    element = document.createElement("div");
+    document.body.appendChild(element);
+    checkValues(element, "justifySelf", "justify-self", "", "auto");
+}, "Test 'initial' value when nothing is specified");
+
+test(function() {
+    container.style.display = "";
+    checkInitialValues(element, "justifySelf", "justify-self", "center", "auto");
+}, "Test justify-self: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    checkInitialValues(element, "justifySelf", "justify-self", "safe start", "auto");
+}, "Test grid items justify-self: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    checkInitialValues(element, "justifySelf", "justify-self", "unsafe end", "auto");
+}, "Test flex items justify-self: 'initial'");
+
+test(function() {
+    container.style.display = "";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifySelf", "justify-self", "start", "auto");
+}, "Test absolute positioned elements justify-self: 'initial'");
+
+test(function() {
+    container.style.display = "grid";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifySelf", "justify-self", "end", "auto");
+}, "Test absolute positioned grid items justify-self: 'initial'");
+
+test(function() {
+    container.style.display = "flex";
+    element.style.position = "absolute";
+    checkInitialValues(element, "justifySelf", "justify-self", "end", "auto");
+}, "Test absolute positioned flex items justify-self: 'initial'");
+</script>
diff --git a/css/css-align/self-alignment/parse-justify-self-003.html b/css/css-align/self-alignment/parse-justify-self-003.html
new file mode 100644
index 0000000..96729166
--- /dev/null
+++ b/css/css-align/self-alignment/parse-justify-self-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: justify-self - setting values via JS</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-self-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-baseline-position" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#typedef-overflow-position" />
+<meta name="assert" content="Check that the computed value is the specified value and the same than the JS value."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let classes = Object.assign({"Auto":"auto", "Normal":"normal", "Stretch":"stretch", "Left":"left",
+                                 "Right":"right"}, selfPositionClasses, baselineClasses, overflowClasses);
+
+    for (var key in classes) {
+        let specifiedValue = classes[key];
+        test(function() {
+            element.style.justifySelf = "";
+            element.style.justifySelf = specifiedValue;
+            if (specifiedValue === "first baseline")
+                checkValues(element, "justifySelf", "justify-self", "baseline", "baseline");
+            else
+                checkValues(element, "justifySelf", "justify-self",  specifiedValue, specifiedValue);
+        }, "Checking justify-self: " + specifiedValue);
+    }
+</script>
diff --git a/css/css-align/self-alignment/parse-justify-self-004.html b/css/css-align/self-alignment/parse-justify-self-004.html
new file mode 100644
index 0000000..ab81bf9
--- /dev/null
+++ b/css/css-align/self-alignment/parse-justify-self-004.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: justify-self - invalid values</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self" />
+<meta name="assert" content="Check bad combinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+    element = document.createElement("div");
+    document.body.appendChild(element);
+
+    let values = ["legacy", "space-around"].concat(invalidPositionValues);
+
+    values.forEach(function(value) {
+        test(function() {
+            checkBadValues(element, "justifySelf", "justify-self",  value);
+        }, "Checking invalid combination - justify-self: " + value);
+    });
+</script>
diff --git a/css/css-align/self-alignment/parse-justify-self-005.html b/css/css-align/self-alignment/parse-justify-self-005.html
new file mode 100644
index 0000000..61b44be
--- /dev/null
+++ b/css/css-align/self-alignment/parse-justify-self-005.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Self-Alignment: justify-self - inherit value</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#self-alignment" />
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self" />
+<meta name="assert" content="Check bad cobinations of specified values."/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+    checkInheritValues("justifySelf", "justify-self", "end");
+}, "Test the value 'inherit' overrides current value ('end')");
+test(function() {
+    checkInheritValues("justifySelf", "justify-self", "safe left");
+}, "Test the value 'inherit' overrides current value ('safe left')");
+test(function() {
+    checkInheritValues("justifySelf", "justify-self", "unsafe center");
+}, "Test the value 'inherit' overrides current value ('unsafe center')");
+</script>
diff --git a/css/css-align/self-alignment/place-self-shorthand-001.html b/css/css-align/self-alignment/place-self-shorthand-001.html
index ba2355e..bd95302 100644
--- a/css/css-align/self-alignment/place-self-shorthand-001.html
+++ b/css/css-align/self-alignment/place-self-shorthand-001.html
@@ -5,13 +5,15 @@
 <meta name="assert" content="Check that setting a single value to place-self expands to such value set in both 'align-self' and 'justify-self'." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["auto", "normal", "stretch"].concat(selfPositionValues, baselineValues);
-    values.forEach(function(value) {
+    let classes = Object.assign({"Auto":"auto", "Normal":"normal", "Stretch":"stretch"}, selfPositionClasses,
+                                baselineClasses, overflowClasses);
+    for (var key in classes) {
+        let value = classes[key];
         test(function() {
             checkPlaceShorhandLonghands("place-self", "align-self", "justify-self", value);
         }, "Checking place-self: " + value);
-    });
+    }
 </script>
diff --git a/css/css-align/self-alignment/place-self-shorthand-002.html b/css/css-align/self-alignment/place-self-shorthand-002.html
index 16f807d..47bb78e 100644
--- a/css/css-align/self-alignment/place-self-shorthand-002.html
+++ b/css/css-align/self-alignment/place-self-shorthand-002.html
@@ -5,15 +5,19 @@
 <meta name="assert" content="Check that setting two values to place-self sets the first one to 'align-self' and the second one to 'justify-self'." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["auto", "normal", "stretch"].concat(selfPositionValues, baselineValues);
-    values.forEach(function(alignValue) {
-        values.forEach(function(justifyValue) {
+    let classes = Object.assign({"Auto":"auto", "Normal":"normal", "Stretch":"stretch"}, selfPositionClasses,
+                                baselineClasses, overflowClasses);
+    for (var key1 in classes) {
+        let alignValue = classes[key1];
+        let classes2 = Object.assign({"Left":"left", "Right":"right"}, classes);
+        for (var key2 in classes2) {
+            let justifyValue = classes2[key2];
             test(function() {
                 checkPlaceShorhandLonghands("place-self", "align-self", "justify-self", alignValue, justifyValue);
             }, "Checking place-self: " + alignValue + " " + justifyValue);
-        });
-    });
+        }
+    }
 </script>
diff --git a/css/css-align/self-alignment/place-self-shorthand-004.html b/css/css-align/self-alignment/place-self-shorthand-004.html
index b9baa6a..b7c9d80 100644
--- a/css/css-align/self-alignment/place-self-shorthand-004.html
+++ b/css/css-align/self-alignment/place-self-shorthand-004.html
@@ -5,7 +5,7 @@
 <meta name="assert" content="Check that place-self's invalid values are properly detected at parsing time." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
     function checkInvalidValues(value)
@@ -14,13 +14,14 @@
     }
 
     test(function() {
-        checkInvalidValues("center safe")
-        checkInvalidValues("true center")
-    }, "Verify overflow keywords are invalid");
+        checkInvalidValues("center end start")
+    }, "Verify fallback values are invalid");
 
     test(function() {
-        checkInvalidValues("center space-between start")
-    }, "Verify fallback values are invalid");
+        checkInvalidValues("left")
+        checkInvalidValues("left start")
+        checkInvalidValues("right center")
+    }, "Verify 'left' and 'right' values are invalid for block/cross axis alignment");
 
     test(function() {
         checkInvalidValues("10px left")
diff --git a/css/css-align/self-alignment/place-self-shorthand-006.html b/css/css-align/self-alignment/place-self-shorthand-006.html
index 7e420f4..fb5dce1 100644
--- a/css/css-align/self-alignment/place-self-shorthand-006.html
+++ b/css/css-align/self-alignment/place-self-shorthand-006.html
@@ -5,16 +5,20 @@
 <meta name="assert" content="Check the place-self's 'specified' and 'resolved' values serialization." />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../resources/alignment-parsing-utils.js"></script>
+<script src="/css/css-align/resources/alignment-parsing-utils.js"></script>
 <div id="log"></div>
 <script>
-    var values = ["auto", "normal", "stretch"].concat(selfPositionValues, baselineValues);
-    values.forEach(function(alignValue) {
-        [""].concat(values).forEach(function(justifyValue) {
+    let classes = Object.assign({"Auto":"auto", "Normal":"normal", "Stretch":"stretch"}, selfPositionClasses,
+                                baselineClasses);
+    for (var key1 in classes) {
+        let alignValue = classes[key1];
+        let classes2 = Object.assign({"Left":"left", "Right":"right"}, classes);
+        for (var key2 in classes2) {
+            let justifyValue = classes2[key2];
             var value = (alignValue + " " + justifyValue).trim();
             test(function() {
-                checkPlaceShorhand("place-self", alignValue, justifyValue)
+                checkPlaceShorhand("place-self", value, alignValue, justifyValue)
             }, "Checking place-self: " + value);
-        });
-    });
+        }
+    }
 </script>
diff --git a/css/css-animations/animation-delay-011.html b/css/css-animations/animation-delay-011.html
new file mode 100644
index 0000000..415a574
--- /dev/null
+++ b/css/css-animations/animation-delay-011.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Animations Test: inherited animation-delay with mismatched animation-name length</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animations">
+<link rel="match" href="animation-common-ref.html">
+<style>
+div:after {
+  content: '';
+  display: block;
+  width: 100px;
+  height: 100px;
+  background: red;
+  animation: doesntmatter 50s linear infinite,
+             bg 100s step-end infinite;
+  animation-play-state: paused;
+  animation-delay: inherit;
+}
+
+@keyframes bg {
+  50% { background: green; }
+}
+</style>
+<div style="animation-delay: -50s"></div>
diff --git a/css/css-animations/animationevent-interface.js b/css/css-animations/animationevent-interface.js
index 7aee234..56d30a4 100644
--- a/css/css-animations/animationevent-interface.js
+++ b/css/css-animations/animationevent-interface.js
@@ -69,6 +69,11 @@
   }, "animationEventInit argument is empty dictionary");
 
   test(function() {
+    var event = new AnimationEvent("test", {pseudoElement: "::testPseudo"});
+    assert_equals(event.pseudoElement, "::testPseudo");
+  }, "AnimationEvent.pseudoElement initialized from the dictionary");
+
+  test(function() {
     var event = new AnimationEvent("test", {animationName: "sample"});
     assert_equals(event.animationName, "sample");
   }, "animationName set to 'sample'");
diff --git a/css/css-animations/animationevent-pseudoelement.html b/css/css-animations/animationevent-pseudoelement.html
new file mode 100644
index 0000000..8de41cc
--- /dev/null
+++ b/css/css-animations/animationevent-pseudoelement.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Animations Test: AnimationEvent pseudoElement</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#interface-animationevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #target::before {
+    content: "";
+    animation: move 1s;
+  }
+
+  @keyframes move {
+    to { transform: translate(100px); }
+  }
+</style>
+<div id='target'></div>
+<script>
+  async_test(function(t) {
+    var target = document.getElementById('target');
+    target.addEventListener("animationstart", t.step_func(function(evt) {
+      assert_true(evt instanceof window.AnimationEvent);
+      assert_equals(evt.pseudoElement, "::before");
+
+      t.done();
+    }), true);
+  }, "AnimationEvent should have the correct pseudoElement memeber");
+</script>
+
diff --git a/css/css-animations/animationevent-types.html b/css/css-animations/animationevent-types.html
index d1cb914..77f514a 100644
--- a/css/css-animations/animationevent-types.html
+++ b/css/css-animations/animationevent-types.html
@@ -39,9 +39,11 @@
 
       assert_idl_attribute(evt, "animationName", "animationstart has animationName property");
       assert_idl_attribute(evt, "elapsedTime", "animationstart has elapsedTime property");
+      assert_idl_attribute(evt, "pseudoElement", "animationstart has pseudoElement property");
 
       assert_equals(evt.animationName, "sample", "animationstart has animationName value");
       assert_equals(evt.elapsedTime, 1, "animationstart has elapsedTime value");
+      assert_equals(evt.pseudoElement, "", "animaitonstart has correct pseudoElement value");
 
       t.done();
     }), true);
@@ -53,9 +55,11 @@
 
       assert_idl_attribute(evt, "animationName", "animationend has animationName property");
       assert_idl_attribute(evt, "elapsedTime", "animationend has elapsedTime property");
+      assert_idl_attribute(evt, "pseudoElement", "animationstart has pseudoElement property");
 
       assert_equals(evt.animationName, "sample", "animationend has animationName value");
       assert_equals(evt.elapsedTime, 4, "animationend has elapsedTime value");
+      assert_equals(evt.pseudoElement, "", "animaitonstart has correct pseudoElement value");
 
       t.done();
     }), true);
@@ -67,9 +71,11 @@
 
       assert_idl_attribute(evt, "animationName", "animationiteration has animationName property");
       assert_idl_attribute(evt, "elapsedTime", "animationiteration has elapsedTime property");
+      assert_idl_attribute(evt, "pseudoElement", "animationstart has pseudoElement property");
 
       assert_equals(evt.animationName, "sample", "animationiteration has animationName value");
       assert_equals(evt.elapsedTime, 2, "animationiteration has elapsedTime value");
+      assert_equals(evt.pseudoElement, "", "animaitonstart has correct pseudoElement value");
 
       t.done();
     }), true);
diff --git a/css/css-animations/pending-style-changes-001.html b/css/css-animations/pending-style-changes-001.html
new file mode 100644
index 0000000..fb74d7f
--- /dev/null
+++ b/css/css-animations/pending-style-changes-001.html
@@ -0,0 +1,34 @@
+<meta charset=utf-8>
+<title>CSS Animations Test: requirement on pending style changes - getAnimations</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations-2/#requirements-on-pending-style-changes">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+@keyframes anim {}
+
+.animate {
+  animation: anim 10s;
+}
+</style>
+
+<div id="target"></div>
+
+<script>
+test (t => {
+  assert_equals(target.getAnimations().length, 0, 'Test precondition.');
+  target.classList.add('animate');
+  assert_equals(target.getAnimations().length, 1,
+      'target.getAnimations() should include the CSS animation after animate class added.');
+  target.classList.remove('animate');
+}, 'Animatable::getAnimations() should be able to see a style-created CSS animation immediately');
+
+test(t => {
+  assert_equals(document.getAnimations().length, 0, 'Test precondition.');
+  target.classList.add('animate');
+  assert_equals(document.getAnimations().length, 1,
+     'document.getAnimations() should include the CSS animation after animate class added.');
+  target.classList.remove('animate');
+}, 'Document::getAnimations() should be able to see a style-created CSS animation immediately');
+</script>
diff --git a/css/css-backgrounds/background-331.html b/css/css-backgrounds/background-331.html
index 3d72ad4..1216234 100644
--- a/css/css-backgrounds/background-331.html
+++ b/css/css-backgrounds/background-331.html
@@ -57,7 +57,7 @@
 
         test(function() {
             assert_equals(cs.getPropertyValue("background-color"),
-                "transparent", "background initial value for background-color");
+                "rgba(0, 0, 0, 0)", "background initial value for background-color");
         }, "background_initial_color");
     </script>
   </body>
diff --git a/css/css-backgrounds/background-332.html b/css/css-backgrounds/background-332.html
index da11dc8..2621cb6 100644
--- a/css/css-backgrounds/background-332.html
+++ b/css/css-backgrounds/background-332.html
@@ -57,7 +57,7 @@
 
         test(function() {
             assert_equals(cs.getPropertyValue("background-color"),
-                "gray", "background specified value for background-color");
+                "rgb(128, 128, 128)", "background specified value for background-color");
         }, "background_specified_color");
     </script>
   </body>
diff --git a/css/css-backgrounds/background-333.html b/css/css-backgrounds/background-333.html
index 8331527..53dd150 100644
--- a/css/css-backgrounds/background-333.html
+++ b/css/css-backgrounds/background-333.html
@@ -57,7 +57,7 @@
 
         test(function() {
             assert_equals(cs.getPropertyValue("background-color"),
-                "red", "background specified value for background-color");
+                "rgb(255, 0, 0)", "background specified value for background-color");
         }, "background_specified_color_color");
     </script>
   </body>
diff --git a/css/css-backgrounds/background-clip-content-box-001.html b/css/css-backgrounds/background-clip-content-box-001.html
index a3b8436..f0777e8 100644
--- a/css/css-backgrounds/background-clip-content-box-001.html
+++ b/css/css-backgrounds/background-clip-content-box-001.html
@@ -1,36 +1,25 @@
 <!DOCTYPE html>
-<html>
-<head>
-    <title>CSS Backgrounds and Borders Test: background-clip Reference</title>
-    <link rel="author" title="James Wang" href="mailto:wangjian@ucweb.com">
-	<link rel="help" href="http://www.w3.org/TR/css3-background/#the-background-clip" />
-    <meta name="flags" content="">
-    <meta name="assert" content="background-clip with content-box means paint the background within the content box">
-
-    <link rel="match" href="reference/background-clip-content-box-ref.html">
-
-    <style type="text/css">
-    	* { padding: 0; margin: 0; }
-    	#test-color-blue {
-    		background-color: blue;
-    		height: 200px;
-            position: absolute;
-    		width: 200px;
-    	}
-    	#test-color-green {
-    		background-clip: content-box;
-    		background-color: rgba(0, 255, 0, 1);
-    		height: 180px;
-    		padding: 10px;
-            position: absolute;
-    		width: 180px;
-    	}
-    </style>
-</head>
-<body>
-    <p>Test pass if the green box has a 10px width blue edge</p>
-    <!-- background -->
-    <div id="test-color-blue"></div>
-    <div id="test-color-green"></div>
-</body>
-</html>
+<title>CSS Backgrounds and Borders Test: background-clip Reference</title>
+<link rel="author" title="James Wang" href="mailto:wangjian@ucweb.com">
+<link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#the-background-clip">
+<link rel="match" href="reference/background-clip-content-box-ref.html">
+<style>
+#test-color-blue {
+  background-color: blue;
+  height: 200px;
+  position: absolute;
+  width: 200px;
+}
+#test-color-orange {
+  background-clip: content-box;
+  background-color: rgba(255, 165, 0, 1);
+  height: 180px;
+  padding: 10px;
+  position: absolute;
+  width: 180px;
+}
+</style>
+<p>Test passes if the orange box has a 10px width blue edge.</p>
+<!-- background -->
+<div id="test-color-blue"></div>
+<div id="test-color-orange"></div>
diff --git a/css/css-backgrounds/background-clip/clip-rounded-corner-ref.html b/css/css-backgrounds/background-clip/clip-rounded-corner-ref.html
new file mode 100644
index 0000000..379ad36
--- /dev/null
+++ b/css/css-backgrounds/background-clip/clip-rounded-corner-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Filled Background with Rounded Corner</title>
+<style>
+    #a {
+        width: 100px;
+        height: 80px;
+        border: 20px blue solid;
+        border-top-right-radius: 20px;
+        background-color: green;
+        background-clip: border-box;
+    }
+    #shield {
+        position: absolute;
+        width: 30px;
+        height: 30px;
+        left: 120px;
+        top: 5px;
+        background-color: black;
+    }
+</style>
+<body>
+    <div id="a"></div>
+    <!-- Hide the curved outside border to deal with imprecise rendering. -->
+    <div id="shield"></div>
+</body>
diff --git a/css/css-backgrounds/background-clip/clip-rounded-corner.html b/css/css-backgrounds/background-clip/clip-rounded-corner.html
new file mode 100644
index 0000000..3453c5b
--- /dev/null
+++ b/css/css-backgrounds/background-clip/clip-rounded-corner.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Filled Background with Rounded Corner</title>
+<link rel="match" href="clip-rounded-corner-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#corner-clipping">
+<style>
+    #b {
+        width: 100px;
+        height: 80px;
+        border: 20px blue solid;
+        border-top-right-radius: 20px;
+        background-color: green;
+        background-clip: content-box;
+    }
+    #shield {
+        position: absolute;
+        width: 30px;
+        height: 30px;
+        left: 120px;
+        top: 5px;
+        background-color: black;
+    }
+</style>
+<body>
+    <div id="b"></div>
+    <!-- Hide the curved outside border to deal with imprecise rendering. -->
+    <div id="shield"></div>
+</body>
diff --git a/css/css-backgrounds/background-color-body-propagation-001.html b/css/css-backgrounds/background-color-body-propagation-001.html
new file mode 100644
index 0000000..4cfc360
--- /dev/null
+++ b/css/css-backgrounds/background-color-body-propagation-001.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>CSS Backgrounds and Borders Test: propagate body background to viewport</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#body-background">
+<link rel="match" href="background-color-body-propagation-ref.html">
+<style>
+  html {
+    background-color: transparent;
+    background-image: none;
+  }
+  body {
+    background-color: green;
+    margin: 0;
+  }
+</style>
+<p>The viewport should have a green background.</p>
diff --git a/css/css-backgrounds/background-color-body-propagation-002.html b/css/css-backgrounds/background-color-body-propagation-002.html
new file mode 100644
index 0000000..1c42637
--- /dev/null
+++ b/css/css-backgrounds/background-color-body-propagation-002.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>CSS Backgrounds and Borders Test: body background not propagating when html does</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#body-background">
+<link rel="match" href="background-color-body-propagation-ref.html">
+<style>
+  html {
+    background-color: green;
+    background-image: none;
+  }
+  body {
+    background-color: red;
+    margin: 0;
+  }
+  p {
+    background: green;
+  }
+</style>
+<p>The viewport should have a green background.</p>
diff --git a/css/css-backgrounds/background-color-body-propagation-003.html b/css/css-backgrounds/background-color-body-propagation-003.html
new file mode 100644
index 0000000..8106822
--- /dev/null
+++ b/css/css-backgrounds/background-color-body-propagation-003.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>CSS Backgrounds and Borders Test: propagate body background while display changes</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#body-background">
+<link rel="match" href="background-color-body-propagation-ref.html">
+<style>body { margin: 0 }</style>
+<p>The viewport should have a green background.</p>
+<script>
+  document.body.offsetTop;
+  document.body.style = "display:inline;background:green";
+</script>
diff --git a/css/css-backgrounds/background-color-body-propagation-ref.html b/css/css-backgrounds/background-color-body-propagation-ref.html
new file mode 100644
index 0000000..1a13874
--- /dev/null
+++ b/css/css-backgrounds/background-color-body-propagation-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html style="background:green">
+<title>CSS Reftest Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<body style="margin:0">
+<p>The viewport should have a green background.</p>
diff --git a/css/css-backgrounds/background-color-clip.html b/css/css-backgrounds/background-color-clip.html
new file mode 100644
index 0000000..961e161
--- /dev/null
+++ b/css/css-backgrounds/background-color-clip.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Background Color Clip</title>
+<link rel="match" href="reference/background-color-clip.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-color">
+<meta name="assert" content="Check that the background color is clipped according to the background-clip value associated with the bottom-most background image layer.">
+<style>
+    div {
+        width: 120px;
+        height: 100px;
+        background-color: green;
+        background-clip: border-box, content-box, border-box;
+        background-image: none, none;
+        border-style: solid;
+        border-width: 10px;
+        border-color: transparent;
+    }
+</style>
+<div></div>
\ No newline at end of file
diff --git a/css/css-backgrounds/background-image-centered-ref.html b/css/css-backgrounds/background-image-centered-ref.html
new file mode 100644
index 0000000..f52cd96
--- /dev/null
+++ b/css/css-backgrounds/background-image-centered-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Centered Background Gradient</title>
+<style>
+    #a {
+        width: 200px;
+        height: 150px;
+        border-style: solid;
+        border-width: 10px;
+        background-position: 0px 0px;
+        background-image: repeating-radial-gradient(blue, green 20px);
+    }
+</style>
+<body>
+    <div id="a"></div>
+</body>
diff --git a/css/css-backgrounds/background-image-centered.html b/css/css-backgrounds/background-image-centered.html
new file mode 100644
index 0000000..7051aa3
--- /dev/null
+++ b/css/css-backgrounds/background-image-centered.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Centered Background Gradient</title>
+<link rel="match" href="background-image-centered-ref.html">
+<link rel="help" href="http://www.w3.org/TR/css3-background/">
+<style>
+    #a {
+        width: 200px;
+        height: 150px;
+        border-style: solid;
+        border-width: 10px;
+        background-position: center center;
+        background-image: repeating-radial-gradient(blue, green 20px);
+    }
+</style>
+<body>
+    <div id="a"></div>
+</body>
diff --git a/css/css-backgrounds/background-image-none-gradient-repaint.html b/css/css-backgrounds/background-image-none-gradient-repaint.html
new file mode 100644
index 0000000..3f39fb8
--- /dev/null
+++ b/css/css-backgrounds/background-image-none-gradient-repaint.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders: Repaint gradient change in second layer</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="match" href="background-clip-color-repaint-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#layering">
+<style>
+  #box {
+    width: 150px;
+    height: 150px;
+  }
+  .red {
+    background: none, linear-gradient(to right, red, red);
+  }
+  .green {
+    background: none, linear-gradient(to right, green, green);
+  }
+</style>
+<p>There should be a green square below.</p>
+<div id="box" class="red"></div>
+<script>
+  requestAnimationFrame(function(){
+    requestAnimationFrame(function(){
+      box.className = "green";
+      document.documentElement.classList.remove("reftest-wait");
+    });
+  });
+</script>
diff --git a/css/css-backgrounds/background-repeat/gradient-repeat-spaced-with-borders.html b/css/css-backgrounds/background-repeat/gradient-repeat-spaced-with-borders.html
new file mode 100644
index 0000000..289e856
--- /dev/null
+++ b/css/css-backgrounds/background-repeat/gradient-repeat-spaced-with-borders.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tiled gradient with spaces is repeated behind the border.</title>
+<link rel="match" href="reference/gradient-repeat-spaced-with-borders.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#valdef-background-repeat-space">
+<style>
+    #foo {
+        width: 65px;
+        height: 65px;
+        border: solid 35px transparent;
+        background: radial-gradient(transparent 50%, #36c 50%);
+        background-size: 30px 30px;
+        background-repeat: space;
+    }
+</style>
+<body>
+    <div id="foo"></div>
+</body>
diff --git a/css/css-backgrounds/background-repeat/reference/gradient-repeat-spaced-with-borders.html b/css/css-backgrounds/background-repeat/reference/gradient-repeat-spaced-with-borders.html
new file mode 100644
index 0000000..0c226c8
--- /dev/null
+++ b/css/css-backgrounds/background-repeat/reference/gradient-repeat-spaced-with-borders.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Spaced Gradient</title>
+<style>
+    #foo {
+        width: calc(4 * 30px + 3 * 5px);
+        height: calc(4 * 30px + 3 * 5px);
+        background: radial-gradient(transparent 50%, #36c 50%);
+        background-size: 30px 30px;
+        background-repeat: space;
+    }
+</style>
+<body>
+    <div id="foo"></div>
+</body>
diff --git a/css/css-backgrounds/background-rounded-image-clip.html b/css/css-backgrounds/background-rounded-image-clip.html
new file mode 100644
index 0000000..1179ba5
--- /dev/null
+++ b/css/css-backgrounds/background-rounded-image-clip.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Background Clip Follows Rounded Corner</title>
+<link rel="match" href="reference/background-rounded-image-clip.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#corner-clipping">
+<style>
+    html {
+        background-color: green;
+    }
+    #a {
+        top: 20px;
+        left: 20px;
+        position: absolute;
+        width: 20px;
+        height: 20px;
+        background-color: black;
+    }
+
+    #b {
+        position: absolute;
+        width: 300px;
+        height: 200px;
+        background-image: linear-gradient(green, green);
+        background-clip: content-box;
+        border-top-left-radius: 90px;
+        border-width: 10px;
+        border-style: solid;
+        border-color: transparent;
+    }
+</style>
+<div id="a"></div>
+<div id="b"></div>
diff --git a/css/css-backgrounds/border-image-outset-001.htm b/css/css-backgrounds/border-image-outset-001.htm
index 232fd74..db0a8f5 100644
--- a/css/css-backgrounds/border-image-outset-001.htm
+++ b/css/css-backgrounds/border-image-outset-001.htm
Binary files differ
diff --git a/css/css-backgrounds/border-image-outset-002.htm b/css/css-backgrounds/border-image-outset-002.htm
index ce20a5b..d8fbf94 100644
--- a/css/css-backgrounds/border-image-outset-002.htm
+++ b/css/css-backgrounds/border-image-outset-002.htm
Binary files differ
diff --git a/css/css-backgrounds/border-image-outset-003-ref.html b/css/css-backgrounds/border-image-outset-003-ref.html
new file mode 100644
index 0000000..4d55cdd
--- /dev/null
+++ b/css/css-backgrounds/border-image-outset-003-ref.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Blue Box with Green Frame</title>
+<style>
+    #a {
+        width: 300px;
+        height: 200px;
+        position: absolute;
+        left: 100px;
+        top: 100px;
+        border-width: 50px;
+        border-style: solid;
+        border-image: linear-gradient(green, green);
+        background-color: blue;
+        background-clip: content-box;
+        border-image-slice: 33%;
+        padding-left: 100px;
+        padding-top: 50px;
+        padding-right: 10px;
+        padding-bottom: 50px;
+    }
+</style>
+<div id="a"></div>
\ No newline at end of file
diff --git a/css/css-backgrounds/border-image-outset-003.html b/css/css-backgrounds/border-image-outset-003.html
new file mode 100644
index 0000000..27bc026
--- /dev/null
+++ b/css/css-backgrounds/border-image-outset-003.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Background Image Outset</title>
+<link rel="match" href="border-image-outset-003-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-image-outset" />
+<style>
+    #a {
+        width: 300px;
+        height: 200px;
+        position: absolute;
+        left: 200px;
+        top: 150px;
+        border-width: 50px;
+        border-style: solid;
+        border-image: linear-gradient(green, green);
+        background-color: blue;
+        background-clip: content-box;
+        border-image-outset: 50px 10px 50px 100px;
+        border-image-slice: 33%;
+    }
+</style>
+<div id="a"></div>
\ No newline at end of file
diff --git a/css/css-backgrounds/border-radius-001-ref.xht b/css/css-backgrounds/border-radius-001-ref.xht
index 9c7bc4b..0d6f4d7 100644
--- a/css/css-backgrounds/border-radius-001-ref.xht
+++ b/css/css-backgrounds/border-radius-001-ref.xht
@@ -3,29 +3,25 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-        	div
-          {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-          }
-        /* ]]> */
+        <style>
+            div {
+                border:2px solid #a1a1a1;
+                background:#dddddd;
+                width:200px;
+                height: 100px;
+            }
         </style>
     </head>
     <body>
-        <p>
-          There should be two boxes with no rounded corners.</p>
+        <p>There should be two boxes with no rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
 
diff --git a/css/css-backgrounds/border-radius-001.xht b/css/css-backgrounds/border-radius-001.xht
index 7536df1..5029dd0 100644
--- a/css/css-backgrounds/border-radius-001.xht
+++ b/css/css-backgrounds/border-radius-001.xht
@@ -3,65 +3,33 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius set to zero</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-001-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify when border-radius property, when set to zero, works as expected." />
-        <style type="text/css">
-         /* <![CDATA[ */
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
             }
-        	#test
-        	{
-        		border-radius: 0;
-        	}
-        /* ]]> */
+            #test {
+                border-radius: 0;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-    	<p>
-			There should be two boxes with no rounded corners.
-		</p>
-		<!-- PASS AND FAIL CRITERIA ARE IN THE LIST BELOW -->
-				<ul>
-					<li>PASS if the two boxes below are the same.</li>
-					<li>FAIL if the output is not as expected.</li>
-				</ul>
+        <p>There should be two boxes with no rounded corners.</p>
+        <ul>
+            <li>PASS if the two boxes below are the same.</li>
+            <li>FAIL if the output is not as expected.</li>
+        </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-    		var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("0px", "0px", "0px", "0px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-         /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
 
diff --git a/css/css-backgrounds/border-radius-002-ref.xht b/css/css-backgrounds/border-radius-002-ref.xht
index 104c1e1..b928234 100644
--- a/css/css-backgrounds/border-radius-002-ref.xht
+++ b/css/css-backgrounds/border-radius-002-ref.xht
@@ -3,34 +3,28 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 25px;
-				border-top-right-radius: 25px;
-				border-bottom-right-radius: 25px;
-				border-bottom-left-radius: 25px;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 25px;
+                border-top-right-radius: 25px;
+                border-bottom-right-radius: 25px;
+                border-bottom-left-radius: 25px;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-002.xht b/css/css-backgrounds/border-radius-002.xht
index 1877e08..9f53fc3 100644
--- a/css/css-backgrounds/border-radius-002.xht
+++ b/css/css-backgrounds/border-radius-002.xht
@@ -3,68 +3,36 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using one length value 25px</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-002-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify border-radius property set with one length value, works as expected" />
-        <style type="text/css">
-        /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: 25px;
-        	}
-		    #reference
-		    {
-			    border-top-left-radius: 25px;
-				border-top-right-radius: 25px;
-				border-bottom-right-radius: 25px;
-				border-bottom-left-radius: 25px;
-			}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: 25px;
+            }
+            #reference {
+                border-top-left-radius: 25px;
+                border-top-right-radius: 25px;
+                border-bottom-right-radius: 25px;
+                border-bottom-left-radius: 25px;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-   	    <p><br/></p>
-		<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-			var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("25px", "25px", "25px", "25px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-				/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-        /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-003-ref.xht b/css/css-backgrounds/border-radius-003-ref.xht
index be824c2..d7017f0 100644
--- a/css/css-backgrounds/border-radius-003-ref.xht
+++ b/css/css-backgrounds/border-radius-003-ref.xht
@@ -3,34 +3,28 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 50px;
-				border-top-right-radius: 0;
-				border-bottom-right-radius: 50px;
-				border-bottom-left-radius: 0;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 50px;
+                border-top-right-radius: 0;
+                border-bottom-right-radius: 50px;
+                border-bottom-left-radius: 0;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with rounded corners at the top left and bottom right only.
-		</p>
+        <p>There should be two identical boxes, each with rounded corners at the top left and bottom right only.</p>
         <ul>
             <li>PASS if the two boxes below are the same and only top left and bottom right corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-003.xht b/css/css-backgrounds/border-radius-003.xht
index cfe4d74..398358f 100644
--- a/css/css-backgrounds/border-radius-003.xht
+++ b/css/css-backgrounds/border-radius-003.xht
@@ -3,68 +3,36 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius set to value: 50px 0</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-003-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify border-radius property set with one length value along with a zero, works as expected" />
-        <style type="text/css">
-         /* <![CDATA[ */
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: 50px 0;
-        	}
-			#reference
-			{
-			    border-top-left-radius: 50px;
-				border-top-right-radius: 0;
-				border-bottom-right-radius: 50px;
-				border-bottom-left-radius: 0;
-			}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: 50px 0;
+            }
+            #reference {
+                border-top-left-radius: 50px;
+                border-top-right-radius: 0;
+                border-bottom-right-radius: 50px;
+                border-bottom-left-radius: 0;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with rounded corners at the top left and bottom right only.
-		</p>
+        <p>There should be two identical boxes, each with rounded corners at the top left and bottom right only.</p>
         <ul>
             <li>PASS if the two boxes below are the same and only top left and bottom right corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-    	<p><br/></p>
-		<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-    		  var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("50px", "0px", "50px", "0px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-         /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-004-ref.xht b/css/css-backgrounds/border-radius-004-ref.xht
index 74e70ab..3062d78 100644
--- a/css/css-backgrounds/border-radius-004-ref.xht
+++ b/css/css-backgrounds/border-radius-004-ref.xht
@@ -3,34 +3,29 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 50px 25px;
-				border-top-right-radius: 50px 25px;
-				border-bottom-right-radius: 50px 25px;
-				border-bottom-left-radius: 50px 25px;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 50px 25px;
+                border-top-right-radius: 50px 25px;
+                border-bottom-right-radius: 50px 25px;
+                border-bottom-left-radius: 50px 25px;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
 
diff --git a/css/css-backgrounds/border-radius-004.xht b/css/css-backgrounds/border-radius-004.xht
index cfd57e2..8a000d9 100644
--- a/css/css-backgrounds/border-radius-004.xht
+++ b/css/css-backgrounds/border-radius-004.xht
@@ -3,68 +3,37 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using slash: 50px / 25px</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-004-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify border-radius property set with value using slash, works as expected" />
-        <style type="text/css">
-        /* <![CDATA[ */
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: 50px / 25px;
-        	}
-			#reference
-			{
-			    border-top-left-radius: 50px 25px;
-				border-top-right-radius: 50px 25px;
-				border-bottom-right-radius: 50px 25px;
-				border-bottom-left-radius: 50px 25px;
-			}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: 50px / 25px;
+            }
+            #reference {
+                border-top-left-radius: 50px 25px;
+                border-top-right-radius: 50px 25px;
+                border-bottom-right-radius: 50px 25px;
+                border-bottom-left-radius: 50px 25px;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-		<p><br/></p>
-		<div id="reference"></div>
-<!--
-		<script type="text/javascript">
-        /* <![CDATA[ */
-    		var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("50px 25px", "50px 25px", "50px 25px", "50px 25px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-         /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
 
diff --git a/css/css-backgrounds/border-radius-005-ref.xht b/css/css-backgrounds/border-radius-005-ref.xht
index 378b40e..57605a7 100644
--- a/css/css-backgrounds/border-radius-005-ref.xht
+++ b/css/css-backgrounds/border-radius-005-ref.xht
@@ -3,34 +3,29 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 50px 20px;
-				border-top-right-radius: 15px 25px;
-				border-bottom-right-radius: 40px 20px;
-				border-bottom-left-radius: 15px 25px;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 50px 20px;
+                border-top-right-radius: 15px 25px;
+                border-bottom-right-radius: 40px 20px;
+                border-bottom-left-radius: 15px 25px;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
 
diff --git a/css/css-backgrounds/border-radius-005.xht b/css/css-backgrounds/border-radius-005.xht
index 839d48f..7be93af 100644
--- a/css/css-backgrounds/border-radius-005.xht
+++ b/css/css-backgrounds/border-radius-005.xht
@@ -3,66 +3,36 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using slash: 50px 15px 40px / 20px 25px</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/"/>
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-005-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify border-radius property set with value using slash, works as expected" />
-        <style type="text/css">
-        /* <![CDATA[ */
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: 50px 15px 40px / 20px 25px;
-        	}
-			#reference
-			{
-			    border-top-left-radius: 50px 20px;
-				border-top-right-radius: 15px 25px;
-				border-bottom-right-radius: 40px 20px;
-				border-bottom-left-radius: 15px 25px;
-			}
-         /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: 50px 15px 40px / 20px 25px;
+            }
+            #reference {
+                border-top-left-radius: 50px 20px;
+                border-top-right-radius: 15px 25px;
+                border-bottom-right-radius: 40px 20px;
+                border-bottom-left-radius: 15px 25px;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-       	<p><br/></p>
-		<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-    		var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("50px 20px", "15px 25px", "40px 20px", "15px 25px");
-         	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-        /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-006-ref.xht b/css/css-backgrounds/border-radius-006-ref.xht
index 730a19b..93c1949 100644
--- a/css/css-backgrounds/border-radius-006-ref.xht
+++ b/css/css-backgrounds/border-radius-006-ref.xht
@@ -3,34 +3,28 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 50px 20px;
-				border-top-right-radius: 15px 25px;
-				border-bottom-right-radius: 50px 10%;
-				border-bottom-left-radius: 15px 25px;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 50px 20px;
+                border-top-right-radius: 15px 25px;
+                border-bottom-right-radius: 50px 10%;
+                border-bottom-left-radius: 15px 25px;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-006.xht b/css/css-backgrounds/border-radius-006.xht
index e2426d0..89da74c 100644
--- a/css/css-backgrounds/border-radius-006.xht
+++ b/css/css-backgrounds/border-radius-006.xht
@@ -3,68 +3,36 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using slash: 50px 15px / 20px 25px 10%</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/"/>
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-006-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify border-radius property set with values using slash, works as expected" />
-        <style type="text/css">
-         /* <![CDATA[ */
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: 50px 15px / 20px 25px 10%;
-        	}
-			#reference
-			{
-			    border-top-left-radius: 50px 20px;
-				border-top-right-radius: 15px 25px;
-				border-bottom-right-radius: 50px 10%;
-				border-bottom-left-radius: 15px 25px;
-			}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: 50px 15px / 20px 25px 10%;
+            }
+            #reference {
+                border-top-left-radius: 50px 20px;
+                border-top-right-radius: 15px 25px;
+                border-bottom-right-radius: 50px 10%;
+                border-bottom-left-radius: 15px 25px;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-    	<p><br/></p>
-		<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-    		var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("50px 20px", "15px 25px", "50px 10%", "15px 25px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-         /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-007-ref.xht b/css/css-backgrounds/border-radius-007-ref.xht
index 3d3ab1b..c10c0ee 100644
--- a/css/css-backgrounds/border-radius-007-ref.xht
+++ b/css/css-backgrounds/border-radius-007-ref.xht
@@ -3,34 +3,28 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 50px 0.5in;
-				border-top-right-radius: 10mm 25px;
-				border-bottom-right-radius: 6pc 30%;
-				border-bottom-left-radius: 15% 70pt;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 50px 0.5in;
+                border-top-right-radius: 10mm 25px;
+                border-bottom-right-radius: 6pc 30%;
+                border-bottom-left-radius: 15% 70pt;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-007.xht b/css/css-backgrounds/border-radius-007.xht
index ab0bdf2..df442e0 100644
--- a/css/css-backgrounds/border-radius-007.xht
+++ b/css/css-backgrounds/border-radius-007.xht
@@ -3,68 +3,36 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using slash: 50px 10mm 3pc 15% / 0.5in 25px 10% 70pt</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/"/>
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-007-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify border-radius property set with values (in different units) using slash, works as expected" />
-        <style type="text/css">
-        /* <![CDATA[ */
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: 50px 10mm 6pc 15% / 0.5in 25px 30% 70pt;
-        	}
-			#reference
-			{
-			    border-top-left-radius: 50px 0.5in;
-				border-top-right-radius: 10mm 25px;
-				border-bottom-right-radius: 6pc 30%;
-				border-bottom-left-radius: 15% 70pt;
-			}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: 50px 10mm 6pc 15% / 0.5in 25px 30% 70pt;
+            }
+            #reference {
+                border-top-left-radius: 50px 0.5in;
+                border-top-right-radius: 10mm 25px;
+                border-bottom-right-radius: 6pc 30%;
+                border-bottom-left-radius: 15% 70pt;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-    	<p><br/></p>
-		<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-    		  var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("50px 48px", "37px 25px", "96px 30%", "15% 93px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-        /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-008.xht b/css/css-backgrounds/border-radius-008.xht
index 8c5a7a9..a4b1408 100644
--- a/css/css-backgrounds/border-radius-008.xht
+++ b/css/css-backgrounds/border-radius-008.xht
@@ -3,65 +3,33 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using slash: 50px 15px 40px 30em 25em / 20px 25px</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/"/>
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-001-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify border-radius property set with excess values, works as expected" />
-        <style type="text/css">
-        /* <![CDATA[ */
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
             }
-        	#test
-        	{
-        		border-radius: 50px 15px 40px 30em 25em / 20px 25px;
-        	}
-        /* ]]> */
+            #test {
+                border-radius: 50px 15px 40px 30em 25em / 20px 25px;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-    	<p>
-			There should be two boxes with no rounded corners.
-		</p>
-		<!-- PASS AND FAIL CRITERIA ARE IN THE LIST BELOW -->
-				<ul>
-					<li>PASS if the two boxes below are the same.</li>
-					<li>FAIL if the output is not as expected.</li>
-				</ul>
+        <p>There should be two boxes with no rounded corners.</p>
+        <!-- PASS AND FAIL CRITERIA ARE IN THE LIST BELOW -->
+        <ul>
+            <li>PASS if the two boxes below are the same.</li>
+            <li>FAIL if the output is not as expected.</li>
+        </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-    		  var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("0px", "0px", "0px", "0px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-        /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-009-ref.xht b/css/css-backgrounds/border-radius-009-ref.xht
index 9906499..c86e22d 100644
--- a/css/css-backgrounds/border-radius-009-ref.xht
+++ b/css/css-backgrounds/border-radius-009-ref.xht
@@ -3,34 +3,29 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 20%;
-				border-top-right-radius: 25px;
-				border-bottom-right-radius: 20%;
-				border-bottom-left-radius: 25px;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 20%;
+                border-top-right-radius: 25px;
+                border-bottom-right-radius: 20%;
+                border-bottom-left-radius: 25px;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
 
diff --git a/css/css-backgrounds/border-radius-009.xht b/css/css-backgrounds/border-radius-009.xht
index 70169c3..822f166 100644
--- a/css/css-backgrounds/border-radius-009.xht
+++ b/css/css-backgrounds/border-radius-009.xht
@@ -3,70 +3,39 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using &quot;inherit&quot;</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/"/>
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-009-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify inherit feature works, when it is assigned to border-radius" />
-        <style type="text/css">
-        /* <![CDATA[ */
-        	body
-        	{
-        		border-radius: 20% 25px;
-        	}
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: inherit;
-        	}
-			#reference
-			{
-			    border-top-left-radius: 20%;
-				border-top-right-radius: 25px;
-				border-bottom-right-radius: 20%;
-				border-bottom-left-radius: 25px;
-			}
-        /* ]]> */
+        <style>
+            body {
+                border-radius: 20% 25px;
+            }
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: inherit;
+            }
+            #reference {
+                border-top-left-radius: 20%;
+                border-top-right-radius: 25px;
+                border-bottom-right-radius: 20%;
+                border-bottom-left-radius: 25px;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with 4 rounded corners.
-		</p>
+        <p>There should be two identical boxes, each with 4 rounded corners.</p>
         <ul>
             <li>PASS if the two boxes below are the same and all 4 corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-    	<p><br/></p>
-		<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-        /* <![CDATA[ */
-        	var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("20%", "25px", "20%", "25px");
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-          /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-010-ref.xht b/css/css-backgrounds/border-radius-010-ref.xht
index 7cb553e..882032b 100644
--- a/css/css-backgrounds/border-radius-010-ref.xht
+++ b/css/css-backgrounds/border-radius-010-ref.xht
@@ -3,34 +3,29 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-         /* <![CDATA[ */
-            div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			    border-top-left-radius: 20% 25px;
-				border-top-right-radius: 0;
-				border-bottom-right-radius: 20pt 3em;
-				border-bottom-left-radius: 0;
-        	}
-        /* ]]> */
+        <style>
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-top-left-radius: 20% 25px;
+                border-top-right-radius: 0;
+                border-bottom-right-radius: 20pt 3em;
+                border-bottom-left-radius: 0;
+            }
         </style>
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with rounded corners at the top left and bottom right only.
-		</p>
+        <p>There should be two identical boxes, each with rounded corners at the top left and bottom right only.</p>
         <ul>
             <li>PASS if the two boxes below are the same and only top left and bottom right corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
-    	<div id="test"></div>
-		<p><br/></p>
-    	<div id="reference"></div>
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
 
diff --git a/css/css-backgrounds/border-radius-010.xht b/css/css-backgrounds/border-radius-010.xht
index 99791d1..32d4646 100644
--- a/css/css-backgrounds/border-radius-010.xht
+++ b/css/css-backgrounds/border-radius-010.xht
@@ -3,73 +3,40 @@
     <head>
         <title>CSS Test: Borders. border&ndash;radius using &quot;inherit&quot;</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <meta http-equiv="Content-Style-Type" content="text/css"/>
-        <meta http-equiv="Content-Script-Type" content="text/javascript"/>
         <link rel="author" title="Nokia" href="http://www.nokia.com/"/>
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-010-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="To verify inherit feature works, when it is assigned to border-radius" />
-        <style type="text/css">
-        /* <![CDATA[ */
-        	body
-        	{
-        		border-top-left-radius: 20% 25px;
-        		border-bottom-right-radius: 20pt 3em;
-        	}
-        	div
-            {
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-        		border-radius: inherit;
-        	}
-			#reference
-			{
-			    border-top-left-radius: 20% 25px;
-				border-top-right-radius: 0;
-				border-bottom-right-radius: 20pt 3em;
-				border-bottom-left-radius: 0;
-			}
-        /* ]]> */
+        <style>
+            body {
+                border-top-left-radius: 20% 25px;
+                border-bottom-right-radius: 20pt 3em;
+            }
+            div {
+                border: 2px solid #a1a1a1;
+                background: #dddddd;
+                width: 200px;
+                height: 100px;
+                border-radius: inherit;
+            }
+            #reference {
+                border-top-left-radius: 20% 25px;
+                border-top-right-radius: 0;
+                border-bottom-right-radius: 20pt 3em;
+                border-bottom-left-radius: 0;
+            }
         </style>
-<!--
-        <script type="text/javascript" src="js/css3_test_helper.js"></script>
--->
     </head>
     <body>
-        <p>
-            There should be two identical boxes, each with rounded corners at the top left and bottom right only.
-		</p>
+        <p>There should be two identical boxes, each with rounded corners at the top left and bottom right only.</p>
         <ul>
             <li>PASS if the two boxes below are the same and only top left and bottom right corners are rounded.</li>
             <li>FAIL if the output is not as expected.</li>
         </ul>
 
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="test"></div>
-    	<p><br/></p>
-		<div id="reference"></div>
-<!--
-    	<script type="text/javascript">
-         /* <![CDATA[ */
-        	var borders = new Array("border-top-left-radius", "border-top-right-radius",
-    		                          "border-bottom-right-radius", "border-bottom-left-radius");
-        	var expectedValues = new Array("20% 25px", "0px", "26px 48px", "0px");
-
-        	var testResult = check_Multiple_CSS_property(borders, expectedValues);
-
-        	/* if (testResult.pass)
-				  // This portion of the code has been removed to ensure that the test case is not automated
-				else {
-					// This portion of the code has been removed to ensure that the test case is not automated
-			  } */
-
-          if (top.FrameEnabled)	top.fnLog(testResult);
-        /* ]]> */
-        </script>
--->
+        <div id="test"></div>
+        <p><br/></p>
+        <div id="reference"></div>
     </body>
 </html>
-
diff --git a/css/css-backgrounds/border-radius-011-ref.xht b/css/css-backgrounds/border-radius-011-ref.xht
index 32120f1..cdeaf1c 100644
--- a/css/css-backgrounds/border-radius-011-ref.xht
+++ b/css/css-backgrounds/border-radius-011-ref.xht
@@ -3,29 +3,22 @@
     <head>
         <title>CSS Test: Border radius reference</title>
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
-        <style type="text/css">
-    	/* <![CDATA[ */
-        	div
-        	{
-        		border:2px solid #a1a1a1;
-        		background:#dddddd;
-        		width:200px;
-        		height: 100px;
-			}
-        /* ]]> */
+        <style>
+            div {
+                position: absolute;
+                top: 150px;
+                width: 200px;
+                height: 100px;
+            }
         </style>
     </head>
     <body>
-    	<p>
-			There should be two boxes, each with corners that have no rounding.
-		</p>
-		<ul>
-			<li>PASS if the two boxes below are the same, each with sharp corner joins (no rounding).</li>
-			<li>FAIL if corner joins have any rounding.</li>
-		</ul>
+        <p>A white rectangle with no border-radius property sits exactly on top of an exact same size red rectangle with border-radius set to zero.</p>
+        <ul>
+            <li>PASS if no red is visible on the page.</li>
+            <li>FAIL if any red is visible.</li>
+        </ul>
 
-    	<div></div>
-		<p><br/></p>
-    	<div></div>
+        <div></div>
     </body>
 </html>
diff --git a/css/css-backgrounds/border-radius-011.xht b/css/css-backgrounds/border-radius-011.xht
index 6a5ceab..8439302 100644
--- a/css/css-backgrounds/border-radius-011.xht
+++ b/css/css-backgrounds/border-radius-011.xht
@@ -5,44 +5,36 @@
         <link rel="author" title="Nokia" href="http://www.nokia.com/" />
         <link rel="help" href="http://www.w3.org/TR/css3-background/#the-border-radius" />
         <link rel="match" href="border-radius-011-ref.xht" />
-        <meta name="flags" content="" />
         <meta name="assert" content="If border-radius is zero, all corners are square." />
-        <style type="text/css"><![CDATA[
-		        	div
-            {
-        		width:200px;
-        		height: 100px;
+        <style>
+            div {
+                width: 200px;
+                height: 100px;
             }
-            #reference
-            {
-            	background: red;
-            	position: absolute;
-            	top: 100px;
-            	left: 30px;
+            #reference {
+                background: red;
+                position: absolute;
+                top: 150px;
+                left: 30px;
             }
-        	#test
-        	{
-        		position: absolute;
-        		border-radius: 0;
-        		background: white;
-        		top: 100px;
-        		left: 30px;
-        	}
-
-     ]]>
+            #test {
+                position: absolute;
+                border-radius: 0;
+                background: white;
+                top: 150px;
+                left: 30px;
+            }
         </style>
     </head>
     <body>
-    	<p>
-			A white rectangle with no border-radius property sits exactly on top of an exact same size red rectangle with border-radius set to zero.
-		</p>
-		<!-- PASS AND FAIL CRITERIA ARE IN THE LIST BELOW -->
-		<ul>
-			<li>PASS if no red is visible on the page.</li>
-			<li>FAIL if any red is visible.</li>
-		</ul>
+        <p>A white rectangle with no border-radius property sits exactly on top of an exact same size red rectangle with border-radius set to zero.</p>
+        <!-- PASS AND FAIL CRITERIA ARE IN THE LIST BELOW -->
+        <ul>
+            <li>PASS if no red is visible on the page.</li>
+            <li>FAIL if any red is visible.</li>
+        </ul>
         <!-- PLACE TEST CONTENT FROM HERE -->
-    	<div id="reference"></div>
-    	<div id="test"></div>
+        <div id="reference"></div>
+        <div id="test"></div>
     </body>
 </html>
diff --git a/css/css-backgrounds/box-shadow-syntax-001.html b/css/css-backgrounds/box-shadow-syntax-001.html
new file mode 100644
index 0000000..e7c83b3
--- /dev/null
+++ b/css/css-backgrounds/box-shadow-syntax-001.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<title>CSS Test: Box Shadow Syntax: Reordering Components</title>
+<link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"/>
+<link rel="help" href="http://www.w3.org/TR/css3-background/#the-box-shadow"/>
+<meta name="assert" content="Box shadow color, inset, and length parameters can be mixed in any order, but lengths must stay adjacent." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="parsing/resources/parsing-testcommon.js"></script>
+<script>
+  // color only
+  test_valid_value("box-shadow", "4px 4px green", "green 4px 4px");
+  test_valid_value("box-shadow", "green -4px 4px", "green -4px 4px");
+  test_valid_value("box-shadow", "-4px 4px 0 green", "green -4px 4px 0px");
+  test_valid_value("box-shadow", "green -4px 4px 0", "green -4px 4px 0px");
+  test_valid_value("box-shadow", "-4px 4px 0 0 green", "green -4px 4px 0px 0px");
+  test_valid_value("box-shadow", "green -4px 4px 0 0", "green -4px 4px 0px 0px");
+
+  // inset only
+  test_valid_value("box-shadow", "4px -4px inset", "4px -4px inset");
+  test_valid_value("box-shadow", "inset 4px -4px", "4px -4px inset");
+  test_valid_value("box-shadow", "4px -4px 0 inset", "4px -4px 0px inset");
+  test_valid_value("box-shadow", "inset 4px -4px 0", "4px -4px 0px inset");
+  test_valid_value("box-shadow", "4px -4px 0 0 inset", "4px -4px 0px 0px inset");
+  test_valid_value("box-shadow", "inset 4px -4px 0 0", "4px -4px 0px 0px inset");
+
+  // color and inset
+  test_valid_value("box-shadow", "4px -4px green inset", "green 4px -4px inset");
+  test_valid_value("box-shadow", "4px -4px inset green", "green 4px -4px inset");
+  test_valid_value("box-shadow", "inset green 4px -4px", "green 4px -4px inset");
+  test_valid_value("box-shadow", "green inset 4px -4px", "green 4px -4px inset");
+  test_valid_value("box-shadow", "green 4px -4px inset", "green 4px -4px inset");
+  test_valid_value("box-shadow", "inset 4px -4px green", "green 4px -4px inset");
+  test_valid_value("box-shadow", "inset green 4px -4px 0", "green 4px -4px 0px inset");
+  test_valid_value("box-shadow", "green inset 4px -4px 0", "green 4px -4px 0px inset");
+  test_valid_value("box-shadow", "4px -4px 0 green inset", "green 4px -4px 0px inset");
+  test_valid_value("box-shadow", "4px -4px 0 inset green", "green 4px -4px 0px inset");
+  test_valid_value("box-shadow", "green 4px -4px 0 inset", "green 4px -4px 0px inset");
+  test_valid_value("box-shadow", "inset 4px -4px 0 green", "green 4px -4px 0px inset");
+  test_valid_value("box-shadow", "inset green 4px -4px 0 0", "green 4px -4px 0px 0px inset");
+  test_valid_value("box-shadow", "green inset 4px -4px 0 0", "green 4px -4px 0px 0px inset");
+  test_valid_value("box-shadow", "4px -4px 0 0 green inset", "green 4px -4px 0px 0px inset");
+  test_valid_value("box-shadow", "4px -4px 0 0 inset green", "green 4px -4px 0px 0px inset");
+  test_valid_value("box-shadow", "green 4px -4px 0 0 inset", "green 4px -4px 0px 0px inset");
+  test_valid_value("box-shadow", "inset 4px -4px 0 0 green", "green 4px -4px 0px 0px inset");
+
+  test_invalid_value("box-shadow", "red");
+  test_invalid_value("box-shadow", "4px red");
+  test_invalid_value("box-shadow", "red 4px");
+  test_invalid_value("box-shadow", "-4px red 4px");
+  test_invalid_value("box-shadow", "red -4px 4px red");
+  test_invalid_value("box-shadow", "-4px 4px red 0");
+  test_invalid_value("box-shadow", "-4px 4px 0 red 0");
+  test_invalid_value("box-shadow", "inset");
+  test_invalid_value("box-shadow", "inset 4px");
+  test_invalid_value("box-shadow", "4px inset");
+  test_invalid_value("box-shadow", "4px inset -4px");
+  test_invalid_value("box-shadow", "inset 4px -4px inset");
+  test_invalid_value("box-shadow", "4px -4px inset 0");
+  test_invalid_value("box-shadow", "4px -4px 0 inset 0");
+  test_invalid_value("box-shadow", "red inset");
+  test_invalid_value("box-shadow", "inset red");
+  test_invalid_value("box-shadow", "4px red inset");
+  test_invalid_value("box-shadow", "red inset 4px");
+  test_invalid_value("box-shadow", "4px inset red");
+  test_invalid_value("box-shadow", "inset red 4px");
+  test_invalid_value("box-shadow", "4px red inset -4px");
+  test_invalid_value("box-shadow", "4px inset red -4px");
+  test_invalid_value("box-shadow", "inset 4px red -4px");
+  test_invalid_value("box-shadow", "4px red 4px inset");
+  test_invalid_value("box-shadow", "red 4px inset -4px");
+  test_invalid_value("box-shadow", "4px inset -4px red");
+  test_invalid_value("box-shadow", "4px -4px red inset 0");
+  test_invalid_value("box-shadow", "4px -4px inset red 0");
+  test_invalid_value("box-shadow", "inset 4px -4px red 0");
+  test_invalid_value("box-shadow", "4px -4px red 0 inset");
+  test_invalid_value("box-shadow", "red 4px -4px inset 0");
+  test_invalid_value("box-shadow", "4px -4px inset 0 red");
+</script>
+</html>
diff --git a/css/css-backgrounds/box-shadow-syntax-001.xht b/css/css-backgrounds/box-shadow-syntax-001.xht
deleted file mode 100644
index 4e32c9f..0000000
--- a/css/css-backgrounds/box-shadow-syntax-001.xht
+++ /dev/null
@@ -1,147 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Test: Box Shadow Syntax: Reordering Components</title>
-  <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"/>
-  <link rel="help" href="http://www.w3.org/TR/css3-background/#the-box-shadow"/>
-  <link rel="match" href="reference/box-shadow-syntax-001.xht"/>
-  <meta name="assert" content="Box shadow color, inset, and length parameters can be mixed in any order, but lengths must stay adjacent." />
-  <style type="text/css">
-
-    .container {
-      margin: 4px;
-      border: solid red 4px;
-      padding-top: 1px;
-      border-style: none none solid solid;
-    }
-    .outset {
-      height: 12px;
-      margin: -5px -4px 0 0;
-
-    }
-    .inset {
-      color: green;
-      height: 13px;
-      margin: -1px 0 -4px -4px;
-    }
-
-    /* color */
-    #c1 > div { box-shadow: -4px 4px green;
-                box-shadow: red;
-                box-shadow: 4px red;
-                box-shadow: red 4px;
-                box-shadow: -4px red 4px;
-                box-shadow: red -4px 4px red;
-                box-shadow: -4px 4px red 0;
-                box-shadow: -4px 4px 0 red 0;
-    }
-    #c2 > div { box-shadow: green -4px 4px;       }
-    #c3 > div { box-shadow: -4px 4px 0 green;     }
-    #c4 > div { box-shadow: green -4px 4px 0;     }
-    #c5 > div { box-shadow: -4px 4px 0 0 green;   }
-    #c6 > div { box-shadow: green -4px 4px 0 0;   }
-
-    /* inset */
-    #i1 > div { box-shadow: 4px -4px inset;       }
-    #i2 > div { box-shadow: inset 4px -4px;       }
-    #i3 > div { box-shadow: 4px -4px 0 inset;     }
-    #i4 > div { box-shadow: inset 4px -4px 0;     }
-    #i5 > div { box-shadow: 4px -4px 0 0 inset;   }
-    #i6 > div { box-shadow: inset 4px -4px 0 0;   }
-    #i7       { border-color: green;              }
-    #i7 > div { box-shadow: 4px -4px green inset;
-                color: red;
-                box-shadow: inset;
-                box-shadow: inset 4px;
-                box-shadow: 4px inset;
-                box-shadow: 4px inset -4px;
-                box-shadow: inset 4px -4px inset;
-                box-shadow: 4px -4px inset 0;
-                box-shadow: 4px -4px 0 inset 0;
-  }
-
-
-    /* color and inset */
-    #ci1 > div { box-shadow: 4px -4px green inset;
-
-                 box-shadow: red inset;
-                 box-shadow: inset red;
-
-                 box-shadow: 4px red inset;
-                 box-shadow: red inset 4px;
-                 box-shadow: 4px inset red;
-                 box-shadow: inset red 4px;
-
-                 box-shadow: 4px red inset -4px;
-                 box-shadow: 4px inset red -4px;
-                 box-shadow: inset 4px red -4px;
-                 box-shadow: 4px red 4px inset;
-                 box-shadow: red 4px inset -4px;
-                 box-shadow: 4px inset -4px red;
-
-                 box-shadow: 4px -4px red inset 0;
-                 box-shadow: 4px -4px inset red 0;
-                 box-shadow: inset 4px -4px red 0;
-                 box-shadow: 4px -4px red 0 inset;
-                 box-shadow: red 4px -4px inset 0;
-                 box-shadow: 4px -4px inset 0 red;
-    }
-    #ci2 > div { box-shadow: 4px -4px inset green; }
-    #ci3 > div { box-shadow: inset green 4px -4px; }
-    #ci4 > div { box-shadow: green inset 4px -4px; }
-    #ci5 > div { box-shadow: green 4px -4px inset; }
-    #ci6 > div { box-shadow: inset 4px -4px green; }
-
-    #ci11 > div { box-shadow: inset green 4px -4px 0; }
-    #ci12 > div { box-shadow: green inset 4px -4px 0; }
-    #ci13 > div { box-shadow: 4px -4px 0 green inset; }
-    #ci14 > div { box-shadow: 4px -4px 0 inset green; }
-    #ci15 > div { box-shadow: green 4px -4px 0 inset; }
-    #ci16 > div { box-shadow: inset 4px -4px 0 green; }
-
-    #ci21 > div { box-shadow: inset green 4px -4px 0 0; }
-    #ci22 > div { box-shadow: green inset 4px -4px 0 0; }
-    #ci23 > div { box-shadow: 4px -4px 0 0 green inset; }
-    #ci24 > div { box-shadow: 4px -4px 0 0 inset green; }
-    #ci25 > div { box-shadow: green 4px -4px 0 0 inset; }
-    #ci26 > div { box-shadow: inset 4px -4px 0 0 green; }
-  </style>
- </head>
- <body>
-  <p>There must be 31 green L-shapes below and no red.</p>
-
-<div class="wrapper">
-  <div id="c1" class="container"><div class="outset"></div></div>
-  <div id="c2" class="container"><div class="outset"></div></div>
-  <div id="c3" class="container"><div class="outset"></div></div>
-  <div id="c4" class="container"><div class="outset"></div></div>
-  <div id="c5" class="container"><div class="outset"></div></div>
-  <div id="c6" class="container"><div class="outset"></div></div>
-  <div id="i1" class="container"><div class="inset"></div></div>
-  <div id="i2" class="container"><div class="inset"></div></div>
-  <div id="i3" class="container"><div class="inset"></div></div>
-  <div id="i4" class="container"><div class="inset"></div></div>
-  <div id="i5" class="container"><div class="inset"></div></div>
-  <div id="i6" class="container"><div class="inset"></div></div>
-  <div id="i7" class="container"><div class="inset"></div></div>
-  <div id="ci1" class="container"><div class="inset"></div></div>
-  <div id="ci2" class="container"><div class="inset"></div></div>
-  <div id="ci3" class="container"><div class="inset"></div></div>
-  <div id="ci4" class="container"><div class="inset"></div></div>
-  <div id="ci5" class="container"><div class="inset"></div></div>
-  <div id="ci6" class="container"><div class="inset"></div></div>
-  <div id="ci11" class="container"><div class="inset"></div></div>
-  <div id="ci12" class="container"><div class="inset"></div></div>
-  <div id="ci13" class="container"><div class="inset"></div></div>
-  <div id="ci14" class="container"><div class="inset"></div></div>
-  <div id="ci15" class="container"><div class="inset"></div></div>
-  <div id="ci16" class="container"><div class="inset"></div></div>
-  <div id="ci21" class="container"><div class="inset"></div></div>
-  <div id="ci22" class="container"><div class="inset"></div></div>
-  <div id="ci23" class="container"><div class="inset"></div></div>
-  <div id="ci24" class="container"><div class="inset"></div></div>
-  <div id="ci25" class="container"><div class="inset"></div></div>
-  <div id="ci26" class="container"><div class="inset"></div></div>
-</div>
- </body>
-</html>
diff --git a/css/css-backgrounds/css3-background-origin-padding-box.html b/css/css-backgrounds/css3-background-origin-padding-box.html
index 1b2fe5e..c1dc3f0 100644
--- a/css/css-backgrounds/css3-background-origin-padding-box.html
+++ b/css/css-backgrounds/css3-background-origin-padding-box.html
@@ -2,11 +2,11 @@
 <html lang="en">
   <head>
     <meta charset="utf-8" />
-    <title>CSS Backgrounds Test:background origin property with value content-box</title>
+    <title>CSS Backgrounds Test:background origin property with value padding-box</title>
     <link rel="author" title="yanshasha" href="mailto:yanshasha133@gmail.com" />
     <link rel="reviewer" title="Dayang Shen" href="mailto:shendayang@baidu.com" /> <!-- 2013-08-26 -->
     <link rel="help" href="http://www.w3.org/TR/css3-background/#the-background-origin" />
-    <link rel="match" href="reference/css3-background-origin-content-box-ref.html" />
+    <link rel="match" href="reference/css3-background-origin-padding-box-ref.html" />
     <style type="text/css">
             div {
                 width: 100px;
diff --git a/css/css-backgrounds/css3-background-size-001.html b/css/css-backgrounds/css3-background-size-001.html
index f049ac3..6da2c9d 100644
--- a/css/css-backgrounds/css3-background-size-001.html
+++ b/css/css-backgrounds/css3-background-size-001.html
@@ -6,7 +6,7 @@
     <link rel="author" title="yanshasha" href="mailto:yanshasha133@gmail.com" />
     <link rel="reviewer" title="Dayang Shen" href="mailto:shendayang@baidu.com" /><!-- 2013-08-26 -->
     <link rel="help" href="http://www.w3.org/TR/css3-background/#the-background-size" />
-    <link rel="match" href="reference/css3-background-size-ref.html" />
+    <link rel="match" href="reference/css3-background-size-001-ref.html" />
     <style type="text/css">
             div {
                 width: 150px;
diff --git a/css/css-backgrounds/parsing/background-attachment-invalid.html b/css/css-backgrounds/parsing/background-attachment-invalid.html
new file mode 100644
index 0000000..e4924a2
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-attachment-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-attachment with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-attachment">
+<meta name="assert" content="background-attachment supports only the grammar '<attachment>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background-attachment", "auto");
+test_invalid_value("background-attachment", "local, none");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-attachment-valid.html b/css/css-backgrounds/parsing/background-attachment-valid.html
new file mode 100644
index 0000000..4c7243f
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-attachment-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-attachment with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-attachment">
+<meta name="assert" content="background-attachment supports the full grammar '<attachment>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("background-attachment", "fixed");
+test_valid_value("background-attachment", "scroll, fixed, local, fixed, scroll");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-clip-invalid.html b/css/css-backgrounds/parsing/background-clip-invalid.html
new file mode 100644
index 0000000..50647e8
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-clip-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-clip with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-clip">
+<meta name="assert" content="background-clip supports only the grammar '<box>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background-clip", "fill-box");
+test_invalid_value("background-clip", "margin-box");
+test_invalid_value("background-clip", "stroke-box");
+test_invalid_value("background-clip", "view-box");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-clip-valid.html b/css/css-backgrounds/parsing/background-clip-valid.html
new file mode 100644
index 0000000..2b6bd49
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-clip-valid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-clip with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-clip">
+<meta name="assert" content="background-clip supports the full grammar '<box>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("background-clip", "border-box");
+test_valid_value("background-clip", "padding-box");
+test_valid_value("background-clip", "content-box");
+
+test_valid_value("background-clip", "border-box, padding-box, content-box");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-color-invalid.html b/css/css-backgrounds/parsing/background-color-invalid.html
new file mode 100644
index 0000000..cc31e26
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-color-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-color with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-color">
+<meta name="assert" content="background-color supports only the grammar '<color>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background-color", "none");
+test_invalid_value("background-color", "black white");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-color-valid.html b/css/css-backgrounds/parsing/background-color-valid.html
new file mode 100644
index 0000000..e5f582f
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-color-valid.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-color with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-color">
+<meta name="assert" content="background-color supports the full grammar '<color>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Edge serializes as "currentColor".
+test_valid_value("background-color", "currentcolor", ["currentcolor", "currentColor"]);
+test_valid_value("background-color", "currentColor", ["currentcolor", "currentColor"]);
+
+test_valid_value("background-color", "red");
+test_valid_value("background-color", "#00FF00", "rgb(0, 255, 0)");
+test_valid_value("background-color", "rgb(0, 0, 255)");
+test_valid_value("background-color", "rgb(100%, 100%, 0%)", "rgb(255, 255, 0)");
+test_valid_value("background-color", "hsl(120, 100%, 50%)", ["rgb(0, 255, 0)", "hsl(120, 100%, 50%)"]); // Edge serializes as hsl
+test_valid_value("background-color", "teal");
+
+test_valid_value("background-color", "transparent");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-image-invalid.html b/css/css-backgrounds/parsing/background-image-invalid.html
new file mode 100644
index 0000000..a5f2a90
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-image-invalid.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-image with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-image">
+<meta name="assert" content="background-image supports only the grammar '<bg-image>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background-image", "none, auto");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-image-valid.html b/css/css-backgrounds/parsing/background-image-valid.html
new file mode 100644
index 0000000..873800f
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-image-valid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-image with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-image">
+<meta name="assert" content="background-image supports the full grammar '<bg-image>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("background-image", "none");
+
+// Safari removes quotes.
+test_valid_value("background-image", 'url("http://www.example.com/")', ['url("http://www.example.com/")', 'url(http://www.example.com/)']);
+test_valid_value("background-image", 'none, url("http://www.example.com/")', ['none, url("http://www.example.com/")', 'none, url(http://www.example.com/)']);
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-invalid.html b/css/css-backgrounds/parsing/background-invalid.html
new file mode 100644
index 0000000..adc178b
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-invalid.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background">
+<meta name="assert" content="background supports only the grammar '<bg-layer># , <final-bg-layer>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background", "red, green");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-origin-invalid.html b/css/css-backgrounds/parsing/background-origin-invalid.html
new file mode 100644
index 0000000..5458925
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-origin-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-origin with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-origin">
+<meta name="assert" content="background-origin supports only the grammar '<box>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background-origin", "fill-box");
+test_invalid_value("background-origin", "margin-box");
+test_invalid_value("background-origin", "stroke-box");
+test_invalid_value("background-origin", "view-box");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-origin-valid.html b/css/css-backgrounds/parsing/background-origin-valid.html
new file mode 100644
index 0000000..ff52126
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-origin-valid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-origin with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-origin">
+<meta name="assert" content="background-origin supports the full grammar '<box>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("background-origin", "border-box");
+test_valid_value("background-origin", "padding-box");
+test_valid_value("background-origin", "content-box");
+
+test_valid_value("background-origin", "border-box, padding-box, content-box");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-position-invalid.html b/css/css-backgrounds/parsing/background-position-invalid.html
new file mode 100644
index 0000000..5ddf5e0
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-position-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-position with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-position">
+<meta name="assert" content="background-position supports only the grammar '<bg-position>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background-position", "left right");
+test_invalid_value("background-position", "top bottom");
+test_invalid_value("background-position", "1% center 2px");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-position-valid.html b/css/css-backgrounds/parsing/background-position-valid.html
new file mode 100644
index 0000000..836f348
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-position-valid.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-position with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-position">
+<meta name="assert" content="background-position supports the full grammar '<bg-position>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("background-position", "1px", ["1px", "1px center"]);
+test_valid_value("background-position", "1px center", ["1px", "1px center"]);
+test_valid_value("background-position", "-2% -3%");
+test_valid_value("background-position", "5% top");
+test_valid_value("background-position", "center", ["center", "center center"]);
+test_valid_value("background-position", "center center", ["center", "center center"]);
+test_valid_value("background-position", "center 6px");
+test_valid_value("background-position", "center left", ["left center", "left"]);
+test_valid_value("background-position", "center right 7%", "right 7% center");
+test_valid_value("background-position", "center bottom", ["center bottom", "bottom"]);
+test_valid_value("background-position", "center top 8px", ["center top 8px", "center 8px"]);
+test_valid_value("background-position", "left", ["left center", "left"]);
+test_valid_value("background-position", "right 9%");
+test_valid_value("background-position", "left 10px center", ["left 10px center", "10px"]);
+test_valid_value("background-position", "right 11% bottom", ["right 11% bottom", "right 11% bottom 0%"]); // "right 11% bottom 0%" in Edge
+test_valid_value("background-position", "left 12px top 13px", ["left 12px top 13px", "12px 13px"]);
+test_valid_value("background-position", "right center", ["right center", "right"]);
+test_valid_value("background-position", "left bottom");
+test_valid_value("background-position", "right top 14%", ["right top 14%", "right 14%"]);
+test_valid_value("background-position", "bottom", ["center bottom", "bottom"]);
+test_valid_value("background-position", "top 15px center", ["center top 15px", "center 15px"]);
+test_valid_value("background-position", "bottom 16% left", ["left bottom 16%", "left 0% bottom 16%"]); // "left 0% bottom 16%" in Edge
+test_valid_value("background-position", "top 17px right 18px", "right 18px top 17px");
+test_valid_value("background-position", "bottom center", ["center bottom", "bottom"]);
+test_valid_value("background-position", "top left", "left top");
+test_valid_value("background-position", "bottom right 19%", ["right 19% bottom", "right 19% bottom 0%"]); // "right 19% bottom 0%" in Edge
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-repeat-invalid.html b/css/css-backgrounds/parsing/background-repeat-invalid.html
new file mode 100644
index 0000000..a32f508
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-repeat-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-repeat with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-repeat">
+<meta name="assert" content="background-repeat supports only the grammar '<repeat-style>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("background-repeat", "repeat repeat-x");
+test_invalid_value("background-repeat", "repeat-y round");
+test_invalid_value("background-repeat", "repeat space round");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-repeat-valid.html b/css/css-backgrounds/parsing/background-repeat-valid.html
new file mode 100644
index 0000000..7554909
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-repeat-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-repeat with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-repeat">
+<meta name="assert" content="background-repeat supports the full grammar '<repeat-style>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("background-repeat", "repeat");
+test_valid_value("background-repeat", "repeat-x, repeat-y, repeat", "repeat-x, repeat-y, repeat");
+test_valid_value("background-repeat", "repeat space, round no-repeat, repeat-x");
+test_valid_value("background-repeat", "repeat repeat", ["repeat", "repeat repeat"]);
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-size-invalid.html b/css/css-backgrounds/parsing/background-size-invalid.html
new file mode 100644
index 0000000..7259cbd
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-size-invalid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-size with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-size">
+<meta name="assert" content="background-size supports only the grammar '<bg-size>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Blink and WebKit fail these by accepting negative values.
+test_invalid_value("background-size", "-1px");
+test_invalid_value("background-size", "2% -3%");
+
+test_invalid_value("background-size", "1px 2px 3px");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-size-valid.html b/css/css-backgrounds/parsing/background-size-valid.html
new file mode 100644
index 0000000..6703164
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-size-valid.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background-size with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background-size">
+<meta name="assert" content="background-size supports the full grammar '<bg-size>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("background-size", "1px", ["1px", "1px auto"]);
+test_valid_value("background-size", "1px auto", ["1px", "1px auto"]);
+test_valid_value("background-size", "2% 3%");
+test_valid_value("background-size", "auto", ["auto", "auto auto"]);
+test_valid_value("background-size", "auto auto", ["auto", "auto auto"]);
+test_valid_value("background-size", "auto 4%");
+test_valid_value("background-size", "contain");
+test_valid_value("background-size", "cover");
+
+test_valid_value("background-size", "auto 1px, 2% 3%, contain");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/background-valid.html b/css/css-backgrounds/parsing/background-valid.html
new file mode 100644
index 0000000..29737e2
--- /dev/null
+++ b/css/css-backgrounds/parsing/background-valid.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing background with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#background">
+<meta name="assert" content="background supports the full grammar '<bg-layer># , <final-bg-layer>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Background serialization varies across browsers. https://github.com/w3c/csswg-drafts/issues/418
+
+test_valid_value("background",
+  'url("https://example.com/") 1px 2px / 3px 4px space round local padding-box content-box, rgb(5, 6, 7) url("https://example.com/") 1px 2px / 3px 4px space round local padding-box content-box', [
+  'url("https://example.com/") 1px 2px / 3px 4px space round local padding-box content-box, rgb(5, 6, 7) url("https://example.com/") 1px 2px / 3px 4px space round local padding-box content-box', // spec
+  'url("https://example.com/") local space round 1px 2px / 3px 4px padding-box content-box, url("https://example.com/") local space round 1px 2px / 3px 4px padding-box content-box rgb(5, 6, 7)', // Edge
+  'url("https://example.com/") space round local 1px 2px / 3px 4px padding-box content-box, rgb(5, 6, 7) url("https://example.com/") space round local 1px 2px / 3px 4px padding-box content-box', // Firefox
+  'url("https://example.com/") 1px 2px / 3px 4px space round local padding-box content-box, url("https://example.com/") 1px 2px / 3px 4px space round local padding-box content-box rgb(5, 6, 7)', // Blink
+  'url(https://example.com/) 1px 2px / 3px 4px space round local padding-box content-box, url(https://example.com/) 1px 2px / 3px 4px space round local padding-box content-box rgb(5, 6, 7)' // WebKit omits quotes - https://bugs.webkit.org/show_bug.cgi?id=28869
+]);
+
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-color-invalid.html b/css/css-backgrounds/parsing/border-color-invalid.html
new file mode 100644
index 0000000..5c63d8b
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-color-invalid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-color with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-color">
+<meta name="assert" content="border-color supports only the grammar '<color>{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-color", "auto");
+
+test_invalid_value("border-color", "black, white");
+
+test_invalid_value("border-color", "black white red green blue");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-color-valid.html b/css/css-backgrounds/parsing/border-color-valid.html
new file mode 100644
index 0000000..be78a06
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-color-valid.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-color with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-color">
+<meta name="assert" content="border-color supports the full grammar '<color>{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Edge serializes as "currentColor".
+test_valid_value("border-color", "currentcolor", ["currentcolor", "currentColor"]);
+test_valid_value("border-color", "currentColor", ["currentcolor", "currentColor"]);
+
+test_valid_value("border-color", "red yellow green blue");
+
+test_valid_value("border-top-color", "red");
+test_valid_value("border-right-color", "yellow");
+test_valid_value("border-bottom-color", "green");
+test_valid_value("border-left-color", "blue");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-invalid.html b/css/css-backgrounds/parsing/border-image-invalid.html
new file mode 100644
index 0000000..762be9a
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-invalid.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image">
+<meta name="assert" content="border-image supports only the grammar ' <‘border-image-source’> || <‘border-image-slice’> [ / <‘border-image-width’> | / <‘border-image-width’>? / <‘border-image-outset’> ]? || <‘border-image-repeat’>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-image", "auto");
+test_invalid_value("border-image", 'none, url("http://www.example.com/")');
+
+test_invalid_value("border-image", "stretch repeat round");
+
+test_invalid_value("border-image", "fill");
+test_invalid_value("border-image", "1 2 3 4 5");
+test_invalid_value("border-image", "1% fill 2%");
+
+test_invalid_value("border-image", "1 / -2px");
+test_invalid_value("border-image", "-1 / 2px");
+
+test_invalid_value("border-image", "1 / 1 2 3 4 5");
+
+test_invalid_value("border-image", "1 2 3 4 5 / / 1px");
+test_invalid_value("border-image", "1 / / auto");
+test_invalid_value("border-image", "1 2% 3 4% / / 1%");
+test_invalid_value("border-image", "1 2% 3 4% fill / / 1 2 3 4 5");
+
+test_invalid_value("border-image", "1 / none / 1px");
+test_invalid_value("border-image", "1 2% 3 4% / 1 2 3 4 5 / 2");
+test_invalid_value("border-image", "1 2 3 4 5 / 1px / 1px");
+test_invalid_value("border-image", "1 / 1px / auto");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-outset-invalid.html b/css/css-backgrounds/parsing/border-image-outset-invalid.html
new file mode 100644
index 0000000..ffe0959
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-outset-invalid.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-outset with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-outset">
+<meta name="assert" content="border-image-outset supports only the grammar '[ <length> | <number> ]{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-image-outset", "auto");
+
+test_invalid_value("border-image-outset", "-1");
+test_invalid_value("border-image-outset", "-2px");
+
+test_invalid_value("border-image-outset", "1%");
+
+test_invalid_value("border-image-outset", "1 2 3 4 5");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-outset-valid.html b/css/css-backgrounds/parsing/border-image-outset-valid.html
new file mode 100644
index 0000000..dc18937
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-outset-valid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-outset with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-outset">
+<meta name="assert" content="border-image-outset supports the full grammar '[ <length> | <number> ]{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-image-outset", "1px");
+test_valid_value("border-image-outset", "2");
+test_valid_value("border-image-outset", "1px 2 3px 4");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-repeat-invalid.html b/css/css-backgrounds/parsing/border-image-repeat-invalid.html
new file mode 100644
index 0000000..b17b531
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-repeat-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-repeat with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-repeat">
+<meta name="assert" content="border-image-repeat supports only the grammar '[ stretch | repeat | round | space ]{1,2}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-image-repeat", "auto");
+
+test_invalid_value("border-image-repeat", "stretch repeat round");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-repeat-valid.html b/css/css-backgrounds/parsing/border-image-repeat-valid.html
new file mode 100644
index 0000000..6e13dc8
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-repeat-valid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-repeat with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-repeat">
+<meta name="assert" content="border-image-repeat supports the full grammar '[ stretch | repeat | round | space ]{1,2}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-image-repeat", "stretch");
+test_valid_value("border-image-repeat", "space space", "space");
+test_valid_value("border-image-repeat", "repeat round");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-slice-invalid.html b/css/css-backgrounds/parsing/border-image-slice-invalid.html
new file mode 100644
index 0000000..7bdb67f
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-slice-invalid.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-slice with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-slice">
+<meta name="assert" content="border-image-slice supports only the grammar '[<number> | <percentage>]{1,4} && fill?'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-image-slice", "fill");
+
+test_invalid_value("border-image-slice", "1 -2% fill");
+test_invalid_value("border-image-slice", "-1 2% fill");
+
+test_invalid_value("border-image-slice", "1 2 3 4 5");
+
+test_invalid_value("border-image-slice", "1% fill 2%");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-slice-valid.html b/css/css-backgrounds/parsing/border-image-slice-valid.html
new file mode 100644
index 0000000..257f276
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-slice-valid.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-slice with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-slice">
+<meta name="assert" content="border-image-slice supports the full grammar '[<number> | <percentage>]{1,4} && fill?'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-image-slice", "1");
+test_valid_value("border-image-slice", "1 2% 3 4%");
+
+test_valid_value("border-image-slice", "1 2% 3 4% fill");
+test_valid_value("border-image-slice", "fill 1 2% 3 4%", "1 2% 3 4% fill");
+
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-source-invalid.html b/css/css-backgrounds/parsing/border-image-source-invalid.html
new file mode 100644
index 0000000..99a06ce
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-source-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-source with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-source">
+<meta name="assert" content="border-image-source supports only the grammar 'none | <image>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-image-source", "auto");
+
+test_invalid_value("border-image-source", 'none, url("http://www.example.com/")');
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-source-valid.html b/css/css-backgrounds/parsing/border-image-source-valid.html
new file mode 100644
index 0000000..e12cb92
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-source-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-source with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-source">
+<meta name="assert" content="border-image-source supports the full grammar 'none | <image>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-image-source", "none");
+
+// Safari removes quotes.
+test_valid_value("border-image-source", 'url("http://www.example.com/")', ['url("http://www.example.com/")', 'url(http://www.example.com/)']);
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-valid.html b/css/css-backgrounds/parsing/border-image-valid.html
new file mode 100644
index 0000000..79bff45
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-valid.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image">
+<meta name="assert" content="border-image supports the full grammar ' <‘border-image-source’> || <‘border-image-slice’> [ / <‘border-image-width’> | / <‘border-image-width’>? / <‘border-image-outset’> ]? || <‘border-image-repeat’>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// WebKit fails all these tests by returning an empty string as the value of border-image.
+
+// "none" in Edge, "none 100% / 1 / 0 stretch" in Firefox and Blink.
+test_valid_value("border-image", "none", ["none", "none 100% / 1 / 0 stretch"]);
+test_valid_value("border-image", "stretch", ["none", "none 100% / 1 / 0 stretch"]);
+test_valid_value("border-image", "none 100% / 1 / 0 stretch", ["none", "none 100% / 1 / 0 stretch"]);
+
+test_valid_value("border-image", 'url("http://www.example.com/") 1 2 3 4 fill', ['url("http://www.example.com/") 1 2 3 4 fill', 'url("http://www.example.com/") 1 2 3 4 fill / 1 / 0 stretch']);
+test_valid_value("border-image", 'url("http://www.example.com/") 1 2 3 4 fill / 1 / 0 stretch', ['url("http://www.example.com/") 1 2 3 4 fill', 'url("http://www.example.com/") 1 2 3 4 fill / 1 / 0 stretch']);
+
+test_valid_value("border-image", 'url("http://www.example.com/")', ['url("http://www.example.com/")', 'url("http://www.example.com/") 100% / 1 / 0 stretch']);
+
+test_valid_value("border-image", "repeat round", ["repeat round", "none repeat round", "none 100% / 1 / 0 repeat round"]);
+test_valid_value("border-image", "none repeat round", ["repeat round", "none repeat round", "none 100% / 1 / 0 repeat round"]);
+
+test_valid_value("border-image", "space", ["space", "none space space", "none 100% / 1 / 0 space"]);
+test_valid_value("border-image", "none space space", ["space", "none space space", "none 100% / 1 / 0 space"]);
+test_valid_value("border-image", "none 100% / 1 / 0 space", ["space", "none space space", "none 100% / 1 / 0 space"]);
+
+test_valid_value("border-image", "1", ["1", "none 1 1 1 1", "none 1 / 1 / 0 stretch"]);
+test_valid_value("border-image", "none 1 1 1 1", ["1", "none 1 1 1 1", "none 1 / 1 / 0 stretch"]);
+test_valid_value("border-image", "none 1 / 1 / 0 stretch", ["1", "none 1 1 1 1", "none 1 / 1 / 0 stretch"]);
+
+test_valid_value("border-image", 'url("http://www.example.com/") 1 2% 3 4%', ['url("http://www.example.com/") 1 2% 3 4%', 'url("http://www.example.com/") 1 2% 3 4% / 1 / 0 stretch']);
+test_valid_value("border-image", 'url("http://www.example.com/") 1 2% 3 4% fill', ['url("http://www.example.com/") 1 2% 3 4% fill', 'url("http://www.example.com/") 1 2% 3 4% fill / 1 / 0 stretch']);
+test_valid_value("border-image", 'url("http://www.example.com/") fill 1 2% 3 4%', ['url("http://www.example.com/") 1 2% 3 4% fill', 'url("http://www.example.com/") 1 2% 3 4% fill / 1 / 0 stretch']);
+
+test_valid_value("border-image", "1 / 1px", ["1 / 1px", "none 1 / 1px / 0 stretch"]);
+test_valid_value("border-image", "1 2% 3 4% / 2%", ["1 2% 3 4% / 2%", "none 1 2% 3 4% / 2% 2% 2% 2%", "none 1 2% 3 4% / 2% / 0 stretch"]);
+test_valid_value("border-image", "1 2% 3 4% fill / 3", ["1 2% 3 4% fill / 3", "none 1 2% 3 4% fill / 3 3 3 3", "none 1 2% 3 4% fill / 3 / 0 stretch"]);
+test_valid_value("border-image", "fill 1 2% 3 4% / auto", ["1 2% 3 4% fill / auto", "none 1 2% 3 4% fill / auto / 0 stretch"]);
+test_valid_value("border-image", "1 / 1px 2% 3 auto", ["1 / 1px 2% 3 auto", "none 1 / 1px 2% 3 auto / 0 stretch"]);
+
+test_valid_value("border-image", "1 / / 1px", ["1 / / 1px", "none 1 / 1 / 1px stretch"]);
+test_valid_value("border-image", "1 2% 3 4% / / 2", ["1 2% 3 4% / / 2", "none 1 2% 3 4% / 1 / 2 stretch"]);
+test_valid_value("border-image", 'url("http://www.example.com/") 1 2% 3 4% fill / / 1px 2 3px 4', ['url("http://www.example.com/") 1 2% 3 4% fill / / 1px 2 3px 4', 'url("http://www.example.com/") 1 2% 3 4% fill / 1 / 1px 2 3px 4 stretch']);
+
+test_valid_value("border-image", "1 / 1px / 1px", ["1 / 1px / 1px", "none 1 / 1px / 1px stretch"]);
+test_valid_value("border-image", "1 2% 3 4% / 2% / 2", ["1 2% 3 4% / 2% / 2", "none 1 2% 3 4% / 2% / 2 stretch"]);
+test_valid_value("border-image", "1 2% 3 4% fill / 3 / 1px 2 3px 4", ["1 2% 3 4% fill / 3 / 1px 2 3px 4", "none 1 2% 3 4% fill / 3 / 1px 2 3px 4 stretch"]);
+test_valid_value("border-image", "1 / auto / 1px", ["1 / auto / 1px", "none 1 / auto / 1px stretch"]);
+test_valid_value("border-image", "1 2% 3 4% / 1px 2% 3 auto / 2", ["1 2% 3 4% / 1px 2% 3 auto / 2", "none 1 2% 3 4% / 1px 2% 3 auto / 2 2 2 2", "none 1 2% 3 4% / 1px 2% 3 auto / 2 stretch"]);
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-width-invalid.html b/css/css-backgrounds/parsing/border-image-width-invalid.html
new file mode 100644
index 0000000..f85e947
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-width-invalid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-width with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-width">
+<meta name="assert" content="border-image-width supports only the grammar '[ <length-percentage> | <number> | auto ]{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-image-width", "none");
+
+test_invalid_value("border-image-width", "-1px");
+test_invalid_value("border-image-width", "-2%");
+test_invalid_value("border-image-width", "-3");
+
+test_invalid_value("border-image-width", "1 2 3 4 5");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-image-width-valid.html b/css/css-backgrounds/parsing/border-image-width-valid.html
new file mode 100644
index 0000000..503f237
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-image-width-valid.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-image-width with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-width">
+<meta name="assert" content="border-image-width supports the full grammar '[ <length-percentage> | <number> | auto ]{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-image-width", "1px");
+test_valid_value("border-image-width", "2%");
+test_valid_value("border-image-width", "3");
+test_valid_value("border-image-width", "auto");
+
+test_valid_value("border-image-width", "1px 2% 3 auto");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-invalid.html b/css/css-backgrounds/parsing/border-invalid.html
new file mode 100644
index 0000000..f59f249
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-shorthands">
+<meta name="assert" content="border-width supports only the grammar '<line-width> || <line-style> || <color>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border", "auto");
+test_invalid_value("border", "thin double green 1px");
+test_invalid_value("border", "-2em");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-radius-invalid.html b/css/css-backgrounds/parsing/border-radius-invalid.html
new file mode 100644
index 0000000..a8afbbe
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-radius-invalid.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-radius with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-radius">
+<meta name="assert" content="border-radius supports only the grammar '<length-percentage>{1,4} [ / <length-percentage>{1,4} ]?'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-radius", "none");
+test_invalid_value("border-radius", "1px 2px 3px 4px 5px");
+test_invalid_value("border-radius", "-1px");
+test_invalid_value("border-radius", "1px -2px");
+test_invalid_value("border-radius", "1em /");
+test_invalid_value("border-radius", "1px / 2px / 3px");
+
+test_invalid_value("border-radius", "5em 1px 5em 2% 5em 3px 5em 4%");
+test_invalid_value("border-radius", "1px 5em 2% 5em 3px 5em 4% 5em");
+
+test_invalid_value("border-top-left-radius", "10px 20px 30px");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-radius-valid.html b/css/css-backgrounds/parsing/border-radius-valid.html
new file mode 100644
index 0000000..cd6fa49
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-radius-valid.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-radius with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-radius">
+<meta name="assert" content="border-radius supports the full grammar '<length-percentage>{1,4} [ / <length-percentage>{1,4} ]?'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-radius", "1px");
+test_valid_value("border-radius", "1px 2% 3px 4%");
+test_valid_value("border-radius", "5em / 1px 2% 3px 4%"); // Blink and Webkit fail by serializing as "5em 1px 5em 2% 5em 3px 5em 4%"
+test_valid_value("border-radius", "1px 2% 3px 4% / 5em"); // Blink and Webkit fail by serializing as "1px 5em 2% 5em 3px 5em 4% 5em"
+
+test_valid_value("border-top-left-radius", "10px");
+test_valid_value("border-top-right-radius", "20%");
+test_valid_value("border-bottom-right-radius", "30px 40%");
+test_valid_value("border-bottom-left-radius", "50% 60px");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-style-invalid.html b/css/css-backgrounds/parsing/border-style-invalid.html
new file mode 100644
index 0000000..47e5422
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-style-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-style with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-style">
+<meta name="assert" content="border-style supports only the grammar '<line-style>{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-style", "auto");
+test_invalid_value("border-style", "solid double groove ridge none");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-style-valid.html b/css/css-backgrounds/parsing/border-style-valid.html
new file mode 100644
index 0000000..0779854
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-style-valid.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-style with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-style">
+<meta name="assert" content="border-style supports the full grammar '<line-style>{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-style", "none");
+test_valid_value("border-style", "none hidden dotted dashed");
+test_valid_value("border-style", "solid double groove ridge");
+test_valid_value("border-style", "inset outset");
+
+test_valid_value("border-top-style", "solid");
+test_valid_value("border-right-style", "double");
+test_valid_value("border-bottom-style", "groove");
+test_valid_value("border-left-style", "ridge");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-valid.html b/css/css-backgrounds/parsing/border-valid.html
new file mode 100644
index 0000000..5f29f6b
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-valid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-shorthands">
+<meta name="assert" content="border-width supports the full grammar '<line-width> || <line-style> || <color>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border", "1px dotted red");
+test_valid_value("border", "green double thin", "thin double green");
+
+test_valid_value("border-top", "thin", ["thin", "thin none"]);
+test_valid_value("border-right", "double", ["double", "medium double"]);
+test_valid_value("border-bottom", "green", ["green", "medium none green"]);
+test_valid_value("border-left", "1px dotted red");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-width-invalid.html b/css/css-backgrounds/parsing/border-width-invalid.html
new file mode 100644
index 0000000..ff90837
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-width-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-width with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-width">
+<meta name="assert" content="border-width supports only the grammar '<line-width>{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("border-width", "none");
+test_invalid_value("border-width", "thin medium thick medium thin");
+test_invalid_value("border-width", "-1px");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/border-width-valid.html b/css/css-backgrounds/parsing/border-width-valid.html
new file mode 100644
index 0000000..b98a208
--- /dev/null
+++ b/css/css-backgrounds/parsing/border-width-valid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing border-width with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-width">
+<meta name="assert" content="border-width supports the full grammar '<line-width>{1,4}'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("border-width", "1em");
+test_valid_value("border-width", "2px thin medium thick");
+
+test_valid_value("border-top-width", "0px");
+test_valid_value("border-right-width", "thin");
+test_valid_value("border-bottom-width", "medium");
+test_valid_value("border-left-width", "thick");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/box-shadow-invalid.html b/css/css-backgrounds/parsing/box-shadow-invalid.html
new file mode 100644
index 0000000..fa00737
--- /dev/null
+++ b/css/css-backgrounds/parsing/box-shadow-invalid.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing box-shadow with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#box-shadow">
+<meta name="assert" content="box-shadow supports only the grammar 'none | <shadow>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("box-shadow", "auto");
+test_invalid_value("box-shadow", "1 2");
+test_invalid_value("box-shadow", "1% 2%");
+test_invalid_value("box-shadow", "1px calc(2px + 2%)");
+
+test_invalid_value("box-shadow", "red inset");
+test_invalid_value("box-shadow", "1px");
+
+test_invalid_value("box-shadow", "1px 2px 3px 4px 5px");
+test_invalid_value("box-shadow", "red 1px 2px blue");
+
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/box-shadow-valid.html b/css/css-backgrounds/parsing/box-shadow-valid.html
new file mode 100644
index 0000000..4ea289b
--- /dev/null
+++ b/css/css-backgrounds/parsing/box-shadow-valid.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Backgrounds and Borders Module Level 3: parsing box-shadow with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#box-shadow">
+<meta name="assert" content="box-shadow supports the full grammar 'none | <shadow>#'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("box-shadow", "none");
+test_valid_value("box-shadow", "1px 2px");
+test_valid_value("box-shadow", "red 1px 2px 3px -4px inset"); // Edge serializes as "inset 1px 2px 3px -4px red"
+test_valid_value("box-shadow", "inset 1px 2px red", "red 1px 2px inset");
+test_valid_value("box-shadow", "1px -2px inset, red -3px 4px"); // Edge serializes as "inset 1px -2px, -3px 4px red"
+test_valid_value("box-shadow", "inset 1px -2px, -3px 4px red", "1px -2px inset, red -3px 4px");
+</script>
+</body>
+</html>
diff --git a/css/css-backgrounds/parsing/resources/parsing-testcommon.js b/css/css-backgrounds/parsing/resources/parsing-testcommon.js
new file mode 100644
index 0000000..b075882
--- /dev/null
+++ b/css/css-backgrounds/parsing/resources/parsing-testcommon.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
+function test_valid_value(property, value, serializedValue) {
+    if (arguments.length < 3)
+        serializedValue = value;
+
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
+
+        var div = document.createElement('div');
+        div.style[property] = value;
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
+        div.style[property] = readValue;
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+}
+
+function test_invalid_value(property, value) {
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_equals(div.style.getPropertyValue(property), "");
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
+}
diff --git a/css/css-backgrounds/reference/background-clip-content-box-ref.html b/css/css-backgrounds/reference/background-clip-content-box-ref.html
index 133e57a..dc6d9a0 100644
--- a/css/css-backgrounds/reference/background-clip-content-box-ref.html
+++ b/css/css-backgrounds/reference/background-clip-content-box-ref.html
@@ -1,22 +1,15 @@
 <!DOCTYPE html>
-<html>
-<head>
-    <title>CSS Backgrounds and Borders Test: background-clip Reference</title>
-    <link rel="author" title="James Wang" href="mailto:wangjian@ucweb.com">
-    <style type="text/css">
-    	* { padding: 0; margin: 0; }
-    	#test-color-box {
-            position: absolute;
-    		width: 180px;
-    		height: 180px;
-    		background-color: rgba(0, 255, 0, 1);
-            border: 10px solid blue;
-    	}
-    </style>
-</head>
-<body>
-    <p>Test pass if the green box has a 10px width blue edge</p>
-    <!-- background -->
-    <div id="test-color-box"></div>
-</body>
-</html>
+<title>CSS Backgrounds and Borders Test: background-clip Reference</title>
+<link rel="author" title="James Wang" href="mailto:wangjian@ucweb.com">
+<style>
+#test-color-box {
+  position: absolute;
+  width: 180px;
+  height: 180px;
+  background-color: rgba(255, 165, 0, 1);
+  border: 10px solid blue;
+}
+</style>
+<p>Test passes if the orange box has a 10px width blue edge.</p>
+<!-- background -->
+<div id="test-color-box"></div>
diff --git a/css/css-backgrounds/reference/background-color-clip.html b/css/css-backgrounds/reference/background-color-clip.html
new file mode 100644
index 0000000..18e80a9
--- /dev/null
+++ b/css/css-backgrounds/reference/background-color-clip.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Green Rectangle</title>
+<style>
+    div {
+        width: 120px;
+        height: 100px;
+        background-color: green;
+        background-clip: content-box;
+        border-style: solid;
+        border-width: 10px;
+        border-color: transparent;
+    }
+</style>
+<div></div>
diff --git a/css/css-backgrounds/reference/background-rounded-image-clip.html b/css/css-backgrounds/reference/background-rounded-image-clip.html
new file mode 100644
index 0000000..2436bed
--- /dev/null
+++ b/css/css-backgrounds/reference/background-rounded-image-clip.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Corner Clipped Background Color</title>
+<style>
+    html {
+        background-color: green;
+    }
+    #a {
+        top: 20px;
+        left: 20px;
+        position: absolute;
+        width: 20px;
+        height: 20px;
+        background-color: black;
+    }
+</style>
+<div id="a"></div>
diff --git a/css/css-backgrounds/reference/box-shadow-syntax-001.xht b/css/css-backgrounds/reference/box-shadow-syntax-001.xht
deleted file mode 100644
index 697c21a..0000000
--- a/css/css-backgrounds/reference/box-shadow-syntax-001.xht
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Reference</title>
-  <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"/>
-  <style type="text/css">
-
-    .container {
-      margin: 4px;
-      border: solid green 4px;
-      padding-top: 1px;
-      border-style: none none solid solid;
-    }
-  </style>
- </head>
- <body>
-  <p>There must be 31 green L-shapes below and no red.</p>
-
-  <div id="c1" class="container"><div></div></div>
-  <div id="c2" class="container"><div></div></div>
-  <div id="c3" class="container"><div></div></div>
-  <div id="c4" class="container"><div></div></div>
-  <div id="c5" class="container"><div></div></div>
-  <div id="c6" class="container"><div></div></div>
-  <div id="i1" class="container"><div></div></div>
-  <div id="i2" class="container"><div></div></div>
-  <div id="i3" class="container"><div></div></div>
-  <div id="i4" class="container"><div></div></div>
-  <div id="i5" class="container"><div></div></div>
-  <div id="i6" class="container"><div></div></div>
-  <div id="i7" class="container"><div></div></div>
-  <div id="ci1" class="container"><div></div></div>
-  <div id="ci2" class="container"><div></div></div>
-  <div id="ci3" class="container"><div></div></div>
-  <div id="ci4" class="container"><div></div></div>
-  <div id="ci5" class="container"><div></div></div>
-  <div id="ci6" class="container"><div></div></div>
-  <div id="ci11" class="container"><div></div></div>
-  <div id="ci12" class="container"><div></div></div>
-  <div id="ci13" class="container"><div></div></div>
-  <div id="ci14" class="container"><div></div></div>
-  <div id="ci15" class="container"><div></div></div>
-  <div id="ci16" class="container"><div></div></div>
-  <div id="ci21" class="container"><div></div></div>
-  <div id="ci22" class="container"><div></div></div>
-  <div id="ci23" class="container"><div></div></div>
-  <div id="ci24" class="container"><div></div></div>
-  <div id="ci25" class="container"><div></div></div>
-  <div id="ci26" class="container"><div></div></div>
- </body>
-</html>
diff --git a/css/css-cascade/important-prop-ref.html b/css/css-cascade/important-prop-ref.html
new file mode 100644
index 0000000..004679d
--- /dev/null
+++ b/css/css-cascade/important-prop-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade Green Right Square Reference File</title>
+<link rel="author" title="David Burns" href="http://www.theautomatedtester.co.uk">
+<style>
+#success {
+  width: 100px;
+  height: 100px;
+  background-color: green;
+}
+</style>
+<body>
+  <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+  <div>
+    <div id="success"></div>
+  </div>
+</body>
+</html>
diff --git a/css/css-cascade/important-prop.html b/css/css-cascade/important-prop.html
new file mode 100644
index 0000000..5a3dad1
--- /dev/null
+++ b/css/css-cascade/important-prop.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Cascade: Important</title>
+  <link rel="author" title="David Burns" href="http://www.theautomatedtester.co.uk">
+  <link rel="help" href="https://drafts.csswg.org/css-cascade/#importance">
+  <link rel="match" href="important-prop-ref.html">
+  <meta name="flags" content="">
+  <style>
+  @keyframes override{
+   from, to{background-color: #f00;}
+  }
+
+  .square {
+    color:#00f;
+    animation: override 1s infinite;
+    width: 100px;
+    height: 100px;
+  }
+  .green {
+    background-color:green !important;
+  }
+  </style>
+</head>
+<body>
+  <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+  <div class="square green"></div>
+
+</body>
+</html>
diff --git a/css/css-color/color-function-parsing.html b/css/css-color/color-function-parsing.html
new file mode 100644
index 0000000..7f483bb
--- /dev/null
+++ b/css/css-color/color-function-parsing.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: color() parsing</title>
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#color-function">
+<meta name="assert" content="Tests basic parsing of the color function">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="test"></div>
+<script>
+    const div = document.querySelector("#test");
+    function testColorFunction(description, rule, expectedValue) {
+        test(function() {
+            div.style.color = "black";
+            div.style.color = rule;
+            assert_equals(getComputedStyle(div).color, expectedValue);
+        }, description);
+    }
+
+    testColorFunction("Basic sRGB white", "color(srgb 1 1 1)", "color(srgb 1 1 1)");
+    testColorFunction("White with lots of space", "color(    srgb         1      1 1       )", "color(srgb 1 1 1)");
+    testColorFunction("sRGB color", "color(srgb 0.25 0.5 0.75)", "color(srgb 0.25 0.5 0.75)");
+    testColorFunction("Different case for sRGB", "color(SrGb 0.25 0.5 0.75)", "color(srgb 0.25 0.5 0.75)");
+    testColorFunction("sRGB color with unnecessary decimals", "color(srgb 1.00000 0.500000 0.20)", "color(srgb 1 0.5 0.2)");
+    testColorFunction("sRGB white with 0.5 alpha", "color(srgb 1 1 1 / 0.5)", "color(srgb 1 1 1 / 0.5)");
+    testColorFunction("sRGB white with 0 alpha", "color(srgb 1 1 1 / 0)", "color(srgb 1 1 1 / 0)");
+    testColorFunction("sRGB white with 50% alpha", "color(srgb 1 1 1 / 50%)", "color(srgb 1 1 1 / 0.5)");
+    testColorFunction("sRGB white with 0% alpha", "color(srgb 1 1 1 / 0%)", "color(srgb 1 1 1 / 0)");
+    testColorFunction("One missing component is 0", "color(srgb 1 1)", "color(srgb 1 1 0)");
+    testColorFunction("Two missing components are 0", "color(srgb 1)", "color(srgb 1 0 0)");
+    testColorFunction("All components missing", "color(srgb)", "color(srgb 0 0 0)");
+    testColorFunction("Display P3 color", "color(display-p3 0.6 0.7 0.8)", "color(display-p3 0.6 0.7 0.8)");
+    testColorFunction("Different case for Display P3", "color(dIspLaY-P3 0.6 0.7 0.8)", "color(display-p3 0.6 0.7 0.8)");
+
+    testColorFunction("Unknown color space should fallback", "color(unknown 1 2 3, red)", "color(unknown 1 2 3, red)");
+
+    testColorFunction("sRGB color with negative component should clamp to 0", "color(srgb -0.25 0.5 0.75)", "color(srgb 0 0.5 0.75)");
+    testColorFunction("sRGB color with component > 1 should clamp", "color(srgb 0.25 1.5 0.75)", "color(srgb 0.25 1 0.75)");
+    testColorFunction("Display P3 color with negative component should clamp to 0", "color(display-p3 0.5 -199 0.75)", "color(display-p3 0.5 0 0.75)");
+    testColorFunction("Display P3 color with component > 1 should clamp", "color(display-p3 184 1.00001 2347329746587)", "color(display-p3 1 1 1)");
+    testColorFunction("Alpha > 1 should clamp", "color(srgb 0.1 0.2 0.3 / 1.9)", "color(srgb 0.1 0.2 0.3)");
+    testColorFunction("Negative alpha should clamp", "color(srgb 1 1 1 / -0.2)", "color(srgb 1 1 1 / 0)");
+
+    // Invalid properties
+    testColorFunction("Empty", "color()", "rgb(0, 0, 0)");
+    testColorFunction("Bad color space", "color(banana 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Bad Display P3 color space", "color(displayp3 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("No color space", "color(1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Too many parameters", "color(srgb 1 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Way too many parameters", "color(srgb 1 1 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Bad parameters", "color(srgb 1 eggs 1)", "rgb(0, 0, 0)");
+    testColorFunction("Bad alpha", "color(srgb 1 1 1 / bacon)", "rgb(0, 0, 0)");
+    testColorFunction("Junk after alpha", "color(srgb 1 1 1 / 1 cucumber)", "rgb(0, 0, 0)");
+</script>
diff --git a/css/css-color/color-resolving-hsl.html b/css/css-color/color-resolving-hsl.html
new file mode 100644
index 0000000..d337017
--- /dev/null
+++ b/css/css-color/color-resolving-hsl.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: Resolving HSL color values</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#resolving-color-values">
+<meta name="assert" content="Tests if HSL color values are resolved properly">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="parent" style="color: rgb(45, 23, 27)">
+    <div id="inner"></div>
+</div>
+
+<script>
+    function color_test(color, expected, reason) {
+        test(function() {
+            var element = document.getElementById('inner');
+            // Random value not in our test data.
+            fail_value = "rgb(12, 34, 223)"
+            element.style.color = "black";
+            element.style.cssText = "color: " + fail_value + "; color: " + color;
+
+            if (expected === null)
+                assert_equals(getComputedStyle(element).color, fail_value);
+            else
+                assert_equals(getComputedStyle(element).color, expected);
+        }, `${reason}: ${color}`);
+    }
+
+    function expected_value(rgb_channels) {
+        if (rgb_channels === null)
+            return null;
+        else if (rgb_channels.length === 3 || rgb_channels[3] == 1 || rgb_channels[3] === undefined)
+            return "rgb(" + rgb_channels.slice(0, 3).join(", ") + ")";
+        else
+            return "rgba(" + rgb_channels.join(", ") + ")";
+    }
+
+    // Taken mostly from https://drafts.csswg.org/css-color/#hsl-to-rgb
+    function hslToRgb(hue, sat, light) {
+      if (light <= .5) {
+        var t2 = light * (sat + 1);
+      } else {
+        var t2 = light + sat - (light * sat);
+      }
+      var t1 = light * 2 - t2;
+      var r = Math.min(Math.max(Math.round(hueToRgb(t1, t2, hue + 2) * 255), 0), 255);
+      var g = Math.min(Math.max(Math.round(hueToRgb(t1, t2, hue) * 255), 0), 255);
+      var b = Math.min(Math.max(Math.round(hueToRgb(t1, t2, hue - 2) * 255), 0), 255);
+      return [r,g,b];
+    }
+
+    function hueToRgb(t1, t2, hue) {
+      if (hue < 0) hue += 6;
+      if (hue >= 6) hue -= 6;
+
+      if (hue < 1) return (t2 - t1) * hue + t1;
+      else if (hue < 3) return t2;
+      else if (hue < 4) return (t2 - t1) * (4 - hue) + t1;
+      else return t1;
+    }
+
+    // Test HSL parsing
+    for (var hue of [0, 30, 60, 90, 120, 180, 210, 240, 270, 300, 330, 360]) {
+        for (var sat of [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]) {
+            for (var light of [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]) {
+                rgb_channels = hslToRgb(hue / 60, sat, light);
+                for (var alpha of [undefined, 0, 0.2, 1]) {
+                    hsl_color = "hsl(" + hue + ", " + sat * 100 + "%, " + light * 100 + "%)";
+                    rgb_channels[3] = alpha;
+                    if (alpha !== undefined) {
+                        hsl_color = "hsla(" + hue + ", " + sat * 100 + "%, " + light * 100 + "%, " + alpha + ")";
+                    }
+                    color_test(hsl_color, expected_value(rgb_channels), "HSL/HSLA value should parse and round correctly");
+                }
+            }
+        }
+    }
+</script>
diff --git a/css/css-color/color-resolving-keywords.html b/css/css-color/color-resolving-keywords.html
new file mode 100644
index 0000000..5cbdcbe
--- /dev/null
+++ b/css/css-color/color-resolving-keywords.html
@@ -0,0 +1,205 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: Resolving keyword color values</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#resolving-color-values">
+<meta name="assert" content="Tests if keyword color values are resolved properly">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="parent" style="color: rgb(45, 23, 27)">
+    <div id="inner"></div>
+</div>
+
+<script>
+    function color_test(color, expected, reason) {
+        test(function() {
+            var element = document.getElementById('inner');
+            // Random value not in our test data.
+            fail_value = "rgb(12, 34, 223)"
+            element.style.color = "black";
+            element.style.cssText = "color: " + fail_value + "; color: " + color;
+
+            if (expected === null)
+                assert_equals(getComputedStyle(element).color, fail_value);
+            else
+                assert_equals(getComputedStyle(element).color, expected);
+        }, `${reason}: ${color}`);
+    }
+
+    function expected_value(rgb_channels) {
+        if (rgb_channels === null)
+            return null;
+        else if (rgb_channels.length === 3 || rgb_channels[3] == 1)
+            return "rgb(" + rgb_channels.slice(0, 3).join(", ") + ")";
+        else
+            return "rgba(" + rgb_channels.join(", ") + ")";
+    }
+
+    keywords = [
+        ['transparent', [0, 0, 0, 0]],
+        ['aliceblue', [240, 248, 255, 1]],
+        ['antiquewhite', [250, 235, 215, 1]],
+        ['aqua', [0, 255, 255, 1]],
+        ['aquamarine', [127, 255, 212, 1]],
+        ['azure', [240, 255, 255, 1]],
+        ['beige', [245, 245, 220, 1]],
+        ['bisque', [255, 228, 196, 1]],
+        ['black', [0, 0, 0, 1]],
+        ['blanchedalmond', [255, 235, 205, 1]],
+        ['blue', [0, 0, 255, 1]],
+        ['blueviolet', [138, 43, 226, 1]],
+        ['brown', [165, 42, 42, 1]],
+        ['burlywood', [222, 184, 135, 1]],
+        ['cadetblue', [95, 158, 160, 1]],
+        ['chartreuse', [127, 255, 0, 1]],
+        ['chocolate', [210, 105, 30, 1]],
+        ['coral', [255, 127, 80, 1]],
+        ['cornflowerblue', [100, 149, 237, 1]],
+        ['cornsilk', [255, 248, 220, 1]],
+        ['crimson', [220, 20, 60, 1]],
+        ['cyan', [0, 255, 255, 1]],
+        ['darkblue', [0, 0, 139, 1]],
+        ['darkcyan', [0, 139, 139, 1]],
+        ['darkgoldenrod', [184, 134, 11, 1]],
+        ['darkgray', [169, 169, 169, 1]],
+        ['darkgreen', [0, 100, 0, 1]],
+        ['darkgrey', [169, 169, 169, 1]],
+        ['darkkhaki', [189, 183, 107, 1]],
+        ['darkmagenta', [139, 0, 139, 1]],
+        ['darkolivegreen', [85, 107, 47, 1]],
+        ['darkorange', [255, 140, 0, 1]],
+        ['darkorchid', [153, 50, 204, 1]],
+        ['darkred', [139, 0, 0, 1]],
+        ['darksalmon', [233, 150, 122, 1]],
+        ['darkseagreen', [143, 188, 143, 1]],
+        ['darkslateblue', [72, 61, 139, 1]],
+        ['darkslategray', [47, 79, 79, 1]],
+        ['darkslategrey', [47, 79, 79, 1]],
+        ['darkturquoise', [0, 206, 209, 1]],
+        ['darkviolet', [148, 0, 211, 1]],
+        ['deeppink', [255, 20, 147, 1]],
+        ['deepskyblue', [0, 191, 255, 1]],
+        ['dimgray', [105, 105, 105, 1]],
+        ['dimgrey', [105, 105, 105, 1]],
+        ['dodgerblue', [30, 144, 255, 1]],
+        ['firebrick', [178, 34, 34, 1]],
+        ['floralwhite', [255, 250, 240, 1]],
+        ['forestgreen', [34, 139, 34, 1]],
+        ['fuchsia', [255, 0, 255, 1]],
+        ['gainsboro', [220, 220, 220, 1]],
+        ['ghostwhite', [248, 248, 255, 1]],
+        ['gold', [255, 215, 0, 1]],
+        ['goldenrod', [218, 165, 32, 1]],
+        ['gray', [128, 128, 128, 1]],
+        ['green', [0, 128, 0, 1]],
+        ['greenyellow', [173, 255, 47, 1]],
+        ['grey', [128, 128, 128, 1]],
+        ['honeydew', [240, 255, 240, 1]],
+        ['hotpink', [255, 105, 180, 1]],
+        ['indianred', [205, 92, 92, 1]],
+        ['indigo', [75, 0, 130, 1]],
+        ['ivory', [255, 255, 240, 1]],
+        ['khaki', [240, 230, 140, 1]],
+        ['lavender', [230, 230, 250, 1]],
+        ['lavenderblush', [255, 240, 245, 1]],
+        ['lawngreen', [124, 252, 0, 1]],
+        ['lemonchiffon', [255, 250, 205, 1]],
+        ['lightblue', [173, 216, 230, 1]],
+        ['lightcoral', [240, 128, 128, 1]],
+        ['lightcyan', [224, 255, 255, 1]],
+        ['lightgoldenrodyellow', [250, 250, 210, 1]],
+        ['lightgray', [211, 211, 211, 1]],
+        ['lightgreen', [144, 238, 144, 1]],
+        ['lightgrey', [211, 211, 211, 1]],
+        ['lightpink', [255, 182, 193, 1]],
+        ['lightsalmon', [255, 160, 122, 1]],
+        ['lightseagreen', [32, 178, 170, 1]],
+        ['lightskyblue', [135, 206, 250, 1]],
+        ['lightslategray', [119, 136, 153, 1]],
+        ['lightslategrey', [119, 136, 153, 1]],
+        ['lightsteelblue', [176, 196, 222, 1]],
+        ['lightyellow', [255, 255, 224, 1]],
+        ['lime', [0, 255, 0, 1]],
+        ['limegreen', [50, 205, 50, 1]],
+        ['linen', [250, 240, 230, 1]],
+        ['magenta', [255, 0, 255, 1]],
+        ['maroon', [128, 0, 0, 1]],
+        ['mediumaquamarine', [102, 205, 170, 1]],
+        ['mediumblue', [0, 0, 205, 1]],
+        ['mediumorchid', [186, 85, 211, 1]],
+        ['mediumpurple', [147, 112, 219, 1]],
+        ['mediumseagreen', [60, 179, 113, 1]],
+        ['mediumslateblue', [123, 104, 238, 1]],
+        ['mediumspringgreen', [0, 250, 154, 1]],
+        ['mediumturquoise', [72, 209, 204, 1]],
+        ['mediumvioletred', [199, 21, 133, 1]],
+        ['midnightblue', [25, 25, 112, 1]],
+        ['mintcream', [245, 255, 250, 1]],
+        ['mistyrose', [255, 228, 225, 1]],
+        ['moccasin', [255, 228, 181, 1]],
+        ['navajowhite', [255, 222, 173, 1]],
+        ['navy', [0, 0, 128, 1]],
+        ['oldlace', [253, 245, 230, 1]],
+        ['olive', [128, 128, 0, 1]],
+        ['olivedrab', [107, 142, 35, 1]],
+        ['orange', [255, 165, 0, 1]],
+        ['orangered', [255, 69, 0, 1]],
+        ['orchid', [218, 112, 214, 1]],
+        ['palegoldenrod', [238, 232, 170, 1]],
+        ['palegreen', [152, 251, 152, 1]],
+        ['paleturquoise', [175, 238, 238, 1]],
+        ['palevioletred', [219, 112, 147, 1]],
+        ['papayawhip', [255, 239, 213, 1]],
+        ['peachpuff', [255, 218, 185, 1]],
+        ['peru', [205, 133, 63, 1]],
+        ['pink', [255, 192, 203, 1]],
+        ['plum', [221, 160, 221, 1]],
+        ['powderblue', [176, 224, 230, 1]],
+        ['purple', [128, 0, 128, 1]],
+        ['red', [255, 0, 0, 1]],
+        ['rosybrown', [188, 143, 143, 1]],
+        ['royalblue', [65, 105, 225, 1]],
+        ['saddlebrown', [139, 69, 19, 1]],
+        ['salmon', [250, 128, 114, 1]],
+        ['sandybrown', [244, 164, 96, 1]],
+        ['seagreen', [46, 139, 87, 1]],
+        ['seashell', [255, 245, 238, 1]],
+        ['sienna', [160, 82, 45, 1]],
+        ['silver', [192, 192, 192, 1]],
+        ['skyblue', [135, 206, 235, 1]],
+        ['slateblue', [106, 90, 205, 1]],
+        ['slategray', [112, 128, 144, 1]],
+        ['slategrey', [112, 128, 144, 1]],
+        ['snow', [255, 250, 250, 1]],
+        ['springgreen', [0, 255, 127, 1]],
+        ['steelblue', [70, 130, 180, 1]],
+        ['tan', [210, 180, 140, 1]],
+        ['teal', [0, 128, 128, 1]],
+        ['thistle', [216, 191, 216, 1]],
+        ['tomato', [255, 99, 71, 1]],
+        ['turquoise', [64, 224, 208, 1]],
+        ['violet', [238, 130, 238, 1]],
+        ['wheat', [245, 222, 179, 1]],
+        ['white', [255, 255, 255, 1]],
+        ['whitesmoke', [245, 245, 245, 1]],
+        ['yellow', [255, 255, 0, 1]],
+        ['yellowgreen', [154, 205, 50, 1]],
+    ]
+
+    for (var value in keywords) {
+        items_to_test = keywords[value];
+        keyword = items_to_test[0];
+        expected = expected_value(items_to_test[1]);
+        letter = value % keyword.length;
+        replacement = keyword;
+
+        color_test(keyword, expected, "Keyword should parse properly");
+        color_test(keyword.toUpperCase(), expected, "Keywords should be case-insensitive");
+        replacement[letter] = "\\" + keyword.codePointAt(letter);
+        color_test(replacement, expected, "Code point should parse");
+        color_test(keyword.slice(0, letter) + keyword.slice(letter + 1, keyword.length), null, "Partial keywords shouldn't parse");
+        if (keyword.indexOf('k') !== -1)
+            color_test(keyword.replace('k', 'K'), null, "Unicode modification shouldn't parse");
+    }
+</script>
diff --git a/css/css-color/color-resolving.html b/css/css-color/color-resolving.html
new file mode 100644
index 0000000..b3d1c74
--- /dev/null
+++ b/css/css-color/color-resolving.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: Resolving color values</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#resolving-color-values">
+<meta name="assert" content="Tests if color values are resolved properly">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="parent" style="color: rgb(45, 23, 27)">
+    <div id="inner"></div>
+</div>
+
+<script>
+    function color_test(color, expected, reason) {
+        test(function() {
+            var element = document.getElementById('inner');
+            // Random value not in our test data.
+            fail_value = "rgb(12, 34, 223)"
+            element.style.color = "black";
+            element.style.cssText = "color: " + fail_value + "; color: " + color;
+
+            if (expected === null)
+                assert_equals(getComputedStyle(element).color, fail_value);
+            else
+                assert_equals(getComputedStyle(element).color, expected);
+        }, `${reason}: ${color}`);
+    }
+
+    function expected_value(rgb_channels) {
+        if (rgb_channels === null)
+            return null;
+        else if (rgb_channels.length === 3 || rgb_channels[3] == 1)
+            return "rgb(" + rgb_channels.slice(0, 3).join(", ") + ")";
+        else
+            return "rgba(" + rgb_channels.join(", ") + ")";
+    }
+
+    tests = [
+        // Keyword tests
+        ["", null, "Should not parse invalid keyword"],
+        [" /* hey */\n", null, "Should not parse invalid keyword"],
+        ["4", null, "Should not parse invalid keyword"],
+        ["top", null, "Should not parse invalid keyword"],
+        ["/**/transparent", [0, 0, 0, 0], "Should parse to completely transparent"],
+        ["transparent", [0, 0, 0, 0], "Should parse to completely transparent"],
+        [" transparent\n", [0, 0, 0, 0], "Should parse to completely transparent"],
+        ["TransParent", [0, 0, 0, 0], "Should parse to completely transparent"],
+        ["currentColor", [45, 23, 27], "Should be same as parent color"],
+        ["CURRENTcolor", [45, 23, 27], "Should be same as parent color"],
+        ["current-Color", null, "Should not parse invalid keyword"],
+        ["black", [0, 0, 0, 1], "Should parse as correct value"],
+        ["white", [255, 255, 255, 1], "Should parse as correct value"],
+        ["fuchsia", [255, 0, 255, 1], "Should parse as correct value"],
+        ["cyan", [0, 255, 255, 1], "Should parse as correct value"],
+        ["CyAn", [0, 255, 255, 1], "Should parse as cyan"],
+
+        // Hex tests
+        ["#", null, "Should not parse invalid hex"],
+        ["#f", null, "Should not parse invalid hex"],
+        ["#ff", null, "Should not parse invalid hex"],
+        ["#fff", [255, 255, 255, 1], "Valid 3-digit hex"],
+        ["#ffg", null, "Should not parse invalid hex"],
+        ["#ffff", [255, 255, 255, 1], "Valid 4-digit hex"],
+        ["#fffg", null, "Should not parse invalid hex"],
+        ["#fffff", null, "Should not parse invalid hex"],
+        ["#ffffff", [255, 255, 255, 1], "Valid 6-digit hex"],
+        ["#fffffg", null, "Should not parse invalid hex"],
+        ["#fffffff", null, "Should not parse invalid hex"],
+        ["#ffffffff", [255, 255, 255, 1], "Valid 8-digit hex"],
+        ["#fffffffg", null, "Should not parse invalid hex"],
+        ["#fffffffff", null, "Should not parse invalid hex"],
+        ["#FFCc99", [255, 204, 153, 1], "Valid 6-digit hex"],
+        ["#369", [51, 102, 153, 1], "Valid 3-digit hex"],
+
+        // RGB tests
+        ["rgb(00, 51, 102)", [0, 51, 102, 1], "Valid numbers should be parsed"],
+        ["r\\gb(00, 51, 102)", [0, 51, 102, 1], "Correct escape sequences should still parse"],
+        ["r\\67 b(00, 51, 102)", [0, 51, 102, 1], "Correct escape sequences should still parse"],
+        ["RGB(153, 204, 255)", [153, 204, 255, 1], "Capitalization should not affect parsing"],
+        ["rgB(0, 0, 0)", [0, 0, 0, 1], "Capitalization should not affect parsing"],
+        ["rgB(0, 51, 255)", [0, 51, 255, 1], "Capitalization should not affect parsing"],
+        ["rgb(0,51,255)", [0, 51, 255, 1], "Lack of whitespace should not affect parsing"],
+        ["rgb(0\t,  51 ,255)", [0, 51, 255, 1], "Whitespace should not affect parsing"],
+        ["rgb(/* R */0, /* G */51, /* B */255)", [0, 51, 255, 1], "Comments should be allowed within function"],
+        ["rgb(-51, 306, 0)", [0, 255, 0, 1], "Invalid values should be clamped to 0 and 255 respectively"],
+        ["rgb(42%, 3%, 50%)", [107, 8, 128, 1], "Valid percentages should be parsed"],
+        ["RGB(100%, 100%, 100%)", [255, 255, 255, 1], "Capitalization should not affect parsing"],
+        ["rgB(0%, 0%, 0%)", [0, 0, 0, 1], "Capitalization should not affect parsing"],
+        ["rgB(10%, 20%, 30%)", [26, 51, 77, 1], "Capitalization should not affect parsing"],
+        ["rgb(10%,20%,30%)", [26, 51, 77, 1], "Whitespace should not affect parsing"],
+        ["rgb(10%\t,  20% ,30%)", [26, 51, 77, 1], "Whitespace should not affect parsing"],
+        ["rgb(/* R */ 10%, /* G */ 20%, /* B */ 30%)", [26, 51, 77, 1], "Comments should not affect parsing"],
+        ["rgb(-12%, 110%, 1400%)", [0, 255, 255, 1], "Invalid values should be clamped to 0 and 255 respectively"],
+        ["rgb(10%, 50%, 0)", null, "Values must be all numbers or all percentages"],
+        ["rgb(255, 50%, 0%)", null, "Values must be all numbers or all percentages"],
+        ["rgb(0, 0 0)", null, "Comma optional syntax requires no commas at all"],
+        ["rgb(0, 0, 0deg)", null, "Angles are not accepted in the rgb function"],
+        ["rgb(0, 0, light)", null, "Keywords are not accepted in the rgb function"],
+        ["rgb()", null, "The rgb function requires 3 or 4 arguments"],
+        ["rgb(0)", null, "The rgb function requires 3 or 4 arguments"],
+        ["rgb(0, 0)", null, "The rgb function requires 3 or 4 arguments"],
+        ["rgb(0%)", null, "The rgb function requires 3 or 4 arguments"],
+        ["rgb(0%, 0%)", null, "The rgb function requires 3 or 4 arguments"],
+        ["rgb(0, 0, 0, 0)", [0, 0, 0, 0], "RGB and RGBA are synonyms"],
+        ["rgb(0%, 0%, 0%, 0%)", [0, 0, 0, 0], "RGB and RGBA are synonyms"],
+        ["rgb(0%, 0%, 0%, 0)", [0, 0, 0, 0], "RGB and RGBA are synonyms"],
+        ["rgba(0, 0, 0, 0)", [0, 0, 0, 0], "Valid numbers should be parsed"],
+        ["rgba(204, 0, 102, 0.3)", [204, 0, 102, 0.3], "Valid numbers should be parsed"],
+        ["RGBA(255, 255, 255, 0)", [255, 255, 255, 0], "Capitalization should not affect parsing"],
+        ["rgBA(0, 51, 255, 1)", [0, 51, 255, 1], "Capitalization should not affect parsing"],
+        ["rgba(0, 51, 255, 1.1)", [0, 51, 255, 1], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["rgba(0, 51, 255, 37)", [0, 51, 255, 1], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["rgba(0, 51, 255, 0.42)", [0, 51, 255, 0.42], "Valid numbers should be parsed"],
+        ["rgba(0, 51, 255, 0)", [0, 51, 255, 0], "Valid numbers should be parsed"],
+        ["rgba(0, 51, 255, -0.1)", [0, 51, 255, 0], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["rgba(0, 51, 255, -139)", [0, 51, 255, 0], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["RGBA(100%, 100%, 100%, 0)", [255, 255, 255, 0], "Capitalization should not affect parsing"],
+        ["rgba(42%, 3%, 50%, 0.3)", [107, 8, 128, 0.3], "Valid percentages should be parsed"],
+        ["rgBA(0%, 20%, 100%, 1)", [0, 51, 255, 1], "Capitalization should not affect parsing"],
+        ["rgba(0%, 20%, 100%, 1.1)", [0, 51, 255, 1], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["rgba(0%, 20%, 100%, 37)", [0, 51, 255, 1], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["rgba(0%, 20%, 100%, 0.42)", [0, 51, 255, 0.42], "Valid percentages should be parsed"],
+        ["rgba(0%, 20%, 100%, 0)", [0, 51, 255, 0], "Valid percentages should be parsed"],
+        ["rgba(0%, 20%, 100%, -0.1)", [0, 51, 255, 0], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["rgba(0%, 20%, 100%, -139)", [0, 51, 255, 0], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["rgba(255, 255, 255, 0%)", [255, 255, 255, 0], "Percent alpha values are accepted in rgb/rgba"],
+        ["rgba(0%, 0%, 0%, 0%)", [0, 0, 0, 0], "Percent alpha values are accepted in rgb/rgba"],
+        ["rgba(0%, 0%, 0%)", [0, 0, 0, 1], "RGB and RGBA are synonyms"],
+        ["rgba(0, 0, 0)", [0, 0, 0, 1], "RGB and RGBA are synonyms"],
+        ["rgba(10%, 50%, 0, 1)", null, "Values must be all numbers or all percentages"],
+        ["rgba(255, 50%, 0%, 1)", null, "Values must be all numbers or all percentages"],
+        ["rgba(0, 0, 0 0)", null, "Comma optional syntax requires no commas at all"],
+        ["rgba(0, 0, 0, 0deg)", null, "Angles are not accepted in the rgb function"],
+        ["rgba(0, 0, 0, light)", null, "Keywords are not accepted in the rgb function"],
+        ["rgba()", null, "The rgba function requires 3 or 4 arguments"],
+        ["rgba(0)", null, "The rgba function requires 3 or 4 arguments"],
+        ["rgba(0, 0, 0, 0, 0)", null, "The rgba function requires 3 or 4 arguments"],
+        ["rgba(0%)", null, "The rgba function requires 3 or 4 arguments"],
+        ["rgba(0%, 0%)", null, "The rgba function requires 3 or 4 arguments"],
+        ["rgba(0%, 0%, 0%, 0%, 0%)", null, "The rgba function requires 3 or 4 arguments"],
+
+        // HSL tests
+        ["HSL(0, 0%, 0%)", [0, 0, 0, 1], "Capitalization should not affect parsing"],
+        ["hsL(0, 100%, 50%)", [255, 0, 0, 1], "Capitalization should not affect parsing"],
+        ["hsl(60, 100%, 37.5%)", [191, 191, 0, 1], "Valid numbers should be parsed"],
+        ["hsl(780, 100%, 37.5%)", [191, 191, 0, 1], "Angles are represented as a part of a circle and wrap around"],
+        ["hsl(-300, 100%, 37.5%)", [191, 191, 0, 1], "Angles are represented as a part of a circle and wrap around"],
+        ["hsl(300, 50%, 50%)", [191, 64, 191, 1], "Valid numbers should be parsed"],
+        ["hsl(30deg, 100%, 100%)", [255, 255, 255, 1], "Angles are accepted in HSL/HSLA"],
+        ["hsl(0, 0%, 0%, 0%)", [0, 0, 0, 0], "HSL and HSLA are synonyms"],
+        ["hsl(10, 50%, 0)", null, "The second and third parameters of hsl/hsla must be a percent"],
+        ["hsl(50%, 50%, 0%)", null, "The first parameter of hsl/hsla must be a number or angle"],
+        ["hsl(0, 0% 0%)", null, "Comma optional syntax requires no commas at all"],
+        ["hsl(0, 0%, light)", null, "Keywords are not accepted in the hsl function"],
+        ["hsl()", null, "The hsl function requires 3 or 4 arguments"],
+        ["hsl(0)", null, "The hsl function requires 3 or 4 arguments"],
+        ["hsl(0, 0%)", null, "The hsl function requires 3 or 4 arguments"],
+        ["HSLA(-300, 100%, 37.5%, 1)", [191, 191, 0, 1], "Angles are represented as a part of a circle and wrap around"],
+        ["hsLA(-300, 100%, 37.5%, 12)", [191, 191, 0, 1], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["hsla(-300, 100%, 37.5%, 0.2)", [191, 191, 0, 0.2], "Angles are represented as a part of a circle and wrap around"],
+        ["hsla(-300, 100%, 37.5%, 0)", [191, 191, 0, 0], "Angles are represented as a part of a circle and wrap around"],
+        ["hsla(-300, 100%, 37.5%, -3)", [191, 191, 0, 0], "Invalid alpha values should be clamped to 0 and 1 respectively"],
+        ["hsla(0, 0%, 0%, 50%)", [0, 0, 0, 0.5], "Percent alpha values are accepted in hsl/hsla"],
+        ["hsla(30deg, 100%, 100%, 1)", [255, 255, 255, 1], "Angles are accepted in HSL/HSLA"],
+        ["hsla(10, 50%, 0, 1)", null, "The second and third parameters of hsl/hsla must be a percent"],
+        ["hsla(50%, 50%, 0%, 1)", null, "The first parameter of hsl/hsla must be a number or angle"],
+        ["hsla(0, 0% 0%, 1)", null, "Comma optional syntax requires no commas at all"],
+        ["hsla(0, 0%, light, 1)", null, "Keywords are not accepted in the hsla function"],
+        ["hsla()", null, "The hsla function requires 3 or 4 arguments"],
+        ["hsla(0)", null, "The hsla function requires 3 or 4 arguments"],
+        ["hsla(0, 0%)", null, "The hsla function requires 3 or 4 arguments"],
+        ["hsla(0, 0%, 0%, 1, 0%)", null, "The hsla function requires 3 or 4 arguments"]
+    ]
+
+    for (var value in tests) {
+        items_to_test = tests[value];
+        color_test(items_to_test[0], expected_value(items_to_test[1]), items_to_test[2]);
+    }
+</script>
diff --git a/css/css-color/currentcolor-001.html b/css/css-color/currentcolor-001.html
index 584d628..e08129e 100644
--- a/css/css-color/currentcolor-001.html
+++ b/css/css-color/currentcolor-001.html
@@ -3,7 +3,7 @@
 <title>CSS Color 4: currentcolor</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#currentcolor-color">
-<link rel="match" href="greentext-ref.html">
+<link rel="match" href="greensquare-ref.html">
 <meta name="assert" content="The keyword currentcolor takes its value from the value of the color property on the same element.">
 <style>
     .outer {color: red; background-color: red; font-size: 200%; width: 6em; height: 6em; }
diff --git a/css/css-color/currentcolor-002.html b/css/css-color/currentcolor-002.html
index ff6d027..aa5d736 100644
--- a/css/css-color/currentcolor-002.html
+++ b/css/css-color/currentcolor-002.html
@@ -3,7 +3,7 @@
 <title>CSS Color 4: currentcolor</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#currentcolor-color">
-<link rel="match" href="greentext-ref.html">
+<link rel="match" href="greensquare-ref.html">
 <meta name="assert" content="This happens at used-value time, which means that if the value is inherited, it’s inherited as currentcolor, not as the value of the color property, so descendants will use their own color property to resolve it.">
 <style>
     .outer {color: red; background-color: currentColor; font-size: 200%; width: 6em; height: 6em; }
diff --git a/css/css-color/greensquare-ref.html b/css/css-color/greensquare-ref.html
new file mode 100644
index 0000000..35a31f8
--- /dev/null
+++ b/css/css-color/greensquare-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Green square reference</title>
+<style>
+    .test { background-color: #008000; width: 12em; height: 12em;}
+</style>
+<body>
+    <p>Test passes if you see a green square, and no red.</p>
+    <div class="test"></div>
+</body>
diff --git a/css/css-color/hex-003-ref.html b/css/css-color/hex-003-ref.html
new file mode 100644
index 0000000..613528e
--- /dev/null
+++ b/css/css-color/hex-003-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Green text reference for hex shorthand tests</title>
+<style>
+    .test { color: #007700}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hex-003.html b/css/css-color/hex-003.html
index db710cf..a6a19ad 100644
--- a/css/css-color/hex-003.html
+++ b/css/css-color/hex-003.html
@@ -3,11 +3,11 @@
 <title>CSS Color 4: The RGB hexadecimal notations: #RRGGBB</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#hex-notation">
+<link rel="match" href="hex-003-ref.html">
 <meta name="assert" content="3 digit hex">
 <style>
     .test {color: #070}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
-    <!-- #007700, not #008000, but visually similar. Thus, no reftest -->
 </body>
diff --git a/css/css-color/hex-004.html b/css/css-color/hex-004.html
index f6ea47f..074fb1d 100644
--- a/css/css-color/hex-004.html
+++ b/css/css-color/hex-004.html
@@ -4,10 +4,10 @@
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#hex-notation">
 <meta name="assert" content="4 digit hex, fully opaque">
+<link rel="match" href="hex-003-ref.html">
 <style>
     .test {color: #070F}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
-    <!-- #007700, not #008000, but visually similar. Thus, no reftest -->
 </body>
diff --git a/css/css-color/hsl-001.html b/css/css-color/hsl-001.html
new file mode 100644
index 0000000..842dbb9
--- /dev/null
+++ b/css/css-color/hsl-001.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="hsl() with number and no alpha, also no comma">
+<style>
+    .test {color: hsl(120 100% 25%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsl-002.html b/css/css-color/hsl-002.html
new file mode 100644
index 0000000..91750d4
--- /dev/null
+++ b/css/css-color/hsl-002.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="hsl() with angle and no alpha, also no comma">
+<style>
+    .test {color: hsl(120deg 100% 25%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsl-003.html b/css/css-color/hsl-003.html
new file mode 100644
index 0000000..5b6bb4b
--- /dev/null
+++ b/css/css-color/hsl-003.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="hsl() with number and numeric alpha, also no comma">
+<style>
+    .test {color: hsl(120 100% 25% / 1.0)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsl-004.html b/css/css-color/hsl-004.html
new file mode 100644
index 0000000..483c821
--- /dev/null
+++ b/css/css-color/hsl-004.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="hsl() with angle and numeric alpha, also no comma">
+<style>
+    .test {color: hsl(120deg 100% 25% / 1)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsl-005.html b/css/css-color/hsl-005.html
new file mode 100644
index 0000000..b0970c3
--- /dev/null
+++ b/css/css-color/hsl-005.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="hsl() with number and percent alpha, also no comma">
+<style>
+    .test {color: hsl(120 100% 25% / 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsl-006.html b/css/css-color/hsl-006.html
new file mode 100644
index 0000000..389c5a4
--- /dev/null
+++ b/css/css-color/hsl-006.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="hsl() with angle and percent alpha, also no comma">
+<style>
+    .test {color: hsl(120deg 100% 25% / 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsl-007.html b/css/css-color/hsl-007.html
new file mode 100644
index 0000000..18bbe40
--- /dev/null
+++ b/css/css-color/hsl-007.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsl() with number and percent alpha, and commas">
+<style>
+    .test {color: hsl(120, 100%, 25%, 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsl-008.html b/css/css-color/hsl-008.html
new file mode 100644
index 0000000..89b9621
--- /dev/null
+++ b/css/css-color/hsl-008.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsl() with angle and percent alpha, with commas">
+<style>
+    .test {color: hsl(120deg, 100%, 25%, 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-001.html b/css/css-color/hsla-001.html
new file mode 100644
index 0000000..9e5bbfa
--- /dev/null
+++ b/css/css-color/hsla-001.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with number and no alpha, also no comma">
+<style>
+    .test {color: hsla(120 100% 25%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-002.html b/css/css-color/hsla-002.html
new file mode 100644
index 0000000..8b9cff8
--- /dev/null
+++ b/css/css-color/hsla-002.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with angle and no alpha, also no comma">
+<style>
+    .test {color: hsla(120deg 100% 25%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-003.html b/css/css-color/hsla-003.html
new file mode 100644
index 0000000..3aadd8e
--- /dev/null
+++ b/css/css-color/hsla-003.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with number and numeric alpha, also no comma">
+<style>
+    .test {color: hsla(120 100% 25% / 1.0)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-004.html b/css/css-color/hsla-004.html
new file mode 100644
index 0000000..e68669f
--- /dev/null
+++ b/css/css-color/hsla-004.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with angle and numeric alpha, also no comma">
+<style>
+    .test {color: hsla(120deg 100% 25% / 1)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-005.html b/css/css-color/hsla-005.html
new file mode 100644
index 0000000..4efeb19
--- /dev/null
+++ b/css/css-color/hsla-005.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with number and percent alpha, also no comma">
+<style>
+    .test {color: hsla(120 100% 25% / 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-006.html b/css/css-color/hsla-006.html
new file mode 100644
index 0000000..e440ebe
--- /dev/null
+++ b/css/css-color/hsla-006.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with angle and percent alpha, also no comma">
+<style>
+    .test {color: hsla(120deg 100% 25% / 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-007.html b/css/css-color/hsla-007.html
new file mode 100644
index 0000000..ba647ae
--- /dev/null
+++ b/css/css-color/hsla-007.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with number and percent alpha, and commas">
+<style>
+    .test {color: hsla(120, 100%, 25%, 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/hsla-008.html b/css/css-color/hsla-008.html
new file mode 100644
index 0000000..8f78f88
--- /dev/null
+++ b/css/css-color/hsla-008.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: HSL functions hsl() and hsla()</title>
+<link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hsl-notation">
+<link rel="match" href="greentext-ref.html">
+<meta name="assert" content="legacy hsla() with angle and percent alpha, with commas">
+<style>
+    .test {color: hsla(120deg, 100%, 25%, 100%)}
+</style>
+<body>
+    <p class="test">Test passes if this text is green</p>
+</body>
diff --git a/css/css-color/lab-004-ref.html b/css/css-color/lab-004-ref.html
new file mode 100644
index 0000000..1919ab4
--- /dev/null
+++ b/css/css-color/lab-004-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: Specifying Lab and LCH</title>
+<style>
+    .match { color: rgb(75.62% 30.45% 47.56%)} /* lab(50 50 0) converted to sRGB */
+</style>
+<body>
+    <p>Test passes if the two lines of filler text are the same color.</p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+</body>
diff --git a/css/css-color/lab-004.html b/css/css-color/lab-004.html
index 0037a1e..2cd327f 100644
--- a/css/css-color/lab-004.html
+++ b/css/css-color/lab-004.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-004-ref.html">
 <meta name="assert" content="lab() with no alpha, positive a axis">
 <style>
     .test { color: red; }
     .test { color: lab(50 50 0)}
-    .match { color: rgb(75.62%, 30.45%, 47.56%)} /* lab(50,0,0) converted to sRGB */
+    .match { color: rgb(75.62%, 30.45%, 47.56%)} /* lab(50 50 0) converted to sRGB */
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/lab-005-ref.html b/css/css-color/lab-005-ref.html
new file mode 100644
index 0000000..207973e
--- /dev/null
+++ b/css/css-color/lab-005-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: Specifying Lab and LCH</title>
+<style>
+    .match { color: rgb(10.79%, 75.55%, 66.40%)} /* lab(70 -45 0) converted to sRGB */
+</style>
+<body>
+    <p>Test passes if the two lines of filler text are the same color.</p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+</body>
diff --git a/css/css-color/lab-005.html b/css/css-color/lab-005.html
index 366cd1b..846430f 100644
--- a/css/css-color/lab-005.html
+++ b/css/css-color/lab-005.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-005-ref.html">
 <meta name="assert" content="lab() with no alpha, negative a axis">
 <style>
     .test { color: red; }
     .test { color: lab(70 -45 0)}
-    .match { color: rgb(10.79%, 75.55%, 66.40%)} /* lab(70,-45,0) converted to sRGB */
+    .match { color: rgb(10.79%, 75.55%, 66.40%)} /* lab(70 -45 0) converted to sRGB */
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/lab-006-ref.html b/css/css-color/lab-006-ref.html
new file mode 100644
index 0000000..8747c97
--- /dev/null
+++ b/css/css-color/lab-006-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: Specifying Lab and LCH</title>
+<style>
+    .match { color: rgb(76.62%, 66.36%, 5.58%)} /* lab(70 0 70) converted to sRGB */
+</style>
+<body>
+    <p>Test passes if the two lines of filler text are the same color.</p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+</body>
diff --git a/css/css-color/lab-006.html b/css/css-color/lab-006.html
index 079c379..926c1f0 100644
--- a/css/css-color/lab-006.html
+++ b/css/css-color/lab-006.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-006-ref.html">
 <meta name="assert" content="lab() with no alpha, positive b axis">
 <style>
     .test { color: red; }
     .test { color: lab(70 0 70)}
-    .match { color: rgb(76.62%, 66.36%, 5.58%)} /* lab(70,0,70) converted to sRGB */
+    .match { color: rgb(76.62%, 66.36%, 5.58%)} /* lab(70 0 70) converted to sRGB */
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/lab-007-ref.html b/css/css-color/lab-007-ref.html
new file mode 100644
index 0000000..00bdb09
--- /dev/null
+++ b/css/css-color/lab-007-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: Specifying Lab and LCH</title>
+<style>
+    .match { color: rgb(12.81%, 53.10%, 92.76%)} /* lab(55 0 -60) converted to sRGB */
+</style>
+<body>
+    <p>Test passes if the two lines of filler text are the same color.</p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+    <p class="match">Filler text. Filler text. Filler text. </p>
+</body>
diff --git a/css/css-color/lab-007.html b/css/css-color/lab-007.html
index 4cdb357..917bc1a 100644
--- a/css/css-color/lab-007.html
+++ b/css/css-color/lab-007.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-007-ref.html">
 <meta name="assert" content="lab() with no alpha, negative b axis">
 <style>
     .test { color: red; }
     .test { color: lab(55 0 -60)}
-    .match { color: rgb(12.81%, 53.10%, 92.76%)} /* lab(55,0,-60) converted to sRGB */
+    .match { color: rgb(12.81%, 53.10%, 92.76%)} /* lab(55 0 -60) converted to sRGB */
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/lch-001.html b/css/css-color/lch-001.html
index 966e1fd..3b0b5af 100644
--- a/css/css-color/lch-001.html
+++ b/css/css-color/lch-001.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="lch() with no alpha">
 <style>
-    .test {color: lab(46.277 -67.989 134.391)} /* green (sRGB #008000) converted to LCH */
+    .test {color: lch(46.277 67.945 134.427)} /* green (sRGB #008000) converted to LCH */
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/lch-004.html b/css/css-color/lch-004.html
index 46ac7d3..0db243c 100644
--- a/css/css-color/lch-004.html
+++ b/css/css-color/lch-004.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-004-ref.html">
 <meta name="assert" content="lch() with no alpha, positive a axis">
 <style>
     .test { color: red; }
     .test { color: lch(50 50 0)}
-    .match { color: rgb(75.62%, 30.45%, 47.56%)} /* lch(50,0,0) converted to sRGB */
+    .match { color: rgb(75.62%, 30.45%, 47.56%)} /* lch(50 50 0) converted to sRGB (happens to be the same as lab(50 50 0)*/
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/lch-005.html b/css/css-color/lch-005.html
index 789d4b2..c16d02b 100644
--- a/css/css-color/lch-005.html
+++ b/css/css-color/lch-005.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-005-ref.html">
 <meta name="assert" content="lch() with no alpha, negative a axis">
 <style>
     .test { color: red; }
-    .test { color: lab(70 45 180)}
-    .match { color: rgb(10.79%, 75.55%, 66.40%)} /* lch(70,45,180) converted to sRGB */
+    .test { color: lch(70 45 -180)}
+    .match { color: rgb(10.79%, 75.55%, 66.40%)} /* lch(70 45 180) converted to sRGB */
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/lch-006.html b/css/css-color/lch-006.html
index 760362c..c3bbd14 100644
--- a/css/css-color/lch-006.html
+++ b/css/css-color/lch-006.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-006-ref.html">
 <meta name="assert" content="lch() with no alpha, positive b axis">
 <style>
     .test { color: red; }
-    .test { color: lab(70 70 90)}
-    .match { color: rgb(76.62%, 66.36%, 5.58%)} /* lch(70,70,90) converted to sRGB */
+    .test { color: lch(70 70 90)}
+    .match { color: rgb(76.62%, 66.36%, 5.58%)} /* lch(70 70 90) converted to sRGB */
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/lch-007.html b/css/css-color/lch-007.html
index 7b8c41f..4646bc9 100644
--- a/css/css-color/lch-007.html
+++ b/css/css-color/lch-007.html
@@ -3,12 +3,12 @@
 <title>CSS Color 4: Specifying Lab and LCH</title>
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#specifying-lab-lch">
-<link rel="match" href="blacktext-ref.html">
+<link rel="match" href="lab-007-ref.html">
 <meta name="assert" content="lab() with no alpha, negative b axis">
 <style>
     .test { color: red; }
-    .test { color: lch(55 60 270)}
-    .match { color: rgb(12.81%, 53.10%, 92.76%)} /* lch(55,60,270) converted to sRGB */
+    .test { color: lch(56 58 275)}
+    .match { color: rgb(12.81%, 53.10%, 92.76%)} /* lch(56 58 275) converted to sRGB */
 </style>
 <body>
     <p>Test passes if the two lines of filler text are the same color.</p>
diff --git a/css/css-color/rgb-002.html b/css/css-color/rgb-002.html
index d9ca685..a50620b 100644
--- a/css/css-color/rgb-002.html
+++ b/css/css-color/rgb-002.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="rgb() with 8-bit numbers and no alpha, also no comma">
 <style>
-    .test {color: rgb(0 80.0 0)}
+    .test {color: rgb(0 128.0 0)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/rgb-004.html b/css/css-color/rgb-004.html
index 7fab317..7bace00 100644
--- a/css/css-color/rgb-004.html
+++ b/css/css-color/rgb-004.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="rgb() with 8-bit numbers and numeric alpha, also no comma">
 <style>
-    .test {color: rgb(0 80.0 0 / 1)}
+    .test {color: rgb(0 128.0 0 / 1)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/rgb-006.html b/css/css-color/rgb-006.html
index 9b4d4fc..5ccb8ea 100644
--- a/css/css-color/rgb-006.html
+++ b/css/css-color/rgb-006.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="rgb() with 8-bit numbers and percent alpha, also no comma">
 <style>
-    .test {color: rgb(0 80.0 0 / 100%)}
+    .test {color: rgb(0 128.0 0 / 100%)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/rgb-008.html b/css/css-color/rgb-008.html
index f8114f0..bd3215d 100644
--- a/css/css-color/rgb-008.html
+++ b/css/css-color/rgb-008.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="legacy rgb() with 8-bit numbers and percent alpha, with commas">
 <style>
-    .test {color: rgb(0, 80.0, 0, 100%)}
+    .test {color: rgb(0, 128.0, 0, 100%)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/rgb-rounding-001.html b/css/css-color/rgb-rounding-001.html
new file mode 100644
index 0000000..ecf93f0
--- /dev/null
+++ b/css/css-color/rgb-rounding-001.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: RGB Channel Rounding</title>
+<link rel="author" title="Chris Nardi" href="mailto:cnardi@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#rgb-functions">
+<meta name="assert" content="Tests if RGB channels indicated as decimals are rounded correctly">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="test" style="color: rgb(45, 23, 27)"></div>
+
+<script>
+    test(function() {
+        var element = document.getElementById('test');
+        color = "rgb(2.5, 3.4, 4.6)";
+        expected = ["rgb(3, 3, 5)", color];
+        element.style.cssText = "color: " + color;
+        assert_in_array(getComputedStyle(element).color, expected);
+    }, 'Tests that RGB channels are rounded appropriately');
+</script>
diff --git a/css/css-color/rgba-002.html b/css/css-color/rgba-002.html
index 3113985..14dd2b5 100644
--- a/css/css-color/rgba-002.html
+++ b/css/css-color/rgba-002.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="legacy rgba() with 8-bit numbers and no alpha, also no comma">
 <style>
-    .test {color: rgba(0 80.0 0)}
+    .test {color: rgba(0 128.0 0)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/rgba-004.html b/css/css-color/rgba-004.html
index 0d75d4f..22e5854 100644
--- a/css/css-color/rgba-004.html
+++ b/css/css-color/rgba-004.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="legacy rgba() with 8-bit numbers and numeric alpha, also no comma">
 <style>
-    .test {color: rgba(0 80.0 0 / 1)}
+    .test {color: rgba(0 128.0 0 / 1)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/rgba-006.html b/css/css-color/rgba-006.html
index e67956e..3b397ab 100644
--- a/css/css-color/rgba-006.html
+++ b/css/css-color/rgba-006.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="legacy rgba() with 8-bit numbers and percent alpha, also no comma">
 <style>
-    .test {color: rgba(0 80.0 0 / 100%)}
+    .test {color: rgba(0 128.0 0 / 100%)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/rgba-008.html b/css/css-color/rgba-008.html
index 19bb956..65eab55 100644
--- a/css/css-color/rgba-008.html
+++ b/css/css-color/rgba-008.html
@@ -6,7 +6,7 @@
 <link rel="match" href="greentext-ref.html">
 <meta name="assert" content="legacy rgba() with 8-bit numbers and percent alpha, with commas">
 <style>
-    .test {color: rgba(0, 80.0, 0, 100%)}
+    .test {color: rgba(0, 128.0, 0, 100%)}
 </style>
 <body>
     <p class="test">Test passes if this text is green</p>
diff --git a/css/css-color/t32-opacity-basic-0.0-a-ref.html b/css/css-color/t32-opacity-basic-0.0-a-ref.html
new file mode 100644
index 0000000..298c489
--- /dev/null
+++ b/css/css-color/t32-opacity-basic-0.0-a-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p id="one">This should be the only text visible on this page.</p>
+</body>
diff --git a/css/css-color/t32-opacity-basic-0.0-a.xht b/css/css-color/t32-opacity-basic-0.0-a.xht
index 20a2dd3..637638d 100644
--- a/css/css-color/t32-opacity-basic-0.0-a.xht
+++ b/css/css-color/t32-opacity-basic-0.0-a.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#transparency" />
+		<link rel="match" href="t32-opacity-basic-0.0-a-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Opacity of 0.0 makes box transparent." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t32-opacity-basic-1.0-a-ref.html b/css/css-color/t32-opacity-basic-1.0-a-ref.html
new file mode 100644
index 0000000..b59c82f
--- /dev/null
+++ b/css/css-color/t32-opacity-basic-1.0-a-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p id="one">This text should be the same color as the line below.</p>
+    <p id="two">This text should be the same color as the line above.</p>
+</body>
diff --git a/css/css-color/t32-opacity-basic-1.0-a.xht b/css/css-color/t32-opacity-basic-1.0-a.xht
index 01dc48c..6d8ea9e 100644
--- a/css/css-color/t32-opacity-basic-1.0-a.xht
+++ b/css/css-color/t32-opacity-basic-1.0-a.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#transparency" />
+		<link rel="match" href="t32-opacity-basic-1.0-a-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Opacity of 1.0 makes box fully opaque." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t32-opacity-clamping-0.0-b.xht b/css/css-color/t32-opacity-clamping-0.0-b.xht
index 61687c0..966f7e0 100644
--- a/css/css-color/t32-opacity-clamping-0.0-b.xht
+++ b/css/css-color/t32-opacity-clamping-0.0-b.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#transparency" />
+		<link rel="match" href="t32-opacity-basic-0.0-a-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Opacity values less than 0.0 are clamped to 0.0" />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t32-opacity-clamping-1.0-b-ref.html b/css/css-color/t32-opacity-clamping-1.0-b-ref.html
new file mode 100644
index 0000000..08854a1
--- /dev/null
+++ b/css/css-color/t32-opacity-clamping-1.0-b-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<body>
+    <p id="one">There should be six lines of text on this page, all the same color. [1 of 6]</p>
+    <p id="two">There should be six lines of text on this page, all the same color. [2 of 6]</p>
+    <p id="three">There should be six lines of text on this page, all the same color. [3 of 6]</p>
+    <p id="four">There should be six lines of text on this page, all the same color. [4 of 6]</p>
+    <p id="five">There should be six lines of text on this page, all the same color. [5 of 6]</p>
+    <p id="six">There should be six lines of text on this page, all the same color. [6 of 6]</p>
+</body>
diff --git a/css/css-color/t32-opacity-clamping-1.0-b.xht b/css/css-color/t32-opacity-clamping-1.0-b.xht
index 56bca60..31121cb 100644
--- a/css/css-color/t32-opacity-clamping-1.0-b.xht
+++ b/css/css-color/t32-opacity-clamping-1.0-b.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#transparency" />
+		<link rel="match" href="t32-opacity-clamping-1.0-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Opacity values greater than 1.0 are clamped to 1.0" />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t32-opacity-offscreen-b-ref.html b/css/css-color/t32-opacity-offscreen-b-ref.html
new file mode 100644
index 0000000..2a3ae62
--- /dev/null
+++ b/css/css-color/t32-opacity-offscreen-b-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+  div { width: 5em; height: 1em; margin: 2px 0; }
+  .test { background: rgb(153, 153, 255); }
+</style>
+<body>
+    <p>The following five boxes should be the same color:</p>
+
+    <div class="test"></div>
+    <div class="test"></div>
+    <div class="test"></div>
+    <div class="test"></div>
+    <div class="test"></div>
+</body>
diff --git a/css/css-color/t32-opacity-offscreen-b.xht b/css/css-color/t32-opacity-offscreen-b.xht
index 2303fac..a373f98 100644
--- a/css/css-color/t32-opacity-offscreen-b.xht
+++ b/css/css-color/t32-opacity-offscreen-b.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#transparency" />
+		<link rel="match" href="t32-opacity-offscreen-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="That opacity specifies compositing the contents as a single offscreen buffer." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t32-opacity-offscreen-with-alpha-c-ref.html b/css/css-color/t32-opacity-offscreen-with-alpha-c-ref.html
new file mode 100644
index 0000000..923e449
--- /dev/null
+++ b/css/css-color/t32-opacity-offscreen-with-alpha-c-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+  div { width: 5em; height: 1em; margin: 2px 0; }
+  .test { background: rgb(204, 204, 255); }
+</style>
+<body>
+    <p>The following four boxes should be the same color:</p>
+
+    <div class="test"></div>
+    <div class="test"></div>
+    <div class="test"></div>
+    <div class="test"></div>
+</body>
diff --git a/css/css-color/t32-opacity-offscreen-with-alpha-c.xht b/css/css-color/t32-opacity-offscreen-with-alpha-c.xht
index 5fa074c..fdd9ae2 100644
--- a/css/css-color/t32-opacity-offscreen-with-alpha-c.xht
+++ b/css/css-color/t32-opacity-offscreen-with-alpha-c.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#transparency" />
+		<link rel="match" href="t32-opacity-offscreen-with-alpha-c-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="That alpha within the offscreen buffer is composited correctly." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t41-html4-keywords-a-ref.html b/css/css-color/t41-html4-keywords-a-ref.html
new file mode 100644
index 0000000..4f95dff
--- /dev/null
+++ b/css/css-color/t41-html4-keywords-a-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    html, body { background: #dddddd; color: #000000; }
+    table { border-spacing: 0; padding: 0; border: none; }
+    td { border: none; padding: 0; }
+</style>
+<body>
+    <p>Each row in the following table (except the first and last, to show where the columns are) should have two cells with the same background color.  None of them should match the background of the page.</p>
+
+    <table>
+        <tr><td style="background: black">&nbsp;&nbsp;&nbsp;</td><td style="background: white">&nbsp;&nbsp;&nbsp;</td></tr>
+        <tr><td style="background: #000000" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #008000" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #C0C0C0" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #00FF00" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #808080" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #808000" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #FFFFFF" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #FFFF00" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #800000" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #000080" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #FF0000" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #0000FF" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #800080" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #008080" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #FF00FF" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: #00FFFF" colspan=2>&nbsp;</td></tr>
+        <tr><td style="background: white">&nbsp;&nbsp;&nbsp;</td><td style="background: black">&nbsp;&nbsp;&nbsp;</td></tr>
+    </table>
+</body>
diff --git a/css/css-color/t41-html4-keywords-a.xht b/css/css-color/t41-html4-keywords-a.xht
index 1862f82..3a3e332 100644
--- a/css/css-color/t41-html4-keywords-a.xht
+++ b/css/css-color/t41-html4-keywords-a.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#html4" />
+		<link rel="match" href="t41-html4-keywords-a-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Test that the HTML4 color keywords have the correct values." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t421-rgb-clip-outside-gamut-b-ref.html b/css/css-color/t421-rgb-clip-outside-gamut-b-ref.html
new file mode 100644
index 0000000..81bbc51
--- /dev/null
+++ b/css/css-color/t421-rgb-clip-outside-gamut-b-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+  table { border-spacing: 0 2px; padding: 0; border: none; }
+  td { border: none; padding: 0; height: 1.2em; }
+</style>
+<body>
+  <p><strong>WARNING: This test assumes that the device gamut is sRGB
+  (as it will be for many CRT monitors).</strong></p>
+  <p>Every row in this table should have both columns the same color:</p>
+  <table>
+    <tr>
+      <th style="background:white; color: black">Column 1</th>
+      <th style="background:black; color: white">Column 2</th>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(0, 255, 0)">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(0, 0, 255)">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(0, 255, 0)">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(0, 255, 0)">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(0, 255, 0)">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(0, 255, 0)">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(50, 0, 255)">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan='2' style="background: rgb(0, 50, 255)">&nbsp;</td>
+    </tr>
+  </table>
+</body>
diff --git a/css/css-color/t421-rgb-clip-outside-gamut-b.xht b/css/css-color/t421-rgb-clip-outside-gamut-b.xht
index 5e00ea6..1fd50ff 100644
--- a/css/css-color/t421-rgb-clip-outside-gamut-b.xht
+++ b/css/css-color/t421-rgb-clip-outside-gamut-b.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#rgb-color" />
+		<link rel="match" href="t421-rgb-clip-outside-gamut-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Test clipping of rgb() values outside the device gamut." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t421-rgb-hex3-expand-b-ref.html b/css/css-color/t421-rgb-hex3-expand-b-ref.html
new file mode 100644
index 0000000..842ead1
--- /dev/null
+++ b/css/css-color/t421-rgb-hex3-expand-b-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+  table { border-spacing: 0; padding: 0; border: none; }
+  td { border: none; padding: 0; width: 1.2em; height: 1.2em; }
+</style>
+<body>
+  <p>The left and right cells in each row of the following table should be the <em>same</em> color.</p>
+
+  <table>
+    <tr><td style="background: #ee9922">&nbsp;</td><td style="background: #ee9922">&nbsp;</td></tr>
+    <tr><td style="background: #ffbb00">&nbsp;</td><td style="background: #ffbb00">&nbsp;</td></tr>
+    <tr><td style="background: #338811">&nbsp;</td><td style="background: #338811">&nbsp;</td></tr>
+  </table>
+
+  <p>The left and right cells in each row of the following table should be slightly <em>different</em> colors.  The right side should be slightly darker than the left.</p>
+
+  <table>
+    <tr><td style="background: #e09020">&nbsp;</td><td style="background: #e09020">&nbsp;</td></tr>
+    <tr><td style="background: #f0b000">&nbsp;</td><td style="background: #f0b000">&nbsp;</td></tr>
+    <tr><td style="background: #308010">&nbsp;</td><td style="background: #308010">&nbsp;</td></tr>
+  </table>
+</body>
diff --git a/css/css-color/t421-rgb-values-meaning-b-ref.html b/css/css-color/t421-rgb-values-meaning-b-ref.html
new file mode 100644
index 0000000..b468f0e
--- /dev/null
+++ b/css/css-color/t421-rgb-values-meaning-b-ref.html
@@ -0,0 +1,1539 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    table { border-spacing: 0; padding: 0; border: none; }
+    td { border: none; padding: 0; }
+    td { width: 1.2em; height: 1.2em; }
+</style>
+<body>
+    <p>The following table should have four matching rows gradually changing from red at the left to black at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee0000">&nbsp;</td>
+            <td style="background: #dd0000">&nbsp;</td>
+            <td style="background: #cc0000">&nbsp;</td>
+            <td style="background: #bb0000">&nbsp;</td>
+            <td style="background: #aa0000">&nbsp;</td>
+            <td style="background: #990000">&nbsp;</td>
+            <td style="background: #880000">&nbsp;</td>
+            <td style="background: #770000">&nbsp;</td>
+            <td style="background: #660000">&nbsp;</td>
+            <td style="background: #550000">&nbsp;</td>
+            <td style="background: #440000">&nbsp;</td>
+            <td style="background: #330000">&nbsp;</td>
+            <td style="background: #220000">&nbsp;</td>
+            <td style="background: #110000">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee0000">&nbsp;</td>
+            <td style="background: #dd0000">&nbsp;</td>
+            <td style="background: #cc0000">&nbsp;</td>
+            <td style="background: #bb0000">&nbsp;</td>
+            <td style="background: #aa0000">&nbsp;</td>
+            <td style="background: #990000">&nbsp;</td>
+            <td style="background: #880000">&nbsp;</td>
+            <td style="background: #770000">&nbsp;</td>
+            <td style="background: #660000">&nbsp;</td>
+            <td style="background: #550000">&nbsp;</td>
+            <td style="background: #440000">&nbsp;</td>
+            <td style="background: #330000">&nbsp;</td>
+            <td style="background: #220000">&nbsp;</td>
+            <td style="background: #110000">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee0000">&nbsp;</td>
+            <td style="background: #dd0000">&nbsp;</td>
+            <td style="background: #cc0000">&nbsp;</td>
+            <td style="background: #bb0000">&nbsp;</td>
+            <td style="background: #aa0000">&nbsp;</td>
+            <td style="background: #990000">&nbsp;</td>
+            <td style="background: #880000">&nbsp;</td>
+            <td style="background: #770000">&nbsp;</td>
+            <td style="background: #660000">&nbsp;</td>
+            <td style="background: #550000">&nbsp;</td>
+            <td style="background: #440000">&nbsp;</td>
+            <td style="background: #330000">&nbsp;</td>
+            <td style="background: #220000">&nbsp;</td>
+            <td style="background: #110000">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee0000">&nbsp;</td>
+            <td style="background: #dd0000">&nbsp;</td>
+            <td style="background: #cc0000">&nbsp;</td>
+            <td style="background: #bb0000">&nbsp;</td>
+            <td style="background: #aa0000">&nbsp;</td>
+            <td style="background: #990000">&nbsp;</td>
+            <td style="background: #880000">&nbsp;</td>
+            <td style="background: #770000">&nbsp;</td>
+            <td style="background: #660000">&nbsp;</td>
+            <td style="background: #550000">&nbsp;</td>
+            <td style="background: #440000">&nbsp;</td>
+            <td style="background: #330000">&nbsp;</td>
+            <td style="background: #220000">&nbsp;</td>
+            <td style="background: #110000">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from white at the left to red at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeee">&nbsp;</td>
+            <td style="background: #ffdddd">&nbsp;</td>
+            <td style="background: #ffcccc">&nbsp;</td>
+            <td style="background: #ffbbbb">&nbsp;</td>
+            <td style="background: #ffaaaa">&nbsp;</td>
+            <td style="background: #ff9999">&nbsp;</td>
+            <td style="background: #ff8888">&nbsp;</td>
+            <td style="background: #ff7777">&nbsp;</td>
+            <td style="background: #ff6666">&nbsp;</td>
+            <td style="background: #ff5555">&nbsp;</td>
+            <td style="background: #ff4444">&nbsp;</td>
+            <td style="background: #ff3333">&nbsp;</td>
+            <td style="background: #ff2222">&nbsp;</td>
+            <td style="background: #ff1111">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeee">&nbsp;</td>
+            <td style="background: #ffdddd">&nbsp;</td>
+            <td style="background: #ffcccc">&nbsp;</td>
+            <td style="background: #ffbbbb">&nbsp;</td>
+            <td style="background: #ffaaaa">&nbsp;</td>
+            <td style="background: #ff9999">&nbsp;</td>
+            <td style="background: #ff8888">&nbsp;</td>
+            <td style="background: #ff7777">&nbsp;</td>
+            <td style="background: #ff6666">&nbsp;</td>
+            <td style="background: #ff5555">&nbsp;</td>
+            <td style="background: #ff4444">&nbsp;</td>
+            <td style="background: #ff3333">&nbsp;</td>
+            <td style="background: #ff2222">&nbsp;</td>
+            <td style="background: #ff1111">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeee">&nbsp;</td>
+            <td style="background: #ffdddd">&nbsp;</td>
+            <td style="background: #ffcccc">&nbsp;</td>
+            <td style="background: #ffbbbb">&nbsp;</td>
+            <td style="background: #ffaaaa">&nbsp;</td>
+            <td style="background: #ff9999">&nbsp;</td>
+            <td style="background: #ff8888">&nbsp;</td>
+            <td style="background: #ff7777">&nbsp;</td>
+            <td style="background: #ff6666">&nbsp;</td>
+            <td style="background: #ff5555">&nbsp;</td>
+            <td style="background: #ff4444">&nbsp;</td>
+            <td style="background: #ff3333">&nbsp;</td>
+            <td style="background: #ff2222">&nbsp;</td>
+            <td style="background: #ff1111">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeee">&nbsp;</td>
+            <td style="background: #ffdddd">&nbsp;</td>
+            <td style="background: #ffcccc">&nbsp;</td>
+            <td style="background: #ffbbbb">&nbsp;</td>
+            <td style="background: #ffaaaa">&nbsp;</td>
+            <td style="background: #ff9999">&nbsp;</td>
+            <td style="background: #ff8888">&nbsp;</td>
+            <td style="background: #ff7777">&nbsp;</td>
+            <td style="background: #ff6666">&nbsp;</td>
+            <td style="background: #ff5555">&nbsp;</td>
+            <td style="background: #ff4444">&nbsp;</td>
+            <td style="background: #ff3333">&nbsp;</td>
+            <td style="background: #ff2222">&nbsp;</td>
+            <td style="background: #ff1111">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from green at the left to black at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee00">&nbsp;</td>
+            <td style="background: #00dd00">&nbsp;</td>
+            <td style="background: #00cc00">&nbsp;</td>
+            <td style="background: #00bb00">&nbsp;</td>
+            <td style="background: #00aa00">&nbsp;</td>
+            <td style="background: #009900">&nbsp;</td>
+            <td style="background: #008800">&nbsp;</td>
+            <td style="background: #007700">&nbsp;</td>
+            <td style="background: #006600">&nbsp;</td>
+            <td style="background: #005500">&nbsp;</td>
+            <td style="background: #004400">&nbsp;</td>
+            <td style="background: #003300">&nbsp;</td>
+            <td style="background: #002200">&nbsp;</td>
+            <td style="background: #001100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee00">&nbsp;</td>
+            <td style="background: #00dd00">&nbsp;</td>
+            <td style="background: #00cc00">&nbsp;</td>
+            <td style="background: #00bb00">&nbsp;</td>
+            <td style="background: #00aa00">&nbsp;</td>
+            <td style="background: #009900">&nbsp;</td>
+            <td style="background: #008800">&nbsp;</td>
+            <td style="background: #007700">&nbsp;</td>
+            <td style="background: #006600">&nbsp;</td>
+            <td style="background: #005500">&nbsp;</td>
+            <td style="background: #004400">&nbsp;</td>
+            <td style="background: #003300">&nbsp;</td>
+            <td style="background: #002200">&nbsp;</td>
+            <td style="background: #001100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee00">&nbsp;</td>
+            <td style="background: #00dd00">&nbsp;</td>
+            <td style="background: #00cc00">&nbsp;</td>
+            <td style="background: #00bb00">&nbsp;</td>
+            <td style="background: #00aa00">&nbsp;</td>
+            <td style="background: #009900">&nbsp;</td>
+            <td style="background: #008800">&nbsp;</td>
+            <td style="background: #007700">&nbsp;</td>
+            <td style="background: #006600">&nbsp;</td>
+            <td style="background: #005500">&nbsp;</td>
+            <td style="background: #004400">&nbsp;</td>
+            <td style="background: #003300">&nbsp;</td>
+            <td style="background: #002200">&nbsp;</td>
+            <td style="background: #001100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee00">&nbsp;</td>
+            <td style="background: #00dd00">&nbsp;</td>
+            <td style="background: #00cc00">&nbsp;</td>
+            <td style="background: #00bb00">&nbsp;</td>
+            <td style="background: #00aa00">&nbsp;</td>
+            <td style="background: #009900">&nbsp;</td>
+            <td style="background: #008800">&nbsp;</td>
+            <td style="background: #007700">&nbsp;</td>
+            <td style="background: #006600">&nbsp;</td>
+            <td style="background: #005500">&nbsp;</td>
+            <td style="background: #004400">&nbsp;</td>
+            <td style="background: #003300">&nbsp;</td>
+            <td style="background: #002200">&nbsp;</td>
+            <td style="background: #001100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from white at the left to green at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffee">&nbsp;</td>
+            <td style="background: #ddffdd">&nbsp;</td>
+            <td style="background: #ccffcc">&nbsp;</td>
+            <td style="background: #bbffbb">&nbsp;</td>
+            <td style="background: #aaffaa">&nbsp;</td>
+            <td style="background: #99ff99">&nbsp;</td>
+            <td style="background: #88ff88">&nbsp;</td>
+            <td style="background: #77ff77">&nbsp;</td>
+            <td style="background: #66ff66">&nbsp;</td>
+            <td style="background: #55ff55">&nbsp;</td>
+            <td style="background: #44ff44">&nbsp;</td>
+            <td style="background: #33ff33">&nbsp;</td>
+            <td style="background: #22ff22">&nbsp;</td>
+            <td style="background: #11ff11">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffee">&nbsp;</td>
+            <td style="background: #ddffdd">&nbsp;</td>
+            <td style="background: #ccffcc">&nbsp;</td>
+            <td style="background: #bbffbb">&nbsp;</td>
+            <td style="background: #aaffaa">&nbsp;</td>
+            <td style="background: #99ff99">&nbsp;</td>
+            <td style="background: #88ff88">&nbsp;</td>
+            <td style="background: #77ff77">&nbsp;</td>
+            <td style="background: #66ff66">&nbsp;</td>
+            <td style="background: #55ff55">&nbsp;</td>
+            <td style="background: #44ff44">&nbsp;</td>
+            <td style="background: #33ff33">&nbsp;</td>
+            <td style="background: #22ff22">&nbsp;</td>
+            <td style="background: #11ff11">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffee">&nbsp;</td>
+            <td style="background: #ddffdd">&nbsp;</td>
+            <td style="background: #ccffcc">&nbsp;</td>
+            <td style="background: #bbffbb">&nbsp;</td>
+            <td style="background: #aaffaa">&nbsp;</td>
+            <td style="background: #99ff99">&nbsp;</td>
+            <td style="background: #88ff88">&nbsp;</td>
+            <td style="background: #77ff77">&nbsp;</td>
+            <td style="background: #66ff66">&nbsp;</td>
+            <td style="background: #55ff55">&nbsp;</td>
+            <td style="background: #44ff44">&nbsp;</td>
+            <td style="background: #33ff33">&nbsp;</td>
+            <td style="background: #22ff22">&nbsp;</td>
+            <td style="background: #11ff11">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffee">&nbsp;</td>
+            <td style="background: #ddffdd">&nbsp;</td>
+            <td style="background: #ccffcc">&nbsp;</td>
+            <td style="background: #bbffbb">&nbsp;</td>
+            <td style="background: #aaffaa">&nbsp;</td>
+            <td style="background: #99ff99">&nbsp;</td>
+            <td style="background: #88ff88">&nbsp;</td>
+            <td style="background: #77ff77">&nbsp;</td>
+            <td style="background: #66ff66">&nbsp;</td>
+            <td style="background: #55ff55">&nbsp;</td>
+            <td style="background: #44ff44">&nbsp;</td>
+            <td style="background: #33ff33">&nbsp;</td>
+            <td style="background: #22ff22">&nbsp;</td>
+            <td style="background: #11ff11">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from blue at the left to black at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: #0000ee">&nbsp;</td>
+            <td style="background: #0000dd">&nbsp;</td>
+            <td style="background: #0000cc">&nbsp;</td>
+            <td style="background: #0000bb">&nbsp;</td>
+            <td style="background: #0000aa">&nbsp;</td>
+            <td style="background: #000099">&nbsp;</td>
+            <td style="background: #000088">&nbsp;</td>
+            <td style="background: #000077">&nbsp;</td>
+            <td style="background: #000066">&nbsp;</td>
+            <td style="background: #000055">&nbsp;</td>
+            <td style="background: #000044">&nbsp;</td>
+            <td style="background: #000033">&nbsp;</td>
+            <td style="background: #000022">&nbsp;</td>
+            <td style="background: #000011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: #0000ee">&nbsp;</td>
+            <td style="background: #0000dd">&nbsp;</td>
+            <td style="background: #0000cc">&nbsp;</td>
+            <td style="background: #0000bb">&nbsp;</td>
+            <td style="background: #0000aa">&nbsp;</td>
+            <td style="background: #000099">&nbsp;</td>
+            <td style="background: #000088">&nbsp;</td>
+            <td style="background: #000077">&nbsp;</td>
+            <td style="background: #000066">&nbsp;</td>
+            <td style="background: #000055">&nbsp;</td>
+            <td style="background: #000044">&nbsp;</td>
+            <td style="background: #000033">&nbsp;</td>
+            <td style="background: #000022">&nbsp;</td>
+            <td style="background: #000011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: #0000ee">&nbsp;</td>
+            <td style="background: #0000dd">&nbsp;</td>
+            <td style="background: #0000cc">&nbsp;</td>
+            <td style="background: #0000bb">&nbsp;</td>
+            <td style="background: #0000aa">&nbsp;</td>
+            <td style="background: #000099">&nbsp;</td>
+            <td style="background: #000088">&nbsp;</td>
+            <td style="background: #000077">&nbsp;</td>
+            <td style="background: #000066">&nbsp;</td>
+            <td style="background: #000055">&nbsp;</td>
+            <td style="background: #000044">&nbsp;</td>
+            <td style="background: #000033">&nbsp;</td>
+            <td style="background: #000022">&nbsp;</td>
+            <td style="background: #000011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: #0000ee">&nbsp;</td>
+            <td style="background: #0000dd">&nbsp;</td>
+            <td style="background: #0000cc">&nbsp;</td>
+            <td style="background: #0000bb">&nbsp;</td>
+            <td style="background: #0000aa">&nbsp;</td>
+            <td style="background: #000099">&nbsp;</td>
+            <td style="background: #000088">&nbsp;</td>
+            <td style="background: #000077">&nbsp;</td>
+            <td style="background: #000066">&nbsp;</td>
+            <td style="background: #000055">&nbsp;</td>
+            <td style="background: #000044">&nbsp;</td>
+            <td style="background: #000033">&nbsp;</td>
+            <td style="background: #000022">&nbsp;</td>
+            <td style="background: #000011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from white at the left to blue at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeeeff">&nbsp;</td>
+            <td style="background: #ddddff">&nbsp;</td>
+            <td style="background: #ccccff">&nbsp;</td>
+            <td style="background: #bbbbff">&nbsp;</td>
+            <td style="background: #aaaaff">&nbsp;</td>
+            <td style="background: #9999ff">&nbsp;</td>
+            <td style="background: #8888ff">&nbsp;</td>
+            <td style="background: #7777ff">&nbsp;</td>
+            <td style="background: #6666ff">&nbsp;</td>
+            <td style="background: #5555ff">&nbsp;</td>
+            <td style="background: #4444ff">&nbsp;</td>
+            <td style="background: #3333ff">&nbsp;</td>
+            <td style="background: #2222ff">&nbsp;</td>
+            <td style="background: #1111ff">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeeeff">&nbsp;</td>
+            <td style="background: #ddddff">&nbsp;</td>
+            <td style="background: #ccccff">&nbsp;</td>
+            <td style="background: #bbbbff">&nbsp;</td>
+            <td style="background: #aaaaff">&nbsp;</td>
+            <td style="background: #9999ff">&nbsp;</td>
+            <td style="background: #8888ff">&nbsp;</td>
+            <td style="background: #7777ff">&nbsp;</td>
+            <td style="background: #6666ff">&nbsp;</td>
+            <td style="background: #5555ff">&nbsp;</td>
+            <td style="background: #4444ff">&nbsp;</td>
+            <td style="background: #3333ff">&nbsp;</td>
+            <td style="background: #2222ff">&nbsp;</td>
+            <td style="background: #1111ff">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeeeff">&nbsp;</td>
+            <td style="background: #ddddff">&nbsp;</td>
+            <td style="background: #ccccff">&nbsp;</td>
+            <td style="background: #bbbbff">&nbsp;</td>
+            <td style="background: #aaaaff">&nbsp;</td>
+            <td style="background: #9999ff">&nbsp;</td>
+            <td style="background: #8888ff">&nbsp;</td>
+            <td style="background: #7777ff">&nbsp;</td>
+            <td style="background: #6666ff">&nbsp;</td>
+            <td style="background: #5555ff">&nbsp;</td>
+            <td style="background: #4444ff">&nbsp;</td>
+            <td style="background: #3333ff">&nbsp;</td>
+            <td style="background: #2222ff">&nbsp;</td>
+            <td style="background: #1111ff">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeeeff">&nbsp;</td>
+            <td style="background: #ddddff">&nbsp;</td>
+            <td style="background: #ccccff">&nbsp;</td>
+            <td style="background: #bbbbff">&nbsp;</td>
+            <td style="background: #aaaaff">&nbsp;</td>
+            <td style="background: #9999ff">&nbsp;</td>
+            <td style="background: #8888ff">&nbsp;</td>
+            <td style="background: #7777ff">&nbsp;</td>
+            <td style="background: #6666ff">&nbsp;</td>
+            <td style="background: #5555ff">&nbsp;</td>
+            <td style="background: #4444ff">&nbsp;</td>
+            <td style="background: #3333ff">&nbsp;</td>
+            <td style="background: #2222ff">&nbsp;</td>
+            <td style="background: #1111ff">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from yellow at the left to black at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #eeee00">&nbsp;</td>
+            <td style="background: #dddd00">&nbsp;</td>
+            <td style="background: #cccc00">&nbsp;</td>
+            <td style="background: #bbbb00">&nbsp;</td>
+            <td style="background: #aaaa00">&nbsp;</td>
+            <td style="background: #999900">&nbsp;</td>
+            <td style="background: #888800">&nbsp;</td>
+            <td style="background: #777700">&nbsp;</td>
+            <td style="background: #666600">&nbsp;</td>
+            <td style="background: #555500">&nbsp;</td>
+            <td style="background: #444400">&nbsp;</td>
+            <td style="background: #333300">&nbsp;</td>
+            <td style="background: #222200">&nbsp;</td>
+            <td style="background: #111100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #eeee00">&nbsp;</td>
+            <td style="background: #dddd00">&nbsp;</td>
+            <td style="background: #cccc00">&nbsp;</td>
+            <td style="background: #bbbb00">&nbsp;</td>
+            <td style="background: #aaaa00">&nbsp;</td>
+            <td style="background: #999900">&nbsp;</td>
+            <td style="background: #888800">&nbsp;</td>
+            <td style="background: #777700">&nbsp;</td>
+            <td style="background: #666600">&nbsp;</td>
+            <td style="background: #555500">&nbsp;</td>
+            <td style="background: #444400">&nbsp;</td>
+            <td style="background: #333300">&nbsp;</td>
+            <td style="background: #222200">&nbsp;</td>
+            <td style="background: #111100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #eeee00">&nbsp;</td>
+            <td style="background: #dddd00">&nbsp;</td>
+            <td style="background: #cccc00">&nbsp;</td>
+            <td style="background: #bbbb00">&nbsp;</td>
+            <td style="background: #aaaa00">&nbsp;</td>
+            <td style="background: #999900">&nbsp;</td>
+            <td style="background: #888800">&nbsp;</td>
+            <td style="background: #777700">&nbsp;</td>
+            <td style="background: #666600">&nbsp;</td>
+            <td style="background: #555500">&nbsp;</td>
+            <td style="background: #444400">&nbsp;</td>
+            <td style="background: #333300">&nbsp;</td>
+            <td style="background: #222200">&nbsp;</td>
+            <td style="background: #111100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #eeee00">&nbsp;</td>
+            <td style="background: #dddd00">&nbsp;</td>
+            <td style="background: #cccc00">&nbsp;</td>
+            <td style="background: #bbbb00">&nbsp;</td>
+            <td style="background: #aaaa00">&nbsp;</td>
+            <td style="background: #999900">&nbsp;</td>
+            <td style="background: #888800">&nbsp;</td>
+            <td style="background: #777700">&nbsp;</td>
+            <td style="background: #666600">&nbsp;</td>
+            <td style="background: #555500">&nbsp;</td>
+            <td style="background: #444400">&nbsp;</td>
+            <td style="background: #333300">&nbsp;</td>
+            <td style="background: #222200">&nbsp;</td>
+            <td style="background: #111100">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from white at the left to yellow at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffffee">&nbsp;</td>
+            <td style="background: #ffffdd">&nbsp;</td>
+            <td style="background: #ffffcc">&nbsp;</td>
+            <td style="background: #ffffbb">&nbsp;</td>
+            <td style="background: #ffffaa">&nbsp;</td>
+            <td style="background: #ffff99">&nbsp;</td>
+            <td style="background: #ffff88">&nbsp;</td>
+            <td style="background: #ffff77">&nbsp;</td>
+            <td style="background: #ffff66">&nbsp;</td>
+            <td style="background: #ffff55">&nbsp;</td>
+            <td style="background: #ffff44">&nbsp;</td>
+            <td style="background: #ffff33">&nbsp;</td>
+            <td style="background: #ffff22">&nbsp;</td>
+            <td style="background: #ffff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffffee">&nbsp;</td>
+            <td style="background: #ffffdd">&nbsp;</td>
+            <td style="background: #ffffcc">&nbsp;</td>
+            <td style="background: #ffffbb">&nbsp;</td>
+            <td style="background: #ffffaa">&nbsp;</td>
+            <td style="background: #ffff99">&nbsp;</td>
+            <td style="background: #ffff88">&nbsp;</td>
+            <td style="background: #ffff77">&nbsp;</td>
+            <td style="background: #ffff66">&nbsp;</td>
+            <td style="background: #ffff55">&nbsp;</td>
+            <td style="background: #ffff44">&nbsp;</td>
+            <td style="background: #ffff33">&nbsp;</td>
+            <td style="background: #ffff22">&nbsp;</td>
+            <td style="background: #ffff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffffee">&nbsp;</td>
+            <td style="background: #ffffdd">&nbsp;</td>
+            <td style="background: #ffffcc">&nbsp;</td>
+            <td style="background: #ffffbb">&nbsp;</td>
+            <td style="background: #ffffaa">&nbsp;</td>
+            <td style="background: #ffff99">&nbsp;</td>
+            <td style="background: #ffff88">&nbsp;</td>
+            <td style="background: #ffff77">&nbsp;</td>
+            <td style="background: #ffff66">&nbsp;</td>
+            <td style="background: #ffff55">&nbsp;</td>
+            <td style="background: #ffff44">&nbsp;</td>
+            <td style="background: #ffff33">&nbsp;</td>
+            <td style="background: #ffff22">&nbsp;</td>
+            <td style="background: #ffff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffffee">&nbsp;</td>
+            <td style="background: #ffffdd">&nbsp;</td>
+            <td style="background: #ffffcc">&nbsp;</td>
+            <td style="background: #ffffbb">&nbsp;</td>
+            <td style="background: #ffffaa">&nbsp;</td>
+            <td style="background: #ffff99">&nbsp;</td>
+            <td style="background: #ffff88">&nbsp;</td>
+            <td style="background: #ffff77">&nbsp;</td>
+            <td style="background: #ffff66">&nbsp;</td>
+            <td style="background: #ffff55">&nbsp;</td>
+            <td style="background: #ffff44">&nbsp;</td>
+            <td style="background: #ffff33">&nbsp;</td>
+            <td style="background: #ffff22">&nbsp;</td>
+            <td style="background: #ffff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from fuchsia at the left to black at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee00ee">&nbsp;</td>
+            <td style="background: #dd00dd">&nbsp;</td>
+            <td style="background: #cc00cc">&nbsp;</td>
+            <td style="background: #bb00bb">&nbsp;</td>
+            <td style="background: #aa00aa">&nbsp;</td>
+            <td style="background: #990099">&nbsp;</td>
+            <td style="background: #880088">&nbsp;</td>
+            <td style="background: #770077">&nbsp;</td>
+            <td style="background: #660066">&nbsp;</td>
+            <td style="background: #550055">&nbsp;</td>
+            <td style="background: #440044">&nbsp;</td>
+            <td style="background: #330033">&nbsp;</td>
+            <td style="background: #220022">&nbsp;</td>
+            <td style="background: #110011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee00ee">&nbsp;</td>
+            <td style="background: #dd00dd">&nbsp;</td>
+            <td style="background: #cc00cc">&nbsp;</td>
+            <td style="background: #bb00bb">&nbsp;</td>
+            <td style="background: #aa00aa">&nbsp;</td>
+            <td style="background: #990099">&nbsp;</td>
+            <td style="background: #880088">&nbsp;</td>
+            <td style="background: #770077">&nbsp;</td>
+            <td style="background: #660066">&nbsp;</td>
+            <td style="background: #550055">&nbsp;</td>
+            <td style="background: #440044">&nbsp;</td>
+            <td style="background: #330033">&nbsp;</td>
+            <td style="background: #220022">&nbsp;</td>
+            <td style="background: #110011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee00ee">&nbsp;</td>
+            <td style="background: #dd00dd">&nbsp;</td>
+            <td style="background: #cc00cc">&nbsp;</td>
+            <td style="background: #bb00bb">&nbsp;</td>
+            <td style="background: #aa00aa">&nbsp;</td>
+            <td style="background: #990099">&nbsp;</td>
+            <td style="background: #880088">&nbsp;</td>
+            <td style="background: #770077">&nbsp;</td>
+            <td style="background: #660066">&nbsp;</td>
+            <td style="background: #550055">&nbsp;</td>
+            <td style="background: #440044">&nbsp;</td>
+            <td style="background: #330033">&nbsp;</td>
+            <td style="background: #220022">&nbsp;</td>
+            <td style="background: #110011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee00ee">&nbsp;</td>
+            <td style="background: #dd00dd">&nbsp;</td>
+            <td style="background: #cc00cc">&nbsp;</td>
+            <td style="background: #bb00bb">&nbsp;</td>
+            <td style="background: #aa00aa">&nbsp;</td>
+            <td style="background: #990099">&nbsp;</td>
+            <td style="background: #880088">&nbsp;</td>
+            <td style="background: #770077">&nbsp;</td>
+            <td style="background: #660066">&nbsp;</td>
+            <td style="background: #550055">&nbsp;</td>
+            <td style="background: #440044">&nbsp;</td>
+            <td style="background: #330033">&nbsp;</td>
+            <td style="background: #220022">&nbsp;</td>
+            <td style="background: #110011">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from white at the left to fuchsia at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeff">&nbsp;</td>
+            <td style="background: #ffddff">&nbsp;</td>
+            <td style="background: #ffccff">&nbsp;</td>
+            <td style="background: #ffbbff">&nbsp;</td>
+            <td style="background: #ffaaff">&nbsp;</td>
+            <td style="background: #ff99ff">&nbsp;</td>
+            <td style="background: #ff88ff">&nbsp;</td>
+            <td style="background: #ff77ff">&nbsp;</td>
+            <td style="background: #ff66ff">&nbsp;</td>
+            <td style="background: #ff55ff">&nbsp;</td>
+            <td style="background: #ff44ff">&nbsp;</td>
+            <td style="background: #ff33ff">&nbsp;</td>
+            <td style="background: #ff22ff">&nbsp;</td>
+            <td style="background: #ff11ff">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeff">&nbsp;</td>
+            <td style="background: #ffddff">&nbsp;</td>
+            <td style="background: #ffccff">&nbsp;</td>
+            <td style="background: #ffbbff">&nbsp;</td>
+            <td style="background: #ffaaff">&nbsp;</td>
+            <td style="background: #ff99ff">&nbsp;</td>
+            <td style="background: #ff88ff">&nbsp;</td>
+            <td style="background: #ff77ff">&nbsp;</td>
+            <td style="background: #ff66ff">&nbsp;</td>
+            <td style="background: #ff55ff">&nbsp;</td>
+            <td style="background: #ff44ff">&nbsp;</td>
+            <td style="background: #ff33ff">&nbsp;</td>
+            <td style="background: #ff22ff">&nbsp;</td>
+            <td style="background: #ff11ff">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeff">&nbsp;</td>
+            <td style="background: #ffddff">&nbsp;</td>
+            <td style="background: #ffccff">&nbsp;</td>
+            <td style="background: #ffbbff">&nbsp;</td>
+            <td style="background: #ffaaff">&nbsp;</td>
+            <td style="background: #ff99ff">&nbsp;</td>
+            <td style="background: #ff88ff">&nbsp;</td>
+            <td style="background: #ff77ff">&nbsp;</td>
+            <td style="background: #ff66ff">&nbsp;</td>
+            <td style="background: #ff55ff">&nbsp;</td>
+            <td style="background: #ff44ff">&nbsp;</td>
+            <td style="background: #ff33ff">&nbsp;</td>
+            <td style="background: #ff22ff">&nbsp;</td>
+            <td style="background: #ff11ff">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #ffeeff">&nbsp;</td>
+            <td style="background: #ffddff">&nbsp;</td>
+            <td style="background: #ffccff">&nbsp;</td>
+            <td style="background: #ffbbff">&nbsp;</td>
+            <td style="background: #ffaaff">&nbsp;</td>
+            <td style="background: #ff99ff">&nbsp;</td>
+            <td style="background: #ff88ff">&nbsp;</td>
+            <td style="background: #ff77ff">&nbsp;</td>
+            <td style="background: #ff66ff">&nbsp;</td>
+            <td style="background: #ff55ff">&nbsp;</td>
+            <td style="background: #ff44ff">&nbsp;</td>
+            <td style="background: #ff33ff">&nbsp;</td>
+            <td style="background: #ff22ff">&nbsp;</td>
+            <td style="background: #ff11ff">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from aqua at the left to black at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #00eeee">&nbsp;</td>
+            <td style="background: #00dddd">&nbsp;</td>
+            <td style="background: #00cccc">&nbsp;</td>
+            <td style="background: #00bbbb">&nbsp;</td>
+            <td style="background: #00aaaa">&nbsp;</td>
+            <td style="background: #009999">&nbsp;</td>
+            <td style="background: #008888">&nbsp;</td>
+            <td style="background: #007777">&nbsp;</td>
+            <td style="background: #006666">&nbsp;</td>
+            <td style="background: #005555">&nbsp;</td>
+            <td style="background: #004444">&nbsp;</td>
+            <td style="background: #003333">&nbsp;</td>
+            <td style="background: #002222">&nbsp;</td>
+            <td style="background: #001111">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #00eeee">&nbsp;</td>
+            <td style="background: #00dddd">&nbsp;</td>
+            <td style="background: #00cccc">&nbsp;</td>
+            <td style="background: #00bbbb">&nbsp;</td>
+            <td style="background: #00aaaa">&nbsp;</td>
+            <td style="background: #009999">&nbsp;</td>
+            <td style="background: #008888">&nbsp;</td>
+            <td style="background: #007777">&nbsp;</td>
+            <td style="background: #006666">&nbsp;</td>
+            <td style="background: #005555">&nbsp;</td>
+            <td style="background: #004444">&nbsp;</td>
+            <td style="background: #003333">&nbsp;</td>
+            <td style="background: #002222">&nbsp;</td>
+            <td style="background: #001111">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #00eeee">&nbsp;</td>
+            <td style="background: #00dddd">&nbsp;</td>
+            <td style="background: #00cccc">&nbsp;</td>
+            <td style="background: #00bbbb">&nbsp;</td>
+            <td style="background: #00aaaa">&nbsp;</td>
+            <td style="background: #009999">&nbsp;</td>
+            <td style="background: #008888">&nbsp;</td>
+            <td style="background: #007777">&nbsp;</td>
+            <td style="background: #006666">&nbsp;</td>
+            <td style="background: #005555">&nbsp;</td>
+            <td style="background: #004444">&nbsp;</td>
+            <td style="background: #003333">&nbsp;</td>
+            <td style="background: #002222">&nbsp;</td>
+            <td style="background: #001111">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #00eeee">&nbsp;</td>
+            <td style="background: #00dddd">&nbsp;</td>
+            <td style="background: #00cccc">&nbsp;</td>
+            <td style="background: #00bbbb">&nbsp;</td>
+            <td style="background: #00aaaa">&nbsp;</td>
+            <td style="background: #009999">&nbsp;</td>
+            <td style="background: #008888">&nbsp;</td>
+            <td style="background: #007777">&nbsp;</td>
+            <td style="background: #006666">&nbsp;</td>
+            <td style="background: #005555">&nbsp;</td>
+            <td style="background: #004444">&nbsp;</td>
+            <td style="background: #003333">&nbsp;</td>
+            <td style="background: #002222">&nbsp;</td>
+            <td style="background: #001111">&nbsp;</td>
+            <td style="background: #000000">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from white at the left to aqua at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffff">&nbsp;</td>
+            <td style="background: #ddffff">&nbsp;</td>
+            <td style="background: #ccffff">&nbsp;</td>
+            <td style="background: #bbffff">&nbsp;</td>
+            <td style="background: #aaffff">&nbsp;</td>
+            <td style="background: #99ffff">&nbsp;</td>
+            <td style="background: #88ffff">&nbsp;</td>
+            <td style="background: #77ffff">&nbsp;</td>
+            <td style="background: #66ffff">&nbsp;</td>
+            <td style="background: #55ffff">&nbsp;</td>
+            <td style="background: #44ffff">&nbsp;</td>
+            <td style="background: #33ffff">&nbsp;</td>
+            <td style="background: #22ffff">&nbsp;</td>
+            <td style="background: #11ffff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffff">&nbsp;</td>
+            <td style="background: #ddffff">&nbsp;</td>
+            <td style="background: #ccffff">&nbsp;</td>
+            <td style="background: #bbffff">&nbsp;</td>
+            <td style="background: #aaffff">&nbsp;</td>
+            <td style="background: #99ffff">&nbsp;</td>
+            <td style="background: #88ffff">&nbsp;</td>
+            <td style="background: #77ffff">&nbsp;</td>
+            <td style="background: #66ffff">&nbsp;</td>
+            <td style="background: #55ffff">&nbsp;</td>
+            <td style="background: #44ffff">&nbsp;</td>
+            <td style="background: #33ffff">&nbsp;</td>
+            <td style="background: #22ffff">&nbsp;</td>
+            <td style="background: #11ffff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffff">&nbsp;</td>
+            <td style="background: #ddffff">&nbsp;</td>
+            <td style="background: #ccffff">&nbsp;</td>
+            <td style="background: #bbffff">&nbsp;</td>
+            <td style="background: #aaffff">&nbsp;</td>
+            <td style="background: #99ffff">&nbsp;</td>
+            <td style="background: #88ffff">&nbsp;</td>
+            <td style="background: #77ffff">&nbsp;</td>
+            <td style="background: #66ffff">&nbsp;</td>
+            <td style="background: #55ffff">&nbsp;</td>
+            <td style="background: #44ffff">&nbsp;</td>
+            <td style="background: #33ffff">&nbsp;</td>
+            <td style="background: #22ffff">&nbsp;</td>
+            <td style="background: #11ffff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffffff">&nbsp;</td>
+            <td style="background: #eeffff">&nbsp;</td>
+            <td style="background: #ddffff">&nbsp;</td>
+            <td style="background: #ccffff">&nbsp;</td>
+            <td style="background: #bbffff">&nbsp;</td>
+            <td style="background: #aaffff">&nbsp;</td>
+            <td style="background: #99ffff">&nbsp;</td>
+            <td style="background: #88ffff">&nbsp;</td>
+            <td style="background: #77ffff">&nbsp;</td>
+            <td style="background: #66ffff">&nbsp;</td>
+            <td style="background: #55ffff">&nbsp;</td>
+            <td style="background: #44ffff">&nbsp;</td>
+            <td style="background: #33ffff">&nbsp;</td>
+            <td style="background: #22ffff">&nbsp;</td>
+            <td style="background: #11ffff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from red at the left to green at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee1100">&nbsp;</td>
+            <td style="background: #dd2200">&nbsp;</td>
+            <td style="background: #cc3300">&nbsp;</td>
+            <td style="background: #bb4400">&nbsp;</td>
+            <td style="background: #aa5500">&nbsp;</td>
+            <td style="background: #996600">&nbsp;</td>
+            <td style="background: #887700">&nbsp;</td>
+            <td style="background: #778800">&nbsp;</td>
+            <td style="background: #669900">&nbsp;</td>
+            <td style="background: #55aa00">&nbsp;</td>
+            <td style="background: #44bb00">&nbsp;</td>
+            <td style="background: #33cc00">&nbsp;</td>
+            <td style="background: #22dd00">&nbsp;</td>
+            <td style="background: #11ee00">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee1100">&nbsp;</td>
+            <td style="background: #dd2200">&nbsp;</td>
+            <td style="background: #cc3300">&nbsp;</td>
+            <td style="background: #bb4400">&nbsp;</td>
+            <td style="background: #aa5500">&nbsp;</td>
+            <td style="background: #996600">&nbsp;</td>
+            <td style="background: #887700">&nbsp;</td>
+            <td style="background: #778800">&nbsp;</td>
+            <td style="background: #669900">&nbsp;</td>
+            <td style="background: #55aa00">&nbsp;</td>
+            <td style="background: #44bb00">&nbsp;</td>
+            <td style="background: #33cc00">&nbsp;</td>
+            <td style="background: #22dd00">&nbsp;</td>
+            <td style="background: #11ee00">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee1100">&nbsp;</td>
+            <td style="background: #dd2200">&nbsp;</td>
+            <td style="background: #cc3300">&nbsp;</td>
+            <td style="background: #bb4400">&nbsp;</td>
+            <td style="background: #aa5500">&nbsp;</td>
+            <td style="background: #996600">&nbsp;</td>
+            <td style="background: #887700">&nbsp;</td>
+            <td style="background: #778800">&nbsp;</td>
+            <td style="background: #669900">&nbsp;</td>
+            <td style="background: #55aa00">&nbsp;</td>
+            <td style="background: #44bb00">&nbsp;</td>
+            <td style="background: #33cc00">&nbsp;</td>
+            <td style="background: #22dd00">&nbsp;</td>
+            <td style="background: #11ee00">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff0000">&nbsp;</td>
+            <td style="background: #ee1100">&nbsp;</td>
+            <td style="background: #dd2200">&nbsp;</td>
+            <td style="background: #cc3300">&nbsp;</td>
+            <td style="background: #bb4400">&nbsp;</td>
+            <td style="background: #aa5500">&nbsp;</td>
+            <td style="background: #996600">&nbsp;</td>
+            <td style="background: #887700">&nbsp;</td>
+            <td style="background: #778800">&nbsp;</td>
+            <td style="background: #669900">&nbsp;</td>
+            <td style="background: #55aa00">&nbsp;</td>
+            <td style="background: #44bb00">&nbsp;</td>
+            <td style="background: #33cc00">&nbsp;</td>
+            <td style="background: #22dd00">&nbsp;</td>
+            <td style="background: #11ee00">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from green at the left to blue at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from blue at the left to red at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ff00">&nbsp;</td>
+            <td style="background: #00ee11">&nbsp;</td>
+            <td style="background: #00dd22">&nbsp;</td>
+            <td style="background: #00cc33">&nbsp;</td>
+            <td style="background: #00bb44">&nbsp;</td>
+            <td style="background: #00aa55">&nbsp;</td>
+            <td style="background: #009966">&nbsp;</td>
+            <td style="background: #008877">&nbsp;</td>
+            <td style="background: #007788">&nbsp;</td>
+            <td style="background: #006699">&nbsp;</td>
+            <td style="background: #0055aa">&nbsp;</td>
+            <td style="background: #0044bb">&nbsp;</td>
+            <td style="background: #0033cc">&nbsp;</td>
+            <td style="background: #0022dd">&nbsp;</td>
+            <td style="background: #0011ee">&nbsp;</td>
+            <td style="background: #0000ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from fuchsia at the left to aqua at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee11ff">&nbsp;</td>
+            <td style="background: #dd22ff">&nbsp;</td>
+            <td style="background: #cc33ff">&nbsp;</td>
+            <td style="background: #bb44ff">&nbsp;</td>
+            <td style="background: #aa55ff">&nbsp;</td>
+            <td style="background: #9966ff">&nbsp;</td>
+            <td style="background: #8877ff">&nbsp;</td>
+            <td style="background: #7788ff">&nbsp;</td>
+            <td style="background: #6699ff">&nbsp;</td>
+            <td style="background: #55aaff">&nbsp;</td>
+            <td style="background: #44bbff">&nbsp;</td>
+            <td style="background: #33ccff">&nbsp;</td>
+            <td style="background: #22ddff">&nbsp;</td>
+            <td style="background: #11eeff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee11ff">&nbsp;</td>
+            <td style="background: #dd22ff">&nbsp;</td>
+            <td style="background: #cc33ff">&nbsp;</td>
+            <td style="background: #bb44ff">&nbsp;</td>
+            <td style="background: #aa55ff">&nbsp;</td>
+            <td style="background: #9966ff">&nbsp;</td>
+            <td style="background: #8877ff">&nbsp;</td>
+            <td style="background: #7788ff">&nbsp;</td>
+            <td style="background: #6699ff">&nbsp;</td>
+            <td style="background: #55aaff">&nbsp;</td>
+            <td style="background: #44bbff">&nbsp;</td>
+            <td style="background: #33ccff">&nbsp;</td>
+            <td style="background: #22ddff">&nbsp;</td>
+            <td style="background: #11eeff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee11ff">&nbsp;</td>
+            <td style="background: #dd22ff">&nbsp;</td>
+            <td style="background: #cc33ff">&nbsp;</td>
+            <td style="background: #bb44ff">&nbsp;</td>
+            <td style="background: #aa55ff">&nbsp;</td>
+            <td style="background: #9966ff">&nbsp;</td>
+            <td style="background: #8877ff">&nbsp;</td>
+            <td style="background: #7788ff">&nbsp;</td>
+            <td style="background: #6699ff">&nbsp;</td>
+            <td style="background: #55aaff">&nbsp;</td>
+            <td style="background: #44bbff">&nbsp;</td>
+            <td style="background: #33ccff">&nbsp;</td>
+            <td style="background: #22ddff">&nbsp;</td>
+            <td style="background: #11eeff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: #ee11ff">&nbsp;</td>
+            <td style="background: #dd22ff">&nbsp;</td>
+            <td style="background: #cc33ff">&nbsp;</td>
+            <td style="background: #bb44ff">&nbsp;</td>
+            <td style="background: #aa55ff">&nbsp;</td>
+            <td style="background: #9966ff">&nbsp;</td>
+            <td style="background: #8877ff">&nbsp;</td>
+            <td style="background: #7788ff">&nbsp;</td>
+            <td style="background: #6699ff">&nbsp;</td>
+            <td style="background: #55aaff">&nbsp;</td>
+            <td style="background: #44bbff">&nbsp;</td>
+            <td style="background: #33ccff">&nbsp;</td>
+            <td style="background: #22ddff">&nbsp;</td>
+            <td style="background: #11eeff">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from yellow at the left to fuchsia at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #ffee11">&nbsp;</td>
+            <td style="background: #ffdd22">&nbsp;</td>
+            <td style="background: #ffcc33">&nbsp;</td>
+            <td style="background: #ffbb44">&nbsp;</td>
+            <td style="background: #ffaa55">&nbsp;</td>
+            <td style="background: #ff9966">&nbsp;</td>
+            <td style="background: #ff8877">&nbsp;</td>
+            <td style="background: #ff7788">&nbsp;</td>
+            <td style="background: #ff6699">&nbsp;</td>
+            <td style="background: #ff55aa">&nbsp;</td>
+            <td style="background: #ff44bb">&nbsp;</td>
+            <td style="background: #ff33cc">&nbsp;</td>
+            <td style="background: #ff22dd">&nbsp;</td>
+            <td style="background: #ff11ee">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #ffee11">&nbsp;</td>
+            <td style="background: #ffdd22">&nbsp;</td>
+            <td style="background: #ffcc33">&nbsp;</td>
+            <td style="background: #ffbb44">&nbsp;</td>
+            <td style="background: #ffaa55">&nbsp;</td>
+            <td style="background: #ff9966">&nbsp;</td>
+            <td style="background: #ff8877">&nbsp;</td>
+            <td style="background: #ff7788">&nbsp;</td>
+            <td style="background: #ff6699">&nbsp;</td>
+            <td style="background: #ff55aa">&nbsp;</td>
+            <td style="background: #ff44bb">&nbsp;</td>
+            <td style="background: #ff33cc">&nbsp;</td>
+            <td style="background: #ff22dd">&nbsp;</td>
+            <td style="background: #ff11ee">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #ffee11">&nbsp;</td>
+            <td style="background: #ffdd22">&nbsp;</td>
+            <td style="background: #ffcc33">&nbsp;</td>
+            <td style="background: #ffbb44">&nbsp;</td>
+            <td style="background: #ffaa55">&nbsp;</td>
+            <td style="background: #ff9966">&nbsp;</td>
+            <td style="background: #ff8877">&nbsp;</td>
+            <td style="background: #ff7788">&nbsp;</td>
+            <td style="background: #ff6699">&nbsp;</td>
+            <td style="background: #ff55aa">&nbsp;</td>
+            <td style="background: #ff44bb">&nbsp;</td>
+            <td style="background: #ff33cc">&nbsp;</td>
+            <td style="background: #ff22dd">&nbsp;</td>
+            <td style="background: #ff11ee">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: #ffee11">&nbsp;</td>
+            <td style="background: #ffdd22">&nbsp;</td>
+            <td style="background: #ffcc33">&nbsp;</td>
+            <td style="background: #ffbb44">&nbsp;</td>
+            <td style="background: #ffaa55">&nbsp;</td>
+            <td style="background: #ff9966">&nbsp;</td>
+            <td style="background: #ff8877">&nbsp;</td>
+            <td style="background: #ff7788">&nbsp;</td>
+            <td style="background: #ff6699">&nbsp;</td>
+            <td style="background: #ff55aa">&nbsp;</td>
+            <td style="background: #ff44bb">&nbsp;</td>
+            <td style="background: #ff33cc">&nbsp;</td>
+            <td style="background: #ff22dd">&nbsp;</td>
+            <td style="background: #ff11ee">&nbsp;</td>
+            <td style="background: #ff00ff">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+
+    <p>The following table should have four matching rows gradually changing from aqua at the left to yellow at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #11ffee">&nbsp;</td>
+            <td style="background: #22ffdd">&nbsp;</td>
+            <td style="background: #33ffcc">&nbsp;</td>
+            <td style="background: #44ffbb">&nbsp;</td>
+            <td style="background: #55ffaa">&nbsp;</td>
+            <td style="background: #66ff99">&nbsp;</td>
+            <td style="background: #77ff88">&nbsp;</td>
+            <td style="background: #88ff77">&nbsp;</td>
+            <td style="background: #99ff66">&nbsp;</td>
+            <td style="background: #aaff55">&nbsp;</td>
+            <td style="background: #bbff44">&nbsp;</td>
+            <td style="background: #ccff33">&nbsp;</td>
+            <td style="background: #ddff22">&nbsp;</td>
+            <td style="background: #eeff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #11ffee">&nbsp;</td>
+            <td style="background: #22ffdd">&nbsp;</td>
+            <td style="background: #33ffcc">&nbsp;</td>
+            <td style="background: #44ffbb">&nbsp;</td>
+            <td style="background: #55ffaa">&nbsp;</td>
+            <td style="background: #66ff99">&nbsp;</td>
+            <td style="background: #77ff88">&nbsp;</td>
+            <td style="background: #88ff77">&nbsp;</td>
+            <td style="background: #99ff66">&nbsp;</td>
+            <td style="background: #aaff55">&nbsp;</td>
+            <td style="background: #bbff44">&nbsp;</td>
+            <td style="background: #ccff33">&nbsp;</td>
+            <td style="background: #ddff22">&nbsp;</td>
+            <td style="background: #eeff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #11ffee">&nbsp;</td>
+            <td style="background: #22ffdd">&nbsp;</td>
+            <td style="background: #33ffcc">&nbsp;</td>
+            <td style="background: #44ffbb">&nbsp;</td>
+            <td style="background: #55ffaa">&nbsp;</td>
+            <td style="background: #66ff99">&nbsp;</td>
+            <td style="background: #77ff88">&nbsp;</td>
+            <td style="background: #88ff77">&nbsp;</td>
+            <td style="background: #99ff66">&nbsp;</td>
+            <td style="background: #aaff55">&nbsp;</td>
+            <td style="background: #bbff44">&nbsp;</td>
+            <td style="background: #ccff33">&nbsp;</td>
+            <td style="background: #ddff22">&nbsp;</td>
+            <td style="background: #eeff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: #00ffff">&nbsp;</td>
+            <td style="background: #11ffee">&nbsp;</td>
+            <td style="background: #22ffdd">&nbsp;</td>
+            <td style="background: #33ffcc">&nbsp;</td>
+            <td style="background: #44ffbb">&nbsp;</td>
+            <td style="background: #55ffaa">&nbsp;</td>
+            <td style="background: #66ff99">&nbsp;</td>
+            <td style="background: #77ff88">&nbsp;</td>
+            <td style="background: #88ff77">&nbsp;</td>
+            <td style="background: #99ff66">&nbsp;</td>
+            <td style="background: #aaff55">&nbsp;</td>
+            <td style="background: #bbff44">&nbsp;</td>
+            <td style="background: #ccff33">&nbsp;</td>
+            <td style="background: #ddff22">&nbsp;</td>
+            <td style="background: #eeff11">&nbsp;</td>
+            <td style="background: #ffff00">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/css-color/t421-rgb-values-meaning-b.xht b/css/css-color/t421-rgb-values-meaning-b.xht
index 760a441..b628d3e 100644
--- a/css/css-color/t421-rgb-values-meaning-b.xht
+++ b/css/css-color/t421-rgb-values-meaning-b.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#rgb-color" />
+		<link rel="match" href="t421-rgb-values-meaning-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Test that the color components in rgb colors are interpreted correctly." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t422-rgba-a0.0-a.xht b/css/css-color/t422-rgba-a0.0-a.xht
index fe3d5c3..28f41b8 100644
--- a/css/css-color/t422-rgba-a0.0-a.xht
+++ b/css/css-color/t422-rgba-a0.0-a.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#rgba-color" />
+		<link rel="match" href="t32-opacity-basic-0.0-a-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Opacity of 0.0 makes text transparent." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t422-rgba-values-meaning-b-ref.html b/css/css-color/t422-rgba-values-meaning-b-ref.html
new file mode 100644
index 0000000..08631f2
--- /dev/null
+++ b/css/css-color/t422-rgba-values-meaning-b-ref.html
@@ -0,0 +1,432 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    table { border-spacing: 0; padding: 0; border: none; }
+    td { border: none; padding: 0; }
+    td { width: 1.2em; height: 1.2em; }
+</style>
+<body>
+    <p>The following table should have four matching rows changing (in 5 steps) from red at the left to black at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: black">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from red at the left to white at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: white">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(255, 51, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 102, 102)">&nbsp;</td>
+            <td style="background: rgb(255, 153, 153)">&nbsp;</td>
+            <td style="background: rgb(255, 204, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(255, 51, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 102, 102)">&nbsp;</td>
+            <td style="background: rgb(255, 153, 153)">&nbsp;</td>
+            <td style="background: rgb(255, 204, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(255, 51, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 102, 102)">&nbsp;</td>
+            <td style="background: rgb(255, 153, 153)">&nbsp;</td>
+            <td style="background: rgb(255, 204, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(255, 51, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 102, 102)">&nbsp;</td>
+            <td style="background: rgb(255, 153, 153)">&nbsp;</td>
+            <td style="background: rgb(255, 204, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from green at the left to black at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: black">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from green at the left to white at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: white">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 255, 51)">&nbsp;</td>
+            <td style="background: rgb(102, 255, 102)">&nbsp;</td>
+            <td style="background: rgb(153, 255, 153)">&nbsp;</td>
+            <td style="background: rgb(204, 255, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 255, 51)">&nbsp;</td>
+            <td style="background: rgb(102, 255, 102)">&nbsp;</td>
+            <td style="background: rgb(153, 255, 153)">&nbsp;</td>
+            <td style="background: rgb(204, 255, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 255, 51)">&nbsp;</td>
+            <td style="background: rgb(102, 255, 102)">&nbsp;</td>
+            <td style="background: rgb(153, 255, 153)">&nbsp;</td>
+            <td style="background: rgb(204, 255, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 255, 51)">&nbsp;</td>
+            <td style="background: rgb(102, 255, 102)">&nbsp;</td>
+            <td style="background: rgb(153, 255, 153)">&nbsp;</td>
+            <td style="background: rgb(204, 255, 204)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from blue at the left to black at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: black">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from blue at the left to white at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: white">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 51, 255)">&nbsp;</td>
+            <td style="background: rgb(102, 102, 255)">&nbsp;</td>
+            <td style="background: rgb(153, 153, 255)">&nbsp;</td>
+            <td style="background: rgb(204, 204, 255)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 51, 255)">&nbsp;</td>
+            <td style="background: rgb(102, 102, 255)">&nbsp;</td>
+            <td style="background: rgb(153, 153, 255)">&nbsp;</td>
+            <td style="background: rgb(204, 204, 255)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 51, 255)">&nbsp;</td>
+            <td style="background: rgb(102, 102, 255)">&nbsp;</td>
+            <td style="background: rgb(153, 153, 255)">&nbsp;</td>
+            <td style="background: rgb(204, 204, 255)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 51, 255)">&nbsp;</td>
+            <td style="background: rgb(102, 102, 255)">&nbsp;</td>
+            <td style="background: rgb(153, 153, 255)">&nbsp;</td>
+            <td style="background: rgb(204, 204, 255)">&nbsp;</td>
+            <td style="background: rgb(255, 255, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from red at the left to green at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: lime">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: rgb(204, 51, 0)">&nbsp;</td>
+            <td style="background: rgb(153, 102, 0)">&nbsp;</td>
+            <td style="background: rgb(102, 153, 0)">&nbsp;</td>
+            <td style="background: rgb(51, 204, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from green at the left to blue at the right: (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: blue">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 255, 0)">&nbsp;</td>
+            <td style="background: rgb(0, 204, 51)">&nbsp;</td>
+            <td style="background: rgb(0, 153, 102)">&nbsp;</td>
+            <td style="background: rgb(0, 102, 153)">&nbsp;</td>
+            <td style="background: rgb(0, 51, 204)">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+
+    <p>The following table should have four matching rows changing (in 5 steps) from blue at the left to red at the right (plus a checkerboard at both ends to show where the rows are):</p>
+
+    <div style="background: red">
+    <table border="border">
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td style="background: rgb(0, 0, 255)">&nbsp;</td>
+            <td style="background: rgb(51, 0, 204)">&nbsp;</td>
+            <td style="background: rgb(102, 0, 153)">&nbsp;</td>
+            <td style="background: rgb(153, 0, 102)">&nbsp;</td>
+            <td style="background: rgb(204, 0, 51)">&nbsp;</td>
+            <td style="background: rgb(255, 0, 0)">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+    </table>
+    </div>
+</body>
diff --git a/css/css-color/t422-rgba-values-meaning-b.xht b/css/css-color/t422-rgba-values-meaning-b.xht
index 27e21cf..daea66c 100644
--- a/css/css-color/t422-rgba-values-meaning-b.xht
+++ b/css/css-color/t422-rgba-values-meaning-b.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#rgba-color" />
+		<link rel="match" href="t422-rgba-values-meaning-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Test that the color components in rgba colors are interpreted correctly." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t424-hsl-h-rotating-b-ref.html b/css/css-color/t424-hsl-h-rotating-b-ref.html
new file mode 100644
index 0000000..60585ea
--- /dev/null
+++ b/css/css-color/t424-hsl-h-rotating-b-ref.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    table { border-spacing: 0; padding: 0; border: none; }
+    td { border: none; padding: 0; }
+    td { width: 1.2em; height: 1.2em; }
+    #one { background: #FF0000; }
+    #two { background: #FFFF00; }
+    #three { background: #00FF00; }
+    #four { background: #00FFFF; }
+    #five { background: #0000FF; }
+    #six { background: #FF00FF; }
+</style>
+<body>
+    <p>Each column in the following table should have every cell the
+    same color, except for the checkerboard pattern at the right and
+    left used to indicate the row positions.</p>
+
+    <table>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+    </table>
+</body>
diff --git a/css/css-color/t424-hsl-h-rotating-b.xht b/css/css-color/t424-hsl-h-rotating-b.xht
index 08196e4..4d2d4cf 100644
--- a/css/css-color/t424-hsl-h-rotating-b.xht
+++ b/css/css-color/t424-hsl-h-rotating-b.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#hsl-color" />
+		<link rel="match" href="t424-hsl-h-rotating-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="H values in hsl() colors should be treated like angles, even when outside [0,360)" />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t425-hsla-h-rotating-b-ref.html b/css/css-color/t425-hsla-h-rotating-b-ref.html
new file mode 100644
index 0000000..2aec2f3
--- /dev/null
+++ b/css/css-color/t425-hsla-h-rotating-b-ref.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    table { border-spacing: 0; padding: 0; border: none; }
+    td { border: none; padding: 0; }
+    td { width: 1.2em; height: 1.2em; }
+    #one { background: #FF0000; }
+    #two { background: rgba(255, 255, 0, 0.8); }
+    #three { background: rgba(0, 255, 0, 0.4); }
+    #four { background: rgba(0, 255, 255, 0.6); }
+    #five { background: rgba(0, 0, 255, 0.8); }
+    #six { background: rgba(255, 0, 255, 0.4); }
+</style>
+<body>
+    <p>Each column in the following table should have every cell the
+    same color, except for the checkerboard pattern at the right and
+    left used to indicate the row positions.</p>
+
+    <table>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: white">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: black">&nbsp;</td>
+        </tr>
+        <tr>
+            <td style="background: black">&nbsp;</td>
+            <td id="one">&nbsp;</td>
+            <td id="two">&nbsp;</td>
+            <td id="three">&nbsp;</td>
+            <td id="four">&nbsp;</td>
+            <td id="five">&nbsp;</td>
+            <td id="six">&nbsp;</td>
+            <td style="background: white">&nbsp;</td>
+        </tr>
+    </table>
+
+</body>
diff --git a/css/css-color/t425-hsla-h-rotating-b.xht b/css/css-color/t425-hsla-h-rotating-b.xht
index b9339ad..0974733 100644
--- a/css/css-color/t425-hsla-h-rotating-b.xht
+++ b/css/css-color/t425-hsla-h-rotating-b.xht
@@ -6,6 +6,7 @@
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#hsl-color" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#hsla-color" />
+		<link rel="match" href="t425-hsla-h-rotating-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="H values in hsla() colors should be treated like angles, even when outside [0,360)" />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t425-hsla-onscreen-b.xht b/css/css-color/t425-hsla-onscreen-b.xht
index 3b5c0b5..60b9efc 100644
--- a/css/css-color/t425-hsla-onscreen-b.xht
+++ b/css/css-color/t425-hsla-onscreen-b.xht
@@ -5,11 +5,12 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#hsla-color" />
+		<link rel="match" href="t422-rgba-onscreen-b-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Test that hsla() does not behave like opacity and draw in an offscreen buffer." />
 		<style type="text/css"><![CDATA[
 		html, body { background: white; }
-		table { border-spacing: 0; padding: 0; border: none; }
+		table { border-spacing: 0 2px; padding: 0; border: none; }
 		td { border: none; padding: 0; }
 
 		td, div { height: 1.2em; }
diff --git a/css/css-color/t43-svg-keywords-a-ref.html b/css/css-color/t43-svg-keywords-a-ref.html
new file mode 100644
index 0000000..cd26c49
--- /dev/null
+++ b/css/css-color/t43-svg-keywords-a-ref.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    html, body { background: #dddddd; color: #000000; }
+    table { border-spacing: 0; padding: 0; border: none; }
+    td { border: none; padding: 0; }
+</style>
+<body>
+    <p>Each row in the following table (except the first and last, to show where the columns are) should have two cells with the same background color.  None of them should match the background of the page.</p>
+
+    <table>
+        <tr><td style="background: black">&nbsp;&nbsp;&nbsp;</td><td style="background: white">&nbsp;&nbsp;&nbsp;</td></tr>
+
+        <tr><td style="background: #f0f8ff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #faebd7" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00ffff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #7fffd4" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f0ffff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f5f5dc" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffe4c4" colspan=2>&nbsp;</td>
+        <tr><td style="background: #000000" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffebcd" colspan=2>&nbsp;</td>
+        <tr><td style="background: #0000ff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #8a2be2" colspan=2>&nbsp;</td>
+        <tr><td style="background: #a52a2a" colspan=2>&nbsp;</td>
+        <tr><td style="background: #deb887" colspan=2>&nbsp;</td>
+        <tr><td style="background: #5f9ea0" colspan=2>&nbsp;</td>
+        <tr><td style="background: #7fff00" colspan=2>&nbsp;</td>
+        <tr><td style="background: #d2691e" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff7f50" colspan=2>&nbsp;</td>
+        <tr><td style="background: #6495ed" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fff8dc" colspan=2>&nbsp;</td>
+        <tr><td style="background: #dc143c" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00ffff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00008b" colspan=2>&nbsp;</td>
+        <tr><td style="background: #008b8b" colspan=2>&nbsp;</td>
+        <tr><td style="background: #b8860b" colspan=2>&nbsp;</td>
+        <tr><td style="background: #a9a9a9" colspan=2>&nbsp;</td>
+        <tr><td style="background: #006400" colspan=2>&nbsp;</td>
+        <tr><td style="background: #a9a9a9" colspan=2>&nbsp;</td>
+        <tr><td style="background: #bdb76b" colspan=2>&nbsp;</td>
+        <tr><td style="background: #8b008b" colspan=2>&nbsp;</td>
+        <tr><td style="background: #556b2f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff8c00" colspan=2>&nbsp;</td>
+        <tr><td style="background: #9932cc" colspan=2>&nbsp;</td>
+        <tr><td style="background: #8b0000" colspan=2>&nbsp;</td>
+        <tr><td style="background: #e9967a" colspan=2>&nbsp;</td>
+        <tr><td style="background: #8fbc8f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #483d8b" colspan=2>&nbsp;</td>
+        <tr><td style="background: #2f4f4f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #2f4f4f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00ced1" colspan=2>&nbsp;</td>
+        <tr><td style="background: #9400d3" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff1493" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00bfff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #696969" colspan=2>&nbsp;</td>
+        <tr><td style="background: #696969" colspan=2>&nbsp;</td>
+        <tr><td style="background: #1e90ff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #b22222" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fffaf0" colspan=2>&nbsp;</td>
+        <tr><td style="background: #228b22" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff00ff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #dcdcdc" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f8f8ff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffd700" colspan=2>&nbsp;</td>
+        <tr><td style="background: #daa520" colspan=2>&nbsp;</td>
+        <tr><td style="background: #808080" colspan=2>&nbsp;</td>
+        <tr><td style="background: #008000" colspan=2>&nbsp;</td>
+        <tr><td style="background: #adff2f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #808080" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f0fff0" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff69b4" colspan=2>&nbsp;</td>
+        <tr><td style="background: #cd5c5c" colspan=2>&nbsp;</td>
+        <tr><td style="background: #4b0082" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fffff0" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f0e68c" colspan=2>&nbsp;</td>
+        <tr><td style="background: #e6e6fa" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fff0f5" colspan=2>&nbsp;</td>
+        <tr><td style="background: #7cfc00" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fffacd" colspan=2>&nbsp;</td>
+        <tr><td style="background: #add8e6" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f08080" colspan=2>&nbsp;</td>
+        <tr><td style="background: #e0ffff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fafad2" colspan=2>&nbsp;</td>
+        <tr><td style="background: #d3d3d3" colspan=2>&nbsp;</td>
+        <tr><td style="background: #90ee90" colspan=2>&nbsp;</td>
+        <tr><td style="background: #d3d3d3" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffb6c1" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffa07a" colspan=2>&nbsp;</td>
+        <tr><td style="background: #20b2aa" colspan=2>&nbsp;</td>
+        <tr><td style="background: #87cefa" colspan=2>&nbsp;</td>
+        <tr><td style="background: #778899" colspan=2>&nbsp;</td>
+        <tr><td style="background: #778899" colspan=2>&nbsp;</td>
+        <tr><td style="background: #b0c4de" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffffe0" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00ff00" colspan=2>&nbsp;</td>
+        <tr><td style="background: #32cd32" colspan=2>&nbsp;</td>
+        <tr><td style="background: #faf0e6" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff00ff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #800000" colspan=2>&nbsp;</td>
+        <tr><td style="background: #66cdaa" colspan=2>&nbsp;</td>
+        <tr><td style="background: #0000cd" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ba55d3" colspan=2>&nbsp;</td>
+        <tr><td style="background: #9370db" colspan=2>&nbsp;</td>
+        <tr><td style="background: #3cb371" colspan=2>&nbsp;</td>
+        <tr><td style="background: #7b68ee" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00fa9a" colspan=2>&nbsp;</td>
+        <tr><td style="background: #48d1cc" colspan=2>&nbsp;</td>
+        <tr><td style="background: #c71585" colspan=2>&nbsp;</td>
+        <tr><td style="background: #191970" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f5fffa" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffe4e1" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffe4b5" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffdead" colspan=2>&nbsp;</td>
+        <tr><td style="background: #000080" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fdf5e6" colspan=2>&nbsp;</td>
+        <tr><td style="background: #808000" colspan=2>&nbsp;</td>
+        <tr><td style="background: #6b8e23" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffa500" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff4500" colspan=2>&nbsp;</td>
+        <tr><td style="background: #da70d6" colspan=2>&nbsp;</td>
+        <tr><td style="background: #eee8aa" colspan=2>&nbsp;</td>
+        <tr><td style="background: #98fb98" colspan=2>&nbsp;</td>
+        <tr><td style="background: #afeeee" colspan=2>&nbsp;</td>
+        <tr><td style="background: #db7093" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffefd5" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffdab9" colspan=2>&nbsp;</td>
+        <tr><td style="background: #cd853f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffc0cb" colspan=2>&nbsp;</td>
+        <tr><td style="background: #dda0dd" colspan=2>&nbsp;</td>
+        <tr><td style="background: #b0e0e6" colspan=2>&nbsp;</td>
+        <tr><td style="background: #800080" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff0000" colspan=2>&nbsp;</td>
+        <tr><td style="background: #bc8f8f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #4169e1" colspan=2>&nbsp;</td>
+        <tr><td style="background: #8b4513" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fa8072" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f4a460" colspan=2>&nbsp;</td>
+        <tr><td style="background: #2e8b57" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fff5ee" colspan=2>&nbsp;</td>
+        <tr><td style="background: #a0522d" colspan=2>&nbsp;</td>
+        <tr><td style="background: #c0c0c0" colspan=2>&nbsp;</td>
+        <tr><td style="background: #87ceeb" colspan=2>&nbsp;</td>
+        <tr><td style="background: #6a5acd" colspan=2>&nbsp;</td>
+        <tr><td style="background: #708090" colspan=2>&nbsp;</td>
+        <tr><td style="background: #708090" colspan=2>&nbsp;</td>
+        <tr><td style="background: #fffafa" colspan=2>&nbsp;</td>
+        <tr><td style="background: #00ff7f" colspan=2>&nbsp;</td>
+        <tr><td style="background: #4682b4" colspan=2>&nbsp;</td>
+        <tr><td style="background: #d2b48c" colspan=2>&nbsp;</td>
+        <tr><td style="background: #008080" colspan=2>&nbsp;</td>
+        <tr><td style="background: #d8bfd8" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ff6347" colspan=2>&nbsp;</td>
+        <tr><td style="background: #40e0d0" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ee82ee" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f5deb3" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffffff" colspan=2>&nbsp;</td>
+        <tr><td style="background: #f5f5f5" colspan=2>&nbsp;</td>
+        <tr><td style="background: #ffff00" colspan=2>&nbsp;</td>
+        <tr><td style="background: #9acd32" colspan=2>&nbsp;</td>
+
+        <tr><td style="background: white">&nbsp;&nbsp;&nbsp;</td><td style="background: black">&nbsp;&nbsp;&nbsp;</td></tr>
+    </table>
+</body>
diff --git a/css/css-color/t43-svg-keywords-a.xht b/css/css-color/t43-svg-keywords-a.xht
index 69b6aef..75bbf4f 100644
--- a/css/css-color/t43-svg-keywords-a.xht
+++ b/css/css-color/t43-svg-keywords-a.xht
@@ -5,6 +5,7 @@
 		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
 		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
 		<link rel="help" href="http://www.w3.org/TR/css3-color/#html4" />
+		<link rel="match" href="t43-svg-keywords-a-ref.html" />
 		<meta name="flags" content="" />
 		<meta name="assert" content="Test that the HTML4 color keywords have the correct values." />
 		<style type="text/css"><![CDATA[
diff --git a/css/css-color/t451-system-colors-a.xht b/css/css-color/t451-system-colors-a.xht
deleted file mode 100644
index bb6f13e..0000000
--- a/css/css-color/t451-system-colors-a.xht
+++ /dev/null
@@ -1,74 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Test: system colors</title>
-		<link rel="author" title="L. David Baron" href="https://dbaron.org/" />
-		<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
-		<link rel="help" href="http://www.w3.org/TR/css3-color/#css2-system" />
-		<meta name="flags" content="" />
-		<meta name="assert" content="That system colors are displayed as described." />
-		<style type="text/css"><![CDATA[
-		html, body { background: #9f9; }
-		p, div { border: medium solid red; background: red; color: red; }
-		]]></style>
-	</head>
-	<body>
-
-		<p style="border: none; background: white; color: black">Nothing in this document should be red unless the operating environment provides red as a color for one of the things mentioned.</p>
-
-		<p style="border: none; background: white; color: black">Every line of text in this document should be a color distinct enough from its background to be legible.</p>
-
-		<p style="border-top: 4px solid ButtonHighlight; border-right: 4px solid ButtonShadow; border-bottom: 4px solid ButtonShadow; border-left: 4px solid ButtonHighlight; background: ButtonFace; color: ButtonText">
-			This should have the text color, background color, and solid border that makes it look like the closest simulation of a raised button (with the light source in the upper left) in the operating environment that is possible using a box with a single border around it.
-		</p>
-
-		<div style="border-top: 2px solid ThreeDHighlight; border-right: 2px solid ThreeDDarkShadow; border-bottom: 2px solid ThreeDDarkShadow; border-left: 2px solid ThreeDHighlight">
-			<div style="border-top: 2px solid ThreeDLightShadow; border-right: 2px solid ThreeDShadow; border-bottom: 2px solid ThreeDShadow; border-left: 2px solid ThreeDLightShadow; background: ThreeDFace; color: ButtonText">
-				<!-- is ButtonText an appropriate color to use here? -->
-				This should have the background color and <em>two</em> solid borders that makes it look like the closest simulation of a raised button (with the light source in the upper left) in the operating environment that is possible using a box with a two-layer border around it.
-			</div>
-		</div>
-
-		<p style="border: medium solid ActiveBorder; background: ActiveCaption; color: CaptionText">If the operating environment provides such colors, this should have the text color and background color of an active window caption, and a border the color of the border of active windows.</p>
-
-		<p style="border: medium solid InactiveBorder; background: InactiveCaption; color: InactiveCaptionText">If the operating environment provides such colors, this should have the text color and background color of an inactive window caption, and a border the color of the border of inactive windows.</p>
-
-		<p style="border: none; background: AppWorkspace; color: WindowText">
-			This should have the background color used by multiple document interface (MDI) applications, if the operating environment provides such a color.
-		</p>
-
-		<p style="background: Background; color: WindowText; border: none">
-			This should have the background color of the desktop, if the operating environment provides such a color.
-		</p>
-
-		<p style="background: Window; color: GrayText; border: none">
-			This should have the text color of disabled controls, if the operating environment provides such a color.
-		</p>
-
-		<p style="background: Highlight; color: HighlightText; border: none">
-			This should have the text color and background color of highlighted items, if the operating system provides such colors.
-		</p>
-
-		<p style="background: InfoBackground; color: InfoText; border: none">
-			This should have the text color and background color of tooltips, if the operating system provides such colors.
-		</p>
-
-		<p style="background: Menu; color: MenuText; border: none">
-			This should have the text color and background color of menus, if the operating system provides such colors.
-		</p>
-
-		<p style="border: none; background: white; color: black">The following (empty) square should have the background color of the area that scrollbar sliders slide over, if the operating environment provides such a color:</p>
-		<p style="background: Scrollbar; color: CaptionText; border: none; margin-left: auto; margin-right: auto; width: 2em; height: 2em;">
-			&nbsp;
-		</p>
-
-		<p style="background: Window; color: WindowText; border: none">
-			This should have the text color and background color of text fields, if the operating system provides such colors.
-		</p>
-
-		<p style="border: 2px solid WindowFrame; background: white; color: black">
-			This should have a border the color of a window's frame, if the operating system provides such a color.
-		</p>
-
-	</body>
-</html>
diff --git a/css/css-conditional/test_group_insertRule.html b/css/css-conditional/test_group_insertRule.html
index 85edc2a..f34245e 100644
--- a/css/css-conditional/test_group_insertRule.html
+++ b/css/css-conditional/test_group_insertRule.html
@@ -11,26 +11,6 @@
 <style id="style">
 @media print {}
 </style>
-<script id="metadata_cache">/*
-{
-  "rule_type": {},
-  "rule_length": {},
-  "insert_import_throws": {},
-  "insert_index_throws1": {},
-  "insert_index_throws2": {},
-  "insert_media_succeed": {},
-  "insert_style_succeed": {},
-  "insert_bad_media_throw": {},
-  "insert_empty_throw": {},
-  "insert_garbage_after_media_throw": {},
-  "insert_garbage_after_style_throw": {},
-  "insert_two_media_throw": {},
-  "insert_style_media_throw": {},
-  "insert_media_style_throw": {},
-  "insert_two_style_throw": {},
-  "insert_retval": {}
-}
-*/</script>
 </head>
 <body onload="run()">
 <div id=log></div>
@@ -240,4 +220,3 @@
 </script>
 </body>
 </html>
-
diff --git a/css/css-contain/OWNERS b/css/css-contain/OWNERS
new file mode 100644
index 0000000..9d556e0
--- /dev/null
+++ b/css/css-contain/OWNERS
@@ -0,0 +1,2 @@
+@tabatkins
+@frivoal
diff --git a/css/css-contain/contain-layout-001.html b/css/css-contain/contain-layout-001.html
new file mode 100644
index 0000000..decea3b
--- /dev/null
+++ b/css/css-contain/contain-layout-001.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment on non-atomic inlines</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment does not apply to non atomic inlines">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+span {
+  contain: layout;
+  height: 100vh; /*If layout containment applies, the span becomes a BFC, height applies, and knocks SS off the page */
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><span>PA</span>SS</div>
diff --git a/css/css-contain/contain-layout-002.html b/css/css-contain/contain-layout-002.html
new file mode 100644
index 0000000..8bbf951
--- /dev/null
+++ b/css/css-contain/contain-layout-002.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: layout containment on ruby-base</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+  <meta name=assert content="layout containment does not apply to ruby-base">
+  <link rel="match" href="reference/contain-layout-002-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rb {
+  contain: layout;
+  display: ruby-base;
+  font-family: ahem;
+  font-size: 20px;
+  line-height: 1;
+}
+rb::before {
+  content: "X";
+  color: green;
+}
+rb::after {
+  content: "X";
+  color: white;
+  position: absolute;
+  top:0; left: 0;
+}
+</style>
+
+<p>This test passes if you can see a green box below.
+<div><ruby><rb></rb></ruby></div>
diff --git a/css/css-contain/contain-layout-003.html b/css/css-contain/contain-layout-003.html
new file mode 100644
index 0000000..2d59bd8
--- /dev/null
+++ b/css/css-contain/contain-layout-003.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: layout containment on ruby-base-container</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+  <meta name=assert content="layout containment does not apply to ruby-base-container">
+  <link rel="match" href="reference/contain-layout-002-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rbc {
+  contain: layout;
+  display: ruby-base-container;
+  font-family: ahem;
+  font-size: 20px;
+  line-height: 1;
+}
+rbc::before {
+  content: "X";
+  color: green;
+}
+rbc::after {
+  content: "X";
+  color: white;
+  position: absolute;
+  top:0; left: 0;
+}
+</style>
+
+<p>This test passes if you can see a green box below.
+<div><ruby><rbc></rbc></ruby></div>
diff --git a/css/css-contain/contain-layout-004.html b/css/css-contain/contain-layout-004.html
new file mode 100644
index 0000000..ef74af9
--- /dev/null
+++ b/css/css-contain/contain-layout-004.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: layout containment on ruby-text-container</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+  <meta name=assert content="layout containment does not apply to ruby-text-container">
+  <link rel="match" href="reference/contain-layout-004-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rtc {
+  contain: layout;
+  display: ruby-text-container;
+  font-family: ahem;
+  font-size: 20px;
+  line-height: 1;
+}
+rtc::before {
+  content: "X";
+  color: green;
+}
+rtc::after {
+  content: "X";
+  color: white;
+  position: absolute;
+  top:0; left: 0;
+}
+</style>
+
+<p>This test passes if you can see a green box below.
+<div><ruby><rtc></rtc></ruby></div>
diff --git a/css/css-contain/contain-layout-005.html b/css/css-contain/contain-layout-005.html
new file mode 100644
index 0000000..a12aef5
--- /dev/null
+++ b/css/css-contain/contain-layout-005.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: layout containment on ruby-text</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+  <meta name=assert content="layout containment does not apply to ruby-text">
+  <link rel="match" href="reference/contain-layout-005-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rt {
+  contain: layout;
+  display: ruby-text;
+  font-family: ahem;
+  font-size: 20px;
+  line-height: 1;
+}
+rt::before {
+  content: "X";
+  color: green;
+}
+rt::after {
+  content: "X";
+  color: white;
+  position: absolute;
+  top:0; left: 0;
+}
+</style>
+
+<p>This test passes if you can see a green box below.
+<div><ruby><rt></rt></ruby></div>
diff --git a/css/css-contain/contain-layout-006.html b/css/css-contain/contain-layout-006.html
new file mode 100644
index 0000000..57dc518
--- /dev/null
+++ b/css/css-contain/contain-layout-006.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Layout containment absolutely positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-layout">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Layout containment makes an element to act as containing block for absolutely positioned descendants.">
+<style>
+#contain-layout {
+  contain: layout;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#abspos {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-layout">
+  <div id="abspos"></div>
+</div>
diff --git a/css/css-contain/contain-layout-007.html b/css/css-contain/contain-layout-007.html
new file mode 100644
index 0000000..39be5fe
--- /dev/null
+++ b/css/css-contain/contain-layout-007.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Layout containment fixed positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-layout">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Layout containment makes an element to act as containing block for fixed positioned descendants.">
+<style>
+#contain-layout {
+  contain: layout;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#fixed {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-layout">
+  <div id="fixed"></div>
+</div>
diff --git a/css/css-contain/contain-layout-breaks-001.html b/css/css-contain/contain-layout-breaks-001.html
new file mode 100644
index 0000000..a85cf2c
--- /dev/null
+++ b/css/css-contain/contain-layout-breaks-001.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: layout containment and forced breaks</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="layout containment allows forced breaks.">
+  <link rel="match" href="reference/contain-style-breaks-004-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-layout">
+  <link rel=help href="https://drafts.csswg.org/css-break-3/#forced-break">
+
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+  line-height: 1;
+  height: 4em;
+  column-fill: auto;
+}
+div > div:last-of-type {
+  break-before: column;
+}
+#test > div {
+  contain: layout;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article id=ref>
+  <div>
+    <div>A<br>A</div>
+    <div>A<br>A</div>
+  </div>
+</article>
+<article id=test>
+  <div>
+    <div>A<br>A</div>
+    <div>A<br>A</div>
+  </div>
+</article>
+<!--
+Having two blocks to avoid making browsers that don't support forced break at all fail.
+Since containment is supposed to have no effect, failing such browsers would not be useful.
+-->
diff --git a/css/css-contain/contain-layout-breaks-002.html b/css/css-contain/contain-layout-breaks-002.html
new file mode 100644
index 0000000..4398387
--- /dev/null
+++ b/css/css-contain/contain-layout-breaks-002.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: layout containment and forced breaks</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="forced breaks within layout containment do not propagate to the parent.">
+  <link rel="match" href="reference/contain-layout-breaks-002-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-layout">
+  <link rel=help href="https://drafts.csswg.org/css-break-3/#forced-break">
+
+<style>
+article {
+  columns: 2 20px;
+  float: left;
+  height: 50px;
+  column-fill: auto;
+}
+div > div {
+  border-top: 20px solid orange;
+  break-before: column;
+}
+article > div {
+  border-top: 20px solid orange;
+  contain: layout;
+}
+</style>
+
+<p>Test passes if there are two orange boxes below.
+<article>
+  <div>
+    <div></div>
+  </div>
+</article>
diff --git a/css/css-contain/contain-paint-001.html b/css/css-contain/contain-paint-001.html
new file mode 100644
index 0000000..37609e6
--- /dev/null
+++ b/css/css-contain/contain-paint-001.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment use the padding edge</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment clips at the padding edge, not content edge, and takes corner clipping into account">
+  <link rel="match" href="reference/contain-paint-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  background: blue;
+  padding: 50px;
+  border-radius: 100px;
+  border: white 50px solid;
+  contain: paint;
+}
+div::before {
+ content:"";
+ display: block;
+ background: green;
+ width: 100px;
+ height: 100px;
+}
+div::after {
+  content:"";
+  display: block;
+  background: red;
+  width: 50px;
+  height: 50px;
+  float: left;
+  margin-top: 36px;
+  margin-left: -86px;
+}
+</style>
+
+<p>Test passes if there is a green square in a rounded blue box, and no red.
+<div></div>
diff --git a/css/css-contain/contain-paint-002.html b/css/css-contain/contain-paint-002.html
new file mode 100644
index 0000000..ff59a23
--- /dev/null
+++ b/css/css-contain/contain-paint-002.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment on non-atomic inlines</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment does not apply to non atomic inlines">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+span {
+  contain: paint;
+  width: 0; /* Because if the test fails, the span may get blockified, and which would make wide enough to hold the PASS */
+}
+span::after {
+  content: "PASS";
+  position: absolute;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><span></span></div>
diff --git a/css/css-contain/contain-paint-003.html b/css/css-contain/contain-paint-003.html
new file mode 100644
index 0000000..7db9c20
--- /dev/null
+++ b/css/css-contain/contain-paint-003.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment applies to the principal box for tables</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment applies to the principal box, which is the table wrapper box for tables">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+table { contain: paint; }
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<table>
+  <caption>PASS</caption>
+</table>
diff --git a/css/css-contain/contain-paint-004.html b/css/css-contain/contain-paint-004.html
new file mode 100644
index 0000000..a3941b3
--- /dev/null
+++ b/css/css-contain/contain-paint-004.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment applies to the principal box for list items</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment applies to the principal box, which for list items excludes the list marker">
+  <link rel="match" href="reference/contain-paint-004-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+li { contain: paint; }
+</style>
+
+<p>This test passes if you can see no number below.
+<ol>
+  <li>
+  <li>
+  <li>
+  <li>
+  <li>
+  <li>
+</ol>
diff --git a/css/css-contain/contain-paint-005.html b/css/css-contain/contain-paint-005.html
new file mode 100644
index 0000000..71ceae4
--- /dev/null
+++ b/css/css-contain/contain-paint-005.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment on ruby-base</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment does not apply to ruby-base">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rb {
+  contain: paint;
+  display: ruby-base;
+  width: 0; /* Because if the test fails, this may get blockified, and which could make wide enough to hold the PASS */
+}
+rb::after {
+  content: "PASS";
+  position: absolute;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rb></rb></ruby></div>
diff --git a/css/css-contain/contain-paint-006.html b/css/css-contain/contain-paint-006.html
new file mode 100644
index 0000000..1349c18
--- /dev/null
+++ b/css/css-contain/contain-paint-006.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment on ruby-base-container</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment does not apply to ruby-base-container">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rbc {
+  contain: paint;
+  display: ruby-base-container;
+  width: 0; /* Because if the test fails, this may get blockified, and which could make wide enough to hold the PASS */
+}
+rbc::after {
+  content: "PASS";
+  position: absolute;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rbc></rbc></ruby></div>
diff --git a/css/css-contain/contain-paint-007.html b/css/css-contain/contain-paint-007.html
new file mode 100644
index 0000000..b02bd53
--- /dev/null
+++ b/css/css-contain/contain-paint-007.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment on ruby-text-container</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment does not apply to ruby-text-container">
+  <link rel="match" href="reference/contain-paint-007-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rtc {
+  contain: paint;
+  display: ruby-text-container;
+  width: 0; /* Because if the test fails, this may get blockified, and which could make wide enough to hold the PASS */
+  font-size: 1rem;
+}
+rtc::after {
+  content: "PASS";
+  position: absolute;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rtc></rtc></ruby></div>
diff --git a/css/css-contain/contain-paint-008.html b/css/css-contain/contain-paint-008.html
new file mode 100644
index 0000000..8fd7ca2
--- /dev/null
+++ b/css/css-contain/contain-paint-008.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment on ruby-text</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="paint containment does not apply to ruby-text">
+  <link rel="match" href="reference/contain-paint-008-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+
+<style>
+rt {
+  contain: paint;
+  display: ruby-text;
+  font-size: 1rem;
+}
+rt::after {
+  content: "PASS";
+
+  /* Doing the following instead of position:absolute to move it out into the area that would be clipped
+     because Firefox clips absolutely positioned content of rt even though it does not support
+     containment.
+     Since this technique works also, not need to trigger a false negative.
+   */
+  position: relative;
+  left: 4ch;
+  font-family: monospace;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rt></rt></ruby></div>
diff --git a/css/css-contain/contain-paint-009.html b/css/css-contain/contain-paint-009.html
new file mode 100644
index 0000000..34ca8d7
--- /dev/null
+++ b/css/css-contain/contain-paint-009.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Paint containment absolutely positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Paint containment makes an element to act as containing block for absolutely positioned descendants.">
+<style>
+#contain-paint {
+  contain: paint;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#abspos {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-paint">
+  <div id="abspos"></div>
+</div>
diff --git a/css/css-contain/contain-paint-010.html b/css/css-contain/contain-paint-010.html
new file mode 100644
index 0000000..a05ce58
--- /dev/null
+++ b/css/css-contain/contain-paint-010.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Paint containment fixed positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Paint containment makes an element to act as containing block for fixed positioned descendants.">
+<style>
+#contain-paint {
+  contain: paint;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#fixed {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-paint">
+  <div id="fixed"></div>
+</div>
diff --git a/css/css-contain/contain-size-001.html b/css/css-contain/contain-size-001.html
new file mode 100644
index 0000000..5ed871b
--- /dev/null
+++ b/css/css-contain/contain-size-001.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: size containment on non-atomic inlines</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="size containment does not apply to non atomic inlines">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-size">
+
+<style>
+div { overflow: hidden; }
+span { contain: size; }
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><span>PASS</span></div>
diff --git a/css/css-contain/contain-size-002.html b/css/css-contain/contain-size-002.html
new file mode 100644
index 0000000..e844c67
--- /dev/null
+++ b/css/css-contain/contain-size-002.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: size containment on ruby-base</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="size containment does not to apply ruby-base, which is an internatl ruby element">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-size">
+  <link rel=help href="https://drafts.csswg.org/css-display-3/#internal-ruby-element">
+
+<style>
+div {
+  overflow: hidden;
+  position: absolute;
+}
+rb {
+  contain: size;
+  display: ruby-base;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rb>PASS</rb></ruby></div>
diff --git a/css/css-contain/contain-size-003.html b/css/css-contain/contain-size-003.html
new file mode 100644
index 0000000..95827dc
--- /dev/null
+++ b/css/css-contain/contain-size-003.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: size containment on ruby-base-container</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="size containment does not to apply ruby-base-container, which is an internatl ruby element">
+  <link rel="match" href="reference/contain-size-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-size">
+  <link rel=help href="https://drafts.csswg.org/css-display-3/#internal-ruby-element">
+
+<style>
+div {
+  overflow: hidden;
+  position: absolute;
+}
+rbc {
+  contain: size;
+  display: ruby-base-container;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rbc>PASS</rbc></ruby></div>
diff --git a/css/css-contain/contain-size-004.html b/css/css-contain/contain-size-004.html
new file mode 100644
index 0000000..b043310
--- /dev/null
+++ b/css/css-contain/contain-size-004.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: size containment on ruby-text-container</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="size containment does not to apply ruby-text-container, which is an internatl ruby element">
+  <link rel="match" href="reference/contain-size-004-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-size">
+  <link rel=help href="https://drafts.csswg.org/css-display-3/#internal-ruby-element">
+
+<style>
+div {
+  overflow: hidden;
+  position: absolute;
+}
+rtc {
+  contain: size;
+  display: ruby-text-container;
+  font-size: 1rem;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rtc>PASS</rtc></ruby></div>
diff --git a/css/css-contain/contain-size-005.html b/css/css-contain/contain-size-005.html
new file mode 100644
index 0000000..9e35a85
--- /dev/null
+++ b/css/css-contain/contain-size-005.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: size containment on ruby-text</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="size containment does not to apply ruby-text, which is an internatl ruby element">
+  <link rel="match" href="reference/contain-size-005-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-size">
+  <link rel=help href="https://drafts.csswg.org/css-display-3/#internal-ruby-element">
+
+<style>
+div {
+  overflow: hidden;
+  position: absolute;
+}
+rt {
+  contain: size;
+  display: ruby-text;
+  font-size: 1rem;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rt>PASS</rt></ruby></div>
diff --git a/css/css-contain/contain-size-breaks-001.html b/css/css-contain/contain-size-breaks-001.html
new file mode 100644
index 0000000..d8538f6
--- /dev/null
+++ b/css/css-contain/contain-size-breaks-001.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: size containment and fragmentation</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+  <meta name=assert content="size containment makes element monolithic">
+  <link rel="match" href="reference/contain-size-breaks-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+  <link rel=help href="https://drafts.csswg.org/css-break-3/#monolithic">
+
+<style>
+article {
+  height: 2.5em;
+  column-gap: 0;
+  columns: 3 1em;
+  width: 3em;
+  column-fill: auto;
+
+  font-size: 40px;
+  font-family: ahem;
+  line-height: 1;
+}
+div {
+  contain: size;
+  height: 5em;
+  color: orange;
+}
+</style>
+
+<p>This test passes if there is an orange rectangle below. If the shape is not a rectangle, the test fails.
+
+<article>
+<div>A A A A A</div>
+</article>
+
+<!--
+If the element is monolythic, it may:
+* overflow and be a 1x5 em rectangle
+* be sliced and be 2 contigious 1x2.5 em rectangles, appearing as a single
+  2x2.5 em rectangle.
+Either way, it will always appear as a single rectangle.
+
+If the element is not monolithic, it will fit 2 As in the first column, 2 in
+the second column, and the fith will be overflowing the div, either into the
+thrid column, or into the bottom of the second one.
+Either way, this will not look like a rectangle.
+-->
diff --git a/css/css-contain/contain-style-breaks-001.html b/css/css-contain/contain-style-breaks-001.html
new file mode 100644
index 0000000..9e5c27a
--- /dev/null
+++ b/css/css-contain/contain-style-breaks-001.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and break-inside:avoid</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment is (no longer) supposed to have any effect on the break-inside property">
+  <link rel="match" href="reference/contain-style-breaks-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+}
+div {
+  page-break-inside: avoid;
+  break-inside: avoid;
+}
+#test > div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article id=ref>
+  <div>
+  A<br>
+  A<br>
+  A<br>
+  A
+  <div>
+</article>
+<article id=test>
+  <div>
+  A<br>
+  A<br>
+  A<br>
+  A
+  <div>
+</article>
+<!--
+Having two blocks to avoid making browsers that don't support the property at all fail.
+Since containment is supposed to have no effect, failing such browsers would not be useful.
+-->
diff --git a/css/css-contain/contain-style-breaks-002.html b/css/css-contain/contain-style-breaks-002.html
new file mode 100644
index 0000000..1b089c8
--- /dev/null
+++ b/css/css-contain/contain-style-breaks-002.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and break-inside:avoid</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment is not (any longer) supposed to have any effect on the break-inside property. Same as -001, applying containment on the parent.">
+  <link rel="match" href="reference/contain-style-breaks-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+}
+div > div {
+  page-break-inside: avoid;
+  break-inside: avoid;
+}
+#test > div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article id=ref>
+  <div>
+    <div>
+    A<br>
+    A<br>
+    A<br>
+    A
+    <div>
+  </div>
+</article>
+<article id=test>
+  <div>
+    <div>
+    A<br>
+    A<br>
+    A<br>
+    A
+    <div>
+  </div>
+</article>
+<!--
+Having two blocks to avoid making browsers that don't support the property at all fail.
+Since containment is supposed to have no effect, failing such browsers would not be useful.
+-->
diff --git a/css/css-contain/contain-style-breaks-003.html b/css/css-contain/contain-style-breaks-003.html
new file mode 100644
index 0000000..2cfd590
--- /dev/null
+++ b/css/css-contain/contain-style-breaks-003.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and break-inside:avoid</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment is not (any longer) supposed to have any effect on the break-inside property. Same as -001, applying break-inside on the parent.">
+  <link rel="match" href="reference/contain-style-breaks-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+}
+article > div {
+  page-break-inside: avoid;
+  break-inside: avoid;
+}
+#test div > div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article id=ref>
+  <div>
+    <div>
+    A<br>
+    A<br>
+    A<br>
+    A
+    <div>
+  </div>
+</article>
+<article id=test>
+  <div>
+    <div>
+    A<br>
+    A<br>
+    A<br>
+    A
+    <div>
+  </div>
+</article>
+<!--
+Having two blocks to avoid making browsers that don't support the property at all fail.
+Since containment is supposed to have no effect, failing such browsers would not be useful.
+-->
diff --git a/css/css-contain/contain-style-breaks-004.html b/css/css-contain/contain-style-breaks-004.html
new file mode 100644
index 0000000..98e79a6
--- /dev/null
+++ b/css/css-contain/contain-style-breaks-004.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and break-before</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment is not (any longer) supposed to have any effect on the break-before property.">
+  <link rel="match" href="reference/contain-style-breaks-004-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+  line-height: 1;
+  height: 4em;
+  column-fill: auto;
+}
+div > div:last-of-type {
+  break-before: column;
+}
+#test > div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article id=ref>
+  <div>
+    <div>A<br>A</div>
+    <div>A<br>A</div>
+  </div>
+</article>
+<article id=test>
+  <div>
+    <div>A<br>A</div>
+    <div>A<br>A</div>
+  </div>
+</article>
+<!--
+Having two blocks to avoid making browsers that don't support the property at all fail.
+Since containment is supposed to have no effect, failing such browsers would not be useful.
+-->
diff --git a/css/css-contain/contain-style-breaks-005.html b/css/css-contain/contain-style-breaks-005.html
new file mode 100644
index 0000000..bfcd1fb
--- /dev/null
+++ b/css/css-contain/contain-style-breaks-005.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and break-after</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment is not (any longer) supposed to have any effect on the break-after property.">
+  <link rel="match" href="reference/contain-style-breaks-004-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+  line-height: 1;
+  height: 4em;
+  column-fill: auto;
+}
+div > div:first-of-type {
+  break-after: column;
+}
+#test > div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article id=ref>
+  <div>
+    <div>A<br>A</div>
+    <div>A<br>A</div>
+  </div>
+</article>
+<article id=test>
+  <div>
+    <div>A<br>A</div>
+    <div>A<br>A</div>
+  </div>
+</article>
+<!--
+Having two blocks to avoid making browsers that don't support the property at all fail.
+Since containment is supposed to have no effect, failing such browsers would not be useful.
+-->
diff --git a/css/css-contain/contain-style-counters.html b/css/css-contain/contain-style-counters.html
index 73de6dc..55bf97f 100644
--- a/css/css-contain/contain-style-counters.html
+++ b/css/css-contain/contain-style-counters.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <title>CSS Containment Test: contain:style for counters</title>
-<link rel="help" href="https://drafts.csswg.org/css-contain/#containment-style">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-style">
 <link rel="match" href="contain-style-counters-ref.html">
 <style>
     #t1 { contain: style }
diff --git a/css/css-contain/counter-scoping-001.html b/css/css-contain/counter-scoping-001.html
new file mode 100644
index 0000000..f7d40dc
--- /dev/null
+++ b/css/css-contain/counter-scoping-001.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and counter-increment</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="counter-increment is scoped to the subtree and creates a new counter at the root of the subtree">
+  <link rel="match" href="reference/counter-scoping-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+div {
+  contain: style;
+   counter-increment: n;
+}
+div::before, div::after {
+  content: counters(n, '.') " ";
+}
+div::after {
+  counter-increment: n 2;
+}
+</style>
+
+<p>Test passes if the text below is "1 1.2" (not including the quotation marks).<p>
+<div></div>
+
diff --git a/css/css-contain/counter-scoping-002.html b/css/css-contain/counter-scoping-002.html
new file mode 100644
index 0000000..0af148c
--- /dev/null
+++ b/css/css-contain/counter-scoping-002.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and counter-set</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="counter-set is scoped to the subtree and creates a new counter at the root of the subtree">
+  <link rel="match" href="reference/counter-scoping-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+  <link rel=help href="https://drafts.csswg.org/css-lists-3/#propdef-counter-set">
+
+<style>
+div {
+  contain: style;
+  counter-set: n;
+}
+div::before, div::after {
+  content: counters(n, '.') " ";
+}
+div::after {
+  counter-set: n 2;
+}
+</style>
+
+<p>Test passes if the text below is "1 1.2" (not including the quotation marks).<p>
+<div></div>
+
diff --git a/css/css-contain/counter-scoping-003.html b/css/css-contain/counter-scoping-003.html
new file mode 100644
index 0000000..c4a004f
--- /dev/null
+++ b/css/css-contain/counter-scoping-003.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and subtree root</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="When considering the effects of the scoped property on elements inside the subtree, the element at the base of the subtree is treated as if it was the root of the document">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+  <link rel="match" href="reference/counter-scoping-003-ref.html">
+
+<style>
+div {
+  contain: style;
+  counter-increment: c 123;
+}
+span::before {
+  content: counter(c);
+  counter-increment: c 1;
+}
+</style>
+
+<p>Test passes if the text below is "1 2" (not including the quotation marks).<p>
+<div>
+  <span></span>
+  <span></span>
+</div>
+
diff --git a/css/css-contain/quote-scoping-001.html b/css/css-contain/quote-scoping-001.html
new file mode 100644
index 0000000..b440fdc
--- /dev/null
+++ b/css/css-contain/quote-scoping-001.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and open-quote</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment cause the open-quote value of the content property are scoped to the element's subtree">
+  <link rel="match" href="reference/quote-scoping-001-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+
+div {
+  quotes: "A" "Z" "1" "9";
+}
+div::before, span::before {
+  content: open-quote;
+}
+div::after {
+  content: close-quote;
+}
+div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if the text below is "A1Z" (not including the quotation marks).<p>
+<div><span></span></div>
diff --git a/css/css-contain/quote-scoping-002.html b/css/css-contain/quote-scoping-002.html
new file mode 100644
index 0000000..d79ec2d
--- /dev/null
+++ b/css/css-contain/quote-scoping-002.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and close-quote</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment cause the close-quote value of the content property are scoped to the element's subtree">
+  <link rel="match" href="reference/quote-scoping-002-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+
+div {
+  quotes: "A" "Z" "1" "9";
+}
+div::before {
+  content: open-quote;
+}
+div::after, span::after {
+  content: close-quote;
+}
+div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if the text below is "A9Z" (not including the quotation marks).<p>
+<div><span></span></div>
diff --git a/css/css-contain/quote-scoping-003.html b/css/css-contain/quote-scoping-003.html
new file mode 100644
index 0000000..ee0420e
--- /dev/null
+++ b/css/css-contain/quote-scoping-003.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and no-open-quote</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment cause the no-open-quote value of the content property are scoped to the element's subtree">
+  <link rel="match" href="reference/quote-scoping-003-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+
+div {
+  quotes: "A" "Z" "1" "9";
+}
+div::before{
+  content: open-quote;
+}
+
+span::before {
+  content: no-open-quote;
+}
+div::after {
+  content: close-quote;
+}
+div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if the text below is "AZ" (not including the quotation marks).<p>
+<div><span></span></div>
diff --git a/css/css-contain/quote-scoping-004.html b/css/css-contain/quote-scoping-004.html
new file mode 100644
index 0000000..b314370
--- /dev/null
+++ b/css/css-contain/quote-scoping-004.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: style containment and no-close-quote</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+  <meta name=assert content="style containment cause the no-close-quote value of the content property are scoped to the element's subtree">
+  <link rel="match" href="reference/quote-scoping-003-ref.html">
+  <link rel=help href="https://drafts.csswg.org/css-contain-1/#containment-style">
+
+<style>
+
+div {
+  quotes: "A" "Z" "1" "9";
+}
+div::before{
+  content: open-quote;
+}
+
+span::after {
+  content: no-close-quote;
+}
+div::after {
+  content: close-quote;
+}
+div {
+  contain: style;
+}
+</style>
+
+<p>Test passes if the text below is "AZ" (not including the quotation marks).<p>
+<div><span></span></div>
diff --git a/css/css-contain/reference/contain-layout-002-ref.html b/css/css-contain/reference/contain-layout-002-ref.html
new file mode 100644
index 0000000..5fbeb3a
--- /dev/null
+++ b/css/css-contain/reference/contain-layout-002-ref.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test referene</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+
+<style>
+rb {
+  font-family: ahem;
+  font-size: 20px;
+  line-height: 1;
+}
+rb::before {
+  content: "X";
+  color: green;
+}
+rb::after {
+  content: "X";
+  color: white;
+  position: absolute;
+  top:0; left: 0;
+}
+</style>
+
+<p>This test passes if you can see a green box below.
+<div><ruby><rb></rb></ruby></div>
diff --git a/css/css-contain/reference/contain-layout-004-ref.html b/css/css-contain/reference/contain-layout-004-ref.html
new file mode 100644
index 0000000..f3279e9
--- /dev/null
+++ b/css/css-contain/reference/contain-layout-004-ref.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+
+<style>
+rtc {
+  display: ruby-text-container;
+  font-family: ahem;
+  font-size: 20px;
+  line-height: 1;
+}
+rtc::before {
+  content: "X";
+  color: green;
+}
+rtc::after {
+  content: "X";
+  color: white;
+  position: absolute;
+  top:0; left: 0;
+}
+</style>
+
+<p>This test passes if you can see a green box below.
+<div><ruby><rtc></rtc></ruby></div>
diff --git a/css/css-contain/reference/contain-layout-005-ref.html b/css/css-contain/reference/contain-layout-005-ref.html
new file mode 100644
index 0000000..0b2792f
--- /dev/null
+++ b/css/css-contain/reference/contain-layout-005-ref.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+
+<style>
+rt {
+  display: ruby-text;
+  font-family: ahem;
+  font-size: 20px;
+  line-height: 1;
+}
+rt::before {
+  content: "X";
+  color: green;
+}
+rt::after {
+  content: "X";
+  color: white;
+  position: absolute;
+  top:0; left: 0;
+}
+</style>
+
+<p>This test passes if you can see a green box below.
+<div><ruby><rt></rt></ruby></div>
diff --git a/css/css-contain/reference/contain-layout-breaks-002-ref.html b/css/css-contain/reference/contain-layout-breaks-002-ref.html
new file mode 100644
index 0000000..9ecc3d2
--- /dev/null
+++ b/css/css-contain/reference/contain-layout-breaks-002-ref.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="">
+
+<style>
+article {
+  columns: 2 20px;
+  float: left;
+  height: 50px;
+  column-fill: auto;
+}
+article > div {
+  break-before: column;
+  border-top: 20px solid orange;
+}
+</style>
+
+<p>Test passes if there are two orange boxes below.
+<article>
+    <div></div>
+    <div></div>
+</article>
diff --git a/css/css-contain/reference/contain-paint-001-ref.html b/css/css-contain/reference/contain-paint-001-ref.html
new file mode 100644
index 0000000..1ed4eca
--- /dev/null
+++ b/css/css-contain/reference/contain-paint-001-ref.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment use the padding edge</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  background: blue;
+  padding: 50px;
+  border-radius: 100px;
+  border: white 50px solid;
+  overflow: hidden
+}
+div::before {
+  content:"";
+  display: block;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+div::after {
+  content:"";
+  display: block;
+  background: red;
+  width: 50px;
+  height: 50px;
+  float: left;
+  margin-top: 36px;
+  margin-left: -86px;
+}
+</style>
+
+<p>Test passes if there is a green square in a rounded blue box, and no red.
+<div></div>
diff --git a/css/css-contain/reference/contain-paint-004-ref.html b/css/css-contain/reference/contain-paint-004-ref.html
new file mode 100644
index 0000000..c03b4c8
--- /dev/null
+++ b/css/css-contain/reference/contain-paint-004-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>This test passes if you can see no number below.
diff --git a/css/css-contain/reference/contain-paint-007-ref.html b/css/css-contain/reference/contain-paint-007-ref.html
new file mode 100644
index 0000000..3048d55
--- /dev/null
+++ b/css/css-contain/reference/contain-paint-007-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<style>
+rtc {
+  display: ruby-text-container;
+  font-size: 1rem;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rtc>PASS</rtc></ruby></div>
diff --git a/css/css-contain/reference/contain-paint-008-ref.html b/css/css-contain/reference/contain-paint-008-ref.html
new file mode 100644
index 0000000..50c3bd7
--- /dev/null
+++ b/css/css-contain/reference/contain-paint-008-ref.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test: paint containment on ruby-text</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<style>
+rt {
+  display: ruby-text;
+  font-size: 1rem;
+  position: relative;
+  left: 4ch;
+  font-family: monospace;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rt>PASS</rt></ruby></div>
diff --git a/css/css-contain/reference/contain-size-001-ref.html b/css/css-contain/reference/contain-size-001-ref.html
new file mode 100644
index 0000000..9b669db
--- /dev/null
+++ b/css/css-contain/reference/contain-size-001-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>This test passes if you can see the word PASS below.
+<div>PASS</div>
diff --git a/css/css-contain/reference/contain-size-004-ref.html b/css/css-contain/reference/contain-size-004-ref.html
new file mode 100644
index 0000000..3048d55
--- /dev/null
+++ b/css/css-contain/reference/contain-size-004-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<style>
+rtc {
+  display: ruby-text-container;
+  font-size: 1rem;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rtc>PASS</rtc></ruby></div>
diff --git a/css/css-contain/reference/contain-size-005-ref.html b/css/css-contain/reference/contain-size-005-ref.html
new file mode 100644
index 0000000..001fc90
--- /dev/null
+++ b/css/css-contain/reference/contain-size-005-ref.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<style>
+div { overflow: hidden; }
+rt {
+  display: ruby-text;
+  font-size: 1rem;
+}
+</style>
+
+<p>This test passes if you can see the word PASS below.
+<div><ruby><rt>PASS</rt></ruby></div>
diff --git a/css/css-contain/reference/contain-size-breaks-001-ref.html b/css/css-contain/reference/contain-size-breaks-001-ref.html
new file mode 100644
index 0000000..6fa1e47
--- /dev/null
+++ b/css/css-contain/reference/contain-size-breaks-001-ref.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+  <meta name=flags content="ahem">
+
+<style>
+article {
+  height: 2.5em;
+  column-gap: 0;
+  columns: 3 1em;
+  width: 3em;
+  column-fill: auto;
+
+  font-size: 40px;
+  font-family: ahem;
+  line-height: 1;
+}
+div {
+  background: orange;
+  padding-top: 5em;
+}
+</style>
+
+<p>This test passes if there is an orange rectangle below. If the shape is not a rectangle, the test fails.
+
+<article>
+<div></div>
+</article>
diff --git a/css/css-contain/reference/contain-style-breaks-001-ref.html b/css/css-contain/reference/contain-style-breaks-001-ref.html
new file mode 100644
index 0000000..2c0b758
--- /dev/null
+++ b/css/css-contain/reference/contain-style-breaks-001-ref.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+}
+div {
+  page-break-inside: avoid;
+  break-inside: avoid;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article>
+  <div>
+  A<br>
+  A<br>
+  A<br>
+  A
+  <div>
+</article>
+<article>
+  <div>
+  A<br>
+  A<br>
+  A<br>
+  A
+  <div>
+</article>
diff --git a/css/css-contain/reference/contain-style-breaks-004-ref.html b/css/css-contain/reference/contain-style-breaks-004-ref.html
new file mode 100644
index 0000000..eaf4d56
--- /dev/null
+++ b/css/css-contain/reference/contain-style-breaks-004-ref.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+article {
+  columns: 2 1ch;
+  column-gap: 0;
+  float: left;
+  font-family: monospace;
+  margin-right: 3em;
+  line-height: 1;
+  height: 4em;
+  column-fill: auto;
+}
+div:last-of-type {
+  break-before: column;
+}
+</style>
+
+<p>Test passes if there are two identical blocks “A” letters below.
+<article>
+  <div>A<br>A</div>
+  <div>A<br>A</div>
+</article>
+<article>
+  <div>A<br>A</div>
+  <div>A<br>A</div>
+</article>
diff --git a/css/css-contain/reference/counter-scoping-001-ref.html b/css/css-contain/reference/counter-scoping-001-ref.html
new file mode 100644
index 0000000..2ebbb9a
--- /dev/null
+++ b/css/css-contain/reference/counter-scoping-001-ref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>Test passes if the text below is "1 1.2" (not including the quotation marks).<p>
+<div>1 1.2</div>
+
diff --git a/css/css-contain/reference/counter-scoping-003-ref.html b/css/css-contain/reference/counter-scoping-003-ref.html
new file mode 100644
index 0000000..49e7b85
--- /dev/null
+++ b/css/css-contain/reference/counter-scoping-003-ref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>Test passes if the text below is "1 2" (not including the quotation marks).<p>
+<div>1 2</div>
+
diff --git a/css/css-contain/reference/quote-scoping-001-ref.html b/css/css-contain/reference/quote-scoping-001-ref.html
new file mode 100644
index 0000000..0b0e9c4
--- /dev/null
+++ b/css/css-contain/reference/quote-scoping-001-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<p>Test passes if the text below is "A1Z" (not including the quotation marks).<p>
+<div>A1Z</div>
+
diff --git a/css/css-contain/reference/quote-scoping-002-ref.html b/css/css-contain/reference/quote-scoping-002-ref.html
new file mode 100644
index 0000000..8b846e2
--- /dev/null
+++ b/css/css-contain/reference/quote-scoping-002-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>Test passes if the text below is "A9Z" (not including the quotation marks).<p>
+<div>A9Z</span></div>
diff --git a/css/css-contain/reference/quote-scoping-003-ref.html b/css/css-contain/reference/quote-scoping-003-ref.html
new file mode 100644
index 0000000..2bc8d67
--- /dev/null
+++ b/css/css-contain/reference/quote-scoping-003-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html lang=en>
+  <meta charset=utf-8>
+  <title>CSS-contain test reference</title>
+  <link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+
+<p>Test passes if the text below is "AZ" (not including the quotation marks).<p>
+<div>AZ</div>
diff --git a/css/css-content/OWNERS b/css/css-content/OWNERS
new file mode 100644
index 0000000..ae23ef8
--- /dev/null
+++ b/css/css-content/OWNERS
@@ -0,0 +1 @@
+@dauwhe
diff --git a/css/css-content/attr-case-insensitive-ref.html b/css/css-content/attr-case-insensitive-ref.html
new file mode 100644
index 0000000..9b718b9
--- /dev/null
+++ b/css/css-content/attr-case-insensitive-ref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<meta charset=utf-8>
+aaabbbccc
diff --git a/css/css-content/attr-case-insensitive.html b/css/css-content/attr-case-insensitive.html
new file mode 100644
index 0000000..e0cf16b
--- /dev/null
+++ b/css/css-content/attr-case-insensitive.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Names specified in attr for a content property are case-insensitive in HTML documents</title>
+<link rel=match href=/css/css-content/attr-case-insensitive-ref.html>
+<link rel="help" href="https://drafts.csswg.org/css-content-3/#content-property" />
+<style type="text/css">
+div#gencon:before { content: attr(foo) attr(Foo) attr(fOO)
+                             attr(bar) attr(Bar) attr(bAR)
+                             attr(baz) attr(Baz) attr(BAZ) }
+</style>
+<body>
+<div id="gencon" foo="a" Bar="b" bAZ="c"></div>
+</body>
diff --git a/css/css-content/element-replacement-ref.html b/css/css-content/element-replacement-ref.html
new file mode 100644
index 0000000..d301f1d
--- /dev/null
+++ b/css/css-content/element-replacement-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>Reference for the content CSS attribute can replace an element's contents</title>
+
+<img src='resources/rect.svg' />
diff --git a/css/css-content/element-replacement.html b/css/css-content/element-replacement.html
new file mode 100644
index 0000000..a78e9f7
--- /dev/null
+++ b/css/css-content/element-replacement.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>The content CSS attribute can replace an element's contents</title>
+<link rel="match" href="element-replacement-ref.html" />
+<link rel="help" href="https://drafts.csswg.org/css-content-3/#content-property" />
+<meta name="assert" content"This test checks that the CSS content propertly can replace a normal element's contents" />
+
+<style>
+p {
+  margin: 0;
+  content: url('resources/rect.svg');
+}
+</style>
+
+<p>This text should not be visible</p>
diff --git a/css/css-content/resources/rect.svg b/css/css-content/resources/rect.svg
new file mode 100644
index 0000000..d5de6b4
--- /dev/null
+++ b/css/css-content/resources/rect.svg
@@ -0,0 +1,3 @@
+<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
+  <rect x="0" y="0" width="100" height="100" fill="green" />
+</svg>
diff --git a/css/css-counter-styles/broken-symbols-ref.htm b/css/css-counter-styles/broken-symbols-ref.htm
new file mode 100644
index 0000000..0247f65
--- /dev/null
+++ b/css/css-counter-styles/broken-symbols-ref.htm
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<ol><li>Should have "1." as bullet point.</ol>
diff --git a/css/css-counter-styles/broken-symbols.htm b/css/css-counter-styles/broken-symbols.htm
new file mode 100644
index 0000000..d449161
--- /dev/null
+++ b/css/css-counter-styles/broken-symbols.htm
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: invalid counter-style symbols</title>
+<link rel="author" title="Anthony Ramine" href="mailto:n.oxyde@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#typedef-symbol">
+<link rel="match" href="broken-symbols-ref.htm">
+<style type="text/css">
+  @counter-style a {
+    system: alphabetic;
+    symbols: ⓐ inherit;
+  }
+</style>
+<ol style="list-style-type: a"><li>Should have "1." as bullet point.</ol>
diff --git a/css/css-display/display-contents-before-after-001.html b/css/css-display/display-contents-before-after-001.html
index e6272da..65fc9fe 100644
--- a/css/css-display/display-contents-before-after-001.html
+++ b/css/css-display/display-contents-before-after-001.html
@@ -5,6 +5,8 @@
 <link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
 <link rel="match" href="display-contents-pass-ref.html">
 <style>
+    /* Disable kerning because kerning may differ for different node tree. */
+    html { font-kerning: none; font-feature-settings: "kern" off; }
     div { display: contents }
     .p::before { content: "P" }
     .a::before { content: "A" }
diff --git a/css/css-display/display-contents-before-after-002.html b/css/css-display/display-contents-before-after-002.html
index 4c5947f..5860a73 100644
--- a/css/css-display/display-contents-before-after-002.html
+++ b/css/css-display/display-contents-before-after-002.html
@@ -5,6 +5,8 @@
 <link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
 <link rel="match" href="display-contents-pass-ref.html">
 <style>
+    /* Disable kerning because kerning may differ for different node tree. */
+    html { font-kerning: none; font-feature-settings: "kern" off; }
     div::before {
         display: contents;
         border: 100px solid red;
diff --git a/css/css-display/display-contents-before-after-003.html b/css/css-display/display-contents-before-after-003.html
index 772ad44..3045142 100644
--- a/css/css-display/display-contents-before-after-003.html
+++ b/css/css-display/display-contents-before-after-003.html
@@ -5,6 +5,8 @@
 <link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
 <link rel="match" href="display-contents-pass-ref.html">
 <style>
+    /* Disable kerning because kerning may differ for different node tree. */
+    html { font-kerning: none; font-feature-settings: "kern" off; }
     .flex { display: inline-flex; flex-direction: column }
     .flex::before { display: contents; content: "A" }
     .flex::after { display: contents; content: "S" }
diff --git a/css/css-display/display-contents-button.html b/css/css-display/display-contents-button.html
new file mode 100644
index 0000000..3711972
--- /dev/null
+++ b/css/css-display/display-contents-button.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents and HTML button element</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox-html">
+<link rel="match" href="display-contents-pass-ref.html">
+<style>
+  /* Disable kerning because kerning may differ for different node tree. */
+  html { font-kerning: none; font-feature-settings: "kern" off; }
+  button {
+    all: initial;
+    font-kerning: none; font-feature-settings: "kern" off;
+    border: 10px solid red;
+    display: contents;
+  }
+</style>
+<p>You should see the word PASS below.</p>
+<button>P<!---->A<!---->S<!---->S</button>
diff --git a/css/css-display/display-contents-details-001-ref.html b/css/css-display/display-contents-details-001-ref.html
new file mode 100644
index 0000000..fc078b8
--- /dev/null
+++ b/css/css-display/display-contents-details-001-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<details>
+  <div>
+    <summary>summary</summary>
+    details
+  </div>
+</details>
diff --git a/css/css-display/display-contents-details-001.html b/css/css-display/display-contents-details-001.html
new file mode 100644
index 0000000..b1cefa6
--- /dev/null
+++ b/css/css-display/display-contents-details-001.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test: display: contents under a details element doesn't prevent content from getting suppressed</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel="match" href="display-contents-details-001-ref.html">
+<details>
+  <div style="display:contents">
+    <summary>summary</summary>
+    details
+  </div>
+</details>
diff --git a/css/css-display/display-contents-details.html b/css/css-display/display-contents-details.html
new file mode 100644
index 0000000..b6a6540
--- /dev/null
+++ b/css/css-display/display-contents-details.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents and HTML details and summary elements</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox-html">
+<link rel="match" href="display-contents-pass-ref.html">
+<style>
+  details, summary {
+    all: initial;
+    border: 10px solid red;
+    display: contents;
+  }
+</style>
+<p>You should see the word PASS below.</p>
+P<details open><summary>A</summary><span>S</span></details>S
diff --git a/css/css-display/display-contents-fieldset-nested-legend-ref.html b/css/css-display/display-contents-fieldset-nested-legend-ref.html
new file mode 100644
index 0000000..6f547b3
--- /dev/null
+++ b/css/css-display/display-contents-fieldset-nested-legend-ref.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+<title>CSS Test Reference</title>
+<fieldset style="color: green">P<legend style="padding: 0">legend</legend>ASS</fieldset>
diff --git a/css/css-display/display-contents-fieldset-nested-legend.html b/css/css-display/display-contents-fieldset-nested-legend.html
new file mode 100644
index 0000000..a3bd2fc
--- /dev/null
+++ b/css/css-display/display-contents-fieldset-nested-legend.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<meta charset="utf-8">
+<link rel="match" href="display-contents-fieldset-nested-legend-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox">
+<link rel="help" href="https://drafts.csswg.org/css-display/#valdef-display-contents">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1427292">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+<title>CSS Test: display: contents on legend</title>
+<style>
+fieldset {
+  color: red;
+}
+.contents {
+  display: contents;
+  color: green;
+  border: 10px solid red;
+}
+</style>
+<fieldset><legend class="contents">P<legend>legend</legend>ASS</legend></fieldset>
diff --git a/css/css-display/display-contents-fieldset.html b/css/css-display/display-contents-fieldset.html
new file mode 100644
index 0000000..f38a376
--- /dev/null
+++ b/css/css-display/display-contents-fieldset.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents and HTML fieldset and legend elements</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox-html">
+<link rel="match" href="display-contents-pass-ref.html">
+<style>
+  /* Disable kerning because kerning may differ for different node tree. */
+  html { font-kerning: none; font-feature-settings: "kern" off; }
+  fieldset, legend {
+    all: initial;
+    border: 10px solid red;
+    display: contents;
+  }
+</style>
+<p>You should see the word PASS below.</p>
+P<fieldset>A<legend>S</legend>S</fieldset>
diff --git a/css/css-display/display-contents-first-letter-002.html b/css/css-display/display-contents-first-letter-002.html
new file mode 100644
index 0000000..8d1d857
--- /dev/null
+++ b/css/css-display/display-contents-first-letter-002.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents and ::first-letter inheritance</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-letter-pseudo">
+<link rel="match" href="display-contents-pass-green-no-red-ref.html">
+<style>
+  div { color: red }
+  div::first-letter { background: transparent /* To trigger first-letter pseudo element */ }
+  span { color: green; display: contents; background-color: red }
+</style>
+<p>You should see the word PASS in green and no red below.</p>
+<div><span>PASS</span></div>
diff --git a/css/css-display/display-contents-first-line-002.html b/css/css-display/display-contents-first-line-002.html
new file mode 100644
index 0000000..ae90023
--- /dev/null
+++ b/css/css-display/display-contents-first-line-002.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents and ::first-line inheritance</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-line-pseudo">
+<link rel="match" href="display-contents-pass-green-no-red-ref.html">
+<style>
+  span { color: green }
+  .contents { display: contents }
+  #container::first-line { color: red }
+</style>
+<p>You should see the word PASS in green and no red below.</p>
+<div id="container">
+  <span class="contents">P</span><span>ASS</span>
+</div>
diff --git a/css/css-display/display-contents-line-height-ref.html b/css/css-display/display-contents-line-height-ref.html
new file mode 100644
index 0000000..d06c63f
--- /dev/null
+++ b/css/css-display/display-contents-line-height-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<style>
+  span { font-size: 40px; }
+</style>
+<p>The two lines below should not overlap.</p>
+<span>Line 1</span><br><span>Line 2</span>
diff --git a/css/css-display/display-contents-line-height.html b/css/css-display/display-contents-line-height.html
new file mode 100644
index 0000000..31fd5a6
--- /dev/null
+++ b/css/css-display/display-contents-line-height.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents font-size should affect line-height</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel="match" href="display-contents-line-height-ref.html">
+<style>
+  span {
+    display: contents;
+    font-size: 40px;
+  }
+</style>
+<p>The two lines below should not overlap.</p>
+<span>Line 1</span><br><span>Line 2</span>
diff --git a/css/css-display/display-contents-parsing-001.html b/css/css-display/display-contents-parsing-001.html
new file mode 100644
index 0000000..085d12b
--- /dev/null
+++ b/css/css-display/display-contents-parsing-001.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests that the 'contents' value for the 'display' property is correctly parsed</title>
+<link rel="help" href="https://drafts.csswg.org/css-display/#box-generation">
+<link rel="author" href="mailto:ecobos@igalia.com" title="Emilio Cobos Álvarez">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .contents {
+    display: contents;
+  }
+
+  .contents-then-block {
+    display: contents;
+    display: block;
+  }
+
+  .content {
+    display: content;
+  }
+</style>
+<div class="contents" id="contentsElement"></div>
+<div class="content" id="bogusContentsElement"></div>
+<div class="contents-then-block" id="contentsThenBlockElement"></div>
+<script>
+test(function() {
+  var contentsElement = document.getElementById("contentsElement");
+  var bogusContentsElement = document.getElementById("bogusContentsElement");
+  var contentsThenBlockElement = document.getElementById("contentsThenBlockElement");
+
+  assert_equals(getComputedStyle(contentsElement).getPropertyValue("display"), "contents");
+  assert_equals(getComputedStyle(bogusContentsElement).getPropertyValue("display"), "block");
+  assert_equals(getComputedStyle(contentsThenBlockElement).getPropertyValue("display"), "block");
+
+  var element = document.createElement("div");
+  document.body.appendChild(element);
+  assert_equals(getComputedStyle(element).getPropertyValue("display"), "block");
+  element.style.display = "contents";
+  assert_equals(getComputedStyle(element).getPropertyValue("display"), "contents");
+  element.style.display = "block";
+  assert_equals(getComputedStyle(element).getPropertyValue("display"), "block");
+});
+</script>
diff --git a/css/css-display/display-contents-pass-ref.html b/css/css-display/display-contents-pass-ref.html
index de26e6d..82321b9 100644
--- a/css/css-display/display-contents-pass-ref.html
+++ b/css/css-display/display-contents-pass-ref.html
@@ -2,5 +2,9 @@
 <meta charset="utf-8">
 <title>CSS Reftest Reference</title>
 <link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<style>
+/* Disable kerning because kerning may differ for different node tree. */
+html { font-kerning: none; font-feature-settings: "kern" off; }
+</style>
 <p>You should see the word PASS below.</p>
-P<span>A</span>S<span>S</span>
+PASS
diff --git a/css/css-display/display-contents-replaced-001-ref.html b/css/css-display/display-contents-replaced-001-ref.html
deleted file mode 100644
index ff807c7..0000000
--- a/css/css-display/display-contents-replaced-001-ref.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>CSS Reftest Reference</title>
-<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
-<style>
-    button, img, input, textarea {
-        display: contents;
-        border: 10px solid green
-    }
-</style>
-<p>You should see five form inputs, and an orange image, with green border below.</p>
-<button>Button</button>
-<input type="text" value="text"></input>
-<input type="file"></input>
-<input type="password" value="password"></input>
-<textarea>textarea</textarea>
-<img src="support/swatch-orange.png">
diff --git a/css/css-display/display-contents-replaced-001.html b/css/css-display/display-contents-replaced-001.html
deleted file mode 100644
index 22d3995..0000000
--- a/css/css-display/display-contents-replaced-001.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>CSS Display: display:contents and replaced elements</title>
-<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
-<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
-<link rel="match" href="display-contents-replaced-001-ref.html">
-<style>
-    button, img, input, textarea {
-        display: contents;
-        border: 10px solid green
-    }
-</style>
-<p>You should see five form inputs, and an orange image, with green border below.</p>
-<button>Button</button>
-<input type="text" value="text"></input>
-<input type="file"></input>
-<input type="password" value="password"></input>
-<textarea>textarea</textarea>
-<img src="support/swatch-orange.png">
diff --git a/css/css-display/display-contents-suppression-dynamic-001-ref.html b/css/css-display/display-contents-suppression-dynamic-001-ref.html
new file mode 100644
index 0000000..07a5663
--- /dev/null
+++ b/css/css-display/display-contents-suppression-dynamic-001-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<p>Test passes if you see nothing below.</p>
diff --git a/css/css-display/display-contents-suppression-dynamic-001.html b/css/css-display/display-contents-suppression-dynamic-001.html
new file mode 100644
index 0000000..5007e1f
--- /dev/null
+++ b/css/css-display/display-contents-suppression-dynamic-001.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test: display: contents unboxing works in presence of dynamic changes to the tree.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#unbox">
+<link rel="match" href="display-contents-suppression-dynamic-001-ref.html">
+<p>Test passes if you see nothing below.</p>
+<textarea style="display: contents">
+  FAIL
+</textarea>
+<script>
+  let textarea = document.querySelector("textarea");
+  textarea.offsetTop;
+  textarea.appendChild(document.createTextNode("FAIL"));
+</script>
diff --git a/css/css-display/display-contents-svg-anchor-child.html b/css/css-display/display-contents-svg-anchor-child.html
new file mode 100644
index 0000000..d0119d7
--- /dev/null
+++ b/css/css-display/display-contents-svg-anchor-child.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents on SVG anchor child</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox-svg">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<svg>
+  <a>
+    <tspan style="display:contents;color:green">Text</tspan>
+  </a>
+  <text>
+    <a>
+      <tspan style="display:contents;color:green">Text</tspan>
+    </a>
+  </text>
+</svg>
+<script>
+  test(() => {}, "Loading this page should not cause a crash.");
+</script>
diff --git a/css/css-display/display-contents-svg-elements-ref.html b/css/css-display/display-contents-svg-elements-ref.html
new file mode 100644
index 0000000..b691e0e
--- /dev/null
+++ b/css/css-display/display-contents-svg-elements-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<p>You should see the word PASS below.</p>
+<div style="font: 16px monospace">
+  <svg>
+    <text x="0" y="20">P</text>
+    <text x="10" y="20">A</text>
+    <text x="20" y="20">S</text>
+    <text x="30" y="20">S</text>
+  </svg>
+</div>
diff --git a/css/css-display/display-contents-svg-elements.html b/css/css-display/display-contents-svg-elements.html
new file mode 100644
index 0000000..7ccc92f
--- /dev/null
+++ b/css/css-display/display-contents-svg-elements.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents and SVG elements</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox-svg">
+<link rel="match" href="display-contents-svg-elements-ref.html">
+<p>You should see the word PASS below.</p>
+<div style="font: 16px monospace">
+  <svg>
+    <defs><text x="20" y="20" id="S">S</text></defs>
+    <text y="40" style="display:contents">FAIL</text>
+    <svg style="display:contents;opacity:0">
+      <text x="0" y="20">P</text>
+    </svg>
+    <g style="display:contents;opacity:0">
+      <text x="10" y="20"><tspan dx="2000" style="display:contents;opacity:0">A</tspan></text>
+    </g>
+    <use xlink:href="#S" style="display:contents;opacity:0"></use>
+    <text x="30" y="20">S</text>
+  </svg>
+  <svg style="display:contents"><text y="40">FAIL</text></svg>
+</div>
diff --git a/css/css-display/display-contents-svg-switch-child.html b/css/css-display/display-contents-svg-switch-child.html
new file mode 100644
index 0000000..7befdb4
--- /dev/null
+++ b/css/css-display/display-contents-svg-switch-child.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents on SVG switch child</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox-svg">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<svg>
+  <switch>
+    <tspan style="display:contents;color:green">Text</tspan>
+  </switch>
+</svg>
+<script>
+  test(() => {}, "Loading this page should not cause a crash.");
+</script>
diff --git a/css/css-display/display-contents-td-001.html b/css/css-display/display-contents-td-001.html
index 9867c0d..5585024 100644
--- a/css/css-display/display-contents-td-001.html
+++ b/css/css-display/display-contents-td-001.html
@@ -5,6 +5,8 @@
 <link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
 <link rel="match" href="display-contents-pass-ref.html">
 <style>
+    /* Disable kerning because kerning may differ for different node tree. */
+    html { font-kerning: none; font-feature-settings: "kern" off; }
     td {
         display: contents;
         padding-right: 3em
diff --git a/css/css-display/display-contents-text-inherit-002.html b/css/css-display/display-contents-text-inherit-002.html
new file mode 100644
index 0000000..51bf968
--- /dev/null
+++ b/css/css-display/display-contents-text-inherit-002.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: Apply white-space property of display:contents element to inline children</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel="match" href="display-contents-text-inherit-ref.html">
+<p>The words "Two" and "lines" should not be on the same line.</p>
+<div id="div" style="background-color:red">
+  <span style="display:contents;white-space:pre-line">Two
+    lines
+  </span>
+</div>
+<script>
+  document.body.offsetTop;
+  div.style.backgroundColor = "transparent";
+</script>
diff --git a/css/css-display/display-contents-text-inherit-ref.html b/css/css-display/display-contents-text-inherit-ref.html
new file mode 100644
index 0000000..163efd4
--- /dev/null
+++ b/css/css-display/display-contents-text-inherit-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<p>The words "Two" and "lines" should not be on the same line.</p>
+Two<br>
+lines
diff --git a/css/css-display/display-contents-text-inherit.html b/css/css-display/display-contents-text-inherit.html
new file mode 100644
index 0000000..b7e769c
--- /dev/null
+++ b/css/css-display/display-contents-text-inherit.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: Apply display:contents text properties to text children</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel="match" href="display-contents-text-inherit-ref.html">
+<style>
+  div {
+    display: contents;
+    white-space: pre;
+  }
+</style>
+<p>The words "Two" and "lines" should not be on the same line.</p>
+<div>Two
+lines</div>
diff --git a/css/css-display/display-contents-unusual-html-elements-none.html b/css/css-display/display-contents-unusual-html-elements-none.html
new file mode 100644
index 0000000..53f5f9e
--- /dev/null
+++ b/css/css-display/display-contents-unusual-html-elements-none.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: display:contents and unusual HTML elements as display:none</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display/#unbox-html">
+<link rel="match" href="display-contents-pass-ref.html">
+<style>
+  /* Disable kerning because kerning may differ for different node tree. */
+  html { font-kerning: none; font-feature-settings: "kern" off; }
+  body { overflow: hidden }
+  br, wbr, meter, progress, canvas, embed, object, audio, iframe, img, video,
+  input, textarea, select {
+    display: contents;
+    border: 10px solid red;
+    width: 200px; height: 200px;
+  }
+</style>
+<p>You should see the word PASS below.</p>
+<div>
+  <meter></meter>
+  <progress></progress>
+  <canvas></canvas>
+  <embed>
+  <object>FAIL</object>
+  <audio controls></audio>
+  <iframe></iframe>
+  <img>
+  <video></video>
+  <input></input>
+  <textarea></textarea>
+  <select></select>
+</div>
+P<br>A<wbr>S<br>S
diff --git a/css/css-display/display-list-item-height-after-dom-change.html b/css/css-display/display-list-item-height-after-dom-change.html
new file mode 100644
index 0000000..f8d6e85
--- /dev/null
+++ b/css/css-display/display-list-item-height-after-dom-change.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Display: reflowing a display:list-item on dom changes - non-zero height</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#list-items">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<ul>
+  <li id="item" style="background:green">
+    <div id="content" style="overflow:hidden">Text<div>
+  </li>
+</ul>
+<script>
+  test(function() {
+    document.body.offsetTop; // force layout
+    item.insertBefore(document.createTextNode("This text should have a green background"), item.firstChild);
+    content.innerHTML = "";
+    assert_not_equals(item.clientHeight, 0, "The height of the LI should not be 0px.");
+  }, "List item height after DOM change.");
+</script>
diff --git a/css/css-fill-stroke/OWNERS b/css/css-fill-stroke/OWNERS
new file mode 100644
index 0000000..4d8fac4
--- /dev/null
+++ b/css/css-fill-stroke/OWNERS
@@ -0,0 +1 @@
+@tabatkins
diff --git a/css/css-fill-stroke/paint-order-001.tentative.html b/css/css-fill-stroke/paint-order-001.tentative.html
new file mode 100644
index 0000000..c8c2b42
--- /dev/null
+++ b/css/css-fill-stroke/paint-order-001.tentative.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>webkit-text-stroke and paint-order</title>
+<link rel="author" title="Jonathan Kew" href="jfkthame@gmail.com">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org">
+<link rel="match" href="reference/paint-order-001-ref.tentative.html">
+<link rel="help" href="https://www.w3.org/TR/fill-stroke-3/#strokes">
+<style>
+span { -webkit-text-stroke: 5px orange; }
+span.i { paint-order: initial; }
+span.f { paint-order: fill; }
+span.fs { paint-order: fill stroke; }
+span.s { paint-order: stroke; }
+span.sf { paint-order: stroke fill; }
+</style>
+<body>
+<div style="font: bold 32px serif;">
+  <span class="i">initial</span>
+  <span class="f">fill</span>
+  <span class="s">stroke</span>
+  <span class="fs">f-s</span>
+  <span class="sf">s-f</span>
+</div>
+
+<div style="font: bold 64px serif;">
+  <span class="i">initial</span>
+  <span class="f">fill</span>
+  <span class="s">stroke</span>
+  <span class="fs">f-s</span>
+  <span class="sf">s-f</span>
+</div>
+
+<div style="font: bold 96px serif;">
+  <span class="i">initial</span><br>
+  <span class="f">fill</span>
+  <span class="s">stroke</span><br>
+  <span class="fs">f-s</span>
+  <span class="sf">s-f</span>
+</div>
+<body>
+<html>
diff --git a/css/css-fill-stroke/reference/paint-order-001-ref.tentative.html b/css/css-fill-stroke/reference/paint-order-001-ref.tentative.html
new file mode 100644
index 0000000..af56147
--- /dev/null
+++ b/css/css-fill-stroke/reference/paint-order-001-ref.tentative.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<title>webkit-text-stroke and paint-order: reference</title>
+<link rel="author" title="Jonathan Kew" href="jfkthame@gmail.com">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org">
+<style>
+body>div { position: relative; clear: left; float: left; }
+span.f { -webkit-text-stroke: 5px orange; }
+span.s,
+span.sf { display: inline-block; position: relative; }
+span.s::before,
+span.sf::before { -webkit-text-stroke: 5px orange; position: absolute; z-index: -1; }
+span.s::before { content: "stroke"; }
+span.sf::before { content: "s-f"; }
+</style>
+<body>
+<div style="font: bold 32px serif;">
+  <span class="f">initial</span>
+  <span class="f">fill</span>
+  <span class="s">stroke</span>
+  <span class="f">f-s</span>
+  <span class="sf">s-f</span>
+</div>
+
+<div style="font: bold 64px serif;">
+  <span class="f">initial</span>
+  <span class="f">fill</span>
+  <span class="s">stroke</span>
+  <span class="f">f-s</span>
+  <span class="sf">s-f</span>
+</div>
+
+<div style="font: bold 96px serif;">
+  <span class="f">initial</span><br>
+  <span class="f">fill</span>
+  <span class="s">stroke</span><br>
+  <span class="f">f-s</span>
+  <span class="sf">s-f</span>
+</div>
+<body>
+<html>
diff --git a/css/css-filter/OWNERS b/css/css-filter/OWNERS
new file mode 100644
index 0000000..e8e7c5d
--- /dev/null
+++ b/css/css-filter/OWNERS
@@ -0,0 +1 @@
+@chrishtr
diff --git a/css/css-filter/filtered-block-is-container-ref.html b/css/css-filter/filtered-block-is-container-ref.html
new file mode 100644
index 0000000..fc9467f
--- /dev/null
+++ b/css/css-filter/filtered-block-is-container-ref.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Filter: Filtered block establishes a containing block reference.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+
+<style>
+#scroller {
+  width: 500px;
+  height: 500px;
+  overflow: scroll
+}
+.spacer {
+  width: 1000px;
+  height: 500px;
+  background-color: lightgreen;
+}
+#transform {
+  /* no-op transform */
+  transform: translateX(0px);
+  width: 100px;
+  height: 50px;
+  background-color: lightblue;
+}
+#fixed, #absolute {
+  width: 100px;
+  height: 100px;
+}
+#fixed {
+  position: fixed;
+  top: 100px;
+  left: 150px;
+  background-color: green;
+}
+#absolute {
+  position: absolute;
+  top: 200px;
+  left: 300px;
+  background-color: blue;
+}
+</style>
+
+<div id="scroller">
+  <div class="spacer">
+  </div>
+  <div id="transform">
+    <div id="fixed"></div>
+    <div id="absolute"></div>
+  </div>
+  <div class="spacer">
+  </div>
+</div>
+
+<script>
+window.onload = function() {
+  document.getElementById("scroller").scrollTo(50, 250);
+}
+</script>
+
+
+
diff --git a/css/css-filter/filtered-block-is-container.html b/css/css-filter/filtered-block-is-container.html
new file mode 100644
index 0000000..6f99f36
--- /dev/null
+++ b/css/css-filter/filtered-block-is-container.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Filter: Filtered block establishes a containing block.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="match" href="filtered-block-is-container-ref.html">
+
+<style>
+#scroller {
+  width: 500px;
+  height: 500px;
+  overflow: scroll
+}
+.spacer {
+  width: 1000px;
+  height: 500px;
+  background-color: lightgreen;
+}
+#filter {
+  /* no-op filter */
+  filter: blur(0px);
+  width: 100px;
+  height: 50px;
+  background-color: lightblue;
+}
+#fixed, #absolute {
+  width: 100px;
+  height: 100px;
+}
+#fixed {
+  position: fixed;
+  top: 100px;
+  left: 150px;
+  background-color: green;
+}
+#absolute {
+  position: absolute;
+  top: 200px;
+  left: 300px;
+  background-color: blue;
+}
+</style>
+
+<div id="scroller">
+  <div class="spacer">
+  </div>
+  <div id="filter">
+    <div id="fixed"></div>
+    <div id="absolute"></div>
+  </div>
+  <div class="spacer">
+  </div>
+</div>
+
+<script>
+window.onload = function() {
+  document.getElementById("scroller").scrollTo(50, 250);
+}
+</script>
+
+
diff --git a/css/css-filter/filtered-html-is-not-container-ref.html b/css/css-filter/filtered-html-is-not-container-ref.html
new file mode 100644
index 0000000..c1f179a
--- /dev/null
+++ b/css/css-filter/filtered-html-is-not-container-ref.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Filter: Filtered html element does not establish a containing block.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+
+<style>
+#spacer {
+  width: 2000px;
+  height: 2000px;
+  background-color: lightgreen;
+}
+#fixed, #absolute {
+  width: 100px;
+  height: 100px;
+}
+#fixed {
+  position: fixed;
+  top: 100px;
+  left: 150px;
+  background-color: green;
+}
+#absolute {
+  position: absolute;
+  top: 200px;
+  left: 300px;
+  background-color: blue;
+}
+</style>
+
+<div id="fixed"></div>
+<div id="absolute"></div>
+<div id="spacer"></div>
+
+<script>
+window.onload = function() { window.scrollTo(50, 100); };
+</script>
+
+
diff --git a/css/css-filter/filtered-html-is-not-container.html b/css/css-filter/filtered-html-is-not-container.html
new file mode 100644
index 0000000..8adc302
--- /dev/null
+++ b/css/css-filter/filtered-html-is-not-container.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Filter: Filtered html element does not establish a containing block.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="match" href="filtered-html-is-not-container-ref.html">
+
+<style>
+html {
+  filter: blur(0px);
+}
+#spacer {
+  width: 2000px;
+  height: 2000px;
+  background-color: lightgreen;
+}
+#fixed, #absolute {
+  width: 100px;
+  height: 100px;
+}
+#fixed {
+  position: fixed;
+  top: 100px;
+  left: 150px;
+  background-color: green;
+}
+#absolute {
+  position: absolute;
+  top: 200px;
+  left: 300px;
+  background-color: blue;
+}
+</style>
+
+<div id="fixed"></div>
+<div id="absolute"></div>
+<div id="spacer"></div>
+
+<script>
+window.onload = function() { window.scrollTo(50, 100); };
+</script>
+
+
diff --git a/css/css-filter/filtered-inline-is-container-ref.html b/css/css-filter/filtered-inline-is-container-ref.html
new file mode 100644
index 0000000..aa6c12e
--- /dev/null
+++ b/css/css-filter/filtered-inline-is-container-ref.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Filter: Filtered inline establishes a containing block.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+
+<style>
+#scroller {
+  width: 500px;
+  height: 500px;
+  overflow: scroll
+}
+.spacer {
+  width: 1000px;
+  height: 500px;
+  background-color: lightgreen;
+}
+#transform {
+  /* no-op filter */
+  transform: translateX(0px);
+}
+#fixed, #absolute {
+  width: 100px;
+  height: 100px;
+}
+#fixed {
+  position: fixed;
+  top: 100px;
+  left: 150px;
+  background-color: green;
+}
+#absolute {
+  position: absolute;
+  top: 200px;
+  left: 300px;
+  background-color: blue;
+}
+</style>
+
+<div id="scroller">
+  <div class="spacer">
+  </div>
+  <div id="transform">
+    a
+    <div id="fixed"></div>
+    <div id="absolute"></div>
+  </div>
+  <div class="spacer">
+  </div>
+</div>
+
+<script>
+window.onload = function() {
+  document.getElementById("scroller").scrollTo(50, 250);
+}
+</script>
+
+
diff --git a/css/css-filter/filtered-inline-is-container.html b/css/css-filter/filtered-inline-is-container.html
new file mode 100644
index 0000000..56f1d91
--- /dev/null
+++ b/css/css-filter/filtered-inline-is-container.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Filter: Filtered inline establishes a containing block reference.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="match" href="filtered-inline-is-container-ref.html">
+
+<style>
+#scroller {
+  width: 500px;
+  height: 500px;
+  overflow: scroll
+}
+.spacer {
+  width: 1000px;
+  height: 500px;
+  background-color: lightgreen;
+}
+#filter {
+  /* no-op filter */
+  filter: blur(0px);
+  display: inline;
+}
+#fixed, #absolute {
+  width: 100px;
+  height: 100px;
+}
+#fixed {
+  position: fixed;
+  top: 100px;
+  left: 150px;
+  background-color: green;
+}
+#absolute {
+  position: absolute;
+  top: 200px;
+  left: 300px;
+  background-color: blue;
+}
+</style>
+
+<div id="scroller">
+  <div class="spacer">
+  </div>
+  <div id="filter">
+    a
+    <div id="fixed"></div>
+    <div id="absolute"></div>
+  </div>
+  <div class="spacer">
+  </div>
+</div>
+
+<script>
+window.onload = function() {
+  document.getElementById("scroller").scrollTo(50, 250);
+}
+</script>
+
+
diff --git a/css/css-flexbox/abspos-autopos-htb-ltr.html b/css/css-flexbox/abspos-autopos-htb-ltr.html
new file mode 100644
index 0000000..095936e
--- /dev/null
+++ b/css/css-flexbox/abspos-autopos-htb-ltr.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Absolutely positioned child with auto position in vertical-rl ltr flexbox</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#abspos-items" title="4.1. Absolutely-Positioned Flex Children">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    position: relative;
+    writing-mode: horizontal-tb;
+    direction: ltr;
+    width: 100px;
+    height: 100px;
+    border: solid white;
+    border-left-width: 20px;
+    left: -20px;
+    border-top-width: 5px;
+    top: -5px;
+    border-right-width: 10px;
+    border-bottom-width: 15px;
+    background: red;
+  }
+  .flex > div {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="flex">
+  <div></div>
+</div>
diff --git a/css/css-flexbox/abspos-autopos-htb-rtl.html b/css/css-flexbox/abspos-autopos-htb-rtl.html
new file mode 100644
index 0000000..b377e8d
--- /dev/null
+++ b/css/css-flexbox/abspos-autopos-htb-rtl.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Absolutely positioned child with auto position in vertical-rl ltr flexbox</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#abspos-items" title="4.1. Absolutely-Positioned Flex Children">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    position: relative;
+    writing-mode: horizontal-tb;
+    direction: rtl;
+    width: 100px;
+    height: 100px;
+    border: solid white;
+    border-left-width: 20px;
+    left: -20px;
+    border-top-width: 5px;
+    top: -5px;
+    border-right-width: 10px;
+    border-bottom-width: 15px;
+    background: red;
+  }
+  .flex > div {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="flex">
+  <div></div>
+</div>
diff --git a/css/css-flexbox/abspos-autopos-vlr-ltr.html b/css/css-flexbox/abspos-autopos-vlr-ltr.html
new file mode 100644
index 0000000..02c7cf3
--- /dev/null
+++ b/css/css-flexbox/abspos-autopos-vlr-ltr.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Absolutely positioned child with auto position in vertical-rl ltr flexbox</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#abspos-items" title="4.1. Absolutely-Positioned Flex Children">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    position: relative;
+    writing-mode: vertical-lr;
+    direction: ltr;
+    width: 100px;
+    height: 100px;
+    border: solid white;
+    border-left-width: 20px;
+    left: -20px;
+    border-top-width: 5px;
+    top: -5px;
+    border-right-width: 10px;
+    border-bottom-width: 15px;
+    background: red;
+  }
+  .flex > div {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="flex">
+  <div></div>
+</div>
diff --git a/css/css-flexbox/abspos-autopos-vlr-rtl.html b/css/css-flexbox/abspos-autopos-vlr-rtl.html
new file mode 100644
index 0000000..a156a72
--- /dev/null
+++ b/css/css-flexbox/abspos-autopos-vlr-rtl.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Absolutely positioned child with auto position in vertical-rl ltr flexbox</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#abspos-items" title="4.1. Absolutely-Positioned Flex Children">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    position: relative;
+    writing-mode: vertical-lr;
+    direction: rtl;
+    width: 100px;
+    height: 100px;
+    border: solid white;
+    border-left-width: 20px;
+    left: -20px;
+    border-top-width: 5px;
+    top: -5px;
+    border-right-width: 10px;
+    border-bottom-width: 15px;
+    background: red;
+  }
+  .flex > div {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="flex">
+  <div></div>
+</div>
diff --git a/css/css-flexbox/abspos-autopos-vrl-ltr.html b/css/css-flexbox/abspos-autopos-vrl-ltr.html
new file mode 100644
index 0000000..99ea3c4
--- /dev/null
+++ b/css/css-flexbox/abspos-autopos-vrl-ltr.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Absolutely positioned child with auto position in vertical-rl ltr flexbox</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#abspos-items" title="4.1. Absolutely-Positioned Flex Children">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    position: relative;
+    writing-mode: vertical-rl;
+    direction: ltr;
+    width: 100px;
+    height: 100px;
+    border: solid white;
+    border-left-width: 20px;
+    left: -20px;
+    border-top-width: 5px;
+    top: -5px;
+    border-right-width: 10px;
+    border-bottom-width: 15px;
+    background: red;
+  }
+  .flex > div {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="flex">
+  <div></div>
+</div>
diff --git a/css/css-flexbox/abspos-autopos-vrl-rtl.html b/css/css-flexbox/abspos-autopos-vrl-rtl.html
new file mode 100644
index 0000000..a156a72
--- /dev/null
+++ b/css/css-flexbox/abspos-autopos-vrl-rtl.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Absolutely positioned child with auto position in vertical-rl ltr flexbox</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#abspos-items" title="4.1. Absolutely-Positioned Flex Children">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    position: relative;
+    writing-mode: vertical-lr;
+    direction: rtl;
+    width: 100px;
+    height: 100px;
+    border: solid white;
+    border-left-width: 20px;
+    left: -20px;
+    border-top-width: 5px;
+    top: -5px;
+    border-right-width: 10px;
+    border-bottom-width: 15px;
+    background: red;
+  }
+  .flex > div {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="flex">
+  <div></div>
+</div>
diff --git a/css/css-flexbox/anonymous-flex-item-001.html b/css/css-flexbox/anonymous-flex-item-001.html
new file mode 100644
index 0000000..e1c6ec6
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-001.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Flexbox Test: Flex item - contiguous text runs - node removal</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items">
+<link rel="match" href="anonymous-flex-item-ref.html">
+<p>There should be a space between "two" and "words" below.</p>
+<div style="display:flex">two <span id="spanRemove"></span>words</div>
+<script>
+  document.body.offsetTop;
+  spanRemove.remove();
+</script>
diff --git a/css/css-flexbox/anonymous-flex-item-002.html b/css/css-flexbox/anonymous-flex-item-002.html
new file mode 100644
index 0000000..4b571fe
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-002.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Flexbox Test: Flex item - contiguous text runs - display:none</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items">
+<link rel="match" href="anonymous-flex-item-ref.html">
+<p>There should be a space between "two" and "words" below.</p>
+<div style="display:flex">two <span style="display:none"></span>words</div>
diff --git a/css/css-flexbox/anonymous-flex-item-003.html b/css/css-flexbox/anonymous-flex-item-003.html
new file mode 100644
index 0000000..1449e1d
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-003.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Flexbox Test: Flex item - contiguous text runs - display:none dynamic</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items">
+<link rel="match" href="anonymous-flex-item-ref.html">
+<p>There should be a space between "two" and "words" below.</p>
+<div style="display:flex">two <span id="noneSpan"></span>words</div>
+<script>
+  document.body.offsetTop;
+  noneSpan.style.display = "none";
+</script>
diff --git a/css/css-flexbox/anonymous-flex-item-004.html b/css/css-flexbox/anonymous-flex-item-004.html
new file mode 100644
index 0000000..a3dfbc3
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-004.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Flexbox Test: Flex item - non-contiguous text runs - position:absolute</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items">
+<link rel="match" href="anonymous-flex-item-split-ref.html">
+<p>The words "Two" and "lines" should not be on the same line.</p>
+<div style="display:flex;flex-direction:column">Two <span style="position:absolute"></span>lines</div>
diff --git a/css/css-flexbox/anonymous-flex-item-005.html b/css/css-flexbox/anonymous-flex-item-005.html
new file mode 100644
index 0000000..a7de7b7
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-005.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Flexbox Test: Flex item - non-contiguous text runs - position:absolute dynamic</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items">
+<link rel="match" href="anonymous-flex-item-split-ref.html">
+<p>The words "Two" and "lines" should not be on the same line.</p>
+<div style="display:flex;flex-direction:column">Two <span id="absSpan"></span>lines</div>
+<script>
+  absSpan.style.display = "none";
+  document.body.offsetTop;
+  absSpan.style.position = "absolute";
+  absSpan.style.display = "inline";
+</script>
diff --git a/css/css-flexbox/anonymous-flex-item-006.html b/css/css-flexbox/anonymous-flex-item-006.html
new file mode 100644
index 0000000..6320b81
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-006.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Flexbox Test: Flex item - non-contiguous text runs - position:absolute and node removal</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items">
+<link rel="match" href="anonymous-flex-item-split-ref.html">
+<style>.abs { position:absolute }</style>
+<p>The words "Two" and "lines" should not be on the same line.</p>
+<div style="display:flex;flex-direction:column">Two <span class="abs"></span><span id="spanRemove"></span><span class="abs"></span>lines</div>
+<script>
+  document.body.offsetTop;
+  spanRemove.remove();
+</script>
diff --git a/css/css-flexbox/anonymous-flex-item-ref.html b/css/css-flexbox/anonymous-flex-item-ref.html
new file mode 100644
index 0000000..4a22854
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<p>There should be a space between "two" and "words" below.</p>
+two words
diff --git a/css/css-flexbox/anonymous-flex-item-split-ref.html b/css/css-flexbox/anonymous-flex-item-split-ref.html
new file mode 100644
index 0000000..163efd4
--- /dev/null
+++ b/css/css-flexbox/anonymous-flex-item-split-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<p>The words "Two" and "lines" should not be on the same line.</p>
+Two<br>
+lines
diff --git a/css/css-flexbox/flexbox_first-letter.html b/css/css-flexbox/flexbox_first-letter.html
new file mode 100644
index 0000000..dbbb03d
--- /dev/null
+++ b/css/css-flexbox/flexbox_first-letter.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>flexbox | first-letter</title>
+<meta name="assert" content="This test is ensures that flexbox placement does not apply to ::first-letter psuedo-content">
+<link rel="author" title="Vince Falconi" href="vince.falconi@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#placement">
+<style>
+  div { display: flex; }
+  div::first-letter { order: 2 }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div>Triceratops</div>
+
+<script>
+  test(function() {
+    let order = getComputedStyle(document.querySelector('div'), '::first-letter').order;
+    assert_not_equals(order, '2');
+  });
+</script>
diff --git a/css/css-flexbox/flexbox_flex-none-wrappable-content-ref.html b/css/css-flexbox/flexbox_flex-none-wrappable-content-ref.html
new file mode 100644
index 0000000..fb69358
--- /dev/null
+++ b/css/css-flexbox/flexbox_flex-none-wrappable-content-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Reference for specifying flex:none on wrappable content should give content its full width</title>
+
+<style>
+span {
+  font-family: Ahem;
+  color: green;
+}
+</style>
+
+<div><span>XXX XXX XXX</span></div>
+
+<div style="margin-top: 1em;">You should see three green rectangles above, all on the same line.</div>
diff --git a/css/css-flexbox/flexbox_flex-none-wrappable-content.html b/css/css-flexbox/flexbox_flex-none-wrappable-content.html
new file mode 100644
index 0000000..de00ff4
--- /dev/null
+++ b/css/css-flexbox/flexbox_flex-none-wrappable-content.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Specifying flex:none on wrappable content should give content its full width</title>
+<link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#flex-common">
+<meta name="assert" content="When content has flex:none, it should be given its full width">
+<link rel="match" href="flexbox_flex-none-wrappable-content-ref.html">
+
+<style>
+span {
+  font-family: Ahem;
+  color: green;
+}
+</style>
+
+<div style="display: flex; width: 5px;">
+  <div style="flex: none;">
+    <span>XXX XXX XXX</span>
+  </div>
+</div>
+
+<div style="margin-top: 1em;">You should see three green rectangles above, all on the same line.</div>
diff --git a/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-invalid.html b/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-invalid.html
index d1f29e3..6ea876b 100644
--- a/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-invalid.html
+++ b/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-items-invalid.html
@@ -17,6 +17,6 @@
     var body = document.body;
 
     assert_equals(getComputedStyle(body).getPropertyValue("align-items"),
-	          "stretch");
+	          "normal");
 });
 </script>
diff --git a/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-invalid.html b/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-invalid.html
index 0059bc2..5312ea8 100644
--- a/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-invalid.html
+++ b/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_align-self-invalid.html
@@ -17,6 +17,6 @@
     var body = document.body;
 
     assert_equals(getComputedStyle(body).getPropertyValue("align-self"),
-	          "stretch");
+	          "auto");
 });
 </script>
diff --git a/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_min-auto-size.html b/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_min-auto-size.html
new file mode 100644
index 0000000..ccfeb35
--- /dev/null
+++ b/css/css-flexbox/getcomputedstyle/flexbox_computedstyle_min-auto-size.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>CSS Flexible Box Test: computed style for auto minimum size</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org" />
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#min-size-auto" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .flex { display: flex }
+  .none { display: none }
+  .min-wh {
+    min-width: auto;
+    min-height: auto;
+  }
+  .contents { display: contents }
+</style>
+<div class="flex">
+  <div class="min-wh"></div>
+</div>
+<div class="none">
+  <div class="flex">
+    <div class="min-wh"></div>
+  </div>
+</div>
+<div class="flex">
+  <div class="contents">
+    <div class="min-wh"></div>
+  </div>
+</div>
+<div class="flex">
+  <div class="min-wh none"></div>
+</div>
+<script>
+  const tests = [
+    { description: "Computed min-width/min-height of specified auto for flex item.", computed: "auto" },
+    { description: "Computed min-width/min-height of specified auto inside display:none which would otherwise have been a flex item.", computed: "0px" },
+    { description: "Computed min-width/min-height of specified auto for flex item inside display:contents.", computed: "auto" },
+    { description: "Computed min-width/min-height of specified auto with display:none which would otherwise have been a flex item.", computed: "0px" }
+  ];
+
+  const testElements = document.querySelectorAll(".min-wh");
+  let testNo = 0;
+  for (let testElement of testElements) {
+    test(() => {
+      assert_equals(getComputedStyle(testElement).minWidth, tests[testNo].computed);
+      assert_equals(getComputedStyle(testElement).minHeight, tests[testNo].computed);
+    }, tests[testNo].description);
+    testNo++;
+  }
+</script>
diff --git a/css/css-flexbox/percentage-size-subitems-001.html b/css/css-flexbox/percentage-size-subitems-001.html
new file mode 100644
index 0000000..70f3953
--- /dev/null
+++ b/css/css-flexbox/percentage-size-subitems-001.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Flexbox Test: Percentage size on child of a flex item with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#flex-items">
+<link rel="match" href="reference/percentage-size-subitems-001-ref.html">
+<meta name="assert" content="Checks that flex items children resolve properly their percentage sizes, even when the flex item has margin, border, padding and scrollbar.">
+<style>
+.flex {
+  display: inline-flex;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+  margin: 10px;
+  vertical-align: top;
+}
+
+.item {
+  flex: 1;
+  overflow: scroll;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  background: cyan;
+}
+
+.subitem {
+  width: 100%;
+  height: 100%;
+  background: yellow;
+}
+
+.horizontalTB { writing-mode: horizontal-tb; }
+.verticalLR { writing-mode: vertical-lr; }
+.verticalRL {  writing-mode: vertical-rl; }
+</style>
+
+<p>The test passes if in the different examples you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="flex">
+  <div class="item horizontalTB">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item horizontalTB">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item horizontalTB">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalLR">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalLR">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalLR">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalRL">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalRL">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalRL">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
diff --git a/css/css-flexbox/reference/percentage-size-subitems-001-ref.html b/css/css-flexbox/reference/percentage-size-subitems-001-ref.html
new file mode 100644
index 0000000..b9883c5
--- /dev/null
+++ b/css/css-flexbox/reference/percentage-size-subitems-001-ref.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Refttest Reference: Percentage size on child of a flex item with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.flex {
+  display: inline-block;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+  margin: 10px;
+  vertical-align: top;
+}
+
+.item {
+  overflow: scroll;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  background: cyan;
+  width: calc(100% - 6px);
+  height: calc(100% - 4px);
+  box-sizing: border-box;
+}
+
+.subitem {
+  width: 100%;
+  height: 100%;
+  background: yellow;
+}
+
+.horizontalTB { writing-mode: horizontal-tb; }
+.verticalLR { writing-mode: vertical-lr; }
+.verticalRL {  writing-mode: vertical-rl; }
+</style>
+
+<p>The test passes if in the different examples you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="flex">
+  <div class="item horizontalTB">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item horizontalTB">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item horizontalTB">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalLR">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalLR">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalLR">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalRL">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalRL">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="flex">
+  <div class="item verticalRL">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
diff --git a/css/css-flexbox/support/100x100-green.png b/css/css-flexbox/support/100x100-green.png
index 3f07c9e..25b76c3 100644
--- a/css/css-flexbox/support/100x100-green.png
+++ b/css/css-flexbox/support/100x100-green.png
Binary files differ
diff --git a/css/css-flexbox/table-as-item-auto-min-width.html b/css/css-flexbox/table-as-item-auto-min-width.html
new file mode 100644
index 0000000..66a7732
--- /dev/null
+++ b/css/css-flexbox/table-as-item-auto-min-width.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>CSS Flexbox Test: Flex item as table, specified width less than minimum intrinsic width</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#layout-algorithm" title="9. Flex Layout Algorithm">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="display:flex; width:100px; background:red;">
+  <div style="display:table; width:10px; max-width:10px; height:100px; background:green;">
+    <div style="width:100px; height:10px; background:green;"></div>
+  </div>
+</div>
diff --git a/css/css-flexbox/table-as-item-change-cell.html b/css/css-flexbox/table-as-item-change-cell.html
new file mode 100644
index 0000000..a83f518
--- /dev/null
+++ b/css/css-flexbox/table-as-item-change-cell.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>CSS Flexbox Test: Flex item as table, change contents of cell</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#layout-algorithm" title="9. Flex Layout Algorithm">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="display:flex; flex-direction:column; width:100px; background:green;">
+  <div style="position:relative; z-index:-1; display:table; width:100%; height:100px; background:hotpink;">
+    <div style="display:table-cell; width:100px;"></div>
+    <div id="cell" style="display:table-cell;">
+      <div id="child" style="display:none; color:red;">
+        <!-- Add some whitespace after the word "FAIL", in case of negative glyph bearings. -->
+        FAIL&nbsp;
+      </div>
+    </div>
+  </div>
+</div>
+<script>
+  document.body.offsetTop;
+  document.getElementById("child").style.display = "block";
+</script>
diff --git a/css/css-flexbox/table-as-item-narrow-content.html b/css/css-flexbox/table-as-item-narrow-content.html
new file mode 100644
index 0000000..31cf112
--- /dev/null
+++ b/css/css-flexbox/table-as-item-narrow-content.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>CSS Flexbox Test: Flex item as table with narrow content</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#layout-algorithm" title="9. Flex Layout Algorithm">
+<meta name="assert" content="A flex item as a table uses the sizing algorithm of the flexbox">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="display:flex; width:200px;">
+  <div style="display:table; flex:1 0; background:green;">
+    <div style="width:10px; height:100px;"></div>
+  </div>
+  <div style="flex:1 0;"></div>
+</div>
diff --git a/css/css-flexbox/table-as-item-wide-content.html b/css/css-flexbox/table-as-item-wide-content.html
new file mode 100644
index 0000000..475adf5
--- /dev/null
+++ b/css/css-flexbox/table-as-item-wide-content.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>CSS Flexbox Test: Flex item as table with wide content</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#layout-algorithm" title="9. Flex Layout Algorithm">
+<meta name="assert" content="A flex item as a table uses the sizing algorithm of the flexbox">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="display:flex; width:100px;">
+  <div style="min-width:0; flex:1 1; display:table; background:green;">
+    <div style="width:500px; height:100px;"></div>
+  </div>
+</div>
diff --git a/css/css-font-loading/OWNERS b/css/css-font-loading/OWNERS
new file mode 100644
index 0000000..404d8d1
--- /dev/null
+++ b/css/css-font-loading/OWNERS
@@ -0,0 +1,2 @@
+@tabatkins
+@svgeesus
diff --git a/css/css-font-loading/idlharness.https.html b/css/css-font-loading/idlharness.https.html
new file mode 100644
index 0000000..037e62f
--- /dev/null
+++ b/css/css-font-loading/idlharness.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Font Loading API IDL tests</title>
+<link rel="help" href="https://drafts.csswg.org/css-font-loading/#fontfacesetloadevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+"use strict";
+
+function doTest([dom, cssfontloading]) {
+  const idl_array = new IdlArray();
+  idl_array.add_untested_idls(dom);
+  idl_array.add_untested_idls("[Exposed=Worker] interface WorkerGlobalScope : EventTarget { };");
+  idl_array.add_objects({Document: ["document"]});
+  idl_array.add_idls(cssfontloading);
+  idl_array.test();
+}
+
+function fetchText(url) {
+  return fetch(url).then((response) => response.text());
+}
+
+promise_test(() => {
+  return Promise.all([
+    "/interfaces/dom.idl",
+    "/interfaces/css-font-loading.idl",
+  ].map(fetchText)).then(doTest);
+}, "Test IDL implementation of CSS Font Loading");
+</script>
diff --git a/css/css-fonts/OWNERS b/css/css-fonts/OWNERS
index 26a28ce..f3c875d 100644
--- a/css/css-fonts/OWNERS
+++ b/css/css-fonts/OWNERS
@@ -2,3 +2,4 @@
 @yunxiaoxie
 @nattokirai
 @litherum
+@drott
diff --git a/css/css-fonts/alternates-order-ref.html b/css/css-fonts/alternates-order-ref.html
index ed53a27..fdb477f 100644
--- a/css/css-fonts/alternates-order-ref.html
+++ b/css/css-fonts/alternates-order-ref.html
@@ -19,6 +19,9 @@
   font-feature-settings: "ss05"; /* crossed W */
 }
 
+/* tests that should NOT use the feature, due to case-sensitivity of font-feature-values names */
+#test2, #test3 { font-feature-settings: "ss05" off; }
+
 </style>
 </head>
 <body lang="en">
diff --git a/css/css-fonts/alternates-order.html b/css/css-fonts/alternates-order.html
index b1baead..d47cbce 100644
--- a/css/css-fonts/alternates-order.html
+++ b/css/css-fonts/alternates-order.html
@@ -3,10 +3,10 @@
 <head>
 <title>CSS Test: feature value matching for font-variant-alternates</title>
 <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
-<link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop"/>
-<link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-feature-values"/>
+<link rel="help" href="http://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop"/>
+<link rel="help" href="http://www.w3.org/TR/css-fonts-4/#font-feature-values"/>
 <link rel="match" href="alternates-order-ref.html"/>
-<meta name="flags" content=""/>
+
 <meta name="assert" content="Case and order of font family name or feature name should not affect alternate rendered"/>
 <style type="text/css">
 @font-face {
@@ -65,13 +65,13 @@
 }
 
 #test2 {
-  /* testing case-insensitivity of styleset name */
+  /* testing case-sensitivity of styleset name */
   font-family: fontB;
   font-variant-alternates: styleset(altW);
 }
 
 #test3 {
-  /* testing case-insensitivity of styleset name */
+  /* testing case-sensitivity of styleset name */
   font-family: fontB;
   font-variant-alternates: styleset(ALTW);
 }
@@ -79,7 +79,7 @@
 #test4 {
   /* testing escapes in styleset name */
   font-family: fontB;
-  font-variant-alternates: styleset(\41 ltW);
+  font-variant-alternates: styleset(\41 lTw);
 }
 
 #test5 {
diff --git a/css/css-fonts/calc-in-font-variation-settings.html b/css/css-fonts/calc-in-font-variation-settings.html
new file mode 100644
index 0000000..1a7c59a
--- /dev/null
+++ b/css/css-fonts/calc-in-font-variation-settings.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: calc() function in font-variation-settings</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-values/#funcdef-calc">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#test {
+  font-variation-settings: "XHGT" calc(0.7);
+}
+</style>
+<div id="test"></div>
+<script>
+const div = document.querySelector("#test");
+test(function() {
+  assert_equals(getComputedStyle(div).fontVariationSettings, "\"XHGT\" 0.7");
+}, "calc() in font-variation-settings");
+</script>
diff --git a/css/css-fonts/first-available-font-001-ref.html b/css/css-fonts/first-available-font-001-ref.html
new file mode 100644
index 0000000..0acbd33
--- /dev/null
+++ b/css/css-fonts/first-available-font-001-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+@font-face {
+  font-family: 'B';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  width: 1ex;
+  height: 1ex;
+
+  font-size: 200px;
+  background: blue;
+  font-family: 'B';
+}
+</style>
+
+<p>Test passes if there is <strong>a blue square</strong> below.
+
+<div></div>
diff --git a/css/css-fonts/first-available-font-001.html b/css/css-fonts/first-available-font-001.html
new file mode 100644
index 0000000..b6f9d18
--- /dev/null
+++ b/css/css-fonts/first-available-font-001.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: first available font and the ex unit</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#first-available-font">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#first-available-font">
+<link rel="match" href="first-available-font-001-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="Fonts that do not include the U+0020 character are not considered the first available font (used to determine the ex unit), even when at the start of the font list.">
+<style>
+/* Two arbitrary fonts with different metrics */
+@font-face {
+  font-family: 'A-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061; /* Not including U+0020, so it cannot be the first available font*/
+}
+@font-face {
+  font-family: 'B';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  width: 1ex;
+  height: 0.5ex;
+
+  font-size: 200px;
+  background: blue;
+  position: absolute;
+}
+#t1 { font-family: 'B', sans-serif; margin-top: 0.5ex; } /* half a square, shifted down */
+#t2 { font-family: 'A-no-space', 'B', monospace; } /* Should use B as the first available font, and therefore be the same size as t1 */
+
+/* Both elements are using different generic fallback fonts, so that they end up being sized differently if web-fonts fail to load. */
+
+</style>
+
+<p>Test passes if there is <strong>a blue square</strong> below.
+
+<div id=t1></div>
+<div id=t2></div>
diff --git a/css/css-fonts/first-available-font-002-ref.html b/css/css-fonts/first-available-font-002-ref.html
new file mode 100644
index 0000000..ff9464b
--- /dev/null
+++ b/css/css-fonts/first-available-font-002-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+@font-face {
+  font-family: 'B';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  width: 1ch;
+  height: 1ch;
+
+  font-size: 200px;
+  background: blue;
+  font-family: 'B';
+}
+</style>
+
+<p>Test passes if there is <strong>a blue square</strong> below.
+
+<div></div>
diff --git a/css/css-fonts/first-available-font-002.html b/css/css-fonts/first-available-font-002.html
new file mode 100644
index 0000000..36437b6
--- /dev/null
+++ b/css/css-fonts/first-available-font-002.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: first available font and the ch unit</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#first-available-font">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#first-available-font">
+<link rel="match" href="first-available-font-002-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="Fonts that do not include the U+0020 character are not considered the first available font (used to determine the ch unit), even when at the start of the font list.">
+<style>
+/* Two arbitrary fonts with different metrics */
+@font-face {
+  font-family: 'A-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061; /* Not including U+0020, so it cannot be the first available font*/
+}
+@font-face {
+  font-family: 'B';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  width: 1ch;
+  height: 0.5ch;
+
+  font-size: 200px;
+  background: blue;
+  position: absolute;
+}
+#t1 { font-family: 'B', sans-serif; margin-top: 0.5ch; } /* half a square, shifted down */
+#t2 { font-family: 'A-no-space', 'B', monospace; } /* Should use B as the first available font, and therefore be the same size as t1 */
+
+/* Both elements are using different generic fallback fonts, so that they end up being sized differently if web-fonts fail to load. */
+
+</style>
+
+<p>Test passes if there is <strong>a blue square</strong> below.
+
+<div id=t1></div>
+<div id=t2></div>
diff --git a/css/css-fonts/first-available-font-003-ref.html b/css/css-fonts/first-available-font-003-ref.html
new file mode 100644
index 0000000..8b01bb7
--- /dev/null
+++ b/css/css-fonts/first-available-font-003-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: reference file</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<style>
+@font-face {
+  font-family: 'A';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+}
+div {
+  position: absolute;
+  line-height: normal;
+  font-size: 100px;
+  color: transparent;
+  border: solid black 1px;
+  width: 100px;
+  font-family: A;
+}
+
+.ba { margin-left: 100px; }
+</style>
+
+<p>There should be <strong>two identically sized rectangles</strong> below.
+
+<div class="ba">a</div>
+<div class="a">a</div>
diff --git a/css/css-fonts/first-available-font-003.html b/css/css-fonts/first-available-font-003.html
new file mode 100644
index 0000000..74a850e
--- /dev/null
+++ b/css/css-fonts/first-available-font-003.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: first available font and the strut</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#first-available-font">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#first-available-font">
+<link rel="match" href="first-available-font-003-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The strut, which impacts the line height, is taken from the primary font, which is the first font to include the U+0020 character.">
+<style>
+/* Two arbitrary fonts with different metrics */
+@font-face {
+  font-family: 'A';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+}
+@font-face {
+  font-family: 'B-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062;
+}
+
+div {
+  position: absolute;
+  line-height: normal;
+  font-size: 100px;
+  color: transparent;
+  border: solid black 1px;
+  width: 100px;
+}
+
+.a { font-family: A; }
+.ba { font-family: B-no-space, A; margin-left: 100px; }
+</style>
+
+<p>There should be <strong>two identically sized rectangles</strong> below.
+
+<!-- Both divs show the same content with the same font,
+     but the first div has an unused font earlier in the list,
+     while the second one does not.
+     However, that font does not include the U+0020 character,
+     and can therefore not be the first available font.
+     If it had been, it would affect the height of the strut,
+     making the height different.
+     Since it is not, both boxes are expected to be the same height. -->
+<div class="ba">a</div>
+<div class="a">a</div>
diff --git a/css/css-fonts/first-available-font-004.html b/css/css-fonts/first-available-font-004.html
new file mode 100644
index 0000000..c7ae1cb
--- /dev/null
+++ b/css/css-fonts/first-available-font-004.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: first available font and the strut</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#first-available-font">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#first-available-font">
+<link rel="match" href="first-available-font-003-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The strut, which impacts the line height, is taken from the primary font, which is the first font to include the U+0020 character.
+The fact that that font is used somewhere else in the page makes no difference.">
+<style>
+/* Two arbitrary fonts with different metrics */
+@font-face {
+  font-family: 'A';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+}
+@font-face {
+  font-family: 'B-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0062;
+}
+
+div {
+  position: absolute;
+  line-height: normal;
+  font-size: 100px;
+  color: transparent;
+  border: solid black 1px;
+  width: 100px;
+}
+
+.a { font-family: A; }
+.ba { font-family: B-no-space, A; margin-left: 100px; }
+.loader { font-family: B-no-space; border: none; }
+</style>
+
+<p>There should be <strong>two identically sized rectangles</strong> below.
+
+<div class=loader>b</div>
+
+<!-- Both divs show the same content with the same font,
+     but the first div has an unused font earlier in the list,
+     while the second one does not.
+     However, that font does not include the U+0020 character,
+     and can therefore not be the first available font.
+     If it had been, it would affect the height of the strut,
+     making the height different.
+     Since it is not, both boxes are expected to be the same height. -->
+<div class="ba">a</div>
+<div class="a">a</div>
+
diff --git a/css/css-fonts/first-available-font-005-ref.html b/css/css-fonts/first-available-font-005-ref.html
new file mode 100644
index 0000000..9f27c19
--- /dev/null
+++ b/css/css-fonts/first-available-font-005-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS fonts test: baseline position with explicit sizing, no space in first font</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<p>Test passes if there is <strong>no red</strong> below.
diff --git a/css/css-fonts/first-available-font-005.html b/css/css-fonts/first-available-font-005.html
new file mode 100644
index 0000000..8f6f66b
--- /dev/null
+++ b/css/css-fonts/first-available-font-005.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS fonts test: baseline position with explicit sizing, no space in first font</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#first-available-font">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#first-available-font">
+<link rel="match" href="first-available-font-005-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The position of the baseline in an inline-level box whose height is determined by a non-normal value of line-height
+                             does depend on the primary font, which is the first that contains U+0020.">
+<style>
+@font-face {
+  font-family: 'A-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061;
+}
+@font-face {
+  font-family: 'B-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0061;
+}
+@font-face {
+  font-family: 'B';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  position: absolute;
+  line-height: 100px;
+  font-size: 100px;
+  width: 300px; /* plenty of room for the (invisible) text */
+  text-align: right;
+  color: transparent;
+  outline: solid;
+}
+span { /* visible thing aligned to the baseline, and small enough to not influence its position */
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+}
+
+/* white #a is on top of red #b,
+   The first font in their respective lists do no contain U+0020,
+   and therfore the first available font should be the next one, which is the same.
+   Since the baseline should be based on the primary font,
+   their baselines should line up and red #b should be invisible.*/
+#a { font-family: A-no-space, B; }
+#a span { background: red; }
+
+#b { font-family: B-no-space, B; }
+#b span { background: white; }
+
+</style>
+
+<p>Test passes if there is <strong>no red</strong> below.
+
+<div id=a>bb<span></span></div>
+<div id=b>bb<span></span></div>
diff --git a/css/css-fonts/first-available-font-006.html b/css/css-fonts/first-available-font-006.html
new file mode 100644
index 0000000..fb50b9b
--- /dev/null
+++ b/css/css-fonts/first-available-font-006.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS fonts test: baseline position with explicit sizing, no space in first font</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#first-available-font">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#first-available-font">
+<link rel="match" href="first-available-font-005-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The position of the baseline in an inline-level box whose height is determined by a non-normal value of line-height
+                             does depend on the primary font, which is the first that contains U+0020.">
+<style>
+@font-face {
+  font-family: 'A-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061;
+}
+@font-face {
+  font-family: 'B-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+  unicode-range: U+0061;
+}
+@font-face {
+  font-family: 'B';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  position: absolute;
+  line-height: 100px;
+  font-size: 100px;
+  width: 300px; /* plenty of room for the (invisible) text */
+  text-align: right;
+  color: transparent;
+  outline: solid;
+}
+span { /* visible thing aligned to the baseline, and small enough to not influence its position */
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+}
+
+/* white #a is on top of red #b,
+   The first font in their respective lists do no contain U+0020,
+   and therfore the first available font should be the next one, which is the same.
+   Since the baseline should be based on the primary font,
+   their baselines should line up and red #b should be invisible.
+   The fact that that font is used should make no difference.
+ */
+#a { font-family: A-no-space, B; }
+#a span { background: red; }
+
+#b { font-family: B-no-space, B; }
+#b span { background: white; }
+
+</style>
+
+<p>Test passes if there is <strong>no red</strong> below.
+
+<div id=a>aa<span></span></div>
+<div id=b>aa<span></span></div>
diff --git a/css/css-fonts/first-available-font-007.html b/css/css-fonts/first-available-font-007.html
new file mode 100644
index 0000000..fce9c99
--- /dev/null
+++ b/css/css-fonts/first-available-font-007.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS-fonts: inline level box content height and first available font, missing U+0020</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#first-available-font">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#first-available-font">
+<link rel="match" href="first-available-font-005-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="The height of the content area of an inline-level depends only on the first available font, which is the first one to include U+0020.">
+<style>
+/* Two Arbitrary fonts with different metrics. One has a taller ascender, the other a deeper descender. */
+@font-face {
+  font-family: 'A-no-space';
+  font-style: normal;
+  font-weight: 400;
+  src:  url(/fonts/Revalia.woff) format('woff');
+  unicode-range: U+0061;
+}
+@font-face {
+  font-family: 'B';
+  font-style: normal;
+  font-weight: 400;
+  src: url(/fonts/AD.woff) format('woff');
+}
+
+div {
+  font-size: 50px;
+  display: inline-block;
+  width: 40px;
+  overflow: hidden;
+}
+
+span { color: transparent; }
+
+div:nth-of-type(1) {
+  font-family: B;
+}
+div:nth-of-type(1) span { background: red; }
+
+/* The second div should have the same first available font as the first one, since A-no-space does no include U+0020.
+   Both div's span's should thefore overflap exactly, with the white one hiding the red.
+ */
+div:nth-of-type(2) {
+  font-family: A-no-space, B;
+  margin-left: -40px;
+}
+div:nth-of-type(2) span { background: white; }
+</style>
+
+<p>Test passes if there is <strong>no red</strong> below.
+
+<div><span>aaaaa</span></div><div><span>aaaaa</span></div>
diff --git a/css/css-fonts/font-default-01-ref.html b/css/css-fonts/font-default-01-ref.html
new file mode 100644
index 0000000..bbe0251
--- /dev/null
+++ b/css/css-fonts/font-default-01-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+  }
+</style>
+
+<body>
+<!-- test font does not currently test for rlig or locl -->
+<p>Test passes if the two lines below are identical, with six check marks (✓). </p>
+<section class="tests">
+	<p class="ref">AAAAAA</p>
+	<p class="ref">AAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-01.html b/css/css-fonts/font-default-01.html
new file mode 100644
index 0000000..99fc58b
--- /dev/null
+++ b/css/css-fonts/font-default-01.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#default-features">
+<link rel="match" href="font-default-01-ref.html">
+<meta name="assert" content="Required ligatures, common ligatures and contextual forms must be enabled by default (OpenType features: rlig, liga, clig, calt), along with localized forms (OpenType feature: locl), and features required for proper display of composed characters and marks (OpenType features: ccmp, mark, mkmk).">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+  }
+</style>
+
+<body>
+<!-- test font does not currently test for rlig or locl -->
+<p>Test passes if the two lines below are identical, with six check marks (✓). </p>
+<section class="tests">
+	<p class="test">CDGÂÂÄ</p>
+	<p class="ref">AAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-02-ref.html b/css/css-fonts/font-default-02-ref.html
new file mode 100644
index 0000000..6d45ace
--- /dev/null
+++ b/css/css-fonts/font-default-02-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+  }
+  .test {
+    font-variant: normal;
+  }
+</style>
+
+<body>
+<!-- test font does not currently test for rlig or locl -->
+<p>Test passes if the three lines below are identical, with six check marks (✓). </p>
+<section class="tests">
+  <p class="ref">AAAAAA</p>
+	<p class="ref">AAAAAA</p>
+	<p class="ref">AAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-02.html b/css/css-fonts/font-default-02.html
new file mode 100644
index 0000000..24f6194
--- /dev/null
+++ b/css/css-fonts/font-default-02.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#default-features">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-prop">
+<link rel="match" href="font-default-02-ref.html">
+<meta name="assert" content="These features must always be enabled, even when the value of the ‘font-variant’ and ‘font-feature-settings’ properties is ‘normal’. ">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+  }
+  .test {
+    font-variant: normal;
+  }
+</style>
+
+<body>
+<!-- test font does not currently test for rlig or locl -->
+<p>Test passes if the three lines below are identical, with six check marks (✓). </p>
+<section class="tests">
+  <p class="default">CDGÂÂÄ</p>
+	<p class="test">CDGÂÂÄ</p>
+	<p class="ref">AAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-03-ref.html b/css/css-fonts/font-default-03-ref.html
new file mode 100644
index 0000000..2f1d160
--- /dev/null
+++ b/css/css-fonts/font-default-03-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+  }
+  .test {
+    font-feature-settings: normal;
+  }
+</style>
+
+<body>
+<!-- test font does not currently test for rlig or locl -->
+<p>Test passes if the three lines below are identical, with six check marks (✓). </p>
+<section class="tests">
+  <p class="default">CDGÂÂÄ</p>
+	<p class="test">CDGÂÂÄ</p>
+	<p class="ref">AAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-03.html b/css/css-fonts/font-default-03.html
new file mode 100644
index 0000000..a72a4bc
--- /dev/null
+++ b/css/css-fonts/font-default-03.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#default-features">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="match" href="font-default-03-ref.html">
+<meta name="assert" content="These features must always be enabled, even when the value of the ‘font-variant’ and ‘font-feature-settings’ properties is ‘normal’. ">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+  }
+  .test {
+    font-feature-settings: normal;
+  }
+</style>
+
+<body>
+<!-- test font does not currently test for rlig or locl -->
+<p>Test passes if the three lines below are identical, with six check marks (✓). </p>
+<section class="tests">
+  <p class="default">CDGÂÂÄ</p>
+	<p class="test">CDGÂÂÄ</p>
+	<p class="ref">AAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-04-a-ref.html b/css/css-fonts/font-default-04-a-ref.html
new file mode 100644
index 0000000..cf428d2
--- /dev/null
+++ b/css/css-fonts/font-default-04-a-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<meta name="assert" content="For upright text within vertical text runs, vertical alternates (OpenType feature: vert) must be enabled">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+    writing-mode: vertical-rl;
+    text-orientation: upright;
+    color: green;
+  }
+  .default {
+    color: purple;
+  }
+</style>
+
+<body>
+<p>Test passes if the first <em>vertical</em> line (in purple) has <em>at least one</em> check mark
+and the next two  lines (in green) are identical, with two check marks (✓).</p>
+<section class="tests">
+  <p class="default">AB</p>
+	<p><span class="test1">A</span><span class="test2">A</span></p>
+	<p class="ref">AA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-04-b-ref.html b/css/css-fonts/font-default-04-b-ref.html
new file mode 100644
index 0000000..3051b27
--- /dev/null
+++ b/css/css-fonts/font-default-04-b-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<meta name="assert" content="For upright text within vertical text runs, vertical alternates (OpenType feature: vert) must be enabled">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+    writing-mode: vertical-rl;
+    text-orientation: upright;
+    color: green;
+  }
+  .default {
+    color: purple;
+  }
+</style>
+
+<body>
+<p>Test passes if the first <em>vertical</em> line (in purple) has <em>at least one</em> check mark
+and the next two  lines (in green) are identical, with two check marks (✓).</p>
+<section class="tests">
+  <p class="default">BA</p>
+	<p><span class="test1">A</span><span class="test2">A</span></p>
+	<p class="ref">AA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-04-c-ref.html b/css/css-fonts/font-default-04-c-ref.html
new file mode 100644
index 0000000..773acfc
--- /dev/null
+++ b/css/css-fonts/font-default-04-c-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<meta name="assert" content="For upright text within vertical text runs, vertical alternates (OpenType feature: vert) must be enabled">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+    writing-mode: vertical-rl;
+    text-orientation: upright;
+    color: green;
+  }
+  .default {
+    color: purple;
+  }
+</style>
+
+<body>
+<p>Test passes if the first <em>vertical</em> line (in purple) has <em>at least one</em> check mark
+and the next two  lines (in green) are identical, with two check marks (✓).</p>
+<section class="tests">
+  <p class="default">AA</p>
+	<p><span class="test1">A</span><span class="test2">A</span></p>
+	<p class="ref">AA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-default-04.html b/css/css-fonts/font-default-04.html
new file mode 100644
index 0000000..5493d72
--- /dev/null
+++ b/css/css-fonts/font-default-04.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font default features</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#default-features">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#block-flow">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#text-orientation">
+<link rel="match" href="font-default-04-a-ref.html">
+<link rel="match" href="font-default-04-b-ref.html">
+<link rel="match" href="font-default-04-c-ref.html">
+<meta name="assert" content="For upright text within vertical text runs, vertical alternates (OpenType feature: vert) must be enabled">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .tests {
+	  font-family: fwf;
+	  font-size: 4em;
+	  line-height: 1.1;
+    writing-mode: vertical-rl;
+    text-orientation: upright;
+    color: green;
+  }
+  .default {
+    color: purple;
+  }
+  .test1 {
+    font-feature-settings: "vert" on;
+  }
+  .test2 {
+    font-feature-settings: "vert" 2;
+  }
+</style>
+
+<body>
+<p>Test passes if the first <em>vertical</em> line (in purple) has <em>at least one</em> check mark
+and the next two  lines (in green) are identical, with two check marks (✓).</p>
+<section class="tests">
+  <p class="default">Å人</p>
+	<p><span class="test1">Å</span><span class="test2">人</span></p>
+	<p class="ref">AA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-display/font-display-change-ref.html b/css/css-fonts/font-display/font-display-change-ref.html
new file mode 100644
index 0000000..e06a559
--- /dev/null
+++ b/css/css-fonts/font-display/font-display-change-ref.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Updating font-display value while loading</title>
+<script>
+
+window.onload = () => {
+    const displayValues = [ 'auto', 'block', 'swap', 'fallback', 'optional' ];
+
+    for (let value of displayValues) {
+        let face = new FontFace(value,
+                                'url("/fonts/Ahem.ttf?pipe=trickle(d5)")',
+                                {display: value});
+        document.fonts.add(face);
+        face.load();
+    }
+
+    setTimeout(() => {
+        document.documentElement.classList.remove("reftest-wait");
+    }, 200);
+};
+
+</script>
+<table>
+  <tr>
+    <th>auto</th>
+    <th>block</th>
+    <th>swap</th>
+    <th>fallback</th>
+    <th>optional</th>
+  </tr>
+  <tr>
+    <td>from auto</td>
+    <td style="font-family: auto">a</td>
+    <td style="font-family: block">a</td>
+    <td style="font-family: swap">a</td>
+    <td style="font-family: fallback">a</td>
+    <td style="font-family: optional">a</td>
+  </tr>
+  <tr>
+    <td>from block</td>
+    <td style="font-family: auto">a</td>
+    <td style="font-family: block">a</td>
+    <td style="font-family: swap">a</td>
+    <td style="font-family: fallback">a</td>
+    <td style="font-family: optional">a</td>
+  </tr>
+  <tr>
+    <td>from swap</td>
+    <td style="font-family: auto">a</td>
+    <td style="font-family: block">a</td>
+    <td style="font-family: swap">a</td>
+    <td style="font-family: fallback">a</td>
+    <td style="font-family: optional">a</td>
+  </tr>
+  <tr>
+    <td>from fallback</td>
+    <td style="font-family: auto">a</td>
+    <td style="font-family: block">a</td>
+    <td style="font-family: swap">a</td>
+    <td style="font-family: fallback">a</td>
+    <td style="font-family: optional">a</td>
+  </tr>
+  <tr>
+    <td>from optional</td>
+    <td style="font-family: auto">a</td>
+    <td style="font-family: block">a</td>
+    <td style="font-family: swap">a</td>
+    <td style="font-family: fallback">a</td>
+    <td style="font-family: optional">a</td>
+  </tr>
+</table>
+</html>
diff --git a/css/css-fonts/font-display/font-display-change.html b/css/css-fonts/font-display/font-display-change.html
new file mode 100644
index 0000000..64130ca
--- /dev/null
+++ b/css/css-fonts/font-display/font-display-change.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Updating font-display value while loading</title>
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-display-desc">
+<link rel="match" href="font-display-change-ref.html">
+<script>
+
+window.onload = () => {
+    const displayValues = [ 'auto', 'block', 'swap', 'fallback', 'optional' ];
+
+    let updateFuncs = [];
+
+    for (let initialValue of displayValues) {
+        for (let targetValue of displayValues) {
+            let face = new FontFace(initialValue + '-' + targetValue,
+                                    'url("/fonts/Ahem.ttf?pipe=trickle(d5)")',
+                                    {display: initialValue});
+            document.fonts.add(face);
+            face.load();
+            updateFuncs.push(() => {
+                face.display = targetValue;
+            });
+        }
+    }
+
+    setTimeout(() => {
+        for (let update of updateFuncs) {
+            update();
+        }
+        document.documentElement.classList.remove("reftest-wait");
+    }, 200);
+};
+
+</script>
+<table>
+  <tr>
+    <th>auto</th>
+    <th>block</th>
+    <th>swap</th>
+    <th>fallback</th>
+    <th>optional</th>
+  </tr>
+  <tr>
+    <td>from auto</td>
+    <td style="font-family: auto-auto">a</td>
+    <td style="font-family: auto-block">a</td>
+    <td style="font-family: auto-swap">a</td>
+    <td style="font-family: auto-fallback">a</td>
+    <td style="font-family: auto-optional">a</td>
+  </tr>
+  <tr>
+    <td>from block</td>
+    <td style="font-family: block-auto">a</td>
+    <td style="font-family: block-block">a</td>
+    <td style="font-family: block-swap">a</td>
+    <td style="font-family: block-fallback">a</td>
+    <td style="font-family: block-optional">a</td>
+  </tr>
+  <tr>
+    <td>from swap</td>
+    <td style="font-family: swap-auto">a</td>
+    <td style="font-family: swap-block">a</td>
+    <td style="font-family: swap-swap">a</td>
+    <td style="font-family: swap-fallback">a</td>
+    <td style="font-family: swap-optional">a</td>
+  </tr>
+  <tr>
+    <td>from fallback</td>
+    <td style="font-family: fallback-auto">a</td>
+    <td style="font-family: fallback-block">a</td>
+    <td style="font-family: fallback-swap">a</td>
+    <td style="font-family: fallback-fallback">a</td>
+    <td style="font-family: fallback-optional">a</td>
+  </tr>
+  <tr>
+    <td>from optional</td>
+    <td style="font-family: optional-auto">a</td>
+    <td style="font-family: optional-block">a</td>
+    <td style="font-family: optional-swap">a</td>
+    <td style="font-family: optional-fallback">a</td>
+    <td style="font-family: optional-optional">a</td>
+  </tr>
+</table>
+</html>
diff --git a/css/css-fonts/font-display/font-display-preload-ref.html b/css/css-fonts/font-display/font-display-preload-ref.html
new file mode 100644
index 0000000..e3ba8ca
--- /dev/null
+++ b/css/css-fonts/font-display/font-display-preload-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<title>font-display should work for preloaded fonts</title>
+<style>
+  .arial {
+    height: 20px;
+    font-family: Arial;
+  }
+</style>
+<div id="container">
+  <div class="arial"></div>
+  <div class="arial"></div>
+  <div class="arial">Swap</div>
+  <div class="arial">Fallback</div>
+  <div class="arial">Optional</div>
+</div>
+</html>
diff --git a/css/css-fonts/font-display/font-display-preload.html b/css/css-fonts/font-display/font-display-preload.html
new file mode 100644
index 0000000..46f392f
--- /dev/null
+++ b/css/css-fonts/font-display/font-display-preload.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>font-display should work for preloaded fonts</title>
+<link id="link" href="/fonts/Ahem.ttf?pipe=trickle(d5)" as="font" type="font/ttf" crossorigin>
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-display-desc">
+<link rel="match" href="font-display-preload-ref.html">
+<style>
+  @font-face {
+    font-family: 'Auto';
+    src: url('/fonts/Ahem.ttf?pipe=trickle(d5)');
+    font-display: auto;
+  }
+  @font-face {
+    font-family: 'Block';
+    src: url('/fonts/Ahem.ttf?pipe=trickle(d5)');
+    font-display: block;
+  }
+  @font-face {
+    font-family: 'Swap';
+    src: url('/fonts/Ahem.ttf?pipe=trickle(d5)');
+    font-display: swap;
+  }
+  @font-face {
+    font-family: 'Fallback';
+    src: url('/fonts/Ahem.ttf?pipe=trickle(d5)');
+    font-display: fallback;
+  }
+  @font-face {
+    font-family: 'Optional';
+    src: url('/fonts/Ahem.ttf?pipe=trickle(d5)');
+    font-display: optional;
+  }
+</style>
+<div id="container" hidden>
+  <div style="height: 20px; font-family: 'Auto', Arial">Auto</div>
+  <div style="height: 20px; font-family: 'Block', Arial">Block</div>
+  <div style="height: 20px; font-family: 'Swap', Arial">Swap</div>
+  <div style="height: 20px; font-family: 'Fallback', Arial">Fallback</div>
+  <div style="height: 20px; font-family: 'Optional', Arial">Optional</div>
+</div>
+<script>
+    window.onload = () => {
+        document.getElementById('link').rel = 'preload';
+        document.getElementById('container').hidden = false;
+        const timeoutMsec = 200; // Between the short limit and the long limit
+        setTimeout(() => {
+            document.documentElement.classList.remove("reftest-wait");
+        }, timeoutMsec);
+    };
+</script>
+</html>
diff --git a/css/css-fonts/font-display/font-display.html b/css/css-fonts/font-display/font-display.html
index 9336377..7b17460 100644
--- a/css/css-fonts/font-display/font-display.html
+++ b/css/css-fonts/font-display/font-display.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html class="reftest-wait">
 <title>Test for font-display @font-face descriptor</title>
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-display-desc">
 <link rel="match" href="font-display-ref.html">
 <style>
 .hidden { display: none; }
diff --git a/css/css-fonts/font-face-unicode-range-2-ref.html b/css/css-fonts/font-face-unicode-range-2-ref.html
new file mode 100644
index 0000000..ff53ab6
--- /dev/null
+++ b/css/css-fonts/font-face-unicode-range-2-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference: font-face unicode-range</title>
+<style>
+  @font-face {
+    font-family: base;
+    src: url(support/fonts/LigatureSymbolsWithSpaces.woff);
+  }
+  @font-face {
+    font-family: swoosh;
+    src: url(support/fonts/Rochester.otf);
+  }
+  .ref {
+    font-family: base;
+  }
+  .ref .amp {
+	  font-family: swoosh;
+  }
+  div {
+	  font-size: 5em;
+  }
+</style>
+<body>
+  <p>Test passes if the two lines look the same, with just the ampersand in italic</p>
+  <div class="ref">This <span class="amp">&amp;</span> That</div>
+  <div class="ref">This <span class="amp">&amp;</span> That</div>
+</body>
diff --git a/css/css-fonts/font-face-unicode-range-2.html b/css/css-fonts/font-face-unicode-range-2.html
index 9be431b..83eab75 100644
--- a/css/css-fonts/font-face-unicode-range-2.html
+++ b/css/css-fonts/font-face-unicode-range-2.html
@@ -3,6 +3,7 @@
 <title>CSS Test: font-face unicode-range</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#unicode-range-desc">
+<link rel="match" href="font-face-unicode-range-2-ref.html">
 <meta name="flags" content="">
 <meta name="assert" content="Check that font-face unicode-range restrics use of glyphs outside that range">
 <style>
diff --git a/css/css-fonts/font-feature-settings-descriptor-01-ref.html b/css/css-fonts/font-feature-settings-descriptor-01-ref.html
new file mode 100644
index 0000000..4a1723e
--- /dev/null
+++ b/css/css-fonts/font-feature-settings-descriptor-01-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test: font-feature-settings descriptor</title>
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .test {
+	  font-family: fwf;
+	  font-size: 2.4em;
+	  line-height: 1.1;
+  }
+</style>
+
+<body>
+<p>Test passes if the three lines below are identical, with eight check marks (✓). </p>
+<section class="test">
+    <p class="ref">AAAAAAAA</p>
+    <p class="ref">AAAAAAAA</p>
+    <p class="ref">AAAAAAAA</p>
+</section>
diff --git a/css/css-fonts/font-feature-settings-descriptor-01.html b/css/css-fonts/font-feature-settings-descriptor-01.html
new file mode 100644
index 0000000..8f7c506
--- /dev/null
+++ b/css/css-fonts/font-feature-settings-descriptor-01.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test: font-feature-settings descriptor</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-rend-desc">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="match" href="font-feature-settings-descriptor-01-ref.html">
+<meta name="assert" content="These descriptors define initial settings that apply when the font defined by an @font-face rule is rendered">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  @font-face {
+    font-family: fwf2;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+    font-feature-settings: "liga" on, "clig" on, "calt" on, "hlig" on, "dlig" on, "onum" on, "smcp" on, "jp90" on;
+  }
+  .test {
+	  font-family: fwf;
+	  font-size: 2.4em;
+	  line-height: 1.1;
+  }
+  .test>p.descriptor {
+      font-family: fwf2;
+  }
+  .property {
+    font-feature-settings: "liga" on, "clig" on, "calt" on, "hlig" on, "dlig" on, "onum" on, "smcp" on, "jp90" on;
+  }
+</style>
+
+<body>
+<p>Test passes if the three lines below are identical, with eight check marks (✓). </p>
+<section class="test">
+    <p class="descriptor">CDGFEJQa</p>
+	<p class="property">CDGFEJQa</p>
+	<p class="ref">AAAAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-feature-settings-serialization-001.html b/css/css-fonts/font-feature-settings-serialization-001.html
new file mode 100644
index 0000000..031dd7d
--- /dev/null
+++ b/css/css-fonts/font-feature-settings-serialization-001.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: font-feature-settings serialization</title>
+<link rel="author" title="Chris Nardi" href="mailto:cnardi@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-3/#feature-tag-value">
+<link rel="help" href="https://drafts.csswg.org/cssom/#common-serializing-idioms">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+    #test {
+        font-feature-settings: "vert" 1;
+    }
+    #test1 {
+        font-feature-settings: 'vert' 1;
+    }
+    #test2 {
+        font-feature-settings: "vert" off;
+    }
+</style>
+<div id="test"></div>
+<div id="test1"></div>
+<div id="test2"></div>
+<script>
+    const div = document.querySelector("#test");
+    const div1 = document.querySelector("#test1");
+    const div2 = document.querySelector("#test2");
+    test(function() {
+        assert_equals(getComputedStyle(div).fontFeatureSettings, '"vert"');
+        assert_equals(getComputedStyle(div1).fontFeatureSettings, '"vert"');
+    }, "font-feature-settings should be serialized with double quotes, and the default value of 1 should be omitted");
+    test(function() {
+        assert_equals(getComputedStyle(div2).fontFeatureSettings, '"vert" 0');
+    }, "font-feature-settings should serialize 0 instead of off, given it's shorter");
+</script>
diff --git a/css/css-fonts/font-shorthand-serialization-001.html b/css/css-fonts/font-shorthand-serialization-001.html
new file mode 100644
index 0000000..e44cc7b
--- /dev/null
+++ b/css/css-fonts/font-shorthand-serialization-001.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: font shorthand serialization</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#propdef-font">
+<link rel="help" href="https://drafts.csswg.org/cssom/">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1564">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436031">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div style="font: bold medium serif"></div>
+<script>
+test(function() {
+  assert_equals(
+    document.querySelector('div').style.font, "bold medium serif",
+    "Default values of the longhands don't get serialized on the font shorthand"
+  );
+}, "Default values of the longhands don't get serialized on the font shorthand");
+</script>
diff --git a/css/css-fonts/font-size-adjust-001-ref.html b/css/css-fonts/font-size-adjust-001-ref.html
new file mode 100644
index 0000000..b608c09
--- /dev/null
+++ b/css/css-fonts/font-size-adjust-001-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Fonts Reference: font-size-adjust - greater than aspect value of font</title>
+<style>
+  div {
+    position: absolute;
+    font: 40px/40px Ahem;
+    color: orange;
+  }
+  #test {
+    color: blue;
+    font-size: 45px;
+  }
+</style>
+<body>
+  <p>Test passes if the size of the blue rectangle is greater than the orange rectangle.</p>
+  <div id="test">FillerText</div>
+  <div>FillerText</div>
+</body>
diff --git a/css/css-fonts/font-size-adjust-001.html b/css/css-fonts/font-size-adjust-001.html
index d2d1241..b4b1c75 100644
--- a/css/css-fonts/font-size-adjust-001.html
+++ b/css/css-fonts/font-size-adjust-001.html
@@ -9,8 +9,9 @@
 is greater than the aspect value of font">
 <style>
   div {
-    font-family: Ahem;
-    font-size: 40px;
+    position: absolute;
+    font: 40px/40px Ahem;
+    color: orange;
   }
   #test {
     color: blue;
@@ -18,7 +19,7 @@
   }
 </style>
 <body>
-  <p>Test passes if the size of blue rectangle is greater than the black rectangle.</p>
-  <div>FillerText</div>
+  <p>Test passes if the size of the blue rectangle is greater than the orange rectangle.</p>
   <div id="test">FillerText</div>
+  <div>FillerText</div>
 </body>
diff --git a/css/css-fonts/font-size-adjust-002-ref.html b/css/css-fonts/font-size-adjust-002-ref.html
new file mode 100644
index 0000000..175802c
--- /dev/null
+++ b/css/css-fonts/font-size-adjust-002-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Fonts Reference: font-size-adjust - less than aspect value of font</title>
+<style>
+  div {
+    position: absolute;
+    font: 40px/40px Ahem;
+    color: blue;
+  }
+  #test {
+    color: orange;
+    font-size: 10px;
+  }
+</style>
+<body>
+  <p>Test passes if the size of the blue rectangle is greater than the orange rectangle.</p>
+  <div>FillerText</div>
+  <div id="test">FillerText</div>
+</body>
diff --git a/css/css-fonts/font-size-adjust-002.html b/css/css-fonts/font-size-adjust-002.html
index d157f13..e1e4768 100644
--- a/css/css-fonts/font-size-adjust-002.html
+++ b/css/css-fonts/font-size-adjust-002.html
@@ -4,21 +4,23 @@
 <link rel="author" title="Intel" href="http://www.intel.com/">
 <link rel="author" title="Shiyou Tan" href="mailto:shiyoux.tan@intel.com">
 <link rel="help" title="3.6 Relative sizing: the font-size-adjust property" href="http://www.w3.org/TR/css-fonts-3/#font-size-adjust-prop">
+<link rel="match" href="font-size-adjust-002-ref.html">
 <meta name="flags" content="Ahem">
 <meta name="assert" content="Test checks that the actual font size is less than the specified size when the value of font-size-adjust
 is less than the aspect value of font">
 <style>
   div {
-    font-family: Ahem;
-    font-size: 40px;
+    position: absolute;
+    font: 40px/40px Ahem;
+    color: blue;
   }
   #test {
-    color: blue;
+    color: orange;
     font-size-adjust: 0.2;
   }
 </style>
 <body>
-  <p>Test passes if the size of blue rectangle is less than the black rectangle.</p>
+  <p>Test passes if the size of the blue rectangle is greater than the orange rectangle.</p>
   <div>FillerText</div>
   <div id="test">FillerText</div>
 </body>
diff --git a/css/css-fonts/font-variant-01.html b/css/css-fonts/font-variant-01.html
index e73b1d5..ed69967 100644
--- a/css/css-fonts/font-variant-01.html
+++ b/css/css-fonts/font-variant-01.html
@@ -23,9 +23,9 @@
   }
   .outer {
     font-variant-ligatures: common-ligatures  discretionary-ligatures historical-ligatures contextual;
-    font-variant-position: sub;
+    font-variant-numeric: oldstyle-nums;
     font-variant-caps: small-caps;
-    font-variant-east-asian: jis04;
+    font-variant-east-asian: jis90;
   }
   .child {
     color: green;
@@ -36,6 +36,6 @@
 <p>Test passes if the two lines below are identical, with (in purple) eight check marks (✓),
 and then (in green) three check marks (✓) followed by five crosses (✗). </p>
 <section class="test">
-	<p class="outer">CDGFEJHa<span class="inner child">CDGFEJHa</span></p>
+	<p class="outer">CDGFEJQa<span class="inner child">CDGFEJQa</span></p>
 	<p class="ref">AAAAAAAA<span class="child">AAABBBBB</span></p>
 </section>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-02.html b/css/css-fonts/font-variant-02.html
index 6e1aecc..acda863 100644
--- a/css/css-fonts/font-variant-02.html
+++ b/css/css-fonts/font-variant-02.html
@@ -23,9 +23,9 @@
   }
   .outer {
     font-variant-ligatures: common-ligatures  discretionary-ligatures historical-ligatures contextual;
-    font-variant-position: sub;
+    font-variant-numeric: oldstyle-nums;
     font-variant-caps: small-caps;
-    font-variant-east-asian: jis04;
+    font-variant-east-asian: jis90;
   }
   .child {
     color: green;
@@ -36,6 +36,6 @@
 <p>Test passes if the two lines below are identical, with (in purple) eight check marks (✓),
 and then (in green) eight crosses (✗). </p>
 <section class="test">
-	<p class="outer">CDGFEJHa<span class="inner child">CDGFEJHa</span></p>
+	<p class="outer">CDGFEJQa<span class="inner child">CDGFEJQa</span></p>
 	<p class="ref">AAAAAAAA<span class="child">BBBBBBBB</span></p>
 </section>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-03.html b/css/css-fonts/font-variant-03.html
index 3da212a..7e130cc 100644
--- a/css/css-fonts/font-variant-03.html
+++ b/css/css-fonts/font-variant-03.html
@@ -22,7 +22,7 @@
 	  font-variant: normal;
   }
   .outer {
-    font-feature-settings: "liga" on, "clig" on, "calt" on, "hlig" on, "dlig" on, "subs" on, "smcp" on, "jp04" on;
+    font-feature-settings: "liga" on, "clig" on, "calt" on, "hlig" on, "dlig" on, "onum" on, "smcp" on, "jp90" on;
   }
   .child {
     color: green;
@@ -33,6 +33,6 @@
 <p>Test passes if the two lines below are identical, with (in purple) eight check marks (✓),
 and then (in green) eight check marks (✓). </p>
 <section class="test">
-	<p class="outer">CDGFEJHa<span class="inner child">CDGFEJHa</span></p>
+	<p class="outer">CDGFEJQa<span class="inner child">CDGFEJQa</span></p>
 	<p class="ref">AAAAAAAA<span class="child">AAAAAAAA</span></p>
 </section>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-04.html b/css/css-fonts/font-variant-04.html
index 12aeaf5..be42639 100644
--- a/css/css-fonts/font-variant-04.html
+++ b/css/css-fonts/font-variant-04.html
@@ -22,7 +22,7 @@
 	  font-variant: none;
   }
   .outer {
-    font-feature-settings: "liga" on, "clig" on, "calt" on, "hlig" on, "dlig" on, "subs" on, "smcp" on, "jp04" on;
+    font-feature-settings: "liga" on, "clig" on, "calt" on, "hlig" on, "dlig" on, "onum" on, "smcp" on, "jp90" on;
   }
   .child {
     color: green;
@@ -33,6 +33,6 @@
 <p>Test passes if the two lines below are identical, with (in purple) eight check marks (✓),
 and then (in green) eight check marks (✓). </p>
 <section class="test">
-	<p class="outer">CDGFEJHa<span class="inner child">CDGFEJHa</span></p>
+	<p class="outer">CDGFEJQa<span class="inner child">CDGFEJQa</span></p>
 	<p class="ref">AAAAAAAA<span class="child">AAAAAAAA</span></p>
 </section>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-05-ref.xht b/css/css-fonts/font-variant-05-ref.xht
new file mode 100644
index 0000000..f3c2d5f
--- /dev/null
+++ b/css/css-fonts/font-variant-05-ref.xht
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>CSS Test:  font-variant: none; low level equivalence</title>
+<link rel="author" title="Mike Bremford" href="mike@bfo.com" />
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .test {
+    font-family: fwf;
+    font-size: 2.4em;
+    line-height: 0.5;
+    color: purple;
+  }
+</style>
+</head>
+<body>
+<p>Test passes if the six lines below are identical, with five check marks.</p>
+<section class="test">
+  <p>AAAAA</p>
+  <p>AAAAA</p>
+  <p>AAAAA</p>
+  <p>AAAAA</p>
+  <p>AAAAA</p>
+  <p>AAAAA</p>
+</section>
+</body>
+</html>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-05.xht b/css/css-fonts/font-variant-05.xht
new file mode 100644
index 0000000..8321a8d
--- /dev/null
+++ b/css/css-fonts/font-variant-05.xht
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>CSS Test:  font-variant descriptor in @font-face rule is overriden by equivalent style rules</title>
+<link rel="author" title="Mike Bremford" href="mike@bfo.com" />
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-prop" />
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-rend-desc" />
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop" />
+<link rel="match" href="font-variant-05-ref.xht" />
+<meta name="assert" content="Setting ‘font-variant-ligatures’ properties will override the same properties set in the @font-face rule. But properties set there and not explicitly turned off or on later remain set." />
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+    font-variant: no-common-ligatures no-contextual discretionary-ligatures historical-ligatures;
+  }
+  .test {
+    font-family: fwf;
+    font-size: 2.4em;
+    line-height: 0.5;
+    color: purple;
+  }
+  .line1 { }
+  .line2 { font-variant-ligatures: normal; }
+  .line3 { font-variant-ligatures: common-ligatures contextual; }
+  .line4 { font-variant-ligatures: common-ligatures contextual discretionary-ligatures historical-ligatures; }
+  .line5 { font-feature-settings: "liga" on, "clig" on, "calt" on, "hlig" on, "dlig" on }
+  /**
+   * Some notes on this test case, which aims to explore the
+   * font-variant precedence rules and the exact value of
+   * "normal" for font-variant-ligatures.
+   *
+   * line1:
+   *   font-variant is completely set in the @font-face rule.
+   *
+   * line2:
+   *   font-variant-ligatures: normal will reset "common-ligatures"
+   *   and "contextual" both to true. Discretionary and historical
+   *   ligatures are off by default, but they are not explicitly
+   *   disabled with a value of "normal". As stated in section 7.1,
+   *   "Individual features are only disabled when explicitly
+   *   overridden by the author". These features were turned on in
+   *   the @font-face rule, so remain on.
+   *
+   * line3:
+   *   identical to line2 except that instead of "normal" for
+   *   font-variant-ligatures we use the expanded equivalent,
+   *   explicitly turning on common-ligatures and contextual, and
+   *   leaving discretionary and historical unchanged.
+   *
+   * line4:
+   *   we explicitly reset all values of font-variant-ligatures
+   *   properties to the same values as are set in the @font-face
+   *   rule.
+   */
+</style>
+</head>
+<body>
+<p>Test passes if the six lines below are identical, with five check marks.</p>
+<section class="test">
+  <p class="line1">AAAFE</p>
+  <p class="line2">CDGFE</p>
+  <p class="line3">CDGFE</p>
+  <p class="line4">CDGFE</p>
+  <p class="line5">CDGFE</p>
+  <p>AAAAA</p>
+</section>
+</body>
+</html>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-06-ref.xht b/css/css-fonts/font-variant-06-ref.xht
new file mode 100644
index 0000000..a85f80a
--- /dev/null
+++ b/css/css-fonts/font-variant-06-ref.xht
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>CSS Test:  font-variant in @font-face rule is overriden by equivalent style rules</title>
+<link rel="author" title="Mike Bremford" href="mike@bfo.com" />
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#feature-precedence" />
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .test {
+    font-family: fwf;
+    font-size: 2.4em;
+    color: green;
+  }
+</style>
+</head>
+<body>
+<p>Test passes if there are nine check marks below and no red visible.</p>
+<section class="test">
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+</section>
+</body>
+</html>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-06.xht b/css/css-fonts/font-variant-06.xht
new file mode 100644
index 0000000..69749e7
--- /dev/null
+++ b/css/css-fonts/font-variant-06.xht
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>CSS Test: font-variant descriptor in @font-face rule is overriden by equivalent style rules</title>
+<link rel="author" title="Mike Bremford" href="mike@bfo.com" />
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-prop" />
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-rend-desc" />
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#feature-precedence" />
+<link rel="match" href="font-variant-06-ref.xht" />
+<meta name="assert" content="‘font-variant-numeric’ cannot turn OFF features turned ON in @font-face" />
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+    font-variant: oldstyle-nums;
+  }
+  .test, .ref {
+    font-family: fwf;
+    font-size: 2.4em;
+    position: absolute;
+  }
+  .test {
+      color: green;
+  }
+  .ref {
+      color: red;
+  }
+  .t1 { }
+  .t2 { font-variant-numeric: oldstyle-nums; }
+  .t3 { font-variant-numeric: normal; }
+  .t4 { font-variant-numeric: slashed-zero; }
+  .t5 { font-variant-numeric: invalid-value; }
+  .t6 { font-variant: none; }
+  .t7 { font-variant: normal; }
+  .t8 { font-variant: initial; }
+  .t9 { font-variant: invalid-value; }
+  /**
+   * Some notes on this test.
+   *
+   * t1
+   *   the font-variant property from @font-face is applied.
+   * t2
+   *   simply restating the value set in the @font-face rule
+   * t3
+   *   "font-variant-numeric: normal" does not turn off the "on" value
+   *   for oldstyle-nums, which remains on as set in @font-face
+   * t4
+   *   "font-variant-numeric: slashed-zero" is valid, but does not
+   *   change the "on" value for oldstyle-nums as set in @font-face
+   * t6, t7, t8
+       these font-variant values all set font-variant-numeric to normal
+   * t5, r9
+   *   an invalid value means the rule is invalid, and should be ignored.
+   */
+</style>
+</head>
+<body>
+<p>Test passes if there are nine check marks below and no red visible.</p>
+<section class="ref">
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+  <span>A</span>
+</section>
+<section class="test">
+  <span class="t1">Q</span>
+  <span class="t2">Q</span>
+  <span class="t3">Q</span>
+  <span class="t4">Q</span>
+  <span class="t5">Q</span>
+  <span class="t6">Q</span>
+  <span class="t7">Q</span>
+  <span class="t8">Q</span>
+  <span class="t9">Q</span>
+</section>
+</body>
+</html>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-alternates-01-ref.html b/css/css-fonts/font-variant-alternates-01-ref.html
index 4bde365..0ded6cd 100644
--- a/css/css-fonts/font-variant-alternates-01-ref.html
+++ b/css/css-fonts/font-variant-alternates-01-ref.html
@@ -10,17 +10,15 @@
   }
   .test {
 	  font-family: fwf;
-	  font-size: 3em;
+	  font-size: 2em;
 	  line-height: 1.1;
   }
 </style>
 <body>
 
-<!-- very minimal test, only tests hist, does not test salt or stylistic sets -->
-
-<p>Test passes if the three lines below are identical, with one cross (✗)</p>
+<p>Test passes if the three lines below are identical, with twenty crosses (✗).</p>
 <section class="test">
-	<p class="ref">B</p>
-  <p class="ref">B</p>
-	<p class="ref">B</p>
-</section>
\ No newline at end of file
+  <p class="ref">BBBBBBBBBBBBBBBBBBBB</p>
+  <p class="ref">BBBBBBBBBBBBBBBBBBBB</p>
+  <p class="ref">BBBBBBBBBBBBBBBBBBBB</p>
+</section>
diff --git a/css/css-fonts/font-variant-alternates-01.html b/css/css-fonts/font-variant-alternates-01.html
index 89cfe2b..82c465f 100644
--- a/css/css-fonts/font-variant-alternates-01.html
+++ b/css/css-fonts/font-variant-alternates-01.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: normal; low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-01-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <meta name="assert" content="None of the features listed below are enabled. ">
 <style>
   @font-face {
@@ -28,10 +28,9 @@
 </style>
 <body>
 
-<p>Test passes if the three lines below are identical, with twenty crosses (✗)
-<!--, followed by one check mark (✓)-->. </p>
+<p>Test passes if the three lines below are identical, with twenty crosses (✗).</p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
-	<p class="low">Xnophijklmqrstuvwxyz</p>
-	<p class="ref">BBBBBBBBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+  <p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="low">Xnophijklmqrstuvwxyz</p>
+  <p class="ref">BBBBBBBBBBBBBBBBBBBB</p>
+</section>
diff --git a/css/css-fonts/font-variant-alternates-02-ref.html b/css/css-fonts/font-variant-alternates-02-ref.html
index d27ffa2..a74c520 100644
--- a/css/css-fonts/font-variant-alternates-02-ref.html
+++ b/css/css-fonts/font-variant-alternates-02-ref.html
@@ -10,7 +10,7 @@
   }
   .test {
 	  font-family: fwf;
-	  font-size: 3em;
+	  font-size: 2em;
 	  line-height: 1.1;
   }
 </style>
@@ -18,9 +18,9 @@
 
 <!-- very minimal test, only tests hist, does not test salt or stylistic sets -->
 
-<p>Test passes if the three lines below are identical, with onecheck mark (✓). </p>
+<p>Test passes if the three lines below are identical, with one check mark (✓) followed by nineteen crosses (✗). </p>
 <section class="test">
-	<p class="ref">A</p>
-  <p class="ref">A</p>
-	<p class="ref">A</p>
-</section>
\ No newline at end of file
+  <p class="ref">ABBBBBBBBBBBBBBBBBBB</p>
+  <p class="ref">ABBBBBBBBBBBBBBBBBBB</p>
+  <p class="ref">ABBBBBBBBBBBBBBBBBBB</p>
+</section>
diff --git a/css/css-fonts/font-variant-alternates-02.html b/css/css-fonts/font-variant-alternates-02.html
index b9f777e..9610508 100644
--- a/css/css-fonts/font-variant-alternates-02.html
+++ b/css/css-fonts/font-variant-alternates-02.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: historical-forms; low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-02-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <meta name="assert" content="Enables display of historical forms (OpenType feature: hist)">
 <style>
   @font-face {
@@ -20,7 +20,7 @@
   .high {
 	 font-variant-alternates: historical-forms;
   }
-    .low {
+  .low {
    font-feature-settings: "hist" on, "salt" 00, "ss01" off, "ss02" off, "ss03" off,
     "cv01" off, "cv02" off, "cv03" off,  "swsh" 00, "cswh" 00, "ornm" 00, "nalt" 00;
   }
@@ -29,7 +29,7 @@
 
 <p>Test passes if the three lines below are identical, with one check mark (✓) followed by nineteen crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">ABBBBBBBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-03.html b/css/css-fonts/font-variant-alternates-03.html
index 6b2eba9..4270e2e 100644
--- a/css/css-fonts/font-variant-alternates-03.html
+++ b/css/css-fonts/font-variant-alternates-03.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: stylistic(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-03-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of stylistic alternates (font specific, OpenType feature: salt &lt;feature-index&gt;)">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with one cross (✗), one check mark (✓) followed by eighteen crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BABBBBBBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-04.html b/css/css-fonts/font-variant-alternates-04.html
index 35b9940..abd361b 100644
--- a/css/css-fonts/font-variant-alternates-04.html
+++ b/css/css-fonts/font-variant-alternates-04.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: stylistic(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-04-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of stylistic alternates (font specific, OpenType feature: salt &lt;feature-index&gt;)">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with two crosses (✗), one check mark (✓) followed by seventeen crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBABBBBBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-05-ref.html b/css/css-fonts/font-variant-alternates-05-ref.html
index 7cb557e..4942718 100644
--- a/css/css-fonts/font-variant-alternates-05-ref.html
+++ b/css/css-fonts/font-variant-alternates-05-ref.html
@@ -16,9 +16,9 @@
 </style>
 <body>
 
-<p>Test passes if the three lines below are identical, with two crosses (✗), one check mark (✓) followed by seventeen crosses (✗). </p>
+<p>Test passes if the three lines below are identical, with three crosses (✗), one check mark (✓) followed by sixteen crosses (✗). </p>
 <section class="test">
   <p class="ref">BBBABBBBBBBBBBBBBBBB</p>
   <p class="ref">BBBABBBBBBBBBBBBBBBB</p>
   <p class="ref">BBBABBBBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-05.html b/css/css-fonts/font-variant-alternates-05.html
index 4f900cc..5ea6450 100644
--- a/css/css-fonts/font-variant-alternates-05.html
+++ b/css/css-fonts/font-variant-alternates-05.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: stylistic(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-05-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of stylistic alternates (font specific, OpenType feature: salt &lt;feature-index&gt;)">
 <style>
@@ -35,9 +35,9 @@
 </style>
 <body>
 
-<p>Test passes if the three lines below are identical, with two crosses (✗), one check mark (✓) followed by seventeen crosses (✗). </p>
+<p>Test passes if the three lines below are identical, with three crosses (✗), one check mark (✓) followed by sixteen crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBABBBBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-06.html b/css/css-fonts/font-variant-alternates-06.html
index fe15c21..bbeca54 100644
--- a/css/css-fonts/font-variant-alternates-06.html
+++ b/css/css-fonts/font-variant-alternates-06.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: styleset(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-06-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display with stylistic sets (font specific, OpenType feature: ss&lt;feature-index&gt;">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with four crosses (✗), one check mark (✓) followed by fifteen crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBABBBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-07.html b/css/css-fonts/font-variant-alternates-07.html
index 70c1138..fdcba5f 100644
--- a/css/css-fonts/font-variant-alternates-07.html
+++ b/css/css-fonts/font-variant-alternates-07.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: styleset(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-07-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display with stylistic sets (font specific, OpenType feature: ss&lt;feature-index&gt;">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with five crosses (✗), one check mark (✓) followed by fourteen crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBABBBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-08.html b/css/css-fonts/font-variant-alternates-08.html
index 3228adb..4ec305b 100644
--- a/css/css-fonts/font-variant-alternates-08.html
+++ b/css/css-fonts/font-variant-alternates-08.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: styleset(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-08-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display with stylistic sets (font specific, OpenType feature: ss&lt;feature-index&gt;">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with six crosses (✗), one check mark (✓) followed by thirteen crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBABBBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-09.html b/css/css-fonts/font-variant-alternates-09.html
index 71fda68..7cc6a1a 100644
--- a/css/css-fonts/font-variant-alternates-09.html
+++ b/css/css-fonts/font-variant-alternates-09.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: character-variant(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-09-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of specific character variants (font specific, OpenType feature: cv&lt;feature-index&gt;">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with seven crosses (✗), one check mark (✓) followed by twelve crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBABBBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-10.html b/css/css-fonts/font-variant-alternates-10.html
index d4054ac..ac8341d 100644
--- a/css/css-fonts/font-variant-alternates-10.html
+++ b/css/css-fonts/font-variant-alternates-10.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: character-variant(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-10-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of specific character variants (font specific, OpenType feature: cv&lt;feature-index&gt;">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with eight crosses (✗), one check mark (✓) followed by eleven crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBABBBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-11.html b/css/css-fonts/font-variant-alternates-11.html
index 19e38ed..2e73fc0 100644
--- a/css/css-fonts/font-variant-alternates-11.html
+++ b/css/css-fonts/font-variant-alternates-11.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: character-variant(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-11-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of specific character variants (font specific, OpenType feature: cv&lt;feature-index&gt;">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with nine crosses (✗), one check mark (✓) followed by ten crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBABBBBBBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-12.html b/css/css-fonts/font-variant-alternates-12.html
index a0061a4..12f1cc7 100644
--- a/css/css-fonts/font-variant-alternates-12.html
+++ b/css/css-fonts/font-variant-alternates-12.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: swash(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-12-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of swash glyphs (font specific, OpenType feature: swsh &lt;feature-index&gt;, cswh &lt;feature-index&gt;).">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with ten crosses (✗), one check mark (✓), two crosses (✗), one check mark (✓) followed by six crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBBABBABBBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-13.html b/css/css-fonts/font-variant-alternates-13.html
index b9fdd49..872c7dd 100644
--- a/css/css-fonts/font-variant-alternates-13.html
+++ b/css/css-fonts/font-variant-alternates-13.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: swash(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-13-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of swash glyphs (font specific, OpenType feature: swsh &lt;feature-index&gt;, cswh &lt;feature-index&gt;).">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with eleven crosses (✗), one check mark (✓), two crosses (✗), one check mark (✓) followed by five crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBBBABBABBBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-14.html b/css/css-fonts/font-variant-alternates-14.html
index 1d57c2c..45c2d02 100644
--- a/css/css-fonts/font-variant-alternates-14.html
+++ b/css/css-fonts/font-variant-alternates-14.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: swash(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-14-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of swash glyphs (font specific, OpenType feature: swsh &lt;feature-index&gt;, cswh &lt;feature-index&gt;).">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with twelve crosses (✗), one check mark (✓), two crosses (✗), one check mark (✓) followed by four crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBBBBABBABBBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-15.html b/css/css-fonts/font-variant-alternates-15.html
index 95ec0fa..becb4d4 100644
--- a/css/css-fonts/font-variant-alternates-15.html
+++ b/css/css-fonts/font-variant-alternates-15.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: ornaments(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-15-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables replacement of default glyphs with ornaments, if provided in the font (font specific, OpenType feature: ornm &lt;feature-index&gt;).">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with sixteen crosses (✗), one check mark (✓) followed by three crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBBBBBBBBABBB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-16.html b/css/css-fonts/font-variant-alternates-16.html
index 21215dd..8353073 100644
--- a/css/css-fonts/font-variant-alternates-16.html
+++ b/css/css-fonts/font-variant-alternates-16.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: ornaments(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-16-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables replacement of default glyphs with ornaments, if provided in the font (font specific, OpenType feature: ornm &lt;feature-index&gt;).">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with seventeen crosses (✗), one check mark (✓) followed by two crosses (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBBBBBBBBBABB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-17.html b/css/css-fonts/font-variant-alternates-17.html
index abbe873..799bf65 100644
--- a/css/css-fonts/font-variant-alternates-17.html
+++ b/css/css-fonts/font-variant-alternates-17.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: ornaments(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-17-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables replacement of default glyphs with ornaments, if provided in the font (font specific, OpenType feature: ornm &lt;feature-index&gt;).">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with eighteen crosses (✗), one check mark (✓) followed by one cross (✗). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBBBBBBBBBBAB</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-18.html b/css/css-fonts/font-variant-alternates-18.html
index f58ab12..8dfe0b8 100644
--- a/css/css-fonts/font-variant-alternates-18.html
+++ b/css/css-fonts/font-variant-alternates-18.html
@@ -4,8 +4,8 @@
 <title>CSS Test:  font-variant-alternates: ornaments(); low level equivalence</title>
 <link rel="author" title="Chris Lilley" href="chris@w3.org">
 <link rel="match" href="font-variant-alternates-18-ref.html">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-alternates-prop">
-<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-feature-settings-prop">
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-feature-values">
 <meta name="assert" content="Enables display of alternate annotation forms (font specific, OpenType feature: nalt &lt;feature-index&gt;).">
 <style>
@@ -37,7 +37,7 @@
 
 <p>Test passes if the three lines below are identical, with nineteen crosses (✗), then one check mark (✓). </p>
 <section class="test">
-	<p class="high">Xnophijklmqrstuvwxyz</p>
+  <p class="high">Xnophijklmqrstuvwxyz</p>
   <p class="low">Xnophijklmqrstuvwxyz</p>
   <p class="ref">BBBBBBBBBBBBBBBBBBBA</p>
-</section>
\ No newline at end of file
+</section>
diff --git a/css/css-fonts/font-variant-alternates-parsing.html b/css/css-fonts/font-variant-alternates-parsing.html
index a02bced..f00fa53 100644
--- a/css/css-fonts/font-variant-alternates-parsing.html
+++ b/css/css-fonts/font-variant-alternates-parsing.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <title>CSS Test:  font-variant-alternates: historical-forms; parses case-insensitively</title>
 <link rel="author" title="Emilio Cobos Álvarez" href="emilio@crisal.io">
-<link rel="help" href="https://drafts.csswg.org/css-fonts/#font-variant-alternates-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-variant-alternates-prop">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
diff --git a/css/css-fonts/font-variant-descriptor-01-ref.html b/css/css-fonts/font-variant-descriptor-01-ref.html
new file mode 100644
index 0000000..6a152db
--- /dev/null
+++ b/css/css-fonts/font-variant-descriptor-01-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font-variant: normal; low level equivalence</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  .test {
+	  font-family: fwf;
+	  font-size: 2.4em;
+	  line-height: 1.1;
+    color: purple;
+  }
+</style>
+
+<body>
+<p>Test passes if the three lines below are identical, with (in purple) eight check marks (✓)</p>
+<section class="test">
+    <p class="ref">AAAAAAAA</p>
+    <p class="ref">AAAAAAAA</p>
+	<p class="ref">AAAAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-variant-descriptor-01.html b/css/css-fonts/font-variant-descriptor-01.html
new file mode 100644
index 0000000..ba4ba86
--- /dev/null
+++ b/css/css-fonts/font-variant-descriptor-01.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title>CSS Test:  font-variant: normal; low level equivalence</title>
+<link rel="author" title="Chris Lilley" href="chris@w3.org">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-variant-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+<link rel="help" href="https://www.w3.org/TR/css-fonts-3/#font-rend-desc">
+<link rel="match" href="font-variant-descriptor-01-ref.html">
+<meta name="assert" content="These descriptors define initial settings that apply when the font defined by an @font-face rule is rendered">
+<style>
+  @font-face {
+    font-family: fwf;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+  }
+  @font-face {
+    font-family: fwf2;
+    src: url(support/fonts/FontWithFancyFeatures.otf);
+    font-variant: common-ligatures  discretionary-ligatures historical-ligatures contextual oldstyle-nums small-caps jis90;
+  }
+  .test {
+	  font-family: fwf;
+	  font-size: 2.4em;
+	  line-height: 1.1;
+    color: purple;
+  }
+  .property {
+    font-variant-ligatures: common-ligatures  discretionary-ligatures historical-ligatures contextual;
+    font-variant-numeric: oldstyle-nums;
+    font-variant-caps: small-caps;
+    font-variant-east-asian: jis90;
+  }
+  .test>p.descriptor {
+    font-family: fwf2;
+  }
+  .child {
+    color: green;
+  }
+</style>
+
+<body>
+<p>Test passes if the three lines below are identical, with (in purple) eight check marks (✓)</p>
+<section class="test">
+    <p class="descriptor">CDGFEJQa</p>
+	<p class="property">CDGFEJQa</p>
+	<p class="ref">AAAAAAAA</p>
+</section>
\ No newline at end of file
diff --git a/css/css-fonts/font-variation-settings-serialization-001.html b/css/css-fonts/font-variation-settings-serialization-001.html
new file mode 100644
index 0000000..65178c9
--- /dev/null
+++ b/css/css-fonts/font-variation-settings-serialization-001.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>CSS Test: font-feature-settings serialization</title>
+        <link rel="author" title="Greg Whitworth" href="mailto:gwhit@microsoft.com">
+        <link rel="help" href="http://www.w3.org/TR/css-fonts-4/#propdef-font-variation-settings">
+        <link rel="issue" href="https://github.com/w3c/csswg-drafts/issues/1959#issuecomment-360864254">
+        <meta name="desctiption" content="It was decided in issue 1959 that font-variation-settings is a map, therefor duplicates should be removed">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+    </head>
+    <body>
+        <div id="test1" style='font-variation-settings: "bldA" 1, "bldA" 2'></div>
+
+        <script>
+            const test1_canEqual = ['"bldA" 2', "'bldA' 2"];
+            test(function() {
+                assert_in_array(getComputedStyle(document.getElementById('test1')).fontVariationSettings, test1_canEqual);
+            }, "font-feature-settings should be serialized to not include duplicates");
+        </script>
+    </body>
+</html>
+
diff --git a/css/css-fonts/format-specifiers-variations.html b/css/css-fonts/format-specifiers-variations.html
new file mode 100644
index 0000000..8caf2f3
--- /dev/null
+++ b/css/css-fonts/format-specifiers-variations.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>CSS Test: Supported format specifiers should load</title>
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#src-desc"/>
+<meta name="assert" content="Supported format specifiers should load"/>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+setup({
+    explicit_done: true
+});
+
+var formatSpecifiers = [
+    "woff",
+    "truetype",
+    "opentype",
+    "woff2",
+    "woff-variations",
+    "truetype-variations",
+    "opentype-variations",
+    "woff2-variations"
+];
+
+var defaultAhemSrc = 'url("/fonts/Ahem.ttf") format("INSERT_FORMAT")';
+
+function runTestOnFormatSpecifiers(formats, expectFail) {
+    for (var i = 0; i < formats.length; ++i) {
+        promise_test((testDetails) => {
+            var familyName = "load_ahem_" + i;
+            var ahemSrcFormat = defaultAhemSrc.replace("INSERT_FORMAT", testDetails.properties.format);
+            var fontFace = new FontFace(familyName, ahemSrcFormat);
+            if (!expectFail) {
+                return fontFace.load();
+            } else {
+                return promise_rejects(testDetails, "NetworkError", fontFace.load());
+            }
+        }, (expectFail ? "Do not load" : "Load") + " Ahem with format " + formats[i], {
+            "format": formats[i]
+        });
+    }
+}
+
+runTestOnFormatSpecifiers(formatSpecifiers, false);
+runTestOnFormatSpecifiers(formatSpecifiers.map(x => "xyz" + x), true);
+runTestOnFormatSpecifiers(formatSpecifiers.map(x => x + "xyz"), true);
+runTestOnFormatSpecifiers(formatSpecifiers.map(x => x.slice(0, -2)), true);
+runTestOnFormatSpecifiers(formatSpecifiers.map(x => x.slice(2)), true);
+runTestOnFormatSpecifiers(formatSpecifiers.map(x => x.slice(0, x.length / 3) + x.slice(x.length / 3 + 1)), true);
+
+done();
+</script>
+</body>
+</html>
diff --git a/css/css-fonts/matching/README.md b/css/css-fonts/matching/README.md
index 0d76075..653f4d9 100644
--- a/css/css-fonts/matching/README.md
+++ b/css/css-fonts/matching/README.md
@@ -23,7 +23,7 @@
 
 ### Building reference tests
 
-Using the **Variable Test Axis Matching** font, [reference tests](http://web-platform-tests.org/writing-tests/reftests.html) in this directory are created as follows:
+Using the **Variable Test Axis Matching** font, [reference tests](https://web-platform-tests.org/writing-tests/reftests.html) in this directory are created as follows:
 
  1. Define `@font-face`s with range expressions, which trigger variation axes to be applied to the variable font.
  2. Use CSS style definitions to request font faces from the set of declared `@font-face`s and use blocks of the glyph sequence MNOP.
diff --git a/css/css-fonts/support/fonts/FontWithFeatures.otf b/css/css-fonts/support/fonts/FontWithFeatures.otf
deleted file mode 100644
index 3e50699..0000000
--- a/css/css-fonts/support/fonts/FontWithFeatures.otf
+++ /dev/null
Binary files differ
diff --git a/css/css-fonts/support/fonts/font-feature-settings-rendering-2-expected.html b/css/css-fonts/support/fonts/font-feature-settings-rendering-2-expected.html
deleted file mode 100644
index 774481b..0000000
--- a/css/css-fonts/support/fonts/font-feature-settings-rendering-2-expected.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<style>
-@font-face {
-    font-family: "FontFeaturesTest";
-    src: url("FontWithFeatures.otf") format("opentype");
-}
-</style>
-</head>
-<body>
-This tests that font features are able to be turned on and off as desired. It uses a special font
-designed specifically for this purpose. The test passes if you see a sequence of alternating check
-marks and X below.
-<div><span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-<span style="font-family: FontFeaturesTest;">BA</span>
-</div>
-</body>
-</html>
diff --git a/css/css-fonts/support/fonts/font-feature-settings-rendering-2.html b/css/css-fonts/support/fonts/font-feature-settings-rendering-2.html
deleted file mode 100644
index f552425..0000000
--- a/css/css-fonts/support/fonts/font-feature-settings-rendering-2.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<style>
-@font-face {
-    font-family: "FontFeaturesTest";
-    src: url("resources/FontWithFeatures.otf") format("opentype");
-}
-</style>
-</head>
-<body>
-This tests that font features are able to be turned on and off as desired. It uses a special font
-designed specifically for this purpose. The test passes if you see a sequence of alternating check
-marks and X below.
-<div id="insertionpoint"></div>
-<script>
-var insertionpoint = document.getElementById("insertionpoint");
-function addElement(feature, c) {
-    ["0", "1"].map(function(state) {
-        var element = document.createElement("span");
-        element.textContent = c;
-        element.style.fontFamily = "FontFeaturesTest";
-        element.style.webkitFontFeatureSettings = '"' + feature + '" ' + state;
-        insertionpoint.appendChild(element);
-    });
-    insertionpoint.appendChild(document.createTextNode(" "));
-}
-addElement("liga", "C");
-addElement("clig", "D");
-addElement("dlig", "E");
-addElement("hlig", "F");
-addElement("calt", "G");
-addElement("subs", "H");
-addElement("sups", "I");
-addElement("smcp", "J");
-addElement("c2sc", "K");
-addElement("pcap", "L");
-addElement("c2pc", "M");
-addElement("unic", "N");
-addElement("titl", "O");
-addElement("onum", "P");
-addElement("pnum", "Q");
-addElement("tnum", "R");
-addElement("frac", "S");
-//addElement("afrc", "T");
-addElement("ordn", "U");
-addElement("zero", "V");
-addElement("hist", "W");
-addElement("jp78", "X");
-addElement("jp83", "Y");
-addElement("jp90", "Z");
-addElement("jp04", "a");
-addElement("smpl", "b");
-addElement("trad", "c");
-addElement("fwid", "d");
-addElement("pwid", "e");
-addElement("ruby", "f");
-</script>
-</body>
-</html>
diff --git a/css/css-fonts/variations/at-font-face-descriptors.html b/css/css-fonts/variations/at-font-face-descriptors.html
new file mode 100644
index 0000000..ece5b4b
--- /dev/null
+++ b/css/css-fonts/variations/at-font-face-descriptors.html
@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing @font-face descriptor values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-face-rule" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style id="testStyle">
+        @font-face { font-family: Test; src: local('Courier New'), local('Courier'); }
+    </style>
+</head>
+<body>
+    <div>@font-face descriptor tests</div>
+    <script>
+
+        function updateFontFaceRule(descriptorName, descriptorValue) {
+            let testRule = document.getElementById("testStyle").sheet.cssRules[0];
+
+            testRule.style.fontWeight = "normal";
+            testRule.style.fontStyle = "normal";
+            testRule.style.fontStretch = "normal";
+            assert_equals(testRule.style.fontWeight, "normal", "Can't clear @font-face.");
+            assert_equals(testRule.style.fontStyle, "normal", "Can't clear @font-face.");
+            assert_equals(testRule.style.fontStretch,  "normal", "Can't clear @font-face.");
+
+            testRule.style.fontWeight = "";
+            testRule.style.fontStyle = "";
+            testRule.style.fontStretch = "";
+            assert_true(!testRule.style.fontWeight, "", "Can't clear @font-face.");
+            assert_true(!testRule.style.fontStyle, "", "Can't clear @font-face.");
+            assert_true(!testRule.style.fontStretch,  "", "Can't clear @font-face.");
+
+            testRule.style[descriptorName] = descriptorValue;
+
+            return testRule;
+        }
+
+        function testDescriptor(descriptorName, testCases) {
+            var propertyName = { 'font-weight':'fontWeight', 'font-stretch':'fontStretch', 'font-style':'fontStyle' }[descriptorName];
+            testCases.forEach(function (testCase) {
+                test(() => {
+                    let rule = updateFontFaceRule(descriptorName, testCase.value);
+
+                    if (testCase.isValid) {
+                        assert_not_equals(rule.style[propertyName], "", "Valid value should be accepted.");
+
+                        let expectedValue = (testCase.expectedValue) ? testCase.expectedValue : testCase.value;
+                        assert_equals(rule.style[propertyName], expectedValue, "Unexpected resulting value.");
+                    }
+                    else {
+                        assert_equals(rule.style[propertyName], "", "No properties should be set.");
+                    }
+                }, descriptorName + (testCase.isValid ? "(valid): " : "(invalid): ") + testCase.description + ": " + testCase.value);
+
+            });
+        }
+
+        testDescriptor("font-weight", [
+            // Single value, keyword
+            { value: "normal",                  isValid: true,  description: "'normal' keyword" },
+            { value: "bold",                    isValid: true,  description: "'bold' keyword" },
+            { value: "lighter",                 isValid: false, description: "'lighter' keyword iside @font-face" },
+            { value: "bolder",                  isValid: false, description: "'lighter' keyword iside @font-face" },
+            { value: "bold a",                  isValid: false, description: "Extra content after keyword" },
+
+            // Single value, number
+            { value: "401",                     isValid: true,  description: "Values that are not multiple of 100 should be parsed successfully" },
+            { value: "400.1",                   isValid: true,  description: "Non-integer values should be parsed successfully" },
+            { value: "1",                       isValid: true,  description: "Minimum allowed value should be parsed successfully" },
+            { value: "0.999",                   isValid: false, description: "Values below minimum should be rejected" },
+            { value: "-100",                    isValid: false, description: "Values below zero should be rejected" },
+            { value: "1000",                    isValid: true,  description: "Maximum allowed value should be parsed successfully" },
+            { value: "1000.001",                isValid: false, description: "Values above maximum should be rejected" },
+            { value: "100 a",                   isValid: false, description: "Extra content after value" },
+
+            // Single value, calc
+            { value: "calc(100.5)",             isValid: true,  expectedValue: "100.5", description: "Simple calc value" },
+            { value: "calc(1001)",              isValid: false, description: "Out-of-range simple calc value" },
+            { value: "calc(100.5*3 + 50.5)",    isValid: true,  expectedValue: "352", description: "Valid calc expression" },
+            { value: "calc(100.5*3 + 800)",     isValid: false, description: "Valid calc expression with out-of-range value" },
+            { value: "calc(100.5px + 50.5px)",  isValid: false, description: "Valid calc expression with units" },
+
+            // Value range
+            { value: "100 900",                         isValid: true,  description: "Simple range" },
+            { value: "500 500",                         isValid: true,  expectedValue: "500", description: "Simple range with equal upper and lower bounds" },
+            { value: "0.9 100",                         isValid: false, description: "Lower bound out of range" },
+            { value: "100 1001",                        isValid: false, description: "Upper bound out of range" },
+            { value: "calc(100 + 100) 400",             isValid: true,  expectedValue: "200 400", description: "Lower bound calc()" },
+            { value: "200 calc(200 + 200)",             isValid: true,  expectedValue: "200 400", description: "Upper bound calc()" },
+            { value: "calc(100 + 100) calc(200 + 200)", isValid: true,  expectedValue: "200 400", description: "Both bounds are calc()" },
+            { value: "400 200",                         isValid: true,  expectedValue: "200 400", description: "Bounds out of order are valid" },
+            { value: "100 200 300",                     isValid: false, description: "Extra content after upper bound" },
+        ]);
+
+        testDescriptor("font-stretch", [
+            // Single value, keyword
+            { value: "ultra-condensed",         isValid: true,  description: "'ultra-condensed' keyword" },
+            { value: "extra-condensed",         isValid: true,  description: "'extra-condensed' keyword" },
+            { value: "condensed",               isValid: true,  description: "'condensed' keyword" },
+            { value: "semi-condensed",          isValid: true,  description: "'semi-condensed' keyword" },
+            { value: "normal",                  isValid: true,  description: "'normal' keyword" },
+            { value: "semi-expanded",           isValid: true,  description: "'semi-expanded' keyword" },
+            { value: "expanded",                isValid: true,  description: "'expanded' keyword" },
+            { value: "extra-expanded",          isValid: true,  description: "'extra-expanded' keyword" },
+            { value: "ultra-expanded",          isValid: true,  description: "'ultra-expanded' keyword" },
+            { value: "expanded a",              isValid: false, description: "Extra content after value" },
+
+            // Single value, number
+            { value: "1%",                      isValid: true,  description:"Legal percentage" },
+            { value: "10.5%",                   isValid: true,  description:"Legal percentage" },
+            { value: "100%",                    isValid: true,  description:"Legal percentage" },
+            { value: "1000%",                   isValid: true,  description:"Legal percentage" },
+            { value: "100",                     isValid: false, description:"Only percentages, not numbers allowed" },
+            { value: "-1%",                     isValid: false, description:"Negative values are illegal" },
+            { value: "0%",                      isValid: true,  description:"Zero is legal" },
+            { value: "100% a",                  isValid: false, description:"Extra content after value" },
+
+            // Single value, calc
+            { value: "calc(200.5%)",            isValid: true,  expectedValue: "200.5%", description: "Simple calc value" },
+            { value: "calc(50%*2 - 20%)",       isValid: true,  expectedValue: "80%", description: "Valid calc expression" },
+            { value: "calc(-100%)",             isValid: true,  description: "Negative calc value (to be clamped)" },
+            { value: "calc(50% - 50%*2)",       isValid: true,  expectedValue: "calc(-50%)", description: "Negative calc expression (to be clamped)" },
+            { value: "calc(100)",               isValid: false, description: "Unit-less calc value" },
+            { value: "calc(100px)",             isValid: false, description: "Calc value with units" },
+
+            // Value range
+            { value: "100% 200%",                       isValid: true,  description: "Simple range" },
+            { value: "100% 100%",                       isValid: true,  expectedValue: "100%", description: "Simple range with equal upper and lower bounds" },
+            { value: "-100% 100%",                      isValid: false, description: "Lower bound out of range" },
+            { value: "calc(10% + 10%) 30%",             isValid: true,  expectedValue: "20% 30%", description: "Lower bound calc()" },
+            { value: "10% calc(10% + 10%)",             isValid: true,  expectedValue: "10% 20%", description: "Upper bound calc()" },
+            { value: "calc(10% + 10%) calc(20% + 20%)", isValid: true,  expectedValue: "20% 40%", description: "Both bounds are calc()" },
+            { value: "200% 100%",                       isValid: true,  expectedValue: "100% 200%", description: "Bounds out of order" },
+            { value: "100% 200% 300%",                  isValid: false, description: "Extra content after upper bound" },
+        ]);
+
+        testDescriptor("font-style", [
+            // Single value, keyword
+            { value: "normal",                  isValid: true,  description: "'normal' keyword" },
+            { value: "italic",                  isValid: true,  description: "'italic' keyword" },
+            { value: "oblique",                 isValid: true,  description: "'oblique' keyword" },
+
+            // Single value
+            { value: "italic 20deg",            isValid: false, description: "'italic' followed by angle" },
+            { value: "italic a",                isValid: false, description: "Extra content after keyword" },
+            { value: "oblique 0deg",            isValid: true,  description: "'oblique' followed by zero degrees" },
+            { value: "oblique 20deg",           isValid: true,  expectedValue: "oblique", description: "'oblique' followed by default 20deg angle" },
+            { value: "oblique 90deg",           isValid: true,  description: "'oblique' followed by maxumum 90 degree angle" },
+            { value: "oblique -90deg",          isValid: true,  description: "'oblique' followed by minimum -90 degree angle" },
+            { value: "oblique 0rad",            isValid: true,  expectedValue: "oblique 0deg", description: "'oblique' followed by  angle in radians" },
+            { value: "oblique 20",              isValid: false, description: "'oblique' followed by unit-less number" },
+            { value: "oblique 20px",            isValid: false, description: "'oblique' followed by non-angle" },
+            { value: "oblique a",               isValid: false, description: "'oblique' followed by non-number" },
+            { value: "oblique -",               isValid: false, description: "'oblique' followed by isolated minus" },
+            { value: "oblique - 20deg",         isValid: false, description: "'oblique' followed by minus and angle separated by space" },
+            { value: "oblique -a",              isValid: false, description: "'oblique' followed by minus and non-number" },
+
+            // Value range
+            { value: "oblique 10deg 20deg",         isValid: true,  description: "Simple range" },
+            { value: "oblique 10deg 10deg",         isValid: true,  expectedValue: "oblique 10deg", description: "Simple range with equal upper and lower bounds" },
+            { value: "oblique 20deg 20deg",         isValid: true,  expectedValue: "oblique", description: "Simple range with  default angle for both bounds" },
+            { value: "oblique 20deg 10deg",         isValid: true,  expectedValue: "oblique 10deg 20deg", description: "Bounds out of order" },
+            { value: "oblique -100deg 20deg",       isValid: false, description: "Lower bound out of range" },
+            { value: "oblique 20deg 100deg",        isValid: false, description: "Upper bound out of range" },
+            { value: "oblique 10deg 20deg 30deg",   isValid: false, description: "Extra content after upper bound" },
+        ]);
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/at-font-face-font-matching.html b/css/css-fonts/variations/at-font-face-font-matching.html
new file mode 100644
index 0000000..24f0bdc
--- /dev/null
+++ b/css/css-fonts/variations/at-font-face-font-matching.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing @font-face font matching logic introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+        .test
+        {
+            float:left;
+            border:1px solid red;
+            font-size:24pt;
+            white-space: nowrap;
+            clear:both;
+        }
+
+        @font-face { font-family: W100; src: url('./resources/csstest-weights-100-kerned.ttf'); }
+        @font-face { font-family: W200; src: url('./resources/csstest-weights-200-kerned.ttf'); }
+        @font-face { font-family: W300; src: url('./resources/csstest-weights-300-kerned.ttf'); }
+
+
+        @font-face { font-family: descriptorPriorityTest; src: url('./resources/csstest-weights-100-kerned.ttf'); font-stretch : 125%; }
+        @font-face { font-family: descriptorPriorityTest; src: url('./resources/csstest-weights-200-kerned.ttf'); font-style: italic; }
+        @font-face { font-family: descriptorPriorityTest; src: url('./resources/csstest-weights-300-kerned.ttf'); font-weight: 350; }
+    </style>
+    <style id="dynamicStyles">
+    </style>
+</head>
+<body>
+
+    <span style="position: absolute; top: -100vh;">
+        <span style="font-family: 'W100';">A</span>
+        <span style="font-family: 'W200';">A</span>
+        <span style="font-family: 'W300';">A</span>
+    </span>
+
+    <div id="master" class="test">A1 A2 A2 A3 A3 A3</div>
+    <div id="test" class="test">A1 A2 A2 A3 A3 A3</div>
+    <div style="clear:both"></div>
+    <script>
+
+        // wait for the fonts to load
+        // -- this should not be necessary if the fonts are installed as required
+        // -- but if they are not, the test is otherwise unstable
+        var once_fonts_are_ready = (document.fonts ? document.fonts.ready : new Promise(function(ready) { window.onload = time => [...document.querySelectorAll('body > span:nth-child(1) > span')].every(e => e.offsetWidth > 20) ? ready() : requestAnimationFrame(window.onload) }));
+
+        var masterElement = document.getElementById("master");
+        var testElement = document.getElementById("test");
+        var dynamicStyles = document.getElementById("dynamicStyles");
+
+        function verifyFont(testFamily, testWeight, testStyle, testStretch, expectedFamily) {
+
+            testElement.style.fontWeight  = "normal";
+            testElement.style.fontStyle   = "normal";
+            testElement.style.fontStretch = "normal";
+
+            masterElement.style.fontFamily = expectedFamily;
+            let masterWidth = masterElement.offsetWidth;
+
+            testElement.style.fontFamily = expectedFamily;
+            assert_equals(masterWidth, testElement.offsetWidth, "Sanity test: same family name gets same width" + dynamicStyles.innerHTML);
+
+            testElement.style.fontFamily = "serif";
+            assert_not_equals(masterWidth, testElement.offsetWidth, "Sanity test: different family get different width");
+
+            testElement.style.fontWeight = testWeight;
+            testElement.style.fontStyle  = testStyle;
+            testElement.style.fontStretch = testStretch;
+            testElement.style.fontFamily = testFamily;
+
+            assert_true(masterWidth == testElement.offsetWidth, "Unexpected font on test element");
+        }
+
+        var descriptorPriorityCases = [
+            { weight: "normal", style: "oblique -5deg", stretch: "125%",   expectedFamily: "'W100'", description: "Stretch has higher priority than style"},
+            { weight: "350",    style: "normal",        stretch: "125%",   expectedFamily: "'W100'", description: "Stretch has higher priority than weight"},
+            { weight: "350",    style: "oblique -5deg", stretch: "normal", expectedFamily: "'W200'", description: "Style has higher priority than weight"}
+        ];
+
+        descriptorPriorityCases.forEach(function (testCase) {
+            promise_test(
+                assert => once_fonts_are_ready.then(assert => {
+                    verifyFont("descriptorPriorityTest", testCase.weight, testCase.style, testCase.stretch, testCase.expectedFamily);
+                }),
+                "Descriptor mathcing priority: " + testCase.description
+            );
+        });
+
+        function createFontFaceRules(fontFaceFamily, descriptorName, expectedMatch, unexpectedMatch) {
+            dynamicStyles.innerHTML =
+                "@font-face { font-family: " + fontFaceFamily + "; src: url('./resources/csstest-weights-100-kerned.ttf'); "+ descriptorName + ": " + expectedMatch   + "; }" +
+                "@font-face { font-family: " + fontFaceFamily + "; src: url('./resources/csstest-weights-200-kerned.ttf'); " + descriptorName + ": " + unexpectedMatch + "; }";
+        }
+
+        function testDescriptor(descriptorName, testCases) {
+            testCases.forEach(function (testCase) {
+                    // Go though test cases, checking each descriptor has higher priority than next in the list
+                    for(let i = 0; i < testCase.testDescriptors.length - 1; i++) {
+                        let expectedMatch   = testCase.testDescriptors[i];
+                        let unexpectedMatch = testCase.testDescriptors[i + 1];
+                        promise_test(
+                            assert => once_fonts_are_ready.then(assert => {
+                                createFontFaceRules("MatchTestFamily", descriptorName, expectedMatch, unexpectedMatch);
+
+                                let testWeight  = (descriptorName == "font-weight")  ? testCase.value : "normal";
+                                let testStyle   = (descriptorName == "font-style")   ? testCase.value : "normal";
+                                let testStretch = (descriptorName == "font-stretch") ? testCase.value : "normal";
+
+                                verifyFont("MatchTestFamily", testWeight, testStyle, testStretch, "'W100'");
+                            }),
+                            "Matching " + descriptorName + ": '" + testCase.value + "' should prefer '" + expectedMatch + "' over '" + unexpectedMatch + "'");
+                    }
+            });
+        }
+
+        // Each case defines property value being tested and set of descriptor values in order of matching priority from highest to lowest
+
+        testDescriptor("font-weight", [
+            { value: "400", testDescriptors: ["400", "450 460", "500", "350 399", "351 398", "501 550", "502 560"] },
+            { value: "430", testDescriptors: ["420 440", "450 460", "500", "400 425", "350 399", "340 398", "501 550", "502 560"] },
+            { value: "500", testDescriptors: ["500", "450 460", "400", "350 399", "351 398", "501 550", "502 560"] },
+            { value: "501", testDescriptors: ["501", "502 510", "503 520", "500", "450 460", "390 410", "300 350"] },
+            { value: "399", testDescriptors: ["350 399", "340 360", "200 300", "400", "450 460", "500 501", "502 510"] }
+        ]);
+
+        testDescriptor("font-stretch", [
+            { value: "100%", testDescriptors: ["100%", "110% 120%", "115% 116%"] },
+            { value: "110%", testDescriptors: ["110% 120%", "115% 116%", "105%", "100%", "50% 80%", "60% 70%"] },
+            { value: "90%",  testDescriptors: ["90% 100%", "50% 80%", "60% 70%", "110% 140%", "120% 130%"] },
+        ]);
+
+        testDescriptor("font-style", [
+            { value: "normal",         testDescriptors: ["normal", "oblique 0deg", "oblique 10deg 40deg", "oblique 20deg 30deg", "oblique -50deg -20deg", "oblique -40deg -30deg" ] },
+            { value: "italic",         testDescriptors: ["italic", "oblique 20deg", "oblique 30deg 60deg", "oblique 40deg 50deg", "oblique 5deg 10deg", "oblique 5deg", "normal", "oblique 0deg", "oblique -60deg -30deg", "oblique -50deg -40deg" ] },
+            { value: "oblique 20deg",  testDescriptors: ["oblique 20deg", "oblique 30deg 60deg", "oblique 40deg 50deg", "oblique 10deg", "italic", "oblique 0deg", "oblique -50deg -20deg", "oblique -40deg -30deg" ] },
+            { value: "oblique 21deg",  testDescriptors: ["oblique 21deg", "oblique 30deg 60deg", "oblique 40deg 50deg", "oblique 20deg", "oblique 10deg", "italic", "oblique 0deg",  "oblique -50deg -20deg", "oblique -40deg -30deg" ] },
+            { value: "oblique 10deg",  testDescriptors: ["oblique 10deg", "oblique 5deg", "oblique 15deg 20deg", "oblique 30deg 60deg", "oblique 40deg 50deg", "italic", "oblique 0deg", "oblique -50deg -20deg", "oblique -40deg -30deg" ] },
+            { value: "oblique 0deg",   testDescriptors: ["oblique 0deg", "oblique 5deg", "oblique 15deg 20deg", "oblique 30deg 60deg", "oblique 40deg 50deg", "italic", "oblique -50deg -20deg", "oblique -40deg -30deg" ] },
+            { value: "oblique -10deg", testDescriptors: ["oblique -10deg", "oblique -5deg", "oblique -1deg 0deg", "oblique -20deg -15deg", "oblique -60deg -30deg", "oblique -50deg -40deg", "italic", "oblique 0deg 10deg", "oblique 40deg 50deg" ] },
+            { value: "oblique -20deg", testDescriptors: ["oblique -20deg", "oblique -60deg -40deg", "oblique -10deg", "italic", "oblique 0deg", "oblique 30deg 60deg", "oblique 40deg 50deg"] },
+            { value: "oblique -21deg", testDescriptors: ["oblique -21deg", "oblique -60deg -40deg", "oblique -10deg", "italic", "oblique 0deg", "oblique 30deg 60deg", "oblique 40deg 50deg"] },
+        ]);
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-opentype-collections.html b/css/css-fonts/variations/font-opentype-collections.html
new file mode 100644
index 0000000..1b403b3
--- /dev/null
+++ b/css/css-fonts/variations/font-opentype-collections.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing support for OpenType collections in @font-face rules</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#src-desc" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      @font-face {
+          font-family: OpenType;
+          src: url(/fonts/Ahem.ttf);
+      }
+
+      @font-face {
+          font-family: OpenType-Collection;
+          src: url(./resources/ahem.ttc);
+        }
+    </style>
+</head>
+<body onload="test()">
+    <div id="opentype" style="display:inline-block; font-family:OpenType,Georgia;">Test</div><br>
+    <div id="collection" style="display:inline-block; font-family:OpenType-Collection,Verdana;">Test</div>
+    <script>
+
+        var opentypeElement = document.getElementById("opentype");
+        var collectionElement = document.getElementById("collection");
+
+        var loadTest = async_test("Verify that collection font is loaded");
+
+        function test() {
+            if (opentypeElement.offsetWidth == collectionElement.offsetWidth) {
+                loadTest.done();
+            }
+            else {
+                window.requestAnimationFrame(test);
+            }
+        }
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html b/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html
index 5152424..fed4aa5 100644
--- a/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html
+++ b/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html
@@ -20,16 +20,18 @@
     '850.3',
     'calc(100 + 300)',
     'calc(0.2 + 205.5)',
+    'calc(0 - 100)',
+    'calc(200 + 801)',
   ],
-  'stretch': ['51%', '199%', 'calc(10% + 20%)'],
+  'stretch': ['51%', '199%', 'calc(10% + 20%)', '0%'],
   'style' : [ 'normal', 'italic', 'oblique', 'oblique 50deg', 'oblique -90deg', 'oblique 90deg',
-              'oblique calc(30deg + 20deg)' ]
+              'oblique calc(90deg + 20deg)', 'oblique calc(30deg + 20deg)' ]
 };
 
 var styleInvalidTests = {
-  'weight': ['100 400', 'calc(0 - 100)', 'calc(200 + 801)'],
-  'stretch': ['100% 110%', '0%', '100% 150%', 'calc(1 + 10%)'],
-  'style' : [ 'normal 10deg', 'italic 10deg', 'oblique -91deg', 'oblique 91deg', 'oblique calc(90deg + 20deg)']
+  'weight': ['100 400'],
+  'stretch': ['100% 110%', '100% 150%', 'calc(1 + 10%)'],
+  'style' : [ 'normal 10deg', 'italic 10deg', 'oblique -91deg', 'oblique 91deg' ]
 };
 
 function testParseStyle() {
@@ -61,9 +63,12 @@
   'weight': [
     ['100', '100'], ['700', '700'], ['900', '900'], ['bold', 'bold'],
     ['normal', 'normal'], ['100 400', '100 400'], ['100 101.5', '100 101.5'],
-    ['999.8 999.9', '999.8 999.9']
+    ['999.8 999.9', '999.8 999.9'],
+    [ '500 400', '500 400']
   ],
   'stretch': [
+    ['0%', '0%'],
+    ['calc(0% - 10%)', 'calc(-10%)' ],
     ['100%', '100%'],
     ['110%', '110%'],
     ['111.5%', '111.5%'],
@@ -88,7 +93,6 @@
     '0.9',
     '-100 200',
     '100 -200',
-    '500 400',
     '100 1001',
     '1001',
     '1000.5',
@@ -97,7 +101,7 @@
     'a b c',
   ],
   'stretch': [
-    '-0.5%', '-1%', '0%', 'calc(0% - 10%)', '60% 70% 80%', 'a%', 'a b c', '0.1',
+    '-0.5%', '-1%', '60% 70% 80%', 'a%', 'a b c', '0.1',
     '-60% 80%', 'ultra-expannnned', '50% 0'
   ],
   'style' : [ 'oblique 100deg', 'oblique italic', 'oblique -91deg', 'oblique 0',
diff --git a/css/css-fonts/variations/font-shorthand.html b/css/css-fonts/variations/font-shorthand.html
new file mode 100644
index 0000000..c0f1f54
--- /dev/null
+++ b/css/css-fonts/variations/font-shorthand.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing font shorthand for new values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-prop" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <div id="shorthand-test">Shorthand test</div>
+
+    <script>
+
+        testFontShorthand = [
+            { value: "calc(24px) Arial",                                    isValid:true,   message: "Font size specified as calc()" },
+
+            // font-weight as number
+            { value: "700.5 24px Arial",                                    isValid:true,   expectedWeight:"700.5", message: "Font weight specified as number" },
+            { value: "0.9 24px Arial",                                      isValid:false,  message: "Font weight specified as number, value less than 1" },
+            { value: "1700.5 24px Arial",                                   isValid:false,  message: "Font weight specified as number, value greater than 1000" },
+
+            // font-weight as calc()
+            { value: "calc(900.7 - 200.1 * 2) calc(12px + 12px) Arial",     isValid:true,   expectedWeight:"500.5", message: "Font weight specified as calc()" },
+            { value: "calc(400.5 - 200.1 * 2) 24px Arial",                  isValid:true,   expectedWeight:"1",     message: "Font weight specified as calc(), value smaller than 1" },
+            { value: "calc(400.5 + 300.1 * 2) 24px Arial",                  isValid:true,   expectedWeight:"1000",  message: "Font weight specified as calc(), value greater than 1000" },
+
+            // font-style
+            { value: "oblique 45deg 24px Arial",                            isValid:true,   expectedStyle: "oblique 45deg",  message: "'oblique' with positive angle" },
+            { value: "oblique -45deg 24px Arial",                           isValid:true,   expectedStyle: "oblique -45deg", message: "'oblique' with negative angle" },
+            { value: "oblique 24px Arial",                                  isValid:true,   expectedStyle: "oblique",        message: "'oblique' without slant angle" },
+            { value: "oblique 100deg 24px Arial",                           isValid:false,                                   message: "'oblique' with positive angle, value out of range" },
+            { value: "oblique -100deg 24px Arial",                          isValid:false,                                   message: "'oblique' with negative angle, value out of range" },
+
+            // font-weight and font-style combined
+            { value: "oblique 50 24px Arial",                               isValid:true,   expectedStyle: "oblique",        expectedWeight:"50",    message: "'oblique' followed by valid small weight" },
+            { value: "oblique 500 24px Arial",                              isValid:true,   expectedStyle: "oblique",        expectedWeight:"500",   message: "'oblique' followed by valid large weight" },
+            { value: "oblique 45deg 500 24px Arial",                        isValid:true,   expectedStyle: "oblique 45deg",  expectedWeight:"500",   message: "'oblique' with positive angle followed by valid weight" },
+            { value: "oblique -45deg 500 24px Arial",                       isValid:true,   expectedStyle: "oblique -45deg", expectedWeight:"500",   message: "'oblique' with negative angle followed by valid weight" },
+
+            // font-weight and font-style combined, with calc()
+            { value: "oblique calc(200 + 300) 24px Arial",                  isValid:true,  expectedStyle: "oblique",         expectedWeight:"500",   message: "'oblique' followed by valid calc() weight" },
+            { value: "oblique 30deg calc(200 + 300) 24px Arial",            isValid:true,  expectedStyle: "oblique 30deg",   expectedWeight:"500",   message: "'oblique' with angle followed by valid calc() weight" },
+            { value: "oblique calc(900 + 300) 24px Arial",                  isValid:true,  expectedStyle: "oblique",         expectedWeight:"1000",  message: "'oblique' followed by a to-be-clamped calc() weight" },
+            { value: "calc(200 + 300) oblique 24px Arial",                  isValid:true,  expectedStyle: "oblique",         expectedWeight:"500",   message: "calc() weight folowed by 'oblique'" },
+            { value: "calc(200 + 300) oblique 45deg 24px Arial",            isValid:true,  expectedStyle: "oblique 45deg",   expectedWeight:"500",   message: "calc() weight folowed by 'oblique' and slant angle" },
+            { value: "calc(900 + 300) oblique 45deg 24px Arial",            isValid:true,  expectedStyle: "oblique 45deg",   expectedWeight:"1000",  message: "To-be-clamped calc() weight folowed by 'oblique' and slant angle" },
+        ];
+
+        testFontShorthand.forEach(function (testCase) {
+            test(() => {
+                assert_equals(window.CSS.supports("font", testCase.value), testCase.isValid, "Font shorthand: " + testCase.message);
+
+                let expectedStyle  = (testCase.expectedStyle) ? testCase.expectedStyle : "normal";
+                let expectedWeight = (testCase.expectedWeight) ? testCase.expectedWeight : "400";
+                let expectedSize = (testCase.isValid) ? "24px" : "16px";
+
+                var testElement = document.getElementById("shorthand-test");
+                testElement.setAttribute("style", "font:" + testCase.value);
+                var style = window.getComputedStyle(testElement);
+                assert_equals(style.fontStyle, expectedStyle, "Font shorthand expected style: " + testCase.message);
+                assert_equals(style.fontWeight, expectedWeight, "Font shorthand expected weight: " + testCase.message);
+                assert_equals(style.fontSize, expectedSize, "Font shorthand expected size: " + testCase.message);
+            }, "Font shorthand: " + testCase.message);
+
+        });
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-stretch.html b/css/css-fonts/variations/font-stretch.html
new file mode 100644
index 0000000..a27b251
--- /dev/null
+++ b/css/css-fonts/variations/font-stretch.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing new font-stretch values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-stretch-prop" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <div id="computedStyleTest">Abc</span></div>
+    <div id="inheritanceTest"><span style="font-stretch:125%;">Abc</span><span style="font-stretch:expanded;">Abc</span><span style="font-weight: 700;">Abc</span></div>
+    <script>
+
+        testStretchValues = [
+            { stretch: "100",               expectedComputedStretch: "100%" ,   expectedIsSupported: false, message: "only percentages, not numbers allowed" },
+            { stretch: "-1%",               expectedComputedStretch: ""  ,      expectedIsSupported: false, message: "negative values are illegal" },
+            { stretch: "0%",                expectedComputedStretch: "0%",      expectedIsSupported: true,  message: "zero is legal" },
+            { stretch: "1%",                expectedComputedStretch: "1%",      expectedIsSupported: true,  message: "legal percentage" },
+            { stretch: "10%",               expectedComputedStretch: "10%",     expectedIsSupported: true,  message: "legal percentage" },
+            { stretch: "100%",              expectedComputedStretch: "100%",    expectedIsSupported: true,  message: "legal percentage" },
+            { stretch: "1000%",             expectedComputedStretch: "1000%",   expectedIsSupported: true,  message: "legal percentage" },
+            { stretch: "1e9%",              expectedComputedStretch: "1e+009%", expectedIsSupported: true,  message: "huge legal percentage" },
+            { stretch: "ultra-condensed",   expectedComputedStretch: "50%",     expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "extra-condensed",   expectedComputedStretch: "62.5%",   expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "condensed",         expectedComputedStretch: "75%",     expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "semi-condensed",    expectedComputedStretch: "87.5%",   expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "normal",            expectedComputedStretch: "100%",    expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "semi-expanded",     expectedComputedStretch: "112.5%",  expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "expanded",          expectedComputedStretch: "125%",    expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "extra-expanded",    expectedComputedStretch: "150%",    expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "ultra-expanded",    expectedComputedStretch: "200%",    expectedIsSupported: true,  message: "legal enum" },
+            { stretch: "narrower",          expectedComputedStretch: "",        expectedIsSupported: false, message: "deprecated" },
+            { stretch: "wider",             expectedComputedStretch: "",        expectedIsSupported: false, message: "deprecated" },
+            { stretch: "calc(200.5%)",      expectedComputedStretch: "200.5%",  expectedIsSupported: true,  message: "Simple calc value" },
+            { stretch: "calc(50%*2 - 20%)", expectedComputedStretch: "80%",     expectedIsSupported: true,  message: "Valid calc expression" },
+            { stretch: "calc(-100%)",       expectedComputedStretch: "0%",      expectedIsSupported: true,  message: "Negative calc value (to be clamped)" },
+            { stretch: "calc(50% - 50%*2)", expectedComputedStretch: "0%",      expectedIsSupported: true,  message: "Negative calc expression (to be clamped)" },
+            { stretch: "calc(100)",         expectedComputedStretch: "",        expectedIsSupported: false, message: "Unit-less calc value" },
+            { stretch: "calc(100px)",       expectedComputedStretch: "",        expectedIsSupported: false, message: "Calc value with units" },
+            { stretch: "100% 700%",         expectedComputedStretch: "",        expectedIsSupported: false, message: "Extra percentage after numeric value" },
+            { stretch: "100% 100",          expectedComputedStretch: "",        expectedIsSupported: false, message: "Extra content after numeric value" },
+            { stretch: "condensed expanded",expectedComputedStretch: "",        expectedIsSupported: false, message: "Extra content after keyword value" },
+            { stretch: "calc(100%) 100%",   expectedComputedStretch: "",        expectedIsSupported: false, message: "Extra content after calc value" }
+        ];
+
+        testStretchValues.forEach(function (element) {
+            test(() => { assert_equals(window.CSS.supports("font-stretch", element.stretch), element.expectedIsSupported, element.message); }, "@supports: " + element.stretch + " - " + element.message);
+
+            // If supported, verify the computed style.
+            if (element.expectedIsSupported)
+            {
+                var testSpan = document.getElementById("computedStyleTest");
+                testSpan.style.fontStretch = element.stretch;
+                var actualStretch = window.getComputedStyle(testSpan).fontStretch;
+
+                test(() => { assert_equals(actualStretch, element.expectedComputedStretch, element.message); }, "@getComputedStyle: " + element.stretch + " - " + element.message);
+            }
+        });
+
+        // Verify computed inheritance of nested elements.
+        {
+            var base = document.getElementById("inheritanceTest");
+            var parentStretch = "condensed";
+            base.style.fontStretch = parentStretch;
+
+            test(() => {
+                var actualStretch = window.getComputedStyle(base.children[0]).fontStretch;
+                assert_equals(actualStretch, "125%", "Overridden value for " + parentStretch + " should match expected value.");
+            }, "Test font-stretch for overridden number " + parentStretch);
+
+            test(() => {
+                var actualStretch = window.getComputedStyle(base.children[1]).fontStretch;
+                assert_equals(actualStretch, "125%", "Inherited value " + parentStretch + " should match expected value.");
+            }, "Test font-stretch for overridden enum name resolved to number " + parentStretch);
+
+            test(() => {
+                var actualStretch = window.getComputedStyle(base.children[2]).fontStretch;
+                assert_equals(actualStretch, "75%", "Inherited value " + parentStretch + " should match expected value.");
+            }, "Test font-stretch for inherited named enum resolved to number " + parentStretch);
+        }
+
+   </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-style-interpolation.html b/css/css-fonts/variations/font-style-interpolation.html
new file mode 100644
index 0000000..0e7eac4
--- /dev/null
+++ b/css/css-fonts/variations/font-style-interpolation.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing the interpolation of new font-style values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-style-prop" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+        @keyframes fontStyleAnimation {
+            from { font-style: oblique -12deg; }
+            to   { font-style: oblique  12deg; }
+        }
+
+        #animation-test.animate {
+            animation: fontStyleAnimation 1s infinite alternate;
+        }
+
+        #transition-test {
+            font-style: oblique -12deg;
+            transition-property: font-style;
+            transition-duration: 10s;
+        }
+
+        #transition-test.animate {
+            font-style: oblique 12deg;
+        }
+
+    </style>
+</head>
+<body>
+    <div style="font-family: serif;">
+        <div id="animation-test">Animation test</div>
+        <div id="transition-test">Transition test</div>
+    </div>
+
+    <script>
+
+        async_test(function (test) {
+            var animationElement = document.getElementById("animation-test");
+
+            // Verify starting value
+            assert_equals(window.getComputedStyle(animationElement).fontStyle, "normal", "Font style before animation");
+
+            // Start animation
+            animationElement.classList.add("animate");
+
+            var waitForAnimationStep = test.step_func(function() {
+                var computedFontStyle = window.getComputedStyle(animationElement).fontStyle;
+                if (computedFontStyle != "normal" &&
+                    computedFontStyle != "oblique -12deg" &&
+                    computedFontStyle != "oblique 12deg") {
+                    test.done();
+                }
+                else {
+                    window.requestAnimationFrame(waitForAnimationStep);
+                }
+            });
+            waitForAnimationStep();
+
+        }, "font-style animation");
+
+        async_test(function (test) {
+            var transitionElement = document.getElementById("transition-test");
+
+            // Verify starting value
+            assert_equals(window.getComputedStyle(transitionElement).fontStyle, "oblique -12deg", "Font style before transition");
+
+            // Start transition
+            transitionElement.classList.add("animate");
+
+            var waitForTransitionStep = test.step_func(function() {
+                var computedFontStyle = window.getComputedStyle(transitionElement).fontStyle;
+                if (computedFontStyle != "normal" &&
+                    computedFontStyle != "oblique -12deg" &&
+                    computedFontStyle != "oblique 12deg") {
+                    test.done();
+                }
+                else {
+                    window.requestAnimationFrame(waitForTransitionStep);
+                }
+            });
+            waitForTransitionStep();
+
+        }, "font-style transition");
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-style-parsing.html b/css/css-fonts/variations/font-style-parsing.html
new file mode 100644
index 0000000..0973d63
--- /dev/null
+++ b/css/css-fonts/variations/font-style-parsing.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing the new font-style values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-style-prop" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+    <script>
+
+        var testFontStyle = [
+            { style: "italic",            expectedResult: true,  message: "'italic' is valid" },
+            { style: "italic 20deg",      expectedResult: false, message: "'italic' followed by angle is invalid" },
+            { style: "italic a",          expectedResult: false, message: "'italic' followed by non-number is invalid" },
+            { style: "oblique",           expectedResult: true,  message: "'oblique' is valid" },
+            { style: "oblique 0deg",      expectedResult: true,  message: "'oblique' followed by zero degrees is valid" },
+            { style: "oblique 20deg",     expectedResult: true,  message: "'oblique' followed by positive angle in degrees is valid" },
+            { style: "oblique 0.5rad",    expectedResult: true,  message: "'oblique' followed by positive angle in radians is valid" },
+            { style: "oblique 20grad",    expectedResult: true,  message: "'oblique' followed by positive angle in gradians is valid" },
+            { style: "oblique 0.1turn",   expectedResult: true,  message: "'oblique' followed by positive angle in turns is valid" },
+            { style: "oblique 20px",      expectedResult: false, message: "'oblique' followed by number with invalid unit type is in valid" },
+            { style: "oblique -20deg",    expectedResult: true,  message: "'oblique' followed by negative angle is valid" },
+            { style: "oblique 20.1deg",   expectedResult: true,  message: "'oblique' followed by fractional angle is valid" },
+            { style: "oblique 90deg",     expectedResult: true,  message: "'oblique' followed by maxumum 90 degree angle is valid" },
+            { style: "oblique -90deg",    expectedResult: true,  message: "'oblique' followed by minimum -90 degree angle is valid" },
+            { style: "oblique 90.01deg",  expectedResult: false, message: "'oblique' followed by positive out of range angle is in invalid" },
+            { style: "oblique -90.01deg", expectedResult: false, message: "'oblique' followed by negative out of range angle is in invalid" },
+            { style: "oblique 10",        expectedResult: false, message: "'oblique' followed by unit-less value is invalid" },
+            { style: "oblique 20deg ",    expectedResult: true,  message: "'oblique' followed by positive angle is valid" },
+            { style: "oblique a",         expectedResult: false, message: "'oblique' followed by non-number is invalid" },
+            { style: "oblique 20deg a",   expectedResult: false, message: "'oblique' and angle followed by non-number is invalid" },
+            { style: "oblique -",         expectedResult: false, message: "'oblique' followed by isolated minus is invalid" },
+            { style: "oblique - 20deg",   expectedResult: false, message: "'oblique' followed by minus and angle separated by space is invalid" },
+            { style: "oblique -a",        expectedResult: false, message: "'oblique' followed by minus and non-number is invalid" },
+            { style: "oblique calc(50deg)",     expectedResult: true,  message: "'oblique' followed by calc is valid" },
+            { style: "oblique calc(-120deg)",   expectedResult: true,  message: "'oblique' followed by calc is valid even if it must be clamped (no computation)" },
+            { style: "oblique calc(6 * 20deg)", expectedResult: true,  message: "'oblique' followed by calc is valid even if it must be clamped (with computation)" },
+            { style: "oblique calc(0.1rad + 1deg)", expectedResult: true,  message: "'oblique' followed by calc is valid even if it mixes units (with computation)" }
+        ];
+
+        testFontStyle.forEach(function (testCase) {
+            test(() => { assert_equals(window.CSS.supports("font-style", testCase.style), testCase.expectedResult, "Font-style: " + testCase.message); }, "Font-style: " + testCase.message);
+        });
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-variation-settings-inherit.html b/css/css-fonts/variations/font-variation-settings-inherit.html
new file mode 100644
index 0000000..f0d8f0b
--- /dev/null
+++ b/css/css-fonts/variations/font-variation-settings-inherit.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing the inheritance of the font-variation-settings property</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#propdef-font-variation-settings" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <div id="inheritanceTestParent" style="font-variation-settings: 'xxxx' 1, 'xxxx' 2;">
+        <span id="inheritanceTestChildInherited">Abc</span>
+        <span id="inheritanceTestChildOverride" style="font-variation-settings: 'cccc' 1, 'bbbb' 2, 'aaaa' 3, 'aaaa' calc(2 + 2);">Abc</span>
+    </div>
+    <script>
+
+        // Verify computed inheritance of nested elements.
+        var elementParent = document.getElementById("inheritanceTestParent");
+        var elementChildInherited = document.getElementById("inheritanceTestChildInherited");
+        var elementChildOverride = document.getElementById("inheritanceTestChildOverride");
+
+        var parentValue = "'yyyy' 1, 'yyyy' 2";
+        elementParent.style.fontVariationSettings = parentValue;
+
+        test(() => {
+            var actualValue = window.getComputedStyle(elementParent).fontVariationSettings;
+            // The following strict test is subject to debate; softening for now:
+            // assert_equals(actualValue, "\"yyyy\" 2", "Duplicate axis tags should be removed, favoring the latter axis tag's value.");
+            assert_equals((/.*(?:"|')yyyy(?:"|') (\d)/.exec(actualValue)||[])[1], '2', "Child should override parent value.");
+        }, "Test font-variation-settings for duplicates using " + parentValue);
+
+        test(() => {
+            var actualValue = window.getComputedStyle(elementChildInherited).fontVariationSettings;
+            // The following strict test is subject to debate; softening for now:
+            // assert_equals(actualValue, "\"yyyy\" 2", "Child should inherit the parent value directly.");
+            assert_equals((/.*(?:"|')yyyy(?:"|') (\d)/.exec(actualValue)||[])[1], '2', "Child should override parent value.");
+        }, "Test font-variation-settings for child inheritance");
+
+        test(() => {
+            var actualValue = window.getComputedStyle(elementChildOverride).fontVariationSettings;
+            // The following strict test is subject to debate; softening for now:
+            // assert_equals(actualValue, "\"aaaa\" 4, \"bbbb\" 2, \"cccc\" 1", "Child should override parent value.");
+            assert_equals((/.*(?:"|')aaaa(?:"|') (\d)/.exec(actualValue)||[])[1], '4', "Child should override parent value.");
+            assert_equals((/.*(?:"|')yyyy(?:"|') (\d)/.exec(actualValue)||[])[1], undefined, "Child should override parent value.");
+        }, "Test font-variation-settings for child override");
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-variation-settings-parsing.html b/css/css-fonts/variations/font-variation-settings-parsing.html
new file mode 100644
index 0000000..3d6bc5f
--- /dev/null
+++ b/css/css-fonts/variations/font-variation-settings-parsing.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing the parsing of the font-variation-settings property</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#propdef-font-variation-settings" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <div id="value-parser-test"></div>
+    <script>
+
+        var valueParserTests = [
+            { value: "'wght' 1000, '9 ~A' -45",           expectedComputedValue: "\"wght\" 1000, \"9 ~A\" -45",    isValid: true,  message: "Axis tag with valid non-letter ascii characters" },
+            { value: "'\u001Fbdc' 123",                   expectedComputedValue: "",                               isValid: false, message: "Invalid character below allowed range (first char)"},
+            { value: "'abc\u007F' 123",                   expectedComputedValue: "",                               isValid: false, message: "Invalid character above allowed range (mid char)"},
+            { value: "'wght' 1e3, 'slnt' -450.0e-1 ",     expectedComputedValue: "\"wght\" 1000, \"slnt\" -45",    isValid: true,  message: "Axis values in scientific form are valid" },
+            { value: "normal",                            expectedComputedValue: "normal",                         isValid: true,  message: "'normal' value is valid" },
+            { value: "'a' 1234",                          expectedComputedValue: "",                               isValid: false, message: "Tag with less than 4 characters is invalid"},
+            { value: "'abcde' 1234",                      expectedComputedValue: "",                               isValid: false, message: "Tag with more than 4 characters is invalid"},
+            { value: "'wght' 200, ",                      expectedComputedValue: "",                               isValid: false, message: "Trailing comma is invalid"},
+            { value: "abcd 123",                          expectedComputedValue: "",                               isValid: false, message: "Unquoted tags are invalid"},
+            { value: "'abcd\" 123",                       expectedComputedValue: "",                               isValid: false, message: "Unmatched quotes around tag are invalid" },
+            { value: "'abcd'",                            expectedComputedValue: "",                               isValid: false, message: "Tag without value isinvalid"},
+            { value: "123",                               expectedComputedValue: "",                               isValid: false, message: "Value without tag is invalid"},
+            { value: "123 'abcd'",                        expectedComputedValue: "",                               isValid: false, message: "Value before tag is invalid"},
+            { value: "'wght' 200 'abcd' 400",             expectedComputedValue: "",                               isValid: false, message: "Missing comma between axes is invalid"},
+            { value: "'wght' calc(100 + 200)",            expectedComputedValue: "\"wght\" 300",                   isValid: true,  message: "Calculations should be supported" },
+            { value: "'wght' 100px",                      expectedComputedValue: "",                               isValid: false, message: "Units should not be supported" },
+            { value: "'wght' calc(100px + 200px)",        expectedComputedValue: "",                               isValid: false, message: "Units should not be supported (in calc)" },
+            { value: "'wght' 42%",                        expectedComputedValue: "",                               isValid: false, message: "Percentages should not be supported" },
+            { value: "'wght' calc(100%)",                 expectedComputedValue: "",                               isValid: false, message: "Percentages should not be supported (in calc)" },
+        ];
+
+        valueParserTests.forEach(function (testCase) {
+            test(() => {
+                var element = document.getElementById("value-parser-test");
+                // Reset to empty in order for testing to continue in case the next test would not parse as valid
+                element.style.fontVariationSettings = "";
+                element.style.fontVariationSettings = testCase.value;
+                var computed = window.getComputedStyle(element).fontVariationSettings;
+                if (testCase.isValid) {
+                    assert_equals(computed, testCase.expectedComputedValue, testCase.message);
+                }
+                else {
+                    assert_equals(computed, "normal", testCase.message);
+                }
+
+                element.style.fontVariationSettings = "";
+            },  "Property value: " + testCase.message);
+        });
+
+        valueParserTests.forEach(function (testCase) {
+            test(() => { assert_equals(window.CSS.supports("font-variation-settings", testCase.value), testCase.isValid, testCase.message); }, "@supports: " + testCase.message);
+        });
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-weight-interpolation.html b/css/css-fonts/variations/font-weight-interpolation.html
new file mode 100644
index 0000000..745be06
--- /dev/null
+++ b/css/css-fonts/variations/font-weight-interpolation.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing the interpolation of new font-weight values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-weight-prop" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+        @keyframes fontWeightAnimation {
+            from { font-weight: 100; }
+            to   { font-weight: 900; }
+        }
+
+        #animation-test.animate {
+            animation: fontWeightAnimation 1s infinite alternate;
+        }
+
+        #transition-test {
+            font-weight: 100;
+            transition-property: font-weight;
+            transition-duration: 10s;
+        }
+
+        #transition-test.animate {
+            font-weight: 900;
+        }
+
+    </style>
+</head>
+<body>
+    <div style="font-family: 'CSSTest Weights Full';">
+        <div id="animation-test">A</div>
+        <div id="transition-test">A</div>
+    </div>
+
+    <script>
+
+        async_test(function (test) {
+            var animationElement = document.getElementById("animation-test");
+
+            // Verify starting value
+            assert_equals(window.getComputedStyle(animationElement).fontWeight, "400", "Font weight before animation");
+
+            // Start animation
+            animationElement.classList.add("animate");
+
+            var waitForAnimationStep = test.step_func(function() {
+                var computedFontWeight = window.getComputedStyle(animationElement).fontWeight;
+                if (computedFontWeight[1] != "0" || computedFontWeight[2] != 0) { // the value should eventually not be a multiple of 100
+                    test.done();
+                }
+                else {
+                    window.requestAnimationFrame(waitForAnimationStep);
+                }
+            });
+            waitForAnimationStep();
+
+        }, "font-weight animation");
+
+        async_test(function (test) {
+            var transitionElement = document.getElementById("transition-test");
+
+            // Verify starting value
+            assert_equals(window.getComputedStyle(transitionElement).fontWeight, "100", "Font weight before transition");
+
+            // Start transition
+            transitionElement.classList.add("animate");
+
+            var waitForTransitionStep = test.step_func(function() {
+                var computedFontWeight = window.getComputedStyle(transitionElement).fontWeight;
+                if (computedFontWeight[1] != "0" || computedFontWeight[2] != 0) { // the value should eventually not be a multiple of 100
+                    test.done();
+                }
+                else {
+                    window.requestAnimationFrame(waitForTransitionStep);
+                }
+            });
+            waitForTransitionStep();
+
+        }, "font-weight transition");
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-weight-lighter-bolder.html b/css/css-fonts/variations/font-weight-lighter-bolder.html
new file mode 100644
index 0000000..68321c5
--- /dev/null
+++ b/css/css-fonts/variations/font-weight-lighter-bolder.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing new font-weight lighter/bolder table introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-weight-prop" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+    <div id="testcases">
+        <div class="testcase" style="font-family: 'CSSTest Weights Full';">
+            <span style="font-weight:lighter;color:red;">A</span>A<span style="font-weight:bolder;color:red;">A</span>
+            <span style="font: menu; font-size: 10px;">(default)</span>
+        </div>
+    </div>
+
+    <script>
+
+        testRelativeWeights = [
+            { baseWeight: 99,   lighter: "99",  bolder: "400" },
+            { baseWeight: 100,  lighter: "100", bolder: "400" },
+            { baseWeight: 349,  lighter: "100", bolder: "400" },
+            { baseWeight: 350,  lighter: "100", bolder: "700" },
+            { baseWeight: 549,  lighter: "100", bolder: "700" },
+            { baseWeight: 550,  lighter: "400", bolder: "900" },
+            { baseWeight: 749,  lighter: "400", bolder: "900" },
+            { baseWeight: 750,  lighter: "700", bolder: "900" },
+            { baseWeight: 899,  lighter: "700", bolder: "900" },
+            { baseWeight: 900,  lighter: "700", bolder: "900" },
+            { baseWeight: 901,  lighter: "700", bolder: "901" },
+        ];
+
+        var testcases = document.getElementById("testcases");
+        var testcase_template = testcases.firstElementChild; testcases.removeChild(testcase_template);
+        testRelativeWeights.forEach(function(element) {
+
+            var base = testcase_template.cloneNode(true);
+            base.children[2].textContent = element.baseWeight;
+            base.style.fontWeight = element.baseWeight;
+            testcases.appendChild(base);
+
+            test(() => {
+                var actualLighter = window.getComputedStyle(base.children[0]).fontWeight;
+                assert_equals(actualLighter, element.lighter, "Lighter value for " + element.baseWeight + " should match expected value.");
+                base.children[0].style.color = 'green';
+            }, "Test lighter font-weight for base weight " + element.baseWeight);
+
+            test(() => {
+                var actualBolder = window.getComputedStyle(base.children[1]).fontWeight;
+                assert_equals(actualBolder, element.bolder, "Bolder value " + element.baseWeight + " should match expected value.");
+                base.children[1].style.color = 'green';
+            }, "Test bolder font-weight for base weight " + element.baseWeight);
+        });
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-weight-matching-installed-fonts.html b/css/css-fonts/variations/font-weight-matching-installed-fonts.html
new file mode 100644
index 0000000..e00e317
--- /dev/null
+++ b/css/css-fonts/variations/font-weight-matching-installed-fonts.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing new font-matching algorithm for font-weight values introduced in CSS Fonts level 4 (for system fonts)</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- THIS TEST REQUIRES THAT YOU INSTALL THE [csstest-*.ttf] FONTS OF THE [resources] FOLDER -->
+    <meta name="flags" content="font" />
+    <style>
+
+        .testcase {
+            float:left;
+            margin: 5px;
+            font-size:48pt;
+            font-feature-settings: "kern" 1;
+            color: rgba(0,0,0,0.5);
+            background: linear-gradient(to left, lime 0%, lime 91px, red 91px);
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 900');
+            font-weight: 100;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 800');
+            font-weight: 250;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 700');
+            font-weight: 400;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 600');
+            font-weight: 450;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 300');
+            font-weight: 500;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 200');
+            font-weight: 750;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 100');
+            font-weight: 900;
+        }
+
+    </style>
+</head>
+<body>
+
+    <span style="position: absolute; top: -100vh;">
+        <span style="font-family: fontMatch; font-weight: 100;">A</span>
+        <span style="font-family: fontMatch; font-weight: 250;">A</span>
+        <span style="font-family: fontMatch; font-weight: 400;">A</span>
+        <span style="font-family: fontMatch; font-weight: 450;">A</span>
+        <span style="font-family: fontMatch; font-weight: 500;">A</span>
+        <span style="font-family: fontMatch; font-weight: 750;">A</span>
+        <span style="font-family: fontMatch; font-weight: 900;">A</span>
+    </span>
+
+    <div id="testcases" style="overflow: hidden">
+        <!--
+            These testcases work using the new kerned CSSTest Weights fonts.
+            The letter A and its corresponding numeric digit kern as one character.
+        -->
+        <div class="testcase" style="font-family:'CSSTest Weights W2569'; font-weight: 375;">
+            A2
+        </div>
+        <div class="testcase" style="font-family:'CSSTest Weights Full'; font-weight: 375;">
+            A3
+        </div>
+        <div class="testcase" style="font-family:'CSSTest Weights W1479'; font-weight: 475;">
+            A4
+        </div>
+        <div class="testcase" style="font-family:'CSSTest Weights Full'; font-weight: 425;">
+            A5
+        </div>
+        <div class="testcase" style="font-family:'CSSTest Weights Full'; font-weight: 525;">
+            A6
+        </div>
+        <div class="testcase" style="font-family:'CSSTest Weights Full'; font-weight: 675;">
+            A7
+        </div>
+        <br clear="all">
+    </div>
+
+    <script>
+
+        var base_testcases = document.querySelectorAll('.testcase');
+        for(var i = 0; i < base_testcases.length; i++) {
+            test(
+                assert => { assert_approx_equals(base_testcases[i].getBoundingClientRect().width, 90, 2, "@font-face should be mapped to " + base_testcases[i].style.fontFamily + "."); },
+                "Test native font matching on " + base_testcases[i].style.fontFamily + " for weight " + base_testcases[i].style.fontWeight
+            );
+        }
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-weight-matching.html b/css/css-fonts/variations/font-weight-matching.html
new file mode 100644
index 0000000..5eb9a99
--- /dev/null
+++ b/css/css-fonts/variations/font-weight-matching.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing new font-matching algorithm for font-weight values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+
+        .testcase {
+            float:left;
+            margin: 5px;
+            font-size:48pt;
+            font-feature-settings: "kern" 1;
+            color: rgba(0,0,0,0.5);
+            background: linear-gradient(to left, lime 0%, lime 91px, red 91px);
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 900'), url('./resources/csstest-weights-900-kerned.ttf');
+            font-weight: 100;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 800'), url('./resources/csstest-weights-800-kerned.ttf');
+            font-weight: 250;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 700'), url('./resources/csstest-weights-700-kerned.ttf');
+            font-weight: 400;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 600'), url('./resources/csstest-weights-600-kerned.ttf');
+            font-weight: 450;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 300'), url('./resources/csstest-weights-300-kerned.ttf');
+            font-weight: 500;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 200'), url('./resources/csstest-weights-200-kerned.ttf');
+            font-weight: 750;
+        }
+
+        @font-face {
+            font-family: fontMatch;
+            src: local('CSSTest Weights 100'), url('./resources/csstest-weights-100-kerned.ttf');
+            font-weight: 900;
+        }
+
+    </style>
+</head>
+<body>
+
+    <span style="position: absolute; top: -100vh;">
+        <span style="font-family: fontMatch; font-weight: 100;">A</span>
+        <span style="font-family: fontMatch; font-weight: 250;">A</span>
+        <span style="font-family: fontMatch; font-weight: 400;">A</span>
+        <span style="font-family: fontMatch; font-weight: 450;">A</span>
+        <span style="font-family: fontMatch; font-weight: 500;">A</span>
+        <span style="font-family: fontMatch; font-weight: 750;">A</span>
+        <span style="font-family: fontMatch; font-weight: 900;">A</span>
+    </span>
+
+    <div id="testcases" style="overflow: hidden">
+        <!--
+            These testcases work using the new kerned CSSTest Weights fonts.
+            The letter A and its corresponding numeric digit kern as one character.
+        -->
+        <div class="testcase" style="font-family:'CSSTest Weights W2569'; font-weight: 375;">
+            A2
+        </div>
+    </div>
+
+    <script>
+
+        var testFontFaceMatch = [
+            { weight:  99, expectedFont: "CSSTest Weights 900" },
+            { weight: 100, expectedFont: "CSSTest Weights 900" },
+            { weight: 249, expectedFont: "CSSTest Weights 900" },
+            { weight: 250, expectedFont: "CSSTest Weights 800" },
+            { weight: 399, expectedFont: "CSSTest Weights 800" },
+            { weight: 400, expectedFont: "CSSTest Weights 700" },
+            { weight: 420, expectedFont: "CSSTest Weights 600" },
+            { weight: 470, expectedFont: "CSSTest Weights 300" },
+            { weight: 500, expectedFont: "CSSTest Weights 300" },
+            { weight: 600, expectedFont: "CSSTest Weights 200" },
+            { weight: 750, expectedFont: "CSSTest Weights 200" },
+            { weight: 751, expectedFont: "CSSTest Weights 100" },
+            { weight: 900, expectedFont: "CSSTest Weights 100" },
+            { weight:1000, expectedFont: "CSSTest Weights 100" },
+        ];
+
+        // wait for the fonts to load
+        // -- this should not be necessary if the fonts are installed as required
+        // -- but if they are not, the test is otherwise unstable
+        var once_fonts_are_ready = (document.fonts ? document.fonts.ready : new Promise(function(ready) { window.onload = time => [...document.querySelectorAll('body > span:nth-child(1) > span')].every(e => e.offsetWidth > 20) ? ready() : requestAnimationFrame(window.onload) }));
+
+        var testcases = document.querySelector("#testcases");
+        var testcaseTemplate = document.querySelector('.testcase'); testcaseTemplate.remove();
+        testFontFaceMatch.forEach(function(element) {
+
+            var testcase = testcaseTemplate.cloneNode(true);
+
+            // setup the test case style
+            testcase.style.fontFamily = 'fontMatch';
+            testcase.style.fontWeight = element.weight;
+
+            // create the assertion
+            var assertText = 'A' + /\d/.exec(element.expectedFont)[0];
+            testcase.textContent = assertText;
+
+            // append the testcase
+            testcases.appendChild(testcase);
+
+            // verify the testcase
+            promise_test(
+                assert => once_fonts_are_ready.then(assert => { assert_approx_equals(testcase.getBoundingClientRect().width, 90, 2, "@font-face should be mapped to " + element.expectedFont + "."); }),
+                "Test @font-face matching for weight " + element.weight
+            );
+        });
+
+    </script>
+</body>
+</html>
diff --git a/css/css-fonts/variations/font-weight-parsing.html b/css/css-fonts/variations/font-weight-parsing.html
new file mode 100644
index 0000000..657616c
--- /dev/null
+++ b/css/css-fonts/variations/font-weight-parsing.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Testing the new font-weight values introduced in CSS Fonts level 4</title>
+    <link rel="help" href="https://www.w3.org/TR/css-fonts-4/#font-weight-prop" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+    <div id="computedStyleTest">A</div>
+
+    <script>
+
+        var testContinuousWeights = [
+            { weight: "401",      isValid: true,  message: "Values that are not multiple of 100 should be parsed successfully" },
+            { weight: "400.5",    isValid: true,  message: "Non-integer Values should be parsed successfully" },
+            { weight: "1",        isValid: true,  message: "Minimum allowed value should be parsed successfully" },
+            { weight: "0.999",    isValid: false, message: "Values below minimum should be rejected" },
+            { weight: "-100",     isValid: false, message: "Values below zero should be rejected" },
+            { weight: "1000",     isValid: true,  message: "Maximum allowed value should be parsed successfully" },
+            { weight: "1000.001", isValid: false, message: "Values above maximum should be rejected" },
+            { weight: "calc(100.5)", isValid: true, expectedWeight: "100.5", message: "Simple calc value" },
+            { weight: "calc(-100)", isValid: true, expectedWeight: "1", message: "Negative simple calc value (to be clamped)" },
+            { weight: "calc(1001)", isValid: true, expectedWeight: "1000", message: "Out-of-range simple calc value (to be clamped)" },
+            { weight: "calc(100.5*3 + 50.5)", isValid: true, expectedWeight: "352", message: "Valid calc expression" },
+            { weight: "calc(100.5*3 + 800)", isValid: true, expectedWeight: "1000", message: "Valid calc expression with out-of-range value (to be clamped)" },
+            { weight: "calc(100.5px + 50.5px)", isValid: false, message: "Valid calc expression with units" },
+            { weight: "400 700", isValid: false, message: "Extra number after numeric value" },
+            { weight: "400 10px", isValid: false, message: "Extra content after numeric value" },
+            { weight: "bold 400", isValid: false, message: "Extra content after keyword value" },
+            { weight: "calc(100.5) 400", isValid: false, message: "Extra content after calc value" }
+        ];
+
+        testContinuousWeights.forEach(function (element) {
+            test(() => { assert_equals(window.CSS.supports("font-weight", element.weight), element.isValid, element.message); }, "@supports: " + element.message);
+        });
+
+        testContinuousWeights.forEach(function (element) {
+            var testElement  = document.getElementById("computedStyleTest");
+
+            if (element.isValid) {
+                testElement.style.fontWeight = "300";
+                testElement.style.fontWeight = element.weight;
+                var expectedWeight = (element.expectedWeight) ? element.expectedWeight : element.weight;
+
+                test(() => { assert_equals(window.getComputedStyle(testElement).fontWeight, expectedWeight, element.message); }, "Computed style: " + element.message);
+            }
+        });
+
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/css/css-fonts/variations/resources/ahem.ttc b/css/css-fonts/variations/resources/ahem.ttc
new file mode 100644
index 0000000..096dd60
--- /dev/null
+++ b/css/css-fonts/variations/resources/ahem.ttc
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-100-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-100-kerned.ttf
new file mode 100644
index 0000000..33553bc
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-100-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-1479-w1-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-1479-w1-kerned.ttf
new file mode 100644
index 0000000..d3add19
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-1479-w1-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-1479-w4-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-1479-w4-kerned.ttf
new file mode 100644
index 0000000..813e5da
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-1479-w4-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-1479-w7-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-1479-w7-kerned.ttf
new file mode 100644
index 0000000..a446f23
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-1479-w7-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-1479-w9-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-1479-w9-kerned.ttf
new file mode 100644
index 0000000..4eb89ad
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-1479-w9-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-15-w1-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-15-w1-kerned.ttf
new file mode 100644
index 0000000..78b0f9e
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-15-w1-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-15-w5-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-15-w5-kerned.ttf
new file mode 100644
index 0000000..ffbb5da
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-15-w5-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-200-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-200-kerned.ttf
new file mode 100644
index 0000000..9713a7d
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-200-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-24-w2-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-24-w2-kerned.ttf
new file mode 100644
index 0000000..838d54a
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-24-w2-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-24-w4-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-24-w4-kerned.ttf
new file mode 100644
index 0000000..70bb052
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-24-w4-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-2569-w2-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-2569-w2-kerned.ttf
new file mode 100644
index 0000000..0af6a94
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-2569-w2-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-2569-w5-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-2569-w5-kerned.ttf
new file mode 100644
index 0000000..7ffd46f
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-2569-w5-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-2569-w6-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-2569-w6-kerned.ttf
new file mode 100644
index 0000000..62c61c6
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-2569-w6-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-2569-w9-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-2569-w9-kerned.ttf
new file mode 100644
index 0000000..dd4fdc0
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-2569-w9-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-258-w2-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-258-w2-kerned.ttf
new file mode 100644
index 0000000..8a8a095
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-258-w2-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-258-w5-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-258-w5-kerned.ttf
new file mode 100644
index 0000000..e7597aa
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-258-w5-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-258-w8-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-258-w8-kerned.ttf
new file mode 100644
index 0000000..dd274d2
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-258-w8-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-300-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-300-kerned.ttf
new file mode 100644
index 0000000..7668bc7
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-300-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-3589-w3-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-3589-w3-kerned.ttf
new file mode 100644
index 0000000..409bbb5
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-3589-w3-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-3589-w5-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-3589-w5-kerned.ttf
new file mode 100644
index 0000000..639cf41
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-3589-w5-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-3589-w8-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-3589-w8-kerned.ttf
new file mode 100644
index 0000000..e5c85c6
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-3589-w8-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-3589-w9-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-3589-w9-kerned.ttf
new file mode 100644
index 0000000..25e4652
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-3589-w9-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-400-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-400-kerned.ttf
new file mode 100644
index 0000000..3046e3c
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-400-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-47-w4-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-47-w4-kerned.ttf
new file mode 100644
index 0000000..9d95fd9
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-47-w4-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-47-w7-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-47-w7-kerned.ttf
new file mode 100644
index 0000000..796f4d5
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-47-w7-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-500-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-500-kerned.ttf
new file mode 100644
index 0000000..d8d136d
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-500-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-600-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-600-kerned.ttf
new file mode 100644
index 0000000..ce7307e
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-600-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-700-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-700-kerned.ttf
new file mode 100644
index 0000000..a80d868
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-700-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-800-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-800-kerned.ttf
new file mode 100644
index 0000000..1fb2730
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-800-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-900-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-900-kerned.ttf
new file mode 100644
index 0000000..faf4bbc
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-900-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w1-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w1-kerned.ttf
new file mode 100644
index 0000000..8b4fc0d
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w1-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w2-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w2-kerned.ttf
new file mode 100644
index 0000000..112683d
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w2-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w3-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w3-kerned.ttf
new file mode 100644
index 0000000..d0ffa58
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w3-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w4-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w4-kerned.ttf
new file mode 100644
index 0000000..faab448
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w4-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w5-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w5-kerned.ttf
new file mode 100644
index 0000000..df6e790
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w5-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w6-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w6-kerned.ttf
new file mode 100644
index 0000000..9d48192
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w6-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w7-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w7-kerned.ttf
new file mode 100644
index 0000000..a358362
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w7-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w8-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w8-kerned.ttf
new file mode 100644
index 0000000..458e401
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w8-kerned.ttf
Binary files differ
diff --git a/css/css-fonts/variations/resources/csstest-weights-full-w9-kerned.ttf b/css/css-fonts/variations/resources/csstest-weights-full-w9-kerned.ttf
new file mode 100644
index 0000000..006a280
--- /dev/null
+++ b/css/css-fonts/variations/resources/csstest-weights-full-w9-kerned.ttf
Binary files differ
diff --git a/css/css-grid/abspos/absolute-positioning-grid-container-containing-block-001.html b/css/css-grid/abspos/absolute-positioning-grid-container-containing-block-001.html
index a447ead..e456af8 100644
--- a/css/css-grid/abspos/absolute-positioning-grid-container-containing-block-001.html
+++ b/css/css-grid/abspos/absolute-positioning-grid-container-containing-block-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <meta name="assert" content="This test checks the behavior of the absolutely positioned elements with a grid container as containing block.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/absolute-positioning-grid-container-parent-001.html b/css/css-grid/abspos/absolute-positioning-grid-container-parent-001.html
index 82e6063..e798b80 100644
--- a/css/css-grid/abspos/absolute-positioning-grid-container-parent-001.html
+++ b/css/css-grid/abspos/absolute-positioning-grid-container-parent-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <meta name="assert" content="This test checks the behavior of the absolutely positioned elements with a grid container as parent.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-paint-positioned-children-001.html b/css/css-grid/abspos/grid-paint-positioned-children-001.html
index 7528b08..ae074da 100644
--- a/css/css-grid/abspos/grid-paint-positioned-children-001.html
+++ b/css/css-grid/abspos/grid-paint-positioned-children-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="match" href="grid-paint-positioned-children-001-ref.html">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 .grid {
   border: 2px solid black;
diff --git a/css/css-grid/abspos/grid-positioned-children-writing-modes-001-ref.html b/css/css-grid/abspos/grid-positioned-children-writing-modes-001-ref.html
index 3610cd3..e8ab242 100644
--- a/css/css-grid/abspos/grid-positioned-children-writing-modes-001-ref.html
+++ b/css/css-grid/abspos/grid-positioned-children-writing-modes-001-ref.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <title>CSS Grid Layout Test: Grid positioned children writing modes reference test</title>
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-children-writing-modes-001.html b/css/css-grid/abspos/grid-positioned-children-writing-modes-001.html
index b922639..eb85527 100644
--- a/css/css-grid/abspos/grid-positioned-children-writing-modes-001.html
+++ b/css/css-grid/abspos/grid-positioned-children-writing-modes-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="match" href="grid-positioned-children-writing-modes-001-ref.html">
 <meta name="assert" content="This test checks the behavior of the positioned grid children in combination with the writing modes and text direction properties.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-001.html b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-001.html
new file mode 100644
index 0000000..81ea909
--- /dev/null
+++ b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-001.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid positioned items in auto-fit tracks</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat" title="7.2.2.2. Repeat-to-fill: auto-fill and auto-fit repetitions">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#valdef-repeat-auto-fit">
+<meta name="assert" content="This test checks that positioned items don't avoid auto-fit tracks to collapse.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.container {
+  width: 200px;
+}
+.grid {
+  position: relative;
+  grid: 10px / repeat(auto-fit, 30px);
+}
+span {
+  background: blue;
+}
+.abs {
+  position: absolute;
+  top:0; right:0; bottom:0; left:0;
+  background: pink;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div id="log"></div>
+<br>
+<div class="container">
+    <div class="grid">
+        <span style="grid-column: 1 / 5" class="abs" data-expected-width="30" data-expected-height="10"></span>
+        <span style="grid-column: 1" data-expected-width="30" data-expected-height="10"></span>
+    </div>
+</div>
+</body>
diff --git a/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-002.html b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-002.html
new file mode 100644
index 0000000..27fc722
--- /dev/null
+++ b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-002.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid positioned items in auto-fit tracks</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat" title="7.2.2.2. Repeat-to-fill: auto-fill and auto-fit repetitions">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#valdef-repeat-auto-fit">
+<meta name="assert" content="This test checks tracks before the first in-flow item also collapse and positioned items don't have any impact.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.container {
+  width: 200px;
+}
+.grid {
+  position: relative;
+  grid: 10px / repeat(auto-fit, 30px);
+}
+span {
+  background: blue;
+}
+.abs {
+  position: absolute;
+  top:0; right:0; bottom:0; left:0;
+  background: pink;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div id="log"></div>
+<br>
+<div class="container">
+    <div class="grid">
+        <span style="grid-column: 1 / 5" class="abs" data-expected-width="30" data-expected-height="10"></span>
+        <span style="grid-column: 2" data-expected-width="30" data-expected-height="10"></span>
+    </div>
+</div>
+</body>
diff --git a/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-003.html b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-003.html
new file mode 100644
index 0000000..1c89339
--- /dev/null
+++ b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-003.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid positioned items in auto-fit tracks</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat" title="7.2.2.2. Repeat-to-fill: auto-fill and auto-fit repetitions">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#valdef-repeat-auto-fit">
+<meta name="assert" content="This test checks that positioned items will use the area defined by the in-flow items, ignoring any collapsed track.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.container {
+  width: 200px;
+}
+.grid {
+  position: relative;
+  grid: 10px / repeat(auto-fit, 30px);
+}
+span {
+  background: blue;
+}
+.abs {
+  position: absolute;
+  top:0; right:0; bottom:0; left:0;
+  background: pink;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div id="log"></div>
+<br>
+<div class="container">
+    <div class="grid">
+        <span style="grid-column: 1 / 5" class="abs" data-expected-width="60" data-expected-height="10"></span>
+        <span style="grid-column: 2 / 4" data-expected-width="60" data-expected-height="10"></span>
+    </div>
+</div>
+</body>
diff --git a/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-004.html b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-004.html
new file mode 100644
index 0000000..54b2ff7
--- /dev/null
+++ b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-004.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid positioned items in auto-fit tracks and gaps</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat" title="7.2.2.2. Repeat-to-fill: auto-fill and auto-fit repetitions">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#valdef-repeat-auto-fit">
+<meta name="assert" content="This test checks that positioned items ignore collapsed gaps.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.container {
+  width: 200px;
+}
+.grid {
+  position: relative;
+  grid: 10px / repeat(auto-fit, 30px);
+  grid-gap: 5px;
+}
+span {
+  background: blue;
+}
+.abs {
+  position: absolute;
+  top:0; right:0; bottom:0; left:0;
+  background: pink;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div id="log"></div>
+<br>
+<div class="container">
+    <div class="grid">
+        <span style="grid-column: 1 / 5" class="abs" data-expected-width="30" data-expected-height="10"></span>
+        <span style="grid-column: 1" data-expected-width="30" data-expected-height="10"></span>
+    </div>
+</div>
+</body>
diff --git a/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-005.html b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-005.html
new file mode 100644
index 0000000..56f870f
--- /dev/null
+++ b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-005.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid positioned items in auto-fit tracks and gaps</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat" title="7.2.2.2. Repeat-to-fill: auto-fill and auto-fit repetitions">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#valdef-repeat-auto-fit">
+<meta name="assert" content="This test checks that positioned items ignore collapsed gaps, both before and after the first in-flow item .">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.container {
+  width: 200px;
+}
+.grid {
+  position: relative;
+  grid: 10px / repeat(auto-fit, 30px);
+  grid-gap: 5px;
+}
+span {
+  background: blue;
+}
+.abs {
+  position: absolute;
+  top:0; right:0; bottom:0; left:0;
+  background: pink;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div id="log"></div>
+<br>
+<div class="container">
+    <div class="grid">
+        <span style="grid-column: 1 / 5" class="abs" data-expected-width="30" data-expected-height="10"></span>
+        <span style="grid-column: 2" data-expected-width="30" data-expected-height="10"></span>
+    </div>
+</div>
+</body>
diff --git a/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-006.html b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-006.html
new file mode 100644
index 0000000..e301d34
--- /dev/null
+++ b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-006.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid positioned items in auto-fit tracks and gaps</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat" title="7.2.2.2. Repeat-to-fill: auto-fill and auto-fit repetitions">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#valdef-repeat-auto-fit">
+<meta name="assert" content="This test checks that positioned items ignore collapsed gaps but consider those between in-flow items inside their grid area.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.container {
+  width: 200px;
+}
+.grid {
+  position: relative;
+  grid: 10px / repeat(auto-fit, 30px);
+  grid-gap: 5px;
+}
+span {
+  background: blue;
+}
+.abs {
+  position: absolute;
+  top:0; right:0; bottom:0; left:0;
+  background: pink;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div id="log"></div>
+<br>
+<div class="container">
+    <div class="grid">
+        <span style="grid-column: 2 / 5" class="abs" data-expected-width="65" data-expected-height="10"></span>
+        <span style="grid-column: 2 / 4" data-expected-width="65" data-expected-height="10"></span>
+    </div>
+</div>
+</body>
diff --git a/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-007.html b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-007.html
new file mode 100644
index 0000000..8c46216
--- /dev/null
+++ b/css/css-grid/abspos/grid-positioned-items-and-autofit-tracks-007.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid positioned items in auto-fit tracks and gaps</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat" title="7.2.2.2. Repeat-to-fill: auto-fill and auto-fit repetitions">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#valdef-repeat-auto-fit">
+<meta name="assert" content="This test checks that positioned items ignore collapsed gaps even with non-empty tracks before and after.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.container {
+  width: 250px;
+}
+.grid {
+  position: relative;
+  grid: 10px / repeat(auto-fit, 30px) 50px;
+  grid-gap: 5px;
+}
+span {
+  background: blue;
+}
+.abs {
+  position: absolute;
+  top:0; right:0; bottom:0; left:0;
+  background: pink;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div id="log"></div>
+<br>
+<div class="container">
+    <div class="grid">
+        <span style="grid-column: 2 / 5" class="abs" data-expected-width="65" data-expected-height="10"></span>
+        <span style="grid-column: 2 / 4" data-expected-width="65" data-expected-height="10"></span>
+    </div>
+</div>
+</body>
diff --git a/css/css-grid/abspos/grid-positioned-items-background-001.html b/css/css-grid/abspos/grid-positioned-items-background-001.html
index 2b299cd..19ee662 100644
--- a/css/css-grid/abspos/grid-positioned-items-background-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-background-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="match" href="grid-positioned-items-background-001-ref.html">
 <meta name="assert" content="This test checks that the background of positioned items is painted in the right position">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-background-rtl-001.html b/css/css-grid/abspos/grid-positioned-items-background-rtl-001.html
index 50741e2..9ed046b 100644
--- a/css/css-grid/abspos/grid-positioned-items-background-rtl-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-background-rtl-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="match" href="grid-positioned-items-background-rtl-001-ref.html">
 <meta name="assert" content="This test checks that the background of positioned items is painted in the right position using RTL direction.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-content-alignment-001.html b/css/css-grid/abspos/grid-positioned-items-content-alignment-001.html
index d99dc53..ebff62a 100644
--- a/css/css-grid/abspos/grid-positioned-items-content-alignment-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-content-alignment-001.html
@@ -5,8 +5,8 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align" title="10.5. Aligning the Grid: the justify-content and align-content properties">
 <meta name="assert" content="This test checks the behavior of the positioned items in a grid using content alignment.">
-<link rel="stylesheet" href="../support/grid.css">
-<link rel="stylesheet" href="../support/grid-alignment.css">
+<link rel="stylesheet" href="support/grid.css">
+<link rel="stylesheet" href="../../support/alignment.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-content-alignment-rtl-001.html b/css/css-grid/abspos/grid-positioned-items-content-alignment-rtl-001.html
index 40580ed..628a63c 100644
--- a/css/css-grid/abspos/grid-positioned-items-content-alignment-rtl-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-content-alignment-rtl-001.html
@@ -5,8 +5,8 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align" title="10.5. Aligning the Grid: the justify-content and align-content properties">
 <meta name="assert" content="This test checks the behavior of the positioned items in a grid using content alignment in RTL.">
-<link rel="stylesheet" href="../support/grid.css">
-<link rel="stylesheet" href="../support/grid-alignment.css">
+<link rel="stylesheet" href="support/grid.css">
+<link rel="stylesheet" href="../../support/alignment.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-gaps-001.html b/css/css-grid/abspos/grid-positioned-items-gaps-001.html
index e1adc6b..b9f3343 100644
--- a/css/css-grid/abspos/grid-positioned-items-gaps-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-gaps-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#gutters" title="10.1. Gutters: the row-gap, column-gap, and gap properties">
 <meta name="assert" content="This test checks the behavior of the positioned items in a grid container with gaps.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-gaps-rtl-001.html b/css/css-grid/abspos/grid-positioned-items-gaps-rtl-001.html
index 4a6cd88..16ab39d 100644
--- a/css/css-grid/abspos/grid-positioned-items-gaps-rtl-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-gaps-rtl-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#gutters" title="10.1. Gutters: the row-gap, column-gap, and gap properties">
 <meta name="assert" content="This test checks the behavior of the positioned items in a grid container with gaps in RTL.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-implicit-grid-001.html b/css/css-grid/abspos/grid-positioned-items-implicit-grid-001.html
index 6d62088..71f096a 100644
--- a/css/css-grid/abspos/grid-positioned-items-implicit-grid-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-implicit-grid-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#implicit-grids" title="7.5. The Implicit Grid">
 <meta name="assert" content="This test checks the behavior of the absolutely positioned grid items placed on the implicit grid.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-implicit-grid-line-001.html b/css/css-grid/abspos/grid-positioned-items-implicit-grid-line-001.html
index f0f98d2..7b8fa4e 100644
--- a/css/css-grid/abspos/grid-positioned-items-implicit-grid-line-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-implicit-grid-line-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#implicit-grids" title="7.5. The Implicit Grid">
 <meta name="assert" content="This test checks that grid placement properties of absolutely positioned items using implicit grid lines are treated as 'auto'.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-padding-001.html b/css/css-grid/abspos/grid-positioned-items-padding-001.html
index a0ccd1e..4941d77 100644
--- a/css/css-grid/abspos/grid-positioned-items-padding-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-padding-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <meta name="assert" content="This test checks that positioned grid items can be placed directly on the padding.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-unknown-named-grid-line-001.html b/css/css-grid/abspos/grid-positioned-items-unknown-named-grid-line-001.html
index a50e12a..24d9d76 100644
--- a/css/css-grid/abspos/grid-positioned-items-unknown-named-grid-line-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-unknown-named-grid-line-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <meta name="assert" content="This test checks that grid placement properties of absolutely positioned items using unknown named grid lines are treated as 'auto'.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-positioned-items-within-grid-implicit-track-001.html b/css/css-grid/abspos/grid-positioned-items-within-grid-implicit-track-001.html
index fb9cad1..5a31c35 100644
--- a/css/css-grid/abspos/grid-positioned-items-within-grid-implicit-track-001.html
+++ b/css/css-grid/abspos/grid-positioned-items-within-grid-implicit-track-001.html
@@ -5,7 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#implicit-grids" title="7.5. The Implicit Grid">
 <meta name="assert" content="This test checks that positioned grid items are placed properly (including implicit tracks) even if the grid has implicit tracks.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/grid-sizing-positioned-items-001.html b/css/css-grid/abspos/grid-sizing-positioned-items-001.html
index 2acae44..0e8cd33 100644
--- a/css/css-grid/abspos/grid-sizing-positioned-items-001.html
+++ b/css/css-grid/abspos/grid-sizing-positioned-items-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <meta name="assert" content="This test checks the different size options for absolutely positioned grid items.">
-<link href="../support/grid.css" rel="stylesheet">
+<link href="support/grid.css" rel="stylesheet">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/positioned-grid-items-should-not-create-implicit-tracks-001.html b/css/css-grid/abspos/positioned-grid-items-should-not-create-implicit-tracks-001.html
index 56aa0e1..5ef006a 100644
--- a/css/css-grid/abspos/positioned-grid-items-should-not-create-implicit-tracks-001.html
+++ b/css/css-grid/abspos/positioned-grid-items-should-not-create-implicit-tracks-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <meta name="assert" content="This test checks that positioned items shouldn't create implicit tracks on the grid.">
-<link href="../support/grid.css" rel="stylesheet">
+<link href="support/grid.css" rel="stylesheet">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/positioned-grid-items-should-not-take-up-space-001.html b/css/css-grid/abspos/positioned-grid-items-should-not-take-up-space-001.html
index a6834cb..597e778 100644
--- a/css/css-grid/abspos/positioned-grid-items-should-not-take-up-space-001.html
+++ b/css/css-grid/abspos/positioned-grid-items-should-not-take-up-space-001.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#abspos" title="9. Absolute Positioning">
 <meta name="assert" content="This test checks that positioned items shouldn't take up space or otherwise participate in the layout of the grid.">
-<link href="../support/grid.css" rel="stylesheet">
+<link href="support/grid.css" rel="stylesheet">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/positioned-grid-items-sizing-001-ref.html b/css/css-grid/abspos/positioned-grid-items-sizing-001-ref.html
index a468863..2ded937 100644
--- a/css/css-grid/abspos/positioned-grid-items-sizing-001-ref.html
+++ b/css/css-grid/abspos/positioned-grid-items-sizing-001-ref.html
@@ -3,7 +3,7 @@
 <title>CSS Grid Layout Test: Positioned grid items sizing reference file</title>
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <meta name="flags" content="ahem">
-<link href="../support/grid.css" rel="stylesheet">
+<link href="support/grid.css" rel="stylesheet">
 <style>
 
 .grid {
diff --git a/css/css-grid/abspos/positioned-grid-items-sizing-001.html b/css/css-grid/abspos/positioned-grid-items-sizing-001.html
index ae12806..4f84957 100644
--- a/css/css-grid/abspos/positioned-grid-items-sizing-001.html
+++ b/css/css-grid/abspos/positioned-grid-items-sizing-001.html
@@ -6,7 +6,7 @@
 <link rel="match" href="positioned-grid-items-sizing-001-ref.html">
 <meta name="assert" content="This test checks that the sizing of positioned grid items without specific dimensions or offsets is equivalent to the size of regular items.">
 <meta name="flags" content="ahem">
-<link href="../support/grid.css" rel="stylesheet">
+<link href="support/grid.css" rel="stylesheet">
 <style>
 
 .grid {
diff --git a/css/css-grid/support/grid.css b/css/css-grid/abspos/support/grid.css
similarity index 100%
rename from css/css-grid/support/grid.css
rename to css/css-grid/abspos/support/grid.css
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-001.html b/css/css-grid/alignment/grid-alignment-implies-size-change-001.html
index c5c2cf5..2dec3c7 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-001.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-001.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 200);
   grid.style.alignItems = "start";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-002.html b/css/css-grid/alignment/grid-alignment-implies-size-change-002.html
index f0dc1530..4463ebf 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-002.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-002.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-height", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-003.html b/css/css-grid/alignment/grid-alignment-implies-size-change-003.html
index 3ae1ba1..f9e1c87 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-003.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-003.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 200);
   grid.style.alignItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-height", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-004.html b/css/css-grid/alignment/grid-alignment-implies-size-change-004.html
index 12ca38d..ab53220 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-004.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-004.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 200);
   grid.style.alignItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-height", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-005.html b/css/css-grid/alignment/grid-alignment-implies-size-change-005.html
index e76a152..3002c17 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-005.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-005.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-height", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-006.html b/css/css-grid/alignment/grid-alignment-implies-size-change-006.html
index 6622601..cf44e9f 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-006.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-006.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 200);
   grid.style.alignItems = "start";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-007.html b/css/css-grid/alignment/grid-alignment-implies-size-change-007.html
index 300ace5..f8aa2da 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-007.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-007.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-height", 80);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-008.html b/css/css-grid/alignment/grid-alignment-implies-size-change-008.html
index e3a8b47..24af744 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-008.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-008.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 80);
   grid.style.alignItems = "start";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-009.html b/css/css-grid/alignment/grid-alignment-implies-size-change-009.html
index 3f7d675..bb33c4a 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-009.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-009.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-height", 80);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-010.html b/css/css-grid/alignment/grid-alignment-implies-size-change-010.html
index fc284b0..876fdcd 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-010.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-010.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 80);
   grid.style.alignItems = "start";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-011.html b/css/css-grid/alignment/grid-alignment-implies-size-change-011.html
index 1367dd4..d219cab 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-011.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-011.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 200);
   grid.style.alignItems = "start";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-012.html b/css/css-grid/alignment/grid-alignment-implies-size-change-012.html
index cebc217..d8b24be 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-012.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-012.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-height", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-013.html b/css/css-grid/alignment/grid-alignment-implies-size-change-013.html
index b606c03..5520a4c 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-013.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-013.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 200);
   grid.style.alignItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-014.html b/css/css-grid/alignment/grid-alignment-implies-size-change-014.html
index 61d6365..26ff656 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-014.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-014.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-height", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-015.html b/css/css-grid/alignment/grid-alignment-implies-size-change-015.html
index b264e36..1a5e52c 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-015.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-015.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-016.html b/css/css-grid/alignment/grid-alignment-implies-size-change-016.html
index 86d5226..66e7e2b 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-016.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-016.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "start";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-017.html b/css/css-grid/alignment/grid-alignment-implies-size-change-017.html
index 543bd0a..d994762 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-017.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-017.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 100);
   grid.style.alignItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-height", 80);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-018.html b/css/css-grid/alignment/grid-alignment-implies-size-change-018.html
index 33aaa6b..f6d0bcb 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-018.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-018.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-height", 80);
   grid.style.alignItems = "start";
   evaluateStyleChange(item, "after", "data-expected-height", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-019.html b/css/css-grid/alignment/grid-alignment-implies-size-change-019.html
index c98144c..53139cb 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-019.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-019.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 200);
   grid.style.justifyItems = "start";
   evaluateStyleChange(item, "after", "data-expected-width", 120);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-020.html b/css/css-grid/alignment/grid-alignment-implies-size-change-020.html
index d2fab32..0b6a26f 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-020.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-020.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 120);
   grid.style.justifyItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-width", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-021.html b/css/css-grid/alignment/grid-alignment-implies-size-change-021.html
index 1a40142..7b68964 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-021.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-021.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 200);
   grid.style.alignItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-width", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-022.html b/css/css-grid/alignment/grid-alignment-implies-size-change-022.html
index 3ea469b..e4f9e7c 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-022.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-022.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 200);
   grid.style.justifyItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-width", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-023.html b/css/css-grid/alignment/grid-alignment-implies-size-change-023.html
index e78986d..a1bfe9c 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-023.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-023.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 120);
   grid.style.justifyItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-width", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-024.html b/css/css-grid/alignment/grid-alignment-implies-size-change-024.html
index 9f6c49c..6d3136e 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-024.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-024.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 200);
   grid.style.justifyItems = "start";
   evaluateStyleChange(item, "after", "data-expected-width", 120);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-025.html b/css/css-grid/alignment/grid-alignment-implies-size-change-025.html
index 471c2ef..917d110 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-025.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-025.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 120);
   grid.style.justifyItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-width", 80);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-026.html b/css/css-grid/alignment/grid-alignment-implies-size-change-026.html
index facad8a..382ef25 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-026.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-026.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 80);
   grid.style.justifyItems = "start";
   evaluateStyleChange(item, "after", "data-expected-width", 120);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-027.html b/css/css-grid/alignment/grid-alignment-implies-size-change-027.html
index dc9c092..0bdedf7 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-027.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-027.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 120);
   grid.style.justifyItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-width", 80);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-028.html b/css/css-grid/alignment/grid-alignment-implies-size-change-028.html
index 870538a..0def10e 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-028.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-028.html
@@ -26,12 +26,13 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 80);
   grid.style.justifyItems = "start";
   evaluateStyleChange(item, "after", "data-expected-width", 120);
+  done();
 }
 </script>
 <body onload="runTest()">
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-029.html b/css/css-grid/alignment/grid-alignment-implies-size-change-029.html
index cb067b6..5247247 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-029.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-029.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 200);
   grid.style.justifyItems = "start";
   evaluateStyleChange(item, "after", "data-expected-width", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-height="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-height="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-030.html b/css/css-grid/alignment/grid-alignment-implies-size-change-030.html
index c7033bb..4254fd6 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-030.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-030.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 100);
   grid.style.justifyItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-width", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-height="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-height="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-031.html b/css/css-grid/alignment/grid-alignment-implies-size-change-031.html
index 55bffbe..35d0dcc 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-031.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-031.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 200);
   grid.style.justifyItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-width", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-height="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-height="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-032.html b/css/css-grid/alignment/grid-alignment-implies-size-change-032.html
index 006cb6a..152c641 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-032.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-032.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 100);
   grid.style.justifyItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-width", 200);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-width="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-width="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-033.html b/css/css-grid/alignment/grid-alignment-implies-size-change-033.html
index 157f43b..5db0980 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-033.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-033.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 100);
   grid.style.justifyItems = "normal";
   evaluateStyleChange(item, "after", "data-expected-width", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-height="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-height="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-034.html b/css/css-grid/alignment/grid-alignment-implies-size-change-034.html
index 6dbd653..98e0436 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-034.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-034.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 100);
   grid.style.justifyItems = "start";
   evaluateStyleChange(item, "after", "data-expected-width", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-height="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-height="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-035.html b/css/css-grid/alignment/grid-alignment-implies-size-change-035.html
index fbd8e6f..c9810b3 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-035.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-035.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 100);
   grid.style.justifyItems = "stretch";
   evaluateStyleChange(item, "after", "data-expected-width", 80);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-height="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-height="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-alignment-implies-size-change-036.html b/css/css-grid/alignment/grid-alignment-implies-size-change-036.html
index 018b13c..634e5c7 100644
--- a/css/css-grid/alignment/grid-alignment-implies-size-change-036.html
+++ b/css/css-grid/alignment/grid-alignment-implies-size-change-036.html
@@ -26,16 +26,17 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/check-layout-th.js"></script>
-<script src="../support/style-change.js"></script>
+<script src="support/style-change.js"></script>
 <script>
 function runTest() {
   evaluateStyleChange(item, "before", "data-expected-width", 80);
   grid.style.justifyItems = "start";
   evaluateStyleChange(item, "after", "data-expected-width", 100);
+  done();
 }
 </script>
 <body onload="runTest()">
 <div class="grid" id="grid">
-  <img data-expected-height="100" id="item" src="../grid-items/support/100x100-green.png"></img>
+  <img data-expected-height="100" id="item" src="support/100x100-green.png"></img>
 </div>
 </body>
diff --git a/css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-001.html b/css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-001.html
new file mode 100644
index 0000000..975360f
--- /dev/null
+++ b/css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-001.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Self-Alignment along column axis of stcky positioned items</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
+<link rel="stylesheet" href="../../support/alignment.css">
+<meta name="assert" content="Sticky positioned grid items are aligned correcly.">
+<style>
+.container {
+  border: solid 1px;
+  overflow: auto;
+  height: 500px;
+}
+.grid {
+  position: relative;
+  float: left;
+  display: grid;
+  grid-template-columns: 75px 75px 75px 75px;
+  grid-template-rows: 100px 100px 100px 300px;
+  background: grey;
+  height: 400px;
+  margin-right: 20px;
+}
+.sticky {
+  position: -webkit-sticky;
+  position: sticky;
+  width: 20px;
+  height: 20px;
+  background-color: #cae8ca;
+}
+.item1 {
+  top: 0px;
+  grid-column: 1;
+  grid-row: 1;
+}
+.item2 {
+  top: 0px;
+  grid-column: 2;
+  grid-row: 2;
+}
+.item3 {
+  top: 0px;
+  grid-column: 3;
+  grid-row: 3;
+}
+.item4 {
+  grid-column: 4;
+  grid-row: 4;
+  background: lightgrey;
+}
+.scroll { overflow: auto; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div class="container">
+    <div style="height:30px"></div>
+    <div class="grid">
+        <div class="item1 sticky alignSelfStart"  data-offset-x="0"   data-offset-y="0"></div>
+        <div class="item2 sticky alignSelfCenter" data-offset-x="75"  data-offset-y="140"></div>
+        <div class="item3 sticky alignSelfEnd"    data-offset-x="150" data-offset-y="280"></div>
+        <div class="item4"></div>
+    </div>
+    <div class="grid scroll">
+        <div class="item1 sticky alignSelfStart"  data-offset-x="0"   data-offset-y="0"></div>
+        <div class="item2 sticky alignSelfCenter" data-offset-x="75"  data-offset-y="140"></div>
+        <div class="item3 sticky alignSelfEnd"    data-offset-x="150" data-offset-y="280"></div>
+        <div class="item4"></div>
+    </div>
+    <div style="height:2000px"></div>
+</div>
+</body>
diff --git a/css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-002.html b/css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-002.html
new file mode 100644
index 0000000..357cbe1
--- /dev/null
+++ b/css/css-grid/alignment/grid-column-axis-alignment-sticky-positioned-items-002.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Self-Alignment along column axis of stcky positioned items</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
+<link rel="stylesheet" href="../../support/alignment.css">
+<meta name="assert" content="Sticky positioned grid items are aligned correcly, but preserving non-static positions when required.">
+<style>
+.container {
+  border: solid 1px;
+  overflow: auto;
+  height: 500px;
+}
+.grid {
+  position: relative;
+  float: left;
+  display: grid;
+  grid-template-columns: 75px 75px 75px 75px;
+  grid-template-rows: 100px 100px 100px 300px;
+  background: grey;
+  height: 400px;
+  margin-right: 20px;
+}
+.sticky {
+  position: -webkit-sticky;
+  position: sticky;
+  width: 20px;
+  height: 20px;
+  background-color: #cae8ca;
+}
+.item1 {
+  top: 40px;
+  grid-column: 1;
+  grid-row: 1;
+}
+.item2 {
+  top: 100px;
+  grid-column: 2;
+  grid-row: 2;
+}
+.item3 {
+  top: 290px;
+  grid-column: 3;
+  grid-row: 3;
+}
+.item4 {
+  grid-column: 4;
+  grid-row: 4;
+  background: lightgrey;
+}
+.scroll { overflow: auto; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div class="container">
+    <div style="height:30px"></div>
+    <div class="grid">
+        <div class="item1 sticky alignSelfStart"  data-offset-x="0"   data-offset-y="10"></div>
+        <div class="item2 sticky alignSelfCenter" data-offset-x="75"  data-offset-y="140"></div>
+        <div class="item3 sticky alignSelfEnd"    data-offset-x="150" data-offset-y="280"></div>
+        <div class="item4"></div>
+    </div>
+    <div class="grid scroll">
+        <div class="item1 sticky alignSelfStart"  data-offset-x="0"   data-offset-y="40"></div>
+        <div class="item2 sticky alignSelfCenter" data-offset-x="75"  data-offset-y="140"></div>
+        <div class="item3 sticky alignSelfEnd"    data-offset-x="150" data-offset-y="290"></div>
+        <div class="item4"></div>
+    </div>
+    <div style="height:2000px"></div>
+</div>
+</body>
diff --git a/css/css-grid/alignment/grid-gutters-011.html b/css/css-grid/alignment/grid-gutters-011.html
new file mode 100644
index 0000000..daeba00
--- /dev/null
+++ b/css/css-grid/alignment/grid-gutters-011.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Support for calc mixing fixed and percentage values for gap</title>
+<link rel="help" href="https://www.w3.org/TR/css-grid-1/#gutters">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#gap-shorthand">
+<link rel="match" href="../reference/grid-different-gutters-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+    #grid {
+        display: grid;
+        width: 200px;
+        height: 220px;
+        gap: calc(15% + 7px) calc(10px + 5%);
+        grid-template-columns: 90px 90px;
+        grid-template-rows: 90px 90px;
+        background-color: green;
+    }
+
+    #grid > div {
+        background-color: silver;
+    }
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+<div id="grid">
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+</div>
diff --git a/css/css-grid/alignment/grid-gutters-012.html b/css/css-grid/alignment/grid-gutters-012.html
new file mode 100644
index 0000000..1e56e70
--- /dev/null
+++ b/css/css-grid/alignment/grid-gutters-012.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Support for calc mixing fixed and percentage values for grid-gap as alias for gap</title>
+<link rel="help" href="https://www.w3.org/TR/css-grid-1/#gutters">
+<link rel="help" href="https://www.w3.org/TR/css-align-3/#gap-shorthand">
+<link rel="help" href0"https://www.w3.org/TR/css-align-3/#gap-legacy">
+<link rel="match" href="../reference/grid-different-gutters-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+    #grid {
+        display: grid;
+        width: 200px;
+        height: 220px;
+        grid-gap: calc(15% + 7px) calc(10px + 5%);
+        grid-template-columns: 90px 90px;
+        grid-template-rows: 90px 90px;
+        background-color: green;
+    }
+
+    #grid > div {
+        background-color: silver;
+    }
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+<div id="grid">
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+</div>
diff --git a/css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-001.html b/css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-001.html
new file mode 100644
index 0000000..31d06b8
--- /dev/null
+++ b/css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-001.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Self-Alignment along row axis of stcky positioned items</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
+<link rel="stylesheet" href="../../support/alignment.css">
+<meta name="assert" content="Sticky positioned grid items are aligned correcly.">
+<style>
+.container {
+  border: solid 1px;
+  overflow: auto;
+  width: 500px;
+}
+.grid {
+  position: relative;
+  display: grid;
+  grid-template-columns: 100px 100px 100px 300px;
+  grid-template-rows: 75px 75px 75px 75px;
+  background: grey;
+  width: 400px;
+  margin-bottom: 20px;
+}
+.sticky {
+  position: -webkit-sticky;
+  position: sticky;
+  width: 20px;
+  height: 20px;
+  background-color: #cae8ca;
+}
+.item1 {
+  left: 0px;
+  grid-column: 1;
+  grid-row: 1;
+}
+.item2 {
+  left: 0px;
+  grid-column: 2;
+  grid-row: 2;
+}
+.item3 {
+  left: 0px;
+  grid-column: 3;
+  grid-row: 3;
+}
+.item4 {
+  grid-column: 4;
+  grid-row: 4;
+  background: lightgrey;
+}
+.scroll { overflow: auto; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div class="container">
+    <div style="width: 30px; float:left; height: 10px;"></div>
+    <div class="grid">
+        <div class="item1 sticky justifySelfStart"  data-offset-x="0"   data-offset-y="0"></div>
+        <div class="item2 sticky justifySelfCenter" data-offset-x="140" data-offset-y="75"></div>
+        <div class="item3 sticky justifySelfEnd"    data-offset-x="280" data-offset-y="150"></div>
+        <div class="item4"></div>
+    </div>
+    <div style="width: 30px; float:left; height: 10px;"></div>
+    <div class="grid scroll">
+        <div class="item1 sticky justifySelfStart"  data-offset-x="0"   data-offset-y="0"></div>
+        <div class="item2 sticky justifySelfCenter" data-offset-x="140" data-offset-y="75"></div>
+        <div class="item3 sticky justifySelfEnd"    data-offset-x="280" data-offset-y="150"></div>
+        <div class="item4"></div>
+    </div>
+    <div style="width: 2000px; height: 10px;"></div>
+</div>
+</body>
diff --git a/css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-002.html b/css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-002.html
new file mode 100644
index 0000000..cf14691
--- /dev/null
+++ b/css/css-grid/alignment/grid-row-axis-alignment-sticky-positioned-items-002.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Self-Alignment along row axis of stcky positioned items</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
+<link rel="stylesheet" href="../../support/alignment.css">
+<meta name="assert" content="Sticky positioned grid items are aligned correcly, but preserving non-static positions when required.">
+<style>
+.container {
+  border: solid 1px;
+  overflow: auto;
+  width: 500px;
+}
+.grid {
+  position: relative;
+  display: grid;
+  grid-template-columns: 100px 100px 100px 300px;
+  grid-template-rows: 75px 75px 75px 75px;
+  background: grey;
+  width: 400px;
+  margin-bottom: 20px;
+}
+.sticky {
+  position: -webkit-sticky;
+  position: sticky;
+  width: 20px;
+  height: 20px;
+  background-color: #cae8ca;
+}
+.item1 {
+  left: 40px;
+  grid-column: 1;
+  grid-row: 1;
+}
+.item2 {
+  left: 100px;
+  grid-column: 2;
+  grid-row: 2;
+}
+.item3 {
+  left: 290px;
+  grid-column: 3;
+  grid-row: 3;
+}
+.item4 {
+  grid-column: 4;
+  grid-row: 4;
+  background: lightgrey;
+}
+.scroll { overflow: auto; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+<div class="container">
+    <div style="width: 30px; float:left; height: 10px;"></div>
+    <div class="grid">
+        <div class="item1 sticky justifySelfStart"  data-offset-x="10"  data-offset-y="0"></div>
+        <div class="item2 sticky justifySelfCenter" data-offset-x="140" data-offset-y="75"></div>
+        <div class="item3 sticky justifySelfEnd"    data-offset-x="280" data-offset-y="150"></div>
+        <div class="item4"></div>
+    </div>
+    <div style="width: 30px; float:left; height: 10px;"></div>
+    <div class="grid scroll">
+        <div class="item1 sticky justifySelfStart"  data-offset-x="40"  data-offset-y="0"></div>
+        <div class="item2 sticky justifySelfCenter" data-offset-x="140" data-offset-y="75"></div>
+        <div class="item3 sticky justifySelfEnd"    data-offset-x="290" data-offset-y="150"></div>
+        <div class="item4"></div>
+    </div>
+    <div style="width: 2000px; height: 10px;"></div>
+</div>
+</body>
diff --git a/css/css-grid/alignment/support/100x100-green.png b/css/css-grid/alignment/support/100x100-green.png
new file mode 100644
index 0000000..25b76c3
--- /dev/null
+++ b/css/css-grid/alignment/support/100x100-green.png
Binary files differ
diff --git a/css/css-grid/alignment/support/style-change.js b/css/css-grid/alignment/support/style-change.js
new file mode 100644
index 0000000..5619394
--- /dev/null
+++ b/css/css-grid/alignment/support/style-change.js
@@ -0,0 +1,5 @@
+function evaluateStyleChange(element, phase, expectedProperty, expectedResult) {
+    element.className += " " + phase;
+    element.setAttribute(expectedProperty, expectedResult);
+    checkLayout("." + phase, false);
+}
diff --git a/css/css-grid/grid-definition/grid-change-fit-content-argument-001.html b/css/css-grid/grid-definition/grid-change-fit-content-argument-001.html
index d08712d..9441b0a 100644
--- a/css/css-grid/grid-definition/grid-change-fit-content-argument-001.html
+++ b/css/css-grid/grid-definition/grid-change-fit-content-argument-001.html
@@ -6,7 +6,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-1/#track-sizing">
 <meta name="assert" content="This test checks that grid-template-{rows|columns} with fit-content() tracks recomputes the tracks when the fit-content() argument is modified.">
 <meta name="flags" content="ahem">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 .grid {
   width: fit-content;
diff --git a/css/css-grid/grid-definition/grid-inline-auto-repeat-001.html b/css/css-grid/grid-definition/grid-inline-auto-repeat-001.html
new file mode 100644
index 0000000..b06ffe5
--- /dev/null
+++ b/css/css-grid/grid-definition/grid-inline-auto-repeat-001.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang=en>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: auto repeat tracks in indefinite containers with minimum size</title>
+<link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#repeat-syntax">
+<meta name="assert" content="This test checks that we properly compute the number of required auto repeat tracks in indefinite sized containers with minimum sizes.">
+<link rel="stylesheet" href="support/grid.css">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/testing-utils.js"></script>
+<style>
+.grid {
+    display: inline-grid;
+    grid: 20px / 20px;
+    min-width: 20px;
+    min-height: 20px;
+    justify-content: start;
+    align-content: start;
+}
+</style>
+<div id="log"></div>
+
+<div id="autoFillColumns" class="grid"></div>
+<div id="autoFitColumns" class="grid"></div>
+
+<div id="autoFillRows" class="grid"></div>
+<div id="autoFitRows" class="grid"></div>
+
+<script>
+// Exact fit
+TestingUtils.testGridTemplateColumnsRows("autoFillColumns", "repeat(auto-fill, 10px)", "20px", ["repeat(2, 10px)", "10px 10px"], "20px");
+TestingUtils.testGridTemplateColumnsRows("autoFitColumns", "repeat(auto-fit, 10px)", "20px", ["repeat(2, 0px)", "0px 0px"], "20px");
+TestingUtils.testGridTemplateColumnsRows("autoFillRows", "20px", "repeat(auto-fill, 10px)", "20px", ["repeat(2, 10px)", "10px 10px"]);
+TestingUtils.testGridTemplateColumnsRows("autoFitRows", "20px", "repeat(auto-fit, 10px)", "20px", ["repeat(2, 0px)", "0px 0px"]);
+
+// Require an extra track
+TestingUtils.testGridTemplateColumnsRows("autoFillColumns", "repeat(auto-fill, 9px)", "20px", ["repeat(3, 9px)", "9px 9px 9px"], "20px");
+TestingUtils.testGridTemplateColumnsRows("autoFitColumns", "repeat(auto-fit, 9px)", "20px", ["repeat(2, 0px)", "0px 0px"], "20px");
+TestingUtils.testGridTemplateColumnsRows("autoFillRows", "20px", "repeat(auto-fill, 9px)", "20px", ["repeat(3, 9px)", "9px 9px 9px"]);
+TestingUtils.testGridTemplateColumnsRows("autoFitRows", "20px", "repeat(auto-fit, 9px)", "20px", ["repeat(3, 0px)", "0px 0px 0px"]);
+
+// A single repetition is enough to fill in all the available space
+TestingUtils.testGridTemplateColumnsRows("autoFillColumns", "repeat(auto-fill, 30px)", "20px", "30px", "20px");
+TestingUtils.testGridTemplateColumnsRows("autoFitColumns", "repeat(auto-fit, 30px)", "20px", "0px", "20px");
+TestingUtils.testGridTemplateColumnsRows("autoFillRows", "20px", "repeat(auto-fill, 30px)", "20px", "30px");
+TestingUtils.testGridTemplateColumnsRows("autoFitRows", "20px", "repeat(auto-fit, 30px)", "20px", "0px");
+</script>
diff --git a/css/css-grid/grid-definition/grid-template-columns-fit-content-001-ref.html b/css/css-grid/grid-definition/grid-template-columns-fit-content-001-ref.html
index cb7a582..f90bd04 100644
--- a/css/css-grid/grid-definition/grid-template-columns-fit-content-001-ref.html
+++ b/css/css-grid/grid-definition/grid-template-columns-fit-content-001-ref.html
@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <title>CSS Grid Layout Test: grid-template-columns fit-content() reference file</title>
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <meta name="flags" content="ahem">
 <style>
 .grid {
diff --git a/css/css-grid/grid-definition/grid-template-columns-fit-content-001.html b/css/css-grid/grid-definition/grid-template-columns-fit-content-001.html
index d492d45..c594d80 100644
--- a/css/css-grid/grid-definition/grid-template-columns-fit-content-001.html
+++ b/css/css-grid/grid-definition/grid-template-columns-fit-content-001.html
@@ -7,7 +7,7 @@
 <link rel="match" href="grid-template-columns-fit-content-001-ref.html">
 <meta name="assert" content="This test checks that 'fit-content()' works as expected in grid-template-columns, i.e., it's similar to 'auto' ('minmax(auto, max-content)') except that the growth limit is clamped at the argument of 'fit-content' (if greater than the 'auto' minimum).">
 <meta name="flags" content="ahem">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 .grid {
   justify-content: start;
diff --git a/css/css-grid/grid-definition/grid-template-rows-fit-content-001-ref.html b/css/css-grid/grid-definition/grid-template-rows-fit-content-001-ref.html
index a1f83dc..a6785fc 100644
--- a/css/css-grid/grid-definition/grid-template-rows-fit-content-001-ref.html
+++ b/css/css-grid/grid-definition/grid-template-rows-fit-content-001-ref.html
@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <title>CSS Grid Layout Test: grid-template-rows fit-content() reference file</title>
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <meta name="flags" content="ahem">
 <style>
 .grid {
diff --git a/css/css-grid/grid-definition/grid-template-rows-fit-content-001.html b/css/css-grid/grid-definition/grid-template-rows-fit-content-001.html
index 6d406c3..2931960 100644
--- a/css/css-grid/grid-definition/grid-template-rows-fit-content-001.html
+++ b/css/css-grid/grid-definition/grid-template-rows-fit-content-001.html
@@ -7,7 +7,7 @@
 <link rel="match" href="grid-template-rows-fit-content-001-ref.html">
 <meta name="assert" content="This test checks that 'fit-content()' works as expected in grid-template-rows, i.e., it's similar to 'auto' ('minmax(auto, max-content)') except that the growth limit is clamped at the argument of 'fit-content' (if greater than the 'auto' minimum).">
 <meta name="flags" content="ahem">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 .grid {
   justify-content: start;
diff --git a/css/css-grid/support/grid.css b/css/css-grid/grid-definition/support/grid.css
similarity index 100%
copy from css/css-grid/support/grid.css
copy to css/css-grid/grid-definition/support/grid.css
diff --git a/css/css-grid/grid-definition/support/testing-utils.js b/css/css-grid/grid-definition/support/testing-utils.js
index 202c865..217b620 100644
--- a/css/css-grid/grid-definition/support/testing-utils.js
+++ b/css/css-grid/grid-definition/support/testing-utils.js
@@ -1,10 +1,14 @@
 var TestingUtils = (function() {
 
     function checkGridTemplateColumns(element, value) {
+        if (!Array.isArray(value))
+            value = new Array(value);
         assert_in_array(getComputedStyle(element).gridTemplateColumns, value, "gridTemplateColumns");
     }
 
     function checkGridTemplateRows(element, value) {
+        if (!Array.isArray(value))
+            value = new Array(value);
         assert_in_array(getComputedStyle(element).gridTemplateRows, value, "gridTemplateRows");
     }
 
@@ -19,6 +23,8 @@
     }
 
     function checkGridTemplateAreas(element, value) {
+        if (!Array.isArray(value))
+            value = new Array(value);
         assert_in_array(getComputedStyle(element).gridTemplateAreas, value, "gridTemplateAreas");
     }
 
diff --git a/css/css-grid/grid-items/anonymous-grid-item-001.html b/css/css-grid/grid-items/anonymous-grid-item-001.html
new file mode 100644
index 0000000..db88ce5
--- /dev/null
+++ b/css/css-grid/grid-items/anonymous-grid-item-001.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Test: Anonymous grid items - non-contiguous text runs - position:absolute</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#grid-items">
+<link rel="match" href="/css/css-flexbox/anonymous-flex-item-split-ref.html">
+<p>The words "Two" and "lines" should not be on the same line.</p>
+<div style="display:grid">Two <span style="position:absolute"></span>lines</div>
diff --git a/css/css-grid/grid-items/explicitly-sized-grid-item-as-table.html b/css/css-grid/grid-items/explicitly-sized-grid-item-as-table.html
new file mode 100644
index 0000000..53a0979
--- /dev/null
+++ b/css/css-grid/grid-items/explicitly-sized-grid-item-as-table.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>CSS Grid Layout Test: Explicitly sized grid item as table with narrow contents</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-grid-1/#grid-track-concept" title="3.2. Grid Tracks and Cells">
+<meta name="assert" content="A grid item as a table uses the sizing algorithm of the grid">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="display:grid; grid-template-columns:50% 50%; width:200px;">
+  <div style="display:table; background:green;">
+    <div style="width:10px; height:100px;"></div>
+  </div>
+</div>
diff --git a/css/css-grid/grid-items/grid-item-min-auto-size-001.html b/css/css-grid/grid-items/grid-item-min-auto-size-001.html
new file mode 100644
index 0000000..f50e9ef
--- /dev/null
+++ b/css/css-grid/grid-items/grid-item-min-auto-size-001.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>CSS Grid Test: computed style for auto minimum size</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org" />
+<link rel="help" href="https://drafts.csswg.org/css-grid/#min-size-auto" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .grid { display: grid }
+  .none { display: none }
+  .min-wh {
+    min-width: auto;
+    min-height: auto;
+  }
+  .contents { display: contents }
+</style>
+<div class="grid">
+  <div class="min-wh"></div>
+</div>
+<div class="none">
+  <div class="grid">
+    <div class="min-wh"></div>
+  </div>
+</div>
+<div class="grid">
+  <div class="contents">
+    <div class="min-wh"></div>
+  </div>
+</div>
+<div class="grid">
+  <div class="min-wh none"></div>
+</div>
+<script>
+  const tests = [
+    { description: "Computed min-width/min-height of specified auto for grid item.", computed: "auto" },
+    { description: "Computed min-width/min-height of specified auto inside display:none which would otherwise have been a grid item.", computed: "0px" },
+    { description: "Computed min-width/min-height of specified auto for grid item inside display:contents.", computed: "auto" },
+    { description: "Computed min-width/min-height of specified auto with display:none which would otherwise have been a grid item.", computed: "0px" }
+  ];
+
+  const testElements = document.querySelectorAll(".min-wh");
+  let testNo = 0;
+  for (let testElement of testElements) {
+    test(() => {
+      assert_equals(getComputedStyle(testElement).minWidth, tests[testNo].computed);
+      assert_equals(getComputedStyle(testElement).minHeight, tests[testNo].computed);
+    }, tests[testNo].description);
+    testNo++;
+  }
+</script>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-001.html b/css/css-grid/grid-items/grid-items-minimum-width-001.html
new file mode 100644
index 0000000..00487f7
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-001.html
@@ -0,0 +1,349 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that grid items minimum width takes into account borders, padding and margins for grid containers with definite width.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  width: 100px;
+  border: solid thick;
+  grid: 10px 10px / minmax(auto, 0px);
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-002.html b/css/css-grid/grid-items/grid-items-minimum-width-002.html
new file mode 100644
index 0000000..c9f77a6
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-002.html
@@ -0,0 +1,349 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that grid items minimum width takes into account borders, padding and margins for grid containers with indefinite width.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  display: inline-grid;
+  border: solid 5px;
+  grid: 10px 10px / minmax(auto, 0px);
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-orthogonal-001.html b/css/css-grid/grid-items/grid-items-minimum-width-orthogonal-001.html
new file mode 100644
index 0000000..943837d
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-orthogonal-001.html
@@ -0,0 +1,349 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items orthogonal</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that orthogonal grid items minimum width takes into account borders, padding and margins for grid containers with definite width.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  width: 100px;
+  border: solid thick;
+  grid: 10px 10px / minmax(auto, 0px);
+}
+
+.grid > div:nth-child(1) { background: cyan; writing-mode: vertical-lr; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-orthogonal-002.html b/css/css-grid/grid-items/grid-items-minimum-width-orthogonal-002.html
new file mode 100644
index 0000000..25ad710
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-orthogonal-002.html
@@ -0,0 +1,349 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items orthogonal</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that orthogonal grid items minimum width takes into account borders, padding and margins for grid containers with indefinite width.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  display: inline-grid;
+  border: solid 5px;
+  grid: 10px 10px / minmax(auto, 0px);
+}
+
+.grid > div:nth-child(1) { background: cyan; writing-mode: vertical-lr; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-001.html b/css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-001.html
new file mode 100644
index 0000000..1afe6df
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-001.html
@@ -0,0 +1,350 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items vertical-lr</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that grid items minimum width takes into account borders, padding and margins for grid containers with definite width in vertical-lr.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  width: 100px;
+  border: solid thick;
+  grid: minmax(auto, 0px) / 10px 10px;
+  writing-mode: vertical-lr;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-002.html b/css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-002.html
new file mode 100644
index 0000000..a239369
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-vertical-lr-002.html
@@ -0,0 +1,350 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items vertical-lr</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that grid items minimum width takes into account borders, padding and margins for grid containers with indefinite width in vertical-lr.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  display: inline-grid;
+  border: solid 5px;
+  grid: minmax(auto, 0px) / 10px 10px;
+  writing-mode: vertical-lr;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-001.html b/css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-001.html
new file mode 100644
index 0000000..df3fe9b
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-001.html
@@ -0,0 +1,350 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items vertical-rl</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that grid items minimum width takes into account borders, padding and margins for grid containers with definite width in vertical-rl.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  width: 100px;
+  border: solid thick;
+  grid: minmax(auto, 0px) / 10px 10px;
+  writing-mode: vertical-rl;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-002.html b/css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-002.html
new file mode 100644
index 0000000..3978ed6
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-minimum-width-vertical-rl-002.html
@@ -0,0 +1,350 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Mininum width of grid items vertical-rl</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<meta name="assert" content="Checks that grid items minimum width takes into account borders, padding and margins for grid containers with indefinite width in vertical-rl.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  display: inline-grid;
+  border: solid 5px;
+  grid: minmax(auto, 0px) / 10px 10px;
+  writing-mode: vertical-rl;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) { background: magenta; }
+
+.width60 { width: 60px; }
+.minWidth60 { min-width: 60px; }
+
+.marginLeft5 { margin-left: 5px; }
+.marginRight10 { margin-right: 10px; }
+
+.paddingLeft6 { padding-left: 6px; }
+.paddingRight3 { padding-right: 3px; }
+
+.borderLeft2, .borderRight4 { border: solid yellow 0px; }
+.borderLeft2 { border-left-width: 2px; }
+.borderRight4 { border-right-width: 4px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="width60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item min-width: 60px;</pre>
+
+<div class="grid directionRTL" data-expected-width="70">
+  <div class="minWidth60" data-expected-width="60"></div>
+  <div data-expected-width="60"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="width60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px;</pre>
+
+<div class="grid directionRTL" data-expected-width="75">
+  <div class="minWidth60 marginLeft5" data-expected-width="60"></div>
+  <div data-expected-width="65"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="width60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="80">
+  <div class="minWidth60 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="70"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="width60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px;</pre>
+
+<div class="grid directionRTL" data-expected-width="85">
+  <div class="minWidth60 marginLeft5 marginRight10" data-expected-width="60"></div>
+  <div data-expected-width="75"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 paddingLeft6" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="width60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="73">
+  <div class="minWidth60 paddingRight3" data-expected-width="63"></div>
+  <div data-expected-width="63"></div>
+</div>
+
+<pre>Item width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="width60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; padding-left: 6px; &amp; padding-right: 3px;</pre>
+
+<div class="grid directionRTL" data-expected-width="79">
+  <div class="minWidth60 paddingLeft6 paddingRight3" data-expected-width="69"></div>
+  <div data-expected-width="69"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="width60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px;</pre>
+
+<div class="grid directionRTL" data-expected-width="72">
+  <div class="minWidth60 borderLeft2" data-expected-width="62"></div>
+  <div data-expected-width="62"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="width60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="74">
+  <div class="minWidth60 borderRight4" data-expected-width="64"></div>
+  <div data-expected-width="64"></div>
+</div>
+
+<pre>Item width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="width60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="76">
+  <div class="minWidth60 borderLeft2 borderRight4" data-expected-width="66"></div>
+  <div data-expected-width="66"></div>
+</div>
+
+<pre>Item width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="width60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
+
+<pre>Item min-width: 60px; &amp; margin-left: 5px; &amp; margin-right: 10px; &amp; padding-left: 6px; &amp; padding-right: 3px; &amp; border-left-width: 2px; &amp; border-right-width: 4px;</pre>
+
+<div class="grid directionRTL" data-expected-width="100">
+  <div class="minWidth60 marginLeft5 marginRight10 paddingLeft6 paddingRight3 borderLeft2 borderRight4" data-expected-width="75"></div>
+  <div data-expected-width="90"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-margins-001.html b/css/css-grid/grid-items/grid-items-percentage-margins-001.html
new file mode 100644
index 0000000..1184409
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-margins-001.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage margins</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage margins are resolved against the inline size of their grid area (in a fixed size track).">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: 100px;
+  width: 500px;
+  justify-items: start;
+  position: relative;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 100%;
+  height: 10px;
+}
+
+.marginLeft50Percent { margin-left: 50%; }
+.marginRight50Percent { margin-right: 50%; }
+.marginTop50Percent { margin-top: 50%; }
+.marginBottom50Percent { margin-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="490" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="440" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-margins-002.html b/css/css-grid/grid-items/grid-items-percentage-margins-002.html
new file mode 100644
index 0000000..b056d3e
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-margins-002.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage margins</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage margins are resolved against the inline size of their grid area (in a track with fixed max sizing function and intrinsic min).">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: minmax(auto, 100px);
+  width: 500px;
+  justify-items: start;
+  position: relative;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 100%;
+  height: 10px;
+}
+
+.marginLeft50Percent { margin-left: 50%; }
+.marginRight50Percent { margin-right: 50%; }
+.marginTop50Percent { margin-top: 50%; }
+.marginBottom50Percent { margin-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="490" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="440" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-001.html b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-001.html
new file mode 100644
index 0000000..82df77f
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-001.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage margins vertical-lr</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage margins are resolved against the inline size of their grid area (in a fixed size track) in a vertical-lr grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: 100px;
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-lr;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.marginLeft50Percent { margin-left: 50%; }
+.marginRight50Percent { margin-right: 50%; }
+.marginTop50Percent { margin-top: 50%; }
+.marginBottom50Percent { margin-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="490" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="440" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-002.html b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-002.html
new file mode 100644
index 0000000..168eeaf
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-lr-002.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage margins vertical-lr</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage margins are resolved against the inline size of their grid area (in a track with fixed max sizing function and intrinsic min) in a vertical-lr grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: minmax(auto, 100px);
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-lr;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.marginLeft50Percent { margin-left: 50%; }
+.marginRight50Percent { margin-right: 50%; }
+.marginTop50Percent { margin-top: 50%; }
+.marginBottom50Percent { margin-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="490" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="440" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-001.html b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-001.html
new file mode 100644
index 0000000..415a07d
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-001.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage margins vertical-rl</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage margins are resolved against the inline size of their grid area (in a fixed size track) in a vertical-rl grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: 100px;
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-rl;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.marginLeft50Percent { margin-left: 50%; }
+.marginRight50Percent { margin-right: 50%; }
+.marginTop50Percent { margin-top: 50%; }
+.marginBottom50Percent { margin-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="60" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="10" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-expected-x="60" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-expected-x="10" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-expected-y="490" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-expected-y="440" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-002.html b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-002.html
new file mode 100644
index 0000000..6f3db26
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-margins-vertical-rl-002.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage margins vertical-rl</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage margins are resolved against the inline size of their grid area (in a track with fixed max sizing function and intrinsic min) in a vertical-rl grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: minmax(auto, 100px);
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-rl;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.marginLeft50Percent { margin-left: 50%; }
+.marginRight50Percent { margin-right: 50%; }
+.marginTop50Percent { margin-top: 50%; }
+.marginBottom50Percent { margin-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-offset-x="60" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-offset-x="10" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-offset-y="50" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-offset-y="0" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item margin-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginLeft50Percent" data-expected-margin-left="50" data-expected-x="60" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginRight50Percent" data-expected-margin-right="50" data-expected-x="10" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginTop50Percent" data-expected-margin-top="50" data-expected-y="490" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item margin-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="marginBottom50Percent" data-expected-margin-bottom="50" data-expected-y="440" data-expected-width="10" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-paddings-001.html b/css/css-grid/grid-items/grid-items-percentage-paddings-001.html
new file mode 100644
index 0000000..3989d8f
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-paddings-001.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage paddings</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage paddings are resolved against the inline size of their grid area (in a fixed size track).">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: 100px;
+  width: 500px;
+  justify-items: start;
+  position: relative;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 100%;
+  height: 10px;
+}
+
+.paddingLeft50Percent { padding-left: 50%; }
+.paddingRight50Percent { padding-right: 50%; }
+.paddingTop50Percent { padding-top: 50%; }
+.paddingBottom50Percent { padding-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-paddings-002.html b/css/css-grid/grid-items/grid-items-percentage-paddings-002.html
new file mode 100644
index 0000000..a358b38
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-paddings-002.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage paddings</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage paddings are resolved against the inline size of their grid area (in a track with fixed max sizing function and intrinsic min).">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: minmax(auto, 100px);
+  width: 500px;
+  justify-items: start;
+  position: relative;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 100%;
+  height: 10px;
+}
+
+.paddingLeft50Percent { padding-left: 50%; }
+.paddingRight50Percent { padding-right: 50%; }
+.paddingTop50Percent { padding-top: 50%; }
+.paddingBottom50Percent { padding-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="400" data-offset-y="10" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="400" data-offset-y="60" data-expected-width="100" data-expected-height="10"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-001.html b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-001.html
new file mode 100644
index 0000000..21ed53a
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-001.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage paddings vertical-lr</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage paddings are resolved against the inline size of their grid area (in a fixed size track) in a vertical-lr grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: 100px;
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-lr;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.paddingLeft50Percent { padding-left: 50%; }
+.paddingRight50Percent { padding-right: 50%; }
+.paddingTop50Percent { padding-top: 50%; }
+.paddingBottom50Percent { padding-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-002.html b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-002.html
new file mode 100644
index 0000000..d4d8aa2
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-lr-002.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage paddings vertical-lr</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage paddings are resolved against the inline size of their grid area (in a track with fixed max sizing function and intrinsic min) in a vertical-lr grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: minmax(auto, 100px);
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-lr;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.paddingLeft50Percent { padding-left: 50%; }
+.paddingRight50Percent { padding-right: 50%; }
+.paddingTop50Percent { padding-top: 50%; }
+.paddingBottom50Percent { padding-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="60" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="10" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-001.html b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-001.html
new file mode 100644
index 0000000..010f4b5
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-001.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage paddings vertical-rl</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage paddings are resolved against the inline size of their grid area (in a fixed size track) in a vertical-rl grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: 100px;
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-rl;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.paddingLeft50Percent { padding-left: 50%; }
+.paddingRight50Percent { padding-right: 50%; }
+.paddingTop50Percent { padding-top: 50%; }
+.paddingBottom50Percent { padding-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-002.html b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-002.html
new file mode 100644
index 0000000..bf38155
--- /dev/null
+++ b/css/css-grid/grid-items/grid-items-percentage-paddings-vertical-rl-002.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid items with percentage paddings vertical-rl</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#item-margins">
+<meta name="assert" content="Checks grid items percentage paddings are resolved against the inline size of their grid area (in a track with fixed max sizing function and intrinsic min) in a vertical-rl grid container.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  font: 10px/1 Ahem;
+  grid-template-columns: minmax(auto, 100px);
+  height: 500px;
+  justify-items: start;
+  position: relative;
+  writing-mode: vertical-rl;
+}
+
+.grid > div:nth-child(1) { background: cyan; }
+.grid > div:nth-child(2) {
+  background: magenta;
+  width: 10px;
+  height: 100%;
+}
+
+.paddingLeft50Percent { padding-left: 50%; }
+.paddingRight50Percent { padding-right: 50%; }
+.paddingTop50Percent { padding-top: 50%; }
+.paddingBottom50Percent { padding-bottom: 50%; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>Direction LTR</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<h3>Direction RTL</h3>
+
+<pre>Item padding-left: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingLeft50Percent" data-expected-padding-left="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-right: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingRight50Percent" data-expected-padding-right="50" data-expected-width="60" data-expected-height="10">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-top: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingTop50Percent" data-expected-padding-top="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
+
+<pre>Item padding-bottom: 50%;</pre>
+
+<div class="grid directionRTL">
+  <div class="paddingBottom50Percent" data-expected-padding-bottom="50" data-expected-width="10" data-expected-height="60">X</div>
+  <div data-offset-x="0" data-offset-y="400" data-expected-width="10" data-expected-height="100"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html
index 66417a4..ce1db93 100644
--- a/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="http://www.w3.org/TR/css-grid-1/#min-size-auto" title="6.5. Implied Minimum Size of Grid Items">
 <meta name="assert" content="Checks that automatic minimum size is clamped with different column sizes.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 .grid {
   border: solid thick;
@@ -135,3 +135,217 @@
   <div data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
   <div data-expected-width="25"></div>
 </div>
+
+<h3>grid content writing-mode: vertical-lr;</h3>
+
+<pre>grid-template-columns: auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 25px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 25px;">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="15" style="margin: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="padding: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="border: solid 5px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<h3>grid content writing-mode: vertical-rl;</h3>
+
+<pre>grid-template-columns: auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 25px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 25px;">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="15" style="margin: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="padding: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="border: solid 5px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html
index d821e5c..c5e6da5 100644
--- a/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html
@@ -4,7 +4,7 @@
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="http://www.w3.org/TR/css-grid-1/#min-size-auto" title="6.5. Implied Minimum Size of Grid Items">
 <meta name="assert" content="Checks that automatic minimum size is clamped with different row sizes.">
-<link rel="stylesheet" href="../support/grid.css">
+<link rel="stylesheet" href="support/grid.css">
 <style>
 .grid {
   border: solid thick;
@@ -32,7 +32,7 @@
 
 <div id="log"></div>
 
-<h3>writing-mode: vertical-lr;</h3>
+<h3>item writing-mode: vertical-lr;</h3>
 
 <pre>grid-template-rows: auto;</pre>
 
@@ -139,7 +139,7 @@
   <div data-expected-height="25"></div>
 </div>
 
-<h3>writing-mode: vertical-rl;</h3>
+<h3>item writing-mode: vertical-rl;</h3>
 
 <pre>grid-template-rows: auto;</pre>
 
@@ -246,3 +246,216 @@
   <div data-expected-height="25"></div>
 </div>
 
+<h3>grid container writing-mode: vertical-lr; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 25px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: 25px;">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="15" style="margin: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="padding: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="border: solid 5px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<h3>grid container writing-mode: vertical-rl; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 25px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: 25px;">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="15" style="margin: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="padding: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="border: solid 5px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-024.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-024.html
new file mode 100644
index 0000000..c77556c
--- /dev/null
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-024.html
@@ -0,0 +1,355 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Minimum size of grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#min-size-auto" title="6.5. Implied Minimum Size of Grid Items">
+<meta name="assert" content="Checks that automatic minimum size is clamped with different column sizes and spaning items.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  border: solid thick;
+  font: 10px/1 Ahem;
+  width: 50px;
+  height: 50px;
+  grid-template-rows: 25px 25px;
+}
+
+.grid > div {
+  grid-column: span 2;
+}
+
+.grid > div:nth-child(1) {
+  color: blue;
+  background: cyan;
+}
+
+.grid > div:nth-child(2) {
+  background: magenta;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<pre>grid-template-columns: auto auto;</pre>
+
+<div class="grid" style="grid-template-columns: auto auto;">
+  <div data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px 0px;</pre>
+
+<div class="grid" style="grid-template-columns: 0px 0px;">
+  <div data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-columns: 20px 20px;</pre>
+
+<div class="grid" style="grid-template-columns: 20px 20px;">
+  <div data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item margin width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="30" style="margin: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item padding width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="40" style="padding: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item border width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="40" style="border: solid 5px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<h3>grid content writing-mode: vertical-lr;</h3>
+
+<pre>grid-template-columns: auto auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: auto auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px 0px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 0px 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 20px 20px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 20px 20px;">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<h3>grid content writing-mode: vertical-rl;</h3>
+
+<pre>grid-template-columns: auto auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: auto auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px 0px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 0px 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 20px 20px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 20px 20px;">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-025.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-025.html
new file mode 100644
index 0000000..a3c35a5
--- /dev/null
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-025.html
@@ -0,0 +1,465 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Minimum size of grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#min-size-auto" title="6.5. Implied Minimum Size of Grid Items">
+<meta name="assert" content="Checks that automatic minimum size is clamped with different row sizes and spaning items.">
+<link rel="stylesheet" href="support/grid.css">
+<style>
+.grid {
+  border: solid thick;
+  font: 10px/1 Ahem;
+  width: 50px;
+  height: 50px;
+  grid-template-columns: 25px 25px;
+  margin: 50px 0px;
+}
+
+.grid > div {
+  grid-row: span 2;
+}
+
+.grid > div:nth-child(1) {
+  color: blue;
+  background: cyan;
+}
+
+.grid > div:nth-child(2) {
+  background: magenta;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>item writing-mode: vertical-lr;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid" style="grid-template-rows: auto auto;">
+  <div class="verticalLR" data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid" style="grid-template-rows: 0px 0px;">
+  <div class="verticalLR" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid" style="grid-template-rows: 20px 20px;">
+  <div class="verticalLR" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<h3>item writing-mode: vertical-rl;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid" style="grid-template-rows: auto auto;">
+  <div class="verticalRL" data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid" style="grid-template-rows: 0px 0px;">
+  <div class="verticalRL" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid" style="grid-template-rows: 20px 20px;">
+  <div class="verticalRL" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<h3>grid container writing-mode: vertical-lr; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: auto auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: 0px 0px;">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: 20px 20px;">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="30" style="margin: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="padding: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="border: solid 5px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<h3>grid container writing-mode: vertical-rl; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: auto auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: 0px 0px;">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: 20px 20px;">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="30" style="margin: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="padding: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="border: solid 5px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
diff --git a/css/css-grid/grid-items/percentage-size-replaced-subitems-001-ref.html b/css/css-grid/grid-items/percentage-size-replaced-subitems-001-ref.html
new file mode 100644
index 0000000..2c21c8c
--- /dev/null
+++ b/css/css-grid/grid-items/percentage-size-replaced-subitems-001-ref.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Refttest Reference: Percentage size on child of a grid item with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.grid {
+  display: inline-block;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+  margin: 10px;
+  vertical-align: top;
+}
+
+.item {
+  overflow: scroll;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  background: cyan;
+  width: calc(100% - 6px);
+  height: calc(100% - 4px);
+  box-sizing: border-box;
+}
+
+img {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.horizontalTB { writing-mode: horizontal-tb; }
+.verticalLR { writing-mode: vertical-lr; }
+.verticalRL {  writing-mode: vertical-rl; }
+</style>
+
+<p>The test passes if in the different examples you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <img src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <img class="verticalLR" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <img class="verticalRL" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <img src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <img class="horizontalTB" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <img class="verticalRL" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <img src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <img class="horizontalTB" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <img class="verticalRL" src="support/100x100-green.png" />
+  </div>
+</div>
diff --git a/css/css-grid/grid-items/percentage-size-replaced-subitems-001.html b/css/css-grid/grid-items/percentage-size-replaced-subitems-001.html
new file mode 100644
index 0000000..190277a
--- /dev/null
+++ b/css/css-grid/grid-items/percentage-size-replaced-subitems-001.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Test: Percentage size on replaced child of a grid item with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<link rel="match" href="percentage-size-replaced-subitems-001-ref.html">
+<meta name="assert" content="Checks that grid items replaced children resolve properly their percentage sizes, even when the grid item has margin, border, padding and scrollbar.">
+<style>
+.grid {
+  display: inline-grid;
+  border: solid 5px black;
+  grid: 100px / 150px;
+  margin: 10px;
+  vertical-align: top;
+}
+
+.item {
+  overflow: scroll;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  background: cyan;
+}
+
+img {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.horizontalTB { writing-mode: horizontal-tb; }
+.verticalLR { writing-mode: vertical-lr; }
+.verticalRL {  writing-mode: vertical-rl; }
+</style>
+
+<p>The test passes if in the different examples you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <img src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <img class="verticalLR" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <img class="verticalRL" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <img src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <img class="horizontalTB" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <img class="verticalRL" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <img src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <img class="horizontalTB" src="support/100x100-green.png" />
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <img class="verticalRL" src="support/100x100-green.png" />
+  </div>
+</div>
diff --git a/css/css-grid/grid-items/percentage-size-subitems-001-ref.html b/css/css-grid/grid-items/percentage-size-subitems-001-ref.html
new file mode 100644
index 0000000..939504e
--- /dev/null
+++ b/css/css-grid/grid-items/percentage-size-subitems-001-ref.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Refttest Reference: Percentage size on child of a grid item with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.grid {
+  display: inline-block;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+  margin: 10px;
+  vertical-align: top;
+}
+
+.item {
+  overflow: scroll;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  background: cyan;
+  width: calc(100% - 6px);
+  height: calc(100% - 4px);
+  box-sizing: border-box;
+}
+
+.subitem {
+  width: 100%;
+  height: 100%;
+  background: yellow;
+  font: 20px/1 Ahem;
+}
+
+.horizontalTB { writing-mode: horizontal-tb; }
+.verticalLR { writing-mode: vertical-lr; }
+.verticalRL {  writing-mode: vertical-rl; }
+</style>
+
+<p>The test passes if in the different examples you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
diff --git a/css/css-grid/grid-items/percentage-size-subitems-001.html b/css/css-grid/grid-items/percentage-size-subitems-001.html
new file mode 100644
index 0000000..ad00990
--- /dev/null
+++ b/css/css-grid/grid-items/percentage-size-subitems-001.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Test: Percentage size on child of a grid item with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-items">
+<link rel="match" href="percentage-size-subitems-001-ref.html">
+<meta name="assert" content="Checks that grid items children resolve properly their percentage sizes, even when the grid item has margin, border, padding and scrollbar.">
+<style>
+.grid {
+  display: inline-grid;
+  border: solid 5px black;
+  grid: 100px / 150px;
+  margin: 10px;
+  vertical-align: top;
+}
+
+.item {
+  overflow: scroll;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  background: cyan;
+}
+
+.subitem {
+  width: 100%;
+  height: 100%;
+  background: yellow;
+}
+
+.horizontalTB { writing-mode: horizontal-tb; }
+.verticalLR { writing-mode: vertical-lr; }
+.verticalRL {  writing-mode: vertical-rl; }
+</style>
+
+<p>The test passes if in the different examples you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item horizontalTB">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalLR">
+    <div class="subitem verticalRL"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <div class="subitem"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <div class="subitem horizontalTB"></div>
+  </div>
+</div>
+
+<div class="grid">
+  <div class="item verticalRL">
+    <div class="subitem verticalLR"></div>
+  </div>
+</div>
diff --git a/css/css-grid/grid-items/support/100x100-green.png b/css/css-grid/grid-items/support/100x100-green.png
index 0dcda08..25b76c3 100644
--- a/css/css-grid/grid-items/support/100x100-green.png
+++ b/css/css-grid/grid-items/support/100x100-green.png
Binary files differ
diff --git a/css/css-grid/support/grid.css b/css/css-grid/grid-items/support/grid.css
similarity index 100%
copy from css/css-grid/support/grid.css
copy to css/css-grid/grid-items/support/grid.css
diff --git a/css/css-grid/grid-layout-properties.html b/css/css-grid/grid-layout-properties.html
index 2e66fb2..249c4a7 100644
--- a/css/css-grid/grid-layout-properties.html
+++ b/css/css-grid/grid-layout-properties.html
@@ -54,7 +54,7 @@
 
       Object.keys(data).forEach(function(prop){
         test(function(){
-          assert_own_property(myDiv.style, prop)
+          assert_true(prop in myDiv.style)
         }, prop)
 
         if ('initial' in data[prop]) test(function(){
@@ -66,7 +66,7 @@
         var syntaxTests = data[prop]
         Object.keys(syntaxTests).forEach(function(testcase){
           test(function(){
-            assert_own_property(myDiv.style, prop)
+            assert_true(prop in myDiv.style)
             myDiv.style[prop] = syntaxTests[testcase][0]
             assert_equals(myDiv.style[prop], syntaxTests[testcase][0], testcase)
             assert_equals(getComputedStyle(myDiv)[prop], syntaxTests[testcase][1], testcase)
diff --git a/css/css-grid/grid-model/grid-container-ignores-first-letter-001.html b/css/css-grid/grid-model/grid-container-ignores-first-letter-001.html
new file mode 100644
index 0000000..b8e344e
--- /dev/null
+++ b/css/css-grid/grid-model/grid-container-ignores-first-letter-001.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: '::first-letter' is ignored in grid containers</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#grid-containers">
+<link rel="help" href="http://www.w3.org/TR/css3-selectors/#first-letter">
+<meta name="assert" content="The test checks that grid item should ignore grid container's first-letter pseudo-element.">
+<link href="support/grid.css" rel="stylesheet">
+<style>
+  body { line-height: 20px; }
+  .grid-first-letter::first-letter { line-height: 100px; color: red; }
+  .container-first-letter::first-letter { line-height: 200px; color: green; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.container');">
+
+<div id="log"></div>
+
+<div class="container">
+  <div class="grid grid-first-letter">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+</div>
+
+<div class="container">
+  <div class="inline-grid grid-first-letter">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+</div>
+
+<div class="container">
+  <div class="grid grid-first-letter" data-expected-height=20>
+    Anonymous item.
+  </div>
+</div>
+
+<div class="container">
+  <div class="inline-grid grid-first-letter" data-expected-height=20>
+    Anonymous item.
+  </div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="grid grid-first-letter">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+  <div data-expected-height=200>Out of grid.</div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="inline-grid grid-first-letter">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+  <div data-expected-height=20>Out of grid.</div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="grid grid-first-letter" data-expected-height=20>
+    Anonymous item.
+  </div>
+  <div data-expected-height=200>Out of grid.</div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="inline-grid grid-first-letter" data-expected-height=20>
+    Anonymous item.
+  </div>
+  <div data-expected-height=20>Out of grid.</div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="grid">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+  <div data-expected-height=200>Out of grid.</div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="inline-grid">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+  <div data-expected-height=20>Out of grid.</div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="grid" data-expected-height=20>
+    Anonymous item.
+  </div>
+  <div data-expected-height=200>Out of grid.</div>
+</div>
+
+<div class="container container-first-letter">
+  <div class="inline-grid" data-expected-height=20>
+    Anonymous item.
+  </div>
+  <div data-expected-height=20>Out of grid.</div>
+</div>
+
+</body>
diff --git a/css/css-grid/grid-model/grid-container-ignores-first-line-001.html b/css/css-grid/grid-model/grid-container-ignores-first-line-001.html
new file mode 100644
index 0000000..140d7bf
--- /dev/null
+++ b/css/css-grid/grid-model/grid-container-ignores-first-line-001.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: '::first-line' is ignored in grid containers</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#grid-containers">
+<link rel="help" href="http://www.w3.org/TR/css3-selectors/#first-formatted-line">
+<meta name="assert" content="The test checks that grid item should ignore grid container's first-line pseudo-element.">
+<link href="support/grid.css" rel="stylesheet">
+<style>
+  body { line-height: 20px; }
+  .grid-first-line::first-line { line-height: 100px; }
+  .container::first-line { line-height: 200px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid,.inline-grid');">
+
+<div id="log"></div>
+
+<div class="grid grid-first-line">
+  <div class="item" data-expected-height=20>The first item.</div>
+  <div class="item" data-expected-height=20>The second item.</div>
+</div>
+
+<div class="inline-grid grid-first-line">
+  <div class="item" data-expected-height=20>The first item.</div>
+  <div class="item" data-expected-height=20>The second item.</div>
+</div>
+
+<div class="grid grid-first-line" data-expected-height=20>
+  Anonymous item.
+</div>
+
+<div class="inline-grid grid-first-line" data-expected-height=20>
+  Anonymous item.
+</div>
+
+<div class="container">
+  <div class="grid grid-first-line">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+</div>
+
+<div class="container">
+  <div class="inline-grid grid-first-line">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+</div>
+
+<div class="container">
+  <div class="grid grid-first-line" data-expected-height=20>
+    Anonymous item.
+  </div>
+</div>
+
+<div class="container">
+  <div class="inline-grid grid-first-line" data-expected-height=20>
+    Anonymous item.
+  </div>
+</div>
+
+<div class="container">
+  <div class="grid">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+</div>
+
+<div class="container">
+  <div class="inline-grid">
+    <div class="item" data-expected-height=20>The first item.</div>
+    <div class="item" data-expected-height=20>The second item.</div>
+  </div>
+</div>
+
+<div class="container">
+  <div class="grid" data-expected-height=20>
+    Anonymous item.
+  </div>
+</div>
+
+<div class="container">
+  <div class="inline-grid" data-expected-height=20>
+    Anonymous item.
+  </div>
+</div>
+
+</body>
diff --git a/css/css-grid/grid-model/grid-item-accepts-first-letter-001.html b/css/css-grid/grid-model/grid-item-accepts-first-letter-001.html
new file mode 100644
index 0000000..b6914eb
--- /dev/null
+++ b/css/css-grid/grid-model/grid-item-accepts-first-letter-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: '::first-letter' is valid in grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#grid-containers">
+<link rel="help" href="http://www.w3.org/TR/css3-selectors/#first-letter">
+<meta name="assert" content="The test checks that grid items accept first-letter pseudo-element.">
+<link href="support/grid.css" rel="stylesheet">
+<style>
+  .item::first-letter { line-height: 100px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid,.inline-grid');">
+
+<div id="log"></div>
+
+<div class="grid">
+  <div class="item" data-expected-height=100>The first item.</div>
+  <div class="item" data-expected-height=100>The second item.</div>
+</div>
+
+<div class="inline-grid">
+  <div class="item" data-expected-height=100>The first item.</div>
+  <div class="item" data-expected-height=100>The second item.</div>
+</div>
+
+</body>
diff --git a/css/css-grid/grid-model/grid-item-accepts-first-line-001.html b/css/css-grid/grid-model/grid-item-accepts-first-line-001.html
new file mode 100644
index 0000000..8bf8c65
--- /dev/null
+++ b/css/css-grid/grid-model/grid-item-accepts-first-line-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: '::first-line' is valid in grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#grid-containers">
+<link rel="help" href="http://www.w3.org/TR/css3-selectors/#first-formatted-line">
+<meta name="assert" content="The test checks that grid items accept first-line pseudo-element.">
+<link href="support/grid.css" rel="stylesheet">
+<style>
+  .item::first-line { line-height: 100px; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid,.inline-grid');">
+
+<div id="log"></div>
+
+<div class="grid">
+  <div class="item" data-expected-height=100>The first item.</div>
+  <div class="item" data-expected-height=100>The second item.</div>
+</div>
+
+<div class="inline-grid">
+  <div class="item" data-expected-height=100>The first item.</div>
+  <div class="item" data-expected-height=100>The second item.</div>
+</div>
+
+</body>
diff --git a/css/css-grid/support/grid.css b/css/css-grid/grid-model/support/grid.css
similarity index 100%
copy from css/css-grid/support/grid.css
copy to css/css-grid/grid-model/support/grid.css
diff --git a/css/css-grid/layout-algorithm/grid-find-fr-size-gutters-001.html b/css/css-grid/layout-algorithm/grid-find-fr-size-gutters-001.html
new file mode 100644
index 0000000..25d236c
--- /dev/null
+++ b/css/css-grid/layout-algorithm/grid-find-fr-size-gutters-001.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid Track Sizing Algorithm Flexible Tracks and Gutters</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#gutters">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-find-fr-size">
+<meta name="assert" content="This test checks that the size of flexible tracks is properly computed when you have gaps (with and without spanning items).">
+<style>
+.grid {
+  position: relative;
+  display: grid;
+  grid-gap: 10px 20px;
+  font: 10px/1 Ahem;
+  margin: 50px;
+}
+
+.fixedSize {
+  width: 200px;
+  height: 100px;
+}
+
+.contentBasedSize {
+  float: left;
+  height: auto;
+}
+
+.grid div:nth-child(1) { background: magenta; }
+.grid div:nth-child(2) { background: cyan; }
+.grid div:nth-child(3) { background: yellow; }
+.grid div:nth-child(4) { background: lime; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<div class="grid fixedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50"></div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40"></div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40"></div>
+</div>
+
+<div class="grid fixedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="200" data-expected-height="50"></div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40"></div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40"></div>
+</div>
+
+<div class="grid fixedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: 1; grid-row: 1;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div style="grid-column: 2; grid-row: 1;"
+    data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50"></div>
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="60" data-expected-width="200" data-expected-height="40"></div>
+</div>
+
+<div class="grid fixedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-row: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="100"></div>
+  <div data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50"></div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40"></div>
+</div>
+
+<div class="grid fixedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: 1; grid-row: 1;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div style="grid-column: 1; grid-row: 2;"
+    data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40"></div>
+  <div style="grid-row: span 2;"
+    data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="100"></div>
+</div>
+
+<div class="grid fixedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-row: span 2; grid-column: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="200" data-expected-height="100"></div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50">XXXXXXXX</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40">X<br>X<br>X<br>X</div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40"></div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50"></div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40"></div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40">XXXXXXXX<br>X<br>X<br>X</div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="200" data-expected-height="50"></div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40">X<br>X<br>X<br>X</div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40">XXXXXXXX</div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="200" data-expected-height="50">XXXXXXXXXXXXXXXXXXXX</div>
+  <div data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40">X<br>X<br>X<br>X</div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40"></div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: 1; grid-row: 1;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div style="grid-column: 2; grid-row: 1;"
+    data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50">XXXXXXXX</div>
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="60" data-expected-width="200" data-expected-height="40">X<br>X<br>X<br>X</div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: 1; grid-row: 1;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div style="grid-column: 2; grid-row: 1;"
+    data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50"></div>
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="60" data-expected-width="200" data-expected-height="40">XXXXXXXXXXXXXXXXXXXX<br>X<br>X<br>X</div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-row: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="100"></div>
+  <div data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50">XXXXXXXX</div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40">X<br>X<br>X<br>X</div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-row: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="100">X<br>X<br>X<br>X<br>X<br>X<br>X<br>X<br>X<br>X</div>
+  <div data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="50">XXXXXXXX</div>
+  <div data-offset-x="120" data-offset-y="60" data-expected-width="80" data-expected-height="40"></div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: 1; grid-row: 1;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div style="grid-column: 1; grid-row: 2;"
+    data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40">X<br>X<br>X<br>X</div>
+  <div style="grid-row: span 2;"
+    data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="100">XXXXXXXX</div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: 1; grid-row: 1;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50"></div>
+  <div style="grid-column: 1; grid-row: 2;"
+    data-offset-x="0" data-offset-y="60" data-expected-width="100" data-expected-height="40"></div>
+  <div style="grid-row: span 2;"
+    data-offset-x="120" data-offset-y="0" data-expected-width="80" data-expected-height="100">XXXXXXXX<br>X<br>X<br>X<br>X<br>X<br>X<br>X<br>X<br>X</div>
+</div>
+
+<div class="grid contentBasedSize" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-row: span 2; grid-column: span 2;"
+       data-offset-x="0" data-offset-y="0" data-expected-width="200" data-expected-height="100">
+XXXXXXXXXXXXXXXXXXXX<br>X<br>X<br>X<br>X<br>X<br>X<br>X<br>X<br>X</div>
+</div>
diff --git a/css/css-grid/layout-algorithm/grid-find-fr-size-gutters-002.html b/css/css-grid/layout-algorithm/grid-find-fr-size-gutters-002.html
new file mode 100644
index 0000000..340fe82
--- /dev/null
+++ b/css/css-grid/layout-algorithm/grid-find-fr-size-gutters-002.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid Track Sizing Algorithm Flexible Tracks and Gutters</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#gutters">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-find-fr-size">
+<meta name="assert" content="This test checks that the size of flexible tracks is properly computed when the grid container is content based and you have items spanning flexbile tracks that are smaller than the gutter sizes.">
+<style>
+.grid {
+  position: relative;
+  display: inline-grid;
+  grid-gap: 50px 100px;
+  font: 10px/1 Ahem;
+  margin: 50px;
+}
+
+.grid div:nth-child(1) { background: magenta; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<div class="grid" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="200" data-expected-height="50">X</div>
+</div>
+
+<div class="grid" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-row: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="100">X</div>
+</div>
+
+<div class="grid" style="grid: 50px 1fr / 100px 1fr;">
+  <div style="grid-column: span 2; grid-row: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="200" data-expected-height="100">X</div>
+</div>
+
+<div class="grid" style="grid: 0px 1fr / 0px 1fr;">
+  <div style="grid-column: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="0">X</div>
+</div>
+
+<div class="grid" style="grid: 0px 1fr / 0px 1fr;">
+  <div style="grid-row: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="0" data-expected-height="50">X</div>
+</div>
+
+<div class="grid" style="grid: 0px 1fr / 0px 1fr;">
+  <div style="grid-column: span 2; grid-row: span 2;"
+    data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="50">X</div>
+</div>
diff --git a/css/css-grid/layout-algorithm/grid-intrinsic-size-with-orthogonal-items.html b/css/css-grid/layout-algorithm/grid-intrinsic-size-with-orthogonal-items.html
new file mode 100644
index 0000000..9fb0f4f
--- /dev/null
+++ b/css/css-grid/layout-algorithm/grid-intrinsic-size-with-orthogonal-items.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Compute the grid's intrinsic size when there are orthogonal items</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#algo-overview">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-4/#establish-an-orthogonal-flow">
+<meta name="assert" content="The test checks that using track's max sizing function to estimate orthogonal items' block-axis size to compute the grid's intrinsic size may lead to content overflow.">
+<style>
+.grid {
+    display: inline-grid;
+    font: 25px/1 Ahem;
+    background: green;
+    height: 150px;
+}
+
+.item { writing-mode: vertical-lr; }
+
+.minmax-100-200 { grid: minmax(100px, 200px) / auto; }
+.minmax-auto-200 { grid: minmax(100px, 200px) / auto; }
+.minmax-auto-100 { grid: minmax(auto, 100px) / auto; }
+.minmax-100-auto { grid: minmax(100px, auto) / auto; }
+.minmax-100-fitcontent { grid: minmax(100px, fit-content) / auto; }
+.minmax-100-1fr { grid: minmax(100px, 1fr) / auto; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.grid')">
+
+<pre>rows: auto</pre>
+
+<div class="grid" data-expected-width="25" data-expected-height="150">
+    <div class="item" data-expected-width="75" data-expected-height="150">XXX XX X XXX XX X</div>
+</div>
+
+<pre>rows: minmax(100px, 200px)</pre>
+
+<div class="grid minmax-100-200" data-expected-width="50" data-expected-height="150">
+    <div class="item" data-expected-width="75" data-expected-height="150">XXX XX X XXX XX X</div>
+</div>
+
+<pre>rows: minmax(auto, 200px)</pre>
+
+<div class="grid minmax-auto-200" data-expected-width="50" data-expected-height="150">
+    <div class="item" data-expected-width="75" data-expected-height="150">XXX XX X XXX XX X</div>
+</div>
+
+<pre>rows: minmax(100px, auto)</pre>
+
+<div class="grid minmax-100-auto" data-expected-width="25" data-expected-height="150">
+    <div class="item" data-expected-width="75" data-expected-height="150">XXX XX X XXX XX X</div>
+</div>
+
+<pre>rows: minmax(auto, 100px)</pre>
+
+<div class="grid minmax-auto-100" data-expected-width="100" data-expected-height="150">
+    <div class="item" data-expected-width="100" data-expected-height="100">XXX XX X XXX XX X</div>
+</div>
+
+<pre>rows: minmax(100px, fit-content)</pre>
+
+<div class="grid minmax-auto-fitcontent" data-expected-width="25" data-expected-height="150">
+    <div class="item" data-expected-width="75" data-expected-height="150">XXX XX X XXX XX X</div>
+</div>
+
+<pre>rows: minmax(100px, 1fr)</pre>
+
+<div class="grid minmax-auto-1fr" data-expected-width="25" data-expected-height="150">
+    <div class="item" data-expected-width="75" data-expected-height="150">XXX XX X XXX XX X</div>
+</div>
+
diff --git a/css/css-grid/reference/grid-collapsed-row-gutters-ref.html b/css/css-grid/reference/grid-collapsed-row-gutters-ref.html
index 7d33e57..a0ff682 100644
--- a/css/css-grid/reference/grid-collapsed-row-gutters-ref.html
+++ b/css/css-grid/reference/grid-collapsed-row-gutters-ref.html
@@ -38,6 +38,7 @@
     }
 </style>
 
+<p>The test passes if it has the same visual effect as reference. Column gap should be percentage of width. Row gap should resolve to auto, and therefore collapse to 0 height.</p>
 <div id="grid">
     <div></div>
     <div></div>
diff --git a/css/css-grid/reference/grid-different-gutters-ref.html b/css/css-grid/reference/grid-different-gutters-ref.html
index 52ee9e0..57d27ff 100644
--- a/css/css-grid/reference/grid-different-gutters-ref.html
+++ b/css/css-grid/reference/grid-different-gutters-ref.html
@@ -38,6 +38,7 @@
     }
 </style>
 
+<p>The test passes if it has the same visual effect as reference.</p>
 <div id="grid">
     <div></div>
     <div></div>
diff --git a/css/css-grid/reference/grid-equal-gutters-ref.html b/css/css-grid/reference/grid-equal-gutters-ref.html
index 3d99526..caea89a 100644
--- a/css/css-grid/reference/grid-equal-gutters-ref.html
+++ b/css/css-grid/reference/grid-equal-gutters-ref.html
@@ -38,6 +38,7 @@
     }
 </style>
 
+<p>The test passes if it has the same visual effect as reference.</p>
 <div id="grid">
     <div></div>
     <div></div>
diff --git a/css/css-grid/support/grid-alignment.css b/css/css-grid/support/grid-alignment.css
deleted file mode 100644
index 4cf5015..0000000
--- a/css/css-grid/support/grid-alignment.css
+++ /dev/null
@@ -1,240 +0,0 @@
-/* align-self */
-.alignSelfAuto { align-self: auto; }
-.alignSelfStretch { align-self: stretch; }
-.alignSelfStart { align-self: start; }
-.alignSelfEnd { align-self: end; }
-.alignSelfCenter { align-self: center; }
-.alignSelfRight { align-self: right; }
-.alignSelfLeft { align-self: left; }
-
-.alignSelfFlexStart { align-self: flex-start; }
-.alignSelfFlexEnd { align-self: flex-end; }
-
-.alignSelfSelfStart { align-self: self-start; }
-.alignSelfSelfEnd { align-self: self-end; }
-
-.alignSelfCenterSafe { align-self: center safe; }
-.alignSelfCenterUnsafe { align-self: center unsafe; }
-.alignSelfEndSafe { align-self: end safe; }
-.alignSelfEndUnsafe { align-self: end unsafe; }
-
-.alignSelfBaseline { align-self: baseline; }
-
-/* align-items */
-.alignItemsAuto { align-items: auto; }
-.alignItemsStretch { align-items: stretch; }
-.alignItemsStart { align-items: start; }
-.alignItemsCenter { align-items: center; }
-.alignItemsEnd { align-items: end; }
-.alignItemsBaseline { align-items: baseline; }
-
-.alignItemsCenterSafe { align-items: center safe; }
-.alignItemsCenterUnsafe { align-items: center unsafe; }
-.alignItemsEndSafe { align-items: end safe; }
-.alignItemsEndUnsafe { align-items: end unsafe; }
-
-/* align-content */
-.alignContentBaseline { align-content: baseline; }
-.alignContentLastBaseline { align-content: last-baseline; }
-.alignContentStart { align-content: start; }
-.alignContentEnd { align-content: end; }
-.alignContentCenter { align-content: center; }
-.alignContentLeft { align-content: left; }
-.alignContentRight { align-content: right; }
-.alignContentFlexStart { align-content: flex-start; }
-.alignContentFlexEnd { align-content: flex-end; }
-.alignContentSpaceBetween { align-content: space-between; }
-.alignContentSpaceAround { align-content: space-around; }
-.alignContentSpaceEvenly { align-content: space-evenly; }
-.alignContentStretch { align-content: stretch; }
-
-/* justify-self */
-.justifySelfAuto { justify-self: auto; }
-.justifySelfStretch { justify-self: stretch; }
-.justifySelfStart { justify-self: start; }
-.justifySelfCenter { justify-self: center; }
-.justifySelfEnd { justify-self: end; }
-.justifySelfRight { justify-self: right; }
-.justifySelfLeft { justify-self: left; }
-
-.justifySelfFlexStart { justify-self: flex-start; }
-.justifySelfFlexEnd { justify-self: flex-end; }
-
-.justifySelfSelfStart { justify-self: self-start; }
-.justifySelfSelfEnd { justify-self: self-end; }
-
-.justifySelfCenterSafe { justify-self: center safe; }
-.justifySelfCenterUnsafe { justify-self: center unsafe; }
-
-.justifySelfBaseline { justify-self: baseline; }
-
-/* justify-items */
-.justifyItemsAuto { justify-items: auto; }
-.justifyItemsStretch { justify-items: stretch; }
-.justifyItemsStart { justify-items: start; }
-.justifyItemsCenter { justify-items: center; }
-.justifyItemsEnd { justify-items: end; }
-
-.justifyItemsCenterSafe { justify-items: center safe; }
-.justifyItemsCenterUnsafe { justify-items: center unsafe; }
-.justifyItemsEndSafe { justify-items: end safe; }
-.justifyItemsEndUnsafe { justify-items: end unsafe; }
-
-.justifyItemsBaseline { justify-items: baseline; }
-
-/* justify-content */
-.justifyContentBaseline { justify-content: baseline; }
-.justifyContentLastBaseline { justify-content: last-baseline; }
-.justifyContentStart { justify-content: start; }
-.justifyContentEnd { justify-content: end; }
-.justifyContentCenter { justify-content: center; }
-.justifyContentLeft { justify-content: left; }
-.justifyContentRight { justify-content: right; }
-.justifyContentFlexStart { justify-content: flex-start; }
-.justifyContentFlexEnd { justify-content: flex-end; }
-.justifyContentSpaceBetween { justify-content: space-between; }
-.justifyContentSpaceAround { justify-content: space-around; }
-.justifyContentSpaceEvenly { justify-content: space-evenly; }
-.justifyContentStretch { justify-content: stretch; }
-
-/* Both align-items and justify-items */
-.itemsNormal {
-  align-items: normal;
-  justify-items: normal;
-}
-
-.itemsStretch {
-  align-items: stretch;
-  justify-items: stretch;
-}
-
-.itemsStart {
-  align-items: start;
-  justify-items: start;
-}
-
-.itemsCenter {
-  align-items: center;
-  justify-items: center;
-}
-
-.itemsEnd {
-  align-items: end;
-  justify-items: end;
-}
-
-.itemsLeft {
-  align-items: left;
-  justify-items: left;
-}
-
-.itemsRight {
-  align-items: right;
-  justify-items: right;
-}
-
-.itemsSelfStart {
-  align-items: self-start;
-  justify-items: self-start;
-}
-
-.itemsSelfEnd {
-  align-items: self-end;
-  justify-items: self-end;
-}
-.itemsBaseline {
-  align-items: baseline;
-  justify-items: baseline;
-}
-
-/* Both align-self and justify-self */
-.selfStretch {
-  align-self: stretch;
-  justify-self: stretch;
-}
-.selfStart {
-  align-self: start;
-  justify-self: start;
-}
-.selfEnd {
-  align-self: end;
-  justify-self: end;
-}
-.selfCenter {
-  align-self: center;
-  justify-self: center;
-}
-.selfRight {
-  align-self: right;
-  justify-self: right;
-}
-.selfLeft {
-  align-self: left;
-  justify-self: left;
-}
-.selfSelfStart {
-  align-self: self-start;
-  justify-self: self-start;
-}
-.selfSelfEnd {
-  align-self: self-end;
-  justify-self: self-end;
-}
-.selfBaseline {
-  align-self: baseline;
-  justify-self: baseline;
-}
-
-/* Both align-content and justify-content */
-.contentStart {
-  align-content: start;
-  justify-content: start;
-}
-.contentCenter {
-  align-content: center;
-  justify-content: center;
-}
-.contentEnd {
-  align-content: end;
-  justify-content: end;
-}
-
-.contentCenterSafe {
-  align-content: center safe;
-  justify-content: center safe;
-}
-
-.contentCenterUnsafe {
-  align-content: center unsafe;
-  justify-content: center unsafe;
-}
-
-.contentEndSafe {
-  align-content: end safe;
-  justify-content: end safe;
-}
-
-.contentEndUnsafe {
-  align-content: end unsafe;
-  justify-content: end unsafe;
-}
-
-.contentSpaceBetween {
-  justify-content: space-between;
-  align-content: space-between;
-}
-
-.contentSpaceAround {
-  justify-content: space-around;
-  align-content: space-around;
-}
-
-.contentSpaceEvenly {
-  justify-content: space-evenly;
-  align-content: space-evenly;
-}
-
-.contentStretch {
-  justify-content: stretch;
-  align-content: stretch;
-}
diff --git a/css/css-grid/support/style-change.js b/css/css-grid/support/style-change.js
deleted file mode 100644
index 636b08a..0000000
--- a/css/css-grid/support/style-change.js
+++ /dev/null
@@ -1,5 +0,0 @@
-function evaluateStyleChange(element, phase, expectedProperty, expectedResult) {
-    element.className += " " + phase;
-    element.setAttribute(expectedProperty, expectedResult);
-    checkLayout("." + phase);
-}
diff --git a/css/css-images/gradient-border-box-ref.html b/css/css-images/gradient-border-box-ref.html
new file mode 100644
index 0000000..5b219ce
--- /dev/null
+++ b/css/css-images/gradient-border-box-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <style>
+#x {
+  width: 280px;
+  height: 280px;
+  background-image: repeating-linear-gradient(to bottom right, white, black, white 30px);
+}
+  </style>
+</head>
+<body>
+  <div id="x"></div>
+</body>
+</html>
diff --git a/css/css-images/gradient-border-box.html b/css/css-images/gradient-border-box.html
new file mode 100644
index 0000000..2938c5e
--- /dev/null
+++ b/css/css-images/gradient-border-box.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Gradient Background aligned to Content Box</title>
+  <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#the-background-origin">
+  <link rel="match" href="gradient-border-box-ref.html">
+  <meta name="assert" content="The background-origin: border-box; statement is understood.">
+  <style>
+#x {
+  background-origin: border-box;
+  width: 200px;
+  height: 200px;
+  border-style: solid;
+  border-width: 40px;
+  border-color: transparent;
+  background-image: repeating-linear-gradient(to bottom right, white, black, white 30px);
+}
+  </style>
+</head>
+<body>
+  <div id="x"></div>
+</body>
+</html>
diff --git a/css/css-images/gradient-content-box-ref.html b/css/css-images/gradient-content-box-ref.html
new file mode 100644
index 0000000..fae2954
--- /dev/null
+++ b/css/css-images/gradient-content-box-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <style>
+#x {
+  width: 200px;
+  height: 200px;
+  border: 40px;
+  border-style: solid;
+  border-color: transparent;
+  background-image: repeating-linear-gradient(to bottom right, white, black, white 30px);
+}
+  </style>
+</head>
+<body>
+  <div id="x"></div>
+</body>
+</html>
diff --git a/css/css-images/gradient-content-box.html b/css/css-images/gradient-content-box.html
new file mode 100644
index 0000000..82b42ee
--- /dev/null
+++ b/css/css-images/gradient-content-box.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Gradient Background aligned to Content Box</title>
+  <link rel="help" href="https://www.w3.org/TR/css-backgrounds-3/#the-background-origin">
+  <link rel="match" href="gradient-content-box-ref.html">
+  <meta name="assert" content="The background-origin: content-box; statement is understood.">
+  <style>
+#x {
+  background-origin: content-box;
+  width: 200px;
+  height: 200px;
+  padding: 40px;
+  background-image: repeating-linear-gradient(to bottom right, white, black, white 30px);
+}
+  </style>
+</head>
+<body>
+  <div id="x"></div>
+</body>
+</html>
diff --git a/css/css-images/gradient-crash-ref.html b/css/css-images/gradient-crash-ref.html
new file mode 100644
index 0000000..82055fc
--- /dev/null
+++ b/css/css-images/gradient-crash-ref.html
@@ -0,0 +1 @@
+<!--intentionally blank-->
diff --git a/css/css-images/gradient-crash.html b/css/css-images/gradient-crash.html
new file mode 100644
index 0000000..dbdf77b
--- /dev/null
+++ b/css/css-images/gradient-crash.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Does this gradient crash the browser?</title>
+<link rel="match" href="gradient-crash-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-images-3/#color-stop-syntax">
+<meta name="assert" content="Gradients with total length zero and absolute positioned stops do not crash.">
+<style>
+  div {
+    background: linear-gradient(black 0,white);
+  }
+</style>
+<div></div>
diff --git a/css/css-images/parsing/gradient-position-invalid.html b/css/css-images/parsing/gradient-position-invalid.html
new file mode 100644
index 0000000..e53486e
--- /dev/null
+++ b/css/css-images/parsing/gradient-position-invalid.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing gradients with invalid position values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#typedef-position">
+<meta name="assert" content="gradient positions support only the '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// The following were supported in an earlier version of the spec.
+// https://github.com/w3c/csswg-drafts/issues/2140
+// Deprecated in Blink with support to be removed in M68, around July 2018.
+test_invalid_value("background-image", "radial-gradient(at center left 1px, red, blue)");
+test_invalid_value("background-image", "radial-gradient(at center top 2px, red, blue)");
+test_invalid_value("background-image", "radial-gradient(at right 3% center, red, blue)");
+test_invalid_value("background-image", "radial-gradient(at left 4px top, red, blue)");
+test_invalid_value("background-image", "radial-gradient(at right top 5px, red, blue)");
+test_invalid_value("background-image", "radial-gradient(at bottom 6% center, red, blue)");
+test_invalid_value("background-image", "radial-gradient(at bottom 7% left, red, blue)");
+test_invalid_value("background-image", "radial-gradient(at bottom right 8%, red, blue)");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/gradient-position-valid.html b/css/css-images/parsing/gradient-position-valid.html
new file mode 100644
index 0000000..6eae484
--- /dev/null
+++ b/css/css-images/parsing/gradient-position-valid.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing gradients with valid position values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#typedef-position">
+<meta name="assert" content="gradient positions support the full '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Where two values are shown, the first serialization is being used by Blink/Firefox/WebKit and the second by Edge.
+
+test_valid_value("background-image", "radial-gradient(at 10%, red, blue)", ["radial-gradient(at 10% center, red, blue)", "radial-gradient(at 10%, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at 20% 30px, red, blue)");
+test_valid_value("background-image", "radial-gradient(at 30px center, red, blue)", ["radial-gradient(at 30px center, red, blue)", "radial-gradient(at 30px, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at 40px top, red, blue)");
+test_valid_value("background-image", "radial-gradient(at bottom 10% right 20%, red, blue)", "radial-gradient(at right 20% bottom 10%, red, blue)");
+test_valid_value("background-image", "radial-gradient(at bottom right, red, blue)", "radial-gradient(at right bottom, red, blue)");
+test_valid_value("background-image", "radial-gradient(at center, red, blue)", ["radial-gradient(at center center, red, blue)", "radial-gradient(at center, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at center 50px, red, blue)");
+test_valid_value("background-image", "radial-gradient(at center bottom, red, blue)", ["radial-gradient(at center bottom, red, blue)", "radial-gradient(at bottom, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at center center, red, blue)", ["radial-gradient(at center center, red, blue)", "radial-gradient(at center, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at center left, red, blue)", ["radial-gradient(at left center, red, blue)", "radial-gradient(at left, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at left, red, blue)", ["radial-gradient(at left center, red, blue)", "radial-gradient(at left, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at left bottom, red, blue)");
+test_valid_value("background-image", "radial-gradient(at left center, red, blue)", ["radial-gradient(at left center, red, blue)", "radial-gradient(at left, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at right 40%, red, blue)");
+test_valid_value("background-image", "radial-gradient(at right 30% top 60px, red, blue)");
+test_valid_value("background-image", "radial-gradient(at top, red, blue)", ["radial-gradient(at center top, red, blue)", "radial-gradient(at top, red, blue)"]);
+test_valid_value("background-image", "radial-gradient(at top center, red, blue)", ["radial-gradient(at center top, red, blue)", "radial-gradient(at top, red, blue)"]);
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/image-orientation-invalid.html b/css/css-images/parsing/image-orientation-invalid.html
new file mode 100644
index 0000000..b36d9b7
--- /dev/null
+++ b/css/css-images/parsing/image-orientation-invalid.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing image-orientation with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-3/#propdef-image-orientation">
+<meta name="assert" content="image-orientation supports only the grammar 'from-image | <angle> | [ <angle>? flip ]'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("image-orientation", "auto");
+test_invalid_value("image-orientation", "none");
+test_invalid_value("image-orientation", "0");
+test_invalid_value("image-orientation", "0 flip");
+test_invalid_value("image-orientation", "0deg from-image");
+test_invalid_value("image-orientation", "flip 0deg");
+test_invalid_value("image-orientation", "flip from-image");
+test_invalid_value("image-orientation", "from-image 0deg");
+test_invalid_value("image-orientation", "from-image flip");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/image-orientation-valid.html b/css/css-images/parsing/image-orientation-valid.html
new file mode 100644
index 0000000..1ed7548
--- /dev/null
+++ b/css/css-images/parsing/image-orientation-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing image-orientation with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-3/#propdef-image-orientation">
+<meta name="assert" content="image-orientation supports the full grammar 'from-image | <angle> | [ <angle>? flip ]'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("image-orientation", "from-image");
+test_valid_value("image-orientation", "30deg");
+test_valid_value("image-orientation", "flip", "0deg flip"); // "0deg flip" in Firefox.
+test_valid_value("image-orientation", "-1.25turn flip");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/image-rendering-invalid.html b/css/css-images/parsing/image-rendering-invalid.html
new file mode 100644
index 0000000..84b0727
--- /dev/null
+++ b/css/css-images/parsing/image-rendering-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing image-rendering with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-3/#propdef-image-rendering">
+<meta name="assert" content="image-rendering supports only the grammar 'auto | smooth | high-quality | crisp-edges | pixelated'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("image-rendering", "none");
+test_invalid_value("image-rendering", "high-quality crisp-edges");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/image-rendering-valid.html b/css/css-images/parsing/image-rendering-valid.html
new file mode 100644
index 0000000..80ea1c4
--- /dev/null
+++ b/css/css-images/parsing/image-rendering-valid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing image-rendering with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-3/#propdef-image-rendering">
+<meta name="assert" content="image-rendering supports the full grammar 'auto | smooth | high-quality | crisp-edges | pixelated'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("image-rendering", "auto");
+test_valid_value("image-rendering", "smooth");
+test_valid_value("image-rendering", "high-quality");
+test_valid_value("image-rendering", "crisp-edges");
+test_valid_value("image-rendering", "pixelated");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/image-resolution-invalid.html b/css/css-images/parsing/image-resolution-invalid.html
new file mode 100644
index 0000000..332ad15
--- /dev/null
+++ b/css/css-images/parsing/image-resolution-invalid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 4: parsing image-resolution with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#propdef-image-resolution">
+<meta name="assert" content="image-resolution supports only the grammar '[ from-image || <resolution> ] && snap?'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("image-resolution", "auto");
+test_invalid_value("image-resolution", "100%");
+test_invalid_value("image-resolution", "2");
+test_invalid_value("image-resolution", "3dpi snap from-image");
+test_invalid_value("image-resolution", "from-image snap 4dppx");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/image-resolution-valid.html b/css/css-images/parsing/image-resolution-valid.html
new file mode 100644
index 0000000..9317902
--- /dev/null
+++ b/css/css-images/parsing/image-resolution-valid.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 4: parsing image-resolution with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#propdef-image-resolution">
+<meta name="assert" content="image-resolution supports the full grammar '[ from-image || <resolution> ] && snap?'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+
+
+
+test_valid_value("image-resolution", "1dpi");
+test_valid_value("image-resolution", "2dpcm from-image");
+test_valid_value("image-resolution", "3dppx from-image snap");
+test_valid_value("image-resolution", "4dpi snap");
+test_valid_value("image-resolution", "from-image");
+test_valid_value("image-resolution", "from-image 5dpcm");
+test_valid_value("image-resolution", "from-image 6dppx snap");
+test_valid_value("image-resolution", "from-image snap");
+test_valid_value("image-resolution", "snap 7.5dpi");
+test_valid_value("image-resolution", "snap -8dpcm from-image");
+test_valid_value("image-resolution", "snap from-image");
+test_valid_value("image-resolution", "snap from-image 0dppx");
+
+
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/object-fit-invalid.html b/css/css-images/parsing/object-fit-invalid.html
new file mode 100644
index 0000000..e4a298b
--- /dev/null
+++ b/css/css-images/parsing/object-fit-invalid.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 4: parsing object-fit with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#propdef-object-fit">
+<meta name="assert" content="object-fit supports only the grammar 'fill | none | [contain | cover] || scale-down'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("object-fit", "auto");
+test_invalid_value("object-fit", "contain cover");
+test_invalid_value("object-fit", "fill scale-down");
+test_invalid_value("object-fit", "contain fill");
+test_invalid_value("object-fit", "cover none");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/object-fit-valid.html b/css/css-images/parsing/object-fit-valid.html
new file mode 100644
index 0000000..0f73cfe
--- /dev/null
+++ b/css/css-images/parsing/object-fit-valid.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 4: parsing object-fit with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#propdef-object-fit">
+<meta name="assert" content="object-fit supports the full grammar 'fill | none | [contain | cover] || scale-down'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("object-fit", "contain");
+test_valid_value("object-fit", "contain scale-down");
+test_valid_value("object-fit", "cover");
+test_valid_value("object-fit", "cover scale-down");
+test_valid_value("object-fit", "fill");
+test_valid_value("object-fit", "none");
+test_valid_value("object-fit", "scale-down");
+test_valid_value("object-fit", "scale-down contain");
+test_valid_value("object-fit", "scale-down cover");
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/object-position-invalid.html b/css/css-images/parsing/object-position-invalid.html
new file mode 100644
index 0000000..87de7b9
--- /dev/null
+++ b/css/css-images/parsing/object-position-invalid.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing object-position with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-3/#propdef-object-position">
+<meta name="assert" content="object-position supports only the '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("object-position", "auto");
+test_invalid_value("object-position", "1px 2px 3px");
+test_invalid_value("object-position", "left right");
+test_invalid_value("object-position", "bottom 10% top 20%");
+
+// The following were supported in an earlier version of the spec.
+// https://github.com/w3c/csswg-drafts/issues/2140
+// Deprecated in Blink with support to be removed in M68, around July 2018.
+test_invalid_value("object-position", "center left 1px");
+test_invalid_value("object-position", "center top 2px");
+test_invalid_value("object-position", "right 3% center");
+test_invalid_value("object-position", "left 4px top");
+test_invalid_value("object-position", "right top 5px");
+test_invalid_value("object-position", "bottom 6% center");
+test_invalid_value("object-position", "bottom 7% left");
+test_invalid_value("object-position", "bottom right 8%");
+
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/object-position-valid.html b/css/css-images/parsing/object-position-valid.html
new file mode 100644
index 0000000..f78f67e
--- /dev/null
+++ b/css/css-images/parsing/object-position-valid.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: parsing object-position with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-3/#propdef-object-position">
+<meta name="assert" content="object-position supports the full '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// First serialization is being returned by Blink/Firefox/WebKit, second by Edge.
+test_valid_value("object-position", "10%", ["10% center", "10% 50%"]);
+test_valid_value("object-position", "right 30% top 60px"); // "calc(70%) 60px" in Edge.
+test_valid_value("object-position", "20% 30px");
+test_valid_value("object-position", "30px center", ["30px center", "30px 50%"]);
+test_valid_value("object-position", "40px top", ["40px top", "40px 0%"]);
+test_valid_value("object-position", "bottom 10% right 20%", "right 20% bottom 10%"); // "calc(80%) calc(90%)" in Edge.
+test_valid_value("object-position", "bottom right", ["right bottom", "100% 100%"]);
+test_valid_value("object-position", "center 50px", ["center 50px", "50% 50px"]);
+test_valid_value("object-position", "center bottom", ["center bottom", "50% 100%"]);
+test_valid_value("object-position", "center left", ["left center", "0% 50%"]);
+test_valid_value("object-position", "left", ["left center", "0% 50%"]);
+test_valid_value("object-position", "left bottom", ["left bottom", "0% 100%"]);
+test_valid_value("object-position", "left center", ["left center", "0% 50%"]);
+test_valid_value("object-position", "right 40%", ["right 40%", "100% 40%"]);
+test_valid_value("object-position", "top", ["center top", "50% 0%"]);
+test_valid_value("object-position", "top center", ["center top", "50% 0%"]);
+
+// ["center center"] in Blink and Firefox, "center" in WebKit, "50% 50%" in Edge.
+test_valid_value("object-position", "center", ["center center", "center", "50% 50%"]);
+test_valid_value("object-position", "center center", ["center center", "center", "50% 50%"]);
+</script>
+</body>
+</html>
diff --git a/css/css-images/parsing/resources/parsing-testcommon.js b/css/css-images/parsing/resources/parsing-testcommon.js
new file mode 100644
index 0000000..b075882
--- /dev/null
+++ b/css/css-images/parsing/resources/parsing-testcommon.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
+function test_valid_value(property, value, serializedValue) {
+    if (arguments.length < 3)
+        serializedValue = value;
+
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
+
+        var div = document.createElement('div');
+        div.style[property] = value;
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
+        div.style[property] = readValue;
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+}
+
+function test_invalid_value(property, value) {
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_equals(div.style.getPropertyValue(property), "");
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
+}
diff --git a/css/css-images/tiled-gradients-ref.html b/css/css-images/tiled-gradients-ref.html
new file mode 100644
index 0000000..a6fa412
--- /dev/null
+++ b/css/css-images/tiled-gradients-ref.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <style>
+            .bar {
+                width: 400px;
+                height: 100px;
+            }
+            .box {
+                display: inline-block;
+                width: 100px;
+                height: 100px;
+                border: 0px;
+                background-image: linear-gradient(to bottom left, red 50%, transparent 50%);
+            }
+
+        </style>
+    </head>
+    <body>
+        <div class="bar">
+            <div class="box"></div><div class="box"></div><div class="box"></div><div class="box"></div>
+        </div>
+        <div class="bar">
+            <div class="box"></div><div class="box"></div><div class="box"></div><div class="box"></div>
+        </div>
+    </body>
+</html>
+
diff --git a/css/css-images/tiled-gradients.html b/css/css-images/tiled-gradients.html
new file mode 100644
index 0000000..f6f5f5b
--- /dev/null
+++ b/css/css-images/tiled-gradients.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Eight Red Triangles on White Ground (with gradients)</title>
+        <link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#propdef-background-size">
+        <meta name="assert" content="Gradients are correctly repeated.">
+        <link rel="match" href="tiled-gradients-ref.html">
+        <style>
+            #gradient {
+                width: 400px;
+                height: 200px;
+                background-size: 25% 50%;
+                background-image: linear-gradient(to bottom left, red 50%, transparent 50%);
+            }
+        </style>
+    </head>
+    <body>
+        <div id="gradient"></div>
+    </body>
+</html>
+
diff --git a/css/css-images/tiled-radial-gradients-ref.html b/css/css-images/tiled-radial-gradients-ref.html
new file mode 100644
index 0000000..30046e4
--- /dev/null
+++ b/css/css-images/tiled-radial-gradients-ref.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <style>
+            body {
+                margin: 0px;
+            }
+
+            #outer {
+                position: absolute;
+                width: 600px;
+                height: 200px;
+                background-color: aquamarine;
+            }
+
+            #left, #right {
+                position: absolute;
+                width: 300px;
+                height: 200px;
+                background-image: radial-gradient(closest-side, red 40%, transparent 40%)
+
+            }
+            #left {
+                left: 80px;
+            }
+
+            #right {
+                left: 380px;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="outer">
+            <div id="left"></div>
+            <div id="right"></div>
+        </div>
+    </body>
+</html>
+
diff --git a/css/css-images/tiled-radial-gradients.html b/css/css-images/tiled-radial-gradients.html
new file mode 100644
index 0000000..6615e8b
--- /dev/null
+++ b/css/css-images/tiled-radial-gradients.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Two Ellipses with Custom Placement (with gradients)</title>
+        <link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#propdef-background-size">
+        <meta name="assert" content="Gradients are correctly repeated.">
+        <link rel="match" href="tiled-radial-gradients-ref.html">
+        <style>
+            body {
+                margin: 0px;
+            }
+            #gradient {
+                position: absolute;
+                width: 600px;
+                height: 200px;
+                left: 0px;
+                margin: 0px;
+                background-color: aquamarine;
+                background-image: radial-gradient(closest-side, red 40%, transparent 40%);
+                background-size: 300px 200px;
+                background-position: 80px 0px;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="gradient"></div>
+    </body>
+</html>
diff --git a/css/css-layout-api/OWNERS b/css/css-layout-api/OWNERS
new file mode 100644
index 0000000..ade6708
--- /dev/null
+++ b/css/css-layout-api/OWNERS
@@ -0,0 +1,2 @@
+@bfgeek
+@tabatkins
diff --git a/css/css-layout-api/at-supports-rule.https.html b/css/css-layout-api/at-supports-rule.https.html
new file mode 100644
index 0000000..0f23325
--- /dev/null
+++ b/css/css-layout-api/at-supports-rule.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#valdef-display-layout">
+<meta name="assert" content="This test checks that a layout() function works correctly with an @supports rule." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#test {
+  content: 'fail';
+}
+
+@supports (display: layout(foo)) {
+  #test {
+    content: 'pass';
+  }
+}
+</style>
+<div id="test"></div>
+<script>
+test(function() {
+  const element = document.getElementById('test');
+  assert_equals(getComputedStyle(element).content, '"pass"');
+});
+</script>
diff --git a/css/css-layout-api/auto-block-size-absolute-ref.html b/css/css-layout-api/auto-block-size-absolute-ref.html
new file mode 100644
index 0000000..416d57c
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-absolute-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<style>
+.container {
+  position: relative;
+  margin: 20px 0;
+  width: 200px;
+  height: 200px;
+  border: solid 2px;
+}
+
+.result {
+  position: absolute;
+  background: green;
+}
+</style>
+<div class="container">
+  <div class="result" style="width: 100px; height: 100px; bottom: 0px;"></div>
+</div>
+<div class="container">
+  <div class="result" style="width: 100px; height: 200px;"></div>
+</div>
+<div class="container">
+  <div class="result" style="width: 100px; height: 100px;"></div>
+</div>
+<div class="container">
+  <div class="result" style="width: 200px; height: 100px;"></div>
+</div>
diff --git a/css/css-layout-api/auto-block-size-absolute.https.html b/css/css-layout-api/auto-block-size-absolute.https.html
new file mode 100644
index 0000000..c2a58c9
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-absolute.https.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-absolute-ref.html">
+<meta name="assert" content="This test checks that the absolute positioning respects the auto-block-size." />
+
+<style>
+.test {
+  position: absolute;
+  background: red;
+}
+
+@supports (display: layout(block-size-100)) {
+  .test {
+    display: layout(block-size-100);
+    background: green;
+  }
+}
+
+.container {
+  position: relative;
+  margin: 20px 0;
+  width: 200px;
+  height: 200px;
+  border: solid 2px;
+}
+
+.width-100 {
+  width: 100px;
+  writing-mode: horizontal-tb;
+}
+
+.height-100 {
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- 100px x 100px, bottom-left -->
+<div class="container">
+  <div class="test width-100" style="bottom: 0px"></div>
+</div>
+
+<!-- 100px x 200px, left, auto-size is ignored. -->
+<div class="container">
+  <div class="test width-100" style="top: 0px; bottom: 0px"></div>
+</div>
+
+<!-- 100px x 100px, top-left -->
+<div class="container">
+  <div class="test height-100" style="left: 0px;"></div>
+</div>
+
+<!-- 100px x 100px, top, auto-size is ignored. -->
+<div class="container">
+  <div class="test height-100" style="left: 0px; right: 0px;"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+  *intrinsicSizes() {}
+  *layout() {
+    return {autoBlockSize: 100};
+  }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/auto-block-size-flex-ref.html b/css/css-layout-api/auto-block-size-flex-ref.html
new file mode 100644
index 0000000..e71b104
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-flex-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+.container {
+  border: solid 2px;
+  display: flow-root;
+  width: 300px;
+}
+
+.child {
+  background: green;
+  border: solid 2px;
+  box-sizing: border-box;
+  float: left;
+  height: 100px;
+}
+</style>
+
+<div class="container">
+  <div class="child" style="width: 100px;"></div>
+  <div class="child" style="width: 200px;"></div>
+</div>
diff --git a/css/css-layout-api/auto-block-size-flex.https.html b/css/css-layout-api/auto-block-size-flex.https.html
new file mode 100644
index 0000000..fe95c7e
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-flex.https.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-flex-ref.html">
+<meta name="assert" content="This test checks that the flex layout respects the auto-block-size." />
+
+<style>
+.flex {
+  width: 300px;
+  display: flex;
+  border: solid 2px;
+}
+
+.custom {
+  background: red;
+  box-sizing: border-box;
+  border: solid 2px;
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+
+@supports (display: layout(block-size-100)) {
+  .custom-100 {
+    display: layout(block-size-100);
+    background: green;
+  }
+  .custom-50 {
+    display: layout(block-size-50);
+    background: green;
+    flex: 1;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- Tests that floats using an auto block size get positioned correctly. -->
+<div class="flex">
+  <div class="custom custom-100"></div>
+  <div class="custom custom-50"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+  *intrinsicSizes() {}
+  *layout() {
+    return {autoBlockSize: 100};
+  }
+});
+registerLayout('block-size-50', class {
+  *intrinsicSizes() {}
+  *layout() {
+    return {autoBlockSize: 50};
+  }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/auto-block-size-floats-ref.html b/css/css-layout-api/auto-block-size-floats-ref.html
new file mode 100644
index 0000000..368d3d8
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-floats-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<style>
+.container {
+  width: 150px;
+  border: solid 2px;
+}
+
+.left {
+  float: left;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+
+.right {
+  float: right;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+
+<div class="container">
+  <div class="left"></div>
+  <div class="right"></div>
+</div>
diff --git a/css/css-layout-api/auto-block-size-floats.https.html b/css/css-layout-api/auto-block-size-floats.https.html
new file mode 100644
index 0000000..67775eb
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-floats.https.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-floats-ref.html">
+<meta name="assert" content="This test checks that if the layout() is a float, the flow layout respects the auto-block-size." />
+
+<style>
+.test {
+  background: red;
+}
+
+@supports (display: layout(block-size-100)) {
+  .test {
+    display: layout(block-size-100);
+    background: green;
+  }
+}
+
+.container {
+  width: 150px;
+  border: solid 2px;
+}
+
+.left {
+  float: left;
+  width: 100px;
+  writing-mode: horizontal-tb;
+}
+
+.right {
+  float: right;
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- Tests that floats using an auto block size get positioned correctly. -->
+<div class="container">
+  <div class="left test"></div>
+  <div class="right test"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+  *intrinsicSizes() {}
+  *layout() {
+    return {autoBlockSize: 100};
+  }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/auto-block-size-inflow-ref.html b/css/css-layout-api/auto-block-size-inflow-ref.html
new file mode 100644
index 0000000..73f8481
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-inflow-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+.result {
+  margin: 20px 0;
+  background: green;
+}
+</style>
+<div class="result" style="width: 100px; height: 100px;"></div>
+<div class="result" style="width: 100px; height: 150px;"></div>
+<div class="result" style="width: 100px; height: 50px;"></div>
+<div class="result" style="width: 100px; height: 100px;"></div>
+<div class="result" style="width: 150px; height: 100px;"></div>
+<div class="result" style="width: 50px; height: 100px;"></div>
diff --git a/css/css-layout-api/auto-block-size-inflow.https.html b/css/css-layout-api/auto-block-size-inflow.https.html
new file mode 100644
index 0000000..b8bc0d1
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-inflow.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-inflow-ref.html">
+<meta name="assert" content="This test checks that min/max-block-size constraints are applied correctly to a layout()." />
+
+<style>
+.test {
+  margin: 20px 0;
+  background: red;
+}
+
+@supports (display: layout(block-size-100)) {
+  .test {
+    display: layout(block-size-100);
+    background: green;
+  }
+}
+
+.width-100 {
+  width: 100px;
+  writing-mode: horizontal-tb;
+}
+
+.height-100 {
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- 100px x 100px -->
+<div class="test width-100"></div>
+
+<!-- 100px x 150px -->
+<div class="test width-100" style="min-height: 150px"></div>
+
+<!-- 100px x 50px -->
+<div class="test width-100" style="max-height: 50px"></div>
+
+<!-- 100px x 100px -->
+<div class="test height-100"></div>
+
+<!-- 150px x 100px -->
+<div class="test height-100" style="min-width: 150px"></div>
+
+<!-- 50px x 100px -->
+<div class="test height-100" style="max-width: 50px"></div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+  *intrinsicSizes() {}
+  *layout() {
+    return {autoBlockSize: 100};
+  }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/auto-block-size-negative-ref.html b/css/css-layout-api/auto-block-size-negative-ref.html
new file mode 100644
index 0000000..665b5c6
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-negative-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+.result {
+  background: green;
+  border: solid 2px;
+  margin: 20px 0;
+}
+</style>
+
+<div class="result" style="width: 100px; height: 0px;"></div>
+<div class="result" style="width: 0px; height: 100px;"></div>
diff --git a/css/css-layout-api/auto-block-size-negative.https.html b/css/css-layout-api/auto-block-size-negative.https.html
new file mode 100644
index 0000000..4e912ed
--- /dev/null
+++ b/css/css-layout-api/auto-block-size-negative.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-negative-ref.html">
+<meta name="assert" content="This test checks that auto-block-size is correctly clamped to zero." />
+<meta name="assert" content="TODO" />
+
+<style>
+
+.test {
+  background: red;
+  border: solid 2px;
+  margin: 20px 0;
+}
+
+.width-100 {
+  width: 100px;
+  writing-mode: horizontal-tb;
+}
+
+.height-100 {
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+
+@supports (display: layout(block-size-negative)) {
+  .test {
+    display: layout(block-size-negative);
+    background: green;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test width-100"></div>
+<div class="test height-100"></div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-negative', class {
+  *intrinsicSizes() {}
+  *layout() {
+    return {autoBlockSize: -100};
+  }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/box-tree-registered-ref.html b/css/css-layout-api/box-tree-registered-ref.html
new file mode 100644
index 0000000..0ba9862
--- /dev/null
+++ b/css/css-layout-api/box-tree-registered-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<style>
+.container {
+  margin: 20px 0;
+  border: solid 2px;
+  width: 100px;
+}
+
+.pink {
+  background: hotpink;
+}
+</style>
+
+<div class="container">
+  <div class="pink" style="width: 50px; height: 100px;"></div>
+</div>
+
+<div class="container">
+  <div class="pink" style="width: 100px; height: 100px;"></div>
+</div>
diff --git a/css/css-layout-api/box-tree-registered.https.html b/css/css-layout-api/box-tree-registered.https.html
new file mode 100644
index 0000000..94790c1
--- /dev/null
+++ b/css/css-layout-api/box-tree-registered.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-api-box-tree">
+<link rel="match" href="box-tree-registered-ref.html">
+<meta name="assert" content="This test checks that a registered layout() has children which are blockified and new formatting contexts." />
+<style>
+@supports (display: layout(registered)) {
+  .test {
+    display: layout(registered);
+  }
+}
+
+.container {
+  margin: 20px 0;
+  border: solid 2px;
+  width: 100px;
+}
+
+.float {
+  float: left;
+  width: 50%;
+  height: 50px;
+}
+
+.pink {
+  background: hotpink;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="container">
+  <!-- This tests that the "layout()" grandchildren floats within the don't interact with each other. -->
+  <div class="test">
+    <div class="inflow">
+      <div class="float pink"></div>
+    </div>
+    <div class="inflow">
+      <div class="float pink"></div>
+    </div>
+  </div>
+</div>
+
+<div class="container">
+  <!-- This tests that the "layout()" children margins interact as if they are new formatting contexts. -->
+  <div class="test">
+    <div class="inflow pink">
+      <div style="margin-bottom: 50px;"></div>
+    </div>
+    <div class="inflow pink">
+      <div style="margin-top: 50px;"></div>
+    </div>
+  </div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('registered', class {
+  *intrinsicSizes() {}
+  *layout() { throw Error(); }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/box-tree-unregistered-ref.html b/css/css-layout-api/box-tree-unregistered-ref.html
new file mode 100644
index 0000000..fc43ad2
--- /dev/null
+++ b/css/css-layout-api/box-tree-unregistered-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<style>
+.container {
+  margin: 20px 0;
+  border: solid 2px;
+  width: 100px;
+}
+
+.inflow {
+  height: 40px;
+}
+</style>
+
+<div class="container">
+  <div class="inflow"></div>
+  <div class="inflow" style="background: hotpink"></div>
+  <div class="inflow"></div>
+</div>
+
+<div class="container">
+  <div class="inflow"></div>
+  <div class="inflow" style="background: hotpink"></div>
+  <div class="inflow"></div>
+</div>
+
+<div class="container">
+  <div class="inflow" style="background: hotpink"></div>
+  <div class="inflow" style="background: green"></div>
+</div>
diff --git a/css/css-layout-api/box-tree-unregistered.https.html b/css/css-layout-api/box-tree-unregistered.https.html
new file mode 100644
index 0000000..48d7aa7
--- /dev/null
+++ b/css/css-layout-api/box-tree-unregistered.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-api-box-tree">
+<link rel="match" href="box-tree-unregistered-ref.html">
+<meta name="assert" content="This test checks that an unregistered layout() works the same as a normal flow-root." />
+<style>
+@supports (display: layout(unregistered)) {
+  .test {
+    display: layout(unregistered);
+  }
+}
+
+.container {
+  margin: 20px 0;
+  border: solid 2px;
+  width: 100px;
+}
+
+.float {
+  float: left;
+  width: 100%;
+  height: 40px;
+  background: hotpink;
+}
+
+.inflow {
+  height: 40px;
+  background: hotpink;
+}
+</style>
+
+<div class="container">
+  <!-- This tests that the "layout()" margins don't collapse with its children. -->
+  <div class="test" style="margin: 20px 0">
+    <div class="inflow" style="margin: 20px 0"></div>
+  </div>
+</div>
+
+<div class="container">
+  <!-- This tests that the "layout()" grows to fit child floats. -->
+  <div class="test">
+    <div class="float" style="margin: 40px 0"></div>
+  </div>
+</div>
+
+<div class="container">
+  <!-- This tests that a float does not intrude into "layout()" box. -->
+  <div class="float"></div>
+  <div class="test" style="width: 100%; height: 40px; background: green;"></div>
+</div>
diff --git a/css/css-layout-api/computed-style-layout-function.https.html b/css/css-layout-api/computed-style-layout-function.https.html
new file mode 100644
index 0000000..d115009
--- /dev/null
+++ b/css/css-layout-api/computed-style-layout-function.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#valdef-display-layout">
+<meta name="assert" content="This test checks that a layout() function parses correctly and serializes correctly from computed style." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#test1 { display: layout(test1); }
+#test2 { display: layout(); }
+#test3 { display: layout(test3, invalid); }
+#test4 { --display: layout(test4); display: var(--display); }
+</style>
+
+<div id=test1></div>
+<div id=test2></div>
+<div id=test3></div>
+<div id=test4></div>
+<div id=test5></div>
+<script>
+test(function() {
+  const test1 = document.getElementById('test1');
+  assert_equals(getComputedStyle(test1).display, 'layout(test1)');
+});
+
+test(function() {
+  // layout() should fail to parse.
+  const test2 = document.getElementById('test2');
+  assert_equals(getComputedStyle(test2).display, 'block');
+});
+
+test(function() {
+  // layout(test3, invalid) should fail to parse.
+  const test3 = document.getElementById('test3');
+  assert_equals(getComputedStyle(test3).display, 'block');
+});
+
+test(function() {
+  // Setting via a custom property should work.
+  const test4 = document.getElementById('test4');
+  assert_equals(getComputedStyle(test4).display, 'layout(test4)');
+});
+
+test(function() {
+  // Setting the inline style should reflect in the computed style.
+  const test5 = document.getElementById('test5');
+  assert_equals(getComputedStyle(test5).display, 'block');
+
+  test5.style.display = 'layout(test5)';
+  assert_equals(getComputedStyle(test5).display, 'layout(test5)');
+});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-absolute-left-right.https.html b/css/css-layout-api/constraints-fixed-inline-size-absolute-left-right.https.html
new file mode 100644
index 0000000..6b3662a
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-absolute-left-right.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  position: relative;
+  width: 120px;
+}
+
+.test {
+  background: red;
+  position: absolute;
+  left: 0px;
+  right: 20px;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-absolute-top-bottom-vrl.https.html b/css/css-layout-api/constraints-fixed-inline-size-absolute-top-bottom-vrl.https.html
new file mode 100644
index 0000000..632a5dd
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-absolute-top-bottom-vrl.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  position: relative;
+  height: 120px;
+}
+
+.test {
+  background: red;
+  position: absolute;
+  left: 0px;
+  top: 0px;
+  bottom: 20px;
+  writing-mode: vertical-rl;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-block-auto-avoid-floats-vlr.https.html b/css/css-layout-api/constraints-fixed-inline-size-block-auto-avoid-floats-vlr.https.html
new file mode 100644
index 0000000..a8f4dd3
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-block-auto-avoid-floats-vlr.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  height: 200px;
+  writing-mode: vertical-lr;
+}
+
+.float {
+  float: right;
+  width: 20px;
+  height: 100px;
+}
+
+.test {
+  background: red;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="float"></div>
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-block-auto-avoid-floats.https.html b/css/css-layout-api/constraints-fixed-inline-size-block-auto-avoid-floats.https.html
new file mode 100644
index 0000000..2eb6197
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-block-auto-avoid-floats.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  width: 200px;
+}
+
+.float {
+  float: right;
+  width: 100px;
+  height: 20px;
+}
+
+.test {
+  background: red;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="float"></div>
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-block-auto-vlr.https.html b/css/css-layout-api/constraints-fixed-inline-size-block-auto-vlr.https.html
new file mode 100644
index 0000000..751ea5e
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-block-auto-vlr.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  height: 120px;
+  writing-mode: vertical-lr;
+}
+
+.test {
+  margin-bottom: 20px;
+  background: red;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-block-auto.https.html b/css/css-layout-api/constraints-fixed-inline-size-block-auto.https.html
new file mode 100644
index 0000000..1458a8b
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-block-auto.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  width: 120px;
+}
+
+.test {
+  margin-right: 20px;
+  background: red;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-fixed-vrl.https.html b/css/css-layout-api/constraints-fixed-inline-size-fixed-vrl.https.html
new file mode 100644
index 0000000..39ea818
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-fixed-vrl.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+.test {
+  background: red;
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-fixed.https.html b/css/css-layout-api/constraints-fixed-inline-size-fixed.https.html
new file mode 100644
index 0000000..6434d33
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-fixed.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+.test {
+  background: red;
+  width: 100px;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-flex-grow-column-vrl.https.html b/css/css-layout-api/constraints-fixed-inline-size-flex-grow-column-vrl.https.html
new file mode 100644
index 0000000..263d45e
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-flex-grow-column-vrl.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  display: flex;
+  flex-flow: column;
+  width: 100px;
+  height: 100px;
+}
+
+.test {
+  background: red;
+  flex-grow: 1;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-flex-grow.https.html b/css/css-layout-api/constraints-fixed-inline-size-flex-grow.https.html
new file mode 100644
index 0000000..d2589e6
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-flex-grow.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  display: flex;
+  width: 100px;
+}
+
+.test {
+  background: red;
+  flex-grow: 1;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-grid.https.html b/css/css-layout-api/constraints-fixed-inline-size-grid.https.html
new file mode 100644
index 0000000..94eca6b
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-grid.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  display: grid;
+  grid: auto-flow / 100px;
+}
+
+.test {
+  background: red;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-percentage-vlr.https.html b/css/css-layout-api/constraints-fixed-inline-size-percentage-vlr.https.html
new file mode 100644
index 0000000..0efabcb
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-percentage-vlr.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  height: 200px;
+  writing-mode: vertical-lr;
+}
+
+.test {
+  background: red;
+  height: 50%;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-percentage.https.html b/css/css-layout-api/constraints-fixed-inline-size-percentage.https.html
new file mode 100644
index 0000000..4b5a567
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-percentage.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraints-fixedinlinesize">
+<link rel="match" href="constraints-fixed-inline-size-ref.html">
+<meta name="assert" content="This test checks that LayoutConstraints#fixedInlineSize is passed into the layout function correctly." />
+<style>
+body {
+  width: 200px;
+}
+
+.test {
+  background: red;
+  width: 50%;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/constraints-fixed-inline-size.js'});
+</script>
diff --git a/css/css-layout-api/constraints-fixed-inline-size-ref.html b/css/css-layout-api/constraints-fixed-inline-size-ref.html
new file mode 100644
index 0000000..e5727c0
--- /dev/null
+++ b/css/css-layout-api/constraints-fixed-inline-size-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+.result {
+  background: green;
+  height: 100px;
+  width: 100px;
+}
+</style>
+
+<div class="result"></div>
diff --git a/css/css-layout-api/crash-multicol.https.html b/css/css-layout-api/crash-multicol.https.html
new file mode 100644
index 0000000..dbcbd18
--- /dev/null
+++ b/css/css-layout-api/crash-multicol.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/">
+<meta name="assert" content="This test checks that browser doesn't crash when the layout() function is used with multicol." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<style>
+#test {
+  display: layout('test');
+  columns: 2;
+}
+</style>
+
+<div id="test"></div>
+
+<script>
+promise_test(async function() {
+  await importWorklet(CSS.layoutWorklet, {url: 'support/layout-position-child-worklet.js'});
+});
+</script>
diff --git a/css/css-layout-api/fallback-constructor-error.https.html b/css/css-layout-api/fallback-constructor-error.https.html
new file mode 100644
index 0000000..590a9d0
--- /dev/null
+++ b/css/css-layout-api/fallback-constructor-error.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#get-a-layout-class-instance">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class with a throwing constructor will fallback to block layout." />
+<style>
+.test {
+  background: red;
+  border: solid 2px;
+  width: 100px;
+}
+
+.float {
+  float: left;
+  width: 50%;
+  height: 100px;
+}
+
+.fc {
+  display: flow-root;
+  height: 100px;
+}
+
+@supports (display: layout(throwing-ctor)) {
+  .test {
+    display: layout(throwing-ctor);
+    background: green;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- This tests that when the "layout()" constructor fails, it will fallback to block layout. -->
+<div class="test">
+  <div class="float"></div>
+  <div class="fc"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('throwing-ctor', class {
+  constructor() { throw Error('fail!'); }
+  *intrinsicSizes() {}
+  *layout() {}
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/fallback-layout-error.https.html b/css/css-layout-api/fallback-layout-error.https.html
new file mode 100644
index 0000000..54ea80b
--- /dev/null
+++ b/css/css-layout-api/fallback-layout-error.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class with a throwing layout function will fallback to block layout." />
+<style>
+.test {
+  background: red;
+  border: solid 2px;
+  width: 100px;
+}
+
+.float {
+  float: left;
+  width: 50%;
+  height: 100px;
+}
+
+.fc {
+  display: flow-root;
+  height: 100px;
+}
+
+@supports (display: layout(throwing-layout)) {
+  .test {
+    display: layout(throwing-layout);
+    background: green;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- This tests that when the "layout()" layout function fails, it will fallback to block layout. -->
+<div class="test">
+  <div class="float"></div>
+  <div class="fc"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('throwing-layout', class {
+  *intrinsicSizes() {}
+  *layout() { throw Error('fail!'); }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/fallback-layout-invalid-child.https.html b/css/css-layout-api/fallback-layout-invalid-child.https.html
new file mode 100644
index 0000000..19b4188
--- /dev/null
+++ b/css/css-layout-api/fallback-layout-invalid-child.https.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class performing layout on an invalid child will fallback to block layout." />
+<style>
+.test {
+  background: red;
+  border: solid 2px;
+  width: 100px;
+}
+
+.test > div {
+  height: 100px;
+}
+
+@supports (display: layout(bad-child-layout)) {
+  .test {
+    display: layout(bad-child-layout);
+    background: green;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('bad-child-layout', class {
+  static get inputProperties() { return ['--fail']; }
+
+  *intrinsicSizes() {}
+  *layout(children, _, __, styleMap) {
+    if (styleMap.get('--fail').toString() !== 'true') {
+      this.child = children[0];
+    }
+
+    // Try to perform layout on the child. If its invalid (we skipped the if
+    // statement above) we should fallback to block layout.
+    const fragment = yield this.child.layoutNextFragment({});
+
+    return {autoBlockSize: 0, childFragments: [fragment]};
+  }
+});
+</script>
+
+<script>
+function raf() {
+  return new Promise((resolve) => {
+    requestAnimationFrame(() => {
+      resolve();
+    });
+  });
+}
+
+(async function() {
+  if (typeof CSS.layoutWorklet === 'undefined') {
+    takeScreenshot();
+    return;
+  }
+
+  await importWorklet(CSS.layoutWorklet, document.getElementById('code').textContent);
+
+  // Ensure that all instances have a child to perform an invalid layout upon.
+  const test = document.getElementsByClassName('test')[0];
+  for (let i = 0; i < 100; i++) {
+    test.innerHTML = '<div><div>';
+    await raf();
+  }
+
+  // The next layout should mean that we will fallback to block.
+  test.innerHTML = '<div></div>';
+  test.style.setProperty('--fail', 'true');
+
+  // Finish up the test.
+  await raf();
+  await raf();
+  takeScreenshot();
+})();
+</script>
+</html>
diff --git a/css/css-layout-api/fallback-layout-invalid-fragment-request.https.html b/css/css-layout-api/fallback-layout-invalid-fragment-request.https.html
new file mode 100644
index 0000000..ccbf38b
--- /dev/null
+++ b/css/css-layout-api/fallback-layout-invalid-fragment-request.https.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class performing layout on an invalid fragment request will fallback to block layout." />
+<style>
+.test {
+  background: red;
+  border: solid 2px;
+  width: 100px;
+}
+
+.test > div {
+  height: 100px;
+}
+
+@supports (display: layout(bad-request)) {
+  .test {
+    display: layout(bad-request);
+    background: green;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('bad-request', class {
+  static get inputProperties() { return ['--fail']; }
+
+  *intrinsicSizes() {}
+  *layout(children, _, __, styleMap) {
+    if (styleMap.get('--fail').toString() !== 'true') {
+      this.request = children[0].layoutNextFragment({});
+    }
+
+    // Try to perform layout on the child. If its invalid (we skipped the if
+    // statement above) we should fallback to block layout.
+    const childFragments = yield [this.request];
+
+    return {autoBlockSize: 0, childFragments};
+  }
+});
+</script>
+
+<script>
+function raf() {
+  return new Promise((resolve) => {
+    requestAnimationFrame(() => {
+      resolve();
+    });
+  });
+}
+
+(async function() {
+  if (typeof CSS.layoutWorklet === 'undefined') {
+    takeScreenshot();
+    return;
+  }
+
+  await importWorklet(CSS.layoutWorklet, document.getElementById('code').textContent);
+
+  // Ensure that all instances have a child to perform an invalid layout upon.
+  const test = document.getElementsByClassName('test')[0];
+  for (let i = 0; i < 100; i++) {
+    test.innerHTML = '<div><div>';
+    await raf();
+  }
+
+  // The next layout should mean that we will fallback to block.
+  test.innerHTML = '<div></div>';
+  test.style.setProperty('--fail', 'true');
+
+  // Finish up the test.
+  await raf();
+  await raf();
+  takeScreenshot();
+})();
+</script>
+</html>
diff --git a/css/css-layout-api/fallback-layout-invalid-fragment.https.html b/css/css-layout-api/fallback-layout-invalid-fragment.https.html
new file mode 100644
index 0000000..e4253ff
--- /dev/null
+++ b/css/css-layout-api/fallback-layout-invalid-fragment.https.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class returning an invalid fragment will fallback to block layout." />
+<style>
+.test {
+  background: red;
+  border: solid 2px;
+  width: 100px;
+}
+
+.test > div {
+  height: 100px;
+}
+
+@supports (display: layout(bad-request)) {
+  .test {
+    display: layout(bad-request);
+    background: green;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('bad-request', class {
+  static get inputProperties() { return ['--fail']; }
+
+  *intrinsicSizes() {}
+  *layout(children, _, __, styleMap) {
+    if (styleMap.get('--fail').toString() !== 'true') {
+      this.fragment = yield children[0].layoutNextFragment({});
+    }
+
+    // Return, if the fragment is invalid (we skipped the if statement above)
+    // we should fallback to block layout.
+    return {autoBlockSize: 0, childFragments: [this.fragment]};
+  }
+});
+</script>
+
+<script>
+function raf() {
+  return new Promise((resolve) => {
+    requestAnimationFrame(() => {
+      resolve();
+    });
+  });
+}
+
+(async function() {
+  if (typeof CSS.layoutWorklet === 'undefined') {
+    takeScreenshot();
+    return;
+  }
+
+  await importWorklet(CSS.layoutWorklet, document.getElementById('code').textContent);
+
+  // Ensure that all instances have a child to perform an invalid layout upon.
+  const test = document.getElementsByClassName('test')[0];
+  for (let i = 0; i < 100; i++) {
+    test.innerHTML = '<div><div>';
+    await raf();
+  }
+
+  // The next layout should mean that we will fallback to block.
+  test.innerHTML = '<div></div>';
+  test.style.setProperty('--fail', 'true');
+
+  // Finish up the test.
+  await raf();
+  await raf();
+  takeScreenshot();
+})();
+</script>
+</html>
diff --git a/css/css-layout-api/fallback-layout-return.https.html b/css/css-layout-api/fallback-layout-return.https.html
new file mode 100644
index 0000000..42ccfc5
--- /dev/null
+++ b/css/css-layout-api/fallback-layout-return.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class with the layout function returning a bad value will fallback to block layout." />
+<style>
+.test {
+  background: red;
+  border: solid 2px;
+  width: 100px;
+}
+
+.float {
+  float: left;
+  width: 50%;
+  height: 100px;
+}
+
+.fc {
+  display: flow-root;
+  height: 100px;
+}
+
+@supports (display: layout(bad-return)) {
+  .test {
+    display: layout(bad-return);
+    background: green;
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- This tests that when the "layout()" layout function returns a bad value, it will fallback to block layout. -->
+<div class="test">
+  <div class="float"></div>
+  <div class="fc"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('bad-return', class {
+  *intrinsicSizes() {}
+  *layout() { return 42; }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/fallback-ref.html b/css/css-layout-api/fallback-ref.html
new file mode 100644
index 0000000..63bb91e
--- /dev/null
+++ b/css/css-layout-api/fallback-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+.result {
+  background: green;
+  border: solid 2px;
+  height: 100px;
+  width: 100px;
+}
+</style>
+
+<div class="result"></div>
diff --git a/css/css-layout-api/inline-style-layout-function.https.html b/css/css-layout-api/inline-style-layout-function.https.html
new file mode 100644
index 0000000..f11405b
--- /dev/null
+++ b/css/css-layout-api/inline-style-layout-function.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#valdef-display-layout">
+<meta name="assert" content="This test checks that a layout() function is parses and serializes correctly from inline style." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=test1></div>
+<div id=test2></div>
+<div id=test3></div>
+<script>
+test(function() {
+  const test1 = document.getElementById('test1');
+  assert_equals(test1.style.display, '');
+
+  test1.style.display = 'layout(test1)';
+  assert_equals(test1.style.display, 'layout(test1)');
+});
+
+test(function() {
+  const test2 = document.getElementById('test2');
+  assert_equals(test2.style.display, '');
+
+  // layout() should fail to parse.
+  test2.style.display = 'layout()';
+  assert_equals(test2.style.display, '');
+});
+
+test(function() {
+  const test3 = document.getElementById('test3');
+  assert_equals(test3.style.display, '');
+
+  // layout(test3, invalid) should fail to parse.
+  test3.style.display = 'layout(test3, invalid)';
+  assert_equals(test3.style.display, '');
+});
+</script>
diff --git a/css/css-layout-api/layout-child-absolute.https.html b/css/css-layout-api/layout-child-absolute.https.html
new file mode 100644
index 0000000..73c0bf7
--- /dev/null
+++ b/css/css-layout-api/layout-child-absolute.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that absolute children don't appear in the children array." />
+
+<style>
+.test {
+  --child-expected: ["2"];
+
+  background: red;
+  margin: 10px;
+  width: 100px;
+}
+
+.absolute {
+  position: absolute;
+  visibility: hidden;
+  --child: 1;
+}
+
+.inflow {
+  visibility: hidden;
+  --child: 2;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="absolute"></div>
+  <div class="inflow"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/layout-child-before-after.https.html b/css/css-layout-api/layout-child-before-after.https.html
new file mode 100644
index 0000000..87d1219
--- /dev/null
+++ b/css/css-layout-api/layout-child-before-after.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that boxes created by ::before/::after appear as children." />
+
+<style>
+.test {
+  --child-expected: ["1", "2", "3"];
+
+  background: red;
+  margin: 10px;
+  width: 100px;
+}
+
+.test::before {
+  visibility: hidden;
+  content: 'before';
+  --child: 1;
+}
+
+.inflow {
+  visibility: hidden;
+  --child: 2;
+}
+
+.test::after {
+  visibility: hidden;
+  content: 'after';
+  --child: 3;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="inflow"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/layout-child-fixed.https.html b/css/css-layout-api/layout-child-fixed.https.html
new file mode 100644
index 0000000..9de556e
--- /dev/null
+++ b/css/css-layout-api/layout-child-fixed.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that fixed children don't appear in the children array." />
+
+<style>
+.test {
+  --child-expected: ["2"];
+
+  background: red;
+  margin: 10px;
+  width: 100px;
+}
+
+.fixed {
+  position: fixed;
+  visibility: hidden;
+  --child: 1;
+}
+
+.inflow {
+  visibility: hidden;
+  --child: 2;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="fixed"></div>
+  <div class="inflow"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/layout-child-float.https.html b/css/css-layout-api/layout-child-float.https.html
new file mode 100644
index 0000000..0465b18
--- /dev/null
+++ b/css/css-layout-api/layout-child-float.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that float children appear in the children array." />
+
+<style>
+.test {
+  --child-expected: ["1", "2"];
+
+  background: red;
+  margin: 10px;
+  width: 100px;
+}
+
+.float {
+  float: right;
+  visibility: hidden;
+  --child: 1;
+}
+
+.inflow {
+  visibility: hidden;
+  --child: 2;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="float"></div>
+  <div class="inflow"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/layout-child-inflow.https.html b/css/css-layout-api/layout-child-inflow.https.html
new file mode 100644
index 0000000..7c5aa29
--- /dev/null
+++ b/css/css-layout-api/layout-child-inflow.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that regular inflow children appear as children." />
+
+<style>
+.test {
+  --child-expected: ["1", "2"];
+
+  background: red;
+  margin: 10px;
+  width: 100px;
+}
+
+.inflow-1 {
+  visibility: hidden;
+  --child: 1;
+}
+
+.inflow-2 {
+  visibility: hidden;
+  --child: 2;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="inflow-1"></div>
+  <div class="inflow-2"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/layout-child-inlines.https.html b/css/css-layout-api/layout-child-inlines.https.html
new file mode 100644
index 0000000..e1d65ab
--- /dev/null
+++ b/css/css-layout-api/layout-child-inlines.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that inline children are correctly blockified or wrapped in anonymous boxes." />
+
+<style>
+/* We have a wrapper in this test to ensure that any text that is positioned
+ * slightly outside the "test" box doesn't affect the rendering.
+ * This wrapper has a 10px inline padding which does the trick. */
+.wrapper {
+  background: green;
+  padding: 0 10px;
+  margin: 10px;
+  width: 80px;
+}
+
+.test {
+  --child-expected: ["1", "default", "3", "4", "5"];
+
+  background: red;
+  color: green;
+  width: 80px;
+  --child: default;
+}
+
+.inflow {
+  visibility: hidden;
+  --child: 3;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="wrapper">
+  <div class="test">
+    <span style="--child: 1;">Text,</span> more text
+    <div class="inflow"></div>
+    <span style="--child: 4;">Text,
+       <div>block!</div>
+    </span>
+    <span style="--child: 5;">Other text</span>
+  </div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/layout-child-ref.html b/css/css-layout-api/layout-child-ref.html
new file mode 100644
index 0000000..641dda1
--- /dev/null
+++ b/css/css-layout-api/layout-child-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+.result {
+  background: green;
+  margin: 10px;
+  height: 100px;
+  width: 100px;
+}
+</style>
+<div class="result"></div>
diff --git a/css/css-layout-api/layout-child-text.https.html b/css/css-layout-api/layout-child-text.https.html
new file mode 100644
index 0000000..0757f5d
--- /dev/null
+++ b/css/css-layout-api/layout-child-text.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that text children are correctly blockified." />
+
+<style>
+/* We have a wrapper in this test to ensure that any text that is positioned
+ * slightly outside the "test" box doesn't affect the rendering.
+ * This wrapper has a 10px inline padding which does the trick. */
+.wrapper {
+  background: green;
+  padding: 0 10px;
+  margin: 10px;
+  width: 80px;
+}
+
+.test {
+  --child-expected: ["default", "2", "default"];
+
+  background: red;
+  color: green;
+  width: 80px;
+  --child: default;
+}
+
+.inflow {
+  visibility: hidden;
+  --child: 2;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="wrapper">
+  <div class="test">
+    Text text text
+    <div class="inflow"></div>
+    Text text text
+  </div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/perform-child-layout-fixed-block-size-vrl.https.html b/css/css-layout-api/perform-child-layout-fixed-block-size-vrl.https.html
new file mode 100644
index 0000000..1fbfec9
--- /dev/null
+++ b/css/css-layout-api/perform-child-layout-fixed-block-size-vrl.https.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-fixedinlinesize">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that fixing the block size of children works as expected." />
+
+<style>
+.test {
+  writing-mode: vertical-rl;
+  background: red;
+  margin: 10px;
+  height: 100px;
+}
+
+.htb {
+  writing-mode: horizontal-tb;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-block-size: 10;
+
+  --inline-size-expected: 2;
+  --block-size-expected: 10;
+}
+
+.vrl {
+  writing-mode: vertical-rl;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-block-size: 10;
+
+  --inline-size-expected: 2;
+  --block-size-expected: 10;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="htb"></div>
+  <div class="vrl"></div>
+  <!-- min/max-width should have no effect, fixedBlockSize wins. -->
+  <div class="htb" style="max-width: 5px;"></div>
+  <div class="vrl" style="max-width: 5px;"></div>
+  <div class="htb" style="min-width: 15px;"></div>
+  <div class="vrl" style="min-width: 15px;"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'});
+</script>
diff --git a/css/css-layout-api/perform-child-layout-fixed-block-size.https.html b/css/css-layout-api/perform-child-layout-fixed-block-size.https.html
new file mode 100644
index 0000000..a158840
--- /dev/null
+++ b/css/css-layout-api/perform-child-layout-fixed-block-size.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-fixedblocksize">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that fixing the block size of children works as expected." />
+
+<style>
+.test {
+  background: red;
+  margin: 10px;
+  width: 100px;
+}
+
+.htb {
+  writing-mode: horizontal-tb;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-block-size: 10;
+
+  --inline-size-expected: 3;
+  --block-size-expected: 10;
+}
+
+.vrl {
+  writing-mode: vertical-rl;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-block-size: 10;
+
+  --inline-size-expected: 3;
+  --block-size-expected: 10;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="htb"></div>
+  <div class="vrl"></div>
+  <!-- min/max-height should have no effect, fixedBlockSize wins. -->
+  <div class="htb" style="max-height: 5px;"></div>
+  <div class="vrl" style="max-height: 5px;"></div>
+  <div class="htb" style="min-height: 15px;"></div>
+  <div class="vrl" style="min-height: 15px;"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'});
+</script>
diff --git a/css/css-layout-api/perform-child-layout-fixed-inline-size-vrl.https.html b/css/css-layout-api/perform-child-layout-fixed-inline-size-vrl.https.html
new file mode 100644
index 0000000..8fab5e4
--- /dev/null
+++ b/css/css-layout-api/perform-child-layout-fixed-inline-size-vrl.https.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-fixedinlinesize">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that fixing the inline size of children works as expected." />
+
+<style>
+.test {
+  writing-mode: vertical-rl;
+  background: red;
+  margin: 10px;
+  height: 100px;
+}
+
+.htb {
+  writing-mode: horizontal-tb;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-inline-size: 10;
+
+  --inline-size-expected: 10;
+  --block-size-expected: 3;
+}
+
+.vrl {
+  writing-mode: vertical-rl;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-inline-size: 10;
+
+  --inline-size-expected: 10;
+  --block-size-expected: 3;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="htb"></div>
+  <div class="vrl"></div>
+  <!-- min/max-height should have no effect, fixedInlineSize wins. -->
+  <div class="htb" style="max-height: 5px;"></div>
+  <div class="vrl" style="max-height: 5px;"></div>
+  <div class="htb" style="min-height: 15px;"></div>
+  <div class="vrl" style="min-height: 15px;"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'});
+</script>
diff --git a/css/css-layout-api/perform-child-layout-fixed-inline-size.https.html b/css/css-layout-api/perform-child-layout-fixed-inline-size.https.html
new file mode 100644
index 0000000..79d2aca
--- /dev/null
+++ b/css/css-layout-api/perform-child-layout-fixed-inline-size.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-fixedinlinesize">
+<link rel="match" href="layout-child-ref.html">
+<meta name="assert" content="This test checks that fixing the inline size of children works as expected." />
+
+<style>
+.test {
+  background: red;
+  margin: 10px;
+  width: 100px;
+}
+
+.htb {
+  writing-mode: horizontal-tb;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-inline-size: 10;
+
+  --inline-size-expected: 10;
+  --block-size-expected: 2;
+}
+
+.vrl {
+  writing-mode: vertical-rl;
+  visibility: hidden;
+  width: 3px;
+  height: 2px;
+
+  --fixed-inline-size: 10;
+
+  --inline-size-expected: 10;
+  --block-size-expected: 2;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="htb"></div>
+  <div class="vrl"></div>
+  <!-- min/max-width should have no effect, fixedInlineSize wins. -->
+  <div class="htb" style="max-width: 5px;"></div>
+  <div class="vrl" style="max-width: 5px;"></div>
+  <div class="htb" style="min-width: 15px;"></div>
+  <div class="vrl" style="min-width: 15px;"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'});
+</script>
diff --git a/css/css-layout-api/position-fragment-htb-ltr.https.html b/css/css-layout-api/position-fragment-htb-ltr.https.html
new file mode 100644
index 0000000..b09cb70
--- /dev/null
+++ b/css/css-layout-api/position-fragment-htb-ltr.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layoutfragment">
+<link rel="match" href="position-fragment-ref.html">
+<meta name="assert" content="This test checks that child fragments get positioned correctly." />
+<style>
+.test {
+  background: red;
+  width: 100px;
+  height: 100px;
+}
+
+.test {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+}
+
+.child-1 {
+  background: rebeccapurple;
+  width: 10px;
+  height: 20px;
+
+  --inline-offset: 5;
+  --block-offset: 25;
+}
+
+.child-2 {
+  writing-mode: vertical-rl;
+  background: rebeccapurple;
+  width: 15px;
+  height: 25px;
+
+  --inline-offset: 50;
+  --block-offset: 60;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="child-1"></div>
+  <div class="child-2"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-position-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/position-fragment-htb-rtl.https.html b/css/css-layout-api/position-fragment-htb-rtl.https.html
new file mode 100644
index 0000000..2d65b8c
--- /dev/null
+++ b/css/css-layout-api/position-fragment-htb-rtl.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layoutfragment">
+<link rel="match" href="position-fragment-ref.html">
+<meta name="assert" content="This test checks that child fragments get positioned correctly." />
+<style>
+.test {
+  background: red;
+  width: 100px;
+  height: 100px;
+}
+
+.test {
+  writing-mode: horizontal-tb;
+  direction: rtl;
+}
+
+.child-1 {
+  background: rebeccapurple;
+  width: 10px;
+  height: 20px;
+
+  --inline-offset: 85;
+  --block-offset: 25;
+}
+
+.child-2 {
+  writing-mode: vertical-rl;
+  background: rebeccapurple;
+  width: 15px;
+  height: 25px;
+
+  --inline-offset: 35;
+  --block-offset: 60;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="child-1"></div>
+  <div class="child-2"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-position-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/position-fragment-ref.html b/css/css-layout-api/position-fragment-ref.html
new file mode 100644
index 0000000..4ce0a6e
--- /dev/null
+++ b/css/css-layout-api/position-fragment-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<style>
+.result {
+  position: relative;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+
+.result-child-1 {
+  background: rebeccapurple;
+  width: 10px;
+  height: 20px;
+
+  position: absolute;
+  top: 25px;
+  left: 5px;
+}
+
+.result-child-2 {
+  background: rebeccapurple;
+  width: 15px;
+  height: 25px;
+
+  position: absolute;
+  top: 60px;
+  left: 50px;
+}
+</style>
+<div class="result">
+  <div class="result-child-1"></div>
+  <div class="result-child-2"></div>
+</div>
diff --git a/css/css-layout-api/position-fragment-vlr-ltr.https.html b/css/css-layout-api/position-fragment-vlr-ltr.https.html
new file mode 100644
index 0000000..62a5980
--- /dev/null
+++ b/css/css-layout-api/position-fragment-vlr-ltr.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layoutfragment">
+<link rel="match" href="position-fragment-ref.html">
+<meta name="assert" content="This test checks that child fragments get positioned correctly." />
+<style>
+.test {
+  background: red;
+  width: 100px;
+  height: 100px;
+}
+
+.test {
+  writing-mode: vertical-lr;
+  direction: ltr;
+}
+
+.child-1 {
+  background: rebeccapurple;
+  width: 10px;
+  height: 20px;
+
+  --inline-offset: 25;
+  --block-offset: 5;
+}
+
+.child-2 {
+  writing-mode: vertical-rl;
+  background: rebeccapurple;
+  width: 15px;
+  height: 25px;
+
+  --inline-offset: 60;
+  --block-offset: 50;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="child-1"></div>
+  <div class="child-2"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-position-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/position-fragment-vlr-rtl.https.html b/css/css-layout-api/position-fragment-vlr-rtl.https.html
new file mode 100644
index 0000000..a9983b1
--- /dev/null
+++ b/css/css-layout-api/position-fragment-vlr-rtl.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layoutfragment">
+<link rel="match" href="position-fragment-ref.html">
+<meta name="assert" content="This test checks that child fragments get positioned correctly." />
+<style>
+.test {
+  background: red;
+  width: 100px;
+  height: 100px;
+}
+
+.test {
+  writing-mode: vertical-lr;
+  direction: rtl;
+}
+
+.child-1 {
+  background: rebeccapurple;
+  width: 10px;
+  height: 20px;
+
+  --inline-offset: 55;
+  --block-offset: 5;
+}
+
+.child-2 {
+  writing-mode: vertical-rl;
+  background: rebeccapurple;
+  width: 15px;
+  height: 25px;
+
+  --inline-offset: 15;
+  --block-offset: 50;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="child-1"></div>
+  <div class="child-2"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-position-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/position-fragment-vrl-ltr.https.html b/css/css-layout-api/position-fragment-vrl-ltr.https.html
new file mode 100644
index 0000000..0901285
--- /dev/null
+++ b/css/css-layout-api/position-fragment-vrl-ltr.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layoutfragment">
+<link rel="match" href="position-fragment-ref.html">
+<meta name="assert" content="This test checks that child fragments get positioned correctly." />
+<style>
+.test {
+  background: red;
+  width: 100px;
+  height: 100px;
+}
+
+.test {
+  writing-mode: vertical-rl;
+  direction: ltr;
+}
+
+.child-1 {
+  background: rebeccapurple;
+  width: 10px;
+  height: 20px;
+
+  --inline-offset: 25;
+  --block-offset: 85;
+}
+
+.child-2 {
+  writing-mode: vertical-rl;
+  background: rebeccapurple;
+  width: 15px;
+  height: 25px;
+
+  --inline-offset: 60;
+  --block-offset: 35;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="child-1"></div>
+  <div class="child-2"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-position-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/position-fragment-vrl-rtl.https.html b/css/css-layout-api/position-fragment-vrl-rtl.https.html
new file mode 100644
index 0000000..e16f26b
--- /dev/null
+++ b/css/css-layout-api/position-fragment-vrl-rtl.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layoutfragment">
+<link rel="match" href="position-fragment-ref.html">
+<meta name="assert" content="This test checks that child fragments get positioned correctly." />
+<style>
+.test {
+  background: red;
+  width: 100px;
+  height: 100px;
+}
+
+.test {
+  writing-mode: vertical-rl;
+  direction: rtl;
+}
+
+.child-1 {
+  background: rebeccapurple;
+  width: 10px;
+  height: 20px;
+
+  --inline-offset: 55;
+  --block-offset: 85;
+}
+
+.child-2 {
+  writing-mode: vertical-rl;
+  background: rebeccapurple;
+  width: 15px;
+  height: 25px;
+
+  --inline-offset: 15;
+  --block-offset: 35;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test">
+  <div class="child-1"></div>
+  <div class="child-2"></div>
+</div>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-position-child-worklet.js'});
+</script>
diff --git a/css/css-layout-api/style-map-multi-ref.html b/css/css-layout-api/style-map-multi-ref.html
new file mode 100644
index 0000000..fc54068
--- /dev/null
+++ b/css/css-layout-api/style-map-multi-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+.result {
+  background: green;
+  margin: 10px;
+  margin-left: 2px;
+
+  width: 100px;
+  height: 100px;
+}
+</style>
+
+<div class="result"></div>
diff --git a/css/css-layout-api/style-map-multi.https.html b/css/css-layout-api/style-map-multi.https.html
new file mode 100644
index 0000000..dd0617c
--- /dev/null
+++ b/css/css-layout-api/style-map-multi.https.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-invalidation">
+<link rel="match" href="style-map-multi-ref.html">
+<meta name="assert" content="This test checks that properties are correctly given to the layout function." />
+
+<style>
+.test {
+  background: red;
+  margin: 10px;
+  width: 100px;
+
+  /* Properties under test. */
+  --foo: bar;
+  margin-left: 2px;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+    display: layout(test);
+  }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test"></div>
+
+<script id="code" type="text/worklet">
+registerLayout('test', class {
+  static get inputProperties() {
+    return [ '--bar', '--foo', 'empty-cells', 'margin-left'];
+  }
+
+  *intrinsicSizes() {}
+  *layout(children, edges, constraints, styleMap) {
+    const expected = [
+      {property: '--bar', value: '[CSSUnparsedValue=]'},
+      {property: '--foo', value: '[CSSUnparsedValue= bar]'},
+      {property: 'empty-cells', value: '[CSSKeywordValue=show]'},
+      {property: 'margin-left', value: '[CSSUnitValue=2px]'},
+    ];
+
+    const actual = Array.from(styleMap.keys()).sort().map((property) => {
+      const valueObject = styleMap.get(property);
+      const value = '[' + valueObject.constructor.name + '=' + valueObject.toString() + ']';
+      return {property, value};
+    });
+
+    if (expected.length != actual.length)
+      return {autoBlockSize: 0};
+
+    for (let i = 0; i < expected.length; i++) {
+      if (expected[i].property != actual[i].property)
+        return {autoBlockSize: 0};
+
+      if (expected[i].value != actual[i].value)
+        return {autoBlockSize: 0};
+    }
+
+    return {autoBlockSize: 100};
+  }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/css/css-layout-api/style-map-ref.html b/css/css-layout-api/style-map-ref.html
new file mode 100644
index 0000000..c24e394
--- /dev/null
+++ b/css/css-layout-api/style-map-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+.result {
+  background: green;
+  margin: 10px;
+  margin-left: 2px;
+
+  width: 100px;
+  height: 100px;
+}
+</style>
+
+<div class="result"></div>
+<div class="result"></div>
+<div class="result"></div>
+<div class="result"></div>
diff --git a/css/css-layout-api/style-map.https.html b/css/css-layout-api/style-map.https.html
new file mode 100644
index 0000000..d16054d
--- /dev/null
+++ b/css/css-layout-api/style-map.https.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-invalidation">
+<link rel="match" href="style-map-ref.html">
+<meta name="assert" content="This test checks that properties are correctly given to the layout function." />
+
+<style>
+.test {
+  background: red;
+  margin: 10px;
+  width: 100px;
+
+  /* Properties under test. */
+  --foo: bar;
+  margin-left: 2px;
+}
+
+@supports (display: layout(test)) {
+  .test {
+    background: green;
+  }
+
+  .test-0 { display: layout(test-0); }
+  .test-1 { display: layout(test-1); }
+  .test-2 { display: layout(test-2); }
+  .test-3 { display: layout(test-3); }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test test-0"></div>
+<div class="test test-1"></div>
+<div class="test test-2"></div>
+<div class="test test-3"></div>
+
+<script>
+const tmpl = (test, idx) => {
+  return `
+    registerLayout('test-${idx}', class {
+      static get inputProperties() { return ['${test.property}']; }
+
+      *intrinsicSizes() {}
+      *layout(children, edges, constraints, styleMap) {
+        const value = styleMap.get('${test.property}');
+        const result = '[' + value.constructor.name + '=' + value.toString() + ']';
+        if (result != '${test.expected}')
+          return {autoBlockSize: 0};
+
+        const size = Array.from(styleMap.keys()).length;
+        if (size != 1)
+          return {autoBlockSize: 0};
+
+        return {autoBlockSize: 100};
+      }
+    });
+  `;
+}
+
+const tests = [
+  {property: '--bar', expected: '[CSSUnparsedValue=]'},
+  {property: '--foo', expected: '[CSSUnparsedValue= bar]'},
+  {property: 'empty-cells', expected: '[CSSKeywordValue=show]'},
+  {property: 'margin-left', expected: '[CSSUnitValue=2px]'},
+];
+
+const workletSource = tests.map(tmpl).join('\n');
+
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, workletSource);
+</script>
+</html>
diff --git a/css/css-layout-api/support/constraints-fixed-inline-size.js b/css/css-layout-api/support/constraints-fixed-inline-size.js
new file mode 100644
index 0000000..4e591f7
--- /dev/null
+++ b/css/css-layout-api/support/constraints-fixed-inline-size.js
@@ -0,0 +1,9 @@
+registerLayout('test', class {
+  *intrinsicSizes() {}
+  *layout(children, edges, constraints, styleMap) {
+    if (constraints.fixedInlineSize !== 100)
+      return {autoBlockSize: 0};
+
+    return {autoBlockSize: 100};
+  }
+});
diff --git a/css/css-layout-api/support/layout-child-fixed-sizes-worklet.js b/css/css-layout-api/support/layout-child-fixed-sizes-worklet.js
new file mode 100644
index 0000000..7315573
--- /dev/null
+++ b/css/css-layout-api/support/layout-child-fixed-sizes-worklet.js
@@ -0,0 +1,52 @@
+import {areArraysEqual} from '/common/arrays.js';
+
+function parseNumber(value) {
+  const num = parseInt(value.toString());
+  if (isNaN(num)) return undefined;
+  return num;
+}
+
+registerLayout('test', class {
+  static get childInputProperties() {
+    return [
+      '--fixed-inline-size',
+      '--fixed-block-size',
+      '--inline-size-expected',
+      '--block-size-expected'
+    ];
+  }
+
+  *intrinsicSizes() {}
+  *layout(children, edges, constraints, styleMap) {
+    const childFragments = yield children.map((child) => {
+      const childConstraints = {};
+      const fixedInlineSize = parseNumber(child.styleMap.get('--fixed-inline-size'));
+      const fixedBlockSize = parseNumber(child.styleMap.get('--fixed-block-size'));
+      return child.layoutNextFragment({fixedInlineSize, fixedBlockSize});
+    });
+
+    const actual = childFragments.map((childFragment) => {
+      return {
+        inlineSize: childFragment.inlineSize,
+        blockSize: childFragment.blockSize,
+      };
+    });
+
+    const expected = children.map((child) => {
+      return {
+        inlineSize: parseInt(child.styleMap.get('--inline-size-expected').toString()),
+        blockSize: parseInt(child.styleMap.get('--block-size-expected').toString()),
+      };
+    });
+
+    const equalityFunc = (a, b) => {
+      return a.inlineSize == b.inlineSize && a.blockSize == b.blockSize;
+    };
+
+    if (!areArraysEqual(expected, actual, equalityFunc)) {
+      return {autoBlockSize: 0, childFragments};
+    }
+
+    return {autoBlockSize: 100, childFragments};
+  }
+});
diff --git a/css/css-layout-api/support/layout-child-worklet.js b/css/css-layout-api/support/layout-child-worklet.js
new file mode 100644
index 0000000..db20e2e
--- /dev/null
+++ b/css/css-layout-api/support/layout-child-worklet.js
@@ -0,0 +1,26 @@
+import {areArraysEqual} from '/common/arrays.js';
+
+registerLayout('test', class {
+  static get inputProperties() {
+    return [ '--child-expected'];
+  }
+
+  static get childInputProperties() {
+    return [ '--child' ];
+  }
+
+  *intrinsicSizes() {}
+  *layout(children, edges, constraints, styleMap) {
+    const expected = JSON.parse(styleMap.get('--child-expected').toString());
+    const actual = children.map((child) => {
+      return child.styleMap.get('--child').toString().trim();
+    });
+
+    const childFragments = yield children.map((child) => { return child.layoutNextFragment({}); });
+
+    if (!areArraysEqual(expected, actual))
+      return {autoBlockSize: 0, childFragments};
+
+    return {autoBlockSize: 100, childFragments};
+  }
+});
diff --git a/css/css-layout-api/support/layout-position-child-worklet.js b/css/css-layout-api/support/layout-position-child-worklet.js
new file mode 100644
index 0000000..1ccfeab
--- /dev/null
+++ b/css/css-layout-api/support/layout-position-child-worklet.js
@@ -0,0 +1,22 @@
+registerLayout('test', class {
+  static get childInputProperties() {
+    return [
+      '--inline-offset',
+      '--block-offset',
+    ];
+  }
+
+  *intrinsicSizes() {}
+  *layout(children, edges, constraints, styleMap) {
+    const childFragments = yield children.map((child) => {
+      return child.layoutNextFragment({});
+    });
+
+    for (let i = 0; i < children.length; i++) {
+      childFragments[i].inlineOffset = parseInt(children[i].styleMap.get('--inline-offset').toString());
+      childFragments[i].blockOffset = parseInt(children[i].styleMap.get('--block-offset').toString());
+    }
+
+    return {autoBlockSize: 0, childFragments};
+  }
+});
diff --git a/css/css-layout-api/supports.https.html b/css/css-layout-api/supports.https.html
new file mode 100644
index 0000000..e269b29
--- /dev/null
+++ b/css/css-layout-api/supports.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#valdef-display-layout">
+<meta name="assert" content="This test checks that a layout() function works correctly with CSS.supports()." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+test(function() {
+  assert_true(CSS.supports('display', 'layout(foo)'));
+});
+</script>
diff --git a/css/css-lists/counter-7-ref.html b/css/css-lists/counter-7-ref.html
new file mode 100644
index 0000000..3d1b672
--- /dev/null
+++ b/css/css-lists/counter-7-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<p>You should see the number 7 below.</p>
+<div>7</div>
diff --git a/css/css-lists/counter-increment-inside-display-contents.html b/css/css-lists/counter-increment-inside-display-contents.html
new file mode 100644
index 0000000..ebfe177
--- /dev/null
+++ b/css/css-lists/counter-increment-inside-display-contents.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Lists: counter-increment on child of display:contents</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#counters-without-boxes">
+<link rel="match" href="counter-7-ref.html">
+<style>
+  .inc { counter-increment: x }
+  .reset-6 { counter-reset: x 6 }
+  .contents { display: contents }
+  .result::before { content: counter(x) }
+</style>
+<p>You should see the number 7 below.</p>
+<div>
+  <span class="reset-6"></span>
+  <span class="contents">
+    <span class="inc"></span>
+  </span>
+  <span class="result"></span>
+</div>
diff --git a/css/css-lists/counter-invalid.htm b/css/css-lists/counter-invalid.htm
new file mode 100644
index 0000000..9380159
--- /dev/null
+++ b/css/css-lists/counter-invalid.htm
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Lists: </title>
+<link rel="author" title="Anthony Ramine" href="mailto:n.oxyde@gmail.com">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#counter-functions">
+<link rel="match" href="counter-7-ref.html">
+<style>
+    div::after { content: " is not " counter(inherit) }
+</style>
+<p>You should see the number 7 below.</p>
+<div>7</div>
diff --git a/css/css-lists/counter-reset-increment-display-contents.html b/css/css-lists/counter-reset-increment-display-contents.html
new file mode 100644
index 0000000..a59576b
--- /dev/null
+++ b/css/css-lists/counter-reset-increment-display-contents.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Lists: counter-reset and counter-increment on display:contents</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#counters-without-boxes">
+<link rel="match" href="counter-7-ref.html">
+<style>
+  .inc { counter-increment: x }
+  .reset-6 { counter-reset: x 6 }
+  .reset-666 { counter-reset: x 666 }
+  .contents { display: contents }
+  .result::before { content: counter(x) }
+</style>
+<p>You should see the number 7 below.</p>
+<div>
+  <span class="reset-6"></span>
+  <span class="contents reset-666 inc"></span>
+  <span class="inc result"></span>
+</div>
diff --git a/css/css-lists/counter-reset-increment-display-none.html b/css/css-lists/counter-reset-increment-display-none.html
new file mode 100644
index 0000000..3b344a7
--- /dev/null
+++ b/css/css-lists/counter-reset-increment-display-none.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Lists: counter-reset and counter-increment on display:none</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#counters-without-boxes">
+<link rel="match" href="counter-7-ref.html">
+<style>
+  .inc { counter-increment: x }
+  .reset-6 { counter-reset: x 6 }
+  .reset-666 { counter-reset: x 666 }
+  .none { display: none }
+  .result::before { content: counter(x) }
+</style>
+<p>You should see the number 7 below.</p>
+<div>
+  <span class="reset-6"></span>
+  <span class="none reset-666 inc"></span>
+  <span class="inc result"></span>
+</div>
diff --git a/css/css-lists/counter-reset-inside-display-contents.html b/css/css-lists/counter-reset-inside-display-contents.html
new file mode 100644
index 0000000..85c137e
--- /dev/null
+++ b/css/css-lists/counter-reset-inside-display-contents.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Lists: counter-reset on child of display:contents</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#counters-without-boxes">
+<link rel="match" href="counter-7-ref.html">
+<style>
+  .inc { counter-increment: x }
+  .reset-6 { counter-reset: x 6 }
+  .reset-666 { counter-reset: x 6 }
+  .contents { display: contents }
+  .result::before { content: counter(x) }
+</style>
+<p>You should see the number 7 below.</p>
+<div>
+  <span class="reset-6"></span>
+  <span class="contents">
+    <span class="reset-666"></span>
+  </span>
+  <span class="inc result"></span>
+</div>
diff --git a/css/css-masking/clip/clip-rect-auto-001.html b/css/css-masking/clip/clip-rect-auto-001.html
index 36cc49d..6a1a064 100644
--- a/css/css-masking/clip/clip-rect-auto-001.html
+++ b/css/css-masking/clip/clip-rect-auto-001.html
@@ -12,7 +12,7 @@
     in the bottom right corner of the blue square.">
 </head>
 <body>
-    <p>The test passes if there is a blue square and a smaller green square in the bottom right corner of the blue square   .</p>
+    <p>The test passes if there is a blue square and a smaller green square in the bottom right corner of the blue square.</p>
     <div style="position: absolute; clip: rect(auto, auto, auto, auto); width: 100px; height: 100px;">
         <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green;"></div>
     </div>
diff --git a/css/css-masking/clip/clip-rect-comma-002.html b/css/css-masking/clip/clip-rect-comma-002.html
index abe56bb..aad2aeb 100644
--- a/css/css-masking/clip/clip-rect-comma-002.html
+++ b/css/css-masking/clip/clip-rect-comma-002.html
@@ -12,6 +12,6 @@
 </head>
 <body>
     <p>The test passes if there is a green square with a blue border.</p>
-    <div style="width: 100px; height: 100px; border: solid red 50px; background-color: green; position: absolute; clip: rect(50px 150px, 150px, 50px);"></div>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(50px 150px, 150px, 50px);"></div>
 </body>
 </html>
\ No newline at end of file
diff --git a/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html b/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html
index d90a5ec..fccb183 100644
--- a/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html
+++ b/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html
@@ -5,7 +5,7 @@
     <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
 </head>
 <body>
-    <p>The test passes if there is only a vertical blue stripe.</p>
+    <p>The test passes if there is only a horizontal blue stripe.</p>
     <div style="width: 200px; height: 50px; background-color: blue;"></div>
 </body>
 </html>
\ No newline at end of file
diff --git a/css/css-masking/parsing/clip-invalid.html b/css/css-masking/parsing/clip-invalid.html
new file mode 100644
index 0000000..1ac795b
--- /dev/null
+++ b/css/css-masking/parsing/clip-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#clip-property">
+<meta name="assert" content="clip supports only the grammar 'rect() | auto'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("clip", "none");
+test_invalid_value("clip", "rect(10px, 20px, 30px)");
+</script>
+</body>
+</html>
diff --git a/css/css-masking/parsing/clip-path-invalid.html b/css/css-masking/parsing/clip-path-invalid.html
new file mode 100644
index 0000000..3f5940a
--- /dev/null
+++ b/css/css-masking/parsing/clip-path-invalid.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-path with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-path">
+<meta name="assert" content="clip-path supports only the grammar '<clip-source> | [ <basic-shape> || <geometry-box> ] | none'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("clip-path", "auto");
+test_invalid_value("clip-path", "ray(0deg)");
+
+test_invalid_value("clip-path", "inset()");
+test_invalid_value("clip-path", "inset(123)");
+test_invalid_value("clip-path", "inset(1% 2% 3% 4% 5%)");
+test_invalid_value("clip-path", "inset(round 0)");
+test_invalid_value("clip-path", "inset(0px round)");
+test_invalid_value("clip-path", "inset(0px round 123)");
+test_invalid_value("clip-path", "inset(0px round 1% 2% 3% 4% 5%)");
+test_invalid_value("clip-path", "inset(0px round / 1px)");
+test_invalid_value("clip-path", "inset(10px round -20px)");
+test_invalid_value("clip-path", "inset(30% round -40%)");
+
+test_invalid_value("clip-path", "circle(123)");
+test_invalid_value("clip-path", "circle(at)");
+test_invalid_value("clip-path", "circle(10% 20%)");
+test_invalid_value("clip-path", "circle(-10px at 20px 30px)");
+test_invalid_value("clip-path", "circle(-10% at 20% 30%)");
+test_invalid_value("clip-path", "circle(1% 2% at 0% 100%)");
+
+test_invalid_value("clip-path", "ellipse(farthest-side at)");
+test_invalid_value("clip-path", "ellipse(1% 2% top right)");
+test_invalid_value("clip-path", "ellipse(3% at 100% 0%)");
+test_invalid_value("clip-path", "ellipse(10% -20% at 30% 40%)");
+test_invalid_value("clip-path", "ellipse(-50px 60px at 70% 80%)");
+
+test_invalid_value("clip-path", "polygon(1%)");
+
+test_invalid_value("clip-path", "unknown-box");
+</script>
+</body>
+</html>
diff --git a/css/css-masking/parsing/clip-path-valid.html b/css/css-masking/parsing/clip-path-valid.html
new file mode 100644
index 0000000..d7b2785
--- /dev/null
+++ b/css/css-masking/parsing/clip-path-valid.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-path with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-path">
+<meta name="assert" content="clip-path supports the full grammar '<clip-source> | [ <basic-shape> || <geometry-box> ] | none'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("clip-path", "none");
+
+// <basic-shape>
+test_valid_value("clip-path", "inset(100%)");
+test_valid_value("clip-path", "inset(0 1px)", "inset(0px 1px)");
+test_valid_value("clip-path", "inset(0px 1px 2%)");
+test_valid_value("clip-path", "inset(0px 1px 2% 3em)");
+test_valid_value("clip-path", "inset(0px round 100%)");
+test_valid_value("clip-path", "inset(0px round 0 1px)", "inset(0px round 0px 1px)");
+test_valid_value("clip-path", "inset(0px round 0px 1px 2%)");
+test_valid_value("clip-path", "inset(0px round 0px 1px 2% 3em)");
+test_valid_value("clip-path", "inset(10px round 20% / 0px 1px 2% 3em)");
+
+test_valid_value("clip-path", "circle()", "circle(at 50% 50%)");
+test_valid_value("clip-path", "circle(1px)", "circle(1px at 50% 50%)");
+test_valid_value("clip-path", "circle(closest-side)", "circle(at 50% 50%)");
+test_valid_value("clip-path", "circle(at 10% 20%)");
+test_valid_value("clip-path", "circle(farthest-side at center top)", "circle(farthest-side at 50% 0%)");
+test_valid_value("clip-path", "circle(4% at top right)", "circle(4% at 100% 0%)");
+
+test_valid_value("clip-path", "ellipse()", "ellipse(at 50% 50%)");
+test_valid_value("clip-path", "ellipse(1px closest-side)", "ellipse(1px at 50% 50%)");
+test_valid_value("clip-path", "ellipse(at 10% 20%)");
+test_valid_value("clip-path", "ellipse(farthest-side 4% at bottom left)", "ellipse(farthest-side 4% at 0% 100%)");
+
+test_valid_value("clip-path", "polygon(1% 2%)");
+test_valid_value("clip-path", "polygon(nonzero, 1px 2px, 3em 4em)", "polygon(1px 2px, 3em 4em)");
+test_valid_value("clip-path", "polygon(evenodd, 1px 2px, 3em 4em, 5pt 6%)");
+
+// <geometry-box>
+test_valid_value("clip-path", "border-box");
+test_valid_value("clip-path", "padding-box");
+test_valid_value("clip-path", "content-box");
+test_valid_value("clip-path", "margin-box");
+test_valid_value("clip-path", "fill-box");
+test_valid_value("clip-path", "stroke-box");
+test_valid_value("clip-path", "view-box");
+
+// basic-shape> <geometry-box>
+test_valid_value("clip-path", "circle(7% at 8% 9%) border-box");
+
+// <geometry-box> basic-shape>
+test_valid_value("clip-path", "border-box circle(7% at 8% 9%)");
+
+//  <clip-source>
+test_valid_value("clip-path", "url(https://example.com/)", ["url(https://example.com/)", "url(\"https://example.com/\")"]);
+test_valid_value("clip-path", "url(\"https://example.com/\")", ["url(https://example.com/)", "url(\"https://example.com/\")"]);
+</script>
+</body>
+</html>
diff --git a/css/css-masking/parsing/clip-rule-invalid.html b/css/css-masking/parsing/clip-rule-invalid.html
new file mode 100644
index 0000000..10f6aee
--- /dev/null
+++ b/css/css-masking/parsing/clip-rule-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-rule with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-rule">
+<meta name="assert" content="clip-rule supports only the grammar 'nonzero | evenodd'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("clip-rule", "auto");
+test_invalid_value("clip-rule", "1");
+</script>
+</body>
+</html>
diff --git a/css/css-masking/parsing/clip-rule-valid.html b/css/css-masking/parsing/clip-rule-valid.html
new file mode 100644
index 0000000..db22680
--- /dev/null
+++ b/css/css-masking/parsing/clip-rule-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-rule with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-rule">
+<meta name="assert" content="clip-rule supports the full grammar 'nonzero | evenodd'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("clip-rule", "nonzero");
+test_valid_value("clip-rule", "evenodd");
+</script>
+</body>
+</html>
diff --git a/css/css-masking/parsing/clip-valid.html b/css/css-masking/parsing/clip-valid.html
new file mode 100644
index 0000000..24bb782
--- /dev/null
+++ b/css/css-masking/parsing/clip-valid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#clip-property">
+<meta name="assert" content="clip supports the full grammar 'rect() | auto'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("clip", "auto");
+test_valid_value("clip", "rect(10px, 20px, -30px, 40px)", ["rect(10px, 20px, -30px, 40px)", "rect(10px 20px -30px 40px)"]);
+test_valid_value("clip", "rect(10%, -20%, auto, auto)");
+</script>
+</body>
+</html>
diff --git a/css/css-masking/parsing/resources/parsing-testcommon.js b/css/css-masking/parsing/resources/parsing-testcommon.js
new file mode 100644
index 0000000..b075882
--- /dev/null
+++ b/css/css-masking/parsing/resources/parsing-testcommon.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
+function test_valid_value(property, value, serializedValue) {
+    if (arguments.length < 3)
+        serializedValue = value;
+
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
+
+        var div = document.createElement('div');
+        div.style[property] = value;
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
+        div.style[property] = readValue;
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+}
+
+function test_invalid_value(property, value) {
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_equals(div.style.getPropertyValue(property), "");
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
+}
diff --git a/css/css-multicol/extremely-tall-multicol-with-extremely-tall-child-crash.html b/css/css-multicol/extremely-tall-multicol-with-extremely-tall-child-crash.html
new file mode 100644
index 0000000..6e40afe
--- /dev/null
+++ b/css/css-multicol/extremely-tall-multicol-with-extremely-tall-child-crash.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>CSS Multi-column Layout Test: extremely tall multicolumn container with extremely tall inline-block inside</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="http://www.w3.org/TR/css3-multicol/" title="CSS Multi-column Layout Module Level 1">
+<meta name="assert" content="This tests that we get no crash if we put an extremely tall inline-block inside an extremely tall multicol container. Engines typically have some maximum limit for how large values they can represent. If the value is large enough to become 'infinity', the engine should still not crash">
+<!-- Explanation for the CSS values specified below: This test was written for
+     the Chromium Blink engine, which uses a fixed-point 32 bit integer. In this
+     example, the value will be too large, so it will be clamped to the maximum
+     value (about 2^25 px). However, there's a a further complication in that
+     CSS length values in Blink are transported via a 32bit float, so precision
+     will be a few pixels off. So even if 1234567890px is way more than Blink
+     can handle, it will be truncated to a bit less than the maximum value
+     (about 2^25). We want the multicol container to be as tall as it can
+     possibly get, so we put it inside a 1234567890px tall container, and
+     specify the height of the child multicol container to be 200% to ensure
+     this. We also use a percentage to specify the height of the child inside
+     the multicol container, for the same reason. -->
+<div style="height:1234567890px;">
+  <div style="columns:2; column-fill:auto; height:200%;">
+    <div style="display:inline-block; height:100%;"></div><br>
+    hest
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => { }, "No crash or assertion failure.");
+</script>
diff --git a/css/css-multicol/float-and-block.html b/css/css-multicol/float-and-block.html
new file mode 100644
index 0000000..21f9fb9
--- /dev/null
+++ b/css/css-multicol/float-and-block.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Paint order with float VS regular block is correct inside multicol</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/zindex.html#painting-order" title="E.2 Painting order">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; column-gap:0; width:100px; background:hotpink;">
+  <div style="float:left; width:50px; height:200px; background:green;"></div>
+  <div style="height:200px; background:red;"></div>
+</div>
diff --git a/css/css-multicol/going-out-of-flow-after-spanner.html b/css/css-multicol/going-out-of-flow-after-spanner.html
new file mode 100644
index 0000000..2433864
--- /dev/null
+++ b/css/css-multicol/going-out-of-flow-after-spanner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Taking the sole box after a spanner out of flow, then remove it</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="http://www.w3.org/TR/css3-multicol/" title="CSS Multi-column Layout Module Level 1">
+<meta name="assert" content="An absolutely positioned box is still contained by the multicol container if its containing block is inside the multicol container">
+<div id="multicol" style="columns:3;">
+  <div style="position:relative;">
+    <div style="column-span:all;"></div>
+    <div id="victim" style="width:100%; height:300px;"></div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+  var multicol = document.getElementById("multicol");
+  var victim = document.getElementById("victim");
+  assert_equals(multicol.getBoundingClientRect().height, 100);
+  victim.style.position = "absolute";
+  assert_equals(multicol.getBoundingClientRect().height, 100);
+  victim.style.display = "none";
+  assert_equals(multicol.getBoundingClientRect().height, 0);
+}, "Going out of flow and still fragmented");
+</script>
diff --git a/css/css-multicol/multicol-br-inside-avoidcolumn-001.xht b/css/css-multicol/multicol-br-inside-avoidcolumn-001.xht
index 419a88b..d3f2c9e 100644
--- a/css/css-multicol/multicol-br-inside-avoidcolumn-001.xht
+++ b/css/css-multicol/multicol-br-inside-avoidcolumn-001.xht
@@ -5,47 +5,33 @@
 <title>multicol | break-inside: avoid-column</title>
 <link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
 <link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-breaks"/>
-<link rel="match" href="multicol-br-inside-avoidcolumn-ref.xht"/>
+<link rel="match" href="../reference/ref-filled-green-200px-square.html"/>
 <meta name="flags" content=""/>
 <style type="text/css"><![CDATA[
-html {
-	width: 800px;
-	background: white;
+.multicol {
+  column-count: 2;
+  column-gap: 0;
+  column-fill: auto;
+  overflow: hidden;
+  width: 200px;
+  height: 300px;
 }
-body {
-	background: black;
-	column-count: 3;
-	column-gap: 0;
-	column-fill: auto;
-	height: 300px;
+.multicol > div {
+  height: 200px;
+  break-inside: avoid-column;
+  background: green;
 }
-h1 {
-	column-span: all;
-	color: white;
-}
-div { background: red;
-	height: 150px;
-	break-inside: avoid-column;
-}
-span {
-	float: left;
+.multicol > div.red {
+  background:red;
 }
 ]]></style>
 </head>
 <body>
-<h1>You should not see the word FAIL</h1>
-
-<div>
-	<span>FAIL</span>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div></div>
+  <div></div>
+  <div class="red"></div>
 </div>
-
-<div>
-	<span>FAIL</span>
-</div>
-
-<div>
-	<span>FAIL</span>
-</div>
-
 </body>
 </html>
diff --git a/css/css-multicol/multicol-br-inside-avoidcolumn-ref.xht b/css/css-multicol/multicol-br-inside-avoidcolumn-ref.xht
deleted file mode 100644
index fc46556..0000000
--- a/css/css-multicol/multicol-br-inside-avoidcolumn-ref.xht
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicol | break-inside: avoid-column</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-html {
-	width: 800px;
-	background: white;
-}
-h1 {
-	color: white;
-	margin-top: 0;
-	padding-top: 0.66em;
-}
-div {
-	height: 450px;
-	background: black;
-	height: 300px;
-}
-]]></style>
-</head>
-<body>
-<div>
-	<h1>You should not see the word FAIL</h1>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-break-001-ref.xht b/css/css-multicol/multicol-break-001-ref.xht
index b363c2b..f753518 100644
--- a/css/css-multicol/multicol-break-001-ref.xht
+++ b/css/css-multicol/multicol-break-001-ref.xht
@@ -21,9 +21,9 @@
 
   <p>Test passes if the 2 horizontal bars are <strong>identical</strong>.</p>
 
-  <div>&nbsp; <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /></div>
+  <div><img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /></div>
 
-  <div>&nbsp; <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /></div>
+  <div><img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /></div>
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/multicol-break-001.xht b/css/css-multicol/multicol-break-001.xht
index 8c3f6a7..a036674 100644
--- a/css/css-multicol/multicol-break-001.xht
+++ b/css/css-multicol/multicol-break-001.xht
@@ -47,7 +47,7 @@
       <div>C</div>
   </div>
 
-  <div id="reference">&nbsp; <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /></div>
+  <div id="reference"><img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /> <img src="support/black20x20.png" alt="Image download support must be enabled" /></div>
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/multicol-breaking-000-ref.html b/css/css-multicol/multicol-breaking-000-ref.html
new file mode 100644
index 0000000..3d496cd
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-000-ref.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.blueborders {
+  position: absolute;
+  top: 0;
+  left: 194px; /* 188px first column + (16px gap - 4px rule) / 2 */
+  width: 200px; /* 188px second column + (16px gap - 4px rule) */
+  height: 100px;
+  border-right: blue solid 4px;
+  border-left: blue solid 4px;
+}
+
+.innerbg {
+  height: 100px;
+  width: 188px;
+  background: rgba(255, 0, 255, 0.3);
+  position: absolute;
+  top: 0;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="blueborders"></div>
+  <div class="innerbg" style="left: 0"></div>
+  <div class="inner lefthalf" style="left: 0; height: 60px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    DDDDD<br>
+    EEEEE
+  </div>
+  <div class="innerbg" style="left: 204px"></div>
+  <div class="innerbg" style="left: 408px"></div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-000.html b/css/css-multicol/multicol-breaking-000.html
new file mode 100644
index 0000000..391985d
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-000.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-000-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-rule: 4px solid blue;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  background: rgba(255, 0, 255, 0.3);
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-001-ref.html b/css/css-multicol/multicol-breaking-001-ref.html
new file mode 100644
index 0000000..46357f9
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-001-ref.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.blueborders {
+  position: absolute;
+  top: 0;
+  left: 194px; /* 188px first column + (16px gap - 4px rule) / 2 */
+  width: 200px; /* 188px second column + (16px gap - 4px rule) */
+  height: 100px;
+  border-right: blue solid 4px;
+  border-left: blue solid 4px;
+}
+
+.innerbg {
+  height: 100px;
+  width: 188px;
+  background: rgba(255, 0, 255, 0.3);
+  position: absolute;
+  top: 0;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="blueborders"></div>
+  <div class="innerbg" style="left: 0"></div>
+  <div class="inner lefthalf" style="left: 0">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ
+  </div>
+  <div class="innerbg" style="left: 204px"></div>
+  <div class="inner lefthalf" style="left: 204px; height: 80px">
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN
+  </div>
+  <div class="inner righthalf" style="left: 299px">
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ<br>
+  </div>
+  <div class="innerbg" style="left: 408px"></div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-001.html b/css/css-multicol/multicol-breaking-001.html
new file mode 100644
index 0000000..69c9049
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-001.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-001-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-rule: 4px solid blue;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  background: rgba(255, 0, 255, 0.3);
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ<br>
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-002-ref.html b/css/css-multicol/multicol-breaking-002-ref.html
new file mode 100644
index 0000000..9b6c5f8
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-002-ref.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.blueborders {
+  position: absolute;
+  top: 0;
+  left: 194px; /* 188px first column + (16px gap - 4px rule) / 2 */
+  width: 200px; /* 188px second column + (16px gap - 4px rule) */
+  height: 100px;
+  border-right: blue solid 4px;
+  border-left: blue solid 4px;
+}
+
+.innerbg {
+  height: 100px;
+  width: 188px;
+  background: rgba(255, 0, 255, 0.3);
+  position: absolute;
+  top: 0;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="blueborders"></div>
+  <div class="innerbg" style="left: 0"></div>
+  <div class="inner lefthalf" style="left: 0">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ
+  </div>
+  <div class="innerbg" style="left: 204px"></div>
+  <div class="inner lefthalf" style="left: 204px">
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO
+  </div>
+  <div class="inner righthalf" style="left: 299px">
+    PPPPP<br>
+    QQQQQ<br>
+    RRRRR<br>
+    SSSSS<br>
+    TTTTT
+  </div>
+  <div class="innerbg" style="left: 408px"></div>
+  <div class="inner lefthalf" style="left: 408px">
+    UUUUU<br>
+    VVVVV<br>
+    WWWWW<br>
+    XXXXX<br>
+    YYYYY
+  </div>
+  <div class="inner righthalf" style="left: 503px">
+    ZZZZZ<br>
+    aaaaa<br>
+    bbbbb<br>
+    ccccc<br>
+    ddddd
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-002.html b/css/css-multicol/multicol-breaking-002.html
new file mode 100644
index 0000000..279ed94
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-002.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-002-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-rule: 4px solid blue;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  background: rgba(255, 0, 255, 0.3);
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ<br>
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ<br>
+    RRRRR<br>
+    SSSSS<br>
+    TTTTT<br>
+    UUUUU<br>
+    VVVVV<br>
+    WWWWW<br>
+    XXXXX<br>
+    YYYYY<br>
+    ZZZZZ<br>
+    aaaaa<br>
+    bbbbb<br>
+    ccccc<br>
+    ddddd
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-003-ref.html b/css/css-multicol/multicol-breaking-003-ref.html
new file mode 100644
index 0000000..eb0d55d
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-003-ref.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.blueborders {
+  position: absolute;
+  top: 0;
+  left: 194px; /* 188px first column + (16px gap - 4px rule) / 2 */
+  width: 200px; /* 188px second column + (16px gap - 4px rule) */
+  height: 100px;
+  border-right: blue solid 4px;
+  border-left: blue solid 4px;
+}
+
+.innerbg {
+  height: 100px;
+  width: 188px;
+  background: rgba(255, 0, 255, 0.3);
+  position: absolute;
+  top: 0;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="blueborders"></div>
+  <div class="innerbg" style="left: 0"></div>
+  <div class="inner lefthalf" style="left: 0">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ
+  </div>
+  <div class="innerbg" style="left: 204px"></div>
+  <div class="inner lefthalf" style="left: 204px">
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO
+  </div>
+  <div class="inner righthalf" style="left: 299px">
+    PPPPP<br>
+    QQQQQ
+  </div>
+  <div class="innerbg" style="left: 408px"></div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-003.html b/css/css-multicol/multicol-breaking-003.html
new file mode 100644
index 0000000..102202d
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-003.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-003-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-rule: 4px solid blue;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  background: rgba(255, 0, 255, 0.3);
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px; column-fill: auto">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ<br>
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-000-ref.html b/css/css-multicol/multicol-breaking-nobackground-000-ref.html
new file mode 100644
index 0000000..e7ebcff
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-000-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner lefthalf" style="left: 0; height: 60px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    DDDDD<br>
+    EEEEE
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-000.html b/css/css-multicol/multicol-breaking-nobackground-000.html
new file mode 100644
index 0000000..a66a58f
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-000.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-nobackground-000-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-001-ref.html b/css/css-multicol/multicol-breaking-nobackground-001-ref.html
new file mode 100644
index 0000000..006ec1d
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-001-ref.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner lefthalf" style="left: 0">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ
+  </div>
+  <div class="inner lefthalf" style="left: 204px; height: 80px">
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN
+  </div>
+  <div class="inner righthalf" style="left: 299px">
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ<br>
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-001.html b/css/css-multicol/multicol-breaking-nobackground-001.html
new file mode 100644
index 0000000..d47fcd3
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-001.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-nobackground-001-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ<br>
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-002-ref.html b/css/css-multicol/multicol-breaking-nobackground-002-ref.html
new file mode 100644
index 0000000..5ec4c61
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-002-ref.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner lefthalf" style="left: 0">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ
+  </div>
+  <div class="inner lefthalf" style="left: 204px">
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO
+  </div>
+  <div class="inner righthalf" style="left: 299px">
+    PPPPP<br>
+    QQQQQ<br>
+    RRRRR<br>
+    SSSSS<br>
+    TTTTT
+  </div>
+  <div class="inner lefthalf" style="left: 408px">
+    UUUUU<br>
+    VVVVV<br>
+    WWWWW<br>
+    XXXXX<br>
+    YYYYY
+  </div>
+  <div class="inner righthalf" style="left: 503px">
+    ZZZZZ<br>
+    aaaaa<br>
+    bbbbb<br>
+    ccccc<br>
+    ddddd
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-002.html b/css/css-multicol/multicol-breaking-nobackground-002.html
new file mode 100644
index 0000000..e20568f
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-002.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-nobackground-002-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ<br>
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ<br>
+    RRRRR<br>
+    SSSSS<br>
+    TTTTT<br>
+    UUUUU<br>
+    VVVVV<br>
+    WWWWW<br>
+    XXXXX<br>
+    YYYYY<br>
+    ZZZZZ<br>
+    aaaaa<br>
+    bbbbb<br>
+    ccccc<br>
+    ddddd
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-003-ref.html b/css/css-multicol/multicol-breaking-nobackground-003-ref.html
new file mode 100644
index 0000000..471df94
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-003-ref.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<style>
+
+.outer {
+  height: 100px;
+  width: 800px;
+  background: rgba(0, 0, 255, 0.3);
+  position: relative;
+}
+
+.inner {
+  height: 100px;
+  width: 86px;
+  font: 16px/1.25 sans-serif;
+  position: absolute;
+  top: 0;
+}
+
+.lefthalf {
+  border-right: 2px solid fuchsia;
+  padding-right: 7px;
+}
+
+.righthalf {
+  padding-left: 7px;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner lefthalf" style="left: 0">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE
+  </div>
+  <div class="inner righthalf" style="left: 95px">
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ
+  </div>
+  <div class="inner lefthalf" style="left: 204px">
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO
+  </div>
+  <div class="inner righthalf" style="left: 299px">
+    PPPPP<br>
+    QQQQQ
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-breaking-nobackground-003.html b/css/css-multicol/multicol-breaking-nobackground-003.html
new file mode 100644
index 0000000..edf18f9
--- /dev/null
+++ b/css/css-multicol/multicol-breaking-nobackground-003.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of a multicolumn</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cf">
+<link rel="match" href="multicol-breaking-nobackground-003-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 800px;
+  column-count: 4;
+  column-gap: 16px;
+  background: rgba(0, 0, 255, 0.3);
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid fuchsia;
+  column-gap: 16px;
+  font: 16px/1.25 sans-serif;
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner" style="height: 300px; column-fill: auto">
+    AAAAA<br>
+    BBBBB<br>
+    CCCCC<br>
+    DDDDD<br>
+    EEEEE<br>
+    FFFFF<br>
+    GGGGG<br>
+    HHHHH<br>
+    IIIII<br>
+    JJJJJ<br>
+    KKKKK<br>
+    LLLLL<br>
+    MMMMM<br>
+    NNNNN<br>
+    OOOOO<br>
+    PPPPP<br>
+    QQQQQ
+  </div>
+</div>
diff --git a/css/css-multicol/multicol-count-computed-001.xht b/css/css-multicol/multicol-count-computed-001.xht
deleted file mode 100644
index 3c9ea3a..0000000
--- a/css/css-multicol/multicol-count-computed-001.xht
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-rule</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#the-number-and-width-of-columns"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-gaps-and-rules"/>
-<link rel="match" href="multicol-count-computed-ref.xht"/>
-<meta name="flags" content="ahem"/>
-<style type="text/css"><![CDATA[
-body {
-	margin: 0;
-}
-div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	widows: 1;
-	orphans: 1;
-
-	column-count: 3;
-	column-gap: 6em;
-	column-rule-color: blue;
-	column-rule-width: 1.5em;
-	column-rule-style: solid;
-}
-#pink {color: pink;}
-#orange {color: orange;}
-#purple {color: purple;}
-#grey {color: grey;}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<span id="pink">xxxx</span>
-	<span id="orange">xxxx</span>
-	<span id="purple">xxxx</span>
-	<span id="grey">xxxx</span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-count-computed-002.xht b/css/css-multicol/multicol-count-computed-002.xht
deleted file mode 100644
index 7bd1b3e..0000000
--- a/css/css-multicol/multicol-count-computed-002.xht
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-rule</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#the-number-and-width-of-columns"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-gaps-and-rules"/>
-<link rel="match" href="multicol-count-computed-2-ref.xht"/>
-<meta name="flags" content="ahem"/>
-<style type="text/css"><![CDATA[
-body {
-	margin: 0;
-}
-div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	widows: 1;
-	orphans: 1;
-
-	column-count: 3;
-	column-gap: 7em;
-	column-rule-color: red;
-	column-rule-width: 1.5em;
-	column-rule-style: solid;
-}
-#pink {color: pink;}
-#orange {color: orange;}
-#purple {color: purple;}
-#grey {color: grey;}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<span id="pink">xxxx</span>
-	<span id="orange">xxxx</span>
-	<span id="purple">xxxx</span>
-	<span id="grey">xxxx</span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-count-computed-2-ref.xht b/css/css-multicol/multicol-count-computed-2-ref.xht
deleted file mode 100644
index 15d365c..0000000
--- a/css/css-multicol/multicol-count-computed-2-ref.xht
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-rule</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body {
-	margin: 0;
-}
-div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	height: 2em;
-	position: relative;
-	widows: 1;
-	orphans: 1;
-}
-#pink {color: pink;}
-#orange {color: orange;}
-#purple {color: purple;}
-#grey {color: grey;}
-span {
-	display: block;
-	position: absolute;
-}
-#orange {
-	left: 5em;
-	top: 0;
-}
-#purple {
-	left: 0;
-	top: 1em;
-}
-#grey {
-	left: 5em;
-	top: 1em;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<span id="purple">xxxx</span>
-	<span id="orange">xxxx</span>
-	<span id="grey">xxxx</span>
-	<span id="pink">xxxx</span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-count-computed-ref.xht b/css/css-multicol/multicol-count-computed-ref.xht
deleted file mode 100644
index ac6d8c1..0000000
--- a/css/css-multicol/multicol-count-computed-ref.xht
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-rule</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body {
-	margin: 0;
-}
-div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	height: 2em;
-	position: relative;
-	widows: 1;
-	orphans: 1;
-
-}
-span {
-	display: block;
-	position: absolute;
-}
-#pink {
-	color: pink;
-}
-#orange {
-	color: orange;
-	left: 0;
-	top: 1em;
-}
-#purple {
-	color: purple;
-	left: 9em;
-	top: 0;
-}
-#grey {
-	color: grey;
-	left: 9em;
-	top: 1em;
-}
-#blue {
-	left: 5.25em;
-	top: 0;
-	width: 1.5em;
-	height: 2em;
-	background: blue;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<span id="pink">xxxx</span>
-	<span id="orange">xxxx</span>
-	<span id="purple">xxxx</span>
-	<span id="grey">xxxx</span>
-	<span id="blue"></span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-count-large-001.xht b/css/css-multicol/multicol-count-large-001.xht
deleted file mode 100644
index 78fff42..0000000
--- a/css/css-multicol/multicol-count-large-001.xht
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-count</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#the-number-and-width-of-columns"/>
-<link rel="match" href="multicol-count-large-ref.xht"/>
-<meta name="flags" content="ahem"/>
-<style type="text/css"><![CDATA[
-body>div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: red;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	orphans: 1;
-	widows: 1;
-
-	column-count: 1000;
-	column-gap: 0;
-}
-span {
-	color: black;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	xx xx
-	xx xx
-	xx xx
-	<span>xx xx</span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-count-large-002.xht b/css/css-multicol/multicol-count-large-002.xht
deleted file mode 100644
index 09b296a..0000000
--- a/css/css-multicol/multicol-count-large-002.xht
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-count</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#the-number-and-width-of-columns"/>
-<link rel="match" href="multicol-count-large-2-ref.xht"/>
-<meta name="flags" content="ahem"/>
-<style type="text/css"><![CDATA[
-body>div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	orphans: 1;
-	widows: 1;
-
-	column-count: 1000;
-	column-gap: 0;
-}
-span {
-	color: blue;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	xx xx
-	<span>xx xx</span>
-	xx xx
-	<span>xx xx</span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-count-large-2-ref.xht b/css/css-multicol/multicol-count-large-2-ref.xht
deleted file mode 100644
index cb4b26f..0000000
--- a/css/css-multicol/multicol-count-large-2-ref.xht
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-count</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body>div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	height: 1em;
-	position: relative;
-	orphans: 1;
-	widows: 1;
-}
-span {
-	color: blue;
-	background: blue;
-	width: 2em;
-	position: absolute;
-	left: 0;
-	top: 0;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<span>x</span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-count-large-ref.xht b/css/css-multicol/multicol-count-large-ref.xht
deleted file mode 100644
index a561c94..0000000
--- a/css/css-multicol/multicol-count-large-ref.xht
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-count</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body>div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	margin: 1em;
-	border: 1em solid gray;
-	width: 12em;
-	height: 1em;
-	orphans: 1;
-	widows: 1;
-
-}
-]]></style>
-</head>
-
-<body>
-
-<div>xx</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-fill-auto-002-ref.xht b/css/css-multicol/multicol-fill-auto-002-ref.xht
index 70c1f06..a49b674 100644
--- a/css/css-multicol/multicol-fill-auto-002-ref.xht
+++ b/css/css-multicol/multicol-fill-auto-002-ref.xht
@@ -7,7 +7,7 @@
 <meta name="flags" content="ahem"/>
 <style type="text/css"><![CDATA[
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1.25em;
 	line-height: 1em;
 	color: green;
diff --git a/css/css-multicol/multicol-fill-auto-002.xht b/css/css-multicol/multicol-fill-auto-002.xht
index 9a28395..7dbf6f7 100644
--- a/css/css-multicol/multicol-fill-auto-002.xht
+++ b/css/css-multicol/multicol-fill-auto-002.xht
@@ -10,7 +10,7 @@
 <meta name="flags" content="ahem"/>
 <style type="text/css"><![CDATA[
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1.25em;
 	line-height: 1em;
 	color: green;
diff --git a/css/css-multicol/multicol-fill-auto-block-children-002-ref.xht b/css/css-multicol/multicol-fill-auto-block-children-002-ref.xht
index f57fa43..3cec0f5 100644
--- a/css/css-multicol/multicol-fill-auto-block-children-002-ref.xht
+++ b/css/css-multicol/multicol-fill-auto-block-children-002-ref.xht
@@ -6,13 +6,15 @@
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-16 -->
   <meta name="flags" content="" />
   <style type="text/css"><![CDATA[
-  body {margin: 8px;}
+  body {margin:0; margin-top:8px;}
 
   div
   {
+  float: left;
   background-color: blue;
   height: 200px;
-  width: 680px;
+  width: 60%;
+  margin-left: 8px;
   }
 
   h1
@@ -20,51 +22,19 @@
   color: white;
   font-size: 2em;
   line-height: 1.25; /* or 1.21875 to achieve a 39px tall line box */
-  margin-top: 8px; /* The margin-top of body and h1 will collapse into an 8px gap */
-  margin-bottom: 21px;
-  padding-top: 21px;
+  margin: 21px 0em;
   }
 
   span#pass
   {
+  float: left;
+  margin-left: 10px;
   color: blue;
   font-size: 1.5em;
   font-weight: bolder;
-  left: 698px;
-
-  /*
-
-  Expected result:
-
-  8px                                           688px
-  v                                              v
-  ************************************************
-  *                                              *
-  *  <h1>Test passes if the word "PASS!" is<br />*  1st line box
-  *  on the right &#8600;</h1>                   *  2nd line box
-  *                                              *
-  ************************************************
-  *                                              *
-  * <h2>nbsp;<h2>  <h2>nbsp;<h2>  <h2>nbsp;<h2>  *  <h2>PASS!</h2>
-  *                                              *
-  ************************************************
-                ^              ^
-               228px          458px
-
-  */
 
   line-height: 1;
-  position: absolute;
-  top: 130px;
-
-  /*
-    8px : margin-top of body element
-   21px : margin-top of h1 element which must not collapse with body's margin-top
-   80px : content height: 2 line boxes required to render the "Test passes if ..." sentence
-   21px : margin-bottom of h1 element
- ====================================
-  130px : top position of span#pass in document box
-  */
+  margin-top: 122px;
   }
   ]]></style>
  </head>
@@ -72,8 +42,8 @@
 
   <div>
     <h1>Test passes if "PASS!" is<br />on the right &#8600;</h1>
-    <span id="pass">PASS!</span>
   </div>
+  <span id="pass">PASS!</span>
 
  </body>
 </html>
diff --git a/css/css-multicol/multicol-fill-auto-block-children-002.xht b/css/css-multicol/multicol-fill-auto-block-children-002.xht
index 9097e3c..d79fa95 100644
--- a/css/css-multicol/multicol-fill-auto-block-children-002.xht
+++ b/css/css-multicol/multicol-fill-auto-block-children-002.xht
@@ -18,16 +18,11 @@
   background-color: blue;
   height: 200px;
   margin: 8px;
-  width: 680px;
+  width: 60%;
 
   column-count: 3;
   column-fill: auto;
   column-gap: 10px;
-
-  /*
-  So, each column box should be
-  [680px minus (2 mult 10px)] divided by 3 == 220px wide
-  */
   }
 
   h1
@@ -75,25 +70,5 @@
 
   <h2>PASS!</h2>
 
-  <!--
-
-  Expected result:
-
-  8px                                           688px
-  v                                              v
-  ************************************************
-  *                                              *
-  *  <h1>Test passes if the word "PASS!" is<br />*  1st line box
-  *  on the right &#8600;</h1>                   *  2nd line box
-  *                                              *
-  ************************************************
-  *                                              *
-  * <h2>nbsp;<h2>  <h2>nbsp;<h2>  <h2>nbsp;<h2>  *  <h2>PASS!</h2>
-  *                                              *
-  ************************************************
-                ^              ^
-               228px          458px
-  -->
-
  </body>
 </html>
diff --git a/css/css-multicol/multicol-fill-auto-ref.xht b/css/css-multicol/multicol-fill-auto-ref.xht
deleted file mode 100644
index b287fb2..0000000
--- a/css/css-multicol/multicol-fill-auto-ref.xht
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-gap</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body>div {
-	font-family: ahem;
-	font-size: 0.5em;
-	line-height: 1em;
-	color: black;
-	height: 12em;
-	width: 100em;
-
-}
-div div {
-	float: left;
-	width: 26em;
-	margin-right: 5em;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<div>one two three four
-	five six seven eight
-	nine ten eleven twelve
-	thirtn fourtn fiftn sixtn
-	seventn eightn ninetn twenty
-	hundred thousand million billion
-	trillion
-	one two three four
-	five six seven eight
-	nine ten eleven twelve
-	thirtn fourtn fiftn sixtn
-	seventn eightn ninetn twenty
-	hundred</div>
-	<div>thousand million billion
-	trillion
-	one two three four
-	five six seven eight
-	nine ten eleven twelve
-	thirtn fourtn fiftn sixtn
-	seventn eightn ninetn twenty
-	hundred thousand million billion
-	trillion</div>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-fill-auto.xht b/css/css-multicol/multicol-fill-auto.xht
deleted file mode 100644
index 20fe7e4..0000000
--- a/css/css-multicol/multicol-fill-auto.xht
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-fill-auto</title>
-<meta name="assert" content="This test checks that columns are properly balanced when 'column-fill: auto' is set"/>
-<link rel="author" title="howcome@opera.com" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#filling-columns"/>
-<link rel="match" href="multicol-fill-ref.xht"/>
-<meta name="flags" content="ahem"/>
-<style type="text/css"><![CDATA[
-body>div {
-	font-family: ahem;
-	font-size: 1.25em;
-	line-height: 1em;
-	color: green;
-	height: 3em;
-	width: 2em;
-	orphans: 1;
-	widows: 1;
-	position: relative;
-	margin: 1em;
-}
-div.col {
-	column-count: 2;
-	column-fill: auto;
-	column-gap: 0;
-}
-div.red {
-	background: red; position: absolute; z-index: -1;
-}
-]]></style>
-</head>
-
-<body>
-
-<div class='col'>
-<div class='red' style="top: 0; left: 0; height: 3em; width: 1em;"></div>
-<div class='red' style="top: 0; left: 0; height: 1em; width: 2em;"></div>
-o<br/>t<br/>
-o<br/>t<br/>
-</div>
-
-<div class='ref'>
-oo<br/>t<br/>o
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-fill-balance-001-ref.xht b/css/css-multicol/multicol-fill-balance-001-ref.xht
index a91908a..bb61ec1 100644
--- a/css/css-multicol/multicol-fill-balance-001-ref.xht
+++ b/css/css-multicol/multicol-fill-balance-001-ref.xht
@@ -7,7 +7,7 @@
 <meta name="flags" content="ahem"/>
 <style type="text/css"><![CDATA[
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1.25em;
 	line-height: 1em;
 	color: green;
diff --git a/css/css-multicol/multicol-fill-balance-001.xht b/css/css-multicol/multicol-fill-balance-001.xht
index 7dbd140..be0136f 100644
--- a/css/css-multicol/multicol-fill-balance-001.xht
+++ b/css/css-multicol/multicol-fill-balance-001.xht
@@ -10,7 +10,7 @@
 <meta name="flags" content="ahem"/>
 <style type="text/css"><![CDATA[
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1.25em;
 	line-height: 1em;
 	color: green;
diff --git a/css/css-multicol/multicol-fill-balance-002.html b/css/css-multicol/multicol-fill-balance-002.html
new file mode 100644
index 0000000..fb6004d
--- /dev/null
+++ b/css/css-multicol/multicol-fill-balance-002.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>CSS Multi-column Layout Test: Balancing with more forced breaks than columns</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#cf" title="7.1. column-fill">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<meta name="assert" content="This multicol container will create overflowing columns, no matter what, due to forced break. Don't overstretch.">
+<p>Test passes if there is a filled green square.</p>
+<div style="columns:2; column-gap:0; width:100px; background:green;">
+  <div style="height:100px;"></div>
+  <div style="break-before:column; height:90px;"></div>
+  <div style="break-before:column; height:10px;"></div>
+</div>
diff --git a/css/css-multicol/multicol-fill-ref.xht b/css/css-multicol/multicol-fill-ref.xht
deleted file mode 100644
index 176631b..0000000
--- a/css/css-multicol/multicol-fill-ref.xht
+++ /dev/null
@@ -1,21 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-gap</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-div {
-	background: yellow;
-	height: 5em;
-	width: 30em;
-}
-]]></style>
-</head>
-
-<body>
-
-<div></div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-gap-001.xht b/css/css-multicol/multicol-gap-001.xht
index 28ff467..ce5d4c6 100644
--- a/css/css-multicol/multicol-gap-001.xht
+++ b/css/css-multicol/multicol-gap-001.xht
@@ -6,8 +6,8 @@
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-05 -->
   <link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-gap" title="4.1. 'column-gap'" />
   <link rel="match" href="multicol-gap-001-ref.xht" />
-  <meta name="flags" content="ahem may" />
-  <meta name="assert" content="This test checks that the 'normal' column gap is 1em, which is suggested -- and not prescribed -- by the specification." />
+  <meta name="flags" content="ahem" />
+  <meta name="assert" content="This test checks that the 'normal' column gap is 1em." />
   <style type="text/css"><![CDATA[
   div
   {
diff --git a/css/css-multicol/multicol-gap-003.xht b/css/css-multicol/multicol-gap-003.xht
index 180fd3c..33011b8 100644
--- a/css/css-multicol/multicol-gap-003.xht
+++ b/css/css-multicol/multicol-gap-003.xht
@@ -6,7 +6,7 @@
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-05 -->
   <link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-gap" title="4.1. 'column-gap'" />
   <link rel="match" href="multicol-gap-002-ref.xht" />
-  <meta name="flags" content="ahem may" />
+  <meta name="flags" content="ahem" />
   <style type="text/css"><![CDATA[
   div
   {
diff --git a/css/css-multicol/multicol-gap-animation-001.html b/css/css-multicol/multicol-gap-animation-001.html
new file mode 100644
index 0000000..6a3a8d3
--- /dev/null
+++ b/css/css-multicol/multicol-gap-animation-001.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Multi-column Layout Test: column-gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#column-gap">
+<link rel="help" href="https://www.w3.org/TR/web-animations-1/#dom-animatable-animate">
+<meta name="assert" content="Test checks that column-gap is an animatable property, and that the computed values during the animation are the expected ones.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #multicol {
+    column-gap: 0px;
+  }
+</style>
+<body>
+  <div id="multicol"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var multicol = document.getElementById("multicol");
+        assert_equals(getComputedStyle(multicol).columnGap, "0px");
+
+        var keyframes = [
+          { columnGap: "100px" },
+          { columnGap: "200px" }
+        ];
+        var options = {
+          duration: 10,
+          fill: "forwards",
+          easing: "linear"
+        };
+
+        var player = multicol.animate(keyframes, options);
+        player.pause();
+        player.currentTime = 0;
+        assert_equals(getComputedStyle(multicol).columnGap, "100px");
+        player.currentTime = 5;
+        assert_equals(getComputedStyle(multicol).columnGap, "150px");
+        player.currentTime = 10;
+        assert_equals(getComputedStyle(multicol).columnGap, "200px");
+      }, "column-gap property is animatable");
+  </script>
+</body>
diff --git a/css/css-multicol/multicol-gap-animation-002.html b/css/css-multicol/multicol-gap-animation-002.html
new file mode 100644
index 0000000..de3756c
--- /dev/null
+++ b/css/css-multicol/multicol-gap-animation-002.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Multi-column Layout Test: column-gap normal test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#column-gap">
+<link rel="help" href="https://www.w3.org/TR/web-animations-1/#dom-animatable-animate">
+<meta name="assert" content="Test checks that 'normal' value for column-gap property is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes column-gap-normal-to-100 {
+    from { column-gap: normal; }
+    to { column-gap: 100px; }
+  }
+
+  #multicol {
+    animation-name: column-gap-normal-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="multicol"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var multicol = document.getElementById("multicol");
+        assert_equals(getComputedStyle(multicol).columnGap, "100px");
+      }, "column-gap: normal is not interpolable");
+  </script>
+</body>
diff --git a/css/css-multicol/multicol-gap-animation-003.html b/css/css-multicol/multicol-gap-animation-003.html
new file mode 100644
index 0000000..71182fa
--- /dev/null
+++ b/css/css-multicol/multicol-gap-animation-003.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Multi-column Layout Test: Default column-gap test animation</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#column-gap">
+<link rel="help" href="https://www.w3.org/TR/web-animations-1/#dom-animatable-animate">
+<meta name="assert" content="Test checks that the default value for column-gap property, which is 'normal', is not interpolable.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes column-gap-to-100 {
+    to { column-gap: 100px; }
+  }
+
+  #multicol {
+    animation-name: column-gap-to-100;
+    animation-duration: 2s;
+    animation-delay: -1s;
+    animation-play-state: paused;
+  }
+</style>
+<body>
+  <div id="multicol"></div>
+  <div id="log"></div>
+
+  <script>
+    test(
+      function(){
+        var multicol = document.getElementById("multicol");
+        assert_equals(getComputedStyle(multicol).columnGap, "100px");
+      }, "Default column-gap is not interpolable");
+  </script>
+</body>
diff --git a/css/css-multicol/multicol-gap-fraction-002.html b/css/css-multicol/multicol-gap-fraction-002.html
new file mode 100644
index 0000000..ae1aaaa
--- /dev/null
+++ b/css/css-multicol/multicol-gap-fraction-002.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<title>CSS Multi-column Layout Test: 'column-gap' with sub-pixel values</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-gap" title="4.1. 'column-gap'">
+<link rel="match" href="../reference/nothing.html" />
+<meta name="assert" content="Specified column-gap should never be rounded up to the nearest integer, or the second column in the tests might overflow the multicol container in the inline direction.">
+<style>
+  .multicol { margin:10px; float:left; columns:2; width:50px; column-gap:10.9px; column-fill:auto; height:50px; background:white; }
+  .filler { height:50px; }
+  .below-everything { position:relative; z-index:-1; float:right; width:20px; height:20px; background:red; }
+</style>
+
+<p>There should be nothing below.</p>
+
+<div class="multicol" style="column-gap:10px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.1px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.2px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.3px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.4px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.5px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.6px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.7px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.8px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:10.9px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
+
+<div class="multicol" style="column-gap:11px;">
+  <div class="filler"></div>
+  <div class="below-everything"></div>
+</div>
diff --git a/css/css-multicol/multicol-gap-percentage-001.html b/css/css-multicol/multicol-gap-percentage-001.html
new file mode 100644
index 0000000..b086eba
--- /dev/null
+++ b/css/css-multicol/multicol-gap-percentage-001.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Multi-column Layout Test: column-gap supports percentages</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#column-gap">
+<meta name="assert" content="This test checks the behavior of precentage column-gap in different situations depending on the multicol container size.">
+<style>
+.multicol {
+  position: relative;
+  font: 20px/1 Ahem;
+  margin: 10px;
+  column-count: 2;
+  column-gap: 25%;
+  background: yellow;
+}
+
+.fixed200 {
+  width: 200px;
+}
+
+.inlineBlock {
+  display: inline-block;
+}
+
+.wrapper200 {
+  width: 200px;
+}
+
+.wrapper8 {
+  width: 8px;
+}
+
+.marginTopBottom {
+  margin: 10px 0;
+}
+
+.multicol > :nth-child(1) { background: magenta; }
+.multicol > :nth-child(2) { background: cyan; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.multicol')">
+
+<div id="log"></div>
+
+<h3>Multicol container: fixed width</h3>
+
+<div class="multicol fixed200" data-expected-width="200" data-expected-height="20">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="75" data-expected-height="20">X</div>
+  <div data-offset-x="125" data-offset-y="0" data-expected-width="75" data-expected-height="20">X</div>
+</div>
+
+<div class="multicol fixed200"
+    data-expected-width="200" data-expected-height="40">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="75" data-expected-height="40">XX X X</div>
+  <div data-offset-x="125" data-offset-y="0" data-expected-width="75" data-expected-height="40">XX X X</div>
+</div>
+
+<div class="multicol fixed200"
+    data-expected-width="200" data-expected-height="20">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="75" data-expected-height="20">XXXXX</div>
+  <div data-offset-x="125" data-offset-y="0" data-expected-width="75" data-expected-height="20">XXXXX</div>
+</div>
+
+<h3>Multicol container: intrinsic width</h3>
+
+<div class="multicol inlineBlock"
+    data-expected-width="40" data-expected-height="20">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="15" data-expected-height="20">X</div>
+  <div data-offset-x="25" data-offset-y="0" data-expected-width="15" data-expected-height="20">X</div>
+</div>
+
+<div class="multicol inlineBlock"
+    data-expected-width="240" data-expected-height="40">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="90" data-expected-height="40">XX X X</div>
+  <div data-offset-x="150" data-offset-y="0" data-expected-width="90" data-expected-height="40">XX X X</div>
+</div>
+
+<div class="multicol inlineBlock"
+    data-expected-width="200" data-expected-height="20">
+  <div data-offset-x="0" data-offset-y="0" data-expected-width="75" data-expected-height="20">XXXXX</div>
+  <div data-offset-x="125" data-offset-y="0" data-expected-width="75" data-expected-height="20">XXXXX</div>
+</div>
+
+<h3>Multicol container: auto width</h3>
+
+<div class="wrapper200">
+
+  <div class="multicol marginTopBottom"
+      data-expected-width="200" data-expected-height="20">
+    <div data-offset-x="0" data-offset-y="0" data-expected-width="75" data-expected-height="20">X</div>
+    <div data-offset-x="125" data-offset-y="0" data-expected-width="75" data-expected-height="20">X</div>
+  </div>
+
+  <div class="multicol marginTopBottom"
+      data-expected-width="200" data-expected-height="40">
+    <div data-offset-x="0" data-offset-y="0" data-expected-width="75" data-expected-height="40">XX X X</div>
+    <div data-offset-x="125" data-offset-y="0" data-expected-width="75" data-expected-height="40">XX X X</div>
+  </div>
+
+  <div class="multicol marginTopBottom"
+      data-expected-width="200" data-expected-height="20">
+    <div data-offset-x="0" data-offset-y="0" data-expected-width="75" data-expected-height="20">XXXXX</div>
+    <div data-offset-x="125" data-offset-y="0" data-expected-width="75" data-expected-height="20">XXXXX</div>
+  </div>
+
+</div>
+
+<div class="wrapper8">
+
+  <div class="multicol marginTopBottom"
+      data-expected-width="8" data-expected-height="20">
+    <div data-offset-x="0" data-offset-y="0" data-expected-width="3" data-expected-height="20">X</div>
+    <div data-offset-x="5" data-offset-y="0" data-expected-width="3" data-expected-height="20">X</div>
+  </div>
+
+  <div class="multicol marginTopBottom"
+      data-expected-width="8" data-expected-height="60">
+    <div data-offset-x="0" data-offset-y="0" data-expected-width="3" data-expected-height="60">XX X X</div>
+    <div data-offset-x="5" data-offset-y="0" data-expected-width="3" data-expected-height="60">XX X X</div>
+  </div>
+
+  <div class="multicol marginTopBottom"
+      data-expected-width="8" data-expected-height="20">
+    <div data-offset-x="0" data-offset-y="0" data-expected-width="3" data-expected-height="20">XXXXX</div>
+    <div data-offset-x="5" data-offset-y="0" data-expected-width="3" data-expected-height="20">XXXXX</div>
+  </div>
+
+</div>
diff --git a/css/css-multicol/multicol-height-block-child-001-ref.xht b/css/css-multicol/multicol-height-block-child-001-ref.xht
index 27a87c3..b238d5c 100644
--- a/css/css-multicol/multicol-height-block-child-001-ref.xht
+++ b/css/css-multicol/multicol-height-block-child-001-ref.xht
@@ -5,51 +5,34 @@
   <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-08 -->
   <meta name="flags" content="image" />
-  <style type="text/css"><![CDATA[
-  div
-  {
-  background-color: blue;
-  font: 1.25em/1 serif;
-  }
-
-  div#top
-  {
-  height: 5em;
-  width: 22em;
-  }
-
-  div#bottom
-  {
-  height: 3em;
-  width: 14em;
-  }
-
-  img {vertical-align: top;}
-  ]]></style>
  </head>
  <body>
-    <div id="top"><img src="support/black20x20.png" width="60" height="100" alt="Image download support must be enabled" /></div>
-
-  <div id="bottom"><img src="support/swatch-orange.png" width="60" height="60" alt="Image download support must be enabled" /></div>
+   <div style="font:20px/1 Ahem; width:22em; height:5em; background:blue;">
+     <div style="width:3em; height:5em; background:black;"></div>
+   </div>
+   <div style="font:20px/1 Ahem; width:14em; height:3em; background:blue;">
+     <div style="float:left; width:3em; height:3em; margin-right:5em; background:orange;"></div>
+     <div style="float:left; width:3em; height:3em; background:orange;"></div>
+   </div>
 
   <!--
 
   Expected results:
 
- 0em                        14em            22em
-  v                           v               v
+ 0em           7em          14em            22em
+  v             v             v               v
 0 *********************************************
-  * bla                       |               *
-  * bla                       |               *
-  * bla                       |               *
-  * bla                       |               *
-5 * bla                       |               *
-6 * orn                       |****************
-7 * orn                       |
-8 * orn                       |
+  * bla         |             |               *
+  * bla         |             |               *
+  * bla         |             |               *
+  * bla         |             |               *
+5 * bla         |             |               *
+6 * orn         | zzz         |****************
+7 * orn         | zzz         |
+8 * orn         | zzz         |
   *****************************
 
   -->
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/multicol-height-block-child-001.xht b/css/css-multicol/multicol-height-block-child-001.xht
index d7dfd3d..b62d92e 100644
--- a/css/css-multicol/multicol-height-block-child-001.xht
+++ b/css/css-multicol/multicol-height-block-child-001.xht
@@ -54,40 +54,35 @@
   </div>
 
   <!--
-  Both column boxes of div#inner start in the 1st
-  column box of div#outer multi-column element.
-  The first 3 lines boxes of 1st
-  column box of div#inner will be rendered inside the
-  1st column box of div#outer but not its 2nd.
-  Because 1st column box of div#outer is
-  6em wide while the div#inner multi-column element
-  is 14em wide, this makes the first 3 line boxes
-  (with the 'z' trios) of 2nd column box of div#inner
-  clipped.
+  Both column boxes of div#inner start in the 1st column box of
+  div#outer multi-column element.  All the 6 lines of div#inner will
+  be rendered inside the 1st column box of div#outer. The first three
+  lines in the first inner column, and the last three in the second
+  inner column, which will overflow the first outer column, and
+  actually appear as if they were part of the second outer column. No
+  clipping should occur, unless explicitly requested.
 
-  Then, at this point, the div#inner multi-column
-  element should be fragmented in order to continue to
-  render the 5 empty line boxes it has into the
-  2nd column box of div#outer. Since those 5 line
-  boxes are empty, we only see the extension it
-  creates horizontally, which is
-  14em - 6em == 8em horizontally and
-  5 lines boxes == 5em tall
-
+  All the 6 lines of #inner fit in a column row that resides in the
+  first outer column. #inner has a fixed height, and is taller than
+  what can fit in the first outer column, so it needs to continue in
+  the second outer column, with no inner column content left to
+  fragment. #inner is a 14em wide block that is put into a 6em wide
+  outer column, so it will overflow the outer column all the way to
+  22em, relatively to the left edge of the outer multicol container.
 
   Expected results:
 
- 0em                        14em            22em
-  v                           v               v
+ 0em           7em          14em            22em
+  v             v             v               v
 0 *********************************************
-  * bla                       |               *
-  * bla                       |               *
-  * bla                       |               *
-  * bla                       |               *
-5 * bla                       |               *
-6 * orn                       |****************
-7 * orn                       |
-8 * orn                       |
+  * bla         |             |               *
+  * bla         |             |               *
+  * bla         |             |               *
+  * bla         |             |               *
+5 * bla         |             |               *
+6 * orn         | zzz         |****************
+7 * orn         | zzz         |
+8 * orn         | zzz         |
   *****************************
 
   -->
diff --git a/css/css-multicol/multicol-inherit-003.xht b/css/css-multicol/multicol-inherit-003.xht
index aa5bac9..42f0b12 100644
--- a/css/css-multicol/multicol-inherit-003.xht
+++ b/css/css-multicol/multicol-inherit-003.xht
@@ -16,7 +16,7 @@
 	column-width: 8em;
 }
 div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-inherit-004.xht b/css/css-multicol/multicol-inherit-004.xht
deleted file mode 100644
index 2272c43..0000000
--- a/css/css-multicol/multicol-inherit-004.xht
+++ /dev/null
@@ -1,55 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | inheritance</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-gaps-and-rules"/>
-<link rel="match" href="multicol-inherit-4-ref.xht"/>
-<meta name="flags" content="ahem"/>
-<style type="text/css"><![CDATA[
-body>div {
-	width: 74em;
-	column-rule: 1em solid blue;
-}
-div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: white;
-	background: white;
-	margin: 1em;
-	orphans: 1;
-	widows: 1;
-
-	column-count: 3;
-	column-gap: 7em;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<div>
-		xx xx
-		xx xx
-		xx xx
-		xx xx
-	</div>
-	<div>
-		xx xx
-		xx xx
-		xx xx
-		xx xx
-	</div>
-	<div>
-		xx xx
-		xx xx
-		xx xx
-		xx xx
-	</div>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-inherit-3-ref.xht b/css/css-multicol/multicol-inherit-3-ref.xht
index 8016fb7..8ab11de 100644
--- a/css/css-multicol/multicol-inherit-3-ref.xht
+++ b/css/css-multicol/multicol-inherit-3-ref.xht
@@ -22,7 +22,7 @@
 #two {left: 19em;}
 #three {left: 38em;}
 div div div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	width: 2em;
diff --git a/css/css-multicol/multicol-inherit-4-ref.xht b/css/css-multicol/multicol-inherit-4-ref.xht
deleted file mode 100644
index 2972d71..0000000
--- a/css/css-multicol/multicol-inherit-4-ref.xht
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | inheritance</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body>div {
-	margin: 1em;
-	background: white;
-	width: 74em;
-	height: 5em;
-	position: relative;
-}
-div div {
-	background: blue;
-	height: 5em;
-	width: 1em;
-	position: absolute;
-	top: 0;
-	left: 23em;
-}
-div>div+div {
-	left: 50em;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<div></div>
-	<div></div>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-nested-column-rule-001-ref.xht b/css/css-multicol/multicol-nested-column-rule-001-ref.xht
index 8d86b42..495ca5d 100644
--- a/css/css-multicol/multicol-nested-column-rule-001-ref.xht
+++ b/css/css-multicol/multicol-nested-column-rule-001-ref.xht
@@ -5,41 +5,8 @@
   <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-09 -->
   <meta name="flags" content="" />
-  <style type="text/css"><![CDATA[
-  div#rel-pos-wrapper
-  {
-  font: 1.25em/1 serif;
-  position: relative;
-  }
-
-  div#rel-pos-wrapper > div
-  {
-  background-color: blue;
-  font-size: 1em;
-  height: 3em;
-  position: absolute;
-  width: 1em;
-  }
-
-  div#first-column-rule
-  {
-  left: 14em;
-  top: auto;
-  }
-
-  div#second-column-rule
-  {
-  left: 29em;
-  top: auto;
-  }
-   ]]></style>
  </head>
  <body>
-
-  <div id="rel-pos-wrapper">
-	<div id="first-column-rule"></div>
-	<div id="second-column-rule"></div>
-  </div>
-
+   <div style="font: 1.25em/1 Ahem; width:8em; height:3em; margin-left:9em; border-left:1em solid blue; border-right:1em solid blue;"></div>
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/multicol-nested-column-rule-001.xht b/css/css-multicol/multicol-nested-column-rule-001.xht
index 11a44f4..78fde1f 100644
--- a/css/css-multicol/multicol-nested-column-rule-001.xht
+++ b/css/css-multicol/multicol-nested-column-rule-001.xht
@@ -18,7 +18,7 @@
   {
   column-rule: blue solid 1em;
   font: 1.25em/1 Ahem;
-  width: 42em;
+  width: 26em;
   }
 
   /*
@@ -26,47 +26,47 @@
   N == 3;
 
   W == max(0, (available-width - ((N - 1) * column-gap)) / N);
-  W == max(0, (42em - ((3 - 1) * 3em)) / 3);
-  W == max(0, (42em - (2 * 3em)) / 3);
-  W == max(0, (42em - 6em) / 3);
-  W == max(0, 36em / 3);
-  W == max(0, 12em);
-  W == 12em;
+  W == max(0, (26em - ((3 - 1) * 1em)) / 3);
+  W == max(0, (26em - (2 * 1em)) / 3);
+  W == max(0, (26em - 2em) / 3);
+  W == max(0, 24em / 3);
+  W == max(0, 8em);
+  W == 8em;
 
   So, the first column-rule should be at:
 
     1.0em : margin-left of outer div
-   12.0em : width of 1st column box
-    1.0em : (3.0em / 2) - (1.0em / 2) : left edge of 1st column-rule
+    8.0em : width of 1st column box
+    0.0em : (1.0em / 2) - (1.0em / 2) : left edge of 1st column-rule
   =========
-   14.0em
+    9.0em
 
   The 2nd column-rule should be at:
 
     1.0em : margin-left of outer div
-   12.0em : width of 1st column box
-    3.0em : first column-gap
-   12.0em : width of 2nd column box
-    1.0em : (3.0em / 2) - (1.0em / 2) : left edge of 2nd column-rule
+    8.0em : width of 1st column box
+    1.0em : first -moz-column-gap
+    8.0em : width of 2nd column box
+    0.0em : (1.0em / 2) - (1.0em / 2) : left edge of 2nd column-rule
   =========
-   29.0em
+   18.0em
 
   The height of column rule depends on number of line boxes in
   each outer column box which depends on number of line boxes
   in each inner column box. So:
 
-    12em : width of each outer column box
+     8em : width of each outer column box
    -
      2em : horizontal margin of each div inside
    =======
-    10em : width of each inner multi-column elements
+     6em : width of each inner multi-column elements
 
   N == 3;
 
-  W == max(0, (available-width - ((N - 1) * column-gap)) / N);
-  W == max(0, (10em - ((3 - 1) * 3em)) / 3);
-  W == max(0, (10em - (2 * 3em)) / 3);
-  W == max(0, (10em - 6em) / 3);
+  W == max(0, (available-width - ((N - 1) * -column-gap)) / N);
+  W == max(0, (6em - ((3 - 1) * 1em)) / 3);
+  W == max(0, (6em - (2 * 1em)) / 3);
+  W == max(0, (6em - 2em) / 3);
   W == max(0, 4em / 3);
   W == max(0, 1.33333em);
   W == 1.33333em;
@@ -90,7 +90,7 @@
   widows: 1;
 
   column-count: 3;
-  column-gap: 3em;
+  column-gap: 1em;
   }
   ]]></style>
  </head>
diff --git a/css/css-multicol/multicol-rule-002.xht b/css/css-multicol/multicol-rule-002.xht
index d187c3d..62c6aeb 100644
--- a/css/css-multicol/multicol-rule-002.xht
+++ b/css/css-multicol/multicol-rule-002.xht
@@ -12,7 +12,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-rule-fraction-003.xht b/css/css-multicol/multicol-rule-fraction-003.xht
index 3ba5044..81bed22 100644
--- a/css/css-multicol/multicol-rule-fraction-003.xht
+++ b/css/css-multicol/multicol-rule-fraction-003.xht
@@ -12,7 +12,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-rule-fraction-3-ref.xht b/css/css-multicol/multicol-rule-fraction-3-ref.xht
index 9189b97..d0e3396 100644
--- a/css/css-multicol/multicol-rule-fraction-3-ref.xht
+++ b/css/css-multicol/multicol-rule-fraction-3-ref.xht
@@ -9,7 +9,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
@@ -32,9 +32,9 @@
 	background: blue;
 	width: 1em;
 }
-#a1 {left: 2.43em;}
+#a1 {left: 2.4em;}
 #a2 {left: 3.75em;}
-#a3 {left: 6.13em;}
+#a3 {left: 6.15em;}
 #a4 {left: 7.5em;}
 #a5 {left: 9.9em;}
 #a6 {left: 11.25em;}
diff --git a/css/css-multicol/multicol-rule-px-001.xht b/css/css-multicol/multicol-rule-px-001.xht
index 2fd1ee5..5df291a 100644
--- a/css/css-multicol/multicol-rule-px-001.xht
+++ b/css/css-multicol/multicol-rule-px-001.xht
@@ -12,7 +12,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-rule-ref.xht b/css/css-multicol/multicol-rule-ref.xht
index fb1e3ed..808de6f 100644
--- a/css/css-multicol/multicol-rule-ref.xht
+++ b/css/css-multicol/multicol-rule-ref.xht
@@ -9,7 +9,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-rule-samelength-001.xht b/css/css-multicol/multicol-rule-samelength-001.xht
index 431eb74..e7eab8e 100644
--- a/css/css-multicol/multicol-rule-samelength-001.xht
+++ b/css/css-multicol/multicol-rule-samelength-001.xht
@@ -1,7 +1,7 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
-  <title>CSS Multi-column Layout Test: 'column-rule-width' has same lenght as 'column-gap'</title>
+  <title>CSS Multi-column Layout Test: 'column-rule-width' has same length as 'column-gap'</title>
   <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-15 -->
   <link rel="help" href="http://www.w3.org/TR/css3-multicol/#crw" title="4.4. 'column-rule-width'" />
@@ -68,4 +68,4 @@
   -->
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/multicol-rule-shorthand-2-ref.xht b/css/css-multicol/multicol-rule-shorthand-2-ref.xht
index e53487b..7572833 100644
--- a/css/css-multicol/multicol-rule-shorthand-2-ref.xht
+++ b/css/css-multicol/multicol-rule-shorthand-2-ref.xht
@@ -7,7 +7,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-rule-shorthand-2.xht b/css/css-multicol/multicol-rule-shorthand-2.xht
index 0e5282b..ab7d756 100644
--- a/css/css-multicol/multicol-rule-shorthand-2.xht
+++ b/css/css-multicol/multicol-rule-shorthand-2.xht
@@ -12,7 +12,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-rule-stacking-001.xht b/css/css-multicol/multicol-rule-stacking-001.xht
index 60045fd..2794e97 100644
--- a/css/css-multicol/multicol-rule-stacking-001.xht
+++ b/css/css-multicol/multicol-rule-stacking-001.xht
@@ -12,7 +12,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: blue;
diff --git a/css/css-multicol/multicol-rule-stacking-ref.xht b/css/css-multicol/multicol-rule-stacking-ref.xht
index 1eac917..5eea2ac 100644
--- a/css/css-multicol/multicol-rule-stacking-ref.xht
+++ b/css/css-multicol/multicol-rule-stacking-ref.xht
@@ -9,7 +9,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-rule-style-groove-001-ref.xht b/css/css-multicol/multicol-rule-style-groove-001-ref.xht
deleted file mode 100644
index a32a71e..0000000
--- a/css/css-multicol/multicol-rule-style-groove-001-ref.xht
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-  <title>CSS Reftest Reference</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-15 -->
-  <meta name="flags" content="ahem" />
-  <style type="text/css"><![CDATA[
-  body {margin: 8px;}
-
-  p
-  {
-  line-height: 1.25em;
-  margin: 1em 0em;
-  }
-
-  strong {line-height: 1;}
-
-  div
-  {
-  border-collapse: collapse;
-  border-left: orange groove 100px;
-  color: transparent;
-  display: table;
-  font: 6.25em/1 Ahem;
-  }
-
-  div + div
-  {
-  position: relative;
-  top: 10px;
-  }
-  ]]></style>
- </head>
- <body>
-
-  <p>Test passes if the 2 orange squares are <strong>identical</strong>.</p>
-
-  <div>T</div>
-
-  <div>T</div>
-
- </body>
-</html>
\ No newline at end of file
diff --git a/css/css-multicol/multicol-rule-style-groove-001.xht b/css/css-multicol/multicol-rule-style-groove-001.xht
deleted file mode 100644
index 43a4a21..0000000
--- a/css/css-multicol/multicol-rule-style-groove-001.xht
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Multi-column Layout Test: 'column-rule-style' groove</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-15 -->
-  <link rel="help" href="http://www.w3.org/TR/css3-multicol/#crs" title="4.3. 'column-rule-style'" />
-  <link rel="match" href="multicol-rule-style-groove-001-ref.xht" />
-  <meta name="flags" content="ahem" />
-  <style type="text/css"><![CDATA[
-  body {margin: 8px;}
-
-  p
-  {
-  line-height: 1.25em;
-  margin: 1em 0em;
-  }
-
-  strong {line-height: 1;}
-
-  div {color: transparent;}
-
-  div#reference
-  {
-  border-collapse: collapse;
-  border-left: orange groove 100px;
-  font: 6.25em/1 Ahem;
-  display: table;
-  }
-
-  div#test
-  {
-  font: 1.25em/1 Ahem; /* equivalent to 20px/1 Ahem */
-  position: relative;
-  right: 40px;
-  top: 0.5em;
-  width: 9em;
-
-  column-count: 2;
-  column-gap: 5em;
-  column-rule: orange groove 5em;
-  }
-  ]]></style>
- </head>
-
- <body>
-
-  <p>Test passes if the 2 orange squares are <strong>identical</strong>.</p>
-
-  <div id="reference">T</div>
-
-  <div id="test">Le ft Co lu mn Ri gh Co lu mn</div>
-
-  <!--
-
-  Expected results
-    column-gap
-    column-rule
-     v=====v
-  *************
-  |Le|     |Ri|
-  |ft|     |gh|
-  |Co|     |Co|
-  |lu|     |lu|
-  |mn|     |mn|
-  *************
-
-  -->
-
- </body>
-</html>
\ No newline at end of file
diff --git a/css/css-multicol/multicol-rule-style-inset-001.xht b/css/css-multicol/multicol-rule-style-inset-001.xht
deleted file mode 100644
index d50bec9..0000000
--- a/css/css-multicol/multicol-rule-style-inset-001.xht
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Multi-column Layout Test: 'column-rule-style' inset</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-15 -->
-  <link rel="help" href="http://www.w3.org/TR/css3-multicol/#crs" title="4.3. 'column-rule-style'" />
-  <link rel="match" href="multicol-rule-style-ridge-001-ref.xht" />
-  <meta name="flags" content="ahem" />
-  <style type="text/css"><![CDATA[
-  body {margin: 8px;}
-
-  p
-  {
-  line-height: 1.25em;
-  margin: 1em 0em;
-  }
-
-  strong {line-height: 1;}
-
-  div {color: transparent;}
-
-  div#reference
-  {
-  border-collapse: collapse;
-  border-left: orange inset 100px;
-  font: 6.25em/1 Ahem;
-  display: table;
-  }
-
-  div#test
-  {
-  font: 1.25em/1 Ahem; /* equivalent to 20px/1 Ahem */
-  position: relative;
-  right: 40px;
-  top: 0.5em;
-  width: 9em;
-
-  column-count: 2;
-  column-gap: 5em;
-  column-rule: orange inset 5em;
-  }
-  ]]></style>
- </head>
-
- <body>
-
-  <p>Test passes if the 2 orange squares are <strong>identical</strong>.</p>
-
-  <div id="reference">T</div>
-
-  <div id="test">Le ft Co lu mn Ri gh Co lu mn</div>
-
-  <!--
-
-  Expected results
-    column-gap
-    column-rule
-     v=====v
-  *************
-  |Le|     |Ri|
-  |ft|     |gh|
-  |Co|     |Co|
-  |lu|     |lu|
-  |mn|     |mn|
-  *************
-
-  -->
-
- </body>
-</html>
\ No newline at end of file
diff --git a/css/css-multicol/multicol-rule-style-outset-001.xht b/css/css-multicol/multicol-rule-style-outset-001.xht
deleted file mode 100644
index ea8c332..0000000
--- a/css/css-multicol/multicol-rule-style-outset-001.xht
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Multi-column Layout Test: 'column-rule-style' outset</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-15 -->
-  <link rel="help" href="http://www.w3.org/TR/css3-multicol/#crs" title="4.3. 'column-rule-style'" />
-  <link rel="match" href="multicol-rule-style-groove-001-ref.xht" />
-  <meta name="flags" content="ahem" />
-  <style type="text/css"><![CDATA[
-  body {margin: 8px;}
-
-  p
-  {
-  line-height: 1.25em;
-  margin: 1em 0em;
-  }
-
-  strong {line-height: 1;}
-
-  div {color: transparent;}
-
-  div#reference
-  {
-  border-collapse: collapse;
-  border-left: orange outset 100px;
-  font: 6.25em/1 Ahem;
-  display: table;
-  }
-
-  div#test
-  {
-  font: 1.25em/1 Ahem; /* equivalent to 20px/1 Ahem */
-  position: relative;
-  right: 40px;
-  top: 0.5em;
-  width: 9em;
-
-  column-count: 2;
-  column-gap: 5em;
-  column-rule: orange outset 5em;
-  }
-  ]]></style>
- </head>
-
- <body>
-
-  <p>Test passes if the 2 orange squares are <strong>identical</strong>.</p>
-
-  <div id="reference">T</div>
-
-  <div id="test">Le ft Co lu mn Ri gh Co lu mn</div>
-
-  <!--
-
-  Expected results
-    column-gap
-    column-rule
-     v=====v
-  *************
-  |Le|     |Ri|
-  |ft|     |gh|
-  |Co|     |Co|
-  |lu|     |lu|
-  |mn|     |mn|
-  *************
-
-  -->
-
- </body>
-</html>
\ No newline at end of file
diff --git a/css/css-multicol/multicol-rule-style-ridge-001-ref.xht b/css/css-multicol/multicol-rule-style-ridge-001-ref.xht
deleted file mode 100644
index 4da6ec8..0000000
--- a/css/css-multicol/multicol-rule-style-ridge-001-ref.xht
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-  <title>CSS Reftest Reference</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-15 -->
-  <meta name="flags" content="ahem" />
-  <style type="text/css"><![CDATA[
-  body {margin: 8px;}
-
-  p
-  {
-  line-height: 1.25em;
-  margin: 1em 0em;
-  }
-
-  strong {line-height: 1;}
-
-  div
-  {
-  border-collapse: collapse;
-  border-left: orange ridge 100px;
-  color: transparent;
-  display: table;
-  font: 6.25em/1 Ahem;
-  }
-
-  div + div
-  {
-  position: relative;
-  top: 10px;
-  }
-  ]]></style>
- </head>
- <body>
-
-  <p>Test passes if the 2 orange squares are <strong>identical</strong>.</p>
-
-  <div>T</div>
-
-  <div>T</div>
-
- </body>
-</html>
\ No newline at end of file
diff --git a/css/css-multicol/multicol-rule-style-ridge-001.xht b/css/css-multicol/multicol-rule-style-ridge-001.xht
deleted file mode 100644
index 1cc97df..0000000
--- a/css/css-multicol/multicol-rule-style-ridge-001.xht
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Multi-column Layout Test: 'column-rule-style' ridge</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-15 -->
-  <link rel="help" href="http://www.w3.org/TR/css3-multicol/#crs" title="4.3. 'column-rule-style'" />
-  <link rel="match" href="multicol-rule-style-ridge-001-ref.xht" />
-  <meta name="flags" content="ahem" />
-  <style type="text/css"><![CDATA[
-  body {margin: 8px;}
-
-  p
-  {
-  line-height: 1.25em;
-  margin: 1em 0em;
-  }
-
-  strong {line-height: 1;}
-
-  div {color: transparent;}
-
-  div#reference
-  {
-  border-collapse: collapse;
-  border-left: orange ridge 100px;
-  font: 6.25em/1 Ahem;
-  display: table;
-  }
-
-  div#test
-  {
-  font: 1.25em/1 Ahem; /* equivalent to 20px/1 Ahem */
-  position: relative;
-  right: 40px;
-  top: 0.5em;
-  width: 9em;
-
-  column-count: 2;
-  column-gap: 5em;
-  column-rule: orange ridge 5em;
-  }
-  ]]></style>
- </head>
-
- <body>
-
-  <p>Test passes if the 2 orange squares are <strong>identical</strong>.</p>
-
-  <div id="reference">T</div>
-
-  <div id="test">Le ft Co lu mn Ri gh Co lu mn</div>
-
-  <!--
-
-  Expected results
-    column-gap
-    column-rule
-     v=====v
-  *************
-  |Le|     |Ri|
-  |ft|     |gh|
-  |Co|     |Co|
-  |lu|     |lu|
-  |mn|     |mn|
-  *************
-
-  -->
-
- </body>
-</html>
\ No newline at end of file
diff --git a/css/css-multicol/multicol-shorthand-001.xht b/css/css-multicol/multicol-shorthand-001.xht
index f06a53e..c9bfdc4 100644
--- a/css/css-multicol/multicol-shorthand-001.xht
+++ b/css/css-multicol/multicol-shorthand-001.xht
@@ -12,7 +12,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: black;
diff --git a/css/css-multicol/multicol-span-all-003.xht b/css/css-multicol/multicol-span-all-003.xht
index 9986f93..fb4a508 100644
--- a/css/css-multicol/multicol-span-all-003.xht
+++ b/css/css-multicol/multicol-span-all-003.xht
@@ -26,6 +26,8 @@
 
   column-count: 4;
   column-gap: 0;
+  orphans: 1;
+  widows: 1;
   }
 
   h4
@@ -45,4 +47,4 @@
   1 22&nbsp; 1 22&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp; 1 333 1 333 1 333 55555 1 22&nbsp; 1 22&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp; 1 22&nbsp; 1 22&nbsp; 1 22&nbsp; 55555 1 333 1 333 1&nbsp;&nbsp;&nbsp;&nbsp; 4444 4444 1&nbsp;&nbsp;&nbsp;&nbsp; 55555 1 333 1 333 1&nbsp;&nbsp;&nbsp;&nbsp; 4444 4444 1&nbsp;&nbsp;&nbsp;&nbsp; 55555
   </div>
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/multicol-span-all-block-sibling-003.xht b/css/css-multicol/multicol-span-all-block-sibling-003.xht
index 484b603..f887d75 100644
--- a/css/css-multicol/multicol-span-all-block-sibling-003.xht
+++ b/css/css-multicol/multicol-span-all-block-sibling-003.xht
@@ -9,7 +9,7 @@
 <meta name="flags" content="ahem"/>
 <style type="text/css"><![CDATA[
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1;
 	color: black;
diff --git a/css/css-multicol/multicol-span-all-block-sibling-3-ref.xht b/css/css-multicol/multicol-span-all-block-sibling-3-ref.xht
index e908215..83debd1 100644
--- a/css/css-multicol/multicol-span-all-block-sibling-3-ref.xht
+++ b/css/css-multicol/multicol-span-all-block-sibling-3-ref.xht
@@ -6,7 +6,7 @@
 <link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
 <style type="text/css"><![CDATA[
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1;
 	color: black;
diff --git a/css/css-multicol/multicol-span-all-child-001-ref.xht b/css/css-multicol/multicol-span-all-child-001-ref.xht
deleted file mode 100644
index fe82d6a..0000000
--- a/css/css-multicol/multicol-span-all-child-001-ref.xht
+++ /dev/null
@@ -1,109 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-  <title>CSS Reftest Reference</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-19 -->
-  <meta name="flags" content="" />
-  <style type="text/css"><![CDATA[
-  div#rel-pos-wrapper
-  {
-  background-color: blue;
-  font-size: 1.25em;
-  height: 10em;
-  position: relative;
-  width: 12em;
-  }
-
-  div > div {position: absolute;}
-
-  div#first-black
-  {
-  background-color: black;
-  left: 7em;
-  height: 1em;
-  top: 0em;
-  width: 5em;
-  }
-
-  div#first-yellow
-  {
-  background-color: yellow;
-  left: 7em;
-  height: 3em;
-  top: 1em;
-  width: 1em;
-  }
-
-  div#first-white
-  {
-  background-color: white;
-  left: 10em;
-  height: 9em;
-  top: 1em;
-  width: 2em;
-  }
-
-  div#second-black
-  {
-  background-color: black;
-  left: 2em;
-  height: 1em;
-  top: 2em;
-  width: 3em;
-  }
-
-  div#second-yellow
-  {
-  background-color: yellow;
-  left: 2em;
-  height: 3em;
-  top: 3em;
-  width: 1em;
-  }
-
-  div#third-black
-  {
-  background-color: black;
-  left: 2em;
-  height: 1em;
-  top: 6em;
-  width: 3em;
-  }
-
-  div#third-yellow
-  {
-  background-color: yellow;
-  left: 2em;
-  height: 3em;
-  top: 7em;
-  width: 1em;
-  }
-
-  div#second-white
-  {
-  background-color: white;
-  left: 5em;
-  height: 4em;
-  top: 6em;
-  width: 7em;
-  }
-  ]]></style>
- </head>
- <body>
-
-  <div id="rel-pos-wrapper">
-
-    <div id="first-black"></div>
-    <div id="first-yellow"></div>
-    <div id="first-white"></div>
-    <div id="second-black"></div>
-    <div id="second-yellow"></div>
-    <div id="third-black"></div>
-    <div id="third-yellow"></div>
-    <div id="second-white"></div>
-
-  </div>
-
- </body>
-</html>
diff --git a/css/css-multicol/multicol-span-all-child-001.xht b/css/css-multicol/multicol-span-all-child-001.xht
deleted file mode 100644
index ebc38ab..0000000
--- a/css/css-multicol/multicol-span-all-child-001.xht
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Multi-column Layout Test: 'column-span: all' element with block children (complex)</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-19 -->
-  <link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-span" title="6.1. 'column-span'" />
-  <link rel="bookmark" title="[css3-multicol] spanning element with only block children" href="http://lists.w3.org/Archives/Public/www-style/2013Aug/0318.html" />
-  <link rel="match" href="multicol-span-all-child-001-ref.xht" />
-  <meta name="flags" content="ahem may" />
-  <meta name="assert" content="This test checks how a spanning element with block children is rendered inside a multi-column element with a set height which is insufficient for rendering the entire spanning element. Since the available height (10em)is insufficient to render the spanning element (height needed: 3 times 4em) entirely inside the multi-column element, then UA may treat 'column-span: all' as 'column-span: none'." />
-  <style type="text/css"><![CDATA[
-  body
-  {
-  color: black;
-  font: 1.25em/1 Ahem;
-  height: 10em;
-  orphans: 1;
-  widows: 1;
-  width: 10em;
-
-  column-count: 2;
-  column-fill: auto;
-  column-gap: 0em;
-  }
-
-  div
-  {
-  background-color: yellow;
-  border: blue solid 2em;
-
-  column-span: all;
-  }
-
-  span
-  {
-  display: block;
-  height: 4em;
-  width: 5em;
-  }
-  ]]></style>
- </head>
-
- <body>
-
-  <div>
-    <span>block</span>
-    <span>block</span>
-    <span>block</span>
-  </div>
-
- </body>
-</html>
\ No newline at end of file
diff --git a/css/css-multicol/multicol-span-all-child-002-ref.xht b/css/css-multicol/multicol-span-all-child-002-ref.xht
deleted file mode 100644
index 417bbe1..0000000
--- a/css/css-multicol/multicol-span-all-child-002-ref.xht
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-  <title>CSS Reftest Reference</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-19 -->
-  <meta name="flags" content="" />
-  <style type="text/css"><![CDATA[
-  div#multi-column
-  {
-  background-color: black;
-  height: 8em;
-  width: 10em;
-  }
-
-  div#overflow
-  {
-  bottom: 8em;
-  font: 1em/1 serif;
-  margin-left: 10em;
-  position: relative;
-  }
-  ]]></style>
- </head>
- <body>
-
-  <div id="multi-column"></div>
-
-  <div id="overflow">abc deg<br />ghk mno</div>
-
- </body>
-</html>
diff --git a/css/css-multicol/multicol-span-all-child-002.xht b/css/css-multicol/multicol-span-all-child-002.xht
deleted file mode 100644
index 72cd8e6..0000000
--- a/css/css-multicol/multicol-span-all-child-002.xht
+++ /dev/null
@@ -1,86 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Multi-column Layout Test: 'column-span: all' element with block children (complex)</title>
-  <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
-  <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-19 -->
-  <link rel="help" href="http://www.w3.org/TR/css3-multicol/#column-span" title="6.1. 'column-span'" />
-  <link rel="bookmark" title="[css3-multicol] spanning element with only block children" href="http://lists.w3.org/Archives/Public/www-style/2013Aug/0318.html" />
-  <link rel="match" href="multicol-span-all-child-002-ref.xht" />
-  <meta name="flags" content="may" />
-  <meta name="assert" content="This test checks how spanning elements with block children is rendered inside a multi-column element with a set height which is insufficient for rendering both spanning elements entirely. Since available space (height of 8em) is insufficient to render both spanning elements (4 times 4em plus 2em) entirely inside the multi-column element, then UA may treat 'column-span: all' as 'column-span: none'." />
-  <style type="text/css"><![CDATA[
-  html {background-color: white;}
-
-  body
-  {
-  background-color: black;
-  height: 8em;
-  orphans: 1;
-  widows: 1;
-  width: 10em;
-
-  column-count: 2;
-  column-gap: 0em;
-
-  /*
-
-  N == 2;
-
-  W == 5em;
-
-  */
-
-  }
-
-  div, p
-  {
-  font: 1em/1 serif;
-  margin-top: 0em;
-
-  column-span: all;
-  }
-
-  span
-  {
-  color: black;
-  display: block;
-  height: 4em;
-  width: 5em;
-  }
-  ]]></style>
- </head>
-
- <body>
-
-  <div>
-    <span>FAIL</span>
-    <span>FAIL</span>
-    <span>FAIL</span>
-    <span>FAIL</span>
-  </div>
-
-  <p>abc deg ghk mno</p>
-
-  <!--
-
-  Expected results:
-
-  ************************
-  |FAIL       |FAIL      |abc deg
-  |           |          |ghk mno
-  |           |          |
-  |           |          |
-  |FAIL       |FAIL      |
-  |           |          |
-  |           |          |
-  |           |          |
-  ************************
-
-  The test presumes, postulates that
-  "abc deg" occupies not more than 5em.
-
-  -->
-
- </body>
-</html>
diff --git a/css/css-multicol/multicol-span-all-margin-nested-003.xht b/css/css-multicol/multicol-span-all-margin-nested-003.xht
deleted file mode 100644
index 9aef675..0000000
--- a/css/css-multicol/multicol-span-all-margin-nested-003.xht
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolomn | column-span inside block</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#spanning-columns"/>
-<link rel="match" href="multicol-span-all-margin-nested-3-ref.xht"/>
-<meta name="flags" content="ahem"/>
-<style type="text/css"><![CDATA[
-body>div {
-	font-family: ahem;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: black;
-	position: absolute;
-	orphans: 1;
-	widows: 1;
-
-	column-count: 4;
-}
-#spanned {
-	column-span: all;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-	<div>
-		<div id="spanned">x</div>
-	</div>
-	FAIL FAIL FAIL FAIL
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-span-all-margin-nested-3-ref.xht b/css/css-multicol/multicol-span-all-margin-nested-3-ref.xht
deleted file mode 100644
index 105d22b..0000000
--- a/css/css-multicol/multicol-span-all-margin-nested-3-ref.xht
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolomn | column-span inside block</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body>div {
-	background: black;
-	width: 22em;
-	height: 2em;
-	position: absolute;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-span-all-margin-nested-firstchild-001.xht b/css/css-multicol/multicol-span-all-margin-nested-firstchild-001.xht
index 488d75a..5c4483e 100644
--- a/css/css-multicol/multicol-span-all-margin-nested-firstchild-001.xht
+++ b/css/css-multicol/multicol-span-all-margin-nested-firstchild-001.xht
@@ -12,7 +12,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: navy;
@@ -29,7 +29,7 @@
 	column-gap: 0;
 }
 span, h6 {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	font-weight: normal;
 	line-height: 1em;
diff --git a/css/css-multicol/multicol-span-all-margin-nested-firstchild-ref.xht b/css/css-multicol/multicol-span-all-margin-nested-firstchild-ref.xht
index 92557cc..c2977be 100644
--- a/css/css-multicol/multicol-span-all-margin-nested-firstchild-ref.xht
+++ b/css/css-multicol/multicol-span-all-margin-nested-firstchild-ref.xht
@@ -9,7 +9,7 @@
 	margin: 0;
 }
 body>div {
-	font-family: ahem;
+	font-family: Ahem;
 	font-size: 1em;
 	line-height: 1em;
 	color: navy;
diff --git a/css/css-multicol/multicol-table-cell-vertical-align-001.xht b/css/css-multicol/multicol-table-cell-vertical-align-001.xht
index 5f73cec..a9176da 100644
--- a/css/css-multicol/multicol-table-cell-vertical-align-001.xht
+++ b/css/css-multicol/multicol-table-cell-vertical-align-001.xht
@@ -9,14 +9,14 @@
 <meta name="flags" content="ahem"/>
 <style type="text/css"><![CDATA[
 div {
-	font-family: ahem;
-	font-size: 1em;
+	font-family: Ahem;
+	font-size: 20px;
 	line-height: 1em;
 	vertical-align: middle;
 	color: white;
 	background: #3366CC;
 	width: 6em;
-	height: 400px;
+	height: 20em;
 	display: table-cell;
 
 	column-count: 2;
diff --git a/css/css-multicol/multicol-table-cell-vertical-align-ref.xht b/css/css-multicol/multicol-table-cell-vertical-align-ref.xht
index 023c9c6..ab68c7a 100644
--- a/css/css-multicol/multicol-table-cell-vertical-align-ref.xht
+++ b/css/css-multicol/multicol-table-cell-vertical-align-ref.xht
@@ -6,17 +6,17 @@
 <link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
 <style type="text/css"><![CDATA[
 div {
-	font-family: ahem;
-	font-size: 1em;
+	font-family: Ahem;
+	font-size: 20px;
 	line-height: 1em;
 	vertical-align: middle;
 	color: white;
 	background: #3366CC;
 	width: 6em;
-	height: 8em;
+	height: 5.5em;
 }
 div+div {
-	margin-top: 10em;
+	margin-top: 9em;
 }
 ]]></style>
 </head>
diff --git a/css/css-multicol/multicol-width-ch-001.xht b/css/css-multicol/multicol-width-ch-001.xht
new file mode 100644
index 0000000..c3e3b35
--- /dev/null
+++ b/css/css-multicol/multicol-width-ch-001.xht
@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>multicolumn | column-width</title>
+<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
+<link rel="help" href="http://www.w3.org/TR/css3-multicol/#the-number-and-width-of-columns"/>
+<link rel="match" href="multicol-width-ch-ref.xht"/>
+<meta name="flags" content=""/>
+<style type="text/css"><![CDATA[
+.multicol {
+  font: 1em monospace;
+  width: 69ch;
+  column-width: 13ch;
+  column-gap: 1ch;
+  orphans: 1;
+  widows: 1;
+  background: yellow;
+}
+]]></style>
+</head>
+
+<body>
+  <div class="multicol">
+    one two three four
+    five six seven eight
+    nineten eleven twelve
+    thirtn fourtnfiftn sixtn
+    seventn eightn ninetn twenty
+    hundred thousand million billion
+    trillion
+  </div>
+</body>
+</html>
diff --git a/css/css-multicol/multicol-width-ch-ref.xht b/css/css-multicol/multicol-width-ch-ref.xht
new file mode 100644
index 0000000..133ad3e
--- /dev/null
+++ b/css/css-multicol/multicol-width-ch-ref.xht
@@ -0,0 +1,49 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>multicolumn | column-width</title>
+<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
+<style type="text/css"><![CDATA[
+.multicol-ref {
+  display: flow-root;
+  width: 69ch;
+  font: 1em monospace;
+  background: yellow;
+}
+.multicol-ref > span {
+  float: left;
+  width: 13ch;
+  margin-right: 1ch;
+}
+.multicol-ref > span:last-child {
+  margin: 0;
+}
+]]></style>
+</head>
+
+<body>
+  <div class="multicol-ref">
+    <span>
+      one two three four
+      five six seven eight
+    </span>
+    <span>
+      nineten eleven twelve
+      thirtn
+    </span>
+    <span>
+      fourtnfiftn sixtn
+      seventn eightn ninetn
+    </span>
+    <span>
+      twenty
+      hundred thousand
+    </span>
+    <span>
+      million billion
+      trillion
+    </span>
+  </div>
+</body>
+</html>
diff --git a/css/css-multicol/multicol-width-ems-001.xht b/css/css-multicol/multicol-width-ems-001.xht
deleted file mode 100644
index c227ab4..0000000
--- a/css/css-multicol/multicol-width-ems-001.xht
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-width</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<link rel="help" href="http://www.w3.org/TR/css3-multicol/#the-number-and-width-of-columns"/>
-<link rel="match" href="multicol-width-ems-ref.xht"/>
-<meta name="flags" content=""/>
-<style type="text/css"><![CDATA[
-body {
-	margin: 0;
-	width: 40em;
-}
-div {
-	font-family: monospace;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	orphans: 1;
-	widows: 1;
-
-	column-width: 8em;
-	column-gap: 0;
-}
-]]></style>
-</head>
-
-<body>
-<div>
-	one two three four
-	five six seven eight
-	nineten eleven twelve
-	thirtn fourtnfiftn sixtn
-	seventn eightn ninetn twenty
-	hundred thousand million billion
-	trillion
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-width-ems-ref.xht b/css/css-multicol/multicol-width-ems-ref.xht
deleted file mode 100644
index b3ca468..0000000
--- a/css/css-multicol/multicol-width-ems-ref.xht
+++ /dev/null
@@ -1,59 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>multicolumn | column-width</title>
-<link rel="author" title="Opera Software ASA" href="http://www.opera.com/"/>
-<style type="text/css"><![CDATA[
-body {
-	margin: 0;
-	width: 40em;
-}
-div {
-	font-family: monospace;
-	font-size: 1em;
-	line-height: 1em;
-	color: black;
-	background: yellow;
-	orphans: 1;
-	widows: 1;
-}
-span {
-	float: left;
-	width: 8em;
-}
-div::after {
-	content: "";
-	clear: both;
-	display: block;
-}
-]]></style>
-</head>
-
-<body>
-
-<div>
-<span>
-	one two three four
-	five six seven eight
-</span>
-<span>
-	nineten eleven twelve
-	thirtn
-</span>
-<span>
-	fourtnfiftn sixtn
-	seventn eightn
-</span>
-<span>
-	ninetn twenty
-	hundred thousand
-</span>
-<span>
-	million billion
-	trillion
-</span>
-</div>
-
-</body>
-</html>
diff --git a/css/css-multicol/multicol-width-small-001.xht b/css/css-multicol/multicol-width-small-001.xht
index 362aeb1..c034815 100644
--- a/css/css-multicol/multicol-width-small-001.xht
+++ b/css/css-multicol/multicol-width-small-001.xht
@@ -10,7 +10,7 @@
   <meta name="flags" content="ahem" />
   <meta name="assert" content="This test checks that a set 'column-width' which is small with regards to width of multi-column element." />
   <style type="text/css"><![CDATA[
-  div
+  body > div
   {
   background-color: yellow;
   border: gray solid 1em;
@@ -23,20 +23,23 @@
   column-gap: 0;
   column-width: 1em;
   }
+  div > div { overflow: hidden; }
 
   span {color: blue;}
   ]]></style>
  </head>
  <body>
   <div>
-  Bl ac
-    <span>
-    bl ue
-    </span>
-    <span>
-    bl ue
-    </span>
-  Bl ac
+    <div>
+      Bl ac
+      <span>
+        bl ue
+      </span>
+      <span>
+        bl ue
+      </span>
+      Bl ac
+    </div>
   </div>
 
   <!--
diff --git a/css/css-multicol/multicol-zero-height-001-ref.xht b/css/css-multicol/multicol-zero-height-001-ref.xht
index 273bb43..389ff0a 100644
--- a/css/css-multicol/multicol-zero-height-001-ref.xht
+++ b/css/css-multicol/multicol-zero-height-001-ref.xht
@@ -4,12 +4,8 @@
   <title>CSS Reftest Reference</title>
   <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-24 -->
-  <meta name="flags" content="image" />
-  <style type="text/css"><![CDATA[
-  img {vertical-align: top;}
-  ]]></style>
  </head>
  <body>
-  <div><img src="support/swatch-lime.png" width="100" height="300" alt="Image download support must be enabled" /></div>
+  <div style="width:200px; height:1px; background-color:lime;"/>
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/multicol-zero-height-001.xht b/css/css-multicol/multicol-zero-height-001.xht
index 5cf3f12..4472bc2 100644
--- a/css/css-multicol/multicol-zero-height-001.xht
+++ b/css/css-multicol/multicol-zero-height-001.xht
@@ -5,6 +5,7 @@
   <link rel="author" title="Opera Software ASA" href="http://www.opera.com/" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2013-08-24 -->
   <link rel="help" href="http://www.w3.org/TR/css3-multicol/#cw" title="3.1. 'column-width'" />
+  <link rel="help" href="https://www.w3.org/TR/css-break-3/#breaking-rules" title="4. Rules for Breaking" />
   <link rel="match" href="multicol-zero-height-001-ref.xht" />
   <meta name="flags" content="" />
   <style type="text/css"><![CDATA[
@@ -14,25 +15,18 @@
   orphans: 1;
   widows: 1;
   width: 200px;
+  border-bottom:1px solid red;
 
-  column-count: 2;
+  column-count: 10;
   column-gap: 0;
-
-  /*
-
-  N == 2;
-
-  W == 100px;
-
-  */
   }
 
   div > div
   {
   background-color: lime;
   float: left;
-  height: 300px;
-  width: 100px;
+  height: 10px;
+  width: 100%;
   }
   ]]></style>
  </head>
@@ -41,4 +35,4 @@
     <div></div>
   </div>
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-multicol/zero-column-width-computed-style.html b/css/css-multicol/zero-column-width-computed-style.html
new file mode 100644
index 0000000..46d876f
--- /dev/null
+++ b/css/css-multicol/zero-column-width-computed-style.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>column-width:0</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cw" title="3.1. column-width">
+<div id="longhand" style="column-width:0;"></div>
+<div id="shorthand" style="columns:0;"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+  assert_equals(getComputedStyle(longhand).columnWidth, "0px");
+  assert_equals(getComputedStyle(shorthand).columnWidth, "0px");
+}, "column-width:0 is a valid CSS declaration");
+</script>
diff --git a/css/css-multicol/zero-column-width-layout.html b/css/css-multicol/zero-column-width-layout.html
new file mode 100644
index 0000000..aaae421
--- /dev/null
+++ b/css/css-multicol/zero-column-width-layout.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>column-width:0</title>
+<meta name="assert" content="column-width:0 is valid as specified and computed value, but its used value may never be less than 1px">
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#cw" title="3.1. column-width">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div style="float:left; width:50px; height:100px; column-width:0; column-gap:0;">
+  <div style="height:5000px; background:green;"></div>
+</div>
+<div style="float:left; width:50px; height:100px; columns:0; column-gap:0;">
+  <div style="height:5000px; background:green;"></div>
+</div>
diff --git a/css/css-overflow/OWNERS b/css/css-overflow/OWNERS
new file mode 100644
index 0000000..5c24843
--- /dev/null
+++ b/css/css-overflow/OWNERS
@@ -0,0 +1,2 @@
+@frivoal
+@dbaron
diff --git a/css/css-overflow/logical-overflow-001.html b/css/css-overflow/logical-overflow-001.html
new file mode 100644
index 0000000..15bf57f
--- /dev/null
+++ b/css/css-overflow/logical-overflow-001.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow Test: flow-relative versions of overflow-x and -y</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#logical">
+<link rel="help" href="https://drafts.csswg.org/css-logical/#box">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#d1, #d2 {
+  overflow-block: hidden;
+  overflow-inline: scroll
+}
+#d1 {
+  writing-mode: horizontal-tb;
+}
+#d2 {
+  writing-mode: vertical-rl;
+}
+
+</style>
+<body>
+  <div id="log"></div>
+
+  <div id=d1></div>
+  <div id=d2></div>
+
+  <script>
+    test(
+      function(){
+        var target = document.getElementById("d1");
+        assert_equals(getComputedStyle(target).overflowX, "scroll");
+        assert_equals(getComputedStyle(target).overflowY, "hidden");
+      }, "overflow-x matches overflow-inline, and overflow-y matches overflow-block when the element has a horizontal writing mode");
+    test(
+      function(){
+        var target = document.getElementById("d2");
+        assert_equals(getComputedStyle(target).overflowX, "hidden");
+        assert_equals(getComputedStyle(target).overflowY, "scroll");
+      }, "overflow-y matches overflow-inline, and overflow-x matches overflow-block when the element has a vertical writing mode");
+  </script>
+</body>
diff --git a/css/css-overflow/overflow-shorthand-001.html b/css/css-overflow/overflow-shorthand-001.html
new file mode 100644
index 0000000..f425636
--- /dev/null
+++ b/css/css-overflow/overflow-shorthand-001.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow Test: Overflow longhand accepts two values</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez <emilio@crisal.io>">
+<link rel="help" href="https://drafts.csswg.org/css-overflow/#propdef-overflow">
+<div id="test-div"></div>
+<script>
+let div = document.getElementById("test-div");
+function testOverflowShorthand(x, y) {
+  test(function() {
+    div.style.overflowX = x;
+    div.style.overflowY = y;
+
+    let expectedX = getComputedStyle(div).overflowX;
+    let expectedY = getComputedStyle(div).overflowY;
+    let expectedComputedSerialization = expectedX == expectedY ? expectedX : `${expectedX} ${expectedY}`;
+    let expectedSpecifiedSerialization = x == y ? x : `${x} ${y}`;
+
+    assert_equals(div.style.overflow, expectedSpecifiedSerialization);
+    assert_equals(getComputedStyle(div).overflow, expectedComputedSerialization);
+
+    div.style.overflowX = "";
+    div.style.overflowY = "";
+    assert_equals(div.style.overflow, "");
+
+    div.style.overflow = `${x} ${y}`;
+    assert_equals(div.style.overflow, expectedSpecifiedSerialization);
+    assert_equals(div.style.overflowX, x);
+    assert_equals(div.style.overflowY, y);
+    assert_equals(getComputedStyle(div).overflow, expectedComputedSerialization);
+    assert_equals(getComputedStyle(div).overflowX, expectedX);
+    assert_equals(getComputedStyle(div).overflowY, expectedY);
+  }, `overflow: ${x} ${y} works`);
+}
+
+let OVERFLOW_VALUES = [ "auto", "hidden", "scroll", "visible" ];
+for (let x of OVERFLOW_VALUES)
+  for (let y of OVERFLOW_VALUES)
+    testOverflowShorthand(x, y);
+</script>
diff --git a/css/css-page/OWNERS b/css/css-page/OWNERS
index 6c0d604..a228538 100644
--- a/css/css-page/OWNERS
+++ b/css/css-page/OWNERS
@@ -1,3 +1,2 @@
 @plinss
 @fantasai
-@SimonSapin
diff --git a/css/css-paint-api/background-image-alpha.https.html b/css/css-paint-api/background-image-alpha.https.html
index de8a0d7..53d869d 100644
--- a/css/css-paint-api/background-image-alpha.https.html
+++ b/css/css-paint-api/background-image-alpha.https.html
@@ -21,7 +21,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="background">
   <div id="canvas-opaque" class="container"></div>
@@ -49,7 +49,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/background-image-multiple.https.html b/css/css-paint-api/background-image-multiple.https.html
index 7fb5751..79ff883 100644
--- a/css/css-paint-api/background-image-multiple.https.html
+++ b/css/css-paint-api/background-image-multiple.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -27,7 +27,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/background-image-tiled.https.html b/css/css-paint-api/background-image-tiled.https.html
index 0497acf..8498c82 100644
--- a/css/css-paint-api/background-image-tiled.https.html
+++ b/css/css-paint-api/background-image-tiled.https.html
@@ -21,7 +21,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="one"></div>
 <div id="two"></div>
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-001.https.html b/css/css-paint-api/geometry-background-image-001.https.html
index d1207e0..601d418 100644
--- a/css/css-paint-api/geometry-background-image-001.https.html
+++ b/css/css-paint-api/geometry-background-image-001.https.html
@@ -13,7 +13,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -28,7 +28,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-002.https.html b/css/css-paint-api/geometry-background-image-002.https.html
index 47455ba..1d57073 100644
--- a/css/css-paint-api/geometry-background-image-002.https.html
+++ b/css/css-paint-api/geometry-background-image-002.https.html
@@ -13,7 +13,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -28,7 +28,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-tiled-001.https.html b/css/css-paint-api/geometry-background-image-tiled-001.https.html
index 5cf8eb7..8e28b54 100644
--- a/css/css-paint-api/geometry-background-image-tiled-001.https.html
+++ b/css/css-paint-api/geometry-background-image-tiled-001.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -30,7 +30,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-tiled-002.https.html b/css/css-paint-api/geometry-background-image-tiled-002.https.html
index 491abd1..9248e38 100644
--- a/css/css-paint-api/geometry-background-image-tiled-002.https.html
+++ b/css/css-paint-api/geometry-background-image-tiled-002.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -29,7 +29,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-tiled-003.https.html b/css/css-paint-api/geometry-background-image-tiled-003.https.html
index 9a29c30..5b6b6c7 100644
--- a/css/css-paint-api/geometry-background-image-tiled-003.https.html
+++ b/css/css-paint-api/geometry-background-image-tiled-003.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -29,7 +29,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-001.https.html b/css/css-paint-api/geometry-border-image-001.https.html
index 54249d3..3fb4643 100644
--- a/css/css-paint-api/geometry-border-image-001.https.html
+++ b/css/css-paint-api/geometry-border-image-001.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -34,7 +34,7 @@
 
 <script>
     document.getElementById('canvas-geometry').style.borderWidth = '10px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-002.https.html b/css/css-paint-api/geometry-border-image-002.https.html
index 4759886..26e24bb 100644
--- a/css/css-paint-api/geometry-border-image-002.https.html
+++ b/css/css-paint-api/geometry-border-image-002.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -34,7 +34,7 @@
 
 <script>
     document.getElementById('canvas-geometry').style.borderImageOutset = '20px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-003.https.html b/css/css-paint-api/geometry-border-image-003.https.html
index 1ce6ac9..a26f2b7 100644
--- a/css/css-paint-api/geometry-border-image-003.https.html
+++ b/css/css-paint-api/geometry-border-image-003.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -34,7 +34,7 @@
 
 <script>
     document.getElementById('canvas-geometry').style.borderImageOutset = '10px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-004.https.html b/css/css-paint-api/geometry-border-image-004.https.html
index b15b66d..60db7ff 100644
--- a/css/css-paint-api/geometry-border-image-004.https.html
+++ b/css/css-paint-api/geometry-border-image-004.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -35,7 +35,7 @@
 <script>
     document.getElementById('canvas-geometry').style.borderWidth = '10px';
     document.getElementById('canvas-geometry').style.borderImageOutset = '10px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-with-float-size.https.html b/css/css-paint-api/geometry-with-float-size.https.html
index 6cd3eca..65477e4 100644
--- a/css/css-paint-api/geometry-with-float-size.https.html
+++ b/css/css-paint-api/geometry-with-float-size.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -29,7 +29,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/hidpi/device-pixel-ratio.https.html b/css/css-paint-api/hidpi/device-pixel-ratio.https.html
index fb62c23..46a9aa2 100644
--- a/css/css-paint-api/hidpi/device-pixel-ratio.https.html
+++ b/css/css-paint-api/hidpi/device-pixel-ratio.https.html
@@ -13,7 +13,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <p>This test ensures that the PaintWorkletGlobalScope.devicePixelRatio returns
 the correct value, which should be identical as window.devicePixelRatio. To
@@ -33,7 +33,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/invalid-image-constructor-error-ref.html b/css/css-paint-api/invalid-image-constructor-error-ref.html
index b6a6dec..5e4c566 100644
--- a/css/css-paint-api/invalid-image-constructor-error-ref.html
+++ b/css/css-paint-api/invalid-image-constructor-error-ref.html
@@ -1,6 +1,10 @@
 <!DOCTYPE html>
 <html>
-<body>
-<div style="background: green; width: 100px; height: 100px"></div>
-</body>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+  var canvas = document.getElementById("canvas");
+  var ctx = canvas.getContext('2d');
+  ctx.fillStyle = 'green';
+  ctx.fillRect(50, 50, 50, 50);
+</script>
 </html>
diff --git a/css/css-paint-api/invalid-image-constructor-error.https.html b/css/css-paint-api/invalid-image-constructor-error.https.html
index 30a06eb..439ff8b 100644
--- a/css/css-paint-api/invalid-image-constructor-error.https.html
+++ b/css/css-paint-api/invalid-image-constructor-error.https.html
@@ -5,27 +5,35 @@
     #output {
         width: 100px;
         height: 100px;
-        background-image: paint(error);
-        background-color: green;
+        background-image: paint(errorIndicator), paint(successIndicator);
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
 <script id="code" type="text/worklet">
-registerPaint('error', class {
+registerPaint('errorIndicator', class {
     constructor() { throw Error('failed!'); }
+    // The paint function should not be executed because an error has been
+    // thrown.
     paint(ctx, geom) {
         ctx.fillStyle = 'red';
-        ctx.fillRect(0, 0, geom.width, geom.height);
+        ctx.fillRect(0, 0, 50, 50);
+    }
+});
+
+registerPaint('successIndicator', class {
+    paint(ctx, geom) {
+        ctx.fillStyle = 'green';
+        ctx.fillRect(50, 50, 50, 50);
     }
 });
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 
 </body>
diff --git a/css/css-paint-api/invalid-image-paint-error.https.html b/css/css-paint-api/invalid-image-paint-error.https.html
index 6e117f8..2806ad9 100644
--- a/css/css-paint-api/invalid-image-paint-error.https.html
+++ b/css/css-paint-api/invalid-image-paint-error.https.html
@@ -5,27 +5,32 @@
     #output {
         width: 100px;
         height: 100px;
-        background-image: paint(error);
-        background-color: green;
+        background-image: paint(errorIndicator), paint(successIndicator);
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
 <script id="code" type="text/worklet">
-registerPaint('error', class {
+registerPaint('errorIndicator', class {
     paint(ctx, geom) {
         ctx.fillStyle = 'red';
         ctx.fillRect(0, 0, geom.width, geom.height);
         throw Error('failed!');
     }
 });
+registerPaint('successIndicator', class {
+    paint(ctx, geom) {
+        ctx.fillStyle = 'green';
+        ctx.fillRect(0, 0, geom.width, geom.height);
+    }
+});
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/invalid-image-pending-script.https.html b/css/css-paint-api/invalid-image-pending-script.https.html
index c4cf755..4347a00 100644
--- a/css/css-paint-api/invalid-image-pending-script.https.html
+++ b/css/css-paint-api/invalid-image-pending-script.https.html
@@ -5,16 +5,28 @@
     #output {
         width: 100px;
         height: 100px;
-        background-image: paint(invalid);
-        background-color: green;
+        background-image: paint(invalid), paint(successIndicator);
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
+
+<script id="code" type="text/worklet">
+// This is testing that even though there is no paint function registered for
+// 'invalid', it won't cause any error, and the other painter (successIndicator)
+// will paint as usual.
+registerPaint('successIndicator', class {
+    paint(ctx, geom) {
+        ctx.fillStyle = 'green';
+        ctx.fillRect(0, 0, geom.width, geom.height);
+    }
+});
+</script>
+
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint("");
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/overdraw.https.html b/css/css-paint-api/overdraw.https.html
index f95eeb5..5be26f7 100644
--- a/css/css-paint-api/overdraw.https.html
+++ b/css/css-paint-api/overdraw.https.html
@@ -10,7 +10,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -24,7 +24,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint-arguments.https.html b/css/css-paint-api/paint-arguments.https.html
index f4ae452..abfb2a6 100644
--- a/css/css-paint-api/paint-arguments.https.html
+++ b/css/css-paint-api/paint-arguments.https.html
@@ -21,7 +21,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 
 <div id="background">
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-  importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+  importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint-function-arguments.https.html b/css/css-paint-api/paint-function-arguments.https.html
index d49f3f2..d87b0b8 100644
--- a/css/css-paint-api/paint-function-arguments.https.html
+++ b/css/css-paint-api/paint-function-arguments.https.html
@@ -21,7 +21,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 
 <div id="background">
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-  importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+  importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-composite.https.html b/css/css-paint-api/paint2d-composite.https.html
index ba88f92..80e94e4 100644
--- a/css/css-paint-api/paint2d-composite.https.html
+++ b/css/css-paint-api/paint2d-composite.https.html
@@ -19,7 +19,7 @@
     #xor { background-image: paint(xor); }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="source-over"></div>
 <div id="source-in"></div>
@@ -67,7 +67,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-filter.https.html b/css/css-paint-api/paint2d-filter.https.html
index 560b1b2..d0c4539 100644
--- a/css/css-paint-api/paint2d-filter.https.html
+++ b/css/css-paint-api/paint2d-filter.https.html
@@ -24,7 +24,7 @@
     #filter-url { background-image: paint(filter-url); }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="filter-none"></div>
 <div id="filter-blur-10px"></div>
@@ -100,7 +100,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-gradient.https.html b/css/css-paint-api/paint2d-gradient.https.html
index b936430..892a791 100644
--- a/css/css-paint-api/paint2d-gradient.https.html
+++ b/css/css-paint-api/paint2d-gradient.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -34,7 +34,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-image-ref.html b/css/css-paint-api/paint2d-image-ref.html
index 31c9f3f..90c62e5 100644
--- a/css/css-paint-api/paint2d-image-ref.html
+++ b/css/css-paint-api/paint2d-image-ref.html
@@ -1,12 +1,15 @@
 <!DOCTYPE html>
-<canvas id="canvas" width="300" height="300"></canvas>
-
-<script>
-    var canvas = document.getElementById("canvas");
-    var ctx = canvas.getContext("2d");
-    var img = new Image;
-    img.src = "resources/html5.png";
-    img.onload = function() {
-        ctx.drawImage(img, 0, 0);
-    };
-</script>
+<html>
+<style>
+div {
+    width: 300px;
+    height: 300px;
+    background-image: url("./resources/html5.png");
+    background-repeat: no-repeat;
+    background-color: green;
+}
+</style>
+<body>
+<div></div>
+</body>
+</html>
diff --git a/css/css-paint-api/paint2d-image.https.html b/css/css-paint-api/paint2d-image.https.html
index 5dc7ebf..6fce4b6 100644
--- a/css/css-paint-api/paint2d-image.https.html
+++ b/css/css-paint-api/paint2d-image.https.html
@@ -2,27 +2,29 @@
 <html class="reftest-wait">
 <link rel="match" href="paint2d-image-ref.html">
 <style>
-    #output {
-        width: 300px;
-        height: 300px;
-        background-image: paint(image);
-        border-image: url("resources/html5.png");
-    }
+#output {
+    width: 300px;
+    height: 300px;
+    background-image: paint(image);
+    border-image: url("./resources/html5.png");
+}
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <div id="output"></div>
 
 <script id="code" type="text/worklet">
     registerPaint('image', class {
         static get inputProperties() { return [ 'border-image-source' ]; };
         paint(ctx, geom, styleMap) {
+            ctx.fillStyle = 'green';
+            ctx.fillRect(0, 0, geom.width, geom.height);
             ctx.drawImage(styleMap.get('border-image-source'), 0, 0);
         }
     });
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </html>
diff --git a/css/css-paint-api/paint2d-paths.https.html b/css/css-paint-api/paint2d-paths.https.html
index 55a01b4..091f548 100644
--- a/css/css-paint-api/paint2d-paths.https.html
+++ b/css/css-paint-api/paint2d-paths.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-rects.https.html b/css/css-paint-api/paint2d-rects.https.html
index 24247da..2494272 100644
--- a/css/css-paint-api/paint2d-rects.https.html
+++ b/css/css-paint-api/paint2d-rects.https.html
@@ -10,7 +10,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -30,7 +30,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-shadows.https.html b/css/css-paint-api/paint2d-shadows.https.html
index ad0a1aa..98dcfbc 100644
--- a/css/css-paint-api/paint2d-shadows.https.html
+++ b/css/css-paint-api/paint2d-shadows.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -33,7 +33,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-transform.https.html b/css/css-paint-api/paint2d-transform.https.html
index f5b6aa8..c91b500 100644
--- a/css/css-paint-api/paint2d-transform.https.html
+++ b/css/css-paint-api/paint2d-transform.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -32,7 +32,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-001.https.html b/css/css-paint-api/parse-input-arguments-001.https.html
index 931a55a..4356ce6 100644
--- a/css/css-paint-api/parse-input-arguments-001.https.html
+++ b/css/css-paint-api/parse-input-arguments-001.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-002.https.html b/css/css-paint-api/parse-input-arguments-002.https.html
index c07c0c0..6fc16c7 100644
--- a/css/css-paint-api/parse-input-arguments-002.https.html
+++ b/css/css-paint-api/parse-input-arguments-002.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-003.https.html b/css/css-paint-api/parse-input-arguments-003.https.html
index ff388de..44fd850 100644
--- a/css/css-paint-api/parse-input-arguments-003.https.html
+++ b/css/css-paint-api/parse-input-arguments-003.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-004.https.html b/css/css-paint-api/parse-input-arguments-004.https.html
index 8d5b8d7..138a790 100644
--- a/css/css-paint-api/parse-input-arguments-004.https.html
+++ b/css/css-paint-api/parse-input-arguments-004.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-005.https.html b/css/css-paint-api/parse-input-arguments-005.https.html
index b726c22..c12e00c 100644
--- a/css/css-paint-api/parse-input-arguments-005.https.html
+++ b/css/css-paint-api/parse-input-arguments-005.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -39,7 +39,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-006.https.html b/css/css-paint-api/parse-input-arguments-006.https.html
index 7d8504c..664b7a8 100644
--- a/css/css-paint-api/parse-input-arguments-006.https.html
+++ b/css/css-paint-api/parse-input-arguments-006.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-007.https.html b/css/css-paint-api/parse-input-arguments-007.https.html
index a59ac93..53f245b 100644
--- a/css/css-paint-api/parse-input-arguments-007.https.html
+++ b/css/css-paint-api/parse-input-arguments-007.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-008.https.html b/css/css-paint-api/parse-input-arguments-008.https.html
index 2d6df23..1914e3c 100644
--- a/css/css-paint-api/parse-input-arguments-008.https.html
+++ b/css/css-paint-api/parse-input-arguments-008.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-009.https.html b/css/css-paint-api/parse-input-arguments-009.https.html
index 03c7c69..21d004d 100644
--- a/css/css-paint-api/parse-input-arguments-009.https.html
+++ b/css/css-paint-api/parse-input-arguments-009.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-010.https.html b/css/css-paint-api/parse-input-arguments-010.https.html
index 0bd6768..d923479 100644
--- a/css/css-paint-api/parse-input-arguments-010.https.html
+++ b/css/css-paint-api/parse-input-arguments-010.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-011.https.html b/css/css-paint-api/parse-input-arguments-011.https.html
index 8945f7e..6cea438 100644
--- a/css/css-paint-api/parse-input-arguments-011.https.html
+++ b/css/css-paint-api/parse-input-arguments-011.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -21,7 +21,7 @@
 try {
     registerPaint('foo7', class { });
 } catch(ex) {
-    if (ex.name == 'TypeError' && ex.message == "Failed to execute 'registerPaint' on 'PaintWorkletGlobalScope': The 'paint' function on the prototype does not exist.")
+    if (ex.name == 'TypeError' && ex.message == "Failed to execute 'registerPaint' on 'PaintWorkletGlobalScope': The 'paint' property on the prototype does not exist.")
         testsPassed = true;
 }
 
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-012.https.html b/css/css-paint-api/parse-input-arguments-012.https.html
index 422b45f..938150a 100644
--- a/css/css-paint-api/parse-input-arguments-012.https.html
+++ b/css/css-paint-api/parse-input-arguments-012.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-013.https.html b/css/css-paint-api/parse-input-arguments-013.https.html
index cd62879..37e3eb435 100644
--- a/css/css-paint-api/parse-input-arguments-013.https.html
+++ b/css/css-paint-api/parse-input-arguments-013.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-014.https.html b/css/css-paint-api/parse-input-arguments-014.https.html
index 39aee6a..690e488 100644
--- a/css/css-paint-api/parse-input-arguments-014.https.html
+++ b/css/css-paint-api/parse-input-arguments-014.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-015.https.html b/css/css-paint-api/parse-input-arguments-015.https.html
index c2e1844..fba7671 100644
--- a/css/css-paint-api/parse-input-arguments-015.https.html
+++ b/css/css-paint-api/parse-input-arguments-015.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-016.https.html b/css/css-paint-api/parse-input-arguments-016.https.html
index 4bea1b4..8c9d05d 100644
--- a/css/css-paint-api/parse-input-arguments-016.https.html
+++ b/css/css-paint-api/parse-input-arguments-016.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-017.https.html b/css/css-paint-api/parse-input-arguments-017.https.html
index 38f9f43..0d14fe74 100644
--- a/css/css-paint-api/parse-input-arguments-017.https.html
+++ b/css/css-paint-api/parse-input-arguments-017.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -41,7 +41,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-018-ref.html b/css/css-paint-api/parse-input-arguments-018-ref.html
index 8222ab4..05da4ed 100644
--- a/css/css-paint-api/parse-input-arguments-018-ref.html
+++ b/css/css-paint-api/parse-input-arguments-018-ref.html
@@ -1,15 +1,17 @@
 <!DOCTYPE html>
 <html>
 <body>
-<p>The test result should show only one black rect border. It should not paint
-any content in the rect because registerPaint will be called twice and the
-inputArguments will return two different strings, that will throw an exception
-and paint won't be executed.</p>
+<p>This test result should show a rect with black border, where the rect is
+filled with green on the lower right corner. The registerPaint('failureIndicator')
+will be called twice and the inputArguments will return two different strings,
+which will throw an exception and the paint function with 'failureIndicator'
+should never be called. In other words, there should be no red painted in the result.</p>
 <canvas id ="canvas" width="100" height="100" style="border:1px solid black"></canvas>
 <script>
 var canvas = document.getElementById('canvas');
 var context = canvas.getContext("2d");
-context.clearRect(0, 0, 100, 100);
+context.fillStyle = 'green'
+context.fillRect(50, 50, 50, 50);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-018.https.html b/css/css-paint-api/parse-input-arguments-018.https.html
index 2cfac40..541332a 100644
--- a/css/css-paint-api/parse-input-arguments-018.https.html
+++ b/css/css-paint-api/parse-input-arguments-018.https.html
@@ -9,16 +9,17 @@
 
 #canvas-geometry {
     border:1px solid black;
-    background-image: paint(geometry);
+    background-image: paint(failureIndicator), paint(geometry);
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
-<p>The test result should show only one black rect border. It should not paint
-any content in the rect because registerPaint will be called twice and the
-inputArguments will return two different strings, that will throw an exception
-and paint won't be executed.</p>
+<p>This test result should show a rect with black border, where the rect is
+filled with green on the lower right corner. The registerPaint('failureIndicator')
+will be called twice and the inputArguments will return two different strings,
+which will throw an exception and the paint function with 'failureIndicator'
+should never be called. In other words, there should be no red painted in the result.</p>
 <div id="canvas-geometry" class="container"></div>
 
 <script id="code" type="text/worklet">
@@ -31,7 +32,7 @@
 }
 
 try {
-    registerPaint('geometry', class {
+    registerPaint('failureIndicator', class {
         static get inputArguments() {
             // This test is testing the case where an exception should be thrown
             // when two paint definitions with different properties are registered
@@ -42,18 +43,27 @@
             var current_str = generateRandString(100);
             return [current_str];
         }
+        // The paint function here should never be called because the inputArguments
+        // will generate two different properties, and that should throw an
+        // exception.
         paint(ctx, geom) {
-            ctx.strokeStyle = 'red';
-            ctx.lineWidth = 4;
-            ctx.strokeRect(0, 0, geom.width, geom.height);
+            ctx.fillStyle = 'red';
+            ctx.fillRect(0, 0, 50, 50);
         }
     });
 } catch(ex) {
 }
+
+registerPaint('geometry', class {
+    paint(ctx, geom) {
+        ctx.fillStyle = 'green';
+        ctx.fillRect(50, 50, 50, 50);
+    }
+});
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-019.https.html b/css/css-paint-api/parse-input-arguments-019.https.html
index 76627fb..707a0c6 100644
--- a/css/css-paint-api/parse-input-arguments-019.https.html
+++ b/css/css-paint-api/parse-input-arguments-019.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -41,7 +41,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-020.https.html b/css/css-paint-api/parse-input-arguments-020.https.html
index b582555..fe8dbad 100644
--- a/css/css-paint-api/parse-input-arguments-020.https.html
+++ b/css/css-paint-api/parse-input-arguments-020.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-021.https.html b/css/css-paint-api/parse-input-arguments-021.https.html
index 666d4c4..0c3a596 100644
--- a/css/css-paint-api/parse-input-arguments-021.https.html
+++ b/css/css-paint-api/parse-input-arguments-021.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-022.https.html b/css/css-paint-api/parse-input-arguments-022.https.html
index 3c5d9f9..50aaa6b 100644
--- a/css/css-paint-api/parse-input-arguments-022.https.html
+++ b/css/css-paint-api/parse-input-arguments-022.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -41,7 +41,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/registered-properties-in-custom-paint.https.html b/css/css-paint-api/registered-properties-in-custom-paint.https.html
index d77a33f..174bfbe 100644
--- a/css/css-paint-api/registered-properties-in-custom-paint.https.html
+++ b/css/css-paint-api/registered-properties-in-custom-paint.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -28,7 +28,7 @@
         ];
     }
     paint(ctx, geom, styleMap) {
-        const properties = styleMap.getProperties().sort();
+        const properties = [...styleMap.keys()].sort();
         var serializedStrings = [];
         for (let i = 0; i < properties.length; i++) {
             const value = styleMap.get(properties[i]);
@@ -44,7 +44,7 @@
             ctx.strokeStyle = 'red';
         if (serializedStrings[1] != "--length-initial: [CSSUnitValue=20px]")
             ctx.strokeStyle = 'blue';
-        if (serializedStrings[2] != "--number: [CSSStyleValue=10]")
+        if (serializedStrings[2] != "--number: [CSSUnitValue=10]")
             ctx.strokeStyle = 'yellow';
         ctx.lineWidth = 4;
         ctx.strokeRect(0, 0, geom.width, geom.height);
@@ -56,7 +56,7 @@
     CSS.registerProperty({name: '--length', syntax: '<length>', initialValue: '0px'});
     CSS.registerProperty({name: '--length-initial', syntax: '<length>', initialValue: '20px'});
     CSS.registerProperty({name: '--number', syntax: '<number>', initialValue: '0'});
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/resources/html5.png b/css/css-paint-api/resources/html5.png
new file mode 100644
index 0000000..e344192
--- /dev/null
+++ b/css/css-paint-api/resources/html5.png
Binary files differ
diff --git a/css/css-paint-api/style-background-image-ref.html b/css/css-paint-api/style-background-image-ref.html
index 3fc9e6e..2fe4d0c 100644
--- a/css/css-paint-api/style-background-image-ref.html
+++ b/css/css-paint-api/style-background-image-ref.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <body>
-<canvas id ="canvas" width="100" height="100" style="border-radius: 2px"></canvas>
+<canvas id ="canvas" width="100" height="100" style="margin-left: 2px"></canvas>
 <script>
 var canvas = document.getElementById('canvas');
 var context = canvas.getContext("2d");
diff --git a/css/css-paint-api/style-background-image.https.html b/css/css-paint-api/style-background-image.https.html
index 716c28c..13204cf 100644
--- a/css/css-paint-api/style-background-image.https.html
+++ b/css/css-paint-api/style-background-image.https.html
@@ -5,7 +5,7 @@
 .container {
   width: 100px;
   height: 100px;
-  border-radius: 2px;
+  margin-left: 2px;
   --foo: bar;
 }
 
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -24,12 +24,12 @@
         return [
             '--bar',
             '--foo',
-            'align-items',
-            'border-radius',
+            'empty-cells',
+            'margin-left',
         ];
     }
     paint(ctx, geom, styleMap) {
-        const properties = styleMap.getProperties().sort();
+        const properties = [...styleMap.keys()].sort();
         var serializedStrings = [];
         for (let i = 0; i < properties.length; i++) {
             const value = styleMap.get(properties[i]);
@@ -41,13 +41,13 @@
             serializedStrings.push(serialized);
         }
         ctx.strokeStyle = 'green';
-        if (serializedStrings[0] != "--bar: [null]")
+        if (serializedStrings[0] != "--bar: [CSSUnparsedValue=]")
             ctx.strokeStyle = 'red';
-        if (serializedStrings[1] != "--foo: [CSSStyleValue= bar]")
+        if (serializedStrings[1] != "--foo: [CSSUnparsedValue= bar]")
             ctx.strokeStyle = 'blue';
-        if (serializedStrings[2] != "align-items: [CSSKeywordValue=normal]")
+        if (serializedStrings[2] != "empty-cells: [CSSKeywordValue=show]")
             ctx.strokeStyle = 'yellow';
-        if (serializedStrings[3] != "border-radius: [CSSStyleValue=2px]")
+        if (serializedStrings[3] != "margin-left: [CSSUnitValue=2px]")
             ctx.strokeStyle = 'cyan';
         ctx.lineWidth = 4;
         ctx.strokeRect(0, 0, geom.width, geom.height);
@@ -56,7 +56,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/style-before-pseudo-ref.html b/css/css-paint-api/style-before-pseudo-ref.html
index 9a666ef..50ec833 100644
--- a/css/css-paint-api/style-before-pseudo-ref.html
+++ b/css/css-paint-api/style-before-pseudo-ref.html
@@ -2,17 +2,17 @@
 <html>
 <style>
 div {
-    border-radius: 3px;
+    margin-left: 3px;
 }
 div::before {
     width: 100px;
     height: 100px;
-    border-radius: 2px;
+    margin-left: 2px;
     content: 'foo';
     color: rgba(0, 0, 0, 0);
 }
 canvas{
-    border-radius: 2px;
+    margin-left: 2px;
     display: block;
     position: relative;
     top: -1em;
diff --git a/css/css-paint-api/style-before-pseudo.https.html b/css/css-paint-api/style-before-pseudo.https.html
index 8ba4cdb..5f342d1 100644
--- a/css/css-paint-api/style-before-pseudo.https.html
+++ b/css/css-paint-api/style-before-pseudo.https.html
@@ -3,7 +3,7 @@
 <link rel="match" href="style-before-pseudo-ref.html">
 <style>
 div {
-    border-radius: 3px;
+    margin-left: 3px;
 }
 
 div::before {
@@ -13,12 +13,12 @@
     color: rgba(0, 0, 0, 0);
 
     background-image: paint(geometry);
-    border-radius: 2px;
+    margin-left: 2px;
     --foo: bar;
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body style="font: 10px/1 Ahem;">
 <div></div>
 
@@ -28,11 +28,11 @@
         return [
             '--bar',
             '--foo',
-            'border-radius',
+            'margin-left',
         ];
     }
     paint(ctx, geom, styleMap) {
-        const properties = styleMap.getProperties().sort();
+        const properties = [...styleMap.keys()].sort();
         var serializedStrings = [];
         for (let i = 0; i < properties.length; i++) {
             const value = styleMap.get(properties[i]);
@@ -44,11 +44,11 @@
             serializedStrings.push(serialized);
         }
         ctx.strokeStyle = 'green';
-        if (serializedStrings[0] != "--bar: [null]")
+        if (serializedStrings[0] != "--bar: [CSSUnparsedValue=]")
             ctx.strokeStyle = 'red';
-        if (serializedStrings[1] != "--foo: [CSSStyleValue= bar]")
+        if (serializedStrings[1] != "--foo: [CSSUnparsedValue= bar]")
             ctx.strokeStyle = 'blue';
-        if (serializedStrings[2] != "border-radius: [CSSStyleValue=2px]")
+        if (serializedStrings[2] != "margin-left: [CSSUnitValue=2px]")
             ctx.strokeStyle = 'yellow';
         ctx.lineWidth = 4;
         ctx.strokeRect(0, 0, geom.width, geom.height);
@@ -57,7 +57,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/style-first-letter-pseudo.https.html b/css/css-paint-api/style-first-letter-pseudo.https.html
index 3cdffb4..f42f82b 100644
--- a/css/css-paint-api/style-first-letter-pseudo.https.html
+++ b/css/css-paint-api/style-first-letter-pseudo.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body style="font: 10px/1 Ahem;">
 <div>ppp</div>
 
@@ -28,7 +28,7 @@
         ];
     }
     paint(ctx, geom, styleMap) {
-        const properties = styleMap.getProperties().sort();
+        const properties = [...styleMap.keys()].sort();
         var serializedStrings = [];
         for (let i = 0; i < properties.length; i++) {
             const value = styleMap.get(properties[i]);
@@ -51,7 +51,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/valid-image-after-load.https.html b/css/css-paint-api/valid-image-after-load.https.html
index 73557d2..44612f9 100644
--- a/css/css-paint-api/valid-image-after-load.https.html
+++ b/css/css-paint-api/valid-image-after-load.https.html
@@ -9,7 +9,6 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
 <body>
 <div id="output"></div>
 
diff --git a/css/css-paint-api/valid-image-before-load.https.html b/css/css-paint-api/valid-image-before-load.https.html
index 483d16c..7738d9d 100644
--- a/css/css-paint-api/valid-image-before-load.https.html
+++ b/css/css-paint-api/valid-image-before-load.https.html
@@ -9,7 +9,6 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
 <body>
 <div id="output"></div>
 
diff --git a/css/css-position/OWNERS b/css/css-position/OWNERS
new file mode 100644
index 0000000..c323201
--- /dev/null
+++ b/css/css-position/OWNERS
@@ -0,0 +1 @@
+@atanassov
diff --git a/css/css-position/hypothetical-box-scroll-parent-ref.html b/css/css-position/hypothetical-box-scroll-parent-ref.html
new file mode 100644
index 0000000..86956ad
--- /dev/null
+++ b/css/css-position/hypothetical-box-scroll-parent-ref.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset=utf-8>
+<!-- Our target div should be placed as if it were a child of the overflowing --
+  -- thing; this is simplest to accomplish by just having the overflowing thing --
+  -- absolutely positioned so the target div is placed as if it were not there -->
+<div style="overflow: auto; height: 100px; width: 200px; position: absolute">
+  <div style="width: 400px; height: 10px"></div>
+</div>
+<div id="target">Modified text</div>
+<script>
+  document.querySelector("div").scrollLeft = 1000;
+</script>
diff --git a/css/css-position/hypothetical-box-scroll-parent.html b/css/css-position/hypothetical-box-scroll-parent.html
new file mode 100644
index 0000000..e342e8c
--- /dev/null
+++ b/css/css-position/hypothetical-box-scroll-parent.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<link rel=match href=hypothetical-box-scroll-parent-ref.html>
+<link rel="help"
+      href="https://www.w3.org/TR/CSS2/visudet.html#abs-non-replaced-width">
+<div style="overflow: auto; height: 100px; width: 200px">
+  <div id="target" style="position: absolute">Original text</div>
+  <div style="width: 400px; height: 10px"></div>
+</div>
+<script>
+  // Scroll the parent.
+  document.querySelector("div").scrollLeft = 1000;
+
+  // Now force relayout of the abs pos div.
+  document.getElementById("target").textContent = "Modified text";
+</script>
diff --git a/css/css-position/hypothetical-box-scroll-viewport-ref.html b/css/css-position/hypothetical-box-scroll-viewport-ref.html
new file mode 100644
index 0000000..368da5f
--- /dev/null
+++ b/css/css-position/hypothetical-box-scroll-viewport-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<meta charset=utf-8>
+<div>Modified text</div>
+<div style="width: 200vw; height: 10px"></div>
+<script>
+  window.scrollTo(window.innerWidth * 2, 0);
+</script>
diff --git a/css/css-position/hypothetical-box-scroll-viewport.html b/css/css-position/hypothetical-box-scroll-viewport.html
new file mode 100644
index 0000000..9ccb822
--- /dev/null
+++ b/css/css-position/hypothetical-box-scroll-viewport.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<link rel=match href=hypothetical-box-scroll-viewport-ref.html>
+<link rel="help"
+      href="https://www.w3.org/TR/CSS2/visudet.html#abs-non-replaced-width">
+<div style="position: absolute">Original text</div>
+<div style="width: 200vw; height: 10px"></div>
+<script>
+  // Scroll the viewport.
+  window.scrollTo(window.innerWidth * 2, 0);
+
+  // Now force relayout of the abs pos div.
+  document.querySelector("div").textContent = "Modified text";
+</script>
diff --git a/css/css-position/position-sticky-bottom-ref.html b/css/css-position/position-sticky-bottom-ref.html
deleted file mode 100644
index 7d4953d..0000000
--- a/css/css-position/position-sticky-bottom-ref.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should respect the top constraint</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
-
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.contents {
-  height: 500px;
-}
-
-.indicator {
-  background-color: green;
-  position: absolute;
-  left: 0;
-}
-
-.box {
-  width: 100%;
-  height: 100px;
-}
-
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 100;
-  document.getElementById('scroller2').scrollTop = 175;
-  document.getElementById('scroller3').scrollTop = 250;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 200px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 250px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 300px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-bottom.html b/css/css-position/position-sticky-bottom.html
index 18c5cc4..ba23f01 100644
--- a/css/css-position/position-sticky-bottom.html
+++ b/css/css-position/position-sticky-bottom.html
@@ -1,104 +1,41 @@
 <!DOCTYPE html>
 <title>position:sticky elements should respect the bottom constraint</title>
-<link rel="match" href="position-sticky-bottom-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that position:sticky elements obey their bottom anchor after scrolling" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 500px;
-}
-
-.prepadding {
-  height: 200px;
-}
-
-.container {
-  height: 200px;
-}
-
-.filler {
-  height: 100px;
-}
-
-.indicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-}
-
-.sticky {
-  background-color: green;
-  position: sticky;
-  bottom: 25px;
-}
-
-.box {
-  width: 100%;
-  height: 100px;
-}
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 100;
-  document.getElementById('scroller2').scrollTop = 175;
-  document.getElementById('scroller3').scrollTop = 250;
-});
+test(() => {
+  const elements = setupStickyTest('bottom', 25);
+  elements.scroller.scrollTop = 300;
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+}, 'before reaching the sticking point the sticky box should not be offset');
+
+test(() => {
+  const elements = setupStickyTest('bottom', 25);
+  elements.scroller.scrollTop = 100;
+
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  const nonStickyBottomY = nonStickyTopY + elements.sticky.clientHeight;
+  const targetBottomY = elements.scroller.clientHeight +
+      elements.scroller.scrollTop - 25;
+  const stickyOffset = nonStickyBottomY - targetBottomY;
+
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY - stickyOffset);
+}, 'after reaching the sticking point the sticky box should be offset');
+
+test(() => {
+  const elements = setupStickyTest('bottom', 25);
+  elements.scroller.scrollTop = 0;
+  assert_equals(elements.sticky.offsetTop, elements.container.offsetTop);
+}, 'the sticky box should not be pushed outside its containing block');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="indicator box" style="top: 200px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="indicator box" style="top: 250px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="indicator box" style="top: 300px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-inflow-position-ref.html b/css/css-position/position-sticky-inflow-position-ref.html
deleted file mode 100644
index beebd7e..0000000
--- a/css/css-position/position-sticky-inflow-position-ref.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should not affect the flow position of other elements</title>
-
-<style>
-.scroller {
-  height: 200px;
-  width: 100px;
-  overflow-y: scroll;
-  margin-bottom: 15px;
-}
-
-.sticky {
-  background-color: green;
-}
-
-.box {
-  height: 50px;
-  width: 50px;
-}
-
-.before {
-  background-color: fuchsia;
-}
-
-.after {
-  background-color: orange;
-}
-
-.padding {
-  height: 450px;
-}
-</style>
-
-<div class="scroller">
-  <div class="before box"></div>
-  <div class="box"></div>
-  <div class="after box"></div>
-  <div class="sticky box"></div>
-  <div class="padding"></div>
-</div>
-
-<div>You should see a fuchsia box, a one-box gap, an orange box, and then a green box above.</div>
diff --git a/css/css-position/position-sticky-inflow-position.html b/css/css-position/position-sticky-inflow-position.html
index fc7e829..a6774b7 100644
--- a/css/css-position/position-sticky-inflow-position.html
+++ b/css/css-position/position-sticky-inflow-position.html
@@ -1,46 +1,58 @@
 <!DOCTYPE html>
 <title>position:sticky elements should not affect the flow position of other elements</title>
-<link rel="match" href="position-sticky-inflow-position-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that position:sticky elements do not affect the flow position of other elements" />
 
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
 <style>
 .scroller {
+  position: relative;
   height: 200px;
   width: 100px;
-  overflow-y: scroll;
-  margin-bottom: 15px;
+  overflow: scroll;
 }
 
-.sticky {
+#sticky {
   background-color: green;
   position: sticky;
   top: 150px;
 }
 
-.box {
-  height: 50px;
-  width: 50px;
-}
-
-.before {
+#before {
   background-color: fuchsia;
 }
 
-.after {
+#after {
   background-color: orange;
 }
 
+.box {
+  height: 50px;
+  width: 50px;
+}
+
 .padding {
   height: 500px;
 }
 </style>
 
 <div class="scroller">
-  <div class="before box"></div>
-  <div class="sticky box"></div>
-  <div class="after box"></div>
+  <div id="before" class="box"></div>
+  <div id="sticky" class="box"></div>
+  <div id="after" class="box"></div>
   <div class="padding"></div>
 </div>
 
-<div>You should see a fuchsia box, a one-box gap, an orange box, and then a green box above.</div>
+<script>
+test(() => {
+  // The sticky element is pushed to be stuck 150 pixels from the top.
+  assert_equals(sticky.offsetTop, 150);
+
+  // Neither 'before' or 'after' should be affected by the change in the sticky
+  // element's location.
+  assert_equals(before.offsetTop, 0);
+  assert_equals(after.offsetTop, before.clientHeight + sticky.clientHeight);
+}, 'sticky offset should not affect the position of other elements.');
+</script>
diff --git a/css/css-position/position-sticky-left-ref.html b/css/css-position/position-sticky-left-ref.html
deleted file mode 100644
index e0de6fb..0000000
--- a/css/css-position/position-sticky-left-ref.html
+++ /dev/null
@@ -1,68 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should respect the left constraint</title>
-
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
-
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
-
-.contents {
-  height: 100%;
-  width: 500px;
-}
-
-.indicator {
-  background-color: green;
-  position: absolute;
-  top: 0;
-}
-
-.box {
-  height: 100%;
-  width: 100px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 50;
-  document.getElementById('scroller2').scrollLeft = 125;
-  document.getElementById('scroller3').scrollLeft = 200;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="left: 150px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="left: 175px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="left: 200px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-left.html b/css/css-position/position-sticky-left.html
index 40a4d72..054cf10 100644
--- a/css/css-position/position-sticky-left.html
+++ b/css/css-position/position-sticky-left.html
@@ -1,103 +1,44 @@
 <!DOCTYPE html>
 <title>position:sticky elements should respect the left constraint</title>
-<link rel="match" href="position-sticky-left-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that position:sticky elements obey their left anchor after scrolling" />
 
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 100%;
-  width: 500px;
-}
-
-.prepadding {
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.container {
-  display: inline-block;
-  height: 100%;
-  width: 200px;
-}
-
-.innerpadding {
-  display: inline-block;
-  height: 100%;
-  width: 50px;
-}
-
-.indicator {
-  background-color: red;
-  position: absolute;
-  top: 0;
-}
-
-.sticky {
-  background-color: green;
-  position: sticky;
-  left: 50px;
-}
-
-.box {
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 50;
-  document.getElementById('scroller2').scrollLeft = 125;
-  document.getElementById('scroller3').scrollLeft = 200;
-});
+test(() => {
+  const elements = setupStickyTest('left', 50);
+  elements.scroller.scrollLeft = 100;
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX);
+}, 'before reaching the sticking point the sticky box should not be offset');
+
+test(() => {
+  const elements = setupStickyTest('left', 50);
+  elements.scroller.scrollLeft = 200;
+
+  // This math actually cancels to sticky.offsetLeft == (scroller.scrollLeft + 50),
+  // but for clarity the calculations are left explicit.
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  const targetLeftX = elements.scroller.scrollLeft + 50;
+  const stickyOffset = targetLeftX - nonStickyLeftX;
+
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX + stickyOffset);
+}, 'after reaching the sticking point the sticky box should be offset');
+
+test(() => {
+  const elements = setupStickyTest('left', 50);
+  elements.scroller.scrollLeft = 300;
+  const maxOffsetInContainer = elements.container.offsetLeft +
+      elements.container.clientWidth - elements.sticky.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, maxOffsetInContainer);
+}, 'the sticky box should not be pushed outside its containing block');
 </script>
 
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="indicator box" style="left: 150px;"></div>
-    <div class="contents">
-      <!-- As these elements are inline, they are whitespace sensitive. -->
-      <div class="prepadding"></div><div class="container"><div class="innerpadding"></div><div class="sticky box"></div></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="indicator box" style="left: 175px;"></div>
-    <div class="contents">
-      <!-- As these elements are inline, they are whitespace sensitive. -->
-      <div class="prepadding"></div><div class="container"><div class="innerpadding"></div><div class="sticky box"></div></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="indicator box" style="left: 200px;"></div>
-    <div class="contents">
-      <!-- As these elements are inline, they are whitespace sensitive. -->
-      <div class="prepadding"></div><div class="container"><div class="innerpadding"></div><div class="sticky box"></div></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-margins-ref.html b/css/css-position/position-sticky-margins-ref.html
deleted file mode 100644
index 3049039..0000000
--- a/css/css-position/position-sticky-margins-ref.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should properly interact with margins</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 180px;
-  height: 400px;
-}
-
-.scroller {
-  width: 150px;
-  height: 300px;
-  overflow-y: scroll;
-  overflow-x: hidden;
-}
-
-.indicator {
-  position: relative;
-  background-color: green;
-  margin: 15px;
-}
-
-.box {
-  width: 100px;
-  height: 100px;
-}
-
-.padding {
-  height: 385px;
-}
-</style>
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 0;
-  document.getElementById('scroller2').scrollTop = 60;
-  document.getElementById('scroller3').scrollTop = 120;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1"  class="scroller">
-    <div class="indicator box" style="top: 0;"></div>
-    <div class="padding"></div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2"  class="scroller">
-    <div class="indicator box" style="top: 50px;"></div>
-    <div class="padding"></div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3"  class="scroller">
-    <div class="indicator box" style="top: 85px;"></div>
-    <div class="padding"></div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-margins.html b/css/css-position/position-sticky-margins.html
index 5b38ab4..b2d9944 100644
--- a/css/css-position/position-sticky-margins.html
+++ b/css/css-position/position-sticky-margins.html
@@ -1,92 +1,49 @@
 <!DOCTYPE html>
 <title>position:sticky elements should properly interact with margins</title>
-<link rel="match" href="position-sticky-margins-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="position:sticky elements should ignore margins when sticking, but consider them when making sure sticky elements do not escape their containing block" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 180px;
-  height: 400px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 150px;
-  height: 300px;
-  overflow-y: scroll;
-  overflow-x: hidden;
-}
+<script src="resources/sticky-util.js"></script>
 
-.holder {
-  width: 130px;
-  height: 200px;
-}
+<body></body>
 
-.sticky {
-  position: sticky;
-  background-color: green;
-  top: 5px;
-  margin: 15px;
-}
-
-.indicator {
-  left: 15px;
-  position: absolute;
-  background-color: red;
-}
-
-.box {
-  width: 100px;
-  height: 100px;
-}
-
-.padding {
-  height: 300px;
-}
-</style>
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 0;
-  document.getElementById('scroller2').scrollTop = 60;
-  document.getElementById('scroller3').scrollTop = 120;
-});
+test(() => {
+    const elements = setupStickyTest('top', 50);
+    elements.sticky.style.margin = '15px';
+    elements.scroller.scrollTop = 100;
+    assert_equals(elements.sticky.offsetTop,
+        elements.container.offsetTop + elements.filler.clientHeight + 15);
+}, 'Before sticking, the margin should be obeyed.');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.margin = '15px';
+
+  elements.scroller.scrollTop = 200;
+
+  // This math cancels to sticky.offsetTop == (scroller.scrollTop + 50), but
+  // for clarity the calculations are left explicit.
+  const nonStickyTopY = elements.container.offsetTop +
+    elements.filler.clientHeight;
+  const targetTopY = elements.scroller.scrollTop + 50;
+  const stickyOffset = targetTopY - nonStickyTopY;
+
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY + stickyOffset);
+}, 'Whilst stuck, the margin is irrelevant.');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.margin = '15px';
+
+  elements.scroller.scrollTop = 300;
+
+  const maxOffsetInContainer = elements.container.offsetTop +
+    elements.container.clientHeight - elements.sticky.clientHeight;
+  assert_equals(elements.sticky.offsetTop, maxOffsetInContainer - 15);
+}, 'The margin is taken into account when making sure the sticky element ' +
+    'does not escape its container');
 </script>
-
-<!-- Before sticking, the margin should be obeyed. -->
-<div class="group">
-  <div id="scroller1"  class="scroller">
-    <div class="indicator box" style="top: 15px;"></div>
-    <div class="holder">
-      <div class="sticky box"></div>
-    </div>
-    <div class="padding"></div>
-  </div>
-</div>
-
-<!-- Whilst stuck, the margin is irrelevant. -->
-<div class="group">
-  <div id="scroller2"  class="scroller">
-    <div class="indicator box" style="top: 65px;"></div>
-    <div class="holder">
-      <div class="sticky box"></div>
-    </div>
-    <div class="padding"></div>
-  </div>
-</div>
-
-<!-- The margin does count when making sure the sticky element does not escape
-     its containing block. -->
-<div class="group">
-  <div id="scroller3"  class="scroller">
-    <div class="indicator box" style="top: 100px;"></div>
-    <div class="holder">
-      <div class="sticky box"></div>
-    </div>
-    <div class="padding"></div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-bottom-ref.html b/css/css-position/position-sticky-nested-bottom-ref.html
deleted file mode 100644
index 6be2b5a..0000000
--- a/css/css-position/position-sticky-nested-bottom-ref.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for nested bottom-constrained position:sticky elements should render correctly</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
-
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.contents {
-  height: 500px;
-}
-
-.outerIndicator {
-  background-color: green;
-  position: absolute;
-  left: 0;
-  width: 100%;
-  height: 100px;
-}
-
-.innerIndicator {
-  background-color: blue;
-  position: absolute;
-  left: 0;
-  width: 100%;
-  height: 50px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 75;
-  document.getElementById('scroller2').scrollTop = 175;
-  document.getElementById('scroller3').scrollTop = 250;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="top: 200px;"></div>
-      <div class="innerIndicator" style="top: 200px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="top: 250px;"></div>
-      <div class="innerIndicator" style="top: 290px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="top: 300px;"></div>
-      <div class="innerIndicator" style="top: 350px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-bottom.html b/css/css-position/position-sticky-nested-bottom.html
index d4d20e5..edea11f 100644
--- a/css/css-position/position-sticky-nested-bottom.html
+++ b/css/css-position/position-sticky-nested-bottom.html
@@ -1,135 +1,68 @@
 <!DOCTYPE html>
 <title>Nested bottom-constrained position:sticky elements should render correctly</title>
-<link rel="match" href="position-sticky-nested-bottom-ref.html" />
+
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that nested position:sticky elements with a bottom constraint render correctly" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 500px;
-}
-
-.prepadding {
-  height: 200px;
-}
-
-.container {
-  height: 200px;
-}
-
-.filler {
-  height: 100px;
-}
-
-.outerIndicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-  width: 100%;
-  height: 100px;
-}
-
-.innerIndicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-  width: 100%;
-  height: 50px;
-}
-
-.outerSticky {
-  background-color: green;
-  position: sticky;
-  bottom: 25px;
-  width: 100%;
-  height: 100px;
-}
-
-.innerFiller {
-  height: 50px;
-}
-
-.innerSticky {
-  background-color: blue;
-  position: sticky;
-  bottom: 35px;
-  width: 100%;
-  height: 50px;
-}
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 75;
-  document.getElementById('scroller2').scrollTop = 175;
-  document.getElementById('scroller3').scrollTop = 250;
-});
+test(() => {
+  const elements = setupNestedStickyTest('bottom', 25, 35);
+  elements.scroller.scrollTop = 300;
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+  // The inner sticky should not be offset from the outer.
+  const nonStickyInnerTopY = elements.sticky.clientHeight -
+      elements.innerSticky.clientHeight;
+  assert_equals(elements.innerSticky.offsetTop, nonStickyInnerTopY);
+}, 'before reaching the sticking point, neither sticky box should be offset');
+
+test(() => {
+  const elements = setupNestedStickyTest('bottom', 25, 50);
+  elements.scroller.scrollTop = 150;
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+  assert_equals(elements.innerSticky.offsetTop, 35);
+}, 'the inner sticky can stick before the outer one if necessary');
+
+test(() => {
+  const elements = setupNestedStickyTest('bottom', 25, 35);
+  elements.scroller.scrollTop = 100;
+
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  const nonStickyBottomY = nonStickyTopY + elements.sticky.clientHeight;
+  const targetBottomY = elements.scroller.clientHeight +
+      elements.scroller.scrollTop - 25;
+  const stickyOffset = nonStickyBottomY - targetBottomY;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY - stickyOffset);
+
+  // The inner sticky has similar math, but its offsetTop is relative to the
+  // sticky element and in this test is (height - the difference between the
+  // top values).
+  assert_equals(elements.innerSticky.offsetTop, 40);
+}, 'both sticky boxes can be stuck at the same time');
+
+test(() => {
+  const elements = setupNestedStickyTest('bottom', 25, 35);
+  elements.scroller.scrollTop = 0;
+  assert_equals(elements.sticky.offsetTop, elements.container.offsetTop);
+  assert_equals(elements.innerSticky.offsetTop, 0);
+}, 'neither sticky can escape their containing block');
+
+test(() => {
+  const elements = setupNestedStickyTest('bottom', 25, 500);
+  elements.scroller.scrollTop = 200;
+  // It doesn't matter how big the inner sticky offset is, it cannot escape its
+  // containing block (the outer sticky).
+  assert_equals(elements.innerSticky.offsetTop, 0);
+}, 'the inner sticky cannot be pushed outside the outer sticky');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="outerIndicator" style="top: 200px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="top: 0;"></div>
-          <div class="innerFiller"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="outerIndicator" style="top: 250px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="top: 40px;"></div>
-          <div class="innerFiller"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="outerIndicator" style="top: 300px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="top: 50px;"></div>
-          <div class="innerFiller"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-left-ref.html b/css/css-position/position-sticky-nested-left-ref.html
deleted file mode 100644
index d3ab0d5..0000000
--- a/css/css-position/position-sticky-nested-left-ref.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for nested left-constrained position:sticky elements should render correctly</title>
-
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
-
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
-
-.contents {
-  height: 100%;
-  width: 500px;
-}
-
-.outerIndicator {
-  background-color: green;
-  position: absolute;
-  top: 0;
-  height: 100%;
-  width: 100px;
-}
-
-.innerIndicator {
-  background-color: blue;
-  position: absolute;
-  top: 0;
-  height: 100%;
-  width: 50px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 50;
-  document.getElementById('scroller2').scrollLeft = 125;
-  document.getElementById('scroller3').scrollLeft = 225;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="left: 150px;"></div>
-      <div class="innerIndicator" style="left: 150px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="left: 175px;"></div>
-      <div class="innerIndicator" style="left: 185px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="left: 200px;"></div>
-      <div class="innerIndicator" style="left: 250px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-left.html b/css/css-position/position-sticky-nested-left.html
index b5a23ab..755b69a 100644
--- a/css/css-position/position-sticky-nested-left.html
+++ b/css/css-position/position-sticky-nested-left.html
@@ -1,141 +1,75 @@
 <!DOCTYPE html>
 <title>Nested left-constrained position:sticky elements should render correctly</title>
-<link rel="match" href="position-sticky-nested-left-ref.html" />
+
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that nested position:sticky elements with a left constraint render correctly" />
 
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 100%;
-  width: 500px;
-  /* Allow nice formatting of inline divs. Fonts are not used in this test. */
-  font-size: 0;
-}
-
-.prepadding {
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.container {
-  display: inline-block;
-  height: 100%;
-  width: 200px;
-}
-
-.innerpadding {
-  display: inline-block;
-  height: 100%;
-  width: 50px;
-}
-
-.outerIndicator {
-  background-color: red;
-  position: absolute;
-  top: 0;
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.innerIndicator {
-  background-color: red;
-  position: absolute;
-  top: 0;
-  display: inline-block;
-  height: 100%;
-  width: 50px;
-}
-
-.outerSticky {
-  background-color: green;
-  position: sticky;
-  left: 50px;
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.innerSticky {
-  background-color: blue;
-  position: sticky;
-  left: 60px;
-  display: inline-block;
-  height: 100%;
-  width: 50px;
-}
-
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 50;
-  document.getElementById('scroller2').scrollLeft = 125;
-  document.getElementById('scroller3').scrollLeft = 225;
-});
+test(() => {
+  const elements = setupNestedStickyTest('left', 50, 60);
+  elements.scroller.scrollLeft = 100;
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX);
+  // The inner sticky should not be offset from the outer.
+  assert_equals(elements.innerSticky.offsetLeft, 0);
+}, 'before reaching the sticking point, neither sticky box should be offset');
+
+test(() => {
+  const elements = setupNestedStickyTest('left', 50, 60);
+  elements.scroller.scrollLeft = 145;
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX);
+  assert_equals(elements.innerSticky.offsetLeft, 5);
+}, 'the inner sticky can stick before the outer one if necessary');
+
+test(() => {
+  const elements = setupNestedStickyTest('left', 50, 60);
+  elements.scroller.scrollLeft = 200;
+
+  // This math cancels to sticky.offsetLeft == (scroller.scrollLeft + 50), but
+  // for clarity the calculations are left explicit.
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  const targetLeftX = elements.scroller.scrollLeft + 50;
+  const stickyOffset = targetLeftX - nonStickyLeftX;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX + stickyOffset);
+
+  // The inner sticky has similar math, but its offsetLeft is relative to the
+  // sticky element and in this test is the difference between the left values.
+  assert_equals(elements.innerSticky.offsetLeft, 10);
+}, 'both sticky boxes can be stuck at the same time');
+
+test(() => {
+  const elements = setupNestedStickyTest('left', 50, 60);
+  elements.scroller.scrollLeft = 300;
+  const maxOffsetInContainer = elements.container.offsetLeft +
+    elements.container.clientWidth - elements.sticky.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, maxOffsetInContainer);
+  const maxOffsetInOuterSticky = elements.sticky.clientWidth -
+      elements.innerSticky.clientWidth;
+  assert_equals(elements.innerSticky.offsetLeft, maxOffsetInOuterSticky);
+}, 'neither sticky can escape their containing block');
+
+test(() => {
+  const elements = setupNestedStickyTest('left', 50, 300);
+  elements.scroller.scrollLeft = 100;
+  // The outer sticky has not stuck yet.
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX);
+  // But the inner sticky still cannot escape the outer sticky (as it is the
+  // containing block).
+  const maxOffsetInOuterSticky = elements.sticky.clientWidth -
+      elements.innerSticky.clientWidth;
+  assert_equals(elements.innerSticky.offsetLeft, maxOffsetInOuterSticky);
+}, 'the inner sticky cannot be pushed outside the outer sticky');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="outerIndicator" style="left: 150px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="left: 0;"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="outerIndicator" style="left: 175px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding">
-        </div><div class="outerSticky">
-          <div class="innerIndicator" style="left: 10px;"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="outerIndicator" style="left: 200px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="left: 50px;"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-right-ref.html b/css/css-position/position-sticky-nested-right-ref.html
deleted file mode 100644
index 07852ba..0000000
--- a/css/css-position/position-sticky-nested-right-ref.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should respect the right constraint</title>
-
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
-
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
-
-.contents {
-  height: 100%;
-  width: 500px;
-}
-
-.outerIndicator {
-  background-color: green;
-  position: absolute;
-  top: 0;
-  height: 100%;
-  width: 100px;
-}
-
-.innerIndicator {
-  background-color: blue;
-  position: absolute;
-  top: 0;
-  height: 100%;
-  width: 50px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 75;
-  document.getElementById('scroller2').scrollLeft = 175;
-  document.getElementById('scroller3').scrollLeft = 250;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="left: 200px;"></div>
-      <div class="innerIndicator" style="left: 200px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="left: 250px;"></div>
-      <div class="innerIndicator" style="left: 290px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="outerIndicator" style="left: 300px;"></div>
-      <div class="innerIndicator" style="left: 350px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-right.html b/css/css-position/position-sticky-nested-right.html
index 411f722..9878aa7 100644
--- a/css/css-position/position-sticky-nested-right.html
+++ b/css/css-position/position-sticky-nested-right.html
@@ -1,149 +1,69 @@
 <!DOCTYPE html>
 <title>Nested right-constrained position:sticky elements should render correctly</title>
-<link rel="match" href="position-sticky-nested-right-ref.html" />
+
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that nested position:sticky elements with a right constraint render correctly" />
 
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 100%;
-  width: 500px;
-  /* Allow nice formatting of inline divs. Fonts are not used in this test. */
-  font-size: 0;
-}
-
-.prepadding {
-  display: inline-block;
-  height: 100%;
-  width: 200px;
-}
-
-.container {
-  display: inline-block;
-  height: 100%;
-  width: 200px;
-}
-
-.filler {
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.outerIndicator {
-  background-color: red;
-  position: absolute;
-  top: 0;
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.innerIndicator {
-  background-color: red;
-  position: absolute;
-  top: 0;
-  display: inline-block;
-  height: 100%;
-  width: 50px;
-}
-
-.outerSticky {
-  background-color: green;
-  position: sticky;
-  right: 25px;
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.innerFiller {
-  display: inline-block;
-  height: 100%;
-  width: 50px;
-}
-
-.innerSticky {
-  background-color: blue;
-  position: sticky;
-  right: 35px;
-  display: inline-block;
-  height: 100%;
-  width: 50px;
-}
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 75;
-  document.getElementById('scroller2').scrollLeft = 175;
-  document.getElementById('scroller3').scrollLeft = 250;
-});
+test(() => {
+  const elements = setupNestedStickyTest('right', 25, 35);
+  elements.scroller.scrollLeft = 200;
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX);
+  // The inner sticky should not be offset from the outer.
+  const nonStickyInnerLeftX = elements.sticky.clientWidth -
+      elements.innerSticky.clientWidth;
+  assert_equals(elements.innerSticky.offsetLeft, nonStickyInnerLeftX);
+}, 'before reaching the sticking point, neither sticky box should be offset');
+
+test(() => {
+  const elements = setupNestedStickyTest('right', 25, 50);
+  elements.scroller.scrollLeft = 150;
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX);
+  assert_equals(elements.innerSticky.offsetLeft, 35);
+}, 'the inner sticky can stick before the outer one if necessary');
+
+test(() => {
+  const elements = setupNestedStickyTest('right', 25, 35);
+  elements.scroller.scrollLeft = 100;
+
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  const nonStickyBottomX = nonStickyLeftX + elements.sticky.clientWidth;
+  const targetBottomX = elements.scroller.clientWidth +
+      elements.scroller.scrollLeft - 25;
+  const stickyOffset = nonStickyBottomX - targetBottomX;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX - stickyOffset);
+
+  // The inner sticky has similar math, but its offsetLeft is relative to the
+  // sticky element and in this test is (height - the difference between the
+  // top values).
+  assert_equals(elements.innerSticky.offsetLeft, 40);
+}, 'both sticky boxes can be stuck at the same time');
+
+test(() => {
+  const elements = setupNestedStickyTest('right', 25, 35);
+  elements.scroller.scrollLeft = 0;
+  assert_equals(elements.sticky.offsetLeft, elements.container.offsetLeft);
+  assert_equals(elements.innerSticky.offsetLeft, 0);
+}, 'neither sticky can escape their containing block');
+
+test(() => {
+  const elements = setupNestedStickyTest('right', 25, 500);
+  elements.scroller.scrollLeft = 200;
+  // It doesn't matter how big the inner sticky offset is, it cannot escape its
+  // containing block (the outer sticky).
+  assert_equals(elements.innerSticky.offsetLeft, 0);
+}, 'the inner sticky cannot be pushed outside the outer sticky');
+
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="outerIndicator" style="left: 200px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="left: 0;"></div>
-          <div class="innerFiller"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="outerIndicator" style="left: 250px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="left: 40px;"></div>
-          <div class="innerFiller"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="outerIndicator" style="left: 300px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="filler"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="left: 50px;"></div>
-          <div class="innerFiller"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-top-ref.html b/css/css-position/position-sticky-nested-top-ref.html
deleted file mode 100644
index f4696ce..0000000
--- a/css/css-position/position-sticky-nested-top-ref.html
+++ /dev/null
@@ -1,83 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for nested top-constrained position:sticky elements should render correctly</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
-
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.contents {
-  height: 500px;
-}
-
-.green {
-  background-color: green;
-}
-
-.blue {
-  background-color: blue;
-}
-
-.indicator {
-  position: absolute;
-  left: 0;
-}
-
-.bigBox {
-  width: 100%;
-  height: 100px;
-}
-
-.smallBox {
-  width: 100%;
-  height: 50px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 125;
-  document.getElementById('scroller3').scrollTop = 225;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="green indicator bigBox" style="top: 150px;"></div>
-      <div class="blue indicator smallBox" style="top: 150px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="green indicator bigBox" style="top: 175px;"></div>
-      <div class="blue indicator smallBox" style="top: 185px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="green indicator bigBox" style="top: 200px;"></div>
-      <div class="blue indicator smallBox" style="top: 250px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-nested-top.html b/css/css-position/position-sticky-nested-top.html
index 1ed9dc2..5332b46 100644
--- a/css/css-position/position-sticky-nested-top.html
+++ b/css/css-position/position-sticky-nested-top.html
@@ -1,128 +1,74 @@
 <!DOCTYPE html>
 <title>Nested top-constrainted position:sticky elements should render correctly</title>
-<link rel="match" href="position-sticky-nested-top-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that nested position:sticky elements with a top constraint render correctly" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 500px;
-}
-
-.prepadding {
-  height: 100px;
-}
-
-.container {
-  height: 200px;
-}
-
-.innerpadding {
-  height: 50px;
-}
-
-.outerIndicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-  height: 100px;
-  width: 100%;
-}
-
-.innerIndicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-  height: 50px;
-  width: 100%;
-}
-
-.outerSticky {
-  background-color: green;
-  position: sticky;
-  top: 50px;
-  width: 100%;
-  height: 100px;
-}
-
-.innerSticky {
-  background-color: blue;
-  position: sticky;
-  top: 60px;
-  width: 100%;
-  height: 50px;
-}
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 125;
-  document.getElementById('scroller3').scrollTop = 225;
-});
+test(() => {
+  const elements = setupNestedStickyTest('top', 50, 60);
+  elements.scroller.scrollTop = 100;
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+  // The inner sticky should not be offset from the outer.
+  assert_equals(elements.innerSticky.offsetTop, 0);
+}, 'before reaching the sticking point, neither sticky box should be offset');
+
+test(() => {
+  const elements = setupNestedStickyTest('top', 50, 60);
+  elements.scroller.scrollTop = 145;
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+  assert_equals(elements.innerSticky.offsetTop, 5);
+}, 'the inner sticky can stick before the outer one if necessary');
+
+test(() => {
+  const elements = setupNestedStickyTest('top', 50, 60);
+  elements.scroller.scrollTop = 200;
+
+  // This math cancels to sticky.offsetTop == (scroller.scrollTop + 50), but
+  // for clarity the calculations are left explicit.
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  const targetTopY = elements.scroller.scrollTop + 50;
+  const stickyOffset = targetTopY - nonStickyTopY;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY + stickyOffset);
+
+  // The inner sticky has similar math, but its offsetTop is relative to the
+  // sticky element and in this test is the difference between the top values.
+  assert_equals(elements.innerSticky.offsetTop, 10);
+}, 'both sticky boxes can be stuck at the same time');
+
+test(() => {
+  const elements = setupNestedStickyTest('top', 50, 60);
+  elements.scroller.scrollTop = 300;
+  const maxOffsetInContainer = elements.container.offsetTop +
+    elements.container.clientHeight - elements.sticky.clientHeight;
+  assert_equals(elements.sticky.offsetTop, maxOffsetInContainer);
+  const maxOffsetInOuterSticky = elements.sticky.clientHeight -
+      elements.innerSticky.clientHeight;
+  assert_equals(elements.innerSticky.offsetTop, maxOffsetInOuterSticky);
+}, 'neither sticky can escape their containing block');
+
+test(() => {
+  const elements = setupNestedStickyTest('top', 50, 300);
+  elements.scroller.scrollTop = 100;
+  // The outer sticky has not stuck yet.
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+  // But the inner sticky still cannot escape the outer sticky (as it is the
+  // containing block).
+  const maxOffsetInOuterSticky = elements.sticky.clientHeight -
+      elements.innerSticky.clientHeight;
+  assert_equals(elements.innerSticky.offsetTop, maxOffsetInOuterSticky);
+}, 'the inner sticky cannot be pushed outside the outer sticky');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="outerIndicator" style="top: 150px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="top: 0;"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="outerIndicator" style="top: 175px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="top: 10px;"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="outerIndicator" style="top: 200px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="outerSticky">
-          <div class="innerIndicator" style="top: 50px;"></div>
-          <div class="innerSticky"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green and three blue boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-offset-overflow.html b/css/css-position/position-sticky-offset-overflow.html
new file mode 100644
index 0000000..f4afed4
--- /dev/null
+++ b/css/css-position/position-sticky-offset-overflow.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Sticky positioning can cause overflow but must be accessible.</title>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="This test checks that a sticky positioned element
+can cause overflow but must still be accessible through scrolling" />
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+.container {
+  overflow: scroll;
+  width: 100px;
+  height: 100px;
+}
+
+.box {
+  background-color: green;
+  height: 50px;
+  width: 50px;
+}
+
+.sticky {
+  position: sticky;
+  top: 200px; /* Forces the sticky position element below the overflow. */
+}
+</style>
+
+<div id="scroller1" class="container">
+  <div class="sticky box"></div>
+</div>
+
+<div id="scroller2" class="container">
+  <div class="sticky box"></div>
+</div>
+
+<script>
+test(() => {
+    var scroller = document.getElementById('scroller1');
+    var sticky = scroller.querySelector('.sticky');
+
+    var stickyOffset = sticky.offsetTop -
+        scroller.scrollTop - scroller.offsetTop;
+    assert_equals(stickyOffset, 200);
+    assert_equals(scroller.scrollHeight, 250);
+}, 'sticky position offset should be able to cause overflow');
+
+test(() => {
+    var scroller = document.getElementById('scroller2');
+    var sticky = scroller.querySelector('.sticky');
+
+    scroller.scrollTop = 150;
+    var stickyOffset = sticky.offsetTop -
+        scroller.scrollTop - scroller.offsetTop;
+    assert_equals(stickyOffset, 50);
+    // Scroll height should be unaffected.
+    assert_equals(scroller.scrollHeight, 250);
+
+}, 'sticky position offset in overflow should be accessible');
+</script>
diff --git a/css/css-position/position-sticky-overflow-padding-ref.html b/css/css-position/position-sticky-overflow-padding-ref.html
deleted file mode 100644
index b0e1d46..0000000
--- a/css/css-position/position-sticky-overflow-padding-ref.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should respect padding on their ancestor overflow element</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
-
-.scroller {
-  padding: 20px 0;
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.contents {
-  height: 500px;
-}
-
-.indicator {
-  background-color: green;
-  position: absolute;
-  left: 0;
-}
-
-.box {
-  width: 100%;
-  height: 100px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 175;
-  document.getElementById('scroller3').scrollTop = 220;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 170px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 195px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 220px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-overflow-padding.html b/css/css-position/position-sticky-overflow-padding.html
index 0324861..fab6f2f 100644
--- a/css/css-position/position-sticky-overflow-padding.html
+++ b/css/css-position/position-sticky-overflow-padding.html
@@ -1,106 +1,54 @@
 <!DOCTYPE html>
 <title>position:sticky elements should respect padding on their ancestor overflow element</title>
-<link rel="match" href="position-sticky-overflow-padding-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that position:sticky elements respect padding on their ancestor overflow element" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  /* The target sticky position should be offset by this padding. */
-  padding: 20px 0;
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 500px;
-}
-
-.prepadding {
-  height: 100px;
-}
-
-.container {
-  height: 200px;
-}
-
-.innerpadding {
-  height: 50px;
-}
-
-.indicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-}
-
-.sticky {
-  background-color: green;
-  position: sticky;
-  top: 0;
-}
-
-.box {
-  width: 100%;
-  height: 100px;
-}
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 175;
-  document.getElementById('scroller3').scrollTop = 220;
-});
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.scroller.style.padding = '20px 0';
+
+  // Before sticking; the element isn't within the padding range.
+  elements.scroller.scrollTop = 150;
+  const nonStickyTopY = elements.container.offsetTop +
+          elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+}, 'A sticky element should not be affected by ancestor padding until it ' +
+    'reaches it');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.top = '0';
+  elements.scroller.style.padding = '20px 0';
+
+  elements.scroller.scrollTop = 200;
+
+  // This math cancels to sticky.offsetTop == (scroller.scrollTop + 50), but
+  // for clarity the calculations are left explicit.
+  const nonStickyTopY = elements.container.offsetTop +
+    elements.filler.clientHeight;
+  const targetTopY = elements.scroller.scrollTop;
+  const stickyOffset = targetTopY - nonStickyTopY;
+
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY + stickyOffset + 20);
+}, 'A sticky element should be offset by ancestor padding even when stuck');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.top = '0';
+  elements.scroller.style.padding = '20px 0';
+
+  elements.scroller.scrollTop = 315;
+  const maxOffsetInContainer = elements.container.offsetTop +
+      elements.container.clientHeight - elements.sticky.clientHeight;
+  assert_equals(elements.sticky.offsetTop, maxOffsetInContainer);
+}, 'Ancestor overflow padding does not allow a sticky element to escape its ' +
+    'container');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="indicator box" style="top: 170px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="indicator box" style="top: 195px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="indicator box" style="top: 220px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-rendering-ref.html b/css/css-position/position-sticky-rendering-ref.html
new file mode 100644
index 0000000..a052eae
--- /dev/null
+++ b/css/css-position/position-sticky-rendering-ref.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<title>Reference for position:sticky elements should be rendered at their sticky offset</title>
+
+<style>
+.group {
+  display: inline-block;
+  position: relative;
+  width: 150px;
+  height: 250px;
+}
+
+.inlineGroup {
+  display: inline-block;
+  position: relative;
+  width: 250px;
+  height: 150px;
+}
+
+.scroller {
+  position: relative;
+  width: 100px;
+  height: 200px;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.inlineGroup .scroller {
+  position: relative;
+  width: 200px;
+  height: 100px;
+  overflow-x: auto;
+  overflow-y: hidden;
+}
+
+.contents {
+  height: 500px;
+}
+
+.inlineGroup .contents {
+  height: 100%;
+  width: 500px;
+}
+
+.indicator {
+  background-color: green;
+  position: absolute;
+}
+
+.box {
+  width: 100%;
+  height: 100px;
+}
+
+.inlineGroup .box {
+  height: 100%;
+  width: 100px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+  document.getElementById('scroller1').scrollTop = 125;
+  document.getElementById('scroller2').scrollTop = 50;
+  document.getElementById('scroller3').scrollLeft = 125;
+  document.getElementById('scroller4').scrollLeft = 75;
+});
+</script>
+
+<div class="group">
+  <div id="scroller1" class="scroller">
+    <div class="contents">
+      <div class="indicator box" style="top: 175px;"></div>
+    </div>
+  </div>
+</div>
+
+<div class="group">
+  <div id="scroller2" class="scroller">
+    <div class="contents">
+      <div class="indicator box" style="top: 125px;"></div>
+    </div>
+  </div>
+</div>
+
+<!-- Force break to make sure we are within 800px wide. -->
+<div></div>
+
+<div class="inlineGroup">
+  <div id="scroller3" class="scroller">
+    <div class="contents">
+      <div class="indicator box" style="left: 175px;"></div>
+    </div>
+  </div>
+</div>
+
+<div class="inlineGroup">
+  <div id="scroller4" class="scroller">
+    <div class="contents">
+      <div class="indicator box" style="left: 150px;"></div>
+    </div>
+  </div>
+</div>
+
+<div>You should see four green squares above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-rendering.html b/css/css-position/position-sticky-rendering.html
new file mode 100644
index 0000000..39afca1
--- /dev/null
+++ b/css/css-position/position-sticky-rendering.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<title>position:sticky elements should be rendered at their sticky offset</title>
+<link rel="match" href="position-sticky-rendering-ref.html" />
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="This test checks that position:sticky elements are rendered correctly" />
+
+<style>
+.group {
+  display: inline-block;
+  position: relative;
+  width: 150px;
+  height: 250px;
+}
+
+.inlineGroup {
+  display: inline-block;
+  position: relative;
+  width: 250px;
+  height: 150px;
+}
+
+.scroller {
+  position: relative;
+  width: 100px;
+  height: 200px;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.inlineGroup .scroller {
+  position: relative;
+  width: 200px;
+  height: 100px;
+  overflow-x: auto;
+  overflow-y: hidden;
+}
+
+.contents {
+  height: 500px;
+}
+
+.inlineGroup .contents {
+  height: 100%;
+  width: 500px;
+}
+
+.prepadding {
+  height: 100px;
+}
+
+.inlineGroup .prepadding {
+  display: inline-block;
+  height: 100%;
+  width: 100px;
+}
+
+.container {
+  height: 200px;
+}
+
+.inlineGroup .container {
+  display: inline-block;
+  height: 100%;
+  width: 200px;
+}
+
+.filler {
+  height: 100px;
+}
+
+.inlineGroup .filler {
+  display: inline-block;
+  height: 100%;
+  width: 100px;
+}
+
+.indicator {
+  background-color: red;
+  position: absolute;
+}
+
+.sticky {
+  background-color: green;
+  position: sticky;
+}
+
+.box {
+  width: 100%;
+  height: 100px;
+}
+
+.inlineGroup .box {
+  display: inline-block;
+  height: 100%;
+  width: 100px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+  document.getElementById('scroller1').scrollTop = 125;
+  document.getElementById('scroller2').scrollTop = 50;
+  document.getElementById('scroller3').scrollLeft = 125;
+  document.getElementById('scroller4').scrollLeft = 75;
+});
+</script>
+
+<div class="group">
+  <div id="scroller1" class="scroller">
+    <div class="indicator box" style="top: 175px;"></div>
+    <div class="contents">
+      <div class="prepadding"></div>
+      <div class="container">
+        <div style="top: 50px;" class="sticky box"></div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="group">
+  <div id="scroller2" class="scroller">
+    <div class="indicator box" style="top: 125px;"></div>
+    <div class="contents">
+      <div class="prepadding"></div>
+      <div class="container">
+        <div class="filler"></div>
+        <div style="bottom: 25px;" class="sticky box"></div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<!-- Force break to make sure we are within 800px wide. -->
+<div></div>
+
+<div class="inlineGroup">
+  <div id="scroller3" class="scroller">
+    <div class="indicator box" style="left: 175px;"></div>
+    <div class="contents">
+      <!-- As these elements are inline, they are whitespace sensitive. -->
+      <div class="prepadding"></div><div class="container"><div style="left: 50px;" class="sticky box"></div></div>
+    </div>
+  </div>
+</div>
+
+<div class="inlineGroup">
+  <div id="scroller4" class="scroller">
+    <div class="indicator box" style="left: 150px;"></div>
+    <div class="contents">
+      <!-- As these elements are inline, they are whitespace sensitive. -->
+      <div class="prepadding"></div><div class="container"><div class="filler"></div><div style="right: 25px;" class="sticky box"></div></div>
+    </div>
+  </div>
+</div>
+
+<div>You should see four green squares above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-right-ref.html b/css/css-position/position-sticky-right-ref.html
deleted file mode 100644
index 14ed476..0000000
--- a/css/css-position/position-sticky-right-ref.html
+++ /dev/null
@@ -1,68 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should respect the right constraint</title>
-
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
-
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
-
-.contents {
-  height: 100%;
-  width: 500px;
-}
-
-.indicator {
-  background-color: green;
-  position: absolute;
-  top: 0;
-}
-
-.box {
-  height: 100%;
-  width: 100px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 100;
-  document.getElementById('scroller2').scrollLeft = 175;
-  document.getElementById('scroller3').scrollLeft = 250;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="left: 200px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="left: 250px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="left: 300px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-right.html b/css/css-position/position-sticky-right.html
index 6c4e696..d280a1f 100644
--- a/css/css-position/position-sticky-right.html
+++ b/css/css-position/position-sticky-right.html
@@ -1,102 +1,41 @@
 <!DOCTYPE html>
 <title>position:sticky elements should respect the right constraint</title>
-<link rel="match" href="position-sticky-right-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that position:sticky elements obey their right anchor after scrolling" />
 
-<style>
-.group {
-  position: relative;
-  width: 250px;
-  height: 150px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 200px;
-  height: 100px;
-  overflow-x: auto;
-  overflow-y: hidden;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 100%;
-  width: 500px;
-}
-
-.prepadding {
-  display: inline-block;
-  height: 100%;
-  width: 200px;
-}
-
-.container {
-  display: inline-block;
-  height: 100%;
-  width: 200px;
-}
-
-.filler {
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-
-.indicator {
-  background-color: red;
-  position: absolute;
-  top: 0;
-}
-
-.sticky {
-  background-color: green;
-  position: sticky;
-  right: 25px;
-}
-
-.box {
-  display: inline-block;
-  height: 100%;
-  width: 100px;
-}
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollLeft = 100;
-  document.getElementById('scroller2').scrollLeft = 175;
-  document.getElementById('scroller3').scrollLeft = 250;
-});
+test(() => {
+  const elements = setupStickyTest('right', 25);
+  elements.scroller.scrollLeft = 200;
+  const nonStickyLeftX = elements.container.offsetLeft +
+    elements.filler.clientWidth;
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX);
+}, 'before reaching the sticking point the sticky box should not be offset');
+
+test(() => {
+  const elements = setupStickyTest('right', 25);
+  elements.scroller.scrollLeft = 75;
+
+  const nonStickyLeftX = elements.container.offsetLeft +
+      elements.filler.clientWidth;
+  const nonStickyRightX = nonStickyLeftX + elements.sticky.clientWidth;
+  const targetRightX = elements.scroller.clientWidth +
+      elements.scroller.scrollLeft - 25;
+  const stickyOffset = nonStickyRightX - targetRightX;
+
+  assert_equals(elements.sticky.offsetLeft, nonStickyLeftX - stickyOffset);
+}, 'after reaching the sticking point the sticky box should be offset');
+
+test(() => {
+  const elements = setupStickyTest('right', 25);
+  elements.scroller.scrollLeft = 15;
+  assert_equals(elements.sticky.offsetLeft, elements.container.offsetLeft);
+}, 'the sticky box should not be pushed outside its containing block');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="indicator box" style="left: 200px;"></div>
-    <div class="contents">
-      <!-- As these elements are inline, they are whitespace sensitive. -->
-      <div class="prepadding"></div><div class="container"><div class="filler"></div><div class="sticky box"></div></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="indicator box" style="left: 250px;"></div>
-    <div class="contents">
-      <!-- As these elements are inline, they are whitespace sensitive. -->
-      <div class="prepadding"></div><div class="container"><div class="filler"></div><div class="sticky box"></div></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="indicator box" style="left: 300px;"></div>
-    <div class="contents">
-      <!-- As these elements are inline, they are whitespace sensitive. -->
-      <div class="prepadding"></div><div class="container"><div class="filler"></div><div class="sticky box"></div></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-root-scroller-ref.html b/css/css-position/position-sticky-root-scroller-ref.html
deleted file mode 100644
index a6ded09..0000000
--- a/css/css-position/position-sticky-root-scroller-ref.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky should operate correctly for the root scroller</title>
-
-<style>
-body {
-  /* Assumption: 3000px is taller than any user agents test window size. */
-  height: 3000px;
-}
-
-.indicator {
-  background-color: green;
-  position: absolute;
-  top: 750px;
-}
-
-.box {
-  width: 200px;
-  height: 200px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  window.scrollTo(0, 700);
-});
-</script>
-
-<div class="indicator box"></div>
-
-<div style="position: absolute; top: 1000px;">You should see a green box above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-root-scroller.html b/css/css-position/position-sticky-root-scroller.html
index 35ab8dc..596fd9b 100644
--- a/css/css-position/position-sticky-root-scroller.html
+++ b/css/css-position/position-sticky-root-scroller.html
@@ -1,39 +1,31 @@
 <!DOCTYPE html>
 <title>position:sticky should operate correctly for the root scroller</title>
-<link rel="match" href="position-sticky-root-scroller-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that position:sticky elements work when using the root (document) scroller" />
 
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
 <style>
 body {
   /* Assumption: 3000px is taller than any user agents test window size. */
   height: 3000px;
 }
 
-.indicator {
-  background-color: red;
-  position: absolute;
-}
-
-.sticky {
-  background-color: green;
+#sticky {
   position: sticky;
   top: 50px;
-}
-
-.box {
   width: 200px;
   height: 200px;
+  background-color: green;
 }
 </style>
 
+<div id="sticky"></div>
+
 <script>
-window.addEventListener('load', function() {
+test(() => {
   window.scrollTo(0, 700);
-});
+  assert_equals(sticky.offsetTop, 700 + 50);
+}, 'Sticky elements work with the root (document) scroller');
 </script>
-
-<div class="indicator box" style="top: 750px;"></div>
-<div class="sticky box"></div>
-
-<div style="position: absolute; top: 1000px;">You should see a green box above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-table-parts-ref.html b/css/css-position/position-sticky-table-parts-ref.html
new file mode 100644
index 0000000..e6988ee
--- /dev/null
+++ b/css/css-position/position-sticky-table-parts-ref.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Nested position:sticky table elements should render correctly</title>
+<style>
+.scroller {
+  width: 100px;
+  height: 250px;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.contents {
+  height: 700px;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+#child, td, th {
+  height: 50px;
+  width: 50px;
+  padding: 0;
+  background: green;
+}
+
+.prepadding {
+  height: 155px;
+}
+
+</style>
+
+<script>
+window.addEventListener('load', function() {
+  document.getElementById('scroller1').scrollTop = 150;
+});
+</script>
+
+  <div id="scroller1" class="scroller">
+    <div class="contents">
+      <div class="prepadding"></div>
+      <table>
+        <tbody>
+          <tr><td><div id="child"></div></td></tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+<div>There should be a green square at the top of the scroll view and no red visible.</div>
diff --git a/css/css-position/position-sticky-table-parts.html b/css/css-position/position-sticky-table-parts.html
new file mode 100644
index 0000000..c1cca83
--- /dev/null
+++ b/css/css-position/position-sticky-table-parts.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<title>Nested position:sticky table elements should render correctly</title>
+<link rel="match" href="position-sticky-table-parts-ref.html" />
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="This test checks that nested position:sticky table elements render correctly" />
+
+<style>
+.scroller {
+  position: relative;
+  width: 100px;
+  height: 250px;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.contents {
+  height: 700px;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+.child, td, th {
+  height: 50px;
+  width: 50px;
+  padding: 0;
+}
+
+.child {
+  background: green;
+}
+
+table * {
+  position: sticky;
+  top: 5px;
+}
+
+.indicator {
+  position: absolute;
+  left: 0;
+  background-color: red;
+  height: 50px;
+  width: 50px;
+}
+
+</style>
+
+<script>
+window.addEventListener('load', function() {
+  document.body.offsetTop;
+  document.getElementById('scroller1').scrollTop = 150;
+});
+</script>
+
+<div id="scroller1" class="scroller">
+  <div class="contents">
+    <div class="indicator" style="top: 155px;"></div>
+    <table>
+      <tbody>
+        <tr><td><div class="child"></div></td></tr>
+        <tr><td></td></tr>
+        <tr><td></td></tr>
+        <tr><td></td></tr>
+        <tr><td></td></tr>
+        <tr><td></td></tr>
+        <tr><td></td></tr>
+      </tbody>
+    </table>
+  </div>
+</div>
+<div>There should be a green square at the top of the scroll view and no red visible.</div>
diff --git a/css/css-position/position-sticky-top-ref.html b/css/css-position/position-sticky-top-ref.html
deleted file mode 100644
index 3003b94..0000000
--- a/css/css-position/position-sticky-top-ref.html
+++ /dev/null
@@ -1,68 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for position:sticky elements should respect the top constraint</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
-
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.contents {
-  height: 500px;
-}
-
-.indicator {
-  background-color: green;
-  position: absolute;
-  left: 0;
-}
-
-.box {
-  width: 100%;
-  height: 100px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 125;
-  document.getElementById('scroller3').scrollTop = 200;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 150px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 175px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 200px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-top.html b/css/css-position/position-sticky-top.html
index fc07313..e4889d1 100644
--- a/css/css-position/position-sticky-top.html
+++ b/css/css-position/position-sticky-top.html
@@ -1,104 +1,43 @@
 <!DOCTYPE html>
 <title>position:sticky elements should respect the top constraint</title>
-<link rel="match" href="position-sticky-top-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that position:sticky elements obey their top anchor after scrolling" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 500px;
-}
-
-.prepadding {
-  height: 100px;
-}
-
-.container {
-  height: 200px;
-}
-
-.innerpadding {
-  height: 50px;
-}
-
-.indicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-}
-
-.sticky {
-  background-color: green;
-  position: sticky;
-  top: 50px;
-}
-
-.box {
-  width: 100%;
-  height: 100px;
-}
-</style>
+<body></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 125;
-  document.getElementById('scroller3').scrollTop = 200;
-});
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.scroller.scrollTop = 100;
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY);
+}, 'before reaching the sticking point the sticky box should not be offset');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.scroller.scrollTop = 200;
+
+  // This math cancels to sticky.offsetTop == (scroller.scrollTop + 50), but
+  // for clarity the calculations are left explicit.
+  const nonStickyTopY = elements.container.offsetTop +
+      elements.filler.clientHeight;
+  const targetTopY = elements.scroller.scrollTop + 50;
+  const stickyOffset = targetTopY - nonStickyTopY;
+
+  assert_equals(elements.sticky.offsetTop, nonStickyTopY + stickyOffset);
+}, 'after reaching the sticking point the sticky box should be offset');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.scroller.scrollTop = 300;
+  const maxOffsetInContainer = elements.container.offsetTop +
+      elements.container.clientHeight - elements.sticky.clientHeight;
+  assert_equals(elements.sticky.offsetTop, maxOffsetInContainer);
+}, 'the sticky box should not be pushed outside its containing block');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="indicator box" style="top: 150px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="indicator box" style="top: 175px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="indicator box" style="top: 200px;"></div>
-    <div class="contents">
-      <div class="prepadding"></div>
-      <div class="container">
-        <div class="innerpadding"></div>
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-transforms-ref.html b/css/css-position/position-sticky-transforms-ref.html
deleted file mode 100644
index f865a9b..0000000
--- a/css/css-position/position-sticky-transforms-ref.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for transforms on position:sticky elements should apply after sticking</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
-
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.contents {
-  height: 500px;
-}
-
-.indicator {
-  background-color: green;
-  position: relative;
-}
-
-.box {
-  width: 100%;
-  height: 50px;
-}
-
-.rotated {
-  transform: rotateX(60deg);
-  height: 100px;
-  width: 100%;
-}
-
-.perspective {
-  transform: perspective(3px) translateZ(1px);
-  height: 50px;
-  width: 50px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 50;
-  document.getElementById('scroller3').scrollTop = 50;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="height: 100px; top: 75px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="rotated indicator" style="top: 100px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <!-- Required for blending. -->
-    <div class="perspective" style="position: absolute; background: red; top: 100px;"></div>
-    <div class="contents">
-      <div class="perspective indicator" style="top: 100px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-transforms-translate-ref.html b/css/css-position/position-sticky-transforms-translate-ref.html
deleted file mode 100644
index b357795..0000000
--- a/css/css-position/position-sticky-transforms-translate-ref.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for translations on position:sticky elements should apply after sticking</title>
-
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
-
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-
-.contents {
-  height: 500px;
-}
-
-.indicator {
-  background-color: green;
-  position: relative;
-}
-
-.box {
-  width: 100%;
-  height: 50px;
-}
-</style>
-
-<script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 70;
-  document.getElementById('scroller3').scrollTop = 50;
-});
-</script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 50px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 50px;"></div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="contents">
-      <div class="indicator box" style="top: 200px;"></div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-transforms-translate.html b/css/css-position/position-sticky-transforms-translate.html
index 076db9e..791b035 100644
--- a/css/css-position/position-sticky-transforms-translate.html
+++ b/css/css-position/position-sticky-transforms-translate.html
@@ -1,92 +1,45 @@
 <!DOCTYPE html>
 <title>translations on position:sticky elements should apply after sticking</title>
-<link rel="match" href="position-sticky-transforms-translate-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that translations on position:sticky elements are carried out on their stuck position" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 500px;
-}
-
-.container {
-  height: 150px;
-}
-
-.indicator {
-  background-color: red;
-  position: absolute;
-  left: 0;
-}
-
-.sticky {
-  background-color: green;
-  position: sticky;
-  top: 50px;
-}
-
-.box {
-  width: 100%;
-  height: 50px;
-}
-</style>
+<body style="margin: 0;"></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 70;
-  document.getElementById('scroller3').scrollTop = 50;
-});
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.transform = 'translateY(-100%)';
+  elements.scroller.scrollTop = 100;
+  // Transforms don't affect offsetTop, so use getBoundingClientRect.
+  assert_equals(elements.sticky.getBoundingClientRect().y,
+      elements.scroller.getBoundingClientRect().y);
+}, 'Translation transform can move sticky element past sticking point');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.transform = 'translateY(50%)';
+  elements.scroller.scrollTop = 200;
+  // Transforms don't affect offsetTop, so use getBoundingClientRect.
+  const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
+      elements.scroller.getBoundingClientRect().y;
+  assert_equals(stickyElementOffset, 100);
+}, 'Stuck elements can still be moved via translations');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.container.style.transform = 'translateY(100px)';
+  elements.scroller.scrollTop = 200;
+  // Transforms don't affect offsetTop, so use getBoundingClientRect.
+  // Here the sticky element will originally have stuck at 50px from the top,
+  // but is then 'pulled' downwards by the 100px container transform.
+  const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
+      elements.scroller.getBoundingClientRect().y;
+  assert_equals(stickyElementOffset, 150);
+}, 'The sticky element should stick before the container is offset by a ' +
+    'translation');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="indicator box" style="top: 50px;"></div>
-    <div class="contents">
-      <div class="container">
-        <div class="sticky box" style="transform: translateY(-100%);"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<!-- The pre-transform sticky is not allowed to escape its containing block. -->
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="indicator box" style="top: 50px;"></div>
-    <div class="contents">
-      <div class="container">
-        <div class="sticky box" style="transform: translateY(-100%);"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<!-- The sticky element should stick before the container is transformed. -->
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="indicator box" style="top: 200px;"></div>
-    <div class="contents">
-      <div class="container" style="transform: translateY(100px);">
-        <div class="sticky box"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/position-sticky-transforms.html b/css/css-position/position-sticky-transforms.html
index f9e6386..9f18d58 100644
--- a/css/css-position/position-sticky-transforms.html
+++ b/css/css-position/position-sticky-transforms.html
@@ -1,101 +1,49 @@
 <!DOCTYPE html>
 <title>transforms on position:sticky elements should apply after sticking</title>
-<link rel="match" href="position-sticky-transforms-ref.html" />
 <link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
 <meta name="assert" content="This test checks that transforms on position:sticky elements are carried out on their stuck position" />
 
-<style>
-.group {
-  display: inline-block;
-  position: relative;
-  width: 150px;
-  height: 250px;
-}
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 
-.scroller {
-  position: relative;
-  width: 100px;
-  height: 200px;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
+<script src="resources/sticky-util.js"></script>
 
-.contents {
-  height: 500px;
-}
-
-.container {
-  height: 150px;
-}
-
-.indicator {
-  background-color: red;
-  position: absolute;
-}
-
-.sticky {
-  background-color: green;
-  position: sticky;
-  top: 50px;
-}
-
-.box {
-  width: 100%;
-  height: 50px;
-}
-
-.rotated {
-  transform: rotateX(60deg);
-  width: 100%;
-  height: 100px;
-}
-
-.perspective {
-  transform: perspective(3px) translateZ(1px);
-  height: 50px;
-  width: 50px;
-}
-</style>
+<body style="margin: 0;"></body>
 
 <script>
-window.addEventListener('load', function() {
-  document.getElementById('scroller1').scrollTop = 50;
-  document.getElementById('scroller2').scrollTop = 50;
-  document.getElementById('scroller3').scrollTop = 50;
-});
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.transform = 'scale(2)';
+  elements.scroller.scrollTop = 200;
+
+  // Transforms don't affect offsetTop, so use getBoundingClientRect.
+  // Scaling the sticky element by 2 means its top-y moves (1/2 * height)
+  // upwards, in this case placing it at the top of the viewport.
+  const boundingRect = elements.sticky.getBoundingClientRect();
+  assert_equals(boundingRect.y, elements.scroller.getBoundingClientRect().y);
+}, 'Scale transforms are carried out on the stuck element position');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.transform = 'rotateX(60deg)';
+  elements.scroller.scrollTop = 200;
+
+  // Transforms don't affect offsetTop, so use getBoundingClientRect.
+  // Rotating around the x-axis essentially 'squashes' it (from the camera's
+  // viewpoint), in this case shifting the offset to 75 rather than 50.
+  const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
+    elements.scroller.getBoundingClientRect().y;
+  assert_equals(stickyElementOffset, 75);
+}, 'Rotate transforms are carried out on the stuck element position');
+
+test(() => {
+  const elements = setupStickyTest('top', 50);
+  elements.sticky.style.transform = 'perspective(3px) translateZ(1px)';
+  elements.scroller.scrollTop = 200;
+
+  // Transforms don't affect offsetTop, so use getBoundingClientRect.
+  const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
+    elements.scroller.getBoundingClientRect().y;
+  assert_equals(stickyElementOffset, 25);
+}, 'Perspective transforms are carried out on the stuck element position');
 </script>
-
-<div class="group">
-  <div id="scroller1" class="scroller">
-    <div class="indicator box" style="height: 100px; top: 75px;"></div>
-    <div class="contents">
-      <div class="container">
-        <div class="sticky box" style="transform: scale(2);"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller2" class="scroller">
-    <div class="rotated indicator" style="top: 100px;"></div>
-    <div class="contents">
-      <div class="container" style="height: 250px;">
-        <div class="rotated sticky"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div class="group">
-  <div id="scroller3" class="scroller">
-    <div class="perspective indicator" style="top: 100px;"></div>
-    <div class="contents">
-      <div class="container">
-        <div class="perspective sticky"></div>
-      </div>
-    </div>
-  </div>
-</div>
-
-<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position/resources/sticky-util.js b/css/css-position/resources/sticky-util.js
new file mode 100644
index 0000000..969cede
--- /dev/null
+++ b/css/css-position/resources/sticky-util.js
@@ -0,0 +1,106 @@
+/**
+ * Builds a generic structure that looks like:
+ *
+ * <div class="scroller">  // 100x200 viewport
+ *   <div class="contents">  // 100x500
+ *     <div class="prepadding"></div> // 100x100
+ *     <div class="container">  // 300x300 containing block
+ *       <div class="filler"></div>  // 100x100
+ *       <div class="sticky box"></div>  // 100x100
+ *     </div>
+ *   </div>
+ * </div>
+ *
+ * If the sticky direction is 'left' or 'right', the necessary blocks will be
+ * marked as inline-block and the dimensions above are flipped.
+ *
+ * Returns an 'elements' object which has each of the above elements as an
+ * accessible property.
+ */
+function setupStickyTest(stickyDirection, stickyOffset) {
+  const elements = {};
+  const inline = stickyDirection === 'left' || stickyDirection === 'right';
+
+  elements.scroller = document.createElement('div');
+  elements.scroller.style.position = 'relative';
+  elements.scroller.style.width = (inline ? '200px' : '100px');
+  elements.scroller.style.height = (inline ? '100px' : '200px');
+  elements.scroller.style.overflow = 'scroll';
+
+  elements.contents = document.createElement('div');
+  elements.contents.style.height = (inline ? '100%' : '500px');
+  elements.contents.style.width = (inline ? '500px' : '100%');
+
+  elements.prepadding = document.createElement('div');
+  elements.prepadding.style.height = (inline ? '100%' : '100px');
+  elements.prepadding.style.width = (inline ? '100px' : '100%');
+  if (inline)
+    elements.prepadding.style.display = 'inline-block';
+
+  elements.container = document.createElement('div');
+  elements.container.style.height = (inline ? '100%' : '300px');
+  elements.container.style.width = (inline ? '300px' : '100%');
+  if (inline)
+    elements.container.style.display = 'inline-block';
+
+  elements.filler = document.createElement('div');
+  elements.filler.style.height = (inline ? '100%' : '100px');
+  elements.filler.style.width = (inline ? '100px' : '100%');
+  if (inline)
+    elements.filler.style.display = 'inline-block';
+
+  elements.sticky = document.createElement('div');
+  elements.sticky.style = `${stickyDirection}: ${stickyOffset}px;`;
+  elements.sticky.style.position = 'sticky';
+  elements.sticky.style.height = (inline ? '100%' : '100px');
+  elements.sticky.style.width = (inline ? '100px' : '100%');
+  elements.sticky.style.backgroundColor = 'green';
+  if (inline)
+    elements.sticky.style.display = 'inline-block';
+
+  elements.scroller.appendChild(elements.contents);
+  elements.contents.appendChild(elements.prepadding);
+  elements.contents.appendChild(elements.container);
+  elements.container.appendChild(elements.filler);
+  elements.container.appendChild(elements.sticky);
+
+  document.body.appendChild(elements.scroller);
+
+  return elements;
+}
+
+/**
+ * Similar to above, but nests a second sticky (named innerSticky) inside the
+ * sticky element.
+ *
+ * In the 'bottom' and 'right' cases, we also inject some padding before the
+ * innerSticky element, to give it something to push into. This inner padding is
+ * not exposed.
+ */
+function setupNestedStickyTest(stickyDirection, outerStickyOffset,
+    innerStickyOffset) {
+  const elements = setupStickyTest(stickyDirection, outerStickyOffset);
+
+  const inline = stickyDirection === 'left' || stickyDirection === 'right';
+  if (stickyDirection === 'bottom' || stickyDirection === 'right') {
+    const innerPadding = document.createElement('div');
+    innerPadding.style.height = (inline ? '100%' : '50px');
+    innerPadding.style.width = (inline ? '50px' : '100%');
+    if (inline)
+      innerPadding.style.display = 'inline-block';
+    elements.sticky.appendChild(innerPadding);
+  }
+
+  elements.innerSticky = document.createElement('div');
+  elements.innerSticky.style = `${stickyDirection}: ${innerStickyOffset}px;`;
+  elements.innerSticky.style.position = 'sticky';
+  elements.innerSticky.style.height = (inline ? '100%' : '50px');
+  elements.innerSticky.style.width = (inline ? '50px' : '100%');
+  elements.innerSticky.style.backgroundColor = 'blue';
+  if (inline)
+    elements.innerSticky.style.display = 'inline-block';
+
+  elements.sticky.appendChild(elements.innerSticky);
+
+  return elements;
+}
diff --git a/css/css-properties-values-api/OWNERS b/css/css-properties-values-api/OWNERS
new file mode 100644
index 0000000..00afa92
--- /dev/null
+++ b/css/css-properties-values-api/OWNERS
@@ -0,0 +1,2 @@
+@tabatkins
+@astearns
diff --git a/css/css-properties-values-api/register-property-syntax-parsing.html b/css/css-properties-values-api/register-property-syntax-parsing.html
new file mode 100644
index 0000000..500add4
--- /dev/null
+++ b/css/css-properties-values-api/register-property-syntax-parsing.html
@@ -0,0 +1,163 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#supported-syntax-strings" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test_count = 0;
+
+function assert_valid(syntax, initialValue) {
+    // No actual assertions, this just shouldn't throw
+    test(function() {
+        var name = '--syntax-test-' + (test_count++);
+        CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue});
+    }, "syntax:'" + syntax + "', initialValue:'" + initialValue + "' is valid");
+}
+
+function assert_invalid(syntax, initialValue) {
+    test(function(){
+        var name = '--syntax-test-' + (test_count++);
+        assert_throws(new SyntaxError(),
+            () => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue}));
+    }, "syntax:'" + syntax + "', initialValue:'" + initialValue + "' is invalid");
+}
+
+assert_valid("*", "a");
+assert_valid(" * ", "b");
+assert_valid("<length>", "2px");
+assert_valid(" <number>", "5");
+assert_valid("<percentage> ", "10%");
+assert_valid("<color>+", "red");
+assert_valid(" <length>+ | <percentage>", "2px 8px");
+assert_valid("<length>|<percentage>|<length-percentage>", "2px"); // Valid but silly
+assert_valid("<color> | <image> | <url> | <integer> | <angle>", "red");
+assert_valid("<time> | <resolution> | <transform-list> | <custom-ident>", "red");
+
+assert_valid("*", ":> hello");
+assert_valid("*", "([ brackets ]) { yay (??)}");
+assert_valid("*", "yep 'this is valid too'");
+assert_valid("*", "unmatched opening bracket is valid :(");
+assert_valid("*", '"');
+
+assert_valid("<length>", "0");
+assert_valid("<length>", "10px /*:)*/");
+assert_valid("<length>", " calc(-2px)");
+assert_valid("<length>", "calc(2px*4 + 10px)");
+assert_valid("<length>", "7.1e-4cm");
+assert_valid("<length>", "calc(7in - 12px)");
+assert_valid("<length>+", "2px 7px calc(8px)");
+assert_valid("<percentage>", "-9.3e3%");
+assert_valid("<length-percentage>", "-54%");
+assert_valid("<length-percentage>", "0");
+assert_valid("<length-percentage>", "calc(-11px + 10.4%)");
+
+assert_valid("<number>", "-109");
+assert_valid("<number>", "2.3e4");
+assert_valid("<integer>", "-109");
+assert_valid("<integer>", "19");
+
+assert_valid("<angle>", "10deg");
+assert_valid("<angle>", "20.5rad");
+assert_valid("<angle>", "calc(50grad + 3.14159rad)");
+assert_valid("<time>", "2s");
+assert_valid("<time>", "calc(2s - 9ms)");
+assert_valid("<resolution>", "10dpi");
+assert_valid("<resolution>", "3dPpX");
+assert_valid("<resolution>", "-5.3dpcm");
+assert_valid("<transform-list>", "scale(2)");
+assert_valid("<transform-list>", "translateX(2px) rotate(20deg)");
+
+assert_valid("<color>", "rgb(12, 34, 56)");
+assert_valid("<color>", "lightgoldenrodyellow");
+assert_valid("<image>", "url(a)");
+assert_valid("<image>", "linear-gradient(yellow, blue)");
+assert_valid("<url>", "url(a)");
+
+assert_valid("banana", "banana");
+assert_valid("bAnAnA", "bAnAnA");
+assert_valid("ba-na-nya", "ba-na-nya");
+assert_valid("banana", "banan\\61");
+assert_valid("<custom-ident>", "banan\\61");
+assert_valid("big | bigger | BIGGER", "bigger");
+assert_valid("foo+|bar", "foo foo foo");
+assert_valid("default", "default");
+
+assert_valid("banana\t", "banana");
+assert_valid("\nbanana\r\n", "banana");
+assert_valid("ba\f\n|\tna\r|nya", "nya");
+
+assert_valid(null, "null");
+assert_valid(undefined, "undefined");
+assert_valid(["array"], "array");
+
+// Invalid syntax
+assert_invalid("banana,nya", "banana");
+assert_invalid("banan\\61", "banana");
+assert_invalid("<\\6c ength>", "10px");
+assert_invalid("<banana>", "banana");
+assert_invalid("<Number>", "10");
+assert_invalid("<length", "10px");
+assert_invalid("<LENGTH>", "10px");
+assert_invalid("< length>", "10px");
+assert_invalid("<length >", "10px");
+assert_invalid("<length> +", "10px");
+
+assert_invalid("<length>++", "10px");
+assert_invalid("<length> | *", "10px");
+assert_invalid("*|banana", "banana");
+assert_invalid("*+", "banana");
+
+assert_invalid("initial", "initial");
+assert_invalid("inherit", "inherit");
+assert_invalid("unset", "unset");
+assert_invalid("<length>|initial", "10px");
+assert_invalid("<length>|INHERIT", "10px");
+assert_invalid("<percentage>|unsEt", "2%");
+
+// Invalid initialValue
+assert_invalid("*", "initial");
+assert_invalid("*", "inherit");
+assert_invalid("*", "unset");
+assert_invalid("*", "revert");
+assert_invalid("<custom-ident>", "initial");
+assert_invalid("<custom-ident>+", "foo inherit bar");
+
+assert_invalid("*", ")");
+assert_invalid("*", "([)]");
+assert_invalid("*", "whee!");
+assert_invalid("*", '"\n');
+assert_invalid("*", "url(moo '')");
+assert_invalid("*", "semi;colon");
+assert_invalid("*", "var(invalid var ref)");
+assert_invalid("*", "var(--foo)");
+
+assert_invalid("banana", "bAnAnA");
+assert_invalid("<length>", "var(--moo)");
+assert_invalid("<length>", "10");
+assert_invalid("<length>", "10%");
+assert_invalid("<length>", "calc(5px + 10%)");
+assert_invalid("<length>", "calc(5px * 3px / 6px)");
+assert_invalid("<length>", "10em");
+assert_invalid("<length>", "10vmin");
+assert_invalid("<length>", "calc(4px + 3em)");
+assert_invalid("<length>", "calc(4px + calc(8 * 2em))");
+assert_invalid("<length>+", "calc(2ex + 16px)");
+assert_invalid("<length>+", "10px calc(20px + 4rem)");
+assert_invalid("<percentage> | <length>+", "calc(100vh - 10px) 30px");
+assert_invalid("<length>", "10px;");
+assert_invalid("<length-percentage>", "calc(2px + 10% + 7ex)");
+assert_invalid("<percentage>", "0");
+assert_invalid("<integer>", "1.0");
+assert_invalid("<integer>", "1e0");
+assert_invalid("<number>|foo", "foo var(--foo, bla)");
+
+assert_invalid("<angle>", "0");
+assert_invalid("<angle>", "10%");
+assert_invalid("<time>", "2px");
+assert_invalid("<resolution>", "10");
+assert_invalid("<transform-list>", "scale()");
+assert_invalid("<transform-list>+", "translateX(2px) rotate(20deg)");
+assert_invalid("<color>", "fancy-looking");
+assert_invalid("<image>", "banana.png");
+assert_invalid("<url>", "banana.png");
+</script>
diff --git a/css/css-properties-values-api/register-property.html b/css/css-properties-values-api/register-property.html
new file mode 100644
index 0000000..597247c
--- /dev/null
+++ b/css/css-properties-values-api/register-property.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#register-a-custom-property" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Tests for error checking during property registration
+
+test(function() {
+    assert_throws(new TypeError(), () => CSS.registerProperty());
+    assert_throws(new TypeError(), () => CSS.registerProperty(undefined));
+    assert_throws(new TypeError(), () => CSS.registerProperty(true));
+    assert_throws(new TypeError(), () => CSS.registerProperty(2));
+    assert_throws(new TypeError(), () => CSS.registerProperty("css"));
+    assert_throws(new TypeError(), () => CSS.registerProperty(null));
+}, "registerProperty requires a Dictionary type");
+
+test(function() {
+    // Valid property names, shouldn't throw
+    CSS.registerProperty({name: '--name1'});
+    CSS.registerProperty({name: '--name2, no need for escapes'});
+    CSS.registerProperty({name: ['--name', 3]});
+
+    // Invalid property names
+    assert_throws(new TypeError(), () => CSS.registerProperty({}));
+    assert_throws(new SyntaxError(), () => CSS.registerProperty({name: 'no-leading-dash'}));
+    assert_throws(new SyntaxError(), () => CSS.registerProperty({name: ''}));
+    assert_throws(new SyntaxError(), () => CSS.registerProperty({name: '\\--name'}));
+}, "registerProperty requires a name matching <custom-property-name>");
+
+test(function() {
+    CSS.registerProperty({name: '--syntax-test-1', syntax: '*'});
+    CSS.registerProperty({name: '--syntax-test-2', syntax: ' * '});
+    assert_throws(new SyntaxError(),
+        () => CSS.registerProperty({name: '--syntax-test-3', syntax: 'length'}));
+}, "registerProperty only allows omitting initialValue is syntax is '*'");
+
+test(function() {
+    CSS.registerProperty({name: '--re-register', syntax: '<length>', initialValue: '0px'});
+    assert_throws({name: 'InvalidModificationError'},
+        () => CSS.registerProperty({name: '--re-register', syntax: '<percentage>', initialValue: '0%'}));
+}, "registerProperty fails for an already registered property");
+</script>
diff --git a/css/css-properties-values-api/registered-properties-inheritance.html b/css/css-properties-values-api/registered-properties-inheritance.html
new file mode 100644
index 0000000..17074a6
--- /dev/null
+++ b/css/css-properties-values-api/registered-properties-inheritance.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-propertydescriptor-inherits" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#register-a-custom-property" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#outer {
+    --inherited-length-1: 10px;
+    --inherited-length-2: var(--non-inherited-length-1);
+    --inherited-length-3: 30px;
+    --non-inherited-length-1: 22px;
+    --non-inherited-length-3: calc(var(--non-inherited-length-2) * 10);
+}
+
+#inner {
+    --inherited-length-3: 15px;
+    --non-inherited-length-1: 40px;
+    --non-inherited-length-2: 90px;
+}
+</style>
+<div id=outer><div id=inner></div></div>
+<script>
+CSS.registerProperty({name: '--inherited-length-1', syntax: '<length>', initialValue: '1px', inherits: true});
+CSS.registerProperty({name: '--inherited-length-2', syntax: '<length>', initialValue: '2px', inherits: true});
+CSS.registerProperty({name: '--inherited-length-3', syntax: '<length>', initialValue: '3px', inherits: true});
+CSS.registerProperty({name: '--non-inherited-length-1', syntax: '<length>', initialValue: '4px'});
+CSS.registerProperty({name: '--non-inherited-length-2', syntax: '<length>', initialValue: '5px'});
+CSS.registerProperty({name: '--non-inherited-length-3', syntax: '<length>', initialValue: '6px'});
+
+test(function() {
+    outerComputedStyle = getComputedStyle(outer);
+    assert_equals(outerComputedStyle.getPropertyValue('--inherited-length-1'), '10px');
+    assert_equals(outerComputedStyle.getPropertyValue('--inherited-length-2'), '22px');
+    assert_equals(outerComputedStyle.getPropertyValue('--inherited-length-3'), '30px');
+    assert_equals(outerComputedStyle.getPropertyValue('--non-inherited-length-1'), '22px');
+    assert_equals(outerComputedStyle.getPropertyValue('--non-inherited-length-2'), '5px');
+    assert_equals(outerComputedStyle.getPropertyValue('--non-inherited-length-3'), '50px');
+
+    innerComputedStyle = getComputedStyle(inner);
+    assert_equals(innerComputedStyle.getPropertyValue('--inherited-length-1'), '10px');
+    assert_equals(innerComputedStyle.getPropertyValue('--inherited-length-2'), '22px');
+    assert_equals(innerComputedStyle.getPropertyValue('--inherited-length-3'), '15px');
+    assert_equals(innerComputedStyle.getPropertyValue('--non-inherited-length-1'), '40px');
+    assert_equals(innerComputedStyle.getPropertyValue('--non-inherited-length-2'), '90px');
+    assert_equals(innerComputedStyle.getPropertyValue('--non-inherited-length-3'), '6px');
+}, "Registered properties are correctly inherited (or not) depending on the inherits flag.");
+</script>
diff --git a/css/css-properties-values-api/registered-property-computation.html b/css/css-properties-values-api/registered-property-computation.html
new file mode 100644
index 0000000..8f635e9
--- /dev/null
+++ b/css/css-properties-values-api/registered-property-computation.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#calculation-of-computed-values" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#divWithFontSizeSet, #parentDiv {
+    font-size: 10px;
+}
+#divWithFontSizeSet, #divWithFontSizeInherited {
+    --length-1: 12px;
+    --length-2: 13vw;
+    --length-3: 14em;
+    --length-4: 15vmin;
+    --length-5: calc(16px - 7em + 10vh);
+    --length-6: var(--length-3);
+    --length-percentage-1: 17em;
+    --length-percentage-2: 18%;
+    --length-percentage-3: calc(19em - 2%);
+    --list-1: 10px 3em;
+    --list-2: 4em 9px;
+    --list-3: 3% 10vmax 22px;
+    --list-4: calc(50% + 1em) 4px;
+}
+#fontSizeCycle {
+    --font-size: 2em;
+    font-size: var(--font-size);
+}
+</style>
+
+<div id=divWithFontSizeSet></div>
+<div id=parentDiv>
+    <div id=divWithFontSizeInherited></div>
+    <div id=fontSizeCycle></div>
+</div>
+
+<script>
+CSS.registerProperty({name: '--length-1', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-2', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-3', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-4', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-5', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-6', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-percentage-1', syntax: '<length-percentage>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-percentage-2', syntax: '<length-percentage>', initialValue: '0px'});
+CSS.registerProperty({name: '--length-percentage-3', syntax: '<length-percentage>', initialValue: '0px'});
+CSS.registerProperty({name: '--list-1', syntax: '<length>+', initialValue: '0px'});
+CSS.registerProperty({name: '--list-2', syntax: '<length>+', initialValue: '0px'});
+CSS.registerProperty({name: '--list-3', syntax: '<length-percentage>+', initialValue: '0px'});
+CSS.registerProperty({name: '--list-4', syntax: '<length-percentage>+', initialValue: '0px'});
+CSS.registerProperty({name: '--font-size', syntax: '<length>', initialValue: '0px'});
+
+for (var element of [divWithFontSizeSet, divWithFontSizeInherited]) {
+    var id = element.id;
+    var computedStyle = getComputedStyle(element);
+
+    test(function() {
+        assert_equals(computedStyle.getPropertyValue('--length-1'), '12px');
+        assert_equals(computedStyle.getPropertyValue('--length-2'), '104px');
+        assert_equals(computedStyle.getPropertyValue('--length-3'), '140px');
+        assert_equals(computedStyle.getPropertyValue('--length-4'), '90px');
+        assert_equals(computedStyle.getPropertyValue('--length-5'), '6px');
+        assert_equals(computedStyle.getPropertyValue('--length-6'), '140px');
+    }, "<length> values are computed correctly for " + id);
+
+    test(function() {
+        assert_equals(computedStyle.getPropertyValue('--length-percentage-1'), '170px');
+        assert_equals(computedStyle.getPropertyValue('--length-percentage-2'), '18%');
+        assert_equals(computedStyle.getPropertyValue('--length-percentage-3'), 'calc(190px + -2%)');
+    }, "<length-percentage> values are computed correctly for " + id);
+
+    test(function() {
+        assert_equals(computedStyle.getPropertyValue('--list-1'), '10px 30px');
+        assert_equals(computedStyle.getPropertyValue('--list-2'), '40px 9px');
+    }, "<length>+ values are computed correctly for " + id);
+
+    test(function() {
+        assert_equals(computedStyle.getPropertyValue('--list-3'), '3% 80px 22px');
+        assert_equals(computedStyle.getPropertyValue('--list-4'), 'calc(10px + 50%) 4px');
+    }, "<length-percentage>+ values are computed correctly for " + id);
+}
+
+test(function() {
+    var computedStyle = getComputedStyle(fontSizeCycle);
+    assert_equals(computedStyle.fontSize, '20px');
+    assert_equals(computedStyle.getPropertyValue('--font-size'), '40px');
+}, "font-size with a var() reference to a registered property using ems works as expected");
+</script>
diff --git a/css/css-properties-values-api/registered-property-cssom.html b/css/css-properties-values-api/registered-property-cssom.html
new file mode 100644
index 0000000..9dc9d7e
--- /dev/null
+++ b/css/css-properties-values-api/registered-property-cssom.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#inner {
+  --length: 10px;
+  --color: red;
+}
+#outer {
+  --length: 77px;
+  --color: blue;
+}
+</style>
+
+<div id=outer>
+  <div id=inner></div>
+</div>
+
+<script>
+var computedStyle = getComputedStyle(inner);
+var inlineStyle = inner.style;
+var sheetStyle = document.styleSheets[0].cssRules[0].style;
+
+test(function() {
+  // Nothing registered yet, whatever you specify works
+  assert_equals(computedStyle.getPropertyValue('--length'), ' 10px');
+  assert_equals(computedStyle.getPropertyValue('--color'), ' red');
+
+  inlineStyle.setProperty('--length', '5');
+  inlineStyle.setProperty('--color', 'hello');
+
+  assert_equals(inlineStyle.getPropertyValue('--length'), '5');
+  assert_equals(inlineStyle.getPropertyValue('--color'), 'hello');
+  assert_equals(computedStyle.getPropertyValue('--length'), '5');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'hello');
+}, "CSSOM setters function as expected for unregistered properties");
+
+CSS.registerProperty({name: '--length', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--color', syntax: '<color>', initialValue: 'white', inherits: true});
+
+test(function() {
+  assert_equals(inlineStyle.getPropertyValue('--length'), '5');
+  assert_equals(inlineStyle.getPropertyValue('--color'), 'hello');
+  assert_equals(computedStyle.getPropertyValue('--length'), '0px');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'blue');
+}, "Formerly valid values are still readable from inline styles but are computed as the unset value");
+
+test(function() {
+  inlineStyle.setProperty('--length', 'hi');
+  inlineStyle.setProperty('--color', '20');
+  assert_equals(inlineStyle.getPropertyValue('--length'), '5');
+  assert_equals(inlineStyle.getPropertyValue('--color'), 'hello');
+}, "Values not matching the registered type can't be set");
+
+test(function() {
+  inlineStyle.removeProperty('--length');
+  inlineStyle.setProperty('--color', '');
+  assert_equals(inlineStyle.getPropertyValue('--length'), '');
+  assert_equals(inlineStyle.getPropertyValue('--color'), '');
+  assert_equals(computedStyle.getPropertyValue('--length'), '10px');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'red');
+}, "Values can be removed from inline styles");
+
+test(function() {
+  sheetStyle.setProperty('--length', 'banana'); // Invalid, no change
+  assert_equals(computedStyle.getPropertyValue('--length'), '10px');
+  sheetStyle.setProperty('--length', '20px');
+  assert_equals(computedStyle.getPropertyValue('--length'), '20px');
+  sheetStyle.setProperty('--length', 'initial');
+  assert_equals(computedStyle.getPropertyValue('--length'), '0px');
+}, "Stylesheets can be modified by CSSOM");
+
+test(function() {
+  inlineStyle.setProperty('--length', '30px');
+  inlineStyle.setProperty('--color', 'pink');
+  assert_equals(inlineStyle.getPropertyValue('--length'), '30px');
+  assert_equals(inlineStyle.getPropertyValue('--color'), 'pink');
+  assert_equals(computedStyle.getPropertyValue('--length'), '30px');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'pink');
+  inlineStyle.setProperty('--color', 'inherit');
+  assert_equals(inlineStyle.getPropertyValue('--color'), 'inherit');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'blue');
+}, "Valid values can be set on inline styles");
+</script>
diff --git a/css/css-properties-values-api/registered-property-initial.html b/css/css-properties-values-api/registered-property-initial.html
new file mode 100644
index 0000000..22ea3c4
--- /dev/null
+++ b/css/css-properties-values-api/registered-property-initial.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-propertydescriptor-initialvalue" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#register-a-custom-property" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target {
+  background: var(--inherited-color);
+  color: var(--non-inherited-color);
+}
+</style>
+<div id=target></div>
+<script>
+CSS.registerProperty({name: '--length', syntax: '<length>', initialValue: 'calc(10px + 15px)'});
+CSS.registerProperty({name: '--length-percentage', syntax: '<length-percentage>', initialValue: 'calc(1in + 10% + 4px)'});
+CSS.registerProperty({name: '--inherited-color', syntax: '<color>', initialValue: 'pink', inherits: true});
+CSS.registerProperty({name: '--non-inherited-color', syntax: '<color>', initialValue: 'purple'});
+CSS.registerProperty({name: '--single-transform-list', syntax: '<transform-list>', initialValue: 'scale(calc(2 + 2))'});
+CSS.registerProperty({name: '--multiple-transform-list', syntax: '<transform-list>', initialValue: 'scale(calc(2 + 1)) translateX(calc(3px + 1px))'});
+
+test(function() {
+    computedStyle = getComputedStyle(target);
+    assert_equals(computedStyle.getPropertyValue('--length'), '25px');
+    assert_equals(computedStyle.getPropertyValue('--length-percentage'), 'calc(100px + 10%)');
+    assert_equals(computedStyle.getPropertyValue('--inherited-color'), 'pink');
+    assert_equals(computedStyle.getPropertyValue('--non-inherited-color'), 'purple');
+    assert_equals(computedStyle.getPropertyValue('--single-transform-list'), 'scale(4)');
+    assert_equals(computedStyle.getPropertyValue('--multiple-transform-list'), 'scale(3) translateX(4px)');
+
+    assert_equals(computedStyle.backgroundColor, 'rgb(255, 192, 203)');
+    assert_equals(computedStyle.color, 'rgb(128, 0, 128)');
+}, "Initial values of registered properties can be referenced when no custom properties are explicitly set.");
+</script>
diff --git a/css/css-properties-values-api/var-reference-registered-properties-cycles.html b/css/css-properties-values-api/var-reference-registered-properties-cycles.html
new file mode 100644
index 0000000..fdf8eaa
--- /dev/null
+++ b/css/css-properties-values-api/var-reference-registered-properties-cycles.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#test1 {
+    --registered-1-a: var(--registered-1-b, 10px);
+    --registered-1-b: var(--registered-1-a, 20px);
+
+    --registered-1-c: var(--registered-1-b, 30px);
+    --registered-1-d: var(--registered-1-b);
+    --unregistered-1-a:var(--registered-1-a,40px);
+    --unregistered-1-a:var(--registered-1-a);
+    left: var(--registered-1-a, 50px);
+    top: var(--registered-1-b, 60px);
+}
+</style>
+<div id=test1></div>
+<script>
+test(function() {
+    CSS.registerProperty({name: '--registered-1-a', syntax: '<length>', initialValue: '1px'});
+    CSS.registerProperty({name: '--registered-1-b', syntax: '<length>', initialValue: '2px'});
+    CSS.registerProperty({name: '--registered-1-c', syntax: '<length>', initialValue: '3px'});
+    CSS.registerProperty({name: '--registered-1-d', syntax: '<length>', initialValue: '4px'});
+
+    computedStyle = getComputedStyle(test1);
+    assert_equals(computedStyle.getPropertyValue('--registered-1-a'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-b'), '2px');
+
+    assert_equals(computedStyle.getPropertyValue('--registered-1-c'), '2px');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-d'), '2px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-1-a'), '1px');
+    assert_equals(computedStyle.left, '1px');
+    assert_equals(computedStyle.top, '2px');
+}, "A var() cycle between two registered properties is handled correctly.");
+</script>
+
+<style>
+#test2 {
+    --registered-2-a: var(--unregistered-2-a, 10px);
+    --unregistered-2-a:var(--registered-2-a,20px);
+
+    --registered-2-b: var(--registered-2-a, 30px);
+    --registered-2-c: var(--registered-2-a);
+    --registered-2-d: var(--unregistered-2-a, 40px);
+    --registered-2-e: var(--unregistered-2-a);
+    --unregistered-2-b:var(--registered-2-a,50px);
+    --unregistered-2-c:var(--registered-2-a);
+    --unregistered-2-d:var(--unregistered-2-a,60px);
+    --unregistered-2-e:var(--unregistered-2-a);
+    left: var(--registered-2-a, 70px);
+    top: var(--unregistered-2-a, 80px);
+}
+</style>
+<div id=test2></div>
+<script>
+test(function() {
+    CSS.registerProperty({name: '--registered-2-a', syntax: '<length>', initialValue: '1px'});
+    CSS.registerProperty({name: '--registered-2-b', syntax: '<length>', initialValue: '2px'});
+    CSS.registerProperty({name: '--registered-2-c', syntax: '<length>', initialValue: '3px'});
+    CSS.registerProperty({name: '--registered-2-d', syntax: '<length>', initialValue: '4px'});
+    CSS.registerProperty({name: '--registered-2-e', syntax: '<length>', initialValue: '5px'});
+
+    computedStyle = getComputedStyle(test2);
+    assert_equals(computedStyle.getPropertyValue('--registered-2-a'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-a'), '');
+
+    assert_equals(computedStyle.getPropertyValue('--registered-2-b'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--registered-2-c'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--registered-2-d'), '40px');
+    assert_equals(computedStyle.getPropertyValue('--registered-2-e'), '5px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-b'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-c'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-d'), '60px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-e'), '');
+    assert_equals(computedStyle.left, '1px');
+    assert_equals(computedStyle.top, '80px');
+}, "A var() cycle between a registered properties and an unregistered property is handled correctly.");
+</script>
+
+<style>
+#test3 {
+    --unregistered-3-a:var(--unregistered-3-b,10px);
+    --unregistered-3-b:var(--unregistered-3-a,20px);
+
+    --registered-3-a: var(--unregistered-3-a, 30px);
+    --registered-3-b: var(--unregistered-3-a);
+    --registered-3-c: var(--unregistered-3-b, 40px);
+    --registered-3-d: var(--registered-3-c, 50px);
+    left: var(--registered-3-d, 60px);
+    top: var(--registered-3-b, 70px);
+}
+</style>
+<div id=test3></div>
+<script>
+test(function() {
+    CSS.registerProperty({name: '--registered-3-a', syntax: '<length>', initialValue: '1px'});
+    CSS.registerProperty({name: '--registered-3-b', syntax: '<length>', initialValue: '2px'});
+    CSS.registerProperty({name: '--registered-3-c', syntax: '<length>', initialValue: '3px'});
+    CSS.registerProperty({name: '--registered-3-d', syntax: '<length>', initialValue: '4px'});
+
+    computedStyle = getComputedStyle(test3);
+    assert_equals(computedStyle.getPropertyValue('--unregistered-3-a'), '');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-3-b'), '');
+
+    assert_equals(computedStyle.getPropertyValue('--registered-3-a'), '30px');
+    assert_equals(computedStyle.getPropertyValue('--registered-3-b'), '2px');
+    assert_equals(computedStyle.getPropertyValue('--registered-3-c'), '40px');
+    assert_equals(computedStyle.getPropertyValue('--registered-3-d'), '40px');
+    assert_equals(computedStyle.left, '40px');
+    assert_equals(computedStyle.top, '2px');
+}, "A var() cycle between a two unregistered properties is handled correctly.");
+</script>
+
+<style>
+#test4 {
+    --registered-4-a:var(--unregistered-4-a,hello);
+    --unregistered-4-a:var(--registered-4-a,world);
+
+    --registered-4-b:var(--unregistered-4-a,meow);
+    --registered-4-c:var(--unregistered-4-a);
+    --unregistered-4-b:var(--unregistered-4-a,woof);
+    --unregistered-4-c:var(--unregistered-4-a);
+    transition-property: var(--registered-4-a, water);
+}
+</style>
+<div id=test4></div>
+<script>
+test(function() {
+    CSS.registerProperty({name: '--registered-4-a', syntax: '*'});
+    CSS.registerProperty({name: '--registered-4-b', syntax: '*', initialValue: 'moo'});
+    CSS.registerProperty({name: '--registered-4-c', syntax: '*', initialValue: 'circle'});
+
+    computedStyle = getComputedStyle(test4);
+    assert_equals(computedStyle.getPropertyValue('--registered-4-a'), '');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-4-a'), '');
+
+    assert_equals(computedStyle.getPropertyValue('--registered-4-b'), 'meow');
+    assert_equals(computedStyle.getPropertyValue('--registered-4-c'), 'circle');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-4-b'), 'woof');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-4-c'), '');
+    assert_equals(computedStyle.transitionProperty, 'water');
+}, "A var() cycle between a syntax:'*' property and an unregistered property is handled correctly.");
+</script>
diff --git a/css/css-properties-values-api/var-reference-registered-properties.html b/css/css-properties-values-api/var-reference-registered-properties.html
new file mode 100644
index 0000000..5dd1f0f
--- /dev/null
+++ b/css/css-properties-values-api/var-reference-registered-properties.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+div {
+    --registered-length-1: 10px;
+    --registered-length-2: var(--registered-length-1);
+    --registered-length-3: var(--length-1);
+    --registered-length-4: calc(var(--length-1) + 40px);
+    --registered-length-5: var(--invalid, 70px);
+    --registered-length-6: calc(var(--registered-length-3)*4);
+    --registered-length-7: var(--123px, 6px);
+
+    --length-1: 20px;
+    --length-2: var(--registered-length-1);
+    --length-3: calc(var(--123px, 6px) + var(--123px));
+
+    --percentage: 10%;
+    --registered-length-invalid: var(--percentage);
+
+    --registered-token-stream-1:var(--invalid);
+    --registered-token-stream-2:var(--invalid,fallback);
+    --token-stream-1:var(--registered-token-stream-1,moo);
+}
+</style>
+<div id=element></div>
+<script>
+CSS.registerProperty({name: '--123px', syntax: '<length>', initialValue: '123px'});
+
+CSS.registerProperty({name: '--registered-length-1', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--registered-length-2', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--registered-length-3', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--registered-length-4', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--registered-length-5', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--registered-length-6', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--registered-length-7', syntax: '<length>', initialValue: '0px'});
+CSS.registerProperty({name: '--registered-length-invalid', syntax: '<length>', initialValue: '15px'});
+
+CSS.registerProperty({name: '--registered-token-stream-1', syntax: '*'});
+CSS.registerProperty({name: '--registered-token-stream-2', syntax: '*'});
+
+test(function() {
+    computedStyle = getComputedStyle(element);
+    assert_equals(computedStyle.getPropertyValue('--registered-length-1'), '10px');
+    assert_equals(computedStyle.getPropertyValue('--registered-length-2'), '10px');
+    assert_equals(computedStyle.getPropertyValue('--registered-length-3'), '20px');
+    assert_equals(computedStyle.getPropertyValue('--registered-length-4'), '60px');
+    assert_equals(computedStyle.getPropertyValue('--registered-length-5'), '70px');
+    assert_equals(computedStyle.getPropertyValue('--registered-length-6'), '80px');
+    assert_equals(computedStyle.getPropertyValue('--registered-length-7'), '123px');
+    assert_equals(computedStyle.getPropertyValue('--length-1'), ' 20px');
+    assert_equals(computedStyle.getPropertyValue('--length-2'), '  10px');
+    assert_equals(computedStyle.getPropertyValue('--length-3'), ' calc(123px + 123px)');
+    assert_equals(computedStyle.getPropertyValue('--registered-length-invalid'), '15px');
+
+    assert_equals(computedStyle.getPropertyValue('--registered-token-stream-1'), '');
+    assert_equals(computedStyle.getPropertyValue('--registered-token-stream-2'), 'fallback');
+    assert_equals(computedStyle.getPropertyValue('--token-stream-1'), 'moo');
+}, "var() references work with registered properties");
+</script>
diff --git a/css/css-pseudo/first-letter-004-ref.html b/css/css-pseudo/first-letter-004-ref.html
new file mode 100644
index 0000000..75a0149
--- /dev/null
+++ b/css/css-pseudo/first-letter-004-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+    div {
+        font-size: 36px;
+    }
+    span {
+        color: green;
+    }
+</style>
+<body>
+    <p>Test passes if the "T" and surrounding punctuation below are green.</p>
+    <div><span>&#x104e;&#x300;T&#x300;&#x104e;&#x300;</span>est</div>
+</body>
diff --git a/css/css-pseudo/first-letter-004.html b/css/css-pseudo/first-letter-004.html
new file mode 100644
index 0000000..9079126
--- /dev/null
+++ b/css/css-pseudo/first-letter-004.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>CSS Test: ::first-letter formatting</title>
+    <link rel="author" title="Chris Nardi" href="mailto:csnardi1@gmail.com">
+    <link rel="match" href="first-letter-004-ref.html">
+    <link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-letter-pseudo">
+    <meta name="flags" content="">
+    <meta name="assert" content="Test checks that punctuation and letters with combining characters still have proper ::first-letter styling.">
+    <style>
+        div {
+            font-size: 36px;
+        }
+        div::first-letter {
+            color: green;
+        }
+    </style>
+</head>
+<body>
+    <p>Test passes if the "T" and surrounding punctuation below are green.</p>
+    <div>&#x104e;&#x300;T&#x300;&#x104e;&#x300;est</div>
+</body>
+</html>
diff --git a/css/css-pseudo/first-letter-property-whitelist.html b/css/css-pseudo/first-letter-property-whitelist.html
new file mode 100644
index 0000000..073e554
--- /dev/null
+++ b/css/css-pseudo/first-letter-property-whitelist.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<title>CSS Test: Properties allowed on ::first-letter pseudo elements</title>
+<link rel="author" title="Chris Nardi" href="mailto:cnardi@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-letter-styling">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target::first-letter {}
+#target { visibility: hidden; }
+</style>
+<div id="target">text</div>
+<script>
+'use strict';
+var style;
+const target = document.querySelector("#target");
+var defaultComputedStyle = getComputedStyle(target);
+
+test(function() {
+  var styleRule = document.styleSheets[0].cssRules[0];
+  assert_equals(styleRule.selectorText, '#target::first-letter', 'make sure we have the correct style rule');
+  style = styleRule.style;
+}, 'pre test setup');
+
+var validProperties = {
+  backgroundAttachment: 'fixed',
+  backgroundBlendMode: 'hue',
+  backgroundClip: 'padding-box',
+  backgroundColor: 'rgb(10, 20, 30)',
+  backgroundImage: 'linear-gradient(black, white)',
+  backgroundOrigin: 'border-box',
+  backgroundPosition: 'left 10px top 20px',
+  backgroundRepeat: 'no-repeat',
+  backgroundSize: '10px 20px',
+  border: '40px dotted rgb(10, 20, 30)',
+  borderImage: 'linear-gradient(black, white) 10% / 20 / 30px repeat',
+  borderRadius: '10px 20px',
+  boxShadow: 'rgb(10, 20, 30) 10px 20px 30px 40px inset',
+  color: 'rgba(10, 20, 30, 0.4)',
+  float: 'right',
+  font: 'italic small-caps 900 normal 10px / 20px sans-serif',
+  fontFeatureSettings: '"vert" 2',
+  fontSizeAdjust: '0.5',
+  fontKerning: 'none',
+  fontVariationSettings: '"XHGT" 0.7',
+  letterSpacing: '12px',
+  margin: '10px 20px 30px 40px',
+  padding: '10px 20px 30px 40px',
+  opacity: '0.5',
+  textDecoration: 'overline wavy rgb(10, 20, 30)',
+  textJustify: 'inter-word',
+  textShadow: 'rgb(10, 20, 30) 10px 20px 30px',
+  textTransform: 'capitalize',
+  textUnderlinePosition: 'under',
+  verticalAlign: '12%',
+  wordSpacing: '12px'
+};
+
+var invalidProperties = {
+  transition: 'transform 1s',
+  transform: 'rotate(45deg)',
+  wordBreak: 'break-all'
+};
+
+function testFirstLetterProperty(property, rule, expected, reason) {
+  test(function() {
+    style[property] = "";
+    style[property] = rule;
+    assert_equals(getComputedStyle(target, '::first-letter')[property], expected);
+    style[property] = "";
+  }, reason);
+}
+
+for (var property in validProperties) {
+  testFirstLetterProperty(property, validProperties[property], validProperties[property],
+                          "Whitelisted property " + property + " should be applied to first-letter pseudo elements.");
+}
+
+for (var property in invalidProperties) {
+  testFirstLetterProperty(property, invalidProperties[property], defaultComputedStyle[property],
+                          "Non-whitelisted property " + property + " should not be applied to first-letter pseudo elements.");
+}
+</script>
diff --git a/css/css-pseudo/marker-font-properties-ref.html b/css/css-pseudo/marker-font-properties-ref.html
index 093674d..a8fb980 100644
--- a/css/css-pseudo/marker-font-properties-ref.html
+++ b/css/css-pseudo/marker-font-properties-ref.html
@@ -5,6 +5,10 @@
 <title>CSS Test: ::marker formatting with font properties reference file</title>
 <link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
 <style>
+ol {
+    line-height: 30px;
+}
+
 li {
     font-family: sans-serif;
     font-size: 24px;
diff --git a/css/css-pseudo/marker-font-properties.html b/css/css-pseudo/marker-font-properties.html
index d2570eb..a108792 100644
--- a/css/css-pseudo/marker-font-properties.html
+++ b/css/css-pseudo/marker-font-properties.html
@@ -8,6 +8,10 @@
 <link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
 <meta name="assert" content="Tests ::marker rendering with font properties">
 <style>
+ol {
+    line-height: 30px;
+}
+
 li {
     list-style-type: lower-alpha;
 }
diff --git a/css/css-scoping/css-scoping-shadow-nested-slot-display-override.html b/css/css-scoping/css-scoping-shadow-nested-slot-display-override.html
new file mode 100644
index 0000000..f194e46
--- /dev/null
+++ b/css/css-scoping/css-scoping-shadow-nested-slot-display-override.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>CSS Scoping: Dynamically overriding nested slot display value</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#slots-in-shadow-tree">
+<link rel="match" href="reference/green-box.html"/>
+<style>
+  #outerHost {
+      width: 100px;
+      height: 100px;
+      background: red;
+  }
+</style>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="outerHost"> </div>
+<script>
+  let outerRoot = outerHost.attachShadow({mode:"open"});
+  outerRoot.innerHTML = '<div id="innerHost"><slot></slot></div>';
+  let innerHost = outerRoot.querySelector("#innerHost");
+  let innerRoot = innerHost.attachShadow({mode:"open"});
+  innerRoot.innerHTML = '<slot style="display:none; border:solid 50px green"></slot>';
+  document.body.offsetTop;
+  innerRoot.querySelector("slot").style.display = "block";
+</script>
diff --git a/css/css-scoping/host-descendant-001.html b/css/css-scoping/host-descendant-001.html
new file mode 100644
index 0000000..423beaf
--- /dev/null
+++ b/css/css-scoping/host-descendant-001.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"><div></div></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host ::slotted(div) { width: 100px; height: 100px; background: green; }
+      * :host ::slotted(div) { background: red; }
+    </style>
+    <slot></slot>
+  `;
+</script>
diff --git a/css/css-scoping/host-descendant-002.html b/css/css-scoping/host-descendant-002.html
new file mode 100644
index 0000000..6660d4a
--- /dev/null
+++ b/css/css-scoping/host-descendant-002.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host { width: 100px; height: 100px; background: green; }
+      * :host { background: red; }
+    </style>
+  `;
+</script>
diff --git a/css/css-scoping/host-descendant-invalidation.html b/css/css-scoping/host-descendant-invalidation.html
new file mode 100644
index 0000000..61ed4bb
--- /dev/null
+++ b/css/css-scoping/host-descendant-invalidation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>CSS Test: element style is correctly updated for rule with :host</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<div id="host"><div id="slotted"></div></div>
+<script>
+test(function() {
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host ::slotted(div) { width: 100px; height: 100px; background: red; }
+      :host ::slotted(.foo) { background: green; }
+    </style>
+    <slot></slot>
+  `;
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(255, 0, 0)");
+  host.firstElementChild.classList.add('foo');
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(0, 128, 0)");
+});
+</script>
diff --git a/css/css-scoping/host-dom-001.html b/css/css-scoping/host-dom-001.html
new file mode 100644
index 0000000..fff0112
--- /dev/null
+++ b/css/css-scoping/host-dom-001.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS Test: :host in DOM APIs</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<div id="host"></div>
+<script>
+test(function() {
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `<div></div>`;
+  assert_false(host.matches(":host"), ":host shouldn't match from CSSOM from outside the shadow tree");
+  assert_true(root.firstElementChild.matches(":host div"), ":host should match from within the shadow tree");
+  assert_equals(root.querySelector(":host div"), root.firstElementChild, ":host should match from within the shadow tree");
+})
+</script>
diff --git a/css/css-scoping/host-functional-descendant-invalidation.html b/css/css-scoping/host-functional-descendant-invalidation.html
new file mode 100644
index 0000000..b9f1329
--- /dev/null
+++ b/css/css-scoping/host-functional-descendant-invalidation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>CSS Test: element style is correctly updated for rule with :host(..)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<div id="host"><div id="slotted"></div></div>
+<script>
+test(function() {
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host ::slotted(div) { width: 100px; height: 100px; background: red; }
+      :host(.foo) ::slotted(div) { background: green; }
+    </style>
+    <slot></slot>
+  `;
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(255, 0, 0)");
+  host.classList.add('foo');
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(0, 128, 0)");
+});
+</script>
diff --git a/css/css-scoping/host-multiple-001.html b/css/css-scoping/host-multiple-001.html
new file mode 100644
index 0000000..c8833c4
--- /dev/null
+++ b/css/css-scoping/host-multiple-001.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>CSS Test: :host multiple times in the same compound selector.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host:host { width: 100px; height: 100px; background: green }
+    </style>
+  `;
+</script>
diff --git a/css/css-scoping/host-nested-001.html b/css/css-scoping/host-nested-001.html
new file mode 100644
index 0000000..f8e412c
--- /dev/null
+++ b/css/css-scoping/host-nested-001.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>CSS Test: :host doesn't match nested shadow hosts.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<style>
+  #host {
+    width: 100px;
+    height: 100px;
+  }
+</style>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">FAIL</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      div {
+        background: green;
+        width: 100px;
+        height: 100px;
+      }
+      :host { background: red !important }
+      div:host { background: red !important }
+    </style>
+    <div id="nested-host">FAIL - nested shadow host</div>
+  `;
+  root.getElementById("nested-host").attachShadow({ mode: "open" });
+</script>
diff --git a/css/css-scoping/host-slotted-001.html b/css/css-scoping/host-slotted-001.html
new file mode 100644
index 0000000..f05b7c7
--- /dev/null
+++ b/css/css-scoping/host-slotted-001.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<title>CSS Test: :host matches while collecting ::slotted rules</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"><div></div></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      ::slotted(div) { width: 100px; height: 100px; background: red; }
+      :host ::slotted(div) { background: green; }
+    </style>
+    <slot></slot>
+  `;
+</script>
diff --git a/css/css-scoping/keyframes-001.html b/css/css-scoping/keyframes-001.html
new file mode 100644
index 0000000..b0a95ac
--- /dev/null
+++ b/css/css-scoping/keyframes-001.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<title>CSS Test: @keyframes applies in the shadow tree.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<!--
+  Beware of https://github.com/w3c/csswg-drafts/issues/1995 potentially, but
+  unlikely, changing the expected result of this test.
+-->
+<style>
+#in-document {
+  width: 100px;
+  height: 100px;
+  background: blue;
+  animation: myanim 10s infinite;
+}
+</style>
+<div id="host"><div id="in-document"></div></div>
+<script>
+test(function() {
+  host.attachShadow({ mode: "open" }).innerHTML = `
+    <style>
+      @keyframes myanim {
+        from { background: red; }
+        to { background: green; }
+      }
+      #in-shadow {
+        width: 100px;
+        height: 100px;
+        background: blue;
+        animation: myanim 10s infinite;
+      }
+    </style>
+    <slot></slot>
+    <div id="in-shadow"></div>
+  `;
+
+  assert_equals(document.getElementById('in-document').getAnimations().length, 0);
+  assert_equals(host.shadowRoot.getElementById('in-shadow').getAnimations().length, 1);
+}, "@keyframes applies in the shadow tree")
+</script>
diff --git a/css/css-scoping/keyframes-002.html b/css/css-scoping/keyframes-002.html
new file mode 100644
index 0000000..b80b250
--- /dev/null
+++ b/css/css-scoping/keyframes-002.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>CSS Test: @keyframes from the document don't apply in the shadow tree.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<!--
+  Beware of https://github.com/w3c/csswg-drafts/issues/1995 potentially, but
+  unlikely, changing the expected result of this test.
+-->
+<style>
+@keyframes myanim {
+  from { background: red; }
+  to { background: red; }
+}
+</style>
+<div id="host"></div>
+<script>
+test(function() {
+  host.attachShadow({ mode: "open" }).innerHTML = `
+    <style>
+      #in-shadow {
+        width: 100px;
+        height: 100px;
+        background: blue;
+        animation: myanim 10s infinite;
+      }
+    </style>
+    <div id="in-shadow"></div>
+  `;
+
+  assert_equals(host.shadowRoot.getElementById('in-shadow').getAnimations().length, 0);
+}, "@keyframes from the document don't apply in the shadow tree");
+</script>
diff --git a/css/css-scoping/shadow-assign-dynamic-001.html b/css/css-scoping/shadow-assign-dynamic-001.html
new file mode 100644
index 0000000..b9a0d1a
--- /dev/null
+++ b/css/css-scoping/shadow-assign-dynamic-001.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Scoping Module Level 1 - Dynamic mutations to both shadow root and shadow host subtrees</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="help" href="https://bugzil.la/1303605">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<style>
+  #host {
+    width: 100px;
+    height: 100px;
+    background: red;
+  }
+
+  #host > div {
+    width: 100px;
+    height: 50px;
+    background: green;
+  }
+</style>
+<div id="host"></div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  document.body.offsetTop;
+
+  root.innerHTML = `<slot name="slot1"></slot><slot name="slot2"></slot>`;
+  host.innerHTML = `<div slot="slot1"></div><div slot="slot2"></div>`;
+</script>
diff --git a/css/css-scoping/shadow-disabled-sheet-001.html b/css/css-scoping/shadow-disabled-sheet-001.html
new file mode 100644
index 0000000..275ac47
--- /dev/null
+++ b/css/css-scoping/shadow-disabled-sheet-001.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>CSS Scoping Module Level 1 - Disabled stylesheet dynamically</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+  let root = host.attachShadow({ mode: 'open' });
+  root.innerHTML = `
+    <style>
+      div { background: green; width: 100px; height: 100px; }
+    </style>
+    <style>
+      div { background: red; }
+    </style>
+    <div></div>
+  `;
+  document.body.offsetTop;
+  root.styleSheets[1].disabled = true;
+</script>
diff --git a/css/css-scoping/shadow-fallback-dynamic-001.html b/css/css-scoping/shadow-fallback-dynamic-001.html
new file mode 100644
index 0000000..9a5672e
--- /dev/null
+++ b/css/css-scoping/shadow-fallback-dynamic-001.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>CSS Scoping Module Level 1 - Dynamic fallback content</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">
+  <span slot="myslot">FAIL</span>
+</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <slot name="myslot">
+      <div style="width: 100px; height: 100px; background: green"></div>
+    </slot>
+  `;
+  document.body.offsetTop;
+  host.firstElementChild.remove();
+</script>
diff --git a/css/css-scoping/shadow-fallback-dynamic-002.html b/css/css-scoping/shadow-fallback-dynamic-002.html
new file mode 100644
index 0000000..f65d0c7
--- /dev/null
+++ b/css/css-scoping/shadow-fallback-dynamic-002.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>CSS Scoping Module Level 1 - Dynamic fallback content</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">
+  <span slot="myslot">FAIL</span>
+</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <slot name="myslot">
+      <div style="width: 100px; height: 100px; background: green"></div>
+    </slot>
+  `;
+  document.body.offsetTop;
+  host.firstElementChild.removeAttribute("slot");
+</script>
diff --git a/css/css-scoping/shadow-fallback-dynamic-003.html b/css/css-scoping/shadow-fallback-dynamic-003.html
new file mode 100644
index 0000000..a30e8fe
--- /dev/null
+++ b/css/css-scoping/shadow-fallback-dynamic-003.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>CSS Scoping Module Level 1 - Dynamic fallback content</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">
+</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <slot name="myslot">FAIL</slot>
+  `;
+  document.body.offsetTop;
+  host.innerHTML = `
+    <div slot="myslot" style="width: 100px; height: 100px; background: green"></div>
+  `;
+</script>
diff --git a/css/css-scoping/shadow-fallback-dynamic-004.html b/css/css-scoping/shadow-fallback-dynamic-004.html
new file mode 100644
index 0000000..29e4075
--- /dev/null
+++ b/css/css-scoping/shadow-fallback-dynamic-004.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>CSS Scoping Module Level 1 - Dynamic fallback content</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">
+  <div slot="myslot" style="width: 100px; height: 100px; background: green"></div>
+</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <slot name="myslot"></slot>
+  `;
+  document.body.offsetTop;
+  let newSlot = document.createElement('slot');
+  newSlot.appendChild(document.createTextNode("FAIL"));
+  newSlot.setAttribute("name", "myslot");
+  root.insertBefore(newSlot, root.firstChild);
+</script>
diff --git a/css/css-scoping/shadow-fallback-dynamic-005.html b/css/css-scoping/shadow-fallback-dynamic-005.html
new file mode 100644
index 0000000..1d84907
--- /dev/null
+++ b/css/css-scoping/shadow-fallback-dynamic-005.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>CSS Scoping Module Level 1 - Dynamic fallback content</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host">
+  <div slot="myslot" style="width: 100px; height: 100px; background: green"></div>
+</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <slot name="myslot"></slot>
+    <slot name="myotherslot">
+      FAIL
+    </slot>
+  `;
+  document.body.offsetTop;
+  host.firstElementChild.setAttribute("slot", "myotherslot");
+</script>
diff --git a/css/css-scoping/shadow-reassign-dynamic-001.html b/css/css-scoping/shadow-reassign-dynamic-001.html
new file mode 100644
index 0000000..e8fe49a
--- /dev/null
+++ b/css/css-scoping/shadow-reassign-dynamic-001.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Scoping: Dynamic reassignment of a slot.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1435632">
+<link rel="match" href="reference/green-box.html"/>
+<div id="host">
+  <div id="green" style="background: green"></div>
+  <div id="red" style="background: red" slot="myslot"></div>
+</div>
+<script>
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>::slotted(div) { width: 100px; height: 100px }</style>
+    <p>Test passes if you see a single 100px by 100px green box below.</p>
+    <slot name="myslot">FAIL</slot>
+  `;
+  document.body.offsetTop;
+  green.setAttribute("slot", "myslot");
+  red.removeAttribute("slot");
+</script>
diff --git a/css/css-scoping/shadow-root-insert-into-document.html b/css/css-scoping/shadow-root-insert-into-document.html
new file mode 100644
index 0000000..a3b89be
--- /dev/null
+++ b/css/css-scoping/shadow-root-insert-into-document.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>CSS Scoping Module Level 1 - Dynamic insertion of shadow host</title>
+<link rel="author" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model">
+<link rel="match" href="reference/green-box.html"/>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<script>
+  let host = document.createElement('div');
+  let root = host.attachShadow({ mode: 'open' });
+  root.innerHTML = `
+    <style>
+      div { background: green; width: 100px; height: 100px; }
+    </style>
+    <div></div>
+  `;
+  document.body.offsetTop;
+  document.body.appendChild(host);
+</script>
diff --git a/css/css-scoping/slotted-invalidation.html b/css/css-scoping/slotted-invalidation.html
new file mode 100644
index 0000000..f847106
--- /dev/null
+++ b/css/css-scoping/slotted-invalidation.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<title>CSS Test: Style invalidation for ::slotted()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
+<div id="host">
+  <div>
+    <span></span>
+    <span></span>
+  </div>
+  <div id="slotted">
+    <span></span>
+    <span></span>
+  </div>
+  <div>
+    <span></span>
+    <span></span>
+  </div>
+</div>
+<script>
+test(function() {
+  var root = host.attachShadow({"mode":"open"});
+  root.innerHTML = '<style>.outer ::slotted(#slotted) { background-color: red } .outer .inner::slotted(#slotted) { background-color: green }</style><div id="outer"><slot id="inner"></slot></div>';
+
+  assert_equals(window.getComputedStyle(slotted).backgroundColor, "rgba(0, 0, 0, 0)");
+
+  root.querySelector("#outer").className = "outer";
+  assert_equals(window.getComputedStyle(slotted).backgroundColor, "rgb(255, 0, 0)");
+
+  root.querySelector("#inner").className = "inner";
+  assert_equals(window.getComputedStyle(slotted).backgroundColor, "rgb(0, 128, 0)");
+})
+</script>
diff --git a/css/css-scoping/slotted-link.html b/css/css-scoping/slotted-link.html
new file mode 100644
index 0000000..c06c961
--- /dev/null
+++ b/css/css-scoping/slotted-link.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Scoping: ::slotted :link</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="host"><a id="a" href="#notvisited">This link should be green.</a></div>
+<script>
+  let root = host.attachShadow({mode:"open"});
+  root.innerHTML = `
+  <style>
+    ::slotted(:link) { color:green }
+    ::slotted(:visited) { color:red }
+  </style>
+  <slot></slot>`;
+
+  test(() => {
+    assert_equals(getComputedStyle(a).color, "rgb(0, 128, 0)", "Unvisited link should be green.");
+  }, "Check that we match :link and not :visited for slotted anchor.");
+</script>
diff --git a/css/css-scoping/slotted-slot.html b/css/css-scoping/slotted-slot.html
new file mode 100644
index 0000000..1f4aa75
--- /dev/null
+++ b/css/css-scoping/slotted-slot.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>CSS Test: ::slotted() not matching slot elements</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="host" style="color:green">This text should be green.</div>
+<script>
+  test(() => {
+    let root = host.attachShadow({mode:"open"});
+    root.innerHTML = '<div><slot id="slot"></slot><div>';
+    let innerHost = root.firstChild;
+    let innerRoot = innerHost.attachShadow({mode:"open"});
+    innerRoot.innerHTML = "<style>::slotted(#slot){color:red}</style><slot></slot>";
+    assert_equals(getComputedStyle(root.querySelector("#slot")).color, "rgb(0, 128, 0)");
+  }, "Check that ::slotted does not match slot elements");
+</script>
diff --git a/css/css-scoping/stylesheet-title-001.html b/css/css-scoping/stylesheet-title-001.html
new file mode 100644
index 0000000..a91fca8
--- /dev/null
+++ b/css/css-scoping/stylesheet-title-001.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: title attribute in stylesheets not in the document tree is ignored</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/cssom/#preferred-css-style-sheet-set-name">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-style-title">
+<link rel="help" href="https://github.com/w3c/webcomponents/issues/535">
+<link rel="match" href="reference/green-box.html">
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<div id="host"></div>
+<script>
+  host.attachShadow({ mode: "open" }).innerHTML = `
+    <style>
+      div { width: 100px; height: 100px; }
+    </style>
+    <style title="Foo">
+      div { background: purple }
+    </style>
+    <style title="Bar">
+      div { background: green }
+    </style>
+    <div></div>
+  `;
+</script>
diff --git a/css/css-scoping/stylesheet-title-002.html b/css/css-scoping/stylesheet-title-002.html
new file mode 100644
index 0000000..2843ada
--- /dev/null
+++ b/css/css-scoping/stylesheet-title-002.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: title attribute in stylesheets not in the document tree is ignored</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/cssom/#preferred-css-style-sheet-set-name">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-style-title">
+<link rel="help" href="https://github.com/w3c/webcomponents/issues/535">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="host"></div>
+<script>
+test(function() {
+  host.attachShadow({ mode: "open" }).innerHTML = `
+    <style>
+      div { width: 100px; height: 100px; }
+    </style>
+    <style title="Foo">
+      div { background: purple }
+    </style>
+    <style title="Bar">
+      div { background: green }
+    </style>
+    <div></div>
+  `;
+  assert_equals(host.shadowRoot.styleSheets.length, 3);
+  for (let sheet of host.shadowRoot.styleSheets) {
+    assert_equals(sheet.title, null, "Sheet outside of the document generates no setter");
+    sheet.title = "Foo";
+    assert_equals(sheet.title, null, "Mutation doesn't change the sheet title");
+  }
+  for (let element of host.shadowRoot.querySelectorAll("style")) {
+    element.setAttribute("title", "Foo");
+    assert_equals(element.sheet.title, null, "Attribute mutation doesn't change the sheet title");
+  }
+}, "Title attribute in stylesheets not in the document tree is ignored");
+</script>
diff --git a/css/css-scroll-anchoring/OWNERS b/css/css-scroll-anchoring/OWNERS
new file mode 100644
index 0000000..4d8fac4
--- /dev/null
+++ b/css/css-scroll-anchoring/OWNERS
@@ -0,0 +1 @@
+@tabatkins
diff --git a/css/css-scroll-snap/OWNERS b/css/css-scroll-snap/OWNERS
new file mode 100644
index 0000000..4d8fac4
--- /dev/null
+++ b/css/css-scroll-snap/OWNERS
@@ -0,0 +1 @@
+@tabatkins
diff --git a/css/css-scroll-snap/scroll-snap-type-proximity.html b/css/css-scroll-snap/scroll-snap-type-proximity.html
new file mode 100644
index 0000000..cfe990c
--- /dev/null
+++ b/css/css-scroll-snap/scroll-snap-type-proximity.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+div {
+  position: absolute;
+  margin: 0px;
+}
+#scroller {
+  height: 400px;
+  width: 400px;
+  overflow: scroll;
+}
+#space {
+  width: 2000px;
+  height: 2000px;
+}
+
+.snap {
+  width: 200px;
+  height: 200px;
+  background-color: blue;
+  scroll-snap-align: start;
+}
+#left-top {
+  left: 0px;
+  top: 0px;
+}
+#right-bottom {
+  left: 1000px;
+  top: 1000px;
+}
+</style>
+
+<div id="scroller">
+  <div id="space"></div>
+  <div class="snap" id="left-top"></div>
+  <div class="snap" id="right-bottom"></div>
+</div>
+
+<script>
+var scroller = document.getElementById("scroller");
+var visible_x = 1000 - scroller.clientWidth;
+var visible_y = 1000 - scroller.clientHeight;
+
+test(() => {
+  scroller.style.scrollSnapType = "both mandatory";
+  scroller.scrollTo(0, 0);
+  assert_equals(scroller.scrollLeft, 0);
+  assert_equals(scroller.scrollTop, 0);
+
+  scroller.scrollTo(visible_x + 10, visible_y + 10);
+  assert_equals(scroller.scrollLeft, 1000);
+  assert_equals(scroller.scrollTop, 1000);
+}, "mandatory scroll-snap-type should snap as long as the element is visible.");
+
+test(() => {
+  scroller.style.scrollSnapType = "both proximity";
+  scroller.scrollTo(0, 0);
+  assert_equals(scroller.scrollLeft, 0);
+  assert_equals(scroller.scrollTop, 0);
+
+  scroller.scrollTo(visible_x + 10, visible_y + 10);
+  assert_equals(scroller.scrollLeft, visible_x + 10);
+  assert_equals(scroller.scrollTop, visible_y + 10);
+}, "proximity scroll-snap-type shouldn't snap if the snap position is too far away.");
+
+test(() => {
+  scroller.style.scrollSnapType = "both proximity";
+  scroller.scrollTo(0, 0);
+  assert_equals(scroller.scrollLeft, 0);
+  assert_equals(scroller.scrollTop, 0);
+
+  scroller.scrollTo(995, 995);
+  assert_equals(scroller.scrollLeft, 1000);
+  assert_equals(scroller.scrollTop, 1000);
+}, "proximity scroll-snap-type should snap if the snap position is close.");
+</script>
diff --git a/css/css-scroll-snap/scrollTo-scrollBy-snaps.html b/css/css-scroll-snap/scrollTo-scrollBy-snaps.html
new file mode 100644
index 0000000..766cf57
--- /dev/null
+++ b/css/css-scroll-snap/scrollTo-scrollBy-snaps.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+body {
+  margin: 0px;
+  overflow: scroll;
+  scroll-snap-type: both mandatory;
+}
+.scroller {
+  overflow: scroll;
+  scroll-snap-type: both mandatory;
+}
+#div-scroller {
+  width: 800px;
+  height: 800px;
+}
+.content {
+  width: 2100px;
+  height: 2100px;
+}
+.target {
+  width: 1000px;
+  height: 1000px;
+  scroll-snap-align: start;
+  float: left;
+}
+</style>
+
+<body class="scroller">
+  <div class="content" id="content">
+    <div class="target"></div>
+    <div class="target"></div>
+    <div class="target"></div>
+    <div class="target"></div>
+  </div>
+
+  <div class="scroller" id="div-scroller">
+    <div class="content">
+      <div class="target"></div>
+      <div class="target"></div>
+      <div class="target"></div>
+      <div class="target"></div>
+    </div>
+  </div>
+</body>
+
+<script>
+function format_dict(dict) {
+  const props = [];
+  for (let prop in dict) {
+    props.push(`${prop}: ${format_value(dict[prop])}`);
+  }
+  return `{${props.join(', ')}}`;
+}
+
+var divScroller = document.getElementById("div-scroller");
+var viewport = document.scrollingElement;
+[
+  [{left: 800}, 1000, 0],
+  [{top: 1200}, 0, 1000],
+  [{left: 1200, top: 800}, 1000, 1000]
+].forEach(([input, expectedX, expectedY]) => {
+  test(() => {
+    divScroller.scrollTo(0, 0);
+    assert_equals(divScroller.scrollLeft, 0);
+    assert_equals(divScroller.scrollTop, 0);
+    if (input.left)
+      divScroller.scrollLeft = input.left;
+    if (input.top)
+      divScroller.scrollTop = input.top;
+    assert_equals(divScroller.scrollLeft, expectedX);
+    assert_equals(divScroller.scrollTop, expectedY);
+  }, `assign scrollLeft and scrollTop for ${format_dict(input)} on div lands on (${expectedX}, ${expectedY})`);
+
+  test(() => {
+    viewport.scrollTo(0, 0);
+    assert_equals(viewport.scrollLeft, 0);
+    assert_equals(viewport.scrollTop, 0);
+    if (input.left)
+      viewport.scrollLeft = input.left;
+    if (input.top)
+      viewport.scrollTop = input.top;
+    assert_equals(viewport.scrollLeft, expectedX);
+    assert_equals(viewport.scrollTop, expectedY);
+  }, `assign scrollLeft and scrollTop for ${format_dict(input)} on viewport-defining element lands on (${expectedX}, ${expectedY})`);
+
+  test(() => {
+    divScroller.scrollTo(0, 0);
+    assert_equals(divScroller.scrollLeft, 0);
+    assert_equals(divScroller.scrollTop, 0);
+    divScroller.scrollTo(input);
+    assert_equals(divScroller.scrollLeft, expectedX);
+    assert_equals(divScroller.scrollTop, expectedY);
+  }, `scrollTo(${format_dict(input)}) on div lands on (${expectedX}, ${expectedY})`);
+
+  test(() => {
+    divScroller.scrollTo(0, 0);
+    assert_equals(divScroller.scrollLeft, 0);
+    assert_equals(divScroller.scrollTop, 0);
+    divScroller.scrollBy(input);
+    assert_equals(divScroller.scrollLeft, expectedX);
+    assert_equals(divScroller.scrollTop, expectedY);
+  }, `scrollBy(${format_dict(input)}) on div lands on (${expectedX}, ${expectedY})`);
+
+  test(() => {
+    viewport.scrollTo(0, 0);
+    assert_equals(viewport.scrollLeft, 0);
+    assert_equals(viewport.scrollTop, 0);
+    viewport.scrollTo(input);
+    assert_equals(viewport.scrollLeft, expectedX);
+    assert_equals(viewport.scrollTop, expectedY);
+  }, `scrollTo(${format_dict(input)}) on viewport-defining element lands on (${expectedX}, ${expectedY})`);
+
+  test(() => {
+    viewport.scrollTo(0, 0);
+    assert_equals(viewport.scrollLeft, 0);
+    assert_equals(viewport.scrollTop, 0);
+    viewport.scrollBy(input);
+    assert_equals(viewport.scrollLeft, expectedX);
+    assert_equals(viewport.scrollTop, expectedY);
+  }, `scrollBy(${format_dict(input)}) on viewport-defining element lands on (${expectedX}, ${expectedY})`);
+
+  test(() => {
+    window.scrollTo(0, 0);
+    assert_equals(window.scrollX, 0);
+    assert_equals(window.scrollY, 0);
+    window.scrollTo(input);
+    assert_equals(window.scrollX, expectedX);
+    assert_equals(window.scrollY, expectedY);
+  }, `scrollTo(${format_dict(input)}) on window lands on (${expectedX}, ${expectedY})`);
+
+  test(() => {
+    window.scrollTo(0, 0);
+    assert_equals(window.scrollX, 0);
+    assert_equals(window.scrollY, 0);
+    window.scrollTo(input);
+    assert_equals(window.scrollX, expectedX);
+    assert_equals(window.scrollY, expectedY);
+  }, `scrollBy(${format_dict(input)}) on window lands on (${expectedX}, ${expectedY})`);
+});
+
+document.body.removeChild(document.getElementById("content"));
+</script>
\ No newline at end of file
diff --git a/css/css-scroll-snap/snap-at-user-scroll-end-manual.html b/css/css-scroll-snap/snap-at-user-scroll-end-manual.html
new file mode 100644
index 0000000..d1e4be5
--- /dev/null
+++ b/css/css-scroll-snap/snap-at-user-scroll-end-manual.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1" />
+<title>Tests that window should snap at user scroll end.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+body {
+  margin: 0px;
+  overflow: scroll;
+  scroll-snap-type: both mandatory;
+}
+#content {
+  width: 2000px;
+  height: 2000px;
+  padding: 0px;
+  margin: 0px;
+}
+#target {
+  position: relative;
+  left: 400px;
+  top: 400px;
+  width: 400px;
+  height: 400px;
+  background-color: lightblue;
+  scroll-snap-align: start;
+}
+#i1 {
+  color: red;
+  font-weight: bold;
+}
+</style>
+
+<div id="content">
+  <div id="target">
+    <h1>CSSScrollSnap</h1>
+    <h4>Tests that the window can snap at user scroll end.</h4>
+    <ol>
+      <li id="i1" style="color: red">
+        Scroll the page vertically and horizontally.
+        Keep scrolling until background has turned yellow.</li>
+      <li id="i2"> Press the button "Done"</li>
+    </ol>
+    <input type="button" id="btn" value="DONE" style="width: 100px; height: 50px;"/>
+  </div>
+</div>
+
+<script>
+var body = document.body;
+var button = document.getElementById("btn");
+var target = document.getElementById("target");
+var instruction1 = document.getElementById("i1");
+var instruction2 = document.getElementById("i2");
+var scrolled_x = false;
+var scrolled_y = false;
+var start_x = window.scrollX;
+var start_y = window.scrollY;
+
+window.onscroll = function() {
+  if (scrolled_x && scrolled_y) {
+    body.style.backgroundColor = "yellow";
+    instruction1.style.color = "black";
+    instruction1.style.fontWeight = "normal";
+    instruction2.style.color = "red";
+    instruction2.style.fontWeight = "bold";
+    return;
+  }
+
+  scrolled_x |= window.scrollX != start_x;
+  scrolled_y |= window.scrollY != start_y;
+}
+
+button.onclick = function() {
+  if (!scrolled_x || !scrolled_y)
+    return;
+
+  assert_equals(window.scrollX, target.offsetLeft,
+        "window.scrollX should be at snapped position.");
+  assert_equals(window.scrollY, target.offsetTop,
+        "window.scrollY should be at snapped position.");
+  // To make the test result visible.
+  var content = document.getElementById("content");
+  body.removeChild(content);
+  done();
+}
+
+</script>
\ No newline at end of file
diff --git a/css/css-scroll-snap/snap-inline-block.html b/css/css-scroll-snap/snap-inline-block.html
new file mode 100644
index 0000000..d41dcc8
--- /dev/null
+++ b/css/css-scroll-snap/snap-inline-block.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+div {
+  position: absolute;
+  margin: 0px;
+}
+#scroller {
+  width: 400px;
+  height: 350px;
+  overflow: scroll;
+  scroll-snap-type: both mandatory;
+}
+#space {
+  width: 1000px;
+  height: 1000px;
+}
+#target {
+  width: 200px;
+  height: 200px;
+  left: 300px;
+  top: 300px;
+  scroll-snap-align: start end;
+}
+</style>
+
+<div id="scroller">
+  <div id="space"></div>
+  <div id="target"></div>
+</div>
+
+<script>
+var scroller = document.getElementById("scroller");
+var width = scroller.clientWidth;
+var height = scroller.clientHeight;
+[
+  ["horizontal-tb", 300, 500 - height],
+  ["vertical-lr", 500 - width, 300],
+  ["vertical-rl", 300, 300]
+].forEach(([writing_mode, left, top]) => {
+  test(() => {
+    scroller.style.writingMode = writing_mode;
+    if (writing_mode == "vertical-rl")
+      document.getElementById("target").style.left = (width - 700) + "px";
+    scroller.scrollTo(0, 0);
+    assert_equals(scroller.scrollLeft, left, "aligns correctly on x");
+    assert_equals(scroller.scrollTop, top, "aligns correctly on y");
+  }, "Snaps correctly for " + writing_mode +
+     " writing mode with 'inline' and 'block' alignments");
+})
+</script>
\ No newline at end of file
diff --git a/css/css-scroll-snap/snap-to-visible-areas.html b/css/css-scroll-snap/snap-to-visible-areas.html
new file mode 100644
index 0000000..8227434
--- /dev/null
+++ b/css/css-scroll-snap/snap-to-visible-areas.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+div {
+  position: absolute;
+  margin: 0px;
+}
+#scroller {
+  height: 600px;
+  width: 600px;
+  overflow: scroll;
+  scroll-snap-type: both mandatory;
+}
+#space {
+  width: 2000px;
+  height: 2000px;
+}
+
+.snap {
+  width: 200px;
+  height: 200px;
+  background-color: blue;
+  scroll-snap-align: start;
+}
+
+#left-top {
+  left: 0px;
+  top: 0px;
+}
+
+#right-top {
+  left: 800px;
+  top: 0px;
+}
+
+#left-bottom {
+  left: 0px;
+  top: 800px;
+}
+
+</style>
+<div id="scroller">
+  <div id="space"></div>
+  <div id="left-top" class="snap"></div>
+  <div id="right-top" class="snap"></div>
+  <div id="left-bottom" class="snap"></div>
+</div>
+<script>
+var scroller = document.getElementById("scroller");
+test(() => {
+  scroller.scrollTo(0, 0);
+  assert_equals(scroller.scrollLeft, 0);
+  assert_equals(scroller.scrollTop, 0);
+  scroller.scrollTo(300, 0);
+  assert_equals(scroller.scrollLeft, 800);
+  assert_equals(scroller.scrollTop, 0);
+}, 'Only snap to visible area on X axis, even when the non-visible ones are closer');
+
+test(() => {
+  scroller.scrollTo(0, 0);
+  assert_equals(scroller.scrollLeft, 0);
+  assert_equals(scroller.scrollTop, 0);
+  scroller.scrollTo(0, 300);
+  assert_equals(scroller.scrollLeft, 0);
+  assert_equals(scroller.scrollTop, 800);
+}, 'Only snap to visible area on Y axis, even when the non-visible ones are closer');
+</script>
diff --git a/css/css-shadow-parts/OWNERS b/css/css-shadow-parts/OWNERS
new file mode 100644
index 0000000..4d8fac4
--- /dev/null
+++ b/css/css-shadow-parts/OWNERS
@@ -0,0 +1 @@
+@tabatkins
diff --git a/css/css-shadow-parts/all-hosts.html b/css/css-shadow-parts/all-hosts.html
new file mode 100644
index 0000000..e6646c0
--- /dev/null
+++ b/css/css-shadow-parts/all-hosts.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - All Hosts</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>::part(partp) { color: green; }</style>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>span { color: red; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <custom-element id="c-e-1"></custom-element>
+    The following text should be green:
+    <custom-element id="c-e-2"></custom-element>
+    <script type="text/javascript">
+      "use strict";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e-1", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "::part with host selector styles in first host");
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e-2", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "::part with host selector styles in second host");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/chaining-invalid-selector.html b/css/css-shadow-parts/chaining-invalid-selector.html
new file mode 100644
index 0000000..1130932
--- /dev/null
+++ b/css/css-shadow-parts/chaining-invalid-selector.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Chaining Invalid Selector</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>#c-e-outer::part(c-e-part)::part(partp) { color: red; }</style>
+    <div>
+      It's invalid to use 2 pseudoelements in a selector but if somehow it becomes valid,
+      it should not expose the internal structure of the shadow tree.
+    </div>
+    <script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
+    <template id="custom-element-inner-template">
+      <style>span { color: green; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    <script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
+    <template id="custom-element-outer-template">
+      The following text should be green:
+      <custom-element-inner id="c-e-inner" part="c-e-part"></custom-element-inner>
+    </template>
+    <custom-element-outer id="c-e-outer"></custom-element-outer>
+    <script type="text/javascript">
+      "use strict";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e-outer", "c-e-inner", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "::part cannot be chained to reach elements in the inner host");
+      test(function() {
+        assert_equals(document.getElementsByTagName("body")[0].style.length, 0);
+      }, "Chained ::part selectors are dropped");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/complex-matching.html b/css/css-shadow-parts/complex-matching.html
new file mode 100644
index 0000000..575edab
--- /dev/null
+++ b/css/css-shadow-parts/complex-matching.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Complex Matching</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>div #c-e::part(partp) { color: green; }</style>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>span { color: red; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <div><custom-element id="c-e"></custom-element></div>
+    <script type="text/javascript">
+      "use strict";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "Complex selector for host works");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/complex-non-matching.html b/css/css-shadow-parts/complex-non-matching.html
new file mode 100644
index 0000000..036713f
--- /dev/null
+++ b/css/css-shadow-parts/complex-non-matching.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Complex Non-matching</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>div #c-e::part(partp) { color: red; }</style>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>span { color: green; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <pre><custom-element id="c-e"></custom-element></pre>
+    <script type="text/javascript">
+      "use strict";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "Non-matching complex selector for host does not style");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/different-host.html b/css/css-shadow-parts/different-host.html
new file mode 100644
index 0000000..c8b5f86
--- /dev/null
+++ b/css/css-shadow-parts/different-host.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Different Host</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>#c-e-other::part(partp) { color: red; }</style>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>span { color: green; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <custom-element id="c-e"></custom-element>
+    <script type="text/javascript">
+      "use strict";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "Part is not styled when host is not selected");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/host-stylesheet.html b/css/css-shadow-parts/host-stylesheet.html
new file mode 100644
index 0000000..ee8f4e7
--- /dev/null
+++ b/css/css-shadow-parts/host-stylesheet.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Host Stylesheet</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>
+        ::part(partp) { color: red; }
+        span { color: green; }
+      </style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <custom-element id="c-e"></custom-element>
+    <script type="text/javascript">
+      "use strict";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "Part in selected host is not styled by ::part in a stylesheet inside the host");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/inner-host.html b/css/css-shadow-parts/inner-host.html
new file mode 100644
index 0000000..8c8cec7
--- /dev/null
+++ b/css/css-shadow-parts/inner-host.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Inner Host</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>#c-e-outer::part(partp) { color: green; }</style>
+    <script>installCustomElement("custom-element-inner", "custom-element-inner-template");</script>
+    <template id="custom-element-inner-template">
+      <style>span { color: blue; }</style>
+      <span id="blue_part" part="partp">This text</span>
+    </template>
+    <script>installCustomElement("custom-element-outer", "custom-element-outer-template");</script>
+    <template id="custom-element-outer-template">
+      <style>span { color: red; }</style>
+      <span id="green_part" part="partp">This text</span>
+      The following text should be blue:
+      <custom-element-inner id="c-e-inner"></custom-element-inner>
+    </template>
+    The following text should be green:
+    <custom-element-outer id="c-e-outer"></custom-element-outer>
+    <script type="text/javascript">
+      "use strict";
+      const colorBlue = "rgb(0, 0, 255)";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e-outer", "green_part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "Part in outer host is styled by document style sheet");
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e-outer", "c-e-inner", "blue_part"]);
+        assert_equals(window.getComputedStyle(el).color, colorBlue);
+      }, "Part in inner host is not styled by document style sheet");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/simple.html b/css/css-shadow-parts/simple.html
new file mode 100644
index 0000000..3733669
--- /dev/null
+++ b/css/css-shadow-parts/simple.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Shadow Parts - Simple</title>
+    <meta href="mailto:fergal@chromium.org" rel="author" title="Fergal Daly">
+    <link href="http://www.google.com/" rel="author" title="Google">
+    <link href="https://drafts.csswg.org/css-shadow-parts/" rel="help">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="support/shadow-helper.js"></script>
+  </head>
+  <body>
+    <style>#c-e::part(partp) { color: green; }</style>
+    <script>installCustomElement("custom-element", "custom-element-template");</script>
+    <template id="custom-element-template">
+      <style>span { color: red; }</style>
+      <span id="part" part="partp">This text</span>
+    </template>
+    The following text should be green:
+    <custom-element id="c-e"></custom-element>
+    <script type="text/javascript">
+      "use strict";
+      const colorGreen = "rgb(0, 128, 0)";
+      test(function() {
+        var el = getElementByShadowIds(document, ["c-e", "part"]);
+        assert_equals(window.getComputedStyle(el).color, colorGreen);
+      }, "Part in selected host is styled");
+    </script>
+  </body>
+</html>
diff --git a/css/css-shadow-parts/support/shadow-helper.js b/css/css-shadow-parts/support/shadow-helper.js
new file mode 100644
index 0000000..94accd6
--- /dev/null
+++ b/css/css-shadow-parts/support/shadow-helper.js
@@ -0,0 +1,33 @@
+// Takes a root element and a list of ids of shadow host elements. Each id refers to a shadow host
+// inside the previous id's shadow tree.
+function getElementByShadowIds(root, ids) {
+  for (var i = 0; ;i++) {
+    var host = root.getElementById(ids[i]);
+    if (host == null) {
+      throw "No element found: i=" + i + " id=" + ids[i] + ". Root was " + root;
+    }
+    if (i == ids.length - 1) {
+      return host;
+    }
+    root = host.shadowRoot;
+    if (root == null) {
+      throw "No shadowRoot found: i=" + i + " id=" + ids[i] + ". Host was " + host;
+    }
+  }
+}
+
+// Installs a mininal custom element based on this template.
+function installCustomElement(element_name, template_id) {
+  ceClass = class extends HTMLElement {
+    constructor() {
+      super();
+      var template = document
+        .getElementById(template_id)
+        .content;
+      this
+        .attachShadow({mode: 'open'})
+        .appendChild(template.cloneNode(true));
+    }
+  };
+  window.customElements.define(element_name, ceClass);
+}
diff --git a/css/css-shapes/basic-shape-circle-ellipse-serialization.html b/css/css-shapes/basic-shape-circle-ellipse-serialization.html
index 9c82039..5e4842d 100644
--- a/css/css-shapes/basic-shape-circle-ellipse-serialization.html
+++ b/css/css-shapes/basic-shape-circle-ellipse-serialization.html
@@ -35,8 +35,8 @@
             "ellipse(closest-side farthest-side at 50% 50%)");
 
 
-checkEquals("circle(at right 5px top)", "circle(at right 5px top 0%)");
-checkEquals("ellipse(at right 10px top)", "ellipse(at right 10px top 0%)");
+checkEquals("circle(at top 0% right 5px)", "circle(at right 5px top 0%)");
+checkEquals("ellipse(at top 0% right 10px)", "ellipse(at right 10px top 0%)");
 // Remove defaults like closest-side
 checkEquals("circle(closest-side at center)",
             "circle(at 50% 50%)");
diff --git a/css/css-shapes/parsing/resources/parsing-testcommon.js b/css/css-shapes/parsing/resources/parsing-testcommon.js
new file mode 100644
index 0000000..b075882
--- /dev/null
+++ b/css/css-shapes/parsing/resources/parsing-testcommon.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
+function test_valid_value(property, value, serializedValue) {
+    if (arguments.length < 3)
+        serializedValue = value;
+
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
+
+        var div = document.createElement('div');
+        div.style[property] = value;
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
+        div.style[property] = readValue;
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+}
+
+function test_invalid_value(property, value) {
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_equals(div.style.getPropertyValue(property), "");
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
+}
diff --git a/css/css-shapes/parsing/shape-image-threshold-invalid.html b/css/css-shapes/parsing/shape-image-threshold-invalid.html
new file mode 100644
index 0000000..e3871d8
--- /dev/null
+++ b/css/css-shapes/parsing/shape-image-threshold-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-image-threshold with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-image-threshold-property">
+<meta name="assert" content="shape-image-threshold supports only the grammar '<number>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("shape-image-threshold", "auto");
+test_invalid_value("shape-image-threshold", "10px");
+test_invalid_value("shape-image-threshold", "100%");
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/parsing/shape-image-threshold-valid.html b/css/css-shapes/parsing/shape-image-threshold-valid.html
new file mode 100644
index 0000000..5b77db8
--- /dev/null
+++ b/css/css-shapes/parsing/shape-image-threshold-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-image-threshold with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-image-threshold-property">
+<meta name="assert" content="shape-image-threshold supports the full grammar '<number>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("shape-image-threshold", "12.5");
+test_valid_value("shape-image-threshold", "-7");
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/parsing/shape-margin-invalid.html b/css/css-shapes/parsing/shape-margin-invalid.html
new file mode 100644
index 0000000..4f3b4f2
--- /dev/null
+++ b/css/css-shapes/parsing/shape-margin-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-margin with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-margin-property">
+<meta name="assert" content="shape-margin supports only the grammar '<length> | <percentage>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("shape-margin", "none");
+test_invalid_value("shape-margin", "10");
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/parsing/shape-margin-valid.html b/css/css-shapes/parsing/shape-margin-valid.html
new file mode 100644
index 0000000..68634b6
--- /dev/null
+++ b/css/css-shapes/parsing/shape-margin-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-margin with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-margin-property">
+<meta name="assert" content="shape-margin supports the full grammar '<length> | <percentage>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("shape-margin", "0", "0px");
+test_valid_value("shape-margin", "10px");
+test_valid_value("shape-margin", "20em");
+test_valid_value("shape-margin", "37.5%");
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/parsing/shape-outside-invalid-position.html b/css/css-shapes/parsing/shape-outside-invalid-position.html
new file mode 100644
index 0000000..7d2b64b
--- /dev/null
+++ b/css/css-shapes/parsing/shape-outside-invalid-position.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-outside with invalid position values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#typedef-position">
+<meta name="assert" content="shape-outside positions support only the '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// The following were supported in an earlier version of the spec.
+// https://github.com/w3c/csswg-drafts/issues/2140
+test_invalid_value("shape-outside", "circle(at center left 1px)");
+test_invalid_value("shape-outside", "circle(at center top 2px)");
+test_invalid_value("shape-outside", "circle(at right 3% center)");
+test_invalid_value("shape-outside", "circle(at left 4px top)");
+test_invalid_value("shape-outside", "circle(at right 5px top)");
+test_invalid_value("shape-outside", "ellipse(at right top 5px)");
+test_invalid_value("shape-outside", "ellipse(at bottom 6% center)");
+test_invalid_value("shape-outside", "ellipse(at bottom 7% left)");
+test_invalid_value("shape-outside", "ellipse(at bottom right 8%)");
+test_invalid_value("shape-outside", "ellipse(at right 10px top)");
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/parsing/shape-outside-invalid.html b/css/css-shapes/parsing/shape-outside-invalid.html
new file mode 100644
index 0000000..9f541cf
--- /dev/null
+++ b/css/css-shapes/parsing/shape-outside-invalid.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-outside with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-outside-property">
+<meta name="assert" content="shape-outside supports only the grammar 'none | [ <basic-shape> || <shape-box> ] | <image>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("shape-outside", "auto");
+test_invalid_value("shape-outside", "ray(0deg)");
+
+test_invalid_value("shape-outside", "inset()");
+test_invalid_value("shape-outside", "inset(123)");
+test_invalid_value("shape-outside", "inset(1% 2% 3% 4% 5%)");
+test_invalid_value("shape-outside", "inset(round 0)");
+test_invalid_value("shape-outside", "inset(0px round)");
+test_invalid_value("shape-outside", "inset(0px round 123)");
+test_invalid_value("shape-outside", "inset(0px round 1% 2% 3% 4% 5%)");
+test_invalid_value("shape-outside", "inset(0px round / 1px)");
+test_invalid_value("shape-outside", "inset(10px round -20px)");
+test_invalid_value("shape-outside", "inset(30% round -40%)");
+
+test_invalid_value("shape-outside", "circle(123)");
+test_invalid_value("shape-outside", "circle(at)");
+test_invalid_value("shape-outside", "circle(10% 20%)");
+test_invalid_value("shape-outside", "circle(-10px at 20px 30px)");
+test_invalid_value("shape-outside", "circle(-10% at 20% 30%)");
+test_invalid_value("shape-outside", "circle(1% 2% at 0% 100%)");
+
+test_invalid_value("shape-outside", "ellipse(farthest-side at)");
+test_invalid_value("shape-outside", "ellipse(1% 2% top right)");
+test_invalid_value("shape-outside", "ellipse(3%)");
+test_invalid_value("shape-outside", "ellipse(3% at 100% 0%)");
+test_invalid_value("shape-outside", "ellipse(10% -20% at 30% 40%)");
+test_invalid_value("shape-outside", "ellipse(-50px 60px at 70% 80%)");
+
+test_invalid_value("shape-outside", "polygon(1%)");
+
+// <geometry-box> other than <shape-box>
+test_invalid_value("shape-outside", "fill-box");
+test_invalid_value("shape-outside", "stroke-box");
+test_invalid_value("shape-outside", "view-box");
+
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/parsing/shape-outside-valid-position.html b/css/css-shapes/parsing/shape-outside-valid-position.html
new file mode 100644
index 0000000..ca01b35
--- /dev/null
+++ b/css/css-shapes/parsing/shape-outside-valid-position.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-outside with valid position values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#typedef-position">
+<meta name="assert" content="shape-outside positions support the full '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// First serialization is being returned by Firefox/Edge, second by Blink/WebKit.
+test_valid_value("shape-outside", "circle(at 10%)", ["circle(at 10%)", "circle(at 10% 50%)"]);
+test_valid_value("shape-outside", "circle(at 20% 30px)");
+test_valid_value("shape-outside", "circle(at 30px center)", ["circle(at 30px center)", "circle(at 30px 50%)"]);
+test_valid_value("shape-outside", "circle(at 40px top)", ["circle(at 40px top)", "circle(at 40px 0%)"]);
+test_valid_value("shape-outside", "circle(at bottom 10% right 20%)", ["circle(at bottom 10% right 20%)", "circle(at 80% 90%)"]);
+test_valid_value("shape-outside", "circle(at bottom right)", ["circle(at bottom right)", "circle(at 100% 100%)"]);
+test_valid_value("shape-outside", "circle(at center)", ["circle(at center)", "circle(at 50% 50%)"]);
+test_valid_value("shape-outside", "circle(at center 50px)", ["circle(at center 50px)", "circle(at 50% 50px)"]);
+test_valid_value("shape-outside", "circle(at center bottom)", ["circle(at center bottom)", "circle(at 50% 100%)"]);
+test_valid_value("shape-outside", "circle(at center center)", ["circle(at center center)", "circle(at 50% 50%)"]);
+test_valid_value("shape-outside", "circle(at center left)", ["circle(at center left)", "circle(at 0% 50%)"]);
+test_valid_value("shape-outside", "circle(at left)", ["circle(at left)", "circle(at 0% 50%)"]);
+test_valid_value("shape-outside", "circle(at left bottom)", ["circle(at left bottom)", "circle(at 0% 100%)"]);
+test_valid_value("shape-outside", "circle(at left center)", ["circle(at left center)", "circle(at 0% 50%)"]);
+test_valid_value("shape-outside", "circle(at right 40%)", ["circle(at right 40%)", "circle(at 100% 40%)"]);
+test_valid_value("shape-outside", "circle(at right 30% top 60px)", ["circle(at right 30% top 60px)",  "circle(at 70% 60px)"]);
+test_valid_value("shape-outside", "circle(at top)", ["circle(at top)", "circle(at 50% 0%)"]);
+test_valid_value("shape-outside", "circle(at top center)", ["circle(at top center)", "circle(at 50% 0%)"]);
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/parsing/shape-outside-valid.html b/css/css-shapes/parsing/shape-outside-valid.html
new file mode 100644
index 0000000..ae0e0bf
--- /dev/null
+++ b/css/css-shapes/parsing/shape-outside-valid.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Shapes Module Level 1: parsing shape-outside with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-outside-property">
+<meta name="assert" content="shape-outside supports the full grammar 'none | [ <basic-shape> || <shape-box> ] | <image>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("shape-outside", "none");
+
+// <basic-shape>
+test_valid_value("shape-outside", "inset(100%)");
+test_valid_value("shape-outside", "inset(0 1px)", "inset(0px 1px)");
+test_valid_value("shape-outside", "inset(0px 1px 2%)");
+test_valid_value("shape-outside", "inset(0px 1px 2% 3em)");
+test_valid_value("shape-outside", "inset(0px round 100%)");
+test_valid_value("shape-outside", "inset(0px round 0 1px)", "inset(0px round 0px 1px)");
+test_valid_value("shape-outside", "inset(0px round 0px 1px 2%)");
+test_valid_value("shape-outside", "inset(0px round 0px 1px 2% 3em)");
+test_valid_value("shape-outside", "inset(10px round 20% / 0px 1px 2% 3em)");
+
+test_valid_value("shape-outside", "circle()", "circle(at 50% 50%)");
+test_valid_value("shape-outside", "circle(1px)", "circle(1px at 50% 50%)");
+test_valid_value("shape-outside", "circle(closest-side)", "circle(at 50% 50%)");
+test_valid_value("shape-outside", "circle(at 10% 20%)");
+test_valid_value("shape-outside", "circle(farthest-side at center top)", "circle(farthest-side at 50% 0%)");
+test_valid_value("shape-outside", "circle(4% at top right)", "circle(4% at 100% 0%)");
+
+test_valid_value("shape-outside", "ellipse()", "ellipse(at 50% 50%)");
+test_valid_value("shape-outside", "ellipse(3% 2%)", "ellipse(3% 2% at 50% 50%)");
+test_valid_value("shape-outside", "ellipse(closest-side 1px)", "ellipse(closest-side 1px at 50% 50%)");
+test_valid_value("shape-outside", "ellipse(at 10% 20%)");
+test_valid_value("shape-outside", "ellipse(farthest-side 4% at bottom left)", "ellipse(farthest-side 4% at 0% 100%)");
+
+test_valid_value("shape-outside", "polygon(1% 2%)");
+test_valid_value("shape-outside", "polygon(nonzero, 1px 2px, 3em 4em)", "polygon(1px 2px, 3em 4em)");
+test_valid_value("shape-outside", "polygon(evenodd, 1px 2px, 3em 4em, 5pt 6%)");
+
+// <shape-box>
+test_valid_value("shape-outside", "border-box");
+test_valid_value("shape-outside", "padding-box");
+test_valid_value("shape-outside", "content-box");
+test_valid_value("shape-outside", "margin-box");
+
+// basic-shape> <shape-box>
+test_valid_value("shape-outside", "circle(7% at 8% 9%) border-box");
+
+// <shape-box> basic-shape>
+test_valid_value("shape-outside", "border-box circle(7% at 8% 9%)");
+
+// <image>
+// Spec is silent about url serialization.
+// Blink, Edge, Firefox add quotes. WebKit removes quotes.
+test_valid_value("shape-outside", "url(https://example.com/)", ["url(https://example.com/)", "url(\"https://example.com/\")"]);
+test_valid_value("shape-outside", "url(\"https://example.com/\")", ["url(https://example.com/)", "url(\"https://example.com/\")"]);
+test_valid_value("shape-outside", "cross-fade(url(\"https://example.com/\"), green)");
+test_valid_value("shape-outside", "linear-gradient(yellow, blue)");
+</script>
+</body>
+</html>
diff --git a/css/css-shapes/shape-outside/formatting-context/reference/shape-outside-formatting-context-ref.html b/css/css-shapes/shape-outside/formatting-context/reference/shape-outside-formatting-context-ref.html
new file mode 100644
index 0000000..2fd6df9
--- /dev/null
+++ b/css/css-shapes/shape-outside/formatting-context/reference/shape-outside-formatting-context-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<style>
+.test {
+  width: 200px;
+  overflow: hidden;
+}
+
+.float {
+  float: left;
+  width: 50%;
+  height: 100px;
+
+  background: orange;
+  clip-path: polygon(0 0, 100% 100%, 0 100%);
+}
+
+.flex {
+  display: flex;
+  height: 50px;
+  background: rebeccapurple;
+}
+</style>
+<div class="test">
+  <div class="float"></div>
+  <div class="flex"></div>
+</div>
diff --git a/css/css-shapes/shape-outside/formatting-context/shape-outside-formatting-context.tentative.html b/css/css-shapes/shape-outside/formatting-context/shape-outside-formatting-context.tentative.html
new file mode 100644
index 0000000..cfb5d46
--- /dev/null
+++ b/css/css-shapes/shape-outside/formatting-context/shape-outside-formatting-context.tentative.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<link rel="match" href="reference/shape-outside-formatting-context-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-shapes/">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1970">
+<meta name="assert" content="The test verifies that a new formatting context does *not* get sized and positioned based off the float area (defined by shape outside).">
+<style>
+.test {
+  width: 200px;
+  overflow: hidden;
+}
+
+.float {
+  float: left;
+  width: 50%;
+  height: 100px;
+
+  background: orange;
+  shape-outside: polygon(0 0, 100% 100%, 0 100%);
+  clip-path: polygon(0 0, 100% 100%, 0 100%);
+}
+
+.flex {
+  display: flex;
+  height: 50px;
+  background: rebeccapurple;
+}
+</style>
+<div class="test">
+  <div class="float"></div>
+  <div class="flex"></div>
+</div>
diff --git a/css/css-shapes/shape-outside/shape-box/shape-outside-box-009.html b/css/css-shapes/shape-outside/shape-box/shape-outside-box-009.html
new file mode 100644
index 0000000..67786bc
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-box/shape-outside-box-009.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: right float, shape-outside: border-box</title>
+        <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-box-values">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#border-box">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
+        <link rel="match" href="reference/shape-outside-box-005-ref.html">
+        <meta name="flags" content="ahem" />
+        <meta name="assert" content="The test verifies that text wraps around a
+                                     right float with a shape-outside defined as
+                                     the border box, with negative margins.
+                                     The float area is clipped to the margin box.">
+    </head>
+    <style>
+    body {
+        margin: 0;
+    }
+    #container {
+        position: relative;
+    }
+    #test-container {
+        font: 40px/1 Ahem, sans-serif;
+        text-align: right;
+        width: 250px;
+        height: 200px;
+        color: green;
+    }
+    #test-shape {
+        float: right;
+        width: 150px;
+        height: 150px;
+        margin: -20px;
+        padding: 10px;
+        border: 10px solid transparent;
+        shape-outside: border-box;
+    }
+    #line {
+        position: absolute;
+        top: 0px;
+        left: 100px;
+        width: 2px;
+        height: 200px;
+        border-left: 2px solid blue;
+    }
+    #failure {
+        position: absolute;
+        top: 80px;
+        left: 60px;
+        width: 40px;
+        height: 40px;
+        background-color: red;
+        z-index: -1;
+    }
+    </style>
+    <body>
+    <p>The test passes if there is a green square to the left of the blue line. There should be no red.</p>
+    <div id="container">
+        <div id="test-container">
+            <div id="test-shape"></div>
+            <br/>
+            <br/>
+            X
+        </div>
+        <div id="line"></div>
+        <div id="failure"></div>
+    </div>
+    </body>
+</html>
+
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-005.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-005.html
new file mode 100644
index 0000000..a622879
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-005.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-rl</title>
+    <link rel="author" title="Ting-Yu Lin" href="mailto:aethanyc@gmail.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient under vertical-rl."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-rl;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 50%, transparent 50%);
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 50%, transparent 50%);
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-006.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-006.html
new file mode 100644
index 0000000..18253ed
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-006.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-lr</title>
+    <link rel="author" title="Ting-Yu Lin" href="mailto:aethanyc@gmail.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient under vertical-lr."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-lr;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 50%, transparent 50%);
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 50%, transparent 50%);
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-007.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-007.html
new file mode 100644
index 0000000..9087fb0
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-007.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: sideways-rl</title>
+    <link rel="author" title="Ting-Yu Lin" href="mailto:aethanyc@gmail.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient under sideways-rl."/>
+    <style type="text/css">
+    .container {
+      writing-mode: sideways-rl;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 50%, transparent 50%);
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 50%, transparent 50%);
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-008.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-008.html
new file mode 100644
index 0000000..05132b4
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-008.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: sideways-lr</title>
+    <link rel="author" title="Ting-Yu Lin" href="mailto:aethanyc@gmail.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient under sideways-lr."/>
+    <style type="text/css">
+    .container {
+      writing-mode: sideways-lr;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the bottom. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 50%, transparent 50%);
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the top. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 50%, transparent 50%);
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-009.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-009.html
new file mode 100644
index 0000000..eb04734
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-009.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-rl and text-orientation: sideways</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient under vertical-rl and text-orientation: sideways."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-rl;
+      text-orientation: sideways;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 50%, transparent 50%);
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 50%, transparent 50%);
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-010.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-010.html
new file mode 100644
index 0000000..26335a6
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-010.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-lr and text-orientation: sideways</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient under vertical-lr and text-orientation: sideways."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-lr;
+      text-orientation: sideways;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 50%, transparent 50%);
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 50%, transparent 50%);
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-011.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-011.html
new file mode 100644
index 0000000..3daa63c
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-011.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-rl</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient with shape-margin under vertical-rl."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-rl;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 25%, transparent 25%);
+      shape-margin: 25%;
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 5%, transparent 5%);
+      shape-margin: 45%
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-012.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-012.html
new file mode 100644
index 0000000..52f5e8a
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-012.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-lr</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient with shape-margin under vertical-lr."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-lr;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 25%, transparent 25%);
+      shape-margin: 25%;
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 5%, transparent 5%);
+      shape-margin: 45%
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-013.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-013.html
new file mode 100644
index 0000000..66a2850
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-013.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: sideways-rl</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient with shape-margin under sideways-rl."/>
+    <style type="text/css">
+    .container {
+      writing-mode: sideways-rl;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 25%, transparent 25%);
+      shape-margin: 25%;
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 5%, transparent 5%);
+      shape-margin: 45%
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-014.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-014.html
new file mode 100644
index 0000000..91ba3ef
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-014.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: sideways-lr</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient with shape-margin under sideways-lr."/>
+    <style type="text/css">
+    .container {
+      writing-mode: sideways-lr;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the bottom. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 25%, transparent 25%);
+      shape-margin: 25%;
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the top. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 5%, transparent 5%);
+      shape-margin: 45%
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-015.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-015.html
new file mode 100644
index 0000000..382586a
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-015.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-rl and text-orientation: sideways</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient with shape-margin under vertical-rl and text-orientation: sideways."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-rl;
+      text-orientation: sideways;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 25%, transparent 25%);
+      shape-margin: 25%;
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 5%, transparent 5%);
+      shape-margin: 45%
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-016.html b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-016.html
new file mode 100644
index 0000000..fe45242
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-016.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test: Test float with linear gradient under writing-mode: vertical-lr and text-orientation: sideways</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin"/>
+    <link rel="match" href="reference/shape-outside-linear-gradient-001-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that shape-outside respects a simple linear gradient with shape-margin under vertical-lr and text-orientation: sideways."/>
+    <style type="text/css">
+    .container {
+      writing-mode: vertical-lr;
+      text-orientation: sideways;
+      inline-size: 100px;
+      block-size: 200px;
+      background-color: red;
+      font-family: Ahem;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #test {
+      color: green;
+    }
+    #float-left {
+      /* Note: In .container's writing-mode, "float: left" actually floats
+         us towards the top. */
+      float: left;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to bottom, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to bottom, green 25%, transparent 25%);
+      shape-margin: 25%;
+    }
+    #float-right {
+      /* Note: In .container's writing-mode, "float: right" actually floats
+         us towards the bottom. */
+      float: right;
+      inline-size: 100px;
+      block-size: 200px;
+      background: linear-gradient(to top, green 50%, transparent 50%);
+      shape-outside: linear-gradient(to top, green 5%, transparent 5%);
+      shape-margin: 45%
+    }
+    </style>
+  </head>
+  <body>
+    <p>
+      The test passes if you see a green square. There should be no red.
+    </p>
+    <div id="test" class="container">
+      <div id="float-left"></div>
+      x x x x
+    </div>
+    <div id="test" class="container" style="direction: rtl;">
+      <div id="float-right"></div>
+      x x x x
+    </div>
+  </body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/shape-image-001.html b/css/css-shapes/shape-outside/shape-image/shape-image-001.html
index 8273030..ac2d92f 100644
--- a/css/css-shapes/shape-outside/shape-image/shape-image-001.html
+++ b/css/css-shapes/shape-outside/shape-image/shape-image-001.html
@@ -22,7 +22,7 @@
         }
         #image {
             float: left;
-            width: 150px;
+            width: 100px;
             height: 100px;
             shape-outside: url("support/left-half-rectangle-70.png");
         }
diff --git a/css/css-shapes/shape-outside/shape-image/shape-image-002.html b/css/css-shapes/shape-outside/shape-image/shape-image-002.html
index ee2dd75..01d916d 100644
--- a/css/css-shapes/shape-outside/shape-image/shape-image-002.html
+++ b/css/css-shapes/shape-outside/shape-image/shape-image-002.html
@@ -22,7 +22,7 @@
         }
         #image {
             float: left;
-            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
+            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
         }
     </style>
 </head>
@@ -31,7 +31,7 @@
         The test passes if you see a solid green square. There should be no red.
     </p>
     <div id="test" class="container">
-        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
+        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
         X
         X
     </div>
diff --git a/css/css-shapes/shape-outside/shape-image/shape-image-005.html b/css/css-shapes/shape-outside/shape-image/shape-image-005.html
index 2c642c0..93f3978 100644
--- a/css/css-shapes/shape-outside/shape-image/shape-image-005.html
+++ b/css/css-shapes/shape-outside/shape-image/shape-image-005.html
@@ -22,7 +22,7 @@
         }
         #image {
             float: left;
-            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
+            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
             shape-image-threshold: 0.8;
         }
     </style>
@@ -32,7 +32,7 @@
         The test passes if you see a solid green square. There should be no red.
     </p>
     <div id="test" class="container">
-        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
+        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
         X
         X
     </div>
diff --git a/css/css-shapes/shape-outside/shape-image/shape-image-010.html b/css/css-shapes/shape-outside/shape-image/shape-image-010.html
index aedab21..df993be 100644
--- a/css/css-shapes/shape-outside/shape-image/shape-image-010.html
+++ b/css/css-shapes/shape-outside/shape-image/shape-image-010.html
@@ -40,7 +40,7 @@
             height: 100px;
             shape-outside: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAALpJREFUeNrs0UEVABAURcHP5pcRSxpR9FHGhhycuQ3emxI9TnxQ7pxttfH6jhoCIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACIiBABASIgAARECACAsQFQAQEiIAAERAgAgJEQAQEiIAAEZDPuwAAAP//AwCf+AWUylJrCQAAAABJRU5ErkJggg==);
             shape-margin: 5%;
-            shape-image-threshold: 0.25;
+            shape-image-threshold: 0.7;
         }
         .blue {
             width: 2px;
diff --git a/css/css-shapes/shape-outside/shape-image/shape-image-026.html b/css/css-shapes/shape-outside/shape-image/shape-image-026.html
new file mode 100644
index 0000000..40482f8
--- /dev/null
+++ b/css/css-shapes/shape-outside/shape-image/shape-image-026.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Test: left float, url(png), real offset image + shape-margin (px)</title>
+    <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shapes-from-image"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property"/>
+    <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property"/>
+    <link rel="match" href="reference/shape-image-006-ref.html"/>
+    <meta name="flags" content="ahem"/>
+    <meta name="assert" content="This test verifies that content wraps around all the image pixels
+                                 + the shape-margin when shape-outside is given a png file.
+                                 Additionally, the shape-outside: image element is offset from its
+                                 containing block."/>
+    <style type="text/css">
+        body {
+            margin: 0;
+        }
+        .container {
+          font: 50px/1 Ahem;
+          padding: 50px;
+          position: absolute;
+          top: 20px;
+          left: -40px;
+        }
+        #test {
+            width: 200px;
+            color: rgb(0,100,0);
+        }
+        #image {
+            float: left;
+            shape-outside: url("support/left-half-rectangle.png");
+            shape-margin: 10px;
+        }
+        .blue {
+            width: 2px;
+            height: 100px;
+            background-color: blue;
+        }
+        .left-line { left: 65px; }
+        .right-line { left: 125px; }
+
+        .failure {
+            left: 70px;
+            width: 50px;
+            height: 100px;
+            background-color: red;
+            z-index: -1;
+        }
+        .blue, .failure {
+            position: absolute;
+            top: 70px;
+        }
+    </style>
+</head>
+<body>
+    <p>
+        The test passes if the green rectangle on the right is completely between the two blue lines.
+        There should be no red.
+    </p>
+    <div id="test" class="container">
+        <img id="image" src="support/left-half-rectangle.png"/>
+        X<br/>X
+    </div>
+    <div class="blue left-line"></div>
+    <div class="blue right-line"></div>
+    <div class="failure"></div>
+</body>
+</html>
diff --git a/css/css-shapes/shape-outside/shape-image/support/animated.gif b/css/css-shapes/shape-outside/shape-image/support/animated.gif
index 083c9a0..9e424df 100644
--- a/css/css-shapes/shape-outside/shape-image/support/animated.gif
+++ b/css/css-shapes/shape-outside/shape-image/support/animated.gif
Binary files differ
diff --git a/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html b/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html
index 2141c12..b00f6c4 100644
--- a/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html
+++ b/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-027.html
@@ -36,7 +36,7 @@
         padding: 10px;
         border: 10px solid transparent;
         shape-margin: 15px;
-        shape-outside: margin-box circle(35% at 85px 75px);
+        shape-outside: margin-box circle(60px);
     }
     #line {
         position: absolute;
diff --git a/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html b/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html
index 3502f8b..1a434a0 100644
--- a/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html
+++ b/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html
@@ -34,7 +34,7 @@
         margin: 10px;
         padding: 10px;
         border: 10px solid transparent;
-        shape-outside: padding-box ellipse(closest-side at 75px 80px);
+        shape-outside: padding-box ellipse(closest-side closest-side at 75px 80px);
     }
     #line {
         position: absolute;
diff --git a/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html b/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html
index 3cf6089..fd9b816 100644
--- a/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html
+++ b/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html
@@ -36,7 +36,7 @@
         padding: 10px;
         border: 10px solid transparent;
         shape-margin: 10px;
-        shape-outside: content-box ellipse(farthest-side);
+        shape-outside: content-box ellipse(farthest-side closest-side);
     }
     #line {
         position: absolute;
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html
index 72147aa..49627c8 100644
--- a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-010.html
@@ -19,8 +19,7 @@
         #test-container {
             width: 200px;
             height: 200px;
-            font-family: Ahem;
-            font-size: 25px;
+            font: 25px/1 Ahem;
             background-color: red;
             color: green;
         }
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html
index 9a65184..96d2421 100644
--- a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-011.html
@@ -21,6 +21,7 @@
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html
index d7988c2..b9dbe52 100644
--- a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-012.html
@@ -22,6 +22,7 @@
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html
index 03e4baa..d49dd70 100644
--- a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-013.html
@@ -22,6 +22,7 @@
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html
index bb791a5..30604af 100644
--- a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-014.html
@@ -22,6 +22,7 @@
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html
index b2e0e06..da54fb2 100644
--- a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-015.html
@@ -22,6 +22,7 @@
             height: 200px;
             font-family: Ahem;
             font-size: 25px;
+            line-height: 1;
             background-color: red;
             color: green;
         }
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-028.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-028.html
new file mode 100644
index 0000000..2e0d9ab
--- /dev/null
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-028.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: left float, inset, px units</title>
+        <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-inset">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
+        <link rel="match" href="reference/shape-outside-inset-010-ref.html"/>
+        <meta name="flags" content="ahem" />
+        <meta name="assert" content="The test verfies that text flows around a
+                                     right float with a shape-outside defined as
+                                     an inset rounded rectangle in px units with
+                                     a shape-margin.">
+    </head>
+    <style>
+        #container {
+            position: relative;
+            margin-left: 25px;
+        }
+        #test-container {
+            width: 200px;
+            height: 200px;
+            font: 25px/1 Ahem;
+            background-color: red;
+            color: green;
+            text-align: right;
+        }
+        #test-shape {
+            float: right;
+            width: 200px;
+            height: 200px;
+            background-color: green;
+            shape-margin: 10px;
+            shape-outside: inset(60px 10px 60px 110px round 20px);
+        }
+        #static-shape {
+            position: absolute;
+            left: 100px;
+            width: 100px;
+            height: 100px;
+            top: 50px;
+            background-color: green;
+        }
+    </style>
+    <body>
+        <p>The test passes if there is a green square and no red.</p>
+        <div id="container">
+            <div id="test-container">
+                <div id="test-shape"></div>
+                XXXXXXXX XXXXXXXX XXXX XXXX XXXX XXXX XXXXXXXX XXXXXXXX
+            </div>
+            <div id="static-shape"></div>
+        </div>
+    </body>
+</html>
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-029.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-029.html
new file mode 100644
index 0000000..fdf226e
--- /dev/null
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-029.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: left float, inset, px units</title>
+        <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-inset">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
+        <link rel="match" href="reference/shape-outside-inset-010-ref.html"/>
+        <meta name="flags" content="ahem" />
+        <meta name="assert" content="The test verfies that text flows around a
+                                     right float with a shape-outside defined as
+                                     an inset irregular elliptically rounded
+                                     rectangle in px units with a shape-margin.">
+    </head>
+    <style>
+        #container {
+            position: relative;
+            margin-left: 25px;
+        }
+        #test-container {
+            width: 200px;
+            height: 200px;
+            font: 25px/1 Ahem;
+            background-color: red;
+            color: green;
+            text-align: right;
+        }
+        #test-shape {
+            float: right;
+            width: 200px;
+            height: 200px;
+            shape-margin: 10px;
+            shape-outside: inset(60px 10px 60px 110px round 70px 0px 0px 10px / 10px 0px 0px 20px);
+        }
+        #static-shape {
+            position: absolute;
+            left: 100px;
+            width: 100px;
+            height: 100px;
+            top: 50px;
+            background-color: green;
+        }
+    </style>
+    <body>
+        <p>The test passes if there is a green square and no red.</p>
+        <div id="container">
+            <div id="test-container">
+                <div id="test-shape"></div>
+                XXXXXXXX XXXXXXXX XXXX XXXX XXXX XXXX XXXXXXXX XXXXXXXX
+            </div>
+            <div id="static-shape"></div>
+        </div>
+    </body>
+</html>
diff --git a/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-030.html b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-030.html
new file mode 100644
index 0000000..0daa382
--- /dev/null
+++ b/css/css-shapes/shape-outside/supported-shapes/inset/shape-outside-inset-030.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: left float, offset inset, px units</title>
+        <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-inset">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
+        <link rel="match" href="reference/shape-outside-inset-010-ref.html"/>
+        <meta name="flags" content="ahem" />
+        <meta name="assert" content="The test verfies that text flows around a
+                                     right float with a shape-outside defined as
+                                     an inset irregular elliptically rounded
+                                     rectangle in px units with a shape-margin.
+                                     Additionally, the shape-outside: inset element is
+                                     offset from its containing block.">
+    </head>
+    <style>
+        #container {
+            position: relative;
+            margin-left: 25px;
+        }
+        #red {
+            position: absolute;
+            width: 200px;
+            height: 200px;
+            background-color: red;
+        }
+        #test-container {
+            width: 200px;
+            height: 200px;
+            font: 25px/1 Ahem;
+            color: green;
+            text-align: right;
+            padding: 50px;
+            position: absolute;
+            top: -50px;
+            left: -50px;
+        }
+        #test-shape {
+            float: right;
+            width: 200px;
+            height: 200px;
+            shape-margin: 10px;
+            shape-outside: inset(60px 10px 60px 110px round 70px 0px 0px 10px / 10px 0px 0px 20px);
+        }
+        #static-shape {
+            position: absolute;
+            left: 100px;
+            width: 100px;
+            height: 100px;
+            top: 50px;
+            background-color: green;
+        }
+    </style>
+    <body>
+        <p>The test passes if there is a green square and no red.</p>
+        <div id="container">
+            <div id="red"></div>
+            <div id="test-container">
+                <div id="test-shape"></div>
+                XXXXXXXX XXXXXXXX XXXX XXXX XXXX XXXX XXXXXXXX XXXXXXXX
+            </div>
+            <div id="static-shape"></div>
+        </div>
+    </body>
+</html>
diff --git a/css/css-shapes/shape-outside/values/shape-image-threshold-003.html b/css/css-shapes/shape-outside/values/shape-image-threshold-003.html
index b939569..cb4027c 100644
--- a/css/css-shapes/shape-outside/values/shape-image-threshold-003.html
+++ b/css/css-shapes/shape-outside/values/shape-image-threshold-003.html
@@ -42,7 +42,7 @@
 
         test(function() {
             var results = setUpTest("0.3", null);
-            assert_equals(results[0], null);
+            assert_equals(results[0], "");
             assert_equals(results[1], "0");
         }, "shape-image-threshold is not inherited and defaults to 0");
 
diff --git a/css/css-shapes/shape-outside/values/support/parsing-utils.js b/css/css-shapes/shape-outside/values/support/parsing-utils.js
index cc174ec..68b0c1b 100644
--- a/css/css-shapes/shape-outside/values/support/parsing-utils.js
+++ b/css/css-shapes/shape-outside/values/support/parsing-utils.js
@@ -534,65 +534,6 @@
     ["60% center", "60% 50%"],
     ["60u1 center", "60u1 50%"],
 
-////// [ keyword | keyword percent ], [ keyword | keyword length ] x 5 keywords
-    ["center top 50%", "50% 50%"],
-    ["center top 50u1", "50% 50u1"],
-    ["center left 50%", "50% 50%"],
-    ["center left 50u1", "50u1 50%"],
-    ["center right 70%", "30% 50%"],
-    ["center right 70u1", "right 70u1 top 50%"],
-    ["center bottom 70%", "50% 30%"],
-    ["center bottom 70u1", "left 50% bottom 70u1"],
-
-    ["left top 50%", "0% 50%"],
-    ["left top 50u1", "0% 50u1"],
-    ["left bottom 70%", "0% 30%"],
-    ["left bottom 70u1", "left 0% bottom 70u1"],
-
-    ["top left 50%", "50% 0%"],
-    ["top left 50u1", "50u1 0%"],
-    ["top right 70%", "30% 0%"],
-    ["top right 70u1", "right 70u1 top 0%"],
-
-    ["bottom left 50%", "50% 100%"],
-    ["bottom left 50u1", "50u1 100%"],
-    ["bottom right 70%", "30% 100%"],
-    ["bottom right 70u1", "right 70u1 top 100%"],
-
-    ["right bottom 70%", "100% 30%"],
-    ["right bottom 70u1", "left 100% bottom 70u1"],
-    ["right top 50%", "100% 50%"],
-    ["right top 50u1", "100% 50u1"],
-
-////// [ keyword percent | keyword], [ keyword length | keyword ] x 5 keywords
-    ["left 50% center", "50% 50%"],
-    ["left 50u1 center", "50u1 50%"],
-    ["left 50% top", "50% 0%"],
-    ["left 50u1 top", "50u1 0%"],
-    ["left 50% bottom", "50% 100%"],
-    ["left 50u1 bottom", "50u1 100%"],
-
-    ["top 50% center", "50% 50%"],
-    ["top 50u1 center", "50% 50u1"],
-    ["top 50% left", "0% 50%"],
-    ["top 50u1 left", "0% 50u1"],
-    ["top 50% right", "100% 50%"],
-    ["top 50u1 right", "100% 50u1"],
-
-    ["bottom 70% center", "50% 30%"],
-    ["bottom 70u1 center", "left 50% bottom 70u1"],
-    ["bottom 70% left", "0% 30%"],
-    ["bottom 70u1 left", "left 0% bottom 70u1"],
-    ["bottom 70% right", "100% 30%"],
-    ["bottom 70u1 right", "left 100% bottom 70u1"],
-
-    ["right 80% center", "20% 50%"],
-    ["right 80u1 center", "right 80u1 top 50%"],
-    ["right 80% bottom", "20% 100%"],
-    ["right 80u1 bottom", "right 80u1 top 100%"],
-    ["right 80% top", "20% 0%"],
-    ["right 80u1 top", "right 80u1 top 0%"],
-
 ////// [ keyword percent |  keyword percent], [ keyword percent |  keyword length],
 ////// [ keyword length | keyword length],  [ keyword length | keyword percent] x 5 keywords
     ["left 50% top 50%", "50% 50%"],
diff --git a/css/css-shapes/spec-examples/shape-outside-010.html b/css/css-shapes/spec-examples/shape-outside-010.html
index 4c4ce48..e85e74d 100644
--- a/css/css-shapes/spec-examples/shape-outside-010.html
+++ b/css/css-shapes/spec-examples/shape-outside-010.html
@@ -30,8 +30,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [182, 199, 201, 199, 182, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the circle,
         the long green bar is beneath the circle, and no bars intersect the circle. There
@@ -47,8 +52,5 @@
         <span id="line-5">XXXXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [182, 199, 201, 199, 182, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-011.html b/css/css-shapes/spec-examples/shape-outside-011.html
index eb4c19d..84defc5 100644
--- a/css/css-shapes/spec-examples/shape-outside-011.html
+++ b/css/css-shapes/spec-examples/shape-outside-011.html
@@ -30,8 +30,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [218, 236, 238, 236, 218, 160]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the green horizontal bars are to the right of the circle,
         and no bars intersect the circle's shadow. There should be no red.
@@ -46,8 +51,5 @@
         <span id="line-5">XXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [218, 236, 238, 236, 218, 160]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-012.html b/css/css-shapes/spec-examples/shape-outside-012.html
index 1d0b052..1adf86e 100644
--- a/css/css-shapes/spec-examples/shape-outside-012.html
+++ b/css/css-shapes/spec-examples/shape-outside-012.html
@@ -32,8 +32,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [181, 199, 201, 199, 181, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the circle, all
         intersect the shadow, none intersect the circle and the long green bar is beneath the
@@ -49,8 +54,5 @@
         <span id="line-5">XXXXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [181, 199, 201, 199, 181, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-013.html b/css/css-shapes/spec-examples/shape-outside-013.html
index 35e4499..a594547 100644
--- a/css/css-shapes/spec-examples/shape-outside-013.html
+++ b/css/css-shapes/spec-examples/shape-outside-013.html
@@ -40,8 +40,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [200, 214, 216, 214, 200, 158, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the circle,
         the long green bar is beneath the circle, and no bars intersect the circle. There
@@ -59,8 +64,5 @@
         <span id="line-6">XXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [200, 214, 216, 214, 200, 158, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-014.html b/css/css-shapes/spec-examples/shape-outside-014.html
index 91be964..307046b 100644
--- a/css/css-shapes/spec-examples/shape-outside-014.html
+++ b/css/css-shapes/spec-examples/shape-outside-014.html
@@ -44,8 +44,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 3, [182, 198, 200, 198, 182, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the shape,
         the long green bar is beneath it, and no bars intersect it. There should be no red.
@@ -61,8 +66,5 @@
         <span id="line-5">XXXXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 3, [182, 198, 200, 198, 182, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-015.html b/css/css-shapes/spec-examples/shape-outside-015.html
index 7f4a6f7..96c48f2 100644
--- a/css/css-shapes/spec-examples/shape-outside-015.html
+++ b/css/css-shapes/spec-examples/shape-outside-015.html
@@ -32,8 +32,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [185, 199, 200, 199, 185, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the shape,
         the long green bar is beneath it, and no bars intersect it. There should be no red.
@@ -48,8 +53,5 @@
         <span id="line-5">XXXXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [185, 199, 200, 199, 185, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-016.html b/css/css-shapes/spec-examples/shape-outside-016.html
index d3b82f6..f34b5c4 100644
--- a/css/css-shapes/spec-examples/shape-outside-016.html
+++ b/css/css-shapes/spec-examples/shape-outside-016.html
@@ -32,8 +32,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [185, 199, 200, 199, 185, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the shape,
         the long green bar is beneath it, and no bars intersect it. There should be no red.
@@ -48,8 +53,5 @@
         <span id="line-5">XXXXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [185, 199, 200, 199, 185, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-017.html b/css/css-shapes/spec-examples/shape-outside-017.html
index ae28284..160671c 100644
--- a/css/css-shapes/spec-examples/shape-outside-017.html
+++ b/css/css-shapes/spec-examples/shape-outside-017.html
@@ -31,8 +31,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [185, 199, 200, 199, 185, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the shape,
         the long green bar is beneath it, and no bars intersect it. There should be no red.
@@ -47,8 +52,5 @@
         <span id="line-5">XXXXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [185, 199, 200, 199, 185, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-018.html b/css/css-shapes/spec-examples/shape-outside-018.html
index ba2c3ff..b95cfd1 100644
--- a/css/css-shapes/spec-examples/shape-outside-018.html
+++ b/css/css-shapes/spec-examples/shape-outside-018.html
@@ -37,8 +37,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [48, 88, 128, 168, 180, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the longest green horizontal bar is beneath the triangle and the
         rest of them are to its right and none intersect it. There should be no red.
@@ -54,8 +59,5 @@
         <span id="line-5">XXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [48, 88, 128, 168, 180, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/shape-outside-019.html b/css/css-shapes/spec-examples/shape-outside-019.html
index 3ef311f..5d19b35 100644
--- a/css/css-shapes/spec-examples/shape-outside-019.html
+++ b/css/css-shapes/spec-examples/shape-outside-019.html
@@ -39,8 +39,13 @@
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <script src="support/spec-example-utils.js"></script>
+    <script>
+    function checkFloats() {
+      approxShapeTest('test', 'line-', 2, [242, 256, 258, 256, 242, 204, 0]);
+    }
+    </script>
 </head>
-<body>
+<body onload="checkFloats();">
     <p>
         The test passes if the short green horizontal bars are the right of the black edge
         of circle, the long green bar is beneath it, and no bars intersect the black.
@@ -58,8 +63,5 @@
         <span id="line-6">XXXXXXXXXXXXXXX</span>
     </div>
     <div id="log"></div>
-    <script>
-        approxShapeTest('test', 'line-', 2, [242, 256, 258, 256, 242, 204, 0]);
-    </script>
 </body>
 </html>
diff --git a/css/css-shapes/spec-examples/support/spec-example-utils.js b/css/css-shapes/spec-examples/support/spec-example-utils.js
index 9f4a19e..c5b0411 100644
--- a/css/css-shapes/spec-examples/support/spec-example-utils.js
+++ b/css/css-shapes/spec-examples/support/spec-example-utils.js
@@ -1,20 +1,11 @@
 function approxShapeTest(testId, linePrefix, epsilon, lineOffsets) {
     var isPositioned = { 'relative': true, 'fixed': true, 'absolute': true, 'sticky': true },
-        loops = 0,
         testDiv = document.getElementById(testId),
         testOffset = isPositioned[getComputedStyle(testDiv).position] ? 0 : testDiv.offsetLeft,
         firstLine = document.getElementById(linePrefix + '0');
 
     function runTest() {
-        if (firstLine.offsetLeft == testOffset) {
-            // wait for the shape image to load and layout to happen
-            if (loops > 100)
-                assert_unreached("Giving up waiting for shape layout to happen!");
-            else
-                loops++;
-            window.setTimeout(runTest, 5);
-            return;
-        }
+        assert_not_equals(firstLine.offsetLeft, testOffset, "Shape layout should have happened already.");
 
         for (var i = 0; i < lineOffsets.length; i++) {
             var line = document.getElementById(linePrefix + i);
diff --git a/css/css-sizing/OWNERS b/css/css-sizing/OWNERS
new file mode 100644
index 0000000..ea4a9a0
--- /dev/null
+++ b/css/css-sizing/OWNERS
@@ -0,0 +1,2 @@
+@tabatkins
+@fantasai
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-001-ref.html b/css/css-sizing/intrinsic-percent-non-replaced-001-ref.html
new file mode 100644
index 0000000..0b217dd
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-001-ref.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>Reference</title>
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    color: blue;
+    font: 20px/1 Ahem;
+  }
+  .zero {
+    width: 0;
+  }
+  .infinite {
+    width: 400px; /* close enough */
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+  }
+
+  /* controls for min-width */
+  /* content = 100% = 80px = 4ch + border */
+  /* choose sizes that are larger than content to see if how they take effect */
+  .control {
+    width: 60px;
+  }
+  .raw-percent,
+  .calc-percent {
+    width: 160px;
+    margin-right: -100px;
+  }
+  .no-percent {
+    width: 160px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='zero container'>
+  <div><div class="control">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="raw-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="calc-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="no-percent">ppp pp</div></div>
+</div>
+
+<!-- calculating max-content widths -->
+<div class='infinite container'>
+  <div><div class="control">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="raw-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="calc-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="no-percent">p p</div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-001.html b/css/css-sizing/intrinsic-percent-non-replaced-001.html
new file mode 100644
index 0000000..19b0a94
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-001.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>Percentages of min-width on non-replaced blocks are ignored for intrinsic sizing and resolved afterwards</title>
+<link rel="help" href="https://www.w3.org/TR/css-sizing-3/#intrinsic-contribution">
+<link rel="match" href="intrinsic-percent-non-replaced-001-ref.html">
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    color: blue;
+    font: 20px/1 Ahem;
+  }
+  .zero {
+    width: 0;
+  }
+  .infinite {
+    width: 400px; /* close enough */
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+  }
+
+  /* test min-width */
+  /* content = 100% = 80px = 4ch + border */
+  /* choose sizes that are larger than content to see if how they take effect */
+  .raw-percent {
+    min-width: 200%;
+  }
+  .calc-percent {
+    min-width: calc(160px + 0%);
+  }
+  .no-percent {
+    min-width: 160px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='zero container'>
+  <div><div class="control">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="raw-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="calc-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="no-percent">ppp pp</div></div>
+</div>
+
+<!-- calculating max-content widths -->
+<div class='infinite container'>
+  <div><div class="control">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="raw-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="calc-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="no-percent">p p</div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-002-ref.html b/css/css-sizing/intrinsic-percent-non-replaced-002-ref.html
new file mode 100644
index 0000000..bb81891
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-002-ref.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>Reference</title>
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    color: blue;
+    font: 20px/1 Ahem;
+  }
+  .zero {
+    width: 0;
+  }
+  .infinite {
+    width: 400px; /* close enough */
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left; /* shrinkwrap */
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+  }
+
+  /* controls for width, max-width */
+  /* content = 100% = 80px = 4ch + border */
+  /* choose sizes that are larger than content to see if how they take effect */
+  .control {
+    width: 60px;
+  }
+  .raw-percent,
+  .calc-percent {
+    width: 40px;
+    margin-right: 20px;
+  }
+  .no-percent {
+    width: 40px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='zero container'>
+  <div><div class="control">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="raw-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="calc-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="no-percent">ppp pp</div></div>
+</div>
+
+<!-- calculating max-content widths -->
+<div class='infinite container'>
+  <div><div class="control">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="raw-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="calc-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="no-percent">p p</div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-002.html b/css/css-sizing/intrinsic-percent-non-replaced-002.html
new file mode 100644
index 0000000..763549f
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-002.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<title>Percentages of max-width on non-replaced blocks are ignored for intrinsic sizing and resolved afterwards</title>
+<link rel="help" href="https://www.w3.org/TR/css-sizing-3/#intrinsic-contribution">
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    color: blue;
+    font: 20px/1 Ahem;
+  }
+  .zero {
+    width: 0;
+  }
+  .infinite {
+    width: 400px; /* close enough */
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left; /* shrinkwrap */
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+  }
+
+  /* test max-width */
+  /* content = 100% = 80px = 4ch + border */
+  /* choose sizes that are smaller than content to see if how they take effect */
+  .raw-percent {
+    max-width: 50%;
+  }
+  .calc-percent {
+    max-width: calc(40px + 0%);
+  }
+  .no-percent {
+    max-width: 40px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='zero container'>
+  <div><div class="control">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="raw-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="calc-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="no-percent">ppp pp</div></div>
+</div>
+
+<!-- calculating max-content widths -->
+<div class='infinite container'>
+  <div><div class="control">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="raw-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="calc-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="no-percent">p p</div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-003.html b/css/css-sizing/intrinsic-percent-non-replaced-003.html
new file mode 100644
index 0000000..2dd247c
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-003.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>Percentages of width on non-replaced blocks are ignored for intrinsic sizing and resolved afterwards</title>
+<link rel="help" href="https://www.w3.org/TR/css-sizing-3/#intrinsic-contribution">
+<link rel="match" href="intrinsic-percent-non-replaced-002-ref.html">
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    color: blue;
+    font: 20px/1 Ahem;
+  }
+  .zero {
+    width: 0;
+  }
+  .infinite {
+    width: 400px; /* close enough */
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+  }
+
+  /* test width */
+  /* content = 100% = 80px = 4ch + border */
+  /* choose sizes that are different than content to see if how they take effect */
+  .raw-percent {
+    width: 50%;
+  }
+  .calc-percent {
+    width: calc(40px + 0%);
+  }
+  .no-percent {
+    width: 40px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='zero container'>
+  <div><div class="control">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="raw-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="calc-percent">ppp pp</div></div>
+</div>
+<div class='zero container'>
+  <div><div class="no-percent">ppp pp</div></div>
+</div>
+
+<!-- calculating max-content widths -->
+<div class='infinite container'>
+  <div><div class="control">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="raw-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="calc-percent">p p</div></div>
+</div>
+<div class='infinite container'>
+  <div><div class="no-percent">p p</div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-004-ref.html b/css/css-sizing/intrinsic-percent-non-replaced-004-ref.html
new file mode 100644
index 0000000..aa735cf
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-004-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Reference</title>
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 40px;
+    height: 20px;
+  }
+
+  /* controls for min-width */
+  /* content = 100% = 80px = 4ch + border */
+  /* choose sizes that are larger than content to see if how they take effect */
+  .container > div > .raw-percent {
+    width: 120px;
+  }
+  .container > div > .calc-percent,
+  .container > div > .no-percent {
+    width: 160px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 60px;'><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 60px;'><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-004.html b/css/css-sizing/intrinsic-percent-non-replaced-004.html
new file mode 100644
index 0000000..6fc061a
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-004.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Percentages of width on non-replaced blocks are ignored for intrinsic sizing and resolved afterwards</title>
+<link rel="help" href="https://www.w3.org/TR/css-sizing-3/#intrinsic-contribution">
+<link rel="match" href="intrinsic-percent-non-replaced-004-ref.html">
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 40px;
+    height: 20px;
+  }
+
+  /* test width */
+  /* content = 100% = 60px = 40px + border */
+  /* choose sizes that are different than content to see if how they take effect */
+  .raw-percent {
+    min-width: 200%;
+  }
+  .calc-percent {
+    min-width: calc(160px + 0%);
+  }
+  .no-percent {
+    min-width: 160px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-005-ref.html b/css/css-sizing/intrinsic-percent-non-replaced-005-ref.html
new file mode 100644
index 0000000..38f480c
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-005-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Reference</title>
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 80px;
+    height: 20px;
+  }
+
+  /* test width */
+  /* content = 100% = 100px = 80px + border */
+  /* choose sizes that are different than content to see if how they take effect */
+  .container > div > .raw-percent {
+    width: 50px;
+  }
+  .container > div > .calc-percent,
+  .container > div > .no-percent {
+    width: 40px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 100px;'><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 100px;'><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
diff --git a/css/css-sizing/intrinsic-percent-non-replaced-005.html b/css/css-sizing/intrinsic-percent-non-replaced-005.html
new file mode 100644
index 0000000..f105d94
--- /dev/null
+++ b/css/css-sizing/intrinsic-percent-non-replaced-005.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Percentages of width on non-replaced blocks are ignored for intrinsic sizing and resolved afterwards</title>
+<link rel="help" href="https://www.w3.org/TR/css-sizing-3/#intrinsic-contribution">
+<link rel="match" href="intrinsic-percent-non-replaced-005-ref.html">
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 80px;
+    height: 20px;
+  }
+
+  /* test width */
+  /* content = 100% = 100px = 80px + border */
+  /* choose sizes that are different than content to see if how they take effect */
+  .raw-percent {
+    max-width: 50%;
+  }
+  .calc-percent {
+    max-width: calc(40px + 0%);
+  }
+  .no-percent {
+    max-width: 40px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
diff --git a/css/css-style-attr/reference/ref-green-on-green2.xht b/css/css-style-attr/reference/ref-green-on-green2.xht
deleted file mode 100644
index 141069c..0000000
--- a/css/css-style-attr/reference/ref-green-on-green2.xht
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
-  <title>CSS Reftest Reference</title>
-  <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"/>
-  <style type="text/css"><![CDATA[
-   p { color: green; background: lime; }
-  ]]></style>
- </head>
- <body>
-  <p>This sentence must be green on a green background.</p>
-  <p>This sentence must be green on a green background.</p>
- </body>
-</html>
diff --git a/css/css-style-attr/style-attr-braces-002-quirks.htm b/css/css-style-attr/style-attr-braces-002-quirks.htm
index d3465d8..7b74d8f 100644
--- a/css/css-style-attr/style-attr-braces-002-quirks.htm
+++ b/css/css-style-attr/style-attr-braces-002-quirks.htm
@@ -8,7 +8,7 @@
   <meta name="assert" content="Style attribute values are the content of a
     declaration block: the braces must not be included and are therefore invalid."/>
   <style type="text/css">
-    p { background: lime; color: green; }
+    p { background: lime; color: green; margin-top: 1em; }
   </style>
  </head>
  <body>
diff --git a/css/css-style-attr/style-attr-urls-003.xht b/css/css-style-attr/style-attr-urls-003.xht
index 8937904..d1326f8 100644
--- a/css/css-style-attr/style-attr-urls-003.xht
+++ b/css/css-style-attr/style-attr-urls-003.xht
@@ -1,28 +1,23 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
-  <title>CSS Test: URLs in style attributes (with xml:base and &lt;base&gt;)</title>
-  <base href="support/support/"/>
+  <title>CSS Test: URLs in style attributes (with xml:base)</title>
   <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"/>
   <link rel="reviewer" title="Arron Eicholz" href="mailto:Arron.Eicholz@microsoft.com"/>
-  <link rel="match" href="reference/ref-green-on-green2.xht" xml:base="../../"/>
-  <link rel="help" href="http://www.w3.org/TR/css-style-attr/#interpret"/>
-  <link rel="help" href="http://www.w3.org/TR/xmlbase"/>
+  <link rel="match" href="reference/ref-green-on-green.xht"/>
+  <link rel="help" href="https://drafts.csswg.org/css-style-attr/#interpret"/>
   <meta http-equiv="Content-Style-Type" content="text/css" />
   <meta name="flags" content="image nonHTML" />
-  <meta name="assert" content="In the presence of xml:base URL manipulation, URLs in style attributes are relative to the base of the element."/>
+  <meta name="assert" content="xml:base is ignored when resolving URLs in style attributes."/>
+  <!-- This test originally asserted that xml:base works, and xml:base is mentioned in
+       https://www.w3.org/TR/2013/REC-css-style-attr-20131107/. Support for xml:base
+       has been removed from browsers and it's no longer mentioned in the spec. -->
   <style type="text/css"><![CDATA[
-    p { background: red; color: green; }
-    img { background: red; color: white; vertical-align: middle; }
+    p { background-color: lime; color: green; }
   ]]></style>
  </head>
  <body>
-  <p style="background-image: url(swatch-lime.png)" xml:base="../">
+  <p style="background-image: url(swatch-red.png)" xml:base="support/">
     This sentence must be green on a green background.
-    <img src="support/swatch-lime.png" alt="[Your UA does not support xml:base. This test is therefore Not Applicable.]"/>
-  </p>
-  <p xml:base="../" style="background-image: url(swatch-lime.png)">
-    This sentence must be green on a green background.
-    <img src="support/swatch-lime.png" alt="[Your UA does not support xml:base. This test is therefore Not Applicable.]"/>
   </p>
  </body>
 </html>
diff --git a/css/css-syntax/declarations-trim-whitespace.html b/css/css-syntax/declarations-trim-whitespace.html
new file mode 100644
index 0000000..a7d69d1
--- /dev/null
+++ b/css/css-syntax/declarations-trim-whitespace.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Declarations trim whitespace from their beginning/ending</title>
+<meta name="author" title="Tab Atkins-Bittner">
+<link rel=help href="https://drafts.csswg.org/css-syntax/#consume-declaration">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+    <div id="log"></div>
+    <style id="style-el">
+    #foo {
+        --foo-1:bar;
+        --foo-2: bar;
+        --foo-3:bar ;
+        --foo-4: bar ;
+        --foo-5: bar !important;
+        --foo-6: bar !important ;
+        --foo-7:bar!important;
+        --foo-8:bar!important ;
+        --foo-9:bar
+    }
+    </style>
+    <p id=foo>foo</p>
+    <script>
+    let stylesheet = getComputedStyle(document.querySelector("#foo"));
+    let canonical = "bar";
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-1"), canonical);
+    }, "--foo-1:bar;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-2"), canonical);
+    }, "--foo-2: bar;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-3"), canonical);
+    }, "--foo-3:bar ;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-4"), canonical);
+    }, "--foo-4: bar ;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-5"), canonical);
+    }, "--foo-5: bar !important;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-6"), canonical);
+    }, "--foo-6: bar !important ;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-7"), canonical);
+    }, "--foo-7:bar!important;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-8"), canonical);
+    }, "--foo-8:bar!important ;");
+    test(function() {
+        assert_equals(stylesheet.getPropertyValue("--foo-9"), canonical);
+    }, "--foo-9:bar (then ws until end of rule)");
+    </script>
diff --git a/css/css-syntax/unicode-range-selector.html b/css/css-syntax/unicode-range-selector.html
new file mode 100644
index 0000000..db09540
--- /dev/null
+++ b/css/css-syntax/unicode-range-selector.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Unicode range is not a token, and doesn't cause confusion in selectors</title>
+<link name="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-syntax/#urange">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  a { color: red }
+  u+a { color: green }
+</style>
+<u></u><a></a>
+<script>
+  test(function() {
+    assert_equals(
+      getComputedStyle(document.querySelector("a")).color,
+      "rgb(0, 128, 0)"
+    );
+  }, "Unicode range is not a token, and doesn't cause confusion in selectors");
+</script>
diff --git a/css/css-tables/anonymous-table-ws-001-ref.html b/css/css-tables/anonymous-table-ws-001-ref.html
new file mode 100644
index 0000000..4429239
--- /dev/null
+++ b/css/css-tables/anonymous-table-ws-001-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<title>CSS Reference</title>
+<style>
+div { font: 16px monospace; }
+</style>
+<p>Test passes if there is a space between the "a" and "b".</p>
+<div>a b</div>
diff --git a/css/css-tables/anonymous-table-ws-001.html b/css/css-tables/anonymous-table-ws-001.html
new file mode 100644
index 0000000..99d2cfc
--- /dev/null
+++ b/css/css-tables/anonymous-table-ws-001.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>CSS Test: White space should not be removed between elements for which a single anonymous table cell is generated</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="match" href="anonymous-table-ws-001-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-tables/#fixup-algorithm">
+<style>
+div { display: table; font: 16px monospace; }
+</style>
+<p>Test passes if there is a space between the "a" and "b".</p>
+<div>
+  <span>a</span> <span>b</span>
+</div>
diff --git a/css/css-tables/fixup-dynamic-anonymous-inline-table-001.html b/css/css-tables/fixup-dynamic-anonymous-inline-table-001.html
new file mode 100644
index 0000000..9bf7b4c
--- /dev/null
+++ b/css/css-tables/fixup-dynamic-anonymous-inline-table-001.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>CSS Test: CSS Tables fixup merge anonymous inline table siblings (row-group + row-group)</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<link rel="help" href="https://drafts.csswg.org/css-tables/#fixup-algorithm">
+<style>
+  .group {
+    display: table-row-group;
+  }
+  .cell {
+    display: table-cell;
+  }
+  .filler {
+    width: 100px;
+    height: 50px;
+    background-color: green;
+  }
+</style>
+<p>Test passes if there is a filled green square.</p>
+<span>
+  <span class="group">
+    <span class="cell">
+      <div class="filler"></div>
+    </span>
+  </span>
+  <span id="rm">Remove me</span>
+  <span class="group">
+    <span class="cell">
+      <div class="filler"></div>
+    </span>
+  </span>
+</span>
+<script>
+  rm.offsetTop;
+  rm.remove();
+</script>
diff --git a/css/css-tables/fixup-dynamic-anonymous-inline-table-002.html b/css/css-tables/fixup-dynamic-anonymous-inline-table-002.html
new file mode 100644
index 0000000..9f3a2ff
--- /dev/null
+++ b/css/css-tables/fixup-dynamic-anonymous-inline-table-002.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>CSS Test: CSS Tables fixup merge anonymous inline table siblings (cell + row)</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<link rel="help" href="https://drafts.csswg.org/css-tables/#fixup-algorithm">
+<style>
+  .row {
+    display: table-row;
+  }
+  .cell {
+    display: table-cell;
+  }
+  .filler {
+    width: 100px;
+    height: 50px;
+    background-color: green;
+  }
+</style>
+<p>Test passes if there is a filled green square.</p>
+<span>
+  <span class="cell">
+    <div class="filler"></div>
+  </span>
+  <span id="rm">Remove me</span>
+  <span class="row">
+    <span class="cell">
+      <div class="filler"></div>
+    </span>
+  </span>
+</span>
+<script>
+  rm.offsetTop;
+  rm.remove();
+</script>
diff --git a/css/css-tables/fixup-dynamic-anonymous-inline-table-003.html b/css/css-tables/fixup-dynamic-anonymous-inline-table-003.html
new file mode 100644
index 0000000..42b7f9a
--- /dev/null
+++ b/css/css-tables/fixup-dynamic-anonymous-inline-table-003.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>CSS Test: CSS Tables fixup merge anonymous inline table siblings (cell + cell)</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<link rel="help" href="https://drafts.csswg.org/css-tables/#fixup-algorithm">
+<style>
+  .cell {
+    display: table-cell;
+  }
+  .filler {
+    width: 50px;
+    height: 100px;
+    background-color: green;
+  }
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div style="width:100px">
+  <span>
+    <span class="cell">
+      <div class="filler"></div>
+    </span>
+    <span id="rm">Remove me</span>
+    <span class="cell">
+      <div class="filler"></div>
+    </span>
+  </span>
+</div>
+<script>
+  rm.offsetTop;
+  rm.remove();
+</script>
diff --git a/css/css-tables/fixup-dynamic-anonymous-table-001.html b/css/css-tables/fixup-dynamic-anonymous-table-001.html
new file mode 100644
index 0000000..d9dbc51
--- /dev/null
+++ b/css/css-tables/fixup-dynamic-anonymous-table-001.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>CSS Test: CSS Tables fixup merge anonymous table siblings (cell + cell)</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<link rel="help" href="https://drafts.csswg.org/css-tables/#fixup-algorithm">
+<style>
+  .cell {
+    display: table-cell;
+    width: 50px;
+    height: 100px;
+    background-color: green;
+  }
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div>
+  <span class="cell"></span>
+  <span id="rm">Remove me</span>
+  <span class="cell"></span>
+</div>
+<script>
+  rm.offsetTop;
+  rm.remove();
+</script>
diff --git a/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-001.html b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-001.html
new file mode 100644
index 0000000..81698a1
--- /dev/null
+++ b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-001.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#height-distribution-algorithm">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=708345" />
+<meta name="flags" content="" />
+<meta name="assert" content="height of rows in thead are increased to match table height" />
+<title>
+all row groups receive extra height distribution
+</title>
+
+<style>
+table {
+  background: green;
+  border-collapse:collapse;
+}
+
+td {
+  padding:0px;
+}
+
+#redSquare {
+  height: 100px;
+  width: 100px;
+  background-color: red;
+  position: absolute;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+<div id="redSquare"></div>
+<table style="height:100px">
+  <thead>
+    <tr>
+      <td><div style="display:inline-block; width:100px;">
+      </div></td>
+    </tr>
+  </thead>
+</table>
diff --git a/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-002.html b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-002.html
new file mode 100644
index 0000000..0bf8a89
--- /dev/null
+++ b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-002.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#height-distribution-algorithm">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=708345" />
+<meta name="flags" content="" />
+<meta name="assert" content="height of rows in tbody are increased to match table height" />
+<title>
+all row groups receive extra height distribution
+</title>
+
+<style>
+table {
+  background: green;
+  border-collapse:collapse;
+}
+
+td {
+  padding:0px;
+}
+
+#redSquare {
+  height: 100px;
+  width: 100px;
+  background-color: red;
+  position: absolute;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+<div id="redSquare"></div>
+<table style="height:100px">
+  <tbody>
+    <tr>
+      <td><div style="display:inline-block; width:100px;">
+      </div></td>
+    </tr>
+  </tbody>
+</table>
diff --git a/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-003.html b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-003.html
new file mode 100644
index 0000000..285017b
--- /dev/null
+++ b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-003.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#height-distribution-algorithm">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=708345" />
+<meta name="flags" content="" />
+<meta name="assert" content="All rows of equal intrinsic height should be increased the same amount, regardless of which group they are in, tbody+tbody case" />
+<title>
+all row groups receive extra height distribution
+</title>
+
+<style>
+table {
+  border-collapse: collapse;
+}
+
+td {
+  padding: 0px;
+}
+
+td div {
+  width: 100px;
+  height:10px;
+}
+
+tbody {
+  outline: 2px solid lightblue;
+}
+
+</style>
+
+<table id="theTable" style="height:100px">
+  <tbody data-expected-height=50>
+    <tr>
+      <td><div></div></td>
+    </tr>
+  </tbody>
+  <tbody data-expected-height=50>
+    <tr>
+      <td><div></div></td>
+    </tr>
+  </tbody>
+</table>
+
+<script>
+checkLayout('#theTable')
+</script>
diff --git a/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-004.html b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-004.html
new file mode 100644
index 0000000..0fcd48e
--- /dev/null
+++ b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-004.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#height-distribution-algorithm">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=708345" />
+<meta name="flags" content="" />
+<meta name="assert" content="All rows of equal intrinsic height should be increased the same amount, regardless of which group they are in, thead+tbody case" />
+<title>
+all row groups receive extra height distribution
+</title>
+
+<style>
+table {
+  border-collapse: collapse;
+}
+
+td {
+  padding: 0px;
+}
+
+td div {
+  width: 100px;
+  height:10px;
+}
+
+thead, tbody {
+  outline: 2px solid lightblue;
+}
+
+</style>
+
+<table id="theTable" style="height:100px">
+  <thead data-expected-height=50>
+    <tr>
+      <td><div></div></td>
+    </tr>
+  </thead>
+  <tbody data-expected-height=50>
+    <tr>
+      <td><div></div></td>
+    </tr>
+  </tbody>
+</table>
+
+<script>
+checkLayout('#theTable')
+</script>
diff --git a/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-005.html b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-005.html
new file mode 100644
index 0000000..8d0411b
--- /dev/null
+++ b/css/css-tables/height-distribution/extra-height-given-to-all-row-groups-005.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#height-distribution-algorithm">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=708345" />
+<meta name="flags" content="" />
+<meta name="assert" content="height of rows in tfoot are increased to match table height" />
+<title>
+all row groups receive extra height distribution
+</title>
+
+<style>
+table {
+  background: green;
+  border-collapse:collapse;
+}
+
+td {
+  padding:0px;
+}
+
+#redSquare {
+  height: 100px;
+  width: 100px;
+  background-color: red;
+  position: absolute;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+<div id="redSquare"></div>
+<table style="height:100px">
+  <tfoot>
+    <tr>
+      <td><div style="display:inline-block; width:100px;">
+      </div></td>
+    </tr>
+  </tfoot>
+</table>
diff --git a/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children-002-ref.html b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children-002-ref.html
new file mode 100644
index 0000000..37fe964
--- /dev/null
+++ b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children-002-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference: Percentage sizing of table cell children with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.table {
+  display: block;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+}
+
+.td {
+  background: cyan;
+  overflow: scroll;
+  padding: 5px 15px 10px 20px;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  height: 100px;
+  box-sizing: border-box;
+}
+
+.child {
+  background: yellow;
+  width: 100%;
+  height: 100%;
+}
+</style>
+
+<p>The test passes if you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="table">
+  <div class="td">
+    <div class="child"></div>
+  </div>
+</div>
diff --git a/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children-002.html b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children-002.html
new file mode 100644
index 0000000..814aa27
--- /dev/null
+++ b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children-002.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Table Test: Percentage sizing of table cell children with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#content-measure">
+<link rel="match" href="percentage-sizing-of-table-cell-children-002-ref.html">
+<meta name="assert" content="Checks that table cell children resolve properly their percentage sizes, even when the table cell has margin, border, padding and scrollbar.">
+<style>
+.table {
+  display: table;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+}
+
+.td {
+  display: table-cell;
+  background: cyan;
+  overflow: scroll;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+}
+
+.child {
+  background: yellow;
+  width: 100%;
+  height: 100%;
+}
+</style>
+
+<p>The test passes if you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="table">
+  <div class="td">
+    <div class="child"></div>
+  </div>
+</div>
diff --git a/css/css-tables/height-distribution/percentage-sizing-of-table-cell-replaced-children-001-ref.html b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-replaced-children-001-ref.html
new file mode 100644
index 0000000..3919e1e
--- /dev/null
+++ b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-replaced-children-001-ref.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference: Percentage sizing of table cell children with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.table {
+  display: block;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+}
+
+.td {
+  background: cyan;
+  overflow: scroll;
+  padding: 5px 15px 10px 20px;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+  height: 100px;
+  box-sizing: border-box;
+}
+
+img {
+  display: block;
+  background: yellow;
+  width: 100%;
+  height: 100%;
+}
+</style>
+
+<p>The test passes if you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="table">
+  <div class="td">
+    <img />
+  </div>
+</div>
diff --git a/css/css-tables/height-distribution/percentage-sizing-of-table-cell-replaced-children-001.html b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-replaced-children-001.html
new file mode 100644
index 0000000..2d36738
--- /dev/null
+++ b/css/css-tables/height-distribution/percentage-sizing-of-table-cell-replaced-children-001.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Table Test: Percentage sizing of table cell replaced children with margin, border, padding and scrollbar</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#content-measure">
+<link rel="match" href="percentage-sizing-of-table-cell-replaced-children-001-ref.html">
+<meta name="assert" content="Checks that table cell replaced children resolve properly their percentage sizes, even when the table cell has margin, border, padding and scrollbar.">
+<style>
+.table {
+  display: table;
+  border: solid 5px black;
+  width: 150px;
+  height: 100px;
+}
+
+.td {
+  display: table-cell;
+  background: cyan;
+  overflow: scroll;
+  margin: 1px 2px 3px 4px;
+  padding: 5px 15px 10px 20px;
+  border: solid magenta;
+  border-width: 12px 9px 6px 3px;
+}
+
+img {
+  display: block;
+  background: yellow;
+  width: 100%;
+  height: 100%;
+}
+</style>
+
+<p>The test passes if you see scrollbars but there's no overflow, so you cannot actually scroll.</p>
+
+<div class="table">
+  <div class="td">
+    <img />
+  </div>
+</div>
diff --git a/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-ref.html b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-ref.html
new file mode 100644
index 0000000..02f0f54
--- /dev/null
+++ b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+Passes if there is an unbroken rectangular border.
+<style>td { width: 20px; height: 20px; padding: 2px }</style>
+<table style="position: absolute; top: 30.3px; border: 2px solid black; border-collapse: collapse">
+  <tr>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+  </tr>
+</table>
diff --git a/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-vertical-rl-ref.html b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-vertical-rl-ref.html
new file mode 100644
index 0000000..9784962
--- /dev/null
+++ b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-vertical-rl-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+Passes if there is an unbroken rectangular border.
+<style>td { width: 20px; height: 20px; padding: 2px }</style>
+<table style="position: absolute; top: 30.3px; border: 2px solid black;
+              border-collapse: collapse; writing-mode: vertical-rl">
+  <tr>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+  </tr>
+</table>
diff --git a/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-vertical-rl.html b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-vertical-rl.html
new file mode 100644
index 0000000..48edbde
--- /dev/null
+++ b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row-vertical-rl.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#height-distribution-algorithm">
+<link rel="match" href="td-different-subpixel-padding-in-same-row-vertical-rl-ref.html">
+Passes if there is an unbroken rectangular border.
+<style>td { width: 20px; height: 20px }</style>
+<table style="position: absolute; top: 30.3px; border: 2px solid black;
+              border-collapse: collapse; writing-mode: vertical-rl">
+  <tr>
+    <td style="padding: 2px 1px"></td>
+    <td style="padding: 2px 1.2px"></td>
+    <td style="padding: 2px 1.5px"></td>
+    <td style="padding: 2px 1.7px"></td>
+    <td style="padding: 2px"></td>
+  </tr>
+</table>
diff --git a/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row.html b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row.html
new file mode 100644
index 0000000..a7fe908
--- /dev/null
+++ b/css/css-tables/height-distribution/td-different-subpixel-padding-in-same-row.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#height-distribution-algorithm">
+<link rel="match" href="td-different-subpixel-padding-in-same-row-ref.html">
+Passes if there is an unbroken rectangular border.
+<style>td { width: 20px; height: 20px }</style>
+<table style="position: absolute; top: 30.3px; border: 2px solid black; border-collapse: collapse">
+  <tr>
+    <td style="padding: 1px 2px"></td>
+    <td style="padding: 1.2px 2px"></td>
+    <td style="padding: 1.5px 2px"></td>
+    <td style="padding: 1.7px 2px"></td>
+    <td style="padding: 2px"></td>
+  </tr>
+</table>
diff --git a/css/css-tables/internal-containing-block-001.html b/css/css-tables/internal-containing-block-001.html
new file mode 100644
index 0000000..a874548
--- /dev/null
+++ b/css/css-tables/internal-containing-block-001.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#abspos-boxes-in-table-internal">
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#valdef-position-absolute">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=798164" />
+<meta name="flags" content="" />
+<meta name="assert" content="A table-row with position:relative establishes a
+containing block for a position:absolute descendent of a table-cell, and the
+descendent is positioned correctly." />
+<title>
+relpos table-row establishes containing block for abspos
+</title>
+
+<style>
+tr {
+  position: relative;
+}
+
+div.abspos {
+  position: absolute;
+  height: 50px;
+  width: 100px;
+  background: green;
+}
+
+div.static {
+  height: 50px;
+  width: 100px;
+  background:red;
+}
+
+td, tr {
+  margin:0px;
+  padding:0px;
+}
+
+table {
+  border-collapse:collapse;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+
+<table>
+  <tr>
+    <td>
+      <div class="abspos"></div>
+      <div class="static"></div>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <div class="abspos"></div>
+      <div class="static"></div>
+    </td>
+  </tr>
+</table>
diff --git a/css/css-tables/table-has-box-sizing-border-box-001.html b/css/css-tables/table-has-box-sizing-border-box-001.html
new file mode 100644
index 0000000..e3cddd1
--- /dev/null
+++ b/css/css-tables/table-has-box-sizing-border-box-001.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#mapping">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=598134" />
+<meta name="flags" content="" />
+<meta name="assert" content="default box-sizing for <table> is border-box" />
+<title>
+UA stylesheet, &lt;table>, box-sizing
+</title>
+
+<style>
+#table {
+  width: 100px;
+  height: 100%;
+  background: green;
+  padding-bottom: 35px;
+}
+</style>
+
+<p>Test passes if there is a filled green square.
+</p>
+<div style="height:100px">
+  <table id="table"></table>
+</div>
diff --git a/css/css-tables/table-has-box-sizing-border-box-002.html b/css/css-tables/table-has-box-sizing-border-box-002.html
new file mode 100644
index 0000000..910eb61
--- /dev/null
+++ b/css/css-tables/table-has-box-sizing-border-box-002.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#mapping">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=598134" />
+<meta name="flags" content="" />
+<meta name="assert" content="default <table> box-sizing:border-box is not applied to non-table elements that have display:table" />
+<title>
+UA stylesheet, &lt;table>, box-sizing
+</title>
+
+<style>
+#table {
+  display: table;
+  width: 100px;
+  height: 100%;
+  background: green;
+  padding-bottom: 35px;
+}
+
+#redSquare {
+  height: 100px;
+  width: 100px;
+  background-color: red;
+  position: absolute;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+<div id="redSquare"></div>
+<div style="height:65px">
+  <div id="table"></div>
+</div>
diff --git a/css/css-tables/table-model-fixup-2.html b/css/css-tables/table-model-fixup-2.html
index 5436451..737a8d7 100644
--- a/css/css-tables/table-model-fixup-2.html
+++ b/css/css-tables/table-model-fixup-2.html
@@ -79,8 +79,8 @@
     <p>Replaced elements inside a table cannot be <mark>table-row</mark> and are considered <mark>block</mark> -- img elements</p>
     <div>
         <x-table style="width: 400px">
-            <x-tr><x-td><img block src="http://w3.org/favicon.ico" target=a /></x-tr>
-            <img src="http://w3.org/favicon.ico" table-row target=b />
+            <x-tr><x-td><img block src="../support/blue32x32.ico" target=a /></x-tr>
+            <img src="../support/blue32x32.ico" table-row target=b />
         </x-table>
     </div>
     <script>
@@ -109,8 +109,8 @@
     <p>Replaced elements inside a table cannot be <mark>table-column</mark> and are considered <mark>inline</mark> -- img elements</p>
     <div>
         <x-table style="width: 400px">
-            <x-tr><x-td><img inline src="http://w3.org/favicon.ico" target=a /></x-tr>
-            <img src="http://w3.org/favicon.ico" table-column target=b />
+            <x-tr><x-td><img inline src="../support/blue32x32.ico" target=a /></x-tr>
+            <img src="../support/blue32x32.ico" table-column target=b />
         </x-table>
     </div>
     <script>
@@ -139,8 +139,8 @@
     <p>Replaced elements inside a table cannot be <mark>table-cell</mark> and are considered <mark>inline</mark> -- img elements</p>
     <div>
         <x-table style="width: 400px">
-            <x-tr><x-td><img inline src="http://w3.org/favicon.ico" target=a /></x-tr>
-            <x-tr><img src="http://w3.org/favicon.ico" table-cell target=b /></x-tr>
+            <x-tr><x-td><img inline src="../support/blue32x32.ico" target=a /></x-tr>
+            <x-tr><img src="../support/blue32x32.ico" table-cell target=b /></x-tr>
         </x-table>
     </div>
     <script>
@@ -192,8 +192,8 @@
     <p>Both images should share the same line:</p>
     <p>Replaced elements outside a table cannot be <mark>inline-table</mark> and are considered <mark>inline</mark> -- img elements</p>
     <div>
-        <img src="http://w3.org/favicon.ico" inline-table target=a />
-        <img src="http://w3.org/favicon.ico" inline-table target=b />
+        <img src="../support/blue32x32.ico" inline-table target=a />
+        <img src="../support/blue32x32.ico" inline-table target=b />
     </div>
     <script>
 
@@ -244,8 +244,8 @@
     <p>Both images should stand on their own line:</p>
     <p>Replaced elements outside a table cannot be <mark>table</mark> and are considered <mark>block</mark> -- img elements</p>
     <div>
-        <img src="http://w3.org/favicon.ico" table target=a />
-        <img src="http://w3.org/favicon.ico" table target=b />
+        <img src="../support/blue32x32.ico" table target=a />
+        <img src="../support/blue32x32.ico" table target=b />
     </div>
     <script>
 
@@ -296,8 +296,8 @@
     <p>Both images should stand on their own line:</p>
     <p>Replaced elements outside a table cannot be <mark>table-row</mark> and are considered <mark>block</mark> -- img elements</p>
     <div>
-        <img src="http://w3.org/favicon.ico" table-row target=a />
-        <img src="http://w3.org/favicon.ico" table-row target=b />
+        <img src="../support/blue32x32.ico" table-row target=a />
+        <img src="../support/blue32x32.ico" table-row target=b />
     </div>
     <script>
 
@@ -348,8 +348,8 @@
     <p>Both images should stand on their own line:</p>
     <p>Replaced elements outside a table cannot be <mark>table-row-group</mark> and are considered <mark>block</mark> -- img elements</p>
     <div>
-        <img src="http://w3.org/favicon.ico" table-row-group target=a />
-        <img src="http://w3.org/favicon.ico" table-row-group target=b />
+        <img src="../support/blue32x32.ico" table-row-group target=a />
+        <img src="../support/blue32x32.ico" table-row-group target=b />
     </div>
     <script>
 
@@ -400,8 +400,8 @@
     <p>Both images should share the same line:</p>
     <p>Replaced elements outside a table cannot be <mark>table-column</mark> and are considered <mark>inline</mark> -- img elements</p>
     <div>
-        <img src="http://w3.org/favicon.ico" table-column target=a />
-        <img src="http://w3.org/favicon.ico" table-column target=b />
+        <img src="../support/blue32x32.ico" table-column target=a />
+        <img src="../support/blue32x32.ico" table-column target=b />
     </div>
     <script>
 
@@ -452,8 +452,8 @@
     <p>Both images should share the same line:</p>
     <p>Replaced elements outside a table cannot be <mark>table-cell</mark> and are considered <mark>inline</mark> -- img elements</p>
     <div>
-        <img src="http://w3.org/favicon.ico" table-cell target=a />
-        <img src="http://w3.org/favicon.ico" table-cell target=b />
+        <img src="../support/blue32x32.ico" table-cell target=a />
+        <img src="../support/blue32x32.ico" table-cell target=b />
     </div>
     <script>
 
diff --git a/css/css-tables/width-distribution/td-with-subpixel-padding-vertical-rl.html b/css/css-tables/width-distribution/td-with-subpixel-padding-vertical-rl.html
new file mode 100644
index 0000000..18ed2dd
--- /dev/null
+++ b/css/css-tables/width-distribution/td-with-subpixel-padding-vertical-rl.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://drafts.csswg.org/css-tables/#width-distribution" />
+<style>
+td div {
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  background: blue;
+}
+</style>
+Passes if each column (logical row) has two blue squares.
+<div style="writing-mode: vertical-rl">
+<table>
+  <tr>
+    <td class='target' style="padding: 0.8px 0"><div></div> <div></div></td>
+    <td style="padding: 0.8px 0; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 1px 0"><div></div> <div></div></td>
+    <td style="padding: 1px 0; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 1.3px 0"><div></div> <div></div></td>
+    <td style="padding: 1.3px 0; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 1.5px 0"><div></div> <div></div></td>
+    <td style="padding: 1.5px 0; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 1.7px 0"><div></div> <div></div></td>
+    <td style="padding: 1.7px 0; width: 30px"></td>
+  </tr>
+</table>
+</div>
+<script>
+test(() => {
+  var targets = document.getElementsByClassName('target');
+  for (var i = 0; i < targets.length; ++i) {
+    var divs = targets[i].getElementsByTagName('div');
+    assert_equals(divs.length, 2);
+    assert_equals(divs[0].offsetLeft, divs[1].offsetLeft, 'Contents of td.target[' + i + '] should not wrap');
+  }
+});
+</script>
diff --git a/css/css-tables/width-distribution/td-with-subpixel-padding.html b/css/css-tables/width-distribution/td-with-subpixel-padding.html
new file mode 100644
index 0000000..ff04077
--- /dev/null
+++ b/css/css-tables/width-distribution/td-with-subpixel-padding.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://drafts.csswg.org/css-tables/#width-distribution" />
+<style>
+div {
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  background: blue;
+}
+</style>
+Passes if each row has two blue squares.
+<table>
+  <tr>
+    <td class='target' style="padding: 0 0.8px"><div></div> <div></div></td>
+    <td style="padding: 0 0.8px; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 0 1px"><div></div> <div></div></td>
+    <td style="padding: 0 1px; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 0 1.3px"><div></div> <div></div></td>
+    <td style="padding: 0 1.3px; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 0 1.5px"><div></div> <div></div></td>
+    <td style="padding: 0 1.5px; width: 30px"></td>
+  </tr>
+</table>
+<table>
+  <tr>
+    <td class='target' style="padding: 0 1.7px"><div></div> <div></div></td>
+    <td style="padding: 0 1.7px; width: 30px"></td>
+  </tr>
+</table>
+<script>
+test(() => {
+  var targets = document.getElementsByClassName('target');
+  for (var i = 0; i < targets.length; ++i) {
+    var divs = targets[i].getElementsByTagName('div');
+    assert_equals(divs.length, 2);
+    assert_equals(divs[0].offsetTop, divs[1].offsetTop, 'Contents of td.target[' + i + '] should not wrap');
+  }
+});
+</script>
diff --git a/css/css-tables/zero-rowspan-001-ref.html b/css/css-tables/zero-rowspan-001-ref.html
new file mode 100644
index 0000000..4f1dbdf
--- /dev/null
+++ b/css/css-tables/zero-rowspan-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>CSS Test: Reference Test</title>
+<link rel="author" title="Robert Hogan" href="mailto:robhogan@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/tables.html#attributes-common-to-td-and-th-elements">
+<table>
+  <tr><td rowspan="4">Foo</td></tr>
+  <tr><td>Foo</td></tr>
+  <tr><td>Foo</td></tr>
+  <tr><td>Foo</td></tr>
+</table>
+
diff --git a/css/css-tables/zero-rowspan-001.html b/css/css-tables/zero-rowspan-001.html
new file mode 100644
index 0000000..bef5b94
--- /dev/null
+++ b/css/css-tables/zero-rowspan-001.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>CSS Test: Overflow clipping in cells that span columns</title>
+<link rel="author" title="Robert Hogan" href="mailto:robhogan@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/tables.html#attributes-common-to-td-and-th-elements">
+<link rel="match" href="zero-rowspan-001-ref.html">
+<table>
+  <tr><td rowspan="0">Foo</td></tr>
+  <tr><td>Foo</td></tr>
+  <tr><td>Foo</td></tr>
+  <tr><td>Foo</td></tr>
+</table>
+
diff --git a/css/css-tables/zero-rowspan-002-ref.html b/css/css-tables/zero-rowspan-002-ref.html
new file mode 100644
index 0000000..3ccac78
--- /dev/null
+++ b/css/css-tables/zero-rowspan-002-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>CSS Test: Reference Test</title>
+<link rel="author" title="Robert Hogan" href="mailto:robhogan@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/tables.html#attributes-common-to-td-and-th-elements">
+<table>
+  <tr><td rowspan="3">1</td><td>2</td><td>3</td></tr>
+  <tr><td>1</td><td rowspan="2">2</td><td>3</td></tr>
+  <tr><td>1</td><td>2</td><td>3</td></tr>
+</table>
diff --git a/css/css-tables/zero-rowspan-002.html b/css/css-tables/zero-rowspan-002.html
new file mode 100644
index 0000000..7a79fc3
--- /dev/null
+++ b/css/css-tables/zero-rowspan-002.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>CSS Test: Overflow clipping in cells that span columns</title>
+<link rel="author" title="Robert Hogan" href="mailto:robhogan@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/tables.html#attributes-common-to-td-and-th-elements">
+<link rel="match" href="zero-rowspan-002-ref.html">
+<table>
+  <tr><td rowspan="0">1</td><td>2</td><td>3</td></tr>
+  <tr><td>1</td><td rowspan="0">2</td><td>3</td></tr>
+  <tr><td>1</td><td>2</td><td>3</td></tr>
+</table>
+
diff --git a/css/css-text-decor/line-through-vertical.html b/css/css-text-decor/line-through-vertical.html
new file mode 100644
index 0000000..633c5c0
--- /dev/null
+++ b/css/css-text-decor/line-through-vertical.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://www.w3.org/TR/css-text-decor-3/#text-decoration-line">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#propdef-writing-mode">
+<link rel="match" href="reference/line-through-vertical-ref.html">
+<style>
+div {
+  writing-mode: vertical-rl;
+  text-decoration: line-through;
+  font-family: Times;
+  font-size: 50px;
+}
+</style>
+<div>
+  <span lang="ja">ABC</span>
+  <span lang="en">ABC</span>
+</div>
diff --git a/css/css-text-decor/reference/line-through-vertical-ref.html b/css/css-text-decor/reference/line-through-vertical-ref.html
new file mode 100644
index 0000000..9795127
--- /dev/null
+++ b/css/css-text-decor/reference/line-through-vertical-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+div {
+  writing-mode: vertical-rl;
+  text-decoration: line-through;
+  font-family: Times;
+  font-size: 50px;
+}
+</style>
+<div>
+  <span lang="en">ABC</span>
+  <span lang="en">ABC</span>
+</div>
diff --git a/css/css-text-decor/reference/text-decoration-color-recalc-ref.html b/css/css-text-decor/reference/text-decoration-color-recalc-ref.html
new file mode 100644
index 0000000..7bc5573
--- /dev/null
+++ b/css/css-text-decor/reference/text-decoration-color-recalc-ref.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<style>
+    div {
+        font-size: 50px;
+        text-decoration: underline solid green;
+    }
+</style>
+<p>Test that changes in text-decoration-color are recalculated correctly. PASS
+if the text below has a solid green underline, and no red.</p>
+<div id="target">
+    Filler text
+</div>
diff --git a/css/css-text-decor/reference/text-decoration-color-ref.html b/css/css-text-decor/reference/text-decoration-color-ref.html
new file mode 100644
index 0000000..33272cb
--- /dev/null
+++ b/css/css-text-decor/reference/text-decoration-color-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: CSS3 text-decoration-color</title>
+        <style>
+            .underline {
+                text-decoration: underline;
+            }
+            .overline {
+                text-decoration: overline;
+            }
+            .line-through {
+                text-decoration: line-through;
+            }
+            .black-fill {
+                -webkit-text-fill-color: black;
+            }
+            .transparent-fill {
+                -webkit-text-fill-color: transparent;
+                -webkit-text-stroke-width: 1px;
+                -webkit-text-stroke-color: black;
+            }
+        </style>
+    </head>
+    <body>
+        <h3>Each line of this test should match its text decoration color description:</h3>
+        <div class="underline" style="color: blue;"><span style="-webkit-text-fill-color: gray;">Gray text with blue underline</span></div><br/>
+        <div class="overline" style="color: black;"><span style="-webkit-text-fill-color: green;">Green text with black overline</span></div><br/>
+        <div class="line-through" style="color: gold;"><span style="-webkit-text-fill-color: black;">Black text with gold line-through</span></div><br/>
+        <div class="underline" style="color: blue;">
+            <span class="overline" style="color: gray;">
+                <span class="line-through" style="color: green;">
+                    <span class="black-fill">Black text with blue underline, gray overline and green line-through</span>
+                </span>
+            </span>
+        </div><br/>
+        <div class="line-through" style="color: green;">
+            <sub class="overline" style="color: gray;"><span class="black-fill">subscript text</span></sub>
+            <sup class="underline" style="color: blue;"><span class="black-fill">superscript text</span></sup>
+        </div><br/>
+        <div class="underline" style="color: green;"><span class="transparent-fill">Transparent fill with black stroke text and green underline</span></div><br/>
+    </body>
+</html>
diff --git a/css/css-text-decor/reference/text-decoration-line-010-ref.xht b/css/css-text-decor/reference/text-decoration-line-010-ref.xht
index 6f88d9b..6bcba28 100644
--- a/css/css-text-decor/reference/text-decoration-line-010-ref.xht
+++ b/css/css-text-decor/reference/text-decoration-line-010-ref.xht
@@ -10,7 +10,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("../support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/reference/text-decoration-line-011-ref.xht b/css/css-text-decor/reference/text-decoration-line-011-ref.xht
index d148514..23c3813 100644
--- a/css/css-text-decor/reference/text-decoration-line-011-ref.xht
+++ b/css/css-text-decor/reference/text-decoration-line-011-ref.xht
@@ -10,7 +10,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("../support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/reference/text-decoration-line-012-ref.xht b/css/css-text-decor/reference/text-decoration-line-012-ref.xht
index dfccd65..13eb7d5 100644
--- a/css/css-text-decor/reference/text-decoration-line-012-ref.xht
+++ b/css/css-text-decor/reference/text-decoration-line-012-ref.xht
@@ -10,7 +10,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("../support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/reference/text-decoration-line-013-ref.xht b/css/css-text-decor/reference/text-decoration-line-013-ref.xht
index 36be520..8fb8eaa 100644
--- a/css/css-text-decor/reference/text-decoration-line-013-ref.xht
+++ b/css/css-text-decor/reference/text-decoration-line-013-ref.xht
@@ -10,7 +10,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("../support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/reference/text-decoration-line-recalc-ref.html b/css/css-text-decor/reference/text-decoration-line-recalc-ref.html
new file mode 100644
index 0000000..92baa6b
--- /dev/null
+++ b/css/css-text-decor/reference/text-decoration-line-recalc-ref.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<style>
+    div {
+        font-size: 50px;
+        text-decoration: overline solid green;
+    }
+</style>
+<p>Test that changes in text-decoration-line are recalculated correctly. PASS
+if the text below has a solid green overline, and no underline.</p>
+<div id="target">
+    Filler text
+</div>
\ No newline at end of file
diff --git a/css/css-text-decor/reference/text-decoration-line-ref.html b/css/css-text-decor/reference/text-decoration-line-ref.html
new file mode 100644
index 0000000..f14d25a
--- /dev/null
+++ b/css/css-text-decor/reference/text-decoration-line-ref.html
@@ -0,0 +1,33 @@
+<html>
+<head>
+    <style>
+            .none { text-decoration: none; }
+            .underline { text-decoration: underline; }
+            .overline { text-decoration: overline; }
+            .line-through { text-decoration: line-through; }
+            .all-decorations { text-decoration: underline overline line-through; }
+    </style>
+</head>
+<body>
+    <div>Each line of this test should match its style description (<i>text-decoration-line</i> resets <i>text-decoration</i>, except when the latter is set as <i>important</i>):</div><br/>
+    <div class="underline">This text is underlined <span>(also inside span).</span></div><br/>
+    <div class="overline">This text is overlined <span>(also inside span).</span></div><br/>
+    <div class="line-through">This text has a line-through <span>(also inside span).</span></div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="underline">This text is underlined.</div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="overline">This text is overlined.</div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="line-through">This text has a line-through.</div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="all-decorations">This text is underlined, overlined and has a line-through.</div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="overline">This text is overlined.</div><br/>
+    <div class="overline">This text is overlined.</div><br/>
+    <div class="underline">This text is underlined.</div>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+    <div class="none">This text contains no decorations.</div><br/>
+</body>
+</html>
diff --git a/css/css-text-decor/reference/text-decoration-style-multiple-ref.html b/css/css-text-decor/reference/text-decoration-style-multiple-ref.html
new file mode 100644
index 0000000..f803969
--- /dev/null
+++ b/css/css-text-decor/reference/text-decoration-style-multiple-ref.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<style>
+    div {
+        color: #aaa;
+        font-size: 50px;
+        position: relative;
+        display: inline-block;
+        width: 200px;
+        height: 200px;
+    }
+
+    div > span { position: absolute; }
+
+    div > span:nth-child(1) { text-decoration: underline solid coral; }
+    div > span:nth-child(2) { text-decoration: overline dashed skyblue; }
+    div > span:nth-child(3) { text-decoration: line-through wavy green; }
+
+</style>
+<div><span>AAAA</span><span>AAAA</span><span>AAAA</span></div>
diff --git a/css/css-text-decor/reference/text-decoration-style-recalc-ref.html b/css/css-text-decor/reference/text-decoration-style-recalc-ref.html
new file mode 100644
index 0000000..30592b4
--- /dev/null
+++ b/css/css-text-decor/reference/text-decoration-style-recalc-ref.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<style>
+    div {
+        font-size: 50px;
+        text-decoration: underline dashed green;
+    }
+</style>
+<p>Test that changes in text-decoration-style are recalculated correctly. PASS
+if the text below has a dashed green underline, and not a solid green underline.</p>
+<div id="target">
+    Filler text
+</div>
\ No newline at end of file
diff --git a/css/css-text-decor/reference/text-emphasis-style-001-ref.html b/css/css-text-decor/reference/text-emphasis-style-001-ref.html
index 4458874..6222a7c 100644
--- a/css/css-text-decor/reference/text-emphasis-style-001-ref.html
+++ b/css/css-text-decor/reference/text-emphasis-style-001-ref.html
@@ -6,7 +6,7 @@
   <link rel="author" title="Takanori Koroki" href="mailto:tak.koroki@gmail.com">
   </head>
   <body>
-    <p>Test passes if there is no dots avobe the text of "Text sample".</p>
+    <p>Test passes if there is no dots above the text of "Text sample".</p>
     <p>Text sample</p>
   </body>
 </html>
diff --git a/css/css-text-decor/text-decoration-color-recalc.html b/css/css-text-decor/text-decoration-color-recalc.html
new file mode 100644
index 0000000..b7cde93
--- /dev/null
+++ b/css/css-text-decor/text-decoration-color-recalc.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<link rel="help" href="https://www.w3.org/TR/css-text-decor-3/#text-decoration-color">
+<link rel="match" href="reference/text-decoration-color-recalc-ref.html">
+<style>
+    div {
+        font-size: 50px;
+        text-decoration: underline solid red;
+    }
+</style>
+<script>
+    onload = function() {
+        target.style.textDecorationColor = "green";
+    };
+</script>
+<p>Test that changes in text-decoration-color are recalculated correctly. PASS
+if the text below has a solid green underline, and no red.</p>
+<div id="target">
+    Filler text
+</div>
diff --git a/css/css-text-decor/text-decoration-color.html b/css/css-text-decor/text-decoration-color.html
new file mode 100644
index 0000000..fd5bc5d
--- /dev/null
+++ b/css/css-text-decor/text-decoration-color.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: CSS3 text-decoration-color</title>
+        <link rel="help" href="https://www.w3.org/TR/css-text-decor-3/#text-decoration-color">
+        <link rel="match" href="reference/text-decoration-color-ref.html">
+        <style>
+            .underline {
+                text-decoration: underline;
+            }
+            .overline {
+                text-decoration: overline;
+            }
+            .line-through {
+                text-decoration: line-through;
+            }
+            #blue-underline {
+                text-decoration: underline;
+                text-decoration-color: blue;
+            }
+            #gray-overline {
+                text-decoration: overline;
+                text-decoration-color: gray;
+            }
+            #green-line-through {
+                text-decoration: line-through;
+                text-decoration-color: green;
+            }
+            #transparent-fill {
+                -webkit-text-fill-color: transparent;
+                -webkit-text-stroke-width: 1px;
+                -webkit-text-stroke-color: black;
+            }
+        </style>
+    </head>
+    <body>
+        <h3>Each line of this test should match its text decoration color description:</h3>
+
+        <!-- Valid values for underline, overline and line-through text decoration lines -->
+        <div class="underline" style="color: gray; text-decoration-color: blue;">Gray text with blue underline</div><br/>
+        <div class="overline" style="color: green; text-decoration-color: black;">Green text with black overline</div><br/>
+        <div class="line-through" style="text-decoration-color: gold;">Black text with gold line-through</div><br/>
+
+        <!-- Mix of underline, overline and line-through with different colors for each -->
+        <div>
+            <span id="blue-underline">
+                <span id="gray-overline">
+                    <span id="green-line-through">Black text with blue underline, gray overline and green line-through</span>
+                </span>
+            </span>
+        </div><br/>
+
+        <!-- Test behavior on subscript and superscript text -->
+        <div>
+            <span id="green-line-through">
+                <sub id="gray-overline">subscript text</sub>
+                <sup id="blue-underline">superscript text</sup>
+            </span>
+        </div><br/>
+
+        <!-- Test with text-fill-color and text-stroke-color values set -->
+        <div class="underline" id="transparent-fill" style="text-decoration-color: green;">Transparent fill with black stroke text and green underline</div><br/>
+    </body>
+</html>
diff --git a/css/css-text-decor/text-decoration-line-010.xht b/css/css-text-decor/text-decoration-line-010.xht
index 456a282..f4b16e3 100644
--- a/css/css-text-decor/text-decoration-line-010.xht
+++ b/css/css-text-decor/text-decoration-line-010.xht
@@ -13,7 +13,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/text-decoration-line-011.xht b/css/css-text-decor/text-decoration-line-011.xht
index d62259a..78f6e54 100644
--- a/css/css-text-decor/text-decoration-line-011.xht
+++ b/css/css-text-decor/text-decoration-line-011.xht
@@ -13,7 +13,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/text-decoration-line-012.xht b/css/css-text-decor/text-decoration-line-012.xht
index 2e18514..9a569eb 100644
--- a/css/css-text-decor/text-decoration-line-012.xht
+++ b/css/css-text-decor/text-decoration-line-012.xht
@@ -13,7 +13,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/text-decoration-line-013.xht b/css/css-text-decor/text-decoration-line-013.xht
index 4ad9781..db379e3 100644
--- a/css/css-text-decor/text-decoration-line-013.xht
+++ b/css/css-text-decor/text-decoration-line-013.xht
@@ -13,7 +13,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/text-decoration-line-014.xht b/css/css-text-decor/text-decoration-line-014.xht
index 6db1518..7d5f057 100644
--- a/css/css-text-decor/text-decoration-line-014.xht
+++ b/css/css-text-decor/text-decoration-line-014.xht
@@ -13,7 +13,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 					/*
 					mplus-1p-regular.ttf can be downloaded at/from [TBD later]
diff --git a/css/css-text-decor/text-decoration-line-recalc.html b/css/css-text-decor/text-decoration-line-recalc.html
new file mode 100644
index 0000000..321aea9
--- /dev/null
+++ b/css/css-text-decor/text-decoration-line-recalc.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<link rel="help" href="https://www.w3.org/TR/css-text-decor-3/#text-decoration-line">
+<link rel="match" href="reference/text-decoration-line-recalc-ref.html">
+<style>
+    div {
+        font-size: 50px;
+        text-decoration: underline solid green;
+    }
+</style>
+<script>
+    onload = function() {
+        target.style.textDecorationLine = "overline";
+    };
+</script>
+<p>Test that changes in text-decoration-line are recalculated correctly. PASS
+if the text below has a solid green overline, and no underline.</p>
+<div id="target">
+    Filler text
+</div>
\ No newline at end of file
diff --git a/css/css-text-decor/text-decoration-line.html b/css/css-text-decor/text-decoration-line.html
new file mode 100644
index 0000000..ea6a0c8
--- /dev/null
+++ b/css/css-text-decor/text-decoration-line.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+    <link rel="help" href="https://www.w3.org/TR/css-text-decor-3/#text-decoration-line">
+    <link rel="match" href="reference/text-decoration-line-ref.html">
+    <style>
+            .none { text-decoration: none; }
+            .underline { text-decoration: underline; }
+            .overline { text-decoration: overline; }
+            .line-through { text-decoration: line-through; }
+            .all-decorations { text-decoration: underline overline line-through; }
+    </style>
+</head>
+<body>
+    <div>Each line of this test should match its style description (<i>text-decoration-line</i> resets <i>text-decoration</i>, except when the latter is set as <i>important</i>):</div><br/>
+    <div style="text-decoration-line: underline;">This text is underlined <span class="none">(also inside span).</span></div><br/>
+    <div style="text-decoration-line: overline;">This text is overlined <span class="none">(also inside span).</span></div><br/>
+    <div style="text-decoration-line: line-through;">This text has a line-through <span class="none">(also inside span).</span></div><br/>
+    <div class="underline" style="text-decoration-line: none;">This text contains no decorations.</div><br/>
+    <div class="underline" style="text-decoration-line: underline;">This text is underlined.</div><br/>
+    <div class="overline" style="text-decoration-line: none;">This text contains no decorations.</div><br/>
+    <div class="overline" style="text-decoration-line: overline;">This text is overlined.</div><br/>
+    <div class="line-through" style="text-decoration-line: none;">This text contains no decorations.</div><br/>
+    <div class="line-through" style="text-decoration-line: line-through;">This text has a line-through.</div><br/>
+    <div class="all-decorations" style="text-decoration-line: none;">This text contains no decorations.</div><br/>
+    <div class="all-decorations" style="text-decoration-line: underline overline line-through;">This text is underlined, overlined and has a line-through.</div><br/>
+    <div style="text-decoration-line: underline; text-decoration: none !important;">This text contains no decorations.</div><br/>
+    <div style="text-decoration-line: underline; text-decoration: overline !important;">This text is overlined.</div><br/>
+    <div style="text-decoration-line: underline !important; text-decoration: overline !important;">This text is overlined.</div><br/>
+    <div style="text-decoration: overline !important; text-decoration-line: underline !important;">This text is underlined.</div>
+    <div style="text-decoration-line: blink blink;">This text contains no decorations.</div><br/>
+    <div style="text-decoration-line: blink underline blink;">This text contains no decorations.</div><br/>
+    <div style="text-decoration-line: blink underline overline blink;">This text contains no decorations.</div><br/>
+    <div style="text-decoration-line: blink underline overline line-through blink;">This text contains no decorations.</div><br/>
+</body>
+</html>
diff --git a/css/css-text-decor/text-decoration-serialization.tentative.html b/css/css-text-decor/text-decoration-serialization.tentative.html
new file mode 100644
index 0000000..2f7b2f5
--- /dev/null
+++ b/css/css-text-decor/text-decoration-serialization.tentative.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<title>text-decoration shorthand serialization</title>
+<link rel="help" href="https://drafts.csswg.org/css-text-decor-3/#text-decoration-property">
+<link rel="help" href="https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block">
+<script type="text/javascript" src="/resources/testharness.js"></script>
+<script type="text/javascript" src="/resources/testharnessreport.js"></script>
+<div style="text-decoration: underline"></div>
+<script>
+test(() => {
+  const style = getComputedStyle(document.querySelector('div'));
+  // Chrome serializes as "underline solid rgb(0, 0, 0)" while Edge, Firefox an
+  // Safari use "underline", which Chrome used to do as well. The spec should
+  // probably require "underline":
+  // https://github.com/w3c/csswg-drafts/issues/1564
+  assert_equals(style.getPropertyValue("text-decoration"), "underline");
+});
+</script>
diff --git a/css/css-text-decor/text-decoration-style-multiple.html b/css/css-text-decor/text-decoration-style-multiple.html
new file mode 100644
index 0000000..567229f
--- /dev/null
+++ b/css/css-text-decor/text-decoration-style-multiple.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<link rel="help" href="https://www.w3.org/TR/css-text-decor-3/#text-decoration-style">
+<link rel="match" href="reference/text-decoration-style-multiple-ref.html">
+<style>
+    div {
+        color: #aaa;
+        font-size: 50px;
+        position: relative;
+        display: inline-block;
+        width: 200px;
+        height: 200px;
+    }
+
+    div > span { position: absolute; }
+
+    div > span:nth-child(3) { text-decoration: underline solid coral; }
+    div > span:nth-child(3) > span { text-decoration: overline dashed skyblue; }
+    div > span:nth-child(3) > span  > span { text-decoration: line-through wavy green; }
+
+</style>
+<div><span>AAAA</span><span>AAAA</span><span><span><span>AAAA</span></span></span></div>
diff --git a/css/css-text-decor/text-decoration-style-recalc.html b/css/css-text-decor/text-decoration-style-recalc.html
new file mode 100644
index 0000000..d4538e7
--- /dev/null
+++ b/css/css-text-decor/text-decoration-style-recalc.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<link rel="help" href="https://www.w3.org/TR/css-text-decor-3/#text-decoration-style">
+<link rel="match" href="reference/text-decoration-style-recalc-ref.html">
+<style>
+    div {
+        font-size: 50px;
+        text-decoration: underline solid green;
+    }
+</style>
+<script>
+    onload = function() {
+        target.style.textDecorationStyle = "dashed";
+    };
+</script>
+<p>Test that changes in text-decoration-style are recalculated correctly. PASS
+if the text below has a dashed green underline, and not a solid green underline.</p>
+<div id="target">
+    Filler text
+</div>
diff --git a/css/css-text/astral-bidi/adlam-anti-ref.html b/css/css-text/astral-bidi/adlam-anti-ref.html
new file mode 100644
index 0000000..db4c2fd
--- /dev/null
+++ b/css/css-text/astral-bidi/adlam-anti-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Adlam</title>
+    <link href="support/adlam.css" rel="stylesheet">
+    <link href="adlam.html" rel="mismatch">
+</head>
+<body>
+<p><bdo dir="ltr">𞤀𞤁𞤂𞤁𞤄</bdo></p>
+<p>U+1E900 U+1E901 U+1E902 U+1E901 U+1E904</p>
+</body>
+</html>
diff --git a/css/css-text/astral-bidi/adlam-ref.html b/css/css-text/astral-bidi/adlam-ref.html
new file mode 100644
index 0000000..d039127
--- /dev/null
+++ b/css/css-text/astral-bidi/adlam-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Adlam</title>
+    <link href="support/adlam.css" rel="stylesheet">
+</head>
+<body>
+<p><bdo dir="rtl">𞤀𞤁𞤂𞤁𞤄</bdo></p>
+<p>U+1E900 U+1E901 U+1E902 U+1E901 U+1E904</p>
+</body>
+</html>
diff --git a/css/css-text/astral-bidi/adlam.html b/css/css-text/astral-bidi/adlam.html
new file mode 100644
index 0000000..613aa3c
--- /dev/null
+++ b/css/css-text/astral-bidi/adlam.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Adlam</title>
+    <link href="support/adlam.css" rel="stylesheet">
+    <link href="adlam-ref.html" rel="match">
+    <link href="https://www.unicode.org/roadmaps/smp/" rel="help">
+    <meta name="assert" content="Upper astral RTL range is treated as RTL.">
+</head>
+<body>
+<p>𞤀𞤁𞤂𞤁𞤄</p>
+<p>U+1E900 U+1E901 U+1E902 U+1E901 U+1E904</p>
+</body>
+</html>
diff --git a/css/css-text/astral-bidi/cypriot-anti-ref.html b/css/css-text/astral-bidi/cypriot-anti-ref.html
new file mode 100644
index 0000000..66ffc08
--- /dev/null
+++ b/css/css-text/astral-bidi/cypriot-anti-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Cypriot</title>
+    <link href="support/cypriot.css" rel="stylesheet">
+    <link href="cypriot.html" rel="mismatch">
+</head>
+<body>
+<p><bdo dir="ltr">𐠀𐠰𐠦𐠡𐠩</bdo></p>
+<p>U+10800 U+10830 U+10826 U+10821 U+10829</p>
+</body>
+</html>
diff --git a/css/css-text/astral-bidi/cypriot-ref.html b/css/css-text/astral-bidi/cypriot-ref.html
new file mode 100644
index 0000000..ad8accd
--- /dev/null
+++ b/css/css-text/astral-bidi/cypriot-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Cypriot</title>
+    <link href="support/cypriot.css" rel="stylesheet">
+</head>
+<body>
+<p><bdo dir="rtl">𐠀𐠰𐠦𐠡𐠩</bdo></p>
+<p>U+10800 U+10830 U+10826 U+10821 U+10829</p>
+</body>
+</html>
diff --git a/css/css-text/astral-bidi/cypriot.html b/css/css-text/astral-bidi/cypriot.html
new file mode 100644
index 0000000..e8dcbce
--- /dev/null
+++ b/css/css-text/astral-bidi/cypriot.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Cypriot</title>
+    <link href="support/cypriot.css" rel="stylesheet">
+    <link href="cypriot-ref.html" rel="match">
+    <link href="https://www.unicode.org/roadmaps/smp/" rel="help">
+    <meta name="assert" content="Lower astral RTL range is treated as RTL.">
+</head>
+<body>
+<p>𐠀𐠰𐠦𐠡𐠩</p>
+<p>U+10800 U+10830 U+10826 U+10821 U+10829</p>
+</body>
+</html>
diff --git a/css/css-text/astral-bidi/support/adlam.css b/css/css-text/astral-bidi/support/adlam.css
new file mode 100644
index 0000000..89e0db1
--- /dev/null
+++ b/css/css-text/astral-bidi/support/adlam.css
@@ -0,0 +1,9 @@
+@font-face {
+    font-family: "Noto Sans Adlam";
+    font-weight: normal;
+    font-style: normal;
+    src: url("/fonts/noto/NotoSansAdlam-hinted/NotoSansAdlam-Regular.ttf") format("truetype");
+}
+p {
+    font-family: "Noto Sans Adlam";
+}
diff --git a/css/css-text/astral-bidi/support/cypriot.css b/css/css-text/astral-bidi/support/cypriot.css
new file mode 100644
index 0000000..d739821
--- /dev/null
+++ b/css/css-text/astral-bidi/support/cypriot.css
@@ -0,0 +1,9 @@
+@font-face {
+    font-family: "Noto Sans Cypriot";
+    font-weight: normal;
+    font-style: normal;
+    src: url("/fonts/noto/NotoSansCypriot-hinted/NotoSansCypriot-Regular.ttf") format("truetype");
+}
+p {
+    font-family: "Noto Sans Cypriot";
+}
diff --git a/css/css-text/hanging-punctuation/hanging-punctuation-allow-end-001.xht b/css/css-text/hanging-punctuation/hanging-punctuation-allow-end-001.xht
index 96f44f4..99bc272 100644
--- a/css/css-text/hanging-punctuation/hanging-punctuation-allow-end-001.xht
+++ b/css/css-text/hanging-punctuation/hanging-punctuation-allow-end-001.xht
@@ -25,6 +25,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -35,59 +41,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="test">
-				これらは満たす文字で、それらも満たす文字です。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				これらは満たす文字で、<br />それらも満たす文字で<br />す。
+			<div class="parent">
+				<div class="test">
+					これらは満たす文字で、それらも満たす文字です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これらは満たす文字で、<br />それらも満たす文字で<br />す。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="test">
-				これは満たす文字です。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは満たす文字です。
+			<div class="parent">
+				<div class="test">
+					これは満たす文字です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは満たす文字です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="test">
-				ミタスモジ、ミタスモジ、ミタスモジ、ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+			<div class="parent">
+				<div class="test">
+					ミタスモジ、ミタスモジ、ミタスモジ、ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="test">
-				ミタスモジ。ミタスモジ。ミタスモジ。ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+			<div class="parent">
+				<div class="test">
+					ミタスモジ。ミタスモジ。ミタスモジ。ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/hanging-punctuation/hanging-punctuation-first-001.xht b/css/css-text/hanging-punctuation/hanging-punctuation-first-001.xht
index 359aa27..92e514f 100644
--- a/css/css-text/hanging-punctuation/hanging-punctuation-first-001.xht
+++ b/css/css-text/hanging-punctuation/hanging-punctuation-first-001.xht
@@ -29,6 +29,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -39,59 +45,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			Opening brackets
-		</div>
-		<div class="parent">
-			<div class="test">
-				「これは、満たすための文字です。」
+		<div class="wrapper">
+			<div>
+				Opening brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				<span class="hanging">「これは、満たすための</span><br />文字です。」
+			<div class="parent">
+				<div class="test">
+					「これは、満たすための文字です。」
+				</div>
+				<br />
+				<div class="no-wrap">
+					<span class="hanging">「これは、満たすための</span><br />文字です。」
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Closing brackets
-		</div>
-		<div class="parent">
-			<div class="test">
-				これは、「満たす文字」です。
+		<div class="wrapper">
+			<div>
+				Closing brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、「満たす文<br />字」です。
+			<div class="parent">
+				<div class="test">
+					これは、「満たす文字」です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、「満たす文<br />字」です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Initial quotes
-		</div>
-		<div class="parent">
-			<div class="test">
-				“これは、満たすための文字です。”
+		<div class="wrapper">
+			<div>
+				Initial quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				<span class="hanging">“これは、満たすための</span><br />文字です。”
+			<div class="parent">
+				<div class="test">
+					“これは、満たすための文字です。”
+				</div>
+				<br />
+				<div class="no-wrap">
+					<span class="hanging">“これは、満たすための</span><br />文字です。”
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Final quotes
-		</div>
-		<div class="parent">
-			<div class="test">
-				これは、“満たす文字”です。
+		<div class="wrapper">
+			<div>
+				Final quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、“満たす文<br />字”です。
+			<div class="parent">
+				<div class="test">
+					これは、“満たす文字”です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、“満たす文<br />字”です。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/hanging-punctuation/hanging-punctuation-force-end-001.xht b/css/css-text/hanging-punctuation/hanging-punctuation-force-end-001.xht
index 31c3a89..b9f9462 100644
--- a/css/css-text/hanging-punctuation/hanging-punctuation-force-end-001.xht
+++ b/css/css-text/hanging-punctuation/hanging-punctuation-force-end-001.xht
@@ -25,6 +25,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -35,59 +41,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="test">
-				これらは満たす文字で、それらも満たす文字です。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				これらは満たす文字で、<br />それらも満たす文字で<br />す。
+			<div class="parent">
+				<div class="test">
+					これらは満たす文字で、それらも満たす文字です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これらは満たす文字で、<br />それらも満たす文字で<br />す。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="test">
-				これは満たす文字です。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは満たす文字です。
+			<div class="parent">
+				<div class="test">
+					これは満たす文字です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは満たす文字です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="test">
-				ミタスモジ、ミタスモジ、ミタスモジ、ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+			<div class="parent">
+				<div class="test">
+					ミタスモジ、ミタスモジ、ミタスモジ、ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="test">
-				ミタスモジ。ミタスモジ。ミタスモジ。ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+			<div class="parent">
+				<div class="test">
+					ミタスモジ。ミタスモジ。ミタスモジ。ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/hanging-punctuation/hanging-punctuation-last-001.xht b/css/css-text/hanging-punctuation/hanging-punctuation-last-001.xht
index 43fff2c..be7ea6b 100644
--- a/css/css-text/hanging-punctuation/hanging-punctuation-last-001.xht
+++ b/css/css-text/hanging-punctuation/hanging-punctuation-last-001.xht
@@ -25,6 +25,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -35,59 +41,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			Opening brackets
-		</div>
-		<div class="parent">
-			<div class="test">
-				「これは、満たすための文字です。」
+		<div class="wrapper">
+			<div>
+				Opening brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				「これは、満たすため<br />の文字です。」
+			<div class="parent">
+				<div class="test">
+					「これは、満たすための文字です。」
+				</div>
+				<br />
+				<div class="no-wrap">
+					「これは、満たすため<br />の文字です。」
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Closing brackets
-		</div>
-		<div class="parent">
-			<div class="test">
-				これは、「満たす文字」です。
+		<div class="wrapper">
+			<div>
+				Closing brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、「満たす文字」<br />です。
+			<div class="parent">
+				<div class="test">
+					これは、「満たす文字」です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、「満たす文字」<br />です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Initial quotes
-		</div>
-		<div class="parent">
-			<div class="test">
-				“これは、満たすための文字です。”
+		<div class="wrapper">
+			<div>
+				Initial quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				“これは、満たすため<br />の文字です。”
+			<div class="parent">
+				<div class="test">
+					“これは、満たすための文字です。”
+				</div>
+				<br />
+				<div class="no-wrap">
+					“これは、満たすため<br />の文字です。”
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Final quotes
-		</div>
-		<div class="parent">
-			<div class="test">
-				これは、“満たす文字”です。
+		<div class="wrapper">
+			<div>
+				Final quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、“満たす文字”<br />です。
+			<div class="parent">
+				<div class="test">
+					これは、“満たす文字”です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、“満たす文字”<br />です。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/hanging-punctuation/reference/hanging-punctuation-allow-end-001-ref.xht b/css/css-text/hanging-punctuation/reference/hanging-punctuation-allow-end-001-ref.xht
index 9d36a45..48be1d9 100644
--- a/css/css-text/hanging-punctuation/reference/hanging-punctuation-allow-end-001-ref.xht
+++ b/css/css-text/hanging-punctuation/reference/hanging-punctuation-allow-end-001-ref.xht
@@ -17,6 +17,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -27,59 +33,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これらは満たす文字で、<br />それらも満たす文字で<br />す。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				これらは満たす文字で、<br />それらも満たす文字で<br />す。
+			<div class="parent">
+				<div class="no-wrap">
+					これらは満たす文字で、<br />それらも満たす文字で<br />す。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これらは満たす文字で、<br />それらも満たす文字で<br />す。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これは満たす文字です。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは満たす文字です。
+			<div class="parent">
+				<div class="no-wrap">
+					これは満たす文字です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは満たす文字です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+			<div class="parent">
+				<div class="no-wrap">
+					ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+			<div class="parent">
+				<div class="no-wrap">
+					ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/hanging-punctuation/reference/hanging-punctuation-first-001-ref.xht b/css/css-text/hanging-punctuation/reference/hanging-punctuation-first-001-ref.xht
index 6b10181..1949939 100644
--- a/css/css-text/hanging-punctuation/reference/hanging-punctuation-first-001-ref.xht
+++ b/css/css-text/hanging-punctuation/reference/hanging-punctuation-first-001-ref.xht
@@ -21,6 +21,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -31,59 +37,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			Opening brackets
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				<span class="hanging">「これは、満たすための</span><br />文字です。」
+		<div class="wrapper">
+			<div>
+				Opening brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				<span class="hanging">「これは、満たすための</span><br />文字です。」
+			<div class="parent">
+				<div class="no-wrap">
+					<span class="hanging">「これは、満たすための</span><br />文字です。」
+				</div>
+				<br />
+				<div class="no-wrap">
+					<span class="hanging">「これは、満たすための</span><br />文字です。」
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Closing brackets
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これは、「満たす文<br />字」です。
+		<div class="wrapper">
+			<div>
+				Closing brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、「満たす文<br />字」です。
+			<div class="parent">
+				<div class="no-wrap">
+					これは、「満たす文<br />字」です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、「満たす文<br />字」です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Initial quotes
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				<span class="hanging">“これは、満たすための</span><br />文字です。”
+		<div class="wrapper">
+			<div>
+				Initial quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				<span class="hanging">“これは、満たすための</span><br />文字です。”
+			<div class="parent">
+				<div class="no-wrap">
+					<span class="hanging">“これは、満たすための</span><br />文字です。”
+				</div>
+				<br />
+				<div class="no-wrap">
+					<span class="hanging">“これは、満たすための</span><br />文字です。”
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Final quotes
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これは、“満たす文<br />字”です。
+		<div class="wrapper">
+			<div>
+				Final quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、“満たす文<br />字”です。
+			<div class="parent">
+				<div class="no-wrap">
+					これは、“満たす文<br />字”です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、“満たす文<br />字”です。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/hanging-punctuation/reference/hanging-punctuation-force-end-001-ref.xht b/css/css-text/hanging-punctuation/reference/hanging-punctuation-force-end-001-ref.xht
index 8796b51..a9e7e0c 100644
--- a/css/css-text/hanging-punctuation/reference/hanging-punctuation-force-end-001-ref.xht
+++ b/css/css-text/hanging-punctuation/reference/hanging-punctuation-force-end-001-ref.xht
@@ -17,6 +17,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -27,59 +33,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これらは満たす文字で、<br />それらも満たす文字で<br />す。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				これらは満たす文字で、<br />それらも満たす文字で<br />す。
+			<div class="parent">
+				<div class="no-wrap">
+					これらは満たす文字で、<br />それらも満たす文字で<br />す。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これらは満たす文字で、<br />それらも満たす文字で<br />す。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これは満たす文字です。
+		<div class="wrapper">
+			<div>
+				IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは満たす文字です。
+			<div class="parent">
+				<div class="no-wrap">
+					これは満たす文字です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは満たす文字です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC COMMA
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC COMMA
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+			<div class="parent">
+				<div class="no-wrap">
+					ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ、ミタスモジ、ミタスモジ、<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			HALFWIDTH IDEOGRAPHIC FULL STOP
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+		<div class="wrapper">
+			<div>
+				HALFWIDTH IDEOGRAPHIC FULL STOP
 			</div>
-			<br />
-			<div class="no-wrap">
-				ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+			<div class="parent">
+				<div class="no-wrap">
+					ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+				</div>
+				<br />
+				<div class="no-wrap">
+					ミタスモジ。ミタスモジ。ミタスモジ。<br />ミタスモジ。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/hanging-punctuation/reference/hanging-punctuation-last-001-ref.xht b/css/css-text/hanging-punctuation/reference/hanging-punctuation-last-001-ref.xht
index c14f931..1f8aa4a5 100644
--- a/css/css-text/hanging-punctuation/reference/hanging-punctuation-last-001-ref.xht
+++ b/css/css-text/hanging-punctuation/reference/hanging-punctuation-last-001-ref.xht
@@ -17,6 +17,12 @@
 					margin-left: 2em;
 					width: 10em
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -27,59 +33,62 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<div>
-			Opening brackets
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				「これは、満たすため<br />の文字です。」
+		<div class="wrapper">
+			<div>
+				Opening brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				「これは、満たすため<br />の文字です。」
+			<div class="parent">
+				<div class="no-wrap">
+					「これは、満たすため<br />の文字です。」
+				</div>
+				<br />
+				<div class="no-wrap">
+					「これは、満たすため<br />の文字です。」
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Closing brackets
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これは、「満たす文字」<br />です。
+		<div class="wrapper">
+			<div>
+				Closing brackets
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、「満たす文字」<br />です。
+			<div class="parent">
+				<div class="no-wrap">
+					これは、「満たす文字」<br />です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、「満たす文字」<br />です。
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Initial quotes
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				“これは、満たすため<br />の文字です。”
+		<div class="wrapper">
+			<div>
+				Initial quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				“これは、満たすため<br />の文字です。”
+			<div class="parent">
+				<div class="no-wrap">
+					“これは、満たすため<br />の文字です。”
+				</div>
+				<br />
+				<div class="no-wrap">
+					“これは、満たすため<br />の文字です。”
+				</div>
 			</div>
 		</div>
-		<hr />
-		<div>
-			Final quotes
-		</div>
-		<div class="parent">
-			<div class="no-wrap">
-				これは、“満たす文字”<br />です。
+		<div class="wrapper">
+			<div>
+				Final quotes
 			</div>
-			<br />
-			<div class="no-wrap">
-				これは、“満たす文字”<br />です。
+			<div class="parent">
+				<div class="no-wrap">
+					これは、“満たす文字”<br />です。
+				</div>
+				<br />
+				<div class="no-wrap">
+					これは、“満たす文字”<br />です。
+				</div>
 			</div>
 		</div>
-		<hr />
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-001.html b/css/css-text/i18n/css3-text-line-break-jazh-001.html
index 2ff57d0..e66d87d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-001.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-001.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-002.html b/css/css-text/i18n/css3-text-line-break-jazh-002.html
index e1c9564..3e5cdc6 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-002.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-002.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-003.html b/css/css-text/i18n/css3-text-line-break-jazh-003.html
index 6f45013..549d248 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-003.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-003.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-004.html b/css/css-text/i18n/css3-text-line-break-jazh-004.html
index ae42e77..078fc1d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-004.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-004.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-005.html b/css/css-text/i18n/css3-text-line-break-jazh-005.html
index 1299f55..395f905 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-005.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-005.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-006.html b/css/css-text/i18n/css3-text-line-break-jazh-006.html
index c586881..21c594e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-006.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-006.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-007.html b/css/css-text/i18n/css3-text-line-break-jazh-007.html
index b9a49ec..d2baa06 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-007.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-007.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-008.html b/css/css-text/i18n/css3-text-line-break-jazh-008.html
index 22a770b..bffdf99 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-008.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-008.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-009.html b/css/css-text/i18n/css3-text-line-break-jazh-009.html
index 8e7f846..290ee31 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-009.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-009.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-010.html b/css/css-text/i18n/css3-text-line-break-jazh-010.html
index 7a450c8..570b6db 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-010.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-010.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-011.html b/css/css-text/i18n/css3-text-line-break-jazh-011.html
index 0fc403d..370da94 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-011.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-011.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-012.html b/css/css-text/i18n/css3-text-line-break-jazh-012.html
index 30c3718..510d268 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-012.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-012.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-013.html b/css/css-text/i18n/css3-text-line-break-jazh-013.html
index 33e5745..b076b3c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-013.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-013.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-014.html b/css/css-text/i18n/css3-text-line-break-jazh-014.html
index f653cc4..2349d5a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-014.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-014.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-015.html b/css/css-text/i18n/css3-text-line-break-jazh-015.html
index 978740e..e5b7733 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-015.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-015.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-016.html b/css/css-text/i18n/css3-text-line-break-jazh-016.html
index 6030aee..29c1914 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-016.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-016.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-017.html b/css/css-text/i18n/css3-text-line-break-jazh-017.html
index b08abf3..7a97acb 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-017.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-017.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-018.html b/css/css-text/i18n/css3-text-line-break-jazh-018.html
index c7e5289..55019a7 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-018.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-018.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-019.html b/css/css-text/i18n/css3-text-line-break-jazh-019.html
index 17af8a4..275adf9 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-019.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-019.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-020.html b/css/css-text/i18n/css3-text-line-break-jazh-020.html
index 971004b..7109f5e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-020.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-020.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-021.html b/css/css-text/i18n/css3-text-line-break-jazh-021.html
index e6a65f5..72a2437 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-021.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-021.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-022.html b/css/css-text/i18n/css3-text-line-break-jazh-022.html
index 34c6735..7f95315 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-022.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-022.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-023.html b/css/css-text/i18n/css3-text-line-break-jazh-023.html
index 4abb966..9064330 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-023.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-023.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-024.html b/css/css-text/i18n/css3-text-line-break-jazh-024.html
index d75706f..99b847b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-024.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-024.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-025.html b/css/css-text/i18n/css3-text-line-break-jazh-025.html
index 4b4580f..5c17b1f 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-025.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-025.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-026.html b/css/css-text/i18n/css3-text-line-break-jazh-026.html
index e21662d..6b5dd2e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-026.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-026.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-027.html b/css/css-text/i18n/css3-text-line-break-jazh-027.html
index a1ab41b..f9514c2 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-027.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-027.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-028.html b/css/css-text/i18n/css3-text-line-break-jazh-028.html
index 307fcf6..0d74635 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-028.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-028.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-029.html b/css/css-text/i18n/css3-text-line-break-jazh-029.html
index 67e0d10..5002f80 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-029.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-029.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-030.html b/css/css-text/i18n/css3-text-line-break-jazh-030.html
index e3a5673..a875f7a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-030.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-030.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-031.html b/css/css-text/i18n/css3-text-line-break-jazh-031.html
index f41fcf6..89595de 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-031.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-031.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-032.html b/css/css-text/i18n/css3-text-line-break-jazh-032.html
index 99ed94f..e1bea31 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-032.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-032.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-033.html b/css/css-text/i18n/css3-text-line-break-jazh-033.html
index 66ee3f5..40e1dee 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-033.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-033.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-034.html b/css/css-text/i18n/css3-text-line-break-jazh-034.html
index 3f678e4..a26d75e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-034.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-034.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-035.html b/css/css-text/i18n/css3-text-line-break-jazh-035.html
index 54a3f32..7ba2d56 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-035.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-035.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-036.html b/css/css-text/i18n/css3-text-line-break-jazh-036.html
index 30cd7a3..29676f5 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-036.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-036.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-037.html b/css/css-text/i18n/css3-text-line-break-jazh-037.html
index 5ca0713..4642fd4 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-037.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-037.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-038.html b/css/css-text/i18n/css3-text-line-break-jazh-038.html
index 6c11c6f..e3614dd 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-038.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-038.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-039.html b/css/css-text/i18n/css3-text-line-break-jazh-039.html
index 74d99d9..51a7f12 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-039.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-039.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-040.html b/css/css-text/i18n/css3-text-line-break-jazh-040.html
index 5dabf4a..58831ae 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-040.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-040.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-041.html b/css/css-text/i18n/css3-text-line-break-jazh-041.html
index 5d1cffc..c8be998 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-041.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-041.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-042.html b/css/css-text/i18n/css3-text-line-break-jazh-042.html
index e6acd38..ace3244 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-042.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-042.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-043.html b/css/css-text/i18n/css3-text-line-break-jazh-043.html
index b903061..1f1b99a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-043.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-043.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-044.html b/css/css-text/i18n/css3-text-line-break-jazh-044.html
index 816f87e..0c3fe2c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-044.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-044.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-045.html b/css/css-text/i18n/css3-text-line-break-jazh-045.html
index 1bf5421..89fb717 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-045.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-045.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-046.html b/css/css-text/i18n/css3-text-line-break-jazh-046.html
index 5e8f550..c8da6f6 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-046.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-046.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-047.html b/css/css-text/i18n/css3-text-line-break-jazh-047.html
index b717edc..bf5e4f3 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-047.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-047.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-048.html b/css/css-text/i18n/css3-text-line-break-jazh-048.html
index 16df07d..af7b5ff 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-048.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-048.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-049.html b/css/css-text/i18n/css3-text-line-break-jazh-049.html
index 5d1774e..472e687 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-049.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-049.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-050.html b/css/css-text/i18n/css3-text-line-break-jazh-050.html
index 40cebd4..2821101 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-050.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-050.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-051.html b/css/css-text/i18n/css3-text-line-break-jazh-051.html
index 5a9903f..c95f342 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-051.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-051.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-052.html b/css/css-text/i18n/css3-text-line-break-jazh-052.html
index 44e5a5a..9453a14 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-052.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-052.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-054.html b/css/css-text/i18n/css3-text-line-break-jazh-054.html
index 95f1f7c..a6584c8 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-054.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-054.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-055.html b/css/css-text/i18n/css3-text-line-break-jazh-055.html
index 7d6c878..61d856a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-055.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-055.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-056.html b/css/css-text/i18n/css3-text-line-break-jazh-056.html
index 7bebdaa..12064b5 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-056.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-056.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-057.html b/css/css-text/i18n/css3-text-line-break-jazh-057.html
index 62f4d59..86d91c0 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-057.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-057.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-058.html b/css/css-text/i18n/css3-text-line-break-jazh-058.html
index a4754f8..5ddf00f 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-058.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-058.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-059.html b/css/css-text/i18n/css3-text-line-break-jazh-059.html
index 4122c0d..ebc3535 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-059.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-059.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
@@ -29,7 +29,7 @@
 
 
 
-<div class='ref'>中中<br/>中&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中<br/>中&#xFFE0;文</div></div>
 
 
 <!--Notes:
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-060.html b/css/css-text/i18n/css3-text-line-break-jazh-060.html
index 804f367..e485f21 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-060.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-060.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-101.html b/css/css-text/i18n/css3-text-line-break-jazh-101.html
index 5002963..9be9b49 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-101.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-101.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-102.html b/css/css-text/i18n/css3-text-line-break-jazh-102.html
index 590a2b5..bd4660c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-102.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-102.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-103.html b/css/css-text/i18n/css3-text-line-break-jazh-103.html
index a543782..ca79201 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-103.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-103.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-104.html b/css/css-text/i18n/css3-text-line-break-jazh-104.html
index 4fb6c71..1445b6f 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-104.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-104.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-105.html b/css/css-text/i18n/css3-text-line-break-jazh-105.html
index 2c9556d..7aaa5d4 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-105.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-105.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-106.html b/css/css-text/i18n/css3-text-line-break-jazh-106.html
index 45548ad..423ccfb 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-106.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-106.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-107.html b/css/css-text/i18n/css3-text-line-break-jazh-107.html
index 2495797..c41577a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-107.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-107.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-108.html b/css/css-text/i18n/css3-text-line-break-jazh-108.html
index 4b1a799..b3cddf3 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-108.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-108.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-109.html b/css/css-text/i18n/css3-text-line-break-jazh-109.html
index f59a2a4..240cab5 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-109.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-109.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-110.html b/css/css-text/i18n/css3-text-line-break-jazh-110.html
index ed5ec11..5da0726 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-110.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-110.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-111.html b/css/css-text/i18n/css3-text-line-break-jazh-111.html
index f0a04ee..0a35d8d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-111.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-111.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-112.html b/css/css-text/i18n/css3-text-line-break-jazh-112.html
index 225dafb..84aabe4 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-112.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-112.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-113.html b/css/css-text/i18n/css3-text-line-break-jazh-113.html
index 3f3671c..dd2dc22 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-113.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-113.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-114.html b/css/css-text/i18n/css3-text-line-break-jazh-114.html
index b3acdeb..338f67b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-114.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-114.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-115.html b/css/css-text/i18n/css3-text-line-break-jazh-115.html
index affc6c6..b99c05f 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-115.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-115.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-116.html b/css/css-text/i18n/css3-text-line-break-jazh-116.html
index 5278f06..f2f66f4 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-116.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-116.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-117.html b/css/css-text/i18n/css3-text-line-break-jazh-117.html
index a646811..efea7d4 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-117.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-117.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-118.html b/css/css-text/i18n/css3-text-line-break-jazh-118.html
index f76038f..31548df 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-118.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-118.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-119.html b/css/css-text/i18n/css3-text-line-break-jazh-119.html
index a592900..1e136f2 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-119.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-119.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-120.html b/css/css-text/i18n/css3-text-line-break-jazh-120.html
index f407e0a..dd0e695 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-120.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-120.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-121.html b/css/css-text/i18n/css3-text-line-break-jazh-121.html
index 299b765..b0394f1 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-121.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-121.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-122.html b/css/css-text/i18n/css3-text-line-break-jazh-122.html
index fb52dfd..82d884b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-122.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-122.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-123.html b/css/css-text/i18n/css3-text-line-break-jazh-123.html
index 75066f5..45fbf74 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-123.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-123.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-124.html b/css/css-text/i18n/css3-text-line-break-jazh-124.html
index 8bca80e..cdc2732 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-124.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-124.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-125.html b/css/css-text/i18n/css3-text-line-break-jazh-125.html
index e9f4a2d..e8db721 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-125.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-125.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-126.html b/css/css-text/i18n/css3-text-line-break-jazh-126.html
index 8da7e8e..7d5a619 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-126.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-126.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-127.html b/css/css-text/i18n/css3-text-line-break-jazh-127.html
index 7995630..efcf54c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-127.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-127.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-128.html b/css/css-text/i18n/css3-text-line-break-jazh-128.html
index 9133b91..76ada49 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-128.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-128.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-129.html b/css/css-text/i18n/css3-text-line-break-jazh-129.html
index 89a17f4..ec44b41 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-129.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-129.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-130.html b/css/css-text/i18n/css3-text-line-break-jazh-130.html
index 7d39f42..e00ecd7 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-130.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-130.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-131.html b/css/css-text/i18n/css3-text-line-break-jazh-131.html
index 78293ee..eaf52c3 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-131.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-131.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-132.html b/css/css-text/i18n/css3-text-line-break-jazh-132.html
index 75c174d..7446e38 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-132.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-132.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-133.html b/css/css-text/i18n/css3-text-line-break-jazh-133.html
index 1c015df..beea9ae 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-133.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-133.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-134.html b/css/css-text/i18n/css3-text-line-break-jazh-134.html
index d6ecc3a..23324ed 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-134.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-134.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-135.html b/css/css-text/i18n/css3-text-line-break-jazh-135.html
index 5e51538..fb3d471 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-135.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-135.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-136.html b/css/css-text/i18n/css3-text-line-break-jazh-136.html
deleted file mode 100644
index efa0855..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-136.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003A COLON (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-136-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 003A COLON at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='ja'>中中中&#x003A;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003A;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-137.html b/css/css-text/i18n/css3-text-line-break-jazh-137.html
deleted file mode 100644
index 9966ccb..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-137.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003B SEMICOLON (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-137-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 003B SEMICOLON at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='ja'>中中中&#x003B;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003B;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-138.html b/css/css-text/i18n/css3-text-line-break-jazh-138.html
index 341ed1e..60afbed 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-138.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-138.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-139.html b/css/css-text/i18n/css3-text-line-break-jazh-139.html
index 4708ef3..816647c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-139.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-139.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-140.html b/css/css-text/i18n/css3-text-line-break-jazh-140.html
index 15225d9..a713d72 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-140.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-140.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-141.html b/css/css-text/i18n/css3-text-line-break-jazh-141.html
index fb5190d..32ab033 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-141.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-141.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-142.html b/css/css-text/i18n/css3-text-line-break-jazh-142.html
deleted file mode 100644
index 44298c5..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-142.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0021 EXCLAMATION MARK (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-142-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 0021 EXCLAMATION MARK at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='ja'>中中中&#x0021;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0021;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-143.html b/css/css-text/i18n/css3-text-line-break-jazh-143.html
deleted file mode 100644
index d5eaf35..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-143.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003F QUESTION MARK (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-143-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 003F QUESTION MARK at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='ja'>中中中&#x003F;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003F;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-144.html b/css/css-text/i18n/css3-text-line-break-jazh-144.html
index 80b44c6..88adc54 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-144.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-144.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-145.html b/css/css-text/i18n/css3-text-line-break-jazh-145.html
index d9d97089..8c1eb40 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-145.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-145.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-146.html b/css/css-text/i18n/css3-text-line-break-jazh-146.html
index 3d860a5..a420d73 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-146.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-146.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-147.html b/css/css-text/i18n/css3-text-line-break-jazh-147.html
index b97902f..b425202 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-147.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-147.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-148.html b/css/css-text/i18n/css3-text-line-break-jazh-148.html
index afdf3d5..e38ee2d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-148.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-148.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-149.html b/css/css-text/i18n/css3-text-line-break-jazh-149.html
index db9ae18..5b6f374 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-149.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-149.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-150.html b/css/css-text/i18n/css3-text-line-break-jazh-150.html
deleted file mode 100644
index 56afc21..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-150.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0025 PERCENT SIGN (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-150-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 0025 PERCENT SIGN at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='ja'>中中中&#x0025;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-151.html b/css/css-text/i18n/css3-text-line-break-jazh-151.html
deleted file mode 100644
index 4c575aa..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-151.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 00A2 CENT SIGN (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-151-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 00A2 CENT SIGN at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='ja'>中中中&#x00A2;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x00A2;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-152.html b/css/css-text/i18n/css3-text-line-break-jazh-152.html
index cf927e9..25391bc 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-152.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-152.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-154.html b/css/css-text/i18n/css3-text-line-break-jazh-154.html
index 71144f4..74e86af 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-154.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-154.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-155.html b/css/css-text/i18n/css3-text-line-break-jazh-155.html
index 744751e..cc1d9f8 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-155.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-155.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-156.html b/css/css-text/i18n/css3-text-line-break-jazh-156.html
index fb30a3f..fb82b5b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-156.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-156.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-157.html b/css/css-text/i18n/css3-text-line-break-jazh-157.html
index 7a5698d..939d99a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-157.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-157.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-158.html b/css/css-text/i18n/css3-text-line-break-jazh-158.html
index 48791ec..ff622c0 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-158.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-158.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-159.html b/css/css-text/i18n/css3-text-line-break-jazh-159.html
index f880636..e136dd3 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-159.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-159.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
@@ -29,7 +29,7 @@
 
 
 
-<div class='ref'>中中中<br/>&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中中<br/>&#xFFE0;文</div></div>
 
 
 <!--Notes:
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-160.html b/css/css-text/i18n/css3-text-line-break-jazh-160.html
index e72ada8..5b3980da 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-160.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-160.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-201.html b/css/css-text/i18n/css3-text-line-break-jazh-201.html
index aed374d..a95a909 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-201.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-201.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-202.html b/css/css-text/i18n/css3-text-line-break-jazh-202.html
index c3a0bfa..514a056 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-202.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-202.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-203.html b/css/css-text/i18n/css3-text-line-break-jazh-203.html
index 1d3c773..0c977e8 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-203.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-203.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-204.html b/css/css-text/i18n/css3-text-line-break-jazh-204.html
index 4d1ca84..553cb1c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-204.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-204.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-205.html b/css/css-text/i18n/css3-text-line-break-jazh-205.html
index 6c60e36..8be2b12 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-205.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-205.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-206.html b/css/css-text/i18n/css3-text-line-break-jazh-206.html
index 9b680c1..256f7a8 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-206.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-206.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-207.html b/css/css-text/i18n/css3-text-line-break-jazh-207.html
index 6942b00..9587653 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-207.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-207.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-208.html b/css/css-text/i18n/css3-text-line-break-jazh-208.html
index 6b6adda..be138b1 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-208.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-208.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-209.html b/css/css-text/i18n/css3-text-line-break-jazh-209.html
index 3a413c4..45344cd 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-209.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-209.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-210.html b/css/css-text/i18n/css3-text-line-break-jazh-210.html
index 3ccc65a..b198bbc 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-210.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-210.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-211.html b/css/css-text/i18n/css3-text-line-break-jazh-211.html
index ce9bbb8..abb878e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-211.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-211.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-212.html b/css/css-text/i18n/css3-text-line-break-jazh-212.html
index ac23111..c3e782c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-212.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-212.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-213.html b/css/css-text/i18n/css3-text-line-break-jazh-213.html
index 4181dc9..b7041a0 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-213.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-213.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-214.html b/css/css-text/i18n/css3-text-line-break-jazh-214.html
index 12493b8..8f02c6c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-214.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-214.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-215.html b/css/css-text/i18n/css3-text-line-break-jazh-215.html
index 0485645..d9bad94 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-215.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-215.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-216.html b/css/css-text/i18n/css3-text-line-break-jazh-216.html
index 9ae9bd9..5d33b53 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-216.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-216.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-217.html b/css/css-text/i18n/css3-text-line-break-jazh-217.html
index 9702c39..686167b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-217.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-217.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-218.html b/css/css-text/i18n/css3-text-line-break-jazh-218.html
index 82ec81a..b383e18 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-218.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-218.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-219.html b/css/css-text/i18n/css3-text-line-break-jazh-219.html
index dada86b..adb1bce 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-219.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-219.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-220.html b/css/css-text/i18n/css3-text-line-break-jazh-220.html
index ac9f2a5..44d0970 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-220.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-220.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-221.html b/css/css-text/i18n/css3-text-line-break-jazh-221.html
index 2f9ed2e..e38a1f9 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-221.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-221.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-222.html b/css/css-text/i18n/css3-text-line-break-jazh-222.html
index 25c398a..c052cd7 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-222.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-222.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-223.html b/css/css-text/i18n/css3-text-line-break-jazh-223.html
index 85996cf..b80cfff 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-223.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-223.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-224.html b/css/css-text/i18n/css3-text-line-break-jazh-224.html
index e62a07936..b543c7a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-224.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-224.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-225.html b/css/css-text/i18n/css3-text-line-break-jazh-225.html
index f9b1e42..548b29a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-225.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-225.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-226.html b/css/css-text/i18n/css3-text-line-break-jazh-226.html
index ff21f1b..6e22171 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-226.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-226.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-227.html b/css/css-text/i18n/css3-text-line-break-jazh-227.html
index 0d2e13f..eb4add9 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-227.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-227.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-228.html b/css/css-text/i18n/css3-text-line-break-jazh-228.html
index 57acef9..8c617d1 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-228.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-228.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-229.html b/css/css-text/i18n/css3-text-line-break-jazh-229.html
index f87a47c..8f7d4b4 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-229.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-229.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-230.html b/css/css-text/i18n/css3-text-line-break-jazh-230.html
index 56d1948..69e4eba 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-230.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-230.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-231.html b/css/css-text/i18n/css3-text-line-break-jazh-231.html
index 1e2af51..1edadfe 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-231.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-231.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-232.html b/css/css-text/i18n/css3-text-line-break-jazh-232.html
index a565366..6085a92 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-232.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-232.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-233.html b/css/css-text/i18n/css3-text-line-break-jazh-233.html
index 5db695a..573aa29 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-233.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-233.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-234.html b/css/css-text/i18n/css3-text-line-break-jazh-234.html
index 89a9e6f..d4b10bf 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-234.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-234.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-235.html b/css/css-text/i18n/css3-text-line-break-jazh-235.html
index cc6364e..5116116 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-235.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-235.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-236.html b/css/css-text/i18n/css3-text-line-break-jazh-236.html
index 6b09d4b..8f07514 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-236.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-236.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-237.html b/css/css-text/i18n/css3-text-line-break-jazh-237.html
index 86ad0fb..05bee48 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-237.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-237.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-238.html b/css/css-text/i18n/css3-text-line-break-jazh-238.html
index cd80d6b..0929f0d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-238.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-238.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-239.html b/css/css-text/i18n/css3-text-line-break-jazh-239.html
index 21bd97e..3d8447b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-239.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-239.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-240.html b/css/css-text/i18n/css3-text-line-break-jazh-240.html
index 1ecff54..605515d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-240.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-240.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-241.html b/css/css-text/i18n/css3-text-line-break-jazh-241.html
index a16bd1d..ae99e8d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-241.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-241.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-242.html b/css/css-text/i18n/css3-text-line-break-jazh-242.html
index 175bac9..f43243b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-242.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-242.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-243.html b/css/css-text/i18n/css3-text-line-break-jazh-243.html
index 2487e6a..a2f3c42 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-243.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-243.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-244.html b/css/css-text/i18n/css3-text-line-break-jazh-244.html
index 0370867..1431628 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-244.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-244.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-245.html b/css/css-text/i18n/css3-text-line-break-jazh-245.html
index b14ecf6..462e805 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-245.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-245.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-246.html b/css/css-text/i18n/css3-text-line-break-jazh-246.html
index 4e28cdd..9c277f8 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-246.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-246.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-247.html b/css/css-text/i18n/css3-text-line-break-jazh-247.html
index 2545e48..c3cb257 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-247.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-247.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-248.html b/css/css-text/i18n/css3-text-line-break-jazh-248.html
index 27991d6..8e58a6a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-248.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-248.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-249.html b/css/css-text/i18n/css3-text-line-break-jazh-249.html
index db017b4..f2f4d93 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-249.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-249.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-250.html b/css/css-text/i18n/css3-text-line-break-jazh-250.html
index ab56bd7..f7f7959 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-250.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-250.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-251.html b/css/css-text/i18n/css3-text-line-break-jazh-251.html
index 96634d6..9a2e401 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-251.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-251.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-252.html b/css/css-text/i18n/css3-text-line-break-jazh-252.html
index 68c0048..a1903d7 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-252.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-252.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-254.html b/css/css-text/i18n/css3-text-line-break-jazh-254.html
index 0ef6d7d..67961f1 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-254.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-254.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-255.html b/css/css-text/i18n/css3-text-line-break-jazh-255.html
index 8b75133..a8a2181 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-255.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-255.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-256.html b/css/css-text/i18n/css3-text-line-break-jazh-256.html
index d0d429e..38a0996 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-256.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-256.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-257.html b/css/css-text/i18n/css3-text-line-break-jazh-257.html
index 504bd5d..a6bccf5 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-257.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-257.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-258.html b/css/css-text/i18n/css3-text-line-break-jazh-258.html
index e2adbac..228dcb2 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-258.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-258.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-259.html b/css/css-text/i18n/css3-text-line-break-jazh-259.html
index 5f56412..8080bac 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-259.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-259.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
@@ -29,7 +29,7 @@
 
 
 
-<div class='ref'>中中<br/>中&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中<br/>中&#xFFE0;文</div></div>
 
 
 <!--Notes:
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-260.html b/css/css-text/i18n/css3-text-line-break-jazh-260.html
index 34a4dc7..8fc5bf9 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-260.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-260.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-301.html b/css/css-text/i18n/css3-text-line-break-jazh-301.html
index 44be8e8..fc4588a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-301.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-301.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-302.html b/css/css-text/i18n/css3-text-line-break-jazh-302.html
index 0178867..a2bf96e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-302.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-302.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-303.html b/css/css-text/i18n/css3-text-line-break-jazh-303.html
index 874447a..0023b1c 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-303.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-303.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-304.html b/css/css-text/i18n/css3-text-line-break-jazh-304.html
index 8e6ec19..f616605 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-304.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-304.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-305.html b/css/css-text/i18n/css3-text-line-break-jazh-305.html
index 7feca2a..29327d5 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-305.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-305.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-306.html b/css/css-text/i18n/css3-text-line-break-jazh-306.html
index 8da6fe9..54505e8 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-306.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-306.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-307.html b/css/css-text/i18n/css3-text-line-break-jazh-307.html
index 41618ca..678bcee 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-307.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-307.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-308.html b/css/css-text/i18n/css3-text-line-break-jazh-308.html
index f8bd523..3e1c5b3 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-308.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-308.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-309.html b/css/css-text/i18n/css3-text-line-break-jazh-309.html
index dc36b81..78f2425 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-309.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-309.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-310.html b/css/css-text/i18n/css3-text-line-break-jazh-310.html
index 61b056d..b043a4b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-310.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-310.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-311.html b/css/css-text/i18n/css3-text-line-break-jazh-311.html
index 6b29659..448020e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-311.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-311.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-312.html b/css/css-text/i18n/css3-text-line-break-jazh-312.html
index 160fab7..45cb49d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-312.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-312.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-313.html b/css/css-text/i18n/css3-text-line-break-jazh-313.html
index b4f5cf9..d8617d7 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-313.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-313.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-314.html b/css/css-text/i18n/css3-text-line-break-jazh-314.html
index ce034c6..dd25133 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-314.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-314.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-315.html b/css/css-text/i18n/css3-text-line-break-jazh-315.html
index 200450a..579c2ae 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-315.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-315.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-316.html b/css/css-text/i18n/css3-text-line-break-jazh-316.html
index b5fb351..dd7489b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-316.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-316.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-317.html b/css/css-text/i18n/css3-text-line-break-jazh-317.html
index e98d755..34407c6 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-317.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-317.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-318.html b/css/css-text/i18n/css3-text-line-break-jazh-318.html
index 85b65c4..ca2006b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-318.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-318.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-319.html b/css/css-text/i18n/css3-text-line-break-jazh-319.html
index c789773..52b037f 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-319.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-319.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-320.html b/css/css-text/i18n/css3-text-line-break-jazh-320.html
index 900ec46..9365a82 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-320.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-320.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-321.html b/css/css-text/i18n/css3-text-line-break-jazh-321.html
index c48c382..619119f 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-321.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-321.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-322.html b/css/css-text/i18n/css3-text-line-break-jazh-322.html
index f0f820b..f121866 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-322.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-322.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-323.html b/css/css-text/i18n/css3-text-line-break-jazh-323.html
index 42d0e9f..10ea9d7 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-323.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-323.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-324.html b/css/css-text/i18n/css3-text-line-break-jazh-324.html
index 393a41d..745eae2 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-324.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-324.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-325.html b/css/css-text/i18n/css3-text-line-break-jazh-325.html
index e6dcd5f..8964578 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-325.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-325.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-326.html b/css/css-text/i18n/css3-text-line-break-jazh-326.html
index 67b195a..3662910 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-326.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-326.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-327.html b/css/css-text/i18n/css3-text-line-break-jazh-327.html
index 42f5795..dcfeec2 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-327.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-327.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-351.html b/css/css-text/i18n/css3-text-line-break-jazh-351.html
index 9556cd8..0288a64 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-351.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-351.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-352.html b/css/css-text/i18n/css3-text-line-break-jazh-352.html
index 5f27525..59d57d9 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-352.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-352.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-353.html b/css/css-text/i18n/css3-text-line-break-jazh-353.html
index ebd19c7..e54d1ea 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-353.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-353.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-354.html b/css/css-text/i18n/css3-text-line-break-jazh-354.html
index dbfea29..90573a2 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-354.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-354.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-355.html b/css/css-text/i18n/css3-text-line-break-jazh-355.html
index 3fc16d2..003bbd6 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-355.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-355.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-356.html b/css/css-text/i18n/css3-text-line-break-jazh-356.html
deleted file mode 100644
index 6f681e7..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-356.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003A COLON (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-356-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 003A COLON at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='zh'>中中中&#x003A;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003A;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-357.html b/css/css-text/i18n/css3-text-line-break-jazh-357.html
deleted file mode 100644
index 11f9dd3..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-357.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003B SEMICOLON (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-357-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 003B SEMICOLON at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='zh'>中中中&#x003B;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003B;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-358.html b/css/css-text/i18n/css3-text-line-break-jazh-358.html
index eb223b2..505a6ce 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-358.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-358.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-359.html b/css/css-text/i18n/css3-text-line-break-jazh-359.html
index 510f8c5..797f742 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-359.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-359.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-360.html b/css/css-text/i18n/css3-text-line-break-jazh-360.html
deleted file mode 100644
index adf4783..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-360.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0021 EXCLAMATION MARK (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-360-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 0021 EXCLAMATION MARK at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='zh'>中中中&#x0021;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0021;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-361.html b/css/css-text/i18n/css3-text-line-break-jazh-361.html
deleted file mode 100644
index a052e31..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-361.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003F QUESTION MARK (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-361-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 003F QUESTION MARK at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='zh'>中中中&#x003F;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003F;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-362.html b/css/css-text/i18n/css3-text-line-break-jazh-362.html
index 0bff4c4..d9eb2fb 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-362.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-362.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-363.html b/css/css-text/i18n/css3-text-line-break-jazh-363.html
index 560eba2..f72075e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-363.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-363.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-364.html b/css/css-text/i18n/css3-text-line-break-jazh-364.html
index 5e29a9d..41ff40e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-364.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-364.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-365.html b/css/css-text/i18n/css3-text-line-break-jazh-365.html
index bfa27b9..3c58f92 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-365.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-365.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-366.html b/css/css-text/i18n/css3-text-line-break-jazh-366.html
index 64c9199..bb6e1e8 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-366.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-366.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-367.html b/css/css-text/i18n/css3-text-line-break-jazh-367.html
index 1831d3e..43a5bab 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-367.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-367.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-368.html b/css/css-text/i18n/css3-text-line-break-jazh-368.html
deleted file mode 100644
index e950f8f..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-368.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0025 PERCENT SIGN (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-368-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 0025 PERCENT SIGN at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='zh'>中中中&#x0025;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-369.html b/css/css-text/i18n/css3-text-line-break-jazh-369.html
deleted file mode 100644
index 6550c12..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-369.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 00A2 CENT SIGN (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-369-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 00A2 CENT SIGN at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='zh'>中中中&#x00A2;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x00A2;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-370.html b/css/css-text/i18n/css3-text-line-break-jazh-370.html
index ed40fd2..cc64573 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-370.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-370.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-371.html b/css/css-text/i18n/css3-text-line-break-jazh-371.html
deleted file mode 100644
index e5b54cc..0000000
--- a/css/css-text/i18n/css3-text-line-break-jazh-371.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0025 PERCENT SIGN (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-break'>
-<link rel="match" href="reference/css3-text-line-break-jazh-371-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will allow 0025 PERCENT SIGN at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test' lang='zh'>中中中&#x0025;文</div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-<!--Notes:
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-372.html b/css/css-text/i18n/css3-text-line-break-jazh-372.html
index abd3e1a..2239909 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-372.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-372.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-373.html b/css/css-text/i18n/css3-text-line-break-jazh-373.html
index 7ff0ff8..f48ea99 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-373.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-373.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-374.html b/css/css-text/i18n/css3-text-line-break-jazh-374.html
index 9fd044a..adb1fc9 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-374.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-374.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-375.html b/css/css-text/i18n/css3-text-line-break-jazh-375.html
index 9a7a596..67ab6ce 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-375.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-375.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-376.html b/css/css-text/i18n/css3-text-line-break-jazh-376.html
index 19b11be..d4894d3 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-376.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-376.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-377.html b/css/css-text/i18n/css3-text-line-break-jazh-377.html
index 87a6869..7617879 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-377.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-377.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-401.html b/css/css-text/i18n/css3-text-line-break-jazh-401.html
index fdc018f..e23b742 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-401.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-401.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-402.html b/css/css-text/i18n/css3-text-line-break-jazh-402.html
index 0e84be4..00e446e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-402.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-402.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-403.html b/css/css-text/i18n/css3-text-line-break-jazh-403.html
index 8646bb3..a4db632 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-403.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-403.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-404.html b/css/css-text/i18n/css3-text-line-break-jazh-404.html
index 1350b82..36f967a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-404.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-404.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-405.html b/css/css-text/i18n/css3-text-line-break-jazh-405.html
index d471395..8bee630 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-405.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-405.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-406.html b/css/css-text/i18n/css3-text-line-break-jazh-406.html
index b3320ad..bc64155 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-406.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-406.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-407.html b/css/css-text/i18n/css3-text-line-break-jazh-407.html
index 90518be..7c384ed 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-407.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-407.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-408.html b/css/css-text/i18n/css3-text-line-break-jazh-408.html
index e511bb8..de17327 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-408.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-408.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-409.html b/css/css-text/i18n/css3-text-line-break-jazh-409.html
index 46625fc..ecaea8a 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-409.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-409.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-410.html b/css/css-text/i18n/css3-text-line-break-jazh-410.html
index bfe0aa8..924040d 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-410.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-410.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-411.html b/css/css-text/i18n/css3-text-line-break-jazh-411.html
index 79415ca..0fb1df0 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-411.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-411.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-412.html b/css/css-text/i18n/css3-text-line-break-jazh-412.html
index dbcd94a..557a613 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-412.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-412.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-413.html b/css/css-text/i18n/css3-text-line-break-jazh-413.html
index 9ba9017..3664122 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-413.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-413.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-414.html b/css/css-text/i18n/css3-text-line-break-jazh-414.html
index 99f8d67..b63c1e9 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-414.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-414.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-415.html b/css/css-text/i18n/css3-text-line-break-jazh-415.html
index 9bf83dc..a70d799 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-415.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-415.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-416.html b/css/css-text/i18n/css3-text-line-break-jazh-416.html
index 86b927e..a100744 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-416.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-416.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-417.html b/css/css-text/i18n/css3-text-line-break-jazh-417.html
index 77927b1..8323214 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-417.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-417.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-418.html b/css/css-text/i18n/css3-text-line-break-jazh-418.html
index e833b39..9c64b7b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-418.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-418.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-419.html b/css/css-text/i18n/css3-text-line-break-jazh-419.html
index bbb7d74..7d7019b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-419.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-419.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-420.html b/css/css-text/i18n/css3-text-line-break-jazh-420.html
index 56761e7..79eb64b 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-420.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-420.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-421.html b/css/css-text/i18n/css3-text-line-break-jazh-421.html
index 6d07781..239efc3 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-421.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-421.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-422.html b/css/css-text/i18n/css3-text-line-break-jazh-422.html
index 64311cb..89c59de 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-422.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-422.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-423.html b/css/css-text/i18n/css3-text-line-break-jazh-423.html
index fc5d7d5..40f50ca 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-423.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-423.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-424.html b/css/css-text/i18n/css3-text-line-break-jazh-424.html
index 0decaac..52c7e0f 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-424.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-424.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-425.html b/css/css-text/i18n/css3-text-line-break-jazh-425.html
index 2e88090..94f3343 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-425.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-425.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-426.html b/css/css-text/i18n/css3-text-line-break-jazh-426.html
index 2ed0fd1..bc0074e 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-426.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-426.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-jazh-427.html b/css/css-text/i18n/css3-text-line-break-jazh-427.html
index c6512ab..2ef1129 100644
--- a/css/css-text/i18n/css3-text-line-break-jazh-427.html
+++ b/css/css-text/i18n/css3-text-line-break-jazh-427.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-001.html b/css/css-text/i18n/css3-text-line-break-opclns-001.html
index 3016578..65d7327 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-001.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-001.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-002.html b/css/css-text/i18n/css3-text-line-break-opclns-002.html
index a5c9068..ca03be5 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-002.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-002.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-003.html b/css/css-text/i18n/css3-text-line-break-opclns-003.html
index d0d9bef..ec0b661 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-003.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-003.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-004.html b/css/css-text/i18n/css3-text-line-break-opclns-004.html
index 4708ae5..686f380 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-004.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-004.html
@@ -10,12 +10,10 @@
 <meta name="assert" content="Because it has OP Opening Punctuation property, the browser will not leave 0F3A TIBETAN MARK GUG RTAGS GYON at the end of a line.">
 <style type='text/css'>
 @font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
+	font-family: CSSFW;
+	src: url('/fonts/adobe-fonts/CSSFWOrientationTest.otf');
 	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.test, .ref { width: 115px; }
+.test, .ref { font-size: 30px; font-family: CSSFW, sans-serif; width: 3em; padding: 0; border: 1px solid orange; line-height: 1em; }
 </style>
 </head>
 <body>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-005.html b/css/css-text/i18n/css3-text-line-break-opclns-005.html
index 24f5247..78b50f1 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-005.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-005.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-006.html b/css/css-text/i18n/css3-text-line-break-opclns-006.html
index 218fc9f..36cfeba 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-006.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-006.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-007.html b/css/css-text/i18n/css3-text-line-break-opclns-007.html
index 025c88d..aa520e1 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-007.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-007.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-008.html b/css/css-text/i18n/css3-text-line-break-opclns-008.html
index 3c7e7f7..ebc4b90 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-008.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-008.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-009.html b/css/css-text/i18n/css3-text-line-break-opclns-009.html
index 9b27f36..d0bcd7f 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-009.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-009.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-010.html b/css/css-text/i18n/css3-text-line-break-opclns-010.html
index 0df9065..89c8f3f 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-010.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-010.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-011.html b/css/css-text/i18n/css3-text-line-break-opclns-011.html
index 3e1f592..9a338ae 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-011.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-011.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-012.html b/css/css-text/i18n/css3-text-line-break-opclns-012.html
index 57c85b2..9d6ea3d 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-012.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-012.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-013.html b/css/css-text/i18n/css3-text-line-break-opclns-013.html
deleted file mode 100644
index 54c0c0a..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-013.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 23B4 TOP SQUARE BRACKET</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-013-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="Because it has OP Opening Punctuation property, the browser will not leave 23B4 TOP SQUARE BRACKET at the end of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>中中&#x23B4;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />&#x23B4;文</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-014.html b/css/css-text/i18n/css3-text-line-break-opclns-014.html
index c06f89f..5a25666 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-014.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-014.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-015.html b/css/css-text/i18n/css3-text-line-break-opclns-015.html
index 216862a..1408381 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-015.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-015.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-016.html b/css/css-text/i18n/css3-text-line-break-opclns-016.html
index bcf80f1..ff9f5a7 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-016.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-016.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-017.html b/css/css-text/i18n/css3-text-line-break-opclns-017.html
index cfef2c0..f1aceca 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-017.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-017.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-018.html b/css/css-text/i18n/css3-text-line-break-opclns-018.html
index b6130a7..be8cd0c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-018.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-018.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-019.html b/css/css-text/i18n/css3-text-line-break-opclns-019.html
index e7eed71..15ef881 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-019.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-019.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-020.html b/css/css-text/i18n/css3-text-line-break-opclns-020.html
index 3553846..0d97212 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-020.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-020.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-021.html b/css/css-text/i18n/css3-text-line-break-opclns-021.html
index 0a17d83..8ad6f60 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-021.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-021.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-022.html b/css/css-text/i18n/css3-text-line-break-opclns-022.html
index e30c8b7..8d0c58f 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-022.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-022.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-023.html b/css/css-text/i18n/css3-text-line-break-opclns-023.html
index ad5a401..14e03e7 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-023.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-023.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-024.html b/css/css-text/i18n/css3-text-line-break-opclns-024.html
index 0919657..d54eccf 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-024.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-024.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-025.html b/css/css-text/i18n/css3-text-line-break-opclns-025.html
index c1ac3a4..fca5eab 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-025.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-025.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-026.html b/css/css-text/i18n/css3-text-line-break-opclns-026.html
index 060fb9f..a7f87a2 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-026.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-026.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-027.html b/css/css-text/i18n/css3-text-line-break-opclns-027.html
index d4f8848..2f00731 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-027.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-027.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-028.html b/css/css-text/i18n/css3-text-line-break-opclns-028.html
index 0901d9e..62a4f29 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-028.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-028.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-029.html b/css/css-text/i18n/css3-text-line-break-opclns-029.html
index e1224b6..003beb5 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-029.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-029.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-030.html b/css/css-text/i18n/css3-text-line-break-opclns-030.html
index d0159ef..74a3f3d 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-030.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-030.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-031.html b/css/css-text/i18n/css3-text-line-break-opclns-031.html
index 12a8e3d..c174431 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-031.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-031.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-032.html b/css/css-text/i18n/css3-text-line-break-opclns-032.html
index 2ab11f6..20094b3 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-032.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-032.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-033.html b/css/css-text/i18n/css3-text-line-break-opclns-033.html
index c589fea..ae72cbc 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-033.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-033.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-034.html b/css/css-text/i18n/css3-text-line-break-opclns-034.html
index 05eafec..37bc76b 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-034.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-034.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-035.html b/css/css-text/i18n/css3-text-line-break-opclns-035.html
index 5c8a2d8..dcaa880 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-035.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-035.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-036.html b/css/css-text/i18n/css3-text-line-break-opclns-036.html
index 9540773..ac62455 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-036.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-036.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-037.html b/css/css-text/i18n/css3-text-line-break-opclns-037.html
index c95111e..e684869 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-037.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-037.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-038.html b/css/css-text/i18n/css3-text-line-break-opclns-038.html
index 76dc4f5..0238636 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-038.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-038.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-039.html b/css/css-text/i18n/css3-text-line-break-opclns-039.html
index db3b0ae..1cc30a0 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-039.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-039.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-040.html b/css/css-text/i18n/css3-text-line-break-opclns-040.html
index df7b72c..2478f1b 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-040.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-040.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-041.html b/css/css-text/i18n/css3-text-line-break-opclns-041.html
index 6749e1e..a56e4de 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-041.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-041.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-042.html b/css/css-text/i18n/css3-text-line-break-opclns-042.html
index 33c11a9..37b9d38 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-042.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-042.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-043.html b/css/css-text/i18n/css3-text-line-break-opclns-043.html
index 71cadfd..70aaa89 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-043.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-043.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-044.html b/css/css-text/i18n/css3-text-line-break-opclns-044.html
index 5a84913..4801e30 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-044.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-044.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-045.html b/css/css-text/i18n/css3-text-line-break-opclns-045.html
index e0f677e..ab03da2 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-045.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-045.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-046.html b/css/css-text/i18n/css3-text-line-break-opclns-046.html
index 8abc726..65cd602 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-046.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-046.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-047.html b/css/css-text/i18n/css3-text-line-break-opclns-047.html
index 6e704b2..bf39278 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-047.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-047.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-048.html b/css/css-text/i18n/css3-text-line-break-opclns-048.html
deleted file mode 100644
index ad2a144..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-048.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: FD3E ORNATE LEFT PARENTHESIS</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-048-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="Because it has OP Opening Punctuation property, the browser will not leave FD3E ORNATE LEFT PARENTHESIS at the end of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>中中&#xFD3E;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />&#xFD3E;文</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-049.html b/css/css-text/i18n/css3-text-line-break-opclns-049.html
index 1f92efd..8c9a86c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-049.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-049.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-050.html b/css/css-text/i18n/css3-text-line-break-opclns-050.html
index d16cbb0..6b3d497 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-050.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-050.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-051.html b/css/css-text/i18n/css3-text-line-break-opclns-051.html
index 1d51978..c028e0b 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-051.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-051.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-052.html b/css/css-text/i18n/css3-text-line-break-opclns-052.html
index 2db8f5e..73ad096 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-052.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-052.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-053.html b/css/css-text/i18n/css3-text-line-break-opclns-053.html
index 1bfe6ba..be5dd30 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-053.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-053.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-054.html b/css/css-text/i18n/css3-text-line-break-opclns-054.html
index a038b8d..2c22489 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-054.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-054.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-055.html b/css/css-text/i18n/css3-text-line-break-opclns-055.html
index 307ad5e..2546535 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-055.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-055.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-056.html b/css/css-text/i18n/css3-text-line-break-opclns-056.html
index 8c3f85a..102cf4d 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-056.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-056.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-057.html b/css/css-text/i18n/css3-text-line-break-opclns-057.html
index ac17225..49ccc36 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-057.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-057.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-058.html b/css/css-text/i18n/css3-text-line-break-opclns-058.html
index 0e6abc9..4293849 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-058.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-058.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-059.html b/css/css-text/i18n/css3-text-line-break-opclns-059.html
index 0ba5ecc..65b95b4 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-059.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-059.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-060.html b/css/css-text/i18n/css3-text-line-break-opclns-060.html
index 62e9986..98a0423 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-060.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-060.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-061.html b/css/css-text/i18n/css3-text-line-break-opclns-061.html
index eafa68a..e351406 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-061.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-061.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-062.html b/css/css-text/i18n/css3-text-line-break-opclns-062.html
index a9af9e6..af9b75e 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-062.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-062.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-063.html b/css/css-text/i18n/css3-text-line-break-opclns-063.html
index a17ba57..fb4dee8 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-063.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-063.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-064.html b/css/css-text/i18n/css3-text-line-break-opclns-064.html
index 8f9183a..1efb48b 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-064.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-064.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-065.html b/css/css-text/i18n/css3-text-line-break-opclns-065.html
index 6e73493..c0a17ec 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-065.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-065.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-100.html b/css/css-text/i18n/css3-text-line-break-opclns-100.html
index 980d477..d97591c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-100.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-100.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-101.html b/css/css-text/i18n/css3-text-line-break-opclns-101.html
index 3a04b0a..125ea52 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-101.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-101.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-102.html b/css/css-text/i18n/css3-text-line-break-opclns-102.html
index a729343..f3e6879 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-102.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-102.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-103.html b/css/css-text/i18n/css3-text-line-break-opclns-103.html
index 2894145..5a5fd93 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-103.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-103.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-104.html b/css/css-text/i18n/css3-text-line-break-opclns-104.html
index f1772ac..e35c82a 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-104.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-104.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-105.html b/css/css-text/i18n/css3-text-line-break-opclns-105.html
index 83f4316..d8baaf0 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-105.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-105.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-106.html b/css/css-text/i18n/css3-text-line-break-opclns-106.html
index aa7b465..200ff0c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-106.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-106.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-107.html b/css/css-text/i18n/css3-text-line-break-opclns-107.html
index 5eaa58b..b76f797 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-107.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-107.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-108.html b/css/css-text/i18n/css3-text-line-break-opclns-108.html
index 9a0ad83..d33e85c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-108.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-108.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-109.html b/css/css-text/i18n/css3-text-line-break-opclns-109.html
index b9b835c..89dd7bb 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-109.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-109.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-110.html b/css/css-text/i18n/css3-text-line-break-opclns-110.html
index 70a70f5..e89c12b 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-110.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-110.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-111.html b/css/css-text/i18n/css3-text-line-break-opclns-111.html
index 6b5dea8..db0a276 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-111.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-111.html
@@ -10,12 +10,10 @@
 <meta name="assert" content="Because it has a CL Closing Punctuation property, the browser will not leave 0F3B TIBETAN MARK GUG RTAGS GYAS at the beginning of a line.">
 <style type='text/css'>
 @font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
+	font-family: CSSFW;
+	src: url('/fonts/adobe-fonts/CSSFWOrientationTest.otf');
 	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.test, .ref { width: 115px; }
+.test, .ref { font-size: 30px; font-family: CSSFW, sans-serif; width: 3em; padding: 0; border: 1px solid orange; line-height: 1em; }
 </style>
 </head>
 <body>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-112.html b/css/css-text/i18n/css3-text-line-break-opclns-112.html
index e3ed1df..01e7faa 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-112.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-112.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-113.html b/css/css-text/i18n/css3-text-line-break-opclns-113.html
index c81b8d2..314674d 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-113.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-113.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-114.html b/css/css-text/i18n/css3-text-line-break-opclns-114.html
index 5af54c9..2a9c9f2 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-114.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-114.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-115.html b/css/css-text/i18n/css3-text-line-break-opclns-115.html
index 758fbb0..eb5cbe0 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-115.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-115.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-116.html b/css/css-text/i18n/css3-text-line-break-opclns-116.html
index 107991c..21ddae5 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-116.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-116.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-117.html b/css/css-text/i18n/css3-text-line-break-opclns-117.html
index b38f7e6..7fa697a 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-117.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-117.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-118.html b/css/css-text/i18n/css3-text-line-break-opclns-118.html
deleted file mode 100644
index 5c0ebe6..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-118.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 23B5 BOTTOM SQUARE BRACKET</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-118-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="Because it has a CL Closing Punctuation property, the browser will not leave 23B5 BOTTOM SQUARE BRACKET at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>中中中&#x23B5;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#x23B5;文</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-119.html b/css/css-text/i18n/css3-text-line-break-opclns-119.html
index adfb543..d6ae588 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-119.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-119.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-120.html b/css/css-text/i18n/css3-text-line-break-opclns-120.html
index 40e2a8e..147d624 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-120.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-120.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-121.html b/css/css-text/i18n/css3-text-line-break-opclns-121.html
index d6db8b7..20e7abc 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-121.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-121.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-122.html b/css/css-text/i18n/css3-text-line-break-opclns-122.html
index 959fb7b..13ad23e 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-122.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-122.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-123.html b/css/css-text/i18n/css3-text-line-break-opclns-123.html
index ace0d6d..d0f81dc 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-123.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-123.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-124.html b/css/css-text/i18n/css3-text-line-break-opclns-124.html
index 740d21d..2c4aa74 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-124.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-124.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-125.html b/css/css-text/i18n/css3-text-line-break-opclns-125.html
index 188eb74..de99e5c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-125.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-125.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-126.html b/css/css-text/i18n/css3-text-line-break-opclns-126.html
index 12cf863..8e5a501 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-126.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-126.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-127.html b/css/css-text/i18n/css3-text-line-break-opclns-127.html
index 4bb26d3..4901e91 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-127.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-127.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-128.html b/css/css-text/i18n/css3-text-line-break-opclns-128.html
index 2bf06c9..a7362c1 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-128.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-128.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-129.html b/css/css-text/i18n/css3-text-line-break-opclns-129.html
index 37cd4e9..de172e2 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-129.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-129.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-130.html b/css/css-text/i18n/css3-text-line-break-opclns-130.html
index ddda11d..3fdc7b1 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-130.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-130.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-131.html b/css/css-text/i18n/css3-text-line-break-opclns-131.html
index 8210086..93b7063 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-131.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-131.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-132.html b/css/css-text/i18n/css3-text-line-break-opclns-132.html
index 51e90c2..8c83a80 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-132.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-132.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-133.html b/css/css-text/i18n/css3-text-line-break-opclns-133.html
index ab2b9e4..3521691 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-133.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-133.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-134.html b/css/css-text/i18n/css3-text-line-break-opclns-134.html
index cc82426..6a10266 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-134.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-134.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-135.html b/css/css-text/i18n/css3-text-line-break-opclns-135.html
index 822d44e..1f91909 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-135.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-135.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-136.html b/css/css-text/i18n/css3-text-line-break-opclns-136.html
index 378aa5a..6b0cddc 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-136.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-136.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-137.html b/css/css-text/i18n/css3-text-line-break-opclns-137.html
index 5b029ad..f6578dc 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-137.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-137.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-138.html b/css/css-text/i18n/css3-text-line-break-opclns-138.html
index 36bbcd3..6fe9782 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-138.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-138.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-139.html b/css/css-text/i18n/css3-text-line-break-opclns-139.html
index dfaffbd..9b9cb61 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-139.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-139.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-140.html b/css/css-text/i18n/css3-text-line-break-opclns-140.html
index f49ff12..676eed0 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-140.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-140.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-141.html b/css/css-text/i18n/css3-text-line-break-opclns-141.html
index 10bcff9..6b6d021 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-141.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-141.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-142.html b/css/css-text/i18n/css3-text-line-break-opclns-142.html
index e397d4c..95ddc35 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-142.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-142.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-143.html b/css/css-text/i18n/css3-text-line-break-opclns-143.html
index f533a02..d3338c2 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-143.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-143.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-144.html b/css/css-text/i18n/css3-text-line-break-opclns-144.html
index 73932c8..397cee6 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-144.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-144.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-145.html b/css/css-text/i18n/css3-text-line-break-opclns-145.html
index c89e7fb..f3c7598 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-145.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-145.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-146.html b/css/css-text/i18n/css3-text-line-break-opclns-146.html
index 2bc68d3..21570bb 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-146.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-146.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-147.html b/css/css-text/i18n/css3-text-line-break-opclns-147.html
index 48b4814..3f20bcf 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-147.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-147.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-148.html b/css/css-text/i18n/css3-text-line-break-opclns-148.html
index f4477a7..5628127 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-148.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-148.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-149.html b/css/css-text/i18n/css3-text-line-break-opclns-149.html
index 60b478f..cc2a953 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-149.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-149.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-150.html b/css/css-text/i18n/css3-text-line-break-opclns-150.html
index 16417ca..380893f 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-150.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-150.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-151.html b/css/css-text/i18n/css3-text-line-break-opclns-151.html
index 1e0c774..87c1db2 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-151.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-151.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-152.html b/css/css-text/i18n/css3-text-line-break-opclns-152.html
index 10b6b10..6f6767c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-152.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-152.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-153.html b/css/css-text/i18n/css3-text-line-break-opclns-153.html
index 5a58da5..bf0381a 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-153.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-153.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-154.html b/css/css-text/i18n/css3-text-line-break-opclns-154.html
deleted file mode 100644
index ad1ddc7..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-154.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: FD3F ORNATE RIGHT PARENTHESIS</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-154-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="Because it has a CL Closing Punctuation property, the browser will not leave FD3F ORNATE RIGHT PARENTHESIS at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>中中中&#xFD3F;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#xFD3F;文</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-155.html b/css/css-text/i18n/css3-text-line-break-opclns-155.html
index 7f03ab4..c1a26be 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-155.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-155.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-156.html b/css/css-text/i18n/css3-text-line-break-opclns-156.html
index 46c0151..d346417 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-156.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-156.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-157.html b/css/css-text/i18n/css3-text-line-break-opclns-157.html
index 511ce4e..808bb91 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-157.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-157.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-158.html b/css/css-text/i18n/css3-text-line-break-opclns-158.html
index 03c4ba8..d5d0309 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-158.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-158.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-159.html b/css/css-text/i18n/css3-text-line-break-opclns-159.html
index c728fab..296673a 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-159.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-159.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-160.html b/css/css-text/i18n/css3-text-line-break-opclns-160.html
index c58320e..1b7419f 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-160.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-160.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-161.html b/css/css-text/i18n/css3-text-line-break-opclns-161.html
index f2eb8df..11de841 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-161.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-161.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-162.html b/css/css-text/i18n/css3-text-line-break-opclns-162.html
index d9fa4b7..1e373b5 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-162.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-162.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-163.html b/css/css-text/i18n/css3-text-line-break-opclns-163.html
index b8b6752..53deaaa 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-163.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-163.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-164.html b/css/css-text/i18n/css3-text-line-break-opclns-164.html
index 5da3c4e..5a59c95 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-164.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-164.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-165.html b/css/css-text/i18n/css3-text-line-break-opclns-165.html
index 26bf8c4..48b071f 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-165.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-165.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-166.html b/css/css-text/i18n/css3-text-line-break-opclns-166.html
index e09927a..a8d357c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-166.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-166.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-167.html b/css/css-text/i18n/css3-text-line-break-opclns-167.html
index 5201b73..c37ca17 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-167.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-167.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-168.html b/css/css-text/i18n/css3-text-line-break-opclns-168.html
index 7291591..eb43c63 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-168.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-168.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-169.html b/css/css-text/i18n/css3-text-line-break-opclns-169.html
index 70b34ba..bb65486 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-169.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-169.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-170.html b/css/css-text/i18n/css3-text-line-break-opclns-170.html
index ebb5721..a67d271 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-170.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-170.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-171.html b/css/css-text/i18n/css3-text-line-break-opclns-171.html
index 516a885..1e931fa 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-171.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-171.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-200.html b/css/css-text/i18n/css3-text-line-break-opclns-200.html
index 39cff9c..57460aa 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-200.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-200.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-201.html b/css/css-text/i18n/css3-text-line-break-opclns-201.html
index df1c16e..09ccb28 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-201.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-201.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-202.html b/css/css-text/i18n/css3-text-line-break-opclns-202.html
index cc2dc2b..70280ce 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-202.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-202.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-203.html b/css/css-text/i18n/css3-text-line-break-opclns-203.html
index af74d9c..2122866 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-203.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-203.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-204.html b/css/css-text/i18n/css3-text-line-break-opclns-204.html
index 0d4dac9..d85601c 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-204.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-204.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-205.html b/css/css-text/i18n/css3-text-line-break-opclns-205.html
index b9b40ab..64eba30 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-205.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-205.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-206.html b/css/css-text/i18n/css3-text-line-break-opclns-206.html
index 322e4a8..0d26e5e 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-206.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-206.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-207.html b/css/css-text/i18n/css3-text-line-break-opclns-207.html
index daa7174..e98aeee 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-207.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-207.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-208.html b/css/css-text/i18n/css3-text-line-break-opclns-208.html
index a65fc5a..86128f8 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-208.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-208.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-209.html b/css/css-text/i18n/css3-text-line-break-opclns-209.html
index 30a4c40..1b8da91 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-209.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-209.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-210.html b/css/css-text/i18n/css3-text-line-break-opclns-210.html
index a1eb8be..7274002 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-210.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-210.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-211.html b/css/css-text/i18n/css3-text-line-break-opclns-211.html
index b8b1de3..22268f4 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-211.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-211.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-212.html b/css/css-text/i18n/css3-text-line-break-opclns-212.html
index 1e83d02..5368110 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-212.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-212.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-213.html b/css/css-text/i18n/css3-text-line-break-opclns-213.html
index 55b9a30..37f6b15 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-213.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-213.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-214.html b/css/css-text/i18n/css3-text-line-break-opclns-214.html
index ba3b457..1295120 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-214.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-214.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-215.html b/css/css-text/i18n/css3-text-line-break-opclns-215.html
index 0f103b9..ec7b51f 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-215.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-215.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-216.html b/css/css-text/i18n/css3-text-line-break-opclns-216.html
deleted file mode 100644
index 2121df1..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-216.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-216-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="Because it has an NS Non-Starter property, the browser will not leave 30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>中中中&#x30FC;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#x30FC;文</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-217.html b/css/css-text/i18n/css3-text-line-break-opclns-217.html
index bd40ba7..fdd4a3b 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-217.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-217.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-218.html b/css/css-text/i18n/css3-text-line-break-opclns-218.html
index b20bb02b..c10d0d6 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-218.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-218.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-219.html b/css/css-text/i18n/css3-text-line-break-opclns-219.html
index 2a1793b..1d991ab 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-219.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-219.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-220.html b/css/css-text/i18n/css3-text-line-break-opclns-220.html
index cd25c5c..eabf2c9 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-220.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-220.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-221.html b/css/css-text/i18n/css3-text-line-break-opclns-221.html
index 11cda59..5fdf1d5 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-221.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-221.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-222.html b/css/css-text/i18n/css3-text-line-break-opclns-222.html
index 42be6f8b..ad07476 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-222.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-222.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-223.html b/css/css-text/i18n/css3-text-line-break-opclns-223.html
index aeedb65..e156415 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-223.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-223.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-224.html b/css/css-text/i18n/css3-text-line-break-opclns-224.html
deleted file mode 100644
index 3f18e7a..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-224.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: FF70 HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-224-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="Because it has an NS Non-Starter property, the browser will not leave FF70 HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>中中中&#xFF70;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#xFF70;文</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-225.html b/css/css-text/i18n/css3-text-line-break-opclns-225.html
index ca6a36f..1bd5a3a 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-225.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-225.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-226.html b/css/css-text/i18n/css3-text-line-break-opclns-226.html
index 0625a02..70c1c6d 100644
--- a/css/css-text/i18n/css3-text-line-break-opclns-226.html
+++ b/css/css-text/i18n/css3-text-line-break-opclns-226.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-250.html b/css/css-text/i18n/css3-text-line-break-opclns-250.html
deleted file mode 100644
index fe70b71..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-250.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3041 HIRAGANA LETTER SMALL A</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-250-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3041 HIRAGANA LETTER SMALL A (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3041;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3041;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-251.html b/css/css-text/i18n/css3-text-line-break-opclns-251.html
deleted file mode 100644
index 1e41acc..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-251.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3043 HIRAGANA LETTER SMALL I</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-251-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3043 HIRAGANA LETTER SMALL I (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3043;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3043;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-252.html b/css/css-text/i18n/css3-text-line-break-opclns-252.html
deleted file mode 100644
index 55d60ab..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-252.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3045 HIRAGANA LETTER SMALL U</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-252-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3045 HIRAGANA LETTER SMALL U (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3045;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3045;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-253.html b/css/css-text/i18n/css3-text-line-break-opclns-253.html
deleted file mode 100644
index 4a5acbb..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-253.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3047 HIRAGANA LETTER SMALL E</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-253-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3047 HIRAGANA LETTER SMALL E (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3047;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3047;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-254.html b/css/css-text/i18n/css3-text-line-break-opclns-254.html
deleted file mode 100644
index 005f8f9..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-254.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3049 HIRAGANA LETTER SMALL O</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-254-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3049 HIRAGANA LETTER SMALL O (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3049;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3049;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-255.html b/css/css-text/i18n/css3-text-line-break-opclns-255.html
deleted file mode 100644
index f147aad..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-255.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3063 HIRAGANA LETTER SMALL TU</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-255-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3063 HIRAGANA LETTER SMALL TU (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3063;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3063;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-256.html b/css/css-text/i18n/css3-text-line-break-opclns-256.html
deleted file mode 100644
index 38afaeb..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-256.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3083 HIRAGANA LETTER SMALL YA</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-256-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3083 HIRAGANA LETTER SMALL YA (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3083;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3083;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-257.html b/css/css-text/i18n/css3-text-line-break-opclns-257.html
deleted file mode 100644
index cd6f95f..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-257.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3085 HIRAGANA LETTER SMALL YU</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-257-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3085 HIRAGANA LETTER SMALL YU (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3085;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3085;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-258.html b/css/css-text/i18n/css3-text-line-break-opclns-258.html
deleted file mode 100644
index 431f4b9..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-258.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 3087 HIRAGANA LETTER SMALL YO </title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-258-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 3087 HIRAGANA LETTER SMALL YO  (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x3087;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x3087;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-259.html b/css/css-text/i18n/css3-text-line-break-opclns-259.html
deleted file mode 100644
index 8bb2bd3..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-259.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 308E HIRAGANA LETTER SMALL WA </title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-259-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 308E HIRAGANA LETTER SMALL WA  (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='test'>かかか&#x308E;な</div>
-
-
-
-
-
-<div class='ref'>かか<br />か&#x308E;な</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-260.html b/css/css-text/i18n/css3-text-line-break-opclns-260.html
deleted file mode 100644
index 95b3e25..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-260.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30A1 KATAKANA LETTER SMALL A</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-260-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30A1 KATAKANA LETTER SMALL A (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30A1;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30A1;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-261.html b/css/css-text/i18n/css3-text-line-break-opclns-261.html
deleted file mode 100644
index 2336b92..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-261.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30A3 KATAKANA LETTER SMALL I</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-261-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30A3 KATAKANA LETTER SMALL I (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30A3;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30A3;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-262.html b/css/css-text/i18n/css3-text-line-break-opclns-262.html
deleted file mode 100644
index 1928589..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-262.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30A5 KATAKANA LETTER SMALL U</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-262-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30A5 KATAKANA LETTER SMALL U (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30A5;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30A5;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-263.html b/css/css-text/i18n/css3-text-line-break-opclns-263.html
deleted file mode 100644
index 59cf0b8..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-263.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30A7 KATAKANA LETTER SMALL E</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-263-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30A7 KATAKANA LETTER SMALL E (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30A7;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30A7;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-264.html b/css/css-text/i18n/css3-text-line-break-opclns-264.html
deleted file mode 100644
index 3bee434..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-264.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30A9 KATAKANA LETTER SMALL O</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-264-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30A9 KATAKANA LETTER SMALL O (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30A9;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30A9;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-265.html b/css/css-text/i18n/css3-text-line-break-opclns-265.html
deleted file mode 100644
index 8accd9e..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-265.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30C3 KATAKANA LETTER SMALL TU</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-265-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30C3 KATAKANA LETTER SMALL TU (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30C3;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30C3;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-266.html b/css/css-text/i18n/css3-text-line-break-opclns-266.html
deleted file mode 100644
index a84f9c1..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-266.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30E3 KATAKANA LETTER SMALL YA</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-266-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30E3 KATAKANA LETTER SMALL YA (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30E3;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30E3;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-267.html b/css/css-text/i18n/css3-text-line-break-opclns-267.html
deleted file mode 100644
index 3aa2848..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-267.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30E5 KATAKANA LETTER SMALL YU</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-267-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30E5 KATAKANA LETTER SMALL YU (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30E5;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30E5;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-268.html b/css/css-text/i18n/css3-text-line-break-opclns-268.html
deleted file mode 100644
index c9632a3..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-268.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30E7 KATAKANA LETTER SMALL YO</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-268-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30E7 KATAKANA LETTER SMALL YO (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30E7;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30E7;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/css3-text-line-break-opclns-269.html b/css/css-text/i18n/css3-text-line-break-opclns-269.html
deleted file mode 100644
index 6bb5d1f..0000000
--- a/css/css-text/i18n/css3-text-line-break-opclns-269.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30EE KATAKANA LETTER SMALL WA</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<link rel='help' href='https://drafts.csswg.org/css-text-3/#line-breaking'>
-<link rel="match" href="reference/css3-text-line-break-opclns-269-ref.html">
-<meta name='flags' content=''>
-<meta name="assert" content="The browser will not allow the small kana character 30EE KATAKANA LETTER SMALL WA (which has the NS Non-Starter property) at the beginning of a line.">
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-
-
-
-<div class='test'>カカカ&#x30EE;ナ</div>
-
-
-
-
-
-<div class='ref'>カカ<br />カ&#x30EE;ナ</div>
-
-
-<!--Notes:
-
-These tests examine the default behavior of characters against the expectations in the Unicode Standard Annex, version 5.1.0. The two-letter abbreviations are conventions for property names in the Unicode Standard. Non-tailorable characters have normative behavior in the Unicode Standard that applies in all normal circumstances.
-<p class='notes'>For more information about expected line break behavior and line break classes, see <a href='http://www.unicode.org/reports/tr14/'>Unicode Standard Annex #14 Line Breaking Properties</a>.
-
-
--->
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-001-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-001-ref.html
index 529c2a4..f038e69 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-001-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-001-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-002-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-002-ref.html
index 1ec32ea..695015e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-002-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-002-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-003-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-003-ref.html
index 91aa011..5dbbb7f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-003-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-003-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-004-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-004-ref.html
index 77c9834..2457628 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-004-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-004-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-005-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-005-ref.html
index 5aa94cc..560b337 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-005-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-005-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-006-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-006-ref.html
index c641c38..bec4d8c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-006-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-006-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-007-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-007-ref.html
index 88d5134..9af2627 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-007-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-007-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-008-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-008-ref.html
index 0bfd77c..a10f72c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-008-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-008-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-009-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-009-ref.html
index 54fc96c..f64c008 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-009-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-009-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-010-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-010-ref.html
index cd9d37a..f9d1390 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-010-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-010-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-011-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-011-ref.html
index 4268e41..32cf232 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-011-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-011-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-012-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-012-ref.html
index ee12521..96924df 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-012-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-012-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-013-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-013-ref.html
index 6e0ec21..a7b7384 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-013-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-013-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-014-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-014-ref.html
index 8fb910f..8a03e58 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-014-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-014-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-015-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-015-ref.html
index 9825aea..f8a4d56 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-015-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-015-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-016-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-016-ref.html
index a979fb0..8a18c2f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-016-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-016-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-017-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-017-ref.html
index 22d1d6b..d6afa5f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-017-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-017-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-018-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-018-ref.html
index 68f26b4..c638c79 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-018-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-018-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-019-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-019-ref.html
index caf621c..70989ad 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-019-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-019-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-020-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-020-ref.html
index 5a66f8f..c1f6a40 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-020-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-020-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-021-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-021-ref.html
index 29b2725..b61d7d3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-021-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-021-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-022-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-022-ref.html
index c9f1fc7..1345777 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-022-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-022-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-023-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-023-ref.html
index fac1002..5563076 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-023-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-023-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-024-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-024-ref.html
index 79b5d7f..f98f561 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-024-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-024-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-025-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-025-ref.html
index c8da937..c043885 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-025-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-025-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-026-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-026-ref.html
index ca27b4b..ea625cd 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-026-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-026-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-027-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-027-ref.html
index 2bc3e6c..32e58c0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-027-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-027-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-028-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-028-ref.html
index d16a469..cce4731 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-028-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-028-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-029-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-029-ref.html
index abde151..63d518a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-029-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-029-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-030-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-030-ref.html
index 0c53c9f..d76cbc8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-030-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-030-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-031-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-031-ref.html
index 0646987..df860b2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-031-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-031-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-032-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-032-ref.html
index 01c6be0..8316cf4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-032-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-032-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-033-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-033-ref.html
index f055bd7..237c599 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-033-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-033-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-034-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-034-ref.html
index fac561c..20a706b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-034-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-034-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-035-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-035-ref.html
index 9d1929a..0f79550 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-035-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-035-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-036-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-036-ref.html
index 503813e..30922c1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-036-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-036-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-037-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-037-ref.html
index 3dd67f6..242c27d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-037-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-037-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-038-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-038-ref.html
index 4f10989..431ed59 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-038-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-038-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-039-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-039-ref.html
index d89c92f..698bed7 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-039-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-039-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-040-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-040-ref.html
index 032c3bc..0cf77da 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-040-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-040-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-041-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-041-ref.html
index 57743e8..38a2248 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-041-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-041-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-042-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-042-ref.html
index f599f0c..51fabf7 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-042-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-042-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-043-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-043-ref.html
index 92df08c..bdf2e68 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-043-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-043-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-044-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-044-ref.html
index a6bd593..a367f67 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-044-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-044-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-045-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-045-ref.html
index ec6b839..adf8122 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-045-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-045-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-046-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-046-ref.html
index cefbc18..80758ab 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-046-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-046-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-047-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-047-ref.html
index a2b581b..08b085e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-047-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-047-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-048-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-048-ref.html
index 526e977..0e5bd4d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-048-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-048-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-049-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-049-ref.html
index 56c8f6a..f377e89 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-049-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-049-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-050-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-050-ref.html
index 04d88c7..892012a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-050-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-050-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-051-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-051-ref.html
index 5fb3475..45108e0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-051-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-051-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-052-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-052-ref.html
index e988a50..1ea1307 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-052-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-052-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-054-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-054-ref.html
index c749e5e..ad9b0fc 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-054-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-054-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-055-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-055-ref.html
index feccbc5..258a691 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-055-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-055-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-056-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-056-ref.html
index fdc82a4..df6ab01 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-056-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-056-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-057-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-057-ref.html
index df2fda9..71c6e8b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-057-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-057-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-058-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-058-ref.html
index 5a41044..649d1c3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-058-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-058-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-059-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-059-ref.html
index a2c5c48..b68f89f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-059-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-059-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
@@ -20,13 +20,13 @@
 <p class='instructions'>Test passes if the two orange boxes are identical.</p>
 
 
-<div class='ref'>中中<br/>中&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中<br/>中&#xFFE0;文</div></div>
 
 
 
 
 
-<div class='ref'>中中<br/>中&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中<br/>中&#xFFE0;文</div></div>
 
 
 
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-060-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-060-ref.html
index 4c5c7c5..fb2c83a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-060-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-060-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-101-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-101-ref.html
index 4f1cef9..7cd7292 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-101-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-101-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-102-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-102-ref.html
index 3cfe66f..2246b06 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-102-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-102-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-103-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-103-ref.html
index 8f0b4c9..b2e45e1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-103-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-103-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-104-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-104-ref.html
index 5f5b1c1..430235e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-104-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-104-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-105-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-105-ref.html
index 21736d9..87c9256 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-105-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-105-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-106-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-106-ref.html
index 651857e..6d076ed 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-106-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-106-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-107-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-107-ref.html
index e08e076..ffe87c5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-107-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-107-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-108-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-108-ref.html
index 82d275f..e417d16 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-108-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-108-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-109-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-109-ref.html
index bb71e55..b42f93c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-109-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-109-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-110-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-110-ref.html
index bc0c0c2..7780e07 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-110-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-110-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-111-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-111-ref.html
index 2d7e79f..77b0fbf 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-111-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-111-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-112-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-112-ref.html
index dff39cc..6e5bf84 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-112-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-112-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-113-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-113-ref.html
index e817f83..d471eff 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-113-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-113-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-114-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-114-ref.html
index 351dd87..2e1b8b6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-114-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-114-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-115-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-115-ref.html
index ddc058f..f33e7ec 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-115-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-115-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-116-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-116-ref.html
index b47d54e..2bd48ed 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-116-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-116-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-117-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-117-ref.html
index 5fda0f7..9ac8973 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-117-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-117-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-118-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-118-ref.html
index 19a6dec..637e227 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-118-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-118-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-119-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-119-ref.html
index 7d41cb2..a3c03a6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-119-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-119-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-120-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-120-ref.html
index 47e1096..04f0d19 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-120-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-120-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-121-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-121-ref.html
index 6bde837..10e8458 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-121-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-121-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-122-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-122-ref.html
index ba9fcef..7fd3f1a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-122-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-122-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-123-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-123-ref.html
index 56da371..65158df 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-123-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-123-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-124-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-124-ref.html
index 7cfaad5..9a934db 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-124-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-124-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-125-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-125-ref.html
index 421b200..a072368 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-125-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-125-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-126-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-126-ref.html
index 2172f51..2603949 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-126-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-126-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-127-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-127-ref.html
index 75d27a9..c2c1aa0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-127-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-127-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-128-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-128-ref.html
index 9df0980..1bdf857 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-128-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-128-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-129-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-129-ref.html
index 41b8d5d..a480560 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-129-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-129-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-130-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-130-ref.html
index 54bc27c..9e6be7a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-130-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-130-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-131-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-131-ref.html
index a494452..d574646 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-131-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-131-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-132-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-132-ref.html
index f5bfd7d..e43edb6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-132-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-132-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-133-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-133-ref.html
index 73f7933..7e0449d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-133-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-133-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-134-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-134-ref.html
index d427a77..75b0f59 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-134-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-134-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-135-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-135-ref.html
index e409d93..0135fb0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-135-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-135-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-136-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-136-ref.html
deleted file mode 100644
index 2f78be6..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-136-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003A COLON (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x003A;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003A;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-137-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-137-ref.html
deleted file mode 100644
index e09981f..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-137-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003B SEMICOLON (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x003B;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003B;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-138-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-138-ref.html
index 6de058b..f7ca6a1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-138-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-138-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-139-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-139-ref.html
index 51d93b7..8bbcee2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-139-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-139-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-140-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-140-ref.html
index a79c518..a0aab3c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-140-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-140-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-141-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-141-ref.html
index b758239..796c8bf 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-141-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-141-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-142-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-142-ref.html
deleted file mode 100644
index 68a07b4..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-142-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0021 EXCLAMATION MARK (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x0021;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0021;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-143-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-143-ref.html
deleted file mode 100644
index c515013..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-143-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003F QUESTION MARK (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x003F;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003F;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-144-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-144-ref.html
index 5b05211..ccb6f49 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-144-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-144-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-145-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-145-ref.html
index d3edb83..136ae62 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-145-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-145-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-146-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-146-ref.html
index 66cb151..6b8422a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-146-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-146-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-147-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-147-ref.html
index 03a73df..ee6b467 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-147-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-147-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-148-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-148-ref.html
index ff4fdc0..3aa5b14 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-148-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-148-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-149-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-149-ref.html
index ff98856..00a7f4f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-149-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-149-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-150-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-150-ref.html
deleted file mode 100644
index 0019808..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-150-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0025 PERCENT SIGN (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-151-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-151-ref.html
deleted file mode 100644
index e0a2f88..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-151-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 00A2 CENT SIGN (loose,ja)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x00A2;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x00A2;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-152-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-152-ref.html
index ff2b2d0..53d7f2c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-152-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-152-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-154-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-154-ref.html
index 35a99d1..a69f361 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-154-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-154-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-155-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-155-ref.html
index 28be22c..31fe768 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-155-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-155-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-156-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-156-ref.html
index ba67361..ed10733 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-156-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-156-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-157-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-157-ref.html
index 2f8503c..06bd81a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-157-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-157-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-158-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-158-ref.html
index b102fb1..2821002 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-158-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-158-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-159-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-159-ref.html
index aeee9f5..82282b7 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-159-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-159-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
@@ -20,13 +20,13 @@
 <p class='instructions'>Test passes if the two orange boxes are identical.</p>
 
 
-<div class='ref'>中中中<br/>&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中中<br/>&#xFFE0;文</div></div>
 
 
 
 
 
-<div class='ref'>中中中<br/>&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中中<br/>&#xFFE0;文</div></div>
 
 
 
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-160-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-160-ref.html
index 0eca8de..36e446d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-160-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-160-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-201-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-201-ref.html
index d65fcf8..0d3abe2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-201-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-201-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-202-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-202-ref.html
index 1fabcad..b078f52 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-202-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-202-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-203-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-203-ref.html
index 1ccca65..1da911c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-203-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-203-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-204-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-204-ref.html
index f6bb661..8711bd3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-204-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-204-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-205-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-205-ref.html
index 07f7e3c..2aedd5e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-205-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-205-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-206-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-206-ref.html
index 2980b01..149310c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-206-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-206-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-207-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-207-ref.html
index a66fa5e..1041f0c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-207-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-207-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-208-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-208-ref.html
index b3e0a06..d1f68e7 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-208-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-208-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-209-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-209-ref.html
index f282bb8..872ed64 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-209-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-209-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-210-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-210-ref.html
index 447554e..c662bec 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-210-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-210-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-211-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-211-ref.html
index 025ffdb..6cbaec6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-211-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-211-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-212-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-212-ref.html
index a6b69eb..751d21f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-212-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-212-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-213-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-213-ref.html
index 6464b8a..47724ce 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-213-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-213-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-214-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-214-ref.html
index 6f9eaae..7408ff8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-214-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-214-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-215-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-215-ref.html
index 6c3d0dd..abf0386 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-215-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-215-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-216-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-216-ref.html
index 3721645..d2de4d9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-216-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-216-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-217-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-217-ref.html
index fb139eb..e482f1b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-217-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-217-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-218-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-218-ref.html
index 9f7fdef..a8e5d27 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-218-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-218-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-219-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-219-ref.html
index 6a7fa10..37dd1a4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-219-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-219-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-220-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-220-ref.html
index 97cc92a..87fd769 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-220-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-220-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-221-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-221-ref.html
index 7dd0a98..fb15f73 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-221-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-221-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-222-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-222-ref.html
index 2b40647..2f872b5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-222-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-222-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-223-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-223-ref.html
index 9031b95..d63772e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-223-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-223-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-224-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-224-ref.html
index 5c5be39..115118f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-224-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-224-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-225-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-225-ref.html
index 2c09fae..bed7290 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-225-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-225-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-226-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-226-ref.html
index b3a0d76..06f98c6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-226-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-226-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-227-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-227-ref.html
index 9658d83..7581702 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-227-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-227-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-228-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-228-ref.html
index 1927628..fde791a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-228-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-228-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-229-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-229-ref.html
index eb1c65c..4c35acf 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-229-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-229-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-230-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-230-ref.html
index 7dc60be..7a2eb9c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-230-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-230-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-231-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-231-ref.html
index b2f5b4b..f261d27 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-231-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-231-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-232-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-232-ref.html
index 8939745..22568be 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-232-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-232-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-233-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-233-ref.html
index 5a4df07..fb0ac93 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-233-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-233-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-234-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-234-ref.html
index 4066f51..f4e84bd 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-234-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-234-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-235-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-235-ref.html
index 85a3478..59b00fa 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-235-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-235-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-236-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-236-ref.html
index 05829c7..5a4d3e1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-236-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-236-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-237-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-237-ref.html
index 207fd24..dca3f05 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-237-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-237-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-238-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-238-ref.html
index 30224d8..cb92b6b1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-238-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-238-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-239-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-239-ref.html
index 12bab42..2ee3d26 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-239-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-239-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-240-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-240-ref.html
index f00a407..d6d2294 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-240-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-240-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-241-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-241-ref.html
index 646fa03..6fdcd88 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-241-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-241-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-242-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-242-ref.html
index ec4f145..bf92e87 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-242-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-242-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-243-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-243-ref.html
index 667be36..7c52a11 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-243-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-243-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-244-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-244-ref.html
index 252bc18..a6149f1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-244-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-244-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-245-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-245-ref.html
index e302f39..c2bb165 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-245-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-245-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-246-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-246-ref.html
index f42897b..77ef571 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-246-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-246-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-247-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-247-ref.html
index 7c91652..4725e79 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-247-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-247-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-248-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-248-ref.html
index 658f966..f2cad74 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-248-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-248-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-249-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-249-ref.html
index c1f21af..bf602c4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-249-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-249-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-250-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-250-ref.html
index 7ccd793..1b75f33 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-250-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-250-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-251-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-251-ref.html
index 3c78562..2e96e41 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-251-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-251-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-252-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-252-ref.html
index 837feb3..088d529 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-252-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-252-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-254-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-254-ref.html
index a472eac..a0ca36d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-254-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-254-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-255-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-255-ref.html
index 8390de8..c2d854c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-255-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-255-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-256-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-256-ref.html
index 974b7e7..aa737fd 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-256-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-256-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-257-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-257-ref.html
index 8a6049b..df1dddd 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-257-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-257-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-258-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-258-ref.html
index 819560e..c7487f0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-258-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-258-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-259-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-259-ref.html
index 68245da..fb23329 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-259-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-259-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
@@ -20,13 +20,13 @@
 <p class='instructions'>Test passes if the two orange boxes are identical.</p>
 
 
-<div class='ref'>中中<br/>中&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中<br/>中&#xFFE0;文</div></div>
 
 
 
 
 
-<div class='ref'>中中<br/>中&#xFFE0;文</div></div>
+<div class='ref' lang='ja'>中中<br/>中&#xFFE0;文</div></div>
 
 
 
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-260-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-260-ref.html
index 206a523..62b441f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-260-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-260-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-301-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-301-ref.html
index 7431b62..cc3ad23 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-301-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-301-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-302-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-302-ref.html
index 1fce738..a77e22e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-302-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-302-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-303-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-303-ref.html
index f3663d5..4492033 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-303-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-303-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-304-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-304-ref.html
index 2da5dd4..1fcc220 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-304-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-304-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-305-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-305-ref.html
index 58f718c..24db183 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-305-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-305-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-306-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-306-ref.html
index 01fc969..4293032 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-306-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-306-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-307-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-307-ref.html
index 9a8c01c..de4366f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-307-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-307-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-308-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-308-ref.html
index 4442a43..613fe43 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-308-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-308-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-309-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-309-ref.html
index 450ea39..9fb4757 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-309-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-309-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-310-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-310-ref.html
index 26fc989..0585929 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-310-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-310-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-311-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-311-ref.html
index 996b1a6..cc653fb 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-311-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-311-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-312-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-312-ref.html
index bcc2a0e..e46aa97 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-312-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-312-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-313-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-313-ref.html
index 49f4c18..f40a57e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-313-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-313-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-314-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-314-ref.html
index 266a4d7..0bb89b3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-314-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-314-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-315-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-315-ref.html
index ccb7d9e..1f08cdc 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-315-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-315-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-316-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-316-ref.html
index e5eb65e..8b599e7 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-316-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-316-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-317-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-317-ref.html
index 2009008..8adec79 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-317-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-317-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-318-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-318-ref.html
index 0abbf5e..f6030d9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-318-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-318-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-319-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-319-ref.html
index 11c2095..5f3b9f1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-319-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-319-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-320-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-320-ref.html
index e98c060..cc54c15 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-320-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-320-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-321-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-321-ref.html
index 0abbf5e..f6030d9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-321-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-321-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-322-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-322-ref.html
index e4cfcc6..c156ff5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-322-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-322-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-323-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-323-ref.html
index bf1628e..15339ff 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-323-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-323-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-324-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-324-ref.html
index 9fa3c2f..fbcc316 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-324-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-324-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-325-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-325-ref.html
index d552e6e..1b3e533 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-325-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-325-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-326-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-326-ref.html
index b23fbf3..9ee68d6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-326-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-326-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-327-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-327-ref.html
index 0073a1d..237027a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-327-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-327-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-351-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-351-ref.html
index 044b9c5..42de27d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-351-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-351-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-352-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-352-ref.html
index e443d4a..c7b9181 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-352-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-352-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-353-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-353-ref.html
index 0819ae2..aba0b2f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-353-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-353-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-354-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-354-ref.html
index 7aa1396..3aa60e8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-354-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-354-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-355-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-355-ref.html
index adc2935..d452a11 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-355-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-355-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-356-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-356-ref.html
deleted file mode 100644
index 860ca69..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-356-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003A COLON (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x003A;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003A;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-357-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-357-ref.html
deleted file mode 100644
index 505af32..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-357-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003B SEMICOLON (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x003B;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003B;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-358-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-358-ref.html
index 3b5dda4..99d59db 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-358-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-358-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-359-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-359-ref.html
index cd514d5..31ce0d6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-359-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-359-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-360-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-360-ref.html
deleted file mode 100644
index 248f422..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-360-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0021 EXCLAMATION MARK (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x0021;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0021;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-361-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-361-ref.html
deleted file mode 100644
index c537143..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-361-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 003F QUESTION MARK (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x003F;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x003F;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-362-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-362-ref.html
index 60b9edc..b030726 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-362-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-362-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-363-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-363-ref.html
index 0512120..94a27b9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-363-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-363-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-364-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-364-ref.html
index 8663223..0d1df47 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-364-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-364-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-365-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-365-ref.html
index 08a89ab..27c15fe 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-365-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-365-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-366-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-366-ref.html
index a5a198a..f22a77c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-366-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-366-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-367-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-367-ref.html
index ca3dec6..b2351f4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-367-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-367-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-368-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-368-ref.html
deleted file mode 100644
index 2a8cc27..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-368-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0025 PERCENT SIGN (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-369-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-369-ref.html
deleted file mode 100644
index 03abdb5..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-369-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 00A2 CENT SIGN (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x00A2;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x00A2;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-370-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-370-ref.html
index 96bbc1a..14233e5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-370-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-370-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-371-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-371-ref.html
deleted file mode 100644
index 2a8cc27..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-371-ref.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 0025 PERCENT SIGN (loose,zh)</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.name { font-size: 10px; }
-.test { line-break: loose; }
-</style>
-</head>
-<body>
-<p class='instructions'>Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-
-
-
-<div class='ref'>中中中<br/>&#x0025;文</div></div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-372-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-372-ref.html
index bcb5b96..4bd341b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-372-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-372-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-373-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-373-ref.html
index 29475b3..e7b8abb 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-373-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-373-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-374-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-374-ref.html
index b7daa28..8280c67 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-374-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-374-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-375-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-375-ref.html
index ccc7618..1a4098d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-375-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-375-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-376-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-376-ref.html
index 261c27f..49cdff5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-376-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-376-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-377-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-377-ref.html
index 6e2742a..bf848bb 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-377-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-377-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-401-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-401-ref.html
index 6f73f41..09cbf2f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-401-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-401-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-402-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-402-ref.html
index 7d8ea80..0408736 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-402-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-402-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-403-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-403-ref.html
index cacd300..286805c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-403-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-403-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-404-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-404-ref.html
index 6e2f6aa..02decf3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-404-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-404-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-405-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-405-ref.html
index d69377f..9e9b0c8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-405-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-405-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-406-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-406-ref.html
index 8dffd70..035c77a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-406-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-406-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-407-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-407-ref.html
index e143c88..774593d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-407-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-407-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-408-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-408-ref.html
index 5a64560..8edd279 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-408-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-408-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-409-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-409-ref.html
index 64b9719..801ad7e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-409-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-409-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-410-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-410-ref.html
index 9d7ecae..df70f56 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-410-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-410-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-411-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-411-ref.html
index a1c4e13..c865ab0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-411-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-411-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-412-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-412-ref.html
index 3b57555..dc0f4a5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-412-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-412-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-413-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-413-ref.html
index c5aa0b4..8069e62 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-413-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-413-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-414-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-414-ref.html
index 6aa35e3..09a265e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-414-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-414-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-415-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-415-ref.html
index 044e27f..df22f36 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-415-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-415-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-416-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-416-ref.html
index cf02f05..66477b1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-416-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-416-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-417-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-417-ref.html
index eb811d7..5add0af 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-417-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-417-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-418-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-418-ref.html
index 0098eb6..4421793 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-418-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-418-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-419-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-419-ref.html
index be526c3..7c3ead4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-419-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-419-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-420-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-420-ref.html
index cb1a067..0cefda8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-420-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-420-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-421-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-421-ref.html
index 0098eb6..4421793 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-421-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-421-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-422-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-422-ref.html
index a7f6073..206486d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-422-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-422-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-423-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-423-ref.html
index 634dba7..ebb86a2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-423-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-423-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-424-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-424-ref.html
index 056635e..e1bf2f3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-424-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-424-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-425-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-425-ref.html
index 3f8c452..1e3f252 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-425-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-425-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-426-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-426-ref.html
index d61999e..af31192 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-426-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-426-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-jazh-427-ref.html b/css/css-text/i18n/reference/css3-text-line-break-jazh-427-ref.html
index f590f2e..15f99c9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-jazh-427-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-jazh-427-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 93px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-001-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-001-ref.html
index 8711d6c..d3827a5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-001-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-001-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-002-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-002-ref.html
index 8dba82d..73c071c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-002-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-002-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-003-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-003-ref.html
index 9e75ce6..9ca9689 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-003-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-003-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-004-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-004-ref.html
index ebc6850..8124c12 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-004-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-004-ref.html
@@ -7,12 +7,10 @@
 <meta name='flags' content=''>
 <style type='text/css'>
 @font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
+	font-family: CSSFW;
+	src: url('/fonts/adobe-fonts/CSSFWOrientationTest.otf');
 	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.test, .ref { width: 115px; }
+.test, .ref { font-size: 30px; font-family: CSSFW, sans-serif; width: 3em; padding: 0; border: 1px solid orange; line-height: 1em; }
 </style>
 </head>
 <body>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-005-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-005-ref.html
index 6cbbc0e..0e46ad5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-005-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-005-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-006-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-006-ref.html
index 8c5615f..daeeed4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-006-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-006-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-007-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-007-ref.html
index 6100c45..6776e6a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-007-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-007-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-008-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-008-ref.html
index f68605d..8e25a1d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-008-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-008-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-009-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-009-ref.html
index cf8ed34..839b139 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-009-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-009-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-010-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-010-ref.html
index e4bdb1d..75b225e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-010-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-010-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-011-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-011-ref.html
index b749a89..fb0d865 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-011-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-011-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-012-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-012-ref.html
index 54703dd..c039c52 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-012-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-012-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-013-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-013-ref.html
deleted file mode 100644
index 71c42e2..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-013-ref.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 23B4 TOP SQUARE BRACKET</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中<br />&#x23B4;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />&#x23B4;文</div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-014-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-014-ref.html
index 4ddeb09..4e60ba2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-014-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-014-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-015-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-015-ref.html
index 42a90bd..1ca390a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-015-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-015-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-016-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-016-ref.html
index 6acd92e..4d59b93 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-016-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-016-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-017-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-017-ref.html
index f00b4fd..ed03663 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-017-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-017-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-018-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-018-ref.html
index 83ee033..1aaa849 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-018-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-018-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-019-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-019-ref.html
index e5b6118..548ac5d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-019-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-019-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-020-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-020-ref.html
index cf062f1..28f4eca 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-020-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-020-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-021-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-021-ref.html
index e9591ba..7bb2c28 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-021-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-021-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-022-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-022-ref.html
index 414fde4..df5b9c0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-022-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-022-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-023-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-023-ref.html
index a7b9a7b..7cce8d4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-023-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-023-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-024-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-024-ref.html
index c05c626..3629e8a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-024-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-024-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-025-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-025-ref.html
index 082eb56..308ebc2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-025-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-025-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-026-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-026-ref.html
index 0d93db3..c45c126 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-026-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-026-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-027-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-027-ref.html
index e966377..6265af1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-027-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-027-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-028-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-028-ref.html
index 754bcbd..de3148e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-028-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-028-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-029-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-029-ref.html
index 6fe7895..468b023 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-029-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-029-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-030-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-030-ref.html
index c83e5c8..3cb0250 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-030-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-030-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-031-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-031-ref.html
index 0c65ec3..80cfca2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-031-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-031-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-032-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-032-ref.html
index 914bf07..671f766 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-032-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-032-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-033-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-033-ref.html
index e2e4bd5..372f5dd 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-033-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-033-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-034-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-034-ref.html
index f65fab0..065ff59 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-034-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-034-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-035-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-035-ref.html
index d9d36af..5a640de 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-035-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-035-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-036-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-036-ref.html
index f4adeea..68b6ef3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-036-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-036-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-037-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-037-ref.html
index 04a0d56..eb8b6a1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-037-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-037-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-038-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-038-ref.html
index eb0f861..3839cb8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-038-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-038-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-039-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-039-ref.html
index fb8dfbb..2bf1ef9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-039-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-039-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-040-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-040-ref.html
index 6c2d482..c2b7588 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-040-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-040-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-041-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-041-ref.html
index f397a36..198d743 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-041-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-041-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-042-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-042-ref.html
index 85f8efe..d532c08 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-042-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-042-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-043-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-043-ref.html
index 1ce17e1..02bf50a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-043-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-043-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-044-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-044-ref.html
index 4b8bd9f..97c7b59 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-044-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-044-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-045-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-045-ref.html
index b3f6980..f6d4e3b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-045-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-045-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-046-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-046-ref.html
index ced2ab5..e2b57d0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-046-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-046-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-047-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-047-ref.html
index 16e3d9a..9281df4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-047-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-047-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-048-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-048-ref.html
deleted file mode 100644
index 3ccc4b7..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-048-ref.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: FD3E ORNATE LEFT PARENTHESIS</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中<br />&#xFD3E;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />&#xFD3E;文</div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-049-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-049-ref.html
index 741f024..32f681e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-049-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-049-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-050-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-050-ref.html
index c3a20a0..63a7039 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-050-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-050-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-051-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-051-ref.html
index 5883c82..ec482d3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-051-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-051-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-052-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-052-ref.html
index 15a4e00..b8dde25 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-052-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-052-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-053-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-053-ref.html
index aac35a9..81e02aa 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-053-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-053-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-054-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-054-ref.html
index 932c18d..77904a9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-054-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-054-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-055-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-055-ref.html
index 1a55f36..96b9bc1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-055-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-055-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-056-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-056-ref.html
index 2e3ddf0..56e1e23 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-056-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-056-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-057-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-057-ref.html
index 1a7778a..9b32b7a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-057-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-057-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-058-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-058-ref.html
index ce42de2..bf7ffd1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-058-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-058-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-059-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-059-ref.html
index 15cadd9..c44b1f4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-059-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-059-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-060-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-060-ref.html
index bfd8b29..938b715 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-060-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-060-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-061-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-061-ref.html
index e22136e..2e803c2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-061-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-061-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-062-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-062-ref.html
index fb8ad3f..5993718 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-062-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-062-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-063-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-063-ref.html
index 3b15bc5..5cff8aa 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-063-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-063-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-064-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-064-ref.html
index 6b28cc4..bcf1f9f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-064-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-064-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-065-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-065-ref.html
index f4700c2..9851bbd 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-065-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-065-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-100-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-100-ref.html
index b97cad5..4ff2ead 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-100-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-100-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-101-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-101-ref.html
index 5f26dfa..16ebd4d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-101-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-101-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-102-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-102-ref.html
index b2fef82..f9c58c3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-102-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-102-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-103-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-103-ref.html
index 2dddfd0..78a67ad 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-103-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-103-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-104-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-104-ref.html
index 3ebdb2b..29c95e4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-104-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-104-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-105-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-105-ref.html
index 8becdcd..935dcff 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-105-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-105-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-106-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-106-ref.html
index 196a44ea..8641c94 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-106-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-106-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-107-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-107-ref.html
index e1f2c71..3ab7951 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-107-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-107-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-108-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-108-ref.html
index c72654e..859959f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-108-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-108-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-109-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-109-ref.html
index 1dd57cf..67c80c3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-109-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-109-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-110-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-110-ref.html
index fc4e6d0..55c1a60 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-110-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-110-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-111-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-111-ref.html
index 030567e..b0c0a8b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-111-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-111-ref.html
@@ -7,12 +7,10 @@
 <meta name='flags' content=''>
 <style type='text/css'>
 @font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
+	font-family: CSSFW;
+	src: url('/fonts/adobe-fonts/CSSFWOrientationTest.otf');
 	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-.test, .ref { width: 115px; }
+.test, .ref { font-size: 30px; font-family: CSSFW, sans-serif; padding: 0; width: 3em; border: 1px solid orange; line-height: 1em; }
 </style>
 </head>
 <body>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-112-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-112-ref.html
index 1505c6d..717e3c5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-112-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-112-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-113-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-113-ref.html
index aacad6b..d0f48e5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-113-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-113-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-114-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-114-ref.html
index f8a37e2..3c89cb8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-114-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-114-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-115-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-115-ref.html
index 807386f..7c677f5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-115-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-115-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-116-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-116-ref.html
index 6524d39..a06da3d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-116-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-116-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-117-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-117-ref.html
index 9918f56..5338dc3 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-117-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-117-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-118-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-118-ref.html
deleted file mode 100644
index e59c668..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-118-ref.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 23B5 BOTTOM SQUARE BRACKET</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中<br />中&#x23B5;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#x23B5;文</div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-119-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-119-ref.html
index dcbdbab..f73bf2e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-119-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-119-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-120-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-120-ref.html
index 6f733c3..f051e3f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-120-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-120-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-121-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-121-ref.html
index 7898708..fee6089 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-121-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-121-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-122-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-122-ref.html
index 4a3529d..dab0f5d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-122-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-122-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-123-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-123-ref.html
index 739a5dd..76b68ff 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-123-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-123-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-124-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-124-ref.html
index 62f1c73..2b732cb 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-124-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-124-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-125-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-125-ref.html
index 841c655..78d4530 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-125-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-125-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-126-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-126-ref.html
index 5fb351d..dccad04 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-126-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-126-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-127-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-127-ref.html
index a1ddd3b..1605b6f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-127-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-127-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-128-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-128-ref.html
index 7038723..e283da7 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-128-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-128-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-129-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-129-ref.html
index 22e4476..dd15ec1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-129-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-129-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-130-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-130-ref.html
index 2092852..f1c2003 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-130-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-130-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-131-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-131-ref.html
index 5c815b7..07bd001 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-131-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-131-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-132-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-132-ref.html
index 29bc4fc..6519533 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-132-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-132-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-133-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-133-ref.html
index fdfaf18..8e56f61 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-133-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-133-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-134-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-134-ref.html
index baab4cc..39943a6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-134-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-134-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-135-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-135-ref.html
index 1815b4e..d4e1daa 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-135-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-135-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-136-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-136-ref.html
index 5c9fdb6..3a222be 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-136-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-136-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-137-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-137-ref.html
index c6b05d6..0ad5152 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-137-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-137-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-138-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-138-ref.html
index ab3a414..7e11d6c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-138-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-138-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-139-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-139-ref.html
index ec073ce..f8c884d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-139-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-139-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-140-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-140-ref.html
index f95093c..1b844fc 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-140-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-140-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-141-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-141-ref.html
index c2a3013..adad14a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-141-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-141-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-142-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-142-ref.html
index 300fa61..992e113 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-142-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-142-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-143-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-143-ref.html
index b56f253..c8ce064 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-143-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-143-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-144-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-144-ref.html
index 2244cdf..10bed50 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-144-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-144-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-145-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-145-ref.html
index 856a80b..34492a2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-145-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-145-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-146-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-146-ref.html
index 44e8252..29adb6f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-146-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-146-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-147-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-147-ref.html
index f8ccc94..bd57d46 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-147-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-147-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-148-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-148-ref.html
index 171101a..a516eb1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-148-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-148-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-149-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-149-ref.html
index a9db0c7..f2a4dd5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-149-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-149-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-150-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-150-ref.html
index 1a1bd5d..0b3ee5d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-150-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-150-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-151-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-151-ref.html
index 156f8d8..38bf135 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-151-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-151-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-152-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-152-ref.html
index cad7749..2f8e31b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-152-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-152-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-153-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-153-ref.html
index 56323b9..62d192d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-153-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-153-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-154-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-154-ref.html
deleted file mode 100644
index 9ebe4f5..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-154-ref.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: FD3F ORNATE RIGHT PARENTHESIS</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中<br />中&#xFD3F;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#xFD3F;文</div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-155-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-155-ref.html
index f12a36c..fe510c9 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-155-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-155-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-156-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-156-ref.html
index 757bb14..287aa82 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-156-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-156-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-157-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-157-ref.html
index 1914e8f..4c4e918 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-157-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-157-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-158-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-158-ref.html
index 5fb73c5..3309389 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-158-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-158-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-159-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-159-ref.html
index 14255c1..71fcdce 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-159-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-159-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-160-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-160-ref.html
index f1e73f4..100bef1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-160-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-160-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-161-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-161-ref.html
index 2630bac..2378a46 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-161-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-161-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-162-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-162-ref.html
index 951ce7d..a3b9f00 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-162-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-162-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-163-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-163-ref.html
index 69a6f1b..8149f16 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-163-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-163-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-164-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-164-ref.html
index 79bc53a..452a169 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-164-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-164-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-165-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-165-ref.html
index 7f083e8..e5a4d7a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-165-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-165-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-166-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-166-ref.html
index 393b51a..8c4edcc 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-166-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-166-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-167-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-167-ref.html
index 4d67db3..50ffcbd 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-167-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-167-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-168-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-168-ref.html
index 01a9c9b..ce49bd8 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-168-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-168-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-169-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-169-ref.html
index f0f081b..e19675d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-169-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-169-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-170-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-170-ref.html
index fa067fa..dde385f 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-170-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-170-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-171-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-171-ref.html
index 3874e4a..24ad24e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-171-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-171-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-200-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-200-ref.html
index ac84160..fcda746 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-200-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-200-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-201-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-201-ref.html
index f64a19c..264e196 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-201-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-201-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-202-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-202-ref.html
index ec186bb..33c1e22 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-202-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-202-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-203-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-203-ref.html
index 2c7c8fa..c5d34d1 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-203-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-203-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-204-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-204-ref.html
index 0411393..660caae 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-204-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-204-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-205-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-205-ref.html
index 0d0891e..0ed44a2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-205-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-205-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-206-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-206-ref.html
index 85fe9c6..aa8a56d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-206-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-206-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-207-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-207-ref.html
index be430e8..7848018 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-207-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-207-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-208-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-208-ref.html
index e828ca7..18c7295 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-208-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-208-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-209-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-209-ref.html
index be59fff..2ede741 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-209-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-209-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-210-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-210-ref.html
index eaf44e6..cf6402e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-210-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-210-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-211-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-211-ref.html
index dc203a5..2bf7690 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-211-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-211-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-212-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-212-ref.html
index 75edefb..cc3f3a5 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-212-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-212-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-213-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-213-ref.html
index ed760d3..14d7e57 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-213-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-213-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-214-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-214-ref.html
index 10a9424..fcaa1eb 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-214-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-214-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-215-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-215-ref.html
index d4b3de8..301baa7 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-215-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-215-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-216-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-216-ref.html
deleted file mode 100644
index 2018d6a..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-216-ref.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: 30FC KATAKANA-HIRAGANA PROLONGED SOUND MARK</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中<br />中&#x30FC;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#x30FC;文</div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-217-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-217-ref.html
index 8f1d650..1ec0df6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-217-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-217-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-218-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-218-ref.html
index ba2d293..307b1f2 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-218-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-218-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-219-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-219-ref.html
index 3acff88..b786389 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-219-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-219-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-220-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-220-ref.html
index 2af37b1..cd15967 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-220-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-220-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-221-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-221-ref.html
index 67a2850..b6dec87 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-221-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-221-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-222-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-222-ref.html
index 15312a5..0147d69 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-222-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-222-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-223-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-223-ref.html
index e87cf0a..5186d38 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-223-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-223-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-224-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-224-ref.html
deleted file mode 100644
index d874ef3..0000000
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-224-ref.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html  lang="en" >
-<head>
-<meta charset="utf-8"/>
-<title>CSS3 Text, linebreaks: FF70 HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK</title>
-<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
-<meta name='flags' content=''>
-<style type='text/css'>
-@font-face {
-	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
-	/* filesize: 803K */
-	}
-.test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
-</style>
-</head>
-<body>
-<p class="instructions">Test passes if the two orange boxes are identical.</p>
-
-
-<div class='ref'>中中<br />中&#xFF70;文</div>
-
-
-
-
-
-<div class='ref'>中中<br />中&#xFF70;文</div>
-
-
-
-
-</body>
-</html>
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-225-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-225-ref.html
index b5065f8..6f03c09 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-225-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-225-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-226-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-226-ref.html
index cf4a827..ea55b48 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-226-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-226-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-250-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-250-ref.html
index e542c14..28bd74a 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-250-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-250-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-251-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-251-ref.html
index b351ad3..a1902e0 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-251-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-251-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-252-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-252-ref.html
index 6676fe5..d3df881 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-252-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-252-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-253-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-253-ref.html
index 4279560..3b225ad 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-253-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-253-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-254-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-254-ref.html
index 0efc2d1..d45f61d 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-254-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-254-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-255-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-255-ref.html
index 7f31328..dc1db9b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-255-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-255-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-256-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-256-ref.html
index 3e527e0..d55dc72 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-256-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-256-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-257-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-257-ref.html
index dd6eefc..2730292 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-257-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-257-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-258-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-258-ref.html
index 9f8a82d..848764b 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-258-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-258-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-259-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-259-ref.html
index a488eb4..e825a25 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-259-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-259-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-260-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-260-ref.html
index 00ca5ca..e0426d6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-260-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-260-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-261-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-261-ref.html
index 45ccde2..a4860e4 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-261-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-261-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-262-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-262-ref.html
index 846828a..a81e04e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-262-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-262-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-263-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-263-ref.html
index 66711b3..971464c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-263-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-263-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-264-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-264-ref.html
index d9ccaf8..1bea8d6 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-264-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-264-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-265-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-265-ref.html
index 40f6d3d..3c89e82 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-265-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-265-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-266-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-266-ref.html
index 2b90338e..e1fc44e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-266-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-266-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-267-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-267-ref.html
index 9448918..57bcf9e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-267-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-267-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-268-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-268-ref.html
index d03b4a8..c5eea6c 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-268-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-268-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/css3-text-line-break-opclns-269-ref.html b/css/css-text/i18n/reference/css3-text-line-break-opclns-269-ref.html
index 160e0ed..1651a6e 100644
--- a/css/css-text/i18n/reference/css3-text-line-break-opclns-269-ref.html
+++ b/css/css-text/i18n/reference/css3-text-line-break-opclns-269-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'mplus-1p-regular';
-	src: url('support/mplus-1p-regular.woff') format('woff');
+	src: url('/fonts/mplus-1p-regular.woff') format('woff');
 	/* filesize: 803K */
 	}
 .test, .ref { font-size: 30px; font-family: mplus-1p-regular, sans-serif; width: 95px; padding: 0; border: 1px solid orange; line-height: 1em; }
diff --git a/css/css-text/i18n/reference/support/mplus-1p-regular.woff b/css/css-text/i18n/reference/support/mplus-1p-regular.woff
deleted file mode 100644
index 42cfff6..0000000
--- a/css/css-text/i18n/reference/support/mplus-1p-regular.woff
+++ /dev/null
Binary files differ
diff --git a/css/css-text/i18n/support/mplus-1p-regular.woff b/css/css-text/i18n/support/mplus-1p-regular.woff
deleted file mode 100644
index 42cfff6..0000000
--- a/css/css-text/i18n/support/mplus-1p-regular.woff
+++ /dev/null
Binary files differ
diff --git a/css/css-text/letter-spacing/letter-spacing-control-chars-001.html b/css/css-text/letter-spacing/letter-spacing-control-chars-001.html
new file mode 100644
index 0000000..87d071a
--- /dev/null
+++ b/css/css-text/letter-spacing/letter-spacing-control-chars-001.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>letter-spacing should not be applied to zero-width format controls</title>
+<link rel=match href="reference/letter-spacing-control-chars-001.ref.html">
+<link rel=help href="https://drafts.csswg.org/css-text-3/#letter-spacing-property">
+<style>
+.test {
+  font: 12px/2 monospace;
+  letter-spacing: 4px;
+}
+</style>
+</head>
+<body>
+The two lines below should match:<br>
+<div class=test>
+&#x200b;let&#x200b;ter&#x200b;spac&#x200b;ing&#x200b; should not be
+&#x200c;af&#x200c;fec&#x200c;ted&#x200c; by
+&#x200d;ze&#x200d;ro&#x200d;-&#x200d;width&#x200d;
+&#x2060;for&#x2060;mat&#x2060;
+&#xfeff;char&#xfeff;ac&#xfeff;ters&#xfeff;
+</div>
+<div class=test>
+letterspacing should not be
+affected by
+zero-width
+format
+characters
+</div>
+</body>
+</html>
diff --git a/css/css-text/letter-spacing/reference/letter-spacing-control-chars-001.ref.html b/css/css-text/letter-spacing/reference/letter-spacing-control-chars-001.ref.html
new file mode 100644
index 0000000..977d821
--- /dev/null
+++ b/css/css-text/letter-spacing/reference/letter-spacing-control-chars-001.ref.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>letter-spacing should not be applied to zero-width format controls</title>
+<style>
+.test {
+  font: 12px/2 monospace;
+  letter-spacing: 4px;
+}
+</style>
+</head>
+<body>
+The two lines below should match:<br>
+<div class=test>
+letterspacing should not be
+affected by
+zero-width
+format
+characters
+</div>
+<div class=test>
+letterspacing should not be
+affected by
+zero-width
+format
+characters
+</div>
+</body>
+</html>
diff --git a/css/css-text/line-break/line-break-normal-021.xht b/css/css-text/line-break/line-break-normal-021.xht
index cc9dca3..a956d30 100644
--- a/css/css-text/line-break/line-break-normal-021.xht
+++ b/css/css-text/line-break/line-break-normal-021.xht
@@ -13,7 +13,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -31,6 +31,12 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
@@ -38,46 +44,53 @@
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
 		<!-- iteration marks -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
diff --git a/css/css-text/line-break/line-break-normal-022.xht b/css/css-text/line-break/line-break-normal-022.xht
index b2ef634..22a2a8a2 100644
--- a/css/css-text/line-break/line-break-normal-022.xht
+++ b/css/css-text/line-break/line-break-normal-022.xht
@@ -13,7 +13,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -31,26 +31,35 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- inseparable characters TWO DOT LEADER -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- inseparable characters HORIZONTAL ELLIPSIS -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- inseparable characters TWO DOT LEADER -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- inseparable characters HORIZONTAL ELLIPSIS -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
diff --git a/css/css-text/line-break/line-break-normal-023.xht b/css/css-text/line-break/line-break-normal-023.xht
deleted file mode 100644
index 7202668..0000000
--- a/css/css-text/line-break/line-break-normal-023.xht
+++ /dev/null
@@ -1,152 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Text Test: line-break - normal and centered punctuation marks</title>
-		<!-- centered punctuation marks -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
-		<link rel="match" href="reference/line-break-normal-023-ref.xht" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<meta name="assert" content="This test verifies that 'line-break: normal' does not allow line breaking before centered punctuation marks such as COLON (U+003A) and SEMICOLON (U+003B)." />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			.test span {
-				line-break: normal;						// The property to be tested
-			}
-			p.test, p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- centered punctuation marks COLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks SEMICOLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH COLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
diff --git a/css/css-text/line-break/line-break-normal-023a.xht b/css/css-text/line-break/line-break-normal-023a.xht
new file mode 100644
index 0000000..7723250
--- /dev/null
+++ b/css/css-text/line-break/line-break-normal-023a.xht
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - normal and centered punctuation marks</title>
+		<!-- centered punctuation marks -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-normal-023a-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: normal' does not allow line breaking before centered punctuation marks such as COLON (U+003A) and SEMICOLON (U+003B)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: normal;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- centered punctuation marks COLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks SEMICOLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH COLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-normal-023b.xht b/css/css-text/line-break/line-break-normal-023b.xht
new file mode 100644
index 0000000..6f867c8
--- /dev/null
+++ b/css/css-text/line-break/line-break-normal-023b.xht
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - normal and centered punctuation marks</title>
+		<!-- centered punctuation marks -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-normal-023b-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: normal' does not allow line breaking before centered punctuation marks such as COLON (U+003A) and SEMICOLON (U+003B)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: normal;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-normal-024.xht b/css/css-text/line-break/line-break-normal-024.xht
deleted file mode 100644
index 01e25e0..0000000
--- a/css/css-text/line-break/line-break-normal-024.xht
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Text Test: line-break - normal and postfixes</title>
-		<!-- postfixes -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
-		<link rel="match" href="reference/line-break-normal-024-ref.xht" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<meta name="assert" content="This test verifies that 'line-break: normal' does not allow line breaking before postfixes such as PERCENT SIGN (U+0025) and CENT SIGN (U+00A2)." />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			.test span {
-				line-break: normal;						// The property to be tested
-			}
-			p.test, p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- postfixes PERCENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes CENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PER MILLE SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PRIME -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DOUBLE PRIME -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE CELSIUS -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH PERCENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH CENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
diff --git a/css/css-text/line-break/line-break-normal-024a.xht b/css/css-text/line-break/line-break-normal-024a.xht
new file mode 100644
index 0000000..cbbeff8
--- /dev/null
+++ b/css/css-text/line-break/line-break-normal-024a.xht
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - normal and postfixes</title>
+		<!-- postfixes -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-normal-024a-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: normal' does not allow line breaking before postfixes such as PERCENT SIGN (U+0025) and CENT SIGN (U+00A2)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: normal;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes PERCENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes CENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DEGREE SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PER MILLE SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PRIME -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DOUBLE PRIME -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-normal-024b.xht b/css/css-text/line-break/line-break-normal-024b.xht
new file mode 100644
index 0000000..e0da408
--- /dev/null
+++ b/css/css-text/line-break/line-break-normal-024b.xht
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - normal and postfixes</title>
+		<!-- postfixes -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-normal-024b-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: normal' does not allow line breaking before postfixes such as PERCENT SIGN (U+0025) and CENT SIGN (U+00A2)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: normal;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes DEGREE CELSIUS -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH PERCENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH CENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-normal-025.xht b/css/css-text/line-break/line-break-normal-025.xht
deleted file mode 100644
index a8e7eee..0000000
--- a/css/css-text/line-break/line-break-normal-025.xht
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Text Test: line-break - normal and prefixes</title>
-		<!-- prefixes -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
-		<link rel="match" href="reference/line-break-normal-025-ref.xht" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<meta name="assert" content="This test verifies that 'line-break: normal' does not allow line breaking before prefixes such as EURO SIGN (U+20AC) and FULLWIDTH DOLLAR SIGN (U+FF04)." />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			.test span {
-				line-break: normal;						// The property to be tested
-			}
-			p.test, p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- prefixes DOLLAR SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes POUND SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes YEN SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes EURO SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes NUMERO SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH DOLLAR SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH POUND SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH YEN SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
diff --git a/css/css-text/line-break/line-break-strict-011.xht b/css/css-text/line-break/line-break-strict-011.xht
index 904f8d8..3ba2e88 100644
--- a/css/css-text/line-break/line-break-strict-011.xht
+++ b/css/css-text/line-break/line-break-strict-011.xht
@@ -13,7 +13,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -31,51 +31,63 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL A -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x3041;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3041;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL I -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x3043;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3043;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL U -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x3045;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3045;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL E -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x3047;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3047;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL O -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x3049;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3049;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL A -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x3041;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3041;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL I -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x3043;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3043;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL U -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x3045;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3045;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL E -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x3047;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3047;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL O -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x3049;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3049;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
 
diff --git a/css/css-text/line-break/line-break-strict-012.xht b/css/css-text/line-break/line-break-strict-012.xht
index 975e965..28358b8 100644
--- a/css/css-text/line-break/line-break-strict-012.xht
+++ b/css/css-text/line-break/line-break-strict-012.xht
@@ -13,7 +13,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -31,26 +31,35 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- Katakana-Hiragana prolonged sound mark - fullwidth -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30FC;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30FC;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Katakana-Hiragana prolonged sound mark - halfwidth -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff70;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff70;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- Katakana-Hiragana prolonged sound mark - fullwidth -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30FC;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30FC;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Katakana-Hiragana prolonged sound mark - halfwidth -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff70;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff70;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
diff --git a/css/css-text/line-break/line-break-strict-013.xht b/css/css-text/line-break/line-break-strict-013.xht
index 5730da1..b946883 100644
--- a/css/css-text/line-break/line-break-strict-013.xht
+++ b/css/css-text/line-break/line-break-strict-013.xht
@@ -13,7 +13,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -31,6 +31,12 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
@@ -38,32 +44,37 @@
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
 		<!-- hyphens -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2010;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2010;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2013;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2013;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x301c;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x301c;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30a0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30a0;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2010;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2010;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2013;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2013;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x301c;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x301c;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30a0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30a0;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
diff --git a/css/css-text/line-break/line-break-strict-014.xht b/css/css-text/line-break/line-break-strict-014.xht
index 57803b7..8cb4660 100644
--- a/css/css-text/line-break/line-break-strict-014.xht
+++ b/css/css-text/line-break/line-break-strict-014.xht
@@ -13,7 +13,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -31,6 +31,12 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
@@ -38,46 +44,53 @@
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
 		<!-- iteration marks -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
diff --git a/css/css-text/line-break/line-break-strict-015.xht b/css/css-text/line-break/line-break-strict-015.xht
index 4480251..8549f73 100644
--- a/css/css-text/line-break/line-break-strict-015.xht
+++ b/css/css-text/line-break/line-break-strict-015.xht
@@ -13,7 +13,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -31,26 +31,35 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- inseparable characters TWO DOT LEADER -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- inseparable characters HORIZONTAL ELLIPSIS -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- inseparable characters TWO DOT LEADER -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- inseparable characters HORIZONTAL ELLIPSIS -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
diff --git a/css/css-text/line-break/line-break-strict-016.xht b/css/css-text/line-break/line-break-strict-016.xht
deleted file mode 100644
index a0ed04b..0000000
--- a/css/css-text/line-break/line-break-strict-016.xht
+++ /dev/null
@@ -1,152 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Text Test: line-break - strict and centered punctuation marks</title>
-		<!-- centered punctuation marks -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
-		<link rel="match" href="reference/line-break-strict-016-ref.xht" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<meta name="assert" content="This test verifies that 'line-break: strict' does not allow line breaking before centered punctuation marks such as COLON (U+003A) and SEMICOLON (U+003B)." />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			.test span {
-				line-break: strict;						// The property to be tested
-			}
-			p.test, p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- centered punctuation marks COLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks SEMICOLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH COLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
diff --git a/css/css-text/line-break/line-break-strict-016a.xht b/css/css-text/line-break/line-break-strict-016a.xht
new file mode 100644
index 0000000..55e5434
--- /dev/null
+++ b/css/css-text/line-break/line-break-strict-016a.xht
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - strict and centered punctuation marks</title>
+		<!-- centered punctuation marks -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-strict-016a-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: strict' does not allow line breaking before centered punctuation marks such as COLON (U+003A) and SEMICOLON (U+003B)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: strict;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- centered punctuation marks COLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks SEMICOLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH COLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-strict-016b.xht b/css/css-text/line-break/line-break-strict-016b.xht
new file mode 100644
index 0000000..66ed3de
--- /dev/null
+++ b/css/css-text/line-break/line-break-strict-016b.xht
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - strict and centered punctuation marks</title>
+		<!-- centered punctuation marks -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-strict-016b-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: strict' does not allow line breaking before centered punctuation marks such as COLON (U+003A) and SEMICOLON (U+003B)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: strict;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-strict-017.xht b/css/css-text/line-break/line-break-strict-017.xht
deleted file mode 100644
index f66f054..0000000
--- a/css/css-text/line-break/line-break-strict-017.xht
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Text Test: line-break - strict and postfixes</title>
-		<!-- postfixes -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
-		<link rel="match" href="reference/line-break-strict-017-ref.xht" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<meta name="assert" content="This test verifies that 'line-break: strict' does not allow line breaking before postfixes such as PERCENT SIGN (U+0025) and CENT SIGN (U+00A2)." />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			.test span {
-				line-break: strict;						// The property to be tested
-			}
-			p.test, p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- postfixes PERCENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes CENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PER MILLE SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PRIME -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DOUBLE PRIME -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE CELSIUS -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH PERCENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH CENT SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
diff --git a/css/css-text/line-break/line-break-strict-017a.xht b/css/css-text/line-break/line-break-strict-017a.xht
new file mode 100644
index 0000000..4c8f9ce
--- /dev/null
+++ b/css/css-text/line-break/line-break-strict-017a.xht
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - strict and postfixes</title>
+		<!-- postfixes -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-strict-017a-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: strict' does not allow line breaking before postfixes such as PERCENT SIGN (U+0025) and CENT SIGN (U+00A2)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: strict;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes PERCENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes CENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DEGREE SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PER MILLE SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PRIME -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DOUBLE PRIME -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-strict-017b.xht b/css/css-text/line-break/line-break-strict-017b.xht
new file mode 100644
index 0000000..95629cb
--- /dev/null
+++ b/css/css-text/line-break/line-break-strict-017b.xht
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Text Test: line-break - strict and postfixes</title>
+		<!-- postfixes -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
+		<link rel="match" href="reference/line-break-strict-017b-ref.xht" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<meta name="assert" content="This test verifies that 'line-break: strict' does not allow line breaking before postfixes such as PERCENT SIGN (U+0025) and CENT SIGN (U+00A2)." />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			.test span {
+				line-break: strict;						// The property to be tested
+			}
+			p.test, p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes DEGREE CELSIUS -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH PERCENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH CENT SIGN -->
+			<p class="test" lang="ja">
+				<span>サンプル文サンプル文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/line-break-strict-018.xht b/css/css-text/line-break/line-break-strict-018.xht
deleted file mode 100644
index 137581b..0000000
--- a/css/css-text/line-break/line-break-strict-018.xht
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Text Test: line-break - strict and prefixes</title>
-		<!-- prefixes -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<link rel="help" title="5.2. Breaking Rules for Punctuation: the 'line-break' property" href="http://www.w3.org/TR/css-text-3/#line-break" />
-		<link rel="match" href="reference/line-break-strict-018-ref.xht" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<meta name="assert" content="This test verifies that 'line-break: strict' does not allow line breaking before prefixes such as EURO SIGN (U+20AC) and FULLWIDTH DOLLAR SIGN (U+FF04)." />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			.test span {
-				line-break: strict;						// The property to be tested
-			}
-			p.test, p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- prefixes DOLLAR SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes POUND SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes YEN SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes EURO SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes NUMERO SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH DOLLAR SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH POUND SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH YEN SIGN -->
-		<p class="test" lang="ja">
-			<span>サンプル文サンプル文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
diff --git a/css/css-text/line-break/reference/line-break-normal-021-ref.xht b/css/css-text/line-break/reference/line-break-normal-021-ref.xht
index 22e4a06..1393746 100644
--- a/css/css-text/line-break/reference/line-break-normal-021-ref.xht
+++ b/css/css-text/line-break/reference/line-break-normal-021-ref.xht
@@ -10,7 +10,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -25,6 +25,12 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body  lang="en">
@@ -32,46 +38,53 @@
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
 		<!-- iteration marks -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/line-break/reference/line-break-normal-022-ref.xht b/css/css-text/line-break/reference/line-break-normal-022-ref.xht
index 6a89e03..c9508ab 100644
--- a/css/css-text/line-break/reference/line-break-normal-022-ref.xht
+++ b/css/css-text/line-break/reference/line-break-normal-022-ref.xht
@@ -10,7 +10,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -25,26 +25,35 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body  lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- inseparable characters TWO DOT LEADER -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- inseparable characters HORIZONTAL ELLIPSIS -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- inseparable characters TWO DOT LEADER -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- inseparable characters HORIZONTAL ELLIPSIS -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/line-break/reference/line-break-normal-023-ref.xht b/css/css-text/line-break/reference/line-break-normal-023-ref.xht
deleted file mode 100644
index b78c6c0..0000000
--- a/css/css-text/line-break/reference/line-break-normal-023-ref.xht
+++ /dev/null
@@ -1,146 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Reftest Reference</title>
-		<!-- reftest for line-break-normal-023.xht -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body  lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- centered punctuation marks COLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks SEMICOLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH COLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
\ No newline at end of file
diff --git a/css/css-text/line-break/reference/line-break-normal-023a-ref.xht b/css/css-text/line-break/reference/line-break-normal-023a-ref.xht
new file mode 100644
index 0000000..07fb202
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-normal-023a-ref.xht
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-normal-023.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<!-- centered punctuation marks COLON -->
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks SEMICOLON -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH COLON -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-normal-023b-ref.xht b/css/css-text/line-break/reference/line-break-normal-023b-ref.xht
new file mode 100644
index 0000000..302ddd6
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-normal-023b-ref.xht
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-normal-023.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-normal-024-ref.xht b/css/css-text/line-break/reference/line-break-normal-024-ref.xht
deleted file mode 100644
index 9e6f847..0000000
--- a/css/css-text/line-break/reference/line-break-normal-024-ref.xht
+++ /dev/null
@@ -1,106 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Reftest Reference</title>
-		<!-- reftest for line-break-normal-024.xht -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body  lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- postfixes PERCENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes CENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PER MILLE SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PRIME -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DOUBLE PRIME -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE CELSIUS -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH PERCENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH CENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
\ No newline at end of file
diff --git a/css/css-text/line-break/reference/line-break-normal-024a-ref.xht b/css/css-text/line-break/reference/line-break-normal-024a-ref.xht
new file mode 100644
index 0000000..63580b2
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-normal-024a-ref.xht
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-normal-024.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes PERCENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes CENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DEGREE SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PER MILLE SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PRIME -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DOUBLE PRIME -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-normal-024b-ref.xht b/css/css-text/line-break/reference/line-break-normal-024b-ref.xht
new file mode 100644
index 0000000..1c63f17
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-normal-024b-ref.xht
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-normal-024.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes DEGREE CELSIUS -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH PERCENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH CENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-normal-025-ref.xht b/css/css-text/line-break/reference/line-break-normal-025-ref.xht
deleted file mode 100644
index ab3b86b..0000000
--- a/css/css-text/line-break/reference/line-break-normal-025-ref.xht
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Reftest Reference</title>
-		<!-- reftest for line-break-normal-025.xht -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body  lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- prefixes DOLLAR SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes POUND SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes YEN SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes EURO SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes NUMERO SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH DOLLAR SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH POUND SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH YEN SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
\ No newline at end of file
diff --git a/css/css-text/line-break/reference/line-break-strict-011-ref.xht b/css/css-text/line-break/reference/line-break-strict-011-ref.xht
index 0b9bd19..1a2841b 100644
--- a/css/css-text/line-break/reference/line-break-strict-011-ref.xht
+++ b/css/css-text/line-break/reference/line-break-strict-011-ref.xht
@@ -10,7 +10,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -25,50 +25,62 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL A -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3041;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3041;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL I -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3043;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3043;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL U -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3045;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3045;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL E -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3047;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3047;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Japanese small kana: HIRAGANA LETTER SMALL O -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3049;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3049;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL A -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3041;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3041;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL I -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3043;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3043;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL U -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3045;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3045;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL E -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3047;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3047;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Japanese small kana: HIRAGANA LETTER SMALL O -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3049;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3049;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
 </html>
diff --git a/css/css-text/line-break/reference/line-break-strict-012-ref.xht b/css/css-text/line-break/reference/line-break-strict-012-ref.xht
index 7774ab9..0deeb9f 100644
--- a/css/css-text/line-break/reference/line-break-strict-012-ref.xht
+++ b/css/css-text/line-break/reference/line-break-strict-012-ref.xht
@@ -10,7 +10,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -25,26 +25,35 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body  lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- Katakana-Hiragana prolonged sound mark - fullwidth -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30FC;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30FC;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- Katakana-Hiragana prolonged sound mark - halfwidth -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff70;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff70;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- Katakana-Hiragana prolonged sound mark - fullwidth -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30FC;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30FC;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- Katakana-Hiragana prolonged sound mark - halfwidth -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff70;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff70;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-013-ref.xht b/css/css-text/line-break/reference/line-break-strict-013-ref.xht
index 65c3612..40e5b1d 100644
--- a/css/css-text/line-break/reference/line-break-strict-013-ref.xht
+++ b/css/css-text/line-break/reference/line-break-strict-013-ref.xht
@@ -10,7 +10,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -25,6 +25,12 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body  lang="en">
@@ -32,32 +38,37 @@
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
 		<!-- hyphens -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2010;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2010;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2013;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2013;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x301c;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x301c;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30a0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30a0;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2010;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2010;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2013;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2013;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x301c;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x301c;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30a0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30a0;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-014-ref.xht b/css/css-text/line-break/reference/line-break-strict-014-ref.xht
index 67f008d..2676a3b 100644
--- a/css/css-text/line-break/reference/line-break-strict-014-ref.xht
+++ b/css/css-text/line-break/reference/line-break-strict-014-ref.xht
@@ -10,7 +10,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -25,6 +25,12 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body  lang="en">
@@ -32,46 +38,53 @@
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
 		<!-- iteration marks -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
-		</p>
-		<hr />
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x3005;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x303b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309d;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x309e;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fd;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fe;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-015-ref.xht b/css/css-text/line-break/reference/line-break-strict-015-ref.xht
index a05fda5..5bcd3c2 100644
--- a/css/css-text/line-break/reference/line-break-strict-015-ref.xht
+++ b/css/css-text/line-break/reference/line-break-strict-015-ref.xht
@@ -10,7 +10,7 @@
 			@font-face
 			{
 				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				/* filesize: 803300 bytes (784.5 KBytes) */
 				/*
 				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
@@ -25,26 +25,35 @@
 			span.target {
 				background-color: aqua;
 			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
 		</style>
 	</head>
 	<body  lang="en">
 		<p>
 			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
 		</p>
-		<!-- inseparable characters TWO DOT LEADER -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- inseparable characters HORIZONTAL ELLIPSIS -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
-		</p>
+		<div class="wrapper">
+			<!-- inseparable characters TWO DOT LEADER -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- inseparable characters HORIZONTAL ELLIPSIS -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2026;</span>サンプル文</span>
+			</p>
+		</div>
 	</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-016-ref.xht b/css/css-text/line-break/reference/line-break-strict-016-ref.xht
deleted file mode 100644
index e0b15dc..0000000
--- a/css/css-text/line-break/reference/line-break-strict-016-ref.xht
+++ /dev/null
@@ -1,146 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Reftest Reference</title>
-		<!-- reftest for line-break-strict-016.xht -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body  lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- centered punctuation marks COLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks SEMICOLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH COLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks DOUBLE QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
\ No newline at end of file
diff --git a/css/css-text/line-break/reference/line-break-strict-016a-ref.xht b/css/css-text/line-break/reference/line-break-strict-016a-ref.xht
new file mode 100644
index 0000000..b95368f
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-strict-016a-ref.xht
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-strict-016.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- centered punctuation marks COLON -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks SEMICOLON -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks KATAKANA MIDDLE DOT -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x30fb;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH COLON -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1a;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH SEMICOLON -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1b;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks HALFWIDTH KATAKANA MIDDLE DOT -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff65;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0021;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x003f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-016b-ref.xht b/css/css-text/line-break/reference/line-break-strict-016b-ref.xht
new file mode 100644
index 0000000..57698e5
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-strict-016b-ref.xht
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-strict-016.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 5px;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x203c;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks DOUBLE QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2047;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks QUESTION EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2048;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks EXCLAMATION QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2049;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH EXCLAMATION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff01;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- centered punctuation marks FULLWIDTH QUESTION MARK -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff1f;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-017-ref.xht b/css/css-text/line-break/reference/line-break-strict-017-ref.xht
deleted file mode 100644
index 8695c67..0000000
--- a/css/css-text/line-break/reference/line-break-strict-017-ref.xht
+++ /dev/null
@@ -1,106 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Reftest Reference</title>
-		<!-- reftest for line-break-strict-017.xht -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body  lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- postfixes PERCENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes CENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PER MILLE SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes PRIME -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DOUBLE PRIME -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes DEGREE CELSIUS -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH PERCENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- postfixes FULLWIDTH CENT SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
\ No newline at end of file
diff --git a/css/css-text/line-break/reference/line-break-strict-017a-ref.xht b/css/css-text/line-break/reference/line-break-strict-017a-ref.xht
new file mode 100644
index 0000000..7234804
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-strict-017a-ref.xht
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-strict-017.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes PERCENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x0025;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes CENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00a2;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DEGREE SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x00b0;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PER MILLE SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2030;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes PRIME -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2032;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes DOUBLE PRIME -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2033;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-017b-ref.xht b/css/css-text/line-break/reference/line-break-strict-017b-ref.xht
new file mode 100644
index 0000000..3960275
--- /dev/null
+++ b/css/css-text/line-break/reference/line-break-strict-017b-ref.xht
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>CSS Reftest Reference</title>
+		<!-- reftest for line-break-strict-017.xht -->
+		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
+		<meta http-equiv="content-language" content="en, ja" />
+		<style type="text/css">
+			@font-face
+			{
+				font-family: "mplus-1p-regular";
+				src: url("/fonts/mplus-1p-regular.woff") format("woff");
+				/* filesize: 803300 bytes (784.5 KBytes) */
+				/*
+				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
+				*/
+			}
+			p.control {
+				border: 1px solid gray;
+				color: blue;
+				font-family: "mplus-1p-regular";
+				width: 10em;
+			}
+			span.target {
+				background-color: aqua;
+			}
+			div.wrapper {
+				display: inline-block;
+				border: 1px solid;
+				margin: 10px;
+				padding: 10px;
+			}
+		</style>
+	</head>
+	<body  lang="en">
+		<p>
+			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
+		</p>
+		<div class="wrapper">
+			<!-- postfixes DEGREE CELSIUS -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#x2103;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH PERCENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xff05;</span>サンプル文</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<!-- postfixes FULLWIDTH CENT SIGN -->
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+			<p class="control" lang="ja">
+				<span>サンプル文サンプル<br />文<span class="target">&#xffe0;</span>サンプル文</span>
+			</p>
+		</div>
+	</body>
+</html>
diff --git a/css/css-text/line-break/reference/line-break-strict-018-ref.xht b/css/css-text/line-break/reference/line-break-strict-018-ref.xht
deleted file mode 100644
index ca08582..0000000
--- a/css/css-text/line-break/reference/line-break-strict-018-ref.xht
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-	<head>
-		<title>CSS Reftest Reference</title>
-		<!-- reftest for line-break-strict-018.xht -->
-		<link rel="author" title="Taka Oshiyama" href="mailto:takaoshiyama@gmail.com" />
-		<meta http-equiv="content-language" content="en, ja" />
-		<style type="text/css">
-			@font-face
-			{
-				font-family: "mplus-1p-regular";
-				src: url("../support/mplus-1p-regular.woff") format("woff");
-				/* filesize: 803300 bytes (784.5 KBytes) */
-				/*
-				mplus-1p-regular.ttf can be downloaded at/from [TBD later]
-				*/
-			}
-			p.control {
-				border: 1px solid gray;
-				color: blue;
-				font-family: "mplus-1p-regular";
-				width: 10em;
-			}
-			span.target {
-				background-color: aqua;
-			}
-		</style>
-	</head>
-	<body  lang="en">
-		<p>
-			Test passes if the highlighted characters in each pair of rectangles are at the exact same horizontal position.
-		</p>
-		<!-- prefixes DOLLAR SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x0024;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes POUND SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a3;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes YEN SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x00a5;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes EURO SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x20ac;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes NUMERO SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#x2116;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH DOLLAR SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xff04;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH POUND SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe1;</span>サンプル文</span>
-		</p>
-		<hr />
-		<!-- prefixes FULLWIDTH YEN SIGN -->
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-		<p class="control" lang="ja">
-			<span>サンプル文サンプル<br />文<span class="target">&#xffe5;</span>サンプル文</span>
-		</p>
-	</body>
-</html>
\ No newline at end of file
diff --git a/css/css-text/line-breaking/line-breaking-001.html b/css/css-text/line-breaking/line-breaking-001.html
index 20b3278..1dd2296 100644
--- a/css/css-text/line-breaking/line-breaking-001.html
+++ b/css/css-text/line-breaking/line-breaking-001.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-002.html b/css/css-text/line-breaking/line-breaking-002.html
index 807711b..ac833f5 100644
--- a/css/css-text/line-breaking/line-breaking-002.html
+++ b/css/css-text/line-breaking/line-breaking-002.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-003.html b/css/css-text/line-breaking/line-breaking-003.html
index 48a8a69..bb9da00 100644
--- a/css/css-text/line-breaking/line-breaking-003.html
+++ b/css/css-text/line-breaking/line-breaking-003.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-004.html b/css/css-text/line-breaking/line-breaking-004.html
index b00b40d..7d895b8 100644
--- a/css/css-text/line-breaking/line-breaking-004.html
+++ b/css/css-text/line-breaking/line-breaking-004.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-005.html b/css/css-text/line-breaking/line-breaking-005.html
index e3e0242..113d762 100644
--- a/css/css-text/line-breaking/line-breaking-005.html
+++ b/css/css-text/line-breaking/line-breaking-005.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-006.html b/css/css-text/line-breaking/line-breaking-006.html
index 240e739..0f7ba1d 100644
--- a/css/css-text/line-breaking/line-breaking-006.html
+++ b/css/css-text/line-breaking/line-breaking-006.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-007.html b/css/css-text/line-breaking/line-breaking-007.html
index 83ac0eb..b25d628 100644
--- a/css/css-text/line-breaking/line-breaking-007.html
+++ b/css/css-text/line-breaking/line-breaking-007.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-008.html b/css/css-text/line-breaking/line-breaking-008.html
index 2f1c589..8b79b96 100644
--- a/css/css-text/line-breaking/line-breaking-008.html
+++ b/css/css-text/line-breaking/line-breaking-008.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-009.html b/css/css-text/line-breaking/line-breaking-009.html
index 37a81b3..482e0ee 100644
--- a/css/css-text/line-breaking/line-breaking-009.html
+++ b/css/css-text/line-breaking/line-breaking-009.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-010.html b/css/css-text/line-breaking/line-breaking-010.html
index c1f4d21..6e681f4 100644
--- a/css/css-text/line-breaking/line-breaking-010.html
+++ b/css/css-text/line-breaking/line-breaking-010.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-011.html b/css/css-text/line-breaking/line-breaking-011.html
index f32b86e..3e086b6 100644
--- a/css/css-text/line-breaking/line-breaking-011.html
+++ b/css/css-text/line-breaking/line-breaking-011.html
@@ -11,7 +11,7 @@
     color:green;
     width: 1em;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     white-space: normal;
     font-size: 20px;
 }
diff --git a/css/css-text/line-breaking/line-breaking-012.html b/css/css-text/line-breaking/line-breaking-012.html
new file mode 100644
index 0000000..08f956c
--- /dev/null
+++ b/css/css-text/line-breaking/line-breaking-012.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<title>CSS Text — line breaking for nowrap and floats</title>
+<meta name=assert content="When whitespace: nowrap is specified, floats should not cause line breaks">
+<link rel=help href="https://www.w3.org/TR/css-text-3/#white-space-property">
+<link rel=match href="reference/line-breaking-012-ref.html">
+<style>
+.container {
+    position: relative;
+    color:green;
+    line-height: 1em;
+    font-family: Ahem;
+    font-size: 20px;
+}
+
+.wrapper {
+    white-space: nowrap;
+}
+
+span {
+    float: left;
+}
+
+.fail {
+    color: red;
+    position: absolute;
+    left: 0;
+    top: 0;
+    z-index: -1;
+}
+</style>
+<body>
+  <p>There should be a green rectangle and no red.</p>
+  <div class="container">
+    <div class="fail">XX</div>
+    <div class="wrapper">X<span>X<span></div>
+  </div>
+</body>
+</html>
diff --git a/css/css-text/line-breaking/reference/line-breaking-001-ref.html b/css/css-text/line-breaking/reference/line-breaking-001-ref.html
index b446efd..a27cbc4 100644
--- a/css/css-text/line-breaking/reference/line-breaking-001-ref.html
+++ b/css/css-text/line-breaking/reference/line-breaking-001-ref.html
@@ -7,7 +7,7 @@
 div {
     color:green;
     line-height: 1em;
-    font-family: ahem;
+    font-family: Ahem;
     font-size: 20px;
 }
 </style>
diff --git a/css/css-text/line-breaking/reference/line-breaking-012-ref.html b/css/css-text/line-breaking/reference/line-breaking-012-ref.html
new file mode 100644
index 0000000..463dc22
--- /dev/null
+++ b/css/css-text/line-breaking/reference/line-breaking-012-ref.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<title>Reference File for line breaking tests</title>
+<style>
+div {
+    color:green;
+    line-height: 1em;
+    font-family: Ahem;
+    font-size: 20px;
+}
+</style>
+<body>
+    <p>There should be a green rectangle and no red.</p>
+    <div>XX</div>
+</body>
+</html>
diff --git a/css/css-text/overflow-wrap/overflow-wrap-break-word-001.html b/css/css-text/overflow-wrap/overflow-wrap-break-word-001.html
index 0601978..7eaa8f3 100644
--- a/css/css-text/overflow-wrap/overflow-wrap-break-word-001.html
+++ b/css/css-text/overflow-wrap/overflow-wrap-break-word-001.html
@@ -11,7 +11,7 @@
   position: relative;
   width: 100px;
   height: 100px;
-  font-family: ahem;
+  font-family: Ahem;
   color: red;
   overflow-wrap: break-word;
   font-size: 25px;
diff --git a/css/css-text/overflow-wrap/overflow-wrap-break-word-002.html b/css/css-text/overflow-wrap/overflow-wrap-break-word-002.html
new file mode 100644
index 0000000..f4e9493
--- /dev/null
+++ b/css/css-text/overflow-wrap/overflow-wrap-break-word-002.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text Test: overflow-wrap: break-word+break-spaces</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-overflow-wrap-break-word">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-overflow-wrap-break-spaces">
+<meta name="flags" content="ahem">
+<link rel="match" href="reference/overflow-wrap-break-word-002-ref.html">
+<meta name="assert" content="break-word + break-spaces do not allow a break
+between the last character of a word and the first space of a sequence of preserved spaces
+if there are other wrapping opportunities earlier in the line">
+<style>
+div {
+  white-space: pre-wrap;
+  overflow-wrap: break-word break-spaces;
+  font-family: monospace;
+  width: 5ch;
+  line-height: 1;
+  overflow: hidden;
+  height: 1em;
+}
+</style>
+
+<p>This test passes if there is nothing below this sentence.
+<div> FAIL <div>
+<!--
+white-space:pre-wrap + overflow:break-spaces should cause the spaces at the end of the line to be preserved.
+Since there is an allowed break point between the first space and the F,
+that's where the line should wrap,
+not between the L and the subsequent space.
+-->
diff --git a/css/css-text/overflow-wrap/overflow-wrap-break-word-003.html b/css/css-text/overflow-wrap/overflow-wrap-break-word-003.html
new file mode 100644
index 0000000..21e0af3
--- /dev/null
+++ b/css/css-text/overflow-wrap/overflow-wrap-break-word-003.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text Test: overflow-wrap: break-word+break-spaces</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-overflow-wrap-break-word">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-overflow-wrap-break-spaces">
+<meta name="flags" content="ahem">
+<link rel="match" href="reference/overflow-wrap-break-word-003-ref.html">
+<meta name="assert" content="break-word + break-spaces do allow a break
+between the last character of a word and the first space of a sequence of preserved spaces
+if there are no other wrapping opportunities earlier in the line">
+<style>
+div {
+  white-space: pre-wrap;
+  overflow-wrap: break-word break-spaces;
+  font-family: monospace;
+  width: 4ch;
+  line-height: 1;
+  overflow: hidden;
+  height: 2em;
+}
+</style>
+
+<p>This test passes if the word FAIL does not appear below.
+<div>PASS FAIL<div>
diff --git a/css/css-text/overflow-wrap/reference/overflow-wrap-break-word-002-ref.html b/css/css-text/overflow-wrap/reference/overflow-wrap-break-word-002-ref.html
new file mode 100644
index 0000000..5dca683
--- /dev/null
+++ b/css/css-text/overflow-wrap/reference/overflow-wrap-break-word-002-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>test reference</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+
+<p>This test passes if there is nothing below this sentence.
diff --git a/css/css-text/overflow-wrap/reference/overflow-wrap-break-word-003-ref.html b/css/css-text/overflow-wrap/reference/overflow-wrap-break-word-003-ref.html
new file mode 100644
index 0000000..6f7315d
--- /dev/null
+++ b/css/css-text/overflow-wrap/reference/overflow-wrap-break-word-003-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text Test: overflow-wrap: break-word+break-spaces</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<style>
+div {
+  font-family: monospace;
+  line-height: 1;
+}
+</style>
+
+<p>This test passes if the word FAIL does not appear below.
+<div>PASS<div>
diff --git a/css/css-text/text-align/reference/text-align-end-ref-001.html b/css/css-text/text-align/reference/text-align-end-ref-001.html
index a509357..124a45f 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-001.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-001.html
@@ -5,7 +5,7 @@
 <title>text-align: end, direction: rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-end-ref-002.html b/css/css-text/text-align/reference/text-align-end-ref-002.html
index 231c1ef..bb63bfe 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-002.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-002.html
@@ -5,7 +5,7 @@
 <title>text-align: end, direction: ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-end-ref-003.html b/css/css-text/text-align/reference/text-align-end-ref-003.html
index 16745c2..9761aee 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-003.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-003.html
@@ -5,7 +5,7 @@
 <title>text-align: end, dir=rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-end-ref-004.html b/css/css-text/text-align/reference/text-align-end-ref-004.html
index 873f58a..d286072 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-004.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-004.html
@@ -5,7 +5,7 @@
 <title>text-align: end, dir=ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-end-ref-005.html b/css/css-text/text-align/reference/text-align-end-ref-005.html
index a4fbc95..7ea79a9 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-005.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-005.html
@@ -5,7 +5,7 @@
 <title>text-align: end, direction: rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-end-ref-006.html b/css/css-text/text-align/reference/text-align-end-ref-006.html
index b988247..f4fe84f 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-006.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-006.html
@@ -5,7 +5,7 @@
 <title>text-align: end, direction: ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-end-ref-007.html b/css/css-text/text-align/reference/text-align-end-ref-007.html
index 3a6f5de..a978a47 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-007.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-007.html
@@ -5,7 +5,7 @@
 <title>text-align: end, dir=rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-end-ref-008.html b/css/css-text/text-align/reference/text-align-end-ref-008.html
index 0f2d530..b7cf327 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-008.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-008.html
@@ -5,7 +5,7 @@
 <title>text-align: end, dir=ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-end-ref-009.html b/css/css-text/text-align/reference/text-align-end-ref-009.html
index fe4c60f..32b8623 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-009.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-009.html
@@ -5,7 +5,7 @@
 <title>text-align: end, dir=auto, RTL first strong</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-end-ref-010.html b/css/css-text/text-align/reference/text-align-end-ref-010.html
index 3bfd0ab..ccfa94c 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-010.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-010.html
@@ -5,7 +5,7 @@
 <title>text-align: end, dir=auto, LTR first strong</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-end-ref-014.html b/css/css-text/text-align/reference/text-align-end-ref-014.html
index 5cf2578..b241168 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-014.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-014.html
@@ -5,7 +5,7 @@
 <title>text-align: end, pre, dir=rtl inherited</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-end-ref-015.html b/css/css-text/text-align/reference/text-align-end-ref-015.html
index 3be3bc4..a3fa03d 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-015.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-015.html
@@ -5,7 +5,7 @@
 <title>text-align: end, pre, dir=ltr inherited</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-end-ref-016.html b/css/css-text/text-align/reference/text-align-end-ref-016.html
index 51fd433..eebdcf5 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-016.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-016.html
@@ -5,7 +5,7 @@
 <title>text-align: end, pre, dir=auto</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 .rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 24px; }
 .rb2 { position: absolute; bottom: 0; left: 0; background-color: orange; width: 72px;  height: 24px; }
diff --git a/css/css-text/text-align/reference/text-align-end-ref-017.html b/css/css-text/text-align/reference/text-align-end-ref-017.html
index 2c92632..7289f14 100644
--- a/css/css-text/text-align/reference/text-align-end-ref-017.html
+++ b/css/css-text/text-align/reference/text-align-end-ref-017.html
@@ -5,7 +5,7 @@
 <title>text-align: end, pre, dir=auto on surrounding block</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-justify-ref-001.html b/css/css-text/text-align/reference/text-align-justify-ref-001.html
index 09898ab..647239e 100644
--- a/css/css-text/text-align/reference/text-align-justify-ref-001.html
+++ b/css/css-text/text-align/reference/text-align-justify-ref-001.html
@@ -5,7 +5,7 @@
 <title>text-align: justify, direction: rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justify-ref-002.html b/css/css-text/text-align/reference/text-align-justify-ref-002.html
index 0874149..c74d32c 100644
--- a/css/css-text/text-align/reference/text-align-justify-ref-002.html
+++ b/css/css-text/text-align/reference/text-align-justify-ref-002.html
@@ -5,7 +5,7 @@
 <title>text-align: justify, direction: ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justify-ref-003.html b/css/css-text/text-align/reference/text-align-justify-ref-003.html
index 5524d97..38e31fc 100644
--- a/css/css-text/text-align/reference/text-align-justify-ref-003.html
+++ b/css/css-text/text-align/reference/text-align-justify-ref-003.html
@@ -5,7 +5,7 @@
 <title>text-align: justify, dir=rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justify-ref-004.html b/css/css-text/text-align/reference/text-align-justify-ref-004.html
index d0c5867..171fa2f 100644
--- a/css/css-text/text-align/reference/text-align-justify-ref-004.html
+++ b/css/css-text/text-align/reference/text-align-justify-ref-004.html
@@ -5,7 +5,7 @@
 <title>text-align: justify, dir=ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justify-ref-005.html b/css/css-text/text-align/reference/text-align-justify-ref-005.html
index 586cc76..9e2612a 100644
--- a/css/css-text/text-align/reference/text-align-justify-ref-005.html
+++ b/css/css-text/text-align/reference/text-align-justify-ref-005.html
@@ -5,7 +5,7 @@
 <title>text-align: justify, dir=auto, RTL first strong</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justify-ref-006.html b/css/css-text/text-align/reference/text-align-justify-ref-006.html
index 98bc1ba..48cc2c6 100644
--- a/css/css-text/text-align/reference/text-align-justify-ref-006.html
+++ b/css/css-text/text-align/reference/text-align-justify-ref-006.html
@@ -5,7 +5,7 @@
 <title>text-align: justify, dir=auto, LTR first strong</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justifyall-ref-001.html b/css/css-text/text-align/reference/text-align-justifyall-ref-001.html
index cab561a..6c54f27 100644
--- a/css/css-text/text-align/reference/text-align-justifyall-ref-001.html
+++ b/css/css-text/text-align/reference/text-align-justifyall-ref-001.html
@@ -5,7 +5,7 @@
 <title>text-align: justify-all, direction: rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justifyall-ref-002.html b/css/css-text/text-align/reference/text-align-justifyall-ref-002.html
index e5e7dfd..15d89b0 100644
--- a/css/css-text/text-align/reference/text-align-justifyall-ref-002.html
+++ b/css/css-text/text-align/reference/text-align-justifyall-ref-002.html
@@ -5,7 +5,7 @@
 <title>text-align: justify-all, direction: ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justifyall-ref-003.html b/css/css-text/text-align/reference/text-align-justifyall-ref-003.html
index 2325bc5..5af7230 100644
--- a/css/css-text/text-align/reference/text-align-justifyall-ref-003.html
+++ b/css/css-text/text-align/reference/text-align-justifyall-ref-003.html
@@ -5,7 +5,7 @@
 <title>text-align: justify-all, dir=rtl</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justifyall-ref-004.html b/css/css-text/text-align/reference/text-align-justifyall-ref-004.html
index 55492ed..1c7f9c5 100644
--- a/css/css-text/text-align/reference/text-align-justifyall-ref-004.html
+++ b/css/css-text/text-align/reference/text-align-justifyall-ref-004.html
@@ -5,7 +5,7 @@
 <title>text-align: justify-all, dir=ltr</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justifyall-ref-005.html b/css/css-text/text-align/reference/text-align-justifyall-ref-005.html
index c14f564..e61054f 100644
--- a/css/css-text/text-align/reference/text-align-justifyall-ref-005.html
+++ b/css/css-text/text-align/reference/text-align-justifyall-ref-005.html
@@ -5,7 +5,7 @@
 <title>text-align: justify-all, dir=auto, RTL first strong</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-justifyall-ref-006.html b/css/css-text/text-align/reference/text-align-justifyall-ref-006.html
index a74dde6..b3a79eb 100644
--- a/css/css-text/text-align/reference/text-align-justifyall-ref-006.html
+++ b/css/css-text/text-align/reference/text-align-justifyall-ref-006.html
@@ -5,7 +5,7 @@
 <title>text-align: justify-all, dir=auto, LTR first strong</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 .rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-001.html b/css/css-text/text-align/reference/text-align-start-ref-001.html
index f245af9..323ece1 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-001.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-001.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-start-ref-002.html b/css/css-text/text-align/reference/text-align-start-ref-002.html
index 55ef0de..9242c57 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-002.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-002.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-start-ref-003.html b/css/css-text/text-align/reference/text-align-start-ref-003.html
index bb86a7e..363e20b 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-003.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-003.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-start-ref-004.html b/css/css-text/text-align/reference/text-align-start-ref-004.html
index 7c41a1a..b4b24ef 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-004.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-004.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-start-ref-005.html b/css/css-text/text-align/reference/text-align-start-ref-005.html
index 525dde6..9b7fbef 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-005.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-005.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-006.html b/css/css-text/text-align/reference/text-align-start-ref-006.html
index 9415a8a..8c3708d 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-006.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-006.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-007.html b/css/css-text/text-align/reference/text-align-start-ref-007.html
index d22b5e7..7b00ff2 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-007.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-007.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-008.html b/css/css-text/text-align/reference/text-align-start-ref-008.html
index 0c0e5eb..5773192 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-008.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-008.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-009.html b/css/css-text/text-align/reference/text-align-start-ref-009.html
index b2093bf..3177d01 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-009.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-009.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-010.html b/css/css-text/text-align/reference/text-align-start-ref-010.html
index fed7fe0..4972f25 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-010.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-010.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-014.html b/css/css-text/text-align/reference/text-align-start-ref-014.html
index 709143d..747c5e6 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-014.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-014.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-start-ref-015.html b/css/css-text/text-align/reference/text-align-start-ref-015.html
index b5a56cb..abc8797 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-015.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-015.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/reference/text-align-start-ref-016.html b/css/css-text/text-align/reference/text-align-start-ref-016.html
index 89ec38e..fbeefd6 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-016.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-016.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 24px; }
 .rb2 { position: absolute; bottom: 0; right: 0; background-color: orange; width: 72px;  height: 24px; }
diff --git a/css/css-text/text-align/reference/text-align-start-ref-017.html b/css/css-text/text-align/reference/text-align-start-ref-017.html
index 8408a30..f802d49 100644
--- a/css/css-text/text-align/reference/text-align-start-ref-017.html
+++ b/css/css-text/text-align/reference/text-align-start-ref-017.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/text-align-end-001.html b/css/css-text/text-align/text-align-end-001.html
index 7aa503d..b7baac8 100644
--- a/css/css-text/text-align/text-align-end-001.html
+++ b/css/css-text/text-align/text-align-end-001.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-end-002.html b/css/css-text/text-align/text-align-end-002.html
index 2b63d6c..e080879 100644
--- a/css/css-text/text-align/text-align-end-002.html
+++ b/css/css-text/text-align/text-align-end-002.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-end-003.html b/css/css-text/text-align/text-align-end-003.html
index a343bda..4402bf4 100644
--- a/css/css-text/text-align/text-align-end-003.html
+++ b/css/css-text/text-align/text-align-end-003.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-end-004.html b/css/css-text/text-align/text-align-end-004.html
index ea19883..cca16fe 100644
--- a/css/css-text/text-align/text-align-end-004.html
+++ b/css/css-text/text-align/text-align-end-004.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-end-005.html b/css/css-text/text-align/text-align-end-005.html
index 9db8726..9fe2dae 100644
--- a/css/css-text/text-align/text-align-end-005.html
+++ b/css/css-text/text-align/text-align-end-005.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-end-006.html b/css/css-text/text-align/text-align-end-006.html
index 4278474..3ea9fdc 100644
--- a/css/css-text/text-align/text-align-end-006.html
+++ b/css/css-text/text-align/text-align-end-006.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-end-007.html b/css/css-text/text-align/text-align-end-007.html
index 7b775d0..403770f 100644
--- a/css/css-text/text-align/text-align-end-007.html
+++ b/css/css-text/text-align/text-align-end-007.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-end-008.html b/css/css-text/text-align/text-align-end-008.html
index 3b74c4d..8c6e5ae 100644
--- a/css/css-text/text-align/text-align-end-008.html
+++ b/css/css-text/text-align/text-align-end-008.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-end-009.html b/css/css-text/text-align/text-align-end-009.html
index d33dd8f..c67fe23 100644
--- a/css/css-text/text-align/text-align-end-009.html
+++ b/css/css-text/text-align/text-align-end-009.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-end-010.html b/css/css-text/text-align/text-align-end-010.html
index 6a3f9bd..abcb7c0 100644
--- a/css/css-text/text-align/text-align-end-010.html
+++ b/css/css-text/text-align/text-align-end-010.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-end-014.html b/css/css-text/text-align/text-align-end-014.html
index 382018e..aff33e2 100644
--- a/css/css-text/text-align/text-align-end-014.html
+++ b/css/css-text/text-align/text-align-end-014.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/text-align-end-015.html b/css/css-text/text-align/text-align-end-015.html
index 4c6a390..c5d6334 100644
--- a/css/css-text/text-align/text-align-end-015.html
+++ b/css/css-text/text-align/text-align-end-015.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/text-align-end-016.html b/css/css-text/text-align/text-align-end-016.html
index f66ff4f..b646102 100644
--- a/css/css-text/text-align/text-align-end-016.html
+++ b/css/css-text/text-align/text-align-end-016.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 24px; }
 #rb2 { position: absolute; bottom: 0; left: 0; background-color: orange; width: 72px;  height: 24px; }
diff --git a/css/css-text/text-align/text-align-end-017.html b/css/css-text/text-align/text-align-end-017.html
index 3939952..9787c3e 100644
--- a/css/css-text/text-align/text-align-end-017.html
+++ b/css/css-text/text-align/text-align-end-017.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: end; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/text-align-justify-001.html b/css/css-text/text-align/text-align-justify-001.html
index 6a21b34..619f5a0 100644
--- a/css/css-text/text-align/text-align-justify-001.html
+++ b/css/css-text/text-align/text-align-justify-001.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justify-002.html b/css/css-text/text-align/text-align-justify-002.html
index 3222825..0b6b4dd 100644
--- a/css/css-text/text-align/text-align-justify-002.html
+++ b/css/css-text/text-align/text-align-justify-002.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justify-003.html b/css/css-text/text-align/text-align-justify-003.html
index 6badf69..3a0eeff 100644
--- a/css/css-text/text-align/text-align-justify-003.html
+++ b/css/css-text/text-align/text-align-justify-003.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justify-004.html b/css/css-text/text-align/text-align-justify-004.html
index 573a3a5..6389e16 100644
--- a/css/css-text/text-align/text-align-justify-004.html
+++ b/css/css-text/text-align/text-align-justify-004.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justify-005.html b/css/css-text/text-align/text-align-justify-005.html
index 0f8a4c4..e097796 100644
--- a/css/css-text/text-align/text-align-justify-005.html
+++ b/css/css-text/text-align/text-align-justify-005.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justify-006.html b/css/css-text/text-align/text-align-justify-006.html
index 3188333..79aee25 100644
--- a/css/css-text/text-align/text-align-justify-006.html
+++ b/css/css-text/text-align/text-align-justify-006.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justifyall-001.html b/css/css-text/text-align/text-align-justifyall-001.html
index 1a5fd5a..9370930 100644
--- a/css/css-text/text-align/text-align-justifyall-001.html
+++ b/css/css-text/text-align/text-align-justifyall-001.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify-all; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justifyall-002.html b/css/css-text/text-align/text-align-justifyall-002.html
index f45f7d3..2289815 100644
--- a/css/css-text/text-align/text-align-justifyall-002.html
+++ b/css/css-text/text-align/text-align-justifyall-002.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify-all; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justifyall-003.html b/css/css-text/text-align/text-align-justifyall-003.html
index dd13d5e..cc21296 100644
--- a/css/css-text/text-align/text-align-justifyall-003.html
+++ b/css/css-text/text-align/text-align-justifyall-003.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify-all; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justifyall-004.html b/css/css-text/text-align/text-align-justifyall-004.html
index 2b5707a..d483941 100644
--- a/css/css-text/text-align/text-align-justifyall-004.html
+++ b/css/css-text/text-align/text-align-justifyall-004.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify-all; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justifyall-005.html b/css/css-text/text-align/text-align-justifyall-005.html
index 451f58f..edc271c 100644
--- a/css/css-text/text-align/text-align-justifyall-005.html
+++ b/css/css-text/text-align/text-align-justifyall-005.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify-all; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; left: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-justifyall-006.html b/css/css-text/text-align/text-align-justifyall-006.html
index 14ee737..63e4b3a 100644
--- a/css/css-text/text-align/text-align-justifyall-006.html
+++ b/css/css-text/text-align/text-align-justifyall-006.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify-all; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 399px; color: orange; font: 24px/24px Ahem; }
 .ref { text-align: right; position: relative; height:72px;  }
 .rb { position: absolute;  background-color: orange;  width: 72px; }
 #rb1 { top: 0; right: 0; height: 48px;  }
diff --git a/css/css-text/text-align/text-align-start-001.html b/css/css-text/text-align/text-align-start-001.html
index 00d13db..40c6abae 100644
--- a/css/css-text/text-align/text-align-start-001.html
+++ b/css/css-text/text-align/text-align-start-001.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-start-002.html b/css/css-text/text-align/text-align-start-002.html
index 0779f70..5e2791d 100644
--- a/css/css-text/text-align/text-align-start-002.html
+++ b/css/css-text/text-align/text-align-start-002.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-start-003.html b/css/css-text/text-align/text-align-start-003.html
index 953bd65..2e1d667 100644
--- a/css/css-text/text-align/text-align-start-003.html
+++ b/css/css-text/text-align/text-align-start-003.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-start-004.html b/css/css-text/text-align/text-align-start-004.html
index 464f775..0014630 100644
--- a/css/css-text/text-align/text-align-start-004.html
+++ b/css/css-text/text-align/text-align-start-004.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 300px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 24px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 120px;  height: 24px; }
 </style>
diff --git a/css/css-text/text-align/text-align-start-005.html b/css/css-text/text-align/text-align-start-005.html
index dc8b2c0..55fe596 100644
--- a/css/css-text/text-align/text-align-start-005.html
+++ b/css/css-text/text-align/text-align-start-005.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: rtl; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-start-006.html b/css/css-text/text-align/text-align-start-006.html
index 800380f..fa9fcc6 100644
--- a/css/css-text/text-align/text-align-start-006.html
+++ b/css/css-text/text-align/text-align-start-006.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; direction: ltr; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-start-007.html b/css/css-text/text-align/text-align-start-007.html
index 3c7e34a..0774716 100644
--- a/css/css-text/text-align/text-align-start-007.html
+++ b/css/css-text/text-align/text-align-start-007.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-start-008.html b/css/css-text/text-align/text-align-start-008.html
index 8eebf98..ba5e78c 100644
--- a/css/css-text/text-align/text-align-start-008.html
+++ b/css/css-text/text-align/text-align-start-008.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-start-009.html b/css/css-text/text-align/text-align-start-009.html
index 8af677d..55753ad 100644
--- a/css/css-text/text-align/text-align-start-009.html
+++ b/css/css-text/text-align/text-align-start-009.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; right: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-start-010.html b/css/css-text/text-align/text-align-start-010.html
index c01ae71..623b3ec 100644
--- a/css/css-text/text-align/text-align-start-010.html
+++ b/css/css-text/text-align/text-align-start-010.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-align/text-align-start-014.html b/css/css-text/text-align/text-align-start-014.html
index 0852095..ac451de 100644
--- a/css/css-text/text-align/text-align-start-014.html
+++ b/css/css-text/text-align/text-align-start-014.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; right: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/text-align-start-015.html b/css/css-text/text-align/text-align-start-015.html
index 9824cee..0b2f909 100644
--- a/css/css-text/text-align/text-align-start-015.html
+++ b/css/css-text/text-align/text-align-start-015.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-align/text-align-start-016.html b/css/css-text/text-align/text-align-start-016.html
index c412365..3b8d858 100644
--- a/css/css-text/text-align/text-align-start-016.html
+++ b/css/css-text/text-align/text-align-start-016.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 24px; }
 #rb2 { position: absolute; bottom: 0; right: 0; background-color: orange; width: 72px;  height: 24px; }
diff --git a/css/css-text/text-align/text-align-start-017.html b/css/css-text/text-align/text-align-start-017.html
index e422abd..5733002 100644
--- a/css/css-text/text-align/text-align-start-017.html
+++ b/css/css-text/text-align/text-align-start-017.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: start; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 48px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 48px; }
 </style>
diff --git a/css/css-text/text-justify/reference/text-justify-ref-001.html b/css/css-text/text-justify/reference/text-justify-ref-001.html
index c5ce890..2a21405 100644
--- a/css/css-text/text-justify/reference/text-justify-ref-001.html
+++ b/css/css-text/text-justify/reference/text-justify-ref-001.html
@@ -5,7 +5,7 @@
 <title>text-justify: none</title>
 <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
 <style type='text/css'>
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 .rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 .rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-justify/text-justify-001.html b/css/css-text/text-justify/text-justify-001.html
index 4af1df9..3e2a0d8 100644
--- a/css/css-text/text-justify/text-justify-001.html
+++ b/css/css-text/text-justify/text-justify-001.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { text-align: justify; text-justify: none; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px ahem; }
+.test, .ref { border: 1px solid orange;  margin: 20px; width: 290px; color: orange; font: 24px/24px Ahem; }
 .ref { position: relative;  height: 72px; }
 #rb1 { position: absolute; top: 0; left: 0; background-color: orange; width: 72px;  height: 72px; }
 #rb2 { position: absolute; top: 0; left: 96px; background-color: orange; width: 72px;  height: 72px; }
diff --git a/css/css-text/text-justify/text-justify-002.html b/css/css-text/text-justify/text-justify-002.html
index b3e9700..ef16eaa 100644
--- a/css/css-text/text-justify/text-justify-002.html
+++ b/css/css-text/text-justify/text-justify-002.html
@@ -9,7 +9,7 @@
 <style type='text/css'>
 .test { text-align: justify; text-justify: inter-character; }
 /* the CSS below is not part of the test */
-.test { border: 1px solid blue;  margin: 20px; width: 290px; color: blue; font: 24px/24px ahem; }
+.test { border: 1px solid blue;  margin: 20px; width: 290px; color: blue; font: 24px/24px Ahem; }
 </style>
 </head>
 <body>
diff --git a/css/css-text/text-justify/text-justify-003.html b/css/css-text/text-justify/text-justify-003.html
index 6381a96..6d181fa 100644
--- a/css/css-text/text-justify/text-justify-003.html
+++ b/css/css-text/text-justify/text-justify-003.html
@@ -9,7 +9,7 @@
 <style type='text/css'>
 .test { text-align: justify; text-justify: distribute; }
 /* the CSS below is not part of the test */
-.test { border: 1px solid blue;  margin: 20px; width: 290px; color: blue; font: 24px/24px ahem; }
+.test { border: 1px solid blue;  margin: 20px; width: 290px; color: blue; font: 24px/24px Ahem; }
 </style>
 </head>
 <body>
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-001-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-001-ref.html
index 30f42ab..afb8ba0 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-001-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-001-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-003-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-003-ref.html
index d0b0b81..72c023f 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-003-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-003-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-005-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-005-ref.html
index 84450bf..2d4e590 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-005-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-005-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-007-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-007-ref.html
index 826a3e8..18d644a 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-007-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-007-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-009-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-009-ref.html
index 0b29e0b..fcfcd33 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-009-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-009-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-010-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-010-ref.html
index f18cbd8..19f0c37 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-010-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-010-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-011-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-011-ref.html
index ab952b8..abd595d 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-011-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-011-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-014-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-014-ref.html
index d880a91..d97eeec 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-014-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-014-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-016-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-016-ref.html
index 4a6a1ed..ac84d5f 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-016-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-016-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-018-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-018-ref.html
index 29e6061..6d4e35a 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-018-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-018-ref.html
@@ -8,8 +8,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-020-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-020-ref.html
index 98e6b32..30cb39c 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-020-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-020-ref.html
@@ -7,8 +7,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-022-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-022-ref.html
index 74069f7..a888563 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-022-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-022-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-024-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-024-ref.html
index 06ac0eb..20bc68d 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-024-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-024-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-026-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-026-ref.html
index 1f5bced..6a0cb13 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-026-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-026-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-028-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-028-ref.html
index a52c5a9..a4df1a0 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-028-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-028-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansDeseret-Regular.ttf') format('truetype');
+	src: url('/fonts/noto/NotoSansDeseret-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-capitalize-030-ref.html b/css/css-text/text-transform/reference/text-transform-capitalize-030-ref.html
index be7b8e5..d5c5df4 100644
--- a/css/css-text/text-transform/reference/text-transform-capitalize-030-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-capitalize-030-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
+	src: url('/fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-001-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-001-ref.html
index 20ebd3e..fd9b5a7 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-001-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-001-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-002-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-002-ref.html
index f70532f..ae73270 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-002-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-002-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-003-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-003-ref.html
index 042af4b..bc8750d 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-003-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-003-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-004-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-004-ref.html
index b35f94f..99d7c91 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-004-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-004-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-005-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-005-ref.html
index 90fdc1d..5919ccd 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-005-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-005-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-006-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-006-ref.html
index 6937ee5..ebcc114 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-006-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-006-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-007-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-007-ref.html
index 1921066..255d7fc 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-007-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-007-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-008-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-008-ref.html
index d7f9311..b794e11 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-008-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-008-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-009-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-009-ref.html
index 4a32196..446fb2c 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-009-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-009-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-010-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-010-ref.html
index 36cbecf..d2c61f9 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-010-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-010-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-011-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-011-ref.html
index e103cb3..17f8026 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-011-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-011-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-012-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-012-ref.html
index 95eef9f..52e3aea 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-012-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-012-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-014-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-014-ref.html
index 629c3e2..b9d1a91 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-014-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-014-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-015-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-015-ref.html
index 72e0a53..fa71792 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-015-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-015-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-016-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-016-ref.html
index 98116cc..e900de1 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-016-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-016-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-017-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-017-ref.html
index a6f1e5c..4f727de 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-017-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-017-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-018-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-018-ref.html
index ca4500e..bcb4a58 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-018-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-018-ref.html
@@ -8,8 +8,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-019-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-019-ref.html
index 4b9d3b3..f63be7d 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-019-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-019-ref.html
@@ -8,8 +8,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-020-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-020-ref.html
index 322937f..5c757b3 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-020-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-020-ref.html
@@ -7,8 +7,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-021-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-021-ref.html
index 9f9f8cd..842a212 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-021-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-021-ref.html
@@ -7,8 +7,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-022-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-022-ref.html
index 99d18c9..b96879a 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-022-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-022-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-023-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-023-ref.html
index da0c261..e054d06 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-023-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-023-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-024-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-024-ref.html
index 3dd4f0b..fca2ceb 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-024-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-024-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-025-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-025-ref.html
index eb9df5f..7304d5f 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-025-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-025-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-026-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-026-ref.html
index 0bbe98f0..9582472 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-026-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-026-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-027-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-027-ref.html
index 11a99b6..1e327b7 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-027-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-027-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-028-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-028-ref.html
index d4faf9d..041def4 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-028-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-028-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansDeseret-Regular.ttf') format('truetype');
+	src: url('/fonts/noto/NotoSansDeseret-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-029-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-029-ref.html
index 3fefafb..7484709 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-029-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-029-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansDeseret-Regular.ttf') format('truetype');
+	src: url('/fonts/noto/NotoSansDeseret-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-030-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-030-ref.html
index f2fab79..ae88eb2 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-030-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-030-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
+	src: url('/fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-031-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-031-ref.html
index 3e3f1c6..5ee04a6 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-031-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-031-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
+	src: url('/fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-033-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-033-ref.html
index e355050..28c8624 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-033-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-033-ref.html
@@ -9,7 +9,7 @@
 /* the CSS below is not part of the test */
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-034-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-034-ref.html
index 5afe60f..eafe630 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-034-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-034-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-035-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-035-ref.html
index 6f531b5..cfd3720 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-035-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-035-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-038-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-038-ref.html
index f5e8471..bc7f562 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-038-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-038-ref.html
@@ -8,7 +8,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-039-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-039-ref.html
index ba8123e..a00b836 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-039-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-039-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-040-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-040-ref.html
index 1fdb897..143ba2a 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-040-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-040-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-041-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-041-ref.html
index 76c4bce..0b80eb9 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-041-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-041-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-042-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-042-ref.html
index ee70285..10c0d36 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-042-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-042-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-043-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-043-ref.html
index fde2108..d629a31 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-043-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-043-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-101-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-101-ref.html
index 61bac52..3d21333 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-101-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-101-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-102-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-102-ref.html
index 1b519c5..0328b65 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-102-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-102-ref.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-103-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-103-ref.html
index 1796190..1f4d421 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-103-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-103-ref.html
@@ -7,8 +7,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/reference/text-transform-upperlower-104-ref.html b/css/css-text/text-transform/reference/text-transform-upperlower-104-ref.html
index 7695cbd..878393c 100644
--- a/css/css-text/text-transform/reference/text-transform-upperlower-104-ref.html
+++ b/css/css-text/text-transform/reference/text-transform-upperlower-104-ref.html
@@ -7,8 +7,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-001.html b/css/css-text/text-transform/text-transform-capitalize-001.html
index 1974ef0..be0dc73 100644
--- a/css/css-text/text-transform/text-transform-capitalize-001.html
+++ b/css/css-text/text-transform/text-transform-capitalize-001.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-003.html b/css/css-text/text-transform/text-transform-capitalize-003.html
index 9030b0f..33664a3 100644
--- a/css/css-text/text-transform/text-transform-capitalize-003.html
+++ b/css/css-text/text-transform/text-transform-capitalize-003.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-005.html b/css/css-text/text-transform/text-transform-capitalize-005.html
index 5d1d5f1..bdb44e5 100644
--- a/css/css-text/text-transform/text-transform-capitalize-005.html
+++ b/css/css-text/text-transform/text-transform-capitalize-005.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-007.html b/css/css-text/text-transform/text-transform-capitalize-007.html
index cbe3dde..f99412c 100644
--- a/css/css-text/text-transform/text-transform-capitalize-007.html
+++ b/css/css-text/text-transform/text-transform-capitalize-007.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-009.html b/css/css-text/text-transform/text-transform-capitalize-009.html
index 70608527..868e629 100644
--- a/css/css-text/text-transform/text-transform-capitalize-009.html
+++ b/css/css-text/text-transform/text-transform-capitalize-009.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-010.html b/css/css-text/text-transform/text-transform-capitalize-010.html
index 2d991bb..25aa18b 100644
--- a/css/css-text/text-transform/text-transform-capitalize-010.html
+++ b/css/css-text/text-transform/text-transform-capitalize-010.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-011.html b/css/css-text/text-transform/text-transform-capitalize-011.html
index 7bab1f0..a66ef35 100644
--- a/css/css-text/text-transform/text-transform-capitalize-011.html
+++ b/css/css-text/text-transform/text-transform-capitalize-011.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-014.html b/css/css-text/text-transform/text-transform-capitalize-014.html
index 5608e79..6fbd05e 100644
--- a/css/css-text/text-transform/text-transform-capitalize-014.html
+++ b/css/css-text/text-transform/text-transform-capitalize-014.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-016.html b/css/css-text/text-transform/text-transform-capitalize-016.html
index e164681..2a45c96 100644
--- a/css/css-text/text-transform/text-transform-capitalize-016.html
+++ b/css/css-text/text-transform/text-transform-capitalize-016.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-018.html b/css/css-text/text-transform/text-transform-capitalize-018.html
index 9ca4686..210ced5 100644
--- a/css/css-text/text-transform/text-transform-capitalize-018.html
+++ b/css/css-text/text-transform/text-transform-capitalize-018.html
@@ -10,8 +10,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-020.html b/css/css-text/text-transform/text-transform-capitalize-020.html
index 0aa7375..d46f346 100644
--- a/css/css-text/text-transform/text-transform-capitalize-020.html
+++ b/css/css-text/text-transform/text-transform-capitalize-020.html
@@ -10,8 +10,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-022.html b/css/css-text/text-transform/text-transform-capitalize-022.html
index 8930ba8..f8b7489 100644
--- a/css/css-text/text-transform/text-transform-capitalize-022.html
+++ b/css/css-text/text-transform/text-transform-capitalize-022.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-024.html b/css/css-text/text-transform/text-transform-capitalize-024.html
index 4c3c9e2..67a9304 100644
--- a/css/css-text/text-transform/text-transform-capitalize-024.html
+++ b/css/css-text/text-transform/text-transform-capitalize-024.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-026.html b/css/css-text/text-transform/text-transform-capitalize-026.html
index fd9e38b..76999c2 100644
--- a/css/css-text/text-transform/text-transform-capitalize-026.html
+++ b/css/css-text/text-transform/text-transform-capitalize-026.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-028.html b/css/css-text/text-transform/text-transform-capitalize-028.html
index 8785e2e..b225970 100644
--- a/css/css-text/text-transform/text-transform-capitalize-028.html
+++ b/css/css-text/text-transform/text-transform-capitalize-028.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansDeseret-Regular.ttf') format('truetype');
+	src: url('/fonts/noto/NotoSansDeseret-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-capitalize-030.html b/css/css-text/text-transform/text-transform-capitalize-030.html
index 8773ea7..1924d3e 100644
--- a/css/css-text/text-transform/text-transform-capitalize-030.html
+++ b/css/css-text/text-transform/text-transform-capitalize-030.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
+	src: url('/fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-001.html b/css/css-text/text-transform/text-transform-upperlower-001.html
index 6e390a0..a30be4e 100644
--- a/css/css-text/text-transform/text-transform-upperlower-001.html
+++ b/css/css-text/text-transform/text-transform-upperlower-001.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-002.html b/css/css-text/text-transform/text-transform-upperlower-002.html
index 0c42526..e82a967 100644
--- a/css/css-text/text-transform/text-transform-upperlower-002.html
+++ b/css/css-text/text-transform/text-transform-upperlower-002.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-003.html b/css/css-text/text-transform/text-transform-upperlower-003.html
index df727cf..8422a9a 100644
--- a/css/css-text/text-transform/text-transform-upperlower-003.html
+++ b/css/css-text/text-transform/text-transform-upperlower-003.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-004.html b/css/css-text/text-transform/text-transform-upperlower-004.html
index 837c5f6..e686383 100644
--- a/css/css-text/text-transform/text-transform-upperlower-004.html
+++ b/css/css-text/text-transform/text-transform-upperlower-004.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-005.html b/css/css-text/text-transform/text-transform-upperlower-005.html
index 12083ba..d8164b6 100644
--- a/css/css-text/text-transform/text-transform-upperlower-005.html
+++ b/css/css-text/text-transform/text-transform-upperlower-005.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-006.html b/css/css-text/text-transform/text-transform-upperlower-006.html
index 5876dfe..c4d607a 100644
--- a/css/css-text/text-transform/text-transform-upperlower-006.html
+++ b/css/css-text/text-transform/text-transform-upperlower-006.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-007.html b/css/css-text/text-transform/text-transform-upperlower-007.html
index ad8a699..1d3e591 100644
--- a/css/css-text/text-transform/text-transform-upperlower-007.html
+++ b/css/css-text/text-transform/text-transform-upperlower-007.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-008.html b/css/css-text/text-transform/text-transform-upperlower-008.html
index 9435769..09e8079 100644
--- a/css/css-text/text-transform/text-transform-upperlower-008.html
+++ b/css/css-text/text-transform/text-transform-upperlower-008.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-009.html b/css/css-text/text-transform/text-transform-upperlower-009.html
index b44c394..dcb0896 100644
--- a/css/css-text/text-transform/text-transform-upperlower-009.html
+++ b/css/css-text/text-transform/text-transform-upperlower-009.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-010.html b/css/css-text/text-transform/text-transform-upperlower-010.html
index c2dc440..e36491a 100644
--- a/css/css-text/text-transform/text-transform-upperlower-010.html
+++ b/css/css-text/text-transform/text-transform-upperlower-010.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-011.html b/css/css-text/text-transform/text-transform-upperlower-011.html
index bc12940..50dbbcd 100644
--- a/css/css-text/text-transform/text-transform-upperlower-011.html
+++ b/css/css-text/text-transform/text-transform-upperlower-011.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-012.html b/css/css-text/text-transform/text-transform-upperlower-012.html
index 7b3c617..8e47999 100644
--- a/css/css-text/text-transform/text-transform-upperlower-012.html
+++ b/css/css-text/text-transform/text-transform-upperlower-012.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-014.html b/css/css-text/text-transform/text-transform-upperlower-014.html
index a1dc9f1..3e391da 100644
--- a/css/css-text/text-transform/text-transform-upperlower-014.html
+++ b/css/css-text/text-transform/text-transform-upperlower-014.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-015.html b/css/css-text/text-transform/text-transform-upperlower-015.html
index 60d1db8..a06cdf5 100644
--- a/css/css-text/text-transform/text-transform-upperlower-015.html
+++ b/css/css-text/text-transform/text-transform-upperlower-015.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-016.html b/css/css-text/text-transform/text-transform-upperlower-016.html
index ec256af..e6cd03f 100644
--- a/css/css-text/text-transform/text-transform-upperlower-016.html
+++ b/css/css-text/text-transform/text-transform-upperlower-016.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-017.html b/css/css-text/text-transform/text-transform-upperlower-017.html
index 7eb202d..821fcf9 100644
--- a/css/css-text/text-transform/text-transform-upperlower-017.html
+++ b/css/css-text/text-transform/text-transform-upperlower-017.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-018.html b/css/css-text/text-transform/text-transform-upperlower-018.html
index c944829..0f4d314 100644
--- a/css/css-text/text-transform/text-transform-upperlower-018.html
+++ b/css/css-text/text-transform/text-transform-upperlower-018.html
@@ -10,8 +10,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-019.html b/css/css-text/text-transform/text-transform-upperlower-019.html
index 6f80c22..683336e 100644
--- a/css/css-text/text-transform/text-transform-upperlower-019.html
+++ b/css/css-text/text-transform/text-transform-upperlower-019.html
@@ -11,8 +11,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-020.html b/css/css-text/text-transform/text-transform-upperlower-020.html
index 05c7a77..42b3906 100644
--- a/css/css-text/text-transform/text-transform-upperlower-020.html
+++ b/css/css-text/text-transform/text-transform-upperlower-020.html
@@ -10,8 +10,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-021.html b/css/css-text/text-transform/text-transform-upperlower-021.html
index 47a70d4..4a4718f 100644
--- a/css/css-text/text-transform/text-transform-upperlower-021.html
+++ b/css/css-text/text-transform/text-transform-upperlower-021.html
@@ -10,8 +10,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-022.html b/css/css-text/text-transform/text-transform-upperlower-022.html
index 28168b4..2f89f31 100644
--- a/css/css-text/text-transform/text-transform-upperlower-022.html
+++ b/css/css-text/text-transform/text-transform-upperlower-022.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-023.html b/css/css-text/text-transform/text-transform-upperlower-023.html
index a807c4b..e92ca82 100644
--- a/css/css-text/text-transform/text-transform-upperlower-023.html
+++ b/css/css-text/text-transform/text-transform-upperlower-023.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-024.html b/css/css-text/text-transform/text-transform-upperlower-024.html
index 1ab8262..ec3312b 100644
--- a/css/css-text/text-transform/text-transform-upperlower-024.html
+++ b/css/css-text/text-transform/text-transform-upperlower-024.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-025.html b/css/css-text/text-transform/text-transform-upperlower-025.html
index 90134e3..4974553 100644
--- a/css/css-text/text-transform/text-transform-upperlower-025.html
+++ b/css/css-text/text-transform/text-transform-upperlower-025.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-026.html b/css/css-text/text-transform/text-transform-upperlower-026.html
index 3bc9c56..b44d892 100644
--- a/css/css-text/text-transform/text-transform-upperlower-026.html
+++ b/css/css-text/text-transform/text-transform-upperlower-026.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-027.html b/css/css-text/text-transform/text-transform-upperlower-027.html
index 395e94e..0e48060 100644
--- a/css/css-text/text-transform/text-transform-upperlower-027.html
+++ b/css/css-text/text-transform/text-transform-upperlower-027.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-028.html b/css/css-text/text-transform/text-transform-upperlower-028.html
index 3c1ae9d..0e85b79 100644
--- a/css/css-text/text-transform/text-transform-upperlower-028.html
+++ b/css/css-text/text-transform/text-transform-upperlower-028.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansDeseret-Regular.ttf') format('truetype');
+	src: url('/fonts/noto/NotoSansDeseret-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-029.html b/css/css-text/text-transform/text-transform-upperlower-029.html
index 089a19c..eb56f45 100644
--- a/css/css-text/text-transform/text-transform-upperlower-029.html
+++ b/css/css-text/text-transform/text-transform-upperlower-029.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansDeseret-Regular.ttf') format('truetype');
+	src: url('/fonts/noto/NotoSansDeseret-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-030.html b/css/css-text/text-transform/text-transform-upperlower-030.html
index c0dd75c..554aa9e 100644
--- a/css/css-text/text-transform/text-transform-upperlower-030.html
+++ b/css/css-text/text-transform/text-transform-upperlower-030.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
+	src: url('/fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-031.html b/css/css-text/text-transform/text-transform-upperlower-031.html
index 004b08e..0393ae5 100644
--- a/css/css-text/text-transform/text-transform-upperlower-031.html
+++ b/css/css-text/text-transform/text-transform-upperlower-031.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
+	src: url('/fonts/NotoSansGeorgian-Regular.ttf') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-033.html b/css/css-text/text-transform/text-transform-upperlower-033.html
index cc27405..6cd5989 100644
--- a/css/css-text/text-transform/text-transform-upperlower-033.html
+++ b/css/css-text/text-transform/text-transform-upperlower-033.html
@@ -12,7 +12,7 @@
 /* the CSS below is not part of the test */
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-034.html b/css/css-text/text-transform/text-transform-upperlower-034.html
index 7327573..2fa234e 100644
--- a/css/css-text/text-transform/text-transform-upperlower-034.html
+++ b/css/css-text/text-transform/text-transform-upperlower-034.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/NotoSansArmenian-Regular') format('truetype');
+	src: url('/fonts/NotoSansArmenian-Regular') format('truetype');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-035.html b/css/css-text/text-transform/text-transform-upperlower-035.html
index ec26a58..cc5e984 100644
--- a/css/css-text/text-transform/text-transform-upperlower-035.html
+++ b/css/css-text/text-transform/text-transform-upperlower-035.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-038.html b/css/css-text/text-transform/text-transform-upperlower-038.html
index 3e5d712..462ed6a 100644
--- a/css/css-text/text-transform/text-transform-upperlower-038.html
+++ b/css/css-text/text-transform/text-transform-upperlower-038.html
@@ -11,7 +11,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-039.html b/css/css-text/text-transform/text-transform-upperlower-039.html
index 2f60ecd..d15c225 100644
--- a/css/css-text/text-transform/text-transform-upperlower-039.html
+++ b/css/css-text/text-transform/text-transform-upperlower-039.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-040.html b/css/css-text/text-transform/text-transform-upperlower-040.html
index 03ac309..b430764 100644
--- a/css/css-text/text-transform/text-transform-upperlower-040.html
+++ b/css/css-text/text-transform/text-transform-upperlower-040.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-041.html b/css/css-text/text-transform/text-transform-upperlower-041.html
index 4fad974..dc24950 100644
--- a/css/css-text/text-transform/text-transform-upperlower-041.html
+++ b/css/css-text/text-transform/text-transform-upperlower-041.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-042.html b/css/css-text/text-transform/text-transform-upperlower-042.html
index 53d42ed..d4d9225 100644
--- a/css/css-text/text-transform/text-transform-upperlower-042.html
+++ b/css/css-text/text-transform/text-transform-upperlower-042.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-043.html b/css/css-text/text-transform/text-transform-upperlower-043.html
index 3fcb21d..5f5d325 100644
--- a/css/css-text/text-transform/text-transform-upperlower-043.html
+++ b/css/css-text/text-transform/text-transform-upperlower-043.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-101.html b/css/css-text/text-transform/text-transform-upperlower-101.html
index 702c277..46b48a9 100644
--- a/css/css-text/text-transform/text-transform-upperlower-101.html
+++ b/css/css-text/text-transform/text-transform-upperlower-101.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-102.html b/css/css-text/text-transform/text-transform-upperlower-102.html
index b9ba213..d36bc36 100644
--- a/css/css-text/text-transform/text-transform-upperlower-102.html
+++ b/css/css-text/text-transform/text-transform-upperlower-102.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/DoulosSIL-R.woff') format('woff');
+	src: url('/fonts/DoulosSIL-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-103.html b/css/css-text/text-transform/text-transform-upperlower-103.html
index ed940c5..ba669c0 100644
--- a/css/css-text/text-transform/text-transform-upperlower-103.html
+++ b/css/css-text/text-transform/text-transform-upperlower-103.html
@@ -10,8 +10,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/text-transform/text-transform-upperlower-104.html b/css/css-text/text-transform/text-transform-upperlower-104.html
index 29ba897..9d5c269 100644
--- a/css/css-text/text-transform/text-transform-upperlower-104.html
+++ b/css/css-text/text-transform/text-transform-upperlower-104.html
@@ -10,8 +10,8 @@
 <style type='text/css'>
 @font-face {
 	font-family: 'webfont';
-	src: url('../../fonts/GentiumPlus-R.woff2') format('woff2'),
-	 url('../../fonts/GentiumPlus-R.woff') format('woff');
+	src: url('/fonts/GentiumPlus-R.woff2') format('woff2'),
+	 url('/fonts/GentiumPlus-R.woff') format('woff');
 	font-weight: normal;
 	font-style: normal;
 	}
diff --git a/css/css-text/white-space/pre-wrap-001.html b/css/css-text/white-space/pre-wrap-001.html
index 6a1893f..31486f8 100644
--- a/css/css-text/white-space/pre-wrap-001.html
+++ b/css/css-text/white-space/pre-wrap-001.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-002.html b/css/css-text/white-space/pre-wrap-002.html
index 891527f..c0db9e6 100644
--- a/css/css-text/white-space/pre-wrap-002.html
+++ b/css/css-text/white-space/pre-wrap-002.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-003.html b/css/css-text/white-space/pre-wrap-003.html
index 56d3080..e5d0a59 100644
--- a/css/css-text/white-space/pre-wrap-003.html
+++ b/css/css-text/white-space/pre-wrap-003.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-004.html b/css/css-text/white-space/pre-wrap-004.html
index b4b3b45..3be7a7d 100644
--- a/css/css-text/white-space/pre-wrap-004.html
+++ b/css/css-text/white-space/pre-wrap-004.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-005.html b/css/css-text/white-space/pre-wrap-005.html
index bf04625..487defb 100644
--- a/css/css-text/white-space/pre-wrap-005.html
+++ b/css/css-text/white-space/pre-wrap-005.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-006.html b/css/css-text/white-space/pre-wrap-006.html
index 8802c30..d725a2d 100644
--- a/css/css-text/white-space/pre-wrap-006.html
+++ b/css/css-text/white-space/pre-wrap-006.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-007.html b/css/css-text/white-space/pre-wrap-007.html
index 29b356c..de69880 100644
--- a/css/css-text/white-space/pre-wrap-007.html
+++ b/css/css-text/white-space/pre-wrap-007.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-008.html b/css/css-text/white-space/pre-wrap-008.html
index 61ccaae..e739a21 100644
--- a/css/css-text/white-space/pre-wrap-008.html
+++ b/css/css-text/white-space/pre-wrap-008.html
@@ -9,7 +9,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-009.html b/css/css-text/white-space/pre-wrap-009.html
index 2aa8cd2..a70ad8f 100644
--- a/css/css-text/white-space/pre-wrap-009.html
+++ b/css/css-text/white-space/pre-wrap-009.html
@@ -9,7 +9,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-010.html b/css/css-text/white-space/pre-wrap-010.html
index 90f2005..c00fe74 100644
--- a/css/css-text/white-space/pre-wrap-010.html
+++ b/css/css-text/white-space/pre-wrap-010.html
@@ -9,7 +9,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-011.html b/css/css-text/white-space/pre-wrap-011.html
index 6659e32..5f86cff 100644
--- a/css/css-text/white-space/pre-wrap-011.html
+++ b/css/css-text/white-space/pre-wrap-011.html
@@ -9,7 +9,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-012.html b/css/css-text/white-space/pre-wrap-012.html
index d96be79..2d322aa 100644
--- a/css/css-text/white-space/pre-wrap-012.html
+++ b/css/css-text/white-space/pre-wrap-012.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-013.html b/css/css-text/white-space/pre-wrap-013.html
index 6e3b6db..9de1f07 100644
--- a/css/css-text/white-space/pre-wrap-013.html
+++ b/css/css-text/white-space/pre-wrap-013.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/pre-wrap-014.html b/css/css-text/white-space/pre-wrap-014.html
index 2275201..5da564d 100644
--- a/css/css-text/white-space/pre-wrap-014.html
+++ b/css/css-text/white-space/pre-wrap-014.html
@@ -8,7 +8,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/reference/pre-wrap-001-ref.html b/css/css-text/white-space/reference/pre-wrap-001-ref.html
index 21fe887..8a8b513 100644
--- a/css/css-text/white-space/reference/pre-wrap-001-ref.html
+++ b/css/css-text/white-space/reference/pre-wrap-001-ref.html
@@ -5,7 +5,7 @@
 <style>
 div {
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   color: green;
 }
diff --git a/css/css-text/white-space/reference/textarea-pre-wrap-001-ref.html b/css/css-text/white-space/reference/textarea-pre-wrap-001-ref.html
index 8951c3d..d09873a 100644
--- a/css/css-text/white-space/reference/textarea-pre-wrap-001-ref.html
+++ b/css/css-text/white-space/reference/textarea-pre-wrap-001-ref.html
@@ -20,7 +20,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   color: green;
   white-space: pre;
diff --git a/css/css-text/white-space/textarea-pre-wrap-001.html b/css/css-text/white-space/textarea-pre-wrap-001.html
index 2c41682..6c7f605 100644
--- a/css/css-text/white-space/textarea-pre-wrap-001.html
+++ b/css/css-text/white-space/textarea-pre-wrap-001.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-002.html b/css/css-text/white-space/textarea-pre-wrap-002.html
index bd411d9..340462b 100644
--- a/css/css-text/white-space/textarea-pre-wrap-002.html
+++ b/css/css-text/white-space/textarea-pre-wrap-002.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-003.html b/css/css-text/white-space/textarea-pre-wrap-003.html
index 81f7a5a..655b1d4 100644
--- a/css/css-text/white-space/textarea-pre-wrap-003.html
+++ b/css/css-text/white-space/textarea-pre-wrap-003.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-004.html b/css/css-text/white-space/textarea-pre-wrap-004.html
index c29ceed..5e8d505 100644
--- a/css/css-text/white-space/textarea-pre-wrap-004.html
+++ b/css/css-text/white-space/textarea-pre-wrap-004.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-005.html b/css/css-text/white-space/textarea-pre-wrap-005.html
index 8137ac6..2f4d54c 100644
--- a/css/css-text/white-space/textarea-pre-wrap-005.html
+++ b/css/css-text/white-space/textarea-pre-wrap-005.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-006.html b/css/css-text/white-space/textarea-pre-wrap-006.html
index b06947c..7805740 100644
--- a/css/css-text/white-space/textarea-pre-wrap-006.html
+++ b/css/css-text/white-space/textarea-pre-wrap-006.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-007.html b/css/css-text/white-space/textarea-pre-wrap-007.html
index c678570..c7e5067 100644
--- a/css/css-text/white-space/textarea-pre-wrap-007.html
+++ b/css/css-text/white-space/textarea-pre-wrap-007.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-008.html b/css/css-text/white-space/textarea-pre-wrap-008.html
index 1361bf1..71eca50 100644
--- a/css/css-text/white-space/textarea-pre-wrap-008.html
+++ b/css/css-text/white-space/textarea-pre-wrap-008.html
@@ -20,7 +20,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-009.html b/css/css-text/white-space/textarea-pre-wrap-009.html
index cecaadb..aedca63 100644
--- a/css/css-text/white-space/textarea-pre-wrap-009.html
+++ b/css/css-text/white-space/textarea-pre-wrap-009.html
@@ -20,7 +20,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-010.html b/css/css-text/white-space/textarea-pre-wrap-010.html
index 7d41fee..7c5d138 100644
--- a/css/css-text/white-space/textarea-pre-wrap-010.html
+++ b/css/css-text/white-space/textarea-pre-wrap-010.html
@@ -20,7 +20,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-011.html b/css/css-text/white-space/textarea-pre-wrap-011.html
index 96d121f..d60348d 100644
--- a/css/css-text/white-space/textarea-pre-wrap-011.html
+++ b/css/css-text/white-space/textarea-pre-wrap-011.html
@@ -20,7 +20,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-012.html b/css/css-text/white-space/textarea-pre-wrap-012.html
index 85fcfee..3177a7b 100644
--- a/css/css-text/white-space/textarea-pre-wrap-012.html
+++ b/css/css-text/white-space/textarea-pre-wrap-012.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-013.html b/css/css-text/white-space/textarea-pre-wrap-013.html
index fd3b26b..05afb06 100644
--- a/css/css-text/white-space/textarea-pre-wrap-013.html
+++ b/css/css-text/white-space/textarea-pre-wrap-013.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/white-space/textarea-pre-wrap-014.html b/css/css-text/white-space/textarea-pre-wrap-014.html
index 881fffe..5c4f33a 100644
--- a/css/css-text/white-space/textarea-pre-wrap-014.html
+++ b/css/css-text/white-space/textarea-pre-wrap-014.html
@@ -19,7 +19,7 @@
   overflow: hidden; /* I don't want scrollbars, and overflow:visible isn't typically supported on textarea */
 
   font-size: 20px;
-  font-family: ahem;
+  font-family: Ahem;
   line-height: 1em;
   white-space: pre-wrap;
   color: green;
diff --git a/css/css-text/word-break/reference/word-break-break-all-009-ref.xht b/css/css-text/word-break/reference/word-break-break-all-009-ref.xht
index 84d9d50..0768b85 100644
--- a/css/css-text/word-break/reference/word-break-break-all-009-ref.xht
+++ b/css/css-text/word-break/reference/word-break-break-all-009-ref.xht
@@ -16,6 +16,12 @@
 					font-family: "IPAMincho", "IPAGothic", "IPA明朝", "IPAゴシック";
 					width: 10em;
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -26,28 +32,30 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<p class="test">
-			<span>Filler Text Filler Text Filler Text</span>
-		</p>
-		<p class="control">
-			<span>Filler Text Filler T<br />ext Filler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字 Filler Text</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字 Fil<br />ler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字満たすための文字</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字満た<br />すための文字</span>
-		</p>
-		<hr />
+		<div class="wrapper">
+			<p class="test">
+				<span>Filler Text Filler Text Filler Text</span>
+			</p>
+			<p class="control">
+				<span>Filler Text Filler T<br />ext Filler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test">
+				<span>満たすための文字 Filler Text</span>
+			</p>
+			<p class="control">
+				<span>満たすための文字 Fil<br />ler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test">
+				<span>満たすための文字満たすための文字</span>
+			</p>
+			<p class="control">
+				<span>満たすための文字満た<br />すための文字</span>
+			</p>
+		</div>
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/word-break/reference/word-break-break-all-ref-005.html b/css/css-text/word-break/reference/word-break-break-all-ref-005.html
index 331239c..151aecb 100644
--- a/css/css-text/word-break/reference/word-break-break-all-ref-005.html
+++ b/css/css-text/word-break/reference/word-break-break-all-ref-005.html
@@ -7,7 +7,7 @@
 <style type='text/css'>
 .test { word-break: break-all; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px;  padding: 10px; width: 390px; font: 36px/1.5 sans-serif; }
+.test, .ref { border: 1px solid orange;  margin: 20px;  padding: 10px; width: 390px; font: 36px/1.5 Arial; }
 </style>
 </head>
 <body>
@@ -15,4 +15,4 @@
 <div class="ref" lang="bo"><span>ལྷ་སའི་སྐད་ད་<br/>ལྟ</span></div>
 <div class="ref" lang="bo"><span>ལྷ་སའི་སྐད་ད་<br/>ལྟ</span></div>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/word-break/reference/word-break-keep-all-004-ref.xht b/css/css-text/word-break/reference/word-break-keep-all-004-ref.xht
index f6afa8e..65ede65 100644
--- a/css/css-text/word-break/reference/word-break-keep-all-004-ref.xht
+++ b/css/css-text/word-break/reference/word-break-keep-all-004-ref.xht
@@ -12,6 +12,12 @@
 					font-family: "IPAMincho", "IPAGothic", "IPA明朝", "IPAゴシック";
 					width: 10em;
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -25,28 +31,30 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<p class="control">
-			<span>Filler Text Filler<br />Text Filler Text</span>
-		</p>
-		<p class="control">
-			<span>Filler Text Filler<br />Text Filler Text</span>
-		</p>
-		<hr />
-		<p class="control">
-			<span>満たすための文字<br />Filler Text</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字<br />Filler Text</span>
-		</p>
-		<hr />
-		<p class="control">
-			<span class="no_wrap">満たすための文字満たすための文字</span>
-		</p>
-		<p class="control">
-			<span class="no_wrap">満たすための文字満たすための文字</span>
-		</p>
-		<hr />
+		<div class="wrapper">
+			<p class="control">
+				<span>Filler Text Filler<br />Text Filler Text</span>
+			</p>
+			<p class="control">
+				<span>Filler Text Filler<br />Text Filler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control">
+				<span>満たすための文字<br />Filler Text</span>
+			</p>
+			<p class="control">
+				<span>満たすための文字<br />Filler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="control">
+				<span class="no_wrap">満たすための文字満たすための文字</span>
+			</p>
+			<p class="control">
+				<span class="no_wrap">満たすための文字満たすための文字</span>
+			</p>
+		</div>
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/word-break/reference/word-break-normal-002-ref.xht b/css/css-text/word-break/reference/word-break-normal-002-ref.xht
deleted file mode 100644
index 92748fc..0000000
--- a/css/css-text/word-break/reference/word-break-normal-002-ref.xht
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
-	<head>
-		<title>CSS Test: word-break - normal - basic cases Reftest Reference</title>
-		<link rel="author" title="Satoshi Umehara" href="mailto:umehara@est.co.jp" />
-		<style type="text/css">
-			<![CDATA[
-				p.control {
-					border: 1px solid gray;
-					color: blue;
-					font-family: "IPAMincho", "IPAGothic", "IPA明朝", "IPAゴシック";
-					width: 10em;
-				}
-				span.attention {
-					color: red;
-				}
-			]]>
-		</style>
-	</head>
-	<body>
-		<p>
-			Test passes if each pair of upper and lower text in the square box is identical.
-		</p>
-		<hr />
-		<p class="control">
-			<span>Filler Text Filler<br />Text Filler Text</span>
-		</p>
-		<p class="control">
-			<span>Filler Text Filler<br />Text Filler Text</span>
-		</p>
-		<hr />
-		<p class="control">
-			<span>満たすための文字<br />Filler Text</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字<br />Filler Text</span>
-		</p>
-		<hr />
-		<p class="control">
-			<span>満たすための文字満た<br />すための文字</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字満た<br />すための文字</span>
-		</p>
-		<hr />
-		<p>
-			<span class="attention">* You will need a Japanese font.</span><br />
-			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
-		</p>
-	</body>
-</html>
diff --git a/css/css-text/word-break/word-break-break-all-005.html b/css/css-text/word-break/word-break-break-all-005.html
index 758a245..02f2335 100644
--- a/css/css-text/word-break/word-break-break-all-005.html
+++ b/css/css-text/word-break/word-break-break-all-005.html
@@ -10,7 +10,7 @@
 <style type='text/css'>
 .test { word-break: break-all; }
 /* the CSS below is not part of the test */
-.test, .ref { border: 1px solid orange;  margin: 20px;  padding: 10px; width: 390px; font: 36px/1.5 sans-serif; }
+.test, .ref { border: 1px solid orange;  margin: 20px;  padding: 10px; width: 390px; font: 36px/1.5 Arial; }
 </style>
 </head>
 <body>
@@ -22,4 +22,4 @@
 document.getElementById('testdiv').style.width = String(sentenceWidth - 5)+'px'
 </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/css/css-text/word-break/word-break-break-all-009.xht b/css/css-text/word-break/word-break-break-all-009.xht
index 847b7d0..9a7a591 100644
--- a/css/css-text/word-break/word-break-break-all-009.xht
+++ b/css/css-text/word-break/word-break-break-all-009.xht
@@ -20,6 +20,12 @@
 					font-family: "IPAMincho", "IPAGothic", "IPA明朝", "IPAゴシック";
 					width: 10em;
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -30,28 +36,30 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<p class="test">
-			<span>Filler Text Filler Text Filler Text</span>
-		</p>
-		<p class="control">
-			<span>Filler Text Filler T<br />ext Filler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字 Filler Text</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字 Fil<br />ler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字満たすための文字</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字満た<br />すための文字</span>
-		</p>
-		<hr />
+		<div class="wrapper">
+			<p class="test">
+				<span>Filler Text Filler Text Filler Text</span>
+			</p>
+			<p class="control">
+				<span>Filler Text Filler T<br />ext Filler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test">
+				<span>満たすための文字 Filler Text</span>
+			</p>
+			<p class="control">
+				<span>満たすための文字 Fil<br />ler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test">
+				<span>満たすための文字満たすための文字</span>
+			</p>
+			<p class="control">
+				<span>満たすための文字満た<br />すための文字</span>
+			</p>
+		</div>
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/word-break/word-break-keep-all-004.xht b/css/css-text/word-break/word-break-keep-all-004.xht
index 7931aa3..c66bfe4 100644
--- a/css/css-text/word-break/word-break-keep-all-004.xht
+++ b/css/css-text/word-break/word-break-keep-all-004.xht
@@ -20,6 +20,12 @@
 					font-family: "IPAMincho", "IPAGothic", "IPA明朝", "IPAゴシック";
 					width: 10em;
 				}
+				div.wrapper {
+					display: inline-block;
+					border: 1px solid;
+					margin: 10px;
+					padding: 10px;
+				}
 				span.attention {
 					color: red;
 				}
@@ -33,28 +39,30 @@
 		<p>
 			Test passes if each pair of upper and lower text in the square box is identical.
 		</p>
-		<hr />
-		<p class="test">
-			<span>Filler Text Filler Text Filler Text</span>
-		</p>
-		<p class="control">
-			<span>Filler Text Filler<br />Text Filler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字 Filler Text</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字<br />Filler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字満たすための文字</span>
-		</p>
-		<p class="control">
-			<span class="no_wrap">満たすための文字満たすための文字</span>
-		</p>
-		<hr />
+		<div class="wrapper">
+			<p class="test">
+				<span>Filler Text Filler Text Filler Text</span>
+			</p>
+			<p class="control">
+				<span>Filler Text Filler<br />Text Filler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test">
+				<span>満たすための文字 Filler Text</span>
+			</p>
+			<p class="control">
+				<span>満たすための文字<br />Filler Text</span>
+			</p>
+		</div>
+		<div class="wrapper">
+			<p class="test">
+				<span>満たすための文字満たすための文字</span>
+			</p>
+			<p class="control">
+				<span class="no_wrap">満たすための文字満たすための文字</span>
+			</p>
+		</div>
 		<p>
 			<span class="attention">* You will need a Japanese font.</span><br />
 			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
diff --git a/css/css-text/word-break/word-break-normal-002.xht b/css/css-text/word-break/word-break-normal-002.xht
deleted file mode 100644
index 77d33e0..0000000
--- a/css/css-text/word-break/word-break-normal-002.xht
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
-	<head>
-		<title>CSS Test: word-break - normal - basic cases</title>
-		<link rel="author" title="Satoshi Umehara" href="mailto:umehara@est.co.jp" />
-		<link rel="help" title="CSS Text Level 3: 5.2. Word Breaking Rules: the ‘word-break’ property" href="http://www.w3.org/TR/css-text-3/#word-break" />
-		<link rel="match" href="reference/word-break-normal-002-ref.xht"/>
-		<meta name="flags" content="font" />
-		<meta name="assert" content="Break lines according to their usual rules." />
-		<style type="text/css">
-			<![CDATA[
-				.test span {
-					word-break: normal;
-				}
-				/* the CSS below is not part of the test */
-				p.test, p.control {
-					border: 1px solid gray;
-					color: blue;
-					font-family: "IPAMincho", "IPAGothic", "IPA明朝", "IPAゴシック";
-					width: 10em;
-				}
-				span.attention {
-					color: red;
-				}
-			]]>
-		</style>
-	</head>
-	<body>
-		<p>
-			Test passes if each pair of upper and lower text in the square box is identical.
-		</p>
-		<hr />
-		<p class="test">
-			<span>Filler Text Filler Text Filler Text</span>
-		</p>
-		<p class="control">
-			<span>Filler Text Filler<br />Text Filler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字 Filler Text</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字<br />Filler Text</span>
-		</p>
-		<hr />
-		<p class="test">
-			<span>満たすための文字満たすための文字</span>
-		</p>
-		<p class="control">
-			<span>満たすための文字満た<br />すための文字</span>
-		</p>
-		<hr />
-		<p>
-			<span class="attention">* You will need a Japanese font.</span><br />
-			If you are unable to see font glyphs for certain characters using the browsers default font, install the <a href="http://ossipedia.ipa.go.jp/ipafont/">IPA Font(http://ossipedia.ipa.go.jp/ipafont/)</a> and reload this page.
-		</p>
-	</body>
-</html>
diff --git a/css/css-timing/cubic-bezier-timing-functions-output.html b/css/css-timing/cubic-bezier-timing-functions-output.html
index 3068e8c..5c2003b 100644
--- a/css/css-timing/cubic-bezier-timing-functions-output.html
+++ b/css/css-timing/cubic-bezier-timing-functions-output.html
@@ -15,7 +15,7 @@
 
 function assert_style_left_at(animation, time, easingFunction) {
   animation.currentTime = time;
-  var portion = time / animation.effect.timing.duration;
+  var portion = time / animation.effect.getTiming()['duration'];
   assert_approx_equals(pxToNum(getComputedStyle(animation.effect.target).left),
                        easingFunction(portion) * 100,
                        0.01,
diff --git a/css/css-transforms/2d-rotate-js.html b/css/css-transforms/2d-rotate-js.html
index 3dcfa8c..11597fb 100644
--- a/css/css-transforms/2d-rotate-js.html
+++ b/css/css-transforms/2d-rotate-js.html
@@ -3,11 +3,12 @@
 	<head>
 		<title>JS test: Rotate via javascript must show the correct computed rotation</title>
 		<link rel="author" title="Rick Hurst" href="http://mrkn.co/axegs">
-		<link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-property">
+		<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#serialization-of-the-computed-value">
+		<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#serialization-of-transform-functions">
 		<script src="/resources/testharness.js"></script>
 		<script src="/resources/testharnessreport.js"></script>
 		<meta name="flags" content="svg">
-		<meta name="assert" content="Asserting that you can rotate an element with JS and it show up in CSS computed values not as a matrix but as the rotation">
+		<meta name="assert" content="Asserting that you can rotate an element with JS and it show up in CSS computed values as a matrix">
 		<style>
 			#box{
 				margin-top:30px;
@@ -26,8 +27,9 @@
 			test(function() {
 				var box = document.getElementById("box");
 				box.style.transform = "rotate(30deg)";
+				assert_equals(box.style.transform, "rotate(30deg)");
 				assert_equals(window.getComputedStyle(box).getPropertyValue("transform"),
-				              "rotate(30deg)");
+				              "matrix(0.866025, 0.5, -0.5, 0.866025, 0, 0)");
 			});
 		</script>
     </body>
diff --git a/css/css-transforms/animation/scale-interpolation.html b/css/css-transforms/animation/scale-interpolation.html
index da25d3a..942b80a 100644
--- a/css/css-transforms/animation/scale-interpolation.html
+++ b/css/css-transforms/animation/scale-interpolation.html
@@ -28,13 +28,13 @@
       test_interpolation({
         property: 'scale',
         from: '26 17 9',
-        to: '2',
+        to: '2 1',
       }, [
         {at: -1, expect: '50 33 17'},
         {at: 0, expect: '26 17 9'},
         {at: 0.125, expect: '23 15 8'},
         {at: 0.875, expect: '5 3 2'},
-        {at: 1, expect: '2'},
+        {at: 1, expect: '2 1'},
         {at: 2, expect: '-22 -15 -7'}
       ]);
 
diff --git a/css/css-transforms/css-transform-animate-translate-implied-y-ref.html b/css/css-transforms/css-transform-animate-translate-implied-y-ref.html
new file mode 100644
index 0000000..43d1737
--- /dev/null
+++ b/css/css-transforms/css-transform-animate-translate-implied-y-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Manish Goregaokar" href="mailto:manishearth@gmail.com">
+    <style>
+        #box {
+            background:red;
+            width: 200px;
+            height: 200px;
+            transform: translate(100px, 0px);
+        }
+    </style>
+  </head>
+  <body>
+    <div id=box></div>
+  </body>
+</html>
diff --git a/css/css-transforms/css-transform-animate-translate-implied-y.html b/css/css-transforms/css-transform-animate-translate-implied-y.html
new file mode 100644
index 0000000..dc0b957
--- /dev/null
+++ b/css/css-transforms/css-transform-animate-translate-implied-y.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Animating between translates where one has an implied `y` value</title>
+    <link rel="author" title="Manish Goregaokar" href="mailto:manishearth@gmail.com">
+    <meta name="assert" content='This tests that translate(x) is animated as if it were translate(x, 0px)'>
+    <link rel=match href=css-transform-animate-translate-implied-y-ref.html>
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-1/#funcdef-transform-translate">
+    <style>
+    #box {
+        background:red;
+        width: 200px;
+        height: 200px;
+    }
+    </style>
+  </head>
+  <body>
+    <div id=box></div>
+    <script>
+        let anim = document.getElementById('box').animate([
+            { transform: 'translate(180px)' },
+            { transform: 'translate(20px, 0px)' }
+        ], {
+            duration: 1000,
+        });
+        anim.pause();
+        anim.currentTime = 500;
+    </script>
+  </body>
+</html>
diff --git a/css/css-transforms/css-transform-inherit-scale.html b/css/css-transforms/css-transform-inherit-scale.html
index b4702f8..fa9b5bb 100644
--- a/css/css-transforms/css-transform-inherit-scale.html
+++ b/css/css-transforms/css-transform-inherit-scale.html
@@ -1,50 +1,49 @@
 <!DOCTYPE html>
 <html>
-<head>
+  <head>
     <title>CSS Transforms Test: CSS transforms scale 2 inheritance on div elements</title>
     <link rel="author" title="Delong Gao" href="mailto:gaodl@uw.edu">
     <link rel="reviewer" title="Rebecca Hauck" href="mailto:rhauck@adobe.com">
     <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-property">
     <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#two-d-transform-functions">
-    <!--<link rel="match" href="reference/ttwf-reftest-tutorial-ref.html">
-	<meta name="flags" content="svg">-->
+    <link rel="match" href="../reference/ref-filled-green-200px-square.html">
     <meta name="assert" content="While child div inherits property from its parent, scaling 2 on parent div will course the child to scale 4 and totally cover the red div. The test passes if there is a green square and no red. ">
     <style type="text/css">
-		* {
-			margin: 0;
-			padding: 0;
-		}
-		.red {
-			position: absolute;
-			width: 200px;
-			height: 200px;
-			background-color: red;
-		}
-        .parent {
-			background: yellow;
-            width: 50px;
-			height: 50px;
-			position: absolute;
-			top: 75px;
-			left: 75px;
-			transform: scale(2);
+      .test {
+          position: relative;
+      }
+      .red {
+          position: absolute;
+          width: 200px;
+          height: 200px;
+          background-color: red;
+      }
+      .parent {
+          background: yellow;
+          width: 50px;
+          height: 50px;
+          position: absolute;
+          top: 75px;
+          left: 75px;
+          transform: scale(2);
 
-        }
-        .child {
-			position: absolute;
-			top: 10px;
-			transform: inherit;
-            width: 50px;
-			height: 50px;
-			background-color: green;
-        }
+      }
+      .child {
+          position: absolute;
+          transform: inherit;
+          width: 50px;
+          height: 50px;
+          background-color: green;
+      }
     </style>
-</head>
-<body>
-    <p>The test passes if there is a green square and no red. </p>
-	<div class="red"></div>
-	<div class="parent">
-		<div class="child"></div>
-	</div>
-</body>
+  </head>
+  <body>
+    <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+    <div class="test">
+      <div class="red"></div>
+      <div class="parent">
+	<div class="child"></div>
+      </div>
+    </div>
+  </body>
 </html>
diff --git a/css/css-transforms/css-transform-property-existence.html b/css/css-transforms/css-transform-property-existence.html
index 75e1a1b..5885db2 100644
--- a/css/css-transforms/css-transform-property-existence.html
+++ b/css/css-transforms/css-transform-property-existence.html
@@ -14,7 +14,7 @@
     <div id="log"></div>
     <script>
       test(function() {
-        assert_not_equals(document.getElementById("test").style.transfor, undefined, "expect transform is not undefined");
+        assert_not_equals(document.getElementById("test").style.transform, undefined, "expect transform is not undefined");
       }, "Check the existence of transform.");
 
       test(function() {
diff --git a/css/css-transforms/individual-transform/individual-transform-1-ref.html b/css/css-transforms/individual-transform/individual-transform-1-ref.html
new file mode 100644
index 0000000..9271c77
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-1-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: compare individual transform with transform functions</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <meta name="assert" content="Tests whether individaul transform works correctlyi by compare the rendering result with transfrom functions of the 'transform' property."/>
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+      }
+      .row_1 {
+        top: 100px;
+      }
+      .scale_1{
+        left: 100px;
+        width: 50px;
+        height: 100px;
+        transform: scale(2, 2);
+      }
+      .translate_1 {
+        left: 150px;
+        transform: translateX(150px);
+      }
+      .rotate_1 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        transform: rotate(90deg);
+      }
+
+      .row_2 {
+        top: 250px;
+      }
+      .scale_2{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        transform: scale(2, 2);
+      }
+      .translate_2 {
+        left: 150px;
+        top: 200px;
+        transform: translate(150px, 50px);
+      }
+      .rotate_2 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        transform: rotate3d(0, 0, 1, 90deg);
+      }
+      .row_3 {
+        transform: perspective(500px);
+        top: 400px;
+      }
+      .scale_3{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        transform: scale3d(2, 2, 2);
+      }
+      .translate_3 {
+        left: 150px;
+        transform: translate3d(150px, 10px, 10px);
+      }
+      .rotate_3 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        transform: rotate3d(0, 1, 0, 45deg);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="scale_1 row_1"></div>
+    <div class="translate_1 row_1"></div>
+    <div class="rotate_1 row_1"></div>
+    <div class="scale_2 row_2"></div>
+    <div class="translate_2 row_2"></div>
+    <div class="rotate_2 row_2"></div>
+    <div class="scale_3 row_3"></div>
+    <div class="translate_3 row_3"></div>
+    <div class="rotate_3 row_3"></div>
+  </body>
+</html>
diff --git a/css/css-transforms/individual-transform/individual-transform-1.html b/css/css-transforms/individual-transform/individual-transform-1.html
new file mode 100644
index 0000000..4f83e2e
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-1.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: compare individual transform with transform functions</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <meta name="assert" content="Tests whether individaul transform works correctlyi by compare the rendering result with transfrom functions of the 'transform' property."/>
+    <link rel="match" href="individual-transform-1-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+      }
+      .row_1 {
+        top: 100px;
+      }
+      .scale_1{
+        left: 100px;
+        width: 50px;
+        height: 100px;
+        /* test 'scale: <number>' */
+        scale: 2;
+      }
+      .translate_1 {
+        left: 150px;
+        /* test 'translate: <length-percentage>' */
+        translate: 150px;
+      }
+      .rotate_1 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        /* test 'rota: te<angle>' */
+        rotate: 90deg;
+      }
+
+      .row_2 {
+        top: 250px;
+      }
+      .scale_2{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        /* test 'scale: <number>{2}'' */
+        scale: 2 2;
+      }
+      .translate_2 {
+        left: 150px;
+        top: 200px;
+        /* test 'translate: <length-percentage><length-percentage>' */
+        translate: 150px 50px;
+      }
+      .rotate_2 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        /* test 'rotate: <number>{3}<angle>'*/
+        rotate: 0 0 1 90deg;
+      }
+      .row_3 {
+        transform: perspective(500px);
+        top: 400px;
+      }
+      .scale_3{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        /* test 'scale: <number>{3}'' */
+        scale: 2 2 2;
+      }
+      .translate_3 {
+        left: 150px;
+        /* test 'translate: <length-percentage><length>' */
+        translate: 150px 10px 10px;
+      }
+      .rotate_3 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        /* test 'rotate: <number>{3}<angle>'*/
+        rotate: 0 1 0 45deg;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="scale_1 row_1"></div>
+    <div class="translate_1 row_1"></div>
+    <div class="rotate_1 row_1"></div>
+    <div class="scale_2 row_2"></div>
+    <div class="translate_2 row_2"></div>
+    <div class="rotate_2 row_2"></div>
+    <div class="scale_3 row_3"></div>
+    <div class="translate_3 row_3"></div>
+    <div class="rotate_3 row_3"></div>
+  </body>
+</html>
diff --git a/css/css-transforms/individual-transform/individual-transform-2-ref.html b/css/css-transforms/individual-transform/individual-transform-2-ref.html
new file mode 100644
index 0000000..ee956aa
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-2-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        transform: translate(50px, 50px) rotate(45deg) scale(2, 2);
+      }
+
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/css-transforms/individual-transform/individual-transform-2a.html b/css/css-transforms/individual-transform/individual-transform-2a.html
new file mode 100644
index 0000000..3f6c973
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-2a.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 50px 50px;
+        rotate: 45deg;
+        scale: 2 2;
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/css-transforms/individual-transform/individual-transform-2b.html b/css/css-transforms/individual-transform/individual-transform-2b.html
new file mode 100644
index 0000000..94dac2f
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-2b.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        rotate: 45deg;
+        scale: 2 2;
+        translate: 50px 50px;
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/css-transforms/individual-transform/individual-transform-2c.html b/css/css-transforms/individual-transform/individual-transform-2c.html
new file mode 100644
index 0000000..f84ae22
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-2c.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 50px 50px;
+        rotate: 45deg;
+        transform: scale(2, 2);
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/css-transforms/individual-transform/individual-transform-2d.html b/css/css-transforms/individual-transform/individual-transform-2d.html
new file mode 100644
index 0000000..5b5694f
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-2d.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 50px 50px;
+        transform: rotate(45deg) scale(2, 2);
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/css-transforms/individual-transform/individual-transform-2e.html b/css/css-transforms/individual-transform/individual-transform-2e.html
new file mode 100644
index 0000000..0350137
--- /dev/null
+++ b/css/css-transforms/individual-transform/individual-transform-2e.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 0px 50px;
+        transform: translateX(50px) rotate(45deg) scale(2, 2);
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/css-transforms/parsing/perspective-origin-parsing-invalid.html b/css/css-transforms/parsing/perspective-origin-parsing-invalid.html
new file mode 100644
index 0000000..09f2f47
--- /dev/null
+++ b/css/css-transforms/parsing/perspective-origin-parsing-invalid.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Transform Module Level 2: parsing perspective-origin with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#perspective-origin-property">
+<meta name="assert" content="perspective-origin supports only the '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("perspective-origin", "auto");
+test_invalid_value("perspective-origin", "1px 2px 3px");
+test_invalid_value("perspective-origin", "left right");
+test_invalid_value("perspective-origin", "bottom 10% top 20%");
+
+// The following were supported in an earlier version of the spec.
+// https://github.com/w3c/csswg-drafts/issues/2140
+test_invalid_value("perspective-origin", "center left 1px");
+test_invalid_value("perspective-origin", "center top 2px");
+test_invalid_value("perspective-origin", "right 3% center");
+test_invalid_value("perspective-origin", "left 4px top");
+test_invalid_value("perspective-origin", "right top 5px");
+test_invalid_value("perspective-origin", "bottom 6% center");
+test_invalid_value("perspective-origin", "bottom 7% left");
+test_invalid_value("perspective-origin", "bottom right 8%");
+
+</script>
+</body>
+</html>
diff --git a/css/css-transforms/parsing/perspective-origin-parsing-valid.html b/css/css-transforms/parsing/perspective-origin-parsing-valid.html
new file mode 100644
index 0000000..8421376
--- /dev/null
+++ b/css/css-transforms/parsing/perspective-origin-parsing-valid.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Transform Module Level 2: parsing perspective-origin with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#perspective-origin-property">
+<meta name="assert" content="perspective-origin supports the full '<position>' grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Blink and WebKit append center. Edge and Firefox append 50%
+test_valid_value("perspective-origin", "10%", ["10% center", "10% 50%"]);
+test_valid_value("perspective-origin", "20% 30px");
+test_valid_value("perspective-origin", "30px center");
+test_valid_value("perspective-origin", "40px top");
+test_valid_value("perspective-origin", "bottom 10% right 20%", "right 20% bottom 10%");
+test_valid_value("perspective-origin", "bottom right", "right bottom");
+test_valid_value("perspective-origin", "center", ["center center", "center 50%"]);
+test_valid_value("perspective-origin", "center 50px");
+test_valid_value("perspective-origin", "center bottom");
+test_valid_value("perspective-origin", "center center");
+test_valid_value("perspective-origin", "center left", "left center");
+test_valid_value("perspective-origin", "left", ["left center", "left 50%"]);
+test_valid_value("perspective-origin", "left bottom");
+test_valid_value("perspective-origin", "left center");
+test_valid_value("perspective-origin", "right 40%");
+test_valid_value("perspective-origin", "right 30% top 60px");
+test_valid_value("perspective-origin", "top", ["center top", "50% top"]);
+test_valid_value("perspective-origin", "top center", "center top");
+</script>
+</body>
+</html>
diff --git a/css/css-transforms/parsing/resources/parsing-testcommon.js b/css/css-transforms/parsing/resources/parsing-testcommon.js
index 688356b..b075882 100644
--- a/css/css-transforms/parsing/resources/parsing-testcommon.js
+++ b/css/css-transforms/parsing/resources/parsing-testcommon.js
@@ -1,5 +1,8 @@
 'use strict';
 
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
 function test_valid_value(property, value, serializedValue) {
     if (arguments.length < 3)
         serializedValue = value;
@@ -9,17 +12,20 @@
     test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        assert_not_equals(div.style[property], "");
-    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
 
-    test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        var readValue = div.style[property];
-        assert_equals(readValue, serializedValue);
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
         div.style[property] = readValue;
-        assert_equals(div.style[property], readValue);
-    }, "Serialization should round-trip after setting e.style['" + property + "'] = " + stringifiedValue);
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
 }
 
 function test_invalid_value(property, value) {
@@ -28,6 +34,6 @@
     test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        assert_equals(div.style[property], "");
+        assert_equals(div.style.getPropertyValue(property), "");
     }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
 }
diff --git a/css/css-transforms/parsing/rotate-parsing-invalid.html b/css/css-transforms/parsing/rotate-parsing-invalid.html
index 2597a42..8326db3 100644
--- a/css/css-transforms/parsing/rotate-parsing-invalid.html
+++ b/css/css-transforms/parsing/rotate-parsing-invalid.html
@@ -17,6 +17,12 @@
 test_invalid_value("rotate", "100 400deg");
 test_invalid_value("rotate", "100 200 400deg");
 test_invalid_value("rotate", "100 200 300 500 400deg");
+
+test_invalid_value("rotate", "x y 45deg");
+test_invalid_value("rotate", "45deg x y");
+test_invalid_value("rotate", "z");
+test_invalid_value("rotate", "1 2");
+test_invalid_value("rotate", "1 2 3");
 </script>
 </body>
 </html>
diff --git a/css/css-transforms/parsing/rotate-parsing-valid.html b/css/css-transforms/parsing/rotate-parsing-valid.html
index a33f0fd..b2e5749 100644
--- a/css/css-transforms/parsing/rotate-parsing-valid.html
+++ b/css/css-transforms/parsing/rotate-parsing-valid.html
@@ -17,6 +17,16 @@
 test_valid_value("rotate", "0deg");
 
 test_valid_value("rotate", "100 200 300 400grad");
+test_valid_value("rotate", "400grad 100 200 300", "100 200 300 400grad");
+
+test_valid_value("rotate", "x 400grad", "1 0 0 400grad");
+test_valid_value("rotate", "400grad x", "1 0 0 400grad");
+
+test_valid_value("rotate", "y 400grad", "0 1 0 400grad");
+test_valid_value("rotate", "400grad y", "0 1 0 400grad");
+
+test_valid_value("rotate", "z 400grad", "0 0 1 400grad");
+test_valid_value("rotate", "400grad z", "0 0 1 400grad");
 </script>
 </body>
 </html>
diff --git a/css/css-transforms/parsing/translate-parsing-valid.html b/css/css-transforms/parsing/translate-parsing-valid.html
index beab0de..b117d27 100644
--- a/css/css-transforms/parsing/translate-parsing-valid.html
+++ b/css/css-transforms/parsing/translate-parsing-valid.html
@@ -23,7 +23,7 @@
 test_valid_value("translate", "100px 200px 300px");
 test_valid_value("translate", "100% 200% 300px");
 
-test_valid_value("translate", "calc(10px + 10%) calc(20px + 20%) calc(30px + 30em)");
+test_valid_value("translate", "calc(10% + 10px) calc(20% + 20px) calc(30em + 30px)");
 
 test_valid_value("translate", "0", "0px");
 test_valid_value("translate", "1px 2px 0", "1px 2px 0px");
diff --git a/css/css-transforms/transform-2d-getComputedStyle-001.html b/css/css-transforms/transform-2d-getComputedStyle-001.html
index 543dd9a..a085b79 100644
--- a/css/css-transforms/transform-2d-getComputedStyle-001.html
+++ b/css/css-transforms/transform-2d-getComputedStyle-001.html
@@ -45,15 +45,6 @@
     	transform: matrix(1, 2, 3, 4, 5, 6);
     }
     </style>
-    <script id="metadata_cache">/*
-	{
-	  "Matrix for translation transforms": {},
-	  "Matrix for rotate": {},
-	  "Matrix for scaling": {},
-	  "Matrix for skew": {},
-	  "Matrix for general transform": {}
-	}
-	*/</script>
     <script type="text/javascript" src="/resources/testharness.js"></script>
     <script type="text/javascript" src="/resources/testharnessreport.js"></script>
 </head>
diff --git a/css/css-transforms/transform-box/fill-box-mutation.html b/css/css-transforms/transform-box/fill-box-mutation.html
new file mode 100644
index 0000000..ae602e3
--- /dev/null
+++ b/css/css-transforms/transform-box/fill-box-mutation.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>transform-box: fill-box, shape mutated</title>
+<link rel="match" href="support/greensquare200x200.html">
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-box">
+<style>
+#target {
+  transform-box: fill-box;
+  transform: translate(-50%, 0);
+}
+</style>
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<svg width="400" height="200">
+  <rect width="200" height="200" fill="red"/>
+  <rect id="target" x="100" width="100" height="200" fill="green"/>
+</svg>
+<script>
+requestAnimationFrame(function() {
+  requestAnimationFrame(function() {
+    document.querySelector('#target').setAttribute('width', 200);
+    document.documentElement.classList.remove('reftest-wait');
+  });
+});
+</script>
diff --git a/css/css-transforms/transform-box/fill-box.html b/css/css-transforms/transform-box/fill-box.html
new file mode 100644
index 0000000..eaea02a
--- /dev/null
+++ b/css/css-transforms/transform-box/fill-box.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>transform-box: fill-box</title>
+<link rel="match" href="support/greensquare200x200.html">
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-box">
+<style>
+svg {
+  background-color: red;
+}
+rect {
+  transform-box: fill-box;
+}
+#target1 {
+  transform: rotate(90deg);
+}
+#target2 {
+  transform: translate(50%, -50%);
+}
+#target3 {
+  transform-origin: 25% 25%;
+  transform: rotate(180deg) translate(-25%, -25%);
+}
+#target4 {
+  transform-origin: 75px 75px;
+  transform: rotate(-180deg) translate(-25%, -25%);
+}
+</style>
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<svg width="200" height="200">
+  <rect id="target1" x="100" y="100" width="100" height="100" fill="green"/>
+  <rect id="target2" x="50" y="50" width="100" height="100" fill="green"/>
+  <rect id="target3" x="25" y="25" width="100" height="100" fill="green"/>
+  <rect id="target4" x="25" y="25" width="100" height="100" fill="green"/>
+</svg>
diff --git a/css/css-transforms/transform-box/support/greensquare200x200.html b/css/css-transforms/transform-box/support/greensquare200x200.html
new file mode 100644
index 0000000..bee8bc7
--- /dev/null
+++ b/css/css-transforms/transform-box/support/greensquare200x200.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<div style="width: 200px; height: 200px; background-color: green"></div>
diff --git a/css/css-transforms/transform-box/value-changed.html b/css/css-transforms/transform-box/value-changed.html
new file mode 100644
index 0000000..825403e
--- /dev/null
+++ b/css/css-transforms/transform-box/value-changed.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>transform-box: value change from 'view-box' to 'fill-box'</title>
+<link rel="match" href="support/greensquare200x200.html">
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-box">
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<svg width="400" height="200">
+  <rect width="200" height="200" fill="red"/>
+  <rect id="target" x="100" width="200" height="200" fill="green"
+        style="transform-box: view-box; transform: translateX(-50%)"/>
+</svg>
+<script>
+requestAnimationFrame(function() {
+  requestAnimationFrame(function() {
+    document.querySelector('#target').style.transformBox = 'fill-box';
+    document.documentElement.classList.remove('reftest-wait');
+  });
+});
+</script>
diff --git a/css/css-transforms/transform-box/view-box-nested.html b/css/css-transforms/transform-box/view-box-nested.html
new file mode 100644
index 0000000..7f59777
--- /dev/null
+++ b/css/css-transforms/transform-box/view-box-nested.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>transform-box: view-box, relative to nested viewport</title>
+<link rel="match" href="support/greensquare200x200.html">
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-box">
+<style>
+#target {
+  transform-box: view-box;
+  transform: translate(-50%, -50%);
+}
+</style>
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<svg width="400" height="200">
+  <rect width="200" height="200" fill="red"/>
+  <svg width="200" height="200">
+    <rect id="target" x="100" y="100" width="200" height="200" fill="green"/>
+  </svg>
+</svg>
diff --git a/css/css-transforms/transform-box/view-box-viewbox-nested.html b/css/css-transforms/transform-box/view-box-viewbox-nested.html
new file mode 100644
index 0000000..4f667e2
--- /dev/null
+++ b/css/css-transforms/transform-box/view-box-viewbox-nested.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>transform-box: view-box, relative to viewport defined by nested viewBox</title>
+<link rel="match" href="support/greensquare200x200.html">
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-box">
+<style>
+#target {
+  transform-box: view-box;
+  transform: translate(-50%, -50%);
+}
+</style>
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<svg width="400" height="200">
+  <rect width="200" height="200" fill="red"/>
+  <svg viewBox="0 0 200 200" preserveAspectRatio="xMinYMin">
+    <rect id="target" x="100" y="100" width="200" height="200" fill="green"/>
+  </svg>
+</svg>
diff --git a/css/css-transforms/transform-box/view-box-viewbox.html b/css/css-transforms/transform-box/view-box-viewbox.html
new file mode 100644
index 0000000..cd040c1
--- /dev/null
+++ b/css/css-transforms/transform-box/view-box-viewbox.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>transform-box: view-box, relative to viewport defined by viewBox</title>
+<link rel="match" href="support/greensquare200x200.html">
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-box">
+<style>
+#target {
+  transform-box: view-box;
+  transform: translate(-50%, -50%);
+}
+</style>
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<svg width="400" height="200" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin">
+  <rect width="200" height="200" fill="red"/>
+  <rect id="target" x="100" y="100" width="200" height="200" fill="green"/>
+</svg>
diff --git a/css/css-transforms/transform-box/view-box.html b/css/css-transforms/transform-box/view-box.html
new file mode 100644
index 0000000..3345467
--- /dev/null
+++ b/css/css-transforms/transform-box/view-box.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>transform-box: view-box</title>
+<link rel="match" href="support/greensquare200x200.html">
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-box">
+<style>
+svg {
+  background-color: red;
+}
+rect {
+  transform-box: view-box;
+}
+#target1 {
+  transform-origin: 25% 25%;
+  transform: scale(2);
+}
+#target2 {
+  transform: translate(50%, 0);
+}
+#target3 {
+  transform: translate(0, 50%);
+}
+#target4 {
+  transform-origin: 50% 50%;
+  transform: rotate(180deg);
+}
+</style>
+<p>There should be a green 200x200 rectangle below, and no red.</p>
+<svg width="200" height="200">
+  <rect id="target1" x="25" y="25" width="50" height="50" fill="green"/>
+  <rect id="target2" width="100" height="100" fill="green"/>
+  <rect id="target3" width="100" height="100" fill="green"/>
+  <rect id="target4" width="100" height="100" fill="green"/>
+</svg>
diff --git a/css/css-transforms/transform-origin-in-shadow.html b/css/css-transforms/transform-origin-in-shadow.html
new file mode 100644
index 0000000..ebbc616
--- /dev/null
+++ b/css/css-transforms/transform-origin-in-shadow.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>'transform-origin' on &lt;svg> being direct descendant of shadow root</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://drafts.csswg.org/css-transforms/#transform-origin-property">
+<template>
+  <style>
+    #target {
+      height: 100px;
+      width: 100px;
+      background-color: green;
+      transform: rotate(90deg);
+    }
+  </style>
+  <svg id="target"></svg>
+</template>
+<div style="position: relative; height: 100px">
+  <div style="width: 100px; height: 100px; background-color: red; position: absolute"></div>
+  <div style="position: absolute" id="wrapper"></div>
+</div>
+<script>
+setup(function() {
+  const wrapper = document.querySelector('#wrapper');
+  wrapper.attachShadow({mode: "open"});
+  wrapper.shadowRoot.appendChild(
+    document.querySelector("template").content.cloneNode(true));
+});
+
+test(function() {
+  const wrapper = document.querySelector('#wrapper');
+  const target = wrapper.shadowRoot.getElementById('target');
+  assert_equals(getComputedStyle(target, null).transformOrigin, '50px 50px');
+});
+</script>
diff --git a/css/css-transforms/transform-transformed-caption-contains-fixed-position-ref.html b/css/css-transforms/transform-transformed-caption-contains-fixed-position-ref.html
new file mode 100644
index 0000000..5400f2a
--- /dev/null
+++ b/css/css-transforms/transform-transformed-caption-contains-fixed-position-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed caption contains fixed position elements reference.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <style>
+      body {
+        overflow: hidden;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <div class='container'>some text<div class='fixed'>fixed</div>
+    </div>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-caption-contains-fixed-position.html b/css/css-transforms/transform-transformed-caption-contains-fixed-position.html
new file mode 100644
index 0000000..69de54b
--- /dev/null
+++ b/css/css-transforms/transform-transformed-caption-contains-fixed-position.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed caption contains fixed position elements.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+    <meta name="assert" content='This test ensures that transformed caption element is a containing block for fixed position elements.'>
+    <link rel="match" href="transform-transformed-caption-contains-fixed-position-ref.html">
+    <style>
+      table, caption {
+        margin: 0;
+        padding: 0;
+        border-spacing: 0;
+        text-align: left;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <table style='width: 100px;'>
+      <caption class='container'>some text<div class='fixed'>fixed</div>
+      </caption>
+    </table>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-tbody-contains-fixed-position-ref.html b/css/css-transforms/transform-transformed-tbody-contains-fixed-position-ref.html
new file mode 100644
index 0000000..f8db164
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tbody-contains-fixed-position-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed tbody contains fixed position elements reference.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <style>
+      body {
+        overflow: hidden;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <div class='container'>some text<div class='fixed'>fixed</div>
+    </div>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-tbody-contains-fixed-position.html b/css/css-transforms/transform-transformed-tbody-contains-fixed-position.html
new file mode 100644
index 0000000..b96e94e
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tbody-contains-fixed-position.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed tbody contains fixed position elements.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+    <meta name="assert" content='This test ensures that transformed tbody element is a containing block for fixed position elements.'>
+    <link rel="match" href="transform-transformed-tbody-contains-fixed-position-ref.html">
+    <style>
+      table, tbody, tr, td {
+        margin: 0;
+        padding: 0;
+        border-spacing: 0;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <table>
+      <tbody class='container'>
+        <tr>
+          <td>some text<div class='fixed'>fixed</div>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-td-contains-fixed-position-ref.html b/css/css-transforms/transform-transformed-td-contains-fixed-position-ref.html
new file mode 100644
index 0000000..27981a6
--- /dev/null
+++ b/css/css-transforms/transform-transformed-td-contains-fixed-position-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed td contains fixed position elements reference.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <style>
+      body {
+        overflow: hidden;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <div class='container'>some text<div class='fixed'>fixed</div>
+    </div>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-td-contains-fixed-position.html b/css/css-transforms/transform-transformed-td-contains-fixed-position.html
new file mode 100644
index 0000000..7e43d2a
--- /dev/null
+++ b/css/css-transforms/transform-transformed-td-contains-fixed-position.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed td contains fixed position elements.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+    <meta name="assert" content='This test ensures that transformed td element is a containing block for fixed position elements.'>
+    <link rel="match" href="transform-transformed-td-contains-fixed-position-ref.html">
+    <style>
+      table, tbody, tr, td {
+        margin: 0;
+        padding: 0;
+        border-spacing: 0;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <table>
+      <tr>
+        <td class='container'>some text<div class='fixed'>fixed</div>
+        </td>
+      </tr>
+    </table>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-tfoot-contains-fixed-position-ref.html b/css/css-transforms/transform-transformed-tfoot-contains-fixed-position-ref.html
new file mode 100644
index 0000000..9b4628d
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tfoot-contains-fixed-position-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed tfoot contains fixed position elements reference.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <style>
+      body {
+        overflow: hidden;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <div class='container'>some text<div class='fixed'>fixed</div>
+    </div>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-tfoot-contains-fixed-position.html b/css/css-transforms/transform-transformed-tfoot-contains-fixed-position.html
new file mode 100644
index 0000000..a04bba3
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tfoot-contains-fixed-position.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed tfoot contains fixed position elements.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+    <meta name="assert" content='This test ensures that transformed tfoot element is a containing block for fixed position elements.'>
+    <link rel="match" href="transform-transformed-tfoot-contains-fixed-position-ref.html">
+    <style>
+      table, tfoot, tr, td {
+        margin: 0;
+        padding: 0;
+        border-spacing: 0;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <table>
+      <tfoot class='container'>
+        <tr>
+          <td>some text<div class='fixed'>fixed</div>
+          </td>
+        </tr>
+      </tfoot>
+    </table>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-th-contains-fixed-position-ref.html b/css/css-transforms/transform-transformed-th-contains-fixed-position-ref.html
new file mode 100644
index 0000000..1c2d3bd
--- /dev/null
+++ b/css/css-transforms/transform-transformed-th-contains-fixed-position-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed th contains fixed position elements reference.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <style>
+      body {
+        overflow: hidden;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        font-weight: bold;
+        text-align: left;
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <div class='container'>some text<div class='fixed'>fixed</div>
+    </div>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-th-contains-fixed-position.html b/css/css-transforms/transform-transformed-th-contains-fixed-position.html
new file mode 100644
index 0000000..4ddde40
--- /dev/null
+++ b/css/css-transforms/transform-transformed-th-contains-fixed-position.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed th contains fixed position elements.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+    <meta name="assert" content='This test ensures that transformed th element is a containing block for fixed position elements.'>
+    <link rel="match" href="transform-transformed-th-contains-fixed-position-ref.html">
+    <style>
+      table, tr, td, th {
+        margin: 0;
+        padding: 0;
+        border-spacing: 0;
+        font-weight: bold;
+        text-align: left;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <table>
+      <tr>
+        <th class='container'>some text<div class='fixed'>fixed</div>
+        </th>
+      </tr>
+    </table>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-thead-contains-fixed-position-ref.html b/css/css-transforms/transform-transformed-thead-contains-fixed-position-ref.html
new file mode 100644
index 0000000..e5e4217
--- /dev/null
+++ b/css/css-transforms/transform-transformed-thead-contains-fixed-position-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed thead contains fixed position elements reference.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <style>
+      body {
+        overflow: hidden;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <div class='container'>some text<div class='fixed'>fixed</div>
+    </div>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-thead-contains-fixed-position.html b/css/css-transforms/transform-transformed-thead-contains-fixed-position.html
new file mode 100644
index 0000000..2270cff
--- /dev/null
+++ b/css/css-transforms/transform-transformed-thead-contains-fixed-position.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed thead contains fixed position elements.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+    <meta name="assert" content='This test ensures that transformed thead element is a containing block for fixed position elements.'>
+    <link rel="match" href="transform-transformed-thead-contains-fixed-position-ref.html">
+    <style>
+      table, thead, tr, td {
+        margin: 0;
+        padding: 0;
+        border-spacing: 0;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <table>
+      <thead class='container'>
+        <tr>
+          <td>some text<div class='fixed'>fixed</div>
+          </td>
+        </tr>
+      </thead>
+    </table>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-tr-contains-fixed-position-ref.html b/css/css-transforms/transform-transformed-tr-contains-fixed-position-ref.html
new file mode 100644
index 0000000..b0b6309
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tr-contains-fixed-position-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed tr contains fixed position elements reference.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <style>
+      body {
+        overflow: hidden;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <div class='container'>some text<div class='fixed'>fixed</div>
+    </div>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-tr-contains-fixed-position.html b/css/css-transforms/transform-transformed-tr-contains-fixed-position.html
new file mode 100644
index 0000000..52f015d
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tr-contains-fixed-position.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Test (Transforms): Transformed tr contains fixed position elements.</title>
+    <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+    <meta name="assert" content='This test ensures that transformed tr element is a containing block for fixed position elements.'>
+    <link rel="match" href="transform-transformed-tr-contains-fixed-position-ref.html">
+    <style>
+      table, tbody, tr, td {
+        margin: 0;
+        padding: 0;
+        border-spacing: 0;
+      }
+      .pad {
+        height: 50px;
+      }
+      .container {
+        transform: translateX(20px) rotate(45deg);
+        transform-origin: left;
+      }
+      .fixed {
+        position: fixed;
+        top: 15px;
+        left: 10px;
+        background-color: lightblue;
+      }
+    </style>
+  </head>
+  <body>
+    <div class='pad'></div>
+    <table>
+      <tr class='container'>
+        <td>some text<div class='fixed'>fixed</div>
+        </td>
+      </tr>
+    </table>
+  </body>
+</html>
diff --git a/css/css-transforms/transform-transformed-tr-percent-height-child-ref.html b/css/css-transforms/transform-transformed-tr-percent-height-child-ref.html
new file mode 100644
index 0000000..7e1ac99
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tr-percent-height-child-ref.html
@@ -0,0 +1,38 @@
+<!doctype HTML>
+
+<title>CSS Test (Transforms): Transformed tr with percent height abspos child reference.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<style>
+.table {
+  width: 100px;
+  height: 100px;
+  background-color: lightblue;
+}
+.tr {
+  height: 50px;
+  background-color: lightgrey;
+}
+.contblock {
+  transform: translateX(10px);
+  width: 200px;
+  height: 200px;
+  background-color: lightyellow;
+}
+.abspos {
+  position: absolute;
+  top: 20px;
+  left: 20px;
+  width: 100%;
+  height: 100%;
+  background-color: blue;
+}
+</style>
+
+<div class="contblock">
+  <div class="table">
+    <div class="tr" style="width: 50px;"></div>
+    <div class="tr" style="width: 100px; transform: translateX(10px)">
+      <div class="abspos"></div>
+    </div>
+  </div>
+</div>
diff --git a/css/css-transforms/transform-transformed-tr-percent-height-child.html b/css/css-transforms/transform-transformed-tr-percent-height-child.html
new file mode 100644
index 0000000..91f2a5f
--- /dev/null
+++ b/css/css-transforms/transform-transformed-tr-percent-height-child.html
@@ -0,0 +1,48 @@
+<!doctype HTML>
+
+<title>CSS Test (Transforms): Transformed tr with percent height abspos child.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-rendering">
+<meta name="assert" content="This test ensures that transformed tr's percent height abspos child uses tr's height as reference.">
+<link rel="match" href="transform-transformed-tr-percent-height-child-ref.html">
+<style>
+  table, td, tr {
+    margin: 0px;
+    padding: 0px;
+    border-spacing: 0px;
+  }
+  table {
+    background-color: lightblue;
+  }
+  td {
+    width: 50px;
+    height: 50px;
+    background-color: lightgrey;
+  }
+  .contblock {
+    transform: translateX(10px);
+    width: 200px;
+    height: 200px;
+    background-color: lightyellow;
+  }
+  .abspos {
+    position: absolute;
+    top: 20px;
+    left: 20px;
+    width: 100%;
+    height: 100%;
+    background-color: blue;
+  }
+</style>
+
+<div class="contblock">
+  <table>
+    <tr>
+      <td></td>
+    </tr>
+    <tr style="transform: translateX(10px)">
+      <td></td>
+      <td><div class="abspos"></div></td>
+    </tr>
+  </table>
+</div>
diff --git a/css/css-transforms/transforms-support-calc.html b/css/css-transforms/transforms-support-calc.html
index 19bacd9..71a046f 100644
--- a/css/css-transforms/transforms-support-calc.html
+++ b/css/css-transforms/transforms-support-calc.html
@@ -31,7 +31,7 @@
 
 test(function(){
   target.style = 'translate: calc(30px + 20%) calc(-200px + 100%);';
-  assert_equals(getComputedStyle(target).translate, '90px 0px');
+  assert_equals(getComputedStyle(target).translate, 'calc(30px + 20%) calc(-200px + 100%)');
 }, 'translate supports calc');
 
 test(function(){
diff --git a/css/css-transforms/translate-getComputedStyle.html b/css/css-transforms/translate-getComputedStyle.html
new file mode 100644
index 0000000..1e6759d
--- /dev/null
+++ b/css/css-transforms/translate-getComputedStyle.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Transform Module Level 2: translate getComputedStyle</title>
+  <link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+  <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#propdef-translate">
+  <meta name="assert" content="translate computed style does not resolve percentages.">
+  <style type="text/css">
+    #container {
+      transform-style: preserve-3d;;
+    }
+    #first {
+      font-size: 10px;
+      translate: 10px 2em;
+    }
+    #second {
+      translate: 30% 40% 50px;
+    }
+    #third {
+      font-size: 10px;
+      width: 98px;
+      height: 76px;
+      translate: calc(7em + 80%) -9em;
+    }
+  </style>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+  <div id="container">
+    <div id="first"></div>
+    <div id="second"></div>
+    <div id="third"></div>
+  </div>
+  <script>
+    'use strict';
+    function getTranslateFor(id) {
+      return window.getComputedStyle(document.getElementById(id)).getPropertyValue("translate");
+    }
+
+    test(function() {
+      assert_equals(getTranslateFor("first"), "10px 20px");
+      assert_equals(getTranslateFor("second"), "30% 40% 50px");
+      assert_equals(getTranslateFor("third"), "calc(70px + 80%) -90px");
+    }, "computed style for translate");
+  </script>
+</body>
+</html>
diff --git a/css/css-transitions/before-DOMContentLoaded-001.html b/css/css-transitions/before-DOMContentLoaded-001.html
index 4e6c3d1..152f777 100644
--- a/css/css-transitions/before-DOMContentLoaded-001.html
+++ b/css/css-transitions/before-DOMContentLoaded-001.html
@@ -26,13 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "transition height from 10px to 100px / values": {},
-          "transition height from 10px to 100px / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/before-load-001.html b/css/css-transitions/before-load-001.html
index 4cb4370..ee633cf 100644
--- a/css/css-transitions/before-load-001.html
+++ b/css/css-transitions/before-load-001.html
@@ -26,13 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "transition height from 10px to 100px / values": {},
-          "transition height from 10px to 100px / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/changing-while-transition.html b/css/css-transitions/changing-while-transition.html
index 99a2bea..2e7d0bb 100644
--- a/css/css-transitions/changing-while-transition.html
+++ b/css/css-transitions/changing-while-transition.html
@@ -25,13 +25,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "changing transition-duration / values": {},
-          "changing transition-property / values": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/currentcolor-animation-001.html b/css/css-transitions/currentcolor-animation-001.html
index 138fec6..e36e748 100644
--- a/css/css-transitions/currentcolor-animation-001.html
+++ b/css/css-transitions/currentcolor-animation-001.html
@@ -9,7 +9,6 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
 <style id="style"></style>
-<script id="metadata_cache"></script>
 </head>
 <body onload="run()">
 <div id=log></div>
diff --git a/css/css-transitions/detached-container-001.html b/css/css-transitions/detached-container-001.html
index 096147e..efbc34f 100644
--- a/css/css-transitions/detached-container-001.html
+++ b/css/css-transitions/detached-container-001.html
@@ -26,13 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "transition within detached container / values": {},
-          "transition within detached container / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/hidden-container-001.html b/css/css-transitions/hidden-container-001.html
index 6bb27ec..4d17d4a 100644
--- a/css/css-transitions/hidden-container-001.html
+++ b/css/css-transitions/hidden-container-001.html
@@ -22,13 +22,6 @@
                 display: none;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "transition within display:none / values": {},
-          "transition within display:none / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-001.html b/css/css-transitions/properties-value-001.html
index ade0c0e..9182930 100644
--- a/css/css-transitions/properties-value-001.html
+++ b/css/css-transitions/properties-value-001.html
@@ -27,573 +27,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "background-color color(rgba) / values": {},
-          "background-color color(rgba) / events": {},
-          "background-position length(pt) / values": {},
-          "background-position length(pt) / events": {},
-          "background-position length(pc) / values": {},
-          "background-position length(pc) / events": {},
-          "background-position length(px) / values": {},
-          "background-position length(px) / events": {},
-          "background-position length(em) / values": {},
-          "background-position length(em) / events": {},
-          "background-position length(ex) / values": {},
-          "background-position length(ex) / events": {},
-          "background-position length(mm) / values": {},
-          "background-position length(mm) / events": {},
-          "background-position length(cm) / values": {},
-          "background-position length(cm) / events": {},
-          "background-position length(in) / values": {},
-          "background-position length(in) / events": {},
-          "background-position percentage(%) / values": {},
-          "background-position percentage(%) / events": {},
-          "border-top-width length(pt) / values": {},
-          "border-top-width length(pt) / events": {},
-          "border-top-width length(pc) / values": {},
-          "border-top-width length(pc) / events": {},
-          "border-top-width length(px) / values": {},
-          "border-top-width length(px) / events": {},
-          "border-top-width length(em) / values": {},
-          "border-top-width length(em) / events": {},
-          "border-top-width length(ex) / values": {},
-          "border-top-width length(ex) / events": {},
-          "border-top-width length(mm) / values": {},
-          "border-top-width length(mm) / events": {},
-          "border-top-width length(cm) / values": {},
-          "border-top-width length(cm) / events": {},
-          "border-top-width length(in) / values": {},
-          "border-top-width length(in) / events": {},
-          "border-right-width length(pt) / values": {},
-          "border-right-width length(pt) / events": {},
-          "border-right-width length(pc) / values": {},
-          "border-right-width length(pc) / events": {},
-          "border-right-width length(px) / values": {},
-          "border-right-width length(px) / events": {},
-          "border-right-width length(em) / values": {},
-          "border-right-width length(em) / events": {},
-          "border-right-width length(ex) / values": {},
-          "border-right-width length(ex) / events": {},
-          "border-right-width length(mm) / values": {},
-          "border-right-width length(mm) / events": {},
-          "border-right-width length(cm) / values": {},
-          "border-right-width length(cm) / events": {},
-          "border-right-width length(in) / values": {},
-          "border-right-width length(in) / events": {},
-          "border-bottom-width length(pt) / values": {},
-          "border-bottom-width length(pt) / events": {},
-          "border-bottom-width length(pc) / values": {},
-          "border-bottom-width length(pc) / events": {},
-          "border-bottom-width length(px) / values": {},
-          "border-bottom-width length(px) / events": {},
-          "border-bottom-width length(em) / values": {},
-          "border-bottom-width length(em) / events": {},
-          "border-bottom-width length(ex) / values": {},
-          "border-bottom-width length(ex) / events": {},
-          "border-bottom-width length(mm) / values": {},
-          "border-bottom-width length(mm) / events": {},
-          "border-bottom-width length(cm) / values": {},
-          "border-bottom-width length(cm) / events": {},
-          "border-bottom-width length(in) / values": {},
-          "border-bottom-width length(in) / events": {},
-          "border-left-width length(pt) / values": {},
-          "border-left-width length(pt) / events": {},
-          "border-left-width length(pc) / values": {},
-          "border-left-width length(pc) / events": {},
-          "border-left-width length(px) / values": {},
-          "border-left-width length(px) / events": {},
-          "border-left-width length(em) / values": {},
-          "border-left-width length(em) / events": {},
-          "border-left-width length(ex) / values": {},
-          "border-left-width length(ex) / events": {},
-          "border-left-width length(mm) / values": {},
-          "border-left-width length(mm) / events": {},
-          "border-left-width length(cm) / values": {},
-          "border-left-width length(cm) / events": {},
-          "border-left-width length(in) / values": {},
-          "border-left-width length(in) / events": {},
-          "border-top-color color(rgba) / values": {},
-          "border-top-color color(rgba) / events": {},
-          "border-right-color color(rgba) / values": {},
-          "border-right-color color(rgba) / events": {},
-          "border-bottom-color color(rgba) / values": {},
-          "border-bottom-color color(rgba) / events": {},
-          "border-left-color color(rgba) / values": {},
-          "border-left-color color(rgba) / events": {},
-          "padding-bottom length(pt) / values": {},
-          "padding-bottom length(pt) / events": {},
-          "padding-bottom length(pc) / values": {},
-          "padding-bottom length(pc) / events": {},
-          "padding-bottom length(px) / values": {},
-          "padding-bottom length(px) / events": {},
-          "padding-bottom length(em) / values": {},
-          "padding-bottom length(em) / events": {},
-          "padding-bottom length(ex) / values": {},
-          "padding-bottom length(ex) / events": {},
-          "padding-bottom length(mm) / values": {},
-          "padding-bottom length(mm) / events": {},
-          "padding-bottom length(cm) / values": {},
-          "padding-bottom length(cm) / events": {},
-          "padding-bottom length(in) / values": {},
-          "padding-bottom length(in) / events": {},
-          "padding-left length(pt) / values": {},
-          "padding-left length(pt) / events": {},
-          "padding-left length(pc) / values": {},
-          "padding-left length(pc) / events": {},
-          "padding-left length(px) / values": {},
-          "padding-left length(px) / events": {},
-          "padding-left length(em) / values": {},
-          "padding-left length(em) / events": {},
-          "padding-left length(ex) / values": {},
-          "padding-left length(ex) / events": {},
-          "padding-left length(mm) / values": {},
-          "padding-left length(mm) / events": {},
-          "padding-left length(cm) / values": {},
-          "padding-left length(cm) / events": {},
-          "padding-left length(in) / values": {},
-          "padding-left length(in) / events": {},
-          "padding-right length(pt) / values": {},
-          "padding-right length(pt) / events": {},
-          "padding-right length(pc) / values": {},
-          "padding-right length(pc) / events": {},
-          "padding-right length(px) / values": {},
-          "padding-right length(px) / events": {},
-          "padding-right length(em) / values": {},
-          "padding-right length(em) / events": {},
-          "padding-right length(ex) / values": {},
-          "padding-right length(ex) / events": {},
-          "padding-right length(mm) / values": {},
-          "padding-right length(mm) / events": {},
-          "padding-right length(cm) / values": {},
-          "padding-right length(cm) / events": {},
-          "padding-right length(in) / values": {},
-          "padding-right length(in) / events": {},
-          "padding-top length(pt) / values": {},
-          "padding-top length(pt) / events": {},
-          "padding-top length(pc) / values": {},
-          "padding-top length(pc) / events": {},
-          "padding-top length(px) / values": {},
-          "padding-top length(px) / events": {},
-          "padding-top length(em) / values": {},
-          "padding-top length(em) / events": {},
-          "padding-top length(ex) / values": {},
-          "padding-top length(ex) / events": {},
-          "padding-top length(mm) / values": {},
-          "padding-top length(mm) / events": {},
-          "padding-top length(cm) / values": {},
-          "padding-top length(cm) / events": {},
-          "padding-top length(in) / values": {},
-          "padding-top length(in) / events": {},
-          "margin-bottom length(pt) / values": {},
-          "margin-bottom length(pt) / events": {},
-          "margin-bottom length(pc) / values": {},
-          "margin-bottom length(pc) / events": {},
-          "margin-bottom length(px) / values": {},
-          "margin-bottom length(px) / events": {},
-          "margin-bottom length(em) / values": {},
-          "margin-bottom length(em) / events": {},
-          "margin-bottom length(ex) / values": {},
-          "margin-bottom length(ex) / events": {},
-          "margin-bottom length(mm) / values": {},
-          "margin-bottom length(mm) / events": {},
-          "margin-bottom length(cm) / values": {},
-          "margin-bottom length(cm) / events": {},
-          "margin-bottom length(in) / values": {},
-          "margin-bottom length(in) / events": {},
-          "margin-left length(pt) / values": {},
-          "margin-left length(pt) / events": {},
-          "margin-left length(pc) / values": {},
-          "margin-left length(pc) / events": {},
-          "margin-left length(px) / values": {},
-          "margin-left length(px) / events": {},
-          "margin-left length(em) / values": {},
-          "margin-left length(em) / events": {},
-          "margin-left length(ex) / values": {},
-          "margin-left length(ex) / events": {},
-          "margin-left length(mm) / values": {},
-          "margin-left length(mm) / events": {},
-          "margin-left length(cm) / values": {},
-          "margin-left length(cm) / events": {},
-          "margin-left length(in) / values": {},
-          "margin-left length(in) / events": {},
-          "margin-right length(pt) / values": {},
-          "margin-right length(pt) / events": {},
-          "margin-right length(pc) / values": {},
-          "margin-right length(pc) / events": {},
-          "margin-right length(px) / values": {},
-          "margin-right length(px) / events": {},
-          "margin-right length(em) / values": {},
-          "margin-right length(em) / events": {},
-          "margin-right length(ex) / values": {},
-          "margin-right length(ex) / events": {},
-          "margin-right length(mm) / values": {},
-          "margin-right length(mm) / events": {},
-          "margin-right length(cm) / values": {},
-          "margin-right length(cm) / events": {},
-          "margin-right length(in) / values": {},
-          "margin-right length(in) / events": {},
-          "margin-top length(pt) / values": {},
-          "margin-top length(pt) / events": {},
-          "margin-top length(pc) / values": {},
-          "margin-top length(pc) / events": {},
-          "margin-top length(px) / values": {},
-          "margin-top length(px) / events": {},
-          "margin-top length(em) / values": {},
-          "margin-top length(em) / events": {},
-          "margin-top length(ex) / values": {},
-          "margin-top length(ex) / events": {},
-          "margin-top length(mm) / values": {},
-          "margin-top length(mm) / events": {},
-          "margin-top length(cm) / values": {},
-          "margin-top length(cm) / events": {},
-          "margin-top length(in) / values": {},
-          "margin-top length(in) / events": {},
-          "height length(pt) / values": {},
-          "height length(pt) / events": {},
-          "height length(pc) / values": {},
-          "height length(pc) / events": {},
-          "height length(px) / values": {},
-          "height length(px) / events": {},
-          "height length(em) / values": {},
-          "height length(em) / events": {},
-          "height length(ex) / values": {},
-          "height length(ex) / events": {},
-          "height length(mm) / values": {},
-          "height length(mm) / events": {},
-          "height length(cm) / values": {},
-          "height length(cm) / events": {},
-          "height length(in) / values": {},
-          "height length(in) / events": {},
-          "height percentage(%) / values": {},
-          "height percentage(%) / events": {},
-          "width length(pt) / values": {},
-          "width length(pt) / events": {},
-          "width length(pc) / values": {},
-          "width length(pc) / events": {},
-          "width length(px) / values": {},
-          "width length(px) / events": {},
-          "width length(em) / values": {},
-          "width length(em) / events": {},
-          "width length(ex) / values": {},
-          "width length(ex) / events": {},
-          "width length(mm) / values": {},
-          "width length(mm) / events": {},
-          "width length(cm) / values": {},
-          "width length(cm) / events": {},
-          "width length(in) / values": {},
-          "width length(in) / events": {},
-          "width percentage(%) / values": {},
-          "width percentage(%) / events": {},
-          "min-height length(pt) / values": {},
-          "min-height length(pt) / events": {},
-          "min-height length(pc) / values": {},
-          "min-height length(pc) / events": {},
-          "min-height length(px) / values": {},
-          "min-height length(px) / events": {},
-          "min-height length(em) / values": {},
-          "min-height length(em) / events": {},
-          "min-height length(ex) / values": {},
-          "min-height length(ex) / events": {},
-          "min-height length(mm) / values": {},
-          "min-height length(mm) / events": {},
-          "min-height length(cm) / values": {},
-          "min-height length(cm) / events": {},
-          "min-height length(in) / values": {},
-          "min-height length(in) / events": {},
-          "min-height percentage(%) / values": {},
-          "min-height percentage(%) / events": {},
-          "min-width length(pt) / values": {},
-          "min-width length(pt) / events": {},
-          "min-width length(pc) / values": {},
-          "min-width length(pc) / events": {},
-          "min-width length(px) / values": {},
-          "min-width length(px) / events": {},
-          "min-width length(em) / values": {},
-          "min-width length(em) / events": {},
-          "min-width length(ex) / values": {},
-          "min-width length(ex) / events": {},
-          "min-width length(mm) / values": {},
-          "min-width length(mm) / events": {},
-          "min-width length(cm) / values": {},
-          "min-width length(cm) / events": {},
-          "min-width length(in) / values": {},
-          "min-width length(in) / events": {},
-          "min-width percentage(%) / values": {},
-          "min-width percentage(%) / events": {},
-          "max-height length(pt) / values": {},
-          "max-height length(pt) / events": {},
-          "max-height length(pc) / values": {},
-          "max-height length(pc) / events": {},
-          "max-height length(px) / values": {},
-          "max-height length(px) / events": {},
-          "max-height length(em) / values": {},
-          "max-height length(em) / events": {},
-          "max-height length(ex) / values": {},
-          "max-height length(ex) / events": {},
-          "max-height length(mm) / values": {},
-          "max-height length(mm) / events": {},
-          "max-height length(cm) / values": {},
-          "max-height length(cm) / events": {},
-          "max-height length(in) / values": {},
-          "max-height length(in) / events": {},
-          "max-height percentage(%) / values": {},
-          "max-height percentage(%) / events": {},
-          "max-width length(pt) / values": {},
-          "max-width length(pt) / events": {},
-          "max-width length(pc) / values": {},
-          "max-width length(pc) / events": {},
-          "max-width length(px) / values": {},
-          "max-width length(px) / events": {},
-          "max-width length(em) / values": {},
-          "max-width length(em) / events": {},
-          "max-width length(ex) / values": {},
-          "max-width length(ex) / events": {},
-          "max-width length(mm) / values": {},
-          "max-width length(mm) / events": {},
-          "max-width length(cm) / values": {},
-          "max-width length(cm) / events": {},
-          "max-width length(in) / values": {},
-          "max-width length(in) / events": {},
-          "max-width percentage(%) / values": {},
-          "max-width percentage(%) / events": {},
-          "top length(pt) / values": {},
-          "top length(pt) / events": {},
-          "top length(pc) / values": {},
-          "top length(pc) / events": {},
-          "top length(px) / values": {},
-          "top length(px) / events": {},
-          "top length(em) / values": {},
-          "top length(em) / events": {},
-          "top length(ex) / values": {},
-          "top length(ex) / events": {},
-          "top length(mm) / values": {},
-          "top length(mm) / events": {},
-          "top length(cm) / values": {},
-          "top length(cm) / events": {},
-          "top length(in) / values": {},
-          "top length(in) / events": {},
-          "top percentage(%) / values": {},
-          "top percentage(%) / events": {},
-          "right length(pt) / values": {},
-          "right length(pt) / events": {},
-          "right length(pc) / values": {},
-          "right length(pc) / events": {},
-          "right length(px) / values": {},
-          "right length(px) / events": {},
-          "right length(em) / values": {},
-          "right length(em) / events": {},
-          "right length(ex) / values": {},
-          "right length(ex) / events": {},
-          "right length(mm) / values": {},
-          "right length(mm) / events": {},
-          "right length(cm) / values": {},
-          "right length(cm) / events": {},
-          "right length(in) / values": {},
-          "right length(in) / events": {},
-          "right percentage(%) / values": {},
-          "right percentage(%) / events": {},
-          "bottom length(pt) / values": {},
-          "bottom length(pt) / events": {},
-          "bottom length(pc) / values": {},
-          "bottom length(pc) / events": {},
-          "bottom length(px) / values": {},
-          "bottom length(px) / events": {},
-          "bottom length(em) / values": {},
-          "bottom length(em) / events": {},
-          "bottom length(ex) / values": {},
-          "bottom length(ex) / events": {},
-          "bottom length(mm) / values": {},
-          "bottom length(mm) / events": {},
-          "bottom length(cm) / values": {},
-          "bottom length(cm) / events": {},
-          "bottom length(in) / values": {},
-          "bottom length(in) / events": {},
-          "bottom percentage(%) / values": {},
-          "bottom percentage(%) / events": {},
-          "left length(pt) / values": {},
-          "left length(pt) / events": {},
-          "left length(pc) / values": {},
-          "left length(pc) / events": {},
-          "left length(px) / values": {},
-          "left length(px) / events": {},
-          "left length(em) / values": {},
-          "left length(em) / events": {},
-          "left length(ex) / values": {},
-          "left length(ex) / events": {},
-          "left length(mm) / values": {},
-          "left length(mm) / events": {},
-          "left length(cm) / values": {},
-          "left length(cm) / events": {},
-          "left length(in) / values": {},
-          "left length(in) / events": {},
-          "left percentage(%) / values": {},
-          "left percentage(%) / events": {},
-          "color color(rgba) / values": {},
-          "color color(rgba) / events": {},
-          "font-size length(pt) / values": {},
-          "font-size length(pt) / events": {},
-          "font-size length(pc) / values": {},
-          "font-size length(pc) / events": {},
-          "font-size length(px) / values": {},
-          "font-size length(px) / events": {},
-          "font-size length(em) / values": {},
-          "font-size length(em) / events": {},
-          "font-size length(ex) / values": {},
-          "font-size length(ex) / events": {},
-          "font-size length(mm) / values": {},
-          "font-size length(mm) / events": {},
-          "font-size length(cm) / values": {},
-          "font-size length(cm) / events": {},
-          "font-size length(in) / values": {},
-          "font-size length(in) / events": {},
-          "font-size percentage(%) / values": {},
-          "font-size percentage(%) / events": {},
-          "font-weight font-weight(keyword) / values": {},
-          "font-weight font-weight(keyword) / events": {},
-          "font-weight font-weight(numeric) / values": {},
-          "font-weight font-weight(numeric) / events": {},
-          "line-height number(integer) / values": {},
-          "line-height number(integer) / events": {},
-          "line-height number(decimal) / values": {},
-          "line-height number(decimal) / events": {},
-          "line-height length(pt) / values": {},
-          "line-height length(pt) / events": {},
-          "line-height length(pc) / values": {},
-          "line-height length(pc) / events": {},
-          "line-height length(px) / values": {},
-          "line-height length(px) / events": {},
-          "line-height length(em) / values": {},
-          "line-height length(em) / events": {},
-          "line-height length(ex) / values": {},
-          "line-height length(ex) / events": {},
-          "line-height length(mm) / values": {},
-          "line-height length(mm) / events": {},
-          "line-height length(cm) / values": {},
-          "line-height length(cm) / events": {},
-          "line-height length(in) / values": {},
-          "line-height length(in) / events": {},
-          "line-height percentage(%) / values": {},
-          "line-height percentage(%) / events": {},
-          "letter-spacing length(pt) / values": {},
-          "letter-spacing length(pt) / events": {},
-          "letter-spacing length(pc) / values": {},
-          "letter-spacing length(pc) / events": {},
-          "letter-spacing length(px) / values": {},
-          "letter-spacing length(px) / events": {},
-          "letter-spacing length(em) / values": {},
-          "letter-spacing length(em) / events": {},
-          "letter-spacing length(ex) / values": {},
-          "letter-spacing length(ex) / events": {},
-          "letter-spacing length(mm) / values": {},
-          "letter-spacing length(mm) / events": {},
-          "letter-spacing length(cm) / values": {},
-          "letter-spacing length(cm) / events": {},
-          "letter-spacing length(in) / values": {},
-          "letter-spacing length(in) / events": {},
-          "word-spacing length(pt) / values": {},
-          "word-spacing length(pt) / events": {},
-          "word-spacing length(pc) / values": {},
-          "word-spacing length(pc) / events": {},
-          "word-spacing length(px) / values": {},
-          "word-spacing length(px) / events": {},
-          "word-spacing length(em) / values": {},
-          "word-spacing length(em) / events": {},
-          "word-spacing length(ex) / values": {},
-          "word-spacing length(ex) / events": {},
-          "word-spacing length(mm) / values": {},
-          "word-spacing length(mm) / events": {},
-          "word-spacing length(cm) / values": {},
-          "word-spacing length(cm) / events": {},
-          "word-spacing length(in) / values": {},
-          "word-spacing length(in) / events": {},
-          "word-spacing percentage(%) / values": {},
-          "word-spacing percentage(%) / events": {},
-          "text-indent length(pt) / values": {},
-          "text-indent length(pt) / events": {},
-          "text-indent length(pc) / values": {},
-          "text-indent length(pc) / events": {},
-          "text-indent length(px) / values": {},
-          "text-indent length(px) / events": {},
-          "text-indent length(em) / values": {},
-          "text-indent length(em) / events": {},
-          "text-indent length(ex) / values": {},
-          "text-indent length(ex) / events": {},
-          "text-indent length(mm) / values": {},
-          "text-indent length(mm) / events": {},
-          "text-indent length(cm) / values": {},
-          "text-indent length(cm) / events": {},
-          "text-indent length(in) / values": {},
-          "text-indent length(in) / events": {},
-          "text-indent percentage(%) / values": {},
-          "text-indent percentage(%) / events": {},
-          "text-shadow shadow(shadow) / values": {},
-          "text-shadow shadow(shadow) / events": {},
-          "outline-color color(rgba) / values": {},
-          "outline-color color(rgba) / events": {},
-          "outline-offset length(pt) / values": {},
-          "outline-offset length(pt) / events": {},
-          "outline-offset length(pc) / values": {},
-          "outline-offset length(pc) / events": {},
-          "outline-offset length(px) / values": {},
-          "outline-offset length(px) / events": {},
-          "outline-offset length(em) / values": {},
-          "outline-offset length(em) / events": {},
-          "outline-offset length(ex) / values": {},
-          "outline-offset length(ex) / events": {},
-          "outline-offset length(mm) / values": {},
-          "outline-offset length(mm) / events": {},
-          "outline-offset length(cm) / values": {},
-          "outline-offset length(cm) / events": {},
-          "outline-offset length(in) / values": {},
-          "outline-offset length(in) / events": {},
-          "outline-width length(pt) / values": {},
-          "outline-width length(pt) / events": {},
-          "outline-width length(pc) / values": {},
-          "outline-width length(pc) / events": {},
-          "outline-width length(px) / values": {},
-          "outline-width length(px) / events": {},
-          "outline-width length(em) / values": {},
-          "outline-width length(em) / events": {},
-          "outline-width length(ex) / values": {},
-          "outline-width length(ex) / events": {},
-          "outline-width length(mm) / values": {},
-          "outline-width length(mm) / events": {},
-          "outline-width length(cm) / values": {},
-          "outline-width length(cm) / events": {},
-          "outline-width length(in) / values": {},
-          "outline-width length(in) / events": {},
-          "clip rectangle(rectangle) / values": {},
-          "clip rectangle(rectangle) / events": {},
-          "crop rectangle(rectangle) / values": {},
-          "crop rectangle(rectangle) / events": {},
-          "vertical-align length(pt) / values": {},
-          "vertical-align length(pt) / events": {},
-          "vertical-align length(pc) / values": {},
-          "vertical-align length(pc) / events": {},
-          "vertical-align length(px) / values": {},
-          "vertical-align length(px) / events": {},
-          "vertical-align length(em) / values": {},
-          "vertical-align length(em) / events": {},
-          "vertical-align length(ex) / values": {},
-          "vertical-align length(ex) / events": {},
-          "vertical-align length(mm) / values": {},
-          "vertical-align length(mm) / events": {},
-          "vertical-align length(cm) / values": {},
-          "vertical-align length(cm) / events": {},
-          "vertical-align length(in) / values": {},
-          "vertical-align length(in) / events": {},
-          "vertical-align percentage(%) / values": {},
-          "vertical-align percentage(%) / events": {},
-          "opacity number[0,1](zero-to-one) / values": {},
-          "opacity number[0,1](zero-to-one) / events": {},
-          "visibility visibility(keyword) / values": {},
-          "visibility visibility(keyword) / events": {},
-          "z-index integer(integer) / values": {},
-          "z-index integer(integer) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-002.html b/css/css-transitions/properties-value-002.html
index ca0277c..7298070 100644
--- a/css/css-transitions/properties-value-002.html
+++ b/css/css-transitions/properties-value-002.html
@@ -27,29 +27,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "margin-bottom percentage(%) / values": {},
-          "margin-bottom percentage(%) / events": {},
-          "margin-left percentage(%) / values": {},
-          "margin-left percentage(%) / events": {},
-          "margin-right percentage(%) / values": {},
-          "margin-right percentage(%) / events": {},
-          "margin-top percentage(%) / values": {},
-          "margin-top percentage(%) / events": {},
-          "padding-bottom percentage(%) / values": {},
-          "padding-bottom percentage(%) / events": {},
-          "padding-left percentage(%) / values": {},
-          "padding-left percentage(%) / events": {},
-          "padding-right percentage(%) / values": {},
-          "padding-right percentage(%) / events": {},
-          "padding-top percentage(%) / values": {},
-          "padding-top percentage(%) / events": {},
-          "vertical-align vertical(keyword) / values": {},
-          "vertical-align vertical(keyword) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-003.html b/css/css-transitions/properties-value-003.html
index e32cd36..23385d1 100644
--- a/css/css-transitions/properties-value-003.html
+++ b/css/css-transitions/properties-value-003.html
@@ -27,203 +27,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "border-top-left-radius border-radius(px) / values": {},
-          "border-top-left-radius border-radius(px) / events": {},
-          "border-top-left-radius border-radius(px-px) / values": {},
-          "border-top-left-radius border-radius(px-px) / events": {},
-          "border-top-right-radius border-radius(px) / values": {},
-          "border-top-right-radius border-radius(px) / events": {},
-          "border-top-right-radius border-radius(px-px) / values": {},
-          "border-top-right-radius border-radius(px-px) / events": {},
-          "border-bottom-left-radius border-radius(px) / values": {},
-          "border-bottom-left-radius border-radius(px) / events": {},
-          "border-bottom-left-radius border-radius(px-px) / values": {},
-          "border-bottom-left-radius border-radius(px-px) / events": {},
-          "border-bottom-right-radius border-radius(px) / values": {},
-          "border-bottom-right-radius border-radius(px) / events": {},
-          "border-bottom-right-radius border-radius(px-px) / values": {},
-          "border-bottom-right-radius border-radius(px-px) / events": {},
-          "background-image image(url) / values": {},
-          "background-image image(url) / events": {},
-          "background-image image(data) / values": {},
-          "background-image image(data) / events": {},
-          "background-image image(gradient) / values": {},
-          "background-image image(gradient) / events": {},
-          "background-size background-size(keyword) / values": {},
-          "background-size background-size(keyword) / events": {},
-          "box-shadow box-shadow(shadow) / values": {},
-          "box-shadow box-shadow(shadow) / events": {},
-          "font-size-adjust number(integer) / values": {},
-          "font-size-adjust number(integer) / events": {},
-          "font-size-adjust number(decimal) / values": {},
-          "font-size-adjust number(decimal) / events": {},
-          "font-stretch font-stretch(keyword) / values": {},
-          "font-stretch font-stretch(keyword) / events": {},
-          "marker-offset length(pt) / values": {},
-          "marker-offset length(pt) / events": {},
-          "marker-offset length(pc) / values": {},
-          "marker-offset length(pc) / events": {},
-          "marker-offset length(px) / values": {},
-          "marker-offset length(px) / events": {},
-          "marker-offset length(em) / values": {},
-          "marker-offset length(em) / events": {},
-          "marker-offset length(ex) / values": {},
-          "marker-offset length(ex) / events": {},
-          "marker-offset length(mm) / values": {},
-          "marker-offset length(mm) / events": {},
-          "marker-offset length(cm) / values": {},
-          "marker-offset length(cm) / events": {},
-          "marker-offset length(in) / values": {},
-          "marker-offset length(in) / events": {},
-          "text-decoration-color color(rgba) / values": {},
-          "text-decoration-color color(rgba) / events": {},
-          "column-count integer(integer) / values": {},
-          "column-count integer(integer) / events": {},
-          "column-gap length(pt) / values": {},
-          "column-gap length(pt) / events": {},
-          "column-gap length(pc) / values": {},
-          "column-gap length(pc) / events": {},
-          "column-gap length(px) / values": {},
-          "column-gap length(px) / events": {},
-          "column-gap length(em) / values": {},
-          "column-gap length(em) / events": {},
-          "column-gap length(ex) / values": {},
-          "column-gap length(ex) / events": {},
-          "column-gap length(mm) / values": {},
-          "column-gap length(mm) / events": {},
-          "column-gap length(cm) / values": {},
-          "column-gap length(cm) / events": {},
-          "column-gap length(in) / values": {},
-          "column-gap length(in) / events": {},
-          "column-rule-color color(rgba) / values": {},
-          "column-rule-color color(rgba) / events": {},
-          "column-rule-width length(pt) / values": {},
-          "column-rule-width length(pt) / events": {},
-          "column-rule-width length(pc) / values": {},
-          "column-rule-width length(pc) / events": {},
-          "column-rule-width length(px) / values": {},
-          "column-rule-width length(px) / events": {},
-          "column-rule-width length(em) / values": {},
-          "column-rule-width length(em) / events": {},
-          "column-rule-width length(ex) / values": {},
-          "column-rule-width length(ex) / events": {},
-          "column-rule-width length(mm) / values": {},
-          "column-rule-width length(mm) / events": {},
-          "column-rule-width length(cm) / values": {},
-          "column-rule-width length(cm) / events": {},
-          "column-rule-width length(in) / values": {},
-          "column-rule-width length(in) / events": {},
-          "column-width length(pt) / values": {},
-          "column-width length(pt) / events": {},
-          "column-width length(pc) / values": {},
-          "column-width length(pc) / events": {},
-          "column-width length(px) / values": {},
-          "column-width length(px) / events": {},
-          "column-width length(em) / values": {},
-          "column-width length(em) / events": {},
-          "column-width length(ex) / values": {},
-          "column-width length(ex) / events": {},
-          "column-width length(mm) / values": {},
-          "column-width length(mm) / events": {},
-          "column-width length(cm) / values": {},
-          "column-width length(cm) / events": {},
-          "column-width length(in) / values": {},
-          "column-width length(in) / events": {},
-          "transform transform(rotate) / values": {},
-          "transform transform(rotate) / events": {},
-          "transform-origin horizontal(keyword) / values": {},
-          "transform-origin horizontal(keyword) / events": {},
-          "zoom number(integer) / values": {},
-          "zoom number(integer) / events": {},
-          "zoom number(decimal) / values": {},
-          "zoom number(decimal) / events": {},
-          "outline-radius-topleft length(pt) / values": {},
-          "outline-radius-topleft length(pt) / events": {},
-          "outline-radius-topleft length(pc) / values": {},
-          "outline-radius-topleft length(pc) / events": {},
-          "outline-radius-topleft length(px) / values": {},
-          "outline-radius-topleft length(px) / events": {},
-          "outline-radius-topleft length(em) / values": {},
-          "outline-radius-topleft length(em) / events": {},
-          "outline-radius-topleft length(ex) / values": {},
-          "outline-radius-topleft length(ex) / events": {},
-          "outline-radius-topleft length(mm) / values": {},
-          "outline-radius-topleft length(mm) / events": {},
-          "outline-radius-topleft length(cm) / values": {},
-          "outline-radius-topleft length(cm) / events": {},
-          "outline-radius-topleft length(in) / values": {},
-          "outline-radius-topleft length(in) / events": {},
-          "outline-radius-topleft percentage(%) / values": {},
-          "outline-radius-topleft percentage(%) / events": {},
-          "outline-radius-topright length(pt) / values": {},
-          "outline-radius-topright length(pt) / events": {},
-          "outline-radius-topright length(pc) / values": {},
-          "outline-radius-topright length(pc) / events": {},
-          "outline-radius-topright length(px) / values": {},
-          "outline-radius-topright length(px) / events": {},
-          "outline-radius-topright length(em) / values": {},
-          "outline-radius-topright length(em) / events": {},
-          "outline-radius-topright length(ex) / values": {},
-          "outline-radius-topright length(ex) / events": {},
-          "outline-radius-topright length(mm) / values": {},
-          "outline-radius-topright length(mm) / events": {},
-          "outline-radius-topright length(cm) / values": {},
-          "outline-radius-topright length(cm) / events": {},
-          "outline-radius-topright length(in) / values": {},
-          "outline-radius-topright length(in) / events": {},
-          "outline-radius-topright percentage(%) / values": {},
-          "outline-radius-topright percentage(%) / events": {},
-          "outline-radius-bottomright length(pt) / values": {},
-          "outline-radius-bottomright length(pt) / events": {},
-          "outline-radius-bottomright length(pc) / values": {},
-          "outline-radius-bottomright length(pc) / events": {},
-          "outline-radius-bottomright length(px) / values": {},
-          "outline-radius-bottomright length(px) / events": {},
-          "outline-radius-bottomright length(em) / values": {},
-          "outline-radius-bottomright length(em) / events": {},
-          "outline-radius-bottomright length(ex) / values": {},
-          "outline-radius-bottomright length(ex) / events": {},
-          "outline-radius-bottomright length(mm) / values": {},
-          "outline-radius-bottomright length(mm) / events": {},
-          "outline-radius-bottomright length(cm) / values": {},
-          "outline-radius-bottomright length(cm) / events": {},
-          "outline-radius-bottomright length(in) / values": {},
-          "outline-radius-bottomright length(in) / events": {},
-          "outline-radius-bottomright percentage(%) / values": {},
-          "outline-radius-bottomright percentage(%) / events": {},
-          "outline-radius-bottomleft length(pt) / values": {},
-          "outline-radius-bottomleft length(pt) / events": {},
-          "outline-radius-bottomleft length(pc) / values": {},
-          "outline-radius-bottomleft length(pc) / events": {},
-          "outline-radius-bottomleft length(px) / values": {},
-          "outline-radius-bottomleft length(px) / events": {},
-          "outline-radius-bottomleft length(em) / values": {},
-          "outline-radius-bottomleft length(em) / events": {},
-          "outline-radius-bottomleft length(ex) / values": {},
-          "outline-radius-bottomleft length(ex) / events": {},
-          "outline-radius-bottomleft length(mm) / values": {},
-          "outline-radius-bottomleft length(mm) / events": {},
-          "outline-radius-bottomleft length(cm) / values": {},
-          "outline-radius-bottomleft length(cm) / events": {},
-          "outline-radius-bottomleft length(in) / values": {},
-          "outline-radius-bottomleft length(in) / events": {},
-          "outline-radius-bottomleft percentage(%) / values": {},
-          "outline-radius-bottomleft percentage(%) / events": {},
-          "display display(static to absolute) / values": {},
-          "display display(static to absolute) / events": {},
-          "display display(block to inline-block) / values": {},
-          "display display(block to inline-block) / events": {},
-          "position position(static to absolute) / values": {},
-          "position position(static to absolute) / events": {},
-          "position position(relative to absolute) / values": {},
-          "position position(relative to absolute) / events": {},
-          "position position(absolute to fixed) / values": {},
-          "position position(absolute to fixed) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-auto-001.html b/css/css-transitions/properties-value-auto-001.html
index fd4befc..087f6bb 100644
--- a/css/css-transitions/properties-value-auto-001.html
+++ b/css/css-transitions/properties-value-auto-001.html
@@ -26,63 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "margin-top auto(to) / values": {},
-          "margin-top auto(to) / events": {},
-          "margin-top auto(from) / values": {},
-          "margin-top auto(from) / events": {},
-          "margin-right auto(to) / values": {},
-          "margin-right auto(to) / events": {},
-          "margin-right auto(from) / values": {},
-          "margin-right auto(from) / events": {},
-          "margin-bottom auto(to) / values": {},
-          "margin-bottom auto(to) / events": {},
-          "margin-bottom auto(from) / values": {},
-          "margin-bottom auto(from) / events": {},
-          "margin-left auto(to) / values": {},
-          "margin-left auto(to) / events": {},
-          "margin-left auto(from) / values": {},
-          "margin-left auto(from) / events": {},
-          "height auto(to) / values": {},
-          "height auto(to) / events": {},
-          "height auto(from) / values": {},
-          "height auto(from) / events": {},
-          "width auto(to) / values": {},
-          "width auto(to) / events": {},
-          "width auto(from) / values": {},
-          "width auto(from) / events": {},
-          "clip auto(to) / values": {},
-          "clip auto(to) / events": {},
-          "clip auto(from) / values": {},
-          "clip auto(from) / events": {},
-          "marker-offset auto(to) / values": {},
-          "marker-offset auto(to) / events": {},
-          "marker-offset auto(from) / values": {},
-          "marker-offset auto(from) / events": {},
-          "top auto(to) / values": {},
-          "top auto(to) / events": {},
-          "top auto(from) / values": {},
-          "top auto(from) / events": {},
-          "right auto(to) / values": {},
-          "right auto(to) / events": {},
-          "right auto(from) / values": {},
-          "right auto(from) / events": {},
-          "left auto(to) / values": {},
-          "left auto(to) / events": {},
-          "left auto(from) / values": {},
-          "left auto(from) / events": {},
-          "bottom auto(to) / values": {},
-          "bottom auto(to) / events": {},
-          "bottom auto(from) / values": {},
-          "bottom auto(from) / events": {},
-          "z-index auto(to) / values": {},
-          "z-index auto(to) / events": {},
-          "z-index auto(from) / values": {},
-          "z-index auto(from) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-implicit-001.html b/css/css-transitions/properties-value-implicit-001.html
index 26d3780..4d93083 100644
--- a/css/css-transitions/properties-value-implicit-001.html
+++ b/css/css-transitions/properties-value-implicit-001.html
@@ -26,71 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "background-position length-em(em) / values": {},
-          "background-position length-em(em) / events": {},
-          "border-top-width length-em(em) / values": {},
-          "border-top-width length-em(em) / events": {},
-          "border-right-width length-em(em) / values": {},
-          "border-right-width length-em(em) / events": {},
-          "border-bottom-width length-em(em) / values": {},
-          "border-bottom-width length-em(em) / events": {},
-          "border-left-width length-em(em) / values": {},
-          "border-left-width length-em(em) / events": {},
-          "padding-bottom length-em(em) / values": {},
-          "padding-bottom length-em(em) / events": {},
-          "padding-left length-em(em) / values": {},
-          "padding-left length-em(em) / events": {},
-          "padding-right length-em(em) / values": {},
-          "padding-right length-em(em) / events": {},
-          "padding-top length-em(em) / values": {},
-          "padding-top length-em(em) / events": {},
-          "margin-bottom length-em(em) / values": {},
-          "margin-bottom length-em(em) / events": {},
-          "margin-left length-em(em) / values": {},
-          "margin-left length-em(em) / events": {},
-          "margin-right length-em(em) / values": {},
-          "margin-right length-em(em) / events": {},
-          "margin-top length-em(em) / values": {},
-          "margin-top length-em(em) / events": {},
-          "height length-em(em) / values": {},
-          "height length-em(em) / events": {},
-          "width length-em(em) / values": {},
-          "width length-em(em) / events": {},
-          "min-height length-em(em) / values": {},
-          "min-height length-em(em) / events": {},
-          "min-width length-em(em) / values": {},
-          "min-width length-em(em) / events": {},
-          "max-height length-em(em) / values": {},
-          "max-height length-em(em) / events": {},
-          "max-width length-em(em) / values": {},
-          "max-width length-em(em) / events": {},
-          "top length-em(em) / values": {},
-          "top length-em(em) / events": {},
-          "right length-em(em) / values": {},
-          "right length-em(em) / events": {},
-          "bottom length-em(em) / values": {},
-          "bottom length-em(em) / events": {},
-          "left length-em(em) / values": {},
-          "left length-em(em) / events": {},
-          "line-height length-em(em) / values": {},
-          "line-height length-em(em) / events": {},
-          "letter-spacing length-em(em) / values": {},
-          "letter-spacing length-em(em) / events": {},
-          "word-spacing length-em(em) / values": {},
-          "word-spacing length-em(em) / events": {},
-          "text-indent length-em(em) / values": {},
-          "text-indent length-em(em) / events": {},
-          "outline-offset length-em(em) / values": {},
-          "outline-offset length-em(em) / events": {},
-          "outline-width length-em(em) / values": {},
-          "outline-width length-em(em) / events": {},
-          "vertical-align length-em(em) / values": {},
-          "vertical-align length-em(em) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-inherit-001.html b/css/css-transitions/properties-value-inherit-001.html
index 97d497f..2de40b4 100644
--- a/css/css-transitions/properties-value-inherit-001.html
+++ b/css/css-transitions/properties-value-inherit-001.html
@@ -26,573 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "background-color color(rgba) / values": {},
-          "background-color color(rgba) / events": {},
-          "background-position length(pt) / values": {},
-          "background-position length(pt) / events": {},
-          "background-position length(pc) / values": {},
-          "background-position length(pc) / events": {},
-          "background-position length(px) / values": {},
-          "background-position length(px) / events": {},
-          "background-position length(em) / values": {},
-          "background-position length(em) / events": {},
-          "background-position length(ex) / values": {},
-          "background-position length(ex) / events": {},
-          "background-position length(mm) / values": {},
-          "background-position length(mm) / events": {},
-          "background-position length(cm) / values": {},
-          "background-position length(cm) / events": {},
-          "background-position length(in) / values": {},
-          "background-position length(in) / events": {},
-          "background-position percentage(%) / values": {},
-          "background-position percentage(%) / events": {},
-          "border-top-width length(pt) / values": {},
-          "border-top-width length(pt) / events": {},
-          "border-top-width length(pc) / values": {},
-          "border-top-width length(pc) / events": {},
-          "border-top-width length(px) / values": {},
-          "border-top-width length(px) / events": {},
-          "border-top-width length(em) / values": {},
-          "border-top-width length(em) / events": {},
-          "border-top-width length(ex) / values": {},
-          "border-top-width length(ex) / events": {},
-          "border-top-width length(mm) / values": {},
-          "border-top-width length(mm) / events": {},
-          "border-top-width length(cm) / values": {},
-          "border-top-width length(cm) / events": {},
-          "border-top-width length(in) / values": {},
-          "border-top-width length(in) / events": {},
-          "border-right-width length(pt) / values": {},
-          "border-right-width length(pt) / events": {},
-          "border-right-width length(pc) / values": {},
-          "border-right-width length(pc) / events": {},
-          "border-right-width length(px) / values": {},
-          "border-right-width length(px) / events": {},
-          "border-right-width length(em) / values": {},
-          "border-right-width length(em) / events": {},
-          "border-right-width length(ex) / values": {},
-          "border-right-width length(ex) / events": {},
-          "border-right-width length(mm) / values": {},
-          "border-right-width length(mm) / events": {},
-          "border-right-width length(cm) / values": {},
-          "border-right-width length(cm) / events": {},
-          "border-right-width length(in) / values": {},
-          "border-right-width length(in) / events": {},
-          "border-bottom-width length(pt) / values": {},
-          "border-bottom-width length(pt) / events": {},
-          "border-bottom-width length(pc) / values": {},
-          "border-bottom-width length(pc) / events": {},
-          "border-bottom-width length(px) / values": {},
-          "border-bottom-width length(px) / events": {},
-          "border-bottom-width length(em) / values": {},
-          "border-bottom-width length(em) / events": {},
-          "border-bottom-width length(ex) / values": {},
-          "border-bottom-width length(ex) / events": {},
-          "border-bottom-width length(mm) / values": {},
-          "border-bottom-width length(mm) / events": {},
-          "border-bottom-width length(cm) / values": {},
-          "border-bottom-width length(cm) / events": {},
-          "border-bottom-width length(in) / values": {},
-          "border-bottom-width length(in) / events": {},
-          "border-left-width length(pt) / values": {},
-          "border-left-width length(pt) / events": {},
-          "border-left-width length(pc) / values": {},
-          "border-left-width length(pc) / events": {},
-          "border-left-width length(px) / values": {},
-          "border-left-width length(px) / events": {},
-          "border-left-width length(em) / values": {},
-          "border-left-width length(em) / events": {},
-          "border-left-width length(ex) / values": {},
-          "border-left-width length(ex) / events": {},
-          "border-left-width length(mm) / values": {},
-          "border-left-width length(mm) / events": {},
-          "border-left-width length(cm) / values": {},
-          "border-left-width length(cm) / events": {},
-          "border-left-width length(in) / values": {},
-          "border-left-width length(in) / events": {},
-          "border-top-color color(rgba) / values": {},
-          "border-top-color color(rgba) / events": {},
-          "border-right-color color(rgba) / values": {},
-          "border-right-color color(rgba) / events": {},
-          "border-bottom-color color(rgba) / values": {},
-          "border-bottom-color color(rgba) / events": {},
-          "border-left-color color(rgba) / values": {},
-          "border-left-color color(rgba) / events": {},
-          "padding-bottom length(pt) / values": {},
-          "padding-bottom length(pt) / events": {},
-          "padding-bottom length(pc) / values": {},
-          "padding-bottom length(pc) / events": {},
-          "padding-bottom length(px) / values": {},
-          "padding-bottom length(px) / events": {},
-          "padding-bottom length(em) / values": {},
-          "padding-bottom length(em) / events": {},
-          "padding-bottom length(ex) / values": {},
-          "padding-bottom length(ex) / events": {},
-          "padding-bottom length(mm) / values": {},
-          "padding-bottom length(mm) / events": {},
-          "padding-bottom length(cm) / values": {},
-          "padding-bottom length(cm) / events": {},
-          "padding-bottom length(in) / values": {},
-          "padding-bottom length(in) / events": {},
-          "padding-left length(pt) / values": {},
-          "padding-left length(pt) / events": {},
-          "padding-left length(pc) / values": {},
-          "padding-left length(pc) / events": {},
-          "padding-left length(px) / values": {},
-          "padding-left length(px) / events": {},
-          "padding-left length(em) / values": {},
-          "padding-left length(em) / events": {},
-          "padding-left length(ex) / values": {},
-          "padding-left length(ex) / events": {},
-          "padding-left length(mm) / values": {},
-          "padding-left length(mm) / events": {},
-          "padding-left length(cm) / values": {},
-          "padding-left length(cm) / events": {},
-          "padding-left length(in) / values": {},
-          "padding-left length(in) / events": {},
-          "padding-right length(pt) / values": {},
-          "padding-right length(pt) / events": {},
-          "padding-right length(pc) / values": {},
-          "padding-right length(pc) / events": {},
-          "padding-right length(px) / values": {},
-          "padding-right length(px) / events": {},
-          "padding-right length(em) / values": {},
-          "padding-right length(em) / events": {},
-          "padding-right length(ex) / values": {},
-          "padding-right length(ex) / events": {},
-          "padding-right length(mm) / values": {},
-          "padding-right length(mm) / events": {},
-          "padding-right length(cm) / values": {},
-          "padding-right length(cm) / events": {},
-          "padding-right length(in) / values": {},
-          "padding-right length(in) / events": {},
-          "padding-top length(pt) / values": {},
-          "padding-top length(pt) / events": {},
-          "padding-top length(pc) / values": {},
-          "padding-top length(pc) / events": {},
-          "padding-top length(px) / values": {},
-          "padding-top length(px) / events": {},
-          "padding-top length(em) / values": {},
-          "padding-top length(em) / events": {},
-          "padding-top length(ex) / values": {},
-          "padding-top length(ex) / events": {},
-          "padding-top length(mm) / values": {},
-          "padding-top length(mm) / events": {},
-          "padding-top length(cm) / values": {},
-          "padding-top length(cm) / events": {},
-          "padding-top length(in) / values": {},
-          "padding-top length(in) / events": {},
-          "margin-bottom length(pt) / values": {},
-          "margin-bottom length(pt) / events": {},
-          "margin-bottom length(pc) / values": {},
-          "margin-bottom length(pc) / events": {},
-          "margin-bottom length(px) / values": {},
-          "margin-bottom length(px) / events": {},
-          "margin-bottom length(em) / values": {},
-          "margin-bottom length(em) / events": {},
-          "margin-bottom length(ex) / values": {},
-          "margin-bottom length(ex) / events": {},
-          "margin-bottom length(mm) / values": {},
-          "margin-bottom length(mm) / events": {},
-          "margin-bottom length(cm) / values": {},
-          "margin-bottom length(cm) / events": {},
-          "margin-bottom length(in) / values": {},
-          "margin-bottom length(in) / events": {},
-          "margin-left length(pt) / values": {},
-          "margin-left length(pt) / events": {},
-          "margin-left length(pc) / values": {},
-          "margin-left length(pc) / events": {},
-          "margin-left length(px) / values": {},
-          "margin-left length(px) / events": {},
-          "margin-left length(em) / values": {},
-          "margin-left length(em) / events": {},
-          "margin-left length(ex) / values": {},
-          "margin-left length(ex) / events": {},
-          "margin-left length(mm) / values": {},
-          "margin-left length(mm) / events": {},
-          "margin-left length(cm) / values": {},
-          "margin-left length(cm) / events": {},
-          "margin-left length(in) / values": {},
-          "margin-left length(in) / events": {},
-          "margin-right length(pt) / values": {},
-          "margin-right length(pt) / events": {},
-          "margin-right length(pc) / values": {},
-          "margin-right length(pc) / events": {},
-          "margin-right length(px) / values": {},
-          "margin-right length(px) / events": {},
-          "margin-right length(em) / values": {},
-          "margin-right length(em) / events": {},
-          "margin-right length(ex) / values": {},
-          "margin-right length(ex) / events": {},
-          "margin-right length(mm) / values": {},
-          "margin-right length(mm) / events": {},
-          "margin-right length(cm) / values": {},
-          "margin-right length(cm) / events": {},
-          "margin-right length(in) / values": {},
-          "margin-right length(in) / events": {},
-          "margin-top length(pt) / values": {},
-          "margin-top length(pt) / events": {},
-          "margin-top length(pc) / values": {},
-          "margin-top length(pc) / events": {},
-          "margin-top length(px) / values": {},
-          "margin-top length(px) / events": {},
-          "margin-top length(em) / values": {},
-          "margin-top length(em) / events": {},
-          "margin-top length(ex) / values": {},
-          "margin-top length(ex) / events": {},
-          "margin-top length(mm) / values": {},
-          "margin-top length(mm) / events": {},
-          "margin-top length(cm) / values": {},
-          "margin-top length(cm) / events": {},
-          "margin-top length(in) / values": {},
-          "margin-top length(in) / events": {},
-          "height length(pt) / values": {},
-          "height length(pt) / events": {},
-          "height length(pc) / values": {},
-          "height length(pc) / events": {},
-          "height length(px) / values": {},
-          "height length(px) / events": {},
-          "height length(em) / values": {},
-          "height length(em) / events": {},
-          "height length(ex) / values": {},
-          "height length(ex) / events": {},
-          "height length(mm) / values": {},
-          "height length(mm) / events": {},
-          "height length(cm) / values": {},
-          "height length(cm) / events": {},
-          "height length(in) / values": {},
-          "height length(in) / events": {},
-          "height percentage(%) / values": {},
-          "height percentage(%) / events": {},
-          "width length(pt) / values": {},
-          "width length(pt) / events": {},
-          "width length(pc) / values": {},
-          "width length(pc) / events": {},
-          "width length(px) / values": {},
-          "width length(px) / events": {},
-          "width length(em) / values": {},
-          "width length(em) / events": {},
-          "width length(ex) / values": {},
-          "width length(ex) / events": {},
-          "width length(mm) / values": {},
-          "width length(mm) / events": {},
-          "width length(cm) / values": {},
-          "width length(cm) / events": {},
-          "width length(in) / values": {},
-          "width length(in) / events": {},
-          "width percentage(%) / values": {},
-          "width percentage(%) / events": {},
-          "min-height length(pt) / values": {},
-          "min-height length(pt) / events": {},
-          "min-height length(pc) / values": {},
-          "min-height length(pc) / events": {},
-          "min-height length(px) / values": {},
-          "min-height length(px) / events": {},
-          "min-height length(em) / values": {},
-          "min-height length(em) / events": {},
-          "min-height length(ex) / values": {},
-          "min-height length(ex) / events": {},
-          "min-height length(mm) / values": {},
-          "min-height length(mm) / events": {},
-          "min-height length(cm) / values": {},
-          "min-height length(cm) / events": {},
-          "min-height length(in) / values": {},
-          "min-height length(in) / events": {},
-          "min-height percentage(%) / values": {},
-          "min-height percentage(%) / events": {},
-          "min-width length(pt) / values": {},
-          "min-width length(pt) / events": {},
-          "min-width length(pc) / values": {},
-          "min-width length(pc) / events": {},
-          "min-width length(px) / values": {},
-          "min-width length(px) / events": {},
-          "min-width length(em) / values": {},
-          "min-width length(em) / events": {},
-          "min-width length(ex) / values": {},
-          "min-width length(ex) / events": {},
-          "min-width length(mm) / values": {},
-          "min-width length(mm) / events": {},
-          "min-width length(cm) / values": {},
-          "min-width length(cm) / events": {},
-          "min-width length(in) / values": {},
-          "min-width length(in) / events": {},
-          "min-width percentage(%) / values": {},
-          "min-width percentage(%) / events": {},
-          "max-height length(pt) / values": {},
-          "max-height length(pt) / events": {},
-          "max-height length(pc) / values": {},
-          "max-height length(pc) / events": {},
-          "max-height length(px) / values": {},
-          "max-height length(px) / events": {},
-          "max-height length(em) / values": {},
-          "max-height length(em) / events": {},
-          "max-height length(ex) / values": {},
-          "max-height length(ex) / events": {},
-          "max-height length(mm) / values": {},
-          "max-height length(mm) / events": {},
-          "max-height length(cm) / values": {},
-          "max-height length(cm) / events": {},
-          "max-height length(in) / values": {},
-          "max-height length(in) / events": {},
-          "max-height percentage(%) / values": {},
-          "max-height percentage(%) / events": {},
-          "max-width length(pt) / values": {},
-          "max-width length(pt) / events": {},
-          "max-width length(pc) / values": {},
-          "max-width length(pc) / events": {},
-          "max-width length(px) / values": {},
-          "max-width length(px) / events": {},
-          "max-width length(em) / values": {},
-          "max-width length(em) / events": {},
-          "max-width length(ex) / values": {},
-          "max-width length(ex) / events": {},
-          "max-width length(mm) / values": {},
-          "max-width length(mm) / events": {},
-          "max-width length(cm) / values": {},
-          "max-width length(cm) / events": {},
-          "max-width length(in) / values": {},
-          "max-width length(in) / events": {},
-          "max-width percentage(%) / values": {},
-          "max-width percentage(%) / events": {},
-          "top length(pt) / values": {},
-          "top length(pt) / events": {},
-          "top length(pc) / values": {},
-          "top length(pc) / events": {},
-          "top length(px) / values": {},
-          "top length(px) / events": {},
-          "top length(em) / values": {},
-          "top length(em) / events": {},
-          "top length(ex) / values": {},
-          "top length(ex) / events": {},
-          "top length(mm) / values": {},
-          "top length(mm) / events": {},
-          "top length(cm) / values": {},
-          "top length(cm) / events": {},
-          "top length(in) / values": {},
-          "top length(in) / events": {},
-          "top percentage(%) / values": {},
-          "top percentage(%) / events": {},
-          "right length(pt) / values": {},
-          "right length(pt) / events": {},
-          "right length(pc) / values": {},
-          "right length(pc) / events": {},
-          "right length(px) / values": {},
-          "right length(px) / events": {},
-          "right length(em) / values": {},
-          "right length(em) / events": {},
-          "right length(ex) / values": {},
-          "right length(ex) / events": {},
-          "right length(mm) / values": {},
-          "right length(mm) / events": {},
-          "right length(cm) / values": {},
-          "right length(cm) / events": {},
-          "right length(in) / values": {},
-          "right length(in) / events": {},
-          "right percentage(%) / values": {},
-          "right percentage(%) / events": {},
-          "bottom length(pt) / values": {},
-          "bottom length(pt) / events": {},
-          "bottom length(pc) / values": {},
-          "bottom length(pc) / events": {},
-          "bottom length(px) / values": {},
-          "bottom length(px) / events": {},
-          "bottom length(em) / values": {},
-          "bottom length(em) / events": {},
-          "bottom length(ex) / values": {},
-          "bottom length(ex) / events": {},
-          "bottom length(mm) / values": {},
-          "bottom length(mm) / events": {},
-          "bottom length(cm) / values": {},
-          "bottom length(cm) / events": {},
-          "bottom length(in) / values": {},
-          "bottom length(in) / events": {},
-          "bottom percentage(%) / values": {},
-          "bottom percentage(%) / events": {},
-          "left length(pt) / values": {},
-          "left length(pt) / events": {},
-          "left length(pc) / values": {},
-          "left length(pc) / events": {},
-          "left length(px) / values": {},
-          "left length(px) / events": {},
-          "left length(em) / values": {},
-          "left length(em) / events": {},
-          "left length(ex) / values": {},
-          "left length(ex) / events": {},
-          "left length(mm) / values": {},
-          "left length(mm) / events": {},
-          "left length(cm) / values": {},
-          "left length(cm) / events": {},
-          "left length(in) / values": {},
-          "left length(in) / events": {},
-          "left percentage(%) / values": {},
-          "left percentage(%) / events": {},
-          "color color(rgba) / values": {},
-          "color color(rgba) / events": {},
-          "font-size length(pt) / values": {},
-          "font-size length(pt) / events": {},
-          "font-size length(pc) / values": {},
-          "font-size length(pc) / events": {},
-          "font-size length(px) / values": {},
-          "font-size length(px) / events": {},
-          "font-size length(em) / values": {},
-          "font-size length(em) / events": {},
-          "font-size length(ex) / values": {},
-          "font-size length(ex) / events": {},
-          "font-size length(mm) / values": {},
-          "font-size length(mm) / events": {},
-          "font-size length(cm) / values": {},
-          "font-size length(cm) / events": {},
-          "font-size length(in) / values": {},
-          "font-size length(in) / events": {},
-          "font-size percentage(%) / values": {},
-          "font-size percentage(%) / events": {},
-          "font-weight font-weight(keyword) / values": {},
-          "font-weight font-weight(keyword) / events": {},
-          "font-weight font-weight(numeric) / values": {},
-          "font-weight font-weight(numeric) / events": {},
-          "line-height number(integer) / values": {},
-          "line-height number(integer) / events": {},
-          "line-height number(decimal) / values": {},
-          "line-height number(decimal) / events": {},
-          "line-height length(pt) / values": {},
-          "line-height length(pt) / events": {},
-          "line-height length(pc) / values": {},
-          "line-height length(pc) / events": {},
-          "line-height length(px) / values": {},
-          "line-height length(px) / events": {},
-          "line-height length(em) / values": {},
-          "line-height length(em) / events": {},
-          "line-height length(ex) / values": {},
-          "line-height length(ex) / events": {},
-          "line-height length(mm) / values": {},
-          "line-height length(mm) / events": {},
-          "line-height length(cm) / values": {},
-          "line-height length(cm) / events": {},
-          "line-height length(in) / values": {},
-          "line-height length(in) / events": {},
-          "line-height percentage(%) / values": {},
-          "line-height percentage(%) / events": {},
-          "letter-spacing length(pt) / values": {},
-          "letter-spacing length(pt) / events": {},
-          "letter-spacing length(pc) / values": {},
-          "letter-spacing length(pc) / events": {},
-          "letter-spacing length(px) / values": {},
-          "letter-spacing length(px) / events": {},
-          "letter-spacing length(em) / values": {},
-          "letter-spacing length(em) / events": {},
-          "letter-spacing length(ex) / values": {},
-          "letter-spacing length(ex) / events": {},
-          "letter-spacing length(mm) / values": {},
-          "letter-spacing length(mm) / events": {},
-          "letter-spacing length(cm) / values": {},
-          "letter-spacing length(cm) / events": {},
-          "letter-spacing length(in) / values": {},
-          "letter-spacing length(in) / events": {},
-          "word-spacing length(pt) / values": {},
-          "word-spacing length(pt) / events": {},
-          "word-spacing length(pc) / values": {},
-          "word-spacing length(pc) / events": {},
-          "word-spacing length(px) / values": {},
-          "word-spacing length(px) / events": {},
-          "word-spacing length(em) / values": {},
-          "word-spacing length(em) / events": {},
-          "word-spacing length(ex) / values": {},
-          "word-spacing length(ex) / events": {},
-          "word-spacing length(mm) / values": {},
-          "word-spacing length(mm) / events": {},
-          "word-spacing length(cm) / values": {},
-          "word-spacing length(cm) / events": {},
-          "word-spacing length(in) / values": {},
-          "word-spacing length(in) / events": {},
-          "word-spacing percentage(%) / values": {},
-          "word-spacing percentage(%) / events": {},
-          "text-indent length(pt) / values": {},
-          "text-indent length(pt) / events": {},
-          "text-indent length(pc) / values": {},
-          "text-indent length(pc) / events": {},
-          "text-indent length(px) / values": {},
-          "text-indent length(px) / events": {},
-          "text-indent length(em) / values": {},
-          "text-indent length(em) / events": {},
-          "text-indent length(ex) / values": {},
-          "text-indent length(ex) / events": {},
-          "text-indent length(mm) / values": {},
-          "text-indent length(mm) / events": {},
-          "text-indent length(cm) / values": {},
-          "text-indent length(cm) / events": {},
-          "text-indent length(in) / values": {},
-          "text-indent length(in) / events": {},
-          "text-indent percentage(%) / values": {},
-          "text-indent percentage(%) / events": {},
-          "text-shadow shadow(shadow) / values": {},
-          "text-shadow shadow(shadow) / events": {},
-          "outline-color color(rgba) / values": {},
-          "outline-color color(rgba) / events": {},
-          "outline-offset length(pt) / values": {},
-          "outline-offset length(pt) / events": {},
-          "outline-offset length(pc) / values": {},
-          "outline-offset length(pc) / events": {},
-          "outline-offset length(px) / values": {},
-          "outline-offset length(px) / events": {},
-          "outline-offset length(em) / values": {},
-          "outline-offset length(em) / events": {},
-          "outline-offset length(ex) / values": {},
-          "outline-offset length(ex) / events": {},
-          "outline-offset length(mm) / values": {},
-          "outline-offset length(mm) / events": {},
-          "outline-offset length(cm) / values": {},
-          "outline-offset length(cm) / events": {},
-          "outline-offset length(in) / values": {},
-          "outline-offset length(in) / events": {},
-          "outline-width length(pt) / values": {},
-          "outline-width length(pt) / events": {},
-          "outline-width length(pc) / values": {},
-          "outline-width length(pc) / events": {},
-          "outline-width length(px) / values": {},
-          "outline-width length(px) / events": {},
-          "outline-width length(em) / values": {},
-          "outline-width length(em) / events": {},
-          "outline-width length(ex) / values": {},
-          "outline-width length(ex) / events": {},
-          "outline-width length(mm) / values": {},
-          "outline-width length(mm) / events": {},
-          "outline-width length(cm) / values": {},
-          "outline-width length(cm) / events": {},
-          "outline-width length(in) / values": {},
-          "outline-width length(in) / events": {},
-          "clip rectangle(rectangle) / values": {},
-          "clip rectangle(rectangle) / events": {},
-          "crop rectangle(rectangle) / values": {},
-          "crop rectangle(rectangle) / events": {},
-          "vertical-align length(pt) / values": {},
-          "vertical-align length(pt) / events": {},
-          "vertical-align length(pc) / values": {},
-          "vertical-align length(pc) / events": {},
-          "vertical-align length(px) / values": {},
-          "vertical-align length(px) / events": {},
-          "vertical-align length(em) / values": {},
-          "vertical-align length(em) / events": {},
-          "vertical-align length(ex) / values": {},
-          "vertical-align length(ex) / events": {},
-          "vertical-align length(mm) / values": {},
-          "vertical-align length(mm) / events": {},
-          "vertical-align length(cm) / values": {},
-          "vertical-align length(cm) / events": {},
-          "vertical-align length(in) / values": {},
-          "vertical-align length(in) / events": {},
-          "vertical-align percentage(%) / values": {},
-          "vertical-align percentage(%) / events": {},
-          "opacity number[0,1](zero-to-one) / values": {},
-          "opacity number[0,1](zero-to-one) / events": {},
-          "visibility visibility(keyword) / values": {},
-          "visibility visibility(keyword) / events": {},
-          "z-index integer(integer) / values": {},
-          "z-index integer(integer) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-inherit-002.html b/css/css-transitions/properties-value-inherit-002.html
index 14a36d5..f2741bd 100644
--- a/css/css-transitions/properties-value-inherit-002.html
+++ b/css/css-transitions/properties-value-inherit-002.html
@@ -26,573 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "background-color color(rgba) / values": {},
-          "background-color color(rgba) / events": {},
-          "background-position length(pt) / values": {},
-          "background-position length(pt) / events": {},
-          "background-position length(pc) / values": {},
-          "background-position length(pc) / events": {},
-          "background-position length(px) / values": {},
-          "background-position length(px) / events": {},
-          "background-position length(em) / values": {},
-          "background-position length(em) / events": {},
-          "background-position length(ex) / values": {},
-          "background-position length(ex) / events": {},
-          "background-position length(mm) / values": {},
-          "background-position length(mm) / events": {},
-          "background-position length(cm) / values": {},
-          "background-position length(cm) / events": {},
-          "background-position length(in) / values": {},
-          "background-position length(in) / events": {},
-          "background-position percentage(%) / values": {},
-          "background-position percentage(%) / events": {},
-          "border-top-width length(pt) / values": {},
-          "border-top-width length(pt) / events": {},
-          "border-top-width length(pc) / values": {},
-          "border-top-width length(pc) / events": {},
-          "border-top-width length(px) / values": {},
-          "border-top-width length(px) / events": {},
-          "border-top-width length(em) / values": {},
-          "border-top-width length(em) / events": {},
-          "border-top-width length(ex) / values": {},
-          "border-top-width length(ex) / events": {},
-          "border-top-width length(mm) / values": {},
-          "border-top-width length(mm) / events": {},
-          "border-top-width length(cm) / values": {},
-          "border-top-width length(cm) / events": {},
-          "border-top-width length(in) / values": {},
-          "border-top-width length(in) / events": {},
-          "border-right-width length(pt) / values": {},
-          "border-right-width length(pt) / events": {},
-          "border-right-width length(pc) / values": {},
-          "border-right-width length(pc) / events": {},
-          "border-right-width length(px) / values": {},
-          "border-right-width length(px) / events": {},
-          "border-right-width length(em) / values": {},
-          "border-right-width length(em) / events": {},
-          "border-right-width length(ex) / values": {},
-          "border-right-width length(ex) / events": {},
-          "border-right-width length(mm) / values": {},
-          "border-right-width length(mm) / events": {},
-          "border-right-width length(cm) / values": {},
-          "border-right-width length(cm) / events": {},
-          "border-right-width length(in) / values": {},
-          "border-right-width length(in) / events": {},
-          "border-bottom-width length(pt) / values": {},
-          "border-bottom-width length(pt) / events": {},
-          "border-bottom-width length(pc) / values": {},
-          "border-bottom-width length(pc) / events": {},
-          "border-bottom-width length(px) / values": {},
-          "border-bottom-width length(px) / events": {},
-          "border-bottom-width length(em) / values": {},
-          "border-bottom-width length(em) / events": {},
-          "border-bottom-width length(ex) / values": {},
-          "border-bottom-width length(ex) / events": {},
-          "border-bottom-width length(mm) / values": {},
-          "border-bottom-width length(mm) / events": {},
-          "border-bottom-width length(cm) / values": {},
-          "border-bottom-width length(cm) / events": {},
-          "border-bottom-width length(in) / values": {},
-          "border-bottom-width length(in) / events": {},
-          "border-left-width length(pt) / values": {},
-          "border-left-width length(pt) / events": {},
-          "border-left-width length(pc) / values": {},
-          "border-left-width length(pc) / events": {},
-          "border-left-width length(px) / values": {},
-          "border-left-width length(px) / events": {},
-          "border-left-width length(em) / values": {},
-          "border-left-width length(em) / events": {},
-          "border-left-width length(ex) / values": {},
-          "border-left-width length(ex) / events": {},
-          "border-left-width length(mm) / values": {},
-          "border-left-width length(mm) / events": {},
-          "border-left-width length(cm) / values": {},
-          "border-left-width length(cm) / events": {},
-          "border-left-width length(in) / values": {},
-          "border-left-width length(in) / events": {},
-          "border-top-color color(rgba) / values": {},
-          "border-top-color color(rgba) / events": {},
-          "border-right-color color(rgba) / values": {},
-          "border-right-color color(rgba) / events": {},
-          "border-bottom-color color(rgba) / values": {},
-          "border-bottom-color color(rgba) / events": {},
-          "border-left-color color(rgba) / values": {},
-          "border-left-color color(rgba) / events": {},
-          "padding-bottom length(pt) / values": {},
-          "padding-bottom length(pt) / events": {},
-          "padding-bottom length(pc) / values": {},
-          "padding-bottom length(pc) / events": {},
-          "padding-bottom length(px) / values": {},
-          "padding-bottom length(px) / events": {},
-          "padding-bottom length(em) / values": {},
-          "padding-bottom length(em) / events": {},
-          "padding-bottom length(ex) / values": {},
-          "padding-bottom length(ex) / events": {},
-          "padding-bottom length(mm) / values": {},
-          "padding-bottom length(mm) / events": {},
-          "padding-bottom length(cm) / values": {},
-          "padding-bottom length(cm) / events": {},
-          "padding-bottom length(in) / values": {},
-          "padding-bottom length(in) / events": {},
-          "padding-left length(pt) / values": {},
-          "padding-left length(pt) / events": {},
-          "padding-left length(pc) / values": {},
-          "padding-left length(pc) / events": {},
-          "padding-left length(px) / values": {},
-          "padding-left length(px) / events": {},
-          "padding-left length(em) / values": {},
-          "padding-left length(em) / events": {},
-          "padding-left length(ex) / values": {},
-          "padding-left length(ex) / events": {},
-          "padding-left length(mm) / values": {},
-          "padding-left length(mm) / events": {},
-          "padding-left length(cm) / values": {},
-          "padding-left length(cm) / events": {},
-          "padding-left length(in) / values": {},
-          "padding-left length(in) / events": {},
-          "padding-right length(pt) / values": {},
-          "padding-right length(pt) / events": {},
-          "padding-right length(pc) / values": {},
-          "padding-right length(pc) / events": {},
-          "padding-right length(px) / values": {},
-          "padding-right length(px) / events": {},
-          "padding-right length(em) / values": {},
-          "padding-right length(em) / events": {},
-          "padding-right length(ex) / values": {},
-          "padding-right length(ex) / events": {},
-          "padding-right length(mm) / values": {},
-          "padding-right length(mm) / events": {},
-          "padding-right length(cm) / values": {},
-          "padding-right length(cm) / events": {},
-          "padding-right length(in) / values": {},
-          "padding-right length(in) / events": {},
-          "padding-top length(pt) / values": {},
-          "padding-top length(pt) / events": {},
-          "padding-top length(pc) / values": {},
-          "padding-top length(pc) / events": {},
-          "padding-top length(px) / values": {},
-          "padding-top length(px) / events": {},
-          "padding-top length(em) / values": {},
-          "padding-top length(em) / events": {},
-          "padding-top length(ex) / values": {},
-          "padding-top length(ex) / events": {},
-          "padding-top length(mm) / values": {},
-          "padding-top length(mm) / events": {},
-          "padding-top length(cm) / values": {},
-          "padding-top length(cm) / events": {},
-          "padding-top length(in) / values": {},
-          "padding-top length(in) / events": {},
-          "margin-bottom length(pt) / values": {},
-          "margin-bottom length(pt) / events": {},
-          "margin-bottom length(pc) / values": {},
-          "margin-bottom length(pc) / events": {},
-          "margin-bottom length(px) / values": {},
-          "margin-bottom length(px) / events": {},
-          "margin-bottom length(em) / values": {},
-          "margin-bottom length(em) / events": {},
-          "margin-bottom length(ex) / values": {},
-          "margin-bottom length(ex) / events": {},
-          "margin-bottom length(mm) / values": {},
-          "margin-bottom length(mm) / events": {},
-          "margin-bottom length(cm) / values": {},
-          "margin-bottom length(cm) / events": {},
-          "margin-bottom length(in) / values": {},
-          "margin-bottom length(in) / events": {},
-          "margin-left length(pt) / values": {},
-          "margin-left length(pt) / events": {},
-          "margin-left length(pc) / values": {},
-          "margin-left length(pc) / events": {},
-          "margin-left length(px) / values": {},
-          "margin-left length(px) / events": {},
-          "margin-left length(em) / values": {},
-          "margin-left length(em) / events": {},
-          "margin-left length(ex) / values": {},
-          "margin-left length(ex) / events": {},
-          "margin-left length(mm) / values": {},
-          "margin-left length(mm) / events": {},
-          "margin-left length(cm) / values": {},
-          "margin-left length(cm) / events": {},
-          "margin-left length(in) / values": {},
-          "margin-left length(in) / events": {},
-          "margin-right length(pt) / values": {},
-          "margin-right length(pt) / events": {},
-          "margin-right length(pc) / values": {},
-          "margin-right length(pc) / events": {},
-          "margin-right length(px) / values": {},
-          "margin-right length(px) / events": {},
-          "margin-right length(em) / values": {},
-          "margin-right length(em) / events": {},
-          "margin-right length(ex) / values": {},
-          "margin-right length(ex) / events": {},
-          "margin-right length(mm) / values": {},
-          "margin-right length(mm) / events": {},
-          "margin-right length(cm) / values": {},
-          "margin-right length(cm) / events": {},
-          "margin-right length(in) / values": {},
-          "margin-right length(in) / events": {},
-          "margin-top length(pt) / values": {},
-          "margin-top length(pt) / events": {},
-          "margin-top length(pc) / values": {},
-          "margin-top length(pc) / events": {},
-          "margin-top length(px) / values": {},
-          "margin-top length(px) / events": {},
-          "margin-top length(em) / values": {},
-          "margin-top length(em) / events": {},
-          "margin-top length(ex) / values": {},
-          "margin-top length(ex) / events": {},
-          "margin-top length(mm) / values": {},
-          "margin-top length(mm) / events": {},
-          "margin-top length(cm) / values": {},
-          "margin-top length(cm) / events": {},
-          "margin-top length(in) / values": {},
-          "margin-top length(in) / events": {},
-          "height length(pt) / values": {},
-          "height length(pt) / events": {},
-          "height length(pc) / values": {},
-          "height length(pc) / events": {},
-          "height length(px) / values": {},
-          "height length(px) / events": {},
-          "height length(em) / values": {},
-          "height length(em) / events": {},
-          "height length(ex) / values": {},
-          "height length(ex) / events": {},
-          "height length(mm) / values": {},
-          "height length(mm) / events": {},
-          "height length(cm) / values": {},
-          "height length(cm) / events": {},
-          "height length(in) / values": {},
-          "height length(in) / events": {},
-          "height percentage(%) / values": {},
-          "height percentage(%) / events": {},
-          "width length(pt) / values": {},
-          "width length(pt) / events": {},
-          "width length(pc) / values": {},
-          "width length(pc) / events": {},
-          "width length(px) / values": {},
-          "width length(px) / events": {},
-          "width length(em) / values": {},
-          "width length(em) / events": {},
-          "width length(ex) / values": {},
-          "width length(ex) / events": {},
-          "width length(mm) / values": {},
-          "width length(mm) / events": {},
-          "width length(cm) / values": {},
-          "width length(cm) / events": {},
-          "width length(in) / values": {},
-          "width length(in) / events": {},
-          "width percentage(%) / values": {},
-          "width percentage(%) / events": {},
-          "min-height length(pt) / values": {},
-          "min-height length(pt) / events": {},
-          "min-height length(pc) / values": {},
-          "min-height length(pc) / events": {},
-          "min-height length(px) / values": {},
-          "min-height length(px) / events": {},
-          "min-height length(em) / values": {},
-          "min-height length(em) / events": {},
-          "min-height length(ex) / values": {},
-          "min-height length(ex) / events": {},
-          "min-height length(mm) / values": {},
-          "min-height length(mm) / events": {},
-          "min-height length(cm) / values": {},
-          "min-height length(cm) / events": {},
-          "min-height length(in) / values": {},
-          "min-height length(in) / events": {},
-          "min-height percentage(%) / values": {},
-          "min-height percentage(%) / events": {},
-          "min-width length(pt) / values": {},
-          "min-width length(pt) / events": {},
-          "min-width length(pc) / values": {},
-          "min-width length(pc) / events": {},
-          "min-width length(px) / values": {},
-          "min-width length(px) / events": {},
-          "min-width length(em) / values": {},
-          "min-width length(em) / events": {},
-          "min-width length(ex) / values": {},
-          "min-width length(ex) / events": {},
-          "min-width length(mm) / values": {},
-          "min-width length(mm) / events": {},
-          "min-width length(cm) / values": {},
-          "min-width length(cm) / events": {},
-          "min-width length(in) / values": {},
-          "min-width length(in) / events": {},
-          "min-width percentage(%) / values": {},
-          "min-width percentage(%) / events": {},
-          "max-height length(pt) / values": {},
-          "max-height length(pt) / events": {},
-          "max-height length(pc) / values": {},
-          "max-height length(pc) / events": {},
-          "max-height length(px) / values": {},
-          "max-height length(px) / events": {},
-          "max-height length(em) / values": {},
-          "max-height length(em) / events": {},
-          "max-height length(ex) / values": {},
-          "max-height length(ex) / events": {},
-          "max-height length(mm) / values": {},
-          "max-height length(mm) / events": {},
-          "max-height length(cm) / values": {},
-          "max-height length(cm) / events": {},
-          "max-height length(in) / values": {},
-          "max-height length(in) / events": {},
-          "max-height percentage(%) / values": {},
-          "max-height percentage(%) / events": {},
-          "max-width length(pt) / values": {},
-          "max-width length(pt) / events": {},
-          "max-width length(pc) / values": {},
-          "max-width length(pc) / events": {},
-          "max-width length(px) / values": {},
-          "max-width length(px) / events": {},
-          "max-width length(em) / values": {},
-          "max-width length(em) / events": {},
-          "max-width length(ex) / values": {},
-          "max-width length(ex) / events": {},
-          "max-width length(mm) / values": {},
-          "max-width length(mm) / events": {},
-          "max-width length(cm) / values": {},
-          "max-width length(cm) / events": {},
-          "max-width length(in) / values": {},
-          "max-width length(in) / events": {},
-          "max-width percentage(%) / values": {},
-          "max-width percentage(%) / events": {},
-          "top length(pt) / values": {},
-          "top length(pt) / events": {},
-          "top length(pc) / values": {},
-          "top length(pc) / events": {},
-          "top length(px) / values": {},
-          "top length(px) / events": {},
-          "top length(em) / values": {},
-          "top length(em) / events": {},
-          "top length(ex) / values": {},
-          "top length(ex) / events": {},
-          "top length(mm) / values": {},
-          "top length(mm) / events": {},
-          "top length(cm) / values": {},
-          "top length(cm) / events": {},
-          "top length(in) / values": {},
-          "top length(in) / events": {},
-          "top percentage(%) / values": {},
-          "top percentage(%) / events": {},
-          "right length(pt) / values": {},
-          "right length(pt) / events": {},
-          "right length(pc) / values": {},
-          "right length(pc) / events": {},
-          "right length(px) / values": {},
-          "right length(px) / events": {},
-          "right length(em) / values": {},
-          "right length(em) / events": {},
-          "right length(ex) / values": {},
-          "right length(ex) / events": {},
-          "right length(mm) / values": {},
-          "right length(mm) / events": {},
-          "right length(cm) / values": {},
-          "right length(cm) / events": {},
-          "right length(in) / values": {},
-          "right length(in) / events": {},
-          "right percentage(%) / values": {},
-          "right percentage(%) / events": {},
-          "bottom length(pt) / values": {},
-          "bottom length(pt) / events": {},
-          "bottom length(pc) / values": {},
-          "bottom length(pc) / events": {},
-          "bottom length(px) / values": {},
-          "bottom length(px) / events": {},
-          "bottom length(em) / values": {},
-          "bottom length(em) / events": {},
-          "bottom length(ex) / values": {},
-          "bottom length(ex) / events": {},
-          "bottom length(mm) / values": {},
-          "bottom length(mm) / events": {},
-          "bottom length(cm) / values": {},
-          "bottom length(cm) / events": {},
-          "bottom length(in) / values": {},
-          "bottom length(in) / events": {},
-          "bottom percentage(%) / values": {},
-          "bottom percentage(%) / events": {},
-          "left length(pt) / values": {},
-          "left length(pt) / events": {},
-          "left length(pc) / values": {},
-          "left length(pc) / events": {},
-          "left length(px) / values": {},
-          "left length(px) / events": {},
-          "left length(em) / values": {},
-          "left length(em) / events": {},
-          "left length(ex) / values": {},
-          "left length(ex) / events": {},
-          "left length(mm) / values": {},
-          "left length(mm) / events": {},
-          "left length(cm) / values": {},
-          "left length(cm) / events": {},
-          "left length(in) / values": {},
-          "left length(in) / events": {},
-          "left percentage(%) / values": {},
-          "left percentage(%) / events": {},
-          "color color(rgba) / values": {},
-          "color color(rgba) / events": {},
-          "font-size length(pt) / values": {},
-          "font-size length(pt) / events": {},
-          "font-size length(pc) / values": {},
-          "font-size length(pc) / events": {},
-          "font-size length(px) / values": {},
-          "font-size length(px) / events": {},
-          "font-size length(em) / values": {},
-          "font-size length(em) / events": {},
-          "font-size length(ex) / values": {},
-          "font-size length(ex) / events": {},
-          "font-size length(mm) / values": {},
-          "font-size length(mm) / events": {},
-          "font-size length(cm) / values": {},
-          "font-size length(cm) / events": {},
-          "font-size length(in) / values": {},
-          "font-size length(in) / events": {},
-          "font-size percentage(%) / values": {},
-          "font-size percentage(%) / events": {},
-          "font-weight font-weight(keyword) / values": {},
-          "font-weight font-weight(keyword) / events": {},
-          "font-weight font-weight(numeric) / values": {},
-          "font-weight font-weight(numeric) / events": {},
-          "line-height number(integer) / values": {},
-          "line-height number(integer) / events": {},
-          "line-height number(decimal) / values": {},
-          "line-height number(decimal) / events": {},
-          "line-height length(pt) / values": {},
-          "line-height length(pt) / events": {},
-          "line-height length(pc) / values": {},
-          "line-height length(pc) / events": {},
-          "line-height length(px) / values": {},
-          "line-height length(px) / events": {},
-          "line-height length(em) / values": {},
-          "line-height length(em) / events": {},
-          "line-height length(ex) / values": {},
-          "line-height length(ex) / events": {},
-          "line-height length(mm) / values": {},
-          "line-height length(mm) / events": {},
-          "line-height length(cm) / values": {},
-          "line-height length(cm) / events": {},
-          "line-height length(in) / values": {},
-          "line-height length(in) / events": {},
-          "line-height percentage(%) / values": {},
-          "line-height percentage(%) / events": {},
-          "letter-spacing length(pt) / values": {},
-          "letter-spacing length(pt) / events": {},
-          "letter-spacing length(pc) / values": {},
-          "letter-spacing length(pc) / events": {},
-          "letter-spacing length(px) / values": {},
-          "letter-spacing length(px) / events": {},
-          "letter-spacing length(em) / values": {},
-          "letter-spacing length(em) / events": {},
-          "letter-spacing length(ex) / values": {},
-          "letter-spacing length(ex) / events": {},
-          "letter-spacing length(mm) / values": {},
-          "letter-spacing length(mm) / events": {},
-          "letter-spacing length(cm) / values": {},
-          "letter-spacing length(cm) / events": {},
-          "letter-spacing length(in) / values": {},
-          "letter-spacing length(in) / events": {},
-          "word-spacing length(pt) / values": {},
-          "word-spacing length(pt) / events": {},
-          "word-spacing length(pc) / values": {},
-          "word-spacing length(pc) / events": {},
-          "word-spacing length(px) / values": {},
-          "word-spacing length(px) / events": {},
-          "word-spacing length(em) / values": {},
-          "word-spacing length(em) / events": {},
-          "word-spacing length(ex) / values": {},
-          "word-spacing length(ex) / events": {},
-          "word-spacing length(mm) / values": {},
-          "word-spacing length(mm) / events": {},
-          "word-spacing length(cm) / values": {},
-          "word-spacing length(cm) / events": {},
-          "word-spacing length(in) / values": {},
-          "word-spacing length(in) / events": {},
-          "word-spacing percentage(%) / values": {},
-          "word-spacing percentage(%) / events": {},
-          "text-indent length(pt) / values": {},
-          "text-indent length(pt) / events": {},
-          "text-indent length(pc) / values": {},
-          "text-indent length(pc) / events": {},
-          "text-indent length(px) / values": {},
-          "text-indent length(px) / events": {},
-          "text-indent length(em) / values": {},
-          "text-indent length(em) / events": {},
-          "text-indent length(ex) / values": {},
-          "text-indent length(ex) / events": {},
-          "text-indent length(mm) / values": {},
-          "text-indent length(mm) / events": {},
-          "text-indent length(cm) / values": {},
-          "text-indent length(cm) / events": {},
-          "text-indent length(in) / values": {},
-          "text-indent length(in) / events": {},
-          "text-indent percentage(%) / values": {},
-          "text-indent percentage(%) / events": {},
-          "text-shadow shadow(shadow) / values": {},
-          "text-shadow shadow(shadow) / events": {},
-          "outline-color color(rgba) / values": {},
-          "outline-color color(rgba) / events": {},
-          "outline-offset length(pt) / values": {},
-          "outline-offset length(pt) / events": {},
-          "outline-offset length(pc) / values": {},
-          "outline-offset length(pc) / events": {},
-          "outline-offset length(px) / values": {},
-          "outline-offset length(px) / events": {},
-          "outline-offset length(em) / values": {},
-          "outline-offset length(em) / events": {},
-          "outline-offset length(ex) / values": {},
-          "outline-offset length(ex) / events": {},
-          "outline-offset length(mm) / values": {},
-          "outline-offset length(mm) / events": {},
-          "outline-offset length(cm) / values": {},
-          "outline-offset length(cm) / events": {},
-          "outline-offset length(in) / values": {},
-          "outline-offset length(in) / events": {},
-          "outline-width length(pt) / values": {},
-          "outline-width length(pt) / events": {},
-          "outline-width length(pc) / values": {},
-          "outline-width length(pc) / events": {},
-          "outline-width length(px) / values": {},
-          "outline-width length(px) / events": {},
-          "outline-width length(em) / values": {},
-          "outline-width length(em) / events": {},
-          "outline-width length(ex) / values": {},
-          "outline-width length(ex) / events": {},
-          "outline-width length(mm) / values": {},
-          "outline-width length(mm) / events": {},
-          "outline-width length(cm) / values": {},
-          "outline-width length(cm) / events": {},
-          "outline-width length(in) / values": {},
-          "outline-width length(in) / events": {},
-          "clip rectangle(rectangle) / values": {},
-          "clip rectangle(rectangle) / events": {},
-          "crop rectangle(rectangle) / values": {},
-          "crop rectangle(rectangle) / events": {},
-          "vertical-align length(pt) / values": {},
-          "vertical-align length(pt) / events": {},
-          "vertical-align length(pc) / values": {},
-          "vertical-align length(pc) / events": {},
-          "vertical-align length(px) / values": {},
-          "vertical-align length(px) / events": {},
-          "vertical-align length(em) / values": {},
-          "vertical-align length(em) / events": {},
-          "vertical-align length(ex) / values": {},
-          "vertical-align length(ex) / events": {},
-          "vertical-align length(mm) / values": {},
-          "vertical-align length(mm) / events": {},
-          "vertical-align length(cm) / values": {},
-          "vertical-align length(cm) / events": {},
-          "vertical-align length(in) / values": {},
-          "vertical-align length(in) / events": {},
-          "vertical-align percentage(%) / values": {},
-          "vertical-align percentage(%) / events": {},
-          "opacity number[0,1](zero-to-one) / values": {},
-          "opacity number[0,1](zero-to-one) / events": {},
-          "visibility visibility(keyword) / values": {},
-          "visibility visibility(keyword) / events": {},
-          "z-index integer(integer) / values": {},
-          "z-index integer(integer) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/properties-value-inherit-003.html b/css/css-transitions/properties-value-inherit-003.html
index d8d3e8e..43d0229 100644
--- a/css/css-transitions/properties-value-inherit-003.html
+++ b/css/css-transitions/properties-value-inherit-003.html
@@ -26,71 +26,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "background-position length-em(em) / values": {},
-          "background-position length-em(em) / events": {},
-          "border-top-width length-em(em) / values": {},
-          "border-top-width length-em(em) / events": {},
-          "border-right-width length-em(em) / values": {},
-          "border-right-width length-em(em) / events": {},
-          "border-bottom-width length-em(em) / values": {},
-          "border-bottom-width length-em(em) / events": {},
-          "border-left-width length-em(em) / values": {},
-          "border-left-width length-em(em) / events": {},
-          "padding-bottom length-em(em) / values": {},
-          "padding-bottom length-em(em) / events": {},
-          "padding-left length-em(em) / values": {},
-          "padding-left length-em(em) / events": {},
-          "padding-right length-em(em) / values": {},
-          "padding-right length-em(em) / events": {},
-          "padding-top length-em(em) / values": {},
-          "padding-top length-em(em) / events": {},
-          "margin-bottom length-em(em) / values": {},
-          "margin-bottom length-em(em) / events": {},
-          "margin-left length-em(em) / values": {},
-          "margin-left length-em(em) / events": {},
-          "margin-right length-em(em) / values": {},
-          "margin-right length-em(em) / events": {},
-          "margin-top length-em(em) / values": {},
-          "margin-top length-em(em) / events": {},
-          "height length-em(em) / values": {},
-          "height length-em(em) / events": {},
-          "width length-em(em) / values": {},
-          "width length-em(em) / events": {},
-          "min-height length-em(em) / values": {},
-          "min-height length-em(em) / events": {},
-          "min-width length-em(em) / values": {},
-          "min-width length-em(em) / events": {},
-          "max-height length-em(em) / values": {},
-          "max-height length-em(em) / events": {},
-          "max-width length-em(em) / values": {},
-          "max-width length-em(em) / events": {},
-          "top length-em(em) / values": {},
-          "top length-em(em) / events": {},
-          "right length-em(em) / values": {},
-          "right length-em(em) / events": {},
-          "bottom length-em(em) / values": {},
-          "bottom length-em(em) / events": {},
-          "left length-em(em) / values": {},
-          "left length-em(em) / events": {},
-          "line-height length-em(em) / values": {},
-          "line-height length-em(em) / events": {},
-          "letter-spacing length-em(em) / values": {},
-          "letter-spacing length-em(em) / events": {},
-          "word-spacing length-em(em) / values": {},
-          "word-spacing length-em(em) / events": {},
-          "text-indent length-em(em) / values": {},
-          "text-indent length-em(em) / events": {},
-          "outline-offset length-em(em) / values": {},
-          "outline-offset length-em(em) / events": {},
-          "outline-width length-em(em) / values": {},
-          "outline-width length-em(em) / events": {},
-          "vertical-align length-em(em) / values": {},
-          "vertical-align length-em(em) / events": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/pseudo-elements-001.html b/css/css-transitions/pseudo-elements-001.html
index ceaff56..cf1d254 100644
--- a/css/css-transitions/pseudo-elements-001.html
+++ b/css/css-transitions/pseudo-elements-001.html
@@ -28,15 +28,6 @@
                 height: 100000px;
             }
         </style>
-
-        <script id="metadata_cache">/*
-        {
-          "transition padding-left on :before / values": {},
-          "transition padding-left on :after / values": {},
-          "transition padding-left on :before, changing content / values": {},
-          "transition padding-left on :after, changing content / values": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/transition-001.html b/css/css-transitions/transition-001.html
index 8b27eb2..e0bf09a 100644
--- a/css/css-transitions/transition-001.html
+++ b/css/css-transitions/transition-001.html
@@ -13,23 +13,6 @@
 
         <script src="./support/vendorPrefix.js" type="text/javascript"></script>
         <script src="./support/helper.js" type="text/javascript"></script>
-
-        <script id="metadata_cache">/*
-        {
-          "parse '1s'": {},
-          "parse '1s 2s'": {},
-          "parse '1s 2s ease-in'": {},
-          "parse '1s ease-in 2s'": {},
-          "parse 'ease-in 1s 2s'": {},
-          "parse '1s width'": {},
-          "parse 'width 1s'": {},
-          "parse '1s width 2s'": {},
-          "parse '1s 2s width ease-in'": {},
-          "parse '1s ease-in 2s width'": {},
-          "parse 'width ease-in 1s 2s'": {},
-          "parse 'width .1s ease-in .2s'": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
@@ -41,26 +24,24 @@
 
         <script>
             var transition = document.getElementById('transition');
-            var ease = 'cubic-bezier(0.25, 0.1, 0.25, 1)';
-            var easeIn = 'cubic-bezier(0.42, 0, 1, 1)';
             // Note that order is important in this property. The first value that can be parsed as a time is assigned to
             // the transition-duration. The second value that can be parsed as a time is assigned to transition-delay.
             // [<‘transition-property’> || <‘transition-duration’> || <‘transition-timing-function’> || <‘transition-delay’> [, [<‘transition-property’> || <‘transition-duration’> || <‘transition-timing-function’> || <‘transition-delay’>]]*
             var values = {
                 // [property, duration, timing, delay]
                 // random order
-                '1s' : ["all", "1s", ease, "0s"],
-                '1s 2s' : ["all", "1s", ease, "2s"],
-                '1s 2s ease-in' : ["all", "1s", easeIn, "2s"],
-                '1s ease-in 2s' : ["all", "1s", easeIn, "2s"],
-                'ease-in 1s 2s' : ["all", "1s", easeIn, "2s"],
-                '1s width' : ["width", "1s", ease, "0s"],
-                'width 1s' : ["width", "1s", ease, "0s"],
-                '1s width 2s' : ["width", "1s", ease, "2s"],
-                '1s 2s width ease-in' : ["width", "1s", easeIn, "2s"],
-                '1s ease-in 2s width' : ["width", "1s", easeIn, "2s"],
-                'width ease-in 1s 2s' : ["width", "1s", easeIn, "2s"],
-                'width .1s ease-in .2s' : ["width", "0.1s", easeIn, "0.2s"]
+                '1s' : ["all", "1s", "ease", "0s"],
+                '1s 2s' : ["all", "1s", "ease", "2s"],
+                '1s 2s ease-in' : ["all", "1s", "ease-in", "2s"],
+                '1s ease-in 2s' : ["all", "1s", "ease-in", "2s"],
+                'ease-in 1s 2s' : ["all", "1s", "ease-in", "2s"],
+                '1s width' : ["width", "1s", "ease", "0s"],
+                'width 1s' : ["width", "1s", "ease", "0s"],
+                '1s width 2s' : ["width", "1s", "ease", "2s"],
+                '1s 2s width ease-in' : ["width", "1s", "ease-in", "2s"],
+                '1s ease-in 2s width' : ["width", "1s", "ease-in", "2s"],
+                'width ease-in 1s 2s' : ["width", "1s", "ease-in", "2s"],
+                'width .1s ease-in .2s' : ["width", "0.1s", "ease-in", "0.2s"]
             };
 
             for (var key in values) {
diff --git a/css/css-transitions/transition-background-position-with-edge-offset.html b/css/css-transitions/transition-background-position-with-edge-offset.html
new file mode 100644
index 0000000..9a74beb
--- /dev/null
+++ b/css/css-transitions/transition-background-position-with-edge-offset.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Transitions Test: transition-property - background-position</title>
+<link rel="author" title="Zhuoyu Qian" href="mailto:zhuoyu.qian@samsung.com">
+<link rel="help" title="7.1. Properties from CSS" href="http://www.w3.org/TR/css3-transitions/#animatable-css">
+<link rel="help" title="5.3.6 background-position" href="https://www.w3.org/TR/CSS1/#background-position">
+<meta name="assert" content="Test checks that the 'background-position' property with edge offset is animatable.">
+<script src="/resources/testharness.js" type="text/javascript"></script>
+<script src="/resources/testharnessreport.js" type="text/javascript"></script>
+<style>
+  #test {
+    border: 1px solid;
+    background-image: url("support/cat.png");
+    background-repeat: no-repeat;
+    height: 200px;
+    transition-duration: 100s;
+    transition-property: background-position;
+    transition-timing-function: step-end;
+  }
+</style>
+<body>
+  <div id="test"></div>
+</body>
+<script>
+var startValue = "left 10px top 10px";
+var endValue = "right 10px bottom 10px";
+var div = document.getElementById("test");
+
+// getComputedStyle helper
+function gCS(aProperty) {
+  return document.defaultView
+         .getComputedStyle(div, "")
+         .getPropertyValue(aProperty);
+}
+
+(function() {
+  div.style.backgroundPosition = startValue;
+
+  // flush styles
+  gCS("background-position");
+
+  // set property to endValue
+  div.setAttribute("style", "background-position: " + endValue);
+
+  test(function() {
+    assert_true(gCS("background-position") != endValue);
+  }, "background-position not equals to end value");
+})();
+</script>
diff --git a/css/css-transitions/transition-delay-001.html b/css/css-transitions/transition-delay-001.html
index d58b003..921525e 100644
--- a/css/css-transitions/transition-delay-001.html
+++ b/css/css-transitions/transition-delay-001.html
@@ -14,32 +14,6 @@
 
         <script src="./support/vendorPrefix.js" type="text/javascript"></script>
         <script src="./support/helper.js" type="text/javascript"></script>
-
-        <script id="metadata_cache">/*
-        {
-          "parse '10.2s'": {},
-          "parse '1s'": {},
-          "parse '0.1s'": {},
-          "parse '0.01s'": {},
-          "parse '0.001s'": {},
-          "parse '0.009s'": {},
-          "parse '0s'": {},
-          "parse '.0s'": {},
-          "parse '0.0s'": {},
-          "parse '.3s'": {},
-          "parse '-5s'": {},
-          "parse '10200ms'": {},
-          "parse '1000ms'": {},
-          "parse '100ms'": {},
-          "parse '10ms'": {},
-          "parse '9ms'": {},
-          "parse '1ms'": {},
-          "parse '0ms'": {},
-          "parse '-500ms'": {},
-          "parse '1s, 0.1s, 10ms'": {},
-          "parse 'foobar'": { "flags": "invalid" }
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/transition-duration-001.html b/css/css-transitions/transition-duration-001.html
index 9b7fe1e..b5c095f 100644
--- a/css/css-transitions/transition-duration-001.html
+++ b/css/css-transitions/transition-duration-001.html
@@ -14,32 +14,6 @@
 
         <script src="./support/vendorPrefix.js" type="text/javascript"></script>
         <script src="./support/helper.js" type="text/javascript"></script>
-
-        <script id="metadata_cache">/*
-        {
-          "parse '10.2s'": {},
-          "parse '1s'": {},
-          "parse '0.1s'": {},
-          "parse '0.01s'": {},
-          "parse '0.001s'": {},
-          "parse '0.009s'": {},
-          "parse '0s'": {},
-          "parse '.0s'": {},
-          "parse '0.0s'": {},
-          "parse '.3s'": {},
-          "parse '-5s'": { "flags": "invalid" },
-          "parse '10200ms'": {},
-          "parse '1000ms'": {},
-          "parse '100ms'": {},
-          "parse '10ms'": {},
-          "parse '9ms'": {},
-          "parse '1ms'": {},
-          "parse '0ms'": {},
-          "parse '-500ms'": { "flags": "invalid" },
-          "parse '1s, 0.1s, 10ms'": {},
-          "parse 'foobar'": { "flags": "invalid" }
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/transition-property-001.html b/css/css-transitions/transition-property-001.html
index ad078bc..47a1417 100644
--- a/css/css-transitions/transition-property-001.html
+++ b/css/css-transitions/transition-property-001.html
@@ -13,16 +13,6 @@
 
         <script src="./support/vendorPrefix.js" type="text/javascript"></script>
         <script src="./support/helper.js" type="text/javascript"></script>
-
-        <script id="metadata_cache">/*
-        {
-          "parse 'none'": {},
-          "parse 'all'": {},
-          "parse 'width'": {},
-          "parse 'all, width'": {},
-          "parse 'width, all'": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/transition-property-002.html b/css/css-transitions/transition-property-002.html
index 504b0a0..99196b6 100644
--- a/css/css-transitions/transition-property-002.html
+++ b/css/css-transitions/transition-property-002.html
@@ -13,18 +13,6 @@
 
         <script src="./support/vendorPrefix.js" type="text/javascript"></script>
         <script src="./support/helper.js" type="text/javascript"></script>
-
-        <script id="metadata_cache">/*
-        {
-          "parse 'none, all'": {},
-          "parse 'all, none'": {},
-          "parse 'foobar'": {},
-          "parse 'all, foobar'": {},
-          "parse 'foobar, all'": {},
-          "parse 'inherit'": {},
-          "parse 'initial'": {}
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
diff --git a/css/css-transitions/transition-timing-function-001.html b/css/css-transitions/transition-timing-function-001.html
index c51ac05..7566545 100644
--- a/css/css-transitions/transition-timing-function-001.html
+++ b/css/css-transitions/transition-timing-function-001.html
@@ -13,31 +13,6 @@
 
         <script src="./support/vendorPrefix.js" type="text/javascript"></script>
         <script src="./support/helper.js" type="text/javascript"></script>
-
-        <script id="metadata_cache">/*
-        {
-          "parse 'ease'": {},
-          "parse 'linear'": {},
-          "parse 'ease-in'": {},
-          "parse 'ease-out'": {},
-          "parse 'ease-in-out'": {},
-          "parse 'step-start'": {},
-          "parse 'step-end'": {},
-          "parse 'cubic-bezier(0.1, 0.2, 0.3, 0.4)'": {},
-          "parse 'cubic-bezier(0.1, -0.2, 0.3, -0.4)'": {},
-          "parse 'cubic-bezier(0.1, 1.2, 0.3, 1.4)'": {},
-          "parse 'steps(3, start)'": {},
-          "parse 'steps(3, end)'": {},
-          "parse 'steps(3)'": {},
-          "parse 'cubic-bezier(foobar)'": { "flags": "invalid" },
-          "parse 'steps(foobar)'": { "flags": "invalid" },
-          "parse 'steps(3.3, end)'": { "flags": "invalid" },
-          "parse 'steps(3, top)'": { "flags": "invalid" },
-          "parse 'steps(-3, top)'": { "flags": "invalid" },
-          "parse 'cubic-bezier(-0.1, -0.2, -0.3, -0.4)'": { "flags": "invalid" },
-          "parse 'cubic-bezier(1.1, 1.2, 1.3, 1.4)'": { "flags": "invalid" }
-        }
-        */</script>
     </head>
     <body>
         <!-- required by testharnessreport.js -->
@@ -49,15 +24,14 @@
 
         <script>
             var transition = document.getElementById('transition');
-            // "ease"
-            var defaultValue = 'cubic-bezier(0.25, 0.1, 0.25, 1)';
+            var defaultValue = 'ease';
             var values = {
                 // keywords
-                'ease': 'cubic-bezier(0.25, 0.1, 0.25, 1)',
-                'linear': 'cubic-bezier(0, 0, 1, 1)',
-                'ease-in': 'cubic-bezier(0.42, 0, 1, 1)',
-                'ease-out': 'cubic-bezier(0, 0, 0.58, 1)',
-                'ease-in-out': 'cubic-bezier(0.42, 0, 0.58, 1)',
+                'ease': 'ease',
+                'linear': 'linear',
+                'ease-in': 'ease-in',
+                'ease-out': 'ease-out',
+                'ease-in-out': 'ease-in-out',
                 'step-start': 'steps(1, start)',
                 'step-end': 'steps(1)',
                 // cubic bezier
diff --git a/css/css-transitions/zero-duration-multiple-transition.html b/css/css-transitions/zero-duration-multiple-transition.html
new file mode 100644
index 0000000..4268ce7
--- /dev/null
+++ b/css/css-transitions/zero-duration-multiple-transition.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="help" href="https://drafts.csswg.org/css-transitions/#starting">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1461070">
+<link rel="author" title="Emilio Cobos Álvarez" href="emilio@crisal.io">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #t {
+    color: red;
+    transition-property: color, color;
+    transition-duration: 1s, 0s;
+  }
+</style>
+<div id="t"></div>
+<script>
+test(function() {
+  let div = document.getElementById("t");
+  assert_equals(getComputedStyle(div).color, "rgb(255, 0, 0)");
+
+  div.style.color = "green";
+  assert_equals(
+    div.getAnimations().length,
+    0,
+    "No transition should've started"
+  );
+
+  assert_equals(getComputedStyle(div).color, "rgb(0, 128, 0)");
+}, "transition-duration of 0 prevents earlier transitions with the same property from starting.");
+</script>
diff --git a/css/css-typed-om/OWNERS b/css/css-typed-om/OWNERS
new file mode 100644
index 0000000..c1510aa
--- /dev/null
+++ b/css/css-typed-om/OWNERS
@@ -0,0 +1 @@
+@darrnshn
diff --git a/css/css-typed-om/factory-absolute-length.html b/css/css-typed-om/factory-absolute-length.html
index a65cf5d..5712ca9 100644
--- a/css/css-typed-om/factory-absolute-length.html
+++ b/css/css-typed-om/factory-absolute-length.html
@@ -26,11 +26,11 @@
           }, 'CSS.mm() produces mm length');
 
           test(function(){
-              var length = CSS.q(30);
+              var length = CSS.Q(30);
               assert_true(length instanceof CSSUnitValue);
               assert_equals(length.value, 30);
               assert_equals(length.unit, 'q');
-          }, 'CSS.q() produces q length');
+          }, 'CSS.Q() produces q length');
 
           test(function(){
               var length = CSS.in(40);
diff --git a/css/css-typed-om/interfaces.html b/css/css-typed-om/interfaces.html
new file mode 100644
index 0000000..e5a3638
--- /dev/null
+++ b/css/css-typed-om/interfaces.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Typed OM IDL</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#idl-index">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+'use strict';
+
+promise_test(t => {
+  return fetch('/interfaces/css-typed-om.idl')
+    .then(response => response.text())
+    .then(idls => {
+      var idl_array = new IdlArray();
+      idl_array.add_idls(idls);
+      idl_array.add_untested_idls('interface Element {};');
+      idl_array.add_untested_idls('interface CSSStyleRule {};');
+      idl_array.add_untested_idls('interface ElementCSSInlineStyle {};');
+      idl_array.add_objects({
+        // TODO: Add more objects.
+      });
+      idl_array.test();
+      t.done();
+    });
+}, 'CSS Typed OM IDL test');
+</script>
diff --git a/css/css-typed-om/resources/testhelper.js b/css/css-typed-om/resources/testhelper.js
new file mode 100644
index 0000000..e1c1f0d
--- /dev/null
+++ b/css/css-typed-om/resources/testhelper.js
@@ -0,0 +1,163 @@
+// Compares two CSSStyleValues to check if they're the same type
+// and have the same attributes.
+function assert_style_value_equals(a, b) {
+  if (a == null || b == null) {
+    assert_equals(a, b);
+    return;
+  }
+
+  assert_equals(a.constructor.name, b.constructor.name);
+  const className = a.constructor.name;
+  switch (className) {
+    case 'CSSStyleValue':
+      assert_equals(a.toString(), b.toString());
+      break;
+    case 'CSSKeywordValue':
+      assert_equals(a.value, b.value);
+      break;
+    case 'CSSUnitValue':
+      assert_approx_equals(a.value, b.value, 1e-6);
+      assert_equals(a.unit, b.unit);
+      break;
+    case 'CSSMathSum':
+    case 'CSSMathProduct':
+    case 'CSSMathMin':
+    case 'CSSMathMax':
+      assert_style_value_array_equals(a.values, b.values);
+      break;
+    case 'CSSMathInvert':
+    case 'CSSMathNegate':
+      assert_style_value_equals(a.value, b.value);
+      break;
+    case 'CSSUnparsedValue':
+      assert_style_value_array_equals(a, b);
+      break;
+    case 'CSSVariableReferenceValue':
+      assert_equals(a.variable, b.variable);
+      assert_style_value_equals(a.fallback, b.fallback);
+      break;
+    case 'CSSPositionValue':
+      assert_style_value_equals(a.x, b.x);
+      assert_style_value_equals(a.y, b.y);
+      break;
+    case 'CSSTransformValue':
+      assert_style_value_array_equals(a, b);
+      break;
+    case 'CSSRotate':
+      assert_style_value_equals(a.angle, b.angle);
+      // fallthrough
+    case 'CSSTranslate':
+    case 'CSSScale':
+      assert_style_value_equals(a.x, b.x);
+      assert_style_value_equals(a.y, b.y);
+      assert_style_value_equals(a.z, b.z);
+      assert_style_value_equals(a.is2D, b.is2D);
+      break;
+    case 'CSSSkew':
+      assert_style_value_equals(a.ax, b.ax);
+      assert_style_value_equals(a.ay, b.ay);
+      break;
+    case 'CSSSkewX':
+      assert_style_value_equals(a.ax, b.ax);
+      break;
+    case 'CSSSkewY':
+      assert_style_value_equals(a.ay, b.ay);
+      break;
+    case 'CSSPerspective':
+      assert_style_value_equals(a.length, b.length);
+      break;
+    case 'CSSMatrixComponent':
+      assert_matrix_approx_equals(a.matrix, b.matrix, 1e-6);
+      break;
+    default:
+      assert_equals(a, b);
+      break;
+  }
+}
+
+// Compares two arrays of CSSStyleValues to check if every element is equal
+function assert_style_value_array_equals(a, b) {
+  assert_equals(a.length, b.length);
+  for (let i = 0; i < a.length; i++) {
+    assert_style_value_equals(a[i], b[i]);
+  }
+}
+
+const gValidUnits = [
+  'number', 'percent', 'em', 'ex', 'ch',
+  'ic', 'rem', 'lh', 'rlh', 'vw',
+  'vh', 'vi', 'vb', 'vmin', 'vmax',
+  'cm', 'mm', 'Q', 'in', 'pt',
+  'pc', 'px', 'deg', 'grad', 'rad',
+  'turn', 's', 'ms', 'Hz', 'kHz',
+  'dpi', 'dpcm', 'dppx', 'fr',
+];
+
+// Creates a new div element with specified inline style |cssText|.
+// The created element is deleted during test cleanup.
+function createDivWithStyle(test, cssText) {
+  let element = document.createElement('div');
+  element.style = cssText || '';
+  document.body.appendChild(element);
+  test.add_cleanup(() => {
+    element.remove();
+  });
+  return element;
+}
+
+// Creates a new div element with inline style |cssText| and returns
+// its inline style property map.
+function createInlineStyleMap(test, cssText) {
+  return createElementWithInlineStyleMap(test, cssText)[1]
+}
+// Same as createInlineStyleMap but also returns the element itself.
+function createElementWithInlineStyleMap(test, cssText) {
+  let elem = createDivWithStyle(test, cssText);
+  return [elem, elem.attributeStyleMap];
+}
+
+// Creates a new div element with inline style |cssText| and returns
+// its computed style property map.
+function createComputedStyleMap(test, cssText) {
+  return createElementWithComputedStyleMap(test, cssText)[1];
+}
+// Same as createComputedStyleMap but also returns the element itself.
+function createElementWithComputedStyleMap(test, cssText) {
+  let elem = createDivWithStyle(test, cssText);
+  return [elem, elem.computedStyleMap()];
+}
+
+// Creates a new style element with a rule |cssText| and returns
+// its declared style property map.
+function createDeclaredStyleMap(test, cssText) {
+  return createRuleWithDeclaredStyleMap(test, cssText)[1];
+}
+// Same as createDeclaredStyleMap but also returns the rule itself.
+function createRuleWithDeclaredStyleMap(test, cssText) {
+  const style = document.createElement('style');
+  document.head.appendChild(style);
+  const rule = style.sheet.cssRules[style.sheet.insertRule('#test { ' + cssText + '}')];
+  test.add_cleanup(() => {
+    style.remove();
+  });
+  return [rule, rule.styleMap];
+}
+
+// Creates a new element with background image set to |imageValue|
+// and returns a new Image element that can be used to attach
+// event listeners regarding the image.
+function loadImageResource(test, imageValue) {
+  // Set a CSSURLImageValue on an element so it can be loaded.
+  let styleMap = createInlineStyleMap(test, '');
+  styleMap.set('background-image', imageValue);
+
+  // add a new Image element to know if the image resource has been loaded
+  let image = new Image();
+  image.src = imageValue.url;
+  return image;
+}
+
+function assert_matrix_approx_equals(actual, expected, epsilon) {
+  assert_array_approx_equals(
+      actual.toFloat64Array(), expected.toFloat64Array(), epsilon);
+}
diff --git a/css/css-typed-om/styleMap-update-function.html b/css/css-typed-om/styleMap-update-function.html
deleted file mode 100644
index 4dea009..0000000
--- a/css/css-typed-om/styleMap-update-function.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>styleMap objects provide an 'update' function</title>
-<meta name="author" title="Shane Stephens">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-  <div id='element'></div>
-  <div id="log"></div>
-  <script>
-    test(function() {
-      element.attributeStyleMap.set('width', new CSSUnitValue(42, 'px'));
-      element.attributeStyleMap.update('width', length => new CSSSimpleLength(length.value + 20, length.type))
-      assert_equals(element.attributeStyleMap.get('width').value, 62, 'update expected to apply callback to old value in map');
-    });
-  </script>
-
diff --git a/css/css-typed-om/stylevalue-normalization/normalize-ident.tentative.html b/css/css-typed-om/stylevalue-normalization/normalize-ident.tentative.html
new file mode 100644
index 0000000..d118dda
--- /dev/null
+++ b/css/css-typed-om/stylevalue-normalization/normalize-ident.tentative.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Identifier normalization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#normalize-ident">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(() => {
+  assert_style_value_equals(CSSStyleValue.parse('width', 'auto'),
+      new CSSKeywordValue('auto'));
+}, 'CSS identifiers are normalized from String to CSSKeywordValues');
+
+test(t => {
+  assert_style_value_equals(
+      createDivWithStyle(t, 'width: auto').attributeStyleMap.get('width'),
+      new CSSKeywordValue('auto'));
+}, 'CSS identifiers are normalized from CSSOM to CSSKeywordValues');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-normalization/normalize-image.html b/css/css-typed-om/stylevalue-normalization/normalize-image.html
new file mode 100644
index 0000000..0b60ec4
--- /dev/null
+++ b/css/css-typed-om/stylevalue-normalization/normalize-image.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSImageValue normalization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#resourcevalue-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gTestUrl = '/media/1x1-green.png';
+const gBadTestUrl = document.location.href;
+
+test(t => {
+  const result = CSSStyleValue.parse('background-image', 'url("' + gTestUrl + '")');
+  assert_class_string(result, 'CSSImageValue');
+}, 'Normalizing a valid <url> returns a CSSImageValue');
+
+test(t => {
+  const result = CSSStyleValue.parse('background-image', 'url("' + gBadTestUrl + '")');
+  assert_class_string(result, 'CSSImageValue');
+}, 'Normalizing a bad <url> returns a CSSImageValue');
+
+test(t => {
+  const result = CSSStyleValue.parse('background-image', 'linear-gradient(red, orange)');
+  assert_equals(result.constructor.name, 'CSSImageValue');
+}, 'Normalizing a <gradient> returns a CSSImageValue');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-normalization/normalize-numeric.tentative.html b/css/css-typed-om/stylevalue-normalization/normalize-numeric.tentative.html
new file mode 100644
index 0000000..6d1ae39
--- /dev/null
+++ b/css/css-typed-om/stylevalue-normalization/normalize-numeric.tentative.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Numeric normalization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#normalize-numeric">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+function test_numeric_normalization(test, property, cssText, expected) {
+  assert_style_value_equals(CSSNumericValue.parse(cssText), expected);
+  assert_style_value_equals(CSSStyleValue.parse(property, cssText), expected);
+  assert_style_value_equals(
+      createInlineStyleMap(test, property + ': ' + cssText).get(property),
+      expected);
+}
+
+test(t => {
+  test_numeric_normalization(t, 'line-height', '3.14', CSS.number(3.14));
+}, 'Normalizing a <number> returns a number CSSUnitValue');
+
+test(t => {
+  test_numeric_normalization(t, 'width', '3.14%', CSS.percent(3.14));
+}, 'Normalizing a <percentage> returns a percent CSSUnitValue');
+
+test(t => {
+  test_numeric_normalization(t, 'width', '3.14px', CSS.px(3.14));
+}, 'Normalizing a <dimension> returns a CSSUnitValue with the correct unit');
+
+test(t => {
+  test_numeric_normalization(t, 'opacity', '0', CSS.number(0));
+}, 'Normalizing a <number> with a unitless zero returns 0');
+
+test(t => {
+  test_numeric_normalization(t, 'width',
+      'calc(1px + calc(1px) + calc(1px * 2) + 1%)',
+      new CSSMathSum(CSS.px(4), CSS.percent(1)));
+}, 'Normalizing a <calc> returns simplified expression');
+
+test(t => {
+  assert_style_value_equals(CSSStyleValue.parse('width', '0px'), CSS.px(0));
+  assert_style_value_equals(
+      createInlineStyleMap(t, 'width: 0').get('width'),
+      CSS.px(0));
+}, 'Normalizing a <dimension> with a unitless zero returns 0px');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-normalization/normalize-tokens.tentative.html b/css/css-typed-om/stylevalue-normalization/normalize-tokens.tentative.html
new file mode 100644
index 0000000..6dbc3e3
--- /dev/null
+++ b/css/css-typed-om/stylevalue-normalization/normalize-tokens.tentative.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Normalization of raw CSS tokens tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#normalize-tokens">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+function assert_string_normalizes_to(test, property, str, expected) {
+  // From string
+  assert_style_value_equals(CSSStyleValue.parse(property, str), expected);
+  // From CSSOM
+  assert_style_value_equals(
+    createInlineStyleMap(test, property + ':' + str).get(property),
+    expected
+  );
+}
+
+const gTestCases = [
+  {
+    value: 'var(--A)',
+    expectedResult: [
+      new CSSVariableReferenceValue('--A'),
+    ]
+  },
+  {
+    value: 'var(--A, 1em)',
+    expectedResult: [
+      new CSSVariableReferenceValue('--A', new CSSUnparsedValue([' 1em'])),
+    ]
+  },
+  {
+    value: 'var(--A, var(--B))',
+    expectedResult: [
+      new CSSVariableReferenceValue('--A', new CSSUnparsedValue([' ', new CSSVariableReferenceValue('--B')])),
+    ]
+  },
+  {
+    value: 'calc(42px + var(--foo, 15em) + var(--bar, var(--far) + 15px))',
+    expectedResult: [
+      'calc(42px + ',
+      new CSSVariableReferenceValue('--foo', new CSSUnparsedValue([' 15em'])),
+      ' + ',
+      new CSSVariableReferenceValue('--bar', new CSSUnparsedValue([' ', new CSSVariableReferenceValue('--far'), ' + 15px'])),
+      ')',
+    ]
+  },
+];
+
+for (const {value, expectedResult} of gTestCases) {
+  test(t => {
+    assert_string_normalizes_to(t, 'color', value, new CSSUnparsedValue(expectedResult));
+  }, 'Normalizing "' + value + '" on a CSS property returns correct CSSUnparsedValue');
+
+  test(t => {
+    assert_string_normalizes_to(t, 'margin', value, new CSSUnparsedValue(expectedResult));
+    assert_false(true);
+  }, 'Normalizing "' + value + '" on a shorthand returns correct CSSUnparsedValue');
+
+  test(t => {
+    assert_string_normalizes_to(t, 'transition-duration', value, new CSSUnparsedValue(expectedResult));
+  }, 'Normalizing "' + value + '" on a list-valued property returns correct CSSUnparsedValue');
+
+  test(t => {
+    assert_string_normalizes_to(t, '--X', value, new CSSUnparsedValue(expectedResult));
+  }, 'Normalizing "' + value + '" on a custom property returns correct CSSUnparsedValue');
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-normalization/positionvalue-normalization.tentative.html b/css/css-typed-om/stylevalue-normalization/positionvalue-normalization.tentative.html
new file mode 100644
index 0000000..8631c05
--- /dev/null
+++ b/css/css-typed-om/stylevalue-normalization/positionvalue-normalization.tentative.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSPositionValue normalization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#positionvalue-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gSingleTestCases = [
+  { cssText: 'left', x: CSS.percent(0), y: CSS.percent(50) },
+  { cssText: 'right', x: CSS.percent(100), y: CSS.percent(50) },
+  { cssText: 'center', x: CSS.percent(50), y: CSS.percent(50) },
+  { cssText: 'top', x: CSS.percent(50), y: CSS.percent(0) },
+  { cssText: 'bottom', x: CSS.percent(50), y: CSS.percent(100) },
+  { cssText: '3.14px', x: CSS.px(3.14), y: CSS.percent(50) },
+  { cssText: '25%', x: CSS.percent(25), y: CSS.percent(50) },
+];
+
+const gXTestCases = [
+  { cssText: 'left', x: CSS.percent(0), type: 'ident' },
+  { cssText: 'center', x: CSS.percent(50), type: 'ident' },
+  { cssText: 'right', x: CSS.percent(100), type: 'ident' },
+  { cssText: '3.14px', x: CSS.px(3.14), type: 'length' },
+  { cssText: '10%', x: CSS.percent(10), type: 'length' },
+  { cssText: 'calc(1px + 1em)', x: new CSSMathSum(CSS.px(1), CSS.em(1)), type: 'length' },
+  { cssText: 'left 3.14px', x: CSS.px(3.14), type: 'offset' },
+  { cssText: 'right 3.14px', x: CSS.percent(100).sub(CSS.px(3.14)), type: 'offset' },
+];
+
+const gYTestCases = [
+  { cssText: 'top', y: CSS.percent(0), type: 'ident' },
+  { cssText: 'center', y: CSS.percent(50), type: 'ident' },
+  { cssText: 'bottom', y: CSS.percent(100), type: 'ident' },
+  { cssText: '3.14px', y: CSS.px(3.14), type: 'length' },
+  { cssText: '10%', y: CSS.percent(10), type: 'length' },
+  { cssText: 'calc(1px + 1em)', y: new CSSMathSum(CSS.px(1), CSS.em(1)), type: 'length' },
+  { cssText: 'top 3.14px', y: CSS.px(3.14), type: 'offset' },
+  { cssText: 'bottom 3.14px', y: CSS.percent(100).sub(CSS.px(3.14)), type: 'offset' },
+];
+
+for (const {cssText, x, y} of gSingleTestCases) {
+    test(t => {
+      const styleMap = createInlineStyleMap(t, 'object-position: ' + cssText);
+      assert_style_value_equals(styleMap.get('object-position'), new CSSPositionValue(x, y));
+    }, 'CSS <position> value "' + cssText + '" normalizes to CSSPositionValue');
+}
+
+for (const {cssText: xCssText, x, type: xType} of gXTestCases) {
+  for (const {cssText: yCssText, y, type: yType} of gYTestCases) {
+    const cssText = xCssText + ' ' + yCssText;
+
+    // Can't have position values with 3 parts
+    if ((xType === 'offset') !== (yType === 'offset'))
+      continue;
+
+    test(t => {
+      const styleMap = createInlineStyleMap(t, 'object-position: ' + cssText);
+      assert_style_value_equals(styleMap.get('object-position'), new CSSPositionValue(x, y));
+    }, 'CSS <position> value "' + cssText + '" normalizes to CSSPositionValue');
+  }
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-normalization/transformvalue-normalization.tentative.html b/css/css-typed-om/stylevalue-normalization/transformvalue-normalization.tentative.html
new file mode 100644
index 0000000..9157ae8
--- /dev/null
+++ b/css/css-typed-om/stylevalue-normalization/transformvalue-normalization.tentative.html
@@ -0,0 +1,182 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Transform normalization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#transformvalue-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/comparisons.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+function test_transform_normalization(test, cssText, expected) {
+  assert_style_value_equals(CSSStyleValue.parse('transform', cssText), expected);
+  assert_style_value_equals(
+      createInlineStyleMap(test, 'transform: ' + cssText).get('transform'),
+      expected);
+}
+
+test(t => {
+  test_transform_normalization(t, 'matrix(1, 2, 3, 4, 5, 6)',
+      new CSSTransformValue([
+        new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6]))
+      ]));
+}, 'Normalizing a matrix() returns a CSSMatrixComponent');
+
+test(t => {
+  test_transform_normalization(t,
+      'matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)',
+      new CSSTransformValue([
+        new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]))
+      ]));
+}, 'Normalizing a matrix3d() returns a CSSMatrixComponent');
+
+const gTestCases = [
+  {
+    cssText: 'translate(1px)',
+    expected: new CSSTranslate(CSS.px(1), CSS.px(0)),
+    desc: 'translate() with X'
+  },
+  {
+    cssText: 'translate(1%, 1px)',
+    expected: new CSSTranslate(CSS.percent(1), CSS.px(1)),
+    desc: 'translate() with X and Y'
+  },
+  {
+    cssText: 'translateX(1%)',
+    expected: new CSSTranslate(CSS.percent(1), CSS.px(0)),
+    desc: 'translateX()'
+  },
+  {
+    cssText: 'translateY(1px)',
+    expected: new CSSTranslate(CSS.px(0), CSS.px(1)),
+    desc: 'translateY()'
+  },
+  {
+    cssText: 'translate3d(1px, 2%, 3px)',
+    expected: new CSSTranslate(CSS.px(1), CSS.percent(2), CSS.px(3)),
+    desc: 'translate3d()'
+  },
+  {
+    cssText: 'translateZ(1px)',
+    expected: new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(1)),
+    desc: 'translateZ()'
+  },
+  {
+    cssText: 'scale(2)',
+    expected: new CSSScale(CSS.number(2), CSS.number(2)),
+    desc: 'scale() with one argument'
+  },
+  {
+    cssText: 'scale(2, 3)',
+    expected: new CSSScale(CSS.number(2), CSS.number(3)),
+    desc: 'scale() with two arguments'
+  },
+  {
+    cssText: 'scaleX(2)',
+    expected: new CSSScale(CSS.number(2), CSS.number(1)),
+    desc: 'scaleX()'
+  },
+  {
+    cssText: 'scaleY(2)',
+    expected: new CSSScale(CSS.number(1), CSS.number(2)),
+    desc: 'scaleY()'
+  },
+  {
+    cssText: 'scale3d(1, 2, 3)',
+    expected: new CSSScale(CSS.number(1), CSS.number(2), CSS.number(3)),
+    desc: 'scale3d()'
+  },
+  {
+    cssText: 'scaleZ(2)',
+    expected: new CSSScale(CSS.number(1), CSS.number(1), CSS.number(2)),
+    desc: 'scaleZ()'
+  },
+  {
+    cssText: 'rotate(90deg)',
+    expected: new CSSRotate(CSS.deg(90)),
+    desc: 'rotate()'
+  },
+  {
+    cssText: 'rotate3d(1, 2, 3, 90deg)',
+    expected: new CSSRotate(CSS.number(1), CSS.number(2), CSS.number(3), CSS.deg(90)),
+    desc: 'rotate3d()'
+  },
+  {
+    cssText: 'rotateX(90deg)',
+    expected: new CSSRotate(CSS.number(1), CSS.number(0), CSS.number(0), CSS.deg(90)),
+    desc: 'rotateX()'
+  },
+  {
+    cssText: 'rotateY(90deg)',
+    expected: new CSSRotate(CSS.number(0), CSS.number(1), CSS.number(0), CSS.deg(90)),
+    desc: 'rotateY()'
+  },
+  {
+    cssText: 'rotateZ(90deg)',
+    expected: new CSSRotate(CSS.number(0), CSS.number(0), CSS.number(1), CSS.deg(90)),
+    desc: 'rotateZ()'
+  },
+  {
+    cssText: 'skew(90deg)',
+    expected: new CSSSkew(CSS.deg(90), CSS.deg(0)),
+    desc: 'skew() with only X'
+  },
+  {
+    cssText: 'skew(90deg, 0deg)',
+    expected: new CSSSkew(CSS.deg(90), CSS.deg(0)),
+    desc: 'skew() with X and Y which is 0 value'
+  },
+  {
+    cssText: 'skew(90deg, 45deg)',
+    expected: new CSSSkew(CSS.deg(90), CSS.deg(45)),
+    desc: 'skew() with X and Y'
+  },
+  {
+    cssText: 'skewX(90deg)',
+    expected: new CSSSkewX(CSS.deg(90)),
+    desc: 'skewX()'
+  },
+  {
+    cssText: 'skewY(90deg)',
+    expected: new CSSSkewY(CSS.deg(90)),
+    desc: 'skewY()'
+  },
+  {
+    cssText: 'perspective(1px)',
+    expected: new CSSPerspective(CSS.px(1)),
+    desc: 'perspective()'
+  },
+];
+
+for (const {cssText, expected, desc} of gTestCases) {
+  test(t => {
+    test_transform_normalization(t, cssText, new CSSTransformValue([expected]));
+  }, 'Normalizing a ' + desc + ' returns a ' + expected.constructor.name);
+}
+
+test(t => {
+  test_transform_normalization(t,
+      'translate(1px) rotateX(90deg) perspective(1px) skew(90deg) skewX(20deg) skewY(30deg) scale3d(1, 2, 3)',
+      new CSSTransformValue([
+        new CSSTranslate(CSS.px(1), CSS.px(0)),
+        new CSSRotate(CSS.number(1), CSS.number(0), CSS.number(0), CSS.deg(90)),
+        new CSSPerspective(CSS.px(1)),
+        new CSSSkew(CSS.deg(90), CSS.deg(0)),
+        new CSSSkewX(CSS.deg(20)),
+        new CSSSkewY(CSS.deg(30)),
+        new CSSScale(CSS.number(1), CSS.number(2), CSS.number(3)),
+      ]));
+}, 'Normalizing a <transform-list> returns a CSSTransformValue containing all the transforms');
+
+test(t => {
+  test_transform_normalization(t,
+      'translate(calc(1px + 1em)) perspective(calc(1px + 1em))',
+      new CSSTransformValue([
+        new CSSTranslate(new CSSMathSum(CSS.px(1), CSS.em(1)), CSS.px(0)),
+        new CSSPerspective(new CSSMathSum(CSS.px(1), CSS.em(1))),
+      ]));
+}, 'Normalizing transforms with calc values contains CSSMathValues');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-objects/parse-invalid.html b/css/css-typed-om/stylevalue-objects/parse-invalid.html
new file mode 100644
index 0000000..ea1d564
--- /dev/null
+++ b/css/css-typed-om/stylevalue-objects/parse-invalid.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSStyleValue.parse Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssstylevalue-parse">
+<meta name="assert" content="Test CSSStyleValue.parse error handling" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parse('', 'auto'));
+}, 'CSSStyleValue.parse() with empty property name throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parse('lemon', 'auto'));
+}, 'CSSStyleValue.parse() with unsupported property name throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parse('width', '10deg'));
+}, 'CSSStyleValue.parse() with invalid value for valid property throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parse('margin', '10deg'));
+}, 'CSSStyleValue.parse() with invalid value for shorthand property throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parse('--foo', ''));
+}, 'CSSStyleValue.parse() with invalid value for custom property throws TypeError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-objects/parse.html b/css/css-typed-om/stylevalue-objects/parse.html
new file mode 100644
index 0000000..65e46f2
--- /dev/null
+++ b/css/css-typed-om/stylevalue-objects/parse.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSStyleValue.parse</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssstylevalue-parse">
+<meta name="assert" content="Test CSSStyleValue.parse returns CSSStyleValues" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = CSSStyleValue.parse('width', '10px');
+  assert_true(result instanceof CSSStyleValue,
+              'Result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parse() with a valid property returns a CSSStyleValue');
+
+test(() => {
+  const result = CSSStyleValue.parse('WiDtH', '10px');
+  assert_true(result instanceof CSSStyleValue,
+              'Result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parse() is not case sensitive');
+
+test(() => {
+  const result = CSSStyleValue.parse('transition-duration', '1s, 2s, 3s');
+  assert_true(result instanceof CSSStyleValue,
+              'Result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parse() with a valid list-valued property returns a CSSStyleValue');
+
+test(() => {
+  const result = CSSStyleValue.parse('margin', '1px 2px 3px 4px');
+  assert_true(result instanceof CSSStyleValue,
+              'Result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parse() with a valid shorthand property returns a CSSStyleValue');
+
+test(() => {
+  const result = CSSStyleValue.parse('--foo', 'auto');
+  assert_true(result instanceof CSSStyleValue,
+              'Result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parse() with a valid custom property returns a CSSStyleValue');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-objects/parseAll-invalid.html b/css/css-typed-om/stylevalue-objects/parseAll-invalid.html
new file mode 100644
index 0000000..c180954
--- /dev/null
+++ b/css/css-typed-om/stylevalue-objects/parseAll-invalid.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSStyleValue.parseAll Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssstylevalue-parseall">
+<meta name="assert" content="Test CSSStyleValue.parseAll error handling" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parseAll('', 'auto'));
+}, 'CSSStyleValue.parseAll() with empty property name throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parseAll('lemon', 'auto'));
+}, 'CSSStyleValue.parseAll() with unsupported property name throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parseAll('width', '10deg'));
+}, 'CSSStyleValue.parseAll() with invalid value for valid property throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parseAll('margin', '10deg'));
+}, 'CSSStyleValue.parseAll() with invalid value for shorthand property throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSSStyleValue.parseAll('--foo', ''));
+}, 'CSSStyleValue.parseAll() with invalid value for custom property throws TypeError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-objects/parseAll.html b/css/css-typed-om/stylevalue-objects/parseAll.html
new file mode 100644
index 0000000..cee4503
--- /dev/null
+++ b/css/css-typed-om/stylevalue-objects/parseAll.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSStyleValue.parseAll</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssstylevalue-parseall">
+<meta name="assert" content="Test CSSStyleValue.parseAll returns CSSStyleValues" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = CSSStyleValue.parseAll('width', '10px');
+  assert_equals(result.length, 1, 'Result must be a list with one element');
+  assert_true(result[0] instanceof CSSStyleValue,
+              'Only element in result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parseAll() with a valid property returns a list with a single CSSStyleValue');
+
+test(() => {
+  const result = CSSStyleValue.parseAll('WiDtH', '10px');
+  assert_equals(result.length, 1, 'Result must be a list with one element');
+  assert_true(result[0] instanceof CSSStyleValue,
+              'Only element in result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parseAll() is not case sensitive');
+
+test(() => {
+  const result = CSSStyleValue.parseAll('transition-duration', '1s, 2s, 3s');
+  assert_equals(result.length, 3, 'Result must be a list with three elements');
+  for (const item of result) {
+    assert_true(item instanceof CSSStyleValue,
+                'All elements in result must be a subclass of CSSStyleValue');
+  }
+}, 'CSSStyleValue.parseAll() with a valid list-valued property returns a list with a single CSSStyleValue');
+
+test(() => {
+  const result = CSSStyleValue.parseAll('margin', '1px 2px 3px 4px');
+  assert_equals(result.length, 1, 'Result must be a list with one element');
+  assert_true(result[0] instanceof CSSStyleValue,
+              'Only element in result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parseAll() with a valid shorthand property returns a CSSStyleValue');
+
+test(() => {
+  const result = CSSStyleValue.parseAll('--foo', 'auto');
+  assert_equals(result.length, 1, 'Result must be a list with one element');
+  assert_true(result[0] instanceof CSSStyleValue,
+              'Only element in result must be a subclass of CSSStyleValue');
+}, 'CSSStyleValue.parseAll() with a valid custom property returns a list with a single CSSStyleValue');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssImageValue.html b/css/css-typed-om/stylevalue-serialization/cssImageValue.html
new file mode 100644
index 0000000..92256e5
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssImageValue.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSURLImageValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#urlimagevalue-serialization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<div id="testUrl" style="background-image: url('/media/1x1-green.png')"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = document.getElementById('testUrl').attributeStyleMap.get('background-image');
+  assert_equals(result.toString(), 'url("/media/1x1-green.png")');
+}, 'CSSUrlImageValue serializes correctly');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssKeywordValue.tentative.html b/css/css-typed-om/stylevalue-serialization/cssKeywordValue.tentative.html
new file mode 100644
index 0000000..1e2041d
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssKeywordValue.tentative.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>IDL-constructed CSSKeywordValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#unparsedvalue-serialization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(() => {
+  assert_equals(new CSSKeywordValue('auto').toString(), 'auto');
+  assert_equals(new CSSKeywordValue('inherit').toString(), 'inherit');
+  assert_equals(new CSSKeywordValue('lemon').toString(), 'lemon');
+}, 'CSSKeywordValue constructed from IDL serializes correctly');
+
+test(() => {
+  assert_equals(new CSSKeywordValue(' Hello World').toString(), CSS.escape(' Hello World'));
+  assert_equals(new CSSKeywordValue('3').toString(), CSS.escape('3'));
+}, 'CSSKeywordValue constructed from IDL serializes to escaped strings');
+
+test(() => {
+  let result = CSSStyleValue.parse('width', 'auto');
+  result.value = 'lemon';
+  assert_equals(result.toString(), 'lemon');
+}, 'CSSKeywordValue from DOMString modified through "value" setter serializes correctly');
+
+test(t => {
+  let result = createInlineStyleMap(t, 'width: auto').get('width');
+  result.value = 'lemon';
+  assert_equals(result.toString(), 'lemon');
+}, 'CSSKeywordValue from CSSOM modified through "value" setter serializes correctly');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssMathValue.tentative.html b/css/css-typed-om/stylevalue-serialization/cssMathValue.tentative.html
new file mode 100644
index 0000000..fd9fce8
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssMathValue.tentative.html
@@ -0,0 +1,120 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>IDL-constructed CSSMathValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#calc-serialization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gTestCases = [
+  {
+    description: 'CSSMathMax with one argument',
+    value: new CSSMathMax(1),
+    cssText: 'max(1)',
+  },
+  {
+    description: 'CSSMathMax with more than one argument',
+    value: new CSSMathMax(1, 2, 3),
+    cssText: 'max(1, 2, 3)',
+  },
+  {
+    description: 'CSSMathMax containing nested CSSMathValues',
+    value: new CSSMathMax(new CSSMathSum(1, 2), 3),
+    cssText: 'max(1 + 2, 3)',
+  },
+  {
+    description: 'CSSMathMin with one argument',
+    value: new CSSMathMin(1),
+    cssText: 'min(1)',
+  },
+  {
+    description: 'CSSMathMin with more than one argument',
+    value: new CSSMathMin(1, 2, 3),
+    cssText: 'min(1, 2, 3)',
+  },
+  {
+    description: 'CSSMathMin containing nested CSSMathValues',
+    value: new CSSMathMin(new CSSMathSum(1, 2), 3),
+    cssText: 'min(1 + 2, 3)',
+  },
+  {
+    description: 'CSSMathSum with one argument',
+    value: new CSSMathSum(1),
+    cssText: 'calc(1)',
+  },
+  {
+    description: 'CSSMathSum with more than one argument',
+    value: new CSSMathSum(1, 2, 3),
+    cssText: 'calc(1 + 2 + 3)',
+  },
+  {
+    description: 'CSSMathSum with a CSSMathNegate as first value',
+    value: new CSSMathSum(new CSSMathNegate(1), 2, 3),
+    cssText: 'calc((-1) + 2 + 3)',
+  },
+  {
+    description: 'CSSMathSum containing a CSSMathNegate after first value',
+    value: new CSSMathSum(1, new CSSMathNegate(2), 3),
+    cssText: 'calc(1 - 2 + 3)',
+  },
+  {
+    description: 'CSSMathSum nested inside a CSSMathValue',
+    value: new CSSMathSum(new CSSMathSum(1, 2), 3),
+    cssText: 'calc((1 + 2) + 3)',
+  },
+  {
+    description: 'CSSMathNegate',
+    value: new CSSMathNegate(1),
+    cssText: 'calc(-1)',
+  },
+  {
+    description: 'CSSMathNegate nested inside a CSSMathValue',
+    value: new CSSMathProduct(new CSSMathNegate(1)),
+    cssText: 'calc((-1))',
+  },
+  {
+    description: 'CSSMathProduct with one argument',
+    value: new CSSMathProduct(1),
+    cssText: 'calc(1)',
+  },
+  {
+    description: 'CSSMathProduct with more than one argument',
+    value: new CSSMathProduct(1, 2, 3),
+    cssText: 'calc(1 * 2 * 3)',
+  },
+  {
+    description: 'CSSMathProduct with a CSSMathInvert as first value',
+    value: new CSSMathProduct(new CSSMathInvert(1), 2, 3),
+    cssText: 'calc((1 / 1) * 2 * 3)',
+  },
+  {
+    description: 'CSSMathProduct containing a CSSMathInvert after first value',
+    value: new CSSMathProduct(1, new CSSMathInvert(2), 3),
+    cssText: 'calc(1 / 2 * 3)',
+  },
+  {
+    description: 'CSSMathProduct nested inside a CSSMathValue',
+    value: new CSSMathProduct(new CSSMathProduct(1, 2), 3),
+    cssText: 'calc((1 * 2) * 3)',
+  },
+  {
+    description: 'CSSMathInvert',
+    value: new CSSMathInvert(1),
+    cssText: 'calc(1 / 1)',
+  },
+  {
+    description: 'CSSMathInvert nested inside a CSSMathValue',
+    value: new CSSMathSum(new CSSMathInvert(1)),
+    cssText: 'calc((1 / 1))',
+  },
+];
+
+for (const {value, cssText, description} of gTestCases) {
+  test(() => {
+    assert_equals(value.toString(), cssText);
+  }, description + ' serializes correctly');
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssPositionValue.html b/css/css-typed-om/stylevalue-serialization/cssPositionValue.html
new file mode 100644
index 0000000..c93b2f1
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssPositionValue.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>IDL-constructed CSSPositionValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#positionvalue-serialization">
+<meta name="assert" content="Test CSSPositionValues are serialized by concatenating their coordinate values" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(() => {
+  assert_equals(new CSSPositionValue(CSS.px(1), CSS.percent(-3.14)).toString(), '1px -3.14%');
+}, 'CSSPositionValue with length and percent serializes by concantenating x and y');
+
+test(() => {
+  let result = CSSStyleValue.parse('object-position', '1px 1px');
+  result.x = CSS.percent(-3.14);
+  assert_equals(result.toString(), '-3.14% 1px');
+}, 'CSSPositionValue from DOMString modified by "x" setter serializes to its new value');
+
+test(() => {
+  let result = CSSStyleValue.parse('object-position', '1px 1px');
+  result.y = CSS.percent(-3.14);
+  assert_equals(result.toString(), '1px -3.14%');
+}, 'CSSPositionValue from DOMString modified by "y" setter serializes to its new value');
+
+test(t => {
+  let result = createInlineStyleMap(t, 'object-position: 1px 1px').get('object-position');
+  result.x = CSS.percent(-3.14);
+  assert_equals(result.toString(), '-3.14% 1px');
+}, 'CSSPositionValue from CSSOM modified by "x" setter serializes to its new value');
+
+test(t => {
+  let result = createInlineStyleMap(t, 'object-position: 1px 1px').get('object-position');
+  result.y = CSS.percent(-3.14);
+  assert_equals(result.toString(), '1px -3.14%');
+}, 'CSSPositionValue from CSSOM modified by "y" setter serializes to its new value');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssStyleValue-cssom.html b/css/css-typed-om/stylevalue-serialization/cssStyleValue-cssom.html
new file mode 100644
index 0000000..5f21bfa
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssStyleValue-cssom.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSStyleValue serialization from CSSOM</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#stylevalue-serialization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  const result = createInlineStyleMap(t, 'color: red').get('color');
+  assert_equals(result.toString(), 'red');
+}, 'CSSStyleValue from specified CSSOM serializes correctly');
+
+test(t => {
+  const result = createComputedStyleMap(t, 'color: red').get('color');
+  assert_equals(result.toString(), 'rgb(255, 0, 0)');
+}, 'CSSStyleValue from computed CSSOM serializes correctly');
+
+test(t => {
+  const result = createInlineStyleMap(t, 'background: blue').get('background');
+  assert_equals(result.toString(), 'blue');
+}, 'Shorthand CSSStyleValue from inline CSSOM serializes correctly');
+
+test(t => {
+  const result = createComputedStyleMap(t, 'background: blue').get('background');
+  assert_equals(result.toString(), 'rgb(0, 0, 255) none repeat scroll 0% 0% / auto padding-box border-box');
+}, 'Shorthand CSSStyleValue from computed CSSOM serializes correctly');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssStyleValue-string.html b/css/css-typed-om/stylevalue-serialization/cssStyleValue-string.html
new file mode 100644
index 0000000..747635d
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssStyleValue-string.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSStyleValue serialization from parsing</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#stylevalue-serialization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = CSSStyleValue.parse('color', 'red');
+  assert_equals(result.toString(), 'red');
+}, 'CSSStyleValue parsed from string serializes to given string');
+
+test(() => {
+  const result = CSSStyleValue.parse('background', 'blue');
+  assert_equals(result.toString(), 'blue');
+}, 'Shorthand CSSStyleValue parsed from string serializes to given string');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html b/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
new file mode 100644
index 0000000..0184cbd
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
@@ -0,0 +1,135 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>IDL-constructed CSSTransformValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#positionvalue-serialization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gTestCases = [
+  {
+    value: new CSSTranslate(CSS.percent(1), CSS.px(1)),
+    cssText: 'translate(1%, 1px)',
+    desc: 'CSSTranslate with 2 arguments'
+  },
+  {
+    value: new CSSTranslate(CSS.px(1), CSS.percent(2), CSS.px(3)),
+    cssText: 'translate3d(1px, 2%, 3px)',
+    desc: 'CSSTranslate with 3 arguments'
+  },
+  {
+    value: new CSSScale(CSS.number(2), CSS.number(3)),
+    cssText: 'scale(2, 3)',
+    desc: 'CSSScale with 2 arguments'
+  },
+  {
+    value: new CSSScale(CSS.number(1), CSS.number(2), CSS.number(3)),
+    cssText: 'scale3d(1, 2, 3)',
+    desc: 'CSSScale with 3 arguments'
+  },
+  {
+    value: new CSSRotate(CSS.deg(90)),
+    cssText: 'rotate(90deg)',
+    desc: 'CSSRotate with 1 argument'
+  },
+  {
+    value: new CSSRotate(CSS.number(1), CSS.number(2), CSS.number(3), CSS.deg(90)),
+    cssText: 'rotate3d(1, 2, 3, 90deg)',
+    desc: 'CSSRotate with 4 arguments'
+  },
+  {
+    value: new CSSSkew(CSS.deg(90), CSS.deg(45)),
+    cssText: 'skew(90deg, 45deg)',
+    desc: 'CSSSkew'
+  },
+  {
+    value: new CSSSkew(CSS.deg(90), CSS.turn(0)),
+    cssText: 'skew(90deg)',
+    desc: 'CSSSkew with Y which is 0 value'
+  },
+  {
+    value: new CSSSkewX(CSS.deg(90)),
+    cssText: 'skewX(90deg)',
+    desc: 'CSSSkewX'
+  },
+  {
+    value: new CSSSkewY(CSS.deg(90)),
+    cssText: 'skewY(90deg)',
+    desc: 'CSSSkewY'
+  },
+  {
+    value: new CSSPerspective(CSS.px(1)),
+    cssText: 'perspective(1px)',
+    desc: 'CSSPerspective'
+  },
+  {
+    value: new CSSPerspective(CSS.px(-1)),
+    cssText: 'perspective(calc(-1px))',
+    desc: 'CSSPerspective with negative length'
+  },
+  {
+    value: new CSSTransformValue([new CSSPerspective(CSS.px(1))]),
+    cssText: 'perspective(1px)',
+    desc: 'CSSTransformValue with a single transform'
+  },
+  {
+    value: new CSSTransformValue([
+      new CSSTranslate(CSS.px(1), CSS.px(0)),
+      new CSSRotate(CSS.deg(90)),
+      new CSSPerspective(CSS.px(1)),
+      new CSSSkew(CSS.deg(90), CSS.deg(45)),
+      new CSSScale(CSS.number(1), CSS.number(2), CSS.number(3)),
+    ]),
+    cssText: 'translate(1px, 0px) rotate(90deg) perspective(1px) skew(90deg, 45deg) scale3d(1, 2, 3)',
+    desc: 'CSSTransformValue with multiple transforms'
+  },
+  {
+    value: new CSSTransformValue([
+      new CSSTranslate(new CSSMathSum(CSS.px(1), CSS.em(1)), CSS.px(0)),
+      new CSSRotate(new CSSMathSum(CSS.deg(90), CSS.turn(1))),
+      new CSSPerspective(new CSSMathSum(CSS.px(1), CSS.em(1))),
+      new CSSSkew(new CSSMathProduct(CSS.deg(90), 2), new CSSMathProduct(CSS.turn(1), 2)),
+      new CSSScale(
+        new CSSMathProduct(CSS.number(1), CSS.number(2)),
+        new CSSMathSum(CSS.number(1), CSS.number(1)),
+        new CSSMathProduct(CSS.number(3))
+      ),
+    ]),
+    cssText: 'translate(calc(1px + 1em), 0px) rotate(calc(90deg + 1turn)) perspective(calc(1px + 1em)) skew(calc(90deg * 2), calc(1turn * 2)) scale3d(calc(1 * 2), calc(1 + 1), calc(3))',
+    desc: 'CSSTransformValue containing CSSMathValues'
+  },
+  {
+    value: new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6])),
+    cssText: 'matrix(1, 2, 3, 4, 5, 6)',
+    desc: 'CSSMatrixComponent with 6 elements'
+  },
+  {
+    value: new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])),
+    cssText: 'matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)',
+    desc: 'CSSMatrixComponent with 16 elements'
+  },
+];
+
+for (const {value, cssText, desc} of gTestCases) {
+  test(() => {
+    assert_equals(value.toString(), cssText);
+  }, desc + ' serializes correctly');
+}
+
+test(() => {
+  let result = new CSSTransformValue([
+    new CSSTranslate(CSS.px(1), CSS.px(2), CSS.px(3)),
+    new CSSRotate(1, 2, 3, CSS.deg(90)),
+    new CSSScale(1, 2, 3),
+  ]);
+
+  for (const transform of result) {
+    transform.is2D = true;
+  }
+
+  assert_equals(result.toString(), 'translate(1px, 2px) rotate(90deg) scale(1, 2)');
+}, 'CSSTransformValue with updated is2D serializes as 2D transforms');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssUnitValue.tentative.html b/css/css-typed-om/stylevalue-serialization/cssUnitValue.tentative.html
new file mode 100644
index 0000000..fc798dd
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssUnitValue.tentative.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>IDL-constructed CSSUnitValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#numericvalue-serialization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(() => {
+  assert_equals(new CSSUnitValue(3.14, 'px').toString(), '3.14px');
+  assert_equals(CSS.px(3.14).toString(), '3.14px');
+}, 'CSSUnitValue with length unit constructed from IDL serializes correctly');
+
+test(() => {
+  assert_equals(new CSSUnitValue(3.14, 'percent').toString(), '3.14%');
+  assert_equals(CSS.percent(3.14).toString(), '3.14%');
+}, 'CSSUnitValue with unit "percent" constructed from IDL serializes correctly');
+
+test(() => {
+  assert_equals(new CSSUnitValue(3.14, 'number').toString(), '3.14');
+  assert_equals(CSS.number(3.14).toString(), '3.14');
+}, 'CSSUnitValue with unit "number" constructed from IDL serializes correctly');
+
+test(() => {
+  assert_equals(new CSSUnitValue(3, 'number').toString(), '3');
+}, 'CSSUnitValue with integer values constructed from IDL serializes correctly');
+
+test(() => {
+  let result = CSSStyleValue.parse('width', '1px');
+  result.value = 3.14;
+  assert_equals(result.toString(), '3.14px');
+}, 'CSSKeywordValue from DOMString modified by "value" setter serializes correctly');
+
+test(t => {
+  let result = createInlineStyleMap(t, 'width: 1px').get('width');
+  result.value = 3.14;
+  assert_equals(result.toString(), '3.14px');
+}, 'CSSKeywordValue from CSSOM modified by "value" setter serializes correctly');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html b/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
new file mode 100644
index 0000000..6fc2e02
--- /dev/null
+++ b/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>IDL-constructed CSSUnparsedValue serialization tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#unparsedvalue-serialization">
+<meta name="assert" content="Test CSSUnparsedValue are serialized by concatenating its contents" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="tag">
+<script>
+'use strict';
+
+test(() => {
+  assert_equals(new CSSUnparsedValue(['lem', 'on', 'ade']).toString(), 'lemonade');
+}, 'CSSUnparsedValue containing strings serializes to its concatenated contents');
+
+test(() => {
+  assert_equals(new CSSUnparsedValue([
+      new CSSVariableReferenceValue('--A',
+        new CSSUnparsedValue([new CSSVariableReferenceValue('--B')])),
+      new CSSVariableReferenceValue('--C')]).toString(),
+    'var(--A,var(--B))var(--C)');
+}, 'CSSUnparsedValue containing variable references serializes its ' +
+   'concatenated contents');
+
+test(() => {
+  assert_equals(new CSSUnparsedValue(['foo', 'bar ',
+      new CSSVariableReferenceValue('--A',
+        new CSSUnparsedValue(['baz ',
+          new CSSVariableReferenceValue('--B'), 'lemon'])),
+      new CSSVariableReferenceValue('--C',
+        new CSSUnparsedValue(['ade']))]).toString(),
+    'foobar var(--A,baz var(--B)lemon)var(--C,ade)');
+}, 'CSSUnparsedValue containing mix of strings and variable references ' +
+   'serializes to its concatenated contents');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssKeywordValue-invalid.html b/css/css-typed-om/stylevalue-subclasses/cssKeywordValue-invalid.html
new file mode 100644
index 0000000..b923f38
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssKeywordValue-invalid.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSKeywordValue Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#csskeywordvalue">
+<meta name="assert" content="Test CSSKeywordValue constructor and attributes error handling" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSKeywordValue(''));
+}, 'Constructing CSSKeywordValue with an empty string throws a TypeError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssKeywordValue-value.html b/css/css-typed-om/stylevalue-subclasses/cssKeywordValue-value.html
new file mode 100644
index 0000000..1af557f
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssKeywordValue-value.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSKeywordValue.value</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-csskeywordvalue-value">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+const gTestArguments = [
+  { keyword: 'initial', description: 'a CSS wide keyword' },
+  { keyword: 'auto', description: 'a CSS keyword' },
+  { keyword: 'lemon', description: 'an unsupported CSS keyword' },
+  { keyword: '3! + 4@', description: 'a string containing multiple tokens' },
+  { keyword: '☺', description: 'a unicode string' },
+];
+
+for (const args of gTestArguments) {
+  test(() => {
+    const result = new CSSKeywordValue(args.keyword);
+    assert_not_equals(result, null, 'a CSSKeywordValue is created');
+    assert_equals(result.value, args.keyword, 'value reflects new value');
+  }, `CSSKeywordValue.value can be updated to ${args.description}`);
+}
+
+test(() => {
+  let result = new CSSKeywordValue('lemon');
+  assert_throws(new TypeError(), () => result.value = '');
+  assert_equals(result.value, 'lemon', 'value does not change');
+}, 'Updating CSSKeywordValue.value with an empty string throws a TypeError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssKeywordValue.html b/css/css-typed-om/stylevalue-subclasses/cssKeywordValue.html
new file mode 100644
index 0000000..1ebbf8d
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssKeywordValue.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSKeywordValue Constructor</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-csskeywordvalue-csskeywordvalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+const gTestArguments = [
+  { keyword: 'initial', description: 'a CSS wide keyword' },
+  { keyword: 'auto', description: 'a CSS keyword' },
+  { keyword: 'lemon', description: 'an unsupported CSS keyword' },
+  { keyword: '3! + 4@', description: 'a string containing multiple tokens' },
+  { keyword: '☺', description: 'a unicode string' },
+];
+
+for (const args of gTestArguments) {
+  test(() => {
+    const result = new CSSKeywordValue(args.keyword);
+    assert_not_equals(result, null, 'a CSSKeywordValue is created');
+    assert_equals(result.value, args.keyword,
+                  'value is same as given by constructor');
+  }, `CSSKeywordValue can be constructed from ${args.description}`);
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssMatrixComponent.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssMatrixComponent.tentative.html
new file mode 100644
index 0000000..2f1fcc1
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssMatrixComponent.tentative.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSMatrixComponent tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssmatrixcomponent-cssmatrixcomponent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script src="../resources/comparisons.js"></script>
+<script>
+'use strict';
+
+const EPSILON = 1e-6;
+
+const gTestMatrices = [
+  {
+    matrix: new DOMMatrix([0, -1, 3.14, -3.14, 10, 0]),
+    desc: 'a 2D matrix'
+  },
+  {
+    matrix: new DOMMatrix([1.1, 1.2, -13, -1.4, 2, 0, -2, 4, 3.1, 3, 3, 3.4, -4.1, 42, 43, 4.4]),
+    desc: 'a 3D matrix'
+  },
+];
+
+for (const {matrix, desc} of gTestMatrices) {
+  test(() => {
+    const component = new CSSMatrixComponent(matrix, { is2D: true });
+    assert_matrix_approx_equals(component.matrix, matrix, EPSILON);
+    assert_true(component.is2D);
+  }, 'CSSMatrixComponent can be constructed from ' + desc + ' with is2D true');
+
+  test(() => {
+    const component = new CSSMatrixComponent(matrix, { is2D: false });
+    assert_matrix_approx_equals(component.matrix, matrix, EPSILON);
+    assert_false(component.is2D);
+  }, 'CSSMatrixComponent can be constructed from ' + desc + ' with is2D false');
+
+  test(() => {
+    const component = new CSSMatrixComponent(matrix);
+    assert_matrix_approx_equals(component.matrix, matrix, EPSILON);
+    assert_equals(component.is2D, matrix.is2D);
+  }, 'CSSMatrixComponent can be constructed from ' + desc + ' without a CSSMatrixComponentOptions');
+
+  test(() => {
+    const component = new CSSMatrixComponent(matrix, null);
+    assert_matrix_approx_equals(component.matrix, matrix, EPSILON);
+    assert_equals(component.is2D, matrix.is2D);
+  }, 'CSSMatrixComponent can be constructed from ' + desc + ' with an invalid CSSMatrixComponentOptions');
+
+  test(() => {
+    let component = new CSSMatrixComponent(new DOMMatrixReadOnly([
+      0, 0, 0, 0, 0, 0
+    ]));
+
+    component.matrix = matrix;
+    assert_matrix_approx_equals(component.matrix, matrix, EPSILON);
+  }, 'CSSMatrixComponent.matrix can be updated to ' + desc);
+}
+
+</script>
+
diff --git a/css/css-typed-om/stylevalue-subclasses/cssPerspective.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssPerspective.tentative.html
new file mode 100644
index 0000000..4fbd7ca
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssPerspective.tentative.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSPerspective tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssperspective">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { length: 'auto', desc: 'a keyword'},
+  { length: 3.14, desc: 'a double'},
+  { length: 0, desc: 'a unitless zero'},
+  { length: '10px', desc: 'a string length'},
+  { length: CSS.number(10), desc: 'a number CSSUnitValue'},
+  { length: CSS.s(10), desc: 'a time dimension CSSUnitValue'},
+  { length: new CSSMathSum(CSS.deg(1)), desc: 'a CSSMathValue of angle type' },
+];
+
+for (const {length, desc} of gInvalidTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSPerspective(length));
+  }, 'Constructing a CSSPerspective with ' + desc + ' throws a TypeError');
+}
+
+for (const {length, desc} of gInvalidTestCases) {
+  test(() => {
+    let perspective = new CSSPerspective(CSS.px(0));
+    assert_throws(new TypeError(), () => perspective.length = length);
+    assert_style_value_equals(perspective.length, CSS.px(0));
+  }, 'Updating CSSPerspective.length with ' + desc + ' throws a TypeError');
+}
+
+const gValidTestCases = [
+  { length: CSS.px(-3.14), desc: 'a length CSSUnitValue' },
+  { length: new CSSMathSum(CSS.px(1)), desc: 'a CSSMathValue of length type' },
+];
+
+for (const {length, desc} of gValidTestCases) {
+  test(() => {
+    const perspective = new CSSPerspective(length);
+    assert_equals(perspective.length, length);
+    assert_false(perspective.is2D);
+  }, 'CSSPerspective can be constructed from ' + desc);
+
+  test(() => {
+    let perspective = new CSSPerspective(CSS.px(0), CSS.px(0));
+    perspective.length = length;
+    assert_style_value_equals(perspective.length, length);
+  }, 'CSSPerspective.length can be updated to ' + desc);
+}
+
+test(() => {
+  let perspective = new CSSPerspective(CSS.px(0), CSS.px(0));
+  perspective.is2D = true;
+  assert_false(perspective.is2D);
+}, 'Modifying CSSPerspective.is2D is a no-op');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssPositionValue-invalid.html b/css/css-typed-om/stylevalue-subclasses/cssPositionValue-invalid.html
new file mode 100644
index 0000000..d63c0c7
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssPositionValue-invalid.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSPositionValue Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#positionvalue-objects">
+<meta name="assert" content="Test CSSPositionValue constructor and attributes error handling" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+const gTestArguments = [
+  {
+    description: 'a keyword',
+    value: 'auto',
+  },
+  {
+    description: 'a double',
+    value: 3.14,
+  },
+  {
+    description: 'a unitless zero',
+    value: 0,
+  },
+  {
+    description: 'a string length',
+    value: '10px',
+  },
+  {
+    description: 'a number CSSUnitValue',
+    value: CSS.number(10),
+  },
+  {
+    description: 'a time CSSUnitValue',
+    value: CSS.s(10),
+  },
+  {
+    description: 'a CSSMathValue of angle type',
+    value: new CSSMathSum(CSS.deg(1)),
+  },
+];
+
+for (const {value, description} of gTestArguments) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSPositionValue(value, CSS.px(0)));
+    assert_throws(new TypeError(), () => new CSSPositionValue(CSS.px(0), value));
+  }, `Constructing a CSSPositionValue with ${description} throws a TypeError`);
+
+  test(() => {
+    let position = new CSSPositionValue(CSS.px(0), CSS.px(0));
+    assert_throws(new TypeError(), () => position.x = value);
+
+    assert_equals(position.x.value, 0,
+        'X member should not have changed');
+  }, `Updating CSSPositionValue.x with ${description} throws a TypeError`);
+
+  test(() => {
+    let position = new CSSPositionValue(CSS.px(0), CSS.px(0));
+    assert_throws(new TypeError(), () => position.y = value);
+
+    assert_equals(position.y.value, 0,
+        'Y member should not have changed');
+  }, `Updating CSSPositionValue.y with ${description} throws a TypeError`);
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssPositionValue.html b/css/css-typed-om/stylevalue-subclasses/cssPositionValue.html
new file mode 100644
index 0000000..a780a86
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssPositionValue.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSPositionValue</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#positionvalue-objects">
+<meta name="assert" content="Test CSSPositionValue constructor and attributes" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+const gTestArguments = [
+  {
+    description: 'a length CSSUnitValue' ,
+    value: CSS.px(1),
+  },
+  {
+    description: 'a percent CSSUnitValue' ,
+    value: CSS.percent(10),
+  },
+  {
+    description: 'a CSSMathValue of length type' ,
+    value: new CSSMathSum(CSS.px(1)),
+  },
+  {
+    description: 'a CSSMathValue of percent type' ,
+    value: new CSSMathSum(CSS.percent(1)),
+  },
+];
+
+for (const {value, description} of gTestArguments) {
+  test(() => {
+    const position = new CSSPositionValue(value, value);
+    assert_style_value_equals(position.x, value,
+        'X member should be same as passed in constructor');
+    assert_style_value_equals(position.y, value,
+        'Y member should be same as passed in constructor');
+  }, 'CSSPositionValue can be constructed from ' + description);
+}
+
+for (const {value, description} of gTestArguments) {
+  test(() => {
+    let position = new CSSPositionValue(CSS.px(0), CSS.px(0));
+    position.x = value;
+
+    assert_style_value_equals(position.x, value,
+        'X member should be updated to new value');
+  }, 'CSSPositionValue.x can be updated to ' + description);
+
+  test(() => {
+    let position = new CSSPositionValue(CSS.px(0), CSS.px(0));
+    position.y = value;
+
+    assert_style_value_equals(position.y, value,
+        'Y member should be updated to new value');
+  }, 'CSSPositionValue.y can be updated to ' + description);
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssRotate.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssRotate.tentative.html
new file mode 100644
index 0000000..17490dc
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssRotate.tentative.html
@@ -0,0 +1,126 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSRotate tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssrotate">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidAngleTestCases = [
+  { angle: CSS.number(10), desc: 'a CSSUnitValue with type other than angle'},
+  { angle: new CSSMathSum(CSS.s(10)), desc: 'a CSSMathValue that doesn\'t match <angle>'},
+];
+
+const gInvalidCoordTestCases = [
+  { coord: CSS.px(10), desc: 'a CSSUnitValue with type other than number'},
+  { coord: new CSSMathSum(CSS.px(10)), desc: 'a CSSMathValue that doesn\'t match <number>'},
+];
+
+for (const {angle, desc} of gInvalidAngleTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSRotate(angle));
+    assert_throws(new TypeError(), () => new CSSRotate(0, 0, 0, angle));
+  }, 'Constructing a CSSRotate with ' + desc + ' for the angle throws a TypeError');
+}
+
+for (const {coord, desc} of gInvalidCoordTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSRotate(coord, 0, 0, CSS.deg(0)));
+    assert_throws(new TypeError(), () => new CSSRotate(0, coord, 0, CSS.deg(0)));
+    assert_throws(new TypeError(), () => new CSSRotate(0, 0, coord, CSS.deg(0)));
+  }, 'Constructing a CSSRotate with ' + desc + ' for the coordinates throws a TypeError');
+}
+
+for (const attr of ['x', 'y', 'z']) {
+  for (const {value, desc} of gInvalidCoordTestCases) {
+    test(() => {
+      let result = new CSSRotate(0, 0, 0, CSS.deg(0));
+      assert_throws(new TypeError(), () => result[attr] = value);
+      assert_style_value_equals(result[attr], CSS.number(0));
+    }, 'Updating CSSRotate.' + attr + ' to ' + desc + ' throws a TypeError');
+  }
+}
+
+for (const {angle, desc} of gInvalidAngleTestCases) {
+  test(() => {
+    let result = new CSSRotate(CSS.deg(0));
+    assert_throws(new TypeError(), () => result.angle = angle);
+    assert_style_value_equals(result.angle, CSS.deg(0));
+  }, 'Updating CSSRotate.angle to ' + desc + ' throws a TypeError');
+}
+
+test(() => {
+  const result = new CSSRotate(CSS.deg(3.14));
+  assert_style_value_equals(result.x, CSS.number(0));
+  assert_style_value_equals(result.y, CSS.number(0));
+  assert_style_value_equals(result.z, CSS.number(1));
+  assert_style_value_equals(result.angle, CSS.deg(3.14));
+  assert_true(result.is2D);
+}, 'CSSRotate can be constructed from a single angle');
+
+test(() => {
+  const result = new CSSRotate(-3.14, CSS.number(3.14), 3.14, CSS.deg(3.14));
+  assert_style_value_equals(result.x, CSS.number(-3.14));
+  assert_style_value_equals(result.y, CSS.number(3.14));
+  assert_style_value_equals(result.z, CSS.number(3.14));
+  assert_style_value_equals(result.angle, CSS.deg(3.14));
+  assert_false(result.is2D);
+}, 'CSSRotate can be constructed from numberish coordinates');
+
+test(() => {
+  const result = new CSSRotate(
+    new CSSMathSum(-3.14),
+    new CSSMathProduct(3.14),
+    new CSSMathNegate(-3.14),
+    new CSSMathMax(CSS.deg(3.14))
+  );
+  assert_style_value_equals(result.x, new CSSMathSum(-3.14));
+  assert_style_value_equals(result.y, new CSSMathProduct(3.14));
+  assert_style_value_equals(result.z, new CSSMathNegate(-3.14));
+  assert_style_value_equals(result.angle, new CSSMathMax(CSS.deg(3.14)));
+  assert_false(result.is2D);
+}, 'CSSRotate can be constructed from CSSMathValues');
+
+for (const attr of ['x', 'y', 'z']) {
+  test(() => {
+    let result = new CSSRotate(0, 0, 0, CSS.deg(0));
+    result[attr] = 3.14;
+    assert_style_value_equals(result[attr], CSS.number(3.14));
+  }, 'CSSRotate.' + attr + ' can be updated to a double');
+
+  test(() => {
+    let result = new CSSRotate(0, 0, 0, CSS.deg(0));
+    result[attr] = CSS.number(3.14);
+    assert_style_value_equals(result[attr], CSS.number(3.14));
+  }, 'CSSRotate.' + attr + ' can be updated to a number CSSUnitValue');
+
+  test(() => {
+    let result = new CSSRotate(0, 0, 0, CSS.deg(0));
+    result[attr] = new CSSMathSum(3.14);
+    assert_style_value_equals(result[attr], new CSSMathSum(3.14));
+  }, 'CSSRotate.' + attr + ' can be updated to a CSSMathValue matching <number>');
+}
+
+test(() => {
+  let rotation = new CSSRotate(CSS.deg(0));
+  rotation.angle = CSS.deg(6);
+  assert_style_value_equals(rotation.angle, CSS.deg(6));
+}, 'CSSRotate.angle can be updated to a degree CSSUnitValue');
+
+test(() => {
+  let rotation = new CSSRotate(CSS.deg(0));
+  rotation.angle = new CSSMathSum(CSS.deg(3.14));
+  assert_style_value_equals(rotation.angle, new CSSMathSum(CSS.deg(3.14)));
+}, 'CSSRotate.angle can be updated to a CSSMathValue matching <angle>');
+
+test(() => {
+  let rotation = new CSSRotate(CSS.deg(0));
+  rotation.is2D = true;
+  assert_true(rotation.is2D);
+  rotation.is2D = false;
+  assert_false(rotation.is2D);
+}, 'Modifying CSSRotate.is2D can be updated to true or false');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssScale.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssScale.tentative.html
new file mode 100644
index 0000000..d52a8f1
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssScale.tentative.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSScale tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssscale">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidCoordTestCases = [
+  { coord: CSS.deg(1), desc: 'an angle CSSUnitValue'},
+  { coord: new CSSMathSum(CSS.px(1)), desc: 'a CSSMathValue that doesn\'t match <number>'},
+];
+
+for (const {coord, desc} of gInvalidCoordTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSScale(coord, 0));
+    assert_throws(new TypeError(), () => new CSSScale(0, coord));
+    assert_throws(new TypeError(), () => new CSSScale(coord, 0, 0));
+    assert_throws(new TypeError(), () => new CSSScale(0, coord, 0));
+    assert_throws(new TypeError(), () => new CSSScale(0, 0, coord));
+  }, 'Constructing a CSSScale with ' + desc + ' for the coordinates throws a TypeError');
+}
+
+for (const attr of ['x', 'y', 'z']) {
+  for (const {value, desc} of gInvalidCoordTestCases) {
+    test(() => {
+      let result = new CSSScale(0, 0, 0);
+      assert_throws(new TypeError(), () => result[attr] = value);
+      assert_style_value_equals(result[attr], CSS.number(0));
+    }, 'Updating CSSScale.' + attr + ' to ' + desc + ' throws a TypeError');
+  }
+}
+
+test(() => {
+  const result = new CSSScale(-3.14, CSS.number(3.14));
+  assert_style_value_equals(result.x, CSS.number(-3.14));
+  assert_style_value_equals(result.y, CSS.number(3.14));
+  assert_style_value_equals(result.z, CSS.number(1));
+  assert_true(result.is2D);
+}, 'CSSScale can be constructed from two number coordinates');
+
+test(() => {
+  const result = new CSSScale(-3.14, CSS.number(3.14), -3.14);
+  assert_style_value_equals(result.x, CSS.number(-3.14));
+  assert_style_value_equals(result.y, CSS.number(3.14));
+  assert_style_value_equals(result.z, CSS.number(-3.14));
+  assert_false(result.is2D);
+}, 'CSSScale can be constructed from three number coordinates');
+
+test(() => {
+  const result = new CSSScale(new CSSMathSum(-3.14), new CSSMathMax(3.14), new CSSMathNegate(-3.14));
+  assert_style_value_equals(result.x, new CSSMathSum(-3.14));
+  assert_style_value_equals(result.y, new CSSMathMax(3.14));
+  assert_style_value_equals(result.z, new CSSMathNegate(-3.14));
+  assert_false(result.is2D);
+}, 'CSSScale can be constructed from CSSMathValue coordinates');
+
+for (const attr of ['x', 'y', 'z']) {
+  test(() => {
+    let result = new CSSScale(0, 0, 0);
+    result[attr] = 3.14;
+    assert_style_value_equals(result[attr], CSS.number(3.14));
+  }, 'CSSScale.' + attr + ' can be updated to a number');
+
+  test(() => {
+    let result = new CSSScale(0, 0, 0);
+    result[attr] = CSS.number(3.14);
+    assert_style_value_equals(result[attr], CSS.number(3.14));
+  }, 'CSSScale.' + attr + ' can be updated to a numberish');
+
+  test(() => {
+    let result = new CSSScale(0, 0, 0);
+    result[attr] = new CSSMathSum(3.14);
+    assert_style_value_equals(result[attr], new CSSMathSum(3.14));
+  }, 'CSSScale.' + attr + ' can be updated to a CSSMathValue');
+}
+
+test(() => {
+  let rotation = new CSSScale(0, 0, 0);
+  rotation.is2D = true;
+  assert_true(rotation.is2D);
+  rotation.is2D = false;
+  assert_false(rotation.is2D);
+}, 'Modifying CSSScale.is2D can be updated to true or false');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssSkew.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssSkew.tentative.html
new file mode 100644
index 0000000..2b22d9b
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssSkew.tentative.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSSkew tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssskew">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { value: 'auto', desc: 'a keyword'},
+  { value: 3.14, desc: 'a double'},
+  { value: 0, desc: 'a unitless zero'},
+  { value: '10deg', desc: 'a string angle'},
+  { value: CSS.number(10), desc: 'a number CSSUnitValue'},
+  { value: CSS.s(10), desc: 'a time dimension CSSUnitValue'},
+  { value: new CSSMathSum(CSS.px(1)), desc: 'a CSSMathValue of length type' },
+];
+
+for (const {value, desc} of gInvalidTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSSkew(value, CSS.deg(0)));
+    assert_throws(new TypeError(), () => new CSSSkew(CSS.deg(0), value));
+  }, 'Constructing a CSSSkew with ' + desc + ' throws a TypeError');
+}
+
+for (const attr of ['ax', 'ay']) {
+  for (const {value, desc} of gInvalidTestCases) {
+    test(() => {
+      let skew = new CSSSkew(CSS.deg(0), CSS.deg(0));
+      assert_throws(new TypeError(), () => skew[attr] = value);
+      assert_style_value_equals(skew[attr], CSS.deg(0));
+    }, 'Updating CSSSkew.' + attr + ' with ' + desc + ' throws a TypeError');
+  }
+}
+
+const gValidTestCases = [
+  { value: CSS.deg(-3.14), desc: 'an angle CSSUnitValue' },
+  { value: new CSSMathSum(CSS.deg(1)), desc: 'a CSSMathValue of angle type' },
+];
+
+for (const {value: ax, desc: axDesc} of gValidTestCases) {
+  for (const {value: ay, desc: ayDesc} of gValidTestCases) {
+    test(() => {
+      const skew = new CSSSkew(ax, ay);
+      assert_equals(skew.ax, ax);
+      assert_equals(skew.ay, ay);
+      assert_true(skew.is2D);
+    }, 'CSSSkew can be constructed from ' + axDesc + ' and ' + ayDesc);
+  }
+}
+
+for (const attr of ['ax', 'ay']) {
+  for (const {value, desc} of gValidTestCases) {
+    test(() => {
+      let skew = new CSSSkew(CSS.deg(0), CSS.deg(0));
+      skew[attr] = value;
+      assert_style_value_equals(skew[attr], value);
+    }, 'CSSSkew.' + attr + ' can be updated to ' + desc);
+  }
+}
+
+test(() => {
+  let skew = new CSSSkew(CSS.deg(0), CSS.deg(0));
+  skew.is2D = false;
+  assert_true(skew.is2D);
+}, 'Modifying CSSSkew.is2D is a no-op');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssSkewX.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssSkewX.tentative.html
new file mode 100644
index 0000000..1e61414
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssSkewX.tentative.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSSkewX tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssskewx">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { value: 'auto', desc: 'a keyword'},
+  { value: 3.14, desc: 'a double'},
+  { value: 0, desc: 'a unitless zero'},
+  { value: '10deg', desc: 'a string angle'},
+  { value: CSS.number(10), desc: 'a number CSSUnitValue'},
+  { value: CSS.s(10), desc: 'a time dimension CSSUnitValue'},
+  { value: new CSSMathSum(CSS.px(1)), desc: 'a CSSMathValue of length type' },
+];
+
+for (const {value, desc} of gInvalidTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSSkewX(value));
+  }, 'Constructing a CSSSkewX with ' + desc + ' throws a TypeError');
+}
+
+for (const {value, desc} of gInvalidTestCases) {
+  test(() => {
+    let skewX = new CSSSkewX(CSS.deg(0));
+    assert_throws(new TypeError(), () => skewX.ax = value);
+    assert_style_value_equals(skewX.ax, CSS.deg(0));
+  }, 'Updating CSSSkewX.ax with ' + desc + ' throws a TypeError');
+}
+
+const gValidTestCases = [
+  { value: CSS.deg(-3.14), desc: 'an angle CSSUnitValue' },
+  { value: new CSSMathSum(CSS.deg(1)), desc: 'a CSSMathValue of angle type' },
+];
+
+for (const {value: ax, desc: axDesc} of gValidTestCases) {
+  test(() => {
+    const skewX = new CSSSkewX(ax);
+    assert_equals(skewX.ax, ax);
+    assert_true(skewX.is2D);
+  }, 'CSSSkewX can be constructed from ' + axDesc);
+}
+
+for (const {value, desc} of gValidTestCases) {
+  test(() => {
+    let skewX = new CSSSkewX(CSS.deg(0));
+    skewX.ax = value;
+    assert_style_value_equals(skewX.ax, value);
+  }, 'CSSSkew.ax can be updated to ' + desc);
+}
+
+test(() => {
+  let skewX = new CSSSkewX(CSS.deg(0));
+  skewX.is2D = false;
+  assert_true(skewX.is2D);
+}, 'Modifying skewX.is2D is a no-op');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssSkewY.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssSkewY.tentative.html
new file mode 100644
index 0000000..643d2f6
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssSkewY.tentative.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSSkewY tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssskewy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { value: 'auto', desc: 'a keyword'},
+  { value: 3.14, desc: 'a double'},
+  { value: 0, desc: 'a unitless zero'},
+  { value: '10deg', desc: 'a string angle'},
+  { value: CSS.number(10), desc: 'a number CSSUnitValue'},
+  { value: CSS.s(10), desc: 'a time dimension CSSUnitValue'},
+  { value: new CSSMathSum(CSS.px(1)), desc: 'a CSSMathValue of length type' },
+];
+
+for (const {value, desc} of gInvalidTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSSkewY(value));
+  }, 'Constructing a CSSSkewY with ' + desc + ' throws a TypeError');
+}
+
+for (const {value, desc} of gInvalidTestCases) {
+  test(() => {
+    let skewY = new CSSSkewY(CSS.deg(0));
+    assert_throws(new TypeError(), () => skewY.ay = value);
+    assert_style_value_equals(skewY.ay, CSS.deg(0));
+  }, 'Updating CSSSkewY.ay with ' + desc + ' throws a TypeError');
+}
+
+const gValidTestCases = [
+  { value: CSS.deg(-3.14), desc: 'an angle CSSUnitValue' },
+  { value: new CSSMathSum(CSS.deg(1)), desc: 'a CSSMathValue of angle type' },
+];
+
+for (const {value: ay, desc: ayDesc} of gValidTestCases) {
+  test(() => {
+    const skewY = new CSSSkewY(ay);
+    assert_equals(skewY.ay, ay);
+    assert_true(skewY.is2D);
+  }, 'CSSSkewY can be constructed from ' + ayDesc);
+}
+
+for (const {value, desc} of gValidTestCases) {
+  test(() => {
+    let skewY = new CSSSkewY(CSS.deg(0));
+    skewY.ay = value;
+    assert_style_value_equals(skewY.ay, value);
+  }, 'CSSSkewY.ay can be updated to ' + desc);
+}
+
+test(() => {
+  let skewY = new CSSSkewY(CSS.deg(0));
+  skewY.is2D = false;
+  assert_true(skewY.is2D);
+}, 'Modifying skewY.is2D is a no-op');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.html b/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.html
new file mode 100644
index 0000000..67b63a6
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix-relative-units.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSTransformComponent.toMatrix with Relative Units</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformcomponent-tomatrix">
+<meta name="assert" content="Test CSSTransformComponent.toMatrix throws when given relative units" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new TypeError(), () => {
+    return new CSSTranslate(
+      new CSSUnitValue(1, 'px'),
+      new CSSUnitValue(1, 'em')
+    ).toMatrix();
+  });
+}, 'CSSTranslate.toMatrix() containing relative units throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => {
+    return new CSSPerspective(new CSSUnitValue(1, 'em')).toMatrix();
+  });
+}, 'CSSPerspective.toMatrix() containing relative units throws TypeError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix.html b/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix.html
new file mode 100644
index 0000000..14d819b
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssTransformComponent-toMatrix.html
@@ -0,0 +1,86 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSTransformComponent.toMatrix tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformcomponent-tomatrix">
+<meta name="assert" content="Test CSSTransformComponent.toMatrix for each type of component" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gEpsilon = 1e-6;
+
+test(() => {
+  const component = new CSSTranslate(
+    new CSSUnitValue(1, 'px'),
+    new CSSUnitValue(2, 'px'),
+    new CSSUnitValue(3, 'px')
+  );
+  const expectedMatrix = (new DOMMatrixReadOnly()).translate(1, 2, 3);
+  assert_matrix_approx_equals(component.toMatrix(), expectedMatrix, 1e-8);
+}, 'CSSTranslate.toMatrix() returns correct matrix');
+
+test(() => {
+  const component = new CSSRotate(
+    new CSSUnitValue(1, 'number'),
+    new CSSUnitValue(2, 'number'),
+    new CSSUnitValue(3, 'number'),
+    new CSSUnitValue(90, 'deg')
+  );
+  const expectedMatrix = (new DOMMatrixReadOnly()).rotateAxisAngle(1, 2, 3, 90);
+  assert_matrix_approx_equals(component.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSRotate.toMatrix() returns correct matrix');
+
+test(() => {
+  const component = new CSSScale(1, 2, 3);
+  const expectedMatrix = (new DOMMatrixReadOnly()).scale(1, 2, 3);
+  assert_matrix_approx_equals(component.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSScale.toMatrix() returns correct matrix');
+
+test(() => {
+  const alpha = 10, beta = 20;
+  const component = new CSSSkew(
+    new CSSUnitValue(alpha, 'rad'),
+    new CSSUnitValue(beta, 'rad')
+  );
+  const expectedMatrix = new DOMMatrixReadOnly(
+        [1, Math.tan(beta), 0, 0, Math.tan(alpha), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
+  assert_matrix_approx_equals(component.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSSkew.toMatrix() returns correct matrix');
+
+test(() => {
+  const component = new CSSSkewX(
+    new CSSUnitValue(10, 'rad'),
+  );
+  const expectedMatrix = (new DOMMatrixReadOnly()).skewX(10);
+  assert_matrix_approx_equals(component.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSSkewX.toMatrix() returns correct matrix');
+
+test(() => {
+  const component = new CSSSkewY(
+    new CSSUnitValue(10, 'rad'),
+  );
+  const expectedMatrix = (new DOMMatrixReadOnly()).skewY(10);
+  assert_matrix_approx_equals(component.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSSkewY.toMatrix() returns correct matrix');
+
+test(() => {
+  const length = 10;
+  const component = new CSSPerspective(new CSSUnitValue(length, 'px'));
+  const expectedMatrix = new DOMMatrixReadOnly(
+        [1, 0, 0, 0,
+         0, 1, 0, 0,
+         0, 0, 1, -1/length,
+         0, 0, 0, 1]);
+  assert_matrix_approx_equals(component.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSPerspective.toMatrix() returns correct matrix');
+
+test(() => {
+  const matrix = new DOMMatrixReadOnly(
+          [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  const component = new CSSMatrixComponent(matrix);
+  assert_matrix_approx_equals(component.toMatrix(), matrix, gEpsilon);
+}, 'CSSMatrixComponent.toMatrix() returns correct matrix');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssTransformValue-toMatrix.html b/css/css-typed-om/stylevalue-subclasses/cssTransformValue-toMatrix.html
new file mode 100644
index 0000000..c0e0bbc
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssTransformValue-toMatrix.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSTransformValue.toMatrix</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-csstransformcomponent-tomatrix">
+<meta name="assert" content="Test CSSTransformValue.toMatrix multiplies component matrices" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gEpsilon = 1e-6;
+
+test(() => {
+  const transformMatrix = new DOMMatrixReadOnly([1, 1, 1, 1, 1, 1]);
+  const transformArray = [
+    new CSSScale(2, 2),
+    new CSSMatrixComponent(transformMatrix),
+    new CSSScale(5, 6)
+  ];
+
+  let expectedMatrix = new DOMMatrix();
+  expectedMatrix.scaleSelf(2, 2);
+  expectedMatrix.multiplySelf(transformMatrix);
+  expectedMatrix.scaleSelf(5, 6);
+
+  const transform = new CSSTransformValue(transformArray);
+  assert_matrix_approx_equals(transform.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSTransformValue.toMatrix() multiplies its component matrices');
+
+test(() => {
+  const transformMatrix = new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  let transformArray = [
+    new CSSTranslate(CSS.px(1), CSS.px(1), CSS.px(1)),
+    new CSSRotate(1, 2, 3, CSS.deg(90)),
+    new CSSScale(2, 3, 2),
+    new CSSMatrixComponent(transformMatrix),
+  ];
+
+  transformArray.forEach(transform => transform.is2D = true);
+
+  let expectedMatrix = new DOMMatrix();
+  expectedMatrix.translateSelf(1, 1);
+  expectedMatrix.rotateSelf(90);
+  expectedMatrix.scaleSelf(2, 3);
+  expectedMatrix.multiplySelf(new DOMMatrixReadOnly([1, 2, 5, 6, 13, 14]));
+
+  const transform = new CSSTransformValue(transformArray);
+  assert_matrix_approx_equals(transform.toMatrix(), expectedMatrix, gEpsilon);
+}, 'CSSTransformValue.toMatrix() respects is2D changes in its components');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssTransformValue.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssTransformValue.tentative.html
new file mode 100644
index 0000000..47dc05d
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssTransformValue.tentative.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSTransformValue tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#transformvalue-objects">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script src="../resources/comparisons.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSTransformValue());
+  assert_throws(new TypeError(), () => new CSSTransformValue([]));
+}, 'Constructing a CSSTransformValue with no components throws TypeError');
+
+test(() => {
+  const values = [
+    new CSSScale(1, 1),
+    new CSSTranslate(CSS.px(1), CSS.px(1)),
+    new CSSRotate(CSS.deg(90))
+  ];
+
+  const transform = new CSSTransformValue(values);
+  assert_style_value_array_equals(transform, values);
+}, 'CSSTransformValue can be constructed with multiple transforms');
+
+test(() => {
+  const values = [
+    new CSSScale(1, 1),
+    new CSSTranslate(CSS.px(1), CSS.px(1)),
+    new CSSRotate(CSS.deg(90))
+  ];
+
+  const transform = new CSSTransformValue(values);
+  assert_style_value_array_equals(transform, values);
+
+  values.pop();
+  var new_value = new CSSRotate(CSS.deg(45));
+  values[2] = new_value;
+  transform[2] = new_value;
+  assert_style_value_array_equals(transform, values);
+}, 'CSSTransformValue.set correctly sets the CSSTransformComponent at the given index');
+
+test(() => {
+  const values = [
+    new CSSScale(1, 1),
+    new CSSTranslate(CSS.px(1), CSS.px(1)),
+    new CSSRotate(CSS.deg(90))
+  ];
+
+  const transform = new CSSTransformValue(values);
+  assert_style_value_array_equals(transform, values);
+
+  var new_value = new CSSRotate(CSS.deg(45));
+  values[3] = new_value;
+  transform[3] = new_value;
+  assert_style_value_array_equals(transform, values);
+}, 'Setting a component in CSSTransformValue correctly appends the CSSTransformComponent if index specified is greater than length');
+
+test(() => {
+  const transform = new CSSTransformValue([
+    new CSSScale(1, 1),
+    new CSSTranslate(CSS.px(1), CSS.px(1), CSS.px(1)),
+    new CSSScale(1, 1)
+  ]);
+  assert_equals(transform.is2D, false);
+}, 'CSSTransformValue.is2D is false when given mix of 2D and 3D transforms');
+
+test(() => {
+  const transform = new CSSTransformValue([
+    new CSSScale(1, 1),
+    new CSSTranslate(CSS.px(1), CSS.px(1)),
+    new CSSScale(1, 1)
+  ]);
+  assert_equals(transform.is2D, true);
+}, 'CSSTransformValue.is2D is true when given only 2D transforms');
+
+test(() => {
+  let transform = new CSSTransformValue([new CSSScale(1, 2)]);
+  assert_throws(new TypeError(), () => transform.is2D = false);
+  assert_equals(transform.is2D, true);
+}, 'CSSTransformValue.is2D is readonly');
+
+test(() => {
+  const transformArray = [
+    new CSSScale(2, 2),
+    new CSSMatrixComponent(new DOMMatrixReadOnly([1, 1, 1, 1, 1, 1])),
+    new CSSScale(5, 6)
+  ];
+
+  const transformValue = new CSSTransformValue(transformArray);
+
+  const newTransformArray = [...transformValue];
+  assert_style_value_array_equals([...transformValue], transformArray);
+}, 'Can iterate through CSSTransformValue components');
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssTranslate.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssTranslate.tentative.html
new file mode 100644
index 0000000..7ce281d
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssTranslate.tentative.html
@@ -0,0 +1,110 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSTranslate tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#csstranslate">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidCoordTestCases = [
+  { coord: CSS.deg(1), desc: 'a CSSUnitValue with type other than length or percent'},
+  { coord: new CSSMathSum(CSS.deg(1)), desc: 'a CSSMathValue that doesn\'t match <length-percentage>'},
+];
+
+for (const {coord, desc} of gInvalidCoordTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSTranslate(coord, CSS.px(0), CSS.px(0)));
+    assert_throws(new TypeError(), () => new CSSTranslate(CSS.px(0), coord, CSS.px(0)));
+    assert_throws(new TypeError(), () => new CSSTranslate(CSS.px(0), CSS.px(0), coord));
+  }, 'Constructing a CSSTranslate with ' + desc + ' for the coordinates throws a TypeError');
+}
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSTranslate(CSS.px(0), CSS.px(0), CSS.percent(0)));
+}, 'Constructing a CSSTranslate with a percent for the Z coordinate throws a TypeError');
+
+for (const attr of ['x', 'y', 'z']) {
+  for (const {value, desc} of gInvalidCoordTestCases) {
+    test(() => {
+      let result = new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(0));
+      assert_throws(new TypeError(), () => result[attr] = value);
+      assert_style_value_equals(result[attr], CSS.px(0));
+    }, 'Updating CSSTranslate.' + attr + ' to ' + desc + ' throws a TypeError');
+  }
+}
+
+test(() => {
+  let result = new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(0));
+  assert_throws(new TypeError(), () => result.z = CSS.percent(0));
+  assert_style_value_equals(result.z, CSS.px(0));
+}, 'Updating CSSTranslate.z to a percent throws a TypeError');
+
+test(() => {
+  const result = new CSSTranslate(CSS.px(-3.14), CSS.percent(3.14));
+  assert_style_value_equals(result.x, CSS.px(-3.14));
+  assert_style_value_equals(result.y, CSS.percent(3.14));
+  assert_style_value_equals(result.z, CSS.px(0));
+  assert_true(result.is2D);
+}, 'CSSTranslate can be constructed from two length or percent coordinates');
+
+test(() => {
+  const result = new CSSTranslate(CSS.px(-3.14), CSS.percent(3.14), CSS.px(10));
+  assert_style_value_equals(result.x, CSS.px(-3.14));
+  assert_style_value_equals(result.y, CSS.percent(3.14));
+  assert_style_value_equals(result.z, CSS.px(10));
+  assert_false(result.is2D);
+}, 'CSSTranslate can be constructed from three length or percent coordinates');
+
+test(() => {
+  const result = new CSSTranslate(new CSSMathSum(CSS.px(-3.14)), new CSSMathSum(CSS.percent(3.14)));
+  assert_style_value_equals(result.x, new CSSMathSum(CSS.px(-3.14)));
+  assert_style_value_equals(result.y, new CSSMathSum(CSS.percent(3.14)));
+  assert_style_value_equals(result.z, CSS.px(0));
+  assert_true(result.is2D);
+}, 'CSSTranslate can be constructed from CSSMathValues');
+
+for (const attr of ['x', 'y']) {
+  test(() => {
+    let result = new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(0));
+    result[attr] = CSS.px(3.14);
+    assert_style_value_equals(result[attr], CSS.px(3.14));
+  }, 'CSSTranslate.' + attr + ' can be updated to a length');
+
+  test(() => {
+    let result = new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(0));
+    result[attr] = CSS.percent(3.14);
+    assert_style_value_equals(result[attr], CSS.percent(3.14));
+  }, 'CSSTranslate.' + attr + ' can be updated to a percent');
+
+  test(() => {
+    let result = new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(0));
+    result[attr] = new CSSMathSum(CSS.px(3.14));
+    assert_style_value_equals(result[attr], new CSSMathSum(CSS.px(3.14)));
+    result[attr] = new CSSMathSum(CSS.percent(3.14));
+    assert_style_value_equals(result[attr], new CSSMathSum(CSS.percent(3.14)));
+  }, 'CSSTranslate.' + attr + ' can be updated to a CSSMathValue');
+}
+
+test(() => {
+  let result = new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(0));
+  result.z = CSS.px(3.14);
+  assert_style_value_equals(result.z, CSS.px(3.14));
+}, 'CSSTranslate.z can be updated to a length');
+
+test(() => {
+  let result = new CSSTranslate(CSS.px(0), CSS.px(0), CSS.px(0));
+  result.z = new CSSMathSum(CSS.px(3.14));
+  assert_style_value_equals(result.z, new CSSMathSum(CSS.px(3.14)));
+}, 'CSSTranslate.z can be updated to a CSSMathValue');
+
+test(() => {
+  let result = new CSSTranslate(CSS.px(0), CSS.px(0));
+  result.is2D = true;
+  assert_true(result.is2D);
+  result.is2D = false;
+  assert_false(result.is2D);
+}, 'Modifying CSSTranslate.is2D can be updated to true or false');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-indexed-getter-setter.html b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-indexed-getter-setter.html
new file mode 100644
index 0000000..9f0403f
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-indexed-getter-setter.html
@@ -0,0 +1,54 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSUnparsedValue Indexed Getter and Setter</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssunparsedvalue-__getter__-index-index">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssunparsedvalue-__setter__-index-val-val">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = new CSSUnparsedValue(['foo', 'bar']);
+  assert_equals(result[3], undefined, 'result from out of range index');
+  assert_equals(result[-1], undefined, 'result from negative index');
+}, 'Getting invalid index in CSSUnparsedValue returns undefined');
+
+test(() => {
+  let result = new CSSUnparsedValue([new CSSVariableReferenceValue('--foo')]);
+  result[0] = 'A';
+  assert_equals(result[0], 'A', 'fragment reflects new value');
+}, 'Can update fragment in CSSUnparsedValue to a String');
+
+test(() => {
+  let result = new CSSUnparsedValue(['foo']);
+  result[0] = new CSSVariableReferenceValue('--A');
+  assert_style_value_equals(result[0], new CSSVariableReferenceValue('--A'),
+                            'fragment reflects new value');
+}, 'Can update fragment in CSSUnparsedValue to a CSSVariableReference');
+
+test(() => {
+  let result = new CSSUnparsedValue([]);
+  result[0] = 'foo';
+  assert_equals(result[0], 'foo', 'new fragment is appended');
+}, 'Setting one past the last fragment in a CSSUnparsedValue to a String ' +
+   'appends the new fragment');
+
+test(() => {
+  let result = new CSSUnparsedValue([' ']);
+  result[1] = new CSSVariableReferenceValue('--A');
+  assert_style_value_equals(result[1], new CSSVariableReferenceValue('--A'),
+                            'new fragment is appended');
+}, 'Setting one past the last fragment in a CSSUnparsedValue to a ' +
+   'CSSVariableReferenceValue appends the new fragment');
+
+test(() => {
+  let result = new CSSUnparsedValue(['foo', 'bar']);
+  assert_throws(new RangeError(), () => result[3] = 'foo');
+  assert_equals(result[3], undefined, 'fragment does not change');
+}, 'Setting out of range index in CSSUnparsedValue throws RangeError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-iterable.html b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-iterable.html
new file mode 100644
index 0000000..186ba73
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-iterable.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSUnparsedValue Iterable Declaration</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssunparsedvalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = new CSSUnparsedValue([]);
+  assert_array_equals([...result], [], 'result of iterating');
+}, 'Iterating over an empty CSSUnparsedValue produces nothing');
+
+test(() => {
+  const args = [' ', new CSSVariableReferenceValue('--A')];
+  const result = new CSSUnparsedValue(args);
+  assert_style_value_array_equals([...result], args, 'result of iterating');
+}, 'Iterating over a CSSUnparsedValue produces all fragments');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-length.html b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-length.html
new file mode 100644
index 0000000..cb2b9c9
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue-length.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSUnparsedValue.length</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssunparsedvalue-length">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = new CSSUnparsedValue([]);
+  assert_equals(result.length, 0, 'length');
+}, 'Length of CSSUnparsedValue with no fragments is zero');
+
+test(() => {
+  const result = new CSSUnparsedValue([
+    ' ', new CSSVariableReferenceValue('--A')
+  ]);
+  assert_equals(result.length, 2, 'length');
+}, 'Length of CSSUnparsedValue with multiple fragments is the number of ' +
+   'fragments');
+
+test(() => {
+  let result = new CSSUnparsedValue([' ']);
+  assert_equals(result.length, 1, 'initial length');
+
+  result[1] = new CSSVariableReferenceValue('--A');
+  assert_equals(result.length, 2, 'length after appending once');
+
+  result[2] = 'lemon';
+  assert_equals(result.length, 3, 'length after appending twice');
+}, 'Length of CSSUnparsedValue updates when fragments are appended');
+
+test(() => {
+  let result = new CSSUnparsedValue([' ']);
+  assert_equals(result.length, 1, 'initial length');
+
+  result[0] = new CSSVariableReferenceValue('--A');
+  assert_equals(result.length, 1, 'length after modification');
+
+  result[0] = 'lemon';
+  assert_equals(result.length, 1, 'length after modification');
+}, 'Length of CSSUnparsedValue does not change when fragments are modified');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue.html b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue.html
new file mode 100644
index 0000000..db43fd7
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSUnparsedValue Constructor</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssunparsedvalue-cssunparsedvalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+const gTestArguments = [
+  {
+    description: 'no arguments',
+    fragments: [],
+  },
+  {
+    description: 'a single empty string',
+    fragments: [''],
+  },
+  {
+    description: 'a single CSSVariableReferenceValue',
+    fragments: [new CSSVariableReferenceValue('--foo')],
+  },
+  {
+    description: 'a mix of strings and CSSVariableReferenceValues',
+    fragments: [
+      'foo',
+      'bar',
+      new CSSVariableReferenceValue('--A'),
+      'baz',
+      new CSSVariableReferenceValue('--B')
+    ],
+  },
+];
+
+for (const args of gTestArguments) {
+  test(() => {
+    const result = new CSSUnparsedValue(args.fragments);
+    assert_not_equals(result, null, 'a CSSUnparsedValue is created');
+    assert_style_value_array_equals(result, args.fragments,
+                                    'fragments are same as given by constructor');
+  }, `CSSUnparsedValue can be constructed from ${args.description}`);
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue-invalid.html b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue-invalid.html
new file mode 100644
index 0000000..3696e99
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue-invalid.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSVariableReferenceValue Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssvariablereferencevalue">
+<meta name="assert" content="Test CSSVariableReferenceValue constructor and attributes error handling" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSVariableReferenceValue(''));
+}, 'Constructing a CSSVariableReferenceValue with an empty variable name ' +
+   'throws a TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSVariableReferenceValue('bar'));
+}, 'Constructing a CSSVariableReferenceValue with an invalid variable name ' +
+   'throws SyntaxError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue-variable.html b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue-variable.html
new file mode 100644
index 0000000..236758b
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue-variable.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSVariableReferenceValue.variable</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssvariablereferencevalue-variable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  let result = new CSSVariableReferenceValue('--foo');
+  result.variable = '--bar';
+  assert_equals(result.variable, '--bar', 'variable reflects new value');
+}, 'CSSVariableReferenceValue.variable can updated to a valid custom ' +
+   'property name');
+
+test(() => {
+  let result = new CSSVariableReferenceValue('--foo');
+  assert_throws(new TypeError(), () => result.variable = '');
+  assert_equals(result.variable, '--foo', 'variable does not change');
+}, 'Updating CSSVariableReferenceValue.variable to the empty string ' +
+   'throws TypeError');
+
+test(() => {
+  let result = new CSSVariableReferenceValue('--foo');
+  assert_throws(new TypeError(), () => result.variable = 'bar');
+  assert_equals(result.variable, '--foo', 'variable does not change');
+}, 'Updating CSSVariableReferenceValue.variable to an invalid custom ' +
+   'property name throws TypeError');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue.html b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue.html
new file mode 100644
index 0000000..9899b21
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSVariableReferenceValue Constructor</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssvariablereferencevalue-cssvariablereferencevalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = new CSSVariableReferenceValue('--foo');
+  assert_not_equals(result, null, 'a CSSVariableReferenceValue is created');
+  assert_equals(result.variable, '--foo',
+                'variable is same as passed by constructor');
+  assert_equals(result.fallback, null, 'fallback');
+}, 'CSSVariableReferenceValue can be constructed with no fallback');
+
+test(() => {
+  const result = new CSSVariableReferenceValue('--foo', null);
+  assert_not_equals(result, null, 'a CSSVariableReferenceValue is created');
+  assert_equals(result.variable, '--foo',
+                'variable is same as passed by constructor');
+  assert_equals(result.fallback, null, 'fallback');
+}, 'CSSVariableReferenceValue can be constructed with null fallback');
+
+test(() => {
+  const result = new CSSVariableReferenceValue('--foo',
+      new CSSUnparsedValue(['lemon']));
+  assert_not_equals(result, null, 'a CSSVariableReferenceValue is created');
+  assert_equals(result.variable, '--foo',
+                'variable is same as passed by constructor');
+  assert_not_equals(result.fallback, null, 'fallback');
+  assert_style_value_equals(result.fallback, new CSSUnparsedValue(['lemon']),
+                            'fallback is same as passed by constructor');
+}, 'CSSVariableReferenceValue can be constructed with valid fallback');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/add-two-types.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/add-two-types.tentative.html
new file mode 100644
index 0000000..313ff5a
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/add-two-types.tentative.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Adding Two Numeric Types</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#add-two-types">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(() => {
+  const a = new CSSUnitValue(0, 'number');
+  const b = new CSSUnitValue(0, 'px');
+  assert_throws(new TypeError(), () => new CSSMathSum(a, b));
+}, 'Adding two types with different non-null percent hints throws TypeError');
+
+test(() => {
+  const a = new CSSUnitValue(0, 'px');
+  const b = new CSSUnitValue(0, 'px');
+  const result = new CSSMathSum(a, b);
+  assert_numeric_type_equals(result.type(), { length: 1 });
+}, 'Adding two types with the same nonzero values returns same type');
+
+test(() => {
+  const a = new CSSUnitValue(0, 'number');
+  const b = new CSSUnitValue(0, 'number');
+  const result = new CSSMathSum(a, b);
+  assert_numeric_type_equals(result.type(), { });
+}, 'Adding two types with empty maps with returns empty map');
+
+test(() => {
+  const a = new CSSUnitValue(0, 'px');
+  const b = new CSSUnitValue(0, 'percent');
+  const result = new CSSMathSum(a, b);
+  assert_numeric_type_equals(result.type(), { length: 1, percentHint: 'length' });
+}, 'Adding a type with percent returns type with percent hint');
+
+test(() => {
+  const a = new CSSMathProduct(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'px'));
+  const b = new CSSUnitValue(0, 'percent');
+  assert_throws(new TypeError(), () => new CSSMathSum(a, b));
+}, 'Adding a type with percent 2 returns type with percent hint throws TypeError');
+
+test(() => {
+  const a = new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'percent'));
+  const b = new CSSUnitValue(0, 'px');
+  const result = new CSSMathSum(a, b);
+  assert_numeric_type_equals(result.type(), { length: 1, percentHint: 'length' });
+}, 'Adding a type with a percent hint returns a type with the percent hint');
+
+test(() => {
+  const a = new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'percent'));
+  const b = new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'percent'));
+  const result = new CSSMathSum(a, b);
+  assert_numeric_type_equals(result.type(), { length: 1, percentHint: 'length' });
+}, 'Adding two types with the same percent hint returns a type with the percent hint');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/arithmetic.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/arithmetic.tentative.html
new file mode 100644
index 0000000..d58bb07
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/arithmetic.tentative.html
@@ -0,0 +1,153 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Arithmetic operations on CSSNumericValue tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-add">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-sub">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-mul">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-div">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-min">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-max">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gArithmeticOps = [
+  { methodName: 'add', mathType: CSSMathSum },
+  { methodName: 'sub', mathType: CSSMathSum },
+  { methodName: 'mul', mathType: CSSMathProduct },
+  { methodName: 'div', mathType: CSSMathProduct },
+  { methodName: 'min', mathType: CSSMathMin },
+  { methodName: 'max', mathType: CSSMathMax },
+];
+
+for (const {methodName, mathType} of gArithmeticOps) {
+  test(() => {
+    const result = CSS.number(1)[methodName]();
+    assert_style_value_equals(result, CSS.number(1));
+  }, 'Calling CSSUnitValue.' + methodName + ' with no arguments returns itself');
+
+  test(() => {
+    // Use an arithmetic expression that can't be simplified to a CSSUnitValue
+    const mathValue = new mathType(CSS.px(1), CSS.percent(1));
+    const result = mathValue[methodName]();
+    assert_style_value_equals(result, mathValue);
+  }, 'Calling CSSMathValue.' + methodName + ' with no arguments returns itself');
+
+  test(() => {
+    const result = CSS.px(1)[methodName](CSS.percent(1));
+    assert_equals(result.constructor.name, mathType.name);
+  }, 'Calling CSSNumericValue.' + methodName + ' with a single CSSNumericValue returns correct type');
+
+  test(() => {
+    const result = new mathType(CSS.px(1))[methodName](CSS.percent(1));
+    assert_equals(result.constructor.name, mathType.name);
+  }, 'Calling CSSMathValue.' + methodName + ' with a single CSSNumericValue returns correct type');
+
+  test(() => {
+    const result = CSS.percent(1)[methodName](CSS.px(1), CSS.px(2), CSS.px(3));
+    assert_equals(result.constructor.name, mathType.name);
+  }, 'Calling CSSNumericValue.' + methodName + ' with more than one number CSSNumericValues returns correct type');
+
+  test(() => {
+    const result = CSS.number(1)[methodName](1, CSS.number(2), 3);
+    assert_equals(result.constructor.name, CSSUnitValue.name);
+  }, 'Calling CSSNumericValue.' + methodName + ' can take numberish values');
+
+  test(() => {
+    const result = new mathType(CSS.number(1))[methodName](CSS.number(2), CSS.number(3));
+    assert_equals(result.constructor.name, CSSUnitValue.name);
+    assert_equals(result.unit, 'number');
+  }, 'Calling ' + mathType.name + '.' + methodName + ' with number CSSUnitValues simplifies to a CSSUnitValue');
+
+  test(() => {
+    assert_throws(new TypeError(), () => new CSS.px(0)[methodName](CSS.px(1), CSS.s(2)));
+  }, 'Calling CSSNumericValue.' + methodName + ' with incompatible types throws TypeError');
+}
+
+test(() => {
+  const result = CSS.px(10).add(CSS.px(5), CSS.px(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 17);
+  assert_equals(result.unit, 'px');
+}, 'Calling CSSUnitValue.add with CSSUnitValues with same unit simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.px(10).sub(CSS.px(5), CSS.px(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 3);
+  assert_equals(result.unit, 'px');
+}, 'Calling CSSUnitValue.sub with CSSUnitValues with same unit simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.number(10).mul(CSS.number(5), CSS.number(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 100);
+  assert_equals(result.unit, 'number');
+}, 'Calling CSSUnitValue.mul with all numbers simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.number(10).mul(CSS.px(5), CSS.number(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 100);
+  assert_equals(result.unit, 'px');
+}, 'Calling CSSUnitValue.mul with only one non-number simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.number(10).mul(CSS.px(5), CSS.px(2));
+  assert_equals(result.constructor.name, CSSMathProduct.name);
+}, 'Calling CSSUnitValue.mul with more than one non-number does not simplify to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.number(10).div(CSS.number(5), CSS.number(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 1);
+  assert_equals(result.unit, 'number');
+}, 'Calling CSSUnitValue.div with all numbers simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.px(10).div(CSS.number(5), CSS.number(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 1);
+  assert_equals(result.unit, 'px');
+}, 'Calling CSSUnitValue.div on a non-number value simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.number(10).div(CSS.px(5), CSS.number(2));
+  assert_equals(result.constructor.name, CSSMathProduct.name);
+}, 'Calling CSSUnitValue.div with a non-number value in the arguments does not simplify to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.px(10).min(CSS.px(5), CSS.px(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 2);
+  assert_equals(result.unit, 'px');
+}, 'Calling CSSUnitValue.min with CSSUnitValues with same unit simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.px(10).max(CSS.px(5), CSS.px(2));
+  assert_equals(result.constructor.name, CSSUnitValue.name);
+  assert_equals(result.value, 10);
+  assert_equals(result.unit, 'px');
+}, 'Calling CSSUnitValue.max with CSSUnitValues with same unit simplifies to a CSSUnitValue');
+
+test(() => {
+  const result = CSS.number(1).sub(CSS.number(1), new CSSMathNegate(1), new CSSMathSum(1));
+  assert_style_value_equals(result,
+    new CSSMathSum(CSS.number(1), CSS.number(-1), CSS.number(1), new CSSMathNegate(new CSSMathSum(1))));
+}, 'Calling CSSNumericValue.sub negates all argument values');
+
+test(() => {
+  const result = CSS.number(2).div(CSS.number(2), CSS.px(2), new CSSMathInvert(2), new CSSMathSum(2));
+  assert_style_value_equals(result,
+    new CSSMathProduct(CSS.number(2), CSS.number(0.5), new CSSMathInvert(CSS.px(2)), CSS.number(2), new CSSMathInvert(new CSSMathSum(2))));
+}, 'Calling CSSNumericValue.div inverts all argument values');
+
+test(() => {
+  assert_throws(new RangeError(), () => CSS.number(2).div(CSS.number(0)));
+  assert_throws(new RangeError(), () => CSS.number(3).div(CSS.px(10) ,CSS.number(0)));
+  assert_throws(new RangeError(), () => CSS.number(2).div(CSS.number(0), CSS.number(0)));
+}, 'Can not divide with CSSUnitValue which has zero value and number type');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/create-a-type.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/create-a-type.tentative.html
new file mode 100644
index 0000000..bad9eba
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/create-a-type.tentative.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Creating Type From A Unit</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#create-a-type">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/testhelper.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  const value = new CSSUnitValue(0, 'number');
+  assert_numeric_type_equals(value.type(), {});
+}, 'Creating a type from "number" returns {}');
+
+test(() => {
+  const value = new CSSUnitValue(0, 'percent');
+  assert_numeric_type_equals(value.type(), { percent: 1 });
+}, 'Creating a type from "percent" returns { percent: 1 }');
+
+test(() => {
+  const value = new CSSUnitValue(0, 'px');
+  assert_numeric_type_equals(value.type(), { length: 1 });
+}, 'Creating a type from <length> returns { length: 1 }');
+
+test(() => {
+  const value = new CSSUnitValue(0, 'deg');
+  assert_numeric_type_equals(value.type(), { angle: 1 });
+}, 'Creating a type from <angle> returns { angle: 1 }');
+
+test(() => {
+  const value = new CSSUnitValue(0, 's');
+  assert_numeric_type_equals(value.type(), { time: 1 });
+}, 'Creating a type from <time> returns { time: 1 }');
+
+test(() => {
+  const value = new CSSUnitValue(0, 'Hz');
+  assert_numeric_type_equals(value.type(), { frequency: 1 });
+}, 'Creating a type from <frequency> returns { frequency: 1 }');
+
+test(() => {
+  const value = new CSSUnitValue(0, 'dpi');
+  assert_numeric_type_equals(value.type(), { resolution: 1 });
+}, 'Creating a type from <resolution> returns { resolution: 1 }');
+
+test(() => {
+  const value = new CSSUnitValue(0, 'fr');
+  assert_numeric_type_equals(value.type(), { flex: 1 });
+}, 'Creating a type from <flex> returns { flex: 1 }');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathInvert-type.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathInvert-type.html
new file mode 100644
index 0000000..61814e3
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathInvert-type.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSMathInvert.type</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#type-of-a-cssmathvalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(() => {
+  const result = new CSSMathInvert(new CSSUnitValue(0, 'number'));
+  assert_numeric_type_equals(result.type(), { });
+}, 'Inverting a type with empty map returns the empty map');
+
+test(() => {
+  const x = new CSSMathProduct(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 's'));
+  const result = new CSSMathInvert(x);
+  assert_numeric_type_equals(result.type(), { length: -1, time: -1 });
+}, 'Inverting a type negates all its exponents');
+
+test(() => {
+  const x = new CSSUnitValue(0, 'px');
+  const result = new CSSMathInvert(new CSSMathInvert(x));
+  assert_numeric_type_equals(result.type(), { length: 1 });
+}, 'Inverting an inverted type returns the original type');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathNegate-type.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathNegate-type.html
new file mode 100644
index 0000000..f1fc73d
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathNegate-type.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSMathNegate.type</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#type-of-a-cssmathvalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(() => {
+  const result = new CSSMathNegate(new CSSUnitValue(0, 'number'));
+  assert_numeric_type_equals(result.type(), { });
+}, 'Negating a type with empty map returns the empty map');
+
+test(() => {
+  const result = new CSSMathNegate(new CSSUnitValue(0, 'px'));
+  assert_numeric_type_equals(result.type(), { length: 1 });
+}, 'Negating a type returns the same type');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathValue.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathValue.tentative.html
new file mode 100644
index 0000000..574043e
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssMathValue.tentative.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSMathValue subclass tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#complex-numeric">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gVariadicMathValueSubclasses = [
+  { subclass: CSSMathSum, operator: 'sum' },
+  { subclass: CSSMathProduct, operator: 'product' },
+  { subclass: CSSMathMin, operator: 'min' },
+  { subclass: CSSMathMax, operator: 'max' },
+];
+
+for (const {subclass, operator} of gVariadicMathValueSubclasses) {
+  test(() => {
+    assert_throws(new SyntaxError(), () => new subclass());
+  }, 'Constructing a ' + subclass.name + ' with no arguments throws a SyntaxError');
+
+  test(() => {
+    const result = new subclass(CSS.number(0));
+    assert_equals(result.operator, operator);
+    assert_style_value_array_equals(result.values, [CSS.number(0)]);
+  }, subclass.name + ' can be constructed from a single number CSSUnitValue');
+
+  test(() => {
+    const args = [CSS.number(1), CSS.number(2), CSS.number(3), CSS.number(4), CSS.number(5)]
+    const result = new subclass(...args);
+    assert_equals(result.operator, operator);
+    assert_style_value_array_equals(result.values, args);
+  }, subclass.name + ' can be constructed from more than one number CSSUnitValue');
+
+  test(() => {
+    let result = new subclass(CSS.number(1), CSS.number(2));
+    assert_throws(new TypeError(), () => result.operator = 'foo');
+  }, subclass.name + '.operator is readonly');
+}
+
+const gUnaryMathValueSubclasses = [
+  { subclass: CSSMathNegate, operator: 'negate' },
+  { subclass: CSSMathInvert, operator: 'invert' },
+]
+
+for (const {subclass, operator} of gUnaryMathValueSubclasses) {
+  test(() => {
+    const result1 = new subclass(CSS.number(0));
+    assert_equals(result1.operator, operator);
+    assert_style_value_equals(result1.value, CSS.number(0));
+
+    const result2 = new subclass(0);
+    assert_equals(result2.operator, operator);
+    assert_style_value_equals(result2.value, CSS.number(0));
+  }, subclass.name + ' can be constructed from a single numberish value');
+
+  test(() => {
+    let result = new subclass(CSS.number(1));
+    assert_throws(new TypeError(), () => result.operator = 'foo');
+  }, subclass.name + '.operator is readonly');
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue-value.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue-value.html
new file mode 100644
index 0000000..4dff735
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue-value.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSUnitValue.value</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssunitvalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  const result = new CSSUnitValue(-3.14, 'px');
+  result.value = 3.14;
+  assert_equals(result.value, 3.14, 'value reflects new value');
+  assert_equals(result.unit, 'px', 'unit does not change');
+}, 'CSSUnitValue.value can be updated to a different value');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue.html
new file mode 100644
index 0000000..411b36f
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSUnitValue Constructor</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssunitvalue-cssunitvalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSUnitValue(0, 'lemon'));
+}, 'Constructing CSSUnitValue with an unknown unit throws a TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSUnitValue(0, ''));
+}, 'Constructing CSSUnitValue with a empty string unit throws a TypeError');
+
+for (const unit of gValidUnits) {
+  test(() => {
+    const result = new CSSUnitValue(-3.14, unit);
+    assert_not_equals(result, null, 'a CSSUnitValue is created');
+    assert_equals(result.value, -3.14,
+                  'value is same as given by constructor');
+    assert_equals(result.unit, unit.toLowerCase(),
+                  'unit is same as given by constructor');
+  }, 'CSSUnitValue can be constructed with ' + unit);
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssnumericvalue-multiply-two-types.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssnumericvalue-multiply-two-types.tentative.html
new file mode 100644
index 0000000..38045ee
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssnumericvalue-multiply-two-types.tentative.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Multiplying Two Numeric Types</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-multiply-two-types">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(() => {
+  const a = new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'percent'));
+  const b = new CSSMathSum(new CSSUnitValue(0, 's'), new CSSUnitValue(0, 'percent'));
+  assert_throws(new TypeError(), () => new CSSMathProduct(a, b));
+}, 'Multiplying two types with different non-null percent hints throws TypeError');
+
+test(() => {
+  const a = new CSSUnitValue(0, 'px');
+  const b = new CSSUnitValue(0, 'px');
+  const result = new CSSMathProduct(a, b);
+  assert_numeric_type_equals(result.type(), { length: 2 });
+}, 'Multiplying two types with same base types adds exponents');
+
+test(() => {
+  const a = new CSSUnitValue(0, 'px');
+  const b = new CSSUnitValue(0, 's');
+  const result = new CSSMathProduct(a, b);
+  assert_numeric_type_equals(result.type(), { length: 1, time: 1 });
+}, 'Multiplying two types with different base types adds exponents');
+
+test(() => {
+  const a = new CSSUnitValue(0, 'px');
+  const b = new CSSMathInvert(new CSSUnitValue(0, 's'));
+  const result = new CSSMathProduct(a, b);
+  assert_numeric_type_equals(result.type(), { length: 1, time: -1 });
+}, 'Multiplying two types respects the sign of the exponents');
+
+test(() => {
+  const a = new CSSUnitValue(0, 'px');
+  const b = new CSSUnitValue(0, 'number');
+  const result = new CSSMathProduct(a, b);
+  assert_numeric_type_equals(result.type(), { length: 1 });
+}, 'Multiplying a type with no exponents is a no-op');
+
+test(() => {
+  const a = new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'percent'));
+  const b = new CSSUnitValue(0, 'px');
+  const result = new CSSMathProduct(a, b);
+  assert_numeric_type_equals(result.type(), { length: 2, percentHint: 'length' });
+}, 'Multiplying a type with percent hint applies the percent hint');
+
+test(() => {
+  const a = new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'percent'));
+  const b = new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'percent'));
+  const result = new CSSMathProduct(a, b);
+  assert_numeric_type_equals(result.type(), { length: 2, percentHint: 'length' });
+}, 'Multiplying two types with same percent hint applies the percent hint');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/equals.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/equals.tentative.html
new file mode 100644
index 0000000..180d3de
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/equals.tentative.html
@@ -0,0 +1,62 @@
+<meta charset="utf-8">
+<title>CSSNumericValue.equals tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-equals">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  assert_true(CSS.px(1).equals(CSS.px(1)));
+}, 'Two CSSUnitValues with same value and unit are equal');
+
+test(() => {
+  assert_false(CSS.px(0).equals(CSS.px(1)));
+}, 'Two CSSUnitValues with different values are not equal');
+
+test(() => {
+  assert_false(CSS.px(1).equals(CSS.number(1)));
+}, 'Two CSSUnitValues with different units are not equal');
+
+test(() => {
+  const a = new CSSMathSum(0, new CSSMathNegate(0));
+  const b = new CSSMathProduct(0, new CSSMathNegate(0));
+  assert_false(a.equals(b));
+}, 'Two CSSMathValues with different types are not equal');
+
+test(() => {
+  const a = new CSSMathSum(0, new CSSMathNegate(0));
+  const b = new CSSMathSum(0);
+  assert_false(a.equals(b));
+}, 'Two CSSMathValues with different number of values are not equal');
+
+test(() => {
+  const a = new CSSMathSum(0, new CSSMathNegate(0));
+  const b = new CSSMathSum(0, new CSSMathNegate(1));
+  assert_false(a.equals(b));
+}, 'Two CSSMathValues with different values are not equal');
+
+test(() => {
+  const a = new CSSMathSum(0, new CSSMathNegate(0));
+  const b = new CSSMathSum(0, new CSSMathNegate(0));
+  assert_true(a.equals(b));
+}, 'Two CSSMathValues with same structure are equal');
+
+test(() => {
+  const a = new CSSMathSum(0, new CSSMathNegate(0));
+  const b = new CSSMathSum(0, new CSSMathNegate(0));
+  const c = new CSSMathSum(0, new CSSMathNegate(0));
+  const d = new CSSMathSum(0, new CSSMathNegate(0));
+  assert_true(a.equals(b, c, d));
+}, 'Multiple CSSMathValues with same structure are equal');
+
+test(() => {
+  const a = new CSSMathSum(0, new CSSMathNegate(0));
+  const b = new CSSMathSum(0, new CSSMathNegate(0));
+  const c = new CSSMathSum(0, new CSSMathNegate(1));
+  const d = new CSSMathSum(0, new CSSMathNegate(0));
+  assert_false(a.equals(b, c, d));
+}, 'Multiple CSSMathValues with one different are not equal');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/numeric-factory.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/numeric-factory.tentative.html
new file mode 100644
index 0000000..51b282b
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/numeric-factory.tentative.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS numeric factory function tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#numeric-factory">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+for (const unit of gValidUnits) {
+  test(() => {
+    const result = CSS[unit](12.3);
+    assert_not_equals(result, null);
+    assert_equals(result.constructor.name, CSSUnitValue.name);
+    assert_equals(result.value, 12.3);
+    assert_equals(result.unit, unit.toLowerCase());
+  }, 'CSS.' + unit + ' returns a CSSUnitValue with correct value and unit');
+}
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative.html
new file mode 100644
index 0000000..80e4f44
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative.html
@@ -0,0 +1,30 @@
+<meta charset="utf-8">
+<title>CSSNumericValue.parse tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-parse">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new SyntaxError(), () => CSSNumericValue.parse('%#('));
+}, 'Parsing an invalid string throws SyntaxError');
+
+test(() => {
+  assert_throws(new SyntaxError(), () => CSSNumericValue.parse('auto'));
+}, 'Parsing a string with a non numeric token throws SyntaxError');
+
+test(() => {
+  assert_throws(new SyntaxError(), () => CSSNumericValue.parse('1 2'));
+}, 'Parsing a string with left over numeric tokens throws SyntaxError');
+
+test(() => {
+  assert_throws(new SyntaxError(), () => CSSNumericValue.parse('calc(calc(1px * 2s) + 3%)'));
+}, 'Parsing a calc with incompatible units throws a SyntaxError');
+
+test(() => {
+  assert_style_value_equals(new CSSUnitValue(1, 'px'), CSSNumericValue.parse(' 1px  '));
+}, 'Parsing ignores surrounding spaces');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/resources/testhelper.js b/css/css-typed-om/stylevalue-subclasses/numeric-objects/resources/testhelper.js
new file mode 100644
index 0000000..42f2c66
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/resources/testhelper.js
@@ -0,0 +1,9 @@
+function assert_numeric_type_equals(type, expectedType) {
+  const baseTypes = [
+    'length', 'angle', 'time', 'frequency', 'resolution', 'flex', 'percent'
+  ];
+  for (const baseType of baseTypes) {
+    assert_equals(type[baseType], expectedType[baseType], baseType);
+  }
+  assert_equals(type.percentHint, expectedType.percentHint);
+}
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/to.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/to.tentative.html
new file mode 100644
index 0000000..57ac631
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/to.tentative.html
@@ -0,0 +1,101 @@
+<meta charset="utf-8">
+<title>CSSNumericValue.to tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-to">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new SyntaxError(), () => CSS.px(1).to('lemon'));
+}, 'Converting a CSSUnitValue to an invalid unit throws SyntaxError');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSMathMax(1, CSS.px(1)).to('number'));
+}, 'Converting a CSSNumericValue with invalid sum value throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSMathProduct(CSS.px(1), CSS.s(1)).to('number'));
+}, 'Converting a CSSNumericValue with sum value containing more than one value throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSS.px(1).to('number'));
+}, 'Converting a CSSUnitValue to an incompatible unit throws TypeError');
+
+test(() => {
+  for (const unit of gValidUnits) {
+    // FIXME(778495): Remove this check onec all the units are supported.
+    if (CSS[unit])
+      assert_style_value_equals(CSS[unit](1).to(unit), CSS[unit](1));
+  }
+}, 'Converting a CSSUnitValue to its own unit returns itself');
+
+// TODO(776173): cssUnitValue_toMethod.html has more comprehensive tests of converting
+// within the same base type. Merge those tests into here.
+test(() => {
+  assert_style_value_equals(CSS.cm(1).to('px'), CSS.px(37.7952755));
+}, 'Converting a CSSUnitValue to its canonical unit returns correct value');
+
+test(() => {
+  assert_style_value_equals(new CSSMathSum(CSS.px(1), CSS.px(1)).to('px'), CSS.px(2));
+  assert_style_value_equals(new CSSMathSum(CSS.px(1), CSS.px(1), CSS.px(1)).to('px'), CSS.px(3));
+}, 'Converting a CSSMathSum to a single unit adds the values');
+
+test(() => {
+  assert_style_value_equals(new CSSMathProduct(CSS.px(2), 3).to('px'), CSS.px(6));
+  assert_style_value_equals(new CSSMathProduct(-1, CSS.px(2), 3).to('px'), CSS.px(-6));
+}, 'Converting a CSSMathProduct to a single unit multiplies the values');
+
+test(() => {
+  assert_style_value_equals(new CSSMathMin(CSS.cm(1), CSS.mm(1)).to('mm'), CSS.mm(1));
+}, 'Converting a CSSMathMin to a single unit finds the min value');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSMathMin(CSS.px(2), CSS.s(3)).to('px'));
+  assert_throws(new TypeError(), () => new CSSMathMin(CSS.px(2), 3).to('px'));
+}, 'Converting a CSSMathMin to a single unit with different units throws a TypeError');
+
+test(() => {
+  assert_style_value_equals(new CSSMathMax(CSS.cm(1), CSS.mm(1)).to('mm'), CSS.mm(10));
+}, 'Converting a CSSMathMax to a single unit finds the max value');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSMathMax(CSS.px(2), CSS.s(3)).to('px'));
+  assert_throws(new TypeError(), () => new CSSMathMax(CSS.px(2), 3).to('px'));
+}, 'Converting a CSSMathMax to a single unit with different units throws a TypeError');
+
+test(() => {
+  assert_style_value_equals(new CSSMathNegate(CSS.px(1)).to('px'), CSS.px(-1));
+}, 'Converting a CSSMathNegate to a single unit negates its value');
+
+test(() => {
+  const expr = new CSSMathProduct(CSS.px(4), new CSSMathInvert(CSS.px(2)));
+  assert_style_value_equals(expr.to('number'), CSS.number(2));
+}, 'Converting a CSSMathInvert to a single unit inverts its value and units');
+
+test(() => {
+  // max((1s * 1s *  1px * 1px) / (1s * 1px), 2000ms * 2em) / 1em - min(500ms, 1s)
+  const expr = new CSSMathSum(
+    new CSSMathProduct(
+      new CSSMathMax(
+        new CSSMathProduct(
+          new CSSMathProduct(CSS.s(1), CSS.s(1), CSS.px(1), CSS.px(1)),
+          new CSSMathInvert(new CSSMathProduct(CSS.s(1), CSS.px(1))),
+        ),
+        new CSSMathProduct(CSS.ms(2000), CSS.cm(2))
+      ),
+      new CSSMathInvert(CSS.cm(1))
+    ),
+    new CSSMathNegate(
+      new CSSMathMin(
+        CSS.ms(500),
+        CSS.s(1)
+      )
+    )
+  );
+
+  assert_style_value_equals(expr.to('ms'), CSS.ms(3500));
+}, 'Converting a complex expression to a single unit');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/numeric-objects/toSum.tentative.html b/css/css-typed-om/stylevalue-subclasses/numeric-objects/toSum.tentative.html
new file mode 100644
index 0000000..3d67f7d
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/numeric-objects/toSum.tentative.html
@@ -0,0 +1,71 @@
+<meta charset="utf-8">
+<title>CSSNumericValue.toSum tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-tosum">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  assert_throws(new SyntaxError(), () => CSS.px(1).toSum('px', 'lemon'));
+}, 'Converting a CSSNumericValue to a sum with invalid units throws SyntaxError');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSMathMax(1, CSS.px(1)).toSum('number'));
+}, 'Converting a CSSNumericValue with an invalid sum value to a sum throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSMathProduct(CSS.px(1), CSS.px(1)).to('px'));
+}, 'Converting a CSSNumericValue with compound units to a sum throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSS.px(1).toSum('number'));
+}, 'Converting a CSSNumericValue to a sum with an incompatible unit throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => CSS.px(1).toSum('px', 's'));
+}, 'Converting a CSSNumericValue to a sum with units that are not addable throws TypeError');
+
+test(() => {
+  assert_throws(new TypeError(), () => new CSSMathSum(CSS.px(1), CSS.em(1)).toSum('px'));
+}, 'Converting a CSSNumericValue with leftover units to a sum throws TypeError');
+
+test(() => {
+  assert_style_value_equals(CSS.number(1).toSum('number'), new CSSMathSum(CSS.number(1)));
+  assert_style_value_equals(CSS.px(1).toSum('px'), new CSSMathSum(CSS.px(1)));
+}, 'Converting CSSNumericValue to a sum with its own unit returns itself');
+
+test(() => {
+  assert_style_value_equals(
+    new CSSMathSum(CSS.px(1), CSS.em(1), CSS.vw(1), CSS.rem(1)).toSum(),
+    new CSSMathSum(CSS.em(1), CSS.px(1), CSS.rem(1), CSS.vw(1))
+  );
+}, 'Converting CSSNumericValue to a sum with no arguments returns all the units in sorted order');
+
+// TODO(776173): cssUnitValue_toMethod.html has more comprehensive tests of converting
+// within the same base type. Merge those tests into here.
+test(() => {
+  assert_style_value_equals(CSS.cm(2).toSum('mm'), new CSSMathSum(CSS.mm(20)));
+}, 'Converting CSSNumericValue to a sum with a relative unit converts correctly');
+
+test(() => {
+  assert_style_value_equals(
+    CSS.px(1).toSum('em', 'px', 'vw'),
+    new CSSMathSum(CSS.em(0), CSS.px(1), CSS.vw(0))
+  );
+}, 'Converting CSSNumericValue to a sum containing extra units returns zero for those units');
+
+test(() => {
+  assert_style_value_equals(
+    new CSSMathSum(CSS.cm(1), CSS.mm(10)).toSum('cm', 'mm'),
+    new CSSMathSum(CSS.cm(2), CSS.mm(0))
+  );
+
+  assert_style_value_equals(
+    new CSSMathSum(CSS.cm(1), CSS.mm(10)).toSum('mm', 'cm'),
+    new CSSMathSum(CSS.mm(20), CSS.cm(0))
+  );
+}, 'CSSNumericValue.toSum converts greedily');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html b/css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html
new file mode 100644
index 0000000..9bbcb30
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Computed StylePropertyMap tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#computed-stylepropertymapreadonly-objects">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<style>#target { height: 10px; --foo: auto; }</style>
+<div style="width: 50px">
+  <div id="target" style="top: 5px; --bar: 5; width: 50%;"></div>
+</div>
+<script>
+'use strict';
+
+const target = document.getElementById('target');
+const styleMap = target.computedStyleMap();
+
+test(() => {
+  const computedStyle = [...getComputedStyle(target)].sort();
+  const properties = [...styleMap.keys()];
+
+  // Two extra entries for custom properties
+  assert_equals(properties.length, computedStyle.length + 2);
+  for (let i = 0; i < computedStyle.length; i++) {
+    assert_true(properties.includes(computedStyle[i]));
+    assert_not_equals(styleMap.get(computedStyle[i]), null);
+    assert_not_equals(styleMap.getAll(computedStyle[i]).length, 0);
+    assert_true(styleMap.has(computedStyle[i]));
+  }
+}, 'Computed StylePropertyMap contains every CSS property');
+
+test(() => {
+  const result = styleMap.get('height');
+  assert_style_value_equals(result, CSS.px(10));
+}, 'Computed StylePropertyMap contains CSS property declarations in style rules');
+
+test(() => {
+  const result = styleMap.get('--foo');
+  assert_style_value_equals(result, new CSSUnparsedValue([' auto']));
+}, 'Computed StylePropertyMap contains custom property declarations in style rules');
+
+test(() => {
+  const result = styleMap.get('top');
+  assert_style_value_equals(result, CSS.px(5));
+}, 'Computed StylePropertyMap contains CSS property declarations in inline styles');
+
+test(() => {
+  const result = styleMap.get('--bar');
+  assert_style_value_equals(result, new CSSUnparsedValue([' 5']));
+}, 'Computed StylePropertyMap contains custom property declarations in inline rules');
+
+test(() => {
+  const computedStyle = getComputedStyle(target);
+  assert_equals(computedStyle.width, '25px');
+
+  const result = styleMap.get('width');
+  assert_style_value_equals(result, CSS.percent(50));
+}, 'Computed StylePropertyMap contains computed values and not resolved values');
+
+test(t => {
+  let target = createDivWithStyle(t, 'width: 10px');
+  const styleMap = target.attributeStyleMap;
+  assert_style_value_equals(styleMap.get('width'), CSS.px(10));
+
+  target.style.width = '20px';
+  assert_style_value_equals(styleMap.get('width'), CSS.px(20));
+}, 'Computed StylePropertyMap is live');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/get-invalid.html b/css/css-typed-om/the-stylepropertymap/computed/get-invalid.html
new file mode 100644
index 0000000..8117d18
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/get-invalid.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Computed StylePropertyMap.get Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createComputedStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.get('lemon'));
+}, 'Calling StylePropertyMap.get with an unsupported property throws a TypeError');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/get-shorthand.html b/css/css-typed-om/the-stylepropertymap/computed/get-shorthand.html
new file mode 100644
index 0000000..a940422
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/get-shorthand.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Computed StylePropertyMap.get with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'margin: 1px 2px 3px 4px');
+  const result = styleMap.get('margin');
+  assert_not_equals(result, null, 'Shorthand value must not be null');
+  assert_class_string(result, 'CSSStyleValue',
+    'Shorthand value must be a base CSSStyleValue');
+}, 'Getting an shorthand property set explicitly in computed style returns ' +
+   'a base CSSStyleValue');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t);
+  const result = styleMap.get('margin');
+  assert_not_equals(result, null, 'Shorthand value must not be null');
+  assert_class_string(result, 'CSSStyleValue',
+    'Shorthand value must be a base CSSStyleValue');
+}, 'Getting a shorthand property from initial computed style returns ' +
+   'a base CSSStyleValue');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/get.html b/css/css-typed-om/the-stylepropertymap/computed/get.html
new file mode 100644
index 0000000..3e061e1
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/get.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Computed StylePropertyMap.get</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<meta name="assert" content="Test computed StylePropertyMap.get" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, '--foo: auto');
+  assert_equals(styleMap.get('--Foo'), null);
+}, 'Getting a custom property not in the computed style returns null');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'width: 10px; height: 20px');
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(10, 'px'));
+}, 'Getting a valid property from computed style returns the correct entry');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, '--foo: auto; --bar: 10px');
+  assert_style_value_equals(styleMap.get('--foo'),
+      new CSSUnparsedValue([' auto']));
+}, 'Getting a valid custom property from computed style returns the ' +
+   'correct entry');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t,
+      'width: 10px; transition-duration: 1s, 2s; height: 10px;');
+  assert_style_value_equals(styleMap.get('transition-duration'),
+      new CSSUnitValue(1, 's'));
+}, 'Getting a list-valued property from computed style returns only ' +
+   'the first value');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'height: 20px; width: 10px;');
+  assert_style_value_equals(styleMap.get('wIdTh'), new CSSUnitValue(10, 'px'));
+}, 'Computed StylePropertyMap.get is not case-sensitive');
+
+test(t => {
+  const [elem, styleMap] = createElementWithComputedStyleMap(t, 'width: 10px;');
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(10, 'px'));
+  elem.style.width = '20px';
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(20, 'px'));
+}, 'Computed StylePropertyMap.get reflects updates in inline style');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/getAll-shorthand.html b/css/css-typed-om/the-stylepropertymap/computed/getAll-shorthand.html
new file mode 100644
index 0000000..6d367c8
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/getAll-shorthand.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Computed StylePropertyMap.getAll with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-getall">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'margin: 1px 2px 3px 4px');
+  const result = styleMap.getAll('margin');
+  assert_not_equals(result, null, 'Result must not be null');
+  assert_equals(result.length, 1, 'Result must be a list with one item');
+  assert_class_string(result[0], 'CSSStyleValue',
+    'Only item in result must be a base CSSStyleValue');
+}, 'StylePropertyMap.getAll() with a shorthand property set explicitly in ' +
+   'computed style returns a list containing a base CSSStyleValue');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t);
+  const result = styleMap.getAll('margin');
+  assert_not_equals(result, null, 'Result must not be null');
+  assert_equals(result.length, 1, 'Result must be a list with one item');
+  assert_class_string(result[0], 'CSSStyleValue',
+    'Only item in result must be a base CSSStyleValue');
+}, 'StylePropertyMap.getAll() with a shorthand property from initial ' +
+   'computed style returns a list containing a base CSSStyleValue');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/getAll.tentative.html b/css/css-typed-om/the-stylepropertymap/computed/getAll.tentative.html
new file mode 100644
index 0000000..ca7d3b6
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/getAll.tentative.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.getAll tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createComputedStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.getAll('lemon'));
+}, 'Calling StylePropertyMap.getAll with an unsupported property throws a TypeError');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, '--foo: auto');
+  assert_style_value_array_equals(styleMap.getAll('--Foo'), []);
+}, 'Calling StylePropertyMap.getAll with a custom property not in the property model returns an empty list');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'width: 10px; height: 20px');
+  assert_style_value_array_equals(styleMap.getAll('width'), [CSS.px(10)]);
+}, 'Calling StylePropertyMap.getAll with a valid property returns a single element list with the correct entry');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'height: 20px; width: 10px');
+  assert_style_value_array_equals(styleMap.getAll('wIdTh'), [CSS.px(10)]);
+}, 'StylePropertyMap.getAll is case-insensitive');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, '--foo: auto; --bar: 10px');
+  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue([' auto'])]);
+}, 'Calling StylePropertyMap.getAll with a valid custom property returns a single element list with the correct entry');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'width: 10px; transition-duration: 1s, 2s; height: 20px');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+}, 'Calling StylePropertyMap.getAll with a list-valued property returns all the values');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/has.tentative.html b/css/css-typed-om/the-stylepropertymap/computed/has.tentative.html
new file mode 100644
index 0000000..d387304
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/has.tentative.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.has tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#check-if-stylepropertymap-has-a-property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createComputedStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.has('lemon'));
+}, 'Calling StylePropertyMap.has with an unsupported property throws a TypeError');
+
+const gTestCases = [
+  { property: '--Foo', expected: false, desc: 'a custom property not in the property model' },
+  { property: 'width', expected: true, desc: 'a valid property' },
+  { property: 'wIdTh', expected: true, desc: 'a valid property in mixed case' },
+  { property: 'margin', expected: true, desc: 'a valid shorthand property' },
+  { property: '--foo', expected: true, desc: 'a valid custom property' },
+  { property: 'transition-duration', expected: true, desc: 'a valid list-valued property' },
+];
+
+for (const {property, expected, desc} of gTestCases) {
+  test(t => {
+    const styleMap = createComputedStyleMap(t, 'width: 10px; --foo: auto; transition-duration: 1s, 2s');
+    assert_equals(styleMap.has(property), expected);
+  }, 'Calling StylePropertyMap.has with ' + desc + ' returns ' + expected);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/computed/iterable.tentative.html b/css/css-typed-om/the-stylepropertymap/computed/iterable.tentative.html
new file mode 100644
index 0000000..bd11dd2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/computed/iterable.tentative.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap iterable tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#the-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+function findInStyleMap(styleMap, property) {
+  const index = [...styleMap.keys()].indexOf(property);
+  if (index == -1)
+    return null;
+  return [...styleMap.values()][index];
+}
+
+// Puts normal CSS properties before vendor prefixed ones
+function comparePropertyNames(a, b) {
+  if (a.startsWith('-') == b.startsWith('-'))
+    return a < b ? -1 : 1;
+  return b.startsWith('-') ? -1 : 1;
+}
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, '--A: A; width: 10px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;');
+  const expectedKeys = [...getComputedStyle(document.body)].sort(comparePropertyNames).concat('--A', '--B', '--C');
+  assert_array_equals([...styleMap.keys()], expectedKeys);
+}, 'StylePropertyMap iterates properties in correct order');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'width: 10px; transition-duration: 1s, 2s; height: 20px');
+  assert_style_value_array_equals(findInStyleMap(styleMap, 'width'), [CSS.px(10)]);
+}, 'StylePropertyMap iterator returns CSS properties with the correct CSSStyleValue');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, 'width: 10px; transition-duration: 1s, 2s; height: 20px');
+  assert_style_value_array_equals(findInStyleMap(styleMap, 'transition-duration'), [CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap iterator returns list-valued properties with the correct CSSStyleValue');
+
+test(t => {
+  const styleMap = createComputedStyleMap(t, '--A: A; --C: C; color: red; --B: B;');
+  assert_style_value_array_equals(findInStyleMap(styleMap, '--A'), [new CSSUnparsedValue([' A'])]);
+  assert_style_value_array_equals(findInStyleMap(styleMap, '--B'), [new CSSUnparsedValue([' B'])]);
+  assert_style_value_array_equals(findInStyleMap(styleMap, '--C'), [new CSSUnparsedValue([' C'])]);
+}, 'StylePropertyMap iterator returns custom properties with the correct CSSStyleValue');
+
+test(t => {
+  // This is to test for https://github.com/w3c/css-houdini-drafts/issues/700
+  const styleMap = createComputedStyleMap(t, '--豈: 豈; --💩: 💩;');
+  const keys = [...styleMap.keys()];
+
+  assert_array_equals(keys.slice(-2), ['--💩', '--豈']);
+}, 'Computed StylePropertyMap sorts custom properties in increasing ' +
+   'code-point order');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/append.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/append.tentative.html
new file mode 100644
index 0000000..3f94a89
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/append.tentative.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.append tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#append-to-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { property: 'lemon', values: ['ade'], desc: 'an unsupported property name' },
+  { property: null, values: ['foo'], desc: 'an null property name' },
+  { property: 'width', values: ['10px'], desc: 'a property that is not list valued' },
+  { property: 'margin', values: ['10px'], desc: 'a shorthand property' },
+  { property: 'transition-duration', values: [CSS.px(10)], desc: 'an invalid CSSStyleValue' },
+  { property: 'transition-duration', values: ['10px'], desc: 'an invalid String value' },
+  { property: 'transition-duration', values: [CSS.s(1), '10px', CSS.px(10)], desc: 'a mix of valid and invalid values' },
+  { property: 'transition-duration', values: [new CSSUnparsedValue([])], desc: 'a CSSUnparsedValue' },
+  { property: 'transition-duration', values: ['var(--A)'], desc: 'a var ref' },
+];
+
+for (const {property, values, desc} of gInvalidTestCases) {
+  test(t => {
+    let styleMap = createDeclaredStyleMap(t, '');
+    assert_throws(new TypeError(), () => styleMap.append(property, ...values));
+  }, 'Calling StylePropertyMap.append with ' + desc + ' throws TypeError');
+}
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+
+  styleMap.append('transition-duration', CSS.s(1), '2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'),
+      [CSS.s(1), CSS.s(2)]);
+
+  styleMap.append('transition-duration', '3s', CSS.s(4));
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'),
+      [CSS.s(1), CSS.s(2), CSS.s(3), CSS.s(4)]);
+}, 'Appending a list-valued property with CSSStyleValue or String updates its values');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+
+  styleMap.append('transition-duration', '1s, 2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'),
+      [CSS.s(1), CSS.s(2)]);
+
+  styleMap.append('transition-duration', '3s, 4s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'),
+      [CSS.s(1), CSS.s(2), CSS.s(3), CSS.s(4)]);
+}, 'Appending a list-valued property with list-valued string updates its values');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, 'transition-duration: 5s, 10s');
+
+  styleMap.append('tRaNsItIoN-dUrAtIoN', '1s', CSS.s(2));
+  const result = styleMap.getAll('transition-duration');
+  assert_style_value_array_equals(result,
+      [CSS.s(5), CSS.s(10), CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap.append is case-insensitive');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/clear.html b/css/css-typed-om/the-stylepropertymap/declared/clear.html
new file mode 100644
index 0000000..e6bdd4f
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/clear.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.clear</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-clear">
+<meta name="assert" content="Test declared StylePropertyMap.clear" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+  styleMap.clear();
+  assert_array_equals([...styleMap], []);
+}, 'Clearing an empty CSS rule is a no-op');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '--foo: auto; width: 10px; transition-duration: 1s, 2s');
+
+  styleMap.clear();
+  assert_equals(styleMap.get('--foo'), null,
+    'Custom properties should be cleared');
+  assert_equals(styleMap.get('width'), null,
+    'CSS properties should be cleared');
+  assert_equals(styleMap.get('transition-duration'), null,
+    'List-valued properties should be cleared');
+  assert_array_equals([...styleMap], []);
+}, 'Can clear a CSS rule containing properties');
+
+test(t => {
+  let [rule, styleMap] = createRuleWithDeclaredStyleMap(t, 'width: 10px;');
+  styleMap.clear();
+
+  assert_equals(rule.style.width, '', 'CSS rule style should be cleared');
+}, 'Declared StylePropertyMap.clear updates the CSS rule');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html
new file mode 100644
index 0000000..3d857f1
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#declared-stylepropertymap-objects">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<style>
+div {
+  height: 10px;
+  width: 50%;
+  width: 'lemon';
+  --foo: auto;
+  transition-duration: 1s, 2s;
+  color: 10;
+}
+
+#target {
+  height: 20px;
+  --foo: 1s;
+  width: 10%;
+}
+</style>
+<div style="width: 50px">
+  <div id="target" style="top: 5px; --bar: auto;"></div>
+</div>
+<script>
+'use strict';
+
+const target = document.getElementById('target');
+const styleMap = document.styleSheets[0].rules[0].styleMap;
+
+test(() => {
+  const properties = [...styleMap.keys()];
+  assert_array_equals(properties, ['height', 'width', '--foo', 'transition-duration']);
+}, 'Declared StylePropertyMap only contains properties in the style rule');
+
+test(() => {
+  assert_style_value_equals(styleMap.get('height'), CSS.px(10));
+}, 'Declared StylePropertyMap contains CSS property declarations in style rules');
+
+test(() => {
+  assert_equals(styleMap.get('top'), null);
+  assert_equals(styleMap.get('--bar'), null);
+}, 'Declared StylePropertyMap does not contain inline styles');
+
+test(() => {
+  assert_style_value_equals(styleMap.get('--foo'), new CSSUnparsedValue([' auto']));
+}, 'Declared StylePropertyMap contains custom property declarations');
+
+test(() => {
+  assert_equals(styleMap.get('color'), null);
+}, 'Declared StylePropertyMap does not contain properties with invalid values');
+
+test(() => {
+  assert_style_value_equals(styleMap.get('width'), CSS.percent(50));
+}, 'Declared StylePropertyMap contains properties with their last valid value');
+
+test(() => {
+  const style = document.createElement('style');
+  document.head.appendChild(style);
+
+  style.sheet.insertRule('.test { width: 10px; }');
+  let rule = style.sheet.rules[0];
+
+  let styleMap = rule.styleMap;
+  assert_style_value_equals(styleMap.get('width'), CSS.px(10));
+
+  rule.style.width = '20px';
+  assert_style_value_equals(styleMap.get('width'), CSS.px(20));
+
+  styleMap.set('width', CSS.px(30));
+  assert_equals(rule.cssText, '.test { width: 30px; }');
+}, 'Declared StylePropertyMap is live');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/delete-invalid.html b/css/css-typed-om/the-stylepropertymap/declared/delete-invalid.html
new file mode 100644
index 0000000..547ea26
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/delete-invalid.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.delete Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#delete-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+  assert_throws(new TypeError(), () => styleMap.delete('lemon'));
+}, 'Deleting an unsupported property name throws a TypeError');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/delete-shorthand.html b/css/css-typed-om/the-stylepropertymap/declared/delete-shorthand.html
new file mode 100644
index 0000000..48d4b49
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/delete-shorthand.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.delete() with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#delete-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  let [elem, styleMap] = createRuleWithDeclaredStyleMap(t, '');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '');
+
+  styleMap.delete('margin');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '');
+}, 'Deleting a shorthand property not in the css rule is a no-op');
+
+test(t => {
+  let [elem, styleMap] = createRuleWithDeclaredStyleMap(t, 'margin: 10px');
+  assert_not_equals(elem.style.getPropertyValue('margin'), '');
+
+  styleMap.delete('margin');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '');
+}, 'Deleting a shorthand property in the css rule removes both it and ' +
+   'its longhands');
+
+test(t => {
+  let [elem, styleMap] = createRuleWithDeclaredStyleMap(t, 'margin: 10px');
+  assert_not_equals(elem.style.getPropertyValue('margin-top'), '');
+
+  styleMap.delete('margin-top');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '10px');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '10px');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '10px');
+}, 'Deleting a longhand property in the css rule removes both it and ' +
+   'its shorthand');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/delete.html b/css/css-typed-om/the-stylepropertymap/declared/delete.html
new file mode 100644
index 0000000..48ac3dc
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/delete.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.delete</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#delete-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  let [rule, styleMap] = createRuleWithDeclaredStyleMap(t, '');
+  assert_equals(rule.style.getPropertyValue('width'), '');
+
+  styleMap.delete('width');
+  assert_equals(rule.style.getPropertyValue('width'), '');
+}, 'Deleting a property not in the css rule is a no-op');
+
+test(t => {
+  let [rule, styleMap] = createRuleWithDeclaredStyleMap(t, 'width: 10px');
+  assert_not_equals(rule.style.getPropertyValue('width'), '');
+
+  styleMap.delete('width');
+  assert_equals(rule.style.getPropertyValue('width'), '');
+}, 'Deleting a property in the css rule removes it from the css rule');
+
+test(t => {
+  let [rule, styleMap] = createRuleWithDeclaredStyleMap(t, '--Foo: 10px');
+  assert_not_equals(rule.style.getPropertyValue('--Foo'), '');
+
+  styleMap.delete('--Foo');
+  assert_equals(rule.style.getPropertyValue('--Foo'), '');
+}, 'Deleting a custom property in the css rule removes it from the ' +
+   'css rule');
+
+test(t => {
+  let [rule, styleMap] = createRuleWithDeclaredStyleMap(t,
+      'transition-duration: 1s, 2s');
+  assert_not_equals(rule.style.getPropertyValue('transition-duration'), '');
+
+  styleMap.delete('transition-duration');
+  assert_equals(rule.style.getPropertyValue('transition-duration'), '');
+}, 'Deleting a list-valued property in the css rule removes it from ' +
+   'the css rule');
+
+test(t => {
+  let [rule, styleMap] = createRuleWithDeclaredStyleMap(t, 'width: 10px');
+  assert_not_equals(rule.style.getPropertyValue('width'), '');
+
+  styleMap.delete('wIdTh');
+  assert_equals(rule.style.getPropertyValue('width'), '');
+}, 'Declared StylePropertyMap.delete is not case-sensitive');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/get-invalid.html b/css/css-typed-om/the-stylepropertymap/declared/get-invalid.html
new file mode 100644
index 0000000..a91d197
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/get-invalid.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.get Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.get('lemon'));
+}, 'Calling StylePropertyMap.get with an unsupported property throws a TypeError');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/get-shorthand.html b/css/css-typed-om/the-stylepropertymap/declared/get-shorthand.html
new file mode 100644
index 0000000..0a83ca4
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/get-shorthand.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.get with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'margin: 1px 2px 3px 4px');
+  const result = styleMap.get('margin');
+  assert_not_equals(result, null, 'Shorthand value must not be null');
+  assert_class_string(result, 'CSSStyleValue',
+    'Shorthand value must be a base CSSStyleValue');
+}, 'Getting a shorthand property set explicitly in css rule returns ' +
+   'a base CSSStyleValue');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'margin-top: 1px');
+  const result = styleMap.get('margin');
+  assert_equals(result, null,
+    'Shorthand value must be null as it is not explicitly set');
+}, 'Getting a shorthand property that is partially set in css rule ' +
+   'returns null');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/get.html b/css/css-typed-om/the-stylepropertymap/declared/get.html
new file mode 100644
index 0000000..c4215cb
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/get.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.get</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<meta name="assert" content="Test declared StylePropertyMap.get" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '--foo: auto');
+  assert_equals(styleMap.get('--Foo'), null);
+}, 'Getting a custom property not in the CSS rule returns null');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '');
+  assert_equals(styleMap.get('width'), null);
+}, 'Getting a valid property not in the CSS rule returns null');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'width: 10px; height: 20px');
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(10, 'px'));
+}, 'Getting a valid property from CSS rule returns the correct entry');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '--foo: auto; --bar: 10px');
+  assert_style_value_equals(styleMap.get('--foo'),
+      new CSSUnparsedValue([' auto']));
+}, 'Getting a valid custom property from CSS rule returns the ' +
+   'correct entry');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t,
+      'width: 10px; transition-duration: 1s, 2s; height: 10px;');
+  assert_style_value_equals(styleMap.get('transition-duration'),
+      new CSSUnitValue(1, 's'));
+}, 'Getting a list-valued property from CSS rule returns only ' +
+   'the first value');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'height: 20px; width: 10px;');
+  assert_style_value_equals(styleMap.get('wIdTh'), new CSSUnitValue(10, 'px'));
+}, 'Declared StylePropertyMap.get is not case-sensitive');
+
+test(t => {
+  const [rule, styleMap] = createRuleWithDeclaredStyleMap(t, 'width: 10px;');
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(10, 'px'));
+  rule.style.width = '20px';
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(20, 'px'));
+}, 'Declared StylePropertyMap.get reflects changes in the CSS rule');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/getAll-shorthand.html b/css/css-typed-om/the-stylepropertymap/declared/getAll-shorthand.html
new file mode 100644
index 0000000..16dda49
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/getAll-shorthand.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.getAll with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-getall">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'margin: 1px 2px 3px 4px');
+  const result = styleMap.getAll('margin');
+  assert_not_equals(result, null, 'Result must not be null');
+  assert_equals(result.length, 1, 'Result must be a list with one item');
+  assert_class_string(result[0], 'CSSStyleValue',
+    'Only item in result must be a base CSSStyleValue');
+}, 'StylePropertyMap.getAll() with a shorthand property set explicitly in ' +
+   'css rule returns a base CSSStyleValue');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'margin-top: 1px');
+  const result = styleMap.getAll('margin');
+  assert_equals(result.length, 0, 'Result must be an empty list');
+}, 'StylePropertyMap.getAll() with a shorthand property that is partially ' +
+   'in css rule returns empty list');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/getAll.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/getAll.tentative.html
new file mode 100644
index 0000000..dd4e90b
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/getAll.tentative.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.getAll tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.getAll('lemon'));
+}, 'Calling StylePropertyMap.getAll with an unsupported property throws a TypeError');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t);
+  assert_style_value_array_equals(styleMap.getAll('height'), []);
+}, 'Calling StylePropertyMap.getAll with a property not in the property model returns an empty list');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '--foo: auto');
+  assert_style_value_array_equals(styleMap.getAll('--Foo'), []);
+}, 'Calling StylePropertyMap.getAll with a custom property not in the property model returns an empty list');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'width: 10px; height: 20px');
+  assert_style_value_array_equals(styleMap.getAll('width'), [CSS.px(10)]);
+}, 'Calling StylePropertyMap.getAll with a valid property returns a single element list with the correct entry');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'height: 20px; width: 10px');
+  assert_style_value_array_equals(styleMap.getAll('wIdTh'), [CSS.px(10)]);
+}, 'StylePropertyMap.getAll is case-insensitive');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '--foo: auto; --bar: 10px');
+  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue([' auto'])]);
+}, 'Calling StylePropertyMap.getAll with a valid custom property returns a single element list with the correct entry');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'width: 10px; transition-duration: 1s, 2s; height: 20px');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+}, 'Calling StylePropertyMap.getAll with a list-valued property returns all the values');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/has.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/has.tentative.html
new file mode 100644
index 0000000..93d29d9
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/has.tentative.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.has tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#check-if-stylepropertymap-has-a-property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.has('lemon'));
+}, 'Calling StylePropertyMap.has with an unsupported property throws a TypeError');
+
+const gTestCases = [
+  { property: 'height', expected: false, desc: 'a property not in the property model' },
+  { property: '--Foo', expected: false, desc: 'a custom property not in the property model' },
+  { property: 'width', expected: true, desc: 'a valid property' },
+  { property: 'wIdTh', expected: true, desc: 'a valid property in mixed case' },
+  { property: 'margin', expected: true, desc: 'a valid shorthand specified explicitly' },
+  { property: 'padding', expected: false, desc: 'a valid shorthand only partially specified' },
+  { property: '--foo', expected: true, desc: 'a valid custom property' },
+  { property: 'transition-duration', expected: true, desc: 'a valid list-valued property' },
+];
+
+for (const {property, expected, desc} of gTestCases) {
+  test(t => {
+    const styleMap = createDeclaredStyleMap(t,
+      'width: 10px; --foo: auto; transition-duration: 1s, 2s; margin: 1px 2px 3px 4px; padding-left: 1px');
+    assert_equals(styleMap.has(property), expected);
+  }, 'Calling StylePropertyMap.has with ' + desc + ' returns ' + expected);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/iterable.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/iterable.tentative.html
new file mode 100644
index 0000000..74a100a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/iterable.tentative.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap iterable tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#the-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '');
+  assert_array_equals([...styleMap.entries()], []);
+}, 'Iterating over an empty StylePropertyMap gives a zero-length array');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '--A: A; width: 10px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;');
+  assert_array_equals([...styleMap.keys()],
+    ['--A', 'width', '--C', 'transition-duration', 'color', '--B']);
+}, 'StylePropertyMap iterates properties in correct order');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'width: 10px; height: 5px');
+  const keys = [...styleMap.keys()], values = [...styleMap.values()];
+
+  assert_array_equals(keys, ['width', 'height']);
+  assert_style_value_array_equals(values[0], [CSS.px(10)]);
+  assert_style_value_array_equals(values[1], [CSS.px(5)]);
+}, 'StylePropertyMap iterator returns CSS properties with the correct CSSStyleValue');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, 'transition-duration: 1s, 2s');
+  const keys = [...styleMap.keys()], values = [...styleMap.values()];
+
+  assert_array_equals(keys, ['transition-duration']);
+  assert_style_value_array_equals(values[0], [CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap iterator returns list-valued properties with the correct CSSStyleValue');
+
+test(t => {
+  const styleMap = createDeclaredStyleMap(t, '--A: A; --B: B; --C: C');
+  const keys = [...styleMap.keys()], values = [...styleMap.values()];
+
+  assert_array_equals(keys, ['--A', '--B', '--C']);
+  assert_style_value_array_equals(values[0], [new CSSUnparsedValue([' A'])]);
+  assert_style_value_array_equals(values[1], [new CSSUnparsedValue([' B'])]);
+  assert_style_value_array_equals(values[2], [new CSSUnparsedValue([' C'])]);
+}, 'StylePropertyMap iterator returns custom properties with the correct CSSStyleValue');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/set-shorthand.html b/css/css-typed-om/the-stylepropertymap/declared/set-shorthand.html
new file mode 100644
index 0000000..2d6ec13
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/set-shorthand.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Declared StylePropertyMap.set() with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#set-a-value-on-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { property: 'margin', values: [CSS.deg(0)], desc: 'an invalid CSSStyleValue' },
+  { property: 'margin', values: ['10s'], desc: 'an invalid String' },
+];
+
+for (const {property, values, desc} of gInvalidTestCases) {
+  test(t => {
+    let styleMap = createInlineStyleMap(t, '');
+    assert_throws(new TypeError(), () => styleMap.set(property, ...values));
+  }, 'Setting a shorthand with ' + desc + ' on css rule throws TypeError');
+}
+
+test(t => {
+  let [elem, styleMap] = createRuleWithDeclaredStyleMap(t, 'margin: 1px 2px 3px 4px');
+
+  const result = styleMap.get('margin');
+  elem.style.margin = '';
+  styleMap.set('margin', result);
+
+  assert_equals(elem.style.getPropertyValue('margin'), '1px 2px 3px 4px');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '1px');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '2px');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '3px');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '4px');
+}, 'Setting a shorthand with a CSSStyleValue updates css rule');
+
+test(t => {
+  let [elem, styleMap] = createRuleWithDeclaredStyleMap(t);
+
+  styleMap.set('margin', '1px 2px 3px 4px');
+
+  assert_equals(elem.style.getPropertyValue('margin'), '1px 2px 3px 4px');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '1px');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '2px');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '3px');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '4px');
+}, 'Setting a shorthand with a string updates css rule');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/set.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/set.tentative.html
new file mode 100644
index 0000000..6161f94
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/declared/set.tentative.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.set</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#set-a-value-on-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { property: 'lemon', values: ['ade'], desc: 'an unsupported property name' },
+  { property: null, values: ['foo'], desc: 'an null property name' },
+  { property: 'width', values: [CSS.deg(0)], desc: 'an invalid CSSStyleValue' },
+  { property: 'width', values: ['10s'], desc: 'an invalid String' },
+];
+
+for (const {property, values, desc} of gInvalidTestCases) {
+  test(t => {
+    let styleMap = createDeclaredStyleMap(t, '');
+    assert_throws(new TypeError(), () => styleMap.set(property, ...values));
+  }, 'Setting a StylePropertyMap with ' + desc + ' throws TypeError');
+}
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+  assert_throws(new TypeError(), () => styleMap.set('width', CSS.px(10), CSS.px(10)));
+}, 'Setting a non list-valued property with multiple arguments throws TypeError');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+  assert_throws(new TypeError(), () => styleMap.set('width', '1s, 2s'));
+}, 'Setting a non list-valued property with list-valued string throws TypeError');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+  assert_throws(new TypeError(), () => {
+    styleMap.set('transition-duration', '1s', new CSSUnparsedValue([]));
+  });
+}, 'Setting a list-valued property with a CSSUnparsedValue and other ' +
+   'values throws TypeError');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+  assert_throws(new TypeError(), () => {
+    styleMap.set('transition-duration', '1s', 'var(--A)');
+  });
+}, 'Setting a list-valued property with a var ref() and other values ' +
+   'throws TypeError');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+
+  styleMap.set('width', CSS.px(10));
+  assert_style_value_array_equals(styleMap.get('width'), CSS.px(10));
+
+  styleMap.set('width', '20px');
+  assert_style_value_array_equals(styleMap.get('width'), CSS.px(20));
+}, 'Setting a property with CSSStyleValue or String updates its value');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+
+  styleMap.set('transition-duration', CSS.s(1), '2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+
+  styleMap.set('transition-duration', '3s', CSS.s(4));
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(3), CSS.s(4)]);
+}, 'Setting a list-valued property with CSSStyleValue or String updates its values');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+
+  styleMap.set('transition-duration', '1s, 2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+}, 'Setting a list-valued property with a list-valued string updates its value');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, '');
+
+  styleMap.set('--foo', new CSSUnparsedValue(['auto']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['auto']));
+
+  styleMap.set('--foo', '20px');
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['20px']));
+}, 'Setting a custom property with CSSStyleValue or String updates its value');
+
+test(t => {
+  let styleMap = createDeclaredStyleMap(t, 'transition-duration: 5s, 10s');
+
+  styleMap.set('tRaNsItIoN-dUrAtIoN', '1s', CSS.s(2));
+  const result = styleMap.getAll('transition-duration');
+  assert_style_value_array_equals(result, [CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap.set is case-insensitive');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/append.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/append.tentative.html
new file mode 100644
index 0000000..36c14d7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/append.tentative.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.append tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#append-to-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { property: 'lemon', values: ['ade'], desc: 'an unsupported property name' },
+  { property: null, values: ['foo'], desc: 'an null property name' },
+  { property: 'width', values: ['10px'], desc: 'a property that is not list valued' },
+  { property: 'margin', values: ['10px'], desc: 'a shorthand property' },
+  { property: 'transition-duration', values: [CSS.px(10)], desc: 'an invalid CSSStyleValue' },
+  { property: 'transition-duration', values: ['10px'], desc: 'an invalid String value' },
+  { property: 'transition-duration', values: [CSS.s(1), '10px', CSS.px(10)], desc: 'a mix of valid and invalid values' },
+  { property: 'transition-duration', values: [new CSSUnparsedValue([])], desc: 'a CSSUnparsedValue' },
+  { property: 'transition-duration', values: ['var(--A)'], desc: 'a var ref' },
+];
+
+for (const {property, values, desc} of gInvalidTestCases) {
+  test(t => {
+    let styleMap = createInlineStyleMap(t, '');
+    assert_throws(new TypeError(), () => styleMap.append(property, ...values));
+  }, 'Calling StylePropertyMap.append with ' + desc + ' throws TypeError');
+}
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+
+  styleMap.append('transition-duration', CSS.s(1), '2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+
+  styleMap.append('transition-duration', '3s', CSS.s(4));
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2), CSS.s(3), CSS.s(4)]);
+}, 'Appending a list-valued property with CSSStyleValue or String updates its values');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+
+  styleMap.append('transition-duration', '1s, 2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+
+  styleMap.append('transition-duration', '3s, 4s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2), CSS.s(3), CSS.s(4)]);
+}, 'Appending a list-valued property with list-valued string updates its values');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, 'transition-duration: 5s, 10s');
+
+  styleMap.append('tRaNsItIoN-dUrAtIoN', '1s', CSS.s(2));
+  const result = styleMap.getAll('transition-duration');
+  assert_style_value_array_equals(result, [CSS.s(5), CSS.s(10), CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap.append is case-insensitive');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/clear.html b/css/css-typed-om/the-stylepropertymap/inline/clear.html
new file mode 100644
index 0000000..24ca699
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/clear.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.clear</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-clear">
+<meta name="assert" content="Test inline StylePropertyMap.clear" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+  styleMap.clear();
+  assert_array_equals([...styleMap], []);
+}, 'Clearing an empty inline style is a no-op');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '--foo: auto; width: 10px; transition-duration: 1s, 2s');
+
+  styleMap.clear();
+  assert_equals(styleMap.get('--foo'), null,
+    'Custom properties should be cleared');
+  assert_equals(styleMap.get('width'), null,
+    'CSS properties should be cleared');
+  assert_equals(styleMap.get('transition-duration'), null,
+    'List-valued properties should be cleared');
+  assert_array_equals([...styleMap], []);
+}, 'Can clear an inline style containing properties');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, 'width: 10px;');
+  styleMap.clear();
+
+  assert_equals(elem.style.width, '', 'Element inline style should be cleared');
+}, 'Inline StylePropertyMap.clear updates the element inline style');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/delete-invalid.html b/css/css-typed-om/the-stylepropertymap/inline/delete-invalid.html
new file mode 100644
index 0000000..cc2657e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/delete-invalid.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.delete Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#delete-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+  assert_throws(new TypeError(), () => styleMap.delete('lemon'));
+}, 'Deleting an unsupported property name throws a TypeError');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/delete-shorthand.html b/css/css-typed-om/the-stylepropertymap/inline/delete-shorthand.html
new file mode 100644
index 0000000..065c754
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/delete-shorthand.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.delete() with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#delete-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, '');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '');
+
+  styleMap.delete('margin');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '');
+}, 'Deleting a shorthand property not in the inline style is a no-op');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, 'margin: 10px');
+  assert_not_equals(elem.style.getPropertyValue('margin'), '');
+
+  styleMap.delete('margin');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '');
+}, 'Deleting a shorthand property in the inline style removes both it and ' +
+   'its longhands');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, 'margin: 10px');
+  assert_not_equals(elem.style.getPropertyValue('margin-top'), '');
+
+  styleMap.delete('margin-top');
+  assert_equals(elem.style.getPropertyValue('margin'), '');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '10px');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '10px');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '10px');
+}, 'Deleting a longhand property in the inline style removes both it and ' +
+   'its shorthand');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/delete.html b/css/css-typed-om/the-stylepropertymap/inline/delete.html
new file mode 100644
index 0000000..82b419b
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/delete.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.delete</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#delete-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, '');
+  assert_equals(elem.style.getPropertyValue('width'), '');
+
+  styleMap.delete('width');
+  assert_equals(elem.style.getPropertyValue('width'), '');
+}, 'Deleting a property not in the inline style is a no-op');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, 'width: 10px');
+  assert_not_equals(elem.style.getPropertyValue('width'), '');
+
+  styleMap.delete('width');
+  assert_equals(elem.style.getPropertyValue('width'), '');
+}, 'Deleting a property in the inline style removes it from the inline style');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, '--Foo: 10px');
+  assert_not_equals(elem.style.getPropertyValue('--Foo'), '');
+
+  styleMap.delete('--Foo');
+  assert_equals(elem.style.getPropertyValue('--Foo'), '');
+}, 'Deleting a custom property in the inline style removes it from the ' +
+   'inline style');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t,
+      'transition-duration: 1s, 2s');
+  assert_not_equals(elem.style.getPropertyValue('transition-duration'), '');
+
+  styleMap.delete('transition-duration');
+  assert_equals(elem.style.getPropertyValue('transition-duration'), '');
+}, 'Deleting a list-valued property in the inline style removes it from ' +
+   'the inline style');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, 'width: 10px');
+  assert_not_equals(elem.style.getPropertyValue('width'), '');
+
+  styleMap.delete('wIdTh');
+  assert_equals(elem.style.getPropertyValue('width'), '');
+}, 'Inline StylePropertyMap.delete is not case-sensitive');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/get-invalid.html b/css/css-typed-om/the-stylepropertymap/inline/get-invalid.html
new file mode 100644
index 0000000..b847567
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/get-invalid.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.get Error Handling</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createInlineStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.get('lemon'));
+}, 'Calling StylePropertyMap.get with an unsupported property throws a TypeError');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/get-shorthand.html b/css/css-typed-om/the-stylepropertymap/inline/get-shorthand.html
new file mode 100644
index 0000000..0355e39
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/get-shorthand.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.get with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'margin: 1px 2px 3px 4px');
+  const result = styleMap.get('margin');
+  assert_not_equals(result, null, 'Shorthand value must not be null');
+  assert_class_string(result, 'CSSStyleValue',
+    'Shorthand value must be a base CSSStyleValue');
+}, 'Getting an shorthand property set explicitly in inline style returns ' +
+   'a base CSSStyleValue');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'margin-top: 1px');
+  const result = styleMap.get('margin');
+  assert_equals(result, null,
+    'Shorthand value must be null as it is not explicitly set');
+}, 'Getting a shorthand property that is partially set in inline style ' +
+   'returns null');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/get.html b/css/css-typed-om/the-stylepropertymap/inline/get.html
new file mode 100644
index 0000000..8588e6e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/get.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.get</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
+<meta name="assert" content="Test inline StylePropertyMap.get" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '--foo: auto');
+  assert_equals(styleMap.get('--Foo'), null);
+}, 'Getting a custom property not in the inline style returns null');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '');
+  assert_equals(styleMap.get('width'), null);
+}, 'Getting a valid property not in the inline style returns null');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'width: 10px; height: 20px');
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(10, 'px'));
+}, 'Getting a valid property from inline style returns the correct entry');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '--foo: auto; --bar: 10px');
+  assert_style_value_equals(styleMap.get('--foo'),
+      new CSSUnparsedValue([' auto']));
+}, 'Getting a valid custom property from inline style returns the ' +
+   'correct entry');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t,
+      'width: 10px; transition-duration: 1s, 2s; height: 10px;');
+  assert_style_value_equals(styleMap.get('transition-duration'),
+      new CSSUnitValue(1, 's'));
+}, 'Getting a list-valued property from inline style returns only ' +
+   'the first value');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'height: 20px; width: 10px;');
+  assert_style_value_equals(styleMap.get('wIdTh'), new CSSUnitValue(10, 'px'));
+}, 'Declared StylePropertyMap.get is not case-sensitive');
+
+test(t => {
+  const [elem, styleMap] = createElementWithInlineStyleMap(t, 'width: 10px;');
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(10, 'px'));
+  elem.style.width = '20px';
+  assert_style_value_equals(styleMap.get('width'), new CSSUnitValue(20, 'px'));
+}, 'Declared StylePropertyMap.get reflects changes in the inline style');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/getAll-shorthand.html b/css/css-typed-om/the-stylepropertymap/inline/getAll-shorthand.html
new file mode 100644
index 0000000..f7051dd
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/getAll-shorthand.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.getAll with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-getall">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log">
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'margin: 1px 2px 3px 4px');
+  const result = styleMap.getAll('margin');
+  assert_not_equals(result, null, 'Result must not be null');
+  assert_equals(result.length, 1, 'Result must be a list with one item');
+  assert_class_string(result[0], 'CSSStyleValue',
+    'Only item in result must be a base CSSStyleValue');
+}, 'StylePropertyMap.getAll() with a shorthand property set explicitly in ' +
+   'inline style returns a base CSSStyleValue');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'margin-top: 1px');
+  const result = styleMap.getAll('margin');
+  assert_equals(result.length, 0, 'Result must be an empty list');
+}, 'StylePropertyMap.getAll() with a shorthand property that is partially ' +
+   'in inline style returns empty list');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/getAll.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/getAll.tentative.html
new file mode 100644
index 0000000..c7e3700
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/getAll.tentative.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.getAll tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createInlineStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.getAll('lemon'));
+}, 'Calling StylePropertyMap.getAll with an unsupported property throws a TypeError');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t);
+  assert_style_value_array_equals(styleMap.getAll('height'), []);
+}, 'Calling StylePropertyMap.getAll with a property not in the property model returns an empty list');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '--foo: auto');
+  assert_style_value_array_equals(styleMap.getAll('--Foo'), []);
+}, 'Calling StylePropertyMap.getAll with a custom property not in the property model returns an empty list');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'width: 10px; height: 20px');
+  assert_style_value_array_equals(styleMap.getAll('width'), [CSS.px(10)]);
+}, 'Calling StylePropertyMap.getAll with a valid property returns a single element list with the correct entry');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'height: 20px; width: 10px');
+  assert_style_value_array_equals(styleMap.getAll('wIdTh'), [CSS.px(10)]);
+}, 'StylePropertyMap.getAll is case-insensitive');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '--foo: auto; --bar: 10px');
+  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue([' auto'])]);
+}, 'Calling StylePropertyMap.getAll with a valid custom property returns a single element list with the correct entry');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'width: 10px; transition-duration: 1s, 2s; height: 20px');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+}, 'Calling StylePropertyMap.getAll with a list-valued property returns all the values');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/has.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/has.tentative.html
new file mode 100644
index 0000000..d252883
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/has.tentative.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.has tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#check-if-stylepropertymap-has-a-property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createInlineStyleMap(t);
+  assert_throws(new TypeError(), () => styleMap.has('lemon'));
+}, 'Calling StylePropertyMap.has with an unsupported property throws a TypeError');
+
+const gTestCases = [
+  { property: 'height', expected: false, desc: 'a property not in the property model' },
+  { property: '--Foo', expected: false, desc: 'a custom property not in the property model' },
+  { property: 'margin', expected: false, desc: 'a valid property in mixed case' },
+  { property: 'width', expected: true, desc: 'a valid property' },
+  { property: 'wIdTh', expected: true, desc: 'a valid property in mixed case' },
+  { property: '--foo', expected: true, desc: 'a valid custom property' },
+  { property: 'transition-duration', expected: true, desc: 'a valid list-valued property' },
+];
+
+for (const {property, expected, desc} of gTestCases) {
+  test(t => {
+    const styleMap = createInlineStyleMap(t, 'width: 10px; --foo: auto; transition-duration: 1s, 2s');
+    assert_equals(styleMap.has(property), expected);
+  }, 'Calling StylePropertyMap.has with ' + desc + ' returns ' + expected);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/iterable.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/iterable.tentative.html
new file mode 100644
index 0000000..d39fc14
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/iterable.tentative.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap iterable tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#the-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '');
+  assert_array_equals([...styleMap.entries()], []);
+}, 'Iterating over an empty StylePropertyMap gives a zero-length array');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '--A: A; width: 10px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;');
+  assert_array_equals([...styleMap.keys()],
+    ['--A', 'width', '--C', 'transition-duration', 'color', '--B']);
+}, 'StylePropertyMap iterates properties in inline style order');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'width: 10px; height: 5px');
+  const keys = [...styleMap.keys()], values = [...styleMap.values()];
+
+  assert_array_equals(keys, ['width', 'height']);
+  assert_style_value_array_equals(values[0], [CSS.px(10)]);
+  assert_style_value_array_equals(values[1], [CSS.px(5)]);
+}, 'StylePropertyMap iterator returns CSS properties with the correct CSSStyleValue');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, 'transition-duration: 1s, 2s');
+  const keys = [...styleMap.keys()], values = [...styleMap.values()];
+
+  assert_array_equals(keys, ['transition-duration']);
+  assert_style_value_array_equals(values[0], [CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap iterator returns list-valued properties with the correct CSSStyleValue');
+
+test(t => {
+  const styleMap = createInlineStyleMap(t, '--A: A; --B: B; --C: C');
+  const keys = [...styleMap.keys()], values = [...styleMap.values()];
+
+  assert_array_equals(keys, ['--A', '--B', '--C']);
+  assert_style_value_array_equals(values[0], [new CSSUnparsedValue([' A'])]);
+  assert_style_value_array_equals(values[1], [new CSSUnparsedValue([' B'])]);
+  assert_style_value_array_equals(values[2], [new CSSUnparsedValue([' C'])]);
+}, 'StylePropertyMap iterator returns custom properties with the correct CSSStyleValue');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/set-shorthand.html b/css/css-typed-om/the-stylepropertymap/inline/set-shorthand.html
new file mode 100644
index 0000000..e00d07c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/set-shorthand.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline StylePropertyMap.set() with shorthands</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#set-a-value-on-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { property: 'margin', values: [CSS.deg(0)], desc: 'an invalid CSSStyleValue' },
+  { property: 'margin', values: ['10s'], desc: 'an invalid String' },
+];
+
+for (const {property, values, desc} of gInvalidTestCases) {
+  test(t => {
+    let styleMap = createInlineStyleMap(t, '');
+    assert_throws(new TypeError(), () => styleMap.set(property, ...values));
+  }, 'Setting a shorthand with ' + desc + ' on inline style throws TypeError');
+}
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t, 'margin: 1px 2px 3px 4px');
+
+  const result = styleMap.get('margin');
+  elem.style.margin = '';
+  styleMap.set('margin', result);
+
+  assert_equals(elem.style.getPropertyValue('margin'), '1px 2px 3px 4px');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '1px');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '2px');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '3px');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '4px');
+}, 'Setting a shorthand with a CSSStyleValue updates inline style');
+
+test(t => {
+  let [elem, styleMap] = createElementWithInlineStyleMap(t);
+
+  styleMap.set('margin', '1px 2px 3px 4px');
+
+  assert_equals(elem.style.getPropertyValue('margin'), '1px 2px 3px 4px');
+  assert_equals(elem.style.getPropertyValue('margin-top'), '1px');
+  assert_equals(elem.style.getPropertyValue('margin-right'), '2px');
+  assert_equals(elem.style.getPropertyValue('margin-bottom'), '3px');
+  assert_equals(elem.style.getPropertyValue('margin-left'), '4px');
+}, 'Setting a shorthand with a string updates inline style');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/inline/set.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/set.tentative.html
new file mode 100644
index 0000000..411323a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/inline/set.tentative.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.set</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#set-a-value-on-a-stylepropertymap">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { property: 'lemon', values: ['ade'], desc: 'an unsupported property name' },
+  { property: null, values: ['foo'], desc: 'an null property name' },
+  { property: 'width', values: [CSS.deg(0)], desc: 'an invalid CSSStyleValue' },
+  { property: 'width', values: ['10s'], desc: 'an invalid String' },
+];
+
+for (const {property, values, desc} of gInvalidTestCases) {
+  test(t => {
+    let styleMap = createInlineStyleMap(t, '');
+    assert_throws(new TypeError(), () => styleMap.set(property, ...values));
+  }, 'Setting a StylePropertyMap with ' + desc + ' throws TypeError');
+}
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+  assert_throws(new TypeError(), () => styleMap.set('width', CSS.px(10), CSS.px(10)));
+}, 'Setting a non list-valued property with multiple arguments throws TypeError');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+  assert_throws(new TypeError(), () => styleMap.set('width', '1s, 2s'));
+}, 'Setting a non list-valued property with list-valued string throws TypeError');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+  assert_throws(new TypeError(), () => {
+    styleMap.set('transition-duration', '1s', new CSSUnparsedValue([]));
+  });
+}, 'Setting a list-valued property with a CSSUnparsedValue and other ' +
+   'values throws TypeError');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+  assert_throws(new TypeError(), () => {
+    styleMap.set('transition-duration', '1s', 'var(--A)');
+  });
+}, 'Setting a list-valued property with a var ref() and other values ' +
+   'throws TypeError');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+
+  styleMap.set('width', CSS.px(10));
+  assert_style_value_array_equals(styleMap.get('width'), CSS.px(10));
+
+  styleMap.set('width', '20px');
+  assert_style_value_array_equals(styleMap.get('width'), CSS.px(20));
+}, 'Setting a property with CSSStyleValue or String updates its value');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+
+  styleMap.set('transition-duration', CSS.s(1), '2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+
+  styleMap.set('transition-duration', '3s', CSS.s(4));
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(3), CSS.s(4)]);
+}, 'Setting a list-valued property with CSSStyleValue or String updates its values');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+
+  styleMap.set('transition-duration', '1s, 2s');
+  assert_style_value_array_equals(styleMap.getAll('transition-duration'), [CSS.s(1), CSS.s(2)]);
+}, 'Setting a list-valued property with a list-valued string updates its value');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, '');
+
+  styleMap.set('--foo', new CSSUnparsedValue(['auto']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['auto']));
+
+  styleMap.set('--foo', '20px');
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['20px']));
+}, 'Setting a custom property with CSSStyleValue or String updates its value');
+
+test(t => {
+  let styleMap = createInlineStyleMap(t, 'transition-duration: 5s, 10s');
+
+  styleMap.set('tRaNsItIoN-dUrAtIoN', '1s', CSS.s(2));
+  const result = styleMap.getAll('transition-duration');
+  assert_style_value_array_equals(result, [CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap.set is case-insensitive');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/alignment-baseline.html b/css/css-typed-om/the-stylepropertymap/properties/alignment-baseline.html
new file mode 100644
index 0000000..aab4a6a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/alignment-baseline.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'alignment-baseline' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('alignment-baseline', [
+  { syntax: 'baseline' },
+  { syntax: 'text-bottom' },
+  { syntax: 'alphabetic' },
+  { syntax: 'ideographic' },
+  { syntax: 'middle' },
+  { syntax: 'central' },
+  { syntax: 'mathematical' },
+  { syntax: 'text-top' },
+  { syntax: 'bottom' },
+  { syntax: 'center' },
+  { syntax: 'top' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/all.html b/css/css-typed-om/the-stylepropertymap/properties/all.html
new file mode 100644
index 0000000..dd618bc
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/all.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'all' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('all', []);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-delay.html b/css/css-typed-om/the-stylepropertymap/properties/animation-delay.html
new file mode 100644
index 0000000..91ee95e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-delay.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation-delay' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-delay', [
+  { syntax: '<time>' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-direction.html b/css/css-typed-om/the-stylepropertymap/properties/animation-direction.html
new file mode 100644
index 0000000..0d4f6c0
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-direction.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation-direction' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-direction', [
+  { syntax: 'normal' },
+  { syntax: 'reverse' },
+  { syntax: 'alternate-reverse' },
+  { syntax: 'alternate' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-duration.html b/css/css-typed-om/the-stylepropertymap/properties/animation-duration.html
new file mode 100644
index 0000000..f0db513
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-duration.html
@@ -0,0 +1,19 @@
+<meta charset="utf-8">
+<title>'animation-duration' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-duration', [
+  { syntax: '<time>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-fill-mode.html b/css/css-typed-om/the-stylepropertymap/properties/animation-fill-mode.html
new file mode 100644
index 0000000..419b988
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-fill-mode.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation-fill-mode' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-fill-mode', [
+  { syntax: 'none' },
+  { syntax: 'forwards' },
+  { syntax: 'backwards' },
+  { syntax: 'both' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-iteration-count.html b/css/css-typed-om/the-stylepropertymap/properties/animation-iteration-count.html
new file mode 100644
index 0000000..7902c80
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-iteration-count.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation-iteration-count' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-iteration-count', [
+  { syntax: 'infinite' },
+  { syntax: '<number>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-name.html b/css/css-typed-om/the-stylepropertymap/properties/animation-name.html
new file mode 100644
index 0000000..df5d44a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-name.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation-name' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-name', [
+  { syntax: 'none' },
+  // FIXME: This should be <custom-ident>, but the test harness doesn't
+  // currently support it.
+  { syntax: 'custom-ident' },
+]);
+
+runUnsupportedPropertyTests('animation-name', [
+  '"foo"'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-play-state.html b/css/css-typed-om/the-stylepropertymap/properties/animation-play-state.html
new file mode 100644
index 0000000..caa9611
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-play-state.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation-play-state' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-play-state', [
+  { syntax: 'running' },
+  { syntax: 'paused' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation-timing-function.html b/css/css-typed-om/the-stylepropertymap/properties/animation-timing-function.html
new file mode 100644
index 0000000..5ec3583
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation-timing-function.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation-timing-function' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('animation-timing-function', [
+  { syntax: 'linear' },
+  { syntax: 'ease' },
+  { syntax: 'ease-in' },
+  { syntax: 'ease-out' },
+  { syntax: 'ease-in-out' },
+  { syntax: 'step-start' },
+  { syntax: 'step-end' },
+]);
+
+runUnsupportedPropertyTests('animation-timing-function', [
+  'cubic-bezier(0.1, 0.7, 1.0, 0.1)', 'steps(4, end)', 'frames(10)'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/animation.html b/css/css-typed-om/the-stylepropertymap/properties/animation.html
new file mode 100644
index 0000000..92a4916
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/animation.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'animation' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('animation', [
+  'slidein 3s ease-in 1s infinite reverse both running',
+  'slidein .5s linear 1s infinite alternate'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/backdrop-filter.html b/css/css-typed-om/the-stylepropertymap/properties/backdrop-filter.html
new file mode 100644
index 0000000..837ceb7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/backdrop-filter.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'backdrop-filter' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('backdrop-filter', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('filter', [
+  'blur(2px)',
+  'url(filters.svg) blur(4px) saturate(150%)',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/backface-visibility.html b/css/css-typed-om/the-stylepropertymap/properties/backface-visibility.html
new file mode 100644
index 0000000..ae9502d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/backface-visibility.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'backface-visibility' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('backface-visibility', [
+  { syntax: 'visible' },
+  { syntax: 'hidden' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-attachment.html b/css/css-typed-om/the-stylepropertymap/properties/background-attachment.html
new file mode 100644
index 0000000..77adf0d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-attachment.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-attachment' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('background-attachment', [
+  { syntax: 'scroll' },
+  { syntax: 'fixed' },
+  { syntax: 'local' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-blend-mode.html b/css/css-typed-om/the-stylepropertymap/properties/background-blend-mode.html
new file mode 100644
index 0000000..92082e2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-blend-mode.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-blend-mode' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('background-blend-mode', [
+  { syntax: 'normal' },
+  { syntax: 'multiply' },
+  { syntax: 'screen' },
+  { syntax: 'overlay' },
+  { syntax: 'darken' },
+  { syntax: 'lighten' },
+  { syntax: 'color-dodge' },
+  { syntax: 'color-burn' },
+  { syntax: 'hard-light' },
+  { syntax: 'soft-light' },
+  { syntax: 'difference' },
+  { syntax: 'exclusion' },
+  { syntax: 'hue' },
+  { syntax: 'saturation' },
+  { syntax: 'color' },
+  { syntax: 'luminosity' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-clip.html b/css/css-typed-om/the-stylepropertymap/properties/background-clip.html
new file mode 100644
index 0000000..46d16a6
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-clip.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-clip' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('background-clip', [
+  { syntax: 'border-box' },
+  { syntax: 'padding-box' },
+  { syntax: 'content-box' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-color.html b/css/css-typed-om/the-stylepropertymap/properties/background-color.html
new file mode 100644
index 0000000..f684a40
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('background-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('background-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-image.html b/css/css-typed-om/the-stylepropertymap/properties/background-image.html
new file mode 100644
index 0000000..b3ddded
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-image.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-image' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('background-image', [
+  { syntax: 'none' },
+  { syntax: '<image>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-origin.html b/css/css-typed-om/the-stylepropertymap/properties/background-origin.html
new file mode 100644
index 0000000..511b5dc
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-origin.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-origin' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('background-origin', [
+  { syntax: 'border-box' },
+  { syntax: 'padding-box' },
+  { syntax: 'content-box' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-position.html b/css/css-typed-om/the-stylepropertymap/properties/background-position.html
new file mode 100644
index 0000000..8e5b09b
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-position.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-position' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('background-position', [
+  { syntax: '<position>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-repeat.html b/css/css-typed-om/the-stylepropertymap/properties/background-repeat.html
new file mode 100644
index 0000000..bc50169
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-repeat.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-repeat' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('background-position', [
+  { syntax: 'repeat-x' },
+  { syntax: 'repeat-y' },
+  { syntax: 'repeat' },
+  { syntax: 'space' },
+  { syntax: 'round' },
+  { syntax: 'no-repeat' },
+]);
+
+runUnsupportedPropertyTests('background-position', [
+  'space repeat'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background-size.html b/css/css-typed-om/the-stylepropertymap/properties/background-size.html
new file mode 100644
index 0000000..7de0c44
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background-size.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background-size' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('background-size', [
+  { syntax: '<length>' },
+  { syntax: '<percentage>' },
+  { syntax: 'auto' },
+  { syntax: 'cover' },
+  { syntax: 'contain' },
+]);
+
+runUnsupportedPropertyTests('background-size', [
+  '200px 100px'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/background.html b/css/css-typed-om/the-stylepropertymap/properties/background.html
new file mode 100644
index 0000000..8926441
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/background.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'background' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('background', []);
+runUnsupportedPropertyTests('background', [
+  'green', 'content-box radial-gradient(crimson, skyblue)',
+  'no-repeat url("http://foo.com")',
+  'left 5% / 15% 60% repeat-x url("http://foo.com")',
+  'center / contain no-repeat url("foo.com"), red'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/baseline-shift.html b/css/css-typed-om/the-stylepropertymap/properties/baseline-shift.html
new file mode 100644
index 0000000..c8e90b7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/baseline-shift.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'baseline-shift' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('baseline-shift', [
+  { syntax: 'sub' },
+  { syntax: 'super' },
+  { syntax: '<percentage>' },
+  { syntax: '<length>' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/block-size.html b/css/css-typed-om/the-stylepropertymap/properties/block-size.html
new file mode 100644
index 0000000..d1a1e09
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/block-size.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'block-size' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('block-size', [
+  { syntax: 'auto' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('min-block-size', [
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('max-block-size', [
+  { syntax: 'none' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
\ No newline at end of file
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-collapse.html b/css/css-typed-om/the-stylepropertymap/properties/border-collapse.html
new file mode 100644
index 0000000..3a48ef0
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-collapse.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'border-collapse' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('border-collapse', [
+  { syntax: 'separate' },
+  { syntax: 'collapse' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-color.html b/css/css-typed-om/the-stylepropertymap/properties/border-color.html
new file mode 100644
index 0000000..3ce4ca9
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-color.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'border-color' properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const prefix of ['top', 'left', 'right', 'bottom']) {
+  runPropertyTests(`border-${prefix}-color`, [
+    {
+      syntax: 'currentcolor',
+      // computes to a <color>, which is not supported in level 1
+      computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+    }
+  ]);
+
+  // <color>s are not supported in level 1
+  runUnsupportedPropertyTests(`border-${prefix}-color`, [
+    'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+    'transparent'
+  ]);
+}
+
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-image-outset.html b/css/css-typed-om/the-stylepropertymap/properties/border-image-outset.html
new file mode 100644
index 0000000..46da4e3
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-image-outset.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'border-image-outset' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('border-image-outset', [
+  // Computed value is always four values, which are not supported in
+  // Typed OM level 1.
+  {
+    syntax: '<length>',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: '<number>',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+]);
+
+runUnsupportedPropertyTests('border-image-outset', [
+  '1 1.2', '30px 2 45px', '7px 12px 14px 5px'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-image-repeat.html b/css/css-typed-om/the-stylepropertymap/properties/border-image-repeat.html
new file mode 100644
index 0000000..885a947
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-image-repeat.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'border-image-repeat' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('border-image-repeat', [
+  // Computed value is always a pair of values, which are not supported in
+  // Typed OM level 1.
+  {
+    syntax: 'stretch',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: 'repeat',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: 'round',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: 'space',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+]);
+
+runUnsupportedPropertyTests('border-image-repeat', [
+  'stretch repeat', 'round space'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-image-slice.html b/css/css-typed-om/the-stylepropertymap/properties/border-image-slice.html
new file mode 100644
index 0000000..2649446
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-image-slice.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'border-image-slice' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('border-image-slice', [
+  // Computed value is always four values, which are not supported in
+  // Typed OM level 1.
+  {
+    syntax: '<number>',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: '<percentage>',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+]);
+
+runUnsupportedPropertyTests('border-image-slice', [
+  '30 fill', '30 40 50', '30 40 50 60 fill'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-image-source.html b/css/css-typed-om/the-stylepropertymap/properties/border-image-source.html
new file mode 100644
index 0000000..b9ddf19
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-image-source.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'border-image-source' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('border-image-source', [
+  { syntax: 'none' },
+  { syntax: '<image>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-image-width.html b/css/css-typed-om/the-stylepropertymap/properties/border-image-width.html
new file mode 100644
index 0000000..7520a98
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-image-width.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'border-image-width' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('border-image-width', [
+  // Computed value is always four values, which are not supported in
+  // Typed OM level 1.
+  {
+    syntax: '<length>',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: '<percentage>',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: '<number>',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+  {
+    syntax: 'auto',
+    computed: (_, result) => assert_is_unsupported(result)
+  },
+]);
+
+runUnsupportedPropertyTests('border-image-width', [
+  '2em 3em', '5% 15% 10%', '5% 2em 10% auto'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-radius.html b/css/css-typed-om/the-stylepropertymap/properties/border-radius.html
new file mode 100644
index 0000000..1d7b359
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-radius.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>border radius properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['top-left', 'top-right', 'bottom-left', 'bottom-right']) {
+  // Computed value is always a pair of values, which are not supported in
+  // Typed OM level 1.
+  runPropertyTests('border-' + suffix + '-radius', [
+    {
+      syntax: '<length>',
+      computed: (_, result) => assert_is_unsupported(result)
+    },
+    {
+      syntax: '<percentage>',
+      computed: (_, result) => assert_is_unsupported(result)
+    },
+  ]);
+}
+
+// shorthand
+runUnsupportedPropertyTests('border-radius', [
+  '30px', '25% 10%', '10% / 50%', '50% 20% / 10% 40%'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-style.html b/css/css-typed-om/the-stylepropertymap/properties/border-style.html
new file mode 100644
index 0000000..1328ab1
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-style.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>border style properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['top', 'left', 'right', 'bottom']) {
+  runPropertyTests('border-' + suffix + '-style', [
+    { syntax: 'none' },
+    { syntax: 'solid' },
+    // and other keywords
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/border-width.html b/css/css-typed-om/the-stylepropertymap/properties/border-width.html
new file mode 100644
index 0000000..5066637
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/border-width.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>border width properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_zero_px(result) {
+  assert_style_value_equals(result, new CSSUnitValue(0, 'px'));
+}
+
+for (const suffix of ['top', 'left', 'right', 'bottom']) {
+  runPropertyTests('border-' + suffix + '-width', [
+    // Computed value is 0 when border-style is 'none'.
+    // FIXME: Add separate test where border-style is not 'none' or 'hidden'.
+    {
+      syntax: 'thin',
+      computed: (_, result) => assert_is_zero_px(result)
+    },
+    {
+      syntax: 'medium',
+      computed: (_, result) => assert_is_zero_px(result)
+    },
+    {
+      syntax: 'thick',
+      computed: (_, result) => assert_is_zero_px(result)
+    },
+    {
+      syntax: '<length>',
+      specified: assert_is_equal_with_range_handling,
+      computed: (_, result) => assert_is_zero_px(result)
+    },
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/bottom.html b/css/css-typed-om/the-stylepropertymap/properties/bottom.html
new file mode 100644
index 0000000..36fe514
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/bottom.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'bottom' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('bottom', [
+  { syntax: 'auto' },
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/box-shadow.html b/css/css-typed-om/the-stylepropertymap/properties/box-shadow.html
new file mode 100644
index 0000000..040e992
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/box-shadow.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'box-shadow' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('box-shadow', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('box-shadow', [
+  '10px 5px 5px red',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/box-sizing.html b/css/css-typed-om/the-stylepropertymap/properties/box-sizing.html
new file mode 100644
index 0000000..551adbf
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/box-sizing.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'box-sizing' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('box-sizing', [
+  { syntax: 'content-box'},
+  { syntax: 'border-box' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/break.html b/css/css-typed-om/the-stylepropertymap/properties/break.html
new file mode 100644
index 0000000..66d24be
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/break.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'break' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['after', 'before']) {
+  runPropertyTests('break-' + suffix, [
+    { syntax: 'auto' },
+    { syntax: 'avoid' },
+    { syntax: 'avoid-column' },
+    { syntax: 'avoid-page' },
+    { syntax: 'avoid-region' },
+    { syntax: 'column' },
+    { syntax: 'left' },
+    { syntax: 'page' },
+    { syntax: 'recto' },
+    { syntax: 'region' },
+    { syntax: 'right' },
+    { syntax: 'verso' },
+  ]);
+}
+
+runPropertyTests('break-inside', [
+    { syntax: 'auto' },
+    { syntax: 'avoid' },
+    { syntax: 'avoid-column' },
+    { syntax: 'avoid-page' },
+    { syntax: 'avoid-region' },
+  ]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/caption-side.html b/css/css-typed-om/the-stylepropertymap/properties/caption-side.html
new file mode 100644
index 0000000..9ae512d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/caption-side.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'caption-side' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('caption-side', [
+  { syntax: 'top' },
+  { syntax: 'bottom' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/caret-color.html b/css/css-typed-om/the-stylepropertymap/properties/caret-color.html
new file mode 100644
index 0000000..5ea78a0
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/caret-color.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'caret-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('caret-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  },
+  {
+    syntax: 'auto',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('caret-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/center-coordinate.html b/css/css-typed-om/the-stylepropertymap/properties/center-coordinate.html
new file mode 100644
index 0000000..97722ec
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/center-coordinate.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'center-coordinate' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('cx', [
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+runPropertyTests('cy', [
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/clear.html b/css/css-typed-om/the-stylepropertymap/properties/clear.html
new file mode 100644
index 0000000..3852dd2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/clear.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'clear' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('clear', [
+  { syntax: 'none' },
+  { syntax: 'left' },
+  { syntax: 'right' },
+  { syntax: 'both' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/clip-path.html b/css/css-typed-om/the-stylepropertymap/properties/clip-path.html
new file mode 100644
index 0000000..d0c7d0a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/clip-path.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'clip-path' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('clip-path', [
+  { syntax: 'none' },
+  { syntax: 'fill-box' },
+  { syntax: 'stroke-box' },
+  { syntax: 'view-box' },
+  { syntax: 'margin-box' },
+  { syntax: 'border-box' },
+  { syntax: 'padding-box' },
+  { syntax: 'content-box' },
+  { syntax: '<url>' },
+]);
+
+runUnsupportedPropertyTests('clip', [
+  'inset(22% 12% 15px 35px)',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/clip-rule.html b/css/css-typed-om/the-stylepropertymap/properties/clip-rule.html
new file mode 100644
index 0000000..9e1c401
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/clip-rule.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'clip-rule' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('clip-rule', [
+  { syntax: 'nonzero' },
+  { syntax: 'evenodd' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/clip.html b/css/css-typed-om/the-stylepropertymap/properties/clip.html
new file mode 100644
index 0000000..fa08eb8
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/clip.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'clip' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('clip', [
+  { syntax: 'auto' }
+]);
+
+runUnsupportedPropertyTests('clip', [
+  'rect(0px, 1px, 2px, 3px)'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/color-interpolation.html b/css/css-typed-om/the-stylepropertymap/properties/color-interpolation.html
new file mode 100644
index 0000000..4713d90
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/color-interpolation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'color-interpolation' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('color-interpolation', [
+  { syntax: 'auto' },
+  { syntax: 'sRGB' },
+  { syntax: 'linearRGB' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/color-rendering.html b/css/css-typed-om/the-stylepropertymap/properties/color-rendering.html
new file mode 100644
index 0000000..3c47e3e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/color-rendering.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'color-rendering' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('color-rendering', [
+  { syntax: 'auto' },
+  { syntax: 'optimizeSpeed' },
+  { syntax: 'optimizeQuality' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/color.html b/css/css-typed-om/the-stylepropertymap/properties/color.html
new file mode 100644
index 0000000..f561829
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/column-count.html b/css/css-typed-om/the-stylepropertymap/properties/column-count.html
new file mode 100644
index 0000000..9dfca61
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/column-count.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'column-count' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('column-count', [
+  { syntax: 'auto' },
+  // FIXME: This should say <integer>, but the test harness currently
+  // doesn't support <integer> data type.
+  {
+    syntax: '<number>',
+    // column-count needs to be a positive integer
+    specified: (input, result) => {
+      if (input instanceof CSSUnitValue && (!Number.isInteger(input.value) || input.value < 1))
+        assert_style_value_equals(result, new CSSMathSum(input));
+      else
+        assert_style_value_equals(result, input);
+    },
+    computed: (input, result) => {
+      const number = input.to('number');
+      if (number < 1)
+        assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+      else if (!Number.isInteger(number.value))
+        assert_style_value_equals(result, new CSSUnitValue(Math.round(number.value), 'number'));
+      else
+        assert_style_value_equals(result, number);
+    }
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/column-rule-color.html b/css/css-typed-om/the-stylepropertymap/properties/column-rule-color.html
new file mode 100644
index 0000000..3d35b85
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/column-rule-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'column-rule-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('column-rule-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('column-rule-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/column-rule-style.html b/css/css-typed-om/the-stylepropertymap/properties/column-rule-style.html
new file mode 100644
index 0000000..8e56f62
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/column-rule-style.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'column-rule-style' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('column-rule-style', [
+  { syntax: 'none' },
+  { syntax: 'hidden' },
+  { syntax: 'dotted' },
+  { syntax: 'dashed' },
+  { syntax: 'solid' },
+  { syntax: 'double' },
+  { syntax: 'groove' },
+  { syntax: 'ridge' },
+  { syntax: 'inset' },
+  { syntax: 'outset' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/column-rule-width.html b/css/css-typed-om/the-stylepropertymap/properties/column-rule-width.html
new file mode 100644
index 0000000..bd3c0ac
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/column-rule-width.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'column-rule-width' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_zero_px(result) {
+  assert_style_value_equals(result, new CSSUnitValue(0, 'px'));
+}
+
+runPropertyTests('column-rule-width', [
+  // Computed value is 0 when column-rule-style is 'none'.
+  // FIXME: Add separate test where column-rule-style is not 'none' or 'hidden'.
+  {
+    syntax: 'thin',
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+  {
+    syntax: 'medium',
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+  {
+    syntax: 'thick',
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling,
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/column-span.html b/css/css-typed-om/the-stylepropertymap/properties/column-span.html
new file mode 100644
index 0000000..28087a1
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/column-span.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'column-span' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#dom-stylepropertymapreadonly-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#reify-stylevalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('column-span', [
+  { syntax: 'none' },
+  { syntax: 'all' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/column-width.html b/css/css-typed-om/the-stylepropertymap/properties/column-width.html
new file mode 100644
index 0000000..97a00ff
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/column-width.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'column-width' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#dom-stylepropertymapreadonly-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#reify-stylevalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('column-width', [
+  { syntax: 'auto' },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/contain.html b/css/css-typed-om/the-stylepropertymap/properties/contain.html
new file mode 100644
index 0000000..0202da4
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/contain.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'contain' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('contain', [
+  { syntax: 'none' },
+  { syntax: 'strict' },
+  { syntax: 'content' },
+  { syntax: 'size' },
+  { syntax: 'layout' },
+  { syntax: 'style' },
+  { syntax: 'paint' },
+]);
+
+runUnsupportedPropertyTests('contain', [
+  'size layout', 'paint style layout size'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/coordinate.html b/css/css-typed-om/the-stylepropertymap/properties/coordinate.html
new file mode 100644
index 0000000..31ccbfa
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/coordinate.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'coordinate' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('x', [
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+runPropertyTests('y', [
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/counter-increment.html b/css/css-typed-om/the-stylepropertymap/properties/counter-increment.html
new file mode 100644
index 0000000..0dba637
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/counter-increment.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'counter-increment' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('counter-increment', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('counter-increment', [
+  'chapter', 'chapter 3'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/counter-reset.html b/css/css-typed-om/the-stylepropertymap/properties/counter-reset.html
new file mode 100644
index 0000000..5323f18
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/counter-reset.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'counter-reset' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('counter-reset', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('counter-reset', [
+  'chapter', 'chapter 3'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/cursor.html b/css/css-typed-om/the-stylepropertymap/properties/cursor.html
new file mode 100644
index 0000000..7465b1a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/cursor.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'cursor' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('cursor', [
+  { syntax: 'auto' },
+  { syntax: 'default' },
+  { syntax: 'none' },
+  { syntax: 'context-menu' },
+  { syntax: 'help' },
+  { syntax: 'pointer' },
+  { syntax: 'progress' },
+  { syntax: 'wait' },
+  { syntax: 'cell' },
+  { syntax: 'crosshair' },
+  { syntax: 'text' },
+  { syntax: 'vertical-text' },
+  { syntax: 'alias' },
+  { syntax: 'copy' },
+  { syntax: 'move' },
+  { syntax: 'no-drop' },
+  { syntax: 'not-allowed' },
+  { syntax: 'grab' },
+  { syntax: 'grabbing' },
+  { syntax: 'e-resize' },
+  { syntax: 'n-resize' },
+  { syntax: 'ne-resize' },
+  { syntax: 'nw-resize' },
+  { syntax: 's-resize' },
+  { syntax: 'se-resize' },
+  { syntax: 'sw-resize' },
+  { syntax: 'w-resize' },
+  { syntax: 'ew-resize' },
+  { syntax: 'ns-resize' },
+  { syntax: 'nesw-resize' },
+  { syntax: 'nwse-resize' },
+  { syntax: 'col-resize' },
+  { syntax: 'row-resize' },
+  { syntax: 'all-scroll' },
+  { syntax: 'zoom-in' },
+  { syntax: 'zoom-out' }
+]);
+
+runUnsupportedPropertyTests('cursor', [
+  'url(hand.cur), pointer', 'url(cursor1.png) 4 12, auto'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/d.html b/css/css-typed-om/the-stylepropertymap/properties/d.html
new file mode 100644
index 0000000..b73daa5
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/d.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'d' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('d', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('d', [
+  'path("M 100 100 L 300 100 L 200 300 Z")'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/direction.html b/css/css-typed-om/the-stylepropertymap/properties/direction.html
new file mode 100644
index 0000000..0681032
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/direction.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'direction' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('direction', [
+  { syntax: 'ltr' },
+  { syntax: 'rtl' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/display.html b/css/css-typed-om/the-stylepropertymap/properties/display.html
new file mode 100644
index 0000000..51f3532
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/display.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'display' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('display', [
+  { syntax: 'none' },
+  { syntax: 'block' },
+  // and other keywords
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/dominant-baseline.html b/css/css-typed-om/the-stylepropertymap/properties/dominant-baseline.html
new file mode 100644
index 0000000..9774ea2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/dominant-baseline.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'dominant-baseline' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('dominant-baseline', [
+  { syntax: 'auto' },
+  { syntax: 'text-bottom' },
+  { syntax: 'alphabetic' },
+  { syntax: 'ideographic' },
+  { syntax: 'middle' },
+  { syntax: 'central' },
+  { syntax: 'mathematical' },
+  { syntax: 'hanging' },
+  { syntax: 'text-top' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/empty-cells.html b/css/css-typed-om/the-stylepropertymap/properties/empty-cells.html
new file mode 100644
index 0000000..f05c09c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/empty-cells.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'empty-cells' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('empty-cells', [
+  { syntax: 'show' },
+  { syntax: 'hide' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/fill-color.html b/css/css-typed-om/the-stylepropertymap/properties/fill-color.html
new file mode 100644
index 0000000..5967f49
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/fill-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'fill-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('fill-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('fill-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/fill-opacity.html b/css/css-typed-om/the-stylepropertymap/properties/fill-opacity.html
new file mode 100644
index 0000000..0985ac6
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/fill-opacity.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'fill-opacity' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_equal_with_clamping(input, result) {
+  const number = input.to('number');
+
+  if (number.value < 0)
+    assert_style_value_equals(result, new CSSUnitValue(0, 'number'));
+  else if (number.value > 1)
+    assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+  else
+    assert_style_value_equals(result, input);
+}
+
+runPropertyTests('fill-opacity', [
+  {
+    syntax: '<number>',
+    computed: assert_is_equal_with_clamping
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/fill-rule.html b/css/css-typed-om/the-stylepropertymap/properties/fill-rule.html
new file mode 100644
index 0000000..30d5ea9
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/fill-rule.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'fill-rule' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('fill-rule', [
+  { syntax: 'nonzero' },
+  { syntax: 'evenodd' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/fill.html b/css/css-typed-om/the-stylepropertymap/properties/fill.html
new file mode 100644
index 0000000..28b3c55
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/fill.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'fill' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('fill', [
+  'black',
+  'red gray',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/filter.html b/css/css-typed-om/the-stylepropertymap/properties/filter.html
new file mode 100644
index 0000000..62ddeff
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/filter.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'filter' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('filter', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('filter', [
+  'blur(2px)',
+  'url(filters.svg) blur(4px) saturate(150%)',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flex-basis.html b/css/css-typed-om/the-stylepropertymap/properties/flex-basis.html
new file mode 100644
index 0000000..650b080
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flex-basis.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flex-basis' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('flex-basis', [
+  { syntax: 'auto' },
+  { syntax: 'content' },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling,
+  },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling,
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flex-direction.html b/css/css-typed-om/the-stylepropertymap/properties/flex-direction.html
new file mode 100644
index 0000000..49b7a98
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flex-direction.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flex-direction' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('flex-direction', [
+  { syntax: 'row' },
+  { syntax: 'row-reverse' },
+  { syntax: 'column' },
+  { syntax: 'column-reverse' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flex-flow.html b/css/css-typed-om/the-stylepropertymap/properties/flex-flow.html
new file mode 100644
index 0000000..3d6b46f
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flex-flow.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flex-flow' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('flex-flow', [
+  'row', 'column wrap', 'row-reverse wrap-reverse'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flex-grow.html b/css/css-typed-om/the-stylepropertymap/properties/flex-grow.html
new file mode 100644
index 0000000..9c04ad3
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flex-grow.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flex-grow' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('flex-grow', [
+  {
+    syntax: '<number>',
+    specified: assert_is_equal_with_range_handling,
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flex-shrink.html b/css/css-typed-om/the-stylepropertymap/properties/flex-shrink.html
new file mode 100644
index 0000000..30ee7db
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flex-shrink.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flex-shrink' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('flex-shrink', [
+  {
+    syntax: '<number>',
+    specified: assert_is_equal_with_range_handling,
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flex-wrap.html b/css/css-typed-om/the-stylepropertymap/properties/flex-wrap.html
new file mode 100644
index 0000000..56d14f6
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flex-wrap.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flex-wrap' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('flex-wrap', [
+  { syntax: 'nowrap' },
+  { syntax: 'wrap' },
+  { syntax: 'wrap-reverse' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flex.html b/css/css-typed-om/the-stylepropertymap/properties/flex.html
new file mode 100644
index 0000000..99a3e12
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flex.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flex' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('flex', [
+  'auto', '2', '1 30px', '2 2 10%'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/float.html b/css/css-typed-om/the-stylepropertymap/properties/float.html
new file mode 100644
index 0000000..569bae3
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/float.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'float' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('float', [
+  { syntax: 'left' },
+  { syntax: 'right' },
+  { syntax: 'none' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flood-color.html b/css/css-typed-om/the-stylepropertymap/properties/flood-color.html
new file mode 100644
index 0000000..bb3ddc9
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flood-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flood-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('flood-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('flood-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/flood-opacity.html b/css/css-typed-om/the-stylepropertymap/properties/flood-opacity.html
new file mode 100644
index 0000000..15824c7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/flood-opacity.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'flood-opacity' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_equal_with_clamping(input, result) {
+  const number = input.to('number');
+
+  if (number.value < 0)
+    assert_style_value_equals(result, new CSSUnitValue(0, 'number'));
+  else if (number.value > 1)
+    assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+  else
+    assert_style_value_equals(result, input);
+}
+
+runPropertyTests('flood-opacity', [
+  {
+    syntax: '<number>',
+    computed: assert_is_equal_with_clamping
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-family.html b/css/css-typed-om/the-stylepropertymap/properties/font-family.html
new file mode 100644
index 0000000..eb35ef1
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-family.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-family' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+// FIXME: font-family is list-valued. Run list-valued tests here too.
+runUnsupportedPropertyTests('font-family', [
+  'Georgia',
+  '"Gill Sans"',
+  'sans-serif',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-feature-settings.html b/css/css-typed-om/the-stylepropertymap/properties/font-feature-settings.html
new file mode 100644
index 0000000..4edf706
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-feature-settings.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-feature-settings' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-feature-settings', [
+  { syntax: 'normal' }
+]);
+
+runUnsupportedPropertyTests('font-feature-settings', [
+  '"dlig" 1',
+  '"smcp" on',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-kerning.html b/css/css-typed-om/the-stylepropertymap/properties/font-kerning.html
new file mode 100644
index 0000000..4eea684
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-kerning.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-kerning' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-kerning', [
+  { syntax: 'auto' },
+  { syntax: 'normal' },
+  { syntax: 'none' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-language-override.html b/css/css-typed-om/the-stylepropertymap/properties/font-language-override.html
new file mode 100644
index 0000000..daf3803
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-language-override.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-language-override' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-language-override', [
+  { syntax: 'normal' },
+]);
+
+runUnsupportedPropertyTests('font-language-override', [
+  '"SRB"',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-optical-sizing.html b/css/css-typed-om/the-stylepropertymap/properties/font-optical-sizing.html
new file mode 100644
index 0000000..0018ef1
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-optical-sizing.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-optical-sizing' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-optical-sizing', [
+  { syntax: 'auto' },
+  { syntax: 'none' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-palette.html b/css/css-typed-om/the-stylepropertymap/properties/font-palette.html
new file mode 100644
index 0000000..ad62945
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-palette.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-palette' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-palette', [
+  { syntax: 'normal' },
+  { syntax: 'light' },
+  { syntax: 'dark' },
+]);
+
+runUnsupportedPropertyTests('font-palette', [
+  'Augusta'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-presentation.html b/css/css-typed-om/the-stylepropertymap/properties/font-presentation.html
new file mode 100644
index 0000000..4448466
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-presentation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-presentation' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-presentation', [
+  { syntax: 'auto' },
+  { syntax: 'text' },
+  { syntax: 'emoji' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-size-adjust.html b/css/css-typed-om/the-stylepropertymap/properties/font-size-adjust.html
new file mode 100644
index 0000000..71cb54c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-size-adjust.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-size-adjust' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-size-adjust', [
+  { syntax: 'none' },
+  {
+    syntax: '<number>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-size.html b/css/css-typed-om/the-stylepropertymap/properties/font-size.html
new file mode 100644
index 0000000..fc76e0f
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-size.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-size' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const property of ['font-size', 'font-min-size', 'font-max-size']) {
+  // font-max-size also supports 'infinity' keyword
+  const infinity = property === 'font-max-size' ?
+    [{ syntax: 'infinity'}] : [];
+
+  runPropertyTests(property, [
+    ...infinity,
+    {
+      syntax: 'xx-small',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'x-small',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'small',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'medium',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'large',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'x-large',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'xx-large',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'larger',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: 'smaller',
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: '<length>',
+      specified: assert_is_equal_with_range_handling,
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+    {
+      syntax: '<percentage>',
+      specified: assert_is_equal_with_range_handling,
+      computed: (_, result) => assert_is_unit('px', result)
+    },
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-stretch.html b/css/css-typed-om/the-stylepropertymap/properties/font-stretch.html
new file mode 100644
index 0000000..3871ff3
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-stretch.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-stretch' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-stretch', [
+  { syntax: 'normal' },
+  { syntax: 'ultra-condensed' },
+  { syntax: 'extra-condensed' },
+  { syntax: 'condensed' },
+  { syntax: 'semi-condensed' },
+  { syntax: 'semi-expanded' },
+  { syntax: 'expanded' },
+  { syntax: 'extra-expanded' },
+  { syntax: 'ultra-expanded' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-style.html b/css/css-typed-om/the-stylepropertymap/properties/font-style.html
new file mode 100644
index 0000000..c361e21
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-style.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-style' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-style', [
+  { syntax: 'normal' },
+  { syntax: 'italic' },
+  { syntax: 'oblique' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-synthesis.html b/css/css-typed-om/the-stylepropertymap/properties/font-synthesis.html
new file mode 100644
index 0000000..b8c79e6
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-synthesis.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-synthesis' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-synthesis', [
+  { syntax: 'none' },
+  { syntax: 'weight' },
+  { syntax: 'style' },
+  { syntax: 'small-caps' },
+]);
+
+runUnsupportedPropertyTests('font-synthesis', [
+  'weight style', 'style small-caps', 'small-caps weight style'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variant-alternates.html b/css/css-typed-om/the-stylepropertymap/properties/font-variant-alternates.html
new file mode 100644
index 0000000..521eb3a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variant-alternates.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-variant-alternates' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-variant-alternates', [
+  { syntax: 'normal' },
+  { syntax: 'historical-forms' },
+]);
+
+runUnsupportedPropertyTests('font-variant-alternates', [
+  'stylistic(foo)',
+  'styleset(foo)',
+  'character-variant(foo)',
+  'swash(foo)',
+  'ornaments(foo)',
+  'annotation(foo)',
+  'swash(foo) annotation(foo2)',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variant-caps.html b/css/css-typed-om/the-stylepropertymap/properties/font-variant-caps.html
new file mode 100644
index 0000000..6a84bbd
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variant-caps.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-variant-caps' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-variant-caps', [
+  { syntax: 'normal' },
+  { syntax: 'small-caps' },
+  { syntax: 'all-small-caps' },
+  { syntax: 'petite-caps' },
+  { syntax: 'all-petite-caps' },
+  { syntax: 'unicase' },
+  { syntax: 'titling-caps' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variant-east-asian.html b/css/css-typed-om/the-stylepropertymap/properties/font-variant-east-asian.html
new file mode 100644
index 0000000..018624e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variant-east-asian.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-variant-east-asian' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-variant-east-asian', [
+  { syntax: 'normal' },
+  { syntax: 'jis78' },
+  { syntax: 'jis83' },
+  { syntax: 'jis90' },
+  { syntax: 'jis04' },
+  { syntax: 'simplified' },
+  { syntax: 'traditional' },
+  { syntax: 'full-width' },
+  { syntax: 'proportional-width' },
+  { syntax: 'ruby' },
+]);
+
+runUnsupportedPropertyTests('font-variant-east-asian', [
+  'jis78 full-width',
+  'traditional proportional-width ruby',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variant-emoji.html b/css/css-typed-om/the-stylepropertymap/properties/font-variant-emoji.html
new file mode 100644
index 0000000..38cd6d2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variant-emoji.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-variant-emoji' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-variant-emoji', [
+  { syntax: 'auto' },
+  { syntax: 'text' },
+  { syntax: 'emoji' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variant-ligatures.html b/css/css-typed-om/the-stylepropertymap/properties/font-variant-ligatures.html
new file mode 100644
index 0000000..c640dc2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variant-ligatures.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-variant-ligatures' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-variant-ligatures', [
+  { syntax: 'normal' },
+  { syntax: 'none' },
+  { syntax: 'common-ligatures' },
+  { syntax: 'no-common-ligatures' },
+  { syntax: 'discretionary-ligatures' },
+  { syntax: 'no-discretionary-ligatures' },
+  { syntax: 'historical-ligatures' },
+  { syntax: 'no-historical-ligatures' },
+  { syntax: 'contextual' },
+  { syntax: 'no-contextual' },
+]);
+
+runUnsupportedPropertyTests('font-variant-ligatures', [
+  'common-ligatures contextual',
+  'no-common-ligatures discretionary-ligatures no-historical-ligatures no-contextual',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variant-numeric.html b/css/css-typed-om/the-stylepropertymap/properties/font-variant-numeric.html
new file mode 100644
index 0000000..cbd454d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variant-numeric.html
@@ -0,0 +1,32 @@
+<meta charset="utf-8">
+<title>'font-variant-numeric' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-variant-numeric', [
+  { syntax: 'normal' },
+  { syntax: 'lining-nums' },
+  { syntax: 'oldstyle-nums' },
+  { syntax: 'proportional-nums' },
+  { syntax: 'tabular-nums' },
+  { syntax: 'diagonal-fractions' },
+  { syntax: 'stacked-fractions' },
+  { syntax: 'ordinal' },
+  { syntax: 'slashed-zero' },
+]);
+
+runUnsupportedPropertyTests('font-variant-numeric', [
+  'lining-nums ordinal',
+  'oldstyle-nums tabular-nums stacked-fractions ordinal slashed-zero',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variant.html b/css/css-typed-om/the-stylepropertymap/properties/font-variant.html
new file mode 100644
index 0000000..765ce28
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variant.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-variant' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('font-variant', [
+  'normal', 'no-common-ligatures proportional-nums',
+  'common-ligatures tabular-nums', 'small-caps slashed-zero'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-variation-settings.html b/css/css-typed-om/the-stylepropertymap/properties/font-variation-settings.html
new file mode 100644
index 0000000..12d6261
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-variation-settings.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-variation-settings' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('font-variation-settings', [
+  { syntax: 'normal' },
+]);
+
+runUnsupportedPropertyTests('font-variation-settings', [
+  '"XHGT" 0.7',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font-weight.html b/css/css-typed-om/the-stylepropertymap/properties/font-weight.html
new file mode 100644
index 0000000..cd6bec3
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font-weight.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font-weight' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_font_weight(weight, result) {
+  assert_style_value_equals(result, new CSSUnitValue(weight, 'number'));
+}
+
+runPropertyTests('font-weight', [
+  {
+    syntax: 'normal',
+    computed: (_, result) => assert_is_font_weight(400, result)
+  },
+  {
+    syntax: 'bold',
+    computed: (_, result) => assert_is_font_weight(700, result)
+  },
+  {
+    syntax: 'bolder',
+    computed: (_, result) => assert_is_unit('number', result)
+  },
+  {
+    syntax: 'lighter',
+    computed: (_, result) => assert_is_unit('number', result)
+  },
+  {
+    syntax: '<number>',
+    specified: (input, result) => {
+      if (input instanceof CSSUnitValue &&
+          (input.value < 0 || input.value > 1000))
+        assert_style_value_equals(result, new CSSMathSum(input));
+      else
+        assert_style_value_equals(result, input);
+    }
+  },
+]);
+
+</script>
+
diff --git a/css/css-typed-om/the-stylepropertymap/properties/font.html b/css/css-typed-om/the-stylepropertymap/properties/font.html
new file mode 100644
index 0000000..b8a1e88
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/font.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'font' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('font', [
+  '1.2em "Fira Sans", sans-serif',
+  'italic 1.2em "Fira Sans", serif',
+  'italic small-caps bold 16px/2 cursive',
+  'small-caps bold 24px/1 sans-serif',
+  'caption',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/gap.html b/css/css-typed-om/the-stylepropertymap/properties/gap.html
new file mode 100644
index 0000000..e07d1cd
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/gap.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'*-gap' properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#dom-stylepropertymapreadonly-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om/#reify-stylevalue">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const prefix of ['column', 'row']) {
+  runPropertyTests(`${prefix}-gap`, [
+    { syntax: 'normal' },
+    {
+      syntax: '<length>',
+      specified: assert_is_equal_with_range_handling
+    },
+    {
+      syntax: '<percentage>',
+      specified: assert_is_equal_with_range_handling
+    },
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-area.html b/css/css-typed-om/the-stylepropertymap/properties/grid-area.html
new file mode 100644
index 0000000..16ac4d5
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-area.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-area' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('grid-area', [
+  'a', 'a / a', 'auto', 'auto / auto', '2 / 1 / 2',
+  'span 3', '2 span / a span'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-auto-columns-rows.html b/css/css-typed-om/the-stylepropertymap/properties/grid-auto-columns-rows.html
new file mode 100644
index 0000000..9f31d9e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-auto-columns-rows.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-auto-columns' and 'grid-auto-rows' properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+// grid-auto-columns/rows are list-valued.
+// Run list-valued tests here too.
+for (const suffix of ['columns', 'rows']) {
+  runPropertyTests(`grid-auto-${suffix}`, [
+    { syntax: 'min-content' },
+    { syntax: 'max-content' },
+    { syntax: 'auto' },
+    { syntax: '<length>' },
+    { syntax: '<percentage>' },
+    { syntax: '<flex>' },
+  ]);
+
+  runUnsupportedPropertyTests(`grid-auto-${suffix}`, [
+    'minmax(100px, auto)', 'fit-content(400px)'
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-auto-flow.html b/css/css-typed-om/the-stylepropertymap/properties/grid-auto-flow.html
new file mode 100644
index 0000000..4447afe
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-auto-flow.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-auto-flow' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('grid-auto-flow', [
+  { syntax: 'row' },
+  { syntax: 'column' },
+]);
+
+runUnsupportedPropertyTests('grid-auto-flow', [
+  'row dense', 'column dense',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-gap.html b/css/css-typed-om/the-stylepropertymap/properties/grid-gap.html
new file mode 100644
index 0000000..c4a41ac
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-gap.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-gap' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('grid-gap', [
+  '20px', '16%', '20px 10px', '15% 100%', '21px 82%'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-start-end.html b/css/css-typed-om/the-stylepropertymap/properties/grid-start-end.html
new file mode 100644
index 0000000..baa48cf
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-start-end.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-{row/column}-{start/end}' properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const orientation of ['row', 'column']) {
+  for (const suffix of ['start', 'end']) {
+    runPropertyTests(`grid-${orientation}-${suffix}`, [
+      { syntax: 'auto' },
+    ]);
+
+    runUnsupportedPropertyTests(`grid-${orientation}-${suffix}`, [
+      '3', 'span 2', '5 somegridarea span'
+    ]);
+  }
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-template-areas.html b/css/css-typed-om/the-stylepropertymap/properties/grid-template-areas.html
new file mode 100644
index 0000000..d367f82
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-template-areas.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-template-areas' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('grid-template-areas', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('grid-template-areas', [
+  '"a a a"', '"a a a" "b b b"',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-template-columns-rows.html b/css/css-typed-om/the-stylepropertymap/properties/grid-template-columns-rows.html
new file mode 100644
index 0000000..ae53c2d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-template-columns-rows.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-template-columns' and 'grid-template-rows' properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['columns', 'rows']) {
+  runPropertyTests(`grid-template-${suffix}`, [
+    { syntax: 'none' },
+  ]);
+
+  runUnsupportedPropertyTests(`grid-template-${suffix}`, [
+    '[linename1] 100px [linename2 linename3]',
+    '200px repeat(auto-fill, 100px) 300px'
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid-template.html b/css/css-typed-om/the-stylepropertymap/properties/grid-template.html
new file mode 100644
index 0000000..669b1bd
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid-template.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid-template' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('grid-template', [
+  'none', '100px 1fr / 50px 1fr', '[linename] 100px / [columnname1] 30% [columname2] 70%',
+  'fit-content(100px) / fit-content(40%)', '"a a a" "b b b"',
+  '[header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/grid.html b/css/css-typed-om/the-stylepropertymap/properties/grid.html
new file mode 100644
index 0000000..e6e5148
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/grid.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'grid' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('grid', [
+  'auto-flow / 1fr 1fr 1fr', 'auto-flow dense / 40px 40px 1fr',
+  'repeat(3, 80px) / auto-flow'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/height.html b/css/css-typed-om/the-stylepropertymap/properties/height.html
new file mode 100644
index 0000000..55af094
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/height.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'height' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('height', [
+  { syntax: 'auto' },
+  { syntax: '<percentage>', specified: assert_is_equal_with_range_handling },
+  { syntax: '<length>', specified: assert_is_equal_with_range_handling },
+]);
+
+runPropertyTests('min-height', [
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('max-height', [
+  { syntax: 'none' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/hyphens.html b/css/css-typed-om/the-stylepropertymap/properties/hyphens.html
new file mode 100644
index 0000000..cbbcd0c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/hyphens.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'hyphens' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('hyphens', [
+  { syntax: 'none' },
+  { syntax: 'manual' },
+  { syntax: 'auto' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/image-rendering.html b/css/css-typed-om/the-stylepropertymap/properties/image-rendering.html
new file mode 100644
index 0000000..c96aa9b
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/image-rendering.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'image-rendering' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('image-rendering', [
+  { syntax: 'auto' },
+  { syntax: 'smooth' },
+  { syntax: 'high-quality' },
+  { syntax: 'crisp-edges' },
+  { syntax: 'pixelated' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/inline-size.html b/css/css-typed-om/the-stylepropertymap/properties/inline-size.html
new file mode 100644
index 0000000..f509d4a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/inline-size.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'inline-size' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('inline-size', [
+  { syntax: 'auto' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('min-inline-size', [
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('max-inline-size', [
+  { syntax: 'none' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
\ No newline at end of file
diff --git a/css/css-typed-om/the-stylepropertymap/properties/isolation.html b/css/css-typed-om/the-stylepropertymap/properties/isolation.html
new file mode 100644
index 0000000..85f5749
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/isolation.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'isolation' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('isolation', [
+  { syntax: 'auto' },
+  { syntax: 'isolate' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/left.html b/css/css-typed-om/the-stylepropertymap/properties/left.html
new file mode 100644
index 0000000..6e82d2e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/left.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'left' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('left', [
+  { syntax: 'auto' },
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/letter-spacing.html b/css/css-typed-om/the-stylepropertymap/properties/letter-spacing.html
new file mode 100644
index 0000000..ccbf0a0
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/letter-spacing.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'letter-spacing' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('letter-spacing', [
+  { syntax: 'normal' },
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/lighting-color.html b/css/css-typed-om/the-stylepropertymap/properties/lighting-color.html
new file mode 100644
index 0000000..aec1643
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/lighting-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'lighting-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('lighting-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('lighting-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/line-break.html b/css/css-typed-om/the-stylepropertymap/properties/line-break.html
new file mode 100644
index 0000000..481a9e9
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/line-break.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'line-break' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('line-break', [
+  { syntax: 'auto' },
+  { syntax: 'loose' },
+  { syntax: 'normal' },
+  { syntax: 'strict' },
+  { syntax: 'anywhere' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/line-height-step.html b/css/css-typed-om/the-stylepropertymap/properties/line-height-step.html
new file mode 100644
index 0000000..2a25562
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/line-height-step.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'line-height-step' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('line-height-step', [
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/line-height.html b/css/css-typed-om/the-stylepropertymap/properties/line-height.html
new file mode 100644
index 0000000..4e35eaf
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/line-height.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'line-height' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('line-height', [
+  { syntax: 'normal' },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling,
+    computed: (_, result) => assert_is_unit('px', result)
+  },
+  {
+    syntax: '<number>',
+    specified: assert_is_equal_with_range_handling,
+    computed: (_, result) => assert_is_unit('px', result)
+  },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling,
+    computed: (_, result) => assert_is_unit('px', result)
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/list-style-image.html b/css/css-typed-om/the-stylepropertymap/properties/list-style-image.html
new file mode 100644
index 0000000..a7f8850
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/list-style-image.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'list-style-image' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('list-style-image', [
+  { syntax: 'none' },
+  { syntax: '<image>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/list-style-position.html b/css/css-typed-om/the-stylepropertymap/properties/list-style-position.html
new file mode 100644
index 0000000..3c6724f
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/list-style-position.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'list-style-position' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('list-style-position', [
+  { syntax: 'inside' },
+  { syntax: 'outside' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/list-style-type.html b/css/css-typed-om/the-stylepropertymap/properties/list-style-type.html
new file mode 100644
index 0000000..61e7540
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/list-style-type.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'list-style-type' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('list-style-type', [
+  { syntax: 'none' },
+  // FIXME: This should be <custom-ident>, but the test harness doesn't
+  // currently support it.
+  { syntax: 'custom-ident' },
+]);
+
+runUnsupportedPropertyTests('list-style-type', [
+  '"Note: "', 'symbols("*" "A" "B" "C")'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/margin.html b/css/css-typed-om/the-stylepropertymap/properties/margin.html
new file mode 100644
index 0000000..3ad126e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/margin.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>margin properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['top', 'left', 'right', 'bottom']) {
+  runPropertyTests('margin-' + suffix, [
+    {
+      syntax: 'auto',
+      // Depending on which CSS spec is implemented, the computed value
+      // can be 'auto' or a browser specific value.
+      // FIXME: Figure out how to test this.
+      computed: () => {}
+    },
+    { syntax: '<percentage>' },
+    { syntax: '<length>' },
+  ]);
+}
+
+runPropertyTests('margin', []);
+runUnsupportedPropertyTests('margin',
+  ['1px', '1px 2px 3px 4px']
+);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/marker.html b/css/css-typed-om/the-stylepropertymap/properties/marker.html
new file mode 100644
index 0000000..9b26461
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/marker.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'marker-*' properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('marker', [
+  'none', 'url(#m1)'
+]);
+
+for (const suffix of ['start', 'mid', 'end']) {
+  runPropertyTests(`marker-${suffix}`, [
+    { syntax: 'none' },
+    { syntax: '<url>' },
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/mask-image.html b/css/css-typed-om/the-stylepropertymap/properties/mask-image.html
new file mode 100644
index 0000000..aa858d2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/mask-image.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'mask-image' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('mask-image', [
+  { syntax: 'none' },
+  { syntax: '<image>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/mask-type.html b/css/css-typed-om/the-stylepropertymap/properties/mask-type.html
new file mode 100644
index 0000000..35e81fb
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/mask-type.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'mask-type' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('mask-type', [
+  { syntax: 'luminance' },
+  { syntax: 'alpha' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/mask.html b/css/css-typed-om/the-stylepropertymap/properties/mask.html
new file mode 100644
index 0000000..bfdc4c4
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/mask.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'mask' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('mask', [
+  'none', 'url(mask.png)'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/mix-blend-mode.html b/css/css-typed-om/the-stylepropertymap/properties/mix-blend-mode.html
new file mode 100644
index 0000000..5509d87
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/mix-blend-mode.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'mix-blend-mode' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('mix-blend-mode', [
+  { syntax: 'normal' },
+  { syntax: 'multiply' },
+  { syntax: 'screen' },
+  { syntax: 'overlay' },
+  { syntax: 'darken' },
+  { syntax: 'lighten' },
+  { syntax: 'color-dodge' },
+  { syntax: 'color-burn' },
+  { syntax: 'hard-light' },
+  { syntax: 'soft-light' },
+  { syntax: 'difference' },
+  { syntax: 'exclusion' },
+  { syntax: 'hue' },
+  { syntax: 'saturation' },
+  { syntax: 'color' },
+  { syntax: 'luminosity' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/object-fit.html b/css/css-typed-om/the-stylepropertymap/properties/object-fit.html
new file mode 100644
index 0000000..1d1ee0a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/object-fit.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'object-fit' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('object-fit', [
+  { syntax: 'fill' },
+  { syntax: 'contain' },
+  { syntax: 'cover' },
+  { syntax: 'none' },
+  { syntax: 'scale-down' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/object-position.html b/css/css-typed-om/the-stylepropertymap/properties/object-position.html
new file mode 100644
index 0000000..0dd30e9
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/object-position.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'object-position' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('object-position', [
+  { syntax: '<position>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/offset-anchor.html b/css/css-typed-om/the-stylepropertymap/properties/offset-anchor.html
new file mode 100644
index 0000000..590e9eb
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/offset-anchor.html
@@ -0,0 +1,20 @@
+<meta charset="utf-8">
+<title>'offset-anchor' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('offset-anchor', [
+  { syntax: 'auto' },
+  { syntax: '<position>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/offset-distance.html b/css/css-typed-om/the-stylepropertymap/properties/offset-distance.html
new file mode 100644
index 0000000..fc4adaf
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/offset-distance.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'offset-distance' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('offset-distance', [
+  { syntax: '<length>' },
+  { syntax: '<percentage>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/offset-path.html b/css/css-typed-om/the-stylepropertymap/properties/offset-path.html
new file mode 100644
index 0000000..1cf1b09
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/offset-path.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'offset-path' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('offset-path', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('offset-path', [
+  'ray(45deg closest-side)',
+  'path("M 100 100 L 300 100 L 200 300 Z")',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/offset-position.html b/css/css-typed-om/the-stylepropertymap/properties/offset-position.html
new file mode 100644
index 0000000..fb843bf
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/offset-position.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'offset-position' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('offset-position', [
+  { syntax: 'auto' },
+  { syntax: '<position>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/offset-rotate.html b/css/css-typed-om/the-stylepropertymap/properties/offset-rotate.html
new file mode 100644
index 0000000..c91e27c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/offset-rotate.html
@@ -0,0 +1,26 @@
+<meta charset="utf-8">
+<title>'offset-rotate' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('offset-rotate', [
+  { syntax: 'auto' },
+  { syntax: 'reverse' },
+  { syntax: '<angle>' },
+]);
+
+runUnsupportedPropertyTests('offset-rotate', [
+  'auto 90deg',
+  'reverse -90deg',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/offset.html b/css/css-typed-om/the-stylepropertymap/properties/offset.html
new file mode 100644
index 0000000..5b7e713
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/offset.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'offset' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('offset', [
+  'auto', '10px 30px', 'none',
+  'ray(45deg closest-side)',
+  'path("M 100 100 L 300 100 L 200 300 z")',
+  'ray(45deg closest-side) / 40px 20px'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/opacity.html b/css/css-typed-om/the-stylepropertymap/properties/opacity.html
new file mode 100644
index 0000000..8c9ff18
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/opacity.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'opacity' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_equal_with_clamping(input, result) {
+  const number = input.to('number');
+
+  if (number.value < 0)
+    assert_style_value_equals(result, new CSSUnitValue(0, 'number'));
+  else if (number.value > 1)
+    assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+  else
+    assert_style_value_equals(result, input);
+}
+
+runPropertyTests('opacity', [
+  {
+    syntax: '<number>',
+    computed: assert_is_equal_with_clamping
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/order.html b/css/css-typed-om/the-stylepropertymap/properties/order.html
new file mode 100644
index 0000000..35b9d26
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/order.html
@@ -0,0 +1,35 @@
+<meta charset="utf-8">
+<title>'order' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('order', [
+  {
+    syntax: '<number>',
+    // order needs to be an integer
+    specified: (input, result) => {
+      if (input instanceof CSSUnitValue && !Number.isInteger(input.value))
+        assert_style_value_equals(result, new CSSMathSum(input));
+      else
+        assert_style_value_equals(result, input);
+    },
+    computed: (input, result) => {
+      const number = input.to('number');
+      if (!Number.isInteger(number.value))
+        assert_style_value_equals(result, new CSSUnitValue(Math.round(number.value), 'number'));
+      else
+        assert_style_value_equals(result, number);
+    }
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/orphans.html b/css/css-typed-om/the-stylepropertymap/properties/orphans.html
new file mode 100644
index 0000000..7d75584
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/orphans.html
@@ -0,0 +1,37 @@
+<meta charset="utf-8">
+<title>'orphans' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('orphans', [
+  {
+    syntax: '<number>',
+    // orphans needs to be a positive integer
+    specified: (input, result) => {
+      if (input instanceof CSSUnitValue && (!Number.isInteger(input.value) || input.value < 1))
+        assert_style_value_equals(result, new CSSMathSum(input));
+      else
+        assert_style_value_equals(result, input);
+    },
+    computed: (input, result) => {
+      const number = input.to('number');
+      if (number < 1)
+        assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+      else if (!Number.isInteger(number.value))
+        assert_style_value_equals(result, new CSSUnitValue(Math.round(number.value), 'number'));
+      else
+        assert_style_value_equals(result, number);
+    }
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/outline-color.html b/css/css-typed-om/the-stylepropertymap/properties/outline-color.html
new file mode 100644
index 0000000..6bfec87
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/outline-color.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'outline-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('outline-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+  // FIXME: browsers may or may not support 'invert' keyword.
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('outline-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/outline-offset.html b/css/css-typed-om/the-stylepropertymap/properties/outline-offset.html
new file mode 100644
index 0000000..f9c0f56
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/outline-offset.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'outline-offset' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('outline-offset', [
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/outline-style.html b/css/css-typed-om/the-stylepropertymap/properties/outline-style.html
new file mode 100644
index 0000000..2d793f3
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/outline-style.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'outline-style' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('outline-style', [
+  { syntax: 'auto' },
+  { syntax: 'none' },
+  { syntax: 'dotted' },
+  { syntax: 'dashed' },
+  { syntax: 'solid' },
+  { syntax: 'double' },
+  { syntax: 'groove' },
+  { syntax: 'ridge' },
+  { syntax: 'inset' },
+  { syntax: 'outset' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/outline-width.html b/css/css-typed-om/the-stylepropertymap/properties/outline-width.html
new file mode 100644
index 0000000..3686741
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/outline-width.html
@@ -0,0 +1,41 @@
+<meta charset="utf-8">
+<title>'outline-width' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_zero_px(result) {
+  assert_style_value_equals(result, new CSSUnitValue(0, 'px'));
+}
+
+runPropertyTests('outline-width', [
+  // Computed value is 0 when outline-style is 'none'.
+  // FIXME: Add separate test where outline-style is not 'none' or 'hidden'.
+  {
+    syntax: 'thin',
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+  {
+    syntax: 'medium',
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+  {
+    syntax: 'thick',
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling,
+    computed: (_, result) => assert_is_zero_px(result)
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/overflow-anchor.html b/css/css-typed-om/the-stylepropertymap/properties/overflow-anchor.html
new file mode 100644
index 0000000..f037469
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/overflow-anchor.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'overflow-anchor' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('overflow-anchor', [
+  { syntax: 'auto' },
+  { syntax: 'none' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/overflow-wrap.html b/css/css-typed-om/the-stylepropertymap/properties/overflow-wrap.html
new file mode 100644
index 0000000..0a7bcc7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/overflow-wrap.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'overflow-wrap' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('overflow-wrap', [
+  { syntax: 'normal' },
+  { syntax: 'break-word' },
+  { syntax: 'break-spaces' },
+]);
+
+runUnsupportedPropertyTests('overflow-wrap', [
+  'break-overflow break-spaces'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/overflow.html b/css/css-typed-om/the-stylepropertymap/properties/overflow.html
new file mode 100644
index 0000000..a4692fc
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/overflow.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'overflow' properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['x', 'y']) {
+  runPropertyTests(`overflow-${suffix}`, [
+    { syntax: 'visible' },
+    { syntax: 'hidden' },
+    { syntax: 'clip' },
+    { syntax: 'scroll' },
+    { syntax: 'auto' },
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/overscroll-behavior.html b/css/css-typed-om/the-stylepropertymap/properties/overscroll-behavior.html
new file mode 100644
index 0000000..5e440b2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/overscroll-behavior.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'overscroll-behavior' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['x', 'y']) {
+  runPropertyTests(`overscroll-behavior-${suffix}`, [
+    { syntax: 'contain' },
+    { syntax: 'none' },
+    { syntax: 'auto' }
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/padding.html b/css/css-typed-om/the-stylepropertymap/properties/padding.html
new file mode 100644
index 0000000..42761f5
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/padding.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>padding properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['top', 'left', 'right', 'bottom']) {
+  runPropertyTests('padding-' + suffix, [
+    {
+      syntax: '<percentage>',
+      specified: assert_is_equal_with_range_handling
+    },
+    {
+      syntax: '<length>',
+      specified: assert_is_equal_with_range_handling
+    },
+  ]);
+}
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/page.html b/css/css-typed-om/the-stylepropertymap/properties/page.html
new file mode 100644
index 0000000..93255b8
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/page.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'page' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('page', [
+  { syntax: 'auto' },
+  // FIXME: This should be <custom-ident>, but the test harness doesn't
+  // currently support it.
+  { syntax: 'custom-ident' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/paint-order.html b/css/css-typed-om/the-stylepropertymap/properties/paint-order.html
new file mode 100644
index 0000000..a547a75
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/paint-order.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'paint-order' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('paint-order', [
+  { syntax: 'normal' },
+  { syntax: 'fill' },
+  { syntax: 'stroke' },
+  { syntax: 'markers' },
+]);
+
+runUnsupportedPropertyTests('paint-order', [
+  'fill stroke', 'markers fill stroke'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/perspective-origin.html b/css/css-typed-om/the-stylepropertymap/properties/perspective-origin.html
new file mode 100644
index 0000000..a72f79c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/perspective-origin.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'perspective-origin' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('perspective-origin', [
+  { syntax: 'none' },
+  { syntax: '<position>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/perspective.html b/css/css-typed-om/the-stylepropertymap/properties/perspective.html
new file mode 100644
index 0000000..d90d939
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/perspective.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'perspective' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('perspective', [
+  { syntax: 'none' },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/pointer-events.html b/css/css-typed-om/the-stylepropertymap/properties/pointer-events.html
new file mode 100644
index 0000000..3e069e7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/pointer-events.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'pointer-events' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('pointer-events', [
+  { syntax: 'bounding-box' },
+  { syntax: 'visiblePainted' },
+  { syntax: 'visibleFill' },
+  { syntax: 'visibleStroke' },
+  { syntax: 'visible' },
+  { syntax: 'painted' },
+  { syntax: 'fill' },
+  { syntax: 'stroke' },
+  { syntax: 'all' },
+  { syntax: 'none' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/position.html b/css/css-typed-om/the-stylepropertymap/properties/position.html
new file mode 100644
index 0000000..4b36ec7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/position.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'position' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('position', [
+  { syntax: 'relative' },
+  { syntax: 'absolute' },
+  // and other keywords
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/quotes.html b/css/css-typed-om/the-stylepropertymap/properties/quotes.html
new file mode 100644
index 0000000..99e8442
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/quotes.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'quotes' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('quotes', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('quotes', [
+  '"<<" ">>" "<" ">"'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/radius.html b/css/css-typed-om/the-stylepropertymap/properties/radius.html
new file mode 100644
index 0000000..ca78592
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/radius.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'radius' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('r', [
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('rx', [
+  { syntax: 'auto' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('ry', [
+  { syntax: 'auto' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/resize.html b/css/css-typed-om/the-stylepropertymap/properties/resize.html
new file mode 100644
index 0000000..c5e0eac
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/resize.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'resize' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('resize', [
+  { syntax: 'none' },
+  { syntax: 'both' },
+  { syntax: 'horizontal' },
+  { syntax: 'vertical' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js b/css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js
new file mode 100644
index 0000000..3ca0b55
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js
@@ -0,0 +1,486 @@
+function assert_is_unit(unit, result) {
+  assert_class_string(result, 'CSSUnitValue',
+    'relative lengths must compute to a CSSUnitValue');
+  assert_equals(result.unit, unit, 'unit');
+}
+
+function assert_is_calc_sum(result) {
+  assert_class_string(result, 'CSSMathSum',
+    'specified calc must be a CSSMathSum');
+}
+
+function assert_is_equal_with_range_handling(input, result) {
+  if (input instanceof CSSUnitValue && input.value < 0)
+    assert_style_value_equals(result, new CSSMathSum(input));
+  else
+    assert_style_value_equals(result, input);
+}
+
+function assert_is_unsupported(result) {
+  assert_class_string(result, 'CSSStyleValue');
+}
+
+const gCssWideKeywordsExamples = [
+  {
+    description: 'initial keyword',
+    input: new CSSKeywordValue('initial')
+  },
+  {
+    description: 'inherit keyword',
+    input: new CSSKeywordValue('initial')
+  },
+  {
+    description: 'unset keyword',
+    input: new CSSKeywordValue('initial')
+  },
+];
+
+const gVarReferenceExamples = [
+  {
+    description: 'a var() reference',
+    input: new CSSUnparsedValue([' ', new CSSVariableReferenceValue('--A')])
+  },
+];
+
+const gTestSyntaxExamples = {
+  '<length>': {
+    description: 'a length',
+    examples: [
+      {
+        description: "zero px",
+        input: new CSSUnitValue(0, 'px')
+      },
+      {
+        description: "a negative em",
+        input: new CSSUnitValue(-3.14, 'em'),
+        // 'ems' are relative units, so just check that it computes to px
+        defaultComputed: (_, result) => assert_is_unit('px', result)
+      },
+      {
+        description: "a positive cm",
+        input: new CSSUnitValue(3.14, 'cm'),
+        // 'cms' are relative units, so just check that it computes to px
+        defaultComputed: (_, result) => assert_is_unit('px', result)
+      },
+      {
+        description: "a calc length",
+        input: new CSSMathSum(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'em')),
+        // Specified/computed calcs are usually simplified.
+        // FIXME: Test this properly
+        defaultSpecified: (_, result) => assert_is_calc_sum(result),
+        defaultComputed: (_, result) => assert_is_unit('px', result)
+      }
+    ],
+  },
+  '<percentage>': {
+    description: 'a percent',
+    examples: [
+      {
+        description: "zero percent",
+        input: new CSSUnitValue(0, 'percent')
+      },
+      {
+        description: "a negative percent",
+        input: new CSSUnitValue(-3.14, 'percent')
+      },
+      {
+        description: "a positive percent",
+        input: new CSSUnitValue(3.14, 'percent')
+      },
+      {
+        description: "a calc percent",
+        input: new CSSMathSum(new CSSUnitValue(0, 'percent'), new CSSUnitValue(0, 'percent')),
+        // Specified/computed calcs are usually simplified.
+        // FIXME: Test this properly
+        defaultSpecified: (_, result) => assert_is_calc_sum(result),
+        defaultComputed: (_, result) => assert_is_unit('percent', result)
+      }
+    ],
+  },
+  '<time>': {
+    description: 'a time',
+    examples: [
+      {
+        description: "zero seconds",
+        input: new CSSUnitValue(0, 's')
+      },
+      {
+        description: "negative milliseconds",
+        input: new CSSUnitValue(-3.14, 'ms'),
+        // Computed values use canonical units
+        defaultComputed: (_, result) => assert_style_value_equals(result, new CSSUnitValue(-0.00314, 's'))
+      },
+      {
+        description: "positive seconds",
+        input: new CSSUnitValue(3.14, 's')
+      },
+      {
+        description: "a calc time",
+        input: new CSSMathSum(new CSSUnitValue(0, 's'), new CSSUnitValue(0, 'ms')),
+        // Specified/computed calcs are usually simplified.
+        // FIXME: Test this properly
+        defaultSpecified: (_, result) => assert_is_calc_sum(result),
+        defaultComputed: (_, result) => assert_is_unit('s', result)
+      }
+    ],
+  },
+  '<time>': {
+    description: 'a time',
+    examples: [
+      {
+        description: "zero seconds",
+        input: new CSSUnitValue(0, 's')
+      },
+      {
+        description: "negative milliseconds",
+        input: new CSSUnitValue(-3.14, 'ms'),
+        // Computed values use canonical units
+        defaultComputed: (_, result) => assert_style_value_equals(result, new CSSUnitValue(-0.00314, 's'))
+      },
+      {
+        description: "positive seconds",
+        input: new CSSUnitValue(3.14, 's')
+      },
+      {
+        description: "a calc time",
+        input: new CSSMathSum(new CSSUnitValue(0, 's'), new CSSUnitValue(0, 'ms')),
+        // Specified/computed calcs are usually simplified.
+        // FIXME: Test this properly
+        defaultSpecified: (_, result) => assert_is_calc_sum(result),
+        defaultComputed: (_, result) => assert_is_unit('s', result)
+      }
+    ],
+  },
+  '<angle>': {
+    description: 'an angle',
+    examples: [
+      {
+        description: "zero degrees",
+        input: new CSSUnitValue(0, 'deg')
+      },
+      {
+        description: "positive radians",
+        input: new CSSUnitValue(3.14, 'rad'),
+        // Computed values use canonical units
+        defaultComputed: (_, result) => assert_style_value_equals(result, new CSSUnitValue(179.908752, 'deg'))
+      },
+      {
+        description: "negative degrees",
+        input: new CSSUnitValue(-3.14, 'deg')
+      },
+      {
+        description: "a calc angle",
+        input: new CSSMathSum(new CSSUnitValue(0, 'rad'), new CSSUnitValue(0, 'deg')),
+        // Specified/computed calcs are usually simplified.
+        // FIXME: Test this properly
+        defaultSpecified: (_, result) => assert_is_calc_sum(result),
+        defaultComputed: (_, result) => assert_is_unit('deg', result)
+      }
+    ],
+  },
+  '<flex>': {
+    description: 'a flexible length',
+    examples: [
+      {
+        description: "zero fractions",
+        input: new CSSUnitValue(0, 'fr')
+      },
+      {
+        description: "one fraction",
+        input: new CSSUnitValue(0, 'fr')
+      },
+      {
+        description: "negative fraction",
+        input: new CSSUnitValue(-3.14, 'fr')
+      },
+      // TODO(https://github.com/w3c/css-houdini-drafts/issues/734):
+      // Add calc tests involving 'fr' when that is spec'd in CSS.
+    ],
+  },
+  '<number>': {
+    description: 'a number',
+    examples: [
+      {
+        description: 'the number zero',
+        input: new CSSUnitValue(0, 'number')
+      },
+      {
+        description: 'a negative number',
+        input: new CSSUnitValue(-3.14, 'number')
+      },
+      {
+        description: 'a positive number',
+        input: new CSSUnitValue(3.14, 'number')
+      },
+      {
+        description: "a calc number",
+        input: new CSSMathSum(new CSSUnitValue(2, 'number'), new CSSUnitValue(3, 'number')),
+        defaultSpecified: (_, result) => assert_is_calc_sum(result),
+        defaultComputed: (_, result) => {
+          assert_style_value_equals(result, new CSSUnitValue(5, 'number'));
+        }
+      }
+    ],
+  },
+  '<position>': {
+    description: 'a position',
+    examples: [
+      {
+        decription: "origin position",
+        input: new CSSPositionValue(new CSSUnitValue(0, 'px'), new CSSUnitValue(0, 'px'))
+      }
+    ],
+  },
+  '<url>': {
+    description: 'a URL',
+    examples: [
+      // TODO(https://github.com/w3c/css-houdini-drafts/issues/716):
+      // We can't test this until CSSURLValue is spec'd.
+    ],
+  },
+  '<transform>': {
+    description: 'a transform',
+    examples: [
+      {
+        description: 'a transform containing percents',
+        input: new CSSTransformValue([
+          new CSSTranslate(
+            new CSSUnitValue(50, 'percent'),
+            new CSSUnitValue(50, 'percent'),
+          )
+        ]),
+      },
+      {
+        description: 'a transform containing relative values',
+        input: new CSSTransformValue([
+          new CSSPerspective(new CSSUnitValue(10, 'em'))
+        ]),
+        defaultComputed: (_, result) => {
+          // Relative units compute to absolute.
+          assert_class_string(result, 'CSSTransformValue',
+            'Result must be a CSSTransformValue');
+          assert_class_string(result[0], 'CSSPerspective',
+            'First component must be a CSSTransformValue');
+          assert_is_unit('px', result[0].length);
+        }
+      },
+      {
+        description: 'a transform containing all the transform components',
+        input: new CSSTransformValue([
+          new CSSTranslate(
+            new CSSUnitValue(0, 'px'),
+            new CSSUnitValue(1, 'px'),
+            new CSSUnitValue(2, 'px'),
+          ),
+          new CSSTranslate(
+            new CSSUnitValue(0, 'px'),
+            new CSSUnitValue(1, 'px'),
+          ),
+          new CSSRotate(1, 2, 3, new CSSUnitValue(45, 'deg')),
+          new CSSRotate(new CSSUnitValue(45, 'deg')),
+          new CSSScale(1, 2, 3),
+          new CSSScale(1, 2),
+          new CSSSkew(new CSSUnitValue(1, 'deg'), new CSSUnitValue(1, 'deg')),
+          new CSSSkewX(new CSSUnitValue(1, 'deg')),
+          new CSSSkewY(new CSSUnitValue(45, 'deg')),
+          new CSSPerspective(new CSSUnitValue(1, 'px')),
+          new CSSMatrixComponent(new DOMMatrixReadOnly(
+            [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
+          ),
+          new CSSMatrixComponent(new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6])),
+        ]),
+      }
+    ],
+  },
+};
+
+// Test setting a value in a style map and then getting it from the inline and
+// computed styles.
+function testPropertyValid(propertyName, examples, specified, computed, description) {
+  test(t => {
+    let element = createDivWithStyle(t);
+
+    for (const example of examples) {
+      element.attributeStyleMap.set(propertyName, example.input);
+
+      // specified style
+      const specifiedResult = element.attributeStyleMap.get(propertyName);
+      assert_not_equals(specifiedResult, null,
+        'Specified value must not be null');
+      assert_true(specifiedResult instanceof CSSStyleValue,
+        'Specified value must be a CSSStyleValue');
+
+      if (specified || example.defaultSpecified) {
+        (specified || example.defaultSpecified)(example.input, specifiedResult);
+      } else {
+        assert_style_value_equals(specifiedResult, example.input,
+          `Setting ${example.description} and getting its specified value`);
+      }
+
+      // computed style
+      const computedResult = element.computedStyleMap().get(propertyName);
+      assert_not_equals(computedResult, null,
+        'Computed value must not be null');
+      assert_true(computedResult instanceof CSSStyleValue,
+        'Computed value must be a CSSStyleValue');
+
+      if (computed || example.defaultComputed) {
+        (computed || example.defaultComputed)(example.input, computedResult);
+      } else {
+        assert_style_value_equals(computedResult, example.input,
+          `Setting ${example.description} and getting its computed value`);
+      }
+    }
+  }, `Can set '${propertyName}' to ${description}`);
+}
+
+// We have to special case CSSImageValue as they cannot be created with a
+// constructor and are completely opaque.
+function testIsImageValidForProperty(propertyName) {
+  test(t => {
+    let element1 = createDivWithStyle(t, `${propertyName}: url("/media/1x1-green.png")`);
+    let element2 = createDivWithStyle(t);
+
+    const result = element1.attributeStyleMap.get(propertyName);
+    assert_not_equals(result, null, 'Image value must not be null');
+    assert_class_string(result, 'CSSImageValue',
+      'Image value must be a CSSImageValue');
+
+    element2.attributeStyleMap.set(propertyName, result);
+    assert_equals(element2.style[propertyName], element1.style[propertyName],
+      'Image value can be set on different element');
+  }, `Can set '${propertyName}' to an image`);
+}
+
+// Test that styleMap.set throws for invalid values
+function testPropertyInvalid(propertyName, examples, description) {
+  test(t => {
+    let styleMap = createInlineStyleMap(t);
+    for (const example of examples) {
+      assert_throws(new TypeError(), () => styleMap.set(propertyName, example.input));
+    }
+  }, `Setting '${propertyName}' to ${description} throws TypeError`);
+}
+
+// Test that styleMap.get/.set roundtrips correctly for unsupported values.
+function testUnsupportedValue(propertyName, cssText) {
+  test(t => {
+    let element1 = createDivWithStyle(t);
+    let element2 = createDivWithStyle(t);
+
+    element1.style[propertyName] = cssText;
+    const result = element1.attributeStyleMap.get(propertyName);
+    assert_not_equals(result, null,
+      'Unsupported value must not be null');
+    assert_class_string(result, 'CSSStyleValue',
+      'Unsupported value must be a CSSStyleValue and not one of its subclasses');
+
+    element2.attributeStyleMap.set(propertyName, result);
+    assert_equals(element2.style[propertyName], element1.style[propertyName],
+      'Unsupported value can be set on different element');
+
+    const resultAll = element2.attributeStyleMap.getAll(propertyName);
+    assert_style_value_equals(resultAll[0], result,
+      `getAll() with single unsupported value returns single-item list ` +
+      `with same result as get()`);
+  }, `'${propertyName}' does not supported '${cssText}'`);
+}
+
+function createKeywordExample(keyword) {
+  return {
+    description: `the '${keyword}' keyword`,
+    examples: [ { input: new CSSKeywordValue(keyword) } ]
+  };
+}
+
+// Run a battery of StylePropertyMap tests on |propertyName|.
+// Second argument is a list of test cases. A test case has the form:
+//
+// {
+//   syntax: "<length>",
+//   specified: /* a callback */ (optional)
+//   computed: /* a callback */ (optional)
+// }
+//
+// If a callback is passed to |specified|, then the callback will be passed
+// two arguments:
+// 1. The input test case
+// 2. The result of calling get() on the inline style map (specified values).
+//
+// The callback should check if the result is expected using assert_* functions.
+// If no callback is passed, then we assert that the result is the same as
+// the input.
+//
+// Same goes for |computed|, but with the computed style map (computed values).
+//
+// FIXME: The reason we pass argument #2 is that it's sometimes difficult to
+// compute exactly what the expected result should be (e.g. browser-specific
+// values). Once we can do that, we can remove argument #2 and just return
+// the expected result.
+function runPropertyTests(propertyName, testCases) {
+  let syntaxTested = new Set();
+
+  // Every property should at least support CSS-wide keywords.
+  testPropertyValid(propertyName,
+    gCssWideKeywordsExamples,
+    null, // should be as specified
+    () => {}, // could be anything
+    'CSS-wide keywords');
+
+  // Every property should support values containing var() references.
+  testPropertyValid(propertyName,
+    gVarReferenceExamples,
+    null, // should be as specified
+    () => {}, // could compute to anything
+    'var() references');
+
+  for (const testCase of testCases) {
+    // <image> is a special case
+    if (testCase.syntax === '<image>') {
+      testIsImageValidForProperty(propertyName);
+      continue;
+    }
+
+    // Retrieve test examples for this test case's syntax. If the syntax
+    // looks like a keyword, then create an example on the fly.
+    const syntaxExamples = testCase.syntax.toLowerCase().match(/^[a-z0-9\-]+$/) ?
+      createKeywordExample(testCase.syntax) :
+      gTestSyntaxExamples[testCase.syntax];
+
+    if (!syntaxExamples)
+      throw new Error(`'${testCase.syntax}' is not a valid CSS component`);
+
+    testPropertyValid(propertyName,
+      syntaxExamples.examples,
+      testCase.specified,
+      testCase.computed,
+      syntaxExamples.description);
+
+    syntaxTested.add(testCase.syntax);
+  }
+
+  // Also test that styleMap.set rejects invalid CSSStyleValues.
+  for (const [syntax, syntaxExamples] of Object.entries(gTestSyntaxExamples)) {
+    if (!syntaxTested.has(syntax)) {
+      testPropertyInvalid(propertyName,
+        syntaxExamples.examples,
+        syntaxExamples.description);
+    }
+  }
+}
+
+// Same as runPropertyTests but for list-valued properties.
+function runListValuedPropertyTests(propertyName, testCases) {
+  // TODO(https://crbug.com/545318): Run list-valued tests as well.
+  runPropertyTests(propertyName, testCases);
+}
+
+// Check that |propertyName| doesn't "support" examples in |testExamples|.
+// |testExamples| is a list of CSS string values. An "unsupported" value
+// doesn't have a corresponding Typed OM representation. It normalizes as
+// the base CSSStyleValue.
+function runUnsupportedPropertyTests(propertyName, testExamples) {
+  for (const cssText of testExamples) {
+    testUnsupportedValue(propertyName, cssText);
+  }
+}
diff --git a/css/css-typed-om/the-stylepropertymap/properties/right.html b/css/css-typed-om/the-stylepropertymap/properties/right.html
new file mode 100644
index 0000000..17488dfb
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/right.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'right' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('right', [
+  { syntax: 'auto' },
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/scroll-behavior.html b/css/css-typed-om/the-stylepropertymap/properties/scroll-behavior.html
new file mode 100644
index 0000000..83baeb0
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/scroll-behavior.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'scroll-behavior' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('scroll-behavior', [
+  { syntax: 'auto' },
+  { syntax: 'smooth' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/scroll-margin.html b/css/css-typed-om/the-stylepropertymap/properties/scroll-margin.html
new file mode 100644
index 0000000..8f2cc07
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/scroll-margin.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>scroll-margin related properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['top', 'left', 'right', 'bottom']) {
+  runPropertyTests('scroll-margin-' + suffix, [
+    { syntax: '<length>' },
+  ]);
+}
+
+for (const suffix of ['inline-start', 'block-start', 'inline-end', 'block-end']) {
+  runPropertyTests('scroll-margin-' + suffix, [
+    { syntax: '<length>' },
+  ]);
+}
+
+runUnsupportedPropertyTests('scroll-margin',
+  ['0px', '1px 2px', '3px 4px 5px', '6px 7px 8px 9px']
+);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/scroll-padding.html b/css/css-typed-om/the-stylepropertymap/properties/scroll-padding.html
new file mode 100644
index 0000000..228cf1f
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/scroll-padding.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>scroll-padding related properties</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (const suffix of ['top', 'left', 'right', 'bottom']) {
+  runPropertyTests('scroll-padding-' + suffix, [
+    { syntax: '<percentage>' },
+    { syntax: '<length>' },
+  ]);
+}
+
+for (const suffix of ['inline-start', 'block-start', 'inline-end', 'block-end']) {
+  runPropertyTests('scroll-padding-' + suffix, [
+    { syntax: '<percentage>' },
+    { syntax: '<length>' },
+  ]);
+}
+
+runUnsupportedPropertyTests('scroll-padding',
+  ['0%', '1px 2px', '3% 4px 5%', '6px 7% 8% 9px']
+);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-align.html b/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-align.html
new file mode 100644
index 0000000..fd92a90
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-align.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'scroll-snap-align' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('scroll-snap-align', [
+  {
+    syntax: 'none',
+    computed: assert_is_unsupported
+  },
+  {
+    syntax: 'start',
+    computed: assert_is_unsupported
+  },
+  {
+    syntax: 'end',
+    computed: assert_is_unsupported
+  },
+  {
+    syntax: 'center',
+    computed: assert_is_unsupported
+  },
+]);
+
+runUnsupportedPropertyTests('scroll-snap-align', [
+  'none center', 'end start'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-stop.html b/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-stop.html
new file mode 100644
index 0000000..959a240
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-stop.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'scroll-snap-stop' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('scroll-snap-stop', [
+  { syntax: 'normal' },
+  { syntax: 'always' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-type.html b/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-type.html
new file mode 100644
index 0000000..4f9c4a4
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/scroll-snap-type.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'scroll-snap-type' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('scroll-snap-type', [
+  { syntax: 'none' },
+  { syntax: 'x' },
+  { syntax: 'y' },
+  { syntax: 'block' },
+  { syntax: 'inline' },
+  { syntax: 'both' },
+]);
+
+runUnsupportedPropertyTests('scroll-snap-type', [
+  'x mandatory', 'inline proximity'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/shape-image-threshold.html b/css/css-typed-om/the-stylepropertymap/properties/shape-image-threshold.html
new file mode 100644
index 0000000..d16d892
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/shape-image-threshold.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'shape-image-threshold' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_equal_with_clamping(input, result) {
+  const number = input.to('number');
+
+  if (number.value < 0)
+    assert_style_value_equals(result, new CSSUnitValue(0, 'number'));
+  else if (number.value > 1)
+    assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+  else
+    assert_style_value_equals(result, input);
+}
+
+runPropertyTests('shape-image-threshold', [
+  {
+    syntax: '<number>',
+    computed: assert_is_equal_with_clamping
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/shape-margin.html b/css/css-typed-om/the-stylepropertymap/properties/shape-margin.html
new file mode 100644
index 0000000..ca517bb
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/shape-margin.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'shape-margin' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('shape-margin', [
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/shape-outside.html b/css/css-typed-om/the-stylepropertymap/properties/shape-outside.html
new file mode 100644
index 0000000..d514c83
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/shape-outside.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'shape-outside' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('shape-outside', [
+  { syntax: 'none' },
+  { syntax: 'margin-box' },
+  { syntax: 'border-box' },
+  { syntax: 'padding-box' },
+  { syntax: 'content-box' },
+  { syntax: '<image>' },
+]);
+
+// <basic-shape>s are not supported in level 1
+runUnsupportedPropertyTests('shape-outside', [
+  'inset(22% 12% 15px 35px)',
+  'circle(6rem at 12rem 6rem)',
+  'ellipse(115px 55px at 50% 40%)',
+  'polygon(50% 20%, 90% 80%, 10% 80%)',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/shape-rendering.html b/css/css-typed-om/the-stylepropertymap/properties/shape-rendering.html
new file mode 100644
index 0000000..28ecdab
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/shape-rendering.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'shape-rendering' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('shape-rendering', [
+  { syntax: 'auto' },
+  { syntax: 'optimizeSpeed' },
+  { syntax: 'crispEdges' },
+  { syntax: 'geometricPrecision' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/speak.html b/css/css-typed-om/the-stylepropertymap/properties/speak.html
new file mode 100644
index 0000000..33512fe
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/speak.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'speak' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('speak', [
+  { syntax: 'auto' },
+  { syntax: 'never' },
+  { syntax: 'always' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stop-color.html b/css/css-typed-om/the-stylepropertymap/properties/stop-color.html
new file mode 100644
index 0000000..83dcf02
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stop-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stop-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('stop-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('stop-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stop-opacity.html b/css/css-typed-om/the-stylepropertymap/properties/stop-opacity.html
new file mode 100644
index 0000000..5ce7134
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stop-opacity.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stop-opacity' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_equal_with_clamping(input, result) {
+  const number = input.to('number');
+
+  if (number.value < 0)
+    assert_style_value_equals(result, new CSSUnitValue(0, 'number'));
+  else if (number.value > 1)
+    assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+  else
+    assert_style_value_equals(result, input);
+}
+
+runPropertyTests('stop-opacity', [
+  {
+    syntax: '<number>',
+    computed: assert_is_equal_with_clamping
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke-dasharray.html b/css/css-typed-om/the-stylepropertymap/properties/stroke-dasharray.html
new file mode 100644
index 0000000..e907415
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke-dasharray.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke-dasharray' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('stroke-dasharray', [
+  { syntax: 'none' }
+]);
+
+runUnsupportedPropertyTests('stroke-dasharray', [
+  '5% 1em 2%',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke-dashoffset.html b/css/css-typed-om/the-stylepropertymap/properties/stroke-dashoffset.html
new file mode 100644
index 0000000..de3c78d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke-dashoffset.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke-dashoffset' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('stroke-dashoffset', [
+  { syntax: '<length>' },
+  { syntax: '<percentage>' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke-linecap.html b/css/css-typed-om/the-stylepropertymap/properties/stroke-linecap.html
new file mode 100644
index 0000000..227b922
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke-linecap.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke-linecap' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('stroke-linecap', [
+  { syntax: 'butt' },
+  { syntax: 'round' },
+  { syntax: 'square' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke-linejoin.html b/css/css-typed-om/the-stylepropertymap/properties/stroke-linejoin.html
new file mode 100644
index 0000000..2f01ee7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke-linejoin.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke-linejoin' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('stroke-linejoin', [
+  { syntax: 'crop' },
+  { syntax: 'arcs' },
+  { syntax: 'miter' },
+  { syntax: 'bevel' },
+  { syntax: 'round' },
+  { syntax: 'stupid' },
+]);
+
+runUnsupportedPropertyTests('stroke-linejoin', [
+  'crop bevel', 'round arcs'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke-miterlimit.html b/css/css-typed-om/the-stylepropertymap/properties/stroke-miterlimit.html
new file mode 100644
index 0000000..2c2e01c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke-miterlimit.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke-miterlimit' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('stroke-miterlimit', [
+  { syntax: '<number>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke-opacity.html b/css/css-typed-om/the-stylepropertymap/properties/stroke-opacity.html
new file mode 100644
index 0000000..6b9e0b5
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke-opacity.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke-opacity' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+function assert_is_equal_with_clamping(input, result) {
+  const number = input.to('number');
+
+  if (number.value < 0)
+    assert_style_value_equals(result, new CSSUnitValue(0, 'number'));
+  else if (number.value > 1)
+    assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+  else
+    assert_style_value_equals(result, input);
+}
+
+runPropertyTests('stroke-opacity', [
+  {
+    syntax: '<number>',
+    computed: assert_is_equal_with_clamping
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke-width.html b/css/css-typed-om/the-stylepropertymap/properties/stroke-width.html
new file mode 100644
index 0000000..52b9320
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke-width.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke-width' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('stroke-width', [
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/stroke.html b/css/css-typed-om/the-stylepropertymap/properties/stroke.html
new file mode 100644
index 0000000..ff81024
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/stroke.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'stroke' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('stroke', [
+  'black',
+  'red gray',
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/tab-size.html b/css/css-typed-om/the-stylepropertymap/properties/tab-size.html
new file mode 100644
index 0000000..d03139e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/tab-size.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'tab-size' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('tab-size', [
+  {
+    // tab-size can be a non-negative integer
+    syntax: '<number>',
+    specified: (input, result) => {
+      if (input instanceof CSSUnitValue && (!Number.isInteger(input.value) || input.value < 0))
+        assert_style_value_equals(result, new CSSMathSum(input));
+      else
+        assert_style_value_equals(result, input);
+    },
+    computed: (input, result) => {
+      const number = input.to('number');
+      if (number < 0)
+        assert_style_value_equals(result, new CSSUnitValue(0, 'number'));
+      else if (!Number.isInteger(number.value))
+        assert_style_value_equals(result, new CSSUnitValue(Math.round(number.value), 'number'));
+      else
+        assert_style_value_equals(result, number);
+    }
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/table-layout.html b/css/css-typed-om/the-stylepropertymap/properties/table-layout.html
new file mode 100644
index 0000000..e58d019
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/table-layout.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'table-layout' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('table-layout', [
+  { syntax: 'auto' },
+  { syntax: 'fixed' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-align-last.html b/css/css-typed-om/the-stylepropertymap/properties/text-align-last.html
new file mode 100644
index 0000000..242906f
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-align-last.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-align-last' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-align-last', [
+  { syntax: 'auto' },
+  { syntax: 'start' },
+  { syntax: 'end' },
+  { syntax: 'left' },
+  { syntax: 'right' },
+  { syntax: 'center' },
+  { syntax: 'justify' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-align.html b/css/css-typed-om/the-stylepropertymap/properties/text-align.html
new file mode 100644
index 0000000..c3d7f2e
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-align.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-align' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-align', [
+  { syntax: 'center' },
+  { syntax: 'justify' },
+  // and other keywords
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-anchor.html b/css/css-typed-om/the-stylepropertymap/properties/text-anchor.html
new file mode 100644
index 0000000..00b04aa
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-anchor.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-anchor' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-anchor', [
+  { syntax: 'start' },
+  { syntax: 'middle' },
+  { syntax: 'end' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-combine-upright.html b/css/css-typed-om/the-stylepropertymap/properties/text-combine-upright.html
new file mode 100644
index 0000000..c0a7f56
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-combine-upright.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-combine-upright' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-combine-upright', [
+  { syntax: 'none' },
+  { syntax: 'all' },
+]);
+
+runUnsupportedPropertyTests('text-combine-upright', [
+  'digits 3'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-decoration-color.html b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-color.html
new file mode 100644
index 0000000..b76f82c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-decoration-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-decoration-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('text-decoration-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-decoration-line.html b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-line.html
new file mode 100644
index 0000000..dba1263
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-line.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-decoration-line' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-decoration-line', [
+  { syntax: 'none' },
+  { syntax: 'underline' },
+  { syntax: 'overline' },
+  { syntax: 'line-through' },
+  { syntax: 'blink' },
+  { syntax: 'spelling-error' },
+  { syntax: 'grammar-error' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-decoration-skip-ink.html b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-skip-ink.html
new file mode 100644
index 0000000..57d445d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-skip-ink.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-decoration-skip-ink' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-decoration-skip-ink', [
+  { syntax: 'auto' },
+  { syntax: 'none' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-decoration-skip.html b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-skip.html
new file mode 100644
index 0000000..796b877
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-skip.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-decoration-skip' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-decoration-skip', [
+  { syntax: 'none' },
+  { syntax: 'objects' },
+  { syntax: 'edges' },
+  { syntax: 'box-decoration' },
+  { syntax: 'spaces' },
+]);
+
+runUnsupportedPropertyTests('text-decoration-skip', [
+  'objects spaces', 'leading-spaces trailing-spaces',
+  'objects edges box-decoration'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-decoration-style.html b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-style.html
new file mode 100644
index 0000000..956c6e5
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-style.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-decoration-style' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-decoration-style', [
+  { syntax: 'solid' },
+  { syntax: 'double' },
+  { syntax: 'dotted' },
+  { syntax: 'dashed' },
+  { syntax: 'wavy' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-decoration-width.html b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-width.html
new file mode 100644
index 0000000..43aeb16
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-decoration-width.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-decoration-width' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-decoration-width', [
+  { syntax: 'auto' },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-decoration.html b/css/css-typed-om/the-stylepropertymap/properties/text-decoration.html
new file mode 100644
index 0000000..e9a9827
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-decoration.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-decoration' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('text-decoration', [
+  'underline', 'underline dotted #ff3028', 'green wavy underline'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-emphasis-color.html b/css/css-typed-om/the-stylepropertymap/properties/text-emphasis-color.html
new file mode 100644
index 0000000..8623d3d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-emphasis-color.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-emphasis-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-emphasis-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('text-emphasis-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-indent.html b/css/css-typed-om/the-stylepropertymap/properties/text-indent.html
new file mode 100644
index 0000000..e7f1b8c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-indent.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-indent' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-indent', [
+  { syntax: '<length>' },
+  { syntax: '<percentage>' },
+]);
+
+runUnsupportedPropertyTests('text-indent', [
+  '5em each-line', '5em hanging', '5em hanging each-line'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-justify.html b/css/css-typed-om/the-stylepropertymap/properties/text-justify.html
new file mode 100644
index 0000000..29f7e60
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-justify.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-justify' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-justify', [
+  { syntax: 'auto' },
+  { syntax: 'none' },
+  { syntax: 'inter-word' },
+  { syntax: 'inter-character' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-orientation.html b/css/css-typed-om/the-stylepropertymap/properties/text-orientation.html
new file mode 100644
index 0000000..8bc74b7
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-orientation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-orientation' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-orientation', [
+  { syntax: 'mixed' },
+  { syntax: 'upright' },
+  { syntax: 'sideways' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-overflow.html b/css/css-typed-om/the-stylepropertymap/properties/text-overflow.html
new file mode 100644
index 0000000..9d4a915
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-overflow.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-overflow' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-overflow', [
+  { syntax: 'clip' },
+  { syntax: 'ellipsis' },
+  { syntax: 'fade' },
+]);
+
+runUnsupportedPropertyTests('text-overflow', [
+  'clip ellipsis', '"..."', 'fade(1px, 50%)'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-rendering.html b/css/css-typed-om/the-stylepropertymap/properties/text-rendering.html
new file mode 100644
index 0000000..13d2e3b
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-rendering.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-rendering' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-rendering', [
+  { syntax: 'auto' },
+  { syntax: 'optimizeSpeed' },
+  { syntax: 'optimizeLegibility' },
+  { syntax: 'geometricPrecision' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-shadow.html b/css/css-typed-om/the-stylepropertymap/properties/text-shadow.html
new file mode 100644
index 0000000..930da94
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-shadow.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-shadow' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-shadow', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('text-shadow', [
+  '1px 1px 2px pink', '1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-size-adjust.html b/css/css-typed-om/the-stylepropertymap/properties/text-size-adjust.html
new file mode 100644
index 0000000..29a60cb
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-size-adjust.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-size-adjust' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-size-adjust', [
+  { syntax: 'none' },
+  { syntax: 'auto' },
+  { syntax: '<percentage>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-transform.html b/css/css-typed-om/the-stylepropertymap/properties/text-transform.html
new file mode 100644
index 0000000..7d6e9c3
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-transform.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-transform' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-transform', [
+  { syntax: 'none' },
+  { syntax: 'captialize' },
+  { syntax: 'uppercase' },
+  { syntax: 'lowercase' },
+  { syntax: 'full-width' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/text-underline-position.html b/css/css-typed-om/the-stylepropertymap/properties/text-underline-position.html
new file mode 100644
index 0000000..f648171
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/text-underline-position.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'text-underline-position' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('text-underline-position', [
+  { syntax: 'auto' },
+  { syntax: 'under' },
+  { syntax: 'left' },
+  { syntax: 'right' },
+]);
+
+runUnsupportedPropertyTests('text-underline-position', [
+  'under left', 'right under'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/top.html b/css/css-typed-om/the-stylepropertymap/properties/top.html
new file mode 100644
index 0000000..f12a76b
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/top.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'top' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('top', [
+  { syntax: 'auto' },
+  { syntax: '<percentage>' },
+  { syntax: '<length>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/touch-action.html b/css/css-typed-om/the-stylepropertymap/properties/touch-action.html
new file mode 100644
index 0000000..2435e34
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/touch-action.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'touch-action' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('touch-action', [
+  { syntax: 'auto' },
+  { syntax: 'none' },
+  { syntax: 'pan-x' },
+  { syntax: 'pan-left' },
+  { syntax: 'pan-right' },
+  { syntax: 'pan-y' },
+  { syntax: 'pan-up' },
+  { syntax: 'pan-down' },
+  { syntax: 'pinch-zoom' },
+  { syntax: 'manipulation' },
+]);
+
+runUnsupportedPropertyTests('touch-action', [
+  'pan-x pan-down', 'pan-down pinch-zoom pan-right'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transform-box.html b/css/css-typed-om/the-stylepropertymap/properties/transform-box.html
new file mode 100644
index 0000000..a5556b1
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transform-box.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transform-box' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('transform-box', [
+  { syntax: 'border-box' },
+  { syntax: 'fill-box' },
+  { syntax: 'view-box' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transform-interpolated.html b/css/css-typed-om/the-stylepropertymap/properties/transform-interpolated.html
new file mode 100644
index 0000000..526717d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transform-interpolated.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transform' property with an interpolated value</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+// TODO: Try to support this sort of test with testsuite.js
+test(t => {
+  let elem = createDivWithStyle(t);
+  elem.animate({
+    transform: ['translate(1px, 1%)', 'rotate(45deg)']
+  }, {
+    fill: 'forwards',
+    iterationStart: 0.5,
+  });
+
+  // TODO: The computed value in this case is not fully spec'd
+  // See https://github.com/w3c/css-houdini-drafts/issues/425
+  const result = elem.computedStyleMap().get('transform');
+  assert_not_equals(result, null);
+}, 'Computed value for interpolated transforms is not null');
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transform-style.html b/css/css-typed-om/the-stylepropertymap/properties/transform-style.html
new file mode 100644
index 0000000..2746db5
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transform-style.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transform-style' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('transform-style', [
+  { syntax: 'auto' },
+  { syntax: 'flat' },
+  { syntax: 'preserve-3d' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transform.html b/css/css-typed-om/the-stylepropertymap/properties/transform.html
new file mode 100644
index 0000000..7a85254
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transform.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transform' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('transform', [
+  { syntax: 'none' },
+  { syntax: '<transform>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transition-delay.html b/css/css-typed-om/the-stylepropertymap/properties/transition-delay.html
new file mode 100644
index 0000000..1ee47f4
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transition-delay.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transition-delay' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('transition-delay', [
+  { syntax: '<time>' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transition-duration.html b/css/css-typed-om/the-stylepropertymap/properties/transition-duration.html
new file mode 100644
index 0000000..c0eb09d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transition-duration.html
@@ -0,0 +1,19 @@
+<meta charset="utf-8">
+<title>'transition-duration' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('transition-duration', [
+  { syntax: '<time>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transition-property.html b/css/css-typed-om/the-stylepropertymap/properties/transition-property.html
new file mode 100644
index 0000000..7f0c48a
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transition-property.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transition-property' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('transition-property', [
+  { syntax: 'none' },
+]);
+
+runUnsupportedPropertyTests('transition-property', [
+  'width', 'width, height', 'all'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transition-timing-function.html b/css/css-typed-om/the-stylepropertymap/properties/transition-timing-function.html
new file mode 100644
index 0000000..4feb817
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transition-timing-function.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transition-timing-function' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runListValuedPropertyTests('transition-timing-function', [
+  { syntax: 'linear' },
+  { syntax: 'ease' },
+  { syntax: 'ease-in' },
+  { syntax: 'ease-out' },
+  { syntax: 'ease-in-out' },
+  { syntax: 'step-start' },
+  { syntax: 'step-end' },
+]);
+
+runUnsupportedPropertyTests('transition-timing-function', [
+  'cubic-bezier(0.1, 0.7, 1.0, 0.1)', 'steps(4, end)', 'frames(10)'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/transition.html b/css/css-typed-om/the-stylepropertymap/properties/transition.html
new file mode 100644
index 0000000..1264550
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/transition.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'transition' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runUnsupportedPropertyTests('transition', [
+  'none', 'none, none', 'margin-right 4s', 'all 0.5s ease-out, color 1s'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/unicode-bidi.html b/css/css-typed-om/the-stylepropertymap/properties/unicode-bidi.html
new file mode 100644
index 0000000..5ac27c4
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/unicode-bidi.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'unicode-bidi' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('unicode-bidi', [
+  { syntax: 'normal' },
+  { syntax: 'embed' },
+  { syntax: 'isolate' },
+  { syntax: 'bidi-override' },
+  { syntax: 'isolate-override' },
+  { syntax: 'plaintext' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/user-select.html b/css/css-typed-om/the-stylepropertymap/properties/user-select.html
new file mode 100644
index 0000000..47ea14d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/user-select.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'user-select' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('user-select', [
+  { syntax: 'auto' },
+  { syntax: 'text' },
+  { syntax: 'none' },
+  { syntax: 'contain' },
+  { syntax: 'all' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/vector-effect.html b/css/css-typed-om/the-stylepropertymap/properties/vector-effect.html
new file mode 100644
index 0000000..6927095
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/vector-effect.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'vector-effect' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('vector-effect', [
+  { syntax: 'non-scaling-stroke' },
+  { syntax: 'none' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/vertical-align.html b/css/css-typed-om/the-stylepropertymap/properties/vertical-align.html
new file mode 100644
index 0000000..af022fd
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/vertical-align.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'vertical-align' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('vertical-align', [
+  { syntax: 'baseline'},
+  // and other keywords
+  { syntax: '<length>' },
+  { syntax: '<percentage>' }
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/visibility.html b/css/css-typed-om/the-stylepropertymap/properties/visibility.html
new file mode 100644
index 0000000..42a020d
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/visibility.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'visibility' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('visibility', [
+  { syntax: 'visible'},
+  { syntax: 'hidden' },
+  { syntax: 'collapse' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/white-space.html b/css/css-typed-om/the-stylepropertymap/properties/white-space.html
new file mode 100644
index 0000000..f050da2
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/white-space.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'white-space' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('white-space', [
+  { syntax: 'normal'},
+  { syntax: 'pre' },
+  { syntax: 'nowrap' },
+  { syntax: 'pre-wrap' },
+  { syntax: 'pre-line' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/widows.html b/css/css-typed-om/the-stylepropertymap/properties/widows.html
new file mode 100644
index 0000000..7503bff
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/widows.html
@@ -0,0 +1,37 @@
+<meta charset="utf-8">
+<title>'widows' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('widows', [
+  {
+    syntax: '<number>',
+    // widows needs to be a positive integer
+    specified: (input, result) => {
+      if (input instanceof CSSUnitValue && (!Number.isInteger(input.value) || input.value < 1))
+        assert_style_value_equals(result, new CSSMathSum(input));
+      else
+        assert_style_value_equals(result, input);
+    },
+    computed: (input, result) => {
+      const number = input.to('number');
+      if (number < 1)
+        assert_style_value_equals(result, new CSSUnitValue(1, 'number'));
+      else if (!Number.isInteger(number.value))
+        assert_style_value_equals(result, new CSSUnitValue(Math.round(number.value), 'number'));
+      else
+        assert_style_value_equals(result, number);
+    }
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/width.html b/css/css-typed-om/the-stylepropertymap/properties/width.html
new file mode 100644
index 0000000..29b3382
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/width.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'width' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('width', [
+  { syntax: 'auto' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('min-width', [
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+runPropertyTests('max-width', [
+  { syntax: 'none' },
+  {
+    syntax: '<percentage>',
+    specified: assert_is_equal_with_range_handling
+  },
+  {
+    syntax: '<length>',
+    specified: assert_is_equal_with_range_handling
+  },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/will-change.html b/css/css-typed-om/the-stylepropertymap/properties/will-change.html
new file mode 100644
index 0000000..2add50c
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/will-change.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'will-change' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('will-change', [
+  { syntax: 'auto' },
+]);
+
+runUnsupportedPropertyTests('will-change', [
+  'scroll-position', 'contents, foo, scroll-position'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/word-break.html b/css/css-typed-om/the-stylepropertymap/properties/word-break.html
new file mode 100644
index 0000000..12370df
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/word-break.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'word-break' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('word-break', [
+  { syntax: 'normal' },
+  { syntax: 'keep-all' },
+  { syntax: 'break-all' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/word-spacing.html b/css/css-typed-om/the-stylepropertymap/properties/word-spacing.html
new file mode 100644
index 0000000..9a44c96
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/word-spacing.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'word-spacing' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('word-spacing', [
+  { syntax: 'normal' },
+  { syntax: '<length>' },
+  { syntax: '<percentage>' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/word-wrap.html b/css/css-typed-om/the-stylepropertymap/properties/word-wrap.html
new file mode 100644
index 0000000..b8ec4fc
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/word-wrap.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'word-wrap' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('word-wrap', [
+  { syntax: 'normal' },
+  { syntax: 'break-word' },
+  { syntax: 'break-spaces' },
+]);
+
+runUnsupportedPropertyTests('word-wrap', [
+  'break-word break-spaces'
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/writing-mode.html b/css/css-typed-om/the-stylepropertymap/properties/writing-mode.html
new file mode 100644
index 0000000..976a0ea
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/writing-mode.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'writing-mode' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('writing-mode', [
+  { syntax: 'horizontal-tb' },
+  { syntax: 'vertical-rl' },
+  { syntax: 'vertical-lr' },
+  { syntax: 'sideways-rl' },
+  { syntax: 'sideways-lr' },
+]);
+
+</script>
diff --git a/css/css-typed-om/the-stylepropertymap/properties/z-index.html b/css/css-typed-om/the-stylepropertymap/properties/z-index.html
new file mode 100644
index 0000000..53773fb
--- /dev/null
+++ b/css/css-typed-om/the-stylepropertymap/properties/z-index.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>'z-index' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('z-index', [
+  { syntax: 'auto' },
+  {
+    syntax: '<number>',
+    // z-index needs to be an integer
+    specified: (input, result) => {
+      if (input instanceof CSSUnitValue && !Number.isInteger(input.value))
+        assert_style_value_equals(result, new CSSMathSum(input));
+      else
+        assert_style_value_equals(result, input);
+    },
+    computed: (input, result) => {
+      const number = input.to('number');
+      if (!Number.isInteger(number.value))
+        assert_style_value_equals(result, new CSSUnitValue(Math.round(number.value), 'number'));
+      else
+        assert_style_value_equals(result, number);
+    }
+  }
+]);
+
+</script>
diff --git a/css/css-ui/outline-016.html b/css/css-ui/outline-016.html
index 0f9047c..e62959c 100644
--- a/css/css-ui/outline-016.html
+++ b/css/css-ui/outline-016.html
@@ -25,7 +25,7 @@
   background: green;
   line-height: 25px;
   font-size: 25px;
-  font-family: ahem;
+  font-family: Ahem;
 }
 </style>
 
diff --git a/css/css-ui/parsing/box-sizing-invalid.html b/css/css-ui/parsing/box-sizing-invalid.html
new file mode 100644
index 0000000..90bb5a1
--- /dev/null
+++ b/css/css-ui/parsing/box-sizing-invalid.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing box-sizing with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#box-sizing">
+<meta name="assert" content="box-sizing supports only the grammar 'content-box | border-box'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("box-sizing", "auto");
+test_invalid_value("box-sizing", "content-box border-box");
+test_invalid_value("box-sizing", "fill-box");
+test_invalid_value("box-sizing", "margin-box");
+test_invalid_value("box-sizing", "padding-box");
+test_invalid_value("box-sizing", "view-box");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/box-sizing-valid.html b/css/css-ui/parsing/box-sizing-valid.html
new file mode 100644
index 0000000..3d23d47
--- /dev/null
+++ b/css/css-ui/parsing/box-sizing-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing box-sizing with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#box-sizing">
+<meta name="assert" content="box-sizing supports the full grammar 'content-box | border-box'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("box-sizing", "content-box");
+test_valid_value("box-sizing", "border-box");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/caret-color-invalid.html b/css/css-ui/parsing/caret-color-invalid.html
new file mode 100644
index 0000000..9751b9b
--- /dev/null
+++ b/css/css-ui/parsing/caret-color-invalid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing caret-color with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#caret-color">
+<meta name="assert" content="caret-color supports only the grammar 'auto | <color>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("caret-color", "none");
+test_invalid_value("caret-color", "invert");
+test_invalid_value("caret-color", "50%");
+test_invalid_value("caret-color", "red green");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/caret-color-valid.html b/css/css-ui/parsing/caret-color-valid.html
new file mode 100644
index 0000000..81cfe25
--- /dev/null
+++ b/css/css-ui/parsing/caret-color-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing caret-color with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#caret-color">
+<meta name="assert" content="caret-color supports the full grammar 'auto | <color>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("caret-color", "auto");
+test_valid_value("caret-color", "rgba(10, 20, 30, 0.4)");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/cursor-invalid.html b/css/css-ui/parsing/cursor-invalid.html
new file mode 100644
index 0000000..bd05530
--- /dev/null
+++ b/css/css-ui/parsing/cursor-invalid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing cursor with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#cursor">
+<meta name="assert" content="cursor supports only the grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("cursor", "en-resize");
+test_invalid_value("cursor", 'url("https://example.com/") alias');
+test_invalid_value("cursor", '1 2 url("https://example.com/"), copy');
+test_invalid_value("cursor", 'url("https://example.com/"), url("https://example.com/") 3, move');
+
+test_invalid_value("cursor", 'url("https://example.com/") 1px 2px, copy');
+test_invalid_value("cursor", 'url("https://example.com/"), url("https://example.com/") 3% 4%, move');
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/cursor-valid.html b/css/css-ui/parsing/cursor-valid.html
new file mode 100644
index 0000000..20ea267
--- /dev/null
+++ b/css/css-ui/parsing/cursor-valid.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing cursor with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#cursor">
+<meta name="assert" content="cursor supports the full grammar.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("cursor", "auto");
+test_valid_value("cursor", "default");
+test_valid_value("cursor", "none");
+test_valid_value("cursor", "context-menu");
+test_valid_value("cursor", "help");
+test_valid_value("cursor", "pointer");
+test_valid_value("cursor", "progress");
+test_valid_value("cursor", "wait");
+test_valid_value("cursor", "cell");
+test_valid_value("cursor", "crosshair");
+test_valid_value("cursor", "text");
+test_valid_value("cursor", "vertical-text");
+test_valid_value("cursor", "alias");
+test_valid_value("cursor", "copy");
+test_valid_value("cursor", "move");
+test_valid_value("cursor", "no-drop");
+test_valid_value("cursor", "not-allowed");
+test_valid_value("cursor", "grab");
+test_valid_value("cursor", "grabbing");
+test_valid_value("cursor", "e-resize");
+test_valid_value("cursor", "n-resize");
+test_valid_value("cursor", "ne-resize");
+test_valid_value("cursor", "nw-resize");
+test_valid_value("cursor", "s-resize");
+test_valid_value("cursor", "se-resize");
+test_valid_value("cursor", "sw-resize");
+test_valid_value("cursor", "w-resize");
+test_valid_value("cursor", "ew-resize");
+test_valid_value("cursor", "ns-resize");
+test_valid_value("cursor", "nesw-resize");
+test_valid_value("cursor", "nwse-resize");
+test_valid_value("cursor", "col-resize");
+test_valid_value("cursor", "row-resize");
+test_valid_value("cursor", "all-scroll");
+test_valid_value("cursor", "zoom-in");
+test_valid_value("cursor", "zoom-out");
+
+test_valid_value("cursor", 'url("https://example.com/"), alias', ['url("https://example.com/"), alias', 'url(https://example.com/), alias']);
+test_valid_value("cursor", 'url("https://example.com/") 1 calc(2 + 0), copy', ['url("https://example.com/") 1 calc(2), copy', 'url("https://example.com/") 1 2, copy', 'url(https://example.com/) 1 2, copy']);
+test_valid_value("cursor", 'url("https://example.com/"), url("https://example.com/") 3 -4, move', ['url("https://example.com/"), url("https://example.com/") 3 -4, move', 'url(https://example.com/), url(https://example.com/) 3 -4, move']);
+test_valid_value("cursor", 'url("https://example.com/") 5 6, grab', ['url("https://example.com/") 5 6, grab', 'url(https://example.com/) 5 6, grab']);
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-color-invalid.html b/css/css-ui/parsing/outline-color-invalid.html
new file mode 100644
index 0000000..029c1e9
--- /dev/null
+++ b/css/css-ui/parsing/outline-color-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline-color with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline-color">
+<meta name="assert" content="outline-color supports only the grammar '<color> | invert'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("outline-color", "auto");
+test_invalid_value("outline-color", "none");
+test_invalid_value("outline-color", "50%");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-color-valid-mandatory.html b/css/css-ui/parsing/outline-color-valid-mandatory.html
new file mode 100644
index 0000000..891bbc0
--- /dev/null
+++ b/css/css-ui/parsing/outline-color-valid-mandatory.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline-color with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline-color">
+<meta name="assert" content="outline-color supports '<color>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("outline-color", "rgba(10, 20, 30, 0.4)");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-color-valid-optional.html b/css/css-ui/parsing/outline-color-valid-optional.html
new file mode 100644
index 0000000..bd3b9e6
--- /dev/null
+++ b/css/css-ui/parsing/outline-color-valid-optional.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline-color with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline-color">
+<meta name="assert" content="outline-color supports 'invert'.">
+<meta name="flags" content="may">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Conformant UAs may ignore the invert value on platforms that do not support color inversion of the pixels on the screen.
+test_valid_value("outline-color", "invert");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-invalid.html b/css/css-ui/parsing/outline-invalid.html
new file mode 100644
index 0000000..ea8d88b
--- /dev/null
+++ b/css/css-ui/parsing/outline-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline">
+<meta name="assert" content="outline supports only the grammar '<outline-color> || <outline> || <outline>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("outline", "invert solid rgba(10, 20, 30, 0.4)");
+test_invalid_value("outline", "double invert groove");
+test_invalid_value("outline", "thin outset thick");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-offset-invalid.html b/css/css-ui/parsing/outline-offset-invalid.html
new file mode 100644
index 0000000..f547160
--- /dev/null
+++ b/css/css-ui/parsing/outline-offset-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline-offset with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline-offset">
+<meta name="assert" content="outline-offset supports only the grammar '<length>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("outline-offset", "auto");
+test_invalid_value("outline-offset", "1%");
+test_invalid_value("outline-offset", "2px 3px");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-offset-valid.html b/css/css-ui/parsing/outline-offset-valid.html
new file mode 100644
index 0000000..a7064e5
--- /dev/null
+++ b/css/css-ui/parsing/outline-offset-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline-offset with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline-offset">
+<meta name="assert" content="outline-offset supports the full grammar '<length>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("outline-offset", "0", "0px");
+test_valid_value("outline-offset", "1px");
+test_valid_value("outline-offset", "2em");
+test_valid_value("outline-offset", "calc(3rem + 4vw)");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-style-invalid.html b/css/css-ui/parsing/outline-style-invalid.html
new file mode 100644
index 0000000..1ea6230
--- /dev/null
+++ b/css/css-ui/parsing/outline-style-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 4: parsing outline-style with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui/#outline-style">
+<meta name="assert" content="outline-style supports only the grammar 'auto | <outline-line-style>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("outline-style", "hidden");
+test_invalid_value("outline-style", "dotted dashed");
+test_invalid_value("outline-style", "solid double groove ridge");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-style-valid.html b/css/css-ui/parsing/outline-style-valid.html
new file mode 100644
index 0000000..cebf614
--- /dev/null
+++ b/css/css-ui/parsing/outline-style-valid.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 4: parsing outline-style with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui/#outline-style">
+<meta name="assert" content="outline-style supports the full grammar 'auto | <outline-line-style>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("outline-style", "auto");
+test_valid_value("outline-style", "none");
+test_valid_value("outline-style", "dotted");
+test_valid_value("outline-style", "dashed");
+test_valid_value("outline-style", "solid");
+test_valid_value("outline-style", "double");
+test_valid_value("outline-style", "groove");
+test_valid_value("outline-style", "ridge");
+test_valid_value("outline-style", "inset");
+test_valid_value("outline-style", "outset");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-valid-mandatory.html b/css/css-ui/parsing/outline-valid-mandatory.html
new file mode 100644
index 0000000..f4cfe2c
--- /dev/null
+++ b/css/css-ui/parsing/outline-valid-mandatory.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline">
+<link rel="help" href="https://drafts.csswg.org/cssom/#serializing-css-values">
+<meta name="assert" content="outline supports the full grammar '<outline-color> || <outline> || <outline>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("outline", "rgba(10, 20, 30, 0.4)");
+
+test_valid_value("outline", "auto");
+test_valid_value("outline", "none"); // Edge serializes as "invert"
+test_valid_value("outline", "dotted");
+test_valid_value("outline", "dashed");
+test_valid_value("outline", "solid");
+test_valid_value("outline", "double");
+test_valid_value("outline", "groove");
+test_valid_value("outline", "ridge");
+test_valid_value("outline", "inset");
+test_valid_value("outline", "outset");
+
+test_valid_value("outline", "0", "0px");
+test_valid_value("outline", "1px");
+test_valid_value("outline", "calc(2em + 3ex)");
+test_valid_value("outline", "thin");
+test_valid_value("outline", "medium"); // Edge serializes as "invert"
+test_valid_value("outline", "thick");
+
+test_valid_value("outline", "dashed thin");
+test_valid_value("outline", "medium rgba(10, 20, 30, 0.4)", ["rgba(10, 20, 30, 0.4) medium", "rgba(10, 20, 30, 0.4)"]);
+
+test_valid_value("outline", "3px ridge rgba(10, 20, 30, 0.4)", "rgba(10, 20, 30, 0.4) ridge 3px");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-valid-optional.html b/css/css-ui/parsing/outline-valid-optional.html
new file mode 100644
index 0000000..66ec897
--- /dev/null
+++ b/css/css-ui/parsing/outline-valid-optional.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing outline with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline">
+<link rel="help" href="https://drafts.csswg.org/cssom/#serializing-css-values">
+<meta name="assert" content="outline supports the full grammar '<outline-color> || <outline> || <outline>'.">
+<meta name="assert" content="outline serializes in canonical order, with shortest possible serialization.">
+<meta name="flags" content="may">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// Conformant UAs may ignore the invert value on platforms that do not support color inversion of the pixels on the screen.
+test_valid_value("outline", "invert");
+test_valid_value("outline", "invert dotted 1px");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-width-invalid.html b/css/css-ui/parsing/outline-width-invalid.html
new file mode 100644
index 0000000..71cb533
--- /dev/null
+++ b/css/css-ui/parsing/outline-width-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 4: parsing outline-width with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui/#outline-width">
+<meta name="assert" content="outline-width supports only the grammar '<border-width>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("outline-width", "auto");
+test_invalid_value("outline-width", "1%");
+test_invalid_value("outline-width", "thin medium thick medium thin");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/outline-width-valid.html b/css/css-ui/parsing/outline-width-valid.html
new file mode 100644
index 0000000..f683991
--- /dev/null
+++ b/css/css-ui/parsing/outline-width-valid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 4: parsing outline-width with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui/#outline-width">
+<meta name="assert" content="outline-width supports the full grammar '<border-width>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("outline-width", "0", "0px");
+test_valid_value("outline-width", "1px");
+test_valid_value("outline-width", "2em");
+test_valid_value("outline-width", "calc(2em + 3ex)");
+test_valid_value("outline-width", "thin");
+test_valid_value("outline-width", "medium");
+test_valid_value("outline-width", "thick");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/resize-invalid.html b/css/css-ui/parsing/resize-invalid.html
new file mode 100644
index 0000000..5fdb8ee
--- /dev/null
+++ b/css/css-ui/parsing/resize-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing resize with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#resize">
+<meta name="assert" content="resize supports only the grammar 'none | both | horizontal | vertical'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("resize", "auto");
+test_invalid_value("resize", "horizontal vertical");
+test_invalid_value("resize", "both 0");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/resize-valid.html b/css/css-ui/parsing/resize-valid.html
new file mode 100644
index 0000000..e9f03d9
--- /dev/null
+++ b/css/css-ui/parsing/resize-valid.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing resize with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#resize">
+<meta name="assert" content="resize supports the full grammar 'none | both | horizontal | vertical'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("resize", "none");
+test_valid_value("resize", "both");
+test_valid_value("resize", "horizontal");
+test_valid_value("resize", "vertical");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/resources/parsing-testcommon.js b/css/css-ui/parsing/resources/parsing-testcommon.js
new file mode 100644
index 0000000..b075882
--- /dev/null
+++ b/css/css-ui/parsing/resources/parsing-testcommon.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
+function test_valid_value(property, value, serializedValue) {
+    if (arguments.length < 3)
+        serializedValue = value;
+
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
+
+        var div = document.createElement('div');
+        div.style[property] = value;
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
+        div.style[property] = readValue;
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+}
+
+function test_invalid_value(property, value) {
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_equals(div.style.getPropertyValue(property), "");
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
+}
diff --git a/css/css-ui/parsing/text-overflow-invalid.html b/css/css-ui/parsing/text-overflow-invalid.html
new file mode 100644
index 0000000..f397481
--- /dev/null
+++ b/css/css-ui/parsing/text-overflow-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing text-overflow with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#text-overflow">
+<meta name="assert" content="text-overflow supports only the grammar 'clip | ellipsis'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("text-overflow", "auto");
+test_invalid_value("text-overflow", "clip ellipsis clip");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/parsing/text-overflow-valid.html b/css/css-ui/parsing/text-overflow-valid.html
new file mode 100644
index 0000000..80f267e
--- /dev/null
+++ b/css/css-ui/parsing/text-overflow-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS UI Level 3: parsing text-overflow with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-3/#text-overflow">
+<meta name="assert" content="text-overflow supports the full grammar 'clip | ellipsis'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("text-overflow", "clip");
+test_valid_value("text-overflow", "ellipsis");
+</script>
+</body>
+</html>
diff --git a/css/css-ui/reference/text-overflow-008-ref.html b/css/css-ui/reference/text-overflow-008-ref.html
index 4eb357f..4af20b7 100644
--- a/css/css-ui/reference/text-overflow-008-ref.html
+++ b/css/css-ui/reference/text-overflow-008-ref.html
@@ -6,7 +6,7 @@
 div {
   font-size: 100px;
   line-height: 1;
-  font-family: ahem;
+  font-family: Ahem;
   overflow: hidden;
   width: 0.5em;
   color: green;
diff --git a/css/css-ui/reference/text-overflow-013-ref.html b/css/css-ui/reference/text-overflow-013-ref.html
index b4565a2..9f6cbd0 100644
--- a/css/css-ui/reference/text-overflow-013-ref.html
+++ b/css/css-ui/reference/text-overflow-013-ref.html
@@ -6,7 +6,7 @@
 .test {
   overflow: hidden;
   white-space: pre;
-  font: 100px/1 ahem;
+  font: 100px/1 Ahem;
   width: 2em;
 }
 .green {
diff --git a/css/css-ui/reference/text-overflow-027-ref.html b/css/css-ui/reference/text-overflow-027-ref.html
new file mode 100644
index 0000000..8fc664b
--- /dev/null
+++ b/css/css-ui/reference/text-overflow-027-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test Reference</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<style>
+div { font-family: monospace; }
+</style>
+
+<p>The test passes if the following text is visible below: 123456 FE…</p>
+<div>123456 FE…</bdo></div>
diff --git a/css/css-ui/reference/text-overflow-028-ref.html b/css/css-ui/reference/text-overflow-028-ref.html
new file mode 100644
index 0000000..b25ab26
--- /dev/null
+++ b/css/css-ui/reference/text-overflow-028-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test Reference</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<style>
+div { font-family: monospace; }
+</style>
+
+<p>The test passes if the following text is visible below: …56 FEDCBA</p>
+<div>…56 FEDCBA</div>
diff --git a/css/css-ui/reference/text-overflow-029-ref.html b/css/css-ui/reference/text-overflow-029-ref.html
new file mode 100644
index 0000000..9cdd741
--- /dev/null
+++ b/css/css-ui/reference/text-overflow-029-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>CSS Basic User Interface Reference</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<style>
+div { font: 20px monospace; }
+</style>
+
+<div>Test passed…</div>
diff --git a/css/css-ui/text-overflow-007.html b/css/css-ui/text-overflow-007.html
index bfc6939..f33cb79 100644
--- a/css/css-ui/text-overflow-007.html
+++ b/css/css-ui/text-overflow-007.html
@@ -9,7 +9,7 @@
 <style>
 .test {
   white-space: pre;
-  font: 50px/1 ahem;
+  font: 50px/1 Ahem;
   text-overflow: ellipsis;
   overflow: hidden;
   width: 2.5em; /* We will be fitting 2em worth of characters in a 2.5em space, to see where the extra .5em goes. It should be after, not between the characters.*/
diff --git a/css/css-ui/text-overflow-008.html b/css/css-ui/text-overflow-008.html
index 79c8443..f22afc1 100644
--- a/css/css-ui/text-overflow-008.html
+++ b/css/css-ui/text-overflow-008.html
@@ -12,7 +12,7 @@
 -->
 <style>
 .test {
-  font: 100px/1 ahem;
+  font: 100px/1 Ahem;
   text-overflow: ellipsis;
   overflow: hidden;
   width: 0.5em;
diff --git a/css/css-ui/text-overflow-010.html b/css/css-ui/text-overflow-010.html
index 1556da3..4cbcb45 100644
--- a/css/css-ui/text-overflow-010.html
+++ b/css/css-ui/text-overflow-010.html
@@ -16,7 +16,7 @@
 }
 .test {
   white-space: pre;
-  font-family: ahem;
+  font-family: Ahem;
   text-overflow: ellipsis;
   overflow: hidden;
   width: 5.2em; /* making this 5.2em rather than 5em, to ensure that the atomic inline gets entirely hidden even if it is only partially obstructing */
diff --git a/css/css-ui/text-overflow-011.html b/css/css-ui/text-overflow-011.html
index 33ebd0b..60db171 100644
--- a/css/css-ui/text-overflow-011.html
+++ b/css/css-ui/text-overflow-011.html
@@ -9,7 +9,7 @@
 <style>
 .test {
   white-space: pre;
-  font: 100px/1 ahem;
+  font: 100px/1 Ahem;
   color: green;
   overflow: hidden;
   text-overflow: ellipsis;
diff --git a/css/css-ui/text-overflow-013.html b/css/css-ui/text-overflow-013.html
index dd8f986..af56474 100644
--- a/css/css-ui/text-overflow-013.html
+++ b/css/css-ui/text-overflow-013.html
@@ -11,7 +11,7 @@
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: pre;
-  font: 100px/1 ahem;
+  font: 100px/1 Ahem;
   color: green;
 
   /* 2em width and starting with a space
diff --git a/css/css-ui/text-overflow-014.html b/css/css-ui/text-overflow-014.html
index 6e1e989..380e5e4 100644
--- a/css/css-ui/text-overflow-014.html
+++ b/css/css-ui/text-overflow-014.html
@@ -11,7 +11,7 @@
   overflow: hidden;
   text-overflow: ellipsis;
   white-space:pre;
-  font: 100px/1 ahem;
+  font: 100px/1 Ahem;
   color: green;
 
   /* 2em width and starting with a space
diff --git a/css/css-ui/text-overflow-015.html b/css/css-ui/text-overflow-015.html
index 8a6672c..2269d5e 100644
--- a/css/css-ui/text-overflow-015.html
+++ b/css/css-ui/text-overflow-015.html
@@ -13,7 +13,7 @@
   width: 100px;
   height: 100px;
   white-space: pre;
-  font: 20px/1 ahem;
+  font: 20px/1 Ahem;
   color: green;
   background: url("support/1x1-red.png") top right / 1em 1em no-repeat, green;
 }
diff --git a/css/css-ui/text-overflow-016.html b/css/css-ui/text-overflow-016.html
index 6e642ae..e123adf 100644
--- a/css/css-ui/text-overflow-016.html
+++ b/css/css-ui/text-overflow-016.html
@@ -12,7 +12,7 @@
   text-overflow: ellipsis;
   width: 100px;
   white-space: pre;
-  font: 50px/1 ahem;
+  font: 50px/1 Ahem;
   height: 20px;
   position: relative;
 }
diff --git a/css/css-ui/text-overflow-017.html b/css/css-ui/text-overflow-017.html
index 7e857bc..05abb61 100644
--- a/css/css-ui/text-overflow-017.html
+++ b/css/css-ui/text-overflow-017.html
@@ -15,7 +15,7 @@
   width: 2.5em;
   height: 1em;
   white-space: pre;
-  font: 100px/1 ahem;
+  font: 100px/1 Ahem;
   color: blue;
 }
 #discard {
diff --git a/css/css-ui/text-overflow-020.html b/css/css-ui/text-overflow-020.html
index caac6fb..6134921 100644
--- a/css/css-ui/text-overflow-020.html
+++ b/css/css-ui/text-overflow-020.html
@@ -16,7 +16,7 @@
 }
 div > p {
   white-space: pre;
-  font: 40px/1 ahem;
+  font: 40px/1 Ahem;
   color: green;
   background: red;
   margin: 0;
diff --git a/css/css-ui/text-overflow-023.html b/css/css-ui/text-overflow-023.html
index ca3f8da..a812218 100644
--- a/css/css-ui/text-overflow-023.html
+++ b/css/css-ui/text-overflow-023.html
@@ -19,7 +19,7 @@
 #parent {
   position: absolute;
   top: 0; left: 0;
-  font: 50px / 1  ahem ;
+  font: 50px/1 Ahem ;
   overflow: hidden;
   width: 3em;
   text-overflow: ellipsis;
diff --git a/css/css-ui/text-overflow-024-ref.html b/css/css-ui/text-overflow-024-ref.html
new file mode 100644
index 0000000..2e9b830
--- /dev/null
+++ b/css/css-ui/text-overflow-024-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: text-overflow on tr::before with overflow:hidden</title>
+  <style type="text/css">
+
+    td {
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+
+  </style>
+</head>
+<body>
+PASS if there is an ellipsis at the end of the text below.
+  <table style="table-layout: fixed; width: 130px" cellpadding="0" cellspacing="0">
+    <tr><td>Some long text here that overflows and whatnot.</td></tr>
+  </table>
+
+</body>
+</html>
diff --git a/css/css-ui/text-overflow-024.html b/css/css-ui/text-overflow-024.html
new file mode 100644
index 0000000..0ca5b42
--- /dev/null
+++ b/css/css-ui/text-overflow-024.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Test: text-overflow on tr::before with overflow:hidden</title>
+  <link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+  <link rel="help" href="https://www.w3.org/TR/css3-ui/#text-overflow" title="5.2. the 'text-overflow' property">
+  <link rel="match" href="text-overflow-024-ref.html">
+  <style type="text/css">
+
+    tr::before {
+      content: "Some long text here that overflows and whatnot.";
+      display: table-cell;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+
+  </style>
+</head>
+<body>
+PASS if there is an ellipsis at the end of the text below.
+  <table style="table-layout: fixed; width: 130px" cellpadding="0" cellspacing="0">
+    <tr></tr>
+  </table>
+
+</body>
+</html>
diff --git a/css/css-ui/text-overflow-025-ref.html b/css/css-ui/text-overflow-025-ref.html
new file mode 100644
index 0000000..c8acd47
--- /dev/null
+++ b/css/css-ui/text-overflow-025-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: text-overflow on tr::before without overflow:hidden</title>
+  <style type="text/css">
+
+    td {
+      white-space: nowrap;
+    }
+
+  </style>
+</head>
+<body>
+PASS if there is no ellipsis below.
+  <table style="table-layout: fixed; width: 130px" cellpadding="0" cellspacing="0">
+    <tr><td>Some long text here that overflows and whatnot.</td></tr>
+  </table>
+
+</body>
+</html>
diff --git a/css/css-ui/text-overflow-025.html b/css/css-ui/text-overflow-025.html
new file mode 100644
index 0000000..3e0fce0
--- /dev/null
+++ b/css/css-ui/text-overflow-025.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Test: text-overflow on tr::before without overflow:hidden</title>
+  <link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+  <link rel="help" href="https://www.w3.org/TR/css3-ui/#text-overflow" title="5.2. the 'text-overflow' property">
+  <link rel="match" href="text-overflow-025-ref.html">
+  <style type="text/css">
+
+    tr::before {
+      content: "Some long text here that overflows and whatnot.";
+      display: table-cell;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+    tr {
+      overflow: hidden;
+    }
+
+  </style>
+</head>
+<body>
+PASS if there is no ellipsis below.
+  <table style="table-layout: fixed; width: 130px" cellpadding="0" cellspacing="0">
+    <tr></tr>
+  </table>
+
+</body>
+</html>
diff --git a/css/css-ui/text-overflow-026-ref.html b/css/css-ui/text-overflow-026-ref.html
new file mode 100644
index 0000000..f2db8c7
--- /dev/null
+++ b/css/css-ui/text-overflow-026-ref.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: text-overflow with leading white-space</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1403986">
+  <style type="text/css">
+html,body {
+  color:black; background-color:white; font:16px/1 monospace;
+}
+
+.ellipsize {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: 100px;
+  border: 1px solid;
+}
+
+.inline-block {
+  display: inline-block;
+  background: red;
+}
+  </style>
+</head>
+<body>
+
+<pre>
+The test PASS if all of the following are true:
+1. there are no red areas
+2. the first two blocks display "PASS" but no ellipsis
+3. the last three blocks display an ellipsis
+</pre>
+
+<div class="ellipsize" style="text-overflow: clip">
+  <span style="margin-left:5px"></span><span class="inline-block" style="background:lime">
+    PASS PASS PASS PASS PASS</span>
+</div>
+
+<div class="ellipsize" style="text-overflow: clip">
+  <span style="margin-left:5px"></span>
+  <span class="inline-block" style="background:lime">
+    PASS PASS PASS PASS PASS</span>
+</div>
+
+<div class="ellipsize">
+  <span style="margin-left:10px"></span>&nbsp;&#x2026;
+</div>
+
+<div class="ellipsize">
+  &nbsp;&#x2026;
+</div>
+
+<div class="ellipsize">
+  a&#x2026;
+</div>
+
+</body>
+</html>
diff --git a/css/css-ui/text-overflow-026.html b/css/css-ui/text-overflow-026.html
new file mode 100644
index 0000000..2d1aa57
--- /dev/null
+++ b/css/css-ui/text-overflow-026.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Test: text-overflow with leading white-space</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1403986">
+  <link rel="help" href="https://www.w3.org/TR/css3-ui/#text-overflow" title="5.2. the 'text-overflow' property">
+  <link rel="match" href="text-overflow-026-ref.html">
+  <style type="text/css">
+html,body {
+  color:black; background-color:white; font:16px/1 monospace;
+}
+
+.ellipsize {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: 100px;
+  border: 1px solid;
+}
+
+.inline-block {
+  display: inline-block;
+  background: red;
+}
+  </style>
+</head>
+<body>
+
+<pre>
+The test PASS if all of the following are true:
+1. there are no red areas
+2. the first two blocks display "PASS" but no ellipsis
+3. the last three blocks display an ellipsis
+</pre>
+
+<div class="ellipsize">
+  <span style="margin-left:5px"></span><span class="inline-block" style="background:lime">
+    PASS PASS PASS PASS PASS</span>
+</div>
+
+<div class="ellipsize">
+  <span style="margin-left:5px"></span>
+  <span class="inline-block" style="background:lime">
+    PASS PASS PASS PASS PASS</span>
+</div>
+
+<div class="ellipsize">
+  <span style="margin-left:10px"></span>&nbsp;<span class="inline-block">
+    FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL </span>
+</div>
+
+<div class="ellipsize">
+  &nbsp;<span class="inline-block">
+    FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL </span>
+</div>
+
+<div class="ellipsize">
+  a<span class="inline-block">
+    FAIL FAIL FAIL FAIL FAIL FAIL FAIL FAIL </span>
+</div>
+
+</body>
+</html>
diff --git a/css/css-ui/text-overflow-027.html b/css/css-ui/text-overflow-027.html
new file mode 100644
index 0000000..fcb0104
--- /dev/null
+++ b/css/css-ui/text-overflow-027.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test: text-overflow applies visually to bidi</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="http://www.w3.org/TR/css-ui-3/#text-overflow">
+<link rel="help" href="http://www.w3.org/TR/css-ui-4/#text-overflow">
+<link rel="match" href="reference/text-overflow-027-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="text-overflow is a visual operation that occurs after layout, and therfore ellides text from the visual end of the line, even in bidi situations">
+<style>
+div {
+  font-family: monospace;
+  width: 10ch;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: pre;
+}
+</style>
+
+<p>The test passes if the following text is visible below: 123456 FE…</p>
+<div>123456 <bdo dir=rtl>ABCDEF</bdo></div>
diff --git a/css/css-ui/text-overflow-028.html b/css/css-ui/text-overflow-028.html
new file mode 100644
index 0000000..af906e1
--- /dev/null
+++ b/css/css-ui/text-overflow-028.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test: text-overflow applies visually to bidi</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="http://www.w3.org/TR/css-ui-3/#text-overflow">
+<link rel="help" href="http://www.w3.org/TR/css-ui-4/#text-overflow">
+<link rel="match" href="reference/text-overflow-028-ref.html">
+<meta name="flags" content="">
+<meta name="assert" content="text-overflow is a visual operation that occurs after layout, and therfore ellides text from the visual end of the line, even in bidi situations">
+<style>
+div {
+  font-family: monospace;
+  width: 10ch;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: pre;
+}
+</style>
+
+<p>The test passes if the following text is visible below: …56 FEDCBA</p>
+<div dir=rtl><bdo dir=rtl>ABCDEF</bdo> 123456</div>
diff --git a/css/css-ui/text-overflow-029.html b/css/css-ui/text-overflow-029.html
new file mode 100644
index 0000000..f178011
--- /dev/null
+++ b/css/css-ui/text-overflow-029.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test: text-overflow and bidi interaction</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="http://www.w3.org/TR/css-ui-3/#text-overflow">
+<link rel="help" href="http://www.w3.org/TR/css-ui-4/#text-overflow">
+<link rel="match" href="reference/text-overflow-029-ref.html">
+<meta name="assert" content="When there's content of mixed directionality, text-overflow ellides the characters at the physical end of the line.">
+<meta name="flags" content="">
+<style>
+div {
+  font: 20px monospace;
+  width: 12.3ch; /* slightly more than 12ch because in some browsers (safari) the ellipsis is slightly large than other characters, even in monospace fonts. */
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+}
+</style>
+
+<div>Test &#x202E;deliafdessap&#x202C;</div>
diff --git a/css/css-values/attr-invalid-type-001.html b/css/css-values/attr-invalid-type-001.html
index a56c144..c427ade 100644
--- a/css/css-values/attr-invalid-type-001.html
+++ b/css/css-values/attr-invalid-type-001.html
@@ -7,7 +7,7 @@
 		Attribute references (types)
 	</title>
 	<meta name="assert" content="
-		When the type of an att() function is known and unexpected, the declaration is ingored
+		When the type of an attr() function is known and unexpected, the declaration is ignored
 	" />
 
 	<link
diff --git a/css/css-values/attr-invalid-type-002.html b/css/css-values/attr-invalid-type-002.html
index 69766c0..797700d 100644
--- a/css/css-values/attr-invalid-type-002.html
+++ b/css/css-values/attr-invalid-type-002.html
@@ -7,7 +7,7 @@
 		Attribute references (types)
 	</title>
 	<meta name="assert" content="
-		When the type of an att() function is known and unexpected, the declaration is ignored
+		When the type of an attr() function is known and unexpected, the declaration is ignored
 	" />
 
 	<link
diff --git a/css/css-values/calc-ch-ex-lang-ref.html b/css/css-values/calc-ch-ex-lang-ref.html
new file mode 100644
index 0000000..e0ac1ea
--- /dev/null
+++ b/css/css-values/calc-ch-ex-lang-ref.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+div {
+  width: calc(1ex + 1ch + 1em);
+  height: calc(1ex + 1ch + 1em);
+  background: green;
+}
+</style>
+<div></div>
diff --git a/css/css-values/calc-ch-ex-lang.html b/css/css-values/calc-ch-ex-lang.html
new file mode 100644
index 0000000..eb44747
--- /dev/null
+++ b/css/css-values/calc-ch-ex-lang.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: Calc in font-size with ch / ex units across lang changes</title>
+<link rel="help" href="https://drafts.csswg.org/css-values/#ch">
+<link rel="help" href="https://drafts.csswg.org/css-values/#ex">
+<link rel="help" href="https://drafts.csswg.org/css-values/#funcdef-calc">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1431031">
+<link rel="match" href="calc-ch-ex-lang-ref.html">
+<style>
+div[lang] {
+  font-size: calc(1ex + 1ch + 1em);
+}
+</style>
+<div lang="en">
+  <div style="width: 1em; height: 1em; background: green;"></div>
+</div>
diff --git a/css/css-values/calc-in-calc.html b/css/css-values/calc-in-calc.html
index 3e035fe..51a1ae5 100644
--- a/css/css-values/calc-in-calc.html
+++ b/css/css-values/calc-in-calc.html
@@ -29,7 +29,7 @@
 
 			html { background: red; overflow: hidden; }
 			#outer { position: absolute; top: 0px; left: 0px; background: green; width: 100%; }
-			#outer { height: calc(calc(100%));
+			#outer { height: calc(calc(100%)); }
 
 	</style>
 
diff --git a/css/css-values/calc-in-color-001.html b/css/css-values/calc-in-color-001.html
new file mode 100644
index 0000000..7db087d
--- /dev/null
+++ b/css/css-values/calc-in-color-001.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: calc() function in &lt;color&gt;</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-values/#funcdef-calc">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="testNode"></div>
+<script>
+const div = document.querySelector("#testNode");
+const TESTS = {
+  // specified -> expected
+  "rgb(calc(0), calc(255 + 0), calc(140 - 139 - 1))": "rgb(0, 255, 0)",
+  "rgba(calc(0%) calc(100%) calc(0%) / calc(10% * 10))": "rgb(0, 255, 0)",
+  "hsl(calc(5deg * (360 / 5)), calc(10% * 10), calc(10% * 10))": "rgb(255, 255, 255)",
+  "hsla(calc(5 * (360 / 5)), calc(10% * 10), calc(10% * 10), calc(1.0))": "rgb(255, 255, 255)"
+}
+
+test(function() {
+  for (let test in TESTS) {
+    div.style.backgroundColor = "";
+    div.style.backgroundColor = test;
+    assert_equals(getComputedStyle(div).backgroundColor, TESTS[test], test);
+  }
+}, "calc() in color functions");
+</script>
diff --git a/css/css-values/calc-in-font-feature-settings.html b/css/css-values/calc-in-font-feature-settings.html
new file mode 100644
index 0000000..0ce8d7e
--- /dev/null
+++ b/css/css-values/calc-in-font-feature-settings.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: calc() function in font-feature-settings</title>
+<link rel="author" title="Chris Nardi" href="mailto:cnardi@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-values/#funcdef-calc">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+    #test {
+        font-feature-settings: "vert" calc(2);
+    }
+</style>
+<div id="test"></div>
+<script>
+    const div = document.querySelector("#test");
+    // Chrome serializes font-feature-settings with single quotes vs. double quotes
+    // in other browsers, but that's not the issue being tested.
+    const expected = ["'vert' 2", "\"vert\" 2"];
+    test(function() {
+        assert_in_array(getComputedStyle(div).fontFeatureSettings, expected);
+    }, "calc() in font-feature-settings");
+</script>
\ No newline at end of file
diff --git a/css/css-values/calc-rem-lang-ref.html b/css/css-values/calc-rem-lang-ref.html
new file mode 100644
index 0000000..a0f6add
--- /dev/null
+++ b/css/css-values/calc-rem-lang-ref.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<p>You should see a green box twice-the-initial-font-size wide.</p>
+<div style="width: 2em; height: 2em; background: green;"></div>
diff --git a/css/css-values/calc-rem-lang.html b/css/css-values/calc-rem-lang.html
new file mode 100644
index 0000000..3994efc
--- /dev/null
+++ b/css/css-values/calc-rem-lang.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html lang="en"><!-- The lang is important! -->
+<meta charset="utf-8">
+<title>CSS Test: Calc with rem and relative units on the root element</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-values/#rem">
+<link rel="help" href="https://drafts.csswg.org/css-values/#funcdef-calc">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1431031">
+<link rel="match" href="calc-rem-lang-ref.html">
+<style>
+  html {
+    font-size: calc(1rem + 1em);
+  }
+</style>
+<p style="font-size: initial">You should see a green box twice-the-initial-font-size wide.</p>
+<div style="width: 1em; height: 1em; background: green;"></div>
+</html>
diff --git a/css/css-values/calc-serialization.html b/css/css-values/calc-serialization.html
new file mode 100644
index 0000000..f92de73
--- /dev/null
+++ b/css/css-values/calc-serialization.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Values and Units: calc() serialization.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-values/#calc-serialize">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1731">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="content"></div>
+<script>
+test(function() {
+  // specified -> expected
+  var values = {
+    "calc(10px + 1vmin + 10%)": "calc(10% + 10px + 1vmin)",
+    "calc(10px + 1vmin)": "calc(10px + 1vmin)",
+    "calc(10px + 1em)": "calc(1em + 10px)",
+    "calc(1vmin - 10px)": "calc(-10px + 1vmin)",
+    "calc(-10px + 1em)": "calc(1em - 10px)",
+    "calc(-10px)": "calc(-10px)",
+  };
+
+  var content = document.getElementById("content");
+
+  for (var prop in values) {
+    content.style.width = prop;
+    assert_equals(content.style.width, values[prop], "Serialization of " + prop);
+  }
+}, "calc() serialization")
+</script>
diff --git a/css/css-values/calc-unit-analysis.html b/css/css-values/calc-unit-analysis.html
index 346a829..1520b4d 100644
--- a/css/css-values/calc-unit-analysis.html
+++ b/css/css-values/calc-unit-analysis.html
@@ -9,19 +9,6 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
 <style id="style"></style>
-<script id="metadata_cache">/*
-{
-  "unitless_zero_in_calc_is_a_numeric_type_not_length": { "assert": "invalid calc expression: unitless zero in calc() is a numeric type, not length" },
-  "0px_in_calc": { "assert": "invalid calc expression: 0px in calc()" },
-  "addition_of_length_and_number": { "assert": "invalid calc expression: addition of length and number" },
-  "addition_of_number_and_length": { "assert": "invalid calc expression: addition of number and length" },
-  "subtraction_of_length_and_number": { "assert": "invalid calc expression: subtraction of length and number" },
-  "subtraction_of_number_and_length": { "assert": "invalid calc expression: subtraction of number and length" },
-  "multiplication_of_length_and_number": { "assert": "invalid calc expression: multiplication of length and number" },
-  "multiplication_of_number_and_length": { "assert": "invalid calc expression: multiplication of number and length" },
-  "multiplication_of_length_and_length": { "assert": "invalid calc expression: multiplication of length and length" }
-}
-*/</script>
 </head>
 <body onload="run()">
 <div id=log></div>
diff --git a/css/css-values/lh-rlh-on-root-001.html b/css/css-values/lh-rlh-on-root-001.html
new file mode 100644
index 0000000..228da4e
--- /dev/null
+++ b/css/css-values/lh-rlh-on-root-001.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Values and Units Test: using lh and rlh units on the root element</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#font-relative-lengths">
+<style>
+#measure_me { position: absolute; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id=measure_me>&nbsp;</div>
+
+<script>
+  function get_root_font_size() {
+    return parseFloat(window.getComputedStyle(window.document.documentElement).fontSize);
+  }
+  function get_root_line_height() {
+    /* getComputedStyle returns the computed value (not the used value) for the line-height property,
+       and the computed value of line-height:normal is normal,
+       so we cannot just query the value fo the proerty directly on the root element.
+       However the height of an abspos that only contains a single character from the first available font
+       and doesn't have any ancestor that changes the font-size or line-height property
+       gives us an indirect way to measure the root line-height in px.
+    */
+    return parseFloat(window.getComputedStyle(document.getElementById("measure_me")).height);
+  }
+
+  window.document.documentElement.style="font-size: initial; line-height:initial;";
+  initial_f_s = get_root_font_size();
+  initial_l_h = get_root_line_height();
+
+  test(function() {
+    window.document.documentElement.style="font-size: 142px; line-height: 1lh;";
+    l_h = get_root_line_height();
+    assert_approx_equals( l_h, initial_l_h, 1, "the lh unit on the root element's line-height property uses font metrics corresponding to the initial values of the font or line-height properties");
+  }, "lh in line-height on root");
+
+  test(function() {
+    window.document.documentElement.style="font-size: 142px; line-height: 1rlh;";
+    l_h = get_root_line_height();
+    assert_approx_equals( l_h, initial_l_h, 1, "the rlh unit on the root element's line-height property uses font metrics corresponding to the initial values of the font or line-height properties");
+  }, "rlh in line-height on root");
+
+  test(function() {
+    window.document.documentElement.style="font-size: 1lh; line-height: 142px;";
+    f_s = get_root_font_size();
+    assert_approx_equals( f_s, initial_f_s, 1, "the lh unit on the root element's font-size property uses font metrics corresponding to the initial values of the font or line-height properties");
+  }, "lh in font-size on root");
+
+  test(function() {
+    window.document.documentElement.style="font-size: 1rlh; line-height: 142px;";
+    f_s = get_root_font_size();
+    assert_approx_equals( f_s, initial_f_s, 1, "the rlh unit on the root element's font-size property uses font metrics corresponding to the initial values of the font or line-height properties");
+
+  }, "rlh in font-size on root");
+
+
+  test(function() {
+    window.document.documentElement.style="font-size: 142px; line-height: 2lh;";
+    l_h = get_root_line_height();
+    assert_approx_equals( l_h, initial_l_h * 2, 1, "the lh unit on the root element's line-height property actually works as a unit and doesn't merely cause a fallback that doesn't take the number of units into account");
+  }, "2lh in line-height on root");
+
+  test(function() {
+    window.document.documentElement.style="font-size: 142px; line-height: 2rlh;";
+    l_h = get_root_line_height();
+    assert_approx_equals( l_h, initial_l_h * 2, 1, "the rlh unit on the root element's line-height property actually works as a unit and doesn't merely cause a fallback that doesn't take the number of units into account");
+  }, "2rlh in line-height on root");
+
+  test(function() {
+    window.document.documentElement.style="font-size: 2lh; line-height: 142px;";
+    f_s = get_root_font_size();
+    assert_approx_equals( f_s, initial_f_s * 2, 1, "the lh unit on the root element's font-size property actually works as a unit and doesn't merely cause a fallback that doesn't take the number of units into account");
+  }, "2lh in font-size on root");
+
+  test(function() {
+    window.document.documentElement.style="font-size: 2rlh; line-height: 142px;";
+    f_s = get_root_font_size();
+    assert_approx_equals( f_s, initial_f_s * 2, 1, "the rlh unit on the root element's font-size property actually works as a unit and doesn't merely cause a fallback that doesn't take the number of units into account");
+
+  }, "2rlh in font-size on root");
+
+  /*make the test result page readable again*/
+  window.document.documentElement.style="font-size: initial; line-height: initial;";
+</script>
diff --git a/css/css-values/lh-unit-001.html b/css/css-values/lh-unit-001.html
new file mode 100644
index 0000000..f7a6fc9
--- /dev/null
+++ b/css/css-values/lh-unit-001.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Values and Units Test: using lh in line-height</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#font-relative-lengths">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="The lh unit resolves against the parent when used in the line-height property.">
+<style>
+div {
+  font-size: 50px;
+  line-height: 1;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+aside {
+  background: green;
+  font-size: 42px; /* number doesn't matter, as long as it's neither 100 nor 50 */
+  line-height: 2lh;
+}
+
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+<div><aside>&nbsp;</aside></div>
diff --git a/css/css-values/lh-unit-002.html b/css/css-values/lh-unit-002.html
new file mode 100644
index 0000000..316637a
--- /dev/null
+++ b/css/css-values/lh-unit-002.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Values and Units Test: using lh in font-size</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#font-relative-lengths">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="The lh unit resolves against the parent when used in the font-size property.">
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  background: red;
+  line-height: 50px;
+}
+aside {
+  background: green;
+  height: 1em;
+  line-height: 42px; /* number doesn't matter, as long as it's neither 50 nor 100*/
+  font-size: 2lh;
+}
+
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+<div><aside>&nbsp;</aside></div>
diff --git a/css/css-values/iframe/vh-support-transform-origin-iframe.html b/css/css-values/support/vh-support-transform-origin-iframe.html
similarity index 100%
rename from css/css-values/iframe/vh-support-transform-origin-iframe.html
rename to css/css-values/support/vh-support-transform-origin-iframe.html
diff --git a/css/css-values/iframe/vh-support-transform-translate-iframe.html b/css/css-values/support/vh-support-transform-translate-iframe.html
similarity index 100%
rename from css/css-values/iframe/vh-support-transform-translate-iframe.html
rename to css/css-values/support/vh-support-transform-translate-iframe.html
diff --git a/css/css-values/urls/empty.html b/css/css-values/urls/empty.html
new file mode 100644
index 0000000..3ab7079
--- /dev/null
+++ b/css/css-values/urls/empty.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>Empty URLs behaviour</title>
+<link rel=help href=https://drafts.csswg.org/css-values/#url-empty>
+<link rel=help href=https://github.com/w3c/csswg-drafts/issues/2211#issuecomment-365677844>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+#inline-unquoted {
+    background-image: url();
+}
+
+#inline-quoted {
+    background-image: url("");
+}
+</style>
+<link rel=stylesheet href=support/empty-urls.css>
+<div id="inline-unquoted"></div>
+<div id="inline-quoted"></div>
+<div id="external-unquoted"></div>
+<div id="external-quoted"></div>
+<script>
+const ids = [
+  "inline-unquoted",
+  "inline-quoted",
+  "external-unquoted",
+  "external-quoted"
+];
+
+const inline_url = location.href;
+const external_url = new URL(document.querySelector("link[rel=stylesheet]").href, location.href).href;
+
+for (let id of ids) {
+    test(function() {
+        const el = document.getElementById(id);
+        const expected = id.startsWith("inline-") ? inline_url : external_url;
+        const style = window.getComputedStyle(el);
+        assert_equals(style["background-image"], 'url("' + expected + '")');
+    }, "empty URL: " + id);
+}
+</script>
diff --git a/css/css-values/urls/support/empty-urls.css b/css/css-values/urls/support/empty-urls.css
new file mode 100644
index 0000000..ceed358
--- /dev/null
+++ b/css/css-values/urls/support/empty-urls.css
@@ -0,0 +1,7 @@
+#external-unquoted {
+    background-image: url();
+}
+
+#external-quoted {
+    background-image: url("");
+}
diff --git a/css/css-values/vh-support-transform-origin.html b/css/css-values/vh-support-transform-origin.html
index 4b06a09..38d17d2 100644
--- a/css/css-values/vh-support-transform-origin.html
+++ b/css/css-values/vh-support-transform-origin.html
@@ -36,7 +36,7 @@
 </head>
 <body>
 
-	<iframe src="iframe/vh-support-transform-origin-iframe.html"></iframe>
+	<iframe src="support/vh-support-transform-origin-iframe.html"></iframe>
 
 </body>
 </html>
diff --git a/css/css-values/vh-support-transform-translate.html b/css/css-values/vh-support-transform-translate.html
index e273026..900b653 100644
--- a/css/css-values/vh-support-transform-translate.html
+++ b/css/css-values/vh-support-transform-translate.html
@@ -36,7 +36,7 @@
 </head>
 <body>
 
-	<iframe src="iframe/vh-support-transform-translate-iframe.html"></iframe>
+	<iframe src="support/vh-support-transform-translate-iframe.html"></iframe>
 
 </body>
 </html>
diff --git a/css/css-variables/OWNERS b/css/css-variables/OWNERS
index 0d2ecd4..0fbb0e9 100644
--- a/css/css-variables/OWNERS
+++ b/css/css-variables/OWNERS
@@ -1,4 +1,3 @@
 @dbaron
 @svgeesus
-@SimonSapin
 @tabatkins
diff --git a/css/css-variables/test_variable_legal_values.html b/css/css-variables/test_variable_legal_values.html
index 0b39c24..2e07438 100644
--- a/css/css-variables/test_variable_legal_values.html
+++ b/css/css-variables/test_variable_legal_values.html
@@ -9,33 +9,6 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
 <style id="style"></style>
-<script id="metadata_cache">/*
-{
-  "percentage": { "assert": "Value allowed within variable: percentage" },
-  "number": { "assert": "Value allowed within variable: number" },
-  "length": { "assert": "Value allowed within variable: length" },
-  "time": { "assert": "Value allowed within variable: time" },
-  "function": { "assert": "Value allowed within variable: function" },
-  "nested_function": { "assert": "Value allowed within variable: nested function" },
-  "parentheses": { "assert": "Value allowed within variable: parentheses" },
-  "braces": { "assert": "Value allowed within variable: braces" },
-  "brackets": { "assert": "Value allowed within variable: brackets" },
-  "at_keyword_unknown": { "assert": "Value allowed within variable: at-keyword (unknown)" },
-  "at_keyword_known": { "assert": "Value allowed within variable: at-keyword (known)" },
-  "at_keyword_unknown_and_block": { "assert": "Value allowed within variable: at-keyword (unknown) and block" },
-  "at_keyword_known_and_block": { "assert": "Value allowed within variable: at-keyword (known) and block" },
-  "unbalanced_close_bracket_at_toplevel": { "assert": "Value not allowed within variable: unbalanced close bracket at toplevel" },
-  "unbalanced_close_paren_at_toplevel": { "assert": "Value not allowed within variable: unbalanced close paren at toplevel" },
-  "unbalanced_close_bracket_in_something_balanced": { "assert": "Value not allowed within variable: unbalanced close bracket in something balanced" },
-  "unbalanced_close_paren_in_something_balanced": { "assert": "Value not allowed within variable: unbalanced close paren in something balanced" },
-  "unbalanced_close_brace_in_something_balanced": { "assert": "Value not allowed within variable: unbalanced close brace in something balanced" },
-  "CDO_at_top_level": { "assert": "Value allowed within variable: CDO at top level" },
-  "CDC_at_top_level": { "assert": "Value allowed within variable: CDC at top level" },
-  "semicolon_not_at_top_level_value_unused": { "assert": "Value allowed within variable: semicolon not at top level (value -> unused)" },
-  "CDO_not_at_top_level_value_unused": { "assert": "Value allowed within variable: CDO not at top level (value -> unused)" },
-  "CDC_not_at_top_level_value_unused": { "assert": "Value allowed within variable: CDC not at top level (value -> unused)" }
-}
-*/</script>
 </head>
 <body onload="run()">
 <div id=log></div>
diff --git a/css/css-variables/variable-cssText.html b/css/css-variables/variable-cssText.html
index fd70ce9..b61d297 100644
--- a/css/css-variables/variable-cssText.html
+++ b/css/css-variables/variable-cssText.html
@@ -28,15 +28,15 @@
     "use strict";
 
     var testcases = [
-        { element: "target1",   expectedCssText: "--var:  var1;" },
+        { element: "target1",   expectedCssText: "--var: var1;" },
         { element: "target2",   expectedCssText: "margin: var(--prop);" },
         { element: "target3",   expectedCssText: "background: var(--prop);" },
         { element: "target4",   expectedCssText: "margin: var(--prop)  !important;" },
         { element: "target5",   expectedCssText: "background: var(--prop)  !important;" },
         { element: "target6",   expectedCssText: "background: green;" },
         { element: "target7",   expectedCssText: "background: var(--prop);" },
-        { element: "target8",   expectedCssText: "color:  var(--prop);" },
-        { element: "target9",   expectedCssText: "margin-top: 10px; margin-right: ; margin-bottom: ; margin-left: ;" },
+        { element: "target8",   expectedCssText: "color: var(--prop);" },
+        { element: "target9",   expectedCssText: "margin-right: ; margin-bottom: ; margin-left: ; margin-top: 10px;" },
         { element: "target10",  expectedCssText: "" }
     ];
 
@@ -45,7 +45,7 @@
             var div = document.getElementById(testcase.element);
             var actualCssText = div.style.cssText;
             assert_equals(actualCssText, testcase.expectedCssText);
-        }, testcase.element + " " + testcase.propertyName);
+        }, testcase.element);
     });
 </script>
 
diff --git a/css/css-variables/variable-substitution-shadow-properties.html b/css/css-variables/variable-substitution-shadow-properties.html
index 58a858e..bd7b5ef 100644
--- a/css/css-variables/variable-substitution-shadow-properties.html
+++ b/css/css-variables/variable-substitution-shadow-properties.html
@@ -29,11 +29,11 @@
         let templates = [
             {
                 testName:"box-shadow",
-                expectedValue:"1px 1px 1px 1px rgb(0,128,0)",
+                expectedValue:"rgb(0, 128, 0) 1px 1px 1px 1px",
             },
             {
                 testName:"text-shadow",
-                expectedValue:"1px 1px 1px rgb(0,128,0)",
+                expectedValue:"rgb(0, 128, 0) 1px 1px 1px",
             },
         ];
 
diff --git a/css/css-writing-modes/available-size-001.html b/css/css-writing-modes/available-size-001.html
new file mode 100644
index 0000000..956c831
--- /dev/null
+++ b/css/css-writing-modes/available-size-001.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / max-height / content height</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does not have a fixed height but does have a fixed max-height, use that, making sure you stick with the content height.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  max-height: 8ch; /* **max**-height does not give the element a definite block size */
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+  padding: 1ch 0;
+}
+
+div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0; top: 1ch;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div take its height from
+  the max-height of its parent, it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-002.html b/css/css-writing-modes/available-size-002.html
new file mode 100644
index 0000000..adc5c52
--- /dev/null
+++ b/css/css-writing-modes/available-size-002.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / max-height / no scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's ancestor doesn't have a definite block size but does have a fixed max-height, but isn't a scroller, do not use that size.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  max-height: 8ch; /* **max**-height does not give the element a definite block size */
+  color: transparent;
+  position: relative; /* to act as a container of #green */
+}
+
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: white;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#green {
+  position: absolute;
+  background: green;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <div>
+    <aside id="green">0</aside>
+    <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div takes its height from
+    the max-height of its parent (which it shouldn't, since it's not a scroller),
+    it should wrap just right for the white 0 to
+    overlap with the green one. -->
+  </div>
+</div>
diff --git a/css/css-writing-modes/available-size-003.html b/css/css-writing-modes/available-size-003.html
new file mode 100644
index 0000000..c938c90
--- /dev/null
+++ b/css/css-writing-modes/available-size-003.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height / content height</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does have a fixed height, use that, making sure you stick with the content height.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 8ch;
+  width: 300px; /* Shrinkwrapping this div is not what we're interested in testing here, so give it a width. See nested-orthogonal-001.html for that. */
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+  writing-mode: vertical-lr;
+  padding: 1ch 0;
+}
+
+div > div { padding-bottom: 2ch; } /* so that the content height of the parent and of the fixed size scrolling ancestor are different */
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0; top: 1ch;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div><div>0 0 0 0 <span>0</span> 0 0 0</div></div> <!-- If this div take its height from
+  the height of its scrollable ancestor, it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-004.html b/css/css-writing-modes/available-size-004.html
new file mode 100644
index 0000000..e4c1187
--- /dev/null
+++ b/css/css-writing-modes/available-size-004.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height / no scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size but an ancestor that is **not** a scroller does have a fixed height, do not use that.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 8ch;
+  width: 300px; /* Shrinkwrapping this div is not what we're interested in testing here, so give it a width. See nested-orthogonal-001.html for that. */
+  color: transparent;
+  position: relative; /* to act as a container of #green */
+}
+
+div > div { padding-bottom: 2ch; } /* so that the content height of the parent and of the fixed size ancestor are different */
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: white;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#green { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: green;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="green">0</aside>
+  <div><div>0 0 0 0 <span>0</span> 0 0 0</div></div> <!-- If this div take its height from
+  the height of its fixed height non scrollable ancestor (which is should not),
+  it should wrap just right for the white 0 to overlap with the green one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-005.html b/css/css-writing-modes/available-size-005.html
new file mode 100644
index 0000000..ee32489
--- /dev/null
+++ b/css/css-writing-modes/available-size-005.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height / not remaining size</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does a have fixed height, use that whole height, even if some other content already consumes some of it.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 8ch;
+  width: 300px; /* Shrinkwrapping this div is not what we're interested in testing here, so give it a width. See nested-orthogonal-001.html for that. */
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+}
+
+div > div { padding-bottom: 2ch; } /* so that the content height of the parent and of the fixed size scrolling ancestor are different */
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  top: 1ch;
+}
+#spacer { /* shrinks the remaining space of the parent div. */
+  height: 1ch;
+  width: 100%;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div><aside id="spacer"></aside><div>0 0 0 0 <span>0</span> 0 0 0</div></div>
+  <!-- If the inner div take its height from the height of its scrollable
+  ancestor, it should wrap just right for the green 0 to overlap with the red
+  one. If instead it takes it size from the remaining height after having
+  removed #spacer, or does some other calculation that takes #spacer into
+  account, it won't line up with #red.-->
+</div>
diff --git a/css/css-writing-modes/available-size-006.html b/css/css-writing-modes/available-size-006.html
new file mode 100644
index 0000000..e884f76
--- /dev/null
+++ b/css/css-writing-modes/available-size-006.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's no scroller among ancestors, use the ICB instead.">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
diff --git a/css/css-writing-modes/available-size-007.html b/css/css-writing-modes/available-size-007.html
new file mode 100644
index 0000000..c8cfea2
--- /dev/null
+++ b/css/css-writing-modes/available-size-007.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's no scroller among ancestors, use the ICB instead (not the content height of the body element, which is the ICB reduced by body's margins).">
+<meta name="flags" content="">
+<style>
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20 taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
diff --git a/css/css-writing-modes/available-size-008.html b/css/css-writing-modes/available-size-008.html
new file mode 100644
index 0000000..d38c9cb
--- /dev/null
+++ b/css/css-writing-modes/available-size-008.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall max-height scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's a scroller with max-height, but that scroller has a taller max-height than the ICB, use the ICB instead.">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section {
+  overflow: hidden;
+  max-height: 120vh;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<div>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</div>
+</section>
diff --git a/css/css-writing-modes/available-size-009.html b/css/css-writing-modes/available-size-009.html
new file mode 100644
index 0000000..9d7a2c8
--- /dev/null
+++ b/css/css-writing-modes/available-size-009.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall height scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's a scroller with height, but that scroller is taller than the ICB, use the ICB instead.">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section {
+  overflow: hidden;
+  writing-mode: vertical-rl;
+  height: 120vh;
+}
+section > section {
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</section>
diff --git a/css/css-writing-modes/available-size-010.html b/css/css-writing-modes/available-size-010.html
new file mode 100644
index 0000000..f6452cc
--- /dev/null
+++ b/css/css-writing-modes/available-size-010.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall max-height scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and the nearest scroller goes not have a fixed height or max-height, use the ICB, even if there is another scroller ancestor up the chain that does have fixed dimensions.">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section { overflow: hidden; }
+body > section { height: 20ch; }
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<section>
+<section>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</section>
+</section>
+</section>
diff --git a/css/css-writing-modes/available-size-011.html b/css/css-writing-modes/available-size-011.html
new file mode 100644
index 0000000..38aa583
--- /dev/null
+++ b/css/css-writing-modes/available-size-011.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>orthogonal flow parent with max-height</title>
+<meta charset=utf-8>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<meta name="assert" content="If an orthogonal flow's parent doesn't have a definite block size but does have a max block size, use that as the available size">
+<link rel="match" href="reference/available-size-011-ref.html">
+<meta name="flags" content="">
+<style>
+main {
+  max-height: 1em;
+  line-height: 1em;
+}
+div {
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>This test passes if the word “PASS” (without the quotation marks) appears below, written horizontally from left to right.
+<main><div>S S A P</div></main>
diff --git a/css/css-writing-modes/available-size-012.html b/css/css-writing-modes/available-size-012.html
new file mode 100644
index 0000000..937129e
--- /dev/null
+++ b/css/css-writing-modes/available-size-012.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / max-height + min-height / content height</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does not have a fixed height but does have a fixed max-height, use that, increased by min-height if it exists and is larger. (same as -001, with min-height)">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  max-height: 4ch; /* **max**-height does not give the element a definite block size */
+  min-height: 8ch;
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+  padding: 1ch 0;
+}
+
+div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0; top: 1ch;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div take its height from
+  the min-height of its parent, it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-013.html b/css/css-writing-modes/available-size-013.html
new file mode 100644
index 0000000..1ffd656
--- /dev/null
+++ b/css/css-writing-modes/available-size-013.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height + min-height/ content height</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does have a fixed height, use that, increased by the min-height if it is set and is larger (same as -003, with min-height).">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 4ch;
+  min-height: 8ch;
+  width: 300px; /* Shrinkwrapping this div is not what we're interested in testing here, so give it a width. See nested-orthogonal-001.html for that. */
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+  writing-mode: vertical-lr;
+  padding: 1ch 0;
+}
+
+div > div { padding-bottom: 2ch; } /* so that the content height of the parent and of the fixed size scrolling ancestor are different */
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0; top: 1ch;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div><div>0 0 0 0 <span>0</span> 0 0 0</div></div> <!-- If this div take its height from
+  the height of its scrollable ancestor, it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-014.html b/css/css-writing-modes/available-size-014.html
new file mode 100644
index 0000000..e0e41fb
--- /dev/null
+++ b/css/css-writing-modes/available-size-014.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height + min-height / not remaining size</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does a have fixed height, use that whole height increased by min-height if that's larger, even if some other content already consumes some of it (same as -005, with min-height).">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 4ch;
+  min-height: 8ch;
+  width: 300px; /* Shrinkwrapping this div is not what we're interested in testing here, so give it a width. See nested-orthogonal-001.html for that. */
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+}
+
+div > div { padding-bottom: 2ch; } /* so that the content height of the parent and of the fixed size scrolling ancestor are different */
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  top: 1ch;
+}
+#spacer { /* shrinks the remaining space of the parent div. */
+  height: 1ch;
+  width: 100%;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div><aside id="spacer"></aside><div>0 0 0 0 <span>0</span> 0 0 0</div></div>
+  <!-- If the inner div take its height from the height of its scrollable
+  ancestor, it should wrap just right for the green 0 to overlap with the red
+  one. If instead it takes it size from the remaining height after having
+  removed #spacer, or does some other calculation that takes #spacer into
+  account, it won't line up with #red.-->
+</div>
diff --git a/css/css-writing-modes/available-size-015.html b/css/css-writing-modes/available-size-015.html
new file mode 100644
index 0000000..f7cb13b
--- /dev/null
+++ b/css/css-writing-modes/available-size-015.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall max-height + min-height scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's a scroller with max-height and min-height, and max-height is smaller than the ICB but min-height is larger than the ICB, use the ICB as the available space (same as -008, with min-height).">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section {
+  overflow: hidden;
+  max-height: 40vh;
+  min-height: 120vh;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</section>
diff --git a/css/css-writing-modes/available-size-016.html b/css/css-writing-modes/available-size-016.html
new file mode 100644
index 0000000..c3c388e
--- /dev/null
+++ b/css/css-writing-modes/available-size-016.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall height + min-height scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's a scroller with height and min-height, and height is smaller than the ICB but min-height is larger than the ICB, use the ICB as the available space (same as -009, with min-height).">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section {
+  overflow: hidden;
+  writing-mode: vertical-rl;
+  height: 40vh;
+  min-height: 120vh;
+}
+section > section {
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</section>
diff --git a/css/css-writing-modes/available-size-017.html b/css/css-writing-modes/available-size-017.html
new file mode 100644
index 0000000..79f1b85
--- /dev/null
+++ b/css/css-writing-modes/available-size-017.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height + min-height parent</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent has a height and a min-height larger than the height, use min-height as the available size.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 4ch;
+  min-height: 8ch;
+  color: transparent;
+  position: relative; /* to act as a container of #green */
+}
+
+div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red {
+  position: absolute;
+  background: red;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div takes its height from
+  the min-height of its parent (which it should)
+  it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-018.html b/css/css-writing-modes/available-size-018.html
new file mode 100644
index 0000000..4e86db7
--- /dev/null
+++ b/css/css-writing-modes/available-size-018.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / max-height + min-height parent</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent has a max-height and a min-height larger than the height, use min-height as the available size.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  max-height: 4ch;
+  min-height: 8ch;
+  color: transparent;
+  position: relative; /* to act as a container of #green */
+}
+
+div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red {
+  position: absolute;
+  background: red;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div takes its height from
+  the min-height of its parent (which it should)
+  it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-019.html b/css/css-writing-modes/available-size-019.html
new file mode 100644
index 0000000..a25a2f6
--- /dev/null
+++ b/css/css-writing-modes/available-size-019.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall max-height parent</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, but has a max-height, but that max-height is than the ICB, use the ICB instead.">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section {
+  max-height: 120vh;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</section>
diff --git a/css/css-writing-modes/bidi-embed-001.html b/css/css-writing-modes/bidi-embed-001.html
index e4dda03..d8215ad 100644
--- a/css/css-writing-modes/bidi-embed-001.html
+++ b/css/css-writing-modes/bidi-embed-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-002.html b/css/css-writing-modes/bidi-embed-002.html
index 3d50000..73bc465 100644
--- a/css/css-writing-modes/bidi-embed-002.html
+++ b/css/css-writing-modes/bidi-embed-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-003.html b/css/css-writing-modes/bidi-embed-003.html
index d923b3d..596892d 100644
--- a/css/css-writing-modes/bidi-embed-003.html
+++ b/css/css-writing-modes/bidi-embed-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-004.html b/css/css-writing-modes/bidi-embed-004.html
index e6d6b88..ddd5229 100644
--- a/css/css-writing-modes/bidi-embed-004.html
+++ b/css/css-writing-modes/bidi-embed-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-005.html b/css/css-writing-modes/bidi-embed-005.html
index ac71d49..a9afc3b 100644
--- a/css/css-writing-modes/bidi-embed-005.html
+++ b/css/css-writing-modes/bidi-embed-005.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-006.html b/css/css-writing-modes/bidi-embed-006.html
index d45c331..2034885 100644
--- a/css/css-writing-modes/bidi-embed-006.html
+++ b/css/css-writing-modes/bidi-embed-006.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-007.html b/css/css-writing-modes/bidi-embed-007.html
index 32b357a..4885f68 100644
--- a/css/css-writing-modes/bidi-embed-007.html
+++ b/css/css-writing-modes/bidi-embed-007.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-008.html b/css/css-writing-modes/bidi-embed-008.html
index 20e176b..da43f26 100644
--- a/css/css-writing-modes/bidi-embed-008.html
+++ b/css/css-writing-modes/bidi-embed-008.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-009.html b/css/css-writing-modes/bidi-embed-009.html
index 3183504..c65fb45 100644
--- a/css/css-writing-modes/bidi-embed-009.html
+++ b/css/css-writing-modes/bidi-embed-009.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-010.html b/css/css-writing-modes/bidi-embed-010.html
index fb1f052..67d3fb9 100644
--- a/css/css-writing-modes/bidi-embed-010.html
+++ b/css/css-writing-modes/bidi-embed-010.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-embed-011.html b/css/css-writing-modes/bidi-embed-011.html
index df49cde..a342350 100644
--- a/css/css-writing-modes/bidi-embed-011.html
+++ b/css/css-writing-modes/bidi-embed-011.html
@@ -17,7 +17,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-001.html b/css/css-writing-modes/bidi-isolate-001.html
index b9cc6e2..335791b 100644
--- a/css/css-writing-modes/bidi-isolate-001.html
+++ b/css/css-writing-modes/bidi-isolate-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-002.html b/css/css-writing-modes/bidi-isolate-002.html
index b34742f..f321cec 100644
--- a/css/css-writing-modes/bidi-isolate-002.html
+++ b/css/css-writing-modes/bidi-isolate-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-003.html b/css/css-writing-modes/bidi-isolate-003.html
index dc4162a..c678cd6 100644
--- a/css/css-writing-modes/bidi-isolate-003.html
+++ b/css/css-writing-modes/bidi-isolate-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-004.html b/css/css-writing-modes/bidi-isolate-004.html
index 0c956d2..5a6a941 100644
--- a/css/css-writing-modes/bidi-isolate-004.html
+++ b/css/css-writing-modes/bidi-isolate-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-005.html b/css/css-writing-modes/bidi-isolate-005.html
index 5912cb4..5ea947d 100644
--- a/css/css-writing-modes/bidi-isolate-005.html
+++ b/css/css-writing-modes/bidi-isolate-005.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-006.html b/css/css-writing-modes/bidi-isolate-006.html
index cf34cee..271845c 100644
--- a/css/css-writing-modes/bidi-isolate-006.html
+++ b/css/css-writing-modes/bidi-isolate-006.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-007.html b/css/css-writing-modes/bidi-isolate-007.html
index cc0a516..d9b2e9f 100644
--- a/css/css-writing-modes/bidi-isolate-007.html
+++ b/css/css-writing-modes/bidi-isolate-007.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-008.html b/css/css-writing-modes/bidi-isolate-008.html
index 32fcc51..6bcf092 100644
--- a/css/css-writing-modes/bidi-isolate-008.html
+++ b/css/css-writing-modes/bidi-isolate-008.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-009.html b/css/css-writing-modes/bidi-isolate-009.html
index 710f0e9..4b762c3 100644
--- a/css/css-writing-modes/bidi-isolate-009.html
+++ b/css/css-writing-modes/bidi-isolate-009.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-010.html b/css/css-writing-modes/bidi-isolate-010.html
index c4e3faf..eb62623 100644
--- a/css/css-writing-modes/bidi-isolate-010.html
+++ b/css/css-writing-modes/bidi-isolate-010.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-011.html b/css/css-writing-modes/bidi-isolate-011.html
index 5baf7ff..c636cc2 100644
--- a/css/css-writing-modes/bidi-isolate-011.html
+++ b/css/css-writing-modes/bidi-isolate-011.html
@@ -17,7 +17,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-001.html b/css/css-writing-modes/bidi-isolate-override-001.html
index d683abf..f539fba 100644
--- a/css/css-writing-modes/bidi-isolate-override-001.html
+++ b/css/css-writing-modes/bidi-isolate-override-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-002.html b/css/css-writing-modes/bidi-isolate-override-002.html
index ceb176a..80a19c4 100644
--- a/css/css-writing-modes/bidi-isolate-override-002.html
+++ b/css/css-writing-modes/bidi-isolate-override-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-003.html b/css/css-writing-modes/bidi-isolate-override-003.html
index 3c41503..2252895 100644
--- a/css/css-writing-modes/bidi-isolate-override-003.html
+++ b/css/css-writing-modes/bidi-isolate-override-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-004.html b/css/css-writing-modes/bidi-isolate-override-004.html
index 2750e53..c2597f7 100644
--- a/css/css-writing-modes/bidi-isolate-override-004.html
+++ b/css/css-writing-modes/bidi-isolate-override-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-005.html b/css/css-writing-modes/bidi-isolate-override-005.html
index 749b943..b92e16a 100644
--- a/css/css-writing-modes/bidi-isolate-override-005.html
+++ b/css/css-writing-modes/bidi-isolate-override-005.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-006.html b/css/css-writing-modes/bidi-isolate-override-006.html
index 963a735..dbf1e29 100644
--- a/css/css-writing-modes/bidi-isolate-override-006.html
+++ b/css/css-writing-modes/bidi-isolate-override-006.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-007.html b/css/css-writing-modes/bidi-isolate-override-007.html
index 9b0b137..3cb763f 100644
--- a/css/css-writing-modes/bidi-isolate-override-007.html
+++ b/css/css-writing-modes/bidi-isolate-override-007.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-008.html b/css/css-writing-modes/bidi-isolate-override-008.html
index abc2965..3f93984 100644
--- a/css/css-writing-modes/bidi-isolate-override-008.html
+++ b/css/css-writing-modes/bidi-isolate-override-008.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-009.html b/css/css-writing-modes/bidi-isolate-override-009.html
index 5de0b71..d149762 100644
--- a/css/css-writing-modes/bidi-isolate-override-009.html
+++ b/css/css-writing-modes/bidi-isolate-override-009.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-010.html b/css/css-writing-modes/bidi-isolate-override-010.html
index a6a6db2..9e6db40 100644
--- a/css/css-writing-modes/bidi-isolate-override-010.html
+++ b/css/css-writing-modes/bidi-isolate-override-010.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-011.html b/css/css-writing-modes/bidi-isolate-override-011.html
index 9cafa11..331ce20 100644
--- a/css/css-writing-modes/bidi-isolate-override-011.html
+++ b/css/css-writing-modes/bidi-isolate-override-011.html
@@ -17,7 +17,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-isolate-override-012.html b/css/css-writing-modes/bidi-isolate-override-012.html
index 82fdb8a..2e93f34 100644
--- a/css/css-writing-modes/bidi-isolate-override-012.html
+++ b/css/css-writing-modes/bidi-isolate-override-012.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-001.html b/css/css-writing-modes/bidi-normal-001.html
index 9fe992d..59479c5 100644
--- a/css/css-writing-modes/bidi-normal-001.html
+++ b/css/css-writing-modes/bidi-normal-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-002.html b/css/css-writing-modes/bidi-normal-002.html
index 1ff7ef9..054142f 100644
--- a/css/css-writing-modes/bidi-normal-002.html
+++ b/css/css-writing-modes/bidi-normal-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-003.html b/css/css-writing-modes/bidi-normal-003.html
index 1d30369..de3d48f 100644
--- a/css/css-writing-modes/bidi-normal-003.html
+++ b/css/css-writing-modes/bidi-normal-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-004.html b/css/css-writing-modes/bidi-normal-004.html
index ad94f57..365e21b 100644
--- a/css/css-writing-modes/bidi-normal-004.html
+++ b/css/css-writing-modes/bidi-normal-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-005.html b/css/css-writing-modes/bidi-normal-005.html
index 7008f84..10d2622 100644
--- a/css/css-writing-modes/bidi-normal-005.html
+++ b/css/css-writing-modes/bidi-normal-005.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-006.html b/css/css-writing-modes/bidi-normal-006.html
index 8fa9e1b..20d44b7 100644
--- a/css/css-writing-modes/bidi-normal-006.html
+++ b/css/css-writing-modes/bidi-normal-006.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-007.html b/css/css-writing-modes/bidi-normal-007.html
index 69390ce..0bb38f9 100644
--- a/css/css-writing-modes/bidi-normal-007.html
+++ b/css/css-writing-modes/bidi-normal-007.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-008.html b/css/css-writing-modes/bidi-normal-008.html
index 3b1587f..fe58f3f 100644
--- a/css/css-writing-modes/bidi-normal-008.html
+++ b/css/css-writing-modes/bidi-normal-008.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-009.html b/css/css-writing-modes/bidi-normal-009.html
index 3ee2482..72d7350 100644
--- a/css/css-writing-modes/bidi-normal-009.html
+++ b/css/css-writing-modes/bidi-normal-009.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-010.html b/css/css-writing-modes/bidi-normal-010.html
index 37684ab1..4300140 100644
--- a/css/css-writing-modes/bidi-normal-010.html
+++ b/css/css-writing-modes/bidi-normal-010.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-normal-011.html b/css/css-writing-modes/bidi-normal-011.html
index c56fa17..54bca7e 100644
--- a/css/css-writing-modes/bidi-normal-011.html
+++ b/css/css-writing-modes/bidi-normal-011.html
@@ -17,7 +17,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-001.html b/css/css-writing-modes/bidi-override-001.html
index fae4b1c..985ecc6 100644
--- a/css/css-writing-modes/bidi-override-001.html
+++ b/css/css-writing-modes/bidi-override-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-002.html b/css/css-writing-modes/bidi-override-002.html
index dfea017..a22d481 100644
--- a/css/css-writing-modes/bidi-override-002.html
+++ b/css/css-writing-modes/bidi-override-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-003.html b/css/css-writing-modes/bidi-override-003.html
index a5e5eb3..fe8f320 100644
--- a/css/css-writing-modes/bidi-override-003.html
+++ b/css/css-writing-modes/bidi-override-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-004.html b/css/css-writing-modes/bidi-override-004.html
index 7413be1..cd607c1 100644
--- a/css/css-writing-modes/bidi-override-004.html
+++ b/css/css-writing-modes/bidi-override-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-005.html b/css/css-writing-modes/bidi-override-005.html
index 69a272b..863ad05 100644
--- a/css/css-writing-modes/bidi-override-005.html
+++ b/css/css-writing-modes/bidi-override-005.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-006.html b/css/css-writing-modes/bidi-override-006.html
index ac0725d..77d4aad 100644
--- a/css/css-writing-modes/bidi-override-006.html
+++ b/css/css-writing-modes/bidi-override-006.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-007.html b/css/css-writing-modes/bidi-override-007.html
index add0289..5ad916d 100644
--- a/css/css-writing-modes/bidi-override-007.html
+++ b/css/css-writing-modes/bidi-override-007.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-008.html b/css/css-writing-modes/bidi-override-008.html
index 55b7e2f..891dfc2 100644
--- a/css/css-writing-modes/bidi-override-008.html
+++ b/css/css-writing-modes/bidi-override-008.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-009.html b/css/css-writing-modes/bidi-override-009.html
index 08cce9c..936cdf4 100644
--- a/css/css-writing-modes/bidi-override-009.html
+++ b/css/css-writing-modes/bidi-override-009.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-010.html b/css/css-writing-modes/bidi-override-010.html
index 1d20bc2..1b6c7a5 100644
--- a/css/css-writing-modes/bidi-override-010.html
+++ b/css/css-writing-modes/bidi-override-010.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-011.html b/css/css-writing-modes/bidi-override-011.html
index c7c8fe9..3ae442f 100644
--- a/css/css-writing-modes/bidi-override-011.html
+++ b/css/css-writing-modes/bidi-override-011.html
@@ -17,7 +17,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-override-012.html b/css/css-writing-modes/bidi-override-012.html
index 9545c0e..8d35376 100644
--- a/css/css-writing-modes/bidi-override-012.html
+++ b/css/css-writing-modes/bidi-override-012.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-001.html b/css/css-writing-modes/bidi-plaintext-001.html
index e4ebe68..afb57d2 100644
--- a/css/css-writing-modes/bidi-plaintext-001.html
+++ b/css/css-writing-modes/bidi-plaintext-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-002.html b/css/css-writing-modes/bidi-plaintext-002.html
index e0256e0..dc44a46 100644
--- a/css/css-writing-modes/bidi-plaintext-002.html
+++ b/css/css-writing-modes/bidi-plaintext-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-003.html b/css/css-writing-modes/bidi-plaintext-003.html
index 05e9e94..2afb754 100644
--- a/css/css-writing-modes/bidi-plaintext-003.html
+++ b/css/css-writing-modes/bidi-plaintext-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-004.html b/css/css-writing-modes/bidi-plaintext-004.html
index b51f6c5..a5b4603 100644
--- a/css/css-writing-modes/bidi-plaintext-004.html
+++ b/css/css-writing-modes/bidi-plaintext-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-005.html b/css/css-writing-modes/bidi-plaintext-005.html
index 5f6ac25..75e0337 100644
--- a/css/css-writing-modes/bidi-plaintext-005.html
+++ b/css/css-writing-modes/bidi-plaintext-005.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-006.html b/css/css-writing-modes/bidi-plaintext-006.html
index b7142b3..d48c948 100644
--- a/css/css-writing-modes/bidi-plaintext-006.html
+++ b/css/css-writing-modes/bidi-plaintext-006.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-007.html b/css/css-writing-modes/bidi-plaintext-007.html
index c455fad..0a15eee 100644
--- a/css/css-writing-modes/bidi-plaintext-007.html
+++ b/css/css-writing-modes/bidi-plaintext-007.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-008.html b/css/css-writing-modes/bidi-plaintext-008.html
index be4b519..3f878ea 100644
--- a/css/css-writing-modes/bidi-plaintext-008.html
+++ b/css/css-writing-modes/bidi-plaintext-008.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-009.html b/css/css-writing-modes/bidi-plaintext-009.html
index af90f9e..68983b2 100644
--- a/css/css-writing-modes/bidi-plaintext-009.html
+++ b/css/css-writing-modes/bidi-plaintext-009.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-010.html b/css/css-writing-modes/bidi-plaintext-010.html
index 36a1a49..c1e955e 100644
--- a/css/css-writing-modes/bidi-plaintext-010.html
+++ b/css/css-writing-modes/bidi-plaintext-010.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-plaintext-011.html b/css/css-writing-modes/bidi-plaintext-011.html
index aef7e93..df75c33 100644
--- a/css/css-writing-modes/bidi-plaintext-011.html
+++ b/css/css-writing-modes/bidi-plaintext-011.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-table-001.html b/css/css-writing-modes/bidi-table-001.html
index fc6902a0..860a4b3 100644
--- a/css/css-writing-modes/bidi-table-001.html
+++ b/css/css-writing-modes/bidi-table-001.html
@@ -6,6 +6,7 @@
 
 <link rel="author" title="Richard Ishida" href='mailto:ishida@w3.org'/>
 <link rel="help" href='http://www.w3.org/TR/css-writing-modes-3/#text-direction'/>
+<link rel="match" href="reference/bidi-table-001.html"/>
 <meta name="assert" content='If direction is applied to the ancestor of a table element, columns will be displayed in that direction.'/>
 <style type="text/css">
 .test { direction: rtl; }
@@ -15,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
@@ -46,4 +47,4 @@
 
 
 
-</body></html>
\ No newline at end of file
+</body></html>
diff --git a/css/css-writing-modes/bidi-unset-001.html b/css/css-writing-modes/bidi-unset-001.html
index ede070c..1f25284 100644
--- a/css/css-writing-modes/bidi-unset-001.html
+++ b/css/css-writing-modes/bidi-unset-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-002.html b/css/css-writing-modes/bidi-unset-002.html
index 91e4e39..7360cde 100644
--- a/css/css-writing-modes/bidi-unset-002.html
+++ b/css/css-writing-modes/bidi-unset-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-003.html b/css/css-writing-modes/bidi-unset-003.html
index c70a544..f7758c7 100644
--- a/css/css-writing-modes/bidi-unset-003.html
+++ b/css/css-writing-modes/bidi-unset-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-004.html b/css/css-writing-modes/bidi-unset-004.html
index 2015288..f8b8788 100644
--- a/css/css-writing-modes/bidi-unset-004.html
+++ b/css/css-writing-modes/bidi-unset-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-005.html b/css/css-writing-modes/bidi-unset-005.html
index 5eb8c4b..3b3a7a2 100644
--- a/css/css-writing-modes/bidi-unset-005.html
+++ b/css/css-writing-modes/bidi-unset-005.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-006.html b/css/css-writing-modes/bidi-unset-006.html
index 386e834..440918c 100644
--- a/css/css-writing-modes/bidi-unset-006.html
+++ b/css/css-writing-modes/bidi-unset-006.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-007.html b/css/css-writing-modes/bidi-unset-007.html
index b04f068..db4d014 100644
--- a/css/css-writing-modes/bidi-unset-007.html
+++ b/css/css-writing-modes/bidi-unset-007.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-008.html b/css/css-writing-modes/bidi-unset-008.html
index 83df25b..290488a 100644
--- a/css/css-writing-modes/bidi-unset-008.html
+++ b/css/css-writing-modes/bidi-unset-008.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-009.html b/css/css-writing-modes/bidi-unset-009.html
index 0588d4c..5c03c76 100644
--- a/css/css-writing-modes/bidi-unset-009.html
+++ b/css/css-writing-modes/bidi-unset-009.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/bidi-unset-010.html b/css/css-writing-modes/bidi-unset-010.html
index b1e5589..f850eb6 100644
--- a/css/css-writing-modes/bidi-unset-010.html
+++ b/css/css-writing-modes/bidi-unset-010.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-embed-001.html b/css/css-writing-modes/block-embed-001.html
index 75ba1d6..260c19f 100644
--- a/css/css-writing-modes/block-embed-001.html
+++ b/css/css-writing-modes/block-embed-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-embed-002.html b/css/css-writing-modes/block-embed-002.html
index 7905f11..ad1c4ca 100644
--- a/css/css-writing-modes/block-embed-002.html
+++ b/css/css-writing-modes/block-embed-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-embed-003.html b/css/css-writing-modes/block-embed-003.html
index 2ecc7c3..aedb9d3 100644
--- a/css/css-writing-modes/block-embed-003.html
+++ b/css/css-writing-modes/block-embed-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-flow-direction-slr-043.xht b/css/css-writing-modes/block-flow-direction-slr-043.xht
index e860cd1..43fa203 100644
--- a/css/css-writing-modes/block-flow-direction-slr-043.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-043.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-lr - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-043-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-047.xht b/css/css-writing-modes/block-flow-direction-slr-047.xht
index ecb3b69..dcc59fa 100644
--- a/css/css-writing-modes/block-flow-direction-slr-047.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-047.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: right' and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-002-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-048.xht b/css/css-writing-modes/block-flow-direction-slr-048.xht
index 6802d8b..f7c9c22 100644
--- a/css/css-writing-modes/block-flow-direction-slr-048.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-048.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: right' and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-002-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-050.xht b/css/css-writing-modes/block-flow-direction-slr-050.xht
index f5bfc73..5d8575d 100644
--- a/css/css-writing-modes/block-flow-direction-slr-050.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-050.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: position absolute and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-054.xht b/css/css-writing-modes/block-flow-direction-slr-054.xht
index a779ab9..47f42ab 100644
--- a/css/css-writing-modes/block-flow-direction-slr-054.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-054.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-055.xht b/css/css-writing-modes/block-flow-direction-slr-055.xht
index 8e35691..b934f4b 100644
--- a/css/css-writing-modes/block-flow-direction-slr-055.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-055.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-056.xht b/css/css-writing-modes/block-flow-direction-slr-056.xht
index da8f483..286c44f 100644
--- a/css/css-writing-modes/block-flow-direction-slr-056.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-056.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-058.xht b/css/css-writing-modes/block-flow-direction-slr-058.xht
index 4b9f063..540991b 100644
--- a/css/css-writing-modes/block-flow-direction-slr-058.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-058.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-cell and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-060.xht b/css/css-writing-modes/block-flow-direction-slr-060.xht
index 3c242b3..a9a9c2a 100644
--- a/css/css-writing-modes/block-flow-direction-slr-060.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-060.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-caption and 'sideways-lr' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-062.xht b/css/css-writing-modes/block-flow-direction-slr-062.xht
index a6609bd..ac1aec3 100644
--- a/css/css-writing-modes/block-flow-direction-slr-062.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-062.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: list and sideways-lr - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-043-ref.xht" />
 
   <meta content="ahem image" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-063.xht b/css/css-writing-modes/block-flow-direction-slr-063.xht
index 5c50569..065f75e 100644
--- a/css/css-writing-modes/block-flow-direction-slr-063.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-063.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-lr - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-043-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-slr-066.xht b/css/css-writing-modes/block-flow-direction-slr-066.xht
index 108d4e2..24a4dfe 100644
--- a/css/css-writing-modes/block-flow-direction-slr-066.xht
+++ b/css/css-writing-modes/block-flow-direction-slr-066.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: writing mode (sideways-lr) of document - horizontal position of first block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="help" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#block-formatting" title="9.4.1 Block formatting contexts" />
   <link rel="match" href="block-flow-direction-066-ref.xht" />
 
diff --git a/css/css-writing-modes/block-flow-direction-srl-042.xht b/css/css-writing-modes/block-flow-direction-srl-042.xht
index 847b39d..6a99d55 100644
--- a/css/css-writing-modes/block-flow-direction-srl-042.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-042.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-rl - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-002-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-045.xht b/css/css-writing-modes/block-flow-direction-srl-045.xht
index 6a868bf..a8cefba 100644
--- a/css/css-writing-modes/block-flow-direction-srl-045.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-045.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: left' and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-046.xht b/css/css-writing-modes/block-flow-direction-srl-046.xht
index e4ab2ec..bb61fcf 100644
--- a/css/css-writing-modes/block-flow-direction-srl-046.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-046.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: left' and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-049.xht b/css/css-writing-modes/block-flow-direction-srl-049.xht
index a0cd94e..447527f 100644
--- a/css/css-writing-modes/block-flow-direction-srl-049.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-049.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: position absolute and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-051.xht b/css/css-writing-modes/block-flow-direction-srl-051.xht
index 14cefa2..2fc0b37 100644
--- a/css/css-writing-modes/block-flow-direction-srl-051.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-051.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-052.xht b/css/css-writing-modes/block-flow-direction-srl-052.xht
index dae358a..f32066e 100644
--- a/css/css-writing-modes/block-flow-direction-srl-052.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-052.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-053.xht b/css/css-writing-modes/block-flow-direction-srl-053.xht
index 8635757..86b36fc 100644
--- a/css/css-writing-modes/block-flow-direction-srl-053.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-053.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-057.xht b/css/css-writing-modes/block-flow-direction-srl-057.xht
index 87c8dc1..1afed95 100644
--- a/css/css-writing-modes/block-flow-direction-srl-057.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-057.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-cell and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-059.xht b/css/css-writing-modes/block-flow-direction-srl-059.xht
index 0d14068..6bd8d4a 100644
--- a/css/css-writing-modes/block-flow-direction-srl-059.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-059.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-caption and 'sideways-rl' - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-061.xht b/css/css-writing-modes/block-flow-direction-srl-061.xht
index b08891e..5b05219 100644
--- a/css/css-writing-modes/block-flow-direction-srl-061.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-061.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: list and sideways-rl - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-002-ref.xht" />
 
   <meta content="ahem image" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-064.xht b/css/css-writing-modes/block-flow-direction-srl-064.xht
index d023e63..40e5be9 100644
--- a/css/css-writing-modes/block-flow-direction-srl-064.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-064.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-rl - block flow direction of block-level boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-002-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/block-flow-direction-srl-065.xht b/css/css-writing-modes/block-flow-direction-srl-065.xht
index ac5fdf2..40c0f83 100644
--- a/css/css-writing-modes/block-flow-direction-srl-065.xht
+++ b/css/css-writing-modes/block-flow-direction-srl-065.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: writing mode (sideways-rl) of document - horizontal position of first block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="help" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#block-formatting" title="9.4.1 Block formatting contexts" />
   <link rel="match" href="block-flow-direction-025-ref.xht" />
 
diff --git a/css/css-writing-modes/block-override-001.html b/css/css-writing-modes/block-override-001.html
index 427b732..72fbce8 100644
--- a/css/css-writing-modes/block-override-001.html
+++ b/css/css-writing-modes/block-override-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-override-002.html b/css/css-writing-modes/block-override-002.html
index 5c97ad1..558c9af 100644
--- a/css/css-writing-modes/block-override-002.html
+++ b/css/css-writing-modes/block-override-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-override-003.html b/css/css-writing-modes/block-override-003.html
index 5ec9ac2..94744d4 100644
--- a/css/css-writing-modes/block-override-003.html
+++ b/css/css-writing-modes/block-override-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-override-004.html b/css/css-writing-modes/block-override-004.html
index 93ffb52..46205ca 100644
--- a/css/css-writing-modes/block-override-004.html
+++ b/css/css-writing-modes/block-override-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-override-isolate-001.html b/css/css-writing-modes/block-override-isolate-001.html
index dfafd93..0c126dd 100644
--- a/css/css-writing-modes/block-override-isolate-001.html
+++ b/css/css-writing-modes/block-override-isolate-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-override-isolate-002.html b/css/css-writing-modes/block-override-isolate-002.html
index 6373504..8422136 100644
--- a/css/css-writing-modes/block-override-isolate-002.html
+++ b/css/css-writing-modes/block-override-isolate-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-override-isolate-003.html b/css/css-writing-modes/block-override-isolate-003.html
index dea6ce3..2e6122a 100644
--- a/css/css-writing-modes/block-override-isolate-003.html
+++ b/css/css-writing-modes/block-override-isolate-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-override-isolate-004.html b/css/css-writing-modes/block-override-isolate-004.html
index 86c2a1f..3bb9e84 100644
--- a/css/css-writing-modes/block-override-isolate-004.html
+++ b/css/css-writing-modes/block-override-isolate-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-plaintext-001.html b/css/css-writing-modes/block-plaintext-001.html
index e489388..22b6340 100644
--- a/css/css-writing-modes/block-plaintext-001.html
+++ b/css/css-writing-modes/block-plaintext-001.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-plaintext-002.html b/css/css-writing-modes/block-plaintext-002.html
index 40e9977..41c0a1b 100644
--- a/css/css-writing-modes/block-plaintext-002.html
+++ b/css/css-writing-modes/block-plaintext-002.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-plaintext-003.html b/css/css-writing-modes/block-plaintext-003.html
index cb24704..3b31280 100644
--- a/css/css-writing-modes/block-plaintext-003.html
+++ b/css/css-writing-modes/block-plaintext-003.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-plaintext-004.html b/css/css-writing-modes/block-plaintext-004.html
index 0b7d26c..9586a1c 100644
--- a/css/css-writing-modes/block-plaintext-004.html
+++ b/css/css-writing-modes/block-plaintext-004.html
@@ -16,7 +16,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-plaintext-005.html b/css/css-writing-modes/block-plaintext-005.html
index b17a088..3bd4d17 100644
--- a/css/css-writing-modes/block-plaintext-005.html
+++ b/css/css-writing-modes/block-plaintext-005.html
@@ -15,7 +15,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/block-plaintext-006.html b/css/css-writing-modes/block-plaintext-006.html
index c26e420..ab966ad 100644
--- a/css/css-writing-modes/block-plaintext-006.html
+++ b/css/css-writing-modes/block-plaintext-006.html
@@ -6,6 +6,7 @@
 
 <link rel="author" title="Richard Ishida" href='mailto:ishida@w3.org'/>
 <link rel="help" href='http://www.w3.org/TR/css-writing-modes-3/#text-direction'/>
+<link rel="match" href="reference/block-plaintext-006.html"/>
 <meta name="assert" content='If unicode-bidi:plaintext is applied to a pre element, each line of characters after a linebreak is displayed according to the first strong character after the linebreak.'/>
 <style type="text/css">
 .test pre { unicode-bidi: plaintext; }
@@ -15,12 +16,12 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
 .test, .ref { font-family: ezra_silregular, serif; }
-pre { font-family: ezra_silregular, serif; height:5em; width: 100%; border: 0; font-size: 1em; }
+pre { font-family: ezra_silregular, serif; width: 100%; border: 0; margin: 0; font-size: 1em; }
 </style>
 </head>
 <body>
@@ -36,10 +37,11 @@
 
 
 <div class="test">
-<pre>
+<pre><!-- comment token so following LF character isn't ignored by the HTML parser -->
 &gt; a &gt; &#x5d1; &gt; c &gt;
 &gt; &#x5d0; &gt; b &gt; &#x5d2; &gt;
 &gt; a &gt; &#x5d1; &gt; c &gt;
+<!-- need a blank line for whitespace to appear-->
 </pre>
 </div>
 
@@ -55,4 +57,4 @@
 
 
 
-</body></html>
\ No newline at end of file
+</body></html>
diff --git a/css/css-writing-modes/ch-units-vrl-001.html b/css/css-writing-modes/ch-units-vrl-001.html
new file mode 100644
index 0000000..5591465
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-001.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>upright vertical writing mode and ch unit on table rows</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-001-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table rows takes the writing mode (with upright text-orientation) into account,
+                            even though theses properties do not apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+}
+tr {
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  line-height: 5ch; /* using the inherited line-height (which the affects the content of the td)
+                       instead of directly using the height property,
+                       because sizing on table cells is complicated and out of scope for this. */
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(1) {
+  background: blue;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(2) {
+  background: orange;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/ch-units-vrl-002.html b/css/css-writing-modes/ch-units-vrl-002.html
new file mode 100644
index 0000000..d1ff259
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-002.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>upright vertical writing mode and ch unit on table row groups</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-001-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table row groups takes the writing mode (with upright text-orientatino) into account,
+                            even though these properties do not apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+}
+tbody {
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  line-height: 5ch; /* using the inherited line-height (which the affects the content of the td)
+                       instead of directly using the height property,
+                       because sizing on table cells is complicated and out of scope for this. */
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(1) {
+  background: blue;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(2) {
+  background: orange;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/ch-units-vrl-003.html b/css/css-writing-modes/ch-units-vrl-003.html
new file mode 100644
index 0000000..5f7de3c
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-003.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>upright vertical writing mode and ch unit on table columns</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-001-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table columns takes the writing mode (with upright orientation) into account,
+                            even though these properties do no apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+}
+col {
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(1) {
+  background: blue;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(2) {
+  background: orange;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><col><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/ch-units-vrl-004.html b/css/css-writing-modes/ch-units-vrl-004.html
new file mode 100644
index 0000000..4264afd
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-004.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>upright vertical writing mode and ch unit on table column groups</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-001-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table column groups takes the writing mode (with upright text-orientation) into account,
+                            even though these properties do not apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+}
+colgroup {
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(1) {
+  background: blue;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(2) {
+  background: orange;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><colgroup><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/ch-units-vrl-005.html b/css/css-writing-modes/ch-units-vrl-005.html
new file mode 100644
index 0000000..6348b00
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-005.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>sideways vertical writing mode and ch unit on table rows</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-005-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table rows takes the writing mode (with a sideways text-orientation) into account,
+                            even though theese properties do not apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+}
+tr {
+  writing-mode: vertical-rl;
+  text-orientation: sideways;
+  line-height: 5ch; /* using the inherited line-height (which the affects the content of the td)
+                       instead of directly using the height property,
+                       because sizing on table cells is complicated and out of scope for this. */
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(2) {
+  background: orange;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(1) {
+  background: blue;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/ch-units-vrl-006.html b/css/css-writing-modes/ch-units-vrl-006.html
new file mode 100644
index 0000000..1395606
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-006.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>sideways vertical writing mode and ch unit on table row groups</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-005-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table row groups takes the writing mode (with a sideways text-orientation) into account,
+                            even though these properties do not apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+}
+tbody {
+  writing-mode: vertical-rl;
+  text-orientation: sideways;
+  line-height: 5ch; /* using the inherited line-height (which the affects the content of the td)
+                       instead of directly using the height property,
+                       because sizing on table cells is complicated and out of scope for this. */
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(2) {
+  background: orange;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(1) {
+  background: orange;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/ch-units-vrl-007.html b/css/css-writing-modes/ch-units-vrl-007.html
new file mode 100644
index 0000000..978e5ba
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-007.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>sideways vertical writing mode and ch unit on table columns</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-005-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table columns takes the writing mode (with a sideways orientation) into account,
+                            even though these properties do not apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+}
+col {
+  writing-mode: vertical-rl;
+  text-orientation: sideways;
+  width: 5ch;
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(2) {
+  background: orange;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(1) {
+  background: blue;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><col><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/ch-units-vrl-008.html b/css/css-writing-modes/ch-units-vrl-008.html
new file mode 100644
index 0000000..eece597
--- /dev/null
+++ b/css/css-writing-modes/ch-units-vrl-008.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>sideways vertical writing mode and ch unit on table column groups</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/ch-units-vrl-005-ref.html"
+<meta name="assert" content="The font-metric dependent ch unit on table column groups takes the writing mode (with a sideways orientation) into account,
+                            even though theses properties do not apply to that element.">
+<style>
+table {
+  font-size: 20px;
+  border-collapse: collapse;
+  border: none;
+}
+td {
+  padding: 0;
+  background: green;
+  height: 5ch;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+}
+colgroup {
+  writing-mode: vertical-rl;
+  text-orientation: sideways;
+  width: 5ch;
+}
+
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+/* Sizing the reference divs using the actual 0 (after which the ch unit is based) in the inline dimensions,
+   and using the ch unit in the block dimension,
+   to make sure that the ch unit itself works well in the general case.
+   If it doesn't, or if writing modes don't work, the divs won't be square.
+ */
+div:nth-of-type(2) {
+  background: orange;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  width: 5ch;
+}
+div:nth-of-type(1) {
+  background: blue;
+  height: 5ch;
+  display: inline-block; /* shrinkwrap */
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <table><colgroup><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+  <div>00000</div>
+  <div>00000</div>
+</body>
diff --git a/css/css-writing-modes/float-lft-orthog-htb-in-vlr-002.xht b/css/css-writing-modes/float-lft-orthog-htb-in-vlr-002.xht
index 690dc78..6cef03a 100644
--- a/css/css-writing-modes/float-lft-orthog-htb-in-vlr-002.xht
+++ b/css/css-writing-modes/float-lft-orthog-htb-in-vlr-002.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-lft-orthog-htb-in-vlr-002-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/float-lft-orthog-htb-in-vrl-002.xht b/css/css-writing-modes/float-lft-orthog-htb-in-vrl-002.xht
index d11a2f9..977c653 100644
--- a/css/css-writing-modes/float-lft-orthog-htb-in-vrl-002.xht
+++ b/css/css-writing-modes/float-lft-orthog-htb-in-vrl-002.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-lft-orthog-htb-in-vrl-002-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/float-lft-orthog-vlr-in-htb-002.xht b/css/css-writing-modes/float-lft-orthog-vlr-in-htb-002.xht
index d760b49..e6229f1 100644
--- a/css/css-writing-modes/float-lft-orthog-vlr-in-htb-002.xht
+++ b/css/css-writing-modes/float-lft-orthog-vlr-in-htb-002.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-lft-orthog-vlr-in-htb-002-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/float-lft-orthog-vrl-in-htb-002.xht b/css/css-writing-modes/float-lft-orthog-vrl-in-htb-002.xht
index ac4462c..63f4359 100644
--- a/css/css-writing-modes/float-lft-orthog-vrl-in-htb-002.xht
+++ b/css/css-writing-modes/float-lft-orthog-vrl-in-htb-002.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-lft-orthog-vrl-in-htb-002-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/float-rgt-orthog-htb-in-vlr-003.xht b/css/css-writing-modes/float-rgt-orthog-htb-in-vlr-003.xht
index e11aad4..29937cd 100644
--- a/css/css-writing-modes/float-rgt-orthog-htb-in-vlr-003.xht
+++ b/css/css-writing-modes/float-rgt-orthog-htb-in-vlr-003.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-rgt-orthog-htb-in-vlr-003-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/float-rgt-orthog-htb-in-vrl-003.xht b/css/css-writing-modes/float-rgt-orthog-htb-in-vrl-003.xht
index 89cde7b..518b1f4 100644
--- a/css/css-writing-modes/float-rgt-orthog-htb-in-vrl-003.xht
+++ b/css/css-writing-modes/float-rgt-orthog-htb-in-vrl-003.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-rgt-orthog-htb-in-vrl-003-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/float-rgt-orthog-vlr-in-htb-003.xht b/css/css-writing-modes/float-rgt-orthog-vlr-in-htb-003.xht
index 09e5979..26589d7 100644
--- a/css/css-writing-modes/float-rgt-orthog-vlr-in-htb-003.xht
+++ b/css/css-writing-modes/float-rgt-orthog-vlr-in-htb-003.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-rgt-orthog-vlr-in-htb-003-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/float-rgt-orthog-vrl-in-htb-003.xht b/css/css-writing-modes/float-rgt-orthog-vrl-in-htb-003.xht
index 203c16e..9ded7b3 100644
--- a/css/css-writing-modes/float-rgt-orthog-vrl-in-htb-003.xht
+++ b/css/css-writing-modes/float-rgt-orthog-vrl-in-htb-003.xht
@@ -15,7 +15,7 @@
   -->
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#auto-multicol" title="7.3.2 Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="float-rgt-orthog-vrl-in-htb-003-ref.xht" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/form-controls-slr-004.xht b/css/css-writing-modes/form-controls-slr-004.xht
index e540990..7774f4b 100644
--- a/css/css-writing-modes/form-controls-slr-004.xht
+++ b/css/css-writing-modes/form-controls-slr-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: order, orientation and layout of form controls in 'sideways-lr' writing-mode</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
 
   <meta content="image should" name="flags" />
   <meta content="This test checks the order, the flow, the orientation and layout of form controls used in Example 4 of section 3.1 when the 'writing-mode' of the whole form element is set to 'sideways-lr'." name="assert" />
diff --git a/css/css-writing-modes/form-controls-slr-005.xht b/css/css-writing-modes/form-controls-slr-005.xht
index 75370af..35b4ace 100644
--- a/css/css-writing-modes/form-controls-slr-005.xht
+++ b/css/css-writing-modes/form-controls-slr-005.xht
@@ -8,7 +8,7 @@
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
 
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
 
   <meta content="should" name="flags" />
   <meta content="This test checks the rendering of various form controls (select, optgroup, options, various types of input and buttons) written with latin text in 'sideways-lr' writing mode. The text of replaced content should match the replaced element's writing mode and line orientation; therefore, in this test, a) the text's inline base direction must be from bottom to top, b) the glyphs must be rotated 90° counter-clockwise and c) the block flow direction must be from left to right." name="assert" />
diff --git a/css/css-writing-modes/form-controls-srl-004.xht b/css/css-writing-modes/form-controls-srl-004.xht
index 919c3de..37f4bb4 100644
--- a/css/css-writing-modes/form-controls-srl-004.xht
+++ b/css/css-writing-modes/form-controls-srl-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: order, orientation and layout of form controls in 'sideways-rl' writing-mode</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
 
   <meta content="image should" name="flags" />
   <meta content="This test checks the order, the flow, the orientation and layout of form controls used in Example 4 of section 3.1 when the 'writing-mode' of the whole form element is set to 'sideways-rl'." name="assert" />
diff --git a/css/css-writing-modes/form-controls-srl-005.xht b/css/css-writing-modes/form-controls-srl-005.xht
index 5fe188e..f092ecc 100644
--- a/css/css-writing-modes/form-controls-srl-005.xht
+++ b/css/css-writing-modes/form-controls-srl-005.xht
@@ -8,7 +8,7 @@
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
 
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
 
   <meta content="should" name="flags" />
   <meta content="This test checks the rendering of various form controls (select, optgroup, options, various types of input and buttons) written with latin text in 'sideways-rl' writing mode. The text of replaced content should match the replaced element's writing mode and line orientation; therefore, in this test, a) the text's inline base direction must be from top to bottom, b) the glyphs must be rotated 90° clockwise and c) the block flow direction must be from right to left." name="assert" />
diff --git a/css/css-writing-modes/full-width-003.html b/css/css-writing-modes/full-width-003.html
index a708656..da0d470 100644
--- a/css/css-writing-modes/full-width-003.html
+++ b/css/css-writing-modes/full-width-003.html
@@ -5,7 +5,7 @@
 <title>CSS Writing Modes: 'text-combine-upright: digits 2' applied on 2 full-width characters</title>
 <link rel="author" title="Masataka Yakura" href="http://google.com/+MasatakaYakura">
 <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-fullwidth" title="9.1.3.1. Full-width Characters">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-fullwidth" title="9.1.3.1. Full-width Characters">
 <link rel="help" href="https://www.w3.org/TR/css-text-3/#text-transform-property" title="2.1. Case Transforms: the 'text-transform' property">
 <link rel="mismatch" href="reference/full-width-002-notcu-notref.html">
 <link rel="mismatch" href="reference/full-width-002-horizontal-notref.html">
diff --git a/css/css-writing-modes/inline-block-alignment-slr-009.xht b/css/css-writing-modes/inline-block-alignment-slr-009.xht
index 1ec6bb9..55836bc 100644
--- a/css/css-writing-modes/inline-block-alignment-slr-009.xht
+++ b/css/css-writing-modes/inline-block-alignment-slr-009.xht
@@ -4,7 +4,7 @@
   <title>CSS Writing Modes Test: inline block alignment - alphabetical alignment with vertical layout</title>
   <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-12 -->
-  <link rel="help" title="CSS3 Writing modes: 4.3. Atomic Inline Baseline" href="http://www.w3.org/TR/css-writing-modes-3/#replaced-baselines" />
+  <link rel="help" title="CSS3 Writing modes: 4.3. Atomic Inline Baseline" href="http://www.w3.org/TR/css-writing-modes-4/#replaced-baselines" />
   <link rel="help" title="CSS2.1 9.2.2 Inline-level elements and inline boxes" href="http://www.w3.org/TR/CSS21/visuren.html#inline-boxes" />
   <link rel="match" href="inline-block-alignment-slr-009-ref.xht" />
   <meta name="assert" content="This test checks the generation of inline-block baseline. When 'writing-mode' is 'sideways-lr', then the alphabetic baseline is used as the dominant baseline." />
diff --git a/css/css-writing-modes/inline-block-alignment-srl-008.xht b/css/css-writing-modes/inline-block-alignment-srl-008.xht
index 5ef9bc9..115f8a5 100644
--- a/css/css-writing-modes/inline-block-alignment-srl-008.xht
+++ b/css/css-writing-modes/inline-block-alignment-srl-008.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: inline block alignment - alphabetical alignment with vertical layout</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-06 -->
-    <link rel="help" title="CSS3 Writing modes: 4.3. Atomic Inline Baseline" href="http://www.w3.org/TR/css-writing-modes-3/#replaced-baselines" />
+    <link rel="help" title="CSS3 Writing modes: 4.3. Atomic Inline Baseline" href="http://www.w3.org/TR/css-writing-modes-4/#replaced-baselines" />
     <link rel="help" title="CSS2.1 9.2.2 Inline-level elements and inline boxes" href="http://www.w3.org/TR/CSS21/visuren.html#inline-boxes" />
     <link rel="match" href="inline-block-alignment-006-ref.xht" />
     <meta name="assert" content="This test checks the generation of inline-block baseline. When 'writing-mode' is 'sideways-rl', then the alphabetic baseline is used as the dominant baseline." />
diff --git a/css/css-writing-modes/line-box-direction-slr-043.xht b/css/css-writing-modes/line-box-direction-slr-043.xht
index 80038f3..63fc7e9 100644
--- a/css/css-writing-modes/line-box-direction-slr-043.xht
+++ b/css/css-writing-modes/line-box-direction-slr-043.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-lr - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-047.xht b/css/css-writing-modes/line-box-direction-slr-047.xht
index 2176713..89c7a6d 100644
--- a/css/css-writing-modes/line-box-direction-slr-047.xht
+++ b/css/css-writing-modes/line-box-direction-slr-047.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: right' and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-002-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-048.xht b/css/css-writing-modes/line-box-direction-slr-048.xht
index 403b3ed..8dc5da4 100644
--- a/css/css-writing-modes/line-box-direction-slr-048.xht
+++ b/css/css-writing-modes/line-box-direction-slr-048.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: right' and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-002-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-050.xht b/css/css-writing-modes/line-box-direction-slr-050.xht
index b9e99a3..10619f3 100644
--- a/css/css-writing-modes/line-box-direction-slr-050.xht
+++ b/css/css-writing-modes/line-box-direction-slr-050.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: position absolute and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-053.xht b/css/css-writing-modes/line-box-direction-slr-053.xht
index 20b23f3..5f98250 100644
--- a/css/css-writing-modes/line-box-direction-slr-053.xht
+++ b/css/css-writing-modes/line-box-direction-slr-053.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-054.xht b/css/css-writing-modes/line-box-direction-slr-054.xht
index 0efae8f..e38aea8 100644
--- a/css/css-writing-modes/line-box-direction-slr-054.xht
+++ b/css/css-writing-modes/line-box-direction-slr-054.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-056.xht b/css/css-writing-modes/line-box-direction-slr-056.xht
index 9a162ff..6f2fdb0 100644
--- a/css/css-writing-modes/line-box-direction-slr-056.xht
+++ b/css/css-writing-modes/line-box-direction-slr-056.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-cell and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-058.xht b/css/css-writing-modes/line-box-direction-slr-058.xht
index d483cff..1b4bc0d 100644
--- a/css/css-writing-modes/line-box-direction-slr-058.xht
+++ b/css/css-writing-modes/line-box-direction-slr-058.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-caption and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-slr-060.xht b/css/css-writing-modes/line-box-direction-slr-060.xht
index 8d1383c..b4b2d19 100644
--- a/css/css-writing-modes/line-box-direction-slr-060.xht
+++ b/css/css-writing-modes/line-box-direction-slr-060.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: list and 'sideways-lr' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-042.xht b/css/css-writing-modes/line-box-direction-srl-042.xht
index 921af5a..a316500 100644
--- a/css/css-writing-modes/line-box-direction-srl-042.xht
+++ b/css/css-writing-modes/line-box-direction-srl-042.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-rl - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-045.xht b/css/css-writing-modes/line-box-direction-srl-045.xht
index ba14203..b6bc08a 100644
--- a/css/css-writing-modes/line-box-direction-srl-045.xht
+++ b/css/css-writing-modes/line-box-direction-srl-045.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: left' and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-046.xht b/css/css-writing-modes/line-box-direction-srl-046.xht
index 2f9edc29..2379135 100644
--- a/css/css-writing-modes/line-box-direction-srl-046.xht
+++ b/css/css-writing-modes/line-box-direction-srl-046.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'float: left' and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-049.xht b/css/css-writing-modes/line-box-direction-srl-049.xht
index babf754..822855e 100644
--- a/css/css-writing-modes/line-box-direction-srl-049.xht
+++ b/css/css-writing-modes/line-box-direction-srl-049.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: position absolute and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-051.xht b/css/css-writing-modes/line-box-direction-srl-051.xht
index 995d7e5..54c0068 100644
--- a/css/css-writing-modes/line-box-direction-srl-051.xht
+++ b/css/css-writing-modes/line-box-direction-srl-051.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-052.xht b/css/css-writing-modes/line-box-direction-srl-052.xht
index 8d1c369..a1ace95 100644
--- a/css/css-writing-modes/line-box-direction-srl-052.xht
+++ b/css/css-writing-modes/line-box-direction-srl-052.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: inline-block and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-055.xht b/css/css-writing-modes/line-box-direction-srl-055.xht
index 0000f0b..23c4d28 100644
--- a/css/css-writing-modes/line-box-direction-srl-055.xht
+++ b/css/css-writing-modes/line-box-direction-srl-055.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-cell and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-057.xht b/css/css-writing-modes/line-box-direction-srl-057.xht
index e694871..18608c5 100644
--- a/css/css-writing-modes/line-box-direction-srl-057.xht
+++ b/css/css-writing-modes/line-box-direction-srl-057.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: table-caption and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/line-box-direction-srl-059.xht b/css/css-writing-modes/line-box-direction-srl-059.xht
index 7a0e79e..5f66d20 100644
--- a/css/css-writing-modes/line-box-direction-srl-059.xht
+++ b/css/css-writing-modes/line-box-direction-srl-059.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: list and 'sideways-rl' - ordering direction of line boxes</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/logical-props-001.html b/css/css-writing-modes/logical-props-001.html
new file mode 100644
index 0000000..599c80e
--- /dev/null
+++ b/css/css-writing-modes/logical-props-001.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>writing-modes and logical props: tr</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/logical-props-001-ref.html"
+<meta name="assert" content="The logical properties on table rows are resolved taking the writing mode of the element into account,
+                            even though the writing mode property does not apply to that element.">
+<style>
+table { border-collapse: collapse; }
+td {
+  padding: 0;
+  height: 100px;
+  width: 100px;
+}
+tr {
+  writing-mode: vertical-rl;
+  border-inline-start: 5px green solid;
+}
+
+.red {
+  position: absolute;
+  width: 100px;
+  height: 5px;
+  background: red;
+  z-index: -1;
+}
+
+</style>
+<body>
+  <p>Test passes if there is a <strong>horizontal green stripe</strong> below and <strong>no red</strong>.
+  <div class=red></div>
+  <table><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+</body>
diff --git a/css/css-writing-modes/logical-props-002.html b/css/css-writing-modes/logical-props-002.html
new file mode 100644
index 0000000..a018e59
--- /dev/null
+++ b/css/css-writing-modes/logical-props-002.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>writing-modes and logical props: tbody</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/logical-props-001-ref.html"
+<meta name="assert" content="The logical properties on table row groups are resolved taking the writing mode of the element into account,
+                            even though the writing mode property does not apply to that element.">
+<style>
+table { border-collapse: collapse; }
+td {
+  padding: 0;
+  height: 100px;
+  width: 100px;
+}
+tbody {
+  writing-mode: vertical-rl;
+  border-inline-start: 5px green solid;
+}
+
+.red {
+  position: absolute;
+  width: 100px;
+  height: 5px;
+  background: red;
+  z-index: -1;
+}
+
+</style>
+<body>
+  <p>Test passes if there is a <strong>horizontal green stripe</strong> below and <strong>no red</strong>.
+  <div class=red></div>
+  <table><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+</body>
diff --git a/css/css-writing-modes/logical-props-003.html b/css/css-writing-modes/logical-props-003.html
new file mode 100644
index 0000000..64e272d
--- /dev/null
+++ b/css/css-writing-modes/logical-props-003.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>writing-modes and logical props: col</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/logical-props-001-ref.html"
+<meta name="assert" content="The logical properties on table columns are resolved taking the writing mode of the element into account,
+                            even though the writing mode property does not apply to that element.">
+<style>
+table { border-collapse: collapse; }
+td {
+  padding: 0;
+  height: 100px;
+  width: 100px;
+}
+col {
+  writing-mode: vertical-rl;
+  border-inline-start: 5px green solid;
+}
+
+.red {
+  position: absolute;
+  width: 100px;
+  height: 5px;
+  background: red;
+  z-index: -1;
+}
+
+</style>
+<body>
+  <p>Test passes if there is a <strong>horizontal green stripe</strong> below and <strong>no red</strong>.
+  <div class=red></div>
+  <table><col><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+</body>
diff --git a/css/css-writing-modes/logical-props-004.html b/css/css-writing-modes/logical-props-004.html
new file mode 100644
index 0000000..556794d
--- /dev/null
+++ b/css/css-writing-modes/logical-props-004.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>writing-modes and logical props: colgroup</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#placement">
+<link rel="match" href="reference/logical-props-001-ref.html"
+<meta name="assert" content="The logical properties on table column groups are resolved taking the writing mode of the element into account,
+                            even though the writing mode property does not apply to that element.">
+<style>
+table { border-collapse: collapse; }
+td {
+  padding: 0;
+  height: 100px;
+  width: 100px;
+}
+colgroup {
+  writing-mode: vertical-rl;
+  border-inline-start: 5px green solid;
+}
+
+.red {
+  position: absolute;
+  width: 100px;
+  height: 5px;
+  background: red;
+  z-index: -1;
+}
+
+</style>
+<body>
+  <p>Test passes if there is a <strong>horizontal green stripe</strong> below and <strong>no red</strong>.
+  <div class=red></div>
+  <table><colgroup><tbody><tr><td>&nbsp;</td></tr></tbody></table>
+</body>
diff --git a/css/css-writing-modes/mongolian-orientation-001.html b/css/css-writing-modes/mongolian-orientation-001.html
new file mode 100644
index 0000000..0c3271c
--- /dev/null
+++ b/css/css-writing-modes/mongolian-orientation-001.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: orientation of mongolian</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#text-orientation">
+<link rel="match" href="reference/mongolian-orientation-001-ref.html">
+<meta name="assert" content="Mongolian is intrinsicly vertical, mixed and upright text-orientation are expected to be the same.">
+<style>
+div {
+  font-family: "Mongolian White"; /* Not required for the test to work,
+but it helps make it look not ugly if you have the font installed.*/
+  font-size: 2em;
+  margin: 1em;
+  writing-mode: vertical-lr;
+  float: left;
+}
+div:nth-of-type(1) { text-orientation: mixed; }
+div:nth-of-type(2) { text-orientation: upright; }
+</style>
+
+<p>The test passes if you see two identical lines of vertical text (in Mongolian).
+
+<div>ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ</div>
+<div>ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ</div>
diff --git a/css/css-writing-modes/mongolian-orientation-002.html b/css/css-writing-modes/mongolian-orientation-002.html
new file mode 100644
index 0000000..66dde36
--- /dev/null
+++ b/css/css-writing-modes/mongolian-orientation-002.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: orientation of mongolian</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#text-orientation">
+<link rel="match" href="reference/mongolian-orientation-001-ref.html">
+<meta name="assert" content="In Mongolian, in horizontal text, glyphs are typeset in a 90° counter-clockwise rotation from their intrisic vertical orientation. text-orientation:sideways causes all text to be typeset sideways, as if in a horizontal layout, but rotated 90° clockwise. text-orientation:mixed causes typographic character units from vertical scripts are typeset with their intrinsic orientation. These two should therefore result in the same thing">
+<style>
+div {
+  font-family: "Mongolian White"; /* Not required for the test to work,
+but it helps make it look not ugly if you have the font installed.*/
+  font-size: 2em;
+  margin: 1em;
+  writing-mode: vertical-lr;
+  float: left;
+}
+div:nth-of-type(1) { text-orientation: mixed; }
+div:nth-of-type(2) { text-orientation: sideways; }
+</style>
+
+<p>The test passes if you see two identical lines of vertical text (in Mongolian).
+
+<div>ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ</div>
+<div>ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ</div>
diff --git a/css/css-writing-modes/nested-orthogonal-001.html b/css/css-writing-modes/nested-orthogonal-001.html
new file mode 100644
index 0000000..bcd2e2a
--- /dev/null
+++ b/css/css-writing-modes/nested-orthogonal-001.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Writing mode</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-flows">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="In the face of multiple levels of orthogonal flow nesting, children's sizes propagate up to parents.">
+<meta name="flags" content="">
+<style>
+body > div {
+  background: green;
+  writing-mode: vertical-lr;
+}
+
+div > div { writing-mode: horizontal-tb; }
+
+div > div > div {
+  writing-mode: vertical-rl;
+  height: 100px;
+  font-size: 100px;
+  line-height: 1;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+<div>
+  <div><div>&nbsp;</div></div>
+</div>
diff --git a/css/css-writing-modes/outline-inline-slr-005.xht b/css/css-writing-modes/outline-inline-slr-005.xht
index 9526785..42adb38 100644
--- a/css/css-writing-modes/outline-inline-slr-005.xht
+++ b/css/css-writing-modes/outline-inline-slr-005.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: outline layout and non-replaced inline in sideways-lr writing-mode</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" title="7.1. Principles of Layout in Vertical Writing Modes" href="http://www.w3.org/TR/css-writing-modes-3/#vertical-layout" />
+  <link rel="help" title="7.1. Principles of Layout in Vertical Writing Modes" href="http://www.w3.org/TR/css-writing-modes-4/#vertical-layout" />
   <link rel="help" title="18.4 Dynamic outlines: the 'outline' property" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/ui.html#dynamic-outlines" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/outline-inline-srl-004.xht b/css/css-writing-modes/outline-inline-srl-004.xht
index 4b077c4..e9fc32a 100644
--- a/css/css-writing-modes/outline-inline-srl-004.xht
+++ b/css/css-writing-modes/outline-inline-srl-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: outline layout and non-replaced inline and sideways-rl writing-mode</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" title="7.1. Principles of Layout in Vertical Writing Modes" href="http://www.w3.org/TR/css-writing-modes-3/#vertical-layout" />
+  <link rel="help" title="7.1. Principles of Layout in Vertical Writing Modes" href="http://www.w3.org/TR/css-writing-modes-4/#vertical-layout" />
   <link rel="help" title="18.4 Dynamic outlines: the 'outline' property" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/ui.html#dynamic-outlines" />
 
   <meta content="" name="flags" />
diff --git a/css/css-writing-modes/page-flow-direction-slr-005.xht b/css/css-writing-modes/page-flow-direction-slr-005.xht
index afc68cc..30d045d 100644
--- a/css/css-writing-modes/page-flow-direction-slr-005.xht
+++ b/css/css-writing-modes/page-flow-direction-slr-005.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-lr' - default page flow (progression) direction</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
 
   <meta content="image paged" name="flags" />
   <meta content="This test checks that when 'writing-mode' is set on the root element, then it determines the default page flow (or progression) direction. In this test, the page flow (or progression) direction is left-to-right." name="assert" />
diff --git a/css/css-writing-modes/page-flow-direction-srl-004.xht b/css/css-writing-modes/page-flow-direction-srl-004.xht
index b12e9c1..d1d1aa6 100644
--- a/css/css-writing-modes/page-flow-direction-srl-004.xht
+++ b/css/css-writing-modes/page-flow-direction-srl-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-rl' - default page flow (progression) direction</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
 
   <meta content="image paged" name="flags" />
   <meta content="This test checks that when 'writing-mode' is set on the root element, then it determines the default page flow (or progression) direction. In this test, the page flow (or progression) direction is right-to-left." name="assert" />
diff --git a/css/css-writing-modes/reference/available-size-001-ref.html b/css/css-writing-modes/reference/available-size-001-ref.html
new file mode 100644
index 0000000..8425294
--- /dev/null
+++ b/css/css-writing-modes/reference/available-size-001-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test reference</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  color: transparent;
+  writing-mode: vertical-rl;
+  background: green;
+  border-top: 1ch solid white;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>0</div>
diff --git a/css/css-writing-modes/reference/available-size-002-ref.html b/css/css-writing-modes/reference/available-size-002-ref.html
new file mode 100644
index 0000000..6fdbf9a
--- /dev/null
+++ b/css/css-writing-modes/reference/available-size-002-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test reference</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  color: transparent;
+  writing-mode: vertical-rl;
+  background: green;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>0</div>
diff --git a/css/css-writing-modes/reference/available-size-011-ref.html b/css/css-writing-modes/reference/available-size-011-ref.html
new file mode 100644
index 0000000..ef66b4e
--- /dev/null
+++ b/css/css-writing-modes/reference/available-size-011-ref.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>CSS writing mode test reference</title>
+<meta charset=utf-8>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<style>
+div {
+  line-height: 1em;
+  height: 1em;
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>This test passes if the word “PASS” (without the quotation marks) appears below, written horizontally from left to right.
+<div>S S A P</div>
diff --git a/css/css-writing-modes/reference/bidi-embed-001.html b/css/css-writing-modes/reference/bidi-embed-001.html
index dd27d55..24723ad 100644
--- a/css/css-writing-modes/reference/bidi-embed-001.html
+++ b/css/css-writing-modes/reference/bidi-embed-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-002.html b/css/css-writing-modes/reference/bidi-embed-002.html
index 7565090..fa9d254 100644
--- a/css/css-writing-modes/reference/bidi-embed-002.html
+++ b/css/css-writing-modes/reference/bidi-embed-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-003.html b/css/css-writing-modes/reference/bidi-embed-003.html
index 332e855..a8e1cb7 100644
--- a/css/css-writing-modes/reference/bidi-embed-003.html
+++ b/css/css-writing-modes/reference/bidi-embed-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-004.html b/css/css-writing-modes/reference/bidi-embed-004.html
index 6986f13..32f96a1 100644
--- a/css/css-writing-modes/reference/bidi-embed-004.html
+++ b/css/css-writing-modes/reference/bidi-embed-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-005.html b/css/css-writing-modes/reference/bidi-embed-005.html
index a321f50..cd46ded 100644
--- a/css/css-writing-modes/reference/bidi-embed-005.html
+++ b/css/css-writing-modes/reference/bidi-embed-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-006.html b/css/css-writing-modes/reference/bidi-embed-006.html
index a01098c..c111084 100644
--- a/css/css-writing-modes/reference/bidi-embed-006.html
+++ b/css/css-writing-modes/reference/bidi-embed-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-007.html b/css/css-writing-modes/reference/bidi-embed-007.html
index 507a293..b6117e4 100644
--- a/css/css-writing-modes/reference/bidi-embed-007.html
+++ b/css/css-writing-modes/reference/bidi-embed-007.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-008.html b/css/css-writing-modes/reference/bidi-embed-008.html
index f30ca53..d38b1b5 100644
--- a/css/css-writing-modes/reference/bidi-embed-008.html
+++ b/css/css-writing-modes/reference/bidi-embed-008.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-009.html b/css/css-writing-modes/reference/bidi-embed-009.html
index 15f64e2..146789a 100644
--- a/css/css-writing-modes/reference/bidi-embed-009.html
+++ b/css/css-writing-modes/reference/bidi-embed-009.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-010.html b/css/css-writing-modes/reference/bidi-embed-010.html
index 5afeafb..2643873 100644
--- a/css/css-writing-modes/reference/bidi-embed-010.html
+++ b/css/css-writing-modes/reference/bidi-embed-010.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-embed-011.html b/css/css-writing-modes/reference/bidi-embed-011.html
index a550d58..e1a01b4 100644
--- a/css/css-writing-modes/reference/bidi-embed-011.html
+++ b/css/css-writing-modes/reference/bidi-embed-011.html
@@ -14,7 +14,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-001.html b/css/css-writing-modes/reference/bidi-isolate-001.html
index 8289b29..40f9409 100644
--- a/css/css-writing-modes/reference/bidi-isolate-001.html
+++ b/css/css-writing-modes/reference/bidi-isolate-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-002.html b/css/css-writing-modes/reference/bidi-isolate-002.html
index b77f5e9..c1abbd0 100644
--- a/css/css-writing-modes/reference/bidi-isolate-002.html
+++ b/css/css-writing-modes/reference/bidi-isolate-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-003.html b/css/css-writing-modes/reference/bidi-isolate-003.html
index 2015852..3ea23c5 100644
--- a/css/css-writing-modes/reference/bidi-isolate-003.html
+++ b/css/css-writing-modes/reference/bidi-isolate-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-004.html b/css/css-writing-modes/reference/bidi-isolate-004.html
index c6c0d41..4e8674f 100644
--- a/css/css-writing-modes/reference/bidi-isolate-004.html
+++ b/css/css-writing-modes/reference/bidi-isolate-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-005.html b/css/css-writing-modes/reference/bidi-isolate-005.html
index a06066f..7e4471c 100644
--- a/css/css-writing-modes/reference/bidi-isolate-005.html
+++ b/css/css-writing-modes/reference/bidi-isolate-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-006.html b/css/css-writing-modes/reference/bidi-isolate-006.html
index 972065b..eb4a12b 100644
--- a/css/css-writing-modes/reference/bidi-isolate-006.html
+++ b/css/css-writing-modes/reference/bidi-isolate-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-007.html b/css/css-writing-modes/reference/bidi-isolate-007.html
index 9526b30..36c3649 100644
--- a/css/css-writing-modes/reference/bidi-isolate-007.html
+++ b/css/css-writing-modes/reference/bidi-isolate-007.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-008.html b/css/css-writing-modes/reference/bidi-isolate-008.html
index 7e70a58..f9e21cd 100644
--- a/css/css-writing-modes/reference/bidi-isolate-008.html
+++ b/css/css-writing-modes/reference/bidi-isolate-008.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-009.html b/css/css-writing-modes/reference/bidi-isolate-009.html
index 47aff59..77e72e7 100644
--- a/css/css-writing-modes/reference/bidi-isolate-009.html
+++ b/css/css-writing-modes/reference/bidi-isolate-009.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-010.html b/css/css-writing-modes/reference/bidi-isolate-010.html
index 2098bfe..5e436f8 100644
--- a/css/css-writing-modes/reference/bidi-isolate-010.html
+++ b/css/css-writing-modes/reference/bidi-isolate-010.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-011.html b/css/css-writing-modes/reference/bidi-isolate-011.html
index 2ac954b..5fc0714 100644
--- a/css/css-writing-modes/reference/bidi-isolate-011.html
+++ b/css/css-writing-modes/reference/bidi-isolate-011.html
@@ -14,7 +14,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-001.html b/css/css-writing-modes/reference/bidi-isolate-override-001.html
index a0e036b..b9d066a 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-001.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-002.html b/css/css-writing-modes/reference/bidi-isolate-override-002.html
index 59ed5ca..56b9e7e 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-002.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-003.html b/css/css-writing-modes/reference/bidi-isolate-override-003.html
index 42c6f91..ec28350 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-003.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-004.html b/css/css-writing-modes/reference/bidi-isolate-override-004.html
index 26fd5e8..8b02488 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-004.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-005.html b/css/css-writing-modes/reference/bidi-isolate-override-005.html
index fc3fdce..03db742 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-005.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-006.html b/css/css-writing-modes/reference/bidi-isolate-override-006.html
index 0513660..2f42138 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-006.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-007.html b/css/css-writing-modes/reference/bidi-isolate-override-007.html
index 1dba58c..379a20b 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-007.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-007.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-008.html b/css/css-writing-modes/reference/bidi-isolate-override-008.html
index 6875e74..1250052 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-008.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-008.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-009.html b/css/css-writing-modes/reference/bidi-isolate-override-009.html
index fa31d87..af9a6eb 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-009.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-009.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-010.html b/css/css-writing-modes/reference/bidi-isolate-override-010.html
index 42e0626..3bf9c69 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-010.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-010.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-011.html b/css/css-writing-modes/reference/bidi-isolate-override-011.html
index 2c2bba4..c2e4d6b 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-011.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-011.html
@@ -14,7 +14,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-isolate-override-012.html b/css/css-writing-modes/reference/bidi-isolate-override-012.html
index 9d87b0a..341913d 100644
--- a/css/css-writing-modes/reference/bidi-isolate-override-012.html
+++ b/css/css-writing-modes/reference/bidi-isolate-override-012.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-001.html b/css/css-writing-modes/reference/bidi-normal-001.html
index bec5010..a57042f 100644
--- a/css/css-writing-modes/reference/bidi-normal-001.html
+++ b/css/css-writing-modes/reference/bidi-normal-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-002.html b/css/css-writing-modes/reference/bidi-normal-002.html
index 7933e86..6ae0ba1 100644
--- a/css/css-writing-modes/reference/bidi-normal-002.html
+++ b/css/css-writing-modes/reference/bidi-normal-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-003.html b/css/css-writing-modes/reference/bidi-normal-003.html
index 27437f0..1af0f83 100644
--- a/css/css-writing-modes/reference/bidi-normal-003.html
+++ b/css/css-writing-modes/reference/bidi-normal-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-004.html b/css/css-writing-modes/reference/bidi-normal-004.html
index 948a41a..deb5877 100644
--- a/css/css-writing-modes/reference/bidi-normal-004.html
+++ b/css/css-writing-modes/reference/bidi-normal-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-005.html b/css/css-writing-modes/reference/bidi-normal-005.html
index a336d2f..c141954 100644
--- a/css/css-writing-modes/reference/bidi-normal-005.html
+++ b/css/css-writing-modes/reference/bidi-normal-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-006.html b/css/css-writing-modes/reference/bidi-normal-006.html
index 054fc91..ddd877b 100644
--- a/css/css-writing-modes/reference/bidi-normal-006.html
+++ b/css/css-writing-modes/reference/bidi-normal-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-007.html b/css/css-writing-modes/reference/bidi-normal-007.html
index 2f5dd35..cd4508f 100644
--- a/css/css-writing-modes/reference/bidi-normal-007.html
+++ b/css/css-writing-modes/reference/bidi-normal-007.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-008.html b/css/css-writing-modes/reference/bidi-normal-008.html
index 8fb21eb..74e3609 100644
--- a/css/css-writing-modes/reference/bidi-normal-008.html
+++ b/css/css-writing-modes/reference/bidi-normal-008.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-009.html b/css/css-writing-modes/reference/bidi-normal-009.html
index 73e3092..97b8591 100644
--- a/css/css-writing-modes/reference/bidi-normal-009.html
+++ b/css/css-writing-modes/reference/bidi-normal-009.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-010.html b/css/css-writing-modes/reference/bidi-normal-010.html
index eb05ec1..cc9a249 100644
--- a/css/css-writing-modes/reference/bidi-normal-010.html
+++ b/css/css-writing-modes/reference/bidi-normal-010.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-normal-011.html b/css/css-writing-modes/reference/bidi-normal-011.html
index 0fe2b2b..295e577 100644
--- a/css/css-writing-modes/reference/bidi-normal-011.html
+++ b/css/css-writing-modes/reference/bidi-normal-011.html
@@ -14,7 +14,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-001.html b/css/css-writing-modes/reference/bidi-override-001.html
index 9a5099d..ba01516 100644
--- a/css/css-writing-modes/reference/bidi-override-001.html
+++ b/css/css-writing-modes/reference/bidi-override-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-002.html b/css/css-writing-modes/reference/bidi-override-002.html
index d0b0b4d..d147bfb 100644
--- a/css/css-writing-modes/reference/bidi-override-002.html
+++ b/css/css-writing-modes/reference/bidi-override-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-003.html b/css/css-writing-modes/reference/bidi-override-003.html
index a1d620e..3b200b3 100644
--- a/css/css-writing-modes/reference/bidi-override-003.html
+++ b/css/css-writing-modes/reference/bidi-override-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-004.html b/css/css-writing-modes/reference/bidi-override-004.html
index 094f1ef..9a6c960 100644
--- a/css/css-writing-modes/reference/bidi-override-004.html
+++ b/css/css-writing-modes/reference/bidi-override-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-005.html b/css/css-writing-modes/reference/bidi-override-005.html
index 5687df9..980beca 100644
--- a/css/css-writing-modes/reference/bidi-override-005.html
+++ b/css/css-writing-modes/reference/bidi-override-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-006.html b/css/css-writing-modes/reference/bidi-override-006.html
index f06ac1e..f40ad50 100644
--- a/css/css-writing-modes/reference/bidi-override-006.html
+++ b/css/css-writing-modes/reference/bidi-override-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-007.html b/css/css-writing-modes/reference/bidi-override-007.html
index b8c8808..4d70ebf 100644
--- a/css/css-writing-modes/reference/bidi-override-007.html
+++ b/css/css-writing-modes/reference/bidi-override-007.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-008.html b/css/css-writing-modes/reference/bidi-override-008.html
index b90409a..a92c6ba 100644
--- a/css/css-writing-modes/reference/bidi-override-008.html
+++ b/css/css-writing-modes/reference/bidi-override-008.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-009.html b/css/css-writing-modes/reference/bidi-override-009.html
index 419b5ac..1cf791a 100644
--- a/css/css-writing-modes/reference/bidi-override-009.html
+++ b/css/css-writing-modes/reference/bidi-override-009.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-010.html b/css/css-writing-modes/reference/bidi-override-010.html
index c378844..654e575 100644
--- a/css/css-writing-modes/reference/bidi-override-010.html
+++ b/css/css-writing-modes/reference/bidi-override-010.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-011.html b/css/css-writing-modes/reference/bidi-override-011.html
index 7d2b6dd..1958917 100644
--- a/css/css-writing-modes/reference/bidi-override-011.html
+++ b/css/css-writing-modes/reference/bidi-override-011.html
@@ -14,7 +14,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-override-012.html b/css/css-writing-modes/reference/bidi-override-012.html
index 577e7c5..9bb8ef8 100644
--- a/css/css-writing-modes/reference/bidi-override-012.html
+++ b/css/css-writing-modes/reference/bidi-override-012.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-001.html b/css/css-writing-modes/reference/bidi-plaintext-001.html
index 5595b2b..932d746 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-001.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-002.html b/css/css-writing-modes/reference/bidi-plaintext-002.html
index 9b75b3b..63e4a61 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-002.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-003.html b/css/css-writing-modes/reference/bidi-plaintext-003.html
index 0a10398..82457f3 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-003.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-004.html b/css/css-writing-modes/reference/bidi-plaintext-004.html
index 1edd969..e91be7c 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-004.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-005.html b/css/css-writing-modes/reference/bidi-plaintext-005.html
index 0b8adb2..4301099 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-005.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-006.html b/css/css-writing-modes/reference/bidi-plaintext-006.html
index 91ef16b..32b211e 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-006.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-007.html b/css/css-writing-modes/reference/bidi-plaintext-007.html
index 44067d7..7b5d23b 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-007.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-007.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-008.html b/css/css-writing-modes/reference/bidi-plaintext-008.html
index 8215b3e..6201cbe 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-008.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-008.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-009.html b/css/css-writing-modes/reference/bidi-plaintext-009.html
index e202027..a2885a1 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-009.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-009.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-010.html b/css/css-writing-modes/reference/bidi-plaintext-010.html
index 8374e98..2b814fc 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-010.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-010.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-plaintext-011.html b/css/css-writing-modes/reference/bidi-plaintext-011.html
index 379265d..91115a5 100644
--- a/css/css-writing-modes/reference/bidi-plaintext-011.html
+++ b/css/css-writing-modes/reference/bidi-plaintext-011.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-table-001.html b/css/css-writing-modes/reference/bidi-table-001.html
index 0db3f3a..076e17e 100644
--- a/css/css-writing-modes/reference/bidi-table-001.html
+++ b/css/css-writing-modes/reference/bidi-table-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-001.html b/css/css-writing-modes/reference/bidi-unset-001.html
index d145814..ca8ead0 100644
--- a/css/css-writing-modes/reference/bidi-unset-001.html
+++ b/css/css-writing-modes/reference/bidi-unset-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-002.html b/css/css-writing-modes/reference/bidi-unset-002.html
index 465cc88..8b52582 100644
--- a/css/css-writing-modes/reference/bidi-unset-002.html
+++ b/css/css-writing-modes/reference/bidi-unset-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-003.html b/css/css-writing-modes/reference/bidi-unset-003.html
index be2334c..364c85b 100644
--- a/css/css-writing-modes/reference/bidi-unset-003.html
+++ b/css/css-writing-modes/reference/bidi-unset-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-004.html b/css/css-writing-modes/reference/bidi-unset-004.html
index 2237222..3c8ab54 100644
--- a/css/css-writing-modes/reference/bidi-unset-004.html
+++ b/css/css-writing-modes/reference/bidi-unset-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-005.html b/css/css-writing-modes/reference/bidi-unset-005.html
index 0b0e81c..877572e 100644
--- a/css/css-writing-modes/reference/bidi-unset-005.html
+++ b/css/css-writing-modes/reference/bidi-unset-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-006.html b/css/css-writing-modes/reference/bidi-unset-006.html
index 63c1ebc..84e87ff 100644
--- a/css/css-writing-modes/reference/bidi-unset-006.html
+++ b/css/css-writing-modes/reference/bidi-unset-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-007.html b/css/css-writing-modes/reference/bidi-unset-007.html
index ec2cfc8..10f7419 100644
--- a/css/css-writing-modes/reference/bidi-unset-007.html
+++ b/css/css-writing-modes/reference/bidi-unset-007.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-008.html b/css/css-writing-modes/reference/bidi-unset-008.html
index acaeb58..6e86d03 100644
--- a/css/css-writing-modes/reference/bidi-unset-008.html
+++ b/css/css-writing-modes/reference/bidi-unset-008.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-009.html b/css/css-writing-modes/reference/bidi-unset-009.html
index e7214ff..bee3e7e 100644
--- a/css/css-writing-modes/reference/bidi-unset-009.html
+++ b/css/css-writing-modes/reference/bidi-unset-009.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/bidi-unset-010.html b/css/css-writing-modes/reference/bidi-unset-010.html
index 4fc4682..f80faf1 100644
--- a/css/css-writing-modes/reference/bidi-unset-010.html
+++ b/css/css-writing-modes/reference/bidi-unset-010.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-embed-001.html b/css/css-writing-modes/reference/block-embed-001.html
index f17115e..52e9244 100644
--- a/css/css-writing-modes/reference/block-embed-001.html
+++ b/css/css-writing-modes/reference/block-embed-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-embed-002.html b/css/css-writing-modes/reference/block-embed-002.html
index 4f5e2ae..e3ab7a3 100644
--- a/css/css-writing-modes/reference/block-embed-002.html
+++ b/css/css-writing-modes/reference/block-embed-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-embed-003.html b/css/css-writing-modes/reference/block-embed-003.html
index 1141095..1f0f8a9 100644
--- a/css/css-writing-modes/reference/block-embed-003.html
+++ b/css/css-writing-modes/reference/block-embed-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-001.html b/css/css-writing-modes/reference/block-override-001.html
index 006abf2..61cf4f6 100644
--- a/css/css-writing-modes/reference/block-override-001.html
+++ b/css/css-writing-modes/reference/block-override-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-002.html b/css/css-writing-modes/reference/block-override-002.html
index baa36bb..3ac969b 100644
--- a/css/css-writing-modes/reference/block-override-002.html
+++ b/css/css-writing-modes/reference/block-override-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-003.html b/css/css-writing-modes/reference/block-override-003.html
index ccab072..96131d4 100644
--- a/css/css-writing-modes/reference/block-override-003.html
+++ b/css/css-writing-modes/reference/block-override-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-004.html b/css/css-writing-modes/reference/block-override-004.html
index 96e2fef..3ae03a6 100644
--- a/css/css-writing-modes/reference/block-override-004.html
+++ b/css/css-writing-modes/reference/block-override-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-isolate-001.html b/css/css-writing-modes/reference/block-override-isolate-001.html
index d0aebce..9e132ce 100644
--- a/css/css-writing-modes/reference/block-override-isolate-001.html
+++ b/css/css-writing-modes/reference/block-override-isolate-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-isolate-002.html b/css/css-writing-modes/reference/block-override-isolate-002.html
index 7491bfc..c3e50f5 100644
--- a/css/css-writing-modes/reference/block-override-isolate-002.html
+++ b/css/css-writing-modes/reference/block-override-isolate-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-isolate-003.html b/css/css-writing-modes/reference/block-override-isolate-003.html
index 95e0471..fc8da85 100644
--- a/css/css-writing-modes/reference/block-override-isolate-003.html
+++ b/css/css-writing-modes/reference/block-override-isolate-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-override-isolate-004.html b/css/css-writing-modes/reference/block-override-isolate-004.html
index 0525d68..8de24ce 100644
--- a/css/css-writing-modes/reference/block-override-isolate-004.html
+++ b/css/css-writing-modes/reference/block-override-isolate-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-plaintext-001.html b/css/css-writing-modes/reference/block-plaintext-001.html
index a2031fd..2c2129f 100644
--- a/css/css-writing-modes/reference/block-plaintext-001.html
+++ b/css/css-writing-modes/reference/block-plaintext-001.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-plaintext-002.html b/css/css-writing-modes/reference/block-plaintext-002.html
index ef14981..ebc0b8f 100644
--- a/css/css-writing-modes/reference/block-plaintext-002.html
+++ b/css/css-writing-modes/reference/block-plaintext-002.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-plaintext-003.html b/css/css-writing-modes/reference/block-plaintext-003.html
index 2b93c79..5f9457d 100644
--- a/css/css-writing-modes/reference/block-plaintext-003.html
+++ b/css/css-writing-modes/reference/block-plaintext-003.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-plaintext-004.html b/css/css-writing-modes/reference/block-plaintext-004.html
index e80be5d..de5c09a 100644
--- a/css/css-writing-modes/reference/block-plaintext-004.html
+++ b/css/css-writing-modes/reference/block-plaintext-004.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-plaintext-005.html b/css/css-writing-modes/reference/block-plaintext-005.html
index ecc665c..014cbf0 100644
--- a/css/css-writing-modes/reference/block-plaintext-005.html
+++ b/css/css-writing-modes/reference/block-plaintext-005.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/block-plaintext-006.html b/css/css-writing-modes/reference/block-plaintext-006.html
index 001bf14..8a68e2b 100644
--- a/css/css-writing-modes/reference/block-plaintext-006.html
+++ b/css/css-writing-modes/reference/block-plaintext-006.html
@@ -13,7 +13,7 @@
 input { margin: 5px; }
 @font-face {
     font-family: 'ezra_silregular';
-    src: url('support/sileot-webfont.woff') format('woff');
+    src: url('/fonts/sileot-webfont.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     }
diff --git a/css/css-writing-modes/reference/ch-units-vrl-001-ref.html b/css/css-writing-modes/reference/ch-units-vrl-001-ref.html
new file mode 100644
index 0000000..7bb13b3
--- /dev/null
+++ b/css/css-writing-modes/reference/ch-units-vrl-001-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS test reference</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<style>
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+div:nth-of-type(1) { background: green; }
+div:nth-of-type(2) { background: blue; }
+div:nth-of-type(1),
+div:nth-of-type(2) {
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  height: 5ch;
+  width: 5ch;
+}
+div:nth-of-type(3) {
+  background: orange;
+  height: 5ch;
+  width: 5ch;
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <div></div>
+  <div></div>
+  <div></div>
+</body>
diff --git a/css/css-writing-modes/reference/ch-units-vrl-005-ref.html b/css/css-writing-modes/reference/ch-units-vrl-005-ref.html
new file mode 100644
index 0000000..dcbe650
--- /dev/null
+++ b/css/css-writing-modes/reference/ch-units-vrl-005-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS test reference</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<style>
+div {
+  font-size: 20px;
+  color: transparent;
+}
+
+div:nth-of-type(1) { background: green; }
+div:nth-of-type(2) { background: blue; }
+div:nth-of-type(1),
+div:nth-of-type(2) {
+  height: 5ch;
+  width: 5ch;
+}
+div:nth-of-type(3) {
+  background: orange;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  height: 5ch;
+  width: 5ch;
+}
+</style>
+<body>
+  <p>Test passes if there is a <strong>green square</strong> below, and if it is the <strong>same size as the blue</strong> one, not the orange one.
+  <p>If any of the 3 colored shapes is not a square, or if the orange and blue squares are the same size, the test fails.
+  <div></div>
+  <div></div>
+  <div></div>
+</body>
diff --git a/css/css-writing-modes/reference/logical-props-001-ref.html b/css/css-writing-modes/reference/logical-props-001-ref.html
new file mode 100644
index 0000000..2154a8d
--- /dev/null
+++ b/css/css-writing-modes/reference/logical-props-001-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS test reference</title>
+<link rel="author" title="Florian Rivoal" href="http://florian.rivoal.net/">
+<style>
+div {
+  position: absolute;
+  width: 100px;
+  height: 5px;
+  background: green;
+}
+
+</style>
+<body>
+  <p>Test passes if there is a <strong>horizontal green stripe</strong> below and <strong>no red</strong>.
+  <div></div>
+</body>
diff --git a/css/css-writing-modes/reference/mongolian-orientation-001-ref.html b/css/css-writing-modes/reference/mongolian-orientation-001-ref.html
new file mode 100644
index 0000000..66cb618
--- /dev/null
+++ b/css/css-writing-modes/reference/mongolian-orientation-001-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test Reference</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<style>
+div {
+  font-family: "Mongolian White";
+  font-size: 2em;
+  margin: 1em;
+  writing-mode: vertical-lr;
+  float: left;
+}
+div:nth-of-type(1) { text-orientation: sideways; }
+div:nth-of-type(2) { text-orientation: sideways; }
+</style>
+
+<p>The test passes if you see two identical lines of vertical text (in Mongolian).
+
+<div>ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ</div>
+<div>ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ</div>
diff --git a/css/css-writing-modes/reference/svg-aliasing-001-ref.html b/css/css-writing-modes/reference/svg-aliasing-001-ref.html
new file mode 100644
index 0000000..7546192
--- /dev/null
+++ b/css/css-writing-modes/reference/svg-aliasing-001-ref.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Writing modes test reference</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<p>The test passes if you can see “1 2 3” 6 times below (without the quotation marks). If any of them is reversed (“3 2 1”) the test fails.</p>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   width="300"
+   height="600">
+<g>
+  <text x="0" y="50" style="fill: blue;">1 2 3</text>
+  <text x="0" y="150" style="fill: blue;">1 2 3</text>
+</g>
+<g>
+  <text x="150" y="50" style="fill: blue;">1 2 3</text>
+  <text x="150" y="150" style="fill: blue;">1 2 3</text>
+</g>
+<g>
+  <text x="300" y="50" style="direction: rtl; fill: blue;">3 2 1</text>
+  <text x="300" y="150" style="direction: rtl; fill: blue;">3 2 1</text>
+</g>
+</svg>
+
diff --git a/css/css-writing-modes/reference/svg-aliasing-002-ref.html b/css/css-writing-modes/reference/svg-aliasing-002-ref.html
new file mode 100644
index 0000000..be3b7d0
--- /dev/null
+++ b/css/css-writing-modes/reference/svg-aliasing-002-ref.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Writing modes test reference</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<p>The test passes if you can see “1 2 3” (without the quotation marks) 3 times below going top-to-bottom.
+If any of them is reversed (going bottom to top), the test fails.</p>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   width="300"
+   height="600">
+<g>
+  <text x="50" y="50" style="writing-mode: vertical-rl; fill: blue;">1 2 3</text>
+</g>
+<g>
+  <text x="125" y="50" style="writing-mode: vertical-rl; fill: blue;">1 2 3</text>
+</g>
+<g>
+  <text x="200" y="80" style="writing-mode: vertical-rl; direction: rtl; fill: blue;">3 2 1</text>
+</g>
+</svg>
+
diff --git a/css/css-writing-modes/reference/text-combine-upright-value-single-character.html b/css/css-writing-modes/reference/text-combine-upright-value-single-character.html
index d25602d..aeda4e4 100644
--- a/css/css-writing-modes/reference/text-combine-upright-value-single-character.html
+++ b/css/css-writing-modes/reference/text-combine-upright-value-single-character.html
@@ -7,7 +7,7 @@
 <style>
 @font-face {
   font-family: tcu-font;
-  src: url("support/tcu-font.woff");
+  src: url("/fonts/tcu-font.woff");
 }
 
 .test {
diff --git a/css/css-writing-modes/row-progression-slr-023.xht b/css/css-writing-modes/row-progression-slr-023.xht
index b907f47..bf0e552 100644
--- a/css/css-writing-modes/row-progression-slr-023.xht
+++ b/css/css-writing-modes/row-progression-slr-023.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-lr - table rows progression</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/row-progression-slr-029.xht b/css/css-writing-modes/row-progression-slr-029.xht
index 8f570da..a0c580c 100644
--- a/css/css-writing-modes/row-progression-slr-029.xht
+++ b/css/css-writing-modes/row-progression-slr-029.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-lr - table rows progression (thead, 2 tbodies, tfoot)</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/row-progression-srl-022.xht b/css/css-writing-modes/row-progression-srl-022.xht
index 7ba0a90..d21a70a 100644
--- a/css/css-writing-modes/row-progression-srl-022.xht
+++ b/css/css-writing-modes/row-progression-srl-022.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-rl - table rows progression</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/row-progression-srl-028.xht b/css/css-writing-modes/row-progression-srl-028.xht
index 748b524..6bfe386 100644
--- a/css/css-writing-modes/row-progression-srl-028.xht
+++ b/css/css-writing-modes/row-progression-srl-028.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sideways-rl - table rows progression (thead, 2 tbodies, tfoot)</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/row-progression-vlr-009.xht b/css/css-writing-modes/row-progression-vlr-009.xht
index dbe1613..64ec1cd 100644
--- a/css/css-writing-modes/row-progression-vlr-009.xht
+++ b/css/css-writing-modes/row-progression-vlr-009.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: vertical-lr - table rows progression (thead, 2 tbodies, tfoot)</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#writing-mode" title="3.1 Block Flow Direction: the writing-mode property" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-001.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-001.xht
index 1f2d1f1..4a651f8 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-001.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-001.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-001-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-003.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-003.xht
index 7e5b672..f8fe8bf 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-003.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-003.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-003-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-004.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-004.xht
index 859d704..2111a8b 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-004.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-004-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-006.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-006.xht
index dde973c..52bdd50 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-006.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-006.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-006-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-007.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-007.xht
index d76033e..57d3975 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-007.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-007.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-007-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-008.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-008.xht
index 1a0d496..e340adb 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-008.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-008.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-008-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-009.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-009.xht
index 1749520..c156228 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-009.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-009.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-003-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-010.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-010.xht
index 716a81a..6d5eb25 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-010.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-010.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-010-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-011.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-011.xht
index a7d0cef..31ada5d 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-011.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-011.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-011-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-012.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-012.xht
index d838466..ce597bd 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-012.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-012.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-006-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-013.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-013.xht
index 645746c..8a8a4f3 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-013.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-013.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-013-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-015.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-015.xht
index cab989a..9a92dcb 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-015.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-015.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-015-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-016.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-016.xht
index 57059d2..fae31d7 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-016.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-016.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-016-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-018.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-018.xht
index 8502cf0..25a2626 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-018.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-018.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-019.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-019.xht
index 5490888..05a6479 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-019.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-019.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-019-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-020.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-020.xht
index 1667163..f950c77 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-020.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-020.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-020-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-021.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-021.xht
index ef18ccb..94e52c4 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-021.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-021.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-015-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-022.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-022.xht
index c945168..39bc330 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-022.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-022.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-022-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-023.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-023.xht
index 21e84bd..4d51832 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-023.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-023.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-023-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vlr-024.xht b/css/css-writing-modes/sizing-orthog-htb-in-vlr-024.xht
index 125ce02..a0e1071 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vlr-024.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vlr-024.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-lr' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-001.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-001.xht
index 94f5128..2e9fe94 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-001.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-001.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-001-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-13T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-003.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-003.xht
index a59172d..828c1e5 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-003.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-003.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-003-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-19T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-004.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-004.xht
index 9ac176a..4a20cd0 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-004.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-004-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-19T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-006.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-006.xht
index c1e3f57..4328830 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-006.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-006.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-006-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-19T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-007.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-007.xht
index 7bda42b..203883d 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-007.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-007.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-007-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-008.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-008.xht
index 9b214c9..f9a3a15 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-008.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-008.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-008-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-009.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-009.xht
index d262bb6..36d7d01 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-009.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-009.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-003-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-19T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-010.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-010.xht
index 842ab97..2defea2 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-010.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-010.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-010-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-19T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-011.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-011.xht
index c843969..3d03d10 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-011.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-011.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-011-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-012.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-012.xht
index a218bdf..8e43062 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-012.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-012.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-006-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-013.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-013.xht
index 1fb1eac..74ce5be 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-013.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-013.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-013-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-015.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-015.xht
index 8c4193c..b616855 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-015.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-015.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-015-ref.xht" />
 
 
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-016.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-016.xht
index 20ec773..a9e5dbc 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-016.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-016.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vlr-016-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-018.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-018.xht
index c0a77d8..5d28446 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-018.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-018.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside auto-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-019.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-019.xht
index 6b168bf..38ea05e 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-019.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-019.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-019-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-020.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-020.xht
index c3a6fa9..0bb3847 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-020.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-020.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-020-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-021.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-021.xht
index 5e8acd5..23555fb 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-021.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-021.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-015-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-022.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-022.xht
index 6c30cf7..1d14778 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-022.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-022.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-022-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-023.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-023.xht
index 47ed5af..c2b6176 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-023.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-023.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-023-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-htb-in-vrl-024.xht b/css/css-writing-modes/sizing-orthog-htb-in-vrl-024.xht
index 7b06bd8..811110e 100644
--- a/css/css-writing-modes/sizing-orthog-htb-in-vrl-024.xht
+++ b/css/css-writing-modes/sizing-orthog-htb-in-vrl-024.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'horizontal-tb' block with 'auto' inline size inside definite-sized 'vertical-rl' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-htb-in-vrl-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-12-20T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-001.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-001.xht
index 49cfa83..1136196 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-001.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-001.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-001-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-003.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-003.xht
index 357c517..4f26ccf 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-003.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-003.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-003-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-004.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-004.xht
index 318c97c..0454836 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-004.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-004-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-006.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-006.xht
index 366749f..c58868f 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-006.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-006.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-006-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-007.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-007.xht
index b52ae55..54e3828 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-007.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-007.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-007-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-008.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-008.xht
index 187a48d..6f01a71 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-008.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-008.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-008-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-009.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-009.xht
index 8f81bce..47c4c1e 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-009.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-009.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-009-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-010.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-010.xht
index db786b2..de7495f 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-010.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-010.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-010-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-011.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-011.xht
index e973ee3..397781c 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-011.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-011.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-011-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-012.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-012.xht
index e1eb5f8..58149a1 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-012.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-012.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-012-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-013.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-013.xht
index cd42dd6..844b28c 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-013.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-013.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-013-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-015.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-015.xht
index 9d9de14..71b6fa6 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-015.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-015.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-015-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-016.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-016.xht
index e26e14d..341d4b6 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-016.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-016.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-016-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-018.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-018.xht
index 51c0bdd..c4d6f12 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-018.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-018.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-019.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-019.xht
index 86760b0..09c5923 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-019.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-019.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-019-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-020.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-020.xht
index 1b8d5ab..afa9a73 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-020.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-020.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-020-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-021.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-021.xht
index 1b37060..8bbf256 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-021.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-021.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-015-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-022.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-022.xht
index bb1a9af..9e15c25 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-022.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-022.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-022-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-023.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-023.xht
index 9cfb956..566da43 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-023.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-023.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-023-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vlr-in-htb-024.xht b/css/css-writing-modes/sizing-orthog-vlr-in-htb-024.xht
index 156d9e6..5ca1c96 100644
--- a/css/css-writing-modes/sizing-orthog-vlr-in-htb-024.xht
+++ b/css/css-writing-modes/sizing-orthog-vlr-in-htb-024.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-lr' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vlr-in-htb-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-10-04T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-001.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-001.xht
index c19ab6c..0d41350 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-001.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-001.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-001-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-02T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-003.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-003.xht
index a610020..22209ba 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-003.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-003.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-003-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-08T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-004.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-004.xht
index 478a6a4..ee1d59d 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-004.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-004.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-004-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-006.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-006.xht
index 80955af..d3576e9 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-006.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-006.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-006-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-007.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-007.xht
index fb3c91d..505d046 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-007.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-007.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-007-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-008.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-008.xht
index 7df2838..1d71744 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-008.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-008.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-008-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-009.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-009.xht
index 399ce8f..0073da6 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-009.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-009.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-009-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-010.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-010.xht
index 5b0f4fa..71bbd3e 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-010.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-010.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-010-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-011.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-011.xht
index fa2ee6b..77bcedc 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-011.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-011.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-011-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-012.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-012.xht
index 1504ed9..5ba8fb9 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-012.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-012.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-012-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-15T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-013.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-013.xht
index 4ccc0d0..997174e 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-013.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-013.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-013-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-015.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-015.xht
index 4b88733..a61c675 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-015.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-015.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-015-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-016.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-016.xht
index bbe2cdb..1e6382c 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-016.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-016.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-016-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-018.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-018.xht
index ab34193..35e9b19 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-018.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-018.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside auto-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-019.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-019.xht
index e4cebf2..0b29bc9 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-019.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-019.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-019-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-020.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-020.xht
index fc88acb..41c4ba9 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-020.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-020.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-020-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-285T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-021.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-021.xht
index 44e085a..9e2e626 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-021.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-021.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-015-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-022.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-022.xht
index a47986d..1feb2c7 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-022.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-022.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-022-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-023.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-023.xht
index fbc6dea..d0c40ba 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-023.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-023.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-023-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthog-vrl-in-htb-024.xht b/css/css-writing-modes/sizing-orthog-vrl-in-htb-024.xht
index c4c0b03..d691383 100644
--- a/css/css-writing-modes/sizing-orthog-vrl-in-htb-024.xht
+++ b/css/css-writing-modes/sizing-orthog-vrl-in-htb-024.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: sizing of orthogonal 'vertical-rl' block with 'auto' inline size inside definite-sized 'horizontal-tb' containing block</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#auto-multicol" title="7.3.2. Auto-sizing Block Containers in Orthogonal Flows" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto" />
   <link rel="match" href="sizing-orthog-vrl-in-htb-018-ref.xht" />
 
   <meta name="DC.date.created" content="2016-09-28T09:54:03+11:00" scheme="W3CDTF" />
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html
new file mode 100644
index 0000000..d8edfb6
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 50px;
+  height: 100px;
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html
new file mode 100644
index 0000000..d5a77cd
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-001-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalLR element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's height should be 100px. */
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html
new file mode 100644
index 0000000..98e6d77
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 50px;
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html
new file mode 100644
index 0000000..55e266f
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-002-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalRL element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's height should be 100px. */
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html
new file mode 100644
index 0000000..f035f59
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 20px;
+  width: 460px;
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html
new file mode 100644
index 0000000..d722504
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-003-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's width should be 460px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html
new file mode 100644
index 0000000..2cd59eb
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-003-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+  writing-mode: vertical-rl;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's width should be 460px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html
new file mode 100644
index 0000000..40e4412
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 20px;
+  height: 460px;
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html
new file mode 100644
index 0000000..7980142
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-005-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalLR element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's height should be 460px. */
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html
new file mode 100644
index 0000000..3b25252
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 20px;
+  height: 460px;
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html
new file mode 100644
index 0000000..f945dec
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-006-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalRL element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's height should be 460px. */
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html
new file mode 100644
index 0000000..02dfe07
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 50px;
+  width: 100px;
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html
new file mode 100644
index 0000000..93f5053
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-007-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's width should be 100px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html b/css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html
new file mode 100644
index 0000000..39bf4f5
--- /dev/null
+++ b/css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-007-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+  writing-mode: vertical-rl;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's width should be 100px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/css/css-writing-modes/sizing-percentages-replaced-orthogonal-001-ref.html b/css/css-writing-modes/sizing-percentages-replaced-orthogonal-001-ref.html
new file mode 100644
index 0000000..0fd818c
--- /dev/null
+++ b/css/css-writing-modes/sizing-percentages-replaced-orthogonal-001-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reftest Reference: Percentage size on orthogonal replaced elements</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  display: inline-block;
+  border: solid 5px black;
+  margin: 10px;
+  vertical-align: top;
+  width: 200px;
+  height: 100px;
+}
+
+img {
+  display: block;
+  width: 200px;
+  height: 100px;
+}
+
+</style>
+
+<p>The test passes if you see four filled lime rectangles with black border and <strong>no red</strong>.</p>
+
+<div class="container">
+  <img src="support/100x100-lime.png" />
+</div>
+
+<div class="container">
+  <img src="support/100x100-lime.png" />
+</div>
+
+<div class="container">
+  <img src="support/100x100-lime.png" />
+</div>
+
+<div class="container">
+  <img src="support/100x100-lime.png" />
+</div>
diff --git a/css/css-writing-modes/sizing-percentages-replaced-orthogonal-001.html b/css/css-writing-modes/sizing-percentages-replaced-orthogonal-001.html
new file mode 100644
index 0000000..6829920
--- /dev/null
+++ b/css/css-writing-modes/sizing-percentages-replaced-orthogonal-001.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Percentage size on orthogonal replaced elements</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="sizing-percentages-replaced-orthogonal-001-ref.html">
+<meta name="assert" content="Checks that orthogonal replaced elements resolve properly their percentage sizes against the expected axis from their containing block.">
+<style>
+.container {
+  display: inline-block;
+  border: solid 5px black;
+  margin: 10px;
+  vertical-align: top;
+  width: 200px;
+  height: 100px;
+  background: red;
+}
+
+img {
+  display: block;
+  width: 100%;
+  height: 100%;
+}
+
+.horizontalTB { writing-mode: horizontal-tb; }
+.verticalLR { writing-mode: vertical-lr; }
+.verticalRL {  writing-mode: vertical-rl; }
+</style>
+
+<p>The test passes if you see four filled lime rectangles with black border and <strong>no red</strong>.</p>
+
+<div class="container horizontalTB">
+  <img class="verticalLR" src="support/100x100-lime.png" />
+</div>
+
+<div class="container horizontalTB">
+  <img class="verticalRL" src="support/100x100-lime.png" />
+</div>
+
+<div class="container verticalLR">
+  <img class="horizontalTB" src="support/100x100-lime.png" />
+</div>
+
+<div class="container verticalRL">
+  <img class="horizontalTB" src="support/100x100-lime.png" />
+</div>
diff --git a/css/css-writing-modes/support/adobe-fonts/CSSFWOrientationTest.otf b/css/css-writing-modes/support/adobe-fonts/CSSFWOrientationTest.otf
deleted file mode 100644
index f9cbd2b..0000000
--- a/css/css-writing-modes/support/adobe-fonts/CSSFWOrientationTest.otf
+++ /dev/null
Binary files differ
diff --git a/css/css-writing-modes/support/adobe-fonts/CSSHWOrientationTest.otf b/css/css-writing-modes/support/adobe-fonts/CSSHWOrientationTest.otf
deleted file mode 100644
index 84ad57b..0000000
--- a/css/css-writing-modes/support/adobe-fonts/CSSHWOrientationTest.otf
+++ /dev/null
Binary files differ
diff --git a/css/css-writing-modes/support/adobe-fonts/LICENSE b/css/css-writing-modes/support/adobe-fonts/LICENSE
deleted file mode 100644
index ac5f0b0..0000000
--- a/css/css-writing-modes/support/adobe-fonts/LICENSE
+++ /dev/null
@@ -1,31 +0,0 @@
-CSS Half-Width Orientation Test and CSS Full-Width Orientation Test are released under the SIL Open Font License - please read it carefully and do not download the fonts unless you agree to the the terms of the license:
-Copyright © 2013 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Names CSS Half-Width Orientation Test and CSS Full-Width Orientation Test
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
-The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
-"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
-"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
-"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
-"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
-1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
-2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
-3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
-5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/css/css-writing-modes/support/adobe-fonts/README.md b/css/css-writing-modes/support/adobe-fonts/README.md
deleted file mode 100644
index 61f6870..0000000
--- a/css/css-writing-modes/support/adobe-fonts/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-CSS Orientation Test
-====
-
-Overview
-----
-CSS Orientation Test are special-purpose OpenType fonts. This open source project provides all of the source files
-that were used to build these OpenType fonts by using the AFDKO *makeotf* tool.
-
-Getting Involved
-----
-Send suggestions for changes to the CSS Orientation Test project maintainer, lunde@adobe.com, for consideration.
-
-Building
-====
-
-Pre-built font binaries
-----
-The installable font resources (font binaries) are not part of the source files.
-They are available at  https://github.com/adobe-fonts/css-orientation-test/
-The latest version of the font binaries is 1.005 (April 4th 2015).
-
-
-Requirements
-----
-
-For building binary font files from source, installation of the
-[Adobe Font Development Kit for OpenType](http://www.adobe.com/devnet/opentype/afdko.html) (AFDKO)
-is necessary. The AFDKO tools are widely used for font development today, and are part of most font editor applications.
-
-Building the fonts
-----
-
-The key to building OpenType fonts is *makeotf*, which is part of AFDKO. Information and usage instructions can be found
-by executing *makeotf -h*.
-
-In this repository, all necessary files are in place for building the OpenType fonts. For example, build a binary OTF font
-for the full-width version like this, which also includes a post-process for inserting a "stub" 'DSIG' table:
-
-    % makeotf -f cidfont.ps -r -ch UnicodeAll-UTF32-H
-    % sfntedit -a DSIG=DSIG.bin CSSFWOrientationTest.otf
-    % sfntedit -f CSSFWOrientationTest.otf
-
-That is all.
diff --git a/css/css-writing-modes/support/mplus-1p-regular.woff b/css/css-writing-modes/support/mplus-1p-regular.woff
deleted file mode 100644
index 42cfff6..0000000
--- a/css/css-writing-modes/support/mplus-1p-regular.woff
+++ /dev/null
Binary files differ
diff --git a/css/css-writing-modes/support/sileot-webfont.woff b/css/css-writing-modes/support/sileot-webfont.woff
deleted file mode 100644
index 8154757..0000000
--- a/css/css-writing-modes/support/sileot-webfont.woff
+++ /dev/null
Binary files differ
diff --git a/css/css-writing-modes/support/tcu-font.woff b/css/css-writing-modes/support/tcu-font.woff
deleted file mode 100644
index c880aaa..0000000
--- a/css/css-writing-modes/support/tcu-font.woff
+++ /dev/null
Binary files differ
diff --git a/css/css-writing-modes/svg-aliasing-001.html b/css/css-writing-modes/svg-aliasing-001.html
new file mode 100644
index 0000000..bc32f20
--- /dev/null
+++ b/css/css-writing-modes/svg-aliasing-001.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>SVG writing modes values parsed as aliases</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#svg-writing-mode">
+<link rel="match" href="reference/svg-aliasing-001-ref.html"
+<meta name="assert" content="The legacy SVG writing mode values are parsed as simple aliases of the newer ones, and do not have side effects on bidi.">
+<meta name="flags" content="may"> <!-- "may" because the support for these values is optional-->
+
+<p>The test passes if you can see “1 2 3” 6 times below (without the quotation marks). If any of them is reversed (“3 2 1”) the test fails.</p>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   width="300"
+   height="600">
+<g style="direction: ltr">
+  <text x="0" y="50" style="writing-mode: lr; fill: blue;">1 2 3</text>
+  <text x="0" y="150" style="writing-mode: rl; fill: blue;">1 2 3</text>
+</g>
+<g>
+  <text x="150" y="50" style="writing-mode: lr; fill: blue;">1 2 3</text>
+  <text x="150" y="150" style="writing-mode: rl; fill: blue;">1 2 3</text>
+</g>
+<g style="direction: rtl">
+  <text x="300" y="50" style="writing-mode: lr; fill: blue;">3 2 1</text>
+  <text x="300" y="150" style="writing-mode: rl; fill: blue;">3 2 1</text>
+</g>
+</svg>
+
diff --git a/css/css-writing-modes/svg-aliasing-002.html b/css/css-writing-modes/svg-aliasing-002.html
new file mode 100644
index 0000000..c1b2627
--- /dev/null
+++ b/css/css-writing-modes/svg-aliasing-002.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>SVG writing modes values parsed as aliases</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#svg-writing-mode">
+<link rel="match" href="reference/svg-aliasing-002-ref.html"
+<meta name="assert" content="The legacy SVG writing mode values are parsed as simple aliases of the newer ones, and do not have side effects on bidi.">
+<meta name="flags" content="may"> <!-- "may" because the support for these values is optional-->
+
+<p>The test passes if you can see “1 2 3” (without the quotation marks) 3 times below going top-to-bottom.
+If any of them is reversed (going bottom to top), the test fails.</p>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   width="300"
+   height="600">
+<g style="direction: ltr">
+  <text x="50" y="50" style="writing-mode: tb; fill: blue;">1 2 3</text>
+</g>
+<g>
+  <text x="125" y="50" style="writing-mode: tb; fill: blue;">1 2 3</text>
+</g>
+<g style="direction: rtl">
+  <text x="200" y="80" style="writing-mode: tb; fill: blue;">3 2 1</text>
+</g>
+</svg>
+
diff --git a/css/css-writing-modes/table-column-order-slr-007.xht b/css/css-writing-modes/table-column-order-slr-007.xht
index 8d6f3fd..f1829e3 100644
--- a/css/css-writing-modes/table-column-order-slr-007.xht
+++ b/css/css-writing-modes/table-column-order-slr-007.xht
@@ -8,8 +8,8 @@
 
   <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-22 -->
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#direction" title="2.1 Specifying Directionality: the direction property" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#vertical-layout" title="7.1 Principles of Layout in Vertical Writing Modes" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#direction" title="2.1 Specifying Directionality: the direction property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#vertical-layout" title="7.1 Principles of Layout in Vertical Writing Modes" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/table-column-order-srl-006.xht b/css/css-writing-modes/table-column-order-srl-006.xht
index ce1d8ff..38cfa2f 100644
--- a/css/css-writing-modes/table-column-order-srl-006.xht
+++ b/css/css-writing-modes/table-column-order-srl-006.xht
@@ -8,8 +8,8 @@
 
   <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
   <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-22 -->
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#direction" title="2.1 Specifying Directionality: the direction property" />
-  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#vertical-layout" title="7.1 Principles of Layout in Vertical Writing Modes" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#direction" title="2.1 Specifying Directionality: the direction property" />
+  <link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#vertical-layout" title="7.1 Principles of Layout in Vertical Writing Modes" />
   <link rel="match" href="block-flow-direction-001-ref.xht" />
 
   <meta content="ahem" name="flags" />
diff --git a/css/css-writing-modes/table-progression-slr-001.html b/css/css-writing-modes/table-progression-slr-001.html
index 45970fb..25050b8 100644
--- a/css/css-writing-modes/table-progression-slr-001.html
+++ b/css/css-writing-modes/table-progression-slr-001.html
@@ -5,8 +5,8 @@
 <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
 <link rel="match" href="table-progression-slr-001-ref.html">
 <meta name="assert" content="This test checks that sideways-lr tables order rows/rowgroups left to right and cells bottom-to-top (LTR) or top-to-bottom (RTL) per 'direction'. This test also checks that 'writing-mode' and 'direction' do not apply to table rows and row groups.">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#direction" title="2.1 Specifying Directionality: the 'direction' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#direction" title="2.1 Specifying Directionality: the 'direction' property">
 
 <style>
   .test {
diff --git a/css/css-writing-modes/table-progression-slr-002.html b/css/css-writing-modes/table-progression-slr-002.html
index 6c4dd6a..1525e24 100644
--- a/css/css-writing-modes/table-progression-slr-002.html
+++ b/css/css-writing-modes/table-progression-slr-002.html
@@ -3,8 +3,8 @@
 <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
 <link rel="match" href="table-progression-002-ref.html">
 <meta name="assert" content="This test checks that sideways-lr tables order columns bottom-to-top (LTR) or top-to-bottom (RTL) per the table's 'direction'. This test also checks that 'writing-mode' and 'direction' do not apply to table columns and column groups.">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#direction" title="2.1 Specifying Directionality: the 'direction' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#direction" title="2.1 Specifying Directionality: the 'direction' property">
 
 <style>
   .test {
diff --git a/css/css-writing-modes/table-progression-srl-001.html b/css/css-writing-modes/table-progression-srl-001.html
index 1ae2dd2..b74b2ca 100644
--- a/css/css-writing-modes/table-progression-srl-001.html
+++ b/css/css-writing-modes/table-progression-srl-001.html
@@ -5,8 +5,8 @@
 <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
 <link rel="match" href="table-progression-001-ref.html">
 <meta name="assert" content="This test checks that sideways-rl tables order rows/rowgroups right to left and cells top-to-bottom (LTR) or bottom-to-top (RTL) per 'direction'. This test also checks that 'writing-mode' and 'direction' do not apply to table rows and row groups.">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#direction" title="2.1 Specifying Directionality: the 'direction' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#direction" title="2.1 Specifying Directionality: the 'direction' property">
 
 <style>
   .test {
diff --git a/css/css-writing-modes/table-progression-srl-002.html b/css/css-writing-modes/table-progression-srl-002.html
index d552244..7d5df32 100644
--- a/css/css-writing-modes/table-progression-srl-002.html
+++ b/css/css-writing-modes/table-progression-srl-002.html
@@ -5,8 +5,8 @@
 <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
 <link rel="match" href="table-progression-002-ref.html">
 <meta name="assert" content="This test checks that sideways-rl tables order columns top-to-bottom (LTR) or bottom-to-top (RTL) per the table's 'direction'. This test also checks that 'writing-mode' and 'direction' do not apply to table columns and column groups.">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#direction" title="2.1 Specifying Directionality: the 'direction' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#block-flow" title="3.1 Block Flow Direction: the 'writing-mode' property">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#direction" title="2.1 Specifying Directionality: the 'direction' property">
 
 <style>
   .test {
diff --git a/css/css-writing-modes/text-baseline-slr-009.xht b/css/css-writing-modes/text-baseline-slr-009.xht
index 6b478a5..a458bab 100644
--- a/css/css-writing-modes/text-baseline-slr-009.xht
+++ b/css/css-writing-modes/text-baseline-slr-009.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: text baseline alignment - alphabetical alignment with vertical layout</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-05 -->
-    <link rel="help" title="4.2. Text Baseline" href="http://www.w3.org/TR/css-writing-modes-3/#text-baselines" />
+    <link rel="help" title="4.2. Text Baseline" href="http://www.w3.org/TR/css-writing-modes-4/#text-baselines" />
     <link rel="match" href="text-baseline-slr-009-ref.xht" />
     <meta name="assert" content="When 'writing-mode' is 'sideways-lr', then the alphabetic baseline is used as the dominant baseline." />
     <meta name="flags" content="ahem" />
diff --git a/css/css-writing-modes/text-baseline-srl-008.xht b/css/css-writing-modes/text-baseline-srl-008.xht
index b7300bc..f9fc267 100644
--- a/css/css-writing-modes/text-baseline-srl-008.xht
+++ b/css/css-writing-modes/text-baseline-srl-008.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: text baseline alignment - alphabetical alignment with vertical layout</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-05 -->
-    <link rel="help" title="4.2. Text Baseline" href="http://www.w3.org/TR/css-writing-modes-3/#text-baselines" />
+    <link rel="help" title="4.2. Text Baseline" href="http://www.w3.org/TR/css-writing-modes-4/#text-baselines" />
     <link rel="match" href="text-baseline-vrl-006-ref.xht" />
     <meta name="assert" content="When 'writing-mode' is 'sideways-rl', then the alphabetic baseline is used as the dominant baseline." />
     <meta name="flags" content="ahem" />
diff --git a/css/css-writing-modes/text-combine-upright-line-breaking-rules-001-ref.html b/css/css-writing-modes/text-combine-upright-line-breaking-rules-001-ref.html
index aa085fa..e1b7b34 100644
--- a/css/css-writing-modes/text-combine-upright-line-breaking-rules-001-ref.html
+++ b/css/css-writing-modes/text-combine-upright-line-breaking-rules-001-ref.html
@@ -8,7 +8,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .vrl {
     border:solid;
diff --git a/css/css-writing-modes/text-combine-upright-line-breaking-rules-001.html b/css/css-writing-modes/text-combine-upright-line-breaking-rules-001.html
index 88a3430..1cf8a7d 100644
--- a/css/css-writing-modes/text-combine-upright-line-breaking-rules-001.html
+++ b/css/css-writing-modes/text-combine-upright-line-breaking-rules-001.html
@@ -11,7 +11,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .vrl {
     border:solid;
diff --git a/css/css-writing-modes/text-combine-upright-parsing-digits-001.html b/css/css-writing-modes/text-combine-upright-parsing-digits-001.html
index bcb6c14..4f083c8 100644
--- a/css/css-writing-modes/text-combine-upright-parsing-digits-001.html
+++ b/css/css-writing-modes/text-combine-upright-parsing-digits-001.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: parsing text-combine-upright for digits</title>
 <link rel="author" title="Masataka Yakura" href="http://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <meta name="assert" content="text-combine-upright supports `none`, `all`, `digits`, and `digits` followed by a digit in the range from 2 to 4.">
 <meta name="flags" content="dom">
 <script src="/resources/testharness.js"></script>
diff --git a/css/css-writing-modes/text-combine-upright-parsing-digits-002.html b/css/css-writing-modes/text-combine-upright-parsing-digits-002.html
index 7a375d6..327ebdd 100644
--- a/css/css-writing-modes/text-combine-upright-parsing-digits-002.html
+++ b/css/css-writing-modes/text-combine-upright-parsing-digits-002.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: parsing text-combine-upright with digits plus calc() computing to valid digits</title>
 <link rel="author" title="Masataka Yakura" href="http://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="help" href="http://www.w3.org/TR/css-values-3/#calc-notation">
 <meta name="assert" content="User Agents support text-combine-upright with values `digits calc(...)` if the expression inside `calc()` computes to a valid integer, which is either 2, 3, or 4.">
 <meta name="flags" content="dom">
diff --git a/css/css-writing-modes/text-combine-upright-parsing-invalid-001.html b/css/css-writing-modes/text-combine-upright-parsing-invalid-001.html
index aabe7a7..beb5410 100644
--- a/css/css-writing-modes/text-combine-upright-parsing-invalid-001.html
+++ b/css/css-writing-modes/text-combine-upright-parsing-invalid-001.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: parsing text-combine-upright with invalid values</title>
 <link rel="author" title="Masataka Yakura" href="http://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <meta name="assert" content="text-combine-upright doesn't support undefined keywords and out-of-range integer, and numbers.">
 <meta name="flags" content="dom invalid">
 <script src="/resources/testharness.js"></script>
diff --git a/css/css-writing-modes/text-combine-upright-parsing-invalid-002.html b/css/css-writing-modes/text-combine-upright-parsing-invalid-002.html
index ed5ce4e..08a0978 100644
--- a/css/css-writing-modes/text-combine-upright-parsing-invalid-002.html
+++ b/css/css-writing-modes/text-combine-upright-parsing-invalid-002.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: parsing text-combine-upright with digits plus calc() computing to invalid digits</title>
 <link rel="author" title="Masataka Yakura" href="http://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="help" href="http://www.w3.org/TR/css-values-3/#calc-notation">
 <meta name="assert" content="User Agents ignore text-combine-upright with values `digits calc(...)` if the expression inside `calc()` computes to either numbers or out-of-range integers.">
 <meta name="flags" content="dom invalid">
diff --git a/css/css-writing-modes/text-combine-upright-value-all-001.html b/css/css-writing-modes/text-combine-upright-value-all-001.html
index cd6f35c..a0e85ec 100644
--- a/css/css-writing-modes/text-combine-upright-value-all-001.html
+++ b/css/css-writing-modes/text-combine-upright-value-all-001.html
@@ -11,7 +11,7 @@
 <style>
 @font-face {
   font-family: tcu-font;
-  src: url("support/tcu-font.woff");
+  src: url("/fonts/tcu-font.woff");
 }
 
 .test {
diff --git a/css/css-writing-modes/text-combine-upright-value-digits2-001.html b/css/css-writing-modes/text-combine-upright-value-digits2-001.html
index ab77a14..a765d2d 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits2-001.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits2-001.html
@@ -4,14 +4,14 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 2</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/text-combine-upright-value-single-character.html">
 <meta name="assert" content="the 'digits' values in text-combine-upright rotates the composition even if there is only one character inside tate-chu-yoko.">
 <meta name="flags" content="">
 <style>
 @font-face {
   font-family: tcu-font;
-  src: url("support/tcu-font.woff");
+  src: url("/fonts/tcu-font.woff");
 }
 
 .test {
diff --git a/css/css-writing-modes/text-combine-upright-value-digits2-002.html b/css/css-writing-modes/text-combine-upright-value-digits2-002.html
index ab8d216..5a9784e 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits2-002.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits2-002.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 2 + two digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x1-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x1-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 2' combines two ASCII digits horizontally.">
diff --git a/css/css-writing-modes/text-combine-upright-value-digits2-003.html b/css/css-writing-modes/text-combine-upright-value-digits2-003.html
index 2a56d3e..dd73b55 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits2-003.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits2-003.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 2 + three digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x3-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x3-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 2' does not combine digits that are greater than two.">
diff --git a/css/css-writing-modes/text-combine-upright-value-digits3-001.html b/css/css-writing-modes/text-combine-upright-value-digits3-001.html
index bec7de9..227ec7e 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits3-001.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits3-001.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 3 + two digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x1-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x1-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 3' combines two ASCII digits horizontally.">
diff --git a/css/css-writing-modes/text-combine-upright-value-digits3-002.html b/css/css-writing-modes/text-combine-upright-value-digits3-002.html
index 18da1a2..d1a352d 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits3-002.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits3-002.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 3 + three digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x1-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x1-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 3' combines three ASCII digits horizontally.">
diff --git a/css/css-writing-modes/text-combine-upright-value-digits3-003.html b/css/css-writing-modes/text-combine-upright-value-digits3-003.html
index 11a6932..f8f8371 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits3-003.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits3-003.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 3 + four digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x4-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x4-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 3' does not combine digits that are greater than three.">
diff --git a/css/css-writing-modes/text-combine-upright-value-digits4-001.html b/css/css-writing-modes/text-combine-upright-value-digits4-001.html
index 5a9f0e4..21a2a0d 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits4-001.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits4-001.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 4 + three digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x1-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x1-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 4' combines three ASCII digits horizontally.">
diff --git a/css/css-writing-modes/text-combine-upright-value-digits4-002.html b/css/css-writing-modes/text-combine-upright-value-digits4-002.html
index d58e555..1481345 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits4-002.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits4-002.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 4 + four digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x1-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x1-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 4' combines four ASCII digits horizontally.">
diff --git a/css/css-writing-modes/text-combine-upright-value-digits4-003.html b/css/css-writing-modes/text-combine-upright-value-digits4-003.html
index 4b30058..8dd3cd5 100644
--- a/css/css-writing-modes/text-combine-upright-value-digits4-003.html
+++ b/css/css-writing-modes/text-combine-upright-value-digits4-003.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Writing Modes: text-combine-upright: digits 4 + five digits</title>
 <link rel="author" title="Masataka Yakura" href="https://google.com/+MasatakaYakura">
-<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#text-combine-upright">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-4/#text-combine-upright">
 <link rel="match" href="reference/vertical-ahem-1x5-ref.html">
 <link rel="mismatch" href="reference/horizontal-ahem-1x5-notref.html">
 <meta name="assert" content="'text-combine-upright: digits 4' does not combine digits that are greater than four.">
diff --git a/css/css-writing-modes/text-orientation-010.xht b/css/css-writing-modes/text-orientation-010.xht
index 97a400f..39da556 100644
--- a/css/css-writing-modes/text-orientation-010.xht
+++ b/css/css-writing-modes/text-orientation-010.xht
@@ -12,7 +12,7 @@
 				@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 				}
 
 				div
diff --git a/css/css-writing-modes/text-orientation-011.xht b/css/css-writing-modes/text-orientation-011.xht
index 931f42c..fb32e97 100644
--- a/css/css-writing-modes/text-orientation-011.xht
+++ b/css/css-writing-modes/text-orientation-011.xht
@@ -17,7 +17,7 @@
   @font-face
     {
       font-family: "CSS Full-Width Orientation Test";
-      src: url("support/adobe-fonts/CSSFWOrientationTest.otf") format("opentype");
+      src: url("/fonts/adobe-fonts/CSSFWOrientationTest.otf") format("opentype");
     }
 
   div
diff --git a/css/css-writing-modes/text-orientation-012.xht b/css/css-writing-modes/text-orientation-012.xht
index 52ab523..59d815a 100644
--- a/css/css-writing-modes/text-orientation-012.xht
+++ b/css/css-writing-modes/text-orientation-012.xht
@@ -17,7 +17,7 @@
   @font-face
     {
       font-family: "mplus-1p-regular";
-      src: url("support/mplus-1p-regular.woff") format("woff");
+      src: url("/fonts/mplus-1p-regular.woff") format("woff");
     }
 
   div
diff --git a/css/css-writing-modes/text-orientation-mixed-slr-015.xht b/css/css-writing-modes/text-orientation-mixed-slr-015.xht
index b26e539..c1d57ef 100644
--- a/css/css-writing-modes/text-orientation-mixed-slr-015.xht
+++ b/css/css-writing-modes/text-orientation-mixed-slr-015.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-lr' - 'text-orientation: mixed' has no effect</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-3/#text-orientation" />
+  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-4/#text-orientation" />
 
   <meta name="flags" content="image" />
   <meta name="assert" content="This test checks that when 'writing-mode' is set to 'sideways-lr', then a 'text-orientation: mixed' declaration has no effect: the alphabetical baseline is used as the dominant baseline and all the glyphs remain rotated 90 degrees counter-clockwise. The typographic mode for 'sideways-lr' is horizontal and 'text-orientation' has no effect on boxes in horizontal typographic modes." />
@@ -16,7 +16,7 @@
   @font-face
     {
       font-family: "mplus-1p-regular";
-      src: url("support/mplus-1p-regular.woff") format("woff");
+      src: url("/fonts/mplus-1p-regular.woff") format("woff");
       /* filesize: 803300 bytes (784.5 KBytes) */
     }
 
diff --git a/css/css-writing-modes/text-orientation-mixed-srl-016-ref.xht b/css/css-writing-modes/text-orientation-mixed-srl-016-ref.xht
index 342f6ff..bae223a 100644
--- a/css/css-writing-modes/text-orientation-mixed-srl-016-ref.xht
+++ b/css/css-writing-modes/text-orientation-mixed-srl-016-ref.xht
@@ -12,7 +12,7 @@
   @font-face
     {
       font-family: "mplus-1p-regular";
-      src: url("support/mplus-1p-regular.woff") format("woff");
+      src: url("/fonts/mplus-1p-regular.woff") format("woff");
       /* filesize: 803300 bytes (784.5 KBytes) */
     }
 
diff --git a/css/css-writing-modes/text-orientation-mixed-srl-016.xht b/css/css-writing-modes/text-orientation-mixed-srl-016.xht
index ff5adf7..4fd928a 100644
--- a/css/css-writing-modes/text-orientation-mixed-srl-016.xht
+++ b/css/css-writing-modes/text-orientation-mixed-srl-016.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-rl' - 'text-orientation: mixed' has no effect</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-3/#text-orientation" />
+  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-4/#text-orientation" />
   <link rel="match" href="text-orientation-mixed-srl-016-ref.xht" />
 
   <meta name="flags" content="" />
@@ -17,7 +17,7 @@
   @font-face
     {
       font-family: "mplus-1p-regular";
-      src: url("support/mplus-1p-regular.woff") format("woff");
+      src: url("/fonts/mplus-1p-regular.woff") format("woff");
       /* filesize: 803300 bytes (784.5 KBytes) */
     }
 
diff --git a/css/css-writing-modes/text-orientation-mixed-vlr-100-ref.html b/css/css-writing-modes/text-orientation-mixed-vlr-100-ref.html
index 0b9ba9b..26e2cb7 100644
--- a/css/css-writing-modes/text-orientation-mixed-vlr-100-ref.html
+++ b/css/css-writing-modes/text-orientation-mixed-vlr-100-ref.html
@@ -5,7 +5,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-lr;
diff --git a/css/css-writing-modes/text-orientation-mixed-vlr-100.html b/css/css-writing-modes/text-orientation-mixed-vlr-100.html
index 35052c6..9e577f1 100644
--- a/css/css-writing-modes/text-orientation-mixed-vlr-100.html
+++ b/css/css-writing-modes/text-orientation-mixed-vlr-100.html
@@ -7,7 +7,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-lr;
diff --git a/css/css-writing-modes/text-orientation-mixed-vrl-002.xht b/css/css-writing-modes/text-orientation-mixed-vrl-002.xht
index c8f1021..506b9d7 100644
--- a/css/css-writing-modes/text-orientation-mixed-vrl-002.xht
+++ b/css/css-writing-modes/text-orientation-mixed-vrl-002.xht
@@ -12,7 +12,7 @@
 			@font-face
 				{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 					/* filesize: 803300 bytes (784.5 KBytes) */
 				}
 
diff --git a/css/css-writing-modes/text-orientation-mixed-vrl-100-ref.html b/css/css-writing-modes/text-orientation-mixed-vrl-100-ref.html
index e6e8981..77de672 100644
--- a/css/css-writing-modes/text-orientation-mixed-vrl-100-ref.html
+++ b/css/css-writing-modes/text-orientation-mixed-vrl-100-ref.html
@@ -5,7 +5,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-rl;
diff --git a/css/css-writing-modes/text-orientation-mixed-vrl-100.html b/css/css-writing-modes/text-orientation-mixed-vrl-100.html
index 38bc406..183217d 100644
--- a/css/css-writing-modes/text-orientation-mixed-vrl-100.html
+++ b/css/css-writing-modes/text-orientation-mixed-vrl-100.html
@@ -7,7 +7,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-rl;
diff --git a/css/css-writing-modes/text-orientation-script-001.html b/css/css-writing-modes/text-orientation-script-001.html
index 4194c8a..cc86fd4 100644
--- a/css/css-writing-modes/text-orientation-script-001.html
+++ b/css/css-writing-modes/text-orientation-script-001.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001a.html b/css/css-writing-modes/text-orientation-script-001a.html
index 1cfee2e..7c99433 100644
--- a/css/css-writing-modes/text-orientation-script-001a.html
+++ b/css/css-writing-modes/text-orientation-script-001a.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001b.html b/css/css-writing-modes/text-orientation-script-001b.html
index af220a0..3801352 100644
--- a/css/css-writing-modes/text-orientation-script-001b.html
+++ b/css/css-writing-modes/text-orientation-script-001b.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001c.html b/css/css-writing-modes/text-orientation-script-001c.html
index bf96879..956dd9d 100644
--- a/css/css-writing-modes/text-orientation-script-001c.html
+++ b/css/css-writing-modes/text-orientation-script-001c.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001d.html b/css/css-writing-modes/text-orientation-script-001d.html
index d24f830..5029828 100644
--- a/css/css-writing-modes/text-orientation-script-001d.html
+++ b/css/css-writing-modes/text-orientation-script-001d.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001e.html b/css/css-writing-modes/text-orientation-script-001e.html
index c4d8c7b..de1bfaa 100644
--- a/css/css-writing-modes/text-orientation-script-001e.html
+++ b/css/css-writing-modes/text-orientation-script-001e.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001f.html b/css/css-writing-modes/text-orientation-script-001f.html
index 78ae052..63711f9 100644
--- a/css/css-writing-modes/text-orientation-script-001f.html
+++ b/css/css-writing-modes/text-orientation-script-001f.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001g.html b/css/css-writing-modes/text-orientation-script-001g.html
index c48f6ab..fa577bd 100644
--- a/css/css-writing-modes/text-orientation-script-001g.html
+++ b/css/css-writing-modes/text-orientation-script-001g.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001h.html b/css/css-writing-modes/text-orientation-script-001h.html
index ed7c107..79953b8 100644
--- a/css/css-writing-modes/text-orientation-script-001h.html
+++ b/css/css-writing-modes/text-orientation-script-001h.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001i.html b/css/css-writing-modes/text-orientation-script-001i.html
index ee03d52..aae690b 100644
--- a/css/css-writing-modes/text-orientation-script-001i.html
+++ b/css/css-writing-modes/text-orientation-script-001i.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001j.html b/css/css-writing-modes/text-orientation-script-001j.html
index 2faaf02..645f57a 100644
--- a/css/css-writing-modes/text-orientation-script-001j.html
+++ b/css/css-writing-modes/text-orientation-script-001j.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001k.html b/css/css-writing-modes/text-orientation-script-001k.html
index 3f3301a..aa48f86 100644
--- a/css/css-writing-modes/text-orientation-script-001k.html
+++ b/css/css-writing-modes/text-orientation-script-001k.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001l.html b/css/css-writing-modes/text-orientation-script-001l.html
index 61f9c87..7a3a413 100644
--- a/css/css-writing-modes/text-orientation-script-001l.html
+++ b/css/css-writing-modes/text-orientation-script-001l.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001m.html b/css/css-writing-modes/text-orientation-script-001m.html
index c9da49b..f34a082 100644
--- a/css/css-writing-modes/text-orientation-script-001m.html
+++ b/css/css-writing-modes/text-orientation-script-001m.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001n.html b/css/css-writing-modes/text-orientation-script-001n.html
index 692e230..89af46a 100644
--- a/css/css-writing-modes/text-orientation-script-001n.html
+++ b/css/css-writing-modes/text-orientation-script-001n.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001o.html b/css/css-writing-modes/text-orientation-script-001o.html
index 81077d1..e7d9dd7 100644
--- a/css/css-writing-modes/text-orientation-script-001o.html
+++ b/css/css-writing-modes/text-orientation-script-001o.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001p.html b/css/css-writing-modes/text-orientation-script-001p.html
index da71fec..7f0532f 100644
--- a/css/css-writing-modes/text-orientation-script-001p.html
+++ b/css/css-writing-modes/text-orientation-script-001p.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-script-001q.html b/css/css-writing-modes/text-orientation-script-001q.html
index e0bcb08..f0fd901 100644
--- a/css/css-writing-modes/text-orientation-script-001q.html
+++ b/css/css-writing-modes/text-orientation-script-001q.html
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/text-orientation-sideways-slr-019.xht b/css/css-writing-modes/text-orientation-sideways-slr-019.xht
index 6ab03e7..9751a85 100644
--- a/css/css-writing-modes/text-orientation-sideways-slr-019.xht
+++ b/css/css-writing-modes/text-orientation-sideways-slr-019.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-lr' - 'text-orientation: sideways' has no effect</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-3/#text-orientation" />
+  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-4/#text-orientation" />
 
   <meta name="flags" content="image" />
   <meta name="assert" content="This test checks that when 'writing-mode' is set to 'sideways-lr', then a 'text-orientation: sideways' declaration has no effect: the alphabetical baseline is used as the dominant baseline and all the glyphs remain rotated 90 degrees counter-clockwise. The typographic mode for 'sideways-lr' is horizontal and 'text-orientation' has no effect on boxes in horizontal typographic modes." />
@@ -16,7 +16,7 @@
   @font-face
     {
       font-family: "mplus-1p-regular";
-      src: url("support/mplus-1p-regular.woff") format("woff");
+      src: url("/fonts/mplus-1p-regular.woff") format("woff");
       /* filesize: 803300 bytes (784.5 KBytes) */
     }
 
diff --git a/css/css-writing-modes/text-orientation-sideways-vlr-100-ref.html b/css/css-writing-modes/text-orientation-sideways-vlr-100-ref.html
index 9710ebb..5b186ca 100644
--- a/css/css-writing-modes/text-orientation-sideways-vlr-100-ref.html
+++ b/css/css-writing-modes/text-orientation-sideways-vlr-100-ref.html
@@ -5,7 +5,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-lr;
diff --git a/css/css-writing-modes/text-orientation-sideways-vlr-100.html b/css/css-writing-modes/text-orientation-sideways-vlr-100.html
index 571d7b9..a3b73bb 100644
--- a/css/css-writing-modes/text-orientation-sideways-vlr-100.html
+++ b/css/css-writing-modes/text-orientation-sideways-vlr-100.html
@@ -7,7 +7,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-lr;
diff --git a/css/css-writing-modes/text-orientation-sideways-vrl-100-ref.html b/css/css-writing-modes/text-orientation-sideways-vrl-100-ref.html
index 7240ae1..255efc8 100644
--- a/css/css-writing-modes/text-orientation-sideways-vrl-100-ref.html
+++ b/css/css-writing-modes/text-orientation-sideways-vrl-100-ref.html
@@ -5,7 +5,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-rl;
diff --git a/css/css-writing-modes/text-orientation-sideways-vrl-100.html b/css/css-writing-modes/text-orientation-sideways-vrl-100.html
index 91cb00f..52355db 100644
--- a/css/css-writing-modes/text-orientation-sideways-vrl-100.html
+++ b/css/css-writing-modes/text-orientation-sideways-vrl-100.html
@@ -7,7 +7,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-rl;
diff --git a/css/css-writing-modes/text-orientation-upright-slr-017.xht b/css/css-writing-modes/text-orientation-upright-slr-017.xht
index e7310d8..eb67dde 100644
--- a/css/css-writing-modes/text-orientation-upright-slr-017.xht
+++ b/css/css-writing-modes/text-orientation-upright-slr-017.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-lr' - 'text-orientation: upright' has no effect</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-3/#text-orientation" />
+  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-4/#text-orientation" />
 
   <meta name="flags" content="image" />
   <meta name="assert" content="This test checks that when 'writing-mode' is set to 'sideways-lr', then a 'text-orientation: upright' declaration has no effect: the alphabetical baseline is used as the dominant baseline and all the glyphs remain rotated 90 degrees counter-clockwise. The typographic mode for 'sideways-lr' is horizontal and 'text-orientation' has no effect on boxes in horizontal typographic modes." />
@@ -16,7 +16,7 @@
   @font-face
     {
       font-family: "mplus-1p-regular";
-      src: url("support/mplus-1p-regular.woff") format("woff");
+      src: url("/fonts/mplus-1p-regular.woff") format("woff");
       /* filesize: 803300 bytes (784.5 KBytes) */
     }
 
diff --git a/css/css-writing-modes/text-orientation-upright-srl-018.xht b/css/css-writing-modes/text-orientation-upright-srl-018.xht
index 12f9b45..95549c2 100644
--- a/css/css-writing-modes/text-orientation-upright-srl-018.xht
+++ b/css/css-writing-modes/text-orientation-upright-srl-018.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-rl' - 'text-orientation: upright' has no effect</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-3/#text-orientation" />
+  <link rel="help" title="5.1. Orienting Text: the 'text-orientation' property" href="http://www.w3.org/TR/css-writing-modes-4/#text-orientation" />
   <link rel="match" href="text-orientation-mixed-srl-016-ref.xht" />
 
   <meta name="flags" content="" />
@@ -17,7 +17,7 @@
   @font-face
     {
       font-family: "mplus-1p-regular";
-      src: url("support/mplus-1p-regular.woff") format("woff");
+      src: url("/fonts/mplus-1p-regular.woff") format("woff");
       /* filesize: 803300 bytes (784.5 KBytes) */
     }
 
diff --git a/css/css-writing-modes/text-orientation-upright-vlr-100-ref.html b/css/css-writing-modes/text-orientation-upright-vlr-100-ref.html
index 8769c6b..b99ca51 100644
--- a/css/css-writing-modes/text-orientation-upright-vlr-100-ref.html
+++ b/css/css-writing-modes/text-orientation-upright-vlr-100-ref.html
@@ -5,7 +5,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-lr;
diff --git a/css/css-writing-modes/text-orientation-upright-vlr-100.html b/css/css-writing-modes/text-orientation-upright-vlr-100.html
index 34ccb72..a0c59c4 100644
--- a/css/css-writing-modes/text-orientation-upright-vlr-100.html
+++ b/css/css-writing-modes/text-orientation-upright-vlr-100.html
@@ -7,7 +7,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-lr;
diff --git a/css/css-writing-modes/text-orientation-upright-vrl-002.xht b/css/css-writing-modes/text-orientation-upright-vrl-002.xht
index 307ecee..511854d 100644
--- a/css/css-writing-modes/text-orientation-upright-vrl-002.xht
+++ b/css/css-writing-modes/text-orientation-upright-vrl-002.xht
@@ -12,7 +12,7 @@
 			@font-face
 			{
 					font-family: "mplus-1p-regular";
-					src: url("support/mplus-1p-regular.woff") format("woff");
+					src: url("/fonts/mplus-1p-regular.woff") format("woff");
 			}
 
 			div {
diff --git a/css/css-writing-modes/text-orientation-upright-vrl-100-ref.html b/css/css-writing-modes/text-orientation-upright-vrl-100-ref.html
index 3621f93..350526f 100644
--- a/css/css-writing-modes/text-orientation-upright-vrl-100-ref.html
+++ b/css/css-writing-modes/text-orientation-upright-vrl-100-ref.html
@@ -5,7 +5,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-rl;
diff --git a/css/css-writing-modes/text-orientation-upright-vrl-100.html b/css/css-writing-modes/text-orientation-upright-vrl-100.html
index 9a2961f..566d046 100644
--- a/css/css-writing-modes/text-orientation-upright-vrl-100.html
+++ b/css/css-writing-modes/text-orientation-upright-vrl-100.html
@@ -7,7 +7,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     writing-mode: vertical-rl;
diff --git a/css/css-writing-modes/tools/generators/text-orientation-ref.ejs b/css/css-writing-modes/tools/generators/text-orientation-ref.ejs
index 89cd382..f4bc801 100644
--- a/css/css-writing-modes/tools/generators/text-orientation-ref.ejs
+++ b/css/css-writing-modes/tools/generators/text-orientation-ref.ejs
@@ -9,7 +9,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 html {
     <%= prefix %>writing-mode: <%= writingMode %>;
diff --git a/css/css-writing-modes/tools/generators/text-orientation-script.ejs b/css/css-writing-modes/tools/generators/text-orientation-script.ejs
index dd96af2..5366366 100644
--- a/css/css-writing-modes/tools/generators/text-orientation-script.ejs
+++ b/css/css-writing-modes/tools/generators/text-orientation-script.ejs
@@ -10,7 +10,7 @@
 <style>
 @font-face {
     font-family: "orientation";
-    src: url("support/adobe-fonts/CSSHWOrientationTest.otf");
+    src: url("/fonts/adobe-fonts/CSSHWOrientationTest.otf");
 }
 .test {
     font: 16px/1 "orientation";
diff --git a/css/css-writing-modes/vertical-alignment-slr-029.xht b/css/css-writing-modes/vertical-alignment-slr-029.xht
index fc738a5..e4c4621 100644
--- a/css/css-writing-modes/vertical-alignment-slr-029.xht
+++ b/css/css-writing-modes/vertical-alignment-slr-029.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'top' and sideways-lr writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-lr', 'vertical-align' is 'top', the physical left (logical top) edge of an inline non-replaced box is aligned with the physical left (logical top) edge of its line box." />
     <link rel="match" href="vertical-alignment-008-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-slr-031.xht b/css/css-writing-modes/vertical-alignment-slr-031.xht
index 68776ff..e26e644 100644
--- a/css/css-writing-modes/vertical-alignment-slr-031.xht
+++ b/css/css-writing-modes/vertical-alignment-slr-031.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'text-top' and sideways-lr writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of an inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-lr', 'vertical-align' is 'text-top', the physical left (logical top) edge of an inline non-replaced box is aligned with the left side (logical top) of parent's content area." />
     <link rel="match" href="vertical-alignment-006-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-slr-033.xht b/css/css-writing-modes/vertical-alignment-slr-033.xht
index 64fa044..594af05 100644
--- a/css/css-writing-modes/vertical-alignment-slr-033.xht
+++ b/css/css-writing-modes/vertical-alignment-slr-033.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'text-bottom' and sideways-lr writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of an inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-lr', 'vertical-align' is 'text-bottom', the physical right (logical bottom) edge of an inline non-replaced box is aligned with the right side (logical bottom) of parent's content area." />
     <link rel="match" href="vertical-alignment-004-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-slr-035.xht b/css/css-writing-modes/vertical-alignment-slr-035.xht
index fc134d8..f080c1a 100644
--- a/css/css-writing-modes/vertical-alignment-slr-035.xht
+++ b/css/css-writing-modes/vertical-alignment-slr-035.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'bottom' and sideways-lr writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-lr', 'vertical-align' is 'bottom', the physical right (logical bottom) edge of an inline non-replaced box is aligned with the physical right (logical bottom) edge of its line box." />
     <link rel="match" href="vertical-alignment-002-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-slr-037.xht b/css/css-writing-modes/vertical-alignment-slr-037.xht
index 2fa7109..2ce7a16 100644
--- a/css/css-writing-modes/vertical-alignment-slr-037.xht
+++ b/css/css-writing-modes/vertical-alignment-slr-037.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'super' and sideways-lr writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-lr' and 'vertical-align' is 'super', the baseline of the box is shifted to the left." />
     <meta name="flags" content="ahem" />
diff --git a/css/css-writing-modes/vertical-alignment-slr-039.xht b/css/css-writing-modes/vertical-alignment-slr-039.xht
index 87a76f3..4421bf4 100644
--- a/css/css-writing-modes/vertical-alignment-slr-039.xht
+++ b/css/css-writing-modes/vertical-alignment-slr-039.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'sub' and sideways-lr writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-lr' and 'vertical-align' is 'sub', the baseline of the box is shifted to the right." />
     <meta name="flags" content="ahem" />
diff --git a/css/css-writing-modes/vertical-alignment-slr-041.xht b/css/css-writing-modes/vertical-alignment-slr-041.xht
index 8d85d9d..478b734 100644
--- a/css/css-writing-modes/vertical-alignment-slr-041.xht
+++ b/css/css-writing-modes/vertical-alignment-slr-041.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'middle' and sideways-lr writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-lr', 'text-orientation' is 'mixed', 'vertical-align' is 'middle', the vertical midpoint of the box is aligned with the alphabetic baseline of the parent box plus half the x-height of the parent." />
     <link rel="match" href="vertical-alignment-slr-049-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-srl-028.xht b/css/css-writing-modes/vertical-alignment-srl-028.xht
index dda7e6e..323d9b9 100644
--- a/css/css-writing-modes/vertical-alignment-srl-028.xht
+++ b/css/css-writing-modes/vertical-alignment-srl-028.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'top' and sideways-rl writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-rl', 'vertical-align' is 'top', the physical right (logical top) edge of an inline non-replaced box is aligned with the physical right (logical top) edge of its line box." />
     <link rel="match" href="vertical-alignment-002-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-srl-030.xht b/css/css-writing-modes/vertical-alignment-srl-030.xht
index ec16633..9d452b9 100644
--- a/css/css-writing-modes/vertical-alignment-srl-030.xht
+++ b/css/css-writing-modes/vertical-alignment-srl-030.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'text-top' and sideways-rl writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of an inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-rl', 'vertical-align' is 'text-top', the physical right (logical top) edge of an inline non-replaced box is aligned with the right side (logical top) of parent's content area." />
     <link rel="match" href="vertical-alignment-004-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-srl-032.xht b/css/css-writing-modes/vertical-alignment-srl-032.xht
index 5905014..f249ebe 100644
--- a/css/css-writing-modes/vertical-alignment-srl-032.xht
+++ b/css/css-writing-modes/vertical-alignment-srl-032.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'text-bottom' and sideways-rl writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of an inline non-replaced box with vertical align property. When 'writing-mode' is 'vertical-rl', 'vertical-align' is 'text-bottom', the physical left (logical bottom) edge of an inline non-replaced box is aligned with the left side (logical bottom) of parent's content area." />
     <link rel="match" href="vertical-alignment-006-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-srl-034.xht b/css/css-writing-modes/vertical-alignment-srl-034.xht
index f997028..3475d21 100644
--- a/css/css-writing-modes/vertical-alignment-srl-034.xht
+++ b/css/css-writing-modes/vertical-alignment-srl-034.xht
@@ -4,7 +4,7 @@
    <title>CSS Writing Modes Test: vertical-align - 'bottom' and sideways-rl writing-mode</title>
    <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
    <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-   <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+   <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
    <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
    <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-rl', 'vertical-align' is 'bottom', the physical left (logical bottom) edge of an inline non-replaced box is aligned with the physical left (logical bottom) edge of its line box." />
     <link rel="match" href="vertical-alignment-008-ref.xht" />
diff --git a/css/css-writing-modes/vertical-alignment-srl-036.xht b/css/css-writing-modes/vertical-alignment-srl-036.xht
index af38d90..59d4949 100644
--- a/css/css-writing-modes/vertical-alignment-srl-036.xht
+++ b/css/css-writing-modes/vertical-alignment-srl-036.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'super' and sideways-rl writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-rl' and 'vertical-align' is 'super', the baseline of the box is shifted to the right." />
     <meta name="flags" content="ahem" />
diff --git a/css/css-writing-modes/vertical-alignment-srl-038.xht b/css/css-writing-modes/vertical-alignment-srl-038.xht
index 941149a..c8aa975 100644
--- a/css/css-writing-modes/vertical-alignment-srl-038.xht
+++ b/css/css-writing-modes/vertical-alignment-srl-038.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'sub' and sideways-rl writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-rl' and 'vertical-align' is 'sub', the baseline of the box is shifted to the left." />
     <meta name="flags" content="ahem" />
diff --git a/css/css-writing-modes/vertical-alignment-srl-040.xht b/css/css-writing-modes/vertical-alignment-srl-040.xht
index 0281e51..fd805a2 100644
--- a/css/css-writing-modes/vertical-alignment-srl-040.xht
+++ b/css/css-writing-modes/vertical-alignment-srl-040.xht
@@ -4,7 +4,7 @@
     <title>CSS Writing Modes Test: vertical-align - 'middle' and sideways-rl writing-mode</title>
     <link rel="author" title="Hajime Shiozawa" href="mailto:hajime.shiozawa@gmail.com" />
     <link rel="reviewer" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> <!-- 2015-12-11 -->
-    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-3/#line-mappings" />
+    <link rel="help" title="7.5 Line-Relative Mappings" href="http://www.w3.org/TR/css-writing-modes-4/#line-mappings" />
     <link rel="help" title="10.8.1 Leading and half-leading" href="http://www.w3.org/TR/2011/REC-CSS2-20110607/visudet.html#leading" />
     <meta name="assert" content="This test checks the position of inline non-replaced box with vertical align property. When 'writing-mode' is 'sideways-rl' and 'vertical-align' is 'middle', the vertical midpoint of the box is aligned with the alphabetic baseline of the parent box plus half the x-height of the parent." />
     <link rel="match" href="vertical-alignment-vrl-026-ref.xht" />
diff --git a/css/css-writing-modes/wm-propagation-body-008.xht b/css/css-writing-modes/wm-propagation-body-008.xht
index 788afb7..feeb857 100644
--- a/css/css-writing-modes/wm-propagation-body-008.xht
+++ b/css/css-writing-modes/wm-propagation-body-008.xht
@@ -7,7 +7,7 @@
   <title>CSS Writing Modes Test: 'writing-mode: sideways-rl' set to body element propagates to viewport</title>
 
   <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
-  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#principal-flow" title="8. The Principal Writing Mode" />
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-4/#principal-flow" title="8. The Principal Writing Mode" />
   <link rel="match" href="block-flow-direction-025-ref.xht" />
 
   <meta name="flags" content="image" />
diff --git a/css/css-writing-modes/writing-mode-parsing-sideways-lr-001.html b/css/css-writing-modes/writing-mode-parsing-sideways-lr-001.html
index 9708302..0997dae 100644
--- a/css/css-writing-modes/writing-mode-parsing-sideways-lr-001.html
+++ b/css/css-writing-modes/writing-mode-parsing-sideways-lr-001.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSS Writing Modes: parsing writing-mode: sideways-lr</title>
 <link rel="author" title="Koji Ishii" href="mailto:kojiishi@gmail.com">
-<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#block-flow">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-4/#block-flow">
 <meta name="assert" content="This test asserts the parser and getComputedStyle works correctly for the writing-mode: sideways-lr.">
 <meta name="flags" content="dom">
 <script src="/resources/testharness.js"></script>
diff --git a/css/css-writing-modes/writing-mode-parsing-sideways-rl-001.html b/css/css-writing-modes/writing-mode-parsing-sideways-rl-001.html
index 71138da..dfd6f4b 100644
--- a/css/css-writing-modes/writing-mode-parsing-sideways-rl-001.html
+++ b/css/css-writing-modes/writing-mode-parsing-sideways-rl-001.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSS Writing Modes: parsing writing-mode: sideways-rl</title>
 <link rel="author" title="Koji Ishii" href="mailto:kojiishi@gmail.com">
-<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#block-flow">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-4/#block-flow">
 <meta name="assert" content="This test asserts the parser and getComputedStyle works correctly for the writing-mode: sideways-rl.">
 <meta name="flags" content="dom">
 <script src="/resources/testharness.js"></script>
diff --git a/css/cssom-view/GetBoundingRect.html b/css/cssom-view/GetBoundingRect.html
new file mode 100644
index 0000000..7286bd0
--- /dev/null
+++ b/css/cssom-view/GetBoundingRect.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>getBoundingClientRect</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+
+    <style>
+      #foo {
+        margin: 0px 0px 0px 5px;
+        transform: translate(10px, 200px);
+        position: fixed;
+        left: 5px;
+        background-color: red;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="foo">
+      FOO
+    </div>
+    <script>
+        test(function () {
+            var foo = document.getElementById("foo").getBoundingClientRect();
+            assert_equals(foo.left, 20);
+        });
+    </script>
+  </body>
+</html>
diff --git a/css/cssom-view/cssom-getBoxQuads-001.html b/css/cssom-view/cssom-getBoxQuads-001.html
new file mode 100644
index 0000000..813c245
--- /dev/null
+++ b/css/cssom-view/cssom-getBoxQuads-001.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+ <head>
+  <title>CSSOM View - getBoxQuads() returns proper border and margin boxes for block and flex</title>
+  <link rel="help" href="https://drafts.csswg.org/cssom-view/#the-geometryutils-interface">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <style>
+    .container {
+      width: 100px;
+      height: 50px;
+      background-color: gray;
+    }
+    span {
+      display: block;
+      background: gold;
+      height: 4px;
+      width: 14px;
+      margin: auto;
+      padding: 0px;
+      border: 3px solid blue;
+    }
+  </style>
+ </head>
+ <body>
+  <div class="container">
+  <span id="block-block"></span>
+  </div>
+
+  <div class="container" style="display:flex">
+  <span id="flex-block"></span>
+  </div>
+
+  <script>
+    test(function() {
+      let bb = document.getElementById("block-block");
+      assert_equals(bb.getBoxQuads({box: "border"})[0].bounds.width,  20, "Block layout border box is expected width.");
+      assert_equals(bb.getBoxQuads({box: "margin"})[0].bounds.width, 100, "Block layout margin box is expected width.");
+
+      // For containers that expand items to fill block-axis space, measure the box heights also.
+      let fb = document.getElementById("flex-block");
+      assert_equals(fb.getBoxQuads({box: "border"})[0].bounds.width,  20, "Flex layout border box is expected width.");
+      assert_equals(fb.getBoxQuads({box: "margin"})[0].bounds.width, 100, "Flex layout margin box is expected width.");
+
+      assert_equals(fb.getBoxQuads({box: "border"})[0].bounds.height, 10, "Flex layout border box is expected height.");
+      assert_equals(fb.getBoxQuads({box: "margin"})[0].bounds.height, 50, "Flex layout margin box is expected height.");
+    });
+  </script>
+ </body>
+</html>
diff --git a/css/cssom-view/elementFromPoint-dynamic-anon-box.html b/css/cssom-view/elementFromPoint-dynamic-anon-box.html
new file mode 100644
index 0000000..e0acb90
--- /dev/null
+++ b/css/cssom-view/elementFromPoint-dynamic-anon-box.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: Hit testing on element previously hidden by an anonymous scroll box</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1433591">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+html, body {
+  margin: 0;
+  padding: 0;
+}
+/*
+  Create a hidden scrollbox that occupies the whole viewport, then give it
+  visibility: hidden dynamically. The link previously under the scrollbox
+  should be clickable.
+ */
+.scrollable {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  overflow: scroll;
+}
+
+.scrollable .inner {
+  display: table-cell;
+  width: 100vw;
+  height: 100vh;
+}
+</style>
+<div class="scrollable">
+  <div class="inner"></div>
+</div>
+<a href="#">Should be clickable</a>
+<script>
+test(function() {
+  assert_equals(document.elementFromPoint(10, 10).tagName, "DIV",
+                "Should hit the scrollbox contents");
+  document.querySelector('div').style.visibility = "hidden";
+  assert_equals(document.elementFromPoint(10, 10), document.querySelector('a'));
+}, "Link should be clickable after hiding a scrollbox with an anonymous table inside");
+</script>
diff --git a/css/cssom-view/elementFromPosition.html b/css/cssom-view/elementFromPosition.html
index d4da36f..ed86d16 100644
--- a/css/cssom-view/elementFromPosition.html
+++ b/css/cssom-view/elementFromPosition.html
@@ -8,26 +8,6 @@
     <meta name="flags" content="dom" />
     <script src="/resources/testharness.js" type="text/javascript"></script>
     <script src="/resources/testharnessreport.js" type="text/javascript"></script>
-    <script id="metadata_cache">/*
-{
-  "document.elementFromPoint": {},
-  "document.elementFromPoint is a Function": {},
-  "test some point of the element: top left corner": {},
-  "test some point of the element: top line": {},
-  "test some point of the element: top right corner": {},
-  "test some point of the element: left line": {},
-  "test some point of the element: inside": {},
-  "test some point of the element: right line": {},
-  "test some point of the element: bottom left corner": {},
-  "test some point of the element: bottom line": {},
-  "test some point of the element: bottom right corner": {},
-  "Point (0, 0), return root element(HTML)": {},
-  " test negative x ": {},
-  " test negative y ": {},
-  "test outside of viewport": {},
-  "test the top of layer": {}
-}
-  */</script>
 </head>
 <body>
     <noscript>Test not run - JavaScript required!</noscript>
@@ -139,5 +119,3 @@
     </script>
 </body>
 </html>
-
-
diff --git a/css/cssom-view/elementScroll-002.html b/css/cssom-view/elementScroll-002.html
new file mode 100644
index 0000000..a1a28aa
--- /dev/null
+++ b/css/cssom-view/elementScroll-002.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>cssom-view - elementScroll - 002</title>
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrolltop">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollleft">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="scroller1" style="height: 100px; width: 100px; overflow: scroll; background: red;">
+    <div style="background: green; margin-top: 100px; margin-left: 100px; width: 100px; height: 100px;"></div>
+</div>
+<div id="scroller2" style="height: 100px; width: 100px; overflow: hidden; background: red;">
+    <div style="background: green; margin-top: 100px; padding-left: 100px; width: 100px; height: 100px;"></div>
+</div>
+<div id="scroller3" style="height: 100px; width: 100px; overflow: scroll; background: red;">
+    <div style="background: green; padding-top: 100px; margin-left: 100px; width: 100px; height: 100px;"></div>
+</div>
+<div id="scroller4" style="height: 100px; width: 100px; overflow: hidden; background: red;">
+    <div style="background: green; padding-top: 100px; padding-left: 100px; width: 100px; height: 100px;"></div>
+</div>
+<script>
+    test(function () {
+        var scroller1 = document.getElementById("scroller1");
+
+        scroller1.scrollTop = 100;
+        scroller1.scrollLeft = 100;
+        assert_equals(scroller1.scrollTop, 100, "changed scrollTop should be 100");
+        assert_equals(scroller1.scrollLeft, 100, "changed scrollLeft should be 100");
+
+    }, "simple scroll with style: 'margin' and 'overflow: scroll'");
+    test(function () {
+        var scroller2 = document.getElementById("scroller2");
+
+        scroller2.scrollTop = 100;
+        scroller2.scrollLeft = 100;
+        assert_equals(scroller2.scrollTop, 100, "changed scrollTop should be 100");
+        assert_equals(scroller2.scrollLeft, 100, "changed scrollLeft should be 100");
+
+    }, "simple scroll with style: 'margin' and 'overflow: hidden'");
+    test(function () {
+        var scroller3 = document.getElementById("scroller3");
+
+        scroller3.scrollTop = 100;
+        scroller3.scrollLeft = 100;
+        assert_equals(scroller3.scrollTop, 100, "changed scrollTop should be 100");
+        assert_equals(scroller3.scrollLeft, 100, "changed scrollLeft should be 100");
+
+    }, "simple scroll with style: 'padding' and 'overflow: scroll'");
+    test(function () {
+        var scroller4 = document.getElementById("scroller4");
+
+        scroller4.scrollTop = 100;
+        scroller4.scrollLeft = 100;
+        assert_equals(scroller4.scrollTop, 100, "changed scrollTop should be 100");
+        assert_equals(scroller4.scrollLeft, 100, "changed scrollLeft should be 100");
+
+    }, "simple scroll with style: 'padding' and 'overflow: hidden'");
+</script>
diff --git a/css/cssom-view/interfaces.html b/css/cssom-view/interfaces.html
index c6e3662..38de8dc 100644
--- a/css/cssom-view/interfaces.html
+++ b/css/cssom-view/interfaces.html
@@ -3,7 +3,7 @@
 <!-- WARNING: These tests are preliminary and probably partly incorrect.  -->
 <title>CSSOM View automated IDL tests</title>
 <link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
-<link rel="help" href="https://drafts.csswg.org/cssom-1/#idl-index">
+<link rel="help" href="https://drafts.csswg.org/cssom-view-1/#idl-index">
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src=/resources/WebIDLParser.js></script>
@@ -17,11 +17,18 @@
 <script>
 "use strict";
 
-function doTest([html, dom, cssom, geometry, cssom_view]) {
+function doTest([html, dom, uievents, cssom, cssom_view]) {
 
   var idlArray = new IdlArray();
   var svg = "interface SVGElement : Element {};";
-  idlArray.add_untested_idls(html + dom + svg + cssom + geometry);
+  idlArray.add_untested_idls(html + dom + svg + cssom);
+  idlArray.add_untested_idls(uievents, { only: [
+    'UIEvent',
+    'UIEventInit',
+    'MouseEvent',
+    'MouseEventInit',
+    'EventModifierInit']
+  });
   idlArray.add_idls(cssom_view);
 
   idlArray.add_objects({
@@ -38,7 +45,6 @@
     // "MouseEvent": ["new MouseEvent('foo')"],
     "Text": ["document.createTextNode('x')"],
     // "CSSPseudoElement": [],
-    "Document": ["document"],
   });
   idlArray.test();
 };
@@ -57,8 +63,8 @@
   // Have to wait for onload
   return Promise.all([fetchData("/interfaces/html.idl"),
                       fetchData("/interfaces/dom.idl"),
+                      fetchData("/interfaces/uievents.idl"),
                       fetchData("/interfaces/cssom.idl"),
-                      fetchData("/interfaces/geometry.idl"),
                       fetchData("/interfaces/cssom-view.idl"),
                       waitForLoad()])
                 .then(doTest);
diff --git a/css/cssom-view/offsetParent_element_test.html b/css/cssom-view/offsetParent_element_test.html
index aa4a1e5..bb5686d 100644
--- a/css/cssom-view/offsetParent_element_test.html
+++ b/css/cssom-view/offsetParent_element_test.html
@@ -8,12 +8,6 @@
 <link rel="help" href="http://www.w3.org/TR/cssom-view/#dom-htmlelement-offsetparent">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script id="metadata_cache">/*
-{
-  "Valid the algorithm rule of offsetParent check step 1": { "assert": "The offsetParent attribute algorithm rule checking passed!" },
-  "Valid the algorithm rule of offsetParent check step 2": { "assert": "The offsetParent attribute algorithm rule checking passed!" }
-}
-*/</script>
 <style>
 #fixed {
     position: fixed;
diff --git a/css/cssom-view/overscrollBehavior-manual.html b/css/cssom-view/overscrollBehavior-manual.html
index b37c4dc..d66317f 100644
--- a/css/cssom-view/overscrollBehavior-manual.html
+++ b/css/cssom-view/overscrollBehavior-manual.html
@@ -52,6 +52,7 @@
 
 
 <script>
+setup({explicit_timeout: true});
 const container = document.getElementById('container');
 const non_scrollable = document.getElementById('non_scrollable');
 const root = document.getElementById('root');
@@ -78,6 +79,7 @@
   container.style.overscrollBehaviorY = 'none';
   setUpForRoot(100);
   setUpForContainer(0);
+  window.scrollTo(0, 0);
 }
 
 function verify_y_prevented_and_set_boundary_prevents_x() {
@@ -86,12 +88,14 @@
   test.step(function() {
     assert_equals(root.scrollTop, 100);
     assert_equals(root.scrollLeft, 0);
+    window.scrollTo(0, 0);
   }, "overscroll-behavior-y: none should only prevent scroll propagation on y axis.");
 
   container.style.overscrollBehaviorX = 'none';
   container.style.overscrollBehaviorY = 'auto';
   setUpForRoot(100);
   setUpForContainer(0);
+  window.scrollTo(0, 0);
 }
 
 function verify_x_prevented_and_set_boundary_allows_inner() {
@@ -106,6 +110,7 @@
   container.style.overscrollBehaviorY = 'none';
   setUpForRoot(100);
   setUpForContainer(100);
+  window.scrollTo(0, 0);
 }
 
 function verify_inner_allowed_and_set_nonscrollable_allows_propagation() {
@@ -123,6 +128,7 @@
   non_scrollable.style.overscrollBehaviorX = 'none';
   non_scrollable.style.overscrollBehaviorY = 'none';
   setUpForRoot(100);
+  window.scrollTo(0, 0);
 }
 
 function verify_non_scrollable_allows_propagation() {
diff --git a/css/cssom-view/scroll-behavior-smooth.html b/css/cssom-view/scroll-behavior-smooth.html
new file mode 100644
index 0000000..e1a7a6a
--- /dev/null
+++ b/css/cssom-view/scroll-behavior-smooth.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<title>cssom-view - scroll-behavior: smooth</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#smooth-scrolling">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .filler { height: 10000px }
+
+  .smooth {
+    scroll-behavior: smooth;
+  }
+
+  #scrollable {
+    overflow: scroll;
+    width: 100px;
+    height: 100px;
+  }
+</style>
+<div id="testContainer">
+  <div id="scrollable">
+    <div class="filler"></div>
+  </div>
+  <div class="filler"></div>
+</div>
+<script>
+  test(() => {
+    scrollable.scrollTo(0, 5000);
+    assert_equals(scrollable.scrollTop, 5000, "Initially scrolls instantly");
+    scrollable.scrollTo(0, 0);
+    scrollable.className = "smooth";
+    scrollable.scrollTo(0, 5000);
+    assert_less_than(scrollable.scrollTop, 5000, "scroll-behavior:smooth should not scroll instantly");
+    scrollable.className = "";
+    scrollable.scrollTo(0, 0);
+  }, "scroll-behavior: smooth on DIV element");
+
+  test(() => {
+    window.scrollTo(0, 5000);
+    assert_equals(window.scrollY, 5000, "Initially scrolls instantly");
+    window.scrollTo(0, 0);
+    document.documentElement.className = "smooth";
+    assert_less_than(window.scrollY, 5000, "scroll-behavior:smooth should not scroll instantly");
+    document.documentElement.className = "";
+    window.scrollTo(0, 0);
+  }, "HTML element scroll-behavior should propagate to viewport");
+
+  test(() => {
+    window.scrollTo(0, 5000);
+    assert_equals(window.scrollY, 5000, "Initially scrolls instantly");
+    window.scrollTo(0, 0);
+    document.body.className = "smooth";
+    window.scrollTo(0, 5000);
+    assert_equals(window.scrollY, 5000, "scroll-behavior:smooth on BODY should scroll viewport instantly");
+    document.body.className = "";
+    window.scrollTo(0, 0);
+  }, "BODY element scroll-behavior should not propagate to viewport");
+
+  var instantHistoryNavigationTest =
+    async_test("Instant scrolling while doing history navigation.");
+  var smoothHistoryNavigationTest =
+    async_test("Smooth scrolling while doing history navigation.");
+
+  function instant() {
+    document.documentElement.className = "";
+    document.body.className = "";
+    window.scrollTo(0, 0);
+    var p = document.createElement("pre");
+    p.textContent = new Array(1000).join("newline\n");
+    var a = document.createElement("a");
+    a.href = "#";
+    a.name = "foo";
+    a.textContent = "foo";
+    p.appendChild(a);
+    document.body.appendChild(p);
+    window.onhashchange = function() {
+      window.onhashchange = function() {
+        instantHistoryNavigationTest.step(function() {
+          assert_equals(location.hash, "", "Shouldn't be scrolled to a fragment.");
+          assert_equals(window.scrollY, 0, "Shouldn't be scrolled back to top yet.");
+        });
+        p.remove();
+        instantHistoryNavigationTest.done();
+        smooth();
+      }
+
+      instantHistoryNavigationTest.step(function() {
+        assert_equals(location.hash, "#foo", "Should be scrolled to a fragment.");
+        assert_not_equals(window.scrollY, 0, "Shouldn't be scrolled to top anymore.");
+      });
+      history.back();
+    }
+
+    instantHistoryNavigationTest.step(function() {
+      assert_equals(window.scrollY, 0, "Should be scrolled to top.");
+      assert_equals(location.hash, "", "Shouldn't be scrolled to a fragment.");
+    });
+    location.hash = "foo";
+  };
+  instant();
+
+  function smooth() {
+    document.documentElement.className = "";
+    document.body.className = "";
+    window.scrollTo(0, 0);
+    var p = document.createElement("pre");
+    p.textContent = new Array(1000).join("newline\n");
+    var a = document.createElement("a");
+    a.href = "#";
+    a.name = "bar";
+    a.textContent = "bar";
+    p.appendChild(a);
+    document.body.appendChild(p);
+    window.onhashchange = function() {
+      window.onhashchange = function() {
+        smoothHistoryNavigationTest.step(function() {
+          assert_equals(location.hash, "", "Shouldn't be scrolled to a fragment.");
+          assert_not_equals(window.scrollY, 0, "Shouldn't be scrolled back to top yet.");
+        });
+        p.remove();
+        smoothHistoryNavigationTest.done();
+      }
+
+      smoothHistoryNavigationTest.step(function() {
+        assert_equals(location.hash, "#bar", "Should be scrolled to a fragment.");
+        assert_not_equals(window.scrollY, 0, "Shouldn't be scrolled to top anymore.");
+      });
+      history.back();
+    }
+
+    smoothHistoryNavigationTest.step(function() {
+      assert_equals(window.scrollY, 0, "Should be scrolled to top.");
+      assert_equals(location.hash, "", "Shouldn't be scrolled to a fragment.");
+    });
+    location.hash = "bar";
+    document.documentElement.className = "smooth";
+  };
+
+  testContainer.style.display = "none";
+</script>
diff --git a/css/cssom-view/scroll-no-layout-box.html b/css/cssom-view/scroll-no-layout-box.html
new file mode 100644
index 0000000..cc67ce7
--- /dev/null
+++ b/css/cssom-view/scroll-no-layout-box.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>cssom-view - Scrolling element with no layout box</title>
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scroll">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#css-layout-box">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div style="display: none">
+  <div id="elem"></div>
+</div>
+
+<script>
+test(() => {
+  const elem = document.getElementById('elem');
+  elem.scroll(1, 2);
+
+  assert_equals(elem.scrollTop, 0, "scrollTop should be unchanged");
+  assert_equals(elem.scrollLeft, 0, "scrollLeft should be unchanged");
+}, "scrolling an element with no CSS layout box should have no effect");
+</script>
diff --git a/css/cssom-view/scrollIntoView-scrollMargin.html b/css/cssom-view/scrollIntoView-scrollMargin.html
new file mode 100644
index 0000000..930702a
--- /dev/null
+++ b/css/cssom-view/scrollIntoView-scrollMargin.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<title>CSSOM View - scrollIntoView considers scroll-margin</title>
+<meta charset="utf-8">
+<link rel="author" title="Sandra Sun" href="mailto:sunyunjia@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview">
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1/#scroll-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#scroller {
+  width: 200px;
+  height: 200px;
+  overflow: scroll;
+}
+#content {
+  width: 500px;
+  height: 500px;
+}
+#target {
+  position: relative;
+  left: 200px;
+  top: 200px;
+  width: 100px;
+  height: 100px;
+  scroll-margin-top: 4px;
+  scroll-margin-right: 8px;
+  scroll-margin-bottom: 12px;
+  scroll-margin-left: 16px;
+  background-color: lightgreen;
+}
+</style>
+
+<div id="scroller">
+  <div id="content">
+    <div id="target"></div>
+  </div>
+</div>
+<div id="log"></div>
+
+<script>
+var target = document.getElementById("target");
+var scroller = document.getElementById("scroller");
+
+var expectedXLeft = 200 - 16;
+var expectedXRight = 200 - scroller.clientWidth + target.clientWidth + 8;
+var expectedXCenter = 200 - (scroller.clientWidth / 2) +
+                      (target.clientWidth + 8 - 16) / 2;
+
+var expectedYTop = 200 - 4;
+var expectedYBottom = 200 - scroller.clientHeight + target.clientHeight + 12;
+var expectedYCenter = 200 - (scroller.clientHeight / 2) +
+                      (target.clientHeight + 12 - 4) / 2;
+
+// This formats dict as a string suitable as test name.
+// format_value() is provided by testharness.js,
+// which also preserves sign for -0.
+function format_dict(dict) {
+  const props = [];
+  for (let prop in dict) {
+    props.push(`${prop}: ${format_value(dict[prop])}`);
+  }
+  return `{${props.join(", ")}}`;
+}
+
+[
+  [{block: "center", inline: "center"}, expectedXCenter, expectedYCenter],
+  [{block: "start", inline: "start"}, expectedXLeft, expectedYTop],
+  [{block: "end", inline: "end"}, expectedXRight, expectedYBottom],
+].forEach(([input, expectedX, expectedY]) => {
+  test(() => {
+    scroller.scrollTo(0, 0);
+    target.scrollIntoView(input);
+    assert_approx_equals(scroller.scrollLeft, expectedX, 0.5, "scrollX");
+    assert_approx_equals(scroller.scrollTop, expectedY, 0.5, "scrollY");
+  }, `scrollIntoView(${format_dict(input)})`);
+})
+</script>
\ No newline at end of file
diff --git a/css/cssom-view/scrollIntoView-scrollPadding.html b/css/cssom-view/scrollIntoView-scrollPadding.html
new file mode 100644
index 0000000..5fdedf7
--- /dev/null
+++ b/css/cssom-view/scrollIntoView-scrollPadding.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<title>CSSOM View - scrollIntoView considers scroll-padding</title>
+<meta charset="utf-8">
+<link rel="author" title="Sandra Sun" href="mailto:sunyunjia@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview">
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1/#scroll-padding">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#scroller {
+  width: 200px;
+  height: 200px;
+  overflow: scroll;
+  scroll-padding-top: 4px;
+  scroll-padding-right: 8px;
+  scroll-padding-bottom: 12px;
+  scroll-padding-left: 16px;
+}
+#content {
+  width: 500px;
+  height: 500px;
+}
+#target {
+  position: relative;
+  left: 200px;
+  top: 200px;
+  width: 100px;
+  height: 100px;
+  background-color: lightgreen;
+}
+</style>
+
+<div id="scroller">
+  <div id="content">
+    <div id="target"></div>
+  </div>
+</div>
+<div id="log"></div>
+
+<script>
+var target = document.getElementById("target");
+var scroller = document.getElementById("scroller");
+var expectedXLeft = 200 - 16;
+var expectedXRight = 200 - scroller.clientWidth + 8 + target.clientWidth;
+var expectedXCenter = 200 - (16 + scroller.clientWidth - 8) / 2 +
+                      target.clientWidth / 2;
+
+var expectedYTop = 200 - 4;
+var expectedYBottom = 200 - scroller.clientHeight + 12 + target.clientHeight;
+var expectedYCenter = 200 - (4 + scroller.clientHeight - 12) / 2 +
+                      target.clientHeight / 2;
+
+// This formats dict as a string suitable as test name.
+// format_value() is provided by testharness.js,
+// which also preserves sign for -0.
+function format_dict(dict) {
+  const props = [];
+  for (let prop in dict) {
+    props.push(`${prop}: ${format_value(dict[prop])}`);
+  }
+  return `{${props.join(", ")}}`;
+}
+
+[
+  [{block: "center", inline: "center"}, expectedXCenter, expectedYCenter],
+  [{block: "start", inline: "start"}, expectedXLeft, expectedYTop],
+  [{block: "end", inline: "end"}, expectedXRight, expectedYBottom],
+].forEach(([input, expectedX, expectedY]) => {
+  test(() => {
+    scroller.scrollTo(0, 0);
+    target.scrollIntoView(input);
+    assert_approx_equals(scroller.scrollLeft, expectedX, 0.5, "scrollX");
+    assert_approx_equals(scroller.scrollTop, expectedY, 0.5, "scrollY");
+  }, `scrollIntoView(${format_dict(input)})`);
+})
+</script>
\ No newline at end of file
diff --git a/css/cssom-view/scrollIntoView-shadow.html b/css/cssom-view/scrollIntoView-shadow.html
index 6d44df6..eb1bce3 100644
--- a/css/cssom-view/scrollIntoView-shadow.html
+++ b/css/cssom-view/scrollIntoView-shadow.html
@@ -14,7 +14,7 @@
 
 test(t => {
   var shadow = document.getElementById("shadow");
-  var shadowRoot = shadow.createShadowRoot();
+  var shadowRoot = shadow.attachShadow({ mode: "open" });
   var shadowDiv = document.createElement("div");
   shadowDiv.style.height = "200px";
   shadowDiv.style.width = "200px";
@@ -30,4 +30,4 @@
   assert_approx_equals(window.scrollX, expected_x, 1);
   assert_approx_equals(window.scrollY, expected_y, 1);
 }, "scrollIntoView should behave correctly if applies to shadow dom elements");
-</script>
\ No newline at end of file
+</script>
diff --git a/css/cssom-view/scrollTop-display-change-ref.html b/css/cssom-view/scrollTop-display-change-ref.html
new file mode 100644
index 0000000..9f12597
--- /dev/null
+++ b/css/cssom-view/scrollTop-display-change-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<div id="scroller" style="height: 100px; overflow: scroll">
+  <div style="height: 1000px">
+    I should be visible.
+  </div>
+  I should not be visible.
+</div>
diff --git a/css/cssom-view/scrollTop-display-change.html b/css/cssom-view/scrollTop-display-change.html
new file mode 100644
index 0000000..916bfec
--- /dev/null
+++ b/css/cssom-view/scrollTop-display-change.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Setting scrollTop to 0 immediately after toggling display from "none" on an element that had nonzero scrollTop before should work.</title>
+<link rel=match href="scrollTop-display-change-ref.html">
+<div id="scroller" style="height: 100px; overflow: scroll">
+  <div style="height: 1000px">
+    I should be visible.
+  </div>
+  I should not be visible.
+</div>
+<script>
+  scroller.scrollTop = 1000;
+  scroller.style.display = "none";
+  var win = scroller.scrollTop; // Force layout flush
+  scroller.style.display = "";
+  scroller.scrollTop = 0;
+</script>
diff --git a/css/cssom-view/scrollWidthHeight.xht b/css/cssom-view/scrollWidthHeight.xht
index b4696e1..77b01db 100644
--- a/css/cssom-view/scrollWidthHeight.xht
+++ b/css/cssom-view/scrollWidthHeight.xht
@@ -38,21 +38,6 @@
                 height:150px;
             }
         ]]></style>
-        <script id="metadata_cache" type="text/javascript"><![CDATA[/*
-{
-  "elemSimple.clientHeight is the height of the padding edge": {},
-  "elemSimple.scrollHeight is its clientHeight": {},
-  "elemSimple.clientWidth is the width of the padding edge": {},
-  "elemSimple.scrollWidth is its clientWidth": {},
-  "elemOverflow.clientHeight is the height of the padding edge": {},
-  "elemOverflow.scrollHeight is the height of its scrolled contents (ignoring padding, since we overflowed)": {},
-  "elemOverflow.clientWidth is the width of the padding edge": {},
-  "elemOverflow.scrollHeight is the width of its scrolled contents (ignoring padding, since we overflowed)": {},
-  "elemNestedOverflow.clientHeight is the height of the padding edge": {},
-  "elemNestedOverflow.scrollHeight is the height of its scrolled contents (ignoring padding, since we overflowed)": {},
-  "elemNestedOverflow.clientWidth is the height of the padding edge": {},
-  "elemNestedOverflow.scrollWidth is the width of its scrolled contents (ignoring padding, since we overflowed)": {}
-*/      ]]></script>
     </head>
     <body>
         <noscript>Test not run - javascript required.</noscript>
diff --git a/css/cssom-view/scrollWidthHeightWhenNotScrollable.xht b/css/cssom-view/scrollWidthHeightWhenNotScrollable.xht
index e8c7f12..f0fd373 100644
--- a/css/cssom-view/scrollWidthHeightWhenNotScrollable.xht
+++ b/css/cssom-view/scrollWidthHeightWhenNotScrollable.xht
@@ -37,21 +37,6 @@
                 height:150px;
             }
         ]]></style>
-        <script id="metadata_cache" type="text/javascript"><![CDATA[/*
-{
-  "elemSimple.clientHeight is the height of the padding edge": {},
-  "elemSimple.scrollHeight is its clientHeight": {},
-  "elemSimple.clientWidth is the width of the padding edge": {},
-  "elemSimple.scrollWidth is its clientWidth": {},
-  "elemOverflow.clientHeight is the height of the padding edge": {},
-  "elemOverflow.scrollHeight is the height of its scrolled contents (ignoring padding, since we overflowed)": {},
-  "elemOverflow.clientWidth is the width of the padding edge": {},
-  "elemOverflow.scrollHeight is the width of its scrolled contents (ignoring padding, since we overflowed)": {},
-  "elemNestedOverflow.clientHeight is the height of the padding edge": {},
-  "elemNestedOverflow.scrollHeight is the height of its scrolled contents (ignoring padding, since we overflowed)": {},
-  "elemNestedOverflow.clientWidth is the height of the padding edge": {},
-  "elemNestedOverflow.scrollWidth is the width of its scrolled contents (ignoring padding, since we overflowed)": {}
-*/      ]]></script>
     </head>
     <body>
         <noscript>Test not run - javascript required.</noscript>
diff --git a/css/cssom-view/window-interface.xht b/css/cssom-view/window-interface.xht
index 2ed64cb..163124b 100644
--- a/css/cssom-view/window-interface.xht
+++ b/css/cssom-view/window-interface.xht
@@ -13,22 +13,6 @@
     <body>
         <noscript>Test not run - javascript required.</noscript>
         <div id="log" />
-        <script id="metadata_cache">/*
-        {
-          "window_exposed_functions": {
-            "assert": ["window functions are exposed"]
-          },
-          "window_functions": {
-            "assert": ["window functions are instances of Function"]
-          },
-          "window_properties": {
-            "assert": ["window properties are owned by window"]
-          },
-          "window_properties_readonly": {
-            "assert": ["window properties are readonly"]
-          }
-        }
-        */</script>
         <script type="text/javascript"><![CDATA[
             /*
                Window Functions
diff --git a/css/cssom/CSS.html b/css/cssom/CSS.html
index 1bbecbf..fd2966a 100644
--- a/css/cssom/CSS.html
+++ b/css/cssom/CSS.html
@@ -1,6 +1,7 @@
 <!doctype html>
 <meta charset="utf-8">
 <title>CSSOM - CSS interface</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#the-css.escape()-method">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
diff --git a/css/cssom/CSSKeyframeRule.html b/css/cssom/CSSKeyframeRule.html
index 532aa6f..3f6d182 100644
--- a/css/cssom/CSSKeyframeRule.html
+++ b/css/cssom/CSSKeyframeRule.html
@@ -1,6 +1,7 @@
 <!doctype html>
 <meta charset="utf-8">
 <title></title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/#interface-csskeyframerule">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <style type="text/css" id="styleElement">
diff --git a/css/cssom/CSSKeyframesRule.html b/css/cssom/CSSKeyframesRule.html
index 6845349..7d91127 100644
--- a/css/cssom/CSSKeyframesRule.html
+++ b/css/cssom/CSSKeyframesRule.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>CSSOM -  CSSKeyframesRule interface</title>
+    <link rel="help" href="https://drafts.csswg.org/css-animations/#interface-csskeyframesrule">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <style>
diff --git a/css/cssom/CSSNamespaceRule.html b/css/cssom/CSSNamespaceRule.html
index 64ed94b..fbbaa3c 100644
--- a/css/cssom/CSSNamespaceRule.html
+++ b/css/cssom/CSSNamespaceRule.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>CSSOM - CSSNamespaceRule interface</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#the-cssnamespacerule-interface">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <style>
diff --git a/css/cssom/CSSRuleList.html b/css/cssom/CSSRuleList.html
index a394779..4605b74 100644
--- a/css/cssom/CSSRuleList.html
+++ b/css/cssom/CSSRuleList.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>CSSOM - CSSRuleList interface</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#the-cssrulelist-interface">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <style>
diff --git a/css/cssom/CSSStyleRule-set-selectorText-namespace.html b/css/cssom/CSSStyleRule-set-selectorText-namespace.html
new file mode 100644
index 0000000..4da0a33
--- /dev/null
+++ b/css/cssom/CSSStyleRule-set-selectorText-namespace.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSSOM StyleRule selectorText property setter with namespaces</title>
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#dom-cssstylerule-selectortext">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<style type="text/css" id="styleElement">
+@namespace url(http://www.w3.org/1999/xhtml);
+@namespace svg url(http://www.w3.org/2000/svg);
+
+svg|*.style0 { background-color: rgb(0, 0, 255) !important; }
+svg|*.style1 { background-color: rgb(255, 0, 255); }
+</style>
+
+<span>
+  <p></p>
+
+  <svg height="30" width="200" id="container" class="style1" lang="zh-CN" language segment="42 43">
+    <text x="0" y="15">SVG text</text>
+  </svg>
+</span>
+
+<script>
+  var styleSheet = document.getElementById("styleElement").sheet;
+  var rule = styleSheet.cssRules[2];
+
+  var divContainerStyle = getComputedStyle(document.getElementById("container"));
+
+  const originalStyleSelector = "svg|*.style0";
+
+  var assertColors = function(selectorMatches) {
+    assert_equals(divContainerStyle.getPropertyValue('background-color'), selectorMatches ? "rgb(0, 0, 255)" : "rgb(255, 0, 255)")
+  };
+
+  [
+    {selector: ".style1", isMatch: false, },
+    {selector: "svg|*.style1  ", isMatch: true, normalizedSelector: "svg|*.style1"},
+    {selector: "*|*.style1  ", isMatch: true, normalizedSelector: "*|*.style1"},
+    {selector: " *.style1  ", isMatch: false, normalizedSelector: ".style1"},
+    {selector: "p", isMatch: false},
+  ].forEach(function(testCase) {
+    test(function() {
+      // Check if starting with the default value.
+      assert_equals(rule.selectorText, originalStyleSelector);
+
+      this.add_cleanup(function() { rule.selectorText = originalStyleSelector; });
+
+      assertColors(false);
+
+      rule.selectorText = testCase.selector;
+
+      var expectedSelector = testCase.normalizedSelector ? testCase.normalizedSelector : testCase.selector;
+
+      assert_equals(rule.selectorText, expectedSelector);
+
+      assertColors(testCase.isMatch);
+    }, "CSSStyleRule: selectorText value: |" + testCase.selector + "| isMatch: " + testCase.isMatch);
+  });
+</script>
diff --git a/css/cssom/CSSStyleRule-set-selectorText.html b/css/cssom/CSSStyleRule-set-selectorText.html
new file mode 100644
index 0000000..e29db52
--- /dev/null
+++ b/css/cssom/CSSStyleRule-set-selectorText.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSSOM StyleRule selectorText property setter</title>
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#dom-cssstylerule-selectortext">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<style type="text/css" id="styleElement">
+  .style0 { background-color: rgb(0, 0, 255) !important; }
+  .style1 { background-color: rgb(255, 0, 255); }
+</style>
+
+<span>
+  <p></p>
+  <div id="container" class="style1" lang="zh-CN" language segment="42 43">
+  </div>
+</span>
+
+<script>
+  var styleSheet = document.getElementById("styleElement").sheet;
+  var rule = styleSheet.cssRules[0];
+
+  var divContainerStyle = getComputedStyle(document.getElementById("container"));
+
+  const originalStyleSelector = ".style0";
+
+  var assertColors = function(selectorMatches) {
+    assert_equals(divContainerStyle.backgroundColor, selectorMatches ? "rgb(0, 0, 255)" : "rgb(255, 0, 255)")
+  };
+
+  test(function() {
+    assert_equals(typeof rule.selectorText, "string");
+    assert_equals(rule.selectorText, originalStyleSelector);
+  }, "CSSStyleRule: Can read selectorText value.");
+
+  [ // Invalid selector values.
+    "",
+    " ",
+    "!!",
+    "123",
+    "-",
+    "$",
+    ":",
+    "::",
+    ":::",
+    "::gibberish",
+    ":gibberish",
+    ".",
+    "#",
+    "[]",
+    "[",
+    "()",
+    "(",
+    "{}",
+    "{",
+  ].forEach(function(selector) {
+    test(function() {
+      assert_equals(rule.selectorText, originalStyleSelector);
+
+      this.add_cleanup(function() { rule.selectorText = originalStyleSelector; });
+
+      rule.selectorText = selector;
+
+      assert_equals(rule.selectorText, originalStyleSelector);
+    }, "CSSStyleRule: Invalid CSS selector: " + selector);
+  });
+
+
+  [ // Valid selector values.
+    {selector: "#container", isMatch: true},
+    {selector: "#container  ", isMatch: true, normalizedSelector: "#container"},
+    {selector: "  #container ", isMatch: true, normalizedSelector: "#container"},
+    {selector: ".style1", isMatch: true},
+    {selector: "div.style1", isMatch: true},
+    {selector: "div:not(#non-existing-id)", isMatch: true},
+    {selector: "div", isMatch: true},
+    {selector: "*", isMatch: true},
+
+    {selector: "#no-match", isMatch: false},
+    {selector: "ÇĞıİ", isMatch: false},
+    {selector: "🤓", isMatch: false},
+
+    {selector: "[language]", isMatch: true},
+    {selector: "[language-no]", isMatch: false},
+    {selector: "[lang=\"zh-CN\"]", isMatch: true},
+    {selector: "[lang=\"ab-CD\"]", isMatch: false},
+    {selector: "[segment~=\"43\"]", isMatch: true},
+    {selector: "[segment~=\"42\"]", isMatch: true},
+    {selector: "[lang|=\"zh\"]", isMatch: true},
+    {selector: "[lang|=\"zh-CN\"]", isMatch: true},
+    {selector: "[lang|=\"ab\"]", isMatch: false},
+    {selector: "[lang|=\"z\"]", isMatch: false},
+    {selector: "[lang^=\"z\"]", isMatch: true},
+    {selector: "[lang^=\"ab\"]", isMatch: false},
+    {selector: "[segment$=\"43\"]", isMatch: true},
+    {selector: "[segment$=\"3\"]", isMatch: true},
+    {selector: "[segment$=\"42\"]", isMatch: false},
+    {selector: "[lang*=\"-\"]", isMatch: true},
+    {selector: "[lang*=\"h-\"]", isMatch: true},
+    {selector: "[lang*=\"ab\"]", isMatch: false},
+
+    {selector: "*|div", isMatch: true, normalizedSelector: "div"},
+    {selector: "|div", isMatch: false},
+    {selector: "*|a", isMatch: false, normalizedSelector: "a"},
+    {selector: "*|*", isMatch: true, normalizedSelector: "*"},
+    {selector: "[*|lang]", isMatch: true, normalizedSelector: "[*|lang]"},
+    {selector: "[|lang]", isMatch: true, normalizedSelector: "[lang]"},
+
+    {selector: ":active", isMatch: false},
+    {selector: ":not(:active)", isMatch: true},
+    {selector: "*:not(:active)", isMatch: true, normalizedSelector: ":not(:active)"},
+    {selector: "div:not(:active)", isMatch: true},
+    {selector: "div:active", isMatch: false},
+
+    {selector: "span div", isMatch: true},
+    {selector: "span  div  ", isMatch: true, normalizedSelector: "span div"},
+    {selector: "span > div", isMatch: true},
+    {selector: "div div", isMatch: false},
+    {selector: "div > div", isMatch: false},
+    {selector: "p + div", isMatch: true},
+    {selector: "span + div", isMatch: false},
+    {selector: "p ~ div", isMatch: true},
+    {selector: "span ~ div", isMatch: false},
+
+    {selector: ":lang(zh-CN)", isMatch: true},
+    {selector: ":lang(zh)", isMatch: true},
+    {selector: ":lang(tr-AZ)", isMatch: false},
+
+    {selector: "::after", isMatch: false, normalizedSelector: "::after"},
+    {selector: ":after", isMatch: false, normalizedSelector: "::after"},
+    {selector: "::before", isMatch: false, normalizedSelector: "::before"},
+    {selector: ":before", isMatch: false, normalizedSelector: "::before"},
+    {selector: "::first-letter", isMatch: false, normalizedSelector: "::first-letter"},
+    {selector: ":first-letter", isMatch: false, normalizedSelector: "::first-letter"},
+    {selector: "::first-line", isMatch: false, normalizedSelector: "::first-line"},
+    {selector: ":first-line", isMatch: false, normalizedSelector: "::first-line"},
+
+    {selector: "div:focus:not([lang=\"zh-CN\"])", isMatch: false},
+    {selector: "div[lang=\"zh-CN\"]:not(:focus)", isMatch: true},
+  ].forEach(function(testCase) {
+    test(function() {
+      // Check if starting with the default value.
+      assert_equals(rule.selectorText, originalStyleSelector);
+
+      this.add_cleanup(function() { rule.selectorText = originalStyleSelector; });
+
+      assertColors(false);
+
+      rule.selectorText = testCase.selector;
+
+      var expectedSelector = testCase.normalizedSelector ? testCase.normalizedSelector : testCase.selector;
+
+      assert_equals(rule.selectorText, expectedSelector);
+
+      assertColors(testCase.isMatch);
+    }, "CSSStyleRule: selectorText value: |" + testCase.selector + "| isMatch: " + testCase.isMatch);
+  });
+</script>
diff --git a/css/cssom/CSSStyleSheet.html b/css/cssom/CSSStyleSheet.html
index 62ad133..86016ef 100644
--- a/css/cssom/CSSStyleSheet.html
+++ b/css/cssom/CSSStyleSheet.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>CSSOM - CSSStyleSheet interface</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#the-cssstylesheet-interface">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <style id="my-stylesheet">
diff --git a/css/cssom/GetBoundingRect.html b/css/cssom/GetBoundingRect.html
deleted file mode 100644
index 6ab5e4c..0000000
--- a/css/cssom/GetBoundingRect.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>getBoundingClientRect</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-
-    <style>
-      #foo {
-        margin: 0px 0px 0px 5px;
-        transform: translate(10px, 200px);
-        position: fixed;
-        left: 5px;
-        background-color: red;
-      }
-    </style>
-  </head>
-  <body>
-    <div id="foo">
-      FOO
-    </div>
-    <script>
-        test(function () {
-            var foo = document.getElementById("foo").getBoundingClientRect();
-            assert_equals(foo.left, 20);
-        });
-    </script>
-  </body>
-</html>
diff --git a/css/cssom/MediaList.html b/css/cssom/MediaList.html
index b39fb20..8308ee8 100644
--- a/css/cssom/MediaList.html
+++ b/css/cssom/MediaList.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>CSSOM - MediaList interface</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#the-medialist-interface">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <style>
@@ -14,6 +15,7 @@
         var media = document.styleSheets[0].cssRules[0].media;
         assert_equals(media.length, 3, "MediaList length attribute");
         assert_equals(media.mediaText, "screen and (min-width: 480px), print, projection", "MediaList mediaText attribute");
+        assert_equals(media.toString(), "screen and (min-width: 480px), print, projection", "MediaList toString method");
         assert_equals(media[0], "screen and (min-width: 480px)", "MediaList indexed getter");
         assert_equals(media[1], "print", "MediaList indexed getter");
         assert_equals(media[2], "projection", "MediaList indexed getter");
@@ -24,6 +26,7 @@
         media.deleteMedium("print");
         assert_equals(media.length, 2, "MediaList length attribute after delete method");
         assert_equals(media.mediaText, "screen and (min-width: 480px), projection", "MediaList mediaText attribute after delete method");
+        assert_equals(media.toString(), "screen and (min-width: 480px), projection", "MediaList toString method after delete method");
         assert_equals(media[1], "projection", "MediaList indexed getter after delete method");
         assert_equals(media[2], undefined, "MediaList indexed getter with out of range after delete method");
         assert_equals(media.item(1), "projection", "MediaList indexed getter after delete method");
@@ -32,11 +35,20 @@
         media.appendMedium("speech");
         assert_equals(media.length, 3, "MediaList length attribute after append method");
         assert_equals(media.mediaText, "screen and (min-width: 480px), projection, speech", "MediaList mediaText attribute after append method");
+        assert_equals(media.toString(), "screen and (min-width: 480px), projection, speech", "MediaList toString method after append method");
         assert_equals(media[1], "projection", "MediaList indexed getter after append method");
         assert_equals(media[2], "speech", "MediaList indexed getter after append method");
         assert_equals(media[3], undefined, "MediaList indexed getter with out of range after append method");
         assert_equals(media.item(2), "speech", "MediaList item method after append method");
         assert_equals(media.item(3), null, "MediaList item method after append method");
+
+        media.mediaText = null;
+        assert_equals(media.mediaText, "", "MediaList mediaText attribute should be empty string in case of null");
+        assert_equals(media.toString(), "", "MediaList toString method should be empty string in case of null");
+
+        var rule = document.styleSheets[0].cssRules[0];
+        rule.media = "speech";
+        assert_equals(rule.media.mediaText, "speech", "MediaList mediaText attribute should be updated");
     });
     </script>
 </head>
diff --git a/css/cssom/MediaList2.xhtml b/css/cssom/MediaList2.xhtml
index fb1062c..73acbde 100644
--- a/css/cssom/MediaList2.xhtml
+++ b/css/cssom/MediaList2.xhtml
@@ -8,15 +8,6 @@
 <style media="screen, print" id="test-style"></style>
 <script src="/resources/testharness.js"/>
 <script src="/resources/testharnessreport.js"/>
-<script id="metadata_cache">/*
-{
-  "MediaList": {},
-  "MediaList.mediaText": {},
-  "MediaList.length": {},
-  "MediaList getter": {},
-  "MediaList.item": {}
-}
-*/</script>
 </head>
 <body>
 <div id="log"/>
diff --git a/css/cssom/StyleSheetList.html b/css/cssom/StyleSheetList.html
index 3582640..bb7d2ff 100644
--- a/css/cssom/StyleSheetList.html
+++ b/css/cssom/StyleSheetList.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>CSSOM - StyleSheetList interface</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#the-stylesheetlist-interface">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <style>
diff --git a/css/cssom/computed-style-001.html b/css/cssom/computed-style-001.html
index 2cdfc9e..80bf06c 100644
--- a/css/cssom/computed-style-001.html
+++ b/css/cssom/computed-style-001.html
@@ -23,14 +23,6 @@
         height: 100px;
     }
   </style>
-<script id="metadata_cache">/*
-{
-  "read_only": { "assert": "do not allow modifications to a computed CSSStyleDeclaration" },
-  "property_values": { "assert": "Directly set properties are resolved" },
-  "inherited_property_values": { "assert": "Inherited properties are resolved" },
-  "relative_property_values": { "assert": "Relative properties are resolved" }
-}
-*/</script>
  </head>
  <body>
  <noscript>Test not run - javascript required.</noscript>
diff --git a/css/cssom/computed-style-002.html b/css/cssom/computed-style-002.html
new file mode 100644
index 0000000..7b31c96
--- /dev/null
+++ b/css/cssom/computed-style-002.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>CSS Test: getComputedStyle - resolved width in iframe</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org" />
+<link rel="help" href="https://drafts.csswg.org/cssom/#resolved-values" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="frm" width="100"></iframe>
+<script>
+  test(() => {
+    const frmDoc = frm.contentWindow.document;
+    frmDoc.open();
+    frmDoc.write('<body style="margin:0"><div style="width:100%"></div>');
+    frmDoc.close();
+
+    assert_equals(frm.contentWindow.getComputedStyle(frmDoc.querySelector("div")).width, "100px");
+  }, "Check that a percent width in an iframe is resolved against iframe width for getComputedStyle.");
+</script>
diff --git a/css/cssom/computed-style-003.html b/css/cssom/computed-style-003.html
new file mode 100644
index 0000000..e73b793
--- /dev/null
+++ b/css/cssom/computed-style-003.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>CSS Test: getComputedStyle - resolved width in iframe dynamic display</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org" />
+<link rel="help" href="https://drafts.csswg.org/cssom/#resolved-values" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="frm" width="100" style="display:none"></iframe>
+<script>
+  const frmDoc = frm.contentWindow.document;
+  frmDoc.open();
+  frmDoc.write('<body style="margin:0"><div style="width:100%"></div>');
+  frmDoc.close();
+
+  document.body.offsetWidth; // Make sure we layout the top document.
+
+  test(() => {
+    frm.style.display = "inline";
+    assert_equals(frm.contentWindow.getComputedStyle(frmDoc.querySelector("div")).width, "100px");
+  }, "Check that a percent width in an iframe is the resolved width when the iframe is displayed.");
+</script>
diff --git a/css/cssom/computed-style-004.html b/css/cssom/computed-style-004.html
new file mode 100644
index 0000000..c5b0871
--- /dev/null
+++ b/css/cssom/computed-style-004.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>CSS Test: getComputedStyle - resolved width in nested iframes dynamic width</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org" />
+<link rel="help" href="https://drafts.csswg.org/cssom/#resolved-values" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="outer" width="100" scrolling="no" frameborder="0"></iframe>
+<script>
+  const outerDoc = outer.contentWindow.document;
+  outerDoc.open();
+  outerDoc.write('<body style="margin:0"><iframe id="inner" scrolling="no" frameborder="0" style="width:100%"></iframe>');
+  outerDoc.close();
+
+  const innerWindow = outerDoc.querySelector("#inner").contentWindow;
+  const innerDoc = innerWindow.document;
+  innerDoc.open();
+  innerDoc.write('<body style="margin:0"><div style="width:100%"></div>');
+  innerDoc.close();
+  innerDoc.body.offsetWidth; // Make sure we layout the top document.
+
+  test(() => {
+    assert_equals(innerWindow.getComputedStyle(innerDoc.querySelector("div")).width, "100px");
+  }, "Check that the initial width is 100px.");
+
+  test(() => {
+    outer.setAttribute("width", "200");
+    assert_equals(innerWindow.getComputedStyle(innerDoc.querySelector("div")).width, "200px");
+  }, "Check that the resolved width of the inner div is affected by changing the width of outer iframe.");
+</script>
diff --git a/css/cssom/computed-style-set-property.html b/css/cssom/computed-style-set-property.html
new file mode 100644
index 0000000..a5fcc15
--- /dev/null
+++ b/css/cssom/computed-style-set-property.html
@@ -0,0 +1,37 @@
+<!DOCTYPE>
+<title>NoModificationAllowedError when mutating read only properties</title>
+<link rel="author" title="Anders Hartvoll Ruud" href="andruud@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/cssom-1/#dom-cssstyledeclaration-setpropertyvalue">
+<meta name="assert" content="This test verifies that NoModificationAllowedError is thrown when mutating read only properties" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body></body>
+<script>
+test(function(t) {
+    assert_equals(document.defaultView.getComputedStyle(document.body, null).parentRule, null);
+}, "Computed style parent (should be null)");
+
+test(function(t) {
+    assert_throws("NoModificationAllowedError", function() {
+        document.defaultView.getComputedStyle(document.body, null).color = "blue";
+    });
+}, "Exception thrown when trying to change a computed style declaration via property");
+
+test(function(t) {
+    assert_throws("NoModificationAllowedError", function() {
+        document.defaultView.getComputedStyle(document.body, null).setProperty("color", "blue");
+    });
+}, "Exception thrown when trying to change a computed style declaration via setProperty");
+
+test(function(t) {
+    assert_throws("NoModificationAllowedError", function() {
+        document.defaultView.getComputedStyle(document.body, null).webkitTransition = "";
+    });
+}, "Exception thrown when trying to change a computed style alias via property");
+
+test(function(t) {
+    assert_throws("NoModificationAllowedError", function() {
+        document.defaultView.getComputedStyle(document.body, null).setProperty("webkitTransition", "");
+    });
+}, "Exception thrown when trying to change a computed style alias via setProperty");
+</script>
diff --git a/css/cssom/css-style-attr-decl-block.html b/css/cssom/css-style-attr-decl-block.html
new file mode 100644
index 0000000..9b45d66
--- /dev/null
+++ b/css/cssom/css-style-attr-decl-block.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<link rel="author" title="Xidorn Quan" href="mailto:me@upsuper.org">
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#css-declaration-blocks">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+function createTestElement(style) {
+  let wrapper = document.createElement("div");
+  wrapper.innerHTML = `<div id="test" style="${style}"></div>`;
+  return wrapper.querySelector("#test");
+}
+
+test(function() {
+  let elem = createTestElement("z-index: 10;");
+  assert_equals(elem.style.cssText, "z-index: 10;");
+}, "Style attribute should create CSS declaration block based on its content");
+
+test(function() {
+  let elem = createTestElement("z-index: 20;");
+  let style = elem.style;
+  assert_equals(style.cssText, "z-index: 20;");
+  function assert_css_text(value, action) {
+    assert_equals(style.cssText, value, "CSS declaration block after " + action);
+  }
+  elem.setAttribute("style", "z-index: 21;");
+  assert_css_text("z-index: 21;", "changing the style attribute");
+  elem.removeAttribute("style");
+  assert_css_text("", "removing the style attribute");
+  elem.setAttribute("style", "position: absolute;");
+  assert_css_text("position: absolute;", "adding style attribute again");
+}, "Changes to style attribute should reflect on CSS declaration block");
+
+test(function() {
+  let elem = createTestElement("z-index: 30;");
+  let style = elem.style;
+  assert_equals(style.cssText, "z-index: 30;");
+  function assert_attr(value, action) {
+    assert_equals(elem.getAttribute("style"), value, "style attribute after " + action);
+  }
+  style.setProperty("z-index", "31");
+  assert_attr("z-index: 31;", "changing property in CSS declaration block");
+  style.removeProperty("z-index");
+  assert_attr("", "removing property from CSS declaration block");
+  style.setProperty("position", "absolute");
+  assert_attr("position: absolute;", "adding property to CSS declaration block");
+  style.cssText = "z-index: 32;";
+  assert_attr("z-index: 32;", "changing cssText");
+  style.cssText = "z-index: 33; invalid";
+  assert_attr("z-index: 33;", "changing cssText to a partial invalid value");
+}, "Changes to CSS declaration block should reflect on style attribute");
+
+test(function() {
+  let elem = createTestElement("z-index: 40;");
+  let style = elem.style;
+  assert_equals(style.cssText, "z-index: 40;");
+  // Create an observer for the element.
+  let observer = new MutationObserver(function() {});
+  observer.observe(elem, {attributes: true, attributeOldValue: true});
+  function assert_record_with_old_value(oldValue, action) {
+    let records = observer.takeRecords();
+    assert_equals(records.length, 1, "number of mutation records after " + action);
+    let record = records[0];
+    assert_equals(record.type, "attributes", "mutation type after " + action);
+    assert_equals(record.attributeName, "style", "mutated attribute after " + action);
+    assert_equals(record.oldValue, oldValue, "old value after " + action);
+  }
+  style.setProperty("z-index", "41");
+  assert_record_with_old_value("z-index: 40;", "changing property in CSS declaration block");
+  style.cssText = "z-index: 42;";
+  assert_record_with_old_value("z-index: 41;", "changing cssText");
+  style.cssText = "z-index: 42;";
+  assert_record_with_old_value("z-index: 42;", "changing cssText with the same content");
+  style.removeProperty("z-index");
+  assert_record_with_old_value("z-index: 42;", "removing property from CSS declaration block");
+  // Mutation to shorthand properties should also trigger only one mutation record.
+  style.setProperty("margin", "1px");
+  assert_record_with_old_value("", "adding shorthand property to CSS declaration block");
+  style.removeProperty("margin");
+  assert_record_with_old_value("margin: 1px;", "removing shorthand property from CSS declaration block");
+  // Final sanity check.
+  assert_equals(elem.getAttribute("style"), "");
+}, "Changes to CSS declaration block should queue mutation record for style attribute");
+
+test(function() {
+  let elem = createTestElement("z-index: 50; invalid");
+  let style = elem.style;
+  assert_equals(style.cssText, "z-index: 50;");
+  // Create an observer for the element.
+  let observer = new MutationObserver(function() {});
+  observer.observe(elem, {attributes: true});
+  function assert_no_record(action) {
+    let records = observer.takeRecords();
+    assert_true(records.length == 0, "expect no record after " + action);
+  }
+  style.setProperty("z-index", "invalid");
+  assert_no_record("setting invalid value to property");
+  // Longhand property.
+  style.removeProperty("position");
+  assert_no_record("removing non-existing longhand property");
+  style.setProperty("position", "");
+  assert_no_record("setting empty string to non-existing longhand property");
+  // Shorthand property.
+  style.removeProperty("margin");
+  assert_no_record("removing non-existing shorthand property");
+  style.setProperty("margin", "");
+  assert_no_record("setting empty string to non-existing shorthand property");
+  // Check that the value really isn't changed.
+  assert_equals(elem.getAttribute("style"), "z-index: 50; invalid",
+                "style attribute after removing non-existing properties");
+}, "Removing non-existing property or setting invalid value on CSS declaration block shouldn't queue mutation record");
+
+test(function() {
+  let elem = createTestElement("background-image: url(./);");
+  let style = elem.style;
+  let base = document.createElement("base");
+  base.href = "/";
+  document.body.appendChild(elem);
+  let originalComputedValue = getComputedStyle(elem).backgroundImage;
+  document.head.appendChild(base);
+  this.add_cleanup(() => {
+    document.head.removeChild(base);
+    document.body.removeChild(elem);
+  });
+  style.setProperty("background-color", "green");
+  assert_equals(getComputedStyle(elem).backgroundImage, originalComputedValue,
+                "getComputedStyle(elem).backgroundImage after setting background-color");
+  style.setProperty("background-image", "url(./)");
+  assert_not_equals(getComputedStyle(elem).backgroundImage, originalComputedValue,
+                    "getComputedStyle(elem).backgroundImage after setting background-image");
+}, "Changes to CSS declaration block after a base URL change");
+</script>
diff --git a/css/cssom/css-style-attribute-modifications.html b/css/cssom/css-style-attribute-modifications.html
index 6177c33..524a5ad 100644
--- a/css/cssom/css-style-attribute-modifications.html
+++ b/css/cssom/css-style-attribute-modifications.html
@@ -9,5 +9,8 @@
   var el = document.getElementById("test");
   el.style.color = "";
   assert_true(el.hasAttribute("style"));
+
+  el.removeAttribute("style");
+  assert_false(el.hasAttribute("style"));
 }, "Mutating the style declaration doesn't remove the style attribute");
 </script>
diff --git a/css/cssom/css-style-reparse.html b/css/cssom/css-style-reparse.html
new file mode 100644
index 0000000..d5153cf
--- /dev/null
+++ b/css/cssom/css-style-reparse.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<html>
+<head>
+  <meta charset=utf-8>
+  <title>CSS Test: DOM modification re-parsing test</title>
+  <link rel="help" href="https://drafts.csswg.org/cssom/">
+  <link rel="help" href="http://www.w3.org/TR/cssom-1/#the-cssrule-interface">
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <style>div { min-width: 0px; }</style>
+  <style id="style-element"></style>
+</head>
+<body>
+<div id="test-div"></div>
+<script type="text/javascript">
+    var style = document.getElementById("style-element");
+    var div = document.getElementById("test-div");
+
+    function testProperty(prop) {
+      // Assigning an empty string to textContent or innerHTML should trigger a
+      // reparse only if the element is not empty.
+      style.sheet.insertRule("#test-div { min-width: 42px; }");
+      assert_equals(getComputedStyle(div).minWidth, "42px");
+
+      style[prop] = "";
+      assert_equals(getComputedStyle(div).minWidth, "42px");
+
+      style[prop] = " ";
+      assert_equals(getComputedStyle(div).minWidth, "0px");
+
+      style.sheet.insertRule("#test-div { min-width: 42px; }");
+      assert_equals(getComputedStyle(div).minWidth, "42px");
+
+      style[prop] = "";
+      assert_equals(getComputedStyle(div).minWidth, "0px");
+
+      style.sheet.insertRule("#test-div { min-width: 42px; }");
+      assert_equals(getComputedStyle(div).minWidth, "42px");
+
+      style.appendChild(document.createTextNode(""));
+      assert_equals(getComputedStyle(div).minWidth, "0px");
+
+      style.sheet.insertRule("#test-div { min-width: 42px; }");
+      assert_equals(getComputedStyle(div).minWidth, "42px");
+
+      style[prop] = "";
+      assert_equals(getComputedStyle(div).minWidth, "0px");
+    }
+
+    test(function() {
+      testProperty("textContent");
+    }, "style.textContent modification");
+
+    test(function() {
+      testProperty("innerHTML");
+    }, "style.innerHTML modification");
+</script>
+</body>
+</html>
diff --git a/css/cssom/cssimportrule.html b/css/cssom/cssimportrule.html
index d4a3250..1d7b18f 100644
--- a/css/cssom/cssimportrule.html
+++ b/css/cssom/cssimportrule.html
@@ -14,18 +14,21 @@
     <style id="styleElement" type="text/css">
         @import url("support/a-green.css");
         @import url("support/a-green.css") screen;
+        @import url("support/a-green.css") all;
+        @page { background-color: red; }
     </style>
 </head>
 <body>
     <div id="log"></div>
 
     <script type="text/javascript">
-        var rule, ruleWithMedia;
+        var styleSheet, ruleList, rule, ruleWithMedia, ruleWithMediaAll;
         setup(function() {
-            var styleSheet = document.getElementById("styleElement").sheet;
-            var ruleList = styleSheet.cssRules;
+            styleSheet = document.getElementById("styleElement").sheet;
+            ruleList = styleSheet.cssRules;
             rule = ruleList[0];
             ruleWithMedia = ruleList[1];
+            ruleWithMediaAll = ruleList[2];
         });
 
         test(function() {
@@ -62,6 +65,7 @@
             assert_equals(typeof rule.cssText, "string");
             assert_equals(rule.cssText, '@import url("support/a-green.css");');
             assert_equals(ruleWithMedia.cssText, '@import url("support/a-green.css") screen;');
+            assert_equals(ruleWithMediaAll.cssText, '@import url("support/a-green.css") all;');
             assert_equals(rule.parentRule, null);
             assert_true(rule.parentStyleSheet instanceof CSSStyleSheet);
         }, "Values of CSSRule attributes");
@@ -83,6 +87,22 @@
             assert_true(ruleWithMedia.media.length > 0);
             assert_equals(ruleWithMedia.media.mediaText, "screen");
         }, "Values of CSSImportRule attributes");
+
+        test(function() {
+            ruleWithMedia.media = "print";
+            assert_equals(ruleWithMedia.media.mediaText, "print");
+        }, "CSSImportRule : MediaList mediaText attribute should be updated due to [PutForwards]");
+
+        test(function() {
+            var ruleWithPage = ruleList[3];
+            ruleWithPage.style = "background-color: green;"
+            assert_equals(ruleWithPage.style.cssText, "background-color: green;");
+        }, "CSSStyleDeclaration cssText attribute should be updated due to [PutForwards]");
+
+        test(function() {
+            styleSheet.media = "screen";
+            assert_equals(styleSheet.media.mediaText, "screen");
+        }, "StyleSheet : MediaList mediaText attribute should be updated due to [PutForwards]");
     </script>
 </body>
 </html>
diff --git a/css/cssom/cssom-fontfacerule-constructors.html b/css/cssom/cssom-fontfacerule-constructors.html
index c94b63e..ed1cab2 100644
--- a/css/cssom/cssom-fontfacerule-constructors.html
+++ b/css/cssom/cssom-fontfacerule-constructors.html
@@ -4,7 +4,7 @@
         <title>CSSOM Parsing Test: @font-face rules toString() as valid interfaces</title>
         <link rel="author" title="Paul Irish" href="mailto:paul.irish@gmail.com">
         <link rel="reviewer" title="Ms2ger" href="mailto:ms2ger@gmail.com"> <!-- 2012-06-17 -->
-        <link rel="help" href="http://www.w3.org/TR/cssom-1/#css-font-face-rule">
+        <link rel="help" href="https://drafts.csswg.org/css-fonts-4/#om-fontface">
 
         <meta name="flags" content="dom">
 
diff --git a/css/cssom/cssom-fontfacerule.html b/css/cssom/cssom-fontfacerule.html
index 0620910..9a06808 100644
--- a/css/cssom/cssom-fontfacerule.html
+++ b/css/cssom/cssom-fontfacerule.html
@@ -3,7 +3,7 @@
     <head>
         <title>CSSOM Parsing Test: @font-face rules parsed into CSSOM CSSFontFaceRules</title>
         <link rel="author" title="Paul Irish" href="mailto:paul.irish@gmail.com">
-        <link rel="help" href="http://www.w3.org/TR/cssom-1/#css-font-face-rule">
+        <link rel="help" href="https://drafts.csswg.org/css-fonts-4/#om-fontface">
 
         <meta name="flags" content="dom">
 
@@ -44,7 +44,7 @@
 
             test(function(){
 
-                assert_equals(validRules[0].style.src, 'url(http://foo/bar/font.ttf)');
+                assert_equals(validRules[0].style.src, 'url("http://foo/bar/font.ttf")');
                 assert_equals(validRules[1].style.fontFamily, 'STIXGeneral');
 
                 /* unimplemented @font-face properties are not represented in CSSOM */
diff --git a/css/cssom/cssom-setProperty-shorthand.html b/css/cssom/cssom-setProperty-shorthand.html
index fe2ad47..4c0f9a2 100644
--- a/css/cssom/cssom-setProperty-shorthand.html
+++ b/css/cssom/cssom-setProperty-shorthand.html
@@ -42,8 +42,8 @@
 
             element = document.createElement('span');
 
-            function canSetProperty(propertyName) {
-                element.style.setProperty(propertyName, 'initial');
+            function canSetProperty(propertyName, priority) {
+                element.style.setProperty(propertyName, 'initial', priority);
                 return element.style.getPropertyValue(propertyName) == 'initial';
             }
 
@@ -56,13 +56,21 @@
                 var propertyName = shorthandProperties[i];
 
                 test(function(){
-                    assert_true(canSetProperty(propertyName), 'can setPropertyValue with shorthand');
+                    assert_true(canSetProperty(propertyName, ''), 'can setPropertyValue with shorthand');
                 }, 'shorthand ' + propertyName + ' can be set with setProperty');
 
                 test(function(){
                     assert_true(canRemoveProperty(propertyName), 'can setPropertyValue with shorthand');
                 }, 'shorthand ' + propertyName + ' can be removed with removeProperty');
 
+                test(function(){
+                    assert_true(canSetProperty(propertyName, 'important'), 'can setPropertyValue with shorthand');
+                }, 'shorthand ' + propertyName + ' can be set with setProperty and priority !important');
+
+                test(function(){
+                    assert_true(canRemoveProperty(propertyName), 'can setPropertyValue with shorthand');
+                }, 'shorthand ' + propertyName + ' can be removed with removeProperty even when set with !important');
+
             }
         </script>
     </body>
diff --git a/css/cssom/cssstyledeclaration-csstext-final-delimiter.html b/css/cssom/cssstyledeclaration-csstext-final-delimiter.html
new file mode 100644
index 0000000..01b0a32
--- /dev/null
+++ b/css/cssom/cssstyledeclaration-csstext-final-delimiter.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSSOM - CSSStyleDeclaration - Text - Serialization - Delimiters</title>
+    <link rel="author" title="Glenn Adams" href="mailto:glenn@skynav.com"/>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface"/>
+    <meta name="flags" content="dom"/>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <div id="box"></div>
+    <script>
+      var style = document.getElementById('box').style;
+
+      test(function(){
+        style.cssText = "";
+        assert_equals(style.cssText, "");
+      }, 'inline style - text - delimiters - zero declarations');
+
+      test(function(){
+        style.cssText = "left: 10px";
+        assert_equals(style.cssText, "left: 10px;");
+      }, 'inline style - text - delimiters - one declaration');
+
+      test(function(){
+        style.cssText = "left: 10px; right: 20px";
+        assert_equals(style.cssText, "left: 10px; right: 20px;");
+      }, 'inline style - text - delimiters - two declarations');
+    </script>
+  </body>
+</html>
diff --git a/css/cssom/cssstyledeclaration-csstext-important.html b/css/cssom/cssstyledeclaration-csstext-important.html
new file mode 100644
index 0000000..c12faf6
--- /dev/null
+++ b/css/cssom/cssstyledeclaration-csstext-important.html
@@ -0,0 +1,12 @@
+<title>CSSOM test: setting a property with cssText and !important</title>
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#dom-cssstyledeclaration-csstext">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="box"></div>
+<script>
+  var style = document.getElementById('box').style;
+  test(function(){
+      style.cssText = "padding: 10px !important; padding-left: 20px;";
+      assert_equals(style.getPropertyValue("padding-left"), "10px");
+  }, "padding-left should be taken from the !important property");
+</script>
diff --git a/css/cssom/cssstyledeclaration-mutationrecord-001.html b/css/cssom/cssstyledeclaration-mutationrecord-001.html
new file mode 100644
index 0000000..5bd8456
--- /dev/null
+++ b/css/cssom/cssstyledeclaration-mutationrecord-001.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSOM: CSSStyleDeclaration.setPropertyValue queues a mutation record when not actually mutated</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setpropertyvalue">
+<link rel="help" href="https://drafts.csswg.org/cssom/#update-style-attribute-for">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+  document.documentElement.style.top = "0px";
+
+  let test = async_test("CSSStyleDeclaration.setPropertyValue queues a mutation record, even if not mutated");
+  let m = new MutationObserver(function(r) {
+    assert_equals(r.length, 1);
+    test.done();
+  });
+
+  m.observe(document.documentElement,  { attributes: true });
+  document.documentElement.style.top = "0px";
+</script>
diff --git a/css/cssom/cssstyledeclaration-mutationrecord-002.html b/css/cssom/cssstyledeclaration-mutationrecord-002.html
new file mode 100644
index 0000000..1cdb41a
--- /dev/null
+++ b/css/cssom/cssstyledeclaration-mutationrecord-002.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSOM: CSSStyleDeclaration.setPropertyValue doesn't queue a mutation record for invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setpropertyvalue">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+  let test = async_test("CSSStyleDeclaration.setPropertyValue doesn't queue a mutation record when setting invalid values");
+  let m = new MutationObserver(test.unreached_func("shouldn't queue a mutation record"));
+  m.observe(document.documentElement,  { attributes: true });
+
+  document.documentElement.style.width = "-100px";
+  requestAnimationFrame(() => test.done());
+</script>
diff --git a/css/cssom/cssstyledeclaration-mutationrecord-003.html b/css/cssom/cssstyledeclaration-mutationrecord-003.html
new file mode 100644
index 0000000..7a99dfc
--- /dev/null
+++ b/css/cssom/cssstyledeclaration-mutationrecord-003.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSOM: CSSStyleDeclaration.removeProperty doesn't queue a mutation record when not actually removed, invoked from setPropertyValue</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setpropertyvalue">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+  let test = async_test("CSSStyleDeclaration.removeProperty doesn't queue a mutation record when not actually removed, invoked from setPropertyValue");
+  document.documentElement.style.top = "0";
+  let m = new MutationObserver(test.unreached_func("shouldn't queue a mutation record"));
+  m.observe(document.documentElement,  { attributes: true });
+
+  document.documentElement.style.width = "";
+  requestAnimationFrame(() => test.done());
+</script>
diff --git a/css/cssom/cssstyledeclaration-mutationrecord-004.html b/css/cssom/cssstyledeclaration-mutationrecord-004.html
new file mode 100644
index 0000000..55956df
--- /dev/null
+++ b/css/cssom/cssstyledeclaration-mutationrecord-004.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSOM: CSSStyleDeclaration.removeProperty doesn't queue a mutation record when not actually removed</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setpropertyvalue">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+  let test = async_test("CSSStyleDeclaration.removeProperty doesn't queue a mutation record when not actually removed");
+  document.documentElement.style.top = "0";
+  let m = new MutationObserver(test.unreached_func("shouldn't queue a mutation record"));
+  m.observe(document.documentElement,  { attributes: true });
+
+  document.documentElement.style.removeProperty("width");
+  requestAnimationFrame(() => test.done());
+</script>
diff --git a/css/cssom/cssstyledeclaration-setter-order.html b/css/cssom/cssstyledeclaration-setter-order.html
new file mode 100644
index 0000000..702b38f
--- /dev/null
+++ b/css/cssom/cssstyledeclaration-setter-order.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<title>CSSOM test: order of declarations after setting via CSSOM</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#set-a-css-declaration-value">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+  function generateCSSDeclBlock(props) {
+    let elem = document.createElement("div");
+    let cssText = props.map(([prop, value]) => `${prop}: ${value};`).join(" ");
+    elem.setAttribute("style", cssText);
+    return elem.style;
+  }
+  function checkOrder(block, props, msg) {
+    assert_array_equals(Array.from(block), props, `Property order should match ${msg}`);
+  }
+  function arrayWithItemsAtEnd(array, items) {
+    let result = array.filter(item => !items.includes(item));
+    return result.concat(items);
+  }
+
+  const SUBPROPS = {
+    "margin": ["margin-top", "margin-right", "margin-bottom", "margin-left"],
+    "padding": ["padding-top", "padding-right", "padding-bottom", "padding-left"],
+  };
+
+  test(function() {
+    let block = generateCSSDeclBlock([
+      ["top", "1px"],
+      ["bottom", "2px"],
+      ["left", "3px"],
+      ["right", "4px"],
+    ]);
+    let expectedOrder = ["top", "bottom", "left", "right"];
+    checkOrder(block, expectedOrder, "in initial block");
+
+    block.setProperty("top", "5px");
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["top"]);
+    checkOrder(block, expectedOrder, "after setting existing property");
+
+    block.setProperty("bottom", "2px");
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["bottom"]);
+    checkOrder(block, expectedOrder, "after setting existing property with identical value");
+  }, "setProperty with existing longhand should change order");
+
+  test(function() {
+    let block = generateCSSDeclBlock([
+      ["top", "1px"],
+      ["bottom", "2px"],
+      ["left", "3px"],
+      ["right", "4px"],
+    ]);
+    let expectedOrder = ["top", "bottom", "left", "right"];
+    checkOrder(block, expectedOrder, "in initial block");
+
+    block.top = "5px";
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["top"]);
+    checkOrder(block, expectedOrder, "after setting existing property");
+
+    block.bottom = "2px";
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["bottom"]);
+    checkOrder(block, expectedOrder, "after setting existing property with identical value");
+  }, "invoke property setter with existing longhand should change order");
+
+  test(function() {
+    let block = generateCSSDeclBlock([
+      ["margin", "1px"],
+      ["top", "2px"],
+      ["padding", "3px"],
+    ]);
+    let expectedOrder = SUBPROPS["margin"].concat(["top"]).concat(SUBPROPS["padding"]);
+    checkOrder(block, expectedOrder, "in initial block");
+
+    block.setProperty("margin", "4px");
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["margin"]);
+    checkOrder(block, expectedOrder, "after setting an existing shorthand");
+
+    block.setProperty("padding", "3px");
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["padding"]);
+    checkOrder(block, expectedOrder, "after setting an existing shorthand with identical value");
+
+    block.setProperty("margin-bottom", "5px");
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["margin-bottom"]);
+    checkOrder(block, expectedOrder, "after setting a longhand in an existing shorthand");
+  }, "setProperty with existing shorthand should change order");
+
+  test(function() {
+    let block = generateCSSDeclBlock([
+      ["margin", "1px"],
+      ["top", "2px"],
+      ["padding", "3px"],
+    ]);
+    let expectedOrder = SUBPROPS["margin"].concat(["top"]).concat(SUBPROPS["padding"]);
+    checkOrder(block, expectedOrder, "in initial block");
+
+    block.margin = "4px";
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["margin"]);
+    checkOrder(block, expectedOrder, "after setting an existing shorthand");
+
+    block.padding = "3px";
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["padding"]);
+    checkOrder(block, expectedOrder, "after setting an existing shorthand with identical value");
+
+    block.marginBottom = "5px";
+    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["margin-bottom"]);
+    checkOrder(block, expectedOrder, "after setting a longhand in an existing shorthand");
+  }, "invoke property setter with existing shorthand should change order");
+</script>
diff --git a/css/cssom/font-shorthand-serialization.html b/css/cssom/font-shorthand-serialization.html
new file mode 100644
index 0000000..a56c204
--- /dev/null
+++ b/css/cssom/font-shorthand-serialization.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<title>Serialization of font shorthand</title>
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#serialize-a-css-declaration-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="target" style="font: 10px/1 Ahem;"></div>
+<script>
+    test(function() {
+        var target = document.getElementById('target');
+        assert_equals(target.style.cssText, 'font: 10px/1 Ahem;');
+        assert_equals(target.style.font, '10px/1 Ahem');
+    }, "The font shorthand should be serialized just like any other shorthand.");
+</script>
+</html>
diff --git a/css/cssom/getComputedStyle-detached-subtree.html b/css/cssom/getComputedStyle-detached-subtree.html
new file mode 100644
index 0000000..1cbb92a
--- /dev/null
+++ b/css/cssom/getComputedStyle-detached-subtree.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSOM: getComputedStyle returns no style for elements not in the tree</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="host">
+  <div id="non-slotted">
+    <div id="non-slotted-descendant"></div>
+  </div>
+</div>
+<iframe srcdoc="<html></html>" style="display: none"></iframe>
+<script>
+function testNoComputedStyle(element, description) {
+  test(function() {
+    assert_true(!!element);
+    let style = getComputedStyle(element);
+    assert_true(!!style);
+    assert_true(style.length === 0);
+    assert_equals(style.color, "");
+  }, `getComputedStyle returns no style for ${description}`);
+}
+
+let detached = document.createElement('div');
+testNoComputedStyle(detached, "detached element");
+
+testNoComputedStyle(document.querySelector('iframe').contentDocument.documentElement,
+                    "element in non-rendered iframe (display: none)");
+
+host.attachShadow({ mode: "open" });
+testNoComputedStyle(document.getElementById('non-slotted'),
+                    "element outside the flat tree");
+
+testNoComputedStyle(document.getElementById('non-slotted-descendant'),
+                    "descendant outside the flat tree");
+
+let shadowRoot = detached.attachShadow({ mode: "open" });
+shadowRoot.innerHTML = `
+  <div id="detached-shadow-tree-descendant"></div>
+`;
+testNoComputedStyle(shadowRoot.getElementById('detached-shadow-tree-descendant'),
+                    "shadow tree outside of flattened tree");
+</script>
diff --git a/css/cssom/getComputedStyle-dynamic-subdoc.html b/css/cssom/getComputedStyle-dynamic-subdoc.html
new file mode 100644
index 0000000..aa49dc3
--- /dev/null
+++ b/css/cssom/getComputedStyle-dynamic-subdoc.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSOM: getComputedStyle cross-doc properly reflects media query changes</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<iframe id="frm" style="width: 100px; height: 100px"></iframe>
+<script>
+test(function() {
+  let frm = document.getElementById('frm');
+  let frmDoc = frm.contentWindow.document;
+  frmDoc.open();
+  frmDoc.write('<style>body { color: red } @media all and (min-width: 101px) { body { color: green } }</style><body>Should be green</body>');
+  frmDoc.close();
+
+  document.body.offsetTop;
+
+  assert_equals(
+    getComputedStyle(frmDoc.body).color,
+    "rgb(255, 0, 0)",
+    "Initial color should be red"
+  );
+
+  frm.style.width = "200px";
+
+  assert_equals(
+    getComputedStyle(frmDoc.body).color,
+    "rgb(0, 128, 0)",
+    "style should have been updated to account for media query changes"
+  );
+}, "getComputedStyle cross-doc properly reflects media query changes");
+</script>
diff --git a/css/cssom/getComputedStyle-pseudo.html b/css/cssom/getComputedStyle-pseudo.html
index ec7a863..f5a637a 100644
--- a/css/cssom/getComputedStyle-pseudo.html
+++ b/css/cssom/getComputedStyle-pseudo.html
@@ -109,4 +109,9 @@
                   "display: contents in " + pseudo + " should reflect other non-inherited properties in CSSOM");
   });
 }, "display: contents on pseudo-elements");
+test(function() {
+  var div = document.getElementById('test');
+  assert_throws(new TypeError(), () => getComputedStyle(div, "totallynotapseudo"),
+                "getComputedStyle with an unknown pseudo-element throws");
+}, "Unknown pseudo-elements throw");
 </script>
diff --git a/css/cssom/historical.html b/css/cssom/historical.html
index ef31d14..ddd264c 100644
--- a/css/cssom/historical.html
+++ b/css/cssom/historical.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <title>Historical features</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#changes-from-5-december-2013">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id=log></div>
@@ -10,6 +11,8 @@
   "preferredStyleSheetSet",
   "styleSheetSets",
   "enableStyleSheetsForSet",
+  "selectedStylesheetSet",
+  "preferredStylesheetSet",
 ].forEach(function(name) {
   test(function() {
     assert_false(name in document);
diff --git a/css/cssom/index-001.html b/css/cssom/index-001.html
deleted file mode 100644
index ca53e50..0000000
--- a/css/cssom/index-001.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!doctype html>
-<head>
-  <title>CSS OM: CSS Values</title>
-  <link rel="author" title="Divya Manian" href="mailto:manian@adobe.com">
-  <link rel="help" href="http://www.w3.org/TR/cssom-1/#css-values">
-  <meta name="flags" content="dom">
-  <meta name="assert" content="The style value should be serialized to margin: 20px;">
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-    <div id="cssomtestElm"></div>
-    <div id="log"></div>
-    <script>
-        var testElm = document.getElementById('cssomtestElm');
-        // Set the transform
-        document.getElementById('cssomtestElm').style.margin = "20px 20px 20px 20px";
-
-        // Verify that the transform was set as expected
-        test(function() {assert_equals(
-            document.getElementById('cssomtestElm').style.cssText, //Actual
-            "margin: 20px;",  //Expected
-            "Margin should be serialized as 'margin: 20px;'")}, //Description
-            "margin_20px_20px"); //name
-    </script>
- </body>
- </html>
-
diff --git a/css/cssom/index-002.html b/css/cssom/index-002.html
deleted file mode 100644
index 29ec3a9..0000000
--- a/css/cssom/index-002.html
+++ /dev/null
@@ -1,79 +0,0 @@
-<!doctype html>
-<head>
-  <title>CSS OM: CSS Values</title>
-  <link rel="author" title="Divya Manian" href="mailto:manian@adobe.com">
-  <link rel="help" href="http://www.w3.org/TR/cssom-1/#css-values">
-  <meta name="flags" content="dom">
-  <meta name="assert" content="Testing Serialization of Shorthand Values">
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-    <div id="cssomtestElm"></div>
-    <div id="log"></div>
-    <script>
-    var tests = {
-      'border': [
-      ['border: 1px; border-top: 1px;', 'border: 1px;'],
-      ['border: 1px solid red;', 'border: 1px solid red;'],
-      ['border: 1px red;', 'border: 1px red;'],
-      ['border: red;', 'border: red;'],
-      ['border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px;', 'border: 1px;', ' (#2)'],
-      ['border-top: 1px; border-right: 2px; border-bottom: 3px; border-left: 4px;', 'border-width: 1px 2px 3px 4px;'],
-      ['border: 1px; border-top: 2px;', 'border-width: 2px 1px 1px;'],
-      ['border: 1px; border-top: 1px !important;',
-      'border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-width: 1px !important;'],
-
-      ['border: 1px; border-top-color: red;', 'border-width: 1px; border-top-color: red;'],
-      ['border: solid; border-style: dotted', 'border: dotted;'],
-      ['border-width: 1px;', 'border-width: 1px;']
-      ],
-
-      'overflow': [
-        ['overflow-x: scroll; overflow-y: hidden;', 'overflow: scroll hidden;'],
-        ['overflow-x: scroll; overflow-y: scroll;', 'overflow: scroll;']
-      ],
-      'outline': [
-        ['outline-width: 2px; outline-style: dotted; outline-color: blue;', 'outline: blue dotted 2px;']
-      ],
-      'margin': [
-        ['margin-top: 1px; margin-right: 2px; margin-bottom: 3px; margin-left: 4px;', 'margin: 1px 2px 3px 4px;']
-      ],
-      'list': [
-        ['list-style-type: circle; list-style-position: inside; list-style-image: initial;', 'list-style: circle inside;'],
-        ['list-style-type: lower-alpha;', 'list-style-type: lower-alpha;']
-      ],
-      'font-family': [
-      ['font-family: sans-serif; line-height: 2em; font-size: 3em; font-style: italic; font-weight: bold;',
-          'font-family: sans-serif; line-height: 2em; font-size: 3em; font-style: italic; font-weight: bold;']
-      ],
-      'padding': [
-        ['padding-top: 1px; padding-right: 2px; padding-bottom: 3px; padding-left: 4px;', 'padding: 1px 2px 3px 4px;'],
-      ]
-    }
-
-    var results = {};
-
-    var testElm = document.getElementById('cssomtestElm');
-    for (var test in tests) {
-      if(tests.hasOwnProperty(test)) {
-        results[test] = [];
-        var propertyTests = tests[test];
-
-        for (i = 0; i < propertyTests.length; i++) {
-          document.getElementById('cssomtestElm').setAttribute('style', propertyTests[i][0]);
-          var titleSuffix = propertyTests[i][2] || "";
-          results[test].push([
-            test + ' is expected to be ' + propertyTests[i][1] + titleSuffix,
-            document.getElementById('cssomtestElm').style.cssText,
-            propertyTests[i][1]
-          ]);
-        }
-
-        generate_tests(assert_equals, results[test]);
-      }
-    }
-
-    </script>
- </body>
- </html>
diff --git a/css/cssom/inline-style-001.html b/css/cssom/inline-style-001.html
index 3a9eebf..51f36f2 100644
--- a/css/cssom/inline-style-001.html
+++ b/css/cssom/inline-style-001.html
@@ -9,22 +9,6 @@
   <script src="/resources/testharness.js" type="text/javascript"></script>
   <script src="/resources/testharnessreport.js" type="text/javascript"></script>
  </head>
- <script id="metadata_cache">/*
-{
-  "CSSStyleDeclaration_accessible": { "assert": "Can access CSSStyleDeclaration through style property" },
-  "read": { "assert": "initial property values are correct" },
-  "csstext_write": {
-    "assert": ["setting cssText adds new properties",
-               "setting cssText removes existing properties",
-               "properties set through cssText are reflected in the computed style"]
-  },
-  "property_write": {
-    "assert": ["setProperty adds new properties",
-               "properties set through setProperty are reflected in the computed style"]
-  },
-  "shorthand_properties": { "assert": "shorthand property is expanded" }
-}
-*/</script>
  <body>
  <noscript>Test not run - javascript required.</noscript>
  <div id="log"></div>
@@ -32,15 +16,14 @@
  <script type="text/javascript">
     test(function() {
         var test = document.getElementById("test");
-        assert_own_property(test, "style");
-        assert_readonly(test, "style");
+        assert_idl_attribute(test, "style");
         declaration = test.style;
     }, "CSSStyleDeclaration_accessible", {
         assert: "Can access CSSStyleDeclaration through style property"
     });
 
     test(function() {
-        assert_regexp_match(declaration.cssText, /margin-left: 5px;\s*/);
+        assert_equals(declaration.cssText, "margin-left: 5px;");
         assert_equals(declaration.getPropertyValue("margin-left"), "5px");
     }, "read", {
         assert: "initial property values are correct"
@@ -48,7 +31,7 @@
 
     test(function() {
         declaration.cssText = "margin-left: 10px; padding-left: 10px;";
-        assert_regexp_match(declaration.cssText, /margin-left: 10px;\s+padding-left: 10px;\s+/);
+        assert_equals(declaration.cssText, "margin-left: 10px; padding-left: 10px;");
         assert_equals(declaration.length, 2);
         assert_equals(declaration.item(0), "margin-left");
         assert_equals(declaration.item(1), "padding-left");
diff --git a/css/cssom/insertRule-syntax-error-01.html b/css/cssom/insertRule-syntax-error-01.html
new file mode 100644
index 0000000..170213c
--- /dev/null
+++ b/css/cssom/insertRule-syntax-error-01.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/cssom-1/#insert-a-css-rule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+p { color: green; }
+</style>
+<p id="test">This text should be green.</p>
+<script>
+    test(function() {
+        assert_throws("SyntaxError", () => document.styleSheets[0].insertRule("p { color: red; } garbage", 1));
+        assert_equals(getComputedStyle(document.getElementById("test")).color, "rgb(0, 128, 0)");
+    }, "A syntax error in insertRule should throw and not affect the style of the page");
+</script>
diff --git a/css/cssom/interfaces.html b/css/cssom/interfaces.html
index e289c5a..08d04c07 100644
--- a/css/cssom/interfaces.html
+++ b/css/cssom/interfaces.html
@@ -31,7 +31,7 @@
 "use strict";
 var style_element, svg_element, xmlss_pi;
 
-function doTest([html, dom, cssom]) {
+function doTest([html, dom, uievents, cssom]) {
   style_element = document.getElementById('styleElement');
   svg_element = document.getElementById('svgElement');
   xmlss_pi = document.getElementById('xmlssPiIframe').contentDocument.firstChild;
@@ -39,6 +39,13 @@
   var idlArray = new IdlArray();
   var svg = "interface SVGElement : Element {};";
   idlArray.add_untested_idls(html + dom + svg);
+  idlArray.add_untested_idls(uievents, { only: [
+    'UIEvent',
+    'UIEventInit',
+    'MouseEvent',
+    'MouseEventInit',
+    'EventModifierInit'
+  ]});
   idlArray.add_idls(cssom);
 
   idlArray.add_objects({
@@ -81,6 +88,7 @@
   // Have to wait for onload
   return Promise.all([fetchData("/interfaces/html.idl"),
                       fetchData("/interfaces/dom.idl"),
+                      fetchData("/interfaces/uievents.idl"),
                       fetchData("/interfaces/cssom.idl"),
                       waitForLoad()])
                 .then(doTest);
diff --git a/css/cssom/medialist-dynamic-001-ref.html b/css/cssom/medialist-dynamic-001-ref.html
new file mode 100644
index 0000000..9715b5a
--- /dev/null
+++ b/css/cssom/medialist-dynamic-001-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+Should not be red.
diff --git a/css/cssom/medialist-dynamic-001.html b/css/cssom/medialist-dynamic-001.html
new file mode 100644
index 0000000..ab9c134
--- /dev/null
+++ b/css/cssom/medialist-dynamic-001.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: Dynamic changes to the stylesheet media attributes via CSSOM get reflected</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="match" href="medialist-dynamic-001-ref.html">
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-medialist-mediatext">
+<style media="all">* { color: red }</style>
+Should not be red.
+<script>
+  document.body.offsetTop;
+  document.styleSheets[0].media.mediaText = "not all";
+</script>
diff --git a/css/cssom/medialist-interfaces-001.html b/css/cssom/medialist-interfaces-001.html
index ad95394..19c661e 100644
--- a/css/cssom/medialist-interfaces-001.html
+++ b/css/cssom/medialist-interfaces-001.html
@@ -9,30 +9,6 @@
   <meta name="assert" content="MediaLists are serialized according to the specification">
   <script src="/resources/testharness.js" type="text/javascript"></script>
   <script src="/resources/testharnessreport.js" type="text/javascript"></script>
-  <script id="metadata_cache">/*
-  {
-    "mediatest_medialist_serialize_element": {
-      "help": ["http://www.w3.org/TR/cssom-1/#the-medialist-interface",
-               "http://www.w3.org/TR/cssom-1/#serializing-media-queries"],
-      "assert": ["MediaList.mediaText equals the 'media' value of the initial 'style' element."]
-    },
-    "mediatest_medialist_serialize_comma": {
-      "help": ["http://www.w3.org/TR/cssom-1/#the-medialist-interface",
-               "http://www.w3.org/TR/cssom-1/#serializing-media-queries"],
-      "assert": ["To serialize a comma-separated list concatenate all items of the list in list order while separating them by \",\" (U+002C), followed by a space (U+0020)."]
-    },
-    "mediatest_medialist_serialize_empty": {
-      "help": ["http://www.w3.org/TR/cssom-1/#the-medialist-interface",
-               "http://www.w3.org/TR/cssom-1/#serializing-media-queries"],
-      "assert": ["If the media query list is empty return the empty string."]
-    },
-    "mediatest_medialist_serialize_lexicographical": {
-      "help": ["http://www.w3.org/TR/cssom-1/#the-medialist-interface",
-               "http://www.w3.org/TR/cssom-1/#serializing-media-queries"],
-      "assert": ["Each media query in the list of media queries should be sorted in lexicographical order."]
-    }
-  }
-  */</script>
  </head>
  <body>
   <noscript>Test not run - javascript required.</noscript>
@@ -81,7 +57,7 @@
       assert_equals(mediaList.mediaText, "all, screen");
 
     }, "mediatest_medialist_serialize_comma",
-    { help: ["http://www.w3.org/TR/cssom-1/#the-medialist-interface", "http://www.w3.org/TR/cssom-1/#serializing-media-queries"],
+    { help: ["http://www.w3.org/TR/cssom-1/#the-medialist-interface", "http://www.w3.org/TR/cssom-1/#serialize-a-media-query-list"],
       assert: ["To serialize a comma-separated list concatenate all items of the list in list order while separating them by \",\" (U+002C), followed by a space (U+0020)."] });
 
     test(function() {
@@ -99,11 +75,11 @@
 
       mediaList.appendMedium('screen');
       mediaList.appendMedium('print');
-      assert_equals(mediaList.mediaText, "all, print, screen");
+      assert_equals(mediaList.mediaText, "all, screen, print");
 
-    }, "mediatest_medialist_serialize_lexicographical",
-    { help: ["http://www.w3.org/TR/cssom-1/#the-medialist-interface", "http://www.w3.org/TR/cssom-1/#serializing-media-queries"],
-      assert: ["Each media query in the list of media queries should be sorted in lexicographical order."] });
+    }, "mediatest_medialist_serialize_order",
+    { help: ["http://www.w3.org/TR/cssom-1/#the-medialist-interface", "http://www.w3.org/TR/cssom-1/#serialize-a-media-query-list"],
+      assert: ["Each media query should be sorted in the same order as they appear in the list of media queries."] });
 
   </script>
  </body>
diff --git a/css/cssom/medialist-interfaces-002.html b/css/cssom/medialist-interfaces-002.html
index d3dfff8..2b6bba2 100644
--- a/css/cssom/medialist-interfaces-002.html
+++ b/css/cssom/medialist-interfaces-002.html
@@ -9,15 +9,6 @@
   <meta name="assert" content="MediaList object has deleteMedium method and it functions properly.">
   <script src="/resources/testharness.js" type="text/javascript"></script>
   <script src="/resources/testharnessreport.js" type="text/javascript"></script>
-
-  <script id="metadata_cache">/*
-  {
-    "deleteMedium_called_without_argument": { "assert": "MediaList.deleteMedium called without argument throws error." },
-    "deleteMedium_removes_correct_medium": { "assert": "MediaList.deleteMedium removes correct medium and updates corresponding properties." },
-    "deleteMedium_no_matching_medium_to_remove": { "assert": "MediaList.deleteMedium doesn't modify MediaList when medium is not found." }
-  }
-*/</script>
-
 </head>
 
 <body>
@@ -67,7 +58,7 @@
 
       media_list.appendMedium("all");
 
-      media_list.deleteMedium("screen");
+      assert_throws("NotFoundError", () => media_list.deleteMedium("screen"));
 
       assert_equals(media_list.length, 1);
       assert_equals(media_list.item(0), "all");
diff --git a/css/cssom/medialist-interfaces-003.html b/css/cssom/medialist-interfaces-003.html
index 717c39d..649f948 100644
--- a/css/cssom/medialist-interfaces-003.html
+++ b/css/cssom/medialist-interfaces-003.html
@@ -10,16 +10,6 @@
   <script src="/resources/testharness.js" type="text/javascript"></script>
   <script src="/resources/testharnessreport.js" type="text/javascript"></script>
  </head>
- <script id="metadata_cache">/*
- {
-   "mediatest_mediaquery_serialize_1": {
-     "assert": ["First explicit example input (first column) and output (second column) in specification."]
-   },
-   "mediatest_mediaquery_serialize_2": {
-     "assert": ["Second explicit example input (first column) and output (second column) in specification."]
-   }
- }
- */</script>
  <body>
   <noscript>Test not run - javascript required.</noscript>
   <div id="log"></div>
diff --git a/css/cssom/medialist-interfaces-004.html b/css/cssom/medialist-interfaces-004.html
index 2fe6ff9..cf9befb 100644
--- a/css/cssom/medialist-interfaces-004.html
+++ b/css/cssom/medialist-interfaces-004.html
@@ -8,14 +8,6 @@
   <meta name="assert" content="MediaList object has appendMedium method and it functions properly.">
   <script src="/resources/testharness.js" type="text/javascript"></script>
   <script src="/resources/testharnessreport.js" type="text/javascript"></script>
-
-  <script id="metadata_cache">/*
-  {
-    "appendMedium_correctly_appends_medium_to_empty_MediaList": { "assert": "MediaList.appendMedium correctly adds medium to empty MediaList." },
-    "appendMedium_correctly_appends_medium_to_nonempty_MediaList": { "assert": "MediaList.appendMedium correctly adds medium to a MediaList that already has a medium." }
-  }
-*/</script>
-
 </head>
 
 <body>
@@ -63,7 +55,7 @@
       assert_equals(media_list.length, 2);
       assert_equals(media_list.item(0), "screen");
       assert_equals(media_list.item(1), "all");
-      assert_equals(media_list.mediaText, "all, screen");
+      assert_equals(media_list.mediaText, "screen, all");
     }, "appendMedium_correctly_appends_medium_to_nonempty_MediaList",
     { assert: "MediaList.appendMedium correctly adds medium to a MediaList that already has a medium." });
 
diff --git a/css/cssom/overflow-serialization.html b/css/cssom/overflow-serialization.html
index c350a1a..136b8ab 100644
--- a/css/cssom/overflow-serialization.html
+++ b/css/cssom/overflow-serialization.html
@@ -2,7 +2,8 @@
 <html>
 <head>
     <meta charset="utf-8">
-    <title>CSSOM - Overlow property has different serialization than other shorthands.</title>
+    <title>CSSOM - Overflow shorthand serialization</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#serialize-a-css-value">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
     <style>
@@ -10,7 +11,7 @@
         div { overflow: hidden; }
         div { overflow-x: initial; overflow-y: initial; }
         div { overflow-x: scroll; overflow-y: scroll; }
-        div { overflow-x: inherit; overflow-y: unset; }
+        div { overflow-x: scroll; overflow-y: hidden; }
     </style>
 
     <script>
@@ -21,7 +22,7 @@
         assert_equals(styleSheet.cssRules[1].style.cssText, "overflow: hidden;", "Single value overflow with non-CSS-wide keyword should serialize correctly.");
         assert_equals(styleSheet.cssRules[2].style.cssText, "overflow: initial;", "Overflow-x/y longhands with same CSS-wide keyword should serialize correctly.");
         assert_equals(styleSheet.cssRules[3].style.cssText, "overflow: scroll;", "Overflow-x/y longhands with same non-CSS-wide keyword should serialize correctly.");
-        assert_equals(styleSheet.cssRules[4].style.cssText, "overflow-x: inherit; overflow-y: unset;", "Overflow-x/y longhands with different keywords should serialize correctly.");
+        assert_equals(styleSheet.cssRules[4].style.cssText, "overflow: scroll hidden;", "Overflow-x/y longhands with different keywords should serialize correctly.");
 
         var div = document.createElement('div');
         div.style.overflow = "inherit";
@@ -39,9 +40,9 @@
         div.style.overflowY = "scroll";
         assert_equals(div.style.overflow, "scroll", "Overflow-x/y longhands with same non-CSS-wide keyword should serialize correctly.");
 
-        div.style.overflowX = "inherit";
-        div.style.overflowY = "unset";
-        assert_equals(div.style.overflow, "", "Overflow-x/y longhands with different keywords shouldn't serialize.");
+        div.style.overflowX = "scroll";
+        div.style.overflowY = "hidden";
+        assert_equals(div.style.overflow, "scroll hidden", "Overflow-x/y longhands with different keywords should serialize correctly.");
     });
     </script>
 </head>
diff --git a/css/cssom/preferred-stylesheet-order.html b/css/cssom/preferred-stylesheet-order.html
new file mode 100644
index 0000000..dc99013
--- /dev/null
+++ b/css/cssom/preferred-stylesheet-order.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/cssom/#add-a-css-style-sheet">
+<link rel="help" href="https://drafts.csswg.org/cssom/#create-a-css-style-sheet">
+<link rel="help" href="https://html.spec.whatwg.org/#update-a-style-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="t1">This text should be green</div>
+<script>
+function createStyleElement(text, title) {
+    var elm = document.createElement("style");
+    elm.setAttribute("title", title);
+    elm.appendChild(document.createTextNode(text));
+    return elm;
+}
+
+test(function() {
+    document.head.appendChild(createStyleElement("#t1 {color:green}", "preferred"));
+    document.head.appendChild(createStyleElement("#t1 {color:red}", "notpreferred"));
+
+    assert_equals(getComputedStyle(t1).color, "rgb(0, 128, 0)");
+}, "Preferred stylesheet where insertion order is reversed tree order");
+</script>
diff --git a/css/cssom/preferred-stylesheet-reversed-order.html b/css/cssom/preferred-stylesheet-reversed-order.html
new file mode 100644
index 0000000..ff3a8b0
--- /dev/null
+++ b/css/cssom/preferred-stylesheet-reversed-order.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/cssom/#add-a-css-style-sheet">
+<link rel="help" href="https://drafts.csswg.org/cssom/#create-a-css-style-sheet">
+<link rel="help" href="https://html.spec.whatwg.org/#update-a-style-block">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="t1">This text should be green</div>
+<script>
+function createStyleElement(text, title) {
+    var elm = document.createElement("style");
+    elm.setAttribute("title", title);
+    elm.appendChild(document.createTextNode(text));
+    return elm;
+}
+
+test(function() {
+    document.head.insertBefore(createStyleElement("#t1 {color:green}", "preferred"), document.head.firstChild);
+    document.head.insertBefore(createStyleElement("#t1 {color:red}", "notpreferred"), document.head.firstChild);
+
+    assert_equals(getComputedStyle(t1).color, "rgb(0, 128, 0)");
+}, "Preferred stylesheet where insertion order is tree order");
+</script>
diff --git a/css/cssom/selectorSerialize.html b/css/cssom/selectorSerialize.html
index fc9445c..e95f453 100644
--- a/css/cssom/selectorSerialize.html
+++ b/css/cssom/selectorSerialize.html
@@ -19,7 +19,7 @@
               var style_element = document.getElementById("teststyles");
               style_element.firstChild.data = source + "{ font-size: 1em; }";
               var sheet = style_element.sheet;
-              assert_equals(sheet.cssRules[0].selectorText, expected_result);
+              assert_equals(sheet.cssRules[sheet.cssRules.length - 1].selectorText, expected_result);
             }
 
             function run_tests_on_anplusb_selector(source) {
@@ -73,8 +73,6 @@
             }, 'single pseudo (simple) selector "lang" which accepts arguments in the sequence of simple selectors that is not a universal selector')
 
 
-
-
             test(function(){
                 run_tests_on_anplusb_selector(':nth-child');
             }, 'single pseudo (simple) selector "nth-child" which accepts arguments in the sequence of simple selectors that is not a universal selector')
@@ -98,10 +96,25 @@
                 assert_selector_serializes_to(' :not(  :hover   ) ', ':not(:hover)');
             }, 'single pseudo (simple) selector ":not" which accepts arguments in the sequence of simple selectors that is not a universal selector')
 
-
-
-
-
+            var escaped_ns_rule = "@namespace ns\\:odd url(ns);";
+            test(function() {
+                assert_selector_serializes_to("[ns\\:foo]", "[ns\\:foo]");
+            }, "escaped character in attribute name");
+            test(function() {
+                assert_selector_serializes_to("[\\30zonk]", "[\\30 zonk]");
+            }, "escaped character as code point in attribute name");
+            test(function() {
+                assert_selector_serializes_to("[\\@]", "[\\@]");
+            }, "escaped character (@) in attribute name");
+            test(function() {
+                assert_selector_serializes_to("[*|ns\\:foo]", "[*|ns\\:foo]");
+            }, "escaped character in attribute name with any namespace");
+            test(function() {
+                assert_selector_serializes_to(escaped_ns_rule + "[ns\\:odd|foo]", "[ns\\:odd|foo]");
+            }, "escaped character in attribute prefix");
+            test(function() {
+                assert_selector_serializes_to(escaped_ns_rule + "[ns\\:odd|odd\\:name]", "[ns\\:odd|odd\\:name]");
+            }, "escaped character in both attribute prefix and name");
         </script>
     </body>
 </html>
diff --git a/css/cssom/selectorText-modification-restyle-001.html b/css/cssom/selectorText-modification-restyle-001.html
index 681d32f..43a76d1 100644
--- a/css/cssom/selectorText-modification-restyle-001.html
+++ b/css/cssom/selectorText-modification-restyle-001.html
@@ -1,6 +1,7 @@
 <!doctype html>
 <meta charset="utf-8">
 <title>(Test #1) CSSOM - CSSStyleRule.selectorText Modification Restyle - Test #1</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext">
 <link rel="match" href="selectorText-modification-restyle-001-ref.html">
 
 <style>
diff --git a/css/cssom/selectorText-modification-restyle-002.html b/css/cssom/selectorText-modification-restyle-002.html
new file mode 100644
index 0000000..a6b37c2
--- /dev/null
+++ b/css/cssom/selectorText-modification-restyle-002.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>CSSOM: Modify selectorText in a shadow tree stylesheet</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #container { color: red }
+  .subtree * { color: pink }
+</style>
+<div id="container">
+  <div id="host"></div>
+</div>
+<script>
+  const root = host.attachShadow({mode:"open"});
+  root.innerHTML = "<style>nomatch { color: green }</style><div>Green</div>";
+  const div = root.querySelector("div");
+
+  test(() => {
+    assert_equals(getComputedStyle(div).color, "rgb(255, 0, 0)", "Color should initial be red.");
+  }, "Check initial color.");
+
+  test(() => {
+    // The combination of the .subtree and CSSOM selector style invalidations
+    // caused the Blink implementation to fail an assertion.
+    container.className = "subtree";
+    root.styleSheets[0].cssRules[0].selectorText = "div";
+    assert_equals(getComputedStyle(div).color, "rgb(0, 128, 0)", "Color should be green after stylesheet change.");
+  }, "Check that color changes correctly after shadow stylesheet selector and #container class is changed.");
+</script>
diff --git a/css/cssom/serialization-CSSDeclaration-with-important.html b/css/cssom/serialization-CSSDeclaration-with-important.html
index 0f165ae..804885a 100644
--- a/css/cssom/serialization-CSSDeclaration-with-important.html
+++ b/css/cssom/serialization-CSSDeclaration-with-important.html
@@ -1,6 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>cssom - Serialization of CSS declaration with "important" flag</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#serialize-a-css-declaration">
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <div id="noWhitespace" style="display: inline !important;"></div>
diff --git a/css/cssom/serialize-values.html b/css/cssom/serialize-values.html
index 8f6f3d2..7334424 100644
--- a/css/cssom/serialize-values.html
+++ b/css/cssom/serialize-values.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSSOM serialize values</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#serializing-css-values">
 <meta name="author" title="Josh Matthews" href="mailto:josh@joshmatthews.net">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
@@ -89,8 +90,8 @@
     }
 
     function counter() {
-      var values = [{actual: 'counter(par-num)',
-                     serialized: 'counter(par-num, decimal)'},
+      var values = ['counter(par-num)',
+                    { actual: 'counter(par-num, decimal)', serialized: 'counter(par-num)' },
                     'counter(par-num, upper-roman)'];
       return iterable(values);
     }
diff --git a/css/cssom/serialize-variable-reference.html b/css/cssom/serialize-variable-reference.html
index d714b81..2b7e23b 100644
--- a/css/cssom/serialize-variable-reference.html
+++ b/css/cssom/serialize-variable-reference.html
@@ -1,6 +1,8 @@
 <!doctype html>
 <meta charset="utf-8">
 <title>CSSOM - Serialization with variable preserves original serialization.</title>
+<link rel="help" href="https://drafts.csswg.org/css-variables/#serializing-custom-props">
+<link rel="help" href="https://drafts.csswg.org/css-variables/#variables-in-shorthands">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="longhand-whitespace" style="font-size: var(--a);"></div>
@@ -12,23 +14,23 @@
         var elem = document.getElementById('longhand-whitespace');
 
         assert_equals(elem.style.cssText, 'font-size: var(--a);');
-    }, 'Longhand with variable preserves original serialization: with withespace')
+    }, 'Longhand with variable preserves original serialization: with whitespace')
 
     test(function() {
         var elem = document.getElementById('shorthand-whitespace');
 
         assert_equals(elem.style.cssText, 'font: var(--a);');
-    }, 'Shorthand with variable preserves original serialization: with withespace')
+    }, 'Shorthand with variable preserves original serialization: with whitespace')
 
     test(function() {
         var elem = document.getElementById('longhand');
 
         assert_equals(elem.style.cssText, 'font-size:var(--a);');
-    }, 'Longhand with variable preserves original serialization: without withespace')
+    }, 'Longhand with variable preserves original serialization: without whitespace')
 
     test(function() {
         var elem = document.getElementById('shorthand');
 
         assert_equals(elem.style.cssText, 'font:var(--a);');
-    }, 'Shorthand with variable preserves original serialization: without withespace')
+    }, 'Shorthand with variable preserves original serialization: without whitespace')
 </script>
diff --git a/css/cssom/setproperty-null-undefined.html b/css/cssom/setproperty-null-undefined.html
new file mode 100644
index 0000000..3de142c
--- /dev/null
+++ b/css/cssom/setproperty-null-undefined.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+<head>
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+var style = document.body.style;
+
+test(function() {
+    style.color = 'white';
+
+    assert_equals(style.color, 'white');
+    style.setProperty('color', undefined);
+    assert_equals(style.color, 'white');
+}, "Verify that setting a CSS property to undefined has no effect.");
+
+test(function() {
+    style.color = 'white';
+
+    assert_equals(style.color, 'white');
+    assert_equals(style.getPropertyPriority('color'), '');
+    style.setProperty('color', 'red', undefined);
+    assert_equals(style.color, 'red');
+    assert_equals(style.getPropertyPriority('color'), '');
+}, "Verify that setting a CSS property priority to undefined is accepted.");
+
+test(function() {
+    style.color = 'white';
+
+    assert_equals(style.color, 'white');
+    style.setProperty('color', null);
+    assert_equals(style.color, '');
+}, "Verify that setting a CSS property to null is treated like empty string.");
+
+test(function() {
+    style.color = 'white';
+
+    assert_equals(style.color, 'white');
+    style.setProperty('color', 'red', null);
+    assert_equals(style.color, 'red');
+}, "Verify that setting a CSS property priority to null is treated like empty string.");
+</script>
+</body>
+</html>
diff --git a/css/cssom/shorthand-serialization.html b/css/cssom/shorthand-serialization.html
index c34b165..2178ba3 100644
--- a/css/cssom/shorthand-serialization.html
+++ b/css/cssom/shorthand-serialization.html
@@ -3,6 +3,8 @@
 <head>
     <meta charset="utf-8">
     <title>Shorthand serialization should be done correctly.</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block">
+    <link rel="help" href="https://drafts.csswg.org/css-variables/#variables-in-shorthands">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
 </head>
@@ -15,7 +17,8 @@
     <div id="foo5" style="margin-right: 10px; margin-left: 10px; margin-top: 10px; margin-bottom: 10px!important;">foo</div>
     <div id="foo6" style="margin-right: 10px !important; margin-left: 10px !important; margin-top: 10px !important; margin-bottom: 10px!important;">foo</div>
 
-    <div id="foo7" style="background:var(--a);">foo</a>
+    <div id="foo7" style="background:var(--a);">foo</div>
+    <div id="test"></div>
 
     <script>
         test(function() {
@@ -48,6 +51,13 @@
           assert_equals(elem7.style.background, 'var(--a)');
           assert_equals(elem7.style.backgroundPosition, '');
         }, "Shorthand serialization with variable and variable from other shorthand.");
+
+        test(function() {
+            var testElem = document.getElementById("test");
+            testElem.style.margin = "20px 20px 20px 20px";
+            assert_equals(testElem.style.margin, "20px");
+            assert_equals(testElem.style.cssText, "margin: 20px;")
+        }, "Shorthand serialization after setting");
     </script>
 </body>
 </html>
diff --git a/css/cssom/shorthand-values.html b/css/cssom/shorthand-values.html
new file mode 100644
index 0000000..d8d7f53
--- /dev/null
+++ b/css/cssom/shorthand-values.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<head>
+  <title>CSS OM: CSS Values</title>
+  <link rel="author" title="Divya Manian" href="mailto:manian@adobe.com">
+  <link rel="help" href="https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block">
+  <meta name="flags" content="dom">
+  <meta name="assert" content="Testing Serialization of Shorthand Values">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <div id="test"></div>
+    <script>
+      function test_shorthand_serialization(value, expected) {
+        test(function() {
+          const div = document.getElementById("test");
+          div.style.cssText = value;
+          assert_equals(div.style.cssText, expected);
+        }, "The serialization of " + value + " should be canonical.");
+      }
+
+      var tests = {
+        // specified -> expected
+        'border: 1px; border-top: 1px;': 'border: 1px;',
+        'border: 1px solid red;': 'border: 1px solid red;',
+        'border: 1px red;': 'border: 1px red;',
+        'border: red;': 'border: red;',
+        'border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px;': 'border: 1px;',
+        'border-top: 1px; border-right: 2px; border-bottom: 3px; border-left: 4px;': 'border-width: 1px 2px 3px 4px;',
+        'border: 1px; border-top: 2px;': 'border-width: 2px 1px 1px;',
+        'border: 1px; border-top: 1px !important;': 'border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-width: 1px !important;',
+        'border: 1px; border-top-color: red;': 'border-width: 1px; border-top-color: red;',
+        'border: solid; border-style: dotted': 'border: dotted;',
+        'border-width: 1px;': 'border-width: 1px;',
+        'overflow-x: scroll; overflow-y: hidden;': 'overflow: scroll hidden;',
+        'overflow-x: scroll; overflow-y: scroll;': 'overflow: scroll;',
+        'outline-width: 2px; outline-style: dotted; outline-color: blue;': 'outline: blue dotted 2px;',
+        'margin-top: 1px; margin-right: 2px; margin-bottom: 3px; margin-left: 4px;': 'margin: 1px 2px 3px 4px;',
+        'list-style-type: circle; list-style-position: inside; list-style-image: initial;': 'list-style: circle inside;',
+        'list-style-type: lower-alpha;': 'list-style-type: lower-alpha;',
+        'font-family: sans-serif; line-height: 2em; font-size: 3em; font-style: italic; font-weight: bold;': 'font-family: sans-serif; line-height: 2em; font-size: 3em; font-style: italic; font-weight: bold;',
+        'padding-top: 1px; padding-right: 2px; padding-bottom: 3px; padding-left: 4px;': 'padding: 1px 2px 3px 4px;'
+      }
+
+      for (let test in tests) {
+        test_shorthand_serialization(test, tests[test]);
+      }
+    </script>
+ </body>
+ </html>
diff --git a/css/cssom/style-sheet-interfaces-001.html b/css/cssom/style-sheet-interfaces-001.html
index fb1fde8..20dc971 100644
--- a/css/cssom/style-sheet-interfaces-001.html
+++ b/css/cssom/style-sheet-interfaces-001.html
@@ -13,31 +13,6 @@
     * { margin: 0; padding: 0; }
   </style>
   <link id="linkElement" rel="stylesheet" href="support/b-green.css">
-<script id="metadata_cache">/*
-{
-  "sheet_property": {
-    "help": "http://www.w3.org/TR/cssom-1/#the-linkstyle-interface",
-    "assert": ["styleElement.sheet exists",
-               "styleElement.sheet is a CSSStyleSheet",
-               "linkElement.sheet exists",
-               "linkElement.sheet is a CSSStyleSheet"]
-  },
-  "CSSStyleSheet_properties": {
-    "assert": ["ownerRule, cssRules, insertRule and deleteRule properties exist on CSSStyleSheet",
-               "ownerRule, cssRules are read only"]
-  },
-  "CSSStyleSheet_property_values": {
-    "help": ["http://www.w3.org/TR/cssom-1/#css-style-sheets",
-             "http://www.w3.org/TR/cssom-1/#cssimportrule"],
-    "assert": "CSSStyleSheet initial property values are correct"
-  },
-  "StyleSheet_properties": {
-    "assert": ["type, disabled, ownerNode, parentStyleSheet, href, title, and media properties exist on StyleSheet",
-               "type, ownerNode, parentStyleSheet, href, title, media properties are read only"]
-  },
-  "StyleSheet_property_values": { "assert": "StyleSheet initial property values are correct" }
-}
-*/</script>
  </head>
  <body>
   <noscript>Test not run - javascript required.</noscript>
@@ -49,11 +24,11 @@
     var styleSheet;
     var linkSheet;
     test(function() {
-        assert_own_property(styleElement, "sheet");
+        assert_idl_attribute(styleElement, "sheet");
         assert_readonly(styleElement, "sheet");
         styleSheet = styleElement.sheet;
         assert_true(styleSheet instanceof CSSStyleSheet);
-        assert_own_property(linkElement, "sheet");
+        assert_idl_attribute(linkElement, "sheet");
         linkSheet = linkElement.sheet;
         assert_true(linkSheet instanceof CSSStyleSheet);
     }, "sheet_property",
@@ -73,8 +48,8 @@
       assert: "The sheet property on LinkStyle should always return the current associated style sheet." });
 
     test(function() {
-        assert_own_property(styleSheet, "ownerRule");
-        assert_own_property(styleSheet, "cssRules");
+        assert_idl_attribute(styleSheet, "ownerRule");
+        assert_idl_attribute(styleSheet, "cssRules");
         assert_inherits(styleSheet, "insertRule");
         assert_inherits(styleSheet, "deleteRule");
 
@@ -96,23 +71,22 @@
       assert: "CSSStyleSheet initial property values are correct" });
 
     test(function() {
-        assert_own_property(styleSheet, "type");
-        assert_own_property(styleSheet, "disabled");
-        assert_own_property(styleSheet, "ownerNode");
-        assert_own_property(styleSheet, "parentStyleSheet");
-        assert_own_property(styleSheet, "href");
-        assert_own_property(styleSheet, "title");
-        assert_own_property(styleSheet, "media");
+        assert_idl_attribute(styleSheet, "type");
+        assert_idl_attribute(styleSheet, "disabled");
+        assert_idl_attribute(styleSheet, "ownerNode");
+        assert_idl_attribute(styleSheet, "parentStyleSheet");
+        assert_idl_attribute(styleSheet, "href");
+        assert_idl_attribute(styleSheet, "title");
+        assert_idl_attribute(styleSheet, "media");
 
         assert_readonly(styleSheet, "type");
         assert_readonly(styleSheet, "ownerNode");
         assert_readonly(styleSheet, "parentStyleSheet");
         assert_readonly(styleSheet, "href");
         assert_readonly(styleSheet, "title");
-        assert_readonly(styleSheet, "media");
     }, "StyleSheet_properties",
     { assert: [ "type, disabled, ownerNode, parentStyleSheet, href, title, and media properties exist on StyleSheet",
-                "type, ownerNode, parentStyleSheet, href, title, media properties are read only" ] });
+                "type, ownerNode, parentStyleSheet, href, and title properties are read only" ] });
 
     test(function() {
         assert_equals(styleSheet.type, "text/css");
diff --git a/css/cssom/style-sheet-interfaces-002.html b/css/cssom/style-sheet-interfaces-002.html
index ad5b1ac..51b0eb3 100644
--- a/css/cssom/style-sheet-interfaces-002.html
+++ b/css/cssom/style-sheet-interfaces-002.html
@@ -13,15 +13,6 @@
   <style id="styleElement" type="text/css" media="all" title="internal style sheet" disabled="disabled">
     * { margin: 0; padding: 0; }
   </style>
-  <script id="metadata_cache">/*
-{
-  "add_rule": {
-    "assert": ["Initial rule list is of size 1",
-               "Can add a rule at first index"]
-  },
-  "delete_rule": { "assert": "Can delete rules until rule list is empty" }
-}
-*/</script>
  </head>
  <body>
   <noscript>Test not run - javascript required.</noscript>
diff --git a/css/cssom/stylesheet-replacedata-dynamic-ref.html b/css/cssom/stylesheet-replacedata-dynamic-ref.html
new file mode 100644
index 0000000..bc9cade
--- /dev/null
+++ b/css/cssom/stylesheet-replacedata-dynamic-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>(Ref #1) CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@mozilla.com">
+<link rel="author" title="Cheng You Bai" href="mailto:cyb.ai.815@gmail.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-characterdata-replacedata">
+<style>.pass { color: green }</style>
+<div class="pass">Should be green</div>
\ No newline at end of file
diff --git a/css/cssom/stylesheet-replacedata-dynamic.html b/css/cssom/stylesheet-replacedata-dynamic.html
new file mode 100644
index 0000000..3ee5937
--- /dev/null
+++ b/css/cssom/stylesheet-replacedata-dynamic.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>(Test #1) CSS Test: Dynamic changes to the stylesheet contents using replaceData are reflected</title>
+<link rel="match" href="stylesheet-replacedata-dynamic-ref.html">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@mozilla.com">
+<link rel="author" title="Cheng You Bai" href="mailto:cyb.ai.815@gmail.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-characterdata-replacedata">
+<style>.fail { color: green }</style>
+<div class="pass">Should be green</div>
+<script>
+  document.body.offsetTop;
+  document.querySelector('style').firstChild.replaceData(1, 4, "pass");
+</script>
\ No newline at end of file
diff --git a/css/cssom/stylesheet-same-origin.sub.html b/css/cssom/stylesheet-same-origin.sub.html
index 9df0a54..ccfbf7c 100644
--- a/css/cssom/stylesheet-same-origin.sub.html
+++ b/css/cssom/stylesheet-same-origin.sub.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>CSSOM - CSSStylesheet should support origins</title>
+    <link rel="help" href="https://drafts.csswg.org/cssom/#the-cssstylesheet-interface">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
 
diff --git a/css/cssom/stylesheet-title.html b/css/cssom/stylesheet-title.html
new file mode 100644
index 0000000..77b1df9
--- /dev/null
+++ b/css/cssom/stylesheet-title.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: StyleSheet's title attribute</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/cssom/#preferred-css-style-sheet-set-name">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-style-title">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style></style>
+<style title=""></style>
+<style title="Preferred">
+  p { color: green; }
+</style>
+<style title="Not preferred">
+  p { color: red; }
+</style>
+<p id="test-element">Should be green</p>
+<script>
+test(function() {
+  assert_equals(
+    getComputedStyle(document.getElementById("test-element")).color,
+    "rgb(0, 128, 0)",
+    "Preferred style should apply"
+  );
+}, "Preferred style sheet name");
+
+test(function() {
+  let sheets = document.styleSheets;
+  let styleElements = Array.from(document.querySelectorAll("style"));
+  assert_equals(sheets.length, styleElements.length);
+  for (let i = 0; i < sheets.length; ++i) {
+    let titleAttr = styleElements[i].getAttribute("title");
+    if (titleAttr === null || titleAttr === "")
+      assert_equals(sheets[i].title, null, "Empty title returns null");
+    else
+      assert_equals(sheets[i].title, titleAttr, "Selected title is properly reflected");
+  }
+}, "StyleSheet.title");
+</script>
diff --git a/css/cssom/ttwf-cssom-doc-ext-load-count.html b/css/cssom/ttwf-cssom-doc-ext-load-count.html
index f507a96..5296aa3 100644
--- a/css/cssom/ttwf-cssom-doc-ext-load-count.html
+++ b/css/cssom/ttwf-cssom-doc-ext-load-count.html
@@ -11,13 +11,6 @@
     <meta name="assert" content="The styleSheets length attribute must reflect the number of sheets at page load and after dynamically">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
-    <script id="metadata_cache">/*
-      {
-          "stylesheet.css should be loaded and styleSheets.length === 1": {},
-            "stylesheet.css should be unloaded and styleSheets.length === 0": {},
-              "stylesheet-1.css should be loaded and styleSheets.length === 1": {}
-      }
-    */</script>
 </head>
 <body>
     <div id="log"></div>
diff --git a/css/cssom/variable-names.html b/css/cssom/variable-names.html
index b951b7d..5591411 100644
--- a/css/cssom/variable-names.html
+++ b/css/cssom/variable-names.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>Tests for handling of CSS Custom Property names</title>
+<link rel="help" href="https://drafts.csswg.org/css-variables/#serializing-custom-props">
 <meta name="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
diff --git a/css/filter-effects/interfaces.any.js b/css/filter-effects/interfaces.any.js
new file mode 100644
index 0000000..86993b1
--- /dev/null
+++ b/css/filter-effects/interfaces.any.js
@@ -0,0 +1,16 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// https://drafts.fxtf.org/filter-effects/
+
+promise_test(async () => {
+  const filterEffectsIdl = await fetch('/interfaces/filter-effects.idl').then(r => r.text());
+  const idlArray = new IdlArray();
+  idlArray.add_idls(filterEffectsIdl);
+  idlArray.add_untested_idls('interface SVGElement {};');
+  idlArray.add_untested_idls('interface SVGURIReference {};');
+  idlArray.test();
+  done();
+}, 'Filter effects interfaces.');
diff --git a/css/filter-effects/parsing/filter-parsing-invalid.html b/css/filter-effects/parsing/filter-parsing-invalid.html
index add2875..30ebf67 100644
--- a/css/filter-effects/parsing/filter-parsing-invalid.html
+++ b/css/filter-effects/parsing/filter-parsing-invalid.html
@@ -34,8 +34,6 @@
 test_invalid_value("filter", "drop-shadow(1px)");
 test_invalid_value("filter", "drop-shadow(1px 2px 3px 4px)");
 test_invalid_value("filter", "drop-shadow(rgb(4, 5, 6))");
-// https://github.com/w3c/fxtf-drafts/issues/231
-test_invalid_value("filter", "drop-shadow(rgb(4, 5, 6) 1px 2px)"); // Fails in Blink/WebKit "drop-shadow(rgb(4, 5, 6) 1px 2px)" and Firefox "drop-shadow(1px 2px rgb(4, 5, 6))".
 test_invalid_value("filter", "drop-shadow()");
 
 test_invalid_value("filter", "grayscale(-20)");
diff --git a/css/filter-effects/parsing/filter-parsing-valid.html b/css/filter-effects/parsing/filter-parsing-valid.html
index d7e0af4..773b110 100644
--- a/css/filter-effects/parsing/filter-parsing-valid.html
+++ b/css/filter-effects/parsing/filter-parsing-valid.html
@@ -26,9 +26,10 @@
 test_valid_value("filter", "drop-shadow(1px 2px)");
 test_valid_value("filter", "drop-shadow(1px 2px 3px)");
 test_valid_value("filter", "drop-shadow(0 0 0)", "drop-shadow(0px 0px 0px)");
-// https://github.com/w3c/fxtf-drafts/issues/231
-test_valid_value("filter", "drop-shadow(1px 2px rgb(4, 5, 6))"); // Blink/WebKit serialize as "drop-shadow(rgb(4, 5, 6) 1px 2px)"
-test_valid_value("filter", "drop-shadow(1px 2px 3px rgba(4, 5, 6, 0.75))"); // Blink/WebKit serialize as "drop-shadow(rgba(4, 5, 6, 0.75) 1px 2px 3px)"
+// https://github.com/w3c/fxtf-drafts/issues/240
+test_valid_value("filter", "drop-shadow(rgb(4, 5, 6) 1px 2px)");
+test_valid_value("filter", "drop-shadow(1px 2px rgb(4, 5, 6))", "drop-shadow(rgb(4, 5, 6) 1px 2px)");
+test_valid_value("filter", "drop-shadow(rgba(4, 5, 6, 0.75) 1px 2px 3px)");
 
 test_valid_value("filter", "grayscale(0)");
 test_valid_value("filter", "grayscale(300%)", "grayscale(100%)");
diff --git a/css/filter-effects/parsing/resources/parsing-testcommon.js b/css/filter-effects/parsing/resources/parsing-testcommon.js
index 70a1c38..b075882 100644
--- a/css/filter-effects/parsing/resources/parsing-testcommon.js
+++ b/css/filter-effects/parsing/resources/parsing-testcommon.js
@@ -1,5 +1,8 @@
 'use strict';
 
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
 function test_valid_value(property, value, serializedValue) {
     if (arguments.length < 3)
         serializedValue = value;
@@ -9,21 +12,20 @@
     test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        assert_not_equals(div.style[property], "");
-    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
 
-    test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        var readValue = div.style[property];
-
-        if (Array.isArray(serializedValue))
-            assert_true(serializedValue.indexOf(readValue) >= 0, '"' + readValue + '" in ' + JSON.stringify(serializedValue));
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
         else
-            assert_equals(readValue, serializedValue);
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
         div.style[property] = readValue;
-        assert_equals(div.style[property], readValue);
-    }, "Serialization should round-trip after setting e.style['" + property + "'] = " + stringifiedValue);
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
 }
 
 function test_invalid_value(property, value) {
@@ -32,6 +34,6 @@
     test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        assert_equals(div.style[property], "");
+        assert_equals(div.style.getPropertyValue(property), "");
     }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
 }
diff --git a/css/fonts/OWNERS b/css/fonts/OWNERS
deleted file mode 100644
index 9daa3eb..0000000
--- a/css/fonts/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-@fantasai
-@kojiishi
-@gsnedders
diff --git a/css/geometry/DOMPoint-001.html b/css/geometry/DOMPoint-001.html
index 5aae0b0..0554d3f 100644
--- a/css/geometry/DOMPoint-001.html
+++ b/css/geometry/DOMPoint-001.html
@@ -21,7 +21,7 @@
             checkDOMPoint(new DOMPoint(), {x:0, y:0, z:0, w:1});
         },'testConstructor0');
         test(function() {
-            assert_throws(new TypeError(), function() { new DOMPoint(1);})
+            checkDOMPoint(new DOMPoint(1), {x:1, y:0, z:0, w:1});
         },'testConstructor1');
         test(function() {
             checkDOMPoint(new DOMPoint(1, 2), {x:1, y:2, z:0, w:1});
@@ -72,11 +72,11 @@
             checkDOMPoint(new DOMPoint({x:"a", y:"b"}), {x:NaN, y:NaN, z:0, w:1});
         },'testConstructorUndefined2');
         test(function() {
-            assert_throws(new TypeError(), function() { new DOMPointReadOnly();})
-        },'testConstructorIllegal1');
+            checkDOMPoint(new DOMPointReadOnly(), {x:0, y:0, z:0, w:1});
+        },'DOMPointReadOnly constructor with no values');
         test(function() {
-            assert_throws(new TypeError(), function() { new DOMPointReadOnly(1, 2, 3, 4);})
-        },'testConstructorIllegal2');
+            checkDOMPoint(new DOMPointReadOnly(1, 2, 3, 4), {x:1, y:2, z:3, w:4});
+        },'DOMPointReadOnly constructor with 4 values');
         test(function() {
             var p = new DOMPoint(0, 0, 0, 1);
             p.x = undefined;
diff --git a/css/geometry/DOMQuad-001.html b/css/geometry/DOMQuad-001.html
index fa8cc6e..4cafa02 100644
--- a/css/geometry/DOMQuad-001.html
+++ b/css/geometry/DOMQuad-001.html
@@ -44,31 +44,31 @@
         },'testConstructor4');
 
         checkDOMQuad(
-                function() { return new DOMQuad(new DOMRect(10, 20, 100, 200)); },
+                function() { return DOMQuad.fromRect(new DOMRect(10, 20, 100, 200)); },
                 {   p1: { x: 10, y: 20, z: 0, w: 1 },
                     p2: { x: 110, y: 20, z: 0, w: 1 },
                     p3: { x: 110, y: 220, z: 0, w: 1 },
                     p4: { x: 10, y: 220, z: 0, w: 1 },
                     bounds: { x: 10, y: 20, width: 100, height: 200 } },
-                'testConstructor5');
+                'fromRect() method on DOMQuad');
 
         checkDOMQuad(
-                function() { return new DOMQuad(new DOMRect(10, 20, -100, -200)) },
+                function() { return DOMQuad.fromRect(new DOMRect(10, 20, -100, -200)) },
                 {   p1: { x: 10, y: 20, z: 0, w: 1 },
                     p2: { x: -90, y: 20, z: 0, w: 1 },
                     p3: { x: -90, y: -180, z: 0, w: 1 },
                     p4: { x: 10, y: -180, z: 0, w: 1 },
                     bounds: { x: -90, y: -180, width: 100, height: 200 } },
-                'testConstructor6');
+                'fromRect() method on DOMQuad with negatives');
 
         checkDOMQuad(
-                function() { return new DOMQuad(new DOMRect(-Infinity, -Infinity, Infinity, Infinity)) },
+                function() { return DOMQuad.fromRect(new DOMRect(-Infinity, -Infinity, Infinity, Infinity)) },
                 {   p1: { x: -Infinity, y: -Infinity, z: 0, w: 1 },
                     p2: { x: NaN, y: -Infinity, z: 0, w: 1 },
                     p3: { x: NaN, y: NaN, z: 0, w: 1 },
                     p4: { x: -Infinity, y: NaN, z: 0, w: 1 },
                     bounds: { x: -Infinity, y: -Infinity, width: NaN, height: NaN } },
-                'testConstructor7');
+                'fromRect() method on DOMQuad with Infinity');
 
         checkDOMQuad(function() { return new DOMQuad(new DOMRect()); }, initial, 'testConstructor8');
 
@@ -120,24 +120,9 @@
             p2: { x: 2, y: 0, z: 0, w: 1 },
             p3: { x: 2, y: 0, z: 0, w: 1 },
             p4: { x: 2, y: 0, z: 0, w: 1 },
-            bounds: { x: 2, y: 0, width: 0, height: 0 } },
+            bounds: { x: 0, y: 0, width: 0, height: 0 } },
         'p1Top4Attributes1');
 
-        checkDOMQuad(function() {
-            var q = new DOMQuad({}, {}, {}, {});
-            q.bounds = new DOMRect(10, 10, 100, 100);
-            return q;
-        }, initial, 'boundsAttribute0');
-
-        checkDOMQuad(function() {
-            var q = new DOMQuad({}, {}, {}, {});
-            q.bounds.x = 10;
-            q.bounds.y = 10;
-            q.bounds.width = 100;
-            q.bounds.height = 100;
-            return q;
-        }, initial, 'boundsAttribute1');
-
         function checkDOMQuad(createQuad, exp, name) {
             test(function() {
                 var q = createQuad();
@@ -161,10 +146,10 @@
 
             test(function() {
                 var q = createQuad();
-                assert_equals(q.bounds.x, exp.bounds.x, "Expected value for bounds.x is " + exp.bounds.x);
-                assert_equals(q.bounds.y, exp.bounds.y, "Expected value for bounds.y is " + exp.bounds.y);
-                assert_equals(q.bounds.width, exp.bounds.width, "Expected value for bounds.width is " + exp.bounds.width);
-                assert_equals(q.bounds.height, exp.bounds.height, "Expected value for bounds.height is " + exp.bounds.height);
+                assert_equals(q.getBounds().x, exp.bounds.x, "Expected value for getBounds().x is " + exp.bounds.x);
+                assert_equals(q.getBounds().y, exp.bounds.y, "Expected value for getBounds().y is " + exp.bounds.y);
+                assert_equals(q.getBounds().width, exp.bounds.width, "Expected value for getBounds().width is " + exp.bounds.width);
+                assert_equals(q.getBounds().height, exp.bounds.height, "Expected value for getBounds().height is " + exp.bounds.height);
             }, name + ": bounds");
         }
     </script>
diff --git a/css/geometry/DOMRectList.html b/css/geometry/DOMRectList.html
index f128a05..f3d050c 100644
--- a/css/geometry/DOMRectList.html
+++ b/css/geometry/DOMRectList.html
@@ -10,12 +10,12 @@
 });
 
 test(() => {
-  assert_false('DOMRectList' in window);
-}, 'DOMRectList [NoInterfaceObject]');
+  assert_true('DOMRectList' in window);
+}, 'DOMRectList is not [NoInterfaceObject]');
 
 test(() => {
-  assert_true(domRectList instanceof Array);
-}, 'DOMRectList [LegacyArrayClass]');
+  assert_false(domRectList instanceof Array);
+}, 'DOMRectList is not [LegacyArrayClass]');
 
 test(() => {
   assert_equals(domRectList.length, 1);
diff --git a/css/geometry/historical.html b/css/geometry/historical.html
index 357ae91..4f8e65e 100644
--- a/css/geometry/historical.html
+++ b/css/geometry/historical.html
@@ -21,6 +21,8 @@
   ['DOMMatrix', 'rotateAxisAngleBy'],
   ['DOMMatrix', 'skewXBy'],
   ['DOMMatrix', 'skewYBy'],
+  // https://github.com/w3c/fxtf-drafts/commit/555a8c0beb1b7b809ccebd861a0352df31530b56
+  ['DOMQuad', 'bounds'],
 ].forEach(([interf, member]) => {
   test(() => {
     assert_true(interf in self, `${interf} should exist`);
diff --git a/css/geometry/support/interfaces.js b/css/geometry/support/interfaces.js
index e5955d1..3dca695 100644
--- a/css/geometry/support/interfaces.js
+++ b/css/geometry/support/interfaces.js
@@ -19,6 +19,8 @@
     DOMMatrixReadOnly: ["new DOMMatrixReadOnly()", "DOMMatrixReadOnly.fromMatrix({is2D: false})"],
     DOMMatrix: ["new DOMMatrix()", "DOMMatrix.fromMatrix({is2D: false})"],
   });
+  idlArray.prevent_multiple_testing("DOMMatrixReadOnly");
+  idlArray.prevent_multiple_testing("DOMMatrix");
   idlArray.test();
   done();
 }
diff --git a/css/mediaqueries/test_media_queries.html b/css/mediaqueries/test_media_queries.html
index 44161ac..57830f4 100644
--- a/css/mediaqueries/test_media_queries.html
+++ b/css/mediaqueries/test_media_queries.html
@@ -8,373 +8,6 @@
   <link rel="help" href="http://www.w3.org/TR/css3-mediaqueries/">
   <script type="text/javascript" src="/resources/testharness.js"></script>
   <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-<script id="metadata_cache">/*
-{
-  "subtest_1": { "assert": "query (orientation) should be parseable" },
-  "subtest_2": { "assert": "query not (orientation) should not be parseable" },
-  "subtest_3": { "assert": "query only (orientation) should not be parseable" },
-  "subtest_4": { "assert": "query all and (orientation) should be parseable" },
-  "subtest_5": { "assert": "query not all and (orientation) should be parseable" },
-  "subtest_6": { "assert": "query only all and (orientation) should be parseable" },
-  "subtest_7": { "assert": "expression width should be parseable" },
-  "subtest_8": { "assert": "expression width: 0 should be parseable" },
-  "subtest_9": { "assert": "expression width: 0px should be parseable" },
-  "subtest_10": { "assert": "expression width: 0em should be parseable" },
-  "subtest_11": { "assert": "expression width: -0 should be parseable" },
-  "subtest_12": { "assert": "expression min-width: -0 should be parseable" },
-  "subtest_13": { "assert": "expression max-width: -0 should be parseable" },
-  "subtest_14": { "assert": "expression width: -0cm should be parseable" },
-  "subtest_15": { "assert": "expression width: 1px should be parseable" },
-  "subtest_16": { "assert": "expression width: 0.001mm should be parseable" },
-  "subtest_17": { "assert": "expression width: 100000px should be parseable" },
-  "subtest_18": { "assert": "expression width: -1px should not be parseable" },
-  "subtest_19": { "assert": "expression min-width: -1px should not be parseable" },
-  "subtest_20": { "assert": "expression max-width: -1px should not be parseable" },
-  "subtest_21": { "assert": "expression width: -0.00001mm should not be parseable" },
-  "subtest_22": { "assert": "expression width: -100000em should not be parseable" },
-  "subtest_23": { "assert": "expression min-width should not be parseable" },
-  "subtest_24": { "assert": "expression max-width should not be parseable" },
-  "subtest_25": { "assert": "expression height should be parseable" },
-  "subtest_26": { "assert": "expression height: 0 should be parseable" },
-  "subtest_27": { "assert": "expression height: 0px should be parseable" },
-  "subtest_28": { "assert": "expression height: 0em should be parseable" },
-  "subtest_29": { "assert": "expression height: -0 should be parseable" },
-  "subtest_30": { "assert": "expression min-height: -0 should be parseable" },
-  "subtest_31": { "assert": "expression max-height: -0 should be parseable" },
-  "subtest_32": { "assert": "expression height: -0cm should be parseable" },
-  "subtest_33": { "assert": "expression height: 1px should be parseable" },
-  "subtest_34": { "assert": "expression height: 0.001mm should be parseable" },
-  "subtest_35": { "assert": "expression height: 100000px should be parseable" },
-  "subtest_36": { "assert": "expression height: -1px should not be parseable" },
-  "subtest_37": { "assert": "expression min-height: -1px should not be parseable" },
-  "subtest_38": { "assert": "expression max-height: -1px should not be parseable" },
-  "subtest_39": { "assert": "expression height: -0.00001mm should not be parseable" },
-  "subtest_40": { "assert": "expression height: -100000em should not be parseable" },
-  "subtest_41": { "assert": "expression min-height should not be parseable" },
-  "subtest_42": { "assert": "expression max-height should not be parseable" },
-  "subtest_43": { "assert": "expression device-width should be parseable" },
-  "subtest_44": { "assert": "expression device-width: 0 should be parseable" },
-  "subtest_45": { "assert": "expression device-width: 0px should be parseable" },
-  "subtest_46": { "assert": "expression device-width: 0em should be parseable" },
-  "subtest_47": { "assert": "expression device-width: -0 should be parseable" },
-  "subtest_48": { "assert": "expression min-device-width: -0 should be parseable" },
-  "subtest_49": { "assert": "expression max-device-width: -0 should be parseable" },
-  "subtest_50": { "assert": "expression device-width: -0cm should be parseable" },
-  "subtest_51": { "assert": "expression device-width: 1px should be parseable" },
-  "subtest_52": { "assert": "expression device-width: 0.001mm should be parseable" },
-  "subtest_53": { "assert": "expression device-width: 100000px should be parseable" },
-  "subtest_54": { "assert": "expression device-width: -1px should not be parseable" },
-  "subtest_55": { "assert": "expression min-device-width: -1px should not be parseable" },
-  "subtest_56": { "assert": "expression max-device-width: -1px should not be parseable" },
-  "subtest_57": { "assert": "expression device-width: -0.00001mm should not be parseable" },
-  "subtest_58": { "assert": "expression device-width: -100000em should not be parseable" },
-  "subtest_59": { "assert": "expression min-device-width should not be parseable" },
-  "subtest_60": { "assert": "expression max-device-width should not be parseable" },
-  "subtest_61": { "assert": "expression device-height should be parseable" },
-  "subtest_62": { "assert": "expression device-height: 0 should be parseable" },
-  "subtest_63": { "assert": "expression device-height: 0px should be parseable" },
-  "subtest_64": { "assert": "expression device-height: 0em should be parseable" },
-  "subtest_65": { "assert": "expression device-height: -0 should be parseable" },
-  "subtest_66": { "assert": "expression min-device-height: -0 should be parseable" },
-  "subtest_67": { "assert": "expression max-device-height: -0 should be parseable" },
-  "subtest_68": { "assert": "expression device-height: -0cm should be parseable" },
-  "subtest_69": { "assert": "expression device-height: 1px should be parseable" },
-  "subtest_70": { "assert": "expression device-height: 0.001mm should be parseable" },
-  "subtest_71": { "assert": "expression device-height: 100000px should be parseable" },
-  "subtest_72": { "assert": "expression device-height: -1px should not be parseable" },
-  "subtest_73": { "assert": "expression min-device-height: -1px should not be parseable" },
-  "subtest_74": { "assert": "expression max-device-height: -1px should not be parseable" },
-  "subtest_75": { "assert": "expression device-height: -0.00001mm should not be parseable" },
-  "subtest_76": { "assert": "expression device-height: -100000em should not be parseable" },
-  "subtest_77": { "assert": "expression min-device-height should not be parseable" },
-  "subtest_78": { "assert": "expression max-device-height should not be parseable" },
-  "subtest_79": { "assert": "all and (width: 117px) should apply" },
-  "subtest_80": { "assert": "all and (width: 118px) should not apply" },
-  "subtest_81": { "assert": "all and (width: 116px) should not apply" },
-  "subtest_82": { "assert": "all and (min-width: 117px) should apply" },
-  "subtest_83": { "assert": "all and (min-width: 118px) should not apply" },
-  "subtest_84": { "assert": "all and (min-width: 116px) should apply" },
-  "subtest_85": { "assert": "all and (max-width: 117px) should apply" },
-  "subtest_86": { "assert": "all and (max-width: 118px) should apply" },
-  "subtest_87": { "assert": "all and (max-width: 116px) should not apply" },
-  "subtest_88": { "assert": "all and (min-width: 9em) should not apply" },
-  "subtest_89": { "assert": "all and (min-width: 6em) should apply" },
-  "subtest_90": { "assert": "all and (max-width: 9em) should apply" },
-  "subtest_91": { "assert": "all and (max-width: 6em) should not apply" },
-  "subtest_92": { "assert": "all and (height: 76px) should apply" },
-  "subtest_93": { "assert": "all and (height: 77px) should not apply" },
-  "subtest_94": { "assert": "all and (height: 75px) should not apply" },
-  "subtest_95": { "assert": "all and (min-height: 76px) should apply" },
-  "subtest_96": { "assert": "all and (min-height: 77px) should not apply" },
-  "subtest_97": { "assert": "all and (min-height: 75px) should apply" },
-  "subtest_98": { "assert": "all and (max-height: 76px) should apply" },
-  "subtest_99": { "assert": "all and (max-height: 77px) should apply" },
-  "subtest_100": { "assert": "all and (max-height: 75px) should not apply" },
-  "subtest_101": { "assert": "all and (min-height: 6em) should not apply" },
-  "subtest_102": { "assert": "all and (min-height: 3em) should apply" },
-  "subtest_103": { "assert": "all and (max-height: 6em) should apply" },
-  "subtest_104": { "assert": "all and (max-height: 3em) should not apply" },
-  "subtest_105": { "assert": "all and (device-width: 1920px) should apply" },
-  "subtest_106": { "assert": "all and (device-width: 1921px) should not apply" },
-  "subtest_107": { "assert": "all and (device-width: 1919px) should not apply" },
-  "subtest_108": { "assert": "all and (min-device-width: 1920px) should apply" },
-  "subtest_109": { "assert": "all and (min-device-width: 1921px) should not apply" },
-  "subtest_110": { "assert": "all and (min-device-width: 1919px) should apply" },
-  "subtest_111": { "assert": "all and (max-device-width: 1920px) should apply" },
-  "subtest_112": { "assert": "all and (max-device-width: 1921px) should apply" },
-  "subtest_113": { "assert": "all and (max-device-width: 1919px) should not apply" },
-  "subtest_114": { "assert": "all and (min-device-width: 121em) should not apply" },
-  "subtest_115": { "assert": "all and (min-device-width: 119em) should apply" },
-  "subtest_116": { "assert": "all and (max-device-width: 121em) should apply" },
-  "subtest_117": { "assert": "all and (max-device-width: 119em) should not apply" },
-  "subtest_118": { "assert": "all and (device-height: 1200px) should apply" },
-  "subtest_119": { "assert": "all and (device-height: 1201px) should not apply" },
-  "subtest_120": { "assert": "all and (device-height: 1199px) should not apply" },
-  "subtest_121": { "assert": "all and (min-device-height: 1200px) should apply" },
-  "subtest_122": { "assert": "all and (min-device-height: 1201px) should not apply" },
-  "subtest_123": { "assert": "all and (min-device-height: 1199px) should apply" },
-  "subtest_124": { "assert": "all and (max-device-height: 1200px) should apply" },
-  "subtest_125": { "assert": "all and (max-device-height: 1201px) should apply" },
-  "subtest_126": { "assert": "all and (max-device-height: 1199px) should not apply" },
-  "subtest_127": { "assert": "all and (min-device-height: 76em) should not apply" },
-  "subtest_128": { "assert": "all and (min-device-height: 74em) should apply" },
-  "subtest_129": { "assert": "all and (max-device-height: 76em) should apply" },
-  "subtest_130": { "assert": "all and (max-device-height: 74em) should not apply" },
-  "subtest_131": { "assert": "all and (height) should apply" },
-  "subtest_132": { "assert": "all and (width) should not apply" },
-  "subtest_133": { "assert": "all and (height) should not apply" },
-  "subtest_134": { "assert": "all and (width) should not apply" },
-  "subtest_135": { "assert": "all and (device-height) should apply" },
-  "subtest_136": { "assert": "all and (device-width) should apply" },
-  "subtest_137": { "assert": "all and (height) should not apply" },
-  "subtest_138": { "assert": "all and (width) should apply" },
-  "subtest_139": { "assert": "all and (height) should apply" },
-  "subtest_140": { "assert": "all and (width) should apply" },
-  "subtest_141": { "assert": "expression orientation should be parseable" },
-  "subtest_142": { "assert": "expression orientation: portrait should be parseable" },
-  "subtest_143": { "assert": "expression orientation: landscape should be parseable" },
-  "subtest_144": { "assert": "expression min-orientation should not be parseable" },
-  "subtest_145": { "assert": "expression min-orientation: portrait should not be parseable" },
-  "subtest_146": { "assert": "expression min-orientation: landscape should not be parseable" },
-  "subtest_147": { "assert": "expression max-orientation should not be parseable" },
-  "subtest_148": { "assert": "expression max-orientation: portrait should not be parseable" },
-  "subtest_149": { "assert": "expression max-orientation: landscape should not be parseable" },
-  "subtest_150": { "assert": "(orientation) should apply" },
-  "subtest_151": { "assert": "(orientation: landscape) should apply" },
-  "subtest_152": { "assert": "(orientation: portrait) should not apply" },
-  "subtest_153": { "assert": "not all and (orientation: portrait) should apply" },
-  "subtest_154": { "assert": "(orientation) should apply" },
-  "subtest_155": { "assert": "(orientation: landscape) should not apply" },
-  "subtest_156": { "assert": "not all and (orientation: landscape) should apply" },
-  "subtest_157": { "assert": "(orientation: portrait) should apply" },
-  "subtest_158": { "assert": "(aspect-ratio: 59/80) should apply" },
-  "subtest_159": { "assert": "(aspect-ratio: 58/80) should not apply" },
-  "subtest_160": { "assert": "(aspect-ratio: 59/81) should not apply" },
-  "subtest_161": { "assert": "(aspect-ratio: 60/80) should not apply" },
-  "subtest_162": { "assert": "(aspect-ratio: 59/79) should not apply" },
-  "subtest_163": { "assert": "(aspect-ratio: 177/240) should apply" },
-  "subtest_164": { "assert": "(aspect-ratio: 413/560) should apply" },
-  "subtest_165": { "assert": "(aspect-ratio: 5900/8000) should apply" },
-  "subtest_166": { "assert": "(aspect-ratio: 5901/8000) should not apply" },
-  "subtest_167": { "assert": "(aspect-ratio: 5899/8000) should not apply" },
-  "subtest_168": { "assert": "(aspect-ratio: 5900/8001) should not apply" },
-  "subtest_169": { "assert": "(aspect-ratio: 5900/7999) should not apply" },
-  "subtest_170": { "assert": "(aspect-ratio) should apply" },
-  "subtest_171": { "assert": "(min-aspect-ratio: 59/80) should apply" },
-  "subtest_172": { "assert": "(min-aspect-ratio: 58/80) should apply" },
-  "subtest_173": { "assert": "(min-aspect-ratio: 59/81) should apply" },
-  "subtest_174": { "assert": "(min-aspect-ratio: 60/80) should not apply" },
-  "subtest_175": { "assert": "(min-aspect-ratio: 59/79) should not apply" },
-  "subtest_176": { "assert": "expression min-aspect-ratio should not be parseable" },
-  "subtest_177": { "assert": "(max-aspect-ratio: 59/80) should apply" },
-  "subtest_178": { "assert": "(max-aspect-ratio: 58/80) should not apply" },
-  "subtest_179": { "assert": "(max-aspect-ratio: 59/81) should not apply" },
-  "subtest_180": { "assert": "(max-aspect-ratio: 60/80) should apply" },
-  "subtest_181": { "assert": "(max-aspect-ratio: 59/79) should apply" },
-  "subtest_182": { "assert": "expression max-aspect-ratio should not be parseable" },
-  "subtest_183": { "assert": "(device-aspect-ratio: 1920/1200) should apply" },
-  "subtest_184": { "assert": "not all and (device-aspect-ratio: 1921/1200) should apply" },
-  "subtest_185": { "assert": "all and (device-aspect-ratio: 1920/1199) should not apply" },
-  "subtest_186": { "assert": "all and (device-aspect-ratio: 1919/1200) should not apply" },
-  "subtest_187": { "assert": "not all and (device-aspect-ratio: 1920/1201) should apply" },
-  "subtest_188": { "assert": "(device-aspect-ratio) should apply" },
-  "subtest_189": { "assert": "(min-device-aspect-ratio: 1920/1200) should apply" },
-  "subtest_190": { "assert": "all and (min-device-aspect-ratio: 1921/1200) should not apply" },
-  "subtest_191": { "assert": "not all and (min-device-aspect-ratio: 1920/1199) should apply" },
-  "subtest_192": { "assert": "not all and (min-device-aspect-ratio: 1919/1200) should not apply" },
-  "subtest_193": { "assert": "all and (min-device-aspect-ratio: 1920/1201) should apply" },
-  "subtest_194": { "assert": "expression min-device-aspect-ratio should not be parseable" },
-  "subtest_195": { "assert": "all and (max-device-aspect-ratio: 1920/1200) should apply" },
-  "subtest_196": { "assert": "(max-device-aspect-ratio: 1921/1200) should apply" },
-  "subtest_197": { "assert": "(max-device-aspect-ratio: 1920/1199) should apply" },
-  "subtest_198": { "assert": "all and (max-device-aspect-ratio: 1919/1200) should not apply" },
-  "subtest_199": { "assert": "not all and (max-device-aspect-ratio: 1920/1201) should apply" },
-  "subtest_200": { "assert": "expression max-device-aspect-ratio should not be parseable" },
-  "subtest_201": { "assert": "expression max-aspect-ratio: 1/1 should be parseable" },
-  "subtest_202": { "assert": "expression max-aspect-ratio: 1  /1 should be parseable" },
-  "subtest_203": { "assert": "expression max-aspect-ratio: 1  / \t\n1 should be parseable" },
-  "subtest_204": { "assert": "expression max-aspect-ratio: 1/\r1 should be parseable" },
-  "subtest_205": { "assert": "expression max-aspect-ratio: 1 should not be parseable" },
-  "subtest_206": { "assert": "expression max-aspect-ratio: 0.5 should not be parseable" },
-  "subtest_207": { "assert": "expression max-aspect-ratio: 1.0/1 should not be parseable" },
-  "subtest_208": { "assert": "expression max-aspect-ratio: 1/1.0 should not be parseable" },
-  "subtest_209": { "assert": "expression max-aspect-ratio: 1.0/1.0 should not be parseable" },
-  "subtest_210": { "assert": "expression max-aspect-ratio: 0/1 should not be parseable" },
-  "subtest_211": { "assert": "expression max-aspect-ratio: 1/0 should not be parseable" },
-  "subtest_212": { "assert": "expression max-aspect-ratio: 0/0 should not be parseable" },
-  "subtest_213": { "assert": "expression max-aspect-ratio: -1/1 should not be parseable" },
-  "subtest_214": { "assert": "expression max-aspect-ratio: 1/-1 should not be parseable" },
-  "subtest_215": { "assert": "expression max-aspect-ratio: -1/-1 should not be parseable" },
-  "subtest_216": { "assert": "expression device-aspect-ratio: 1/1 should be parseable" },
-  "subtest_217": { "assert": "expression device-aspect-ratio: 1  /1 should be parseable" },
-  "subtest_218": { "assert": "expression device-aspect-ratio: 1  / \t\n1 should be parseable" },
-  "subtest_219": { "assert": "expression device-aspect-ratio: 1/\r1 should be parseable" },
-  "subtest_220": { "assert": "expression device-aspect-ratio: 1 should not be parseable" },
-  "subtest_221": { "assert": "expression device-aspect-ratio: 0.5 should not be parseable" },
-  "subtest_222": { "assert": "expression device-aspect-ratio: 1.0/1 should not be parseable" },
-  "subtest_223": { "assert": "expression device-aspect-ratio: 1/1.0 should not be parseable" },
-  "subtest_224": { "assert": "expression device-aspect-ratio: 1.0/1.0 should not be parseable" },
-  "subtest_225": { "assert": "expression device-aspect-ratio: 0/1 should not be parseable" },
-  "subtest_226": { "assert": "expression device-aspect-ratio: 1/0 should not be parseable" },
-  "subtest_227": { "assert": "expression device-aspect-ratio: 0/0 should not be parseable" },
-  "subtest_228": { "assert": "expression device-aspect-ratio: -1/1 should not be parseable" },
-  "subtest_229": { "assert": "expression device-aspect-ratio: 1/-1 should not be parseable" },
-  "subtest_230": { "assert": "expression device-aspect-ratio: -1/-1 should not be parseable" },
-  "monochrome_and_color": {},
-  "find_depth": {},
-  "subtest_231": { "assert": "all and (color:8) should apply" },
-  "subtest_232": { "assert": "all and (color:7) should not apply" },
-  "subtest_233": { "assert": "all and (color:9) should not apply" },
-  "subtest_234": { "assert": "all and (max-color:8) should apply" },
-  "subtest_235": { "assert": "all and (max-color:7) should not apply" },
-  "subtest_236": { "assert": "all and (max-color:9) should apply" },
-  "subtest_237": { "assert": "all and (color) should apply" },
-  "subtest_238": { "assert": "expression max-color should not be parseable" },
-  "subtest_239": { "assert": "expression min-color should not be parseable" },
-  "subtest_240": { "assert": "all and (monochrome) should not apply" },
-  "subtest_241": { "assert": "expression max-monochrome should not be parseable" },
-  "subtest_242": { "assert": "expression min-monochrome should not be parseable" },
-  "subtest_243": { "assert": "not all and (monochrome) should apply" },
-  "subtest_244": { "assert": "not all and (color) should not apply" },
-  "subtest_245": { "assert": "only all and (color) should apply" },
-  "subtest_246": { "assert": "only all and (monochrome) should not apply" },
-  "subtest_247": { "assert": "expression color: 1 should be parseable" },
-  "subtest_248": { "assert": "expression color: 327 should be parseable" },
-  "subtest_249": { "assert": "expression color: 0 should be parseable" },
-  "subtest_250": { "assert": "expression color: 1.0 should not be parseable" },
-  "subtest_251": { "assert": "expression color: -1 should not be parseable" },
-  "subtest_252": { "assert": "expression color: 1/1 should not be parseable" },
-  "subtest_253": { "assert": "expression min-monochrome: 1 should be parseable" },
-  "subtest_254": { "assert": "expression min-monochrome: 327 should be parseable" },
-  "subtest_255": { "assert": "expression min-monochrome: 0 should be parseable" },
-  "subtest_256": { "assert": "expression min-monochrome: 1.0 should not be parseable" },
-  "subtest_257": { "assert": "expression min-monochrome: -1 should not be parseable" },
-  "subtest_258": { "assert": "expression min-monochrome: 1/1 should not be parseable" },
-  "subtest_259": { "assert": "expression max-color-index: 1 should be parseable" },
-  "subtest_260": { "assert": "expression max-color-index: 327 should be parseable" },
-  "subtest_261": { "assert": "expression max-color-index: 0 should be parseable" },
-  "subtest_262": { "assert": "expression max-color-index: 1.0 should not be parseable" },
-  "subtest_263": { "assert": "expression max-color-index: -1 should not be parseable" },
-  "subtest_264": { "assert": "expression max-color-index: 1/1 should not be parseable" },
-  "subtest_265": { "assert": "(color-index: 0) should apply" },
-  "subtest_266": { "assert": "(color-index: 1) should not apply" },
-  "subtest_267": { "assert": "(min-color-index: 0) should apply" },
-  "subtest_268": { "assert": "(min-color-index: 1) should not apply" },
-  "subtest_269": { "assert": "(max-color-index: 0) should apply" },
-  "subtest_270": { "assert": "(max-color-index: 1) should apply" },
-  "subtest_271": { "assert": "(max-color-index: 157) should apply" },
-  "subtest_272": { "assert": "expression resolution: 3dpi should be parseable" },
-  "subtest_273": { "assert": "expression resolution:3dpi should be parseable" },
-  "subtest_274": { "assert": "expression resolution: 3.0dpi should be parseable" },
-  "subtest_275": { "assert": "expression resolution: 3.4dpi should be parseable" },
-  "subtest_276": { "assert": "expression resolution\t: 120dpcm should be parseable" },
-  "subtest_277": { "assert": "expression resolution: 0dpi should not be parseable" },
-  "subtest_278": { "assert": "expression resolution: -3dpi should not be parseable" },
-  "subtest_279": { "assert": "expression min-resolution: 3dpi should be parseable" },
-  "subtest_280": { "assert": "expression min-resolution:3dpi should be parseable" },
-  "subtest_281": { "assert": "expression min-resolution: 3.0dpi should be parseable" },
-  "subtest_282": { "assert": "expression min-resolution: 3.4dpi should be parseable" },
-  "subtest_283": { "assert": "expression min-resolution\t: 120dpcm should be parseable" },
-  "subtest_284": { "assert": "expression min-resolution: 0dpi should not be parseable" },
-  "subtest_285": { "assert": "expression min-resolution: -3dpi should not be parseable" },
-  "subtest_286": { "assert": "expression max-resolution: 3dpi should be parseable" },
-  "subtest_287": { "assert": "expression max-resolution:3dpi should be parseable" },
-  "subtest_288": { "assert": "expression max-resolution: 3.0dpi should be parseable" },
-  "subtest_289": { "assert": "expression max-resolution: 3.4dpi should be parseable" },
-  "subtest_290": { "assert": "expression max-resolution\t: 120dpcm should be parseable" },
-  "subtest_291": { "assert": "expression max-resolution: 0dpi should not be parseable" },
-  "subtest_292": { "assert": "expression max-resolution: -3dpi should not be parseable" },
-  "find_resolution": {},
-  "subtest_293": { "assert": "(resolution: 133dpi) should apply" },
-  "subtest_294": { "assert": "(resolution: 134dpi) should not apply" },
-  "subtest_295": { "assert": "(resolution: 132dpi) should not apply" },
-  "subtest_296": { "assert": "(min-resolution: 132dpi) should apply" },
-  "subtest_297": { "assert": "not all and (min-resolution: 132dpi) should not apply" },
-  "subtest_298": { "assert": "not all and (min-resolution: 134dpi) should apply" },
-  "subtest_299": { "assert": "all and (min-resolution: 134dpi) should not apply" },
-  "subtest_300": { "assert": "(min-resolution: 51dpcm) should apply" },
-  "subtest_301": { "assert": "(max-resolution: 53dpcm) should apply" },
-  "subtest_302": { "assert": "(max-resolution: 51dpcm) should not apply" },
-  "subtest_303": { "assert": "not all and (min-resolution: 53dpcm) should apply" },
-  "subtest_304": { "assert": "expression scan should be parseable" },
-  "subtest_305": { "assert": "expression scan: progressive should be parseable" },
-  "subtest_306": { "assert": "expression scan:interlace should be parseable" },
-  "subtest_307": { "assert": "expression min-scan:interlace should not be parseable" },
-  "subtest_308": { "assert": "expression scan: 1 should not be parseable" },
-  "subtest_309": { "assert": "expression max-scan should not be parseable" },
-  "subtest_310": { "assert": "expression max-scan: progressive should not be parseable" },
-  "subtest_311": { "assert": "(scan) should not apply" },
-  "subtest_312": { "assert": "(scan: progressive) should not apply" },
-  "subtest_313": { "assert": "(scan: interlace) should not apply" },
-  "subtest_314": { "assert": "not all and (scan) should apply" },
-  "subtest_315": { "assert": "not all and (scan: progressive) should apply" },
-  "subtest_316": { "assert": "not all and (scan: interlace) should apply" },
-  "subtest_317": { "assert": "expression grid should be parseable" },
-  "subtest_318": { "assert": "expression grid: 0 should be parseable" },
-  "subtest_319": { "assert": "expression grid: 1 should be parseable" },
-  "subtest_320": { "assert": "expression grid: 1 should be parseable" },
-  "subtest_321": { "assert": "expression min-grid should not be parseable" },
-  "subtest_322": { "assert": "expression min-grid:0 should not be parseable" },
-  "subtest_323": { "assert": "expression max-grid: 1 should not be parseable" },
-  "subtest_324": { "assert": "expression grid: 2 should not be parseable" },
-  "subtest_325": { "assert": "expression grid: -1 should not be parseable" },
-  "subtest_326": { "assert": "(grid) should not apply" },
-  "subtest_327": { "assert": "(grid: 0) should apply" },
-  "subtest_328": { "assert": "(grid: 1) should not apply" },
-  "subtest_329": { "assert": "(grid: 2) should not apply" },
-  "subtest_330": { "assert": "(grid: -1) should not apply" },
-  "subtest_331": { "assert": "(orientation should apply" },
-  "subtest_332": { "assert": "not all and (orientation should not apply" },
-  "subtest_333": { "assert": "(orientation: should not apply" },
-  "subtest_334": { "assert": "all,(orientation: should apply" },
-  "subtest_335": { "assert": "(orientation:,all should not apply" },
-  "subtest_336": { "assert": "not all and (grid should apply" },
-  "subtest_337": { "assert": "only all and (grid should not apply" },
-  "subtest_338": { "assert": "(grid should not apply" },
-  "subtest_339": { "assert": "all,(grid should apply" },
-  "subtest_340": { "assert": "(grid,all should not apply" },
-  "subtest_341": { "assert": ",all should apply" },
-  "subtest_342": { "assert": "all, should apply" },
-  "subtest_343": { "assert": ",all, should apply" },
-  "subtest_344": { "assert": "all,badmedium should apply" },
-  "subtest_345": { "assert": "badmedium,all should apply" },
-  "subtest_346": { "assert": ",badmedium, should not apply" },
-  "subtest_347": { "assert": "all,(badexpression) should apply" },
-  "subtest_348": { "assert": "(badexpression),all should apply" },
-  "subtest_349": { "assert": "(badexpression),badmedium should not apply" },
-  "subtest_350": { "assert": "badmedium,(badexpression) should not apply" },
-  "subtest_351": { "assert": "all,[badsyntax] should apply" },
-  "subtest_352": { "assert": "[badsyntax],all should apply" },
-  "subtest_353": { "assert": "badmedium,[badsyntax] should not apply" },
-  "subtest_354": { "assert": "[badsyntax],badmedium should not apply" },
-  "subtest_355": { "assert": "query all and color : should not be parseable" },
-  "subtest_356": { "assert": "query all and color : 1 should not be parseable" },
-  "subtest_357": { "assert": "all and min-color : 1 should not apply" },
-  "subtest_358": { "assert": "(bogus) should not apply" },
-  "subtest_359": { "assert": "not all and (bogus) should not apply" },
-  "subtest_360": { "assert": "only all and (bogus) should not apply" }
-}
-*/</script>
 </head>
 <body onload="run()">
 <div id=log></div>
@@ -394,7 +27,7 @@
 
     function query_applies(q) {
       style.setAttribute("media", q);
-      return body_cs.getPropertyValue("text-decoration") == "underline";
+      return body_cs.getPropertyValue("text-decoration-line") == "underline";
     }
 
     function should_apply(q) {
@@ -715,8 +348,16 @@
       expression_should_be_parseable(feature + ": 3.0dpi");
       expression_should_be_parseable(feature + ": 3.4dpi");
       expression_should_be_parseable(feature + "\t: 120dpcm");
+      expression_should_be_parseable(feature + ": 1dppx");
+      expression_should_be_parseable(feature + ": 1x");
+      expression_should_be_parseable(feature + ": 1.5dppx");
+      expression_should_be_parseable(feature + ": 1.5x");
+      expression_should_be_parseable(feature + ": 2.0dppx");
+      // TODO(emilio): Doesn't seem right to exclude 0 here.
       expression_should_not_be_parseable(feature + ": 0dpi");
       expression_should_not_be_parseable(feature + ": -3dpi");
+      expression_should_not_be_parseable(feature + ": 0dppx");
+      expression_should_not_be_parseable(feature + ": 0x");
     }
 
     // Find the resolution using max-resolution
@@ -737,6 +378,8 @@
     if (query_applies("(min-resolution: " + resolution + "dpi)")) {
       // It's exact!
       should_apply("(resolution: " + resolution + "dpi)");
+      should_apply("(resolution: " + Math.floor(resolution/96) + "dppx)");
+      should_apply("(resolution: " + Math.floor(resolution/96) + "x)");
       should_not_apply("(resolution: " + (resolution + 1) + "dpi)");
       should_not_apply("(resolution: " + (resolution - 1) + "dpi)");
       dpi_high = resolution + 1;
@@ -798,6 +441,8 @@
     should_apply("(orientation");
     should_not_apply("not all and (orientation");
     should_not_apply("(orientation:");
+    should_not_apply("(orientation:)");
+    should_not_apply("(orientation:  )");
     should_apply("all,(orientation:");
     should_not_apply("(orientation:,all");
     should_apply("not all and (grid");
diff --git a/css/mediaqueries/viewport-script-dynamic-ref.html b/css/mediaqueries/viewport-script-dynamic-ref.html
new file mode 100644
index 0000000..e3f1c95
--- /dev/null
+++ b/css/mediaqueries/viewport-script-dynamic-ref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io">
+<meta name="viewport" content="width=300">
+<style>
+p { color: green; }
+</style>
+<p>Should be green</p>
diff --git a/css/mediaqueries/viewport-script-dynamic.html b/css/mediaqueries/viewport-script-dynamic.html
new file mode 100644
index 0000000..7433877
--- /dev/null
+++ b/css/mediaqueries/viewport-script-dynamic.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: Meta viewport after a script and stylesheets</title>
+<link rel="author" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1424878">
+<link rel="help" href="https://drafts.csswg.org/mediaqueries/#mf-dimensions">
+<link rel="match" href="viewport-script-dynamic-ref.html">
+<style>
+p { color: green; }
+/* Ensure that we initially match it, and stop matching it afterwards */
+@media (min-width: 310px) {
+  p {
+    color: red;
+  }
+}
+</style>
+<!-- The broken script below is the point of the test, see the bugzilla bug. -->
+<script src="intentionally-broken-url.js"></script>
+<meta name="viewport" content="width=300">
+<p>Should be green</p>
diff --git a/css/motion/OWNERS b/css/motion/OWNERS
new file mode 100644
index 0000000..ac8ec26
--- /dev/null
+++ b/css/motion/OWNERS
@@ -0,0 +1,3 @@
+@dirkschulze
+@jihyerish
+@ewilligers
diff --git a/css/motion/offset-anchor-transform-box-fill-box-ref.html b/css/motion/offset-anchor-transform-box-fill-box-ref.html
new file mode 100644
index 0000000..f718ea6
--- /dev/null
+++ b/css/motion/offset-anchor-transform-box-fill-box-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="width: 100px; height: 100px; background-color: green"></div>
diff --git a/css/motion/offset-anchor-transform-box-fill-box.html b/css/motion/offset-anchor-transform-box-fill-box.html
new file mode 100644
index 0000000..563ffda
--- /dev/null
+++ b/css/motion/offset-anchor-transform-box-fill-box.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>CSS Motion Path: offset-anchor with transform-box: fill-box</title>
+<link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-anchor-property">
+<link rel="match" href="offset-anchor-transform-box-fill-box-ref.html">
+<meta name="assert" content="Tests offset-anchor together with a fill-box transform-box">
+<style>
+#target {
+  transform-box: fill-box;
+  transform-origin: 50% 50%;
+  offset-anchor: 25% 25%;
+  offset-path: path("M75,-25v100");
+  offset-distance: 50%;
+}
+</style>
+<svg width="400" height="400">
+  <rect width="100" height="100" fill="red"/>
+  <rect id="target" x="150" y="100" width="100" height="100" fill="green"/>
+</svg>
diff --git a/css/motion/parsing/resources/parsing-testcommon.js b/css/motion/parsing/resources/parsing-testcommon.js
index 688356b..b075882 100644
--- a/css/motion/parsing/resources/parsing-testcommon.js
+++ b/css/motion/parsing/resources/parsing-testcommon.js
@@ -1,5 +1,8 @@
 'use strict';
 
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
 function test_valid_value(property, value, serializedValue) {
     if (arguments.length < 3)
         serializedValue = value;
@@ -9,17 +12,20 @@
     test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        assert_not_equals(div.style[property], "");
-    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
 
-    test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        var readValue = div.style[property];
-        assert_equals(readValue, serializedValue);
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
         div.style[property] = readValue;
-        assert_equals(div.style[property], readValue);
-    }, "Serialization should round-trip after setting e.style['" + property + "'] = " + stringifiedValue);
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
 }
 
 function test_invalid_value(property, value) {
@@ -28,6 +34,6 @@
     test(function(){
         var div = document.createElement('div');
         div.style[property] = value;
-        assert_equals(div.style[property], "");
+        assert_equals(div.style.getPropertyValue(property), "");
     }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
 }
diff --git a/css/reference/nothing.html b/css/reference/nothing.html
new file mode 100644
index 0000000..98ee818
--- /dev/null
+++ b/css/reference/nothing.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>CSS Reftest Reference</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<p>There should be nothing below.</p>
diff --git a/css/reference/ref-filled-green-100px-square-only.html b/css/reference/ref-filled-green-100px-square-only.html
new file mode 100644
index 0000000..82fcaa3
--- /dev/null
+++ b/css/reference/ref-filled-green-100px-square-only.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<p>Test passes if there is a filled green square.</p>
+<div style="width:100px; height:100px; background:green;"></div>
diff --git a/css/requirements.txt b/css/requirements.txt
index 9267157..416628d 100644
--- a/css/requirements.txt
+++ b/css/requirements.txt
@@ -1,5 +1,6 @@
-six==1.10.0
 Template-Python==0.1.post1
-html5lib==0.9999999
-lxml==3.7.3
-mercurial==4.1
+html5lib==1.0.1
+lxml==4.1.1
+mercurial==4.5
+six==1.11.0
+webencodings==0.5.1
diff --git a/css/selectors/anplusb-selector-parsing.html b/css/selectors/anplusb-selector-parsing.html
new file mode 100644
index 0000000..b7cfd29
--- /dev/null
+++ b/css/selectors/anplusb-selector-parsing.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Selectors: Test parsing of an+b selectors</title>
+        <link rel="author" title="Chris Nardi" href="mailto:cnardi@chromium.org">
+        <link rel="help" href="https://drafts.csswg.org/selectors-3/#nth-child-pseudo">
+        <link rel="help" href="https://drafts.csswg.org/selectors-3/#nth-last-child-pseudo">
+        <link rel="help" href="https://drafts.csswg.org/selectors-3/#nth-of-type-pseudo">
+        <link rel="help" href="https://drafts.csswg.org/selectors-3/#nth-last-of-type-pseudo">
+        <meta charset="utf-8">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <style id="teststyles">
+        </style>
+    </head>
+    <body>
+        <script>
+            function add_selector_style(source) {
+                var style_element = document.getElementById("teststyles");
+                style_element.firstChild.data = source + "{ font-size: 1em; }";
+                return style_element.sheet;
+            }
+
+            function assert_selector_serializes_to(source, expected_result) {
+                test(function() {
+                    var sheet = add_selector_style(source);
+                    assert_equals(sheet.cssRules[0].selectorText, expected_result);
+                }, source + " should be parsed and serialized correctly");
+            }
+
+            function assert_invalid_selector(source) {
+                test(function() {
+                    var sheet = add_selector_style(source);
+                    assert_equals(sheet.cssRules[0], undefined);
+                }, source + " should not parse");
+            }
+
+            function run_tests_on_anplusb_selector(source) {
+                assert_selector_serializes_to(source + '(1n+0)', source + '(n)');
+                assert_selector_serializes_to(source + '(n+0)', source + '(n)');
+                assert_selector_serializes_to(source + '(n)', source + '(n)');
+                assert_selector_serializes_to(source + '(-n+0)', source + '(-n)');
+                assert_selector_serializes_to(source + '(-n)', source + '(-n)');
+                assert_selector_serializes_to(source + '(N)', source + '(n)');
+                assert_selector_serializes_to(source + '(+n+3)', source + '(n+3)');
+                assert_selector_serializes_to(source + '( +n + 7 )', source + '(n+7)');
+                assert_selector_serializes_to(source + '(  N- 123)', source + '(n-123)');
+                assert_selector_serializes_to(source + '(n- 10)', source + '(n-10)');
+                assert_selector_serializes_to(source + '(-n\n- 1)', source + '(-n-1)');
+                assert_selector_serializes_to(source + '( 23n\n\n+\n\n123 )', source + '(23n+123)');
+
+                assert_invalid_selector(source + '(n- 1 2)');
+                assert_invalid_selector(source + '(n-b1)');
+                assert_invalid_selector(source + '(n-+1)');
+                assert_invalid_selector(source + '(n-1n)');
+                assert_invalid_selector(source + '(-n -b1)');
+                assert_invalid_selector(source + '(-1n- b1)');
+                assert_invalid_selector(source + '(-n-13b1)');
+                assert_invalid_selector(source + '(-n-+1)');
+                assert_invalid_selector(source + '(-n+n)');
+                assert_invalid_selector(source + '(+ 1n)');
+                assert_invalid_selector(source + '(  n +12 3)');
+                assert_invalid_selector(source + '(  12 n )');
+                assert_invalid_selector(source + '(+12n-0+1)');
+                assert_invalid_selector(source + '(+12N -- 1)');
+                assert_invalid_selector(source + '(+12 N )');
+                assert_invalid_selector(source + '(+ n + 7)');
+            }
+
+            run_tests_on_anplusb_selector(':nth-child');
+            run_tests_on_anplusb_selector(':nth-last-child');
+            run_tests_on_anplusb_selector(':nth-of-type');
+            run_tests_on_anplusb_selector(':nth-last-of-type');
+        </script>
+    </body>
+</html>
diff --git a/css/selectors/any-link-dynamic-001-ref.html b/css/selectors/any-link-dynamic-001-ref.html
new file mode 100644
index 0000000..b540742
--- /dev/null
+++ b/css/selectors/any-link-dynamic-001-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS test reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<style>
+  span { color: green; }
+</style>
+<body>
+  <a></a><span>This should be green</span>
+</body>
diff --git a/css/selectors/any-link-dynamic-001.html b/css/selectors/any-link-dynamic-001.html
new file mode 100644
index 0000000..e84989f
--- /dev/null
+++ b/css/selectors/any-link-dynamic-001.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS test: Handling of dynamic changes to :any-link selectors</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="match" href="any-link-dynamic-001-ref.html">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#the-any-link-pseudo">
+<style>
+  span { color: green; }
+  :any-link + span { color: red; }
+</style>
+<body onload="window.oldColor = getComputedStyle(document.querySelector('span')).color;
+              document.querySelector('a').removeAttribute('href');">
+  <a href=""></a><span>This should be green</span>
+</body>
diff --git a/css/selectors/child-indexed-pseudo-class.html b/css/selectors/child-indexed-pseudo-class.html
index 1c58a5e..2e496d8 100644
--- a/css/selectors/child-indexed-pseudo-class.html
+++ b/css/selectors/child-indexed-pseudo-class.html
@@ -6,37 +6,50 @@
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script>
-test(function() {
-  var check = function(element, selectors) {
-    for (var i = 0; i < selectors.length; ++i) {
-      var selector = selectors[i][0];
-      var expected = selectors[i][1];
-      assert_equals(expected, element.matches(selector),
-                    "Expected " + element.tagName + " element to " +
-                    (expected ? "match " : "not match ") + selector);
-    }
+var check = function(element, selectors, qsRoot) {
+  for (var i = 0; i < selectors.length; ++i) {
+    var selector = selectors[i][0];
+    var expected = selectors[i][1];
+    test(function() {
+      assert_equals(expected, element.matches(selector));
+
+      if (qsRoot) {
+        assert_equals(expected, element === qsRoot.querySelector(selector));
+        var qsa = qsRoot.querySelectorAll(selector);
+        assert_equals(expected, !!qsa.length && element === qsa[0]);
+      }
+    }, "Expected " + element.tagName + " element to " +
+         (expected ? "match " : "not match ") + selector + " with matches" +
+         (qsRoot ? ", querySelector(), and querySelectorAll()" : ""));
   }
+}
 
-  var rootOfSubtreeSelectors = [
-    [ ":first-child", true ],
-    [ ":last-child", true ],
-    [ ":first-of-type", true ],
-    [ ":last-of-type", true ],
-    [ ":nth-child(1)", true ],
-    [ ":nth-child(n)", true ],
-    [ ":nth-last-child(1)", true ],
-    [ ":nth-last-child(n)", true ],
-    [ ":nth-of-type(1)", true ],
-    [ ":nth-of-type(n)", true ],
-    [ ":nth-last-of-type(1)", true ],
-    [ ":nth-last-of-type(n)", true ],
-    [ ":nth-child(2)", false ],
-    [ ":nth-last-child(2)", false],
-    [ ":nth-of-type(2)", false ],
-    [ ":nth-last-of-type(2)", false],
-  ];
+var rootOfSubtreeSelectors = [
+  [ ":first-child", true ],
+  [ ":last-child", true ],
+  [ ":only-child", true ],
+  [ ":first-of-type", true ],
+  [ ":last-of-type", true ],
+  [ ":only-of-type", true ],
+  [ ":nth-child(1)", true ],
+  [ ":nth-child(n)", true ],
+  [ ":nth-last-child(1)", true ],
+  [ ":nth-last-child(n)", true ],
+  [ ":nth-of-type(1)", true ],
+  [ ":nth-of-type(n)", true ],
+  [ ":nth-last-of-type(1)", true ],
+  [ ":nth-last-of-type(n)", true ],
+  [ ":nth-child(2)", false ],
+  [ ":nth-last-child(2)", false],
+  [ ":nth-of-type(2)", false ],
+  [ ":nth-last-of-type(2)", false],
+];
 
-  check(document.documentElement, rootOfSubtreeSelectors);
-  check(document.createElement('div'), rootOfSubtreeSelectors);
-}, "child-indexed pseudo-classes should match without a parent")
+check(document.documentElement, rootOfSubtreeSelectors, document);
+check(document.createElement('div'), rootOfSubtreeSelectors);
+
+var fragment = document.createDocumentFragment();
+var div = document.createElement('div');
+fragment.appendChild(div);
+check(div, rootOfSubtreeSelectors, fragment);
 </script>
diff --git a/css/selectors/focus-visible-001-manual.html b/css/selectors/focus-visible-001-manual.html
new file mode 100644
index 0000000..956b1a9
--- /dev/null
+++ b/css/selectors/focus-visible-001-manual.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8" />
+  <title>CSS Test (Selectors): Keyboard focus enables :focus-visible</title>
+  <link rel="author" title="Rob Dodson" href="robdodson@chromium.org" />
+  <link rel="help" href="https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo" />
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <style>
+    :focus-visible { color: rgb(0, 128, 0); }
+  </style>
+</head>
+<body>
+  <ol id="instructions">
+    <li>If the user-agent does not claim to support the <code>:focus-visible</code> pseudo-class then SKIP this test.</li>
+    <li>Use the TAB key on the keyboard to focus the element below that says "Focus me."</li>
+    <li>If the element does not have green text, then the test result is FAILURE. If the element has green text, then the test result is SUCCESS.</li>
+  </ol>
+  <br />
+  <div id="el" tabindex="0">Focus me.</div>
+  <script>
+    async_test(function(t) {
+      el.addEventListener("focus", t.step_func(function() {
+        assert_equals(getComputedStyle(el).color, "rgb(0, 128, 0)");
+        t.done();
+      }));
+    }, "Keyboard focus should match :focus-visible");
+  </script>
+</body>
+</html>
diff --git a/css/selectors/hover-002-manual.html b/css/selectors/hover-002-manual.html
new file mode 100644
index 0000000..50859c5
--- /dev/null
+++ b/css/selectors/hover-002-manual.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selectors: Change class to enable :hover</title>
+<link rel="author" title="Rune Lillesveen" href="futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/selectors/#the-hover-pseudo">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  .affected:hover { color: green }
+  #hoveredContents { display: contents }
+</style>
+<div id="hovered">Hover me - should become green</div>
+<div id="hoveredContents">
+  <div id="hovered2">Hover me - should become green</div>
+</div>
+<script>
+  function testElementGreen(test, element) {
+    element.addEventListener("mouseover", test.step_func(event => {
+      assert_equals(getComputedStyle(element).color, "rgb(0, 128, 0)");
+      test.done();
+    }));
+  }
+
+  // Setting the affected classes here makes the two elements go from never
+  // reacting to hover to being affected by hover without changing computed
+  // style.
+  hovered.offsetTop;
+  hovered.className = "affected";
+  hoveredContents.className = "affected";
+
+  async_test(t => { testElementGreen(t, hovered); }, "Hover #hovered element should make it go green");
+  async_test(t => { testElementGreen(t, hovered2); }, "Hover #hoveredContents child should make it go green");
+</script>
diff --git a/css/selectors/invalidation/any-link-pseudo.html b/css/selectors/invalidation/any-link-pseudo.html
new file mode 100644
index 0000000..9792fd0
--- /dev/null
+++ b/css/selectors/invalidation/any-link-pseudo.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Selectors Invalidation: :any-link</title>
+    <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
+    <link rel="help" href="https://drafts.csswg.org/selectors-4/#the-any-link-pseudo">
+    <meta name="assert" content="This tests that the :any-link selector is effective">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      #link { background-color: red }
+      #link:any-link { background-color: green }
+      #link + div { color: pink }
+    </style>
+    <a id="link">This link should have a green background.</a>
+    <div>
+        <div></div>
+        <div></div>
+        <div></div>
+        <div></div>
+    </div>
+    <script>
+      test(function() {
+        var red = "rgb(255, 0, 0)";
+        var green = "rgb(0, 128, 0)";
+
+        assert_equals(getComputedStyle(link).backgroundColor, red);
+
+        link.href = "not-visited.html";
+
+        assert_equals(getComputedStyle(link).backgroundColor, green);
+      }, "Style was recalculated for the :any-link pseudo class.");
+
+    </script>
+  </head>
+</html>
\ No newline at end of file
diff --git a/css/selectors/invalidation/matches.html b/css/selectors/invalidation/matches.html
new file mode 100644
index 0000000..634ded3
--- /dev/null
+++ b/css/selectors/invalidation/matches.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Selectors Invalidation: :matches()</title>
+    <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
+    <link rel="help" href="https://drafts.csswg.org/selectors-4/#matches">
+    <meta name="assert" content="This tests that the :matches() selector is effective">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      .b {
+        color: yellow;
+      }
+      /*Simple selector arguments */
+      .a :matches(.b, .c) {
+        color: red;
+      }
+      /*Compound selector arguments */
+      .a :matches(.c#d, .e) {
+        color: green;
+      }
+      /* Complex selector arguments */
+      .a .g>.b {
+        color: black;
+      }
+      .a :matches(.e+.f, .g>.b, .h) {
+        color: blue;
+      }
+      .g>.b {
+        color: black;
+      }
+      .a .h {
+        color: black;
+      }
+      /* Nested */
+      .a+.c>.e {
+        color: black;
+      }
+      .c>.a+.e {
+        color: black;
+      }
+      .a+:matches(.b+.f, :matches(.c>.e, .g)) {
+        color: red;
+      }
+      .c>.e {
+        color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="a1">
+      <div class="b" id="b1">
+        Red
+      </div>
+      <div class="c" id="c1">
+        Red
+      </div>
+      <div class="c" id="d">
+        Green
+      </div>
+      <div class="e" id="e1">
+        Green
+      </div>
+      <div class="f" id="f1">
+        Blue
+      </div>
+      <div class="g">
+        <div class="b" id="b2">
+          Blue
+          <div class="b" id="b3">
+            Red
+          </div>
+        </div>
+      </div>
+      <div class="h" id="h1">
+        Black
+      </div>
+    </div>
+    <div class="c" id="c2">
+      <div id="a2"></div>
+      <div class="e" id="e2">
+        Red
+      </div>
+    </div>
+    <script>
+      document.body.offsetTop;
+
+      var black = "rgb(0, 0, 0)";
+      var blue = "rgb(0, 0, 255)";
+      var green = "rgb(0, 128, 0)";
+      var red = "rgb(255, 0, 0)";
+      var yellow = "rgb(255, 255, 0)";
+
+      test(() => {
+        assert_equals(getComputedStyle(b1).color, yellow);
+        assert_equals(getComputedStyle(b2).color, black);
+        assert_equals(getComputedStyle(b3).color, yellow);
+        assert_equals(getComputedStyle(c1).color, black);
+        assert_equals(getComputedStyle(d).color, black);
+        assert_equals(getComputedStyle(e1).color, black);
+        assert_equals(getComputedStyle(e2).color, black);
+        assert_equals(getComputedStyle(f1).color, black);
+        assert_equals(getComputedStyle(h1).color, black);
+      }, "Preconditions.");
+
+      test(() => {
+        a1.className = "a";
+        assert_equals(getComputedStyle(b1).color, red);
+        assert_equals(getComputedStyle(b3).color, red);
+        assert_equals(getComputedStyle(c1).color, red);
+      }, "Invalidate :matches() for simple selector arguments.");
+
+      test(() => {
+        a1.className = "a";
+        assert_equals(getComputedStyle(d).color, green);
+      }, "Invalidate :matches() for compound selector arguments.");
+
+      test(() => {
+        a1.className = "a";
+        assert_equals(getComputedStyle(b2).color, blue);
+        assert_equals(getComputedStyle(b3).color, red);
+        assert_equals(getComputedStyle(f1).color, blue);
+      }, "Invalidate :matches() for complex selector arguments.");
+
+      test(() => {
+        a1.className = "a";
+        assert_equals(getComputedStyle(e2).color, black);
+        a2.className = "a";
+        assert_equals(getComputedStyle(e2).color, red);
+      }, "Invalidate nested :matches().");
+
+      test(() => {
+        a1.className = "a";
+        assert_equals(getComputedStyle(b2).color, blue);
+        assert_equals(getComputedStyle(h1).color, black);
+      }, "Test specificity of :matches().");
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/css/selectors/invalidation/quirks-mode-stylesheet-dynamic-add-001.html b/css/selectors/invalidation/quirks-mode-stylesheet-dynamic-add-001.html
new file mode 100644
index 0000000..3d7be98
--- /dev/null
+++ b/css/selectors/invalidation/quirks-mode-stylesheet-dynamic-add-001.html
@@ -0,0 +1,27 @@
+<!-- Quirks mode -->
+<meta charset="utf-8">
+<title>Invalidation of style due to a dynamic stylesheet change in quirks mode</title>
+<link rel="help" href="https://html.spec.whatwg.org/#case-sensitivity-of-selectors">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1433589">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #foo {
+    width: 100px;
+    height: 100px;
+    background: red;
+  }
+</style>
+Should see a green square below.
+<div id="foo"></div>
+<script>
+test(function() {
+  let foo = document.getElementById('foo');
+  assert_equals(getComputedStyle(foo).backgroundColor, "rgb(255, 0, 0)");
+  let style = document.createElement('style');
+  style.textContent = "#FoO { background: green; }";
+  document.body.appendChild(style);
+  assert_equals(getComputedStyle(foo).backgroundColor, "rgb(0, 128, 0)");
+}, "Style should've changed to a green background");
+</script>
diff --git a/css/selectors/invalidation/selectorText-dynamic-001.html b/css/selectors/invalidation/selectorText-dynamic-001.html
new file mode 100644
index 0000000..c705c2b
--- /dev/null
+++ b/css/selectors/invalidation/selectorText-dynamic-001.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: invalidation of class changes when the selector in a rule has changed</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  body { background: green; }
+  .red { background: red; }
+</style>
+<body class="red">
+Should have a green background.
+<script>
+test(() => {
+  document.body.offsetTop;
+  assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(255, 0, 0)");
+  document.body.className = "";
+  document.styleSheets[0].cssRules[1].selectorText = ".bar";
+  assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(0, 128, 0)");
+}, "Style should be recomputed correctly when the selector it depends on changes");
+</script>
+</body>
diff --git a/css/selectors/invalidation/sheet-going-away-001.html b/css/selectors/invalidation/sheet-going-away-001.html
new file mode 100644
index 0000000..7b704ec
--- /dev/null
+++ b/css/selectors/invalidation/sheet-going-away-001.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: invalidation of class changes when the sheet the style depends on goes away</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  body { background: green; }
+</style>
+<style id="style">
+  .red { background: red; }
+</style>
+<body class="red">
+Should have a green background.
+<script>
+test(() => {
+  document.body.offsetTop;
+  assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(255, 0, 0)");
+  document.body.className = "";
+  style.remove();
+  assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(0, 128, 0)");
+}, "Style should be recomputed correctly when the stylesheet it depends on goes away");
+</script>
+</body>
diff --git a/css/selectors/invalidation/sheet-going-away-002-ref.html b/css/selectors/invalidation/sheet-going-away-002-ref.html
new file mode 100644
index 0000000..6784161
--- /dev/null
+++ b/css/selectors/invalidation/sheet-going-away-002-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<p style="color: green">
+  Should be green.
+</p>
diff --git a/css/selectors/invalidation/sheet-going-away-002.html b/css/selectors/invalidation/sheet-going-away-002.html
new file mode 100644
index 0000000..4177823
--- /dev/null
+++ b/css/selectors/invalidation/sheet-going-away-002.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: invalidation of class changes when the sheet the style depends on goes away</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850">
+<link rel="match" href="sheet-going-away-002-ref.html">
+<style>
+  p { color: green; }
+</style>
+<style id="style">
+  .red p { color: red; }
+</style>
+<body class="red">
+<p>
+  Should be green.
+</p>
+<script>
+document.body.offsetTop;
+document.body.className = "";
+style.remove();
+</script>
+</body>
diff --git a/css/selectors/matches-nested.html b/css/selectors/matches-nested.html
new file mode 100644
index 0000000..b82d43d
--- /dev/null
+++ b/css/selectors/matches-nested.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Selectors: :matches()</title>
+    <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
+    <link rel="help" href="https://drafts.csswg.org/selectors-4/#matches">
+    <meta name="assert" content="This tests that the :matches() selector is effective when nested">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      /* Testing that highest specificity is chosen for class outside of :matches() */
+      .a+.b+.c>.e+.d {
+        color: black;
+        font-size: 10px;
+        width: 10px;
+      }
+      .a+:matches(.b+.f, .b+:matches(*, .c>.e, .g, *))+.d {
+        color: red;
+        font-size: 20px;
+      }
+      .a+.b+.c>.e+.d {
+        color: yellow;
+      }
+      /* Testing specificty of a class within :matches() */
+      .a+.c>.e {
+        color: black;
+      }
+      .a+:matches(.b+.f, :matches(.c>.e, .g)) {
+        color: red;
+      }
+      .c>.e {
+        color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="a">
+    </div>
+    <div class="b" id="b2">
+    </div>
+    <div class="c" id="c2">
+      <div class="e">
+      </div>
+      <div class="d" id="d1">
+        Yellow
+      </div>
+    </div>
+    <div class="a">
+    </div>
+    <div class="c" id="c2">
+      <div class="e" id="e1">
+        Red
+      </div>
+    </div>
+    <script>
+
+      var red = "rgb(255, 0, 0)";
+      var yellow = "rgb(255, 255, 0)";
+
+      test(() => {
+        assert_equals(getComputedStyle(d1).color, yellow);
+        assert_equals(getComputedStyle(d1).fontSize, "20px");
+        assert_equals(getComputedStyle(d1).width, "10px");
+      }, "Test nested :matches() chooses highest specificity for class outside :matches().");
+
+      test(() => {
+        assert_equals(getComputedStyle(e1).color, red);
+      }, "Test nested :matches() specificity for class within arguments.");
+
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/css/selectors/matches-specificity.html b/css/selectors/matches-specificity.html
new file mode 100644
index 0000000..41d7251
--- /dev/null
+++ b/css/selectors/matches-specificity.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Selectors: :matches()</title>
+    <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
+    <link rel="help" href="https://drafts.csswg.org/selectors-4/#matches">
+    <meta name="assert" content="This tests that the :matches() selector chooses the correct specificity">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      .b.c + .d + .q.r + .s + #target {
+        font-size: 10px;
+        height: 10px;
+        width: 10px;
+      }
+      :matches(.a, .b.c + .d, .e) + :matches(* + .p, .q.r + .s, * + .t) + #target {
+        height: 20px;
+        width: 20px;
+      }
+      .b.c + .d + .q.r + .s + #target {
+        width: 30px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="b c"></div>
+    <div class="a d e"></div>
+    <div class="q r"></div>
+    <div class="p s t"></div>
+    <div id="target"></div>
+    <script>
+
+      test(() => {
+        assert_equals(getComputedStyle(target).width, "30px");
+        assert_equals(getComputedStyle(target).height, "20px");
+        assert_equals(getComputedStyle(target).fontSize, "10px");
+      }, "Test :matches() uses highest possible specificity");
+
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/css/selectors/resources/blue15x15.png b/css/selectors/resources/blue15x15.png
new file mode 100644
index 0000000..89de32f
--- /dev/null
+++ b/css/selectors/resources/blue15x15.png
Binary files differ
diff --git a/css/selectors/selection-image-001-no-selection-noref.html b/css/selectors/selection-image-001-no-selection-noref.html
new file mode 100644
index 0000000..ad2104b
--- /dev/null
+++ b/css/selectors/selection-image-001-no-selection-noref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
diff --git a/css/selectors/selection-image-001-noref.html b/css/selectors/selection-image-001-noref.html
new file mode 100644
index 0000000..df3d946
--- /dev/null
+++ b/css/selectors/selection-image-001-noref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
+<script>
+onload = () => {
+  getSelection().removeAllRanges();
+  let r = document.createRange();
+  r.selectNode(document.documentElement);
+  getSelection().addRange(r);
+}
+</script>
diff --git a/css/selectors/selection-image-001.html b/css/selectors/selection-image-001.html
new file mode 100644
index 0000000..51a1643
--- /dev/null
+++ b/css/selectors/selection-image-001.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>::selection is respected on images</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<link rel="mismatch" href="selection-image-001-noref.html">
+<style>
+img::-moz-selection {
+  background: green;
+}
+img::selection {
+  background: green;
+}
+</style>
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
+<script>
+onload = () => {
+  getSelection().removeAllRanges();
+  let r = document.createRange();
+  r.selectNode(document.documentElement);
+  getSelection().addRange(r);
+}
+</script>
diff --git a/css/selectors/selection-image-002.html b/css/selectors/selection-image-002.html
new file mode 100644
index 0000000..72958da
--- /dev/null
+++ b/css/selectors/selection-image-002.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS Test: Image and text selection is painted.</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449010">
+<link rel="mismatch" href="selection-image-001-no-selection-noref.html">
+<p>
+  Some text <img src="resources/blue15x15.png"> some more.
+</p>
+<script>
+onload = () => {
+  getSelection().removeAllRanges();
+  let r = document.createRange();
+  r.selectNode(document.documentElement);
+  getSelection().addRange(r);
+}
+</script>
diff --git a/css/selectors/selector-structural-pseudo-root-ref.html b/css/selectors/selector-structural-pseudo-root-ref.html
new file mode 100644
index 0000000..6523fd3
--- /dev/null
+++ b/css/selectors/selector-structural-pseudo-root-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+html { background: green; }
+</style>
+<body>
+
+</body>
diff --git a/css/selectors/selector-structural-pseudo-root.html b/css/selectors/selector-structural-pseudo-root.html
new file mode 100644
index 0000000..2009ec1
--- /dev/null
+++ b/css/selectors/selector-structural-pseudo-root.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<style>
+html { background: red; }
+html:nth-child(1):nth-last-child(1):nth-of-type(1):nth-last-of-type(1):first-child:last-child:only-child:first-of-type:last-of-type:only-of-type { background: green; }
+</style>
+<link rel="help" href="https://www.w3.org/TR/css3-selectors/#structural-pseudos">
+<link rel="match" href="selector-structural-pseudo-root-ref.html">
+<meta name="assert" content="Structural pseudo-classes match the root element, sibling pseudos are based on siblings without needing a parent (Selectors 3 erratum)">
\ No newline at end of file
diff --git a/css/selectors/selectors-attr-white-space-001-ref.html b/css/selectors/selectors-attr-white-space-001-ref.html
new file mode 100644
index 0000000..3b768b3
--- /dev/null
+++ b/css/selectors/selectors-attr-white-space-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selectors: White space in attribute selectors (reference)</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au"/>
+<style>
+body { color: green; }
+</style>
+<p>This text should be green.</p>
+<p>This text should be green.</p>
+<p>This text should be green.</p>
+<p>This text should be green.</p>
diff --git a/css/selectors/selectors-attr-white-space-001.html b/css/selectors/selectors-attr-white-space-001.html
new file mode 100644
index 0000000..62df884
--- /dev/null
+++ b/css/selectors/selectors-attr-white-space-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selectors: White space in attribute selectors</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au"/>
+<link rel="help" href="https://drafts.csswg.org/selectors-3/#w3cselgrammar"/>
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#grammar"/>
+<link rel="match" href="selectors-attr-white-space-001-ref.html"/>
+<style>
+body { color: red; }
+[ data-test-1] { color: green; }
+[data-test-2 ] { color: green; }
+[data-test-3 = x] { color: green; }
+[ |data-test-4] { color: green; }
+[ | data-test-4] { color: red; }
+</style>
+<p data-test-1="">This text should be green.</p>
+<p data-test-2="">This text should be green.</p>
+<p data-test-3="x">This text should be green.</p>
+<p data-test-4="">This text should be green.</p>
diff --git a/css/selectors/user-invalid.html b/css/selectors/user-invalid.html
new file mode 100644
index 0000000..f0c3d93
--- /dev/null
+++ b/css/selectors/user-invalid.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Support for the :user-invalid pseudo-class</title>
+<link rel="help" href="https://drafts.csswg.org/selectors/#user-pseudos">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input>
+<script>
+test(() => {
+  const input = document.querySelector('input');
+  // The selector can't match because no interaction has happened.
+  assert_false(input.matches(':user-invalid'));
+}, ':user-invalid selector should be supported');
+
+// historical: https://github.com/w3c/csswg-drafts/issues/1329
+test(() => {
+  const input = document.querySelector('input');
+  // matches() will throw if the selector isn't suppported
+  assert_throws("SyntaxError", () => input.matches(':user-error'));
+}, ':user-error selector should not be supported');
+</script>
diff --git a/css/support/alignment.css b/css/support/alignment.css
new file mode 100644
index 0000000..d4c970c
--- /dev/null
+++ b/css/support/alignment.css
@@ -0,0 +1,367 @@
+/* align-self */
+.alignSelfAuto { align-self: auto; }
+.alignSelfNormal { align-self: normal; }
+.alignSelfStretch { align-self: stretch; }
+.alignSelfStart { align-self: start; }
+.alignSelfEnd { align-self: end; }
+.alignSelfCenter { align-self: center; }
+.alignSelfRight { align-self: right; }
+.alignSelfLeft { align-self: left; }
+
+.alignSelfFlexStart { align-self: flex-start; }
+.alignSelfFlexEnd { align-self: flex-end; }
+
+.alignSelfSelfStart { align-self: self-start; }
+.alignSelfSelfEnd { align-self: self-end; }
+
+.alignSelfSafeCenter { align-self: safe center; }
+.alignSelfUnsafeCenter { align-self: unsafe center; }
+.alignSelfSafeEnd { align-self: safe end; }
+.alignSelfUnsafeEnd { align-self: unsafe end; }
+.alignSelfSafeSelfEnd { align-self: safe self-end; }
+.alignSelfUnsafeSelfEnd { align-self: unsafe self-end; }
+.alignSelfSafeSelfStart { align-self: safe self-start; }
+.alignSelfUnsafeSelfStart { align-self: unsafe self-start; }
+.alignSelfSafeRight { align-self: safe right; }
+.alignSelfUnsafeRight { align-self: unsafe right; }
+.alignSelfSafeLeft { align-self: safe left; }
+.alignSelfUnsafeLeft { align-self: unsafe left; }
+.alignSelfSafeFlexEnd { align-self: safe flex-end; }
+.alignSelfUnsafeFlexEnd { align-self: unsafe flex-end; }
+.alignSelfSafeFlexStart { align-self: safe flex-start; }
+.alignSelfUnsafeFlexStart { align-self: unsafe flex-start; }
+
+.alignSelfBaseline { align-self: baseline; }
+.alignSelfFirstBaseline { align-self: first baseline; }
+.alignSelfLastBaseline { align-self: last baseline; }
+
+/* align-items */
+.alignItemsAuto { align-items: auto; }
+.alignItemsNormal { align-items: normal; }
+.alignItemsStretch { align-items: stretch; }
+.alignItemsStart { align-items: start; }
+.alignItemsCenter { align-items: center; }
+.alignItemsEnd { align-items: end; }
+.alignItemsLeft { align-items: left; }
+.alignItemsRight { align-items: right; }
+
+.alignItemsFlexStart { align-items: flex-start; }
+.alignItemsFlexEnd { align-items: flex-end; }
+
+.alignItemsSelfStart { align-items: self-start; }
+.alignItemsSelfEnd { align-items: self-end; }
+
+.alignItemsSafeCenter { align-items: safe center; }
+.alignItemsUnsafeCenter { align-items: unsafe center; }
+.alignItemsSafeEnd { align-items: safe end; }
+.alignItemsUnsafeEnd { align-items: unsafe end; }
+.alignItemsSafeSelfEnd { align-items: safe self-end; }
+.alignItemsUnsafeSelfEnd { align-items: unsafe self-end; }
+.alignItemsSafeSelfStart { align-items: safe self-start; }
+.alignItemsUnsafeSelfStart { align-items: unsafe self-start; }
+.alignItemsSafeRight { align-items: safe right; }
+.alignItemsUnsafeRight { align-items: unsafe right; }
+.alignItemsSafeLeft { align-items: safe left; }
+.alignItemsUnsafeLeft { align-items: unsafe left; }
+.alignItemsSafeFlexEnd { align-items: safe flex-end; }
+.alignItemsUnsafeFlexEnd { align-items: unsafe flex-end; }
+.alignItemsSafeFlexStart { align-items: safe flex-start; }
+.alignItemsUnsafeFlexStart { align-items: unsafe flex-start; }
+
+.alignItemsBaseline { align-items: baseline; }
+.alignItemsFirstBaseline { align-items: first baseline; }
+.alignItemsLastBaseline { align-items: last baseline; }
+
+/* align-content */
+.alignContentBaseline { align-content: baseline; }
+.alignContentLastBaseline { align-content: last-baseline; }
+.alignContentStart { align-content: start; }
+.alignContentEnd { align-content: end; }
+.alignContentCenter { align-content: center; }
+.alignContentLeft { align-content: left; }
+.alignContentRight { align-content: right; }
+
+.alignContentFlexStart { align-content: flex-start; }
+.alignContentFlexEnd { align-content: flex-end; }
+
+.alignContentSpaceBetween { align-content: space-between; }
+.alignContentSpaceAround { align-content: space-around; }
+.alignContentSpaceEvenly { align-content: space-evenly; }
+.alignContentStretch { align-content: stretch; }
+
+.alignContentSafeCenter { align-content: safe center; }
+.alignContentUnsafeCenter { align-content: unsafe center; }
+.alignContentSafeEnd { align-content: safe end; }
+.alignContentUnsafeEnd { align-content: unsafe end; }
+.alignContentSafeRight { align-content: safe right; }
+.alignContentUnsafeRight { align-content: unsafe right; }
+.alignContentSafeLeft { align-content: safe left; }
+.alignContentUnsafeLeft { align-content: unsafe left; }
+.alignContentSafeFlexEnd { align-content: safe flex-end; }
+.alignContentUnsafeFlexEnd { align-content: unsafe flex-end; }
+.alignContentSafeFlexStart { align-content: safe flex-start; }
+.alignContentUnsafeFlexStart { align-content: unsafe flex-start; }
+
+.alignContentBaseline { align-content: baseline; }
+.alignContentFirstBaseline { align-content: first baseline; }
+.alignContentLastBaseline { align-content: last baseline; }
+
+/* justify-self */
+.justifySelfAuto { justify-self: auto; }
+.justifySelfNormal { justify-self: normal; }
+.justifySelfStretch { justify-self: stretch; }
+.justifySelfStart { justify-self: start; }
+.justifySelfCenter { justify-self: center; }
+.justifySelfEnd { justify-self: end; }
+.justifySelfRight { justify-self: right; }
+.justifySelfLeft { justify-self: left; }
+
+.justifySelfFlexStart { justify-self: flex-start; }
+.justifySelfFlexEnd { justify-self: flex-end; }
+
+.justifySelfSelfStart { justify-self: self-start; }
+.justifySelfSelfEnd { justify-self: self-end; }
+
+.justifySelfSafeCenter { justify-self: safe center; }
+.justifySelfUnsafeCenter { justify-self: unsafe center; }
+.justifySelfSafeEnd { justify-self: safe end; }
+.justifySelfUnsafeEnd { justify-self: unsafe end; }
+.justifySelfSafeSelfEnd { justify-self: safe self-end; }
+.justifySelfUnsafeSelfEnd { justify-self: unsafe self-end; }
+.justifySelfSafeSelfStart { justify-self: safe self-start; }
+.justifySelfUnsafeSelfStart { justify-self: unsafe self-start; }
+.justifySelfSafeRight { justify-self: safe right; }
+.justifySelfUnsafeRight { justify-self: unsafe right; }
+.justifySelfSafeLeft { justify-self: safe left; }
+.justifySelfUnsafeLeft { justify-self: unsafe left; }
+.justifySelfSafeFlexEnd { justify-self: safe flex-end; }
+.justifySelfUnsafeFlexEnd { justify-self: unsafe flex-end; }
+.justifySelfSafeFlexStart { justify-self: safe flex-start; }
+.justifySelfUnsafeFlexStart { justify-self: unsafe flex-start; }
+
+.justifySelfBaseline { justify-self: baseline; }
+.justifySelfFirstBaseline { justify-self: first baseline; }
+.justifySelfLastBaseline { justify-self: last baseline; }
+
+/* justify-items */
+.justifyItemsAuto { justify-items: auto; }
+.justifyItemsNormal { justify-items: normal; }
+.justifyItemsStretch { justify-items: stretch; }
+.justifyItemsStart { justify-items: start; }
+.justifyItemsCenter { justify-items: center; }
+.justifyItemsEnd { justify-items: end; }
+.justifyItemsLeft { justify-items: left; }
+.justifyItemsRight { justify-items: right; }
+
+.justifyItemsFlexStart { justify-items: flex-start; }
+.justifyItemsFlexEnd { justify-items: flex-end; }
+
+.justifyItemsSelfStart { justify-items: self-start; }
+.justifyItemsSelfEnd { justify-items: self-end; }
+
+.justifyItemsLegacy { justify-items: legacy; }
+.justifyItemsLegacyLeft { justify-items: legacy left; }
+.justifyItemsLegacyCenter { justify-items: legacy center; }
+.justifyItemsLegacyRight { justify-items: legacy right; }
+.justifyItemsLeftLegacy { justify-items: left legacy; }
+.justifyItemsCenterLegacy { justify-items: center legacy; }
+.justifyItemsRightLegacy { justify-items: right legacy; }
+
+.justifyItemsSafeCenter { justify-items: safe center; }
+.justifyItemsUnsafeCenter { justify-items: unsafe center; }
+.justifyItemsSafeEnd { justify-items: safe end; }
+.justifyItemsUnsafeEnd { justify-items: unsafe end; }
+.justifyItemsSafeSelfEnd { justify-items: safe self-end; }
+.justifyItemsUnsafeSelfEnd { justify-items: unsafe self-end; }
+.justifyItemsSafeSelfStart { justify-items: safe self-start; }
+.justifyItemsUnsafeSelfStart { justify-items: unsafe self-start; }
+.justifyItemsSafeRight { justify-items: safe right; }
+.justifyItemsUnsafeRight { justify-items: unsafe right; }
+.justifyItemsSafeLeft { justify-items: safe left; }
+.justifyItemsUnsafeLeft { justify-items: unsafe left; }
+.justifyItemsSafeFlexEnd { justify-items: safe flex-end; }
+.justifyItemsUnsafeFlexEnd { justify-items: unsafe flex-end; }
+.justifyItemsSafeFlexStart { justify-items: safe flex-start; }
+.justifyItemsUnsafeFlexStart { justify-items: unsafe flex-start; }
+
+.justifyItemsTest { justify-items: safe end; }
+
+.justifyItemsBaseline { justify-items: baseline; }
+.justifyItemsFirstBaseline { justify-items: first baseline; }
+.justifyItemsLastBaseline { justify-items: last baseline; }
+
+/* justify-content */
+.justifyContentBaseline { justify-content: baseline; }
+.justifyContentLastBaseline { justify-content: last-baseline; }
+.justifyContentStart { justify-content: start; }
+.justifyContentEnd { justify-content: end; }
+.justifyContentCenter { justify-content: center; }
+.justifyContentLeft { justify-content: left; }
+.justifyContentRight { justify-content: right; }
+
+.justifyContentFlexStart { justify-content: flex-start; }
+.justifyContentFlexEnd { justify-content: flex-end; }
+
+.justifyContentSpaceBetween { justify-content: space-between; }
+.justifyContentSpaceAround { justify-content: space-around; }
+.justifyContentSpaceEvenly { justify-content: space-evenly; }
+.justifyContentStretch { justify-content: stretch; }
+
+.justifyContentSafeCenter { justify-content: safe center; }
+.justifyContentUnsafeCenter { justify-content: unsafe center; }
+.justifyContentSafeEnd { justify-content: safe end; }
+.justifyContentUnsafeEnd { justify-content: unsafe end; }
+.justifyContentSafeRight { justify-content: safe right; }
+.justifyContentUnsafeRight { justify-content: unsafe right; }
+.justifyContentSafeLeft { justify-content: safe left; }
+.justifyContentUnsafeLeft { justify-content: unsafe left; }
+.justifyContentSafeFlexEnd { justify-content: safe flex-end; }
+.justifyContentUnsafeFlexEnd { justify-content: unsafe flex-end; }
+.justifyContentSafeFlexStart { justify-content: safe flex-start; }
+.justifyContentUnsafeFlexStart { justify-content: unsafe flex-start; }
+
+.justifyContentBaseline { justify-content: baseline; }
+.justifyContentFirstBaseline { justify-content: first baseline; }
+.justifyContentLastBaseline { justify-content: last baseline; }
+
+/* Both align-items and justify-items */
+.itemsNormal {
+    align-items: normal;
+    justify-items: normal;
+}
+
+.itemsStretch {
+    align-items: stretch;
+    justify-items: stretch;
+}
+
+.itemsStart {
+    align-items: start;
+    justify-items: start;
+}
+
+.itemsCenter {
+    align-items: center;
+    justify-items: center;
+}
+
+.itemsEnd {
+    align-items: end;
+    justify-items: end;
+}
+
+.itemsLeft {
+    align-items: left;
+    justify-items: left;
+}
+
+.itemsRight {
+    align-items: right;
+    justify-items: right;
+}
+
+.itemsSelfStart {
+    align-items: self-start;
+    justify-items: self-start;
+}
+
+.itemsSelfEnd {
+    align-items: self-end;
+    justify-items: self-end;
+}
+.itemsBaseline {
+    align-items: baseline;
+    justify-items: baseline;
+}
+
+/* Both align-self and justify-self */
+.selfStretch {
+    align-self: stretch;
+    justify-self: stretch;
+}
+.selfStart {
+    align-self: start;
+    justify-self: start;
+}
+.selfEnd {
+    align-self: end;
+    justify-self: end;
+}
+.selfCenter {
+    align-self: center;
+    justify-self: center;
+}
+.selfRight {
+    align-self: right;
+    justify-self: right;
+}
+.selfLeft {
+    align-self: left;
+    justify-self: left;
+}
+.selfSelfStart {
+    align-self: self-start;
+    justify-self: self-start;
+}
+.selfSelfEnd {
+    align-self: self-end;
+    justify-self: self-end;
+}
+.selfBaseline {
+    align-self: baseline;
+    justify-self: baseline;
+}
+
+/* Both align-content and justify-content */
+.contentStart {
+    align-content: start;
+    justify-content: start;
+}
+.contentCenter {
+    align-content: center;
+    justify-content: center;
+}
+.contentEnd {
+    align-content: end;
+    justify-content: end;
+}
+
+.contentCenterSafe {
+    align-content: safe center;
+    justify-content: safe center;
+}
+
+.contentCenterUnsafe {
+    align-content: unsafe center;
+    justify-content: unsafe center;
+}
+
+.contentEndSafe {
+    align-content: safe end;
+    justify-content: safe end;
+}
+
+.contentEndUnsafe {
+    align-content: unsafe end;
+    justify-content: unsafe end;
+}
+
+.contentSpaceBetween {
+    justify-content: space-between;
+    align-content: space-between;
+}
+
+.contentSpaceAround {
+    justify-content: space-around;
+    align-content: space-around;
+}
+
+.contentSpaceEvenly {
+    justify-content: space-evenly;
+    align-content: space-evenly;
+}
+
+.contentStretch {
+    justify-content: stretch;
+    align-content: stretch;
+}
diff --git a/css/support/blue32x32.ico b/css/support/blue32x32.ico
new file mode 100644
index 0000000..5844dd6
--- /dev/null
+++ b/css/support/blue32x32.ico
Binary files differ
diff --git a/css/tools/w3ctestlib b/css/tools/w3ctestlib
index 03dbe84..02ee141 160000
--- a/css/tools/w3ctestlib
+++ b/css/tools/w3ctestlib
@@ -1 +1 @@
-Subproject commit 03dbe8407789c6d70d9067eb287aadc15130065e
+Subproject commit 02ee141b222acf27c1d10ebafc88f0961ad34e34
diff --git a/css/vendor-imports/mozilla/OWNERS b/css/vendor-imports/mozilla/OWNERS
index 62886bc..cf1f9b5 100644
--- a/css/vendor-imports/mozilla/OWNERS
+++ b/css/vendor-imports/mozilla/OWNERS
@@ -1 +1,2 @@
 @dbaron
+@Ms2ger
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001-ref.html
index f96f07c..98b4bd9 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001-ref.html
@@ -67,8 +67,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -95,8 +93,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001.html
index 3e18191..62a8ecb 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-001.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002-ref.html
index 93a027b..b9bde37 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002-ref.html
@@ -67,8 +67,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -95,8 +93,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002.html
index ad34374..d546bf5 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-002.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003-ref.html
index 383d181..3d4867f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003-ref.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003.html
index 4ed94d0..3960f47 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-003.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004-ref.html
index 3702330..82399e3 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004-ref.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004.html
index d859af2..a1b0054 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-004.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005-ref.html
index d2d5a99..8d853a8 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005-ref.html
@@ -67,8 +67,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -95,8 +93,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005.html
index ec491ff..65b744f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-005.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006-ref.html
index 880f258..f4880c4 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006-ref.html
@@ -67,8 +67,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -95,8 +93,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006.html
index 1ce890a..209f041 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-006.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007-ref.html
index aaac464..55a390d 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007-ref.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007.html
index 9964af6..eb2365a 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-007.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008-ref.html
index 14166e1..f8b40d2 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008-ref.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008.html
index 2a8fa4b..6dafc46 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-008.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001-ref.html
index 383d181..3d4867f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001-ref.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001.html
index f45b537..e0d3708 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-001.html
@@ -71,8 +71,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -99,8 +97,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002-ref.html
index c1f117e..989d6ac 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002-ref.html
@@ -67,8 +67,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignEnd"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -95,8 +93,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignEnd"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002.html
index 755db6c..9f8ae7f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-rtl-002.html
@@ -71,8 +71,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -99,8 +97,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001-ref.html
index f952c8d..989d6ac 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001-ref.html
@@ -67,8 +67,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -95,8 +93,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001.html
index 5fa7f77..456cc59 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-001.html
@@ -71,8 +71,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -99,8 +97,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002-ref.html
index 4929245..3d4867f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002-ref.html
@@ -70,8 +70,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -98,8 +96,6 @@
     <!-- <content-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002.html
index 2544032..f1013db 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-content-vertWM-002.html
@@ -71,8 +71,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
   <div class="small">
@@ -99,8 +97,6 @@
     <!-- <content-position>, part 2 -->
     <div class="container" style="align-content: flex-start"><div></div></div>
     <div class="container" style="align-content: flex-end"><div></div></div>
-    <div class="container" style="align-content: left"><div></div></div>
-    <div class="container" style="align-content: right"><div></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001-ref.html
index e85089f..6c0f732 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001-ref.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001.html
index ea05c96..6aad518 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-001.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002-ref.html
index c6398ca..c1715dc 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002-ref.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002.html
index a45c61d..06956ba 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-002.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003-ref.html
index 6d08814..d1b2c59 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003.html
index 5af8077..247f521 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-003.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004-ref.html
index 7065861..24fb8a1 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004.html
index 6f00b15..e684dd6 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-004.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005-ref.html
index 08501e8..9d8f446 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005-ref.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005.html
index c5d1f5b..1421878 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-005.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006-ref.html
index 8ad1291..b3df68f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006-ref.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006.html
index ee19796..aa09343 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-006.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007-ref.html
index 25609fc..a5316ce 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007.html
index b7b1d8a..7721031 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-007.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008-ref.html
index 3feea0b..c40a2d7 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignEnd"><!--flex-start--></div></div>
     <div class="container"><div class="alignStart"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008.html
index b1fdc92..6bea8aa 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-008.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001-ref.html
index 4e395aa..75a998e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001.html
index 6c8bc5c..82120fb 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-001.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002-ref.html
index 4e395aa..75a998e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002.html
index 99e171a..1b2ff2e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-002.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003-ref.html
index b773df5..b48de7e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003-ref.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignEnd"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignEnd"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003.html
index 3bac61b..498e230 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-003.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004-ref.html
index 3fb5842..2ed4a2d 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004-ref.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignEnd"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignEnd"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004.html
index 5588bee..c4cb4b5 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-rtl-004.html
@@ -65,8 +65,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -91,8 +89,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003-ref.html
index 9cd5fb6..75a998e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003.html
index a54c7d5..c340cdc 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-003.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004-ref.html
index 7787fba..a4be517 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004-ref.html
@@ -68,8 +68,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -94,8 +92,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignEnd"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004.html
index c0ccc9b..5113cdd 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-align-self-vertWM-004.html
@@ -66,8 +66,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -92,8 +90,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001-ref.html
index 24935ee..ec0d6cc 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001-ref.html
@@ -51,10 +51,16 @@
     <div class="container"><div class="alignStart"><!--stretch--></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
-    <div class="container"><div class="alignEnd"><!--space-between--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-around--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
-    <div class="container"><div class="alignEnd"><!--stretch--></div></div>
+    <div style="display: none">
+      <!--
+        These tests will be valid when
+        https://github.com/w3c/csswg-drafts/issues/1002 is fixed
+      -->
+      <div class="container"><div class="alignEnd"><!--space-between--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-around--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
+      <div class="container"><div class="alignEnd"><!--stretch--></div></div>
+    </div>
     <br>
   </div>
   <div class="small">
@@ -65,10 +71,16 @@
     <div class="container"><div class="alignStart"><!--stretch--></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
-    <div class="container"><div class="alignEnd"><!--space-between--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-around--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
-    <div class="container"><div class="alignEnd"><!--stretch--></div></div>
+    <div style="display: none">
+      <!--
+        These tests will be valid when
+        https://github.com/w3c/csswg-drafts/issues/1002 is fixed
+      -->
+      <div class="container"><div class="alignEnd"><!--space-between--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-around--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
+      <div class="container"><div class="alignEnd"><!--stretch--></div></div>
+    </div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001.html
index e1de7af..e1c6b79 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-align-content-001.html
@@ -49,10 +49,12 @@
     <div class="container" style="align-content: stretch"><div></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
+    <!--
     <div class="container" style="align-content: space-between flex-end"><div></div></div>
     <div class="container" style="align-content: space-around flex-end"><div></div></div>
     <div class="container" style="align-content: space-evenly flex-end"><div></div></div>
     <div class="container" style="align-content: stretch flex-end"><div></div></div>
+    -->
     <br>
   </div>
   <div class="small">
@@ -63,10 +65,12 @@
     <div class="container" style="align-content: stretch"><div></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
+    <!--
     <div class="container" style="align-content: space-between flex-end"><div></div></div>
     <div class="container" style="align-content: space-around flex-end"><div></div></div>
     <div class="container" style="align-content: space-evenly flex-end"><div></div></div>
     <div class="container" style="align-content: stretch flex-end"><div></div></div>
+    -->
   </div>
 </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001-ref.html
index 21185bc..0abf592 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001-ref.html
@@ -51,10 +51,12 @@
     <div class="container"><div class="alignStart"><!--stretch--></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
-    <div class="container"><div class="alignEnd"><!--space-between--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-around--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
-    <div class="container"><div class="alignStart"><!--stretch--></div></div>
+    <div style="display: none">
+      <div class="container"><div class="alignEnd"><!--space-between--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-around--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
+      <div class="container"><div class="alignStart"><!--stretch--></div></div>
+    </div>
     <br>
   </div>
   <div class="small">
@@ -65,10 +67,12 @@
     <div class="container"><div class="alignStart"><!--stretch--></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
-    <div class="container"><div class="alignEnd"><!--space-between--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-around--></div></div>
-    <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
-    <div class="container"><div class="alignStart"><!--stretch--></div></div>
+    <div style="display: none">
+      <div class="container"><div class="alignEnd"><!--space-between--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-around--></div></div>
+      <div class="container"><div class="alignEnd"><!--space-evenly--></div></div>
+      <div class="container"><div class="alignStart"><!--stretch--></div></div>
+    </div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001.html
index d90dc75..88335dc 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-fallback-justify-content-001.html
@@ -49,10 +49,12 @@
     <div class="container" style="justify-content: stretch"><div></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
+    <!-- TODO: https://github.com/w3c/csswg-drafts/issues/1002
     <div class="container" style="justify-content: space-between flex-end"><div></div></div>
     <div class="container" style="justify-content: space-around flex-end"><div></div></div>
     <div class="container" style="justify-content: space-evenly flex-end"><div></div></div>
     <div class="container" style="justify-content: stretch flex-end"><div></div></div>
+    -->
     <!-- Note: specifically for flex containers, in the justify-content axis,
          "stretch behaves as flex-start (ignoring the specified fallback
          alignment, if any." https://drafts.csswg.org/css-align-3/#distribution-flex -->
@@ -66,10 +68,12 @@
     <div class="container" style="justify-content: stretch"><div></div></div>
     <br>
     <!-- <content-distribution> with explicit "flex-end" fallback -->
+    <!-- TODO: https://github.com/w3c/csswg-drafts/issues/1002
     <div class="container" style="justify-content: space-between flex-end"><div></div></div>
     <div class="container" style="justify-content: space-around flex-end"><div></div></div>
     <div class="container" style="justify-content: space-evenly flex-end"><div></div></div>
     <div class="container" style="justify-content: stretch flex-end"><div></div></div>
+    -->
     <!-- As noted above, the explicit fallback for "stretch" is ignored here. -->
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001-ref.html
index fc45098..be61c53 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001.html
index 1d57136..33cec45 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-001.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002-ref.html
index fc45098..be61c53 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002.html
index 09073d4..68025bf 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-002.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003-ref.html
index d496269..f609ed2 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003.html
index f2801eb..80c7477 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-003.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004-ref.html
index d496269..f609ed2 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004.html
index 0f8d330..6c0f8e4 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-004.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005-ref.html
index 807f161..b04e8f2 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005.html
index 729ca97..83703c8 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-005.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006-ref.html
index 807f161..b04e8f2 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006.html
index 83da7de..f30bd06 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-006.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007-ref.html
index 04979df..2da4bf9 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007.html
index ad97ac3..4eef2a7 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-007.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008-ref.html
index 04979df..2da4bf9 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignEnd"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008.html
index d6a4074..375e9aa 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-008.html
@@ -47,10 +47,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -75,10 +71,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001-ref.html
index 2ccb6b1..2c70fa1 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001.html
index 94df40c..5143a56 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-001.html
@@ -48,10 +48,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -76,10 +72,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002-ref.html
index 4cf9471..8901d35 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002-ref.html
@@ -52,10 +52,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -80,10 +76,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002.html
index f85c7d0..d56a25c 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-rtl-002.html
@@ -48,10 +48,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -76,10 +72,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001-ref.html
index dc9b6da..64a8544 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001-ref.html
@@ -52,10 +52,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -80,10 +76,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001.html
index db4b5b6..8971b4b 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-001.html
@@ -48,10 +48,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -76,10 +72,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002-ref.html
index d4fd90e..3e109d0 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002-ref.html
@@ -49,10 +49,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
@@ -77,10 +73,6 @@
     <!-- normal -->
     <div class="container"><div class="alignStart"><!--normal--></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container"><div class="alignStart"><!--baseline--></div></div>
-    <div class="container"><div class="alignEnd"><!--last baseline--></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container"><div class="alignStart"><!--space-between--></div></div>
     <div class="container"><div class="alignCenter"><!--space-around--></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002.html
index 1eb456d..59ea153 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-justify-content-vertWM-002.html
@@ -48,10 +48,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
@@ -76,10 +72,6 @@
     <!-- normal -->
     <div class="container" style="justify-content: normal"><div></div></div>
     <br>
-    <!-- <baseline-position> -->
-    <div class="container" style="justify-content: baseline"><div></div></div>
-    <div class="container" style="justify-content: last baseline"><div></div></div>
-    <br>
     <!-- <content-distribution> -->
     <div class="container" style="justify-content: space-between"><div></div></div>
     <div class="container" style="justify-content: space-around"><div></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001-ref.html
index e519f2d..36b570f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001-ref.html
@@ -66,8 +66,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -92,8 +90,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001.html
index 02b7a13..191d6b8 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-001.html
@@ -67,8 +67,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -93,8 +91,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002-ref.html
index eaa1c71..376e008 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002-ref.html
@@ -64,8 +64,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
   <div class="small">
@@ -90,8 +88,6 @@
     <!-- <self-position>, part 2: -->
     <div class="container"><div class="alignStart"><!--flex-start--></div></div>
     <div class="container"><div class="alignEnd"><!--flex-end--></div></div>
-    <div class="container"><div class="alignStart"><!--left--></div></div>
-    <div class="container"><div class="alignStart"><!--right--></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002.html
index 32f9093..b9ee727 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/align3/grid-abspos-staticpos-align-self-002.html
@@ -66,8 +66,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
   <div class="small">
@@ -92,8 +90,6 @@
     <!-- <self-position>, part 2 -->
     <div class="container"><div style="align-self: flex-start"></div></div>
     <div class="container"><div style="align-self: flex-end"></div></div>
-    <div class="container"><div style="align-self: left"></div></div>
-    <div class="container"><div style="align-self: right"></div></div>
     <br>
   </div>
 </body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1-ref.html
index 43fa1ca..ecd58d7 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1-ref.html
@@ -10,7 +10,7 @@
         width: 72px;
         height: 72px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-size: 36px 36px;
         background-repeat: repeat;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1a.html
index 7ba2329..6717ce1 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1a.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1a.html
@@ -13,7 +13,7 @@
         width: 72px;
         height: 72px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: round;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1b.html
index 126909c..b3e1e96 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1b.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1b.html
@@ -13,7 +13,7 @@
         width: 72px;
         height: 72px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: round round;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1c.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1c.html
index e956e16..3fcfd80 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1c.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1c.html
@@ -14,7 +14,7 @@
         height: 72px;
         border: 1px solid black;
         background-size: 36px 36px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: repeat round;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1d.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1d.html
index 424c959..69045cf 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1d.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1d.html
@@ -14,7 +14,7 @@
         height: 72px;
         border: 1px solid black;
         background-size: 32px 36px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: round repeat;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1e.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1e.html
index 64640ff..5fd4973 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1e.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1e.html
@@ -14,7 +14,7 @@
         height: 72px;
         border: 1px solid black;
         background-size: 36px 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: repeat round;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2-ref.html
index 6d62330..b530e61 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2-ref.html
@@ -10,7 +10,7 @@
       width: 72px;
       height: 72px;
       border: 1px solid black;
-      background-image: url(aqua-yellow-32x32.png);
+      background-image: url(support/aqua-yellow-32x32.png);
       background-size: 36px 36px;
       background-repeat: repeat-x;
     }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2.html
index cdb8583..702931a 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2.html
@@ -13,7 +13,7 @@
       width: 72px;
       height: 72px;
       border: 1px solid black;
-      background-image: url(aqua-yellow-32x32.png);
+      background-image: url(support/aqua-yellow-32x32.png);
       background-repeat: round no-repeat;
     }
     .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3-ref.html
index 49eaccb..e44cecd 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3-ref.html
@@ -10,7 +10,7 @@
         width: 72px;
         height: 72px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-size: 36px 36px;
         background-repeat: repeat-y;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3.html
index 410d7ed..c3b5e2b 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3.html
@@ -13,7 +13,7 @@
         width: 72px;
         height: 72px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-size: 36px auto;
         background-repeat: no-repeat round;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4-ref.html
index 8bb7d70..8139ace 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4-ref.html
@@ -10,7 +10,7 @@
         width: 72px;
         height: 72px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-size: 36px 36px;
         background-repeat: repeat;
         background-position: 5px 5px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4.html
index f02ad55..be1a4c8 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4.html
@@ -13,7 +13,7 @@
         width: 72px;
         height: 72px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: round;
         background-position: 5px 5px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1-ref.html
index f9efeeb..11ea2c7 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1-ref.html
@@ -20,7 +20,7 @@
       {
         height: 32px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
       }
       .outer_gradient
       {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10-ref.html
index 35ed461..f4c2b7c 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10-ref.html
@@ -20,7 +20,7 @@
       {
         height: 32px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
       }
     </style>
   </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10.html
index db8c910..00205a9 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10.html
@@ -13,7 +13,7 @@
         width: 96px;
         height: 106px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space;
       }
     </style>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1a.html
index 3729e5c..e4376c6 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1a.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1a.html
@@ -13,7 +13,7 @@
         width: 106px;
         height: 106px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1b.html
index 50efcc2..55bc6dc 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1b.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1b.html
@@ -13,7 +13,7 @@
         width: 106px;
         height: 106px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-position: 15px 15px;
         background-repeat: space;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1c.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1c.html
index 263bb0c..27470f9 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1c.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1c.html
@@ -13,7 +13,7 @@
         width: 106px;
         height: 106px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space space;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2-ref.html
index c2fc93a..5dbd30d 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2-ref.html
@@ -10,7 +10,7 @@
         width: 48px;
         height: 48px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: no-repeat;
         background-position: 5px 5px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2.html
index 6099aa0..ac96644 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2.html
@@ -13,7 +13,7 @@
         width: 48px;
         height: 48px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space;
         background-position: 5px 5px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3-ref.html
index add80bc..f132f2e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3-ref.html
@@ -18,7 +18,7 @@
       {
         height: 32px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         margin-top: 40px;
       }
       .outer2
@@ -34,7 +34,7 @@
       {
         height: 32px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         margin-left: 40px;
       }
       .inner_gradient1
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3.html
index 39d5701..9ea709b 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3.html
@@ -17,14 +17,14 @@
       .inner1 {
         width: 106px;
         height: 106px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space no-repeat;
         background-position: 7px 40px;
       }
       .inner2 {
         width: 106px;
         height: 106px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: no-repeat space;
         background-position: 40px 7px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4-ref.html
index e7814de..a375db5 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4-ref.html
@@ -19,7 +19,7 @@
       {
         height: 32px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
       }
       .inner_gradient
       {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4.html
index c8be103..834381c 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4.html
@@ -13,7 +13,7 @@
         width: 96px;
         height: 106px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: repeat space;
       }
       .outer_graident {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5-ref.html
index 9ff6c6e..d9d3ca1 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5-ref.html
@@ -19,7 +19,7 @@
       {
         height: 32px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
       }
       .inner_gradient
       {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5.html
index 3c1b3d2..0beca23 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5.html
@@ -13,7 +13,7 @@
         width: 106px;
         height: 96px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space repeat;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6-ref.html
index 7e2f19c..c074401 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6-ref.html
@@ -19,7 +19,7 @@
       {
         height: 32px;
         width: 64px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: no-repeat;
         background-size: 64px 32px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6.html
index 5075352..ae2c0fa 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6.html
@@ -14,7 +14,7 @@
         height: 106px;
         border: 1px solid black;
         background-size: 60px 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: round space;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7-ref.html
index 5285990..541fbd4 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7-ref.html
@@ -19,7 +19,7 @@
       {
         height: 64px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: no-repeat;
         background-size: 32px 64px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7.html
index 49bee1f..809875f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7.html
@@ -14,7 +14,7 @@
         height: 192px;
         border: 1px solid black;
         background-size: 32px 60px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space round;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8-ref.html
index 1f7f39e..5846409 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8-ref.html
@@ -11,7 +11,7 @@
         border: 20px solid rgba(0, 0, 0, 0.5);
         width: 106px;
         height: 106px;
-        background-image: url(aqua-yellow-37x37.png);
+        background-image: url(support/aqua-yellow-37x37.png);
         background-repeat: repeat;
       }
       .outer_gradient
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8.html
index 0ad57af..473d115 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8.html
@@ -13,7 +13,7 @@
         width: 106px;
         height: 106px;
         border: 20px solid rgba(0, 0, 0, .5);
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space;
       }
       .outer_gradient {
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9-ref.html
index 1476eca..6aa9c73 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9-ref.html
@@ -20,7 +20,7 @@
       {
         height: 32px;
         width: 32px;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
       }
     </style>
   </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9.html
index 66f6c22..28ff464 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9.html
@@ -13,7 +13,7 @@
         width: 106px;
         height: 96px;
         border: 1px solid black;
-        background-image: url(aqua-yellow-32x32.png);
+        background-image: url(support/aqua-yellow-32x32.png);
         background-repeat: space;
       }
     </style>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1-ref.html
index 69d38c3..298b46d 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1-ref.html
@@ -16,7 +16,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner2 {
         position: absolute;
@@ -25,7 +25,7 @@
         width: 34px;
         height: 27px;
         background-size: 102px 81px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -34px 0px;
       }
       .inner3 {
@@ -34,7 +34,7 @@
         left: 61px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner4 {
         position: absolute;
@@ -43,7 +43,7 @@
         width: 27px;
         height: 34px;
         background-size: 81px 102px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -34px;
       }
       .inner5 {
@@ -53,7 +53,7 @@
         width: 27px;
         height: 34px;
         background-size: 81px 102px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -34px;
       }
       .inner6 {
@@ -62,7 +62,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner7 {
         position: absolute;
@@ -71,7 +71,7 @@
         width: 34px;
         height: 27px;
         background-size: 102px 81px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -34px -54px;
       }
       .inner8 {
@@ -80,7 +80,7 @@
         left: 61px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1.html
index a935e44..832126e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1.html
@@ -14,7 +14,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: round round;
         width: 34px;
         height: 34px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2-ref.html
index e96185c..e76f7ce 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2-ref.html
@@ -16,7 +16,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner2 {
         position: absolute;
@@ -25,7 +25,7 @@
         width: 13px;
         height: 27px;
         background-size: 39px 81px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -13px 0px;
       }
       .inner3 {
@@ -34,7 +34,7 @@
         left: 40px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner4 {
         position: absolute;
@@ -43,7 +43,7 @@
         width: 27px;
         height: 13px;
         background-size: 81px 39px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -13px;
       }
       .inner5 {
@@ -53,7 +53,7 @@
         width: 27px;
         height: 13px;
         background-size: 81px 39px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -13px;
       }
       .inner6 {
@@ -62,7 +62,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner7 {
         position: absolute;
@@ -71,7 +71,7 @@
         width: 13px;
         height: 27px;
         background-size: 39px 81px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -13px -54px;
       }
       .inner8 {
@@ -80,7 +80,7 @@
         left: 40px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2.html
index 3c1cd33..4e08ae0 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2.html
@@ -14,7 +14,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: round round;
         width: 13px;
         height: 13px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1-ref.html
index e2cc710..7c6555a 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1-ref.html
@@ -16,7 +16,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner2 {
         position: absolute;
@@ -24,7 +24,7 @@
         left: 31px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px 0px;
       }
       .inner3 {
@@ -33,7 +33,7 @@
         left: 62px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner4 {
         position: absolute;
@@ -41,7 +41,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -27px;
       }
       .inner5 {
@@ -50,7 +50,7 @@
         left: 62px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -27px;
       }
       .inner6 {
@@ -59,7 +59,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner7 {
         position: absolute;
@@ -67,7 +67,7 @@
         left: 31px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px -54px;
       }
       .inner8 {
@@ -76,7 +76,7 @@
         left: 62px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1.html
index 113d000..c186a31 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1.html
@@ -14,7 +14,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: space space;
         width: 35px;
         height: 35px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2-ref.html
index 17b83e1..6afc073 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2-ref.html
@@ -16,7 +16,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner2 {
         position: absolute;
@@ -24,7 +24,7 @@
         left: 40px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner3 {
         position: absolute;
@@ -32,7 +32,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner4 {
         position: absolute;
@@ -40,7 +40,7 @@
         left: 40px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2.html
index 99dc605..b38bc84 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2.html
@@ -14,7 +14,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: space space;
         width: 13px;
         height: 13px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3-ref.html
index 03c2301..904b325c 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3-ref.html
@@ -16,7 +16,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner2_1 {
         position: absolute;
@@ -24,7 +24,7 @@
         left: 29px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px 0px;
       }
       .inner2_2 {
@@ -33,7 +33,7 @@
         left: 58px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px 0px;
       }
       .inner3 {
@@ -42,7 +42,7 @@
         left: 87px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner4_1 {
         position: absolute;
@@ -50,7 +50,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -27px;
       }
       .inner4_2 {
@@ -59,7 +59,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -27px;
       }
       .inner5_1 {
@@ -68,7 +68,7 @@
         left: 87px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -27px;
       }
       .inner5_2 {
@@ -77,7 +77,7 @@
         left: 87px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -27px;
       }
       .inner6 {
@@ -86,7 +86,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner7_1 {
         position: absolute;
@@ -94,7 +94,7 @@
         left: 29px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px -54px;
       }
       .inner7_2 {
@@ -103,7 +103,7 @@
         left: 58px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px -54px;
       }
       .inner8 {
@@ -112,7 +112,7 @@
         left: 87px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3.html
index c43f1d3..085498f 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3.html
@@ -14,7 +14,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: space space;
         width: 60px;
         height: 60px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-1.html
index 2cf4097..ff7700e 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-1.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-1.html
@@ -17,7 +17,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner2 {
         position: absolute;
@@ -25,7 +25,7 @@
         left: 27px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px 0px;
       }
       .inner3 {
@@ -34,7 +34,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner4 {
         position: absolute;
@@ -42,7 +42,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -27px;
       }
       .inner5 {
@@ -51,7 +51,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -27px;
       }
       .inner6 {
@@ -60,7 +60,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner7 {
         position: absolute;
@@ -68,7 +68,7 @@
         left: 27px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px -54px;
       }
       .inner8 {
@@ -77,7 +77,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-2.html
index eee8c77..dda3de6 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-2.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-2.html
@@ -11,7 +11,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: stretch stretch;
         width: 27px;
         height: 27px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4.html
index b91e3ab..d82969d 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4.html
@@ -14,7 +14,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: space space;
         width: 27px;
         height: 27px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-1.html
index ac2fcea..d52cab5 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-1.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-1.html
@@ -17,7 +17,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner2_1 {
         position: absolute;
@@ -25,7 +25,7 @@
         left: 27px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px 0px;
       }
       .inner2_2 {
@@ -34,7 +34,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px 0px;
       }
       .inner2_3 {
@@ -43,7 +43,7 @@
         left: 81px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px 0px;
       }
       .inner3 {
@@ -52,7 +52,7 @@
         left: 108px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner4_1 {
         position: absolute;
@@ -60,7 +60,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -27px;
       }
       .inner4_2 {
@@ -69,7 +69,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -27px;
       }
       .inner4_3 {
@@ -78,7 +78,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: 0px -27px;
       }
       .inner5_1 {
@@ -87,7 +87,7 @@
         left: 108px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -27px;
       }
       .inner5_2 {
@@ -96,7 +96,7 @@
         left: 108px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -27px;
       }
       .inner5_3 {
@@ -105,7 +105,7 @@
         left: 108px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -54px -27px;
       }
       .inner6 {
@@ -114,7 +114,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
       .inner7_1 {
         position: absolute;
@@ -122,7 +122,7 @@
         left: 27px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px -54px;
       }
       .inner7_2 {
@@ -131,7 +131,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px -54px;
       }
       .inner7_3 {
@@ -140,7 +140,7 @@
         left: 81px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
         background-position: -27px -54px;
       }
       .inner8 {
@@ -149,7 +149,7 @@
         left: 108px;
         width: 27px;
         height: 27px;
-        background-image: url("border.png");
+        background-image: url("support/border.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-2.html
index 1d24b44..bda8d72 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-2.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-2.html
@@ -11,7 +11,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: repeat repeat;
         width: 81px;
         height: 81px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5.html
index 582c658..db21548 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5.html
@@ -14,7 +14,7 @@
         left: 0px;
         top: 0px;
         border: 27px solid transparent;
-        border-image: url("border.png") 27;
+        border-image: url("support/border.png") 27;
         border-image-repeat: space space;
         width: 81px;
         height: 81px;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6-ref.html
index 16647b3..307eda0 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6-ref.html
@@ -16,7 +16,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-tl.png");
+        background-image: url("support/reticule-tl.png");
       }
       .inner2 {
         position: absolute;
@@ -24,7 +24,7 @@
         left: 27px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-to.png");
+        background-image: url("support/reticule-to.png");
       }
       .inner3 {
         position: absolute;
@@ -32,7 +32,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-tr.png");
+        background-image: url("support/reticule-tr.png");
       }
       .inner4 {
         position: absolute;
@@ -40,7 +40,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-le.png");
+        background-image: url("support/reticule-le.png");
       }
       .inner5 {
         position: absolute;
@@ -48,7 +48,7 @@
         left: 27px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ct.png");
+        background-image: url("support/reticule-ct.png");
       }
       .inner6 {
         position: absolute;
@@ -56,7 +56,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ri.png");
+        background-image: url("support/reticule-ri.png");
       }
       .inner7 {
         position: absolute;
@@ -64,7 +64,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-bl.png");
+        background-image: url("support/reticule-bl.png");
       }
       .inner8 {
         position: absolute;
@@ -72,7 +72,7 @@
         left: 27px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-bo.png");
+        background-image: url("support/reticule-bo.png");
       }
       .inner9 {
         position: absolute;
@@ -80,7 +80,7 @@
         left: 54px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-br.png");
+        background-image: url("support/reticule-br.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6.html
index 3eb6431..045cde1 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6.html
@@ -15,7 +15,7 @@
         top: 0px;
         border-width: 27px;
         border-style: solid;
-        border-image: url("reticule.png") 27 fill space;
+        border-image: url("support/reticule.png") 27 fill space;
         width: 27px;
         height: 27px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7-ref.html
index e0cbdfe..44b07f7 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7-ref.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7-ref.html
@@ -16,7 +16,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-tl.png");
+        background-image: url("support/reticule-tl.png");
       }
       .inner2_1 {
         position: absolute;
@@ -24,7 +24,7 @@
         left: 35px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-to.png");
+        background-image: url("support/reticule-to.png");
       }
       .inner2_2 {
         position: absolute;
@@ -32,7 +32,7 @@
         left: 70px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-to.png");
+        background-image: url("support/reticule-to.png");
       }
       .inner3 {
         position: absolute;
@@ -40,7 +40,7 @@
         left: 105px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-tr.png");
+        background-image: url("support/reticule-tr.png");
       }
       .inner4_1 {
         position: absolute;
@@ -48,7 +48,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-le.png");
+        background-image: url("support/reticule-le.png");
       }
       .inner4_2 {
         position: absolute;
@@ -56,7 +56,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-le.png");
+        background-image: url("support/reticule-le.png");
       }
       .inner5_1 {
         position: absolute;
@@ -64,7 +64,7 @@
         left: 35px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ct.png");
+        background-image: url("support/reticule-ct.png");
       }
       .inner5_2 {
         position: absolute;
@@ -72,7 +72,7 @@
         left: 70px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ct.png");
+        background-image: url("support/reticule-ct.png");
       }
       .inner5_3 {
         position: absolute;
@@ -80,7 +80,7 @@
         left: 35px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ct.png");
+        background-image: url("support/reticule-ct.png");
       }
       .inner5_4 {
         position: absolute;
@@ -88,7 +88,7 @@
         left: 70px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ct.png");
+        background-image: url("support/reticule-ct.png");
       }
       .inner6_1 {
         position: absolute;
@@ -96,7 +96,7 @@
         left: 105px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ri.png");
+        background-image: url("support/reticule-ri.png");
       }
       .inner6_2 {
         position: absolute;
@@ -104,7 +104,7 @@
         left: 105px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-ri.png");
+        background-image: url("support/reticule-ri.png");
       }
       .inner7 {
         position: absolute;
@@ -112,7 +112,7 @@
         left: 0px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-bl.png");
+        background-image: url("support/reticule-bl.png");
       }
       .inner8_1 {
         position: absolute;
@@ -120,7 +120,7 @@
         left: 35px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-bo.png");
+        background-image: url("support/reticule-bo.png");
       }
       .inner8_2 {
         position: absolute;
@@ -128,7 +128,7 @@
         left: 70px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-bo.png");
+        background-image: url("support/reticule-bo.png");
       }
       .inner9 {
         position: absolute;
@@ -136,7 +136,7 @@
         left: 105px;
         width: 27px;
         height: 27px;
-        background-image: url("reticule-br.png");
+        background-image: url("support/reticule-br.png");
       }
     </style>
     </head>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7.html b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7.html
index 3e63287..3676578 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7.html
@@ -15,7 +15,7 @@
         top: 0px;
         border-width: 27px;
         border-style: solid;
-        border-image: url("reticule.png") 27 fill space;
+        border-image: url("support/reticule.png") 27 fill space;
         width: 78px;
         height: 78px;
       }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-32x32.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-32x32.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-32x32.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-32x32.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-37x37.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-37x37.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-37x37.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-37x37.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/border.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/border.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/border.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/border.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bl.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bl.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bl.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bl.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bo.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bo.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bo.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bo.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-br.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-br.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-br.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-br.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ct.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ct.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ct.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ct.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-le.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-le.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-le.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-le.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ri.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ri.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ri.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ri.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tl.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tl.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tl.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tl.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-to.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-to.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-to.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-to.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tr.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tr.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tr.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tr.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule.png b/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule.png
similarity index 100%
rename from css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule.png
rename to css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule.png
Binary files differ
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/dependent-builtin-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/dependent-builtin-ref.html
new file mode 100644
index 0000000..a558ce2
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/dependent-builtin-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>&#x4e00;&#x5343;&#x3001;</div>
+<div>&#x4e00;&#x5343;&#x96f6;&#x4e00;&#x3001;</div>
+<div>&#x4e00;&#x5343;&#x96f6;&#x4e8c;&#x3001;</div>
+<div>&#x4e00;&#x5343;&#x96f6;&#x4e09;&#x3001;</div>
+<div>&#x4e00;&#x5343;&#x96f6;&#x56db;&#x3001;</div>
+<div>&#x4e00;&#x3007;&#x3007;&#x4e94;&#x3001;</div>
+<div>&#x4e00;&#x3007;&#x3007;&#x516d;&#x3001;</div>
+<div>&#x4e00;&#x3007;&#x3007;&#x4e03;&#x3001;</div>
+<div>&#x4e00;&#x3007;&#x3007;&#x516b;&#x3001;</div>
+<div>&#x4e00;&#x3007;&#x3007;&#x4e5d;&#x3001;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/dependent-builtin.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/dependent-builtin.html
new file mode 100644
index 0000000..553dd72
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/dependent-builtin.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: dependent builtin</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#extends-system">
+<link rel="match" href="dependent-builtin-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends simp-chinese-informal;
+    range: 1000 1004;
+  }
+</style>
+<ol start="1000" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-invalid-ref.html
new file mode 100644
index 0000000..4b1c78b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-invalid-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>00.&nbsp;</div>
+<div>A.&nbsp;</div>
+<div>B.&nbsp;</div>
+<div>C.&nbsp;</div>
+<div>D.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-invalid.html
new file mode 100644
index 0000000..e19d9d6
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-invalid.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor fallback, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-fallback">
+<link rel="match" href="descriptor-fallback-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends upper-alpha;
+    fallback: decimal-leading-zero;
+    fallback: decimal cjk-decimal;
+    fallback: "*";
+  }
+</style>
+<ol start="0" style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-ref.html
new file mode 100644
index 0000000..1dc3f45
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>c.&nbsp;</div>
+<div>d.&nbsp;</div>
+<div>e.&nbsp;</div>
+<div>f.&nbsp;</div>
+<div>7.&nbsp;</div>
+<div>8.&nbsp;</div>
+<div>9.&nbsp;</div>
+<!-- list-style-type: b -->
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>c.&nbsp;</div>
+<div>d.&nbsp;</div>
+<div>e.&nbsp;</div>
+<div>f.&nbsp;</div>
+<div>7.&nbsp;</div>
+<div>8.&nbsp;</div>
+<div>9.&nbsp;</div>
+<!-- list-style-type: c -->
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>c.&nbsp;</div>
+<div>d.&nbsp;</div>
+<div>e.&nbsp;</div>
+<div>f.&nbsp;</div>
+<div>g.&nbsp;</div>
+<div>h.&nbsp;</div>
+<div>i.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback.html
new file mode 100644
index 0000000..7cbfb88
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor fallback</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-fallback">
+<link rel="match" href="descriptor-fallback-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: fixed;
+    symbols: a b c;
+    fallback: b;
+  }
+  @counter-style b {
+    system: fixed 4;
+    symbols: d e f;
+    fallback: a;
+  }
+  @counter-style c {
+    system: fixed 7;
+    symbols: g h i;
+    fallback: a;
+  }
+</style>
+<ol style="list-style-type: a">
+  <li><li><li>
+  <li><li><li>
+  <li><li><li>
+</ol>
+<ol style="list-style-type: b">
+  <li><li><li>
+  <li><li><li>
+  <li><li><li>
+</ol>
+<ol style="list-style-type: c">
+  <li><li><li>
+  <li><li><li>
+  <li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-invalid-ref.html
new file mode 100644
index 0000000..a14d7b4
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-invalid-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>!2.&nbsp;</div>
+<div>!1.&nbsp;</div>
+<div>0.&nbsp;</div>
+<div>1.&nbsp;</div>
+<div>2.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-invalid.html
new file mode 100644
index 0000000..f7376ea
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor negative, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-negative">
+<link rel="match" href="descriptor-negative-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends decimal;
+    negative: '!';
+    negative: 0;
+    negative: ~;
+    negative: '(' 'x' ')';
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-ref.html
new file mode 100644
index 0000000..9216a87
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>&#x207B;2.&nbsp;</div>
+<div>&#x207B;1.&nbsp;</div>
+<div>0.&nbsp;</div>
+<div>1.&nbsp;</div>
+<div>2.&nbsp;</div>
+<!-- list-style-type: b -->
+<div>(2).&nbsp;</div>
+<div>(1).&nbsp;</div>
+<div>0.&nbsp;</div>
+<div>1.&nbsp;</div>
+<div>2.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative.html
new file mode 100644
index 0000000..cf06bc4
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor negative</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-negative">
+<link rel="match" href="descriptor-negative-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends decimal;
+    negative: \207B;
+  }
+  @counter-style b {
+    system: extends decimal;
+    negative: '(' ')';
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-invalid-ref.html
new file mode 100644
index 0000000..1da956e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-invalid-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>001.&nbsp;</div>
+<div>002.&nbsp;</div>
+<div>003.&nbsp;</div>
+<div>004.&nbsp;</div>
+<div>005.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-invalid.html
new file mode 100644
index 0000000..4145eb7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor pad, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-pad">
+<link rel="match" href="descriptor-pad-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends decimal;
+    pad: 3 "0";
+    pad: -1 "X";
+    pad: "#";
+    pad: 2 0;
+  }
+</style>
+<ol style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-ref.html
new file mode 100644
index 0000000..8d47b10
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>-III.&nbsp;</div>
+<div>-*II.&nbsp;</div>
+<div>-**I.&nbsp;</div>
+<div>0.&nbsp;</div>
+<div>**I.&nbsp;</div>
+<div>*II.&nbsp;</div>
+<div>III.&nbsp;</div>
+<div>*IV.&nbsp;</div>
+<div>**V.&nbsp;</div>
+<div>6.&nbsp;</div>
+<!-- list-style-type: b -->
+<div>(002).&nbsp;</div>
+<div>(001).&nbsp;</div>
+<div>000.&nbsp;</div>
+<div>001.&nbsp;</div>
+<div>002.&nbsp;</div>
+<!-- list-style-type: c -->
+<div>ooa&#x0304;</div>
+<div>ooa&#x0301;</div>
+<div>oa&#x0304;a&#x0301;</div>
+<div>oa&#x030c;a&#x0300;</div>
+<div>a&#x0304;a&#x0301;a&#x030c;</div>
+<div>a&#x0300;a&#x0304;a&#x0301;</div>
+<!-- list-style-type: d -->
+<div>001.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad.html
new file mode 100644
index 0000000..220e36b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-pad.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor pad</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-pad">
+<link rel="match" href="descriptor-pad-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends upper-roman;
+    range: infinite 5;
+    pad: 3 '*';
+  }
+  @counter-style b {
+    system: extends decimal;
+    negative: '(' ')';
+    pad: 3 '0';
+  }
+  @counter-style c {
+    system: alphabetic;
+    symbols: a\0304  a\0301  a\030c  a\0300;
+    pad: 3 o;
+    suffix: '';
+  }
+  @counter-style d {
+    system: extends decimal;
+    pad: '0' 3;
+  }
+</style>
+<ol start="-3" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li><li><li><li><li>
+</ol>
+<ol style="list-style-type: c">
+  <li><li>
+  <li value="6"><li value="16">
+  <li value="27"><li value="70">
+</ol>
+<ol style="list-style-type: d">
+  <li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-invalid-ref.html
new file mode 100644
index 0000000..4d5b82e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-invalid-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>#-2.&nbsp;</div>
+<div>#-1.&nbsp;</div>
+<div>#0.&nbsp;</div>
+<div>#1.&nbsp;</div>
+<div>#2.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-invalid.html
new file mode 100644
index 0000000..33aa545
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor prefix, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-prefix">
+<link rel="match" href="descriptor-prefix-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends decimal;
+    prefix: "#";
+    prefix: *;
+    prefix: 0;
+    prefix: '$' '$';
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-ref.html
new file mode 100644
index 0000000..aa32f53
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- ol -->
+<div>Appendix -2.&nbsp;</div>
+<div>Appendix -1.&nbsp;</div>
+<div>Appendix 0.&nbsp;</div>
+<div>Appendix I.&nbsp;</div>
+<div>Appendix II.&nbsp;</div>
+<!-- section -->
+<p>-2</p>
+<p>-1</p>
+<p>0</p>
+<p>I</p>
+<p>II</p>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix.html
new file mode 100644
index 0000000..ecf18420
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-prefix.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor prefix</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-prefix">
+<link rel="match" href="descriptor-prefix-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends upper-roman;
+    prefix: "Appendix ";
+  }
+  section {
+    counter-reset: p -3;
+  }
+  p {
+    counter-increment: p;
+  }
+  p::before {
+    content: counter(p, a);
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
+<section>
+  <p><p><p><p><p>
+</section>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-invalid-ref.html
new file mode 100644
index 0000000..88922a2
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-invalid-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>3.&nbsp;</div>
+<div>4.&nbsp;</div>
+<div>5.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-invalid.html
new file mode 100644
index 0000000..2f55ef0
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor range, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-range">
+<link rel="match" href="descriptor-range-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends lower-alpha;
+    range: 1 2;
+    range: 1;
+    range: 3 1;
+    range: xx yy;
+  }
+</style>
+<ol style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-ref.html
new file mode 100644
index 0000000..8b35eea
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>-III.&nbsp;</div>
+<div>-2.&nbsp;</div>
+<div>-I.&nbsp;</div>
+<div>0.&nbsp;</div>
+<div>I.&nbsp;</div>
+<div>II.&nbsp;</div>
+<div>3.&nbsp;</div>
+<div>IV.&nbsp;</div>
+<div>5.&nbsp;</div>
+<div>6.&nbsp;</div>
+<div>VII.&nbsp;</div>
+<div>VIII.&nbsp;</div>
+<div>IX.&nbsp;</div>
+<div>10.&nbsp;</div>
+<div>XI.&nbsp;</div>
+<div>MMMCMXCIX.&nbsp;</div>
+<div>MMMM.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range.html
new file mode 100644
index 0000000..699c985
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-range.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor range</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-range">
+<link rel="match" href="descriptor-range-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends upper-roman;
+    range: infinite -3, -1 2, 4 4, 7 9, 11 infinite;
+  }
+</style>
+<ol start="-3" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+  <li value="3999"><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-invalid-ref.html
new file mode 100644
index 0000000..e350778
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-invalid-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>-2,</div>
+<div>-1,</div>
+<div>0,</div>
+<div>1,</div>
+<div>2,</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-invalid.html
new file mode 100644
index 0000000..f62181b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor suffix, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-suffix">
+<link rel="match" href="descriptor-suffix-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends decimal;
+    suffix: ',';
+    suffix: *;
+    suffix: 0;
+    suffix: '$' '$';
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-ref.html
new file mode 100644
index 0000000..ccbb7bd
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<style type="text/css">
+  div {
+    padding: 0; margin: 0;
+    line-height: 150%;
+  }
+</style>
+<!-- list-style-type: a -->
+<div>-2,-2</div>
+<div>-1,-1</div>
+<div>0,0</div>
+<div>1,1</div>
+<div>2,2</div>
+<!-- list-style-type: b -->
+<div>-2&#x3001;-2</div>
+<div>-1&#x3001;-1</div>
+<div>0&#x3001;0</div>
+<div>1&#x3001;1</div>
+<div>2&#x3001;2</div>
+<!-- section -->
+<div>-2</div>
+<div>-1</div>
+<div>0</div>
+<div>1</div>
+<div>2</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix.html
new file mode 100644
index 0000000..c558d6b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-suffix.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor suffix</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-suffix">
+<link rel="match" href="descriptor-suffix-ref.html">
+<style type="text/css">
+  @counter-style a {
+    system: extends decimal;
+    suffix: ",";
+  }
+  @counter-style b {
+    system: extends decimal;
+    suffix: \3001;
+  }
+  ol {
+    list-style-position: inside;
+  }
+  ol, section, p {
+    padding: 0; margin: 0;
+    line-height: 150%;
+  }
+  section {
+    counter-reset: p -3;
+  }
+  p {
+    counter-increment: p;
+  }
+  p::before {
+    content: counter(p, a);
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li>-2<li>-1<li>0<li>1<li>2
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li>-2<li>-1<li>0<li>1<li>2
+</ol>
+<section>
+  <p><p><p><p><p>
+</section>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-invalid-ref.html
new file mode 100644
index 0000000..50e1ee8
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-invalid-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>c.&nbsp;</div>
+<div>4.&nbsp;</div>
+<div>5.&nbsp;</div>
+<!-- list-style-type: b -->
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>c.&nbsp;</div>
+<div>ca.&nbsp;</div>
+<div>cb.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-invalid.html
new file mode 100644
index 0000000..a0d0e35
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-invalid.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor symbols, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-symbols">
+<link rel="match" href="descriptor-symbols-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: fixed;
+    symbols: a b c;
+    symbols: 0 1 2;
+  }
+  @counter-style b {
+    system: additive;
+    additive-symbols: 3 c, 2 b, 1 a;
+    additive-symbols: 1 x, 2 y, 3 z;
+    additive-symbols: x, y, z;
+    additive-symbols: 1, 2, 3;
+  }
+</style>
+<ol style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
+<ol style="list-style-type: b">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-ref.html
new file mode 100644
index 0000000..e353cc9
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>c.&nbsp;</div>
+<!-- list-style-type: b -->
+<div>a.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>c.&nbsp;</div>
+<div>d.&nbsp;</div>
+<div>e.&nbsp;</div>
+<div>f.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols.html
new file mode 100644
index 0000000..15568f0
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-symbols.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: descriptor symbols</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#counter-style-symbols">
+<link rel="match" href="descriptor-symbols-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: fixed;
+    symbols: a "b" \63;
+  }
+  @counter-style b {
+    system: additive;
+    additive-symbols: \66  6, 'e' 5, d 4, 3 \63, 2 "b", 1 a;
+  }
+</style>
+<ol style="list-style-type: a">
+  <li><li><li>
+</ol>
+<ol style="list-style-type: b">
+  <li><li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/disclosure-styles-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/disclosure-styles-ref.html
new file mode 100644
index 0000000..a1dafa7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/disclosure-styles-ref.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<style type="text/css">
+  @counter-style disclosure-closed2-ltr {
+    system: cyclic;
+    symbols: \25b8;
+    suffix: ' ';
+  }
+  @counter-style disclosure-closed2-rtl {
+    system: cyclic;
+    symbols: \25c2;
+    suffix: ' ';
+  }
+  @counter-style disclosure-open2 {
+    system: cyclic;
+    symbols: \25be;
+    suffix: ' ';
+  }
+  .open { list-style-type: disclosure-open2; }
+  .closed:dir(ltr) { list-style-type: disclosure-closed2-ltr; }
+  .closed:dir(rtl) { list-style-type: disclosure-closed2-rtl; }
+  ul {
+    padding: 0;
+    list-style-position: inside;
+  }
+</style>
+<ul dir="ltr">
+  <li class="closed">closed ltr
+  <li class="open">open ltr
+</ul>
+<ul dir="rtl">
+  <li class="closed">closed rtl
+  <li class="open">open rtl
+</ul>
+<p dir="ltr">&#x25b8;&nbsp;closed ltr
+<p dir="rtl">&#x25c2;&nbsp;closed rtl
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/disclosure-styles.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/disclosure-styles.html
new file mode 100644
index 0000000..79ac6ca
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/disclosure-styles.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: disclosure styles</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#disclosure-open">
+<link rel="match" href="disclosure-styles-ref.html">
+<style type="text/css">
+  @counter-style disclosure-closed2 {
+    system: extends disclosure-closed;
+  }
+  @counter-style disclosure-open2 {
+    system: extends disclosure-open;
+  }
+  ul {
+    padding: 0;
+    list-style-position: inside;
+  }
+  .closed { list-style-type: disclosure-closed2; }
+  .open { list-style-type: disclosure-open2; }
+  p::before {
+    content: counter(a, disclosure-closed) " ";
+  }
+</style>
+<ul dir="ltr">
+  <li class="closed">closed ltr
+  <li class="open">open ltr
+</ul>
+<ul dir="rtl">
+  <li class="closed">closed rtl
+  <li class="open">open rtl
+</ul>
+<p dir="ltr">closed ltr
+<p dir="rtl">closed rtl
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/name-case-sensitivity-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/name-case-sensitivity-ref.html
new file mode 100644
index 0000000..96a848a
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/name-case-sensitivity-ref.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<style type="text/css">
+  ol, div, p {
+    padding: 0; margin: 0;
+    line-height: 150%;
+  }
+  ol {
+    list-style-position: inside;
+  }
+  li, div, p {
+    float: left;
+  }
+  @counter-style decimal-leading-zero {
+    system: extends decimal;
+    pad: 3 '0';
+  }
+  @counter-style custom-style {
+    system: cyclic;
+    symbols: \2023;
+  }
+</style>
+
+<!-- list-style -->
+<ol><li style="list-style: hiragana inside;"></li></ol>
+<ol><li style="list-style: decimal-leading-zero inside;"></li></ol>
+<ol><li style="list-style: custom-style inside;"></li></ol>
+<ol><li style="list-style: decimal inside;"></li></ol>
+
+<!-- list-style-type -->
+<ol><li style="list-style-type: hiragana;"></li></ol>
+<ol><li style="list-style-type: decimal-leading-zero;"></li></ol>
+<ol><li style="list-style-type: custom-style;"></li></ol>
+<ol><li style="list-style-type: decimal;"></li></ol>
+
+<!-- counter() -->
+<style type="text/css">
+  #counter { counter-reset: a 1; }
+  #counter-a::before { content: counter(a, hiragana); }
+  #counter-b::before { content: counter(a, decimal-leading-zero); }
+  #counter-c::before { content: counter(a, custom-style); }
+  #counter-d::before { content: counter(a, decimal); }
+</style>
+<div id="counter">
+  <p id="counter-a"></p>
+  <p id="counter-b"></p>
+  <p id="counter-c"></p>
+  <p id="counter-d"></p>
+</div>
+
+<!-- counters() -->
+<style type="text/css">
+  #counters { counter-reset: a 1; }
+  #counters-a::before { content: counters(a, '', hiragana); }
+  #counters-b::before { content: counters(a, '', decimal-leading-zero); }
+  #counters-c::before { content: counters(a, '', custom-style); }
+  #counters-d::before { content: counters(a, '', decimal); }
+</style>
+<div id="counters">
+  <p id="counters-a"></p>
+  <p id="counters-b"></p>
+  <p id="counters-c"></p>
+  <p id="counters-d"></p>
+</div>
+
+<style type="text/css">
+  @counter-style a { system: extends hiragana; }
+  @counter-style b { system: extends decimal-leading-zero; }
+  @counter-style c { system: extends custom-style; }
+  @counter-style d { system: extends decimal; }
+</style>
+<ol><li style="list-style-type: a;"></li></ol>
+<ol><li style="list-style-type: b;"></li></ol>
+<ol><li style="list-style-type: c;"></li></ol>
+<ol><li style="list-style-type: d;"></li></ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/name-case-sensitivity.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/name-case-sensitivity.html
new file mode 100644
index 0000000..1e478bf
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/name-case-sensitivity.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: name case sensitivity</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#typedef-counter-style-name">
+<link rel="match" href="name-case-sensitivity-ref.html">
+<style type="text/css">
+  ol, div, p {
+    padding: 0; margin: 0;
+    line-height: 150%;
+  }
+  ol {
+    list-style-position: inside;
+  }
+  li, div, p {
+    float: left;
+  }
+  @counter-style decimal-leading-zero {
+    system: extends decimal;
+    pad: 3 '0';
+  }
+  @counter-style custom-style {
+    system: cyclic;
+    symbols: \2023;
+  }
+</style>
+
+<!-- list-style -->
+<ol><li style="list-style: Hiragana inside;"></li></ol>
+<ol><li style="list-style: Decimal-Leading-Zero inside;"></li></ol>
+<ol><li style="list-style: custom-style inside;"></li></ol>
+<ol><li style="list-style: Custom-Style inside;"></li></ol>
+
+<!-- list-style-type -->
+<ol><li style="list-style-type: Hiragana;"></li></ol>
+<ol><li style="list-style-type: Decimal-Leading-Zero;"></li></ol>
+<ol><li style="list-style-type: custom-style;"></li></ol>
+<ol><li style="list-style-type: Custom-Style;"></li></ol>
+
+<!-- counter() -->
+<style type="text/css">
+  #counter { counter-reset: a 1; }
+  #counter-a::before { content: counter(a, Hiragana); }
+  #counter-b::before { content: counter(a, Decimal-leading-Zero); }
+  #counter-c::before { content: counter(a, custom-style); }
+  #counter-d::before { content: counter(a, Custom-Style); }
+</style>
+<div id="counter">
+  <p id="counter-a"></p>
+  <p id="counter-b"></p>
+  <p id="counter-c"></p>
+  <p id="counter-d"></p>
+</div>
+
+<!-- counters() -->
+<style type="text/css">
+  #counters { counter-reset: a 1; }
+  #counters-a::before { content: counters(a, '', Hiragana); }
+  #counters-b::before { content: counters(a, '', Decimal-leading-Zero); }
+  #counters-c::before { content: counters(a, '', custom-style); }
+  #counters-d::before { content: counters(a, '', Custom-Style); }
+</style>
+<div id="counters">
+  <p id="counters-a"></p>
+  <p id="counters-b"></p>
+  <p id="counters-c"></p>
+  <p id="counters-d"></p>
+</div>
+
+<style type="text/css">
+  @counter-style a { system: extends HiRaGaNa; }
+  @counter-style b { system: extends Decimal-leading-ZERO; }
+  @counter-style c { system: extends custom-style; }
+  @counter-style d { system: extends Custom-Style; }
+</style>
+<ol><li style="list-style-type: a;"></li></ol>
+<ol><li style="list-style-type: b;"></li></ol>
+<ol><li style="list-style-type: c;"></li></ol>
+<ol><li style="list-style-type: d;"></li></ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping-ref.html
new file mode 100644
index 0000000..3924851
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping-ref.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style triangle {
+    system: cyclic;
+    symbols: \2023;
+    suffix: "";
+  }
+  .triangle { list-style-type: triangle; }
+  .hiragana { list-style-type: hiragana; }
+  .katakana { list-style-type: katakana; }
+  .hiragana-iroha { list-style-type: hiragana-iroha; }
+  .katakana-iroha { list-style-type: katakana-iroha; }
+</style>
+<ul class="triangle"><li></ul>
+<ul class="triangle"><li></ul>
+<ul class="triangle"><li></ul>
+<ul class="hiragana"><li></ul>
+<ul class="katakana"><li></ul>
+<ul class="hiragana-iroha"><li></ul>
+<ul class="katakana-iroha"><li></ul>
+<ul class="hiragana"><li></ul>
+<ul class="katakana"><li></ul>
+<ul class="hiragana-iroha"><li></ul>
+<ul class="katakana-iroha"><li></ul>
+
+<ol><li></ol>
+<ol class="triangle"><li></ol>
+<ol class="triangle"><li></ol>
+<ol class="triangle"><li></ol>
+<ol class="hiragana"><li></ol>
+<ol class="katakana"><li></ol>
+<ol class="hiragana-iroha"><li></ol>
+<ol class="katakana-iroha"><li></ol>
+<ol class="hiragana"><li></ol>
+<ol class="katakana"><li></ol>
+<ol class="hiragana-iroha"><li></ol>
+<ol class="katakana-iroha"><li></ol>
+
+<ul>
+  <li class="triangle">
+  <li class="triangle">
+  <li class="triangle">
+  <li class="hiragana">
+  <li class="katakana">
+  <li class="hiragana-iroha">
+  <li class="katakana-iroha">
+</ul>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html
new file mode 100644
index 0000000..1e3fcfc
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: redefine attr mapping</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#the-counter-style-rule">
+<link rel="match" href="redefine-attr-mapping-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style triangle {
+    system: cyclic;
+    symbols: \2023;
+    suffix: "";
+  }
+  @counter-style disc {
+    system: extends triangle;
+  }
+  @counter-style circle {
+    system: extends triangle;
+  }
+  @counter-style square {
+    system: extends triangle;
+  }
+  @counter-style lower-roman {
+    system: extends hiragana;
+  }
+  @counter-style upper-roman {
+    system: extends katakana;
+  }
+  @counter-style lower-alpha {
+    system: extends hiragana-iroha;
+  }
+  @counter-style upper-alpha {
+    system: extends katakana-iroha;
+  }
+</style>
+<ul type="circle"><li></ul>
+<ul type="round"><li></ul>
+<ul type="square"><li></ul>
+<ul type="i"><li></ul>
+<ul type="I"><li></ul>
+<ul type="a"><li></ul>
+<ul type="A"><li></ul>
+<ul type="lower-roman"><li></ul>
+<ul type="upper-roman"><li></ul>
+<ul type="lower-alpha"><li></ul>
+<ul type="upper-alpha"><li></ul>
+
+<ol><li></ol>
+<ol type="circle"><li></ol>
+<ol type="round"><li></ol>
+<ol type="square"><li></ol>
+<ol type="i"><li></ol>
+<ol type="I"><li></ol>
+<ol type="a"><li></ol>
+<ol type="A"><li></ol>
+<ol type="lower-roman"><li></ol>
+<ol type="upper-roman"><li></ol>
+<ol type="lower-alpha"><li></ol>
+<ol type="upper-alpha"><li></ol>
+
+<ul>
+  <li type="circle">
+  <li type="round">
+  <li type="square">
+  <li type="i">
+  <li type="I">
+  <li type="a">
+  <li type="A">
+</ul>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-builtin-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-builtin-ref.html
new file mode 100644
index 0000000..1d32dac
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-builtin-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/test-common.css">
+<ol>
+  <li style="list-style-type: none">foo
+  <li style="list-style-type: decimal">bar
+  <li style="list-style-type: disc">baz
+  <li style="list-style-type: cjk-decimal">
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-builtin.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-builtin.html
new file mode 100644
index 0000000..af90fca
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-builtin.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: redefine builtin</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#the-counter-style-rule">
+<link rel="match" href="redefine-builtin-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style none {
+    system: extends lower-roman;
+  }
+  @counter-style decimal {
+    system: extends upper-roman;
+  }
+  @counter-style disc {
+    system: extends decimal;
+  }
+  @counter-style hebrew {
+    system: extends cjk-decimal;
+  }
+</style>
+<ol>
+  <li style="list-style-type: none">foo
+  <li style="list-style-type: decimal">bar
+  <li style="list-style-type: disc">baz
+  <li style="list-style-type: hebrew">
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/reftest.list b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/reftest.list
new file mode 100644
index 0000000..f931e37
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/reftest.list
@@ -0,0 +1,35 @@
+== system-cyclic.html     system-cyclic-ref.html
+== system-fixed.html      system-fixed-ref.html
+== system-symbolic.html   system-symbolic-ref.html
+== system-alphabetic.html system-alphabetic-ref.html
+== system-numeric.html    system-numeric-ref.html
+== system-additive.html   system-additive-ref.html
+== system-extends.html    system-extends-ref.html
+== system-cyclic-invalid.html     system-common-invalid-ref.html
+== system-fixed-invalid.html      system-common-invalid2-ref.html
+== system-symbolic-invalid.html   system-common-invalid-ref.html
+== system-alphabetic-invalid.html system-common-invalid2-ref.html
+== system-numeric-invalid.html    system-common-invalid2-ref.html
+== system-additive-invalid.html   system-common-invalid-ref.html
+== system-extends-invalid.html    system-extends-invalid-ref.html
+== descriptor-negative.html descriptor-negative-ref.html
+== descriptor-prefix.html   descriptor-prefix-ref.html
+== descriptor-suffix.html   descriptor-suffix-ref.html
+== descriptor-range.html    descriptor-range-ref.html
+== descriptor-pad.html      descriptor-pad-ref.html
+== descriptor-fallback.html descriptor-fallback-ref.html
+== descriptor-symbols.html  descriptor-symbols-ref.html
+== descriptor-negative-invalid.html descriptor-negative-invalid-ref.html
+== descriptor-prefix-invalid.html   descriptor-prefix-invalid-ref.html
+== descriptor-suffix-invalid.html   descriptor-suffix-invalid-ref.html
+== descriptor-range-invalid.html    descriptor-range-invalid-ref.html
+== descriptor-pad-invalid.html      descriptor-pad-invalid-ref.html
+== descriptor-fallback-invalid.html descriptor-fallback-invalid-ref.html
+== descriptor-symbols-invalid.html  descriptor-symbols-invalid-ref.html
+== name-case-sensitivity.html       name-case-sensitivity-ref.html
+== dependent-builtin.html           dependent-builtin-ref.html
+== redefine-builtin.html            redefine-builtin-ref.html
+== redefine-attr-mapping.html       redefine-attr-mapping-ref.html
+== disclosure-styles.html           disclosure-styles-ref.html
+== symbols-function.html            symbols-function-ref.html
+== symbols-function-invalid.html    symbols-function-invalid-ref.html
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/ref-common.css b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/ref-common.css
new file mode 100644
index 0000000..92d77de
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/ref-common.css
@@ -0,0 +1,12 @@
+body {
+  /* to match ua.css, see bug 1020143 */
+  font-variant-numeric: tabular-nums;
+}
+div, p {
+  padding: 0; margin: 0;
+  line-height: 150%;
+  float: left;
+}
+p {
+  padding-right: .5em;
+}
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/test-common.css b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/test-common.css
new file mode 100644
index 0000000..dcbf4e1
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/support/test-common.css
@@ -0,0 +1,18 @@
+body {
+  /* to match ua.css, see bug 1020143 */
+  font-variant-numeric: tabular-nums;
+}
+ol, ul, section, p {
+  padding: 0; margin: 0;
+  line-height: 150%;
+}
+ol, ul {
+  list-style-position: inside;
+}
+li, p {
+  float: left;
+  padding: 0;
+}
+p {
+  padding-right: .5em;
+}
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid-ref.html
new file mode 100644
index 0000000..e07de5f
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  .invalid {
+    list-style-type: lower-greek;
+  }
+</style>
+<ol start="-2" class="invalid">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid.html
new file mode 100644
index 0000000..35aa832
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#symbols-function">
+<link rel="match" href="symbols-function-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  .invalid {
+    list-style-type: lower-greek;
+    list-style-type: symbols(a b c);
+    list-style-type: symbols(alphabetic a b c);
+    list-style-type: symbols(numeric 0 1 2);
+    list-style-type: symbols(additive 'a' 'b');
+    list-style-type: symbols(fixed);
+    list-style-type: symbols(alphabetic 'a');
+    list-style-type: symbols(numeric '0');
+  }
+</style>
+<ol start="-2" class="invalid">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-ref.html
new file mode 100644
index 0000000..d2291bd
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-ref.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style cyclic {
+    system: cyclic;
+    symbols: '*' '\2020' '\2021' '\A7';
+    suffix: ' ';
+  }
+  @counter-style numeric {
+    system: numeric;
+    symbols: '0' '1' '2';
+    suffix: ' ';
+  }
+  @counter-style alphabetic {
+    system: alphabetic;
+    symbols: '\26AA' '\26AB';
+    suffix: ' ';
+  }
+  @counter-style symbolic {
+    system: symbolic;
+    symbols: '*' '\2020' '\2021' '\A7';
+    suffix: ' ';
+  }
+  @counter-style fixed {
+    system: fixed;
+    symbols: '\25F0' '\25F1' '\25F2' '\25F3';
+    suffix: ' ';
+  }
+  @counter-style counter {
+    symbols: '*';
+  }
+  @counter-style counters {
+    system: numeric;
+    symbols: '0' '1';
+  }
+  .counter { counter-reset: a; }
+  .counter p { counter-increment: a 1; }
+  .counter p::after {
+    content: counter(a, counter);
+  }
+  .counter, .counters {
+    list-style-type: none;
+    counter-reset: a;
+  }
+  .counter li, .counters li {
+    counter-increment: a;
+    padding-right: .5em;
+  }
+  .counter li::after {
+    content: counter(a, counter);
+  }
+  .counters .counters li::after {
+    content: counters(a, '.', counters);
+  }
+</style>
+<ol start="-2" style="list-style-type: symbolic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: cyclic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: numeric">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: alphabetic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: symbolic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: fixed">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol class="counter">
+  <li><li><li><li><li>
+</ol>
+<ol class="counters">
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function.html
new file mode 100644
index 0000000..9f12958
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: symbols function</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#symbols-function">
+<link rel="match" href="symbols-function-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  .default {
+    list-style-type: symbols('*' '\2020' '\2021' '\A7');
+  }
+  .cyclic {
+    list-style-type: symbols(cyclic '*' '\2020' '\2021' '\A7');
+  }
+  .numeric {
+    list-style-type: symbols(numeric '0' '1' '2');
+  }
+  .alphabetic {
+    list-style-type: symbols(alphabetic '\26AA' '\26AB');
+  }
+  .symbolic {
+    list-style-type: symbols(symbolic '*' '\2020' '\2021' '\A7');
+  }
+  .fixed {
+    list-style-type: symbols(fixed '\25F0' '\25F1' '\25F2' '\25F3');
+  }
+  .counter, .counters {
+    list-style-type: none;
+    counter-reset: a;
+  }
+  .counter li, .counters li {
+    counter-increment: a;
+    padding-right: .5em;
+  }
+  .counter li::after {
+    content: counter(a, symbols('*'));
+  }
+  .counters .counters li::after {
+    content: counters(a, '.', symbols(numeric '0' '1'));
+  }
+</style>
+<ol start="-2" class="default">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="cyclic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="numeric">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="alphabetic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="symbolic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="fixed">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol class="counter">
+  <li><li><li><li><li>
+</ol>
+<ol class="counters">
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-invalid.html
new file mode 100644
index 0000000..1a8f94c
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-invalid.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system additive, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#additive-system">
+<link rel="match" href="system-common-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: additive;
+    suffix: ":";
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-ref.html
new file mode 100644
index 0000000..b45ab5f
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>-2</div>
+<div>-1</div>
+<div>0</div>
+<div>&#x2680;</div>
+<div>&#x2681;</div>
+<div>&#x2682;</div>
+<div>&#x2683;</div>
+<div>&#x2684;</div>
+<div>&#x2685;</div>
+<div>&#x2685;&#x2680;</div>
+<div>&#x2685;&#x2683;</div>
+<div>&#x2685;&#x2684;</div>
+<div>&#x2685;&#x2685;</div>
+<div>&#x2685;&#x2685;&#x2680;</div>
+<div><script type="text/javascript">
+  document.write(Array(61).join('&#x2685;'));
+</script></div>
+<!-- list-style-type: b -->
+<div>-2</div>
+<div>-1</div>
+<div>&#x2637;</div>
+<div>&#x2636;</div>
+<div>&#x2635;</div>
+<!-- list-style-type: c -->
+<div>1.&nbsp;</div>
+<div>b.&nbsp;</div>
+<div>a.&nbsp;</div>
+<div>4.&nbsp;</div>
+<div>ab.&nbsp;</div>
+<!-- list-style-type: d -->
+<div><script type="text/javascript">
+  document.write(Array(61).join('&#x10300;'));
+</script>.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive.html
new file mode 100644
index 0000000..74568bc
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system additive</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#additive-system">
+<link rel="match" href="system-additive-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: additive;
+    additive-symbols: 6 \2685, 5 \2684, 4 \2683, 3 \2682, 2 \2681, 1 \2680;
+    suffix: "";
+  }
+  @counter-style b {
+    system: additive;
+    additive-symbols: 7 \2630, 6 \2631, 5 \2632, 4 \2633, 3 \2634, 2 \2635, 1 \2636, 0 \2637;
+    suffix: "";
+  }
+  @counter-style c {
+    system: additive;
+    additive-symbols: 3 "a", 2 "b";
+  }
+  @counter-style d {
+    system: additive;
+    additive-symbols: 1 \10300;
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+  <li value="10"><li><li><li>
+  <li value="360"><!-- 60 code points -->
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li><li><li><li><li>
+</ol>
+<ol style="list-style-type: c">
+  <li><li><li><li><li>
+</ol>
+<ol style="list-style-type: d">
+  <li value="60"><!-- 60 code points -->
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic-invalid.html
new file mode 100644
index 0000000..c0f8ae9
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic-invalid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system alphabetic, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#alphabetic-system">
+<link rel="match" href="system-common-invalid2-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: alphabetic;
+    suffix: ":";
+  }
+  @counter-style b {
+    system: alphabetic;
+    symbols: A;
+    suffix: ":";
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic-ref.html
new file mode 100644
index 0000000..331d96e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>-2</div>
+<div>-1</div>
+<div>0</div>
+<div>&#x26AA;</div>
+<div>&#x26AB;</div>
+<div>&#x26AA;&#x26AA;</div>
+<div>&#x26AA;&#x26AB;</div>
+<div>&#x26AB;&#x26AA;</div>
+<div>&#x26AB;&#x26AB;</div>
+<div>&#x26AA;&#x26AA;&#x26AA;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic.html
new file mode 100644
index 0000000..9447eaf
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system alphabetic</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#alphabetic-system">
+<link rel="match" href="system-alphabetic-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: alphabetic;
+    symbols: \26AA  \26AB;
+    suffix: '';
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-common-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-common-invalid-ref.html
new file mode 100644
index 0000000..0b91fd4
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-common-invalid-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/test-common.css">
+<ol start="-2" style="list-style-type: decimal">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-common-invalid2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-common-invalid2-ref.html
new file mode 100644
index 0000000..c0292b7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-common-invalid2-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/test-common.css">
+<ol start="-2" style="list-style-type: decimal">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
+<ol start="-2" style="list-style-type: decimal">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic-invalid.html
new file mode 100644
index 0000000..602df64
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic-invalid.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system cyclic, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#cyclic-system">
+<link rel="match" href="system-common-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: cyclic;
+    suffix: ":";
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic-ref.html
new file mode 100644
index 0000000..605a7ec
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>&#x2023;</div>
+<div>&#x2023;</div>
+<div>&#x2023;</div>
+<div>&#x2023;</div>
+<div>&#x2023;</div>
+<!-- list-style-type: b -->
+<div>&#x2021;</div>
+<div>&#x2020;</div>
+<div>&#x2021;</div>
+<div>&#x2020;</div>
+<div>&#x2021;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic.html
new file mode 100644
index 0000000..9d96b82
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-cyclic.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system cyclic</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#cyclic-system">
+<link rel="match" href="system-cyclic-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: cyclic;
+    symbols: \2023;
+    suffix: "";
+  }
+  @counter-style b {
+    system: cyclic;
+    symbols: \2020  \2021;
+    suffix: "";
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-invalid-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-invalid-ref.html
new file mode 100644
index 0000000..3360e90
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-invalid-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/test-common.css">
+<link rel="stylesheet" href="support/ref-common.css">
+<style type="text/css">
+  ol {
+    list-style: decimal inside;
+  }
+</style>
+<div>a1b</div>
+<div>2b</div>
+<div>c3.&nbsp;</div>
+<div>d4.&nbsp;</div>
+<div>e5.&nbsp;</div>
+<ol start="6">
+  <li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-invalid.html
new file mode 100644
index 0000000..446fe45
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-invalid.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system extends, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#extends-system">
+<link rel="match" href="system-extends-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends b;
+    prefix: a;
+  }
+  @counter-style b {
+    system: extends c;
+    suffix: b;
+  }
+  @counter-style c {
+    system: extends b;
+    pad: 2 c;
+  }
+  @counter-style d {
+    system: extends d;
+    prefix: d;
+  }
+  @counter-style e {
+    system: extends unknown;
+    prefix: e;
+  }
+  @counter-style f {
+    system: extends decimal;
+    symbols: a;
+  }
+  @counter-style g {
+    system: extends decimal;
+    additive-symbols: 1 a;
+  }
+</style>
+<ol>
+  <li style="list-style-type: a;">
+  <li style="list-style-type: b;">
+  <li style="list-style-type: c;">
+  <li style="list-style-type: d;">
+  <li style="list-style-type: e;">
+  <li style="list-style-type: f;">foo
+  <li style="list-style-type: g;">bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-ref.html
new file mode 100644
index 0000000..c988310
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>Chapter -2.&nbsp;</div>
+<div>Chapter -1.&nbsp;</div>
+<div>Chapter 0.&nbsp;</div>
+<div>Chapter I.&nbsp;</div>
+<div>Chapter II.&nbsp;</div>
+<div>Chapter III.&nbsp;</div>
+<div>Chapter IV.&nbsp;</div>
+<div>Chapter V.&nbsp;</div>
+<div>Chapter 6.&nbsp;</div>
+<div>Chapter 7.&nbsp;</div>
+<!-- list-style-type: b -->
+<div>Section -2.&nbsp;</div>
+<div>Section -1.&nbsp;</div>
+<div>Section 0.&nbsp;</div>
+<div>Section I.&nbsp;</div>
+<div>Section II.&nbsp;</div>
+<div>Section III.&nbsp;</div>
+<div>Section IV.&nbsp;</div>
+<div>Section V.&nbsp;</div>
+<div>Section VI.&nbsp;</div>
+<div>Section 7.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends.html
new file mode 100644
index 0000000..287930d
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-extends.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system extends</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#extends-system">
+<link rel="match" href="system-extends-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: extends upper-roman;
+    prefix: "Chapter ";
+    range: 1 5;
+  }
+  @counter-style b {
+    system: extends a;
+    prefix: "Section ";
+    range: 1 6;
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-invalid.html
new file mode 100644
index 0000000..b12b786
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-invalid.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system fixed, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#fixed-system">
+<link rel="match" href="system-common-invalid2-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: fixed;
+    suffix: ":";
+  }
+  @counter-style b {
+    system: fixed invalid;
+    suffix: ":";
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-ref.html
new file mode 100644
index 0000000..0de7bb8
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>-2:</div>
+<div>-1:</div>
+<div>0:</div>
+<div>&#x25F0;:</div>
+<div>&#x25F1;:</div>
+<div>&#x25F2;:</div>
+<div>&#x25F3;:</div>
+<div>5:</div>
+<div>6:</div>
+<div>7:</div>
+<!-- list-style-type: b -->
+<div>-2:</div>
+<div>&#x25F4;:</div>
+<div>&#x25F5;:</div>
+<div>&#x25F6;:</div>
+<div>&#x25F7;:</div>
+<div>3:</div>
+<div>4:</div>
+<div>5:</div>
+<div>6:</div>
+<div>7:</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed.html
new file mode 100644
index 0000000..900c952
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system fixed</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#fixed-system">
+<link rel="match" href="system-fixed-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: fixed;
+    symbols: \25F0  \25F1  \25F2  \25F3;
+    suffix: ':';
+  }
+  @counter-style b {
+    system: fixed -1;
+    symbols: \25F4  \25F5  \25F6  \25F7;
+    suffix: ':';
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-invalid.html
new file mode 100644
index 0000000..064da8e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-invalid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system numeric, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#numeric-system">
+<link rel="match" href="system-common-invalid2-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: numeric;
+    suffix: ":";
+  }
+  @counter-style b {
+    system: numeric;
+    symbols: A;
+    suffix: ":";
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
+<ol start="-2" style="list-style-type: b">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-ref.html
new file mode 100644
index 0000000..c7e70d6
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<div>-11.&nbsp;</div>
+<div>-10.&nbsp;</div>
+<div>-2.&nbsp;</div>
+<div>-1.&nbsp;</div>
+<div>0.&nbsp;</div>
+<div>1.&nbsp;</div>
+<div>2.&nbsp;</div>
+<div>10.&nbsp;</div>
+<div>11.&nbsp;</div>
+<div>12.&nbsp;</div>
+<div>10201.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric.html
new file mode 100644
index 0000000..b5be591
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system numeric</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#numeric-system">
+<link rel="match" href="system-numeric-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: numeric;
+    symbols: '0' '1' '2';
+  }
+</style>
+<ol start="-4" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+  <li value="100">
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-invalid.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-invalid.html
new file mode 100644
index 0000000..1c0fac4
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-invalid.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system symbolic, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#symbolic-system">
+<link rel="match" href="system-common-invalid-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    system: symbolic;
+    suffix: ":";
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li>foo<li>bar<li>foo<li>bar
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-ref.html
new file mode 100644
index 0000000..7229c89
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Reference: symbols function, invalid</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="stylesheet" href="support/ref-common.css">
+<!-- list-style-type: a -->
+<div>-2</div>
+<div>-1</div>
+<div>0</div>
+<div>*</div>
+<div>&#x2051;</div>
+<div>&#x2020;</div>
+<div>&#x2021;</div>
+<div>**</div>
+<div>&#x2051;&#x2051;</div>
+<div>&#x2020;&#x2020;</div>
+<div><script type="text/javascript">
+  document.write(Array(61).join('&#x2021;'));
+</script></div>
+<!-- list-style-type: b -->
+<div><script type="text/javascript">
+  document.write(Array(61).join('&#x10300;'));
+</script>.&nbsp;</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic.html b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic.html
new file mode 100644
index 0000000..a3ab8b1
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>CSS Test: system symbolic</title>
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org/">
+<link rel="help" href="https://drafts.csswg.org/css-counter-styles-3/#symbolic-system">
+<link rel="match" href="system-symbolic-ref.html">
+<link rel="stylesheet" href="support/test-common.css">
+<style type="text/css">
+  @counter-style a {
+    /* system: symbolic; */
+    symbols: '*' \2051  \2020  \2021;
+    suffix: '';
+  }
+  @counter-style b {
+    symbols: \10300;
+  }
+</style>
+<ol start="-2" style="list-style-type: a">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+  <li value="240"><!-- 60 code points -->
+</ol>
+<ol style="list-style-type: b">
+  <li value="60"><!-- 60 code points -->
+</ol>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-abspos-child-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-abspos-child-002-ref.html
new file mode 100644
index 0000000..876e5d6
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-abspos-child-002-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <style>
+    .tealBlock {
+      background: teal;
+      width: 10px;
+      height: 10px;
+      margin-bottom: 5px;
+    }
+    .purpleBlock {
+      background: purple;
+      width: 10px;
+      height: 10px;
+      margin-bottom: 5px;
+    }
+  </style>
+</head>
+<body>
+
+  <div class="purpleBlock"></div>
+  <div class="purpleBlock"></div>
+  <div class="purpleBlock"></div>
+  <div class="purpleBlock"></div>
+
+  <div class="tealBlock"></div>
+  <div class="tealBlock"></div>
+  <div class="tealBlock"></div>
+  <div class="tealBlock"></div>
+
+  <div class="tealBlock"></div>
+  <div class="tealBlock"></div>
+  <div class="tealBlock"></div>
+  <div class="tealBlock"></div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-abspos-child-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-abspos-child-002.html
new file mode 100644
index 0000000..2bbb5f0
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-abspos-child-002.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>
+    CSS Test: Test that "flex-basis" doesn't affect layout of abspos flex child
+  </title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#abspos-items">
+  <link rel="match" href="flexbox-abspos-child-001-ref.html">
+  <style>
+    .flex {
+      display: flex;
+      height: 10px;
+      width: 10px;
+      background: purple;
+      margin-bottom: 5px;
+      position: relative;
+    }
+    .flex > * {
+      position: absolute;
+      background: teal;
+      height: 10px;
+    }
+    .sized {
+      width: 10px;
+    }
+    .implied {
+      left: 0;
+      right: 0;
+    }
+  </style>
+</head>
+<body>
+  <!-- In all cases below, flex-basis should have no effect on layout (because
+       it's on an element that is abspos and hence not a flex item). -->
+
+  <!-- Abspos child has auto width (which should end up 0): -->
+  <div class="flex"><div style="flex-basis: 2px"></div></div>
+  <div class="flex"><div style="flex-basis: 100px"></div></div>
+  <div class="flex"><div style="flex-basis: 80%"></div></div>
+  <div class="flex"><div style="flex-basis: content"></div></div>
+
+  <!-- Abspos child has explicit 10px width: -->
+  <div class="flex"><div class="sized" style="flex-basis: 2px"></div></div>
+  <div class="flex"><div class="sized" style="flex-basis: 100px"></div></div>
+  <div class="flex"><div class="sized" style="flex-basis: 80%"></div></div>
+  <div class="flex"><div class="sized" style="flex-basis: content"></div></div>
+
+  <!-- Abspos child has implicit 10px width (implied by auto width and
+       constrained left/right properties): -->
+  <div class="flex"><div class="implied" style="flex-basis: 2px"></div></div>
+  <div class="flex"><div class="implied" style="flex-basis: 100px"></div></div>
+  <div class="flex"><div class="implied" style="flex-basis: 80%"></div></div>
+  <div class="flex"><div class="implied" style="flex-basis: content"></div></div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001-ref.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001-ref.xhtml
index 18d4c58..c83f5a0 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001-ref.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001-ref.xhtml
@@ -138,34 +138,5 @@
       <div class="b" style="margin-top: 30px"><div class="fixedSizeChild"/></div>
       <div class="c" style="margin-top: 30px"/>
     </div>
-
-    <!-- left -->
-    <div class="flexbox">
-      <div class="a"/>
-    </div>
-    <div class="flexbox">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
-    <!-- right -->
-    <div class="flexbox">
-      <div class="a"/>
-    </div>
-    <div class="flexbox">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001a.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001a.xhtml
index c7ef7d6..96fc4cd 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001a.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001a.xhtml
@@ -147,34 +147,5 @@
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
-
-    <!-- left -->
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
-    <!-- right -->
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001b.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001b.xhtml
index 1905438..0f2a254 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001b.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001b.xhtml
@@ -148,34 +148,5 @@
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
-
-    <!-- left -->
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
-    <!-- right -->
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001-ref.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001-ref.xhtml
index 460ccef..ab407a4 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001-ref.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001-ref.xhtml
@@ -141,34 +141,5 @@
       <div class="b" style="margin-left: 30px"><div class="fixedSizeChild"/></div>
       <div class="c" style="margin-left: 30px"/>
     </div>
-
-    <!-- left -->
-    <div class="flexbox">
-      <div class="a"/>
-    </div>
-    <div class="flexbox">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
-    <!-- right -->
-    <div class="flexbox">
-      <div class="a" style="margin-left: 190px"/>
-    </div>
-    <div class="flexbox">
-      <div class="a" style="margin-left: 160px"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox">
-      <div class="a" style="margin-left: 120px"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001a.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001a.xhtml
index ef91d5f..e8319cb 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001a.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001a.xhtml
@@ -147,34 +147,5 @@
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
-
-    <!-- left -->
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
-    <!-- right -->
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001b.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001b.xhtml
index 9b9b2fa..a2e7ae6 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001b.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001b.xhtml
@@ -148,34 +148,5 @@
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
-
-    <!-- left -->
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: left">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
-    <!-- right -->
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-    </div>
-    <div class="flexbox" style="align-content: right">
-      <div class="a"/>
-      <div class="b"><div class="fixedSizeChild"/></div>
-      <div class="c"/>
-    </div>
-
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-block.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-block.xhtml
index 569312a..3653545 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-block.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-block.xhtml
@@ -74,14 +74,6 @@
         background: violet;
         align-self: inherit;
       }
-      .left {
-        background: tan;
-        align-self: left;
-      }
-      .right {
-        background: brown;
-        align-self: right;
-      }
     </style>
   </head>
   <body>
@@ -100,8 +92,6 @@
       <div class="unspecified">unspec</div>
       <div class="initial">initial</div>
       <div class="inherit">inherit</div>
-      <div class="left">left</div>
-      <div class="right">right</div>
     </div>
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-ref.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-ref.xhtml
index 356ab3d..a95a724 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-ref.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-ref.xhtml
@@ -14,7 +14,7 @@
       .flexbox {
         border: 1px dashed blue;
         height: 200px;
-        width: 640px;
+        width: 560px;
         font-size: 10px;
         line-height: 10px;
       }
@@ -58,12 +58,6 @@
       .inherit {
         background: violet;
       }
-      .left {
-        background: tan;
-      }
-      .right {
-        background: brown;
-      }
    </style>
   </head>
   <body>
@@ -92,8 +86,6 @@
       <div class="unspecified" style="margin-top: 95px">unspec</div>
       <div class="initial" style="margin-top: 95px">initial</div>
       <div class="inherit" style="margin-top: 190px">inherit</div>
-      <div class="left">left</div>
-      <div class="right">right</div>
     </div>
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-table.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-table.xhtml
index 10180ad..1785ca8 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-table.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-001-table.xhtml
@@ -76,14 +76,6 @@
         background: violet;
         align-self: inherit;
       }
-      .left {
-        background: tan;
-        align-self: left;
-      }
-      .right {
-        background: brown;
-        align-self: right;
-      }
    </style>
   </head>
   <body>
@@ -102,8 +94,6 @@
       <div class="unspecified">unspec</div>
       <div class="initial">initial</div>
       <div class="inherit">inherit</div>
-      <div class="left">left</div>
-      <div class="right">right</div>
     </div>
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001-ref.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001-ref.xhtml
index 1d19034..2288c21 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001-ref.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001-ref.xhtml
@@ -63,14 +63,6 @@
         background: violet;
         float: right;
       }
-      .left {
-        background: tan;
-        float: left;
-      }
-      .right {
-        background: brown;
-        float: right;
-      }
 
       <!-- We center shrinkwrapped text by putting it into an inline-block, and
            then wrapping that inline-block in a helper-div that has
@@ -108,12 +100,10 @@
         <div class="initial">initial</div>
       </div>
       <div class="inherit">inherit</div>
-      <div class="left">left</div>
-      <div class="right">right</div>
-      <!-- Since the last three divs are floated, the container doesn't include
-           their heights by default. So we add some invisible hacky text (of the
+      <!-- Since the last div is floated, the container doesn't include
+           its height by default. So we add some invisible hacky text (of the
            same font) to make sure our container is tall enough. -->
-      <span style="visibility:hidden">hacky text<br/>(line 2)<br/>(line 3)</span>
+      <span style="visibility:hidden">hacky text</span>
     </div>
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001.xhtml
index e611f48..4080d34 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-001.xhtml
@@ -69,14 +69,6 @@
         background: violet;
         align-self: inherit;
       }
-      .left {
-        background: tan;
-        align-self: left;
-      }
-      .right {
-        background: brown;
-        align-self: right;
-      }
    </style>
   </head>
   <body>
@@ -95,8 +87,6 @@
       <div class="unspecified">unspec</div>
       <div class="initial">initial</div>
       <div class="inherit">inherit</div>
-      <div class="left">left</div>
-      <div class="right">right</div>
     </div>
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001-ref.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001-ref.xhtml
index a9235f0..5c2f62a 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001-ref.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001-ref.xhtml
@@ -66,14 +66,6 @@
         background: violet;
         float: left;
       }
-      .left {
-        background: tan;
-        float: left;
-      }
-      .right {
-        background: brown;
-        float: right;
-      }
 
       <!-- We center shrinkwrapped text by putting it into an inline-block, and
            then wrapping that inline-block in a helper-div that has
@@ -111,12 +103,10 @@
         <div class="initial">initial</div>
       </div>
       <div class="inherit">inherit</div>
-      <div class="left">left</div>
-      <div class="right">right</div>
-      <!-- Since the last three divs are floated, the container doesn't include
-           their heights by default. So we add some invisible hacky text (of the
+      <!-- Since the last div is floated, the container doesn't include
+           its height by default. So we add some invisible hacky text (of the
            same font) to make sure our container is tall enough. -->
-      <span style="visibility:hidden">hacky text<br/>(line 2)<br/>(line 3)</span>
+      <span style="visibility:hidden">hacky text</span>
     </div>
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001.xhtml
index d0ef296..1016c28 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-001.xhtml
@@ -72,14 +72,6 @@
         background: violet;
         align-self: inherit;
       }
-      .left {
-        background: tan;
-        align-self: left;
-      }
-      .right {
-        background: brown;
-        align-self: right;
-      }
    </style>
   </head>
   <body>
@@ -98,8 +90,6 @@
       <div class="unspecified">unspec</div>
       <div class="initial">initial</div>
       <div class="inherit">inherit</div>
-      <div class="left">left</div>
-      <div class="right">right</div>
     </div>
   </body>
 </html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-block-horiz-001v.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-block-horiz-001v.xhtml
new file mode 100644
index 0000000..f1dfdfd
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-block-horiz-001v.xhtml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!-- Testcase with blocks as flex items in a horizontal flex container, with
+     various "flex" values and various combinations of the items
+     and with various writing-modes on the items. -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>
+      CSS Test: Testing flexbox layout algorithm property on block flex items in a horizontal flex container
+      (with various writing-modes on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"/>
+    <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#layout-algorithm"/>
+    <link rel="match" href="flexbox-basic-block-horiz-001-ref.xhtml"/>
+    <style>
+      div { height: 100px; }
+      div.flexbox {
+        border: 1px dashed blue;
+        width: 200px;
+        font-size: 10px;
+        display: flex;
+      }
+      div.a {
+        flex: 1 0 30px;
+        background: lightgreen;
+        writing-mode: vertical-lr;
+      }
+      div.b {
+        flex: 2 0 20px;
+        background: yellow;
+        writing-mode: vertical-rl;
+      }
+      div.c {
+        flex: 3 0 40px;
+        background: orange;
+        writing-mode: sideways-lr;
+      }
+      div.flexNone {
+        flex: none;
+        background: pink;
+        writing-mode: vertical-lr;
+      }
+      div.flexBasis {
+        flex: 0 0 20px;
+        background: gray;
+        writing-mode: sideways-rl;
+      }
+      div.spacer {
+        width: 15px;
+        height: 15px;
+        background: purple;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="flexbox"><div class="a"></div><div class="b"/></div>
+    <div class="flexbox"><div class="a"/><div class="c"/></div>
+    <div class="flexbox"><div class="a"/>
+      <div class="flexNone"><div class="spacer"/></div>
+    </div>
+    <div class="flexbox"><div class="b"/><div class="c"/></div>
+    <div class="flexbox"><div class="b"/>
+      <div class="flexNone"><div class="spacer"/><div class="spacer"/></div>
+    </div>
+
+    <div class="flexbox">
+      <div class="a"/><div class="b"/><div class="flexBasis"/><div class="c"/>
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-block-vert-001v.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-block-vert-001v.xhtml
new file mode 100644
index 0000000..da943df
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-block-vert-001v.xhtml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!-- Testcase with blocks as flex items in a vertical flex container, with
+     various "flex" values and various combinations of the items. -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>
+      CSS Test: Testing flexbox layout algorithm property on block flex items in a vertical flex container
+      (with various writing-modes on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"/>
+    <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#layout-algorithm"/>
+    <link rel="match" href="flexbox-basic-block-vert-001-ref.xhtml"/>
+    <style>
+      div { width: 50px; }
+      div.flexbox {
+        float: left;
+        border: 1px dashed blue;
+        height: 200px;
+        font-size: 10px;
+        display: flex;
+        flex-direction: column;
+      }
+      div.a {
+        flex: 1 0 30px;
+        background: lightgreen;
+        writing-mode: vertical-lr;
+      }
+      div.b {
+        flex: 2 0 20px;
+        background: yellow;
+        writing-mode: vertical-rl;
+      }
+      div.c {
+        flex: 3 0 40px;
+        background: orange;
+        writing-mode: sideways-lr;
+      }
+      div.flexNone {
+        flex: none;
+        background: pink;
+        writing-mode: vertical-lr;
+      }
+      div.flexBasis {
+        flex: 0 0 20px;
+        background: gray;
+        writing-mode: sideways-rl;
+      }
+      div.spacer {
+        display: inline-block;
+        width: 15px;
+        height: 15px;
+        background: purple;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="flexbox"><div class="a"></div><div class="b"/></div>
+    <div class="flexbox"><div class="a"/><div class="c"/></div>
+    <div class="flexbox"><div class="a"/>
+      <div class="flexNone"><div class="spacer"/></div>
+    </div>
+    <div class="flexbox"><div class="b"/><div class="c"/></div>
+    <div class="flexbox"><div class="b"/>
+      <div class="flexNone"><div class="spacer"/><div class="spacer"/></div>
+    </div>
+
+    <div class="flexbox">
+      <div class="a"/><div class="b"/><div class="flexBasis"/><div class="c"/>
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-canvas-horiz-001v.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-canvas-horiz-001v.xhtml
new file mode 100644
index 0000000..ad623d7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-canvas-horiz-001v.xhtml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!--
+     This test checks that canvas elements behave correctly as flex items.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>
+      CSS Test: Testing flexbox layout algorithm property on canvas flex items in a horizontal flex container
+      (with a vertical writing-mode on the canvas flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"/>
+    <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#layout-algorithm"/>
+    <link rel="match" href="flexbox-basic-canvas-horiz-001-ref.xhtml"/>
+    <style>
+      div.flexbox {
+        width: 200px;
+        background: lightgreen;
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 5px;
+        line-height: 8px;
+      }
+      canvas {
+        min-width: 0;
+        width: 10px;
+        height: 20px;
+        border: 1px dotted green;
+        writing-mode: vertical-lr;
+      }
+    </style>
+  </head>
+  <body>
+
+    <!-- A) One flex item -->
+    <div class="flexbox">
+      <canvas/>
+    </div>
+
+    <!-- B) Text and a canvas (ensure they aren't merged into one flex item) -->
+    <div class="flexbox">
+      some words <canvas/>
+    </div>
+
+    <!-- C) Two canvas elements, getting stretched by different ratios, from 0.
+
+         Space-to-be-distributed = 200px - borders = 200 - (1 + 1) - (1 + 1)
+                                 = 196px
+
+         1st element gets 5/8 of space: 5/8 * 196px = 122.5px
+         1st element gets 3/8 of space: 3/8 * 196px = 73.5px
+      -->
+    <div class="flexbox">
+      <canvas style="flex: 5"/>
+      <canvas style="flex: 3"/>
+    </div>
+
+    <!-- D) Two canvas elements, getting stretched by different ratios, from
+         different flex bases.
+
+         Space-to-be-distributed = 200px - (33 + 1 + 1) - (13 + 1 + 1) = 150px
+         1st element gets 2/5 of space: 33px + 2/5 * 150px = 93px
+         1st element gets 3/5 of space: 13px + 3/5 * 150px = 103px
+      -->
+    <div class="flexbox">
+      <canvas style="width: 33px; flex: 2 auto"/>
+      <canvas style="width: 13px; flex: 3 auto"/>
+    </div>
+
+    <!-- E) Two flex items, getting shrunk by different amounts.
+
+         Space-to-be-distributed = 200px - (150 + 1 + 1) - (100 + 1 + 1) = -54px
+         First element scaled flex ratio = 4 * 150 = 600
+         Second element scaled flex ratio = 3 * 100 = 300
+           * So, total flexibility is 600 + 300 = 900
+
+         1st element gets 600/900 of space: 150 + 600/900 * -54 = 114px
+         2nd element gets 300/900 of space: 100 + 300/900 * -54 = 82px
+      -->
+    <div class="flexbox">
+      <canvas style="width: 150px; flex: 1 4 auto"/>
+      <canvas style="width: 100px; flex: 1 3 auto"/>
+    </div>
+
+    <!-- F) Making sure we don't grow past max-width -->
+    <!-- Same as (D), except we've added a max-width on the second element.
+      -->
+    <div class="flexbox">
+      <canvas style="width: 33px; flex: 2 auto"/>
+      <canvas style="width: 13px; max-width: 90px; flex: 3 auto"/>
+    </div>
+
+    <!-- G) Making sure we grow at least as large as min-width -->
+    <!-- Same as (C), except we've added a min-width on the second element.
+      -->
+    <div class="flexbox">
+      <canvas style="width: 33px; flex: 2 auto"/>
+      <canvas style="width: 13px; min-width: 150px; flex: 3 auto"/>
+    </div>
+
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-canvas-vert-001v.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-canvas-vert-001v.xhtml
new file mode 100644
index 0000000..057f63a
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-basic-canvas-vert-001v.xhtml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!--
+     This test checks that canvas elements behave correctly as flex items.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>
+      CSS Test: Testing flexbox layout algorithm property on canvas flex items in a vertical flex container
+      (with a vertical writing-mode on the canvas flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"/>
+    <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#layout-algorithm"/>
+    <link rel="match" href="flexbox-basic-canvas-vert-001-ref.xhtml"/>
+    <style>
+      div.flexbox {
+        height: 200px;
+        background: lightgreen;
+        display: flex;
+        justify-content: space-between;
+        flex-direction: column;
+        float: left;
+        margin-right: 10px;
+        font: 8px monospace;
+      }
+      canvas {
+        width: 20px;
+        height: 10px;
+        min-height: 0;
+        border: 1px dotted green;
+        writing-mode: vertical-lr;
+      }
+    </style>
+  </head>
+  <body>
+
+    <!-- A) One flex item -->
+    <div class="flexbox">
+      <canvas/>
+    </div>
+
+    <!-- B) Text and a canvas (ensure they aren't merged into one flex item) -->
+    <div class="flexbox">
+      a b <canvas/>
+    </div>
+
+    <!-- C) Two canvas elements, getting stretched by different ratios, from 0.
+
+         Space-to-be-distributed = 200px - borders = 200 - (1 + 1) - (1 + 1)
+                                 = 196px
+
+         1st element gets 5/8 of space: 5/8 * 196px = 122.5px
+         1st element gets 3/8 of space: 3/8 * 196px = 73.5px
+      -->
+    <div class="flexbox">
+      <canvas style="flex: 5"/>
+      <canvas style="flex: 3"/>
+    </div>
+
+    <!-- D) Two canvas elements, getting stretched by different ratios, from
+         different flex bases.
+
+         Space-to-be-distributed = 200px - (33 + 1 + 1) - (13 + 1 + 1) = 150px
+         1st element gets 2/5 of space: 33px + 2/5 * 150px = 93px
+         1st element gets 3/5 of space: 13px + 3/5 * 150px = 103px
+      -->
+    <div class="flexbox">
+      <canvas style="height: 33px; flex: 2 auto"/>
+      <canvas style="height: 13px; flex: 3 auto"/>
+    </div>
+
+    <!-- E) Two flex items, getting shrunk by different amounts.
+
+         Space-to-be-distributed = 200px - (150 + 1 + 1) - (100 + 1 + 1) = -54px
+         First element scaled flex ratio = 4 * 150 = 600
+         Second element scaled flex ratio = 3 * 100 = 300
+           * So, total flexibility is 600 + 300 = 900
+
+         1st element gets 600/900 of space: 150 + 600/900 * -54 = 114px
+         2nd element gets 300/900 of space: 100 + 300/900 * -54 = 82px
+      -->
+    <div class="flexbox">
+      <canvas style="height: 150px; flex: 1 4 auto"/>
+      <canvas style="height: 100px; flex: 1 3 auto"/>
+    </div>
+
+    <!-- F) Making sure we don't grow past max-height -->
+    <!-- Same as (D), except we've added a max-height on the second element.
+      -->
+    <div class="flexbox">
+      <canvas style="height: 33px; flex: 2 auto"/>
+      <canvas style="height: 13px; max-height: 90px; flex: 3 auto"/>
+    </div>
+
+    <!-- G) Making sure we grow at least as large as min-height -->
+    <!-- Same as (C), except we've added a min-height on the second element.
+      -->
+    <div class="flexbox">
+      <canvas style="height: 33px; flex: 2 auto"/>
+      <canvas style="height: 13px; min-height: 150px; flex: 3 auto"/>
+    </div>
+
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-001-ref.html
new file mode 100644
index 0000000..ed0cc57
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-001-ref.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<p>Test passes if you see a green 100px x 100px square, and no red</p>
+<div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-001.html
new file mode 100644
index 0000000..5bff614
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-001.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: nested flex containers with height established by 'min-height'</title>
+<link rel="match" href="flexbox-definite-sizes-001-ref.html">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#definite-sizes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449326">
+<style>
+div {
+  display: flex;
+}
+
+.item {
+  width: 100px;
+  background: red;
+  align-items: center;
+}
+
+.item span {
+  min-height: 100%;
+  width: 100%;
+  background: green;
+}
+</style>
+<p>Test passes if you see a green 100px x 100px square, and no red</p>
+<div style="min-height: 100px;">
+  <div class="item">
+    <span></span>
+  </div>
+</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-002.html
new file mode 100644
index 0000000..126fd5e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-002.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: nested flex containers with height established by 'min-height'</title>
+<link rel="match" href="flexbox-definite-sizes-001-ref.html">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#definite-sizes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449326">
+<style>
+div {
+  display: flex;
+}
+
+.item {
+  width: 100px;
+  background: red;
+  align-items: center;
+  min-height: 100px;
+}
+
+.item span {
+  min-height: 100%;
+  width: 100%;
+  background: green;
+}
+</style>
+<p>Test passes if you see a green 100px x 100px square, and no red</p>
+<div>
+  <div class="item">
+    <span></span>
+  </div>
+</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-003.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-003.html
new file mode 100644
index 0000000..bc5b075
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-003.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: nested flex containers with definite max-height</title>
+<link rel="match" href="flexbox-definite-sizes-001-ref.html">
+<link rel="author" href="mailto:dholbert@mozilla.com" title="Daniel Holbert">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#definite-sizes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449326">
+<style>
+body { overflow: hidden }
+
+.outerFlex {
+  display: flex;
+  width: 100px;
+  /* Implicit "align-items:stretch" */
+}
+
+.innerFlex {
+  display: flex;
+  width: 100px;
+  background: red;
+
+  /* This reveals if we miscalculate the height of our flex item: */
+  align-items: flex-end;
+}
+
+.block {
+  width: 100px;
+  max-height: 100%;
+  background-color: green;
+}
+</style>
+<p>Test passes if you see a green 100px x 100px square, and no red</p>
+<div class="outerFlex" style="max-height: 100px">
+  <div class="innerFlex">
+    <div class="block"><div style="height:9999px"></div></div>
+  </div>
+</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-004.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-004.html
new file mode 100644
index 0000000..cf54aab
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-004.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: nested flex containers with definite max-height</title>
+<link rel="match" href="flexbox-definite-sizes-001-ref.html">
+<link rel="author" href="mailto:dholbert@mozilla.com" title="Daniel Holbert">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#definite-sizes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1449326">
+<style>
+body { overflow: hidden }
+
+.outerFlex {
+  display: flex;
+  width: 100px;
+  /* Implicit "align-items:stretch" */
+}
+
+.innerFlex {
+  display: flex;
+  width: 100px;
+  background: red;
+
+  /* This reveals if we miscalculate the height of our flex item: */
+  align-items: flex-end;
+}
+
+.block {
+  width: 100px;
+  max-height: 100%;
+  background-color: green;
+}
+</style>
+<p>Test passes if you see a green 100px x 100px square, and no red</p>
+<div class="outerFlex">
+  <div class="innerFlex" style="max-height: 100px">
+    <div class="block"><div style="height:9999px"></div></div>
+  </div>
+</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001-ref.html
new file mode 100644
index 0000000..b537711
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001-ref.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 50px;
+    width: 200px;
+  }
+
+  .container > * {
+    flex-shrink: 0;
+    min-width: 0;
+    border: 2px solid teal;
+  }
+
+  .smallText { font: 10px Ahem; }
+  .bigText   { font: 20px Ahem; }
+  .spacerChild::before {
+    content: '';
+    display: block;
+    background: brown;
+    height: 10px;
+    width: 10px;
+  }
+  .justPadding {
+    /* Empty div with 5px padding on each side */
+    padding: 5px;
+    background: cyan;
+  }
+  canvas { background: fuchsia }
+  </style>
+</head>
+<body>
+<!-- Flex items have unspecified size properties: -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas width="20"></canvas>
+</div>
+
+<!-- Various specified main-size values, in testcase
+     (removed here in reference case, because they shouldn't affect sizing): -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas width="20"></canvas>
+</div>
+
+<!-- Various specified cross-size values (should be honored): -->
+<div class="container">
+  <div class="smallText"    style="height: 0px">a b</div>
+  <div class="bigText"      style="height: 40px">c</div>
+  <div class="spacerChild"  style="height: 20px"></div>
+  <div class="justPadding"  style="height: 10px"></div>
+  <canvas width="20"        style="height: 8px"></canvas>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001a.html
new file mode 100644
index 0000000..4227f68
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001a.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing "flex-basis: content" in a row-oriented flex container
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#propdef-flex-basis">
+  <link rel="match" href="flexbox-flex-basis-content-001-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 50px;
+    width: 200px;
+  }
+
+  .container > * {
+    /* All flex items have "flex-basis: content" (and zero flex-shrink and
+       min-main-size, to avoid any influence from those). */
+    flex-basis: content;
+    flex-shrink: 0;
+    min-width: 0;
+    border: 2px solid teal;
+  }
+
+  .smallText { font: 10px Ahem; }
+  .bigText   { font: 20px Ahem; }
+  .spacerChild::before {
+    content: '';
+    display: block;
+    background: brown;
+    height: 10px;
+    width: 10px;
+  }
+  .justPadding {
+    /* Empty div with 5px padding on each side */
+    padding: 5px;
+    background: cyan;
+  }
+  canvas { background: fuchsia }
+  </style>
+</head>
+<body>
+<!-- Flex items have unspecified size properties: -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas width="20"></canvas>
+</div>
+
+<!-- Various specified main-size values (should be ignored): -->
+<div class="container">
+  <div class="smallText"    style="width: 0px">a b</div>
+  <div class="bigText"      style="width: 40px">c</div>
+  <div class="spacerChild"  style="width: 20px"></div>
+  <div class="justPadding"  style="width: 10px"></div>
+  <canvas width="20"        style="width: 8px"></canvas>
+</div>
+
+<!-- Various specified cross-size values (should be honored): -->
+<div class="container">
+  <div class="smallText"    style="height: 0px">a b</div>
+  <div class="bigText"      style="height: 40px">c</div>
+  <div class="spacerChild"  style="height: 20px"></div>
+  <div class="justPadding"  style="height: 10px"></div>
+  <canvas width="20"        style="height: 8px"></canvas>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001b.html
new file mode 100644
index 0000000..489ce65
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001b.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing "flex-basis: content" (set via the "flex" shorthand)
+    in a row-oriented flex container.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#propdef-flex-basis">
+  <link rel="match" href="flexbox-flex-basis-content-001-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 50px;
+    width: 200px;
+  }
+
+  .container > * {
+    /* All flex items have "flex-basis: content" (and zero flex-shrink and
+       min-main-size, to avoid any influence from those). */
+    flex: 0 0 content;
+    min-width: 0;
+    border: 2px solid teal;
+  }
+
+  .smallText { font: 10px Ahem; }
+  .bigText   { font: 20px Ahem; }
+  .spacerChild::before {
+    content: '';
+    display: block;
+    background: brown;
+    height: 10px;
+    width: 10px;
+  }
+  .justPadding {
+    /* Empty div with 5px padding on each side */
+    padding: 5px;
+    background: cyan;
+  }
+  canvas { background: fuchsia }
+  </style>
+</head>
+<body>
+<!-- Flex items have unspecified size properties: -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas width="20"></canvas>
+</div>
+
+<!-- Various specified main-size values (should be ignored): -->
+<div class="container">
+  <div class="smallText"    style="width: 0px">a b</div>
+  <div class="bigText"      style="width: 40px">c</div>
+  <div class="spacerChild"  style="width: 20px"></div>
+  <div class="justPadding"  style="width: 10px"></div>
+  <canvas width="20"        style="width: 8px"></canvas>
+</div>
+
+<!-- Various specified cross-size values (should be honored): -->
+<div class="container">
+  <div class="smallText"    style="height: 0px">a b</div>
+  <div class="bigText"      style="height: 40px">c</div>
+  <div class="spacerChild"  style="height: 20px"></div>
+  <div class="justPadding"  style="height: 10px"></div>
+  <canvas width="20"        style="height: 8px"></canvas>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002-ref.html
new file mode 100644
index 0000000..a7d1bcf
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002-ref.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-right: 2em;
+    width: 50px;
+    height: 200px;
+    float: left;
+  }
+
+  .container > * {
+    flex-shrink: 0;
+    min-height: 0;
+    border: 2px solid teal;
+  }
+
+  .smallText { font: 10px Ahem; }
+  .bigText   { font: 20px Ahem; }
+  .spacerChild::before {
+    content: '';
+    display: block;
+    background: brown;
+    height: 10px;
+    width: 10px;
+  }
+  .justPadding {
+    /* Empty div with 5px padding on each side */
+    padding: 5px;
+    background: cyan;
+  }
+  canvas { background: fuchsia }
+  </style>
+</head>
+<body>
+<!-- Flex items have unspecified size properties: -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas height="20"></canvas>
+</div>
+
+<!-- Various specified main-size values, in testcase
+     (removed here in reference case, because they shouldn't affect sizing): -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas height="20"></canvas>
+</div>
+
+<!-- Various specified cross-size values (should be honored): -->
+<div class="container">
+  <div class="smallText"    style="width: 0px">a b</div>
+  <div class="bigText"      style="width: 40px">c</div>
+  <div class="spacerChild"  style="width: 20px"></div>
+  <div class="justPadding"  style="width: 10px"></div>
+  <canvas height="20"       style="width: 8px"></canvas>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002a.html
new file mode 100644
index 0000000..481a3f2
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002a.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing "flex-basis: content" in a column-oriented flex container
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#propdef-flex-basis">
+  <link rel="match" href="flexbox-flex-basis-content-002-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-right: 2em;
+    width: 50px;
+    height: 200px;
+    float: left;
+  }
+
+  .container > * {
+    /* All flex items have "flex-basis: content" (and zero flex-shrink and
+       min-main-size, to avoid any influence from those). */
+    flex-basis: content;
+    flex-shrink: 0;
+    min-height: 0;
+    border: 2px solid teal;
+  }
+
+  .smallText { font: 10px Ahem; }
+  .bigText   { font: 20px Ahem; }
+  .spacerChild::before {
+    content: '';
+    display: block;
+    background: brown;
+    height: 10px;
+    width: 10px;
+  }
+  .justPadding {
+    /* Empty div with 5px padding on each side */
+    padding: 5px;
+    background: cyan;
+  }
+  canvas { background: fuchsia }
+  </style>
+</head>
+<body>
+<!-- Flex items have unspecified size properties: -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas height="20"></canvas>
+</div>
+
+<!-- Various specified main-size values (should be ignored): -->
+<div class="container">
+  <div class="smallText"    style="height: 0px">a b</div>
+  <div class="bigText"      style="height: 40px">c</div>
+  <div class="spacerChild"  style="height: 20px"></div>
+  <div class="justPadding"  style="height: 10px"></div>
+  <canvas height="20"       style="height: 8px"></canvas>
+</div>
+
+<!-- Various specified cross-size values (should be honored): -->
+<div class="container">
+  <div class="smallText"    style="width: 0px">a b</div>
+  <div class="bigText"      style="width: 40px">c</div>
+  <div class="spacerChild"  style="width: 20px"></div>
+  <div class="justPadding"  style="width: 10px"></div>
+  <canvas height="20"       style="width: 8px"></canvas>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002b.html
new file mode 100644
index 0000000..694e672
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002b.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing "flex-basis: content" (set via the "flex" shorthand)
+    in a column-oriented flex container.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#propdef-flex-basis">
+  <link rel="match" href="flexbox-flex-basis-content-002-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-right: 2em;
+    width: 50px;
+    height: 200px;
+    float: left;
+  }
+
+  .container > * {
+    /* All flex items have "flex-basis: content" (and zero flex-shrink and
+       min-main-size, to avoid any influence from those). */
+    flex: 0 0 content;
+    min-height: 0;
+    border: 2px solid teal;
+  }
+
+  .smallText { font: 10px Ahem; }
+  .bigText   { font: 20px Ahem; }
+  .spacerChild::before {
+    content: '';
+    display: block;
+    background: brown;
+    height: 10px;
+    width: 10px;
+  }
+  .justPadding {
+    /* Empty div with 5px padding on each side */
+    padding: 5px;
+    background: cyan;
+  }
+  canvas { background: fuchsia }
+  </style>
+</head>
+<body>
+<!-- Flex items have unspecified size properties: -->
+<div class="container">
+  <div class="smallText">a b</div>
+  <div class="bigText">c</div>
+  <div class="spacerChild"></div>
+  <div class="justPadding"></div>
+  <canvas height="20"></canvas>
+</div>
+
+<!-- Various specified main-size values (should be ignored): -->
+<div class="container">
+  <div class="smallText"    style="height: 0px">a b</div>
+  <div class="bigText"      style="height: 40px">c</div>
+  <div class="spacerChild"  style="height: 20px"></div>
+  <div class="justPadding"  style="height: 10px"></div>
+  <canvas height="20"       style="height: 8px"></canvas>
+</div>
+
+<!-- Various specified cross-size values (should be honored): -->
+<div class="container">
+  <div class="smallText"    style="width: 0px">a b</div>
+  <div class="bigText"      style="width: 40px">c</div>
+  <div class="spacerChild"  style="width: 20px"></div>
+  <div class="justPadding"  style="width: 10px"></div>
+  <canvas height="20"       style="width: 8px"></canvas>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003-ref.html
new file mode 100644
index 0000000..63ce9d7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003-ref.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <style>
+  .container {
+    clear: both; /* In this reference case, we use floats instead of
+                    flex items (see below), so the container just
+                    needs to reset the float state for each example. */
+  }
+
+  .item {
+    border: 2px solid teal;
+    float: left; /* Use floated elements as a reference for (hopefully)
+                    max-content sized flex items in testcase. */
+  }
+  ib {
+    display: inline-block;
+    background: blue;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  float {
+    float: left;
+    background: fuchsia;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  canvas {
+    background: brown;
+    border: 1px solid gray;
+  }
+  .innerFlex {
+    display: flex;
+  }
+  innerItem {
+    background: salmon;
+    border: 1px solid gray;
+    height: 10px;
+    width: 15px;
+    flex: none;
+  }
+  </style>
+</head>
+<body>
+<!-- In testcase, flex item has several inline-blocks
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item"><ib></ib><ib></ib><ib></ib></div>
+</div>
+
+<!-- In testcase, flex item has several floats: -->
+<div class="container">
+  <div class="item">
+    <float></float>
+    <float></float>
+    <float></float>
+  </div>
+</div>
+
+<!-- In testcase, flex item has several inline replaced elements:
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item">
+    <canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas>
+  </div>
+</div>
+
+<!-- In testcase, flex item *is* a replaced element: -->
+<div class="container">
+  <canvas class="item" width="25" height="10"></canvas>
+</div>
+
+<!-- In testcase, flex item is itself a flex container: -->
+<div class="container">
+  <div class="item innerFlex">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+<!-- In testcase, flex item is itself a multi-line flex container: -->
+<div class="container">
+  <div class="item innerFlex" style="flex-wrap: wrap">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003a.html
new file mode 100644
index 0000000..83dbae0
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003a.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing that explicit "flex-basis: content" is treated as
+    "max-content" when calculating flex base size
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-base-size">
+  <link rel="match" href="flexbox-flex-basis-content-003-ref.html">
+  <style>
+  .container {
+    display: flex;
+    /* flex container has an extremely-constrained width (and items will
+       overflow horizontally). This is intentional, as part of stress-testing
+       item sizing. */
+    width: 1px;
+  }
+
+  .item {
+    /* We give all flex items "flex-basis: content".
+       We also give them zero flex-grow, flex-shrink, and min-main-size, so
+       that the flex base size entirely determines the flex item's size. */
+    flex: 0 0 content;
+    min-width: 0;
+    border: 2px solid teal;
+  }
+  ib {
+    display: inline-block;
+    background: blue;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  float {
+    float: left;
+    background: fuchsia;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  canvas {
+    background: brown;
+    border: 1px solid gray;
+  }
+  .innerFlex {
+    display: flex;
+  }
+  innerItem {
+    background: salmon;
+    border: 1px solid gray;
+    height: 10px;
+    width: 15px;
+    flex: none;
+  }
+  </style>
+</head>
+<body>
+<!-- The idea of this test is to be sure the UA is using the "max-content" size
+     (and not e.g. the "fit-content size") when resolving the flex base size
+     inside each flex container.  To differentiate between max-content and
+     other intrinsic size possibilities (min-content/fit-content), we:
+       - use flex items with a large difference between its min-content size &
+       its max-content size (e.g. wrappable content).
+       - use a very small container (to compress the size, if the UA incorrectly
+       allows the size to be influenced by the container size).
+-->
+
+<!-- Flex item has several inline-blocks
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item"><ib></ib><ib></ib><ib></ib></div>
+</div>
+
+<!-- Flex item has several floats: -->
+<div class="container">
+  <div class="item">
+    <float></float>
+    <float></float>
+    <float></float>
+  </div>
+</div>
+
+<!-- Flex item has several inline replaced elements:
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item">
+    <canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas>
+  </div>
+</div>
+
+<!-- Flex item *is* a replaced element: -->
+<div class="container">
+  <canvas class="item" width="25" height="10"></canvas>
+</div>
+
+<!-- Flex item is itself a flex container: -->
+<div class="container">
+  <div class="item innerFlex">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+<!-- Flex item is itself a multi-line flex container: -->
+<div class="container">
+  <div class="item innerFlex" style="flex-wrap: wrap">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003b.html
new file mode 100644
index 0000000..a81403c
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003b.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing that used "flex-basis: content" is treated as
+    "max-content" when calculating flex base size
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-base-size">
+  <link rel="match" href="flexbox-flex-basis-content-003-ref.html">
+  <style>
+  .container {
+    display: flex;
+    /* flex container has an extremely-constrained width (and items will
+       overflow horizontally). This is intentional, as part of stress-testing
+       item sizing. */
+    width: 1px;
+  }
+
+  .item {
+    /* We give all flex items a used "flex-basis" of "content"
+       (from "flex-basis:auto" and default "width:auto").
+       We also give them zero flex-grow, flex-shrink, and min-main-size, so
+       that the flex base size entirely determines the flex item's size. */
+    flex: 0 0 auto;
+    min-width: 0;
+    border: 2px solid teal;
+  }
+  ib {
+    display: inline-block;
+    background: blue;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  float {
+    float: left;
+    background: fuchsia;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  canvas {
+    background: brown;
+    border: 1px solid gray;
+  }
+  .innerFlex {
+    display: flex;
+  }
+  innerItem {
+    background: salmon;
+    border: 1px solid gray;
+    height: 10px;
+    width: 15px;
+    flex: none;
+  }
+  </style>
+</head>
+<body>
+<!-- The idea of this test is to be sure the UA is using the "max-content" size
+     (and not e.g. the "fit-content size") when resolving the flex base size
+     inside each flex container.  To differentiate between max-content and
+     other intrinsic size possibilities (min-content/fit-content), we:
+       - use flex items with a large difference between its min-content size &
+       its max-content size (e.g. wrappable content).
+       - use a very small container (to compress the size, if the UA incorrectly
+       allows the size to be influenced by the container size).
+-->
+
+<!-- Flex item has several inline-blocks
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item"><ib></ib><ib></ib><ib></ib></div>
+</div>
+
+<!-- Flex item has several floats: -->
+<div class="container">
+  <div class="item">
+    <float></float>
+    <float></float>
+    <float></float>
+  </div>
+</div>
+
+<!-- Flex item has several inline replaced elements:
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item">
+    <canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas>
+  </div>
+</div>
+
+<!-- Flex item *is* a replaced element: -->
+<div class="container">
+  <canvas class="item" width="25" height="10"></canvas>
+</div>
+
+<!-- Flex item is itself a flex container: -->
+<div class="container">
+  <div class="item innerFlex">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+<!-- Flex item is itself a multi-line flex container: -->
+<div class="container">
+  <div class="item innerFlex" style="flex-wrap: wrap">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004-ref.html
new file mode 100644
index 0000000..7da4de7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004-ref.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <style>
+  .container {
+    clear: both; /* In this reference case, we use floats instead of
+                    flex items (see below), so the container just
+                    needs to reset the float state for each example. */
+    height: 50px;
+  }
+
+  .item {
+    border: 2px solid teal;
+    float: left; /* Use floated elements as a reference for (hopefully)
+                    max-content sized flex items in testcase. */
+  }
+  ib {
+    display: inline-block;
+    background: blue;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  float {
+    float: left;
+    background: fuchsia;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  canvas {
+    background: brown;
+    border: 1px solid gray;
+  }
+  .innerFlex {
+    display: flex;
+    flex-direction: column;
+  }
+  innerItem {
+    background: salmon;
+    border: 1px solid gray;
+    height: 10px;
+    width: 15px;
+    flex: none;
+  }
+  </style>
+</head>
+<body>
+<!-- In testcase, flex item has several inline-blocks
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item"><ib></ib><ib></ib><ib></ib></div>
+</div>
+
+<!-- In testcase, flex item has several floats: -->
+<div class="container">
+  <div class="item">
+    <float></float>
+    <float></float>
+    <float></float>
+  </div>
+</div>
+
+<!-- In testcase, flex item has several inline replaced elements:
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item">
+    <canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas>
+  </div>
+</div>
+
+<!-- In testcase, flex item *is* a replaced element: -->
+<div class="container">
+  <canvas class="item" width="25" height="10"></canvas>
+</div>
+
+<!-- In testcase, flex item is itself a flex container: -->
+<div class="container">
+  <div class="item innerFlex">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+<!-- In testcase, flex item is itself a multi-line flex container: -->
+<div class="container">
+  <div class="item innerFlex" style="flex-wrap: wrap">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004a.html
new file mode 100644
index 0000000..65a86b5
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004a.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing that explicit "flex-basis: content" is treated as
+    "max-content" when calculating flex base size
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-base-size">
+  <link rel="match" href="flexbox-flex-basis-content-004-ref.html">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    /* flex container has an extremely-constrained height (and items will
+       overflow vertically). This is intentional, as part of stress-testing
+       item sizing. We add a large margin-bottom so that overflowing
+       items don't overlap between examples. */
+    height: 1px;
+    margin-bottom: 49px;
+  }
+
+  .item {
+    /* We give all flex items "flex-basis: content".
+       We also give them zero flex-grow, flex-shrink, and min-main-size, so
+       that the flex base size entirely determines the flex item's size. */
+    flex: 0 0 content;
+    min-height: 0;
+    border: 2px solid teal;
+  }
+  ib {
+    display: inline-block;
+    background: blue;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  float {
+    float: left;
+    background: fuchsia;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  canvas {
+    background: brown;
+    border: 1px solid gray;
+  }
+  .innerFlex {
+    display: flex;
+    flex-direction: column;
+  }
+  innerItem {
+    background: salmon;
+    border: 1px solid gray;
+    height: 10px;
+    width: 15px;
+    flex: none;
+  }
+  </style>
+</head>
+<body>
+<!-- This test exists for symmetry with the previous set of tests
+     (flexbox-flex-basis-content-003*). Those previous tests check how
+     "flex-basis:content" is resolved to a flex base size, in the inline axis,
+     when the container's size is constrained in that axis. This test does the
+     same, but for the *block* axis, using flex-direction:column. As with the
+     previous set of tests, the expectation here is that we should use the
+     item's max-content size as its flex base size. Note that there's a bit
+     less subtlety here because intrinsic sizes (min-content, max-content) are
+     typically all the same in the block axis.
+-->
+
+<!-- Flex item has several inline-blocks
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item"><ib></ib><ib></ib><ib></ib></div>
+</div>
+
+<!-- Flex item has several floats: -->
+<div class="container">
+  <div class="item">
+    <float></float>
+    <float></float>
+    <float></float>
+  </div>
+</div>
+
+<!-- Flex item has several inline replaced elements:
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item">
+    <canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas>
+  </div>
+</div>
+
+<!-- Flex item *is* a replaced element: -->
+<div class="container">
+  <canvas class="item" width="25" height="10"></canvas>
+</div>
+
+<!-- Flex item is itself a flex container: -->
+<div class="container">
+  <div class="item innerFlex">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+<!-- Flex item is itself a multi-line flex container: -->
+<div class="container">
+  <div class="item innerFlex" style="flex-wrap: wrap">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004b.html
new file mode 100644
index 0000000..a686f1a
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004b.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing that used "flex-basis: content" is treated as
+    "max-content" when calculating flex base size
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-base-size">
+  <link rel="match" href="flexbox-flex-basis-content-004-ref.html">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    /* flex container has an extremely-constrained height (and items will
+       overflow vertically). This is intentional, as part of stress-testing
+       item sizing. We add a large margin-bottom so that overflowing
+       items don't overlap between examples. */
+    height: 1px;
+    margin-bottom: 49px;
+  }
+
+  .item {
+    /* We give all flex items a used "flex-basis" of "content"
+       (from "flex-basis:auto" and default "width:auto").
+       We also give them zero flex-grow, flex-shrink, and min-main-size, so
+       that the flex base size entirely determines the flex item's size. */
+    flex: 0 0 auto;
+    min-height: 0;
+    border: 2px solid teal;
+  }
+  ib {
+    display: inline-block;
+    background: blue;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  float {
+    float: left;
+    background: fuchsia;
+    border: 1px solid gray;
+    width: 15px;
+    height: 10px;
+  }
+  canvas {
+    background: brown;
+    border: 1px solid gray;
+  }
+  .innerFlex {
+    display: flex;
+    flex-direction: column;
+  }
+  innerItem {
+    background: salmon;
+    border: 1px solid gray;
+    height: 10px;
+    width: 15px;
+    flex: none;
+  }
+  </style>
+</head>
+<body>
+<!-- This test exists for symmetry with the previous set of tests
+     (flexbox-flex-basis-content-003*). Those previous tests check how
+     "flex-basis:content" is resolved to a flex base size, in the inline axis,
+     when the container's size is constrained in that axis. This test does the
+     same, but for the *block* axis, using flex-direction:column. As with the
+     previous set of tests, the expectation here is that we should use the
+     item's max-content size as its flex base size. Note that there's a bit
+     less subtlety here because intrinsic sizes (min-content, max-content) are
+     typically all the same in the block axis.
+-->
+
+<!-- Flex item has several inline-blocks
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item"><ib></ib><ib></ib><ib></ib></div>
+</div>
+
+<!-- Flex item has several floats: -->
+<div class="container">
+  <div class="item">
+    <float></float>
+    <float></float>
+    <float></float>
+  </div>
+</div>
+
+<!-- Flex item has several inline replaced elements:
+     (no spaces, to avoid any text-layout dependency): -->
+<div class="container">
+  <div class="item">
+    <canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas
+    ><canvas width="15" height="10"></canvas>
+  </div>
+</div>
+
+<!-- Flex item *is* a replaced element: -->
+<div class="container">
+  <canvas class="item" width="25" height="10"></canvas>
+</div>
+
+<!-- Flex item is itself a flex container: -->
+<div class="container">
+  <div class="item innerFlex">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+<!-- Flex item is itself a multi-line flex container: -->
+<div class="container">
+  <div class="item innerFlex" style="flex-wrap: wrap">
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+    <innerItem></innerItem>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-001v.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-001v.html
new file mode 100644
index 0000000..4cc910b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-001v.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>
+      CSS Test: Testing how explicit main-size & cross-size constraints
+      influence sizing on non-stretched flex item w/ intrinsic ratio
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-main-size">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size">
+    <link rel="match" href="flexbox-intrinsic-ratio-001-ref.html">
+    <style>
+      .flexbox {
+        display: flex;
+        flex-direction: row;
+        border: 1px solid black;
+        margin: 0 2px 2px 0; /* (Just for spacing things out, visually) */
+        width: 40px;
+        height: 40px;
+
+        justify-content: flex-start;
+        align-items: flex-start;
+
+        float: left; /* For testing in "rows" */
+      }
+      br { clear: both; }
+
+      .flexbox > * {
+        writing-mode: vertical-lr;
+
+        /* Disable "min-width:auto"/"min-height:auto" to focus purely on
+           later channels of influence. */
+        min-width: 0;
+        min-height: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- NOTE: solidblue.png has an intrinsic size of 16px by 16px. -->
+
+    <!-- Row 1: no special sizing: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png">
+    </div>
+    <br>
+
+    <!-- Row 2: Specified main-size, cross-size, or flex-basis: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="flex: 0 0 30px">
+    </div>
+    <br>
+
+    <!-- Row 3: min main-size OR min cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 4: max main-size OR max cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              max-height: 6px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 6px;
+                                              max-height: 10px">
+    </div>
+    <br>
+
+    <!-- Row 5: min main-size vs. max cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 6: min|max main-size vs. explicit cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 10px;
+                                              min-height: 30px">
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-002v.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-002v.html
new file mode 100644
index 0000000..0df92f9
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-002v.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>
+      CSS Test: Testing how explicit main-size & cross-size constraints
+      influence sizing on non-stretched flex item w/ intrinsic ratio
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-main-size">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size">
+    <link rel="match" href="flexbox-intrinsic-ratio-001-ref.html">
+    <style>
+      .flexbox {
+        display: flex;
+        flex-direction: column;
+        border: 1px solid black;
+        margin: 0 2px 2px 0; /* (Just for spacing things out, visually) */
+        width: 40px;
+        height: 40px;
+
+        justify-content: flex-start;
+        align-items: flex-start;
+
+        float: left; /* For testing in "rows" */
+      }
+      br { clear: both; }
+
+      .flexbox > * {
+        writing-mode: vertical-lr;
+
+        /* Disable "min-width:auto"/"min-height:auto" to focus purely on
+           later channels of influence. */
+        min-width: 0;
+        min-height: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- NOTE: solidblue.png has an intrinsic size of 16px by 16px. -->
+
+    <!-- Row 1: no special sizing: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png">
+    </div>
+    <br>
+
+    <!-- Row 2: Specified main-size, cross-size, or flex-basis: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="flex: 0 0 30px">
+    </div>
+    <br>
+
+    <!-- Row 3: min main-size OR min cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 4: max main-size OR max cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              max-height: 6px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 6px;
+                                              max-height: 10px">
+    </div>
+    <br>
+
+    <!-- Row 5: min main-size vs. max cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 6: min|max main-size vs. explicit cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 10px;
+                                              min-height: 30px">
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-003v.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-003v.html
new file mode 100644
index 0000000..cb9275f
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-003v.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>
+      CSS Test: Testing how explicit main-size & cross-size constraints
+      influence sizing on stretched flex item w/ intrinsic ratio
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-main-size">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size">
+    <link rel="match" href="flexbox-intrinsic-ratio-003-ref.html">
+    <style>
+      .flexbox {
+        display: flex;
+        flex-direction: row;
+        border: 1px solid black;
+        margin: 0 2px 2px 0; /* (Just for spacing things out, visually) */
+        width: 40px;
+        height: 40px;
+
+        justify-content: flex-start;
+        align-items: stretch;
+
+        float: left; /* For testing in "rows" */
+      }
+      br { clear: both; }
+
+      .flexbox > * {
+        writing-mode: vertical-rl;
+
+        /* Disable "min-width:auto"/"min-height:auto" to focus purely on
+           later channels of influence. */
+        min-width: 0;
+        min-height: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- NOTE: solidblue.png has an intrinsic size of 16px by 16px. -->
+
+    <!-- Row 1: no special sizing: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png">
+    </div>
+    <br>
+
+    <!-- Row 2: Specified main-size, cross-size, or flex-basis: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="flex: 0 0 30px">
+    </div>
+    <br>
+
+    <!-- Row 3: min main-size OR min cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 4: max main-size OR max cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              max-height: 6px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 6px;
+                                              max-height: 10px">
+    </div>
+    <br>
+
+    <!-- Row 5: min main-size vs. max cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 6: min|max main-size vs. explicit cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 10px;
+                                              min-height: 30px">
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-004v.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-004v.html
new file mode 100644
index 0000000..01c5271
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-004v.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>
+      CSS Test: Testing how explicit main-size & cross-size constraints
+      influence sizing on stretched flex item w/ intrinsic ratio
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-main-size">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size">
+    <link rel="match" href="flexbox-intrinsic-ratio-004-ref.html">
+    <style>
+      .flexbox {
+        display: flex;
+        flex-direction: column;
+        border: 1px solid black;
+        margin: 0 2px 2px 0; /* (Just for spacing things out, visually) */
+        width: 40px;
+        height: 40px;
+
+        justify-content: flex-start;
+        align-items: stretch;
+
+        float: left; /* For testing in "rows" */
+      }
+      br { clear: both; }
+
+      .flexbox > * {
+        writing-mode: vertical-rl;
+
+        /* Disable "min-width:auto"/"min-height:auto" to focus purely on
+           later channels of influence. */
+        min-width: 0;
+        min-height: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- NOTE: solidblue.png has an intrinsic size of 16px by 16px. -->
+
+    <!-- Row 1: no special sizing: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png">
+    </div>
+    <br>
+
+    <!-- Row 2: Specified main-size, cross-size, or flex-basis: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="flex: 0 0 30px">
+    </div>
+    <br>
+
+    <!-- Row 3: min main-size OR min cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 4: max main-size OR max cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              max-height: 6px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 6px;
+                                              max-height: 10px">
+    </div>
+    <br>
+
+    <!-- Row 5: min main-size vs. max cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 6: min|max main-size vs. explicit cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 10px;
+                                              min-height: 30px">
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-005v.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-005v.html
new file mode 100644
index 0000000..ed1dcac
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-005v.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>
+      CSS Test: Testing how explicit main-size & cross-size constraints
+      influence sizing on non-stretched flexible flex item w/ intrinsic ratio
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-main-size">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size">
+    <link rel="match" href="flexbox-intrinsic-ratio-005-ref.html">
+    <style>
+      .flexbox {
+        display: flex;
+        flex-direction: row;
+        border: 1px solid black;
+        margin: 0 2px 2px 0; /* (Just for spacing things out, visually) */
+        width: 40px;
+        height: 40px;
+
+        justify-content: flex-start;
+        align-items: flex-start;
+
+        float: left; /* For testing in "rows" */
+      }
+      br { clear: both; }
+
+      .flexbox > * {
+        flex: 1;
+        writing-mode: vertical-lr;
+
+        /* Disable "min-width:auto"/"min-height:auto" to focus purely on
+           later channels of influence. */
+        min-width: 0;
+        min-height: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- NOTE: solidblue.png has an intrinsic size of 16px by 16px. -->
+
+    <!-- Row 1: no special sizing: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png">
+    </div>
+    <br>
+
+    <!-- Row 2: Specified main-size, cross-size, or flex-basis: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="flex: 1 1 30px">
+    </div>
+    <br>
+
+    <!-- Row 3: min main-size OR min cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 4: max main-size OR max cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              max-height: 6px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 6px;
+                                              max-height: 10px">
+    </div>
+    <br>
+
+    <!-- Row 5: min main-size vs. max cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 6: min|max main-size vs. explicit cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 10px;
+                                              min-height: 30px">
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-006v.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-006v.html
new file mode 100644
index 0000000..8c12fad
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-006v.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>
+      CSS Test: Testing how explicit main-size & cross-size constraints
+      influence sizing on non-stretched flexible flex item w/ intrinsic ratio
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-main-size">
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size">
+    <link rel="match" href="flexbox-intrinsic-ratio-006-ref.html">
+    <style>
+      .flexbox {
+        display: flex;
+        flex-direction: column;
+        border: 1px solid black;
+        margin: 0 2px 2px 0; /* (Just for spacing things out, visually) */
+        width: 40px;
+        height: 40px;
+
+        justify-content: flex-start;
+        align-items: flex-start;
+
+        float: left; /* For testing in "rows" */
+      }
+      br { clear: both; }
+
+      .flexbox > * {
+        flex: 1;
+        writing-mode: vertical-lr;
+
+        /* Disable "min-width:auto"/"min-height:auto" to focus purely on
+           later channels of influence. */
+        min-width: 0;
+        min-height: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- NOTE: solidblue.png has an intrinsic size of 16px by 16px. -->
+
+    <!-- Row 1: no special sizing: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png">
+    </div>
+    <br>
+
+    <!-- Row 2: Specified main-size, cross-size, or flex-basis: -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="flex: 1 1 30px">
+    </div>
+    <br>
+
+    <!-- Row 3: min main-size OR min cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              min-height: 34px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 34px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 4: max main-size OR max cross-size, or both -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              max-height: 6px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 6px;
+                                              max-height: 10px">
+    </div>
+    <br>
+
+    <!-- Row 5: min main-size vs. max cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              min-height: 30px">
+    </div>
+    <br>
+
+    <!-- Row 6: min|max main-size vs. explicit cross-size, & vice versa -->
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="min-width: 30px;
+                                              height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 30px;
+                                              max-height: 10px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="max-width: 10px;
+                                              height: 30px">
+    </div>
+    <div class="flexbox">
+      <img src="support/solidblue.png" style="width: 10px;
+                                              min-height: 30px">
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-002v.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-002v.xhtml
new file mode 100644
index 0000000..a02e340
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-002v.xhtml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!-- Testcase with margin/border/padding on flex items
+     and with various writing-modes on the items. -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>
+      CSS Test: Testing margins, borders, and padding on flex items in a horizontal flex container
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"/>
+    <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#layout-algorithm"/>
+    <link rel="match" href="flexbox-mbp-horiz-002-ref.xhtml"/>
+    <style>
+      div { height: 100px; border: 0; }
+      div.flexbox {
+        width: 200px;
+        font-size: 10px;
+        display: flex;
+      }
+      div.a {
+        flex: 1 0 9px;
+        background: lightgreen;
+        margin-left: 1px;
+        margin-right: 3px;
+        border-style: dotted;
+        border-left-width: 2px;
+        border-right-width: 4px;
+        padding-left: 5px;
+        padding-right: 6px;
+        writing-mode: vertical-lr;
+      }
+      div.b {
+        flex: 2 0 1px;
+        background: yellow;
+        margin-left: 2px;
+        margin-right: 4px;
+        border-style: dashed;
+        border-left-width: 7px;
+        border-right-width: 3px;
+        padding-left: 1px;
+        padding-right: 2px;
+        writing-mode: vertical-rl;
+      }
+      div.c {
+        flex: 3 0 40px;
+        background: orange;
+        writing-mode: sideways-lr;
+      }
+      div.flexNone {
+        flex: none;
+        background: pink;
+        writing-mode: vertical-lr;
+      }
+      div.flexBasis {
+        flex: 0 0 20px;
+        background: gray;
+        writing-mode: sideways-rl;
+      }
+      div.spacer {
+        width: 15px;
+        height: 15px;
+        background: purple;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="flexbox"><div class="a"></div><div class="b"/></div>
+    <div class="flexbox"><div class="a"/><div class="c"/></div>
+    <div class="flexbox"><div class="a"/>
+      <div class="flexNone"><div class="spacer"/></div>
+    </div>
+    <div class="flexbox"><div class="b"/><div class="c"/></div>
+    <div class="flexbox"><div class="b"/>
+      <div class="flexNone"><div class="spacer"/><div class="spacer"/></div>
+    </div>
+
+    <div class="flexbox">
+      <div class="a"/><div class="b"/><div class="flexBasis"/><div class="c"/>
+    </div>
+  </body>
+</html>
+
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-003v.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-003v.xhtml
new file mode 100644
index 0000000..8baef30
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-003v.xhtml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!-- Testcase with border/padding on a flex container and on its children -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>
+      CSS Test: Testing borders and padding on a horizontal flex container and its flex items
+      (with a vertical writing-mode on the flex items).
+    </title>
+    <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"/>
+    <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#layout-algorithm"/>
+    <link rel="match" href="flexbox-mbp-horiz-003-ref.xhtml"/>
+    <style>
+      div { height: 20px; border: 0; }
+      div.flexbox {
+        width: 200px;
+        display: flex;
+        margin-bottom: 2px;
+      }
+
+      <!-- customizations for flex container border/padding -->
+      .borderA {
+        border-style: dashed;
+        border-color: purple;
+        border-top-width: 6px;
+        border-right-width: 4px;
+        border-bottom-width: 2px;
+        border-left-width: 8px;
+      }
+
+      .borderB {
+        border-style: dashed;
+        border-color: purple;
+        border-top-width: 4px;
+        border-right-width: 5px;
+        border-bottom-width: 6px;
+        border-left-width: 7px;
+      }
+
+      .paddingA {
+        padding: 4px 3px 2px 1px;
+      }
+
+      .paddingB {
+        padding: 8px 11px 14px 17px;
+      }
+
+      div.child1 {
+        flex: 1 0 24px;
+        background: lightgreen;
+        border-style: dotted;
+        border-left-width: 2px;
+        border-right-width: 4px;
+        writing-mode: vertical-rl;
+      }
+      div.child2 {
+        flex: 2 0 10px;
+        background: yellow;
+        border-style: dashed;
+        border-left-width: 7px;
+        border-right-width: 3px;
+        writing-mode: vertical-lr;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="flexbox borderA"
+         ><div class="child1"/><div class="child2"/></div>
+    <div class="flexbox borderA paddingA"
+         ><div class="child1"/><div class="child2"/></div>
+    <div class="flexbox borderA paddingB"
+         ><div class="child1"/><div class="child2"/></div>
+    <div class="flexbox borderB"
+         ><div class="child1"/><div class="child2"/></div>
+    <div class="flexbox borderB paddingA"
+         ><div class="child1"/><div class="child2"/></div>
+    <div class="flexbox borderB paddingB"
+         ><div class="child1"/><div class="child2"/></div>
+    <div class="flexbox paddingA"
+         ><div class="child1"/><div class="child2"/></div>
+    <div class="flexbox paddingB"
+         ><div class="child1"/><div class="child2"/></div>
+  </body>
+</html>
+
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004-ref.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004-ref.xhtml
index 00db482..245e6f9 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004-ref.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004-ref.xhtml
@@ -4,8 +4,7 @@
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <!-- Reference case - identical to the testcase, but with with the flex items'
-     vertical margin and padding values set to 0 by default, and then set to
-     specific pixel values for the items that have a 50px percent-basis.
+     margin and padding values set to explicit pixel values.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
@@ -21,27 +20,11 @@
       }
       div.height50 { height: 50px; }
 
-      .marginA  { margin:  0  8% 0  4%; }
-      .marginB  { margin:  0 10% 0 14%; }
-      .paddingA { padding: 0  6% 0  2%; }
-      .paddingB { padding: 0  8% 0 12%; }
+      .marginA  { margin:  20px 16px 12px  8px; }
+      .marginB  { margin:  16px 20px 24px 28px; }
+      .paddingA { padding: 16px 12px  8px  4px; }
+      .paddingB { padding: 12px 16px 20px 24px; }
 
-      div.height50 > .marginA {
-        margin-top: 5px;
-        margin-bottom: 3px;
-      }
-      div.height50 > .marginB {
-        margin-top: 4px;
-        margin-bottom: 6px;
-      }
-      div.height50 > .paddingA {
-        padding-top: 4px;
-        padding-bottom: 2px;
-      }
-      div.height50 > .paddingB {
-        padding-top: 3px;
-        padding-bottom: 5px;
-      }
 
       div.child1 {
         flex: none;
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004.xhtml b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004.xhtml
index 545e54f..b513253 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004.xhtml
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-mbp-horiz-004.xhtml
@@ -3,19 +3,17 @@
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
-<!-- Testcase with percent-valued padding and/or margin on flex items. The spec
-     says that percentage values on padding/margin-top and -bottom should be
-     resolved against the flex container's height (not its width, as would
-     be the case in a block).
+<!-- Testcase with percent-valued padding and/or margin on flex items.
+     The spec allows these to be resolved against the flex container's
+     inline size (regardless of which axis the percent padding/margin is in).
 -->
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>CSS Test: Testing percent-valued padding and margin on flex items</title>
     <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com"/>
-    <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#layout-algorithm"/>
+    <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#item-margins"/>
     <link rel="match" href="flexbox-mbp-horiz-004-ref.xhtml"/>
     <style>
-      div { border: 0; }
       div.flexbox {
         width: 200px;
         display: flex;
@@ -52,8 +50,9 @@
     </style>
   </head>
   <body>
-    <!-- Flex container is auto-height - vertical margin and padding should
-         resolve to 0, since they don't have anything to resolve % against. -->
+    <!-- Flex container is auto-height - this shouldn't impact percent
+         margin/padding resolution, since they resolve against container's
+         inline-size, i.e. its width in this case. -->
     <div class="flexbox"
          ><div class="child1 paddingA"><div class="filler"/></div
          ><div class="child2 paddingB"><div class="filler"/></div
@@ -61,8 +60,9 @@
          ><div class="child2 marginB"></div
     ></div>
 
-    <!-- Flex container has height: 50px - vertical margin and padding should
-         resolve % values against that. -->
+    <!-- Flex container has height: 50px - again, this shouldn't impact percent
+         margin/padding resolution, since they resolve against container's
+         inline-size, i.e. its width in this case. -->
     <div class="flexbox height50"
          ><div class="child1 paddingA"><div class="filler"/></div
          ><div class="child2 paddingB"><div class="filler"/></div
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-paint-ordering-003-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-paint-ordering-003-ref.html
new file mode 100644
index 0000000..5eadc30
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-paint-ordering-003-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>CSS Reftest Reference</title>
+ <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <style>
+    .limeSquare {
+      background: lime;
+      height: 100px; width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div class="limeSquare"></div>
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-paint-ordering-003.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-paint-ordering-003.html
new file mode 100644
index 0000000..1a5175c
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-paint-ordering-003.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Test: Testing that paint order isn't influenced
+         by "order" for absolutely positioned flex children</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#abspos-items">
+  <link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#painting">
+  <link rel="match" href="flexbox-paint-ordering-003-ref.html">
+  <style>
+    .container { display: flex; }
+    .absPosLowOrder {
+      position: absolute;
+      order: 5;
+      background: red;
+      height: 0;
+      width: 0;
+    }
+    .absPosHighOrder {
+      position: absolute;
+      order: 10;
+      height: 0;
+      width: 0;
+    }
+    .redBlock {
+      height: 100px;
+      width: 100px;
+      background: red;
+    }
+    .limeBlock {
+      height: 100px;
+      width: 100px;
+      background: lime;
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <!-- Note: The following elements will be superimposed.  If they weren't
+         positioned, then they'd be flex items, and their relative "order"
+         values would force the first one (with the red child) to paint on top.
+         But since they're absolutely positioned, they're not flex items &
+         "order" has no effect, and so the lime one should end up on top. -->
+    <div class="absPosHighOrder"><div class="redBlock"></div></div>
+    <div class="absPosLowOrder"><div class="limeBlock"></div></div>
+  </div>
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-010-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-010-ref.html
new file mode 100644
index 0000000..72765c5
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-010-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: block;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 150px;
+    width: 500px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+
+  .container > .hl, .container > .hr {
+    /* In the testcase, these items are stretched vertically
+       via the default "align-self:stretch" behavior, and because
+       they have a height of "auto".
+       (The rest of the items have a non-auto height from "inline-size"
+       and their vertical writing-mode, so those ones do not stretch.) */
+    height: 118px;
+  }
+
+  .container.hl > * { float: left; }
+  .container.hr > * { float: right; }
+
+  </style>
+</head>
+<body>
+
+<div class="container hl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container hl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container hr">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container hr">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-010.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-010.html
new file mode 100644
index 0000000..565a4e4
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-010.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing a mix of flex items with various values for
+    'writing-mode' / 'direction' in a horizontal row-oriented flex container.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-direction-property">
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#propdef-writing-mode">
+  <link rel="match" href="flexbox-writing-mode-010-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: row;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 150px;
+    width: 500px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+  </style>
+</head>
+<body>
+
+<div class="container hl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container hl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container hr">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container hr">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011-ref.html
new file mode 100644
index 0000000..ad241c3
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011-ref.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: block;
+    float: left;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 500px;
+    width: 150px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+
+    /* This really floats to top ('logical left'), since all the containers
+       have a vertical writing mode. */
+    float: left;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+
+
+  .container > .vl,     .container > .vr,
+  .container > .vl_rtl, .container > .vr_rtl {
+    /* In the testcase, these items are stretched horizontally
+       via the default "align-self:stretch" behavior, and because
+       they have a width of "auto".
+       (The rest of the items have a non-auto width from "inline-size"
+       and their horizontal writing-mode, so those ones do not stretch.) */
+    width: 126px;
+  }
+  </style>
+</head>
+<body>
+
+<div class="container vl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container vr">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vr">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011.html
new file mode 100644
index 0000000..223be96
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing a mix of flex items with various values for
+    'writing-mode' / 'direction' in a vertical row-oriented flex container.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-direction-property">
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#propdef-writing-mode">
+  <link rel="match" href="flexbox-writing-mode-011-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: row;
+    float: left;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 500px;
+    width: 150px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+  </style>
+</head>
+<body>
+
+<div class="container vl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container vr">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vr">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012-ref.html
new file mode 100644
index 0000000..a9bc4ab
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012-ref.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: block;
+    float: left;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 500px;
+    width: 150px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+
+    /* This really floats to top ('logical left'), since all the containers
+       have a vertical writing mode. */
+    float: right;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+
+
+  .container > .vl,     .container > .vr,
+  .container > .vl_rtl, .container > .vr_rtl {
+    /* In the testcase, these items are stretched horizontally
+       via the default "align-self:stretch" behavior, and because
+       they have a width of "auto".
+       (The rest of the items have a non-auto width from "inline-size"
+       and their horizontal writing-mode, so those ones do not stretch.) */
+    width: 126px;
+  }
+  </style>
+</head>
+<body>
+
+<div class="container vl_rtl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vl_rtl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container vr_rtl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vr_rtl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012.html
new file mode 100644
index 0000000..fe9052a
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing a mix of flex items with various values for
+    'writing-mode' / 'direction' in a vertical row-oriented flex container
+    with 'direction' flipped.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-direction-property">
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#propdef-writing-mode">
+  <link rel="match" href="flexbox-writing-mode-012-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: row;
+    float: left;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 500px;
+    width: 150px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+  </style>
+</head>
+<body>
+
+<div class="container vl_rtl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vl_rtl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container vr_rtl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vr_rtl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-013-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-013-ref.html
new file mode 100644
index 0000000..591f764
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-013-ref.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: block;
+    float: left;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 500px;
+    width: 150px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  nocollapse {
+    /* special element to disable margin-collapsing */
+    display: block;
+    overflow: hidden;
+    height: 0;
+  }
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+
+  .container > .vl,     .container > .vr,
+  .container > .vl_rtl, .container > .vr_rtl {
+    /* In the testcase, these items are stretched horizontally
+       via the default "align-self:stretch" behavior, and because
+       they have a width of "auto".
+       (The rest of the items have a non-auto width from "inline-size"
+       and their horizontal writing-mode, so those ones do not stretch.) */
+    width: 126px;
+  }
+  </style>
+</head>
+<body>
+
+<div class="container hl">
+  <span class="hl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hl big">p e</span>        <nocollapse></nocollapse>
+  <span class="hr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vl big">p e</span>        <nocollapse></nocollapse>
+</div>
+<div class="container hl">
+  <span class="vr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vl_rtl big">p e</span>    <nocollapse></nocollapse>
+  <span class="vr_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vr_rtl big">p e</span>    <nocollapse></nocollapse>
+</div>
+<div class="container hr">
+  <span class="hl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hl big">p e</span>        <nocollapse></nocollapse>
+  <span class="hr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vl big">p e</span>        <nocollapse></nocollapse>
+</div>
+<div class="container hr">
+  <span class="vr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vl_rtl big">p e</span>    <nocollapse></nocollapse>
+  <span class="vr_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vr_rtl big">p e</span>    <nocollapse></nocollapse>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-013.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-013.html
new file mode 100644
index 0000000..a1c7101
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-013.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing a mix of flex items with various values for
+    'writing-mode' / 'direction' in a horizontal column-oriented flex container.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-direction-property">
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#propdef-writing-mode">
+  <link rel="match" href="flexbox-writing-mode-013-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    float: left;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 500px;
+    width: 150px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+  </style>
+</head>
+<body>
+
+<div class="container hl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container hl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container hr">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container hr">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-014-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-014-ref.html
new file mode 100644
index 0000000..df61c52
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-014-ref.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: block;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 150px;
+    width: 500px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  nocollapse {
+    /* special element to disable margin-collapsing */
+    display: block;
+    overflow: hidden;
+    height: 0;
+  }
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+
+  .container > .hl, .container > .hr {
+    /* In the testcase, these items are stretched vertically
+       via the default "align-self:stretch" behavior, and because
+       they have a height of "auto".
+       (The rest of the items have a non-auto height from "inline-size"
+       and their vertical writing-mode, so those ones do not stretch.) */
+    height: 118px;
+  }
+  </style>
+</head>
+<body>
+
+<div class="container vl">
+  <span class="hl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hl big">p e</span>        <nocollapse></nocollapse>
+  <span class="hr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vl big">p e</span>        <nocollapse></nocollapse>
+</div>
+<div class="container vl">
+  <span class="vr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vl_rtl big">p e</span>    <nocollapse></nocollapse>
+  <span class="vr_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vr_rtl big">p e</span>    <nocollapse></nocollapse>
+</div>
+<div class="container vr">
+  <span class="hl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hl big">p e</span>        <nocollapse></nocollapse>
+  <span class="hr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vl big">p e</span>        <nocollapse></nocollapse>
+</div>
+<div class="container vr">
+  <span class="vr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vl_rtl big">p e</span>    <nocollapse></nocollapse>
+  <span class="vr_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vr_rtl big">p e</span>    <nocollapse></nocollapse>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-014.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-014.html
new file mode 100644
index 0000000..e28394f
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-014.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing a mix of flex items with various values for
+    'writing-mode' / 'direction' in a vertical column-oriented flex container.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-direction-property">
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#propdef-writing-mode">
+  <link rel="match" href="flexbox-writing-mode-014-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 150px;
+    width: 500px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+  </style>
+</head>
+<body>
+
+<div class="container vl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container vr">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vr">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-015-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-015-ref.html
new file mode 100644
index 0000000..eb35f9e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-015-ref.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: block;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 150px;
+    width: 500px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  nocollapse {
+    /* special element to disable margin-collapsing */
+    display: block;
+    overflow: hidden;
+    height: 0;
+  }
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+
+  .container > .hl, .container > .hr {
+    /* In the testcase, these items are stretched vertically
+       via the default "align-self:stretch" behavior, and because
+       they have a height of "auto".
+       (The rest of the items have a non-auto height from "inline-size"
+       and their vertical writing-mode, so those ones do not stretch.) */
+    height: 118px;
+  }
+  </style>
+</head>
+<body>
+
+<div class="container vl_rtl">
+  <span class="hl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hl big">p e</span>        <nocollapse></nocollapse>
+  <span class="hr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vl big">p e</span>        <nocollapse></nocollapse>
+</div>
+<div class="container vl_rtl">
+  <span class="vr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vl_rtl big">p e</span>    <nocollapse></nocollapse>
+  <span class="vr_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vr_rtl big">p e</span>    <nocollapse></nocollapse>
+</div>
+<div class="container vr_rtl">
+  <span class="hl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hl big">p e</span>        <nocollapse></nocollapse>
+  <span class="hr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="hr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vl big">p e</span>        <nocollapse></nocollapse>
+</div>
+<div class="container vr_rtl">
+  <span class="vr small">p b c</span>    <nocollapse></nocollapse>
+  <span class="vr big">p e</span>        <nocollapse></nocollapse>
+  <span class="vl_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vl_rtl big">p e</span>    <nocollapse></nocollapse>
+  <span class="vr_rtl small">p b c</span><nocollapse></nocollapse>
+  <span class="vr_rtl big">p e</span>    <nocollapse></nocollapse>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-015.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-015.html
new file mode 100644
index 0000000..6a6de33
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-015.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>
+    CSS Test: Testing a mix of flex items with various values for
+    'writing-mode' / 'direction' in a vertical column-oriented flex container
+    with 'direction' flipped.
+  </title>
+  <meta charset="utf-8">
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-direction-property">
+  <link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#propdef-writing-mode">
+  <link rel="match" href="flexbox-writing-mode-015-ref.html">
+  <link rel="stylesheet" type="text/css" href="support/ahem.css">
+  <style>
+  .container {
+    display: flex;
+    flex-direction: column;
+    border: 2px solid purple;
+    padding: 2px;
+    margin-bottom: 2em;
+    height: 150px;
+    width: 500px;
+  }
+
+  span {
+    display: block;
+    background: lightgrey;
+    border: 2px solid black;
+    margin: 11px 13px 17px 7px;
+    inline-size: 6px;
+  }
+
+  .small { font: 12px Ahem; }
+  .big   { font: 20px Ahem; }
+
+  .hl  { writing-mode: horizontal-tb;  direction: ltr; }
+  .hr  { writing-mode: horizontal-tb;  direction: rtl; }
+  .vl  { writing-mode: vertical-lr;    direction: ltr; }
+  .vr  { writing-mode: vertical-rl;    direction: ltr; }
+  .vl_rtl { writing-mode: vertical-lr; direction: rtl; }
+  .vr_rtl { writing-mode: vertical-rl; direction: rtl; }
+  </style>
+</head>
+<body>
+
+<div class="container vl_rtl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vl_rtl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+<div class="container vr_rtl">
+  <span class="hl small">p b c</span>
+  <span class="hl big">p e</span>
+  <span class="hr small">p b c</span>
+  <span class="hr big">p e</span>
+  <span class="vl small">p b c</span>
+  <span class="vl big">p e</span>
+</div>
+<div class="container vr_rtl">
+  <span class="vr small">p b c</span>
+  <span class="vr big">p e</span>
+  <span class="vl_rtl small">p b c</span>
+  <span class="vl_rtl big">p e</span>
+  <span class="vr_rtl small">p b c</span>
+  <span class="vr_rtl big">p e</span>
+</div>
+
+</body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-001-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-001-ref.html
new file mode 100644
index 0000000..5f8bcaf
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-001-ref.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<meta charset="utf-8">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+.parent {
+  position: fixed;
+  top: 0;
+  left: 0;
+  display: block;
+  width: 200px;
+  height: 200px;
+  background: yellow;
+}
+
+.child {
+  position: absolute;
+  left: 50px;
+  top: 50px;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div class="parent"><div class="child"></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-001.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-001.html
new file mode 100644
index 0000000..1edfdb0
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-001.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>CSS Test: Absolutely positioned children of flex container with CSS align</title>
+<meta charset="utf-8">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#abspos-items">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1386654">
+<link rel="match" href="position-absolute-containing-block-001-ref.html">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+.parent {
+  position: fixed;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 200px;
+  height: 200px;
+  background: yellow;
+}
+
+.child {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div class="parent"><div class="child"></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-002-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-002-ref.html
new file mode 100644
index 0000000..90ee388
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-002-ref.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<meta charset="utf-8">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+.parent {
+  position: fixed;
+  top: 0;
+  left: 0;
+  display: block;
+  width: 200px;
+  height: 200px;
+  background: yellow;
+}
+
+.child {
+  position: absolute;
+  left: 60px;
+  top: 60px;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div class="parent"><div class="child"></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-002.html b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-002.html
new file mode 100644
index 0000000..aecbf0b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/position-absolute-containing-block-002.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>CSS Test: Absolutely positioned children of flex container with CSS align</title>
+<meta charset="utf-8">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#abspos-items">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1386654">
+<link rel="match" href="position-absolute-containing-block-002-ref.html">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<style>
+.parent {
+  position: fixed;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 180px;
+  height: 180px;
+
+  /* Expand the background area to 200px, without touching the content-box,
+     which is what flex absolute children should be aligned relative to. */
+  border-top: 5px solid yellow;
+  padding-top: 15px;
+  border-left: 5px solid yellow;
+  padding-left: 15px;
+
+  background: yellow;
+}
+
+.child {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div class="parent"><div class="child"></div></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/reftest.list b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/reftest.list
index bff3a67..5c38083 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/reftest.list
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/reftest.list
@@ -1,6 +1,7 @@
 # Tests for absolutely-positioned children of a flex container
 == flexbox-abspos-child-001a.html flexbox-abspos-child-001-ref.html
 == flexbox-abspos-child-001b.html flexbox-abspos-child-001-ref.html
+== flexbox-abspos-child-002.html flexbox-abspos-child-002-ref.html
 
 # Tests for handling anonymous flex items
 == flexbox-anonymous-items-001.html flexbox-anonymous-items-001-ref.html
@@ -61,11 +62,15 @@
 
 # Basic tests with with blocks as flex items
 == flexbox-basic-block-horiz-001.xhtml flexbox-basic-block-horiz-001-ref.xhtml
+== flexbox-basic-block-horiz-001v.xhtml flexbox-basic-block-horiz-001-ref.xhtml
 == flexbox-basic-block-vert-001.xhtml flexbox-basic-block-vert-001-ref.xhtml
+== flexbox-basic-block-vert-001v.xhtml flexbox-basic-block-vert-001-ref.xhtml
 
 # Tests for basic handling of <canvas>/<img>/etc as a flex item
 == flexbox-basic-canvas-horiz-001.xhtml   flexbox-basic-canvas-horiz-001-ref.xhtml
+== flexbox-basic-canvas-horiz-001v.xhtml  flexbox-basic-canvas-horiz-001-ref.xhtml
 == flexbox-basic-canvas-vert-001.xhtml    flexbox-basic-canvas-vert-001-ref.xhtml
+== flexbox-basic-canvas-vert-001v.xhtml   flexbox-basic-canvas-vert-001-ref.xhtml
 == flexbox-basic-fieldset-horiz-001.xhtml flexbox-basic-fieldset-horiz-001-ref.xhtml
 == flexbox-basic-fieldset-vert-001.xhtml  flexbox-basic-fieldset-vert-001-ref.xhtml
 == flexbox-basic-iframe-horiz-001.xhtml   flexbox-basic-iframe-horiz-001-ref.xhtml
@@ -93,6 +98,16 @@
 == flexbox-collapsed-item-horiz-002.html flexbox-collapsed-item-horiz-002-ref.html
 == flexbox-collapsed-item-horiz-003.html flexbox-collapsed-item-horiz-003-ref.html
 
+# Tests for "flex-basis: content"
+== flexbox-flex-basis-content-001a.html flexbox-flex-basis-content-001-ref.html
+== flexbox-flex-basis-content-001b.html flexbox-flex-basis-content-001-ref.html
+== flexbox-flex-basis-content-002a.html flexbox-flex-basis-content-002-ref.html
+== flexbox-flex-basis-content-002b.html flexbox-flex-basis-content-002-ref.html
+== flexbox-flex-basis-content-003a.html flexbox-flex-basis-content-003-ref.html
+== flexbox-flex-basis-content-003b.html flexbox-flex-basis-content-003-ref.html
+== flexbox-flex-basis-content-004a.html flexbox-flex-basis-content-004-ref.html
+== flexbox-flex-basis-content-004b.html flexbox-flex-basis-content-004-ref.html
+
 # Tests for flex-flow shorthand property
 == flexbox-flex-flow-001.html flexbox-flex-flow-001-ref.html
 == flexbox-flex-flow-002.html flexbox-flex-flow-002-ref.html
@@ -107,11 +122,23 @@
 # (Note that tests 001 and 002 share a reference case; they render the same,
 # because they don't do any direction-specific stretching/flexing.)
 == flexbox-intrinsic-ratio-001.html flexbox-intrinsic-ratio-001-ref.html
+== flexbox-intrinsic-ratio-001v.html flexbox-intrinsic-ratio-001-ref.html
 == flexbox-intrinsic-ratio-002.html flexbox-intrinsic-ratio-001-ref.html
+== flexbox-intrinsic-ratio-002v.html flexbox-intrinsic-ratio-001-ref.html
 == flexbox-intrinsic-ratio-003.html flexbox-intrinsic-ratio-003-ref.html
+== flexbox-intrinsic-ratio-003v.html flexbox-intrinsic-ratio-003-ref.html
 == flexbox-intrinsic-ratio-004.html flexbox-intrinsic-ratio-004-ref.html
+== flexbox-intrinsic-ratio-004v.html flexbox-intrinsic-ratio-004-ref.html
 == flexbox-intrinsic-ratio-005.html flexbox-intrinsic-ratio-005-ref.html
+== flexbox-intrinsic-ratio-005v.html flexbox-intrinsic-ratio-005-ref.html
 == flexbox-intrinsic-ratio-006.html flexbox-intrinsic-ratio-006-ref.html
+== flexbox-intrinsic-ratio-006v.html flexbox-intrinsic-ratio-006-ref.html
+
+# Test for definite and indefinite sizes.
+== flexbox-definite-sizes-001.html flexbox-definite-sizes-001-ref.html
+== flexbox-definite-sizes-002.html flexbox-definite-sizes-001-ref.html
+== flexbox-definite-sizes-003.html flexbox-definite-sizes-001-ref.html
+== flexbox-definite-sizes-004.html flexbox-definite-sizes-001-ref.html
 
 # Tests for flex items as (pseudo) stacking contexts
 == flexbox-items-as-stacking-contexts-001.xhtml flexbox-items-as-stacking-contexts-001-ref.xhtml
@@ -141,7 +168,9 @@
 == flexbox-mbp-horiz-001-rtl-reverse.xhtml flexbox-mbp-horiz-001-ref.xhtml
 == flexbox-mbp-horiz-002a.xhtml            flexbox-mbp-horiz-002-ref.xhtml
 == flexbox-mbp-horiz-002b.xhtml            flexbox-mbp-horiz-002-ref.xhtml
+== flexbox-mbp-horiz-002v.xhtml            flexbox-mbp-horiz-002-ref.xhtml
 == flexbox-mbp-horiz-003.xhtml             flexbox-mbp-horiz-003-ref.xhtml
+== flexbox-mbp-horiz-003v.xhtml            flexbox-mbp-horiz-003-ref.xhtml
 == flexbox-mbp-horiz-003-reverse.xhtml     flexbox-mbp-horiz-003-reverse-ref.xhtml
 == flexbox-mbp-horiz-004.xhtml             flexbox-mbp-horiz-004-ref.xhtml
 
@@ -174,6 +203,7 @@
 # Tests for the order in which we paint flex items
 == flexbox-paint-ordering-001.xhtml flexbox-paint-ordering-001-ref.xhtml
 == flexbox-paint-ordering-002.xhtml flexbox-paint-ordering-002-ref.xhtml
+== flexbox-paint-ordering-003.html  flexbox-paint-ordering-003-ref.html
 
 # Tests for "display:flex" on root node
 == flexbox-root-node-001a.html flexbox-root-node-001-ref.html
@@ -198,7 +228,8 @@
 == flexbox-with-pseudo-elements-002.html flexbox-with-pseudo-elements-002-ref.html
 == flexbox-with-pseudo-elements-003.html flexbox-with-pseudo-elements-003-ref.html
 
-# Tests for combined influence of 'writing-mode' & 'direction' on flex axes
+# Tests for combined influence of 'writing-mode' & 'direction'
+# on flex container axes & flex item placement
 == flexbox-writing-mode-001.html flexbox-writing-mode-001-ref.html
 == flexbox-writing-mode-002.html flexbox-writing-mode-002-ref.html
 == flexbox-writing-mode-003.html flexbox-writing-mode-003-ref.html
@@ -208,8 +239,18 @@
 == flexbox-writing-mode-007.html flexbox-writing-mode-007-ref.html
 == flexbox-writing-mode-008.html flexbox-writing-mode-008-ref.html
 == flexbox-writing-mode-009.html flexbox-writing-mode-009-ref.html
+== flexbox-writing-mode-010.html flexbox-writing-mode-010-ref.html
+== flexbox-writing-mode-011.html flexbox-writing-mode-011-ref.html
+== flexbox-writing-mode-012.html flexbox-writing-mode-012-ref.html
+== flexbox-writing-mode-013.html flexbox-writing-mode-013-ref.html
+== flexbox-writing-mode-014.html flexbox-writing-mode-014-ref.html
+== flexbox-writing-mode-015.html flexbox-writing-mode-015-ref.html
 
 # Single-line size clamping
 == flexbox-single-line-clamp-1.html flexbox-single-line-clamp-1-ref.html
 == flexbox-single-line-clamp-2.html flexbox-single-line-clamp-2-ref.html
 == flexbox-single-line-clamp-3.html flexbox-single-line-clamp-3-ref.html
+
+# Flexbox as an absolute containing block.
+== position-absolute-containing-block-001.html position-absolute-containing-block-001-ref.html
+== position-absolute-containing-block-002.html position-absolute-containing-block-002-ref.html
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-1-ref.html
new file mode 100644
index 0000000..a66a85e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<body style="direction: ltr">
+  <span style="display: block">x</span>
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-right: 10px"></span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-1.html
new file mode 100644
index 0000000..dcf9367
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="emptyspan-1-ref.html">
+<body style="direction: ltr">
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-right: 10px">
+    <span style="display: block">x</span>
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-2-ref.html
new file mode 100644
index 0000000..941044b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-2-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<body style="direction: rtl">
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-right: 10px"></span>
+  <span style="display: block">x</span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-2.html
new file mode 100644
index 0000000..b740fad
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="emptyspan-2-ref.html">
+<body style="direction: rtl">
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-right: 10px">
+    <span style="display: block">x</span>
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-3-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-3-ref.html
new file mode 100644
index 0000000..629c342
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-3-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<body style="direction: ltr">
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-left: 10px"></span>
+  <span style="display: block">x</span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-3.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-3.html
new file mode 100644
index 0000000..1be809e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="emptyspan-3-ref.html">
+<body style="direction: ltr">
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-left: 10px">
+    <span style="display: block">x</span>
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4-ref.html
new file mode 100644
index 0000000..af74e69
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<body style="direction: rtl">
+  <span style="display: block">x</span>
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-left: 10px"></span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4.html
new file mode 100644
index 0000000..3656ada
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="emptyspan-4-ref.html">
+<body style="direction: rtl">
+  <span style="border: 5px solid blue; border-left: none; border-right: none;
+               padding-left: 10px">
+    <span style="display: block">x</span>
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/float-inside-inline-between-blocks-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/float-inside-inline-between-blocks-1-ref.html
new file mode 100644
index 0000000..d9146e2
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/float-inside-inline-between-blocks-1-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<div style="position: relative; left: 100px">
+  aaa
+</div>
+<span  style="position: relative; left: 100px">
+  <span style="float: left">bbb</span>
+</span>
+<div style="position: relative; left: 100px">
+  aaa
+</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/float-inside-inline-between-blocks-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/float-inside-inline-between-blocks-1.html
new file mode 100644
index 0000000..296dcb9
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/float-inside-inline-between-blocks-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="float-inside-inline-between-blocks-1-ref.html">
+<span style="position: relative; left: 100px">
+  <span style="display: block">
+    aaa
+  </span>
+  <span style="float: left">bbb</span>
+  <span style="display: block">
+    aaa
+  </span>
+</span>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/percent-height-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/percent-height-1-ref.html
new file mode 100644
index 0000000..0f74e18
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/percent-height-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<div style="height: 100px; border: 10px solid black"></div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/percent-height-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/percent-height-1.html
new file mode 100644
index 0000000..91c1594
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/percent-height-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="percent-height-1-ref.html">
+<body style="height: 200px">
+  <span>
+    <span style="display: block; height: 50%; border: 10px solid black">
+    </span>
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/reftest.list b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/reftest.list
new file mode 100644
index 0000000..2cc5531
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/reftest.list
@@ -0,0 +1,23 @@
+== remove-split-inline-1.html remove-split-inline-1-ref.html
+== remove-from-split-inline-1.html remove-from-split-inline-1-ref.html
+== remove-from-split-inline-1-ref.html remove-from-split-inline-1-noib-ref.html
+== remove-from-split-inline-2.html remove-from-split-inline-2-ref.html
+== remove-from-split-inline-3.html remove-from-split-inline-3-ref.html
+== remove-from-split-inline-3-ref.html remove-from-split-inline-3-noib-ref.html
+== remove-from-split-inline-4.html remove-from-split-inline-4-ref.html
+== remove-from-split-inline-4-ref.html remove-from-split-inline-4-noib-ref.html
+== remove-from-split-inline-5.html remove-from-split-inline-5-ref.html
+== remove-from-split-inline-5-ref.html remove-from-split-inline-5-noib-ref.html
+== remove-from-split-inline-6.html remove-from-split-inline-6-ref.html
+== remove-from-split-inline-6-ref.html remove-from-split-inline-6-noib-ref.html
+== float-inside-inline-between-blocks-1.html float-inside-inline-between-blocks-1-ref.html
+== table-pseudo-in-part3-1.html table-pseudo-in-part3-1-ref.html
+== emptyspan-1.html emptyspan-1-ref.html
+== emptyspan-2.html emptyspan-2-ref.html
+== emptyspan-3.html emptyspan-3-ref.html
+== emptyspan-4.html emptyspan-4-ref.html
+== split-inner-inline-1.html split-inner-inline-1-ref.html
+== split-inner-inline-2.html split-inner-inline-2-ref.html
+== whitespace-present-1a.html whitespace-present-1-ref.html
+== whitespace-present-1b.html whitespace-present-1-ref.html
+== percent-height-1.html percent-height-1-ref.html
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1-noib-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1-noib-ref.html
new file mode 100644
index 0000000..7b8a141
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1-noib-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<style>
+  body > span { border: 3px solid blue }
+  #start { border-right: none; }
+  #two { border-left: none; }
+</style>
+<body>
+  <span id="start"></span>
+  <div>One</div>
+  <span id="two">
+    Two
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1-ref.html
new file mode 100644
index 0000000..f197d6a
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-1-noib-ref.html">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body>
+  <span
+  ><div>One</div>
+    Two
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1.html
new file mode 100644
index 0000000..07053ef
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-1-ref.html">
+<script>
+function doit() {
+  var target = document.getElementById("target");
+  target.remove();
+}
+</script>
+<style>
+ body > span { border: 3px solid blue }
+</style>
+<body onload='doit()'>
+  <span
+  ><span id="target">Four</span
+  ><div>One</div>
+    Two
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-2-ref.html
new file mode 100644
index 0000000..9fd41e1
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-2-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body>
+  <span>
+   One
+   Two
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-2.html
new file mode 100644
index 0000000..d4cf510
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-2-ref.html">
+<script>
+function doit() {
+  var target = document.getElementById("target");
+  target.remove();
+}
+</script>
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body onload='doit()'>
+  <span>
+    One
+    <div id="target">Three</div>
+    Two
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3-noib-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3-noib-ref.html
new file mode 100644
index 0000000..124b8fd
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3-noib-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<style>
+  body > span { border: 3px solid blue }
+  #one { border-right: none; }
+  #tail { border-left: none; }
+</style>
+<body>
+  <span id="one">
+    One
+  </span>
+  <div>Two</div>
+  <span id="tail"></span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3-ref.html
new file mode 100644
index 0000000..5822665
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-3-noib-ref.html">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body>
+  <span>
+    One
+    <div>Two</div
+  ></span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3.html
new file mode 100644
index 0000000..1d5e2a4
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-3.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-3-ref.html">
+<script>
+function doit() {
+  var target = document.getElementById("target");
+  target.remove();
+}
+</script>
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body onload='doit()'>
+  <span>
+    One
+    <div>Two</div
+    ><span id="target">Three</span
+  ></span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4-noib-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4-noib-ref.html
new file mode 100644
index 0000000..10c563c
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4-noib-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<style>
+  body > span { border: 3px solid blue }
+  #one { border-right: none; }
+  #four { border-left: none; }
+</style>
+<body>
+  <span id="one">
+    One
+    Two
+  </span>
+  <div>Three</div>
+  <span id="four">
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4-ref.html
new file mode 100644
index 0000000..8768b0d
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-4-noib-ref.html">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body>
+  <span>
+    One
+    Two
+    <div>Three</div>
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4.html
new file mode 100644
index 0000000..44c0cee
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-4.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-4-ref.html">
+<script>
+function doit() {
+  var target = document.getElementById("target");
+  target.remove();
+}
+</script>
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body onload='doit()'>
+  <span>
+    One
+    <div id="target">Five</div>
+    Two
+    <div>Three</div>
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5-noib-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5-noib-ref.html
new file mode 100644
index 0000000..8bd8802
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5-noib-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<style>
+  body > span { border: 3px solid blue }
+  #one { border-right: none; }
+  #three { border-left: none; }
+</style>
+<body>
+  <span id="one">
+    One
+  </span>
+  <div>Two</div>
+  <span id="three">
+    Three
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5-ref.html
new file mode 100644
index 0000000..68258ef
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-5-noib-ref.html">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body>
+  <span>
+    One
+    <div>Two</div>
+    Three
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5.html
new file mode 100644
index 0000000..539115e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-5.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-5-ref.html">
+<script>
+function doit() {
+  var target = document.getElementById("target");
+  target.remove();
+}
+</script>
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body onload='doit()'>
+  <span>
+    One
+    <div>Two</div>
+    Three
+    <div id="target">Five</div>
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6-noib-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6-noib-ref.html
new file mode 100644
index 0000000..7678e81
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6-noib-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<style>
+  body > span { border: 3px solid blue }
+  #one { border-right: none; }
+  #four { border-left: none; }
+</style>
+<body>
+  <span id="one">
+    One
+  </span>
+  <div>Two</div>
+  <div>Three</div>
+  <span id="four">
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6-ref.html
new file mode 100644
index 0000000..bd43b45
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-from-split-inline-6-noib-ref.html">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body>
+  <span>
+    One
+    <div>Two</div>
+    <div>Three</div>
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6.html
new file mode 100644
index 0000000..99bbcf7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level"/>
+<link rel="match" href="remove-from-split-inline-6-ref.html">
+<script>
+function doit() {
+  var target = document.getElementById("target");
+  target.remove();
+}
+</script>
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body onload='doit()'>
+  <span>
+    One
+    <div>Two</div>
+    <span id="target">Five</span>
+    <div>Three</div>
+    Four
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-split-inline-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-split-inline-1-ref.html
new file mode 100644
index 0000000..cf4cfff
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-split-inline-1-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+One
+Two
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-split-inline-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-split-inline-1.html
new file mode 100644
index 0000000..cfe6a5e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-split-inline-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="remove-split-inline-1-ref.html">
+<script>
+function doit() {
+  var target = document.getElementById("target");
+  target.remove();
+}
+</script>
+<body onload='doit()'>
+  One
+  <span id="target">
+    Three
+    <div>Four</div>
+    Five
+  </span>
+  Two
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-1-ref.html
new file mode 100644
index 0000000..3f2a82f
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<span>First line</span>
+<div>Second line</div>
+<span>Third line, yes</span>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-1.html
new file mode 100644
index 0000000..3ae7392
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="split-inner-inline-1-ref.html">
+<span>
+  First
+  <span>
+    line
+    <span style="display: block">
+      Second line
+    </span>
+    Third
+  </span>
+  line, yes
+</span>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2-ref.html
new file mode 100644
index 0000000..1ace2f8
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<span>First line
+  <span style="border: 5px solid blue; border-right: none"></span>
+</span>
+<div>Second line</div>
+<span>
+  <span style="border: 5px solid blue; border-left: none"></span>
+  Third line, yes
+</span>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2.html
new file mode 100644
index 0000000..318df53
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="split-inner-inline-2-ref.html">
+<span>
+  First line
+  <span style="border: 5px solid blue">
+    <span style="display: block">
+      Second line
+    </span>
+  </span>
+  Third line, yes
+</span>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/table-pseudo-in-part3-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/table-pseudo-in-part3-1-ref.html
new file mode 100644
index 0000000..b210503
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/table-pseudo-in-part3-1-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+  </head>
+  <body>
+    <div>
+      aaa
+      <div>bbb</div>
+    </div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/table-pseudo-in-part3-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/table-pseudo-in-part3-1.html
new file mode 100644
index 0000000..cb50af9
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/table-pseudo-in-part3-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="table-pseudo-in-part3-1-ref.html">
+<span style="display: table-row">
+  <span>
+    aaa
+    <span style="display: block"></span>
+    <span style="display: table-cell">bbb</span>
+  </span>
+</span>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1-ref.html
new file mode 100644
index 0000000..46cac67
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<style>
+  body > span { border: 3px solid blue }
+  .notstart { border-left: none; }
+  .notend { border-right: none; }
+</style>
+<body>
+  <span class="notend"></span>
+  <div>One</div>
+  <span class="notstart notend"></span>
+  <div>Two</div>
+  <span class="notstart"></span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1a.html
new file mode 100644
index 0000000..283884b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1a.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="whitespace-present-1-ref.html">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<body>
+  <span>
+    <div>One</div>
+    <div>Two</div>
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1b.html
new file mode 100644
index 0000000..70239a8
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/whitespace-present-1b.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS 2.1 Test Suite: handling of blocks inside inlines</title>
+<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu">
+<link rel="author" title="Mozilla Corporation" href="http://mozilla.com/">
+<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level">
+<link rel="match" href="whitespace-present-1-ref.html">
+<style>
+  body > span { border: 3px solid blue }
+</style>
+<script>
+function doIt() {
+  var t = document.createTextNode("   ");
+  var d = document.getElementById("d");
+  d.parentNode.insertBefore(t, d);
+}
+</script>
+<body onload="doIt()">
+  <span>
+    <div>One</div><div id="d">Two</div>
+  </span>
+</body>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/broken-column-rule-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/broken-column-rule-1-ref.html
new file mode 100644
index 0000000..c696d07
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/broken-column-rule-1-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<title>CSS Test Reference: breaking of column rule</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<style>
+
+.ref1, .ref2 {
+  display: inline-block;
+  vertical-align: top;
+
+  border: 2px solid blue;
+  border-top: none;
+  border-bottom: none;
+}
+
+.ref1 {
+  margin-left:49px;
+  height: 100px;
+  width: 148px;
+}
+
+.ref2 {
+  margin-left: 148px;
+  height: 50px;
+  width: 10px;
+
+  border-right: none;
+}
+
+</style>
+<span class="ref1"></span><span class="ref2"></span>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/broken-column-rule-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/broken-column-rule-1.html
new file mode 100644
index 0000000..13deaa7
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/broken-column-rule-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<title>CSS Test: breaking of column rule</title>
+<meta charset="utf-8">
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="help" href="https://drafts.csswg.org/css-multicol/#column-gaps-and-rules">
+<link rel="match" href="broken-column-rule-1-ref.html">
+<style>
+
+.outer {
+  height: 100px;
+  column-fill: auto;
+  width: 550px;
+  column-count: 4;
+  column-gap: 50px;
+  /* leaves 100px for each column */
+}
+
+.inner {
+  column-count: 2;
+  column-rule: 2px solid blue;
+  height: 250px;
+}
+
+.fill {
+  height: 500px; /* work around https://bugzilla.mozilla.org/show_bug.cgi?id=1374479#c4 */
+}
+
+</style>
+
+<div class="outer">
+  <div class="inner">
+    <div class="fill"></div>
+  </div>
+</div>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list b/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list
index 52e8756..35102af 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/multicol3/reftest.list
@@ -1,2 +1,3 @@
+== broken-column-rule-1.html broken-column-rule-1-ref.html
 == moz-multicol3-column-balancing-break-inside-avoid-1.html moz-multicol3-column-balancing-break-inside-avoid-1-ref.html
 == multicol-height-002.xht reference/multicol-height-002.xht
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/reftest.list b/css/vendor-imports/mozilla/mozilla-central-reftests/reftest.list
index 363c678..6630187 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/reftest.list
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/reftest.list
@@ -28,6 +28,9 @@
 # Containment
 include contain/reftest.list
 
+# Counter Styles Level 3
+include counter-styles-3/reftest.list
+
 # Filter Effects Module
 include filters/reftest.list
 
@@ -37,6 +40,9 @@
 # Fonts Level 3
 include fonts3/reftest.list
 
+# block-inside-inline splits
+include ib-split/reftest.list
+
 # Image Values and Replaced Content Level 3
 include images3/reftest.list
 
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/reftest.list b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/reftest.list
index eb4d7f4..fbee289 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/reftest.list
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/reftest.list
@@ -60,6 +60,7 @@
 == shape-outside-circle-053.html shape-outside-circle-053-ref.html
 == shape-outside-circle-054.html shape-outside-circle-054-ref.html
 == shape-outside-circle-055.html shape-outside-circle-055-ref.html
+== shape-outside-circle-056.html shape-outside-circle-056-ref.html
 
 # Basic shape: ellipse()
 == shape-outside-ellipse-032.html shape-outside-ellipse-032-ref.html
@@ -82,6 +83,7 @@
 == shape-outside-ellipse-049.html shape-outside-ellipse-049-ref.html
 == shape-outside-ellipse-050.html shape-outside-ellipse-050-ref.html
 == shape-outside-ellipse-051.html shape-outside-ellipse-051-ref.html
+== shape-outside-ellipse-052.html shape-outside-ellipse-052-ref.html
 
 # Basic shape: inset()
 == shape-outside-inset-016.html shape-outside-inset-016-ref.html
@@ -112,3 +114,4 @@
 == shape-outside-polygon-029.html shape-outside-polygon-027-ref.html
 == shape-outside-polygon-030.html shape-outside-polygon-030-ref.html
 == shape-outside-polygon-031.html shape-outside-polygon-031-ref.html
+== shape-outside-polygon-032.html shape-outside-polygon-032-ref.html
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-056-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-056-ref.html
new file mode 100644
index 0000000..f4f2105
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-056-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Reference File</title>
+        <link rel="author" title="Rebecca Hauck" href="mailto:rhauck@adobe.com">
+    </head>
+    <style>
+       body {
+           margin: 0;
+       }
+       #container {
+           position: relative;
+       }
+       #line {
+           position: absolute;
+           top: 0px;
+           left: 168px;
+           width: 2px;
+           height: 200px;
+           border-left: 2px solid blue;
+       }
+       #square {
+           position: absolute;
+           top: 80px;
+           left: 170px;
+           width: 40px;
+           height: 40px;
+           background-color: green;
+       }
+    </style>
+    <body>
+        <p>The test passes if there is a green square to the right of the blue line. There should be no red.</p>
+        <div id="container">
+            <div id="line"></div>
+            <div id="square"></div>
+        </div>
+    </body>
+</html>
+
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-056.html b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-056.html
new file mode 100644
index 0000000..056d40b
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-056.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: left float, offset circle at top left + margin-box + shape-margin</title>
+        <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-circle">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#margin-box">
+        <link rel="match" href="shape-outside-circle-056-ref.html">
+        <meta name="flags" content="ahem" />
+        <meta name="assert" content="The test verifies that text wraps around a
+                                     left float with a shape-outside defined as
+                                     a circle from the margin box and is positioned
+                                     top left with a shape-margin. Additionally, the
+                                     shape-outside: circle element is offset from
+                                     its containing block.">
+    </head>
+    <style>
+    body {
+        margin: 0;
+    }
+    #container {
+        position: relative;
+    }
+    #test-container {
+        font: 40px/1 Ahem, sans-serif;
+        width: 300px;
+        height: 200px;
+        padding: 50px;
+        position: absolute;
+        top: -50px;
+        left: -50px;
+        color: green;
+    }
+    #test-shape {
+        float: left;
+        width: 120px;
+        height: 120px;
+        margin: 10px;
+        padding: 10px;
+        border: 10px solid transparent;
+        shape-margin: 10px;
+        shape-outside: margin-box circle(99% at top left);
+    }
+    #line {
+        position: absolute;
+        top: 0px;
+        left: 168px;
+        width: 2px;
+        height: 200px;
+        border-left: 2px solid blue;
+    }
+    #failure {
+        position: absolute;
+        top: 80px;
+        left: 170px;
+        width: 40px;
+        height: 40px;
+        background-color: red;
+        z-index: -1;
+    }
+    </style>
+    <body>
+    <p>The test passes if there is a green square to the right of the blue line. There should be no red.</p>
+    <div id="container">
+        <div id="test-container">
+            <div id="test-shape"></div>
+            <br/>
+            <br/>
+            X
+        </div>
+        <div id="line"></div>
+        <div id="failure"></div>
+    </div>
+    </body>
+</html>
+
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-ellipse-052-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-ellipse-052-ref.html
new file mode 100644
index 0000000..f4f2105
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-ellipse-052-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Reference File</title>
+        <link rel="author" title="Rebecca Hauck" href="mailto:rhauck@adobe.com">
+    </head>
+    <style>
+       body {
+           margin: 0;
+       }
+       #container {
+           position: relative;
+       }
+       #line {
+           position: absolute;
+           top: 0px;
+           left: 168px;
+           width: 2px;
+           height: 200px;
+           border-left: 2px solid blue;
+       }
+       #square {
+           position: absolute;
+           top: 80px;
+           left: 170px;
+           width: 40px;
+           height: 40px;
+           background-color: green;
+       }
+    </style>
+    <body>
+        <p>The test passes if there is a green square to the right of the blue line. There should be no red.</p>
+        <div id="container">
+            <div id="line"></div>
+            <div id="square"></div>
+        </div>
+    </body>
+</html>
+
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-ellipse-052.html b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-ellipse-052.html
new file mode 100644
index 0000000..831b101
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-ellipse-052.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: left float, offset ellipse + shape-margin in % units</title>
+        <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-ellipse">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
+        <link rel="match" href="shape-outside-ellipse-052-ref.html">
+        <meta name="flags" content="ahem" />
+        <meta name="assert" content="The test verifies that text wraps around a
+                                     left float with a shape-outside defined as
+                                     a ellipse with a shape-margin in pixel units.
+                                     Additionally, the shape-outside: ellipse element
+                                     is offset from its containing block.">
+    </head>
+    <style>
+    body {
+        margin: 0;
+    }
+    #container {
+        position: relative;
+    }
+    #test-container {
+        font: 40px/1 Ahem, sans-serif;
+        width: 300px;
+        height: 200px;
+        color: green;
+        padding: 50px;
+        position: absolute;
+        top: -50px;
+        left: -50px;
+    }
+    #test-shape {
+        float: left;
+        width: 140px;
+        height: 140px;
+        margin: 10px;
+        padding: 10px;
+        border: 10px solid transparent;
+        shape-margin: 30px;
+        shape-outside: ellipse(40px 28px);
+    }
+    #line {
+        position: absolute;
+        top: 0px;
+        left: 168px;
+        width: 2px;
+        height: 200px;
+        border-left: 2px solid blue;
+    }
+    #failure {
+        position: absolute;
+        top: 80px;
+        left: 170px;
+        width: 40px;
+        height: 40px;
+        background-color: red;
+        z-index: -1;
+    }
+    </style>
+    <body>
+    <p>The test passes if there is a green square to the right of the blue line. There should be no red.</p>
+    <div id="container">
+        <div id="test-container">
+            <div id="test-shape"></div>
+            <br/>
+            <br/>
+            X
+        </div>
+        <div id="line"></div>
+        <div id="failure"></div>
+    </div>
+    </body>
+</html>
+
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-032-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-032-ref.html
new file mode 100644
index 0000000..f1fcdf4
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-032-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Reference File</title>
+        <link rel="author" title="Rebecca Hauck" href="mailto:rhauck@adobe.com">
+    </head>
+    <style>
+       body {
+            margin: 0;
+        }
+       #green-square {
+          position: absolute;
+          top: 50px;
+          left: 10px;
+          width: 240px;
+          height: 240px;
+          background-color: green;
+      }
+    </style>
+    <body>
+        <p>The test passes if there is green square and no red.</p>
+        <div id="green-square"></div>
+    </body>
+</html>
\ No newline at end of file
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-032.html b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-032.html
new file mode 100644
index 0000000..126e0b9
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-032.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>CSS Test: right float, offset polygon + margin-box + shape-margin</title>
+        <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-polygon">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
+        <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#margin-box">
+        <link rel="match" href="shape-outside-polygon-032-ref.html">
+        <meta name="flags" content="ahem" />
+        <meta name="assert" content="The test verifies that text wraps around a
+                                     right float with a shape-outside defined as
+                                     an polygon from the margin box with a shape margin.
+                                     Additionally, the shape-outside: polygon element is
+                                     offset from its containing block.">
+    </head>
+    <style>
+        body {
+            margin: 0;
+        }
+        #red {
+            position: absolute;
+            top: 50px;
+            left: 10px;
+            width: 240px;
+            height: 240px;
+            background-color: red;
+        }
+        #container {
+            position: absolute;
+            top: 0px;
+            left: -40px;
+            font-size: 20px;
+            font-family: Ahem;
+            line-height: 20px;
+            width: 240px;
+            height: 240px;
+            padding: 50px;
+            color: green;
+        }
+        #test-shape {
+            float: right;
+            width: 120px;
+            height: 120px;
+            margin: 10px;
+            border: 10px solid transparent;
+            padding: 10px;
+            shape-margin: 20px;
+            shape-outside: margin-box polygon(20% 20%, 90% 20%, 90% 80%, 50% 80%, 50% 70%, 70% 70%, 70% 40%, 20% 40%);
+        }
+        .ref-shape {
+            position: absolute;
+            background-color: green;
+        }
+        #ref-1 {
+            top: 70px;
+            left: 70px;
+            width: 180px;
+            height: 80px;
+        }
+        #ref-2 {
+            top: 150px;
+            left: 150px;
+            width: 100px;
+            height: 20px;
+        }
+        #ref-3 {
+            top: 170px;
+            left: 130px;
+            width: 120px;
+            height: 60px;
+        }
+    </style>
+    <body>
+    <p>The test passes if there is green square and no red.</p>
+    <div id="red"></div>
+    <div id="container">
+        <div id="test-shape"></div>
+        XXXXXXXXXXXX XXX XXX XXX XXX XXXXXXX XXXXXX XXXXXX XXXXXX XXXXXXXXXXXX XXXXXXXXXXXX XXXXXXXXXXXX
+    </div>
+    <div id="ref-1" class="ref-shape"></div>
+    <div id="ref-2" class="ref-shape"></div>
+    <div id="ref-3" class="ref-shape"></div>
+    </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/text3/text-align-match-parent-01.html b/css/vendor-imports/mozilla/mozilla-central-reftests/text3/text-align-match-parent-01.html
index 2c390dc..7560a42 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/text3/text-align-match-parent-01.html
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/text3/text-align-match-parent-01.html
@@ -7,7 +7,7 @@
     <link rel="help" href="http://www.w3.org/TR/css-text-3/#text-align-property">
     <meta name="assert" content="Text checks that an element with
      text-align: match-parent inherits its parent's value and calculates
-     'start' and 'end' with respect to its parent's direction"
+     'start' and 'end' with respect to its parent's direction">
      <link rel="match" href="text-align-match-parent-ref.html">
     <style type="text/css">
 div.start  { text-align: start; }
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1-ref.html
new file mode 100644
index 0000000..8a568d2
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: compare individual transform with transform functions</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <meta name="assert" content="Tests whether individaul transform works correctlyi by compare the rendering result with transfrom functions of the 'transform' property."/>
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+      }
+      .row_1 {
+        top: 100px;
+      }
+      .scale_1{
+        left: 100px;
+        width: 50px;
+        height: 100px;
+        transform: scaleX(2);
+      }
+      .translate_1 {
+        left: 150px;
+        transform: translateX(150px);
+      }
+      .rotate_1 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        transform: rotate(90deg);
+      }
+
+      .row_2 {
+        top: 250px;
+      }
+      .scale_2{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        transform: scale(2, 2);
+      }
+      .translate_2 {
+        left: 150px;
+        top: 200px;
+        transform: translate(150px, 50px);
+      }
+      .rotate_2 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        transform: rotate3d(0, 0, 1, 90deg);
+      }
+      .row_3 {
+        transform: perspective(500px);
+        top: 400px;
+      }
+      .scale_3{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        transform: scale3d(2, 2, 2);
+      }
+      .translate_3 {
+        left: 150px;
+        transform: translate3d(150px, 10px, 10px);
+      }
+      .rotate_3 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        transform: rotate3d(0, 1, 0, 45deg);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="scale_1 row_1"></div>
+    <div class="translate_1 row_1"></div>
+    <div class="rotate_1 row_1"></div>
+    <div class="scale_2 row_2"></div>
+    <div class="translate_2 row_2"></div>
+    <div class="rotate_2 row_2"></div>
+    <div class="scale_3 row_3"></div>
+    <div class="translate_3 row_3"></div>
+    <div class="rotate_3 row_3"></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1.html
new file mode 100644
index 0000000..4f83e2e
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: compare individual transform with transform functions</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <meta name="assert" content="Tests whether individaul transform works correctlyi by compare the rendering result with transfrom functions of the 'transform' property."/>
+    <link rel="match" href="individual-transform-1-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+      }
+      .row_1 {
+        top: 100px;
+      }
+      .scale_1{
+        left: 100px;
+        width: 50px;
+        height: 100px;
+        /* test 'scale: <number>' */
+        scale: 2;
+      }
+      .translate_1 {
+        left: 150px;
+        /* test 'translate: <length-percentage>' */
+        translate: 150px;
+      }
+      .rotate_1 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        /* test 'rota: te<angle>' */
+        rotate: 90deg;
+      }
+
+      .row_2 {
+        top: 250px;
+      }
+      .scale_2{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        /* test 'scale: <number>{2}'' */
+        scale: 2 2;
+      }
+      .translate_2 {
+        left: 150px;
+        top: 200px;
+        /* test 'translate: <length-percentage><length-percentage>' */
+        translate: 150px 50px;
+      }
+      .rotate_2 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        /* test 'rotate: <number>{3}<angle>'*/
+        rotate: 0 0 1 90deg;
+      }
+      .row_3 {
+        transform: perspective(500px);
+        top: 400px;
+      }
+      .scale_3{
+        left: 100px;
+        width: 50px;
+        height: 50px;
+        /* test 'scale: <number>{3}'' */
+        scale: 2 2 2;
+      }
+      .translate_3 {
+        left: 150px;
+        /* test 'translate: <length-percentage><length>' */
+        translate: 150px 10px 10px;
+      }
+      .rotate_3 {
+        left: 450px;
+        transform-origin: 50% 50%;
+        /* test 'rotate: <number>{3}<angle>'*/
+        rotate: 0 1 0 45deg;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="scale_1 row_1"></div>
+    <div class="translate_1 row_1"></div>
+    <div class="rotate_1 row_1"></div>
+    <div class="scale_2 row_2"></div>
+    <div class="translate_2 row_2"></div>
+    <div class="rotate_2 row_2"></div>
+    <div class="scale_3 row_3"></div>
+    <div class="translate_3 row_3"></div>
+    <div class="rotate_3 row_3"></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2-ref.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2-ref.html
new file mode 100644
index 0000000..ee956aa
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        transform: translate(50px, 50px) rotate(45deg) scale(2, 2);
+      }
+
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2a.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2a.html
new file mode 100644
index 0000000..3f6c973
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2a.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 50px 50px;
+        rotate: 45deg;
+        scale: 2 2;
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2b.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2b.html
new file mode 100644
index 0000000..94dac2f
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2b.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        rotate: 45deg;
+        scale: 2 2;
+        translate: 50px 50px;
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2c.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2c.html
new file mode 100644
index 0000000..f84ae22
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2c.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 50px 50px;
+        rotate: 45deg;
+        transform: scale(2, 2);
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2d.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2d.html
new file mode 100644
index 0000000..5b5694f
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2d.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 50px 50px;
+        transform: rotate(45deg) scale(2, 2);
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2e.html b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2e.html
new file mode 100644
index 0000000..0350137
--- /dev/null
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2e.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Individual transform: combine individual transform properties</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#individual-transforms">
+    <link rel="help" href="https://drafts.csswg.org/css-transforms-2/#ctm">
+    <meta name="assert" content="Tests that we combine transforms in the correct order."/>
+    <link rel="match" href="individual-transform-2-ref.html">
+    <style>
+      div {
+        position: fixed;
+        width: 100px;
+        height: 100px;
+        top: 200px;
+        left: 200px;
+        transform-origin: 0 0;
+        border-style: solid;
+        border-width: 10px 0px 10px 0px;
+        border-color: lime;
+        translate: 0px 50px;
+        transform: translateX(50px) rotate(45deg) scale(2, 2);
+      }
+    </style>
+  </head>
+  <body>
+    <div></div>
+  </body>
+</html>
diff --git a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/reftest.list b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/reftest.list
index 4bc525b..f398b1b 100644
--- a/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/reftest.list
+++ b/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/reftest.list
@@ -4,3 +4,10 @@
 == perspective-containing-block-dynamic-1b.html containing-block-dynamic-1-ref.html
 == perspective-zero.html reference/green.html
 == perspective-zero-2.html perspective-zero-2-ref.html
+
+== individual-transform-1.html individual-transform-1-ref.html
+== individual-transform-2a.html individual-transform-2-ref.html
+== individual-transform-2b.html individual-transform-2-ref.html
+== individual-transform-2c.html individual-transform-2-ref.html
+== individual-transform-2d.html individual-transform-2-ref.html
+== individual-transform-2e.html individual-transform-2-ref.html
diff --git a/custom-elements/Document-createElement-svg.svg b/custom-elements/Document-createElement-svg.svg
new file mode 100644
index 0000000..0b53bd8
--- /dev/null
+++ b/custom-elements/Document-createElement-svg.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg:svg xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/1999/xhtml"
+   width="100%" height="100%" viewBox="0 0 800 600">
+<svg:title>document.createElement in SVG for custom elements</svg:title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script><![CDATA[
+test(() => {
+  class MyElement1 extends HTMLElement {}
+  customElements.define('my-element', MyElement1);
+  let element = document.createElement('my-element', {});
+  assert_false(element instanceof MyElement1, 'Autonomous custom element should not be created.');
+}, 'document.createElement() in SVG documents should not create autonomous custom elements.')
+
+test(() => {
+  class MyElement2 extends HTMLDivElement {}
+  customElements.define('my-div', MyElement2, { extends: 'div' });
+
+  let element = document.createElement('div', { is: 'my-div' });
+  assert_false(element instanceof MyElement2, 'Custom built-in element should not be created.');
+}, 'document.createElement() in SVG documents should not create custom built-in elements.')
+]]></script>
+</svg:svg>
diff --git a/custom-elements/Document-createElement.html b/custom-elements/Document-createElement.html
index e446c50..ad29b91 100644
--- a/custom-elements/Document-createElement.html
+++ b/custom-elements/Document-createElement.html
@@ -29,6 +29,42 @@
 
 }, 'document.createElement must create an instance of custom elements');
 
+test(function () {
+    class AutonomousCustomElement extends HTMLElement {};
+    class IsCustomElement extends HTMLElement {};
+
+    customElements.define('autonomous-custom-element', AutonomousCustomElement);
+    customElements.define('is-custom-element', IsCustomElement);
+
+    var instance = document.createElement('autonomous-custom-element', { is: 'is-custom-element'});
+
+    assert_true(instance instanceof AutonomousCustomElement);
+    assert_equals(instance.localName, 'autonomous-custom-element');
+    assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
+
+    var instance2 = document.createElement('undefined-element', { is: 'is-custom-element'});
+    assert_false(instance2.matches(':defined'));
+    class DefinedLater extends HTMLElement {}
+    customElements.define('undefined-element', DefinedLater);
+    document.body.appendChild(instance2);
+    assert_true(instance2 instanceof DefinedLater);
+}, 'document.createElement must create an instance of autonomous custom elements when it has is attribute');
+
+test(() => {
+    class SuperP extends HTMLParagraphElement {}
+    customElements.define("super-p", SuperP, { extends: "p" });
+
+    const superP = document.createElement("p", { is: "super-p" });
+    assert_true(superP instanceof HTMLParagraphElement);
+    assert_true(superP instanceof SuperP);
+    assert_equals(superP.localName, "p");
+
+    const notSuperP = document.createElement("p", "super-p");
+    assert_true(notSuperP instanceof HTMLParagraphElement);
+    assert_false(notSuperP instanceof SuperP);
+    assert_equals(notSuperP.localName, "p");
+}, "document.createElement()'s second argument is to be ignored when it's a string");
+
 function assert_reports(expected, testFunction, message) {
     var uncaughtError = null;
     window.onerror = function (message, url, lineNumber, columnNumber, error) { uncaughtError = error; return true; }
@@ -327,6 +363,19 @@
 
 }, 'document.createElement must report an exception thrown by a custom element constructor');
 
+test(() => {
+  class MyElement extends HTMLDivElement {}
+
+  // createElement with unknown 'is' should not throw.
+  // https://github.com/w3c/webcomponents/issues/608
+  let div = document.createElement('div', { is: 'my-div' });
+  assert_false(div instanceof MyElement);
+  assert_false(div.hasAttribute('is'));
+
+  customElements.define('my-div', MyElement, { extends: 'div' });
+  document.body.appendChild(div);
+  assert_true(div instanceof MyElement, 'Undefined element is upgraded on connecting to a document');
+}, 'document.createElement with unknown "is" value should create "undefined" state element');
 </script>
 </body>
 </html>
diff --git a/custom-elements/Document-createElementNS.html b/custom-elements/Document-createElementNS.html
new file mode 100644
index 0000000..6377ba6
--- /dev/null
+++ b/custom-elements/Document-createElementNS.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<title>Custom Elements: document.createElementNS should support custom elements</title>
+<link rel="help" content="https://dom.spec.whatwg.org/#concept-create-element">
+<link rel="help" content="https://dom.spec.whatwg.org/#internal-createelementns-steps">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+test(() => {
+  class MyElement extends HTMLElement {};
+
+  customElements.define('my-autonomous', MyElement);
+  let element = document.createElementNS('http://www.w3.org/1999/xhtml', 'p:my-autonomous');
+  assert_true(element instanceof MyElement);
+  assert_equals(element.prefix, 'p');
+}, 'autonomous: document.createElementNS should create custom elements with prefixes.');
+
+test(() => {
+  class MyElement2 extends HTMLElement {};
+
+  customElements.define('my-autonomous2', MyElement2);
+  let element = document.createElementNS('urn:example', 'my-autonomous2');
+  assert_false(element instanceof MyElement2);
+}, 'autonomous: document.createElementNS should check namespaces.');
+
+test(() => {
+  const xhtmlNS = 'http://www.w3.org/1999/xhtml';
+  assert_false(document.createElementNS(xhtmlNS, 'x-foo') instanceof HTMLUnknownElement);
+  assert_false(document.createElementNS(xhtmlNS, 'x-foo', {}) instanceof HTMLUnknownElement);
+  assert_false((new Document()).createElementNS(xhtmlNS, 'x-foo') instanceof HTMLUnknownElement);
+  assert_false((new Document()).createElementNS(xhtmlNS, 'x-foo', {}) instanceof HTMLUnknownElement);
+}, 'autonomous: document.createElementNS should not create HTMLUnknownElement for a valid custom element name');
+
+test(() => {
+  class MyBuiltinElement extends HTMLElement {};
+
+  customElements.define('my-builtin', MyBuiltinElement, { extends: 'address' });
+  let element = document.createElementNS('http://www.w3.org/1999/xhtml', 'p:address', { is: 'my-builtin'});
+  assert_true(element instanceof MyBuiltinElement);
+  assert_equals(element.prefix, 'p');
+  assert_false(element.hasAttribute('is'));
+}, 'builtin: document.createElementNS should create custom elements with prefixes.');
+
+test(() => {
+  class MyBuiltinElement2 extends HTMLElement {};
+
+  customElements.define('my-builtin2', MyBuiltinElement2, { extends: 'address'});
+  let element = document.createElementNS('urn:example', 'address', { is: 'my-builtin2' });
+  assert_false(element instanceof MyBuiltinElement2);
+  assert_false(element.hasAttribute('is'));
+}, 'builtin: document.createElementNS should check namespaces.');
+
+test(() => {
+    class SuperP extends HTMLParagraphElement {}
+    customElements.define("super-p", SuperP, { extends: "p" });
+
+    const superP = document.createElementNS("http://www.w3.org/1999/xhtml", "p", { is: "super-p" });
+    assert_true(superP instanceof HTMLParagraphElement);
+    assert_true(superP instanceof SuperP);
+    assert_equals(superP.localName, "p");
+
+    const notSuperP = document.createElementNS("http://www.w3.org/1999/xhtml", "p", "super-p");
+    assert_true(notSuperP instanceof HTMLParagraphElement);
+    assert_false(notSuperP instanceof SuperP);
+    assert_equals(notSuperP.localName, "p");
+}, "document.createElementNS()'s third argument is to be ignored when it's a string");
+</script>
+</body>
diff --git a/custom-elements/HTMLElement-constructor.html b/custom-elements/HTMLElement-constructor.html
index cb6d540..a0bfa90 100644
--- a/custom-elements/HTMLElement-constructor.html
+++ b/custom-elements/HTMLElement-constructor.html
@@ -91,6 +91,159 @@
 
 }, 'HTMLElement constructor must allow subclassing an user-defined subclass of HTMLElement');
 
+test(function() {
+    class SomeCustomElement extends HTMLElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+    customElements.define("success-counting-element-1", countingProxy);
+    // define() gets the prototype of the constructor it's passed, so
+    // reset the counter.
+    getCount = 0;
+    var instance = new countingProxy();
+    assert_equals(getCount, 1, "Should have gotten .prototype once");
+    assert_true(instance instanceof countingProxy);
+    assert_true(instance instanceof HTMLElement);
+    assert_true(instance instanceof SomeCustomElement);
+    assert_equals(instance.localName, "success-counting-element-1");
+    assert_equals(instance.nodeName, "SUCCESS-COUNTING-ELEMENT-1");
+}, 'HTMLElement constructor must only get .prototype once, calling proxy constructor directly');
+
+test(function() {
+    class SomeCustomElement extends HTMLElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+    customElements.define("success-counting-element-2", countingProxy);
+    // define() gets the prototype of the constructor it's passed, so
+    // reset the counter.
+    getCount = 0;
+    var instance = Reflect.construct(HTMLElement, [], countingProxy);
+    assert_equals(getCount, 1, "Should have gotten .prototype once");
+    assert_true(instance instanceof countingProxy);
+    assert_true(instance instanceof HTMLElement);
+    assert_true(instance instanceof SomeCustomElement);
+    assert_equals(instance.localName, "success-counting-element-2");
+    assert_equals(instance.nodeName, "SUCCESS-COUNTING-ELEMENT-2");
+}, 'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect');
+
+test(function() {
+    class SomeCustomElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+    customElements.define("success-counting-element-3", countingProxy);
+    // define() gets the prototype of the constructor it's passed, so
+    // reset the counter.
+    getCount = 0;
+    var instance = Reflect.construct(HTMLElement, [], countingProxy);
+    assert_equals(getCount, 1, "Should have gotten .prototype once");
+    assert_true(instance instanceof countingProxy);
+    assert_true(instance instanceof SomeCustomElement);
+    assert_equals(instance.localName, undefined);
+    assert_equals(instance.nodeName, undefined);
+}, 'HTMLElement constructor must only get .prototype once, calling proxy constructor via Reflect with no inheritance');
+
+test(function() {
+    class SomeCustomElement extends HTMLElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+    customElements.define("failure-counting-element-1", countingProxy,
+                          { extends: "button" });
+    // define() gets the prototype of the constructor it's passed, so
+    // reset the counter.
+    getCount = 0;
+    assert_throws({'name': 'TypeError'},
+                  function () { new countingProxy() },
+                  "Should not be able to construct an HTMLElement named 'button'");
+    assert_equals(getCount, 0, "Should never have gotten .prototype");
+}, 'HTMLElement constructor must not get .prototype until it finishes its extends sanity checks, calling proxy constructor directly');
+
+test(function() {
+    class SomeCustomElement extends HTMLElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+    customElements.define("failure-counting-element-2", countingProxy,
+                          { extends: "button" });
+    // define() gets the prototype of the constructor it's passed, so
+    // reset the counter.
+    getCount = 0;
+    assert_throws({'name': 'TypeError'},
+                  function () { Reflect.construct(HTMLElement, [], countingProxy) },
+                  "Should not be able to construct an HTMLElement named 'button'");
+    assert_equals(getCount, 0, "Should never have gotten .prototype");
+}, 'HTMLElement constructor must not get .prototype until it finishes its extends sanity checks, calling via Reflect');
+
+test(function() {
+    class SomeCustomElement extends HTMLElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+
+    // Purposefully don't register it.
+    assert_throws({'name': 'TypeError'},
+                  function () { new countingProxy() },
+                  "Should not be able to construct an HTMLElement named 'button'");
+    assert_equals(getCount, 0, "Should never have gotten .prototype");
+}, 'HTMLElement constructor must not get .prototype until it finishes its registration sanity checks, calling proxy constructor directly');
+
+test(function() {
+    class SomeCustomElement extends HTMLElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+
+    // Purposefully don't register it.
+    assert_throws({'name': 'TypeError'},
+                  function () { Reflect.construct(HTMLElement, [], countingProxy) },
+                  "Should not be able to construct an HTMLElement named 'button'");
+    assert_equals(getCount, 0, "Should never have gotten .prototype");
+}, 'HTMLElement constructor must not get .prototype until it finishes its registration  sanity checks, calling via Reflect');
 </script>
 </body>
 </html>
+
diff --git a/custom-elements/attribute-changed-callback.html b/custom-elements/attribute-changed-callback.html
index bd46791..5090bfb 100644
--- a/custom-elements/attribute-changed-callback.html
+++ b/custom-elements/attribute-changed-callback.html
@@ -11,6 +11,7 @@
 </head>
 <body>
 <div id="log"></div>
+<parser-created-element title></parser-created-element>
 <script>
 
 var customElement = define_new_custom_element(['title', 'id', 'r']);
@@ -218,6 +219,36 @@
     assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hello', namespace: null});
 }, 'attributedChangedCallback must not be enqueued when mutating inline style declaration if the style attribute is not observed');
 
+test(function () {
+    var calls = [];
+    class CustomElement extends HTMLElement { }
+    CustomElement.prototype.attributeChangedCallback = function (...args) {
+        calls.push(create_attribute_changed_callback_log(this, ...args));
+    }
+    CustomElement.observedAttributes = ['title'];
+    customElements.define('parser-created-element', CustomElement);
+    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
+}, 'Upgrading a parser created element must enqueue and invoke attributeChangedCallback for an HTML attribute');
+
+test(function () {
+    var calls = [];
+    class CustomElement extends HTMLElement { }
+    CustomElement.prototype.attributeChangedCallback = function (...args) {
+        calls.push(create_attribute_changed_callback_log(this, ...args));
+    }
+    CustomElement.observedAttributes = ['title'];
+    customElements.define('cloned-element-with-attribute', CustomElement);
+
+    var instance = document.createElement('cloned-element-with-attribute');
+    assert_equals(calls.length, 0);
+    instance.title = '';
+    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
+
+    calls = [];
+    var clone = instance.cloneNode(false);
+    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
+}, 'Upgrading a cloned element must enqueue and invoke attributeChangedCallback for an HTML attribute');
+
 </script>
 </body>
 </html>
diff --git a/custom-elements/builtin-coverage.html b/custom-elements/builtin-coverage.html
new file mode 100644
index 0000000..e3001a2
--- /dev/null
+++ b/custom-elements/builtin-coverage.html
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<html is="my-html">
+<head>
+<meta charset="utf-8">
+<meta name="help" content="https://html.spec.whatwg.org/multipage/custom-elements.html#element-definition">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body is="my-body">
+<div id="container"></div>
+<script>
+let testData = [
+  {tag: 'a', interface: 'HTMLAnchorElement'},
+  {tag: 'abbr', interface: 'HTMLElement'},
+  {tag: 'address', interface: 'HTMLElement'},
+  {tag: 'area', interface: 'HTMLAreaElement'},
+  {tag: 'article', interface: 'HTMLElement'},
+  {tag: 'aside', interface: 'HTMLElement'},
+  {tag: 'audio', interface: 'HTMLAudioElement'},
+  {tag: 'b', interface: 'HTMLElement'},
+  {tag: 'base', interface: 'HTMLBaseElement'},
+  {tag: 'bdi', interface: 'HTMLElement'},
+  {tag: 'bdo', interface: 'HTMLElement'},
+  {tag: 'blockquote', interface: 'HTMLQuoteElement'},
+  {tag: 'body', interface: 'HTMLBodyElement', parsing: 'document'},
+  {tag: 'br', interface: 'HTMLBRElement'},
+  {tag: 'button', interface: 'HTMLButtonElement'},
+  {tag: 'canvas', interface: 'HTMLCanvasElement'},
+  {tag: 'caption', interface: 'HTMLTableCaptionElement', parsing: 'table'},
+  {tag: 'cite', interface: 'HTMLElement'},
+  {tag: 'code', interface: 'HTMLElement'},
+  {tag: 'col', interface: 'HTMLTableColElement', parsing: 'table'},
+  {tag: 'colgroup', interface: 'HTMLTableColElement', parsing: 'table'},
+  {tag: 'data', interface: 'HTMLDataElement'},
+  {tag: 'dd', interface: 'HTMLElement'},
+  {tag: 'del', interface: 'HTMLModElement'},
+  {tag: 'details', interface: 'HTMLDetailsElement'},
+  {tag: 'dfn', interface: 'HTMLElement'},
+  {tag: 'div', interface: 'HTMLDivElement'},
+  {tag: 'dl', interface: 'HTMLDListElement'},
+  {tag: 'dt', interface: 'HTMLElement'},
+  {tag: 'em', interface: 'HTMLElement'},
+  {tag: 'embed', interface: 'HTMLEmbedElement'},
+  {tag: 'fieldset', interface: 'HTMLFieldSetElement'},
+  {tag: 'figcaption', interface: 'HTMLElement'},
+  {tag: 'figure', interface: 'HTMLElement'},
+  {tag: 'footer', interface: 'HTMLElement'},
+  {tag: 'form', interface: 'HTMLFormElement'},
+  {tag: 'h1', interface: 'HTMLHeadingElement'},
+  {tag: 'h2', interface: 'HTMLHeadingElement'},
+  {tag: 'h3', interface: 'HTMLHeadingElement'},
+  {tag: 'h4', interface: 'HTMLHeadingElement'},
+  {tag: 'h5', interface: 'HTMLHeadingElement'},
+  {tag: 'h6', interface: 'HTMLHeadingElement'},
+  {tag: 'header', interface: 'HTMLElement'},
+  {tag: 'hgroup', interface: 'HTMLElement'},
+  {tag: 'hr', interface: 'HTMLHRElement'},
+  {tag: 'html', interface: 'HTMLHtmlElement', parsing: 'document'},
+  {tag: 'i', interface: 'HTMLElement'},
+  {tag: 'iframe', interface: 'HTMLIFrameElement'},
+  {tag: 'img', interface: 'HTMLImageElement'},
+  {tag: 'input', interface: 'HTMLInputElement'},
+  {tag: 'ins', interface: 'HTMLModElement'},
+  {tag: 'kbd', interface: 'HTMLElement'},
+  {tag: 'label', interface: 'HTMLLabelElement'},
+  {tag: 'legend', interface: 'HTMLLegendElement'},
+  {tag: 'li', interface: 'HTMLLIElement'},
+  {tag: 'link', interface: 'HTMLLinkElement'},
+  {tag: 'main', interface: 'HTMLElement'},
+  {tag: 'map', interface: 'HTMLMapElement'},
+  {tag: 'mark', interface: 'HTMLElement'},
+  {tag: 'menu', interface: 'HTMLMenuElement'},
+  {tag: 'meta', interface: 'HTMLMetaElement'},
+  {tag: 'meter', interface: 'HTMLMeterElement'},
+  {tag: 'nav', interface: 'HTMLElement'},
+  {tag: 'noscript', interface: 'HTMLElement'},
+  {tag: 'object', interface: 'HTMLObjectElement'},
+  {tag: 'ol', interface: 'HTMLOListElement'},
+  {tag: 'optgroup', interface: 'HTMLOptGroupElement'},
+  {tag: 'option', interface: 'HTMLOptionElement'},
+  {tag: 'output', interface: 'HTMLOutputElement'},
+  {tag: 'p', interface: 'HTMLParagraphElement'},
+  {tag: 'param', interface: 'HTMLParamElement'},
+  {tag: 'picture', interface: 'HTMLPictureElement'},
+  {tag: 'pre', interface: 'HTMLPreElement'},
+  {tag: 'progress', interface: 'HTMLProgressElement'},
+  {tag: 'q', interface: 'HTMLQuoteElement'},
+  {tag: 'rp', interface: 'HTMLElement'},
+  {tag: 'rt', interface: 'HTMLElement'},
+  {tag: 'ruby', interface: 'HTMLElement'},
+  {tag: 's', interface: 'HTMLElement'},
+  {tag: 'samp', interface: 'HTMLElement'},
+  {tag: 'script', interface: 'HTMLScriptElement'},
+  {tag: 'section', interface: 'HTMLElement'},
+  {tag: 'select', interface: 'HTMLSelectElement'},
+  {tag: 'small', interface: 'HTMLElement'},
+  {tag: 'source', interface: 'HTMLSourceElement'},
+  {tag: 'span', interface: 'HTMLSpanElement'},
+  {tag: 'strong', interface: 'HTMLElement'},
+  {tag: 'style', interface: 'HTMLStyleElement'},
+  {tag: 'sub', interface: 'HTMLElement'},
+  {tag: 'summary', interface: 'HTMLElement'},
+  {tag: 'sup', interface: 'HTMLElement'},
+  {tag: 'table', interface: 'HTMLTableElement'},
+  {tag: 'tbody', interface: 'HTMLTableSectionElement', parsing: 'table'},
+  {tag: 'td', interface: 'HTMLTableCellElement', parsing: 'table'},
+  {tag: 'template', interface: 'HTMLTemplateElement'},
+  {tag: 'textarea', interface: 'HTMLTextAreaElement'},
+  {tag: 'tfoot', interface: 'HTMLTableSectionElement', parsing: 'table'},
+  {tag: 'th', interface: 'HTMLTableCellElement', parsing: 'table'},
+  {tag: 'thead', interface: 'HTMLTableSectionElement', parsing: 'table'},
+  {tag: 'time', interface: 'HTMLTimeElement'},
+  {tag: 'title', interface: 'HTMLTitleElement'},
+  {tag: 'tr', interface: 'HTMLTableRowElement', parsing: 'table'},
+  {tag: 'track', interface: 'HTMLTrackElement'},
+  {tag: 'u', interface: 'HTMLElement'},
+  {tag: 'ul', interface: 'HTMLUListElement'},
+  {tag: 'var', interface: 'HTMLElement'},
+  {tag: 'video', interface: 'HTMLVideoElement'},
+  {tag: 'wbr', interface: 'HTMLElement'},
+];
+// HTMLDataListElement isn't implemented by all major browsers yet.
+if (window.HTMLDataListElement) {
+  testData.push({tag: 'datalist', interface: 'HTMLDataListElement'});
+}
+// HTMLDialogElement isn't implemented by all major browsers yet.
+if (window.HTMLDialogElement) {
+  testData.push({tag: 'dialog', interface: 'HTMLDialogElement'});
+}
+// HTMLSlotElement isn't implemented by all major browsers yet.
+if (window.HTMLSlotElement) {
+  testData.push({tag: 'slot', interface: 'HTMLSlotElement'});
+}
+
+for (const t of testData) {
+  test(() => {
+    let name = 'my-' + t.tag;
+    let klass = eval(`(class extends ${t.interface} {})`);
+    customElements.define(name, klass, { extends: t.tag });
+
+    test(() => {
+      let customized = new klass();
+      assert_equals(customized.constructor, klass);
+      assert_equals(customized.cloneNode().constructor, klass,
+                    'Cloning a customized built-in element should succeed.');
+    }, `${t.tag}: Operator 'new' should instantiate a customized built-in element`);
+
+    test(() => {
+      let customized = document.createElement(t.tag, { is: name });
+      assert_equals(customized.constructor, klass);
+      assert_equals(customized.cloneNode().constructor, klass,
+                    'Cloning a customized built-in element should succeed.');
+    }, `${t.tag}: document.createElement() should instantiate a customized built-in element`);
+
+    if (t.parsing == 'document') {
+      let test = async_test(`${t.tag}: document parser should instantiate a customized built-in element`);
+      window.addEventListener('load', test.step_func_done(() => {
+        let customized = document.querySelector(t.tag);
+        assert_equals(customized.constructor, klass);
+        assert_equals(customized.cloneNode().constructor, klass,
+                      'Cloning a customized built-in element should succeed.');
+      }));
+      return;
+    }
+    test(() => {
+      let container = document.getElementById('container');
+      if (t.parsing == 'table') {
+        container.innerHTML = `<table><${t.tag} is="${name}" id="${name}">`;
+      } else {
+        container.innerHTML = `<${t.tag} is="${name}" id="${name}">`;
+      }
+      let customized = document.getElementById(name);
+      assert_equals(customized.constructor, klass);
+      assert_equals(customized.cloneNode().constructor, klass,
+                    'Cloning a customized built-in element should succeed.');
+    }, `${t.tag}: innerHTML should instantiate a customized built-in element`);
+
+  }, `${t.tag}: Define a customized built-in element`);
+}
+</script>
+</body>
+</html>
diff --git a/custom-elements/custom-element-registry/upgrade.html b/custom-elements/custom-element-registry/upgrade.html
new file mode 100644
index 0000000..e020d95
--- /dev/null
+++ b/custom-elements/custom-element-registry/upgrade.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>customElements.upgrade()</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-customelementregistry-upgrade">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+  const el = document.createElement("spider-man");
+
+  class SpiderMan extends HTMLElement {}
+  customElements.define("spider-man", SpiderMan);
+
+  assert_false(el instanceof SpiderMan, "The element must not yet be upgraded");
+
+  customElements.upgrade(el);
+  assert_true(el instanceof SpiderMan, "The element must now be upgraded");
+}, "Upgrading an element directly (example from the spec)");
+
+test(() => {
+  const el1 = document.createElement("element-a-1");
+  const el2 = document.createElement("element-a-2");
+  const container = document.createElement("div");
+  container.appendChild(el1);
+  container.appendChild(el2);
+
+  class Element1 extends HTMLElement {}
+  class Element2 extends HTMLElement {}
+  customElements.define("element-a-1", Element1);
+  customElements.define("element-a-2", Element2);
+
+  assert_false(el1 instanceof Element1, "element 1 must not yet be upgraded");
+  assert_false(el2 instanceof Element2, "element 2 must not yet be upgraded");
+
+  customElements.upgrade(container);
+  assert_true(el1 instanceof Element1, "element 1 must now be upgraded");
+  assert_true(el2 instanceof Element2, "element 2 must now be upgraded");
+}, "Two elements as children of the upgraded node");
+
+test(() => {
+  const el1 = document.createElement("element-b-1");
+  const el2 = document.createElement("element-b-2");
+  const container = document.createElement("div");
+  const subContainer = document.createElement("span");
+  const subSubContainer = document.createElement("span");
+  container.appendChild(subContainer);
+  subContainer.appendChild(el1);
+  subContainer.appendChild(subSubContainer);
+  subSubContainer.appendChild(el2);
+
+  class Element1 extends HTMLElement {}
+  class Element2 extends HTMLElement {}
+  customElements.define("element-b-1", Element1);
+  customElements.define("element-b-2", Element2);
+
+  assert_false(el1 instanceof Element1, "element 1 must not yet be upgraded");
+  assert_false(el2 instanceof Element2, "element 2 must not yet be upgraded");
+
+  customElements.upgrade(container);
+  assert_true(el1 instanceof Element1, "element 1 must now be upgraded");
+  assert_true(el2 instanceof Element2, "element 2 must now be upgraded");
+}, "Two elements as descendants of the upgraded node");
+
+test(() => {
+  const el1 = document.createElement("element-d-1");
+  const el2 = document.createElement("element-d-2");
+
+  const container = document.createElement("div");
+  const subContainer = document.createElement("span");
+  subContainer.attachShadow({ mode: "open" });
+  const subSubContainer = document.createElement("span");
+  subSubContainer.attachShadow({ mode: "open" });
+
+  container.appendChild(subContainer);
+  subContainer.shadowRoot.appendChild(el1);
+  subContainer.shadowRoot.appendChild(subSubContainer);
+  subSubContainer.shadowRoot.appendChild(el2);
+
+  class Element1 extends HTMLElement {}
+  class Element2 extends HTMLElement {}
+  customElements.define("element-d-1", Element1);
+  customElements.define("element-d-2", Element2);
+
+  assert_false(el1 instanceof Element1, "element 1 must not yet be upgraded");
+  assert_false(el2 instanceof Element2, "element 2 must not yet be upgraded");
+
+  customElements.upgrade(container);
+  assert_true(el1 instanceof Element1, "element 1 must now be upgraded");
+  assert_true(el2 instanceof Element2, "element 2 must now be upgraded");
+}, "Two elements as shadow-including descendants (and not descendants) of the upgraded node");
+
+test(() => {
+  const template = document.createElement("template");
+  template.innerHTML = `
+    <div>
+      <element-c-1></element-c-1>
+      <element-c-2>
+        <element-c-3></element-c-3>
+        <span>
+          <element-c-4></element-c-4>
+        </span>
+      </element-c-2>
+    </div>
+    <element-c-5></element-c-5>
+  `;
+
+  // This code feels repetitive but I tried to make it use loops and it became harder to see the correctness.
+
+  const el1 = template.content.querySelector("element-c-1");
+  const el2 = template.content.querySelector("element-c-2");
+  const el3 = template.content.querySelector("element-c-3");
+  const el4 = template.content.querySelector("element-c-4");
+  const el5 = template.content.querySelector("element-c-5");
+
+  class Element1 extends HTMLElement {}
+  class Element2 extends HTMLElement {}
+  class Element3 extends HTMLElement {}
+  class Element4 extends HTMLElement {}
+  class Element5 extends HTMLElement {}
+
+  customElements.define("element-c-1", Element1);
+  customElements.define("element-c-2", Element2);
+  customElements.define("element-c-3", Element3);
+  customElements.define("element-c-4", Element4);
+  customElements.define("element-c-5", Element5);
+
+  assert_false(el1 instanceof Element1, "element 1 must not yet be upgraded");
+  assert_false(el2 instanceof Element2, "element 2 must not yet be upgraded");
+  assert_false(el3 instanceof Element3, "element 3 must not yet be upgraded");
+  assert_false(el4 instanceof Element4, "element 4 must not yet be upgraded");
+  assert_false(el5 instanceof Element5, "element 5 must not yet be upgraded");
+
+  customElements.upgrade(template);
+
+  assert_false(el1 instanceof Element1, "element 1 must not yet be upgraded despite upgrading the template");
+  assert_false(el2 instanceof Element2, "element 2 must not yet be upgraded despite upgrading the template");
+  assert_false(el3 instanceof Element3, "element 3 must not yet be upgraded despite upgrading the template");
+  assert_false(el4 instanceof Element4, "element 4 must not yet be upgraded despite upgrading the template");
+  assert_false(el5 instanceof Element5, "element 5 must not yet be upgraded despite upgrading the template");
+
+  customElements.upgrade(template.content);
+
+  // Template contents owner documents don't have a browsing context, so
+  // https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-definition does not find any
+  // custom element definition.
+  assert_false(el1 instanceof Element1, "element 1 must still not be upgraded after upgrading the template contents");
+  assert_false(el2 instanceof Element2, "element 2 must still not be upgraded after upgrading the template contents");
+  assert_false(el3 instanceof Element3, "element 3 must still not be upgraded after upgrading the template contents");
+  assert_false(el4 instanceof Element4, "element 4 must still not be upgraded after upgrading the template contents");
+  assert_false(el5 instanceof Element5, "element 5 must still not be upgraded after upgrading the template contents");
+}, "Elements inside a template contents DocumentFragment node");
+</script>
diff --git a/custom-elements/htmlconstructor/newtarget.html b/custom-elements/htmlconstructor/newtarget.html
index ab43803..7dad264 100644
--- a/custom-elements/htmlconstructor/newtarget.html
+++ b/custom-elements/htmlconstructor/newtarget.html
@@ -124,5 +124,46 @@
   }, "If prototype is not object (" + notAnObject + "), derives the fallback from NewTarget's realm (customized built-in elements)");
 });
 
+test_with_window(w => {
+    class SomeCustomElement extends HTMLParagraphElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+    w.customElements.define("failure-counting-element", countingProxy);
+    // define() gets the prototype of the constructor it's passed, so
+    // reset the counter.
+    getCount = 0;
+    assert_throws({'name': 'TypeError'},
+                  function () { new countingProxy() },
+                  "Should not be able to construct an HTMLParagraphElement not named 'p'");
+    assert_equals(getCount, 0, "Should never have gotten .prototype");
+}, 'HTMLParagraphElement constructor must not get .prototype until it finishes its extends sanity checks, calling proxy constructor directly');
+
+test_with_window(w => {
+    class SomeCustomElement extends HTMLParagraphElement {};
+    var getCount = 0;
+    var countingProxy = new Proxy(SomeCustomElement, {
+        get: function(target, prop, receiver) {
+            if (prop == "prototype") {
+                ++getCount;
+            }
+            return Reflect.get(target, prop, receiver);
+        }
+    });
+    w.customElements.define("failure-counting-element", countingProxy);
+    // define() gets the prototype of the constructor it's passed, so
+    // reset the counter.
+    getCount = 0;
+    assert_throws({'name': 'TypeError'},
+                  function () { Reflect.construct(HTMLParagraphElement, [], countingProxy) },
+                  "Should not be able to construct an HTMLParagraphElement not named 'p'");
+    assert_equals(getCount, 0, "Should never have gotten .prototype");
+}, 'HTMLParagraphElement constructor must not get .prototype until it finishes its extends sanity checks, calling via Reflect');
 </script>
-</body>
\ No newline at end of file
+</body>
diff --git a/custom-elements/parser/parser-constructs-custom-elements-with-is.html b/custom-elements/parser/parser-constructs-custom-elements-with-is.html
new file mode 100644
index 0000000..96c0027
--- /dev/null
+++ b/custom-elements/parser/parser-constructs-custom-elements-with-is.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: Changes to the HTML parser</title>
+<meta name="author" title="John Dai" href="mailto:jdai@mozilla.com">
+<meta name="assert" content="HTML parser creates a custom element which contains is attribute">
+<link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<autonomous-custom-element id="instance1" is="is-custom-element"></autonomous-custom-element>
+<script>
+
+class AutonomousCustomElement extends HTMLElement { };
+class IsCustomElement extends HTMLElement { };
+
+customElements.define('autonomous-custom-element', AutonomousCustomElement);
+customElements.define('is-custom-element', IsCustomElement);
+
+test(function () {
+    var customElement = document.getElementById('instance1');
+
+    assert_true(customElement instanceof HTMLElement, 'A resolved custom element must be an instance of HTMLElement');
+    assert_false(customElement instanceof HTMLUnknownElement, 'A resolved custom element must NOT be an instance of HTMLUnknownElement');
+    assert_true(customElement instanceof AutonomousCustomElement, 'A resolved custom element must be an instance of that custom element');
+    assert_equals(customElement.localName, 'autonomous-custom-element');
+    assert_equals(customElement.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
+
+}, 'HTML parser must create a defined autonomous custom element when customElements.define comes after HTML parser creation');
+
+</script>
+<autonomous-custom-element id="instance2" is="is-custom-element"></autonomous-custom-element>
+<script>
+
+test(function () {
+    var customElement = document.getElementById('instance2');
+
+    assert_true(customElement instanceof HTMLElement, 'A resolved custom element must be an instance of HTMLElement');
+    assert_false(customElement instanceof HTMLUnknownElement, 'A resolved custom element must NOT be an instance of HTMLUnknownElement');
+    assert_true(customElement instanceof AutonomousCustomElement, 'A resolved custom element must be an instance of that custom element');
+    assert_equals(customElement.localName, 'autonomous-custom-element');
+    assert_equals(customElement.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
+
+}, 'HTML parser must create a defined autonomous custom element when customElements.define comes before HTML parser creation');
+
+</script>
+</body>
+</html>
diff --git a/custom-elements/parser/parser-sets-attributes-and-children.html b/custom-elements/parser/parser-sets-attributes-and-children.html
index ba33137..987bf85 100644
--- a/custom-elements/parser/parser-sets-attributes-and-children.html
+++ b/custom-elements/parser/parser-sets-attributes-and-children.html
@@ -5,16 +5,21 @@
 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
 <meta name="assert" content="HTML parser must set the attributes and append the children on a custom element">
 <link rel="help" href="https://html.spec.whatwg.org/#create-an-element-for-the-token">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/parsing.html#insert-a-foreign-element">
 <link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
 </head>
 <body>
 <div id="log"></div>
 <script>
 
-var numberOfAttributesInConstructor;
-var numberOfChildNodesInConstructor;
+var numberOfAttributesInConstructor = 0;
+var numberOfChildNodesInConstructor = 0;
+var numberOfChildNodesInAttributeChangedCallback = 0;
+var numberOfChildNodesInConnectedCallback = 0;
+var attributesChangedCalls = [];
 
 class MyCustomElement extends HTMLElement {
     constructor(...args) {
@@ -22,6 +27,19 @@
         numberOfAttributesInConstructor = this.attributes.length;
         numberOfChildNodesInConstructor = this.childNodes.length;
     }
+
+    attributeChangedCallback(...args) {
+        attributesChangedCalls.push(create_attribute_changed_callback_log(this, ...args));
+        numberOfChildNodesInAttributeChangedCallback = this.childNodes.length;
+    }
+
+    static get observedAttributes() {
+        return ['id', 'class'];
+    }
+
+    connectedCallback() {
+        numberOfChildNodesInConnectedCallback = this.childNodes.length;
+    }
 };
 customElements.define('my-custom-element', MyCustomElement);
 
@@ -54,6 +72,24 @@
     assert_equals(numberOfChildNodesInConstructor, 0, 'HTML parser must not append child nodes to a custom element before invoking the constructor');
 }, 'HTML parser must set the attributes or append children before calling constructor');
 
+test(function () {
+    // https://html.spec.whatwg.org/multipage/parsing.html#insert-a-foreign-element
+    // 3.3. Pop the element queue from the custom element reactions
+    // stack, and invoke custom element reactions in that queue.
+    assert_equals(numberOfChildNodesInConnectedCallback, 0);
+}, 'HTML parser should call connectedCallback before appending child nodes.');
+
+test(function () {
+    assert_equals(attributesChangedCalls.length, 2);
+    assert_attribute_log_entry(attributesChangedCalls[0], {name: 'id', oldValue: null, newValue: 'custom-element-id', namespace: null});
+    assert_attribute_log_entry(attributesChangedCalls[1], {name: 'class', oldValue: null, newValue: 'class1 class2', namespace: null});
+    // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token
+    // 9.2. Invoke custom element reactions in queue.
+    assert_equals(numberOfChildNodesInAttributeChangedCallback, 0,
+                  'attributeChangedCallback should be called ' +
+                      'before appending a child');
+}, 'HTML parser must enqueue attributeChanged reactions');
+
 </script>
 </body>
 </html>
diff --git a/custom-elements/parser/parser-uses-create-an-element-for-a-token-svg.svg b/custom-elements/parser/parser-uses-create-an-element-for-a-token-svg.svg
new file mode 100644
index 0000000..526de0f
--- /dev/null
+++ b/custom-elements/parser/parser-uses-create-an-element-for-a-token-svg.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg:svg xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/1999/xhtml"
+   width="100%" height="100%" viewBox="0 0 800 600">
+<svg:title>XML parser should use "create an element for a token"</svg:title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/xhtml.html#parsing-xhtml-documents:create-an-element-for-the-token"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script><![CDATA[
+class MyElement1 extends HTMLElement {}
+customElements.define('my-element', MyElement1);
+class MyElement2 extends HTMLDivElement {}
+customElements.define('my-div', MyElement2, { extends: 'div' });
+
+var test1 = async_test('XML parser should create autonomous custom elements.');
+window.addEventListener('load', test1.step_func_done(() => {
+  assert_true(document.getElementById('me1') instanceof MyElement1);
+}));
+
+var test2 = async_test('XML parser should create custom built-in elements.');
+window.addEventListener('load', test2.step_func_done(() => {
+  assert_true(document.getElementById('me2') instanceof MyElement2);
+}));
+]]></script>
+<my-element id="me1"></my-element>
+<div is="my-div" id="me2"></div>
+</svg:svg>
diff --git a/custom-elements/parser/parser-uses-registry-of-owner-document.html b/custom-elements/parser/parser-uses-registry-of-owner-document.html
index efdc3a2..51e3e5d 100644
--- a/custom-elements/parser/parser-uses-registry-of-owner-document.html
+++ b/custom-elements/parser/parser-uses-registry-of-owner-document.html
@@ -8,6 +8,7 @@
 <link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
 </head>
 <body>
 <div id="log"></div>
@@ -71,17 +72,14 @@
 document.body.removeChild(iframe);
 
 test(function () {
-    var windowlessDocument = document.implementation.createHTMLDocument();
-    windowlessDocument.open();
-    windowlessDocument.write('<my-custom-element></my-custom-element>');
-    windowlessDocument.close();
+    var windowlessDocument = (new DOMParser()).parseFromString('<my-custom-element></my-custom-element>', "text/html");
 
     var instance = windowlessDocument.querySelector('my-custom-element');
 
     assert_true(instance instanceof HTMLElement);
     assert_false(instance instanceof MyCustomElement);
 
-}, 'HTML parser must use the registry of window.document in a document created by document.implementation.createHTMLDocument()');
+}, 'HTML parser must use the registry of window.document in a document created by DOMParser');
 
 test(function () {
     var windowlessDocument = document.implementation.createDocument ('http://www.w3.org/1999/xhtml', 'html', null);
@@ -108,19 +106,51 @@
 promise_test(function () {
     return new Promise(function (resolve, reject) {
         var xhr = new XMLHttpRequest();
-        xhr.open('GET', '../resources/empty-html-document.html');
+        xhr.open('GET', '../resources/my-custom-element-html-document.html');
         xhr.overrideMimeType('text/xml');
         xhr.onload = function () { resolve(xhr.responseXML); }
         xhr.onerror = function () { reject('Failed to fetch the document'); }
         xhr.send();
     }).then(function (doc) {
-        doc.documentElement.innerHTML = '<my-custom-element></my-custom-element>';
         var instance = doc.querySelector('my-custom-element');
         assert_true(instance instanceof Element);
-        assert_false(instance instanceof MyCustomElement);        
+        assert_false(instance instanceof MyCustomElement);
+
+        doc.documentElement.innerHTML = '<my-custom-element></my-custom-element>';
+        var instance2 = doc.querySelector('my-custom-element');
+        assert_true(instance2 instanceof Element);
+        assert_false(instance2 instanceof MyCustomElement);
     });
 }, 'HTML parser must use the registry of window.document in a document created by XMLHttpRequest');
 
+test_with_window(function (contentWindow, contentDocument) {
+    const element = define_custom_element_in_window(contentWindow, 'my-custom-element', []);
+    // document-open-steps spec doesn't match most browsers; see https://github.com/whatwg/html/issues/1698.
+    // However, as explained in https://github.com/whatwg/html/issues/1698#issuecomment-298748641
+    // the custom element registry will be replaced after document-open-steps.
+    contentDocument.write('<my-custom-element></my-custom-element>');
+
+    var instance = contentDocument.querySelector('my-custom-element');
+
+    assert_true(instance instanceof contentWindow.HTMLElement);
+    assert_false(instance instanceof element.class);
+
+}, 'document.write() must not instantiate a custom element without a defined insertion point');
+
+test_with_window(function (contentWindow, contentDocument) {
+    const element = define_custom_element_in_window(contentWindow, 'my-custom-element', []);
+    // document-open-steps spec doesn't match most browsers; see https://github.com/whatwg/html/issues/1698.
+    // However, as explained in https://github.com/whatwg/html/issues/1698#issuecomment-298748641
+    // the custom element registry will be replaced after document-open-steps.
+    contentDocument.writeln('<my-custom-element></my-custom-element>');
+
+    var instance = contentDocument.querySelector('my-custom-element');
+
+    assert_true(instance instanceof contentWindow.HTMLElement);
+    assert_false(instance instanceof element.class);
+
+}, 'document.writeln() must not instantiate a custom element without a defined insertion point');
+
 </script>
 </body>
 </html>
diff --git a/custom-elements/parser/serializing-html-fragments.html b/custom-elements/parser/serializing-html-fragments.html
new file mode 100644
index 0000000..6992dd6
--- /dev/null
+++ b/custom-elements/parser/serializing-html-fragments.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/parsing.html#serialising-html-fragments">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div id="container"></div>
+<script>
+test(() => {
+  class MyParagraph extends HTMLParagraphElement {}
+  customElements.define('my-p', MyParagraph, { extends: 'p' });
+
+  let p = new MyParagraph();
+  p.setAttribute('class', 'foo');
+  assert_equals(p.outerHTML, '<p is="my-p" class="foo"></p>');
+
+  let container = document.querySelector('#container');
+  container.appendChild(p);
+  container.innerHTML = container.innerHTML;
+  assert_not_equals(container.firstChild, p);
+  assert_true(container.firstChild instanceof MyParagraph);
+}, '"is" value should be serialized if the custom element has no "is" content attribute');
+
+test(() => {
+  let p = document.createElement('p', { is: 'your-p' });
+  assert_equals(p.outerHTML, '<p is="your-p"></p>');
+}, '"is" value should be serialized even for an undefined element');
+
+test(() => {
+  class MyDiv extends HTMLDivElement {}
+  customElements.define('my-div', MyDiv, { extends: 'div' });
+
+  let div = document.createElement('div', { is: 'my-div' });
+  div.setAttribute('is', 'foo"bar\n');
+  assert_equals(div.outerHTML, '<div is="foo&quot;bar\n"></div>');
+}, '"is" content attribute should be serialized even if the element is a customized built-in element');
+</script>
+</body>
diff --git a/custom-elements/pseudo-class-defined.html b/custom-elements/pseudo-class-defined.html
new file mode 100644
index 0000000..60d88cf
--- /dev/null
+++ b/custom-elements/pseudo-class-defined.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics-other.html#selector-defined">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="iframe"></iframe>
+<script>
+const testList = [
+  { tag_name: 'div', defined: true },
+  { tag_name: 'a-a', defined: false },
+  { tag_name: 'font-face', defined: true },
+  { tag_name: 'abbr', is: 'my-abbr', defined: false },
+  { tag_name: 'p', is: '', defined: false },
+];
+
+// Setup iframe to test the parser.
+const neither = 'rgb(255, 0, 0)';
+const defined = 'rgb(255, 165, 0)';
+const not_defined = 'rgb(0, 0, 255)';
+iframe.srcdoc = `<style>
+  * { color:${neither}; }
+  :defined { color:${defined}; }
+  :not(:defined) { color:${not_defined}; }
+</style>`
+  + testList.map(d => `<${d.tag_name}${d.is !== undefined ? ' is=' + d.is : ''}></${d.tag_name}>`).join('');
+setup({ explicit_done: true });
+iframe.onload = () => {
+  const doc = iframe.contentDocument;
+  const doc_without_browsing_context = doc.implementation.createHTMLDocument();
+  for (const data of testList) {
+    // Test elements inserted by parser.
+    test_defined(data.defined, doc.getElementsByTagName(data.tag_name)[0],
+                 `<${data.tag_name}${data.is ? ' is=' + data.is : ''}>`);
+
+    // Test DOM createElement() methods.
+    let try_upgrade = !data.defined && (data.is === undefined || data.is.length > 0);
+    test_defined_for_createElement(data.defined, try_upgrade, doc, data.tag_name, data.is);
+
+    // Documents without browsing context should behave the same.
+    test_defined_for_createElement(data.defined, false, doc_without_browsing_context, data.tag_name, data.is, 'Without browsing context: ');
+  }
+
+  done();
+};
+
+function test_defined_for_createElement(defined, should_test_change, doc, tag_name, is, description = '') {
+  let has_is = is !== undefined;
+  let is_desc = has_is ? `, { is: "${is}" }` : '';
+  // Test document.createElement().
+  let element = has_is ? doc.createElement(tag_name, { is: is }) : doc.createElement(tag_name);
+  doc.body.appendChild(element);
+  test_defined(defined, element, `${description}createElement("${tag_name}"${is_desc})`);
+
+  // Test document.createElementNS().
+  let html_element = has_is ? doc.createElementNS('http://www.w3.org/1999/xhtml', tag_name, { is: is })
+                            : doc.createElementNS('http://www.w3.org/1999/xhtml', tag_name);
+  doc.body.appendChild(html_element);
+  test_defined(defined, html_element, `${description}createElementNS("http://www.w3.org/1999/xhtml", "${tag_name}"${is_desc})`);
+
+  // If the element namespace is not HTML, it should be "uncustomized"; i.e., "defined".
+  let svg_element = has_is ? doc.createElementNS('http://www.w3.org/2000/svg', tag_name, { is: is })
+                           : doc.createElementNS('http://www.w3.org/2000/svg', tag_name);
+  doc.body.appendChild(svg_element);
+  test_defined(true, svg_element, `${description}createElementNS("http://www.w3.org/2000/svg", "${tag_name}"${is_desc})`);
+
+  // Test ":defined" changes when the custom element was defined.
+  if (should_test_change) {
+    let w = doc.defaultView;
+    assert_false(!w, 'defaultView required to test change');
+    if (is) {
+      w.customElements.define(is, class extends w.HTMLElement {}, { extends: tag_name });
+    } else {
+      w.customElements.define(tag_name, class extends w.HTMLElement {
+        constructor() { super(); }
+      });
+    }
+
+    test_defined(true, element, `Upgraded ${description}createElement("${tag_name}"${is_desc})`);
+    test_defined(true, html_element, `Upgraded ${description}createElementNS("http://www.w3.org/1999/xhtml", "${tag_name}"${is_desc})`);
+  }
+}
+
+function test_defined(expected, element, description) {
+  test(() => {
+    assert_equals(element.matches(':defined'), expected, 'matches(":defined")');
+    assert_equals(element.matches(':not(:defined)'), !expected, 'matches(":not(:defined")');
+    const view = element.ownerDocument.defaultView;
+    if (!view)
+      return;
+    const style = view.getComputedStyle(element);
+    assert_equals(style.color, expected ? defined : not_defined, 'getComputedStyle');
+  }, `${description} should ${expected ? 'be' : 'not be'} :defined`);
+}
+</script>
diff --git a/custom-elements/reactions/CSSStyleDeclaration.html b/custom-elements/reactions/CSSStyleDeclaration.html
index bf9e00a..95274d8 100644
--- a/custom-elements/reactions/CSSStyleDeclaration.html
+++ b/custom-elements/reactions/CSSStyleDeclaration.html
@@ -26,13 +26,17 @@
     instance.style.setProperty(propertyName, instance.style[idlName], isImportant ? 'important': '');
 }, 'setProperty on CSSStyleDeclaration');
 
-test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
-    instance.style.setPropertyValue(propertyName, value);
-}, 'setPropertyValue on CSSStyleDeclaration');
+if (CSSStyleDeclaration.prototype.setPropertyValue) {
+    test_mutating_style_property_value(function (instance, propertyName, idlName, value) {
+        instance.style.setPropertyValue(propertyName, value);
+    }, 'setPropertyValue on CSSStyleDeclaration');
+}
 
-test_mutating_style_property_priority(function (instance, propertyName, idlName, isImportant) {
-    instance.style.setPropertyPriority(propertyName, isImportant ? 'important': '');
-}, 'setPropertyPriority on CSSStyleDeclaration');
+if (CSSStyleDeclaration.prototype.setPropertyPriority) {
+    test_mutating_style_property_priority(function (instance, propertyName, idlName, isImportant) {
+        instance.style.setPropertyPriority(propertyName, isImportant ? 'important': '');
+    }, 'setPropertyPriority on CSSStyleDeclaration');
+}
 
 test_removing_style_property_value(function (instance, propertyName, idlName) {
     instance.style.removeProperty(propertyName);
diff --git a/custom-elements/reactions/Document.html b/custom-elements/reactions/Document.html
index 98c642b..721ad1f 100644
--- a/custom-elements/reactions/Document.html
+++ b/custom-elements/reactions/Document.html
@@ -129,6 +129,12 @@
 }, 'write on Document must enqueue disconnectedCallback when removing a custom element');
 
 test_with_window(function (contentWindow, contentDocument) {
+    contentWindow.document.open();
+    // document.open()'s spec doesn't match most browsers; see https://github.com/whatwg/html/issues/1698.
+    // However, as explained in https://github.com/whatwg/html/issues/1698#issuecomment-298748641
+    // the custom element registry will be replaced after document.open() call,
+    // So call customElements.define() after that in order to register defintion
+    // to correct custom elements registry.
     const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
     contentWindow.document.write('<custom-element></custom-element>');
     assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
@@ -144,6 +150,12 @@
 }, 'writeln on Document must enqueue disconnectedCallback when removing a custom element');
 
 test_with_window(function (contentWindow) {
+    contentWindow.document.open();
+    // document.open()'s spec doesn't match most browsers; see https://github.com/whatwg/html/issues/1698.
+    // However, as explained in https://github.com/whatwg/html/issues/1698#issuecomment-298748641
+    // the custom element registry will be replaced after document.open() call,
+    // So call customElements.define() after that in order to register defintion
+    // to correct custom elements registry.
     const element = define_custom_element_in_window(contentWindow, 'custom-element', []);
     contentWindow.document.writeln('<custom-element></custom-element>');
     assert_array_equals(element.takeLog().types(), ['constructed', 'connected']);
diff --git a/custom-elements/reactions/HTMLElement.html b/custom-elements/reactions/HTMLElement.html
index bce4ef1..5fe422c 100644
--- a/custom-elements/reactions/HTMLElement.html
+++ b/custom-elements/reactions/HTMLElement.html
@@ -22,8 +22,6 @@
 testReflectAttribute('tabIndex', 'tabindex', '0', '1', 'tabIndex on HTMLElement');
 testReflectAttribute('accessKey', 'accesskey', 'a', 'b', 'accessKey on HTMLElement');
 testReflectAttributeWithContentValues('draggable', 'draggable', true, 'true', false, 'false', 'draggable on HTMLElement');
-testReflectAttribute('dropzone', 'dropzone', 'copy', 'move', 'dropzone on HTMLElement');
-testReflectAttribute('contextMenu', 'contextmenu', 'menu1', 'menu2', 'contextMenu on HTMLElement');
 testReflectAttributeWithContentValues('spellcheck', 'spellcheck', true, 'true', false, 'false', 'spellcheck on HTMLElement');
 
 testNodeDisconnector(function (customElement) {
diff --git a/custom-elements/reactions/HTMLInputElement.html b/custom-elements/reactions/HTMLInputElement.html
index 8193d2c..dc4b22a 100644
--- a/custom-elements/reactions/HTMLInputElement.html
+++ b/custom-elements/reactions/HTMLInputElement.html
@@ -11,51 +11,56 @@
 <script src="./resources/reactions.js"></script>
 <body>
 <script>
-test(() => {
-    const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
-    const instance = document.createElement('input', { is: element.name });
+if (HTMLInputElement.prototype.capture) {
+    test(() => {
+        const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+        const instance = document.createElement('input', { is: element.name });
 
-    assert_array_equals(element.takeLog().types(), ['constructed']);
-    instance['capture'] = 'user';
-    const logEntries = element.takeLog();
-    assert_array_equals(logEntries.types(), ['attributeChanged']);
-    assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: '', newValue: 'user', namespace: null});
-}, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding new attribute');
+        assert_array_equals(element.takeLog().types(), ['constructed']);
+        instance['capture'] = 'user';
+        const logEntries = element.takeLog();
+        assert_array_equals(logEntries.types(), ['attributeChanged']);
+        assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: '', newValue: 'user', namespace: null});
+    }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding new attribute');
 
-test(() => {
-    const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
-    const instance = document.createElement('input', { is: element.name });
+    test(() => {
+        const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+        const instance = document.createElement('input', { is: element.name });
 
-    instance['capture'] = 'user';
-    assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
-    instance['capture'] = 'environment';
-    const logEntries = element.takeLog();
-    assert_array_equals(logEntries.types(), ['attributeChanged']);
-    assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: 'user', newValue: 'environment', namespace: null});
-}, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when replacing an existing attribute');
+        instance['capture'] = 'user';
+        assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+        instance['capture'] = 'environment';
+        const logEntries = element.takeLog();
+        assert_array_equals(logEntries.types(), ['attributeChanged']);
+        assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: 'user', newValue: 'environment', namespace: null});
+    }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when replacing an existing attribute');
 
-test(() => {
-    const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
-    const instance = document.createElement('input', { is: element.name });
+    test(() => {
+        const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+        const instance = document.createElement('input', { is: element.name });
 
-    assert_array_equals(element.takeLog().types(), ['constructed']);
-    instance['capture'] = 'asdf';
-    const logEntries = element.takeLog();
-    assert_array_equals(logEntries.types(), ['attributeChanged']);
-    assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: '', newValue: 'asdf', namespace: null});
-}, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding invalid value default');
+        assert_array_equals(element.takeLog().types(), ['constructed']);
+        instance['capture'] = 'asdf';
+        const logEntries = element.takeLog();
+        assert_array_equals(logEntries.types(), ['attributeChanged']);
+        assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: '', newValue: 'asdf', namespace: null});
+    }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding invalid value default');
 
+    test(() => {
+        const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
+        const instance = document.createElement('input', { is: element.name });
 
-test(() => {
-    const element = define_build_in_custom_element(['capture'], HTMLInputElement, 'input');
-    const instance = document.createElement('input', { is: element.name });
-
-    instance['capture'] = 'user';
-    assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
-    instance['capture'] = '';
-    const logEntries = element.takeLog();
-    assert_array_equals(logEntries.types(), ['attributeChanged']);
-    assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: 'user', newValue: '', namespace: null});
-}, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when removing the attribute');
+        instance['capture'] = 'user';
+        assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']);
+        instance['capture'] = '';
+        const logEntries = element.takeLog();
+        assert_array_equals(logEntries.types(), ['attributeChanged']);
+        assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: 'user', newValue: '', namespace: null});
+    }, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when removing the attribute');
+} else {
+    // testharness.js doesn't allow a test file with no tests.
+    test(() => {
+    }, 'No tests if HTMLInputEement has no "capture" IDL attribute');
+}
 </script>
 </body>
diff --git a/custom-elements/reactions/with-exceptions.html b/custom-elements/reactions/with-exceptions.html
new file mode 100644
index 0000000..82e0f59
--- /dev/null
+++ b/custom-elements/reactions/with-exceptions.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Custom Elements: CEReactions interaction with exceptions</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<meta name="help" content="https://html.spec.whatwg.org/multipage/#cereactions">
+<meta name="help" content="https://github.com/whatwg/html/pull/3235">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+// Basically from https://github.com/whatwg/html/issues/3217#issuecomment-343633273
+test_with_window((contentWindow, contentDocument) => {
+  let reactionRan = false;
+  contentWindow.customElements.define("custom-element", class extends contentWindow.HTMLElement {
+    disconnectedCallback() {
+      reactionRan = true;
+    }
+  });
+  const text = contentDocument.createTextNode("");
+  contentDocument.documentElement.appendChild(text);
+  const element = contentDocument.createElement("custom-element");
+  contentDocument.documentElement.appendChild(element);
+  assert_throws("HierarchyRequestError", () => text.before("", contentDocument.documentElement));
+  assert_true(reactionRan);
+}, "Reaction must run even after the exception is thrown");
+</script>
diff --git a/custom-elements/resources/my-custom-element-html-document.html b/custom-elements/resources/my-custom-element-html-document.html
new file mode 100644
index 0000000..b9bfdf9
--- /dev/null
+++ b/custom-elements/resources/my-custom-element-html-document.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<my-custom-element></my-custom-element>
+</body>
+</html>
diff --git a/custom-elements/upgrading/Document-importNode.html b/custom-elements/upgrading/Document-importNode.html
new file mode 100644
index 0000000..b80f906
--- /dev/null
+++ b/custom-elements/upgrading/Document-importNode.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-document-importnode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/custom-elements-helpers.js"></script>
+<body>
+<script>
+test_with_window((w, doc) => {
+  class MyElement extends HTMLElement {}
+  class MyElement2 extends w.HTMLElement {}
+  customElements.define('my-element', MyElement);
+  w.customElements.define('my-element', MyElement2);
+
+  let original = document.createElement('my-element');
+  assert_true(original instanceof MyElement);
+
+  let imported = doc.importNode(original);
+  assert_true(imported instanceof MyElement2);
+}, 'autonomous: document.importNode() should import custom elements successfully');
+
+test_with_window((w, doc) => {
+  class MyElement3 extends w.HTMLElement {}
+  w.customElements.define('my-element3', MyElement3);
+
+  let original = document.createElement('my-element3');
+  assert_equals(original.constructor, HTMLElement);
+
+  let imported = doc.importNode(original);
+  assert_true(imported instanceof MyElement3);
+}, 'autonomous: document.importNode() should import "undefined" custom elements successfully');
+
+test_with_window((w, doc) => {
+  class MyDiv extends HTMLDivElement {}
+  class MyDiv2 extends w.HTMLDivElement {}
+  customElements.define('my-div', MyDiv, { extends: 'div' });
+  w.customElements.define('my-div', MyDiv2, { extends: 'div' });
+
+  let original = document.createElement('div', { is: 'my-div' });
+  assert_true(original instanceof MyDiv);
+
+  let imported = doc.importNode(original);
+  assert_true(imported instanceof MyDiv2);
+}, 'built-in: document.importNode() should import custom elements successfully');
+
+test_with_window((w, doc) => {
+  class MyDiv2 extends w.HTMLDivElement {}
+  w.customElements.define('my-div2', MyDiv2, { extends: 'div' });
+
+  let original = document.createElement('div', { is: 'my-div2' });
+  assert_equals(original.constructor, HTMLDivElement);
+
+  let imported = doc.importNode(original);
+  assert_true(imported instanceof MyDiv2);
+}, 'built-in: document.importNode() should import "undefined" custom elements successfully');
+</script>
+</body>
diff --git a/custom-elements/upgrading/Node-cloneNode.html b/custom-elements/upgrading/Node-cloneNode.html
index 0d158fd..1a8786e 100644
--- a/custom-elements/upgrading/Node-cloneNode.html
+++ b/custom-elements/upgrading/Node-cloneNode.html
@@ -30,6 +30,54 @@
         'A cloned custom element must be an instance of the custom element');
 }, 'Node.prototype.cloneNode(false) must be able to clone a custom element');
 
+test(function () {
+    class AutonomousCustomElement extends HTMLElement {};
+    class IsCustomElement extends HTMLElement {};
+
+    customElements.define('autonomous-custom-element', AutonomousCustomElement);
+    customElements.define('is-custom-element', IsCustomElement);
+
+    var instance = document.createElement('autonomous-custom-element', { is: "is-custom-element"});
+    assert_true(instance instanceof HTMLElement);
+    assert_true(instance instanceof AutonomousCustomElement);
+
+    var clone = instance.cloneNode(false);
+    assert_not_equals(instance, clone);
+    assert_true(clone instanceof HTMLElement,
+        'A cloned custom element must be an instance of HTMLElement');
+    assert_true(clone instanceof AutonomousCustomElement,
+        'A cloned custom element must be an instance of the custom element');
+}, 'Node.prototype.cloneNode(false) must be able to clone as a autonomous custom element when it contains is attribute');
+
+test(function () {
+    class MyDiv1 extends HTMLDivElement {};
+    class MyDiv2 extends HTMLDivElement {};
+    class MyDiv3 extends HTMLDivElement {};
+    customElements.define('my-div1', MyDiv1, { extends: 'div' });
+    customElements.define('my-div2', MyDiv2, { extends: 'div' });
+
+    let instance = document.createElement('div', { is: 'my-div1'});
+    assert_true(instance instanceof MyDiv1);
+    instance.setAttribute('is', 'my-div2');
+    let clone = instance.cloneNode(false);
+    assert_not_equals(instance, clone);
+    assert_true(clone instanceof MyDiv1,
+        'A cloned custom element must be an instance of the custom element even with an inconsistent "is" attribute');
+
+    let instance3 = document.createElement('div', { is: 'my-div3'});
+    assert_false(instance3 instanceof MyDiv3);
+    instance3.setAttribute('is', 'my-div2');
+    let clone3 = instance3.cloneNode(false);
+    assert_not_equals(instance3, clone);
+    customElements.define('my-div3', MyDiv3, { extends: 'div' });
+    document.body.appendChild(instance3);
+    document.body.appendChild(clone3);
+    assert_true(instance3 instanceof MyDiv3,
+        'An undefined element must be upgraded even with an inconsistent "is" attribute');
+    assert_true(clone3 instanceof MyDiv3,
+        'A cloned undefined element must be upgraded even with an inconsistent "is" attribute');
+}, 'Node.prototype.cloneNode(false) must be able to clone as a customized built-in element when it has an inconsistent "is" attribute');
+
 test_with_window(function (contentWindow) {
     var contentDocument = contentWindow.document;
     class MyCustomElement extends contentWindow.HTMLElement {}
diff --git a/device-memory/OWNERS b/device-memory/OWNERS
new file mode 100644
index 0000000..3e7089c
--- /dev/null
+++ b/device-memory/OWNERS
@@ -0,0 +1,2 @@
+@tdresser
+@npm1
diff --git a/device-memory/device-memory.https.any.js b/device-memory/device-memory.https.any.js
new file mode 100644
index 0000000..8f81ffc
--- /dev/null
+++ b/device-memory/device-memory.https.any.js
@@ -0,0 +1,8 @@
+test(function() {
+    assert_equals(typeof navigator.deviceMemory, "number",
+        "navigator.deviceMemory returns a number");
+    assert_true(navigator.deviceMemory >= 0,
+        "navigator.deviceMemory returns a positive value");
+    assert_true([0.25, 0.5, 1, 2, 4, 8].includes(navigator.deviceMemory),
+        "navigator.deviceMemory returns a power of 2 between 0.25 and 8");
+}, "navigator.deviceMemory is a positive number, a power of 2, between 0.25 and 8");
\ No newline at end of file
diff --git a/docs/_config.yml b/docs/_config.yml
index c70d94b..91ece99 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -41,3 +41,5 @@
   - vendor/bundle/
   - .bundle/
   - OWNERS
+sass:
+    sass_dir: assets/
diff --git a/docs/_includes/header.html b/docs/_includes/header.html
index 150d616..6c96640 100644
--- a/docs/_includes/header.html
+++ b/docs/_includes/header.html
@@ -1,5 +1,7 @@
 <header class="site-header" role="banner">
 
+  <a class="github-fork-ribbon left-top" href="https://github.com/w3c/web-platform-tests/blob/master/docs/{{ page.path }}" data-ribbon="Edit on GitHub">Edit on GitHub</a>
+
   <div class="wrapper">
 
     <a class="site-title" href="{{ "/" | relative_url}}">{{ site.title | escape }}</a>
diff --git a/docs/_running-tests/chrome.md b/docs/_running-tests/chrome.md
new file mode 100644
index 0000000..b3b9874
--- /dev/null
+++ b/docs/_running-tests/chrome.md
@@ -0,0 +1,41 @@
+---
+layout: page
+title: Chrome
+---
+When running Chrome, there are some additional useful command line arguments.
+
+As with most products, you can use a different binary with `--binary`, e.g., to
+run Chrome Dev on Linux:
+
+```
+./wpt run --binary `which google-chrome-unstable` chrome
+```
+
+Extra arguments to Chrome can be passed with `--binary-args`.
+
+To enable all [experimental web platform features](https://www.chromium.org/blink/runtime-enabled-features) (chrome://flags/#enable-experimental-web-platform-features):
+
+```
+./wpt run --binary-arg=--enable-experimental-web-platform-features chrome fullscreen/
+```
+
+To enable a specific [runtime enabled feature](http://dev.chromium.org/blink/runtime-enabled-features):
+
+```
+./wpt run --binary-arg=--enable-blink-features=AsyncClipboard chrome clipboard-apis/
+```
+
+To bypass device selection and use mock media for tests using `getUserMedia`:
+
+```
+./wpt run --binary-arg=--use-fake-ui-for-media-stream --binary-arg=--use-fake-device-for-media-stream chrome mediacapture-streams/
+```
+
+Note: there's an [open issue for doing this using WebDriver](https://github.com/w3c/web-platform-tests/issues/7424).
+
+Some of the above are most useful in combination, e.g., to run all tests in
+Chrome Dev with experimental web platform features enabled:
+
+```
+./wpt run --binary `which google-chrome-unstable` --binary-arg=--enable-experimental-web-platform-features chrome
+```
diff --git a/docs/_running-tests/chrome_android.md b/docs/_running-tests/chrome_android.md
new file mode 100644
index 0000000..8af8aae
--- /dev/null
+++ b/docs/_running-tests/chrome_android.md
@@ -0,0 +1,44 @@
+---
+layout: page
+title: Chrome for Android
+---
+To run WPT on Chrome on an Android device, some additional set up is required.
+
+First of all, as usual Android development, we need to have `adb` and be able to
+connect to the device.
+
+## Hosts
+
+Until we find a better way, we need to root the Android device and update the
+/etc/hosts file to include
+
+```
+127.0.0.1   web-platform.test
+127.0.0.1   www.web-platform.test
+127.0.0.1   www1.web-platform.test
+127.0.0.1   www2.web-platform.test
+127.0.0.1   xn--n8j6ds53lwwkrqhv28a.web-platform.test
+127.0.0.1   xn--lve-6lad.web-platform.test
+```
+
+## CA certificate
+
+In order to run HTTPS tests, we need to add
+[WPT's CA](https://github.com/w3c/web-platform-tests/blob/master/tools/certs/cacert.pem)
+to the phone. First, convert the certificate from PEM to CRT:
+
+```
+openssl x509 -outform der -in tools/certs/cacert.pem -out cacert.crt
+```
+
+Then copy `cacert.crt` to your phone's external storage (preferably to
+Downloads/ as it'll be easier to find). Open Settings -> Security & location ->
+Encryption & credentials -> Install from storage. Find and install `cacert.crt`.
+(The setting entries might be slightly different based your Android version.)
+
+
+Finally, we may run wpt with the `chrome_android` product
+
+```
+./wpt run chrome_android [test_list]
+```
diff --git a/docs/_running-tests/index.md b/docs/_running-tests/index.md
index 4cd6c73..c9e030f 100644
--- a/docs/_running-tests/index.md
+++ b/docs/_running-tests/index.md
@@ -20,15 +20,21 @@
 
     ./wpt run --binary ~/local/firefox/firefox firefox dom/historical.html
 
-On Windows `edge` is also supported, and if you have a sauce labs
-account, any browser can be run using product `sauce`. For details on
-how to pass information to sauce, including credentials see `./wpt run
---help`. That also details a large number of other options for
-customising the test run.
+`./wpt run --help` lists the supported products.
 
+For details on the supported products and a large number of other options for
+customising the test run, see `./wpt run --help`.
+
+Additional browser-specific documentation:
+
+  * [Chrome][chrome]
+
+  * [Chrome for Android][chrome android]
+
+  * [Safari][safari]
 
 ## From Inside a Browser
-Tests that have been merged on GitHub are mirrored at [http://w3c-test.org/].
+Tests that have been merged on GitHub are mirrored at [http://w3c-test.org/][w3c-test].
 
 For running multiple tests inside a browser, there is a test runner
 located at `/tools/runner/index.html`.
@@ -65,6 +71,10 @@
 documented!
 
 
+[chrome]: {{ site.baseurl }}{% link _running-tests/chrome.md %}
+[chrome android]: {{ site.baseurl }}{% link _running-tests/chrome_android.md %}
+[safari]: {{ site.baseurl }}{% link _running-tests/safari.md %}
 [public-test-infra]: https://lists.w3.org/Archives/Public/public-test-infra/
 [IRC]: irc://irc.w3.org:6667/testing
 [web irc]: http://irc.w3.org
+[w3c-test]: http://w3c-test.org
diff --git a/docs/_running-tests/safari.md b/docs/_running-tests/safari.md
new file mode 100644
index 0000000..b36f6d7
--- /dev/null
+++ b/docs/_running-tests/safari.md
@@ -0,0 +1,28 @@
+---
+layout: page
+title: Safari
+---
+To run Safari on macOS, some manual setup is required:
+
+  * Allow Safari to be controlled by SafariDriver: `safaridriver --enable`
+
+  * Allow pop-up windows:
+    `defaults write com.apple.Safari WebKitJavaScriptCanOpenWindowsAutomatically 1`
+
+  * Trust the certificate:
+    `security add-trusted-cert -k "$(security default-keychain | cut -d\" -f2)" tools/certs/cacert.pem`
+
+  * Set `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` in your environment. This is a
+    workaround for a known
+    [macOS High Sierra issue](https://github.com/w3c/web-platform-tests/issues/9007).
+
+Now, run the tests using the `safari` product:
+```
+./wpt run safari [test_list]
+```
+
+This will use the `safaridriver` found on the path, which will be stable Safari.
+To run Safari Technology Preview instead, use the `--webdriver-binary` argument:
+```
+./wpt run --webdriver-binary "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver" safari [test_list]
+```
diff --git a/docs/_writing-tests/file-names.md b/docs/_writing-tests/file-names.md
index 065be4e..797b7d3 100644
--- a/docs/_writing-tests/file-names.md
+++ b/docs/_writing-tests/file-names.md
@@ -52,7 +52,7 @@
 
 `.any`
  : (js files only) Indicates that the file generates tests in which it
-    is run in Window and dedicated worker environments.
+    is [run in multiple scopes][multi-global-tests].
 
 `.tentative`
  : Indicates that a test makes assertions not yet required by any specification,
@@ -63,3 +63,4 @@
 
 
 [server-side substitution]: https://wptserve.readthedocs.io/en/latest/pipes.html#sub
+[multi-global-tests]: {{ site.baseurl }}{% link _writing-tests/testharness.md %}#multi-global-tests
diff --git a/docs/_writing-tests/index.md b/docs/_writing-tests/index.md
index 6454eb1..3922d7a 100644
--- a/docs/_writing-tests/index.md
+++ b/docs/_writing-tests/index.md
@@ -40,6 +40,9 @@
 for things which would typically be tested using reftests but for
 which it would be overly cumbersome.
 
+See [file names][] for test types and features determined by the file names,
+and [server features][] for advanced testing features.
+
 In addition to the four main test types, there are also WebDriver
 tests, which are used exclusively for testing the WebDriver protocol
 itself. There is currently no documentation about these tests,
@@ -52,8 +55,10 @@
 make sure you run the [`lint` script][lint-tool] before opening a pull request!
 
 [introduction]: {{ site.baseurl }}{% link introduction.md %}
+[file names]: {{ site.baseurl }}{% link _writing-tests/file-names.md %}
 [general guidelines]: {{ site.baseurl }}{% link _writing-tests/general-guidelines.md %}
 [reftests]: {{ site.baseurl }}{% link _writing-tests/reftests.md %}
+[server features]: {{ site.baseurl }}{% link _writing-tests/server-features.md %}
 [testharness.js]: {{ site.baseurl }}{% link _writing-tests/testharness.md %}
 [visual]: {{ site.baseurl }}{% link _writing-tests/visual.md %}
 [manual]: {{ site.baseurl }}{% link _writing-tests/manual.md %}
diff --git a/docs/_writing-tests/lint-tool.md b/docs/_writing-tests/lint-tool.md
index c536975..d818f4b 100644
--- a/docs/_writing-tests/lint-tool.md
+++ b/docs/_writing-tests/lint-tool.md
@@ -58,6 +58,9 @@
   element whose `content` attribute has a malformed value; **fix**: ensure
   the value of the `content` attribute starts with `?` or `#` or is empty.
 
+* **MISSING-LINK**: CSS test file is missing a link to a spec. **fix**: Ensure that there is a `<link rel="help" src="[url]">` for the spec.
+  * Note: `MISSING-LINK` is designed to ensure that the CSS build tool can find the tests. Note that the CSS build system is primarily used by [test.csswg.org/](http://test.csswg.org/), which doesn't use `wptserve`, so `*.any.js` and similar tests won't work there; stick with the `.html` equivalent.
+
 * **MISSING-TESTHARNESSREPORT**: Test file is missing an instance of
   `<script src='/resources/testharnessreport.js'>`; **fix**: ensure each
   test file contains `<script src='/resources/testharnessreport.js'>`.
diff --git a/docs/_writing-tests/reftests.md b/docs/_writing-tests/reftests.md
index 2658962..f90d66d 100644
--- a/docs/_writing-tests/reftests.md
+++ b/docs/_writing-tests/reftests.md
@@ -99,14 +99,15 @@
 ## Controlling When Comparison Occurs
 
 By default reftest screenshots are taken after the `load` event has
-fired. In some cases it is necessary to delay the screenshot later
-than this, for example because some DOM manipulation is required to
-set up the desired test conditions. To enable this, the test may have
-a `class="reftest-wait"` attribute specified on the root element. This
-will cause the screenshot to be delayed until the `load` event has
-fired and the `reftest-wait` class has been removed from the root
-element. Note that in neither case is exact timing of the screenshot
-guaranteed: it is only guaranteed to be after those events.
+fired, and web fonts (if any) are loaded. In some cases it is
+necessary to delay the screenshot later than this, for example because
+some DOM manipulation is required to set up the desired test
+conditions. To enable this, the test may have a `class="reftest-wait"`
+attribute specified on the root element. This will cause the
+screenshot to be delayed until the `load` event has fired and the
+`reftest-wait` class has been removed from the root element. Note that
+in neither case is exact timing of the screenshot guaranteed: it is
+only guaranteed to be after those events.
 
 ## Fuzzy Matching
 
diff --git a/docs/_writing-tests/server-features.md b/docs/_writing-tests/server-features.md
index b8f50ab..a3cd417 100644
--- a/docs/_writing-tests/server-features.md
+++ b/docs/_writing-tests/server-features.md
@@ -8,21 +8,24 @@
 
 Certain test scenarios require more than just static HTML
 generation. This is supported through the
-[wptserve](http://wptserve.readthedocs.io) server. Several scenarios
-in particular are common:
+[wptserve](http://wptserve.readthedocs.io) server, and controlled by
+[file name flags][file names]. Several scenarios in particular are common:
 
 
 ### Tests Involving Multiple Origins
 
-In the test environment, five subdomains are available: `www`, `www1`,
-`www2`, `天気の良い日`, and `élève`; there is also
-`nonexistent-origin` which is guaranteed not to resolve. In addition,
-the HTTP server listens on two ports, and the WebSockets server on
-one. These subdomains and ports must be used for cross-origin
-tests. Tests must not hardcode the hostname of the server that they
-expect to be running on or the port numbers, as these are not
-guaranteed by the test environment. Instead they can get this
-information in one of two ways:
+Our test servers are guaranteed to be accessible through two domains
+and five subdomains under each. The 'main' domain is unnamed; the
+other is called 'alt'. These subdomains are: `www`, `www1`, `www2`,
+`天気の良い日`, and `élève`; there is also `nonexistent` which is
+guaranteed not to resolve. In addition, the HTTP server listens on two
+ports, and the WebSockets server on one. These subdomains and ports
+must be used for cross-origin tests.
+
+Tests must not hardcode the hostname of the server that they expect to
+be running on or the port numbers, as these are not guaranteed by the
+test environment. Instead they can get this information in one of two
+ways:
 
 * From script, using the `location` API.
 
@@ -33,15 +36,19 @@
 through a URL containing `pipe=sub` in the query string
 e.g. `example-test.html?pipe=sub`. The substitution syntax uses `{%
 raw %}{{ }}{% endraw %}` to delimit items for substitution. For
-example to substitute in the host name on which the tests are running,
-one would write: `{% raw %}{{host}}{% endraw %}`.
+example to substitute in the main host name, one would write:
+`{% raw %}{{host}}{% endraw %}`.
 
+To get full domains, including subdomains, there is the `hosts`
+dictionary, where the first dimension is the name of the domain, and
+the second the subdomain. For example, `{% raw %}{{hosts[][www]}}{%
+endraw %}` would give the `www` subdomain under the main (unnamed)
+domain, and `{% raw %}{{hosts[alt][élève]}}{% endraw %}` would give
+the `élève` subdomain under the alt domain.
 
-As well as the host, one can get full domains, including subdomains
-using the `domains` dictionary. For example, `{% raw
-%}{{domains[www]}}{% endraw %}` or `{% raw %}{{domains[élève]}}{%
-endraw %}` would be replaced by the full qualified domain name of the
-respective subdomains.
+For mostly historic reasons, the subdomains of the main domain are
+also available under the `domains` dictionary; this is identical to
+`hosts[]`.
 
 Ports are also available on a per-protocol basis. For example, `{% raw
 %}{{ports[ws][0]}}{% endraw %}` is replaced with the first (and only)
@@ -85,3 +92,6 @@
 have access to request data and can manipulate the content and timing
 of the response. For details see the
 [wptserve documentation](https://wptserve.readthedocs.org).
+
+
+[file names]: {{ site.baseurl }}{% link _writing-tests/file-names.md %}
diff --git a/docs/_writing-tests/testdriver-tutorial.md b/docs/_writing-tests/testdriver-tutorial.md
new file mode 100644
index 0000000..0b2165c
--- /dev/null
+++ b/docs/_writing-tests/testdriver-tutorial.md
@@ -0,0 +1,317 @@
+# Adding new commands to testdriver.js
+
+## Assumptions
+We assume the following in this writeup:
+ - You know what web-platform-tests is and you have a working checkout and can run tests
+ - You know what WebDriver or Selenium is
+ - Familiarity with JavaScript and Python
+
+## Introduction!
+
+Let's implement window resizing. We can do this via the [Set Window Rect](https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-window-rect) command in WebDriver.
+
+First, we need to think of what the API will look like a little. We will be using Selenium and Marionette for this, so we can look and see that they take in x, y coordinates, width and height integers.
+
+The first part of this will be browser agnostic, but later we will need to implement a specific layer for each browser (here we will do Firefox and Chrome).
+
+## Code!
+
+### [resources/testdriver.js](resources/testdriver.js)
+
+This is the main entry point the tests get. Here we need to add a function to the `test_driver` object that will call the `test_driver_internal` object.
+
+```javascript
+window.test_driver = {
+
+    // other commands...
+
+    /**
+    * Triggers browser window to be resized and relocated
+    *
+    * This matches the behaviour of the {@link
+    * https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-window-rect|WebDriver
+    * Set Window Rect command}.
+    *
+    * @param {Integer} x - The x coordinate of the top left of the window
+    * @param {Integer} y - The x coordinate of the top left of the window
+    * @param {Integer} width - The width of the window
+    * @param {Integer} height - The width of the window
+    * @returns {Promise} fulfilled after window rect is set occurs, or rejected in
+    *                    the cases the WebDriver command errors
+    */
+    set_window_rect: function(x, y, width, height) {
+        return window.test_driver_internal.set_element_rect(x, y, width, height);
+    }
+```
+
+In the same file, lets add to the internal object. ( do we need to do this?) (make sure to do this if the internal call has different arguments than the external call, especially if it calls multiple internal calls)
+
+```javascript
+window.test_driver_internal = {
+
+    // other commands...
+
+    /**
+     * Triggers browser window to be resized and relocated
+     *
+     * This matches the behaviour of the {@link
+     * https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-window-rect|WebDriver
+     * Set Window Rect command}.
+     *
+     * @param {Integer} x - The x coordinate of the top left of the window
+     * @param {Integer} y - The x coordinate of the top left of the window
+     * @param {Integer} width - The width of the window
+     * @param {Integer} height - The width of the window
+     * @returns {Promise} fulfilled after window rect is set occurs, or rejected in
+     *                    the cases the WebDriver command errors
+     */
+    set_window_rect: function(x, y, width, height) {
+        return Promise.reject(new Error("unimplemented"))
+    }
+```
+We will leave this unimplemented and override it in another file. Lets do that now!
+
+### [wptrunner/wptrunner/testdriver-extra.js](tools/wptrunner/wptrunner/testdriver-extra.js)
+
+This will be the default function called when invoking the test driver commands (sometimes it is overridden by testdriver-vendor.js, but this is outside the scope of this writeup).
+
+```javascript
+window.test_driver_internal.set_element_rect = function(x, y, width, height) {
+    const pending_promise = new Promise(function(resolve, reject) {
+        pending_resolve = resolve;
+        pending_reject = reject;
+    });
+    window.opener.postMessage(
+        {"type": "action", "action": "set_window_rect", "x": x, "y": y, "width": width, "height": height}, "*");
+    return pending_promise;
+};
+```
+The main thing here is the `postMessage` argument. The first argument is an object with properties
+ - `type`: this always has to be the string `"action"`
+ - `action`: the name of the testdriver command this defines (in this case, `set_window_rect`)
+ - any other things you want to pass to the next point of execution (in this case, the x, y coordinates and the width and height)
+
+<!-- The pending promise needs to be there as it is resolved when the window recieves a completion message from the executor. -->
+The pending promise is out of scope of this function and is resolved when the window recieves a completion message from the executor.
+This happens here in the same file:
+
+```javascript
+    let pending_resolve = null;
+    let pending_reject = null;
+    window.addEventListener("message", function(event) {
+        const data = event.data;
+
+        if (typeof data !== "object" && data !== null) {
+            return;
+        }
+
+        if (data.type !== "testdriver-complete") {
+            return;
+        }
+
+        if (data.status === "success") {
+            pending_resolve();
+        } else {
+            pending_reject();
+        }
+    });
+```
+
+One limitation this introduces is that only one testdriver call can be made at one time since the `pending_resolve` and `pending_reject` variables are in an outer scope.
+
+Next, this is passed to the executor and protocol in wptrunner. Time to switch to Python!
+
+[tools/wptrunner/wptrunner/executors/protocol.py](tools/wptrunner/wptrunner/executors/protocol.py)
+
+```python
+class SetWindowRectProtocolPart(ProtocolPart):
+    """Protocol part for resizing and changing location of window"""
+    __metaclass__ = ABCMeta
+
+    name = "set_window_rect"
+
+    @abstractmethod
+    def set_window_rect(self, x, y, width, height):
+        """Change the window rect
+
+        :param x: The x coordinate of the top left of the window.
+        :param y: The y coordinate of the top left of the window.
+        :param width: The width of the window.
+        :param height: The height of the window."""
+        pass
+```
+
+Next we change the base executor.
+
+[tools/wptrunner/wptrunner/executors/base.py](tools/wptrunner/wptrunner/executors/base.py)
+
+```python
+class CallbackHandler(object):
+    """Handle callbacks from testdriver-using tests.
+
+    The default implementation here makes sense for things that are roughly like
+    WebDriver. Things that are more different to WebDriver may need to create a
+    fully custom implementation."""
+
+    def __init__(self, logger, protocol, test_window):
+        self.protocol = protocol
+        self.test_window = test_window
+        self.logger = logger
+        self.callbacks = {
+            "action": self.process_action,
+            "complete": self.process_complete
+        }
+
+        self.actions = {
+            "click": ClickAction(self.logger, self.protocol),
+            "send_keys": SendKeysAction(self.logger, self.protocol),
+            {other actions},
+            "set_window_rect": SetWindowRectAction(self.logger, self.protocol) # add this!
+        }
+```
+
+```python
+class SetWindowRectAction(object):
+    def __init__(self, logger, protocol):
+        self.logger = logger
+        self.protocol = protocol
+
+    def __call__(self, payload):
+        x, y, width, height = payload["x"], payload["y"], payload["width"], payload["height"]
+        self.logger.debug("Setting window rect to be: x=%s, y=%s, width=%s, height=%s"
+                          .format(x, y, width, height))
+        self.protocol.set_window_rect.set_window_rect(x, y, width, height)
+```
+
+Don't forget to write docs in ```testdriver.md```.
+Now we write the browser specific implementations.
+
+### Chrome
+
+We will use [executorselenium](tools/wptrunner/wptrunner/executors/executorselenium.py) and use the Selenium API (in the future there are plans to use the WebDriver API directly).
+
+There isn't too much work to do here, we just need to define a subclass of the protocol part we defined earlier.
+
+```python
+class SeleniumSetWindowRectProtocolPart(SetWindowRectProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def set_window_rect(self, x, y, width, height):
+        return self.webdriver.set_window_rect(x, y, width, height)
+```
+
+Make sure to import the protocol part too!
+
+```python
+from .protocol import (BaseProtocolPart,
+                       TestharnessProtocolPart,
+                       Protocol,
+                       SelectorProtocolPart,
+                       ClickProtocolPart,
+                       SendKeysProtocolPart,
+                       {... other protocol parts}
+                       SetWindowRectProtocolPart, # add this!
+                       TestDriverProtocolPart)
+```
+
+Here we have the setup method which just redefines the webdriver object at this level. The important part is the `set_window_rect` function (and it's important it is named that since we called it that earlier). This will be call the Selenium API for [set window rect](http://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webdriver.WebDriver.set_window_rect) (`self.webdriver` is a Selenium WebDriver instance here).
+
+Finally, we just need to tell the SeleniumProtocol to implement this part.
+
+```python
+class SeleniumProtocol(Protocol):
+    implements = [SeleniumBaseProtocolPart,
+                  SeleniumTestharnessProtocolPart,
+                  SeleniumSelectorProtocolPart,
+                  SeleniumClickProtocolPart,
+                  SeleniumSendKeysProtocolPart,
+                  {... other protocol parts}
+                  SeleniumSetWindowRectProtocolPart,
+                  SeleniumTestDriverProtocolPart]
+```
+
+
+### Firefox
+We use the [set window rect](http://marionette-client.readthedocs.io/en/master/reference.html#marionette_driver.marionette.Marionette.set_window_rect) Marionette command.
+
+We will use [executormarionette](tools/wptrunner/wptrunner/executors/executormarionette.py) and use the Marionette Python API.
+
+We have little actual work to do here! We just need to define a subclass of the protocol part we defined earlier.
+
+```python
+class MarionetteSetWindowRectProtocolPart(SetWindowRectProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def set_window_rect(self, x, y, width, height):
+        return self.marionette.set_window_rect(x, y, width, height)
+```
+
+Make sure to import the protocol part too!
+
+```python
+from .protocol import (BaseProtocolPart,
+                       TestharnessProtocolPart,
+                       Protocol,
+                       SelectorProtocolPart,
+                       ClickProtocolPart,
+                       SendKeysProtocolPart,
+                       {... other protocol parts}
+                       SetWindowRectProtocolPart, # add this!
+                       TestDriverProtocolPart)
+```
+
+Here we have the setup method which just redefines the webdriver object at this level. The important part is the `set_window_rect` function (and it's important it is named that since we called it that earlier). This will be call the Marionette API for [set window rect](http://marionette-client.readthedocs.io/en/master/reference.html#marionette_driver.marionette.Marionette.set_window_rect) (`self.marionette` is a marionette instance here).
+
+Finally, we just need to tell the SeleniumProtocol to implement this part.
+
+```python
+class MarionetteProtocol(Protocol):
+    implements = [MarionetteBaseProtocolPart,
+                  MarionetteTestharnessProtocolPart,
+                  MarionettePrefsProtocolPart,
+                  MarionetteStorageProtocolPart,
+                  MarionetteSelectorProtocolPart,
+                  MarionetteClickProtocolPart,
+                  MarionetteSendKeysProtocolPart,
+                  {... other protocol parts}
+                  MarionetteSetWindowRectProtocolPart # add this
+                  MarionetteTestDriverProtocolPart]
+```
+
+### Other Browsers
+
+Other browsers may also use executorselenium (such as safari), or a completely new executor (such as servo). For these, you must change the executor in the same way as we did with chrome and firefox.
+
+### Write an infra test
+
+Make sure to add a test to `infrastructure/testdriver` :)
+
+Here is some template code!
+
+```html
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver set window rect method</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script>
+promise_test(async t => {
+  await test_driver.set_window_rect(100, 100, 100, 100);
+  // do something
+}
+</script>
+```
+### What about testdriver-vendor.js?
+
+The file [testdriver-vendor.js](resources/testdriver-vendor.js) is the equivalent to testdriver-extra.js above, except is
+run instead of testdriver-extra.js in browser specific test environments. For example, in [Chromium LayoutTests](https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/?q=LayoutTests&sq=package:chromium&dr).
+
+### What if I need to return a value from my testdriver API?
+
+We currently don't have this capability, but it is coming soon and will be documented. The bug is [here](https://github.com/w3c/web-platform-tests/issues/10716)
+
diff --git a/docs/_writing-tests/testdriver.md b/docs/_writing-tests/testdriver.md
index 3f5787f..51e09ba 100644
--- a/docs/_writing-tests/testdriver.md
+++ b/docs/_writing-tests/testdriver.md
@@ -19,6 +19,7 @@
 context (and not therefore in any frame or window opened from it).
 
 ### `test_driver.click(element)`
+#### `element: a DOM Element object`
 
 This function causes a click to occur on the target element (an
 `Element` object), potentially scrolling the document to make it
@@ -30,5 +31,21 @@
 document must not have any DOM mutations made between the function
 being called and the promise settling.
 
+### `test_driver.send_keys(element, keys)`
+#### `element: a DOM Element object`
+#### `keys: string to send to the element`
+
+This function causes the string `keys` to be send to the target
+element (an `Element` object), potentially scrolling the document to
+make it possible to send keys. It returns a `Promise` that resolves
+after the keys have been send or rejects if the keys cannot be sent
+to the element.
+
+Note that if the element that's keys need to be send to does not have
+a unique ID, the document must not have any DOM mutations made
+between the function being called and the promise settling.
+
+To send special keys, one must send the respective key's codepoint. Since this uses the WebDriver protocol, you can find a list for code points to special keys in the spec (here)[https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions].
+For example, to send the tab key you would send "\uE004".
 
 [testharness]: {{ site.baseurl }}{% link _writing-tests/testharness.md %}
diff --git a/docs/_writing-tests/testharness-api.md b/docs/_writing-tests/testharness-api.md
index d750086..baa8772 100644
--- a/docs/_writing-tests/testharness-api.md
+++ b/docs/_writing-tests/testharness-api.md
@@ -668,6 +668,17 @@
 Once called, the containing document fetches all the tests from the worker and
 behaves as if those tests were running in the containing document itself.
 
+`fetch_tests_from_worker` returns a promise that resolves once all the remote
+tests have completed. This is useful if you're importing tests from multiple
+workers and want to ensure they run in series:
+
+```js
+(async function() {
+  await fetch_tests_from_worker(new Worker("worker-1.js"));
+  await fetch_tests_from_worker(new Worker("worker-2.js"));
+})();
+```
+
 ## List of Assertions ##
 
 ### `assert_true(actual, description)`
diff --git a/docs/_writing-tests/testharness.md b/docs/_writing-tests/testharness.md
index 84aef58..652cf63 100644
--- a/docs/_writing-tests/testharness.md
+++ b/docs/_writing-tests/testharness.md
@@ -16,8 +16,46 @@
   * [idlharness.js Documentation][idlharness] — A library for testing
      IDL interfaces using `testharness.js`.
 
-As always, we recommend reading over the [general guidelines][] for
-all test types.
+See [server features][] for advanced testing features that are commonly used
+with testharness.js. See also the [general guidelines][] for all test types.
+
+## Variants
+
+A test file can have multiple variants by including `meta` elements,
+for example:
+
+```
+<meta name="variant" content="">
+<meta name="variant" content="?wss">
+```
+
+The test can then do different things based on the URL.
+
+There is a utility script in `/common/subset-tests.js` that works
+well together with variants, where a test that would otherwise have
+too many tests to be useful can be split up in ranges of subtests.
+For example:
+
+```
+<!doctype html>
+<title>Testing variants</title>
+<meta name="variant" content="?1-1000">
+<meta name="variant" content="?1001-2000">
+<meta name="variant" content="?2001-last">
+<script src="/resources/testharness.js">
+<script src="/resources/testharnessreport.js">
+<script src="/common/subset-tests.js">
+<script>
+ const tests = [
+                 { fn: t => { ... }, name: "..." },
+                 ... lots of tests ...
+               ];
+ for (const test of tests) {
+   subsetTest(async_test, test.fn, test.name);
+ }
+</script>
+```
+
 
 ## Auto-generated test boilerplate
 
@@ -59,13 +97,11 @@
 
 ### Multi-global tests
 
-Tests for features that exist in multiple global scopes can be written
-in a way that they are automatically run in a window scope and a
-worker scope.
+Tests for features that exist in multiple global scopes can be written in a way
+that they are automatically run in several scopes. In this case, the test is a
+JavaScript file with extension `.any.js`, which can use all the usual APIs.
 
-In this case, the test is a JavaScript file with extension `.any.js`.
-The test can then use all the usual APIs, and can be run from the path to the
-JavaScript file with the `.js` replaced by `.worker.html` or `.html`.
+By default, the test runs in a window scope and a dedicated worker scope.
 
 For example, one could write a test for the `Blob` constructor by
 creating a `FileAPI/Blob-constructor.any.js` as follows:
@@ -80,6 +116,30 @@
 This test could then be run from `FileAPI/Blob-constructor.any.worker.html` as well
 as `FileAPI/Blob-constructor.any.html`.
 
+It is possible to customize the set of scopes with a metadata comment, such as
+
+    // META: global=sharedworker
+    //       ==> would run in the default window and dedicated worker scopes,
+    //           as well as the shared worker scope
+    // META: global=!default,serviceworker
+    //       ==> would only run in the service worker scope
+    // META: global=!window
+    //       ==> would run in the default dedicated worker scope, but not the
+    //           window scope
+    // META: global=worker
+    //       ==> would run in the default window scope, as well as in the
+    //           dedicated, shared and service worker scopes
+
+For a test file <code><var>x</var>.any.js</code>, the available scope keywords
+are:
+
+* `window` (default): to be run at <code><var>x</var>.any.html</code>
+* `dedicatedworker` (default): to be run at <code><var>x</var>.any.worker.html</code>
+* `serviceworker`: to be run at <code><var>x</var>.https.any.serviceworker.html</code>
+* `sharedworker`: to be run at <code><var>x</var>.any.sharedworker.html</code>
+* `default`: shorthand for the default scopes
+* `worker`: shorthand for the dedicated, shared and service worker scopes
+
 To check if your test is run from a window or worker you can use the following two methods that will
 be made available by the framework:
 
@@ -99,7 +159,15 @@
 
 Use `// META: timeout=long` at the beginning of the resource.
 
+### Specifying test [variants](#variants) in auto-generated boilerplate tests
+
+Use `// META: variant=url-suffix` at the beginning of the resource. For example,
+
+    // META: variant=
+    // META: variant=?wss
+
 
 [general guidelines]: {{ site.baseurl }}{% link _writing-tests/general-guidelines.md %}
 [testharness-api]: {{ site.baseurl }}{% link _writing-tests/testharness-api.md %}
 [idlharness]: {{ site.baseurl }}{% link _writing-tests/idlharness.md %}
+[server features]: {{ site.baseurl }}{% link _writing-tests/server-features.md %}
diff --git a/docs/assets/gh-fork-ribbon.scss b/docs/assets/gh-fork-ribbon.scss
new file mode 100644
index 0000000..c81530b
--- /dev/null
+++ b/docs/assets/gh-fork-ribbon.scss
@@ -0,0 +1,124 @@
+/*!
+ * "Fork me on GitHub" CSS ribbon v0.2.2 | MIT License
+ * https://github.com/simonwhitaker/github-fork-ribbon-css
+*/
+
+.github-fork-ribbon {
+  width: 12.1em;
+  height: 12.1em;
+  position: absolute;
+  overflow: hidden;
+  top: 0;
+  right: 0;
+  z-index: 9999;
+  pointer-events: none;
+  font-size: 13px;
+  text-decoration: none;
+  text-indent: -999999px;
+}
+
+.github-fork-ribbon.fixed {
+  position: fixed;
+}
+
+.github-fork-ribbon:hover, .github-fork-ribbon:active {
+  background-color: rgba(0, 0, 0, 0.0);
+}
+
+.github-fork-ribbon:before, .github-fork-ribbon:after {
+  /* The right and left classes determine the side we attach our banner to */
+  position: absolute;
+  display: block;
+  width: 15.38em;
+  height: 1.54em;
+
+  top: 3.23em;
+  right: -3.23em;
+
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+
+  -webkit-transform: rotate(45deg);
+  -moz-transform: rotate(45deg);
+  -ms-transform: rotate(45deg);
+  -o-transform: rotate(45deg);
+  transform: rotate(45deg);
+}
+
+.github-fork-ribbon:before {
+  content: "";
+
+  /* Add a bit of padding to give some substance outside the "stitching" */
+  padding: .38em 0;
+
+  /* Set the base colour */
+  background-color: #a00;
+
+  /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */
+  background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15)));
+  background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+
+  /* Add a drop shadow */
+  -webkit-box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);
+  -moz-box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);
+  box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.5);
+
+  pointer-events: auto;
+}
+
+.github-fork-ribbon:after {
+  /* Set the text from the data-ribbon attribute */
+  content: attr(data-ribbon);
+
+  /* Set the text properties */
+  color: #fff;
+  font: 700 1em "Helvetica Neue", Helvetica, Arial, sans-serif;
+  line-height: 1.54em;
+  text-decoration: none;
+  text-shadow: 0 -.08em rgba(0, 0, 0, 0.5);
+  text-align: center;
+  text-indent: 0;
+
+  /* Set the layout properties */
+  padding: .15em 0;
+  margin: .15em 0;
+
+  /* Add "stitching" effect */
+  border-width: .08em 0;
+  border-style: dotted;
+  border-color: #fff;
+  border-color: rgba(255, 255, 255, 0.7);
+}
+
+.github-fork-ribbon.left-top, .github-fork-ribbon.left-bottom {
+  right: auto;
+  left: 0;
+}
+
+.github-fork-ribbon.left-bottom, .github-fork-ribbon.right-bottom {
+  top: auto;
+  bottom: 0;
+}
+
+.github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after {
+  right: auto;
+  left: -3.23em;
+}
+
+.github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after {
+  top: auto;
+  bottom: 3.23em;
+}
+
+.github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after {
+  -webkit-transform: rotate(-45deg);
+  -moz-transform: rotate(-45deg);
+  -ms-transform: rotate(-45deg);
+  -o-transform: rotate(-45deg);
+  transform: rotate(-45deg);
+}
diff --git a/docs/assets/main.scss b/docs/assets/main.scss
index 4dc5e5c..6274e75 100644
--- a/docs/assets/main.scss
+++ b/docs/assets/main.scss
@@ -164,3 +164,9 @@
         display: block !important;
     }
 }
+
+@import "gh-fork-ribbon";
+
+.github-fork-ribbon:before {
+    background-color: $grey-color-dark;
+}
diff --git a/docs/introduction.md b/docs/introduction.md
index c718efd..d021663 100644
--- a/docs/introduction.md
+++ b/docs/introduction.md
@@ -102,17 +102,22 @@
 and read the [Windows Notes](#windows-notes) section below.
 
 To get the tests running, you need to set up the test domains in your
-[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system). The
-following entries are required:
+[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system).
 
+The necessary content can be generated with `./wpt make-hosts-file`; on
+Windows, you will need to preceed the prior command with `python` or
+the path to the Python binary (`python wpt make-hosts-file`).
+
+For example, on most UNIX-like systems, you can setup the hosts file with:
+
+```bash
+./wpt make-hosts-file | sudo tee -a /etc/hosts
 ```
-127.0.0.1   web-platform.test
-127.0.0.1   www.web-platform.test
-127.0.0.1   www1.web-platform.test
-127.0.0.1   www2.web-platform.test
-127.0.0.1   xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1   xn--lve-6lad.web-platform.test
-0.0.0.0     nonexistent-origin.web-platform.test
+
+And on Windows (this must be run in a PowerShell session with Administrator privileges):
+
+```bash
+python wpt make-hosts-file | Out-File %SystemRoot%\System32\drivers\etc\hosts -Encoding ascii -Append
 ```
 
 If you are behind a proxy, you also need to make sure the domains above are
@@ -123,21 +128,27 @@
     ./wpt serve
 
 This will start HTTP servers on two ports and a websockets server on
-one port. By default one web server starts on port 8000 and the other
+one port. By default the web servers start on ports 8000 and 8443 and the other
 ports are randomly-chosen free ports. Tests must be loaded from the
-*first* HTTP server in the output. To change the ports, copy the
-`config.default.json` file to `config.json` and edit the new file,
-replacing the part that reads:
+*first* HTTP server in the output. To change the ports,
+create a `config.json` file in the wpt root directory, and add
+port definitions of your choice e.g.:
 
 ```
-"http": [8000, "auto"]
+{
+  "ports": {
+    "http": [1234, "auto"],
+    "https":[5678]
+  }
+}
 ```
 
-to some port of your choice e.g.
+After your `hosts` file is configured, the servers will be locally accessible at:
 
-```
-"http": [1234, "auto"]
-```
+http://web-platform.test:8000/<br>
+https://web-platform.test:8443/ *
+
+\**See [Trusting Root CA](https://github.com/w3c/web-platform-tests/blob/master/README.md#trusting-root-ca)*
 
 ## Running tests automatically
 
diff --git a/dom/OWNERS b/dom/OWNERS
index 487f8dd..9e0a739 100644
--- a/dom/OWNERS
+++ b/dom/OWNERS
@@ -1,3 +1,4 @@
 @ayg
 @jdm
 @zqzhang
+@annevk
diff --git a/dom/abort/event.any.js b/dom/abort/event.any.js
index 3b25702..a67e6f4 100644
--- a/dom/abort/event.any.js
+++ b/dom/abort/event.any.js
@@ -17,6 +17,51 @@
   assert_true(s.aborted);
 
   c.abort();
-}, "AbortController() basics");
+}, "AbortController abort() should fire event synchronously");
+
+test(t => {
+  const controller = new AbortController();
+  const signal = controller.signal;
+  assert_equals(controller.signal, signal,
+                "value of controller.signal should not have changed");
+  controller.abort();
+  assert_equals(controller.signal, signal,
+                "value of controller.signal should still not have changed");
+}, "controller.signal should always return the same object");
+
+test(t => {
+  const controller = new AbortController();
+  const signal = controller.signal;
+  let eventCount = 0;
+  signal.onabort = () => {
+    ++eventCount;
+  };
+  controller.abort();
+  assert_true(signal.aborted);
+  assert_equals(eventCount, 1, "event handler should have been called once");
+  controller.abort();
+  assert_true(signal.aborted);
+  assert_equals(eventCount, 1,
+                "event handler should not have been called again");
+}, "controller.abort() should do nothing the second time it is called");
+
+test(t => {
+  const controller = new AbortController();
+  controller.abort();
+  controller.signal.onabort =
+      t.unreached_func("event handler should not be called");
+}, "event handler should not be called if added after controller.abort()");
+
+test(t => {
+  const controller = new AbortController();
+  const signal = controller.signal;
+  signal.onabort = t.step_func(e => {
+    assert_equals(e.type, "abort", "event type should be abort");
+    assert_equals(e.target, signal, "event target should be signal");
+    assert_false(e.bubbles, "event should not bubble");
+    assert_true(e.isTrusted, "event should be trusted");
+  });
+  controller.abort();
+}, "the abort event should have the right properties");
 
 done();
diff --git a/dom/events/AddEventListenerOptions-passive.html b/dom/events/AddEventListenerOptions-passive.html
index 1f0118e..bf41580 100644
--- a/dom/events/AddEventListenerOptions-passive.html
+++ b/dom/events/AddEventListenerOptions-passive.html
@@ -55,6 +55,32 @@
   testPassiveValue({passive: 1}, false);
 }, "preventDefault should be ignored if-and-only-if the passive option is true");
 
+function testPassiveValueOnReturnValue(test, optionsValue, expectedDefaultPrevented) {
+  var defaultPrevented = undefined;
+  var handler = test.step_func(e => {
+    assert_false(e.defaultPrevented, "Event prematurely marked defaultPrevented");
+    e.returnValue = false;
+    defaultPrevented = e.defaultPrevented;
+  });
+  document.addEventListener('test', handler, optionsValue);
+  var uncanceled = document.body.dispatchEvent(new Event('test', {bubbles: true, cancelable: true}));
+
+  assert_equals(defaultPrevented, expectedDefaultPrevented, "Incorrect defaultPrevented for options: " + JSON.stringify(optionsValue));
+  assert_equals(uncanceled, !expectedDefaultPrevented, "Incorrect return value from dispatchEvent");
+
+  document.removeEventListener('test', handler, optionsValue);
+}
+
+async_test(t => {
+  testPassiveValueOnReturnValue(t, undefined, true);
+  testPassiveValueOnReturnValue(t, {}, true);
+  testPassiveValueOnReturnValue(t, {passive: false}, true);
+  testPassiveValueOnReturnValue(t, {passive: true}, false);
+  testPassiveValueOnReturnValue(t, {passive: 0}, true);
+  testPassiveValueOnReturnValue(t, {passive: 1}, false);
+  t.done();
+}, "returnValue should be ignored if-and-only-if the passive option is true");
+
 function testPassiveWithOtherHandlers(optionsValue, expectedDefaultPrevented) {
   var handlerInvoked1 = false;
   var dummyHandler1 = function() {
@@ -81,7 +107,7 @@
   testPassiveWithOtherHandlers({}, true);
   testPassiveWithOtherHandlers({passive: false}, true);
   testPassiveWithOtherHandlers({passive: true}, false);
-}, "passive behavior of one listener should be unaffeted by the presence of other listeners");
+}, "passive behavior of one listener should be unaffected by the presence of other listeners");
 
 function testOptionEquivalence(optionValue1, optionValue2, expectedEquality) {
   var invocationCount = 0;
diff --git a/dom/events/Event-constructors.html b/dom/events/Event-constructors.html
index a3cd3f8..5f1ed15 100644
--- a/dom/events/Event-constructors.html
+++ b/dom/events/Event-constructors.html
@@ -19,11 +19,13 @@
   var ev = new Event("")
   assert_equals(ev.type, "")
   assert_equals(ev.target, null)
+  assert_equals(ev.srcElement, null)
   assert_equals(ev.currentTarget, null)
   assert_equals(ev.eventPhase, Event.NONE)
   assert_equals(ev.bubbles, false)
   assert_equals(ev.cancelable, false)
   assert_equals(ev.defaultPrevented, false)
+  assert_equals(ev.returnValue, true)
   assert_equals(ev.isTrusted, false)
   assert_true(ev.timeStamp > 0)
   assert_true("initEvent" in ev)
@@ -32,11 +34,13 @@
   var ev = new Event("test")
   assert_equals(ev.type, "test")
   assert_equals(ev.target, null)
+  assert_equals(ev.srcElement, null)
   assert_equals(ev.currentTarget, null)
   assert_equals(ev.eventPhase, Event.NONE)
   assert_equals(ev.bubbles, false)
   assert_equals(ev.cancelable, false)
   assert_equals(ev.defaultPrevented, false)
+  assert_equals(ev.returnValue, true)
   assert_equals(ev.isTrusted, false)
   assert_true(ev.timeStamp > 0)
   assert_true("initEvent" in ev)
diff --git a/dom/events/Event-defaultPrevented-after-dispatch.html b/dom/events/Event-defaultPrevented-after-dispatch.html
index decf7e9..8fef005 100644
--- a/dom/events/Event-defaultPrevented-after-dispatch.html
+++ b/dom/events/Event-defaultPrevented-after-dispatch.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>Event.defaultPrevented is not reset after dipatchEvent()</title>
+<title>Event.defaultPrevented is not reset after dispatchEvent()</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
@@ -22,5 +22,23 @@
 
     assert_true(evt.defaultPrevented, "after dispatch");
     assert_equals(evt.target, TARGET);
-});
+    assert_equals(evt.srcElement, TARGET);
+}, "Default prevention via preventDefault");
+
+test(function() {
+    var EVENT = "foo";
+    var TARGET = document.getElementById("target");
+    var evt = document.createEvent("Event");
+    evt.initEvent(EVENT, true, true);
+
+    TARGET.addEventListener(EVENT, this.step_func(function(e) {
+        e.returnValue = false;
+        assert_true(e.defaultPrevented, "during dispatch");
+    }), true);
+    TARGET.dispatchEvent(evt);
+
+    assert_true(evt.defaultPrevented, "after dispatch");
+    assert_equals(evt.target, TARGET);
+    assert_equals(evt.srcElement, TARGET);
+}, "Default prevention via returnValue");
 </script>
diff --git a/dom/events/Event-defaultPrevented.html b/dom/events/Event-defaultPrevented.html
index 2a3d171..f023008 100644
--- a/dom/events/Event-defaultPrevented.html
+++ b/dom/events/Event-defaultPrevented.html
@@ -22,6 +22,12 @@
   assert_equals(ev.defaultPrevented, false, "defaultPrevented");
 }, "preventDefault() should not change defaultPrevented if cancelable is false.");
 test(function() {
+  assert_equals(ev.cancelable, false, "cancelable (before)");
+  ev.returnValue = false;
+  assert_equals(ev.cancelable, false, "cancelable (after)");
+  assert_equals(ev.defaultPrevented, false, "defaultPrevented");
+}, "returnValue should not change defaultPrevented if cancelable is false.");
+test(function() {
   ev.initEvent("foo", true, true);
   assert_equals(ev.bubbles, true, "bubbles");
   assert_equals(ev.cancelable, true, "cancelable");
@@ -34,6 +40,12 @@
   assert_equals(ev.defaultPrevented, true, "defaultPrevented");
 }, "preventDefault() should change defaultPrevented if cancelable is true.");
 test(function() {
+  assert_equals(ev.cancelable, true, "cancelable (before)");
+  ev.returnValue = false;
+  assert_equals(ev.cancelable, true, "cancelable (after)");
+  assert_equals(ev.defaultPrevented, true, "defaultPrevented");
+}, "returnValue should change defaultPrevented if cancelable is true.");
+test(function() {
   ev.initEvent("foo", true, true);
   assert_equals(ev.bubbles, true, "bubbles");
   assert_equals(ev.cancelable, true, "cancelable");
diff --git a/dom/events/Event-dispatch-click.html b/dom/events/Event-dispatch-click.html
index 29b0cae..4aa4bbe 100644
--- a/dom/events/Event-dispatch-click.html
+++ b/dom/events/Event-dispatch-click.html
@@ -94,9 +94,11 @@
   var clickEvent = new MouseEvent("click")
   input.onchange = t.step_func_done(function() {
     assert_false(clickEvent.defaultPrevented)
+    assert_true(clickEvent.returnValue)
     assert_equals(clickEvent.eventPhase, 0)
     assert_equals(clickEvent.currentTarget, null)
     assert_equals(clickEvent.target, input)
+    assert_equals(clickEvent.srcElement, input)
     assert_equals(clickEvent.composedPath().length, 0)
   })
   input.dispatchEvent(clickEvent)
@@ -110,6 +112,7 @@
   var finalTarget = document.createElement("doesnotmatter")
   finalTarget.onclick = t.step_func_done(function() {
     assert_equals(clickEvent.target, finalTarget)
+    assert_equals(clickEvent.srcElement, finalTarget)
   })
   input.onchange = t.step_func(function() {
     finalTarget.dispatchEvent(clickEvent)
diff --git a/dom/events/Event-dispatch-detached-click.html b/dom/events/Event-dispatch-detached-click.html
index 30e15b8..76ea3d7 100644
--- a/dom/events/Event-dispatch-detached-click.html
+++ b/dom/events/Event-dispatch-detached-click.html
@@ -10,6 +10,7 @@
   var t = async_test("Click event can be dispatched to an element that is not in the document.")
   TARGET.addEventListener(EVENT, t.step_func(function(evt) {
     assert_equals(evt.target, TARGET);
+    assert_equals(evt.srcElement, TARGET);
     t.done();
   }), true);
   var e = document.createEvent("Event");
diff --git a/dom/events/Event-dispatch-other-document.html b/dom/events/Event-dispatch-other-document.html
index 0252a4f..689b480 100644
--- a/dom/events/Event-dispatch-other-document.html
+++ b/dom/events/Event-dispatch-other-document.html
@@ -12,6 +12,7 @@
     assert_false(called);
     called = true;
     assert_equals(ev.target, element);
+    assert_equals(ev.srcElement, element);
   }));
   doc.body.appendChild(element);
 
diff --git a/dom/events/Event-initEvent.html b/dom/events/Event-initEvent.html
index c6b8564..34ed32a 100644
--- a/dom/events/Event-initEvent.html
+++ b/dom/events/Event-initEvent.html
@@ -12,18 +12,20 @@
       var e = document.createEvent("Event")
       e.initEvent("type", bubbles, cancelable)
 
-      // Step 3.
+      // Step 2.
       // Stop (immediate) propagation flag is tested later
       assert_equals(e.defaultPrevented, false, "defaultPrevented")
-      // Step 4.
+      assert_equals(e.returnValue, true, "returnValue")
+      // Step 3.
       assert_equals(e.isTrusted, false, "isTrusted")
-      // Step 5.
+      // Step 4.
       assert_equals(e.target, null, "target")
-      // Step 6.
+      assert_equals(e.srcElement, null, "srcElement")
+      // Step 5.
       assert_equals(e.type, "type", "type")
-      // Step 7.
+      // Step 6.
       assert_equals(e.bubbles, bubbles, "bubbles")
-      // Step 8.
+      // Step 7.
       assert_equals(e.cancelable, cancelable, "cancelable")
     }, "Properties of initEvent(type, " + bubbles + ", " + cancelable + ")")
   })
diff --git a/dom/events/Event-returnValue.html b/dom/events/Event-returnValue.html
new file mode 100644
index 0000000..08df2d4
--- /dev/null
+++ b/dom/events/Event-returnValue.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Event.returnValue</title>
+  <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+  <link rel="help" href="https://dom.spec.whatwg.org/#dom-event-returnvalue">
+  <meta name="flags" content="dom">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+  <div id="log"></div>
+  <script>
+test(function() {
+  var ev = new Event("foo");
+  assert_true(ev.returnValue, "returnValue");
+}, "When an event is created, returnValue should be initialized to true.");
+test(function() {
+  var ev = new Event("foo", {"cancelable": false});
+  assert_false(ev.cancelable, "cancelable (before)");
+  ev.preventDefault();
+  assert_false(ev.cancelable, "cancelable (after)");
+  assert_true(ev.returnValue, "returnValue");
+}, "preventDefault() should not change returnValue if cancelable is false.");
+test(function() {
+  var ev = new Event("foo", {"cancelable": false});
+  assert_false(ev.cancelable, "cancelable (before)");
+  ev.returnValue = false;
+  assert_false(ev.cancelable, "cancelable (after)");
+  assert_true(ev.returnValue, "returnValue");
+}, "returnValue=false should have no effect if cancelable is false.");
+test(function() {
+  var ev = new Event("foo", {"cancelable": true});
+  assert_true(ev.cancelable, "cancelable (before)");
+  ev.preventDefault();
+  assert_true(ev.cancelable, "cancelable (after)");
+  assert_false(ev.returnValue, "returnValue");
+}, "preventDefault() should change returnValue if cancelable is true.");
+test(function() {
+  var ev = new Event("foo", {"cancelable": true});
+  assert_true(ev.cancelable, "cancelable (before)");
+  ev.returnValue = false;
+  assert_true(ev.cancelable, "cancelable (after)");
+  assert_false(ev.returnValue, "returnValue");
+}, "returnValue should change returnValue if cancelable is true.");
+test(function() {
+  var ev = document.createEvent("Event");
+  ev.returnValue = false;
+  ev.initEvent("foo", true, true);
+  assert_true(ev.bubbles, "bubbles");
+  assert_true(ev.cancelable, "cancelable");
+  assert_true(ev.returnValue, "returnValue");
+}, "initEvent should unset returnValue.");
+test(function() {
+  var ev = new Event("foo", {"cancelable": true});
+  ev.preventDefault();
+  ev.returnValue = true;// no-op
+  assert_true(ev.defaultPrevented);
+  assert_false(ev.returnValue);
+}, "returnValue=true should have no effect once the canceled flag was set.");
+  </script>
+</body>
+</html>
diff --git a/dom/events/Event-subclasses-constructors.html b/dom/events/Event-subclasses-constructors.html
index 1741b96..1fd70d4 100644
--- a/dom/events/Event-subclasses-constructors.html
+++ b/dom/events/Event-subclasses-constructors.html
@@ -21,6 +21,23 @@
   }
 }
 
+// Class declarations don't go on the global by default, so put it there ourselves:
+
+self.SubclassedEvent = class SubclassedEvent extends Event {
+  constructor(name, props) {
+    super(name, props);
+    if (props && typeof(props) == "object" && "customProp" in props) {
+      this.customProp = props.customProp;
+    } else {
+      this.customProp = 5;
+    }
+  }
+
+  get fixedProp() {
+    return 17;
+  }
+}
+
 var EventModifierInit = [
   ["ctrlKey", false, true],
   ["shiftKey", false, true],
@@ -32,6 +49,7 @@
     "properties": [
       ["bubbles", false, true],
       ["cancelable", false, true],
+      ["isTrusted", false, false],
     ],
   },
 
@@ -93,6 +111,14 @@
       ["data", "", "string"],
     ],
   },
+
+  "SubclassedEvent": {
+    "parent": "Event",
+    "properties": [
+      ["customProp", 5, 8],
+      ["fixedProp", 17, 17],
+    ],
+  },
 };
 
 Object.keys(expected).forEach(function(iface) {
diff --git a/dom/events/EventListener-handleEvent.html b/dom/events/EventListener-handleEvent.html
index 3b58c49..b33b030 100644
--- a/dom/events/EventListener-handleEvent.html
+++ b/dom/events/EventListener-handleEvent.html
@@ -27,6 +27,7 @@
             t.step(function() {
                 assert_equals(evt.type, event);
                 assert_equals(evt.target, target);
+                assert_equals(evt.srcElement, target);
                 assert_equals(that, event_listener);
             });
         }
diff --git a/dom/events/EventListener-invoke-legacy.html b/dom/events/EventListener-invoke-legacy.html
index 85a4b0a..a01afcd 100644
--- a/dom/events/EventListener-invoke-legacy.html
+++ b/dom/events/EventListener-invoke-legacy.html
@@ -51,22 +51,16 @@
 }
 
 function setupTransition(elem) {
-  elem.style.transition = '';
-  requestAnimationFrame(function() {
-    elem.style.color = 'red';
-    elem.style.transition = 'color 30ms';
-    requestAnimationFrame(function() {
-      elem.style.color = 'green';
-    });
-  });
+  getComputedStyle(elem).color;
+  elem.style.color = 'green';
+  elem.style.transition = 'color 30ms';
 }
 
 function setupAnimation(elem) {
-  elem.style.animation = 'test 30ms 2';
+  elem.style.animation = 'test 30ms';
 }
 
 runLegacyEventTest('transitionend', 'webkitTransitionEnd', "TransitionEvent", setupTransition);
 runLegacyEventTest('animationend', 'webkitAnimationEnd', "AnimationEvent", setupAnimation);
-runLegacyEventTest('animationiteration', 'webkitAnimationIteration', "AnimationEvent", setupAnimation);
 runLegacyEventTest('animationstart', 'webkitAnimationStart', "AnimationEvent", setupAnimation);
 </script>
diff --git a/dom/events/EventTarget-dispatchEvent-returnvalue.html b/dom/events/EventTarget-dispatchEvent-returnvalue.html
index 8804c38..c4466e0 100644
--- a/dom/events/EventTarget-dispatchEvent-returnvalue.html
+++ b/dom/events/EventTarget-dispatchEvent-returnvalue.html
@@ -3,6 +3,7 @@
 <title>EventTarget.dispatchEvent: return value</title>
 <link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
 <link rel="help" href="https://dom.spec.whatwg.org/#dom-event-preventdefault">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-event-returnvalue">
 <link rel="help" href="https://dom.spec.whatwg.org/#dom-event-defaultprevented">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
@@ -25,11 +26,13 @@
     var target = document.getElementById("target");
     var parent = document.getElementById("parent");
     var default_prevented;
+    var return_value;
 
     parent.addEventListener(event_type, function(e) {}, true);
     target.addEventListener(event_type, function(e) {
         evt.preventDefault();
         default_prevented = evt.defaultPrevented;
+        return_value = evt.returnValue;
     }, true);
     target.addEventListener(event_type, function(e) {}, true);
 
@@ -39,5 +42,30 @@
     assert_true(parent.dispatchEvent(evt));
     assert_false(target.dispatchEvent(evt));
     assert_true(default_prevented);
-}, "Return value of EventTarget.dispatchEvent.");
+    assert_false(return_value);
+}, "Return value of EventTarget.dispatchEvent() affected by preventDefault().");
+
+test(function() {
+    var event_type = "foo";
+    var target = document.getElementById("target");
+    var parent = document.getElementById("parent");
+    var default_prevented;
+    var return_value;
+
+    parent.addEventListener(event_type, function(e) {}, true);
+    target.addEventListener(event_type, function(e) {
+        evt.returnValue = false;
+        default_prevented = evt.defaultPrevented;
+        return_value = evt.returnValue;
+    }, true);
+    target.addEventListener(event_type, function(e) {}, true);
+
+    var evt = document.createEvent("Event");
+    evt.initEvent(event_type, true, true);
+
+    assert_true(parent.dispatchEvent(evt));
+    assert_false(target.dispatchEvent(evt));
+    assert_true(default_prevented);
+    assert_false(return_value);
+}, "Return value of EventTarget.dispatchEvent() affected by returnValue.");
 </script>
diff --git a/dom/events/event-disabled-dynamic.html b/dom/events/event-disabled-dynamic.html
new file mode 100644
index 0000000..3f995b0
--- /dev/null
+++ b/dom/events/event-disabled-dynamic.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that disabled is honored immediately in presence of dynamic changes</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Andreas Farre" href="mailto:afarre@mozilla.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls:-the-disabled-attribute">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1405087">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<input type="button" value="Click" disabled>
+<script>
+async_test(t => {
+  // NOTE: This test will timeout if it fails.
+  window.addEventListener('load', t.step_func(() => {
+    let e = document.querySelector('input');
+    e.disabled = false;
+    e.onclick = t.step_func_done(() => {});
+    e.click();
+  }));
+}, "disabled is honored properly in presence of dynamic changes");
+</script>
diff --git a/dom/events/event-global.worker.js b/dom/events/event-global.worker.js
new file mode 100644
index 0000000..116cf32
--- /dev/null
+++ b/dom/events/event-global.worker.js
@@ -0,0 +1,14 @@
+importScripts("/resources/testharness.js");
+test(t => {
+  let seen = false;
+  const event = new Event("hi");
+  assert_equals(self.event, undefined);
+  self.addEventListener("hi", t.step_func(e => {
+    seen = true;
+    assert_equals(self.event, undefined);
+    assert_equals(e, event);
+  }));
+  self.dispatchEvent(event);
+  assert_true(seen);
+}, "There's no self.event (that's why we call it window.event) in workers");
+done();
diff --git a/dom/events/relatedTarget.window.js b/dom/events/relatedTarget.window.js
new file mode 100644
index 0000000..ebc83ce
--- /dev/null
+++ b/dom/events/relatedTarget.window.js
@@ -0,0 +1,81 @@
+// https://dom.spec.whatwg.org/#concept-event-dispatch
+
+const host = document.createElement("div"),
+      child = host.appendChild(document.createElement("p")),
+      shadow = host.attachShadow({ mode: "closed" }),
+      slot = shadow.appendChild(document.createElement("slot"));
+
+test(() => {
+  for (target of [shadow, slot]) {
+    for (relatedTarget of [new XMLHttpRequest(), self, host]) {
+      const event = new FocusEvent("demo", { relatedTarget: relatedTarget });
+      target.dispatchEvent(event);
+      assert_equals(event.target, null);
+      assert_equals(event.relatedTarget, null);
+    }
+  }
+}, "Reset if target pointed to a shadow tree");
+
+test(() => {
+  for (relatedTarget of [shadow, slot]) {
+    for (target of [new XMLHttpRequest(), self, host]) {
+      const event = new FocusEvent("demo", { relatedTarget: relatedTarget });
+      target.dispatchEvent(event);
+      assert_equals(event.target, target);
+      assert_equals(event.relatedTarget, host);
+    }
+  }
+}, "Retarget a shadow-tree relatedTarget");
+
+test(t => {
+  const shadowChild = shadow.appendChild(document.createElement("div"));
+  shadowChild.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+  const event = new FocusEvent("demo", { relatedTarget: new XMLHttpRequest() });
+  shadowChild.dispatchEvent(event);
+  assert_equals(shadowChild.parentNode, document.body);
+  assert_equals(event.target, null);
+  assert_equals(event.relatedTarget, null);
+  shadowChild.remove();
+}, "Reset if target pointed to a shadow tree pre-dispatch");
+
+test(t => {
+  const shadowChild = shadow.appendChild(document.createElement("div"));
+  document.body.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+  const event = new FocusEvent("demo", { relatedTarget: shadowChild });
+  document.body.dispatchEvent(event);
+  assert_equals(shadowChild.parentNode, document.body);
+  assert_equals(event.target, document.body);
+  assert_equals(event.relatedTarget, host);
+  shadowChild.remove();
+}, "Retarget a shadow-tree relatedTarget, part 2");
+
+test(t => {
+  const event = new FocusEvent("heya", { relatedTarget: shadow, cancelable: true }),
+        callback = t.unreached_func();
+  host.addEventListener("heya", callback);
+  t.add_cleanup(() => host.removeEventListener("heya", callback));
+  event.preventDefault();
+  assert_true(event.defaultPrevented);
+  assert_false(host.dispatchEvent(event));
+  assert_equals(event.target, null);
+  assert_equals(event.relatedTarget, null);
+  // Check that the dispatch flag is cleared
+  event.initEvent("x");
+  assert_equals(event.type, "x");
+}, "Reset targets on early return");
+
+test(t => {
+  const input = document.body.appendChild(document.createElement("input")),
+        event = new MouseEvent("click", { relatedTarget: shadow });
+  let seen = false;
+  t.add_cleanup(() => input.remove());
+  input.type = "checkbox";
+  input.oninput = t.step_func(() => {
+    assert_equals(event.target, null);
+    assert_equals(event.relatedTarget, null);
+    assert_equals(event.composedPath().length, 0);
+    seen = true;
+  });
+  assert_true(input.dispatchEvent(event));
+  assert_true(seen);
+}, "Reset targets before activation behavior");
diff --git a/dom/historical.html b/dom/historical.html
index 388366c..b45bebf 100644
--- a/dom/historical.html
+++ b/dom/historical.html
@@ -12,12 +12,14 @@
 }
 var nukedInterfaces = [
   "DOMConfiguration",
+  "DOMCursor",
   "DOMError",
   "DOMErrorHandler",
   "DOMImplementationList",
   "DOMImplementationSource",
   "DOMLocator",
   "DOMObject",
+  "DOMRequest",
   "DOMSettableTokenList",
   "DOMUserData",
   "Entity",
diff --git a/dom/interfaces.html b/dom/interfaces.html
index 8e9572d..3cb08f4 100644
--- a/dom/interfaces.html
+++ b/dom/interfaces.html
@@ -18,8 +18,12 @@
 
 var idlArray = new IdlArray();
 
-function doTest(idl) {
-  idlArray.add_idls(idl);
+function doTest([html, dom]) {
+  // HTML is needed for EventHandler. Provide a dummy interface for
+  // LinkStyle which HTML depends on but we're not testing.
+  idlArray.add_untested_idls('interface LinkStyle {};');
+  idlArray.add_untested_idls(html);
+  idlArray.add_idls(dom);
   idlArray.add_objects({
     EventTarget: ['new EventTarget()'],
     Event: ['document.createEvent("Event")', 'new Event("foo")'],
@@ -46,8 +50,13 @@
   idlArray.test();
 }
 
+function fetchText(url) {
+  return fetch(url).then((response) => response.text());
+}
+
 promise_test(function() {
-  return fetch("/interfaces/dom.idl").then(response => response.text())
-                                     .then(doTest);
+  return Promise.all(['/interfaces/html.idl',
+                      '/interfaces/dom.idl'].map(fetchText))
+                .then(doTest);
 }, "Test driver");
 </script>
diff --git a/dom/lists/DOMTokenList-coverage-for-attributes.html b/dom/lists/DOMTokenList-coverage-for-attributes.html
index 034e31c..e5f060b 100644
--- a/dom/lists/DOMTokenList-coverage-for-attributes.html
+++ b/dom/lists/DOMTokenList-coverage-for-attributes.html
@@ -10,9 +10,10 @@
 var pairs = [
   // Defined in DOM
   {attr: "classList", sup: ["anyElement"]},
+  // Defined in HTML except for a which is also SVG
+  {attr: "relList", sup: ["a", "area", "link"]},
   // Defined in HTML
   {attr: "htmlFor", sup: ["output"]},
-  {attr: "relList", sup: ["a", "area", "link"]},
   {attr: "sandbox", sup: ["iframe"]},
   {attr: "sizes", sup: ["link"]}
 ];
@@ -26,7 +27,11 @@
 
 var elements = ["a", "area", "link", "iframe", "output", "td", "th"];
 function testAttr(pair, new_el){
-  return (pair.attr === "classList" || (new_el.namespaceURI === "http://www.w3.org/1999/xhtml" && pair.sup.indexOf(new_el.localName) != -1));
+  return (pair.attr === "classList" ||
+          (pair.attr === "relList" && new_el.localName === "a" &&
+           new_el.namespaceURI === "http://www.w3.org/2000/svg") ||
+          (new_el.namespaceURI === "http://www.w3.org/1999/xhtml" &&
+           pair.sup.indexOf(new_el.localName) != -1));
 }
 
 pairs.forEach(function(pair) {
diff --git a/dom/nodes/Document-Element-getElementsByTagName.js b/dom/nodes/Document-Element-getElementsByTagName.js
index edbac64..c18b206 100644
--- a/dom/nodes/Document-Element-getElementsByTagName.js
+++ b/dom/nodes/Document-Element-getElementsByTagName.js
@@ -190,4 +190,19 @@
     get_elements(context);
     assert_array_equals(actual, expected);
   }, "getElementsByTagName('*')")
+
+  test(function() {
+    var t1 = element.appendChild(document.createElement("abc"));
+    this.add_cleanup(function() {element.removeChild(t1)});
+
+    var l = context.getElementsByTagName("abc");
+    assert_true(l instanceof HTMLCollection);
+    assert_equals(l.length, 1);
+
+    var t2 = element.appendChild(document.createElement("abc"));
+    assert_equals(l.length, 2);
+
+    element.removeChild(t2);
+    assert_equals(l.length, 1);
+  }, "getElementsByTagName() should be a live collection");
 }
diff --git a/dom/nodes/Document-Element-getElementsByTagNameNS.js b/dom/nodes/Document-Element-getElementsByTagNameNS.js
index a1bb315..b610c49 100644
--- a/dom/nodes/Document-Element-getElementsByTagNameNS.js
+++ b/dom/nodes/Document-Element-getElementsByTagNameNS.js
@@ -125,4 +125,19 @@
     assert_array_equals(context.getElementsByTagNameNS(null, "0"), []);
     assert_array_equals(context.getElementsByTagNameNS(null, "div"), []);
   }, "Empty lists")
+
+  test(function() {
+    var t1 = element.appendChild(document.createElementNS("test", "abc"));
+    this.add_cleanup(function() {element.removeChild(t1)});
+
+    var l = context.getElementsByTagNameNS("test", "abc");
+    assert_true(l instanceof HTMLCollection);
+    assert_equals(l.length, 1);
+
+    var t2 = element.appendChild(document.createElementNS("test", "abc"));
+    assert_equals(l.length, 2);
+
+    element.removeChild(t2);
+    assert_equals(l.length, 1);
+  }, "getElementsByTagNameNS() should be a live collection");
 }
diff --git a/dom/nodes/Document-constructor.html b/dom/nodes/Document-constructor.html
index 2f26982..ceb4192 100644
--- a/dom/nodes/Document-constructor.html
+++ b/dom/nodes/Document-constructor.html
@@ -36,6 +36,7 @@
   assert_equals(doc.contentType, "application/xml");
   assert_equals(doc.origin, document.origin);
   assert_equals(doc.createElement("DIV").localName, "DIV");
+  assert_equals(doc.createElement("a").constructor, Element);
 }, "new Document(): metadata")
 
 test(function() {
@@ -47,7 +48,8 @@
 
 test(function() {
   var doc = new Document();
-  var a = doc.createElement("a");
+  var a = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
+  assert_equals(a.constructor, HTMLAnchorElement);
   // In UTF-8: 0xC3 0xA4
   a.href = "http://example.org/?\u00E4";
   assert_equals(a.href, "http://example.org/?%C3%A4");
diff --git a/dom/nodes/Document-createElement-namespace-tests/generate.py b/dom/nodes/Document-createElement-namespace-tests/generate.py
index 88c4da1..811413d 100755
--- a/dom/nodes/Document-createElement-namespace-tests/generate.py
+++ b/dom/nodes/Document-createElement-namespace-tests/generate.py
@@ -1,4 +1,7 @@
 #!/usr/bin/python
+
+from __future__ import print_function
+
 import os
 import sys
 
@@ -51,11 +54,11 @@
 
 def __main__():
     if len(sys.argv) > 1:
-        print "No arguments expected, aborting"
+        print("No arguments expected, aborting")
         return
 
     if not os.access(THIS_NAME, os.F_OK):
-        print "Must be run from the directory of " + THIS_NAME + ", aborting"
+        print("Must be run from the directory of " + THIS_NAME + ", aborting")
         return
 
     for name in os.listdir("."):
diff --git a/dom/nodes/Document-getElementsByClassName.html b/dom/nodes/Document-getElementsByClassName.html
new file mode 100644
index 0000000..db8fac2
--- /dev/null
+++ b/dom/nodes/Document-getElementsByClassName.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Document.getElementsByClassName</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+  var a = document.createElement("a"),
+      b = document.createElement("b");
+  a.className = "foo";
+  this.add_cleanup(function() {document.body.removeChild(a);});
+  document.body.appendChild(a);
+
+  var l = document.getElementsByClassName("foo");
+  assert_true(l instanceof HTMLCollection);
+  assert_equals(l.length, 1);
+
+  b.className = "foo";
+  document.body.appendChild(b);
+  assert_equals(l.length, 2);
+
+  document.body.removeChild(b);
+  assert_equals(l.length, 1);
+}, "getElementsByClassName() should be a live collection");
+</script>
diff --git a/dom/nodes/Document-importNode.html b/dom/nodes/Document-importNode.html
index 32e2f31..d27cce6 100644
--- a/dom/nodes/Document-importNode.html
+++ b/dom/nodes/Document-importNode.html
@@ -54,4 +54,14 @@
   assert_equals(newDiv.ownerDocument, document);
   assert_equals(newDiv.firstChild, null);
 }, "False 'deep' argument.")
+
+test(function() {
+  let doc = document.implementation.createHTMLDocument("Title");
+  doc.body.setAttributeNS("http://example.com/", "p:name", "value");
+  let originalAttr = doc.body.getAttributeNodeNS("http://example.com/", "name");
+  let imported = document.importNode(originalAttr, true);
+  assert_equals(imported.prefix, originalAttr.prefix);
+  assert_equals(imported.namespaceURI, originalAttr.namespaceURI);
+  assert_equals(imported.localName, originalAttr.localName);
+}, "Import an Attr node with namespace/prefix correctly.");
 </script>
diff --git a/dom/nodes/Element-classlist.html b/dom/nodes/Element-classlist.html
index bc28b1c..5453da9 100644
--- a/dom/nodes/Element-classlist.html
+++ b/dom/nodes/Element-classlist.html
@@ -31,6 +31,13 @@
     }
     setClass(e, before);
 
+    var obs;
+    // If we have MutationObservers available,  do some checks to make
+    // sure attribute sets are happening at sane times.
+    if (self.MutationObserver) {
+      obs = new MutationObserver(() => {});
+      obs.observe(e, { attributes: true });
+    }
     if (shouldThrow) {
       assert_throws(expectedException, function() {
         var list = e.classList;
@@ -40,6 +47,21 @@
       var list = e.classList;
       var res = list[funcName].apply(list, args);
     }
+    if (obs) {
+      var mutationRecords = obs.takeRecords();
+      obs.disconnect();
+      if (shouldThrow) {
+        assert_equals(mutationRecords.length, 0,
+                      "There should have been no mutation");
+      } else if (funcName == "replace") {
+        assert_equals(mutationRecords.length == 1,
+                      expectedRes,
+                      "Should have a mutation exactly when replace() returns true");
+      } else {
+        // For other functions, would need to check when exactly
+        // mutations are supposed to happen.
+      }
+    }
     if (!shouldThrow) {
       assert_equals(res, expectedRes, "wrong return value");
     }
@@ -370,65 +392,66 @@
 
 
   // replace() method
-  function checkReplace(before, token, newToken, after, expectedException) {
-    checkModification(e, "replace", [token, newToken], undefined, before,
+  function checkReplace(before, token, newToken, expectedRes, after, expectedException) {
+    checkModification(e, "replace", [token, newToken], expectedRes, before,
                       after, expectedException, desc);
   }
 
-  checkReplace(null, "", "a", null, "SyntaxError");
-  checkReplace(null, "", " ", null, "SyntaxError");
-  checkReplace(null, " ", "a", null, "InvalidCharacterError");
-  checkReplace(null, "\ta", "b", null, "InvalidCharacterError");
-  checkReplace(null, "a\t", "b", null, "InvalidCharacterError");
-  checkReplace(null, "\na", "b", null, "InvalidCharacterError");
-  checkReplace(null, "a\n", "b", null, "InvalidCharacterError");
-  checkReplace(null, "\fa", "b", null, "InvalidCharacterError");
-  checkReplace(null, "a\f", "b", null, "InvalidCharacterError");
-  checkReplace(null, "\ra", "b", null, "InvalidCharacterError");
-  checkReplace(null, "a\r", "b", null, "InvalidCharacterError");
-  checkReplace(null, " a", "b", null, "InvalidCharacterError");
-  checkReplace(null, "a ", "b", null, "InvalidCharacterError");
+  checkReplace(null, "", "a", null, null, "SyntaxError");
+  checkReplace(null, "", " ", null, null, "SyntaxError");
+  checkReplace(null, " ", "a", null, null, "InvalidCharacterError");
+  checkReplace(null, "\ta", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "a\t", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "\na", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "a\n", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "\fa", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "a\f", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "\ra", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "a\r", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, " a", "b", null, null, "InvalidCharacterError");
+  checkReplace(null, "a ", "b", null, null, "InvalidCharacterError");
 
-  checkReplace(null, "a", "", null, "SyntaxError");
-  checkReplace(null, " ", "", null, "SyntaxError");
-  checkReplace(null, "a", " ", null, "InvalidCharacterError");
-  checkReplace(null, "b", "\ta", null, "InvalidCharacterError");
-  checkReplace(null, "b", "a\t", null, "InvalidCharacterError");
-  checkReplace(null, "b", "\na", null, "InvalidCharacterError");
-  checkReplace(null, "b", "a\n", null, "InvalidCharacterError");
-  checkReplace(null, "b", "\fa", null, "InvalidCharacterError");
-  checkReplace(null, "b", "a\f", null, "InvalidCharacterError");
-  checkReplace(null, "b", "\ra", null, "InvalidCharacterError");
-  checkReplace(null, "b", "a\r", null, "InvalidCharacterError");
-  checkReplace(null, "b", " a", null, "InvalidCharacterError");
-  checkReplace(null, "b", "a ", null, "InvalidCharacterError");
+  checkReplace(null, "a", "", null, null, "SyntaxError");
+  checkReplace(null, " ", "", null, null, "SyntaxError");
+  checkReplace(null, "a", " ", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "\ta", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "a\t", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "\na", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "a\n", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "\fa", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "a\f", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "\ra", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "a\r", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", " a", null, null, "InvalidCharacterError");
+  checkReplace(null, "b", "a ", null, null, "InvalidCharacterError");
 
-  checkReplace("a", "a", "a", "a");
-  checkReplace("a", "a", "b", "b");
-  checkReplace("a", "A", "b", "a");
-  checkReplace("a b", "b", "A", "a A");
-  checkReplace("a b c", "d", "e", "a b c");
+  checkReplace("a", "a", "a", true, "a");
+  checkReplace("a", "a", "b", true, "b");
+  checkReplace("a", "A", "b", false, "a");
+  checkReplace("a b", "b", "A", true, "a A");
+  checkReplace("a b", "c", "a", false, "a b");
+  checkReplace("a b c", "d", "e", false, "a b c");
   // https://github.com/whatwg/dom/issues/443
-  checkReplace("a a a  b", "a", "a", "a b");
-  checkReplace("a a a  b", "c", "d", "a a a  b");
-  checkReplace(null, "a", "b", null);
-  checkReplace("", "a", "b", "");
-  checkReplace(" ", "a", "b", " ");
-  checkReplace(" a  \f", "a", "b", "b");
-  checkReplace("a b c", "b", "d", "a d c");
-  checkReplace("a b c", "c", "a", "a b");
-  checkReplace("c b a", "c", "a", "a b");
-  checkReplace("a b a", "a", "c", "c b");
-  checkReplace("a b a", "b", "c", "a c");
-  checkReplace("   a  a b", "a", "c", "c b");
-  checkReplace("   a  a b", "b", "c", "a c");
-  checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "c", "c b");
-  checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "b", "c", "a c");
+  checkReplace("a a a  b", "a", "a", true, "a b");
+  checkReplace("a a a  b", "c", "d", false, "a a a  b");
+  checkReplace(null, "a", "b", false, null);
+  checkReplace("", "a", "b", false, "");
+  checkReplace(" ", "a", "b", false, " ");
+  checkReplace(" a  \f", "a", "b", true, "b");
+  checkReplace("a b c", "b", "d", true, "a d c");
+  checkReplace("a b c", "c", "a", true, "a b");
+  checkReplace("c b a", "c", "a", true, "a b");
+  checkReplace("a b a", "a", "c", true, "c b");
+  checkReplace("a b a", "b", "c", true, "a c");
+  checkReplace("   a  a b", "a", "c", true, "c b");
+  checkReplace("   a  a b", "b", "c", true, "a c");
+  checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "c", true, "c b");
+  checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "b", "c", true, "a c");
 
-  checkReplace("a null", null, "b", "a b");
-  checkReplace("a b", "a", null, "null b");
-  checkReplace("a undefined", undefined, "b", "a b");
-  checkReplace("a b", "a", undefined, "undefined b");
+  checkReplace("a null", null, "b", true, "a b");
+  checkReplace("a b", "a", null, true, "null b");
+  checkReplace("a undefined", undefined, "b", true, "a b");
+  checkReplace("a b", "a", undefined, true, "undefined b");
 }
 
 var content = document.getElementById("content");
diff --git a/dom/nodes/Element-getElementsByClassName.html b/dom/nodes/Element-getElementsByClassName.html
index 332c306..bc87b05 100644
--- a/dom/nodes/Element-getElementsByClassName.html
+++ b/dom/nodes/Element-getElementsByClassName.html
@@ -13,9 +13,31 @@
   var secondList = a.getElementsByClassName("foo")
   assert_true(list === secondList || list !== secondList, "Caching is allowed.")
 }, "getElementsByClassName should work on disconnected subtrees.")
+
 test(function() {
   var list = document.getElementsByClassName("foo")
   assert_false(list instanceof NodeList, "NodeList")
   assert_true(list instanceof HTMLCollection, "HTMLCollection")
 }, "Interface should be correct.")
+
+test(function() {
+  var a = document.createElement("a");
+  var b = document.createElement("b");
+  var c = document.createElement("c");
+  b.className = "foo";
+  document.body.appendChild(a);
+  this.add_cleanup(function() {document.body.removeChild(a)});
+  a.appendChild(b);
+
+  var l = a.getElementsByClassName("foo");
+  assert_true(l instanceof HTMLCollection);
+  assert_equals(l.length, 1);
+
+  c.className = "foo";
+  a.appendChild(c);
+  assert_equals(l.length, 2);
+
+  a.removeChild(c);
+  assert_equals(l.length, 1);
+}, "getElementsByClassName() should be a live collection");
 </script>
diff --git a/dom/nodes/Node-childNodes.html b/dom/nodes/Node-childNodes.html
index f7586fa..0d38df3 100644
--- a/dom/nodes/Node-childNodes.html
+++ b/dom/nodes/Node-childNodes.html
@@ -7,6 +7,9 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
+<div style="display: none">
+  <ul id='test'><li>1</li><li>2</li><li>3</li><li>4</li></ul>
+</div>
 <script>
 test(function() {
   var element = document.createElement("p");
@@ -96,4 +99,19 @@
   assert_equals(list.entries, Array.prototype.entries);
   assert_equals(list.forEach, Array.prototype.forEach);
 }, "Iterator behavior of Node.childNodes");
+
+
+test(() => {
+  var node = document.getElementById("test");
+  var children = node.childNodes;
+  assert_true(children instanceof NodeList);
+  var li = document.createElement("li");
+  assert_equals(children.length, 4);
+
+  node.appendChild(li);
+  assert_equals(children.length, 5);
+
+  node.removeChild(li);
+  assert_equals(children.length, 4);
+}, "Node.childNodes should be a live collection");
 </script>
diff --git a/dom/nodes/Node-cloneNode.html b/dom/nodes/Node-cloneNode.html
index 6c86630..08d620b 100644
--- a/dom/nodes/Node-cloneNode.html
+++ b/dom/nodes/Node-cloneNode.html
@@ -240,7 +240,6 @@
     assert_equals(doc.contentType, copy.contentType, "contentType equality");
     assert_equals(doc.URL, "about:blank", "URL value")
     assert_equals(doc.URL, copy.URL, "URL equality");
-    assert_equals(doc.origin, "null", "origin value")
     assert_equals(doc.origin, copy.origin, "origin equality");
     assert_equals(doc.compatMode, "CSS1Compat", "compatMode value");
     assert_equals(doc.compatMode, copy.compatMode, "compatMode equality");
@@ -286,4 +285,17 @@
     assert_equals(copy.childNodes.length, 0,
                   "copy.childNodes.length with non-deep copy");
 }, "node with children");
+
+test(() => {
+  const proto = Object.create(HTMLElement.prototype),
+        node = document.createElement("hi");
+  Object.setPrototypeOf(node, proto);
+  assert_true(proto.isPrototypeOf(node));
+  const clone = node.cloneNode();
+  assert_false(proto.isPrototypeOf(clone));
+  assert_true(HTMLUnknownElement.prototype.isPrototypeOf(clone));
+  const deepClone = node.cloneNode(true);
+  assert_false(proto.isPrototypeOf(deepClone));
+  assert_true(HTMLUnknownElement.prototype.isPrototypeOf(deepClone));
+}, "Node with custom prototype")
 </script>
diff --git a/dom/nodes/Node-normalize.html b/dom/nodes/Node-normalize.html
index 7598852..4d45599 100644
--- a/dom/nodes/Node-normalize.html
+++ b/dom/nodes/Node-normalize.html
@@ -51,4 +51,33 @@
   div.normalize();
   assert_array_equals(div.childNodes, [])
 }, "Empty text nodes")
+
+// The specification for normalize is clear that only "exclusive Text
+// nodes" are to be modified. This excludes CDATASection nodes, which
+// derive from Text. Naïve implementations may fail to skip
+// CDATASection nodes, or even worse, try to test textContent or
+// nodeValue without taking care to check the node type. They will
+// fail this test.
+test(function() {
+  // We create an XML document so that we can create CDATASection.
+  // Except for the CDATASection the result should be the same for
+  // an HTML document. (No non-Text node should be touched.)
+  var doc = new DOMParser().parseFromString("<div/>", "text/xml")
+  var div = doc.documentElement
+  var t1 = div.appendChild(doc.createTextNode("a"))
+  // The first parameter is the "target" of the processing
+  // instruction, and the 2nd is the text content.
+  var t2 = div.appendChild(doc.createProcessingInstruction("pi", ""))
+  var t3 = div.appendChild(doc.createTextNode("b"))
+  var t4 = div.appendChild(doc.createCDATASection(""))
+  var t5 = div.appendChild(doc.createTextNode("c"))
+  var t6 = div.appendChild(doc.createComment(""))
+  var t7 = div.appendChild(doc.createTextNode("d"))
+  var t8 = div.appendChild(doc.createElement("el"))
+  var t9 = div.appendChild(doc.createTextNode("e"))
+  var expected = [t1, t2, t3, t4, t5, t6, t7, t8, t9]
+  assert_array_equals(div.childNodes, expected)
+  div.normalize()
+  assert_array_equals(div.childNodes, expected)
+}, "Non-text nodes with empty textContent values.")
 </script>
diff --git a/dom/nodes/ParentNode-children.html b/dom/nodes/ParentNode-children.html
new file mode 100644
index 0000000..6621e7d
--- /dev/null
+++ b/dom/nodes/ParentNode-children.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>ParentNode.children</title>
+<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-children">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div style="display: none">
+  <ul><li id='test'>1</li><li>2</li><li>3</li><li>4</li></ul>
+</div>
+<script>
+test(() => {
+  var node = document.getElementById("test");
+  var parentNode = node.parentNode;
+  var children = parentNode.children;
+  assert_true(children instanceof HTMLCollection);
+  var li = document.createElement("li");
+  assert_equals(children.length, 4);
+
+  parentNode.appendChild(li);
+  assert_equals(children.length, 5);
+
+  parentNode.removeChild(li);
+  assert_equals(children.length, 4);
+}, "ParentNode.children should be a live collection");
+</script>
+</html>
diff --git a/dom/nodes/selectors.js b/dom/nodes/selectors.js
index 95d6204..4051b9d 100644
--- a/dom/nodes/selectors.js
+++ b/dom/nodes/selectors.js
@@ -90,6 +90,7 @@
 
   // - value                     [att=val]
   {name: "Attribute value selector, matching align attribute with value",                                    selector: "#attr-value [align=\"center\"]",                                     expect: ["attr-value-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute value selector, matching align attribute with value, unclosed bracket",                  selector: "#attr-value [align=\"center\"",                                      expect: ["attr-value-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
   {name: "Attribute value selector, matching align attribute with empty value",                              selector: "#attr-value [align=\"\"]",                                           expect: ["attr-value-div2"], level: 2, testType: TEST_QSA | TEST_MATCH},
   {name: "Attribute value selector, not matching align attribute with partial value",                        selector: "#attr-value [align=\"c\"]",                                          expect: [] /*no matches*/,   level: 2, testType: TEST_QSA},
   {name: "Attribute value selector, not matching align attribute with incorrect value",                      selector: "#attr-value [align=\"centera\"]",                                    expect: [] /*no matches*/,   level: 2, testType: TEST_QSA},
diff --git a/dom/ranges/Range-intersectsNode-2.html b/dom/ranges/Range-intersectsNode-2.html
new file mode 100644
index 0000000..48072d9
--- /dev/null
+++ b/dom/ranges/Range-intersectsNode-2.html
@@ -0,0 +1,36 @@
+<!doctype htlml>
+<title>Range.intersectsNode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="div"><span id="s0">s0</span><span id="s1">s1</span><span id="s2">s2</span></div>
+<script>
+// Taken from Chromium bug: http://crbug.com/822510
+test(() => {
+  const range = new Range();
+  const div = document.getElementById('div');
+  const s0 = document.getElementById('s0');
+  const s1 = document.getElementById('s1');
+  const s2 = document.getElementById('s2');
+
+  // Range encloses s0
+  range.setStart(div, 0);
+  range.setEnd(div, 1);
+  assert_true(range.intersectsNode(s0), '[s0] range.intersectsNode(s0)');
+  assert_false(range.intersectsNode(s1), '[s0] range.intersectsNode(s1)');
+  assert_false(range.intersectsNode(s2), '[s0] range.intersectsNode(s2)');
+
+  // Range encloses s1
+  range.setStart(div, 1);
+  range.setEnd(div, 2);
+  assert_false(range.intersectsNode(s0), '[s1] range.intersectsNode(s0)');
+  assert_true(range.intersectsNode(s1), '[s1] range.intersectsNode(s1)');
+  assert_false(range.intersectsNode(s2), '[s1] range.intersectsNode(s2)');
+
+  // Range encloses s2
+  range.setStart(div, 2);
+  range.setEnd(div, 3);
+  assert_false(range.intersectsNode(s0), '[s2] range.intersectsNode(s0)');
+  assert_false(range.intersectsNode(s1), '[s2] range.intersectsNode(s1)');
+  assert_true(range.intersectsNode(s2), '[s2] range.intersectsNode(s2)');
+}, 'Range.intersectsNode() simple cases');
+</script>
diff --git a/dom/traversal/NodeIterator.html b/dom/traversal/NodeIterator.html
index 677858d..3db5dec 100644
--- a/dom/traversal/NodeIterator.html
+++ b/dom/traversal/NodeIterator.html
@@ -200,20 +200,15 @@
   "(function(node) { return node.nodeName[0] == '#' })",
 ];
 
-var tests = [];
 for (var i = 0; i < testNodes.length; i++) {
   for (var j = 0; j < whatToShows.length; j++) {
     for (var k = 0; k < callbacks.length; k++) {
-      tests.push([
-        "document.createNodeIterator(" + testNodes[i]
-          + ", " + whatToShows[j] + ", " + callbacks[k] + ")",
-        eval(testNodes[i]), eval(whatToShows[j]), eval(callbacks[k])
-      ]);
+      test(() => {
+        testIterator(eval(testNodes[i]), eval(whatToShows[j]), eval(callbacks[k]));
+      }, "document.createNodeIterator(" + testNodes[i] + ", " + whatToShows[j] + ", " + callbacks[k] + ")");
     }
   }
 }
 
-generate_tests(testIterator, tests);
-
 testDiv.style.display = "none";
 </script>
diff --git a/domparsing/XMLSerializer-serializeToString.html b/domparsing/XMLSerializer-serializeToString.html
index 23323b9..8ee4aa4 100644
--- a/domparsing/XMLSerializer-serializeToString.html
+++ b/domparsing/XMLSerializer-serializeToString.html
@@ -39,6 +39,22 @@
   var xmlString = (new XMLSerializer()).serializeToString(root);
   assert_equals(xmlString, '<root xmlns="urn:bar"><outer xmlns=""><inner>value1</inner></outer></root>');
 }, 'Check if there is no redundant empty namespace declaration.');
+
+test(function() {
+  var serializer = new XMLSerializer();
+  var parser = new DOMParser();
+  var root = parser.parseFromString('<root />', 'text/xml').documentElement;
+  root.setAttribute('attr', '\t');
+  assert_in_array(serializer.serializeToString(root), [
+    '<root attr="&#9;"/>', '<root attr="&#x9;"/>']);
+  root.setAttribute('attr', '\n');
+  assert_in_array(serializer.serializeToString(root), [
+    '<root attr="&#xA;"/>', '<root attr="&#10;"/>']);
+  root.setAttribute('attr', '\r');
+  assert_in_array(serializer.serializeToString(root), [
+    '<root attr="&#xD;"/>', '<root attr="&#13;"/>']);
+}, 'check XMLSerializer.serializeToString escapes attribute values for roundtripping');
+
 </script>
  </body>
 </html>
diff --git a/domparsing/xmldomparser.html b/domparsing/xmldomparser.html
new file mode 100644
index 0000000..9dac65d
--- /dev/null
+++ b/domparsing/xmldomparser.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>XML Dom Parse readyState Test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    test(function () {
+        assert_equals(
+            (new DOMParser()).parseFromString("<html></html>", "text/xml").readyState,
+            "complete"
+        );
+    });
+</script>
diff --git a/domxpath/document.tentative.html b/domxpath/document.tentative.html
new file mode 100644
index 0000000..c44ebd9
--- /dev/null
+++ b/domxpath/document.tentative.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>XPath parent of documentElement</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<body>
+<script>
+test(function() {
+  var result = document.evaluate("..", // expression
+                                document.documentElement, // context node
+                                null, // resolver
+                                XPathResult.ANY_TYPE, // type
+                                null); // result
+  var matched = [];
+  var cur;
+  while ((cur = result.iterateNext()) !== null) {
+    matched.push(cur);
+  }
+  assert_array_equals(matched, [document]);
+});
+</script>
diff --git a/dpub-aam/doc-abstract-manual.html b/dpub-aam/doc-abstract-manual.html
index c8b39bd..b67c7fa 100644
--- a/dpub-aam/doc-abstract-manual.html
+++ b/dpub-aam/doc-abstract-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-abstract</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-acknowledgments-manual.html b/dpub-aam/doc-acknowledgments-manual.html
index e45c462..fad68ae 100644
--- a/dpub-aam/doc-acknowledgments-manual.html
+++ b/dpub-aam/doc-acknowledgments-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-acknowledgments</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-afterword-manual.html b/dpub-aam/doc-afterword-manual.html
index 94cd2d8..2a1f84b 100644
--- a/dpub-aam/doc-afterword-manual.html
+++ b/dpub-aam/doc-afterword-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-afterword</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-appendix-manual.html b/dpub-aam/doc-appendix-manual.html
index da4bf3c..c0ec8c8 100644
--- a/dpub-aam/doc-appendix-manual.html
+++ b/dpub-aam/doc-appendix-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-appendix</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-backlink-manual.html b/dpub-aam/doc-backlink-manual.html
index 51eb6ac..fc4b4fa 100644
--- a/dpub-aam/doc-backlink-manual.html
+++ b/dpub-aam/doc-backlink-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-backlink</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-biblioentry-manual.html b/dpub-aam/doc-biblioentry-manual.html
index d97ec2a..160bf10 100644
--- a/dpub-aam/doc-biblioentry-manual.html
+++ b/dpub-aam/doc-biblioentry-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-biblioentry</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-bibliography-manual.html b/dpub-aam/doc-bibliography-manual.html
index c69a17aa..b5eedf1 100644
--- a/dpub-aam/doc-bibliography-manual.html
+++ b/dpub-aam/doc-bibliography-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-bibliography</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-biblioref-manual.html b/dpub-aam/doc-biblioref-manual.html
index 29ea65f..860f1ef 100644
--- a/dpub-aam/doc-biblioref-manual.html
+++ b/dpub-aam/doc-biblioref-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-biblioref</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-chapter-manual.html b/dpub-aam/doc-chapter-manual.html
index 3a080ae..1a4e95e 100644
--- a/dpub-aam/doc-chapter-manual.html
+++ b/dpub-aam/doc-chapter-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-chapter</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-colophon-manual.html b/dpub-aam/doc-colophon-manual.html
index b7cdb5a..78ae8ed 100644
--- a/dpub-aam/doc-colophon-manual.html
+++ b/dpub-aam/doc-colophon-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-colophon</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-conclusion-manual.html b/dpub-aam/doc-conclusion-manual.html
index c1786c8..6ad672e 100644
--- a/dpub-aam/doc-conclusion-manual.html
+++ b/dpub-aam/doc-conclusion-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-conclusion</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-cover-manual.html b/dpub-aam/doc-cover-manual.html
index c09931f..4c2da2f 100644
--- a/dpub-aam/doc-cover-manual.html
+++ b/dpub-aam/doc-cover-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-cover</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-credit-manual.html b/dpub-aam/doc-credit-manual.html
index f23baa5..a3774d3 100644
--- a/dpub-aam/doc-credit-manual.html
+++ b/dpub-aam/doc-credit-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-credit</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-credits-manual.html b/dpub-aam/doc-credits-manual.html
index 5562c5d..f0eb111 100644
--- a/dpub-aam/doc-credits-manual.html
+++ b/dpub-aam/doc-credits-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-credits</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-dedication-manual.html b/dpub-aam/doc-dedication-manual.html
index 9c95d92..20d8348 100644
--- a/dpub-aam/doc-dedication-manual.html
+++ b/dpub-aam/doc-dedication-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-dedication</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-endnote-manual.html b/dpub-aam/doc-endnote-manual.html
index 63320c9..43dd4b5 100644
--- a/dpub-aam/doc-endnote-manual.html
+++ b/dpub-aam/doc-endnote-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-endnote</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-endnotes-manual.html b/dpub-aam/doc-endnotes-manual.html
index 040b6e9..11365c0 100644
--- a/dpub-aam/doc-endnotes-manual.html
+++ b/dpub-aam/doc-endnotes-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-endnotes</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-epigraph-manual.html b/dpub-aam/doc-epigraph-manual.html
index 26b5d63..7e1e038 100644
--- a/dpub-aam/doc-epigraph-manual.html
+++ b/dpub-aam/doc-epigraph-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-epigraph</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-epilogue-manual.html b/dpub-aam/doc-epilogue-manual.html
index 059e7c3..f00613c 100644
--- a/dpub-aam/doc-epilogue-manual.html
+++ b/dpub-aam/doc-epilogue-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-epilogue</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-errata-manual.html b/dpub-aam/doc-errata-manual.html
index 759fb6c..e37fe92 100644
--- a/dpub-aam/doc-errata-manual.html
+++ b/dpub-aam/doc-errata-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-errata</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-example-manual.html b/dpub-aam/doc-example-manual.html
index df11392..32c32d8 100644
--- a/dpub-aam/doc-example-manual.html
+++ b/dpub-aam/doc-example-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-example</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-footnote-manual.html b/dpub-aam/doc-footnote-manual.html
index ad5f1f7..c865fa1 100644
--- a/dpub-aam/doc-footnote-manual.html
+++ b/dpub-aam/doc-footnote-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-footnote</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-foreword-manual.html b/dpub-aam/doc-foreword-manual.html
index c56d314..2774b50 100644
--- a/dpub-aam/doc-foreword-manual.html
+++ b/dpub-aam/doc-foreword-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-foreword</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-glossary-manual.html b/dpub-aam/doc-glossary-manual.html
index b22e3be..8a5baee 100644
--- a/dpub-aam/doc-glossary-manual.html
+++ b/dpub-aam/doc-glossary-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-glossary</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-glossref-manual.html b/dpub-aam/doc-glossref-manual.html
index 6a03b87..99b8edd 100644
--- a/dpub-aam/doc-glossref-manual.html
+++ b/dpub-aam/doc-glossref-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-glossref</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-index-manual.html b/dpub-aam/doc-index-manual.html
index b24abfa..39491e0 100644
--- a/dpub-aam/doc-index-manual.html
+++ b/dpub-aam/doc-index-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-index</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-introduction-manual.html b/dpub-aam/doc-introduction-manual.html
index e720f74..fadf2d9 100644
--- a/dpub-aam/doc-introduction-manual.html
+++ b/dpub-aam/doc-introduction-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-introduction</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-noteref-manual.html b/dpub-aam/doc-noteref-manual.html
index f78af80..3f053bc 100644
--- a/dpub-aam/doc-noteref-manual.html
+++ b/dpub-aam/doc-noteref-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-noteref</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-notice-manual.html b/dpub-aam/doc-notice-manual.html
index d3135a7..0656b53 100644
--- a/dpub-aam/doc-notice-manual.html
+++ b/dpub-aam/doc-notice-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-notice</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-pagebreak-manual.html b/dpub-aam/doc-pagebreak-manual.html
index 1bafd77..2390948 100644
--- a/dpub-aam/doc-pagebreak-manual.html
+++ b/dpub-aam/doc-pagebreak-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-pagebreak</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-pagelist-manual.html b/dpub-aam/doc-pagelist-manual.html
index 24bd42e..33993fe 100644
--- a/dpub-aam/doc-pagelist-manual.html
+++ b/dpub-aam/doc-pagelist-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-pagelist</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-part-manual.html b/dpub-aam/doc-part-manual.html
index bc06f0e..997be1e 100644
--- a/dpub-aam/doc-part-manual.html
+++ b/dpub-aam/doc-part-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-part</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-preface-manual.html b/dpub-aam/doc-preface-manual.html
index 0a2bdbf..8ed5d3b 100644
--- a/dpub-aam/doc-preface-manual.html
+++ b/dpub-aam/doc-preface-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-preface</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-prologue-manual.html b/dpub-aam/doc-prologue-manual.html
index 077cfb8..b806c2e 100644
--- a/dpub-aam/doc-prologue-manual.html
+++ b/dpub-aam/doc-prologue-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-prologue</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-pullquote-manual.html b/dpub-aam/doc-pullquote-manual.html
index 543c4ed..b52c970 100644
--- a/dpub-aam/doc-pullquote-manual.html
+++ b/dpub-aam/doc-pullquote-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-pullquote</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-qna-manual.html b/dpub-aam/doc-qna-manual.html
index 8b9c466..a334d64 100644
--- a/dpub-aam/doc-qna-manual.html
+++ b/dpub-aam/doc-qna-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-qna</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-subtitle-manual.html b/dpub-aam/doc-subtitle-manual.html
index 5518919..217f612 100644
--- a/dpub-aam/doc-subtitle-manual.html
+++ b/dpub-aam/doc-subtitle-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-subtitle</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-tip-manual.html b/dpub-aam/doc-tip-manual.html
index ec59b0f..dda02b6 100644
--- a/dpub-aam/doc-tip-manual.html
+++ b/dpub-aam/doc-tip-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-tip</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aam/doc-toc-manual.html b/dpub-aam/doc-toc-manual.html
index c3969e2..a06a58d 100644
--- a/dpub-aam/doc-toc-manual.html
+++ b/dpub-aam/doc-toc-manual.html
@@ -2,7 +2,6 @@
 <html>
   <head>
     <title>doc-toc</title>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/dpub-aria/inuse-manual.html b/dpub-aria/inuse-manual.html
index 1609337..cd6af1e 100644
--- a/dpub-aria/inuse-manual.html
+++ b/dpub-aria/inuse-manual.html
@@ -2,7 +2,6 @@
 <html>
 <head>
   <title>Check for Dpub Vocabulary Role Usage</title>
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
diff --git a/editing/data/insertparagraph.js b/editing/data/insertparagraph.js
index 70811ca..fcf9e85 100644
--- a/editing/data/insertparagraph.js
+++ b/editing/data/insertparagraph.js
@@ -780,6 +780,36 @@
     "<p><b>foo</b></p><p>{}bar</p>",
     [true,true],
     {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b>foo[]</b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b>foo</b></p><p><b>{}<br></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b>foo[]</b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b>foo</b></div><div><b>{}<br></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b><i>foo[]</i></b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><i>foo</i></b></p><p><b><i>{}<br></i></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b><i>foo[]</i></b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><i>foo</i></b></div><div><b><i>{}<br></i></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><i><b>foo[]</b></i></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><i><b>foo</b></i></p><p><i><b>{}<br></b></i></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><i><b>foo[]</b></i></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><i><b>foo</b></i></div><div><i><b>{}<br></b></i></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
 ["foo<b>[]bar</b>",
     [["defaultparagraphseparator","div"],["insertparagraph",""]],
     "<div>foo</div><div>{}<b>bar</b></div>",
@@ -790,6 +820,36 @@
     "<p>foo</p><p>{}<b>bar</b></p>",
     [true,true],
     {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b>[]foo</b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><br></b></p><p><b>{}foo</b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b>[]foo</b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><br></b></div><div><b>{}foo</b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b><i>[]foo</i></b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><i><br></i></b></p><p><b><i>{}foo</i></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b><i>[]foo</i></b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><i><br></i></b></div><div><b><i>{}foo</i></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><i><b>[]foo</b></i></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><i><b><br></b></i></p><p><i><b>{}foo</b></i></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><i><b>[]foo</b></i></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><i><b><br></b></i></div><div><i><b>{}foo</b></i></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
 ["<b>foo[]</b><i>bar</i>",
     [["defaultparagraphseparator","div"],["insertparagraph",""]],
     "<div><b>foo</b></div><div>{}<i>bar</i></div>",
@@ -860,16 +920,6 @@
     "<p><b>foo</b></p><p>{}<b>bar</b></p>",
     [true,true,true],
     {"stylewithcss":[false,true,"",false,false,""],"defaultparagraphseparator":[false,false,"p",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
-["<p><b>[]foo</b></p>",
-    [["defaultparagraphseparator","div"],["insertparagraph",""]],
-    "<p><br></p><p>{}<b>foo</b></p>",
-    [true,true],
-    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
-["<p><b>[]foo</b></p>",
-    [["defaultparagraphseparator","p"],["insertparagraph",""]],
-    "<p><br></p><p>{}<b>foo</b></p>",
-    [true,true],
-    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
 ["<p><b id=x class=y>foo[]bar</b></p>",
     [["stylewithcss","true"],["defaultparagraphseparator","div"],["insertparagraph",""]],
     "<p><b id=\"x\" class=\"y\">foo</b></p><p>{}<b class=\"y\">bar</b></p>",
@@ -930,6 +980,38 @@
     "<p><a href=\"foo\">foo</a></p><p>{}<a href=\"foo\">bar</a>baz</p>",
     [true,true],
     {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo>foo[]bar</a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\">foo</a></p><p><a href=\"foo\">{}bar</a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo>foo[]bar</a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\">foo</a></div><div><a href=\"foo\">{}bar</a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo[]bar</b></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo</b></a></p><p><a href=\"foo\"><b>{}bar</b></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo[]bar</b></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo</b></a></div><div><a href=\"foo\"><b>{}bar</b></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b><a href=foo>foo[]bar</a></b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><a href=\"foo\">foo</a></b></p><p><b><a href=\"foo\">{}bar</a></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b><a href=foo>foo[]bar</a></b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><a href=\"foo\">foo</a></b></div><div><b><a href=\"foo\">{}bar</a></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+// <a href="foo"> shouldn't be duplicated in new paragraph when it's split at
+// start or end of it.
 ["<a href=foo>foo[]</a>bar",
     [["defaultparagraphseparator","div"],["insertparagraph",""]],
     "<div><a href=\"foo\">foo</a></div><div>{}bar</div>",
@@ -950,6 +1032,190 @@
     "<p>foo</p><p>{}<a href=\"foo\">bar</a></p>",
     [true,true],
     {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo>foo[]</a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\">foo</a></p><p>{}<br></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo>foo[]</a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\">foo</a></div><div>{}<br></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo>[]foo</a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><br></p><p><a href=\"foo\">{}foo</a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo>[]foo</a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><br></div><div><a href=\"foo\">{}foo</a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo[]</b></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo</b></a></p><p>{}<br></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo[]</b></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo</b></a></div><div>{}<br></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>[]foo</b></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><br></p><p><a href=\"foo\"><b>{}foo</b></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>[]foo</b></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><br></div><div><a href=\"foo\"><b>{}foo</b></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b><a href=foo>foo[]</a></b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><a href=\"foo\">foo</a></b></p><p><b>{}<br></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b><a href=foo>foo[]</a></b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><a href=\"foo\">foo</a></b></div><div><b>{}<br></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b><a href=foo>[]foo</a></b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><br></b></p><p><b><a href=\"foo\">{}foo</a></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b><a href=foo>[]foo</a></b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><br></b></div><div><b><a href=\"foo\">{}foo</a></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+// <br> is usually inserted after an <a href="foo"> element is split next to
+// an ASCII whitespace.  So, <br> element should be ignored if it's invisible.
+["<p><a href=foo>foo []<br></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\">foo <br></a></p><p>{}<br></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo>foo []<br></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\">foo <br></a></div><div>{}<br></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo []<br></b></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo <br></b></a></p><p>{}<br></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo []<br></b></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo <br></b></a></div><div>{}<br></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b><a href=foo>foo []<br></a></b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><a href=\"foo\">foo <br></a></b></p><p><b>{}<br></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b><a href=foo>foo []<br></a></b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><a href=\"foo\">foo <br></a></b></div><div><b>{}<br></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo>foo {}<br></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\">foo <br></a></p><p>{}<br></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo>foo {}<br></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\">foo <br></a></div><div>{}<br></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo {}<br></b></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo <br></b></a></p><p>{}<br></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo {}<br></b></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo <br></b></a></div><div>{}<br></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><b><a href=foo>foo {}<br></a></b></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><b><a href=\"foo\">foo <br></a></b></p><p><b>{}<br></b></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><b><a href=foo>foo {}<br></a></b></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><b><a href=\"foo\">foo <br></a></b></div><div><b>{}<br></b></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+// So, if <br> is visible, its any inline containers should be split.
+["<p><a href=foo>foo []<br><br></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\">foo <br></a></p><p><a href=\"foo\">{}<br><br></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo>foo []<br><br></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\">foo <br></a></div><div><a href=\"foo\">{}<br><br></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo []<br><br></b></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo <br></b></a></p><p><a href=\"foo\"><b>{}<br><br></b></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo []<br><br></b></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo <br></b><br></a></div><div><a href=\"foo\"><b>{}<br><br></b></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo []<br></b><br></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo <br></b></a></p><p><a href=\"foo\"><b>{}<br></b><br></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo []<br></b><br></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo <br></b></a></div><div><a href=\"foo\"><b>{}<br></b><br></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo>foo {}<br><br></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\">foo <br><br></a></p><p><a href=\"foo\">{}<br></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo>foo {}<br><br></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\">foo <br></a></div><div><a href=\"foo\">{}<br></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo {}<br><br></b></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo <br></b><br></a></p><p><a href=\"foo\"><b>{}<br><br></b></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo {}<br><br></b></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo <br></b><br></a></div><div><a href=\"foo\"><b>{}<br><br></b></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+["<p><a href=foo><b>foo {}<br></b><br></a></p>",
+    [["defaultparagraphseparator","div"],["insertparagraph",""]],
+    "<p><a href=\"foo\"><b>foo <br></b></a></p><p><a href=\"foo\"><b>{}<br></b><br></a></p>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"p",false,false,"div"],"insertparagraph":[false,false,"",false,false,""]}],
+["<div><a href=foo><b>foo {}<br></b><br></a></div>",
+    [["defaultparagraphseparator","p"],["insertparagraph",""]],
+    "<div><a href=\"foo\"><b>foo <br></b></a></div><div><a href=\"foo\"><b>{}<br></b><br></a></div>",
+    [true,true],
+    {"defaultparagraphseparator":[false,false,"div",false,false,"p"],"insertparagraph":[false,false,"",false,false,""]}],
+
 ["<p>foo[]<!--bar-->",
     [["defaultparagraphseparator","div"],["insertparagraph",""]],
     "<p>foo</p><p>{}<!--bar--><br></p>",
diff --git a/encoding/big5-encoder.html b/encoding/big5-encoder.html
index 7260b6b..58d60d5 100644
--- a/encoding/big5-encoder.html
+++ b/encoding/big5-encoder.html
@@ -15,8 +15,8 @@
 
  encode("ab", "ab", "very basic")
  // edge cases
- encode("\u9EA6", "%26%2340614%3B", "Highest-pointer BMP character excluded from encoder");
- encode("\uD858\uDE6B", "%26%23156267%3B", "Highest-pointer character excluded from encoder");
+ encode("\u9EA6", "&%2340614;", "Highest-pointer BMP character excluded from encoder");
+ encode("\uD858\uDE6B", "&%23156267;", "Highest-pointer character excluded from encoder");
  encode("\u3000", "%A1@", "Lowest-pointer character included in encoder");
  encode("\u20AC", "%A3%E1", "Euro; the highest-pointer character before a range of 30 unmapped pointers");
  encode("\u4E00", "%A4@", "The lowest-pointer character after the range of 30 unmapped pointers");
@@ -24,8 +24,8 @@
  encode("\uFFE2", "%C8%CD", "The lowest-pointer character after the range of 41 unmapped pointers");
  encode("\u79D4", "%FE%FE", "The last character in the index");
  // not in index
- encode("\u2603", "%26%239731%3B", "The canonical BMP test character that is not in the index");
- encode("\uD83D\uDCA9", "%26%23128169%3B", "The canonical astral test character that is not in the index");
+ encode("\u2603", "&%239731;", "The canonical BMP test character that is not in the index");
+ encode("\uD83D\uDCA9", "&%23128169;", "The canonical astral test character that is not in the index");
  // duplicate low bits
  encode("\uD840\uDFB5", "%FDj", "A Plane 2 character whose low 16 bits match a BMP character that has a lower pointer");
  // prefer last
diff --git a/encoding/gbk-encoder.html b/encoding/gbk-encoder.html
index a6074f9..90d0824 100644
--- a/encoding/gbk-encoder.html
+++ b/encoding/gbk-encoder.html
@@ -17,5 +17,5 @@
  encode("\u4E02", "%81@", "character")
  encode("\uE4C6", "%A1@", "PUA")
  encode("\uE4C5", "%FE%FE", "PUA #2")
- encode("\ud83d\udca9", "%26%23128169%3B", "poo")
+ encode("\ud83d\udca9", "&%23128169;", "poo")
 </script>
diff --git a/encoding/idlharness.any.js b/encoding/idlharness.any.js
new file mode 100644
index 0000000..25ec97a
--- /dev/null
+++ b/encoding/idlharness.any.js
@@ -0,0 +1,14 @@
+// META: global=window,worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+promise_test(async() => {
+  const text = await (await fetch('/interfaces/encoding.idl')).text();
+  const idl_array = new IdlArray();
+  idl_array.add_idls(text);
+  idl_array.add_objects({
+    TextEncoder: ['new TextEncoder()'],
+    TextDecoder: ['new TextDecoder()']
+  });
+  idl_array.test();
+}, 'Encoding Standard IDL');
diff --git a/encoding/idlharness.html b/encoding/idlharness.html
deleted file mode 100644
index 3359744..0000000
--- a/encoding/idlharness.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>idlharness test: Encoding Living Standard API</title>
-<link rel="author" title="Joshua Bell" href="mailto:jsbell@google.com" />
-<link rel="help" href="https://encoding.spec.whatwg.org/#api"/>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/resources/idlharness.js"></script>
-
-<h1>idlharness test</h1>
-<p>This test validates the WebIDL included in the Encoding Living Standard.</p>
-
-<script type="text/plain" class="untested-idl">
-interface Window {};
-</script>
-
-<script type="text/plain" class="idl">
-// 8.1 Interface TextDecoder
-
-dictionary TextDecoderOptions {
-  boolean fatal = false;
-  boolean ignoreBOM = false;
-};
-
-dictionary TextDecodeOptions {
-  boolean stream = false;
-};
-
-[Constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options),
- Exposed=(Window,Worker)]
-interface TextDecoder {
-  readonly attribute DOMString encoding;
-  readonly attribute boolean fatal;
-  readonly attribute boolean ignoreBOM;
-  USVString decode(optional BufferSource input, optional TextDecodeOptions options);
-};
-
-// 8.2 Interface TextDecoder
-
-[Constructor,
- Exposed=(Window,Worker)]
-interface TextEncoder {
-  readonly attribute DOMString encoding;
-  [NewObject] Uint8Array encode(optional USVString input = "");
-};
-</script>
-
-<script>
-function select(selector) {
-  return [].slice.call(document.querySelectorAll(selector))
-    .map(function(e) { return e.textContent; })
-    .join('\n\n');
-}
-
-var idl = select('.idl')
-var untested = select('.untested-idl');
-var idl_array = new IdlArray();
-idl_array.add_untested_idls(untested);
-idl_array.add_idls(idl);
-idl_array.add_objects({
-  TextEncoder: ['new TextEncoder()'],
-  TextDecoder: ['new TextDecoder()']
-});
-idl_array.test();
-</script>
diff --git a/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-csiso2022jp.html b/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-csiso2022jp.html
index 8089b83..213791a 100644
--- a/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-csiso2022jp.html
+++ b/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-csiso2022jp.html
@@ -7,6 +7,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="jis0208_index.js"></script>
+<script src="iso2022jp-encoder.js"></script>
 <link rel="author" title="Richard Ishida" href="mailto:ishida@w3.org">
 <link rel="help" href="https://encoding.spec.whatwg.org/#names-and-labels">
 <meta name="assert" content="The browser produces the same encoding behavior for a document labeled 'csiso2022jp' as for a document labeled 'iso-2022-jp'.">
diff --git a/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-href-ascii.html b/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-href-ascii.html
deleted file mode 100644
index d5a747b..0000000
--- a/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-href-ascii.html
+++ /dev/null
@@ -1,72 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="iso-2022-jp"> <!-- test breaks if the server overrides this -->
-<title>ISO 2022-JP encoding (href)</title>
-<meta name="timeout" content="long">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="jis0208_index.js"></script>
-<script src="iso2022jp-encoder.js"></script>
-<link rel="author" title="Richard Ishida" href="mailto:ishida@w3.org">
-<link rel="help" href="https://encoding.spec.whatwg.org/#iso-2022-jp">
-<meta name="assert" content="The browser produces the expected byte sequences for all characters in the iso-2022-jp encoding after 0x9F when writing characters to an href value, using the encoder steps in the specification.">
-<script>
-function XnormalizeStr(str) {
-    str = encodeURIComponent(str);
-    var ptr = 0;
-    var out = "";
-    for (c = 0; c < str.length; c++) {
-        if (str.charAt(ptr) == "%") {
-            out += String.fromCodePoint(
-                parseInt(str.charAt(ptr + 1) + str.charAt(ptr + 2), 16)
-            );
-            ptr += 2;
-        } else out += str.charAt(ptr);
-        ptr++;
-    }
-}
-
-function encode(input, expected, desc) {
-    // tests whether a Unicode character is converted to an equivalent byte sequence by href
-    // input: a Unicode character
-    // expected: expected byte sequence
-    // desc: what's being tested
-    test(function() {
-        var a = document.createElement("a"); // <a> uses document encoding for URL's query
-        a.href = "https://example.com/?" + input;
-        var result = a.search.substr(1); // remove leading "?"
-        assert_equals(normalizeStr(result), normalizeStr(expected));
-    }, desc);
-}
-
-// create a simple list of just those code points for which there is an encoding possible
-codepoints = [];
-for (var i = 0x00; i < 0xfe; i++) {
-    result = iso2022jpEncoder(String.fromCodePoint(i));
-    if (result) {
-        var item = {};
-        codepoints.push(item);
-        item.cp = i;
-        item.expected = "%" + result.replace(/ /g, "%");
-        item.expected = item.expected.replace(/%1B%28%42$/, "");
-    }
-}
-
-// run the tests
-encode(String.fromCodePoint(0x65), "e;", "%65");
-encode(String.fromCodePoint(0x1b), "d;", "%1B");
-
-//for (var x=0;x<codepoints.length;x++) {
-//  encode(String.fromCodePoint(codepoints[x].cp), codepoints[x].expected, "U+"+codepoints[x].cp.toString(16).toUpperCase()+' '+String.fromCodePoint(codepoints[x].cp)+" "+codepoints[x].expected)
-//  }
-
-// NOTES
-// this test relies on support for String.fromCodePoint, which appears to be supported by major desktop browsers
-// the test excludes ASCII characters
-</script>
-</head>
-<body>
-<div id="log"></div>
-</body>
-</html>
diff --git a/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-errors-hangul.html b/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-errors-hangul.html
deleted file mode 100644
index 1a79c6f..0000000
--- a/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-errors-hangul.html
+++ /dev/null
@@ -1,181 +0,0 @@
-<!DOCTYPE html>
-<html>   <!-- DOESN'T WORK, NOT SURE WHY - THERE ARE 11 CHARACTERS PRODUCED, BUT ALL ARE UNASSIGNED CODE POINTS, SO SAFE TO IGNORE FOR NOW -->
-<head>
-<title>EUC-KR encoding errors (form, hangul)</title>
-<meta charset="euc-kr"> <!-- test breaks if the server overrides this -->
-<meta name="timeout" content="long">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="euckr_index.js"></script>
-<link rel="author" title="Richard Ishida" href="mailto:ishida@w3.org">
-<link rel="help" href="https://encoding.spec.whatwg.org/#euc-kr">
-<meta name="assert" content="The browser produces percent-escaped character references when encoding bytes for a URL produced by a form when encoding hangul characters that are not in the euc-kr encoding.">
-<style>
- iframe { display:none }
- form { display:none }
-</style>
-</head>
-<body>
-<div id="log"></div>
-<script>
-var tests = [];
-var cplist = [];
-var numTests = null;
-var numFrames = 2;
-var chunkSize = 400;
-var numChunks = null;
-var frames = null;
-var frames = null;
-var forms = null;
-var seperator = ",";
-var encodedSeperator = encodeURIComponent(",");
-var currentChunkIndex = 0;
-
-function getByteSequence(cp) {
-  // uses the Encoding spec algorithm to derive a sequence of bytes for a character that can be encoded
-  // the result is either a percent-encoded value or null (if the character can't be encoded)
-  // cp: integer, a code point number
-  var cps = [cp];
-  var out = "";
-
-  while (cps.length > 0) {
-    cp = cps.shift();
-    if (cp >= 0x00 && cp <= 0x7f) {
-      // ASCII
-      out += "%" + cp.toString(16);
-      continue;
-    }
-    var ptr = indexcodepoints[cp];
-    if (ptr == null) {
-      return null;
-    }
-    var lead = Math.floor(ptr / 190) + 0x81;
-    var trail = ptr % 190 + 0x41;
-    out +=
-      "%" +
-      lead.toString(16).toUpperCase() +
-      "%" +
-      trail.toString(16).toUpperCase();
-  }
-  return out;
-}
-
-// set up a sparse array of all unicode codepoints listed in the index
-// this will be used for lookup in getByteSequence
-var indexcodepoints = []; // index is unicode cp, value is pointer
-for (p = 0; p < euckr.length; p++) {
-  if (euckr[p] != null && indexcodepoints[euckr[p]] == null) {
-    indexcodepoints[euckr[p]] = p;
-  }
-}
-
-setup(function() {
-  // set up a simple array of all unicode codepoints that are not encoded
-  var codepoints = [];
-
-  for (i = 0xac00; i < 0xd7af; i++) {
-    result = getByteSequence(i);
-    if (!result) {
-      var item = {};
-      codepoints.push(item);
-      item.cp = i;
-      item.expected = "%26%23" + item.cp + "%3B";
-      item.desc = "hangul ";
-    }
-  }
-
-  // convert the information into a simple array of objects that can be easily traversed
-  var currentChunk = [];
-  var currentTests = [];
-  cplist = [currentChunk];
-  tests = [currentTests];
-  for (i = 161; i < codepoints.length; i++) {
-    if (currentChunk.length == chunkSize) {
-      currentChunk = [];
-      cplist.push(currentChunk);
-      currentTests = [];
-      tests.push(currentTests);
-    }
-    var item = {};
-    currentChunk.push(item);
-    item.cp = codepoints[i].cp;
-    item.expected = codepoints[i].expected;
-    item.desc = codepoints[i].desc;
-    currentTests.push(
-      async_test(
-        item.desc +
-          " U+" +
-          item.cp.toString(16).toUpperCase() +
-          " " +
-          String.fromCodePoint(item.cp) +
-          item.expected
-      )
-    );
-  }
-
-  numChunks = cplist.length;
-
-  for (var i = 0; i < numFrames; i++) {
-    var frame = document.createElement("iframe");
-    frame.id = frame.name = "frame-" + i;
-    document.body.appendChild(frame);
-    var form = document.createElement("form");
-    form.id = "form-" + i;
-    form.method = "GET";
-    form.action = "/common/blank.html";
-    form.acceptCharset = "euc-kr";
-    form.target = frame.id;
-    var input = document.createElement("input");
-    input.id = input.name = "input-" + i;
-    form.appendChild(input);
-    document.body.appendChild(form);
-  }
-
-  addEventListener("load", function() {
-    frames = Array.prototype.slice.call(
-      document.getElementsByTagName("iframe")
-    );
-    forms = Array.prototype.slice.call(document.getElementsByTagName("form"));
-    inputs = Array.prototype.slice.call(document.getElementsByTagName("input"));
-    for (var i = 0; i < Math.min(numFrames, numChunks); i++) {
-      runNext(i);
-    }
-  });
-});
-
-function runNext(id) {
-  var i = currentChunkIndex;
-  currentChunkIndex += 1;
-
-  var iframe = frames[id];
-  var form = forms[id];
-  var input = inputs[id];
-
-  input.value = cplist[i]
-    .map(function(x) {
-      return String.fromCodePoint(x.cp);
-    })
-    .join(seperator);
-  form.submit();
-
-  iframe.onload = function() {
-    var url = iframe.contentWindow.location;
-    var query = url.search;
-    var result_string = query.substr(query.indexOf("=") + 1);
-    var results = result_string.split(encodedSeperator);
-
-    for (var j = 0; j < cplist[i].length; j++) {
-      var t = tests[i][j];
-      t.step(function() {
-        assert_equals(results[j], cplist[i][j].expected); // HERE'S THE TEST
-      });
-      t.done();
-    }
-    if (currentChunkIndex < numChunks) {
-      runNext(id);
-    }
-  };
-}
-</script>
-</body>
-</html>
diff --git a/encoding/legacy-mb-tchinese/big5/big5-enc-ascii.html b/encoding/legacy-mb-tchinese/big5/big5-enc-ascii.html
index 0065ab5..9195920 100644
--- a/encoding/legacy-mb-tchinese/big5/big5-enc-ascii.html
+++ b/encoding/legacy-mb-tchinese/big5/big5-enc-ascii.html
@@ -33,6 +33,10 @@
 
 // test ASCII - test separately for chars that aren't escaped
 for (var a = 0; a < 0x7f; a++) {
+  // The first 3 are stripped from URLs and the last is # which introduces a new URL segment
+  if (a === 0x09 || a === 0x0a || a === 0x0d || a === 0x23) {
+    continue;
+  }
   hex = a.toString(16).toUpperCase();
   while (hex.length < 2) {
     hex = "0" + hex;
diff --git a/encrypted-media/drm-mp4-playback-temporary-playduration-keystatus.html b/encrypted-media/drm-mp4-playback-temporary-playduration-keystatus.html
new file mode 100644
index 0000000..cbecb0d
--- /dev/null
+++ b/encrypted-media/drm-mp4-playback-temporary-playduration-keystatus.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Encrypted Media Extensions: Successful Playback, Temporary session limited playduration, check keystatus, DRM, mp4</title>
+    <link rel="help" href="https://w3c.github.io/encrypted-media/">
+
+    <!-- Web Platform Test Harness scripts -->
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+
+    <!-- Helper scripts for Encrypted Media Extensions tests  -->
+    <script src=/encrypted-media/util/utils.js></script>
+    <script src=/encrypted-media/util/utf8.js></script>
+    <script src=/encrypted-media/util/fetch.js></script>
+    <script src=/encrypted-media/util/testmediasource.js></script>
+
+    <!-- Content metadata -->
+    <script src=/encrypted-media/content/content-metadata.js></script>
+
+    <!-- Message handler for DRM servers -->
+    <script src=/encrypted-media/util/drm-messagehandler.js></script>
+
+    <!-- The script for this specific test -->
+    <script src=/encrypted-media/scripts/playback-temporary-playduration-keystatus.js></script>
+
+  </head>
+  <body>
+    <div id='log'></div>
+
+    <div id='video'>
+      <video id="videoelement" width="200px"></video>
+    </div>
+
+    <script>
+        var keysystem = getSupportedKeySystem(),
+            contentitem = content['mp4-basic'],
+            handler = new MessageHandler( keysystem, contentitem ),
+            config = {  video:              document.getElementById('videoelement'),
+                        keysystem:          keysystem,
+                        messagehandler:     handler.messagehandler,
+                        audioPath:          contentitem.audio.path,
+                        videoPath:          contentitem.video.path,
+                        audioType:          contentitem.audio.type,
+                        videoType:          contentitem.video.type,
+                        initDataType:       contentitem.initDataType,
+                        playduration:       2000,
+                        testcase:           'single key' };
+
+        runTest(config);
+    </script>
+  </body>
+</html>
diff --git a/encrypted-media/drm-mp4-playback-temporary-playduration.html b/encrypted-media/drm-mp4-playback-temporary-playduration.html
new file mode 100644
index 0000000..24012b6
--- /dev/null
+++ b/encrypted-media/drm-mp4-playback-temporary-playduration.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Encrypted Media Extensions: Successful Playback, Temporary session limited playduration, DRM, mp4</title>
+    <link rel="help" href="https://w3c.github.io/encrypted-media/">
+
+    <!-- Web Platform Test Harness scripts -->
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+
+    <!-- Helper scripts for Encrypted Media Extensions tests  -->
+    <script src=/encrypted-media/util/utils.js></script>
+    <script src=/encrypted-media/util/utf8.js></script>
+    <script src=/encrypted-media/util/fetch.js></script>
+    <script src=/encrypted-media/util/testmediasource.js></script>
+
+    <!-- Content metadata -->
+    <script src=/encrypted-media/content/content-metadata.js></script>
+
+    <!-- Message handler for DRM servers -->
+    <script src=/encrypted-media/util/drm-messagehandler.js></script>
+
+    <!-- The script for this specific test -->
+    <script src=/encrypted-media/scripts/playback-temporary-playduration.js></script>
+
+  </head>
+  <body>
+    <div id='log'></div>
+
+    <div id='video'>
+      <video id="videoelement" width="200px"></video>
+    </div>
+
+    <script>
+        var keysystem = getSupportedKeySystem(),
+            contentitem = content['mp4-basic'],
+            handler = new MessageHandler( keysystem, contentitem ),
+            config = {  video:              document.getElementById('videoelement'),
+                        keysystem:          keysystem,
+                        messagehandler:     handler.messagehandler,
+                        audioPath:          contentitem.audio.path,
+                        videoPath:          contentitem.video.path,
+                        audioType:          contentitem.audio.type,
+                        videoType:          contentitem.video.type,
+                        initDataType:       contentitem.initDataType,
+                        playduration:       2000,
+                        testcase:           'single key' };
+
+        runTest(config);
+    </script>
+  </body>
+</html>
diff --git a/encrypted-media/idlharness.https.html b/encrypted-media/idlharness.https.html
index e65ad5e..a609f94 100644
--- a/encrypted-media/idlharness.https.html
+++ b/encrypted-media/idlharness.https.html
@@ -22,7 +22,7 @@
     <script>
         setup(function() {
 
-            fetch( 'EncryptedMediaExtensions.idl')
+            fetch( '/interfaces/encrypted-media.idl' )
             .then( function( response ) {
                 if ( !response.ok ) throw new Error( 'IDL fetch failed' );
                 return response.text();
@@ -30,11 +30,10 @@
             .then( function( idls ) {
 
                 var idl_array = new IdlArray();
-                idl_array.add_untested_idls("[PrimaryGlobal] interface Window {};");
                 idl_array.add_untested_idls("interface Navigator {};");
-                idl_array.add_untested_idls("interface ArrayBuffer {};");
                 idl_array.add_untested_idls("interface HTMLMediaElement {};");
                 idl_array.add_untested_idls("interface Event {};");
+                idl_array.add_untested_idls("dictionary EventInit {};");
                 idl_array.add_untested_idls("interface EventTarget {};");
 
                 idl_array.add_idls(idls);
diff --git a/encrypted-media/polyfill/make-polyfill-tests.py b/encrypted-media/polyfill/make-polyfill-tests.py
index b424856..532037e 100644
--- a/encrypted-media/polyfill/make-polyfill-tests.py
+++ b/encrypted-media/polyfill/make-polyfill-tests.py
@@ -1,4 +1,7 @@
 #!/usr/bin/python
+
+from __future__ import print_function
+
 import os, re, os.path, glob
 
 head = re.compile( r"^(\s*</head>)", re.MULTILINE )
@@ -19,11 +22,11 @@
 
 if __name__ == '__main__' :
     if (not os.getcwd().endswith('polyfill')) :
-        print "Please run from polyfill directory"
+        print("Please run from polyfill directory")
         exit( 1 )
 
     for infile in glob.glob( "../*.html" ) :
         process_file( infile, os.path.basename( infile ) )
 
     for infile in glob.glob( "../resources/*.html" ) :
-        process_file( infile, os.path.join( "resources", os.path.basename( infile ) ) )
\ No newline at end of file
+        process_file( infile, os.path.join( "resources", os.path.basename( infile ) ) )
diff --git a/encrypted-media/resources/clearkey-retrieve-destroy-persistent-license.html b/encrypted-media/resources/clearkey-retrieve-destroy-persistent-license.html
index b1bec54..3178c86 100644
--- a/encrypted-media/resources/clearkey-retrieve-destroy-persistent-license.html
+++ b/encrypted-media/resources/clearkey-retrieve-destroy-persistent-license.html
@@ -40,7 +40,7 @@
             config.messagehandler = (new MessageHandler( 'org.w3.clearkey')).messagehandler;
 
         function onComplete() {
-            window.opener.postMessage(assertions, '*');
+            window.opener.postMessage({ testResult: assertions }, '*');
         }
 
         function onFailure(error) {
diff --git a/encrypted-media/resources/clearkey-retrieve-persistent-license.html b/encrypted-media/resources/clearkey-retrieve-persistent-license.html
index 78ca4e1..0562322 100644
--- a/encrypted-media/resources/clearkey-retrieve-persistent-license.html
+++ b/encrypted-media/resources/clearkey-retrieve-persistent-license.html
@@ -35,7 +35,7 @@
         config.video = document.getElementById('videoelement');
 
         function onComplete() {
-            window.opener.postMessage(assertions, '*');
+            window.opener.postMessage({ testResult: assertions }, '*');
         }
 
         function onFailure(error) {
@@ -54,9 +54,10 @@
         .then(function(access) {
             return access.createMediaKeys();
         }).then(function(mediaKeys) {
-            config.video.setMediaKeys(mediaKeys);
+            return config.video.setMediaKeys(mediaKeys);
+        }).then(function() {
             config.video.addEventListener('timeupdate', onTimeupdate, true);
-            _mediaKeySession = mediaKeys.createSession( 'persistent-license' );
+            _mediaKeySession = config.video.mediaKeys.createSession( 'persistent-license' );
             _mediaKeySession.closed.then(onComplete);
             return _mediaKeySession.load(event.data.sessionId);
         }).then(function( success ) {
diff --git a/encrypted-media/resources/drm-retrieve-destroy-persistent-license.html b/encrypted-media/resources/drm-retrieve-destroy-persistent-license.html
index 39798fe..0803eb1 100644
--- a/encrypted-media/resources/drm-retrieve-destroy-persistent-license.html
+++ b/encrypted-media/resources/drm-retrieve-destroy-persistent-license.html
@@ -40,7 +40,7 @@
         config.messagehandler = (new MessageHandler(config.keysystem, config.content, 'persistent-license')).messagehandler;
 
         function onComplete() {
-            window.opener.postMessage(assertions, '*');
+            window.opener.postMessage({ testResult: assertions }, '*');
         }
 
         function onFailure(error) {
diff --git a/encrypted-media/resources/drm-retrieve-persistent-license.html b/encrypted-media/resources/drm-retrieve-persistent-license.html
index 3b7da8e..b6a0ae2 100644
--- a/encrypted-media/resources/drm-retrieve-persistent-license.html
+++ b/encrypted-media/resources/drm-retrieve-persistent-license.html
@@ -33,7 +33,7 @@
         config.video = document.getElementById('videoelement');
 
         function onComplete() {
-            window.opener.postMessage(assertions, '*');
+            window.opener.postMessage({ testResult: assertions }, '*');
         }
 
         function onFailure(error) {
@@ -58,9 +58,10 @@
         .then(function(access) {
             return access.createMediaKeys();
         }).then(function(mediaKeys) {
-            config.video.setMediaKeys(mediaKeys);
+            return config.video.setMediaKeys(mediaKeys);
+        }).then(function() {
             config.video.addEventListener('timeupdate', onTimeupdate);
-            _mediaKeySession = mediaKeys.createSession( 'persistent-license' );
+            _mediaKeySession = config.video.mediaKeys.createSession( 'persistent-license' );
             return _mediaKeySession.load(event.data.sessionId);
         }).then(function( success ) {
             if ( !success ) throw new DOMException( 'Could not load session' );
diff --git a/encrypted-media/resources/drm-retrieve-persistent-usage-record.html b/encrypted-media/resources/drm-retrieve-persistent-usage-record.html
index 935f777..a98c438 100644
--- a/encrypted-media/resources/drm-retrieve-persistent-usage-record.html
+++ b/encrypted-media/resources/drm-retrieve-persistent-usage-record.html
@@ -33,7 +33,7 @@
 
         function onFailure(error) {
             assertions.push( { actual: false, expected: true, message: error } );
-            window.opener.postMessage(assertions, '*');
+            window.opener.postMessage({ testResult: assertions }, '*');
         }
 
         function onMessage( event )
@@ -56,13 +56,13 @@
             _mediaKeySession = _mediaKeys.createSession( 'persistent-usage-record' );
             _mediaKeySession.addEventListener( 'message', onMessage );
             _mediaKeySession.closed.then( function() {
-                window.opener.postMessage(assertions, '*');
+                window.opener.postMessage({ testResult: assertions }, '*');
             });
             return _mediaKeySession.load( event.data.sessionId );
         }).then(function( success ) {
             if ( !success ) {
                 assertions.push( { actual: success, expected: true, message: "Error loading session" } );
-                window.opener.postMessage(assertions, '*');
+                window.opener.postMessage({ testResult: assertions }, '*');
             }
         }).catch( onFailure );
     });
diff --git a/encrypted-media/resources/retrieve-persistent-usage-record.html b/encrypted-media/resources/retrieve-persistent-usage-record.html
index 339e3c3..3a4262b 100644
--- a/encrypted-media/resources/retrieve-persistent-usage-record.html
+++ b/encrypted-media/resources/retrieve-persistent-usage-record.html
@@ -55,7 +55,7 @@
 
                     assertions.push( { actual: false, expected: true, message: error } );
 
-                    window.opener.postMessage(assertions, '*');
+                    window.opener.postMessage({ testResult: assertions }, '*');
                 });
             });
         }
@@ -71,7 +71,7 @@
             mediaKeySession.addEventListener( 'message', onMessage );
             mediaKeySession.closed.then( function() {
 
-                window.opener.postMessage(assertions, '*');
+                window.opener.postMessage({ testResult: assertions }, '*');
 
             });
 
@@ -81,7 +81,7 @@
 
             assertions.push( { actual: false, expected: true, message: error.toString() } );
 
-            window.opener.postMessage(assertions, '*');
+            window.opener.postMessage({ testResult: assertions }, '*');
 
         });
 
diff --git a/encrypted-media/scripts/playback-retrieve-persistent-license.js b/encrypted-media/scripts/playback-retrieve-persistent-license.js
index e8d9d02b..83cba34 100644
--- a/encrypted-media/scripts/playback-retrieve-persistent-license.js
+++ b/encrypted-media/scripts/playback-retrieve-persistent-license.js
@@ -74,12 +74,14 @@
 
             // Lisen for an event from the new window containing its test assertions
             window.addEventListener('message', test.step_func(function(messageEvent) {
-                messageEvent.data.forEach(test.step_func(function(assertion) {
-                    assert_equals(assertion.actual, assertion.expected, assertion.message);
-                }));
+                if (messageEvent.data.testResult) {
+                    messageEvent.data.testResult.forEach(test.step_func(function(assertion) {
+                        assert_equals(assertion.actual, assertion.expected, assertion.message);
+                    }));
 
-                win.close();
-                test.done();
+                    win.close();
+                    test.done();
+                }
             }));
 
             // Delete things which can't be cloned and posted over to the new window
@@ -96,7 +98,8 @@
             return access.createMediaKeys();
         }).then(function(mediaKeys) {
             _mediaKeys = mediaKeys;
-            _video.setMediaKeys( mediaKeys );
+            return _video.setMediaKeys( mediaKeys );
+        }).then(function() {
             _mediaKeySession = _mediaKeys.createSession('persistent-license');
             waitForEventAndRunStep('encrypted', _video, onEncrypted, test);
             waitForEventAndRunStep('playing', _video, onPlaying, test);
diff --git a/encrypted-media/scripts/playback-retrieve-persistent-usage-record.js b/encrypted-media/scripts/playback-retrieve-persistent-usage-record.js
index 9467fd5..a04f97d 100644
--- a/encrypted-media/scripts/playback-retrieve-persistent-usage-record.js
+++ b/encrypted-media/scripts/playback-retrieve-persistent-usage-record.js
@@ -45,7 +45,7 @@
             config.messagehandler( event.messageType, event.message ).then(function(response) {
                 return _mediaKeySession.update(response);
             }).then(function() {
-                _video.setMediaKeys(_mediaKeys);
+                return _video.setMediaKeys(_mediaKeys);
             }).catch(onFailure);
         }
 
@@ -70,13 +70,17 @@
             _video.setMediaKeys( null );
 
             var win = window.open(config.windowscript);
-            window.addEventListener('message', test.step_func(function(event) {
-                event.data.forEach(test.step_func(function(assertion) {
-                    assert_equals(assertion.actual, assertion.expected, assertion.message);
-                }));
+            assert_not_equals(win, null, "Popup windows not allowed?");
 
-                win.close();
-                test.done();
+            window.addEventListener('message', test.step_func(function(event) {
+                if (event.data.testResult) {
+                    event.data.testResult.forEach(test.step_func(function(assertion) {
+                        assert_equals(assertion.actual, assertion.expected, assertion.message);
+                    }));
+
+                    win.close();
+                    test.done();
+                }
             }));
 
             delete config.video;
diff --git a/encrypted-media/scripts/playback-temporary-playduration-keystatus.js b/encrypted-media/scripts/playback-temporary-playduration-keystatus.js
new file mode 100644
index 0000000..e21b8f8
--- /dev/null
+++ b/encrypted-media/scripts/playback-temporary-playduration-keystatus.js
@@ -0,0 +1,78 @@
+function runTest(config,qualifier) {
+
+    var testname = testnamePrefix(qualifier, config.keysystem)
+                                    + ', temporary, '
+                                    + /video\/([^;]*)/.exec(config.videoType)[1]
+                                    + ', playback with limited playduration, check keystatus, ' + config.testcase;
+
+    var configuration = {   initDataTypes: [ config.initDataType ],
+                            audioCapabilities: [ { contentType: config.audioType } ],
+                            videoCapabilities: [ { contentType: config.videoType } ],
+                            sessionTypes: [ 'temporary' ] };
+
+    async_test(function(test) {
+
+        var _video = config.video,
+            _mediaKeys,
+            _mediaKeySession,
+            _mediaSource;
+
+        function onFailure(error) {
+            forceTestFailureFromPromise(test, error);
+        }
+
+        function onEncrypted(event) {
+            assert_equals(event.target, _video);
+            assert_true(event instanceof window.MediaEncryptedEvent);
+            assert_equals(event.type, 'encrypted');
+
+            // Only create the session for the first encrypted event
+            if (_mediaKeySession !== undefined) return;
+
+            var initDataType = config.initData ? config.initDataType : event.initDataType;
+            var initData = config.initData || event.initData;
+
+            _mediaKeySession = _mediaKeys.createSession('temporary');
+            waitForEventAndRunStep('message', _mediaKeySession, onMessage, test);
+            _mediaKeySession.generateRequest( initDataType, initData ).catch(onFailure);
+        }
+
+        function onMessage(event) {
+            assert_equals(event.target, _mediaKeySession);
+            assert_true(event instanceof window.MediaKeyMessageEvent);
+            assert_equals(event.type, 'message');
+
+            assert_in_array(event.messageType, ['license-request', 'individualization-request']);
+
+            config.messagehandler(event.messageType, event.message, {playDuration: config.playduration}).then(function(response) {
+                return event.target.update(response);
+            }).catch(onFailure);
+        }
+
+        function onPlaying(event) {
+            waitForEventAndRunStep('keystatuseschange', _mediaKeySession, onKeystatuseschange, test);
+        }
+
+        function onKeystatuseschange(event) {
+            for (var status of event.target.keyStatuses.values()) {
+                assert_equals(status, "expired", "All keys should have keyStatus expired");
+            }
+            test.done();
+        }
+
+        navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) {
+            return access.createMediaKeys();
+        }).then(function(mediaKeys) {
+            _mediaKeys = mediaKeys;
+            return _video.setMediaKeys(_mediaKeys);
+        }).then(function(){
+            waitForEventAndRunStep('encrypted', _video, onEncrypted, test);
+            waitForEventAndRunStep('playing', _video, onPlaying, test);
+            return testmediasource(config);
+        }).then(function(source) {
+            _mediaSource = source;
+            _video.src = URL.createObjectURL(_mediaSource);
+            _video.play();
+        }).catch(onFailure);
+    }, testname);
+}
diff --git a/encrypted-media/scripts/playback-temporary-playduration.js b/encrypted-media/scripts/playback-temporary-playduration.js
new file mode 100644
index 0000000..6ce6157
--- /dev/null
+++ b/encrypted-media/scripts/playback-temporary-playduration.js
@@ -0,0 +1,80 @@
+function runTest(config,qualifier) {
+
+    var testname = testnamePrefix(qualifier, config.keysystem)
+                                    + ', temporary, '
+                                    + /video\/([^;]*)/.exec(config.videoType)[1]
+                                    + ', playback with limited playduration, ' + config.testcase;
+
+    var configuration = {   initDataTypes: [ config.initDataType ],
+                            audioCapabilities: [ { contentType: config.audioType } ],
+                            videoCapabilities: [ { contentType: config.videoType } ],
+                            sessionTypes: [ 'temporary' ] };
+
+    async_test(function(test) {
+
+        var _video = config.video,
+            _mediaKeys,
+            _mediaKeySession,
+            _mediaSource;
+
+        function onFailure(error) {
+            forceTestFailureFromPromise(test, error);
+        }
+
+        function onEncrypted(event) {
+            assert_equals(event.target, _video);
+            assert_true(event instanceof window.MediaEncryptedEvent);
+            assert_equals(event.type, 'encrypted');
+
+            // Only create the session for the first encrypted event
+            if (_mediaKeySession !== undefined) return;
+
+            var initDataType = config.initData ? config.initDataType : event.initDataType;
+            var initData = config.initData || event.initData;
+
+            _mediaKeySession = _mediaKeys.createSession('temporary');
+            waitForEventAndRunStep('message', _mediaKeySession, onMessage, test);
+            _mediaKeySession.generateRequest( initDataType, initData ).catch(onFailure);
+        }
+
+        function onMessage(event) {
+            assert_equals(event.target, _mediaKeySession);
+            assert_true(event instanceof window.MediaKeyMessageEvent);
+            assert_equals(event.type, 'message');
+
+            assert_in_array(event.messageType, ['license-request', 'individualization-request']);
+
+            config.messagehandler(event.messageType, event.message, {playDuration: config.playduration}).then(function(response) {
+                return event.target.update(response);
+            }).catch(onFailure);
+        }
+
+        function onPlaying(event) {
+            // Not using waitForEventAndRunStep() to avoid too many
+            // EVENT(onTimeUpdate) logs.
+            _video.addEventListener('timeupdate', test.step_func(onTimeupdate), true);
+            test.step_timeout(function(){
+                test.done();
+            },config.playduration * 2);
+        }
+
+        function onTimeupdate(event) {
+            assert_less_than(_video.currentTime * 1000, config.playduration, "Video should not play for more than playDuration from licence");
+        }
+
+        navigator.requestMediaKeySystemAccess(config.keysystem, [configuration]).then(function(access) {
+            return access.createMediaKeys();
+        }).then(function(mediaKeys) {
+            _mediaKeys = mediaKeys;
+            return _video.setMediaKeys(_mediaKeys);
+        }).then(function(){
+            waitForEventAndRunStep('encrypted', _video, onEncrypted, test);
+            waitForEventAndRunStep('playing', _video, onPlaying, test);
+            return testmediasource(config);
+        }).then(function(source) {
+            _mediaSource = source;
+            _video.src = URL.createObjectURL(_mediaSource);
+            _video.play();
+        }).catch(onFailure);
+    }, testname);
+}
diff --git a/encrypted-media/scripts/requestmediakeysystemaccess.js b/encrypted-media/scripts/requestmediakeysystemaccess.js
index a6b0a96..edfcbfc 100644
--- a/encrypted-media/scripts/requestmediakeysystemaccess.js
+++ b/encrypted-media/scripts/requestmediakeysystemaccess.js
@@ -91,10 +91,12 @@
         initDataTypes: [config.initDataType],
         audioCapabilities: [{contentType: config.audioType}],
         videoCapabilities: [{contentType: config.videoType}],
+        label: 'abcd',
     }], {
         initDataTypes: [config.initDataType],
         audioCapabilities: [{contentType: config.audioType}],
         videoCapabilities: [{contentType: config.videoType}],
+        label: 'abcd',
     }, 'Basic supported configuration');
 
     expect_config(config.keysystem, [{
diff --git a/encrypted-media/util/drm-messagehandler.js b/encrypted-media/util/drm-messagehandler.js
index 28fb90f..5c5577c 100644
--- a/encrypted-media/util/drm-messagehandler.js
+++ b/encrypted-media/util/drm-messagehandler.js
@@ -107,11 +107,14 @@
                     outputProtection: {digital : false, analogue: false, enforce: false},
                     storeLicense: (sessionType === 'persistent-license')};
 
-            if (!params || params.expiration === undefined) {
+            if (!params || (params.expiration === undefined && params.playDuration === undefined)) {
                 crt.profile = {purchase: {}};
             } else {
-                crt.profile = {rental: {absoluteExpiration: (new Date(params.expiration)).toISOString(),
-                                        playDuration: 3600000 } };
+                var expiration = params.expiration || (Date.now().valueOf() + 3600000),
+                    playDuration = params.playDuration || 3600000;
+
+                crt.profile = {rental: {absoluteExpiration: (new Date(expiration)).toISOString(),
+                                        playDuration: playDuration } };
             }
 
             if (content.variantId !== undefined) {
diff --git a/encrypted-media/util/utils.js b/encrypted-media/util/utils.js
index 978cc54..41bd71f 100644
--- a/encrypted-media/util/utils.js
+++ b/encrypted-media/util/utils.js
@@ -173,32 +173,34 @@
     return '0x' + array.map( function( x ) { return x < 16 ? '0'+x.toString(16) : x.toString(16); } ).join('');
 }
 
-function dumpKeyStatuses(keyStatuses)
+function dumpKeyStatuses(keyStatuses,short)
 {
     var userAgent = navigator.userAgent.toLowerCase();
     if (userAgent.indexOf('edge') === -1) {
-        consoleWrite("for (var entry of keyStatuses)");
+        if (!short) { consoleWrite("for (var entry of keyStatuses)"); }
         for (var entry of keyStatuses) {
             consoleWrite(arrayBufferAsString(entry[0]) + ": " + entry[1]);
         }
-        consoleWrite("for (var keyId of keyStatuses.keys())");
-        for (var keyId of keyStatuses.keys()) {
-            consoleWrite(arrayBufferAsString(keyId));
+        if (!short) {
+            consoleWrite("for (var keyId of keyStatuses.keys())");
+            for (var keyId of keyStatuses.keys()) {
+                consoleWrite(arrayBufferAsString(keyId));
+            }
+            consoleWrite("for (var status of keyStatuses.values())");
+            for (var status of keyStatuses.values()) {
+                consoleWrite(status);
+            }
+            consoleWrite("for (var entry of keyStatuses.entries())");
+            for (var entry of keyStatuses.entries()) {
+                consoleWrite(arrayBufferAsString(entry[0]) + ": " + entry[1]);
+            }
+            consoleWrite("keyStatuses.forEach()");
+            keyStatuses.forEach(function(status, keyId) {
+                consoleWrite(arrayBufferAsString(keyId) + ": " + status);
+            });
         }
-        consoleWrite("for (var status of keyStatuses.values())");
-        for (var status of keyStatuses.values()) {
-            consoleWrite(status);
-        }
-        consoleWrite("for (var entry of keyStatuses.entries())");
-        for (var entry of keyStatuses.entries()) {
-            consoleWrite(arrayBufferAsString(entry[0]) + ": " + entry[1]);
-        }
-        consoleWrite("keyStatuses.forEach()");
-        keyStatuses.forEach(function(status, keyId) {
-            consoleWrite(arrayBufferAsString(keyId) + ": " + status);
-        });
     } else {
-        consoleWrite("keyStatuses.forEach()");
+        if (!short) { consoleWrite("keyStatuses.forEach()"); }
         keyStatuses.forEach(function(keyId, status) {
             consoleWrite(arrayBufferAsString(keyId) + ": " + status);
         });
diff --git a/entries-api/README.md b/entries-api/README.md
new file mode 100644
index 0000000..2f54d8b
--- /dev/null
+++ b/entries-api/README.md
@@ -0,0 +1,9 @@
+Tests for the [Files and Directory Entries API](https://github.com/wicg/entries-api)
+
+* [Explainer](https://github.com/WICG/entries-api/blob/gh-pages/EXPLAINER.md)
+* [Draft Specification](https://wicg.github.io/entries-api/)
+
+Unfortunately, most of the tests are **manual** and require drag-and-drop of test
+data files, which can be found in the `entries-api/support` directory. The tests
+can be run via [w3c-test.org](http://w3c-test.org/entries-api/), but a local clone
+of the repo is required for access to the test data files.
diff --git a/entries-api/interfaces-manual.html b/entries-api/interfaces-manual.html
index 8b48416..72bdf67 100644
--- a/entries-api/interfaces-manual.html
+++ b/entries-api/interfaces-manual.html
@@ -14,7 +14,7 @@
   assert_true(entry.isDirectory);
   Promise.all([
     getEntriesAsPromise(entry),
-    fetch('interfaces.idl').then(r => r.text())
+    fetch('/interfaces/entries-api.idl').then(r => r.text())
   ]).then(t.step_func(([entries, idls]) => {
     window.samples = {
       item: item,
diff --git a/entries-api/interfaces.html b/entries-api/interfaces.html
index 7714155..091549e 100644
--- a/entries-api/interfaces.html
+++ b/entries-api/interfaces.html
@@ -10,7 +10,7 @@
 'use strict';
 
 promise_test(t => {
-  return fetch('interfaces.idl')
+  return fetch('/interfaces/entries-api.idl')
     .then(r => r.text())
     .then(idls => {
 
diff --git a/entries-api/interfaces.idl b/entries-api/interfaces.idl
deleted file mode 100644
index bef08ab..0000000
--- a/entries-api/interfaces.idl
+++ /dev/null
@@ -1,70 +0,0 @@
-
-partial interface File {
-    readonly attribute USVString webkitRelativePath;
-};
-
-partial interface HTMLInputElement {
-    attribute boolean webkitdirectory;
-    readonly attribute FrozenArray<FileSystemEntry> webkitEntries;
-};
-
-partial interface DataTransferItem {
-    FileSystemEntry? webkitGetAsEntry();
-};
-
-callback interface ErrorCallback {
-    void handleEvent(DOMException err);
-};
-
-interface FileSystemEntry {
-    readonly attribute boolean isFile;
-    readonly attribute boolean isDirectory;
-    readonly attribute USVString name;
-    readonly attribute USVString fullPath;
-    readonly attribute FileSystem filesystem;
-
-    void getParent(optional FileSystemEntryCallback successCallback,
-                   optional ErrorCallback errorCallback);
-};
-
-interface FileSystemDirectoryEntry : FileSystemEntry {
-    FileSystemDirectoryReader createReader();
-    void getFile(optional USVString? path,
-                 optional FileSystemFlags options,
-                 optional FileSystemEntryCallback successCallback,
-                 optional ErrorCallback errorCallback);
-    void getDirectory(optional USVString? path,
-                      optional FileSystemFlags options,
-                      optional FileSystemEntryCallback successCallback,
-                      optional ErrorCallback errorCallback);
-};
-
-dictionary FileSystemFlags {
-    boolean create = false;
-    boolean exclusive = false;
-};
-
-callback interface FileSystemEntryCallback {
-    void handleEvent(FileSystemEntry entry);
-};
-
-interface FileSystemDirectoryReader {
-    void readEntries(FileSystemEntriesCallback successCallback,
-                     optional ErrorCallback errorCallback);
-};
-callback interface FileSystemEntriesCallback {
-    void handleEvent(sequence<FileSystemEntry> entries);
-};
-
-interface FileSystemFileEntry : FileSystemEntry {
-    void file(FileCallback successCallback,
-              optional ErrorCallback errorCallback);
-};
-callback interface FileCallback {
-    void handleEvent(File file);
-};
-
-interface FileSystem {
-    readonly attribute USVString name;
-    readonly attribute FileSystemDirectoryEntry root;
-};
diff --git a/eventsource/dedicated-worker/eventsource-close2.htm b/eventsource/dedicated-worker/eventsource-close2.htm
new file mode 100644
index 0000000..b1b74e8
--- /dev/null
+++ b/eventsource/dedicated-worker/eventsource-close2.htm
@@ -0,0 +1,29 @@
+<!--
+self.close()
+var source = new EventSource("../resources/message.py")
+postMessage(source.readyState)
+/*-->
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>dedicated worker - EventSource created after: worker.close()</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      var test = async_test();
+      test.step(function() {
+        var worker = new Worker('#')
+        worker.onmessage = function(e) {
+          test.step(function() {
+            assert_equals(e.data, EventSource.CONNECTING, 'this.readyState')
+          })
+          test.done()
+        }
+      })
+    </script>
+  </body>
+</html>
+<!--*/ //-->
diff --git a/feature-policy/README.md b/feature-policy/README.md
index a5276f0..48cb95e 100644
--- a/feature-policy/README.md
+++ b/feature-policy/README.md
@@ -28,7 +28,7 @@
 
 * Define the header policy in `<feature-name>-<enabled | disabled | enabled-on-self-origin>-by-feature-policy.https.sub.html.headers`. Example:
 
-    Feature-Policy: {"feature-name": ["*"]}
+    Feature-Policy: feature-name *
 
 
 * In `<feature-name>-<enabled | disabled | enabled-on-self-origin>-by-feature-policy.https.sub.html`:
diff --git a/feature-policy/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html b/feature-policy/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000..6f11f89
--- /dev/null
+++ b/feature-policy/autoplay-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/common/media.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/autoplay.js></script>
+  <script>
+  'use strict';
+  const relative_path = '/feature-policy/resources/feature-policy-autoplay.html';
+  const base_src = '/feature-policy/resources/redirect-on-load.html#';
+  const same_origin_src = base_src + relative_path;
+  const cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    relative_path;
+  const header = 'Feature-Policy allow="autoplay"';
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability(
+          'autoplay', t, same_origin_src,
+          expect_feature_available_default, 'autoplay');
+    });
+  }, header + ' allows same-origin navigation in an iframe.');
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability(
+          'autoplay', t, cross_origin_src,
+          expect_feature_unavailable_default, 'autoplay');
+    });
+  }, header + ' disallows cross-origin navigation in an iframe.');
+  </script>
+</body>
diff --git a/feature-policy/autoplay-allowed-by-feature-policy-attribute.https.sub.html b/feature-policy/autoplay-allowed-by-feature-policy-attribute.https.sub.html
new file mode 100644
index 0000000..59b33d7
--- /dev/null
+++ b/feature-policy/autoplay-allowed-by-feature-policy-attribute.https.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/common/media.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/autoplay.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const feature_name = 'Feature policy "autoplay"';
+  const header = 'allow="autoplay" attribute';
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability(
+          'autoplay', t, same_origin_src,
+          expect_feature_available_default, 'autoplay');
+    });
+  }, feature_name + ' can be enabled in same-origin iframe using ' + header);
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability(
+          'autoplay', t, cross_origin_src,
+          expect_feature_available_default, 'autoplay');
+    });
+  }, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+  </script>
+</body>
diff --git a/feature-policy/autoplay-allowed-by-feature-policy.https.sub.html b/feature-policy/autoplay-allowed-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..63479c0
--- /dev/null
+++ b/feature-policy/autoplay-allowed-by-feature-policy.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/common/media.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/autoplay.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const header = 'Feature-Policy header: autoplay *';
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      isAutoplayAllowed().then(t.step_func_done((result) => {
+        assert_true(result);
+      }));
+    });
+  }, header + ' allows the top-level document.');
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability('autoplay', t, same_origin_src,
+          expect_feature_available_default);
+    });
+  }, header + ' allows same-origin iframes.');
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability('autoplay', t, cross_origin_src,
+          expect_feature_available_default);
+    });
+  }, header + ' allows cross-origin iframes.');
+  </script>
+</body>
diff --git a/feature-policy/autoplay-allowed-by-feature-policy.https.sub.html.headers b/feature-policy/autoplay-allowed-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..08461fa
--- /dev/null
+++ b/feature-policy/autoplay-allowed-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: autoplay *
diff --git a/feature-policy/autoplay-default-feature-policy.https.sub.html b/feature-policy/autoplay-default-feature-policy.https.sub.html
new file mode 100644
index 0000000..763073e
--- /dev/null
+++ b/feature-policy/autoplay-default-feature-policy.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/common/media.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/autoplay.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const header = 'Default "autoplay" feature policy ["self"]';
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      isAutoplayAllowed().then(t.step_func_done((result) => {
+        assert_true(result);
+      }));
+    });
+  }, header + ' allows the top-level document.');
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability('autoplay', t, same_origin_src,
+          expect_feature_available_default);
+    });
+  }, header + ' allows same-origin iframes.');
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability('autoplay', t, cross_origin_src,
+          expect_feature_unavailable_default,);
+    });
+  }, header + ' disallows cross-origin iframes.');
+  </script>
+</body>
diff --git a/feature-policy/autoplay-disabled-by-feature-policy.https.sub.html b/feature-policy/autoplay-disabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..3dd3afb
--- /dev/null
+++ b/feature-policy/autoplay-disabled-by-feature-policy.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/common/media.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/autoplay.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-autoplay.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const header = 'Feature-Policy header: autoplay "none"';
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      isAutoplayAllowed().then(t.step_func_done((result) => {
+        assert_true(result);
+      }));
+    });
+  }, header + ' has no effect on the top level document.');
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability('autoplay', t, same_origin_src,
+          expect_feature_unavailable_default);
+    });
+  }, header + ' disallows same-origin iframes.');
+
+  async_test(t => {
+    simulateGesture(t, () => {
+      test_feature_availability('autoplay', t, cross_origin_src,
+          expect_feature_unavailable_default,);
+    });
+  }, header + ' disallows cross-origin iframes.');
+  </script>
+</body>
diff --git a/feature-policy/autoplay-disabled-by-feature-policy.https.sub.html.headers b/feature-policy/autoplay-disabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..69ce436
--- /dev/null
+++ b/feature-policy/autoplay-disabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: autoplay 'none'
diff --git a/feature-policy/experimental-features/resources/feature-policy-image.html b/feature-policy/experimental-features/resources/feature-policy-image.html
new file mode 100644
index 0000000..a0008ee
--- /dev/null
+++ b/feature-policy/experimental-features/resources/feature-policy-image.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<script>
+window.addEventListener('load', function() {
+  var img = document.createElement('IMG');
+  img.setAttribute("src", "/feature-policy/experimental-features/resources/image.jpg");
+  img.addEventListener('load', function() {
+    parent.postMessage({width: img.width, height: img.height}, '*');
+  });
+  document.body.appendChild(img);
+});
+</script>
+</body>
diff --git a/feature-policy/experimental-features/resources/image.jpg b/feature-policy/experimental-features/resources/image.jpg
new file mode 100644
index 0000000..430f5c5
--- /dev/null
+++ b/feature-policy/experimental-features/resources/image.jpg
Binary files differ
diff --git a/feature-policy/experimental-features/resources/image.png b/feature-policy/experimental-features/resources/image.png
new file mode 100644
index 0000000..556fa72
--- /dev/null
+++ b/feature-policy/experimental-features/resources/image.png
Binary files differ
diff --git a/feature-policy/experimental-features/resources/image.svg b/feature-policy/experimental-features/resources/image.svg
new file mode 100644
index 0000000..73ca820
--- /dev/null
+++ b/feature-policy/experimental-features/resources/image.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" standalone="yes"?>
+
+<svg version="1.1" viewBox="0.0 0.0 960.0 720.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M267.88715 112.77165L267.88715 112.77165C267.88715 93.211205 283.74402 77.35433 303.30447 77.35433L303.30447 77.35433C312.69772 77.35433 321.70627 81.085785 328.3483 87.72782C334.99033 94.36986 338.72177 103.378395 338.72177 112.77165L338.72177 112.77165C338.72177 132.3321 322.8649 148.18898 303.30447 148.18898L303.30447 148.18898C283.74402 148.18898 267.88715 132.3321 267.88715 112.77165Z" fill-rule="nonzero" fill="#cfe2f3" stroke="#073763" stroke-width="2.0" stroke-linejoin="round" stroke-linecap="butt"></path><path d="M294.15125 114.47782L295.1825 114.61845Q295.01062 115.68095 294.3075 116.29032Q293.60437 116.8997 292.58875 116.8997Q291.3075 116.8997 290.53406 116.06376Q289.76062 115.22782 289.76062 113.66532Q289.76062 112.66532 290.09656 111.90751Q290.4325 111.1497 291.11218 110.7747Q291.79187 110.3997 292.60437 110.3997Q293.60437 110.3997 294.2528 110.90751Q294.90125 111.41532 295.08875 112.36845L294.0575 112.5247Q293.91687 111.8997 293.54187 111.579384Q293.16687 111.25907 292.63562 111.25907Q291.83875 111.25907 291.33875 111.829384Q290.83875 112.3997 290.83875 113.63407Q290.83875 114.8997 291.32312 115.4622Q291.8075 116.0247 292.57312 116.0247Q293.19812 116.0247 293.61218 115.6497Q294.02625 115.2747 294.15125 114.47782ZM296.08875 109.38407L296.08875 108.16532L297.15125 108.16532L297.15125 109.38407L296.08875 109.38407ZM296.08875 116.75907L296.08875 110.54032L297.15125 110.54032L297.15125 116.75907L296.08875 116.75907ZM298.745 116.75907L298.745 110.54032L299.69812 110.54032L299.69812 111.47782Q300.0575 110.82157 300.36218 110.610634Q300.66687 110.3997 301.04187 110.3997Q301.57312 110.3997 302.12 110.72782L301.76062 111.7122Q301.37 111.47782 300.995 111.47782Q300.63562 111.47782 300.36218 111.68876Q300.08875 111.8997 299.97937 112.2747Q299.79187 112.8372 299.79187 113.49345L299.79187 116.75907L298.745 116.75907ZM306.82312 114.47782L307.85437 114.61845Q307.6825 115.68095 306.97937 116.29032Q306.27625 116.8997 305.26062 116.8997Q303.97937 116.8997 303.20593 116.06376Q302.4325 115.22782 302.4325 113.66532Q302.4325 112.66532 302.76843 111.90751Q303.10437 111.1497 303.78406 110.7747Q304.46375 110.3997 305.27625 110.3997Q306.27625 110.3997 306.92468 110.90751Q307.57312 111.41532 307.76062 112.36845L306.72937 112.5247Q306.58875 111.8997 306.21375 111.579384Q305.83875 111.25907 305.3075 111.25907Q304.51062 111.25907 304.01062 111.829384Q303.51062 112.3997 303.51062 113.63407Q303.51062 114.8997 303.995 115.4622Q304.47937 116.0247 305.245 116.0247Q305.87 116.0247 306.28406 115.6497Q306.69812 115.2747 306.82312 114.47782ZM308.72937 116.75907L308.72937 108.16532L309.79187 108.16532L309.79187 116.75907L308.72937 116.75907ZM315.66687 114.75907L316.76062 114.88407Q316.51062 115.8372 315.8075 116.36845Q315.10437 116.8997 314.02625 116.8997Q312.66687 116.8997 311.86218 116.05595Q311.0575 115.2122 311.0575 113.69657Q311.0575 112.13407 311.87 111.266884Q312.6825 110.3997 313.96375 110.3997Q315.21375 110.3997 316.0028 111.24345Q316.79187 112.0872 316.79187 113.63407Q316.79187 113.72782 316.79187 113.91532L312.15125 113.91532Q312.21375 114.94657 312.72937 115.485634Q313.245 116.0247 314.02625 116.0247Q314.60437 116.0247 315.01843 115.72001Q315.4325 115.41532 315.66687 114.75907ZM312.21375 113.05595L315.6825 113.05595Q315.62 112.25907 315.29187 111.86845Q314.77625 111.25907 313.97937 111.25907Q313.245 111.25907 312.7528 111.74345Q312.26062 112.22782 312.21375 113.05595Z" fill-rule="nonzero" fill="#000000"></path></svg>
+
diff --git a/feature-policy/experimental-features/resources/vertical-scroll-scrollintoview.html b/feature-policy/experimental-features/resources/vertical-scroll-scrollintoview.html
new file mode 100644
index 0000000..7bed27c
--- /dev/null
+++ b/feature-policy/experimental-features/resources/vertical-scroll-scrollintoview.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<style>
+html, body, #container {
+  width: 100%;
+  height: 100%;
+}
+
+#spacer {
+  width: 200%;
+  height: 200%;
+}
+</style>
+<div id="container">
+  <div id="spacer"></div>
+  <button>Element To Scroll</button>
+</div>
+<script>
+  window.addEventListener('message', onMessageReceived);
+
+  function scrollingElementBounds() {
+    var rect = document.querySelector("button").getBoundingClientRect();
+    return {
+        x: rect.x, y: rect.y, width: rect.width, height: rect.height
+      };
+  }
+
+  function onMessageReceived(e) {
+    if (!e.data || !e.data.type)
+      return;
+    switch(e.data.type) {
+      case "scroll":
+        document.querySelector("button").scrollIntoView({behavior: "instant"});
+        ackMessage({bounds: scrollingElementBounds()}, e.source);
+      break;
+
+      case "scrolling-element-bounds":
+        ackMessage({bounds: scrollingElementBounds()}, e.source);
+      break;
+    }
+  }
+
+  function ackMessage(msg, source) {
+    source.postMessage(msg, "*");
+  }
+</script>
diff --git a/feature-policy/experimental-features/resources/vertical-scroll-touch-action.html b/feature-policy/experimental-features/resources/vertical-scroll-touch-action.html
new file mode 100644
index 0000000..51b715f
--- /dev/null
+++ b/feature-policy/experimental-features/resources/vertical-scroll-touch-action.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<style>
+  body, html {
+    height: 100%;
+    width: 100%;
+    overflow: hidden;
+  }
+  body {
+    touch-action: none;
+  }
+</style>
+<body>
+  <p>This page blocks all 'touch-action'.</p>
+</body>
diff --git a/feature-policy/experimental-features/resources/vertical-scroll.js b/feature-policy/experimental-features/resources/vertical-scroll.js
new file mode 100644
index 0000000..bbae658
--- /dev/null
+++ b/feature-policy/experimental-features/resources/vertical-scroll.js
@@ -0,0 +1,55 @@
+const url_base = "/feature-policy/experimental-features/resources/";
+window.messageResponseCallback = null;
+
+function rectMaxY(rect) {
+  return rect.height + rect.y;
+}
+
+function rectMaxX(rect) {
+  return rect.width + rect.x;
+}
+
+function isEmptyRect(rect) {
+  return !rect.width || !rect.height;
+}
+
+// Returns true if the given rectangles intersect.
+function rects_intersect(rect1, rect2) {
+  if (isEmptyRect(rect1) || isEmptyRect(rect2))
+    return false;
+  return rect1.x < rectMaxX(rect2) &&
+         rect2.x < rectMaxX(rect1) &&
+         rect1.y < rectMaxY(rect2) &&
+         rect2.y < rectMaxY(rect1);
+}
+
+// Returns a promise which is resolved when the <iframe> is navigated to |url|
+// and "load" handler has been called.
+function loadUrlInIframe(iframe, url) {
+  return new Promise((resolve) => {
+    iframe.addEventListener("load", resolve);
+    iframe.src = url;
+  });
+}
+
+// Posts |message| to |target| and resolves the promise with the response coming
+// back from |target|.
+function sendMessageAndGetResponse(target, message) {
+  return new Promise((resolve) => {
+    window.messageResponseCallback = resolve;
+    target.postMessage(message, "*");
+  });
+}
+
+function rectToString(rect) {
+  return `Location: (${rect.x}, ${rect.y}) Size: (${rect.width}, ${rect.height})`;
+}
+
+function onMessage(e) {
+  if (window.messageResponseCallback) {
+    window.messageResponseCallback(e.data);
+    window.messageResponseCallback = null;
+  }
+}
+
+window.addEventListener("message", onMessage);
diff --git a/feature-policy/experimental-features/resources/video.ogv b/feature-policy/experimental-features/resources/video.ogv
new file mode 100644
index 0000000..c9ee910
--- /dev/null
+++ b/feature-policy/experimental-features/resources/video.ogv
Binary files differ
diff --git a/feature-policy/experimental-features/unsized-image.tentative.https.sub.html b/feature-policy/experimental-features/unsized-image.tentative.https.sub.html
new file mode 100644
index 0000000..3731f0b
--- /dev/null
+++ b/feature-policy/experimental-features/unsized-image.tentative.https.sub.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe></iframe>
+<script>
+'use strict';
+const default_width = 300;
+const default_height = 150;
+const srcs = [
+  "/feature-policy/experimental-features/resources/image.jpg",
+  "/feature-policy/experimental-features/resources/image.png",
+  "/feature-policy/experimental-features/resources/image.svg"
+];
+const test_cases = [
+// Test when no size is specified, img/video is laid out by the default size.
+{expected_width: default_width, expected_height: default_height},
+// Test when only one dimension is specified, img/video uses the default length for
+// the other dimension.
+{attribute: "width", value: 500, expected_width: 500, expected_height: default_height},
+{attribute: "height", value: 800, expected_width: default_width, expected_height: 800},
+// Test when only one dimension is specified by CSS style, img/video uses the
+// default length for the other dimension.
+{attribute: "style", value: "width:500px;", expected_width: 500, expected_height: default_height},
+{attribute: "style", value: "height:800px;", expected_width: default_width, expected_height: 800},
+// Test when the size of the image is specified, img/video is laid out by the
+// specified size.
+{attribute: "width", value: 500, attribute1: "height", value1: 800, expected_width: 500, expected_height:800},
+{attribute: "style", value: "width:500px;height:800px;", expected_width: 500, expected_height:800}
+];
+
+for (var test of test_cases) {
+  for (var src of srcs) {
+    async_test(t=> {
+      var img = document.createElement('IMG');
+      var expected_width = test.expected_width;
+      var expected_height = test.expected_height;
+      img.setAttribute("src", src);
+      if (typeof test.attribute !== "undefined") {
+        img.setAttribute(test.attribute, test.value);
+      }
+      if (typeof test.attribute1 !== "undefined") {
+        img.setAttribute(test.attribute1, test.value1);
+      }
+      img.addEventListener('load', t.step_func(function() {
+        assert_equals(img.width, expected_width, 'width:');
+        assert_equals(img.height, expected_height, 'height:');
+        t.done();
+      }));
+      document.body.appendChild(img);
+    }, 'Test image with attribute ' + test.attribute + '=' + test.value +
+       ' and attribute ' + test.attribute1 + '=' + test.value1 + ' on src ' + src);
+  }
+  async_test(t=> {
+    var video = document.createElement('video');
+    video.addEventListener('canplaythrough', t.step_func(function() {
+      assert_equals(video.getBoundingClientRect().width, expected_width, 'width:');
+      assert_equals(video.getBoundingClientRect().height, expected_height, 'height:');
+      t.done();
+    }));
+    var expected_width = test.expected_width;
+    var expected_height = test.expected_height;
+    video.setAttribute("src", "/feature-policy/experimental-features/resources/video.ogv");
+    if (typeof test.attribute !== "undefined") {
+      video.setAttribute(test.attribute, test.value);
+    }
+    if (typeof test.attribute1 !== "undefined") {
+      video.setAttribute(test.attribute1, test.value1);
+    }
+    document.body.appendChild(video);
+  }, 'Test video with attribute ' + test.attribute + '=' + test.value +
+     ' and attribute ' + test.attribute1 + '=' + test.value1);
+}
+
+// Test unsized-image works in local and remote iframes.
+var iframe = document.querySelector('iframe');
+var iframe_srcs = [
+  "/feature-policy/experimental-features/resources/feature-policy-image.html",
+  "https://{{domains[www]}}:{{ports[https][0]}}/feature-policy/experimental-features/resources/feature-policy-image.html"];
+for (var src of iframe_srcs) {
+  promise_test(function() {
+    iframe.src = src;
+    return new Promise(function(resolve, reject) {
+      window.addEventListener('message', function(e) {
+        resolve(e.data);
+      });
+    }).then(function(data) {
+      assert_equals(data.width, default_width, 'width');
+      assert_equals(data.height, default_height, 'height');
+    });
+  }, 'Test image size is correctly rendered in iframe of src ' + src);
+}
+</script>
+</body>
diff --git a/feature-policy/experimental-features/unsized-image.tentative.https.sub.html.headers b/feature-policy/experimental-features/unsized-image.tentative.https.sub.html.headers
new file mode 100644
index 0000000..db2dcbc
--- /dev/null
+++ b/feature-policy/experimental-features/unsized-image.tentative.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: unsized-media 'none'
diff --git a/feature-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html b/feature-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html
new file mode 100644
index 0000000..e9c7cba
--- /dev/null
+++ b/feature-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+  height: 100%;
+  width: 100%;
+}
+
+iframe {
+  width: 95%;
+  height: 95%;
+  overflow: scroll;
+  margin-top: 200%;
+}
+
+.spacer {
+  width: 100%;
+  height: 100%;
+  margin-top: 100%;
+  margin-bottom: 100%;
+}
+
+</style>
+<p> An &lt;iframe&gt; further below which is not allowed to block scroll.</p>
+<div class="spacer"></div>
+<iframe></iframe>
+<p> Making sure there is room for vertical scroll </p>
+<script>
+  "use strict";
+
+  let url = url_base + "vertical-scroll-scrollintoview.html";
+  let iframeElement = document.querySelector("iframe");
+
+  function iframeBounds() {
+    return iframeElement.getBoundingClientRect();
+  }
+
+  // Enabled 'vertical-scroll': scrollIntoView should work in all frames.
+  promise_test(async() => {
+    window.scrollTo(0, 0);
+    await loadUrlInIframe(iframeElement, url);
+
+    await sendMessageAndGetResponse(
+      iframeElement.contentWindow,
+      {type: "scrolling-element-bounds"}).then((response) => {
+        let iframeBoundsAtOrigin = {
+          x: 0,
+          y: 0,
+          width: iframeBounds().width,
+          height: iframeBounds().height};
+          let scrollingElementBounds = response.bounds;
+          assert_false(
+            rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+            "Scrolling element should not be visible in <iframe>." +
+            `Scrolling element's bounds is: ${rectToString(response.bounds)}  ` +
+            "but <iframe>'s size is:" +
+            `${iframeBounds().width}x${iframeBounds().height}.`);
+      });
+
+    // Scroll the scrolling element inside the <iframe>.
+    await sendMessageAndGetResponse(iframeElement.contentWindow,
+                                   {type: "scroll"});
+    // The page should have scrolled vertically.
+      assert_greater_than(window.scrollY,
+                          0,
+                          "Main frame must scroll vertically.");
+    }, "Calling 'scrollIntoView()' inside a <iframe> will propagate up by" +
+       " default('vertical-scroll' enabled).");
+
+  // Disabled 'vertical-scroll': The scope of scrollIntoView is within the set
+  // of disabled frames (does not propagate to main frame).
+  promise_test(async() => {
+    window.scrollTo(0, 0);
+    iframeElement.allow = "vertical-scroll 'none';";
+    await loadUrlInIframe(iframeElement, url);
+
+    await sendMessageAndGetResponse(
+      iframeElement.contentWindow,
+      {type: "scrolling-element-bounds"}).then((response) => {
+      let iframeBoundsAtOrigin = {
+        x: 0,
+        y: 0,
+        width: iframeBounds().width,
+        height: iframeBounds().height};
+      let scrollingElementBounds = response.bounds;
+      assert_false(rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+            "Scrolling element should not be visible in <iframe>." +
+            `Scrolling element's bounds is: ${rectToString(response.bounds)}` +
+            "but <iframe>'s size is:" +
+            `${iframeBounds().width}x${iframeBounds().height}.`);
+      });
+
+    // Scroll scrolling element inside the <iframe>.
+    await sendMessageAndGetResponse(iframeElement.contentWindow,
+      {type: "scroll"}).then((response) => {
+      // Make sure the nested <iframe> is visible.
+      let scrollingElementBounds = response.bounds;
+      let iframeBoundsAtOrigin = {
+          x: 0,
+          y: 0,
+          width: iframeBounds().width,
+          height: iframeBounds().height};
+      // The scrolling element should be visible inside <iframe>.
+      assert_true(rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+          "Scrolling element should be visible in <iframe>." +
+          `Scrolling element's bounds is: ${rectToString(response.bounds)}` +
+          "but <iframe>'s size is:" +
+          `${iframeBounds().width}, ${iframeBounds().height}.`);
+      // The page however should not have scrolled.
+      assert_equals(window.scrollY, 0, "Main frame must not scroll vertically.");
+    });
+    }, "Calling 'scrollIntoView()' inside a <iframe> with" +
+       " 'vertical-scroll none;'will not propagate upwards.");
+</script>
diff --git a/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html b/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html
new file mode 100644
index 0000000..761c0d7
--- /dev/null
+++ b/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html
@@ -0,0 +1,102 @@
+<!doctype html>
+<title>vertical-scroll test for touch-action</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+  height: 100%;
+  width: 100%;
+}
+
+iframe {
+  width: 90%;
+  height: 90%;
+  margin: 0;
+  padding: 0;
+}
+
+.spacer {
+  width: 100%;
+  height: 100%;
+  margin: 100%;
+}
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+
+<script>
+  "use strict";
+
+  let url = url_base + "vertical-scroll-touch-action.html";
+  let iframeElement = document.querySelector("iframe");
+
+  // Wait for the helper scripts to load.
+  promise_test(async() => {
+    if (window.touch_scroll_api_ready)
+      return Promise.resolve();
+    await new Promise( (r) => {
+      window.resolve_on_touch_scroll_api_ready = r;
+    });
+
+  }, "Make sure input injection API is ready.");
+
+  // Sanity-check: Verify we can scroll using the test-API (empty <iframe>).
+  promise_test(async() => {
+    window.scrollTo(0, 0);
+
+    await inject_input("down");
+    assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+    await inject_input("right");
+    assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+  }, "Verify that the page normally scrolls.");
+
+  // Enable 'vertical-scroll': "touch-action" should be able to block scroll.
+  promise_test(async() => {
+    // Make sure <window> can scroll both towards right and bottom.
+    window.scrollTo(0, 0);
+
+    await loadUrlInIframe(iframeElement, url);
+
+    // Apply the scroll gesture.
+    await inject_input("down");
+
+    // The scroll should normally bubble and affect this page, but the <iframe>
+    // is allowed to block it.
+    assert_equals(window.scrollY,
+                  0,
+                  "Main frame must not scroll vertically");
+  }, "Vertical scrolling through 'touch-action' can normally be blocked if" +
+     " 'pan-y' is not present.");
+
+  // Disable 'vertical-scroll': "touch-action" should not be able to block
+  // scroll.
+  promise_test(async() => {
+    window.scrollTo(0, 0);
+
+    // Disallow vertical scroll and reload the <iframe>.
+    iframeElement.setAttribute("allow", "vertical-scroll 'none';");
+    await loadUrlInIframe(iframeElement, url);
+
+    // Apply the scroll gesture. Main frame should scroll vertically.
+    await inject_input("down");
+
+    assert_greater_than(window.scrollY,
+                        0,
+                        "Main frame must scroll vertically.");
+    window.scrollTo(0, 0);
+
+    await inject_input("right");
+    assert_equals(
+        window.scrollX,
+        0,
+        "'pan-x' can be blocked even when 'vertical-scroll' is disabled");
+  }, "Vertical scrolling (and only vertical scrolling) through 'touch-action'" +
+     " cannot be blocked by a disabled frame.");
+</script>
diff --git a/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html b/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000..defe06f
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen *; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var policies = [
+    {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true},
+    {allow: "'self'", sameOriginTestExpect: true, crossOriginTestExpect: false},
+    {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false},
+    {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: true, crossOriginTestExpect: true}];
+  var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+  var pipe_end = ';)';
+  var header_policies = ["*", "'self'", "'none'"];
+
+  // Test that frame.policy inherits from parent's header policy when allow
+  // attribute is not specified.
+  test(function() {
+    test_frame_policy('fullscreen', same_origin_src, true);
+  }, 'Test frame policy on same origin iframe inherit from header policy.');
+  test(function() {
+    test_frame_policy('fullscreen', cross_origin_src, true);
+  }, 'Test frame policy on cross origin iframe inherit from header policy.');
+
+  // Test frame policy with allow attribute set to be one of the policies above.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+  }
+
+  // Test that the header policy of the iframe document does not change the
+  // frame policy.
+  for (var i = 0; i < policies.length; i++) {
+    for (var j = 0; j < header_policies.length; j++) {
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          same_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].sameOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].crossOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+    }
+  }
+
+  // Test that the allow attribute overrides allowfullscreen.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+  }
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers b/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000..111121a
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen *;
diff --git a/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html b/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000..d71a09f
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen 'self'; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var policies = [
+    {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true},
+    {allow: "'self'", sameOriginTestExpect: true, crossOriginTestExpect: false},
+    {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false},
+    {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: true, crossOriginTestExpect: true}];
+  var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+  var pipe_end = ';)';
+  var header_policies = ["*", "'self'", "'none'"];
+
+  // Test that frame.policy inherits from parent's header policy when allow
+  // attribute is not specified.
+  test(function() {
+    test_frame_policy('fullscreen', same_origin_src, true);
+  }, 'Test frame policy on same origin iframe inherit from header policy.');
+  test(function() {
+    test_frame_policy('fullscreen', cross_origin_src, false);
+  }, 'Test frame policy on cross origin iframe inherit from header policy.');
+
+  // Test that frame policy can be used for sandboxed frames
+  test(function() {
+    test_frame_policy(
+      'fullscreen', same_origin_src, false, undefined, false, true);
+    }, 'Test frame policy on sandboxed iframe with no allow attribute.');
+  test(function() {
+    test_frame_policy(
+      'fullscreen', same_origin_src, true, 'fullscreen', false, true);
+    }, 'Test frame policy on sandboxed iframe with allow="fullscreen".');
+  test(function() {
+    test_frame_policy(
+      'fullscreen', same_origin_src, true, 'fullscreen \'src\'', false, true);
+    }, 'Test frame policy on sandboxed iframe with allow="fullscreen \'src\'".');
+  test(function() {
+    test_frame_policy(
+      'fullscreen', cross_origin_src, false, 'fullscreen ' + cross_origin, false, true);
+    }, 'Test frame policy on sandboxed iframe with allow="fullscreen ' + cross_origin + '".');
+
+  // Test frame policy with allow attribute set to be one of the policies above.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+  }
+
+  // Test that the header policy of the iframe document does not change the
+  // frame policy.
+  for (var i = 0; i < policies.length; i++) {
+    for (var j = 0; j < header_policies.length; j++) {
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          same_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].sameOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].crossOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+    }
+  }
+
+  // Test that the allow attribute overrides allowfullscreen.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+  }
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers b/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000..0cc259b
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self';
diff --git a/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html b/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html
new file mode 100644
index 0000000..f10c66f
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen 'self' cross_origin https://www.example.com; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var cross_origin1 = 'https://{{domains[www1]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var cross_origin_src1 = cross_origin1 + same_origin_src;
+  // Test feature policy with same_origin_src and cross_origin_src.
+  var policies = [
+    {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: true},
+    {allow: "'self'", sameOriginTestExpect: true, crossOriginTestExpect: false, crossOrigin1TestExpect: false},
+    {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false, crossOrigin1TestExpect: false},
+    {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: false}];
+  var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+  var pipe_end = ';)';
+  var header_policies = ["*", "'self'", "'none'"];
+
+  // Test that frame.policy inherits from parent's header policy when allow
+  // attribute is not specified.
+  test(function() {
+    test_frame_policy('fullscreen', same_origin_src, true);
+  }, 'Test frame policy on same origin iframe inherit from header policy.');
+  test(function() {
+    test_frame_policy('fullscreen', cross_origin_src, true);
+  }, 'Test frame policy on cross origin iframe inherit from header policy.');
+  test(function() {
+    test_frame_policy('fullscreen', cross_origin_src1, false);
+  }, 'Test frame policy on another cross origin iframe inherit from header policy.');
+
+  // Test frame policy with allow attribute set to be one of the policies above.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src1, policies[i].crossOrigin1TestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on another cross origin iframe with allow = "' + policies[i].allow + '".');
+  }
+
+  // Test that the header policy of the iframe document does not change the
+  // frame policy.
+  for (var i = 0; i < policies.length; i++) {
+    for (var j = 0; j < header_policies.length; j++) {
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          same_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].sameOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].crossOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          cross_origin_src1 + pipe_front + header_policies[j] + pipe_end,
+          policies[i].crossOrigin1TestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on another cross origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+    }
+  }
+
+  // Test that the allow attribute overrides allowfullscreen.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src1, policies[i].crossOrigin1TestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on another cross origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+  }
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers b/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers
new file mode 100644
index 0000000..c2493a0
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self' https://{{domains[www]}}:{{ports[https][0]}} https://www.example.com;
diff --git a/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html b/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000..e1178e7
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen 'none'; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var policies = [
+    {allow: "*", sameOriginTestExpect: false, crossOriginTestExpect: false},
+    {allow: "'self'", sameOriginTestExpect: false, crossOriginTestExpect: false},
+    {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false},
+    {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: false, crossOriginTestExpect: false}];
+  var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+  var pipe_end = ';)';
+  var header_policies = ["*", "'self'", "'none'"];
+
+  // Test that frame.policy inherits from parent's header policy when allow
+  // attribute is not specified.
+  test(function() {
+    test_frame_policy('fullscreen', same_origin_src, false);
+  }, 'Test frame policy on same origin iframe inherit from header policy.');
+  test(function() {
+    test_frame_policy('fullscreen', cross_origin_src, false);
+  }, 'Test frame policy on cross origin iframe inherit from header policy.');
+
+  // Test frame policy with allow attribute set to be one of the policies above.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';');
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+  }
+
+  // Test that the header policy of the iframe document does not change the
+  // frame policy.
+  for (var i = 0; i < policies.length; i++) {
+    for (var j = 0; j < header_policies.length; j++) {
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          same_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].sameOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+      test(function() {
+        test_frame_policy(
+          'fullscreen',
+          cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+          policies[i].crossOriginTestExpect,
+          'fullscreen ' + policies[i].allow + ';');
+      }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+         '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+    }
+  }
+
+  // Test that the allow attribute overrides allowfullscreen.
+  for (var i = 0; i < policies.length; i++) {
+    test(function() {
+      test_frame_policy(
+        'fullscreen', same_origin_src, policies[i].sameOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+    test(function() {
+      test_frame_policy(
+        'fullscreen', cross_origin_src, policies[i].crossOriginTestExpect,
+        'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+    }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+       '" and allowfullscreen.');
+  }
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers b/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000..961d403
--- /dev/null
+++ b/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'none';
diff --git a/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html b/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000..3334b97
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen *; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var header_policy = 'Feature-Policy: fullscreen *';
+
+  // Test that fullscreen's allowlist is ['*']
+  test(function() {
+    assert_array_equals(
+      document.policy.getAllowlistForFeature('fullscreen'),
+      ['*']);
+  }, header_policy + ' -- test allowlist is ['*']');
+
+  // Test that fullscreen is allowed on all subframes.
+  test_allowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src);
+  test_allowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is allowed on cross-origin subframe',
+    'fullscreen',
+    cross_origin_src);
+
+  // Dynamically update sub frame's container policy
+  var allow = "fullscreen 'self';"
+  test_allowed_feature_for_subframe(
+    header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src,
+    allow);
+
+  test_disallowed_feature_for_subframe(
+    header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+    'fullscreen',
+    cross_origin_src,
+    allow);
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html.sub.headers b/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000..111121a
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen *;
diff --git a/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html b/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000..60e22f4
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen 'self'; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var header_policy = 'Feature-Policy: fullscreen \'self\'';
+
+  // Test that fullscreen's allowlist is ['same_origin']
+  test(function() {
+    assert_array_equals(
+      document.policy.getAllowlistForFeature('fullscreen'),
+      [same_origin]);
+  }, header_policy + ' -- test allowlist is [same_origin]');
+
+  // Test that fullscreen is only allowed on same-origin subframe.
+  test_allowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src);
+  test_disallowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is disallowed on cross-origin subframe',
+    'fullscreen',
+    cross_origin_src);
+
+  // Dynamically update sub frame's container policy
+  var allow = "fullscreen 'src';"
+  test_allowed_feature_for_subframe(
+    header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src,
+    allow);
+
+  test_allowed_feature_for_subframe(
+    header_policy + ', iframe.allow = ' + allow  + ' -- test fullscreen is allowed on cross-origin subframe',
+    'fullscreen',
+    same_origin_src,
+    allow);
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html.sub.headers b/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000..0cc259b
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self';
diff --git a/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html b/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html
new file mode 100644
index 0000000..cce2fdb
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen 'self' cross_origin https://www.example.com; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var header_policy = 'Feature-Policy: fullscreen \'self\' ' + cross_origin +
+  ' https://www.example.com;';
+
+  // Test that fullscreen's allowlist is [same_origin, cross_origin, 'https://www.example.com']
+  test(function() {
+    assert_array_equals(
+      document.policy.getAllowlistForFeature('fullscreen'),
+      [same_origin, cross_origin, 'https://www.example.com']);
+  }, header_policy + ' -- test allowlist is [same_origin, cross_origin, https://www.example.com]');
+
+  // Test that fullscreen is allowed on same_origin, some cross_origin subframes.
+  test_allowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src);
+  test_allowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is allowed on cross-origin ' + cross_origin_src + ' subframe',
+    'fullscreen',
+    cross_origin_src);
+  var cross_origin_src1 = 'https://{{domains[www1]}}:{{ports[https][0]}}' + same_origin_src;
+  test_disallowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is disallowed on cross-origin ' + cross_origin_src1 + ' subframe',
+    'fullscreen',
+    cross_origin_src1);
+
+  // dynamically update sub frame's container policy
+  var allow = "fullscreen 'none';"
+  test_disallowed_feature_for_subframe(
+    header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src,
+    allow);
+
+  test_disallowed_feature_for_subframe(
+    header_policy + 'iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+    'fullscreen',
+    cross_origin_src,
+    allow);
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html.sub.headers b/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html.sub.headers
new file mode 100644
index 0000000..c2493a0
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self' https://{{domains[www]}}:{{ports[https][0]}} https://www.example.com;
diff --git a/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html b/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000..c025705
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <!-- Feature-Policy: fullscreen 'none'; -->
+  <script>
+  'use strict';
+  var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+  var cross_origin_src = cross_origin + same_origin_src;
+  var header_policy = 'Feature-Policy: fullscreen \'none\'';
+
+  // Test that fullscreen's allowlist is []
+  test(function() {
+    assert_array_equals(
+      document.policy.getAllowlistForFeature('fullscreen'),
+      []);
+  }, header_policy + ' -- test allowlist is []');
+
+  // Test that fullscreen is disallowed on all subframes.
+  test_disallowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is disallowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src);
+  test_disallowed_feature_for_subframe(
+    header_policy + ' -- test fullscreen is disallowed on cross-origin subframe',
+    'fullscreen',
+    cross_origin_src);
+
+  // Dynamically update sub frame's container policy
+  var allow = "fullscreen 'src';"
+  test_disallowed_feature_for_subframe(
+    header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on same-origin subframe',
+    'fullscreen',
+    same_origin_src,
+    allow);
+
+  test_disallowed_feature_for_subframe(
+    header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+    'fullscreen',
+    cross_origin_src,
+    allow);
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers b/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000..04d160a
--- /dev/null
+++ b/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Feature-Policy: fullscreen 'none';
+
diff --git a/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html b/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000..289fd50
--- /dev/null
+++ b/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script>
+  /*
+  fullscreen is allowed for all at the top-level document. It can be disabled by
+  subframes.
+  */
+  'use strict';
+  const same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  const cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  const same_origin_src = '/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html';
+  const cross_origin_src = cross_origin + same_origin_src;
+
+  /* ------------------------------------------
+     |  top-level document                    |
+     |  ------------------------------------  |
+     |  |  same-origin iframe              |  |
+     |  |  ------------------------------  |  |
+     |  |  |  local and remote iframes  |  |  |
+     |  |  ------------------------------  |  |
+     |  ------------------------------------  |
+     ------------------------------------------ */
+  test_subframe_header_policy('fullscreen', '*', same_origin_src,
+      {local_all: true, local_self: true, local_none: false,
+      remote_all: true, remote_self: true, remote_none: false},
+      'Test nested header policy with local iframe on policy "fullscreen *"');
+  test_subframe_header_policy('fullscreen', '\'self\'', same_origin_src,
+      {local_all: true, local_self: true, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with local iframe on policy "fullscreen \'self\'"');
+  test_subframe_header_policy('fullscreen', '\'none\'', same_origin_src,
+      {local_all: false, local_self: false, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with local iframe on policy "fullscreen \'none\'"');
+
+  /* -------------------------------------------
+     |  top-level document                     |
+     |  -------------------------------------  |
+     |  |  cross-origin iframe              |  |
+     |  |  -------------------------------  |  |
+     |  |  |  local and remote iframes   |  |  |
+     |  |  -------------------------------  |  |
+     |  -------------------------------------  |
+     ------------------------------------------- */
+  test_subframe_header_policy('fullscreen', '*', cross_origin_src,
+      {local_all: true, local_self: true, local_none: false,
+      remote_all: true, remote_self: true, remote_none: false},
+      'Test nested header policy with remote iframe on policy "fullscreen *"');
+  test_subframe_header_policy('fullscreen', '\'self\'', cross_origin_src,
+      {local_all: true, local_self: true, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with remote iframe on policy "fullscreen \'self\'"');
+  test_subframe_header_policy('fullscreen', '\'none\'', cross_origin_src,
+      {local_all: false, local_self: false, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with remote iframe on policy "fullscreen \'none\'"');
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers b/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000..111121a
--- /dev/null
+++ b/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen *;
diff --git a/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html b/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000..274b3eb
--- /dev/null
+++ b/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script>
+  /*
+  fullscreen is allowed for 'self' at the top-level document and through the
+  chain of same-origin iframes. It can be enabled by subframes, but otherwise
+  is disallowed everywhere else.
+  */
+  'use strict';
+  const same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  const cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  const same_origin_src = '/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html';
+  const cross_origin_src = cross_origin + same_origin_src;
+
+  /* ------------------------------------------
+     |  top-level document                    |
+     |  ------------------------------------  |
+     |  |  same-origin iframe              |  |
+     |  |  ------------------------------  |  |
+     |  |  |  local and remote iframes  |  |  |
+     |  |  ------------------------------  |  |
+     |  ------------------------------------  |
+     ------------------------------------------ */
+  test_subframe_header_policy('fullscreen', '*', same_origin_src,
+      {local_all: true, local_self: true, local_none: false,
+      remote_all: true, remote_self: true, remote_none: false},
+      'Test nested header policy with local iframe on policy "fullscreen *"');
+  test_subframe_header_policy('fullscreen', '\'self\'', same_origin_src,
+      {local_all: true, local_self: true, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with local iframe on policy "fullscreen \'self\'"');
+  test_subframe_header_policy('fullscreen', '\'none\'', same_origin_src,
+      {local_all: false, local_self: false, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with local iframe on policy "fullscreen \'none\'"');
+
+  /* -------------------------------------------
+     |  top-level document                     |
+     |  -------------------------------------  |
+     |  |  cross-origin iframe              |  |
+     |  |  -------------------------------  |  |
+     |  |  |  local and remote iframes   |  |  |
+     |  |  -------------------------------  |  |
+     |  -------------------------------------  |
+     ------------------------------------------- */
+  test_subframe_header_policy('fullscreen', '*', cross_origin_src,
+      {local_all: false, local_self: false, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with remote iframe on policy "fullscreen *"');
+  test_subframe_header_policy('fullscreen', '\'self\'', cross_origin_src,
+      {local_all: false, local_self: false, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with remote iframe on policy "fullscreen \'self\'"');
+  test_subframe_header_policy('fullscreen', '\'none\'', cross_origin_src,
+      {local_all: false, local_self: false, local_none: false,
+      remote_all: false, remote_self: false, remote_none: false},
+      'Test nested header policy with remote iframe on policy "fullscreen \'none\'"');
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers b/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000..0cc259b
--- /dev/null
+++ b/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self';
diff --git a/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html b/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000..f15b435
--- /dev/null
+++ b/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script>
+  /*
+  fullscreen is disabled at the top-level document, therefore disabled
+  everywhere throughout inheritance.
+  */
+  'use strict';
+  const same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+  const cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+  const same_origin_src = '/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html';
+  const cross_origin_src = cross_origin + same_origin_src;
+  const policies = ['*', '\'self\'', '\'none\''];
+
+  for (var i = 0; i < policies.length; i++) {
+    /* ------------------------------------------
+       |  top-level document                    |
+       |  ------------------------------------  |
+       |  |  same-origin iframe              |  |
+       |  |  ------------------------------  |  |
+       |  |  |  local and remote iframes  |  |  |
+       |  |  ------------------------------  |  |
+       |  ------------------------------------  |
+       ------------------------------------------ */
+    test_subframe_header_policy('fullscreen', policies[i], same_origin_src,
+        {local_all: false, local_self: false, local_none: false,
+        remote_all: false, remote_self: false, remote_none: false},
+        'Test nested header policy with local iframe on policy "fullscreen '
+          + policies[i] + '".');
+
+    /* -------------------------------------------
+       |  top-level document                     |
+       |  -------------------------------------  |
+       |  |  cross-origin iframe              |  |
+       |  |  -------------------------------  |  |
+       |  |  |  local and remote iframes   |  |  |
+       |  |  -------------------------------  |  |
+       |  -------------------------------------  |
+       ------------------------------------------- */
+    test_subframe_header_policy('fullscreen', policies[i], cross_origin_src,
+        {local_all: false, local_self: false, local_none: false,
+        remote_all: false, remote_self: false, remote_none: false},
+        'Test nested header policy with remote iframe on policy "fullscreen '
+          + policies[i] + '".');
+}
+  </script>
+</body>
diff --git a/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers b/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000..961d403
--- /dev/null
+++ b/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'none';
diff --git a/feature-policy/interfaces.any.js b/feature-policy/interfaces.any.js
new file mode 100644
index 0000000..baebc2f
--- /dev/null
+++ b/feature-policy/interfaces.any.js
@@ -0,0 +1,23 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+"use strict";
+
+// https://wicg.github.io/feature-policy/
+
+var idlArray = new IdlArray();
+
+function doTest(idl) {
+  idlArray.add_untested_idls("interface HTMLIFrameElement {};");
+  idlArray.add_idls(idl);
+  idlArray.add_objects({
+    HTMLIframeElement: ['document.createElement("iframe")'],
+  })
+  idlArray.test();
+  done();
+}
+
+promise_test(function () {
+  return fetch("/interfaces/feature-policy.idl").then(response => response.text())
+    .then(doTest);
+}, "Test interfaces");
diff --git a/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html b/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
index 9a79217..daa2aa1 100644
--- a/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
+++ b/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -16,24 +16,24 @@
     test_feature_availability(
         'PaymentRequest()', t, same_origin_src,
         expect_feature_available_default, 'payment');
-  }, header + ' allows same-origin relocation.');
+  }, header + ' allows same-origin navigation in an iframe.');
 
   async_test(t => {
     test_feature_availability(
         'PaymentRequest()', t, cross_origin_src,
         expect_feature_unavailable_default, 'payment');
-  }, header + ' disallows cross-origin relocation.');
+  }, header + ' disallows cross-origin navigation in an iframe.');
 
   async_test(t => {
     test_feature_availability(
         'PaymentRequest()', t, same_origin_src,
         expect_feature_available_default, 'payment', 'allowpaymentrequest');
-  }, header + ' allowpaymentrequest=true allows same-origin relocation.');
+  }, header + ' allowpaymentrequest=true allows same-origin navigation in an iframe.');
 
   async_test(t => {
     test_feature_availability(
         'PaymentRequest()', t, cross_origin_src,
         expect_feature_unavailable_default, 'payment', 'allowpaymentrequest');
-  }, header + ' allowpaymentrequest=true disallows cross-origin relocation.');
+  }, header + ' allowpaymentrequest=true disallows cross-origin navigation in an iframe.');
   </script>
 </body>
diff --git a/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html b/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000..c591be4
--- /dev/null
+++ b/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/picture-in-picture.js></script>
+  <script>
+  'use strict';
+  const relative_path = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+  const base_src = '/feature-policy/resources/redirect-on-load.html#';
+  const same_origin_src = base_src + relative_path;
+  const cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    relative_path;
+  const header = 'Feature-Policy allow="picture-in-picture"';
+
+  async_test(t => {
+    test_feature_availability(
+        'picture-in-picture', t, same_origin_src,
+        expect_feature_available_default, 'picture-in-picture');
+  }, header + ' allows same-origin navigation in an iframe.');
+
+  async_test(t => {
+    test_feature_availability(
+        'picture-in-picture', t, cross_origin_src,
+        expect_feature_unavailable_default, 'picture-in-picture');
+  }, header + ' disallows cross-origin navigation in an iframe.');
+  </script>
+</body>
diff --git a/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html b/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html
new file mode 100644
index 0000000..8a0a437
--- /dev/null
+++ b/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/picture-in-picture.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const feature_name = 'Feature policy "picture-in-picture"';
+  const header = 'allow="picture-in-picture" attribute';
+
+  async_test(t => {
+    test_feature_availability(
+        'picture-in-picture', t, same_origin_src,
+        expect_feature_available_default, 'picture-in-picture');
+  }, feature_name + ' can be enabled in same-origin iframe using ' + header);
+
+  async_test(t => {
+    test_feature_availability(
+        'picture-in-picture', t, cross_origin_src,
+        expect_feature_available_default, 'picture-in-picture');
+  }, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+  </script>
+</body>
diff --git a/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html b/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..0f8a0c0
--- /dev/null
+++ b/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/picture-in-picture.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const header = 'Feature-Policy header: picture-in-picture *';
+
+  async_test(t => {
+    isPictureInPictureAllowed().then(t.step_func_done((result) => {
+      assert_true(result);
+    }));
+  }, header + ' allows the top-level document.');
+
+  async_test(t => {
+    test_feature_availability('picture-in-picture', t, same_origin_src,
+        expect_feature_available_default);
+  }, header + ' allows same-origin iframes.');
+
+  async_test(t => {
+    test_feature_availability('picture-in-picture', t, cross_origin_src,
+        expect_feature_available_default);
+  }, header + ' allows cross-origin iframes.');
+  </script>
+</body>
diff --git a/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html.headers b/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..0204b73
--- /dev/null
+++ b/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: picture-in-picture *
diff --git a/feature-policy/picture-in-picture-default-feature-policy.https.sub.html b/feature-policy/picture-in-picture-default-feature-policy.https.sub.html
new file mode 100644
index 0000000..94cec08
--- /dev/null
+++ b/feature-policy/picture-in-picture-default-feature-policy.https.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/picture-in-picture.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const header = 'Default "picture-in-picture" feature policy [*]';
+
+  async_test(t => {
+    isPictureInPictureAllowed().then(t.step_func_done((result) => {
+      assert_true(result);
+    }));
+  }, header + ' allows the top-level document.');
+
+  async_test(t => {
+    test_feature_availability('picture-in-picture', t, same_origin_src,
+        expect_feature_available_default);
+  }, header + ' allows same-origin iframes.');
+
+  async_test(t => {
+    test_feature_availability('picture-in-picture', t, cross_origin_src,
+        expect_feature_available_default,);
+  }, header + ' allows cross-origin iframes.');
+
+  </script>
+</body>
diff --git a/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html b/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..5080c80
--- /dev/null
+++ b/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/resources/testdriver.js></script>
+  <script src=/resources/testdriver-vendor.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=/feature-policy/resources/picture-in-picture.js></script>
+  <script>
+  'use strict';
+  const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+  const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+    same_origin_src;
+  const header = 'Feature-Policy header: picture-in-picture "none"';
+
+  async_test(t => {
+    isPictureInPictureAllowed().then(t.step_func_done((result) => {
+      assert_false(result);
+    }));
+  }, header + ' disallows the top-level document.');
+
+  async_test(t => {
+    test_feature_availability('picture-in-picture', t, same_origin_src,
+        expect_feature_unavailable_default);
+  }, header + ' disallows same-origin iframes.');
+
+  async_test(t => {
+    test_feature_availability('picture-in-picture', t, cross_origin_src,
+        expect_feature_unavailable_default,);
+  }, header + ' disallows cross-origin iframes.');
+  </script>
+</body>
diff --git a/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html.headers b/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..1759381
--- /dev/null
+++ b/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: picture-in-picture 'none'
diff --git a/feature-policy/resources/autoplay.js b/feature-policy/resources/autoplay.js
new file mode 100644
index 0000000..56780cf
--- /dev/null
+++ b/feature-policy/resources/autoplay.js
@@ -0,0 +1,28 @@
+
+
+function simulateGesture(t, callback) {
+  // Get or create the target element.
+  let target = document.getElementById('target');
+  if (!target) {
+    target = document.createElement('button');
+    target.setAttribute('id', 'target');
+    document.body.appendChild(target);
+  }
+
+  // Simulate a gesture in the top frame to remove any gesture based autoplay
+  // restrictions.
+  test_driver.click(target).then(callback, t.unreached_func('click failed'));
+}
+
+function isAutoplayAllowed() {
+  return new Promise((resolve, reject) => {
+    const video = document.createElement('video');
+    video.src = getVideoURI('/media/A4');
+    video.play().then(() => resolve(true), (e) => {
+      if (e.name == 'NotAllowedError')
+        resolve(false);
+      else
+        resolve(true);
+    });
+  });
+}
diff --git a/feature-policy/resources/feature-policy-allowedfeatures.html b/feature-policy/resources/feature-policy-allowedfeatures.html
new file mode 100644
index 0000000..9cc8e1e
--- /dev/null
+++ b/feature-policy/resources/feature-policy-allowedfeatures.html
@@ -0,0 +1,7 @@
+<script>
+'use strict';
+
+window.onload = function() {
+  parent.postMessage(document.policy.allowedFeatures(), '*');
+}
+</script>
diff --git a/feature-policy/resources/feature-policy-autoplay.html b/feature-policy/resources/feature-policy-autoplay.html
new file mode 100644
index 0000000..79f8eef
--- /dev/null
+++ b/feature-policy/resources/feature-policy-autoplay.html
@@ -0,0 +1,11 @@
+<script src="/common/media.js"></script>
+<script src=/feature-policy/resources/autoplay.js></script>
+<script>
+'use strict';
+
+window.addEventListener('load', () => {
+  isAutoplayAllowed().then((result) => {
+    window.parent.postMessage({ enabled: result }, '*');
+  });
+}, { once: true });
+</script>
diff --git a/feature-policy/resources/feature-policy-generic-sensor.html b/feature-policy/resources/feature-policy-generic-sensor.html
new file mode 100644
index 0000000..59652e2
--- /dev/null
+++ b/feature-policy/resources/feature-policy-generic-sensor.html
@@ -0,0 +1,11 @@
+<script>
+"use strict";
+
+try {
+  const sensorName = location.hash.substring(1);
+  const sensor = new window[sensorName]();
+  window.parent.postMessage({ enabled: true }, "*");
+} catch (e) {
+  window.parent.postMessage({ enabled: false }, "*");
+}
+</script>
diff --git a/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html b/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html
new file mode 100644
index 0000000..3d9530c
--- /dev/null
+++ b/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<body>
+<script>
+'use strict';
+var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+var cross_origin_src = 'https://{{domains[www1]}}:{{ports[https][0]}}' + same_origin_src;
+var subframe_header_policy = '?pipe=header(Feature-Policy, fullscreen ';
+var policy_all = '*';
+var policy_self = '\'self\'';
+var policy_none = '\'none\'';
+
+let local_frame_all = document.createElement('iframe');
+let local_frame_self = document.createElement('iframe');
+let local_frame_none = document.createElement('iframe');
+local_frame_all.src = same_origin_src + subframe_header_policy + policy_all + ';)';
+local_frame_self.src = same_origin_src + subframe_header_policy + policy_self + ';)';
+local_frame_none.src = same_origin_src + subframe_header_policy + policy_none + ';)';
+
+let remote_frame_all = document.createElement('iframe');
+let remote_frame_self = document.createElement('iframe');
+let remote_frame_none = document.createElement('iframe');
+remote_frame_all.src = cross_origin_src + subframe_header_policy + policy_all + ';)';
+remote_frame_self.src = cross_origin_src + subframe_header_policy + policy_self + ';)';
+remote_frame_none.src = cross_origin_src + subframe_header_policy + policy_none + ';)';
+
+window.addEventListener('message', function(evt) {
+  if (evt.source === local_frame_all.contentWindow) {
+    parent.postMessage({frame: 'local', policy: policy_all, allowedfeatures: evt.data}, '*');
+  } else if (evt.source === local_frame_self.contentWindow) {
+    parent.postMessage({frame: 'local', policy: policy_self, allowedfeatures: evt.data}, '*');
+  } else if (evt.source === local_frame_none.contentWindow) {
+    parent.postMessage({frame: 'local', policy: policy_none, allowedfeatures: evt.data}, '*');
+  } else if (evt.source === remote_frame_all.contentWindow) {
+    parent.postMessage({frame: 'remote', policy: policy_all, allowedfeatures: evt.data}, '*');
+  } else if (evt.source === remote_frame_self.contentWindow) {
+    parent.postMessage({frame: 'remote', policy: policy_self, allowedfeatures: evt.data}, '*');
+  } else if (evt.source === remote_frame_none.contentWindow) {
+    parent.postMessage({frame: 'remote', policy: policy_none, allowedfeatures: evt.data}, '*');
+  }
+});
+
+document.body.appendChild(local_frame_all);
+document.body.appendChild(local_frame_self);
+document.body.appendChild(local_frame_none);
+document.body.appendChild(remote_frame_all);
+document.body.appendChild(remote_frame_self);
+document.body.appendChild(remote_frame_none);
+</script>
+</body>
+
diff --git a/feature-policy/resources/feature-policy-picture-in-picture.html b/feature-policy/resources/feature-policy-picture-in-picture.html
new file mode 100644
index 0000000..436682d
--- /dev/null
+++ b/feature-policy/resources/feature-policy-picture-in-picture.html
@@ -0,0 +1,10 @@
+<script src=/feature-policy/resources/picture-in-picture.js></script>
+<script>
+'use strict';
+
+window.addEventListener('load', () => {
+  isPictureInPictureAllowed().then(result => {
+    window.parent.postMessage({ enabled: result }, '*');
+  });
+}, { once: true });
+</script>
diff --git a/feature-policy/resources/featurepolicy.js b/feature-policy/resources/featurepolicy.js
index b08d560..bf7693f 100644
--- a/feature-policy/resources/featurepolicy.js
+++ b/feature-policy/resources/featurepolicy.js
@@ -247,3 +247,174 @@
       'Feature policy "' + feature_name +
           '" can be disabled in cross-origin iframes using "allow" attribute.');
 }
+
+// This function tests that a given policy allows each feature for the correct
+// list of origins specified by the |expected_policy|.
+// Arguments:
+//     expected_policy: A list of {feature, allowlist} pairs where the feature is
+//         enabled for every origin in the allowlist, in the |policy|.
+//     policy: Either a document.policy or a iframe.policy to be tested.
+//     message: A short description of what policy is being tested.
+function test_allowlists(expected_policy, policy, message) {
+  for (var allowlist of allowlists) {
+    test(function() {
+      assert_array_equals(
+        policy.getAllowlistForFeature(allowlist.feature),
+        allowlist.allowlist);
+    }, message + ' for feature ' + allowlist.feature);
+  }
+}
+
+// This function tests that a subframe's document policy allows a given feature.
+// A feature is allowed in a frame either through inherited policy or specified
+// by iframe allow attribute.
+// Arguments:
+//     test: test created by testharness. Examples: async_test, promise_test.
+//     feature: feature name that should be allowed in the frame.
+//     src: the URL to load in the frame.
+//     allow: the allow attribute (container policy) of the iframe
+function test_allowed_feature_for_subframe(message, feature, src, allow) {
+  let frame = document.createElement('iframe');
+  if (typeof allow !== 'undefined') {
+    frame.allow = allow;
+  }
+  promise_test(function() {
+    frame.src = src;
+    return new Promise(function(resolve, reject) {
+      window.addEventListener('message', function handler(evt) {
+        resolve(evt.data);
+      }, { once: true });
+      document.body.appendChild(frame);
+    }).then(function(data) {
+      assert_true(data.includes(feature), feature);
+    });
+  }, message);
+}
+
+// This function tests that a subframe's document policy disallows a given
+// feature. A feature is allowed in a frame either through inherited policy or
+// specified by iframe allow attribute.
+// Arguments:
+//     test: test created by testharness. Examples: async_test, promise_test.
+//     feature: feature name that should not be allowed in the frame.
+//     src: the URL to load in the frame.
+//     allow: the allow attribute (container policy) of the iframe
+function test_disallowed_feature_for_subframe(message, feature, src, allow) {
+  let frame = document.createElement('iframe');
+  if (typeof allow !== 'undefined') {
+    frame.allow = allow;
+  }
+  promise_test(function() {
+    frame.src = src;
+    return new Promise(function(resolve, reject) {
+      window.addEventListener('message', function handler(evt) {
+        resolve(evt.data);
+      }, { once: true });
+      document.body.appendChild(frame);
+    }).then(function(data) {
+      assert_false(data.includes(feature), feature);
+    });
+  }, message);
+}
+
+// This function tests that a subframe with header policy defined on a given
+// feature allows and disallows the feature as expected.
+// Arguments:
+//     feature: feature name.
+//     frame_header_policy: either *, 'self' or 'none', defines the frame
+//                          document's header policy on |feature|.
+//     src: the URL to load in the frame.
+//     test_expects: contains 6 expected results of either |feature| is allowed
+//                   or not inside of a local or remote iframe nested inside
+//                   the subframe given the header policy to be either *,
+//                   'slef', or 'none'.
+//     test_name: name of the test.
+function test_subframe_header_policy(
+    feature, frame_header_policy, src, test_expects, test_name) {
+  let frame = document.createElement('iframe');
+  promise_test(function() {
+    frame.src = src + '?pipe=sub|header(Feature-Policy,' + feature + ' '
+        + frame_header_policy + ';)';
+    return new Promise(function(resolve, reject) {
+      let results = [];
+      window.addEventListener('message', function handler(evt) {
+        results.push(evt.data);
+        if (results.length >= 6) {
+          resolve(results);
+        }
+      });
+      document.body.appendChild(frame);
+    }).then(function(results) {
+      for (var j = 0; j < results.length; j++) {
+        var data = results[j];
+
+        function test_result(message, test_expect) {
+          if (test_expect) {
+            assert_true(data.allowedfeatures.includes(feature), message);
+          } else {
+            assert_false(data.allowedfeatures.includes(feature), message);
+          }
+        }
+
+        if (data.frame === 'local') {
+          if (data.policy === '*') {
+            test_result('local_all:', test_expects.local_all);
+          }
+          if (data.policy === '\'self\'') {
+            test_result('local_self:', test_expects.local_self);
+          }
+          if (data.policy === '\'none\'') {
+            test_result('local_none:', test_expects.local_none);
+          }
+        }
+
+        if (data.frame === 'remote') {
+          if (data.policy === '*') {
+            test_result('remote_all:', test_expects.remote_all);
+          }
+          if (data.policy === '\'self\'') {
+            test_result('remote_self:', test_expects.remote_self);
+          }
+          if (data.policy === '\'none\'') {
+            test_result('remote_none:', test_expects.remote_none);
+          }
+        }
+      }
+    });
+  }, test_name);
+}
+
+// This function tests that frame policy allows a given feature correctly. A
+// feature is allowed in a frame either through inherited policy or specified
+// by iframe allow attribute.
+// Arguments:
+//     feature: feature name.
+//     src: the URL to load in the frame.
+//     test_expect: boolean value of whether the feature should be allowed.
+//     allow: optional, the allow attribute (container policy) of the iframe.
+//     allowfullscreen: optional, boolean value of allowfullscreen attribute.
+//     sandbox: optional boolean. If true, the frame will be sandboxed (with
+//         allow-scripts, so that tests can run in it.)
+function test_frame_policy(
+    feature, src, test_expect, allow, allowfullscreen, sandbox) {
+  let frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+  // frame_policy should be dynamically updated as allow and allowfullscreen is
+  // updated.
+  var frame_policy = frame.policy;
+  if (typeof allow !== 'undefined') {
+    frame.setAttribute('allow', allow);
+  }
+  if (!!allowfullscreen) {
+    frame.setAttribute('allowfullscreen', true);
+  }
+  if (!!sandbox) {
+    frame.setAttribute('sandbox', 'allow-scripts');
+  }
+  frame.src = src;
+  if (test_expect) {
+    assert_true(frame_policy.allowedFeatures().includes(feature));
+  } else {
+    assert_false(frame_policy.allowedFeatures().includes(feature));
+  }
+}
diff --git a/feature-policy/resources/picture-in-picture.js b/feature-policy/resources/picture-in-picture.js
new file mode 100644
index 0000000..6bdb7e5
--- /dev/null
+++ b/feature-policy/resources/picture-in-picture.js
@@ -0,0 +1,16 @@
+function isPictureInPictureAllowed() {
+  return new Promise(resolve => {
+    let video = document.createElement('video');
+    video.src = '/media/movie_5.ogv';
+    video.onloadedmetadata = () => {
+      video.requestPictureInPicture()
+      .then(() => resolve(document.pictureInPictureEnabled))
+      .catch(e => {
+        if (e.name == 'NotAllowedError')
+          resolve(document.pictureInPictureEnabled);
+        else
+          resolve(false);
+      });
+    };
+  });
+}
\ No newline at end of file
diff --git a/fetch/README.md b/fetch/README.md
index 3c59056..dcaad02 100644
--- a/fetch/README.md
+++ b/fetch/README.md
@@ -3,4 +3,4 @@
 More Fetch tests can be found in
 
 * /cors
-* /XMLHttpRequest
+* /xhr
diff --git a/fetch/api/abort/general-serviceworker.https.html b/fetch/api/abort/general-serviceworker.https.html
deleted file mode 100644
index 74de287..0000000
--- a/fetch/api/abort/general-serviceworker.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>General fetch abort tests in a service worker</title>
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-<script>
-  (async function() {
-    const scope = 'does/not/exist';
-
-    let reg = await navigator.serviceWorker.getRegistration(scope);
-    if (reg) await reg.unregister();
-
-    reg = await navigator.serviceWorker.register('general.any.worker.js', {scope});
-
-    fetch_tests_from_worker(reg.installing);
-  })();
-</script>
-</body>
-</html>
diff --git a/fetch/api/abort/general-sharedworker.html b/fetch/api/abort/general-sharedworker.html
deleted file mode 100644
index 9378e16..0000000
--- a/fetch/api/abort/general-sharedworker.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>General fetch abort tests - shared worker</title>
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-<script>
-  fetch_tests_from_worker(new SharedWorker("general.any.worker.js"));
-</script>
-</body>
-</html>
diff --git a/fetch/api/abort/general.any.js b/fetch/api/abort/general.any.js
index e2c6e8d..4bc404d 100644
--- a/fetch/api/abort/general.any.js
+++ b/fetch/api/abort/general.any.js
@@ -1,3 +1,4 @@
+// META: global=window,worker
 // META: script=/common/utils.js
 // META: script=../request/request-error.js
 
@@ -57,7 +58,7 @@
       // Add signal to 2nd arg
       args[1] = args[1] || {};
       args[1].signal = controller.signal;
-      await promise_rejects(t, err, fetch(...args));
+      await promise_rejects(t, new TypeError, fetch(...args));
     }
   }, `TypeError from request constructor takes priority - ${testName}`);
 }
@@ -78,6 +79,7 @@
   assert_true(Boolean(request.signal), "Signal member is present & truthy");
   assert_equals(request.signal.constructor, AbortSignal);
   assert_not_equals(request.signal, signal, 'Request has a new signal, not a reference');
+  assert_true(request.signal.aborted, `Request's signal has aborted`);
 
   const fetchPromise = fetch(request);
 
diff --git a/fetch/api/abort/serviceworker-intercepted.https.html b/fetch/api/abort/serviceworker-intercepted.https.html
index 6df6aef..623036d 100644
--- a/fetch/api/abort/serviceworker-intercepted.https.html
+++ b/fetch/api/abort/serviceworker-intercepted.https.html
@@ -13,25 +13,18 @@
   const SCOPE = '../resources/basic.html';
   const BODY_METHODS = ['arrayBuffer', 'blob', 'formData', 'json', 'text'];
 
-  async function cleanup() {
-    for (const iframe of document.querySelectorAll('.test-iframe')) {
-      iframe.parentNode.removeChild(iframe);
-    }
-
-    const reg = await navigator.serviceWorker.getRegistration(SCOPE);
-    if (reg) await reg.unregister();
-  }
-
-  async function setupRegistration(t) {
-    await cleanup();
-    const reg = await navigator.serviceWorker.register('../resources/sw-intercept.js', { scope: SCOPE });
+  async function setupRegistration(t, scope) {
+    const reg = await navigator.serviceWorker.register('../resources/sw-intercept.js', { scope });
     await wait_for_state(t, reg.installing, 'activated');
+    add_completion_callback(_ => reg.unregister());
     return reg;
   }
 
   promise_test(async t => {
-    await setupRegistration(t);
-    const iframe = await with_iframe(SCOPE);
+    const scope = SCOPE + "?q=aborted-not-intercepted";
+    await setupRegistration(t, scope);
+    const iframe = await with_iframe(scope);
+    add_completion_callback(_ => iframe.remove());
     const w = iframe.contentWindow;
 
     const controller = new w.AbortController();
@@ -56,8 +49,10 @@
 
   for (const bodyMethod of BODY_METHODS) {
     promise_test(async t => {
-      await setupRegistration(t);
-      const iframe = await with_iframe(SCOPE);
+      const scope = SCOPE + "?q=aborted-" + bodyMethod + "-rejects";
+      await setupRegistration(t, scope);
+      const iframe = await with_iframe(scope);
+      add_completion_callback(_ => iframe.remove());
       const w = iframe.contentWindow;
 
       const controller = new w.AbortController();
@@ -82,8 +77,10 @@
   }
 
   promise_test(async t => {
-    await setupRegistration(t);
-    const iframe = await with_iframe(SCOPE);
+    const scope = SCOPE + "?q=aborted-stream-errors";
+    await setupRegistration(t, scope);
+    const iframe = await with_iframe(scope);
+    add_completion_callback(_ => iframe.remove());
     const w = iframe.contentWindow;
 
     const controller = new w.AbortController();
diff --git a/fetch/api/basic/integrity-sharedworker.html b/fetch/api/basic/integrity-sharedworker.html
deleted file mode 100644
index fa90a60..0000000
--- a/fetch/api/basic/integrity-sharedworker.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in sharedworker: integrity handling</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new SharedWorker("integrity.js?pipe=sub"));
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/basic/integrity-worker.html b/fetch/api/basic/integrity-worker.html
deleted file mode 100644
index 9240bc6..0000000
--- a/fetch/api/basic/integrity-worker.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: integrity handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("integrity.js?pipe=sub"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/integrity.html b/fetch/api/basic/integrity.html
deleted file mode 100644
index 150c9b7..0000000
--- a/fetch/api/basic/integrity.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: integrity handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="integrity.js?pipe=sub"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/integrity.js b/fetch/api/basic/integrity.js
deleted file mode 100644
index 0a3c298..0000000
--- a/fetch/api/basic/integrity.js
+++ /dev/null
@@ -1,79 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function integrity(desc, url, integrity, initRequestMode, shouldPass) {
-  var fetchRequestInit = {'integrity': integrity}
-  if (!!initRequestMode && initRequestMode !== "") {
-    fetchRequestInit.mode = initRequestMode;
-  }
-
-  if (shouldPass) {
-    promise_test(function(test) {
-      return fetch(url, fetchRequestInit).then(function(resp) {
-        if (initRequestMode !== "no-cors") {
-          assert_equals(resp.status, 200, "Response's status is 200");
-        } else {
-          assert_equals(resp.status, 0, "Opaque response's status is 0");
-          assert_equals(resp.type, "opaque");
-        }
-      });
-    }, desc);
-  } else {
-    promise_test(function(test) {
-      return promise_rejects(test, new TypeError(), fetch(url, fetchRequestInit));
-    }, desc);
-  }
-}
-
-var topSha256 = "sha256-KHIDZcXnR2oBHk9DrAA+5fFiR6JjudYjqoXtMR1zvzk=";
-var topSha384 = "sha384-MgZYnnAzPM/MjhqfOIMfQK5qcFvGZsGLzx4Phd7/A8fHTqqLqXqKo8cNzY3xEPTL";
-var topSha512 = "sha512-D6yns0qxG0E7+TwkevZ4Jt5t7Iy3ugmAajG/dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg==";
-var invalidSha256 = "sha256-dKUcPOn/AlUjWIwcHeHNqYXPlvyGiq+2dWOdFcE+24I=";
-var invalidSha512 = "sha512-oUceBRNxPxnY60g/VtPCj2syT4wo4EZh2CgYdWy9veW8+OsReTXoh7dizMGZafvx9+QhMS39L/gIkxnPIn41Zg==";
-
-var url = "../resources/top.txt";
-var corsUrl = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
-/* Enable CORS*/
-corsUrl += "?pipe=header(Access-Control-Allow-Origin,*)";
-var corsUrl2 = "https://{{host}}:{{ports[https][0]}}/fetch/api/resource/top.txt";
-
-integrity("Empty string integrity", url, "", /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("SHA-256 integrity", url, topSha256, /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("SHA-384 integrity", url, topSha384, /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("SHA-512 integrity", url, topSha512, /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("Invalid integrity", url, invalidSha256,
-          /* initRequestMode */ undefined, /* shouldPass */  false);
-integrity("Multiple integrities: valid stronger than invalid", url,
-          invalidSha256 + " " + topSha384, /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("Multiple integrities: invalid stronger than valid",
-          url, invalidSha512 + " " + topSha384, /* initRequestMode */ undefined,
-          /* shouldPass */ false);
-integrity("Multiple integrities: invalid as strong as valid", url,
-          invalidSha512 + " " + topSha512, /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("Multiple integrities: both are valid", url,
-          topSha384 + " " + topSha512, /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("Multiple integrities: both are invalid", url,
-          invalidSha256 + " " + invalidSha512, /* initRequestMode */ undefined,
-          /* shouldPass */ false);
-integrity("CORS empty integrity", corsUrl, "", /* initRequestMode */ undefined,
-          /* shouldPass */ true);
-integrity("CORS SHA-512 integrity", corsUrl, topSha512,
-          /* initRequestMode */ undefined, /* shouldPass */ true);
-integrity("CORS invalid integrity", corsUrl, invalidSha512,
-          /* initRequestMode */ undefined, /* shouldPass */ false);
-
-integrity("Empty string integrity for opaque response", corsUrl2, "",
-          /* initRequestMode */ "no-cors", /* shouldPass */ true);
-integrity("SHA-* integrity for opaque response", corsUrl2, topSha512,
-          /* initRequestMode */ "no-cors", /* shouldPass */ false);
-
-done();
diff --git a/fetch/api/basic/integrity.sub.any.js b/fetch/api/basic/integrity.sub.any.js
new file mode 100644
index 0000000..d487c37
--- /dev/null
+++ b/fetch/api/basic/integrity.sub.any.js
@@ -0,0 +1,77 @@
+// META: global=sharedworker
+// META: script=../resources/utils.js
+
+function integrity(desc, url, integrity, initRequestMode, shouldPass) {
+  var fetchRequestInit = {'integrity': integrity}
+  if (!!initRequestMode && initRequestMode !== "") {
+    fetchRequestInit.mode = initRequestMode;
+  }
+
+  if (shouldPass) {
+    promise_test(function(test) {
+      return fetch(url, fetchRequestInit).then(function(resp) {
+        if (initRequestMode !== "no-cors") {
+          assert_equals(resp.status, 200, "Response's status is 200");
+        } else {
+          assert_equals(resp.status, 0, "Opaque response's status is 0");
+          assert_equals(resp.type, "opaque");
+        }
+      });
+    }, desc);
+  } else {
+    promise_test(function(test) {
+      return promise_rejects(test, new TypeError(), fetch(url, fetchRequestInit));
+    }, desc);
+  }
+}
+
+const topSha256 = "sha256-KHIDZcXnR2oBHk9DrAA+5fFiR6JjudYjqoXtMR1zvzk=";
+const topSha384 = "sha384-MgZYnnAzPM/MjhqfOIMfQK5qcFvGZsGLzx4Phd7/A8fHTqqLqXqKo8cNzY3xEPTL";
+const topSha512 = "sha512-D6yns0qxG0E7+TwkevZ4Jt5t7Iy3ugmAajG/dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg==";
+const invalidSha256 = "sha256-dKUcPOn/AlUjWIwcHeHNqYXPlvyGiq+2dWOdFcE+24I=";
+const invalidSha512 = "sha512-oUceBRNxPxnY60g/VtPCj2syT4wo4EZh2CgYdWy9veW8+OsReTXoh7dizMGZafvx9+QhMS39L/gIkxnPIn41Zg==";
+
+const path = dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+const url = path;
+const corsUrl =
+  `http://{{host}}:{{ports[http][1]}}${path}?pipe=header(Access-Control-Allow-Origin,*)`;
+const corsUrl2 = `https://{{host}}:{{ports[https][0]}}${path}`
+
+integrity("Empty string integrity", url, "", /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("SHA-256 integrity", url, topSha256, /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("SHA-384 integrity", url, topSha384, /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("SHA-512 integrity", url, topSha512, /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("Invalid integrity", url, invalidSha256,
+          /* initRequestMode */ undefined, /* shouldPass */  false);
+integrity("Multiple integrities: valid stronger than invalid", url,
+          invalidSha256 + " " + topSha384, /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("Multiple integrities: invalid stronger than valid",
+          url, invalidSha512 + " " + topSha384, /* initRequestMode */ undefined,
+          /* shouldPass */ false);
+integrity("Multiple integrities: invalid as strong as valid", url,
+          invalidSha512 + " " + topSha512, /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("Multiple integrities: both are valid", url,
+          topSha384 + " " + topSha512, /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("Multiple integrities: both are invalid", url,
+          invalidSha256 + " " + invalidSha512, /* initRequestMode */ undefined,
+          /* shouldPass */ false);
+integrity("CORS empty integrity", corsUrl, "", /* initRequestMode */ undefined,
+          /* shouldPass */ true);
+integrity("CORS SHA-512 integrity", corsUrl, topSha512,
+          /* initRequestMode */ undefined, /* shouldPass */ true);
+integrity("CORS invalid integrity", corsUrl, invalidSha512,
+          /* initRequestMode */ undefined, /* shouldPass */ false);
+
+integrity("Empty string integrity for opaque response", corsUrl2, "",
+          /* initRequestMode */ "no-cors", /* shouldPass */ true);
+integrity("SHA-* integrity for opaque response", corsUrl2, topSha512,
+          /* initRequestMode */ "no-cors", /* shouldPass */ false);
+
+done();
diff --git a/fetch/api/basic/mediasource.window.js b/fetch/api/basic/mediasource.window.js
new file mode 100644
index 0000000..ff58a52
--- /dev/null
+++ b/fetch/api/basic/mediasource.window.js
@@ -0,0 +1,5 @@
+promise_test(t => {
+  const mediaSource = new MediaSource(),
+        mediaSourceURL = URL.createObjectURL(mediaSource);
+  return promise_rejects(t, new TypeError(), fetch(mediaSourceURL));
+}, "Cannot fetch blob: URL from a MediaSource");
diff --git a/fetch/api/basic/mode-no-cors-worker.html b/fetch/api/basic/mode-no-cors-worker.html
deleted file mode 100644
index 87376a1..0000000
--- a/fetch/api/basic/mode-no-cors-worker.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: no-cors mode and opaque filtering</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("mode-no-cors.js?pipe=sub"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/mode-no-cors.html b/fetch/api/basic/mode-no-cors.html
deleted file mode 100644
index 7aee379..0000000
--- a/fetch/api/basic/mode-no-cors.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: no-cors mode and opaque filtering</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="mode-no-cors.js?pipe=sub"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/mode-no-cors.js b/fetch/api/basic/mode-no-cors.js
deleted file mode 100644
index 53e8490..0000000
--- a/fetch/api/basic/mode-no-cors.js
+++ /dev/null
@@ -1,31 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function fetchNoCors(url, isOpaqueFiltered) {
-  var urlQuery = "?pipe=header(x-is-filtered,value)"
-  promise_test(function(test) {
-    if (isOpaqueFiltered)
-      return fetch(url + urlQuery, {"mode": "no-cors"}).then(function(resp) {
-        assert_equals(resp.status, 0, "Opaque filter: status is 0");
-        assert_equals(resp.statusText, "", "Opaque filter: statusText is \"\"");
-        assert_equals(resp.type , "opaque", "Opaque filter: response's type is opaque");
-        assert_equals(resp.headers.get("x-is-filtered"), null, "Header x-is-filtered is filtered");
-      });
-    else
-      return fetch(url + urlQuery, {"mode": "no-cors"}).then(function(resp) {
-        assert_equals(resp.status, 200, "HTTP status is 200");
-        assert_equals(resp.type , "basic", "Response's type is basic");
-        assert_equals(resp.headers.get("x-is-filtered"), "value", "Header x-is-filtered is not filtered");
-      });
-  }, "Fetch "+ url + " with no-cors mode");
-}
-
-fetchNoCors(RESOURCES_DIR + "top.txt", false);
-fetchNoCors("http://{{host}}:{{ports[http][0]}}/fetch/api/resources/top.txt", false);
-fetchNoCors("https://{{host}}:{{ports[https][0]}}/fetch/api/resources/top.txt", true);
-fetchNoCors("http://{{host}}:{{ports[http][1]}}/fetch/api/resources/top.txt", true);
-
-done();
-
diff --git a/fetch/api/basic/mode-no-cors.sub.any.js b/fetch/api/basic/mode-no-cors.sub.any.js
new file mode 100644
index 0000000..709eef5
--- /dev/null
+++ b/fetch/api/basic/mode-no-cors.sub.any.js
@@ -0,0 +1,28 @@
+// META: script=../resources/utils.js
+
+function fetchNoCors(url, isOpaqueFiltered) {
+  var urlQuery = "?pipe=header(x-is-filtered,value)"
+  promise_test(function(test) {
+    if (isOpaqueFiltered)
+      return fetch(url + urlQuery, {"mode": "no-cors"}).then(function(resp) {
+        assert_equals(resp.status, 0, "Opaque filter: status is 0");
+        assert_equals(resp.statusText, "", "Opaque filter: statusText is \"\"");
+        assert_equals(resp.type , "opaque", "Opaque filter: response's type is opaque");
+        assert_equals(resp.headers.get("x-is-filtered"), null, "Header x-is-filtered is filtered");
+      });
+    else
+      return fetch(url + urlQuery, {"mode": "no-cors"}).then(function(resp) {
+        assert_equals(resp.status, 200, "HTTP status is 200");
+        assert_equals(resp.type , "basic", "Response's type is basic");
+        assert_equals(resp.headers.get("x-is-filtered"), "value", "Header x-is-filtered is not filtered");
+      });
+  }, "Fetch "+ url + " with no-cors mode");
+}
+
+fetchNoCors(RESOURCES_DIR + "top.txt", false);
+fetchNoCors("http://{{host}}:{{ports[http][0]}}/fetch/api/resources/top.txt", false);
+fetchNoCors("https://{{host}}:{{ports[https][0]}}/fetch/api/resources/top.txt", true);
+fetchNoCors("http://{{host}}:{{ports[http][1]}}/fetch/api/resources/top.txt", true);
+
+done();
+
diff --git a/fetch/api/basic/request-headers-case.any.js b/fetch/api/basic/request-headers-case.any.js
index 549744f..067eabb 100644
--- a/fetch/api/basic/request-headers-case.any.js
+++ b/fetch/api/basic/request-headers-case.any.js
@@ -1,11 +1,11 @@
 promise_test(() => {
-  return fetch("/XMLHttpRequest/resources/echo-headers.py", {headers: [["THIS-is-A-test", 1], ["THIS-IS-A-TEST", 2]] }).then(res => res.text()).then(body => {
+  return fetch("/xhr/resources/echo-headers.py", {headers: [["THIS-is-A-test", 1], ["THIS-IS-A-TEST", 2]] }).then(res => res.text()).then(body => {
     assert_regexp_match(body, /THIS-is-A-test: 1, 2/)
   })
 }, "Multiple headers with the same name, different case (THIS-is-A-test first)")
 
 promise_test(() => {
-  return fetch("/XMLHttpRequest/resources/echo-headers.py", {headers: [["THIS-IS-A-TEST", 1], ["THIS-is-A-test", 2]] }).then(res => res.text()).then(body => {
+  return fetch("/xhr/resources/echo-headers.py", {headers: [["THIS-IS-A-TEST", 1], ["THIS-is-A-test", 2]] }).then(res => res.text()).then(body => {
     assert_regexp_match(body, /THIS-IS-A-TEST: 1, 2/)
   })
 }, "Multiple headers with the same name, different case (THIS-IS-A-TEST first)")
diff --git a/fetch/api/basic/response-url-worker.html b/fetch/api/basic/response-url-worker.html
deleted file mode 100644
index 03374e0..0000000
--- a/fetch/api/basic/response-url-worker.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: response url getter</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#response-class">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-    fetch_tests_from_worker(new Worker("response-url.js?pipe=sub"));
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/basic/response-url.html b/fetch/api/basic/response-url.html
deleted file mode 100644
index dfe9d96..0000000
--- a/fetch/api/basic/response-url.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: response url getter</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#response-class">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="response-url.js?pipe=sub"></script>
-  </body>
-</html>
diff --git a/fetch/api/basic/response-url.js b/fetch/api/basic/response-url.js
deleted file mode 100644
index 91b553a..0000000
--- a/fetch/api/basic/response-url.js
+++ /dev/null
@@ -1,21 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-}
-
-function checkResponseURL(fetchedURL, expectedURL)
-{
-    promise_test(function() {
-        return fetch(fetchedURL).then(function(response) {
-            assert_equals(response.url, expectedURL);
-        });
-    }, "Testing response url getter with " +fetchedURL);
-}
-
-var baseURL = "http://{{host}}:{{ports[http][0]}}";
-checkResponseURL(baseURL + "/ada", baseURL + "/ada");
-checkResponseURL(baseURL + "/#", baseURL + "/");
-checkResponseURL(baseURL + "/#ada", baseURL + "/");
-checkResponseURL(baseURL + "#ada", baseURL + "/");
-
-done();
-
diff --git a/fetch/api/basic/response-url.sub.any.js b/fetch/api/basic/response-url.sub.any.js
new file mode 100644
index 0000000..0d123c4
--- /dev/null
+++ b/fetch/api/basic/response-url.sub.any.js
@@ -0,0 +1,16 @@
+function checkResponseURL(fetchedURL, expectedURL)
+{
+    promise_test(function() {
+        return fetch(fetchedURL).then(function(response) {
+            assert_equals(response.url, expectedURL);
+        });
+    }, "Testing response url getter with " +fetchedURL);
+}
+
+var baseURL = "http://{{host}}:{{ports[http][0]}}";
+checkResponseURL(baseURL + "/ada", baseURL + "/ada");
+checkResponseURL(baseURL + "/#", baseURL + "/");
+checkResponseURL(baseURL + "/#ada", baseURL + "/");
+checkResponseURL(baseURL + "#ada", baseURL + "/");
+
+done();
diff --git a/fetch/api/basic/scheme-about.any.js b/fetch/api/basic/scheme-about.any.js
index ee0572f..aae7146 100644
--- a/fetch/api/basic/scheme-about.any.js
+++ b/fetch/api/basic/scheme-about.any.js
@@ -1,34 +1,17 @@
 // META: script=../resources/utils.js
 
-function checkFetchResponse(url, method, desc) {
-  if (!desc) {
-    var cut = (url.length >= 40) ? "[...]" : "";
-    cut += " (" + method + ")"
-    desc = "Fetching " + url.substring(0, 40) + cut + " is OK"
-  }
+function checkNetworkError(url, method) {
+  method = method || "GET";
+  const desc = "Fetching " + url.substring(0, 45) + " with method " + method + " is KO"
   promise_test(function(test) {
-    return fetch(url, { method: method }).then(function(resp) {
-     assert_equals(resp.status, 200, "HTTP status is 200");
-     assert_equals(resp.type, "basic", "response type is basic");
-     assert_equals(resp.headers.get("Content-Type"), "text/html;charset=utf-8", "Content-Type is " + resp.headers.get("Content-Type"));
-     return resp.text();
-    })
-  }, desc);
-}
-
-checkFetchResponse("about:blank", "GET");
-checkFetchResponse("about:blank", "PUT");
-checkFetchResponse("about:blank", "POST");
-
-function checkKoUrl(url, desc) {
-  if (!desc)
-    desc = "Fetching " + url.substring(0, 45) + " is KO"
-  promise_test(function(test) {
-    var promise = fetch(url);
+    var promise = fetch(url, { method: method });
     return promise_rejects(test, new TypeError(), promise);
   }, desc);
 }
 
-checkKoUrl("about:invalid.com");
-checkKoUrl("about:config");
-checkKoUrl("about:unicorn");
+checkNetworkError("about:blank", "GET");
+checkNetworkError("about:blank", "PUT");
+checkNetworkError("about:blank", "POST");
+checkNetworkError("about:invalid.com");
+checkNetworkError("about:config");
+checkNetworkError("about:unicorn");
diff --git a/fetch/api/basic/scheme-blob-worker.html b/fetch/api/basic/scheme-blob-worker.html
deleted file mode 100644
index 961ecbd..0000000
--- a/fetch/api/basic/scheme-blob-worker.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: blob scheme</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#basic-fetch">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("scheme-blob.js?pipe=sub"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/scheme-blob.html b/fetch/api/basic/scheme-blob.html
deleted file mode 100644
index 7787c37..0000000
--- a/fetch/api/basic/scheme-blob.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: blob scheme</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#basic-fetch">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="scheme-blob.js?pipe=sub"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/scheme-blob.js b/fetch/api/basic/scheme-blob.js
deleted file mode 100644
index 9bf73a6..0000000
--- a/fetch/api/basic/scheme-blob.js
+++ /dev/null
@@ -1,48 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function checkFetchResponse(url, data, mime, size, desc) {
-  promise_test(function(test) {
-    size = size.toString();
-    return fetch(url).then(function(resp) {
-      assert_equals(resp.status, 200, "HTTP status is 200");
-      assert_equals(resp.type, "basic", "response type is basic");
-      assert_equals(resp.headers.get("Content-Type"), mime, "Content-Type is " + resp.headers.get("Content-Type"));
-      assert_equals(resp.headers.get("Content-Length"), size, "Content-Length is " + resp.headers.get("Content-Length"));
-      return resp.text();
-    }).then(function(bodyAsText) {
-      assert_equals(bodyAsText, data, "Response's body is " + data);
-    });
-  }, desc);
-}
-
-var blob = new Blob(["Blob's data"], { "type" : "text/plain" });
-checkFetchResponse(URL.createObjectURL(blob), "Blob's data", "text/plain",  blob.size,
-                  "Fetching [GET] URL.createObjectURL(blob) is OK");
-
-function checkKoUrl(url, method, desc) {
-  promise_test(function(test) {
-    var promise = fetch(url, {"method": method});
-    return promise_rejects(test, new TypeError(), promise);
-  }, desc);
-}
-
-var blob2 = new Blob(["Blob's data"], { "type" : "text/plain" });
-checkKoUrl("blob:http://{{domains[www]}}:{{ports[http][0]}}/", "GET",
-          "Fetching [GET] blob:http://{{domains[www]}}:{{ports[http][0]}}/ is KO");
-
-var invalidRequestMethods = [
-  "POST",
-  "OPTIONS",
-  "HEAD",
-  "PUT",
-  "DELETE",
-  "INVALID",
-];
-invalidRequestMethods.forEach(function(method) {
-  checkKoUrl(URL.createObjectURL(blob2), method, "Fetching [" + method + "] URL.createObjectURL(blob) is KO");
-});
-
-done();
diff --git a/fetch/api/basic/scheme-blob.sub.any.js b/fetch/api/basic/scheme-blob.sub.any.js
new file mode 100644
index 0000000..fb1357e
--- /dev/null
+++ b/fetch/api/basic/scheme-blob.sub.any.js
@@ -0,0 +1,45 @@
+// META: script=../resources/utils.js
+
+function checkFetchResponse(url, data, mime, size, desc) {
+  promise_test(function(test) {
+    size = size.toString();
+    return fetch(url).then(function(resp) {
+      assert_equals(resp.status, 200, "HTTP status is 200");
+      assert_equals(resp.type, "basic", "response type is basic");
+      assert_equals(resp.headers.get("Content-Type"), mime, "Content-Type is " + resp.headers.get("Content-Type"));
+      assert_equals(resp.headers.get("Content-Length"), size, "Content-Length is " + resp.headers.get("Content-Length"));
+      return resp.text();
+    }).then(function(bodyAsText) {
+      assert_equals(bodyAsText, data, "Response's body is " + data);
+    });
+  }, desc);
+}
+
+var blob = new Blob(["Blob's data"], { "type" : "text/plain" });
+checkFetchResponse(URL.createObjectURL(blob), "Blob's data", "text/plain",  blob.size,
+                  "Fetching [GET] URL.createObjectURL(blob) is OK");
+
+function checkKoUrl(url, method, desc) {
+  promise_test(function(test) {
+    var promise = fetch(url, {"method": method});
+    return promise_rejects(test, new TypeError(), promise);
+  }, desc);
+}
+
+var blob2 = new Blob(["Blob's data"], { "type" : "text/plain" });
+checkKoUrl("blob:http://{{domains[www]}}:{{ports[http][0]}}/", "GET",
+          "Fetching [GET] blob:http://{{domains[www]}}:{{ports[http][0]}}/ is KO");
+
+var invalidRequestMethods = [
+  "POST",
+  "OPTIONS",
+  "HEAD",
+  "PUT",
+  "DELETE",
+  "INVALID",
+];
+invalidRequestMethods.forEach(function(method) {
+  checkKoUrl(URL.createObjectURL(blob2), method, "Fetching [" + method + "] URL.createObjectURL(blob) is KO");
+});
+
+done();
diff --git a/fetch/api/basic/scheme-others-worker.html b/fetch/api/basic/scheme-others-worker.html
deleted file mode 100644
index 397d925..0000000
--- a/fetch/api/basic/scheme-others-worker.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: urls with unsupported schemes</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#basic-fetch">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("scheme-others.js?pipe=sub"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/scheme-others.html b/fetch/api/basic/scheme-others.html
deleted file mode 100644
index dd37143..0000000
--- a/fetch/api/basic/scheme-others.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: urls with unsupported schemes</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#basic-fetch">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="scheme-others.js?pipe=sub"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/basic/scheme-others.js b/fetch/api/basic/scheme-others.js
deleted file mode 100644
index ce02ec1..0000000
--- a/fetch/api/basic/scheme-others.js
+++ /dev/null
@@ -1,33 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function checkKoUrl(url, desc) {
-  if (!desc)
-    desc = "Fetching " + url.substring(0, 45) + " is KO"
-  promise_test(function(test) {
-    var promise = fetch(url);
-    return promise_rejects(test, new TypeError(), promise);
-  }, desc);
-}
-
-var urlWithoutScheme = "://{{host}}:{{ports[http][0]}}/";
-checkKoUrl("aaa" + urlWithoutScheme);
-checkKoUrl("cap" + urlWithoutScheme);
-checkKoUrl("cid" + urlWithoutScheme);
-checkKoUrl("dav" + urlWithoutScheme);
-checkKoUrl("dict" + urlWithoutScheme);
-checkKoUrl("dns" + urlWithoutScheme);
-checkKoUrl("geo" + urlWithoutScheme);
-checkKoUrl("im" + urlWithoutScheme);
-checkKoUrl("imap" + urlWithoutScheme);
-checkKoUrl("ipp" + urlWithoutScheme);
-checkKoUrl("ldap" + urlWithoutScheme);
-checkKoUrl("mailto" + urlWithoutScheme);
-checkKoUrl("nfs" + urlWithoutScheme);
-checkKoUrl("pop" + urlWithoutScheme);
-checkKoUrl("rtsp" + urlWithoutScheme);
-checkKoUrl("snmp" + urlWithoutScheme);
-
-done();
diff --git a/fetch/api/basic/scheme-others.sub.any.js b/fetch/api/basic/scheme-others.sub.any.js
new file mode 100644
index 0000000..5f9848ff
--- /dev/null
+++ b/fetch/api/basic/scheme-others.sub.any.js
@@ -0,0 +1,30 @@
+// META: script=../resources/utils.js
+
+function checkKoUrl(url, desc) {
+  if (!desc)
+    desc = "Fetching " + url.substring(0, 45) + " is KO"
+  promise_test(function(test) {
+    var promise = fetch(url);
+    return promise_rejects(test, new TypeError(), promise);
+  }, desc);
+}
+
+var urlWithoutScheme = "://{{host}}:{{ports[http][0]}}/";
+checkKoUrl("aaa" + urlWithoutScheme);
+checkKoUrl("cap" + urlWithoutScheme);
+checkKoUrl("cid" + urlWithoutScheme);
+checkKoUrl("dav" + urlWithoutScheme);
+checkKoUrl("dict" + urlWithoutScheme);
+checkKoUrl("dns" + urlWithoutScheme);
+checkKoUrl("geo" + urlWithoutScheme);
+checkKoUrl("im" + urlWithoutScheme);
+checkKoUrl("imap" + urlWithoutScheme);
+checkKoUrl("ipp" + urlWithoutScheme);
+checkKoUrl("ldap" + urlWithoutScheme);
+checkKoUrl("mailto" + urlWithoutScheme);
+checkKoUrl("nfs" + urlWithoutScheme);
+checkKoUrl("pop" + urlWithoutScheme);
+checkKoUrl("rtsp" + urlWithoutScheme);
+checkKoUrl("snmp" + urlWithoutScheme);
+
+done();
diff --git a/fetch/api/cors/cors-cookies-redirect.any.js b/fetch/api/cors/cors-cookies-redirect.any.js
new file mode 100644
index 0000000..f5217b4
--- /dev/null
+++ b/fetch/api/cors/cors-cookies-redirect.any.js
@@ -0,0 +1,49 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+var redirectUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
+var urlSetCookies1 = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+var urlSetCookies2 = get_host_info().HTTP_ORIGIN_WITH_DIFFERENT_PORT + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+var urlCheckCookies = get_host_info().HTTP_ORIGIN_WITH_DIFFERENT_PORT + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=cookie";
+
+var urlSetCookiesParameters = "?pipe=header(Access-Control-Allow-Origin," + location.origin + ")";
+urlSetCookiesParameters += "|header(Access-Control-Allow-Credentials,true)";
+
+urlSetCookiesParameters1 = urlSetCookiesParameters + "|header(Set-Cookie,a=1)";
+urlSetCookiesParameters2 = urlSetCookiesParameters + "|header(Set-Cookie,a=2)";
+
+urlClearCookiesParameters1 = urlSetCookiesParameters + "|header(Set-Cookie,a=1%3B%20max-age=0)";
+urlClearCookiesParameters2 = urlSetCookiesParameters + "|header(Set-Cookie,a=2%3B%20max-age=0)";
+
+promise_test(async (test) => {
+    await fetch(urlSetCookies1 + urlSetCookiesParameters1, {"credentials": "include", "mode": "cors"});
+    await fetch(urlSetCookies2 + urlSetCookiesParameters2, {"credentials": "include", "mode": "cors"});
+}, "Set cookies");
+
+function doTest(usePreflight) {
+    promise_test(async (test) => {
+        var url = redirectUrl;
+        var uuid_token = token();
+        var urlParameters = "?token=" + uuid_token + "&max_age=0";
+        urlParameters += "&redirect_status=301";
+        urlParameters += "&location=" + encodeURIComponent(urlCheckCookies);
+        urlParameters += "&allow_headers=a&headers=Cookie";
+        headers = [];
+        if (usePreflight)
+            headers.push(["a", "b"]);
+
+        var requestInit = {"credentials": "include", "mode": "cors", "headers": headers};
+        var response = await fetch(url + urlParameters, requestInit);
+
+        assert_equals(response.headers.get("x-request-cookie") , "a=2", "Request includes cookie(s)");
+    }, "Testing credentials after cross-origin redirection with CORS and " + (usePreflight ? "" : "no ") + "preflight");
+}
+
+doTest(false);
+doTest(true);
+
+promise_test(async (test) => {
+    await fetch(urlSetCookies1 + urlClearCookiesParameters1, {"credentials": "include", "mode": "cors"});
+    await fetch(urlSetCookies2 + urlClearCookiesParameters2, {"credentials": "include", "mode": "cors"});
+}, "Clean cookies");
diff --git a/fetch/api/cors/cors-expose-star-worker.html b/fetch/api/cors/cors-expose-star-worker.html
deleted file mode 100644
index db9b943..0000000
--- a/fetch/api/cors/cors-expose-star-worker.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: Access-Control-Expose-Headers: *</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("cors-expose-star.js?pipe=sub"));
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/cors/cors-expose-star.html b/fetch/api/cors/cors-expose-star.html
deleted file mode 100644
index 37223bd..0000000
--- a/fetch/api/cors/cors-expose-star.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: Access-Control-Expose-Headers: *</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="cors-expose-star.js?pipe=sub"></script>
-  </body>
-</html>
diff --git a/fetch/api/cors/cors-expose-star.js b/fetch/api/cors/cors-expose-star.js
deleted file mode 100644
index e37ddb4..0000000
--- a/fetch/api/cors/cors-expose-star.js
+++ /dev/null
@@ -1,44 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-const url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt",
-      sharedHeaders = "?pipe=header(Access-Control-Expose-Headers,*)|header(Test,X)|header(Set-Cookie,X)|header(*,whoa)|"
-
-promise_test(() => {
-  const headers = "header(Access-Control-Allow-Origin,*)"
-  return fetch(url + sharedHeaders + headers).then(resp => {
-    assert_equals(resp.status, 200)
-    assert_equals(resp.type , "cors")
-    assert_equals(resp.headers.get("test"), "X")
-    assert_equals(resp.headers.get("set-cookie"), null)
-    assert_equals(resp.headers.get("*"), "whoa")
-  })
-}, "Basic Access-Control-Expose-Headers: * support")
-
-promise_test(() => {
-  const origin = location.origin, // assuming an ASCII origin
-        headers = "header(Access-Control-Allow-Origin," + origin + ")|header(Access-Control-Allow-Credentials,true)"
-  return fetch(url + sharedHeaders + headers, { credentials:"include" }).then(resp => {
-    assert_equals(resp.status, 200)
-    assert_equals(resp.type , "cors")
-    assert_equals(resp.headers.get("content-type"), "text/plain") // safelisted
-    assert_equals(resp.headers.get("test"), null)
-    assert_equals(resp.headers.get("set-cookie"), null)
-    assert_equals(resp.headers.get("*"), "whoa")
-  })
-}, "* for credentialed fetches only matches literally")
-
-promise_test(() => {
-  const headers =  "header(Access-Control-Allow-Origin,*)|header(Access-Control-Expose-Headers,set-cookie)"
-  return fetch(url + sharedHeaders + headers).then(resp => {
-    assert_equals(resp.status, 200)
-    assert_equals(resp.type , "cors")
-    assert_equals(resp.headers.get("test"), "X")
-    assert_equals(resp.headers.get("set-cookie"), null)
-    assert_equals(resp.headers.get("*"), "whoa")
-  })
-}, "* can be one of several values")
-
-done();
diff --git a/fetch/api/cors/cors-expose-star.sub.any.js b/fetch/api/cors/cors-expose-star.sub.any.js
new file mode 100644
index 0000000..340e99a
--- /dev/null
+++ b/fetch/api/cors/cors-expose-star.sub.any.js
@@ -0,0 +1,41 @@
+// META: script=../resources/utils.js
+
+const url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt",
+      sharedHeaders = "?pipe=header(Access-Control-Expose-Headers,*)|header(Test,X)|header(Set-Cookie,X)|header(*,whoa)|"
+
+promise_test(() => {
+  const headers = "header(Access-Control-Allow-Origin,*)"
+  return fetch(url + sharedHeaders + headers).then(resp => {
+    assert_equals(resp.status, 200)
+    assert_equals(resp.type , "cors")
+    assert_equals(resp.headers.get("test"), "X")
+    assert_equals(resp.headers.get("set-cookie"), null)
+    assert_equals(resp.headers.get("*"), "whoa")
+  })
+}, "Basic Access-Control-Expose-Headers: * support")
+
+promise_test(() => {
+  const origin = location.origin, // assuming an ASCII origin
+        headers = "header(Access-Control-Allow-Origin," + origin + ")|header(Access-Control-Allow-Credentials,true)"
+  return fetch(url + sharedHeaders + headers, { credentials:"include" }).then(resp => {
+    assert_equals(resp.status, 200)
+    assert_equals(resp.type , "cors")
+    assert_equals(resp.headers.get("content-type"), "text/plain") // safelisted
+    assert_equals(resp.headers.get("test"), null)
+    assert_equals(resp.headers.get("set-cookie"), null)
+    assert_equals(resp.headers.get("*"), "whoa")
+  })
+}, "* for credentialed fetches only matches literally")
+
+promise_test(() => {
+  const headers =  "header(Access-Control-Allow-Origin,*)|header(Access-Control-Expose-Headers,set-cookie\\,*)"
+  return fetch(url + sharedHeaders + headers).then(resp => {
+    assert_equals(resp.status, 200)
+    assert_equals(resp.type , "cors")
+    assert_equals(resp.headers.get("test"), "X")
+    assert_equals(resp.headers.get("set-cookie"), null)
+    assert_equals(resp.headers.get("*"), "whoa")
+  })
+}, "* can be one of several values")
+
+done();
diff --git a/fetch/api/cors/cors-filtering-worker.html b/fetch/api/cors/cors-filtering-worker.html
deleted file mode 100644
index f15566f..0000000
--- a/fetch/api/cors/cors-filtering-worker.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: filtered headers in CORS response</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-cors-protocol">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("cors-filtering.js?pipe=sub"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/cors/cors-filtering.html b/fetch/api/cors/cors-filtering.html
deleted file mode 100644
index b7500ad..0000000
--- a/fetch/api/cors/cors-filtering.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: filtered headers in CORS response</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-cors-protocol">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="cors-filtering.js?pipe=sub"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/cors/cors-filtering.js b/fetch/api/cors/cors-filtering.js
deleted file mode 100644
index 1feaa59..0000000
--- a/fetch/api/cors/cors-filtering.js
+++ /dev/null
@@ -1,73 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function corsFilter(corsUrl, headerName, headerValue, isFiltered) {
-  var url = corsUrl + "?pipe=header(" + headerName + "," + encodeURIComponent(headerValue) +")|header(Access-Control-Allow-Origin,*)";
-  promise_test(function(test) {
-    return fetch(url).then(function(resp) {
-      assert_equals(resp.status, 200, "Fetch success with code 200");
-      assert_equals(resp.type , "cors", "CORS fetch's response has cors type");
-      if (!isFiltered) {
-        assert_equals(resp.headers.get(headerName), headerValue,
-          headerName + " header should be included in response with value: " + headerValue);
-      } else {
-        assert_false(resp.headers.has(headerName), "UA should exclude " + headerName + " header from response");
-      }
-      test.done();
-    });
-  }, "CORS filter on " + headerName + " header");
-}
-
-function corsExposeFilter(corsUrl, headerName, headerValue, isForbidden, withCredentials) {
-  var url = corsUrl + "?pipe=header(" + headerName + "," + encodeURIComponent(headerValue) +")|" +
-                            "header(Access-Control-Allow-Origin, http://{{host}}:{{ports[http][0]}})" +
-                            "header(Access-Control-Allow-Credentials, true)" +
-                            "header(Access-Control-Expose-Headers," + headerName + ")";
-
-  var title = "CORS filter on " + headerName + " header, header is " + (isForbidden ? "forbidden" : "exposed");
-  if (withCredentials)
-      title+= "(credentials = include)";
-  promise_test(function(test) {
-    return fetch(new Request(url, { credentials: withCredentials ? "include" : "omit" })).then(function(resp) {
-      assert_equals(resp.status, 200, "Fetch success with code 200");
-      assert_equals(resp.type , "cors", "CORS fetch's response has cors type");
-      if (!isForbidden) {
-        assert_equals(resp.headers.get(headerName), headerValue,
-          headerName + " header should be included in response with value: " + headerValue);
-      } else {
-        assert_false(resp.headers.has(headerName), "UA should exclude " + headerName + " header from response");
-      }
-      test.done();
-    });
-  }, title);
-}
-
-var url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
-
-corsFilter(url, "Cache-Control", "no-cache", false);
-corsFilter(url, "Content-Language", "fr", false);
-corsFilter(url, "Content-Type", "text/html", false);
-corsFilter(url, "Expires","04 May 1988 22:22:22 GMT" , false);
-corsFilter(url, "Last-Modified", "04 May 1988 22:22:22 GMT", false);
-corsFilter(url, "Pragma", "no-cache", false);
-
-corsFilter(url, "Age", "27", true);
-corsFilter(url, "Server", "wptServe" , true);
-corsFilter(url, "Warning", "Mind the gap" , true);
-corsFilter(url, "Content-Length", "3" , true); // top.txt contains "top"
-corsFilter(url, "Set-Cookie", "name=value" , true);
-corsFilter(url, "Set-Cookie2", "name=value" , true);
-
-corsExposeFilter(url, "Age", "27", false);
-corsExposeFilter(url, "Server", "wptServe" , false);
-corsExposeFilter(url, "Warning", "Mind the gap" , false);
-corsExposeFilter(url, "Content-Length", "3" , false);
-
-corsExposeFilter(url, "Set-Cookie", "name=value" , true);
-corsExposeFilter(url, "Set-Cookie2", "name=value" , true);
-corsExposeFilter(url, "Set-Cookie", "name=value" , true, true);
-corsExposeFilter(url, "Set-Cookie2", "name=value" , true, true);
-
-done();
diff --git a/fetch/api/cors/cors-filtering.sub.any.js b/fetch/api/cors/cors-filtering.sub.any.js
new file mode 100644
index 0000000..a26eacc
--- /dev/null
+++ b/fetch/api/cors/cors-filtering.sub.any.js
@@ -0,0 +1,69 @@
+// META: script=../resources/utils.js
+
+function corsFilter(corsUrl, headerName, headerValue, isFiltered) {
+  var url = corsUrl + "?pipe=header(" + headerName + "," + encodeURIComponent(headerValue) +")|header(Access-Control-Allow-Origin,*)";
+  promise_test(function(test) {
+    return fetch(url).then(function(resp) {
+      assert_equals(resp.status, 200, "Fetch success with code 200");
+      assert_equals(resp.type , "cors", "CORS fetch's response has cors type");
+      if (!isFiltered) {
+        assert_equals(resp.headers.get(headerName), headerValue,
+          headerName + " header should be included in response with value: " + headerValue);
+      } else {
+        assert_false(resp.headers.has(headerName), "UA should exclude " + headerName + " header from response");
+      }
+      test.done();
+    });
+  }, "CORS filter on " + headerName + " header");
+}
+
+function corsExposeFilter(corsUrl, headerName, headerValue, isForbidden, withCredentials) {
+  var url = corsUrl + "?pipe=header(" + headerName + "," + encodeURIComponent(headerValue) +")|" +
+                            "header(Access-Control-Allow-Origin, http://{{host}}:{{ports[http][0]}})" +
+                            "header(Access-Control-Allow-Credentials, true)" +
+                            "header(Access-Control-Expose-Headers," + headerName + ")";
+
+  var title = "CORS filter on " + headerName + " header, header is " + (isForbidden ? "forbidden" : "exposed");
+  if (withCredentials)
+      title+= "(credentials = include)";
+  promise_test(function(test) {
+    return fetch(new Request(url, { credentials: withCredentials ? "include" : "omit" })).then(function(resp) {
+      assert_equals(resp.status, 200, "Fetch success with code 200");
+      assert_equals(resp.type , "cors", "CORS fetch's response has cors type");
+      if (!isForbidden) {
+        assert_equals(resp.headers.get(headerName), headerValue,
+          headerName + " header should be included in response with value: " + headerValue);
+      } else {
+        assert_false(resp.headers.has(headerName), "UA should exclude " + headerName + " header from response");
+      }
+      test.done();
+    });
+  }, title);
+}
+
+var url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+
+corsFilter(url, "Cache-Control", "no-cache", false);
+corsFilter(url, "Content-Language", "fr", false);
+corsFilter(url, "Content-Type", "text/html", false);
+corsFilter(url, "Expires","04 May 1988 22:22:22 GMT" , false);
+corsFilter(url, "Last-Modified", "04 May 1988 22:22:22 GMT", false);
+corsFilter(url, "Pragma", "no-cache", false);
+corsFilter(url, "Content-Length", "3" , false); // top.txt contains "top"
+
+corsFilter(url, "Age", "27", true);
+corsFilter(url, "Server", "wptServe" , true);
+corsFilter(url, "Warning", "Mind the gap" , true);
+corsFilter(url, "Set-Cookie", "name=value" , true);
+corsFilter(url, "Set-Cookie2", "name=value" , true);
+
+corsExposeFilter(url, "Age", "27", false);
+corsExposeFilter(url, "Server", "wptServe" , false);
+corsExposeFilter(url, "Warning", "Mind the gap" , false);
+
+corsExposeFilter(url, "Set-Cookie", "name=value" , true);
+corsExposeFilter(url, "Set-Cookie2", "name=value" , true);
+corsExposeFilter(url, "Set-Cookie", "name=value" , true, true);
+corsExposeFilter(url, "Set-Cookie2", "name=value" , true, true);
+
+done();
diff --git a/fetch/api/cors/cors-multiple-origins-worker.html b/fetch/api/cors/cors-multiple-origins-worker.html
deleted file mode 100644
index a8e5057..0000000
--- a/fetch/api/cors/cors-multiple-origins-worker.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: check multiple Access-Control-Allow-Origin header management</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-cors-protocol">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#cors-check">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("cors-multiple-origins.js?pipe=sub"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/cors/cors-multiple-origins.html b/fetch/api/cors/cors-multiple-origins.html
deleted file mode 100644
index 9b12b05..0000000
--- a/fetch/api/cors/cors-multiple-origins.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: check multiple Access-Control-Allow-Origin header management</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-cors-protocol">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#cors-check">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="cors-multiple-origins.js?pipe=sub"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/cors/cors-multiple-origins.js b/fetch/api/cors/cors-multiple-origins.js
deleted file mode 100644
index e4cf724..0000000
--- a/fetch/api/cors/cors-multiple-origins.js
+++ /dev/null
@@ -1,32 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function corsMultipleOrigins(desc, originList, shouldPass) {
-  var urlParameters = "?origin=" + encodeURIComponent(originList.join(", "));
-  var url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
-
-  if (shouldPass) {
-    promise_test(function(test) {
-      return fetch(url + urlParameters).then(function(resp) {
-        assert_equals(resp.status, 200, "Response's status is 200");
-      });
-    }, desc);
-  } else {
-    promise_test(function(test) {
-      return promise_rejects(test, new TypeError(), fetch(url + urlParameters));
-    }, desc);
-  }
-}
-/* Actual origin */
-var origin = "http://{{host}}:{{ports[http][0]}}";
-
-corsMultipleOrigins("3 origins allowed, match the 3rd (" + origin + ")", ["\"\"", "http://example.com", origin], true);
-corsMultipleOrigins("3 origins allowed, match the 3rd (\"*\")", ["\"\"", "http://example.com", "*"], true);
-corsMultipleOrigins("3 origins allowed, match twice (" + origin + ")", ["\"\"", origin, origin], true);
-corsMultipleOrigins("3 origins allowed, match twice (\"*\")", ["*", "http://example.com", "*"], true);
-corsMultipleOrigins("3 origins allowed, match twice (\"*\" and " + origin + ")", ["*", "http://example.com", origin], true);
-corsMultipleOrigins("3 origins allowed, no match", ["", "http://example.com", "https://example2.com"], false);
-
-done();
diff --git a/fetch/api/cors/cors-multiple-origins.sub.any.js b/fetch/api/cors/cors-multiple-origins.sub.any.js
new file mode 100644
index 0000000..f32b387
--- /dev/null
+++ b/fetch/api/cors/cors-multiple-origins.sub.any.js
@@ -0,0 +1,21 @@
+// META: script=../resources/utils.js
+
+function corsMultipleOrigins(originList) {
+  var urlParameters = "?origin=" + encodeURIComponent(originList.join(", "));
+  var url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+  promise_test(function(test) {
+    return promise_rejects(test, new TypeError(), fetch(url + urlParameters));
+  }, "Listing multiple origins is illegal: " + originList);
+}
+/* Actual origin */
+var origin = "http://{{host}}:{{ports[http][0]}}";
+
+corsMultipleOrigins(["\"\"", "http://example.com", origin]);
+corsMultipleOrigins(["\"\"", "http://example.com", "*"]);
+corsMultipleOrigins(["\"\"", origin, origin]);
+corsMultipleOrigins(["*", "http://example.com", "*"]);
+corsMultipleOrigins(["*", "http://example.com", origin]);
+corsMultipleOrigins(["", "http://example.com", "https://example2.com"]);
+
+done();
diff --git a/fetch/api/cors/cors-preflight-star.any.js b/fetch/api/cors/cors-preflight-star.any.js
index 8a18b51..44255d8 100644
--- a/fetch/api/cors/cors-preflight-star.any.js
+++ b/fetch/api/cors/cors-preflight-star.any.js
@@ -32,13 +32,18 @@
   }, "CORS that " + (succeeds ? "succeeds" : "fails") + " with credentials: " + withCredentials + "; method: " + useMethod + " (allowed: " + allowMethod + "); header: " + useHeader + " (allowed: " + allowHeader + ")")
 }
 
+// "GET" does not pass the case-sensitive method check, but in the safe list.
 preflightTest(true, false, "get", "x-test", "GET", ["X-Test", "1"])
+// Headers check is case-insensitive, and "*" works as any for method.
 preflightTest(true, false, "*", "x-test", "SUPER", ["X-Test", "1"])
+// "*" works as any only without credentials.
 preflightTest(true, false, "*", "*", "OK", ["X-Test", "1"])
 preflightTest(false, true, "*", "*", "OK", ["X-Test", "1"])
 preflightTest(false, true, "*", "", "PUT", [])
 preflightTest(true, true, "PUT", "*", "PUT", [])
-preflightTest(false, true, "put", "*", "PUT", [])
 preflightTest(false, true, "get", "*", "GET", ["X-Test", "1"])
 preflightTest(false, true, "*", "*", "GET", ["X-Test", "1"])
+// Exact character match works even for "*" with credentials.
 preflightTest(true, true, "*", "*", "*", ["*", "1"])
+// "PUT" does not pass the case-sensitive method check, and not in the safe list.
+preflightTest(false, true, "put", "*", "PUT", [])
diff --git a/fetch/api/headers/header-values-normalize.html b/fetch/api/headers/header-values-normalize.html
index 616d83a..30e7a58 100644
--- a/fetch/api/headers/header-values-normalize.html
+++ b/fetch/api/headers/header-values-normalize.html
@@ -56,9 +56,9 @@
   promise_test((t) => {
     if(fail) {
       return Promise.all([
-        promise_rejects(t, new TypeError(), fetch("about:blank", { headers: {"val1": val1} })),
-        promise_rejects(t, new TypeError(), fetch("about:blank", { headers: {"val2": val2} })),
-        promise_rejects(t, new TypeError(), fetch("about:blank", { headers: {"val3": val3} }))
+        promise_rejects(t, new TypeError(), fetch(url, { headers: {"val1": val1} })),
+        promise_rejects(t, new TypeError(), fetch(url, { headers: {"val2": val2} })),
+        promise_rejects(t, new TypeError(), fetch(url, { headers: {"val3": val3} }))
       ])
     } else {
       return fetch(url, { headers: {"val1": val1, "val2": val2, "val3": val3} }).then((res) => {
diff --git a/fetch/api/headers/header-values.html b/fetch/api/headers/header-values.html
index 35e7b7e..6dfe0d3 100644
--- a/fetch/api/headers/header-values.html
+++ b/fetch/api/headers/header-values.html
@@ -15,7 +15,7 @@
     assert_throws("SyntaxError", () => xhr.setRequestHeader("value-test", val))
   }, "XMLHttpRequest with value " + encodeURI(val) + " needs to throw")
 
-  promise_test(t => promise_rejects(t, new TypeError(), fetch("about:blank", { headers: {"value-test": val} })), "fetch() with value " + encodeURI(val) + " needs to throw")
+  promise_test(t => promise_rejects(t, new TypeError(), fetch("/", { headers: {"value-test": val} })), "fetch() with value " + encodeURI(val) + " needs to throw")
 })
 
 // Valid values
diff --git a/fetch/api/headers/headers-idl.html b/fetch/api/headers/headers-idl.html
deleted file mode 100644
index 078c9d0..0000000
--- a/fetch/api/headers/headers-idl.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Headers idl interface</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#response">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/resources/WebIDLParser.js"></script>
-    <script src="/resources/idlharness.js"></script>
-  </head>
-  <body>
-    <script id="headers-idl" type="text/plain">
-      typedef (sequence<sequence<ByteString>> or record<ByteString, ByteString>) HeadersInit;
-
-      [Constructor(optional HeadersInit init),
-       Exposed=(Window,Worker)]
-      interface Headers {
-        void append(ByteString name, ByteString value);
-        void delete(ByteString name);
-        ByteString? get(ByteString name);
-        boolean has(ByteString name);
-        void set(ByteString name, ByteString value);
-        iterable<ByteString, ByteString>;
-      };
-    </script>
-    <script>
-      var idlsArray = new IdlArray();
-      var idl = document.getElementById("headers-idl").textContent
-      idlsArray.add_idls(idl);
-      idlsArray.add_objects({ Headers: ['new Headers()'] });
-      idlsArray.test();
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/idl.any.js b/fetch/api/idl.any.js
new file mode 100644
index 0000000..cae5ca3
--- /dev/null
+++ b/fetch/api/idl.any.js
@@ -0,0 +1,18 @@
+// META: global=window,worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+promise_test(async() => {
+  const text = await (await fetch("/interfaces/fetch.idl")).text();
+  const idl_array = new IdlArray();
+  idl_array.add_idls(text);
+  idl_array.add_untested_idls("[Exposed=(Window,Worker)] interface AbortSignal {};");
+  idl_array.add_untested_idls("[Exposed=(Window,Worker)] interface ReadableStream {};");
+  idl_array.add_untested_idls("enum ReferrerPolicy {};");
+  idl_array.add_objects({
+    Headers: ["new Headers()"],
+    Request: ["new Request('about:blank')"],
+    Response: ["new Response()"],
+  });
+  idl_array.test();
+}, "Fetch Standard IDL");
diff --git a/fetch/api/redirect/redirect-count-worker.html b/fetch/api/redirect/redirect-count-worker.html
deleted file mode 100644
index 0f624e8..0000000
--- a/fetch/api/redirect/redirect-count-worker.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: rediraction loop</title>
-    <meta name="timeout" content="long">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("redirect-count.js"));
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-count.any.js b/fetch/api/redirect/redirect-count.any.js
new file mode 100644
index 0000000..7fa6dd1
--- /dev/null
+++ b/fetch/api/redirect/redirect-count.any.js
@@ -0,0 +1,40 @@
+// META: script=../resources/utils.js
+// META: script=/common/utils.js
+
+function redirectCount(desc, redirectUrl, redirectLocation, redirectStatus, maxCount, shouldPass) {
+  var uuid_token = token();
+
+  var urlParameters = "?token=" + uuid_token + "&max_age=0";
+  urlParameters += "&redirect_status=" + redirectStatus;
+  urlParameters += "&max_count=" + maxCount;
+  if (redirectLocation)
+    urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+  var url = redirectUrl;
+  var requestInit = {"redirect": "follow"};
+
+  promise_test(function(test) {
+    return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+      assert_equals(resp.status, 200, "Clean stash response's status is 200");
+
+      if (!shouldPass)
+        return promise_rejects(test, new TypeError(), fetch(url + urlParameters, requestInit));
+
+      return fetch(url + urlParameters, requestInit).then(function(resp) {
+        assert_equals(resp.status, 200, "Response's status is 200");
+        return resp.text();
+      }).then(function(body) {
+        assert_equals(body, maxCount.toString(), "Redirected " + maxCount + " times");
+      });
+    });
+  }, desc);
+}
+
+var redirUrl = RESOURCES_DIR + "redirect.py";
+
+for (var statusCode of [301, 302, 303, 307, 308]) {
+  redirectCount("Redirect " + statusCode + " 20 times", redirUrl, redirUrl, statusCode, 20, true);
+  redirectCount("Redirect " + statusCode + " 21 times", redirUrl, redirUrl, statusCode, 21, false);
+}
+
+done();
diff --git a/fetch/api/redirect/redirect-count.html b/fetch/api/redirect/redirect-count.html
deleted file mode 100644
index d6a66e6..0000000
--- a/fetch/api/redirect/redirect-count.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: redirection loop</title>
-    <meta name="timeout" content="long">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="/common/utils.js"></script>
-    <script src="../resources/utils.js"></script>
-    <script src="redirect-count.js"></script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-count.js b/fetch/api/redirect/redirect-count.js
deleted file mode 100644
index 0103280..0000000
--- a/fetch/api/redirect/redirect-count.js
+++ /dev/null
@@ -1,43 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-  importScripts("/common/utils.js");
-}
-
-function redirectCount(desc, redirectUrl, redirectLocation, redirectStatus, maxCount, shouldPass) {
-  var uuid_token = token();
-
-  var urlParameters = "?token=" + uuid_token + "&max_age=0";
-  urlParameters += "&redirect_status=" + redirectStatus;
-  urlParameters += "&max_count=" + maxCount;
-  if (redirectLocation)
-    urlParameters += "&location=" + encodeURIComponent(redirectLocation);
-
-  var url = redirectUrl;
-  var requestInit = {"redirect": "follow"};
-
-  promise_test(function(test) {
-    return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
-      assert_equals(resp.status, 200, "Clean stash response's status is 200");
-
-      if (!shouldPass)
-        return promise_rejects(test, new TypeError(), fetch(url + urlParameters, requestInit));
-
-      return fetch(url + urlParameters, requestInit).then(function(resp) {
-        assert_equals(resp.status, 200, "Response's status is 200");
-        return resp.text();
-      }).then(function(body) {
-        assert_equals(body, maxCount.toString(), "Redirected " + maxCount + " times");
-      });
-    });
-  }, desc);
-}
-
-var redirUrl = RESOURCES_DIR + "redirect.py";
-
-for (var statusCode of [301, 302, 303, 307, 308]) {
-  redirectCount("Redirect " + statusCode + " 20 times", redirUrl, redirUrl, statusCode, 20, true);
-  redirectCount("Redirect " + statusCode + " 21 times", redirUrl, redirUrl, statusCode, 21, false);
-}
-
-done();
diff --git a/fetch/api/redirect/redirect-empty-location.any.js b/fetch/api/redirect/redirect-empty-location.any.js
new file mode 100644
index 0000000..ace8f22
--- /dev/null
+++ b/fetch/api/redirect/redirect-empty-location.any.js
@@ -0,0 +1,20 @@
+// META: script=../resources/utils.js
+
+// Tests receiving a redirect response with a Location header with an empty
+// value.
+
+const url = RESOURCES_DIR + 'redirect-empty-location.py';
+
+promise_test(t => {
+  return promise_rejects(t, new TypeError(), fetch(url, {redirect:'follow'}));
+}, 'redirect response with empty Location, follow mode');
+
+promise_test(t => {
+  return fetch(url, {redirect:'manual'})
+    .then(resp => {
+      assert_equals(resp.type, 'opaqueredirect');
+      assert_equals(resp.status, 0);
+    });
+}, 'redirect response with empty Location, manual mode');
+
+done();
diff --git a/fetch/api/redirect/redirect-location-worker.html b/fetch/api/redirect/redirect-location-worker.html
deleted file mode 100644
index e297081..0000000
--- a/fetch/api/redirect/redirect-location-worker.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: handling Location header during redirection</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("redirect-location.js"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/redirect/redirect-location.any.js b/fetch/api/redirect/redirect-location.any.js
new file mode 100644
index 0000000..27baefc
--- /dev/null
+++ b/fetch/api/redirect/redirect-location.any.js
@@ -0,0 +1,47 @@
+// META: script=../resources/utils.js
+
+function redirectLocation(desc, redirectUrl, redirectLocation, redirectStatus, redirectMode, shouldPass) {
+  var url = redirectUrl;
+  var urlParameters = "?redirect_status=" + redirectStatus;
+  if (redirectLocation)
+    urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+  var requestInit = {"redirect": redirectMode};
+
+  promise_test(function(test) {
+    if (redirectMode === "error" || !shouldPass)
+      return promise_rejects(test, new TypeError(), fetch(url + urlParameters, requestInit));
+    if (redirectMode === "manual")
+      return fetch(url + urlParameters, requestInit).then(function(resp) {
+        assert_equals(resp.status, 0, "Response's status is 0");
+        assert_equals(resp.type, "opaqueredirect", "Response's type is opaqueredirect");
+        assert_equals(resp.statusText, "", "Response's statusText is \"\"");
+        assert_true(resp.headers.entries().next().done, "Headers should be empty");
+      });
+
+    if (redirectMode === "follow")
+      return fetch(url + urlParameters, requestInit).then(function(resp) {
+        assert_equals(resp.status, redirectStatus, "Response's status is " + redirectStatus);
+      });
+    assert_unreached(redirectMode + " is not a valid redirect mode");
+  }, desc);
+}
+
+var redirUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = "top.txt";
+var invalidLocationUrl = "invalidurl:";
+var dataLocationUrl = "data:,data%20url";
+// FIXME: We may want to mix redirect-mode and cors-mode.
+// FIXME: Add tests for "error" redirect-mode.
+for (var statusCode of [301, 302, 303, 307, 308]) {
+  redirectLocation("Redirect " + statusCode + " in \"follow\" mode without location", redirUrl, undefined, statusCode, "follow", true);
+  redirectLocation("Redirect " + statusCode + " in \"manual\" mode without location", redirUrl, undefined, statusCode, "manual", true);
+
+  redirectLocation("Redirect " + statusCode + " in \"follow\" mode with invalid location", redirUrl, invalidLocationUrl, statusCode, "follow", false);
+  redirectLocation("Redirect " + statusCode + " in \"manual\" mode with invalid location", redirUrl, invalidLocationUrl, statusCode, "manual", true);
+
+  redirectLocation("Redirect " + statusCode + " in \"follow\" mode with data location", redirUrl, dataLocationUrl, statusCode, "follow", false);
+  redirectLocation("Redirect " + statusCode + " in \"manual\" mode with data location", redirUrl, dataLocationUrl, statusCode, "manual", true);
+}
+
+done();
diff --git a/fetch/api/redirect/redirect-location.html b/fetch/api/redirect/redirect-location.html
deleted file mode 100644
index ac35dea..0000000
--- a/fetch/api/redirect/redirect-location.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: handling Location header during redirection</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="redirect-location.js"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/redirect/redirect-location.js b/fetch/api/redirect/redirect-location.js
deleted file mode 100644
index cb038a6..0000000
--- a/fetch/api/redirect/redirect-location.js
+++ /dev/null
@@ -1,50 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function redirectLocation(desc, redirectUrl, redirectLocation, redirectStatus, redirectMode, shouldPass) {
-  var url = redirectUrl;
-  var urlParameters = "?redirect_status=" + redirectStatus;
-  if (redirectLocation)
-    urlParameters += "&location=" + encodeURIComponent(redirectLocation);
-
-  var requestInit = {"redirect": redirectMode};
-
-  promise_test(function(test) {
-    if (redirectMode === "error" || !shouldPass)
-      return promise_rejects(test, new TypeError(), fetch(url + urlParameters, requestInit));
-    if (redirectMode === "manual")
-      return fetch(url + urlParameters, requestInit).then(function(resp) {
-        assert_equals(resp.status, 0, "Response's status is 0");
-        assert_equals(resp.type, "opaqueredirect", "Response's type is opaqueredirect");
-        assert_equals(resp.statusText, "", "Response's statusText is \"\"");
-        assert_true(resp.headers.entries().next().done, "Headers should be empty");
-      });
-
-    if (redirectMode === "follow")
-      return fetch(url + urlParameters, requestInit).then(function(resp) {
-        assert_equals(resp.status, redirectStatus, "Response's status is " + redirectStatus);
-      });
-    assert_unreached(redirectMode + " is not a valid redirect mode");
-  }, desc);
-}
-
-var redirUrl = RESOURCES_DIR + "redirect.py";
-var locationUrl = "top.txt";
-var invalidLocationUrl = "#invalidurl:";
-var dataLocationUrl = "data:,data%20url";
-// FIXME: We may want to mix redirect-mode and cors-mode.
-// FIXME: Add tests for "error" redirect-mode.
-for (var statusCode of [301, 302, 303, 307, 308]) {
-  redirectLocation("Redirect " + statusCode + " in \"follow\" mode without location", redirUrl, undefined, statusCode, "follow", true);
-  redirectLocation("Redirect " + statusCode + " in \"manual\" mode without location", redirUrl, undefined, statusCode, "manual", true);
-
-  redirectLocation("Redirect " + statusCode + " in \"follow\" mode with invalid location", redirUrl, invalidLocationUrl, statusCode, "follow", false);
-  redirectLocation("Redirect " + statusCode + " in \"manual\" mode with invalid location", redirUrl, invalidLocationUrl, statusCode, "manual", true);
-
-  redirectLocation("Redirect " + statusCode + " in \"follow\" mode with data location", redirUrl, dataLocationUrl, statusCode, "follow", false);
-  redirectLocation("Redirect " + statusCode + " in \"manual\" mode with data location", redirUrl, dataLocationUrl, statusCode, "manual", true);
-}
-
-done();
diff --git a/fetch/api/redirect/redirect-method-worker.html b/fetch/api/redirect/redirect-method-worker.html
deleted file mode 100644
index fc0bc5e..0000000
--- a/fetch/api/redirect/redirect-method-worker.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: request method handling when redirected</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("redirect-method.js"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/redirect/redirect-method.any.js b/fetch/api/redirect/redirect-method.any.js
new file mode 100644
index 0000000..056ccf8
--- /dev/null
+++ b/fetch/api/redirect/redirect-method.any.js
@@ -0,0 +1,68 @@
+// META: script=../resources/utils.js
+
+// Creates a promise_test that fetches a URL that returns a redirect response.
+//
+// |opts| has additional options:
+// |opts.body|: the request body as a string or blob (default is empty body)
+// |opts.expectedBodyAsString|: the expected response body as a string. The
+// server is expected to echo the request body. The default is the empty string
+// if the request after redirection isn't POST; otherwise it's |opts.body|.
+function redirectMethod(desc, redirectUrl, redirectLocation, redirectStatus, method, expectedMethod, opts) {
+  var url = redirectUrl;
+  var urlParameters = "?redirect_status=" + redirectStatus;
+  urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+  var requestInit = {"method": method, "redirect": "follow"};
+  opts = opts || {};
+  if (opts.body)
+    requestInit.body = opts.body;
+
+  promise_test(function(test) {
+    return fetch(url + urlParameters, requestInit).then(function(resp) {
+      assert_equals(resp.status, 200, "Response's status is 200");
+      assert_equals(resp.type, "basic", "Response's type basic");
+      assert_equals(resp.headers.get("x-request-method"), expectedMethod, "Request method after redirection is " + expectedMethod);
+      assert_true(resp.redirected);
+      return resp.text().then(function(text) {
+        let expectedBody = "";
+        if (expectedMethod == "POST")
+          expectedBody = opts.expectedBodyAsString || requestInit.body;
+        assert_equals(text, expectedBody, "request body");
+      });
+    });
+  }, desc);
+}
+
+promise_test(function(test) {
+  assert_false(new Response().redirected);
+  return fetch(RESOURCES_DIR + "method.py").then(function(resp) {
+    assert_equals(resp.status, 200, "Response's status is 200");
+    assert_false(resp.redirected);
+  });
+}, "Response.redirected should be false on not-redirected responses");
+
+var redirUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = "method.py";
+
+const stringBody = "this is my body";
+const blobBody = new Blob(["it's me the blob!", " ", "and more blob!"]);
+const blobBodyAsString = "it's me the blob! and more blob!";
+
+redirectMethod("Redirect 301 with GET", redirUrl, locationUrl, 301, "GET", "GET");
+redirectMethod("Redirect 301 with POST", redirUrl, locationUrl, 301, "POST", "GET", { body: stringBody });
+redirectMethod("Redirect 301 with HEAD", redirUrl, locationUrl, 301, "HEAD", "HEAD");
+
+redirectMethod("Redirect 302 with GET", redirUrl, locationUrl, 302, "GET", "GET");
+redirectMethod("Redirect 302 with POST", redirUrl, locationUrl, 302, "POST", "GET", { body: stringBody });
+redirectMethod("Redirect 302 with HEAD", redirUrl, locationUrl, 302, "HEAD", "HEAD");
+
+redirectMethod("Redirect 303 with GET", redirUrl, locationUrl, 303, "GET", "GET");
+redirectMethod("Redirect 303 with POST", redirUrl, locationUrl, 303, "POST", "GET", { body: stringBody });
+redirectMethod("Redirect 303 with HEAD", redirUrl, locationUrl, 303, "HEAD", "HEAD");
+
+redirectMethod("Redirect 307 with GET", redirUrl, locationUrl, 307, "GET", "GET");
+redirectMethod("Redirect 307 with POST (string body)", redirUrl, locationUrl, 307, "POST", "POST", { body: stringBody });
+redirectMethod("Redirect 307 with POST (blob body)", redirUrl, locationUrl, 307, "POST", "POST", { body: blobBody, expectedBodyAsString: blobBodyAsString });
+redirectMethod("Redirect 307 with HEAD", redirUrl, locationUrl, 307, "HEAD", "HEAD");
+
+done();
diff --git a/fetch/api/redirect/redirect-method.html b/fetch/api/redirect/redirect-method.html
deleted file mode 100644
index 028842d..0000000
--- a/fetch/api/redirect/redirect-method.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: request method handling when redirected</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="../resources/utils.js"></script>
-    <script src="redirect-method.js"></script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/redirect/redirect-method.js b/fetch/api/redirect/redirect-method.js
deleted file mode 100644
index 13433a1..0000000
--- a/fetch/api/redirect/redirect-method.js
+++ /dev/null
@@ -1,55 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-}
-
-function redirectMethod(desc, redirectUrl, redirectLocation, redirectStatus, method, expectedMethod) {
-  var url = redirectUrl;
-  var urlParameters = "?redirect_status=" + redirectStatus;
-  urlParameters += "&location=" + encodeURIComponent(redirectLocation);
-
-  var requestInit = {"method": method, "redirect": "follow"};
-  if (method != "GET" && method != "HEAD")
-    requestInit.body = "this is my body";
-
-  promise_test(function(test) {
-    return fetch(url + urlParameters, requestInit).then(function(resp) {
-      assert_equals(resp.status, 200, "Response's status is 200");
-      assert_equals(resp.type, "basic", "Response's type basic");
-      assert_equals(resp.headers.get("x-request-method"), expectedMethod, "Request method after redirection is " + expectedMethod);
-      assert_true(resp.redirected);
-      return resp.text().then(function(text) {
-        assert_equals(text, expectedMethod == "POST" ? requestInit.body : "");
-      });
-    });
-  }, desc);
-}
-
-promise_test(function(test) {
-  assert_false(new Response().redirected);
-  return fetch(RESOURCES_DIR + "method.py").then(function(resp) {
-    assert_equals(resp.status, 200, "Response's status is 200");
-    assert_false(resp.redirected);
-  });
-}, "Response.redirected should be false on not-redirected responses");
-
-var redirUrl = RESOURCES_DIR + "redirect.py";
-var locationUrl = "method.py";
-
-redirectMethod("Redirect 301 with GET", redirUrl, locationUrl, 301, "GET", "GET");
-redirectMethod("Redirect 301 with POST", redirUrl, locationUrl, 301, "POST", "GET");
-redirectMethod("Redirect 301 with HEAD", redirUrl, locationUrl, 301, "HEAD", "HEAD");
-
-redirectMethod("Redirect 302 with GET", redirUrl, locationUrl, 302, "GET", "GET");
-redirectMethod("Redirect 302 with POST", redirUrl, locationUrl, 302, "POST", "GET");
-redirectMethod("Redirect 302 with HEAD", redirUrl, locationUrl, 302, "HEAD", "HEAD");
-
-redirectMethod("Redirect 303 with GET", redirUrl, locationUrl, 303, "GET", "GET");
-redirectMethod("Redirect 303 with POST", redirUrl, locationUrl, 303, "POST", "GET");
-redirectMethod("Redirect 303 with HEAD", redirUrl, locationUrl, 303, "HEAD", "HEAD");
-
-redirectMethod("Redirect 307 with GET", redirUrl, locationUrl, 307, "GET", "GET");
-redirectMethod("Redirect 307 with POST", redirUrl, locationUrl, 307, "POST", "POST");
-redirectMethod("Redirect 307 with HEAD", redirUrl, locationUrl, 307, "HEAD", "HEAD");
-
-done();
diff --git a/fetch/api/redirect/redirect-mode-worker.html b/fetch/api/redirect/redirect-mode-worker.html
deleted file mode 100644
index 32d219f..0000000
--- a/fetch/api/redirect/redirect-mode-worker.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: redirect mode handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("redirect-mode.js"));
-    </script>
-  </body>
-</html>
\ No newline at end of file
diff --git a/fetch/api/redirect/redirect-mode.any.js b/fetch/api/redirect/redirect-mode.any.js
new file mode 100644
index 0000000..7d34fa6
--- /dev/null
+++ b/fetch/api/redirect/redirect-mode.any.js
@@ -0,0 +1,49 @@
+// META: script=/common/get-host-info.sub.js
+
+var redirectLocation = "cors-top.txt";
+
+function testRedirect(origin, redirectStatus, redirectMode, corsMode) {
+  var url = new URL("../resources/redirect.py", self.location);
+  if (origin === "cross-origin") {
+    url.host = get_host_info().REMOTE_HOST;
+  }
+
+  var urlParameters = "?redirect_status=" + redirectStatus;
+  urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+  var requestInit = {redirect: redirectMode, mode: corsMode};
+
+  promise_test(function(test) {
+    if (redirectMode === "error" ||
+        (corsMode === "no-cors" && redirectMode !== "follow" && origin !== "same-origin"))
+      return promise_rejects(test, new TypeError(), fetch(url + urlParameters, requestInit));
+    if (redirectMode === "manual")
+      return fetch(url + urlParameters, requestInit).then(function(resp) {
+        assert_equals(resp.status, 0, "Response's status is 0");
+        assert_equals(resp.type, "opaqueredirect", "Response's type is opaqueredirect");
+        assert_equals(resp.statusText, "", "Response's statusText is \"\"");
+        assert_equals(resp.url, url + urlParameters, "Response URL should be the original one");
+      });
+    if (redirectMode === "follow")
+      return fetch(url + urlParameters, requestInit).then(function(resp) {
+        if (corsMode !== "no-cors" || origin === "same-origin") {
+          assert_true(new URL(resp.url).pathname.endsWith(redirectLocation), "Response's url should be the redirected one");
+          assert_equals(resp.status, 200, "Response's status is 200");
+        } else {
+          assert_equals(resp.type, "opaque", "Response is opaque");
+        }
+      });
+    assert_unreached(redirectMode + " is no a valid redirect mode");
+  }, origin + " redirect " + redirectStatus + " in " + redirectMode + " redirect and " + corsMode + " mode");
+}
+
+for (var origin of ["same-origin", "cross-origin"]) {
+  for (var statusCode of [301, 302, 303, 307, 308]) {
+    for (var redirect of ["error", "manual", "follow"]) {
+      for (var mode of ["cors", "no-cors"])
+        testRedirect(origin, statusCode, redirect, mode);
+    }
+  }
+}
+
+done();
diff --git a/fetch/api/redirect/redirect-mode.html b/fetch/api/redirect/redirect-mode.html
deleted file mode 100644
index 20a0cd6..0000000
--- a/fetch/api/redirect/redirect-mode.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: redirect mode handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="redirect-mode.js"></script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-mode.js b/fetch/api/redirect/redirect-mode.js
deleted file mode 100644
index b59a8d5..0000000
--- a/fetch/api/redirect/redirect-mode.js
+++ /dev/null
@@ -1,41 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/resources/testharness.js");
-  importScripts("/common/get-host-info.sub.js")
-}
-
-function redirectMode(desc, redirectUrl, redirectLocation, redirectStatus, redirectMode) {
-  var url = redirectUrl;
-  var urlParameters = "?redirect_status=" + redirectStatus;
-  urlParameters += "&location=" + encodeURIComponent(redirectLocation);
-
-  var requestInit = {"redirect": redirectMode};
-
-  promise_test(function(test) {
-    if (redirectMode === "error")
-      return promise_rejects(test, new TypeError(), fetch(url + urlParameters, requestInit));
-    if (redirectMode === "manual")
-      return fetch(url + urlParameters, requestInit).then(function(resp) {
-        assert_equals(resp.status, 0, "Response's status is 0");
-        assert_equals(resp.type, "opaqueredirect", "Response's type is opaqueredirect");
-        assert_equals(resp.statusText, "", "Response's statusText is \"\"");
-        assert_equals(resp.url, url + urlParameters, "Response URL should be the original one");
-      });
-    if (redirectMode === "follow")
-      return fetch(url + urlParameters, requestInit).then(function(resp) {
-        assert_true(new URL(resp.url).pathname.endsWith(locationUrl), "Response's url should be the redirected one");
-        assert_equals(resp.status, 200, "Response's status is 200");
-      });
-    assert_unreached(redirectMode + " is no a valid redirect mode");
-  }, desc);
-}
-
-var redirUrl = get_host_info().HTTP_ORIGIN + "/fetch/api/resources/redirect.py";
-var locationUrl = "top.txt";
-
-for (var statusCode of [301, 302, 303, 307, 308]) {
-  redirectMode("Redirect " + statusCode + " in \"error\" mode ", redirUrl, locationUrl, statusCode, "error");
-  redirectMode("Redirect " + statusCode + " in \"follow\" mode ", redirUrl, locationUrl, statusCode, "follow");
-  redirectMode("Redirect " + statusCode + " in \"manual\" mode ", redirUrl, locationUrl, statusCode, "manual");
-}
-
-done();
diff --git a/fetch/api/redirect/redirect-origin-worker.html b/fetch/api/redirect/redirect-origin-worker.html
deleted file mode 100644
index fdb5422..0000000
--- a/fetch/api/redirect/redirect-origin-worker.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: redirect mode handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("redirect-origin.js"));
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-origin.any.js b/fetch/api/redirect/redirect-origin.any.js
new file mode 100644
index 0000000..3edb1bd
--- /dev/null
+++ b/fetch/api/redirect/redirect-origin.any.js
@@ -0,0 +1,37 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function testOriginAfterRedirection(desc, redirectUrl, redirectLocation, redirectStatus, expectedOrigin) {
+    var uuid_token = token();
+    var url = redirectUrl;
+    var urlParameters = "?token=" + uuid_token + "&max_age=0";
+    urlParameters += "&redirect_status=" + redirectStatus;
+    urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+    var requestInit = {"mode": "cors", "redirect": "follow"};
+
+    promise_test(function(test) {
+        return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+            assert_equals(resp.status, 200, "Clean stash response's status is 200");
+            return fetch(url + urlParameters, requestInit).then(function(response) {
+                assert_equals(response.status, 200, "Inspect header response's status is 200");
+                assert_equals(response.headers.get("x-request-origin"), expectedOrigin, "Check origin header");
+            });
+        });
+    }, desc);
+}
+
+var redirectUrl = RESOURCES_DIR + "redirect.py";
+var corsRedirectUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
+var locationUrl =  get_host_info().HTTP_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?headers=origin";
+var corsLocationUrl =  get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=origin";
+
+for (var code of [301, 302, 303, 307, 308]) {
+    testOriginAfterRedirection("Same origin to same origin redirection " + code, redirectUrl, locationUrl, code, null);
+    testOriginAfterRedirection("Same origin to other origin redirection " + code, redirectUrl, corsLocationUrl, code, get_host_info().HTTP_ORIGIN);
+    testOriginAfterRedirection("Other origin to other origin redirection " + code, corsRedirectUrl, corsLocationUrl, code, get_host_info().HTTP_ORIGIN);
+    testOriginAfterRedirection("Other origin to same origin redirection " + code, corsRedirectUrl, locationUrl + "&cors", code, "null");
+}
+
+done();
diff --git a/fetch/api/redirect/redirect-origin.html b/fetch/api/redirect/redirect-origin.html
deleted file mode 100644
index 4cbe1c0..0000000
--- a/fetch/api/redirect/redirect-origin.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: redirect mode handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="/common/utils.js"></script>
-    <script src="../resources/utils.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="redirect-origin.js"></script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-origin.js b/fetch/api/redirect/redirect-origin.js
deleted file mode 100644
index 77f841e..0000000
--- a/fetch/api/redirect/redirect-origin.js
+++ /dev/null
@@ -1,40 +0,0 @@
-if (this.document === undefined) {
-    importScripts("/common/utils.js");
-    importScripts("/resources/testharness.js");
-    importScripts("../resources/utils.js");
-    importScripts("/common/get-host-info.sub.js");
-}
-
-function testOriginAfterRedirection(desc, redirectUrl, redirectLocation, redirectStatus, expectedOrigin) {
-    var uuid_token = token();
-    var url = redirectUrl;
-    var urlParameters = "?token=" + uuid_token + "&max_age=0";
-    urlParameters += "&redirect_status=" + redirectStatus;
-    urlParameters += "&location=" + encodeURIComponent(redirectLocation);
-
-    var requestInit = {"mode": "cors", "redirect": "follow"};
-
-    promise_test(function(test) {
-        return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
-            assert_equals(resp.status, 200, "Clean stash response's status is 200");
-            return fetch(url + urlParameters, requestInit).then(function(response) {
-                assert_equals(response.status, 200, "Inspect header response's status is 200");
-                assert_equals(response.headers.get("x-request-origin"), expectedOrigin, "Check origin header");
-            });
-        });
-    }, desc);
-}
-
-var redirectUrl = RESOURCES_DIR + "redirect.py";
-var corsRedirectUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
-var locationUrl =  get_host_info().HTTP_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?headers=origin";
-var corsLocationUrl =  get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=origin";
-
-for (var code of [301, 302, 303, 307, 308]) {
-    testOriginAfterRedirection("Same origin to same origin redirection " + code, redirectUrl, locationUrl, code, null);
-    testOriginAfterRedirection("Same origin to other origin redirection " + code, redirectUrl, corsLocationUrl, code, get_host_info().HTTP_ORIGIN);
-    testOriginAfterRedirection("Other origin to other origin redirection " + code, corsRedirectUrl, corsLocationUrl, code, get_host_info().HTTP_ORIGIN);
-    testOriginAfterRedirection("Other origin to same origin redirection " + code, corsRedirectUrl, locationUrl + "&cors", code, "null");
-}
-
-done();
diff --git a/fetch/api/redirect/redirect-referrer-worker.html b/fetch/api/redirect/redirect-referrer-worker.html
deleted file mode 100644
index 6e5cd45..0000000
--- a/fetch/api/redirect/redirect-referrer-worker.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: redirect referrer handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-redirect-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("redirect-referrer.js"));
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-referrer.any.js b/fetch/api/redirect/redirect-referrer.any.js
new file mode 100644
index 0000000..92f0b9b
--- /dev/null
+++ b/fetch/api/redirect/redirect-referrer.any.js
@@ -0,0 +1,65 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function testReferrerAfterRedirection(desc, redirectUrl, redirectLocation, referrerPolicy, redirectReferrerPolicy, expectedReferrer) {
+  var url = redirectUrl;
+  var urlParameters = "?location=" + encodeURIComponent(redirectLocation);
+
+  if (redirectReferrerPolicy)
+    urlParameters += "&redirect_referrerpolicy=" + redirectReferrerPolicy;
+
+  var requestInit = {"redirect": "follow", "referrerPolicy": referrerPolicy};
+
+    promise_test(function(test) {
+      return fetch(url + urlParameters, requestInit).then(function(response) {
+        assert_equals(response.status, 200, "Inspect header response's status is 200");
+        assert_equals(response.headers.get("x-request-referer"), expectedReferrer ? expectedReferrer : null, "Check referrer header");
+      });
+    }, desc);
+}
+
+var referrerOrigin = get_host_info().HTTP_ORIGIN + "/";
+var referrerUrl = location.href;
+
+var redirectUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = get_host_info().HTTP_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?headers=referer";
+var crossLocationUrl =  get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=referer";
+
+testReferrerAfterRedirection("Same origin redirection, empty init, unsafe-url redirect header ", redirectUrl, locationUrl, "", "unsafe-url", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, locationUrl, "", "no-referrer-when-downgrade", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, same-origin redirect header ", redirectUrl, locationUrl, "", "same-origin", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, origin redirect header ", redirectUrl, locationUrl, "", "origin", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty init, origin-when-cross-origin redirect header ", redirectUrl, locationUrl, "", "origin-when-cross-origin", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, no-referrer redirect header ", redirectUrl, locationUrl, "", "no-referrer", null);
+testReferrerAfterRedirection("Same origin redirection, empty init, strict-origin redirect header ", redirectUrl, locationUrl, "", "strict-origin", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, locationUrl, "", "strict-origin-when-cross-origin", referrerUrl);
+
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, unsafe-url init ", redirectUrl, locationUrl, "unsafe-url", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, locationUrl, "no-referrer-when-downgrade", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, same-origin init ", redirectUrl, locationUrl, "same-origin", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, origin init ", redirectUrl, locationUrl, "origin", "", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, origin-when-cross-origin init ", redirectUrl, locationUrl, "origin-when-cross-origin", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, no-referrer init ", redirectUrl, locationUrl, "no-referrer", "", null);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, strict-origin init ", redirectUrl, locationUrl, "strict-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, locationUrl, "strict-origin-when-cross-origin", "", referrerUrl);
+
+testReferrerAfterRedirection("Cross origin redirection, empty init, unsafe-url redirect header ", redirectUrl, crossLocationUrl, "", "unsafe-url", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, crossLocationUrl, "", "no-referrer-when-downgrade", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty init, same-origin redirect header ", redirectUrl, crossLocationUrl, "", "same-origin", null);
+testReferrerAfterRedirection("Cross origin redirection, empty init, origin redirect header ", redirectUrl, crossLocationUrl, "", "origin", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty init, origin-when-cross-origin redirect header ", redirectUrl, crossLocationUrl, "", "origin-when-cross-origin", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty init, no-referrer redirect header ", redirectUrl, crossLocationUrl, "", "no-referrer", null);
+testReferrerAfterRedirection("Cross origin redirection, empty init, strict-origin redirect header ", redirectUrl, crossLocationUrl, "", "strict-origin", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, crossLocationUrl, "", "strict-origin-when-cross-origin", referrerOrigin);
+
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, unsafe-url init ", redirectUrl, crossLocationUrl, "unsafe-url", "", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, crossLocationUrl, "no-referrer-when-downgrade", "", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, same-origin init ", redirectUrl, crossLocationUrl, "same-origin", "", null);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, origin init ", redirectUrl, crossLocationUrl, "origin", "", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, origin-when-cross-origin init ", redirectUrl, crossLocationUrl, "origin-when-cross-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, no-referrer init ", redirectUrl, crossLocationUrl, "no-referrer", "", null);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, strict-origin init ", redirectUrl, crossLocationUrl, "strict-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, crossLocationUrl, "strict-origin-when-cross-origin", "", referrerOrigin);
+
+done();
diff --git a/fetch/api/redirect/redirect-referrer.html b/fetch/api/redirect/redirect-referrer.html
deleted file mode 100644
index 473b5e6..0000000
--- a/fetch/api/redirect/redirect-referrer.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: redirect referrer handling</title>
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
-    <meta name="help" href="https://fetch.spec.whatwg.org/#http-redirect-fetch">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="/common/utils.js"></script>
-    <script src="../resources/utils.js"></script>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="redirect-referrer.js"></script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-referrer.js b/fetch/api/redirect/redirect-referrer.js
deleted file mode 100644
index fabecea..0000000
--- a/fetch/api/redirect/redirect-referrer.js
+++ /dev/null
@@ -1,68 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/common/utils.js");
-  importScripts("/resources/testharness.js");
-  importScripts("../resources/utils.js");
-  importScripts("/common/get-host-info.sub.js");
-}
-
-function testReferrerAfterRedirection(desc, redirectUrl, redirectLocation, referrerPolicy, redirectReferrerPolicy, expectedReferrer) {
-  var url = redirectUrl;
-  var urlParameters = "?location=" + encodeURIComponent(redirectLocation);
-
-  if (redirectReferrerPolicy)
-    urlParameters += "&redirect_referrerpolicy=" + redirectReferrerPolicy;
-
-  var requestInit = {"redirect": "follow", "referrerPolicy": referrerPolicy};
-
-    promise_test(function(test) {
-      return fetch(url + urlParameters, requestInit).then(function(response) {
-        assert_equals(response.status, 200, "Inspect header response's status is 200");
-        assert_equals(response.headers.get("x-request-referer"), expectedReferrer ? expectedReferrer : null, "Check referrer header");
-      });
-    }, desc);
-}
-
-var referrerOrigin = get_host_info().HTTP_ORIGIN + "/";
-var referrerUrl = location.href;
-
-var redirectUrl = RESOURCES_DIR + "redirect.py";
-var locationUrl = get_host_info().HTTP_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?headers=referer";
-var crossLocationUrl =  get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=referer";
-
-testReferrerAfterRedirection("Same origin redirection, empty init, unsafe-url redirect header ", redirectUrl, locationUrl, "", "unsafe-url", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, locationUrl, "", "no-referrer-when-downgrade", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty init, same-origin redirect header ", redirectUrl, locationUrl, "", "same-origin", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty init, origin redirect header ", redirectUrl, locationUrl, "", "origin", referrerOrigin);
-testReferrerAfterRedirection("Same origin redirection, empty init, origin-when-cross-origin redirect header ", redirectUrl, locationUrl, "", "origin-when-cross-origin", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty init, no-referrer redirect header ", redirectUrl, locationUrl, "", "no-referrer", null);
-testReferrerAfterRedirection("Same origin redirection, empty init, strict-origin redirect header ", redirectUrl, locationUrl, "", "strict-origin", referrerOrigin);
-testReferrerAfterRedirection("Same origin redirection, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, locationUrl, "", "strict-origin-when-cross-origin", referrerUrl);
-
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, unsafe-url init ", redirectUrl, locationUrl, "unsafe-url", "", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, locationUrl, "no-referrer-when-downgrade", "", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, same-origin init ", redirectUrl, locationUrl, "same-origin", "", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, origin init ", redirectUrl, locationUrl, "origin", "", referrerOrigin);
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, origin-when-cross-origin init ", redirectUrl, locationUrl, "origin-when-cross-origin", "", referrerUrl);
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, no-referrer init ", redirectUrl, locationUrl, "no-referrer", "", null);
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, strict-origin init ", redirectUrl, locationUrl, "strict-origin", "", referrerOrigin);
-testReferrerAfterRedirection("Same origin redirection, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, locationUrl, "strict-origin-when-cross-origin", "", referrerUrl);
-
-testReferrerAfterRedirection("Cross origin redirection, empty init, unsafe-url redirect header ", redirectUrl, crossLocationUrl, "", "unsafe-url", referrerUrl);
-testReferrerAfterRedirection("Cross origin redirection, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, crossLocationUrl, "", "no-referrer-when-downgrade", referrerUrl);
-testReferrerAfterRedirection("Cross origin redirection, empty init, same-origin redirect header ", redirectUrl, crossLocationUrl, "", "same-origin", null);
-testReferrerAfterRedirection("Cross origin redirection, empty init, origin redirect header ", redirectUrl, crossLocationUrl, "", "origin", referrerOrigin);
-testReferrerAfterRedirection("Cross origin redirection, empty init, origin-when-cross-origin redirect header ", redirectUrl, crossLocationUrl, "", "origin-when-cross-origin", referrerOrigin);
-testReferrerAfterRedirection("Cross origin redirection, empty init, no-referrer redirect header ", redirectUrl, crossLocationUrl, "", "no-referrer", null);
-testReferrerAfterRedirection("Cross origin redirection, empty init, strict-origin redirect header ", redirectUrl, crossLocationUrl, "", "strict-origin", referrerOrigin);
-testReferrerAfterRedirection("Cross origin redirection, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, crossLocationUrl, "", "strict-origin-when-cross-origin", referrerOrigin);
-
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, unsafe-url init ", redirectUrl, crossLocationUrl, "unsafe-url", "", referrerUrl);
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, crossLocationUrl, "no-referrer-when-downgrade", "", referrerUrl);
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, same-origin init ", redirectUrl, crossLocationUrl, "same-origin", "", null);
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, origin init ", redirectUrl, crossLocationUrl, "origin", "", referrerOrigin);
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, origin-when-cross-origin init ", redirectUrl, crossLocationUrl, "origin-when-cross-origin", "", referrerOrigin);
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, no-referrer init ", redirectUrl, crossLocationUrl, "no-referrer", "", null);
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, strict-origin init ", redirectUrl, crossLocationUrl, "strict-origin", "", referrerOrigin);
-testReferrerAfterRedirection("Cross origin redirection, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, crossLocationUrl, "strict-origin-when-cross-origin", "", referrerOrigin);
-
-done();
diff --git a/fetch/api/redirect/redirect-to-dataurl-worker.html b/fetch/api/redirect/redirect-to-dataurl-worker.html
deleted file mode 100644
index 428f513..0000000
--- a/fetch/api/redirect/redirect-to-dataurl-worker.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch in worker: data URL loading after redirections</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script>
-      fetch_tests_from_worker(new Worker("redirect-to-dataurl.js"));
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-to-dataurl.any.js b/fetch/api/redirect/redirect-to-dataurl.any.js
new file mode 100644
index 0000000..68ff2c5
--- /dev/null
+++ b/fetch/api/redirect/redirect-to-dataurl.any.js
@@ -0,0 +1,27 @@
+// META: script=/common/get-host-info.sub.js
+
+var dataURL = "data:text/plain;base64,cmVzcG9uc2UncyBib2R5";
+var body = "response's body";
+var contentType = "text/plain";
+
+function redirectDataURL(desc, redirectUrl, mode) {
+    var url = redirectUrl +  "?cors&location=" + encodeURIComponent(dataURL);
+
+    var requestInit = {"mode": mode};
+
+    promise_test(function(test) {
+        return promise_rejects(test, new TypeError(), fetch(url, requestInit));
+    }, desc);
+}
+
+var redirUrl = get_host_info().HTTP_ORIGIN + "/fetch/api/resources/redirect.py";
+var corsRedirUrl = get_host_info().HTTP_REMOTE_ORIGIN + "/fetch/api/resources/redirect.py";
+
+redirectDataURL("Testing data URL loading after same-origin redirection (cors mode)", redirUrl, "cors");
+redirectDataURL("Testing data URL loading after same-origin redirection (no-cors mode)", redirUrl, "no-cors");
+redirectDataURL("Testing data URL loading after same-origin redirection (same-origin mode)", redirUrl, "same-origin");
+
+redirectDataURL("Testing data URL loading after cross-origin redirection (cors mode)", corsRedirUrl, "cors");
+redirectDataURL("Testing data URL loading after cross-origin redirection (no-cors mode)", corsRedirUrl, "no-cors");
+
+done();
diff --git a/fetch/api/redirect/redirect-to-dataurl.html b/fetch/api/redirect/redirect-to-dataurl.html
deleted file mode 100644
index ed7159f..0000000
--- a/fetch/api/redirect/redirect-to-dataurl.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Fetch: data URL loading after redirections</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-  </head>
-  <body>
-    <script src="/common/get-host-info.sub.js"></script>
-    <script src="redirect-to-dataurl.js"></script>
-  </body>
-</html>
diff --git a/fetch/api/redirect/redirect-to-dataurl.js b/fetch/api/redirect/redirect-to-dataurl.js
deleted file mode 100644
index c3bae3f..0000000
--- a/fetch/api/redirect/redirect-to-dataurl.js
+++ /dev/null
@@ -1,30 +0,0 @@
-if (this.document === undefined) {
-  importScripts("/common/get-host-info.sub.js")
-  importScripts("/resources/testharness.js");
-}
-
-var dataURL = "data:text/plain;base64,cmVzcG9uc2UncyBib2R5";
-var body = "response's body";
-var contentType = "text/plain";
-
-function redirectDataURL(desc, redirectUrl, mode) {
-    var url = redirectUrl +  "?cors&location=" + encodeURIComponent(dataURL);
-
-    var requestInit = {"mode": mode};
-
-    promise_test(function(test) {
-        return promise_rejects(test, new TypeError(), fetch(url, requestInit));
-    }, desc);
-}
-
-var redirUrl = get_host_info().HTTP_ORIGIN + "/fetch/api/resources/redirect.py";
-var corsRedirUrl = get_host_info().HTTP_REMOTE_ORIGIN + "/fetch/api/resources/redirect.py";
-
-redirectDataURL("Testing data URL loading after same-origin redirection (cors mode)", redirUrl, "cors");
-redirectDataURL("Testing data URL loading after same-origin redirection (no-cors mode)", redirUrl, "no-cors");
-redirectDataURL("Testing data URL loading after same-origin redirection (same-origin mode)", redirUrl, "same-origin");
-
-redirectDataURL("Testing data URL loading after cross-origin redirection (cors mode)", corsRedirUrl, "cors");
-redirectDataURL("Testing data URL loading after cross-origin redirection (no-cors mode)", corsRedirUrl, "no-cors");
-
-done();
diff --git a/fetch/api/request/destination/fetch-destination-iframe.https.html b/fetch/api/request/destination/fetch-destination-iframe.https.html
new file mode 100644
index 0000000..cb1e9d8
--- /dev/null
+++ b/fetch/api/request/destination/fetch-destination-iframe.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Fetch destination tests for resources with no load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+const kScope = 'resources/dummy.html?dest=document';
+
+// Set up the service worker and the frame.
+promise_test(t => {
+    const kScript = 'resources/fetch-destination-worker-iframe.js';
+    return service_worker_unregister_and_register(t, kScript, kScope)
+      .then(registration => {
+          add_completion_callback(() => {
+              registration.unregister();
+            });
+
+          return wait_for_state(t, registration.installing, 'activated');
+        });
+  }, 'Initialize global state');
+
+var waitOnMessageFromSW = async t => {
+    await new Promise((resolve, reject) => {
+        navigator.serviceWorker.onmessage = t.step_func(event => {
+            if (event.data == "PASS") {
+                resolve();
+            } else {
+                reject();
+            }
+        });
+    }).catch(() => {;
+        assert_unreached("Wrong destination.");
+    });
+    t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; });
+}
+
+// Document destination
+///////////////////////
+promise_test(async t => {
+    var f = document.createElement('iframe');
+    frame = f;
+    f.className = 'test-iframe';
+    f.src = kScope;
+    document.body.appendChild(f);
+    await waitOnMessageFromSW(t);
+    add_completion_callback(() => { f.remove(); });
+}, 'iframe fetches with a "document" Request.destination');
+
+</script>
diff --git a/fetch/api/request/destination/fetch-destination-no-load-event.https.html b/fetch/api/request/destination/fetch-destination-no-load-event.https.html
new file mode 100644
index 0000000..1778bf2
--- /dev/null
+++ b/fetch/api/request/destination/fetch-destination-no-load-event.https.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<title>Fetch destination tests for resources with no load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+
+// Set up the service worker and the frame.
+promise_test(t => {
+    const kScope = 'resources/';
+    const kFrame = 'resources/empty.https.html';
+    const kScript = 'resources/fetch-destination-worker-no-load-event.js';
+    return service_worker_unregister_and_register(t, kScript, kScope)
+      .then(registration => {
+          add_completion_callback(() => {
+              registration.unregister();
+            });
+
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(() => {
+          return with_iframe(kFrame);
+        })
+      .then(f => {
+          frame = f;
+          add_completion_callback(() => { f.remove(); });
+        });
+  }, 'Initialize global state');
+
+var waitOnMessageFromSW = async t => {
+    await new Promise((resolve, reject) => {
+        frame.contentWindow.navigator.serviceWorker.onmessage = t.step_func(event => {
+            if (event.data == "PASS") {
+                resolve();
+            } else {
+                reject();
+            }
+        });
+    }).catch(() => {;
+        assert_unreached("Wrong destination.");
+    });
+    t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; });
+}
+// Actual tests
+
+// Image destination
+////////////////////
+
+// CSS background image - image destination
+promise_test(async t => {
+    let node = frame.contentWindow.document.createElement("div");
+    node.style = "background-image: url(dummy.png?t=bg2&dest=image)";
+    frame.contentWindow.document.body.appendChild(node);
+
+    await waitOnMessageFromSW(t);
+}, 'Background image fetches with an "image" Request.destination');
+
+// Font destination
+///////////////////
+
+// Font loading API - font destination
+promise_test(async t => {
+    let font = new frame.contentWindow.FontFace("foo", "url(dummy.ttf?t=api&dest=font)");
+    font.load();
+
+    await waitOnMessageFromSW(t);
+}, 'Font loading API fetches with an "font" Request.destination');
+
+// CSS font - font destination
+promise_test(async t => {
+    let style = frame.contentWindow.document.createElement("style");
+    style.innerHTML = "@font-face { font-family: foo; src: url(dummy.ttf?t=css&dest=font); }";
+    style.innerHTML += "div {font-family: foo; }";
+    let div = frame.contentWindow.document.createElement("div");
+    div.innerHTML = "bar";
+    frame.contentWindow.document.body.appendChild(style);
+    frame.contentWindow.document.body.appendChild(div);
+
+    await waitOnMessageFromSW(t);
+}, 'CSS font fetches with an "font" Request.destination');
+
+// Empty string destination
+///////////////////////////
+
+// sendBeacon() - empty string destination
+promise_test(async t => {
+    frame.contentWindow.navigator.sendBeacon("dummy?t=beacon&dest=", "foobar");
+
+    await waitOnMessageFromSW(t);
+}, 'sendBeacon() fetches with an empty string Request.destination');
+
+// Cache.add() - empty string destination
+promise_test(async t => {
+    frame.contentWindow.caches.open("foo").then(cache => {
+        cache.add("dummy?t=cache&dest=");
+    });
+
+    await waitOnMessageFromSW(t);
+}, 'Cache.add() fetches with an empty string Request.destination');
+
+// script destination
+/////////////////////
+
+// importScripts() - script destination
+promise_test(async t => {
+    let worker = new frame.contentWindow.Worker("importer.js");
+
+    await waitOnMessageFromSW(t);
+}, 'importScripts() fetches with a "script" Request.destination');
+
+// style destination
+/////////////////////
+// @import - style destination
+promise_test(async t => {
+    let node = frame.contentWindow.document.createElement("style");
+    node.innerHTML = '@import url("dummy?t=import&dest=style")';
+    frame.contentWindow.document.body.appendChild(node);
+
+    await waitOnMessageFromSW(t);
+}, '@import fetches with a "style" Request.destination');
+
+</script>
diff --git a/fetch/api/request/destination/fetch-destination-worker.https.html b/fetch/api/request/destination/fetch-destination-worker.https.html
new file mode 100644
index 0000000..8421d4a
--- /dev/null
+++ b/fetch/api/request/destination/fetch-destination-worker.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Fetch destination tests for resources with no load event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+
+// Set up the service worker and the frame.
+promise_test(t => {
+    const kScope = 'resources/dummy.html';
+    const kScript = 'resources/fetch-destination-worker-no-load-event.js';
+    return service_worker_unregister_and_register(t, kScript, kScope)
+      .then(registration => {
+          add_completion_callback(() => {
+              registration.unregister();
+            });
+
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(() => {
+          return with_iframe(kScope);
+        })
+      .then(f => {
+          frame = f;
+          add_completion_callback(() => { f.remove(); });
+        });
+  }, 'Initialize global state');
+
+var waitOnMessageFromSW = async t => {
+    await new Promise((resolve, reject) => {
+        frame.contentWindow.navigator.serviceWorker.onmessage = t.step_func(event => {
+            if (event.data == "PASS") {
+                resolve();
+            } else {
+                reject();
+            }
+        });
+    }).catch(() => {;
+        assert_unreached("Wrong destination.");
+    });
+    t.add_cleanup(() => { frame.contentWindow.navigator.serviceWorker.onmessage = null; });
+}
+
+// worker destination
+/////////////////////
+promise_test(async t => {
+    // We can use an html file as we don't really care about the worker successfully loading.
+    let worker = new frame.contentWindow.Worker("dummy.html?t=worker&dest=worker");
+    await waitOnMessageFromSW(t);
+}, 'Worker fetches with a "worker" Request.destination');
+
+</script>
diff --git a/fetch/api/request/destination/fetch-destination.https.html b/fetch/api/request/destination/fetch-destination.https.html
new file mode 100644
index 0000000..5d9f717
--- /dev/null
+++ b/fetch/api/request/destination/fetch-destination.https.html
@@ -0,0 +1,442 @@
+<!DOCTYPE html>
+<title>Fetch destination tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/media.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+let frame;
+
+// Set up the service worker and the frame.
+promise_test(t => {
+    const kScope = 'resources/empty.https.html';
+    const kScript = 'resources/fetch-destination-worker.js';
+    return service_worker_unregister_and_register(t, kScript, kScope)
+      .then(registration => {
+          add_completion_callback(() => {
+              registration.unregister();
+            });
+
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(() => {
+          return with_iframe(kScope);
+        })
+      .then(f => {
+          frame = f;
+          add_completion_callback(() => { f.remove(); });
+        });
+  }, 'Initialize global state');
+
+// Actual tests
+
+// Image destination
+////////////////////
+
+// HTMLImageElement - image destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("img");
+      node.onload = resolve;
+      node.onerror = reject;
+      node.src = "dummy.png?dest=image";
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLImageElement fetches with an "image" Request.destination');
+
+// HTMLImageElement with srcset attribute - image destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("img");
+      node.onload = resolve;
+      node.onerror = reject;
+      node.srcset = "dummy.png?t=srcset&dest=image";
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLImageElement with srcset attribute fetches with an "image" Request.destination');
+
+// HTMLImageElement with srcset attribute - image destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let img = frame.contentWindow.document.createElement("img");
+      let picture = frame.contentWindow.document.createElement("picture");
+      let source = frame.contentWindow.document.createElement("source");
+      picture.appendChild(source);
+      picture.appendChild(img);
+      img.onload = resolve;
+      img.onerror = reject;
+      source.srcset = "dummy.png?t=picture&dest=image";
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLImageElement with a HTMLPictureElement parent attribute fetches with an "image" Request.destination');
+
+// SVGImageElement - image destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let svg = frame.contentWindow.document.createElementNS('http://www.w3.org/2000/svg','svg');
+      svg.setAttributeNS('http://www.w3.org/2000/svg','xlink','http://www.w3.org/1999/xlink');
+      let svgimg = frame.contentWindow.document.createElementNS('http://www.w3.org/2000/svg','image');
+      svgimg.onload = resolve;
+      svgimg.onerror = reject;
+      svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href','dummy.png?t=svg&dest=image');
+      svg.appendChild(svgimg);
+      frame.contentWindow.document.documentElement.appendChild(svg);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'SVGImageElement fetches with an "image" Request.destination');
+
+// Empty string destination
+///////////////////////////
+
+// fetch() - empty string destination
+promise_test(async t => {
+  let response = await frame.contentWindow.fetch("dummy?dest=");
+  assert_true(response.ok);
+}, 'fetch() fetches with an empty string Request.destination');
+
+// XMLHttpRequest - empty string destination
+promise_test(async t => {
+  let xhr;
+  await new Promise((resolve, reject) => {
+      xhr = new frame.contentWindow.XMLHttpRequest();
+      xhr.onload = resolve;
+      xhr.onerror = reject;
+      xhr.open("GET", "dummy?t=xhr&dest=");
+      xhr.send();
+    }).catch(() => {
+      assert_unreached("Fetch errored.");
+    });
+  assert_equals(xhr.status, 200);
+}, 'XMLHttpRequest() fetches with an empty string Request.destination');
+
+// EventSource - empty string destination
+promise_test(async t => {
+  let xhr;
+  await new Promise((resolve, reject) => {
+      eventSource = new frame.contentWindow.EventSource("dummy.es?t=eventsource&dest=");
+      eventSource.onopen = resolve;
+      eventSource.onerror = reject;
+    }).catch(() => {
+      assert_unreached("Fetch errored.");
+    });
+}, 'EventSource() fetches with an empty string Request.destination');
+
+// HTMLAudioElement - audio destination
+///////////////////////////////////////
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let audioURL = getAudioURI("dummy_audio");
+      let node = frame.contentWindow.document.createElement("audio");
+      node.onloadeddata = resolve;
+      node.onerror = reject;
+      node.src = audioURL + "?dest=audio";
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLAudioElement fetches with an "audio" Request.destination');
+
+// HTMLVideoElement - video destination
+///////////////////////////////////////
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let videoURL = getVideoURI("dummy_video");
+      let node = frame.contentWindow.document.createElement("video");
+      node.onloadeddata = resolve;
+      node.onerror = reject;
+      node.src = videoURL + "?dest=video";
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLVideoElement fetches with a "video" Request.destination');
+
+// script destinations
+//////////////////////
+
+// HTMLScriptElement - script destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("script");
+      node.onload = resolve;
+      node.onerror = reject;
+      node.src = "dummy?dest=script";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLScriptElement fetches with a "script" Request.destination');
+
+// Style destination
+////////////////////
+
+// HTMLLinkElement with rel=stylesheet - style destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "stylesheet";
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=style";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=stylesheet fetches with a "style" Request.destination');
+
+// Preload tests
+////////////////
+// HTMLLinkElement with rel=preload and as=fetch - empty string destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "fetch";
+      if (node.as != "fetch") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?t=2&dest=";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=fetch fetches with an empty string Request.destination');
+
+// HTMLLinkElement with rel=preload and as=style - style destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "style";
+      if (node.as != "style") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?t=2&dest=style";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=style fetches with a "style" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=script - script destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "script";
+      if (node.as != "script") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?t=2&dest=script";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=script fetches with a "script" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=font - font destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "font";
+      if (node.as != "font") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?t=2&dest=font";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=font fetches with a "font" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=image - image destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "image";
+      if (node.as != "image") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy.png?t=2&dest=image";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=image fetches with a "image" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=audio - audio destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let audioURL = getAudioURI("dummy_audio");
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "audio";
+      if (node.as != "audio") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = audioURL + "?dest=audio";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=audio fetches with a "audio" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=video - video destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let videoURL = getVideoURI("dummy_video");
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "video";
+      if (node.as != "video") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = videoURL + "?dest=video";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=video fetches with a "video" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=track - track destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "track";
+      if (node.as != "track") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=track";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=track fetches with a "track" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=document - document destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "document";
+      if (node.as != "document") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=document";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=document fetches with a "document" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=worker - worker destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "worker";
+      if (node.as != "worker") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=worker";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=worker fetches with a "worker" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=sharedworker - sharedworker destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "sharedworker";
+      if (node.as != "sharedworker") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=sharedworker";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=sharedworker fetches with a "sharedworker" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=xslt - xslt destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "xslt";
+      if (node.as != "xslt") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=xslt";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=xslt fetches with a "xslt" Request.destination');
+
+// HTMLLinkElement with rel=preload and as=manifest - manifest destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "preload";
+      node.as = "manifest";
+      if (node.as != "manifest") {
+        resolve();
+      }
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=manifest";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=preload and as=manifest fetches with a "manifest" Request.destination');
+
+// HTMLLinkElement with rel=prefetch - empty string destination
+promise_test(async t => {
+  await new Promise((resolve, reject) => {
+      let node = frame.contentWindow.document.createElement("link");
+      node.rel = "prefetch";
+      node.onload = resolve;
+      node.onerror = reject;
+      node.href = "dummy?dest=";
+      frame.contentWindow.document.body.appendChild(node);
+  }).catch(() => {
+      assert_unreached("Fetch errored.");
+  });
+}, 'HTMLLinkElement with rel=prefetch fetches with an empty string Request.destination');
+
+</script>
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/fetch/api/request/destination/resources/dummy
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to fetch/api/request/destination/resources/dummy
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/fetch/api/request/destination/resources/dummy.es
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to fetch/api/request/destination/resources/dummy.es
diff --git a/fetch/api/request/destination/resources/dummy.es.headers b/fetch/api/request/destination/resources/dummy.es.headers
new file mode 100644
index 0000000..9bb8bad
--- /dev/null
+++ b/fetch/api/request/destination/resources/dummy.es.headers
@@ -0,0 +1 @@
+Content-Type: text/event-stream
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/fetch/api/request/destination/resources/dummy.html
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to fetch/api/request/destination/resources/dummy.html
diff --git a/fetch/api/request/destination/resources/dummy.png b/fetch/api/request/destination/resources/dummy.png
new file mode 100644
index 0000000..01c9666
--- /dev/null
+++ b/fetch/api/request/destination/resources/dummy.png
Binary files differ
diff --git a/preload/resources/CanvasTest.ttf b/fetch/api/request/destination/resources/dummy.ttf
similarity index 100%
rename from preload/resources/CanvasTest.ttf
rename to fetch/api/request/destination/resources/dummy.ttf
Binary files differ
diff --git a/fetch/api/request/destination/resources/dummy_audio.mp3 b/fetch/api/request/destination/resources/dummy_audio.mp3
new file mode 100644
index 0000000..0091330
--- /dev/null
+++ b/fetch/api/request/destination/resources/dummy_audio.mp3
Binary files differ
diff --git a/fetch/api/request/destination/resources/dummy_audio.oga b/fetch/api/request/destination/resources/dummy_audio.oga
new file mode 100644
index 0000000..239ad2b
--- /dev/null
+++ b/fetch/api/request/destination/resources/dummy_audio.oga
Binary files differ
diff --git a/fetch/api/request/destination/resources/dummy_video.mp4 b/fetch/api/request/destination/resources/dummy_video.mp4
new file mode 100644
index 0000000..7022e75
--- /dev/null
+++ b/fetch/api/request/destination/resources/dummy_video.mp4
Binary files differ
diff --git a/fetch/api/request/destination/resources/dummy_video.ogv b/fetch/api/request/destination/resources/dummy_video.ogv
new file mode 100644
index 0000000..de99616
--- /dev/null
+++ b/fetch/api/request/destination/resources/dummy_video.ogv
Binary files differ
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/fetch/api/request/destination/resources/empty.https.html
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to fetch/api/request/destination/resources/empty.https.html
diff --git a/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js b/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js
new file mode 100644
index 0000000..7634583
--- /dev/null
+++ b/fetch/api/request/destination/resources/fetch-destination-worker-iframe.js
@@ -0,0 +1,20 @@
+self.addEventListener('fetch', function(event) {
+    if (event.request.url.includes('dummy')) {
+        event.waitUntil(async function() {
+            let destination = new URL(event.request.url).searchParams.get("dest");
+            let clients = await self.clients.matchAll({"includeUncontrolled": true});
+            clients.forEach(function(client) {
+                if (client.url.includes("fetch-destination-iframe")) {
+                    if (event.request.destination == destination) {
+                        client.postMessage("PASS");
+                    } else {
+                        client.postMessage("FAIL");
+                    }
+                }
+            })
+        }());
+    }
+    event.respondWith(fetch(event.request));
+});
+
+
diff --git a/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js b/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js
new file mode 100644
index 0000000..5a3c679
--- /dev/null
+++ b/fetch/api/request/destination/resources/fetch-destination-worker-no-load-event.js
@@ -0,0 +1,18 @@
+self.addEventListener('fetch', function(event) {
+    if (event.request.url.includes('dummy')) {
+        event.waitUntil(async function() {
+            let destination = new URL(event.request.url).searchParams.get("dest");
+            var result = "FAIL";
+            if (event.request.destination == destination) {
+              result = "PASS";
+            }
+            let cl = await clients.matchAll({includeUncontrolled: true});
+            for (i = 0; i < cl.length; i++) {
+              cl[i].postMessage(result);
+            }
+        }())
+    }
+    event.respondWith(fetch(event.request));
+});
+
+
diff --git a/fetch/api/request/destination/resources/fetch-destination-worker.js b/fetch/api/request/destination/resources/fetch-destination-worker.js
new file mode 100644
index 0000000..f8e8e26
--- /dev/null
+++ b/fetch/api/request/destination/resources/fetch-destination-worker.js
@@ -0,0 +1,11 @@
+self.addEventListener('fetch', function(event) {
+    if (event.request.url.includes('dummy')) {
+        let destination = new URL(event.request.url).searchParams.get("dest");
+        if (event.request.destination == destination) {
+            event.respondWith(fetch(event.request));
+        } else {
+            event.respondWith(Response.error());
+        }
+    }
+});
+
diff --git a/fetch/api/request/destination/resources/importer.js b/fetch/api/request/destination/resources/importer.js
new file mode 100644
index 0000000..9568474
--- /dev/null
+++ b/fetch/api/request/destination/resources/importer.js
@@ -0,0 +1 @@
+importScripts("dummy?t=importScripts&dest=script");
diff --git a/fetch/api/request/request-disturbed.html b/fetch/api/request/request-disturbed.html
index 83a1a1f..d247a7f 100644
--- a/fetch/api/request/request-disturbed.html
+++ b/fetch/api/request/request-disturbed.html
@@ -49,6 +49,17 @@
       }, "Check creating a new request from a disturbed request");
 
       promise_test(function() {
+        assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
+        const originalBody = bodyConsumed.body;
+        const bodyReplaced = new Request(bodyConsumed, { body: "Replaced body" });
+        assert_not_equals(bodyReplaced.body, originalBody, "new request's body is new");
+        assert_false(bodyReplaced.bodyUsed, "bodyUsed is false when request is not disturbed");
+        return bodyReplaced.text().then(text => {
+          assert_equals(text, "Replaced body");
+        });
+      }, "Check creating a new request with a new body from a disturbed request");
+
+      promise_test(function() {
         var bodyRequest = new Request("", initValuesDict);
         const originalBody = bodyRequest.body;
         assert_false(bodyRequest.bodyUsed , "bodyUsed is false when request is not disturbed");
@@ -58,7 +69,7 @@
         assert_not_equals(originalBody, undefined, "body should not be undefined");
         assert_not_equals(originalBody, null, "body should not be null");
         assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new");
-        return requestFromRequest.text(text => {
+        return requestFromRequest.text().then(text => {
           assert_equals(text, "Request's body");
         });
       }, "Input request used for creating new request became disturbed");
@@ -74,7 +85,7 @@
         assert_not_equals(originalBody, null, "body should not be null");
         assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new");
 
-        return requestFromRequest.text(text => {
+        return requestFromRequest.text().then(text => {
           assert_equals(text, "init body");
         });
       }, "Input request used for creating new request became disturbed even if body is not used");
diff --git a/fetch/api/request/request-idl.html b/fetch/api/request/request-idl.html
deleted file mode 100644
index f78f0ca..0000000
--- a/fetch/api/request/request-idl.html
+++ /dev/null
@@ -1,90 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Request idl interface</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#response">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/resources/WebIDLParser.js"></script>
-    <script src="/resources/idlharness.js"></script>
-  </head>
-  <body>
-    <script id="body-idl" type="text/plain">
-      typedef any JSON;
-      typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) BodyInit;
-
-      [NoInterfaceObject,
-      Exposed=(Window,Worker)]
-      interface Body {
-        readonly attribute ReadableStream? body;
-        readonly attribute boolean bodyUsed;
-        [NewObject] Promise<ArrayBuffer> arrayBuffer();
-        [NewObject] Promise<Blob> blob();
-        [NewObject] Promise<FormData> formData();
-        [NewObject] Promise<JSON> json();
-        [NewObject] Promise<USVString> text();
-      };
-    </script>
-    <script id="request-idl" type="text/plain">
-      typedef (Request or USVString) RequestInfo;
-
-      [Constructor(RequestInfo input, optional RequestInit init),
-      Exposed=(Window,Worker)]
-      interface Request {
-        readonly attribute ByteString method;
-        readonly attribute USVString url;
-        [SameObject] readonly attribute Headers headers;
-
-        readonly attribute RequestDestination destination;
-        readonly attribute USVString referrer;
-        readonly attribute ReferrerPolicy referrerPolicy;
-        readonly attribute RequestMode mode;
-        readonly attribute RequestCredentials credentials;
-        readonly attribute RequestCache cache;
-        readonly attribute RequestRedirect redirect;
-        readonly attribute DOMString integrity;
-
-        [NewObject] Request clone();
-      };
-      Request implements Body;
-
-      dictionary RequestInit {
-        ByteString method;
-        HeadersInit headers;
-        BodyInit? body;
-        USVString referrer;
-        ReferrerPolicy referrerPolicy;
-        RequestMode mode;
-        RequestCredentials credentials;
-        RequestCache cache;
-        RequestRedirect redirect;
-        DOMString integrity;
-        any window; // can only be set to null
-      };
-
-      enum RequestType { "", "audio", "font", "image", "script", "style", "track", "video" };
-      enum RequestDestination { "", "document", "sharedworker", "subresource", "unknown", "worker" };
-      enum RequestMode { "navigate", "same-origin", "no-cors", "cors" };
-      enum RequestCredentials { "omit", "same-origin", "include" };
-      enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" };
-      enum RequestRedirect { "follow", "error", "manual" };
-      enum ReferrerPolicy {
-        "", "no-referrer", "no-referrer-when-downgrade", "origin",
-        "origin-when-cross-origin", "unsafe-url", "same-origin", "strict-origin",
-        "strict-origin-when-cross-origin"
-      };
-    </script>
-    <script>
-      var idlsArray = new IdlArray();
-      var idl = document.getElementById("body-idl").textContent
-      idl += document.getElementById("request-idl").textContent
-
-      idlsArray.add_idls(idl);
-      idlsArray.add_untested_idls("interface Headers {};");
-      idlsArray.add_objects({ Request: ['new Request("")'] });
-      idlsArray.test();
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/request/request-init-002.html b/fetch/api/request/request-init-002.html
index 5d92b09..221b415 100644
--- a/fetch/api/request/request-init-002.html
+++ b/fetch/api/request/request-init-002.html
@@ -43,7 +43,7 @@
             //not equals: cannot guess formData exact value
             assert_true( bodyAsText.search(expectedTextBody) > -1, "Retrieve and verify request body");
           });
-        }, "Initialize Request's body with " + bodyType);
+        }, `Initialize Request's body with "${body}", ${bodyType}`);
       }
 
       var blob = new Blob(["This is a blob"], {type: "application/octet-binary"});
@@ -56,6 +56,7 @@
       checkRequestInit(blob, "application/octet-binary", "This is a blob");
       checkRequestInit(formaData, "multipart/form-data", "name=\"name\"\r\n\r\nvalue");
       checkRequestInit(usvString, "text/plain;charset=UTF-8", "This is a USVString");
+      checkRequestInit({toString: () => "hi!"}, "text/plain;charset=UTF-8", "hi!");
 
       // Ensure test does not time out in case of missing URLSearchParams support.
       if (window.URLSearchParams) {
diff --git a/fetch/api/request/request-init-003.sub.html b/fetch/api/request/request-init-003.sub.html
index 507007f..79c91cd 100644
--- a/fetch/api/request/request-init-003.sub.html
+++ b/fetch/api/request/request-init-003.sub.html
@@ -44,7 +44,7 @@
                              "referrer" : "about:client",
                              "referrerPolicy" : "",
                              "mode" : "cors",
-                             "credentials" : "omit",
+                             "credentials" : "same-origin",
                              "cache" : "default",
                              "redirect" : "follow",
                              "integrity" : "",
diff --git a/fetch/api/request/request-keepalive-quota.html b/fetch/api/request/request-keepalive-quota.html
index dacc638..19596ce 100644
--- a/fetch/api/request/request-keepalive-quota.html
+++ b/fetch/api/request/request-keepalive-quota.html
@@ -12,81 +12,80 @@
     </head>
     <body>
         <script>
-            "use strict";
+            'use strict';
 
             // We want to ensure that our keepalive requests hang slightly before completing so we can validate
             // the effects of a rolling quota. To do this we will utilize trickle.py with a 1s delay. This should
             // prevent any of the Fetch's from finishing in this window.
-            var trickleURL = "../resources/trickle.py?count=1&ms=";
-            var standardDelay = 1000;
+            const trickleURL = '../resources/trickle.py?count=1&ms=';
+            const noDelay = 0;
+            const standardDelay = 1000;
+            function wait(ms) {
+                return new Promise(resolve => step_timeout(resolve, ms));
+            }
 
             // We should expect 64KiB of rolling quota for any type of keep-alive request sent.
-            var expectedQuota = 65536;
+            const expectedQuota = 65536;
 
-            function CreateKeepAliveRequest(delay, bodySize) {
+            function fetchKeepAliveRequest(delay, bodySize) {
                 // Create a body of the specified size that's filled with *'s
-                var requestBody = "*".repeat(bodySize);
-                return new Request(trickleURL+delay, {keepalive: true, body: requestBody, method: "POST"});
+                const body = '*'.repeat(bodySize);
+                return fetch(trickleURL + delay, {keepalive: true, body, method: 'POST'}).then(res => {
+                    return res.text();
+                }).then(() => {
+                    return wait(1);
+                });
             }
 
             // Test 1 Byte
             promise_test(function(test) {
-                return fetch(CreateKeepAliveRequest(0 /* delay */, 1 /* bodySize */));
-            }, "A Keep-Alive fetch() with a small body should succeed.");
+                return fetchKeepAliveRequest(noDelay, 1 /* bodySize */);
+            }, 'A Keep-Alive fetch() with a small body should succeed.');
 
             // Test Quota full limit
             promise_test(function(test) {
-                return fetch(CreateKeepAliveRequest(0 /* delay */, expectedQuota));
-            }, "A Keep-Alive fetch() with a body at the Quota Limit should succeed.");
+                return fetchKeepAliveRequest(noDelay, expectedQuota /* bodySize */);
+            }, 'A Keep-Alive fetch() with a body at the Quota Limit should succeed.');
 
             // Test Quota + 1 Byte
             promise_test(function(test) {
-                return promise_rejects(test, new TypeError(), fetch(CreateKeepAliveRequest(0 /* delay */, expectedQuota + 1)));
-            }, "A Keep-Alive fetch() with a body over the Quota Limit should reject.");
+                return promise_rejects(test, TypeError(), fetchKeepAliveRequest(noDelay, expectedQuota + 1));
+            }, 'A Keep-Alive fetch() with a body over the Quota Limit should reject.');
 
             // Test the Quota becomes available upon promise completion.
             promise_test(function (test) {
                 // Fill our Quota then try to send a second fetch.
-                var firstFetch = fetch(CreateKeepAliveRequest(standardDelay, expectedQuota)).then(function(response) {
+                return fetchKeepAliveRequest(standardDelay, expectedQuota).then(() => {
                     // Now validate that we can send another Keep-Alive fetch for the full size of the quota.
-                    return fetch(CreateKeepAliveRequest(0 /* delay */, expectedQuota));
+                    return fetchKeepAliveRequest(noDelay, expectedQuota);
                 });
-
-                return firstFetch;
-            }, "A Keep-Alive fetch() should return it's allocated Quota upon promise resolution.");
+            }, 'A Keep-Alive fetch() should return its allocated Quota upon promise resolution.');
 
             // Ensure only the correct amount of Quota becomes available when a fetch completes.
             promise_test(function(test) {
-                var lastFetchSucceeded = false;
                 // Create a fetch that uses all but 1 Byte of the Quota and runs for 2x as long as the other requests.
-                var firstFetch = fetch(CreateKeepAliveRequest(standardDelay * 2, expectedQuota - 1)).then(function(response) {
-                    // This should be our last completing fetch(). We need to validate that the last fetch we sent out actually
-                    // completed.
-                    assert_true(lastFetchSucceeded, "Out last fetch after gaining Quota back should have succeeded.");
-                });
+                const first = fetchKeepAliveRequest(standardDelay * 2, expectedQuota - 1);
 
                 // Now create a single Byte request that will complete quicker.
-                fetch(CreateKeepAliveRequest(standardDelay, 1 /* bodySize */)).then(function(response) {
+                const second = fetchKeepAliveRequest(standardDelay, 1 /* bodySize */).then(() => {
                     // We shouldn't be able to create a 2 Byte request right now as only 1 Byte should have freed up.
-                    assert_throws(new TypeError(), fetch(CreateKeepAliveRequest(0 /* delay */, 2 /* bodySize */)), "Only 1 Byte of Quota should be available right now.");
-
+                    return promise_rejects(test, TypeError(), fetchKeepAliveRequest(noDelay, 2 /* bodySize */));
+                }).then(() => {
                     // Now validate that we can send another Keep-Alive fetch for just 1 Byte.
-                    fetch(CreateKeepAliveRequest(0 /* delay */, 1 /* bodySize */)).then(function(response) {
-                        // Flag we got a response from this request.
-                        lastFetchSucceeded = true;
-                    });
+                    return fetchKeepAliveRequest(noDelay, 1 /* bodySize */);
                 });
 
-                return firstFetch;
-            }, "A Keep-Alive fetch() should return only it's allocated Quota upon promise resolution.");
+                return Promise.all([first, second]);
+            }, 'A Keep-Alive fetch() should return only its allocated Quota upon promise resolution.');
 
             // Test rejecting a fetch() after the quota is used up.
             promise_test(function (test) {
                 // Fill our Quota then try to send a second fetch.
-                fetch(CreateKeepAliveRequest(standardDelay, expectedQuota));
+                const p = fetchKeepAliveRequest(standardDelay, expectedQuota);
 
-                return promise_rejects(test, new TypeError(), fetch(CreateKeepAliveRequest(0 /* delay */, 1 /* bodySize */)));
-            }, "A Keep-Alive fetch() should not be allowed if the Quota is used up.");
+                const q = promise_rejects(test, TypeError(), fetchKeepAliveRequest(noDelay, 1 /* bodySize */));
+                return Promise.all([p, q]);
+            }, 'A Keep-Alive fetch() should not be allowed if the Quota is used up.');
 
         </script>
     </body>
diff --git a/fetch/api/request/request-reset-attributes.https.html b/fetch/api/request/request-reset-attributes.https.html
new file mode 100644
index 0000000..ba04d19
--- /dev/null
+++ b/fetch/api/request/request-reset-attributes.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<body>
+<script>
+const worker = 'resources/request-reset-attributes-worker.js';
+
+promise_test(async (t) => {
+    const scope = 'resources/hello.txt?name=isReloadNavigation';
+    let frame;
+    let reg;
+
+    try {
+      reg = await service_worker_unregister_and_register(t, worker, scope);
+      await wait_for_state(t, reg.installing, 'activated');
+      frame = await with_iframe(scope);
+      assert_equals(frame.contentDocument.body.textContent,
+                    'old: false, new: false');
+      await new Promise((resolve) => {
+        frame.onload = resolve;
+        frame.contentWindow.location.reload();
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'old: true, new: false');
+    } finally {
+      if (frame) {
+        frame.remove();
+      }
+      if (reg) {
+        await reg.unregister();
+      }
+    }
+  }, 'Request.isReloadNavigation is reset with non-empty RequestInit');
+</script>
diff --git a/fetch/api/request/request-structure.html b/fetch/api/request/request-structure.html
index 98c2451..3f82e9c 100644
--- a/fetch/api/request/request-structure.html
+++ b/fetch/api/request/request-structure.html
@@ -76,7 +76,7 @@
             break;
 
           case "credentials":
-            defaultValue = "omit";
+            defaultValue = "same-origin";
             newValue = "cors";
             break;
 
@@ -99,6 +99,11 @@
             newValue = true;
             break;
 
+          case "isReloadNavigation":
+            defaultValue = false;
+            newValue = true;
+            break;
+
           default:
             return;
         }
diff --git a/fetch/api/request/resources/hello.txt b/fetch/api/request/resources/hello.txt
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/fetch/api/request/resources/hello.txt
@@ -0,0 +1 @@
+hello
diff --git a/fetch/api/request/resources/request-reset-attributes-worker.js b/fetch/api/request/resources/request-reset-attributes-worker.js
new file mode 100644
index 0000000..4b264ca
--- /dev/null
+++ b/fetch/api/request/resources/request-reset-attributes-worker.js
@@ -0,0 +1,19 @@
+self.addEventListener('fetch', (event) => {
+    const params = new URL(event.request.url).searchParams;
+    if (params.has('ignore')) {
+      return;
+    }
+    if (!params.has('name')) {
+      event.respondWith(Promise.reject(TypeError('No name is provided.')));
+      return;
+    }
+
+    const name = params.get('name');
+    const old_attribute = event.request[name];
+    // If any of |init|'s member is present...
+    const init = {cache: 'no-store'}
+    const new_attribute = (new Request(event.request, init))[name];
+
+    event.respondWith(
+      new Response(`old: ${old_attribute}, new: ${new_attribute}`));
+  });
diff --git a/fetch/api/resources/cors-top.txt b/fetch/api/resources/cors-top.txt
new file mode 100644
index 0000000..83a3157
--- /dev/null
+++ b/fetch/api/resources/cors-top.txt
@@ -0,0 +1 @@
+top
\ No newline at end of file
diff --git a/fetch/api/resources/cors-top.txt.headers b/fetch/api/resources/cors-top.txt.headers
new file mode 100644
index 0000000..cb762ef
--- /dev/null
+++ b/fetch/api/resources/cors-top.txt.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/fetch/api/resources/inspect-headers.py b/fetch/api/resources/inspect-headers.py
index c4ace18..d53038c 100644
--- a/fetch/api/resources/inspect-headers.py
+++ b/fetch/api/resources/inspect-headers.py
@@ -16,7 +16,10 @@
         headers.append(("Access-Control-Allow-Methods", "GET, POST, HEAD"))
         exposed_headers = ["x-request-" + header for header in checked_headers]
         headers.append(("Access-Control-Expose-Headers", ", ".join(exposed_headers)))
-        headers.append(("Access-Control-Allow-Headers", ", ".join(request.headers)))
+        if "allow_headers" in request.GET:
+            headers.append(("Access-Control-Allow-Headers", request.GET['allow_headers']))
+        else:
+            headers.append(("Access-Control-Allow-Headers", ", ".join(request.headers)))
 
     headers.append(("content-type", "text/plain"))
     return headers, ""
diff --git a/fetch/api/resources/redirect-empty-location.py b/fetch/api/resources/redirect-empty-location.py
new file mode 100644
index 0000000..2baabae
--- /dev/null
+++ b/fetch/api/resources/redirect-empty-location.py
@@ -0,0 +1,3 @@
+def main(request, response):
+    headers = [("Location", "")]
+    return 302, headers, ""
diff --git a/fetch/api/resources/redirect.py b/fetch/api/resources/redirect.py
index 3d313e5..40c1b99 100644
--- a/fetch/api/resources/redirect.py
+++ b/fetch/api/resources/redirect.py
@@ -7,10 +7,14 @@
     status = 302
     headers = [("Content-Type", "text/plain"),
                ("Cache-Control", "no-cache"),
-               ("Pragma", "no-cache"),
-               ("Access-Control-Allow-Origin", "*")]
-    token = None
+               ("Pragma", "no-cache")]
+    if "Origin" in request.headers:
+        headers.append(("Access-Control-Allow-Origin", request.headers.get("Origin", "")))
+        headers.append(("Access-Control-Allow-Credentials", "true"))
+    else:
+        headers.append(("Access-Control-Allow-Origin", "*"))
 
+    token = None
     if "token" in request.GET:
         token = request.GET.first("token")
         data = request.server.stash.take(token)
diff --git a/fetch/api/resources/trickle.py b/fetch/api/resources/trickle.py
index 0e70944..319ecd2 100644
--- a/fetch/api/resources/trickle.py
+++ b/fetch/api/resources/trickle.py
@@ -3,6 +3,8 @@
 def main(request, response):
     delay = float(request.GET.first("ms", 500)) / 1E3
     count = int(request.GET.first("count", 50))
+    # Read request body
+    request.body
     time.sleep(delay)
     response.headers.set("Content-type", "text/plain")
     response.write_status_headers()
diff --git a/fetch/api/response/response-consume.html b/fetch/api/response/response-consume.html
index 24d127f..4946a4d 100644
--- a/fetch/api/response/response-consume.html
+++ b/fetch/api/response/response-consume.html
@@ -31,6 +31,13 @@
       });
     }
 
+    function blobToTypeViaFetch(blob) {
+      var url = URL.createObjectURL(blob);
+      return fetch(url).then(function(response) {
+        return response.headers.get('Content-Type');
+      });
+    }
+
     function responsePromise(body, responseInit) {
       return new Promise(function(resolve, reject) {
         resolve(new Response(body, responseInit));
@@ -75,15 +82,18 @@
       return response.blob().then(function(bodyAsBlob) {
         assert_equals(bodyAsBlob.type, expectedType || "text/plain", "Blob body type should be computed from the response Content-Type");
 
-        var promise = new Promise( function (resolve, reject) {
-          var reader = new FileReader();
-          reader.onload = function(evt) {
-            resolve(reader.result)
-          };
-          reader.onerror = function () {
-            reject("Blob's reader failed");
-          };
-          reader.readAsText(bodyAsBlob);
+        var promise = blobToTypeViaFetch(bodyAsBlob).then(function(type) {
+          assert_equals(type, expectedType || "text/plain", 'Type via blob URL');
+          return new Promise( function (resolve, reject) {
+            var reader = new FileReader();
+            reader.onload = function(evt) {
+              resolve(reader.result)
+            };
+            reader.onerror = function () {
+              reject("Blob's reader failed");
+            };
+            reader.readAsText(bodyAsBlob);
+          });
         });
         return promise.then(function(body) {
           assert_equals(body, expectedBody, "Retrieve and verify response's body");
diff --git a/fetch/api/response/response-error-from-stream.html b/fetch/api/response/response-error-from-stream.html
new file mode 100644
index 0000000..392bc77
--- /dev/null
+++ b/fetch/api/response/response-error-from-stream.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Response Receives Propagated Error from ReadableStream</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      function CustomTestError() {
+        const error = Error();
+        error.name = 'custom-test-error';
+        return error;
+      }
+
+      function newStreamWithStartError() {
+        return new ReadableStream({
+          start(controller) {
+            controller.error(CustomTestError());
+          }
+        })
+      }
+
+      function newStreamWithPullError() {
+        return new ReadableStream({
+          pull(controller) {
+            controller.error(CustomTestError());
+          }
+        })
+      }
+
+      function runRequestPromiseTest(stream, responseReaderMethod, testDescription) {
+        promise_test(test => {
+          return promise_rejects(
+            test,
+            CustomTestError(),
+            new Response(stream)[responseReaderMethod](),
+            'CustomTestError should propagate'
+          )
+        }, testDescription)
+      }
+
+
+      promise_test(test => {
+        return promise_rejects(test, CustomTestError(), newStreamWithStartError().getReader().read(), 'CustomTestError should propagate')
+      }, "ReadableStreamDefaultReader Promise receives ReadableStream start() Error")
+
+      promise_test(test => {
+        return promise_rejects(test, CustomTestError(), newStreamWithPullError().getReader().read(), 'CustomTestError should propagate')
+      }, "ReadableStreamDefaultReader Promise receives ReadableStream pull() Error")
+
+
+      // test start() errors for all Body reader methods
+      runRequestPromiseTest(newStreamWithStartError(), 'arrayBuffer', 'ReadableStream start() Error propagates to Response.arrayBuffer() Promise');
+      runRequestPromiseTest(newStreamWithStartError(), 'blob',        'ReadableStream start() Error propagates to Response.blob() Promise');
+      runRequestPromiseTest(newStreamWithStartError(), 'formData',    'ReadableStream start() Error propagates to Response.formData() Promise');
+      runRequestPromiseTest(newStreamWithStartError(), 'json',        'ReadableStream start() Error propagates to Response.json() Promise');
+      runRequestPromiseTest(newStreamWithStartError(), 'text',        'ReadableStream start() Error propagates to Response.text() Promise');
+
+      // test pull() errors for all Body reader methods
+      runRequestPromiseTest(newStreamWithPullError(), 'arrayBuffer', 'ReadableStream pull() Error propagates to Response.arrayBuffer() Promise');
+      runRequestPromiseTest(newStreamWithPullError(), 'blob',        'ReadableStream pull() Error propagates to Response.blob() Promise');
+      runRequestPromiseTest(newStreamWithPullError(), 'formData',    'ReadableStream pull() Error propagates to Response.formData() Promise');
+      runRequestPromiseTest(newStreamWithPullError(), 'json',        'ReadableStream pull() Error propagates to Response.json() Promise');
+      runRequestPromiseTest(newStreamWithPullError(), 'text',        'ReadableStream pull() Error propagates to Response.text() Promise');
+    </script>
+  </body>
+</html>
diff --git a/fetch/api/response/response-idl.html b/fetch/api/response/response-idl.html
deleted file mode 100644
index 3bbf54e..0000000
--- a/fetch/api/response/response-idl.html
+++ /dev/null
@@ -1,70 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>Response idl interface</title>
-    <meta name="help" href="https://fetch.spec.whatwg.org/#response">
-    <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="/resources/WebIDLParser.js"></script>
-    <script src="/resources/idlharness.js"></script>
-  </head>
-  <body>
-    <script id="body-idl" type="text/plain">
-      typedef any JSON;
-      typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) BodyInit;
-
-      [NoInterfaceObject,
-      Exposed=(Window,Worker)]
-      interface Body {
-        readonly attribute ReadableStream? body;
-        readonly attribute boolean bodyUsed;
-        [NewObject] Promise<ArrayBuffer> arrayBuffer();
-        [NewObject] Promise<Blob> blob();
-        [NewObject] Promise<FormData> formData();
-        [NewObject] Promise<JSON> json();
-        [NewObject] Promise<USVString> text();
-      };
-    </script>
-    <script id="response-idl" type="text/plain">
-      [Constructor(optional BodyInit body, optional ResponseInit init),
-      Exposed=(Window,Worker)]
-      interface Response {
-        [NewObject] static Response error();
-        [NewObject] static Response redirect(USVString url, optional unsigned short status = 302);
-
-        readonly attribute ResponseType type;
-
-        readonly attribute USVString url;
-        readonly attribute unsigned short status;
-        readonly attribute boolean ok;
-        readonly attribute ByteString statusText;
-        [SameObject] readonly attribute Headers headers;
-        readonly attribute Promise<Headers> trailer;
-
-        [NewObject] Response clone();
-      };
-      Response implements Body;
-
-      dictionary ResponseInit {
-        unsigned short status = 200;
-        ByteString statusText = "OK";
-        HeadersInit headers;
-      };
-
-      enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };
-    </script>
-    <script>
-      var idlsArray = new IdlArray();
-      var idl = document.getElementById("body-idl").textContent
-      idl += document.getElementById("response-idl").textContent
-
-      idlsArray.add_idls(idl);
-      idlsArray.add_untested_idls("interface Headers {};");
-      idlsArray.add_untested_idls("interface ReadableStream {};");
-      idlsArray.add_objects({ Response: ['new Response()'] });
-      idlsArray.test();
-    </script>
-  </body>
-</html>
diff --git a/fetch/api/response/response-init-002.html b/fetch/api/response/response-init-002.html
index 0bb2e8d..a48af83 100644
--- a/fetch/api/response/response-init-002.html
+++ b/fetch/api/response/response-init-002.html
@@ -65,6 +65,11 @@
         });
       }, "Testing empty Response Content-Type header");
 
+      test(function() {
+        var response = new Response(null, {status: 204});
+        assert_equals(response.body, null);
+      }, "Testing null Response body");
+
     </script>
   </body>
 </html>
diff --git a/fetch/api/response/response-static-redirect.html b/fetch/api/response/response-static-redirect.html
index e09c666..a749222 100644
--- a/fetch/api/response/response-static-redirect.html
+++ b/fetch/api/response/response-static-redirect.html
@@ -14,16 +14,24 @@
       var url = "http://test.url:1234/";
       test(function() {
         redirectResponse = Response.redirect(url);
+        assert_equals(redirectResponse.type, "default");
+        assert_false(redirectResponse.redirected);
+        assert_false(redirectResponse.ok);
         assert_equals(redirectResponse.status, 302, "Default redirect status is 302");
         assert_equals(redirectResponse.headers.get("Location"), url,
           "redirected response has Location header with the correct url");
+        assert_equals(redirectResponse.statusText, "");
       }, "Check default redirect response");
 
-      var redirectStatus = [301, 302, 303, 307, 308];
-      redirectStatus.forEach(function(status) {
+      [301, 302, 303, 307, 308].forEach(function(status) {
         test(function() {
           redirectResponse = Response.redirect(url, status);
+          assert_equals(redirectResponse.type, "default");
+          assert_false(redirectResponse.redirected);
+          assert_false(redirectResponse.ok);
           assert_equals(redirectResponse.status, status, "Redirect status is " + status);
+          assert_equals(redirectResponse.headers.get("Location"), url);
+          assert_equals(redirectResponse.statusText, "");
         }, "Check response returned by static method redirect(), status = " + status);
       });
 
diff --git a/fetch/api/response/response-stream-disturbed-6.html b/fetch/api/response/response-stream-disturbed-6.html
index 15c8dfd..30492d4 100644
--- a/fetch/api/response/response-stream-disturbed-6.html
+++ b/fetch/api/response/response-stream-disturbed-6.html
@@ -65,7 +65,7 @@
   const reader = stream.getReader();
   assert_false(response.bodyUsed, "After getting a reader");
 
-  reader.read();
+  reader.read().then(() => { }, () => { });
   assert_true(response.bodyUsed, "After calling stream.read()");
 }, "An errored stream on which read() has been called");
 
@@ -81,7 +81,7 @@
   const reader = stream.getReader();
   assert_false(response.bodyUsed, "After getting a reader");
 
-  reader.cancel();
+  reader.cancel().then(() => { }, () => { });
   assert_true(response.bodyUsed, "After calling stream.cancel()");
 }, "An errored stream on which cancel() has been called");
 
diff --git a/fetch/api/response/response-stream-with-broken-then.any.js b/fetch/api/response/response-stream-with-broken-then.any.js
new file mode 100644
index 0000000..35b984c
--- /dev/null
+++ b/fetch/api/response/response-stream-with-broken-then.any.js
@@ -0,0 +1,75 @@
+// META: script=../resources/utils.js
+
+promise_test(async () => {
+  add_completion_callback(() => delete Object.prototype.then);
+  const hello = new TextEncoder().encode('hello');
+  const bye = new TextEncoder().encode('bye');
+  const rs = new ReadableStream({
+    start(controller) {
+      controller.enqueue(hello);
+      controller.close();
+    }
+  });
+  const resp = new Response(rs);
+  Object.prototype.then = (onFulfilled) => {
+    delete Object.prototype.then;
+    onFulfilled({done: false, value: bye});
+  };
+  const text = await resp.text();
+  assert_equals(text, 'bye', 'The valud should be replaced with "bye".');
+}, 'Inject {done: false, value: bye} via Object.prototype.then.');
+
+promise_test(async (t) => {
+  add_completion_callback(() => delete Object.prototype.then);
+  const hello = new TextEncoder().encode('hello');
+  const rs = new ReadableStream({
+    start(controller) {
+      controller.enqueue(hello);
+      controller.close();
+    }
+  });
+  const resp = new Response(rs);
+  Object.prototype.then = (onFulfilled) => {
+    delete Object.prototype.then;
+    onFulfilled({done: false, value: undefined});
+  };
+  promise_rejects(t, TypeError(), resp.text(),
+      'The value should be replaced with undefined.');
+}, 'Inject {done: false, value: undefined} via Object.prototype.then.');
+
+promise_test(async (t) => {
+  add_completion_callback(() => delete Object.prototype.then);
+  const hello = new TextEncoder().encode('hello');
+  const rs = new ReadableStream({
+    start(controller) {
+      controller.enqueue(hello);
+      controller.close();
+    }
+  });
+  const resp = new Response(rs);
+  Object.prototype.then = (onFulfilled) => {
+    delete Object.prototype.then;
+    onFulfilled(undefined);
+  };
+  promise_rejects(t, TypeError(), resp.text(),
+      'The read result should be replaced with undefined.');
+}, 'Inject undefined via Object.prototype.then.');
+
+promise_test(async (t) => {
+  add_completion_callback(() => delete Object.prototype.then);
+  const hello = new TextEncoder().encode('hello');
+  const rs = new ReadableStream({
+    start(controller) {
+      controller.enqueue(hello);
+      controller.close();
+    }
+  });
+  const resp = new Response(rs);
+  Object.prototype.then = (onFulfilled) => {
+    delete Object.prototype.then;
+    onFulfilled(8.2);
+  };
+  promise_rejects(t, TypeError(), resp.text(),
+      'The read result should be replaced with a number.');
+}, 'Inject 8.2 via Object.prototype.then.');
+
diff --git a/fetch/api/response/response-trailer.html b/fetch/api/response/response-trailer.html
index 5454040..cbea155 100644
--- a/fetch/api/response/response-trailer.html
+++ b/fetch/api/response/response-trailer.html
@@ -3,10 +3,10 @@
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <div id=log></div>
-<!-- based on /XMLHttpRequest/getresponseheader-chunked-trailer.htm -->
+<!-- based on /xhr/getresponseheader-chunked-trailer.htm -->
 <script>
 promise_test(() => {
-  return fetch("/XMLHttpRequest/resources/chunked.py").then(res => {
+  return fetch("/xhr/resources/chunked.py").then(res => {
     assert_equals(res.headers.get("Trailer"), "X-Test-Me")
     assert_equals(res.headers.get("X-Test-Me"), null)
     assert_equals(res.headers.get("Content-Type"), "text/plain")
diff --git a/fetch/content-encoding/bad-gzip-body.any.js b/fetch/content-encoding/bad-gzip-body.any.js
new file mode 100644
index 0000000..34557de
--- /dev/null
+++ b/fetch/content-encoding/bad-gzip-body.any.js
@@ -0,0 +1,20 @@
+promise_test((test) => {
+    return fetch("resources/bad-gzip-body.py").then(res => {
+      assert_equals(res.status, 200);
+    });
+}, "Fetching a resource with bad gzip content should still resolve");
+
+[
+  "arrayBuffer",
+  "blob",
+  "formData",
+  "json",
+  "text"
+].forEach(method => {
+  promise_test(t => {
+    return fetch("resources/bad-gzip-body.py").then(res => {
+      assert_equals(res.status, 200);
+      return promise_rejects(t, new TypeError(), res[method]());
+    });
+  }, "Consuming the body of a resource with bad gzip content with " + method + "() should reject");
+});
diff --git a/fetch/content-encoding/resources/bad-gzip-body.py b/fetch/content-encoding/resources/bad-gzip-body.py
new file mode 100644
index 0000000..b2f8cfe
--- /dev/null
+++ b/fetch/content-encoding/resources/bad-gzip-body.py
@@ -0,0 +1,3 @@
+def main(request, response):
+    headers = [("Content-Encoding", "gzip")]
+    return headers, "not actually gzip"
diff --git a/http/content_length.html b/fetch/content-length/content-length.html
similarity index 100%
rename from http/content_length.html
rename to fetch/content-length/content-length.html
diff --git a/http/content_length.html.headers b/fetch/content-length/content-length.html.headers
similarity index 100%
rename from http/content_length.html.headers
rename to fetch/content-length/content-length.html.headers
diff --git a/fetch/corb/README.md b/fetch/corb/README.md
new file mode 100644
index 0000000..48fc4ad
--- /dev/null
+++ b/fetch/corb/README.md
@@ -0,0 +1,64 @@
+# Tests related to Cross-Origin Resource Blocking (CORB).
+
+### Summary
+
+This directory contains tests related to the
+[Cross-Origin Resource Blocking (CORB)](https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md)
+algorithm.
+
+The tests in this directory interact with various, random features,
+but the tests have been grouped together into the `fetch/corb` directory,
+because all of these tests verify behavior that is important to the CORB
+algorithm.
+
+
+### Disclaimer: CORB is not standardized yet
+
+Note that CORB is currently in very early stages of standardization path.  At
+the same time, some tests in this directory (e.g.
+`css-with-json-parser-breaker`) cover behavior spec-ed outside of CORB (making
+sure that CORB doesn't change the existing web behavior) and therefore are
+valuable independently from CORB's standardization efforts.
+
+Tests that cover behavior that is changed by CORB have to be marked as
+[tentative](https://web-platform-tests.org/writing-tests/file-names.html)
+(using `.tentative` substring in their filename) until CORB
+is included in the official
+[Fetch spec](https://fetch.spec.whatwg.org/).  Such tests may fail unless
+CORB is enabled.  In practice this means that:
+* Such tests will pass in Chromium
+  (where CORB is enabled by default [since M68](https://crrev.com/553830)).
+* Such tests may fail in other browsers.
+
+
+### Limitations of WPT test coverage
+
+CORB is a defense-in-depth and in general should not cause changes in behavior
+that can be observed by web features or by end users.  This makes CORB difficult
+or even impossible to test via WPT.
+
+WPT tests can cover the following:
+
+* Helping verify CORB has no observable impact in specific scenarios.
+  Examples:
+  * image rendering of (an empty response of) a html document blocked by CORB
+    should be indistinguishable from rendering such html document without CORB -
+    `img-html-correctly-labeled.sub.html`
+  * CORB shouldn't block responses that don't sniff as a CORB-protected document
+    type - `img-png-mislabeled-as-html.sub.html`
+* Helping document cases where CORB causes observable changes in behavior.
+  Examples:
+  * blocking of nosniff images labeled as non-image, CORB-protected
+    Content-Type - `img-png-mislabeled-as-html-nosniff.tentative.sub.html`
+  * blocking of CORB-protected documents can prevent triggering
+    syntax errors in scripts -
+    `script-html-via-cross-origin-blob-url.tentative.sub.html`
+
+Examples of aspects that WPT tests cannot cover (these aspects have to be
+covered in other, browser-specific tests):
+* Verifying that CORB doesn't affect things that are only indirectly
+  observable by the web (like
+  [prefetch](https://html.spec.whatwg.org/#link-type-prefetch).
+* Verifying that CORB strips non-safe-listed headers of blocked responses.
+* Verifying that CORB blocks responses before they reach the process hosting
+  a cross-origin execution context.
diff --git a/fetch/corb/img-html-correctly-labeled.sub-ref.html b/fetch/corb/img-html-correctly-labeled.sub-ref.html
new file mode 100644
index 0000000..0e75596
--- /dev/null
+++ b/fetch/corb/img-html-correctly-labeled.sub-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<!-- Same-origin, so the HTTP response is not CORB-eligible -->
+<img src="resources/html-correctly-labeled.html">
diff --git a/fetch/corb/img-html-correctly-labeled.sub.html b/fetch/corb/img-html-correctly-labeled.sub.html
new file mode 100644
index 0000000..844cd0c
--- /dev/null
+++ b/fetch/corb/img-html-correctly-labeled.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Test verifies that html fed to an <img> tag doesn't have any observable
+  difference with and without CORB (in both cases the resource body cannot be
+  rendered as an image - html cannot be rendered as an image and the empty body
+  from a CORB-blocked response also cannot be rendered as an image).
+-->
+<meta charset="utf-8">
+<!-- Reference page uses same-origin resources, which are not CORB-eligible. -->
+<link rel="match" href="img-html-correctly-labeled.sub-ref.html">
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<img src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/html-correctly-labeled.html">
diff --git a/fetch/corb/img-mime-types-coverage.tentative.sub.html b/fetch/corb/img-mime-types-coverage.tentative.sub.html
new file mode 100644
index 0000000..65c5b84
--- /dev/null
+++ b/fetch/corb/img-mime-types-coverage.tentative.sub.html
@@ -0,0 +1,77 @@
+<!-- Test verifies that cross-origin, nosniff images are 1) blocked when their
+  MIME type is covered by CORB and 2) allowed otherwise.
+
+  This test is very similar to fetch/nosniff/images.html, except that
+  1) it deals with cross-origin images (CORB ignores same-origin fetches),
+  2) it focuses on MIME types relevant to CORB.
+  There are opportunities to unify the test here with nosniff tests *if*
+  we can also start blocking same-origin (or cors-allowed) images.  We
+  should try to gather data to quantify the impact of such change.
+-->
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+  var passes = [
+      // Empty or non-sensical MIME types
+      null, "", "x", "x/x",
+
+      // MIME-types not protected by CORB
+      "image/gif", "image/png", "image/png;blah", "image/svg+xml",
+      "application/javascript", "application/jsonp",
+      "image/gif;HI=THERE",
+
+      // MIME types that may seem to be JSON or XML, but really aren't - i.e.
+      // these MIME types are not covered by:
+      // - https://mimesniff.spec.whatwg.org/#json-mime-type
+      // - https://mimesniff.spec.whatwg.org/#xml-mime-type
+      // - https://tools.ietf.org/html/rfc6839
+      // - https://tools.ietf.org/html/rfc7303
+      "text/x-json", "text/json+blah", "application/json+blah",
+      "text/xml+blah", "application/xml+blah",
+      "application/blahjson", "text/blahxml"]
+
+  var fails = [
+      // CORB-protected MIME-types - i.e. ones covered by:
+      // - https://mimesniff.spec.whatwg.org/#html-mime-type
+      // - https://mimesniff.spec.whatwg.org/#json-mime-type
+      // - https://mimesniff.spec.whatwg.org/#xml-mime-type
+      "text/html",
+      "text/json", "application/json", "text/xml", "application/xml",
+      "application/blah+json", "text/blah+json",
+      "application/blah+xml", "text/blah+xml",
+      "TEXT/HTML", "TEXT/JSON", "TEXT/BLAH+JSON", "APPLICATION/BLAH+XML",
+      "text/json;does=it;matter", "text/HTML;NO=it;does=NOT"]
+
+  const get_url = (mime) => {
+    // www1 is cross-origin, so the HTTP response is CORB-eligible -->
+    url = "http://{{domains[www1]}}:{{ports[http][0]}}"
+    url = url + "/fetch/nosniff/resources/image.py"
+    if (mime != null) {
+        url += "?type=" + encodeURIComponent(mime)
+    }
+    return url
+  }
+
+  passes.forEach(function(mime) {
+    async_test(function(t) {
+      var img = document.createElement("img")
+      img.onerror = t.unreached_func("Unexpected error event")
+      img.onload = t.step_func_done(function(){
+        assert_equals(img.width, 96)
+      })
+      img.src = get_url(mime)
+      document.body.appendChild(img)
+    }, "CORB should allow the response if Content-Type is: '" + mime + "'.  ")
+  })
+
+  fails.forEach(function(mime) {
+    async_test(function(t) {
+      var img = document.createElement("img")
+      img.onerror = t.step_func_done()
+      img.onload = t.unreached_func("Unexpected load event")
+      img.src = get_url(mime)
+      document.body.appendChild(img)
+    }, "CORB should block the response if Content-Type is: '" + mime + "'.  ")
+  })
+</script>
diff --git a/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html b/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html
new file mode 100644
index 0000000..a771ed6
--- /dev/null
+++ b/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<!-- Same-origin, so the HTTP response is not CORB-eligible -->
+<img src="resources/empty-labeled-as-png.png">
diff --git a/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html b/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
new file mode 100644
index 0000000..82adc47
--- /dev/null
+++ b/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Test verifies that CORB blocks an image mislabeled as text/html if
+  sniffing is disabled via `X-Content-Type-Options: nosniff` response header.
+  This has an observable effect (the image stops rendering), compared to the
+  behavior with no CORB.
+-->
+<meta charset="utf-8">
+<!-- Reference page uses same-origin resources, which are not CORB-eligible. -->
+<link rel="match" href="img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html">
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<img src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html-nosniff.png">
diff --git a/fetch/corb/img-png-mislabeled-as-html.sub-ref.html b/fetch/corb/img-png-mislabeled-as-html.sub-ref.html
new file mode 100644
index 0000000..ebb337d
--- /dev/null
+++ b/fetch/corb/img-png-mislabeled-as-html.sub-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<!-- Same-origin, so the HTTP response is not CORB-eligible -->
+<img src="resources/png-correctly-labeled.png">
diff --git a/fetch/corb/img-png-mislabeled-as-html.sub.html b/fetch/corb/img-png-mislabeled-as-html.sub.html
new file mode 100644
index 0000000..1ae4cfc
--- /dev/null
+++ b/fetch/corb/img-png-mislabeled-as-html.sub.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<!-- Test verifies that CORB won't block an image after sniffing determines
+  that the text/html Content-Type response header doesn't match the response
+  body.
+-->
+<meta charset="utf-8">
+<!-- Reference page uses same-origin resources, which are not CORB-eligible. -->
+<link rel="match" href="img-png-mislabeled-as-html.sub-ref.html">
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<img src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html.png">
diff --git a/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html b/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html
new file mode 100644
index 0000000..cea80f2
--- /dev/null
+++ b/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!-- This test verifies observable CORB impact on <link rel="preload"> elements.
+-->
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<script>
+async_test(function(t) {
+  // With CORB the link.onerror event will be reached
+  // (because CORB will block the cross-origin preload).
+  window.preloadErrorEvent = t.step_func_done();
+
+  // Without CORB the link.onload event will be reached.
+  window.preloadLoadEvent = t.unreached_func("link/preload onload event reached.");
+});
+</script>
+
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<link rel="preload" as="image"
+      onerror="window.preloadErrorEvent()"
+      onload="window.preloadLoadEvent()"
+      href="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html-nosniff.png">
diff --git a/fetch/corb/resources/css-mislabeled-as-html-nosniff.css b/fetch/corb/resources/css-mislabeled-as-html-nosniff.css
new file mode 100644
index 0000000..afd2b92
--- /dev/null
+++ b/fetch/corb/resources/css-mislabeled-as-html-nosniff.css
@@ -0,0 +1 @@
+#header { color: red; }
diff --git a/fetch/corb/resources/css-mislabeled-as-html-nosniff.css.headers b/fetch/corb/resources/css-mislabeled-as-html-nosniff.css.headers
new file mode 100644
index 0000000..0f228f9
--- /dev/null
+++ b/fetch/corb/resources/css-mislabeled-as-html-nosniff.css.headers
@@ -0,0 +1,2 @@
+Content-Type: text/html
+X-Content-Type-Options: nosniff
diff --git a/fetch/corb/resources/css-mislabeled-as-html.css b/fetch/corb/resources/css-mislabeled-as-html.css
new file mode 100644
index 0000000..afd2b92
--- /dev/null
+++ b/fetch/corb/resources/css-mislabeled-as-html.css
@@ -0,0 +1 @@
+#header { color: red; }
diff --git a/fetch/corb/resources/css-mislabeled-as-html.css.headers b/fetch/corb/resources/css-mislabeled-as-html.css.headers
new file mode 100644
index 0000000..156209f
--- /dev/null
+++ b/fetch/corb/resources/css-mislabeled-as-html.css.headers
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/fetch/corb/resources/css-with-json-parser-breaker.css b/fetch/corb/resources/css-with-json-parser-breaker.css
new file mode 100644
index 0000000..7db6f5c
--- /dev/null
+++ b/fetch/corb/resources/css-with-json-parser-breaker.css
@@ -0,0 +1,3 @@
+)]}'
+{}
+#header { color: red; }
diff --git a/html/browsers/origin/.gitkeep b/fetch/corb/resources/empty-labeled-as-png.png
similarity index 100%
rename from html/browsers/origin/.gitkeep
rename to fetch/corb/resources/empty-labeled-as-png.png
diff --git a/fetch/corb/resources/empty-labeled-as-png.png.headers b/fetch/corb/resources/empty-labeled-as-png.png.headers
new file mode 100644
index 0000000..e7be84a
--- /dev/null
+++ b/fetch/corb/resources/empty-labeled-as-png.png.headers
@@ -0,0 +1 @@
+Content-Type: image/png
diff --git a/fetch/corb/resources/html-correctly-labeled.html b/fetch/corb/resources/html-correctly-labeled.html
new file mode 100644
index 0000000..7bad71b
--- /dev/null
+++ b/fetch/corb/resources/html-correctly-labeled.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Page Title</title>
+  </head>
+  <body>
+    <p>Page body</p>
+  </body>
+</html>
diff --git a/fetch/corb/resources/html-correctly-labeled.html.headers b/fetch/corb/resources/html-correctly-labeled.html.headers
new file mode 100644
index 0000000..156209f
--- /dev/null
+++ b/fetch/corb/resources/html-correctly-labeled.html.headers
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/fetch/corb/resources/html-js-polyglot.js b/fetch/corb/resources/html-js-polyglot.js
new file mode 100644
index 0000000..5b048d3
--- /dev/null
+++ b/fetch/corb/resources/html-js-polyglot.js
@@ -0,0 +1,9 @@
+<!--/*--><html><body><script type="text/javascript"><!--//*/
+
+// This is a regression test for https://crbug.com/839425
+// which found out that some script resources are served
+// with text/html content-type and with a body that is
+// both a valid html and a valid javascript.
+window.polyglot = "html-js-polyglot.js";
+
+//--></script></body></html>
diff --git a/fetch/corb/resources/html-js-polyglot.js.headers b/fetch/corb/resources/html-js-polyglot.js.headers
new file mode 100644
index 0000000..156209f
--- /dev/null
+++ b/fetch/corb/resources/html-js-polyglot.js.headers
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/fetch/corb/resources/html-js-polyglot2.js b/fetch/corb/resources/html-js-polyglot2.js
new file mode 100644
index 0000000..9443f8e
--- /dev/null
+++ b/fetch/corb/resources/html-js-polyglot2.js
@@ -0,0 +1,10 @@
+<!-- comment --> <script type='text/javascript'>
+//<![CDATA[
+
+// This is a regression test for https://crbug.com/839945
+// which found out that some script resources are served
+// with text/html content-type and with a body that is
+// both a valid html and a valid javascript.
+window.polyglot = "html-js-polyglot2.js";
+
+//]]>--></script>
diff --git a/fetch/corb/resources/html-js-polyglot2.js.headers b/fetch/corb/resources/html-js-polyglot2.js.headers
new file mode 100644
index 0000000..156209f
--- /dev/null
+++ b/fetch/corb/resources/html-js-polyglot2.js.headers
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/fetch/corb/resources/js-mislabeled-as-html-nosniff.js b/fetch/corb/resources/js-mislabeled-as-html-nosniff.js
new file mode 100644
index 0000000..a880a5b
--- /dev/null
+++ b/fetch/corb/resources/js-mislabeled-as-html-nosniff.js
@@ -0,0 +1 @@
+window.has_executed_script = true;
diff --git a/fetch/corb/resources/js-mislabeled-as-html-nosniff.js.headers b/fetch/corb/resources/js-mislabeled-as-html-nosniff.js.headers
new file mode 100644
index 0000000..0f228f9
--- /dev/null
+++ b/fetch/corb/resources/js-mislabeled-as-html-nosniff.js.headers
@@ -0,0 +1,2 @@
+Content-Type: text/html
+X-Content-Type-Options: nosniff
diff --git a/fetch/corb/resources/js-mislabeled-as-html.js b/fetch/corb/resources/js-mislabeled-as-html.js
new file mode 100644
index 0000000..a880a5b
--- /dev/null
+++ b/fetch/corb/resources/js-mislabeled-as-html.js
@@ -0,0 +1 @@
+window.has_executed_script = true;
diff --git a/fetch/corb/resources/js-mislabeled-as-html.js.headers b/fetch/corb/resources/js-mislabeled-as-html.js.headers
new file mode 100644
index 0000000..156209f
--- /dev/null
+++ b/fetch/corb/resources/js-mislabeled-as-html.js.headers
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/fetch/corb/resources/png-correctly-labeled.png b/fetch/corb/resources/png-correctly-labeled.png
new file mode 100644
index 0000000..820f8ca
--- /dev/null
+++ b/fetch/corb/resources/png-correctly-labeled.png
Binary files differ
diff --git a/fetch/corb/resources/png-correctly-labeled.png.headers b/fetch/corb/resources/png-correctly-labeled.png.headers
new file mode 100644
index 0000000..e7be84a
--- /dev/null
+++ b/fetch/corb/resources/png-correctly-labeled.png.headers
@@ -0,0 +1 @@
+Content-Type: image/png
diff --git a/fetch/corb/resources/png-mislabeled-as-html-nosniff.png b/fetch/corb/resources/png-mislabeled-as-html-nosniff.png
new file mode 100644
index 0000000..820f8ca
--- /dev/null
+++ b/fetch/corb/resources/png-mislabeled-as-html-nosniff.png
Binary files differ
diff --git a/fetch/corb/resources/png-mislabeled-as-html-nosniff.png.headers b/fetch/corb/resources/png-mislabeled-as-html-nosniff.png.headers
new file mode 100644
index 0000000..0f228f9
--- /dev/null
+++ b/fetch/corb/resources/png-mislabeled-as-html-nosniff.png.headers
@@ -0,0 +1,2 @@
+Content-Type: text/html
+X-Content-Type-Options: nosniff
diff --git a/fetch/corb/resources/png-mislabeled-as-html.png b/fetch/corb/resources/png-mislabeled-as-html.png
new file mode 100644
index 0000000..820f8ca
--- /dev/null
+++ b/fetch/corb/resources/png-mislabeled-as-html.png
Binary files differ
diff --git a/fetch/corb/resources/png-mislabeled-as-html.png.headers b/fetch/corb/resources/png-mislabeled-as-html.png.headers
new file mode 100644
index 0000000..156209f
--- /dev/null
+++ b/fetch/corb/resources/png-mislabeled-as-html.png.headers
@@ -0,0 +1 @@
+Content-Type: text/html
diff --git a/fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html b/fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html
new file mode 100644
index 0000000..67b3ad5
--- /dev/null
+++ b/fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+fetch('html-correctly-labeled.html')
+    .then(response => response.blob())
+    .then(blob => {
+        let msg = { blob_size: blob.size,
+                    blob_type: blob.type,
+                    blob_url: URL.createObjectURL(blob) };
+        window.parent.postMessage(msg, '*');
+    })
+    .catch(error => {
+        let msg = { error: error };
+        window.parent.postMessage(msg, '*');
+    });
+</script>
diff --git a/fetch/corb/script-html-correctly-labeled.tentative.sub.html b/fetch/corb/script-html-correctly-labeled.tentative.sub.html
new file mode 100644
index 0000000..8f4d767
--- /dev/null
+++ b/fetch/corb/script-html-correctly-labeled.tentative.sub.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- Test verifies that html fed to a <script> tag won't report a syntax
+  error after CORB blocks the response (an empty response body injected
+  by CORB won't have any JavaScript syntax errors).
+-->
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+setup({allow_uncaught_exception : true});
+async_test(function(t) {
+  var script = document.createElement("script")
+
+  // Without CORB, the html document would cause a syntax error when parsed as
+  // JavaScript, but with CORB there should be no errors (because CORB will
+  // replace the response body with an empty body).
+  script.onload = t.step_func_done(function(){})
+  addEventListener("error",function(e) {
+    t.step(function() {
+      assert_unreached("Empty body of a CORS-blocked response shouldn't trigger syntax errors.");
+      t.done();
+    })
+  });
+
+  // www1 is cross-origin, so the HTTP response is CORB-eligible.
+  script.src = 'http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/html-correctly-labeled.html';
+  document.body.appendChild(script)
+}, "CORB-blocked script has no syntax errors");
+</script>
diff --git a/fetch/corb/script-html-js-polyglot.sub.html b/fetch/corb/script-html-js-polyglot.sub.html
new file mode 100644
index 0000000..ec41037
--- /dev/null
+++ b/fetch/corb/script-html-js-polyglot.sub.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- Test verifies that CORB won't block a polyglot script that is
+     both a valid HTML document and also valid Javascript.
+-->
+<meta charset="utf-8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+["html-js-polyglot.js", "html-js-polyglot2.js"].forEach(polyglot_name => {
+  async_test(function(t) {
+    window.polyglot = "not yet set by the script";
+    var script = document.createElement("script");
+
+    script.onload = t.step_func_done(function(){
+      // Verify that the script response wasn't blocked - that script
+      // should have set window.polyglot to |polyglot_name|.
+      assert_equals(window.polyglot, polyglot_name);
+    })
+    addEventListener("error",function(e) {
+      t.step(function() {
+        assert_unreached("No errors are expected with or without CORB.");
+        t.done();
+      })
+    });
+
+    // www1 is cross-origin, so the HTTP response is CORB-eligible.
+    script.src = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/" + polyglot_name;
+    document.body.appendChild(script);
+  }, "CORB cannot block polyglot HTML/JS: " + polyglot_name);
+});
+</script>
diff --git a/fetch/corb/script-html-via-cross-origin-blob-url.sub.html b/fetch/corb/script-html-via-cross-origin-blob-url.sub.html
new file mode 100644
index 0000000..8ce7987
--- /dev/null
+++ b/fetch/corb/script-html-via-cross-origin-blob-url.sub.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!-- Test verifies that cross-origin blob URIs are blocked both with and
+  without CORB.
+-->
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function(t) {
+  function step1_createSubframe() {
+    addEventListener("message", function(e) {
+      t.step(function() { step2_processSubframeMsg(e.data); })
+    });
+    var subframe = document.createElement("iframe")
+    // www1 is cross-origin, to ensure that the received blob will be cross-origin.
+    subframe.src = 'http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html';
+    document.body.appendChild(subframe);
+  }
+
+  function step2_processSubframeMsg(msg) {
+    assert_not_exists(msg, 'error');
+    assert_equals(msg.blob_type, 'text/html');
+    assert_equals(msg.blob_size, 147);
+
+    // With and without CORB loading of a cross-origin blob should be blocked
+    // (this is verified by expecting |script.onerror|, but not |script.onload|
+    // below).
+    var script = document.createElement("script")
+    script.src = msg.blob_url;
+    script.onerror = t.step_func_done(function(){})
+    script.onload = t.unreached_func("Unexpected load event")
+    document.body.appendChild(script)
+  }
+
+  step1_createSubframe();
+});
+</script>
diff --git a/fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html b/fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html
new file mode 100644
index 0000000..f155def
--- /dev/null
+++ b/fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- Test verifies that script mislabeled as html won't execute with and without CORB
+  if the nosniff response header is present.
+
+  The expected behavior is covered by the Fetch spec at
+  https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?
+
+  See also the following tests:
+  - fetch/nosniff/importscripts.html
+  - fetch/nosniff/script.html
+  - fetch/nosniff/worker.html
+-->
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<script>
+window.has_executed_script = false;
+</script>
+
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<script src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/js-mislabeled-as-html-nosniff.js">
+</script>
+
+<script>
+// Verify what observable effects the <script> tag above had.
+// Assertion should hold with and without CORB:
+assert_false(window.has_executed_script,
+             'The cross-origin script should not be executed');
+done();
+</script>
diff --git a/fetch/corb/script-js-mislabeled-as-html.sub.html b/fetch/corb/script-js-mislabeled-as-html.sub.html
new file mode 100644
index 0000000..3a923e6
--- /dev/null
+++ b/fetch/corb/script-js-mislabeled-as-html.sub.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!-- Test verifies that script mislabeled as html will execute with and without
+  CORB (CORB should allow the script after sniffing).
+-->
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<script>
+window.has_executed_script = false;
+</script>
+
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<script src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/js-mislabeled-as-html.js">
+</script>
+
+<script>
+// Verify what observable effects the <script> tag above had.
+// Assertion should hold with and without CORB:
+assert_true(window.has_executed_script,
+            'The cross-origin script should execute');
+done();
+</script>
diff --git a/fetch/corb/style-css-mislabeled-as-html-nosniff.sub.html b/fetch/corb/style-css-mislabeled-as-html-nosniff.sub.html
new file mode 100644
index 0000000..1d53621
--- /dev/null
+++ b/fetch/corb/style-css-mislabeled-as-html-nosniff.sub.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!-- Test verifies that a stylesheet mislabeled as html won't execute with and
+  without CORB if the nosniff response header is present.
+
+  The expected behavior is covered by the Fetch spec at
+  https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?
+
+  See also the following tests:
+  - fetch/nosniff/stylesheet.html
+-->
+<meta charset="utf-8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<!-- Default style that will be applied if the external stylesheet resource
+  below won't load for any reason.  This stylesheet will set h1's
+  color to green (see |default_color| below). -->
+<style>
+h1 { color: green; }
+</style>
+
+<!-- This stylesheet (if loaded) should set h1#header's color to red
+    (see |external_color| below). -->
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<link rel="stylesheet" type="text/css"
+      href="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/css-mislabeled-as-html-nosniff.css">
+
+<body>
+  <h1 id="header">Header example</h1>
+  <p>Paragraph body</p>
+</body>
+
+<script>
+// Verify that CSS is not applied (because of nosniff + non-text/css headers).
+let style = getComputedStyle(document.getElementById('header'));
+const external_color = 'rgb(255, 0, 0)';  // red
+const default_color = 'rgb(0, 128, 0)';  // green
+assert_equals(style.getPropertyValue('color'), default_color);
+assert_not_equals(style.getPropertyValue('color'), external_color);
+done();
+</script>
diff --git a/fetch/corb/style-css-mislabeled-as-html.sub.html b/fetch/corb/style-css-mislabeled-as-html.sub.html
new file mode 100644
index 0000000..05f6fdb
--- /dev/null
+++ b/fetch/corb/style-css-mislabeled-as-html.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- Test verifies that CORB won't impact a cross-origin stylesheet mislabeled
+  as text/html (because even without CORB mislabeled CSS will be rejected).
+-->
+<meta charset="utf-8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<!-- Default style that will be applied if the external stylesheet resource
+  below won't load for any reason.  This stylesheet will set h1's
+  color to green (see |default_color| below). -->
+<style>
+h1 { color: green; }
+</style>
+
+<!-- This stylesheet (if loaded) should set h1#header's color to red
+    (see |external_color| below). -->
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<link rel="stylesheet" type="text/css"
+      href="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/css-mislabeled-as-html.css">
+
+<body>
+  <h1 id="header">Header example</h1>
+  <p>Paragraph body</p>
+</body>
+
+<script>
+// Verify that CSS is not applied (because of strict content-type enforcement
+// for cross-origin stylesheets).
+let style = getComputedStyle(document.getElementById('header'));
+const external_color = 'rgb(255, 0, 0)';  // red
+const default_color = 'rgb(0, 128, 0)';  // green
+assert_equals(style.getPropertyValue('color'), default_color);
+assert_not_equals(style.getPropertyValue('color'), external_color);
+done();
+</script>
diff --git a/fetch/corb/style-css-with-json-parser-breaker.sub.html b/fetch/corb/style-css-with-json-parser-breaker.sub.html
new file mode 100644
index 0000000..c6b5889
--- /dev/null
+++ b/fetch/corb/style-css-with-json-parser-breaker.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- Test verifies that CORB won't block a stylesheet that
+     1) is correctly labeled with text/css Content-Type and parsing fine as text/css
+     2) starts with a JSON parser breaker (like )]}')
+-->
+<meta charset="utf-8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<!-- Default style that will be applied if the external stylesheet resource
+  below won't load for any reason.  This stylesheet will set h1's
+  color to green (see |default_color| below). -->
+<style>
+h1 { color: green; }
+</style>
+
+<!-- This stylesheet (if loaded) should set h1#header's color to red
+    (see |external_color| below). -->
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<link rel="stylesheet" type="text/css"
+      href="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/css-with-json-parser-breaker.css">
+
+<body>
+  <h1 id="header">Header example</h1>
+  <p>Paragraph body</p>
+</body>
+
+<script>
+// Verify that CSS got applied / did not get blocked by CORB.
+let style = getComputedStyle(document.getElementById('header'));
+const external_color = 'rgb(255, 0, 0)';  // red
+const default_color = 'rgb(0, 128, 0)';  // green
+assert_equals(style.getPropertyValue('color'), external_color);
+assert_not_equals(style.getPropertyValue('color'), default_color);
+done();
+</script>
diff --git a/fetch/corb/style-html-correctly-labeled.sub.html b/fetch/corb/style-html-correctly-labeled.sub.html
new file mode 100644
index 0000000..c99eb05
--- /dev/null
+++ b/fetch/corb/style-html-correctly-labeled.sub.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- Test verifies that using a HTML document as a stylesheet has no observable
+  differences with and without CORB:
+  - The cross-origin stylesheet requires a correct text/css Content-Type
+    and therefore won't render even without CORB.  This aspect of this test
+    is similar to the style-css-mislabeled-as-html.sub.html test.
+  - Even if the Content-Type requirements were relaxed for cross-origin stylesheets,
+    the HTML document is unlikely to parse as a stylesheet (unless a polyglot
+    HTML/CSS document is crafted as part of an attack) and therefore the
+    observable behavior should be indistinguishable from parsing the empty,
+    CORB-blocked response as a stylesheet.
+-->
+<meta charset="utf-8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<!-- Default style that will be applied if the external stylesheet resource
+  below won't load for any reason.  This stylesheet will set h1's
+  color to green (see |default_color| below). -->
+<style>
+h1 { color: green; }
+</style>
+
+<!-- This is not really a stylesheet... -->
+<!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
+<link rel="stylesheet" type="text/css"
+      href="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/html-correctly-labeled.html">
+
+<body>
+  <h1 id="header">Header example</h1>
+  <p>Paragraph body</p>
+</body>
+
+<script>
+// Verify that CSS is not applied (because of mismatched Content-Type header).
+var style = getComputedStyle(document.getElementById('header'));
+const default_color = 'rgb(0, 128, 0)';  // green
+assert_equals(style.getPropertyValue('color'), default_color);
+done();
+</script>
diff --git a/fetch/data-urls/README.md b/fetch/data-urls/README.md
new file mode 100644
index 0000000..1ce5b18
--- /dev/null
+++ b/fetch/data-urls/README.md
@@ -0,0 +1,11 @@
+## data: URLs
+
+`resources/data-urls.json` contains `data:` URL tests. The tests are encoded as a JSON array. Each value in the array is an array of two or three values. The first value describes the input, the second value describes the expected MIME type, null if the input is expected to fail somehow, or the empty string if the expected value is `text/plain;charset=US-ASCII`. The third value, if present, describes the expected body as an array of integers representing bytes.
+
+These tests are used for `data:` URLs in this directory (see `processing.any.js`).
+
+## Forgiving-base64 decode
+
+`resources/base64.json` contains [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode) tests. The tests are encoded as a JSON array. Each value in the array is an array of two values. The first value describes the input, the second value describes the output as an array of integers representing bytes or null if the input cannot be decoded.
+
+These tests are used for `data:` URLs in this directory (see `base64.any.js`) and `window.atob()` in `../../html/webappapis/atob/base64.html`.
diff --git a/fetch/data-urls/base64.any.js b/fetch/data-urls/base64.any.js
new file mode 100644
index 0000000..48e4c53
--- /dev/null
+++ b/fetch/data-urls/base64.any.js
@@ -0,0 +1,16 @@
+promise_test(() => fetch("resources/base64.json").then(res => res.json()).then(runBase64Tests), "Setup.");
+function runBase64Tests(tests) {
+  for(let i = 0; i < tests.length; i++) {
+    const input = tests[i][0],
+          output = tests[i][1],
+          dataURL = "data:;base64," + input;
+    promise_test(t => {
+      if(output === null) {
+        return promise_rejects(t, new TypeError(), fetch(dataURL));
+      }
+      return fetch(dataURL).then(res => res.arrayBuffer()).then(body => {
+        assert_array_equals(new Uint8Array(body), output);
+      });
+    }, "data: URL base64 handling: " + format_value(input));
+  }
+}
diff --git a/fetch/data-urls/processing.any.js b/fetch/data-urls/processing.any.js
new file mode 100644
index 0000000..b66a874
--- /dev/null
+++ b/fetch/data-urls/processing.any.js
@@ -0,0 +1,20 @@
+promise_test(() => fetch("resources/data-urls.json").then(res => res.json()).then(runDataURLTests), "Setup.");
+function runDataURLTests(tests) {
+  for(let i = 0; i < tests.length; i++) {
+    const input = tests[i][0],
+          expectedMimeType = tests[i][1],
+          expectedBody = expectedMimeType !== null ? tests[i][2] : null;
+    promise_test(t => {
+      if(expectedMimeType === null) {
+        return promise_rejects(t, new TypeError(), fetch(input));
+      } else {
+        return fetch(input).then(res => {
+          return res.arrayBuffer().then(body => {
+            assert_array_equals(new Uint8Array(body), expectedBody);
+            assert_equals(res.headers.get("content-type"), expectedMimeType); // We could assert this earlier, but this fails often
+          });
+        });
+      }
+    }, format_value(input));
+  }
+}
diff --git a/fetch/data-urls/resources/base64.json b/fetch/data-urls/resources/base64.json
new file mode 100644
index 0000000..c4f79aa
--- /dev/null
+++ b/fetch/data-urls/resources/base64.json
@@ -0,0 +1,80 @@
+[
+  ["", []],
+  ["abcd", [105, 183, 29]],
+  [" abcd", [105, 183, 29]],
+  ["abcd ", [105, 183, 29]],
+  [" abcd===", null],
+  ["abcd=== ", null],
+  ["abcd ===", null],
+  ["a", null],
+  ["ab", [105]],
+  ["abc", [105, 183]],
+  ["abcde", null],
+  ["𐀀", null],
+  ["=", null],
+  ["==", null],
+  ["===", null],
+  ["====", null],
+  ["=====", null],
+  ["a=", null],
+  ["a==", null],
+  ["a===", null],
+  ["a====", null],
+  ["a=====", null],
+  ["ab=", null],
+  ["ab==", [105]],
+  ["ab===", null],
+  ["ab====", null],
+  ["ab=====", null],
+  ["abc=", [105, 183]],
+  ["abc==", null],
+  ["abc===", null],
+  ["abc====", null],
+  ["abc=====", null],
+  ["abcd=", null],
+  ["abcd==", null],
+  ["abcd===", null],
+  ["abcd====", null],
+  ["abcd=====", null],
+  ["abcde=", null],
+  ["abcde==", null],
+  ["abcde===", null],
+  ["abcde====", null],
+  ["abcde=====", null],
+  ["=a", null],
+  ["=a=", null],
+  ["a=b", null],
+  ["a=b=", null],
+  ["ab=c", null],
+  ["ab=c=", null],
+  ["abc=d", null],
+  ["abc=d=", null],
+  ["ab\u000Bcd", null],
+  ["ab\tcd", [105, 183, 29]],
+  ["ab\ncd", [105, 183, 29]],
+  ["ab\fcd", [105, 183, 29]],
+  ["ab\rcd", [105, 183, 29]],
+  ["ab cd", [105, 183, 29]],
+  ["ab\u00a0cd", null],
+  ["ab\t\n\f\r cd", [105, 183, 29]],
+  [" \t\n\f\r ab\t\n\f\r cd\t\n\f\r ", [105, 183, 29]],
+  ["ab\t\n\f\r =\t\n\f\r =\t\n\f\r ", [105]],
+  ["A", null],
+  ["/A", [252]],
+  ["//A", [255, 240]],
+  ["///A", [255, 255, 192]],
+  ["////A", null],
+  ["/", null],
+  ["A/", [3]],
+  ["AA/", [0, 15]],
+  ["AAAA/", null],
+  ["AAA/", [0, 0, 63]],
+  ["\u0000nonsense", null],
+  ["abcd\u0000nonsense", null],
+  ["YQ", [97]],
+  ["YR", [97]],
+  ["~~", null],
+  ["..", null],
+  ["--", null],
+  ["__", null]
+]
diff --git a/fetch/data-urls/resources/data-urls.json b/fetch/data-urls/resources/data-urls.json
new file mode 100644
index 0000000..4311f59
--- /dev/null
+++ b/fetch/data-urls/resources/data-urls.json
@@ -0,0 +1,208 @@
+[
+  ["data://test/,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data://test:test/,X",
+   null],
+  ["data:,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:",
+   null],
+  ["data:text/html",
+   null],
+  ["data:text/html    ;charset=x   ",
+   null],
+  ["data:,",
+   "text/plain;charset=US-ASCII",
+   []],
+  ["data:,X#X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:text/plain,X",
+   "text/plain",
+   [88]],
+  ["data:text/plain ,X",
+   "text/plain",
+   [88]],
+  ["data:text/plain%20,X",
+   "text/plain%20",
+   [88]],
+  ["data:text/plain\f,X",
+   "text/plain%0c",
+   [88]],
+  ["data:text/plain%0C,X",
+   "text/plain%0c",
+   [88]],
+  ["data:text/plain;,X",
+   "text/plain",
+   [88]],
+  ["data:;x=x;charset=x,X",
+   "text/plain;x=x;charset=x",
+   [88]],
+  ["data:;x=x,X",
+   "text/plain;x=x",
+   [88]],
+  ["data:text/plain;charset=windows-1252,%C2%B1",
+   "text/plain;charset=windows-1252",
+   [194, 177]],
+  ["data:text/plain;Charset=UTF-8,%C2%B1",
+   "text/plain;charset=UTF-8",
+   [194, 177]],
+  ["data:image/gif,%C2%B1",
+   "image/gif",
+   [194, 177]],
+  ["data:IMAGE/gif,%C2%B1",
+   "image/gif",
+   [194, 177]],
+  ["data:IMAGE/gif;hi=x,%C2%B1",
+   "image/gif;hi=x",
+   [194, 177]],
+  ["data:IMAGE/gif;CHARSET=x,%C2%B1",
+   "image/gif;charset=x",
+   [194, 177]],
+  ["data: ,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:%20,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:\f,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:%1F,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:\u0000,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:%00,%FF",
+   "text/plain;charset=US-ASCII",
+   [255]],
+  ["data:text/html  ,X",
+   "text/html",
+   [88]],
+  ["data:text / html,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:†,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:†/†,X",
+   "%e2%80%a0/%e2%80%a0",
+   [88]],
+  ["data:X,X",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:image/png,X X",
+   "image/png",
+   [88, 32, 88]],
+  ["data:application/javascript,X X",
+   "application/javascript",
+   [88, 32, 88]],
+  ["data:application/xml,X X",
+   "application/xml",
+   [88, 32, 88]],
+  ["data:text/javascript,X X",
+   "text/javascript",
+   [88, 32, 88]],
+  ["data:text/plain,X X",
+   "text/plain",
+   [88, 32, 88]],
+  ["data:unknown/unknown,X X",
+   "unknown/unknown",
+   [88, 32, 88]],
+  ["data:text/plain;a=\",\",X",
+   "text/plain",
+   [34, 44, 88]],
+  ["data:text/plain;a=%2C,X",
+   "text/plain;a=%2C",
+   [88]],
+  ["data:;base64;base64,WA",
+   "text/plain",
+   [88]],
+  ["data:x/x;base64;base64,WA",
+   "x/x",
+   [88]],
+  ["data:x/x;base64;charset=x,WA",
+   "x/x;charset=x",
+   [87, 65]],
+  ["data:x/x;base64;charset=x;base64,WA",
+   "x/x;charset=x",
+   [88]],
+  ["data:x/x;base64;base64x,WA",
+   "x/x",
+   [87, 65]],
+  ["data:;base64,W%20A",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;base64,W%0CA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:x;base64x,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:x;base64;x,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:x;base64=x,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:; base64,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;  base64,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:  ;charset=x   ;  base64,WA",
+   "text/plain;charset=x",
+   [88]],
+  ["data:;base64;,WA",
+   "text/plain",
+   [87, 65]],
+  ["data:;base64 ,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;base64   ,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;base 64,WA",
+   "text/plain",
+   [87, 65]],
+  ["data:;BASe64,WA",
+   "text/plain;charset=US-ASCII",
+   [88]],
+  ["data:;%62ase64,WA",
+   "text/plain",
+   [87, 65]],
+  ["data:%3Bbase64,WA",
+   "text/plain;charset=US-ASCII",
+   [87, 65]],
+  ["data:;charset=x,X",
+   "text/plain;charset=x",
+   [88]],
+  ["data:; charset=x,X",
+   "text/plain;charset=x",
+   [88]],
+  ["data:;charset =x,X",
+   "text/plain",
+   [88]],
+  ["data:;charset= x,X",
+   "text/plain;charset=\" x\"",
+   [88]],
+  ["data:;charset=,X",
+   "text/plain",
+   [88]],
+  ["data:;charset,X",
+   "text/plain",
+   [88]],
+  ["data:;charset=\"x\",X",
+   "text/plain;charset=x",
+   [88]],
+  ["data:;CHARSET=\"X\",X",
+   "text/plain;charset=X",
+   [88]]
+]
diff --git a/http/basic-auth-cache-test-ref.html b/fetch/http-cache/basic-auth-cache-test-ref.html
similarity index 100%
rename from http/basic-auth-cache-test-ref.html
rename to fetch/http-cache/basic-auth-cache-test-ref.html
diff --git a/fetch/http-cache/basic-auth-cache-test.html b/fetch/http-cache/basic-auth-cache-test.html
new file mode 100644
index 0000000..a8979ba
--- /dev/null
+++ b/fetch/http-cache/basic-auth-cache-test.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html id="doc" class="reftest-wait">
+  <meta charset="utf-8">
+  <link rel="match" href="basic-auth-cache-test-ref.html">
+
+  <img id="auth" onload="loadNoAuth()">
+  <img id="noauth" onload="removeWait()">
+
+
+  <script type="text/javascript">
+    function loadAuth() {
+        var authUrl = 'http://testuser:testpass@' + window.location.host + '/fetch/http-cache/resources/securedimage.py';
+        document.getElementById('auth').src = authUrl;
+    }
+
+    function loadNoAuth() {
+        var noAuthUrl = 'http://' + window.location.host + '/fetch/http-cache/resources/securedimage.py';
+        document.getElementById('noauth').src = noAuthUrl;
+    }
+
+    function removeWait() {
+        document.getElementById('doc').className = "";
+    }
+
+    window.onload = loadAuth;
+  </script>
+</html>
diff --git a/fetch/http-cache/cc-request.html b/fetch/http-cache/cc-request.html
index 05d6f6b..6ea8fbc 100644
--- a/fetch/http-cache/cc-request.html
+++ b/fetch/http-cache/cc-request.html
@@ -201,7 +201,8 @@
             request_headers: [
               ["Cache-Control", "only-if-cached"]
             ],
-            expected_status: 504
+            expected_status: 504,
+            expected_response_text: ""
           }
         ]
       }
diff --git a/fetch/http-cache/heuristic.html b/fetch/http-cache/heuristic.html
index 429ddda..81deb1d 100644
--- a/fetch/http-cache/heuristic.html
+++ b/fetch/http-cache/heuristic.html
@@ -26,6 +26,7 @@
           },
           {
             expected_type: "cached",
+            response_status: [299, "Whatever"],
           }
         ]
       },
@@ -35,8 +36,7 @@
           {
             response_status: [299, "Whatever"],
             response_headers: [
-              ['Last-Modified', http_date(-3 * 100)],
-              ['Cache-Control', 'public']
+              ['Last-Modified', http_date(-3 * 100)]
             ],
           },
           {
diff --git a/fetch/http-cache/partial.html b/fetch/http-cache/partial.html
index 3ad5933..8d5d61d 100644
--- a/fetch/http-cache/partial.html
+++ b/fetch/http-cache/partial.html
@@ -24,7 +24,7 @@
             response_status: [206, "Partial Content"],
             response_headers: [
               ['Cache-Control', 'max-age=3600'],
-              ['Content-Range', 'bytes 0-4/10']
+              ['Content-Range', 'bytes 4-9/10']
             ],
             response_body: "01234",
             expected_request_headers: [
@@ -36,12 +36,13 @@
               ['Range', "bytes=-5"]
             ],
             expected_type: "cached",
-            expected_status: 206
+            expected_status: 206,
+            expected_response_text: "01234"
           }
         ]
       },
       {
-        name: 'HTTP cache stores complete response and serves smaller ranges from it.',
+        name: 'HTTP cache stores complete response and serves smaller ranges from it(byte-range-spec).',
         requests: [
           {
             response_headers: [
@@ -51,15 +52,54 @@
           },
           {
             request_headers: [
-              ['Range', "bytes=-1"]
+              ['Range', "bytes=0-1"]
             ],
             expected_type: "cached",
+            expected_status: 206,
             expected_response_text: "01"
+          },
+        ]
+      },
+      {
+        name: 'HTTP cache stores complete response and serves smaller ranges from it(absent last-byte-pos).',
+        requests: [
+          {
+            response_headers: [
+              ['Cache-Control', 'max-age=3600'],
+            ],
+            response_body: "01234567890",
+          },
+          {
+            request_headers: [
+              ['Range', "bytes=1-"]
+            ],
+            expected_type: "cached",
+            expected_status: 206,
+            expected_response_text: "1234567890"
           }
         ]
       },
       {
-        name: 'HTTP cache stores partial response and serves smaller ranges from it.',
+        name: 'HTTP cache stores complete response and serves smaller ranges from it(suffix-byte-range-spec).',
+        requests: [
+          {
+            response_headers: [
+              ['Cache-Control', 'max-age=3600'],
+            ],
+            response_body: "0123456789A",
+          },
+          {
+            request_headers: [
+              ['Range', "bytes=-1"]
+            ],
+            expected_type: "cached",
+            expected_status: 206,
+            expected_response_text: "A"
+          }
+        ]
+      },
+      {
+        name: 'HTTP cache stores partial response and serves smaller ranges from it(byte-range-spec).',
         requests: [
           {
             request_headers: [
@@ -68,7 +108,55 @@
             response_status: [206, "Partial Content"],
             response_headers: [
               ['Cache-Control', 'max-age=3600'],
-              ['Content-Range', 'bytes 0-4/10']
+              ['Content-Range', 'bytes 4-9/10']
+            ],
+            response_body: "01234",
+          },
+          {
+            request_headers: [
+              ['Range', "bytes=6-8"]
+            ],
+            expected_type: "cached",
+            expected_status: 206,
+            expected_response_text: "234"
+          }
+        ]
+      },
+      {
+        name: 'HTTP cache stores partial response and serves smaller ranges from it(absent last-byte-pos).',
+        requests: [
+          {
+            request_headers: [
+              ['Range', "bytes=-5"]
+            ],
+            response_status: [206, "Partial Content"],
+            response_headers: [
+              ['Cache-Control', 'max-age=3600'],
+              ['Content-Range', 'bytes 4-9/10']
+            ],
+            response_body: "01234",
+          },
+          {
+            request_headers: [
+              ['Range', "bytes=6-"]
+            ],
+            expected_type: "cached",
+            expected_status: 206,
+            expected_response_text: "234"
+          }
+        ]
+      },
+      {
+        name: 'HTTP cache stores partial response and serves smaller ranges from it(suffix-byte-range-spec).',
+        requests: [
+          {
+            request_headers: [
+              ['Range', "bytes=-5"]
+            ],
+            response_status: [206, "Partial Content"],
+            response_headers: [
+              ['Cache-Control', 'max-age=3600'],
+              ['Content-Range', 'bytes 4-9/10']
             ],
             response_body: "01234",
           },
@@ -77,7 +165,8 @@
               ['Range', "bytes=-1"]
             ],
             expected_type: "cached",
-            expected_response_text: "01"
+            expected_status: 206,
+            expected_response_text: "4"
           }
         ]
       },
diff --git a/fetch/http-cache/resources/securedimage.py b/fetch/http-cache/resources/securedimage.py
new file mode 100644
index 0000000..445b0bd
--- /dev/null
+++ b/fetch/http-cache/resources/securedimage.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -
+
+def main(request, response):
+    image_url = str.replace(request.url, "fetch/http-cache/resources/securedimage.py", "images/green.png")
+
+    if "authorization" not in request.headers:
+        response.status = 401
+        response.headers.set("WWW-Authenticate", "Basic")
+        return
+    else:
+        auth = request.headers.get("Authorization")
+        if auth != "Basic dGVzdHVzZXI6dGVzdHBhc3M=":
+            response.set_error(403, "Invalid username or password - " + auth)
+            return
+
+    response.status = 301
+    response.headers.set("Location", image_url)
diff --git a/fetch/http-cache/vary.html b/fetch/http-cache/vary.html
index 2f4b945..dd42b14 100644
--- a/fetch/http-cache/vary.html
+++ b/fetch/http-cache/vary.html
@@ -103,6 +103,7 @@
             request_headers: [
               ["Foo", "1"]
             ],
+            response_body: http_content('foo_1'),
             expected_type: "cached"
           }
         ]
@@ -245,7 +246,32 @@
         ]
       },
       {
-        name: "HTTP cache doesn't use three-way Vary response when request omits variant header.",
+        name: "HTTP cache doesn't use three-way Vary response when request doesn't match, regardless of header order.",
+        requests: [
+          {
+            request_headers: [
+              ["Foo", "1"],
+              ["Bar", "abc4"],
+              ["Baz", "789"]
+            ],
+            response_headers: [
+              ["Expires", http_date(5000)],
+              ["Last-Modified", http_date(-3000)],
+              ["Vary", "Foo, Bar, Baz"]
+            ]
+          },
+          {
+            request_headers: [
+              ["Foo", "1"],
+              ["Bar", "abc"],
+              ["Baz", "789"]
+            ],
+            expected_type: "not_cached"
+          }
+        ]
+      },
+      {
+        name: "HTTP cache uses three-way Vary response when both request and the original request omited a variant header.",
         requests: [
           {
             request_headers: [
@@ -259,6 +285,33 @@
             ]
           },
           {
+            request_headers: [
+              ["Foo", "1"],
+              ["Baz", "789"]
+            ],
+            expected_type: "cached"
+          }
+        ]
+      },
+      {
+        name: "HTTP cache doesn't use Vary response with a field value of '*'.",
+        requests: [
+          {
+            request_headers: [
+              ["Foo", "1"],
+              ["Baz", "789"]
+            ],
+            response_headers: [
+              ["Expires", http_date(5000)],
+              ["Last-Modified", http_date(-3000)],
+              ["Vary", "*"]
+            ]
+          },
+          {
+            request_headers: [
+              ["*", "1"],
+              ["Baz", "789"]
+            ],
             expected_type: "not_cached"
           }
         ]
diff --git a/fetch/nosniff/image.html b/fetch/nosniff/image.html
index 3273f19..9dfdb94 100644
--- a/fetch/nosniff/image.html
+++ b/fetch/nosniff/image.html
@@ -3,7 +3,19 @@
 <div id=log></div>
 <script>
   // Note: images get always sniffed, nosniff doesn't do anything
-  var passes = [null, "", "x", "x/x", "image/gif", "image/png", "image/png;blah"]
+  // (but note the tentative Cross-Origin Read Blocking (CORB) tests
+  // - for example wpt/fetch/corb/img-mime-types-coverage.tentative.sub.html).
+  var passes = [
+      // Empty or non-sensical MIME types
+      null, "", "x", "x/x",
+
+      // Image MIME types
+      "image/gif", "image/png", "image/png;blah", "image/svg+xml",
+
+      // CORB-protected MIME types (but note that CORB doesn't apply here,
+      // because CORB ignores same-origin requests).
+      "text/html", "application/xml", "application/blah+xml"
+  ]
 
   const get_url = (mime) => {
     let url = "resources/image.py"
diff --git a/fetch/nosniff/importscripts.js b/fetch/nosniff/importscripts.js
index d7b1213..1895280 100644
--- a/fetch/nosniff/importscripts.js
+++ b/fetch/nosniff/importscripts.js
@@ -14,7 +14,7 @@
   return url
 }
 
-[null, "", "x", "x/x"].forEach(function(mime) {
+[null, "", "x", "x/x", "text/html", "text/json"].forEach(function(mime) {
   try {
     importScripts(get_url(mime))
   } catch(e) {
diff --git a/fetch/nosniff/resources/css.py b/fetch/nosniff/resources/css.py
index 7c4c63b..55712c5 100644
--- a/fetch/nosniff/resources/css.py
+++ b/fetch/nosniff/resources/css.py
@@ -1,15 +1,23 @@
 def main(request, response):
-    outcome = request.GET.first("outcome", "f")
     type = request.GET.first("type", None)
+    is_revalidation = request.headers.get("If-Modified-Since", None)
 
     content = "/* nothing to see here */"
 
     response.add_required_headers = False
-    response.writer.write_status(200)
-    response.writer.write_header("x-content-type-options", "nosniff")
-    response.writer.write_header("content-length", len(content))
-    if(type != None):
-      response.writer.write_header("content-type", type)
-    response.writer.end_headers()
-
-    response.writer.write(content)
+    if is_revalidation is not None:
+      response.writer.write_status(304)
+      response.writer.write_header("x-content-type-options", "nosniff")
+      response.writer.write_header("content-length", 0)
+      if(type != None):
+        response.writer.write_header("content-type", type)
+      response.writer.end_headers()
+      response.writer.write("")
+    else:
+      response.writer.write_status(200)
+      response.writer.write_header("x-content-type-options", "nosniff")
+      response.writer.write_header("content-length", len(content))
+      if(type != None):
+        response.writer.write_header("content-type", type)
+      response.writer.end_headers()
+      response.writer.write(content)
diff --git a/fetch/nosniff/resources/image.py b/fetch/nosniff/resources/image.py
index 8fb05ed..623a40d 100644
--- a/fetch/nosniff/resources/image.py
+++ b/fetch/nosniff/resources/image.py
@@ -3,7 +3,13 @@
 def main(request, response):
     type = request.GET.first("type", None)
 
-    body = open(os.path.join(os.path.dirname(__file__), "../../../images/blue96x96.png"), "rb").read()
+    if type != None and "svg" in type:
+      filename = "green-96x96.svg"
+    else:
+      filename = "blue96x96.png"
+
+    path = os.path.join(os.path.dirname(__file__), "../../../images", filename)
+    body = open(path, "rb").read()
 
     response.add_required_headers = False
     response.writer.write_status(200)
diff --git a/fetch/nosniff/script.html b/fetch/nosniff/script.html
index aec5a08..e0b5dac 100644
--- a/fetch/nosniff/script.html
+++ b/fetch/nosniff/script.html
@@ -4,8 +4,8 @@
 <script>
   var log = function() {}, // see comment below
       p = function() {}, // see comment below
-      fails = [null, "", "x", "x/x"],
-      passes = ["text/javascript", "text/ecmascript", "text/ecmascript;blah"]
+      fails = [null, "", "x", "x/x", "text/html", "text/json"],
+      passes = ["text/javascript", "text/ecmascript", "text/ecmascript;blah", "text/javascript1.0"]
 
   // Ideally we'd also check whether the scripts in fact execute, but that would involve
   // timers and might get a bit racy without cross-browser support for the execute events.
diff --git a/fetch/nosniff/stylesheet.html b/fetch/nosniff/stylesheet.html
index 9e47f75..8f2b547 100644
--- a/fetch/nosniff/stylesheet.html
+++ b/fetch/nosniff/stylesheet.html
@@ -3,7 +3,7 @@
 <script src=/resources/testharnessreport.js></script>
 <div id=log></div>
 <script>
-  var fails = [null, "", "x", "x/x"],
+  var fails = [null, "", "x", "x/x", "text/html", "text/json"],
       passes = ["text/css", "text/css;charset=utf-8", "text/css;blah"]
 
   const get_url = (mime) => {
@@ -18,21 +18,43 @@
     async_test(function(t) {
       var link = document.createElement("link")
       link.rel = "stylesheet"
-      link.onerror = t.step_func_done(function(){})
+      link.onerror = t.step_func_done()
       link.onload = t.unreached_func("Unexpected load event")
       link.href = get_url(mime)
       document.body.appendChild(link)
     }, "URL query: " + mime)
   })
 
+  fails.forEach(function(mime) {
+    async_test(function(t) {
+      var link = document.createElement("link")
+      link.rel = "stylesheet"
+      link.onerror = t.step_func_done()
+      link.onload = t.unreached_func("Unexpected load event")
+      link.href = get_url(mime)
+      document.body.appendChild(link)
+    }, "Revalidated URL query: " + mime)
+  })
+
+  passes.forEach(function(mime) {
+    async_test(function(t) {
+      var link = document.createElement("link")
+      link.rel = "stylesheet"
+      link.onerror = t.unreached_func("Unexpected error event")
+      link.onload = t.step_func_done()
+      link.href = get_url(mime)
+      document.body.appendChild(link)
+    }, "URL query: " + mime)
+  })
+
   passes.forEach(function(mime) {
     async_test(function(t) {
       var link = document.createElement("link")
       link.rel = "stylesheet"
       link.onerror = t.unreached_func("Unexpected error event")
-      link.onload = t.step_func_done(function(){})
+      link.onload = t.step_func_done()
       link.href = get_url(mime)
       document.body.appendChild(link)
-    }, "URL query: " + mime)
+    }, "Revalidated URL query: " + mime)
   })
 </script>
diff --git a/fetch/nosniff/worker.html b/fetch/nosniff/worker.html
index 466b207..c8c1076 100644
--- a/fetch/nosniff/worker.html
+++ b/fetch/nosniff/worker.html
@@ -3,7 +3,7 @@
 <div id=log></div>
 <script>
   var workers = [],
-      fails = ["", "?type=", "?type=x", "?type=x/x"],
+      fails = ["", "?type=", "?type=x", "?type=x/x", "?type=text/html", "?type=text/json"],
       passes = ["?type=text/javascript", "?type=text/ecmascript", "?type=text/ecmascript;yay"]
 
   fails.forEach(function(urlpart) {
diff --git a/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html b/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
index 3b13fcc..f27735d 100644
--- a/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
+++ b/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
@@ -16,7 +16,8 @@
   var doubleEscapedBrace = "&amp;amp;lt;";
   var rawNewline = "&#10;";
   var escapedNewline = "&amp;#10;";
-  var doubleEscapedNewline = "&amp;amp;#10;";
+  // doubleEscapedNewline is used inside a data URI, and so must have its '#' escaped.
+  var doubleEscapedNewline = "&amp;amp;%2310;";
 
   function appendFrameAndGetElement(test, frame) {
     return new Promise((resolve, reject) => {
diff --git a/fetch/security/redirect-to-url-with-credentials.https.html b/fetch/security/redirect-to-url-with-credentials.https.html
new file mode 100644
index 0000000..c5c32d3
--- /dev/null
+++ b/fetch/security/redirect-to-url-with-credentials.https.html
@@ -0,0 +1,69 @@
+<html>
+<header>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+</header>
+<body>
+<script>
+var host = get_host_info();
+
+var sameOriginImageURL = "/common/redirect.py?location=" + host.HTTPS_ORIGIN_WITH_CREDS + "/service-workers/service-worker/resources/fetch-access-control.py?ACAOrigin= " + host.HTTPS_ORIGIN + "%26PNGIMAGE%26ACACredentials=true";
+var imageURL = "/common/redirect.py?location=" + host.HTTPS_REMOTE_ORIGIN_WITH_CREDS + "/service-workers/service-worker/resources/fetch-access-control.py?ACAOrigin= " + host.HTTPS_ORIGIN + "%26PNGIMAGE%26ACACredentials=true";
+var frameURL = "/common/redirect.py?location=" + host.HTTPS_REMOTE_ORIGIN_WITH_CREDS + "/common/blank.html";
+
+promise_test((test) => {
+    return fetch(imageURL, {mode: "no-cors"});
+}, "No CORS fetch after a redirect with an URL containing credentials");
+
+promise_test((test) => {
+    return promise_rejects(test, new TypeError, fetch(imageURL, {mode: "cors"}));
+}, "CORS fetch after a redirect with a cross origin URL containing credentials");
+
+promise_test((test) => {
+    return fetch(sameOriginImageURL, {mode: "cors"});
+}, "CORS fetch after a redirect with a same origin URL containing credentials");
+
+promise_test((test) => {
+    return new Promise((resolve, reject) => {
+        var image = new Image();
+        image.onload = resolve;
+        image.onerror = (e) => reject(e);
+        image.src = imageURL;
+    });
+}, "Image loading after a redirect with an URL containing credentials");
+
+promise_test((test) => {
+    return new Promise((resolve, reject) => {
+        var image = new Image();
+        image.crossOrigin = "use-credentials";
+        image.onerror = resolve;
+        image.onload = () => reject("Image should not load");
+        image.src = imageURL;
+    });
+}, "CORS Image loading after a redirect with a cross origin URL containing credentials");
+
+promise_test((test) => {
+    return new Promise((resolve, reject) => {
+        var image = new Image();
+        image.crossOrigin = "use-credentials";
+        image.onload = resolve;
+        image.onerror = (e) => reject(e);
+        image.src = sameOriginImageURL;
+    });
+}, "CORS Image loading after a redirect with a same origin URL containing credentials");
+
+promise_test(async (test) => {
+    var iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+    await new Promise((resolve, reject) => {
+        iframe.src = frameURL;
+        iframe.onload = resolve;
+        iframe.onerror = (e) => reject(e);
+        test.step_timeout(() => reject("Frame loading timed out"), 5000);
+    });
+    document.body.removeChild(iframe);
+}, "Frame loading after a redirect with an URL containing credentials");
+</script>
+</body>
+</html>
diff --git a/fonts/AD.woff b/fonts/AD.woff
new file mode 100644
index 0000000..3df8ea8
--- /dev/null
+++ b/fonts/AD.woff
Binary files differ
diff --git a/css/fonts/CSSTest/LICENSE b/fonts/CSSTest/LICENSE
similarity index 100%
rename from css/fonts/CSSTest/LICENSE
rename to fonts/CSSTest/LICENSE
diff --git a/css/fonts/CSSTest/README b/fonts/CSSTest/README
similarity index 100%
rename from css/fonts/CSSTest/README
rename to fonts/CSSTest/README
diff --git a/css/fonts/CSSTest/csstest-ascii.ttf b/fonts/CSSTest/csstest-ascii.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-ascii.ttf
rename to fonts/CSSTest/csstest-ascii.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-basic-bold.ttf b/fonts/CSSTest/csstest-basic-bold.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-basic-bold.ttf
rename to fonts/CSSTest/csstest-basic-bold.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-basic-bolditalic.ttf b/fonts/CSSTest/csstest-basic-bolditalic.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-basic-bolditalic.ttf
rename to fonts/CSSTest/csstest-basic-bolditalic.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-basic-italic.ttf b/fonts/CSSTest/csstest-basic-italic.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-basic-italic.ttf
rename to fonts/CSSTest/csstest-basic-italic.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-basic-regular.ttf b/fonts/CSSTest/csstest-basic-regular.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-basic-regular.ttf
rename to fonts/CSSTest/csstest-basic-regular.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-fallback.ttf b/fonts/CSSTest/csstest-fallback.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-fallback.ttf
rename to fonts/CSSTest/csstest-fallback.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-familyname-bold.ttf b/fonts/CSSTest/csstest-familyname-bold.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-familyname-bold.ttf
rename to fonts/CSSTest/csstest-familyname-bold.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-familyname-funkyA.ttf b/fonts/CSSTest/csstest-familyname-funkyA.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-familyname-funkyA.ttf
rename to fonts/CSSTest/csstest-familyname-funkyA.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-familyname-funkyB.ttf b/fonts/CSSTest/csstest-familyname-funkyB.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-familyname-funkyB.ttf
rename to fonts/CSSTest/csstest-familyname-funkyB.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-familyname-funkyC.ttf b/fonts/CSSTest/csstest-familyname-funkyC.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-familyname-funkyC.ttf
rename to fonts/CSSTest/csstest-familyname-funkyC.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-familyname.ttf b/fonts/CSSTest/csstest-familyname.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-familyname.ttf
rename to fonts/CSSTest/csstest-familyname.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-verify.ttf b/fonts/CSSTest/csstest-verify.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-verify.ttf
rename to fonts/CSSTest/csstest-verify.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-100.ttf b/fonts/CSSTest/csstest-weights-100.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-100.ttf
rename to fonts/CSSTest/csstest-weights-100.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-1479-w1.ttf b/fonts/CSSTest/csstest-weights-1479-w1.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-1479-w1.ttf
rename to fonts/CSSTest/csstest-weights-1479-w1.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-1479-w4.ttf b/fonts/CSSTest/csstest-weights-1479-w4.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-1479-w4.ttf
rename to fonts/CSSTest/csstest-weights-1479-w4.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-1479-w7.ttf b/fonts/CSSTest/csstest-weights-1479-w7.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-1479-w7.ttf
rename to fonts/CSSTest/csstest-weights-1479-w7.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-1479-w9.ttf b/fonts/CSSTest/csstest-weights-1479-w9.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-1479-w9.ttf
rename to fonts/CSSTest/csstest-weights-1479-w9.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-15-w1.ttf b/fonts/CSSTest/csstest-weights-15-w1.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-15-w1.ttf
rename to fonts/CSSTest/csstest-weights-15-w1.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-15-w5.ttf b/fonts/CSSTest/csstest-weights-15-w5.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-15-w5.ttf
rename to fonts/CSSTest/csstest-weights-15-w5.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-200.ttf b/fonts/CSSTest/csstest-weights-200.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-200.ttf
rename to fonts/CSSTest/csstest-weights-200.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-24-w2.ttf b/fonts/CSSTest/csstest-weights-24-w2.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-24-w2.ttf
rename to fonts/CSSTest/csstest-weights-24-w2.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-24-w4.ttf b/fonts/CSSTest/csstest-weights-24-w4.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-24-w4.ttf
rename to fonts/CSSTest/csstest-weights-24-w4.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-2569-w2.ttf b/fonts/CSSTest/csstest-weights-2569-w2.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-2569-w2.ttf
rename to fonts/CSSTest/csstest-weights-2569-w2.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-2569-w5.ttf b/fonts/CSSTest/csstest-weights-2569-w5.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-2569-w5.ttf
rename to fonts/CSSTest/csstest-weights-2569-w5.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-2569-w6.ttf b/fonts/CSSTest/csstest-weights-2569-w6.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-2569-w6.ttf
rename to fonts/CSSTest/csstest-weights-2569-w6.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-2569-w9.ttf b/fonts/CSSTest/csstest-weights-2569-w9.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-2569-w9.ttf
rename to fonts/CSSTest/csstest-weights-2569-w9.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-258-w2.ttf b/fonts/CSSTest/csstest-weights-258-w2.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-258-w2.ttf
rename to fonts/CSSTest/csstest-weights-258-w2.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-258-w5.ttf b/fonts/CSSTest/csstest-weights-258-w5.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-258-w5.ttf
rename to fonts/CSSTest/csstest-weights-258-w5.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-258-w8.ttf b/fonts/CSSTest/csstest-weights-258-w8.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-258-w8.ttf
rename to fonts/CSSTest/csstest-weights-258-w8.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-300.ttf b/fonts/CSSTest/csstest-weights-300.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-300.ttf
rename to fonts/CSSTest/csstest-weights-300.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-3589-w3.ttf b/fonts/CSSTest/csstest-weights-3589-w3.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-3589-w3.ttf
rename to fonts/CSSTest/csstest-weights-3589-w3.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-3589-w5.ttf b/fonts/CSSTest/csstest-weights-3589-w5.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-3589-w5.ttf
rename to fonts/CSSTest/csstest-weights-3589-w5.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-3589-w8.ttf b/fonts/CSSTest/csstest-weights-3589-w8.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-3589-w8.ttf
rename to fonts/CSSTest/csstest-weights-3589-w8.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-3589-w9.ttf b/fonts/CSSTest/csstest-weights-3589-w9.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-3589-w9.ttf
rename to fonts/CSSTest/csstest-weights-3589-w9.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-400.ttf b/fonts/CSSTest/csstest-weights-400.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-400.ttf
rename to fonts/CSSTest/csstest-weights-400.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-47-w4.ttf b/fonts/CSSTest/csstest-weights-47-w4.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-47-w4.ttf
rename to fonts/CSSTest/csstest-weights-47-w4.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-47-w7.ttf b/fonts/CSSTest/csstest-weights-47-w7.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-47-w7.ttf
rename to fonts/CSSTest/csstest-weights-47-w7.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-500.ttf b/fonts/CSSTest/csstest-weights-500.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-500.ttf
rename to fonts/CSSTest/csstest-weights-500.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-600.ttf b/fonts/CSSTest/csstest-weights-600.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-600.ttf
rename to fonts/CSSTest/csstest-weights-600.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-700.ttf b/fonts/CSSTest/csstest-weights-700.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-700.ttf
rename to fonts/CSSTest/csstest-weights-700.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-800.ttf b/fonts/CSSTest/csstest-weights-800.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-800.ttf
rename to fonts/CSSTest/csstest-weights-800.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-900.ttf b/fonts/CSSTest/csstest-weights-900.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-900.ttf
rename to fonts/CSSTest/csstest-weights-900.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w1.ttf b/fonts/CSSTest/csstest-weights-full-w1.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w1.ttf
rename to fonts/CSSTest/csstest-weights-full-w1.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w2.ttf b/fonts/CSSTest/csstest-weights-full-w2.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w2.ttf
rename to fonts/CSSTest/csstest-weights-full-w2.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w3.ttf b/fonts/CSSTest/csstest-weights-full-w3.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w3.ttf
rename to fonts/CSSTest/csstest-weights-full-w3.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w4.ttf b/fonts/CSSTest/csstest-weights-full-w4.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w4.ttf
rename to fonts/CSSTest/csstest-weights-full-w4.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w5.ttf b/fonts/CSSTest/csstest-weights-full-w5.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w5.ttf
rename to fonts/CSSTest/csstest-weights-full-w5.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w6.ttf b/fonts/CSSTest/csstest-weights-full-w6.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w6.ttf
rename to fonts/CSSTest/csstest-weights-full-w6.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w7.ttf b/fonts/CSSTest/csstest-weights-full-w7.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w7.ttf
rename to fonts/CSSTest/csstest-weights-full-w7.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w8.ttf b/fonts/CSSTest/csstest-weights-full-w8.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w8.ttf
rename to fonts/CSSTest/csstest-weights-full-w8.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights-full-w9.ttf b/fonts/CSSTest/csstest-weights-full-w9.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights-full-w9.ttf
rename to fonts/CSSTest/csstest-weights-full-w9.ttf
Binary files differ
diff --git a/css/fonts/CSSTest/csstest-weights.ttf b/fonts/CSSTest/csstest-weights.ttf
similarity index 100%
rename from css/fonts/CSSTest/csstest-weights.ttf
rename to fonts/CSSTest/csstest-weights.ttf
Binary files differ
diff --git a/preload/resources/CanvasTest.ttf.sub.headers b/fonts/CanvasTest.ttf.sub.headers
similarity index 100%
rename from preload/resources/CanvasTest.ttf.sub.headers
rename to fonts/CanvasTest.ttf.sub.headers
diff --git a/css/css-text/i18n/support/GentiumPlus-R.woff b/fonts/GentiumPlus-R.woff
similarity index 100%
rename from css/css-text/i18n/support/GentiumPlus-R.woff
rename to fonts/GentiumPlus-R.woff
Binary files differ
diff --git a/fonts/OWNERS b/fonts/OWNERS
index 26ea315..9daa3eb 100644
--- a/fonts/OWNERS
+++ b/fonts/OWNERS
@@ -1 +1,3 @@
+@fantasai
+@kojiishi
 @gsnedders
diff --git a/fonts/README.md b/fonts/README.md
index f69b95a..7c6ed9a 100644
--- a/fonts/README.md
+++ b/fonts/README.md
@@ -1,2 +1,6 @@
 This directory only contains auxiliary font files used by other tests. See
 /css-fonts for tests covering the CSS Fonts Module specification.
+
+The font named `Ahem.ttf` is referenced from the project documentation and the
+CLI's scripts for provisioning virtual machines provided by Sauce Labs. If that
+file is re-located, the references should be updated accordingly.
diff --git a/fonts/Revalia.woff b/fonts/Revalia.woff
new file mode 100644
index 0000000..631bee6
--- /dev/null
+++ b/fonts/Revalia.woff
Binary files differ
diff --git a/css/fonts/adobe-fonts/CSSFWOrientationTest.otf b/fonts/adobe-fonts/CSSFWOrientationTest.otf
similarity index 100%
rename from css/fonts/adobe-fonts/CSSFWOrientationTest.otf
rename to fonts/adobe-fonts/CSSFWOrientationTest.otf
Binary files differ
diff --git a/css/fonts/adobe-fonts/CSSHWOrientationTest.otf b/fonts/adobe-fonts/CSSHWOrientationTest.otf
similarity index 100%
rename from css/fonts/adobe-fonts/CSSHWOrientationTest.otf
rename to fonts/adobe-fonts/CSSHWOrientationTest.otf
Binary files differ
diff --git a/css/fonts/adobe-fonts/LICENSE b/fonts/adobe-fonts/LICENSE
similarity index 100%
rename from css/fonts/adobe-fonts/LICENSE
rename to fonts/adobe-fonts/LICENSE
diff --git a/css/fonts/adobe-fonts/README.md b/fonts/adobe-fonts/README.md
similarity index 100%
rename from css/fonts/adobe-fonts/README.md
rename to fonts/adobe-fonts/README.md
diff --git "a/css/fonts/ahem-extra/AHEM_Ahem\041.TTF" "b/fonts/ahem-extra/AHEM_Ahem\041.TTF"
similarity index 100%
rename from "css/fonts/ahem-extra/AHEM_Ahem\041.TTF"
rename to "fonts/ahem-extra/AHEM_Ahem\041.TTF"
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_MissingItalicOblique.TTF b/fonts/ahem-extra/AHEM_MissingItalicOblique.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_MissingItalicOblique.TTF
rename to fonts/ahem-extra/AHEM_MissingItalicOblique.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_MissingNormal.TTF b/fonts/ahem-extra/AHEM_MissingNormal.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_MissingNormal.TTF
rename to fonts/ahem-extra/AHEM_MissingNormal.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_SmallCaps.TTF b/fonts/ahem-extra/AHEM_SmallCaps.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_SmallCaps.TTF
rename to fonts/ahem-extra/AHEM_SmallCaps.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_WhiteSpace.TTF b/fonts/ahem-extra/AHEM_WhiteSpace.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_WhiteSpace.TTF
rename to fonts/ahem-extra/AHEM_WhiteSpace.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_cursive.TTF b/fonts/ahem-extra/AHEM_cursive.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_cursive.TTF
rename to fonts/ahem-extra/AHEM_cursive.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_default.TTF b/fonts/ahem-extra/AHEM_default.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_default.TTF
rename to fonts/ahem-extra/AHEM_default.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_fantasy.TTF b/fonts/ahem-extra/AHEM_fantasy.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_fantasy.TTF
rename to fonts/ahem-extra/AHEM_fantasy.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_inherit.TTF b/fonts/ahem-extra/AHEM_inherit.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_inherit.TTF
rename to fonts/ahem-extra/AHEM_inherit.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_initial.TTF b/fonts/ahem-extra/AHEM_initial.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_initial.TTF
rename to fonts/ahem-extra/AHEM_initial.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_monospace.TTF b/fonts/ahem-extra/AHEM_monospace.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_monospace.TTF
rename to fonts/ahem-extra/AHEM_monospace.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_sans-serif.TTF b/fonts/ahem-extra/AHEM_sans-serif.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_sans-serif.TTF
rename to fonts/ahem-extra/AHEM_sans-serif.TTF
Binary files differ
diff --git a/css/fonts/ahem-extra/AHEM_serif.TTF b/fonts/ahem-extra/AHEM_serif.TTF
similarity index 100%
rename from css/fonts/ahem-extra/AHEM_serif.TTF
rename to fonts/ahem-extra/AHEM_serif.TTF
Binary files differ
diff --git a/fonts/math/largeop-displayoperatorminheight2000-2AFF-italiccorrection3000.woff b/fonts/math/largeop-displayoperatorminheight2000-2AFF-italiccorrection3000.woff
new file mode 100644
index 0000000..0b4f8bf
--- /dev/null
+++ b/fonts/math/largeop-displayoperatorminheight2000-2AFF-italiccorrection3000.woff
Binary files differ
diff --git a/css/css-text/support/mplus-1p-regular.woff b/fonts/mplus-1p-regular.woff
similarity index 100%
rename from css/css-text/support/mplus-1p-regular.woff
rename to fonts/mplus-1p-regular.woff
Binary files differ
diff --git a/fonts/noto/NotoSansAdlam-hinted/LICENSE_OFL.txt b/fonts/noto/NotoSansAdlam-hinted/LICENSE_OFL.txt
new file mode 100644
index 0000000..d952d62
--- /dev/null
+++ b/fonts/noto/NotoSansAdlam-hinted/LICENSE_OFL.txt
@@ -0,0 +1,92 @@
+This Font Software is licensed under the SIL Open Font License,
+Version 1.1.
+
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font
+creation efforts of academic and linguistic communities, and to
+provide a free and open framework in which fonts may be shared and
+improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply to
+any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software
+components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to,
+deleting, or substituting -- in part or in whole -- any of the
+components of the Original Version, by changing formats or by porting
+the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed,
+modify, redistribute, and sell modified and unmodified copies of the
+Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in
+Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the
+corresponding Copyright Holder. This restriction only applies to the
+primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created using
+the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/fonts/noto/NotoSansAdlam-hinted/NotoSansAdlam-Regular.ttf b/fonts/noto/NotoSansAdlam-hinted/NotoSansAdlam-Regular.ttf
new file mode 100644
index 0000000..0ab5d99
--- /dev/null
+++ b/fonts/noto/NotoSansAdlam-hinted/NotoSansAdlam-Regular.ttf
Binary files differ
diff --git a/fonts/noto/NotoSansAdlam-hinted/README b/fonts/noto/NotoSansAdlam-hinted/README
new file mode 100644
index 0000000..d228764
--- /dev/null
+++ b/fonts/noto/NotoSansAdlam-hinted/README
@@ -0,0 +1,11 @@
+This package is part of the noto project.  Visit
+google.com/get/noto for more information.
+
+Built on 2017-10-24 from the following noto repository:
+-----
+Repo: noto-fonts
+Tag: v2017-10-24-phase3-second-cleanup
+Date: 2017-10-24 12:10:34 GMT
+Commit: 8ef14e6c606a7a0ef3943b9ca01fd49445620d79
+
+Remove some files that aren't for release.
diff --git a/fonts/noto/NotoSansCypriot-hinted/LICENSE_OFL.txt b/fonts/noto/NotoSansCypriot-hinted/LICENSE_OFL.txt
new file mode 100644
index 0000000..d952d62
--- /dev/null
+++ b/fonts/noto/NotoSansCypriot-hinted/LICENSE_OFL.txt
@@ -0,0 +1,92 @@
+This Font Software is licensed under the SIL Open Font License,
+Version 1.1.
+
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font
+creation efforts of academic and linguistic communities, and to
+provide a free and open framework in which fonts may be shared and
+improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply to
+any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software
+components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to,
+deleting, or substituting -- in part or in whole -- any of the
+components of the Original Version, by changing formats or by porting
+the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed,
+modify, redistribute, and sell modified and unmodified copies of the
+Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in
+Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the
+corresponding Copyright Holder. This restriction only applies to the
+primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created using
+the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/fonts/noto/NotoSansCypriot-hinted/NotoSansCypriot-Regular.ttf b/fonts/noto/NotoSansCypriot-hinted/NotoSansCypriot-Regular.ttf
new file mode 100644
index 0000000..6727e13
--- /dev/null
+++ b/fonts/noto/NotoSansCypriot-hinted/NotoSansCypriot-Regular.ttf
Binary files differ
diff --git a/fonts/noto/NotoSansCypriot-hinted/README b/fonts/noto/NotoSansCypriot-hinted/README
new file mode 100644
index 0000000..d228764
--- /dev/null
+++ b/fonts/noto/NotoSansCypriot-hinted/README
@@ -0,0 +1,11 @@
+This package is part of the noto project.  Visit
+google.com/get/noto for more information.
+
+Built on 2017-10-24 from the following noto repository:
+-----
+Repo: noto-fonts
+Tag: v2017-10-24-phase3-second-cleanup
+Date: 2017-10-24 12:10:34 GMT
+Commit: 8ef14e6c606a7a0ef3943b9ca01fd49445620d79
+
+Remove some files that aren't for release.
diff --git a/css/fonts/NotoSansDeseret-Regular.ttf b/fonts/noto/NotoSansDeseret-Regular.ttf
similarity index 100%
rename from css/fonts/NotoSansDeseret-Regular.ttf
rename to fonts/noto/NotoSansDeseret-Regular.ttf
Binary files differ
diff --git a/css/css-writing-modes/reference/support/sileot-webfont.woff b/fonts/sileot-webfont.woff
similarity index 100%
rename from css/css-writing-modes/reference/support/sileot-webfont.woff
rename to fonts/sileot-webfont.woff
Binary files differ
diff --git a/css/css-writing-modes/reference/support/tcu-font.woff b/fonts/tcu-font.woff
similarity index 100%
rename from css/css-writing-modes/reference/support/tcu-font.woff
rename to fonts/tcu-font.woff
Binary files differ
diff --git a/fullscreen/model/move-to-fullscreen-iframe-manual.html b/fullscreen/model/move-to-fullscreen-iframe-manual.html
index d152f7d..683865a 100644
--- a/fullscreen/model/move-to-fullscreen-iframe-manual.html
+++ b/fullscreen/model/move-to-fullscreen-iframe-manual.html
@@ -12,6 +12,8 @@
   // Enter fullscreen for the iframe's body element.
   trusted_request(t, iframeDoc.body, document.body);
   document.onfullscreenchange = t.step_func(() => {
+    assert_equals(document.fullscreenElement, iframe, "document's initial fullscreen element");
+    assert_equals(iframeDoc.fullscreenElement, iframeDoc.body, "iframe's initial fullscreen element");
 
     // Then, move the outer document's body into the iframe. This is an unusual
     // thing to do, but means that the iframe is removed from its document and
@@ -20,12 +22,15 @@
 
     // If we exit in an orderly fashion, that's all one can ask for.
     document.onfullscreenchange = t.step_func_done(() => {
-      assert_equals(document.fullscreenElement, null, "document's fullscreen element");
+      assert_equals(document.fullscreenElement, null, "document's final fullscreen element");
 
-      // the iframe's contentDocument has become undefined, but the reference
-      // we're holding on to should not have a fullscreen element either.
-      assert_equals(iframe.contentDocuemnt, undefined, "iframe's content document");
-      assert_equals(iframeDoc.fullscreenElement, null, "iframe's fullscreen element");
+      // Because the iframe was removed, its browsing context was discarded and
+      // its contentDocument has become null. Because that browsing context was
+      // neither a descendant browsing context nor had an active document,
+      // nothing at all was done with it in the exit fullscreen algorithm, so
+      // its fullscreenElement is unchanged.
+      assert_equals(iframe.contentDocument, null, "iframe's content document");
+      assert_equals(iframeDoc.fullscreenElement, iframeDoc.body, "iframe's final fullscreen element");
     });
   });
 });
diff --git a/gamepad/idlharness-manual.html b/gamepad/idlharness-manual.html
index 3b96073..8bb740a 100644
--- a/gamepad/idlharness-manual.html
+++ b/gamepad/idlharness-manual.html
@@ -1,78 +1,32 @@
 <!doctype html>
-<html>
-<head>
 <title>Manual Gamepad IDL tests</title>
-<link rel="help" href="https://w3c.github.io/gamepad/#gamepad-interface">
-<link rel="help" href="https://w3c.github.io/gamepad/#gamepadbutton-interface">
-<link rel="help" href="https://w3c.github.io/gamepad/#gamepadevent-interface">
-<link rel="help" href="https://w3c.github.io/gamepad/#navigator-interface-extension">
+<link rel="help" href="https://w3c.github.io/gamepad/">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
+<p id="instructions">This test requires a gamepad. Connect one and press any button to start the test.</p>
 <script>
-setup({explicit_done: true, explicit_timeout: true});
+"use strict";
 
-addEventListener("gamepadconnected", function (e) {
-  var idl_array = new IdlArray();
-  idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
-  idl_array.add_idls(document.getElementById("idl").textContent);
+setup({explicit_timeout: true});
+
+promise_test(async t => {
+  const eventWatcher = new EventWatcher(t, window, "gamepadconnected");
+  let e = await eventWatcher.wait_for("gamepadconnected");
+  const idl_array = new IdlArray();
+  const gamepad_idl = await fetch("/interfaces/gamepad.idl").then(r => r.text());
+
+  idl_array.add_untested_idls('interface Event {};');
+  idl_array.add_untested_idls('interface Navigator {};');
+  idl_array.add_idls(gamepad_idl);
+
   idl_array.add_objects({
     GamepadEvent: [e],
     Gamepad: [e.gamepad],
     GamepadButton: [e.gamepad.buttons[0]],
-    Navigator: ["navigator"],
+    Navigator: ["navigator"]
   });
   idl_array.test();
-  done();
-});
+}, "Test IDL implementation of Gamepad API");
 </script>
-</head>
-<body>
-<pre id="untested_idl" style="display: none">
-interface Navigator {
-};
-
-interface Event {
-};
-</pre>
-<pre id="idl" style="display: none">
-interface Gamepad {
-    readonly    attribute DOMString           id;
-    readonly    attribute long                index;
-    readonly    attribute boolean             connected;
-    readonly    attribute DOMHighResTimeStamp timestamp;
-    readonly    attribute GamepadMappingType  mapping;
-    readonly    attribute FrozenArray<double> axes;
-    readonly    attribute FrozenArray<GamepadButton> buttons;
-};
-
-enum GamepadMappingType {
-  "",
-  "standard"
-};
-
-interface GamepadButton {
-    readonly    attribute boolean pressed;
-    readonly    attribute double  value;
-};
-
-[Constructor(DOMString type, optional GamepadEventInit eventInitDict)]
-interface GamepadEvent : Event
-{
-  readonly attribute Gamepad? gamepad;
-};
-
-dictionary GamepadEventInit : EventInit
-{
-  Gamepad? gamepad = null;
-};
-
-partial interface Navigator {
-    Gamepad[] getGamepads();
-};
-</pre>
-<p id="instructions">This test requires a gamepad. Connect one and press any button to start the test.</p>
-<div id="log"></div>
-</body>
-</html>
diff --git a/gamepad/idlharness.html b/gamepad/idlharness.html
index e78a56b..02737fc 100644
--- a/gamepad/idlharness.html
+++ b/gamepad/idlharness.html
@@ -1,6 +1,4 @@
 <!doctype html>
-<html>
-<head>
 <title>Gamepad IDL tests</title>
 <link rel="help" href="https://w3c.github.io/gamepad/#gamepad-interface">
 <link rel="help" href="https://w3c.github.io/gamepad/#gamepadbutton-interface">
@@ -10,61 +8,23 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
-</head>
-<body>
-<pre id="untested_idl" style="display: none">
-interface Navigator {
-};
-
-interface Event {
-};
-</pre>
-<pre id="idl" style="display: none">
-interface Gamepad {
-    readonly    attribute DOMString           id;
-    readonly    attribute long                index;
-    readonly    attribute boolean             connected;
-    readonly    attribute DOMHighResTimeStamp timestamp;
-    readonly    attribute GamepadMappingType  mapping;
-    readonly    attribute FrozenArray<double> axes;
-    readonly    attribute FrozenArray<GamepadButton> buttons;
-};
-
-enum GamepadMappingType {
-  "",
-  "standard"
-};
-
-interface GamepadButton {
-    readonly    attribute boolean pressed;
-    readonly    attribute double  value;
-};
-
-[Constructor(DOMString type, optional GamepadEventInit eventInitDict)]
-interface GamepadEvent : Event
-{
-  readonly attribute Gamepad? gamepad;
-};
-
-dictionary GamepadEventInit : EventInit
-{
-  Gamepad? gamepad = null;
-};
-
-partial interface Navigator {
-    sequence<Gamepad?> getGamepads();
-};
-</pre>
 <script>
-var idl_array = new IdlArray();
-idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
-idl_array.add_idls(document.getElementById("idl").textContent);
-idl_array.add_objects({
-    GamepadEvent: [new GamepadEvent("something")],
-    Navigator: ["navigator"],
-  });
-idl_array.test();
+  "use strict";
+
+  promise_test(async () => {
+    const idl_array = new IdlArray();
+    const gamepad_idl = await fetch("/interfaces/gamepad.idl").then(r => r.text());
+    const dom = await fetch("/interfaces/dom.idl").then(r => r.text());
+    const html = await fetch("/interfaces/html.idl").then(r => r.text());
+
+    idl_array.add_idls(gamepad_idl);
+    idl_array.add_dependency_idls(dom);
+    idl_array.add_dependency_idls(html);
+
+    idl_array.add_objects({
+      GamepadEvent: [new GamepadEvent("something")],
+      Navigator: ["navigator"]
+    });
+    idl_array.test();
+  }, "Test IDL implementation of Gamepad API");
 </script>
-<div id="log"></div>
-</body>
-</html>
diff --git a/generic-sensor/README.md b/generic-sensor/README.md
new file mode 100644
index 0000000..993e0c5
--- /dev/null
+++ b/generic-sensor/README.md
@@ -0,0 +1,20 @@
+The `generic-sensor-tests.js` tests require an implementation of
+the `GenericSensorTest` interface, which should emulate platform
+sensor backends. The `GenericSensorTest` interface is defined as:
+
+```
+  class GenericSensorTest {
+    async initialize();  // Sets up the testing enviroment.
+    async reset(); // Frees the resources.
+  };
+```
+
+The Chromium implementation of the `GenericSensorTest` interface is located in
+[generic_sensor_mocks.js](../resources/chromium/generic_sensor_mocks.js).
+
+Other browser vendors should provide their own implementations of
+the `GenericSensorTest` interface.
+
+[Known issue](https://github.com/w3c/web-platform-tests/issues/9686): a
+WebDriver extension is a better approach for the Generic Sensor tests
+automation.
diff --git a/generic-sensor/generic-sensor-feature-policy-test.sub.js b/generic-sensor/generic-sensor-feature-policy-test.sub.js
new file mode 100644
index 0000000..94e5347
--- /dev/null
+++ b/generic-sensor/generic-sensor-feature-policy-test.sub.js
@@ -0,0 +1,170 @@
+const feature_policies = {
+  "AmbientLightSensor" : ["ambient-light-sensor"],
+  "Accelerometer" : ["accelerometer"],
+  "LinearAccelerationSensor" : ["accelerometer"],
+  "GravitySensor" : ["accelerometer"],
+  "Gyroscope" : ["gyroscope"],
+  "GeolocationSensor" : ["geolocation"],
+  "Magnetometer" : ["magnetometer"],
+  "UncalibratedMagnetometer" : ["magnetometer"],
+  "AbsoluteOrientationSensor" : ["accelerometer", "gyroscope", "magnetometer"],
+  "RelativeOrientationSensor" : ["accelerometer", "gyroscope"]
+};
+
+const same_origin_src =
+  "/feature-policy/resources/feature-policy-generic-sensor.html#";
+const cross_origin_src =
+  "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
+const base_src = "/feature-policy/resources/redirect-on-load.html#";
+
+function run_fp_tests_disabled(sensorName) {
+  const sensorType = self[sensorName];
+  const featureNameList = feature_policies[sensorName];
+  const header = "Feature-Policy header " + featureNameList.join(" 'none';") + " 'none'";
+  const desc = "'new " + sensorName + "()'";
+
+  test(() => {
+    assert_true(sensorName in self);
+    assert_throws("SecurityError", () => {new sensorType()});
+  }, `${sensorName}: ${header} disallows the top-level document.`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      same_origin_src + sensorName,
+      expect_feature_unavailable_default
+    );
+  }, `${sensorName}: ${header} disallows same-origin iframes.`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      cross_origin_src + sensorName,
+      expect_feature_unavailable_default
+    );
+  }, `${sensorName}: ${header} disallows cross-origin iframes.`);
+}
+
+function run_fp_tests_enabled(sensorName) {
+  const sensorType = self[sensorName];
+  const featureNameList = feature_policies[sensorName];
+  const header = "Feature-Policy header " + featureNameList.join(" *;") + " *";
+  const desc = "'new " + sensorName + "()'";
+
+  test(() => {
+    assert_true(sensorName in self);
+  }, `${sensorName}: ${header} allows the top-level document.`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      same_origin_src + sensorName,
+      expect_feature_available_default
+    );
+  }, `${sensorName}: ${header} allows same-origin iframes.`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      cross_origin_src + sensorName,
+      expect_feature_available_default
+    );
+  }, `${sensorName}: ${header} allows cross-origin iframes.`);
+}
+
+function run_fp_tests_enabled_by_attribute(sensorName) {
+  const sensorType = self[sensorName];
+  const featureNameList = feature_policies[sensorName];
+  const header = "Feature-Policy allow='" + featureNameList.join(" ") + "' attribute";
+  const desc = "'new " + sensorName + "()'";
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      same_origin_src + sensorName,
+      expect_feature_available_default,
+      featureNameList.join(";")
+    );
+  }, `${sensorName}: ${header} allows same-origin iframe`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      cross_origin_src + sensorName,
+      expect_feature_available_default,
+      featureNameList.join(";")
+    );
+  }, `${sensorName}: ${header} allows cross-origin iframe`);
+}
+
+function run_fp_tests_enabled_by_attribute_redirect_on_load(sensorName) {
+  const sensorType = self[sensorName];
+  const featureNameList = feature_policies[sensorName];
+  const header = "Feature-Policy allow='" + featureNameList.join(" ") + "' attribute";
+  const desc = "'new " + sensorName + "()'";
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      base_src + same_origin_src + sensorName,
+      expect_feature_available_default,
+      featureNameList.join(";")
+    );
+  }, `${sensorName}: ${header} allows same-origin relocation`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      base_src + cross_origin_src + sensorName,
+      expect_feature_unavailable_default,
+      featureNameList.join(";")
+    );
+  }, `${sensorName}: ${header} disallows cross-origin relocation`);
+}
+
+function run_fp_tests_enabled_on_self_origin(sensorName) {
+  const sensorType = self[sensorName];
+  const featureNameList = feature_policies[sensorName];
+  const header = "Feature-Policy header " + featureNameList.join(" 'self';") + " 'self'";
+  const desc = "'new " + sensorName + "()'";
+
+  test(() => {
+    assert_true(sensorName in self);
+  }, `${sensorName}: ${header} allows the top-level document.`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      same_origin_src + sensorName,
+      expect_feature_available_default
+    );
+  }, `${sensorName}: ${header} allows same-origin iframes.`);
+
+  async_test(t => {
+    assert_true(sensorName in self);
+    test_feature_availability(
+      desc,
+      t,
+      cross_origin_src + sensorName,
+      expect_feature_unavailable_default
+    );
+  }, `${sensorName}: ${header} disallows cross-origin iframes.`);
+}
diff --git a/generic-sensor/generic-sensor-tests.js b/generic-sensor/generic-sensor-tests.js
index 16d8dc5..da2e65d 100644
--- a/generic-sensor/generic-sensor-tests.js
+++ b/generic-sensor/generic-sensor-tests.js
@@ -1,14 +1,77 @@
+// These tests rely on the User Agent providing an implementation of
+// platform sensor backends.
+//
+// In Chromium-based browsers this implementation is provided by a polyfill
+// in order to reduce the amount of test-only code shipped to users. To enable
+// these tests the browser must be run with these options:
+//
+//   --enable-blink-features=MojoJS,MojoJSTest
+let loadChromiumResources = Promise.resolve().then(() => {
+  if (!MojoInterfaceInterceptor) {
+    // Do nothing on non-Chromium-based browsers or when the Mojo bindings are
+    // not present in the global namespace.
+    return;
+  }
+
+  let chain = Promise.resolve();
+  [
+    '/resources/chromium/mojo_bindings.js',
+    '/resources/chromium/string16.mojom.js',
+    '/resources/chromium/sensor.mojom.js',
+    '/resources/chromium/sensor_provider.mojom.js',
+    '/resources/chromium/generic_sensor_mocks.js',
+  ].forEach(path => {
+    let script = document.createElement('script');
+    script.src = path;
+    script.async = false;
+    chain = chain.then(() => new Promise(resolve => {
+      script.onload = resolve;
+    }));
+    document.head.appendChild(script);
+  });
+
+  return chain;
+});
+
+function sensor_test(func, name, properties) {
+  promise_test(async (t) => {
+    if (typeof GenericSensorTest === 'undefined') {
+      await loadChromiumResources;
+    }
+    assert_true(typeof GenericSensorTest !== 'undefined');
+    let sensorTest = new GenericSensorTest();
+    await sensorTest.initialize();
+    try {
+      await func(t);
+    } finally {
+      await sensorTest.reset();
+    };
+  }, name, properties);
+}
+
 const properties = {
   'AmbientLightSensor' : ['timestamp', 'illuminance'],
   'Accelerometer' : ['timestamp', 'x', 'y', 'z'],
   'LinearAccelerationSensor' : ['timestamp', 'x', 'y', 'z'],
+  "GravitySensor" : ['timestamp', 'x', 'y', 'z'],
   'Gyroscope' : ['timestamp', 'x', 'y', 'z'],
   'Magnetometer' : ['timestamp', 'x', 'y', 'z'],
+  "UncalibratedMagnetometer" : ['timestamp', 'x', 'y', 'z',
+                                'xBias', 'yBias', 'zBias'],
   'AbsoluteOrientationSensor' : ['timestamp', 'quaternion'],
   'RelativeOrientationSensor' : ['timestamp', 'quaternion'],
   'GeolocationSensor' : ['timestamp', 'latitude', 'longitude', 'altitude',
-                         'accuracy', 'altitudeAccuracy', 'heading', 'speed']
+                         'accuracy', 'altitudeAccuracy', 'heading', 'speed'],
+  'ProximitySensor' : ['timestamp', 'max']
 };
+const spatialSensors = ['Accelerometer',
+                       'LinearAccelerationSensor',
+                       'GravitySensor',
+                       'Gyroscope',
+                       'Magnetometer',
+                       'UncalibratedMagnetometer',
+                       'AbsoluteOrientationSensor',
+                       'RelativeOrientationSensor'];
 
 function assert_reading_not_null(sensor) {
   for (let property in properties[sensor.constructor.name]) {
@@ -33,8 +96,11 @@
   return arr;
 }
 
-function runGenericSensorTests(sensorType) {
-  promise_test(async t => {
+function runGenericSensorTests(sensorName) {
+  const sensorType = self[sensorName];
+
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
     sensor.start();
@@ -46,9 +112,10 @@
     sensor.stop();
     assert_reading_null(sensor);
     assert_false(sensor.hasReading);
-  }, `${sensorType.name}: Test that 'onreading' is called and sensor reading is valid`);
+  }, `${sensorName}: Test that 'onreading' is called and sensor reading is valid`);
 
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor1 = new sensorType();
     const sensor2 = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor1, ["reading", "error"]);
@@ -67,9 +134,10 @@
     assert_reading_not_null(sensor2);
     sensor2.stop();
     assert_reading_null(sensor2);
-  }, `${sensorType.name}: sensor reading is correct`);
+  }, `${sensorName}: sensor reading is correct`);
 
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
     sensor.start();
@@ -82,9 +150,10 @@
 
     assert_greater_than(cachedTimeStamp2, cachedTimeStamp1);
     sensor.stop();
-  }, `${sensorType.name}: sensor timestamp is updated when time passes`);
+  }, `${sensorName}: sensor timestamp is updated when time passes`);
 
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
     assert_false(sensor.activated);
@@ -96,9 +165,10 @@
 
     sensor.stop();
     assert_false(sensor.activated);
-  }, `${sensorType.name}: Test that sensor can be successfully created and its states are correct.`);
+  }, `${sensorName}: Test that sensor can be successfully created and its states are correct.`);
 
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
     const start_return = sensor.start();
@@ -106,9 +176,10 @@
     await sensorWatcher.wait_for("activate");
     assert_equals(start_return, undefined);
     sensor.stop();
-  }, `${sensorType.name}: sensor.start() returns undefined`);
+  }, `${sensorName}: sensor.start() returns undefined`);
 
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
     sensor.start();
@@ -117,9 +188,10 @@
     await sensorWatcher.wait_for("activate");
     assert_true(sensor.activated);
     sensor.stop();
-  }, `${sensorType.name}: no exception is thrown when calling start() on already started sensor`);
+  }, `${sensorName}: no exception is thrown when calling start() on already started sensor`);
 
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
     sensor.start();
@@ -127,9 +199,10 @@
     await sensorWatcher.wait_for("activate");
     const stop_return = sensor.stop();
     assert_equals(stop_return, undefined);
-  }, `${sensorType.name}: sensor.stop() returns undefined`);
+  }, `${sensorName}: sensor.stop() returns undefined`);
 
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
     sensor.start();
@@ -138,63 +211,192 @@
     sensor.stop();
     sensor.stop();
     assert_false(sensor.activated);
-  }, `${sensorType.name}: no exception is thrown when calling stop() on already stopped sensor`);
+  }, `${sensorName}: no exception is thrown when calling stop() on already stopped sensor`);
 
-  promise_test(async t => {
-    const iframe = document.createElement('iframe');
-    iframe.srcdoc = '<script>' +
-                    '  window.onmessage = message => {' +
-                    '    if (message.data === "LOADED") {' +
-                    '      try {' +
-                    '        new ' + sensorType.name + '();' +
-                    '        parent.postMessage("FAIL", "*");' +
-                    '      } catch (e) {' +
-                    '        parent.postMessage(e.name, "*");' +
-                    '      }' +
-                    '    }' +
-                    '   };' +
-                    '<\/script>';
-    iframe.onload = () => iframe.contentWindow.postMessage('LOADED', '*');
-    document.body.appendChild(iframe);
-    const sensorWatcher = new EventWatcher(t, window, "message");
-    const message = await sensorWatcher.wait_for("message");
-    assert_equals(message.data, 'SecurityError');
-  }, `${sensorType.name}: throw a 'SecurityError' when constructing sensor object within iframe`);
-
-  promise_test(async t => {
+  sensor_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
-    const visibilityChangeWatcher = new EventWatcher(t, document, "visibilitychange");
+    sensor.start();
+
+    await sensorWatcher.wait_for("reading");
+    assert_true(sensor.hasReading);
+    const timestamp = sensor.timestamp;
+    sensor.stop();
+    assert_false(sensor.hasReading);
+
+    sensor.start();
+    await sensorWatcher.wait_for("reading");
+    assert_true(sensor.hasReading);
+    assert_greater_than(timestamp, 0);
+    assert_greater_than(sensor.timestamp, timestamp);
+    sensor.stop();
+  }, `${sensorName}: Test that fresh reading is fetched on start()`);
+
+//  TBD file a WPT issue: visibilityChangeWatcher times out.
+//  sensor_test(async t => {
+//    const sensor = new sensorType();
+//    const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+//    const visibilityChangeWatcher = new EventWatcher(t, document, "visibilitychange");
+//    sensor.start();
+
+//    await sensorWatcher.wait_for("reading");
+//    assert_reading_not_null(sensor);
+//    const cachedSensor1 = reading_to_array(sensor);
+
+//    const win = window.open('', '_blank');
+//    await visibilityChangeWatcher.wait_for("visibilitychange");
+//    const cachedSensor2 = reading_to_array(sensor);
+
+//    win.close();
+//    sensor.stop();
+//    assert_object_equals(cachedSensor1, cachedSensor2);
+//  }, `${sensorName}: sensor readings can not be fired on the background tab`);
+
+  sensor_test(async t => {
+    assert_true(sensorName in self);
+    const fastSensor = new sensorType({frequency: 30});
+    const slowSensor = new sensorType({frequency: 5});
+    slowSensor.start();
+
+    const fastCounter = await new Promise((resolve, reject) => {
+      let fastCounter = 0;
+      let slowCounter = 0;
+
+      fastSensor.onreading = () => {
+        fastCounter++;
+      }
+      slowSensor.onreading = () => {
+        slowCounter++;
+        if (slowCounter == 1) {
+          fastSensor.start();
+        } else if (slowCounter == 3) {
+          fastSensor.stop();
+          slowSensor.stop();
+          resolve(fastCounter);
+        }
+      }
+      fastSensor.onerror = reject;
+      slowSensor.onerror = reject;
+    });
+    assert_greater_than(fastCounter, 2,
+                        "Fast sensor overtakes the slow one");
+  }, `${sensorName}: frequency hint works`);
+
+  sensor_test(async t => {
+    assert_true(sensorName in self);
+    // Create a focused editbox inside a cross-origin iframe,
+    // sensor notification must suspend.
+    const iframeSrc = 'data:text/html;charset=utf-8,<html><body>'
+                    + '<input type="text" autofocus></body></html>';
+    const iframe = document.createElement('iframe');
+    iframe.src = encodeURI(iframeSrc);
+
+    const sensor = new sensorType();
+    const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
     sensor.start();
 
     await sensorWatcher.wait_for("reading");
     assert_reading_not_null(sensor);
+    const cachedTimestamp = sensor.timestamp;
     const cachedSensor1 = reading_to_array(sensor);
 
-    const win = window.open('', '_blank');
-    await visibilityChangeWatcher.wait_for("visibilitychange");
+    const iframeWatcher = new EventWatcher(t, iframe, "load");
+    document.body.appendChild(iframe);
+    await iframeWatcher.wait_for("load");
     const cachedSensor2 = reading_to_array(sensor);
-
-    win.close();
-    sensor.stop();
     assert_array_equals(cachedSensor1, cachedSensor2);
-  }, `${sensorType.name}: sensor readings can not be fired on the background tab`);
-}
 
-function runGenericSensorInsecureContext(sensorType) {
+    iframe.remove();
+    await sensorWatcher.wait_for("reading");
+    const cachedSensor3 = reading_to_array(sensor);
+    assert_greater_than(sensor.timestamp, cachedTimestamp);
+
+    sensor.stop();
+  }, `${sensorName}: sensor receives suspend / resume notifications when\
+  cross-origin subframe is focused`);
+
+//  Re-enable after https://github.com/w3c/sensors/issues/361 is fixed.
+//  test(() => {
+//     assert_throws("NotSupportedError", () => { new sensorType({invalid: 1}) });
+//     assert_throws("NotSupportedError", () => { new sensorType({frequency: 60, invalid: 1}) });
+//     if (spatialSensors.indexOf(sensorName) == -1) {
+//       assert_throws("NotSupportedError", () => { new sensorType({referenceFrame: "screen"}) });
+//     }
+//  }, `${sensorName}: throw 'NotSupportedError' for an unsupported sensor option`);
+
   test(() => {
-    assert_false(sensorType in window, `${sensorType} must not be exposed`);
-  }, `${sensorType} is not exposed in an insecure context`);
+    assert_true(sensorName in self);
+    const invalidFreqs = [
+      "invalid",
+      NaN,
+      Infinity,
+      -Infinity,
+      {},
+      undefined
+    ];
+    invalidFreqs.map(freq => {
+      assert_throws(new TypeError(),
+                    () => { new sensorType({frequency: freq}) },
+                    `when freq is ${freq}`);
+    });
+  }, `${sensorName}: throw 'TypeError' if frequency is invalid`);
+
+  if (spatialSensors.indexOf(sensorName) == -1) {
+    // The sensorType does not represent a spatial sensor.
+    return;
+  }
+
+  sensor_test(async t => {
+    assert_true(sensorName in self);
+    const sensor = new sensorType({referenceFrame: "screen"});
+    const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+    sensor.start();
+
+    await sensorWatcher.wait_for("reading");
+    //TODO use mock data to verify sensor readings, blocked by issue:
+    // https://github.com/w3c/web-platform-tests/issues/9686
+    assert_reading_not_null(sensor);
+
+    sensor.stop();
+  }, `${sensorName}: sensor reading is correct when options.referenceFrame is 'screen'`);
+
+  test(() => {
+    assert_true(sensorName in self);
+    const invalidRefFrames = [
+      "invalid",
+      null,
+      123,
+      {},
+      "",
+      true
+    ];
+    invalidRefFrames.map(refFrame => {
+      assert_throws(new TypeError(),
+                    () => { new sensorType({referenceFrame: refFrame}) },
+                    `when refFrame is ${refFrame}`);
+    });
+  }, `${sensorName}: throw 'TypeError' if referenceFrame is not one of enumeration values`);
 }
 
-function runGenericSensorOnerror(sensorType) {
+function runGenericSensorInsecureContext(sensorName) {
+  test(() => {
+    assert_false(sensorName in window, `${sensorName} must not be exposed`);
+  }, `${sensorName} is not exposed in an insecure context`);
+}
+
+function runGenericSensorOnerror(sensorName) {
+  const sensorType = self[sensorName];
+
   promise_test(async t => {
+    assert_true(sensorName in self);
     const sensor = new sensorType();
     const sensorWatcher = new EventWatcher(t, sensor, ["error", "activate"]);
     sensor.start();
 
     const event = await sensorWatcher.wait_for("error");
     assert_false(sensor.activated);
-    assert_equals(event.error.name, 'NotReadableError');
-  }, `${sensorType.name}: 'onerror' event is fired when sensor is not supported`);
+    assert_true(event.error.name == 'NotReadableError' ||
+                event.error.name == 'NotAllowedError');
+  }, `${sensorName}: 'onerror' event is fired when sensor is not supported`);
 }
diff --git a/generic-sensor/idlharness.https.html b/generic-sensor/idlharness.https.html
index 7dd10ce..56208e4 100644
--- a/generic-sensor/idlharness.https.html
+++ b/generic-sensor/idlharness.https.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <title>Generic Sensor IDL tests</title>
 <link rel="author" title="Tobie Langel" href="http://www.codespeaks.com">
-<link rel="help" href="http://www.w3.org/TR/generic-sensor/">
+<link rel="help" href="https://w3c.github.io/sensors">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
@@ -27,7 +27,7 @@
 
 promise_test(() => {
   return Promise.all(["/interfaces/dom.idl",
-                      "/interfaces/generic-sensor.idl"].map(fetchText))
+                      "/interfaces/sensors.idl"].map(fetchText))
                 .then(doTest);
 }, "Test IDL implementation of Generic Sensor");
 </script>
diff --git a/geolocation-sensor/GeolocationSensor-disabled-by-feature-policy.https.html b/geolocation-sensor/GeolocationSensor-disabled-by-feature-policy.https.html
new file mode 100644
index 0000000..88f92c6
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-disabled-by-feature-policy.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<body>
+<title>GeolocationSensor Feature Policy Test: Disabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_disabled(GeolocationSensor);
+
+promise_test(async t => {
+  await promise_rejects(t, 'SecurityError', GeolocationSensor.read());
+}, "GeolocationSensor.read(): 'SecurityError' is thrown when disabled by Feature Policy");
+
+promise_test(async t => {
+  const controller = new AbortController();
+  const signal = controller.signal;
+  controller.abort();
+
+  await promise_rejects(t, 'AbortError', GeolocationSensor.read({ signal }));
+}, "GeolocationSensor.read(): 'AbortError' takes priority");
+</script>
+</body>
diff --git a/geolocation-sensor/GeolocationSensor-disabled-by-feature-policy.https.html.headers b/geolocation-sensor/GeolocationSensor-disabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..7e75481
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-disabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation 'none'
diff --git a/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
new file mode 100644
index 0000000..66b7832
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>GeolocationSensor Feature Policy Test: Enabled by attribute redirect on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute_redirect_on_load(GeolocationSensor);
+</script>
+</body>
diff --git a/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy-attribute.https.html b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy-attribute.https.html
new file mode 100644
index 0000000..5ea2518
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy-attribute.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>GeolocationSensor Feature Policy Test: Enabled by attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute(GeolocationSensor);
+</script>
+</body>
diff --git a/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy.https.html b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy.https.html
new file mode 100644
index 0000000..46ef9b7
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>GeolocationSensor Feature Policy Test: Enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled(GeolocationSensor);
+</script>
+</body>
diff --git a/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy.https.html.headers b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..40e9bc1
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-enabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation *
diff --git a/geolocation-sensor/GeolocationSensor-enabled-on-self-origin-by-feature-policy.https.html b/geolocation-sensor/GeolocationSensor-enabled-on-self-origin-by-feature-policy.https.html
new file mode 100644
index 0000000..1694ed0
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-enabled-on-self-origin-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>GeolocationSensor Feature Policy Test: Enabled on self origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_on_self_origin(GeolocationSensor);
+</script>
+</body>
diff --git a/geolocation-sensor/GeolocationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers b/geolocation-sensor/GeolocationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..b83264e
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation 'self'
diff --git a/geolocation-sensor/GeolocationSensor_read.https.html b/geolocation-sensor/GeolocationSensor_read.https.html
new file mode 100644
index 0000000..9eb0339
--- /dev/null
+++ b/geolocation-sensor/GeolocationSensor_read.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>GeolocationSensor.read() Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://wicg.github.io/geolocation-sensor/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const properties = [
+  'timestamp',
+  'latitude',
+  'longitude',
+  'altitude',
+  'accuracy',
+  'altitudeAccuracy',
+  'heading',
+  'speed'
+];
+
+promise_test(async t => {
+  const geo = await GeolocationSensor.read({ signal: null });
+  assert_not_equals(geo.timestamp, null);
+  properties.forEach(property => assert_own_property(geo, property));
+}, "Test that read() method resolves with valid reading when signal is null");
+
+promise_test(async t => {
+  const geo = await GeolocationSensor.read();
+  assert_not_equals(geo.timestamp, null);
+  properties.forEach(property => assert_own_property(geo, property));
+}, "Test that read() method resolves with valid reading");
+
+promise_test(async t => {
+  const controller = new AbortController();
+  const signal = controller.signal;
+  controller.abort();
+
+  await promise_rejects(t, 'AbortError', GeolocationSensor.read({ signal }));
+}, "Test that read() method rejects 'AbortError' if signal's aborted flag is set");
+</script>
diff --git a/geolocation-sensor/idlharness.https.html b/geolocation-sensor/idlharness.https.html
index 183b3f8..75d29eb 100644
--- a/geolocation-sensor/idlharness.https.html
+++ b/geolocation-sensor/idlharness.https.html
@@ -14,7 +14,7 @@
   const idl_array = new IdlArray();
   idl_array.add_untested_idls('interface EventTarget {};');
   idl_array.add_untested_idls('interface EventHandler {};');
-  idl_array.add_idls(generic_sensor, { only: ['Sensor'] });
+  idl_array.add_idls(generic_sensor, { only: ['Sensor', 'SensorOptions'] });
   idl_array.add_idls(geolocation_sensor);
   idl_array.add_objects({
     GeolocationSensor: ['new GeolocationSensor'],
@@ -28,7 +28,7 @@
 
 promise_test(() => {
   return Promise.all([
-    "/interfaces/generic-sensor.idl",
+    "/interfaces/sensors.idl",
     "/interfaces/geolocation-sensor.idl"
   ].map(fetchText)).then(doTest);
 }, "Test IDL implementation of Geolocation Sensor");
diff --git a/graphics-aam/OWNERS b/graphics-aam/OWNERS
new file mode 100644
index 0000000..2a4c47e
--- /dev/null
+++ b/graphics-aam/OWNERS
@@ -0,0 +1,2 @@
+@halindrome
+@joanmarie
diff --git a/graphics-aam/graphics-document_on_html_element-manual.html b/graphics-aam/graphics-document_on_html_element-manual.html
new file mode 100644
index 0000000..cc9b407
--- /dev/null
+++ b/graphics-aam/graphics-document_on_html_element-manual.html
@@ -0,0 +1,102 @@
+<!doctype html>
+<html>
+  <head>
+    <title>graphics-document on HTML element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_DOCUMENT_FRAME"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-document"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXRole",
+                  "is",
+                  "AXGroup"
+               ],
+               [
+                  "property",
+                  "AXSubrole",
+                  "is",
+                  "AXDocument"
+               ],
+               [
+                  "property",
+                  "AXRoleDescription",
+                  "is",
+                  "document"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_SYSTEM_DOCUMENT"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-document"
+               ],
+               [
+                  "property",
+                  "states",
+                  "contains",
+                  "STATE_SYSTEM_READONLY"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "Document"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "graphics-document on HTML element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for graphics-document on HTML element.</p>
+    <div id="test" role="graphics-document" aria-label="house">
+    <div role="graphics-object" aria-label="door"></div>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/graphics-aam/graphics-document_on_svg_element-manual.html b/graphics-aam/graphics-document_on_svg_element-manual.html
new file mode 100644
index 0000000..1b61446
--- /dev/null
+++ b/graphics-aam/graphics-document_on_svg_element-manual.html
@@ -0,0 +1,105 @@
+<!doctype html>
+<html>
+  <head>
+    <title>graphics-document on SVG element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_DOCUMENT_FRAME"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-document"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXRole",
+                  "is",
+                  "AXGroup"
+               ],
+               [
+                  "property",
+                  "AXSubrole",
+                  "is",
+                  "AXDocument"
+               ],
+               [
+                  "property",
+                  "AXRoleDescription",
+                  "is",
+                  "document"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_SYSTEM_DOCUMENT"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-document"
+               ],
+               [
+                  "property",
+                  "states",
+                  "contains",
+                  "STATE_SYSTEM_READONLY"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "Document"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "graphics-document on SVG element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for graphics-document on SVG element.</p>
+    <svg xmlns="http://www.w3.org/2000/svg" id="test" role="graphics-document">
+    <g role="graphics-object" aria-label="door">
+      <rect fill="darkKhaki" stroke="#632" width="50" height="90" />
+      <circle fill="gray" stroke="#444" stroke-width="0.7" cx="10" cy="50" r="4" />
+    </g>
+  </svg>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/graphics-aam/graphics-object_on_html_element-manual.html b/graphics-aam/graphics-object_on_html_element-manual.html
new file mode 100644
index 0000000..074fe51
--- /dev/null
+++ b/graphics-aam/graphics-object_on_html_element-manual.html
@@ -0,0 +1,96 @@
+<!doctype html>
+<html>
+  <head>
+    <title>graphics-object on HTML element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_PANEL"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-object"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXRole",
+                  "is",
+                  "AXGroup"
+               ],
+               [
+                  "property",
+                  "AXSubrole",
+                  "is",
+                  "<nil>"
+               ],
+               [
+                  "property",
+                  "AXRoleDescription",
+                  "is",
+                  "group"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_SYSTEM_GROUPING"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-object"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "Group"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "graphics-object on HTML element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for graphics-object on HTML element.</p>
+    <div role="graphics-document" aria-label="house">
+    <div id="test" role="graphics-object" aria-label="door"></div>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/graphics-aam/graphics-object_on_svg_element-manual.html b/graphics-aam/graphics-object_on_svg_element-manual.html
new file mode 100644
index 0000000..d326ec5
--- /dev/null
+++ b/graphics-aam/graphics-object_on_svg_element-manual.html
@@ -0,0 +1,99 @@
+<!doctype html>
+<html>
+  <head>
+    <title>graphics-object on SVG element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_PANEL"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-object"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXRole",
+                  "is",
+                  "AXGroup"
+               ],
+               [
+                  "property",
+                  "AXSubrole",
+                  "is",
+                  "<nil>"
+               ],
+               [
+                  "property",
+                  "AXRoleDescription",
+                  "is",
+                  "group"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_SYSTEM_GROUPING"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-object"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "Group"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "graphics-object on SVG element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for graphics-object on SVG element.</p>
+    <svg xmlns="http://www.w3.org/2000/svg" role="graphics-document">
+    <g id="test" role="graphics-object" aria-label="door">
+      <rect fill="darkKhaki" stroke="#632" width="50" height="90" />
+      <circle fill="gray" stroke="#444" stroke-width="0.7" cx="10" cy="50" r="4" />
+    </g>
+  </svg>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/graphics-aam/graphics-symbol_on_html_element-manual.html b/graphics-aam/graphics-symbol_on_html_element-manual.html
new file mode 100644
index 0000000..913a7d1
--- /dev/null
+++ b/graphics-aam/graphics-symbol_on_html_element-manual.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<html>
+  <head>
+    <title>graphics-symbol on HTML element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_IMAGE"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-symbol"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXRole",
+                  "is",
+                  "AXImage"
+               ],
+               [
+                  "property",
+                  "AXSubrole",
+                  "is",
+                  "<nil>"
+               ],
+               [
+                  "property",
+                  "AXRoleDescription",
+                  "is",
+                  "image"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_SYSTEM_GRAPHIC"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-symbol"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "Image"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "graphics-symbol on HTML element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for graphics-symbol on HTML element.</p>
+    <div>Spinach Salad with Strawberry &amp; Almonds
+    <span id="test" aria-label="vegetarian" role="graphics-symbol">🌿</span>
+    <span aria-label="contains nuts" role="graphics-symbol">🌰</span>
+  </div>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/graphics-aam/graphics-symbol_on_svg_element-manual.html b/graphics-aam/graphics-symbol_on_svg_element-manual.html
new file mode 100644
index 0000000..7884826
--- /dev/null
+++ b/graphics-aam/graphics-symbol_on_svg_element-manual.html
@@ -0,0 +1,98 @@
+<!doctype html>
+<html>
+  <head>
+    <title>graphics-symbol on SVG element</title>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
+    <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/wai-aria/scripts/ATTAcomm.js"></script>
+    <script>
+    setup({explicit_timeout: true, explicit_done: true });
+
+    var theTest = new ATTAcomm(
+    {
+   "steps" : [
+      {
+         "element" : "test",
+         "test" : {
+            "ATK" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_IMAGE"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-symbol"
+               ]
+            ],
+            "AXAPI" : [
+               [
+                  "property",
+                  "AXRole",
+                  "is",
+                  "AXImage"
+               ],
+               [
+                  "property",
+                  "AXSubrole",
+                  "is",
+                  "<nil>"
+               ],
+               [
+                  "property",
+                  "AXRoleDescription",
+                  "is",
+                  "image"
+               ]
+            ],
+            "IAccessible2" : [
+               [
+                  "property",
+                  "role",
+                  "is",
+                  "ROLE_SYSTEM_GRAPHIC"
+               ],
+               [
+                  "property",
+                  "objectAttributes",
+                  "contains",
+                  "xml-roles:graphics-symbol"
+               ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "Image"
+               ]
+            ]
+         },
+         "title" : "step 1",
+         "type" : "test"
+      }
+   ],
+   "title" : "graphics-symbol on SVG element"
+}
+
+    ) ;
+    </script>
+  </head>
+  <body>
+  <p>This test examines the ARIA properties for graphics-symbol on SVG element.</p>
+    <svg xmlns="http://www.w3.org/2000/svg">
+    <g id="test" role="graphics-symbol" aria-label="lightbulb">
+      <circle r="10" />
+    </g>
+  </svg>
+
+  <div id="manualMode"></div>
+  <div id="log"></div>
+  <div id="ATTAmessages"></div>
+  </body>
+</html>
diff --git a/gyroscope/Gyroscope-disabled-by-feature-policy.https.html b/gyroscope/Gyroscope-disabled-by-feature-policy.https.html
new file mode 100644
index 0000000..02cc44e
--- /dev/null
+++ b/gyroscope/Gyroscope-disabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>Gyroscope Feature Policy Test: Disabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_disabled('Gyroscope');
+</script>
+</body>
diff --git a/gyroscope/Gyroscope-disabled-by-feature-policy.https.html.headers b/gyroscope/Gyroscope-disabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..3d91d58
--- /dev/null
+++ b/gyroscope/Gyroscope-disabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: gyroscope 'none'
diff --git a/gyroscope/Gyroscope-enabled-by-feature-policy-attribute-redirect-on-load.https.html b/gyroscope/Gyroscope-enabled-by-feature-policy-attribute-redirect-on-load.https.html
new file mode 100644
index 0000000..e1aa0f9
--- /dev/null
+++ b/gyroscope/Gyroscope-enabled-by-feature-policy-attribute-redirect-on-load.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>Gyroscope Feature Policy Test: Enabled by attribute redirect on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute_redirect_on_load('Gyroscope');
+</script>
+</body>
diff --git a/gyroscope/Gyroscope-enabled-by-feature-policy-attribute.https.html b/gyroscope/Gyroscope-enabled-by-feature-policy-attribute.https.html
new file mode 100644
index 0000000..17e5b27
--- /dev/null
+++ b/gyroscope/Gyroscope-enabled-by-feature-policy-attribute.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>Gyroscope Feature Policy Test: Enabled by attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute('Gyroscope');
+</script>
+</body>
diff --git a/gyroscope/Gyroscope-enabled-by-feature-policy.https.html b/gyroscope/Gyroscope-enabled-by-feature-policy.https.html
new file mode 100644
index 0000000..9bcced9
--- /dev/null
+++ b/gyroscope/Gyroscope-enabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>Gyroscope Feature Policy Test: Enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled('Gyroscope');
+</script>
+</body>
diff --git a/gyroscope/Gyroscope-enabled-by-feature-policy.https.html.headers b/gyroscope/Gyroscope-enabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..0fd938b
--- /dev/null
+++ b/gyroscope/Gyroscope-enabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: gyroscope *
diff --git a/gyroscope/Gyroscope-enabled-on-self-origin-by-feature-policy.https.html b/gyroscope/Gyroscope-enabled-on-self-origin-by-feature-policy.https.html
new file mode 100644
index 0000000..76d1c5e
--- /dev/null
+++ b/gyroscope/Gyroscope-enabled-on-self-origin-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>Gyroscope Feature Policy Test: Enabled on self origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_on_self_origin('Gyroscope');
+</script>
+</body>
diff --git a/gyroscope/Gyroscope-enabled-on-self-origin-by-feature-policy.https.html.headers b/gyroscope/Gyroscope-enabled-on-self-origin-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..7cf4fd8
--- /dev/null
+++ b/gyroscope/Gyroscope-enabled-on-self-origin-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: gyroscope 'self'
diff --git a/gyroscope/Gyroscope.https.html b/gyroscope/Gyroscope.https.html
index 81cdfdd..0c3f08f 100644
--- a/gyroscope/Gyroscope.https.html
+++ b/gyroscope/Gyroscope.https.html
@@ -9,6 +9,6 @@
 <div id="log"></div>
 <script>
 
-runGenericSensorTests(Gyroscope);
+runGenericSensorTests('Gyroscope');
 
 </script>
diff --git a/gyroscope/Gyroscope_onerror-manual.https.html b/gyroscope/Gyroscope_onerror-manual.https.html
index f012615..b63448b 100644
--- a/gyroscope/Gyroscope_onerror-manual.https.html
+++ b/gyroscope/Gyroscope_onerror-manual.https.html
@@ -15,6 +15,6 @@
 </ol>
 <script>
 
-runGenericSensorOnerror(Gyroscope);
+runGenericSensorOnerror('Gyroscope');
 
 </script>
diff --git a/gyroscope/idlharness.https.html b/gyroscope/idlharness.https.html
index 39b44c2..4b3ed48 100644
--- a/gyroscope/idlharness.https.html
+++ b/gyroscope/idlharness.https.html
@@ -15,7 +15,7 @@
   const idl_array = new IdlArray();
   idl_array.add_untested_idls(dom);
   idl_array.add_untested_idls('interface EventHandler {};');
-  idl_array.add_idls(generic_sensor, { only: ['Sensor'] });
+  idl_array.add_idls(generic_sensor, { only: ['Sensor', 'SensorOptions'] });
   idl_array.add_idls(gyroscope);
   idl_array.add_objects({
     Gyroscope: ['new Gyroscope();']
@@ -30,7 +30,7 @@
 promise_test(() => {
   return Promise.all([
     "/interfaces/dom.idl",
-    "/interfaces/generic-sensor.idl",
+    "/interfaces/sensors.idl",
     "/interfaces/gyroscope.idl",
   ].map(fetchText)).then(doTest);
 }, "Test IDL implementation of Gyroscope Sensor");
diff --git a/hr-time/idlharness.html b/hr-time/idlharness.html
index c3fd907..d28cee3 100644
--- a/hr-time/idlharness.html
+++ b/hr-time/idlharness.html
@@ -16,14 +16,10 @@
 <script>
 'use strict';
 
-function doTest([html, hr_time]) {
+function doTest([dom, html, hr_time]) {
   var idl_array = new IdlArray();
-  // HTML is needed for WindowOrWorkerGlobalScope. Provide dummy interfaces for
-  // things that HTML depends on in turn which are not under tests.
-  idl_array.add_untested_idls('interface Document {};');
-  idl_array.add_untested_idls('interface EventTarget {};');
-  idl_array.add_untested_idls('interface LinkStyle {};');
-  idl_array.add_untested_idls(html);
+  idl_array.add_untested_idls(dom, { only: ['EventTarget'] });
+  idl_array.add_untested_idls(html, { only: ['WindowOrWorkerGlobalScope'] });
   idl_array.add_idls(hr_time);
   idl_array.add_objects({
     Performance: ["window.performance"],
@@ -37,7 +33,8 @@
 }
 
 promise_test(() => {
-  return Promise.all(['/interfaces/html.idl',
+  return Promise.all(['/interfaces/dom.idl',
+                      '/interfaces/html.idl',
                       '/interfaces/hr-time.idl'].map(fetchText))
                 .then(doTest);
 }, 'Test driver');
diff --git a/hr-time/performance-tojson.html b/hr-time/performance-tojson.html
new file mode 100644
index 0000000..fd8049c
--- /dev/null
+++ b/hr-time/performance-tojson.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+test(() => {
+  // Check Performance attributes.
+  assert_equals(typeof(performance.toJSON), 'function');
+  const json = performance.toJSON();
+  assert_equals(typeof(json), 'object');
+  assert_equals(json.timeOrigin, performance.timeOrigin,
+    'performance.toJSON().timeOrigin should match performance.timeOrigin');
+
+  // Check PerformanceTiming toJSON.
+  const jsonTiming = json.timing;
+  const timing = performance.timing;
+  assert_equals(typeof(timing.toJSON), 'function');
+  const timingJSON = timing.toJSON();
+  assert_equals(typeof(timingJSON), 'object');
+  // Check PerformanceTiming attributes, from both:
+  // 1) |jsonTiming| from  Performance.
+  // 2) |timingJSON| from PerformanceTiming.
+  const performanceTimingKeys = [
+    'navigationStart',
+    'unloadEventStart',
+    'unloadEventEnd',
+    'redirectStart',
+    'redirectEnd',
+    'fetchStart',
+    'domainLookupStart',
+    'domainLookupEnd',
+    'connectStart',
+    'connectEnd',
+    'secureConnectionStart',
+    'requestStart',
+    'responseStart',
+    'responseEnd',
+    'domLoading',
+    'domInteractive',
+    'domContentLoadedEventStart',
+    'domContentLoadedEventEnd',
+    'domComplete',
+    'loadEventStart',
+    'loadEventEnd'
+  ];
+  for (const key of performanceTimingKeys) {
+    assert_equals(jsonTiming[key], timing[key],
+      `performance.toJSON().timing.${key} should match performance.timing.${key}`);
+    assert_equals(timingJSON[key], timing[key],
+      `performance.timing.toJSON().${key} should match performance.timing.${key}`);
+  }
+
+  // Check PerformanceNavigation toJSON.
+  const jsonNavigation = json.navigation;
+  const navigation = performance.navigation;
+  assert_equals(typeof(navigation.toJSON), 'function');
+  const navigationJSON = navigation.toJSON();
+  assert_equals(typeof(navigationJSON), 'object');
+  // Check PerformanceNavigation attributes, from both:
+  // 1) |jsonNavigation| from  Performance.
+  // 2) |navigationJSON| from PerformanceNavigation.
+  let performanceNavigationKeys = ['type', 'redirectCount'];
+  for (const key of performanceNavigationKeys) {
+    assert_equals(jsonNavigation[key], navigation[key],
+      `performance.toJSON().navigation.${key} should match performance.navigation.${key}`);
+    assert_equals(navigationJSON[key], navigation[key],
+      `performance.navigation.toJSON().${key} should match performance.navigation.${key}`);
+  }
+}, 'Test performance.toJSON()');
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/html/browsers/browsing-the-web/history-traversal/PopStateEvent.html b/html/browsers/browsing-the-web/history-traversal/PopStateEvent.html
index 85205c1..8db1d27 100644
--- a/html/browsers/browsing-the-web/history-traversal/PopStateEvent.html
+++ b/html/browsers/browsing-the-web/history-traversal/PopStateEvent.html
@@ -11,6 +11,11 @@
 }, 'initPopStateEvent');
 
 test(function () {
+  var popStateEvent = new PopStateEvent("popstate");
+  assert_equals(popStateEvent.state, null, "the PopStateEvent.state");
+}, "Initial value of PopStateEvent.state must be null");
+
+test(function () {
   var state = history.state;
   var data;
   window.addEventListener('popstate', function (e) {
diff --git a/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_2.html b/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_2.html
index d7fae15..f1be043 100644
--- a/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_2.html
+++ b/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_2.html
@@ -6,25 +6,21 @@
 <pre id="step_log"></pre>
 <iframe id="test"></iframe>
 <script>
-
 var t = async_test(undefined, {timeout:10000});
 var f = document.getElementById("test");
 var l = document.getElementById("step_log");
-var navigated = false;
 
 log = function(t) {l.textContent += ("\n" + t)}
 
 var steps = [
   function() {f.src = "browsing_context_name-1.html"},
   function() {
-                navigated = true;
                 assert_equals(f.contentWindow.name, "test", "Initial load");
                 setTimeout(next, 0);
               },
   function() {f.src = "browsing_context_name-3.html"},
   function() {
-                var navigated = true;
-                assert_equals(f.contentWindow.name, "test3", "Initial load");
+                assert_equals(f.contentWindow.name, "test3", "After navigation 1");
                 setTimeout(next, 0);
               },
   function() {f.src = f.src.replace("http://", "http://www.").replace("browsing_context_name-3", "browsing_context_name-2");},
@@ -33,10 +29,10 @@
              },
   function() {history.go(-2); setTimeout(next, 500)},
   function() {
-               assert_equals(f.contentWindow.name, "test3", "After navigation");
+               assert_equals(f.contentWindow.name, "test3", "After navigation 2");
                t.done();
              }
-].map(function(x) {return t.step_func(function() {log("Step " + step); x()})});
+].map(function(x) {return t.step_func(function() {log("Step " + step + " " + f.contentWindow.location); x()})});
 
 var step = 0;
 next = t.step_func(function() {steps[step++]()});
diff --git a/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_3.html b/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_3.html
index ea4cd0c..88b0578 100644
--- a/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_3.html
+++ b/html/browsers/browsing-the-web/history-traversal/browsing_context_name_cross_origin_3.html
@@ -9,31 +9,28 @@
 var t = async_test(undefined, {timeout:10000});
 var f = document.getElementById("test");
 var l = document.getElementById("step_log");
-var navigated = false;
 
 log = function(t) {l.textContent += ("\n" + t)}
 
 var steps = [
   function() {f.src = "browsing_context_name-1.html"},
   function() {
-                navigated = true;
                 assert_equals(f.contentWindow.name, "test", "Initial load");
                 setTimeout(next, 0);
               },
   function() {f.src = "browsing_context_name-3.html"},
   function() {
-                var navigated = true;
-                assert_equals(f.contentWindow.name, "test3", "Initial load");
+                assert_equals(f.contentWindow.name, "test3", "After navigation 1");
                 setTimeout(next, 0);
               },
   function() {f.src = f.src.replace("http://", "http://www.").replace("browsing_context_name-1", "browsing_context_name-2");},
   function() {f.src = f.src.replace("http://www.", "http://").replace("browsing_context_name-2", "browsing_context_name-4");},
   function() {
-                assert_equals(f.contentWindow.name, "test3", "After navigation");
+                assert_equals(f.contentWindow.name, "test3", "After navigation 2");
                 history.go(-3); setTimeout(next, 500)
              },
   function() {
-               assert_equals(f.contentWindow.name, "test3", "After navigation");
+               assert_equals(f.contentWindow.name, "test3", "After navigation 3");
                t.done();
              }
 ].map(function(x) {return t.step_func(function() {log("Step " + step + " " + f.contentWindow.location); x()})});
diff --git a/html/browsers/browsing-the-web/history-traversal/hashchange_event.html b/html/browsers/browsing-the-web/history-traversal/hashchange_event.html
index 4b701ad..287e7a6 100644
--- a/html/browsers/browsing-the-web/history-traversal/hashchange_event.html
+++ b/html/browsers/browsing-the-web/history-traversal/hashchange_event.html
@@ -18,6 +18,12 @@
 
   location.hash = 'foo';
   window.onhashchange = t.step_func(function (e) {
+    assert_true(e.isTrusted);
+    assert_equals(e.target, window);
+    assert_equals(e.type, "hashchange");
+    assert_true(e instanceof HashChangeEvent);
+    assert_true(e.bubbles, "bubble");
+    assert_false(e.cancelable, "cancelable");
     oldURLs.push(e.oldURL);
     newURLs.push(e.newURL);
     if (newURLs.length === 2) {
diff --git a/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html b/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html
index 4594a1e..7d9a31d 100644
--- a/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html
+++ b/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html
@@ -1,20 +1,18 @@
 <!DOCTYPE html>
 <meta name=timeout content=long>
 <title>Precedence of scroll restoration mode over fragment scrolling in cross-origin history traversal</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
 <style>
   iframe {
     height: 300px;
     width: 300px;
   }
 </style>
-
-<body>
-  <iframe></iframe>
-</body>
-
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="text/javascript">
+<div id="log"></div>
+<iframe></iframe>
+<script>
   'use strict';
 
   // The test does the following navigation steps for iframe
@@ -23,13 +21,16 @@
   // 3. go back to page-with-fragment.html
   async_test(function(t) {
     var iframe = document.querySelector('iframe');
-    var baseURL = location.href.substring(0, location.href.lastIndexOf('/'));
+    var hostInfo = get_host_info();
+    var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/'));
+    var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment';
+    var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html"
 
     var steps = [
       function() {
         iframe.src = 'resources/page-with-fragment.html#fragment';
       }, function() {
-        assert_equals(iframe.contentWindow.location.href, baseURL + '/resources/page-with-fragment.html#fragment', 'should be on page-with-fragment page');
+        assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page');
         // wait one animation frame to ensure layout is run and fragment scrolling is complete
         iframe.contentWindow.requestAnimationFrame(function() {
           assert_equals(iframe.contentWindow.scrollY, 800, 'should scroll to fragment');
@@ -40,13 +41,13 @@
         });
       }, function() {
         // navigate to a new page from a different origin
-        iframe.src = iframe.src.replace("http://", "http://www.").replace("page-with-fragment.html#fragment", "blank1.html");
+        iframe.src = remoteURL;
       }, function() {
         // going back causes the iframe to traverse back
         history.back();
       }, function() {
         // coming back from history, scrollRestoration should be set to manual and respected
-        assert_equals(iframe.contentWindow.location.href, baseURL + '/resources/page-with-fragment.html#fragment', 'should be back on page-with-fragment page');
+        assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page');
         iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() {
           assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value');
           assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment');
diff --git a/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html
new file mode 100644
index 0000000..f626a79
--- /dev/null
+++ b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>Aborting fetch for javascript:string navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigate">
+<link rel="help" href="https://github.com/whatwg/html/issues/2590">
+<div id="log"></div>
+<iframe src="support/iframe-and-links.html"></iframe>
+<script>
+async_test(test => {
+  onload = () => {
+    const child = document.querySelector('iframe').contentWindow;
+    child.document.querySelector("#slowLink").click();
+    // The step below is in a timeout. The framed page can't communicate back mid-parse because that
+    // would involve running script, which makes that navigation "mature", and we need to do this
+    // before it matures.
+    test.step_timeout(() => {
+      child.document.querySelector("#javascriptStringLink").click();
+      child.document.querySelector("iframe").onload = test.step_func_done(() => {
+        assert_false(child.childLoaded, 'child.childLoaded');
+      });
+    }, 100);
+  };
+  window.javascriptStringDocLoaded = test.step_func(() => {
+    assert_unreached("javascript: URL doc replaced the document, should be targeted to child iframe.");
+  });
+});
+</script>
diff --git a/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-undefined.tentative.html b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-undefined.tentative.html
new file mode 100644
index 0000000..80a0d27
--- /dev/null
+++ b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-undefined.tentative.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>Not aborting fetch for javascript:undefined navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigate">
+<link rel="help" href="https://github.com/whatwg/html/issues/2590">
+<div id="log"></div>
+<iframe src="support/iframe-and-links.html"></iframe>
+<script>
+async_test(test => {
+  onload = () => {
+    const child = document.querySelector('iframe').contentWindow;
+    child.document.querySelector("#slowLink").click();
+    // The step below is in a timeout. The framed page can't communicate back mid-parse because that
+    // would involve running script, which makes that navigation "mature", and we need to do this
+    // before it matures.
+    test.step_timeout(() => {
+      child.document.querySelector("#javascriptUndefinedLink").click();
+      child.document.querySelector("iframe").onload = test.step_func_done(() => {
+        assert_true(child.childLoaded, 'child.childLoaded');
+      });
+    }, 100);
+  };
+});
+</script>
diff --git a/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/iframe-and-links.html b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/iframe-and-links.html
new file mode 100644
index 0000000..545b098
--- /dev/null
+++ b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/iframe-and-links.html
@@ -0,0 +1,18 @@
+<!doctype html>
+
+<iframe name="iframe"></iframe>
+
+<!-- slow link's response is delayed by 1 second -->
+<!-- https://wptserve.readthedocs.io/en/latest/pipes.html#trickle -->
+<a target="iframe" href="set-child-loaded.html?pipe=trickle(d1)" id="slowLink">slow link</a>
+<a target="iframe" href="javascript:'javascript:string <script> parent.javascriptStringDocLoaded(); </script>'" id="javascriptStringLink">javascript:string link</a>
+<a target="iframe" href="javascript:undefined" id="javascriptUndefinedLink">javascript:undefined link</a>
+
+<script>
+// set-child-loaded.html (the slow link) sets this to true.
+window.childLoaded = false;
+
+// Do nothing when the javascript:string doc has loaded, if it's correctly targeted to the above iframe.
+// However, if it replaces this document, it needs to fail the test (handled in the parent).
+function javascriptStringDocLoaded() {}
+</script>
diff --git a/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/set-child-loaded.html b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/set-child-loaded.html
new file mode 100644
index 0000000..a4b34ad
--- /dev/null
+++ b/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/support/set-child-loaded.html
@@ -0,0 +1,5 @@
+<!doctype html>
+set-child-loaded.html
+<script>
+parent.childLoaded = true;
+</script>
diff --git a/html/browsers/browsing-the-web/navigating-across-documents/refresh/README.md b/html/browsers/browsing-the-web/navigating-across-documents/refresh/README.md
new file mode 100644
index 0000000..52548db
--- /dev/null
+++ b/html/browsers/browsing-the-web/navigating-across-documents/refresh/README.md
@@ -0,0 +1 @@
+See `/html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/parsing.html` for more detailed parsing tests (shared with `<meta http-equiv=refresh>`).
diff --git a/http/refresh/navigate.window.js b/html/browsers/browsing-the-web/navigating-across-documents/refresh/navigate.window.js
similarity index 100%
rename from http/refresh/navigate.window.js
rename to html/browsers/browsing-the-web/navigating-across-documents/refresh/navigate.window.js
diff --git a/http/refresh/resources/multiple.asis b/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/multiple.asis
similarity index 100%
rename from http/refresh/resources/multiple.asis
rename to html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/multiple.asis
diff --git a/http/refresh/resources/refresh.py b/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh.py
similarity index 100%
rename from http/refresh/resources/refresh.py
rename to html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh.py
diff --git a/http/refresh/resources/refreshed.txt b/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refreshed.txt
similarity index 100%
rename from http/refresh/resources/refreshed.txt
rename to html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refreshed.txt
diff --git a/http/refresh/subresource.any.js b/html/browsers/browsing-the-web/navigating-across-documents/refresh/subresource.any.js
similarity index 100%
rename from http/refresh/subresource.any.js
rename to html/browsers/browsing-the-web/navigating-across-documents/refresh/subresource.any.js
diff --git a/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html b/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html
new file mode 100644
index 0000000..4d81119
--- /dev/null
+++ b/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media documents: image</title>
+  <link rel="author" title="Takayoshi Kochi" href="mailto:kochi@chromium.org">
+  <link rel="author" title="Michael Ventnor" href="mailto:mventnor@mozilla.com">
+  <link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+  <link rel="help" href="https://html.spec.whatwg.org/multipage/#read-media">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+<script>
+  var t = async_test("The document for a standalone media file should have one child in the body.");
+
+  var imgwin = window.open('/images/blue.png');
+  // imgwin.onload doesn't work, check popup's URL to see if the loading of
+  // the image and creation of image document is finished.
+  function checkURL() {
+    if (imgwin.location.href.indexOf('blue.png') == -1) {
+      step_timeout(checkURL, 100);
+      return;
+    }
+    t.step(frameLoaded);
+  }
+  checkURL();
+
+  function frameLoaded() {
+    assert_equals(imgwin.opener, window);
+    assert_equals(imgwin.document.contentType, "image/png");
+    var imgwinChildren = imgwin.document.body.childNodes;
+    assert_equals(imgwinChildren.length, 1, "Body of image document has 1 child");
+    assert_equals(imgwinChildren[0].nodeName, "IMG", "Only child of body must be an <img> element");
+    assert_equals(imgwinChildren[0].namespaceURI, "http://www.w3.org/1999/xhtml",
+                  "Only child of body must be an HTML element");
+    imgwin.close();
+    t.done();
+  }
+</script>
+</head>
+<body>
+  <div id="log"></div>
+</body>
+</html>
diff --git a/html/browsers/browsing-the-web/read-media/pageload-video.html b/html/browsers/browsing-the-web/read-media/pageload-video.html
index 69ef741..1ae414c 100644
--- a/html/browsers/browsing-the-web/read-media/pageload-video.html
+++ b/html/browsers/browsing-the-web/read-media/pageload-video.html
@@ -1,30 +1,27 @@
 <!DOCTYPE HTML>
-<html>
-<head>
-  <title>Media documents: video</title>
-  <link rel="author" title="Michael Ventnor" href="mailto:mventnor@mozilla.com">
-  <link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
-  <link rel="help" href="https://html.spec.whatwg.org/multipage/#read-media">
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-
+<meta charset="utf-8">
+<title>Media documents: video</title>
+<link rel="author" title="Michael Ventnor" href="mailto:mventnor@mozilla.com">
+<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#read-media">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
 <script>
-  var t = async_test("The document for a standalone media file should have one child in the body.");
-
-  function frameLoaded() {
-    var testframe = document.getElementById('testframe');
-    assert_equals(testframe.contentDocument.contentType, "video/webm");
+async_test(function() {
+  var testframe = document.createElement('iframe');
+  var url = getVideoURI("/media/A4");
+  var contentType = getMediaContentType(url);
+  testframe.onload = this.step_func_done(function() {
+    assert_equals(testframe.contentDocument.contentType, contentType);
     var testframeChildren = testframe.contentDocument.body.childNodes;
     assert_equals(testframeChildren.length, 1, "Body of image document has 1 child");
     assert_equals(testframeChildren[0].nodeName, "VIDEO", "Only child of body must be an <video> element");
     assert_equals(testframeChildren[0].namespaceURI, "http://www.w3.org/1999/xhtml",
                   "Only child of body must be an HTML element");
-    t.done();
-  }
+  });
+  testframe.src = url;
+  document.body.appendChild(testframe);
+}, "The document for a standalone media file should have one child in the body.");
 </script>
-</head>
-<body>
-  <div id="log"></div>
-  <iframe id="testframe" onload="t.step(frameLoaded)" src="/media/white.webm"></iframe>
-</body>
-</html>
diff --git a/html/browsers/browsing-the-web/scroll-to-fragid/003.html b/html/browsers/browsing-the-web/scroll-to-fragid/003.html
index 200a6dc..86d85b9 100644
--- a/html/browsers/browsing-the-web/scroll-to-fragid/003.html
+++ b/html/browsers/browsing-the-web/scroll-to-fragid/003.html
@@ -10,14 +10,14 @@
 <div id="test1">scroll 2</div>
 <script>
 test(function() {
-  assert_equals(document.body.scrollTop, 0);
+  assert_equals(document.scrollingElement.scrollTop, 0);
   location.hash = "test";
 
-  var scroll1 = document.body.scrollTop;
+  var scroll1 = document.scrollingElement.scrollTop;
   assert_true(scroll1 > 0);
 
   location.hash = "test1";
-  var scroll2 = document.body.scrollTop;
+  var scroll2 = document.scrollingElement.scrollTop;
   assert_true(scroll2 > scroll1);
 
   location.hash = ""
diff --git a/html/browsers/browsing-the-web/scroll-to-fragid/replacement-enabled.html b/html/browsers/browsing-the-web/scroll-to-fragid/replacement-enabled.html
index f684bcb..b22fbed 100644
--- a/html/browsers/browsing-the-web/scroll-to-fragid/replacement-enabled.html
+++ b/html/browsers/browsing-the-web/scroll-to-fragid/replacement-enabled.html
@@ -57,7 +57,7 @@
     // we automatically assume navigation succeeded after 100 ms. A sibling test will test this
     // particular Firefox bug.
     await navigateAndWaitForChange(frameWindow, w => w.history.forward(),
-      { assumeSuccessAfter: 100 });
+      { assumeSuccessAfter: 500 });
 
     assert_equals(frameWindow.location.hash, "#cat");
     assert_equals(frameWindow.history.length, afterThreeNavigations,
diff --git a/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html b/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html
index 43dbaf9..060aed1 100644
--- a/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html
+++ b/html/browsers/browsing-the-web/scroll-to-fragid/scroll-to-anchor-name.html
@@ -8,6 +8,7 @@
 <a name="anchor1" style="position:absolute; top:200px;"></a>
 <div id="id-equals-anchor" style="position:absolute; top:300px;"></div>
 <a name="id-equals-anchor" style="position:absolute; top:400px;"></a>
+<a name="§1" style="position:absolute; top:400px;"></a>
 <div style="height:200em;"></div>
 <script>
 var steps = [{
@@ -21,6 +22,11 @@
         // id still takes precedence over anchor name
         assert_equals( scrollPosition(), 300 );
       }
+    },{
+      fragid:'§1',
+      handler: function(){
+        assert_equals( scrollPosition(), 400 );
+      }
     }];
 
 function scrollPosition(){
diff --git a/html/browsers/browsing-the-web/unloading-documents/unload/007.html b/html/browsers/browsing-the-web/unloading-documents/unload/007.html
index 0d5b72e..4a2fed5 100644
--- a/html/browsers/browsing-the-web/unloading-documents/unload/007.html
+++ b/html/browsers/browsing-the-web/unloading-documents/unload/007.html
@@ -4,17 +4,17 @@
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
-var t = async_test(undefined, {timeout:2000});
+var t = async_test();
 
 var loaded = false;
 var unload_fired = false;
 var timeout_fired = false;
 
 function start_test() {
-  setTimeout(t.step_func(function() {
-                           assert_true(unload_fired);
-                           assert_false(timeout_fired);
-                           t.done()
+  step_timeout(t.step_func(function() {
+                             assert_true(unload_fired);
+                             assert_false(timeout_fired);
+                             t.done()
                          }), 1000);
 }
 
diff --git a/html/browsers/history/joint-session-history/joint-session-history-filler.html b/html/browsers/history/joint-session-history/joint-session-history-filler.html
new file mode 100644
index 0000000..b6d47c3
--- /dev/null
+++ b/html/browsers/history/joint-session-history/joint-session-history-filler.html
@@ -0,0 +1 @@
+<body>Filler</body>
diff --git a/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html b/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html
new file mode 100644
index 0000000..b66adcb
--- /dev/null
+++ b/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Joint session history length does not include entries from a removed iframe.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe id="frame" src="about:blank"></iframe>
+<script>
+async_test(function(t) {
+    t.step_timeout(() => {
+        var child = document.getElementById("frame");
+        var old_history_len = history.length;
+        child.onload = () => {
+            assert_equals(old_history_len + 1, history.length);
+            document.body.removeChild(document.getElementById("frame"));
+            assert_equals(old_history_len, history.length);
+            t.done();
+        }
+        child.src = "joint-session-history-filler.html";
+    }, 1000);
+});
+</script>
+</body>
diff --git a/html/browsers/history/the-history-interface/history_pushstate_url.html b/html/browsers/history/the-history-interface/history_pushstate_url.html
new file mode 100644
index 0000000..cfbca35
--- /dev/null
+++ b/html/browsers/history/the-history-interface/history_pushstate_url.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>History pushState sets the url</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+async_test(function(t) {
+    var oldLocation = window.location.toString();
+    window.history.pushState(null, "", "#hash");
+    assert_equals(oldLocation + "#hash", window.location.toString(), "pushState updates url");
+    history.back();
+    window.onhashchange = () => {
+        assert_equals(oldLocation, window.location.toString(), 'history traversal restores old url');
+        t.done();
+    };
+}, "history pushState sets url");
+</script>
+</body>
+</html>
diff --git a/html/browsers/offline/appcache/workers/appcache-worker.html b/html/browsers/offline/appcache/workers/appcache-worker.https.html
similarity index 100%
rename from html/browsers/offline/appcache/workers/appcache-worker.html
rename to html/browsers/offline/appcache/workers/appcache-worker.https.html
diff --git a/html/browsers/offline/application-cache-api/api_status_idle.html b/html/browsers/offline/application-cache-api/api_status_idle.https.html
similarity index 100%
rename from html/browsers/offline/application-cache-api/api_status_idle.html
rename to html/browsers/offline/application-cache-api/api_status_idle.https.html
diff --git a/html/browsers/offline/application-cache-api/api_status_uncached.html b/html/browsers/offline/application-cache-api/api_status_uncached.https.html
similarity index 100%
rename from html/browsers/offline/application-cache-api/api_status_uncached.html
rename to html/browsers/offline/application-cache-api/api_status_uncached.https.html
diff --git a/html/browsers/offline/application-cache-api/api_swapcache_error.html b/html/browsers/offline/application-cache-api/api_swapcache_error.https.html
similarity index 100%
rename from html/browsers/offline/application-cache-api/api_swapcache_error.html
rename to html/browsers/offline/application-cache-api/api_swapcache_error.https.html
diff --git a/html/browsers/offline/application-cache-api/api_update.html b/html/browsers/offline/application-cache-api/api_update.https.html
similarity index 100%
rename from html/browsers/offline/application-cache-api/api_update.html
rename to html/browsers/offline/application-cache-api/api_update.https.html
diff --git a/html/browsers/offline/application-cache-api/api_update_error.html b/html/browsers/offline/application-cache-api/api_update_error.https.html
similarity index 100%
rename from html/browsers/offline/application-cache-api/api_update_error.html
rename to html/browsers/offline/application-cache-api/api_update_error.https.html
diff --git a/html/browsers/offline/application-cache-api/secure_context.html b/html/browsers/offline/application-cache-api/secure_context.html
new file mode 100644
index 0000000..b9ccc1f
--- /dev/null
+++ b/html/browsers/offline/application-cache-api/secure_context.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script>
+    test(t => {
+      assert_false('applicationCache' in window);
+      assert_equals(window.applicationCache, undefined);
+    }, "window.applicationCache does not exist in non-secure contexts.");
+
+    test(t => {
+      assert_false('ApplicationCache' in window);
+      assert_equals(typeof ApplicationCache, "undefined");
+    }, "ApplicationCache interface does not exist in non-secure contexts.");
+
+    test(t => {
+      assert_false('ApplicationCacheErrorEvent' in window);
+      assert_equals(typeof ApplicationCacheErrorEvent, "undefined");
+    }, "ApplicationCacheErrorEvent interface does not exist in non-secure contexts.");
+  </script>
+</head>
+</html>
diff --git a/html/browsers/offline/browser-state/navigator_online_event-manual.html b/html/browsers/offline/browser-state/navigator_online_event-manual.html
deleted file mode 100644
index b100f17..0000000
--- a/html/browsers/offline/browser-state/navigator_online_event-manual.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE HTML>
-<html>
- <head>
-  <title>Offline Application Cache</title>
-  <link rel="stylesheet" href="../resources/css/result.css">
- </head>
- <body>
-  <h1>navigator_online_event</h1>
-
-  <ol>
-  <li>Change the 'work offline' mode.</li>
-  <li>If actual result and expected result are same, then test is <span class="manualpass">Pass</span>, otherwise <span class="manualfail">Fail</span>.</li>
-  </ol>
-
-  <hr>
-
-  <h2>Actual Result</h2>
-  <div id="actualResult">
-    <span id="actualMsg"></span>
-  </div>
-
-  <h2>Expected Result</h2>
-  <div id="expectedResult">
-    <span id="expectedMsg">apply 'work offline': offline event is raised.<p>release 'work offline': online event is raised.</span>
-  </div>
-  <script>
-
-  function showOnline() {
-    document.getElementById('actualMsg').innerHTML = 'online event is raised.';
-  }
-
-  function showOffline() {
-    document.getElementById('actualMsg').innerHTML = 'offline event is raised.';
-  }
-
-  window.addEventListener("online", showOnline, false);
-  window.addEventListener("offline", showOffline, false);
-  </script>
- </body>
-</html>
diff --git a/html/browsers/offline/browser-state/navigator_online_event-manual.https.html b/html/browsers/offline/browser-state/navigator_online_event-manual.https.html
new file mode 100644
index 0000000..81cad4f
--- /dev/null
+++ b/html/browsers/offline/browser-state/navigator_online_event-manual.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+  <title>Offline Application Cache</title>
+  <link rel="stylesheet" href="../resources/css/result.css">
+ </head>
+ <body>
+  <h1>navigator_online_event</h1>
+
+  <ol>
+  <li>Change the 'work offline' mode.</li>
+  <li>If actual result and expected result are same, then test is <span class="manualpass">Pass</span>, otherwise <span class="manualfail">Fail</span>.</li>
+  </ol>
+
+  <hr>
+
+  <h2>Actual Result</h2>
+  <div id="actualResult">
+    <span id="actualMsg"></span>
+  </div>
+
+  <h2>Expected Result</h2>
+  <div id="expectedResult">
+    <span id="expectedMsg">apply 'work offline': offline event is raised.<p>release 'work offline': online event is raised.</span>
+  </div>
+  <script>
+
+  function showOnline(e) {
+    let msg = 'online event is raised';
+    if (e.target != window)
+      msg += ' (on the WRONG target)';
+    document.getElementById('actualMsg').innerHTML = msg + '.';
+  }
+
+  function showOffline(e) {
+    let msg = 'offline event is raised';
+    if (e.target != window)
+      msg += ' (on the WRONG target)';
+    document.getElementById('actualMsg').innerHTML = msg + '.';
+  }
+
+  window.addEventListener("online", showOnline, false);
+  window.addEventListener("offline", showOffline, false);
+  </script>
+ </body>
+</html>
diff --git a/html/browsers/offline/browser-state/navigator_online_online.html b/html/browsers/offline/browser-state/navigator_online_online.https.html
similarity index 100%
rename from html/browsers/offline/browser-state/navigator_online_online.html
rename to html/browsers/offline/browser-state/navigator_online_online.https.html
diff --git a/html/browsers/offline/introduction-4/event_cached.html b/html/browsers/offline/introduction-4/event_cached.https.html
similarity index 100%
rename from html/browsers/offline/introduction-4/event_cached.html
rename to html/browsers/offline/introduction-4/event_cached.https.html
diff --git a/html/browsers/offline/introduction-4/event_checking.html b/html/browsers/offline/introduction-4/event_checking.https.html
similarity index 100%
rename from html/browsers/offline/introduction-4/event_checking.html
rename to html/browsers/offline/introduction-4/event_checking.https.html
diff --git a/html/browsers/offline/introduction-4/event_noupdate.html b/html/browsers/offline/introduction-4/event_noupdate.https.html
similarity index 100%
rename from html/browsers/offline/introduction-4/event_noupdate.html
rename to html/browsers/offline/introduction-4/event_noupdate.https.html
diff --git a/html/browsers/offline/introduction-4/event_progress.html b/html/browsers/offline/introduction-4/event_progress.https.html
similarity index 100%
rename from html/browsers/offline/introduction-4/event_progress.html
rename to html/browsers/offline/introduction-4/event_progress.https.html
diff --git a/html/browsers/offline/manifest_main_empty-manual.html b/html/browsers/offline/manifest_main_empty-manual.https.html
similarity index 100%
rename from html/browsers/offline/manifest_main_empty-manual.html
rename to html/browsers/offline/manifest_main_empty-manual.https.html
diff --git a/html/browsers/offline/manifest_notchanged_online-manual.html b/html/browsers/offline/manifest_notchanged_online-manual.https.html
similarity index 100%
rename from html/browsers/offline/manifest_notchanged_online-manual.html
rename to html/browsers/offline/manifest_notchanged_online-manual.https.html
diff --git a/html/browsers/offline/manifest_section_empty-manual.html b/html/browsers/offline/manifest_section_empty-manual.https.html
similarity index 100%
rename from html/browsers/offline/manifest_section_empty-manual.html
rename to html/browsers/offline/manifest_section_empty-manual.https.html
diff --git a/html/browsers/offline/manifest_section_many-manual.html b/html/browsers/offline/manifest_section_many-manual.https.html
similarity index 100%
rename from html/browsers/offline/manifest_section_many-manual.html
rename to html/browsers/offline/manifest_section_many-manual.https.html
diff --git a/html/browsers/offline/manifest_url_check.html b/html/browsers/offline/manifest_url_check.https.https.html
similarity index 100%
rename from html/browsers/offline/manifest_url_check.html
rename to html/browsers/offline/manifest_url_check.https.https.html
diff --git a/html/browsers/offline/no-appcache-in-shared-workers-historical.html b/html/browsers/offline/no-appcache-in-shared-workers-historical.https.html
similarity index 100%
rename from html/browsers/offline/no-appcache-in-shared-workers-historical.html
rename to html/browsers/offline/no-appcache-in-shared-workers-historical.https.html
diff --git a/html/browsers/offline/section_network_offline-manual.html b/html/browsers/offline/section_network_offline-manual.https.html
similarity index 100%
rename from html/browsers/offline/section_network_offline-manual.html
rename to html/browsers/offline/section_network_offline-manual.https.html
diff --git a/html/browsers/offline/section_network_online-manual.html b/html/browsers/offline/section_network_online-manual.https.html
similarity index 100%
rename from html/browsers/offline/section_network_online-manual.html
rename to html/browsers/offline/section_network_online-manual.https.html
diff --git a/html/browsers/origin/cross-origin-objects/cross-origin-objects.html b/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
index 2820b75..caac56a 100644
--- a/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
+++ b/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
@@ -11,6 +11,7 @@
 <div id=log></div>
 <iframe id="B"></iframe>
 <iframe id="C"></iframe>
+<iframe id="D"></iframe>
 <script>
 
 /*
@@ -21,10 +22,13 @@
 
 setup({explicit_done: true});
 path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html';
+pathWithThen = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame-with-then.html';
 var B = document.getElementById('B').contentWindow;
 var C = document.getElementById('C').contentWindow;
+var D = document.getElementById('D').contentWindow;
 B.frameElement.uriToLoad = path;
 C.frameElement.uriToLoad = get_host_info().HTTP_REMOTE_ORIGIN + path;
+D.frameElement.uriToLoad = get_host_info().HTTP_REMOTE_ORIGIN + pathWithThen;
 
 function reloadSubframes(cb) {
   var iframes = document.getElementsByTagName('iframe');
@@ -45,8 +49,8 @@
  */
 
 var testList = [];
-function addTest(fun, desc) { testList.push([fun, desc]); }
-
+function addTest(func, desc) { testList.push({func, desc, promiseTest: false}); }
+function addPromiseTest(func, desc) { testList.push({func, desc, promiseTest: true}); }
 
 /*
  * Basic sanity testing.
@@ -72,10 +76,10 @@
 
 var whitelistedWindowIndices = ['0', '1'];
 var whitelistedWindowPropNames = ['location', 'postMessage', 'window', 'frames', 'self', 'top', 'parent',
-                                  'opener', 'closed', 'close', 'blur', 'focus', 'length'];
+                                  'opener', 'closed', 'close', 'blur', 'focus', 'length', 'then'];
 whitelistedWindowPropNames = whitelistedWindowPropNames.concat(whitelistedWindowIndices);
 whitelistedWindowPropNames.sort();
-var whitelistedLocationPropNames = ['href', 'replace'];
+var whitelistedLocationPropNames = ['href', 'replace', 'then'];
 whitelistedLocationPropNames.sort();
 var whitelistedSymbols = [Symbol.toStringTag, Symbol.hasInstance,
                           Symbol.isConcatSpreadable];
@@ -123,11 +127,11 @@
  * [[GetPrototypeOf]]
  */
 addTest(function() {
-  assert_true(Object.getPrototypeOf(C) === null, "cross-origin Window proto is null");
-  assert_true(Object.getPrototypeOf(C.location) === null, "cross-origin Location proto is null (__proto__)");
+  assert_equals(Object.getPrototypeOf(C), null, "cross-origin Window proto is null");
+  assert_equals(Object.getPrototypeOf(C.location), null, "cross-origin Location proto is null (__proto__)");
   var protoGetter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').get;
-  assert_true(protoGetter.call(C) === null, "cross-origin Window proto is null");
-  assert_true(protoGetter.call(C.location) === null, "cross-origin Location proto is null (__proto__)");
+  assert_equals(protoGetter.call(C), null, "cross-origin Window proto is null");
+  assert_equals(protoGetter.call(C.location), null, "cross-origin Location proto is null (__proto__)");
   assert_throws("SecurityError", function() { C.__proto__; }, "__proto__ property not available cross-origin");
   assert_throws("SecurityError", function() { C.location.__proto__; }, "__proto__ property not available cross-origin");
 
@@ -191,7 +195,7 @@
   assert_equals(desc.configurable, true, "property descriptor for " + propName + " should be configurable");
   if (!isArrayIndexPropertyName) {
     assert_equals(desc.enumerable, false, "property descriptor for " + propName + " should not be enumerable");
-    if(isSymbol) {
+    if (isSymbol || propName == "then") {
       assert_true("value" in desc,
                   "property descriptor for " + propName + " should be a value descriptor");
       assert_equals(desc.value, undefined,
@@ -222,6 +226,10 @@
   });
 }, "[[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly");
 
+addTest(function() {
+  assert_equals(typeof D.then, "object");
+}, "[[GetOwnProperty]] - Subframe named 'then' should shadow the default 'then' value");
+
 /*
  * [[Delete]]
  */
@@ -309,6 +317,10 @@
   indexedWindowProps = allWindowProps.slice(0, whitelistedWindowIndices.length);
   stringWindowProps = allWindowProps.slice(0, -1 * whitelistedSymbols.length);
   symbolWindowProps = allWindowProps.slice(-1 * whitelistedSymbols.length);
+  // stringWindowProps should have "then" last in this case.  Do this
+  // check before we call stringWindowProps.sort() below.
+  assert_equals(stringWindowProps[stringWindowProps.length - 1], "then",
+                "'then' property should be added to the end of the string list if not there");
   assert_array_equals(indexedWindowProps, whitelistedWindowIndices,
                       "Reflect.ownKeys should start with the indices exposed on the cross-origin window.");
   assert_array_equals(stringWindowProps.sort(), whitelistedWindowPropNames,
@@ -326,8 +338,19 @@
 }, "[[OwnPropertyKeys]] should place the symbols after the property names after the subframe indices");
 
 addTest(function() {
-  assert_true(B.eval('parent.C') === C, "A and B observe the same identity for C's Window");
-  assert_true(B.eval('parent.C.location') === C.location, "A and B observe the same identity for C's Location");
+  var stringProps = Object.getOwnPropertyNames(D);
+  // Named frames are not exposed via [[OwnPropertyKeys]].
+  assert_equals(stringProps.indexOf("a"), -1);
+  assert_equals(stringProps.indexOf("b"), -1);
+  assert_equals(typeof D.a, "object");
+  assert_equals(typeof D.b, "object");
+  assert_equals(stringProps[stringProps.length - 1], "then");
+  assert_equals(stringProps.indexOf("then"), stringProps.lastIndexOf("then"));
+}, "[[OwnPropertyKeys]] should not reorder where 'then' appears if it's a named subframe, nor add another copy of 'then'");
+
+addTest(function() {
+  assert_equals(B.eval('parent.C'), C, "A and B observe the same identity for C's Window");
+  assert_equals(B.eval('parent.C.location'), C.location, "A and B observe the same identity for C's Location");
 }, "A and B jointly observe the same identity for cross-origin Window and Location");
 
 function checkFunction(f, proto) {
@@ -350,17 +373,17 @@
 
 addTest(function() {
   checkFunction(close, Function.prototype);
-  assert_true(close != B.close, 'same-origin Window functions get their own object');
-  assert_true(close != C.close, 'cross-origin Window functions get their own object');
+  assert_not_equals(close, B.close, 'same-origin Window functions get their own object');
+  assert_not_equals(close, C.close, 'cross-origin Window functions get their own object');
   var close_B = B.eval('parent.C.close');
-  assert_true(close != close_B, 'close_B is unique when viewed by the parent');
-  assert_true(close_B != C.close, 'different Window functions per-incumbent script settings object');
+  assert_not_equals(close, close_B, 'close_B is unique when viewed by the parent');
+  assert_not_equals(close_B, C.close, 'different Window functions per-incumbent script settings object');
   checkFunction(close_B, B.Function.prototype);
 
   checkFunction(location.replace, Function.prototype);
-  assert_true(location.replace != C.location.replace, "cross-origin Location functions get their own object");
+  assert_not_equals(location.replace, C.location.replace, "cross-origin Location functions get their own object");
   var replace_B = B.eval('parent.C.location.replace');
-  assert_true(replace_B != C.location.replace, 'different Location functions per-incumbent script settings object');
+  assert_not_equals(replace_B, C.location.replace, 'different Location functions per-incumbent script settings object');
   checkFunction(replace_B, B.Function.prototype);
 }, "Same-origin observers get different functions for cross-origin objects");
 
@@ -370,8 +393,8 @@
   var get_self_parent = Object.getOwnPropertyDescriptor(window, 'parent').get;
   var get_parent_A = Object.getOwnPropertyDescriptor(C, 'parent').get;
   var get_parent_B = B.eval('Object.getOwnPropertyDescriptor(parent.C, "parent").get');
-  assert_true(get_self_parent != get_parent_A, 'different Window accessors per-incumbent script settings object');
-  assert_true(get_parent_A != get_parent_B, 'different Window accessors per-incumbent script settings object');
+  assert_not_equals(get_self_parent, get_parent_A, 'different Window accessors per-incumbent script settings object');
+  assert_not_equals(get_parent_A, get_parent_B, 'different Window accessors per-incumbent script settings object');
   checkFunction(get_self_parent, Function.prototype);
   checkFunction(get_parent_A, Function.prototype);
   checkFunction(get_parent_B, B.Function.prototype);
@@ -381,8 +404,8 @@
   var set_self_href = Object.getOwnPropertyDescriptor(window.location, 'href').set;
   var set_href_A = Object.getOwnPropertyDescriptor(C.location, 'href').set;
   var set_href_B = B.eval('Object.getOwnPropertyDescriptor(parent.C.location, "href").set');
-  assert_true(set_self_href != set_href_A, 'different Location accessors per-incumbent script settings object');
-  assert_true(set_href_A != set_href_B, 'different Location accessors per-incumbent script settings object');
+  assert_not_equals(set_self_href, set_href_A, 'different Location accessors per-incumbent script settings object');
+  assert_not_equals(set_href_A, set_href_B, 'different Location accessors per-incumbent script settings object');
   checkFunction(set_self_href, Function.prototype);
   checkFunction(set_href_A, Function.prototype);
   checkFunction(set_href_B, B.Function.prototype);
@@ -393,16 +416,43 @@
   assert_equals({}.toString.call(C.location), "[object Object]");
 }, "{}.toString.call() does the right thing on cross-origin objects");
 
+addPromiseTest(function() {
+  return Promise.resolve(C).then((arg) => {
+    assert_equals(arg, C);
+  });
+}, "Resolving a promise with a cross-origin window without a 'then' subframe should work.");
+
+addPromiseTest(function() {
+  return Promise.resolve(D).then((arg) => {
+    assert_equals(arg, D);
+  });
+}, "Resolving a promise with a cross-origin window with a 'then' subframe should work.");
+
+addPromiseTest(function() {
+  return Promise.resolve(D.location).then((arg) => {
+    assert_equals(arg, D.location);
+  });
+}, "Resolving a promise with a cross-origin location should work.");
+
 // We do a fresh load of the subframes for each test to minimize side-effects.
 // It would be nice to reload ourselves as well, but we can't do that without
 // disrupting the test harness.
+function testDone() {
+  if (testList.length != 0) {
+    reloadSubframes(runNextTest);
+  } else {
+    done();
+  }
+}
+
 function runNextTest() {
   var entry = testList.shift();
-  test(() => entry[0](), entry[1])
-  if (testList.length != 0)
-    reloadSubframes(runNextTest);
-  else
-    done();
+  if (entry.promiseTest) {
+    promise_test(() => entry.func().finally(testDone), entry.desc);
+  } else {
+    test(entry.func, entry.desc);
+    testDone();
+  }
 }
 reloadSubframes(runNextTest);
 
diff --git a/html/browsers/origin/cross-origin-objects/frame-with-then.html b/html/browsers/origin/cross-origin-objects/frame-with-then.html
new file mode 100644
index 0000000..96cdf1e
--- /dev/null
+++ b/html/browsers/origin/cross-origin-objects/frame-with-then.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+  <body>
+    <!--- Some frames to test ordering -->
+    <iframe name="a"></iframe>
+    <!-- A subframe to test "then" behavior -->
+    <iframe name="then"></iframe>
+    <iframe name="b"></iframe>
+  </body>
+</html>
diff --git a/html/browsers/origin/cross-origin-objects/frame.html b/html/browsers/origin/cross-origin-objects/frame.html
index 341da6a..0d81624 100644
--- a/html/browsers/origin/cross-origin-objects/frame.html
+++ b/html/browsers/origin/cross-origin-objects/frame.html
@@ -6,6 +6,10 @@
   // properly ignored cross-origin.
   window.frames = "override";
 
+  // Also add a |then| property to test that it doesn't get exposed.
+  window.then = "something";
+  window.location.then = "something-else";
+
   // If we get a postMessage, we grab references to everything and set
   // document.domain to trim off our topmost subdomain.
   window.onmessage = function(evt) {
diff --git a/html/browsers/origin/origin-of-data-document.html b/html/browsers/origin/origin-of-data-document.html
index 345a3a6..d6b6d53 100644
--- a/html/browsers/origin/origin-of-data-document.html
+++ b/html/browsers/origin/origin-of-data-document.html
@@ -20,16 +20,7 @@
           assert_throws("SecurityError", function () {
             var couldAccessCrossOriginProperty = e.source.location.href;
           }, "The 'data:' frame should be cross-origin: 'window.location.href'");
-
-          // Try to access contentDocument of the 'data: ' frame. Some browsers
-          // (i.e. Firefox, Safari) will return |null| and some (i.e. Chrome)
-          // will throw an exception.
-          var dataFrameContentDocument = null;
-          try {
-            dataFrameContentDocument = i.contentDocument;
-          } catch (ex) {
-          }
-          assert_equals(dataFrameContentDocument, null, "The 'data:' iframe should be unable to access its contentDocument.");
+          assert_equals(i.contentDocument, null, "The 'data:' iframe should be unable to access its contentDocument.");
         }));
 
         document.body.appendChild(i);
diff --git a/html/browsers/origin/relaxing-the-same-origin-restriction/.gitkeep b/html/browsers/origin/relaxing-the-same-origin-restriction/.gitkeep
deleted file mode 100644
index e69de29..0000000
--- a/html/browsers/origin/relaxing-the-same-origin-restriction/.gitkeep
+++ /dev/null
diff --git a/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html b/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html
index bf5b0bc..6ffea4f 100644
--- a/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html
+++ b/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html
@@ -15,7 +15,7 @@
       var SUFFIX_HOST = ORIGINAL_HOST.substring(ORIGINAL_HOST.lastIndexOf('.') + 1); // e.g. "test"
       var PREFIX_HOST = "www1." + ORIGINAL_HOST; // e.g. "www1.web-platform.test"
       var iframe = document.getElementById("iframe");
-      var iframe_url = new URL("document_domain_setter_iframe.html", document.location);
+      var iframe_url = new URL("support/document_domain_setter_iframe.html", document.location);
       iframe_url.hostname = PREFIX_HOST;
       iframe.src = iframe_url;
       test(function() {
@@ -25,11 +25,11 @@
         assert_throws("SecurityError", function() { document.domain = "example.com"; });
       }, "failed setting of document.domain");
       async_test(function(t) {
-        iframe.addEventListener("load", t.step_func(function() {
+        iframe.addEventListener("load", t.step_func_done(function() {
           // Before setting document.domain, the iframe is not
           // same-origin-domain, so security checks fail.
           assert_equals(iframe.contentDocument, null);
-          assert_equals(iframe.contentWindow.frameElement, null);
+          assert_throws("SecurityError", () => iframe.contentWindow.frameElement);
           assert_throws("SecurityError", function() { iframe.contentWindow.location.origin; });
           assert_throws("SecurityError", function() { iframe.contentWindow.location.href; });
           assert_throws("SecurityError", function() { iframe.contentWindow.location.protocol; });
@@ -45,8 +45,8 @@
           // After setting document.domain, the iframe is
           // same-origin-domain, so security checks pass.
           assert_equals(iframe.contentDocument.domain, document.domain);
-          assert_equals(iframe.contentWindow.frameElement, iframe)
-          assert_equals(iframe.contentWindow.origin, window.origin);
+          assert_equals(iframe.contentWindow.frameElement, iframe);
+          assert_equals(iframe.contentWindow.origin, iframe_url.origin);
           assert_equals(iframe.contentWindow.location.href, iframe_url.href);
           assert_equals(iframe.contentWindow.location.protocol, iframe_url.protocol);
           assert_equals(iframe.contentWindow.location.host, iframe_url.host);
@@ -60,7 +60,6 @@
           // document.open checks for same-origin, not same-origin-domain,
           // https://github.com/whatwg/html/issues/2282
           assert_throws("SecurityError", function() { iframe.contentDocument.open(); });
-          t.done();
         }));
       }, "same-origin-domain iframe");
     </script>
diff --git a/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_iframe.html b/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html
similarity index 100%
rename from html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_iframe.html
rename to html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html
diff --git a/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-non-integer-height.html b/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-non-integer-height.html
index 08d127e..cd2d019 100644
--- a/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-non-integer-height.html
+++ b/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-non-integer-height.html
@@ -40,7 +40,7 @@
       var prefixedMessage = new PrefixedMessageTest();
       prefixedMessage.onMessage(t.step_func_done((data, e) => {
         e.source.close();
-        assert_equals(data.width, baselineDimensions.width);
+        assert_equals(data.height, baselineDimensions.height);
       }));
       var win = window.open(prefixedMessage.url(windowURL), '', feature);
     }, `${feature}: absence of feature "height" should be treated same as "height=0"`);
diff --git a/html/browsers/the-window-object/security-window/window-security.html b/html/browsers/the-window-object/security-window/window-security.html
deleted file mode 100644
index 8b067da..0000000
--- a/html/browsers/the-window-object/security-window/window-security.html
+++ /dev/null
@@ -1,202 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>HTML Test: Window Security</title>
-<link rel="author" title="Intel" href="http://www.intel.com/" />
-<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#the-window-object" />
-<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/timers.html#timers" />
-<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/webappapis.html#atob" />
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowsessionstorage" />
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowlocalstorage" />
-<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#window" />
-<link rel="help" href="http://dev.w3.org/csswg/cssom/#extensions-to-the-window-interface" />
-<link rel="help" href="http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface" />
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/common/get-host-info.sub.js"></script>
-<div id="log"></div>
-<script>
-var t = async_test("Window Security testing");
-
-function fr_load() {
-  fr = document.getElementById("fr");
-
-  t.step(function () {
-    //SecurityError should be thrown
-    [
-      //attributes
-      {name: "applicationCache"},
-      {name: "devicePixelRatio"},
-      {name: "document"},
-      {name: "external"},
-      {name: "frameElement"},
-      {name: "history"},
-      {name: "innerWidth"},
-      {name: "innerHeight"},
-      {name: "locationbar"},
-      {name: "localStorage"},
-      {name: "menubar"},
-      {name: "name"},
-      {name: "navigator"},
-      {name: "onabort"},
-      {name: "onafterprint"},
-      {name: "onbeforeprint"},
-      {name: "onbeforeunload"},
-      {name: "onblur"},
-      {name: "oncancel"},
-      {name: "oncanplay"},
-      {name: "oncanplaythrough"},
-      {name: "onchange"},
-      {name: "onclick"},
-      {name: "onclose"},
-      {name: "oncontextmenu"},
-      {name: "oncuechange"},
-      {name: "ondblclick"},
-      {name: "ondrag"},
-      {name: "ondragend"},
-      {name: "ondragenter"},
-      {name: "ondragleave"},
-      {name: "ondragover"},
-      {name: "ondragstart"},
-      {name: "ondrop"},
-      {name: "ondurationchange"},
-      {name: "onemptied"},
-      {name: "onended"},
-      {name: "onerror"},
-      {name: "onfocus"},
-      {name: "onhashchange"},
-      {name: "oninput"},
-      {name: "oninvalid"},
-      {name: "onkeydown"},
-      {name: "onkeypress"},
-      {name: "onkeyup"},
-      {name: "onload"},
-      {name: "onloadeddata"},
-      {name: "onloadedmetadata"},
-      {name: "onloadstart"},
-      {name: "onmessage"},
-      {name: "onmousedown"},
-      {name: "onmousemove"},
-      {name: "onmouseout"},
-      {name: "onmouseover"},
-      {name: "onmouseup"},
-      {name: "onmousewheel"},
-      {name: "onoffline"},
-      {name: "ononline"},
-      {name: "onpause"},
-      {name: "onplay"},
-      {name: "onplaying"},
-      {name: "onpagehide"},
-      {name: "onpageshow"},
-      {name: "onpopstate"},
-      {name: "onprogress"},
-      {name: "onratechange"},
-      {name: "onreset"},
-      {name: "onresize"},
-      {name: "onscroll"},
-      {name: "onseeked"},
-      {name: "onseeking"},
-      {name: "onselect"},
-      {name: "onshow"},
-      {name: "onstalled"},
-      {name: "onstorage"},
-      {name: "onsubmit"},
-      {name: "onsuspend"},
-      {name: "ontimeupdate"},
-      {name: "onunload"},
-      {name: "onvolumechange"},
-      {name: "onwaiting"},
-      {name: "pageXOffset"},
-      {name: "pageYOffset"},
-      {name: "personalbar"},
-      {name: "screen"},
-      {name: "scrollbars"},
-      {name: "statusbar"},
-      {name: "status"},
-      {name: "screenX"},
-      {name: "screenY"},
-      {name: "sessionStorage"},
-      {name: "toolbar"},
-      //methods
-      {name: "alert", isMethod: true},
-      {name: "clearInterval", isMethod: true, args:[1]},
-      {name: "clearTimeout", isMethod: true, args:[function () {}, 1]},
-      {name: "confirm", isMethod: true},
-      {name: "getComputedStyle", isMethod: true, args:[document.body, null]},
-      {name: "getSelection", isMethod: true},
-      {name: "matchMedia", isMethod: true, args:["(min-width:50px)"]},
-      {name: "moveBy", isMethod: true, args:[10, 10]},
-      {name: "moveTo", isMethod: true, args:[10, 10]},
-      {name: "open", isMethod: true},
-      {name: "print", isMethod: true},
-      {name: "prompt", isMethod: true},
-      {name: "resizeTo", isMethod: true, args:[10, 10]},
-      {name: "resizeBy", isMethod: true, args:[10, 10]},
-      {name: "scroll", isMethod: true, args:[10, 10]},
-      {name: "scrollTo", isMethod: true, args:[10, 10]},
-      {name: "scrollBy", isMethod: true, args:[10, 10]},
-      {name: "setInterval", isMethod: true, args:[function () {}, 1]},
-      {name: "setTimeout", isMethod: true, args:[function () {}, 1]},
-      {name: "stop", isMethod: true},
-    ].forEach(function (item) {
-      test(function () {
-        assert_true(item.name in window, "window." + item.name + " should exist.");
-        assert_throws("SecurityError", function () {
-          if (item.isMethod)
-            if (item.args)
-              fr.contentWindow[item.name](item.args[0], item.args[1]);
-            else
-              fr.contentWindow[item.name]();
-          else
-            fr.contentWindow[item.name];
-        }, "A SecurityError exception should be thrown.");
-      }, "A SecurityError exception must be thrown when window." + item.name + " is accessed from a different origin.");
-    });
-
-    //SecurityError should not be thrown
-    [
-      //attributes
-      {name: "closed"},
-      {name: "frames"},
-      {name: "length"},
-      {name: "location"},
-      {name: "opener"},
-      {name: "parent"},
-      {name: "self"},
-      {name: "top"},
-      {name: "window"},
-      //methods
-      {name: "blur", isMethod: true},
-      {name: "close", isMethod: true},
-      {name: "focus", isMethod: true},
-      {name: "postMessage", isMethod: true, args: [{msg: 'foo'}, "*"]}
-    ].forEach(function (item) {
-      test(function () {
-        assert_true(item.name in window, "window." + item.name + " should exist.");
-        try {
-          if (item.isMethod)
-            if (item.args)
-              fr.contentWindow[item.name](item.args[0], item.args[1]);
-            else
-               fr.contentWindow[item.name]();
-          else
-            fr.contentWindow[item.name];
-        } catch (e) {
-          assert_unreached("An unexpected exception was thrown.");
-        }
-      }, "A SecurityError exception should not be thrown when window." + item.name + " is accessed from a different origin.");
-    });
-  });
-  t.done();
-}
-
-</script>
-<script>
-onload = function() {
-  var frame = document.createElement('iframe');
-  frame.id = "fr";
-  frame.setAttribute("style", "display:none");
-  frame.setAttribute('src', get_host_info().HTTP_REMOTE_ORIGIN + "/");
-  frame.setAttribute("onload", "fr_load()");
-  document.body.appendChild(frame);
-}
-</script>
diff --git a/html/browsers/the-window-object/security-window/window-security.https.html b/html/browsers/the-window-object/security-window/window-security.https.html
new file mode 100644
index 0000000..9126014
--- /dev/null
+++ b/html/browsers/the-window-object/security-window/window-security.https.html
@@ -0,0 +1,202 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: Window Security</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#the-window-object" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/timers.html#timers" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/webappapis.html#atob" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowsessionstorage" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#windowlocalstorage" />
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/browsers.html#window" />
+<link rel="help" href="http://dev.w3.org/csswg/cssom/#extensions-to-the-window-interface" />
+<link rel="help" href="http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test("Window Security testing");
+
+function fr_load() {
+  fr = document.getElementById("fr");
+
+  t.step(function () {
+    //SecurityError should be thrown
+    [
+      //attributes
+      {name: "applicationCache"},
+      {name: "devicePixelRatio"},
+      {name: "document"},
+      {name: "external"},
+      {name: "frameElement"},
+      {name: "history"},
+      {name: "innerWidth"},
+      {name: "innerHeight"},
+      {name: "locationbar"},
+      {name: "localStorage"},
+      {name: "menubar"},
+      {name: "name"},
+      {name: "navigator"},
+      {name: "onabort"},
+      {name: "onafterprint"},
+      {name: "onbeforeprint"},
+      {name: "onbeforeunload"},
+      {name: "onblur"},
+      {name: "oncancel"},
+      {name: "oncanplay"},
+      {name: "oncanplaythrough"},
+      {name: "onchange"},
+      {name: "onclick"},
+      {name: "onclose"},
+      {name: "oncontextmenu"},
+      {name: "oncuechange"},
+      {name: "ondblclick"},
+      {name: "ondrag"},
+      {name: "ondragend"},
+      {name: "ondragenter"},
+      {name: "ondragleave"},
+      {name: "ondragover"},
+      {name: "ondragstart"},
+      {name: "ondrop"},
+      {name: "ondurationchange"},
+      {name: "onemptied"},
+      {name: "onended"},
+      {name: "onerror"},
+      {name: "onfocus"},
+      {name: "onhashchange"},
+      {name: "oninput"},
+      {name: "oninvalid"},
+      {name: "onkeydown"},
+      {name: "onkeypress"},
+      {name: "onkeyup"},
+      {name: "onload"},
+      {name: "onloadeddata"},
+      {name: "onloadedmetadata"},
+      {name: "onloadstart"},
+      {name: "onmessage"},
+      {name: "onmousedown"},
+      {name: "onmousemove"},
+      {name: "onmouseout"},
+      {name: "onmouseover"},
+      {name: "onmouseup"},
+      {name: "onmousewheel"},
+      {name: "onoffline"},
+      {name: "ononline"},
+      {name: "onpause"},
+      {name: "onplay"},
+      {name: "onplaying"},
+      {name: "onpagehide"},
+      {name: "onpageshow"},
+      {name: "onpopstate"},
+      {name: "onprogress"},
+      {name: "onratechange"},
+      {name: "onreset"},
+      {name: "onresize"},
+      {name: "onscroll"},
+      {name: "onseeked"},
+      {name: "onseeking"},
+      {name: "onselect"},
+      {name: "onshow"},
+      {name: "onstalled"},
+      {name: "onstorage"},
+      {name: "onsubmit"},
+      {name: "onsuspend"},
+      {name: "ontimeupdate"},
+      {name: "onunload"},
+      {name: "onvolumechange"},
+      {name: "onwaiting"},
+      {name: "pageXOffset"},
+      {name: "pageYOffset"},
+      {name: "personalbar"},
+      {name: "screen"},
+      {name: "scrollbars"},
+      {name: "statusbar"},
+      {name: "status"},
+      {name: "screenX"},
+      {name: "screenY"},
+      {name: "sessionStorage"},
+      {name: "toolbar"},
+      //methods
+      {name: "alert", isMethod: true},
+      {name: "clearInterval", isMethod: true, args:[1]},
+      {name: "clearTimeout", isMethod: true, args:[function () {}, 1]},
+      {name: "confirm", isMethod: true},
+      {name: "getComputedStyle", isMethod: true, args:[document.body, null]},
+      {name: "getSelection", isMethod: true},
+      {name: "matchMedia", isMethod: true, args:["(min-width:50px)"]},
+      {name: "moveBy", isMethod: true, args:[10, 10]},
+      {name: "moveTo", isMethod: true, args:[10, 10]},
+      {name: "open", isMethod: true},
+      {name: "print", isMethod: true},
+      {name: "prompt", isMethod: true},
+      {name: "resizeTo", isMethod: true, args:[10, 10]},
+      {name: "resizeBy", isMethod: true, args:[10, 10]},
+      {name: "scroll", isMethod: true, args:[10, 10]},
+      {name: "scrollTo", isMethod: true, args:[10, 10]},
+      {name: "scrollBy", isMethod: true, args:[10, 10]},
+      {name: "setInterval", isMethod: true, args:[function () {}, 1]},
+      {name: "setTimeout", isMethod: true, args:[function () {}, 1]},
+      {name: "stop", isMethod: true},
+    ].forEach(function (item) {
+      test(function () {
+        assert_true(item.name in window, "window." + item.name + " should exist.");
+        assert_throws("SecurityError", function () {
+          if (item.isMethod)
+            if (item.args)
+              fr.contentWindow[item.name](item.args[0], item.args[1]);
+            else
+              fr.contentWindow[item.name]();
+          else
+            fr.contentWindow[item.name];
+        }, "A SecurityError exception should be thrown.");
+      }, "A SecurityError exception must be thrown when window." + item.name + " is accessed from a different origin.");
+    });
+
+    //SecurityError should not be thrown
+    [
+      //attributes
+      {name: "closed"},
+      {name: "frames"},
+      {name: "length"},
+      {name: "location"},
+      {name: "opener"},
+      {name: "parent"},
+      {name: "self"},
+      {name: "top"},
+      {name: "window"},
+      //methods
+      {name: "blur", isMethod: true},
+      {name: "close", isMethod: true},
+      {name: "focus", isMethod: true},
+      {name: "postMessage", isMethod: true, args: [{msg: 'foo'}, "*"]}
+    ].forEach(function (item) {
+      test(function () {
+        assert_true(item.name in window, "window." + item.name + " should exist.");
+        try {
+          if (item.isMethod)
+            if (item.args)
+              fr.contentWindow[item.name](item.args[0], item.args[1]);
+            else
+               fr.contentWindow[item.name]();
+          else
+            fr.contentWindow[item.name];
+        } catch (e) {
+          assert_unreached("An unexpected exception was thrown.");
+        }
+      }, "A SecurityError exception should not be thrown when window." + item.name + " is accessed from a different origin.");
+    });
+  });
+  t.done();
+}
+
+</script>
+<script>
+onload = function() {
+  var frame = document.createElement('iframe');
+  frame.id = "fr";
+  frame.setAttribute("style", "display:none");
+  frame.setAttribute('src', get_host_info().HTTPS_REMOTE_ORIGIN + "/");
+  frame.setAttribute("onload", "fr_load()");
+  document.body.appendChild(frame);
+}
+</script>
diff --git a/html/browsers/the-window-object/window-properties.html b/html/browsers/the-window-object/window-properties.https.html
similarity index 100%
rename from html/browsers/the-window-object/window-properties.html
rename to html/browsers/the-window-object/window-properties.https.html
diff --git a/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.window.js b/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.window.js
new file mode 100644
index 0000000..6d540ce
--- /dev/null
+++ b/html/browsers/windows/auxiliary-browsing-contexts/opener-setter.window.js
@@ -0,0 +1,38 @@
+[
+  undefined,
+  42,
+  function() { return "hi" },
+  "hi",
+  {},
+  [],
+  Symbol()
+].forEach(val => {
+  test(t => {
+    const frame = document.body.appendChild(document.createElement("iframe")),
+          win = frame.contentWindow;
+    t.add_cleanup(() => frame.remove());
+
+    assert_own_property(win, "opener");
+    assert_equals(win.opener, null);
+    const beforeDesc = Object.getOwnPropertyDescriptor(win, "opener"),
+          openerGet = beforeDesc.get,
+          openerSet = beforeDesc.set;
+    assert_own_property(beforeDesc, "get");
+    assert_own_property(beforeDesc, "set");
+    assert_true(beforeDesc.enumerable);
+    assert_true(beforeDesc.configurable);
+
+    win.opener = val;
+    assert_equals(win.opener, val);
+    assert_equals(openerGet(), null);
+
+    const desc = Object.getOwnPropertyDescriptor(win, "opener");
+    assert_equals(desc.value, val);
+    assert_true(desc.writable);
+    assert_true(desc.enumerable);
+    assert_true(desc.configurable);
+
+    openerSet("x");
+    assert_equals(win.opener, "x");
+  }, "Setting window.opener to " + String(val)); // String() needed for symbols
+});
diff --git a/html/browsers/windows/browsing-context.html b/html/browsers/windows/browsing-context.html
index ad3a01f..5e99bb5 100644
--- a/html/browsers/windows/browsing-context.html
+++ b/html/browsers/windows/browsing-context.html
@@ -44,11 +44,6 @@
       assert_equals(doc.referrer, document.URL, "The document's referrer should be its creator document's address.");
       assert_equals(iframe.contentWindow.parent.document, document);
     }, "Check the document properties corresponding to the creator browsing context");
-
-    test(function () {
-      assert_equals(iframe.contentWindow.history.length, 1, "The history.length should be 1.");
-    }, "Check the history.length of the created browsing context");
-
     </script>
   </body>
 </html>
diff --git a/html/dom/documents/dom-tree-accessors/Document.body.html b/html/dom/documents/dom-tree-accessors/Document.body.html
index 07f1edf..f421250 100644
--- a/html/dom/documents/dom-tree-accessors/Document.body.html
+++ b/html/dom/documents/dom-tree-accessors/Document.body.html
@@ -166,4 +166,62 @@
   doc.body = new_frameset;
   assert_equals(doc.body, new_frameset, "test6-3, append frameset to a new document");
 }, "Setting document.body to a new frameset element.");
+
+test(function() {
+  var doc = createDocument();
+  var html = doc.appendChild(doc.createElement("html"));
+  var f =
+    html.appendChild(doc.createElement("frameset"));
+  assert_equals(doc.body, f);
+
+  var b = doc.createElement("body");
+  doc.body = b;
+
+  assert_equals(f.parentNode, null,
+                "Frameset should have been removed from the tree");
+  assert_equals(doc.body, b, "Body should be the new doc.body");
+}, "Setting document.body to a body will replace an existing frameset if there is one.");
+
+test(function() {
+  var doc = createDocument();
+  var html = doc.appendChild(doc.createElement("html"));
+  var b =
+    html.appendChild(doc.createElement("body"));
+  assert_equals(doc.body, b);
+
+  var f = doc.createElement("frameset");
+  doc.body = f;
+
+  assert_equals(b.parentNode, null,
+                "Body should have been removed from the tree");
+  assert_equals(doc.body, f, "Frameset should be the new doc.body");
+}, "Setting document.body to a frameset will replace an existing body if there is one.");
+
+test(function() {
+  var doc = createDocument();
+  var html = doc.appendChild(doc.createElement("html"));
+  var b =
+    html.appendChild(doc.createElement("body"));
+  var f1 = html.appendChild(doc.createElement("frameset"));
+  assert_equals(doc.body, b);
+
+  var f2 = doc.createElement("frameset");
+  doc.body = f2;
+
+  assert_equals(b.parentNode, null,
+                "Body should have been removed from the tree");
+  assert_equals(f1.parentNode, html,
+                "Frameset following body should still be in the tree.");
+  assert_equals(doc.body, f2, "New frameset should be the new doc.body");
+  assert_equals(f2.nextSibling, f1, "New frameset should have replaced the body");
+}, "Setting document.body to a frameset will replace the first existing body/frameset.");
+
+test(function() {
+  var doc = createDocument();
+  doc.appendChild(doc.createElement("test"));
+  var new_body = doc.createElement("body");
+  doc.body = new_body;
+  assert_equals(doc.documentElement.firstChild, new_body, "new_body should be inserted");
+  assert_equals(doc.body, null, "Getter should return null when the root is not html");
+}, "Setting document.body to a new body element when the root element is a test element.");
 </script>
diff --git a/html/dom/documents/dom-tree-accessors/document.embeds-document.plugins-01.html b/html/dom/documents/dom-tree-accessors/document.embeds-document.plugins-01.html
index b4fe36d..e710798 100644
--- a/html/dom/documents/dom-tree-accessors/document.embeds-document.plugins-01.html
+++ b/html/dom/documents/dom-tree-accessors/document.embeds-document.plugins-01.html
@@ -52,4 +52,36 @@
   assert_equals(document.embeds, document.plugins,
                 "embeds should be the same as plugins");
 }, "Two plugins");
+
+test(function() {
+  var embed1 = document.createElement("embed"),
+      embed2 = document.createElement("embed");
+  document.body.appendChild(embed1);
+  this.add_cleanup(function() { document.body.removeChild(embed1) });
+  var embeds = document.embeds;
+  assert_true(embeds instanceof HTMLCollection);
+  assert_equals(embeds.length, 1);
+
+  document.body.appendChild(embed2);
+  assert_equals(embeds.length, 2);
+
+  document.body.removeChild(embed2);
+  assert_equals(embeds.length, 1);
+}, "Document.embeds should be a live collection");
+
+test(function() {
+  var embed1 = document.createElement("embed"),
+      embed2 = document.createElement("embed");
+  document.body.appendChild(embed1);
+  this.add_cleanup(function() { document.body.removeChild(embed1) });
+  var pls = document.plugins;
+  assert_true(pls instanceof HTMLCollection);
+  assert_equals(pls.length, 1);
+
+  document.body.appendChild(embed2);
+  assert_equals(pls.length, 2);
+
+  document.body.removeChild(embed2);
+  assert_equals(pls.length, 1);
+}, "Document.plugins should be a live collection");
 </script>
diff --git a/html/dom/documents/dom-tree-accessors/document.forms.html b/html/dom/documents/dom-tree-accessors/document.forms.html
index e0689c7..093c47c 100644
--- a/html/dom/documents/dom-tree-accessors/document.forms.html
+++ b/html/dom/documents/dom-tree-accessors/document.forms.html
@@ -67,4 +67,17 @@
   var result = Object.getOwnPropertyNames(document.forms);
   assert_array_equals(result, ["0", "1", "2", "form1", "form2"])
 }, "document.forms getOwnPropertyNames")
+
+test(function() {
+  var forms = document.forms;
+  assert_true(forms instanceof HTMLCollection);
+  assert_equals(forms.length, 3);
+
+  var form = document.createElement("form");
+  document.body.appendChild(form);
+  assert_equals(forms.length, 4);
+
+  document.body.removeChild(form);
+  assert_equals(forms.length, 3);
+}, "Document.forms should be a live collection");
 </script>
diff --git a/html/dom/documents/dom-tree-accessors/document.getElementsByName/document.getElementsByName-liveness.html b/html/dom/documents/dom-tree-accessors/document.getElementsByName/document.getElementsByName-liveness.html
new file mode 100644
index 0000000..74aad69
--- /dev/null
+++ b/html/dom/documents/dom-tree-accessors/document.getElementsByName/document.getElementsByName-liveness.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Document.getElementsByName: liveness</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+  var input = document.createElement("input"),
+      embed = document.createElement("embed");
+  input.setAttribute("name", "test");
+  input.setAttribute("type", "text");
+  embed.setAttribute("name", "test");
+  document.body.appendChild(input);
+  this.add_cleanup(function() { document.body.removeChild(input) });
+  var e = document.getElementsByName("test");
+  assert_true(e instanceof NodeList);
+  assert_equals(e.length, 1);
+
+  document.body.appendChild(embed);
+  assert_equals(e.length, 2);
+
+  document.body.removeChild(embed);
+  assert_equals(e.length, 1);
+}, "Document.getElementsByName() should be a live collection");
+</script>
diff --git a/html/dom/documents/dom-tree-accessors/document.images.html b/html/dom/documents/dom-tree-accessors/document.images.html
index 558fdeb..10ebe5e 100644
--- a/html/dom/documents/dom-tree-accessors/document.images.html
+++ b/html/dom/documents/dom-tree-accessors/document.images.html
@@ -102,4 +102,18 @@
   assert_equals(c.namedItem(""), null);
   assert_false("" in c, '"" in c');
 }, "The empty string should not be in the collections");
+
+test(function() {
+  var div = document.getElementById("test");
+  var imgs = document.images;
+  assert_true(imgs instanceof HTMLCollection);
+  assert_equals(imgs.length, 10);
+
+  var img = document.createElement("img");
+  div.appendChild(img);
+  assert_equals(imgs.length, 11);
+
+  div.removeChild(img);
+  assert_equals(imgs.length, 10);
+}, "Document.images should be a live collection");
 </script>
diff --git a/html/dom/documents/dom-tree-accessors/document.links.html b/html/dom/documents/dom-tree-accessors/document.links.html
new file mode 100644
index 0000000..69c7d8c
--- /dev/null
+++ b/html/dom/documents/dom-tree-accessors/document.links.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.links</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id=test>
+<a href=""></a>
+<a href=""></a>
+</div>
+<script>
+test(function() {
+  var div = document.getElementById("test");
+  var links = document.links;
+  assert_true(links instanceof HTMLCollection);
+  assert_equals(links.length, 2);
+
+  var a = document.createElement("a");
+  a.setAttribute("href", "");
+  div.appendChild(a);
+  assert_equals(links.length, 3);
+
+  div.removeChild(a);
+  assert_equals(links.length, 2);
+}, "Document.links should be a live collection");
+</script>
diff --git a/html/dom/documents/dom-tree-accessors/document.scripts.html b/html/dom/documents/dom-tree-accessors/document.scripts.html
new file mode 100644
index 0000000..82d3db1
--- /dev/null
+++ b/html/dom/documents/dom-tree-accessors/document.scripts.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Document.scripts</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+  var scripts = document.scripts;
+  assert_true(scripts instanceof HTMLCollection);
+  assert_equals(scripts.length, 3);
+
+  var script = document.createElement("script");
+  document.body.appendChild(script);
+  assert_equals(scripts.length, 4);
+
+  document.body.removeChild(script);
+  assert_equals(scripts.length, 3);
+}, "Document.scripts should be a live collection");
+</script>
diff --git a/html/dom/dynamic-markup-insertion/document-write/contentType.window.js b/html/dom/dynamic-markup-insertion/document-write/contentType.window.js
new file mode 100644
index 0000000..dbabb1b
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/document-write/contentType.window.js
@@ -0,0 +1,28 @@
+// META: script=/common/media.js
+
+const videoURL = getVideoURI("/images/pattern"),
+      videoMIMEType = getMediaContentType(videoURL);
+
+[
+  [videoURL, videoMIMEType, "video"],
+  ["/images/red.png", "image/png", "image"],
+  ["/common/text-plain.txt", "text/plain", "text"],
+  ["/common/blank.html", "text/html", "HTML"]
+].forEach(val => {
+  async_test(t => {
+    const frame = document.body.appendChild(document.createElement("iframe"));
+    t.add_cleanup(() => frame.remove());
+    frame.src = val[0];
+    frame.onload = t.step_func_done(() => {
+      assert_equals(frame.contentDocument.contentType, val[1]);
+      frame.contentDocument.write("<b>Heya</b>");
+      assert_equals(frame.contentDocument.body.firstChild.localName, "b");
+      assert_equals(frame.contentDocument.body.firstChild.textContent, "Heya");
+      assert_equals(frame.contentDocument.contentType, val[1]);
+
+      // Make sure a load event is fired across browsers
+      // https://github.com/w3c/web-platform-tests/pull/10239
+      frame.contentDocument.close();
+    });
+  }, "document.write(): " + val[2] + " document");
+});
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/009.html b/html/dom/dynamic-markup-insertion/opening-the-input-stream/009.https.html
similarity index 100%
rename from html/dom/dynamic-markup-insertion/opening-the-input-stream/009.html
rename to html/dom/dynamic-markup-insertion/opening-the-input-stream/009.https.html
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js b/html/dom/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js
new file mode 100644
index 0000000..a610ce9
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js
@@ -0,0 +1,12 @@
+async_test(t => {
+  const frame = document.body.appendChild(document.createElement("iframe"));
+  frame.src = "resources/encoding-frame.html";
+  frame.onload = t.step_func_done(t => {
+    // Using toLowerCase() to avoid an Edge bug
+    assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "precondition");
+    frame.contentDocument.open();
+    assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "actual test");
+    frame.contentDocument.close();
+    assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "might as well");
+  });
+}, "doucment.open() and the document's encoding");
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js b/html/dom/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js
new file mode 100644
index 0000000..97334ce
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js
@@ -0,0 +1,42 @@
+test(t => {
+  const frame = document.body.appendChild(document.createElement("iframe")),
+        body = frame.contentDocument.body;
+  t.add_cleanup(() => frame.remove());
+  frame.contentDocument.addEventListener("x", t.unreached_func("document event listener not removed"));
+  body.addEventListener("x", t.unreached_func("body event listener not removed"));
+  frame.contentDocument.open();
+  frame.contentDocument.dispatchEvent(new Event("x"));
+  body.dispatchEvent(new Event("x"));
+  frame.contentDocument.close();
+}, "Event listeners are to be removed");
+
+test(t => {
+  const frame = document.body.appendChild(document.createElement("iframe"));
+  t.add_cleanup(() => frame.remove());
+  let once = false;
+  frame.contentDocument.addEventListener("x", () => {
+    frame.contentDocument.open();
+    once = true;
+  });
+  frame.contentDocument.addEventListener("x", t.unreached_func("second event listener not removed"));
+  frame.contentDocument.dispatchEvent(new Event("x"));
+  assert_true(once);
+  frame.contentDocument.close();
+}, "Event listeners are to be removed with immediate effect");
+
+test(t => {
+  const frame = document.body.appendChild(document.createElement("iframe")),
+        shadow = frame.contentDocument.body.attachShadow({ mode: "closed" }),
+        shadowChild = shadow.appendChild(document.createElement("div")),
+        shadowShadow = shadowChild.attachShadow({ mode: "open" }),
+        nodes = [shadow, shadowChild, shadowShadow];
+  t.add_cleanup(() => frame.remove());
+  nodes.forEach(node => {
+    node.addEventListener("x", t.unreached_func(node + "'s event listener not removed"));
+  });
+  frame.contentDocument.open();
+  nodes.forEach(node => {
+    node.dispatchEvent(new Event("x"));
+  });
+  frame.contentDocument.close();
+}, "Event listeners are to be removed from shadow trees as well");
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js b/html/dom/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js
new file mode 100644
index 0000000..5c8e7d6
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js
@@ -0,0 +1,22 @@
+// In an ideal world this test would eventually be obsolete due to mutation events disappearing. Or
+// would have to change to account for mutation events not firing synchronously. Neither seems
+// realistic to the author though.
+
+test(t => {
+  const frame = document.body.appendChild(document.createElement("iframe"));
+  frame.contentWindow.addEventListener("DOMNodeInserted", t.unreached_func());
+  frame.contentWindow.addEventListener("DOMNodeInserted", t.unreached_func(), true);
+  frame.contentWindow.addEventListener("DOMNodeInsertedIntoDocument", t.unreached_func(), true);
+  frame.contentWindow.addEventListener("DOMNodeRemoved", t.unreached_func());
+  frame.contentWindow.addEventListener("DOMNodeRemoved", t.unreached_func(), true);
+  frame.contentWindow.addEventListener("DOMNodeRemovedFromDocument", t.unreached_func(), true);
+  frame.contentWindow.addEventListener("DOMSubtreeModified", t.unreached_func());
+  frame.contentWindow.addEventListener("DOMSubtreeModified", t.unreached_func(), true);
+  assert_equals(frame.contentDocument.documentElement.localName, "html");
+  frame.contentDocument.open();
+  assert_equals(frame.contentDocument.documentElement, null);
+  frame.contentDocument.write("<div>heya</div>");
+  frame.contentDocument.close();
+  assert_equals(frame.contentDocument.documentElement.localName, "html");
+  frame.remove();
+}, "document.open(), the HTML parser, and mutation events");
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html b/html/dom/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html
new file mode 100644
index 0000000..843c3a2
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<meta charset=ms932>
+<p>Encoded in Shift_JIS.</p>
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt b/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt
new file mode 100644
index 0000000..3e71550
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt
@@ -0,0 +1 @@
+Some text.
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js b/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js
new file mode 100644
index 0000000..52edd00
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js
@@ -0,0 +1,23 @@
+["replace",
+ "NOBODY",
+ "@ FD ;",
+ "it does not matter, you see \f",
+ "text/plain",
+ "text/xml",
+ "application/octet-stream",
+ "\0"].forEach(type => {
+  async_test(t => {
+    const frame = document.createElement("iframe");
+    frame.src = "type-argument-plaintext-subframe.txt";
+    document.body.appendChild(frame);
+    t.add_cleanup(() => frame.remove());
+    frame.onload = t.step_func_done(() => {
+      frame.contentDocument.open(type);
+      frame.contentDocument.write("<B>heya</b>");
+      frame.contentDocument.close();
+      assert_equals(frame.contentDocument.body.firstChild.localName, "b");
+      assert_equals(frame.contentDocument.body.textContent, "heya");
+      assert_equals(frame.contentDocument.contentType, "text/plain");
+    });
+  }, "document.open() on plaintext document with type set to: " + type + " (type argument is supposed to be ignored)");
+});
diff --git a/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js b/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js
new file mode 100644
index 0000000..704130a
--- /dev/null
+++ b/html/dom/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js
@@ -0,0 +1,20 @@
+["replace",
+ "NOBODY",
+ "@ FD ;",
+ "it does not matter, you see \f",
+ "text/plain",
+ "text/xml",
+ "application/octet-stream",
+ "\0"].forEach(type => {
+  async_test(t => {
+    const frame = document.body.appendChild(document.createElement("iframe"));
+    t.add_cleanup(() => frame.remove());
+    frame.contentDocument.open(type);
+    frame.contentDocument.write("<B>heya</b>");
+    frame.contentDocument.close();
+    assert_equals(frame.contentDocument.body.firstChild.localName, "b");
+    assert_equals(frame.contentDocument.body.textContent, "heya");
+    assert_equals(frame.contentDocument.contentType, "text/html");
+    t.done();
+  }, "document.open() with type set to: " + type + " (type argument is supposed to be ignored)");
+});
diff --git a/html/dom/elements-forms.js b/html/dom/elements-forms.js
index fc45535..051bc20 100644
--- a/html/dom/elements-forms.js
+++ b/html/dom/elements-forms.js
@@ -1,17 +1,3 @@
-var inputModeKeywords = [
-  "verbatim",
-  "latin",
-  "latin-name",
-  "latin-prose",
-  "full-width-latin",
-  "kana",
-  "kana-name",
-  "katakana",
-  "numeric",
-  "tel",
-  "email",
-  "url",
-];
 var formElements = {
   form: {
     acceptCharset: {type: "string", domAttrName: "accept-charset"},
@@ -52,7 +38,6 @@
     formNoValidate: "boolean",
     formTarget: "string",
     height: {type: "unsigned long", customGetter: true},
-    inputMode: {type: "enum", keywords: inputModeKeywords},
     max: "string",
     maxLength: "limited long",
     min: "string",
@@ -118,7 +103,6 @@
     cols: {type: "limited unsigned long with fallback", defaultVal: 20},
     dirName: "string",
     disabled: "boolean",
-    inputMode: {type: "enum", keywords: inputModeKeywords},
     maxLength: "limited long",
     minLength: "limited long",
     name: "string",
diff --git a/html/dom/elements-misc.js b/html/dom/elements-misc.js
index 43cdf5f..df415ae 100644
--- a/html/dom/elements-misc.js
+++ b/html/dom/elements-misc.js
@@ -52,7 +52,9 @@
   },
 
   // Global attributes should exist even on unknown elements
-  undefinedelement: {},
+  undefinedelement: {
+    inputMode: {type: "enum", keywords: ["none", "text", "tel", "url", "email", "numeric", "decimal", "search"]},
+  },
 };
 
 mergeElements(miscElements);
diff --git a/html/dom/elements/global-attributes/title-manual.html b/html/dom/elements/global-attributes/title-manual.html
new file mode 100644
index 0000000..d781172
--- /dev/null
+++ b/html/dom/elements/global-attributes/title-manual.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>The title attribute</title>
+<style>
+div > * { display: inline }
+link::before { content: "link" }
+</style>
+<p>Hover each word below. The tooltip for each of them should be "PASS".</p>
+<div title=PASS>div <link> <style>style</style> <dfn>dfn</dfn> <abbr>abbr</abbr> <menuitem>menuitem</menuitem></div>
diff --git a/html/dom/elements/the-innertext-idl-attribute/OWNERS b/html/dom/elements/the-innertext-idl-attribute/OWNERS
new file mode 100644
index 0000000..6385161
--- /dev/null
+++ b/html/dom/elements/the-innertext-idl-attribute/OWNERS
@@ -0,0 +1 @@
+@zcorpan
diff --git a/html/dom/elements/the-innertext-idl-attribute/getter-tests.js b/html/dom/elements/the-innertext-idl-attribute/getter-tests.js
new file mode 100644
index 0000000..77a3ebb
--- /dev/null
+++ b/html/dom/elements/the-innertext-idl-attribute/getter-tests.js
@@ -0,0 +1,350 @@
+testText("<div>abc", "abc", "Simplest possible test");
+
+/**** white-space:normal ****/
+
+testText("<div> abc", "abc", "Leading whitespace removed");
+testText("<div>abc ", "abc", "Trailing whitespace removed");
+testText("<div>abc  def", "abc def", "Internal whitespace compressed");
+testText("<div>abc\ndef", "abc def", "\\n converted to space");
+testText("<div>abc\rdef", "abc def", "\\r converted to space");
+testText("<div>abc\tdef", "abc def", "\\t converted to space");
+testText("<div>abc <br>def", "abc\ndef", "Trailing whitespace before hard line break removed");
+
+/**** <pre> ****/
+
+testText("<pre> abc", " abc", "Leading whitespace preserved");
+testText("<pre>abc ", "abc ", "Trailing whitespace preserved");
+testText("<pre>abc  def", "abc  def", "Internal whitespace preserved");
+testText("<pre>abc\ndef", "abc\ndef", "\\n preserved");
+testText("<pre>abc\rdef", "abc\ndef", "\\r converted to newline");
+testText("<pre>abc\tdef", "abc\tdef", "\\t preserved");
+testText("<div><pre>abc</pre><pre>def</pre>", "abc\ndef", "Two <pre> siblings");
+
+/**** <div style="white-space:pre"> ****/
+
+testText("<div style='white-space:pre'> abc", " abc", "Leading whitespace preserved");
+testText("<div style='white-space:pre'>abc ", "abc ", "Trailing whitespace preserved");
+testText("<div style='white-space:pre'>abc  def", "abc  def", "Internal whitespace preserved");
+testText("<div style='white-space:pre'>abc\ndef", "abc\ndef", "\\n preserved");
+testText("<div style='white-space:pre'>abc\rdef", "abc\ndef", "\\r converted to newline");
+testText("<div style='white-space:pre'>abc\tdef", "abc\tdef", "\\t preserved");
+
+/**** <span style="white-space:pre"> ****/
+
+testText("<span style='white-space:pre'> abc", " abc", "Leading whitespace preserved");
+testText("<span style='white-space:pre'>abc ", "abc ", "Trailing whitespace preserved");
+testText("<span style='white-space:pre'>abc  def", "abc  def", "Internal whitespace preserved");
+testText("<span style='white-space:pre'>abc\ndef", "abc\ndef", "\\n preserved");
+testText("<span style='white-space:pre'>abc\rdef", "abc\ndef", "\\r converted to newline");
+testText("<span style='white-space:pre'>abc\tdef", "abc\tdef", "\\t preserved");
+
+/**** <div style="white-space:pre-line"> ****/
+
+testText("<div style='white-space:pre-line'> abc", "abc", "Leading whitespace removed");
+testText("<div style='white-space:pre-line'>abc ", "abc", "Trailing whitespace removed");
+testText("<div style='white-space:pre-line'>abc  def", "abc def", "Internal whitespace collapsed");
+testText("<div style='white-space:pre-line'>abc\ndef", "abc\ndef", "\\n preserved");
+testText("<div style='white-space:pre-line'>abc\rdef", "abc\ndef", "\\r converted to newline");
+testText("<div style='white-space:pre-line'>abc\tdef", "abc def", "\\t converted to space");
+
+/**** Collapsing whitespace across element boundaries ****/
+
+testText("<div><span>abc </span> def", "abc def", "Whitespace collapses across element boundaries");
+testText("<div><span>abc </span><span></span> def", "abc def", "Whitespace collapses across element boundaries");
+testText("<div><span>abc </span><span style='white-space:pre'></span> def", "abc def", "Whitespace collapses across element boundaries");
+
+/**** Soft line breaks ****/
+
+testText("<div style='width:0'>abc def", "abc def", "Soft line breaks ignored");
+
+/**** first-line/first-letter ****/
+
+testText("<div class='first-line-uppercase' style='width:0'>abc def", "ABC def", "::first-line styles applied");
+testText("<div class='first-letter-uppercase' style='width:0'>abc def", "Abc def", "::first-letter styles applied");
+testText("<div class='first-letter-float' style='width:0'>abc def", "abc def", "::first-letter float ignored");
+
+/**** &nbsp; ****/
+
+testText("<div>&nbsp;", "\xA0", "&nbsp; preserved");
+
+/**** display:none ****/
+
+testText("<div style='display:none'>abc", "abc", "display:none container");
+testText("<div style='display:none'>abc  def", "abc  def", "No whitespace compression in display:none container");
+testText("<div style='display:none'> abc def ", " abc def ", "No removal of leading/trailing whitespace in display:none container");
+testText("<div>123<span style='display:none'>abc", "123", "display:none child not rendered");
+testText("<div style='display:none'><span id='target'>abc", "abc", "display:none container with non-display-none target child");
+testTextInSVG("<div id='target'>abc", "", "non-display-none child of svg");
+testTextInSVG("<div style='display:none' id='target'>abc", "abc", "display:none child of svg");
+testTextInSVG("<div style='display:none'><div id='target'>abc", "abc", "child of display:none child of svg");
+
+/**** display:contents ****/
+
+if (CSS.supports("display", "contents")) {
+  testText("<div style='display:contents'>abc", "abc", "display:contents container");
+  testText("<div><div style='display:contents'>abc", "abc", "display:contents container");
+  testText("<div>123<span style='display:contents'>abc", "123abc", "display:contents rendered");
+  testText("<div style='display:contents'>   ", "", "display:contents not processed via textContent");
+  testText("<div><div style='display:contents'>   ", "", "display:contents not processed via textContent");
+}
+
+/**** visibility:hidden ****/
+
+testText("<div style='visibility:hidden'>abc", "", "visibility:hidden container");
+testText("<div>123<span style='visibility:hidden'>abc", "123", "visibility:hidden child not rendered");
+testText("<div style='visibility:hidden'>123<span style='visibility:visible'>abc", "abc", "visibility:visible child rendered");
+
+/**** visibility:collapse ****/
+
+testText("<table><tbody style='visibility:collapse'><tr><td>abc", "", "visibility:collapse row-group");
+testText("<table><tr style='visibility:collapse'><td>abc", "", "visibility:collapse row");
+testText("<table><tr><td style='visibility:collapse'>abc", "", "visibility:collapse cell");
+testText("<table><tbody style='visibility:collapse'><tr><td style='visibility:visible'>abc", "abc",
+         "visibility:collapse row-group with visible cell");
+testText("<table><tr style='visibility:collapse'><td style='visibility:visible'>abc", "abc",
+         "visibility:collapse row with visible cell");
+testText("<div style='display:flex'><span style='visibility:collapse'>1</span><span>2</span></div>",
+         "2", "visibility:collapse honored on flex item");
+testText("<div style='display:grid'><span style='visibility:collapse'>1</span><span>2</span></div>",
+         "2", "visibility:collapse honored on grid item");
+
+/**** opacity:0 ****/
+
+testText("<div style='opacity:0'>abc", "abc", "opacity:0 container");
+testText("<div style='opacity:0'>abc  def", "abc def", "Whitespace compression in opacity:0 container");
+testText("<div style='opacity:0'> abc def ", "abc def", "Remove leading/trailing whitespace in opacity:0 container");
+testText("<div>123<span style='opacity:0'>abc", "123abc", "opacity:0 child rendered");
+
+/**** generated content ****/
+
+testText("<div class='before'>", "", "Generated content not included");
+testText("<div><div class='before'>", "", "Generated content on child not included");
+
+/**** innerText on replaced elements ****/
+
+testText("<button>abc", "abc", "<button> contents preserved");
+testText("<fieldset>abc", "abc", "<fieldset> contents preserved");
+testText("<fieldset><legend>abc", "abc", "<fieldset> <legend> contents preserved");
+testText("<input type='text' value='abc'>", "", "<input> contents ignored");
+testText("<textarea>abc", "", "<textarea> contents ignored");
+testText("<iframe>abc", "", "<iframe> contents ignored");
+testText("<iframe><div id='target'>abc", "", "<iframe> contents ignored");
+testText("<iframe src='data:text/html,abc'>", "","<iframe> subdocument ignored");
+testText("<audio style='display:block'>abc", "", "<audio> contents ignored");
+testText("<audio style='display:block'><source id='target' class='poke' style='display:block'>", "", "<audio> contents ignored");
+testText("<audio style='display:block'><source id='target' class='poke' style='display:none'>", "abc", "<audio> contents ok if display:none");
+testText("<video>abc", "", "<video> contents ignored");
+testText("<video style='display:block'><source id='target' class='poke' style='display:block'>", "", "<video> contents ignored");
+testText("<video style='display:block'><source id='target' class='poke' style='display:none'>", "abc", "<video> contents ok if display:none");
+testText("<canvas>abc", "", "<canvas> contents ignored");
+testText("<canvas><div id='target'>abc", "", "<canvas><div id='target'> contents ignored");
+testText("<img alt='abc'>", "", "<img> alt text ignored");
+testText("<img src='about:blank' class='poke'>", "", "<img> contents ignored");
+
+/**** <select>, <optgroup> & <option> ****/
+
+testText("<select size='1'><option>abc</option><option>def", "abc\ndef", "<select size='1'> contents of options preserved");
+testText("<select size='2'><option>abc</option><option>def", "abc\ndef", "<select size='2'> contents of options preserved");
+testText("<select size='1'><option id='target'>abc</option><option>def", "abc", "<select size='1'> contents of target option preserved");
+testText("<select size='2'><option id='target'>abc</option><option>def", "abc", "<select size='2'> contents of target option preserved");
+testText("<div>a<select></select>bc", "abc", "empty <select>");
+testText("<div>a<select><optgroup></select>bc", "a\nbc", "empty <optgroup> in <select>");
+testText("<div>a<select><option></select>bc", "a\nbc", "empty <option> in <select>");
+testText("<select class='poke'></select>", "", "<select> containing text node child");
+testText("<select><optgroup class='poke-optgroup'></select>", "", "<optgroup> containing <optgroup>");
+testText("<select><optgroup><option>abc</select>", "abc", "<optgroup> containing <option>");
+testText("<select><option class='poke-div'>123</select>", "123\nabc", "<div> in <option>");
+testText("<div>a<optgroup></optgroup>bc", "a\nbc", "empty <optgroup> in <div>");
+testText("<div>a<optgroup>123</optgroup>bc", "a\nbc", "<optgroup> in <div>");
+testText("<div>a<option></option>bc", "a\nbc", "empty <option> in <div>");
+testText("<div>a<option>123</option>bc", "a\n123\nbc", "<option> in <div>");
+
+/**** innerText on replaced element children ****/
+
+testText("<div><button>abc", "abc", "<button> contents preserved");
+testText("<div><fieldset>abc", "abc", "<fieldset> contents preserved");
+testText("<div><fieldset><legend>abc", "abc", "<fieldset> <legend> contents preserved");
+testText("<div><input type='text' value='abc'>", "", "<input> contents ignored");
+testText("<div><textarea>abc", "", "<textarea> contents ignored");
+testText("<div><select size='1'><option>abc</option><option>def", "abc\ndef", "<select size='1'> contents of options preserved");
+testText("<div><select size='2'><option>abc</option><option>def", "abc\ndef", "<select size='2'> contents of options preserved");
+testText("<div><iframe>abc", "", "<iframe> contents ignored");
+testText("<div><iframe src='data:text/html,abc'>", ""," <iframe> subdocument ignored");
+testText("<div><audio>abc", "", "<audio> contents ignored");
+testText("<div><video>abc", "", "<video> contents ignored");
+testText("<div><canvas>abc", "", "<canvas> contents ignored");
+testText("<div><img alt='abc'>", "", "<img> alt text ignored");
+
+/**** Lines around blocks ****/
+
+testText("<div>123<div>abc</div>def", "123\nabc\ndef", "Newline at block boundary");
+testText("<div>123<span style='display:block'>abc</span>def", "123\nabc\ndef", "Newline at display:block boundary");
+testText("<div>abc<div></div>def", "abc\ndef", "Empty block induces single line break");
+testText("<div>abc<div></div><div></div>def", "abc\ndef", "Consecutive empty blocks ignored");
+testText("<div><p>abc", "abc", "No blank lines around <p> alone");
+testText("<div><p>abc</p> ", "abc", "No blank lines around <p> followed by only collapsible whitespace");
+testText("<div> <p>abc</p>", "abc", "No blank lines around <p> preceded by only collapsible whitespace");
+testText("<div><p>abc<p>def", "abc\n\ndef", "Blank line between consecutive <p>s");
+testText("<div><p>abc</p> <p>def", "abc\n\ndef", "Blank line between consecutive <p>s separated only by collapsible whitespace");
+testText("<div><p>abc</p><div></div><p>def", "abc\n\ndef", "Blank line between consecutive <p>s separated only by empty block");
+testText("<div><p>abc</p><div>123</div><p>def", "abc\n\n123\n\ndef", "Blank lines between <p>s separated by non-empty block");
+testText("<div>abc<div><p>123</p></div>def", "abc\n\n123\n\ndef", "Blank lines around a <p> in its own block");
+testText("<div>abc<p>def", "abc\n\ndef", "Blank line before <p>");
+testText("<div><p>abc</p>def", "abc\n\ndef", "Blank line after <p>");
+testText("<div><p>abc<p></p><p></p><p>def", "abc\n\ndef", "One blank line between <p>s, ignoring empty <p>s");
+testText("<div style='visibility:hidden'><p><span style='visibility:visible'>abc</span></p>\n<div style='visibility:visible'>def</div>",
+     "abc\ndef", "Invisible <p> doesn't induce extra line breaks");
+testText("<div>abc<div style='margin:2em'>def", "abc\ndef", "No blank lines around <div> with margin");
+testText("<div>123<span style='display:inline-block'>abc</span>def", "123abcdef", "No newlines at display:inline-block boundary");
+testText("<div>123<span style='display:inline-block'> abc </span>def", "123abcdef", "Leading/trailing space removal at display:inline-block boundary");
+testText("<div>123<p style='margin:0px'>abc</p>def", "123\n\nabc\n\ndef", "Blank lines around <p> even without margin");
+testText("<div>123<h1>abc</h1>def", "123\nabc\ndef", "No blank lines around <h1>");
+testText("<div>123<h2>abc</h2>def", "123\nabc\ndef", "No blank lines around <h2>");
+testText("<div>123<h3>abc</h3>def", "123\nabc\ndef", "No blank lines around <h3>");
+testText("<div>123<h4>abc</h4>def", "123\nabc\ndef", "No blank lines around <h4>");
+testText("<div>123<h5>abc</h5>def", "123\nabc\ndef", "No blank lines around <h5>");
+testText("<div>123<h6>abc</h6>def", "123\nabc\ndef", "No blank lines around <h6>");
+
+/**** Spans ****/
+
+testText("<div>123<span>abc</span>def", "123abcdef", "<span> boundaries are irrelevant");
+testText("<div>123 <span>abc</span> def", "123 abc def", "<span> boundaries are irrelevant");
+testText("<div style='width:0'>123 <span>abc</span> def", "123 abc def", "<span> boundaries are irrelevant");
+testText("<div>123<em>abc</em>def", "123abcdef", "<em> gets no special treatment");
+testText("<div>123<b>abc</b>def", "123abcdef", "<b> gets no special treatment");
+testText("<div>123<i>abc</i>def", "123abcdef", "<i> gets no special treatment");
+testText("<div>123<strong>abc</strong>def", "123abcdef", "<strong> gets no special treatment");
+testText("<div>123<tt>abc</tt>def", "123abcdef", "<tt> gets no special treatment");
+testText("<div>123<code>abc</code>def", "123abcdef", "<code> gets no special treatment");
+
+/**** Soft hyphen ****/
+
+testText("<div>abc&shy;def", "abc\xADdef", "soft hyphen preserved");
+testText("<div style='width:0'>abc&shy;def", "abc\xADdef", "soft hyphen preserved");
+
+/**** Tables ****/
+
+testText("<div><table style='white-space:pre'>  <td>abc</td>  </table>", "abc", "Ignoring non-rendered table whitespace");
+testText("<div><table><tr><td>abc<td>def</table>", "abc\tdef", "Tab-separated table cells");
+testText("<div><table><tr><td>abc<td><td>def</table>", "abc\t\tdef", "Tab-separated table cells including empty cells");
+testText("<div><table><tr><td>abc<td><td></table>", "abc\t\t", "Tab-separated table cells including trailing empty cells");
+testText("<div><table><tr><td>abc<tr><td>def</table>", "abc\ndef", "Newline-separated table rows");
+testText("<div>abc<table><td>def</table>ghi", "abc\ndef\nghi", "Newlines around table");
+testText("<div><table style='border-collapse:collapse'><tr><td>abc<td>def</table>", "abc\tdef",
+         "Tab-separated table cells in a border-collapse table");
+testText("<div><table><tfoot>x</tfoot><tbody>y</tbody></table>", "xy", "tfoot not reordered");
+testText("<table><tfoot><tr><td>footer</tfoot><thead><tr><td style='visibility:collapse'>thead</thead><tbody><tr><td>tbody</tbody></table>",
+         "footer\n\ntbody", "");
+
+/**** Table captions ****/
+
+testText("<div><table><tr><td>abc<caption>def</caption></table>", "abc\ndef", "Newline between cells and caption");
+
+/**** display:table ****/
+
+testText("<div><div class='table'><span class='cell'>abc</span>\n<span class='cell'>def</span></div>",
+         "abc\tdef", "Tab-separated table cells");
+testText("<div><div class='table'><span class='row'><span class='cell'>abc</span></span>\n<span class='row'><span class='cell'>def</span></span></div>",
+         "abc\ndef", "Newline-separated table rows");
+testText("<div>abc<div class='table'><span class='cell'>def</span></div>ghi", "abc\ndef\nghi", "Newlines around table");
+
+/**** display:inline-table ****/
+
+testText("<div><div class='itable'><span class='cell'>abc</span>\n<span class='cell'>def</span></div>", "abc\tdef", "Tab-separated table cells");
+testText("<div><div class='itable'><span class='row'><span class='cell'>abc</span></span>\n<span class='row'><span class='cell'>def</span></span></div>",
+         "abc\ndef", "Newline-separated table rows");
+testText("<div>abc<div class='itable'><span class='cell'>def</span></div>ghi", "abcdefghi", "No newlines around inline-table");
+testText("<div>abc<div class='itable'><span class='row'><span class='cell'>def</span></span>\n<span class='row'><span class='cell'>123</span></span></div>ghi",
+         "abcdef\n123ghi", "Single newline in two-row inline-table");
+
+/**** Lists ****/
+
+testText("<div><ol><li>abc", "abc", "<ol> list items get no special treatment");
+testText("<div><ul><li>abc", "abc", "<ul> list items get no special treatment");
+
+/**** Misc elements ****/
+
+testText("<div><script style='display:block'>abc", "abc", "display:block <script> is rendered");
+testText("<div><style style='display:block'>abc", "abc", "display:block <style> is rendered");
+testText("<div><noscript style='display:block'>abc", "", "display:block <noscript> is not rendered (it's not parsed!)");
+testText("<div><template style='display:block'>abc", "",
+         "display:block <template> contents are not rendered (the contents are in a different document)");
+testText("<div>abc<br>def", "abc\ndef", "<br> induces line break");
+testText("<div>abc<br>", "abc\n", "<br> induces line break even at end of block");
+testText("<div><br class='poke'>", "\n", "<br> content ignored");
+testText("<div>abc<hr>def", "abc\ndef", "<hr> induces line break");
+testText("<div>abc<hr><hr>def", "abc\ndef", "<hr><hr> induces just one line break");
+testText("<div>abc<hr><hr><hr>def", "abc\ndef", "<hr><hr><hr> induces just one line break");
+testText("<div><hr class='poke'>", "abc", "<hr> content rendered");
+testText("<div>abc<!--comment-->def", "abcdef", "comment ignored");
+
+/**** text-transform ****/
+
+testText("<div><div style='text-transform:uppercase'>abc", "ABC", "text-transform is applied");
+testText("<div><div style='text-transform:uppercase'>Ma\xDF", "MASS", "text-transform handles es-zet");
+testText("<div><div lang='tr' style='text-transform:uppercase'>i \u0131", "\u0130 I", "text-transform handles Turkish casing");
+
+/**** block-in-inline ****/
+
+testText("<div>abc<span>123<div>456</div>789</span>def", "abc123\n456\n789def", "block-in-inline doesn't add unnecessary newlines");
+
+/**** floats ****/
+
+testText("<div>abc<div style='float:left'>123</div>def", "abc\n123\ndef", "floats induce a block boundary");
+testText("<div>abc<span style='float:left'>123</span>def", "abc\n123\ndef", "floats induce a block boundary");
+
+/**** position ****/
+
+testText("<div>abc<div style='position:absolute'>123</div>def", "abc\n123\ndef", "position:absolute induces a block boundary");
+testText("<div>abc<span style='position:absolute'>123</span>def", "abc\n123\ndef", "position:absolute induces a block boundary");
+testText("<div>abc<div style='position:relative'>123</div>def", "abc\n123\ndef", "position:relative has no effect");
+testText("<div>abc<span style='position:relative'>123</span>def", "abc123def", "position:relative has no effect");
+
+/**** text-overflow:ellipsis ****/
+
+testText("<div style='overflow:hidden'>abc", "abc", "overflow:hidden ignored");
+// XXX Chrome skips content with width:0 or height:0 and overflow:hidden;
+// should we spec that?
+testText("<div style='width:0; overflow:hidden'>abc", "abc", "overflow:hidden ignored even with zero width");
+testText("<div style='height:0; overflow:hidden'>abc", "abc", "overflow:hidden ignored even with zero height");
+testText("<div style='width:0; overflow:hidden; text-overflow:ellipsis'>abc", "abc", "text-overflow:ellipsis ignored");
+
+/**** Support on non-HTML elements ****/
+
+testText("<svg>abc", undefined, "innerText not supported on SVG elements");
+testText("<math>abc", undefined, "innerText not supported on MathML elements");
+
+/**** Ruby ****/
+
+testText("<div><ruby>abc<rt>def</rt></ruby>", "abcdef", "<rt> and no <rp>");
+testText("<div><ruby>abc<rp>(</rp><rt>def</rt><rp>)</rp></ruby>", "abcdef", "<rp>");
+testText("<div><rp>abc</rp>", "", "Lone <rp>");
+testText("<div><rp style='visibility:hidden'>abc</rp>", "", "visibility:hidden <rp>");
+testText("<div><rp style='display:block'>abc</rp>def", "abc\ndef", "display:block <rp>");
+testText("<div><rp style='display:block'> abc </rp>def", "abc\ndef", "display:block <rp> with whitespace");
+testText("<div><select class='poke-rp'></select>", "", "<rp> in a <select>");
+
+/**** Shadow DOM ****/
+
+if ("attachShadow" in document.body) {
+  testText("<div class='shadow'>", "", "Shadow DOM contents ignored");
+  testText("<div><div class='shadow'>", "", "Shadow DOM contents ignored");
+}
+
+/**** Flexbox ****/
+
+if (CSS.supports('display', 'flex')) {
+  testText("<div style='display:flex'><div style='order:1'>1</div><div>2</div></div>",
+           "1\n2", "CSS 'order' property ignored");
+  testText("<div style='display:flex'><span>1</span><span>2</span></div>",
+           "1\n2", "Flex items blockified");
+}
+
+/**** Grid ****/
+
+if (CSS.supports('display', 'grid')) {
+  testText("<div style='display:grid'><div style='order:1'>1</div><div>2</div></div>",
+           "1\n2", "CSS 'order' property ignored");
+  testText("<div style='display:grid'><span>1</span><span>2</span></div>",
+           "1\n2", "Grid items blockified");
+}
diff --git a/html/dom/elements/the-innertext-idl-attribute/getter.html b/html/dom/elements/the-innertext-idl-attribute/getter.html
new file mode 100644
index 0000000..c84bb04
--- /dev/null
+++ b/html/dom/elements/the-innertext-idl-attribute/getter.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>innerText getter test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.before::before { content:'abc'; }
+.table { display:table; }
+.itable { display:inline-table; }
+.row { display:table-row; }
+.cell { display:table-cell; }
+.first-line-uppercase::first-line { text-transform:uppercase; }
+.first-letter-uppercase::first-letter { text-transform:uppercase; }
+.first-letter-float::first-letter { float:left; }
+</style>
+<div id="container"></div>
+<svg id="svgContainer"></svg>
+<script>
+let container = document.querySelector('#container');
+let svgContainer = document.querySelector('#svgContainer');
+function testText(html, expectedPlain, msg) {
+  textTextInContainer(container, html, expectedPlain, msg);
+}
+function testTextInSVG(html, expectedPlain, msg) {
+  textTextInContainer(svgContainer, html, expectedPlain, msg);
+}
+function textTextInContainer(cont, html, expectedPlain, msg) {
+  test(function() {
+    container.innerHTML = html;
+    if (cont != container) {
+      while (container.firstChild) {
+        cont.appendChild(container.firstChild);
+      }
+    }
+    var e = document.getElementById('target');
+    if (!e) {
+      e = cont.firstChild;
+    }
+    var pokes = document.getElementsByClassName('poke');
+    for (var i = 0; i < pokes.length; ++i) {
+      pokes[i].textContent = 'abc';
+    }
+    ['rp', 'optgroup', 'div'].forEach(function(tag) {
+      pokes = document.getElementsByClassName('poke-' + tag);
+      for (var i = 0; i < pokes.length; ++i) {
+        var el = document.createElement(tag);
+        el.textContent = "abc";
+        pokes[i].appendChild(el);
+      }
+    });
+    var shadows = document.getElementsByClassName('shadow');
+    for (var i = 0; i < shadows.length; ++i) {
+      var s = shadows[i].attachShadow({ mode: "open" });
+      s.textContent = 'abc';
+    }
+    while (e && e.nodeType != Node.ELEMENT_NODE) {
+      e = e.nextSibling;
+    }
+    assert_equals(e.innerText, expectedPlain);
+    cont.textContent = '';
+  }, msg + ' (' + format_value(html) + ')');
+}
+</script>
+<script src="getter-tests.js"></script>
diff --git a/innerText/multiple-text-nodes.window.js b/html/dom/elements/the-innertext-idl-attribute/multiple-text-nodes.window.js
similarity index 100%
rename from innerText/multiple-text-nodes.window.js
rename to html/dom/elements/the-innertext-idl-attribute/multiple-text-nodes.window.js
diff --git a/innerText/setter-tests.js b/html/dom/elements/the-innertext-idl-attribute/setter-tests.js
similarity index 100%
rename from innerText/setter-tests.js
rename to html/dom/elements/the-innertext-idl-attribute/setter-tests.js
diff --git a/html/dom/elements/the-innertext-idl-attribute/setter.html b/html/dom/elements/the-innertext-idl-attribute/setter.html
new file mode 100644
index 0000000..0b8d568
--- /dev/null
+++ b/html/dom/elements/the-innertext-idl-attribute/setter.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<title>innerText setter test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="container"></div>
+<script>
+// As of March 2017, WebKit and Blink have inconsistent results depending on
+// rendered or not.  setupTest() tests a rendered case, and setupTestDetached()
+// tests a not-rendered case.
+
+function setupTest(context, plain) {
+  var container = document.getElementById("container");
+  // context is either a string or an element node
+  if (typeof context === "string") {
+    container.innerHTML = context;
+  } else {
+    container.innerHTML = "";
+    container.appendChild(context);
+  }
+  var e = container.firstChild;
+  while (e && e.nodeType != Node.ELEMENT_NODE) {
+    e = e.nextSibling;
+  }
+  e.offsetWidth;
+  var oldChild = e.firstChild;
+  e.innerText = plain;
+  return [e, oldChild];
+}
+
+function setupTestDetached(context, plain) {
+  var detachedContainer = document.createElement("div");
+  // context is either a string or an element node
+  if (typeof context === "string") {
+    detachedContainer.innerHTML = context;
+  } else {
+    detachedContainer.innerHTML = "";
+    detachedContainer.appendChild(context);
+  }
+  var e = detachedContainer.firstChild;
+  while (e && e.nodeType != Node.ELEMENT_NODE) {
+    e = e.nextSibling;
+  }
+  var oldChild = e.firstChild;
+  e.innerText = plain;
+  return [e, oldChild];
+}
+
+function assertNewSingleTextNode(newChild, expectedText, oldChild) {
+  assert_not_equals(newChild, null, "Should have a child");
+  assert_equals(newChild.nodeType, Node.TEXT_NODE, "Child should be a text node");
+  assert_equals(newChild.nextSibling, null, "Should have only one child");
+  assert_equals(newChild.data, expectedText);
+  assert_not_equals(newChild, oldChild, "Child should be a *new* text node");
+}
+
+function assertNoEmptyTextChild(parent) {
+  for (var child = parent.firstChild; child; child = child.nextSibling) {
+    if (child.nodeType === Node.TEXT_NODE) {
+      assert_not_equals(child.data, "", "Should not have empty text nodes");
+    }
+  }
+}
+
+function testText(context, plain, expectedText, msg) {
+  test(function(){
+    var arr = setupTest(context, plain);
+    assertNewSingleTextNode(arr[0].firstChild, expectedText, arr[1]);
+  }, msg);
+  test(function() {
+    var arr = setupTestDetached(context, plain);
+    assertNewSingleTextNode(arr[0].firstChild, expectedText, arr[1]);
+  }, msg + ", detached");
+}
+
+function testHTML(context, plain, expectedHTML, msg) {
+  test(function(){
+    var e = setupTest(context, plain)[0];
+    assert_equals(e.innerHTML, expectedHTML);
+    assertNoEmptyTextChild(e);
+  }, msg);
+  test(function() {
+    var e = setupTestDetached(context, plain)[0];
+    assert_equals(e.innerHTML, expectedHTML);
+    assertNoEmptyTextChild(e);
+  }, msg + ", detached");
+}
+</script>
+<script src="setter-tests.js"></script>
diff --git a/html/dom/interfaces.html b/html/dom/interfaces.html
deleted file mode 100644
index b8c2af3..0000000
--- a/html/dom/interfaces.html
+++ /dev/null
@@ -1,239 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<!-- WARNING: These tests are preliminary and probably partly incorrect.  -->
-<title>HTML IDL tests</title>
-<meta name=timeout content=long>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-
-<h1>HTML IDL tests</h1>
-<div id=log></div>
-
-<script>
-"use strict";
-var errorVideo; // used to get a MediaError object
-var iframe; // used to get a Document object (can't use `document` because some test clears the page)
-setup(function() {
-  errorVideo = document.createElement('video');
-  errorVideo.src = 'data:,';
-  errorVideo.preload = 'auto';
-  iframe = document.createElement('iframe');
-  iframe.hidden = true;
-  document.body.appendChild(iframe);
-});
-
-function createInput(type) {
-  var input = document.createElement('input');
-  input.type = type;
-  return input;
-}
-
-function doTest([html, dom, cssom, uievents, touchevents]) {
-  var idlArray = new IdlArray();
-  var svg = "interface SVGElement : Element {};";
-  idlArray.add_untested_idls(dom + svg + cssom + uievents + touchevents);
-  idlArray.add_idls(html);
-
-  idlArray.add_objects({
-    NodeList: ['document.getElementsByName("name")'],
-    HTMLAllCollection: ['document.all'],
-    HTMLFormControlsCollection: ['document.createElement("form").elements'],
-    RadioNodeList: [],
-    HTMLOptionsCollection: ['document.createElement("select").options'],
-    DOMStringMap: ['document.head.dataset'],
-    Transferable: [],
-    Document: ['iframe.contentDocument', 'new Document()'],
-    XMLDocument: ['document.implementation.createDocument(null, "", null)'],
-    HTMLElement: ['document.createElement("noscript")'], // more tests in html/semantics/interfaces.js
-    HTMLUnknownElement: ['document.createElement("bgsound")'], // more tests in html/semantics/interfaces.js
-    HTMLHtmlElement: ['document.createElement("html")'],
-    HTMLHeadElement: ['document.createElement("head")'],
-    HTMLTitleElement: ['document.createElement("title")'],
-    HTMLBaseElement: ['document.createElement("base")'],
-    HTMLLinkElement: ['document.createElement("link")'],
-    HTMLMetaElement: ['document.createElement("meta")'],
-    HTMLStyleElement: ['document.createElement("style")'],
-    HTMLScriptElement: ['document.createElement("script")'],
-    HTMLBodyElement: ['document.createElement("body")'],
-    HTMLHeadingElement: ['document.createElement("h1")'],
-    HTMLParagraphElement: ['document.createElement("p")'],
-    HTMLHRElement: ['document.createElement("hr")'],
-    HTMLPreElement: [
-      'document.createElement("pre")',
-      'document.createElement("listing")',
-      'document.createElement("xmp")',
-    ],
-    HTMLQuoteElement: [
-      'document.createElement("blockquote")',
-      'document.createElement("q")',
-    ],
-    HTMLOlistElement: ['document.createElement("ol")'],
-    HTMLUlistElement: ['document.createElement("ul")'],
-    HTMLLIElement: ['document.createElement("li")'],
-    HTMLDlistElement: ['document.createElement("dl")'],
-    HTMLDivElement: ['document.createElement("div")'],
-    HTMLAnchorElement: ['document.createElement("a")'],
-    HTMLDataElement: ['document.createElement("data")'],
-    HTMLTimeElement: ['document.createElement("time")'],
-    HTMLSpanElement: ['document.createElement("span")'],
-    HTMLBRElement: ['document.createElement("br")'],
-    HTMLModElement: [
-      'document.createElement("ins")',
-      'document.createElement("del")',
-    ],
-    HTMLPictureElement: ['document.createElement("picture")'],
-    HTMLImageElement: ['document.createElement("img")', 'new Image()'],
-    HTMLIframeElement: ['document.createElement("iframe")'],
-    HTMLEmbedElement: ['document.createElement("embed")'],
-    HTMLObjectElement: ['document.createElement("object")'],
-    HTMLParamElement: ['document.createElement("param")'],
-    HTMLVideoElement: ['document.createElement("video")'],
-    HTMLAudioElement: ['document.createElement("audio")', 'new Audio()'],
-    HTMLSourceElement: ['document.createElement("source")'],
-    HTMLTrackElement: ['document.createElement("track")'],
-    HTMLMediaElement: [],
-    MediaError: ['errorVideo.error'],
-    AudioTrackList: [],
-    AudioTrack: [],
-    VideoTrackList: [],
-    VideoTrack: [],
-    TextTrackList: ['document.createElement("video").textTracks'],
-    TextTrack: ['document.createElement("track").track'],
-    TextTrackCueList: ['document.createElement("video").addTextTrack("subtitles").cues'],
-    TextTrackCue: [],
-    DataCue: [],
-    TimeRanges: ['document.createElement("video").buffered'],
-    TrackEvent: ['new TrackEvent("addtrack", {track:document.createElement("track").track})'],
-    HTMLTemplateElement: ['document.createElement("template")'],
-    HTMLSlotElement: ['document.createElement("slot")'],
-    HTMLCanvasElement: ['document.createElement("canvas")'],
-    CanvasRenderingContext2D: ['document.createElement("canvas").getContext("2d")'],
-    CanvasGradient: [],
-    CanvasPattern: [],
-    TextMetrics: [],
-    ImageData: [],
-    HTMLMapElement: ['document.createElement("map")'],
-    HTMLAreaElement: ['document.createElement("area")'],
-    HTMLTableElement: ['document.createElement("table")'],
-    HTMLTableCaptionElement: ['document.createElement("caption")'],
-    HTMLTableColElement: [
-      'document.createElement("colgroup")',
-      'document.createElement("col")',
-    ],
-    HTMLTableSectionElement: [
-      'document.createElement("tbody")',
-      'document.createElement("thead")',
-      'document.createElement("tfoot")',
-    ],
-    HTMLTableRowElement: ['document.createElement("tr")'],
-    HTMLTableCellElement: [
-      'document.createElement("td")',
-      'document.createElement("th")',
-    ],
-    HTMLFormElement: ['document.createElement("form")'],
-    HTMLFieldsetElement: ['document.createElement("fieldset")'],
-    HTMLLegendElement: ['document.createElement("legend")'],
-    HTMLLabelElement: ['document.createElement("label")'],
-    HTMLInputElement: [
-      'document.createElement("input")',
-      'createInput("text")',
-      'createInput("hidden")',
-      'createInput("search")',
-      'createInput("tel")',
-      'createInput("url")',
-      'createInput("email")',
-      'createInput("password")',
-      'createInput("date")',
-      'createInput("month")',
-      'createInput("week")',
-      'createInput("time")',
-      'createInput("datetime-local")',
-      'createInput("number")',
-      'createInput("range")',
-      'createInput("color")',
-      'createInput("checkbox")',
-      'createInput("radio")',
-      'createInput("file")',
-      'createInput("submit")',
-      'createInput("image")',
-      'createInput("reset")',
-      'createInput("button")'
-    ],
-    HTMLButtonElement: ['document.createElement("button")'],
-    HTMLSelectElement: ['document.createElement("select")'],
-    HTMLDataListElement: ['document.createElement("datalist")'],
-    HTMLOptGroupElement: ['document.createElement("optgroup")'],
-    HTMLOptionElement: ['document.createElement("option")', 'new Option()'],
-    HTMLTextAreaElement: ['document.createElement("textarea")'],
-    HTMLOutputElement: ['document.createElement("output")'],
-    HTMLProgressElement: ['document.createElement("progress")'],
-    HTMLMeterElement: ['document.createElement("meter")'],
-    ValidityState: ['document.createElement("input").validity'],
-    HTMLDetailsElement: ['document.createElement("details")'],
-    HTMLMenuElement: ['document.createElement("menu")'],
-    Window: ['window'],
-    BarProp: [],
-    History: ['window.history'],
-    Location: ['window.location'],
-    PopStateEvent: ['new PopStateEvent("popstate", { data: {} })'],
-    HashChangeEvent: [],
-    PageTransitionEvent: [],
-    BeforeUnloadEvent: [],
-    ApplicationCache: ['window.applicationCache'],
-    WindowModal: [],
-    Navigator: ['window.navigator'],
-    External: ['window.external'],
-    DataTransfer: [],
-    DataTransferItemList: [],
-    DataTransferItem: [],
-    DragEvent: [],
-    NavigatorUserMediaError: [],
-    MediaStream: [],
-    LocalMediaStream: [],
-    MediaStreamTrack: [],
-    MediaStreamRecorder: [],
-    PeerConnection: [],
-    MediaStreamEvent: [],
-    ErrorEvent: [],
-    WebSocket: ['new WebSocket("ws://foo")'],
-    CloseEvent: ['new CloseEvent("close")'],
-    AbstractWorker: [],
-    Worker: [],
-    SharedWorker: [],
-    MessageEvent: ['new MessageEvent("message", { data: 5 })'],
-    MessageChannel: [],
-    MessagePort: [],
-    HTMLMarqueeElement: ['document.createElement("marquee")'],
-    HTMLFrameSetElement: ['document.createElement("frameset")'],
-    HTMLFrameElement: ['document.createElement("frame")'],
-    HTMLDirectoryElement: ['document.createElement("dir")'],
-    HTMLFontElement: ['document.createElement("font")'],
-  });
-  idlArray.prevent_multiple_testing("HTMLElement");
-  idlArray.test();
-};
-
-function fetchData(url) {
-  return fetch(url).then((response) => response.text());
-}
-
-function waitForLoad() {
-  return new Promise(function(resolve) {
-    addEventListener("load", resolve);
-  });
-}
-
-promise_test(function() {
-  // Have to wait for onload
-  return Promise.all([fetchData("/interfaces/html.idl"),
-                      fetchData("/interfaces/dom.idl"),
-                      fetchData("/interfaces/cssom.idl"),
-                      fetchData("/interfaces/touchevents.idl"),
-                      fetchData("/interfaces/uievents.idl"),
-                      waitForLoad()])
-                .then(doTest);
-}, "Test driver");
-
-</script>
diff --git a/html/dom/interfaces.https.html b/html/dom/interfaces.https.html
new file mode 100644
index 0000000..4cb509d
--- /dev/null
+++ b/html/dom/interfaces.https.html
@@ -0,0 +1,239 @@
+<!doctype html>
+<meta charset=utf-8>
+<!-- WARNING: These tests are preliminary and probably partly incorrect.  -->
+<title>HTML IDL tests</title>
+<meta name=timeout content=long>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/WebIDLParser.js></script>
+<script src=/resources/idlharness.js></script>
+
+<h1>HTML IDL tests</h1>
+<div id=log></div>
+
+<script>
+"use strict";
+var errorVideo; // used to get a MediaError object
+var iframe; // used to get a Document object (can't use `document` because some test clears the page)
+setup(function() {
+  errorVideo = document.createElement('video');
+  errorVideo.src = 'data:,';
+  errorVideo.preload = 'auto';
+  iframe = document.createElement('iframe');
+  iframe.hidden = true;
+  document.body.appendChild(iframe);
+});
+
+function createInput(type) {
+  var input = document.createElement('input');
+  input.type = type;
+  return input;
+}
+
+function doTest([html, dom, cssom, uievents, touchevents]) {
+  var idlArray = new IdlArray();
+  var svg = "interface SVGElement : Element {};";
+  idlArray.add_untested_idls(dom + svg + cssom + uievents + touchevents);
+  idlArray.add_idls(html);
+
+  idlArray.add_objects({
+    NodeList: ['document.getElementsByName("name")'],
+    HTMLAllCollection: ['document.all'],
+    HTMLFormControlsCollection: ['document.createElement("form").elements'],
+    RadioNodeList: [],
+    HTMLOptionsCollection: ['document.createElement("select").options'],
+    DOMStringMap: ['document.head.dataset'],
+    Transferable: [],
+    Document: ['iframe.contentDocument', 'new Document()'],
+    XMLDocument: ['document.implementation.createDocument(null, "", null)'],
+    HTMLElement: ['document.createElement("noscript")'], // more tests in html/semantics/interfaces.js
+    HTMLUnknownElement: ['document.createElement("bgsound")'], // more tests in html/semantics/interfaces.js
+    HTMLHtmlElement: ['document.createElement("html")'],
+    HTMLHeadElement: ['document.createElement("head")'],
+    HTMLTitleElement: ['document.createElement("title")'],
+    HTMLBaseElement: ['document.createElement("base")'],
+    HTMLLinkElement: ['document.createElement("link")'],
+    HTMLMetaElement: ['document.createElement("meta")'],
+    HTMLStyleElement: ['document.createElement("style")'],
+    HTMLScriptElement: ['document.createElement("script")'],
+    HTMLBodyElement: ['document.createElement("body")'],
+    HTMLHeadingElement: ['document.createElement("h1")'],
+    HTMLParagraphElement: ['document.createElement("p")'],
+    HTMLHRElement: ['document.createElement("hr")'],
+    HTMLPreElement: [
+      'document.createElement("pre")',
+      'document.createElement("listing")',
+      'document.createElement("xmp")',
+    ],
+    HTMLQuoteElement: [
+      'document.createElement("blockquote")',
+      'document.createElement("q")',
+    ],
+    HTMLOlistElement: ['document.createElement("ol")'],
+    HTMLUlistElement: ['document.createElement("ul")'],
+    HTMLLIElement: ['document.createElement("li")'],
+    HTMLDlistElement: ['document.createElement("dl")'],
+    HTMLDivElement: ['document.createElement("div")'],
+    HTMLAnchorElement: ['document.createElement("a")'],
+    HTMLDataElement: ['document.createElement("data")'],
+    HTMLTimeElement: ['document.createElement("time")'],
+    HTMLSpanElement: ['document.createElement("span")'],
+    HTMLBRElement: ['document.createElement("br")'],
+    HTMLModElement: [
+      'document.createElement("ins")',
+      'document.createElement("del")',
+    ],
+    HTMLPictureElement: ['document.createElement("picture")'],
+    HTMLImageElement: ['document.createElement("img")', 'new Image()'],
+    HTMLIframeElement: ['document.createElement("iframe")'],
+    HTMLEmbedElement: ['document.createElement("embed")'],
+    HTMLObjectElement: ['document.createElement("object")'],
+    HTMLParamElement: ['document.createElement("param")'],
+    HTMLVideoElement: ['document.createElement("video")'],
+    HTMLAudioElement: ['document.createElement("audio")', 'new Audio()'],
+    HTMLSourceElement: ['document.createElement("source")'],
+    HTMLTrackElement: ['document.createElement("track")'],
+    HTMLMediaElement: [],
+    MediaError: ['errorVideo.error'],
+    AudioTrackList: [],
+    AudioTrack: [],
+    VideoTrackList: [],
+    VideoTrack: [],
+    TextTrackList: ['document.createElement("video").textTracks'],
+    TextTrack: ['document.createElement("track").track'],
+    TextTrackCueList: ['document.createElement("video").addTextTrack("subtitles").cues'],
+    TextTrackCue: [],
+    DataCue: [],
+    TimeRanges: ['document.createElement("video").buffered'],
+    TrackEvent: ['new TrackEvent("addtrack", {track:document.createElement("track").track})'],
+    HTMLTemplateElement: ['document.createElement("template")'],
+    HTMLSlotElement: ['document.createElement("slot")'],
+    HTMLCanvasElement: ['document.createElement("canvas")'],
+    CanvasRenderingContext2D: ['document.createElement("canvas").getContext("2d")'],
+    CanvasGradient: [],
+    CanvasPattern: [],
+    TextMetrics: [],
+    ImageData: [],
+    HTMLMapElement: ['document.createElement("map")'],
+    HTMLAreaElement: ['document.createElement("area")'],
+    HTMLTableElement: ['document.createElement("table")'],
+    HTMLTableCaptionElement: ['document.createElement("caption")'],
+    HTMLTableColElement: [
+      'document.createElement("colgroup")',
+      'document.createElement("col")',
+    ],
+    HTMLTableSectionElement: [
+      'document.createElement("tbody")',
+      'document.createElement("thead")',
+      'document.createElement("tfoot")',
+    ],
+    HTMLTableRowElement: ['document.createElement("tr")'],
+    HTMLTableCellElement: [
+      'document.createElement("td")',
+      'document.createElement("th")',
+    ],
+    HTMLFormElement: ['document.createElement("form")'],
+    HTMLFieldsetElement: ['document.createElement("fieldset")'],
+    HTMLLegendElement: ['document.createElement("legend")'],
+    HTMLLabelElement: ['document.createElement("label")'],
+    HTMLInputElement: [
+      'document.createElement("input")',
+      'createInput("text")',
+      'createInput("hidden")',
+      'createInput("search")',
+      'createInput("tel")',
+      'createInput("url")',
+      'createInput("email")',
+      'createInput("password")',
+      'createInput("date")',
+      'createInput("month")',
+      'createInput("week")',
+      'createInput("time")',
+      'createInput("datetime-local")',
+      'createInput("number")',
+      'createInput("range")',
+      'createInput("color")',
+      'createInput("checkbox")',
+      'createInput("radio")',
+      'createInput("file")',
+      'createInput("submit")',
+      'createInput("image")',
+      'createInput("reset")',
+      'createInput("button")'
+    ],
+    HTMLButtonElement: ['document.createElement("button")'],
+    HTMLSelectElement: ['document.createElement("select")'],
+    HTMLDataListElement: ['document.createElement("datalist")'],
+    HTMLOptGroupElement: ['document.createElement("optgroup")'],
+    HTMLOptionElement: ['document.createElement("option")', 'new Option()'],
+    HTMLTextAreaElement: ['document.createElement("textarea")'],
+    HTMLOutputElement: ['document.createElement("output")'],
+    HTMLProgressElement: ['document.createElement("progress")'],
+    HTMLMeterElement: ['document.createElement("meter")'],
+    ValidityState: ['document.createElement("input").validity'],
+    HTMLDetailsElement: ['document.createElement("details")'],
+    HTMLMenuElement: ['document.createElement("menu")'],
+    Window: ['window'],
+    BarProp: [],
+    History: ['window.history'],
+    Location: ['window.location'],
+    PopStateEvent: ['new PopStateEvent("popstate", { data: {} })'],
+    HashChangeEvent: [],
+    PageTransitionEvent: [],
+    BeforeUnloadEvent: [],
+    ApplicationCache: ['window.applicationCache'],
+    WindowModal: [],
+    Navigator: ['window.navigator'],
+    External: ['window.external'],
+    DataTransfer: [],
+    DataTransferItemList: [],
+    DataTransferItem: [],
+    DragEvent: [],
+    NavigatorUserMediaError: [],
+    MediaStream: [],
+    LocalMediaStream: [],
+    MediaStreamTrack: [],
+    MediaStreamRecorder: [],
+    PeerConnection: [],
+    MediaStreamEvent: [],
+    ErrorEvent: [],
+    WebSocket: ['new WebSocket("wss://foo")'],
+    CloseEvent: ['new CloseEvent("close")'],
+    AbstractWorker: [],
+    Worker: [],
+    SharedWorker: [],
+    MessageEvent: ['new MessageEvent("message", { data: 5 })'],
+    MessageChannel: [],
+    MessagePort: [],
+    HTMLMarqueeElement: ['document.createElement("marquee")'],
+    HTMLFrameSetElement: ['document.createElement("frameset")'],
+    HTMLFrameElement: ['document.createElement("frame")'],
+    HTMLDirectoryElement: ['document.createElement("dir")'],
+    HTMLFontElement: ['document.createElement("font")'],
+  });
+  idlArray.prevent_multiple_testing("HTMLElement");
+  idlArray.test();
+};
+
+function fetchData(url) {
+  return fetch(url).then((response) => response.text());
+}
+
+function waitForLoad() {
+  return new Promise(function(resolve) {
+    addEventListener("load", resolve);
+  });
+}
+
+promise_test(function() {
+  // Have to wait for onload
+  return Promise.all([fetchData("/interfaces/html.idl"),
+                      fetchData("/interfaces/dom.idl"),
+                      fetchData("/interfaces/cssom.idl"),
+                      fetchData("/interfaces/touchevents.idl"),
+                      fetchData("/interfaces/uievents.idl"),
+                      waitForLoad()])
+                .then(doTest);
+}, "Test driver");
+
+</script>
diff --git a/html/dom/usvstring-reflection.html b/html/dom/usvstring-reflection.html
new file mode 100644
index 0000000..b9c06d8
--- /dev/null
+++ b/html/dom/usvstring-reflection.html
@@ -0,0 +1,137 @@
+<!doctype html>
+<title>USVString test relate to url</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../../webrtc/RTCPeerConnection-helper.js"></script>
+<div id=log></div>
+<script>
+// Unpaired surrogate codepoints present in USVString are replaced
+// with U+FFFD. %EF%BF%BD is UTF-8 encoding of U+FFFD.
+'use strict';
+test(() => {
+  location.hash = '\uD999';
+  assert_equals(location.hash, '#%EF%BF%BD');
+}, "location.hash : unpaired surrogate codepoint should be replaced with U+FFFD");
+
+test(() => {
+  var w = window.open("about:blank#\uD800");
+  assert_equals(w.location.href, 'about:blank#%EF%BF%BD');
+  w.location.href = 'about:blank#\uD999';
+  assert_equals(w.location.href, 'about:blank#%EF%BF%BD');
+}, "location.href : unpaired surrogate codepoint should be replaced with U+FFFD");
+
+test(() => {
+  var w = window.open("about:blank#\uD800");
+  assert_equals(w.location.hash, '#%EF%BF%BD');
+}, "window.open : unpaired surrogate codepoint should be replaced with U+FFFD");
+
+test(() => {
+  var w = document.open("about:blank#\uD800", "", "");
+  assert_equals(w.location.hash, '#%EF%BF%BD');
+}, "document.open : unpaired surrogate codepoint should be replaced with U+FFFD");
+
+test(() => {
+  var element = document.createElement("a");
+  element.ping = '\uD989';
+  assert_equals(element.ping, '\uFFFD');
+}, "anchor : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var element = document.createElement("area");
+  element.ping = '\uDA99';
+  assert_equals(element.ping, '\uFFFD');
+}, "area : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var element = document.createElement("base");
+  element.href = '\uD989';
+  assert_equals(element.href.endsWith('%EF%BF%BD'), true);
+}, "base : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var src = new EventSource('\uD899');
+  assert_equals(src.url.endsWith('%EF%BF%BD'), true);
+}, "EventSource : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var element = document.createElement("frame");
+  element.src = '\uDCA9';
+  element.longDesc = '\uDCA8';
+  assert_equals(element.src.endsWith('%EF%BF%BD'), true);
+  assert_equals(element.longDesc.endsWith('%EF%BF%BD'), true);
+}, "frame : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var element = document.createElement("iframe");
+  element.src = '\uDC89';
+  element.longDesc = '\uDC88';
+  assert_equals(element.src.endsWith('%EF%BF%BD'), true);
+  assert_equals(element.longDesc.endsWith('%EF%BF%BD'), true);
+}, "iframe : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var element = document.createElement("link");
+  element.href = '\uDB89';
+  assert_equals(element.href.endsWith('%EF%BF%BD'), true);
+}, "link : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var element = document.createElement("source");
+  element.src = '\uDDDD';
+  element.srcset = '\uD800';
+  assert_equals(element.src.endsWith('%EF%BF%BD'), true);
+  assert_equals(element.srcset, '\uFFFD');
+}, "source : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  const event = new StorageEvent('storage', {
+    url: window.location.href + '\uD999',
+  });
+  assert_equals(event.url, window.location.href + "\uFFFD");
+}, "storage event : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  var wsocket = new EventSource('ws://www.example.com/socketserve\uD899/');
+  assert_true(wsocket.url.endsWith('ws://www.example.com/socketserve%EF%BF%BD/'));
+}, "websocket url : unpaired surrogate codepoint should be replaced with U+FFFD")
+
+test(() => {
+  try {
+    navigator.sendBeacon("resources/\uD800blank.txt");
+    assert_true(true);
+  } catch (e) {
+    assert_true(false);
+  }
+}, "sendBeacon URL: unpaired surrogate codepoint should not make any exceptions.")
+
+test(() => {
+  // This shouldn't throw an exception.
+  window.navigator.registerProtocolHandler('web+myprotocol', "custom-scheme\uD800/url=%s", "title");
+}, "RegisterPtotocolHandler URL: unpaired surrogate codepoint should not make any exceptions.")
+
+test(() => {
+  var w = window.open("about:blank#\uD800");
+  assert_equals(w.document.URL, 'about:blank#%EF%BF%BD');
+  assert_equals(w.document.documentURI, 'about:blank#%EF%BF%BD');
+  // TODO(gyuyoung): How to test document.origin? When opening a URL with an
+  // unpaired surrogate codepoint, invalid URL exception happens.
+  //   e.g) var w = window.open("http://test.com\uDB89");
+}, "Document URLs: unpaired surrogate codepoint should be replaced with U+FFFD")
+
+promise_test(t => {
+  const sendString = 'hello\uD999';
+  const receiveString = 'hello\uFFFD';
+
+  return createDataChannelPair()
+  .then(([channel1, channel2]) => {
+    channel1.send(sendString);
+    return awaitMessage(channel2)
+  }).then(message => {
+    assert_equals(typeof message, 'string',
+      'Expect message to be a string');
+
+    assert_equals(message, receiveString);
+  });
+}, "RTCDataChannel.send: unpaired surrogate codepoint should be replaced with U+FFFD.")
+
+</script>
diff --git a/html/editing/dnd/datastore/015-manual.html b/html/editing/dnd/datastore/015-manual.html
index 6bb3cad..604c1d4 100644
--- a/html/editing/dnd/datastore/015-manual.html
+++ b/html/editing/dnd/datastore/015-manual.html
@@ -9,6 +9,7 @@
 blockquote + div { clear: left; }
     </style>
     <script type="text/javascript" src="/resources/testharness.js"></script>
+    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
     <script type="text/javascript">
 setup(function () {},{explicit_done:true,explicit_timeout:true});
 window.onload = function () {
diff --git a/html/editing/dnd/events/events-cross-document-suite-manual.html b/html/editing/dnd/events/events-cross-document-suite-manual.html
index 1a07a94..9428a87 100644
--- a/html/editing/dnd/events/events-cross-document-suite-manual.html
+++ b/html/editing/dnd/events/events-cross-document-suite-manual.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <title>drag &amp; drop - event sequence for cross-document drag</title>
 <script type="text/javascript" src="/resources/testharness.js"></script>
+<script type="text/javascript" src="/resources/testharnessreport.js"></script>
 <script>
 var events = new Array();
 </script>
diff --git a/html/editing/dnd/events/events-file-suite-manual.html b/html/editing/dnd/events/events-file-suite-manual.html
index 380247b..22a66f5 100644
--- a/html/editing/dnd/events/events-file-suite-manual.html
+++ b/html/editing/dnd/events/events-file-suite-manual.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <title>drag &amp; drop - event sequence for file drops</title>
 <script type="text/javascript" src="/resources/testharness.js"></script>
+<script type="text/javascript" src="/resources/testharnessreport.js"></script>
 <style type="text/css">
   /* use margins instead of padding to make sure the body begins at the top of the page */
   html, body {
diff --git a/html/editing/dnd/events/events-suite-manual.html b/html/editing/dnd/events/events-suite-manual.html
index b848fa9..16c6583 100644
--- a/html/editing/dnd/events/events-suite-manual.html
+++ b/html/editing/dnd/events/events-suite-manual.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <title>drag &amp; drop - event sequence for draggable elements</title>
 <script type="text/javascript" src="/resources/testharness.js"></script>
+<script type="text/javascript" src="/resources/testharnessreport.js"></script>
 <style type="text/css">
   /* use margins instead of padding to make sure the body begins at the top of the page */
   html, body {
diff --git a/html/editing/dnd/file/001.html b/html/editing/dnd/file/001.html
index 07ef4db..b911920 100644
--- a/html/editing/dnd/file/001.html
+++ b/html/editing/dnd/file/001.html
@@ -55,8 +55,8 @@
       fails[fails.length] = 'dataTransfer.files[0].size '+e.dataTransfer.files[0].size+' instead of '+filesize;
     }
     /*
-    if( !e.dataTransfer.files[0].lastModifiedDate ) {
-      fails[fails.length] = 'no dataTransfer.files[0].lastModifiedDate';
+    if( !e.dataTransfer.files[0].lastModified ) {
+      fails[fails.length] = 'no dataTransfer.files[0].lastModified';
     }
     */
     if( e.dataTransfer.files[0].name != filename ) {
diff --git a/html/editing/dnd/file/002.html b/html/editing/dnd/file/002.html
index b029afb..c8d633d 100644
--- a/html/editing/dnd/file/002.html
+++ b/html/editing/dnd/file/002.html
@@ -66,8 +66,8 @@
       fails[fails.length] = 'dataTransfer.files['+i0+'].size '+e.dataTransfer.files[i0].size+' instead of '+filesize1;
     }
     /*
-    if( !e.dataTransfer.files[i0].lastModifiedDate ) {
-      fails[fails.length] = 'no dataTransfer.files['+i0+'].lastModifiedDate';
+    if( !e.dataTransfer.files[i0].lastModified ) {
+      fails[fails.length] = 'no dataTransfer.files['+i0+'].lastModified';
     }
     */
     if( e.dataTransfer.files[i0].name != filename1 ) {
@@ -80,8 +80,8 @@
       fails[fails.length] = 'dataTransfer.files['+i1+'].size '+e.dataTransfer.files[i1].size+' instead of '+filesize2;
     }
     /*
-    if( !e.dataTransfer.files[i1].lastModifiedDate ) {
-      fails[fails.length] = 'no dataTransfer.files['+i1+'].lastModifiedDate';
+    if( !e.dataTransfer.files[i1].lastModified ) {
+      fails[fails.length] = 'no dataTransfer.files['+i1+'].lastModified';
     }
     */
     if( e.dataTransfer.files[i1].name != filename2 ) {
diff --git a/html/editing/dnd/file/003.html b/html/editing/dnd/file/003.html
index 0c532ea..51e5a5a 100644
--- a/html/editing/dnd/file/003.html
+++ b/html/editing/dnd/file/003.html
@@ -42,8 +42,8 @@
       fails[fails.length] = 'dataTransfer.files[0].size '+e.dataTransfer.files[0].size+' instead of '+filesize;
     }
     /*
-    if( !e.dataTransfer.files[0].lastModifiedDate ) {
-      fails[fails.length] = 'no dataTransfer.files[0].lastModifiedDate';
+    if( !e.dataTransfer.files[0].lastModified ) {
+      fails[fails.length] = 'no dataTransfer.files[0].lastModified';
     }
     */
     if( e.dataTransfer.files[0].name != filename ) {
diff --git a/html/editing/dnd/file/007.html b/html/editing/dnd/file/007.html
index f3e5117..099716f 100644
--- a/html/editing/dnd/file/007.html
+++ b/html/editing/dnd/file/007.html
@@ -57,8 +57,8 @@
       fails[fails.length] = 'dataTransfer.files[0].size '+e.dataTransfer.files[0].size+' instead of '+filesize1+' or '+filesize2;
     }
     /*
-    if( !e.dataTransfer.files[0].lastModifiedDate ) {
-      fails[fails.length] = 'no dataTransfer.files[0].lastModifiedDate';
+    if( !e.dataTransfer.files[0].lastModified ) {
+      fails[fails.length] = 'no dataTransfer.files[0].lastModified';
     }
     */
     if( !window.FileReader ) {
diff --git a/html/editing/dnd/file/008.html b/html/editing/dnd/file/008.html
index 3ba2827..4975158 100644
--- a/html/editing/dnd/file/008.html
+++ b/html/editing/dnd/file/008.html
@@ -60,8 +60,8 @@
     }
     */
     /*
-    if( !e.dataTransfer.files[0].lastModifiedDate ) {
-      fails[fails.length] = 'no dataTransfer.files[0].lastModifiedDate';
+    if( !e.dataTransfer.files[0].lastModified ) {
+      fails[fails.length] = 'no dataTransfer.files[0].lastModified';
     }
     */
     if( !window.FileReader ) {
diff --git a/html/editing/dnd/file/011.html b/html/editing/dnd/file/011.html
index 2c12d64..a265e7b 100644
--- a/html/editing/dnd/file/011.html
+++ b/html/editing/dnd/file/011.html
@@ -45,8 +45,8 @@
       fails[fails.length] = 'dataTransfer.files[0].size '+e.dataTransfer.files[0].size+' instead of '+filesize;
     }
     /*
-    if( !e.dataTransfer.files[0].lastModifiedDate ) {
-      fails[fails.length] = 'no dataTransfer.files[0].lastModifiedDate';
+    if( !e.dataTransfer.files[0].lastModified ) {
+      fails[fails.length] = 'no dataTransfer.files[0].lastModified';
     }
     */
     if( e.dataTransfer.files[0].name != filename ) {
diff --git a/html/editing/dnd/synthetic/005-manual.html b/html/editing/dnd/synthetic/005-manual.html
index f3bb221..4e0b00d 100644
--- a/html/editing/dnd/synthetic/005-manual.html
+++ b/html/editing/dnd/synthetic/005-manual.html
@@ -9,6 +9,7 @@
 blockquote + div { clear: left; }
     </style>
     <script type="text/javascript" src="/resources/testharness.js"></script>
+    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
     <script type="text/javascript">
 setup(function () {},{explicit_done:true,explicit_timeout:true});
 window.onload = function () {
diff --git a/html/editing/dnd/synthetic/006-manual.html b/html/editing/dnd/synthetic/006-manual.html
index 2007116..e7d1677 100644
--- a/html/editing/dnd/synthetic/006-manual.html
+++ b/html/editing/dnd/synthetic/006-manual.html
@@ -9,6 +9,7 @@
 blockquote + div { clear: left; }
     </style>
     <script type="text/javascript" src="/resources/testharness.js"></script>
+    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
     <script type="text/javascript">
 setup(function () {},{explicit_done:true,explicit_timeout:true});
 window.onload = function () {
diff --git a/html/editing/dnd/target-origin/001-manual.html b/html/editing/dnd/target-origin/001-manual.html
index 13c6864..4622c76 100644
--- a/html/editing/dnd/target-origin/001-manual.html
+++ b/html/editing/dnd/target-origin/001-manual.html
@@ -6,6 +6,7 @@
 blockquote { height: 100px; width: 100px; background: orange; margin: 0; padding: 0; }
     </style>
     <script type="text/javascript" src="/resources/testharness.js"></script>
+    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
     <script type="text/javascript">
 setup(function () {},{explicit_done:true});
 window.onload = function () {
diff --git a/html/editing/dnd/target-origin/002-manual.html b/html/editing/dnd/target-origin/002-manual.html
index c4e8acd..d161509 100644
--- a/html/editing/dnd/target-origin/002-manual.html
+++ b/html/editing/dnd/target-origin/002-manual.html
@@ -9,6 +9,7 @@
 blockquote + div { clear: left; }
     </style>
     <script type="text/javascript" src="/resources/testharness.js"></script>
+    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
     <script type="text/javascript">
 setup(function () {},{explicit_done:true});
 window.onload = function () {
diff --git a/html/editing/dnd/target-origin/101-manual.html b/html/editing/dnd/target-origin/101-manual.html
index e5bbd7f..23f5cf9 100644
--- a/html/editing/dnd/target-origin/101-manual.html
+++ b/html/editing/dnd/target-origin/101-manual.html
@@ -10,6 +10,7 @@
     </style>
     <script type="text/javascript" src="../resources/crossorigin.sub.js"></script>
     <script type="text/javascript" src="/resources/testharness.js"></script>
+    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
     <script type="text/javascript">
 setup(function () {},{explicit_done:true});
 window.onload = function () {
diff --git a/html/editing/dnd/the-datatransfer-interface/DataTransfer-types-manual.html b/html/editing/dnd/the-datatransfer-interface/DataTransfer-types-manual.html
new file mode 100644
index 0000000..0a62997
--- /dev/null
+++ b/html/editing/dnd/the-datatransfer-interface/DataTransfer-types-manual.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>DataTransferItem Test: types - files</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/dnd.html#dom-datatransfer-types"/>
+<meta name="flags" content="interact">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<p><div id="div" style="border: 2px green solid; width: 200px; height: 200px;"></div></p>
+
+<h2>Test steps:</h2>
+<p>Drag a file enter the green box, then drop file out</p>
+
+<script>
+
+let div = document.getElementById("div");
+
+setup({explicit_done: true});
+setup({explicit_timeout: true});
+
+on_event(div, "dragenter", evt => {
+  let type = evt.dataTransfer.types[0];
+  test(() => {
+    assert_equals(type, "Files");
+  }, "Check if one of the types will be the string 'Files' when drag a file");
+  done();
+});
+
+</script>
diff --git a/html/editing/editing-0/autocapitalization/autocapitalize.html b/html/editing/editing-0/autocapitalization/autocapitalize.html
new file mode 100644
index 0000000..392ada4
--- /dev/null
+++ b/html/editing/editing-0/autocapitalization/autocapitalize.html
@@ -0,0 +1,688 @@
+<!DOCTYPE html>
+<html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#autocapitalization">
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+test(function() {
+    assert_true('autocapitalize' in document.createElement('input'));
+}, "Test that the autocapitalize is avaible on HTMLInputElement.")
+
+test(function() {
+    assert_true('autocapitalize' in document.createElement('textarea'));
+}, "Test that the autocapitalize is avaible on HTMLTextAreaElement.")
+
+test(function() {
+    assert_true('autocapitalize' in document.createElement('div'));
+}, "Test that the autocapitalize is avaible on div.")
+
+test(function() {
+  var elements = [ document.createElement('input'),
+                   document.createElement('textarea'),
+                   document.createElement('div') ];
+
+  elements.forEach(function(e) {
+    e.autocapitalize = 'on';
+    assert_equals(e.autocapitalize, 'sentences');
+
+    e.autocapitalize = 'off';
+    assert_equals(e.autocapitalize, 'none');
+  });
+}, "Test deprecated values of autocapitalize.");
+
+test(function() {
+  var elements = [ document.createElement('input'),
+                   document.createElement('textarea'),
+                   document.createElement('div') ];
+  var knownValues = [ 'none', 'characters', 'words', 'sentences' ];
+
+  elements.forEach(function(e) {
+    // Default value.
+    assert_equals(e.autocapitalize, '');
+
+    // Empty value.
+    e.autocapitalize = '';
+    assert_equals(e.autocapitalize, '');
+    assert_equals(e.getAttribute('autocapitalize'), '');
+    e.setAttribute('autocapitalize', '');
+    assert_equals(e.autocapitalize, '');
+    assert_equals(e.getAttribute('autocapitalize'), '');
+    assert_equals(e.autocapitalize, '');
+
+    // Invalid value.
+    e.autocapitalize = 'foo';
+    assert_equals(e.autocapitalize, 'sentences');
+    assert_equals(e.getAttribute('autocapitalize'), 'foo');
+    e.setAttribute('autocapitalize', 'bar');
+    assert_equals(e.autocapitalize, 'sentences');
+    assert_equals(e.getAttribute('autocapitalize'), 'bar');
+
+    // Default value.
+    e.removeAttribute('autocapitalize');
+    assert_equals(e.autocapitalize, '');
+    assert_equals(e.getAttribute('autocapitalize'), null);
+
+    // Case insensitive.
+    e.setAttribute('autocapitalize', 'NoNe');
+    assert_equals(e.autocapitalize, 'none');
+    assert_equals(e.getAttribute('autocapitalize'), 'NoNe');
+    e.autocapitalize = 'WORDS';
+    assert_equals(e.autocapitalize, 'words');
+    assert_equals(e.getAttribute('autocapitalize'), 'WORDS');
+
+    knownValues.forEach(function(value) {
+      e.setAttribute('autocapitalize', value);
+      assert_equals(e.autocapitalize, value);
+      assert_equals(e.getAttribute('autocapitalize'), value);
+
+      e.removeAttribute('autocapitalize');
+
+      e.autocapitalize = value;
+      assert_equals(e.autocapitalize, value);
+      assert_equals(e.getAttribute('autocapitalize'), value);
+
+      e.removeAttribute('autocapitalize');
+    });
+  });
+}, "Test reflection of autocapitalize.");
+
+test(function() {
+var testData = [ 'text',
+                 'search',
+                 'email',
+                 'url',
+                 'tel',
+                 'number',
+                 'date',
+                 'color',
+                 'password' ];
+
+  testData.forEach(function(data) {
+    const input = document.createElement('input');
+    input.type = data;
+    assert_equals(input.autocapitalize, '');
+
+    // Verify that wrapping the input element in a form doesn't change the
+    // defaults.
+    const form = document.createElement('form');
+    form.appendChild(input);
+    assert_equals(input.autocapitalize, '');
+  });
+}, "Test that the IDL attribute returns the empty string if the content "
++ "attribute is not set.")
+
+test(function() {
+  const testData = [
+    {
+      formValue: null,
+      formElementValue: null,
+      inheritedResult: '',
+      uninheritedResult: '',
+    },
+    {
+      formValue: null,
+      formElementValue: '',
+      inheritedResult: '',
+      uninheritedResult: '',
+    },
+    {
+      formValue: null,
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: null,
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: null,
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: null,
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: null,
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: null,
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: null,
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: '',
+      formElementValue: null,
+      inheritedResult: '',
+      uninheritedResult: '',
+    },
+    {
+      formValue: '',
+      formElementValue: '',
+      inheritedResult: '',
+      uninheritedResult: '',
+    },
+    {
+      formValue: '',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: '',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: '',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: '',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: '',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: '',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: '',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'on',
+      formElementValue: null,
+      inheritedResult: 'sentences',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'on',
+      formElementValue: '',
+      inheritedResult: 'sentences',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'on',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'on',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'on',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'on',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: 'on',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: 'on',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'on',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'off',
+      formElementValue: null,
+      inheritedResult: 'none',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'off',
+      formElementValue: '',
+      inheritedResult: 'none',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'off',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'off',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'off',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'off',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: 'off',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: 'off',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'off',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'none',
+      formElementValue: null,
+      inheritedResult: 'none',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'none',
+      formElementValue: '',
+      inheritedResult: 'none',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'none',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'none',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'none',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'none',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: 'none',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: 'none',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'none',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: null,
+      inheritedResult: 'characters',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: '',
+      inheritedResult: 'characters',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'characters',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'words',
+      formElementValue: null,
+      inheritedResult: 'words',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'words',
+      formElementValue: '',
+      inheritedResult: 'words',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'words',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'words',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'words',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'words',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: 'words',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: 'words',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'words',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: null,
+      inheritedResult: 'sentences',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: '',
+      inheritedResult: 'sentences',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'sentences',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: null,
+      inheritedResult: 'sentences',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: '',
+      inheritedResult: 'sentences',
+      uninheritedResult: '',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: 'on',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: 'off',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: 'none',
+      inheritedResult: 'none',
+      uninheritedResult: 'none',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: 'characters',
+      inheritedResult: 'characters',
+      uninheritedResult: 'characters',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: 'words',
+      inheritedResult: 'words',
+      uninheritedResult: 'words',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: 'sentences',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+    {
+      formValue: 'foo',
+      formElementValue: 'foo',
+      inheritedResult: 'sentences',
+      uninheritedResult: 'sentences',
+    },
+  ];
+
+  const formElements = [
+    {element: 'button', inherits: true},
+    {element: 'fieldset', inherits: true},
+    {element: 'img', inherits: false},
+    {element: 'input', inherits: true},
+    {element: 'object', inherits: false},
+    {element: 'output', inherits: true},
+    {element: 'select', inherits: true},
+    {element: 'textarea', inherits: true},
+  ];
+
+  const form = document.createElement('form');
+  form.id = 'form';
+  document.body.appendChild(form);
+
+  testData.forEach(data => {
+    form.removeAttribute('autocapitalize');
+
+    if (data.formValue !== null) {
+      form.setAttribute('autocapitalize', data.formValue);
+    }
+
+    formElements.forEach(elementData => {
+      const element = document.createElement(elementData.element);
+      form.appendChild(element);
+
+      const element2 = document.createElement(elementData.element);
+      element2.setAttribute('form', 'form');
+      document.body.appendChild(element2);
+
+      if (data.formElementValue !== null) {
+        element.setAttribute('autocapitalize', data.formElementValue);
+        element2.setAttribute('autocapitalize', data.formElementValue);
+      }
+
+      const descriptionSuffix = 'with "' + data.formValue
+          + '" and form element with "'+ data.formElementValue + '"';
+
+      if (elementData.inherits) {
+        assert_equals(element.autocapitalize, data.inheritedResult,
+                      `${elementData.element} element with form parent `
+                          + `${descriptionSuffix}`);
+        assert_equals(element2.autocapitalize, data.inheritedResult,
+                      `${elementData.element} element with form owner attribute`
+                          + ` set ${descriptionSuffix}`);
+      } else {
+        assert_equals(element.autocapitalize, data.uninheritedResult,
+                      `${elementData.element} element with form parent `
+                          + `${descriptionSuffix}`);
+        assert_equals(element2.autocapitalize, data.uninheritedResult,
+                      `${elementData.element} element with form owner attribute`
+                          + `set ${descriptionSuffix}`);
+      }
+    });
+  });
+}, "Test inheriting values from a form.")
+
+test(function() {
+  const testData = [ 'text',
+                     'search',
+                     'email',
+                     'url',
+                     'tel',
+                     'number',
+                     'date',
+                     'color',
+                     'password' ];
+
+  testData.forEach(function(data) {
+    const form = document.createElement('form');
+    form.setAttribute('autocapitalize', 'sentences');
+    const input = document.createElement('input');
+    input.setAttribute('type', data);
+    form.appendChild(input);
+
+    assert_equals(input.autocapitalize, 'sentences');
+  });
+}, "Verify that even input types that are never autocapitalized support the "
++ "IDL interface.")
+
+</script>
+</body>
+</html>
diff --git a/html/editing/editing-0/contenteditable/contentEditable-slotted-inherit.html b/html/editing/editing-0/contenteditable/contentEditable-slotted-inherit.html
new file mode 100644
index 0000000..42da515
--- /dev/null
+++ b/html/editing/editing-0/contenteditable/contentEditable-slotted-inherit.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>contentEditable inherit from light tree parent</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#contenteditable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p>You should see the word PASS two times below and no FAIL.</p>
+<div id="host1" contenteditable><div>FAILPASS</div></div>
+<div id="host2" contenteditable><div>FAILPASS</div></div>
+<script>
+  test(() => {
+    const root = host1.attachShadow({mode:"open"});
+    root.innerHTML = "<slot></slot>";
+    const text = host1.firstChild.firstChild;
+    const selection = window.getSelection();
+    selection.collapse(text, 0);
+    selection.extend(text, 4);
+    host1.focus();
+    document.execCommand("delete");
+    host1.blur();
+    assert_equals(text.data, "PASS", "Text should be PASS after FAIL is deleted");
+  }, "Slotted child of contenteditable host should be editable - slot direct child of shadow root");
+
+  test(() => {
+    const root = host2.attachShadow({mode:"open"});
+    root.innerHTML = "<div><slot></slot></div>";
+    const text = host2.firstChild.firstChild;
+    const selection = window.getSelection();
+    selection.collapse(text, 0);
+    selection.extend(text, 4);
+    host2.focus();
+    document.execCommand("delete");
+    host2.blur();
+    assert_equals(text.data, "PASS", "Text should be PASS after FAIL is deleted");
+  }, "Slotted child of contenteditable host should be editable - slot wrapped in shadow tree ancestor");
+</script>
diff --git a/html/editing/focus/composed.window.js b/html/editing/focus/composed.window.js
new file mode 100644
index 0000000..8951afc
--- /dev/null
+++ b/html/editing/focus/composed.window.js
@@ -0,0 +1,16 @@
+async_test(t => {
+  const input = document.body.appendChild(document.createElement("input"));
+  let happened = false;
+  input.onfocus = t.step_func(e => {
+    happened = true;
+    assert_equals(e.type, "focus");
+    assert_true(e.composed);
+  });
+  input.focus();
+  input.onblur = t.step_func_done(e => {
+    assert_true(happened);
+    assert_equals(e.type, "blur");
+    assert_true(e.composed);
+  });
+  input.blur();
+}, "Focus events are composed");
diff --git a/html/editing/focus/focus-01-manual.html b/html/editing/focus/focus-01-manual.html
deleted file mode 100644
index 16e0b0f..0000000
--- a/html/editing/focus/focus-01-manual.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>HTML Test: focus - key events</title>
-<link rel="author" title="Intel" href="http://www.intel.com/">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
-<meta assert="flag" content="interact">
-<meta assert="assert" content="Check if the key events received by document are targeted at the element when it is focused">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<h2>Steps:</h2>
-<ol>
-  <li>Input any character into the textbox by keyboard in 10 seconds.</li>
-</ol>
-<h2>Expect results:</h2>
-<p>PASS</p>
-<div id="log"></div>
-<input id="test">
-<script>
-
-//These tests can be automated once we have an uniform way to use webdriver.
-var t1 = async_test("The keydown event must be targeted at the input element"),
-    t2 = async_test("The keypress event must be targeted at the input element"),
-    t3 = async_test("The keyup event must be targeted at the input element"),
-    testEle;
-
-setup(function () {
-  testEle = document.getElementById("test");
-  testEle.focus();
-}, {timeout: 10000});
-
-document.onkeydown = t1.step_func_done(function(evt){
-  assert_equals(evt.target, testEle, "The keydown events must be targeted at the input element.");
-});
-
-document.onkeypress = t2.step_func_done(function(evt){
-  assert_equals(evt.target, testEle, "The keypress events must be targeted at the input element.");
-});
-
-document.onkeyup = t3.step_func_done(function(evt){
-  assert_equals(evt.target, testEle, "The keyup events must be targeted at the input element.");
-});
-
-</script>
diff --git a/html/editing/focus/focus-01.html b/html/editing/focus/focus-01.html
new file mode 100644
index 0000000..9d1bf1b
--- /dev/null
+++ b/html/editing/focus/focus-01.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - key events</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the key events received by document are targeted at the element when it is focused">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id="log"></div>
+<input id="test">
+<script>
+
+var t1 = async_test("The keydown event must be targeted at the input element"),
+    t2 = async_test("The keypress event must be targeted at the input element"),
+    t3 = async_test("The keyup event must be targeted at the input element"),
+    testEle;
+
+setup(function () {
+  testEle = document.getElementById("test");
+  testEle.focus();
+}, {timeout: 10000});
+
+document.onkeydown = t1.step_func_done(function(evt){
+  assert_equals(evt.target, testEle, "The keydown events must be targeted at the input element.");
+});
+
+document.onkeypress = t2.step_func_done(function(evt){
+  assert_equals(evt.target, testEle, "The keypress events must be targeted at the input element.");
+});
+
+document.onkeyup = t3.step_func_done(function(evt){
+  assert_equals(evt.target, testEle, "The keyup events must be targeted at the input element.");
+});
+
+var input_element = document.getElementById("test");
+
+t1.step(function() {
+  test_driver.send_keys(input_element, "a");
+});
+
+</script>
diff --git a/html/editing/focus/focus-02-manual.html b/html/editing/focus/focus-02-manual.html
deleted file mode 100644
index 16dcfcf..0000000
--- a/html/editing/focus/focus-02-manual.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>HTML Test: focus - key events</title>
-<link rel="author" title="Intel" href="http://www.intel.com/">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
-<meta assert="flag" content="interact">
-<meta assert="assert" content="Check if the key events received by document are targeted at the element when no element is focused">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<h2>Steps:</h2>
-<ol>
-  <li>Press any key in [0-9a-zA-Z].</li>
-</ol>
-<h2>Expect results:</h2>
-<p>PASS</p>
-<div id="log"></div>
-<script>
-
-//These tests can be automated once we have an uniform way to use webdriver.
-var t1 = async_test("The keydown event must be targeted at the body element"),
-    t2 = async_test("The keypress event must be targeted at the body element"),
-    t3 = async_test("The keyup event must be targeted at the body element");
-
-setup({timeout: 10000});
-
-document.onkeydown = t1.step_func_done(function(evt){
-  assert_equals(evt.target, document.body, "The keydown events must be targeted at the document's body.");
-});
-
-document.onkeypress = t2.step_func_done(function(evt){
-  assert_equals(evt.target, document.body, "The keypress events must be targeted at the document's body.");
-});
-
-document.onkeyup = t3.step_func_done(function(evt){
-  assert_equals(evt.target, document.body, "The keyup events must be targeted at the document's body.");
-});
-
-</script>
diff --git a/html/editing/focus/focus-02.html b/html/editing/focus/focus-02.html
new file mode 100644
index 0000000..1858d6a
--- /dev/null
+++ b/html/editing/focus/focus-02.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - key events</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the key events received by document are targeted at the element when no element is focused">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id="log"></div>
+<script>
+
+var t1 = async_test("The keydown event must be targeted at the body element"),
+    t2 = async_test("The keypress event must be targeted at the body element"),
+    t3 = async_test("The keyup event must be targeted at the body element");
+
+setup({timeout: 10000});
+
+document.onkeydown = t1.step_func_done(function(evt){
+  assert_equals(evt.target, document.body, "The keydown events must be targeted at the document's body.");
+});
+
+document.onkeypress = t2.step_func_done(function(evt){
+  assert_equals(evt.target, document.body, "The keypress events must be targeted at the document's body.");
+});
+
+document.onkeyup = t3.step_func_done(function(evt){
+  assert_equals(evt.target, document.body, "The keyup events must be targeted at the document's body.");
+});
+
+t1.step(function() {
+  test_driver.send_keys(document.body, "a");
+});
+
+</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative-manual.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative-manual.html
deleted file mode 100644
index 5064350..0000000
--- a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative-manual.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>HTML Test: focus - negative tabindex</title>
-<link rel="author" title="Intel" href="http://www.intel.com/">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
-<meta assert="flag" content="interact">
-<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<h2>Steps:</h2>
-<ol>
-  <li>Press 'Tab' key in 10 seconds.</li>
-</ol>
-<h2>Expect results:</h2>
-<p>PASS</p>
-<div id="log"></div>
-<form id="fm">
-  <input id="test1" tabindex="-1">
-  <input id="test2" tabindex="0">
-</form>
-<script>
-
-//This test can be automated once we have an uniform way to use webdriver.
-var t = async_test("The element with a negative tabindex must not be focused by press 'Tab' key");
-
-setup({timeout: 10000});
-
-document.forms.fm.addEventListener("focus", function (evt) {
-  t.step(function () {
-    var testEle = document.getElementById("test1");
-    assert_equals(testEle.tabIndex, -1, "The tabIndex attribute of the first input element should be -1.");
-    assert_not_equals(evt.target, testEle, "The second input element must be focused.");
-    assert_equals(document.activeElement, document.getElementById("test2"), "The second input element must be activated.");
-  });
-  t.done();
-}, true);
-
-document.addEventListener("keydown", function (evt) {
-  t.step(function () {
-    assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
-  });
-}, true);
-
-</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html
new file mode 100644
index 0000000..92bf174
--- /dev/null
+++ b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - negative tabindex</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id="log"></div>
+<form id="fm">
+  <input id="test1" tabindex="-1">
+  <input id="test2" tabindex="0">
+</form>
+<script>
+
+var t = async_test("The element with a negative tabindex must not be focused by press 'Tab' key");
+
+setup({timeout: 10000});
+
+document.forms.fm.addEventListener("focus", function (evt) {
+  t.step(function () {
+    var testEle = document.getElementById("test1");
+    assert_equals(testEle.tabIndex, -1, "The tabIndex attribute of the first input element should be -1.");
+    assert_not_equals(evt.target, testEle, "The second input element must be focused.");
+    assert_equals(document.activeElement, document.getElementById("test2"), "The second input element must be activated.");
+  });
+  t.done();
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+  t.step(function () {
+    assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+  });
+}, true);
+
+t.step(function () {
+  // TAB = '\ue004'
+  test_driver.send_keys(document.body, "\ue004");
+});
+
+</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order-manual.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order-manual.html
deleted file mode 100644
index c837626..0000000
--- a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order-manual.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>HTML Test: focus - the sequential focus navigation order</title>
-<link rel="author" title="Intel" href="http://www.intel.com/">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
-<meta assert="flag" content="interact">
-<meta assert="assert" content="Check the sequential focus navigation order">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<h2>Steps:</h2>
-<ol>
-  <li>Press 'Tab' key at least 10 times in 20 seconds.(Long press the 'Tab' key will be better.)</li>
-</ol>
-<h2>Expect results:</h2>
-<p>PASS</p>
-<div id="log"></div>
-<form id="fm">
-  <button id="btn0">tabindex(omitted)</button>
-  <button id="btn1" tabindex="">tabindex(empty)</button>
-  <button id="btn2" tabindex="a">tabindex(a)</button>
-  <button id="btn3" tabindex="-1">tabindex(-1)</button>
-  <button id="btn4" tabindex="0">tabindex(0)</button>
-  <button id="btn5" tabindex="3">tabindex(3)</button>
-  <button id="btn6" tabindex="2">tabindex(2)</button>
-  <button id="btn7" tabindex="2">tabindex(2)</button>
-  <button id="btn8" tabindex="2">tabindex(2)</button>
-  <button id="btn9" tabindex="1">tabindex(1)</button>
-</form>
-<script>
-
-//This test can be automated once we have an uniform way to use webdriver.
-var i = 0,
-    expectation = ["btn9", "btn6", "btn7", "btn8", "btn5", "btn0", "btn1", "btn2", "btn4"],
-    results = [],
-    t = async_test("The element with a zero tabindex must be focused by press 'Tab' key");
-
-setup(function () {
-  document.body.focus();
-}, {timeout: 20000});
-
-document.forms.fm.addEventListener("focus", function (evt) {
-  results.push(evt.target.id);
-  if (i >= 9) {
-    t.step(function () {
-      assert_array_equals(results, expectation);
-    });
-  }
-}, true);
-
-document.addEventListener("keydown", function (evt) {
-  if (evt.keyCode === 9) {
-    i += 1;
-    if (i === 10) {
-      t.done();
-    }
-  }
-}, true);
-
-</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order.html
new file mode 100644
index 0000000..45429cc
--- /dev/null
+++ b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-order.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - the sequential focus navigation order</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check the sequential focus navigation order">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id="log"></div>
+<form id="fm">
+  <button id="btn0">tabindex(omitted)</button>
+  <button id="btn1" tabindex="">tabindex(empty)</button>
+  <button id="btn2" tabindex="a">tabindex(a)</button>
+  <button id="btn3" tabindex="-1">tabindex(-1)</button>
+  <button id="btn4" tabindex="0">tabindex(0)</button>
+  <button id="btn5" tabindex="3">tabindex(3)</button>
+  <button id="btn6" tabindex="2">tabindex(2)</button>
+  <button id="btn7" tabindex="2">tabindex(2)</button>
+  <button id="btn8" tabindex="2">tabindex(2)</button>
+  <button id="btn9" tabindex="1">tabindex(1)</button>
+</form>
+<script>
+
+var i = 0,
+    expectation = ["btn9", "btn6", "btn7", "btn8", "btn5", "btn0", "btn1", "btn2", "btn4"],
+    results = [],
+    t = async_test("Elements with different tabindex must be focused sequentially when pressing 'Tab' keys");
+
+setup(function () {
+  document.body.focus();
+}, {timeout: 20000});
+
+
+
+document.forms.fm.addEventListener("focus", function (evt) {
+  results.push(evt.target.id);
+  if (i >= 8) {
+    t.step(function () {
+      assert_array_equals(results, expectation);
+    });
+    t.done();
+  } else {
+    t.step(function () {
+      // TAB = '\ue004'
+      test_driver.send_keys(document.body, "\ue004");
+    });
+  }
+  i++;
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+  t.step(function () {
+    assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+  });
+}, true);
+
+t.step(function () {
+  // TAB = '\ue004'
+  test_driver.send_keys(document.body, "\ue004");
+});
+</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive-manual.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive-manual.html
deleted file mode 100644
index 012a3e7..0000000
--- a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive-manual.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>HTML Test: focus - positive tabindex</title>
-<link rel="author" title="Intel" href="http://www.intel.com/">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
-<meta assert="flag" content="interact">
-<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<h2>Steps:</h2>
-<ol>
-  <li>Press 'Tab' key in 10 seconds.</li>
-</ol>
-<h2>Expect results:</h2>
-<p>PASS</p>
-<div id="log"></div>
-<form id="fm">
-  <input id="test" tabindex="1">
-</form>
-<script>
-
-//This test can be automated once we have an uniform way to use webdriver.
-var t = async_test("The element with a positive tabindex must be focused by press 'Tab' key");
-
-setup({timeout: 10000});
-
-document.forms.fm.addEventListener("focus", function (evt) {
-  t.step(function () {
-    var testEle = document.getElementById("test");
-    assert_equals(testEle.tabIndex, 1, "The tabIndex attribute of the input element should be 1.");
-    assert_equals(evt.target, testEle, "The input element must be focused.");
-    assert_equals(document.activeElement, testEle, "The input element must be activated.");
-  });
-  t.done();
-}, true);
-
-document.addEventListener("keydown", function (evt) {
-  t.step(function () {
-    assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
-  });
-}, true);
-
-</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html
new file mode 100644
index 0000000..9a13184
--- /dev/null
+++ b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - positive tabindex</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id="log"></div>
+<form id="fm">
+  <input id="test" tabindex="1">
+</form>
+<script>
+
+var t = async_test("The element with a positive tabindex must be focused by press 'Tab' key");
+
+setup({timeout: 10000});
+
+document.forms.fm.addEventListener("focus", function (evt) {
+  t.step(function () {
+    var testEle = document.getElementById("test");
+    assert_equals(testEle.tabIndex, 1, "The tabIndex attribute of the input element should be 1.");
+    assert_equals(evt.target, testEle, "The input element must be focused.");
+    assert_equals(document.activeElement, testEle, "The input element must be activated.");
+  });
+  t.done();
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+  t.step(function () {
+    assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+  });
+}, true);
+
+t.step(function () {
+  // TAB = '\ue004'
+  test_driver.send_keys(document.body, "\ue004");
+});
+
+</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero-manual.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero-manual.html
deleted file mode 100644
index 108b818..0000000
--- a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero-manual.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!DOCTYPE html>
-<head>
-<meta charset="utf-8">
-<title>HTML Test: focus - zero tabindex</title>
-<link rel="author" title="Intel" href="http://www.intel.com/">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
-<meta assert="flag" content="interact">
-<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-</head>
-<h2>Steps:</h2>
-<ol>
-  <li>Press 'Tab' key in 10 seconds.</li>
-</ol>
-<h2>Expect results:</h2>
-<p>PASS</p>
-<div id="log"></div>
-<form id="fm">
-  <input id="test" tabindex="0">
-</form>
-<script>
-
-//This test can be automated once we have an uniform way to use webdriver.
-var t = async_test("The element with a zero tabindex must be focused by press 'Tab' key");
-
-setup({timeout: 10000});
-
-document.forms.fm.addEventListener("focus", function (evt) {
-  t.step(function () {
-    var testEle = document.getElementById("test");
-    assert_equals(testEle.tabIndex, 0, "The tabIndex attribute of the input element should be 0.");
-    assert_equals(evt.target, testEle, "The input element must be focused.");
-    assert_equals(document.activeElement, testEle, "The input element must be activated.");
-  });
-  t.done();
-}, true);
-
-document.addEventListener("keydown", function (evt) {
-  t.step(function () {
-    assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
-  });
-}, true);
-
-</script>
diff --git a/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html
new file mode 100644
index 0000000..c46acd0
--- /dev/null
+++ b/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<title>HTML Test: focus - zero tabindex</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-and-the-tabindex-attribute">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the tabindex attribute controls whether an element is supposed to be focusable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+</head>
+<div id="log"></div>
+<form id="fm">
+  <input id="test" tabindex="0">
+</form>
+<script>
+
+var t = async_test("The element with a zero tabindex must be focused by press 'Tab' key");
+
+setup({timeout: 10000});
+
+document.forms.fm.addEventListener("focus", function (evt) {
+  t.step(function () {
+    var testEle = document.getElementById("test");
+    assert_equals(testEle.tabIndex, 0, "The tabIndex attribute of the input element should be 0.");
+    assert_equals(evt.target, testEle, "The input element must be focused.");
+    assert_equals(document.activeElement, testEle, "The input element must be activated.");
+  });
+  t.done();
+}, true);
+
+document.addEventListener("keydown", function (evt) {
+  t.step(function () {
+    assert_equals(evt.keyCode, 9, "Please press 'Tab' key.");
+  });
+}, true);
+
+t.step(function () {
+  // TAB = '\ue004'
+  test_driver.send_keys(document.body, "\ue004");
+});
+
+</script>
diff --git a/html/form-elements/the-textarea-element/multiline-placeholder-cr.html b/html/form-elements/the-textarea-element/multiline-placeholder-cr.html
new file mode 100644
index 0000000..8879ca4
--- /dev/null
+++ b/html/form-elements/the-textarea-element/multiline-placeholder-cr.html
Binary files differ
diff --git a/html/form-elements/the-textarea-element/multiline-placeholder-crlf.html b/html/form-elements/the-textarea-element/multiline-placeholder-crlf.html
new file mode 100644
index 0000000..9632ef1
--- /dev/null
+++ b/html/form-elements/the-textarea-element/multiline-placeholder-crlf.html
@@ -0,0 +1,21 @@
+<!doctype html>

+<html class="reftest-wait">

+<meta charset="utf-8">

+<title>textarea multiline placeholder (CRLF)</title>

+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-placeholder">

+<meta name="assert" content="textarea element's placeholder preserves newlines (CRLF)">

+<link rel="match" href="/html/form-elements/the-textarea-element/multiline-placeholder-ref.html">

+<link rel="stylesheet" href="support/placeholder.css">

+<textarea rows="5" placeholder="this is

+a multiline

+

+placeholder"></textarea>

+<textarea rows="5" placeholder="this is&#xd;&#xa;a multiline&#xd;&#xa;&#xd;&#xa;placeholder"></textarea>

+<textarea rows="5" id="dynamic"></textarea>

+<script>

+  document.querySelector("#dynamic")

+          .setAttribute("placeholder", "this is\r\na multiline\r\n\r\nplaceholder");

+  document.documentElement.classList.remove("reftest-wait");

+</script>

+</html>

+

diff --git a/html/form-elements/the-textarea-element/multiline-placeholder-ref.html b/html/form-elements/the-textarea-element/multiline-placeholder-ref.html
new file mode 100644
index 0000000..0234ed6
--- /dev/null
+++ b/html/form-elements/the-textarea-element/multiline-placeholder-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="stylesheet" href="support/placeholder.css">
+<textarea rows="5" class="placeholder">this is
+a multiline
+
+placeholder</textarea>
+<textarea rows="5" class="placeholder">this is
+a multiline
+
+placeholder</textarea>
+<textarea rows="5" class="placeholder">this is
+a multiline
+
+placeholder</textarea>
diff --git a/html/form-elements/the-textarea-element/multiline-placeholder.html b/html/form-elements/the-textarea-element/multiline-placeholder.html
new file mode 100644
index 0000000..00bb969
--- /dev/null
+++ b/html/form-elements/the-textarea-element/multiline-placeholder.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>textarea multiline placeholder</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-placeholder">
+<meta name="assert" content="textarea element's placeholder preserves newlines">
+<link rel="match" href="/html/form-elements/the-textarea-element/multiline-placeholder-ref.html">
+<link rel="stylesheet" href="support/placeholder.css">
+<textarea rows="5" placeholder="this is
+a multiline
+
+placeholder"></textarea>
+<textarea rows="5" placeholder="this is&#xa;a multiline&#xa;&#xa;placeholder"></textarea>
+<textarea rows="5" id="dynamic"></textarea>
+<script>
+  document.querySelector("#dynamic")
+          .setAttribute("placeholder", "this is\na multiline\n\nplaceholder");
+  document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
+
+
diff --git a/html/form-elements/the-textarea-element/support/placeholder.css b/html/form-elements/the-textarea-element/support/placeholder.css
new file mode 100644
index 0000000..9aaed05
--- /dev/null
+++ b/html/form-elements/the-textarea-element/support/placeholder.css
@@ -0,0 +1,6 @@
+textarea.placeholder,
+textarea::placeholder {
+  /* revert browser styling of the placeholder */
+  color: GrayText; /* blink/webkit use colour */
+  opacity: 1.0; /* gecko uses opacity */
+}
diff --git a/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.html b/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.html
index a4bc173..e991e19 100644
--- a/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.html
+++ b/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.html
@@ -13,13 +13,13 @@
 "use strict";
 async_test(function(t) {
   var request = new XMLHttpRequest();
-  request.open("GET", "domstringlist.idl");
+  request.open("GET", "/interfaces/html.idl");
   request.send();
   request.onload = t.step_func(function() {
     var idlArray = new IdlArray();
     var idls = request.responseText;
 
-    idlArray.add_idls(idls);
+    idlArray.add_idls(idls, { only: ["DOMStringList"] });
 
     idlArray.add_objects({
       DOMStringList: ['location.ancestorOrigins'],
diff --git a/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.worker.js b/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.worker.js
index 0231b50..e957b9e 100644
--- a/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.worker.js
+++ b/html/infrastructure/common-dom-interfaces/collections/domstringlist-interface.worker.js
@@ -5,13 +5,13 @@
 
 async_test(function(t) {
   var request = new XMLHttpRequest();
-  request.open("GET", "domstringlist.idl");
+  request.open("GET", "/interfaces/html.idl");
   request.send();
   request.onload = t.step_func(function() {
     var idlArray = new IdlArray();
     var idls = request.responseText;
 
-    idlArray.add_idls(idls);
+    idlArray.add_idls(idls, { only: ["DOMStringList"] });
 
     idlArray.add_objects({
       DOMStringList: [],
diff --git a/html/infrastructure/common-dom-interfaces/collections/domstringlist.idl b/html/infrastructure/common-dom-interfaces/collections/domstringlist.idl
deleted file mode 100644
index 9b72a7c..0000000
--- a/html/infrastructure/common-dom-interfaces/collections/domstringlist.idl
+++ /dev/null
@@ -1,6 +0,0 @@
-[Exposed=(Window,Worker)]
-interface DOMStringList {
-  readonly attribute unsigned long length;
-  getter DOMString? item(unsigned long index);
-  boolean contains(DOMString string);
-};
diff --git a/html/infrastructure/common-dom-interfaces/collections/historical.html b/html/infrastructure/common-dom-interfaces/collections/historical.html
index a26dd4c..ef8345a 100644
--- a/html/infrastructure/common-dom-interfaces/collections/historical.html
+++ b/html/infrastructure/common-dom-interfaces/collections/historical.html
@@ -5,6 +5,8 @@
 <div id=log></div>
 <form id=form><input name=foo></form>
 <select id=select><option name=bar></select>
+<div id=dupe>
+<div id=dupe>
 <script>
 test(function() {
   var collection = document.getElementById('form').elements;
@@ -22,4 +24,10 @@
   });
 }, 'HTMLOptionsCollection legacycaller should not be supported');
 
+test(function() {
+  var collection = document.all('dupe', 0);
+  // If the second argument were used, it would return the first item of the
+  // collection instead of the whole collection.
+  assert_equals(collection.length, 2, 'length');
+}, 'HTMLAllCollection legacycaller with two arguments should not be supported');
 </script>
diff --git a/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html b/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html
index e17c0eb..d425996 100644
--- a/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html
+++ b/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html
@@ -22,6 +22,7 @@
 <div id="undefined"></div>
 <div id="null"></div>
 <div name="divwithname"></div>
+<div id="-0"></div>
 <script>
 var anchors = document.querySelectorAll("a");
 var divs = document.querySelectorAll("div");
@@ -33,19 +34,20 @@
 }, "document.all is an HTMLAllCollection");
 
 test(function() {
-  assert_equals(document.all.length, 23);
+  assert_equals(document.all.length, 24);
 }, "length attribute");
 
 // indexed property getter
 
 test(function() {
   assert_equals(document.all[0], document.documentElement);
-  assert_equals(document.all[22], scripts[2]);
+  assert_equals(document.all[-0], document.documentElement);
+  assert_equals(document.all[23], scripts[2]);
 }, "indexed property getter");
 
 test(function() {
   assert_equals(document.all[-1], undefined);
-  assert_equals(document.all[23], undefined);
+  assert_equals(document.all[24], undefined);
   assert_equals(document.all[42], undefined);
   assert_equals(document.all[43], undefined);
   assert_equals(document.all[4294967294], undefined);
@@ -84,8 +86,8 @@
 
 test(function() {
   assert_equals(document.all["0"], document.documentElement);
-  assert_equals(document.all["22"], document.scripts[2]);
-  assert_equals(document.all["23"], undefined);
+  assert_equals(document.all["23"], document.scripts[2]);
+  assert_equals(document.all["24"], undefined);
   assert_equals(document.all["42"], undefined);
   assert_equals(document.all["43"], undefined);
 }, "named property getter with \"array index property name\"");
@@ -97,6 +99,7 @@
   assert_equals(document.all["4294967294"], undefined);
   assert_equals(document.all["4294967295"], divs[1]);
   assert_equals(document.all["4294967296"], divs[2]);
+  assert_equals(document.all["-0"], divs[6]);
 }, "named property getter with invalid \"array index property name\"");
 
 test(function() {
@@ -130,8 +133,8 @@
 
 test(function() {
   assert_equals(document.all.namedItem("0"), null);
-  assert_equals(document.all.namedItem("22"), null);
   assert_equals(document.all.namedItem("23"), null);
+  assert_equals(document.all.namedItem("24"), null);
   assert_equals(document.all.namedItem("42"), spans[0]);
   assert_equals(document.all.namedItem("43"), null);
 }, "namedItem method with \"array index property name\"");
@@ -143,6 +146,7 @@
   assert_equals(document.all.namedItem("4294967294"), divs[0]);
   assert_equals(document.all.namedItem("4294967295"), divs[1]);
   assert_equals(document.all.namedItem("4294967296"), divs[2]);
+  assert_equals(document.all.namedItem("-0"), divs[6]);
 }, "namedItem method with invalid \"array index property name\"");
 
 test(function() {
@@ -183,16 +187,16 @@
 
 test(function() {
   assert_equals(document.all("0"), document.documentElement);
-  assert_equals(document.all("22"), document.scripts[2]);
-  assert_equals(document.all("23"), null);
+  assert_equals(document.all("23"), document.scripts[2]);
+  assert_equals(document.all("24"), null);
   assert_equals(document.all("42"), null);
   assert_equals(document.all("43"), null);
 }, "legacy caller with \"array index property name\"");
 
 test(function() {
   assert_equals(document.all(0), document.documentElement);
-  assert_equals(document.all(22), document.scripts[2]);
-  assert_equals(document.all(23), null);
+  assert_equals(document.all(23), document.scripts[2]);
+  assert_equals(document.all(24), null);
   assert_equals(document.all(42), null);
   assert_equals(document.all(43), null);
 }, "legacy caller with \"array index property name\" as number");
@@ -204,6 +208,7 @@
   assert_equals(document.all("4294967294"), null);
   assert_equals(document.all("4294967295"), divs[1]);
   assert_equals(document.all("4294967296"), divs[2]);
+  assert_equals(document.all("-0"), divs[6]);
 }, "legacy caller with invalid \"array index property name\"");
 
 test(function() {
@@ -262,16 +267,16 @@
 
 test(function() {
   assert_equals(document.all.item("0"), document.documentElement);
-  assert_equals(document.all.item("22"), document.scripts[2]);
-  assert_equals(document.all.item("23"), null);
+  assert_equals(document.all.item("23"), document.scripts[2]);
+  assert_equals(document.all.item("24"), null);
   assert_equals(document.all.item("42"), null);
   assert_equals(document.all.item("43"), null);
 }, "item method with \"array index property name\"");
 
 test(function() {
   assert_equals(document.all.item(0), document.documentElement);
-  assert_equals(document.all.item(22), document.scripts[2]);
-  assert_equals(document.all.item(23), null);
+  assert_equals(document.all.item(23), document.scripts[2]);
+  assert_equals(document.all.item(24), null);
   assert_equals(document.all.item(42), null);
   assert_equals(document.all.item(43), null);
 }, "item method with \"array index property name\" as number");
@@ -283,6 +288,7 @@
   assert_equals(document.all.item("4294967294"), null);
   assert_equals(document.all.item("4294967295"), divs[1]);
   assert_equals(document.all.item("4294967296"), divs[2]);
+  assert_equals(document.all.item("-0"), divs[6]);
 }, "item method with invalid \"array index property name\"");
 
 test(function() {
diff --git a/html/infrastructure/safe-passing-of-structured-data/structured_clone_bigint.html b/html/infrastructure/safe-passing-of-structured-data/structured_clone_bigint.html
new file mode 100644
index 0000000..e51837b
--- /dev/null
+++ b/html/infrastructure/safe-passing-of-structured-data/structured_clone_bigint.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+ <head>
+  <meta content="text/html; charset=utf-8" http-equiv="content-type" />
+  <title>2.7 Safe passing of structured data</title>
+  <link rel="help" href="https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data" />
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+
+<script type="text/javascript">
+    // Note, this test is designed to be in a similar style to
+    // html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
+    // It is in a separate file to avoid causing a syntax error on UAs which
+    // do not yet support BigInt, so the rest of the test can continue running.
+
+    var worker;
+    var testCollection;
+    setup(function()
+    {
+        //the worker is used for each test in sequence
+        //worker's callback will be set for each test
+        //worker's internal onmessage echoes the data back to this thread through postMessage
+        worker = new Worker("./echo.js");
+        testCollection = [
+            function() {
+                var t = async_test("Primitive BigInt is cloned");
+                t.id = 0;
+                worker.onmessage = t.step_func(function(e) {assert_equals(1n, e.data, "1n === event.data"); t.done(); });
+                t.step(function() { worker.postMessage(1n);});
+            },
+            function() {
+                var t = async_test("Instance of BigInt is cloned");
+                t.id = 1;
+                var obj;
+                t.step(function() {obj = Object(1n);});
+                worker.onmessage = t.step_func(function(e) {
+                    assert_equals(obj.constructor, e.data.constructor, "BigInt === event.data.constructor");
+                    assert_equals(obj.valueOf(), e.data.valueOf(), "(BigInt(1n)).valueOf() === event.data.valueOf()");
+                    t.done();
+                });
+                t.step(function() { worker.postMessage(obj);});
+            },
+        ];
+    }, {explicit_done:true});
+
+    //Callback for result_callback
+    //queues the next test in the array testCollection
+    //serves to make test execution sequential from the async worker callbacks
+    //alternatively, we would have to create a worker for each test
+    function testFinished(test) {
+        if(test.id < testCollection.length - 1) {
+            //queue the function so that stack remains shallow
+            queue(testCollection[test.id+1]);
+        } else {
+            //when the last test has run, explicitly end test suite
+            done();
+        }
+    }
+    function queue(func) {
+        step_timeout(func, 10);
+    }
+
+    add_result_callback(testFinished);
+    //start the first test manually
+    queue(testCollection[0]);
+
+
+
+
+ </script>
+</body>
+</html>
+
diff --git a/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js b/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js
new file mode 100644
index 0000000..bb1c083
--- /dev/null
+++ b/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js
@@ -0,0 +1,47 @@
+function assert_transfer_error(transferList) {
+  assert_throws("DataCloneError", () => self.postMessage({ get whatever() { throw new Error("You should not have gotten to this point") } }, "*", transferList));
+}
+
+test(() => {
+  [self, self.document, new Image()].forEach(val => {
+    assert_transfer_error([val]);
+  });
+}, "Cannot transfer all objects");
+
+function transfer_tests(name, create) {
+  promise_test(async () => {
+    const transferable = await create();
+    assert_transfer_error([transferable, transferable]);
+  }, `Cannot transfer the same ${name} twice`);
+
+  promise_test(async () => {
+    const transferable = await create();
+    self.postMessage(null, "*", [transferable]);
+    assert_throws("DataCloneError", () => self.postMessage(null, "*", [transferable]));
+  }, `Serialize should make the ${name} detached, so it cannot be transferred again`);
+
+  promise_test(async () => {
+    const transferable = await create(),
+          customError = new Error("hi");
+    self.postMessage(null, "*", [transferable]);
+    assert_throws(customError, () => self.postMessage({ get whatever() { throw customError } }, "*", [transferable]));
+  }, `Serialize should throw before a detached ${name} is found`);
+
+  promise_test(async () => {
+    const transferable = await create();
+    let seen = false;
+    const message = {
+      get a() {
+        self.postMessage(null, '*', [transferable]);
+        seen = true;
+      }
+    };
+    assert_throws("DataCloneError", () => self.postMessage(message, "*", [transferable]));
+    assert_true(seen);
+  }, `Cannot transfer ${name} detached while the message was serialized`);
+}
+
+transfer_tests("ArrayBuffer", () => new ArrayBuffer(1));
+transfer_tests("MessagePort", () => new MessageChannel().port1);
+transfer_tests("ImageBitmap", () => self.createImageBitmap(document.createElement("canvas")));
+transfer_tests("OffscreenCanvas", () => new OffscreenCanvas(1, 1));
diff --git a/html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html b/html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html
new file mode 100644
index 0000000..b1a4d5bd
--- /dev/null
+++ b/html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset={{GET[encoding]}}> <!-- ends up as <meta charset> by default which is windows-1252 -->
+<meta name=variant content="?encoding=x-cp1251">
+<meta name=variant content="?encoding=utf8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+function expected(encoding) {
+  return "?" + {
+    "UTF-8": "%C3%BF",
+    "windows-1251": "&%23255;",
+    "windows-1252": "%FF"
+  }[encoding];
+}
+
+[
+  [(win, input) => { win.location = input; }, "location [PutForwards]"],
+  [(win, input) => { win.location.assign(input); }, "location.assign()"],
+  [(win, input) => { win.location.replace(input); }, "location.replace()"],
+  [(win, input) => { win.location.href = input; }, "location.href"]
+].forEach(([callback, desc]) => {
+  async_test(t => {
+    const frame = document.body.appendChild(document.createElement("iframe")),
+          actualEncoding = document.characterSet
+    callback(frame.contentWindow, "/common/blank.html?\u00FF");
+    frame.onload = t.step_func_done(() => {
+      assert_equals(frame.contentWindow.location.search, expected(actualEncoding));
+    });
+  }, desc);
+});
+
+async_test(t => {
+  const frame = document.body.appendChild(document.createElement("iframe")),
+        actualEncoding = document.characterSet;
+  frame.src = "/common/blank.html";
+  frame.onload = t.step_func(() => {
+    frame.contentWindow.location.search = "\u00FF";
+    frame.onload = t.step_func_done(() => {
+      assert_equals(frame.contentWindow.location.search, expected(actualEncoding));
+    });
+  });
+}, "location.search");
+</script>
diff --git a/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js b/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js
index 769b41b..0d14fe70 100644
--- a/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js
+++ b/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js
@@ -1,3 +1,6 @@
+// NOTE: this file needs to be split up rather than expanded. See ../location.sub.html for some
+// extracted tests. Tracked by https://github.com/w3c/web-platform-tests/issues/4934.
+
 setup({explicit_done:true});
 onload = function() {
   var encoding = '{{GET[encoding]}}';
@@ -35,7 +38,7 @@
                       // page and the test timing out
                       test_obj.force_timeout();
                   }
-                  step_timeout(poll, 200);
+                  test_obj.step_timeout(poll, 200);
               } else {
                   assert_equals(xhr.response, expected);
                   test_obj.done();
@@ -43,7 +46,7 @@
           });
           xhr.send();
       })
-      step_timeout(poll, 200);
+      test_obj.step_timeout(poll, 200);
   }
 
   // background attribute, check with getComputedStyle
@@ -505,55 +508,6 @@
   }, 'window.open()',
   {help:'https://html.spec.whatwg.org/multipage/#dom-open'});
 
-  // location
-  function test_location(func, desc) {
-    async_test(function() {
-      var iframe = document.createElement('iframe');
-      document.body.appendChild(iframe);
-      this.add_cleanup(function() {
-        document.body.removeChild(iframe);
-      });
-      func(iframe.contentWindow, input_url_html);
-      iframe.onload = this.step_func(function() {
-        var got = iframe.contentDocument.body.textContent;
-        if (got != '') {
-          assert_equals(got, expected_current);
-          this.done();
-        }
-      });
-    }, desc,
-    {help:'https://html.spec.whatwg.org/multipage/#the-location-interface'});
-  }
-  [[function(win, input) { win.location = input; }, "location [PutForwards]"],
-   [function(win, input) { win.location.assign(input); }, "location.assign()"],
-   [function(win, input) { win.location.replace(input); }, "location.replace()"],
-   [function(win, input) { win.location.href = input; }, "location.href"]].forEach(function(arr) {
-    test_location(arr[0], arr[1]);
-  });
-
-  // location.search
-  async_test(function() {
-    var iframe = document.createElement('iframe');
-    iframe.src = input_url_html;
-    document.body.appendChild(iframe);
-    this.add_cleanup(function() {
-      document.body.removeChild(iframe);
-    });
-    var i = 0;
-    iframe.onload = this.step_func(function() {
-      i++;
-      if (i == 1) {
-        iframe.contentWindow.location.search = '?' + input_url_html.split('?')[1] + '&other=foobar';
-      } else {
-        var got = iframe.contentDocument.body.textContent;
-        assert_equals(got, expected_current);
-        this.done();
-      }
-    });
-  }, 'location.search',
-  {help:['https://html.spec.whatwg.org/multipage/#the-location-interface',
-         'http://url.spec.whatwg.org/#dom-url-search']});
-
   // a.search, area.search
   function test_hyperlink_search(tag) {
     test(function() {
diff --git a/html/input/the-placeholder-attribute/multiline-cr.html b/html/input/the-placeholder-attribute/multiline-cr.html
new file mode 100644
index 0000000..4184ab2
--- /dev/null
+++ b/html/input/the-placeholder-attribute/multiline-cr.html
Binary files differ
diff --git a/html/input/the-placeholder-attribute/multiline-crlf.html b/html/input/the-placeholder-attribute/multiline-crlf.html
new file mode 100644
index 0000000..50c91fb
--- /dev/null
+++ b/html/input/the-placeholder-attribute/multiline-crlf.html
@@ -0,0 +1,19 @@
+<!doctype html>

+<html class="reftest-wait">

+<meta charset="utf-8">

+<title>input multiline placeholder (CRLF)</title>

+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-placeholder-attribute">

+<meta name="assert" content="input element's placeholder strips newlines (CRLF)">

+<link rel="match" href="/html/input/the-placeholder-attribute/multiline-ref.html">

+<input placeholder="this is

+a multiline

+

+placeholder">

+<input placeholder="this is&#xd;&#xa;a multiline&#xd;&#xa;&#xd;&#xa;placeholder">

+<input id="dynamic">

+<script>

+  document.querySelector("#dynamic")

+          .setAttribute("placeholder", "this is\r\na multiline\r\n\r\nplaceholder");

+  document.documentElement.classList.remove("reftest-wait");

+</script>

+</html>

diff --git a/html/input/the-placeholder-attribute/multiline-ref.html b/html/input/the-placeholder-attribute/multiline-ref.html
new file mode 100644
index 0000000..2812f86
--- /dev/null
+++ b/html/input/the-placeholder-attribute/multiline-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset=utf-8>
+<input placeholder="this isa multilineplaceholder">
+<input placeholder="this isa multilineplaceholder">
+<input placeholder="this isa multilineplaceholder">
diff --git a/html/input/the-placeholder-attribute/multiline.html b/html/input/the-placeholder-attribute/multiline.html
new file mode 100644
index 0000000..2d7102b
--- /dev/null
+++ b/html/input/the-placeholder-attribute/multiline.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>input multiline placeholder</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-placeholder-attribute">
+<meta name="assert" content="input element's placeholder strips newlines">
+<link rel="match" href="/html/input/the-placeholder-attribute/multiline-ref.html">
+<input placeholder="this is
+a multiline
+
+placeholder">
+<input placeholder="this is&#xa;a multiline&#xa;&#xa;placeholder">
+<input id="dynamic">
+<script>
+  document.querySelector("#dynamic")
+          .setAttribute("placeholder", "this is\na multiline\n\nplaceholder");
+  document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/html/obsolete/requirements-for-implementations/other-elements-attributes-and-apis/document-color-01.html b/html/obsolete/requirements-for-implementations/other-elements-attributes-and-apis/document-color-01.html
index 38364cd..e6f0c2b 100644
--- a/html/obsolete/requirements-for-implementations/other-elements-attributes-and-apis/document-color-01.html
+++ b/html/obsolete/requirements-for-implementations/other-elements-attributes-and-apis/document-color-01.html
@@ -7,47 +7,89 @@
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
+function setColorAttributes(doc, color) {
+  doc.fgColor = color;
+  doc.bgColor = color;
+  doc.linkColor = color;
+  doc.vlinkColor = color;
+  doc.alinkColor = color;
+}
+
+function checkColorAttributes(doc, expected) {
+  assert_equals(document.fgColor, expected);
+  assert_equals(document.bgColor, expected);
+  assert_equals(document.linkColor, expected);
+  assert_equals(document.vlinkColor, expected);
+  assert_equals(document.alinkColor, expected);
+}
+
 test(function() {
-  document.fgColor = "green";
-  document.bgColor = "green";
-  document.linkColor = "green";
-  document.vlinkColor = "green";
-  document.alinkColor = "green";
+  setColorAttributes(document, 'green');
 
   var body = document.documentElement.removeChild(document.body);
-
+  this.add_cleanup(function() {
+    // Re-add body and reset color attributes.
+    document.body = body;
+    setColorAttributes(document, '');
+  });
   // When there is no body element, the color attributes return an
   // empty string upon getting.
-  assert_equals(document.fgColor, "");
-  assert_equals(document.bgColor, "");
-  assert_equals(document.linkColor, "");
-  assert_equals(document.vlinkColor, "");
-  assert_equals(document.alinkColor, "");
-
-  // Re-add body and reset color attributes.
-  document.body = body;
-  document.fgColor = "";
-  document.bgColor = "";
-  document.linkColor = "";
-  document.vlinkColor = "";
-  document.alinkColor = "";
+  checkColorAttributes(document, '');
 }, "getting document color attributes with no body");
 
 test(function() {
   var body = document.documentElement.removeChild(document.body);
+  this.add_cleanup(function() {
+    document.body = body;
+  });
 
   // When there is no body element, setting the color attributes has no effect.
-  document.fgColor = "red";
-  document.bgColor = "red";
-  document.linkColor = "red";
-  document.vlinkColor = "red";
-  document.alinkColor = "red";
-  assert_equals(document.fgColor, "");
-  assert_equals(document.bgColor, "");
-  assert_equals(document.linkColor, "");
-  assert_equals(document.vlinkColor, "");
-  assert_equals(document.alinkColor, "");
-
-  document.body = body;
+  setColorAttributes(document, 'red');
+  checkColorAttributes(document, '');
 }, "setting document color attributes with no body");
+
+function testBogusRootElement(doc) {
+  doc.replaceChild(doc.createElement('test'), doc.documentElement);
+  var new_body = doc.createElement('body');
+  doc.documentElement.appendChild(new_body);
+
+  setColorAttributes(doc, 'red');
+
+  assert_equals(new_body.attributes.length, 0, 'new_body.attributes.length');
+  checkColorAttributes(doc, '');
+}
+
+function createIframeDoc(markup) {
+  var iframe = document.createElement('iframe');
+  document.body.appendChild(iframe);
+  var doc = iframe.contentDocument;
+  doc.open();
+  doc.write(markup);
+  doc.close();
+  return doc;
+}
+
+test(function() {
+  // Use standards mode for doc
+  var doc = createIframeDoc('<!doctype html>');
+  testBogusRootElement(doc);
+}, "document color attributes when the root element is a test element (iframe)");
+
+test(function() {
+  var doc = document.implementation.createHTMLDocument();
+  testBogusRootElement(doc);
+}, "document color attributes when the root element is a test element (createHTMLDocument)");
+
+test(function() {
+  var doc = createIframeDoc('<!doctype html><frameset text=red link=red vlink=red alink=red bgcolor=red>');
+  assert_equals(doc.body.attributes.length, 5, 'attributes.length on the frameset');
+  checkColorAttributes(doc, '');
+}, "getting document color attributes when document.body is a frameset");
+
+test(function() {
+  var doc = createIframeDoc('<!doctype html><frameset>');
+  setColorAttributes(doc, 'red');
+  assert_equals(doc.body.attributes.length, 0, 'attributes.length on the frameset');
+  checkColorAttributes(doc, '');
+}, "setting document color attributes when document.body is a frameset");
 </script>
diff --git a/html/rendering/replaced-elements/embedded-content/video-controls-vertical-writing-mode-ref.html b/html/rendering/replaced-elements/embedded-content/video-controls-vertical-writing-mode-ref.html
new file mode 100644
index 0000000..9a2d1d0
--- /dev/null
+++ b/html/rendering/replaced-elements/embedded-content/video-controls-vertical-writing-mode-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<div>
+  <video controls />
+</div>
diff --git a/html/rendering/replaced-elements/embedded-content/video-controls-vertical-writing-mode.html b/html/rendering/replaced-elements/embedded-content/video-controls-vertical-writing-mode.html
new file mode 100644
index 0000000..d124396
--- /dev/null
+++ b/html/rendering/replaced-elements/embedded-content/video-controls-vertical-writing-mode.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Video controls rendering in vertical-lr</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#embedded-content-rendering-rules" />
+<link rel="match" href="video-controls-vertical-writing-mode-ref.html" />
+<div style="writing-mode:vertical-lr">
+  <video controls />
+</div>
diff --git a/html/semantics/document-metadata/interactions-of-styling-and-scripting/conditionally-block-rendering-on-link-media-attr.html b/html/semantics/document-metadata/interactions-of-styling-and-scripting/conditionally-block-rendering-on-link-media-attr.html
new file mode 100644
index 0000000..3304982
--- /dev/null
+++ b/html/semantics/document-metadata/interactions-of-styling-and-scripting/conditionally-block-rendering-on-link-media-attr.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<link rel=stylesheet href=stylesheet.py>
+<link rel=stylesheet media="screen and (max-width:10px)" href=stylesheet.py?stylesNotMatchingEnvironment&delay=2>
+<h1>Dominic Farolino</h1>
+<script>
+  function styleExists(styleText) {
+    for (let styleRule of document.styleSheets) {
+      let currentStyleText = styleRule.cssRules["0"].cssText;
+      if (currentStyleText == styleText) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  test(() => {
+    const h1 = document.querySelector('h1');
+    const computedColor = getComputedStyle(h1).color;
+    const expectedColor = "rgb(128, 0, 128)";
+
+    assert_equals(computedColor, expectedColor);
+    assert_true(styleExists("h1 { color: purple; }")); // first style sheet
+    assert_false(styleExists("h1 { color: brown; }")); // second style sheet (should not be loaded yet)
+  }, "Only the style sheet loaded via a link element whose media attribute matches the environment should block following script execution");
+
+  const secondStylesheetTest = async_test("Both style sheets loaded via the link elements should be registered as style sheets for the document after 2 seconds");
+  secondStylesheetTest.step_timeout(() => {
+      assert_true(styleExists("h1 { color: purple; }")); // first style sheet
+      assert_true(styleExists("h1 { color: brown; }")); // second style sheet (loaded now!)
+      secondStylesheetTest.done();
+  }, 3000);
+</script>
diff --git a/html/semantics/document-metadata/interactions-of-styling-and-scripting/stylesheet.py b/html/semantics/document-metadata/interactions-of-styling-and-scripting/stylesheet.py
new file mode 100644
index 0000000..9befce9
--- /dev/null
+++ b/html/semantics/document-metadata/interactions-of-styling-and-scripting/stylesheet.py
@@ -0,0 +1,10 @@
+from time import sleep
+def main(request, response):
+  if "delay" in request.GET:
+    delay = int(request.GET["delay"])
+    sleep(delay)
+
+  if "stylesNotMatchingEnvironment" in request.GET:
+    return 'h1 {color: brown;}'
+  else:
+    return 'h1 {color: purple;}'
diff --git a/html/semantics/document-metadata/the-link-element/link-load-error-events.html b/html/semantics/document-metadata/the-link-element/link-load-error-events.html
new file mode 100644
index 0000000..e4f617d
--- /dev/null
+++ b/html/semantics/document-metadata/the-link-element/link-load-error-events.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="resources/link-load-error-events.sub.js"></script>
diff --git a/html/semantics/document-metadata/the-link-element/link-load-error-events.https.html b/html/semantics/document-metadata/the-link-element/link-load-error-events.https.html
new file mode 100644
index 0000000..e4f617d
--- /dev/null
+++ b/html/semantics/document-metadata/the-link-element/link-load-error-events.https.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="resources/link-load-error-events.sub.js"></script>
diff --git a/html/semantics/document-metadata/the-link-element/link-load-event.html b/html/semantics/document-metadata/the-link-element/link-load-event.html
index 6f6172a..5412913 100644
--- a/html/semantics/document-metadata/the-link-element/link-load-event.html
+++ b/html/semantics/document-metadata/the-link-element/link-load-event.html
@@ -3,16 +3,15 @@
 <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<link href="style.css?pipe=trickle(d3)" rel="stylesheet" id="style_test"></link>
 <script>
 var saw_link_onload = false;
 var t = async_test("Check if the stylesheet's load event blocks the document load event");
-document.getElementById('style_test').onload = t.step_func(function() {
-  saw_link_onload = true;
-});
 window.addEventListener('load', t.step_func_done(function() {
   assert_true(saw_link_onload);
 }));
 </script>
+<link href="style.css?pipe=trickle(d3)" rel="stylesheet" id="style_test"
+      onload="t.step(function() { saw_link_onload = true; });"
+      onerror="t.step(function() { assert_unreached('Sheet should load OK'); });"></link>
 </head>
 </html>
diff --git a/html/semantics/document-metadata/the-link-element/link-rel-attribute.html b/html/semantics/document-metadata/the-link-element/link-rel-attribute.html
new file mode 100644
index 0000000..14d0622
--- /dev/null
+++ b/html/semantics/document-metadata/the-link-element/link-rel-attribute.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src = "/resources/testharness.js"></script>
+<script src = "/resources/testharnessreport.js"></script>
+
+<link id="light-link" rel="stylesheet" href="resources/link-rel-attribute.css">
+<div id="light-div" class="green">I"m green when light DOM link is on</div>
+
+<div id="host">
+  I"m green when Shadow DOM link is on
+  <template id="shadow-dom">
+    <link id="shadow-link" rel="stylesheet" href="resources/link-rel-attribute.css">
+    <div id="shadow-div" class="green">
+      <slot></slot>
+    </div>
+  </template>
+</div>
+
+<script>
+
+function testLinkRelModification(testDiv, testLink) {
+  assert_equals(getComputedStyle(testDiv).color, "rgb(0, 128, 0)");
+  testLink.setAttribute("rel", "no-stylesheet");
+  assert_equals(getComputedStyle(testDiv).color, "rgb(0, 0, 0)");
+  testLink.setAttribute("rel", "stylesheet");
+  assert_equals(getComputedStyle(testDiv).color, "rgb(0, 128, 0)");
+  testLink.removeAttribute("rel");
+  assert_equals(getComputedStyle(testDiv).color, "rgb(0, 0, 0)");
+}
+
+test (() => {
+  testLinkRelModification(document.querySelector("#light-div"),
+                          document.querySelector("#light-link"));
+}, "Removing stylesheet from link rel attribute should remove the stylesheet for light DOM");
+
+test (() => {
+  var host = document.querySelector("#host");
+  var shadow = host.attachShadow({ mode: "open" });
+  var tmpl = document.querySelector("template#shadow-dom");
+  var clone = document.importNode(tmpl.content, true);
+  shadow.appendChild(clone);
+  testLinkRelModification(shadow.querySelector("#shadow-div"),
+                          shadow.querySelector("#shadow-link"));
+}, "Removing stylesheet from link rel attribute should remove the stylesheet for shadow DOM");
+</script>
diff --git a/html/semantics/document-metadata/the-link-element/resources/link-load-error-events.sub.js b/html/semantics/document-metadata/the-link-element/resources/link-load-error-events.sub.js
new file mode 100644
index 0000000..33c8709
--- /dev/null
+++ b/html/semantics/document-metadata/the-link-element/resources/link-load-error-events.sub.js
@@ -0,0 +1,192 @@
+/**
+ * This is the guts of the load/error event tests for <link rel="stylesheet">.
+ *
+ * We have a list of tests each of which is an object containing: href value,
+ * expected load success boolean, test description.  Href values are set up in
+ * such a way that we guarantee that all stylesheet URLs are unique.  This
+ * avoids issues around caching of sheets based on URL.
+ */
+
+// Our URLs are random, so we don't use them in error messages by
+// default, but enable doing it if someone wants to debug things.
+const DEBUG_URLS = false;
+
+var isHttps = location.protocol == "https:";
+
+var tests = [
+  // Basic tests
+  {
+    href: existingSheet(),
+    success: true,
+    description: "Basic load of stylesheet",
+  },
+  {
+    href: nonexistentSheet(),
+    success: false,
+    description: "Attempted load of nonexistent stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("${existingSheet()}")`,
+    success: true,
+    description: "Import of stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("${nonexistentSheet()}")`,
+    success: false,
+    description: "Import of nonexistent stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("data:text/css,@import url('${existingSheet()}')")`,
+    success: true,
+    description: "Import of import of stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("data:text/css,@import url('${nonexistentSheet()}')")`,
+    success: false,
+    description: "Import of import of nonexistent stylesheet",
+  },
+
+  // Non-CSS-response tests.
+  {
+    href: makeUnique(""),
+    success: false,
+    description: "Load of non-CSS stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("${makeUnique("")}")`,
+    success: false,
+    description: "Import of non-CSS stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("data:text/css,@import url('${makeUnique("")}')")`,
+    success: false,
+    description: "Import of import of non-CSS stylesheet",
+  },
+
+  // http:// tests, to test what happens with mixed content blocking.
+  {
+    href: httpSheet(),
+    success: !isHttps,
+    description: "Load of http:// stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("${httpSheet()}")`,
+    success: !isHttps,
+    description: "Import of http:// stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("data:text/css,@import url('${httpSheet()}')")`,
+    success: !isHttps,
+    description: "Import of import of http:// stylesheet",
+  },
+
+  // https:// tests just as a control
+  {
+    href: httpsSheet(),
+    success: true,
+    description: "Load of https:// stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("${httpsSheet()}")`,
+    success: true,
+    description: "Import of https:// stylesheet",
+  },
+  {
+    href: `data:text/css,@import url("data:text/css,@import url('${httpsSheet()}')")`,
+    success: true,
+    description: "Import of import of https:// stylesheet",
+  },
+
+  // Tests with multiple imports some of which are slow and some are fast.
+  {
+    href: `data:text/css,@import url("${slowResponse(existingSheet())}"); @import url("${nonexistentSheet()}");`,
+    success: false,
+    description: "Slow successful import, fast failing import",
+  },
+  {
+    href: `data:text/css,@import url("${existingSheet()}"); @import url("${slowResponse(nonexistentSheet())}");`,
+    success: false,
+    description: "Fast successful import, slow failing import",
+  }
+];
+
+// Note: Here we really do need to use "let" at least for the href,
+// because we lazily evaluate it in the unreached cases.
+for (var test of tests) {
+  let {href, success, description} = test;
+  var t = async_test(description);
+  var link = document.createElement("link");
+  link.rel = "stylesheet";
+  hrefString = DEBUG_URLS ? `: ${href}` : "";
+  if (success) {
+    link.onload = t.step_func_done(() => {});
+    link.onerror = t.step_func_done(() => assert_unreached(`error fired when load expected${hrefString}`) );
+  } else {
+    link.onerror = t.step_func_done(() => {});
+    link.onload = t.step_func_done(() => assert_unreached(`load fired when error expected${hrefString}`) );
+  }
+  link.href = href;
+  document.head.appendChild(link);
+}
+
+/* Utility function */
+function makeUnique(url) {
+  // Make sure we copy here, even if the thing coming in is a URL, so we don't
+  // mutate our caller's data.
+  url = new URL(url, location.href);
+  // We want to generate a unique URI to avoid the various caches browsers have
+  // for stylesheets.  We don't want to just use a counter, because that would
+  // not be robust to the test being reloaded or othewise run multiple times
+  // without a browser restart.  We don't want to use timstamps, because those
+  // are not likely to be unique across calls to this function, especially given
+  // the degraded timer resolution browsers have due to Spectre.
+  //
+  // So just fall back on Math.random() and assume it can't duplicate values.
+  url.searchParams.append("r", Math.random());
+  return url;
+}
+
+function existingSheet() {
+  return makeUnique("resources/good.css");
+}
+
+/**
+ * Function the add values to the "pipe" search param.  See
+ * http://wptserve.readthedocs.io/en/latest/pipes.html for why one would do
+ * this.  Because this param uses a weird '|'-separated syntax instead of just
+ * using multiple params with the same name, we need some manual code to munge
+ * the value properly.
+ */
+function addPipe(url, pipeVal) {
+  url = new URL(url, location.href);
+  var params = url.searchParams;
+  var oldVal = params.get("pipe");
+  if (oldVal) {
+    params.set("pipe", oldVal + "|" + pipeVal);
+  } else {
+    params.set("pipe", pipeVal);
+  }
+  return url;
+}
+
+function nonexistentSheet() {
+  return addPipe(existingSheet(), "status(404)");
+}
+
+function httpSheet() {
+  var url = existingSheet();
+  url.protocol = "http";
+  url.port = {{ports[http][0]}};
+  return url;
+}
+
+function httpsSheet() {
+  var url = existingSheet();
+  url.protocol = "https";
+  url.port = {{ports[https][0]}};
+  return url;
+}
+
+function slowResponse(url) {
+  return addPipe(url, "trickle(d1)");
+}
diff --git a/html/semantics/document-metadata/the-link-element/resources/link-rel-attribute.css b/html/semantics/document-metadata/the-link-element/resources/link-rel-attribute.css
new file mode 100644
index 0000000..fa95e11
--- /dev/null
+++ b/html/semantics/document-metadata/the-link-element/resources/link-rel-attribute.css
@@ -0,0 +1,3 @@
+.green {
+  color: green;
+}
diff --git a/html/semantics/document-metadata/the-style-element/style_load_async.html b/html/semantics/document-metadata/the-style-element/style_load_async.html
new file mode 100644
index 0000000..ef8ac89
--- /dev/null
+++ b/html/semantics/document-metadata/the-style-element/style_load_async.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<title>Style load event should be async</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test("style load should be async");
+    var sync = true;
+    function check() {
+        assert_false(sync);
+        t.done();
+    }
+</script>
+<style onload="t.step(check)">
+</style>
+<script>
+  sync = false
+</script>
+
+<body>
+  <div id="log"></div>
+  <div id="test"></div>
+</body>
+</html>
diff --git a/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html b/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html
new file mode 100644
index 0000000..f687edf
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#text-track-model">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<div id="log"></div>
+<script>
+// Media elements have a "list of pending text tracks" which should be populated
+// with text tracks with readyState "loading". When the text track src is
+// invalid or points to a non-existent resource, it shouldn't be possible to
+// block the media element's readyState indefinitely.
+function t(trackSrc) {
+  const track = document.createElement('track');
+  track.src = trackSrc;
+  track.default = true;
+  async_test(t => {
+    const video = document.createElement('video');
+    video.autoplay = true;
+    video.controls = true; // for visual inspection, not part of test
+    video.src = getVideoURI('/media/movie_5');
+    video.appendChild(track);
+    document.body.appendChild(video);
+    // The playing event isn't used because it's fired in Safari even when the
+    // playback doesn't actually start.
+    video.ontimeupdate = t.step_func(() => {
+      if (video.currentTime > 0)
+        t.done();
+    });
+  }, `<video autoplay> with ${track.outerHTML} child`);
+}
+t("invalid://url");
+t("404");
+t("");
+</script>
diff --git a/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/crossOrigin.html b/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/crossOrigin.html
new file mode 100644
index 0000000..e29f2b0
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement/crossOrigin.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<title>HTMLMediaElement.crossOrigin</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function(){
+    var video = document.createElement('video');
+    assert_true('crossOrigin' in video);
+});
+test(function(){
+    var video = document.createElement('video');
+    assert_equals(video.crossOrigin, null);
+}, document.title+', content attribute missing');
+test(function(){
+    var video = document.createElement('video');
+    video.setAttribute('crossorigin', 'foo');
+    assert_equals(video.crossOrigin, 'anonymous');
+}, document.title+', content attribute invalid value');
+test(function(){
+    var video = document.createElement('video');
+    video.setAttribute('crossorigin', '');
+    assert_equals(video.crossOrigin, 'anonymous');
+}, document.title+', content attribute empty string');
+test(function(){
+    var video = document.createElement('video');
+    video.setAttribute('crossorigin', 'ANONYMOUS');
+    assert_equals(video.crossOrigin, 'anonymous');
+}, document.title+', content attribute uppercase ANONYMOUS');
+test(function(){
+    var video = document.createElement('video');
+    video.setAttribute('crossorigin', 'use-credentials');
+    assert_equals(video.crossOrigin, 'use-credentials');
+}, document.title+', content attribute use-credentials');
+test(function(){
+    var video = document.createElement('video');
+    video.crossOrigin = '';
+    assert_equals(video.getAttribute('crossorigin'), '');
+}, document.title+', setting to empty string');
+test(function(){
+    var video = document.createElement('video');
+    video.crossOrigin = null;
+    assert_false(video.hasAttribute('crossorigin'));
+}, document.title+', setting to null');
+test(function(){
+    var video = document.createElement('video');
+    video.crossOrigin = 'foo';
+    assert_equals(video.getAttribute('crossorigin'), 'foo');
+}, document.title+', setting to invalid value');
+test(function(){
+    var video = document.createElement('video');
+    video.crossOrigin = 'ANONYMOUS';
+    assert_equals(video.getAttribute('crossorigin'), 'ANONYMOUS');
+}, document.title+', setting to uppercase ANONYMOUS');
+test(function(){
+    var video = document.createElement('video');
+    video.crossOrigin = 'use-credentials';
+    assert_equals(video.getAttribute('crossorigin'), 'use-credentials');
+}, document.title+', setting to use-credentials');
+</script>
diff --git a/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html b/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html
index e18f219..cde21e6 100644
--- a/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html
+++ b/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement/readyState.html
@@ -8,4 +8,11 @@
     var track = document.createElement('track');
     assert_equals(track.readyState, 0);
 }, document.title + ' default value');
+
+test(function(){
+    assert_equals(HTMLTrackElement.NONE, 0);
+    assert_equals(HTMLTrackElement.LOADING, 1);
+    assert_equals(HTMLTrackElement.LOADED, 2);
+    assert_equals(HTMLTrackElement.ERROR, 3);
+}, document.title + ' values');
 </script>
diff --git a/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html b/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html
new file mode 100644
index 0000000..c066f60
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/constructor.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>TextTrackCue constructor</title>
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+    </head>
+    <body>
+        <script>
+            test(function()
+            {
+                assert_not_equals(TextTrackCue, VTTCue);
+            }, "TextTrackCue and VTTCue are separate interfaces");
+            test(function()
+            {
+                assert_throws(new TypeError(), function()
+                {
+                    new TextTrackCue(0, 0, "");
+                });
+            }, "TextTrackCue constructor should not be supported");
+        </script>
+    </body>
+</html>
diff --git a/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html b/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html
index 4f94cdd..56edf25 100644
--- a/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html
+++ b/html/semantics/embedded-content/media-elements/mime-types/canPlayType.html
@@ -2,8 +2,13 @@
 <title>canPlayType</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<audio id="audio"></audio>
+<video id="video"></video>
 <div id="log"></div>
 <script>
+let VIDEO_ELEM = document.getElementById('video');
+let AUDIO_ELEM = document.getElementById('audio');
+
 function t(type, expected) {
   assert_equals(canPlayType(type), expected, type);
 }
@@ -22,12 +27,13 @@
 }, 'utility code');
 
 function canPlayType(type) {
-  var canPlay = document.createElement('audio').canPlayType(type);
-  assert_equals(canPlay, document.createElement('video').canPlayType(type),
+  let audioCanPlay = AUDIO_ELEM.canPlayType(type);
+  let videoCanPlay = VIDEO_ELEM.canPlayType(type);
+  assert_equals(audioCanPlay, videoCanPlay,
                 'audio.canPlayType() and video.canPlayType() agree');
-  assert_in_array(canPlay, ['', 'maybe', 'probably'],
+  assert_in_array(audioCanPlay, ['', 'maybe', 'probably'],
                   'return value is one of "", "maybe" and "probably"');
-  return canPlay;
+  return audioCanPlay;
 }
 
 test(function() {
@@ -37,20 +43,22 @@
   t('application/octet-stream; codecs="mp4a.40.2"', '');
   t('application/octet-stream; codecs="theora, vorbis"', '');
   t('application/octet-stream; codecs="avc1.42E01E, mp4a.40.2"', '');
-}, 'application/octet-stream');
+}, 'application/octet-stream not supported');
 
 test(function() {
+  t('application/marks-fantasmagorical-format', '');
   t('video/x-new-fictional-format', '');
   t('video/x-new-fictional-format;codecs="kittens,bunnies"', '');
-}, 'video/x-new-fictional-format');
+}, 'fictional formats and codecs not supported');
 
 function type_codecs_test(type, audioCodecs, videoCodecs) {
   var typeSupported = false;
   var codecSupported = false;
 
+  // Test 'type' without codecs.
+  // Spec: Generally, a user agent should never return "probably" for a type
+  // that allows the codecs parameter if that parameter is not present.
   test(function() {
-    // Spec: Generally, a user agent should never return "probably" for a type
-    // that allows the codecs parameter if that parameter is not present.
     t(type, 'maybe');
     t(type + ';', 'maybe');
     t(type + ';codecs', 'maybe');
@@ -66,9 +74,11 @@
     }, typeWithCodec + ' (optional)');
   }
 
+  // Test each audio and video codec separately.
   audioCodecs.forEach(test_codec);
   videoCodecs.forEach(test_codec);
 
+  // Test different pairings and orderings of audio+video codecs.
   if (audioCodecs.length > 0 && videoCodecs.length > 0) {
     test(function() {
       audioCodecs.forEach(function(ac) {
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning-bad.vtt
new file mode 100644
index 0000000..ff4c3fb
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning-bad.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Either one or both of positioning and alignment values are invalid.
+
+1
+00:00:00.000 --> 00:00:30.500 position:10% align: start
+Bear is Coming!!!!!
+Positioning on the left bottom, middle aligned,
+because the alignment is mistyped.
+
+2
+00:00:31.000 --> 00:00:45.500 position:200% align:middle
+I said Bear is coming!!!!
+Positioning on the bottom middle, middle aligned,
+because the positioning is off.
+
+3
+00:01:01.000 --> 00:02:00.500 position:-80% align:ends
+I said Bear is coming now!!!!
+Positioning on the bottom middle, middle aligned,
+because both the alignment and positioning don't apply.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning.vtt
new file mode 100644
index 0000000..a6e6af2
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-positioning.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Cues should position at different horizontal positions with different alignments.
+
+1
+00:00:00.000 --> 00:00:30.500 position:10% align:start
+Bear is Coming!!!!!
+Positioning on the left bottom, start aligned, and
+first character rendering position is at 10% of width.
+
+2
+00:00:31.000 --> 00:00:45.500 position:20% align:middle
+I said Bear is coming!!!!
+Positioning on the bottom left, middle aligned, and
+middle character rendering position of each line is at 20% of width.
+
+3
+00:01:01.000 --> 00:02:00.500 align:end position:80%
+I said Bear is coming now!!!!
+Positioning on the bottom right, end aligned, and
+last character rendering position of each line is at 80% of width.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position-bad.vtt
new file mode 100644
index 0000000..b196f13
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position-bad.vtt
@@ -0,0 +1,21 @@
+WEBVTT
+One or more of line/text positioning and alignment values are invalid (settings are ignored).
+
+1
+00:00:00.000 --> 00:00:30.500  position: 0% align: start line: 0%
+Bear is Coming!!!!!
+None of the cue settings will be applied, just the default.
+
+2
+00:00:31.000 --> 00:00:01.500 position:0% align:end line:-30%
+I said Bear is coming!!!!
+The line position setting is ignored.
+No text is visible though because it's off-screen at position
+0 and the last character is at position 0%.
+
+3
+00:01:01.000 --> 00:01:30.000 line:-3 align:middler position:60%
+I said Bear is coming now!!!!
+Positioning on line 3 from the viewport bottom, middle aligned,
+with middle character of cue at 60% width.
+The alignment is ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position.vtt
new file mode 100644
index 0000000..dd3a6de
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/align-text-line-position.vtt
@@ -0,0 +1,28 @@
+WEBVTT
+Cues with valid alignment, line and text position settings.
+
+1
+00:00:00.000 --> 00:00:15.000  position:10% align:start line:0%
+Bear is Coming!!!!!
+Positioning on the top of the viewport at 10% horizontally,
+start aligned.
+
+00:00:15.500 --> 00:00:30.500 line:0 align:start
+Bear is Coming!!!!!
+This is line 0, middle aligned, first character at 50% width.
+
+2
+00:00:31.000 --> 00:00:45.500 position:80% line:80%
+I said Bear is coming!!!!
+Middle aligned, middle of cue's character is at 80% width and 80% height.
+
+00:00:46.000 --> 00:01:00.500 line:5 align:end position:30%
+I said Bear is coming!!!!
+This is line 6 from the top of the video viewport,
+end aligned with last character at 30% of viewport width.
+
+3
+00:01:01.000 --> 00:01:30.000 line:-3 align:middle position:60%
+I said Bear is coming now!!!!
+Positioning on line 3 from the viewport bottom, middle aligned,
+with middle character of cue at 60% width.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-bad.vtt
new file mode 100644
index 0000000..5beb376
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-bad.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue alignment may only be start, middle, or end.  These are all misspelled and so will default to middle.
+
+1
+00:00:00.000 --> 00:00:30.500 align:starta
+Bear is Coming!!!!!
+Erroneous alignment value -> middle.
+
+2
+00:00:31.000 --> 00:01:00.500 align:-start
+I said Bear is coming!!!!
+Erroneous alignment value --> middle.
+
+3
+00:01:01.000 --> 00:02:00.500 align: end
+I said Bear is coming now!!!!
+Erroneous alignment value with surplus whitespace --> middle.
+
+4
+00:02:01.000 --> 100:20:00.500 align:piugjk
+I said Bear is coming now!!!!
+Erroneous alignment value -> middle.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-ltr.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-ltr.vtt
new file mode 100644
index 0000000..673b29a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment-ltr.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue alignment may be start, middle, or end (default is middle).
+
+1
+00:00:00.000 --> 00:00:30.500 align:start
+الدب قادم!!!!!
+بدء محاذاته.
+
+2
+00:00:31.000 --> 00:01:00.500 align:middle
+قلت الدب قادم!!
+محاذاة الوسط.
+
+3
+00:01:01.000 --> 00:02:00.500 align:end
+قلت الدب قادم الآن!!
+محاذاة الغاية.
+
+4
+00:02:01.000 --> 100:20:00.500
+قلت الدب قادم الآن!!
+الافتراضية هي محاذاة الوسط.
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment.vtt
new file mode 100644
index 0000000..ad7792f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/alignment.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue alignment may be start, middle, or end (default is middle).
+
+1
+00:00:00.000 --> 00:00:30.500 align:start
+Bear is Coming!!!!!
+Start align.
+
+2
+00:00:31.000 --> 00:01:00.500 align:middle
+I said Bear is coming!!!!
+Middle align.
+
+3
+00:01:01.000 --> 00:02:00.500 align:end
+I said Bear is coming now!!!!
+End align.
+
+4
+00:02:01.000 --> 100:20:00.500
+I said Bear is coming now!!!!
+Default is middle alignment.
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/bom.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/bom.vtt
new file mode 100644
index 0000000..0c8de32
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/bom.vtt
@@ -0,0 +1,10 @@
+WEBVTT FILE
+A BOM character at the start of a file should be ignored.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-fast.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-fast.vtt
new file mode 100644
index 0000000..cd138fd
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-fast.vtt
@@ -0,0 +1,13 @@
+WEBVTT
+
+1
+00:00:00.000 --> 00:00:00.300
+Lorem
+
+2
+00:00:00.300 --> 00:00:00.700
+ipsum
+
+3
+00:00:01.200 --> 00:00:01.500
+dolor
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-gaps.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-gaps.vtt
new file mode 100644
index 0000000..44c7466
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-gaps.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+
+1
+00:00:01.000 --> 00:00:02.000
+Lorem ipsum dolor sit amet,
+
+2
+00:00:03.000 --> 00:00:04.000
+consectetuer adipiscing elit,
+
+3
+00:00:05.000 --> 00:00:06.000
+sed diam nonummy nibh euismod tincidunt
+
+4
+00:00:07.000 --> 00:00:08.000
+ut laoreet dolore magna aliquam erat volutpat.
+
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-html.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-html.vtt
new file mode 100644
index 0000000..0730f8b
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions-html.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+
+1
+00:00:00.000 --> 00:00:01.000
+Lorem <b>ipsum</b> <u>dolor</u> <i.sit>sit</i> amet,
+
+2
+00:00:03.000 --> 00:00:04.000
+consectetuer adipiscing elit,
+
+3
+00:00:05.000 --> 00:00:06.000
+sed diam nonummy nibh euismod tincidunt
+
+4
+00:00:07.000 --> 00:00:08.000
+ut laoreet dolore magna aliquam erat volutpat.
+
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/captions.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions.vtt
new file mode 100644
index 0000000..787c430
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/captions.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+
+1
+00:00:00.000 --> 00:00:01.000
+Lorem
+
+2
+00:00:01.000 --> 00:00:02.000
+ipsum
+
+3
+00:00:02.000 --> 00:00:03.000
+dolor
+
+4
+00:00:03.000 --> 00:00:04.000
+sit
+
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/class-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/class-bad.vtt
new file mode 100644
index 0000000..650ea2c
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/class-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid <c> class markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<c .black>Bear is Coming!!!!!</c>
+The space signified an annotation start.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<c.red&large>I said Bear is coming!!!!</c>
+Probably should only allow characters that CSS allows in class names.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <c.9red.upper+case>Bear is coming now</c>!!!!
+Probably should only allow characters that CSS allows in class names.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/class.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/class.vtt
new file mode 100644
index 0000000..ea3ef62
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/class.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cue text fragment with <c> class markup is mapped to HTML <span> element with CSS classes.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<c.black>Bear is Coming!!!!!</c>
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<c.green>I said Bear is coming!!!!</c>
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <c.red.uppercase>Bear is coming now</c>!!!!
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id-error.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id-error.vtt
new file mode 100644
index 0000000..2b5db0c
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id-error.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cue identifiers cannot contain the string "-->".
+
+-->random_id
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+another random identifier-->
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+identifier-->too
+00:01:01.000 --> 00:20:00.500
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id.vtt
new file mode 100644
index 0000000..3902118
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-id.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+Random text is accepted for cue identifiers.
+
+random_id
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+another random identifier
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+identifier--too
+00:01:01.000 --> 00:02:00.500
+I said Bear is coming now!!!!
+
+identifier--too
+00:02:01.000 --> 00:03:00.500
+Duplicate identifier
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id-error.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id-error.vtt
new file mode 100644
index 0000000..111bae6
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id-error.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cue identifiers cannot contain "-->".  Whole cue is ignored.
+
+-->
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+-->
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+-->
+00:01:01.000 --> 00:20:00.500
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id.vtt
new file mode 100644
index 0000000..0d52a70
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-no-id.vtt
@@ -0,0 +1,11 @@
+WEBVTT
+Cues don't have to have identifiers.
+
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+00:01:01.000 --> 00:20:00.500
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-cuetext.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-cuetext.vtt
new file mode 100644
index 0000000..88f56cc
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-cuetext.vtt
@@ -0,0 +1,6 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+Valid cue 1
+00:02.000 --> 00:03.000
+Valid cue 2
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-header.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-header.vtt
new file mode 100644
index 0000000..205955e3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-header.vtt
@@ -0,0 +1,6 @@
+WEBVTT
+00:00.000 --> 00:01.000
+Valid cue 1
+
+00:02.000 --> 00:03.000
+Valid cue 2
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-note.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-note.vtt
new file mode 100644
index 0000000..56defcc
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-recovery-note.vtt
@@ -0,0 +1,9 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+Valid cue 1
+
+NOTE about something
+NOTE or something else - maybe an identifier
+00:02.000 --> 00:03.000
+Valid cue 2
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align-bad.vtt
new file mode 100644
index 0000000..5e4a61a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align-bad.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+Either size or alignment are invalid.
+
+1
+00:00:00.000 --> 00:00:30.500 size:100% align:@start
+Bear is Coming!!!!!
+Box for the cue is 100% of the video viewport width, alignment is ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 size:-10% align:end
+I said Bear is coming!!!!
+Box for the cue is as big as the text, no line wrapping,
+(except if viewport is too small) and end aligned.
+
+3
+00:01:01.000 --> 00:02:00.500 size:110% align:@end
+I said Bear is coming now!!!!
+Both cue size and alignment are ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align.vtt
new file mode 100644
index 0000000..6d36536
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-align.vtt
@@ -0,0 +1,19 @@
+WEBVTT
+Valid cue size with alignment settings.
+
+1
+00:00:00.000 --> 00:00:30.500 size:100% align:start
+Bear is Coming!!!!!
+Box for the cue is 100% of the video viewport width
+and because of the start align, all text is left aligned on the video viewport.
+
+2
+00:00:31.000 --> 00:01:00.500 size:10% align:end
+I said Bear is coming!!!!
+Box for the cue is 10% of the video viewport width, which will mean that automatic line wrapping will happen
+and the text is aligned to the end.
+
+3
+00:01:01.000 --> 00:02:00.500 size:0% align:middle
+I said Bear is coming now!!!!
+Cue text box size of 0 is acceptable, even if not visible.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-bad.vtt
new file mode 100644
index 0000000..700600d
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid cue sizes (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:30.500 size:	50%
+Bear is Coming!!!!!
+Cue size setting doesn't parse and is ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 size:-10%
+I said Bear is coming!!!!
+Negative cue size setting is not acceptable and is ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 size:4000%
+I said Bear is coming now!!!!
+Cue size beyond 100% is not acceptable and is ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size.vtt
new file mode 100644
index 0000000..017d59a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cue-size.vtt
@@ -0,0 +1,19 @@
+WEBVTT
+Valid cue size values.
+
+1
+00:00:00.000 --> 00:00:30.500 size:100%
+Bear is Coming!!!!!
+Box for the cue is 100% of the video viewport width,
+exemplified through background color,
+even if the text needs less.
+
+2
+00:00:31.000 --> 00:01:00.500 size:10%
+I said Bear is coming!!!!
+Box for the cue is 10% of the video viewport width, which will mean that automatic line wrapping will happen.
+
+3
+00:01:01.000 --> 00:02:00.500 size:0%
+I said Bear is coming now!!!!
+Cue text box size of 0 is acceptable, even if not visible.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-chrono-order.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-chrono-order.vtt
new file mode 100644
index 0000000..fd6d484
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-chrono-order.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cues that have overlapping time ranges.
+
+1
+00:00:01.000 --> 00:00:02.000
+Bear is Coming!!!!!
+
+2
+00:00:02.500 --> 00:00:03.500
+I said Bear is coming!!!!
+
+3
+00:00:04.000 --> 00:00:05.000
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-no-separation.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-no-separation.vtt
new file mode 100644
index 0000000..9062c67
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-no-separation.vtt
@@ -0,0 +1,11 @@
+WEBVTT
+Cues must be separated by at least one blank line, otherwise treated like one big cue.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+2
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+00:01:01.000 --> 100:20:00.500
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-overlapping.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-overlapping.vtt
new file mode 100644
index 0000000..3f035d3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues-overlapping.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Cues that have overlapping time ranges.
+
+1
+00:00:01.000 --> 00:00:06.000
+Bear is Coming!!!!!
+
+2
+00:00:01.500 --> 00:00:05.000
+I said Bear is coming!!!!
+
+3
+00:00:02.000 --> 00:00:05.000
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/cues.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues.vtt
new file mode 100644
index 0000000..125ed66
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/cues.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Cues may be separated by one or more blank lines.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+
+2
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+
+
+3
+00:01:01.000 --> 100:20:00.500
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/default-styles.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/default-styles.vtt
new file mode 100644
index 0000000..d890ca3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/default-styles.vtt
@@ -0,0 +1,19 @@
+WEBVTT
+
+COMMENT-->
+this is a comment, that will parse as part of the header;
+the STYLE and DEFAULTS below are parsed as invalid cues
+
+STYLE-->
+::cue(.narration) { color: blue; }
+
+DEFAULTS -->
+line:-1 align:middle size:50%
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/degenerate-cues.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/degenerate-cues.vtt
new file mode 100644
index 0000000..c043904
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/degenerate-cues.vtt
@@ -0,0 +1,5 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+00:02.000 --> 00:03.000
+00:04.000 --> 00:05.000
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/empty-cue.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/empty-cue.vtt
new file mode 100644
index 0000000..dbfde34
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/empty-cue.vtt
@@ -0,0 +1,11 @@
+WEBVTT
+Empty cues should not be discarded.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/entities-wrong.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/entities-wrong.vtt
new file mode 100644
index 0000000..f45fee4
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/entities-wrong.vtt
@@ -0,0 +1,15 @@
+WEBVTT
+Invalid use of < and > characters.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+This cue has a less than < character.
+It turns everything from there on into an annotation
+for an empty tag and ends only at the next &gt; or &amp; character.
+
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+This cue has a greater than > character.
+Since it's not related to a &lt; character,
+it's just interpreted as text.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/entities.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/entities.vtt
new file mode 100644
index 0000000..a881795
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/entities.vtt
@@ -0,0 +1,30 @@
+WEBVTT
+Cue content with escape characters for &, <, >, LRM, RLM and non-breaking space.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+This cue has an ampersand &amp; character.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+This cue has a less than &lt; character.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+This cue has a greater than &gt; character.
+
+4
+00:02:01.000 --> 00:02:30.500 align:start position:20%
+This cue has a Left-to-Right Mark &lrm;.
+
+5
+00:02:31.000 --> 00:03:00.500 align:start position:20%
+This cue has a Right-to-Left Mark &rlm;.
+
+6
+00:03:01.000 --> 00:03:30.500 align:start position:20%
+This cue has a non-breaking space &nbsp;.
+
+7
+00:03:31.000 --> 00:04:00.500
+This & is parsed to the same as &amp;.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/interspersed-non-cue.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/interspersed-non-cue.vtt
new file mode 100644
index 0000000..c825ab3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/interspersed-non-cue.vtt
@@ -0,0 +1,9 @@
+WEBVTT
+
+00:00.000 --> 00:01.000
+First
+
+Stray Id or other non-cue content
+
+00:02.000 --> 00:03.000
+Second
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/iso2022jp3.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/iso2022jp3.vtt
new file mode 100644
index 0000000..10a1624
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/iso2022jp3.vtt
@@ -0,0 +1,10 @@
+WEBVTT FILE
+Different encodings (iconv) should not be recognized as WebVTT a file.
+
+1
+00:00:00.000 --> 00:00:30.500
+$B7J5$H=CG(B
+
+2
+00:00:31.000 --> 00:20:00.500
+$BEENOITB-(B
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/large-timestamp.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/large-timestamp.vtt
new file mode 100644
index 0000000..e6c18ce
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/large-timestamp.vtt
@@ -0,0 +1,5 @@
+WEBVTT
+
+1
+1234567:00:00.000 --> 1234567890:00:00.000
+A very long cue.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position-bad.vtt
new file mode 100644
index 0000000..3d52175
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position-bad.vtt
@@ -0,0 +1,30 @@
+WEBVTT
+Invalid positioning values (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:15.000 line:-0%
+Bear is Coming!!!!!
+Negative percentages are not allowed.
+Line position is ignored.
+
+2
+00:00:31.000 --> 00:00:45.500 line:+50%
+I said Bear is coming!!!!
+Non-numbers are not allowed.
+Line position is ignored.
+
+00:00:46.000 --> 00:01:00.500 line:+5
+I said Bear is coming!!!!
+Plus sign is not allowed.
+Line position is ignored.
+
+3
+00:01:01.000 --> 00:01:30.000 line:10%0%
+I said Bear is coming now!!!!
+Doesn't parse into a percentage.
+Line position is ignored.
+
+00:01:31.000 --> 00:02:00.500 line:-10l
+I said Bear is coming now!!!!
+Doesn't parse into a number.
+Line position is ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position.vtt
new file mode 100644
index 0000000..82f7e2a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/line-position.vtt
@@ -0,0 +1,37 @@
+WEBVTT
+Cues with valid vertical line positioning values.
+
+1
+00:00:00.000 --> 00:00:15.000 line:0%
+Bear is Coming!!!!!
+Positioning on the top of the viewport, in the middle.
+
+00:00:15.500 --> 00:00:30.500 line:0
+Bear is Coming!!!!!
+This is line 0.
+Positioning on the top of the viewport, in the middle.
+
+2
+00:00:31.000 --> 00:00:45.500 line:50%
+I said Bear is coming!!!!
+Positioning on the center of the video.
+
+
+00:00:46.000 --> 00:01:00.500 line:5
+I said Bear is coming!!!!
+This is line 6 from the top of the video viewport.
+
+3
+00:01:01.000 --> 00:01:30.000 line:100%
+I said Bear is coming now!!!!
+Positioning on the bottom middle.
+
+00:01:31.000 --> 00:02:00.500 line:-1
+I said Bear is coming now!!!!
+This is the first line at the bottom of the video viewport.
+Positioning on the bottom middle. Only 1 line shows.
+
+00:02:01.000 --> 00:02:30.000 line:500
+I said Bear is coming now!!!!
+This is legal,
+even though the line will likely not be within the video viewport.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/markup-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/markup-bad.vtt
new file mode 100644
index 0000000..4ff7add
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/markup-bad.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cue text has invalid markup of <b>, <i>, <u>, <rt> and <ruby>.  Has a bad effect on the remainder of the cue.
+
+1
+00:00:00.000 --> 00:00:15.000 align:start position:20%
+The following bear starts bold but end is broken:
+<b>Bear</ b> is Coming!!!!!
+
+00:00:15.500 --> 00:00:30.500 align:start position:20%
+The following bear is not in italics but the markup is removed:
+< i>Bear</i> is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+The following bear is not underlined and markup is removed:
+I said < u >Bear</u> is coming!!!!
+
+3
+00:01:01.000 --> 00:01:30.000 align:start position:20%
+The following bear is not ruby annotated and markup is removed:
+I said <ru by>Bear<rt>bear with me</rt></ruby> is coming!!!!
+
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/markup.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/markup.vtt
new file mode 100644
index 0000000..252a599
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/markup.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+Cues with <b>, <i>, <u>, <rt> and <ruby> tags (all valid).
+
+1
+00:00:00.000 --> 00:00:15.000 align:start position:20%
+The following bear is bold:
+<b>Bear</b> is Coming!!!!!
+
+00:00:15.500 --> 00:00:30.500 align:start position:20%
+The following bear is in italics and has a class of "larger":
+<i.larger>Bear</i> is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+The following bear is underlined even though the element has a blank:
+I said <u >Bear</u> is coming!!!!
+
+3
+00:01:01.000 --> 00:01:30.000 align:start position:20%
+The following bear is ruby annotated:
+I said <ruby>Bear<rt>bear with me</rt></ruby> is coming!!!!
+
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata-area.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata-area.vtt
new file mode 100644
index 0000000..255298a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata-area.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+This is where metadata would go and these lines should be skipped.
+author = silviapf@google.com
+COMMENT-->
+this is a comment, that will parse as part of the header;
+the STYLE and DEFAULTS below are parsed as invalid cues
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata.vtt
new file mode 100644
index 0000000..03d8cf4
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/metadata.vtt
@@ -0,0 +1,38 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+Lorem ipsum dolor sit amet,
+
+00:00:02.000 --> 00:00:03.000
+consectetuer adipiscing elit,
+
+00:00:04.000 --> 00:00:05.000
+sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+00:00:06.000 --> 00:00:07.000
+Ut wisi enim ad minim veniam,
+
+00:00:08.000 --> 00:00:09.000
+quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+00:00:10.000 --> 00:00:11.000
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat,
+
+00:00:12.000 --> 00:00:13.000
+vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
+
+00:00:14.000 --> 00:00:15.000
+dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+00:00:16.000 --> 00:00:17.000
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id
+
+00:00:18.000 --> 00:00:19.000
+quod mazim placerat facer possim assum.
+
+00:00:20.000 --> 00:00:21.000
+Typi non habent claritatem insitam;
+
+00:00:22.000 --> 00:00:23.000
+est usus legentis in iis qui facit eorum claritatem.
+
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/missed-cues.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/missed-cues.vtt
new file mode 100644
index 0000000..36e8366
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/missed-cues.vtt
@@ -0,0 +1,31 @@
+WEBVTT
+Events should be triggered for missed (skipped) cues during normal playback.
+
+1
+00:00:00.000 --> 00:00:01.500 align:start position:20%
+Bear is Coming!!!!!
+And what kind of a bear it is - just have look.
+
+2
+00:00:02.000 --> 00:00:02.500 align:start position:20%
+I said Bear is coming!!!!
+
+3
+00:00:05.500 --> 00:00:05.501 align:start position:20%
+I said Bear is coming now!!!!
+
+4
+00:00:05.700 --> 00:00:05.701 align:start position:20%
+This is the second missed cue in the test.
+
+5
+00:00:05.800 --> 00:00:05.800 align:start position:20%
+Third missed cue - zero-length cue.
+
+6
+00:00:05.850 --> 00:00:05.851 align:start position:20%
+Fourth missed cue.
+
+7
+00:00:05.950 --> 00:00:01.100
+Negative length cue. Should be treated correctly.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/no-newline-at-eof.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/no-newline-at-eof.vtt
new file mode 100644
index 0000000..49e4e90
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/no-newline-at-eof.vtt
@@ -0,0 +1,6 @@
+WEBVTT
+A file with no line terminator at the end should be fine (last cue should be recognized).
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/no-timings.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/no-timings.vtt
new file mode 100644
index 0000000..4cb85b6
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/no-timings.vtt
@@ -0,0 +1,13 @@
+WEBVTT
+Cues without timings are ignored.
+
+1
+00:00:00.000
+Bear is Coming!!!!!
+
+2
+00h:00m:31s.000ms
+I said Bear is coming!!!!
+
+3
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/no-webvtt.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/no-webvtt.vtt
new file mode 100644
index 0000000..12053b2
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/no-webvtt.vtt
@@ -0,0 +1,10 @@
+AWEBVTT FILE
+A file with wrong file header should not be recognized as a webvtt file.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-bad.vtt
new file mode 100644
index 0000000..58ca679
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-bad.vtt
@@ -0,0 +1,39 @@
+WEBVTT
+Invalid horizontal positioning values (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:15.500 position:-5%
+Bear is Coming!!!!!
+This would be off screen -> ignored.
+
+00:00:16.000 --> 00:00:30.500 position:150%
+Bear is Coming!!!!!
+This would be off screen -> ignored.
+
+2
+00:00:31.000 --> 00:00:45.500 position:50
+I said Bear is coming!!!!
+Missing percent sign -> ignored.
+
+2
+00:00:46.000 --> 00:01:00.500 position:50a%
+I said Bear is coming!!!!
+Surplus character between number and percent sign -> ignored.
+
+3
+00:01:01.000 --> 00:01:30.500 position:100%-fj
+I said Bear is coming now!!!!
+Surplus characters after percent sign -> ignored.
+
+
+00:01:31.000 --> 00:02:00.500 position:100asdf
+I said Bear is coming now!!!!
+Surplus characters and no percent sign -> ignored.
+
+00:02:01.000 --> 00:02:02.000 position:e50%
+I said Bear is coming now!!!!
+Surplus characters at beginning of size string -> ignored.
+
+00:02:02.100 --> 00:02:02.500 position:5g0%
+I said Bear is coming now!!!!
+Surplus characters in middle of size string -> ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-ltr.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-ltr.vtt
new file mode 100644
index 0000000..b23a744
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning-ltr.vtt
@@ -0,0 +1,21 @@
+WEBVTT
+Valid horizontal positioning values.
+
+1
+00:00:00.000 --> 00:00:30.500 position:0%
+الدب قادم!!!!!
+تحديد المواقع في أسفل اليمين.
+
+2
+00:00:31.000 --> 00:00:45.500 position:50%
+قلت الدب قادم!!
+تحديد المواقع في منتصف القاع.
+
+00:00:46.000 --> 00:01:00.500
+قلت الدب قادم!!
+المواقع الافتراضية على منتصف أسفل تزال قائمة.
+
+3
+00:01:01.000 --> 00:02:00.500 position:100%
+قلت الدب قادم الآن!!
+غادر لتحديد المواقع في القاع.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning.vtt
new file mode 100644
index 0000000..ccf6024
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/positioning.vtt
@@ -0,0 +1,21 @@
+WEBVTT
+Valid horizontal positioning values.
+
+1
+00:00:00.000 --> 00:00:30.500 position:0%
+Bear is Coming!!!!!
+Positioning on the left bottom.
+
+2
+00:00:31.000 --> 00:00:45.500 position:50%
+I said Bear is coming!!!!
+Positioning on the bottom middle.
+
+00:00:46.000 --> 00:01:00.500
+I said Bear is coming!!!!
+Default positioning on the bottom middle still.
+
+3
+00:01:01.000 --> 00:02:00.500 position:100%
+I said Bear is coming now!!!!
+Positioning on the bottom right.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/settings-bad-separation.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/settings-bad-separation.vtt
new file mode 100644
index 0000000..cbfe6ea
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/settings-bad-separation.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Cues settings may only be separated by spaces or tabs, but illegal characters
+between settings are ignored.
+
+1
+00:00:00.000 --> 00:00:30.500 - line:43% position:10% -
+Bear is Coming!!!!! Bad separator ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 --> position:50% Vertical:lr align:end
+I said Bear is coming!!!! Bad separator and setting ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 <align:end> <position:90%>
+I said Bear is coming now!!!! Bad setting markup. Not ignored because the settings are
+not delimited by spaces or tabs.
+
+4
+00:02:01.000 --> 100:20:00.500 / vertical:lr | position:90%
+I said Bear is coming now!!!! Bad separator ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/settings.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/settings.vtt
new file mode 100644
index 0000000..dd6b022
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/settings.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+Cue settings may be separated by spaces or tabs.
+
+1
+00:00:00.000 --> 00:00:30.500 line:100% align:start
+Bear is Coming!!!!! One blank.
+
+2
+00:00:31.000 --> 00:01:00.500   position:40% vertical:rl line:15%
+I said Bear is coming!!!! Several blanks.
+
+3
+00:01:01.000 --> 00:02:00.500	align:middle position:10%
+I said Bear is coming now!!!! Tab separator.
+
+4
+00:02:01.000 --> 100:20:00.500		line:95%	vertical:lr align:end
+I said Bear is coming now!!!! Tab separators.
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/simple-captions.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/simple-captions.vtt
new file mode 100644
index 0000000..9815b11
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/simple-captions.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+
+0
+00:00:04.000 --> 00:00:04.500
+First cue
+
+1
+00:00:04.500 --> 00:00:05.000
+Lorem
+
+2
+00:00:05.000 --> 00:00:05.500
+ipsum
+
+3
+00:00:05.500 --> 00:00:05.501
+Missed cue with pause-on-exit
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/sorted-dispatch.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/sorted-dispatch.vtt
new file mode 100644
index 0000000..438ea6a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/sorted-dispatch.vtt
@@ -0,0 +1,34 @@
+WEBVTT
+Enter and exit events should be dispatched in a sorted order according to their times.
+
+0
+00:00:04.000 --> 00:00:04.500
+Missed cue that should not be considered because of seeking.
+
+1
+00:00:05.100 --> 00:00:05.800 align:start position:20%
+Bear is Coming!!!!!
+
+2
+00:00:05.100 --> 00:00:05.101
+Missed cue 1
+
+3
+00:00:05.100 --> 00:00:05.301
+And what kind of a bear it is - just have look.
+
+4
+00:00:05.100 --> 00:00:05.101
+Missed Cue 2
+
+5
+00:00:05.300 --> 00:00:05.800 align:start position:20%
+I said Bear is coming!!!!
+
+6
+00:00:05.990 --> 00:00:05.993 align:start position:20%
+I said Bear is coming now!!!!
+
+7
+00:00:05.994 --> 00:00:05.998 align:start position:20%
+Bear is already here
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp-bad.vtt
new file mode 100644
index 0000000..4479cdb
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid <timestamp> markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+This <00:00:05.000>cue <00:00:10.000>is <00:00:12.000>painted <00:00:08.000>on.
+But since the last two timestamps are out of order, they are ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+I <00:00:20.000>said <00:00:22.000>Bear <00:00:24.000>is <00:00:26.000>coming!!!!
+All of these timestamps are before the start of the cue, so get ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I <00:02:05.000>said <00:02:10.000>Bear <00:02:15.000>is <00:02:20.000>coming <00:02:25.000>now!!!!
+All of these timestamps are after the end of the cue, so get ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp.vtt
new file mode 100644
index 0000000..17d464b
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/timestamp.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Paint-on text in cues with <timestamp> markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+This <00:00:05.000>cue <00:00:10.000>is <00:00:15.000>painted <00:00:20.000>on.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+I <00:00:35.000>said <00:00:40.000>Bear <00:00:45.000>is <00:00:50.000>coming!!!!
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I <00:01:05.000>said <00:01:10.000>Bear <00:01:15.000>is <00:01:20.000>coming <00:01:25.000>now!!!!
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour-error.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour-error.vtt
new file mode 100644
index 0000000..c33f8a9
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour-error.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+These timings all have errors and all cues should be ignored.
+
+1
+00:00.00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00:500
+I said Bear is coming!!!!
+
+3
+00:01:01.000 --> 00:120:00.500
+I said Bear is coming now!!!!
+
+4
+00:02:01.000 - 00:03:00.500
+I said Bear is coming now!!!!
+
+5
+00h:03m:01s.000ms --> 00h:03m:00s.500ms
+I said Bear is coming now!!!!
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour.vtt
new file mode 100644
index 0000000..b708b83
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-hour.vtt
@@ -0,0 +1,14 @@
+WEBVTT
+Timings can optionally contain an hour.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:01:00.500
+I said Bear is coming!!!!
+
+3
+00:01:01.000 --> 100:20:00.500
+I said Bear is coming now!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour-errors.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour-errors.vtt
new file mode 100644
index 0000000..e4bf27d
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour-errors.vtt
@@ -0,0 +1,22 @@
+WEBVTT
+These timings all have errors and all cues should be ignored.
+
+1
+00.00.000 --> 00:30.500
+Bear is Coming!!!!!
+
+2
+00:31.000 --> 01:00:500
+I said Bear is coming!!!!
+
+3
+01:01.000 --> 120:00.500
+I said Bear is coming now!!!!
+
+4
+01:01.000 - 02:00.500
+I said Bear is coming now!!!!
+
+5
+02:01.000 --> 03m:00.500
+I said Bear is coming now!!!!
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour.vtt
new file mode 100644
index 0000000..745c34f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-no-hour.vtt
@@ -0,0 +1,18 @@
+WEBVTT
+The hour of a timestamp is optional.
+
+1
+00:00.000 --> 00:30.500
+Bear is Coming!!!!!
+
+2
+00:31.000 --> 01:00.500
+I said Bear is coming!!!!
+
+3
+01:01.000 --> 02:00.500
+I said Bear is coming now!!!!
+
+4
+02:01.000	-->	03:00.500
+tab separators
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-whitespace.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-whitespace.vtt
new file mode 100644
index 0000000..9d9ac9a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/timings-whitespace.vtt
@@ -0,0 +1,51 @@
+WEBVTT
+Whitespace (U+0020, U+0009, U+000C) surrounding cue-timings separator ("-->") is optional
+
+1
+00:00:00.100 -->00:00:01.500
+Single U+0020 SPACE left of cue-timings separator
+
+2
+00:00:00.100--> 00:00:01.500
+Single U+0020 SPACE right of cue-timings separator
+
+3
+00:00:00.100	-->00:00:01.500
+Single U+0009 TAB left of cue-timings separator
+
+4
+00:00:00.100-->	00:00:01.500
+Single U+0009 TAB right of cue-timings separator
+
+5
+00:00:00.100-->00:00:01.500
+Single U+000C FORM FEED left of cue-timings separator
+
+6
+00:00:00.100-->00:00:01.500
+Single U+000C FORM FEED right of cue-timings separator
+
+7
+00:00:00.100   -->00:00:01.500
+Several U+0020 SPACE left of cue-timings separator
+
+8
+00:00:00.100-->   00:00:01.500
+Several U+0020 SPACE right of cue-timings separator
+
+9
+00:00:00.100			-->00:00:01.500
+Several U+0009 TAB left of cue-timings separator
+
+10
+00:00:00.100-->			00:00:01.500
+Several U+0009 TAB right of cue-timings separator
+
+11
+00:00:00.100-->00:00:01.500
+Several U+000C FORM FEED left of cue-timings separator
+
+12
+00:00:00.100-->00:00:01.500
+Several U+000C FORM FEED right of cue-timings separator
+
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/unsupported-markup.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/unsupported-markup.vtt
new file mode 100644
index 0000000..b4ea7ea
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/unsupported-markup.vtt
@@ -0,0 +1,23 @@
+WEBVTT
+Any HTML markup that is not supported should be ignored.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<h1>Bear is Coming!!!!!</h1>
+<p>And what kind of a bear it is - just have <a href="webpage.html">look</a>.</p>
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<ul>
+  <li>I said Bear is coming!!!!</li>
+  <li>I said Bear is still coming!!!!</li>
+</ul>
+
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+<ol>
+  <li>I said Bear is coming now!!!!</li>
+  <li><img src="bear.png" alt="mighty bear"></li>
+  <li><video src="bear_ad.webm" controls></video></li>
+</ol>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/utf8.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/utf8.vtt
new file mode 100644
index 0000000..8dd8f27
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/utf8.vtt
@@ -0,0 +1,10 @@
+WEBVTT
+UTF-8 encoded characters should be recognized.
+
+1
+00:00:00.000 --> 00:00:30.500
+景気判断
+
+2
+00:00:31.000 --> 00:20:00.500
+電力不足
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-bad.vtt
new file mode 100644
index 0000000..8e7b3b7
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid vertical direction settings (all settings are ignored).
+
+1
+00:00:00.000 --> 00:00:30.500 vertical:#vertical
+Bear is Coming!!!!!
+Normal rendering - direction setting is ignored.
+
+2
+00:00:31.000 --> 00:01:00.500 vertical:verticallr
+I said Bear is coming!!!!
+Normal rendering - direction setting is ignored.
+
+3
+00:01:01.000 --> 00:02:00.500 vertical:vertical-rl
+I said Bear is coming now!!!!
+Normal rendering - direction setting is ignored.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-ltr.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-ltr.vtt
new file mode 100644
index 0000000..7483836
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/valign-ltr.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Valid vertical direction settings.
+
+1
+00:00:00.000 --> 00:00:30.500 vertical:rl
+الدب قادم!!!!!
+يجعل على الجانب الأيمن من المعاينة الفيديو والمتوسطة الانحياز ،
+أسفل إلى أعلى، وتزايد اليسار.
+
+2
+00:00:31.000 --> 00:01:00.500 vertical:lr
+قلت الدب قادم!!
+يجعل على الجانب الأيسر من المعاينة الفيديو والمتوسطة الانحياز ،
+أسفل إلى أعلى، وتنامي اليمين.
+
+3
+00:01:01.000 --> 00:02:00.500 vertical:rl align:start position:0%
+قلت الدب قادم الآن!!
+يجعل على الجانب الأيمن من المعاينة الفيديو ، على حد سواء أسفل محاذاة
+لمربع جديلة والنص داخل النص ، من أسفل إلى أعلى، وتزايد اليسار.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/valign.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/valign.vtt
new file mode 100644
index 0000000..f757a36
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/valign.vtt
@@ -0,0 +1,20 @@
+WEBVTT
+Valid vertical direction settings.
+
+1
+00:00:00.000 --> 00:00:30.500 vertical:rl
+Bear is Coming!!!!!
+Renders on the right side of the video viewport, middle aligned,
+top to bottom, growing left.
+
+2
+00:00:31.000 --> 00:01:00.500 vertical:lr
+I said Bear is coming!!!!
+Renders on the left side of the video viewport, middle aligned,
+top to bottom, growing right.
+
+3
+00:01:01.000 --> 00:02:00.500 vertical:rl align:start position:0%
+I said Bear is coming now!!!!
+Renders on the right side of the video viewport, top aligned both
+for the cue box and the text within, text from top to bottom, growing left.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/voice-bad.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/voice-bad.vtt
new file mode 100644
index 0000000..12ffdeb
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/voice-bad.vtt
@@ -0,0 +1,17 @@
+WEBVTT
+Invalid <v> voice markup.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+< v Speaker>Bear is Coming!!!!!</v>
+This is two annotations for an empty tag.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<v&Doe Hunter>I said Bear is coming!!!!</v>
+This does not parse as a voice tag.
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <v-Speaker>Bear is coming now</v>!!!!
+This does not parse as a voice tag.
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/voice.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/voice.vtt
new file mode 100644
index 0000000..d6cfc68
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/voice.vtt
@@ -0,0 +1,15 @@
+WEBVTT
+Cue text fragment with <v> voice markup mapped to HTML <q> element with @title for annotation.
+
+1
+00:00:00.000 --> 00:00:30.500 align:start position:20%
+<v.blue Speaker>Bear is Coming!!!!!</v>
+Text span with a class and an annotation.
+
+2
+00:00:31.000 --> 00:01:00.500 align:start position:20%
+<v Doe Hunter>I said Bear is coming!!!!</v>
+
+3
+00:01:01.000 --> 00:02:00.500 align:start position:20%
+I said <v.blue	Speaker>Bear is coming now</v>!!!!
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/vp8-vorbis-webvtt.webm b/html/semantics/embedded-content/media-elements/track/track-element/resources/vp8-vorbis-webvtt.webm
new file mode 100644
index 0000000..c626f86
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/vp8-vorbis-webvtt.webm
Binary files differ
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-file.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-file.vtt
new file mode 100644
index 0000000..0c1a5fb
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-file.vtt
@@ -0,0 +1,9 @@
+WEBVTT FILE
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-rubbish.vtt b/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-rubbish.vtt
new file mode 100644
index 0000000..dacc215
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/resources/webvtt-rubbish.vtt
@@ -0,0 +1,10 @@
+WEBVTT asdfasdfauhio
+Rubbish after the WEBVTT header should be ignored.
+
+1
+00:00:00.000 --> 00:00:30.500
+Bear is Coming!!!!!
+
+2
+00:00:31.000 --> 00:20:00.500
+I said Bear is coming!!!!
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/src-empty-string.html b/html/semantics/embedded-content/media-elements/track/track-element/src-empty-string.html
new file mode 100644
index 0000000..27c76b6
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/src-empty-string.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Setting HTMLTrackElement.src to the empty string fires 'error' and sets readyState to ERROR</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video></video>
+<script>
+async_test(t => {
+  let track = document.createElement("track");
+  track.src = '';
+  track.default = true;
+  track.onerror = t.step_func_done(() => {
+    assert_equals(track.readyState, HTMLTrackElement.ERROR);
+  });
+  track.onload = t.unreached_func('fired load');
+
+  assert_equals(track.readyState, HTMLTrackElement.NONE);
+
+  document.querySelector('video').appendChild(track);
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-active-cues.html b/html/semantics/embedded-content/media-elements/track/track-element/track-active-cues.html
new file mode 100644
index 0000000..67ab3bd
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-active-cues.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<title>Ensure that no text track cues are active after the video is unloaded</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var eventCount = 0;
+
+    function eventCallback() {
+        eventCount++;
+        if (eventCount == 3) {
+            assert_equals(trackElement.track.activeCues.length, 1);
+            video.src = '';
+        }
+    }
+
+    var video = document.createElement('video');
+    video.src = getVideoURI('/media/movie_5');
+    var trackElement = document.createElement('track');
+
+    trackElement.onload = t.step_func(eventCallback);
+    trackElement.oncuechange = t.step_func(eventCallback);
+    video.oncanplaythrough = t.step_func(eventCallback);
+
+    video.onerror = t.step_func_done(function() {
+        assert_equals(event.target, video);
+        assert_not_equals(video.error, null);
+        assert_equals(video.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE);
+        assert_equals(trackElement.track.activeCues.length, 0);
+    });
+
+    trackElement.src = 'resources/captions-fast.vtt';
+    trackElement.kind = 'captions';
+    trackElement.default = true;
+    video.appendChild(trackElement);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-add-remove-cue.html b/html/semantics/embedded-content/media-elements/track/track-element/track-add-remove-cue.html
new file mode 100644
index 0000000..1e6c557
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-add-remove-cue.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<title>TextTrack's addCue and removeCue</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var video = document.createElement("video");
+    var trackElement = document.createElement("track");
+
+    trackElement.onload = t.step_func_done(function() {
+        var cues = trackElement.track.cues;
+        // Test cues loaded from the file.
+        assert_equals(cues.length, 4);
+        assert_equals(cues.getCueById("1").startTime, 0);
+        assert_equals(cues[1].startTime, 31);
+        assert_equals(cues[2].startTime, 61);
+        assert_equals(cues.getCueById("4").startTime, 121);
+        assert_object_equals(cues.getCueById("junk"), undefined);
+
+        // Create a new cue, check values.
+        var textCue = new VTTCue(33, 3.4, "Sausage?");
+        assert_equals(textCue.track, null);
+        assert_equals(textCue.id, "");
+        assert_equals(textCue.startTime, 33);
+        assert_equals(textCue.endTime, 3.4);
+        assert_equals(textCue.pauseOnExit, false);
+        assert_equals(textCue.vertical, "");
+        assert_equals(textCue.snapToLines, true);
+        assert_equals(textCue.line, "auto");
+        assert_equals(textCue.position, "auto");
+        assert_equals(textCue.size, 100);
+        assert_equals(textCue.align, "center");
+
+        // Remove the unadded track, make sure it throws correctly.
+        assert_throws("NotFoundError", function() { trackElement.track.removeCue(textCue); });
+
+        // Add the new cue to a track, make sure it is inserted correctly.
+        trackElement.track.addCue(textCue);
+        assert_equals(textCue.track, trackElement.track);
+        assert_equals(cues[1].startTime, 31);
+        assert_equals(cues[2].startTime, 33);
+        assert_equals(cues[3].startTime, 61);
+
+        // create a new cue and add it to a track created with
+        // video.addTextTrack, make sure it is inserted correctly.
+        var newTrack = video.addTextTrack("subtitles", "French subtitles", "fr");
+        newTrack.mode = "showing";
+        var newCue = new VTTCue(0, 1, "Test!");
+        newTrack.addCue(newCue);
+        assert_equals(newCue, newTrack.cues[0])
+        assert_equals(newCue.track, newTrack);
+        assert_equals(newCue.id, "");
+        assert_equals(newCue.startTime, 0);
+        assert_equals(newCue.endTime, 1);
+        assert_equals(newCue.pauseOnExit, false);
+        assert_equals(newCue.vertical, "");
+        assert_equals(newCue.snapToLines, true);
+        assert_equals(newCue.line, "auto");
+        assert_equals(newCue.position, "auto");
+        assert_equals(newCue.size, 100);
+        assert_equals(newCue.align, "center");
+
+        trackElement.track.removeCue(textCue);
+        assert_equals(textCue.track, null);
+        assert_equals(cues[1].startTime, 31);
+        assert_equals(cues[2].startTime, 61);
+
+        // Remove a cue added from the WebVTT file.
+        textCue = cues[2];
+        trackElement.track.removeCue(textCue);
+        assert_equals(textCue.track, null);
+        assert_equals(cues[1].startTime, 31);
+        assert_equals(cues[2].startTime, 121);
+
+        // Try to remove the cue again.
+        assert_throws("NotFoundError", function() { trackElement.track.removeCue(textCue); });
+
+        // Add a cue before all the existing cues.
+        trackElement.track.addCue(new VTTCue(0, 31, "I am first"));
+        assert_equals(cues[0].startTime, 0);
+        assert_equals(cues[0].endTime, 31);
+        assert_equals(cues[1].startTime, 0);
+        assert_equals(cues[1].endTime, 30.5);
+        assert_equals(cues[2].startTime, 31);
+    });
+
+    trackElement.src = "resources/settings.vtt";
+    trackElement.kind = "captions";
+    trackElement.default = true;
+    video.appendChild(trackElement);
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-add-track.html b/html/semantics/embedded-content/media-elements/track/track-element/track-add-track.html
new file mode 100644
index 0000000..7f8ee2f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-add-track.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>'addtrack' event is fired when a TextTrack is created</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var video = document.createElement('video');
+
+    var trackElement = document.createElement('track');
+    video.appendChild(trackElement);
+    var tracks = [];
+    tracks.push(trackElement.track);
+
+    // Register the 'addtrack' listener after creating the element
+    // to make sure the event is dispatched asynchronously.
+    video.textTracks.onaddtrack = t.step_func(function() {
+        assert_equals(event.target, video.textTracks);
+        assert_true(event instanceof TrackEvent, 'instanceof');
+        assert_equals(event.track, tracks[video.textTracks.length - 1]);
+
+        if (video.textTracks.length == 1) {
+            tracks.push(video.addTextTrack('captions', 'Caption Track', 'en'));
+            assert_equals(video.textTracks.length, 2);
+        } else {
+            t.done();
+        }
+    });
+
+    trackElement.src = 'resources/webvtt-file.vtt';
+    trackElement.track.mode = 'hidden';
+    assert_equals(video.textTracks.length, 1);
+    assert_equals(trackElement.readyState, HTMLTrackElement.NONE);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-addtrack-kind.html b/html/semantics/embedded-content/media-elements/track/track-element/track-addtrack-kind.html
new file mode 100644
index 0000000..4503a06
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-addtrack-kind.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>addTextTrack() only accepts known "kind" values</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+    var trackCount = 0;
+
+    function addTrack(type) {
+        video.addTextTrack(type);
+        assert_equals(video.textTracks.length, ++trackCount);
+    }
+
+    var video = document.createElement("video");
+    assert_equals(video.textTracks.length, 0);
+    assert_throws(new TypeError(), function() { video.addTextTrack("kaptions"); });
+    assert_equals(video.textTracks.length, 0);
+
+    addTrack("subtitles");
+    addTrack("captions");
+    addTrack("descriptions");
+    addTrack("chapters");
+    addTrack("metadata");
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-change-event.html b/html/semantics/embedded-content/media-elements/track/track-element/track-change-event.html
new file mode 100644
index 0000000..6794126
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-change-event.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>A 'change' event is fired when a TextTrack's mode changes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var video = document.createElement('video');
+    var track = video.addTextTrack('subtitles', 'test', 'en');
+
+    // addTextTrack() defaults to "hidden", so settings
+    // mode to "showing" should trigger a "change" event.
+    track.mode = 'showing';
+    assert_equals(video.textTracks.length, 1);
+
+    video.textTracks.onchange = t.step_func_done(function() {
+        assert_equals(event.target, video.textTracks);
+        assert_true(event instanceof Event, 'instanceof');
+        assert_not_exists(event, 'track');
+    });
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-css-cue-pseudo-class.html b/html/semantics/embedded-content/media-elements/track/track-element/track-css-cue-pseudo-class.html
new file mode 100644
index 0000000..d18f8b5
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-css-cue-pseudo-class.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+:cue { color: red; }
+:cue(i) { color: red; }
+</style>
+<script>
+test(function() {
+  assert_equals(document.styleSheets[0].cssRules.length, 0);
+}, ":cue pseudo-class is not supported and dropped during parsing");
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-empty.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-empty.html
new file mode 100644
index 0000000..59f8fc6
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-empty.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Invoke getCueAsHTML() on an empty cue</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+    var emptyCue = new VTTCue(0, 0, "");
+    var fragment = emptyCue.getCueAsHTML();
+
+    // The getCueAsHTML() method should return a document fragment.
+    assert_true(fragment instanceof DocumentFragment);
+
+    // The document fragment should have one child, an empty Text node.
+    assert_equals(fragment.childNodes.length, 1);
+    assert_equals(fragment.childNodes[0].constructor.name, Text.name);
+    assert_equals(fragment.childNodes[0].length, 0);
+    assert_equals(fragment.childNodes[0].data, "");
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-inline.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-inline.html
new file mode 100644
index 0000000..3b4c354
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-inline.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Add a track and change its mode through JS</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <source src="/media/test.mp4" type="video/mp4">
+    <source src="/media/test.ogv" type="video/ogg">
+</video>
+<script>
+test(function() {
+    var video = document.querySelector('video');
+    var track = video.addTextTrack('captions', 'English', 'en');
+    track.addCue(new VTTCue(0.0, 10.0, 'wow wow'));
+    track.mode = 'showing';
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html
new file mode 100644
index 0000000..713e781
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<title>Cue fragment is mutable</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+p, div { display: none; }
+</style>
+<video>
+    <track src="resources/captions-html.vtt" kind="captions" default>
+    <script>
+        async_test(function(t) {
+            var video = document.querySelector("video");
+            var testTrack = document.querySelector("track");
+
+            video.oncanplaythrough = t.step_func(testMutability);
+            testTrack.onload = t.step_func(testMutability);
+
+            var fragment;
+            var eventCount = 0;
+            function testMutability() {
+                eventCount++;
+                if (eventCount != 2)
+                    return;
+
+                var testCue = testTrack.track.cues[0];
+
+                // Test initial cue contents.
+                assert_equals(testCue.text, "Lorem <b>ipsum</b> <u>dolor</u> <i.sit>sit</i> amet,");
+
+                // Cue getCueAsHTML() should return a correct fragment.
+                createExpectedFragment(document.createDocumentFragment());
+                assert_true(fragment.isEqualNode(testCue.getCueAsHTML()));
+
+                // Appending getCuesAsHTML() twice to the DOM should be succesful.
+                document.getElementsByTagName("div")[0].appendChild(testCue.getCueAsHTML());
+                document.getElementsByTagName("div")[1].appendChild(testCue.getCueAsHTML());
+
+                createExpectedFragment(document.createElement("div"));
+                assert_true(fragment.isEqualNode(document.getElementsByTagName("div")[0]));
+                assert_true(fragment.isEqualNode(document.getElementsByTagName("div")[1]));
+
+                // The fragment returned by getCuesAsHTML() should be independently mutable.
+                document.getElementsByTagName("div")[0].firstChild.textContent = "Different text ";
+                assert_false(fragment.isEqualNode(document.getElementsByTagName("div")[0]));
+                assert_true(fragment.isEqualNode(document.getElementsByTagName("div")[1]));
+
+                // Calling twice getCueAsHTML() should not return the same fragment.
+                assert_not_equals(testCue.getCueAsHTML(), testCue.getCueAsHTML());
+
+                t.done();
+            }
+
+            function createExpectedFragment(rootNode) {
+                fragment = rootNode;
+                fragment.appendChild(document.createTextNode("Lorem "));
+
+                var bold = document.createElement("b");
+                bold.appendChild(document.createTextNode("ipsum"));
+                fragment.appendChild(bold);
+
+                fragment.appendChild(document.createTextNode(" "));
+
+                var underline = document.createElement("u");
+                underline.appendChild(document.createTextNode("dolor"));
+                fragment.appendChild(underline);
+
+                fragment.appendChild(document.createTextNode(" "));
+
+                var italics = document.createElement("i");
+                italics.className = "sit";
+                italics.appendChild(document.createTextNode("sit"));
+                fragment.appendChild(italics);
+
+                fragment.appendChild(document.createTextNode(" amet,"));
+            }
+
+            video.src = getVideoURI("/media/counting");
+        });
+    </script>
+</video>
+<p>Fragment 1</p>
+<div></div>
+<p>Fragment 2</p>
+<div></div>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable.html
new file mode 100644
index 0000000..63c3018
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<title>Modifying attributes of a VTTCue</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track id="captions" src="resources/captions.vtt" kind="captions" default>
+    <script>
+        async_test(function(t) {
+            var track = document.querySelector("track");
+
+            track.onload = t.step_func_done(function() {
+                var cues = track.track.cues;
+
+                // Test initial values.
+                textCue = cues.getCueById("1");
+
+                assert_equals(textCue.startTime, 0);
+                assert_equals(textCue.endTime, 1.0);
+                assert_equals(textCue.pauseOnExit, false);
+                assert_equals(textCue.vertical, "");
+                assert_equals(textCue.snapToLines, true);
+                assert_equals(textCue.line, "auto");
+                assert_equals(textCue.position, "auto");
+                assert_equals(textCue.size, 100);
+                assert_equals(textCue.align, "center");
+
+                // Modify cue values.
+                textCue.startTime = 1.1;
+                assert_equals(textCue.startTime, 1.1);
+
+                textCue.endTime = 3.9;
+                assert_equals(textCue.endTime, 3.9);
+
+                textCue.pauseOnExit = true;
+                assert_equals(textCue.pauseOnExit, true);
+
+                // http://dev.w3.org/html5/webvtt/#dfn-dom-vttcue-vertical
+                // On setting, the text track cue writing direction must be
+                // set to the value given in the first cell of the row in
+                // the table above whose second cell is a case-sensitive
+                // match for the new value.
+                textCue.vertical = "RL";
+                assert_equals(textCue.vertical, "");
+                textCue.vertical = "rl";
+                assert_equals(textCue.vertical, "rl");
+
+                textCue.snapToLines = false;
+                assert_equals(textCue.snapToLines, false);
+
+                // http://dev.w3.org/html5/webvtt/#dfn-vttcue-line
+                // On setting, the text track cue line position must be set
+                // to the new value; if the new value is the string "auto",
+                // then it must be interpreted as the special value auto.
+                assert_equals(textCue.line, "auto");
+                assert_throws(new TypeError, function() { textCue.line = "gazonk"; });
+                assert_equals(textCue.line, "auto");
+                textCue.line = 42;
+                assert_equals(textCue.line, 42);
+                textCue.line = -2;
+                assert_equals(textCue.line, -2);
+                textCue.line = 102;
+                assert_equals(textCue.line, 102);
+                textCue.snapToLines = true;
+                textCue.line = -2;
+                assert_equals(textCue.line, -2);
+                textCue.line = 102;
+                assert_equals(textCue.line, 102);
+
+                // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-line
+                // On setting, if the new value is negative or greater than 100,
+                // then throw an IndexSizeError exception.
+                // Otherwise, set the text track cue text position to the new value.
+                assert_throws("IndexSizeError", function() { textCue.position = -200; });
+                assert_throws("IndexSizeError", function() { textCue.position = 110; });
+                textCue.position = 11;
+                assert_equals(textCue.position, 11);
+
+                // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size
+                // On setting, if the new value is negative or greater than 100,
+                // then throw an IndexSizeError exception.
+                // Otherwise, set the text track cue size to the new value.
+                assert_throws("IndexSizeError", function() { textCue.size = -200 });
+                assert_throws("IndexSizeError", function() { textCue.size = 110 });
+                textCue.size = 57;
+                assert_equals(textCue.size, 57);
+
+                // http://dev.w3.org/html5/webvtt/#dfn-dom-vttcue-align
+                // On setting, the text track cue text alignment must be
+                // set to the value given in the first cell of the row
+                // in the table above whose second cell is a case-sensitive
+                // match for the new value.
+                textCue.align = "End";
+                assert_equals(textCue.align, "center");
+                textCue.align = "end";
+                assert_equals(textCue.align, "end");
+            });
+        });
+    </script>
+</video>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html
new file mode 100644
index 0000000..e2f7890
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Enter, Exit events for a cue with negative duration</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+  <script>
+  async_test(function(t) {
+    var video = document.querySelector("video");
+    var track = video.addTextTrack("subtitles");
+
+    // Add a cue with negative duration.
+    var cue = new VTTCue(1, -10, "Sausage?");
+    track.addCue(cue);
+    assert_equals(track.cues.length, 1);
+
+    // Verify that enter and exit events are fired.
+    var enterEvent = false;
+    cue.onenter = t.step_func(function() {
+      enterEvent = true;
+    });
+    cue.onexit = t.step_func_done(function() {
+      assert_true(enterEvent);
+    });
+
+    video.src = getVideoURI("/media/test");
+    video.play();
+  });
+  </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html
new file mode 100644
index 0000000..ebd7877f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp-events.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Enter, Exit events for cues with negative timestamps</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+  <script>
+  async_test(function(t) {
+    var video = document.querySelector("video");
+    var track = video.addTextTrack("subtitles");
+
+    // Add cue with negative startTime.
+    var cue = new VTTCue(-10, 1, "Sausage?");
+    track.addCue(cue);
+    assert_equals(track.cues.length, 1);
+    cue.onenter = t.step_func(function() {
+      cue.onexit = t.step_func_done();
+    });
+
+    // Add cue with negative startTime and negative endTime.
+    // This cue should never be active.
+    var missedCue = new VTTCue(-110, -3.4, "Pepperoni?");
+    track.addCue(missedCue);
+    assert_equals(track.cues.length, 2);
+    missedCue.onenter = t.unreached_func();
+    missedCue.onexit = t.unreached_func();
+
+    video.src = getVideoURI("/media/test");
+    video.play();
+  });
+  </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp.html
new file mode 100644
index 0000000..5dc54ed
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-timestamp.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Negative timestamps</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/settings.vtt" default>
+    <script>
+    async_test(function(t) {
+        var testTrack = document.querySelector("track");
+
+        testTrack.onload = t.step_func_done(function() {
+            var cues = testTrack.track.cues;
+            assert_equals(testTrack.track.cues.length, 4);
+            // Add cue with negative startTime.
+            var cue = new VTTCue(-3439332606, 3.4, "Sausage?");
+            testTrack.track.addCue(cue);
+            assert_equals(cues.length, 5);
+
+            // Add cue with negative startTime and negative endTime.
+            cue = new VTTCue(-110, -3.4, "Pepperoni?");
+            testTrack.track.addCue(cue);
+            assert_equals(cues.length, 6);
+
+            // Set startTime and endTime to negative values.
+            var testCue = cues[2];
+            assert_equals(testCue.startTime, 0);
+            testCue.startTime = -5;
+            assert_equals(testCue.startTime, -5);
+            assert_equals(testCue.endTime, 30.5);
+            testCue.endTime = -3439332606;
+            assert_equals(testCue.endTime, -3439332606);
+
+            // Check negative cues ordering.
+            testCue = cues[3];
+            assert_equals(testCue.startTime, 31);
+            testCue.startTime = -200;
+            // Verify that this cue is moved to 2nd position.
+            assert_equals(cues[1].startTime, -200);
+        });
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added-ref.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added-ref.html
new file mode 100644
index 0000000..bd43c46
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added-ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<title>Text track cue layout after controls are added (reference)</title>
+<style>
+::cue {
+  font-size: 50px;
+}
+
+/* Video width should be large enough to display all of the media controls. */
+video {
+  border: 1px solid gray;
+  width: 500px;
+}
+</style>
+<video controls onloadeddata="this.onloadeddata = null; takeScreenshot();">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a single cue at line -2, where it would be if there were controls visible
+// at the bottom. (This assumes that those controls are less than 50px high.)
+// cue at line -1.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 1, "text");
+cue.line = -2;
+track.addCue(cue);
+track.mode = "showing";
+</script>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html
new file mode 100644
index 0000000..23c27e4
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-after-controls-added-ref.html">
+<title>Text track cue layout after controls are added</title>
+<style>
+::cue {
+  font-size: 50px;
+}
+</style>
+<!-- Width should be large enough to display all of the media controls. -->
+<video style="border:1px solid gray; width: 500px;">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a cue that will overlap with the video controls.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+track.addCue(new VTTCue(0, 1, "text"));
+track.mode = "showing";
+
+video.onloadeddata = function() {
+  // Double nesting of requestAnimationFrame to
+  // make sure cue layout and paint happens.
+  window.requestAnimationFrame(function() {
+    window.requestAnimationFrame(function() {
+      video.controls = true;
+      // Wait for the relayout before screenshot.
+      window.requestAnimationFrame(function() {
+        takeScreenshot();
+      });
+    });
+  });
+};
+</script>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed-ref.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed-ref.html
new file mode 100644
index 0000000..96afaef
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<title>Text track cue layout after controls are removed (reference)</title>
+<style>
+::cue {
+  font-size: 50px;
+}
+
+video {
+  border: 1px solid gray;
+}
+</style>
+<video onloadeddata="this.onloadeddata = null; takeScreenshot();">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a single cue at line -2, where it would be if there were controls visible
+// at the bottom. (This assumes that those controls are less than 50px high.)
+// cue at line -1.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 1, "text");
+cue.line = -2;
+track.addCue(cue);
+track.mode = "showing";
+</script>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html
new file mode 100644
index 0000000..76019c9
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-after-controls-removed-ref.html">
+<title>Text track cue layout after controls are removed</title>
+<style>
+::cue {
+  font-size: 50px;
+}
+</style>
+<video controls style="border:1px solid gray">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a cue that will overlap with the video controls.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+track.addCue(new VTTCue(0, 1, "text"));
+track.mode = "showing";
+
+video.onloadeddata = function() {
+  // Double nesting of requestAnimationFrame to
+  // make sure cue layout and paint happens.
+  window.requestAnimationFrame(function() {
+    window.requestAnimationFrame(function() {
+      // Remove the controls. The cue should not move.
+      video.controls = false;
+      takeScreenshot();
+    });
+  });
+};
+</script>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html
new file mode 100644
index 0000000..427189f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-empty-cue.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Empty cues</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var video = document.createElement("video");
+    video.src = getVideoURI("/media/test");
+    video.addTextTrack("captions", "regular captions track", "en");
+    video.textTracks[0].addCue(new VTTCue(0, 4, ""));
+
+    video.onplaying = t.step_func_done();
+    video.play();
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html
new file mode 100644
index 0000000..8354041
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.container {
+  position: relative;
+  display: inline-block;
+  width: 320px;
+  height: 240px;
+}
+.cue {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  overflow: hidden;
+}
+.cue > span {
+  font-family: sans-serif;
+  background: green;
+  color: green;
+  font-size: 120px;
+  padding: 2px;
+}
+</style>
+<div class="container">
+  <video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+    <source src="/media/white.webm" type="video/webm">
+    <source src="/media/white.mp4" type="video/mp4">
+  </video>
+  <div class="cue"><span>PAS</span></div>
+</div>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html
new file mode 100644
index 0000000..d3dcee1
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-line-doesnt-fit.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-line-doesnt-fit-ref.html">
+<script>
+function addCue(track, cueData) {
+  var cue = new VTTCue(0, 10, 'XXX');
+  for (var prop in cueData)
+    cue[prop] = cueData[prop];
+  track.addCue(cue);
+}
+</script>
+<style>
+video::cue {
+  font-size: 120px;
+  color: green;
+  background-color: green;
+}
+</style>
+<video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+  <script>
+  var video = document.querySelector("video");
+  var track = video.addTextTrack('subtitles');
+  addCue(track, { line: 0, align: 'start', text: 'PAS' });
+  // This cue will not fit, and will not be displayed.
+  addCue(track, { line: 1, align: 'start', text: 'FAI' });
+  track.mode = 'showing';
+  </script>
+</video>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html
new file mode 100644
index 0000000..3946135
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.container {
+  transform: translate(1px, 0px);
+  position: relative;
+  display: inline-block;
+  width: 320px;
+  height: 240px;
+}
+.cue {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  overflow: hidden;
+  text-align: start;
+}
+.cue > span {
+  font-family: sans-serif;
+  background: green;
+  color: green;
+  font-size: 50px;
+  padding: 2px;
+}
+</style>
+<div class="container">
+  <video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+    <source src="/media/white.webm" type="video/webm">
+    <source src="/media/white.mp4" type="video/mp4">
+  </video>
+  <div class="cue"><span>XXX</span></div>
+</div>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html
new file mode 100644
index 0000000..69ca92e
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-transformed-video.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/common/reftest-wait.js"></script>
+<link rel="match" href="track-cue-rendering-transformed-video-ref.html">
+<style>
+video {
+  transform: translate(1px, 0px);
+}
+video::cue {
+  font-size: 50px;
+  color: green;
+  background-color: green;
+}
+</style>
+<video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+  <script>
+  var video = document.querySelector('video');
+  var track = video.addTextTrack('subtitles');
+  var cue = new VTTCue(0, 10, 'XXX');
+  cue.align = 'start';
+  cue.line = 0;
+  track.addCue(cue);
+  track.mode = 'showing';
+  </script>
+</video>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html
new file mode 100644
index 0000000..b9c8412
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-cuechange.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>TextTrack's cues are indexed and updated in order during video playback</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/cues-chrono-order.vtt" kind="captions" default>
+    <script>
+    // Use the cuechange event on TextTrack.
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        var testTrack = document.querySelector("track");
+
+        video.src = getVideoURI("/media/test");
+        video.oncanplaythrough = t.step_func(attemptTests);
+
+        function attemptTests() {
+            assert_equals(testTrack.track.cues.length, 3);
+            testTrack.oncuechange = t.step_func(cueChangedFromTrackElement);
+            video.play();
+        }
+
+        var currentCueIndex;
+        var cueChangeCount = 0;
+        function cueChangedFromTrackElement() {
+            currentCueIndex = Math.floor(cueChangeCount / 2);
+            currentCue = event.target.track.cues[currentCueIndex];
+            if (cueChangeCount % 2 == 0) {
+                // Cue entered.
+                assert_equals(currentCue, testTrack.track.activeCues[0]);
+                assert_equals(currentCue.id, (currentCueIndex + 1).toString());
+            }
+
+            ++cueChangeCount;
+            if (cueChangeCount == testTrack.track.cues.length * 2)
+                t.done();
+        }
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-exit.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-exit.html
new file mode 100644
index 0000000..9706667
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-enter-exit.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>TextTrack's cues are indexed and updated in order during video playback</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/cues-chrono-order.vtt" kind="captions" default>
+    <script>
+    // Use the enter and exit events on TextTrackCue.
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        var testTrack = document.querySelector("track");
+
+        video.src = getVideoURI("/media/test");
+
+        video.oncanplaythrough = t.step_func(attemptTests);
+
+        function attemptTests() {
+            assert_equals(testTrack.track.cues.length, 3);
+            for (var i = 0; i < testTrack.track.cues.length; i++) {
+                testTrack.track.cues[i].onenter = t.step_func(cueEntered);
+                testTrack.track.cues[i].onexit = t.step_func(cueExited);
+            }
+            video.play();
+        }
+
+        var cueCount = 0;
+        function cueEntered() {
+            var currentCue = event.target;
+
+            // This cue is the currently active cue.
+            assert_equals(currentCue, testTrack.track.activeCues[0]);
+            assert_equals(currentCue.id, (cueCount + 1).toString());
+        }
+
+        function cueExited() {
+            ++cueCount;
+            if (cueCount == testTrack.track.cues.length)
+                t.done();
+        }
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cues-missed.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-missed.html
new file mode 100644
index 0000000..043c941
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-missed.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>Events are triggered for missed (skipped) cues during normal playback</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/missed-cues.vtt" default>
+    <script>
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        var testTrack = document.querySelector("track");
+
+        video.src = getVideoURI("/media/test");
+
+        video.onended = t.step_func_done();
+
+        video.oncanplaythrough = t.step_func(function() {
+            video.oncanplaythrough = null;
+            video.currentTime = 5.00;
+            runTests();
+        });
+
+        testTrack.onload = t.step_func(runTests);
+
+        var cueCount;
+        var eventCount = 0;
+        function runTests() {
+            eventCount++;
+
+            if(eventCount != 2)
+                return;
+
+            assert_equals(testTrack.track.cues.length, 7);
+
+            for (cueCount = 2; cueCount < testTrack.track.cues.length; cueCount++) {
+                var cue = testTrack.track.cues[cueCount];
+
+                cue.onenter = t.step_func(cueEnteredOrExited);
+                cue.onexit = t.step_func(cueEnteredOrExited);
+            }
+
+            // Test events for missed cues, which are cues with ids
+            // from 3 to 7 in the file resources/missed-cues.vtt.
+            cueCount = 3;
+            video.play();
+        }
+
+        function cueEnteredOrExited() {
+            var currentCue = event.target;
+            assert_equals(testTrack.track.cues.getCueById(cueCount).text, currentCue.text);
+            assert_equals(currentCue.id, cueCount.toString());
+
+            if (event.type == "exit")
+                cueCount++;
+        }
+
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html
new file mode 100644
index 0000000..eaf7e2a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-pause-on-exit.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Video is paused after cues having pause-on-exit flag are processed</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/simple-captions.vtt" default>
+    <script>
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        var track = document.querySelector("track");
+        track.onload = t.step_func(function() {
+            assert_equals(track.track.cues.length, 4);
+            for (var i = 0; i < track.track.cues.length; ++i) {
+                var cue = track.track.cues[i];
+                if (i % 2 == 0) {
+                    cue.pauseOnExit = true;
+                    cue.onexit = t.step_func(function(event) {
+                        assert_true(video.paused);
+
+                        video.play();
+
+                        if (event.target.id == 2)
+                            t.done();
+                    });
+                }
+            }
+            video.src = getVideoURI("/media/test");
+            video.currentTime = 4.00;
+            video.play();
+            assert_false(video.paused);
+        });
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cues-seeking.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-seeking.html
new file mode 100644
index 0000000..99cd2d5
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-seeking.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>TextTrack's activeCues are indexed and updated during video playback</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/cues-overlapping.vtt" kind="subtitles" default>
+    <script>
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        var track = document.querySelector("track");
+        track.onload = t.step_func(function() {
+            assert_equals(track.track.cues.length, 3);
+            video.src = getVideoURI("/media/test");
+            video.currentTime = 0.5;
+        });
+
+        var seekedCount = 0;
+        video.onseeked = t.step_func(function() {
+            ++seekedCount;
+
+            assert_equals(video.currentTime, seekedCount * 0.5);
+            assert_equals(track.track.activeCues.length, seekedCount - 1);
+            video.currentTime = (seekedCount + 1) * 0.5;
+
+            if (seekedCount == 4)
+                t.done();
+        });
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html
new file mode 100644
index 0000000..edc202f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-cues-sorted-before-dispatch.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>All events are triggered in chronological order</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/sorted-dispatch.vtt" default>
+    <script>
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        video.src = getVideoURI("/media/test");
+        var track = document.querySelector("track");
+
+        track.onload = t.step_func(function() {
+            var cues = track.track.cues;
+            assert_equals(cues.length, 8);
+
+            for (var i = 0; i < cues.length; ++i) {
+                cues[i].onenter = t.step_func(cueEnteredOrExited);
+                cues[i].onexit = t.step_func(cueEnteredOrExited);
+            }
+
+            video.play();
+        });
+
+        var cueTimings = [];
+        function cueEnteredOrExited(event) {
+            var currentCue = event.target;
+
+            if (event.type == "exit")
+                cueTimings.push(currentCue.endTime);
+            else
+                cueTimings.push(currentCue.startTime);
+        }
+
+        video.onended = t.step_func_done(function() {
+            assert_equals(cueTimings.length, 14);
+            var time = 0;
+            for (var i = 0; i < cueTimings.length; ++i) {
+                assert_less_than_equal(time, cueTimings[i], "cueTimings[" + i + "]");
+                time = cueTimings[i];
+            }
+        });
+
+        video.currentTime = 5;
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-default-attribute.html b/html/semantics/embedded-content/media-elements/track/track-element/track-default-attribute.html
new file mode 100644
index 0000000..3e8c547
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-default-attribute.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>A track with the "default" attribute loads automatically</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+  <track kind="captions" src="resources/default-styles.vtt">
+  <track kind="captions" src="resources/metadata-area.vtt">
+  <track kind="captions" src="resources/webvtt-file.vtt" id="default" default>
+  <script>
+  async_test(function(t) {
+    var timer = null;
+    var tracks = document.querySelectorAll("track");
+    for (var track of tracks) {
+      track.onload = t.step_func(function() {
+        assert_equals(event.target.readyState, HTMLTrackElement.LOADED);
+        assert_equals(event.target.id, "default");
+        assert_true(event.target.default);
+        // End the test after a brief pause so we allow other tracks to load if they will.
+        if (timer)
+          clearTimeout(timer);
+        timer = t.step_timeout(t.step_func_done(), 200);
+      });
+    }
+  });
+  </script>
+</video>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-delete-during-setup.html b/html/semantics/embedded-content/media-elements/track/track-element/track-delete-during-setup.html
new file mode 100644
index 0000000..ce9f733
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-delete-during-setup.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Track deletion during setup</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/metadata.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    var track = document.querySelector("track");
+    t.step_timeout(function() {
+        video.parentNode.removeChild(video);
+    }, 61);
+
+    track.onload = t.step_func(function() {
+        var track2 = document.createElement("track");
+        video.appendChild(track2);
+        t.step_timeout(t.step_func_done(), 100);
+    });
+
+    assert_equals(track.readyState, HTMLTrackElement.NONE);
+    assert_equals(track.track.mode, "disabled");
+    track.track.mode = "hidden";
+
+    video.src = getVideoURI("/media/test");
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html b/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html
new file mode 100644
index 0000000..038e6f6
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-disabled-addcue.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Adding cues to a disabled text track</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var cueDuration = 0.1;
+    var video = document.createElement("video");
+    var track = video.addTextTrack("subtitles");
+    track.mode = "disabled";
+
+    for (var i = 0; i < 10; ++i) {
+        var start = i * cueDuration;
+        var end = start + cueDuration;
+        track.addCue(new VTTCue(start, end, "Test Cue " + i));
+    }
+
+    // Waiting for 2 cue durations to elapse.
+    video.ontimeupdate = t.step_func(function(event) {
+        if (event.target.currentTime < (2 * cueDuration))
+            return;
+
+        // End test after at least 2 cueDurations to make sure the test
+        // would have gone through the period where the first 2 cues would
+        // have been rendered if the track was not disabled.
+        t.done();
+    });
+
+    video.src = getVideoURI("/media/test");
+    video.play();
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html b/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html
new file mode 100644
index 0000000..d517b9d
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-disabled.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Disabling a track</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track kind="subtitles" src="resources/captions.vtt"/>
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    video.textTracks[0].mode = "disabled";
+
+    // Waiting for the duration of the first cue to elapse.
+    video.ontimeupdate = t.step_func(function (event) {
+        if (event.target.currentTime < 1)
+            return;
+
+        // End test after the duration of the first cue to make sure
+        // the test would have gone through the period where this cue
+        // would have been rendered if the track was not disabled.
+        t.done();
+    });
+
+    video.src = getVideoURI("/media/test");
+    video.play();
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html b/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html
new file mode 100644
index 0000000..ff447f3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-element-dom-change.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Simple DOM mutations with track element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+    var video = document.createElement("video");
+    var testTrack = document.createElement("track");
+
+    // Append the track element to the video element.
+    video.appendChild(testTrack);
+
+    // Set the mode of the text track to "showing".
+    testTrack.track.mode = "showing";
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html b/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html
new file mode 100644
index 0000000..ffc8ec0
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change-error.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/settings.vtt" default>
+    <script>
+    async_test(function(t) {
+        var cues = null;
+        var testTrack = document.querySelector("track");
+        var stage = 0;
+        var timer = null;
+        function step_onLoad() {
+            switch (stage) {
+                case 0:
+                    cues = testTrack.track.cues;
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
+                    assert_equals(cues.length, 4, "Number of cues after first loading of the track");
+                    ++stage;
+                    testTrack.src = "resources/non-existing-file.vtt"; // this should fail
+                    break;
+                case 1:
+                case 3:
+                case 5:
+                    assert_unreached("'error' event did not fire, stage = " + stage);
+                    break;
+                case 2:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after loading of the second track");
+                    assert_equals(cues.length, 4, "Number of cues after loading of the second track");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    ++stage;
+                    testTrack.src = ""; // this should fail
+                    // CuesList will be cleared in the next tick. Spec claims that this should happen immediately,
+                    // but all implementations are doing this asynchronously.
+                    assert_equals(cues.length, 4, "Number of cues immediately after 'src' mutation with the empty URL");
+                    // This should raise onError event. If no, we'll know about this after some time.
+                    timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
+                    break;
+                case 4:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after loading of the second track");
+                    assert_equals(cues.length, 4, "Number of cues after loading of the second track");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    ++stage;
+                    testTrack.removeAttribute('src');
+                    // This should raise onError event, so we'll wait for it for some time
+                    timer = t.step_timeout(t.unreached_func("'error' event is not fired when an empty URL is set"), 100);
+                    break;
+                default:
+                    assert_unreached("unexpected stage number = " + stage);
+                    break;
+            }
+        }
+
+        function step_onError() {
+            switch (stage) {
+                case 0:
+                case 2:
+                case 4:
+                    assert_unreached("'error' event fired, stage = " + stage);
+                    break;
+                case 1:
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 0, "Number of cues after trying to load non-existing url");
+                    assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after trying to load non-existing url");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    break;
+                case 3:
+                    clearTimeout(timer);
+                    assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after setting an empty URL");
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 0, "Number of cues with an empty URL set");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    break;
+                case 5:
+                    clearTimeout(timer);
+                    assert_equals(testTrack.readyState, HTMLTrackElement.ERROR, "readyState after removing 'src' attr");
+                    assert_equals(cues.length, 0, "Number of cues after removing 'src' attr");
+                    t.done();
+                    break;
+                default:
+                    assert_unreached("unexpected stage number = " + stage);
+                    break;
+            }
+        }
+
+        testTrack.onload = t.step_func(step_onLoad);
+        testTrack.onerror = t.step_func(step_onError);
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html b/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html
new file mode 100644
index 0000000..34a53d1
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-element-src-change.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>HTMLTrackElement 'src' attribute mutations</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/settings.vtt" default>
+    <script>
+    async_test(function(t) {
+        var cues = null;
+        var testTrack = document.querySelector("track");
+        var stage = 0;
+        function step_onLoad() {
+            switch (stage) {
+                case 0:
+                    cues = testTrack.track.cues;
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after first loading of the track");
+                    assert_equals(cues.length, 4, "Number of cues after first loading of the track");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    ++stage;
+                    testTrack.src = "resources/entities.vtt";
+                    // CuesList will be cleared in a microtask. Spec claims that this should happen immediately,
+                    // but all known implementations are doing this asynchronously.
+                    assert_equals(cues.length, 4, "Number of cues immediately after 'src' mutation with the new URL");
+                    break;
+                case 1:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED), "readyState after loading of the second track";
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 7, "Number of cues after loading of the second track");
+                    assert_equals(cues[cues.length-1].text, 'This & is parsed to the same as &amp;.', "Last cue content check");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    break;
+                case 2:
+                    assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after after loading of the first track again");
+                    assert_equals(cues[cues.length-1].text, 'I said Bear is coming now!!!! Tab separators.', "Last cue content check");
+                    assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                    assert_equals(cues.length, 4, "Number of cues after loading of the first track");
+                    ++stage;
+                    testTrack.src = "resources/settings.vtt";
+                    // This should not raise onLoad or onError event, so we'll wait for it for some time
+                    t.step_timeout(t.step_func_done(function() {
+                        assert_equals(testTrack.readyState, HTMLTrackElement.LOADED, "readyState after changing 'src' to the same value");
+                        assert_equals(cues, testTrack.track.cues, ".cues object are the same after 'src' attr mutation");
+                        assert_equals(cues.length, 4, "Number of cues after changing 'src' to the same value");
+                    }, 100));
+                    break;
+                case 3:
+                    assert_unreached("'load' event should not fire, stage = " + stage);
+                    break;
+            }
+        }
+
+        testTrack.onload = t.step_func(step_onLoad);
+        testTrack.onerror = t.unreached_func("'error' event should not fire");
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-helpers.js b/html/semantics/embedded-content/media-elements/track/track-element/track-helpers.js
new file mode 100644
index 0000000..09c85dd
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-helpers.js
@@ -0,0 +1,83 @@
+function enableAllTextTracks(textTracks) {
+    for (var i = 0; i < textTracks.length; i++) {
+        var track = textTracks[i];
+        if (track.mode == "disabled")
+            track.mode = "hidden";
+    }
+}
+
+function assert_cues_equal(cues, expected) {
+    assert_equals(cues.length, expected.length);
+    for (var i = 0; i < cues.length; i++) {
+        assert_equals(cues[i].id, expected[i].id);
+        assert_equals(cues[i].startTime, expected[i].startTime);
+        assert_equals(cues[i].endTime, expected[i].endTime);
+        assert_equals(cues[i].text, expected[i].text);
+    }
+}
+
+function assert_cues_match(cues, expected) {
+    assert_equals(cues.length, expected.length);
+    for (var i = 0; i < cues.length; i++) {
+        var cue = cues[i];
+        var expectedItem = expected[i];
+        for (var property of Object.getOwnPropertyNames(expectedItem))
+            assert_equals(cue[property], expectedItem[property]);
+    }
+}
+
+function assert_cues_html_content(cues, expected) {
+    assert_equals(cues.length, expected.length);
+    for (var i = 0; i < cues.length; i++) {
+        var expectedItem = expected[i];
+        var property = Object.getOwnPropertyNames(expectedItem)[0];
+        var propertyValue = expectedItem[property];
+        assert_equals(propertyValue(cues[i]), expectedItem.expected);
+    }
+}
+
+function check_cues_from_track(src, func) {
+    async_test(function(t) {
+        var video = document.createElement("video");
+        var trackElement = document.createElement("track");
+        trackElement.src = src;
+        trackElement.default = true;
+        video.appendChild(trackElement);
+
+        trackElement.onload = t.step_func_done(function() {
+            func(trackElement.track);
+        });
+    }, "Check cues from " + src);
+}
+
+function assert_cue_fragment(cue, children) {
+    var fragment = createFragment(children);
+    assert_true(fragment.isEqualNode(cue.getCueAsHTML()));
+}
+
+function assert_cue_fragment_as_textcontent(cue, children) {
+    var fragment = createFragment(children);
+    assert_equals(cue.getCueAsHTML().textContent, fragment.textContent);
+}
+
+function createFragment(children) {
+    var fragment = document.createDocumentFragment();
+    cloneChildrenToFragment(fragment, children);
+    return fragment;
+}
+
+function cloneChildrenToFragment(root, children) {
+    for (var child of children) {
+        var childElement;
+        if (child.type == "text") {
+            childElement = document.createTextNode(child.value);
+        } else {
+            childElement = document.createElement(child.type);
+            var styles = child.style || {};
+            for (var attr of Object.getOwnPropertyNames(styles))
+                childElement[attr] = styles[attr];
+            cloneChildrenToFragment(childElement, child.value);
+        }
+        root.appendChild(childElement);
+    }
+}
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-id.html b/html/semantics/embedded-content/media-elements/track/track-element/track-id.html
new file mode 100644
index 0000000..f0223fd
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-id.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>TextTrack "id" attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track id="LoremIpsum" src="resources/captions-fast.vtt" default>
+    <script>
+    test(function() {
+        var video = document.querySelector("video");
+        var track = document.querySelector("track");
+        var textTrack = track.track;
+
+        // Test default attribute value.
+        assert_equals(textTrack.id, "LoremIpsum");
+        assert_equals(video.textTracks[0].id, "LoremIpsum");
+
+        // Make sure we can look up tracks by id.
+        assert_equals(video.textTracks.getTrackById("LoremIpsum"), textTrack);
+
+        // Test that it's readonly.
+        textTrack.id = "newvalue";
+        assert_equals(textTrack.id, "LoremIpsum");
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-insert-after-load.html b/html/semantics/embedded-content/media-elements/track/track-element/track-insert-after-load.html
new file mode 100644
index 0000000..28b4f82
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-insert-after-load.html
@@ -0,0 +1,14 @@
+<!DOCTYPE  html>
+<title>Inserting a track element immediately after video load</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(function(t) {
+    var video = document.createElement('video');
+    video.src = getVideoURI('/media/test');
+    video.load();
+    video.appendChild(document.createElement('track'));
+    video.onloadedmetadata = t.step_func_done();
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-large-timestamp.html b/html/semantics/embedded-content/media-elements/track/track-element/track-large-timestamp.html
new file mode 100644
index 0000000..bae1852
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-large-timestamp.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Very large timestamp is parsed correctly</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/large-timestamp.vtt" default>
+    <script>
+    async_test(function(t) {
+        var testTrack = document.querySelector("track");
+        testTrack.onload = t.step_func_done(function() {
+            assert_equals(testTrack.track.cues.length, 1);
+            var cue = testTrack.track.cues[0];
+            assert_equals(parseInt(cue.id), 1);
+            assert_equals(cue.startTime / 3600, 1234567);
+            assert_equals(cue.endTime / 3600, 1234567890);
+        });
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-load-error-readyState.html b/html/semantics/embedded-content/media-elements/track/track-element/track-load-error-readyState.html
new file mode 100644
index 0000000..8e232bf
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-load-error-readyState.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Error event on HTMLTrackElement and ERROR readyState on TextTrack</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+  <track src="junk" default>
+  <script>
+  async_test(function(t) {
+    var track = document.querySelector("track");
+    track.onerror = t.step_func_done(function() {
+      assert_equals(track.readyState, HTMLTrackElement.ERROR);
+    });
+  });
+  </script>
+</video>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-element-readyState.html b/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-element-readyState.html
new file mode 100644
index 0000000..62a68f6
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-element-readyState.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Load event on HTMLTrackElement and LOADED readyState on TextTrack when src is set on the element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+  <track src="resources/webvtt-file.vtt" default>
+  <script>
+  async_test(function(t) {
+    var track = document.querySelector("track");
+    track.onload = t.step_func_done(function() {
+      assert_equals(track.readyState, HTMLTrackElement.LOADED);
+    });
+  });
+  </script>
+</video>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-src-readyState.html b/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-src-readyState.html
new file mode 100644
index 0000000..e569eeb
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-load-from-src-readyState.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Load event on HTMLTrackElement and LOADED readyState on TextTrack when src is set from JavaScript</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track>
+</video>
+<script>
+async_test(function(t) {
+    var track = document.querySelector("track");
+    assert_equals(track.readyState, HTMLTrackElement.NONE);
+
+    track.onload = t.step_func_done(function() {
+        assert_equals(track.readyState, HTMLTrackElement.LOADED);
+    });
+
+    track.src = "resources/webvtt-file.vtt";
+    track.track.mode = "hidden";
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-mode-disabled.html b/html/semantics/embedded-content/media-elements/track/track-element/track-mode-disabled.html
new file mode 100644
index 0000000..6b46bf4
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-mode-disabled.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Cues are properly removed from the active cue list when their track changes mode to disabled</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/captions-gaps.vtt" kind="captions" default >
+    <script>
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        var testTrack = document.querySelector("track");
+
+        video.src = getVideoURI("/media/counting");
+        video.oncanplaythrough = t.step_func(startTest);
+        video.onseeked = t.step_func_done(seeked);
+
+        function startTest() {
+            // Set the mode of the text track to "showing".
+            testTrack.track.mode = "showing";
+            // Seek to a time with a caption.
+            video.currentTime = 1.5;
+        }
+
+        function seeked() {
+            // Set the mode of the text track to "hidden", then to "showing" again.
+            testTrack.track.mode = "hidden";
+            testTrack.track.mode = "showing";
+
+            // Set the mode of the text track to "disabled".
+            testTrack.track.mode = "disabled";
+        }
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html b/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html
new file mode 100644
index 0000000..2902ba9
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<title>A track appended after the initial track configuration does not change other tracks</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track kind="metadata" src="resources/metadata.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector('video');
+
+    var track1 = document.querySelectorAll('track')[0];
+    assert_equals(track1.readyState, HTMLTrackElement.NONE);
+    assert_equals(track1.track.mode, 'disabled');
+
+    video.src = getVideoURI('/media/test');
+    video.oncanplaythrough = t.step_func(canplaythrough);
+    track1.onload = t.step_func(metadataTrackLoaded);
+
+    function canplaythrough() {
+        // check initial metadata track state.
+        assert_equals(track1.readyState, HTMLTrackElement.NONE);
+        assert_equals(track1.track.mode, 'disabled');
+        assert_equals(track1.track.cues, null);
+        track1.track.mode = 'hidden';
+    }
+
+    function metadataTrackLoaded() {
+        // check metadata track state.
+        assert_equals(track1.readyState, HTMLTrackElement.LOADED);
+        assert_equals(track1.track.mode, 'hidden');
+        assert_equals(track1.track.cues.length, 12);
+        assert_equals(track1.track.cues[11].startTime, 22);
+
+        // Add a caption track, configured to load automatically.
+        track2 = document.createElement('track');
+        track2.setAttribute('kind', 'captions');
+        track2.setAttribute('default', 'default');
+        track2.setAttribute('src', 'resources/webvtt-file.vtt');
+        track2.onload = t.step_func(captionsTrackLoaded);
+        video.appendChild(track2);
+    }
+
+    function captionsTrackLoaded() {
+        // Check that metadata track state has not changed.
+        assert_equals(track1.readyState, HTMLTrackElement.LOADED);
+        assert_equals(track1.track.mode, 'hidden');
+        // and that the caption track state is correct.
+        assert_equals(track2.readyState, HTMLTrackElement.LOADED);
+        assert_equals(track2.track.mode, 'showing');
+
+        video.textTracks.onaddtrack = t.step_func_done(trackAdded);
+        // add a subtitle track with video.addTextTrack().
+        track3 = video.addTextTrack('subtitles', 'Subtitle Track', 'en');
+        track3.mode = 'showing';
+    }
+
+    function trackAdded() {
+        // Check that metadata track state has not changed.
+        assert_equals(track1.readyState, HTMLTrackElement.LOADED);
+        assert_equals(track1.track.mode, 'hidden');
+        // and that the caption track state has not changed.
+        assert_equals(track2.readyState, HTMLTrackElement.LOADED);
+        assert_equals(track2.track.mode, 'showing');
+        // and that the subtitle track state is correct.
+        assert_equals(event.target, video.textTracks);
+        assert_true(event instanceof window.TrackEvent);
+        assert_equals(event.track, video.textTracks[video.textTracks.length - 1]);
+        assert_equals(track3.mode, 'showing');
+    }
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-mode-triggers-loading.html b/html/semantics/embedded-content/media-elements/track/track-element/track-mode-triggers-loading.html
new file mode 100644
index 0000000..2e29d70
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-mode-triggers-loading.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>A "metadata" track does not load automatically, but it does load when the mode is changed</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track kind="metadata" src="resources/metadata.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    // Check initial metadata track state.
+    var track = document.querySelectorAll("track")[0];
+    assert_equals(track.readyState, HTMLTrackElement.NONE);
+    assert_equals(video.textTracks[0].mode, "disabled");
+
+    video.src = getVideoURI("/media/test");
+    video.oncanplaythrough = t.step_func(canplaythrough);
+    track.onload = t.step_func_done(trackLoaded);
+
+    function trackLoaded() {
+        assert_equals(track.readyState, HTMLTrackElement.LOADED);
+        assert_equals(track.track.mode, "hidden");
+        assert_equals(video.textTracks[0].cues.length, 12);
+        assert_equals(video.textTracks[0].cues[11].startTime, 22);
+    }
+
+    function canplaythrough() {
+        assert_equals(track.readyState, HTMLTrackElement.NONE);
+        assert_equals(video.textTracks[0].mode, "disabled");
+        assert_equals(video.textTracks[0].cues, null);
+        // Change metadata track mode so it loads.
+        video.textTracks[0].mode = "hidden";
+    }
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html b/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html
new file mode 100644
index 0000000..29208a3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>TextTrack mode attribute</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/captions-fast.vtt" default>
+    <script>
+    async_test(function(t) {
+        var video = document.querySelector("video");
+        var track = document.querySelector("track");
+        track.onload = t.step_func(trackLoaded);
+
+        var cueCount = 0;
+        var textTrack;
+        function trackLoaded() {
+            textTrack = track.track;
+            // Test default attribute value.
+            assert_equals(textTrack.mode, "showing");
+            assert_equals(video.textTracks[0].mode, "showing");
+            // Set to bogus value, should return default.
+            var value = "bogus";
+            textTrack.mode = value;
+            assert_equals(textTrack.mode, "showing");
+            assert_equals(video.textTracks[0].mode, "showing");
+
+            // Set to numeric value (no longer supported), should return default.
+            textTrack.mode = 2;
+            assert_equals(textTrack.mode, "showing");
+            assert_equals(video.textTracks[0].mode, "showing");
+
+            // Set to known values.
+            setModeAndCheck("disabled");
+
+            video.src = getVideoURI("/media/test");
+            video.play();
+            // Wait for end of first cue (no events should fire while track is disabled).
+            t.step_timeout(testHiddenAndShowing, 400);
+        }
+
+        track.oncuechange = t.step_func(function(event) {
+            cueCount++;
+            if (cueCount == textTrack.cues.length)
+                t.done();
+        });
+
+        function setModeAndCheck(value) {
+            textTrack.mode =  value;
+            assert_equals(textTrack.mode, value);
+            assert_equals(video.textTracks[0].mode, value);
+            if (value == "disabled")
+                assert_equals(textTrack.cues, null);
+        }
+
+        function testHiddenAndShowing() {
+            setModeAndCheck("hidden");
+            setModeAndCheck("showing");
+        }
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-node-add-remove.html b/html/semantics/embedded-content/media-elements/track/track-element/track-node-add-remove.html
new file mode 100644
index 0000000..2708879
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-node-add-remove.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Add and remove track node</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+test(function() {
+  var video = document.createElement('video');
+  var tracka = document.createElement('track');
+  video.appendChild(tracka);
+  var trackb = document.createElement('track');
+  video.appendChild(trackb);
+
+  // Adding tracks outside the DOM tree.
+  assert_array_equals(video.textTracks, [tracka.track, trackb.track]);
+
+  // Inserting the parent video element into the document.
+  document.body.appendChild(video);
+  assert_array_equals(video.textTracks, [tracka.track, trackb.track]);
+
+  // Inserting and removing another track in the document.
+  var trackc = document.createElement('track');
+  video.appendChild(trackc);
+  assert_array_equals(video.textTracks, [tracka.track, trackb.track, trackc.track]);
+
+  trackb.parentNode.removeChild(trackb);
+  assert_array_equals(video.textTracks, [tracka.track, trackc.track]);
+
+  // Removing the video from the document.
+  document.body.removeChild(video);
+  assert_array_equals(video.textTracks, [tracka.track, trackc.track]);
+
+  tracka.parentNode.removeChild(tracka);
+  assert_array_equals(video.textTracks, [trackc.track]);
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-remove-active-cue.html b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-active-cue.html
new file mode 100644
index 0000000..176e006
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-active-cue.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Removing an active cue</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video></video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+    video.src = getVideoURI("/media/test");
+
+    // Add a text track to the video element.
+    video.addTextTrack("captions", "regular captions track", "en");
+
+    // Add a cue to the track with enter event listener.
+    var cue = new VTTCue(0, 4, "Random");
+    cue.onenter = t.step_func_done(removeActiveCue);
+
+    var track = video.textTracks[0];
+    track.addCue(cue);
+
+    function removeActiveCue() {
+        assert_equals(track.activeCues.length, 1);
+
+        // Remove the cue while it is active.
+        track.removeCue(track.activeCues[0]);
+
+        // No crash. PASS.
+    }
+
+    // Play the video and remove cue when it becomes active.
+    video.play();
+    track.mode = "showing";
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-remove-by-setting-innerHTML.html b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-by-setting-innerHTML.html
new file mode 100644
index 0000000..95929bc
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-by-setting-innerHTML.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Removing a track by setting video.innerHTML doesn't crash</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track default src="resources/captions-gaps.vtt">
+    <script>
+        // https://bugs.webkit.org/show_bug.cgi?id=100981
+        async_test(function(t) {
+            var firstSeek = true;
+            var video = document.querySelector('video');
+            video.onseeked = t.step_func(function() {
+                if (!firstSeek) {
+                    t.done();
+                    return;
+                }
+
+                // Remove the text track
+                video.innerHTML = '';
+
+                // Seek again to force a repaint.
+                video.currentTime = 7.9;
+                firstSeek = false;
+            });
+
+            video.currentTime = 0.5;
+            video.src = getVideoURI('/media/counting');
+        });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-remove-insert-ready-state.html b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-insert-ready-state.html
new file mode 100644
index 0000000..1c854ac
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-insert-ready-state.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Attaching a media element again to the document, having a child track that failed loading doesn't block video from playing</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/no-webvtt.vtt" kind="captions" default>
+    <script>
+    async_test(function(t) {
+        var video = document.querySelector('video');
+        video.src = getVideoURI('/media/test');
+        video.oncanplaythrough = t.step_func(canplaythrough);
+
+        function canplaythrough() {
+            video.oncanplaythrough = null;
+            var track = document.querySelector('track');
+
+            // Track should have error as ready state.
+            assert_equals(track.readyState, HTMLTrackElement.ERROR);
+
+            // Remove the video element from body.
+            document.body.removeChild(video);
+
+            // Reset the video src attribute to re-trigger resource selection for tracks.
+            video.src = getVideoURI('/media/test');
+
+            // Append the video element back to the body.
+            document.body.appendChild(video);
+
+            assert_equals(track.readyState, HTMLTrackElement.ERROR);
+
+            video.onplaying = t.step_func_done();
+            video.play();
+            // The video should start playing.
+        }
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-remove-quickly.html b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-quickly.html
new file mode 100644
index 0000000..4be040c
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-quickly.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Removing a track element before it has been processed doesn't crash</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="video_container"></div>
+<script>
+var mediaFile = getVideoURI("/media/test");
+document.getElementById("video_container").innerHTML = "<video src='" + mediaFile + "' controls ><track kind='captions' src='resources/simple-captions.vtt' default ></video>";
+test(function() {
+// https://bugs.webkit.org/show_bug.cgi?id=85095
+// Test passes if it doesn't crash.
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track.html b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track.html
new file mode 100644
index 0000000..29938e3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-remove-track.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+        <script src="/common/media.js"></script>
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+    </head>
+    <body>
+        <script>
+            async_test(function(test)
+            {
+                var video = document.createElement("video");
+                var track;
+
+                function trackRemoved()
+                {
+                    assert_equals(event.target, video.textTracks);
+                    assert_equals(event instanceof window.TrackEvent, true);
+                    assert_equals(event.track, track);
+                    test.done();
+                }
+
+                var trackElement = document.createElement('track');
+                video.appendChild(trackElement);
+
+                trackElement.src = 'resources/webvtt-file.vtt';
+                trackElement.track.mode = 'hidden';
+
+                assert_equals(video.textTracks.length, 1);
+
+                track = video.textTracks[0];
+                video.removeChild(trackElement);
+                video.textTracks.addEventListener("removetrack", test.step_func(trackRemoved));
+            }, "Tests that the 'removetrack' event is fired when an out-of-band TextTrack is removed.");
+
+            async_test(function(test)
+            {
+                var video = document.createElement("video");
+
+                // Create an out-of-band text track by adding a track element.
+                var trackElement = document.createElement('track');
+
+                trackElement.addEventListener("error", test.step_func(function()
+                {
+                    assert_unreached("'error' event on track element should not fire.")
+                }));
+
+                video.appendChild(trackElement);
+                trackElement.src = 'resources/webvtt-file.vtt';
+                trackElement.track.mode = 'hidden';
+
+                assert_equals(video.textTracks.length, 1);
+                var outOfBandTrack = video.textTracks[0];
+
+                // Load a media file with an inband text track.
+                var inbandTrack = null;
+                var url = "resources/vp8-vorbis-webvtt.webm"
+
+                var firstAddTrackHandler = test.step_func(function()
+                {
+                    assert_equals(event.target, video.textTracks);
+                    assert_equals(event instanceof window.TrackEvent, true);
+                    if (event.track == outOfBandTrack) {
+                        return;
+                    }
+
+                    assert_equals(inbandTrack, null);
+                    assert_equals(video.textTracks.length, 2);
+                    assert_equals(event.track, video.textTracks[1]);
+                    inbandTrack = event.track;
+
+                    video.textTracks.removeEventListener("addtrack", firstAddTrackHandler);
+
+                    // Clear .src to force the inband track to get destroyed.
+                    video.src = "";
+
+                    // Verify that the inband track was removed.
+                    assert_not_equals(inbandTrack, null);
+                    assert_equals(video.textTracks.length, 1);
+                    assert_equals(video.textTracks[0], outOfBandTrack);
+
+                    // Load the URL again to trigger another 'addtrack' event to make sure
+                    // no 'removetrack' event was queued.
+                    video.src = url;
+                    video.textTracks.addEventListener("addtrack", test.step_func(function()
+                    {
+                        assert_equals(video.textTracks.length, 2);
+                        test.done();
+                    }));
+                });
+                video.textTracks.addEventListener("addtrack", firstAddTrackHandler);
+
+                video.textTracks.addEventListener("removetrack", test.step_func(function()
+                {
+                    assert_unreached("'removetrack' event should not fire.")
+                }));
+
+                video.src = url;
+            }, "Tests that the 'removetrack' event is NOT fired for inband TextTrack on a failed load.");
+
+        </script>
+    </body>
+</html>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-selection-metadata.html b/html/semantics/embedded-content/media-elements/track/track-element/track-selection-metadata.html
new file mode 100644
index 0000000..1f7df3b
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-selection-metadata.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Multiple 'metadata' tracks with 'default'</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track kind="metadata" src="resources/default-styles.vtt" id="t1">
+    <track kind="metadata" src="resources/class.vtt" default id="t2hidden">
+    <track kind="metadata" src="resources/metadata-area.vtt" id="t3">
+    <track kind="metadata" src="resources/webvtt-file.vtt" default id="t4hidden">
+    <script>
+    async_test(function() {
+        var video = document.querySelector('video');
+        video.onloadstart = this.step_func_done(function() {
+            assert_equals(video.textTracks.length, 4);
+            for (var track of video.textTracks) {
+                assert_equals(track.kind, 'metadata');
+
+                var trackElement = document.getElementById(track.id);
+                if (track.id.indexOf('hidden') != -1) {
+                    assert_true(trackElement.default);
+                    assert_equals(track.mode, 'hidden');
+                } else {
+                    assert_false(trackElement.default);
+                    assert_equals(track.mode, 'disabled');
+                }
+            }
+        });
+
+        video.src = getVideoURI("/media/test");
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-text-track-cue-list.html b/html/semantics/embedded-content/media-elements/track/track-element/track-text-track-cue-list.html
new file mode 100644
index 0000000..5b11bfd
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-text-track-cue-list.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>TextTrackCueList functionality: length, operator[], and getCueById()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/settings.vtt" kind="captions" default>
+    <script>
+    async_test(function(t) {
+        var testTrack = document.querySelector("track");
+
+        testTrack.onload = t.step_func_done(function() {
+            var cues = testTrack.track.cues;
+
+            // Testing TextTrackCueList length.
+            assert_equals(cues.length, 4);
+
+            // Testing TextTrackCueList [] operator.
+            assert_equals(cues[0].id, "1");
+            assert_equals(cues[3].id, "4");
+            assert_object_equals(cues[4], undefined);
+
+            // Testing TextTrackCueList getCueById().
+            assert_equals(cues.getCueById("1").startTime, 0);
+            assert_equals(cues.getCueById("4").startTime, 121);
+            assert_object_equals(cues.getCueById("junk"), undefined);
+        });
+    });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-texttracks.html b/html/semantics/embedded-content/media-elements/track/track-element/track-texttracks.html
new file mode 100644
index 0000000..4d006fc
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-texttracks.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>TextTracks in a TextTrackList are kept in the correct order</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track kind="captions" src="resources/webvtt-file.vtt">
+</video>
+<script>
+test(function() {
+    var video = document.querySelector("video");
+
+    // Add a track with video.addTextTrack().
+    video.addTextTrack("descriptions", "Descriptions Track", "en");
+
+    // Add a track element with DOM API.
+    var trackElement = document.createElement("track");
+    trackElement.setAttribute("kind", "chapters");
+    video.appendChild(trackElement);
+
+    // Verify track order.
+    assert_equals(video.textTracks.length, 3);
+    assert_equals(video.textTracks[0].kind, "captions");
+    assert_equals(video.textTracks[1].kind, "chapters");
+    assert_equals(video.textTracks[2].kind, "descriptions");
+
+    // Verify the default parameters of the text track object
+    // returned by addTextTrack().
+    assert_equals(video.textTracks[2].mode, "hidden");
+    assert_not_equals(video.textTracks[2].cues, null);
+    assert_equals(video.textTracks[2].cues.length, 0);
+
+    // Add another track element, it should insert
+    // before the addTextTrack() track.
+    trackElement = document.createElement("track");
+    trackElement.setAttribute("kind", "metadata");
+    video.appendChild(trackElement);
+
+    assert_equals(video.textTracks.length, 4);
+    assert_equals(video.textTracks[0].kind, "captions");
+    assert_equals(video.textTracks[1].kind, "chapters");
+    assert_equals(video.textTracks[2].kind, "metadata");
+    assert_equals(video.textTracks[3].kind, "descriptions");
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-positioning.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-positioning.html
new file mode 100644
index 0000000..07ebfd6
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-positioning.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Cue text position and alignment from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/align-positioning.vtt">
+    <track src="resources/align-positioning-bad.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    assert_equals(trackElements.length, video.textTracks.length);
+    for (var i = 0; i < trackElements.length; i++)
+        trackElements[i].onload = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 2)
+            return;
+
+        testTrack(0);
+        testTrackError(1);
+        t.done();
+    }
+
+    function testTrack(index) {
+        var expected = [
+            { position : 10, align : "start"  },
+            { position : 20, align : "center" },
+            { position : 80, align : "end"    }
+        ];
+
+        assert_cues_match(video.textTracks[index].cues, expected);
+    }
+
+    function testTrackError(index) {
+        var expected = [
+            { position : 10,     align : "center" },
+            { position : "auto", align : "center" },
+            { position : "auto", align : "center" }
+        ];
+
+        assert_cues_match(video.textTracks[index].cues, expected);
+    }
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-text-line-position.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-text-line-position.html
new file mode 100644
index 0000000..deb3899
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-align-text-line-position.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Cue alignment, line and text position from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/align-text-line-position.vtt">
+    <track src="resources/align-text-line-position-bad.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    assert_equals(trackElements.length, video.textTracks.length);
+    for (var i = 0; i < trackElements.length; i++)
+        trackElements[i].onload = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 2)
+            return;
+
+        testTrack(0);
+        testTrackError(1);
+        t.done();
+    }
+
+    function testTrack(index) {
+        var expected = [
+            { align : "start",  position : 10,     line : 0,  snapToLines : false },
+            { align : "start",  position : "auto", line : 0,  snapToLines : true  },
+            { align : "center", position : 80,     line : 80, snapToLines : false },
+            { align : "end",    position : 30,     line : 5,  snapToLines : true  },
+            { align : "center", position : 60,     line : -3, snapToLines : true  }
+        ];
+
+        assert_cues_match(video.textTracks[index].cues, expected);
+    }
+
+    function testTrackError(index) {
+        var expected = [
+            { align : "center", position : "auto", line : "auto", snapToLines : true },
+            { align : "end",    position : 0,      line : "auto", snapToLines : true },
+            { align : "center", position : 60,     line : -3,     snapToLines : true }
+        ];
+
+        assert_cues_match(video.textTracks[index].cues, expected);
+    }
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-alignment.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-alignment.html
new file mode 100644
index 0000000..e8f47e8
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-alignment.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Cue alignment from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/alignment.vtt", testTrack);
+check_cues_from_track("resources/alignment-ltr.vtt", testTrack);
+
+check_cues_from_track("resources/alignment-bad.vtt", function(track) {
+    var expected = [
+        { align: "center" },
+        { align: "center" },
+        { align: "center" },
+        { align: "center" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+function testTrack(track) {
+    var expected = [
+        { align: "start" },
+        { align: "center" },
+        { align: "end" },
+        { align: "center" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+}
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-blank-lines.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-blank-lines.html
new file mode 100644
index 0000000..114aebc
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-blank-lines.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Cues are affected neither by multiple newlines \n, \r, and \r\n nor by the absence of a seperating line</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cues.vtt", function(track) {
+    var expected = [
+        { id: "1", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+        { id: "2", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+        { id: "3", startTime: 61, endTime: 361200.5, text: "I said Bear is coming now!!!!" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cues-no-separation.vtt", function(track) {
+    var expected = [
+        { id: "1", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!\n2" },
+        { id: "", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+        { id: "", startTime: 61, endTime: 361200.5, text: "I said Bear is coming now!!!!" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-bom.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-bom.html
new file mode 100644
index 0000000..c138f96
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-bom.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Parser properly ignores a UTF-8 BOM character at the beginning of a file and all other cues are properly parsed</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/bom.vtt" default>
+    <script>
+        async_test(function(t) {
+            var track = document.querySelector("track");
+
+            track.onload = t.step_func_done(function() {
+                var expected = [
+                    {
+                        id : "1",
+                        startTime : 0,
+                        endTime : 30.5,
+                        text : "Bear is Coming!!!!!"
+                    },
+                    {
+                        id : "2",
+                        startTime : 31,
+                        endTime : 1200.5,
+                        text : "I said Bear is coming!!!!"
+                    }
+                ];
+
+                var cues = track.track.cues;
+                assert_equals(cues.length, 2);
+                assert_cues_equal(cues, expected);
+            });
+        });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-class-markup.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-class-markup.html
new file mode 100644
index 0000000..fe3c868
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-class-markup.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>Tests cues with class markup &lt;c&gt;.</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/class.vtt", function(track) {
+    assert_equals(track.cues.length, 3);
+
+    var children = [
+        { type: "span", style: { className: "black" },
+            value: [ { type: "text", value: "Bear is Coming!!!!!" } ] }
+    ];
+    assert_cue_fragment(track.cues[0], children);
+
+    children = [
+        { type: "span", style: { className: "green" },
+            value: [ { type: "text", value: "I said Bear is coming!!!!" } ] }
+    ];
+    assert_cue_fragment(track.cues[1], children);
+
+    children = [
+        { type: "text", value: "I said " },
+        { type: "span", style: { className: "red uppercase" },
+            value: [ { type: "text", value: "Bear is coming now" } ] },
+        { type: "text", value: "!!!!" }
+    ];
+    assert_cue_fragment(track.cues[2], children);
+});
+
+check_cues_from_track("resources/class-bad.vtt", function(track) {
+    assert_equals(track.cues.length, 3);
+
+    var children = [
+        { type: "span", value: [ { type: "text", value: "Bear is Coming!!!!!" } ] },
+        { type: "text", value: "\nThe space signified an annotation start." }
+    ];
+    assert_cue_fragment(track.cues[0], children);
+
+    children = [
+        { type: "span", style: { className: "red&large" },
+            value: [ { type: "text", value: "I said Bear is coming!!!!" } ] },
+        { type: "text", value: "\nProbably should only allow characters that CSS allows in class names." }
+    ];
+    assert_cue_fragment(track.cues[1], children);
+
+    children = [
+        { type: "text", value: "I said " },
+        { type: "span", style: { className: "9red upper+case" },
+            value: [ { type: "text", value: "Bear is coming now" } ] },
+        { type: "text", value: "!!!!\nProbably should only allow characters that CSS allows in class names." }
+    ];
+    assert_cue_fragment(track.cues[2], children);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-identifiers.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-identifiers.html
new file mode 100644
index 0000000..02b0a15
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-identifiers.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Any text other than "-->" is recognized as optional cue identifier</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-id.vtt", function(track) {
+    var expected = [
+        { id: "random_id", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+        { id: "another random identifier", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+        { id: "identifier--too", startTime: 61, endTime: 120.5, text: "I said Bear is coming now!!!!" },
+        { id: "identifier--too", startTime: 121, endTime: 180.5, text: "Duplicate identifier" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cue-id-error.vtt", function(track) {
+    var expected = [
+        { id: "", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+        { id: "", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+        { id: "", startTime: 61, endTime: 1200.5, text: "I said Bear is coming now!!!!" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-no-id.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-no-id.html
new file mode 100644
index 0000000..b2f4b77
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-no-id.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Empty cue identifiers, but having "-->" leads to discarded cue</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-no-id.vtt", testTrack);
+check_cues_from_track("resources/cue-no-id-error.vtt", testTrack);
+
+function testTrack(track) {
+    var expected = [
+        { id: "", startTime: 0, endTime: 30.5, text: "Bear is Coming!!!!!" },
+        { id: "", startTime: 31, endTime: 60.5, text: "I said Bear is coming!!!!" },
+        { id: "", startTime: 61, endTime: 1200.5, text: "I said Bear is coming now!!!!" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+}
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-recovery.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-recovery.html
new file mode 100644
index 0000000..6a10491
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-recovery.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>A cue is recovered when a line with a "-->" is encountered without blank line separator</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-recovery-header.vtt", testTrack);
+check_cues_from_track("resources/cue-recovery-note.vtt", testTrack);
+check_cues_from_track("resources/cue-recovery-cuetext.vtt", testTrack);
+
+function testTrack(track) {
+    var expected = [
+        { startTime: 0, endTime: 1, text: "Valid cue 1" },
+        { startTime: 2, endTime: 3, text: "Valid cue 2" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+}
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size-align.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size-align.html
new file mode 100644
index 0000000..a1243a9
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size-align.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Cue size and alignment from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-size-align.vtt", function(track) {
+    var expected = [
+        { size: 100, align: "start"  },
+        { size: 10,  align: "end"    },
+        { size: 0,   align: "center" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cue-size-align-bad.vtt", function(track) {
+    var expected = [
+        { size: 100, align: "center" },
+        { size: 100, align: "end"    },
+        { size: 100, align: "center" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size.html
new file mode 100644
index 0000000..d8e03ed
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-cue-size.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Cue size from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/cue-size.vtt", function(track) {
+    var expected = [
+        { size: 100 },
+        { size: 10  },
+        { size: 0   }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/cue-size-bad.vtt", function(track) {
+    var expected = [
+        { size: 100 },
+        { size: 100 },
+        { size: 100 }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-degenerate-cues.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-degenerate-cues.html
new file mode 100644
index 0000000..8d25699
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-degenerate-cues.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Degenerate cues without separating blank lines</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/degenerate-cues.vtt", function(track) {
+    var expected = [
+        { startTime: 0, endTime: 1, text: "" },
+        { startTime: 2, endTime: 3, text: "" },
+        { startTime: 4, endTime: 5, text: "" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-empty-cue.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-empty-cue.html
new file mode 100644
index 0000000..e1f5570
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-empty-cue.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Empty cues should not be discarded</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/empty-cue.vtt", function(track) {
+    assert_equals(track.cues.length, 3);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-entities.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-entities.html
new file mode 100644
index 0000000..a529579
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-entities.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Entities in the cue text</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var getCueAsHTMLContent = function(cue) {
+  return cue.getCueAsHTML().textContent;
+};
+
+check_cues_from_track("resources/entities.vtt", function(track) {
+    var expected = [
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has an ampersand & character." },
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has a less than < character." },
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has a greater than > character." },
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has a Left-to-Right Mark \u200e." },
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has a Right-to-Left Mark \u200f." },
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has a non-breaking space \u00a0." },
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This & is parsed to the same as &." }
+    ];
+
+    assert_cues_html_content(track.cues, expected);
+});
+
+check_cues_from_track("resources/entities-wrong.vtt", function(track) {
+    var expected = [
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has a less than ", },
+        { innerHTML: getCueAsHTMLContent,
+          expected: "This cue has a greater than > character.\nSince it's not related to a < character,\nit's just interpreted as text.", }
+    ];
+
+    assert_cues_html_content(track.cues, expected);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-header-comment.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-header-comment.html
new file mode 100644
index 0000000..f9b3557
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-header-comment.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Optional comment area under the "WEBVTT" file header is properly ignored and also, default settings and styling are currently ignored (treated as faulty cues)</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/default-styles.vtt">
+    <track src="resources/metadata-area.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    for (var i = 0; i < video.textTracks.length; i++)
+        trackElements[i].onload = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 2)
+            return;
+
+        testTrack(0);
+        testTrack(1);
+        t.done();
+    }
+
+    function testTrack(index) {
+        var expected = [
+            {
+                id : "1",
+                startTime : 0,
+                endTime : 30.5,
+                text : "Bear is Coming!!!!!"
+            },
+            {
+                id : "2",
+                startTime : 31,
+                endTime : 1200.5,
+                text : "I said Bear is coming!!!!"
+            }
+        ];
+
+        assert_cues_equal(video.textTracks[index].cues, expected);
+    }
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-interspersed-non-cue.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-interspersed-non-cue.html
new file mode 100644
index 0000000..2287cc2
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-interspersed-non-cue.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>An empty line after an identifier line discards the current cue and restarts the cue loop</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/interspersed-non-cue.vtt", function(track) {
+    var expected = [
+        { text: "First" },
+        { text: "Second" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-line-position.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-line-position.html
new file mode 100644
index 0000000..bea4acb
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-line-position.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<title>Cue line position from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/line-position.vtt">
+    <track src="resources/line-position-bad.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    assert_equals(trackElements.length, video.textTracks.length);
+    for (var i = 0; i < trackElements.length; i++)
+        trackElements[i].onload = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 2)
+            return;
+
+        testTrack(0);
+        testTrackError(1);
+        t.done();
+    }
+
+    function testTrack(index) {
+        var expected = [
+            { line : 0,   snapToLines : false },
+            { line : 0,   snapToLines : true  },
+            { line : 50,  snapToLines : false },
+            { line : 5,   snapToLines : true  },
+            { line : 100, snapToLines : false },
+            { line : -1,  snapToLines : true  },
+            { line : 500, snapToLines : true  }
+        ];
+
+        assert_cues_match(video.textTracks[index].cues, expected);
+    }
+
+    function testTrackError(index) {
+        var expected = [
+            { line : "auto", snapToLines : true },
+            { line : "auto", snapToLines : true },
+            { line : "auto", snapToLines : true },
+            { line : "auto", snapToLines : true },
+            { line : "auto", snapToLines : true }
+        ];
+
+        assert_cues_match(video.textTracks[index].cues, expected);
+    }
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-magic-header.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-magic-header.html
new file mode 100644
index 0000000..ff4637a
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-magic-header.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>Magic file header "WEBVTT" leads to the file properly recognized as a WebVTT file</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/webvtt-file.vtt">
+    <track src="resources/webvtt-rubbish.vtt">
+    <track src="resources/no-webvtt.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    trackElements[0].onload = t.step_func(trackLoaded);
+    trackElements[1].onload = t.step_func(trackLoaded);
+    trackElements[2].onerror = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 3)
+            return;
+
+        testTrack(0);
+        testTrack(1);
+        testTrackError(2);
+        t.done();
+    }
+
+    function testTrack(index) {
+        var expected = [
+            {
+                id : "1",
+                startTime : 0,
+                endTime : 30.5,
+                text : "Bear is Coming!!!!!"
+            },
+            {
+                id : "2",
+                startTime : 31,
+                endTime : 1200.5,
+                text : "I said Bear is coming!!!!"
+            }
+        ];
+
+        assert_cues_equal(video.textTracks[index].cues, expected);
+    }
+
+    function testTrackError(index) {
+        assert_cues_equal(video.textTracks[index].cues, []);
+    }
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-markup.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-markup.html
new file mode 100644
index 0000000..2b04437
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-markup.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<title>Cues with &lt;b&gt;, &lt;i&gt;, &lt;u&gt;, &lt;rt&gt; and &lt;ruby&gt; tags</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/markup.vtt", function(track) {
+    assert_equals(track.cues.length, 4);
+
+    var children = [
+        { type: "text", value: "The following bear is bold:\n" },
+        { type: "b", value: [ { type: "text", value: "Bear" } ] },
+        { type: "text", value: " is Coming!!!!!" }
+    ];
+    assert_cue_fragment(track.cues[0], children);
+
+    children = [
+        { type: "text", value: "The following bear is in italics and has a class of \"larger\":\n" },
+        { type: "i", value: [ { type: "text", value: "Bear" } ] },
+        { type: "text", value: " is Coming!!!!!" }
+    ];
+
+    var fragment = createFragment(children);
+    fragment.querySelector("i").className = "larger";
+    assert_true(fragment.isEqualNode(track.cues[1].getCueAsHTML()));
+
+    children = [
+        { type: "text", value: "The following bear is underlined even though the element has a blank:\nI said " },
+        { type: "u", value: [ { type: "text", value: "Bear" } ] },
+        { type: "text", value: " is coming!!!!" }
+    ];
+    assert_cue_fragment(track.cues[2], children);
+
+    children = [
+        { type: "text", value: "The following bear is ruby annotated:\nI said " },
+        {
+            type: "ruby",
+            value: [
+                { type: "text", value: "Bear" },
+                {
+                    type: "rt",
+                    value: [ { type: "text", value: "bear with me" } ]
+                }
+            ]
+        },
+        { type: "text", value: " is coming!!!!" }
+    ];
+    assert_cue_fragment(track.cues[3], children);
+});
+
+check_cues_from_track("resources/markup-bad.vtt", function(track) {
+    assert_equals(track.cues.length, 4);
+
+    var children = [
+        { type: "text", value: "The following bear starts bold but end is broken:\n" },
+        {
+            type: "b",
+            value:
+            [
+                { type: "text", value: "Bear" },
+                { type: "text", value: " is Coming!!!!!" }
+            ]
+        }
+    ];
+    assert_cue_fragment(track.cues[0], children);
+
+    children = [
+        { type: "text", value: "The following bear is not in italics but the markup is removed:\n" },
+        { type: "text", value: "Bear" },
+        { type: "text", value: " is Coming!!!!!" }
+    ];
+    assert_cue_fragment(track.cues[1], children);
+
+    children = [
+        { type: "text", value: "The following bear is not underlined and markup is removed:\nI said " },
+        { type: "text", value : "Bear" },
+        { type: "text", value : " is coming!!!!" }
+    ];
+    assert_cue_fragment(track.cues[2], children);
+
+    children = [
+        { type: "text", value: "The following bear is not ruby annotated and markup is removed:\nI said " },
+        { type: "text", value: "Bear" },
+        { type: "text", value: "bear with me" },
+        { type: "text", value: " is coming!!!!" }
+    ];
+    assert_cue_fragment(track.cues[3], children);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-newlines.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-newlines.html
new file mode 100644
index 0000000..4da7e6b
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-newlines.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>A cue with no newline at eof is parsed properly</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/no-newline-at-eof.vtt" default>
+    <script>
+        async_test(function(t) {
+            var track = document.querySelector("track");
+
+            track.onload = t.step_func_done(function() {
+                var expected = [
+                    {
+                        id : "1",
+                        startTime : 0,
+                        endTime : 30.5,
+                        text : "Bear is Coming!!!!!"
+                    }
+                ];
+
+                assert_cues_equal(track.track.cues, expected);
+            });
+        });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-no-timings.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-no-timings.html
new file mode 100644
index 0000000..a39a2c3
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-no-timings.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Cue without timings are ignored</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/no-timings.vtt" default>
+    <script>
+        async_test(function(t) {
+            var track = document.querySelector("track");
+
+            track.onload = t.step_func_done(function() {
+                assert_cues_equal(track.track.cues, []);
+            });
+        });
+    </script>
+</video>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines-ref.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines-ref.html
new file mode 100644
index 0000000..92c1e9f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Reference test for track-webvtt-non-snap-to-lines.html</title>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/media.js"></script>
+<style>
+.container {
+  position: relative;
+  display: inline-block;
+}
+.cue {
+  position: absolute;
+  top: 48px;
+  font-family: sans-serif;
+  background: green;
+  color: rgba(255, 255, 255, 1);
+  font-size: 12px;
+  padding: 0px 2px;
+}
+</style>
+<div class="container">
+  <video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+    <script>
+    document.currentScript.parentNode.src = getVideoURI("/media/test");
+    </script>
+  </video>
+  <span class="cue">Bear is Coming!!!!!</span>
+</div>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html
new file mode 100644
index 0000000..0da8c6f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Position is not adjusted for non snap-to-lines cues</title>
+<link rel="match" href="track-webvtt-non-snap-to-lines-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/media.js"></script>
+<style>
+::cue {
+  background: green;
+}
+</style>
+<video autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();"></video>
+<script>
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 1, "Bear is Coming!!!!!");
+cue.snapToLines = false;
+cue.line = 20;
+cue.align = "left";
+track.addCue(cue);
+track.mode = "showing";
+video.src = getVideoURI("/media/test");
+</script>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-positioning.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-positioning.html
new file mode 100644
index 0000000..d14a576
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-positioning.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Cue text position from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/positioning.vtt", testTrack);
+check_cues_from_track("resources/positioning-ltr.vtt", testTrack);
+
+check_cues_from_track("resources/positioning-bad.vtt", function(track) {
+    var expected = [
+        { position: "auto" },
+        { position: "auto" },
+        { position: "auto" },
+        { position: "auto" },
+        { position: "auto" },
+        { position: "auto" },
+        { position: "auto" },
+        { position: "auto" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+function testTrack(track) {
+    var expected = [
+        { position: 0 },
+        { position: 50 },
+        { position: "auto" },
+        { position: 100 }
+    ];
+
+    assert_cues_match(track.cues, expected);
+}
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-settings.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-settings.html
new file mode 100644
index 0000000..9ad98ff
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-settings.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>WebVTT settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/settings.vtt", function(track) {
+    var expected = [
+        { line: 100, position: "auto", align: "start", vertical: "" },
+        { line: 15, position: 40, align: "center", vertical: "rl" },
+        { line: "auto", position: 10, align: "center", vertical: "" },
+        { line: 95, position: "auto", align: "end", vertical: "lr" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+check_cues_from_track("resources/settings-bad-separation.vtt", function(track) {
+    var expected = [
+        { line: 43, position: 10, align: "center", vertical: "" },
+        { line: "auto", position: 50, align: "end", vertical: "" },
+        { line: "auto", position: "auto", align: "center", vertical: "" },
+        { line: "auto", position: 90, align: "center", vertical: "lr" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timestamp.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timestamp.html
new file mode 100644
index 0000000..e311f12
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timestamp.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Cues with &lt;timestamps&gt; tags</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/timestamp.vtt", function(track) {
+    assert_equals(track.cues.length, 3);
+
+    // TODO(srirama.m): Timestamps are handled as ProcessingInstructions,
+    // but because ProcessingInstructions are used in XML and not HTML,
+    // they are ignored here. This should later be tested with oncuechange events.
+
+    var children = [ { type: "text", value: "This cue is painted on." } ];
+    assert_cue_fragment_as_textcontent(track.cues[0], children);
+
+    children = [ { type: "text", value: "I said Bear is coming!!!!" } ];
+    assert_cue_fragment_as_textcontent(track.cues[1], children);
+
+    children = [ { type: "text", value: "I said Bear is coming now!!!!" } ];
+    assert_cue_fragment_as_textcontent(track.cues[2], children);
+});
+
+check_cues_from_track("resources/timestamp-bad.vtt", function(track) {
+    assert_equals(track.cues.length, 3);
+
+    var children = [ { type: "text", value: "This cue is painted on.\nBut since the last two timestamps are out of order, they are ignored." } ];
+    assert_cue_fragment_as_textcontent(track.cues[0], children);
+
+    children = [ { type: "text", value: "I said Bear is coming!!!!\nAll of these timestamps are before the start of the cue, so get ignored." } ];
+    assert_cue_fragment_as_textcontent(track.cues[1], children);
+
+    children = [ { type: "text", value: "I said Bear is coming now!!!!\nAll of these timestamps are after the end of the cue, so get ignored." } ];
+    assert_cue_fragment_as_textcontent(track.cues[2], children);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-hour.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-hour.html
new file mode 100644
index 0000000..c03e182
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-hour.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>Cue timings and various syntax errors in timings, with hours</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/timings-hour.vtt">
+    <track src="resources/timings-hour-error.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    for (var i = 0; i < video.textTracks.length; i++)
+        trackElements[i].onload = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 2)
+            return;
+
+        testTrack0();
+        testTrack1();
+        t.done();
+    }
+
+    function testTrack0() {
+        var expected = [
+            {
+                id : "1",
+                startTime : 0,
+                endTime : 30.5,
+                text : "Bear is Coming!!!!!"
+            },
+            {
+                id : "2",
+                startTime : 31,
+                endTime : 60.5,
+                text : "I said Bear is coming!!!!"
+            },
+            {
+                id : "3",
+                startTime : 61,
+                endTime : 361200.5,
+                text : "I said Bear is coming now!!!!"
+            }
+        ];
+
+        assert_cues_equal(video.textTracks[0].cues, expected);
+    }
+
+    function testTrack1() {
+        // Test that all the cues are ignored.
+        assert_cues_equal(video.textTracks[1].cues, []);
+    }
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-no-hours.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-no-hours.html
new file mode 100644
index 0000000..e81ae03
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-no-hours.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Cue timings and various syntax errors in timings, without hours</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/timings-no-hour.vtt">
+    <track src="resources/timings-no-hour-errors.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    for (var i = 0; i < video.textTracks.length; i++)
+        trackElements[i].onload = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 2)
+            return;
+
+        testTrack0();
+        testTrack1();
+        t.done();
+    }
+
+    function testTrack0() {
+        var expected = [
+            {
+                id : "1",
+                startTime : 0,
+                endTime : 30.5,
+                text : "Bear is Coming!!!!!"
+            },
+            {
+                id : "2",
+                startTime : 31,
+                endTime : 60.5,
+                text : "I said Bear is coming!!!!"
+            },
+            {
+                id : "3",
+                startTime : 61,
+                endTime : 120.5,
+                text : "I said Bear is coming now!!!!"
+            },
+            {
+                id : "4",
+                startTime : 121,
+                endTime : 180.5,
+                text : "tab separators"
+            }
+        ];
+
+        assert_cues_equal(video.textTracks[0].cues, expected);
+    }
+
+    function testTrack1() {
+        // Test that all the cues are ignored.
+        assert_cues_equal(video.textTracks[1].cues, []);
+    }
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-whitespace.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-whitespace.html
new file mode 100644
index 0000000..db1346d
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-timings-whitespace.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>"Skip whitespace" step around cue-timings separator</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/timings-whitespace.vtt", function(track) {
+    var expected = [
+        { id: "1", startTime: 0.1, endTime: 1.5, text: "Single U+0020 SPACE left of cue-timings separator" },
+        { id: "2", startTime: 0.1, endTime: 1.5, text: "Single U+0020 SPACE right of cue-timings separator" },
+        { id: "3", startTime: 0.1, endTime: 1.5, text: "Single U+0009 TAB left of cue-timings separator" },
+        { id: "4", startTime: 0.1, endTime: 1.5, text: "Single U+0009 TAB right of cue-timings separator" },
+        { id: "5", startTime: 0.1, endTime: 1.5, text: "Single U+000C FORM FEED left of cue-timings separator" },
+        { id: "6", startTime: 0.1, endTime: 1.5, text: "Single U+000C FORM FEED right of cue-timings separator" },
+        { id: "7", startTime: 0.1, endTime: 1.5, text: "Several U+0020 SPACE left of cue-timings separator" },
+        { id: "8", startTime: 0.1, endTime: 1.5, text: "Several U+0020 SPACE right of cue-timings separator" },
+        { id: "9", startTime: 0.1, endTime: 1.5, text: "Several U+0009 TAB left of cue-timings separator" },
+        { id: "10", startTime: 0.1, endTime: 1.5, text: "Several U+0009 TAB right of cue-timings separator" },
+        { id: "11", startTime: 0.1, endTime: 1.5, text: "Several U+000C FORM FEED left of cue-timings separator" },
+        { id: "12", startTime: 0.1, endTime: 1.5, text: "Several U+000C FORM FEED right of cue-timings separator" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end-ref.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end-ref.html
new file mode 100644
index 0000000..c041536
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>WebVTT two-cue layout after the first cue has ended (reference)</title>
+<script src="/common/reftest-wait.js"></script>
+<video style="border:1px solid gray">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add a single cue at line -2, where it would be if there was a first
+// cue at line -1.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+var cue = new VTTCue(0, 3, "cue 2");
+cue.line = -2;
+track.addCue(cue);
+track.mode = "showing";
+
+video.onloadeddata = function() {
+  takeScreenshot();
+};
+</script>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end.html
new file mode 100644
index 0000000..c90313c
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-two-cue-layout-after-first-end.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>WebVTT two-cue layout after the first cue has ended</title>
+<link rel="match" href="track-webvtt-two-cue-layout-after-first-end-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<video style="border:1px solid gray">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+</video>
+<script>
+// Add two cues, where the first cue ends before the second.
+var video = document.querySelector("video");
+var track = video.addTextTrack("captions");
+track.addCue(new VTTCue(0, 1, "cue 1"));
+track.addCue(new VTTCue(0, 3, "cue 2"));
+track.mode = "showing";
+
+video.onloadeddata = function() {
+  // Double nesting of requestAnimationFrame to
+  // make sure cue layout and paint happens.
+  window.requestAnimationFrame(function() {
+    window.requestAnimationFrame(function() {
+      // Seek past the end of the first cue. The second cue should not move.
+      video.currentTime = 2;
+      video.onseeked = function() { takeScreenshot(); };
+    });
+  });
+};
+</script>
+</html>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-unsupported-markup.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-unsupported-markup.html
new file mode 100644
index 0000000..ed3107f
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-unsupported-markup.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Unsupported markup is properly ignored</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var getCueAsHTMLContent = function(cue) {
+    return cue.getCueAsHTML().textContent;
+};
+
+check_cues_from_track("resources/unsupported-markup.vtt", function(track) {
+    var expected = [
+        {
+            innerHTML: getCueAsHTMLContent,
+            expected: "Bear is Coming!!!!!\nAnd what kind of a bear it is - just have look."
+        },
+        {
+            innerHTML: getCueAsHTMLContent,
+            expected: "\n  I said Bear is coming!!!!\n  I said Bear is still coming!!!!\n",
+        },
+        {
+            innerHTML: getCueAsHTMLContent,
+            expected: "\n  I said Bear is coming now!!!!\n  \n  \n",
+        }
+    ];
+
+    assert_cues_html_content(track.cues, expected);
+
+    var expected_text = [
+        { text: "<h1>Bear is Coming!!!!!</h1>\n<p>And what kind of a bear it is - just have <a href=\"webpage.html\">look</a>.</p>" },
+        { text: "<ul>\n  <li>I said Bear is coming!!!!</li>\n  <li>I said Bear is still coming!!!!</li>\n</ul>" },
+        { text: "<ol>\n  <li>I said Bear is coming now!!!!</li>\n  <li><img src=\"bear.png\" alt=\"mighty bear\"></li>\n  <li><video src=\"bear_ad.webm\" controls></video></li>\n</ol>" }
+    ];
+
+    assert_cues_match(track.cues, expected_text);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-utf8.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-utf8.html
new file mode 100644
index 0000000..eb44c85
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-utf8.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>UTF-8 encoded characters are recognized properly and different encodings (iconv) are not recognized as a WebVTT file</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video>
+    <track src="resources/utf8.vtt">
+    <track src="resources/iso2022jp3.vtt">
+</video>
+<script>
+async_test(function(t) {
+    var video = document.querySelector("video");
+
+    var trackElements = document.querySelectorAll("track");
+    for (var i = 0; i < video.textTracks.length; i++)
+        trackElements[i].onload = t.step_func(trackLoaded);
+
+    enableAllTextTracks(video.textTracks);
+
+    var numberOfTracksLoaded = 0;
+    function trackLoaded() {
+        numberOfTracksLoaded++;
+        if (numberOfTracksLoaded != 2)
+            return;
+
+        testTrack0();
+        testTrack1();
+        t.done();
+    }
+
+    function testTrack0() {
+        var expected = [
+            {
+                id : "1",
+                startTime : 0,
+                endTime : 30.5,
+                text : "景気判断"
+            },
+            {
+                id : "2",
+                startTime : 31,
+                endTime : 1200.5,
+                text : "電力不足"
+            }
+        ];
+
+        var cues = video.textTracks[0].cues;
+        assert_equals(cues.length, 2);
+        assert_cues_equal(cues, expected);
+    }
+
+    function testTrack1() {
+        assert_equals(video.textTracks[1].cues.length, 2);
+    }
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-valign.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-valign.html
new file mode 100644
index 0000000..ace0760
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-valign.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Cue vertical alignment (direction) from settings</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/valign.vtt", testTrack);
+check_cues_from_track("resources/valign-ltr.vtt", testTrack);
+check_cues_from_track("resources/valign-bad.vtt", function(track) {
+    var expected = [
+        { vertical: "" },
+        { vertical: "" },
+        { vertical: "" }
+    ];
+
+    assert_cues_match(track.cues, expected);
+});
+
+function testTrack(track) {
+    var expected = [
+        { vertical: "rl", align: "center", position: "auto" },
+        { vertical: "lr", align: "center", position: "auto" },
+        { vertical: "rl", align: "start",  position: 0      }
+    ];
+
+    assert_cues_match(track.cues, expected);
+}
+</script>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-voice.html b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-voice.html
new file mode 100644
index 0000000..5df8b40
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-voice.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Cues with voice markup &lt;v&gt;</title>
+<script src="track-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+check_cues_from_track("resources/voice.vtt", function(track) {
+    assert_equals(track.cues.length, 3);
+
+    var children = [
+        { type: "span", style: { className: "blue", title: "Speaker" },
+            value: [ { type: "text", value: "Bear is Coming!!!!!" } ] },
+        { type: "text", value: "\nText span with a class and an annotation." }
+    ];
+    assert_cue_fragment(track.cues[0], children);
+
+    children = [
+        { type: "span", style: { title: "Doe Hunter" },
+            value: [ { type: "text", value: "I said Bear is coming!!!!" } ] }
+    ];
+    assert_cue_fragment(track.cues[1], children);
+
+    children = [
+        { type: "text", value: "I said " },
+        { type: "span", style: { className: "blue", title: "Speaker" },
+            value: [ { type: "text", value: "Bear is coming now" } ] },
+        { type: "text", value: "!!!!" }
+    ];
+    assert_cue_fragment(track.cues[2], children);
+});
+
+check_cues_from_track("resources/voice-bad.vtt", function(track) {
+    assert_equals(track.cues.length, 3);
+
+    var children = [
+        { type: "text", value: "Bear is Coming!!!!!" },
+        { type: "text", value: "\nThis is two annotations for an empty tag." }
+    ];
+    assert_cue_fragment(track.cues[0], children);
+
+    children = [
+        { type: "text", value: "I said Bear is coming!!!!" },
+        { type: "text", value: "\nThis does not parse as a voice tag." }
+    ];
+    assert_cue_fragment(track.cues[1], children);
+
+    children = [
+        { type: "text", value: "I said " },
+        { type: "text", value: "Bear is coming now" },
+        { type: "text", value: "!!!!\nThis does not parse as a voice tag." }
+    ];
+    assert_cue_fragment(track.cues[2], children);
+});
+</script>
\ No newline at end of file
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/vtt-cue-float-precision.html b/html/semantics/embedded-content/media-elements/track/track-element/vtt-cue-float-precision.html
new file mode 100644
index 0000000..9cb5824
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/vtt-cue-float-precision.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>Float precision of VTTCue attributes line, position and size</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+    var cue = new VTTCue(0, 1, 'text');
+
+    // Assign a value which is exactly representable as double but not float.
+    var doubleValue = 1.000000000000004;
+    cue.line = doubleValue;
+    assert_equals(cue.line, doubleValue);
+    cue.position = doubleValue;
+    assert_equals(cue.position, doubleValue);
+    cue.size = doubleValue;
+    assert_equals(cue.size, doubleValue);
+
+    // Assign a value which is exactly representable as float but is non-integral.
+    var floatValue = 1.5;
+    cue.line = floatValue;
+    assert_equals(cue.line, floatValue);
+    cue.position = floatValue;
+    assert_equals(cue.position, floatValue);
+    cue.size = floatValue;
+    assert_equals(cue.size, floatValue);
+}, document.title+', stored as floats');
+</script>
diff --git a/html/semantics/embedded-content/media-elements/user-interface/muted.html b/html/semantics/embedded-content/media-elements/user-interface/muted.html
index 906350d..eb6d2ac 100644
--- a/html/semantics/embedded-content/media-elements/user-interface/muted.html
+++ b/html/semantics/embedded-content/media-elements/user-interface/muted.html
@@ -148,5 +148,22 @@
     assert_true(m.hasAttribute('muted'));
     assert_true(m.muted);
   }, 'getting ' + tagName + '.muted with muted="" (document.write-created)');
+
+  test(function() {
+    var m = document.createElement(tagName);
+    m.setAttribute('muted', '');
+
+    var c = m.cloneNode(true);
+    assert_true(c.muted);
+  }, 'cloning ' + tagName + ' propagates muted (script-created)');
+
+  test(function() {
+    var div = document.createElement('div');
+    div.innerHTML = '<' + tagName + ' muted>';
+    m = div.firstChild;
+
+    var c = m.cloneNode(true);
+    assert_true(c.muted);
+  }, 'cloning ' + tagName + ' propagates muted (innerHTML-created)');
 });
 </script>
diff --git a/html/semantics/embedded-content/the-area-element/area-download-click.html b/html/semantics/embedded-content/the-area-element/area-download-click.html
index 7554c9b..8100ada 100644
--- a/html/semantics/embedded-content/the-area-element/area-download-click.html
+++ b/html/semantics/embedded-content/the-area-element/area-download-click.html
@@ -6,22 +6,27 @@
 <link rel="help" href="https://github.com/whatwg/html/issues/2116">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-
-<img src="/images/threecolors.png" usemap="#x" id="img" width="300" height="300">
-<map name="x">
-  <area id="blob-url" download="foo.html" coords="0,0,300,300">
-</map>
-
+<body>
 <script>
 "use strict";
+async_test(t => {
+    const frame = document.createElement("iframe");
 
-const string = "test";
-const blob = new Blob([string], { type: "text/html" });
+    frame.addEventListener("load", t.step_func(function () {
+        frame.contentWindow.addEventListener(
+            "beforeunload", t.unreached_func("Navigated instead of downloading"));
+        const string = "test";
+        const blob = new Blob([string], { type: "text/html" });
 
-const link = document.querySelector("#blob-url");
-link.href = URL.createObjectURL(blob);
+        const link = frame.contentDocument.querySelector("#blob-url");
+        link.href = URL.createObjectURL(blob);
 
-link.click();
+        link.click();
 
-done();
+        t.step_timeout(() => t.done(), 1000);
+    }));
+    frame.src = "resources/area-download-click.html";
+    document.body.appendChild(frame);
+}, "Clicking on an <area> element with a download attribute must not throw an exception");
 </script>
+</body>
diff --git a/html/semantics/embedded-content/the-area-element/resources/area-download-click.html b/html/semantics/embedded-content/the-area-element/resources/area-download-click.html
new file mode 100644
index 0000000..c0679f8
--- /dev/null
+++ b/html/semantics/embedded-content/the-area-element/resources/area-download-click.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<img src="/images/threecolors.png" usemap="#x" id="img" width="300" height="300">
+<map name="x">
+  <area id="blob-url" download="foo.html" coords="0,0,300,300">
+</map>
diff --git a/html/semantics/embedded-content/the-embed-element/document-getters-return-null-for-cross-origin.html b/html/semantics/embedded-content/the-embed-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000..89dc5d6
--- /dev/null
+++ b/html/semantics/embedded-content/the-embed-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that getSVGDocument() returns null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<embed src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></embed>
+<script>
+const embed = document.querySelector('embed');
+var t = async_test('HTMLEmbedElement.getSVGDocument() for cross-origin document');
+window.addEventListener(
+    'load', t.step_func_done(() => { assert_equals(embed.getSVGDocument(), null); }));
+</script>
+</body>
diff --git a/html/semantics/embedded-content/the-frame-element/document-getters-return-null-for-cross-origin.html b/html/semantics/embedded-content/the-frame-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000..2628e91
--- /dev/null
+++ b/html/semantics/embedded-content/the-frame-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that contentDocument returns null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test('HTMLFrameElement.contentDocument for cross-origin document');
+window.addEventListener(
+    'load', t.step_func_done(() => { assert_equals(document.querySelector('frame').contentDocument, null); }));
+</script>
+<frameset>
+<frame src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></frame>
+</frameset>
diff --git a/html/semantics/embedded-content/the-iframe-element/document-getters-return-null-for-cross-origin.html b/html/semantics/embedded-content/the-iframe-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000..e3dc0b0
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that contentDocument/getSVGDocument() return null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></iframe>
+<script>
+const iframe = document.querySelector('iframe');
+var t1 = async_test('HTMLIFrameElement.contentDocument for cross-origin document');
+window.addEventListener(
+    'load', t1.step_func_done(() => { assert_equals(iframe.contentDocument, null); }));
+var t2 = async_test('HTMLIFrameElement.getSVGDocument() for cross-origin document');
+window.addEventListener(
+    'load', t2.step_func_done(() => { assert_equals(iframe.getSVGDocument(), null); }));
+</script>
+</body>
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-1.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-1.html
new file mode 100644
index 0000000..ce171bf
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (alert)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+    runTest("alert", undefined);
+</script>
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html
new file mode 100644
index 0000000..fbd4d23
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (confirm)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+    runTest("confirm", false);
+</script>
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html
new file mode 100644
index 0000000..5712301
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (prompt)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+    runTest("prompt", null);
+</script>
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-4.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-4.html
new file mode 100644
index 0000000..f750e34
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-4.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe sandbox without allow_modals (print)</title>
+<link rel="author" title="Igalia" href="https://www.igalia.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts"></iframe>
+<script src="support/iframe_sandbox_block_modals.js"></script>
+<script>
+    runTest("print", undefined);
+</script>
diff --git a/html/semantics/embedded-content/the-iframe-element/support/iframe-that-opens-modals.html b/html/semantics/embedded-content/the-iframe-element/support/iframe-that-opens-modals.html
new file mode 100644
index 0000000..50f56c6
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/support/iframe-that-opens-modals.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script>
+function openModal(name) {
+    switch (name) {
+    case "alert":
+        return alert("MESSAGE");
+    break;
+    case "confirm":
+        return confirm("MESSAGE?");
+    break;
+    case "prompt":
+        return prompt("MESSAGE:", "DEFAULT VALUE");
+    break;
+    case "print":
+        return print();
+    break;
+  }
+}
+
+onmessage = function(e) {
+    parent.postMessage(openModal(e.data), "*");
+}
+</script>
diff --git a/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_block_modals.js b/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_block_modals.js
new file mode 100644
index 0000000..67733d8
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_block_modals.js
@@ -0,0 +1,18 @@
+function runTest(modalName, expectedValue) {
+    let timeOutForFailingToOpenModal = 500;
+    let startTime;
+    async_test(t => {
+        let iframe = document.querySelector("iframe");
+        iframe.onload = t.step_func(() => {
+            window.addEventListener("message", t.step_func_done(e => {
+                // This tests work by checking the call to open the modal diaglog will return immediately (or at least within timeOutForFailingToOpenModal).
+                // If the modal dialog is not blocked, then it will wait for user input and the test will time out.
+                assert_less_than(new Date().getTime() - startTime, timeOutForFailingToOpenModal, "Call to open modal dialog did not return immediately");
+                assert_equals(e.data, expectedValue, "Call to open modal dialog did not return expected value");
+            }));
+            startTime = new Date().getTime();
+            iframe.contentWindow.postMessage(modalName, "*");
+        });
+        iframe.src = "support/iframe-that-opens-modals.html";
+    }, "Frames without `allow-modals` should not be able to open modal dialogs");
+}
diff --git a/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute.html b/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute.html
index bd20454..711af8c 100644
--- a/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute.html
+++ b/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute.html
@@ -14,7 +14,7 @@
 function check(p, iframe) {
   var current = p.firstElementChild;
   var ref_sizes = current.getAttribute('sizes');
-  var expect = p.firstElementChild.currentSrc;
+  var expect = current.currentSrc;
   if (expect) {
     expect = expect.split('?')[0];
   }
diff --git a/html/semantics/embedded-content/the-img-element/sizes/sizes-iframed.sub.html b/html/semantics/embedded-content/the-img-element/sizes/sizes-iframed.sub.html
index 925bb46..c564a58 100644
--- a/html/semantics/embedded-content/the-img-element/sizes/sizes-iframed.sub.html
+++ b/html/semantics/embedded-content/the-img-element/sizes/sizes-iframed.sub.html
@@ -52,8 +52,14 @@
 <img srcset='/images/green-1x1.png?e34 50w, /images/green-16x16.png?e34 51w' sizes='\[,1px'>
 <img srcset='/images/green-1x1.png?e35 50w, /images/green-16x16.png?e35 51w' sizes='1\p\x'>
 <img srcset='/images/green-1x1.png?e36 50w, /images/green-16x16.png?e36 51w' sizes='calc(1px)'>
+<img srcset='/images/green-1x1.png?e36a 50w, /images/green-16x16.png?e36a 51w' sizes='min(1px, 100px)'>
+<img srcset='/images/green-1x1.png?e36b 50w, /images/green-16x16.png?e36b 51w' sizes='min(-100px, 1px)'>
 <img srcset='/images/green-1x1.png?e37 50w, /images/green-16x16.png?e37 51w' sizes='(min-width:0) calc(1px)'>
+<img srcset='/images/green-1x1.png?e37a 50w, /images/green-16x16.png?e37a 51w' sizes='(min-width:0) min(1px, 100px)'>
+<img srcset='/images/green-1x1.png?e37b 50w, /images/green-16x16.png?e37b 51w' sizes='(min-width:0) max(-100px, 1px)'>
 <img srcset='/images/green-1x1.png?e38 50w, /images/green-16x16.png?e38 51w' sizes='(min-width:calc(0)) 1px'>
+<img srcset='/images/green-1x1.png?e38a 50w, /images/green-16x16.png?e38a 51w' sizes='(min-width:min(0, 200vw)) 1px'>
+<img srcset='/images/green-1x1.png?e38b 50w, /images/green-16x16.png?e38b 51w' sizes='(min-width:max(-200vw, 0)) 1px'>
 <img srcset='/images/green-1x1.png?e39 50w, /images/green-16x16.png?e39 51w' sizes='(min-width:0) 1px, 100vw'>
 <img srcset='/images/green-1x1.png?e40 50w, /images/green-16x16.png?e40 51w' sizes='(min-width:0) 1px, (min-width:0) 100vw, 100vw'>
 <img srcset='/images/green-1x1.png?e41 50w, /images/green-16x16.png?e41 51w' sizes='(min-width:0) 1px'>
@@ -126,7 +132,11 @@
 <img srcset='/images/green-1x1.png?e108 50w, /images/green-16x16.png?e108 51w' sizes='(max-width:0) or (unknown-general-enclosed !) 100vw, 1px'>
 <img srcset='/images/green-1x1.png?e109 50w, /images/green-16x16.png?e109 51w' sizes='not ((max-width:0) or (unknown "general-enclosed")) 100vw, 1px'>
 <img srcset='/images/green-1x1.png?f48 50w, /images/green-16x16.png?f48 51w' sizes='calc(1px'>
+<img srcset='/images/green-1x1.png?f48a 50w, /images/green-16x16.png?f48a 51w' sizes='min(1px, 200vw'>
+<img srcset='/images/green-1x1.png?f48b 50w, /images/green-16x16.png?f48b 51w' sizes='max(-200vw, 1px'>
 <img srcset='/images/green-1x1.png?f49 50w, /images/green-16x16.png?f49 51w' sizes='(min-width:0) calc(1px'>
+<img srcset='/images/green-1x1.png?f49a 50w, /images/green-16x16.png?f49a 51w' sizes='(min-width:0) min(1px, 200vw'>
+<img srcset='/images/green-1x1.png?f49b 50w, /images/green-16x16.png?f49b 51w' sizes='(min-width:0) max(-200vw, 1px'>
 
 <p>
 <img srcset='/images/green-1x1.png?f1 50w, /images/green-16x16.png?f1 51w' sizes='100vw'>
@@ -146,6 +156,7 @@
 <img srcset='/images/green-1x1.png?f15 50w, /images/green-16x16.png?f15 51w' sizes='0.1dpi'>
 <img srcset='/images/green-1x1.png?f16 50w, /images/green-16x16.png?f16 51w' sizes='0.1dpcm'>
 <img srcset='/images/green-1x1.png?f17 50w, /images/green-16x16.png?f17 51w' sizes='0.1dppx'>
+<img srcset='/images/green-1x1.png?f17a 50w, /images/green-16x16.png?f17a 51w' sizes='0.1x'>
 <img srcset='/images/green-1x1.png?f18 50w, /images/green-16x16.png?f18 51w' data-foo='1px' sizes='attr(data-foo, length, 1px)'>
 <img srcset='/images/green-1x1.png?f19 50w, /images/green-16x16.png?f19 51w' data-foo='1' sizes='attr(data-foo, px, 1px)'>
 <img srcset='/images/green-1x1.png?f20 50w, /images/green-16x16.png?f20 51w' sizes='toggle(1px)'>
diff --git a/html/semantics/embedded-content/the-object-element/document-getters-return-null-for-cross-origin.html b/html/semantics/embedded-content/the-object-element/document-getters-return-null-for-cross-origin.html
new file mode 100644
index 0000000..3d10775
--- /dev/null
+++ b/html/semantics/embedded-content/the-object-element/document-getters-return-null-for-cross-origin.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that contentDocument/getSVGDocument() return null for a cross-origin document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<object data='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="100" width="100"/></svg>'></object>
+<script>
+const object = document.querySelector('object');
+var t1 = async_test('HTMLObjectElement.contentDocument for cross-origin document');
+window.addEventListener(
+    'load', t1.step_func_done(() => { assert_equals(object.contentDocument, null); }));
+var t2 = async_test('HTMLObjectElement.getSVGDocument() for cross-origin document');
+window.addEventListener(
+    'load', t2.step_func_done(() => { assert_equals(object.getSVGDocument(), null); }));
+</script>
+</body>
diff --git a/html/semantics/forms/autofocus/first-when-later-but-before.html b/html/semantics/forms/autofocus/first-when-later-but-before.html
new file mode 100644
index 0000000..0267f46
--- /dev/null
+++ b/html/semantics/forms/autofocus/first-when-later-but-before.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The temporally first autofocus in the document wins, even if an element is inserted later that is previous in the document tree</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input autofocus>
+
+<script>
+"use strict";
+
+const input1 = document.querySelector("input");
+
+const input2 = document.createElement("input");
+input2.autofocus = true;
+document.body.prepend(input2);
+
+step_timeout(() => {
+    assert_equals(document.activeElement, input1);
+    assert_not_equals(document.activeElement, input2);
+
+    done();
+}, 100);
+</script>
diff --git a/html/semantics/forms/autofocus/first-when-later.html b/html/semantics/forms/autofocus/first-when-later.html
new file mode 100644
index 0000000..9ced437
--- /dev/null
+++ b/html/semantics/forms/autofocus/first-when-later.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The first autofocus in the document wins, even if elements are inserted later</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input autofocus>
+
+<script>
+"use strict";
+
+const input1 = document.querySelector("input");
+
+const input2 = document.createElement("input");
+input2.autofocus = true;
+document.body.appendChild(input2);
+
+step_timeout(() => {
+    assert_equals(document.activeElement, input1);
+    assert_not_equals(document.activeElement, input2);
+
+    done();
+}, 100);
+</script>
diff --git a/html/semantics/forms/autofocus/first.html b/html/semantics/forms/autofocus/first.html
new file mode 100644
index 0000000..de56cf7
--- /dev/null
+++ b/html/semantics/forms/autofocus/first.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The first autofocus in the document wins</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input autofocus>
+<input autofocus>
+
+<script>
+"use strict";
+
+const [input1, input2] = document.querySelectorAll("input");
+
+step_timeout(() => {
+  assert_equals(document.activeElement, input1);
+  assert_not_equals(document.activeElement, input2);
+
+  done();
+}, 100);
+</script>
diff --git a/html/semantics/forms/autofocus/not-on-first-task.html b/html/semantics/forms/autofocus/not-on-first-task.html
new file mode 100644
index 0000000..50efc17
--- /dev/null
+++ b/html/semantics/forms/autofocus/not-on-first-task.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The element is not focused during the initial parsing task</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#autofocusing-a-form-control:-the-autofocus-attribute">
+<link rel="author" title="Domenic Denicola" href="d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input autofocus>
+<input autofocus>
+
+<script>
+"use strict";
+
+const input = document.querySelector("input");
+
+assert_equals(document.activeElement, document.body);
+assert_not_equals(document.activeElement, input);
+
+done();
+</script>
diff --git a/html/semantics/forms/form-submission-0/constructing-form-data-set.html b/html/semantics/forms/form-submission-0/constructing-form-data-set.html
new file mode 100644
index 0000000..0fe9171
--- /dev/null
+++ b/html/semantics/forms/form-submission-0/constructing-form-data-set.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set">
+<link ref="help" href="https://xhr.spec.whatwg.org/#dom-formdata">
+<link rel="help" href="https://fetch.spec.whatwg.org/#concept-bodyinit-extract">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+<iframe name="frame1"></iframe>
+<form accept-charset="iso-8859-1" target="frame1" action="/common/blank.html">
+<input type="hidden" name="_charset_">
+</form>
+
+<script>
+test(() => {
+  let formData = new FormData(document.querySelector('form'));
+  assert_equals(formData.get('_charset_'), 'UTF-8');
+}, 'FormData constructor always produces UTF-8 _charset_ value.');
+
+let t = async_test('_charset_ control sets the expected encoding name.');
+t.step(() => {
+  let iframe = document.querySelector('iframe');
+  iframe.onload = t.step_func_done(() => {
+    assert_not_equals(iframe.contentDocument.URL.indexOf('_charset_=windows-1252'), -1);
+  });
+  document.querySelector('form').submit();
+});
+</script>
+</body>
diff --git a/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js b/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js
new file mode 100644
index 0000000..693bcf9
--- /dev/null
+++ b/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js
@@ -0,0 +1,14 @@
+promise_test(() => {
+  const form = document.body.appendChild(document.createElement("form")),
+        input = form.appendChild(document.createElement("input"));
+  input.type = "file";
+  input.name = "hi";
+  const fd = new FormData(form),
+        value = fd.get(input.name);
+  assert_true(value instanceof File, "value is a File");
+  assert_equals(value.name, "", "name");
+  assert_equals(value.type, "application/octet-stream", "type");
+  return new Response(value).text().then(body => {
+    assert_equals(body, "", "body");
+  });
+}, "Empty <input type=file> is still serialized");
diff --git a/html/semantics/forms/form-submission-0/resources/file-submission.py b/html/semantics/forms/form-submission-0/resources/file-submission.py
new file mode 100644
index 0000000..5fc67fa
--- /dev/null
+++ b/html/semantics/forms/form-submission-0/resources/file-submission.py
@@ -0,0 +1,2 @@
+def main(request, response):
+    return ([("Content-Type", "text/html")], "<script>parent.postMessage(\"" + str(request.POST.first("testinput")) + "\", '*');</script>")
diff --git a/html/semantics/forms/form-submission-0/submit-file.sub.html b/html/semantics/forms/form-submission-0/submit-file.sub.html
new file mode 100644
index 0000000..1cf939c
--- /dev/null
+++ b/html/semantics/forms/form-submission-0/submit-file.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe id=testframe name=testframe></iframe>
+<form id=testform method=post action="//{{domains[www1]}}:{{location[port]}}/html/semantics/forms/form-submission-0/resources/file-submission.py" target=testframe enctype="multipart/form-data">
+<input name=testinput id=testinput type=file>
+</form>
+<script>
+async_test(t => {
+  const dataTransfer = new DataTransfer();
+  dataTransfer.items.add(new File(["foobar"], "name"));
+  assert_equals(1, dataTransfer.files.length);
+
+  testinput.files = dataTransfer.files;
+  testform.submit();
+
+  onmessage = e => {
+    if (e.source !== testframe) return;
+    assert_equals("FieldStorage('testinput', 'name', 'foobar')", e.data);
+    t.done();
+  };
+}, 'Posting a File');
+</script>
diff --git a/html/semantics/forms/textfieldselection/defaultSelection.html b/html/semantics/forms/textfieldselection/defaultSelection.html
new file mode 100644
index 0000000..be965bf
--- /dev/null
+++ b/html/semantics/forms/textfieldselection/defaultSelection.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>foo</textarea>
+<input type="text" value="foo"></input>
+<script>
+
+for (let el of [document.querySelector("textarea"), document.querySelector("input")]) {
+    test(function() {
+        assert_equals(el.selectionStart, 0);
+        assert_equals(el.selectionEnd, 0);
+    }, `Default selectionStart and selectionEnd for ${el}`);
+
+    test(function() {
+        el.value="foo";
+        assert_equals(el.selectionStart, 0);
+        assert_equals(el.selectionEnd, 0);
+    }, `selectionStart and selectionEnd do not change when same value set again for ${el}`);
+
+    test(function() {
+        el.value="Foo";
+        assert_equals(el.selectionStart, 3);
+        assert_equals(el.selectionEnd, 3);
+    }, `selectionStart and selectionEnd change when value changed to upper case for ${el}`);
+  }
+</script>
diff --git a/html/semantics/forms/textfieldselection/select-event.html b/html/semantics/forms/textfieldselection/select-event.html
index 6cfe52e..2fb018f 100644
--- a/html/semantics/forms/textfieldselection/select-event.html
+++ b/html/semantics/forms/textfieldselection/select-event.html
@@ -47,10 +47,10 @@
   }
 ];
 
-for (const el of els) {
+els.forEach((el) => {
   const elLabel = el.localName === "textarea" ? "textarea" : "input type " + el.type;
 
-  for (const action of actions) {
+  actions.forEach((action) => {
     // promise_test instead of async_test is important because these need to happen in sequence (to test that events
     // fire if and only if the selection changes).
     promise_test(t => {
@@ -79,6 +79,6 @@
         }, 200);
       });
     }, `${elLabel}: ${action.label} a second time (must not fire select)`);
-  }
-}
+  });
+});
 </script>
diff --git a/html/semantics/forms/textfieldselection/selection-not-application.html b/html/semantics/forms/textfieldselection/selection-not-application.html
index b0db8a9..04b4fdd 100644
--- a/html/semantics/forms/textfieldselection/selection-not-application.html
+++ b/html/semantics/forms/textfieldselection/selection-not-application.html
@@ -7,45 +7,90 @@
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
-  var types = ["hidden", "email", "datetime-local", "date", "month", "week", "time", "number", "range", "color", "checkbox", "radio", "file", "submit", "image", "reset", "button"]; //types for which the API doesn't apply
-  var types2 = ["text", "search", "tel", "url", "password"]; //types for which the API applies
+  var nonApplicableTypes = ["hidden", "email", "datetime-local", "date", "month", "week", "time", "number", "range", "color", "checkbox", "radio", "file", "submit", "image", "reset", "button"];
+  var applicableTypes = ["text", "search", "tel", "url", "password", "aninvalidtype", null];
 
-  types.forEach(function(type){
-    test(function(){
-      var el = document.createElement("input");
-      el.type = type;
+  nonApplicableTypes.forEach(function(type){
+    var el = document.createElement("input");
+    el.type = type;
+
+    test(() => {
       assert_equals(el.selectionStart, null);
+    }, `selectionStart on an input[type=${type}] returns null`);
+
+    test(() => {
       assert_equals(el.selectionEnd, null);
+    }, `selectionEnd on an input[type=${type}] returns null`);
+
+    test(() => {
       assert_equals(el.selectionDirection, null);
+    }, `selectionDirection on an input[type=${type}] returns null`);
+
+    test(() => {
       assert_throws("InvalidStateError", function(){
         el.selectionStart = 0;
       });
+    }, `assigning selectionStart on an input[type=${type}] throws InvalidStateError`);
+
+    test(() => {
       assert_throws("InvalidStateError", function(){
         el.selectionEnd = 0;
       });
+    }, `assigning selectionEnd on an input[type=${type}] throws InvalidStateError`);
+
+    test(() => {
       assert_throws("InvalidStateError", function(){
         el.selectionDirection = 'none';
       });
+    }, `assigning selectionDirection on an input[type=${type}] throws InvalidStateError`);
+
+    test(() => {
       assert_throws("InvalidStateError", function(){
         el.setRangeText("foobar");
       });
+    }, `setRangeText on an input[type=${type}] throws InvalidStateError`);
+
+    test(() => {
       assert_throws("InvalidStateError", function(){
         el.setSelectionRange(0, 1);
       });
-    }, "text field selection for the input " + type);
+    }, `setSelectionRange on an input[type=${type}] throws InvalidStateError`);
   });
 
-  types2.forEach(function(type) {
-    test(function() {
-      var el = document.createElement("input");
-      el.type = type;
+  applicableTypes.forEach(function(type) {
+    var el = document.createElement("input");
+    if (type) el.type = type;
+
+    test(() => {
       assert_equals(el.selectionStart, 0);
+    }, `selectionStart on an input[type=${type}] returns a value`);
+
+    test(() => {
       assert_equals(el.selectionEnd, 0);
+    }, `selectionEnd on an input[type=${type}] returns a value`);
+
+    test(() => {
+      assert_equals(el.selectionDirection, "none");
+    }, `selectionDirection on an input[type=${type}] returns a value`);
+
+    test(() => {
       el.selectionStart = 1;
+    }, `assigning selectionStart on an input[type=${type}] doesn't throw an exception`);
+
+    test(() => {
       el.selectionEnd = 1;
+    }, `assigning selectionEnd on an input[type=${type}] doesn't throw an exception`);
+
+    test(() => {
       el.selectionDirection = "forward";
+    }, `assigning selectionDirection on an input[type=${type}] doesn't throw an exception`);
+
+    test(() => {
       el.setRangeText("foobar");
+    }, `setRangeText on an input[type=${type}] doesn't throw an exception`);
+
+    test(() => {
       el.setSelectionRange(0, 1);
-    }, "text field selection for the input " + type);
+    }, `setSelectionRange on an input[type=${type}] doesn't throw an exception`);
   });
 </script>
diff --git a/html/semantics/forms/textfieldselection/selection-start-end-extra.html b/html/semantics/forms/textfieldselection/selection-start-end-extra.html
new file mode 100644
index 0000000..af51354
--- /dev/null
+++ b/html/semantics/forms/textfieldselection/selection-start-end-extra.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<form id="form"><input id="form-input" type="text" value="abc" /></form>
+<script>
+  // * Should we test setting the dirty flag in any way that isn't
+  //   setting the value?
+  // * How to simulate users typing?
+
+  test(function() {
+    var el = document.createElement("textarea");
+    assert_equals(el.selectionStart, 0);
+    assert_equals(el.selectionEnd, 0);
+    el.defaultValue = "123";
+    assert_equals(el.value.length, 3);
+    assert_equals(el.selectionStart, 3);
+    assert_equals(el.selectionEnd, 3);
+  }, "Setting defaultValue in a textarea should move the cursor to the end");
+
+  test(function() {
+    var el = document.createElement("textarea");
+    el.value = "abcdef";
+    assert_equals(el.selectionStart, 6);
+    assert_equals(el.selectionEnd, 6);
+    el.defaultValue = "123";
+    assert_equals(el.value.length, 6);
+    assert_equals(el.selectionStart, 6);
+    assert_equals(el.selectionEnd, 6);
+  }, "Setting defaultValue in a textarea with a value should NOT make any difference");
+
+  test(function() {
+    var el = document.createElement("textarea");
+    el.appendChild(document.createTextNode("abcdef"));
+    assert_equals(el.selectionStart, 6);
+    assert_equals(el.selectionEnd, 6);
+    el.textContent = "abcdef123456";
+    assert_equals(el.selectionStart, 12);
+    assert_equals(el.selectionEnd, 12);
+  }, "Setting textContent in a textarea should move selection{Start,End} to the end");
+
+  test(function() {
+    var el = document.createElement("textarea");
+    el.appendChild(document.createTextNode("abcdef"));
+    assert_equals(el.selectionStart, 6);
+    assert_equals(el.selectionEnd, 6);
+    el.appendChild(document.createTextNode("123456"));
+    assert_equals(el.selectionStart, 12);
+    assert_equals(el.selectionEnd, 12);
+  }, "Adding children to a textarea should move selection{Start,End} to the end");
+
+  test(function() {
+    var el = document.createElement("textarea");
+    el.appendChild(document.createTextNode("abcdef"));
+    el.appendChild(document.createTextNode("123"));
+    assert_equals(el.selectionStart, 9);
+    assert_equals(el.selectionEnd, 9);
+
+    el.removeChild(el.firstChild);
+    assert_equals(el.selectionStart, 3);
+    assert_equals(el.selectionEnd, 3);
+  }, "Removing children from a textarea should update selection{Start,End}");
+
+  test(function() {
+    var el = document.createElement("textarea");
+    el.textContent = "abcdef\nwhatevs";
+    el.selectionStart = 3;
+    el.selectionEnd = 5;
+
+    el.textContent = "abcdef\r\nwhatevs";
+    assert_equals(el.selectionStart, 3);
+    assert_equals(el.selectionEnd, 5);
+  }, "Setting the same value (with different newlines) in a textarea should NOT update selection{Start,End}");
+
+  test(function() {
+    var el = document.createElement("textarea");
+    el.defaultValue = "123";
+    assert_equals(el.value.length, 3);
+    assert_equals(el.selectionStart, 3);
+    assert_equals(el.selectionEnd, 3);
+    el.value = "12";
+    assert_equals(el.value.length, 2);
+    assert_equals(el.selectionStart, 2);
+    assert_equals(el.selectionEnd, 2);
+  }, "Setting value to a shorter string than defaultValue should correct the cursor position");
+
+  test(function() {
+    var el = document.createElement("input");
+    el.type = "text";
+    el.value = "http://example.com   ";
+    assert_equals(el.selectionStart, 21);
+    assert_equals(el.selectionEnd, 21);
+    el.type = "url";
+    assert_equals(el.selectionStart, 18);
+    assert_equals(el.selectionEnd, 18);
+  }, "Shortening value by turning the input type into 'url' should correct selection{Start,End}");
+
+  test(function() {
+    var el = document.createElement("input");
+    el.type = "text";
+    el.value = "#123456xx";
+    assert_equals(el.selectionStart, 9);
+    assert_equals(el.selectionEnd, 9);
+    el.type = "color";
+    el.type = "text";
+    assert_equals(el.selectionStart, 7);
+    assert_equals(el.selectionEnd, 7);
+  }, "Shortening value by turning the input type into 'color' and back to 'text' should correct selection{Start,End}");
+
+  test(function() {
+    var form = document.getElementById("form");
+    var el = document.getElementById("form-input");
+
+    el.value = "abcde";
+    assert_equals(el.value.length, 5);
+    form.reset();
+    assert_equals(el.value.length, 3);
+    assert_equals(el.selectionStart, 3);
+    assert_equals(el.selectionEnd, 3);
+  }, "Resetting a value to a shorter string than defaultValue should correct the cursor position");
+</script>
diff --git a/html/semantics/forms/textfieldselection/selection-start-end.html b/html/semantics/forms/textfieldselection/selection-start-end.html
index 0638380..4bcecf2 100644
--- a/html/semantics/forms/textfieldselection/selection-start-end.html
+++ b/html/semantics/forms/textfieldselection/selection-start-end.html
@@ -59,10 +59,16 @@
     assert_equals(testValue.length, 10);
   }, "Sanity check for testValue length; if this fails, variou absolute offsets in the test below need to be adjusted to be less than testValue.length");
 
+  for (let prop of ["selectionStart", "selectionEnd"]) {
+    for (let el of createTestElements(testValue)) {
+      test(function() {
+        assert_equals(el[prop], testValue.length);
+      }, `Initial .value set on ${el.id} should set ${prop} to end of value`);
+    }
+  }
+
   test(function() {
     for (let el of createTestElements(testValue)) {
-      assert_equals(el.selectionStart, testValue.length,
-                    `Initial .value set on ${el.id} should set selectionStart to end of value`);
       var t = async_test(`onselect should fire when selectionStart is changed on ${el.id}`);
       el.onselect = t.step_func_done(function(e) {
         assert_equals(e.type, "select");
@@ -74,8 +80,6 @@
 
   test(function() {
     for (let el of createTestElements(testValue)) {
-      assert_equals(el.selectionEnd, testValue.length,
-                    `Initial .value set on ${el.id} should set selectionEnd to end of value`);
       var t = async_test(`onselect should fire when selectionEnd is changed on ${el.id}`);
       el.onselect = t.step_func_done(function(e) {
         assert_equals(e.type, "select");
@@ -87,8 +91,6 @@
 
   test(function() {
     for (let el of createTestElements(testValue)) {
-      assert_equals(el.selectionStart, testValue.length,
-                    `Initial .value set on ${el.id} should set selectionStart to end of value`);
       el.selectionStart = 0;
       el.selectionEnd = 5;
       el.selectionStart = 8;
@@ -100,10 +102,6 @@
 
   test(function() {
     for (let el of createTestElements(testValue)) {
-      assert_equals(el.selectionStart, testValue.length,
-                    `Initial .value set on ${el.id} should set selectionStart to end of value`);
-      assert_equals(el.selectionEnd, testValue.length,
-                    `Initial .value set on ${el.id} should set selectionEnd to end of value`);
       el.selectionStart = 8;
       el.selectionEnd = 5;
       assert_equals(el.selectionStart, 5, `selectionStart on ${el.id}`);
@@ -145,4 +143,30 @@
       el.remove();
     }
   }, "selectionEnd edge-case values");
+
+  test(() => {
+    for (let el of createTestElements(testValue)) {
+      const start = 1;
+      const end = testValue.length - 1;
+
+      el.setSelectionRange(start, end);
+
+      assert_equals(el.selectionStart, start, `selectionStart on ${el.id}`);
+      assert_equals(el.selectionEnd, end, `selectionEnd on ${el.id}`);
+
+      el.selectionDirection = "backward";
+
+      assert_equals(el.selectionStart, start,
+                    `selectionStart on ${el.id} after setting selectionDirection to "backward"`);
+      assert_equals(el.selectionEnd, end,
+                    `selectionEnd on ${el.id} after setting selectionDirection to "backward"`);
+
+      el.selectionDirection = "forward";
+
+      assert_equals(el.selectionStart, start,
+                    `selectionStart on ${el.id} after setting selectionDirection to "forward"`);
+      assert_equals(el.selectionEnd, end,
+                    `selectionEnd on ${el.id} after setting selectionDirection to "forward"`);
+    }
+  }, "selectionStart and selectionEnd should remain the same when selectionDirection is changed");
 </script>
diff --git a/html/semantics/forms/textfieldselection/selection-value-interactions.html b/html/semantics/forms/textfieldselection/selection-value-interactions.html
index c453efd..0fd8a5b 100644
--- a/html/semantics/forms/textfieldselection/selection-value-interactions.html
+++ b/html/semantics/forms/textfieldselection/selection-value-interactions.html
@@ -97,22 +97,31 @@
     var el = document.createElement(tag);
     document.body.appendChild(el);
     this.add_cleanup(() => el.remove());
-    el.value = "";
-    assert_equals(el.selectionStart, el.value.length,
-                 "element.selectionStart should be value.length");
-    assert_equals(el.selectionEnd, el.value.length,
-                  "element.selectionEnd should be value.length");
-    el.value = "foo";
-    assert_equals(el.selectionStart, el.value.length,
-                 "element.selectionStart should be value.length");
-    assert_equals(el.selectionEnd, el.value.length,
-                  "element.selectionEnd should be value.length");
-    el.value = "foobar";
-    assert_equals(el.selectionStart, el.value.length,
-                 "element.selectionStart should be value.length");
-    assert_equals(el.selectionEnd, el.value.length,
-                  "element.selectionEnd should be value.length");
-  }, `selection is always collapsed to the end after setting values on ${tag}`);
+
+    for (let val of ["", "foo", "foobar"]) {
+      el.value = val;
+      assert_equals(el.selectionStart, val.length,
+                   "element.selectionStart should be value.length");
+      assert_equals(el.selectionEnd, val.length,
+                    "element.selectionEnd should be value.length");
+    }
+  }, `selection is collapsed to the end after changing values on ${tag}`);
+
+  test(function() {
+    var el = document.createElement(tag);
+    document.body.appendChild(el);
+    this.add_cleanup(() => el.remove());
+
+    el.value = "foobar"
+    el.selectionStart = 2
+    el.selectionEnd = 4
+    el.value = "foobar"
+
+    assert_equals(el.selectionStart, 2,
+                  "element.selectionStart should be unchanged");
+    assert_equals(el.selectionEnd, 4,
+                  "element.selectionEnd should be unchanged");
+  }, `selection is not collapsed to the end when value is set to its existing value on ${tag}`);
 }
 
 </script>
diff --git a/html/semantics/forms/textfieldselection/setSelectionRange.html b/html/semantics/forms/textfieldselection/setSelectionRange.html
new file mode 100644
index 0000000..bdf52a7
--- /dev/null
+++ b/html/semantics/forms/textfieldselection/setSelectionRange.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>
+
+</textarea>
+<script>
+test(function() {
+    let textarea = document.querySelector('textarea');
+    assert_equals(textarea.selectionStart, 0);
+    assert_equals(textarea.selectionEnd, 0);
+    textarea.setSelectionRange(0, 1);
+    assert_equals(textarea.selectionStart, 0);
+    assert_equals(textarea.selectionEnd, 1);
+}, "setSelectionRange on line boundaries");
+</script>
diff --git a/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html b/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html
index 8650876..3aba6b7 100644
--- a/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html
+++ b/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html
@@ -10,6 +10,17 @@
   <textarea id="b">abcde</textarea>
 </div>
 <script>
+var expected_direction_none;
+setup(function() {
+  var input = document.createElement("input");
+  input.setSelectionRange(0, 1, "none");
+  var direction = input.selectionDirection;
+  if (direction !== "none" && direction !== "forward") {
+    throw new Error("Unexpected direction");
+  }
+  expected_direction_none = direction;
+});
+
 test(function() {
   var input = document.getElementById("a");
   test(function() {
@@ -67,22 +78,22 @@
 
   test(function() {
     input.setSelectionRange(0,1,"none")
-    assert_equals(input.selectionDirection, "none", 'The direction of the selection must be set to forward if direction is a case-sensitive match for the string "none"');
+    assert_equals(input.selectionDirection, expected_direction_none);
   },'input direction of setSelectionRange(0,1,"none")');
 
   test(function() {
     input.setSelectionRange(0,1,"hoge")
-    assert_equals(input.selectionDirection, "none", "otherwise");
+    assert_equals(input.selectionDirection, expected_direction_none);
   },'input direction of setSelectionRange(0,1,"hoge")');
 
   test(function() {
     input.setSelectionRange(0,1,"BACKWARD")
-    assert_equals(input.selectionDirection, "none", "selectionDirection should be 'none'");
+    assert_equals(input.selectionDirection, expected_direction_none);
   },'input direction of setSelectionRange(0,1,"BACKWARD")');
 
   test(function() {
     input.setSelectionRange(0,1)
-    assert_equals(input.selectionDirection, "none", "if the argument is omitted");
+    assert_equals(input.selectionDirection, expected_direction_none);
   },'input direction of setSelectionRange(0,1)');
 
   test(function() {
@@ -214,22 +225,22 @@
 
   test(function() {
     textarea.setSelectionRange(0,1,"none")
-    assert_equals(textarea.selectionDirection, "none", 'The direction of the selection must be set to forward if direction is a case-sensitive match for the string "none"');
+    assert_equals(textarea.selectionDirection, expected_direction_none);
   },'textarea direction of setSelectionRange(0,1,"none")');
 
   test(function() {
     textarea.setSelectionRange(0,1,"hoge")
-    assert_equals(textarea.selectionDirection, "none", "otherwise");
+    assert_equals(textarea.selectionDirection, expected_direction_none);
   },'textarea direction of setSelectionRange(0,1,"hoge")');
 
   test(function() {
     textarea.setSelectionRange(0,1,"BACKWARD")
-    assert_equals(textarea.selectionDirection, "none", "selectionDirection should be 'none'");
+    assert_equals(textarea.selectionDirection, expected_direction_none);
   },'textarea direction of setSelectionRange(0,1,"BACKWARD")');
 
   test(function() {
     textarea.setSelectionRange(0,1)
-    assert_equals(textarea.selectionDirection, "none", "if the argument is omitted");
+    assert_equals(textarea.selectionDirection, expected_direction_none);
   },'textarea direction of setSelectionRange(0,1)');
 
   test(function() {
diff --git a/html/semantics/forms/the-fieldset-element/disabled-001.html b/html/semantics/forms/the-fieldset-element/disabled-001.html
index c457bf1..02137ab 100644
--- a/html/semantics/forms/the-fieldset-element/disabled-001.html
+++ b/html/semantics/forms/the-fieldset-element/disabled-001.html
@@ -31,7 +31,7 @@
   </fieldset>
   <fieldset id=fs4 disabled>
     <legend>
-      <fieldset><input type=checkbox id=club4></fieldset>
+      <fieldset id=fs4-1><input type=checkbox id=club4></fieldset>
     </legend>
     <p><label>Name on card: <input id=clubname4 required></label></p>
     <p><label>Card number: <input id=clubnum4 required pattern="[-0-9]+"></label></p>
@@ -68,4 +68,11 @@
     assert_false(document.getElementById('clubnum4').willValidate, "fieldset is disabled so is input 'clubnum4'");
     assert_true(document.getElementById('club4').willValidate, "the first legend a child of the disabled fieldset: input 'club4' is disabled");
   }, "The <legend> element is child of the disabled fieldset: Its descendants should be disabled.");
+
+  test(function () {
+    let fs41 = document.querySelector('#fs4-1');
+    fs41.disabled = true;
+    assert_true(fs41.disabled, "The fieldset in a legend is disabled");
+    assert_false(document.getElementById('club4').willValidate, "In a disabled fieldset in the first legend child of another disabled fieldset: input 'club4' is disabled");
+  }, "A <fieldset> element is in the <legend> element of another disabled <fieldset>: Its descendants should be disabled.");
 </script>
diff --git a/html/semantics/forms/the-input-element/datetime-local.html b/html/semantics/forms/the-input-element/datetime-local.html
index b4548b7..60a6817 100644
--- a/html/semantics/forms/the-input-element/datetime-local.html
+++ b/html/semantics/forms/the-input-element/datetime-local.html
@@ -16,6 +16,12 @@
     {value: "2014-01-01 00:00:00.000", expected: "2014-01-01T00:00", testname: "datetime-local input value set to 2014-01-01 00:00:00.000 without min/max"},
     {value: "2014-01-0 11:11", expected: "", testname: "datetime-local input value set to 2014-01-0 11:11 without min/max"},
     {value: "2014-01-01 11:1", expected: "", testname: "datetime-local input value set to 2014-01-01 11:1 without min/max"},
+    {value: "2014-01-01 11:1d1", expected: "", testname: "invalid datetime-local input value 1"},
+    {value: "2014-01-01H11:11", expected: "", testname: "invalid datetime-local input value 2"},
+    {value: "2014-01-01 11:11:", expected: "", testname: "invalid datetime-local input value 3"},
+    {value: "2014-01-01 11-11", expected: "", testname: "invalid datetime-local input value 4"},
+    {value: "2014-01-01 11:11:123", expected: "", testname: "invalid datetime-local input value 5"},
+    {value: "2014-01-01 11:11:12.1234", expected: "", testname: "invalid datetime-local input value 6"},
     {value: "2014-01-01 11:12", attributes: { min: "2014-01-01 11:11" }, expected: "2014-01-01T11:12", testname: "Value >= min attribute"},
     {value: "2014-01-01 11:10", attributes: { min: "2014-01-01 11:11" }, expected: "2014-01-01T11:11", testname: "Value < min attribute"},
     {value: "2014-01-01 11:10", attributes: { max: "2014-01-01 11:11" }, expected: "2014-01-01T11:10", testname: "Value <= max attribute"},
diff --git a/html/semantics/forms/the-input-element/image-click-form-data.html b/html/semantics/forms/the-input-element/image-click-form-data.html
new file mode 100644
index 0000000..87b77e4
--- /dev/null
+++ b/html/semantics/forms/the-input-element/image-click-form-data.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Check form-data for image submit button with non-empty 'value' attribute</title>
+<link rel="author" title="Shanmuga Pandi" href="mailto:shanmuga.m@samsung.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+"use strict";
+
+// promise_test instead of async_test because this test use window.success, and so can't run at the same time.
+
+promise_test(t => {
+  return new Promise(resolve => {
+    window.success = t.step_func(locationLoaded => {
+      const expected = (new URL("resources/image-submit-click.html?name.x=0&name.y=0", location.href)).href;
+      assert_equals(locationLoaded, expected);
+      resolve();
+    });
+
+    const iframe = document.createElement("iframe");
+    iframe.src = "resources/image-submit-click.html";
+    document.body.appendChild(iframe);
+  });
+}, "Image submit button should not add extra form data if 'value' attribute is present with non-empty value");
+</script>
\ No newline at end of file
diff --git a/html/semantics/forms/the-input-element/month.html b/html/semantics/forms/the-input-element/month.html
index 15fa76d..99be9bc 100644
--- a/html/semantics/forms/the-input-element/month.html
+++ b/html/semantics/forms/the-input-element/month.html
@@ -13,6 +13,9 @@
   <body>
     <h1>Inputs Month</h1>
     <div style="display: none">
+      <input id="valid_value_1" type="month" value="20133-12" />
+      <input id="valid_value_2" type="month" value="2013-12" />
+      <input id="valid_value_3" type="month" value="0003-01" />
       <input id="valid" type="month" value="2011-11" min="2011-01" max="2011-12" />
       <input id="invalid_value" type="month" value="invalid-month" min="2011-01" max="2011-12"/>
       <input id="value_can_be_empty_string" type="month" value="2013-06" />
@@ -21,12 +24,28 @@
       <input id="step_attribute_is_invalid_value" type="month" value="2013-06" step="invalid_step_value" />
       <input id="invalid_month_too_high" type="month" value="2013-13" />
       <input id="invalid_month_too_low" type="month" value="2013-00" />
+      <input id="invalid_year_all_zero" type="month" value="0000-10" />
+      <input id="invalid_month_with_one_number" type="month" value="2013-1" />
+      <input id="invalid_month_non_numerical" type="month" value="2013-abc" />
+      <input id="invalid_date_additional_tuples" type="month" value="2013-11-1-1" />
     </div>
 
     <div id="log"></div>
 
     <script>
       test(function() {
+        assert_equals(document.getElementById("valid_value_1").value, "20133-12")
+      }, "year can be more than four digits");
+
+      test(function() {
+        assert_equals(document.getElementById("valid_value_2").value, "2013-12")
+      }, "valid value test");
+
+      test(function() {
+        assert_equals(document.getElementById("valid_value_3").value, "0003-01")
+      }, "year can contain prefixes of zero, as long as there are at least four digits");
+
+      test(function() {
         assert_equals(document.getElementById("valid").type, "month")
       }, "month type support on input element");
 
@@ -60,6 +79,22 @@
       test(function() {
         assert_equals(document.getElementById("invalid_month_too_low").value, "");
       }, "Month should be > 0: If the value of the element is not a valid month string, then set it to the empty string instead.>");
+
+      test(function() {
+        assert_equals(document.getElementById("invalid_year_all_zero").value, "");
+      }, "Year should be > 0: If the value of the element is not a valid year string, then set it to the empty string instead.>");
+
+      test(function() {
+        assert_equals(document.getElementById("invalid_month_with_one_number").value, "");
+      }, "Month should be two digits: If the value of the element is not a valid month string, then set it to the empty string instead.>");
+
+      test(function() {
+        assert_equals(document.getElementById("invalid_month_non_numerical").value, "");
+      }, "Month should be two digits not characters: If the value of the element is not a valid month string, then set it to the empty string instead.>");
+
+      test(function() {
+        assert_equals(document.getElementById("invalid_date_additional_tuples").value, "");
+      }, "Value should be two parts: If the value of the element is not a valid month string, then set it to the empty string instead.>");
     </script>
   </body>
 </html>
diff --git a/html/semantics/forms/the-input-element/range.html b/html/semantics/forms/the-input-element/range.html
index 6ae5b8a..209ce25 100644
--- a/html/semantics/forms/the-input-element/range.html
+++ b/html/semantics/forms/the-input-element/range.html
@@ -34,6 +34,9 @@
       <input type="range" id="stepdown_beyond_min" min=3 max=11 value=6 step=3 />
       <input type="range" id="illegal_min_and_max" min="ab" max="f" />
       <input type="range" id="illegal_value_and_step" min=0 max=5 value="ppp" step="xyz" />
+      <input type="range" id="should_skip_whitespace" value=" 123"/>
+      <input type="range" id="exponent_value1" value=""/>
+      <input type="range" id="exponent_value2" value=""/>
     </div>
 
     <div id="log">
@@ -280,6 +283,35 @@
         }
       );
 
+      test(
+        function() {
+          var e = document.getElementById('should_skip_whitespace');
+          assert_equals(e.value, "123")
+        }, "Skip ASCII whitespace within input", {
+          "help" : "https://html.spec.whatwg.org/multipage/#best-representation-of-the-number-as-a-floating-point-number"
+        }
+      );
+
+      test(
+        function() {
+          var e = document.getElementById('exponent_value1');
+          e.value = 1e2;
+          assert_equals(e.value, "100")
+        }, "Multiply value by ten raised to the exponentth power with `e`", {
+          "help" : "https://html.spec.whatwg.org/multipage/#best-representation-of-the-number-as-a-floating-point-number"
+        }
+      );
+
+      test(
+        function() {
+          var e = document.getElementById('exponent_value2');
+          e.value = 1E2;
+          assert_equals(e.value, "100")
+        }, "Multiply value by ten raised to the exponentth power with `E`", {
+          "help" : "https://html.spec.whatwg.org/multipage/#best-representation-of-the-number-as-a-floating-point-number"
+        }
+      );
+
     </script>
 
   </body>
diff --git a/html/semantics/forms/the-input-element/resources/image-submit-click.html b/html/semantics/forms/the-input-element/resources/image-submit-click.html
new file mode 100644
index 0000000..8461a03
--- /dev/null
+++ b/html/semantics/forms/the-input-element/resources/image-submit-click.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<form>
+  <input type="image" name="name" value="value">
+</form>
+
+<script>
+"use strict";
+if (window.location.search.startsWith("?name.x")) {
+  // The action pointed to ourself, so the form submitted something
+  window.parent.success(window.location.href);
+} else {
+  const input = document.querySelector("input");
+  input.click();
+}
+</script>
\ No newline at end of file
diff --git a/html/semantics/forms/the-input-element/type-change-state.html b/html/semantics/forms/the-input-element/type-change-state.html
index d98b623..34cfd43 100644
--- a/html/semantics/forms/the-input-element/type-change-state.html
+++ b/html/semantics/forms/the-input-element/type-change-state.html
@@ -15,46 +15,85 @@
     { type: "url", sanitizedValue: "foobar" },
     { type: "email", sanitizedValue: "foobar" },
     { type: "password", sanitizedValue: "  foobar  " },
-    { type: "datetime-local", sanitizedValue: "" },
-    { type: "date", sanitizedValue: "" },
-    { type: "month", sanitizedValue: "" },
-    { type: "week", sanitizedValue: "" },
-    { type: "time", sanitizedValue: "" },
-    { type: "number", sanitizedValue: "" },
-    { type: "range", sanitizedValue: "50" },
-    { type: "color", sanitizedValue: "#000000" },
-    { type: "checkbox" },
-    { type: "radio" },
+    { type: "datetime-local", sanitizedValue: "", overridesSanitization: true },
+    { type: "date", sanitizedValue: "", overridesSanitization: true },
+    { type: "month", sanitizedValue: "", overridesSanitization: true },
+    { type: "week", sanitizedValue: "", overridesSanitization: true },
+    { type: "time", sanitizedValue: "", overridesSanitization: true },
+    { type: "number", sanitizedValue: "", overridesSanitization: true },
+    { type: "range", sanitizedValue: "50", overridesSanitization: true },
+    { type: "color", sanitizedValue: "#000000", overridesSanitization: true },
+    { type: "checkbox", defaultValue: "on" },
+    { type: "radio", defaultValue: "on" },
     { type: "file" },
     { type: "submit" },
     { type: "image" },
     { type: "reset" },
     { type: "button" }
   ];
+
+  const selectionStart = 2;
+  const selectionEnd = 5;
+  const selectionDirection = "backward";
+
   for (var i = 0; i < types.length; i++) {
     for (var j = 0; j < types.length; j++) {
       if (types[i] != types[j]) {
         test(function() {
           var input = document.createElement("input");
+          var expected = "  foo\rbar  ";
           input.type = types[i].type;
           if (types[i].type === "file") {
             assert_throws("INVALID_STATE_ERR", function() {
-              input.value = "  foo\rbar  ";
+              input.value = expected;
             });
             assert_equals(input.value, "");
           } else if (types[j].type === "file") {
-            input.value = "  foo\rbar  ";
+            input.value = expected;
             input.type = types[j].type;  // change state
             assert_equals(input.value, "");
           } else {
-            input.value = "  foo\rbar  ";
+            input.value = expected;
+
+            const previouslySelectable = (input.selectionStart !== null);
+
+            if (previouslySelectable) {
+              input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
+            }
+
             input.type = types[j].type;  // change state
+
+            // type[i] sanitization
+            if (types[i].sanitizedValue || types[i].sanitizedValue === "") {
+              expected = types[i].sanitizedValue;
+            }
+
+            // type[j] sanitization
             if (types[j].sanitizedValue || types[j].sanitizedValue === "") {
-              assert_equals(input.value, types[j].sanitizedValue, "input.value should be " + types[j].sanitizedValue + " after change of state");
-            } else if (types[i].sanitizedValue || types[i].sanitizedValue === "") {
-              assert_equals(input.value, types[i].sanitizedValue, "input.value should be " + types[i].sanitizedValue + " after change of state");
-            } else {
-              assert_equals(input.value, "  foo\rbar  ", "input.value should be '  foo\\rbar  ' after change of state");
+              if ((expected !== "" && !types[i].overridesSanitization) || types[j].overridesSanitization) {
+                expected = types[j].sanitizedValue;
+              }
+            }
+
+            // type[j] defaultValue
+            if (expected === "" && types[j].defaultValue) {
+              expected = types[j].defaultValue;
+            }
+
+            assert_equals(input.value, expected, "input.value should be '" + expected + "' after change of state");
+
+            const nowSelectable = (input.selectionStart !== null);
+
+            if (nowSelectable) {
+              if (previouslySelectable) {
+                assert_equals(input.selectionStart, selectionStart, "selectionStart should be unchanged");
+                assert_equals(input.selectionEnd, selectionEnd, "selectionEnd should be unchanged");
+                assert_equals(input.selectionDirection, selectionDirection, "selectionDirection should be unchanged");
+              } else {
+                assert_equals(input.selectionStart, 0, "selectionStart should be 0");
+                assert_equals(input.selectionEnd, 0, "selectionEnd should be 0");
+                assert_equals(input.selectionDirection, "none", "selectionDirection should be 'none'");
+              }
             }
           }
         }, "change state from " + types[i].type + " to " + types[j].type);
diff --git a/html/semantics/forms/the-input-element/week.html b/html/semantics/forms/the-input-element/week.html
index 77978a2..925acfd 100644
--- a/html/semantics/forms/the-input-element/week.html
+++ b/html/semantics/forms/the-input-element/week.html
@@ -17,6 +17,9 @@
     {value: "2014W", expected: "", testname: "Invalid value: no week number"},
     {value: "2014W52", expected: "", testname: "Invalid value: no '-' (U+002D)"},
     {value: "-W52", expected: "", testname: "Invalid value: yearless week"},
+    {value: "2017-w52", expected: "", testname: "Invalid value: should be capital letter 'W'"},
+    {value: "2017-W52-", expected: "", testname: "Invalid value: incorrect with '-' at the end"},
+    {value: "2017-W52-12", expected: "", testname: "Invalid value: value should be two parts"},
     {value: "W52", expected: "", testname: "Invalid value: yearless week and no '-' (U+002D)"},
     {value: "2014-W03", attributes: { min: "2014-W02" }, expected: "2014-W03", testname: "Value >= min attribute"},
     {value: "2014-W01", attributes: { min: "2014-W02" }, expected: "2014-W01", testname: "Value < min attribute"},
diff --git a/html/semantics/forms/the-progress-element/progress.html b/html/semantics/forms/the-progress-element/progress.html
index 0c176d8..00d63c3 100644
--- a/html/semantics/forms/the-progress-element/progress.html
+++ b/html/semantics/forms/the-progress-element/progress.html
@@ -42,7 +42,7 @@
       }, "Indeterminate progress bar should have value 0");
 
       test(function() {
-        assert_array_equals(largerthanmax.value, 1);
+        assert_equals(largerthanmax.value, 1);
       }, "Value must equal max if the parsed value is larger than max");
 
       test(function() {
@@ -50,7 +50,7 @@
       }, "Max must be 1 by default");
 
       test(function() {
-        assert_array_equals(largerthanmax.max, 1);
+        assert_equals(largerthanmax.max, 1);
       }, "Max must be 1 by default, even if value is specified");
 
       test(function() {
diff --git a/html/semantics/forms/the-select-element/select-add.html b/html/semantics/forms/the-select-element/select-add.html
new file mode 100644
index 0000000..84a5442
--- /dev/null
+++ b/html/semantics/forms/the-select-element/select-add.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTMLSelectElement Test: add()</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-add-dev">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form style="display:none">
+  <option id="testoption">
+    <select id="testselect1">
+    </select>
+    <select id="testselect2">
+      <option>TEST</option>
+    </select>
+  </option>
+</form>
+
+<script>
+
+test(() => {
+  let testselect1 = document.getElementById("testselect1");
+  let opt1 = new Option("Marry","1");
+  testselect1.add(opt1);
+  assert_equals(testselect1.options[0].value, "1");
+}, "test that HTMLSelectElement.add method can add option element");
+
+test(() => {
+  let testselect2 = document.getElementById("testselect2");
+  let opt2 = document.getElementById("testoption");
+  assert_throws("HierarchyRequestError", () => {
+    testselect2.add(opt2);
+  });
+}, "test that HierarchyRequestError exception must be thrown when element is an ancestor of the element into which it is to be inserted");
+
+</script>
diff --git a/html/semantics/forms/the-select-element/selected-index.html b/html/semantics/forms/the-select-element/selected-index.html
index 6c30698..70f6772 100644
--- a/html/semantics/forms/the-select-element/selected-index.html
+++ b/html/semantics/forms/the-select-element/selected-index.html
@@ -25,6 +25,11 @@
     <option></option>
     <option selected></option>
   </select>
+
+  <select id=display-none>
+    <option style="display:none"></option>
+    <option></option>
+  </select>
 </form>
 
 <script>
@@ -70,7 +75,7 @@
   assertSelectedIndex(select, 0);
   select.selectedIndex = 2;
   assertSelectedIndex(select, 2);
-  this.add_cleanup(() => select.selectedIndex = 0);
+  this.add_cleanup(() => { select.selectedIndex = 0; });
 }, "set (HTMLSelectElement)");
 
 test(function () {
@@ -78,7 +83,7 @@
   assertSelectedIndex(select, 0);
   select.options.selectedIndex = 2;
   assertSelectedIndex(select, 2);
-  this.add_cleanup(() => select.selectedIndex = 0);
+  this.add_cleanup(() => { select.selectedIndex = 0; });
 }, "set (HTMLOptionsCollection)");
 
 test(function () {
@@ -100,4 +105,19 @@
   form.reset();
   assertSelectedIndex(select, 1);
 }, "set and reset (HTMLOptionsCollection)");
+
+test(function () {
+  var select = document.getElementById('display-none');
+  assertSelectedIndex(select, 0);
+}, "get display:none");
+
+test(function () {
+  var select = document.getElementById('display-none');
+  select.offsetTop; // force rendering
+  assertSelectedIndex(select, 0);
+  select.options[1].selected = true;
+  assertSelectedIndex(select, 1);
+  select.options[1].selected = false;
+  assertSelectedIndex(select, 0);
+}, "reset to display:none");
 </script>
diff --git a/html/semantics/rellist-feature-detection.html b/html/semantics/rellist-feature-detection.html
new file mode 100644
index 0000000..f4ce621
--- /dev/null
+++ b/html/semantics/rellist-feature-detection.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<title>Test relList attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let link_support_table = {};
+// https://html.spec.whatwg.org/multipage/links.html#linkTypes
+link_support_table['link'] = {
+  supported : ['modulepreload', 'preload', 'preconnect', 'dns-prefetch',
+               'stylesheet', 'import', 'icon', 'alternate', 'prefetch',
+               'prerender', 'next', 'manifest', 'apple-touch-icon',
+               'apple-touch-icon-precomposed', 'canonical'],
+  unsupported : ['author', 'bookmark', 'external', 'help', 'license',
+                 'nofollow', 'pingback', 'prev', 'search', 'tag',
+                 'noreferrer', 'noopener']
+};
+link_support_table['a'] =  {
+  supported : ['noreferrer', 'noopener'],
+  unsupported : ['author', 'bookmark', 'external', 'help', 'license',
+                 'nofollow', 'pingback', 'prev', 'search', 'tag',
+                 'modulepreload', 'preload', 'preconnect', 'dns-prefetch',
+                 'stylesheet', 'import', 'icon', 'alternate', 'prefetch',
+                 'prerender', 'next', 'manifest', 'apple-touch-icon',
+                 'apple-touch-icon-precomposed', 'canonical']
+};
+link_support_table['area'] = link_support_table['a'];
+
+function test_rellist(tag_name, rel_table) {
+  let element = document.createElement(tag_name);
+  let tag = element.tagName;
+  // Test that setting rel is also setting relList, for both
+  // valid and invalid values.
+  element.rel = 'whatever';
+  assert_true(element.relList.contains('whatever'), 'tag = ' + tag + ', setting rel must work');
+  element.rel = 'prefetch';
+  assert_true(element.relList.contains('prefetch'), 'tag = ' + tag + ', setting rel must work');
+  // Test that add() works.
+  element.relList.add('preloadwhatever');
+  assert_equals(element.rel, 'prefetch preloadwhatever', 'tag = ' + tag + ', add must work');
+  assert_true(element.relList.contains('preloadwhatever'), 'tag = ' + tag + ', add must work');
+  // Test that remove() works.
+  element.relList.remove('preloadwhatever');
+  assert_equals(element.rel, 'prefetch', 'tag = ' + tag + ', remove must work');
+  assert_false(element.relList.contains('preloadwhatever'), 'tag = ' + tag + ', remove must work');
+  // Test that toggle() works.
+  element.relList.toggle('prefetch', false);
+  assert_equals(element.rel, '', 'tag = ' + tag + ', toggle must work');
+  element.relList.toggle('prefetch', true);
+  assert_equals(element.rel, 'prefetch', 'tag = ' + tag + ', toggle must work');
+  // Test that replace() works.
+  element.relList.replace('prefetch', 'first');
+  assert_equals(element.rel, 'first', 'tag = ' + tag + ', replace must work');
+  // Test that indexed item getter works.
+  element.relList.add('second');
+  assert_equals(element.relList.length, 2, 'tag = ' + tag + ', relList length must be correct');
+  assert_equals(element.relList[0], 'first', 'tag = ' + tag + ', relList indexed item must work');
+  assert_equals(element.relList[1], 'second', 'tag = ' + tag + ', relList indexed item must work');
+  // Test that relList is  [SameObject].
+  let savedRelList = element.relList;
+  element.rel = 'something';
+  assert_equals(element.relList, savedRelList, 'tag = ' + tag + ', SameObject must work');
+
+  // Test that supports() is returning true for valid values
+  // and false for invalid ones.
+  let supported = rel_table['supported'];
+  for (let link_type in supported) {
+    assert_true(element.relList.supports(supported[link_type]), 'tag = ' + tag + ', link type = ' + supported[link_type] + ' must be supported');
+    assert_true(element.relList.supports(supported[link_type].toUpperCase()), 'tag = ' + tag + ', link type = ' + supported[link_type].toUpperCase() + ' must be supported');
+  }
+  let unsupported = rel_table['unsupported'];
+  for (let link_type in unsupported) {
+    assert_false(element.relList.supports(unsupported[link_type]), 'tag = ' + tag + ', link type = ' + unsupported[link_type] + ' must be unsupported');
+  }
+}
+
+test(function() {
+  test_rellist('LINK', link_support_table['link']);
+  test_rellist('A', link_support_table['a']);
+  test_rellist('AREA', link_support_table['area']);
+}, 'Make sure that relList based feature detection is working');
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py b/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py
new file mode 100644
index 0000000..5df883c
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py
@@ -0,0 +1,4 @@
+def main(request, response):
+    headers = [("Content-Type", "text/javascript"), ("Cache-control", "public, max-age=100")]
+    body = "throw('fox');"
+    return 200, headers, body
diff --git a/html/semantics/scripting-1/the-script-element/defer.js b/html/semantics/scripting-1/the-script-element/defer.js
new file mode 100644
index 0000000..c4449ca
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/defer.js
@@ -0,0 +1,4 @@
+t.step(() => {
+  assert_equals(script_run_status, "deferred", "the script run status");
+});
+t.done();
diff --git a/html/semantics/scripting-1/the-script-element/emptyish-script-elements.html b/html/semantics/scripting-1/the-script-element/emptyish-script-elements.html
new file mode 100644
index 0000000..37f4a87
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/emptyish-script-elements.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Treatment of various empty-ish script elements</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#prepare-a-script">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#already-started">
+<link rel="help" href="https://github.com/whatwg/html/issues/3419">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script id="no-children"></script>
+<script id="whitespace-child"> </script>
+
+<script id="gets-a-no-text-child"></script>
+<script id="gets-an-empty-text-child"></script>
+<script id="gets-a-text-child"></script>
+<script id="gets-a-comment-child"></script>
+<script id="gets-a-text-descendant"></script>
+
+<script>
+"use strict";
+
+test(() => {
+  const el = document.querySelector("#no-children");
+  el.appendChild(document.createTextNode("window.noChildrenExecuted = true;"));
+  assert_true(window.noChildrenExecuted);
+}, "A script with no children bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+  const el = document.querySelector("#whitespace-child");
+  el.appendChild(document.createTextNode("window.whitespaceChildExecuted = true;"));
+  assert_equals(window.whitespaceChildExecuted, undefined);
+}, "A script with a whitespace child executes, setting already-started, so adding text is a no-op");
+
+test(() => {
+  const el = document.querySelector("#gets-a-no-text-child");
+  el.appendChild(document.createElement("span"));
+  el.appendChild(document.createTextNode("window.getsANoTextChildExecuted = true;"));
+  assert_true(window.getsANoTextChildExecuted);
+}, "A script with an empty element inserted bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+  const el = document.querySelector("#gets-an-empty-text-child");
+  el.appendChild(document.createTextNode(""));
+  el.appendChild(document.createTextNode("window.getsAnEmptyTextChildExecuted = true;"));
+  assert_true(window.getsAnEmptyTextChildExecuted);
+}, "A script with an empty text node inserted bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+  const el = document.querySelector("#gets-a-text-child");
+  el.appendChild(document.createTextNode("window.getsATextChildExecuted1 = true;"));
+  el.appendChild(document.createTextNode("window.getsATextChildExecuted2 = true;"));
+  assert_true(window.getsATextChildExecuted1);
+  assert_equals(window.getsATextChildExecuted2, undefined);
+}, "A script with a text child inserted executes, setting already-started, so adding text is a no-op");
+
+test(() => {
+  const el = document.querySelector("#gets-a-comment-child");
+  el.appendChild(document.createComment("window.getsACommentChild1 = true;"));
+  el.appendChild(document.createTextNode("window.getsACommentChild2 = true;"));
+  assert_equals(window.getsACommentChild1, undefined);
+  assert_true(window.getsACommentChild2);
+}, "A script with a comment child inserted bails early, before setting already-started, so can be executed when adding text");
+
+test(() => {
+  const el = document.querySelector("#gets-a-text-descendant");
+  const child = document.createElement("span");
+  child.appendChild(document.createTextNode("window.getsATextDescendantExecuted1 = true;"));
+  el.appendChild(child);
+  el.appendChild(document.createTextNode("window.getsATextDescendantExecuted2 = true;"));
+  assert_equals(window.getsATextDescendantExecuted1, undefined);
+  assert_true(window.getsATextDescendantExecuted2);
+}, "A script with an element containing text inserted bails early, before setting already-started, so can be executed when adding text");
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/execution-timing/077.html b/html/semantics/scripting-1/the-script-element/execution-timing/077.html
index a7a5942..4b98851 100644
--- a/html/semantics/scripting-1/the-script-element/execution-timing/077.html
+++ b/html/semantics/scripting-1/the-script-element/execution-timing/077.html
@@ -17,12 +17,12 @@
                         var t = async_test()
 
                         function test() {
-                                var script = createScript('data:text\/javascript,log("Script #1 ran")');
+                                var script = createScript('data:text\/javascript,log("Script %231 ran")');
                                 var script2 = createScript('','log("Script #2 ran")');
                                 if(script2) {
                                     head.removeChild(script2);
                                 }
-                                var script3 = createScript('data:text\/javascript, log("Script #3 ran"); createScript(\'\', \'log("Script #4 ran")\')');
+                                var script3 = createScript('data:text\/javascript, log("Script %233 ran"); createScript(\'\', \'log("Script #4 ran")\')');
                                 if(script3) {
                                     head.removeChild(script3);
                                 }
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html b/html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html
new file mode 100644
index 0000000..73a6ce3
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Choice of parse errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Parse errors in different files should be reported " +
+        "depending on different roots");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 4);
+
+      // Two different parse errors from different module scripts
+      // should be reported for each <script> element.
+      assert_equals(log[0].constructor, SyntaxError);
+      assert_equals(log[1], 1);
+
+      assert_equals(log[2].constructor, SyntaxError);
+      assert_equals(log[3], 2);
+
+      assert_not_equals(log[0], log[2],
+          'two different parse errors should be reported');
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./choice-of-error-1a.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./choice-of-error-1b.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-1a.js b/html/semantics/scripting-1/the-script-element/module/choice-of-error-1a.js
new file mode 100644
index 0000000..f479e5e
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-1a.js
@@ -0,0 +1,2 @@
+import './choice-of-error-1b.js';
+import './syntaxerror.js?1c';
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-1b.js b/html/semantics/scripting-1/the-script-element/module/choice-of-error-1b.js
new file mode 100644
index 0000000..257f4a4
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-1b.js
@@ -0,0 +1,2 @@
+import './choice-of-error-1a.js';
+import './syntaxerror.js?1d';
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-2.html b/html/semantics/scripting-1/the-script-element/module/choice-of-error-2.html
new file mode 100644
index 0000000..0d67cb8
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-2.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Choice of instantiation errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Instantiation errors in different files should be reported " +
+        "depending on different roots");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 4);
+
+      // Two different instantiation errors from different module scripts
+      // should be reported for each <script> element.
+      assert_equals(log[0].constructor, SyntaxError);
+      assert_equals(log[1], 1);
+
+      assert_equals(log[2].constructor, SyntaxError);
+      assert_equals(log[3], 2);
+
+      assert_not_equals(log[0], log[2],
+          'two different instantiation errors should be reported');
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./choice-of-error-2a.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./choice-of-error-2b.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-2a.js b/html/semantics/scripting-1/the-script-element/module/choice-of-error-2a.js
new file mode 100644
index 0000000..2dc7aac
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-2a.js
@@ -0,0 +1,2 @@
+import './choice-of-error-2b.js';
+import './instantiation-error-1.js?2c';
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-2b.js b/html/semantics/scripting-1/the-script-element/module/choice-of-error-2b.js
new file mode 100644
index 0000000..2adb9ee
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-2b.js
@@ -0,0 +1,2 @@
+import './choice-of-error-2a.js';
+import './instantiation-error-1.js?2d';
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-3.html b/html/semantics/scripting-1/the-script-element/module/choice-of-error-3.html
new file mode 100644
index 0000000..5c0adff
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-3.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Choice of evaluation errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Evaluation errors are cached in intermediate module scripts");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 5);
+
+      // Evaluation errors, unlike parse/instantiation errors, are remembered
+      // and cached in module scripts between the root and the script that
+      // caused an evaluation error, and thus the same evaluation error
+      // is reported for both <script> elements.
+      assert_equals(log[0], "throw2");
+      assert_true(log[1].bar);
+      assert_equals(log[2], 1);
+
+      assert_true(log[3].bar);
+      assert_equals(log[4], 2);
+
+      assert_equals(log[1], log[3], 'evaluation errors must be the same');
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./choice-of-error-3a.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./choice-of-error-3b.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-3a.js b/html/semantics/scripting-1/the-script-element/module/choice-of-error-3a.js
new file mode 100644
index 0000000..7115467
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-3a.js
@@ -0,0 +1,2 @@
+import './choice-of-error-3b.js';
+import './throw.js?3c';
diff --git a/html/semantics/scripting-1/the-script-element/module/choice-of-error-3b.js b/html/semantics/scripting-1/the-script-element/module/choice-of-error-3b.js
new file mode 100644
index 0000000..2131a35
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/choice-of-error-3b.js
@@ -0,0 +1,2 @@
+import './choice-of-error-3a.js';
+import './throw2.js?3d';
diff --git a/html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html b/html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html
new file mode 100644
index 0000000..57002a3
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Importing a module multiple times with the same specifier</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+window.log = [];
+</script>
+<script type="module">
+import { foo } from './export-something.js';
+import { set_foo } from './export-something.js';
+import default1 from './export-default.js';
+import default2 from './export-default.js';
+
+test(() => {
+  assert_array_equals(log, ['export-something', 'export-default']);
+  assert_equals(foo, 42);
+  set_foo(43);
+  assert_equals(foo, 43);
+  assert_equals(default1, "fox");
+  assert_equals(default2, "fox");
+}, 'Duplicated imports with the same specifier');
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html b/html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html
new file mode 100644
index 0000000..6a01495
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Importing a module multiple times with the different specifier</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+window.log = [];
+</script>
+<script type="module">
+import { foo } from './export-something.js';
+import { set_foo } from '../module/export-something.js';
+import default1 from './export-default.js';
+import default2 from '../module/export-default.js';
+
+test(() => {
+  assert_array_equals(log, ['export-something', 'export-default']);
+  assert_equals(foo, 42);
+  set_foo(43);
+  assert_equals(foo, 43);
+  assert_equals(default1, "fox");
+  assert_equals(default2, "fox");
+}, 'Duplicated imports with the different specifier');
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html
index 9390ce0..1578f85 100644
--- a/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html
@@ -9,7 +9,7 @@
   ["parse error", "../syntaxerror.js", new SyntaxError],
   ["bad module specifier", "does-not-start-with-dot.js", new TypeError, { differentErrorObjects: true }],
   ["bad module specifier in a dependency", "../bad-module-specifier.js", new TypeError],
-  ["instantiation error", "../instantiation-error-1.js", new SyntaxError],
+  ["instantiation error", "../instantiation-error-1.js", new SyntaxError, { differentErrorObjects: true }],
   ["evaluation error", "../throw-error.js", new Error]
 ];
 
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/Function.js b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/Function.js
new file mode 100644
index 0000000..bc88bf7
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/Function.js
@@ -0,0 +1 @@
+Function(`import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`)();
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/eval.js b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/eval.js
new file mode 100644
index 0000000..a8bcffe
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/eval.js
@@ -0,0 +1 @@
+eval(`import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`);
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/inline-event-handlers-UA-code.js b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/inline-event-handlers-UA-code.js
new file mode 100644
index 0000000..c0bd865
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/inline-event-handlers-UA-code.js
@@ -0,0 +1,2 @@
+window.dummyDiv.setAttribute("onclick", `import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`);
+window.dummyDiv.click(); // different from **on**click()
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/reflected-inline-event-handlers.js b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/reflected-inline-event-handlers.js
new file mode 100644
index 0000000..f19ec2b
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/reflected-inline-event-handlers.js
@@ -0,0 +1,2 @@
+window.dummyDiv.setAttribute("onclick", `import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`);
+window.dummyDiv.onclick();
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/setTimeout.js b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/setTimeout.js
new file mode 100644
index 0000000..c6f2dda
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/scripts/setTimeout.js
@@ -0,0 +1 @@
+setTimeout(`import('../../imports-a.js?label=' + window.label).then(window.continueTest, window.errorTest)`, 0);
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-classic.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-classic.html
deleted file mode 100644
index 062bb62..0000000
--- a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-classic.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>import() inside compiled strings uses the document base URL inside a classic script</title>
-<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
-
-<base href="..">
-
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="dummy"></div>
-
-<script>
-function createTestPromise() {
-  return new Promise((resolve, reject) => {
-    window.continueTest = resolve;
-    window.errorTest = reject;
-  });
-}
-
-const dummyDiv = document.querySelector("#dummy");
-
-const evaluators = {
-  eval,
-  setTimeout,
-  "the Function constructor"(x) {
-    Function(x)();
-  },
-  "reflected inline event handlers"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.onclick();
-  },
-  "inline event handlers triggered via UA code"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.click(); // different from .**on**click()
-  }
-};
-
-for (const [label, evaluator] of Object.entries(evaluators)) {
-  promise_test(t => {
-    t.add_cleanup(() => {
-      dummyDiv.removeAttribute("onclick");
-      delete window.evaluated_imports_a;
-    });
-
-    const promise = createTestPromise();
-
-    evaluator(`import('./imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
-
-    return promise.then(module => {
-      assert_true(window.evaluated_imports_a, "The module must have been evaluated");
-      assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
-    });
-  }, label + " should successfully import");
-};
-</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html
new file mode 100644
index 0000000..7cf2dac
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-classic.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL inside a classic script that is loaded from a file</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script>
+function load(scriptSrc) {
+  const el = document.createElement("script");
+  el.src = scriptSrc;
+  document.body.appendChild(el);
+}
+
+function createTestPromise() {
+  return new Promise((resolve, reject) => {
+    window.dummyDiv.removeAttribute("onclick");
+    delete window.evaluated_imports_a;
+    delete window.label;
+
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+window.dummyDiv = document.querySelector("#dummy");
+
+const evaluators = [
+  "setTimeout",
+  "eval",
+  "Function",
+  "reflected-inline-event-handlers",
+  "inline-event-handlers-UA-code"
+];
+
+for (const label of evaluators) {
+  promise_test(() => {
+    const promise = createTestPromise();
+
+    window.label = label;
+    load(`scripts/${label}.js`);
+
+    return promise.then(module => {
+      assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+      assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+    });
+  }, label + " should successfully import");
+};
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html
new file mode 100644
index 0000000..73986c2
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-external-module.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL inside a module script that is loaded from a file</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script type="module">
+function load(scriptSrc) {
+  const el = document.createElement("script");
+  el.type = "module";
+  el.src = scriptSrc;
+  document.body.appendChild(el);
+}
+
+function createTestPromise() {
+  return new Promise((resolve, reject) => {
+    window.dummyDiv.removeAttribute("onclick");
+    delete window.evaluated_imports_a;
+    delete window.label;
+
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+window.dummyDiv = document.querySelector("#dummy");
+
+const evaluators = [
+  "setTimeout",
+  "eval",
+  "Function",
+  "reflected-inline-event-handlers",
+  "inline-event-handlers-UA-code"
+];
+
+for (const label of evaluators) {
+  promise_test(() => {
+    const promise = createTestPromise();
+
+    window.label = label;
+    load(`scripts/${label}.js`);
+
+    return promise.then(module => {
+      assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+      assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+    });
+  }, label + " should successfully import");
+};
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html
new file mode 100644
index 0000000..1bd6d7d
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-classic.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL (= document base URL) inside an inline classic script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<base href="..">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script>
+function createTestPromise() {
+  return new Promise((resolve, reject) => {
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+const evaluators = {
+  setTimeout,
+  eval,
+  "the Function constructor"(x) {
+    Function(x)();
+  },
+  "reflected inline event handlers"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.onclick();
+  },
+  "inline event handlers triggered via UA code"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.click(); // different from .**on**click()
+  }
+};
+
+for (const [label, evaluator] of Object.entries(evaluators)) {
+  promise_test(t => {
+    t.add_cleanup(() => {
+      dummyDiv.removeAttribute("onclick");
+      delete window.evaluated_imports_a;
+    });
+
+    const promise = createTestPromise();
+
+    evaluator(`import('./imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+    return promise.then(module => {
+      assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+      assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+    });
+  }, label + " should successfully import");
+};
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html
new file mode 100644
index 0000000..f5b8574
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the script base URL (= document base URL) inside an inline module script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<base href="..">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script type="module">
+function createTestPromise() {
+  return new Promise((resolve, reject) => {
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+const evaluators = {
+  setTimeout,
+  eval,
+  "the Function constructor"(x) {
+    Function(x)();
+  },
+  "reflected inline event handlers"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.onclick();
+  },
+  "inline event handlers triggered via UA code"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.click(); // different from .**on**click()
+  }
+};
+
+for (const [label, evaluator] of Object.entries(evaluators)) {
+  promise_test(t => {
+    t.add_cleanup(() => {
+      dummyDiv.removeAttribute("onclick");
+      delete window.evaluated_imports_a;
+    });
+
+    const promise = createTestPromise();
+
+    evaluator(`import('./imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+    return promise.then(module => {
+      assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+      assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+    });
+  }, label + " should successfully import");
+};
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-module.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-module.html
deleted file mode 100644
index de51c9a..0000000
--- a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-module.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>import() inside compiled strings uses the document base URL inside a module script</title>
-<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
-
-<base href="..">
-
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="dummy"></div>
-
-<script type="module">
-function createTestPromise() {
-  return new Promise((resolve, reject) => {
-    window.continueTest = resolve;
-    window.errorTest = reject;
-  });
-}
-
-const dummyDiv = document.querySelector("#dummy");
-
-const evaluators = {
-  eval,
-  setTimeout,
-  "the Function constructor"(x) {
-    Function(x)();
-  },
-  "reflected inline event handlers"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.onclick();
-  },
-  "inline event handlers triggered via UA code"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.click(); // different from .**on**click()
-  }
-};
-
-for (const [label, evaluator] of Object.entries(evaluators)) {
-  promise_test(t => {
-    t.add_cleanup(() => {
-      dummyDiv.removeAttribute("onclick");
-      delete window.evaluated_imports_a;
-    });
-
-    const promise = createTestPromise();
-
-    evaluator(`import('./imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
-
-    return promise.then(module => {
-      assert_true(window.evaluated_imports_a, "The module must have been evaluated");
-      assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
-    });
-  }, label + " should successfully import");
-};
-</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html
new file mode 100644
index 0000000..80a2989
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() doesn't have any integrity metadata when initiated by compiled strings inside a classic script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<meta http-equiv="Content-Security-Policy" content="require-sri-for script">
+
+<script src="/resources/testharness.js" integrity="sha384-{{file_hash(sha384, resources/testharness.js)}}"></script>
+<script src="/resources/testharnessreport.js" integrity="sha384-{{file_hash(sha384, resources/testharnessreport.js)}}"></script>
+
+<div id="dummy"></div>
+
+<script>
+function createTestPromise() {
+  return new Promise((resolve, reject) => {
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+const evaluators = {
+  eval,
+  setTimeout,
+  "the Function constructor"(x) {
+    Function(x)();
+  },
+  "reflected inline event handlers"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.onclick();
+  },
+  "inline event handlers triggered via UA code"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.click(); // different from .**on**click()
+  }
+};
+
+for (const [label, evaluator] of Object.entries(evaluators)) {
+  promise_test(t => {
+    t.add_cleanup(() => {
+      dummyDiv.removeAttribute("onclick");
+      delete window.evaluated_imports_a;
+    });
+
+    const promise = createTestPromise();
+
+    evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+    return promise_rejects(t, new TypeError(), promise);
+  }, label + " should fail to import");
+};
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html
new file mode 100644
index 0000000..db3d3b1
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() doesn't have any integrity metadata when initiated by compiled strings inside a module script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<meta http-equiv="Content-Security-Policy" content="require-sri-for script">
+
+<script src="/resources/testharness.js" integrity="sha384-{{file_hash(sha384, resources/testharness.js)}}"></script>
+<script src="/resources/testharnessreport.js" integrity="sha384-{{file_hash(sha384, resources/testharnessreport.js)}}"></script>
+
+<div id="dummy"></div>
+
+<script type="module">
+function createTestPromise() {
+  return new Promise((resolve, reject) => {
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+const dummyDiv = document.querySelector("#dummy");
+
+const evaluators = {
+  eval,
+  setTimeout,
+  "the Function constructor"(x) {
+    Function(x)();
+  },
+  "reflected inline event handlers"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.onclick();
+  },
+  "inline event handlers triggered via UA code"(x) {
+    dummyDiv.setAttribute("onclick", x);
+    dummyDiv.click(); // different from .**on**click()
+  }
+};
+
+for (const [label, evaluator] of Object.entries(evaluators)) {
+  promise_test(t => {
+    t.add_cleanup(() => {
+      dummyDiv.removeAttribute("onclick");
+      delete window.evaluated_imports_a;
+    });
+
+    const promise = createTestPromise();
+
+    evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
+
+    return promise_rejects(t, new TypeError(), promise);
+  }, label + " should fail to import");
+};
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html
new file mode 100644
index 0000000..ba82fe8
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the appropriate nonce inside a classic script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct' 'unsafe-eval' 'unsafe-hashed-attributes' 'sha256-cAMzxBL19bKt4KwKGbxy/ZOFIIjH5AmRjlVbsD5pvNw=' 'sha256-3VjoJYNK/9HJMS8rrZHlqSZgUssDY+GPyc7AU8lNM3k='">
+
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script nonce="correct">
+"use strict";
+const dummyDiv = document.querySelector("#dummy");
+
+function createTestPromise(t) {
+  t.add_cleanup(() => {
+    delete window.evaluated_imports_a;
+    delete window.unreached;
+    delete window.continueTest;
+    delete window.errorTest;
+  });
+
+  return new Promise((resolve, reject) => {
+    window.unreached = t.unreached_func("Must not reach this");
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+function assertSuccessful(module) {
+  assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+  assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+}
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  setTimeout(`import('../imports-a.js?label=setTimeout').then(window.unreached, window.continueTest)`, 0);
+
+  return promise.then(assertSuccessful);
+}, "setTimeout must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  eval(`import('../imports-a.js?label=direct eval').then(window.continueTest, window.errorTest)`);
+
+  return promise.then(assertSuccessful);
+}, "direct eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  const evalAlias = eval;
+  evalAlias(`import('../imports-a.js?label=indirect eval').then(window.continueTest, window.errorTest)`);
+
+  return promise.then(assertSuccessful);
+}, "indirect eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  Function(`import('../imports-a.js?label=the Function constructor').then(window.continueTest, window.errorTest)`)();
+
+  return promise.then(assertSuccessful);
+}, "the Function constructor must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    dummyDiv.removeAttribute("onclick");
+  });
+
+  const promise = createTestPromise(t);
+
+  // This only works because of the 'unsafe-hashed-attributes' and the hash in the CSP policy
+  dummyDiv.setAttribute(
+    "onclick",
+    `import('../imports-a.js?label=reflected inline event handlers').then(window.continueTest, window.errorTest)`
+  );
+  dummyDiv.onclick();
+
+  return promise.then(assertSuccessful);
+}, "reflected inline event handlers must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    dummyDiv.removeAttribute("onclick");
+  });
+
+  const promise = createTestPromise(t);
+
+  // This only works because of the 'unsafe-hashed-attributes' and the hash in the CSP policy
+  dummyDiv.setAttribute(
+    "onclick",
+    `import('../imports-a.js?label=inline event handlers triggered via UA code').then(window.continueTest, window.errorTest)`
+  );
+  dummyDiv.click(); // different from **on**click()
+
+  return promise.then(assertSuccessful);
+}, "inline event handlers triggered via UA code must inherit the nonce from the triggering script, thus execute");
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html
new file mode 100644
index 0000000..889628f
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings uses the appropriate nonce inside a module script</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<meta http-equiv="content-security-policy" content="script-src 'nonce-correct' 'unsafe-eval' 'unsafe-hashed-attributes' 'sha256-cAMzxBL19bKt4KwKGbxy/ZOFIIjH5AmRjlVbsD5pvNw=' 'sha256-3VjoJYNK/9HJMS8rrZHlqSZgUssDY+GPyc7AU8lNM3k='">
+
+<script nonce="correct" src="/resources/testharness.js"></script>
+<script nonce="correct" src="/resources/testharnessreport.js"></script>
+
+<div id="dummy"></div>
+
+<script type="module" nonce="correct">
+const dummyDiv = document.querySelector("#dummy");
+
+function createTestPromise(t) {
+  t.add_cleanup(() => {
+    delete window.evaluated_imports_a;
+    delete window.unreached;
+    delete window.continueTest;
+    delete window.errorTest;
+  });
+
+  return new Promise((resolve, reject) => {
+    window.unreached = t.unreached_func("Must not reach this");
+    window.continueTest = resolve;
+    window.errorTest = reject;
+  });
+}
+
+function assertSuccessful(module) {
+  assert_true(window.evaluated_imports_a, "The module must have been evaluated");
+  assert_equals(module.A.from, "imports-a.js", "The module namespace object must be correct");
+}
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  setTimeout(`import('../imports-a.js?label=setTimeout').then(window.unreached, window.continueTest)`, 0);
+
+  return promise.then(assertSuccessful);
+}, "setTimeout must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  eval(`import('../imports-a.js?label=direct eval').then(window.continueTest, window.errorTest)`);
+
+  return promise.then(assertSuccessful);
+}, "direct eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  const evalAlias = eval;
+  evalAlias(`import('../imports-a.js?label=indirect eval').then(window.continueTest, window.errorTest)`);
+
+  return promise.then(assertSuccessful);
+}, "indirect eval must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  const promise = createTestPromise(t);
+
+  Function(`import('../imports-a.js?label=the Function constructor').then(window.continueTest, window.errorTest)`)();
+
+  return promise.then(assertSuccessful);
+}, "the Function constructor must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    dummyDiv.removeAttribute("onclick");
+  });
+
+  const promise = createTestPromise(t);
+
+  // This only works because of the 'unsafe-hashed-attributes' and the hash in the CSP policy
+  dummyDiv.setAttribute(
+    "onclick",
+    `import('../imports-a.js?label=reflected inline event handlers').then(window.continueTest, window.errorTest)`
+  );
+  dummyDiv.onclick();
+
+  return promise.then(assertSuccessful);
+}, "reflected inline event handlers must inherit the nonce from the triggering script, thus execute");
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    dummyDiv.removeAttribute("onclick");
+  });
+
+  const promise = createTestPromise(t);
+
+  // This only works because of the 'unsafe-hashed-attributes' and the hash in the CSP policy
+  dummyDiv.setAttribute(
+    "onclick",
+    `import('../imports-a.js?label=inline event handlers triggered via UA code').then(window.continueTest, window.errorTest)`
+  );
+  dummyDiv.click(); // different from **on**click()
+
+  return promise.then(assertSuccessful);
+}, "inline event handlers triggered via UA code must inherit the nonce from the triggering script, thus execute");
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html
new file mode 100644
index 0000000..e0e3ec8
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>import() inside compiled strings inside a classic script</title>
+<link rel="help" href="https://github.com/whatwg/html/pull/3163">
+<link rel="help" href="https://github.com/tc39/ecma262/issues/871#issuecomment-292493142">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+self.ran = false;
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    self.ran = false;
+  })
+
+  return Promise.resolve(`import("../imports-a.js?1").then(() => { self.ran = true; })`)
+    .then(eval)
+    .then(() => {
+      assert_true(self.ran);
+    });
+}, "Evaled the script via eval, successful import");
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    self.ran = false;
+  })
+
+  return Promise.resolve(`import("bad-specifier?1").catch(() => { self.ran = true; })`)
+    .then(eval)
+    .then(() => {
+      assert_true(self.ran);
+    });
+}, "Evaled the script via eval, failed import");
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    self.ran = false;
+  })
+
+  return Promise.resolve(`return import("../imports-a.js?2").then(() => { self.ran = true; })`)
+    .then(Function)
+    .then(Function.prototype.call.bind(Function.prototype.call))
+    .then(() => {
+      assert_true(self.ran);
+    });
+}, "Evaled the script via Function, successful import");
+
+promise_test(t => {
+  t.add_cleanup(() => {
+    self.ran = false;
+  })
+
+  return Promise.resolve(`return import("bad-specifier?2").catch(() => { self.ran = true; })`)
+    .then(Function)
+    .then(Function.prototype.call.bind(Function.prototype.call))
+    .then(() => {
+      assert_true(self.ran);
+    });
+}, "Evaled the script via Function, failed import");
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/error-type-1.html b/html/semantics/scripting-1/the-script-element/module/error-type-1.html
new file mode 100644
index 0000000..6f119e3
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/error-type-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Handling of different types of errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "network error has higher priority than parse error");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 3);
+
+      // A parse error is reported for the first top-level
+      // <script> element for syntaxerror.js.
+      assert_equals(log[0].constructor, SyntaxError);
+      assert_equals(log[1], 1);
+
+      // onerror is called (with no errors reported) due to a network error
+      // for the second top-level <script>.
+      assert_equals(log[2], 2);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./syntaxerror.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./error-type-1.js"
+    onerror="log.push(2)" onload="unreachable()"></script>
diff --git a/html/semantics/scripting-1/the-script-element/module/error-type-1.js b/html/semantics/scripting-1/the-script-element/module/error-type-1.js
new file mode 100644
index 0000000..4882d3f
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/error-type-1.js
@@ -0,0 +1,2 @@
+import './syntaxerror.js';
+import './404.js';
diff --git a/html/semantics/scripting-1/the-script-element/module/error-type-2.html b/html/semantics/scripting-1/the-script-element/module/error-type-2.html
new file mode 100644
index 0000000..a7df1df
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/error-type-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Handling of different types of errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "parse error has higher priority than instantiation error");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 4);
+
+      // An instantiation error is reported for the first top-level
+      // <script> element for instantiation-error-1.js.
+      assert_equals(log[0].constructor, SyntaxError);
+      assert_equals(log[1], 1);
+
+      // A parse error is reported for the second top-level <script>.
+      assert_equals(log[2].constructor, SyntaxError);
+      assert_equals(log[3], 2);
+      assert_not_equals(log[0], log[2]);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./instantiation-error-1.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./error-type-2.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/html/semantics/scripting-1/the-script-element/module/error-type-2.js b/html/semantics/scripting-1/the-script-element/module/error-type-2.js
new file mode 100644
index 0000000..6b11397
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/error-type-2.js
@@ -0,0 +1,2 @@
+import './instantiation-error-1.js';
+import './syntaxerror.js';
diff --git a/html/semantics/scripting-1/the-script-element/module/error-type-3.html b/html/semantics/scripting-1/the-script-element/module/error-type-3.html
new file mode 100644
index 0000000..9b00df3
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/error-type-3.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Handling of different types of errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "instantiation error has higher priority than evaluation error");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 5);
+
+      // An evaluation error is reported for the first top-level
+      // <script> element for throw.js.
+      assert_equals(log[0], 'throw');
+      assert_true(log[1].foo);
+      assert_equals(log[2], 1);
+
+      // An instantiation error is reported for the second top-level <script>.
+      assert_equals(log[3].constructor, SyntaxError);
+      assert_equals(log[4], 2);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./throw.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./error-type-3.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
diff --git a/html/semantics/scripting-1/the-script-element/module/error-type-3.js b/html/semantics/scripting-1/the-script-element/module/error-type-3.js
new file mode 100644
index 0000000..542be52
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/error-type-3.js
@@ -0,0 +1,2 @@
+import './throw.js';
+import './instantiation-error-1.js';
diff --git a/html/semantics/scripting-1/the-script-element/module/export-default.js b/html/semantics/scripting-1/the-script-element/module/export-default.js
new file mode 100644
index 0000000..283830a
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/export-default.js
@@ -0,0 +1,2 @@
+log.push("export-default");
+export default "fox";
diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html
index 3c853b2..fa1c7de 100644
--- a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html
+++ b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.html
@@ -20,4 +20,26 @@
   assert_equals(importMetaOnDependentModule.url,
                 base + "/import-meta-dependent.js");
 }, "import.meta.url in a dependent external script");
+
+import { importMetaOnRootModule as hashedImportMetaOnRootModule1,
+         importMetaOnDependentModule as hashedImportMetaOnDependentModule1 }
+       from "./import-meta-root.js#1";
+
+import { importMetaOnRootModule as hashedImportMetaOnRootModule2,
+         importMetaOnDependentModule as hashedImportMetaOnDependentModule2 }
+       from "./import-meta-root.js#2";
+
+test(() => {
+  assert_equals(hashedImportMetaOnRootModule1.url,
+                base + "/import-meta-root.js#1");
+  assert_equals(hashedImportMetaOnRootModule2.url,
+                base + "/import-meta-root.js#2");
+
+  // Must not be affected
+  assert_equals(hashedImportMetaOnDependentModule1.url,
+                base + "/import-meta-dependent.js");
+  assert_equals(hashedImportMetaOnDependentModule2.url,
+                base + "/import-meta-dependent.js");
+}, "import.meta.url when importing the module with different fragments");
+
 </script>
diff --git a/html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html b/html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html
new file mode 100644
index 0000000..db03612
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html
@@ -0,0 +1,29 @@
+<html>
+  <head>
+    <title>Inline async module script execution order</title>
+    <meta name=timeout content=long>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      let loaded = [];
+      let test = async_test("Inline async module script execution order");
+      window.addEventListener("load", test.step_func(function() {
+        assert_array_equals(loaded,
+                            ["fast", "fast", "fast", "slow", "slow", "slow"]);
+      test.done();
+      }));
+    </script>
+    <script type="module" async src="resources/slow-module.js?pipe=trickle(d2)&unique=1"></script>
+    <script type="module" async>
+      import "./resources/slow-module.js?pipe=trickle(d2)&unique=2";
+      loaded.push("slow");
+    </script>
+    <script type="module" async src="resources/fast-module.js?unique=1"></script>
+    <script type="module" async>
+      import "./resources/fast-module.js?unique=2";
+      loaded.push("fast");
+    </script>
+  </body>
+</html>
diff --git a/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html b/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
index efdf587..57b40f5 100644
--- a/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
+++ b/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
@@ -6,17 +6,19 @@
 <script>
     setup({allow_uncaught_exception: true});
 
-    window.log = [];
-
-    window.addEventListener("error", ev => log.push(ev.error));
-
     const test_load = async_test(
         "Test that missing exports lead to SyntaxError events on window and " +
-        "load events on script, and that exceptions are remembered");
+        "load events on script");
+
+    window.log = [];
+    window.addEventListener("error", ev => {
+      test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+      log.push(ev.message);
+    });
+
     window.addEventListener("load", test_load.step_func_done(ev => {
-      const exn = log[0];
-      assert_array_equals(log, [exn, 1, exn, 2, exn, 3, exn, 4, exn, 5]);
-      assert_equals(exn.constructor, SyntaxError);
+      const msg = log[0];
+      assert_array_equals(log, [msg, 1, msg, 2, msg, 3, msg, 4, msg, 5]);
     }));
 
     function unreachable() { log.push("unexpected"); }
diff --git a/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html b/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html
index 3d50ce6..27ba006 100644
--- a/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html
+++ b/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html
@@ -6,17 +6,19 @@
 <script>
     setup({allow_uncaught_exception: true});
 
-    window.log = [];
-
-    window.addEventListener("error", ev => log.push(ev.error));
-
     const test_load = async_test(
         "Test that missing exports lead to SyntaxError events on window and " +
-        "load events on script, and that exceptions are remembered");
+        "load events on script");
+
+    window.log = [];
+    window.addEventListener("error", ev => {
+      test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+      log.push(ev.message);
+    });
+
     window.addEventListener("load", test_load.step_func_done(ev => {
-      const exn = log[0];
-      assert_array_equals(log, [exn, 1, exn, 2, exn, 3, exn, 4, exn, 5]);
-      assert_equals(exn.constructor, SyntaxError);
+      const msg = log[0];
+      assert_array_equals(log, [msg, 1, msg, 2, msg, 3, msg, 4, msg, 5]);
     }));
 
     function unreachable() { log.push("unexpected"); }
diff --git a/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html b/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
index ab510c6..8fa2b8b 100644
--- a/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
+++ b/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
@@ -6,17 +6,19 @@
 <script>
     setup({allow_uncaught_exception: true});
 
-    window.log = [];
-
-    window.addEventListener("error", ev => log.push(ev.error));
-
     const test_load = async_test(
         "Test that unresolvable cycles lead to SyntaxError events on window " +
-        "and load events on script, and that exceptions are remembered");
+        "and load events on script");
+
+    window.log = [];
+    window.addEventListener("error", ev => {
+      test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+      log.push(ev.message);
+    });
+
     window.addEventListener("load", test_load.step_func_done(ev => {
-      const exn = log[0];
-      assert_array_equals(log, [exn, 1, exn, 2, exn, 3]);
-      assert_equals(exn.constructor, SyntaxError);
+      const msg = log[0];
+      assert_array_equals(log, [msg, 1, msg, 2, msg, 3]);
     }));
 
     function unreachable() { log.push("unexpected"); }
@@ -26,4 +28,4 @@
 <script type="module" src="./cycle-unresolvable-a.js"
     onerror="unreachable()" onload="log.push(2)"></script>
 <script type="module" src="./cycle-unresolvable.js"
-    onerror="unreachable()" onload="log.push(3)" async></script>
+    onerror="unreachable()" onload="log.push(3)"></script>
diff --git a/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html b/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html
index 4eb2f9f..238e883 100644
--- a/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html
+++ b/html/semantics/scripting-1/the-script-element/module/instantiation-error-4.html
@@ -7,16 +7,18 @@
     setup({allow_uncaught_exception: true});
 
     window.log = [];
-
-    window.addEventListener("error", ev => log.push(ev.error));
-
     const test_load = async_test(
         "Test that loading a graph in which a module is already " +
-        "errored results in that module's error.");
+        "errored results in an error.");
+
+    window.addEventListener("error", ev => {
+      test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+      log.push(ev.message);
+    });
+
     window.addEventListener("load", test_load.step_func_done(ev => {
-      const exn = log[0];
-      assert_array_equals(log, [exn, 1, exn, 2]);
-      assert_equals(exn.constructor, SyntaxError);
+      const msg = log[0];
+      assert_array_equals(log, [msg, 1, msg, 2]);
     }));
 
     function unreachable() { log.push("unexpected"); }
diff --git a/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html b/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html
index 86d0fb3..de2b6ba 100644
--- a/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html
+++ b/html/semantics/scripting-1/the-script-element/module/instantiation-error-5.html
@@ -6,17 +6,19 @@
 <script>
     setup({allow_uncaught_exception: true});
 
-    window.log = [];
-
-    window.addEventListener("error", ev => log.push(ev.error));
-
     const test_load = async_test(
         "Test that loading a graph in which a module is already " +
-        "errored results in that module's error.");
+        "errored results an error.");
+
+    window.log = [];
+    window.addEventListener("error", ev => {
+      test_load.step(() => assert_equals(ev.error.constructor, SyntaxError));
+      log.push(ev.message);
+    });
+
     window.addEventListener("load", test_load.step_func_done(ev => {
-      const exn = log[0];
-      assert_array_equals(log, [exn, 1, exn, 2]);
-      assert_equals(exn.constructor, SyntaxError);
+      const msg = log[0];
+      assert_array_equals(log, [msg, 1, msg, 2]);
     }));
 
     function unreachable() { log.push("unexpected"); }
diff --git a/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html b/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html
index c4dd080..58397dd 100644
--- a/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html
+++ b/html/semantics/scripting-1/the-script-element/module/load-error-events-inline.html
@@ -59,4 +59,3 @@
 <script onload="onLoad(test4_load);" onerror="onError(test4_load);" type="module" async>"use strict";onExecute(test4_load);</script>
 <script onload="onLoad(test1_error);" onerror="onError(test1_error);" type="module">"use strict";import "./not_found.js";</script>
 <script onload="onLoad(test4_error);" onerror="onError(test4_error);" type="module" async>"use strict";import "./not_found.js";</script>
-</script>
diff --git a/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml b/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml
index 724c9e7..1655e61 100644
--- a/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml
+++ b/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml
@@ -10,7 +10,11 @@
 window.evaluated_module_script = true;
 </script>
 <script>
-test(() => assert_true(window.evaluated_module_script), "module script in XHTML documents should be evaluated.");
+  var test = async_test("module script in XHTML documents should be evaluated.");
+  window.addEventListener("load", () => {
+    test.step(() => { assert_true(window.evaluated_module_script); });
+    test.done();
+  });
 </script>
 </body>
 </html>
diff --git a/html/semantics/scripting-1/the-script-element/module/resources/fast-module.js b/html/semantics/scripting-1/the-script-element/module/resources/fast-module.js
new file mode 100644
index 0000000..3a76cf7
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/resources/fast-module.js
@@ -0,0 +1 @@
+loaded.push("fast");
diff --git a/html/semantics/scripting-1/the-script-element/module/resources/slow-module.js b/html/semantics/scripting-1/the-script-element/module/resources/slow-module.js
new file mode 100644
index 0000000..4623ef7
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/resources/slow-module.js
@@ -0,0 +1,3 @@
+// This module is imported with pipe=trickle(d2) to make it load more slowly
+// than fast-module.js
+loaded.push("slow");
diff --git a/html/semantics/scripting-1/the-script-element/module/throw2.js b/html/semantics/scripting-1/the-script-element/module/throw2.js
new file mode 100644
index 0000000..2931eec
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/module/throw2.js
@@ -0,0 +1,2 @@
+log.push("throw2");
+throw {bar: true}
diff --git a/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html b/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html
new file mode 100644
index 0000000..255e79e
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<script src="cacheable-script-throw.py?iframe"></script>
diff --git a/html/semantics/scripting-1/the-script-element/muted-errors.sub.html b/html/semantics/scripting-1/the-script-element/muted-errors.sub.html
new file mode 100644
index 0000000..a42179d
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/muted-errors.sub.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Muted Errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// https://html.spec.whatwg.org/#report-the-error
+// If script's muted errors is true, then set message to "Script error.",
+// urlString to the empty string, line and col to 0, and errorValue to null.
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+    window.addEventListener("error", ev => log.push(ev));
+
+    function check(shouldBeMuted) {
+      assert_equals(log.length, 1);
+      var ev = log[0];
+      log = [];
+      if (shouldBeMuted) {
+        assert_equals(ev.message, "Script error.");
+        assert_equals(ev.error, null, 'error');
+        assert_equals(ev.filename, "", 'filename');
+        assert_equals(ev.lineno, 0, 'lineno');
+        assert_equals(ev.colno, 0, 'colno');
+      } else {
+        assert_not_equals(ev.message, "Script error.");
+        assert_not_equals(ev.error, null);
+      }
+    }
+
+    var test1 = async_test("Errors for same-origin script shouldn't be muted");
+    var check1 = test1.step_func_done(() => check(false));
+
+    var test2 = async_test("Errors for cross-origin script should be muted");
+    var check2 = test2.step_func_done(() => check(true));
+
+    var test3 = async_test("Errors for cross-origin script should be muted " +
+                           "even if the script is once loaded as same-origin");
+    function step3() {
+      var script = document.createElement('script');
+      script.setAttribute('src', "//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py?iframe");
+      script.onerror = test3.unreached_func();
+      script.onload = test3.step_func_done(() => check(true));
+      document.body.appendChild(script);
+    }
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script src="cacheable-script-throw.py" onerror="test1.unreached_func()()" onload="check1()"></script>
+<script src="//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py"
+    onerror="test2.unreached_func()()" onload="check2()"></script>
+<iframe src="//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html"
+    onerror="test3.unreached_func()()" onload="step3()"></iframe>
diff --git a/html/semantics/scripting-1/the-script-element/script-defer.html b/html/semantics/scripting-1/the-script-element/script-defer.html
new file mode 100644
index 0000000..80eb98d
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/script-defer.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: HTMLScriptElement - defer</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script>
+
+let script_run_status = "inline";
+let t = async_test("the defer script run later");
+
+</script>
+
+<script type="text/javascript" src="defer.js" defer></script>
+
+<script>
+
+t.step(() => {
+  assert_equals(script_run_status, "inline", "the script run status");
+  script_run_status = "deferred";
+});
+
+</script>
diff --git a/html/semantics/tabular-data/the-table-element/caption-methods.html b/html/semantics/tabular-data/the-table-element/caption-methods.html
index 0f576ce..ec95eab 100644
--- a/html/semantics/tabular-data/the-table-element/caption-methods.html
+++ b/html/semantics/tabular-data/the-table-element/caption-methods.html
@@ -55,6 +55,33 @@
     </tr>
     </tbody>
   </table>
+  <table id="table10" style="display:none">
+    <tbody>
+    <tr>
+      <td>cell</td>
+      <td>cell</td>
+    </tr>
+    </tbody>
+    <caption>caption 10</caption>
+  </table>
+  <table id="table11" style="display:none">
+    <caption id="caption11">caption 11</caption>
+  </table>
+  <table id="table12" style="display:none">
+    <caption>caption 1</caption>
+    <caption>caption 2</caption>
+  </table>
+  <table id="table13" style="display:none">
+  </table>
+  <table id="table14" style="display:none">
+    <tbody>
+    <tr>
+      <td>cell</td>
+      <td>cell</td>
+    </tr>
+    </tbody>
+    <caption id="caption14">caption 14</caption>
+  </table>
   <script>
     test(function () {
       var table0 = document.getElementById('table0');
@@ -158,6 +185,92 @@
         table9.caption = caption;
       });
     }, "Assigning a foreign caption to table.caption")
+
+    test(function() {
+      var table = document.createElement("table");
+      var caption = document.createElement("caption");
+      caption.innerHTML = "new caption";
+      table.caption = caption;
+
+      assert_equals(caption.parentNode, table);
+      assert_equals(table.firstChild, caption);
+      assert_equals(table.caption.innerHTML, "new caption");
+    }, "Set table.caption when the table doesn't already have a caption")
+
+    test(function() {
+      var table10 = document.getElementById("table10");
+      var caption = document.createElement("caption");
+      caption.innerHTML = "new caption";
+      table10.caption = caption;
+
+      assert_equals(caption.parentNode, table10);
+      assert_equals(table10.firstChild, caption);
+      assert_equals(table10.caption.innerHTML, "new caption");
+
+      var captions = table10.getElementsByTagName('caption');
+      assert_equals(captions.length, 1);
+    }, "Set table.caption when the table has a caption child but with other siblings before it")
+
+    test(function() {
+      var table11 = document.getElementById("table11");
+      var caption = document.createElement("caption");
+      caption.innerHTML = "new caption";
+      table11.caption = caption;
+
+      assert_equals(caption.parentNode, table11);
+      assert_equals(table11.firstChild, caption);
+      assert_equals(table11.caption.innerHTML, "new caption");
+
+      var captions = table11.getElementsByTagName('caption');
+      assert_equals(captions.length, 1);
+    }, "Set table.caption when the table has a caption descendant")
+
+    test(function() {
+      var table12 = document.getElementById("table12");
+      var caption = document.createElement("caption");
+      caption.innerHTML = "new caption";
+      table12.caption = caption;
+
+      assert_equals(caption.parentNode, table12);
+      assert_equals(table12.firstChild, caption);
+      assert_equals(table12.caption.innerHTML, "new caption");
+
+      var captions = table12.getElementsByTagName('caption');
+      assert_equals(captions.length, 2);
+      assert_equals(captions[0].innerHTML, "new caption");
+      assert_equals(captions[1].innerHTML, "caption 2");
+    }, "Set table.caption when the table has two caption children")
+
+    promise_test(async t => {
+      var table13 = document.getElementById("table13");
+      var iframe = document.createElement("iframe");
+      iframe.srcdoc = '<table><caption id="caption13">caption 13</caption></table>';
+      document.body.appendChild(iframe);
+
+      var iframeWatcher = new EventWatcher(t, iframe, "load");
+      await iframeWatcher.wait_for("load");
+      var caption = iframe.contentWindow.document.getElementById("caption13");
+      table13.caption = caption;
+
+      assert_equals(caption.parentNode, table13);
+      assert_equals(table13.firstChild, caption);
+      assert_equals(table13.caption.innerHTML, "caption 13");
+
+      var captions = table13.getElementsByTagName('caption');
+      assert_equals(captions.length, 1);
+    }, "Assigning a caption has a different owner document to table.caption")
+
+    test(function() {
+      var table14 = document.getElementById("table14");
+      var caption = document.getElementById("caption14");
+      table14.caption = caption;
+
+      assert_equals(caption.parentNode, table14);
+      assert_equals(table14.firstChild, caption);
+
+      var captions = table14.getElementsByTagName('caption');
+      assert_equals(captions.length, 1);
+    }, "Assigning the caption already in the table to table.caption")
   </script>
 </body>
 </html>
diff --git a/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html b/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html
index db9e1cb..3c8adc0 100644
--- a/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html
+++ b/html/semantics/text-level-semantics/the-a-element/a-download-click-404.html
@@ -11,15 +11,15 @@
 async_test(t => {
     const errorFrame = document.createElement("iframe");
 
-    errorFrame.addEventListener("load", function () {
+    errorFrame.addEventListener("load", t.step_func(function () {
         errorFrame.contentWindow.addEventListener(
             "beforeunload", t.unreached_func("Navigated instead of downloading"));
 
         errorFrame.contentDocument.querySelector("#error-url").click();
         t.step_timeout(() => t.done(), 1000);
-    });
+    }));
     errorFrame.src = "resources/a-download-404.html";
     document.body.appendChild(errorFrame);
 }, "Do not navigate to 404 for anchor with download");
 </script>
-</body>
\ No newline at end of file
+</body>
diff --git a/html/semantics/text-level-semantics/the-a-element/a-download-click.html b/html/semantics/text-level-semantics/the-a-element/a-download-click.html
index ad6a3df..22d329f 100644
--- a/html/semantics/text-level-semantics/the-a-element/a-download-click.html
+++ b/html/semantics/text-level-semantics/the-a-element/a-download-click.html
@@ -7,18 +7,27 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 
-<a id="blob-url" download="foo.html">Click me</a>
-
+<body>
 <script>
 "use strict";
+async_test(t => {
+    const frame = document.createElement("iframe");
 
-const string = "test";
-const blob = new Blob([string], { type: "text/html" });
+    frame.addEventListener("load", t.step_func(function () {
+        frame.contentWindow.addEventListener(
+            "beforeunload", t.unreached_func("Navigated instead of downloading"));
+        const string = "test";
+        const blob = new Blob([string], { type: "text/html" });
 
-const link = document.querySelector("#blob-url");
-link.href = URL.createObjectURL(blob);
+        const link = frame.contentDocument.querySelector("#blob-url");
+        link.href = URL.createObjectURL(blob);
 
-link.click();
+        link.click();
 
-done();
+        t.step_timeout(() => t.done(), 1000);
+    }));
+    frame.src = "resources/a-download-click.html";
+    document.body.appendChild(frame);
+}, "Clicking on an <a> element with a download attribute must not throw an exception");
 </script>
+</body>
diff --git a/html/semantics/text-level-semantics/the-a-element/resources/a-download-click.html b/html/semantics/text-level-semantics/the-a-element/resources/a-download-click.html
new file mode 100644
index 0000000..7d36c21
--- /dev/null
+++ b/html/semantics/text-level-semantics/the-a-element/resources/a-download-click.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<a id="blob-url" download="foo.html">Click me</a>
diff --git a/html/semantics/text-level-semantics/the-b-element/b-usage-notref.html b/html/semantics/text-level-semantics/the-b-element/b-usage-notref.html
new file mode 100644
index 0000000..3d3c46a
--- /dev/null
+++ b/html/semantics/text-level-semantics/the-b-element/b-usage-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Reference File</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+
+<p>You enter a small room. Your sword glows brighter. A rat scurries past the corner wall.</p>
diff --git a/html/semantics/text-level-semantics/the-b-element/b-usage.html b/html/semantics/text-level-semantics/the-b-element/b-usage.html
new file mode 100644
index 0000000..ff2105d
--- /dev/null
+++ b/html/semantics/text-level-semantics/the-b-element/b-usage.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML test: b - highlight keywords</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="mismatch" href="b-usage-notref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-b-element"/>
+
+<p>You enter a small room. Your <b>sword</b> glows brighter. A <b>rat</b> scurries past the corner wall.</p>
diff --git a/html/semantics/text-level-semantics/the-ruby-element/ruby-usage-notref.html b/html/semantics/text-level-semantics/the-ruby-element/ruby-usage-notref.html
new file mode 100644
index 0000000..f574781
--- /dev/null
+++ b/html/semantics/text-level-semantics/the-ruby-element/ruby-usage-notref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Reference File</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+
+<p>君くん子しは和わして同どうぜず</p>
diff --git a/html/semantics/text-level-semantics/the-ruby-element/ruby-usage.html b/html/semantics/text-level-semantics/the-ruby-element/ruby-usage.html
new file mode 100644
index 0000000..59c076c
--- /dev/null
+++ b/html/semantics/text-level-semantics/the-ruby-element/ruby-usage.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML test: ruby - mark phrasing content</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="mismatch" href="ruby-usage-notref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-ruby-element"/>
+
+<p><ruby>君<rt>くん</ruby><ruby>子<rt>し</ruby>は<ruby>和<rt>わ</ruby>して<ruby>同<rt>どう</ruby>ぜず</p>
diff --git a/html/syntax/parsing/html-integration-point.html b/html/syntax/parsing/html-integration-point.html
new file mode 100644
index 0000000..be6b42d
--- /dev/null
+++ b/html/syntax/parsing/html-integration-point.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/parsing.html#tree-construction:html-integration-point">
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<math><annotation-xml id="point-1" encoding="text/html"><xmp>&lt;/xmp&gt;&lt;img></xmp></annotation-xml></math>
+<math><annotation-xml id="point-2" encoding="application/xhtml+xml"><style>&lt;/style&gt;&lt;img></style></annotation-xml></math>
+<svg><foreignObject id="point-3"><iframe>&lt;/iframe&gt;&lt;img></iframe></foreignObject></svg>
+<svg><desc id="point-4"><noembed>&lt;/noembed&gt;&lt;img></noembed></desc></svg>
+<svg><title id="point-5"><noframes>&lt;/noframes&gt;&lt;img></noframes></title></svg>
+
+<script>
+function generate_test(id) {
+  return () => {
+    let point = document.querySelector('#' + id);
+    assert_not_equals(point.namespaceURI, 'http://www.w3.org/1999/xhtml');
+    let rawTextElement = point.firstChild;
+    assert_equals(rawTextElement.namespaceURI, 'http://www.w3.org/1999/xhtml');
+    assert_equals(rawTextElement.textContent.substr(0, 4), '&lt;',
+                  'Entity references should not be decoded.');
+  };
+}
+
+test(generate_test('point-1'), 'MathML annotation-xml with encoding=text/html should be an HTML integration point');
+test(generate_test('point-2'), 'MathML annotation-xml with encoding=application/xhtml+xml should be an HTML integration point');
+test(generate_test('point-3'), 'SVG foreignObject should be an HTML integration point');
+test(generate_test('point-4'), 'SVG desc should be an HTML integration point');
+test(generate_test('point-5'), 'SVG title should be an HTML integration point');
+</script>
+</body>
diff --git a/html/syntax/parsing/the-end.html b/html/syntax/parsing/the-end.html
index 0a7babf..181d899 100644
--- a/html/syntax/parsing/the-end.html
+++ b/html/syntax/parsing/the-end.html
@@ -29,12 +29,28 @@
 }, "load");
 
 async_test(function() {
-  var seen = false;
-  document.addEventListener("DOMContentLoaded", this.step_func(function() {
-    seen = true;
+  window.addEventListener("pageshow", this.step_func_done(function(e) {
+    assert_equals(e.type, "pageshow");
+    assert_false(e.bubbles, "bubbles should be false");
+    assert_false(e.cancelable, "cancelable should be false");
+    assert_equals(e.target, document, "target should be document");
+    assert_true(e.isTrusted, "isTrusted should be true");
+    assert_class_string(e, "PageTransitionEvent");
   }));
-  window.addEventListener("load", this.step_func_done(function() {
-    assert_true(seen, "DOMContentLoaded should be fired before load");
+}, "pageshow");
+
+async_test(function() {
+  var seen_dcl = false;
+  var seen_load = false;
+  document.addEventListener("DOMContentLoaded", this.step_func(function() {
+    seen_dcl = true;
+  }));
+  window.addEventListener("load", this.step_func(function() {
+    seen_load = true;
+    assert_true(seen_dcl, "DOMContentLoaded should be fired before load");
+  }));
+  window.addEventListener("pageshow", this.step_func_done(function() {
+    assert_true(seen_load, "load should be fired before pageshow")
   }));
 }, "order");
 </script>
diff --git a/html/tools/update_html5lib_tests.py b/html/tools/update_html5lib_tests.py
index 2235ddf..0bc21d3 100644
--- a/html/tools/update_html5lib_tests.py
+++ b/html/tools/update_html5lib_tests.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 import sys
 import os
 import hashlib
@@ -14,7 +16,7 @@
 
     from html5lib.tests import support
 except ImportError:
-    print """This script requires the Genshi templating library and html5lib source
+    print("""This script requires the Genshi templating library and html5lib source
 
 It is recommended that these are installed in a virtualenv:
 
@@ -30,7 +32,7 @@
 
 Then run this script again, with the virtual environment still active.
 When you are done, type "deactivate" to deactivate the virtual environment.
-"""
+""")
 
 TESTS_PATH = "html/syntax/parsing/"
 
@@ -61,7 +63,7 @@
     tests = []
     innerHTML_tests = []
     ids_seen = {}
-    print input_file_name
+    print(input_file_name)
     for test in test_data:
         if "script-off" in test:
             continue
@@ -73,7 +75,7 @@
         test_list = innerHTML_tests if is_innerHTML else tests
         test_id = get_hash(data, container)
         if test_id in ids_seen:
-            print "WARNING: id %s seen multiple times in file %s this time for test (%s, %s) before for test %s, skipping"%(test_id, input_file_name, container, data, ids_seen[test_id])
+            print("WARNING: id %s seen multiple times in file %s this time for test (%s, %s) before for test %s, skipping"%(test_id, input_file_name, container, data, ids_seen[test_id]))
             continue
         ids_seen[test_id] = (container, data)
         test_list.append({'string_uri_encoded_input':"\"%s\""%urllib.quote(data.encode("utf8")),
diff --git a/html/user-activation/activation-thru-contextmenu-event-manual.html b/html/user-activation/activation-thru-contextmenu-event-manual.html
new file mode 100644
index 0000000..96d17e2
--- /dev/null
+++ b/html/user-activation/activation-thru-contextmenu-event-manual.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>User activation with 'contextmenu' event</title>
+    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
+    <link rel="author" title="Google" href="http://www.google.com "/>
+    <link rel="help" href="https://html.spec.whatwg.org/#triggered-by-user-activation">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      #target {
+        width: 250px;
+        height: 150px;
+        float: left;
+        background-color: green;
+      }
+
+      #done {
+        float: left;
+        padding: 20px;
+        margin: 10px;
+      }
+    </style>
+    <script type="text/javascript">
+      let activation_event_fired = false;
+
+      function run() {
+        let success = false;
+        let test_contextmenu = async_test("'contextmenu' can call vibrate.");
+
+        on_event(document.getElementById("done"), "click", () => {
+          test_contextmenu.step(() => {
+            assert_true(activation_event_fired, "activation event has fired");
+          });
+          test_contextmenu.done();
+        });
+
+        on_event(document.getElementById("target"), "contextmenu", (e) => {
+            test_contextmenu.step(() => {
+              e.preventDefault();
+              assert_true(navigator.vibrate(200), "navigator.vibrate is successful");
+              activation_event_fired = true;
+            });
+        });
+      }
+    </script>
+  </head>
+  <body onload="run()">
+    <h1>User activation with 'contextmenu' event</h1>
+    <h4>Tests that a 'contextmenu' event is treated like a user activation.</h4>
+    <ol>
+      <li>Right-click or long-press on green.</li>
+      <li>Click or tap on Done.</li>
+    </ol>
+    <div id="target"></div>
+    <input type="button" id="done" value="Done" />
+  </body>
+</html>
diff --git a/html/webappapis/animation-frames/idlharness.html b/html/webappapis/animation-frames/idlharness.html
index acc6657..3a9d1d9 100644
--- a/html/webappapis/animation-frames/idlharness.html
+++ b/html/webappapis/animation-frames/idlharness.html
@@ -15,7 +15,7 @@
 <p>This test validates the WebIDL included in the Timing control for script-based animations specification.</p>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global=Window, Exposed=Window]
 interface Window {
 };
 </pre>
diff --git a/html/webappapis/atob/base64.html b/html/webappapis/atob/base64.html
index c33ab22..c522afd 100644
--- a/html/webappapis/atob/base64.html
+++ b/html/webappapis/atob/base64.html
@@ -70,124 +70,6 @@
     // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
 }
 
-/**
- * Implementation of atob() according to the HTML spec, except that instead of
- * throwing INVALID_CHARACTER_ERR we return null.
- */
-function myatob(input) {
-    // WebIDL requires DOMStrings to just be converted using ECMAScript
-    // ToString, which in our case amounts to calling String().
-    input = String(input);
-
-    // "Remove all space characters from input."
-    input = input.replace(/[ \t\n\f\r]/g, "");
-
-    // "If the length of input divides by 4 leaving no remainder, then: if
-    // input ends with one or two U+003D EQUALS SIGN (=) characters, remove
-    // them from input."
-    if (input.length % 4 == 0 && /==?$/.test(input)) {
-        input = input.replace(/==?$/, "");
-    }
-
-    // "If the length of input divides by 4 leaving a remainder of 1, throw an
-    // INVALID_CHARACTER_ERR exception and abort these steps."
-    //
-    // "If input contains a character that is not in the following list of
-    // characters and character ranges, throw an INVALID_CHARACTER_ERR
-    // exception and abort these steps:
-    //
-    // U+002B PLUS SIGN (+)
-    // U+002F SOLIDUS (/)
-    // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)
-    // U+0041 LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z
-    // U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z"
-    if (input.length % 4 == 1
-    || !/^[+/0-9A-Za-z]*$/.test(input)) {
-        return null;
-    }
-
-    // "Let output be a string, initially empty."
-    var output = "";
-
-    // "Let buffer be a buffer that can have bits appended to it, initially
-    // empty."
-    //
-    // We append bits via left-shift and or.  accumulatedBits is used to track
-    // when we've gotten to 24 bits.
-    var buffer = 0;
-    var accumulatedBits = 0;
-
-    // "While position does not point past the end of input, run these
-    // substeps:"
-    for (var i = 0; i < input.length; i++) {
-        // "Find the character pointed to by position in the first column of
-        // the following table. Let n be the number given in the second cell of
-        // the same row."
-        //
-        // "Append to buffer the six bits corresponding to number, most
-        // significant bit first."
-        //
-        // atobLookup() implements the table from the spec.
-        buffer <<= 6;
-        buffer |= atobLookup(input[i]);
-
-        // "If buffer has accumulated 24 bits, interpret them as three 8-bit
-        // big-endian numbers. Append the three characters with code points
-        // equal to those numbers to output, in the same order, and then empty
-        // buffer."
-        accumulatedBits += 6;
-        if (accumulatedBits == 24) {
-            output += String.fromCharCode((buffer & 0xff0000) >> 16);
-            output += String.fromCharCode((buffer & 0xff00) >> 8);
-            output += String.fromCharCode(buffer & 0xff);
-            buffer = accumulatedBits = 0;
-        }
-
-        // "Advance position by one character."
-    }
-
-    // "If buffer is not empty, it contains either 12 or 18 bits. If it
-    // contains 12 bits, discard the last four and interpret the remaining
-    // eight as an 8-bit big-endian number. If it contains 18 bits, discard the
-    // last two and interpret the remaining 16 as two 8-bit big-endian numbers.
-    // Append the one or two characters with code points equal to those one or
-    // two numbers to output, in the same order."
-    if (accumulatedBits == 12) {
-        buffer >>= 4;
-        output += String.fromCharCode(buffer);
-    } else if (accumulatedBits == 18) {
-        buffer >>= 2;
-        output += String.fromCharCode((buffer & 0xff00) >> 8);
-        output += String.fromCharCode(buffer & 0xff);
-    }
-
-    // "Return output."
-    return output;
-}
-
-/**
- * A lookup table for atob(), which converts an ASCII character to the
- * corresponding six-bit number.
- */
-function atobLookup(chr) {
-    if (/[A-Z]/.test(chr)) {
-        return chr.charCodeAt(0) - "A".charCodeAt(0);
-    }
-    if (/[a-z]/.test(chr)) {
-        return chr.charCodeAt(0) - "a".charCodeAt(0) + 26;
-    }
-    if (/[0-9]/.test(chr)) {
-        return chr.charCodeAt(0) - "0".charCodeAt(0) + 52;
-    }
-    if (chr == "+") {
-        return 62;
-    }
-    if (chr == "/") {
-        return 63;
-    }
-    // Throw exception; should not be hit in tests
-}
-
 function btoaException(input) {
     input = String(input);
     for (var i = 0; i < input.length; i++) {
@@ -252,55 +134,40 @@
 
 generate_tests(testBtoa, tests);
 
-function testAtob(input) {
-    var expected = myatob(input);
-    if (expected === null) {
-        assert_throws("InvalidCharacterError", function() { atob(input) });
-        return;
-    }
+promise_test(() => fetch("../../../fetch/data-urls/resources/base64.json").then(res => res.json()).then(runAtobTests), "atob() setup.");
 
-    assert_equals(atob(input), expected);
-}
-
-var tests = ["", "abcd", " abcd", "abcd ", " abcd===", "abcd=== ",
-    "abcd ===", "a", "ab", "abc", "abcde", String.fromCharCode(0xd800, 0xdc00),
-    "=", "==", "===", "====", "=====",
-    "a=", "a==", "a===", "a====", "a=====",
-    "ab=", "ab==", "ab===", "ab====", "ab=====",
-    "abc=", "abc==", "abc===", "abc====", "abc=====",
-    "abcd=", "abcd==", "abcd===", "abcd====", "abcd=====",
-    "abcde=", "abcde==", "abcde===", "abcde====", "abcde=====",
-    "=a", "=a=", "a=b", "a=b=", "ab=c", "ab=c=", "abc=d", "abc=d=",
-    // With whitespace
-    "ab\tcd", "ab\ncd", "ab\fcd", "ab\rcd", "ab cd", "ab\u00a0cd",
-    "ab\t\n\f\r cd", " \t\n\f\r ab\t\n\f\r cd\t\n\f\r ",
-    "ab\t\n\f\r =\t\n\f\r =\t\n\f\r ",
-    // Test if any bits are set at the end.  These should all be fine, since
-    // they end with A, which becomes 0:
-    "A", "/A", "//A", "///A", "////A",
-    // These are all bad, since they end in / (= 63, all bits set) but their
-    // length isn't a multiple of four characters, so they can't be output by
-    // btoa().  Thus one might expect some UAs to throw exceptions or otherwise
-    // object, since they could never be output by btoa(), so they're good to
-    // test.
-    "/", "A/", "AA/", "AAAA/",
-    // But this one is possible:
-    "AAA/",
-    // Binary-safety tests
-    "\0nonsense", "abcd\0nonsense",
-    // WebIDL tests
-    undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0,
-    {toString: function() { return "foo" }},
-    {toString: function() { return "abcd" }},
+const idlTests = [
+  [undefined, null],
+  [null, [158, 233, 101]],
+  [7, null],
+  [12, [215]],
+  [1.5, null],
+  [true, [182, 187]],
+  [false, null],
+  [NaN, [53, 163]],
+  [+Infinity, [34, 119, 226, 158, 43, 114]],
+  [-Infinity, null],
+  [0, null],
+  [-0, null],
+  [{toString: function() { return "foo" }}, [126, 138]],
+  [{toString: function() { return "abcd" }}, [105, 183, 29]]
 ];
-tests = tests.map(
-    function(elem) {
-        if (myatob(elem) === null) {
-            return ["atob(" + format_value(elem) + ") must raise InvalidCharacterError", elem];
-        }
-        return ["atob(" + format_value(elem) + ") == " + format_value(myatob(elem)), elem];
-    }
-);
 
-generate_tests(testAtob, tests);
+function runAtobTests(tests) {
+  const allTests = tests.concat(idlTests);
+  for(let i = 0; i < allTests.length; i++) {
+    const input = allTests[i][0],
+          output = allTests[i][1];
+    test(() => {
+      if(output === null) {
+        assert_throws("InvalidCharacterError", () => window.atob(input));
+      } else {
+        const result = window.atob(input);
+        for(let ii = 0; ii < output.length; ii++) {
+          assert_equals(result.charCodeAt(ii), output[ii]);
+        }
+      }
+    }, "atob(" + format_value(input) + ")");
+  }
+}
 </script>
diff --git a/html/webappapis/scripting/events/event-handler-attributes-window-body.html b/html/webappapis/scripting/events/event-handler-attributes-window-body.html
new file mode 100644
index 0000000..29c4c13
--- /dev/null
+++ b/html/webappapis/scripting/events/event-handler-attributes-window-body.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>HTMLBodyElement event handlers</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<body>
+<script>
+function f() {
+  return 0;
+}
+
+var handlers = ['blur','error','focus','load','resize','scroll',
+                'afterprint','beforeprint','beforeunload','hashchange',
+                'languagechange','message','offline','online','pagehide',
+                'pageshow','popstate','storage','unload'];
+handlers.forEach(function(handler) {
+  test(function() {
+    window['on' + handler] = f;
+    assert_equals(document.body['on' + handler], f);
+  }, handler);
+});
+
+handlers.forEach(function(handler) {
+    window['on' + handler] = null;
+});
+
+handlers.forEach(function(handler) {
+  test(function() {
+    assert_equals(window['on' + handler], null);
+    assert_equals(document.body['on' + handler], null);
+  }, handler + " removal");
+});
+</script>
diff --git a/html/webappapis/scripting/events/event-handler-spec-example.html b/html/webappapis/scripting/events/event-handler-spec-example.html
index c06806e..1f3cff4 100644
--- a/html/webappapis/scripting/events/event-handler-spec-example.html
+++ b/html/webappapis/scripting/events/event-handler-spec-example.html
@@ -15,6 +15,7 @@
     var uncalled = "t.step(function() { assert_unreached('First event handler.') })"
     var button = document.createElement('button');
     button.onclick = object; // event handler listener is registered here
+    assert_equals(button.onclick, object);
     button.addEventListener('click', t.step_func(function () { assert_equals(++i, 2) }), false);
     button.setAttribute('onclick', uncalled);
     button.addEventListener('click', t.step_func(function () { assert_equals(++i, 3) }), false);
@@ -35,6 +36,7 @@
     var uncalled = "t.step(function() { assert_unreached('First event handler.') })"
     var button = document.createElement('button');
     button.onclick = primitive;
+    assert_equals(button.onclick, null);
     button.addEventListener('click', t.step_func(function () { assert_equals(++i, 1) }), false);
     button.setAttribute('onclick', uncalled); // event handler listener is registered here
     button.addEventListener('click', t.step_func(function () { assert_equals(++i, 3) }), false);
diff --git a/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html b/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html
index 109b10e..17cc35c 100644
--- a/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html
+++ b/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html
@@ -33,7 +33,7 @@
   // reason is passed.
   var r = new Error();
   assert_equals(new PromiseRejectionEvent('eventType', { promise: p, reason: r }).reason, r);
-
+  assert_equals(new PromiseRejectionEvent('eventType', { promise: p, reason: null }).reason, null);
 
   // All initializers are passed.
   assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).bubbles, true);
diff --git a/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js b/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
new file mode 100644
index 0000000..624f983
--- /dev/null
+++ b/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
@@ -0,0 +1,39 @@
+/**
+ * Runs a collection of tests that determine if an API implements structured clone
+ * correctly.
+ *
+ * The `runner` parameter has the following properties:
+ * - `setup()`: An optional function run once before testing starts
+ * - `teardown()`: An option function run once after all tests are done
+ * - `preTest()`: An optional, async function run before a test
+ * - `postTest()`: An optional, async function run after a test is done
+ * - `structuredClone(obj, transferList)`: Required function that somehow
+ *                                         structurally clones an object.
+ * - `noTransferTests`: When true, disables tests with transferables
+ */
+
+function runStructuredCloneBatteryOfTests(runner) {
+  const defaultRunner = {
+    setup() {},
+    preTest() {},
+    postTest() {},
+    teardown() {}
+  };
+  runner = Object.assign({}, defaultRunner, runner);
+
+  let setupPromise = runner.setup();
+  const allTests = structuredCloneBatteryOfTests.map(test => {
+
+    return new Promise(resolve => {
+      promise_test(async _ => {
+        test = await test;
+        await setupPromise;
+        await runner.preTest(test);
+        await test.f(runner)
+        await runner.postTest(test);
+        resolve();
+      }, test.description);
+    }).catch(_ => {});
+  });
+  Promise.all(allTests).then(_ => runner.teardown());
+}
diff --git a/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js b/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
new file mode 100644
index 0000000..744f116
--- /dev/null
+++ b/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
@@ -0,0 +1,22 @@
+structuredCloneBatteryOfTests.push({
+  description: 'ArrayBuffer',
+  async f(runner) {
+    const buffer = new Uint8Array([1]).buffer;
+    const copy = await runner.structuredClone(buffer, [buffer]);
+    assert_equals(buffer.byteLength, 0);
+    assert_equals(copy.byteLength, 1);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'MessagePort',
+  async f(runner) {
+    const {port1, port2} = new MessageChannel();
+    const copy = await runner.structuredClone(port2, [port2]);
+    const msg = new Promise(resolve => port1.onmessage = resolve);
+    copy.postMessage('ohai');
+    assert_equals((await msg).data, 'ohai');
+  }
+});
+
+// TODO: ImageBitmap
diff --git a/html/webappapis/structured-clone/structured-clone-battery-of-tests.js b/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
new file mode 100644
index 0000000..40bcd80
--- /dev/null
+++ b/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
@@ -0,0 +1,586 @@
+/* This file is mostly a remix of @zcorpan’s web worker test suite */
+
+structuredCloneBatteryOfTests = [];
+
+function check(description, input, callback) {
+  testObjMock = {
+    done() {},
+    step_func(f) {return _ => f()},
+  };
+
+  structuredCloneBatteryOfTests.push({
+    description,
+    async f(runner) {
+      let newInput = input;
+      if (typeof input === 'function') {
+        newInput = input();
+      }
+      const copy = await runner.structuredClone(newInput);
+      await callback(copy, newInput, testObjMock);
+    }
+  });
+}
+
+function compare_primitive(actual, input, test_obj) {
+  assert_equals(actual, input);
+  if (test_obj)
+    test_obj.done();
+}
+function compare_Array(callback, callback_is_async) {
+  return function(actual, input, test_obj) {
+    if (typeof actual === 'string')
+      assert_unreached(actual);
+    assert_true(actual instanceof Array, 'instanceof Array');
+    assert_not_equals(actual, input);
+    assert_equals(actual.length, input.length, 'length');
+    callback(actual, input);
+    if (test_obj && !callback_is_async)
+      test_obj.done();
+  }
+}
+
+function compare_Object(callback, callback_is_async) {
+  return function(actual, input, test_obj) {
+    if (typeof actual === 'string')
+      assert_unreached(actual);
+    assert_true(actual instanceof Object, 'instanceof Object');
+    assert_false(actual instanceof Array, 'instanceof Array');
+    assert_not_equals(actual, input);
+    callback(actual, input);
+    if (test_obj && !callback_is_async)
+      test_obj.done();
+  }
+}
+
+function enumerate_props(compare_func, test_obj) {
+  return function(actual, input) {
+    for (var x in input) {
+      compare_func(actual[x], input[x], test_obj);
+    }
+  };
+}
+
+check('primitive undefined', undefined, compare_primitive);
+check('primitive null', null, compare_primitive);
+check('primitive true', true, compare_primitive);
+check('primitive false', false, compare_primitive);
+check('primitive string, empty string', '', compare_primitive);
+check('primitive string, lone high surrogate', '\uD800', compare_primitive);
+check('primitive string, lone low surrogate', '\uDC00', compare_primitive);
+check('primitive string, NUL', '\u0000', compare_primitive);
+check('primitive string, astral character', '\uDBFF\uDFFD', compare_primitive);
+check('primitive number, 0.2', 0.2, compare_primitive);
+check('primitive number, 0', 0, compare_primitive);
+check('primitive number, -0', -0, compare_primitive);
+check('primitive number, NaN', NaN, compare_primitive);
+check('primitive number, Infinity', Infinity, compare_primitive);
+check('primitive number, -Infinity', -Infinity, compare_primitive);
+check('primitive number, 9007199254740992', 9007199254740992, compare_primitive);
+check('primitive number, -9007199254740992', -9007199254740992, compare_primitive);
+check('primitive number, 9007199254740994', 9007199254740994, compare_primitive);
+check('primitive number, -9007199254740994', -9007199254740994, compare_primitive);
+
+check('Array primitives', [undefined,
+                           null,
+                           true,
+                           false,
+                           '',
+                           '\uD800',
+                           '\uDC00',
+                           '\u0000',
+                           '\uDBFF\uDFFD',
+                           0.2,
+                           0,
+                           -0,
+                           NaN,
+                           Infinity,
+                           -Infinity,
+                           9007199254740992,
+                           -9007199254740992,
+                           9007199254740994,
+                           -9007199254740994], compare_Array(enumerate_props(compare_primitive)));
+check('Object primitives', {'undefined':undefined,
+                           'null':null,
+                           'true':true,
+                           'false':false,
+                           'empty':'',
+                           'high surrogate':'\uD800',
+                           'low surrogate':'\uDC00',
+                           'nul':'\u0000',
+                           'astral':'\uDBFF\uDFFD',
+                           '0.2':0.2,
+                           '0':0,
+                           '-0':-0,
+                           'NaN':NaN,
+                           'Infinity':Infinity,
+                           '-Infinity':-Infinity,
+                           '9007199254740992':9007199254740992,
+                           '-9007199254740992':-9007199254740992,
+                           '9007199254740994':9007199254740994,
+                           '-9007199254740994':-9007199254740994}, compare_Object(enumerate_props(compare_primitive)));
+
+function compare_Boolean(actual, input, test_obj) {
+  if (typeof actual === 'string')
+    assert_unreached(actual);
+  assert_true(actual instanceof Boolean, 'instanceof Boolean');
+  assert_equals(String(actual), String(input), 'converted to primitive');
+  assert_not_equals(actual, input);
+  if (test_obj)
+    test_obj.done();
+}
+check('Boolean true', new Boolean(true), compare_Boolean);
+check('Boolean false', new Boolean(false), compare_Boolean);
+check('Array Boolean objects', [new Boolean(true), new Boolean(false)], compare_Array(enumerate_props(compare_Boolean)));
+check('Object Boolean objects', {'true':new Boolean(true), 'false':new Boolean(false)}, compare_Object(enumerate_props(compare_Boolean)));
+
+function compare_obj(what) {
+  var Type = window[what];
+  return function(actual, input, test_obj) {
+    if (typeof actual === 'string')
+      assert_unreached(actual);
+    assert_true(actual instanceof Type, 'instanceof '+what);
+    assert_equals(Type(actual), Type(input), 'converted to primitive');
+    assert_not_equals(actual, input);
+    if (test_obj)
+      test_obj.done();
+  };
+}
+check('String empty string', new String(''), compare_obj('String'));
+check('String lone high surrogate', new String('\uD800'), compare_obj('String'));
+check('String lone low surrogate', new String('\uDC00'), compare_obj('String'));
+check('String NUL', new String('\u0000'), compare_obj('String'));
+check('String astral character', new String('\uDBFF\uDFFD'), compare_obj('String'));
+check('Array String objects', [new String(''),
+                               new String('\uD800'),
+                               new String('\uDC00'),
+                               new String('\u0000'),
+                               new String('\uDBFF\uDFFD')], compare_Array(enumerate_props(compare_obj('String'))));
+check('Object String objects', {'empty':new String(''),
+                               'high surrogate':new String('\uD800'),
+                               'low surrogate':new String('\uDC00'),
+                               'nul':new String('\u0000'),
+                               'astral':new String('\uDBFF\uDFFD')}, compare_Object(enumerate_props(compare_obj('String'))));
+
+check('Number 0.2', new Number(0.2), compare_obj('Number'));
+check('Number 0', new Number(0), compare_obj('Number'));
+check('Number -0', new Number(-0), compare_obj('Number'));
+check('Number NaN', new Number(NaN), compare_obj('Number'));
+check('Number Infinity', new Number(Infinity), compare_obj('Number'));
+check('Number -Infinity', new Number(-Infinity), compare_obj('Number'));
+check('Number 9007199254740992', new Number(9007199254740992), compare_obj('Number'));
+check('Number -9007199254740992', new Number(-9007199254740992), compare_obj('Number'));
+check('Number 9007199254740994', new Number(9007199254740994), compare_obj('Number'));
+check('Number -9007199254740994', new Number(-9007199254740994), compare_obj('Number'));
+check('Array Number objects', [new Number(0.2),
+                               new Number(0),
+                               new Number(-0),
+                               new Number(NaN),
+                               new Number(Infinity),
+                               new Number(-Infinity),
+                               new Number(9007199254740992),
+                               new Number(-9007199254740992),
+                               new Number(9007199254740994),
+                               new Number(-9007199254740994)], compare_Array(enumerate_props(compare_obj('Number'))));
+check('Object Number objects', {'0.2':new Number(0.2),
+                               '0':new Number(0),
+                               '-0':new Number(-0),
+                               'NaN':new Number(NaN),
+                               'Infinity':new Number(Infinity),
+                               '-Infinity':new Number(-Infinity),
+                               '9007199254740992':new Number(9007199254740992),
+                               '-9007199254740992':new Number(-9007199254740992),
+                               '9007199254740994':new Number(9007199254740994),
+                               '-9007199254740994':new Number(-9007199254740994)}, compare_Object(enumerate_props(compare_obj('Number'))));
+
+function compare_Date(actual, input, test_obj) {
+  if (typeof actual === 'string')
+    assert_unreached(actual);
+  assert_true(actual instanceof Date, 'instanceof Date');
+  assert_equals(Number(actual), Number(input), 'converted to primitive');
+  assert_not_equals(actual, input);
+  if (test_obj)
+    test_obj.done();
+}
+check('Date 0', new Date(0), compare_Date);
+check('Date -0', new Date(-0), compare_Date);
+check('Date -8.64e15', new Date(-8.64e15), compare_Date);
+check('Date 8.64e15', new Date(8.64e15), compare_Date);
+check('Array Date objects', [new Date(0),
+                             new Date(-0),
+                             new Date(-8.64e15),
+                             new Date(8.64e15)], compare_Array(enumerate_props(compare_Date)));
+check('Object Date objects', {'0':new Date(0),
+                              '-0':new Date(-0),
+                              '-8.64e15':new Date(-8.64e15),
+                              '8.64e15':new Date(8.64e15)}, compare_Object(enumerate_props(compare_Date)));
+
+function compare_RegExp(expected_source) {
+  // XXX ES6 spec doesn't define exact serialization for `source` (it allows several ways to escape)
+  return function(actual, input, test_obj) {
+    if (typeof actual === 'string')
+      assert_unreached(actual);
+    assert_true(actual instanceof RegExp, 'instanceof RegExp');
+    assert_equals(actual.global, input.global, 'global');
+    assert_equals(actual.ignoreCase, input.ignoreCase, 'ignoreCase');
+    assert_equals(actual.multiline, input.multiline, 'multiline');
+    assert_equals(actual.source, expected_source, 'source');
+    assert_equals(actual.sticky, input.sticky, 'sticky');
+    assert_equals(actual.unicode, input.unicode, 'unicode');
+    assert_equals(actual.lastIndex, 0, 'lastIndex');
+    assert_not_equals(actual, input);
+    if (test_obj)
+      test_obj.done();
+  }
+}
+function func_RegExp_flags_lastIndex() {
+  var r = /foo/gim;
+  r.lastIndex = 2;
+  return r;
+}
+function func_RegExp_sticky() {
+  return new RegExp('foo', 'y');
+}
+function func_RegExp_unicode() {
+  return new RegExp('foo', 'u');
+}
+check('RegExp flags and lastIndex', func_RegExp_flags_lastIndex, compare_RegExp('foo'));
+check('RegExp sticky flag', func_RegExp_sticky, compare_RegExp('foo'));
+check('RegExp unicode flag', func_RegExp_unicode, compare_RegExp('foo'));
+check('RegExp empty', new RegExp(''), compare_RegExp('(?:)'));
+check('RegExp slash', new RegExp('/'), compare_RegExp('\\/'));
+check('RegExp new line', new RegExp('\n'), compare_RegExp('\\n'));
+check('Array RegExp object, RegExp flags and lastIndex', [func_RegExp_flags_lastIndex()], compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp sticky flag', function() { return [func_RegExp_sticky()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp unicode flag', function() { return [func_RegExp_unicode()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp empty', [new RegExp('')], compare_Array(enumerate_props(compare_RegExp('(?:)'))));
+check('Array RegExp object, RegExp slash', [new RegExp('/')], compare_Array(enumerate_props(compare_RegExp('\\/'))));
+check('Array RegExp object, RegExp new line', [new RegExp('\n')], compare_Array(enumerate_props(compare_RegExp('\\n'))));
+check('Object RegExp object, RegExp flags and lastIndex', {'x':func_RegExp_flags_lastIndex()}, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp sticky flag', function() { return {'x':func_RegExp_sticky()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp unicode flag', function() { return {'x':func_RegExp_unicode()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp empty', {'x':new RegExp('')}, compare_Object(enumerate_props(compare_RegExp('(?:)'))));
+check('Object RegExp object, RegExp slash', {'x':new RegExp('/')}, compare_Object(enumerate_props(compare_RegExp('\\/'))));
+check('Object RegExp object, RegExp new line', {'x':new RegExp('\n')}, compare_Object(enumerate_props(compare_RegExp('\\n'))));
+
+async function compare_Blob(actual, input, test_obj, expect_File) {
+  if (typeof actual === 'string')
+    assert_unreached(actual);
+  assert_true(actual instanceof Blob, 'instanceof Blob');
+  if (!expect_File)
+    assert_false(actual instanceof File, 'instanceof File');
+  assert_equals(actual.size, input.size, 'size');
+  assert_equals(actual.type, input.type, 'type');
+  assert_not_equals(actual, input);
+  const ab1 = new Response(actual).arrayBuffer();
+  const ab2 = new Response(input).arrayBuffer();
+  assert_equals(ab1.btyeLength, ab2.byteLength, 'byteLength');
+  const ta1 = new Uint8Array(ab1);
+  const ta2 = new Uint8Array(ab2);
+  for(let i = 0; i < ta1.size; i++) {
+    assert_equals(ta1[i], ta2[i]);
+  }
+}
+function func_Blob_basic() {
+  return new Blob(['foo'], {type:'text/x-bar'});
+}
+check('Blob basic', func_Blob_basic, compare_Blob);
+
+function b(str) {
+  return parseInt(str, 2);
+}
+function encode_cesu8(codeunits) {
+  // http://www.unicode.org/reports/tr26/ section 2.2
+  // only the 3-byte form is supported
+  var rv = [];
+  codeunits.forEach(function(codeunit) {
+    rv.push(b('11100000') + ((codeunit & b('1111000000000000')) >> 12));
+    rv.push(b('10000000') + ((codeunit & b('0000111111000000')) >> 6));
+    rv.push(b('10000000') +  (codeunit & b('0000000000111111')));
+  });
+  return rv;
+}
+function func_Blob_bytes(arr) {
+  return function() {
+    var buffer = new ArrayBuffer(arr.length);
+    var view = new DataView(buffer);
+    for (var i = 0; i < arr.length; ++i) {
+      view.setUint8(i, arr[i]);
+    }
+    return new Blob([view]);
+  };
+}
+check('Blob unpaired high surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800])), compare_Blob);
+check('Blob unpaired low surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xDC00])), compare_Blob);
+check('Blob paired surrogates (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800, 0xDC00])), compare_Blob);
+
+function func_Blob_empty() {
+  return new Blob(['']);
+}
+check('Blob empty', func_Blob_empty , compare_Blob);
+function func_Blob_NUL() {
+  return new Blob(['\u0000']);
+}
+check('Blob NUL', func_Blob_NUL, compare_Blob);
+
+check('Array Blob object, Blob basic', [func_Blob_basic()], compare_Array(enumerate_props(compare_Blob), true));
+check('Array Blob object, Blob unpaired high surrogate (invalid utf-8)', [func_Blob_bytes([0xD800])()], compare_Array(enumerate_props(compare_Blob), true));
+check('Array Blob object, Blob unpaired low surrogate (invalid utf-8)', [func_Blob_bytes([0xDC00])()], compare_Array(enumerate_props(compare_Blob), true));
+check('Array Blob object, Blob paired surrogates (invalid utf-8)', [func_Blob_bytes([0xD800, 0xDC00])()], compare_Array(enumerate_props(compare_Blob), true));
+check('Array Blob object, Blob empty', [func_Blob_empty()], compare_Array(enumerate_props(compare_Blob), true));
+check('Array Blob object, Blob NUL', [func_Blob_NUL()], compare_Array(enumerate_props(compare_Blob), true));
+
+check('Object Blob object, Blob basic', {'x':func_Blob_basic()}, compare_Object(enumerate_props(compare_Blob), true));
+check('Object Blob object, Blob unpaired high surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xD800])()}, compare_Object(enumerate_props(compare_Blob), true));
+check('Object Blob object, Blob unpaired low surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xDC00])()}, compare_Object(enumerate_props(compare_Blob), true));
+check('Object Blob object, Blob paired surrogates (invalid utf-8)', {'x':func_Blob_bytes([0xD800, 0xDC00])()  }, compare_Object(enumerate_props(compare_Blob), true));
+check('Object Blob object, Blob empty', {'x':func_Blob_empty()}, compare_Object(enumerate_props(compare_Blob), true));
+check('Object Blob object, Blob NUL', {'x':func_Blob_NUL()}, compare_Object(enumerate_props(compare_Blob), true));
+
+function compare_File(actual, input, test_obj) {
+  assert_true(actual instanceof File, 'instanceof File');
+  assert_equals(actual.name, input.name, 'name');
+  assert_equals(actual.lastModified, input.lastModified, 'lastModified');
+  compare_Blob(actual, input, test_obj, true);
+}
+function func_File_basic() {
+  return new File(['foo'], 'bar', {type:'text/x-bar', lastModified:42});
+}
+check('File basic', func_File_basic, compare_File);
+
+function compare_FileList(actual, input, test_obj) {
+  if (typeof actual === 'string')
+    assert_unreached(actual);
+  assert_true(actual instanceof FileList, 'instanceof FileList');
+  assert_equals(actual.length, input.length, 'length');
+  assert_not_equals(actual, input);
+  // XXX when there's a way to populate or construct a FileList,
+  // check the items in the FileList
+  if (test_obj)
+    test_obj.done();
+}
+function func_FileList_empty() {
+  var input = document.createElement('input');
+  input.type = 'file';
+  return input.files;
+}
+check('FileList empty', func_FileList_empty, compare_FileList);
+check('Array FileList object, FileList empty', [func_FileList_empty()], compare_Array(enumerate_props(compare_FileList)));
+check('Object FileList object, FileList empty', {'x':func_FileList_empty()}, compare_Object(enumerate_props(compare_FileList)));
+
+function compare_ArrayBufferView(view) {
+  var Type = window[view];
+  return function(actual, input, test_obj) {
+    if (typeof actual === 'string')
+      assert_unreached(actual);
+    assert_true(actual instanceof Type, 'instanceof '+view);
+    assert_equals(actual.length, input.length, 'length');
+    assert_not_equals(actual.buffer, input.buffer, 'buffer');
+    for (var i = 0; i < actual.length; ++i) {
+      assert_equals(actual[i], input[i], 'actual['+i+']');
+    }
+    if (test_obj)
+      test_obj.done();
+  };
+}
+function compare_ImageData(actual, input, test_obj) {
+  if (typeof actual === 'string')
+    assert_unreached(actual);
+  assert_equals(actual.width, input.width, 'width');
+  assert_equals(actual.height, input.height, 'height');
+  assert_not_equals(actual.data, input.data, 'data');
+  compare_ArrayBufferView('Uint8ClampedArray')(actual.data, input.data, null);
+  if (test_obj)
+    test_obj.done();
+}
+function func_ImageData_1x1_transparent_black() {
+  var canvas = document.createElement('canvas');
+  var ctx = canvas.getContext('2d');
+  return ctx.createImageData(1, 1);
+}
+check('ImageData 1x1 transparent black', func_ImageData_1x1_transparent_black, compare_ImageData);
+function func_ImageData_1x1_non_transparent_non_black() {
+  var canvas = document.createElement('canvas');
+  var ctx = canvas.getContext('2d');
+  var imagedata = ctx.createImageData(1, 1);
+  imagedata.data[0] = 100;
+  imagedata.data[1] = 101;
+  imagedata.data[2] = 102;
+  imagedata.data[3] = 103;
+  return imagedata;
+}
+check('ImageData 1x1 non-transparent non-black', func_ImageData_1x1_non_transparent_non_black, compare_ImageData);
+check('Array ImageData object, ImageData 1x1 transparent black', [func_ImageData_1x1_transparent_black()], compare_Array(enumerate_props(compare_ImageData)));
+check('Array ImageData object, ImageData 1x1 non-transparent non-black', [func_ImageData_1x1_non_transparent_non_black()], compare_Array(enumerate_props(compare_ImageData)));
+check('Object ImageData object, ImageData 1x1 transparent black', {'x':func_ImageData_1x1_transparent_black()}, compare_Object(enumerate_props(compare_ImageData)));
+check('Object ImageData object, ImageData 1x1 non-transparent non-black', {'x':func_ImageData_1x1_non_transparent_non_black()}, compare_Object(enumerate_props(compare_ImageData)));
+
+
+check('Array sparse', new Array(10), compare_Array(enumerate_props(compare_primitive)));
+check('Array with non-index property', function() {
+  var rv = [];
+  rv.foo = 'bar';
+  return rv;
+}, compare_Array(enumerate_props(compare_primitive)));
+check('Object with index property and length', {'0':'foo', 'length':1}, compare_Object(enumerate_props(compare_primitive)));
+function check_circular_property(prop) {
+  return function(actual) {
+    assert_equals(actual[prop], actual);
+  };
+}
+check('Array with circular reference', function() {
+  var rv = [];
+  rv[0] = rv;
+  return rv;
+}, compare_Array(check_circular_property('0')));
+check('Object with circular reference', function() {
+  var rv = {};
+  rv['x'] = rv;
+  return rv;
+}, compare_Object(check_circular_property('x')));
+function check_identical_property_values(prop1, prop2) {
+  return function(actual) {
+    assert_equals(actual[prop1], actual[prop2]);
+  };
+}
+check('Array with identical property values', function() {
+  var obj = {}
+  return [obj, obj];
+}, compare_Array(check_identical_property_values('0', '1')));
+check('Object with identical property values', function() {
+  var obj = {}
+  return {'x':obj, 'y':obj};
+}, compare_Object(check_identical_property_values('x', 'y')));
+
+function check_absent_property(prop) {
+  return function(actual) {
+    assert_false(prop in actual);
+  };
+}
+check('Object with property on prototype', function() {
+  var Foo = function() {};
+  Foo.prototype = {'foo':'bar'};
+  return new Foo();
+}, compare_Object(check_absent_property('foo')));
+
+check('Object with non-enumerable property', function() {
+  var rv = {};
+  Object.defineProperty(rv, 'foo', {value:'bar', enumerable:false, writable:true, configurable:true});
+  return rv;
+}, compare_Object(check_absent_property('foo')));
+
+function check_writable_property(prop) {
+  return function(actual, input) {
+    assert_equals(actual[prop], input[prop]);
+    actual[prop] += ' baz';
+    assert_equals(actual[prop], input[prop] + ' baz');
+  };
+}
+check('Object with non-writable property', function() {
+  var rv = {};
+  Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:false, configurable:true});
+  return rv;
+}, compare_Object(check_writable_property('foo')));
+
+function check_configurable_property(prop) {
+  return function(actual, input) {
+    assert_equals(actual[prop], input[prop]);
+    delete actual[prop];
+    assert_false('prop' in actual);
+  };
+}
+check('Object with non-configurable property', function() {
+  var rv = {};
+  Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:true, configurable:false});
+  return rv;
+}, compare_Object(check_configurable_property('foo')));
+
+/* The tests below are inspired by @zcorpan’s work but got some
+more substantial changed due to their previous async setup */
+
+function get_canvas_1x1_transparent_black() {
+  var canvas = document.createElement('canvas');
+  canvas.width = 1;
+  canvas.height = 1;
+  return canvas;
+}
+
+function get_canvas_1x1_non_transparent_non_black() {
+  var canvas = document.createElement('canvas');
+  canvas.width = 1;
+  canvas.height = 1;
+  var ctx = canvas.getContext('2d');
+  var imagedata = ctx.getImageData(0, 0, 1, 1);
+  imagedata.data[0] = 100;
+  imagedata.data[1] = 101;
+  imagedata.data[2] = 102;
+  imagedata.data[3] = 103;
+  return canvas;
+}
+
+function compare_ImageBitmap(actual, input) {
+  if (typeof actual === 'string')
+    assert_unreached(actual);
+  assert_true(actual instanceof ImageBitmap, 'instanceof ImageBitmap');
+  assert_not_equals(actual, input);
+  // XXX paint the ImageBitmap on a canvas and check the data
+}
+
+structuredCloneBatteryOfTests.push({
+  description: 'ImageBitmap 1x1 transparent black',
+  async f(runner) {
+    var canvas = get_canvas_1x1_transparent_black();
+    const bm = await createImageBitmap(canvas);
+    const copy = await runner.structuredClone(bm);
+    compare_ImageBitmap(bm, copy);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'ImageBitmap 1x1 non-transparent non-black',
+  async f(runner) {
+    var canvas = get_canvas_1x1_non_transparent_non_black();
+    const bm = await createImageBitmap(canvas);
+    const copy = await runner.structuredClone(bm);
+    compare_ImageBitmap(bm, copy);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent black',
+  async f(runner) {
+    var canvas = get_canvas_1x1_transparent_black();
+    const bm = [await createImageBitmap(canvas)];
+    const copy = await runner.structuredClone(bm);
+    compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent non-black',
+  async f(runner) {
+    var canvas = get_canvas_1x1_non_transparent_non_black();
+    const bm = [await createImageBitmap(canvas)];
+    const copy = await runner.structuredClone(bm);
+    compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent black',
+  async f(runner) {
+    var canvas = get_canvas_1x1_transparent_black();
+    const bm = {x: await createImageBitmap(canvas)};
+    const copy = await runner.structuredClone(bm);
+    compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent non-black',
+  async f(runner) {
+    var canvas = get_canvas_1x1_non_transparent_non_black();
+    const bm = {x: await createImageBitmap(canvas)};
+    const copy = await runner.structuredClone(bm);
+    compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy);
+  }
+});
diff --git a/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js b/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js
index 8cfaef0..07bccb7 100644
--- a/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js
+++ b/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js
@@ -54,7 +54,7 @@
                     "userAgent should return the value sent in the " +
                     "User-Agent header");
     });
-    request.open("GET", "/XMLHttpRequest/resources/inspect-headers.py?" +
+    request.open("GET", "/xhr/resources/inspect-headers.py?" +
                         "filter_name=User-Agent");
     request.send();
   }, "userAgent value");
diff --git a/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.html b/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.html
index a9ceee5..d090aad 100644
--- a/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.html
+++ b/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.html
@@ -58,12 +58,12 @@
 }, 'an empty url argument should throw SYNTAX_ERR');
 
 test(function () {
-  assert_throws('SYNTAX_ERR', function () { navigator.registerProtocolHandler('mailto', 'http://%s.com', 'foo') } );
-}, '%s instead of domain name should throw SYNTAX_ERR');
+  assert_throws('SECURITY_ERR', function () { navigator.registerProtocolHandler('mailto', 'http://%s.com', 'foo') } );
+}, '%s instead of domain name should throw SECURITY_ERR');
 
 test(function () {
-  assert_throws('SYNTAX_ERR', function () { navigator.registerProtocolHandler('mailto', 'http://%s.example.com', 'foo') } );
-}, '%s instead of subdomain name should throw SYNTAX_ERR');
+  assert_throws('SECURITY_ERR', function () { navigator.registerProtocolHandler('mailto', 'http://%s.example.com', 'foo') } );
+}, '%s instead of subdomain name should throw SECURITY_ERR');
 
 test(function () {
   assert_throws('SYNTAX_ERR', function () { navigator.registerProtocolHandler('mailto', location.href + '', 'foo') } );
diff --git a/html/webappapis/timers/missing-timeout-setinterval.any.js b/html/webappapis/timers/missing-timeout-setinterval.any.js
new file mode 100644
index 0000000..33a1cc0
--- /dev/null
+++ b/html/webappapis/timers/missing-timeout-setinterval.any.js
@@ -0,0 +1,34 @@
+function timeout_trampoline(t, timeout, message) {
+  t.step_timeout(function() {
+    // Yield in case we managed to be called before the second interval callback.
+    t.step_timeout(function() {
+      assert_unreached(message);
+    }, timeout);
+  }, timeout);
+}
+
+async_test(function(t) {
+  let ctr = 0;
+  let h = setInterval(t.step_func(function() {
+    if (++ctr == 2) {
+      clearInterval(h);
+      t.done();
+      return;
+    }
+  }) /* no interval */);
+
+  timeout_trampoline(t, 100, "Expected setInterval callback to be called two times");
+}, "Calling setInterval with no interval should be the same as if called with 0 interval");
+
+async_test(function(t) {
+  let ctr = 0;
+  let h = setInterval(t.step_func(function() {
+    if (++ctr == 2) {
+      clearInterval(h);
+      t.done();
+      return;
+    }
+  }),  undefined);
+
+  timeout_trampoline(t, 100, "Expected setInterval callback to be called two times");
+}, "Calling setInterval with undefined interval should be the same as if called with 0 interval");
diff --git a/http/OWNERS b/http/OWNERS
deleted file mode 100644
index cb2fb66..0000000
--- a/http/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-@annevk
-@mnot
diff --git a/http/basic-auth-cache-test.html b/http/basic-auth-cache-test.html
deleted file mode 100644
index 0d3895b..0000000
--- a/http/basic-auth-cache-test.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html>
-<html id="doc" class="reftest-wait">
-  <meta charset="utf-8">
-  <link rel="match" href="basic-auth-cache-test-ref.html">
-
-  <img id="auth" onload="loadNoAuth()">
-  <img id="noauth" onload="removeWait()">
-
-
-  <script type="text/javascript">
-    function loadAuth() {
-        var authUrl = 'http://testuser:testpass@' + window.location.host + '/http/resources/securedimage.py';
-        document.getElementById('auth').src = authUrl;
-    }
-
-    function loadNoAuth() {
-        var noAuthUrl = 'http://' + window.location.host + '/http/resources/securedimage.py';
-        document.getElementById('noauth').src = noAuthUrl;
-    }
-
-    function removeWait() {
-        document.getElementById('doc').className = "";
-    }
-
-    window.onload = loadAuth;
-  </script>
-</html>
diff --git a/http/refresh/README.md b/http/refresh/README.md
deleted file mode 100644
index bad9833..0000000
--- a/http/refresh/README.md
+++ /dev/null
@@ -1 +0,0 @@
-See `../../html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/parsing.html` for more detailed parsing tests (shared with `<meta http-equiv=refresh>`).
diff --git a/http/resources/securedimage.py b/http/resources/securedimage.py
deleted file mode 100644
index 311b0c5..0000000
--- a/http/resources/securedimage.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -
-
-def main(request, response):
-    image_url = str.replace(request.url, "http/resources/securedimage.py", "images/green.png")
-
-    if "authorization" not in request.headers:
-        response.status = 401
-        response.headers.set("WWW-Authenticate", "Basic")
-        return response
-    else:
-        auth = request.headers.get("Authorization")
-        if auth != "Basic dGVzdHVzZXI6dGVzdHBhc3M=":
-            response.set_error(403, "Invalid username or password - " + auth)
-            return response
-
-    response.status = 301
-    response.headers.set("Location", image_url)
diff --git a/imagebitmap-renderingcontext/OWNERS b/imagebitmap-renderingcontext/OWNERS
new file mode 100644
index 0000000..3bb6edf
--- /dev/null
+++ b/imagebitmap-renderingcontext/OWNERS
@@ -0,0 +1,5 @@
+@AmeliaBR
+@annevk
+@kenrussell
+@jdashg
+@fserb
diff --git a/images/blue-png-cachable.py b/images/blue-png-cachable.py
new file mode 100644
index 0000000..e879753
--- /dev/null
+++ b/images/blue-png-cachable.py
@@ -0,0 +1,13 @@
+import os
+import time
+
+def main(request, response):
+  """Serves the contents in blue.png but with a Cache-Control header.
+
+  Emits a Cache-Control header with max-age set to 1h to allow the browser
+  cache the image. Used for testing behaviors involving caching logics.
+  """
+  image_path = os.path.join(os.path.dirname(__file__), "blue.png")
+  response.headers.set("Cache-Control", "max-age=3600")
+  response.headers.set("Content-Type", "image/png")
+  response.content = open(image_path, mode='rb').read()
diff --git a/images/colors.svg b/images/colors.svg
new file mode 100644
index 0000000..024de84
--- /dev/null
+++ b/images/colors.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="100" height="50">
+  <style>use:not(:target) { display: none; }</style>
+  <defs>
+    <rect id="green-box" fill="#00ff00" width="100" height="50"/>
+    <rect id="red-box" fill="#ff0000" width="100" height="50"/>
+  </defs>
+  <use id="green" xlink:href="#green-box"/>
+  <use id="red" xlink:href="#red-box"/>
+</svg>
diff --git a/images/green-96x96.svg b/images/green-96x96.svg
new file mode 100644
index 0000000..fdaffd9
--- /dev/null
+++ b/images/green-96x96.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96">
+  <rect fill="lime" width="96" height="96"/>
+</svg>
diff --git a/images/pattern.mp4 b/images/pattern.mp4
new file mode 100644
index 0000000..c866bfe
--- /dev/null
+++ b/images/pattern.mp4
Binary files differ
diff --git a/images/pattern.svg b/images/pattern.svg
new file mode 100644
index 0000000..40c6ad3
--- /dev/null
+++ b/images/pattern.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">
+  <rect x="00" y="00" width="10" height="10" fill="#ff0000"/>
+  <rect x="10" y="00" width="10" height="10" fill="#00ff00"/>
+  <rect x="00" y="10" width="10" height="10" fill="#0000ff"/>
+  <rect x="10" y="10" width="10" height="10" fill="#000000"/>
+</svg>
diff --git a/images/wpt-logo/wpt-logo-darkblue-bg.svg b/images/wpt-logo/wpt-logo-darkblue-bg.svg
new file mode 100644
index 0000000..49f374c
--- /dev/null
+++ b/images/wpt-logo/wpt-logo-darkblue-bg.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 762 762">
+<rect x="0" y="0" fill="#003C56" width="762" height="762"/>
+<rect x="183.9" y="511.4" fill="#FFFFFF" width="201.5" height="38"/>
+<path fill="#FFFFFF" d="M504.9,549.4c37.3,0,67.6-30.3,67.6-67.6v-19h-38v19c0,16.3-13.3,29.6-29.6,29.6c-16.3,0-29.6-13.3-29.6-29.6
+V287.4h-38v56.5h-51.9v-92.6h112.4c25.5,0,46.3,20.8,46.3,46.3c0,20-13,37.8-31.8,44v39.1c40-6.9,69.8-42.1,69.8-83
+c0-46.5-37.8-84.3-84.3-84.3H366.4c-10.5,0-19,8.5-19,19V386l-46.4-79.4c-3.4-5.8-9.7-9.4-16.4-9.4c-6.7,0-13,3.6-16.4,9.4
+l-46.2,79.2V213.4h-38v242.7c0,8.6,5.8,16.1,14,18.3c8.3,2.2,17.1-1.4,21.4-8.8l65.2-111.8L350,465.7c3.4,5.8,9.7,9.4,16.4,9.4
+c1.7,0,3.3-0.2,5-0.7c8.3-2.2,14-9.8,14-18.3v-74.2h51.9v99.8C437.3,519.1,467.6,549.4,504.9,549.4z"/>
+</svg>
diff --git a/images/wpt-logo/wpt-logo-darkblue.svg b/images/wpt-logo/wpt-logo-darkblue.svg
new file mode 100644
index 0000000..2db07da
--- /dev/null
+++ b/images/wpt-logo/wpt-logo-darkblue.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 398 340">
+<rect x="0" y="300" fill="#003C56" width="201.5" height="38"/>
+<path fill="#003C56" d="M320.9,338c37.3,0,67.6-30.3,67.6-67.6v-19h-38v19c0,16.3-13.3,29.6-29.6,29.6c-16.3,0-29.6-13.3-29.6-29.6V76
+h-38v56.5h-51.9V40h112.4c25.5,0,46.3,20.8,46.3,46.3c0,20-13,37.8-31.8,44v39.1c40-6.9,69.8-42.1,69.8-83
+C398,39.8,360.2,2,313.8,2H182.4c-10.5,0-19,8.5-19,19v153.6l-46.4-79.4c-3.4-5.8-9.7-9.4-16.4-9.4c-6.7,0-13,3.6-16.4,9.4
+l-46.2,79.2V2h-38v242.7c0,8.6,5.8,16.1,14,18.3c8.3,2.2,17.1-1.4,21.4-8.8l65.2-111.8L166,254.3c3.4,5.8,9.7,9.4,16.4,9.4
+c1.7,0,3.3-0.2,5-0.7c8.3-2.2,14-9.8,14-18.3v-74.2h51.9v99.8C253.3,307.7,283.6,338,320.9,338z"/>
+</svg>
diff --git a/images/wpt-logo/wpt-logo-lightblue-bg.svg b/images/wpt-logo/wpt-logo-lightblue-bg.svg
new file mode 100644
index 0000000..2f61672
--- /dev/null
+++ b/images/wpt-logo/wpt-logo-lightblue-bg.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 762 762">
+<rect x="0" y="0" fill="#78D9F4" width="762" height="762"/>
+<rect x="183.9" y="511.4" fill="#003C56" width="201.5" height="38"/>
+<path fill="#003C56" d="M504.9,549.4c37.3,0,67.6-30.3,67.6-67.6v-19h-38v19c0,16.3-13.3,29.6-29.6,29.6c-16.3,0-29.6-13.3-29.6-29.6
+V287.4h-38v56.5h-51.9v-92.6h112.4c25.5,0,46.3,20.8,46.3,46.3c0,20-13,37.8-31.8,44v39.1c40-6.9,69.8-42.1,69.8-83
+c0-46.5-37.8-84.3-84.3-84.3H366.4c-10.5,0-19,8.5-19,19V386l-46.4-79.4c-3.4-5.8-9.7-9.4-16.4-9.4c-6.7,0-13,3.6-16.4,9.4
+l-46.2,79.2V213.4h-38v242.7c0,8.6,5.8,16.1,14,18.3c8.3,2.2,17.1-1.4,21.4-8.8l65.2-111.8L350,465.7c3.4,5.8,9.7,9.4,16.4,9.4
+c1.7,0,3.3-0.2,5-0.7c8.3-2.2,14-9.8,14-18.3v-74.2h51.9v99.8C437.3,519.1,467.6,549.4,504.9,549.4z"/>
+</svg>
diff --git a/images/wpt-logo/wpt-logo-orange-bg.svg b/images/wpt-logo/wpt-logo-orange-bg.svg
new file mode 100644
index 0000000..fde2c15
--- /dev/null
+++ b/images/wpt-logo/wpt-logo-orange-bg.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 762 762">
+<rect x="0" y="0" fill="#E86E43" width="762" height="762"/>
+<rect x="183.9" y="511.4" fill="#FFFFFF" width="201.5" height="38"/>
+<path fill="#FFFFFF" d="M504.9,549.4c37.3,0,67.6-30.3,67.6-67.6v-19h-38v19c0,16.3-13.3,29.6-29.6,29.6c-16.3,0-29.6-13.3-29.6-29.6
+V287.4h-38v56.5h-51.9v-92.6h112.4c25.5,0,46.3,20.8,46.3,46.3c0,20-13,37.8-31.8,44v39.1c40-6.9,69.8-42.1,69.8-83
+c0-46.5-37.8-84.3-84.3-84.3H366.4c-10.5,0-19,8.5-19,19V386l-46.4-79.4c-3.4-5.8-9.7-9.4-16.4-9.4c-6.7,0-13,3.6-16.4,9.4
+l-46.2,79.2V213.4h-38v242.7c0,8.6,5.8,16.1,14,18.3c8.3,2.2,17.1-1.4,21.4-8.8l65.2-111.8L350,465.7c3.4,5.8,9.7,9.4,16.4,9.4
+c1.7,0,3.3-0.2,5-0.7c8.3-2.2,14-9.8,14-18.3v-74.2h51.9v99.8C437.3,519.1,467.6,549.4,504.9,549.4z"/>
+</svg>
diff --git a/images/wpt-logo/wpt-logo-white.svg b/images/wpt-logo/wpt-logo-white.svg
new file mode 100644
index 0000000..4cab7b5
--- /dev/null
+++ b/images/wpt-logo/wpt-logo-white.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 398 340">
+<rect x="0" y="300" fill="#FFFFFF" width="201.5" height="38"/>
+<path fill="#FFFFFF" d="M320.9,338c37.3,0,67.6-30.3,67.6-67.6v-19h-38v19c0,16.3-13.3,29.6-29.6,29.6c-16.3,0-29.6-13.3-29.6-29.6V76
+h-38v56.5h-51.9V40h112.4c25.5,0,46.3,20.8,46.3,46.3c0,20-13,37.8-31.8,44v39.1c40-6.9,69.8-42.1,69.8-83
+C398,39.8,360.2,2,313.8,2H182.4c-10.5,0-19,8.5-19,19v153.6l-46.4-79.4c-3.4-5.8-9.7-9.4-16.4-9.4c-6.7,0-13,3.6-16.4,9.4
+l-46.2,79.2V2h-38v242.7c0,8.6,5.8,16.1,14,18.3c8.3,2.2,17.1-1.4,21.4-8.8l65.2-111.8L166,254.3c3.4,5.8,9.7,9.4,16.4,9.4
+c1.7,0,3.3-0.2,5-0.7c8.3-2.2,14-9.8,14-18.3v-74.2h51.9v99.8C253.3,307.7,283.6,338,320.9,338z"/>
+</svg>
diff --git a/infrastructure/OWNERS b/infrastructure/OWNERS
new file mode 100644
index 0000000..684dcf3
--- /dev/null
+++ b/infrastructure/OWNERS
@@ -0,0 +1,4 @@
+@gsnedders
+@jgraham
+@jugglinmike
+@kereliuk
diff --git a/infrastructure/README.md b/infrastructure/README.md
index 28a764d..82138a3 100644
--- a/infrastructure/README.md
+++ b/infrastructure/README.md
@@ -4,4 +4,6 @@
  * The tests in assumptions/ are designed to test UA assumptions
    documented in [assumptions.md](/docs/_writing-tests/assumptions.md).
 
+ * The tests in server/ are designed to test the WPT server configuration
+
  * The tests in expected-fail/ should all fail.
diff --git a/infrastructure/assumptions/html-elements.html b/infrastructure/assumptions/html-elements.html
index 6be17b1..1cfe1d1 100644
--- a/infrastructure/assumptions/html-elements.html
+++ b/infrastructure/assumptions/html-elements.html
@@ -10,7 +10,7 @@
 div.b {
   all: initial;
   direction: initial;
-  unicode-bidi: initial;
+  unicode-bidi: isolate;
   display: block;
 }
 
diff --git a/infrastructure/assumptions/non-secure-context.any.js b/infrastructure/assumptions/non-secure-context.any.js
new file mode 100644
index 0000000..c05689f
--- /dev/null
+++ b/infrastructure/assumptions/non-secure-context.any.js
@@ -0,0 +1,9 @@
+test(() => {
+  assert_false(self.isSecureContext);
+}, "Lack of .https file name flag implies non-secure context");
+
+test(() => {
+  assert_equals(location.protocol, "http:");
+}, "Lack of .https file name flag implies HTTP scheme");
+
+done();
diff --git a/infrastructure/browsers/firefox/prefs.html b/infrastructure/browsers/firefox/prefs.html
new file mode 100644
index 0000000..51f9f99
--- /dev/null
+++ b/infrastructure/browsers/firefox/prefs.html
@@ -0,0 +1,8 @@
+<title>Ensure that setting gecko prefs works</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+assert_equals(getComputedStyle(document.documentElement).color, "rgb(0, 255, 0)")
+done();
+</script>
+<p>This should be green</p>
diff --git a/infrastructure/expected-fail/timeout.html b/infrastructure/expected-fail/timeout.html
new file mode 100644
index 0000000..29ff348
--- /dev/null
+++ b/infrastructure/expected-fail/timeout.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that should time out</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test()
+</script>
diff --git a/infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini b/infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini
new file mode 100644
index 0000000..93a497d
--- /dev/null
+++ b/infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini
@@ -0,0 +1,6 @@
+[html-elements.html]
+  [Compare CSS div definitions (only valid if pre-reqs pass)]
+    expected:
+      if product == "chrome": FAIL
+
+
diff --git a/infrastructure/metadata/infrastructure/browsers/firefox/__dir__.ini b/infrastructure/metadata/infrastructure/browsers/firefox/__dir__.ini
new file mode 100644
index 0000000..3e0ed18
--- /dev/null
+++ b/infrastructure/metadata/infrastructure/browsers/firefox/__dir__.ini
@@ -0,0 +1,2 @@
+disabled:
+  if product != "firefox": true
\ No newline at end of file
diff --git a/infrastructure/metadata/infrastructure/browsers/firefox/prefs.html.ini b/infrastructure/metadata/infrastructure/browsers/firefox/prefs.html.ini
new file mode 100644
index 0000000..7b78d21
--- /dev/null
+++ b/infrastructure/metadata/infrastructure/browsers/firefox/prefs.html.ini
@@ -0,0 +1,2 @@
+[prefs.html]
+  prefs: ["browser.display.foreground_color:#00FF00"]
diff --git a/infrastructure/metadata/infrastructure/expected-fail/failing-test.html.ini b/infrastructure/metadata/infrastructure/expected-fail/failing-test.html.ini
new file mode 100644
index 0000000..b954a0e
--- /dev/null
+++ b/infrastructure/metadata/infrastructure/expected-fail/failing-test.html.ini
@@ -0,0 +1,4 @@
+[failing-test.html]
+  [Failing test]
+    expected: FAIL
+
diff --git a/infrastructure/metadata/infrastructure/expected-fail/timeout.html.ini b/infrastructure/metadata/infrastructure/expected-fail/timeout.html.ini
new file mode 100644
index 0000000..53b281f
--- /dev/null
+++ b/infrastructure/metadata/infrastructure/expected-fail/timeout.html.ini
@@ -0,0 +1,4 @@
+[timeout.html]
+  expected: TIMEOUT
+  [Test that should time out]
+    expected: NOTRUN
diff --git a/tools/wptrunner/test/metadata/reftest/reftest_and_fail.html.ini b/infrastructure/metadata/infrastructure/reftest/reftest_and_fail.html.ini
similarity index 100%
rename from tools/wptrunner/test/metadata/reftest/reftest_and_fail.html.ini
rename to infrastructure/metadata/infrastructure/reftest/reftest_and_fail.html.ini
diff --git a/tools/wptrunner/test/metadata/reftest/reftest_cycle_fail.html.ini b/infrastructure/metadata/infrastructure/reftest/reftest_cycle_fail.html.ini
similarity index 100%
rename from tools/wptrunner/test/metadata/reftest/reftest_cycle_fail.html.ini
rename to infrastructure/metadata/infrastructure/reftest/reftest_cycle_fail.html.ini
diff --git a/tools/wptrunner/test/metadata/reftest/reftest_match_fail.html.ini b/infrastructure/metadata/infrastructure/reftest/reftest_match_fail.html.ini
similarity index 100%
rename from tools/wptrunner/test/metadata/reftest/reftest_match_fail.html.ini
rename to infrastructure/metadata/infrastructure/reftest/reftest_match_fail.html.ini
diff --git a/tools/wptrunner/test/metadata/reftest/reftest_mismatch_fail.html.ini b/infrastructure/metadata/infrastructure/reftest/reftest_mismatch_fail.html.ini
similarity index 100%
rename from tools/wptrunner/test/metadata/reftest/reftest_mismatch_fail.html.ini
rename to infrastructure/metadata/infrastructure/reftest/reftest_mismatch_fail.html.ini
diff --git a/tools/wptrunner/test/metadata/reftest/reftest_ref_timeout.html.ini b/infrastructure/metadata/infrastructure/reftest/reftest_ref_timeout.html.ini
similarity index 100%
rename from tools/wptrunner/test/metadata/reftest/reftest_ref_timeout.html.ini
rename to infrastructure/metadata/infrastructure/reftest/reftest_ref_timeout.html.ini
diff --git a/tools/wptrunner/test/metadata/reftest/reftest_timeout.html.ini b/infrastructure/metadata/infrastructure/reftest/reftest_timeout.html.ini
similarity index 100%
rename from tools/wptrunner/test/metadata/reftest/reftest_timeout.html.ini
rename to infrastructure/metadata/infrastructure/reftest/reftest_timeout.html.ini
diff --git a/tools/wptrunner/test/testdata/reftest/green-ref.html b/infrastructure/reftest/green-ref.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/green-ref.html
rename to infrastructure/reftest/green-ref.html
diff --git a/tools/wptrunner/test/testdata/reftest/green.html b/infrastructure/reftest/green.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/green.html
rename to infrastructure/reftest/green.html
diff --git a/tools/wptrunner/test/testdata/reftest/red.html b/infrastructure/reftest/red.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/red.html
rename to infrastructure/reftest/red.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest.https.html b/infrastructure/reftest/reftest.https.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest.https.html
rename to infrastructure/reftest/reftest.https.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_and_fail.html b/infrastructure/reftest/reftest_and_fail.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_and_fail.html
rename to infrastructure/reftest/reftest_and_fail.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_and_fail_0-ref.html b/infrastructure/reftest/reftest_and_fail_0-ref.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_and_fail_0-ref.html
rename to infrastructure/reftest/reftest_and_fail_0-ref.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_cycle.html b/infrastructure/reftest/reftest_cycle.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_cycle.html
rename to infrastructure/reftest/reftest_cycle.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_cycle_0-ref.html b/infrastructure/reftest/reftest_cycle_0-ref.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_cycle_0-ref.html
rename to infrastructure/reftest/reftest_cycle_0-ref.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_cycle_1-ref.html b/infrastructure/reftest/reftest_cycle_1-ref.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_cycle_1-ref.html
rename to infrastructure/reftest/reftest_cycle_1-ref.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_cycle_fail.html b/infrastructure/reftest/reftest_cycle_fail.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_cycle_fail.html
rename to infrastructure/reftest/reftest_cycle_fail.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_cycle_fail_0-ref.html b/infrastructure/reftest/reftest_cycle_fail_0-ref.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_cycle_fail_0-ref.html
rename to infrastructure/reftest/reftest_cycle_fail_0-ref.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_match.html b/infrastructure/reftest/reftest_match.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_match.html
rename to infrastructure/reftest/reftest_match.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_match_fail.html b/infrastructure/reftest/reftest_match_fail.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_match_fail.html
rename to infrastructure/reftest/reftest_match_fail.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_mismatch.html b/infrastructure/reftest/reftest_mismatch.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_mismatch.html
rename to infrastructure/reftest/reftest_mismatch.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_mismatch_fail.html b/infrastructure/reftest/reftest_mismatch_fail.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_mismatch_fail.html
rename to infrastructure/reftest/reftest_mismatch_fail.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_or_0.html b/infrastructure/reftest/reftest_or_0.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_or_0.html
rename to infrastructure/reftest/reftest_or_0.html
diff --git a/infrastructure/reftest/reftest_ref_timeout-ref.html b/infrastructure/reftest/reftest_ref_timeout-ref.html
new file mode 100644
index 0000000..2f52c05
--- /dev/null
+++ b/infrastructure/reftest/reftest_ref_timeout-ref.html
@@ -0,0 +1,5 @@
+<html class="reftest-wait">
+<title>rel=match that should time out in the ref</title>
+<style>
+:root {background-color:green}
+</style>
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_ref_timeout.html b/infrastructure/reftest/reftest_ref_timeout.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_ref_timeout.html
rename to infrastructure/reftest/reftest_ref_timeout.html
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_timeout.html b/infrastructure/reftest/reftest_timeout.html
similarity index 100%
rename from tools/wptrunner/test/testdata/reftest/reftest_timeout.html
rename to infrastructure/reftest/reftest_timeout.html
diff --git a/infrastructure/reftest/reftest_wait_0.html b/infrastructure/reftest/reftest_wait_0.html
new file mode 100644
index 0000000..fec62a3
--- /dev/null
+++ b/infrastructure/reftest/reftest_wait_0.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait">
+<title>Test with reftest-wait</title>
+<link rel=match href=green.html>
+<style>
+:root {background-color:red}
+</style>
+<script>
+setTimeout(function() {
+  document.documentElement.style.backgroundColor = "green";
+  document.documentElement.className = "";
+}, 2000);
+</script>
+</html>
diff --git a/infrastructure/server/secure-context.https.any.js b/infrastructure/server/secure-context.https.any.js
new file mode 100644
index 0000000..99f6c4c
--- /dev/null
+++ b/infrastructure/server/secure-context.https.any.js
@@ -0,0 +1,9 @@
+test(() => {
+  assert_true(self.isSecureContext);
+}, "Use of .https file name flag implies secure context");
+
+test(() => {
+  assert_equals(location.protocol, "https:");
+}, "Use of .https file name flag implies HTTPS scheme");
+
+done();
diff --git a/infrastructure/server/wpt-server-http.sub.html b/infrastructure/server/wpt-server-http.sub.html
new file mode 100644
index 0000000..8099b9d
--- /dev/null
+++ b/infrastructure/server/wpt-server-http.sub.html
@@ -0,0 +1,204 @@
+<!doctype html>
+<html>
+  <head>
+    <title>WPT Server checker</title>
+    <meta charset="utf-8" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+</body>
+<body>
+<script>
+function check(protocol, domain, port, done) {
+  var url = protocol + '://' + domain + ':' + port + '/media/1x1-green.png';
+  var img = document.createElement('img');
+  img.setAttribute('src', url);
+  img.style.display = 'none';
+  img.onerror = function() {
+    done(false);
+  };
+  img.onload = function() {
+    done(true);
+  };
+
+  document.body.appendChild(img);
+}
+
+async_test(function(t) {
+  check('http', '{{browser_host}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, no subdomain, port #1');
+
+async_test(function(t) {
+  check('http', '{{browser_host}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, no subdomain, port #2');
+
+async_test(function(t) {
+  check('http', '{{domains[www]}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, www subdomain #1, port #1');
+
+async_test(function(t) {
+  check('http', '{{domains[www]}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, www subdomain #1, port #2');
+
+async_test(function(t) {
+  check('http', '{{domains[www1]}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, www subdomain #2, port #1');
+
+async_test(function(t) {
+  check('http', '{{domains[www1]}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, www subdomain #2, port #2');
+
+async_test(function(t) {
+  check('http', '{{domains[www2]}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, www subdomain #3, port #1');
+
+async_test(function(t) {
+  check('http', '{{domains[www2]}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, www subdomain #3, port #2');
+
+async_test(function(t) {
+  check('http', '{{domains[élève]}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, punycode subdomain #1, port #1');
+
+async_test(function(t) {
+  check('http', '{{domains[élève]}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, punycode subdomain #1, port #2');
+
+async_test(function(t) {
+  check('http', '{{domains[天気の良い日]}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, punycode subdomain #2, port #1');
+
+async_test(function(t) {
+  check('http', '{{domains[天気の良い日]}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, punycode subdomain #2, port #2');
+
+async_test(function(t) {
+  check('http', 'nonexistent.{{domains[]}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_false(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, non-existent domain, port #1');
+
+async_test(function(t) {
+  check('http', 'nonexistent.{{domains[]}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_false(result);
+
+    t.done();
+  }));
+}, 'HTTP protocol, non-existent domain, port #2');
+
+async_test(function(t) {
+  check('https', '{{browser_host}}', {{ports[https][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, no subdomain');
+
+async_test(function(t) {
+  check('https', '{{domains[www]}}', {{ports[https][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, www subdomain #1');
+
+async_test(function(t) {
+  check('https', '{{domains[www1]}}', {{ports[https][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, www subdomain #2');
+
+async_test(function(t) {
+  check('https', '{{domains[www2]}}', {{ports[https][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, www subdomain #3');
+
+async_test(function(t) {
+  check('https', '{{domains[élève]}}', {{ports[https][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, punycode subdomain #1');
+
+async_test(function(t) {
+  check('https', '{{domains[天気の良い日]}}', {{ports[https][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, punycode subdomain #2');
+
+async_test(function(t) {
+  check('https', 'nonexistent.{{domains[]}}', {{ports[http][0]}}, t.step_func(function(result) {
+    assert_false(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, non-existent domain, port #1');
+
+async_test(function(t) {
+  check('https', 'nonexistent.{{domains[]}}', {{ports[http][1]}}, t.step_func(function(result) {
+    assert_false(result);
+
+    t.done();
+  }));
+}, 'HTTPS protocol, non-existent domain, port #2');
+</script>
+</body>
+</html>
diff --git a/infrastructure/server/wpt-server-websocket.sub.html b/infrastructure/server/wpt-server-websocket.sub.html
new file mode 100644
index 0000000..ea7973a
--- /dev/null
+++ b/infrastructure/server/wpt-server-websocket.sub.html
@@ -0,0 +1,122 @@
+<!doctype html>
+<html>
+  <head>
+    <title>WPT Server checker</title>
+    <meta charset="utf-8" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+</body>
+<body>
+<script>
+function check(protocol, domain, port, done) {
+  var url = protocol + '://' + domain + ':' + port + '/echo';
+  var ws = new WebSocket(url);
+
+  ws.addEventListener('error', function() {
+    done(false);
+  });
+
+  ws.addEventListener('open', function() {
+    done(true);
+  });
+}
+
+async_test(function(t) {
+  check('ws', '{{browser_host}}', {{ports[ws][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WS protocol, no subdomain');
+
+async_test(function(t) {
+  check('ws', '{{domains[www1]}}', {{ports[ws][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WS protocol, www subdomain #1');
+
+async_test(function(t) {
+  check('ws', '{{domains[www1]}}', {{ports[ws][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WS protocol, www subdomain #2');
+
+async_test(function(t) {
+  check('ws', '{{domains[www2]}}', {{ports[ws][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WS protocol, www subdomain #3');
+
+async_test(function(t) {
+  check('ws', '{{domains[élève]}}', {{ports[ws][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WS protocol, punycode subdomain #1');
+
+async_test(function(t) {
+  check('ws', '{{domains[天気の良い日]}}', {{ports[ws][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WS protocol, punycode subdomain #2');
+
+async_test(function(t) {
+  check('wss', '{{browser_host}}', {{ports[wss][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WSS protocol, no subdomain');
+
+async_test(function(t) {
+  check('wss', '{{domains[www1]}}', {{ports[wss][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WSS protocol, www subdomain #1');
+
+async_test(function(t) {
+  check('wss', '{{domains[www1]}}', {{ports[wss][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WSS protocol, www subdomain #2');
+
+async_test(function(t) {
+  check('wss', '{{domains[www2]}}', {{ports[wss][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WSS protocol, www subdomain #3');
+
+async_test(function(t) {
+  check('wss', '{{domains[élève]}}', {{ports[wss][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WSS protocol, punycode subdomain #1');
+
+async_test(function(t) {
+  check('wss', '{{domains[天気の良い日]}}', {{ports[wss][0]}}, t.step_func(function(result) {
+    assert_true(result);
+
+    t.done();
+  }));
+}, 'WSS protocol, punycode subdomain #2');
+</script>
+</body>
+</html>
diff --git a/infrastructure/testdriver/click.html b/infrastructure/testdriver/click.html
new file mode 100644
index 0000000..37721ad
--- /dev/null
+++ b/infrastructure/testdriver/click.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver click method</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<button type="button" id="button">Button</button>
+
+<script>
+async_test(t => {
+  let button = document.getElementById("button");
+  test_driver
+    .click(button)
+    .then(() => t.done())
+    .catch(t.unreached_func("click failed"));
+});
+</script>
diff --git a/infrastructure/testdriver/send_keys.html b/infrastructure/testdriver/send_keys.html
new file mode 100644
index 0000000..2170347
--- /dev/null
+++ b/infrastructure/testdriver/send_keys.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver send keys method</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<input type="text" id="text">Text Input</button>
+
+<script>
+async_test(t => {
+  let input_text = "Hello, wpt!";
+  let text_box = document.getElementById("text");
+  test_driver
+    .send_keys(text_box, input_text)
+    .then(() => {
+      assert_true(text_box.value == input_text);
+      t.done();
+    })
+    .catch(t.unreached_func("send keys failed"));
+});
+</script>
diff --git a/infrastructure/webdriver/tests/conftest.py b/infrastructure/webdriver/tests/conftest.py
new file mode 100644
index 0000000..cbc4f83
--- /dev/null
+++ b/infrastructure/webdriver/tests/conftest.py
@@ -0,0 +1,7 @@
+import os
+import sys
+# Hack to avoid duplicating the conftest file
+wdpath = os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                      "../../../webdriver/"))
+sys.path.insert(0, wdpath)
+from tests.conftest import *
diff --git a/infrastructure/webdriver/tests/test_load_file.py b/infrastructure/webdriver/tests/test_load_file.py
new file mode 100644
index 0000000..370e63c
--- /dev/null
+++ b/infrastructure/webdriver/tests/test_load_file.py
@@ -0,0 +1,4 @@
+from tests.support.inline import inline
+
+def test_load(session):
+    inline("PASS")
diff --git a/innerText/getter-tests.js b/innerText/getter-tests.js
deleted file mode 100644
index 6df8912..0000000
--- a/innerText/getter-tests.js
+++ /dev/null
@@ -1,350 +0,0 @@
-testText("<div>abc", "abc", "Simplest possible test");
-
-/**** white-space:normal ****/
-
-testText("<div> abc", "abc", "Leading whitespace removed");
-testText("<div>abc ", "abc", "Trailing whitespace removed");
-testText("<div>abc  def", "abc def", "Internal whitespace compressed");
-testText("<div>abc\ndef", "abc def", "\\n converted to space");
-testText("<div>abc\rdef", "abc def", "\\r converted to space");
-testText("<div>abc\tdef", "abc def", "\\t converted to space");
-testText("<div>abc <br>def", "abc\ndef", "Trailing whitespace before hard line break removed");
-
-/**** <pre> ****/
-
-testText("<pre> abc", " abc", "Leading whitespace preserved");
-testText("<pre>abc ", "abc ", "Trailing whitespace preserved");
-testText("<pre>abc  def", "abc  def", "Internal whitespace preserved");
-testText("<pre>abc\ndef", "abc\ndef", "\\n preserved");
-testText("<pre>abc\rdef", "abc\ndef", "\\r converted to newline");
-testText("<pre>abc\tdef", "abc\tdef", "\\t preserved");
-testText("<div><pre>abc</pre><pre>def</pre>", "abc\ndef", "Two <pre> siblings");
-
-/**** <div style="white-space:pre"> ****/
-
-testText("<div style='white-space:pre'> abc", " abc", "Leading whitespace preserved");
-testText("<div style='white-space:pre'>abc ", "abc ", "Trailing whitespace preserved");
-testText("<div style='white-space:pre'>abc  def", "abc  def", "Internal whitespace preserved");
-testText("<div style='white-space:pre'>abc\ndef", "abc\ndef", "\\n preserved");
-testText("<div style='white-space:pre'>abc\rdef", "abc\ndef", "\\r converted to newline");
-testText("<div style='white-space:pre'>abc\tdef", "abc\tdef", "\\t preserved");
-
-/**** <span style="white-space:pre"> ****/
-
-testText("<span style='white-space:pre'> abc", " abc", "Leading whitespace preserved");
-testText("<span style='white-space:pre'>abc ", "abc ", "Trailing whitespace preserved");
-testText("<span style='white-space:pre'>abc  def", "abc  def", "Internal whitespace preserved");
-testText("<span style='white-space:pre'>abc\ndef", "abc\ndef", "\\n preserved");
-testText("<span style='white-space:pre'>abc\rdef", "abc\ndef", "\\r converted to newline");
-testText("<span style='white-space:pre'>abc\tdef", "abc\tdef", "\\t preserved");
-
-/**** <div style="white-space:pre-line"> ****/
-
-testText("<div style='white-space:pre-line'> abc", "abc", "Leading whitespace removed");
-testText("<div style='white-space:pre-line'>abc ", "abc", "Trailing whitespace removed");
-testText("<div style='white-space:pre-line'>abc  def", "abc def", "Internal whitespace collapsed");
-testText("<div style='white-space:pre-line'>abc\ndef", "abc\ndef", "\\n preserved");
-testText("<div style='white-space:pre-line'>abc\rdef", "abc\ndef", "\\r converted to newline");
-testText("<div style='white-space:pre-line'>abc\tdef", "abc def", "\\t converted to space");
-
-/**** Collapsing whitespace across element boundaries ****/
-
-testText("<div><span>abc </span> def", "abc def", "Whitespace collapses across element boundaries");
-testText("<div><span>abc </span><span></span> def", "abc def", "Whitespace collapses across element boundaries");
-testText("<div><span>abc </span><span style='white-space:pre'></span> def", "abc def", "Whitespace collapses across element boundaries");
-
-/**** Soft line breaks ****/
-
-testText("<div style='width:0'>abc def", "abc def", "Soft line breaks ignored");
-
-/**** first-line/first-letter ****/
-
-testText("<div class='first-line-uppercase' style='width:0'>abc def", "ABC def", "::first-line styles applied");
-testText("<div class='first-letter-uppercase' style='width:0'>abc def", "Abc def", "::first-letter styles applied");
-testText("<div class='first-letter-float' style='width:0'>abc def", "abc def", "::first-letter float ignored");
-
-/**** &nbsp; ****/
-
-testText("<div>&nbsp;", "\xA0", "&nbsp; preserved");
-
-/**** display:none ****/
-
-testText("<div style='display:none'>abc", "abc", "display:none container");
-testText("<div style='display:none'>abc  def", "abc  def", "No whitespace compression in display:none container");
-testText("<div style='display:none'> abc def ", " abc def ", "No removal of leading/trailing whitespace in display:none container");
-testText("<div>123<span style='display:none'>abc", "123", "display:none child not rendered");
-testText("<div style='display:none'><span id='target'>abc", "abc", "display:none container with non-display-none target child");
-testTextInSVG("<div id='target'>abc", "", "non-display-none child of svg");
-testTextInSVG("<div style='display:none' id='target'>abc", "abc", "display:none child of svg");
-testTextInSVG("<div style='display:none'><div id='target'>abc", "abc", "child of display:none child of svg");
-
-/**** display:contents ****/
-
-if (CSS.supports("display", "contents")) {
-  testText("<div style='display:contents'>abc", "abc", "display:contents container");
-  testText("<div><div style='display:contents'>abc", "abc", "display:contents container");
-  testText("<div>123<span style='display:contents'>abc", "123abc", "display:contents rendered");
-  testText("<div style='display:contents'>   ", "", "display:contents not processed via textContent");
-  testText("<div><div style='display:contents'>   ", "", "display:contents not processed via textContent");
-}
-
-/**** visibility:hidden ****/
-
-testText("<div style='visibility:hidden'>abc", "", "visibility:hidden container");
-testText("<div>123<span style='visibility:hidden'>abc", "123", "visibility:hidden child not rendered");
-testText("<div style='visibility:hidden'>123<span style='visibility:visible'>abc", "abc", "visibility:visible child rendered");
-
-/**** visibility:collapse ****/
-
-testText("<table><tbody style='visibility:collapse'><tr><td>abc", "", "visibility:collapse row-group");
-testText("<table><tr style='visibility:collapse'><td>abc", "", "visibility:collapse row");
-testText("<table><tr><td style='visibility:collapse'>abc", "", "visibility:collapse cell");
-testText("<table><tbody style='visibility:collapse'><tr><td style='visibility:visible'>abc", "abc",
-         "visibility:collapse row-group with visible cell");
-testText("<table><tr style='visibility:collapse'><td style='visibility:visible'>abc", "abc",
-         "visibility:collapse row with visible cell");
-testText("<div style='display:flex'><span style='visibility:collapse'>1</span><span>2</span></div>",
-         "2", "visibility:collapse honored on flex item");
-testText("<div style='display:grid'><span style='visibility:collapse'>1</span><span>2</span></div>",
-         "2", "visibility:collapse honored on grid item");
-
-/**** opacity:0 ****/
-
-testText("<div style='opacity:0'>abc", "abc", "opacity:0 container");
-testText("<div style='opacity:0'>abc  def", "abc def", "Whitespace compression in opacity:0 container");
-testText("<div style='opacity:0'> abc def ", "abc def", "Remove leading/trailing whitespace in opacity:0 container");
-testText("<div>123<span style='opacity:0'>abc", "123abc", "opacity:0 child rendered");
-
-/**** generated content ****/
-
-testText("<div class='before'>", "", "Generated content not included");
-testText("<div><div class='before'>", "", "Generated content on child not included");
-
-/**** innerText on replaced elements ****/
-
-testText("<button>abc", "abc", "<button> contents preserved");
-testText("<fieldset>abc", "abc", "<fieldset> contents preserved");
-testText("<fieldset><legend>abc", "abc", "<fieldset> <legend> contents preserved");
-testText("<input type='text' value='abc'>", "", "<input> contents ignored");
-testText("<textarea>abc", "", "<textarea> contents ignored");
-testText("<iframe>abc", "", "<iframe> contents ignored");
-testText("<iframe><div id='target'>abc", "", "<iframe> contents ignored");
-testText("<iframe src='data:text/html,abc'>", "","<iframe> subdocument ignored");
-testText("<audio style='display:block'>abc", "", "<audio> contents ignored");
-testText("<audio style='display:block'><source id='target' class='poke' style='display:block'>", "", "<audio> contents ignored");
-testText("<audio style='display:block'><source id='target' class='poke' style='display:none'>", "abc", "<audio> contents ok if display:none");
-testText("<video>abc", "", "<video> contents ignored");
-testText("<video style='display:block'><source id='target' class='poke' style='display:block'>", "", "<video> contents ignored");
-testText("<video style='display:block'><source id='target' class='poke' style='display:none'>", "abc", "<video> contents ok if display:none");
-testText("<canvas>abc", "", "<canvas> contents ignored");
-testText("<canvas><div id='target'>abc", "", "<canvas><div id='target'> contents ignored");
-testText("<img alt='abc'>", "", "<img> alt text ignored");
-testText("<img src='about:blank' class='poke'>", "", "<img> contents ignored");
-
-/**** <select>, <optgroup> & <option> ****/
-
-testText("<select size='1'><option>abc</option><option>def", "abc\ndef", "<select size='1'> contents of options preserved");
-testText("<select size='2'><option>abc</option><option>def", "abc\ndef", "<select size='2'> contents of options preserved");
-testText("<select size='1'><option id='target'>abc</option><option>def", "abc", "<select size='1'> contents of target option preserved");
-testText("<select size='2'><option id='target'>abc</option><option>def", "abc", "<select size='2'> contents of target option preserved");
-testText("<div>a<select></select>bc", "abc", "empty <select>");
-testText("<div>a<select><optgroup></select>bc", "a\nbc", "empty <optgroup> in <select>");
-testText("<div>a<select><option></select>bc", "a\nbc", "empty <option> in <select>");
-testText("<select class='poke'></select>", "", "<select> containing text node child");
-testText("<select><optgroup class='poke-optgroup'></select>", "", "<optgroup> containing <optgroup>");
-testText("<select><optgroup><option>abc</select>", "abc", "<optgroup> containing <option>");
-testText("<select><option class='poke-div'>123</select>", "123\nabc", "<div> in <option>");
-testText("<div>a<optgroup></optgroup>bc", "a\nbc", "empty <optgroup> in <div>");
-testText("<div>a<optgroup>123</optgroup>bc", "a\nbc", "<optgroup> in <div>");
-testText("<div>a<option></option>bc", "a\nbc", "empty <option> in <div>");
-testText("<div>a<option>123</option>bc", "a\n123\nbc", "<option> in <div>");
-
-/**** innerText on replaced element children ****/
-
-testText("<div><button>abc", "abc", "<button> contents preserved");
-testText("<div><fieldset>abc", "abc", "<fieldset> contents preserved");
-testText("<div><fieldset><legend>abc", "abc", "<fieldset> <legend> contents preserved");
-testText("<div><input type='text' value='abc'>", "", "<input> contents ignored");
-testText("<div><textarea>abc", "", "<textarea> contents ignored");
-testText("<div><select size='1'><option>abc</option><option>def", "abc\ndef", "<select size='1'> contents of options preserved");
-testText("<div><select size='2'><option>abc</option><option>def", "abc\ndef", "<select size='2'> contents of options preserved");
-testText("<div><iframe>abc", "", "<iframe> contents ignored");
-testText("<div><iframe src='data:text/html,abc'>", ""," <iframe> subdocument ignored");
-testText("<div><audio>abc", "", "<audio> contents ignored");
-testText("<div><video>abc", "", "<video> contents ignored");
-testText("<div><canvas>abc", "", "<canvas> contents ignored");
-testText("<div><img alt='abc'>", "", "<img> alt text ignored");
-
-/**** Lines around blocks ****/
-
-testText("<div>123<div>abc</div>def", "123\nabc\ndef", "Newline at block boundary");
-testText("<div>123<span style='display:block'>abc</span>def", "123\nabc\ndef", "Newline at display:block boundary");
-testText("<div>abc<div></div>def", "abc\ndef", "Empty block induces single line break");
-testText("<div>abc<div></div><div></div>def", "abc\ndef", "Consecutive empty blocks ignored");
-testText("<div><p>abc", "abc", "No blank lines around <p> alone");
-testText("<div><p>abc</p> ", "abc", "No blank lines around <p> followed by only collapsible whitespace");
-testText("<div> <p>abc</p>", "abc", "No blank lines around <p> preceded by only collapsible whitespace");
-testText("<div><p>abc<p>def", "abc\n\ndef", "Blank line between consecutive <p>s");
-testText("<div><p>abc</p> <p>def", "abc\n\ndef", "Blank line between consecutive <p>s separated only by collapsible whitespace");
-testText("<div><p>abc</p><div></div><p>def", "abc\n\ndef", "Blank line between consecutive <p>s separated only by empty block");
-testText("<div><p>abc</p><div>123</div><p>def", "abc\n\n123\n\ndef", "Blank lines between <p>s separated by non-empty block");
-testText("<div>abc<div><p>123</p></div>def", "abc\n\n123\n\ndef", "Blank lines around a <p> in its own block");
-testText("<div>abc<p>def", "abc\n\ndef", "Blank line before <p>");
-testText("<div><p>abc</p>def", "abc\n\ndef", "Blank line after <p>");
-testText("<div><p>abc<p></p><p></p><p>def", "abc\n\ndef", "One blank line between <p>s, ignoring empty <p>s");
-testText("<div style='visibility:hidden'><p><span style='visibility:visible'>abc</span></p>\n<div style='visibility:visible'>def</div>",
-     "abc\ndef", "Invisible <p> doesn't induce extra line breaks");
-testText("<div>abc<div style='margin:2em'>def", "abc\ndef", "No blank lines around <div> with margin");
-testText("<div>123<span style='display:inline-block'>abc</span>def", "123abcdef", "No newlines at display:inline-block boundary");
-testText("<div>123<span style='display:inline-block'> abc </span>def", "123abcdef", "Leading/trailing space removal at display:inline-block boundary");
-testText("<div>123<p style='margin:0px'>abc</p>def", "123\n\nabc\n\ndef", "Blank lines around <p> even without margin");
-testText("<div>123<h1>abc</h1>def", "123\nabc\ndef", "No blank lines around <h1>");
-testText("<div>123<h2>abc</h2>def", "123\nabc\ndef", "No blank lines around <h2>");
-testText("<div>123<h3>abc</h3>def", "123\nabc\ndef", "No blank lines around <h3>");
-testText("<div>123<h4>abc</h4>def", "123\nabc\ndef", "No blank lines around <h4>");
-testText("<div>123<h5>abc</h5>def", "123\nabc\ndef", "No blank lines around <h5>");
-testText("<div>123<h6>abc</h6>def", "123\nabc\ndef", "No blank lines around <h6>");
-
-/**** Spans ****/
-
-testText("<div>123<span>abc</span>def", "123abcdef", "<span> boundaries are irrelevant");
-testText("<div>123 <span>abc</span> def", "123 abc def", "<span> boundaries are irrelevant");
-testText("<div style='width:0'>123 <span>abc</span> def", "123 abc def", "<span> boundaries are irrelevant");
-testText("<div>123<em>abc</em>def", "123abcdef", "<em> gets no special treatment");
-testText("<div>123<b>abc</b>def", "123abcdef", "<b> gets no special treatment");
-testText("<div>123<i>abc</i>def", "123abcdef", "<i> gets no special treatment");
-testText("<div>123<strong>abc</strong>def", "123abcdef", "<strong> gets no special treatment");
-testText("<div>123<tt>abc</tt>def", "123abcdef", "<tt> gets no special treatment");
-testText("<div>123<code>abc</code>def", "123abcdef", "<code> gets no special treatment");
-
-/**** Soft hyphen ****/
-
-testText("<div>abc&shy;def", "abc\xADdef", "soft hyphen preserved");
-testText("<div style='width:0'>abc&shy;def", "abc\xADdef", "soft hyphen preserved");
-
-/**** Tables ****/
-
-testText("<div><table style='white-space:pre'>  <td>abc</td>  </table>", "abc", "Ignoring non-rendered table whitespace");
-testText("<div><table><tr><td>abc<td>def</table>", "abc\tdef", "Tab-separated table cells");
-testText("<div><table><tr><td>abc<td><td>def</table>", "abc\t\tdef", "Tab-separated table cells including empty cells");
-testText("<div><table><tr><td>abc<td><td></table>", "abc\t\t", "Tab-separated table cells including trailing empty cells");
-testText("<div><table><tr><td>abc<tr><td>def</table>", "abc\ndef", "Newline-separated table rows");
-testText("<div>abc<table><td>def</table>ghi", "abc\ndef\nghi", "Newlines around table");
-testText("<div><table style='border-collapse:collapse'><tr><td>abc<td>def</table>", "abc\tdef",
-         "Tab-separated table cells in a border-collapse table");
-testText("<div><table><tfoot>x</tfoot><tbody>y</tbody></table>", "xy", "tfoot not reordered");
-testText("<table><tfoot><tr><td>footer</tfoot><thead><tr><td style='visibility:collapse'>thead</thead><tbody><tr><td>tbody</tbody></table>",
-         "footer\n\ntbody", "");
-
-/**** Table captions ****/
-
-testText("<div><table><tr><td>abc<caption>def</caption></table>", "abc\ndef", "Newline between cells and caption");
-
-/**** display:table ****/
-
-testText("<div><div class='table'><span class='cell'>abc</span>\n<span class='cell'>def</span></div>",
-         "abc\tdef", "Tab-separated table cells");
-testText("<div><div class='table'><span class='row'><span class='cell'>abc</span></span>\n<span class='row'><span class='cell'>def</span></span></div>",
-         "abc\ndef", "Newline-separated table rows");
-testText("<div>abc<div class='table'><span class='cell'>def</span></div>ghi", "abc\ndef\nghi", "Newlines around table");
-
-/**** display:inline-table ****/
-
-testText("<div><div class='itable'><span class='cell'>abc</span>\n<span class='cell'>def</span></div>", "abc\tdef", "Tab-separated table cells");
-testText("<div><div class='itable'><span class='row'><span class='cell'>abc</span></span>\n<span class='row'><span class='cell'>def</span></span></div>",
-         "abc\ndef", "Newline-separated table rows");
-testText("<div>abc<div class='itable'><span class='cell'>def</span></div>ghi", "abcdefghi", "No newlines around inline-table");
-testText("<div>abc<div class='itable'><span class='row'><span class='cell'>def</span></span>\n<span class='row'><span class='cell'>123</span></span></div>ghi",
-         "abcdef\n123ghi", "Single newline in two-row inline-table");
-
-/**** Lists ****/
-
-testText("<div><ol><li>abc", "abc", "<ol> list items get no special treatment");
-testText("<div><ul><li>abc", "abc", "<ul> list items get no special treatment");
-
-/**** Misc elements ****/
-
-testText("<div><script style='display:block'>abc", "abc", "display:block <script> is rendered");
-testText("<div><style style='display:block'>abc", "abc", "display:block <style> is rendered");
-testText("<div><noscript style='display:block'>abc", "", "display:block <noscript> is not rendered (it's not parsed!)");
-testText("<div><template style='display:block'>abc", "",
-         "display:block <template> contents are not rendered (the contents are in a different document)");
-testText("<div>abc<br>def", "abc\ndef", "<br> induces line break");
-testText("<div>abc<br>", "abc\n", "<br> induces line break even at end of block");
-testText("<div><br class='poke'>", "\n", "<br> content ignored");
-testText("<div>abc<hr>def", "abc\ndef", "<hr> induces line break");
-testText("<div>abc<hr><hr>def", "abc\ndef", "<hr><hr> induces just one line break");
-testText("<div>abc<hr><hr><hr>def", "abc\ndef", "<hr><hr><hr> induces just one line break");
-testText("<div><hr class='poke'>", "abc", "<hr> content rendered");
-testText("<div>abc<!--comment-->def", "abcdef", "comment ignored");
-
-/**** text-transform ****/
-
-testText("<div><div style='text-transform:uppercase'>abc", "ABC", "text-transform is applied");
-testText("<div><div style='text-transform:uppercase'>Ma\xDF", "MASS", "text-transform handles es-zet");
-testText("<div><div lang='tr' style='text-transform:uppercase'>i \u0131", "\u0130 I", "text-transform handles Turkish casing");
-
-/**** block-in-inline ****/
-
-testText("<div>abc<span>123<div>456</div>789</span>def", "abc123\n456\n789def", "block-in-inline doesn't add unnecessary newlines");
-
-/**** floats ****/
-
-testText("<div>abc<div style='float:left'>123</div>def", "abc\n123\ndef", "floats induce a block boundary");
-testText("<div>abc<span style='float:left'>123</span>def", "abc\n123\ndef", "floats induce a block boundary");
-
-/**** position ****/
-
-testText("<div>abc<div style='position:absolute'>123</div>def", "abc\n123\ndef", "position:absolute induces a block boundary");
-testText("<div>abc<span style='position:absolute'>123</span>def", "abc\n123\ndef", "position:absolute induces a block boundary");
-testText("<div>abc<div style='position:relative'>123</div>def", "abc\n123\ndef", "position:relative has no effect");
-testText("<div>abc<span style='position:relative'>123</span>def", "abc123def", "position:relative has no effect");
-
-/**** text-overflow:ellipsis ****/
-
-testText("<div style='overflow:hidden'>abc", "abc", "overflow:hidden ignored");
-// XXX Chrome skips content with width:0 or height:0 and overflow:hidden;
-// should we spec that?
-testText("<div style='width:0; overflow:hidden'>abc", "abc", "overflow:hidden ignored even with zero width");
-testText("<div style='height:0; overflow:hidden'>abc", "abc", "overflow:hidden ignored even with zero height");
-testText("<div style='width:0; overflow:hidden; text-overflow:ellipsis'>abc", "abc", "text-overflow:ellipsis ignored");
-
-/**** Support on non-HTML elements ****/
-
-testText("<svg>abc", undefined, "innerText not supported on SVG elements");
-testText("<math>abc", undefined, "innerText not supported on MathML elements");
-
-/**** Ruby ****/
-
-testText("<div><ruby>abc<rt>def</rt></ruby>", "abcdef", "<rt> and no <rp>");
-testText("<div><ruby>abc<rp>(</rp><rt>def</rt><rp>)</rp></ruby>", "abcdef", "<rp>");
-testText("<div><rp>abc</rp>", "", "Lone <rp>");
-testText("<div><rp style='visibility:hidden'>abc</rp>", "", "visibility:hidden <rp>");
-testText("<div><rp style='display:block'>abc</rp>def", "abc\ndef", "display:block <rp>");
-testText("<div><rp style='display:block'> abc </rp>def", "abc\ndef", "display:block <rp> with whitespace");
-testText("<div><select class='poke-rp'></select>", "", "<rp> in a <select>");
-
-/**** Shadow DOM ****/
-
-if ("createShadowRoot" in document.body) {
-  testText("<div class='shadow'>", "", "Shadow DOM contents ignored");
-  testText("<div><div class='shadow'>", "", "Shadow DOM contents ignored");
-}
-
-/**** Flexbox ****/
-
-if (CSS.supports('display', 'flex')) {
-  testText("<div style='display:flex'><div style='order:1'>1</div><div>2</div></div>",
-           "1\n2", "CSS 'order' property ignored");
-  testText("<div style='display:flex'><span>1</span><span>2</span></div>",
-           "1\n2", "Flex items blockified");
-}
-
-/**** Grid ****/
-
-if (CSS.supports('display', 'grid')) {
-  testText("<div style='display:grid'><div style='order:1'>1</div><div>2</div></div>",
-           "1\n2", "CSS 'order' property ignored");
-  testText("<div style='display:grid'><span>1</span><span>2</span></div>",
-           "1\n2", "Grid items blockified");
-}
diff --git a/innerText/getter.html b/innerText/getter.html
deleted file mode 100644
index e6ba0db..0000000
--- a/innerText/getter.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!DOCTYPE html>
-<title>innerText getter test</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<style>
-.before::before { content:'abc'; }
-.table { display:table; }
-.itable { display:inline-table; }
-.row { display:table-row; }
-.cell { display:table-cell; }
-.first-line-uppercase::first-line { text-transform:uppercase; }
-.first-letter-uppercase::first-letter { text-transform:uppercase; }
-.first-letter-float::first-letter { float:left; }
-</style>
-<div id="container"></div>
-<svg id="svgContainer"></svg>
-<script>
-function testText(html, expectedPlain, msg) {
-  textTextInContainer(container, html, expectedPlain, msg);
-}
-function testTextInSVG(html, expectedPlain, msg) {
-  textTextInContainer(svgContainer, html, expectedPlain, msg);
-}
-function textTextInContainer(cont, html, expectedPlain, msg) {
-  test(function() {
-    container.innerHTML = html;
-    if (cont != container) {
-      while (container.firstChild) {
-        cont.appendChild(container.firstChild);
-      }
-    }
-    var e = document.getElementById('target');
-    if (!e) {
-      e = cont.firstChild;
-    }
-    var pokes = document.getElementsByClassName('poke');
-    for (var i = 0; i < pokes.length; ++i) {
-      pokes[i].textContent = 'abc';
-    }
-    ['rp', 'optgroup', 'div'].forEach(function(tag) {
-      pokes = document.getElementsByClassName('poke-' + tag);
-      for (var i = 0; i < pokes.length; ++i) {
-        var el = document.createElement(tag);
-        el.textContent = "abc";
-        pokes[i].appendChild(el);
-      }
-    });
-    var shadows = document.getElementsByClassName('shadow');
-    for (var i = 0; i < shadows.length; ++i) {
-      var s = shadows[i].createShadowRoot();
-      s.textContent = 'abc';
-    }
-    while (e && e.nodeType != Node.ELEMENT_NODE) {
-      e = e.nextSibling;
-    }
-    assert_equals(e.innerText, expectedPlain);
-    cont.textContent = '';
-  }, msg + ' (' + format_value(html) + ')');
-}
-</script>
-<script src="getter-tests.js"></script>
diff --git a/innerText/setter.html b/innerText/setter.html
deleted file mode 100644
index 2c47ff6..0000000
--- a/innerText/setter.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!DOCTYPE html>
-<title>innerText setter test</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="container"></div>
-<script>
-// As of March 2017, WebKit and Blink have inconsistent results depending on
-// rendered or not.  setupTest() tests a rendered case, and setupTestDetached()
-// tests a not-rendered case.
-
-function setupTest(context, plain) {
-  // context is either a string or an element node
-  if (typeof context === "string") {
-    container.innerHTML = context;
-  } else {
-    container.innerHTML = "";
-    container.appendChild(context);
-  }
-  var e = container.firstChild;
-  while (e && e.nodeType != Node.ELEMENT_NODE) {
-    e = e.nextSibling;
-  }
-  e.offsetWidth;
-  var oldChild = e.firstChild;
-  e.innerText = plain;
-  return [e, oldChild];
-}
-
-function setupTestDetached(context, plain) {
-  var detachedContainer = document.createElement("div");
-  // context is either a string or an element node
-  if (typeof context === "string") {
-    detachedContainer.innerHTML = context;
-  } else {
-    detachedContainer.innerHTML = "";
-    detachedContainer.appendChild(context);
-  }
-  var e = detachedContainer.firstChild;
-  while (e && e.nodeType != Node.ELEMENT_NODE) {
-    e = e.nextSibling;
-  }
-  var oldChild = e.firstChild;
-  e.innerText = plain;
-  return [e, oldChild];
-}
-
-function assertNewSingleTextNode(newChild, expectedText, oldChild) {
-  assert_not_equals(newChild, null, "Should have a child");
-  assert_equals(newChild.nodeType, Node.TEXT_NODE, "Child should be a text node");
-  assert_equals(newChild.nextSibling, null, "Should have only one child");
-  assert_equals(newChild.data, expectedText);
-  assert_not_equals(newChild, oldChild, "Child should be a *new* text node");
-}
-
-function assertNoEmptyTextChild(parent) {
-  for (var child = parent.firstChild; child; child = child.nextSibling) {
-    if (child.nodeType === Node.TEXT_NODE) {
-      assert_not_equals(child.data, "", "Should not have empty text nodes");
-    }
-  }
-}
-
-function testText(context, plain, expectedText, msg) {
-  test(function(){
-    var arr = setupTest(context, plain);
-    assertNewSingleTextNode(arr[0].firstChild, expectedText, arr[1]);
-  }, msg);
-  test(function() {
-    var arr = setupTestDetached(context, plain);
-    assertNewSingleTextNode(arr[0].firstChild, expectedText, arr[1]);
-  }, msg + ", detached");
-}
-
-function testHTML(context, plain, expectedHTML, msg) {
-  test(function(){
-    var e = setupTest(context, plain)[0];
-    assert_equals(e.innerHTML, expectedHTML);
-    assertNoEmptyTextChild(e);
-  }, msg);
-  test(function() {
-    var e = setupTestDetached(context, plain)[0];
-    assert_equals(e.innerHTML, expectedHTML);
-    assertNoEmptyTextChild(e);
-  }, msg + ", detached");
-}
-</script>
-<script src="setter-tests.js"></script>
diff --git a/interfaces/BackgroundSync.idl b/interfaces/BackgroundSync.idl
new file mode 100644
index 0000000..3580e3b
--- /dev/null
+++ b/interfaces/BackgroundSync.idl
@@ -0,0 +1,28 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Web Background Synchronization spec.
+// See https://wicg.github.io/BackgroundSync/spec/
+
+partial interface ServiceWorkerRegistration {
+  readonly attribute SyncManager sync;
+};
+
+[Exposed=(Window,Worker)]
+interface SyncManager {
+  Promise<void> register(DOMString tag);
+  Promise<sequence<DOMString>> getTags();
+};
+
+partial interface ServiceWorkerGlobalScope {
+  attribute EventHandler onsync;
+};
+
+[Constructor(DOMString type, SyncEventInit init), Exposed=ServiceWorker]
+interface SyncEvent : ExtendableEvent {
+  readonly attribute DOMString tag;
+  readonly attribute boolean lastChance;
+};
+
+dictionary SyncEventInit : ExtendableEventInit {
+  required DOMString tag;
+  boolean lastChance = false;
+};
diff --git a/interfaces/FileAPI.idl b/interfaces/FileAPI.idl
new file mode 100644
index 0000000..af094c8
--- /dev/null
+++ b/interfaces/FileAPI.idl
@@ -0,0 +1,94 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the FileAPI spec.
+// See https://w3c.github.io/FileAPI/
+
+[Constructor(optional sequence<BlobPart> blobParts,
+             optional BlobPropertyBag options),
+ Exposed=(Window,Worker), Serializable]
+interface Blob {
+
+  readonly attribute unsigned long long size;
+  readonly attribute DOMString type;
+
+  // slice Blob into byte-ranged chunks
+  Blob slice([Clamp] optional long long start,
+            [Clamp] optional long long end,
+            optional DOMString contentType);
+};
+
+enum EndingType { "transparent", "native" };
+
+dictionary BlobPropertyBag {
+  DOMString type = "";
+  EndingType endings = "transparent";
+};
+
+typedef (BufferSource or Blob or USVString) BlobPart;
+
+[Constructor(sequence<BlobPart> fileBits,
+             USVString fileName,
+             optional FilePropertyBag options),
+ Exposed=(Window,Worker), Serializable]
+interface File : Blob {
+  readonly attribute DOMString name;
+  readonly attribute long long lastModified;
+};
+
+dictionary FilePropertyBag : BlobPropertyBag {
+  long long lastModified;
+};
+
+[Exposed=(Window,Worker), Serializable]
+interface FileList {
+  getter File? item(unsigned long index);
+  readonly attribute unsigned long length;
+};
+
+[Constructor, Exposed=(Window,Worker)]
+interface FileReader: EventTarget {
+
+  // async read methods
+  void readAsArrayBuffer(Blob blob);
+  void readAsBinaryString(Blob blob);
+  void readAsText(Blob blob, optional DOMString label);
+  void readAsDataURL(Blob blob);
+
+  void abort();
+
+  // states
+  const unsigned short EMPTY = 0;
+  const unsigned short LOADING = 1;
+  const unsigned short DONE = 2;
+
+  readonly attribute unsigned short readyState;
+
+  // File or Blob data
+  readonly attribute (DOMString or ArrayBuffer)? result;
+
+  readonly attribute DOMException? error;
+
+  // event handler content attributes
+  attribute EventHandler onloadstart;
+  attribute EventHandler onprogress;
+  attribute EventHandler onload;
+  attribute EventHandler onabort;
+  attribute EventHandler onerror;
+  attribute EventHandler onloadend;
+
+};
+
+[Constructor, Exposed=(DedicatedWorker,SharedWorker)]
+interface FileReaderSync {
+  // Synchronously return strings
+
+  ArrayBuffer readAsArrayBuffer(Blob blob);
+  DOMString readAsBinaryString(Blob blob);
+  DOMString readAsText(Blob blob, optional DOMString label);
+  DOMString readAsDataURL(Blob blob);
+};
+
+[Exposed=(Window,DedicatedWorker,SharedWorker)]
+partial interface URL {
+  static DOMString createObjectURL(Blob blob);
+  static void revokeObjectURL(DOMString url);
+};
diff --git a/interfaces/IndexedDB.idl b/interfaces/IndexedDB.idl
new file mode 100644
index 0000000..89924c0
--- /dev/null
+++ b/interfaces/IndexedDB.idl
@@ -0,0 +1,203 @@
+[Exposed=(Window,Worker)]
+interface IDBRequest : EventTarget {
+  readonly attribute any                                        result;
+  readonly attribute DOMException?                              error;
+  readonly attribute (IDBObjectStore or IDBIndex or IDBCursor)? source;
+  readonly attribute IDBTransaction?                            transaction;
+  readonly attribute IDBRequestReadyState                       readyState;
+
+  // Event handlers:
+  attribute EventHandler onsuccess;
+  attribute EventHandler onerror;
+};
+
+enum IDBRequestReadyState {
+  "pending",
+  "done"
+};
+
+[Exposed=(Window,Worker)]
+interface IDBOpenDBRequest : IDBRequest {
+  // Event handlers:
+  attribute EventHandler onblocked;
+  attribute EventHandler onupgradeneeded;
+};
+
+[Exposed=(Window,Worker),
+ Constructor(DOMString type, optional IDBVersionChangeEventInit eventInitDict)]
+interface IDBVersionChangeEvent : Event {
+  readonly attribute unsigned long long  oldVersion;
+  readonly attribute unsigned long long? newVersion;
+};
+
+dictionary IDBVersionChangeEventInit : EventInit {
+  unsigned long long  oldVersion = 0;
+  unsigned long long? newVersion = null;
+};
+
+partial interface WindowOrWorkerGlobalScope {
+  [SameObject] readonly attribute IDBFactory indexedDB;
+};
+
+[Exposed=(Window,Worker)]
+interface IDBFactory {
+  IDBOpenDBRequest open(DOMString name,
+                        optional [EnforceRange] unsigned long long version);
+  IDBOpenDBRequest deleteDatabase(DOMString name);
+
+  short cmp(any first, any second);
+};
+
+[Exposed=(Window,Worker)]
+interface IDBDatabase : EventTarget {
+  readonly attribute DOMString          name;
+  readonly attribute unsigned long long version;
+  readonly attribute DOMStringList      objectStoreNames;
+
+  IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames,
+                             optional IDBTransactionMode mode = "readonly");
+  void           close();
+
+  IDBObjectStore createObjectStore(DOMString name,
+                                   optional IDBObjectStoreParameters options);
+  void           deleteObjectStore(DOMString name);
+
+  // Event handlers:
+  attribute EventHandler onabort;
+  attribute EventHandler onclose;
+  attribute EventHandler onerror;
+  attribute EventHandler onversionchange;
+};
+
+dictionary IDBObjectStoreParameters {
+  (DOMString or sequence<DOMString>)? keyPath = null;
+  boolean                             autoIncrement = false;
+};
+
+[Exposed=(Window,Worker)]
+interface IDBObjectStore {
+           attribute DOMString      name;
+  readonly attribute any            keyPath;
+  readonly attribute DOMStringList  indexNames;
+  readonly attribute IDBTransaction transaction;
+  readonly attribute boolean        autoIncrement;
+
+  IDBRequest put(any value, optional any key);
+  IDBRequest add(any value, optional any key);
+  IDBRequest delete(any query);
+  IDBRequest clear();
+  IDBRequest get(any query);
+  IDBRequest getKey(any query);
+  IDBRequest getAll(optional any query,
+                    [EnforceRange] optional unsigned long count);
+  IDBRequest getAllKeys(optional any query,
+                        [EnforceRange] optional unsigned long count);
+  IDBRequest count(optional any query);
+
+  IDBRequest openCursor(optional any query,
+                        optional IDBCursorDirection direction = "next");
+  IDBRequest openKeyCursor(optional any query,
+                           optional IDBCursorDirection direction = "next");
+
+  IDBIndex   index(DOMString name);
+
+  IDBIndex   createIndex(DOMString name,
+                         (DOMString or sequence<DOMString>) keyPath,
+                         optional IDBIndexParameters options);
+  void       deleteIndex(DOMString indexName);
+};
+
+dictionary IDBIndexParameters {
+  boolean unique = false;
+  boolean multiEntry = false;
+};
+
+[Exposed=(Window,Worker)]
+interface IDBIndex {
+           attribute DOMString      name;
+  readonly attribute IDBObjectStore objectStore;
+  readonly attribute any            keyPath;
+  readonly attribute boolean        multiEntry;
+  readonly attribute boolean        unique;
+
+  IDBRequest get(any query);
+  IDBRequest getKey(any query);
+  IDBRequest getAll(optional any query,
+                    [EnforceRange] optional unsigned long count);
+  IDBRequest getAllKeys(optional any query,
+                        [EnforceRange] optional unsigned long count);
+  IDBRequest count(optional any query);
+
+  IDBRequest openCursor(optional any query,
+                        optional IDBCursorDirection direction = "next");
+  IDBRequest openKeyCursor(optional any query,
+                           optional IDBCursorDirection direction = "next");
+};
+
+[Exposed=(Window,Worker)]
+interface IDBKeyRange {
+  readonly attribute any     lower;
+  readonly attribute any     upper;
+  readonly attribute boolean lowerOpen;
+  readonly attribute boolean upperOpen;
+
+  // Static construction methods:
+  static IDBKeyRange only(any value);
+  static IDBKeyRange lowerBound(any lower, optional boolean open = false);
+  static IDBKeyRange upperBound(any upper, optional boolean open = false);
+  static IDBKeyRange bound(any lower,
+                           any upper,
+                           optional boolean lowerOpen = false,
+                           optional boolean upperOpen = false);
+
+  boolean includes(any key);
+};
+
+[Exposed=(Window,Worker)]
+interface IDBCursor {
+  readonly attribute (IDBObjectStore or IDBIndex) source;
+  readonly attribute IDBCursorDirection           direction;
+  readonly attribute any                          key;
+  readonly attribute any                          primaryKey;
+
+  void advance([EnforceRange] unsigned long count);
+  void continue(optional any key);
+  void continuePrimaryKey(any key, any primaryKey);
+
+  IDBRequest update(any value);
+  IDBRequest delete();
+};
+
+enum IDBCursorDirection {
+  "next",
+  "nextunique",
+  "prev",
+  "prevunique"
+};
+
+[Exposed=(Window,Worker)]
+interface IDBCursorWithValue : IDBCursor {
+  readonly attribute any value;
+};
+
+[Exposed=(Window,Worker)]
+interface IDBTransaction : EventTarget {
+  readonly attribute DOMStringList      objectStoreNames;
+  readonly attribute IDBTransactionMode mode;
+  readonly attribute IDBDatabase        db;
+  readonly attribute DOMException       error;
+
+  IDBObjectStore objectStore(DOMString name);
+  void           abort();
+
+  // Event handlers:
+  attribute EventHandler onabort;
+  attribute EventHandler oncomplete;
+  attribute EventHandler onerror;
+};
+
+enum IDBTransactionMode {
+  "readonly",
+  "readwrite",
+  "versionchange"
+};
diff --git a/interfaces/OWNERS b/interfaces/OWNERS
index 7ccc510..a200ae7 100644
--- a/interfaces/OWNERS
+++ b/interfaces/OWNERS
@@ -1,4 +1,2 @@
-@domenic
 @jensl
-@tobie
 @yuki3
diff --git a/interfaces/ServiceWorker.idl b/interfaces/ServiceWorker.idl
new file mode 100644
index 0000000..e9899cc
--- /dev/null
+++ b/interfaces/ServiceWorker.idl
@@ -0,0 +1,220 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Service Workers spec.
+// See https://w3c.github.io/ServiceWorker/
+
+[SecureContext, Exposed=(Window,Worker)]
+interface ServiceWorker : EventTarget {
+  readonly attribute USVString scriptURL;
+  readonly attribute ServiceWorkerState state;
+  void postMessage(any message, optional sequence<object> transfer = []);
+
+  // event
+  attribute EventHandler onstatechange;
+};
+ServiceWorker includes AbstractWorker;
+
+enum ServiceWorkerState {
+  "installing",
+  "installed",
+  "activating",
+  "activated",
+  "redundant"
+};
+
+[SecureContext, Exposed=(Window,Worker)]
+interface ServiceWorkerRegistration : EventTarget {
+  readonly attribute ServiceWorker? installing;
+  readonly attribute ServiceWorker? waiting;
+  readonly attribute ServiceWorker? active;
+  [SameObject] readonly attribute NavigationPreloadManager navigationPreload;
+
+  readonly attribute USVString scope;
+  readonly attribute ServiceWorkerUpdateViaCache updateViaCache;
+
+  [NewObject] Promise<void> update();
+  [NewObject] Promise<boolean> unregister();
+
+  // event
+  attribute EventHandler onupdatefound;
+};
+
+enum ServiceWorkerUpdateViaCache {
+  "imports",
+  "all",
+  "none"
+};
+
+partial interface Navigator {
+  [SecureContext, SameObject] readonly attribute ServiceWorkerContainer serviceWorker;
+};
+
+partial interface WorkerNavigator {
+  [SecureContext, SameObject] readonly attribute ServiceWorkerContainer serviceWorker;
+};
+
+[SecureContext, Exposed=(Window,Worker)]
+interface ServiceWorkerContainer : EventTarget {
+  readonly attribute ServiceWorker? controller;
+  readonly attribute Promise<ServiceWorkerRegistration> ready;
+
+  [NewObject] Promise<ServiceWorkerRegistration> register(USVString scriptURL, optional RegistrationOptions options);
+
+  [NewObject] Promise<any> getRegistration(optional USVString clientURL = "");
+  [NewObject] Promise<FrozenArray<ServiceWorkerRegistration>> getRegistrations();
+
+  void startMessages();
+
+  // events
+  attribute EventHandler oncontrollerchange;
+  attribute EventHandler onmessage; // event.source of message events is ServiceWorker object
+  attribute EventHandler onmessageerror;
+};
+
+dictionary RegistrationOptions {
+  USVString scope;
+  WorkerType type = "classic";
+  ServiceWorkerUpdateViaCache updateViaCache = "imports";
+};
+
+[SecureContext, Exposed=(Window,Worker)]
+interface NavigationPreloadManager {
+  Promise<void> enable();
+  Promise<void> disable();
+  Promise<void> setHeaderValue(ByteString value);
+  Promise<NavigationPreloadState> getState();
+};
+
+dictionary NavigationPreloadState {
+  boolean enabled = false;
+  ByteString headerValue;
+};
+
+[Global=(Worker,ServiceWorker), Exposed=ServiceWorker]
+interface ServiceWorkerGlobalScope : WorkerGlobalScope {
+  [SameObject] readonly attribute Clients clients;
+  [SameObject] readonly attribute ServiceWorkerRegistration registration;
+
+  [NewObject] Promise<void> skipWaiting();
+
+  attribute EventHandler oninstall;
+  attribute EventHandler onactivate;
+  attribute EventHandler onfetch;
+
+  // event
+  attribute EventHandler onmessage; // event.source of the message events is Client object
+  attribute EventHandler onmessageerror;
+};
+
+[Exposed=ServiceWorker]
+interface Client {
+  readonly attribute USVString url;
+  readonly attribute DOMString id;
+  readonly attribute ClientType type;
+  void postMessage(any message, optional sequence<object> transfer = []);
+};
+
+[Exposed=ServiceWorker]
+interface WindowClient : Client {
+  readonly attribute VisibilityState visibilityState;
+  readonly attribute boolean focused;
+  [SameObject] readonly attribute FrozenArray<USVString> ancestorOrigins;
+  [NewObject] Promise<WindowClient> focus();
+  [NewObject] Promise<WindowClient?> navigate(USVString url);
+};
+
+[Exposed=ServiceWorker]
+interface Clients {
+  // The objects returned will be new instances every time
+  [NewObject] Promise<any> get(DOMString id);
+  [NewObject] Promise<FrozenArray<Client>> matchAll(optional ClientQueryOptions options);
+  [NewObject] Promise<WindowClient?> openWindow(USVString url);
+  [NewObject] Promise<void> claim();
+};
+
+dictionary ClientQueryOptions {
+  boolean includeUncontrolled = false;
+  ClientType type = "window";
+};
+
+enum ClientType {
+  "window",
+  "worker",
+  "sharedworker",
+  "all"
+};
+
+[Constructor(DOMString type, optional ExtendableEventInit eventInitDict), Exposed=ServiceWorker]
+interface ExtendableEvent : Event {
+  void waitUntil(Promise<any> f);
+};
+
+dictionary ExtendableEventInit : EventInit {
+  // Defined for the forward compatibility across the derived events
+};
+
+[Constructor(DOMString type, FetchEventInit eventInitDict), Exposed=ServiceWorker]
+interface FetchEvent : ExtendableEvent {
+  [SameObject] readonly attribute Request request;
+  readonly attribute Promise<any> preloadResponse;
+  readonly attribute DOMString clientId;
+  readonly attribute DOMString resultingClientId;
+  readonly attribute DOMString targetClientId;
+
+  void respondWith(Promise<Response> r);
+};
+
+dictionary FetchEventInit : ExtendableEventInit {
+  required Request request;
+  required Promise<any> preloadResponse;
+  DOMString clientId = "";
+  DOMString resultingClientId = "";
+  DOMString targetClientId = "";
+};
+
+[Constructor(DOMString type, optional ExtendableMessageEventInit eventInitDict), Exposed=ServiceWorker]
+interface ExtendableMessageEvent : ExtendableEvent {
+  readonly attribute any data;
+  readonly attribute USVString origin;
+  readonly attribute DOMString lastEventId;
+  [SameObject] readonly attribute (Client or ServiceWorker or MessagePort)? source;
+  readonly attribute FrozenArray<MessagePort> ports;
+};
+
+dictionary ExtendableMessageEventInit : ExtendableEventInit {
+  any data = null;
+  USVString origin = "";
+  DOMString lastEventId = "";
+  (Client or ServiceWorker or MessagePort)? source = null;
+  sequence<MessagePort> ports = [];
+};
+
+partial interface WindowOrWorkerGlobalScope {
+  [SecureContext, SameObject] readonly attribute CacheStorage caches;
+};
+
+[SecureContext, Exposed=(Window,Worker)]
+interface Cache {
+  [NewObject] Promise<any> match(RequestInfo request, optional CacheQueryOptions options);
+  [NewObject] Promise<FrozenArray<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options);
+  [NewObject] Promise<void> add(RequestInfo request);
+  [NewObject] Promise<void> addAll(sequence<RequestInfo> requests);
+  [NewObject] Promise<void> put(RequestInfo request, Response response);
+  [NewObject] Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options);
+  [NewObject] Promise<FrozenArray<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
+};
+
+dictionary CacheQueryOptions {
+  boolean ignoreSearch = false;
+  boolean ignoreMethod = false;
+  boolean ignoreVary = false;
+  DOMString cacheName;
+};
+
+[SecureContext, Exposed=(Window,Worker)]
+interface CacheStorage {
+  [NewObject] Promise<any> match(RequestInfo request, optional CacheQueryOptions options);
+  [NewObject] Promise<boolean> has(DOMString cacheName);
+  [NewObject] Promise<Cache> open(DOMString cacheName);
+  [NewObject] Promise<boolean> delete(DOMString cacheName);
+  [NewObject] Promise<sequence<DOMString>> keys();
+};
diff --git a/interfaces/WebCryptoAPI.idl b/interfaces/WebCryptoAPI.idl
new file mode 100644
index 0000000..f48eaf1
--- /dev/null
+++ b/interfaces/WebCryptoAPI.idl
@@ -0,0 +1,277 @@
+[NoInterfaceObject]
+interface GlobalCrypto {
+  readonly attribute Crypto crypto;
+};
+
+//Window implements GlobalCrypto;
+//WorkerGlobalScope implements GlobalCrypto;
+
+[Exposed=(Window,Worker)]
+interface Crypto {
+  readonly attribute SubtleCrypto subtle;
+  ArrayBufferView getRandomValues(ArrayBufferView array);
+};
+
+typedef (object or DOMString) AlgorithmIdentifier;
+
+typedef AlgorithmIdentifier HashAlgorithmIdentifier;
+
+dictionary Algorithm {
+  required DOMString name;
+};
+
+dictionary KeyAlgorithm {
+  required DOMString name;
+};
+
+enum KeyType { "public", "private", "secret" };
+
+enum KeyUsage { "encrypt", "decrypt", "sign", "verify", "deriveKey", "deriveBits", "wrapKey", "unwrapKey" };
+
+[Exposed=(Window,Worker)]
+interface CryptoKey {
+  readonly attribute KeyType type;
+  readonly attribute boolean extractable;
+  readonly attribute object algorithm;
+  readonly attribute object usages;
+};
+
+
+enum KeyFormat { "raw", "spki", "pkcs8", "jwk" };
+
+[Exposed=(Window,Worker)]
+interface SubtleCrypto {
+  Promise<any> encrypt(AlgorithmIdentifier algorithm,
+                       CryptoKey key,
+                       BufferSource data);
+  Promise<any> decrypt(AlgorithmIdentifier algorithm,
+                       CryptoKey key,
+                       BufferSource data);
+  Promise<any> sign(AlgorithmIdentifier algorithm,
+                    CryptoKey key,
+                    BufferSource data);
+  Promise<any> verify(AlgorithmIdentifier algorithm,
+                      CryptoKey key,
+                      BufferSource signature,
+                      BufferSource data);
+  Promise<any> digest(AlgorithmIdentifier algorithm,
+                      BufferSource data);
+
+  Promise<any> generateKey(AlgorithmIdentifier algorithm,
+                          boolean extractable,
+                          sequence<KeyUsage> keyUsages );
+  Promise<any> deriveKey(AlgorithmIdentifier algorithm,
+                         CryptoKey baseKey,
+                         AlgorithmIdentifier derivedKeyType,
+                         boolean extractable,
+                         sequence<KeyUsage> keyUsages );
+  Promise<any> deriveBits(AlgorithmIdentifier algorithm,
+                          CryptoKey baseKey,
+                          unsigned long length);
+
+  Promise<any> importKey(KeyFormat format,
+                         (BufferSource or JsonWebKey) keyData,
+                         AlgorithmIdentifier algorithm,
+                         boolean extractable,
+                         sequence<KeyUsage> keyUsages );
+  Promise<any> exportKey(KeyFormat format, CryptoKey key);
+
+  Promise<any> wrapKey(KeyFormat format,
+                       CryptoKey key,
+                       CryptoKey wrappingKey,
+                       AlgorithmIdentifier wrapAlgorithm);
+  Promise<any> unwrapKey(KeyFormat format,
+                         BufferSource wrappedKey,
+                         CryptoKey unwrappingKey,
+                         AlgorithmIdentifier unwrapAlgorithm,
+                         AlgorithmIdentifier unwrappedKeyAlgorithm,
+                         boolean extractable,
+                         sequence<KeyUsage> keyUsages );
+};
+
+dictionary RsaOtherPrimesInfo {
+ // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
+  DOMString r;
+  DOMString d;
+  DOMString t;
+};
+
+dictionary JsonWebKey {
+  // The following fields are defined in Section 3.1 of JSON Web Key
+  DOMString kty;
+  DOMString use;
+  sequence<DOMString> key_ops;
+  DOMString alg;
+
+  // The following fields are defined in JSON Web Key Parameters Registration
+  boolean ext;
+
+  // The following fields are defined in Section 6 of JSON Web Algorithms
+  DOMString crv;
+  DOMString x;
+  DOMString y;
+  DOMString d;
+  DOMString n;
+  DOMString e;
+  DOMString p;
+  DOMString q;
+  DOMString dp;
+  DOMString dq;
+  DOMString qi;
+  sequence<RsaOtherPrimesInfo> oth;
+  DOMString k;
+};
+
+typedef Uint8Array BigInteger;
+
+dictionary CryptoKeyPair {
+  CryptoKey publicKey;
+  CryptoKey privateKey;
+};
+
+dictionary RsaKeyGenParams : Algorithm {
+  // The length, in bits, of the RSA modulus
+  required [EnforceRange] unsigned long modulusLength;
+  // The RSA public exponent
+  required BigInteger publicExponent;
+};
+
+dictionary RsaHashedKeyGenParams : RsaKeyGenParams {
+  // The hash algorithm to use
+  required HashAlgorithmIdentifier hash;
+};
+
+dictionary RsaKeyAlgorithm : KeyAlgorithm {
+  // The length, in bits, of the RSA modulus
+  required unsigned long modulusLength;
+  // The RSA public exponent
+  required BigInteger publicExponent;
+};
+
+dictionary RsaHashedKeyAlgorithm : RsaKeyAlgorithm {
+  // The hash algorithm that is used with this key
+  required KeyAlgorithm hash;
+};
+
+dictionary RsaHashedImportParams {
+  // The hash algorithm to use
+  required HashAlgorithmIdentifier hash;
+};
+
+dictionary RsaPssParams : Algorithm {
+// The desired length of the random salt
+[EnforceRange] required unsigned long saltLength;
+};
+
+dictionary RsaOaepParams : Algorithm {
+// The optional label/application data to associate with the message
+BufferSource label;
+};
+
+dictionary EcdsaParams : Algorithm {
+// The hash algorithm to use
+required HashAlgorithmIdentifier hash;
+};
+
+typedef DOMString NamedCurve;
+
+dictionary EcKeyGenParams : Algorithm {
+// A named curve
+required NamedCurve namedCurve;
+};
+
+dictionary EcKeyAlgorithm : KeyAlgorithm {
+// The named curve that the key uses
+required NamedCurve namedCurve;
+};
+
+dictionary EcKeyImportParams : Algorithm {
+// A named curve
+required NamedCurve namedCurve;
+};
+
+dictionary EcdhKeyDeriveParams : Algorithm {
+// The peer's EC public key.
+required CryptoKey public;
+};
+
+dictionary AesCtrParams : Algorithm {
+// The initial value of the counter block. counter MUST be 16 bytes
+// (the AES block size). The counter bits are the rightmost length
+// bits of the counter block. The rest of the counter block is for
+// the nonce. The counter bits are incremented using the standard
+// incrementing function specified in NIST SP 800-38A Appendix B.1:
+// the counter bits are interpreted as a big-endian integer and
+// incremented by one.
+required BufferSource counter;
+// The length, in bits, of the rightmost part of the counter block
+// that is incremented.
+[EnforceRange] required octet length;
+};
+
+dictionary AesKeyAlgorithm : KeyAlgorithm {
+// The length, in bits, of the key.
+required unsigned short length;
+};
+
+dictionary AesKeyGenParams : Algorithm {
+// The length, in bits, of the key.
+[EnforceRange] required unsigned short length;
+};
+
+dictionary AesDerivedKeyParams : Algorithm {
+// The length, in bits, of the key.
+[EnforceRange] required unsigned short length;
+};
+
+dictionary AesCbcParams : Algorithm {
+// The initialization vector. MUST be 16 bytes.
+required BufferSource iv;
+};
+
+dictionary AesGcmParams : Algorithm {
+// The initialization vector to use. May be up to 2^64-1 bytes long.
+required BufferSource iv;
+// The additional authentication data to include.
+BufferSource additionalData;
+// The desired length of the authentication tag. May be 0 - 128.
+[EnforceRange] octet tagLength;
+};
+
+dictionary HmacImportParams : Algorithm {
+// The inner hash function to use.
+HashAlgorithmIdentifier hash;
+// The length (in bits) of the key.
+[EnforceRange] unsigned long length;
+};
+
+dictionary HmacKeyAlgorithm : KeyAlgorithm {
+// The inner hash function to use.
+required KeyAlgorithm hash;
+// The length (in bits) of the key.
+required unsigned long length;
+};
+
+dictionary HmacKeyGenParams : Algorithm {
+// The inner hash function to use.
+required HashAlgorithmIdentifier hash;
+// The length (in bits) of the key to generate. If unspecified, the
+// recommended length will be used, which is the size of the associated hash function's block
+// size.
+[EnforceRange] unsigned long length;
+};
+
+dictionary HkdfCtrParams : Algorithm {
+// The algorithm to use with HMAC (e.g.: SHA-256)
+required HashAlgorithmIdentifier hash;
+// A bit string that corresponds to the label that identifies the purpose for the derived keying material.
+required BufferSource label;
+// A bit string that corresponds to the context of the key derivation, as described in Section 5 of [NIST SP800-108]
+required BufferSource context;
+};
+
+dictionary Pbkdf2Params : Algorithm {
+required BufferSource salt;
+[EnforceRange] required unsigned long iterations;
+required HashAlgorithmIdentifier hash;
+};
diff --git a/interfaces/accelerometer.idl b/interfaces/accelerometer.idl
index df78885..d3db602 100644
--- a/interfaces/accelerometer.idl
+++ b/interfaces/accelerometer.idl
@@ -1,14 +1,23 @@
-[Constructor(optional SensorOptions options), SecureContext, Exposed=Window]
+[Constructor(optional AccelerometerSensorOptions options), SecureContext,
+  Exposed=Window]
 interface Accelerometer : Sensor {
   readonly attribute double? x;
   readonly attribute double? y;
   readonly attribute double? z;
 };
 
-[Constructor(optional SensorOptions options), SecureContext, Exposed=Window]
+enum AccelerometerLocalCoordinateSystem { "device", "screen" };
+
+dictionary AccelerometerSensorOptions : SensorOptions {
+  AccelerometerLocalCoordinateSystem referenceFrame = "device";
+};
+
+[Constructor(optional AccelerometerSensorOptions options), SecureContext,
+  Exposed=Window]
 interface LinearAccelerationSensor : Accelerometer {
 };
 
-[Constructor(optional SensorOptions options), SecureContext, Exposed=Window]
+[Constructor(optional AccelerometerSensorOptions options), SecureContext,
+  Exposed=Window]
 interface GravitySensor : Accelerometer {
 };
diff --git a/background-fetch/interfaces.idl b/interfaces/background-fetch.idl
similarity index 100%
rename from background-fetch/interfaces.idl
rename to interfaces/background-fetch.idl
diff --git a/interfaces/budget-api.idl b/interfaces/budget-api.idl
new file mode 100644
index 0000000..a7fd1f2
--- /dev/null
+++ b/interfaces/budget-api.idl
@@ -0,0 +1,31 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the budget-api spec.
+// See https://wicg.github.io/budget-api/
+
+[Exposed=Window]
+partial interface Navigator {
+    [SameObject] readonly attribute BudgetService budget;
+};
+
+[Exposed=Worker]
+partial interface WorkerNavigator {
+    [SameObject] readonly attribute BudgetService budget;
+};
+
+[Exposed=(Window,Worker)]
+interface BudgetService {
+    Promise<double> getCost(OperationType operation);
+    Promise<sequence<BudgetState>> getBudget();
+
+    Promise<boolean> reserve(OperationType operation);
+};
+
+[Exposed=(Window,Worker)]
+interface BudgetState {
+  readonly attribute double budgetAt;
+  readonly attribute DOMTimeStamp time;
+};
+
+enum OperationType {
+  "silent-push"
+};
diff --git a/interfaces/clipboard-apis.idl b/interfaces/clipboard-apis.idl
new file mode 100644
index 0000000..4679483
--- /dev/null
+++ b/interfaces/clipboard-apis.idl
@@ -0,0 +1,23 @@
+dictionary ClipboardEventInit : EventInit {
+  DataTransfer? clipboardData = null;
+};
+
+[Constructor(DOMString type, optional ClipboardEventInit eventInitDict), Exposed=Window]
+interface ClipboardEvent : Event {
+  readonly attribute DataTransfer? clipboardData;
+};
+
+partial interface Navigator {
+  [SecureContext, SameObject] readonly attribute Clipboard clipboard;
+};
+
+[SecureContext, Exposed=Window] interface Clipboard : EventTarget {
+  Promise<DataTransfer> read();
+  Promise<DOMString> readText();
+  Promise<void> write(DataTransfer data);
+  Promise<void> writeText(DOMString data);
+};
+
+dictionary ClipboardPermissionDescriptor : PermissionDescriptor {
+  boolean allowWithoutGesture = false;
+};
diff --git a/interfaces/clipboard.idl b/interfaces/clipboard.idl
deleted file mode 100644
index ea969ca..0000000
--- a/interfaces/clipboard.idl
+++ /dev/null
@@ -1,13 +0,0 @@
-[SecureContext]
-interface Clipboard : EventTarget {
-  Promise<DataTransfer> read();
-  Promise<DOMString> readText();
-
-  Promise<void> write(DataTransfer data);
-  Promise<void> writeText(DOMString data);
-};
-
-[SecureContext]
-partial interface Navigator {
-  [SameObject] readonly attribute Clipboard clipboard;
-};
diff --git a/interfaces/compat.idl b/interfaces/compat.idl
new file mode 100644
index 0000000..bbb268b
--- /dev/null
+++ b/interfaces/compat.idl
@@ -0,0 +1,12 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the compat spec.
+// See https://compat.spec.whatwg.org/
+
+partial interface Window {
+    readonly attribute short orientation;
+    attribute EventHandler onorientationchange;
+};
+
+partial interface HTMLBodyElement {
+    attribute EventHandler onorientationchange;
+};
diff --git a/interfaces/console.idl b/interfaces/console.idl
index 7f5da77..7351bfc 100644
--- a/interfaces/console.idl
+++ b/interfaces/console.idl
@@ -1,5 +1,6 @@
 [Exposed=(Window,Worker,Worklet)]
-namespace console {
+namespace console { // but see namespace object requirements below
+  // Logging
   void assert(optional boolean condition = false, any... data);
   void clear();
   void count(optional DOMString label = "default");
@@ -13,10 +14,12 @@
   void dir(any item, optional object? options);
   void dirxml(any... data);
 
+  // Grouping
   void group(any... data);
   void groupCollapsed(any... data);
   void groupEnd();
 
+  // Timing
   void time(optional DOMString label = "default");
   void timeEnd(optional DOMString label = "default");
 };
diff --git a/interfaces/cookie-store.idl b/interfaces/cookie-store.idl
new file mode 100644
index 0000000..b38f61e
--- /dev/null
+++ b/interfaces/cookie-store.idl
@@ -0,0 +1,88 @@
+// https://github.com/WICG/cookie-store/blob/gh-pages/explainer.md
+
+dictionary CookieListItem {
+  USVString name;
+  USVString value;
+};
+
+typedef sequence<CookieListItem> CookieList;
+
+dictionary CookieChangeEventInit : EventInit {
+  CookieList changed;
+  CookieList deleted;
+};
+
+[
+  Exposed=Window,
+  Constructor(DOMString type, optional CookieChangeEventInit eventInitDict)
+] interface CookieChangeEvent : Event {
+  readonly attribute CookieList changed;
+  readonly attribute CookieList deleted;
+};
+
+dictionary ExtendableCookieChangeEventInit : ExtendableEventInit {
+  CookieList changed;
+  CookieList deleted;
+};
+
+[
+  Exposed=ServiceWorker,
+  Constructor(DOMString type, optional ExtendableCookieChangeEventInit eventInitDict)
+] interface ExtendableCookieChangeEvent : ExtendableEvent {
+  readonly attribute CookieList changed;
+  readonly attribute CookieList deleted;
+};
+
+enum CookieMatchType {
+  "equals",
+  "startsWith"
+};
+
+dictionary CookieStoreGetOptions {
+  USVString name;
+  USVString url;
+  CookieMatchType matchType = "equals";
+};
+
+dictionary CookieStoreSetOptions {
+  USVString name;
+  USVString value;
+  DOMTimeStamp? expires = null;
+  USVString domain;
+  USVString path = "/";
+  boolean? secure;
+  boolean httpOnly = false;
+};
+
+[
+  Exposed=(ServiceWorker,Window)
+] interface CookieStore : EventTarget {
+  Promise<CookieList?> getAll(USVString name, optional CookieStoreGetOptions options);
+  Promise<CookieList?> getAll(optional CookieStoreGetOptions options);
+
+  Promise<CookieListItem?> get(USVString name, optional CookieStoreGetOptions options);
+  Promise<CookieListItem?> get(optional CookieStoreGetOptions options);
+
+  Promise<boolean> has(USVString name, optional CookieStoreGetOptions options);
+  Promise<boolean> has(optional CookieStoreGetOptions options);
+
+  Promise<void> set(USVString name, USVString value, optional CookieStoreSetOptions options);
+  Promise<void> set(CookieStoreSetOptions options);
+
+  Promise<void> delete(USVString name, optional CookieStoreSetOptions options);
+  Promise<void> delete(CookieStoreSetOptions options);
+
+  [Exposed=ServiceWorker] Promise<void> subscribeToChanges(sequence<CookieStoreGetOptions> subscriptions);
+
+  [Exposed=ServiceWorker] Promise<sequence<CookieStoreGetOptions>> getChangeSubscriptions();
+
+  [Exposed=Window] attribute EventHandler onchange;
+};
+
+partial interface Window {
+  [Replaceable, SameObject] readonly attribute CookieStore cookieStore;
+};
+
+partial interface ServiceWorkerGlobalScope {
+  [Replaceable, SameObject] readonly attribute CookieStore cookieStore;
+};
diff --git a/interfaces/css-font-loading.idl b/interfaces/css-font-loading.idl
new file mode 100644
index 0000000..5609101
--- /dev/null
+++ b/interfaces/css-font-loading.idl
@@ -0,0 +1,86 @@
+typedef (ArrayBuffer or ArrayBufferView) BinaryData;
+
+dictionary FontFaceDescriptors {
+  CSSOMString style = "normal";
+  CSSOMString weight = "normal";
+  CSSOMString stretch = "normal";
+  CSSOMString unicodeRange = "U+0-10FFFF";
+  CSSOMString variant = "normal";
+  CSSOMString featureSettings = "normal";
+  CSSOMString variationSettings = "normal";
+  CSSOMString display = "auto";
+};
+
+enum FontFaceLoadStatus { "unloaded", "loading", "loaded", "error" };
+
+[Constructor(CSSOMString family, (CSSOMString or BinaryData) source,
+             optional FontFaceDescriptors descriptors),
+ Exposed=(Window,Worker)]
+interface FontFace {
+  attribute CSSOMString family;
+  attribute CSSOMString style;
+  attribute CSSOMString weight;
+  attribute CSSOMString stretch;
+  attribute CSSOMString unicodeRange;
+  attribute CSSOMString variant;
+  attribute CSSOMString featureSettings;
+  attribute CSSOMString variationSettings;
+  attribute CSSOMString display;
+
+  readonly attribute FontFaceLoadStatus status;
+
+  Promise<FontFace> load();
+  readonly attribute Promise<FontFace> loaded;
+};
+
+dictionary FontFaceSetLoadEventInit : EventInit {
+  sequence<FontFace> fontfaces = [];
+};
+
+[Constructor(CSSOMString type, optional FontFaceSetLoadEventInit eventInitDict),
+ Exposed=(Window,Worker)]
+interface FontFaceSetLoadEvent : Event {
+  [SameObject] readonly attribute FrozenArray<FontFace> fontfaces;
+};
+
+enum FontFaceSetLoadStatus { "loading", "loaded" };
+
+callback ForEachCallback = void (FontFace font, long index, FontFaceSet self);
+
+[Exposed=(Window,Worker),
+ Constructor(sequence<FontFace> initialFaces)]
+interface FontFaceSet : EventTarget {
+  // FontFaceSet is Set-like!
+  setlike<FontFace>;
+  FontFaceSet add(FontFace font);
+  boolean delete(FontFace font);
+  void clear();
+
+  // events for when loading state changes
+  attribute EventHandler onloading;
+  attribute EventHandler onloadingdone;
+  attribute EventHandler onloadingerror;
+
+  // check and start loads if appropriate
+  // and fulfill promise when all loads complete
+  Promise<sequence<FontFace>> load(CSSOMString font, optional CSSOMString text = " ");
+
+  // return whether all fonts in the fontlist are loaded
+  // (does not initiate load if not available)
+  boolean check(CSSOMString font, optional CSSOMString text = " ");
+
+  // async notification that font loading and layout operations are done
+  readonly attribute Promise<FontFaceSet> ready;
+
+  // loading state, "loading" while one or more fonts loading, "loaded" otherwise
+  readonly attribute FontFaceSetLoadStatus status;
+};
+
+[Exposed=(Window,Worker),
+ NoInterfaceObject]
+interface FontFaceSource {
+  readonly attribute FontFaceSet fonts;
+};
+
+Document implements FontFaceSource;
+WorkerGlobalScope implements FontFaceSource;
diff --git a/interfaces/css-typed-om.idl b/interfaces/css-typed-om.idl
new file mode 100644
index 0000000..39e53ce
--- /dev/null
+++ b/interfaces/css-typed-om.idl
@@ -0,0 +1,317 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the CSS Typed OM spec.
+// See https://drafts.css-houdini.org/css-typed-om/
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface CSSStyleValue {
+    stringifier;
+    [Exposed=Window] static CSSStyleValue parse(USVString property, USVString cssText);
+    [Exposed=Window] static sequence<CSSStyleValue> parseAll(USVString property, USVString cssText);
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface StylePropertyMapReadOnly {
+    iterable<USVString, sequence<CSSStyleValue>>;
+    any get(USVString property);
+    /* 'any' means (undefined or CSSStyleValue) here,
+       see https://github.com/heycam/webidl/issues/60 */
+    sequence<CSSStyleValue> getAll(USVString property);
+    boolean has(USVString property);
+    readonly attribute unsigned long size;
+};
+
+[Exposed=Window]
+interface StylePropertyMap : StylePropertyMapReadOnly {
+    void set(USVString property, (CSSStyleValue or USVString)... values);
+    void append(USVString property, (CSSStyleValue or USVString)... values);
+    void delete(USVString property);
+    void clear();
+};
+
+partial interface Element {
+    [SameObject] StylePropertyMapReadOnly computedStyleMap();
+};
+
+partial interface CSSStyleRule {
+    [SameObject] readonly attribute StylePropertyMap styleMap;
+};
+
+partial interface ElementCSSInlineStyle {
+    [SameObject] readonly attribute StylePropertyMap attributeStyleMap;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(sequence<CSSUnparsedSegment> members)]
+interface CSSUnparsedValue : CSSStyleValue {
+    iterable<CSSUnparsedSegment>;
+    readonly attribute unsigned long length;
+    getter CSSUnparsedSegment (unsigned long index);
+    setter CSSUnparsedSegment (unsigned long index, CSSUnparsedSegment val);
+};
+
+typedef (USVString or CSSVariableReferenceValue) CSSUnparsedSegment;
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(USVString variable, optional CSSUnparsedValue? fallback = null)]
+interface CSSVariableReferenceValue {
+    attribute USVString variable;
+    readonly attribute CSSUnparsedValue? fallback;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(USVString value)]
+interface CSSKeywordValue : CSSStyleValue {
+    attribute USVString value;
+};
+
+typedef (double or CSSNumericValue) CSSNumberish;
+
+enum CSSNumericBaseType {
+    "length",
+    "angle",
+    "time",
+    "frequency",
+    "resolution",
+    "flex",
+    "percent",
+};
+
+dictionary CSSNumericType {
+    long length;
+    long angle;
+    long time;
+    long frequency;
+    long resolution;
+    long flex;
+    long percent;
+    CSSNumericBaseType percentHint;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface CSSNumericValue : CSSStyleValue {
+    CSSNumericValue add(CSSNumberish... values);
+    CSSNumericValue sub(CSSNumberish... values);
+    CSSNumericValue mul(CSSNumberish... values);
+    CSSNumericValue div(CSSNumberish... values);
+    CSSNumericValue min(CSSNumberish... values);
+    CSSNumericValue max(CSSNumberish... values);
+
+    boolean equals(CSSNumberish... value);
+
+    CSSUnitValue to(USVString unit);
+    CSSMathSum toSum(USVString... units);
+    CSSNumericType type();
+
+    [Exposed=Window] static CSSNumericValue parse(USVString cssText);
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(double value, USVString unit)]
+interface CSSUnitValue : CSSNumericValue {
+    attribute double value;
+    readonly attribute USVString unit;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface CSSMathValue : CSSNumericValue {
+    readonly attribute CSSMathOperator operator;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish... args)]
+interface CSSMathSum : CSSMathValue {
+    readonly attribute CSSNumericArray values;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish... args)]
+interface CSSMathProduct : CSSMathValue {
+    readonly attribute CSSNumericArray values;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish arg)]
+interface CSSMathNegate : CSSMathValue {
+    readonly attribute CSSNumericValue value;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish arg)]
+interface CSSMathInvert : CSSMathValue {
+    readonly attribute CSSNumericValue value;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish... args)]
+interface CSSMathMin : CSSMathValue {
+    readonly attribute CSSNumericArray values;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish... args)]
+interface CSSMathMax : CSSMathValue {
+    readonly attribute CSSNumericArray values;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface CSSNumericArray {
+    iterable<CSSNumericValue>;
+    readonly attribute unsigned long length;
+    getter CSSNumericValue (unsigned long index);
+};
+
+enum CSSMathOperator {
+    "sum",
+    "product",
+    "negate",
+    "invert",
+    "min",
+    "max",
+};
+
+// FIXME: Uncomment this when IDLHarness supports CSS namespaces:
+// https://github.com/w3c/web-platform-tests/issues/7583
+/*
+partial namespace CSS {
+    CSSUnitValue number(double value);
+    CSSUnitValue percent(double value);
+
+    // <length>
+    CSSUnitValue em(double value);
+    CSSUnitValue ex(double value);
+    CSSUnitValue ch(double value);
+    CSSUnitValue ic(double value);
+    CSSUnitValue rem(double value);
+    CSSUnitValue lh(double value);
+    CSSUnitValue rlh(double value);
+    CSSUnitValue vw(double value);
+    CSSUnitValue vh(double value);
+    CSSUnitValue vi(double value);
+    CSSUnitValue vb(double value);
+    CSSUnitValue vmin(double value);
+    CSSUnitValue vmax(double value);
+    CSSUnitValue cm(double value);
+    CSSUnitValue mm(double value);
+    CSSUnitValue Q(double value);
+    CSSUnitValue in(double value);
+    CSSUnitValue pt(double value);
+    CSSUnitValue pc(double value);
+    CSSUnitValue px(double value);
+
+    // <angle>
+    CSSUnitValue deg(double value);
+    CSSUnitValue grad(double value);
+    CSSUnitValue rad(double value);
+    CSSUnitValue turn(double value);
+
+    // <time>
+    CSSUnitValue s(double value);
+    CSSUnitValue ms(double value);
+
+    // <frequency>
+    CSSUnitValue Hz(double value);
+    CSSUnitValue kHz(double value);
+
+    // <resolution>
+    CSSUnitValue dpi(double value);
+    CSSUnitValue dpcm(double value);
+    CSSUnitValue dppx(double value);
+
+    // <flex>
+    CSSUnitValue fr(double value);
+};
+*/
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(sequence<CSSTransformComponent> transforms)]
+interface CSSTransformValue : CSSStyleValue {
+    iterable<CSSTransformComponent>;
+    readonly attribute unsigned long length;
+    getter CSSTransformComponent (unsigned long index);
+    setter CSSTransformComponent (unsigned long index, CSSTransformComponent val);
+
+    readonly attribute boolean is2D;
+    DOMMatrix toMatrix();
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface CSSTransformComponent {
+    stringifier;
+    attribute boolean is2D;
+    DOMMatrix toMatrix();
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumericValue x, CSSNumericValue y, optional CSSNumericValue z)]
+interface CSSTranslate : CSSTransformComponent {
+    attribute CSSNumericValue x;
+    attribute CSSNumericValue y;
+    attribute CSSNumericValue z;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumericValue angle),
+ Constructor(CSSNumberish x, CSSNumberish y, CSSNumberish z, CSSNumericValue angle)]
+interface CSSRotate : CSSTransformComponent {
+    attribute CSSNumberish x;
+    attribute CSSNumberish y;
+    attribute CSSNumberish z;
+    attribute CSSNumericValue angle;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish x, CSSNumberish y, optional CSSNumberish z)]
+interface CSSScale : CSSTransformComponent {
+    attribute CSSNumberish x;
+    attribute CSSNumberish y;
+    attribute CSSNumberish z;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumericValue ax, CSSNumericValue ay)]
+interface CSSSkew : CSSTransformComponent {
+    attribute CSSNumericValue ax;
+    attribute CSSNumericValue ay;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumericValue ax)]
+interface CSSSkewX : CSSTransformComponent {
+    attribute CSSNumericValue ax;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumericValue ay)]
+interface CSSSkewY : CSSTransformComponent {
+    attribute CSSNumericValue ay;
+};
+
+/* Note that skew(x,y) is *not* the same as skewX(x) skewY(y),
+   thus the separate interfaces for all three. */
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumericValue length)]
+interface CSSPerspective : CSSTransformComponent {
+    attribute CSSNumericValue length;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(DOMMatrixReadOnly matrix, optional CSSMatrixComponentOptions options)]
+interface CSSMatrixComponent : CSSTransformComponent {
+    attribute DOMMatrix matrix;
+};
+
+dictionary CSSMatrixComponentOptions {
+    boolean is2D;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumericValue x, CSSNumericValue y)]
+interface CSSPositionValue : CSSStyleValue {
+    attribute CSSNumericValue x;
+    attribute CSSNumericValue y;
+};
+
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
+interface CSSImageValue : CSSStyleValue {
+};
diff --git a/interfaces/cssom.idl b/interfaces/cssom.idl
index b372ed6..b99bbc7 100644
--- a/interfaces/cssom.idl
+++ b/interfaces/cssom.idl
@@ -1,7 +1,10 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the CSS Object Model (CSSOM) spec.
+// See https://drafts.csswg.org/cssom/
+
 typedef USVString CSSOMString;
 
-[Exposed=Window,
- LegacyArrayClass]
+[Exposed=Window]
 interface MediaList {
   stringifier attribute [TreatNullAs=EmptyString] CSSOMString mediaText;
   readonly attribute unsigned long length;
@@ -29,8 +32,7 @@
   void deleteRule(unsigned long index);
 };
 
-[Exposed=Window,
- LegacyArrayClass]
+[Exposed=Window]
 interface StyleSheetList {
   getter StyleSheet? item(unsigned long index);
   readonly attribute unsigned long length;
@@ -47,8 +49,8 @@
 };
 
 ProcessingInstruction implements LinkStyle;
-[Exposed=Window,
- LegacyArrayClass]
+
+[Exposed=Window]
 interface CSSRuleList {
   getter CSSRule? item(unsigned long index);
   readonly attribute unsigned long length;
@@ -116,8 +118,6 @@
   CSSOMString getPropertyValue(CSSOMString property);
   CSSOMString getPropertyPriority(CSSOMString property);
   [CEReactions] void setProperty(CSSOMString property, [TreatNullAs=EmptyString] CSSOMString value, [TreatNullAs=EmptyString] optional CSSOMString priority = "");
-  [CEReactions] void setPropertyValue(CSSOMString property, [TreatNullAs=EmptyString] CSSOMString value);
-  [CEReactions] void setPropertyPriority(CSSOMString property, [TreatNullAs=EmptyString] CSSOMString priority);
   [CEReactions] CSSOMString removeProperty(CSSOMString property);
   readonly attribute CSSRule? parentRule;
   [CEReactions] attribute [TreatNullAs=EmptyString] CSSOMString cssFloat;
diff --git a/interfaces/dom.idl b/interfaces/dom.idl
index fce7432..45cae33 100644
--- a/interfaces/dom.idl
+++ b/interfaces/dom.idl
@@ -3,7 +3,9 @@
 interface Event {
   readonly attribute DOMString type;
   readonly attribute EventTarget? target;
+  readonly attribute EventTarget? srcElement;
   readonly attribute EventTarget? currentTarget;
+  sequence<EventTarget> composedPath();
 
   const unsigned short NONE = 0;
   const unsigned short CAPTURING_PHASE = 1;
@@ -16,8 +18,10 @@
 
   readonly attribute boolean bubbles;
   readonly attribute boolean cancelable;
+           attribute boolean returnValue;
   void preventDefault();
   readonly attribute boolean defaultPrevented;
+  readonly attribute boolean composed;
 
   [Unforgeable] readonly attribute boolean isTrusted;
   readonly attribute DOMTimeStamp timeStamp;
@@ -79,26 +83,20 @@
 };
 
 
-[NoInterfaceObject,
- Exposed=Window]
-interface NonElementParentNode {
+interface mixin NonElementParentNode {
   Element? getElementById(DOMString elementId);
 };
-Document implements NonElementParentNode;
-DocumentFragment implements NonElementParentNode;
+Document includes NonElementParentNode;
+DocumentFragment includes NonElementParentNode;
 
 
-[NoInterfaceObject,
- Exposed=Window]
-interface DocumentOrShadowRoot {
+interface mixin DocumentOrShadowRoot {
 };
-Document implements DocumentOrShadowRoot;
-ShadowRoot implements DocumentOrShadowRoot;
+Document includes DocumentOrShadowRoot;
+ShadowRoot includes DocumentOrShadowRoot;
 
 
-[NoInterfaceObject,
- Exposed=Window]
-interface ParentNode {
+interface mixin ParentNode {
   [SameObject] readonly attribute HTMLCollection children;
   readonly attribute Element? firstElementChild;
   readonly attribute Element? lastElementChild;
@@ -110,41 +108,35 @@
   Element? querySelector(DOMString selectors);
   [NewObject] NodeList querySelectorAll(DOMString selectors);
 };
-Document implements ParentNode;
-DocumentFragment implements ParentNode;
-Element implements ParentNode;
+Document includes ParentNode;
+DocumentFragment includes ParentNode;
+Element includes ParentNode;
 
 
-[NoInterfaceObject,
- Exposed=Window]
-interface NonDocumentTypeChildNode {
+interface mixin NonDocumentTypeChildNode {
   readonly attribute Element? previousElementSibling;
   readonly attribute Element? nextElementSibling;
 };
-Element implements NonDocumentTypeChildNode;
-CharacterData implements NonDocumentTypeChildNode;
+Element includes NonDocumentTypeChildNode;
+CharacterData includes NonDocumentTypeChildNode;
 
 
-[NoInterfaceObject,
- Exposed=Window]
-interface ChildNode {
+interface mixin ChildNode {
   [Unscopable] void before((Node or DOMString)... nodes);
   [Unscopable] void after((Node or DOMString)... nodes);
   [Unscopable] void replaceWith((Node or DOMString)... nodes);
   [Unscopable] void remove();
 };
-DocumentType implements ChildNode;
-Element implements ChildNode;
-CharacterData implements ChildNode;
+DocumentType includes ChildNode;
+Element includes ChildNode;
+CharacterData includes ChildNode;
 
 
-[NoInterfaceObject,
- Exposed=Window]
-interface Slotable {
+interface mixin Slotable {
   readonly attribute HTMLSlotElement? assignedSlot;
 };
-Element implements Slotable;
-Text implements Slotable;
+Element includes Slotable;
+Text includes Slotable;
 
 
 [Exposed=Window]
@@ -278,8 +270,8 @@
   HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
   HTMLCollection getElementsByClassName(DOMString classNames);
 
-  [NewObject] Element createElement(DOMString localName, optional ElementCreationOptions options);
-  [NewObject] Element createElementNS(DOMString? namespace, DOMString qualifiedName, optional ElementCreationOptions options);
+  [NewObject] Element createElement(DOMString localName, optional (DOMString or ElementCreationOptions) options);
+  [NewObject] Element createElementNS(DOMString? namespace, DOMString qualifiedName, optional (DOMString or ElementCreationOptions) options);
   [NewObject] DocumentFragment createDocumentFragment();
   [NewObject] Text createTextNode(DOMString data);
   [NewObject] CDATASection createCDATASection(DOMString data);
@@ -352,7 +344,7 @@
            attribute DOMString id;
            attribute DOMString className;
   [SameObject, PutForwards=value] readonly attribute DOMTokenList classList;
-           attribute DOMString slot;
+  [Unscopable] attribute DOMString slot;
 
   boolean hasAttributes();
   [SameObject] readonly attribute NamedNodeMap attributes;
@@ -452,14 +444,22 @@
 };
 
 
-[Constructor,
- Exposed=Window]
-interface Range {
+[Exposed=Window]
+interface AbstractRange {
   readonly attribute Node startContainer;
   readonly attribute unsigned long startOffset;
   readonly attribute Node endContainer;
   readonly attribute unsigned long endOffset;
   readonly attribute boolean collapsed;
+};
+
+[Exposed=Window]
+interface StaticRange : AbstractRange {
+};
+
+[Constructor,
+ Exposed=Window]
+interface Range : AbstractRange {
   readonly attribute Node commonAncestorContainer;
 
   void setStart(Node node, unsigned long offset);
@@ -560,7 +560,7 @@
   [CEReactions] void add(DOMString... tokens);
   [CEReactions] void remove(DOMString... tokens);
   [CEReactions] boolean toggle(DOMString token, optional boolean force);
-  [CEReactions] void replace(DOMString token, DOMString newToken);
+  [CEReactions] boolean replace(DOMString token, DOMString newToken);
   boolean supports(DOMString token);
   [CEReactions] stringifier attribute DOMString value;
   //  iterable<DOMString>;
diff --git a/interfaces/encoding.idl b/interfaces/encoding.idl
new file mode 100644
index 0000000..8f4189b
--- /dev/null
+++ b/interfaces/encoding.idl
@@ -0,0 +1,24 @@
+dictionary TextDecoderOptions {
+  boolean fatal = false;
+  boolean ignoreBOM = false;
+};
+
+dictionary TextDecodeOptions {
+  boolean stream = false;
+};
+
+[Constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options),
+ Exposed=(Window,Worker)]
+interface TextDecoder {
+  readonly attribute DOMString encoding;
+  readonly attribute boolean fatal;
+  readonly attribute boolean ignoreBOM;
+  USVString decode(optional BufferSource input, optional TextDecodeOptions options);
+};
+
+[Constructor,
+ Exposed=(Window,Worker)]
+interface TextEncoder {
+  readonly attribute DOMString encoding;
+  [NewObject] Uint8Array encode(optional USVString input = "");
+};
diff --git a/encrypted-media/EncryptedMediaExtensions.idl b/interfaces/encrypted-media.idl
similarity index 100%
rename from encrypted-media/EncryptedMediaExtensions.idl
rename to interfaces/encrypted-media.idl
diff --git a/interfaces/entries-api.idl b/interfaces/entries-api.idl
new file mode 100644
index 0000000..8e177e1
--- /dev/null
+++ b/interfaces/entries-api.idl
@@ -0,0 +1,65 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the File and Directory Entries API spec.
+// See https://wicg.github.io/entries-api/
+
+partial interface File {
+    readonly attribute USVString webkitRelativePath;
+};
+
+partial interface HTMLInputElement {
+    attribute boolean webkitdirectory;
+    readonly attribute FrozenArray<FileSystemEntry> webkitEntries;
+};
+
+partial interface DataTransferItem {
+    FileSystemEntry? webkitGetAsEntry();
+};
+
+callback ErrorCallback = void (DOMException err);
+
+interface FileSystemEntry {
+    readonly attribute boolean isFile;
+    readonly attribute boolean isDirectory;
+    readonly attribute USVString name;
+    readonly attribute USVString fullPath;
+    readonly attribute FileSystem filesystem;
+
+    void getParent(optional FileSystemEntryCallback successCallback,
+                   optional ErrorCallback errorCallback);
+};
+
+interface FileSystemDirectoryEntry : FileSystemEntry {
+    FileSystemDirectoryReader createReader();
+    void getFile(optional USVString? path,
+                 optional FileSystemFlags options,
+                 optional FileSystemEntryCallback successCallback,
+                 optional ErrorCallback errorCallback);
+    void getDirectory(optional USVString? path,
+                      optional FileSystemFlags options,
+                      optional FileSystemEntryCallback successCallback,
+                      optional ErrorCallback errorCallback);
+};
+
+dictionary FileSystemFlags {
+    boolean create = false;
+    boolean exclusive = false;
+};
+
+callback FileSystemEntryCallback = void (FileSystemEntry entry);
+
+interface FileSystemDirectoryReader {
+    void readEntries(FileSystemEntriesCallback successCallback,
+                     optional ErrorCallback errorCallback);
+};
+callback FileSystemEntriesCallback = void (sequence<FileSystemEntry> entries);
+
+interface FileSystemFileEntry : FileSystemEntry {
+    void file(FileCallback successCallback,
+              optional ErrorCallback errorCallback);
+};
+callback FileCallback = void (File file);
+
+interface FileSystem {
+    readonly attribute USVString name;
+    readonly attribute FileSystemDirectoryEntry root;
+};
diff --git a/interfaces/feature-policy.idl b/interfaces/feature-policy.idl
new file mode 100644
index 0000000..f11815a
--- /dev/null
+++ b/interfaces/feature-policy.idl
@@ -0,0 +1,7 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Feature Policy spec.
+// See https://wicg.github.io/feature-policy/
+
+partial interface HTMLIFrameElement {
+    [CEReactions] attribute DOMString allow;
+};
diff --git a/interfaces/fetch.idl b/interfaces/fetch.idl
new file mode 100644
index 0000000..346d1b0
--- /dev/null
+++ b/interfaces/fetch.idl
@@ -0,0 +1,97 @@
+typedef (sequence<sequence<ByteString>> or record<ByteString, ByteString>) HeadersInit;
+
+[Constructor(optional HeadersInit init),
+ Exposed=(Window,Worker)]
+interface Headers {
+  void append(ByteString name, ByteString value);
+  void delete(ByteString name);
+  ByteString? get(ByteString name);
+  boolean has(ByteString name);
+  void set(ByteString name, ByteString value);
+  iterable<ByteString, ByteString>;
+};
+typedef (Blob or BufferSource or FormData or URLSearchParams or ReadableStream or USVString) BodyInit;
+interface mixin Body {
+  readonly attribute ReadableStream? body;
+  readonly attribute boolean bodyUsed;
+  [NewObject] Promise<ArrayBuffer> arrayBuffer();
+  [NewObject] Promise<Blob> blob();
+  [NewObject] Promise<FormData> formData();
+  [NewObject] Promise<any> json();
+  [NewObject] Promise<USVString> text();
+};
+typedef (Request or USVString) RequestInfo;
+
+[Constructor(RequestInfo input, optional RequestInit init),
+ Exposed=(Window,Worker)]
+interface Request {
+  readonly attribute ByteString method;
+  readonly attribute USVString url;
+  [SameObject] readonly attribute Headers headers;
+
+  readonly attribute RequestDestination destination;
+  readonly attribute USVString referrer;
+  readonly attribute ReferrerPolicy referrerPolicy;
+  readonly attribute RequestMode mode;
+  readonly attribute RequestCredentials credentials;
+  readonly attribute RequestCache cache;
+  readonly attribute RequestRedirect redirect;
+  readonly attribute DOMString integrity;
+  readonly attribute boolean keepalive;
+  readonly attribute boolean isReloadNavigation;
+  readonly attribute AbortSignal signal;
+
+  [NewObject] Request clone();
+};
+Request includes Body;
+
+dictionary RequestInit {
+  ByteString method;
+  HeadersInit headers;
+  BodyInit? body;
+  USVString referrer;
+  ReferrerPolicy referrerPolicy;
+  RequestMode mode;
+  RequestCredentials credentials;
+  RequestCache cache;
+  RequestRedirect redirect;
+  DOMString integrity;
+  boolean keepalive;
+  AbortSignal? signal;
+  any window; // can only be set to null
+};
+
+enum RequestDestination { "", "audio", "audioworklet", "document", "embed", "font", "image", "manifest", "object", "paintworklet", "report", "script", "sharedworker", "style",  "track", "video", "worker", "xslt" };
+enum RequestMode { "navigate", "same-origin", "no-cors", "cors" };
+enum RequestCredentials { "omit", "same-origin", "include" };
+enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" };
+enum RequestRedirect { "follow", "error", "manual" };
+[Constructor(optional BodyInit? body = null, optional ResponseInit init), Exposed=(Window,Worker)]
+interface Response {
+  [NewObject] static Response error();
+  [NewObject] static Response redirect(USVString url, optional unsigned short status = 302);
+
+  readonly attribute ResponseType type;
+
+  readonly attribute USVString url;
+  readonly attribute boolean redirected;
+  readonly attribute unsigned short status;
+  readonly attribute boolean ok;
+  readonly attribute ByteString statusText;
+  [SameObject] readonly attribute Headers headers;
+  readonly attribute Promise<Headers> trailer;
+
+  [NewObject] Response clone();
+};
+Response includes Body;
+
+dictionary ResponseInit {
+  unsigned short status = 200;
+  ByteString statusText = "OK";
+  HeadersInit headers;
+};
+
+enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };
+partial interface mixin WindowOrWorkerGlobalScope {
+  [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init);
+};
diff --git a/interfaces/filter-effects.idl b/interfaces/filter-effects.idl
new file mode 100644
index 0000000..2e37464
--- /dev/null
+++ b/interfaces/filter-effects.idl
@@ -0,0 +1,301 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Filter Effects spec.
+// See https://drafts.fxtf.org/filter-effects/
+
+interface SVGFilterElement : SVGElement {
+  readonly attribute SVGAnimatedEnumeration filterUnits;
+  readonly attribute SVGAnimatedEnumeration primitiveUnits;
+  readonly attribute SVGAnimatedLength x;
+  readonly attribute SVGAnimatedLength y;
+  readonly attribute SVGAnimatedLength width;
+  readonly attribute SVGAnimatedLength height;
+};
+
+SVGFilterElement implements SVGURIReference;
+
+interface mixin SVGFilterPrimitiveStandardAttributes {
+  readonly attribute SVGAnimatedLength x;
+  readonly attribute SVGAnimatedLength y;
+  readonly attribute SVGAnimatedLength width;
+  readonly attribute SVGAnimatedLength height;
+  readonly attribute SVGAnimatedString result;
+};
+
+interface SVGFEBlendElement : SVGElement {
+
+  // Blend Mode Types
+  const unsigned short SVG_FEBLEND_MODE_UNKNOWN = 0;
+  const unsigned short SVG_FEBLEND_MODE_NORMAL = 1;
+  const unsigned short SVG_FEBLEND_MODE_MULTIPLY = 2;
+  const unsigned short SVG_FEBLEND_MODE_SCREEN = 3;
+  const unsigned short SVG_FEBLEND_MODE_DARKEN = 4;
+  const unsigned short SVG_FEBLEND_MODE_LIGHTEN = 5;
+
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedString in2;
+  readonly attribute SVGAnimatedEnumeration mode;
+};
+
+SVGFEBlendElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEColorMatrixElement : SVGElement {
+
+  // Color Matrix Types
+  const unsigned short SVG_FECOLORMATRIX_TYPE_UNKNOWN = 0;
+  const unsigned short SVG_FECOLORMATRIX_TYPE_MATRIX = 1;
+  const unsigned short SVG_FECOLORMATRIX_TYPE_SATURATE = 2;
+  const unsigned short SVG_FECOLORMATRIX_TYPE_HUEROTATE = 3;
+  const unsigned short SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA = 4;
+
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedEnumeration type;
+  readonly attribute SVGAnimatedNumberList values;
+};
+
+SVGFEColorMatrixElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEComponentTransferElement : SVGElement {
+  readonly attribute SVGAnimatedString in1;
+};
+
+SVGFEComponentTransferElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGComponentTransferFunctionElement : SVGElement {
+
+  // Component Transfer Types
+  const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN = 0;
+  const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY = 1;
+  const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_TABLE = 2;
+  const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE = 3;
+  const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_LINEAR = 4;
+  const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_GAMMA = 5;
+
+  readonly attribute SVGAnimatedEnumeration type;
+  readonly attribute SVGAnimatedNumberList tableValues;
+  readonly attribute SVGAnimatedNumber slope;
+  readonly attribute SVGAnimatedNumber intercept;
+  readonly attribute SVGAnimatedNumber amplitude;
+  readonly attribute SVGAnimatedNumber exponent;
+  readonly attribute SVGAnimatedNumber offset;
+};
+
+interface SVGFEFuncRElement : SVGComponentTransferFunctionElement {
+};
+
+interface SVGFEFuncGElement : SVGComponentTransferFunctionElement {
+};
+
+interface SVGFEFuncBElement : SVGComponentTransferFunctionElement {
+};
+
+interface SVGFEFuncAElement : SVGComponentTransferFunctionElement {
+};
+
+interface SVGFECompositeElement : SVGElement {
+
+  // Composite Operators
+  const unsigned short SVG_FECOMPOSITE_OPERATOR_UNKNOWN = 0;
+  const unsigned short SVG_FECOMPOSITE_OPERATOR_OVER = 1;
+  const unsigned short SVG_FECOMPOSITE_OPERATOR_IN = 2;
+  const unsigned short SVG_FECOMPOSITE_OPERATOR_OUT = 3;
+  const unsigned short SVG_FECOMPOSITE_OPERATOR_ATOP = 4;
+  const unsigned short SVG_FECOMPOSITE_OPERATOR_XOR = 5;
+  const unsigned short SVG_FECOMPOSITE_OPERATOR_ARITHMETIC = 6;
+
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedString in2;
+  readonly attribute SVGAnimatedEnumeration operator;
+  readonly attribute SVGAnimatedNumber k1;
+  readonly attribute SVGAnimatedNumber k2;
+  readonly attribute SVGAnimatedNumber k3;
+  readonly attribute SVGAnimatedNumber k4;
+};
+
+SVGFECompositeElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEConvolveMatrixElement : SVGElement {
+
+  // Edge Mode Values
+  const unsigned short SVG_EDGEMODE_UNKNOWN = 0;
+  const unsigned short SVG_EDGEMODE_DUPLICATE = 1;
+  const unsigned short SVG_EDGEMODE_WRAP = 2;
+  const unsigned short SVG_EDGEMODE_NONE = 3;
+
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedInteger orderX;
+  readonly attribute SVGAnimatedInteger orderY;
+  readonly attribute SVGAnimatedNumberList kernelMatrix;
+  readonly attribute SVGAnimatedNumber divisor;
+  readonly attribute SVGAnimatedNumber bias;
+  readonly attribute SVGAnimatedInteger targetX;
+  readonly attribute SVGAnimatedInteger targetY;
+  readonly attribute SVGAnimatedEnumeration edgeMode;
+  readonly attribute SVGAnimatedNumber kernelUnitLengthX;
+  readonly attribute SVGAnimatedNumber kernelUnitLengthY;
+};
+
+SVGFEConvolveMatrixElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEDiffuseLightingElement : SVGElement {
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedNumber surfaceScale;
+  readonly attribute SVGAnimatedNumber diffuseConstant;
+  readonly attribute SVGAnimatedNumber kernelUnitLengthX;
+  readonly attribute SVGAnimatedNumber kernelUnitLengthY;
+};
+
+SVGFEDiffuseLightingElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEDistantLightElement : SVGElement {
+  readonly attribute SVGAnimatedNumber azimuth;
+  readonly attribute SVGAnimatedNumber elevation;
+};
+
+interface SVGFEPointLightElement : SVGElement {
+  readonly attribute SVGAnimatedNumber x;
+  readonly attribute SVGAnimatedNumber y;
+  readonly attribute SVGAnimatedNumber z;
+};
+
+interface SVGFESpotLightElement : SVGElement {
+  readonly attribute SVGAnimatedNumber x;
+  readonly attribute SVGAnimatedNumber y;
+  readonly attribute SVGAnimatedNumber z;
+  readonly attribute SVGAnimatedNumber pointsAtX;
+  readonly attribute SVGAnimatedNumber pointsAtY;
+  readonly attribute SVGAnimatedNumber pointsAtZ;
+  readonly attribute SVGAnimatedNumber specularExponent;
+  readonly attribute SVGAnimatedNumber limitingConeAngle;
+};
+
+interface SVGFEDisplacementMapElement : SVGElement {
+
+  // Channel Selectors
+  const unsigned short SVG_CHANNEL_UNKNOWN = 0;
+  const unsigned short SVG_CHANNEL_R = 1;
+  const unsigned short SVG_CHANNEL_G = 2;
+  const unsigned short SVG_CHANNEL_B = 3;
+  const unsigned short SVG_CHANNEL_A = 4;
+
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedString in2;
+  readonly attribute SVGAnimatedNumber scale;
+  readonly attribute SVGAnimatedEnumeration xChannelSelector;
+  readonly attribute SVGAnimatedEnumeration yChannelSelector;
+};
+
+SVGFEDisplacementMapElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEDropShadowElement : SVGElement {
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedNumber dx;
+  readonly attribute SVGAnimatedNumber dy;
+  readonly attribute SVGAnimatedNumber stdDeviationX;
+  readonly attribute SVGAnimatedNumber stdDeviationY;
+
+  void setStdDeviation(float stdDeviationX, float stdDeviationY);
+};
+
+SVGFEDropShadowElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEFloodElement : SVGElement {
+};
+
+SVGFEFloodElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEGaussianBlurElement : SVGElement {
+
+  // Edge Mode Values
+  const unsigned short SVG_EDGEMODE_UNKNOWN = 0;
+  const unsigned short SVG_EDGEMODE_DUPLICATE = 1;
+  const unsigned short SVG_EDGEMODE_WRAP = 2;
+  const unsigned short SVG_EDGEMODE_NONE = 3;
+
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedNumber stdDeviationX;
+  readonly attribute SVGAnimatedNumber stdDeviationY;
+  readonly attribute SVGAnimatedEnumeration edgeMode;
+
+  void setStdDeviation(float stdDeviationX, float stdDeviationY);
+};
+
+SVGFEGaussianBlurElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEImageElement : SVGElement {
+  readonly attribute SVGAnimatedPreserveAspectRatio preserveAspectRatio;
+  readonly attribute SVGAnimatedString crossOrigin;
+};
+
+SVGFEImageElement includes SVGFilterPrimitiveStandardAttributes;
+SVGFEImageElement implements SVGURIReference;
+
+interface SVGFEMergeElement : SVGElement {
+};
+
+SVGFEMergeElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEMergeNodeElement : SVGElement {
+  readonly attribute SVGAnimatedString in1;
+};
+
+interface SVGFEMorphologyElement : SVGElement {
+
+  // Morphology Operators
+  const unsigned short SVG_MORPHOLOGY_OPERATOR_UNKNOWN = 0;
+  const unsigned short SVG_MORPHOLOGY_OPERATOR_ERODE = 1;
+  const unsigned short SVG_MORPHOLOGY_OPERATOR_DILATE = 2;
+
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedEnumeration operator;
+  readonly attribute SVGAnimatedNumber radiusX;
+  readonly attribute SVGAnimatedNumber radiusY;
+};
+
+SVGFEMorphologyElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFEOffsetElement : SVGElement {
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedNumber dx;
+  readonly attribute SVGAnimatedNumber dy;
+};
+
+SVGFEOffsetElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFESpecularLightingElement : SVGElement {
+  readonly attribute SVGAnimatedString in1;
+  readonly attribute SVGAnimatedNumber surfaceScale;
+  readonly attribute SVGAnimatedNumber specularConstant;
+  readonly attribute SVGAnimatedNumber specularExponent;
+  readonly attribute SVGAnimatedNumber kernelUnitLengthX;
+  readonly attribute SVGAnimatedNumber kernelUnitLengthY;
+};
+
+SVGFESpecularLightingElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFETileElement : SVGElement {
+  readonly attribute SVGAnimatedString in1;
+};
+
+SVGFETileElement includes SVGFilterPrimitiveStandardAttributes;
+
+interface SVGFETurbulenceElement : SVGElement {
+
+  // Turbulence Types
+  const unsigned short SVG_TURBULENCE_TYPE_UNKNOWN = 0;
+  const unsigned short SVG_TURBULENCE_TYPE_FRACTALNOISE = 1;
+  const unsigned short SVG_TURBULENCE_TYPE_TURBULENCE = 2;
+
+  // Stitch Options
+  const unsigned short SVG_STITCHTYPE_UNKNOWN = 0;
+  const unsigned short SVG_STITCHTYPE_STITCH = 1;
+  const unsigned short SVG_STITCHTYPE_NOSTITCH = 2;
+
+  readonly attribute SVGAnimatedNumber baseFrequencyX;
+  readonly attribute SVGAnimatedNumber baseFrequencyY;
+  readonly attribute SVGAnimatedInteger numOctaves;
+  readonly attribute SVGAnimatedNumber seed;
+  readonly attribute SVGAnimatedEnumeration stitchTiles;
+  readonly attribute SVGAnimatedEnumeration type;
+};
+
+SVGFETurbulenceElement includes SVGFilterPrimitiveStandardAttributes;
diff --git a/interfaces/gamepad.idl b/interfaces/gamepad.idl
new file mode 100644
index 0000000..0f7ca2c
--- /dev/null
+++ b/interfaces/gamepad.idl
@@ -0,0 +1,33 @@
+interface Gamepad {
+    readonly attribute DOMString                  id;
+    readonly attribute long                       index;
+    readonly attribute boolean                    connected;
+    readonly attribute DOMHighResTimeStamp        timestamp;
+    readonly attribute GamepadMappingType         mapping;
+    readonly attribute FrozenArray<double>        axes;
+    readonly attribute FrozenArray<GamepadButton> buttons;
+};
+
+interface GamepadButton {
+    readonly attribute boolean pressed;
+    readonly attribute boolean touched;
+    readonly attribute double  value;
+};
+
+enum GamepadMappingType {
+    "",
+    "standard",
+};
+
+partial interface Navigator {
+    sequence<Gamepad?> getGamepads();
+};
+
+[Constructor(GamepadEventInit eventInitDict)]
+interface GamepadEvent : Event {
+    readonly attribute Gamepad gamepad;
+};
+
+dictionary GamepadEventInit : EventInit {
+    required Gamepad gamepad;
+};
diff --git a/interfaces/geolocation-sensor.idl b/interfaces/geolocation-sensor.idl
index 8189155..28508db 100644
--- a/interfaces/geolocation-sensor.idl
+++ b/interfaces/geolocation-sensor.idl
@@ -1,5 +1,6 @@
-[Constructor(optional SensorOptions options), SecureContext, Exposed=Window]
+[Constructor(optional GeolocationSensorOptions options), SecureContext, Exposed=Window]
 interface GeolocationSensor : Sensor {
+  static Promise<GeolocationSensorReading> read(optional ReadOptions readOptions);
   readonly attribute unrestricted double? latitude;
   readonly attribute unrestricted double? longitude;
   readonly attribute unrestricted double? altitude;
@@ -8,3 +9,22 @@
   readonly attribute unrestricted double? heading;
   readonly attribute unrestricted double? speed;
 };
+
+dictionary GeolocationSensorOptions : SensorOptions {
+  // placeholder for GeolocationSensor-specific options
+};
+
+dictionary ReadOptions : GeolocationSensorOptions {
+  AbortSignal? signal;
+};
+
+dictionary GeolocationSensorReading {
+  DOMHighResTimeStamp? timestamp;
+  double? latitude;
+  double? longitude;
+  double? altitude;
+  double? accuracy;
+  double? altitudeAccuracy;
+  double? heading;
+  double? speed;
+};
diff --git a/interfaces/geometry.idl b/interfaces/geometry.idl
index 556e8a5..be1d56b 100644
--- a/interfaces/geometry.idl
+++ b/interfaces/geometry.idl
@@ -1,3 +1,7 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Geometry Interfaces spec.
+// See https://drafts.fxtf.org/geometry/
+
 [Constructor(optional unrestricted double x = 0, optional unrestricted double y = 0,
              optional unrestricted double z = 0, optional unrestricted double w = 1),
  Exposed=(Window,Worker),
@@ -12,7 +16,7 @@
 
     DOMPoint matrixTransform(optional DOMMatrixInit matrix);
 
-    [Default] toJSON();
+    [Default] object toJSON();
 };
 
 [Constructor(optional unrestricted double x = 0, optional unrestricted double y = 0,
@@ -52,7 +56,7 @@
     readonly attribute unrestricted double bottom;
     readonly attribute unrestricted double left;
 
-    [Default] toJSON();
+    [Default] object toJSON();
 };
 
 [Constructor(optional unrestricted double x = 0, optional unrestricted double y = 0,
@@ -76,8 +80,6 @@
     unrestricted double height = 0;
 };
 
-[NoInterfaceObject,
- LegacyArrayClass]
 interface DOMRectList {
     readonly attribute unsigned long length;
     getter DOMRect? item(unsigned long index);
@@ -97,7 +99,7 @@
     [SameObject] readonly attribute DOMPoint p4;
     [NewObject] DOMRect getBounds();
 
-    [Default] toJSON();
+    [Default] object toJSON();
 };
 
 dictionary DOMQuadInit {
@@ -178,7 +180,7 @@
     [NewObject] Float64Array toFloat64Array();
 
     [Exposed=Window] stringifier;
-    [Default] toJSON();
+    [Default] object toJSON();
 };
 
 [Constructor(optional (DOMString or sequence<unrestricted double>) init),
diff --git a/interfaces/gyroscope.idl b/interfaces/gyroscope.idl
index 227a3b9..5dbb2ea 100644
--- a/interfaces/gyroscope.idl
+++ b/interfaces/gyroscope.idl
@@ -1,6 +1,12 @@
-[Constructor(optional SensorOptions sensorOptions), SecureContext, Exposed=Window]
+[Constructor(optional GyroscopeSensorOptions sensorOptions), SecureContext, Exposed=Window]
 interface Gyroscope : Sensor {
   readonly attribute double? x;
   readonly attribute double? y;
   readonly attribute double? z;
 };
+
+enum GyroscopeLocalCoordinateSystem { "device", "screen" };
+
+dictionary GyroscopeSensorOptions : SensorOptions {
+  GyroscopeLocalCoordinateSystem referenceFrame = "device";
+};
diff --git a/interfaces/hr-time.idl b/interfaces/hr-time.idl
index ccbbc33..3c793c3 100644
--- a/interfaces/hr-time.idl
+++ b/interfaces/hr-time.idl
@@ -1,13 +1,17 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the High Resolution Time spec.
+// See https://w3c.github.io/hr-time/
+
 typedef double DOMHighResTimeStamp;
 
 [Exposed=(Window,Worker)]
 interface Performance : EventTarget {
-    DOMHighResTimeStamp now();
+    DOMHighResTimeStamp now ();
     readonly attribute DOMHighResTimeStamp timeOrigin;
-    [Default] object              toJSON();
+    [Default] object toJSON();
 };
 
 partial interface WindowOrWorkerGlobalScope {
     [Replaceable]
-    readonly attribute Performance performance;
+    readonly    attribute Performance performance;
 };
diff --git a/interfaces/html.idl b/interfaces/html.idl
index c958d76..8d9cb2f 100644
--- a/interfaces/html.idl
+++ b/interfaces/html.idl
@@ -2,7 +2,7 @@
  LegacyUnenumerableNamedProperties]
 interface HTMLAllCollection {
   readonly attribute unsigned long length;
-  getter Element? (unsigned long index);
+  getter Element (unsigned long index);
   getter (HTMLCollection or Element)? namedItem(DOMString name);
   (HTMLCollection or Element)? item(optional DOMString nameOrIndex);
 
@@ -86,8 +86,8 @@
   // special event handler IDL attributes that only apply to Document objects
   [LenientThis] attribute EventHandler onreadystatechange;
 };
-Document implements GlobalEventHandlers;
-Document implements DocumentAndElementEventHandlers;
+Document includes GlobalEventHandlers;
+Document includes DocumentAndElementEventHandlers;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -97,14 +97,10 @@
   [CEReactions] attribute DOMString lang;
   [CEReactions] attribute boolean translate;
   [CEReactions] attribute DOMString dir;
-  [SameObject] readonly attribute DOMStringMap dataset;
 
   // user interaction
   [CEReactions] attribute boolean hidden;
   void click();
-  [CEReactions] attribute long tabIndex;
-  void focus(optional FocusOptions options);
-  void blur();
   [CEReactions] attribute DOMString accessKey;
   readonly attribute DOMString accessKeyLabel;
   [CEReactions] attribute boolean draggable;
@@ -117,13 +113,23 @@
   boolean preventScroll = false;
 };
 
-HTMLElement implements GlobalEventHandlers;
-HTMLElement implements DocumentAndElementEventHandlers;
-HTMLElement implements ElementContentEditable;
+HTMLElement includes GlobalEventHandlers;
+HTMLElement includes DocumentAndElementEventHandlers;
+HTMLElement includes ElementContentEditable;
+HTMLElement includes HTMLOrSVGElement;
 
 // Note: intentionally not [HTMLConstructor]
 interface HTMLUnknownElement : HTMLElement { };
 
+interface mixin HTMLOrSVGElement {
+  [SameObject] readonly attribute DOMStringMap dataset;
+  attribute DOMString nonce;
+
+  [CEReactions] attribute long tabIndex;
+  void focus(optional FocusOptions options);
+  void blur();
+};
+
 [Exposed=Window,
  OverrideBuiltins]
 interface DOMStringMap {
@@ -164,19 +170,13 @@
   [CEReactions] attribute DOMString as; // (default "")
   [SameObject, PutForwards=value] readonly attribute DOMTokenList relList;
   [CEReactions] attribute DOMString media;
-  [CEReactions] attribute DOMString nonce;
   [CEReactions] attribute DOMString integrity;
   [CEReactions] attribute DOMString hreflang;
   [CEReactions] attribute DOMString type;
   [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes;
   [CEReactions] attribute DOMString referrerPolicy;
-  [CEReactions] attribute USVString scope;
-  [CEReactions] attribute DOMString workerType;
-  [CEReactions] attribute DOMString updateViaCache;
-
-  // also has obsolete members
 };
-HTMLLinkElement implements LinkStyle;
+HTMLLinkElement includes LinkStyle;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -192,10 +192,8 @@
  HTMLConstructor]
 interface HTMLStyleElement : HTMLElement {
   [CEReactions] attribute DOMString media;
-  [CEReactions] attribute DOMString nonce;
-  [CEReactions] attribute DOMString type;
 };
-HTMLStyleElement implements LinkStyle;
+HTMLStyleElement includes LinkStyle;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -203,7 +201,7 @@
   // also has obsolete members
 };
 
-HTMLBodyElement implements WindowEventHandlers;
+HTMLBodyElement includes WindowEventHandlers;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -295,7 +293,7 @@
 
   // also has obsolete members
 };
-HTMLAnchorElement implements HTMLHyperlinkElementUtils;
+HTMLAnchorElement includes HTMLHyperlinkElementUtils;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -319,9 +317,7 @@
   // also has obsolete members
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface HTMLHyperlinkElementUtils {
+interface mixin HTMLHyperlinkElementUtils {
   [CEReactions] stringifier attribute USVString href;
   readonly attribute USVString origin;
   [CEReactions] attribute USVString protocol;
@@ -688,7 +684,7 @@
 
   // also has obsolete members
 };
-HTMLAreaElement implements HTMLHyperlinkElementUtils;
+HTMLAreaElement includes HTMLHyperlinkElementUtils;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -819,7 +815,6 @@
   [CEReactions] attribute DOMString formTarget;
   [CEReactions] attribute unsigned long height;
   attribute boolean indeterminate;
-  [CEReactions] attribute DOMString inputMode;
   readonly attribute HTMLElement? list;
   [CEReactions] attribute DOMString max;
   [CEReactions] attribute long maxLength;
@@ -963,7 +958,6 @@
   [CEReactions] attribute DOMString dirName;
   [CEReactions] attribute boolean disabled;
   readonly attribute HTMLFormElement? form;
-  [CEReactions] attribute DOMString inputMode;
   [CEReactions] attribute long maxLength;
   [CEReactions] attribute long minLength;
   [CEReactions] attribute DOMString name;
@@ -1113,11 +1107,8 @@
   [CEReactions] attribute boolean defer;
   [CEReactions] attribute DOMString? crossOrigin;
   [CEReactions] attribute DOMString text;
-  [CEReactions] attribute DOMString nonce;
   [CEReactions] attribute DOMString integrity;
 
-
-  // also has obsolete members
 };
 
 [Exposed=Window,
@@ -1131,13 +1122,14 @@
 interface HTMLSlotElement : HTMLElement {
   [CEReactions] attribute DOMString name;
   sequence<Node> assignedNodes(optional AssignedNodesOptions options);
+  sequence<Element> assignedElements(optional AssignedNodesOptions options);
 };
 
 dictionary AssignedNodesOptions {
   boolean flatten = false;
 };
 
-typedef (CanvasRenderingContext2D or WebGLRenderingContext) RenderingContext;
+typedef (CanvasRenderingContext2D or ImageBitmapRenderingContext or WebGLRenderingContext) RenderingContext;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -1145,7 +1137,7 @@
   [CEReactions] attribute unsigned long width;
   [CEReactions] attribute unsigned long height;
 
-  RenderingContext? getContext(DOMString contextId, any... arguments);
+  RenderingContext? getContext(DOMString contextId, optional any options = null);
 
   USVString toDataURL(optional DOMString type, optional any quality);
   void toBlob(BlobCallback _callback, optional DOMString type, optional any quality);
@@ -1176,32 +1168,30 @@
   // back-reference to the canvas
   readonly attribute HTMLCanvasElement canvas;
 };
-CanvasRenderingContext2D implements CanvasState;
-CanvasRenderingContext2D implements CanvasTransform;
-CanvasRenderingContext2D implements CanvasCompositing;
-CanvasRenderingContext2D implements CanvasImageSmoothing;
-CanvasRenderingContext2D implements CanvasFillStrokeStyles;
-CanvasRenderingContext2D implements CanvasShadowStyles;
-CanvasRenderingContext2D implements CanvasFilters;
-CanvasRenderingContext2D implements CanvasRect;
-CanvasRenderingContext2D implements CanvasDrawPath;
-CanvasRenderingContext2D implements CanvasUserInterface;
-CanvasRenderingContext2D implements CanvasText;
-CanvasRenderingContext2D implements CanvasDrawImage;
-CanvasRenderingContext2D implements CanvasImageData;
-CanvasRenderingContext2D implements CanvasPathDrawingStyles;
-CanvasRenderingContext2D implements CanvasTextDrawingStyles;
-CanvasRenderingContext2D implements CanvasPath;
+CanvasRenderingContext2D includes CanvasState;
+CanvasRenderingContext2D includes CanvasTransform;
+CanvasRenderingContext2D includes CanvasCompositing;
+CanvasRenderingContext2D includes CanvasImageSmoothing;
+CanvasRenderingContext2D includes CanvasFillStrokeStyles;
+CanvasRenderingContext2D includes CanvasShadowStyles;
+CanvasRenderingContext2D includes CanvasFilters;
+CanvasRenderingContext2D includes CanvasRect;
+CanvasRenderingContext2D includes CanvasDrawPath;
+CanvasRenderingContext2D includes CanvasUserInterface;
+CanvasRenderingContext2D includes CanvasText;
+CanvasRenderingContext2D includes CanvasDrawImage;
+CanvasRenderingContext2D includes CanvasImageData;
+CanvasRenderingContext2D includes CanvasPathDrawingStyles;
+CanvasRenderingContext2D includes CanvasTextDrawingStyles;
+CanvasRenderingContext2D includes CanvasPath;
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasState {
+interface mixin CanvasState {
   // state
   void save(); // push state on state stack
   void restore(); // pop state stack and restore state
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasTransform {
+interface mixin CanvasTransform {
   // transformations (default transform is the identity matrix)
   void scale(unrestricted double x, unrestricted double y);
   void rotate(unrestricted double angle);
@@ -1212,37 +1202,30 @@
   void setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
   void setTransform(optional DOMMatrix2DInit transform);
   void resetTransform();
-
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasCompositing {
+interface mixin CanvasCompositing {
   // compositing
   attribute unrestricted double globalAlpha; // (default 1.0)
   attribute DOMString globalCompositeOperation; // (default source-over)
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasImageSmoothing {
+interface mixin CanvasImageSmoothing {
   // image smoothing
   attribute boolean imageSmoothingEnabled; // (default true)
   attribute ImageSmoothingQuality imageSmoothingQuality; // (default low)
-
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasFillStrokeStyles {
+interface mixin CanvasFillStrokeStyles {
   // colors and styles (see also the CanvasPathDrawingStyles and CanvasTextDrawingStyles interfaces)
   attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; // (default black)
   attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; // (default black)
   CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
   CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
   CanvasPattern? createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition);
-
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasShadowStyles {
+interface mixin CanvasShadowStyles {
   // shadows
   attribute unrestricted double shadowOffsetX; // (default 0)
   attribute unrestricted double shadowOffsetY; // (default 0)
@@ -1250,22 +1233,19 @@
   attribute DOMString shadowColor; // (default transparent black)
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasFilters {
+interface mixin CanvasFilters {
   // filters
   attribute DOMString filter; // (default "none")
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasRect {
+interface mixin CanvasRect {
   // rects
   void clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
   void fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
   void strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasDrawPath {
+interface mixin CanvasDrawPath {
   // path API (see also CanvasPath)
   void beginPath();
   void fill(optional CanvasFillRule fillRule = "nonzero");
@@ -1281,34 +1261,28 @@
   boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface CanvasUserInterface {
+interface mixin CanvasUserInterface {
   void drawFocusIfNeeded(Element element);
   void drawFocusIfNeeded(Path2D path, Element element);
   void scrollPathIntoView();
   void scrollPathIntoView(Path2D path);
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface CanvasText {
+interface mixin CanvasText {
   // text (see also the CanvasPathDrawingStyles and CanvasTextDrawingStyles interfaces)
   void fillText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
   void strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
   TextMetrics measureText(DOMString text);
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasDrawImage {
+interface mixin CanvasDrawImage {
   // drawing images
   void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy);
   void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh);
   void drawImage(CanvasImageSource image, unrestricted double sx, unrestricted double sy, unrestricted double sw, unrestricted double sh, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh);
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasImageData {
+interface mixin CanvasImageData {
   // pixel manipulation
   ImageData createImageData(long sw, long sh);
   ImageData createImageData(ImageData imagedata);
@@ -1323,8 +1297,7 @@
 enum CanvasTextBaseline { "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" };
 enum CanvasDirection { "ltr", "rtl", "inherit" };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasPathDrawingStyles {
+interface mixin CanvasPathDrawingStyles {
   // line caps/joins
   attribute unrestricted double lineWidth; // (default 1)
   attribute CanvasLineCap lineCap; // (default "butt")
@@ -1337,9 +1310,7 @@
   attribute unrestricted double lineDashOffset;
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface CanvasTextDrawingStyles {
+interface mixin CanvasTextDrawingStyles {
   // text
   attribute DOMString font; // (default 10px sans-serif)
   attribute CanvasTextAlign textAlign; // (default: "start")
@@ -1347,8 +1318,7 @@
   attribute CanvasDirection direction; // (default: "inherit")
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface CanvasPath {
+interface mixin CanvasPath {
   // shared path API methods
   void closePath();
   void moveTo(unrestricted double x, unrestricted double y);
@@ -1356,7 +1326,6 @@
   void quadraticCurveTo(unrestricted double cpx, unrestricted double cpy, unrestricted double x, unrestricted double y);
   void bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y);
   void arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius);
-  void arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation);
   void rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
   void arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false);
   void ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false);
@@ -1403,15 +1372,12 @@
   readonly attribute Uint8ClampedArray data;
 };
 
-[Constructor,
- Constructor(Path2D path),
- Constructor(sequence<Path2D> paths, optional CanvasFillRule fillRule = "nonzero"),
- Constructor(DOMString d),
+[Constructor(optional (Path2D or DOMString) path),
  Exposed=(Window,Worker)]
 interface Path2D {
   void addPath(Path2D path, optional DOMMatrix2DInit transform);
 };
-Path2D implements CanvasPath;
+Path2D includes CanvasPath;
 
 [Exposed=Window]
 interface ImageBitmapRenderingContext {
@@ -1431,14 +1397,14 @@
   unrestricted double quality = 1.0;
 };
 
-enum OffscreenRenderingContextType { "2d", "webgl" };
+enum OffscreenRenderingContextId { "2d", "webgl" };
 
 [Constructor([EnforceRange] unsigned long long width, [EnforceRange] unsigned long long height), Exposed=(Window,Worker), Transferable]
 interface OffscreenCanvas : EventTarget {
   attribute unsigned long long width;
   attribute unsigned long long height;
 
-  OffscreenRenderingContext? getContext(OffscreenRenderingContextType contextType, any... arguments);
+  OffscreenRenderingContext? getContext(OffscreenRenderingContextId contextId, optional any options = null);
   ImageBitmap transferToImageBitmap();
   Promise<Blob> convertToBlob(optional ImageEncodeOptions options);
 };
@@ -1450,19 +1416,19 @@
   readonly attribute OffscreenCanvas canvas;
 };
 
-OffscreenCanvasRenderingContext2D implements CanvasState;
-OffscreenCanvasRenderingContext2D implements CanvasTransform;
-OffscreenCanvasRenderingContext2D implements CanvasCompositing;
-OffscreenCanvasRenderingContext2D implements CanvasImageSmoothing;
-OffscreenCanvasRenderingContext2D implements CanvasFillStrokeStyles;
-OffscreenCanvasRenderingContext2D implements CanvasShadowStyles;
-OffscreenCanvasRenderingContext2D implements CanvasFilters;
-OffscreenCanvasRenderingContext2D implements CanvasRect;
-OffscreenCanvasRenderingContext2D implements CanvasDrawPath;
-OffscreenCanvasRenderingContext2D implements CanvasDrawImage;
-OffscreenCanvasRenderingContext2D implements CanvasImageData;
-OffscreenCanvasRenderingContext2D implements CanvasPathDrawingStyles;
-OffscreenCanvasRenderingContext2D implements CanvasPath;
+OffscreenCanvasRenderingContext2D includes CanvasState;
+OffscreenCanvasRenderingContext2D includes CanvasTransform;
+OffscreenCanvasRenderingContext2D includes CanvasCompositing;
+OffscreenCanvasRenderingContext2D includes CanvasImageSmoothing;
+OffscreenCanvasRenderingContext2D includes CanvasFillStrokeStyles;
+OffscreenCanvasRenderingContext2D includes CanvasShadowStyles;
+OffscreenCanvasRenderingContext2D includes CanvasFilters;
+OffscreenCanvasRenderingContext2D includes CanvasRect;
+OffscreenCanvasRenderingContext2D includes CanvasDrawPath;
+OffscreenCanvasRenderingContext2D includes CanvasDrawImage;
+OffscreenCanvasRenderingContext2D includes CanvasImageData;
+OffscreenCanvasRenderingContext2D includes CanvasPathDrawingStyles;
+OffscreenCanvasRenderingContext2D includes CanvasPath;
 
 
 [Exposed=Window]
@@ -1470,17 +1436,17 @@
   [CEReactions] void define(DOMString name, Function constructor, optional ElementDefinitionOptions options);
   any get(DOMString name);
   Promise<void> whenDefined(DOMString name);
+  [CEReactions] void upgrade(Node root);
 };
 
 dictionary ElementDefinitionOptions {
   DOMString extends;
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface ElementContentEditable {
+interface mixin ElementContentEditable {
   [CEReactions] attribute DOMString contentEditable;
   readonly attribute boolean isContentEditable;
+  [CEReactions] attribute DOMString inputMode;
 };
 
 [Exposed=Window,
@@ -1531,7 +1497,9 @@
   DataTransfer? dataTransfer = null;
 };
 
-[PrimaryGlobal, LegacyUnenumerableNamedProperties]
+[Global=Window,
+ Exposed=Window,
+ LegacyUnenumerableNamedProperties]
 interface Window : EventTarget {
   // the current browsing context
   [Unforgeable] readonly attribute WindowProxy window;
@@ -1585,8 +1553,8 @@
 
   // also has obsolete members
 };
-Window implements GlobalEventHandlers;
-Window implements WindowEventHandlers;
+Window includes GlobalEventHandlers;
+Window includes WindowEventHandlers;
 
 callback FrameRequestCallback = void (DOMHighResTimeStamp time);
 
@@ -1694,8 +1662,7 @@
   attribute EventHandler onobsolete;
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface NavigatorOnLine {
+interface mixin NavigatorOnLine {
   readonly attribute boolean onLine;
 };
 
@@ -1739,9 +1706,7 @@
 callback OnBeforeUnloadEventHandlerNonNull = DOMString? (Event event);
 typedef OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEventHandler;
 
-[Exposed=Window,
- NoInterfaceObject]
-interface GlobalEventHandlers {
+interface mixin GlobalEventHandlers {
   attribute EventHandler onabort;
   attribute EventHandler onauxclick;
   attribute EventHandler onblur;
@@ -1806,9 +1771,7 @@
   attribute EventHandler onwaiting;
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface WindowEventHandlers {
+interface mixin WindowEventHandlers {
   attribute EventHandler onafterprint;
   attribute EventHandler onbeforeprint;
   attribute OnBeforeUnloadEventHandler onbeforeunload;
@@ -1827,9 +1790,7 @@
   attribute EventHandler onunload;
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface DocumentAndElementEventHandlers {
+interface mixin DocumentAndElementEventHandlers {
   attribute EventHandler oncopy;
   attribute EventHandler oncut;
   attribute EventHandler onpaste;
@@ -1837,8 +1798,7 @@
 
 typedef (DOMString or Function) TimerHandler;
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface WindowOrWorkerGlobalScope {
+interface mixin WindowOrWorkerGlobalScope {
   [Replaceable] readonly attribute USVString origin;
 
   // base64 utility methods
@@ -1855,23 +1815,22 @@
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options);
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options);
 };
-Window implements WindowOrWorkerGlobalScope;
-WorkerGlobalScope implements WindowOrWorkerGlobalScope;
+Window includes WindowOrWorkerGlobalScope;
+WorkerGlobalScope includes WindowOrWorkerGlobalScope;
 
 [Exposed=Window]
 interface Navigator {
   // objects implementing this interface also implement the interfaces given below
 };
-Navigator implements NavigatorID;
-Navigator implements NavigatorLanguage;
-Navigator implements NavigatorOnLine;
-Navigator implements NavigatorContentUtils;
-Navigator implements NavigatorCookies;
-Navigator implements NavigatorPlugins;
-Navigator implements NavigatorConcurrentHardware;
+Navigator includes NavigatorID;
+Navigator includes NavigatorLanguage;
+Navigator includes NavigatorOnLine;
+Navigator includes NavigatorContentUtils;
+Navigator includes NavigatorCookies;
+Navigator includes NavigatorPlugins;
+Navigator includes NavigatorConcurrentHardware;
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface NavigatorID {
+interface mixin NavigatorID {
   readonly attribute DOMString appCodeName; // constant "Mozilla"
   readonly attribute DOMString appName; // constant "Netscape"
   readonly attribute DOMString appVersion;
@@ -1890,28 +1849,21 @@
   [Exposed=Window] readonly attribute DOMString oscpu;
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface NavigatorLanguage {
+interface mixin NavigatorLanguage {
   readonly attribute DOMString language;
   readonly attribute FrozenArray<DOMString> languages;
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface NavigatorContentUtils {
+interface mixin NavigatorContentUtils {
   void registerProtocolHandler(DOMString scheme, USVString url, DOMString title);
   void unregisterProtocolHandler(DOMString scheme, USVString url);
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface NavigatorCookies {
+interface mixin NavigatorCookies {
   readonly attribute boolean cookieEnabled;
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface NavigatorPlugins {
+interface mixin NavigatorPlugins {
   [SameObject] readonly attribute PluginArray plugins;
   [SameObject] readonly attribute MimeTypeArray mimeTypes;
   boolean javaEnabled();
@@ -2126,8 +2078,7 @@
   attribute EventHandler onconnect;
 };
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface AbstractWorker {
+interface mixin AbstractWorker {
   attribute EventHandler onerror;
 };
 
@@ -2148,26 +2099,25 @@
 
 enum WorkerType { "classic", "module" };
 
-Worker implements AbstractWorker;
+Worker includes AbstractWorker;
 
 [Constructor(USVString scriptURL, optional (DOMString or WorkerOptions) options),
  Exposed=(Window,Worker)]
 interface SharedWorker : EventTarget {
   readonly attribute MessagePort port;
 };
-SharedWorker implements AbstractWorker;
+SharedWorker includes AbstractWorker;
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface NavigatorConcurrentHardware {
+interface mixin NavigatorConcurrentHardware {
   readonly attribute unsigned long long hardwareConcurrency;
 };
 
 [Exposed=Worker]
 interface WorkerNavigator {};
-WorkerNavigator implements NavigatorID;
-WorkerNavigator implements NavigatorLanguage;
-WorkerNavigator implements NavigatorOnLine;
-WorkerNavigator implements NavigatorConcurrentHardware;
+WorkerNavigator includes NavigatorID;
+WorkerNavigator includes NavigatorLanguage;
+WorkerNavigator includes NavigatorOnLine;
+WorkerNavigator includes NavigatorConcurrentHardware;
 
 [Exposed=Worker]
 interface WorkerLocation {
@@ -2192,19 +2142,15 @@
   void clear();
 };
 
-[Exposed=Window,
- NoInterfaceObject]
-interface WindowSessionStorage {
+interface mixin WindowSessionStorage {
   readonly attribute Storage sessionStorage;
 };
-Window implements WindowSessionStorage;
+Window includes WindowSessionStorage;
 
-[Exposed=Window,
- NoInterfaceObject]
-interface WindowLocalStorage {
+interface mixin WindowLocalStorage {
   readonly attribute Storage localStorage;
 };
-Window implements WindowLocalStorage;
+Window includes WindowLocalStorage;
 
 [Exposed=Window,
  Constructor(DOMString type, optional StorageEventInit eventInitDict)]
@@ -2253,7 +2199,7 @@
   [CEReactions] attribute DOMString cols;
   [CEReactions] attribute DOMString rows;
 };
-HTMLFrameSetElement implements WindowEventHandlers;
+HTMLFrameSetElement includes WindowEventHandlers;
 
 [Exposed=Window,
  HTMLConstructor]
@@ -2430,6 +2376,10 @@
   [CEReactions] attribute long width;
 };
 
+partial interface HTMLStyleElement {
+  [CEReactions] attribute DOMString type;
+};
+
 partial interface HTMLScriptElement {
   [CEReactions] attribute DOMString charset;
   [CEReactions] attribute DOMString event;
diff --git a/interfaces/keyboard-lock.idl b/interfaces/keyboard-lock.idl
new file mode 100644
index 0000000..771c716
--- /dev/null
+++ b/interfaces/keyboard-lock.idl
@@ -0,0 +1,8 @@
+partial interface Navigator {
+  [SecureContext, SameObject] readonly attribute Keyboard keyboard;
+};
+
+[SecureContext, Exposed=Window] interface Keyboard {
+  Promise<void> lock(optional sequence<DOMString> keyCodes = []);
+  void unlock();
+};
diff --git a/interfaces/keyboard-map.idl b/interfaces/keyboard-map.idl
new file mode 100644
index 0000000..e2f2c32
--- /dev/null
+++ b/interfaces/keyboard-map.idl
@@ -0,0 +1,11 @@
+partial interface Navigator {
+  [SecureContext, SameObject] readonly attribute Keyboard keyboard;
+};
+
+interface KeyboardLayoutMap {
+  readonly maplike<DOMString, DOMString>;
+};
+
+[SecureContext, Exposed=Window] interface Keyboard {
+  Promise<KeyboardLayoutMap> getLayoutMap();
+};
diff --git a/interfaces/magnetometer.idl b/interfaces/magnetometer.idl
index 184757a..a9ef069 100644
--- a/interfaces/magnetometer.idl
+++ b/interfaces/magnetometer.idl
@@ -1,11 +1,19 @@
-[Constructor(optional SensorOptions sensorOptions), SecureContext, Exposed=Window]
+[Constructor(optional MagnetometerSensorOptions sensorOptions), SecureContext,
+  Exposed=Window]
 interface Magnetometer : Sensor {
   readonly attribute double? x;
   readonly attribute double? y;
   readonly attribute double? z;
 };
 
-[Constructor(optional SensorOptions sensorOptions), SecureContext, Exposed=Window]
+enum MagnetometerLocalCoordinateSystem { "device", "screen" };
+
+dictionary MagnetometerSensorOptions : SensorOptions {
+  MagnetometerLocalCoordinateSystem referenceFrame = "device";
+};
+
+[Constructor(optional MagnetometerSensorOptions sensorOptions), SecureContext,
+  Exposed=Window]
 interface UncalibratedMagnetometer : Sensor {
   readonly attribute double? x;
   readonly attribute double? y;
diff --git a/interfaces/mediacapture-main.idl b/interfaces/mediacapture-main.idl
index 498bd3e..0513f8f 100644
--- a/interfaces/mediacapture-main.idl
+++ b/interfaces/mediacapture-main.idl
@@ -167,6 +167,7 @@
     "videoinput"
 };
 
+[Exposed=Window]
 interface InputDeviceInfo : MediaDeviceInfo {
     MediaTrackCapabilities getCapabilities();
 };
diff --git a/interfaces/orientation-sensor.idl b/interfaces/orientation-sensor.idl
index 3f96445..df2ea38 100644
--- a/interfaces/orientation-sensor.idl
+++ b/interfaces/orientation-sensor.idl
@@ -6,10 +6,16 @@
   void populateMatrix(RotationMatrixType targetMatrix);
 };
 
-[Constructor(optional SensorOptions sensorOptions), SecureContext, Exposed=Window]
+enum OrientationSensorLocalCoordinateSystem { "device", "screen" };
+
+dictionary OrientationSensorOptions : SensorOptions  {
+  OrientationSensorLocalCoordinateSystem referenceFrame = "device";
+};
+
+[Constructor(optional OrientationSensorOptions sensorOptions), SecureContext, Exposed=Window]
 interface AbsoluteOrientationSensor : OrientationSensor {
 };
 
-[Constructor(optional SensorOptions sensorOptions), SecureContext, Exposed=Window]
+[Constructor(optional OrientationSensorOptions sensorOptions), SecureContext, Exposed=Window]
 interface RelativeOrientationSensor : OrientationSensor {
 };
diff --git a/interfaces/payment-handler.idl b/interfaces/payment-handler.idl
index 88d3b1c..ec42e21 100644
--- a/interfaces/payment-handler.idl
+++ b/interfaces/payment-handler.idl
@@ -19,7 +19,7 @@
 dictionary PaymentInstrument {
     required DOMString             name;
              sequence<ImageObject> icons;
-             sequence<DOMString>   enabledMethods;
+             DOMString             method;
              object                capabilities;
 };
 dictionary ImageObject {
@@ -27,6 +27,21 @@
              DOMString sizes;
              DOMString type;
 };
+[Constructor(DOMString type, CanMakePaymentEventInit eventInitDict),
+ Exposed=ServiceWorker]
+interface CanMakePaymentEvent : ExtendableEvent {
+    readonly attribute USVString                           topLevelOrigin;
+    readonly attribute USVString                           paymentRequestOrigin;
+    readonly attribute FrozenArray<PaymentMethodData>      methodData;
+    readonly attribute FrozenArray<PaymentDetailsModifier> modifiers;
+    void respondWith(Promise<boolean> canMakePaymentResponse);
+};
+dictionary CanMakePaymentEventInit : ExtendableEventInit {
+    USVString                        topLevelOrigin;
+    USVString                        paymentRequestOrigin;
+    sequence<PaymentMethodData>      methodData;
+    sequence<PaymentDetailsModifier> modifiers;
+};
 [Constructor(DOMString type, PaymentRequestEventInit eventInitDict),
  Exposed=ServiceWorker]
 interface PaymentRequestEvent : ExtendableEvent {
diff --git a/interfaces/payment-request.idl b/interfaces/payment-request.idl
index beb6d5f..50a5dea 100644
--- a/interfaces/payment-request.idl
+++ b/interfaces/payment-request.idl
@@ -1,8 +1,11 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Payment Request API spec.
+// See https://w3c.github.io/payment-request/
+
 [Constructor(sequence<PaymentMethodData> methodData, PaymentDetailsInit details, optional PaymentOptions options),
-  SecureContext,
-  Exposed=Window]
+SecureContext, Exposed=Window]
 interface PaymentRequest : EventTarget {
-  Promise<PaymentResponse> show();
+  Promise<PaymentResponse> show(optional Promise<PaymentDetailsUpdate> detailsPromise);
   Promise<void> abort();
   Promise<boolean> canMakePayment();
 
@@ -15,40 +18,48 @@
 
   attribute EventHandler onshippingoptionchange;
 };
+
 dictionary PaymentMethodData {
   required DOMString supportedMethods;
   object data;
 };
+
 dictionary PaymentCurrencyAmount {
   required DOMString currency;
   required DOMString value;
   // Note: currencySystem is "at risk" of being removed!
   DOMString currencySystem = "urn:iso:std:iso:4217";
 };
+
 dictionary PaymentDetailsBase {
   sequence<PaymentItem> displayItems;
   sequence<PaymentShippingOption> shippingOptions;
   sequence<PaymentDetailsModifier> modifiers;
 };
+
 dictionary PaymentDetailsInit : PaymentDetailsBase {
   DOMString id;
   required PaymentItem total;
 };
+
 dictionary PaymentDetailsUpdate : PaymentDetailsBase {
   DOMString error;
   PaymentItem total;
 };
+
 dictionary PaymentDetailsModifier {
   required DOMString supportedMethods;
   PaymentItem total;
   sequence<PaymentItem> additionalDisplayItems;
   object data;
 };
+
 enum PaymentShippingType {
   "shipping",
   "delivery",
   "pickup"
 };
+
 dictionary PaymentOptions {
   boolean requestPayerName = false;
   boolean requestPayerEmail = false;
@@ -56,13 +67,20 @@
   boolean requestShipping = false;
   PaymentShippingType shippingType = "shipping";
 };
+
 dictionary PaymentItem {
   required DOMString label;
   required PaymentCurrencyAmount amount;
   boolean pending = false;
+  // Note: type member is "at risk" of being removed!
+  PaymentItemType type;
 };
-[SecureContext,
-  Exposed=Window]
+
+enum PaymentItemType {
+  "tax"
+};
+
+[SecureContext, Exposed=Window]
 interface PaymentAddress {
   [Default] object toJSON();
   readonly attribute DOMString country;
@@ -77,19 +95,21 @@
   readonly attribute DOMString recipient;
   readonly attribute DOMString phone;
 };
+
 dictionary PaymentShippingOption {
   required DOMString id;
   required DOMString label;
   required PaymentCurrencyAmount amount;
   boolean selected = false;
 };
+
 enum PaymentComplete {
   "fail",
   "success",
   "unknown"
 };
-[SecureContext,
-  Exposed=Window]
+
+[SecureContext, Exposed=Window]
 interface PaymentResponse {
   [Default] object toJSON();
 
@@ -104,11 +124,10 @@
 
   Promise<void> complete(optional PaymentComplete result = "unknown");
 };
-[Constructor(DOMString type, optional PaymentRequestUpdateEventInit eventInitDict),
-  SecureContext,
-  Exposed=Window]
+
+[Constructor(DOMString type, optional PaymentRequestUpdateEventInit eventInitDict), SecureContext, Exposed=Window]
 interface PaymentRequestUpdateEvent : Event {
   void updateWith(Promise<PaymentDetailsUpdate> detailsPromise);
 };
-dictionary PaymentRequestUpdateEventInit : EventInit {
-};
+
+dictionary PaymentRequestUpdateEventInit : EventInit {};
diff --git a/interfaces/proximity.idl b/interfaces/proximity.idl
new file mode 100644
index 0000000..8939729
--- /dev/null
+++ b/interfaces/proximity.idl
@@ -0,0 +1,6 @@
+[Constructor(optional SensorOptions sensorOptions), SecureContext, Exposed=Window]
+interface ProximitySensor : Sensor {
+  readonly attribute double? distance;
+  readonly attribute double? max;
+  readonly attribute boolean? near;
+};
diff --git a/interfaces/screen-orientation.idl b/interfaces/screen-orientation.idl
new file mode 100644
index 0000000..faa1c93
--- /dev/null
+++ b/interfaces/screen-orientation.idl
@@ -0,0 +1,34 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Screen Orientation API spec.
+// See https://w3c.github.io/screen-orientation/
+
+partial interface Screen {
+  [SameObject] readonly attribute ScreenOrientation orientation;
+};
+
+[Exposed=Window]
+interface ScreenOrientation : EventTarget {
+  Promise<void> lock(OrientationLockType orientation);
+  void unlock();
+  readonly attribute OrientationType type;
+  readonly attribute unsigned short angle;
+  attribute EventHandler onchange;
+};
+
+enum OrientationType {
+  "portrait-primary",
+  "portrait-secondary",
+  "landscape-primary",
+  "landscape-secondary"
+};
+
+enum OrientationLockType {
+  "any",
+  "natural",
+  "landscape",
+  "portrait",
+  "portrait-primary",
+  "portrait-secondary",
+  "landscape-primary",
+  "landscape-secondary"
+};
diff --git a/interfaces/selection-api.idl b/interfaces/selection-api.idl
new file mode 100644
index 0000000..27efc2a
--- /dev/null
+++ b/interfaces/selection-api.idl
@@ -0,0 +1,43 @@
+// http://w3c.github.io/selection-api/#selection-interface
+interface Selection {
+  readonly attribute Node?         anchorNode;
+  readonly attribute unsigned long anchorOffset;
+  readonly attribute Node?         focusNode;
+  readonly attribute unsigned long focusOffset;
+  readonly attribute boolean       isCollapsed;
+  readonly attribute unsigned long rangeCount;
+  readonly attribute DOMString     type;
+  Range     getRangeAt(unsigned long index);
+  void      addRange(Range range);
+  void      removeRange(Range range);
+  void      removeAllRanges();
+  void      empty();
+  void      collapse(Node? node, optional unsigned long offset = 0);
+  void      setPosition(Node? node, optional unsigned long offset = 0);
+  void      collapseToStart();
+  void      collapseToEnd();
+  void      extend(Node node, optional unsigned long offset = 0);
+  void      setBaseAndExtent(Node anchorNode,
+                             unsigned long anchorOffset,
+                             Node focusNode,
+                             unsigned long focusOffset);
+  void      selectAllChildren(Node node);
+  [CEReactions]
+  void      deleteFromDocument();
+  boolean   containsNode(Node node,
+                         optional boolean allowPartialContainment = false);
+  stringifier DOMString ();
+};
+
+partial interface Document {
+  Selection? getSelection();
+};
+
+partial interface Window {
+  Selection? getSelection();
+};
+
+partial interface GlobalEventHandlers {
+  attribute EventHandler onselectstart;
+  attribute EventHandler onselectionchange;
+};
diff --git a/interfaces/generic-sensor.idl b/interfaces/sensors.idl
similarity index 100%
rename from interfaces/generic-sensor.idl
rename to interfaces/sensors.idl
diff --git a/interfaces/storage.idl b/interfaces/storage.idl
new file mode 100644
index 0000000..cbaf9d0
--- /dev/null
+++ b/interfaces/storage.idl
@@ -0,0 +1,24 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Storage spec.
+// See https://storage.spec.whatwg.org/
+
+[SecureContext]
+interface mixin NavigatorStorage {
+  readonly attribute StorageManager storage;
+};
+Navigator includes NavigatorStorage;
+WorkerNavigator includes NavigatorStorage;
+
+[SecureContext,
+ Exposed=(Window,Worker)]
+interface StorageManager {
+  Promise<boolean> persisted();
+  [Exposed=Window] Promise<boolean> persist();
+
+  Promise<StorageEstimate> estimate();
+};
+
+dictionary StorageEstimate {
+  unsigned long long usage;
+  unsigned long long quota;
+};
diff --git a/interfaces/uievents.idl b/interfaces/uievents.idl
index 5964ee8..a273887 100644
--- a/interfaces/uievents.idl
+++ b/interfaces/uievents.idl
@@ -1,4 +1,8 @@
-[Constructor(DOMString type, optional UIEventInit eventInitDict)]
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the UI Events spec.
+// See https://w3c.github.io/uievents/
+
+[Constructor(DOMString type, optional UIEventInit eventInitDict), Exposed=Window]
 interface UIEvent : Event {
   readonly attribute Window? view;
   readonly attribute long detail;
@@ -9,7 +13,7 @@
   long detail = 0;
 };
 
-[Constructor(DOMString type, optional FocusEventInit eventInitDict)]
+[Constructor(DOMString type, optional FocusEventInit eventInitDict), Exposed=Window]
 interface FocusEvent : UIEvent {
   readonly attribute EventTarget? relatedTarget;
 };
@@ -18,7 +22,7 @@
   EventTarget? relatedTarget = null;
 };
 
-[Constructor(DOMString type, optional MouseEventInit eventInitDict)]
+[Constructor(DOMString type, optional MouseEventInit eventInitDict), Exposed=Window]
 interface MouseEvent : UIEvent {
   readonly attribute long screenX;
   readonly attribute long screenY;
@@ -67,7 +71,7 @@
   boolean modifierSymbolLock = false;
 };
 
-[Constructor(DOMString type, optional WheelEventInit eventInitDict)]
+[Constructor(DOMString type, optional WheelEventInit eventInitDict), Exposed=Window]
 interface WheelEvent : MouseEvent {
   // DeltaModeCode
   const unsigned long DOM_DELTA_PIXEL = 0x00;
@@ -87,19 +91,18 @@
   unsigned long deltaMode = 0;
 };
 
-[Constructor(DOMString type, optional InputEventInit eventInitDict)]
+[Constructor(DOMString type, optional InputEventInit eventInitDict), Exposed=Window]
 interface InputEvent : UIEvent {
   readonly attribute DOMString? data;
   readonly attribute boolean isComposing;
 };
 
-
 dictionary InputEventInit : UIEventInit {
   DOMString? data = "";
   boolean isComposing = false;
 };
 
-[Constructor(DOMString type, optional KeyboardEventInit eventInitDict)]
+[Constructor(DOMString type, optional KeyboardEventInit eventInitDict), Exposed=Window]
 interface KeyboardEvent : UIEvent {
   // KeyLocationCode
   const unsigned long DOM_KEY_LOCATION_STANDARD = 0x00;
@@ -130,7 +133,7 @@
   boolean isComposing = false;
 };
 
-[Constructor(DOMString type, optional CompositionEventInit eventInitDict)]
+[Constructor(DOMString type, optional CompositionEventInit eventInitDict), Exposed=Window]
 interface CompositionEvent : UIEvent {
   readonly attribute DOMString data;
 };
diff --git a/interfaces/url.idl b/interfaces/url.idl
new file mode 100644
index 0000000..aefaa56
--- /dev/null
+++ b/interfaces/url.idl
@@ -0,0 +1,35 @@
+[Constructor(USVString url, optional USVString base),
+ Exposed=(Window,Worker),
+ LegacyWindowAlias=webkitURL]
+interface URL {
+  stringifier attribute USVString href;
+  readonly attribute USVString origin;
+           attribute USVString protocol;
+           attribute USVString username;
+           attribute USVString password;
+           attribute USVString host;
+           attribute USVString hostname;
+           attribute USVString port;
+           attribute USVString pathname;
+           attribute USVString search;
+  [SameObject] readonly attribute URLSearchParams searchParams;
+           attribute USVString hash;
+
+  USVString toJSON();
+};
+
+[Constructor(optional (sequence<sequence<USVString>> or record<USVString, USVString> or USVString) init = ""),
+ Exposed=(Window,Worker)]
+interface URLSearchParams {
+  void append(USVString name, USVString value);
+  void delete(USVString name);
+  USVString? get(USVString name);
+  sequence<USVString> getAll(USVString name);
+  boolean has(USVString name);
+  void set(USVString name, USVString value);
+
+  void sort();
+
+  iterable<USVString, USVString>;
+  stringifier;
+};
diff --git a/interfaces/web-animations.idl b/interfaces/web-animations.idl
new file mode 100644
index 0000000..f5683f5
--- /dev/null
+++ b/interfaces/web-animations.idl
@@ -0,0 +1,154 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Web Animations spec.
+// See https://drafts.csswg.org/web-animations/
+
+[Exposed=Window]
+interface AnimationTimeline {
+    readonly attribute double? currentTime;
+};
+
+dictionary DocumentTimelineOptions {
+  DOMHighResTimeStamp originTime = 0;
+};
+
+[Exposed=Window,
+ Constructor (optional DocumentTimelineOptions options)]
+interface DocumentTimeline : AnimationTimeline {
+};
+
+[Exposed=Window,
+ Constructor (optional AnimationEffect? effect = null,
+              optional AnimationTimeline? timeline)]
+interface Animation : EventTarget {
+             attribute DOMString                id;
+             attribute AnimationEffect?         effect;
+             attribute AnimationTimeline?       timeline;
+             attribute double?                  startTime;
+             attribute double?                  currentTime;
+             attribute double                   playbackRate;
+    readonly attribute AnimationPlayState       playState;
+    readonly attribute boolean                  pending;
+    readonly attribute Promise<Animation>       ready;
+    readonly attribute Promise<Animation>       finished;
+             attribute EventHandler             onfinish;
+             attribute EventHandler             oncancel;
+    void cancel ();
+    void finish ();
+    void play ();
+    void pause ();
+    void updatePlaybackRate (double playbackRate);
+    void reverse ();
+};
+
+enum AnimationPlayState { "idle", "running", "paused", "finished" };
+
+[Exposed=Window]
+interface AnimationEffect {
+    EffectTiming         getTiming();
+    ComputedEffectTiming getComputedTiming();
+    void                 updateTiming(optional OptionalEffectTiming timing);
+};
+
+dictionary EffectTiming {
+    double                             delay = 0;
+    double                             endDelay = 0;
+    FillMode                           fill = "auto";
+    double                             iterationStart = 0.0;
+    unrestricted double                iterations = 1.0;
+    (unrestricted double or DOMString) duration = "auto";
+    PlaybackDirection                  direction = "normal";
+    DOMString                          easing = "linear";
+};
+
+dictionary OptionalEffectTiming {
+    double                             delay;
+    double                             endDelay;
+    FillMode                           fill;
+    double                             iterationStart;
+    unrestricted double                iterations;
+    (unrestricted double or DOMString) duration;
+    PlaybackDirection                  direction;
+    DOMString                          easing;
+};
+
+enum FillMode { "none", "forwards", "backwards", "both", "auto" };
+
+enum PlaybackDirection { "normal", "reverse", "alternate", "alternate-reverse" };
+
+dictionary ComputedEffectTiming : EffectTiming {
+    unrestricted double  endTime;
+    unrestricted double  activeDuration;
+    double?              localTime;
+    double?              progress;
+    unrestricted double? currentIteration;
+};
+
+[Exposed=Window,
+ Constructor ((Element or CSSPseudoElement)? target,
+              object? keyframes,
+              optional (unrestricted double or KeyframeEffectOptions) options),
+ Constructor (KeyframeEffect source)]
+interface KeyframeEffect : AnimationEffect {
+    attribute (Element or CSSPseudoElement)? target;
+    attribute IterationCompositeOperation    iterationComposite;
+    attribute CompositeOperation             composite;
+    sequence<object> getKeyframes ();
+    void             setKeyframes (object? keyframes);
+};
+
+dictionary BaseComputedKeyframe {
+     double?             offset = null;
+     double              computedOffset;
+     DOMString           easing = "linear";
+     CompositeOperation? composite = null;
+};
+
+dictionary BasePropertyIndexedKeyframe {
+    (double? or sequence<double?>)                         offset = [];
+    (DOMString or sequence<DOMString>)                     easing = [];
+    (CompositeOperation? or sequence<CompositeOperation?>) composite = [];
+};
+
+dictionary BaseKeyframe {
+    double?             offset = null;
+    DOMString           easing = "linear";
+    CompositeOperation? composite = null;
+};
+
+dictionary KeyframeEffectOptions : EffectTiming {
+    IterationCompositeOperation iterationComposite = "replace";
+    CompositeOperation          composite = "replace";
+};
+
+enum IterationCompositeOperation {"replace", "accumulate"};
+
+enum CompositeOperation {"replace", "add", "accumulate"};
+
+interface mixin Animatable {
+    Animation           animate (object? keyframes,
+                                 optional (unrestricted double or KeyframeAnimationOptions) options);
+    sequence<Animation> getAnimations ();
+};
+dictionary KeyframeAnimationOptions : KeyframeEffectOptions {
+    DOMString id = "";
+};
+
+partial interface Document {
+    readonly attribute DocumentTimeline timeline;
+    sequence<Animation> getAnimations();
+};
+
+Element includes Animatable;
+
+CSSPseudoElement includes Animatable;
+
+[Exposed=Window,
+ Constructor (DOMString type, optional AnimationPlaybackEventInit eventInitDict)]
+interface AnimationPlaybackEvent : Event {
+    readonly attribute double? currentTime;
+    readonly attribute double? timelineTime;
+};
+dictionary AnimationPlaybackEventInit : EventInit {
+    double? currentTime = null;
+    double? timelineTime = null;
+};
diff --git a/interfaces/web-audio-api.idl b/interfaces/web-audio-api.idl
new file mode 100644
index 0000000..9c25365
--- /dev/null
+++ b/interfaces/web-audio-api.idl
@@ -0,0 +1,606 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Web Audio API spec.
+// See https://webaudio.github.io/web-audio-api/
+
+enum AudioContextState {
+  "suspended",
+  "running",
+  "closed"
+};
+
+callback DecodeErrorCallback = void (DOMException error);
+
+callback DecodeSuccessCallback = void (AudioBuffer decodedData);
+
+[Exposed=Window]
+interface BaseAudioContext : EventTarget {
+  readonly attribute AudioDestinationNode destination;
+  readonly attribute float sampleRate;
+  readonly attribute double currentTime;
+  readonly attribute AudioListener listener;
+  readonly attribute AudioContextState state;
+  [SameObject, SecureContext]
+  readonly attribute AudioWorklet audioWorklet;
+  Promise<void> resume ();
+  attribute EventHandler onstatechange;
+  AudioBuffer createBuffer (unsigned long numberOfChannels, unsigned long length, float sampleRate);
+  Promise<AudioBuffer> decodeAudioData (ArrayBuffer audioData,
+                                        optional DecodeSuccessCallback successCallback,
+                                        optional DecodeErrorCallback errorCallback);
+  AudioBufferSourceNode createBufferSource ();
+  ConstantSourceNode createConstantSource ();
+  ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0,
+                                            optional unsigned long numberOfInputChannels = 2,
+                                            optional unsigned long numberOfOutputChannels = 2);
+  AnalyserNode createAnalyser ();
+  GainNode createGain ();
+  DelayNode createDelay (optional double maxDelayTime = 1.0);
+  BiquadFilterNode createBiquadFilter ();
+  IIRFilterNode createIIRFilter (sequence<double> feedforward, sequence<double> feedback);
+  WaveShaperNode createWaveShaper ();
+  PannerNode createPanner ();
+  StereoPannerNode createStereoPanner ();
+  ConvolverNode createConvolver ();
+  ChannelSplitterNode createChannelSplitter (optional unsigned long numberOfOutputs = 6);
+  ChannelMergerNode createChannelMerger (optional unsigned long numberOfInputs = 6);
+  DynamicsCompressorNode createDynamicsCompressor ();
+  OscillatorNode createOscillator ();
+  PeriodicWave createPeriodicWave (sequence<float> real, sequence<float> imag, optional PeriodicWaveConstraints constraints);
+};
+
+[Exposed=Window]
+enum AudioContextLatencyCategory {
+    "balanced",
+    "interactive",
+    "playback"
+};
+
+[Exposed=Window,
+ Constructor (optional AudioContextOptions contextOptions)]
+interface AudioContext : BaseAudioContext {
+    readonly attribute double baseLatency;
+    readonly attribute double outputLatency;
+    AudioTimestamp getOutputTimestamp ();
+    Promise<void> suspend ();
+    Promise<void> close ();
+    MediaElementAudioSourceNode createMediaElementSource (HTMLMediaElement mediaElement);
+    MediaStreamAudioSourceNode createMediaStreamSource (MediaStream mediaStream);
+    MediaStreamTrackAudioSourceNode createMediaStreamTrackSource (MediaStreamTrack mediaStreamTrack);
+    MediaStreamAudioDestinationNode createMediaStreamDestination ();
+};
+
+[Exposed=Window]
+dictionary AudioContextOptions {
+  (AudioContextLatencyCategory or double) latencyHint = "interactive";
+  float sampleRate;
+};
+
+[Exposed=Window]
+dictionary AudioTimestamp {
+  double contextTime;
+  DOMHighResTimeStamp performanceTime;
+};
+
+[Exposed=Window,
+ Constructor (OfflineAudioContextOptions contextOptions),
+ Constructor (unsigned long numberOfChannels, unsigned long length, float sampleRate)]
+interface OfflineAudioContext : BaseAudioContext {
+  Promise<AudioBuffer> startRendering();
+  Promise<void> suspend(double suspendTime);
+  readonly attribute unsigned long length;
+  attribute EventHandler oncomplete;
+};
+
+[Exposed=Window]
+dictionary OfflineAudioContextOptions {
+  unsigned long numberOfChannels = 1;
+  required unsigned long length;
+  required float sampleRate;
+};
+
+[Exposed=Window,
+ Constructor (DOMString type, OfflineAudioCompletionEventInit eventInitDict)]
+interface OfflineAudioCompletionEvent : Event {
+  readonly attribute AudioBuffer renderedBuffer;
+};
+
+[Exposed=Window]
+dictionary OfflineAudioCompletionEventInit : EventInit {
+  required AudioBuffer renderedBuffer;
+};
+
+[Exposed=Window,
+ Constructor (AudioBufferOptions options)]
+interface AudioBuffer {
+  readonly attribute float sampleRate;
+  readonly attribute unsigned long length;
+  readonly attribute double duration;
+  readonly attribute unsigned long numberOfChannels;
+  Float32Array getChannelData (unsigned long channel);
+  void copyFromChannel (Float32Array destination, unsigned long channelNumber, optional unsigned long startInChannel = 0);
+  void copyToChannel (Float32Array source, unsigned long channelNumber, optional unsigned long startInChannel = 0);
+};
+
+dictionary AudioBufferOptions {
+  unsigned long numberOfChannels = 1;
+  required unsigned long length;
+  required float sampleRate;
+};
+
+[Exposed=Window]
+interface AudioNode : EventTarget {
+  AudioNode connect (AudioNode destinationNode,
+                     optional unsigned long output = 0,
+                     optional unsigned long input = 0);
+  void connect (AudioParam destinationParam, optional unsigned long output = 0);
+  void disconnect ();
+  void disconnect (unsigned long output);
+  void disconnect (AudioNode destinationNode);
+  void disconnect (AudioNode destinationNode, unsigned long output);
+  void disconnect (AudioNode destinationNode, unsigned long output, unsigned long input);
+  void disconnect (AudioParam destinationParam);
+  void disconnect (AudioParam destinationParam, unsigned long output);
+  readonly attribute BaseAudioContext context;
+  readonly attribute unsigned long numberOfInputs;
+  readonly attribute unsigned long numberOfOutputs;
+  attribute unsigned long channelCount;
+  attribute ChannelCountMode channelCountMode;
+  attribute ChannelInterpretation channelInterpretation;
+};
+
+[Exposed=Window]
+enum ChannelCountMode {
+  "max",
+  "clamped-max",
+  "explicit"
+};
+
+[Exposed=Window]
+enum ChannelInterpretation {
+  "speakers",
+  "discrete"
+};
+
+dictionary AudioNodeOptions {
+  unsigned long channelCount;
+  ChannelCountMode channelCountMode;
+  ChannelInterpretation channelInterpretation;
+};
+
+enum AutomationRate {
+  "a-rate",
+  "k-rate"
+};
+
+[Exposed=Window]
+interface AudioParam {
+  attribute float value;
+  attribute AutomationRate automationRate;
+  readonly attribute float defaultValue;
+  readonly attribute float minValue;
+  readonly attribute float maxValue;
+  AudioParam setValueAtTime (float value, double startTime);
+  AudioParam linearRampToValueAtTime (float value, double endTime);
+  AudioParam exponentialRampToValueAtTime (float value, double endTime);
+  AudioParam setTargetAtTime (float target, double startTime, float timeConstant);
+  AudioParam setValueCurveAtTime (sequence<float> values, double startTime, double duration);
+  AudioParam cancelScheduledValues (double cancelTime);
+  AudioParam cancelAndHoldAtTime (double cancelTime);
+};
+
+[Exposed=Window]
+interface AudioScheduledSourceNode : AudioNode {
+  attribute EventHandler onended;
+  void start(optional double when = 0);
+  void stop(optional double when = 0);
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional AnalyserOptions options)]
+interface AnalyserNode : AudioNode {
+  void getFloatFrequencyData (Float32Array array);
+  void getByteFrequencyData (Uint8Array array);
+  void getFloatTimeDomainData (Float32Array array);
+  void getByteTimeDomainData (Uint8Array array);
+  attribute unsigned long fftSize;
+  readonly attribute unsigned long frequencyBinCount;
+  attribute double minDecibels;
+  attribute double maxDecibels;
+  attribute double smoothingTimeConstant;
+};
+
+dictionary AnalyserOptions : AudioNodeOptions {
+  unsigned long fftSize = 2048;
+  double maxDecibels = -30;
+  double minDecibels = -100;
+  double smoothingTimeConstant = 0.8;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional AudioBufferSourceOptions options)]
+interface AudioBufferSourceNode : AudioScheduledSourceNode {
+  attribute AudioBuffer? buffer;
+  readonly attribute AudioParam playbackRate;
+  readonly attribute AudioParam detune;
+  attribute boolean loop;
+  attribute double loopStart;
+  attribute double loopEnd;
+  void start (optional double when = 0,
+              optional double offset,
+              optional double duration);
+  void stop (optional double when = 0);
+};
+
+dictionary AudioBufferSourceOptions {
+  AudioBuffer? buffer;
+  float detune = 0;
+  boolean loop = false;
+  double loopEnd = 0;
+  double loopStart = 0;
+  float playbackRate = 1;
+};
+
+[Exposed=Window]
+interface AudioDestinationNode : AudioNode {
+  readonly attribute unsigned long maxChannelCount;
+};
+
+[Exposed=Window]
+interface AudioListener {
+  readonly attribute AudioParam positionX;
+  readonly attribute AudioParam positionY;
+  readonly attribute AudioParam positionZ;
+  readonly attribute AudioParam forwardX;
+  readonly attribute AudioParam forwardY;
+  readonly attribute AudioParam forwardZ;
+  readonly attribute AudioParam upX;
+  readonly attribute AudioParam upY;
+  readonly attribute AudioParam upZ;
+  void setPosition (float x, float y, float z);
+  void setOrientation (float x, float y, float z, float xUp, float yUp, float zUp);
+};
+
+[Exposed=Window,
+ Constructor (DOMString type, AudioProcessingEventInit eventInitDict)]
+interface AudioProcessingEvent : Event {
+  readonly attribute double playbackTime;
+  readonly attribute AudioBuffer inputBuffer;
+  readonly attribute AudioBuffer outputBuffer;
+};
+
+dictionary AudioProcessingEventInit : EventInit {
+  required double playbackTime;
+  required AudioBuffer inputBuffer;
+  required AudioBuffer outputBuffer;
+};
+
+enum BiquadFilterType {
+  "lowpass",
+  "highpass",
+  "bandpass",
+  "lowshelf",
+  "highshelf",
+  "peaking",
+  "notch",
+  "allpass"
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional BiquadFilterOptions options)]
+interface BiquadFilterNode : AudioNode {
+  attribute BiquadFilterType type;
+  readonly attribute AudioParam frequency;
+  readonly attribute AudioParam detune;
+  readonly attribute AudioParam Q;
+  readonly attribute AudioParam gain;
+  void getFrequencyResponse (Float32Array frequencyHz, Float32Array magResponse, Float32Array phaseResponse);
+};
+
+dictionary BiquadFilterOptions : AudioNodeOptions {
+  BiquadFilterType type = "lowpass";
+  float Q = 1;
+  float detune = 0;
+  float frequency = 350;
+  float gain = 0;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional ChannelMergerOptions options)]
+interface ChannelMergerNode : AudioNode {
+};
+
+dictionary ChannelMergerOptions : AudioNodeOptions {
+  unsigned long numberOfInputs = 6;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional ChannelSplitterNode options)]
+interface ChannelSplitterNode : AudioNode {
+};
+
+dictionary ChannelSplitterOptions : AudioNodeOptions {
+  unsigned long numberOfOutputs = 6;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional ConstantSourceOptions options)]
+interface ConstantSourceNode : AudioScheduledSourceNode {
+  readonly attribute AudioParam offset;
+};
+
+dictionary ConstantSourceOptions {
+  float offset = 1;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional ConvolverOptions options)]
+interface ConvolverNode : AudioNode {
+  attribute AudioBuffer? buffer;
+  attribute boolean normalize;
+};
+
+dictionary ConvolverOptions : AudioNodeOptions {
+  AudioBuffer? buffer;
+  boolean disableNormalization = false;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional DelayOptions options)]
+interface DelayNode : AudioNode {
+  readonly attribute AudioParam delayTime;
+};
+
+dictionary DelayOptions : AudioNodeOptions {
+  double maxDelayTime = 1;
+  double delayTime = 0;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional DynamicsCompressorOptions options)]
+interface DynamicsCompressorNode : AudioNode {
+  readonly attribute AudioParam threshold;
+  readonly attribute AudioParam knee;
+  readonly attribute AudioParam ratio;
+  readonly attribute float reduction;
+  readonly attribute AudioParam attack;
+  readonly attribute AudioParam release;
+};
+
+dictionary DynamicsCompressorOptions : AudioNodeOptions {
+  float attack = 0.003;
+  float knee = 30;
+  float ratio = 12;
+  float release = 0.25;
+  float threshold = -24;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional GainOptions options)]
+interface GainNode : AudioNode {
+  readonly attribute AudioParam gain;
+};
+
+dictionary GainOptions : AudioNodeOptions {
+  float gain = 1.0;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, IIRFilterOptions options)]
+interface IIRFilterNode : AudioNode {
+  void getFrequencyResponse (Float32Array frequencyHz, Float32Array magResponse, Float32Array phaseResponse);
+};
+
+dictionary IIRFilterOptions : AudioNodeOptions {
+  required sequence<double> feedforward;
+  required sequence<double> feedback;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, MediaElementAudioSourceOptions options)]
+interface MediaElementAudioSourceNode : AudioNode {
+  [SameObject] readonly attribute HTMLMediaElement mediaElement;
+};
+
+dictionary MediaElementAudioSourceOptions {
+  required HTMLMediaElement mediaElement;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional AudioNodeOptions options)]
+interface MediaStreamAudioDestinationNode : AudioNode {
+  readonly attribute MediaStream stream;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, MediaStreamAudioSourceOptions options)]
+interface MediaStreamAudioSourceNode : AudioNode {
+  [SameObject] readonly attribute MediaStream mediaStream;
+};
+
+dictionary MediaStreamAudioSourceOptions {
+  required MediaStream mediaStream;
+};
+
+[Exposed=Window,
+ Constructor (AudioContext context, MediaStreamTrackAudioSourceOptions options)]
+interface MediaStreamTrackAudioSourceNode : AudioNode {
+};
+
+dictionary MediaStreamTrackAudioSourceOptions {
+  required MediaStreamTrack mediaStreamTrack;
+};
+
+enum OscillatorType {
+  "sine",
+  "square",
+  "sawtooth",
+  "triangle",
+  "custom"
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional OscillatorOptions options)]
+interface OscillatorNode : AudioScheduledSourceNode {
+  attribute OscillatorType type;
+  readonly attribute AudioParam frequency;
+  readonly attribute AudioParam detune;
+  void setPeriodicWave (PeriodicWave periodicWave);
+};
+
+dictionary OscillatorOptions : AudioNodeOptions {
+  OscillatorType type = "sine";
+  float frequency = 440;
+  float detune = 0;
+  PeriodicWave periodicWave;
+};
+
+enum PanningModelType {
+    "equalpower",
+    "HRTF"
+};
+
+enum DistanceModelType {
+  "linear",
+  "inverse",
+  "exponential"
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional PannerOptions options)]
+interface PannerNode : AudioNode {
+  attribute PanningModelType panningModel;
+  readonly attribute AudioParam positionX;
+  readonly attribute AudioParam positionY;
+  readonly attribute AudioParam positionZ;
+  readonly attribute AudioParam orientationX;
+  readonly attribute AudioParam orientationY;
+  readonly attribute AudioParam orientationZ;
+  attribute DistanceModelType distanceModel;
+  attribute double refDistance;
+  attribute double maxDistance;
+  attribute double rolloffFactor;
+  attribute double coneInnerAngle;
+  attribute double coneOuterAngle;
+  attribute double coneOuterGain;
+  void setPosition (float x, float y, float z);
+  void setOrientation (float x, float y, float z);
+};
+
+dictionary PannerOptions : AudioNodeOptions {
+  PanningModelType panningModel = "equalpower";
+  DistanceModelType distanceModel = "inverse";
+  float positionX = 0;
+  float positionY = 0;
+  float positionZ = 0;
+  float orientationX = 1;
+  float orientationY = 0;
+  float orientationZ = 0;
+  double refDistance = 1;
+  double maxDistance = 10000;
+  double rolloffFactor = 1;
+  double coneInnerAngle = 360;
+  double coneOuterAngle = 360;
+  double coneOuterGain = 0;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional PeriodicWaveOptions options)]
+interface PeriodicWave {
+};
+
+dictionary PeriodicWaveConstraints {
+  boolean disableNormalization = false;
+};
+
+dictionary PeriodicWaveOptions : PeriodicWaveConstraints {
+  sequence<float> real;
+  sequence<float> imag;
+};
+
+[Exposed=Window]
+interface ScriptProcessorNode : AudioNode {
+  attribute EventHandler onaudioprocess;
+  readonly attribute long bufferSize;
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional StereoPannerOptions options)]
+interface StereoPannerNode : AudioNode {
+  readonly attribute AudioParam pan;
+};
+
+dictionary StereoPannerOptions : AudioNodeOptions {
+  float pan = 0;
+};
+
+enum OverSampleType {
+  "none",
+  "2x",
+  "4x"
+};
+
+[Exposed=Window,
+ Constructor (BaseAudioContext context, optional WaveShaperOptions options)]
+interface WaveShaperNode : AudioNode {
+  attribute Float32Array? curve;
+  attribute OverSampleType oversample;
+};
+
+dictionary WaveShaperOptions : AudioNodeOptions {
+  sequence<float> curve;
+  OverSampleType oversample = "none";
+};
+
+[Exposed=Window, SecureContext]
+interface AudioWorklet : Worklet {
+};
+
+[Global=(Worklet, AudioWorklet), Exposed=AudioWorklet]
+interface AudioWorkletGlobalScope : WorkletGlobalScope {
+  void registerProcessor (DOMString name, VoidFunction processorCtor);
+  readonly attribute double currentTime;
+  readonly attribute float sampleRate;
+};
+
+[Exposed=Window]
+interface AudioParamMap {
+  readonly maplike<DOMString, AudioParam>;
+};
+
+enum AudioWorkletProcessorState {
+  "pending",
+  "running",
+  "stopped",
+  "error"
+};
+
+[Exposed=Window,
+ SecureContext,
+ Constructor (BaseAudioContext context, DOMString name, optional AudioWorkletNodeOptions options)]
+interface AudioWorkletNode : AudioNode {
+  readonly attribute AudioParamMap parameters;
+  readonly attribute MessagePort port;
+  readonly attribute AudioWorkletProcessorState processorState;
+  attribute EventHandler onprocessorstatechange;
+};
+
+dictionary AudioWorkletNodeOptions : AudioNodeOptions {
+  unsigned long numberOfInputs = 1;
+  unsigned long numberOfOutputs = 1;
+  sequence<unsigned long> outputChannelCount;
+  record<DOMString, double> parameterData;
+  object processorOptions = null;
+};
+
+[Exposed=AudioWorklet,
+Constructor (optional AudioWorkletNodeOptions options)]
+interface AudioWorkletProcessor {
+  readonly attribute MessagePort port;
+};
+
+dictionary AudioParamDescriptor {
+  required DOMString name;
+  float defaultValue = 0;
+  float minValue = -3.4028235e38;
+  float maxValue = 3.4028235e38;
+};
diff --git a/interfaces/web-nfc.idl b/interfaces/web-nfc.idl
new file mode 100644
index 0000000..30f57ca
--- /dev/null
+++ b/interfaces/web-nfc.idl
@@ -0,0 +1,63 @@
+dictionary NFCMessage {
+    sequence<NFCRecord> records;
+    USVString           url;
+};
+
+typedef (DOMString or unrestricted double or ArrayBuffer or Dictionary) NFCRecordData;
+
+dictionary NFCRecord {
+    NFCRecordType recordType;
+    USVString     mediaType;
+    NFCRecordData data;
+};
+
+enum NFCRecordType {
+    "empty",
+    "text",
+    "url",
+    "json",
+    "opaque"
+};
+
+partial interface Navigator {
+    [SecureContext,
+     SameObject]
+    readonly attribute NFC nfc;
+};
+
+typedef (DOMString or ArrayBuffer or NFCMessage) NFCPushMessage;
+
+[SecureContext]
+interface NFC {
+    Promise<void> push(NFCPushMessage message, optional NFCPushOptions options);
+    Promise<void> cancelPush(optional NFCPushTarget target = "any");
+    Promise<long> watch(MessageCallback callback,
+                        optional NFCWatchOptions options);
+    Promise<void> cancelWatch(optional long id);
+};
+
+callback MessageCallback = void (NFCMessage message);
+
+dictionary NFCPushOptions {
+    NFCPushTarget       target = "any";
+    unrestricted double timeout = Infinity;
+    boolean             ignoreRead = true;
+};
+
+enum NFCPushTarget {
+    "tag",
+    "peer",
+    "any"
+};
+
+dictionary NFCWatchOptions {
+    USVString      url = "";
+    NFCRecordType? recordType;
+    USVString      mediaType = "";
+    NFCWatchMode   mode = "web-nfc-only";
+};
+
+enum NFCWatchMode {
+    "web-nfc-only",
+    "any"
+};
diff --git a/interfaces/web-share.idl b/interfaces/web-share.idl
index cf19b7e..7403af3 100644
--- a/interfaces/web-share.idl
+++ b/interfaces/web-share.idl
@@ -1,8 +1,9 @@
-// https://wicg.github.io/web-share/
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Web Share API spec.
+// See https://wicg.github.io/web-share/
 
 partial interface Navigator {
-  [SecureContext]
-  Promise<void> share(optional ShareData data);
+  [SecureContext] Promise<void> share(optional ShareData data);
 };
 
 dictionary ShareData {
diff --git a/interfaces/webauthn.idl b/interfaces/webauthn.idl
new file mode 100644
index 0000000..2d2ef50
--- /dev/null
+++ b/interfaces/webauthn.idl
@@ -0,0 +1,227 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the Web Authentication spec.
+// See https://w3c.github.io/webauthn/
+
+[SecureContext, Exposed=Window]
+interface PublicKeyCredential : Credential {
+    [SameObject] readonly attribute ArrayBuffer              rawId;
+    [SameObject] readonly attribute AuthenticatorResponse    response;
+    AuthenticationExtensionsClientOutputs getClientExtensionResults();
+};
+
+partial dictionary CredentialCreationOptions {
+    PublicKeyCredentialCreationOptions      publicKey;
+};
+
+partial dictionary CredentialRequestOptions {
+    PublicKeyCredentialRequestOptions      publicKey;
+};
+
+partial interface PublicKeyCredential {
+    static Promise < boolean > isUserVerifyingPlatformAuthenticatorAvailable();
+};
+
+[SecureContext, Exposed=Window]
+interface AuthenticatorResponse {
+    [SameObject] readonly attribute ArrayBuffer      clientDataJSON;
+};
+
+[SecureContext, Exposed=Window]
+interface AuthenticatorAttestationResponse : AuthenticatorResponse {
+    [SameObject] readonly attribute ArrayBuffer      attestationObject;
+};
+
+[SecureContext, Exposed=Window]
+interface AuthenticatorAssertionResponse : AuthenticatorResponse {
+    [SameObject] readonly attribute ArrayBuffer      authenticatorData;
+    [SameObject] readonly attribute ArrayBuffer      signature;
+    [SameObject] readonly attribute ArrayBuffer?     userHandle;
+};
+
+dictionary PublicKeyCredentialParameters {
+    required PublicKeyCredentialType      type;
+    required COSEAlgorithmIdentifier      alg;
+};
+
+dictionary PublicKeyCredentialCreationOptions {
+    required PublicKeyCredentialRpEntity         rp;
+    required PublicKeyCredentialUserEntity       user;
+
+    required BufferSource                             challenge;
+    required sequence<PublicKeyCredentialParameters>  pubKeyCredParams;
+
+    unsigned long                                timeout;
+    sequence<PublicKeyCredentialDescriptor>      excludeCredentials = [];
+    AuthenticatorSelectionCriteria               authenticatorSelection;
+    AttestationConveyancePreference              attestation = "none";
+    AuthenticationExtensionsClientInputs         extensions;
+};
+
+dictionary PublicKeyCredentialEntity {
+    required DOMString    name;
+    USVString             icon;
+};
+
+dictionary PublicKeyCredentialRpEntity : PublicKeyCredentialEntity {
+    DOMString      id;
+};
+
+dictionary PublicKeyCredentialUserEntity : PublicKeyCredentialEntity {
+    required BufferSource   id;
+    required DOMString      displayName;
+};
+
+dictionary AuthenticatorSelectionCriteria {
+    AuthenticatorAttachment      authenticatorAttachment;
+    boolean                      requireResidentKey = false;
+    UserVerificationRequirement  userVerification = "preferred";
+};
+
+enum AuthenticatorAttachment {
+    "platform",       // Platform attachment
+    "cross-platform"  // Cross-platform attachment
+};
+
+enum AttestationConveyancePreference {
+    "none",
+    "indirect",
+    "direct"
+};
+
+dictionary PublicKeyCredentialRequestOptions {
+    required BufferSource                challenge;
+    unsigned long                        timeout;
+    USVString                            rpId;
+    sequence<PublicKeyCredentialDescriptor> allowCredentials = [];
+    UserVerificationRequirement          userVerification = "preferred";
+    AuthenticationExtensionsClientInputs extensions;
+};
+
+dictionary AuthenticationExtensionsClientInputs {
+};
+
+dictionary AuthenticationExtensionsClientOutputs {
+};
+
+typedef record<DOMString, DOMString> AuthenticationExtensionsAuthenticatorInputs;
+
+dictionary CollectedClientData {
+    required DOMString           type;
+    required DOMString           challenge;
+    required DOMString           origin;
+    TokenBinding                 tokenBinding;
+};
+
+dictionary TokenBinding {
+    required TokenBindingStatus status;
+    DOMString id;
+};
+
+enum TokenBindingStatus { "present", "supported", "not-supported" };
+
+enum PublicKeyCredentialType {
+    "public-key"
+};
+
+dictionary PublicKeyCredentialDescriptor {
+    required PublicKeyCredentialType      type;
+    required BufferSource                 id;
+    sequence<AuthenticatorTransport>      transports;
+};
+
+enum AuthenticatorTransport {
+    "usb",
+    "nfc",
+    "ble"
+};
+
+typedef long COSEAlgorithmIdentifier;
+
+enum UserVerificationRequirement {
+    "required",
+    "preferred",
+    "discouraged"
+};
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  USVString appid;
+};
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  boolean appid;
+};
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  USVString txAuthSimple;
+};
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  USVString txAuthSimple;
+};
+
+dictionary txAuthGenericArg {
+    required USVString contentType;    // MIME-Type of the content, e.g., "image/png"
+    required ArrayBuffer content;
+};
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  txAuthGenericArg txAuthGeneric;
+};
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  ArrayBuffer txAuthGeneric;
+};
+
+typedef sequence<AAGUID> AuthenticatorSelectionList;
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  AuthenticatorSelectionList authnSel;
+};
+
+typedef BufferSource      AAGUID;
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  boolean authnSel;
+};
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  boolean exts;
+};
+
+typedef sequence<USVString> AuthenticationExtensionsSupported;
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  AuthenticationExtensionsSupported exts;
+};
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  boolean uvi;
+};
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  ArrayBuffer uvi;
+};
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  boolean loc;
+};
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  Coordinates loc;
+};
+
+partial dictionary AuthenticationExtensionsClientInputs {
+  boolean uvm;
+};
+
+typedef sequence<unsigned long> UvmEntry;
+typedef sequence<UvmEntry> UvmEntries;
+
+partial dictionary AuthenticationExtensionsClientOutputs {
+  UvmEntries uvm;
+};
+
+dictionary authenticatorBiometricPerfBounds{
+    float FAR;
+    float FRR;
+    };
diff --git a/interfaces/webidl.idl b/interfaces/webidl.idl
index 8eb1d06..924fc57 100644
--- a/interfaces/webidl.idl
+++ b/interfaces/webidl.idl
@@ -6,7 +6,8 @@
 
 [
  Exposed=(Window,Worker),
- Constructor(optional DOMString message = "", optional DOMString name = "Error")]
+ Constructor(optional DOMString message = "", optional DOMString name = "Error")
+]
 interface DOMException { // but see below note about ECMAScript binding
   readonly attribute DOMString name;
   readonly attribute DOMString message;
@@ -40,5 +41,7 @@
 };
 
 typedef unsigned long long DOMTimeStamp;
+
 callback Function = any (any... arguments);
+
 callback VoidFunction = void ();
diff --git a/interfaces/webrtc-pc.idl b/interfaces/webrtc-pc.idl
index ae20055..fde2ba0 100644
--- a/interfaces/webrtc-pc.idl
+++ b/interfaces/webrtc-pc.idl
@@ -1,15 +1,16 @@
-// IDL definition extracted from
-// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the WebRTC spec.
+// See https://w3c.github.io/webrtc-pc/
 
 dictionary RTCConfiguration {
-    sequence<RTCIceServer>   iceServers;
-    RTCIceTransportPolicy    iceTransportPolicy = "all";
-    RTCBundlePolicy          bundlePolicy = "balanced";
-    RTCRtcpMuxPolicy         rtcpMuxPolicy = "require";
-    DOMString                peerIdentity;
-    sequence<RTCCertificate> certificates;
-    [EnforceRange]
-    octet                    iceCandidatePoolSize = 0;
+             sequence<RTCIceServer>   iceServers;
+             RTCIceTransportPolicy    iceTransportPolicy = "all";
+             RTCBundlePolicy          bundlePolicy = "balanced";
+             RTCRtcpMuxPolicy         rtcpMuxPolicy = "require";
+             DOMString                peerIdentity;
+             sequence<RTCCertificate> certificates;
+             [EnforceRange]
+             octet                    iceCandidatePoolSize = 0;
 };
 
 enum RTCIceCredentialType {
@@ -18,14 +19,14 @@
 };
 
 dictionary RTCOAuthCredential {
-    required DOMString macKey;
-    required DOMString accessToken;
+    required DOMString        macKey;
+    required DOMString        accessToken;
 };
 
 dictionary RTCIceServer {
     required (DOMString or sequence<DOMString>) urls;
              DOMString                          username;
-             (DOMString or RTCOAuthCredential)  credential;
+             (DOMString or RTCOAuthCredential)     credential;
              RTCIceCredentialType               credentialType = "password";
 };
 
@@ -47,71 +48,16 @@
 };
 
 dictionary RTCOfferAnswerOptions {
-    boolean voiceActivityDetection = true;
+             boolean voiceActivityDetection = true;
 };
 
 dictionary RTCOfferOptions : RTCOfferAnswerOptions {
-    boolean iceRestart = false;
-    boolean offerToReceiveAudio;
-    boolean offerToReceiveVideo;
+             boolean iceRestart = false;
 };
 
-dictionary RTCAnswerOptions : RTCOfferAnswerOptions {
+          dictionary RTCAnswerOptions : RTCOfferAnswerOptions {
 };
 
-[Constructor(optional RTCConfiguration configuration)]
-interface RTCPeerConnection : EventTarget {
-    Promise<RTCSessionDescriptionInit> createOffer(optional RTCOfferOptions options);
-    Promise<RTCSessionDescriptionInit> createAnswer(optional RTCAnswerOptions options);
-    Promise<void>                      setLocalDescription(RTCSessionDescriptionInit description);
-    readonly attribute RTCSessionDescription? localDescription;
-    readonly attribute RTCSessionDescription? currentLocalDescription;
-    readonly attribute RTCSessionDescription? pendingLocalDescription;
-    Promise<void>                      setRemoteDescription(RTCSessionDescriptionInit description);
-    readonly attribute RTCSessionDescription? remoteDescription;
-    readonly attribute RTCSessionDescription? currentRemoteDescription;
-    readonly attribute RTCSessionDescription? pendingRemoteDescription;
-    Promise<void>                      addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate);
-    readonly attribute RTCSignalingState      signalingState;
-    readonly attribute RTCIceGatheringState   iceGatheringState;
-    readonly attribute RTCIceConnectionState  iceConnectionState;
-    readonly attribute RTCPeerConnectionState connectionState;
-    readonly attribute boolean?               canTrickleIceCandidates;
-    static sequence<RTCIceServer>      getDefaultIceServers();
-    RTCConfiguration                   getConfiguration();
-    void                               setConfiguration(RTCConfiguration configuration);
-    void                               close();
-             attribute EventHandler           onnegotiationneeded;
-             attribute EventHandler           onicecandidate;
-             attribute EventHandler           onicecandidateerror;
-             attribute EventHandler           onsignalingstatechange;
-             attribute EventHandler           oniceconnectionstatechange;
-             attribute EventHandler           onicegatheringstatechange;
-             attribute EventHandler           onconnectionstatechange;
-             attribute EventHandler           onfingerprintfailure;
-};
-
-partial interface RTCPeerConnection {
-    Promise<void> createOffer(RTCSessionDescriptionCallback successCallback,
-                              RTCPeerConnectionErrorCallback failureCallback,
-                              optional RTCOfferOptions options);
-    Promise<void> setLocalDescription(RTCSessionDescriptionInit description,
-                                      VoidFunction successCallback,
-                                      RTCPeerConnectionErrorCallback failureCallback);
-    Promise<void> createAnswer(RTCSessionDescriptionCallback successCallback,
-                               RTCPeerConnectionErrorCallback failureCallback);
-    Promise<void> setRemoteDescription(RTCSessionDescriptionInit description,
-                                       VoidFunction successCallback,
-                                       RTCPeerConnectionErrorCallback failureCallback);
-    Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate,
-                                  VoidFunction successCallback,
-                                  RTCPeerConnectionErrorCallback failureCallback);
-};
-
-callback RTCPeerConnectionErrorCallback = void (DOMException error);
-
-callback RTCSessionDescriptionCallback = void (RTCSessionDescriptionInit description);
-
 enum RTCSignalingState {
     "stable",
     "have-local-offer",
@@ -141,11 +87,59 @@
     "checking",
     "connected",
     "completed",
-    "failed",
     "disconnected",
+    "failed",
     "closed"
 };
 
+          [ Constructor (optional RTCConfiguration configuration), Exposed=Window]
+interface RTCPeerConnection : EventTarget  {
+    Promise<RTCSessionDescriptionInit> createOffer (optional RTCOfferOptions options);
+    Promise<RTCSessionDescriptionInit> createAnswer (optional RTCAnswerOptions options);
+    Promise<void>                      setLocalDescription (RTCSessionDescriptionInit description);
+    readonly        attribute RTCSessionDescription?    localDescription;
+    readonly        attribute RTCSessionDescription?    currentLocalDescription;
+    readonly        attribute RTCSessionDescription?    pendingLocalDescription;
+    Promise<void>                      setRemoteDescription (RTCSessionDescriptionInit description);
+    readonly        attribute RTCSessionDescription?    remoteDescription;
+    readonly        attribute RTCSessionDescription?    currentRemoteDescription;
+    readonly        attribute RTCSessionDescription?    pendingRemoteDescription;
+    Promise<void>                      addIceCandidate ((RTCIceCandidateInit or RTCIceCandidate) candidate);
+    readonly        attribute RTCSignalingState         signalingState;
+    readonly        attribute RTCIceGatheringState      iceGatheringState;
+    readonly        attribute RTCIceConnectionState     iceConnectionState;
+    readonly        attribute RTCPeerConnectionState    connectionState;
+    readonly        attribute boolean?                  canTrickleIceCandidates;
+    static sequence<RTCIceServer>      getDefaultIceServers ();
+    RTCConfiguration                   getConfiguration ();
+    void                               setConfiguration (RTCConfiguration configuration);
+    void                               close ();
+                    attribute EventHandler              onnegotiationneeded;
+                    attribute EventHandler              onicecandidate;
+                    attribute EventHandler              onicecandidateerror;
+                    attribute EventHandler              onsignalingstatechange;
+                    attribute EventHandler              oniceconnectionstatechange;
+                    attribute EventHandler              onicegatheringstatechange;
+                    attribute EventHandler              onconnectionstatechange;
+};
+
+partial interface RTCPeerConnection {
+    Promise<void> createOffer (RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback, optional RTCOfferOptions options);
+    Promise<void> setLocalDescription (RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
+    Promise<void> createAnswer (RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback);
+    Promise<void> setRemoteDescription (RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
+    Promise<void> addIceCandidate ((RTCIceCandidateInit or RTCIceCandidate) candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
+};
+
+                callback RTCPeerConnectionErrorCallback = void (DOMException error);
+
+                callback RTCSessionDescriptionCallback = void (RTCSessionDescriptionInit description);
+
+partial dictionary RTCOfferOptions {
+            boolean offerToReceiveAudio;
+            boolean offerToReceiveVideo;
+          };
+
 enum RTCSdpType {
     "offer",
     "pranswer",
@@ -153,10 +147,10 @@
     "rollback"
 };
 
-[Constructor(RTCSessionDescriptionInit descriptionInitDict)]
+          [ Constructor (RTCSessionDescriptionInit descriptionInitDict), Exposed=Window]
 interface RTCSessionDescription {
-    readonly attribute RTCSdpType type;
-    readonly attribute DOMString  sdp;
+    readonly        attribute RTCSdpType type;
+    readonly        attribute DOMString  sdp;
     [Default] object toJSON();
 };
 
@@ -165,30 +159,30 @@
              DOMString  sdp = "";
 };
 
-[Constructor(optional RTCIceCandidateInit candidateInitDict)]
+          [ Constructor (optional RTCIceCandidateInit candidateInitDict), Exposed=Window]
 interface RTCIceCandidate {
-    readonly attribute DOMString               candidate;
-    readonly attribute DOMString?              sdpMid;
-    readonly attribute unsigned short?         sdpMLineIndex;
-    readonly attribute DOMString?              foundation;
-    readonly attribute RTCIceComponent?        component;
-    readonly attribute unsigned long?          priority;
-    readonly attribute DOMString?              ip;
-    readonly attribute RTCIceProtocol?         protocol;
-    readonly attribute unsigned short?         port;
-    readonly attribute RTCIceCandidateType?    type;
-    readonly attribute RTCIceTcpCandidateType? tcpType;
-    readonly attribute DOMString?              relatedAddress;
-    readonly attribute unsigned short?         relatedPort;
-    readonly attribute DOMString?              usernameFragment;
+    readonly        attribute DOMString               candidate;
+    readonly        attribute DOMString?              sdpMid;
+    readonly        attribute unsigned short?         sdpMLineIndex;
+    readonly        attribute DOMString?              foundation;
+    readonly        attribute RTCIceComponent?        component;
+    readonly        attribute unsigned long?          priority;
+    readonly        attribute DOMString?              ip;
+    readonly        attribute RTCIceProtocol?         protocol;
+    readonly        attribute unsigned short?         port;
+    readonly        attribute RTCIceCandidateType?    type;
+    readonly        attribute RTCIceTcpCandidateType? tcpType;
+    readonly        attribute DOMString?              relatedAddress;
+    readonly        attribute unsigned short?         relatedPort;
+    readonly        attribute DOMString?              usernameFragment;
     RTCIceCandidateInit toJSON();
 };
 
 dictionary RTCIceCandidateInit {
-    DOMString       candidate = "";
-    DOMString?      sdpMid = null;
-    unsigned short? sdpMLineIndex = null;
-    DOMString       usernameFragment;
+             DOMString       candidate = "";
+             DOMString?      sdpMid = null;
+             unsigned short? sdpMLineIndex = null;
+             DOMString       usernameFragment;
 };
 
 enum RTCIceProtocol {
@@ -209,29 +203,29 @@
     "relay"
 };
 
-[Constructor(DOMString type, optional RTCPeerConnectionIceEventInit eventInitDict)]
+          [ Constructor (DOMString type, optional RTCPeerConnectionIceEventInit eventInitDict), Exposed=Window]
 interface RTCPeerConnectionIceEvent : Event {
-    readonly attribute RTCIceCandidate? candidate;
-    readonly attribute DOMString?       url;
+    readonly        attribute RTCIceCandidate? candidate;
+    readonly        attribute DOMString?       url;
 };
 
-dictionary RTCPeerConnectionIceEventInit : EventInit {
-    RTCIceCandidate? candidate;
-    DOMString?       url;
+          dictionary RTCPeerConnectionIceEventInit : EventInit {
+             RTCIceCandidate? candidate;
+             DOMString?       url;
 };
 
-[Constructor(DOMString type, RTCPeerConnectionIceErrorEventInit eventInitDict)]
+          [ Constructor (DOMString type, RTCPeerConnectionIceErrorEventInit eventInitDict), Exposed=Window]
 interface RTCPeerConnectionIceErrorEvent : Event {
-    readonly attribute DOMString      hostCandidate;
-    readonly attribute DOMString      url;
-    readonly attribute unsigned short errorCode;
-    readonly attribute USVString      errorText;
+    readonly        attribute DOMString      hostCandidate;
+    readonly        attribute DOMString      url;
+    readonly        attribute unsigned short errorCode;
+    readonly        attribute USVString      errorText;
 };
 
-dictionary RTCPeerConnectionIceErrorEventInit : EventInit {
+          dictionary RTCPeerConnectionIceErrorEventInit : EventInit {
              DOMString      hostCandidate;
              DOMString      url;
-    required unsigned short errorCode;
+             required unsigned short errorCode;
              USVString      statusText;
 };
 
@@ -243,7 +237,7 @@
 };
 
 partial interface RTCPeerConnection {
-    static Promise<RTCCertificate> generateCertificate(AlgorithmIdentifier keygenAlgorithm);
+    static Promise<RTCCertificate> generateCertificate (AlgorithmIdentifier keygenAlgorithm);
 };
 
 dictionary RTCCertificateExpiration {
@@ -251,29 +245,26 @@
     DOMTimeStamp expires;
 };
 
-interface RTCCertificate {
-    readonly attribute DOMTimeStamp expires;
-    sequence<RTCDtlsFingerprint> getFingerprints();
-    // At risk due to lack of implementers' interest.
-    AlgorithmIdentifier          getAlgorithm();
+[Exposed=Window] interface RTCCertificate {
+    readonly        attribute DOMTimeStamp expires;
+    static sequence<AlgorithmIdentifier> getSupportedAlgorithms();
+    sequence<RTCDtlsFingerprint> getFingerprints ();
 };
 
 partial interface RTCPeerConnection {
-    sequence<RTCRtpSender>      getSenders();
-    sequence<RTCRtpReceiver>    getReceivers();
-    sequence<RTCRtpTransceiver> getTransceivers();
-    RTCRtpSender                addTrack(MediaStreamTrack track,
-                                         MediaStream... streams);
-    void                        removeTrack(RTCRtpSender sender);
-    RTCRtpTransceiver           addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
-                                               optional RTCRtpTransceiverInit init);
-    attribute EventHandler ontrack;
+    sequence<RTCRtpSender>      getSenders ();
+    sequence<RTCRtpReceiver>    getReceivers ();
+    sequence<RTCRtpTransceiver> getTransceivers ();
+    RTCRtpSender                addTrack (MediaStreamTrack track, MediaStream... streams);
+    void                        removeTrack (RTCRtpSender sender);
+    RTCRtpTransceiver           addTransceiver ((MediaStreamTrack or DOMString) trackOrKind, optional RTCRtpTransceiverInit init);
+                    attribute EventHandler ontrack;
 };
 
 dictionary RTCRtpTransceiverInit {
-    RTCRtpTransceiverDirection         direction = "sendrecv";
-    sequence<MediaStream>              streams;
-    sequence<RTCRtpEncodingParameters> sendEncodings;
+             RTCRtpTransceiverDirection         direction = "sendrecv";
+             sequence<MediaStream>              streams = [];
+             sequence<RTCRtpEncodingParameters> sendEncodings = [];
 };
 
 enum RTCRtpTransceiverDirection {
@@ -283,45 +274,42 @@
     "inactive"
 };
 
-interface RTCRtpSender {
-    readonly attribute MediaStreamTrack? track;
-    readonly attribute RTCDtlsTransport? transport;
-    readonly attribute RTCDtlsTransport? rtcpTransport;
-    // Feature at risk
-    static RTCRtpCapabilities getCapabilities(DOMString kind);
-    Promise<void>           setParameters(optional RTCRtpParameters parameters);
-    RTCRtpParameters        getParameters();
-    Promise<void>           replaceTrack(MediaStreamTrack withTrack);
-    Promise<RTCStatsReport> getStats();
+[Exposed=Window] interface RTCRtpSender {
+    readonly        attribute MediaStreamTrack? track;
+    readonly        attribute RTCDtlsTransport?  transport;
+    readonly        attribute RTCDtlsTransport? rtcpTransport;
+    static RTCRtpCapabilities getCapabilities (DOMString kind);
+    Promise<void>             setParameters (optional RTCRtpParameters parameters);
+    RTCRtpParameters          getParameters ();
+    Promise<void>             replaceTrack (MediaStreamTrack? withTrack);
+    Promise<RTCStatsReport>   getStats();
 };
 
 dictionary RTCRtpParameters {
-    DOMString                                 transactionId;
-    sequence<RTCRtpEncodingParameters>        encodings;
-    sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
-    RTCRtcpParameters                         rtcp;
-    sequence<RTCRtpCodecParameters>           codecs;
-    RTCDegradationPreference                  degradationPreference;
+             DOMString                                 transactionId;
+             sequence<RTCRtpEncodingParameters>        encodings;
+             sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
+             RTCRtcpParameters                         rtcp;
+             sequence<RTCRtpCodecParameters>           codecs;
+             RTCDegradationPreference                  degradationPreference;
 };
 
 dictionary RTCRtpEncodingParameters {
-    unsigned long       ssrc;
-    RTCRtpRtxParameters rtx;
-    RTCRtpFecParameters fec;
-    RTCDtxStatus        dtx;
-    boolean             active;
-    RTCPriorityType     priority;
-    unsigned long       ptime;
-    unsigned long       maxBitrate;
-    double              maxFramerate;
-    DOMString           rid;
-    double              scaleResolutionDownBy;
+             octet               codecPayloadType;
+             RTCDtxStatus        dtx;
+             boolean             active = true;
+             RTCPriorityType     priority = "low";
+             unsigned long       ptime;
+             unsigned long       maxBitrate;
+             double              maxFramerate;
+             DOMString           rid;
+             double              scaleResolutionDownBy;
 };
 
 enum RTCDtxStatus {
-    "disabled",
-    "enabled"
-};
+         "disabled",
+         "enabled"
+         };
 
 enum RTCDegradationPreference {
     "maintain-framerate",
@@ -329,93 +317,81 @@
     "balanced"
 };
 
-dictionary RTCRtpRtxParameters {
-    unsigned long ssrc;
-};
-
-dictionary RTCRtpFecParameters {
-    unsigned long ssrc;
-};
-
 dictionary RTCRtcpParameters {
-    DOMString cname;
-    boolean   reducedSize;
+             DOMString cname;
+             boolean   reducedSize;
 };
 
 dictionary RTCRtpHeaderExtensionParameters {
-    DOMString      uri;
-    unsigned short id;
-    boolean        encrypted;
+             DOMString      uri;
+             unsigned short id;
+             boolean        encrypted;
 };
 
 dictionary RTCRtpCodecParameters {
-    unsigned short payloadType;
-    DOMString      mimeType;
-    unsigned long  clockRate;
-    unsigned short channels;
-    DOMString      sdpFmtpLine;
+             octet          payloadType;
+             DOMString      mimeType;
+             unsigned long  clockRate;
+             unsigned short channels;
+             DOMString      sdpFmtpLine;
 };
 
 dictionary RTCRtpCapabilities {
-    sequence<RTCRtpCodecCapability>           codecs;
-    sequence<RTCRtpHeaderExtensionCapability> headerExtensions;
+             sequence<RTCRtpCodecCapability>           codecs;
+             sequence<RTCRtpHeaderExtensionCapability> headerExtensions;
 };
 
 dictionary RTCRtpCodecCapability {
-    DOMString      mimeType;
-    unsigned long  clockRate;
-    unsigned short channels;
-    DOMString      sdpFmtpLine;
+             DOMString mimeType;
+             unsigned long  clockRate;
+             unsigned short channels;
+             DOMString      sdpFmtpLine;
 };
 
 dictionary RTCRtpHeaderExtensionCapability {
-    DOMString uri;
+             DOMString uri;
 };
 
-interface RTCRtpReceiver {
-    readonly attribute MediaStreamTrack  track;
-    readonly attribute RTCDtlsTransport? transport;
-    readonly attribute RTCDtlsTransport? rtcpTransport;
-    // Feature at risk
-    static RTCRtpCapabilities             getCapabilities(DOMString kind);
-    RTCRtpParameters                      getParameters();
-    sequence<RTCRtpContributingSource>    getContributingSources();
-    sequence<RTCRtpSynchronizationSource> getSynchronizationSources();
-    Promise<RTCStatsReport>               getStats();
+[Exposed=Window] interface RTCRtpReceiver {
+    readonly        attribute MediaStreamTrack  track;
+    readonly        attribute RTCDtlsTransport? transport;
+    readonly        attribute RTCDtlsTransport? rtcpTransport;
+    static RTCRtpCapabilities          getCapabilities (DOMString kind);
+    RTCRtpParameters                   getParameters ();
+    sequence<RTCRtpContributingSource>    getContributingSources ();
+    sequence<RTCRtpSynchronizationSource> getSynchronizationSources ();
+    Promise<RTCStatsReport>   getStats();
 };
 
-interface RTCRtpContributingSource {
-    readonly attribute DOMHighResTimeStamp timestamp;
-    readonly attribute unsigned long       source;
-    readonly attribute byte?               audioLevel;
+dictionary RTCRtpContributingSource {
+    required DOMHighResTimeStamp timestamp;
+    required unsigned long       source;
+             double              audioLevel;
 };
 
-interface RTCRtpSynchronizationSource {
-    readonly attribute DOMHighResTimeStamp timestamp;
-    readonly attribute unsigned long       source;
-    readonly attribute byte                audioLevel;
-    readonly attribute boolean?            voiceActivityFlag;
+dictionary RTCRtpSynchronizationSource : RTCRtpContributingSource {
+    boolean voiceActivityFlag;
 };
 
-interface RTCRtpTransceiver {
-    readonly attribute DOMString?                  mid;
+[Exposed=Window] interface RTCRtpTransceiver {
+    readonly        attribute DOMString?                  mid;
     [SameObject]
-    readonly attribute RTCRtpSender                sender;
+    readonly        attribute RTCRtpSender                sender;
     [SameObject]
-    readonly attribute RTCRtpReceiver              receiver;
-    readonly attribute boolean                     stopped;
-    readonly attribute RTCRtpTransceiverDirection  direction;
-    readonly attribute RTCRtpTransceiverDirection? currentDirection;
-    void setDirection(RTCRtpTransceiverDirection direction);
-    void stop();
-    void setCodecPreferences(sequence<RTCRtpCodecCapability> codecs);
+    readonly        attribute RTCRtpReceiver              receiver;
+    readonly        attribute boolean                     stopped;
+                    attribute RTCRtpTransceiverDirection  direction;
+    readonly        attribute RTCRtpTransceiverDirection? currentDirection;
+    void stop ();
+    void setCodecPreferences (sequence<RTCRtpCodecCapability> codecs);
 };
 
-interface RTCDtlsTransport {
-    readonly attribute RTCIceTransport       transport;
-    readonly attribute RTCDtlsTransportState state;
-    sequence<ArrayBuffer> getRemoteCertificates();
-             attribute EventHandler          onstatechange;
+[Exposed=Window] interface RTCDtlsTransport : EventTarget {
+    readonly        attribute RTCIceTransport       transport;
+    readonly        attribute RTCDtlsTransportState state;
+    sequence<ArrayBuffer> getRemoteCertificates ();
+                    attribute EventHandler          onstatechange;
+                    attribute EventHandler          onerror;
 };
 
 enum RTCDtlsTransportState {
@@ -427,33 +403,33 @@
 };
 
 dictionary RTCDtlsFingerprint {
-    DOMString algorithm;
-    DOMString value;
+             DOMString algorithm;
+             DOMString value;
 };
 
-interface RTCIceTransport {
-    readonly attribute RTCIceRole           role;
-    readonly attribute RTCIceComponent      component;
-    readonly attribute RTCIceTransportState state;
-    readonly attribute RTCIceGathererState  gatheringState;
-    sequence<RTCIceCandidate> getLocalCandidates();
-    sequence<RTCIceCandidate> getRemoteCandidates();
-    RTCIceCandidatePair?      getSelectedCandidatePair();
-    RTCIceParameters?         getLocalParameters();
-    RTCIceParameters?         getRemoteParameters();
-             attribute EventHandler         onstatechange;
-             attribute EventHandler         ongatheringstatechange;
-             attribute EventHandler         onselectedcandidatepairchange;
+[Exposed=Window] interface RTCIceTransport : EventTarget {
+    readonly        attribute RTCIceRole           role;
+    readonly        attribute RTCIceComponent      component;
+    readonly        attribute RTCIceTransportState state;
+    readonly        attribute RTCIceGathererState gatheringState;
+    sequence<RTCIceCandidate> getLocalCandidates ();
+    sequence<RTCIceCandidate> getRemoteCandidates ();
+    RTCIceCandidatePair?      getSelectedCandidatePair ();
+    RTCIceParameters?         getLocalParameters ();
+    RTCIceParameters?         getRemoteParameters ();
+                    attribute EventHandler         onstatechange;
+                    attribute EventHandler         ongatheringstatechange;
+                    attribute EventHandler         onselectedcandidatepairchange;
 };
 
 dictionary RTCIceParameters {
-    DOMString usernameFragment;
-    DOMString password;
+             DOMString usernameFragment;
+             DOMString password;
 };
 
 dictionary RTCIceCandidatePair {
-    RTCIceCandidate local;
-    RTCIceCandidate remote;
+             RTCIceCandidate local;
+             RTCIceCandidate remote;
 };
 
 enum RTCIceGathererState {
@@ -467,8 +443,8 @@
     "checking",
     "connected",
     "completed",
-    "failed",
     "disconnected",
+    "failed",
     "closed"
 };
 
@@ -482,13 +458,13 @@
     "rtcp"
 };
 
-[Constructor(DOMString type, RTCTrackEventInit eventInitDict)]
+        [ Constructor (DOMString type, RTCTrackEventInit eventInitDict), Exposed=Window]
 interface RTCTrackEvent : Event {
-    readonly attribute RTCRtpReceiver           receiver;
-    readonly attribute MediaStreamTrack         track;
+    readonly        attribute RTCRtpReceiver           receiver;
+    readonly        attribute MediaStreamTrack         track;
     [SameObject]
-    readonly attribute FrozenArray<MediaStream> streams;
-    readonly attribute RTCRtpTransceiver        transceiver;
+    readonly        attribute FrozenArray<MediaStream> streams;
+    readonly        attribute RTCRtpTransceiver        transceiver;
 };
 
 dictionary RTCTrackEventInit : EventInit {
@@ -499,51 +475,59 @@
 };
 
 partial interface RTCPeerConnection {
-    readonly attribute RTCSctpTransport? sctp;
-    RTCDataChannel createDataChannel(USVString label,
-                                     optional RTCDataChannelInit dataChannelDict);
-             attribute EventHandler      ondatachannel;
+    readonly        attribute RTCSctpTransport? sctp;
+    RTCDataChannel createDataChannel (USVString label, optional RTCDataChannelInit dataChannelDict);
+                    attribute EventHandler      ondatachannel;
 };
 
-interface RTCSctpTransport {
-    readonly attribute RTCDtlsTransport transport;
-    readonly attribute unsigned long    maxMessageSize;
+[Exposed=Window] interface RTCSctpTransport {
+    readonly        attribute RTCDtlsTransport transport;
+    readonly        attribute RTCSctpTransportState state;
+    readonly        attribute unrestricted double maxMessageSize;
+                    attribute EventHandler     onstatechange;
 };
 
-interface RTCDataChannel : EventTarget {
-    readonly attribute USVString           label;
-    readonly attribute boolean             ordered;
-    readonly attribute unsigned short?     maxPacketLifeTime;
-    readonly attribute unsigned short?     maxRetransmits;
-    readonly attribute USVString           protocol;
-    readonly attribute boolean             negotiated;
-    readonly attribute unsigned short?     id;
-    readonly attribute RTCPriorityType     priority;
-    readonly attribute RTCDataChannelState readyState;
-    readonly attribute unsigned long       bufferedAmount;
-             attribute unsigned long       bufferedAmountLowThreshold;
-             attribute EventHandler        onopen;
-             attribute EventHandler        onbufferedamountlow;
-             attribute EventHandler        onerror;
-             attribute EventHandler        onclose;
-    void close();
-             attribute EventHandler        onmessage;
-             attribute DOMString           binaryType;
-    void send(USVString data);
-    void send(Blob data);
-    void send(ArrayBuffer data);
-    void send(ArrayBufferView data);
+enum RTCSctpTransportState {
+    "new",
+    "connecting",
+    "connected",
+    "closed"
+};
+
+[Exposed=Window] interface RTCDataChannel : EventTarget {
+    readonly        attribute USVString           label;
+    readonly        attribute boolean             ordered;
+    readonly        attribute unsigned short?     maxPacketLifeTime;
+    readonly        attribute unsigned short?     maxRetransmits;
+    readonly        attribute USVString           protocol;
+    readonly        attribute boolean             negotiated;
+    readonly        attribute unsigned short?     id;
+    readonly        attribute RTCPriorityType     priority;
+    readonly        attribute RTCDataChannelState readyState;
+    readonly        attribute unsigned long       bufferedAmount;
+                    attribute unsigned long       bufferedAmountLowThreshold;
+                    attribute EventHandler        onopen;
+                    attribute EventHandler        onbufferedamountlow;
+                    attribute EventHandler        onerror;
+                    attribute EventHandler        onclose;
+    void close ();
+                    attribute EventHandler        onmessage;
+                    attribute DOMString           binaryType;
+    void send (USVString data);
+    void send (Blob data);
+    void send (ArrayBuffer data);
+    void send (ArrayBufferView data);
 };
 
 dictionary RTCDataChannelInit {
-    boolean         ordered = true;
-    unsigned short  maxPacketLifeTime;
-    unsigned short  maxRetransmits;
-    USVString       protocol = "";
-    boolean         negotiated = false;
-    [EnforceRange]
-    unsigned short  id;
-    RTCPriorityType priority = "low";
+             boolean         ordered = true;
+             unsigned short  maxPacketLifeTime;
+             unsigned short  maxRetransmits;
+             USVString       protocol = "";
+             boolean         negotiated = false;
+             [EnforceRange]
+             unsigned short  id;
+             RTCPriorityType priority = "low";
 };
 
 enum RTCDataChannelState {
@@ -553,59 +537,64 @@
     "closed"
 };
 
-[Constructor(DOMString type, RTCDataChannelEventInit eventInitDict)]
+        [ Constructor (DOMString type, RTCDataChannelEventInit eventInitDict), Exposed=Window]
 interface RTCDataChannelEvent : Event {
-    readonly attribute RTCDataChannel channel;
+    readonly        attribute RTCDataChannel channel;
 };
 
 dictionary RTCDataChannelEventInit : EventInit {
-    required RTCDataChannel channel;
+             required RTCDataChannel channel;
 };
 
 partial interface RTCRtpSender {
-    readonly attribute RTCDTMFSender? dtmf;
+    readonly        attribute RTCDTMFSender? dtmf;
 };
 
-interface RTCDTMFSender : EventTarget {
-    void insertDTMF(DOMString tones,
-                    optional unsigned long duration = 100,
-                    optional unsigned long interToneGap = 70);
-             attribute EventHandler ontonechange;
-    readonly attribute DOMString    toneBuffer;
+[Exposed=Window] interface RTCDTMFSender : EventTarget {
+    void insertDTMF (DOMString tones, optional unsigned long duration = 100, optional unsigned long interToneGap = 70);
+                    attribute EventHandler ontonechange;
+    readonly        attribute boolean      canInsertDTMF;
+    readonly        attribute DOMString    toneBuffer;
 };
 
-[Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict)]
+        [ Constructor (DOMString type, RTCDTMFToneChangeEventInit eventInitDict), Exposed=Window]
 interface RTCDTMFToneChangeEvent : Event {
-    readonly attribute DOMString tone;
+    readonly        attribute DOMString tone;
 };
 
 dictionary RTCDTMFToneChangeEventInit : EventInit {
-    required DOMString tone;
+             required DOMString tone;
 };
 
 partial interface RTCPeerConnection {
-    Promise<RTCStatsReport> getStats(optional MediaStreamTrack? selector = null);
-};
+    Promise<RTCStatsReport> getStats (optional MediaStreamTrack? selector = null);
+    attribute EventHandler onstatsended;
+          };
 
-interface RTCStatsReport {
+[Exposed=Window] interface RTCStatsReport {
     readonly maplike<DOMString, object>;
 };
 
 dictionary RTCStats {
-    DOMHighResTimeStamp timestamp;
-    RTCStatsType        type;
-    DOMString           id;
+             required DOMHighResTimeStamp timestamp;
+             required RTCStatsType        type;
+             required DOMString           id;
 };
 
-[Global,
- Exposed=RTCIdentityProviderGlobalScope]
+[ Constructor (DOMString type, RTCStatsEventInit
+          eventInitDict), Exposed=Window]
+          interface RTCStatsEvent : Event {
+            readonly attribute RTCStatsReport report;
+          };
+
+[Global, Exposed=RTCIdentityProviderGlobalScope]
 interface RTCIdentityProviderGlobalScope : WorkerGlobalScope {
-    readonly attribute RTCIdentityProviderRegistrar rtcIdentityProvider;
+    readonly        attribute RTCIdentityProviderRegistrar rtcIdentityProvider;
 };
 
 [Exposed=RTCIdentityProviderGlobalScope]
 interface RTCIdentityProviderRegistrar {
-    void register(RTCIdentityProvider idp);
+    void register (RTCIdentityProvider idp);
 };
 
 dictionary RTCIdentityProvider {
@@ -613,12 +602,11 @@
     required ValidateAssertionCallback validateAssertion;
 };
 
-callback GenerateAssertionCallback = Promise<RTCIdentityAssertionResult> (DOMString contents,
-                                                                          DOMString origin,
-                                                                          RTCIdentityProviderOptions options);
+          callback GenerateAssertionCallback = Promise<RTCIdentityAssertionResult>
+          (DOMString contents, DOMString origin, RTCIdentityProviderOptions options);
 
-callback ValidateAssertionCallback = Promise<RTCIdentityValidationResult> (DOMString assertion,
-                                                                           DOMString origin);
+          callback ValidateAssertionCallback = Promise<RTCIdentityValidationResult>
+          (DOMString assertion, DOMString origin);
 
 dictionary RTCIdentityAssertionResult {
     required RTCIdentityProviderDetails idp;
@@ -636,12 +624,11 @@
 };
 
 partial interface RTCPeerConnection {
-    void               setIdentityProvider(DOMString provider,
-                                           optional RTCIdentityProviderOptions options);
-    Promise<DOMString> getIdentityAssertion();
-    readonly attribute Promise<RTCIdentityAssertion> peerIdentity;
-    readonly attribute DOMString?                    idpLoginUrl;
-    readonly attribute DOMString?                    idpErrorInfo;
+    void               setIdentityProvider (DOMString provider, optional RTCIdentityProviderOptions options);
+    Promise<DOMString> getIdentityAssertion ();
+    readonly        attribute Promise<RTCIdentityAssertion> peerIdentity;
+    readonly        attribute DOMString?                    idpLoginUrl;
+    readonly        attribute DOMString?                    idpErrorInfo;
 };
 
 dictionary RTCIdentityProviderOptions {
@@ -650,41 +637,45 @@
     DOMString peerIdentity;
 };
 
-[Constructor(DOMString idp, DOMString name)]
+[Constructor(DOMString idp, DOMString name), Exposed=Window]
 interface RTCIdentityAssertion {
     attribute DOMString idp;
     attribute DOMString name;
 };
 
 partial dictionary MediaStreamConstraints {
-    DOMString peerIdentity;
+             DOMString peerIdentity;
 };
 
 partial interface MediaStreamTrack {
-    readonly attribute boolean      isolated;
-             attribute EventHandler onisolationchange;
+    readonly        attribute boolean      isolated;
+                    attribute EventHandler onisolationchange;
 };
 
 enum RTCErrorDetailType {
-    "data-channel-failure",
-    "idp-bad-script-failure",
-    "idp-execution-failure",
-    "idp-load-failure",
-    "idp-need-login",
-    "idp-timeout",
-    "idp-tls-failure",
-    "idp-token-expired",
-    "idp-token-invalid",
-    "sctp-failure",
-    "sdp-syntax-error"
-};
+              "data-channel-failure",
+              "dtls-failure",
+              "fingerprint-failure",
+              "idp-bad-script-failure",
+              "idp-execution-failure",
+              "idp-load-failure",
+              "idp-need-login",
+              "idp-timeout",
+              "idp-tls-failure",
+              "idp-token-expired",
+              "idp-token-invalid",
+              "sctp-failure",
+              "sdp-syntax-error",
+              "hardware-encoder-not-available",
+              "hardware-encoder-error"
+          };
 
 [Exposed=Window,
- Constructor(DOMString type, RTCErrorEventInit eventInitDict)]
+ Constructor (DOMString type, RTCErrorEventInit eventInitDict)]
 interface RTCErrorEvent : Event {
-    readonly attribute RTCError? error;
+    readonly        attribute RTCError? error;
 };
 
 dictionary RTCErrorEventInit : EventInit {
-    RTCError? error = null;
+             RTCError? error = null;
 };
diff --git a/interfaces/webusb.idl b/interfaces/webusb.idl
index bad8383..03e03b0 100644
--- a/interfaces/webusb.idl
+++ b/interfaces/webusb.idl
@@ -14,27 +14,37 @@
   required sequence<USBDeviceFilter> filters;
 };
 
+[Exposed=(DedicatedWorker, SharedWorker, Window), SecureContext]
 interface USB : EventTarget {
   attribute EventHandler onconnect;
   attribute EventHandler ondisconnect;
   Promise<sequence<USBDevice>> getDevices();
-  Promise<USBDevice> requestDevice(USBDeviceRequestOptions options);
+  [Exposed=Window] Promise<USBDevice> requestDevice(USBDeviceRequestOptions options);
 };
 
-[SecureContext]
+[Exposed=Window, SecureContext]
 partial interface Navigator {
   [SameObject] readonly attribute USB usb;
 };
 
+[Exposed=(DedicatedWorker, SharedWorker), SecureContext]
+partial interface WorkerNavigator {
+  [SameObject] readonly attribute USB usb;
+};
+
 dictionary USBConnectionEventInit : EventInit {
     required USBDevice device;
 };
 
-[Constructor(DOMString type, USBConnectionEventInit eventInitDict)]
+[
+  Constructor(DOMString type, USBConnectionEventInit eventInitDict),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBConnectionEvent : Event {
   [SameObject] readonly attribute USBDevice device;
 };
 
+[Exposed=(DedicatedWorker, SharedWorker, Window)]
 interface USBDevice {
   readonly attribute octet usbVersionMajor;
   readonly attribute octet usbVersionMinor;
@@ -96,49 +106,73 @@
   required unsigned short index;
 };
 
-[Constructor(USBTransferStatus status, optional DataView? data)]
+[
+  Constructor(USBTransferStatus status, optional DataView? data),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBInTransferResult {
   readonly attribute DataView? data;
   readonly attribute USBTransferStatus status;
 };
 
-[Constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0)]
+[
+  Constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBOutTransferResult {
   readonly attribute unsigned long bytesWritten;
   readonly attribute USBTransferStatus status;
 };
 
-[Constructor(USBTransferStatus status, optional DataView? data)]
+[
+  Constructor(USBTransferStatus status, optional DataView? data),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBIsochronousInTransferPacket {
   readonly attribute DataView? data;
   readonly attribute USBTransferStatus status;
 };
 
-[Constructor(sequence<USBIsochronousInTransferPacket> packets, optional DataView? data)]
+[
+  Constructor(sequence<USBIsochronousInTransferPacket> packets, optional DataView? data),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBIsochronousInTransferResult {
   readonly attribute DataView? data;
   readonly attribute FrozenArray<USBIsochronousInTransferPacket> packets;
 };
 
-[Constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0)]
+[
+  Constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBIsochronousOutTransferPacket {
   readonly attribute unsigned long bytesWritten;
   readonly attribute USBTransferStatus status;
 };
 
-[Constructor(sequence<USBIsochronousOutTransferPacket> packets)]
+[
+  Constructor(sequence<USBIsochronousOutTransferPacket> packets),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBIsochronousOutTransferResult {
   readonly attribute FrozenArray<USBIsochronousOutTransferPacket> packets;
 };
 
-[Constructor(USBDevice device, octet configurationValue)]
+[
+  Constructor(USBDevice device, octet configurationValue),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBConfiguration {
   readonly attribute octet configurationValue;
   readonly attribute DOMString? configurationName;
   readonly attribute FrozenArray<USBInterface> interfaces;
 };
 
-[Constructor(USBConfiguration configuration, octet interfaceNumber)]
+[
+  Constructor(USBConfiguration configuration, octet interfaceNumber),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBInterface {
   readonly attribute octet interfaceNumber;
   readonly attribute USBAlternateInterface alternate;
@@ -146,7 +180,10 @@
   readonly attribute boolean claimed;
 };
 
-[Constructor(USBInterface deviceInterface, octet alternateSetting)]
+[
+  Constructor(USBInterface deviceInterface, octet alternateSetting),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBAlternateInterface {
   readonly attribute octet alternateSetting;
   readonly attribute octet interfaceClass;
@@ -167,7 +204,10 @@
   "isochronous"
 };
 
-[Constructor(USBAlternateInterface alternate, octet endpointNumber, USBDirection direction)]
+[
+  Constructor(USBAlternateInterface alternate, octet endpointNumber, USBDirection direction),
+  Exposed=(DedicatedWorker, SharedWorker, Window)
+]
 interface USBEndpoint {
   readonly attribute octet endpointNumber;
   readonly attribute USBDirection direction;
diff --git a/interfaces/webvtt.idl b/interfaces/webvtt.idl
new file mode 100644
index 0000000..d4603d1
--- /dev/null
+++ b/interfaces/webvtt.idl
@@ -0,0 +1,39 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the WebVTT spec.
+// See https://w3c.github.io/webvtt/
+
+enum AutoKeyword { "auto" };
+typedef (double or AutoKeyword) LineAndPositionSetting;
+enum DirectionSetting { "" /* horizontal */, "rl", "lr" };
+enum LineAlignSetting { "start", "center", "end" };
+enum PositionAlignSetting { "line-left", "center", "line-right", "auto" };
+enum AlignSetting { "start", "center", "end", "left", "right" };
+[Exposed=Window,
+ Constructor(double startTime, double endTime, DOMString text)]
+interface VTTCue : TextTrackCue {
+  attribute VTTRegion? region;
+  attribute DirectionSetting vertical;
+  attribute boolean snapToLines;
+  attribute LineAndPositionSetting line;
+  attribute LineAlignSetting lineAlign;
+  attribute LineAndPositionSetting position;
+  attribute PositionAlignSetting positionAlign;
+  attribute double size;
+  attribute AlignSetting align;
+  attribute DOMString text;
+  DocumentFragment getCueAsHTML();
+};
+
+enum ScrollSetting { "" /* none */, "up" };
+[Exposed=Window,
+ Constructor]
+interface VTTRegion {
+  attribute DOMString id;
+  attribute double width;
+  attribute unsigned long lines;
+  attribute double regionAnchorX;
+  attribute double regionAnchorY;
+  attribute double viewportAnchorX;
+  attribute double viewportAnchorY;
+  attribute ScrollSetting scroll;
+};
diff --git a/interfaces/webxr.idl b/interfaces/webxr.idl
new file mode 100644
index 0000000..b409009
--- /dev/null
+++ b/interfaces/webxr.idl
@@ -0,0 +1,176 @@
+[SecureContext, Exposed=Window] interface XR : EventTarget {
+  // Methods
+  Promise<XRDevice?> requestDevice();
+
+  // Events
+  attribute EventHandler ondevicechange;
+};
+
+[SecureContext]
+partial interface Navigator {
+  [SameObject] readonly attribute XR xr;
+};
+
+[SecureContext, Exposed=Window] interface XRDevice : EventTarget {
+  // Methods
+  Promise<void> supportsSession(optional XRSessionCreationOptions options);
+  Promise<XRSession> requestSession(optional XRSessionCreationOptions options);
+};
+
+dictionary XRSessionCreationOptions {
+  boolean exclusive = false;
+  XRPresentationContext outputContext;
+};
+
+[SecureContext, Exposed=Window] interface XRSession : EventTarget {
+  // Attributes
+  readonly attribute XRDevice device;
+  readonly attribute boolean exclusive;
+  readonly attribute XRPresentationContext outputContext;
+
+  attribute double depthNear;
+  attribute double depthFar;
+  attribute XRLayer baseLayer;
+
+  // Methods
+  Promise<XRFrameOfReference> requestFrameOfReference(XRFrameOfReferenceType type, optional XRFrameOfReferenceOptions options);
+
+  long requestAnimationFrame(XRFrameRequestCallback callback);
+  void cancelAnimationFrame(long handle);
+
+  Promise<void> end();
+
+  // Events
+  attribute EventHandler onblur;
+  attribute EventHandler onfocus;
+  attribute EventHandler onresetpose;
+  attribute EventHandler onend;
+};
+
+callback XRFrameRequestCallback = void (DOMHighResTimeStamp time, XRPresentationFrame frame);
+
+[SecureContext, Exposed=Window] interface XRPresentationFrame {
+  readonly attribute FrozenArray<XRView> views;
+
+  XRDevicePose? getDevicePose(XRCoordinateSystem coordinateSystem);
+};
+
+[SecureContext, Exposed=Window] interface XRCoordinateSystem : EventTarget {
+  Float32Array? getTransformTo(XRCoordinateSystem other);
+};
+
+enum XRFrameOfReferenceType {
+  "headModel",
+  "eyeLevel",
+  "stage",
+};
+
+dictionary XRFrameOfReferenceOptions {
+  boolean disableStageEmulation = false;
+  double stageEmulationHeight = 0.0;
+};
+
+[SecureContext, Exposed=Window] interface XRFrameOfReference : XRCoordinateSystem {
+  readonly attribute XRStageBounds? bounds;
+  readonly attribute double emulatedHeight;
+
+  attribute EventHandler onboundschange;
+};
+
+[SecureContext, Exposed=Window] interface XRStageBounds {
+  readonly attribute FrozenArray<XRStageBoundsPoint> geometry;
+};
+
+[SecureContext, Exposed=Window] interface XRStageBoundsPoint {
+  readonly attribute double x;
+  readonly attribute double z;
+};
+
+enum XREye {
+  "left",
+  "right"
+};
+
+[SecureContext, Exposed=Window] interface XRView {
+  readonly attribute XREye eye;
+  readonly attribute Float32Array projectionMatrix;
+};
+
+[SecureContext, Exposed=Window] interface XRViewport {
+  readonly attribute long x;
+  readonly attribute long y;
+  readonly attribute long width;
+  readonly attribute long height;
+};
+
+[SecureContext, Exposed=Window] interface XRDevicePose {
+  readonly attribute Float32Array poseModelMatrix;
+
+  Float32Array getViewMatrix(XRView view);
+};
+
+[SecureContext, Exposed=Window] interface XRLayer {};
+
+typedef (WebGLRenderingContext or
+         WebGL2RenderingContext) XRWebGLRenderingContext;
+
+dictionary XRWebGLLayerInit {
+  boolean antialias = true;
+  boolean depth = false;
+  boolean stencil = false;
+  boolean alpha = true;
+  boolean multiview = false;
+  double framebufferScaleFactor;
+};
+
+[SecureContext, Exposed=Window, Constructor(XRSession session,
+             XRWebGLRenderingContext context,
+             optional XRWebGLLayerInit layerInit)]
+interface XRWebGLLayer : XRLayer {
+  // Attributes
+  readonly attribute XRWebGLRenderingContext context;
+
+  readonly attribute boolean antialias;
+  readonly attribute boolean depth;
+  readonly attribute boolean stencil;
+  readonly attribute boolean alpha;
+  readonly attribute boolean multiview;
+
+  readonly attribute WebGLFramebuffer framebuffer;
+  readonly attribute unsigned long framebufferWidth;
+  readonly attribute unsigned long framebufferHeight;
+
+  // Methods
+  XRViewport? getViewport(XRView view);
+  void requestViewportScaling(double viewportScaleFactor);
+};
+
+partial dictionary WebGLContextAttributes {
+    XRDevice compatibleXRDevice = null;
+};
+
+partial interface mixin WebGLRenderingContextBase {
+    Promise<void> setCompatibleXRDevice(XRDevice device);
+};
+
+[SecureContext, Exposed=Window] interface XRPresentationContext {
+  readonly attribute HTMLCanvasElement canvas;
+};
+
+[SecureContext, Exposed=Window, Constructor(DOMString type, XRSessionEventInit eventInitDict)]
+interface XRSessionEvent : Event {
+  readonly attribute XRSession session;
+};
+
+dictionary XRSessionEventInit : EventInit {
+  required XRSession session;
+};
+
+[SecureContext, Exposed=Window, Constructor(DOMString type, XRCoordinateSystemEventInit eventInitDict)]
+interface XRCoordinateSystemEvent : Event {
+  readonly attribute XRCoordinateSystem coordinateSystem;
+};
+
+dictionary XRCoordinateSystemEventInit : EventInit {
+  required XRCoordinateSystem coordinateSystem;
+};
diff --git a/interfaces/xhr.idl b/interfaces/xhr.idl
new file mode 100644
index 0000000..44774da
--- /dev/null
+++ b/interfaces/xhr.idl
@@ -0,0 +1,91 @@
+[Exposed=(Window,DedicatedWorker,SharedWorker)]
+interface XMLHttpRequestEventTarget : EventTarget {
+  // event handlers
+  attribute EventHandler onloadstart;
+  attribute EventHandler onprogress;
+  attribute EventHandler onabort;
+  attribute EventHandler onerror;
+  attribute EventHandler onload;
+  attribute EventHandler ontimeout;
+  attribute EventHandler onloadend;
+};
+
+[Exposed=(Window,DedicatedWorker,SharedWorker)]
+interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {
+};
+
+enum XMLHttpRequestResponseType {
+  "",
+  "arraybuffer",
+  "blob",
+  "document",
+  "json",
+  "text"
+};
+
+[Constructor,
+ Exposed=(Window,DedicatedWorker,SharedWorker)]
+interface XMLHttpRequest : XMLHttpRequestEventTarget {
+  // event handler
+  attribute EventHandler onreadystatechange;
+
+  // states
+  const unsigned short UNSENT = 0;
+  const unsigned short OPENED = 1;
+  const unsigned short HEADERS_RECEIVED = 2;
+  const unsigned short LOADING = 3;
+  const unsigned short DONE = 4;
+  readonly attribute unsigned short readyState;
+
+  // request
+  void open(ByteString method, USVString url);
+  void open(ByteString method, USVString url, boolean async, optional USVString? username = null, optional USVString? password = null);
+  void setRequestHeader(ByteString name, ByteString value);
+           attribute unsigned long timeout;
+           attribute boolean withCredentials;
+  [SameObject] readonly attribute XMLHttpRequestUpload upload;
+  void send(optional (Document or BodyInit)? body = null);
+  void abort();
+
+  // response
+  readonly attribute USVString responseURL;
+  readonly attribute unsigned short status;
+  readonly attribute ByteString statusText;
+  ByteString? getResponseHeader(ByteString name);
+  ByteString getAllResponseHeaders();
+  void overrideMimeType(DOMString mime);
+           attribute XMLHttpRequestResponseType responseType;
+  readonly attribute any response;
+  readonly attribute USVString responseText;
+  [Exposed=Window] readonly attribute Document? responseXML;
+};
+
+typedef (File or USVString) FormDataEntryValue;
+
+[Constructor(optional HTMLFormElement form),
+ Exposed=(Window,Worker)]
+interface FormData {
+  void append(USVString name, USVString value);
+  void append(USVString name, Blob blobValue, optional USVString filename);
+  void delete(USVString name);
+  FormDataEntryValue? get(USVString name);
+  sequence<FormDataEntryValue> getAll(USVString name);
+  boolean has(USVString name);
+  void set(USVString name, USVString value);
+  void set(USVString name, Blob blobValue, optional USVString filename);
+  iterable<USVString, FormDataEntryValue>;
+};
+
+[Constructor(DOMString type, optional ProgressEventInit eventInitDict),
+ Exposed=(Window,DedicatedWorker,SharedWorker)]
+interface ProgressEvent : Event {
+  readonly attribute boolean lengthComputable;
+  readonly attribute unsigned long long loaded;
+  readonly attribute unsigned long long total;
+};
+
+dictionary ProgressEventInit : EventInit {
+  boolean lengthComputable = false;
+  unsigned long long loaded = 0;
+  unsigned long long total = 0;
+};
diff --git a/intersection-observer/OWNERS b/intersection-observer/OWNERS
new file mode 100644
index 0000000..50699c8
--- /dev/null
+++ b/intersection-observer/OWNERS
@@ -0,0 +1,2 @@
+@scottlow
+@szager-chromium
diff --git a/intersection-observer/isIntersecting-change-events.html b/intersection-observer/isIntersecting-change-events.html
new file mode 100644
index 0000000..f9362c3
--- /dev/null
+++ b/intersection-observer/isIntersecting-change-events.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/intersection-observer-test-utils.js"></script>
+
+<style>
+pre, #log {
+  position: absolute;
+  top: 0;
+  left: 200px;
+}
+#root {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 150px;
+  height: 200px;
+  overflow-y: scroll;
+}
+#target1, #target2, #target3, #target4 {
+  width: 100px;
+  height: 100px;
+}
+#target1 {
+  background-color: green;
+}
+#target2 {
+  background-color: red;
+}
+#target3 {
+  background-color: blue;
+}
+#target4 {
+  background-color: yellow;
+}
+</style>
+
+<div id="root">
+  <div id="target1"></div>
+  <div id="target2"></div>
+  <div id="target3"></div>
+</div>
+
+<script>
+var entries = [];
+var observer;
+
+runTestCycle(function() {
+  var root = document.getElementById('root');
+  var target1 = document.getElementById('target1');
+  var target2 = document.getElementById('target2');
+  var target3 = document.getElementById('target3');
+  assert_true(!!root, "root element exists.");
+  assert_true(!!target1, "target1 element exists.");
+  assert_true(!!target2, "target2 element exists.");
+  assert_true(!!target3, "target3 element exists.");
+  observer = new IntersectionObserver(function(changes) {
+    entries = entries.concat(changes);
+  }, { root: root });
+  observer.observe(target1);
+  observer.observe(target2);
+  observer.observe(target3);
+  entries = entries.concat(observer.takeRecords());
+  assert_equals(entries.length, 0, "No initial notifications.");
+  runTestCycle(step0, "Rects in initial notifications should report initial positions.");
+}, "isIntersecting changes should trigger notifications.");
+
+function step0() {
+  assert_equals(entries.length, 3, "Has 3 initial notifications.");
+  checkRect(entries[0].boundingClientRect, [0, 100, 0, 100], "Check 1st entry rect");
+  assert_equals(entries[0].target.id, 'target1', "Check 1st entry target id.");
+  checkIsIntersecting(entries, 0, true);
+  checkRect(entries[1].boundingClientRect, [0, 100, 100, 200], "Check 2nd entry rect");
+  assert_equals(entries[1].target.id, 'target2', "Check 2nd entry target id.");
+  checkIsIntersecting(entries, 1, true);
+  checkRect(entries[2].boundingClientRect, [0, 100, 200, 300], "Check 3rd entry rect");
+  assert_equals(entries[2].target.id, 'target3', "Check 3rd entry target id.");
+  checkIsIntersecting(entries, 2, true);
+  runTestCycle(step1, "Set scrollTop=100 and check for no new notifications.");
+  root.scrollTop = 100;
+}
+
+function step1() {
+  assert_equals(entries.length, 3, "Has 3 total notifications because isIntersecting did not change.");
+  runTestCycle(step2, "Add 4th target.");
+  root.scrollTop = 0;
+  var target4 = document.createElement('div');
+  target4.setAttribute('id', 'target4');
+  root.appendChild(target4);
+  observer.observe(target4);
+}
+
+function step2() {
+  assert_equals(entries.length, 4, "Has 3 total notifications because 4th element was added.");
+  checkRect(entries[3].boundingClientRect, [0, 100, 300, 400], "Check 4th entry rect");
+  assert_equals(entries[3].target.id, 'target4', "Check 4th entry target id.");
+  checkIsIntersecting(entries, 3, false);
+  assert_equals(entries[3].intersectionRatio, 0, 'target4 initially has intersectionRatio of 0.');
+  runTestCycle(step3, "Set scrollTop=100 and check for one new notification.");
+  root.scrollTop = 100;
+}
+
+function step3() {
+  assert_equals(entries.length, 5, "Has 5 total notifications.");
+  checkRect(entries[4].boundingClientRect, [0, 100, 200, 300], "Check 5th entry rect");
+  assert_equals(entries[4].target.id, 'target4', "Check 5th entry target id.");
+  checkIsIntersecting(entries, 4, true);
+  assert_equals(entries[4].intersectionRatio, 0, 'target4 still has intersectionRatio of 0.');
+  root.scrollTop = 0; // reset to make it easier to refresh and run the test
+}
+
+</script>
diff --git a/intersection-observer/resources/intersection-observer-test-utils.js b/intersection-observer/resources/intersection-observer-test-utils.js
index 48ccbb1..5ad8119 100644
--- a/intersection-observer/resources/intersection-observer-test-utils.js
+++ b/intersection-observer/resources/intersection-observer-test-utils.js
@@ -120,3 +120,8 @@
       checkJsonEntry(actual[i], expected[i]);
   }, description);
 }
+
+function checkIsIntersecting(entries, i, expected) {
+  assert_equals(entries[i].isIntersecting, expected,
+    'entries[' + i + '].target.isIntersecting equals ' + expected);
+}
\ No newline at end of file
diff --git a/intersection-observer/shadow-content.html b/intersection-observer/shadow-content.html
index a0a6242..d049c70 100644
--- a/intersection-observer/shadow-content.html
+++ b/intersection-observer/shadow-content.html
@@ -23,7 +23,7 @@
 runTestCycle(function() {
   var shadowHost = document.getElementById("host");
   assert_true(!!shadowHost, "Host exists");
-  var shadowRoot = shadowHost.createShadowRoot();
+  var shadowRoot = shadowHost.attachShadow({ mode: "open" });
   assert_true(!!shadowRoot, "Shadow root exists");
   shadowRoot.innerHTML = "<div id='target' style='width: 100px; height: 100px; background-color: green;'></div>";
   target = shadowRoot.getElementById("target");
diff --git a/js/OWNERS b/js/OWNERS
new file mode 100644
index 0000000..fd31fb2
--- /dev/null
+++ b/js/OWNERS
@@ -0,0 +1 @@
+@Ms2ger
diff --git a/keyboard-lock/OWNERS b/keyboard-lock/OWNERS
new file mode 100644
index 0000000..9e83dd9
--- /dev/null
+++ b/keyboard-lock/OWNERS
@@ -0,0 +1,2 @@
+@garykac
+@joedow-42
diff --git a/keyboard-lock/idlharness.https.html b/keyboard-lock/idlharness.https.html
index 6fabb41..1c3ade0 100644
--- a/keyboard-lock/idlharness.https.html
+++ b/keyboard-lock/idlharness.https.html
@@ -1,41 +1,34 @@
 <!doctype html>
 <html>
 <head>
-<title>Keyboard Lock IDL tests</title>
-<link rel="help" href="https://github.com/w3c/keyboard-lock"/>
+<title>Keyboard IDL tests</title>
+<link rel="help" href="https://w3c.github.io/keyboard-lock/"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
-</head>
-<body>
-<pre id="untested_idl" style="display: none">
-interface Navigator {
-};
-</pre>
-<!--
-  The reason of the failure of requestKeyboardLock test looks like a code defect in
-  idlharness.js. media-capabilities/idlharness.html is also impacted by this
-  issue. See https://codereview.chromium.org/2805763004/#ps620001, which
-  includes a potential fix.
-  TODO(zijiehe): Submit the fix.
--->
-<pre id="idl" style="display: none">
-partial interface Navigator {
-  [SecureContext] Promise<void> requestKeyboardLock(optional sequence<DOMString> keyCodes = []);
-  [SecureContext] void cancelKeyboardLock();
-};
-</pre>
 <script>
-var idl_array = new IdlArray();
-idl_array.add_untested_idls(
-    document.getElementById("untested_idl").textContent);
-idl_array.add_idls(document.getElementById("idl").textContent);
-idl_array.add_objects({
-  Navigator: ["navigator"]
-});
-idl_array.test();
+'use strict';
+
+function doTest(idls) {
+  var idl_array = new IdlArray();
+  idl_array.add_untested_idls('interface Navigator {};');
+  for (let idl of idls) {
+    idl_array.add_idls(idl);
+  }
+  idl_array.add_objects({
+    Navigator: ['navigator'],
+    Keyboard: ['navigator.keyboard'],
+  });
+  idl_array.test();
+};
+
+function fetchText(url) {
+  return fetch(url).then((response) => response.text());
+}
+
+promise_test(() => {
+  return Promise.all(["/interfaces/keyboard-lock.idl"].map(fetchText))
+    .then(doTest);
+}, "Test driver");
 </script>
-<div id="log"></div>
-</body>
-</html>
diff --git a/keyboard-lock/navigator-cancelKeyboardLock.https.html b/keyboard-lock/navigator-cancelKeyboardLock.https.html
deleted file mode 100644
index 10fd50d..0000000
--- a/keyboard-lock/navigator-cancelKeyboardLock.https.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-test(() => {
-  assert_equals(navigator.cancelKeyboardLock(),
-                undefined);
-}, 'Keyboard Lock cancelKeyboardLock');
-
-</script>
diff --git a/keyboard-lock/navigator-keyboard-lock-blocked-from-cross-origin-iframe.https.html b/keyboard-lock/navigator-keyboard-lock-blocked-from-cross-origin-iframe.https.html
new file mode 100644
index 0000000..ef79c0c
--- /dev/null
+++ b/keyboard-lock/navigator-keyboard-lock-blocked-from-cross-origin-iframe.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cors/support.js?pipe=sub"></script>
+<body>
+<script>
+'use strict';
+
+promise_test(() => {
+  let iframe = document.createElement('iframe');
+  iframe.src = CROSSDOMAIN  + 'resources/iframe-lock-helper.html';
+  iframe.onload = () => {
+    iframe.contentWindow.postMessage('Ready', '*');
+  }
+
+  document.body.appendChild(iframe);
+
+  return new Promise((resolve,reject) => {
+    window.onmessage = message => {
+      if (message.data == 'Success') {
+        resolve();
+      } else if (message.data == 'Failure') {
+        reject();
+      }
+    }
+  });
+}, '[Keyboard Lock] lock method call blocked from within cross-origin iframe');
+
+</script>
diff --git a/keyboard-lock/navigator-keyboard-lock-blocked-from-iframe.https.html b/keyboard-lock/navigator-keyboard-lock-blocked-from-iframe.https.html
new file mode 100644
index 0000000..a01f6a2
--- /dev/null
+++ b/keyboard-lock/navigator-keyboard-lock-blocked-from-iframe.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+'use strict';
+
+promise_test(() => {
+  let iframe = document.createElement('iframe');
+  iframe.src = 'resources/iframe-lock-helper.html';
+  iframe.onload = () => {
+    iframe.contentWindow.postMessage('Ready', '*');
+  }
+
+  document.body.appendChild(iframe);
+
+  return new Promise((resolve,reject) => {
+    window.onmessage = message => {
+      if (message.data == 'Success') {
+        resolve();
+      } else if (message.data == 'Failure') {
+        reject();
+      }
+    }
+  });
+}, '[Keyboard Lock] navigator.keyboard.lock blocked from within iframe');
+
+</script>
diff --git a/keyboard-lock/navigator-keyboard-lock-two-parallel-requests.https.html b/keyboard-lock/navigator-keyboard-lock-two-parallel-requests.https.html
new file mode 100644
index 0000000..5f493e9
--- /dev/null
+++ b/keyboard-lock/navigator-keyboard-lock-two-parallel-requests.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+promise_test((t) => {
+  const p1 = navigator.keyboard.lock(["KeyA", "KeyB"]);
+  const p2 = navigator.keyboard.lock(["KeyC", "KeyD"]);
+  return Promise.all([promise_rejects(t, "AbortError", p1), p2]);
+}, '[Keyboard Lock] keyboard.lock twice in parallel');
+
+</script>
diff --git a/keyboard-lock/navigator-keyboard-lock-two-sequential-requests.https.html b/keyboard-lock/navigator-keyboard-lock-two-sequential-requests.https.html
new file mode 100644
index 0000000..520da36
--- /dev/null
+++ b/keyboard-lock/navigator-keyboard-lock-two-sequential-requests.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+promise_test(() => {
+  return navigator.keyboard.lock(["KeyA", "KeyB"])
+      .then(() => {
+        return navigator.keyboard.lock(["KeyC", "KeyD"]);
+      });
+}, '[Keyboard Lock] keyboard.lock called twice sequentially');
+
+</script>
diff --git a/keyboard-lock/navigator-keyboard-lock.https.html b/keyboard-lock/navigator-keyboard-lock.https.html
new file mode 100644
index 0000000..7f8724c
--- /dev/null
+++ b/keyboard-lock/navigator-keyboard-lock.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+promise_test(() => {
+  const p = navigator.keyboard.lock(["KeyA", "KeyB"]);
+  assert_true(p instanceof Promise);
+  return p;
+}, '[Keyboard Lock] keyboard.lock');
+
+</script>
diff --git a/keyboard-lock/navigator-keyboard-unlock.https.html b/keyboard-lock/navigator-keyboard-unlock.https.html
new file mode 100644
index 0000000..87b10ee
--- /dev/null
+++ b/keyboard-lock/navigator-keyboard-unlock.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  assert_equals(navigator.keyboard.unlock(),
+                undefined);
+}, '[Keyboard Lock] keyboard.unlock');
+
+</script>
diff --git a/keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https.html b/keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https.html
deleted file mode 100644
index 8e84d14..0000000
--- a/keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-promise_test((t) => {
-  const p1 = navigator.requestKeyboardLock(['a', 'b']);
-  const p2 = navigator.requestKeyboardLock(['c', 'd']);
-  return promise_rejects(t, null, p2,
-      'requestKeyboardLock() should only be ' +
-      'executed if another request has finished.');
-}, 'Keyboard Lock requestKeyboardLock twice in parallel');
-
-</script>
diff --git a/keyboard-lock/navigator-requestKeyboardLock-two-sequential-requests.https.html b/keyboard-lock/navigator-requestKeyboardLock-two-sequential-requests.https.html
deleted file mode 100644
index 30f4905..0000000
--- a/keyboard-lock/navigator-requestKeyboardLock-two-sequential-requests.https.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-promise_test(() => {
-  return navigator.requestKeyboardLock(['a', 'b'])
-      .then(() => {
-        return navigator.requestKeyboardLock(['c', 'd']);
-      });
-}, 'Keyboard Lock requestKeyboardLock twice sequentially');
-
-</script>
diff --git a/keyboard-lock/navigator-requestKeyboardLock.https.html b/keyboard-lock/navigator-requestKeyboardLock.https.html
deleted file mode 100644
index e6e0121..0000000
--- a/keyboard-lock/navigator-requestKeyboardLock.https.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-'use strict';
-
-promise_test(() => {
-  const p = navigator.requestKeyboardLock(['a', 'b']);
-  assert_true(p instanceof Promise);
-  return p;
-}, 'Keyboard Lock requestKeyboardLock');
-
-</script>
diff --git a/keyboard-lock/resources/iframe-lock-helper.html b/keyboard-lock/resources/iframe-lock-helper.html
new file mode 100644
index 0000000..3950802
--- /dev/null
+++ b/keyboard-lock/resources/iframe-lock-helper.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script>
+'use strict';
+
+window.onmessage = message => {
+  if (message.data === 'Ready') {
+    let onSuccess = () => { parent.postMessage('Failure', '*'); };
+    let onError = error => {
+      if (error.name == 'InvalidStateError') {
+        parent.postMessage('Success', '*');
+      } else {
+        parent.postMessage('Failure', '*');
+      }
+    };
+
+    navigator.keyboard.lock().then(onSuccess, onError);
+  }
+};
+
+</script>
diff --git a/keyboard-map/idlharness.https.html b/keyboard-map/idlharness.https.html
new file mode 100644
index 0000000..05d687d
--- /dev/null
+++ b/keyboard-map/idlharness.https.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<head>
+<title>Keyboard Map IDL tests</title>
+<link rel="help" href="https://wicg.github.io/keyboard-map/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+'use strict';
+
+promise_test(async () =>  {
+  var idl_array = new IdlArray();
+  const keyboard_map_idl = await fetch("/interfaces/keyboard-map.idl")
+      .then(r => r.text());
+  const layout_map = await navigator.keyboard.getLayoutMap();
+
+  idl_array.add_untested_idls('interface Navigator {};');
+  idl_array.add_idls(keyboard_map_idl);
+
+  window.layout_map = layout_map;
+
+  idl_array.add_objects({
+    Navigator: ['navigator'],
+    Keyboard: ['navigator.keyboard'],
+    KeyboardLayoutMap: ['layout_map'],
+  });
+  idl_array.test();
+}, "Test IDL implementation of Keyboard Map API");
+</script>
diff --git a/keyboard-map/keyboard-map-https.html b/keyboard-map/keyboard-map-https.html
new file mode 100644
index 0000000..97a50dd
--- /dev/null
+++ b/keyboard-map/keyboard-map-https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Keyboard Map basic tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+test(function() {
+  assert_true(navigator.keyboard instanceof Keyboard);
+}, "navigator.keyboard instanceof Keyboard");
+
+test(function() {
+  assert_equals(navigator.keyboard, navigator.keyboard);
+}, "navigator.keyboard SameObject");
+
+promise_test(function() {
+  const p = navigator.keyboard.getLayoutMap();
+  assert_true(p instanceof Promise);
+  return p.then(function(map) {
+      assert_true(map instanceof KeyboardLayoutMap);
+      for (var [k,v] of map) {
+        assert_true(typeof k === 'string');
+        assert_true(k.length != 0);
+        assert_true(typeof v === 'string');
+        assert_true(v.length != 0);
+      }
+    });
+}, "navigator.keyboard.getLayoutMap() returns a Promise<KeyboardLayoutMap> when successful");
+
+</script>
diff --git a/lint.whitelist b/lint.whitelist
index dcbc2a5..ce825bf 100644
--- a/lint.whitelist
+++ b/lint.whitelist
@@ -7,25 +7,16 @@
 
 ## Whitespace rules that we can't enforce yet ##
 
-CR AT EOL: svg/import/*
-
 INDENT TABS: .gitmodules
 INDENT TABS: conformance-checkers/*
 INDENT TABS: content-security-policy/*
-INDENT TABS: custom-elements/*
-INDENT TABS: old-tests/*
 INDENT TABS: pointerlock/*
 INDENT TABS: shadow-dom/*
-INDENT TABS: svg/import/*
-INDENT TABS: tools/*
-INDENT TABS: touch-events/*
-INDENT TABS: web-animations/*
 INDENT TABS: webaudio/*
 INDENT TABS: webvtt/*
 INDENT TABS: encoding/legacy*/*
 
 TRAILING WHITESPACE: 2dcontext/tools/current-work-canvas.xhtml
-TRAILING WHITESPACE: battery-status/*
 TRAILING WHITESPACE: conformance-checkers/*
 TRAILING WHITESPACE: content-security-policy/*
 TRAILING WHITESPACE: custom-elements/*
@@ -33,12 +24,10 @@
 TRAILING WHITESPACE: old-tests/*
 TRAILING WHITESPACE: pointerevents/*
 TRAILING WHITESPACE: shadow-dom/*
-TRAILING WHITESPACE: svg/import/*
-TRAILING WHITESPACE: tools/*
 TRAILING WHITESPACE: webaudio/*
 TRAILING WHITESPACE: WebIDL/*
 TRAILING WHITESPACE: webvtt/*
-TRAILING WHITESPACE: encoding/legacy*/*
+TRAILING WHITESPACE: server-timing/resources/parsing/*.sub.headers
 
 ## File types that should never be checked ##
 
@@ -58,6 +47,7 @@
 TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.otf
 TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.ttf
 TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.TTF
+TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.ttc
 TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.woff
 TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.woff2
 TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.eot
@@ -70,68 +60,74 @@
 
 ## Documentation ##
 
-W3C-TEST.ORG:README.md
-W3C-TEST.ORG:*/README.md
-W3C-TEST.ORG:docs/*
+W3C-TEST.ORG: README.md
+W3C-TEST.ORG: */README.md
+W3C-TEST.ORG: docs/*
 SET TIMEOUT: docs/*
+WEB-PLATFORM.TEST:README.md
+WEB-PLATFORM.TEST:*/README.md
+WEB-PLATFORM.TEST:docs/*
 
 ## Helper scripts ##
 
-W3C-TEST.ORG:tools/*
-PRINT STATEMENT:tools/*
-W3C-TEST.ORG:*/tools/*
-PRINT STATEMENT:*/tools/*
+PRINT STATEMENT: */tools/*
 
 ## Deliberate copies of Ahem ##
 # The allowed copy
 AHEM COPY: fonts/Ahem.ttf
 
 # None of these are actually Ahem
-AHEM COPY: css/fonts/ahem-extra/AHEM_*.TTF
+AHEM COPY: fonts/ahem-extra/AHEM_*.TTF
 
 # https://github.com/w3c/web-platform-tests/issues/7437
 AHEM COPY: css/vendor-imports/mozilla/mozilla-central-reftests/*/Ahem.ttf
 
-### Test exclusions ##
+## Test exclusions ##
 
-CR AT EOL:WebIDL/valid/idl/documentation-dos.widl
-CR AT EOL:cors/resources/cors-headers.asis
-CR AT EOL:html/semantics/embedded-content/the-canvas-element/size.attributes.parse.whitespace.html
-INDENT TABS:html/semantics/embedded-content/the-canvas-element/size.attributes.parse.whitespace.html
-CR AT EOL:webvtt/parsing/file-parsing/tests/support/newlines.vtt
-PARSE-FAILED:dom/nodes/Document-createElement-namespace-tests/empty.svg
-PARSE-FAILED:dom/nodes/Document-createElement-namespace-tests/empty.xhtml
-PARSE-FAILED:dom/nodes/Document-createElement-namespace-tests/minimal_html.svg
-PARSE-FAILED:dom/nodes/Document-createElement-namespace-tests/minimal_html.xhtml
+# Intentional use of CRLF
+CR AT EOL: WebIDL/valid/idl/documentation-dos.widl
+CR AT EOL: cors/resources/cors-headers.asis
+CR AT EOL: html/form-elements/the-textarea-element/multiline-placeholder-cr.html
+CR AT EOL: html/form-elements/the-textarea-element/multiline-placeholder-crlf.html
+CR AT EOL: html/input/the-placeholder-attribute/multiline-cr.html
+CR AT EOL: html/input/the-placeholder-attribute/multiline-crlf.html
+CR AT EOL: webvtt/parsing/file-parsing/tests/support/newlines.vtt
 
-# Test generation files containing print statements
-PRINT STATEMENT:dom/nodes/Document-createElement-namespace-tests/generate.py
-PRINT STATEMENT:encrypted-media/polyfill/make-polyfill-tests.py
+# Intentional use of tabs
+INDENT TABS: html/semantics/embedded-content/the-canvas-element/size.attributes.parse.whitespace.html
+
+# Intentional use of print statements
+PRINT STATEMENT: dom/nodes/Document-createElement-namespace-tests/generate.py
+PRINT STATEMENT: encrypted-media/polyfill/make-polyfill-tests.py
+PRINT STATEMENT: webdriver/tests/support/fixtures.py
 
 # semi-legitimate use of console.*
-CONSOLE:console/*
-CONSOLE:streams/resources/test-utils.js
-CONSOLE:service-workers/service-worker/resources/navigation-redirect-other-origin.html
-CONSOLE:service-workers/service-worker/navigation-redirect.https.html
-CONSOLE:service-workers/service-worker/resources/clients-get-other-origin.html
+CONSOLE: console/*
+CONSOLE: resources/check-layout-th.js
+CONSOLE: resources/chromium/*
+CONSOLE: resources/idlharness.js
+CONSOLE: streams/resources/test-utils.js
+CONSOLE: service-workers/service-worker/resources/navigation-redirect-other-origin.html
+CONSOLE: service-workers/service-worker/navigation-redirect.https.html
+CONSOLE: service-workers/service-worker/resources/clients-get-other-origin.html
 
 # use of console in a public library - annotation-model ensures
 # it is not actually used
-CONSOLE:annotation-model/scripts/ajv.min.js
-CONSOLE:annotation-model/scripts/showdown.min.js
+CONSOLE: annotation-model/scripts/ajv.min.js
+CONSOLE: annotation-model/scripts/showdown.min.js
 CR AT EOL: annotation-model/scripts/showdown.min.js
 
-# Lint doesn't know about sub.svg I guess
-PARSE-FAILED:content-security-policy/svg/including.sub.svg
-
-#Helper files that aren't valid XML
-PARSE-FAILED:dom/nodes/Document-createElement-namespace-tests/empty.xml
-PARSE-FAILED:dom/nodes/Document-createElement-namespace-tests/minimal_html.xml
-PARSE-FAILED:acid/acid3/empty.xml
+# Helper files that aren't valid XML
+PARSE-FAILED: acid/acid3/empty.xml
+PARSE-FAILED: dom/nodes/Document-createElement-namespace-tests/empty.svg
+PARSE-FAILED: dom/nodes/Document-createElement-namespace-tests/empty.xhtml
+PARSE-FAILED: dom/nodes/Document-createElement-namespace-tests/empty.xml
+PARSE-FAILED: dom/nodes/Document-createElement-namespace-tests/minimal_html.svg
+PARSE-FAILED: dom/nodes/Document-createElement-namespace-tests/minimal_html.xhtml
+PARSE-FAILED: dom/nodes/Document-createElement-namespace-tests/minimal_html.xml
 
 # setTimeout usage (should probably mostly be fixed)
 SET TIMEOUT: *-manual.*
-SET TIMEOUT: 2dcontext/*
 SET TIMEOUT: annotation-model/scripts/ajv.min.js
 SET TIMEOUT: apng/animated-png-timeout.html
 SET TIMEOUT: cookies/resources/testharness-helpers.js
@@ -143,7 +139,6 @@
 SET TIMEOUT: css/selectors/selector-placeholder-shown-type-change-003.html
 SET TIMEOUT: css/selectors/selector-read-write-type-change-002.html
 SET TIMEOUT: css/selectors/selector-required-type-change-002.html
-SET TIMEOUT: css/css-fonts/font-display/font-display.html
 SET TIMEOUT: encrypted-media/polyfill/chrome-polyfill.js
 SET TIMEOUT: encrypted-media/polyfill/clearkey-polyfill.js
 SET TIMEOUT: encrypted-media/scripts/playback-temporary-events.js
@@ -174,19 +169,15 @@
 SET TIMEOUT: navigation-timing/*
 SET TIMEOUT: offscreen-canvas/the-offscreen-canvas/*
 SET TIMEOUT: old-tests/submission/Microsoft/history/history_000.htm
-SET TIMEOUT: page-visibility/resources/pagevistestharness.js
 SET TIMEOUT: paint-timing/resources/subframe-painting.html
 SET TIMEOUT: payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub.html
-SET TIMEOUT: payment-request/payment-request-response-id.html
-SET TIMEOUT: pointerevents/pointerevent_support.js
 SET TIMEOUT: preload/single-download-preload.html
 SET TIMEOUT: resize-observer/resources/iframe.html
 SET TIMEOUT: resource-timing/resource-timing.js
 SET TIMEOUT: resource-timing/single-entry-per-resource.html
-SET TIMEOUT: screen-orientation/lock-bad-argument.html
 SET TIMEOUT: screen-orientation/onchange-event.html
-SET TIMEOUT: screen-orientation/resources/sandboxed-iframe-locking.html
 SET TIMEOUT: secure-contexts/basic-popup-and-iframe-tests.https.js
+SET TIMEOUT: service-workers/cache-storage/script-tests/cache-abort.js
 SET TIMEOUT: service-workers/service-worker/activation.https.html
 SET TIMEOUT: service-workers/service-worker/fetch-frame-resource.https.html
 SET TIMEOUT: service-workers/service-worker/fetch-request-redirect.https.html
@@ -200,12 +191,10 @@
 SET TIMEOUT: service-workers/service-worker/resources/opaque-response-preloaded-xhr.html
 SET TIMEOUT: service-workers/service-worker/resources/performance-timeline-worker.js
 SET TIMEOUT: service-workers/service-worker/resources/resource-timing-worker.js
-SET TIMEOUT: service-workers/service-worker/resources/register-foreign-fetch-errors-worker.js
 SET TIMEOUT: shadow-dom/Document-prototype-currentScript.html
 SET TIMEOUT: shadow-dom/scroll-to-the-fragment-in-shadow-tree.html
 SET TIMEOUT: shadow-dom/slotchange-event.html
 SET TIMEOUT: shadow-dom/untriaged/html-elements-in-shadow-trees/html-forms/test-003.html
-SET TIMEOUT: shadow-dom/untriaged/html-elements-in-shadow-trees/inert-html-elements/test-001.html
 SET TIMEOUT: streams/piping/close-propagation-forward.js
 SET TIMEOUT: streams/piping/error-propagation-backward.js
 SET TIMEOUT: streams/piping/error-propagation-forward.js
@@ -214,16 +203,19 @@
 SET TIMEOUT: streams/resources/rs-utils.js
 SET TIMEOUT: streams/writable-streams/byte-length-queuing-strategy.js
 SET TIMEOUT: user-timing/*
-SET TIMEOUT: webaudio/js/lodash.js
 SET TIMEOUT: webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
+SET TIMEOUT: webauthn/*timeout.https.html
 SET TIMEOUT: webdriver/*
 SET TIMEOUT: webmessaging/*
-SET TIMEOUT: websockets/*
 SET TIMEOUT: webstorage/eventTestHarness.js
 SET TIMEOUT: webvtt/*
 SET TIMEOUT: workers/*
-SET TIMEOUT: XMLHttpRequest/resources/init.htm
-SET TIMEOUT: XMLHttpRequest/resources/xmlhttprequest-timeout.js
+SET TIMEOUT: xhr/resources/init.htm
+SET TIMEOUT: xhr/resources/xmlhttprequest-timeout.js
+
+# generate_tests implementation and sample usage
+GENERATE_TESTS: resources/test/tests/generate-callback.html
+GENERATE_TESTS: resources/testharness.js
 
 # generate_tests usage (should be got rid of)
 GENERATE_TESTS: 2dcontext/drawing-images-to-the-canvas/*
@@ -237,17 +229,13 @@
 GENERATE_TESTS: css/css-tables/height-distribution/computing-row-measure-0.html
 GENERATE_TESTS: css/css-tables/height-distribution/computing-row-measure-1.html
 GENERATE_TESTS: css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html
-GENERATE_TESTS: css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html
 GENERATE_TESTS: css/css-tables/html-to-css-mapping-1.html
 GENERATE_TESTS: css/css-tables/html-to-css-mapping-2.html
 GENERATE_TESTS: css/css-tables/html5-table-formatting-1.html
-GENERATE_TESTS: css/css-tables/html5-table-formatting-1.html
 GENERATE_TESTS: css/css-tables/html5-table-formatting-2.html
 GENERATE_TESTS: css/css-tables/html5-table-formatting-3.html
 GENERATE_TESTS: css/css-tables/html5-table-formatting-fixed-layout-1.html
 GENERATE_TESTS: css/css-tables/table-model-fixup-2.html
-GENERATE_TESTS: css/css-tables/table-model-fixup-2.html
-GENERATE_TESTS: css/css-tables/table-model-fixup-2.html
 GENERATE_TESTS: css/css-tables/table-model-fixup.html
 GENERATE_TESTS: css/css-tables/visibility-collapse-col-001.html
 GENERATE_TESTS: css/css-tables/visibility-collapse-row-001.html
@@ -261,14 +249,12 @@
 GENERATE_TESTS: css/css-tables/width-distribution/distribution-algo-min-content-percent-guess.html
 GENERATE_TESTS: css/css-tables/width-distribution/distribution-algo-min-content-specified-guess.1.html
 GENERATE_TESTS: css/css-tables/width-distribution/distribution-algo-min-content-specified-guess.html
-GENERATE_TESTS: css/cssom/index-002.html
 GENERATE_TESTS: dom/nodes/case.js
 GENERATE_TESTS: dom/ranges/Range-cloneRange.html
 GENERATE_TESTS: dom/ranges/Range-collapse.html
 GENERATE_TESTS: dom/ranges/Range-mutations.js
 GENERATE_TESTS: dom/ranges/Range-selectNode.html
 GENERATE_TESTS: dom/ranges/Range-set.html
-GENERATE_TESTS: dom/traversal/NodeIterator.html
 GENERATE_TESTS: dom/traversal/TreeWalker.html
 GENERATE_TESTS: domparsing/createContextualFragment.html
 GENERATE_TESTS: domxpath/001.html
@@ -295,47 +281,51 @@
 GENERATE_TESTS: shadow-dom/untriaged/shadow-trees/upper-boundary-encapsulation/window-named-properties-003.html
 
 # Intentional use of setTimeout
+SET TIMEOUT: css/css-fonts/font-display/font-display.html
+SET TIMEOUT: css/css-fonts/font-display/font-display-change.html
+SET TIMEOUT: css/css-fonts/font-display/font-display-change-ref.html
+SET TIMEOUT: css/css-fonts/font-display/font-display-preload.html
 SET TIMEOUT: html/browsers/windows/auxiliary-browsing-contexts/resources/close-opener.html
 SET TIMEOUT: html/dom/documents/dom-tree-accessors/Document.currentScript.html
 SET TIMEOUT: html/webappapis/timers/*
+SET TIMEOUT: resources/chromium/*
+SET TIMEOUT: resources/test/tests/add_cleanup.html
+SET TIMEOUT: resources/test/tests/api-tests-1.html
+SET TIMEOUT: resources/test/tests/worker.js
+SET TIMEOUT: resources/testharness.js
 
 # setTimeout use in reftests
 SET TIMEOUT: acid/acid3/test.html
 
 # Travis
-W3C-TEST.ORG:.travis.yml
+WEB-PLATFORM.TEST: .travis.yml
 
-# Git submodules are not currently scanned
-*:tools/*
-*:resources/*
-*:css/tools/apiclient/*
-*:css/tools/w3ctestlib/*
+
+# Third party code
+*: css/tools/apiclient/*
+*: css/tools/w3ctestlib/*
+*: resources/webidl2/*
+*: tools/*
 
 # Build system virtualenv
-*:css/tools/_virtualenv/*
-
+*: css/tools/_virtualenv/*
 
 ## Third party data files
 TRAILING WHITESPACE: css/css-writing-modes/tools/generators/ucd/Blocks.txt
-
+TRAILING WHITESPACE: resources/chromium/*
 
 ## Test generation files
 CONSOLE: css/css-writing-modes/tools/generators/unicode-data.js
 
-
 ## Test plans and implementation reports
-*: css/*/reports/*
 *: css/*/test-plan/*
 
-
 ## Things we don't have enabled yet
 OPEN-NO-MODE: css/*
-PARSE-FAILED: css/*
 PRINT STATEMENT: css/*
 CONTENT-VISUAL: css/*
 CONTENT-MANUAL: css/*
 
-
 ## Support files not in /support/ or similar
 SUPPORT-WRONG-DIR: css/requirements.txt
 SUPPORT-WRONG-DIR: css/README.md
@@ -348,10 +338,8 @@
 SUPPORT-WRONG-DIR: css/*-README
 SUPPORT-WRONG-DIR: css/*/LICENSE
 SUPPORT-WRONG-DIR: css/*/LICENSE-*
-SUPPORT-WRONG-DIR: css/*/COPYING
 SUPPORT-WRONG-DIR: css/*/Makefile
 SUPPORT-WRONG-DIR: css/*/OWNERS
-SUPPORT-WRONG-DIR: css/fonts/*
 
 # The selectors-3 testsuite has a weird build system
 SUPPORT-WRONG-DIR: css/selectors/*
@@ -374,19 +362,6 @@
 SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/check-for-references.sh
 SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/sync-tests-filter
 SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/sync-tests.sh
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-32x32.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-37x37.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/border.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bl.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bo.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-br.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ct.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-le.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ri.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tl.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-to.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tr.png
-SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule.png
 SUPPORT-WRONG-DIR: css/vendor-imports/mozilla/mozilla-central-reftests/masking/blank.html
 SUPPORT-WRONG-DIR: css/WOFF2/testcaseindex.xht
 NON-EXISTENT-REF: css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg
@@ -407,8 +382,6 @@
 INDENT TABS: css/css-lists/*
 INDENT TABS: css/css-masking/*
 INDENT TABS: css/css-multicol/*
-INDENT TABS: css/cssom/*
-INDENT TABS: css/cssom-view/*
 INDENT TABS: css/css-page/*
 INDENT TABS: css/css-pseudo/*
 INDENT TABS: css/css-regions/*
@@ -424,7 +397,6 @@
 INDENT TABS: css/css-variables/*
 INDENT TABS: css/css-writing-modes/*
 INDENT TABS: css/filter-effects/*
-INDENT TABS: css/fonts/*
 INDENT TABS: css/mediaqueries/*
 INDENT TABS: css/selectors/*
 INDENT TABS: css/vendor-imports/*
@@ -442,7 +414,6 @@
 CONSOLE: css/css-writing-modes/orthogonal-parent-shrink-to-fit-001*.html
 CONSOLE: css/css-writing-modes/tools/generators/gulpfile.js
 CONSOLE: css/css-writing-modes/tools/generators/text-orientation-generator.js
-CONSOLE: css/cssom/index-002.html
 
 TRAILING WHITESPACE: css/CSS2/generated-content/before-after-positioned-002.html
 TRAILING WHITESPACE: css/CSS2/generated-content/before-after-positioned-003.html
@@ -462,10 +433,7 @@
 TRAILING WHITESPACE: css/css-scoping/css-scoping-shadow-with-outside-rules.html
 TRAILING WHITESPACE: css/css-scoping/css-scoping-shadow-with-rules.html
 TRAILING WHITESPACE: css/css-scoping/css-scoping-shadow-with-rules-no-style-leak.html
-TRAILING WHITESPACE: css/css-transforms/css3-transform-rotateY.html
 TRAILING WHITESPACE: css/css-variables/reference/vars-font-shorthand-001-ref.html
-TRAILING WHITESPACE: css/css-variables/vars-font-shorthand-001.html
-TRAILING WHITESPACE: css/geometry/DOMMatrix-001.html
 TRAILING WHITESPACE: css/vendor-imports/mozilla/mozilla-central-reftests/css21/pagination/moz-css21-float-page-break-inside-avoid-7.html
 TRAILING WHITESPACE: css/vendor-imports/mozilla/mozilla-central-reftests/css21/pagination/moz-css21-float-page-break-inside-avoid-7-ref.html
 TRAILING WHITESPACE: css/vendor-imports/mozilla/mozilla-central-reftests/css21/pagination/moz-css21-float-page-break-inside-avoid-8.html
@@ -537,29 +505,13 @@
 SET TIMEOUT: css/CSS2/selectors/dom-hover-001.xht
 SET TIMEOUT: css/CSS2/selectors/dom-hover-002.xht
 SET TIMEOUT: css/CSS2/tables/tables-102.xht
-SET TIMEOUT: css/cssom-view/matchMediaAddListener.html
 SET TIMEOUT: css/mediaqueries/min-width-tables-001.html
-SET TIMEOUT: css/selectors/focus-within-shadow-001.html
-SET TIMEOUT: css/selectors/focus-within-shadow-002.html
-SET TIMEOUT: css/selectors/focus-within-shadow-003.html
-SET TIMEOUT: css/selectors/focus-within-shadow-004.html
-SET TIMEOUT: css/selectors/focus-within-shadow-005.html
 
 ## Build system stuff
-CSS-COLLIDING-TEST-NAME: css/*/OWNERS
-CSS-COLLIDING-TEST-NAME: css/*/README
-CSS-COLLIDING-TEST-NAME: css/*/README.md
-CSS-COLLIDING-TEST-NAME: css/*/LICENSE
-CSS-COLLIDING-TEST-NAME: css/*/Makefile
-CSS-COLLIDING-TEST-NAME: css/*/reftest.list
-CSS-COLLIDING-TEST-NAME: css/*/.htaccess
 CSS-COLLIDING-SUPPORT-NAME: css/*/README
-CSS-COLLIDING-SUPPORT-NAME: css/*/.htaccess
 CSS-COLLIDING-SUPPORT-NAME: css/*/LOCK
 
 # These are all the current "merge mismatch" errors the build system produces
-CSS-COLLIDING-SUPPORT-NAME: css/css-flexbox/support/100x100-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-grid/grid-items/support/100x100-green.png
 CSS-COLLIDING-SUPPORT-NAME: css/css-flexbox/support/200x200-green.png
 CSS-COLLIDING-SUPPORT-NAME: css/css-grid/grid-items/support/200x200-green.png
 CSS-COLLIDING-SUPPORT-NAME: css/css-regions/contentEditable/support/common.css
@@ -654,95 +606,6 @@
 CSS-COLLIDING-SUPPORT-NAME: css/CSS2/tables/support/pattern-grg-rgr-grg.png
 CSS-COLLIDING-SUPPORT-NAME: css/CSS2/values/support/pattern-grg-rgr-grg.png
 
-# These are thrown off by their support/support/ copies
-# https:css///github.com/w3c/csswg-test/issues/1235
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/bidi-text/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/tables/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/lists/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/filter-effects/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/cssom-view/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-values/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-style-attr/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/text/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-multicol/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/values/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/box-display/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/generated-content/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/support/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-fonts/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/pagination/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-flexbox/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/fonts/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/selectors/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/ui/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/zindex/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/csswg-issues/submitted/css2.1/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/backgrounds/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/i18n/syndata/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-text/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-backgrounds/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-transforms/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/syntax/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-images/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-transitions/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/cascade/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/floats-clear/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/css1/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-shapes/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/colors/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/positioning/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-regions/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/borders/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/cssom/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-page/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/generate/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/normal-flow/support/swatch-red.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-flexbox/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/generated-content/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/backgrounds/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/text/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-values/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/csswg-issues/submitted/css2.1/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/borders/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/filter-effects/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/support/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/cssom/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-writing-modes/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/positioning/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-fonts/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-images/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/box-display/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/colors/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/generate/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-transforms/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/normal-flow/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-text/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-page/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/bidi-text/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-style-attr/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/zindex/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-transitions/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/fonts/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/i18n/syndata/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/selectors/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/syntax/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/css1/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-shapes/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/lists/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/ui/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/margin-padding-clear/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-backgrounds/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/tables/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/cascade/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-multicol/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/values/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/css-regions/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/floats-clear/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/cssom-view/support/swatch-green.png
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/pagination/support/swatch-green.png
-
 # Duplicate filename not picked up by the build system
 # https:css///github.com/w3c/csswg-test/issues/1236
 CSS-COLLIDING-REF-NAME: css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg
@@ -768,6 +631,10 @@
 CSS-COLLIDING-REF-NAME: css/CSS2/ui/overflow-applies-to-001-ref.xht
 CSS-COLLIDING-REF-NAME: css/CSS2/visuren/inline-formatting-context-001-ref.xht
 CSS-COLLIDING-REF-NAME: css/CSS2/linebox/inline-formatting-context-001-ref.xht
+CSS-COLLIDING-REF-NAME: css/css-transforms/individual-transform/individual-transform-1-ref.html
+CSS-COLLIDING-REF-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1-ref.html
+CSS-COLLIDING-REF-NAME: css/css-flexbox/reference/percentage-size-subitems-001-ref.html
+CSS-COLLIDING-REF-NAME: css/css-grid/grid-items/percentage-size-subitems-001-ref.html
 CSS-COLLIDING-SUPPORT-NAME: css/css-backgrounds/support/red.png
 CSS-COLLIDING-SUPPORT-NAME: css/compositing/mix-blend-mode/support/red.png
 CSS-COLLIDING-SUPPORT-NAME: css/compositing/background-blending/support/red.png
@@ -780,13 +647,11 @@
 CSS-COLLIDING-SUPPORT-NAME: css/CSS2/ui/support/animated.gif
 CSS-COLLIDING-SUPPORT-NAME: css/CSS2/backgrounds/support/animated.gif
 CSS-COLLIDING-SUPPORT-NAME: css/css-shapes/shape-outside/shape-image/support/animated.gif
-CSS-COLLIDING-SUPPORT-NAME: css/CSS2/css1/support/pattern-gg-gr.png
 CSS-COLLIDING-SUPPORT-NAME: css/css-display/support/util.js
 CSS-COLLIDING-SUPPORT-NAME: css/CSS2/normal-flow/support/replaced-min-max-1.png
 CSS-COLLIDING-SUPPORT-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/ui3/support/replaced-min-max-1.png
 
 # CSS tests that used to be at the top level and weren't subject to lints
-MISSING-LINK: css/css-fonts/font-display/font-display.html
 MISSING-LINK: css/css-fonts/matching/fixed-stretch-style-over-weight.html
 SUPPORT-WRONG-DIR: css/css-fonts/matching/font-matching.css
 MISSING-LINK: css/css-fonts/matching/stretch-distance-over-weight-distance.html
@@ -872,26 +737,7 @@
 SUPPORT-WRONG-DIR: css/css-timing/testcommon.js
 MISSING-LINK: css/css-typed-om/CSSMatrixComponent-DOMMatrix-mutable.html
 MISSING-LINK: css/css-typed-om/declared-styleMap-accepts-inherit.html
-MISSING-LINK: css/css-typed-om/styleMap-update-function.html
-MISSING-LINK: css/cssom/CSS.html
-MISSING-LINK: css/cssom/CSSKeyframeRule.html
-MISSING-LINK: css/cssom/CSSKeyframesRule.html
-MISSING-LINK: css/cssom/CSSNamespaceRule.html
-MISSING-LINK: css/cssom/CSSRuleList.html
-MISSING-LINK: css/cssom/CSSStyleSheet.html
-MISSING-LINK: css/cssom/GetBoundingRect.html
-MISSING-LINK: css/cssom/historical.html
-MISSING-LINK: css/cssom/MediaList.html
-MISSING-LINK: css/cssom/overflow-serialization.html
-MISSING-LINK: css/cssom/selectorText-modification-restyle-001.html
-MISSING-LINK: css/cssom/serialization-CSSDeclaration-with-important.html
-MISSING-LINK: css/cssom/serialize-values.html
-MISSING-LINK: css/cssom/serialize-variable-reference.html
-MISSING-LINK: css/cssom/shorthand-serialization.html
 SUPPORT-WRONG-DIR: css/cssom/stylesheet-same-origin.css
-MISSING-LINK: css/cssom/stylesheet-same-origin.sub.html
-MISSING-LINK: css/cssom/StyleSheetList.html
-MISSING-LINK: css/cssom/variable-names.html
 MISSING-LINK: css/cssom-view/DOMRectList.html
 MISSING-LINK: css/cssom-view/elementFromPoint-002.html
 MISSING-LINK: css/cssom-view/elementFromPoint-003.html
@@ -917,16 +763,28 @@
 MISSING-LINK: css/cssom-view/scrollingElement.html
 MISSING-LINK: css/cssom-view/scrollIntoView-shadow.html
 MISSING-LINK: css/cssom-view/scrollIntoView-smooth.html
-CSS-COLLIDING-TEST-NAME: css/cssom-view/interfaces.html
-CSS-COLLIDING-TEST-NAME: css/cssom/interfaces.html
+MISSING-LINK: css/cssom-view/scrollTop-display-change.html
 
 # TODO https://github.com/w3c/web-platform-tests/issues/5770
 MISSING-LINK: css/geometry/*.worker.js
-
-WEBIDL2.JS:.gitmodules
-
-# Manual test that uses console.logs for feedback
-CONSOLE:payment-request/payment-request-response-id.html
+MISSING-LINK: css/filter-effects/*.any.js
 
 # Tests that use WebKit/Blink testing APIs
 LAYOUTTESTS APIS: css/css-regions/interactivity/*
+LAYOUTTESTS APIS: resources/chromium/generic_sensor_mocks.js
+
+# Existing use of WEB-PLATFORM.TEST
+WEB-PLATFORM.TEST: clear-site-data/support/test_utils.sub.js
+WEB-PLATFORM.TEST: content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html
+WEB-PLATFORM.TEST: content-security-policy/generic/generic-0_8.sub.html
+WEB-PLATFORM.TEST: content-security-policy/generic/generic-0_8_1.sub.html
+WEB-PLATFORM.TEST: content-security-policy/nonce-hiding/script-nonces-hidden-meta.tentative.html
+WEB-PLATFORM.TEST: content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.tentative.html
+WEB-PLATFORM.TEST: fetch/api/request/request-structure.html
+WEB-PLATFORM.TEST: html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html
+WEB-PLATFORM.TEST: html/semantics/embedded-content/the-iframe-element/cross_origin_parentage.html
+WEB-PLATFORM.TEST: html/semantics/forms/the-label-element/label-attributes.html
+WEB-PLATFORM.TEST: navigation-timing/nav2_test_attributes_values.html
+WEB-PLATFORM.TEST: navigation-timing/nav2_test_instance_accessors.html
+WEB-PLATFORM.TEST: webrtc/RTCPeerConnection-getIdentityAssertion.html
+WEB-PLATFORM.TEST: webrtc/identity-helper.js
diff --git a/longtask-timing/longtask-in-childiframe-crossorigin.html b/longtask-timing/longtask-in-childiframe-crossorigin.html
index 4ad9a74..2fa0f78 100644
--- a/longtask-timing/longtask-in-childiframe-crossorigin.html
+++ b/longtask-timing/longtask-in-childiframe-crossorigin.html
@@ -35,7 +35,7 @@
             assert_equals(attribution.containerId, 'child-iframe-id');
             assert_equals(attribution.containerName, 'child-iframe-name');
             assert_equals(attribution.containerSrc,
-                'http://www1.web-platform.test:8000/longtask-timing/resources/subframe-with-longtask.html');
+                'resources/subframe-with-longtask.html');
             observer.disconnect();
             t.done();
         })
@@ -44,10 +44,11 @@
     const iframe = document.createElement('iframe');
     iframe.id = 'child-iframe-id';
     iframe.name = 'child-iframe-name';
+    // Simulate cross-origin by using sandbox.
+    iframe.sandbox = "allow-scripts";
     document.body.appendChild(iframe);
-    // TODO(panicker): simulate cross-origin instead
-    iframe.src = 'http://www1.web-platform.test:8000/longtask-timing/resources/subframe-with-longtask.html';
-}, 'Performance longtask entries in child iframe are observable in parent.');
+    iframe.src = 'resources/subframe-with-longtask.html';
+}, 'Performance longtask entries in cross-origin child iframe are observable in parent.');
 </script>
 
 </body>
diff --git a/longtask-timing/longtask-in-parentiframe.html b/longtask-timing/longtask-in-parentiframe.html
index 71f309a..be29b6c 100644
--- a/longtask-timing/longtask-in-parentiframe.html
+++ b/longtask-timing/longtask-in-parentiframe.html
@@ -9,7 +9,12 @@
 <script>
   const t = async_test(t => {
     window.addEventListener('message', t.step_func(e => {
-      assert_equals(e.data, 'longtask+same-origin-ancestor+script+++');
+      assert_equals(e.data['entryType'], 'longtask');
+      assert_equals(e.data['frame-attribution'], 'same-origin-ancestor');
+      assert_equals(e.data['task-attribution'], 'script');
+      assert_equals(e.data['containerId'], '');
+      assert_equals(e.data['containerName'], '');
+      assert_equals(e.data['containerSrc'], '');
       t.done();
     }));
   }, 'Performance longtask entries in parent are observable in child iframe.');
diff --git a/longtask-timing/longtask-in-sibling-iframe-crossorigin.html b/longtask-timing/longtask-in-sibling-iframe-crossorigin.html
index 503d878..8e92284 100644
--- a/longtask-timing/longtask-in-sibling-iframe-crossorigin.html
+++ b/longtask-timing/longtask-in-sibling-iframe-crossorigin.html
@@ -9,21 +9,28 @@
 <script>
   async_test(t => {
     window.addEventListener('message', t.step_func(e => {
-      assert_equals(e.data, 'longtask+cross-origin-unreachable+script+++');
+      assert_equals(e.data['entryType'], 'longtask');
+      assert_equals(e.data['frame-attribution'], 'cross-origin-unreachable');
+      assert_equals(e.data['task-attribution'], 'script');
+      assert_equals(e.data['containerId'], '');
+      assert_equals(e.data['containerName'], '');
+      assert_equals(e.data['containerSrc'], '');
       t.done();
     }));
-}, 'Performance longtask entries in parent are observable in child iframe.');
+    const observingFrame = document.createElement('iframe');
+    observingFrame.id = 'observing-iframe-id';
+    observingFrame.name = 'observing-iframe-name';
+    document.body.appendChild(observingFrame);
+    observingFrame.src = 'resources/subframe-observing-longtask.html'
+
+    /* Create a cross-origin iframe that generates a long task. */
+    const longtaskFrame = document.createElement('iframe');
+    longtaskFrame.id = 'longtask-iframe-id';
+    longtaskFrame.name = 'longtask-iframe-name';
+    // Simulate cross-origin by using sandbox.
+    longtaskFrame.sandbox = "allow-scripts";
+    document.body.appendChild(longtaskFrame);
+    longtaskFrame.src = 'resources/subframe-with-longtask.html'
+}, 'Performance longtask entries from cross-origin iframe are observable in its sibling.');
 </script>
-
-<iframe src="resources/subframe-observing-longtask.html"></iframe>
-
-<script>
-  /* Create a cross-origin iframe that generates a long task. */
-  const iframe = document.createElement('iframe');
-  iframe.id = 'child-iframe-id';
-  iframe.name = 'child-iframe-name';
-  document.body.appendChild(iframe);
-  iframe.src = 'http://www1.web-platform.test:8000/longtask-timing/resources/subframe-with-longtask.html';
-</script>
-
 </body>
diff --git a/longtask-timing/longtask-in-sibling-iframe.html b/longtask-timing/longtask-in-sibling-iframe.html
index 9f53621..d463810 100644
--- a/longtask-timing/longtask-in-sibling-iframe.html
+++ b/longtask-timing/longtask-in-sibling-iframe.html
@@ -9,21 +9,25 @@
 <script>
   async_test(t => {
     window.addEventListener('message', t.step_func(e => {
-      assert_equals(e.data, 'longtask+same-origin+script+child-iframe-id+' +
-        'child-iframe-name+resources/subframe-with-longtask.html');
+      assert_equals(e.data['entryType'], 'longtask');
+      assert_equals(e.data['frame-attribution'], 'same-origin');
+      assert_equals(e.data['task-attribution'], 'script');
+      assert_equals(e.data['containerId'], 'longtask-iframe-id');
+      assert_equals(e.data['containerName'], 'longtask-iframe-name');
+      assert_equals(e.data['containerSrc'], 'resources/subframe-with-longtask.html');
       t.done();
     }));
-}, 'Performance longtask entries in parent are observable in child iframe.');
-</script>
+    const observingFrame = document.createElement('iframe');
+    observingFrame.id = 'observing-iframe-id';
+    observingFrame.name = 'observing-iframe-name';
+    document.body.appendChild(observingFrame);
+    observingFrame.src = 'resources/subframe-observing-longtask.html'
 
-<iframe src="resources/subframe-observing-longtask.html"></iframe>
-
-<script>
-  /* Create an iframe that generates a long task. */
-  const iframe = document.createElement('iframe');
-  iframe.id = 'child-iframe-id';
-  iframe.name = 'child-iframe-name';
-  document.body.appendChild(iframe);
-  iframe.src = 'resources/subframe-with-longtask.html'
+    const longtaskFrame = document.createElement('iframe');
+    longtaskFrame.id = 'longtask-iframe-id';
+    longtaskFrame.name = 'longtask-iframe-name';
+    document.body.appendChild(longtaskFrame);
+    longtaskFrame.src = 'resources/subframe-with-longtask.html'
+}, 'Performance longtask entries are observable in sibling iframe.');
 </script>
 </body>
diff --git a/longtask-timing/longtask-tojson.html b/longtask-timing/longtask-tojson.html
new file mode 100644
index 0000000..bbe0d66
--- /dev/null
+++ b/longtask-timing/longtask-tojson.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+  async_test(function (t) {
+    const observer = new PerformanceObserver(
+      t.step_func(function (entryList) {
+        const entries = entryList.getEntries();
+        assert_greater_than_equal(entries.length, 1);
+        const entry = entries[0];
+        assert_equals(typeof(entry.toJSON), 'function');
+        const entryJSON = entry.toJSON();
+        assert_equals(typeof(entryJSON), 'object');
+        // Check attributes inheritted from PerformanceEntry.
+        const performanceEntryKeys = [
+            'name',
+            'entryType',
+            'startTime',
+            'duration'
+        ];
+        for (const key of performanceEntryKeys) {
+            assert_equals(entryJSON[key], entry[key],
+                `entry.toJSON().${key} should match entry.${key}`);
+        }
+
+        // Check PerformanceLongTaskTiming specific entries.
+        assert_equals(typeof(entryJSON.attribution), 'object');
+        const entryJsonAttribution = entryJSON.attribution[0];
+        assert_equals(typeof(entryJsonAttribution), 'object');
+        assert_equals(entryJSON.attribution.length, entry.attribution.length);
+
+        // Check TaskAttributionTiming toJSON.
+        const entryAttribution = entry.attribution[0];
+        assert_equals(typeof(entryAttribution.toJSON), 'function');
+        const entryAttributionJSON = entryAttribution.toJSON();
+        assert_equals(typeof(entryAttributionJSON), 'object');
+        // Check TaskAttributionTiming attributes, from both:
+        // 1) |entryJsonAttribution| from  PerformanceLongTaskTiming.
+        // 2) |entryAttributionJSON| from TaskAttributionTiming.
+        const taskAttributionTimingKeys = [
+            'name',
+            'entryType',
+            'startTime',
+            'duration',
+            'containerType',
+            'containerSrc',
+            'containerId',
+            'containerName'
+        ];
+        for (const key of taskAttributionTimingKeys) {
+            assert_equals(entryAttributionJSON[key], entryAttribution[key],
+                `attribution.toJSON().${key} should match attribution.${key}`);
+            assert_equals(entryJsonAttribution[key], entryAttribution[key],
+                `entry.toJSON().attribution[0].${key} should match attribution.${key}`);
+        }
+        t.done();
+      })
+    );
+    observer.observe({entryTypes: ['longtask']});
+
+    // Trigger a long task.
+    const begin = window.performance.now();
+    while (window.performance.now() < begin + 51);
+  }, 'Test toJSON() in PerformanceLongTaskTiming and TaskAttributionTiming');
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/longtask-timing/resources/subframe-observing-longtask.html b/longtask-timing/resources/subframe-observing-longtask.html
index e1e1a93..b232ecd 100644
--- a/longtask-timing/resources/subframe-observing-longtask.html
+++ b/longtask-timing/resources/subframe-observing-longtask.html
@@ -15,9 +15,14 @@
           return;
         // TODO(panicker): include containerType.
         const attribution = longtask.attribution[0];
-        const entryContents = longtask.entryType + '+' + longtask.name + '+' +
-          attribution.name + '+' + attribution.containerId + '+' +
-            attribution.containerName + '+' + attribution.containerSrc;
+        const entryContents = {
+          'entryType': longtask.entryType,
+          'frame-attribution': longtask.name,
+          'task-attribution': attribution.name,
+          'containerId': attribution.containerId,
+          'containerName': attribution.containerName,
+          'containerSrc': attribution.containerSrc
+        };
         top.postMessage(entryContents, '*');
       }
     });
diff --git a/magnetometer/Magnetometer-disabled-by-feature-policy.https.html b/magnetometer/Magnetometer-disabled-by-feature-policy.https.html
new file mode 100644
index 0000000..6e496c1
--- /dev/null
+++ b/magnetometer/Magnetometer-disabled-by-feature-policy.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<title>Magnetometer Feature Policy Test: Disabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_disabled('Magnetometer');
+run_fp_tests_disabled('UncalibratedMagnetometer');
+</script>
+</body>
diff --git a/magnetometer/Magnetometer-disabled-by-feature-policy.https.html.headers b/magnetometer/Magnetometer-disabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..afbd454
--- /dev/null
+++ b/magnetometer/Magnetometer-disabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: magnetometer 'none'
diff --git a/magnetometer/Magnetometer-enabled-by-feature-policy-attribute-redirect-on-load.https.html b/magnetometer/Magnetometer-enabled-by-feature-policy-attribute-redirect-on-load.https.html
new file mode 100644
index 0000000..4b65317
--- /dev/null
+++ b/magnetometer/Magnetometer-enabled-by-feature-policy-attribute-redirect-on-load.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<title>Magnetometer Feature Policy Test: Enabled by attribute redirect on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute_redirect_on_load('Magnetometer');
+run_fp_tests_enabled_by_attribute_redirect_on_load('UncalibratedMagnetometer');
+</script>
+</body>
diff --git a/magnetometer/Magnetometer-enabled-by-feature-policy-attribute.https.html b/magnetometer/Magnetometer-enabled-by-feature-policy-attribute.https.html
new file mode 100644
index 0000000..4c120f6
--- /dev/null
+++ b/magnetometer/Magnetometer-enabled-by-feature-policy-attribute.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<title>Magnetometer Feature Policy Test: Enabled by attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute('Magnetometer');
+run_fp_tests_enabled_by_attribute('UncalibratedMagnetometer');
+</script>
+</body>
diff --git a/magnetometer/Magnetometer-enabled-by-feature-policy.https.html b/magnetometer/Magnetometer-enabled-by-feature-policy.https.html
new file mode 100644
index 0000000..ae59834
--- /dev/null
+++ b/magnetometer/Magnetometer-enabled-by-feature-policy.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<title>Magnetometer Feature Policy Test: Enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled('Magnetometer');
+run_fp_tests_enabled('UncalibratedMagnetometer');
+</script>
+</body>
diff --git a/magnetometer/Magnetometer-enabled-by-feature-policy.https.html.headers b/magnetometer/Magnetometer-enabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..64ec55e
--- /dev/null
+++ b/magnetometer/Magnetometer-enabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: magnetometer *
diff --git a/magnetometer/Magnetometer-enabled-on-self-origin-by-feature-policy.https.html b/magnetometer/Magnetometer-enabled-on-self-origin-by-feature-policy.https.html
new file mode 100644
index 0000000..e621040
--- /dev/null
+++ b/magnetometer/Magnetometer-enabled-on-self-origin-by-feature-policy.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<title>Magnetometer Feature Policy Test: Enabled on self origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_on_self_origin('Magnetometer');
+run_fp_tests_enabled_on_self_origin('UncalibratedMagnetometer');
+</script>
+</body>
diff --git a/magnetometer/Magnetometer-enabled-on-self-origin-by-feature-policy.https.html.headers b/magnetometer/Magnetometer-enabled-on-self-origin-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..4223f53
--- /dev/null
+++ b/magnetometer/Magnetometer-enabled-on-self-origin-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: magnetometer 'self'
diff --git a/magnetometer/Magnetometer.https.html b/magnetometer/Magnetometer.https.html
index f327ed3..661ea68 100644
--- a/magnetometer/Magnetometer.https.html
+++ b/magnetometer/Magnetometer.https.html
@@ -9,6 +9,7 @@
 <div id="log"></div>
 <script>
 
-runGenericSensorTests(Magnetometer);
+runGenericSensorTests('Magnetometer');
+runGenericSensorTests('UncalibratedMagnetometer');
 
 </script>
diff --git a/magnetometer/Magnetometer_insecure_context.html b/magnetometer/Magnetometer_insecure_context.html
index 307c19b..45ff584 100644
--- a/magnetometer/Magnetometer_insecure_context.html
+++ b/magnetometer/Magnetometer_insecure_context.html
@@ -16,5 +16,6 @@
 <script>
 
 runGenericSensorInsecureContext("Magnetometer");
+runGenericSensorInsecureContext("UncalibratedMagnetometer");
 
 </script>
diff --git a/magnetometer/Magnetometer_onerror-manual.https.html b/magnetometer/Magnetometer_onerror-manual.https.html
index 3cd62db..dbdcf1d 100644
--- a/magnetometer/Magnetometer_onerror-manual.https.html
+++ b/magnetometer/Magnetometer_onerror-manual.https.html
@@ -15,6 +15,7 @@
 </ol>
 <script>
 
-runGenericSensorOnerror(Magnetometer);
+runGenericSensorOnerror('Magnetometer');
+runGenericSensorOnerror('UncalibratedMagnetometer');
 
 </script>
diff --git a/magnetometer/idlharness.https.html b/magnetometer/idlharness.https.html
index f874e19..904869e 100644
--- a/magnetometer/idlharness.https.html
+++ b/magnetometer/idlharness.https.html
@@ -15,7 +15,7 @@
   const idl_array = new IdlArray();
   idl_array.add_untested_idls(dom);
   idl_array.add_untested_idls('interface EventHandler {};');
-  idl_array.add_idls(generic_sensor, { only: ['Sensor'] });
+  idl_array.add_idls(generic_sensor, { only: ['Sensor', 'SensorOptions'] });
   idl_array.add_idls(magnetometer);
   idl_array.add_objects({
     Magnetometer: ['new Magnetometer();'],
@@ -31,7 +31,7 @@
 promise_test(() => {
   return Promise.all([
     "/interfaces/dom.idl",
-    "/interfaces/generic-sensor.idl",
+    "/interfaces/sensors.idl",
     "/interfaces/magnetometer.idl",
   ].map(fetchText)).then(doTest);
 }, "Test IDL implementation of Magnetometer Sensor");
diff --git a/mathml/presentation-markup/fractions/frac-parameters-1.html b/mathml/presentation-markup/fractions/frac-parameters-1.html
index f2b3251..a047a30 100644
--- a/mathml/presentation-markup/fractions/frac-parameters-1.html
+++ b/mathml/presentation-markup/fractions/frac-parameters-1.html
@@ -62,7 +62,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/fractions/frac-parameters-2.html b/mathml/presentation-markup/fractions/frac-parameters-2.html
index 08d639d..5445113 100644
--- a/mathml/presentation-markup/fractions/frac-parameters-2.html
+++ b/mathml/presentation-markup/fractions/frac-parameters-2.html
@@ -50,7 +50,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/operators/mo-axis-height-1.html b/mathml/presentation-markup/operators/mo-axis-height-1.html
index ee06e19..327a72e 100644
--- a/mathml/presentation-markup/operators/mo-axis-height-1.html
+++ b/mathml/presentation-markup/operators/mo-axis-height-1.html
@@ -26,7 +26,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/radicals/root-parameters-1.html b/mathml/presentation-markup/radicals/root-parameters-1.html
index beddc51..67a4613 100644
--- a/mathml/presentation-markup/radicals/root-parameters-1.html
+++ b/mathml/presentation-markup/radicals/root-parameters-1.html
@@ -50,7 +50,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/scripts/subsup-parameters-1.html b/mathml/presentation-markup/scripts/subsup-parameters-1.html
index bc710f3..9bc6bcb 100644
--- a/mathml/presentation-markup/scripts/subsup-parameters-1.html
+++ b/mathml/presentation-markup/scripts/subsup-parameters-1.html
@@ -62,7 +62,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/scripts/subsup-parameters-2.html b/mathml/presentation-markup/scripts/subsup-parameters-2.html
new file mode 100644
index 0000000..eaa4f0f
--- /dev/null
+++ b/mathml/presentation-markup/scripts/subsup-parameters-2.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Subscripts and Superscripts parameters</title>
+<link rel="help" href="http://www.mathml-association.org/MathMLinHTML5/S3.html#SS4">
+<meta name="assert" content="Elements msub, msup, subsup and msubsup correctly use the italic correction from the MATH table.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  math, mspace {
+    font-size: 10px;
+  }
+  @font-face {
+    font-family: largeop-displayoperatorminheight5000;
+    src: url("/fonts/math/largeop-displayoperatorminheight5000.woff");
+  }
+  @font-face {
+    font-family: largeop-displayoperatorminheight2000-2AFF-italiccorrection3000;
+    src: url("/fonts/math/largeop-displayoperatorminheight2000-2AFF-italiccorrection3000.woff");
+  }
+</style>
+<script>
+  function getBox(aId) {
+    return document.getElementById(aId).getBoundingClientRect();
+  }
+
+  setup({ explicit_done: true });
+  window.addEventListener("load", function() {
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
+  });
+
+  /*
+    These two tests verify that:
+    - In msub, the script is at the right of the base minus the italic correction.
+    - In msup, the script is just at the right of the base.
+    - In msubsup, the scripts are shifted by the italic correction.
+    - In mmultiscripts, postscript pairs are shifted by the italic correction.
+    - In mmultiscripts, prescript pairs are vertically aligned.
+  */
+  var epsilon = 1;
+  function runTests() {
+    test(function() {
+      var v = 0;
+      assert_approx_equals(getBox("base001").right - getBox("sub001").left, v, epsilon, "msub");
+      assert_approx_equals(getBox("sup002").left, getBox("base002").right, epsilon, "msup");
+      assert_approx_equals(getBox("sup003").left - getBox("sub003").left, v, epsilon, "msubsup");
+      assert_approx_equals(getBox("sup004").left - getBox("sub004").left, v, epsilon, "mmultiscripts postscripts");
+      assert_approx_equals(getBox("sup005").left - getBox("sub005").left, 0, epsilon, "mmultiscripts prescripts");
+    }, "Null Italic Correction");
+    test(function() {
+      var emToPx = 10 / 1000; // font-size: 10px, font.em = 1000
+      var v = 3000 * emToPx;
+      assert_approx_equals(getBox("base011").right - getBox("sub011").left, v, epsilon, "msub");
+      assert_approx_equals(getBox("sup012").left, getBox("base012").right, epsilon, "msup");
+      assert_approx_equals(getBox("sup013").left - getBox("sub013").left, v, epsilon, "msubsup");
+      assert_approx_equals(getBox("sup014").left - getBox("sub014").left, v, epsilon, "mmultiscripts postscripts");
+      assert_approx_equals(getBox("sup015").left - getBox("sub015").left, 0, epsilon, "mmultiscripts prescripts");
+    }, "NonNull Italic Correction");
+    done();
+  }
+</script>
+</head>
+<body>
+  <div id="log"></div>
+  <h2>Null Italic Correction</h2>
+  <p>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight5000;">
+      <msub>
+        <mo id="base001" lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sub001" height="1em" width="1em" mathbackground="blue"/>
+      </msub>
+    </math>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight5000;">
+      <msup>
+        <mo id="base002" lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sup002" height="1em" width="1em" mathbackground="blue"/>
+      </msup>
+    </math>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight5000;">
+      <msubsup>
+        <mo lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sub003" height="1em" width="1em" mathbackground="blue"/>
+        <mspace id="sup003" height="1em" width="1em" mathbackground="green"/>
+      </msubsup>
+    </math>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight5000;">
+      <mmultiscripts>
+        <mo lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sub004" height="1em" width="1em" mathbackground="blue"/>
+        <mspace id="sup004" height="1em" width="1em" mathbackground="green"/>
+        <mprescripts/>
+        <mspace id="sub005" height="1em" width="1em" mathbackground="magenta"/>
+        <mspace id="sup005" height="1em" width="1em" mathbackground="cyan"/>
+      </mmultiscripts>
+    </math>
+  </p>
+  <h2>NonNull Italic Correction</h2>
+  <p>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight2000-2AFF-italiccorrection3000;">
+      <msub>
+        <mo id="base011" lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sub011" height="1em" width="1em" mathbackground="blue"/>
+      </msub>
+    </math>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight2000-2AFF-italiccorrection3000;">
+      <msup>
+        <mo id="base012" lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sup012" height="1em" width="1em" mathbackground="blue"/>
+      </msup>
+    </math>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight2000-2AFF-italiccorrection3000;">
+      <msubsup>
+        <mo lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sub013" height="1em" width="1em" mathbackground="blue"/>
+        <mspace id="sup013" height="1em" width="1em" mathbackground="green"/>
+      </msubsup>
+    </math>
+    <math displaystyle="true" style="font-family: largeop-displayoperatorminheight2000-2AFF-italiccorrection3000;">
+      <mmultiscripts>
+        <mo lspace="0px" rspace="0px">&#x2AFF;</mo>
+        <mspace id="sub014" height="1em" width="1em" mathbackground="blue"/>
+        <mspace id="sup014" height="1em" width="1em" mathbackground="green"/>
+        <mprescripts/>
+        <mspace id="sub015" height="1em" width="1em" mathbackground="magenta"/>
+        <mspace id="sup015" height="1em" width="1em" mathbackground="cyan"/>
+      </mmultiscripts>
+    </math>
+  </p>
+</body>
+</html>
diff --git a/mathml/presentation-markup/scripts/underover-parameters-1.html b/mathml/presentation-markup/scripts/underover-parameters-1.html
index 462c819..1e5a660 100644
--- a/mathml/presentation-markup/scripts/underover-parameters-1.html
+++ b/mathml/presentation-markup/scripts/underover-parameters-1.html
@@ -38,7 +38,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/scripts/underover-parameters-2.html b/mathml/presentation-markup/scripts/underover-parameters-2.html
index afd5b8e..c28f29c 100644
--- a/mathml/presentation-markup/scripts/underover-parameters-2.html
+++ b/mathml/presentation-markup/scripts/underover-parameters-2.html
@@ -38,7 +38,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/scripts/underover-parameters-3.html b/mathml/presentation-markup/scripts/underover-parameters-3.html
index 29b023b..0172ff1 100644
--- a/mathml/presentation-markup/scripts/underover-parameters-3.html
+++ b/mathml/presentation-markup/scripts/underover-parameters-3.html
@@ -41,7 +41,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => {   document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/scripts/underover-parameters-4.html b/mathml/presentation-markup/scripts/underover-parameters-4.html
index 4dfe2ea..061cda7 100644
--- a/mathml/presentation-markup/scripts/underover-parameters-4.html
+++ b/mathml/presentation-markup/scripts/underover-parameters-4.html
@@ -41,7 +41,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => {   document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/presentation-markup/tables/table-axis-height.html b/mathml/presentation-markup/tables/table-axis-height.html
index f68f13d..50c3491 100644
--- a/mathml/presentation-markup/tables/table-axis-height.html
+++ b/mathml/presentation-markup/tables/table-axis-height.html
@@ -26,7 +26,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/relations/css-styling/displaystyle-1.html b/mathml/relations/css-styling/displaystyle-1.html
index a616e7f..2749e09 100644
--- a/mathml/relations/css-styling/displaystyle-1.html
+++ b/mathml/relations/css-styling/displaystyle-1.html
@@ -32,7 +32,8 @@
   }
 
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/relations/css-styling/lengths-3.html b/mathml/relations/css-styling/lengths-3.html
index 3dadb39..a7133f8 100644
--- a/mathml/relations/css-styling/lengths-3.html
+++ b/mathml/relations/css-styling/lengths-3.html
@@ -26,7 +26,8 @@
 
   setup({ explicit_done: true });
   window.addEventListener("load", function() {
-    document.fonts.ready.then(runTests);
+    // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+    requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
   });
 
   function runTests() {
diff --git a/mathml/tools/largeop.py b/mathml/tools/largeop.py
index 3c9c6c4..73d9676 100644
--- a/mathml/tools/largeop.py
+++ b/mathml/tools/largeop.py
@@ -12,3 +12,21 @@
 mathfont.drawRectangleGlyph(g, mathfont.em, v1, 0)
 f[nAryWhiteVerticalBarCodePoint].verticalVariants = "uni2AFF uni2AFF.display"
 mathfont.save(f)
+
+v1 = 2 * mathfont.em
+v2 = 3 * mathfont.em
+f = mathfont.create("largeop-displayoperatorminheight%d-2AFF-italiccorrection%d" % (v1, v2))
+f.copyright = "Copyright (c) 2018 Igalia S.L."
+f.math.DisplayOperatorMinHeight = v1
+mathfont.createSquareGlyph(f, nAryWhiteVerticalBarCodePoint)
+g = f.createChar(-1, "uni2AFF.display")
+p = g.glyphPen()
+p.moveTo(0, 0)
+p.lineTo(v2, v1)
+p.lineTo(v2 + mathfont.em, v1)
+p.lineTo(mathfont.em, 0)
+p.closePath();
+g.width = mathfont.em + v2
+g.italicCorrection = v2
+f[nAryWhiteVerticalBarCodePoint].verticalVariants = "uni2AFF uni2AFF.display"
+mathfont.save(f)
diff --git a/mathml/tools/utils/misc.py b/mathml/tools/utils/misc.py
index 3907abf..e4d21d6 100644
--- a/mathml/tools/utils/misc.py
+++ b/mathml/tools/utils/misc.py
@@ -1,7 +1,10 @@
 from __future__ import print_function
 import os
 import progressbar
-import urllib2
+try:
+    from urllib.request import urlopen
+except ImportError:
+    from urllib2 import urlopen
 
 MathMLAssociationCopyright = "Copyright (c) 2016 MathML Association"
 
@@ -13,14 +16,14 @@
     if not forceDownload and os.path.exists(fileName):
         return fileName
 
-    request = urllib2.urlopen(url)
+    request = urlopen(url)
     totalSize = int(request.info().getheader('Content-Length').strip())
     bar = progressbar.ProgressBar(maxval=totalSize).start()
 
     chunkSize = 16 * 1024
     downloaded = 0
     print("Downloading %s" % url)
-    os.umask(0002)
+    os.umask(0o002)
     with open(fileName, 'wb') as fp:
         while True:
             chunk = request.read(chunkSize)
diff --git a/media-capabilities/OWNERS b/media-capabilities/OWNERS
new file mode 100644
index 0000000..b05a482
--- /dev/null
+++ b/media-capabilities/OWNERS
@@ -0,0 +1 @@
+@mounirlamouri
diff --git a/media-capabilities/idlharness.html b/media-capabilities/idlharness.html
index 3efe5a6..c4b0b99 100644
--- a/media-capabilities/idlharness.html
+++ b/media-capabilities/idlharness.html
@@ -12,7 +12,7 @@
 <body>
 <h1>Media Session IDL tests</h1>
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global=Window, Exposed=Window]
 interface Window {
 };
 interface Worker {
diff --git a/media-source/generate-config-change-tests.py b/media-source/generate-config-change-tests.py
new file mode 100755
index 0000000..6ab2c8b
--- /dev/null
+++ b/media-source/generate-config-change-tests.py
@@ -0,0 +1,225 @@
+#!/usr/bin/python
+# Copyright (C) 2013 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+This is a script that generates the content and HTML files for Media Source
+codec config change LayoutTests.
+"""
+import json
+import os
+
+DURATION = 2
+MEDIA_FORMATS = ['webm', 'mp4']
+ENCODE_SETTINGS = [
+    ## Video-only files
+    # Frame rate changes
+    {'fs': '320x240', 'fr': 24, 'kfr': 8, 'c': '#ff0000', 'vbr': 128, 'abr': 0, 'asr': 0, 'ach': 0, 'afreq': 0},
+    {'fs': '320x240', 'fr': 30, 'kfr': 10, 'c': '#ff0000', 'vbr': 128, 'abr': 0, 'asr': 0, 'ach': 0, 'afreq': 0},
+    # Frame size change
+    {'fs': '640x480', 'fr': 30, 'kfr': 10, 'c': '#00ff00', 'vbr': 128, 'abr': 0, 'asr': 0, 'ach': 0, 'afreq': 0},
+    # Bitrate change
+    {'fs': '320x240', 'fr': 30, 'kfr': 10, 'c': '#ff00ff', 'vbr': 256, 'abr': 0, 'asr': 0, 'ach': 0, 'afreq': 0},
+
+    ## Audio-only files
+    # Bitrate/Codebook changes
+    {'fs': '0x0', 'fr': 0, 'kfr': 0, 'c': '#000000', 'vbr': 0, 'abr': 128, 'asr': 44100, 'ach': 1, 'afreq': 2000},
+    {'fs': '0x0', 'fr': 0, 'kfr': 0, 'c': '#000000', 'vbr': 0, 'abr': 192, 'asr': 44100, 'ach': 1, 'afreq': 4000},
+
+    ## Audio-Video files
+    # Frame size change.
+    {'fs': '320x240', 'fr': 30, 'kfr': 10, 'c': '#ff0000', 'vbr': 256, 'abr': 128, 'asr': 44100, 'ach': 1, 'afreq': 2000},
+    {'fs': '640x480', 'fr': 30, 'kfr': 10, 'c': '#00ff00', 'vbr': 256, 'abr': 128, 'asr': 44100, 'ach': 1, 'afreq': 2000},
+    # Audio bitrate change.
+    {'fs': '640x480', 'fr': 30, 'kfr': 10, 'c': '#00ff00', 'vbr': 256, 'abr': 192, 'asr': 44100, 'ach': 1, 'afreq': 4000},
+    # Video bitrate change.
+    {'fs': '640x480', 'fr': 30, 'kfr': 10, 'c': '#00ffff', 'vbr': 512, 'abr': 128, 'asr': 44100, 'ach': 1, 'afreq': 2000},
+]
+
+CONFIG_CHANGE_TESTS = [
+    ["v-framerate", 0, 1, "Tests %s video-only frame rate changes."],
+    ["v-framesize", 1, 2, "Tests %s video-only frame size changes."],
+    ["v-bitrate", 1, 3, "Tests %s video-only bitrate changes."],
+    ["a-bitrate", 4, 5, "Tests %s audio-only bitrate changes."],
+    ["av-framesize", 6, 7, "Tests %s frame size changes in multiplexed content."],
+    ["av-audio-bitrate", 7, 8, "Tests %s audio bitrate changes in multiplexed content."],
+    ["av-video-bitrate", 7, 9, "Tests %s video bitrate changes in multiplexed content."]
+]
+
+CODEC_INFO = {
+    "mp4": {"audio": "mp4a.40.2", "video": "avc1.4D4001"},
+    "webm": {"audio": "vorbis", "video": "vp8"}
+}
+
+HTML_TEMPLATE = """<!DOCTYPE html>
+<html>
+    <head>
+        <script src="/w3c/resources/testharness.js"></script>
+        <script src="/w3c/resources/testharnessreport.js"></script>
+        <script src="mediasource-util.js"></script>
+        <script src="mediasource-config-changes.js"></script>
+    </head>
+    <body>
+        <div id="log"></div>
+        <script>
+            mediaSourceConfigChangeTest("%(media_format)s", "%(idA)s", "%(idB)s", "%(description)s");
+        </script>
+    </body>
+</html>
+"""
+
+def run(cmd_line):
+    os.system(" ".join(cmd_line))
+
+def generate_manifest(filename, media_filename, media_format, has_audio, has_video):
+    major_type = "audio"
+    if has_video:
+        major_type = "video"
+
+    codecs = []
+    if has_video:
+        codecs.append(CODEC_INFO[media_format]["video"])
+
+    if has_audio:
+        codecs.append(CODEC_INFO[media_format]["audio"])
+
+    mimetype = "%s/%s;codecs=\"%s\"" % (major_type, media_format, ",".join(codecs))
+
+    manifest = { 'url': media_filename, 'type': mimetype}
+
+    f = open(filename, "wb")
+    f.write(json.dumps(manifest, indent=4, separators=(',', ': ')))
+    f.close()
+
+def generate_test_html(media_format, config_change_tests, encoding_ids):
+    for test_info in config_change_tests:
+        filename = "../../media-source/mediasource-config-change-%s-%s.html" % (media_format, test_info[0])
+        html = HTML_TEMPLATE % {'media_format': media_format,
+                                 'idA': encoding_ids[test_info[1]],
+                                 'idB': encoding_ids[test_info[2]],
+                                 'description':  test_info[3] % (media_format)}
+        f = open(filename, "wb")
+        f.write(html)
+        f.close()
+
+
+def main():
+    encoding_ids = []
+
+    for media_format in MEDIA_FORMATS:
+        run(["mkdir ", media_format])
+
+        for settings in ENCODE_SETTINGS:
+            video_bitrate = settings['vbr']
+            has_video = (video_bitrate > 0)
+
+            audio_bitrate = settings['abr']
+            has_audio = (audio_bitrate > 0)
+            bitrate = video_bitrate + audio_bitrate
+
+            frame_size = settings['fs']
+            frame_rate = settings['fr']
+            keyframe_rate = settings['kfr']
+            color = settings['c']
+
+            sample_rate = settings['asr']
+            channels = settings['ach']
+            frequency = settings['afreq']
+
+            cmdline = ["ffmpeg", "-y"]
+
+            id_prefix = ""
+            id_params = ""
+            if has_audio:
+                id_prefix += "a"
+                id_params += "-%sHz-%sch" % (sample_rate, channels)
+
+                channel_layout = "FC"
+                sin_func = "sin(%s*2*PI*t)" % frequency
+                func = sin_func
+                if channels == 2:
+                    channel_layout += "|BC"
+                    func += "|" + sin_func
+
+                cmdline += ["-f", "lavfi", "-i", "aevalsrc=\"%s:s=%s:c=%s:d=%s\"" % (func, sample_rate, channel_layout, DURATION)]
+
+            if has_video:
+                id_prefix += "v"
+                id_params += "-%s-%sfps-%skfr" % (frame_size, frame_rate, keyframe_rate)
+
+                cmdline += ["-f", "lavfi", "-i", "color=%s:duration=%s:size=%s:rate=%s" % (color, DURATION, frame_size, frame_rate)]
+
+            if has_audio:
+                cmdline += ["-b:a", "%sk" % audio_bitrate]
+
+            if has_video:
+                cmdline += ["-b:v", "%sk" % video_bitrate]
+                cmdline += ["-keyint_min", "%s" % keyframe_rate]
+                cmdline += ["-g", "%s" % keyframe_rate]
+
+
+                textOverlayInfo = "'drawtext=fontfile=Mono:fontsize=32:text=Time\\\\:\\\\ %{pts}"
+                textOverlayInfo += ",drawtext=fontfile=Mono:fontsize=32:y=32:text=Size\\\\:\\\\ %s" % (frame_size)
+                textOverlayInfo += ",drawtext=fontfile=Mono:fontsize=32:y=64:text=Bitrate\\\\:\\\\ %s" % (bitrate)
+                textOverlayInfo += ",drawtext=fontfile=Mono:fontsize=32:y=96:text=FrameRate\\\\:\\\\ %s" % (frame_rate)
+                textOverlayInfo += ",drawtext=fontfile=Mono:fontsize=32:y=128:text=KeyFrameRate\\\\:\\\\ %s" % (keyframe_rate)
+
+                if has_audio:
+                    textOverlayInfo += ",drawtext=fontfile=Mono:fontsize=32:y=160:text=SampleRate\\\\:\\\\ %s" % (sample_rate)
+                    textOverlayInfo += ",drawtext=fontfile=Mono:fontsize=32:y=192:text=Channels\\\\:\\\\ %s" % (channels)
+
+                textOverlayInfo += "'"
+                cmdline += ["-vf", textOverlayInfo]
+
+            encoding_id = "%s-%sk%s" % (id_prefix, bitrate, id_params)
+
+            if len(encoding_ids) < len(ENCODE_SETTINGS):
+                encoding_ids.append(encoding_id)
+
+            filename_base = "%s/test-%s" % (media_format, encoding_id)
+            media_filename = filename_base + "." + media_format
+            manifest_filename = filename_base + "-manifest.json"
+
+            cmdline.append(media_filename)
+            run(cmdline)
+
+            # Remux file so it conforms to MSE bytestream requirements.
+            if media_format == "webm":
+                tmp_filename = media_filename + ".tmp"
+                run(["mse_webm_remuxer", media_filename, tmp_filename])
+                run(["mv", tmp_filename, media_filename])
+            elif media_format == "mp4":
+                run(["MP4Box", "-dash", "250", "-rap", media_filename])
+                run(["mv", filename_base + "_dash.mp4", media_filename])
+                run(["rm", filename_base + "_dash.mpd"])
+
+            generate_manifest(manifest_filename, media_filename, media_format, has_audio, has_video)
+        generate_test_html(media_format, CONFIG_CHANGE_TESTS, encoding_ids)
+
+if '__main__' == __name__:
+    main()
diff --git a/media-source/mediasource-addsourcebuffer.html b/media-source/mediasource-addsourcebuffer.html
index 9fc9463..7f6aa2a 100644
--- a/media-source/mediasource-addsourcebuffer.html
+++ b/media-source/mediasource-addsourcebuffer.html
@@ -21,7 +21,7 @@
 
           mediasource_test(function(test, mediaElement, mediaSource)
           {
-              assert_throws(new TypeError(),
+              assert_throws({name: "TypeError"},
                           function() { mediaSource.addSourceBuffer(""); },
                           "addSourceBuffer() threw an exception when passed an empty string.");
               test.done();
@@ -43,7 +43,6 @@
               test.done();
           }, "Test addSourceBuffer() with unsupported type");
 
-
           mediasource_test(function(test, mediaElement, mediaSource)
           {
               var mimetype = 'video/webm;codecs="vp8,vorbis"';
@@ -83,7 +82,7 @@
               var sourceBuffer = mediaSource.addSourceBuffer(mimetype);
               assert_true(sourceBuffer != null, "New SourceBuffer returned");
               assert_equals(mediaSource.sourceBuffers[0], sourceBuffer, "SourceBuffer is in mediaSource.sourceBuffers");
-              assert_equals(mediaSource.activeSourceBuffers.length, 0, "SourceBuffer is in mediaSource.activeSourceBuffers");
+              assert_equals(mediaSource.activeSourceBuffers.length, 0, "SourceBuffer is not in mediaSource.activeSourceBuffers");
               test.done();
           }, "Test addSourceBuffer() video only");
 
@@ -96,7 +95,7 @@
               var sourceBuffer = mediaSource.addSourceBuffer(mimetype);
               assert_true(sourceBuffer != null, "New SourceBuffer returned");
               assert_equals(mediaSource.sourceBuffers[0], sourceBuffer, "SourceBuffer is in mediaSource.sourceBuffers");
-              assert_equals(mediaSource.activeSourceBuffers.length, 0, "SourceBuffer is in mediaSource.activeSourceBuffers");
+              assert_equals(mediaSource.activeSourceBuffers.length, 0, "SourceBuffer is not in mediaSource.activeSourceBuffers");
               test.done();
           }, "Test addSourceBuffer() audio only");
 
@@ -129,26 +128,6 @@
               assert_equals(mediaSource.activeSourceBuffers.length, 0, "SourceBufferB is not in mediaSource.activeSourceBuffers");
               test.done();
           }, "Test addSourceBuffer() with AAC and H.264 in separate SourceBuffers");
-
-          mediasource_test(function(test, mediaElement, mediaSource)
-          {
-              var reachedLimit = false;
-
-             // The 20 here is an arbitrary upper limit to make sure the test terminates. This test
-             // assumes that implementations won't support more than 20 SourceBuffers simultaneously.
-             for (var i = 0; i < 20; ++i) {
-                 try {
-                     mediaSource.addSourceBuffer(MediaSourceUtil.AUDIO_VIDEO_TYPE);
-                 } catch(e) {
-                     assert_equals(e.name, "QuotaExceededError");
-                     reachedLimit = true;
-                     break;
-                 }
-             }
-             assert_true(reachedLimit, "Reached SourceBuffer limit.");
-             test.done();
-          }, "Test addSourceBuffer() QuotaExceededError.");
-
         </script>
     </body>
 </html>
diff --git a/media-source/mediasource-config-changes.js b/media-source/mediasource-config-changes.js
index ea99b8b..b28aa90 100644
--- a/media-source/mediasource-config-changes.js
+++ b/media-source/mediasource-config-changes.js
@@ -68,9 +68,10 @@
                     assert_false(sourceBuffer.updating, "updating");
 
                     // Truncate the presentation to a duration of 2 seconds.
+                    // First, explicitly remove the media beyond 2 seconds.
                     sourceBuffer.remove(2, Infinity);
 
-                    assert_true(sourceBuffer.updating, "updating");
+                    assert_true(sourceBuffer.updating, "sourceBuffer.updating during range removal");
                     test.expectEvent(sourceBuffer, 'updatestart', 'sourceBuffer');
                     test.expectEvent(sourceBuffer, 'update', 'sourceBuffer');
                     test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
@@ -78,11 +79,13 @@
 
                 test.waitForExpectedEvents(function()
                 {
-                    assert_false(sourceBuffer.updating, "updating");
+                    assert_false(sourceBuffer.updating, "sourceBuffer.updating prior to duration reduction");
                     assert_greater_than(mediaSource.duration, 2, "duration");
 
-                    // Truncate the presentation to a duration of 2 seconds.
+                    // Complete the truncation of presentation to 2 second
+                    // duration.
                     mediaSource.duration = 2;
+                    assert_false(sourceBuffer.updating, "sourceBuffer.updating synchronously after duration reduction");
 
                     test.expectEvent(mediaElement, "durationchange");
                 });
diff --git a/media-source/mediasource-endofstream.html b/media-source/mediasource-endofstream.html
index bca80af..5b6114f 100644
--- a/media-source/mediasource-endofstream.html
+++ b/media-source/mediasource-endofstream.html
@@ -52,7 +52,8 @@
             // Note that segmentInfo.duration is expected to also be the
             // highest track buffer range end time. Therefore, endOfStream() should
             // not change duration with this media.
-            assert_equals(threeDecimalPlaces(segmentInfo.duration), threeDecimalPlaces(mediaSource.duration));
+            assert_equals(threeDecimalPlaces(segmentInfo.duration), threeDecimalPlaces(mediaSource.duration),
+                'SegmentInfo duration should initially roughly match mediaSource duration');
             assert_less_than_equal(highestEndTime, mediaSource.duration,
                 'Media duration may be slightly longer than intersected track buffered ranges');
 
@@ -64,7 +65,8 @@
             assert_equals(sourceBuffer.buffered.length, 1,
               'Media data properly buffered after endOfStream');
 
-            assert_equals(threeDecimalPlaces(segmentInfo.duration), threeDecimalPlaces(mediaSource.duration));
+            assert_equals(threeDecimalPlaces(segmentInfo.duration), threeDecimalPlaces(mediaSource.duration),
+                'SegmentInfo duration should still roughly match mediaSource duration');
             assert_less_than_equal(highestEndTime, mediaSource.duration,
                 'Media duration may be slightly longer than intersected track buffered ranges');
             assert_equals(sourceBuffer.buffered.end(0), mediaSource.duration,
diff --git a/media-source/mediasource-is-type-supported.html b/media-source/mediasource-is-type-supported.html
index 985e206..a364693 100644
--- a/media-source/mediasource-is-type-supported.html
+++ b/media-source/mediasource-is-type-supported.html
@@ -5,7 +5,6 @@
         <title>MediaSource.isTypeSupported() test cases.</title>
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
-        <script src="mediasource-util.js"></script>
     </head>
     <body>
         <div id="log"></div>
@@ -32,6 +31,7 @@
               'video/webm;codecs="',
               'video/webm;codecs=""',
               'video/webm;codecs=","',
+              'unsupported_mediatype',
               '',
               null
           ], false, 'Test invalid MIME format');
@@ -45,15 +45,12 @@
           test_type_support([
               'audio/webm;codecs="vp8"',
               'audio/mp4;codecs="avc1.4d001e"',
-          ], false, 'Test invalid mismatch between major type and codec ID');
-
-          test_type_support([
               'audio/mp4;codecs="vorbis"',
               'audio/webm;codecs="mp4a.40.2"',
               'video/mp4;codecs="vp8"',
-              'video/webm;codecs="mp4a.40.2"',
               'video/mp4;codecs="vorbis"',
-          ], false, 'Test invalid mismatch between minor type and codec ID');
+              'video/webm;codecs="mp4a.40.2"',
+          ], false, 'Test invalid mismatch between MIME type and codec ID');
 
           test_type_support([
               'audio/mp4;codecs="mp4a"',
diff --git a/media-source/mediasource-play.html b/media-source/mediasource-play.html
index 5bbfa29..62fb046 100644
--- a/media-source/mediasource-play.html
+++ b/media-source/mediasource-play.html
@@ -19,15 +19,20 @@
               test.expectEvent(sourceBuffer, 'update', 'sourceBuffer');
               test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
 
+              assert_false(sourceBuffer.updating, "sourceBuffer.updating");
+
               sourceBuffer.appendBuffer(mediaData);
 
+              assert_true(sourceBuffer.updating, "sourceBuffer.updating");
+
               test.waitForExpectedEvents(function()
               {
-                  assert_false(sourceBuffer.updating, "updating");
+                  assert_false(sourceBuffer.updating, "sourceBuffer.updating");
 
-                  sourceBuffer.remove(1, Infinity);
+                  // Truncate the buffered media to about 1 second duration.
+                  sourceBuffer.remove(1, +Infinity);
 
-                  assert_true(sourceBuffer.updating, "updating");
+                  assert_true(sourceBuffer.updating, "sourceBuffer.updating");
                   test.expectEvent(sourceBuffer, 'updatestart', 'sourceBuffer');
                   test.expectEvent(sourceBuffer, 'update', 'sourceBuffer');
                   test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
@@ -38,6 +43,7 @@
                   assert_false(sourceBuffer.updating, "updating");
                   assert_greater_than(mediaSource.duration, 1, "duration");
 
+                  // Complete truncation of duration to 1 second.
                   mediaSource.duration = 1;
 
                   test.expectEvent(mediaElement, "durationchange");
diff --git a/media/1x1-green.png b/media/1x1-green.png
new file mode 100644
index 0000000..b98ca0b
--- /dev/null
+++ b/media/1x1-green.png
Binary files differ
diff --git a/media/counting.mp4 b/media/counting.mp4
new file mode 100644
index 0000000..5fbd6d9
--- /dev/null
+++ b/media/counting.mp4
Binary files differ
diff --git a/media/counting.ogv b/media/counting.ogv
new file mode 100644
index 0000000..ce03c19
--- /dev/null
+++ b/media/counting.ogv
Binary files differ
diff --git a/media/foo-no-cors.vtt b/media/foo-no-cors.vtt
new file mode 100644
index 0000000..b533895
--- /dev/null
+++ b/media/foo-no-cors.vtt
@@ -0,0 +1,4 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:05.000
+Foo
diff --git a/media/test.mp4 b/media/test.mp4
new file mode 100644
index 0000000..d278c8a
--- /dev/null
+++ b/media/test.mp4
Binary files differ
diff --git a/media/test.ogv b/media/test.ogv
new file mode 100644
index 0000000..0c55f6c
--- /dev/null
+++ b/media/test.ogv
Binary files differ
diff --git a/mediacapture-fromelement/OWNERS b/mediacapture-fromelement/OWNERS
new file mode 100644
index 0000000..496af7c
--- /dev/null
+++ b/mediacapture-fromelement/OWNERS
@@ -0,0 +1,3 @@
+@yellowdoge
+@martinthomson
+@uysalere
diff --git a/mediacapture-image/OWNERS b/mediacapture-image/OWNERS
new file mode 100644
index 0000000..32380f1
--- /dev/null
+++ b/mediacapture-image/OWNERS
@@ -0,0 +1,2 @@
+@yellowdoge
+@reillyeon
diff --git a/mediacapture-record/idlharness.html b/mediacapture-record/idlharness.html
index 95f60d7..2da20a1 100644
--- a/mediacapture-record/idlharness.html
+++ b/mediacapture-record/idlharness.html
@@ -80,19 +80,24 @@
 
   </pre>
   <script>
-    var canvas = document.getElementById('canvas');
-    var context = canvas.getContext("2d");
-    context.fillStyle = "red";
-    context.fillRect(0, 0, 10, 10);
-    var stream = canvas.captureStream();
+    promise_test(async function() {
+      const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
 
-    var idl_array = new IdlArray();
-    idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
-    idl_array.add_idls(document.getElementById("idl").textContent);
-    idl_array.add_objects({
-        MediaRecorder: [new MediaRecorder(stream)],
-      });
-    idl_array.test();
+      var canvas = document.getElementById('canvas');
+      var context = canvas.getContext("2d");
+      context.fillStyle = "red";
+      context.fillRect(0, 0, 10, 10);
+      var stream = canvas.captureStream();
+
+      var idl_array = new IdlArray();
+      idl_array.add_untested_idls(dom, { only: ['EventInit'] });
+      idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
+      idl_array.add_idls(document.getElementById("idl").textContent);
+      idl_array.add_objects({
+          MediaRecorder: [new MediaRecorder(stream)],
+        });
+      idl_array.test();
+    }, 'mediacapture-record interfaces');
   </script>
   <div id="log"></div>
 </body>
diff --git a/mediacapture-streams/MediaDevices-IDL-all.html b/mediacapture-streams/MediaDevices-IDL-all.html
index f6dfb36..a793bed 100644
--- a/mediacapture-streams/MediaDevices-IDL-all.html
+++ b/mediacapture-streams/MediaDevices-IDL-all.html
@@ -19,10 +19,11 @@
     <script>
       'use strict';
 
-      function doIdlTest(idlText) {
+      function doIdlTest([dom, idlText]) {
         var idl_array = new IdlArray();
 
         // dummies
+        idl_array.add_untested_idls(dom, { only: ['Event', 'EventInit']});
         idl_array.add_untested_idls("interface Navigator {};");
         idl_array.add_untested_idls("interface EventTarget {};");
         idl_array.add_untested_idls("interface EventHandler {};");
@@ -35,10 +36,12 @@
       }
 
       promise_test(() => {
-        return fetch('/interfaces/mediacapture-main.idl')
-          .then(response => response.text())
-          .then(doIdlTest);
-
+        return Promise.all(
+          [
+            '/interfaces/dom.idl',
+            '/interfaces/mediacapture-main.idl',
+          ].map(url => fetch(url).then(r => r.text())))
+            .then(doIdlTest);
       }, 'Test driver')
     </script>
   </body>
diff --git a/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html b/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html
index 4dc1c60..0d6c6c0 100644
--- a/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html
+++ b/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html
@@ -20,9 +20,11 @@
 <script>
   "use strict";
 
-  function doIdlTest(idlText) {
+  function doIdlTest([dom, idlText]) {
     const MDI_idl = new IdlArray();
 
+    MDI_idl.add_untested_idls(dom, { only: ['Event', 'EventInit'] });
+    MDI_idl.add_untested_idls("interface EventTarget {};");
     MDI_idl.add_untested_idls("interface Navigator {};");
     MDI_idl.add_idls(idlText);
 
@@ -32,15 +34,20 @@
     return navigator.mediaDevices.enumerateDevices()
     .then(function(list) {
       if( list.length > 0 ) {
-        window._mediaInfo = list[0];
-        MDI_idl.add_objects({MediaDeviceInfo: ["_mediaInfo"]});
+        var kind = list[0].kind;
+        if (kind == "audioinput" ||
+            kind == "videoinput") {
+          MDI_idl.add_objects({InputDeviceInfo: [list[0]]});
+        } else if (kind == "audiooutput" ) {
+          MDI_idl.add_objects({MediaDeviceInfo: [list[0]]});
+        }
       }
 
       for(const media of list) {
-        if( media.kind == "audioinput" ||
+        if (media.kind == "audioinput" ||
             media.kind == "videoinput") {
           // TODO -- Check InputDeviceInfo IDL, getCapabilities()
-        } else if ( media.kind == "audiooutput" ) {
+        } else if (media.kind == "audiooutput" ) {
           // TODO -- pass
         } else {
           assert_unreached("media.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.")
@@ -52,8 +59,11 @@
   }
 
   promise_test(() => {
-    return fetch('/interfaces/mediacapture-main.idl')
-      .then(response => response.text())
+      return Promise.all(
+        [
+          '/interfaces/dom.idl',
+          '/interfaces/mediacapture-main.idl',
+        ].map(url => fetch(url).then(r => r.text())))
       .then(doIdlTest);
 
   }, "Test MediaDevices.enumerateDevices call and result. Types only.");
diff --git a/mediacapture-streams/MediaDevices-enumerateDevices.https.html b/mediacapture-streams/MediaDevices-enumerateDevices.https.html
index 806416e..c66251a 100644
--- a/mediacapture-streams/MediaDevices-enumerateDevices.https.html
+++ b/mediacapture-streams/MediaDevices-enumerateDevices.https.html
@@ -16,32 +16,65 @@
 <script>
 "use strict";
 //NOTE ALEX: for completion, a test for ondevicechange event is missing.
-test(function () {
+promise_test(function() {
   assert_true(undefined !== navigator.mediaDevices.enumerateDevices, "navigator.mediaDevices.enumerateDevices exists");
-  var p = navigator.mediaDevices.enumerateDevices()
-  p.then(function(list){
-    for(let mediainfo of list){
-      // TODO check the type of mediainfo
+  return navigator.mediaDevices.enumerateDevices().then(function(list) {
+    for (let mediainfo of list) {
       assert_true(undefined !== mediainfo.deviceId, "mediaInfo's deviceId should exist.");
       assert_true(undefined !== mediainfo.kind,     "mediaInfo's kind     should exist.");
       assert_true(undefined !== mediainfo.label,    "mediaInfo's label    should exist.");
       assert_true(undefined !== mediainfo.groupId,  "mediaInfo's groupId  should exist.");
       // TODO the values of some of those fields should be empty string by default if no permission has been requested.
-      if( mediainfo.kind == "audioinput" ||
-          mediainfo.kind == "videoinput") {
-        // NOTE ALEX: looks like nobody has implemented that. How can I make it a separate test,
-        // ...        to have better granularity?
-        // assert_true(undefined !== mediainfo.getCapabilities(), "MediaDeviceInfo.getCapabilities() exists.");
-        // var cap = mediainfo.getcapabilities();
-      } else if ( mediainfo.kind !== "audiooutput" ) {
+      if ( mediainfo.kind == "audioinput" || mediainfo.kind == "videoinput") {
+        assert_true(mediainfo instanceof InputDeviceInfo);
+        var capabilities = mediainfo.getCapabilities();
+        assert_equals(typeof capabilities, "object", "capabilities must be an object.");
+        assert_equals(typeof capabilities.deviceId, "string", "deviceId must be a string.");
+        assert_equals(typeof capabilities.groupId, "string", "groupId must be a string.");
+        if (mediainfo.kind == "audioinput") {
+          assert_equals(typeof capabilities.echoCancellation, "object", "echoCancellation must be an object.");
+          assert_equals(typeof capabilities.autoGainControl, "object", "autoGainControl must be an object.");
+          assert_equals(typeof capabilities.noiseSuppression, "object", "noiseSuppression must be an object.");
+        }
+        if (mediainfo.kind == "videoinput") {
+          assert_equals(typeof capabilities.facingMode, "object", "facingMode must be an object.");
+          verifyVideoRangeProperties(capabilities);
+        }
+      } else if ( mediainfo.kind == "audiooutput" ) {
+        assert_true(mediainfo instanceof MediaDeviceInfo);
+      } else {
         assert_unreached("mediainfo.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.")
       }
     }
-  })
-  p.catch(function(err){
-    assert_unreached("A call to enumerateDevices() should never fail.");
   });
 }, "mediaDevices.enumerateDevices() is present and working on navigator");
+
+function verifyVideoRangeProperties(capabilities) {
+  if (capabilities.hasOwnProperty('width')) {
+      assert_equals(Object.keys(capabilities.width).length, 2);
+      assert_true(capabilities.width.hasOwnProperty('min'));
+      assert_true(capabilities.width.hasOwnProperty('max'));
+      assert_less_than_equal(capabilities.width.min, capabilities.width.max);
+  }
+  if (capabilities.hasOwnProperty('height')) {
+    assert_equals(Object.keys(capabilities.height).length, 2);
+    assert_true(capabilities.height.hasOwnProperty('min'));
+    assert_true(capabilities.height.hasOwnProperty('max'));
+    assert_less_than_equal(capabilities.height.min, capabilities.height.max);
+  }
+  if (capabilities.hasOwnProperty('aspectRatio')) {
+    assert_equals(Object.keys(capabilities.aspectRatio).length, 2);
+    assert_true(capabilities.aspectRatio.hasOwnProperty('min'));
+    assert_true(capabilities.aspectRatio.hasOwnProperty('max'));
+    assert_less_than_equal(capabilities.aspectRatio.min, capabilities.aspectRatio.max);
+  }
+  if (capabilities.hasOwnProperty('frameRate')) {
+    assert_equals(Object.keys(capabilities.frameRate).length, 2);
+    assert_true(capabilities.frameRate.hasOwnProperty('min'));
+    assert_true(capabilities.frameRate.hasOwnProperty('max'));
+    assert_less_than_equal(capabilities.frameRate.min, capabilities.frameRate.max);
+  }
+}
 </script>
 </body>
 </html>
diff --git a/mediacapture-streams/MediaDevices-getUserMedia.https.html b/mediacapture-streams/MediaDevices-getUserMedia.https.html
index 0573ab0..0a2f2ff 100644
--- a/mediacapture-streams/MediaDevices-getUserMedia.https.html
+++ b/mediacapture-streams/MediaDevices-getUserMedia.https.html
@@ -36,6 +36,34 @@
   //   list.deviceId
   //   list.groupId
   }, "mediaDevices.getUserMedia() is present on navigator");
+
+promise_test(t => {
+  return navigator.mediaDevices.enumerateDevices()
+    .then(t.step_func(async devices => {
+      for (var i in devices) {
+        await navigator.mediaDevices.getUserMedia(
+          {video: {groupId: {exact: devices[i].groupId}}})
+            .then(
+              t.step_func(stream => {
+                var found_device = devices.find(element => {
+                  return element.deviceId ==
+                           stream.getTracks()[0].getSettings().deviceId;
+                });
+                assert_true(undefined !== found_device);
+                assert_equals(found_device.kind, "videoinput");
+                assert_equals(found_device.groupId, devices[i].groupId);
+              }),
+              t.step_func(error => {
+                assert_equals(error.name, "OverconstrainedError");
+                assert_equals(error.constraint, "groupId");
+                var found_device = devices.find(element => {
+                  return element.kind == "videoinput" &&
+                        element.groupId == devices[i].groupId});
+                assert_true(undefined === found_device);
+              }));
+      }
+    }));
+}, 'groupId is correctly supported by getUserMedia() for video devices');
 </script>
 </body>
 </html>
diff --git a/mediacapture-streams/MediaStream-MediaElement-preload-none-manual.https.html b/mediacapture-streams/MediaStream-MediaElement-preload-none-manual.https.html
new file mode 100644
index 0000000..758a273
--- /dev/null
+++ b/mediacapture-streams/MediaStream-MediaElement-preload-none-manual.https.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Test that the HTMLMediaElement preload 'none' attribute value is ignored for MediaStream used as srcObject and MediaStream object URLs used as src.</title>
+        <link rel="author" title="Matthew Wolenetz" href="mailto:wolenetz@chromium.org"/>
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+    </head>
+    <body>
+        <p class="instructions">When prompted, accept to share your audio and video streams.</p>
+        <p class="instructions">This test checks that the HTMLMediaElement preload 'none' attribute value is ignored for MediaStream used as srcObject and MediaStream object URLs used as src.</p>
+        <div id=log></div>
+
+        <audio preload="none"></audio>
+        <video preload="none"></video>
+
+        <script>
+            function testPreloadNone(t, mediaElement, setSourceStreamFunc)
+            {
+                // The optional deferred load steps (for preload none) for MediaStream resources should be skipped.
+                mediaElement.addEventListener("suspend", t.unreached_func("'suspend' should not be fired."));
+                mediaElement.addEventListener("error", t.step_func(function() {
+                  assert_unreached("'error' should not be fired, code=" + mediaElement.error.code);
+                }));
+
+                mediaElement.addEventListener("loadeddata", t.step_func(function()
+                {
+                    assert_equals(mediaElement.networkState, mediaElement.NETWORK_LOADING);
+                    t.done();
+                }));
+
+                setSourceStreamFunc();
+                assert_equals(mediaElement.networkState, mediaElement.NETWORK_NO_SOURCE); // Resource selection is active.
+            }
+
+            async_test(function(t)
+            {
+                var aud = document.querySelector("audio");
+                navigator.mediaDevices.getUserMedia({audio:true})
+                  .then(t.step_func(function(stream)
+                  {
+                      testPreloadNone(t, aud, t.step_func(function() { aud.srcObject = stream; }));
+                  }),
+                  t.unreached_func("getUserMedia error callback was invoked."));
+            }, "Test that preload 'none' is ignored for MediaStream object URL used as srcObject for audio");
+
+            async_test(function(t)
+            {
+                var vid = document.querySelector("video");
+                navigator.mediaDevices.getUserMedia({video:true})
+                  .then(t.step_func(function(stream)
+                  {
+                      testPreloadNone(t, vid, t.step_func(function() { vid.srcObject = stream; }));
+                  }), t.unreached_func("getUserMedia error callback was invoked."));
+            }, "Test that preload 'none' is ignored for MediaStream used as srcObject for video");
+        </script>
+    </body>
+</html>
diff --git a/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html b/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html
deleted file mode 100644
index 6015bb7..0000000
--- a/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<!DOCTYPE html>
-<!-- Copyright © 2016 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
-<html>
-    <head>
-        <title>Test that the HTMLMediaElement preload 'none' attribute value is ignored for MediaStream used as srcObject and MediaStream object URLs used as src.</title>>
-        <link rel="author" title="Matthew Wolenetz" href="mailto:wolenetz@chromium.org"/>
-        <script src="/resources/testharness.js"></script>
-        <script src="/resources/testharnessreport.js"></script>
-    </head>
-    <body>
-        <p class="instructions">When prompted, accept to share your audio and video streams.</p>
-        <h1 class="instructions">Description</h1>
-        <p class="instructions">This test checks that the HTMLMediaElement preload 'none' attribute value is ignored for MediaStream used as srcObject and MediaStream object URLs used as src.</p>
-
-        <audio preload="none"></audio>
-        <video preload="none"></video>
-
-        <script>
-            function testPreloadNone(t, mediaElement, setSourceStreamFunc)
-            {
-                // The optional deferred load steps (for preload none) for MediaStream resources should be skipped.
-                mediaElement.addEventListener("suspend", t.unreached_func("'suspend' should not be fired."));
-                mediaElement.addEventListener("error", t.step_func(function() {
-                  assert_unreached("'error' should not be fired, code=" + mediaElement.error.code);
-                }));
-
-                mediaElement.addEventListener("loadeddata", t.step_func(function()
-                {
-                    assert_equals(mediaElement.networkState, mediaElement.NETWORK_LOADING);
-                    t.done();
-                }));
-
-                setSourceStreamFunc();
-                assert_equals(mediaElement.networkState, mediaElement.NETWORK_NO_SOURCE); // Resource selection is active.
-            }
-
-            async_test(function(t)
-            {
-                var aud = document.querySelector("audio");
-                navigator.mediaDevices.getUserMedia({audio:true})
-                  .then(t.step_func(function(stream)
-                  {
-                      testPreloadNone(t, aud, t.step_func(function()
-                      {
-                          aud.src = URL.createObjectURL(stream);
-                          t.add_cleanup(function() { URL.revokeObjectURL(aud.src); });
-                      }));
-                  }),
-                  t.unreached_func("getUserMedia error callback was invoked."));
-            }, "Test that preload 'none' is ignored for MediaStream object URL used as src");
-
-            async_test(function(t)
-            {
-                var vid = document.querySelector("video");
-                navigator.mediaDevices.getUserMedia({video:true})
-                  .then(t.step_func(function(stream)
-                  {
-                      testPreloadNone(t, vid, t.step_func(function() { vid.srcObject = stream; }));
-                  }), t.unreached_func("getUserMedia error callback was invoked."));
-            }, "Test that preload 'none' is ignored for MediaStream used as srcObject");
-        </script>
-    </body>
-</html>
diff --git a/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html b/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html
index 8aee8e2..9f44a34 100644
--- a/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html
+++ b/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html
@@ -46,7 +46,7 @@
       vid.srcObject = stream;
       vid.play();
       vid.addEventListener("loadeddata", testOnceLoadeddata, false);
-    })));
+    }));
 });
 </script>
 </body>
diff --git a/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html b/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html
new file mode 100644
index 0000000..0056695
--- /dev/null
+++ b/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<title>MediaStreamTrack applyConstraints</title>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+  'use strict'
+
+  // https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-applyconstraints
+
+  promise_test(t => {
+    return navigator.mediaDevices.getUserMedia({ video: true })
+      .then(t.step_func(stream => {
+        return stream.getVideoTracks()[0].applyConstraints(
+          { groupId: { exact: "INVALID" } }).then(
+            t.unreached_func('Accepted invalid groupID'),
+            t.step_func(e => {
+              assert_equals(e.name, 'OverconstrainedError');
+              assert_equals(e.constraint, 'groupId');
+            }));
+      }));
+  }, 'applyConstraints rejects invalid groupID');
+
+  promise_test(t => {
+    return navigator.mediaDevices.getUserMedia({ video: true })
+      .then(t.step_func(stream => {
+        var track = stream.getVideoTracks()[0];
+        var groupId = track.getSettings().groupId;
+        return track.applyConstraints({ groupId: "INVALID" }).then(
+          t.step_func(() => {
+            assert_equals(track.getSettings().groupId, groupId);
+          }));
+      }));
+  }, 'applyConstraints accepts invalid ideal groupID, does not change setting');
+
+  promise_test(t => {
+    return navigator.mediaDevices.getUserMedia({ video: true })
+      .then(t.step_func(stream => {
+        var track = stream.getVideoTracks()[0];
+        var groupId = track.getSettings().groupId;
+        return navigator.mediaDevices.enumerateDevices().then(devices => {
+          var anotherDevice = devices.find(device => {
+            return device.kind == "videoinput" && device.groupId != groupId;
+          });
+          if (anotherDevice !== undefined) {
+            return track.applyConstraints(
+              { groupId: { exact: anotherDevice.groupId } }).then(
+                t.unreached_func(),
+                t.step_func(e => {
+                  assert_equals(e.name, 'OverconstrainedError');
+                  assert_equals(e.constraint, 'groupId');
+                }));
+          }
+        });
+      }));
+  }, 'applyConstraints rejects attempt to switch device using groupId');
+</script>
diff --git a/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html b/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html
new file mode 100644
index 0000000..d928370
--- /dev/null
+++ b/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>MediaStreamTrack GetCapabilities</title>
+<p class="instructions">This test checks for the presence of audio and video properties in
+<code>MediaStreamTrack.getCapabilities()</code> method.</p>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+  promise_test(() => {
+  return navigator.mediaDevices.getUserMedia({audio: true, video: true})
+    .then(stream => {
+      var audioCapabilities = stream.getAudioTracks()[0].getCapabilities();
+      var videoCapabilities = stream.getVideoTracks()[0].getCapabilities();
+      assert_true(undefined !== audioCapabilities.deviceId, "MediaTrackCapabilities's deviceId should exist for an audio track.");
+      assert_true(undefined !== audioCapabilities.echoCancellation, "MediaTrackCapabilities's echoCancellation should exist for an audio track.");
+      assert_true(undefined !== audioCapabilities.autoGainControl, "MediaTrackCapabilities's autoGainControl should exist for an audio track.");
+      assert_true(undefined !== audioCapabilities.noiseSuppression, "MediaTrackCapabilities's noiseSuppression should exist for an audio track.");
+      assert_true(undefined !== videoCapabilities.deviceId, "MediaTrackCapabilities's deviceId should exist for a video track.");
+      assert_true(undefined !== videoCapabilities.groupId, "MediaTrackCapabilities's groupId should exist for a video track.");
+    });
+  });
+</script>
diff --git a/mediacapture-streams/MediaStreamTrack-getSettings.https.html b/mediacapture-streams/MediaStreamTrack-getSettings.https.html
index 42674ba..4c2feef 100644
--- a/mediacapture-streams/MediaStreamTrack-getSettings.https.html
+++ b/mediacapture-streams/MediaStreamTrack-getSettings.https.html
@@ -57,4 +57,22 @@
       });
     });
   }, 'A device can be opened twice with different resolutions');
+
+  promise_test(t => {
+    return navigator.mediaDevices.enumerateDevices().then(async devices => {
+      for (var device of devices) {
+        if (device.kind == "audiooutput")
+          continue;
+        var device_id_constraint = {deviceId: {exact: device.deviceId}};
+        var constraints = device.kind == "audioinput"
+          ? {audio: device_id_constraint}
+          : {video: device_id_constraint};
+
+        var stream = await navigator.mediaDevices.getUserMedia(constraints);
+        assert_equals(stream.getTracks()[0].getSettings().groupId,
+                      device.groupId);
+        assert_true(device.groupId.length > 0);
+      }
+    });
+  }, 'groupId is correctly reported by getSettings() for all devices');
 </script>
diff --git a/mediacapture-streams/historical.html b/mediacapture-streams/historical.html
index d9616f1..74efc75 100644
--- a/mediacapture-streams/historical.html
+++ b/mediacapture-streams/historical.html
@@ -15,4 +15,9 @@
 test(function() {
   assert_false("mozGetUserMedia" in navigator);
 }, "navigator.mozGetUserMedia should not exist");
+
+test(() => {
+  const mediaStream = new MediaStream();
+  assert_throws(new TypeError(), () => URL.createObjectURL(mediaStream));
+}, "Passing MediaStream to URL.createObjectURL() should throw");
 </script>
diff --git a/mediasession/OWNERS b/mediasession/OWNERS
new file mode 100644
index 0000000..b05a482
--- /dev/null
+++ b/mediasession/OWNERS
@@ -0,0 +1 @@
+@mounirlamouri
diff --git a/mediasession/idlharness.html b/mediasession/idlharness.html
index a90c83e..4991404 100644
--- a/mediasession/idlharness.html
+++ b/mediasession/idlharness.html
@@ -13,7 +13,7 @@
 <h1>Media Session IDL tests</h1>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global=Window, Exposed=Global]
 interface Window {
 };
 
diff --git a/mediasession/setactionhandler.html b/mediasession/setactionhandler.html
index 120686a..dc7c335 100644
--- a/mediasession/setactionhandler.html
+++ b/mediasession/setactionhandler.html
@@ -15,9 +15,7 @@
 
 test(function(t) {
   assert_throws(
-      new TypeError("Failed to execute 'setActionHandler' on 'MediaSession':" +
-                    "The provided value 'invalid' is not a valid enum value" +
-                    "of type MediaSessionAction."),
+      new TypeError,
       _ => { window.navigator.mediaSession.setActionHandler("invalid", null); });
 }, "Test that setActionHandler() throws exception for unsupported actions");
 
diff --git a/mimesniff/OWNERS b/mimesniff/OWNERS
new file mode 100644
index 0000000..e3fa6b6
--- /dev/null
+++ b/mimesniff/OWNERS
@@ -0,0 +1 @@
+@annevk
diff --git a/mimesniff/README.md b/mimesniff/README.md
new file mode 100644
index 0000000..8083431
--- /dev/null
+++ b/mimesniff/README.md
@@ -0,0 +1 @@
+Tests for the [MIME Sniffing Standard](https://mimesniff.spec.whatwg.org/).
diff --git a/mimesniff/mime-types/README.md b/mimesniff/mime-types/README.md
new file mode 100644
index 0000000..b0b1fbf
--- /dev/null
+++ b/mimesniff/mime-types/README.md
@@ -0,0 +1,34 @@
+== MIME types ==
+
+`resources/mime-types.json` and `resources/generated-mime-types.json` contain MIME type tests. The tests are encoded as a JSON array. String values in the array serve as documentation. All other values are objects with the following fields:
+
+* `input`: The string to be parsed.
+* `output`: Null if parsing resulted in failure and the MIME type record serialized as string otherwise.
+* `navigable`: True if the MIME type can be used for a document to be loaded in a browsing context (i.e., does not result in a download) and omitted otherwise.
+* `encoding`: The encoding that can be extracted from the MIME type or null if no encoding can be extracted, and omitted otherwise.
+
+Note: the object description implies that there tests without `navigable` or `encoding` set.
+
+A wrapper for these JSON MIME type tests needs to take care that not all `input` values can be tested in all entrypoints. Some entrypoints only accept bytes and some have further restrictions. A function such as the one below can be used to differentiate:
+
+```js
+function isByteCompatible(str) {
+  for(let i = 0; i < str.length; i++) {
+    const charCode = str.charCodeAt(i);
+    // See https://github.com/w3c/web-platform-tests/issues/8372 for 0x0B and 0x0C
+    // See https://fetch.spec.whatwg.org/#concept-header-value for the remainder
+    if(charCode > 0xFF) {
+      return "incompatible";
+    } else if(charCode === 0x00 || charCode === 0x0A || charCode === 0x0D) {
+      return "header-value-incompatible";
+    } else if(charCode === 0x0B || charCode === 0x0C) {
+      return "wptserve-incompatible";
+    }
+  }
+  return "compatible";
+}
+```
+
+`resources/generated-mime-types.json` is generated by running `resources/generated-mime-types.py`. Modify the latter to correct the former.
+
+These tests are used by resources in this directory to test various aspects of MIME types.
diff --git a/mimesniff/mime-types/charset-parameter.window.js b/mimesniff/mime-types/charset-parameter.window.js
new file mode 100644
index 0000000..7b4e946
--- /dev/null
+++ b/mimesniff/mime-types/charset-parameter.window.js
@@ -0,0 +1,57 @@
+promise_test(() => {
+  // Don't load generated-mime-types.json as none of them are navigable
+  return fetch("resources/mime-types.json").then(res => res.json().then(runTests));
+}, "Loading data…");
+
+function isByteCompatible(str) {
+  for(let i = 0; i < str.length; i++) {
+    const charCode = str.charCodeAt(i);
+    // See https://github.com/w3c/web-platform-tests/issues/8372 for 0x0B and 0x0C
+    // See https://fetch.spec.whatwg.org/#concept-header-value for the remainder
+    if(charCode > 0xFF) {
+      return "incompatible";
+    } else if(charCode === 0x00 || charCode === 0x0A || charCode === 0x0D) {
+      return "header-value-incompatible";
+    } else if(charCode === 0x0B || charCode === 0x0C) {
+      return "wptserve-incompatible";
+    }
+  }
+  return "compatible";
+}
+
+function encodeForURL(str) {
+  let output = "";
+  for(let i = 0; i < str.length; i++) {
+    const char = str.charCodeAt(i);
+    if(char > 0xFF) {
+      throw new Error("We cannot deal with input that is not latin1");
+    } else  {
+      output += "%" + char.toString(16).padStart(2, "0");
+    }
+  }
+  return output;
+}
+
+function runTests(tests) {
+  tests.forEach(val => {
+    if(typeof val === "string" || val.navigable === undefined || val.encoding === undefined || isByteCompatible(val.input) !== "compatible") {
+      return;
+    }
+    const mime = val.input;
+    async_test(t => {
+      const frame = document.createElement("iframe"),
+            expectedEncoding = val.encoding === null ? "UTF-8" : val.encoding;
+      t.add_cleanup(() => frame.remove());
+      frame.onload = t.step_func(() => {
+        if(frame.contentWindow.location.href === "about:blank") {
+          return;
+        }
+        // Edge fails all these tests due to not using the correct encoding label.
+        assert_equals(frame.contentDocument.characterSet, expectedEncoding);
+        t.done();
+      });
+      frame.src = "resources/mime-charset.py?type=" + encodeForURL(mime);
+      document.body.appendChild(frame);
+    }, mime);
+  });
+}
diff --git a/mimesniff/mime-types/parsing.any.js b/mimesniff/mime-types/parsing.any.js
new file mode 100644
index 0000000..bd9a62e
--- /dev/null
+++ b/mimesniff/mime-types/parsing.any.js
@@ -0,0 +1,51 @@
+// META: timeout=long
+
+promise_test(() => {
+  return Promise.all([
+    fetch("resources/mime-types.json"),
+    fetch("resources/generated-mime-types.json")
+  ]).then(([res, res2]) => res.json().then(runTests).then(() => res2.json().then(runTests)));
+}, "Loading data…");
+
+function isByteCompatible(str) {
+  for(let i = 0; i < str.length; i++) {
+    const charCode = str.charCodeAt(i);
+    // See https://github.com/w3c/web-platform-tests/issues/8372 for 0x0B and 0x0C
+    // See https://fetch.spec.whatwg.org/#concept-header-value for the remainder
+    if(charCode > 0xFF) {
+      return "incompatible";
+    } else if(charCode === 0x00 || charCode === 0x0A || charCode === 0x0D) {
+      return "header-value-incompatible";
+    } else if(charCode === 0x0B || charCode === 0x0C) {
+      return "wptserve-incompatible";
+    }
+  }
+  return "compatible";
+}
+
+function runTests(tests) {
+  tests.forEach(val => {
+    if(typeof val === "string") {
+      return;
+    }
+    const output = val.output === null ? "" : val.output
+    test(() => {
+      assert_equals(new Blob([], { type: val.input}).type, output, "Blob");
+      assert_equals(new File([], "noname", { type: val.input}).type, output, "File");
+    }, val.input + " (Blob/File)");
+
+    promise_test(() => {
+      const compatibleNess = isByteCompatible(val.input);
+      if(compatibleNess === "incompatible" || compatibleNess === "header-value-incompatible") {
+        assert_throws(new TypeError(), () => new Request("about:blank", { headers: [["Content-Type", val.input]] }));
+        assert_throws(new TypeError(), () => new Response(null, { headers: [["Content-Type", val.input]] }));
+        return Promise.resolve();
+      } else {
+        return Promise.all([
+          new Request("about:blank", { headers: [["Content-Type", val.input]] }).blob().then(blob => assert_equals(blob.type, output)),
+          new Response(null, { headers: [["Content-Type", val.input]] }).blob().then(blob => assert_equals(blob.type, output))
+        ]);
+      }
+    }, val.input + " (Request/Response)");
+  });
+}
diff --git a/mimesniff/mime-types/resources/generated-mime-types.json b/mimesniff/mime-types/resources/generated-mime-types.json
new file mode 100644
index 0000000..f8934da
--- /dev/null
+++ b/mimesniff/mime-types/resources/generated-mime-types.json
@@ -0,0 +1,3526 @@
+[
+  {
+    "input": "\u0000/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0000",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0000=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0000;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0000\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0001/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0001",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0001=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0001;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0001\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0002/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0002",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0002=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0002;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0002\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0003/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0003",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0003=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0003;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0003\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0004/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0004",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0004=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0004;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0004\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0005/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0005",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0005=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0005;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0005\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0006/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0006",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0006=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0006;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0006\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0007/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0007",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0007=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0007;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0007\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\b/x",
+    "output": null
+  },
+  {
+    "input": "x/\b",
+    "output": null
+  },
+  {
+    "input": "x/x;\b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\b;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\b\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\t/x",
+    "output": null
+  },
+  {
+    "input": "x/\t",
+    "output": null
+  },
+  {
+    "input": "x/x;\t=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\n/x",
+    "output": null
+  },
+  {
+    "input": "x/\n",
+    "output": null
+  },
+  {
+    "input": "x/x;\n=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\n;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\n\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u000b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u000b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u000b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u000b;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u000b\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\f/x",
+    "output": null
+  },
+  {
+    "input": "x/\f",
+    "output": null
+  },
+  {
+    "input": "x/x;\f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\r/x",
+    "output": null
+  },
+  {
+    "input": "x/\r",
+    "output": null
+  },
+  {
+    "input": "x/x;\r=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\r;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\r\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u000e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u000e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u000e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u000e;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u000e\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u000f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u000f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u000f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u000f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u000f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0010/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0010",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0010=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0010;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0010\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0011/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0011",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0011=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0011;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0011\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0012/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0012",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0012=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0012;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0012\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0013/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0013",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0013=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0013;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0013\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0014/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0014",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0014=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0014;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0014\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0015/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0015",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0015=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0015;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0015\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0016/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0016",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0016=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0016;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0016\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0017/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0017",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0017=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0017;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0017\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0018/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0018",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0018=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0018;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0018\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0019/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0019",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0019=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0019;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0019\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001a/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001a",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001a=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001a;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001a\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001b;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001b\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001c/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001c",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001c=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001c;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001c\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001d/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001d",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001d=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001d;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001d\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001e;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001e\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u001f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u001f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u001f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u001f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u001f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": " /x",
+    "output": null
+  },
+  {
+    "input": "x/ ",
+    "output": null
+  },
+  {
+    "input": "x/x; =x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\"/x",
+    "output": null
+  },
+  {
+    "input": "x/\"",
+    "output": null
+  },
+  {
+    "input": "x/x;\"=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "(/x",
+    "output": null
+  },
+  {
+    "input": "x/(",
+    "output": null
+  },
+  {
+    "input": "x/x;(=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=(;bonus=x",
+    "output": "x/x;x=\"(\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"(\";bonus=x",
+    "output": "x/x;x=\"(\";bonus=x"
+  },
+  {
+    "input": ")/x",
+    "output": null
+  },
+  {
+    "input": "x/)",
+    "output": null
+  },
+  {
+    "input": "x/x;)=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=);bonus=x",
+    "output": "x/x;x=\")\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\")\";bonus=x",
+    "output": "x/x;x=\")\";bonus=x"
+  },
+  {
+    "input": ",/x",
+    "output": null
+  },
+  {
+    "input": "x/,",
+    "output": null
+  },
+  {
+    "input": "x/x;,=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=,;bonus=x",
+    "output": "x/x;x=\",\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\",\";bonus=x",
+    "output": "x/x;x=\",\";bonus=x"
+  },
+  {
+    "input": "x/x;/=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=/;bonus=x",
+    "output": "x/x;x=\"/\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"/\";bonus=x",
+    "output": "x/x;x=\"/\";bonus=x"
+  },
+  {
+    "input": ":/x",
+    "output": null
+  },
+  {
+    "input": "x/:",
+    "output": null
+  },
+  {
+    "input": "x/x;:=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=:;bonus=x",
+    "output": "x/x;x=\":\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\":\";bonus=x",
+    "output": "x/x;x=\":\";bonus=x"
+  },
+  {
+    "input": ";/x",
+    "output": null
+  },
+  {
+    "input": "x/;",
+    "output": null
+  },
+  {
+    "input": "</x",
+    "output": null
+  },
+  {
+    "input": "x/<",
+    "output": null
+  },
+  {
+    "input": "x/x;<=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=<;bonus=x",
+    "output": "x/x;x=\"<\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"<\";bonus=x",
+    "output": "x/x;x=\"<\";bonus=x"
+  },
+  {
+    "input": "=/x",
+    "output": null
+  },
+  {
+    "input": "x/=",
+    "output": null
+  },
+  {
+    "input": "x/x;x==;bonus=x",
+    "output": "x/x;x=\"=\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"=\";bonus=x",
+    "output": "x/x;x=\"=\";bonus=x"
+  },
+  {
+    "input": ">/x",
+    "output": null
+  },
+  {
+    "input": "x/>",
+    "output": null
+  },
+  {
+    "input": "x/x;>=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=>;bonus=x",
+    "output": "x/x;x=\">\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\">\";bonus=x",
+    "output": "x/x;x=\">\";bonus=x"
+  },
+  {
+    "input": "?/x",
+    "output": null
+  },
+  {
+    "input": "x/?",
+    "output": null
+  },
+  {
+    "input": "x/x;?=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=?;bonus=x",
+    "output": "x/x;x=\"?\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"?\";bonus=x",
+    "output": "x/x;x=\"?\";bonus=x"
+  },
+  {
+    "input": "@/x",
+    "output": null
+  },
+  {
+    "input": "x/@",
+    "output": null
+  },
+  {
+    "input": "x/x;@=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=@;bonus=x",
+    "output": "x/x;x=\"@\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"@\";bonus=x",
+    "output": "x/x;x=\"@\";bonus=x"
+  },
+  {
+    "input": "[/x",
+    "output": null
+  },
+  {
+    "input": "x/[",
+    "output": null
+  },
+  {
+    "input": "x/x;[=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=[;bonus=x",
+    "output": "x/x;x=\"[\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"[\";bonus=x",
+    "output": "x/x;x=\"[\";bonus=x"
+  },
+  {
+    "input": "\\/x",
+    "output": null
+  },
+  {
+    "input": "x/\\",
+    "output": null
+  },
+  {
+    "input": "x/x;\\=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "]/x",
+    "output": null
+  },
+  {
+    "input": "x/]",
+    "output": null
+  },
+  {
+    "input": "x/x;]=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=];bonus=x",
+    "output": "x/x;x=\"]\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"]\";bonus=x",
+    "output": "x/x;x=\"]\";bonus=x"
+  },
+  {
+    "input": "{/x",
+    "output": null
+  },
+  {
+    "input": "x/{",
+    "output": null
+  },
+  {
+    "input": "x/x;{=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x={;bonus=x",
+    "output": "x/x;x=\"{\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"{\";bonus=x",
+    "output": "x/x;x=\"{\";bonus=x"
+  },
+  {
+    "input": "}/x",
+    "output": null
+  },
+  {
+    "input": "x/}",
+    "output": null
+  },
+  {
+    "input": "x/x;}=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=};bonus=x",
+    "output": "x/x;x=\"}\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"}\";bonus=x",
+    "output": "x/x;x=\"}\";bonus=x"
+  },
+  {
+    "input": "\u007f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u007f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u007f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u007f;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u007f\";bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "\u0080/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0080",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0080=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0080;bonus=x",
+    "output": "x/x;x=\"\u0080\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0080\";bonus=x",
+    "output": "x/x;x=\"\u0080\";bonus=x"
+  },
+  {
+    "input": "\u0081/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0081",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0081=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0081;bonus=x",
+    "output": "x/x;x=\"\u0081\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0081\";bonus=x",
+    "output": "x/x;x=\"\u0081\";bonus=x"
+  },
+  {
+    "input": "\u0082/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0082",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0082=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0082;bonus=x",
+    "output": "x/x;x=\"\u0082\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0082\";bonus=x",
+    "output": "x/x;x=\"\u0082\";bonus=x"
+  },
+  {
+    "input": "\u0083/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0083",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0083=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0083;bonus=x",
+    "output": "x/x;x=\"\u0083\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0083\";bonus=x",
+    "output": "x/x;x=\"\u0083\";bonus=x"
+  },
+  {
+    "input": "\u0084/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0084",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0084=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0084;bonus=x",
+    "output": "x/x;x=\"\u0084\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0084\";bonus=x",
+    "output": "x/x;x=\"\u0084\";bonus=x"
+  },
+  {
+    "input": "\u0085/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0085",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0085=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0085;bonus=x",
+    "output": "x/x;x=\"\u0085\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0085\";bonus=x",
+    "output": "x/x;x=\"\u0085\";bonus=x"
+  },
+  {
+    "input": "\u0086/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0086",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0086=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0086;bonus=x",
+    "output": "x/x;x=\"\u0086\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0086\";bonus=x",
+    "output": "x/x;x=\"\u0086\";bonus=x"
+  },
+  {
+    "input": "\u0087/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0087",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0087=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0087;bonus=x",
+    "output": "x/x;x=\"\u0087\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0087\";bonus=x",
+    "output": "x/x;x=\"\u0087\";bonus=x"
+  },
+  {
+    "input": "\u0088/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0088",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0088=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0088;bonus=x",
+    "output": "x/x;x=\"\u0088\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0088\";bonus=x",
+    "output": "x/x;x=\"\u0088\";bonus=x"
+  },
+  {
+    "input": "\u0089/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0089",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0089=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0089;bonus=x",
+    "output": "x/x;x=\"\u0089\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0089\";bonus=x",
+    "output": "x/x;x=\"\u0089\";bonus=x"
+  },
+  {
+    "input": "\u008a/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008a",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008a=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008a;bonus=x",
+    "output": "x/x;x=\"\u008a\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008a\";bonus=x",
+    "output": "x/x;x=\"\u008a\";bonus=x"
+  },
+  {
+    "input": "\u008b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008b;bonus=x",
+    "output": "x/x;x=\"\u008b\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008b\";bonus=x",
+    "output": "x/x;x=\"\u008b\";bonus=x"
+  },
+  {
+    "input": "\u008c/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008c",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008c=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008c;bonus=x",
+    "output": "x/x;x=\"\u008c\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008c\";bonus=x",
+    "output": "x/x;x=\"\u008c\";bonus=x"
+  },
+  {
+    "input": "\u008d/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008d",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008d=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008d;bonus=x",
+    "output": "x/x;x=\"\u008d\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008d\";bonus=x",
+    "output": "x/x;x=\"\u008d\";bonus=x"
+  },
+  {
+    "input": "\u008e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008e;bonus=x",
+    "output": "x/x;x=\"\u008e\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008e\";bonus=x",
+    "output": "x/x;x=\"\u008e\";bonus=x"
+  },
+  {
+    "input": "\u008f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u008f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u008f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u008f;bonus=x",
+    "output": "x/x;x=\"\u008f\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u008f\";bonus=x",
+    "output": "x/x;x=\"\u008f\";bonus=x"
+  },
+  {
+    "input": "\u0090/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0090",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0090=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0090;bonus=x",
+    "output": "x/x;x=\"\u0090\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0090\";bonus=x",
+    "output": "x/x;x=\"\u0090\";bonus=x"
+  },
+  {
+    "input": "\u0091/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0091",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0091=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0091;bonus=x",
+    "output": "x/x;x=\"\u0091\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0091\";bonus=x",
+    "output": "x/x;x=\"\u0091\";bonus=x"
+  },
+  {
+    "input": "\u0092/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0092",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0092=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0092;bonus=x",
+    "output": "x/x;x=\"\u0092\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0092\";bonus=x",
+    "output": "x/x;x=\"\u0092\";bonus=x"
+  },
+  {
+    "input": "\u0093/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0093",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0093=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0093;bonus=x",
+    "output": "x/x;x=\"\u0093\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0093\";bonus=x",
+    "output": "x/x;x=\"\u0093\";bonus=x"
+  },
+  {
+    "input": "\u0094/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0094",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0094=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0094;bonus=x",
+    "output": "x/x;x=\"\u0094\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0094\";bonus=x",
+    "output": "x/x;x=\"\u0094\";bonus=x"
+  },
+  {
+    "input": "\u0095/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0095",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0095=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0095;bonus=x",
+    "output": "x/x;x=\"\u0095\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0095\";bonus=x",
+    "output": "x/x;x=\"\u0095\";bonus=x"
+  },
+  {
+    "input": "\u0096/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0096",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0096=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0096;bonus=x",
+    "output": "x/x;x=\"\u0096\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0096\";bonus=x",
+    "output": "x/x;x=\"\u0096\";bonus=x"
+  },
+  {
+    "input": "\u0097/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0097",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0097=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0097;bonus=x",
+    "output": "x/x;x=\"\u0097\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0097\";bonus=x",
+    "output": "x/x;x=\"\u0097\";bonus=x"
+  },
+  {
+    "input": "\u0098/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0098",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0098=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0098;bonus=x",
+    "output": "x/x;x=\"\u0098\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0098\";bonus=x",
+    "output": "x/x;x=\"\u0098\";bonus=x"
+  },
+  {
+    "input": "\u0099/x",
+    "output": null
+  },
+  {
+    "input": "x/\u0099",
+    "output": null
+  },
+  {
+    "input": "x/x;\u0099=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u0099;bonus=x",
+    "output": "x/x;x=\"\u0099\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u0099\";bonus=x",
+    "output": "x/x;x=\"\u0099\";bonus=x"
+  },
+  {
+    "input": "\u009a/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009a",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009a=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009a;bonus=x",
+    "output": "x/x;x=\"\u009a\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009a\";bonus=x",
+    "output": "x/x;x=\"\u009a\";bonus=x"
+  },
+  {
+    "input": "\u009b/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009b",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009b=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009b;bonus=x",
+    "output": "x/x;x=\"\u009b\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009b\";bonus=x",
+    "output": "x/x;x=\"\u009b\";bonus=x"
+  },
+  {
+    "input": "\u009c/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009c",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009c=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009c;bonus=x",
+    "output": "x/x;x=\"\u009c\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009c\";bonus=x",
+    "output": "x/x;x=\"\u009c\";bonus=x"
+  },
+  {
+    "input": "\u009d/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009d",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009d=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009d;bonus=x",
+    "output": "x/x;x=\"\u009d\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009d\";bonus=x",
+    "output": "x/x;x=\"\u009d\";bonus=x"
+  },
+  {
+    "input": "\u009e/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009e",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009e=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009e;bonus=x",
+    "output": "x/x;x=\"\u009e\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009e\";bonus=x",
+    "output": "x/x;x=\"\u009e\";bonus=x"
+  },
+  {
+    "input": "\u009f/x",
+    "output": null
+  },
+  {
+    "input": "x/\u009f",
+    "output": null
+  },
+  {
+    "input": "x/x;\u009f=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u009f;bonus=x",
+    "output": "x/x;x=\"\u009f\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u009f\";bonus=x",
+    "output": "x/x;x=\"\u009f\";bonus=x"
+  },
+  {
+    "input": "\u00a0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a0;bonus=x",
+    "output": "x/x;x=\"\u00a0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a0\";bonus=x",
+    "output": "x/x;x=\"\u00a0\";bonus=x"
+  },
+  {
+    "input": "\u00a1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a1;bonus=x",
+    "output": "x/x;x=\"\u00a1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a1\";bonus=x",
+    "output": "x/x;x=\"\u00a1\";bonus=x"
+  },
+  {
+    "input": "\u00a2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a2;bonus=x",
+    "output": "x/x;x=\"\u00a2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a2\";bonus=x",
+    "output": "x/x;x=\"\u00a2\";bonus=x"
+  },
+  {
+    "input": "\u00a3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a3;bonus=x",
+    "output": "x/x;x=\"\u00a3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a3\";bonus=x",
+    "output": "x/x;x=\"\u00a3\";bonus=x"
+  },
+  {
+    "input": "\u00a4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a4;bonus=x",
+    "output": "x/x;x=\"\u00a4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a4\";bonus=x",
+    "output": "x/x;x=\"\u00a4\";bonus=x"
+  },
+  {
+    "input": "\u00a5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a5;bonus=x",
+    "output": "x/x;x=\"\u00a5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a5\";bonus=x",
+    "output": "x/x;x=\"\u00a5\";bonus=x"
+  },
+  {
+    "input": "\u00a6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a6;bonus=x",
+    "output": "x/x;x=\"\u00a6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a6\";bonus=x",
+    "output": "x/x;x=\"\u00a6\";bonus=x"
+  },
+  {
+    "input": "\u00a7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a7;bonus=x",
+    "output": "x/x;x=\"\u00a7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a7\";bonus=x",
+    "output": "x/x;x=\"\u00a7\";bonus=x"
+  },
+  {
+    "input": "\u00a8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a8;bonus=x",
+    "output": "x/x;x=\"\u00a8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a8\";bonus=x",
+    "output": "x/x;x=\"\u00a8\";bonus=x"
+  },
+  {
+    "input": "\u00a9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00a9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00a9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00a9;bonus=x",
+    "output": "x/x;x=\"\u00a9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00a9\";bonus=x",
+    "output": "x/x;x=\"\u00a9\";bonus=x"
+  },
+  {
+    "input": "\u00aa/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00aa",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00aa=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00aa;bonus=x",
+    "output": "x/x;x=\"\u00aa\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00aa\";bonus=x",
+    "output": "x/x;x=\"\u00aa\";bonus=x"
+  },
+  {
+    "input": "\u00ab/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ab",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ab=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ab;bonus=x",
+    "output": "x/x;x=\"\u00ab\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ab\";bonus=x",
+    "output": "x/x;x=\"\u00ab\";bonus=x"
+  },
+  {
+    "input": "\u00ac/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ac",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ac=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ac;bonus=x",
+    "output": "x/x;x=\"\u00ac\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ac\";bonus=x",
+    "output": "x/x;x=\"\u00ac\";bonus=x"
+  },
+  {
+    "input": "\u00ad/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ad",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ad=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ad;bonus=x",
+    "output": "x/x;x=\"\u00ad\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ad\";bonus=x",
+    "output": "x/x;x=\"\u00ad\";bonus=x"
+  },
+  {
+    "input": "\u00ae/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ae",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ae=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ae;bonus=x",
+    "output": "x/x;x=\"\u00ae\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ae\";bonus=x",
+    "output": "x/x;x=\"\u00ae\";bonus=x"
+  },
+  {
+    "input": "\u00af/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00af",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00af=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00af;bonus=x",
+    "output": "x/x;x=\"\u00af\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00af\";bonus=x",
+    "output": "x/x;x=\"\u00af\";bonus=x"
+  },
+  {
+    "input": "\u00b0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b0;bonus=x",
+    "output": "x/x;x=\"\u00b0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b0\";bonus=x",
+    "output": "x/x;x=\"\u00b0\";bonus=x"
+  },
+  {
+    "input": "\u00b1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b1;bonus=x",
+    "output": "x/x;x=\"\u00b1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b1\";bonus=x",
+    "output": "x/x;x=\"\u00b1\";bonus=x"
+  },
+  {
+    "input": "\u00b2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b2;bonus=x",
+    "output": "x/x;x=\"\u00b2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b2\";bonus=x",
+    "output": "x/x;x=\"\u00b2\";bonus=x"
+  },
+  {
+    "input": "\u00b3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b3;bonus=x",
+    "output": "x/x;x=\"\u00b3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b3\";bonus=x",
+    "output": "x/x;x=\"\u00b3\";bonus=x"
+  },
+  {
+    "input": "\u00b4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b4;bonus=x",
+    "output": "x/x;x=\"\u00b4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b4\";bonus=x",
+    "output": "x/x;x=\"\u00b4\";bonus=x"
+  },
+  {
+    "input": "\u00b5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b5;bonus=x",
+    "output": "x/x;x=\"\u00b5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b5\";bonus=x",
+    "output": "x/x;x=\"\u00b5\";bonus=x"
+  },
+  {
+    "input": "\u00b6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b6;bonus=x",
+    "output": "x/x;x=\"\u00b6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b6\";bonus=x",
+    "output": "x/x;x=\"\u00b6\";bonus=x"
+  },
+  {
+    "input": "\u00b7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b7;bonus=x",
+    "output": "x/x;x=\"\u00b7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b7\";bonus=x",
+    "output": "x/x;x=\"\u00b7\";bonus=x"
+  },
+  {
+    "input": "\u00b8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b8;bonus=x",
+    "output": "x/x;x=\"\u00b8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b8\";bonus=x",
+    "output": "x/x;x=\"\u00b8\";bonus=x"
+  },
+  {
+    "input": "\u00b9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00b9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00b9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00b9;bonus=x",
+    "output": "x/x;x=\"\u00b9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00b9\";bonus=x",
+    "output": "x/x;x=\"\u00b9\";bonus=x"
+  },
+  {
+    "input": "\u00ba/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ba",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ba=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ba;bonus=x",
+    "output": "x/x;x=\"\u00ba\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ba\";bonus=x",
+    "output": "x/x;x=\"\u00ba\";bonus=x"
+  },
+  {
+    "input": "\u00bb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bb;bonus=x",
+    "output": "x/x;x=\"\u00bb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bb\";bonus=x",
+    "output": "x/x;x=\"\u00bb\";bonus=x"
+  },
+  {
+    "input": "\u00bc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bc;bonus=x",
+    "output": "x/x;x=\"\u00bc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bc\";bonus=x",
+    "output": "x/x;x=\"\u00bc\";bonus=x"
+  },
+  {
+    "input": "\u00bd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bd;bonus=x",
+    "output": "x/x;x=\"\u00bd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bd\";bonus=x",
+    "output": "x/x;x=\"\u00bd\";bonus=x"
+  },
+  {
+    "input": "\u00be/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00be",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00be=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00be;bonus=x",
+    "output": "x/x;x=\"\u00be\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00be\";bonus=x",
+    "output": "x/x;x=\"\u00be\";bonus=x"
+  },
+  {
+    "input": "\u00bf/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00bf",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00bf=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00bf;bonus=x",
+    "output": "x/x;x=\"\u00bf\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00bf\";bonus=x",
+    "output": "x/x;x=\"\u00bf\";bonus=x"
+  },
+  {
+    "input": "\u00c0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c0;bonus=x",
+    "output": "x/x;x=\"\u00c0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c0\";bonus=x",
+    "output": "x/x;x=\"\u00c0\";bonus=x"
+  },
+  {
+    "input": "\u00c1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c1;bonus=x",
+    "output": "x/x;x=\"\u00c1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c1\";bonus=x",
+    "output": "x/x;x=\"\u00c1\";bonus=x"
+  },
+  {
+    "input": "\u00c2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c2;bonus=x",
+    "output": "x/x;x=\"\u00c2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c2\";bonus=x",
+    "output": "x/x;x=\"\u00c2\";bonus=x"
+  },
+  {
+    "input": "\u00c3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c3;bonus=x",
+    "output": "x/x;x=\"\u00c3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c3\";bonus=x",
+    "output": "x/x;x=\"\u00c3\";bonus=x"
+  },
+  {
+    "input": "\u00c4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c4;bonus=x",
+    "output": "x/x;x=\"\u00c4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c4\";bonus=x",
+    "output": "x/x;x=\"\u00c4\";bonus=x"
+  },
+  {
+    "input": "\u00c5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c5;bonus=x",
+    "output": "x/x;x=\"\u00c5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c5\";bonus=x",
+    "output": "x/x;x=\"\u00c5\";bonus=x"
+  },
+  {
+    "input": "\u00c6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c6;bonus=x",
+    "output": "x/x;x=\"\u00c6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c6\";bonus=x",
+    "output": "x/x;x=\"\u00c6\";bonus=x"
+  },
+  {
+    "input": "\u00c7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c7;bonus=x",
+    "output": "x/x;x=\"\u00c7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c7\";bonus=x",
+    "output": "x/x;x=\"\u00c7\";bonus=x"
+  },
+  {
+    "input": "\u00c8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c8;bonus=x",
+    "output": "x/x;x=\"\u00c8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c8\";bonus=x",
+    "output": "x/x;x=\"\u00c8\";bonus=x"
+  },
+  {
+    "input": "\u00c9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00c9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00c9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00c9;bonus=x",
+    "output": "x/x;x=\"\u00c9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00c9\";bonus=x",
+    "output": "x/x;x=\"\u00c9\";bonus=x"
+  },
+  {
+    "input": "\u00ca/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ca",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ca=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ca;bonus=x",
+    "output": "x/x;x=\"\u00ca\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ca\";bonus=x",
+    "output": "x/x;x=\"\u00ca\";bonus=x"
+  },
+  {
+    "input": "\u00cb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cb;bonus=x",
+    "output": "x/x;x=\"\u00cb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cb\";bonus=x",
+    "output": "x/x;x=\"\u00cb\";bonus=x"
+  },
+  {
+    "input": "\u00cc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cc;bonus=x",
+    "output": "x/x;x=\"\u00cc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cc\";bonus=x",
+    "output": "x/x;x=\"\u00cc\";bonus=x"
+  },
+  {
+    "input": "\u00cd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cd;bonus=x",
+    "output": "x/x;x=\"\u00cd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cd\";bonus=x",
+    "output": "x/x;x=\"\u00cd\";bonus=x"
+  },
+  {
+    "input": "\u00ce/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ce",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ce=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ce;bonus=x",
+    "output": "x/x;x=\"\u00ce\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ce\";bonus=x",
+    "output": "x/x;x=\"\u00ce\";bonus=x"
+  },
+  {
+    "input": "\u00cf/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00cf",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00cf=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00cf;bonus=x",
+    "output": "x/x;x=\"\u00cf\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00cf\";bonus=x",
+    "output": "x/x;x=\"\u00cf\";bonus=x"
+  },
+  {
+    "input": "\u00d0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d0;bonus=x",
+    "output": "x/x;x=\"\u00d0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d0\";bonus=x",
+    "output": "x/x;x=\"\u00d0\";bonus=x"
+  },
+  {
+    "input": "\u00d1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d1;bonus=x",
+    "output": "x/x;x=\"\u00d1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d1\";bonus=x",
+    "output": "x/x;x=\"\u00d1\";bonus=x"
+  },
+  {
+    "input": "\u00d2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d2;bonus=x",
+    "output": "x/x;x=\"\u00d2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d2\";bonus=x",
+    "output": "x/x;x=\"\u00d2\";bonus=x"
+  },
+  {
+    "input": "\u00d3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d3;bonus=x",
+    "output": "x/x;x=\"\u00d3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d3\";bonus=x",
+    "output": "x/x;x=\"\u00d3\";bonus=x"
+  },
+  {
+    "input": "\u00d4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d4;bonus=x",
+    "output": "x/x;x=\"\u00d4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d4\";bonus=x",
+    "output": "x/x;x=\"\u00d4\";bonus=x"
+  },
+  {
+    "input": "\u00d5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d5;bonus=x",
+    "output": "x/x;x=\"\u00d5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d5\";bonus=x",
+    "output": "x/x;x=\"\u00d5\";bonus=x"
+  },
+  {
+    "input": "\u00d6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d6;bonus=x",
+    "output": "x/x;x=\"\u00d6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d6\";bonus=x",
+    "output": "x/x;x=\"\u00d6\";bonus=x"
+  },
+  {
+    "input": "\u00d7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d7;bonus=x",
+    "output": "x/x;x=\"\u00d7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d7\";bonus=x",
+    "output": "x/x;x=\"\u00d7\";bonus=x"
+  },
+  {
+    "input": "\u00d8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d8;bonus=x",
+    "output": "x/x;x=\"\u00d8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d8\";bonus=x",
+    "output": "x/x;x=\"\u00d8\";bonus=x"
+  },
+  {
+    "input": "\u00d9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00d9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00d9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00d9;bonus=x",
+    "output": "x/x;x=\"\u00d9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00d9\";bonus=x",
+    "output": "x/x;x=\"\u00d9\";bonus=x"
+  },
+  {
+    "input": "\u00da/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00da",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00da=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00da;bonus=x",
+    "output": "x/x;x=\"\u00da\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00da\";bonus=x",
+    "output": "x/x;x=\"\u00da\";bonus=x"
+  },
+  {
+    "input": "\u00db/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00db",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00db=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00db;bonus=x",
+    "output": "x/x;x=\"\u00db\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00db\";bonus=x",
+    "output": "x/x;x=\"\u00db\";bonus=x"
+  },
+  {
+    "input": "\u00dc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00dc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00dc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00dc;bonus=x",
+    "output": "x/x;x=\"\u00dc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00dc\";bonus=x",
+    "output": "x/x;x=\"\u00dc\";bonus=x"
+  },
+  {
+    "input": "\u00dd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00dd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00dd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00dd;bonus=x",
+    "output": "x/x;x=\"\u00dd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00dd\";bonus=x",
+    "output": "x/x;x=\"\u00dd\";bonus=x"
+  },
+  {
+    "input": "\u00de/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00de",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00de=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00de;bonus=x",
+    "output": "x/x;x=\"\u00de\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00de\";bonus=x",
+    "output": "x/x;x=\"\u00de\";bonus=x"
+  },
+  {
+    "input": "\u00df/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00df",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00df=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00df;bonus=x",
+    "output": "x/x;x=\"\u00df\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00df\";bonus=x",
+    "output": "x/x;x=\"\u00df\";bonus=x"
+  },
+  {
+    "input": "\u00e0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e0;bonus=x",
+    "output": "x/x;x=\"\u00e0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e0\";bonus=x",
+    "output": "x/x;x=\"\u00e0\";bonus=x"
+  },
+  {
+    "input": "\u00e1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e1;bonus=x",
+    "output": "x/x;x=\"\u00e1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e1\";bonus=x",
+    "output": "x/x;x=\"\u00e1\";bonus=x"
+  },
+  {
+    "input": "\u00e2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e2;bonus=x",
+    "output": "x/x;x=\"\u00e2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e2\";bonus=x",
+    "output": "x/x;x=\"\u00e2\";bonus=x"
+  },
+  {
+    "input": "\u00e3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e3;bonus=x",
+    "output": "x/x;x=\"\u00e3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e3\";bonus=x",
+    "output": "x/x;x=\"\u00e3\";bonus=x"
+  },
+  {
+    "input": "\u00e4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e4;bonus=x",
+    "output": "x/x;x=\"\u00e4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e4\";bonus=x",
+    "output": "x/x;x=\"\u00e4\";bonus=x"
+  },
+  {
+    "input": "\u00e5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e5;bonus=x",
+    "output": "x/x;x=\"\u00e5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e5\";bonus=x",
+    "output": "x/x;x=\"\u00e5\";bonus=x"
+  },
+  {
+    "input": "\u00e6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e6;bonus=x",
+    "output": "x/x;x=\"\u00e6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e6\";bonus=x",
+    "output": "x/x;x=\"\u00e6\";bonus=x"
+  },
+  {
+    "input": "\u00e7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e7;bonus=x",
+    "output": "x/x;x=\"\u00e7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e7\";bonus=x",
+    "output": "x/x;x=\"\u00e7\";bonus=x"
+  },
+  {
+    "input": "\u00e8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e8;bonus=x",
+    "output": "x/x;x=\"\u00e8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e8\";bonus=x",
+    "output": "x/x;x=\"\u00e8\";bonus=x"
+  },
+  {
+    "input": "\u00e9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00e9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00e9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00e9;bonus=x",
+    "output": "x/x;x=\"\u00e9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00e9\";bonus=x",
+    "output": "x/x;x=\"\u00e9\";bonus=x"
+  },
+  {
+    "input": "\u00ea/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ea",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ea=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ea;bonus=x",
+    "output": "x/x;x=\"\u00ea\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ea\";bonus=x",
+    "output": "x/x;x=\"\u00ea\";bonus=x"
+  },
+  {
+    "input": "\u00eb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00eb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00eb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00eb;bonus=x",
+    "output": "x/x;x=\"\u00eb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00eb\";bonus=x",
+    "output": "x/x;x=\"\u00eb\";bonus=x"
+  },
+  {
+    "input": "\u00ec/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ec",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ec=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ec;bonus=x",
+    "output": "x/x;x=\"\u00ec\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ec\";bonus=x",
+    "output": "x/x;x=\"\u00ec\";bonus=x"
+  },
+  {
+    "input": "\u00ed/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ed",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ed=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ed;bonus=x",
+    "output": "x/x;x=\"\u00ed\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ed\";bonus=x",
+    "output": "x/x;x=\"\u00ed\";bonus=x"
+  },
+  {
+    "input": "\u00ee/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ee",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ee=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ee;bonus=x",
+    "output": "x/x;x=\"\u00ee\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ee\";bonus=x",
+    "output": "x/x;x=\"\u00ee\";bonus=x"
+  },
+  {
+    "input": "\u00ef/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ef",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ef=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ef;bonus=x",
+    "output": "x/x;x=\"\u00ef\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ef\";bonus=x",
+    "output": "x/x;x=\"\u00ef\";bonus=x"
+  },
+  {
+    "input": "\u00f0/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f0",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f0=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f0;bonus=x",
+    "output": "x/x;x=\"\u00f0\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f0\";bonus=x",
+    "output": "x/x;x=\"\u00f0\";bonus=x"
+  },
+  {
+    "input": "\u00f1/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f1",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f1=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f1;bonus=x",
+    "output": "x/x;x=\"\u00f1\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f1\";bonus=x",
+    "output": "x/x;x=\"\u00f1\";bonus=x"
+  },
+  {
+    "input": "\u00f2/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f2",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f2=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f2;bonus=x",
+    "output": "x/x;x=\"\u00f2\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f2\";bonus=x",
+    "output": "x/x;x=\"\u00f2\";bonus=x"
+  },
+  {
+    "input": "\u00f3/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f3",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f3=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f3;bonus=x",
+    "output": "x/x;x=\"\u00f3\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f3\";bonus=x",
+    "output": "x/x;x=\"\u00f3\";bonus=x"
+  },
+  {
+    "input": "\u00f4/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f4",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f4=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f4;bonus=x",
+    "output": "x/x;x=\"\u00f4\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f4\";bonus=x",
+    "output": "x/x;x=\"\u00f4\";bonus=x"
+  },
+  {
+    "input": "\u00f5/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f5",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f5=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f5;bonus=x",
+    "output": "x/x;x=\"\u00f5\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f5\";bonus=x",
+    "output": "x/x;x=\"\u00f5\";bonus=x"
+  },
+  {
+    "input": "\u00f6/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f6",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f6=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f6;bonus=x",
+    "output": "x/x;x=\"\u00f6\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f6\";bonus=x",
+    "output": "x/x;x=\"\u00f6\";bonus=x"
+  },
+  {
+    "input": "\u00f7/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f7",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f7=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f7;bonus=x",
+    "output": "x/x;x=\"\u00f7\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f7\";bonus=x",
+    "output": "x/x;x=\"\u00f7\";bonus=x"
+  },
+  {
+    "input": "\u00f8/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f8",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f8=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f8;bonus=x",
+    "output": "x/x;x=\"\u00f8\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f8\";bonus=x",
+    "output": "x/x;x=\"\u00f8\";bonus=x"
+  },
+  {
+    "input": "\u00f9/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00f9",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00f9=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00f9;bonus=x",
+    "output": "x/x;x=\"\u00f9\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00f9\";bonus=x",
+    "output": "x/x;x=\"\u00f9\";bonus=x"
+  },
+  {
+    "input": "\u00fa/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fa",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fa=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fa;bonus=x",
+    "output": "x/x;x=\"\u00fa\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fa\";bonus=x",
+    "output": "x/x;x=\"\u00fa\";bonus=x"
+  },
+  {
+    "input": "\u00fb/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fb",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fb=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fb;bonus=x",
+    "output": "x/x;x=\"\u00fb\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fb\";bonus=x",
+    "output": "x/x;x=\"\u00fb\";bonus=x"
+  },
+  {
+    "input": "\u00fc/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fc",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fc=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fc;bonus=x",
+    "output": "x/x;x=\"\u00fc\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fc\";bonus=x",
+    "output": "x/x;x=\"\u00fc\";bonus=x"
+  },
+  {
+    "input": "\u00fd/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fd",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fd=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fd;bonus=x",
+    "output": "x/x;x=\"\u00fd\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fd\";bonus=x",
+    "output": "x/x;x=\"\u00fd\";bonus=x"
+  },
+  {
+    "input": "\u00fe/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00fe",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00fe=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00fe;bonus=x",
+    "output": "x/x;x=\"\u00fe\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00fe\";bonus=x",
+    "output": "x/x;x=\"\u00fe\";bonus=x"
+  },
+  {
+    "input": "\u00ff/x",
+    "output": null
+  },
+  {
+    "input": "x/\u00ff",
+    "output": null
+  },
+  {
+    "input": "x/x;\u00ff=x;bonus=x",
+    "output": "x/x;bonus=x"
+  },
+  {
+    "input": "x/x;x=\u00ff;bonus=x",
+    "output": "x/x;x=\"\u00ff\";bonus=x"
+  },
+  {
+    "input": "x/x;x=\"\u00ff\";bonus=x",
+    "output": "x/x;x=\"\u00ff\";bonus=x"
+  }
+]
diff --git a/mimesniff/mime-types/resources/generated-mime-types.py b/mimesniff/mime-types/resources/generated-mime-types.py
new file mode 100644
index 0000000..9192114
--- /dev/null
+++ b/mimesniff/mime-types/resources/generated-mime-types.py
@@ -0,0 +1,45 @@
+import json
+
+def isHTTPTokenCodePoint(cp):
+    if cp in (0x21, 0x23, 0x24, 0x25, 0x26, 0x27, 0x2A, 0x2B, 0x2D, 0x2E, 0x5E, 0x5F, 0x60, 0x7C, 0x7E) or (cp >= 0x30 and cp <= 0x39) or (cp >= 0x41 and cp <= 0x5A) or (cp >= 0x61 and cp <= 0x7A):
+        return True
+    else:
+        return False
+
+def isHTTPQuotedStringTokenCodePoint(cp):
+    if cp == 0x09 or (cp >= 0x20 and cp <= 0x7E) or (cp >= 0x80 and cp <= 0xFF):
+        return True
+    else:
+        return False
+
+tests = []
+
+for cp in range(0x00, 0x100):
+    if isHTTPTokenCodePoint(cp):
+        continue
+    for scenario in ("type", "subtype", "name", "value"):
+        if scenario == "type" or scenario == "subtype":
+            if cp == 0x2F: # /
+                continue
+            if scenario == "type":
+                test = unichr(cp) + "/x"
+            else:
+                test = "x/" + unichr(cp)
+            tests.append({"input": test, "output": None})
+        elif scenario == "name":
+            if cp == 0x3B or cp == 0x3D: # ; =
+                continue
+            tests.append({"input": "x/x;" + unichr(cp) + "=x;bonus=x", "output": "x/x;bonus=x"})
+        elif scenario == "value":
+            if cp == 0x09 or cp == 0x20 or cp == 0x22 or cp == 0x3B or cp == 0x5C: # TAB SP " ; \
+                continue
+            if isHTTPQuotedStringTokenCodePoint(cp):
+                testOutput = "x/x;x=\"" + unichr(cp) + "\";bonus=x"
+            else:
+                testOutput = "x/x;bonus=x"
+            tests.append({"input": "x/x;x=" + unichr(cp) + ";bonus=x", "output": testOutput})
+            tests.append({"input": "x/x;x=\"" + unichr(cp) + "\";bonus=x", "output": testOutput})
+
+handle = open("generated-mime-types.json", "w")
+handle.write(json.dumps(tests, indent=2, separators=(',', ': ')))
+handle.write("\n")
diff --git a/mimesniff/mime-types/resources/mime-charset.py b/mimesniff/mime-types/resources/mime-charset.py
new file mode 100644
index 0000000..433a5bb
--- /dev/null
+++ b/mimesniff/mime-types/resources/mime-charset.py
@@ -0,0 +1,3 @@
+def main(request, response):
+    response.headers.set("Content-Type", request.GET.first("type"));
+    response.content = "<meta charset=utf-8>\n<script>document.write(document.characterSet)</script>"
diff --git a/mimesniff/mime-types/resources/mime-types.json b/mimesniff/mime-types/resources/mime-types.json
new file mode 100644
index 0000000..1d0b152
--- /dev/null
+++ b/mimesniff/mime-types/resources/mime-types.json
@@ -0,0 +1,318 @@
+[
+  "Basics",
+  {
+    "input": "text/html;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "TEXT/HTML;CHARSET=GBK",
+    "output": "text/html;charset=GBK",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Legacy comment syntax",
+  {
+    "input": "text/html;charset=gbk(",
+    "output": "text/html;charset=\"gbk(\"",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;x=(;charset=gbk",
+    "output": "text/html;x=\"(\";charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Duplicate parameter",
+  {
+    "input": "text/html;charset=gbk;charset=windows-1255",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=();charset=GBK",
+    "output": "text/html;charset=\"()\"",
+    "navigable": true,
+    "encoding": null
+  },
+  "Spaces",
+  {
+    "input": "text/html;charset =gbk",
+    "output": "text/html",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html ;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html; charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset= gbk",
+    "output": "text/html;charset=\" gbk\"",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset= \"gbk\"",
+    "output": "text/html;charset=\" \\\"gbk\\\"\"",
+    "navigable": true,
+    "encoding": null
+  },
+  "Single quotes are a token, not a delimiter",
+  {
+    "input": "text/html;charset='gbk'",
+    "output": "text/html;charset='gbk'",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;charset='gbk",
+    "output": "text/html;charset='gbk",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;charset=gbk'",
+    "output": "text/html;charset=gbk'",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;charset=';charset=GBK",
+    "output": "text/html;charset='",
+    "navigable": true,
+    "encoding": null
+  },
+  "Invalid parameters",
+  {
+    "input": "text/html;test;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;test=;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;';charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;\";charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html ; ; charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;;;;charset=gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset= \"\u007F;charset=GBK",
+    "output": "text/html;charset=GBK",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"\u007F;charset=foo\";charset=GBK",
+    "output": "text/html;charset=GBK",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "Double quotes",
+  {
+    "input": "text/html;charset=\"gbk\"",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"gbk",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=gbk\"",
+    "output": "text/html;charset=\"gbk\\\"\"",
+    "navigable": true,
+    "encoding": null
+  },
+  {
+    "input": "text/html;charset=\" gbk\"",
+    "output": "text/html;charset=\" gbk\"",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"gbk \"",
+    "output": "text/html;charset=\"gbk \"",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"\\ gbk\"",
+    "output": "text/html;charset=\" gbk\"",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"\\g\\b\\k\"",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"gbk\"x",
+    "output": "text/html;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\"\";charset=GBK",
+    "output": "text/html;charset=GBK",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  {
+    "input": "text/html;charset=\";charset=GBK",
+    "output": "text/html;charset=\";charset=GBK\"",
+    "navigable": true,
+    "encoding": null
+  },
+  "Unexpected code points",
+  {
+    "input": "text/html;charset={gbk}",
+    "output": "text/html;charset=\"{gbk}\"",
+    "navigable": true,
+    "encoding": null
+  },
+  "Parameter name longer than 127",
+  {
+    "input": "text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk",
+    "output": "text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  "type/subtype longer than 127",
+  {
+    "input": "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
+    "output": "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+  },
+  "Valid",
+  {
+    "input": "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+    "output": "!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+  },
+  {
+    "input": "x/x;x=\"\t !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\"",
+    "output": "x/x;x=\"\t !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\""
+  },
+  "End-of-file handling",
+  {
+    "input": "x/x;test",
+    "output": "x/x"
+  },
+  {
+    "input": "x/x;test=\"\\",
+    "output": "x/x;test=\"\\\\\""
+  },
+  "Whitespace (not handled by generated-mime-types.json or above)",
+  {
+    "input": "x/x;x= ",
+    "output": "x/x"
+  },
+  {
+    "input": "x/x;x=\t",
+    "output": "x/x"
+  },
+  "Latin1",
+  {
+    "input": "text/html;test=\u00FF;charset=gbk",
+    "output": "text/html;test=\"\u00FF\";charset=gbk",
+    "navigable": true,
+    "encoding": "GBK"
+  },
+  ">Latin1",
+  {
+    "input": "x/x;test=\uFFFD;x=x",
+    "output": "x/x;x=x"
+  },
+  "Failure",
+  {
+    "input": "",
+    "output": null
+  },
+  {
+    "input": "\t",
+    "output": null
+  },
+  {
+    "input": "/",
+    "output": null
+  },
+  {
+    "input": "bogus",
+    "output": null
+  },
+  {
+    "input": "bogus/",
+    "output": null
+  },
+  {
+    "input": "bogus/ ",
+    "output": null
+  },
+  {
+    "input": "bogus/bogus/;",
+    "output": null
+  },
+  {
+    "input": "</>",
+    "output": null
+  },
+  {
+    "input": "(/)",
+    "output": null
+  },
+  {
+    "input": "ÿ/ÿ",
+    "output": null
+  },
+  {
+    "input": "text/html(;doesnot=matter",
+    "output": null
+  },
+  {
+    "input": "{/}",
+    "output": null
+  },
+  {
+    "input": "\u0100/\u0100",
+    "output": null
+  }
+]
diff --git a/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..f0c5ae1
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: cross-origin-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: keep-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "cross-origin-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "keep-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html.headers b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..e592db5
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: cross-origin-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "cross-origin-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html.headers b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..ee9c3c6
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: cross-origin-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: swap-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "cross-origin-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "swap-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html.headers b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/cross-origin-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/http-csp/same-host-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/http-csp/same-host-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..e050885
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: same-host-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: keep-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "same-host-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "keep-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/same-host-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html.headers b/mixed-content/beacon-request/http-csp/same-host-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-http/top-level/keep-scheme-redirect/blockable/opt-in-blocks.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/http-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/http-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..369044f
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: same-host-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "same-host-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html.headers b/mixed-content/beacon-request/http-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/http-csp/same-host-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/http-csp/same-host-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..aea84f7
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: same-host-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: swap-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "same-host-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "swap-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/same-host-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html.headers b/mixed-content/beacon-request/http-csp/same-host-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-http/top-level/swap-scheme-redirect/blockable/opt-in-blocks.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/http-csp/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html b/mixed-content/beacon-request/http-csp/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html
new file mode 100644
index 0000000..83d425c
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Allowed content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of allowed content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: same-host-https
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: keep-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: allowed">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "same-host-https",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "keep-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "allowed"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html.headers b/mixed-content/beacon-request/http-csp/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/http-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html b/mixed-content/beacon-request/http-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html
new file mode 100644
index 0000000..728e6ce
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Allowed content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of allowed content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/">
+    <meta name="assert" content="opt_in_method: http-csp
+                                 origin: same-host-https
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: allowed">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "http-csp",
+          "origin": "same-host-https",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "allowed"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/http-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html.headers b/mixed-content/beacon-request/http-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html.headers
new file mode 100644
index 0000000..46e2255
--- /dev/null
+++ b/mixed-content/beacon-request/http-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: block-all-mixed-content
diff --git a/mixed-content/beacon-request/meta-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/meta-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..4753bb3
--- /dev/null
+++ b/mixed-content/beacon-request/meta-csp/cross-origin-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: meta-csp
+                                 origin: cross-origin-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "meta-csp",
+          "origin": "cross-origin-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/meta-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html b/mixed-content/beacon-request/meta-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
new file mode 100644
index 0000000..e82c6cf
--- /dev/null
+++ b/mixed-content/beacon-request/meta-csp/same-host-http/top-level/no-redirect/blockable/opt-in-blocks.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: meta-csp
+                                 origin: same-host-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "meta-csp",
+          "origin": "same-host-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/meta-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html b/mixed-content/beacon-request/meta-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html
new file mode 100644
index 0000000..d9c87b3
--- /dev/null
+++ b/mixed-content/beacon-request/meta-csp/same-host-https/top-level/no-redirect/allowed/allowed.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Allowed content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of allowed content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/">
+    <meta name="assert" content="opt_in_method: meta-csp
+                                 origin: same-host-https
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: allowed">
+    <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "meta-csp",
+          "origin": "same-host-https",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "allowed"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/keep-scheme-redirect/blockable/no-opt-in-blocks.https.html b/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/keep-scheme-redirect/blockable/no-opt-in-blocks.https.html
new file mode 100644
index 0000000..2a4f6a5
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/keep-scheme-redirect/blockable/no-opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: cross-origin-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: keep-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "cross-origin-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "keep-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/no-redirect/blockable/no-opt-in-blocks.https.html b/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/no-redirect/blockable/no-opt-in-blocks.https.html
new file mode 100644
index 0000000..6b9d9b4
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/no-redirect/blockable/no-opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: cross-origin-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "cross-origin-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/swap-scheme-redirect/blockable/no-opt-in-blocks.https.html b/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/swap-scheme-redirect/blockable/no-opt-in-blocks.https.html
new file mode 100644
index 0000000..a3f302a
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/cross-origin-http/top-level/swap-scheme-redirect/blockable/no-opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: cross-origin-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: swap-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "cross-origin-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "swap-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/keep-scheme-redirect/blockable/no-opt-in-blocks.https.html b/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/keep-scheme-redirect/blockable/no-opt-in-blocks.https.html
new file mode 100644
index 0000000..0a507e5
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/keep-scheme-redirect/blockable/no-opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: same-host-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: keep-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "same-host-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "keep-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/no-redirect/blockable/no-opt-in-blocks.https.html b/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/no-redirect/blockable/no-opt-in-blocks.https.html
new file mode 100644
index 0000000..5375ae5
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/no-redirect/blockable/no-opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: same-host-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "same-host-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/swap-scheme-redirect/blockable/no-opt-in-blocks.https.html b/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/swap-scheme-redirect/blockable/no-opt-in-blocks.https.html
new file mode 100644
index 0000000..c6afc29
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/same-host-http/top-level/swap-scheme-redirect/blockable/no-opt-in-blocks.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Blockable content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of blockable content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/#category-blockable">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: same-host-http
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: swap-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: blocked">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "same-host-http",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "swap-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "blocked"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html b/mixed-content/beacon-request/no-opt-in/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html
new file mode 100644
index 0000000..2b83f31
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/same-host-https/top-level/keep-scheme-redirect/allowed/allowed.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Allowed content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of allowed content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: same-host-https
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: keep-scheme-redirect
+                                 subresource: beacon-request
+                                 expectation: allowed">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "same-host-https",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "keep-scheme-redirect",
+          "subresource": "beacon-request",
+          "expectation": "allowed"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/beacon-request/no-opt-in/same-host-https/top-level/no-redirect/allowed/allowed.https.html b/mixed-content/beacon-request/no-opt-in/same-host-https/top-level/no-redirect/allowed/allowed.https.html
new file mode 100644
index 0000000..efc6ce6
--- /dev/null
+++ b/mixed-content/beacon-request/no-opt-in/same-host-https/top-level/no-redirect/allowed/allowed.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by mixed-content/generic/tools/generate.py using mixed-content/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Mixed-Content: Allowed content</title>
+    <meta charset='utf-8'>
+    <meta name="description" content="Test behavior of allowed content.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="http://www.w3.org/TR/mixed-content/">
+    <meta name="assert" content="opt_in_method: no-opt-in
+                                 origin: same-host-https
+                                 source_scheme: https
+                                 context_nesting: top-level
+                                 redirection: no-redirect
+                                 subresource: beacon-request
+                                 expectation: allowed">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/mixed-content/generic/common.js"></script>
+    <script src="/mixed-content/generic/mixed-content-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      MixedContentTestCase(
+        {
+          "opt_in_method": "no-opt-in",
+          "origin": "same-host-https",
+          "source_scheme": "https",
+          "context_nesting": "top-level",
+          "redirection": "no-redirect",
+          "subresource": "beacon-request",
+          "expectation": "allowed"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/mixed-content/generic/common.js b/mixed-content/generic/common.js
index d04aabc..12e9262 100644
--- a/mixed-content/generic/common.js
+++ b/mixed-content/generic/common.js
@@ -82,9 +82,12 @@
  */
 function bindEvents(element, resolveEventName, rejectEventName) {
   element.eventPromise = new Promise(function(resolve, reject) {
-    element.addEventListener(resolveEventName  || "load", resolve);
-    element.addEventListener(rejectEventName || "error",
-                             function(e) { e.preventDefault(); reject(); } );
+    element.addEventListener(resolveEventName  || "load", function (e) {
+      resolve(e);
+    });
+    element.addEventListener(rejectEventName || "error", function(e) {
+      reject(e);
+    });
   });
 }
 
@@ -291,12 +294,34 @@
  * @return {Promise} The promise for success/error events.
  */
 function requestViaLinkPrefetch(url) {
-  // TODO(kristijanburnik): Check if prefetch should support load and error
-  // events. For now we assume it's not specified.
-  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ
-  return createRequestViaElement("link",
-                                 {"rel": "prefetch", "href": url},
-                                 document.head);
+  var link = document.createElement('link');
+  if (link.relList && link.relList.supports && link.relList.supports("prefetch")) {
+    return createRequestViaElement("link",
+                                   {"rel": "prefetch", "href": url},
+                                   document.head);
+  } else {
+    return Promise.reject("This browser does not support 'prefetch'.");
+  }
+}
+
+/**
+ * Initiates a new beacon request.
+ * @param {string} url The URL of a resource to prefetch.
+ * @return {Promise} The promise for success/error events.
+ */
+async function requestViaSendBeacon(url) {
+  function wait(ms) {
+    return new Promise(resolve => step_timeout(resolve, ms));
+  }
+  if (!navigator.sendBeacon(url)) {
+    // If mixed-content check fails, it should return false.
+    throw new Error('sendBeacon() fails.');
+  }
+  // We don't have a means to see the result of sendBeacon() request
+  // for sure. Let's wait for a while and let the generic test function
+  // ask the server for the result.
+  await wait(500);
+  return 'allowed';
 }
 
 /**
@@ -313,10 +338,18 @@
   var sourceElement = createElement("source", {});
 
   mediaElement.eventPromise = new Promise(function(resolve, reject) {
-    mediaElement.addEventListener("loadeddata", resolve);
+    mediaElement.addEventListener("loadeddata", function (e) {
+      resolve(e);
+    });
 
-    // Notice that the source element will raise the error.
-    sourceElement.addEventListener("error", reject);
+    // Safari doesn't fire an `error` event when blocking mixed content.
+    mediaElement.addEventListener("stalled", function(e) {
+      reject(e);
+    });
+
+    sourceElement.addEventListener("error", function(e) {
+      reject(e);
+    });
   });
 
   setAttributes(mediaElement, media_attrs);
@@ -337,7 +370,7 @@
 function requestViaVideo(url) {
   return createMediaElement("video",
                             {},
-                            {type: "video/ogg", src: url}).eventPromise;
+                            {"src": url}).eventPromise;
 }
 
 /**
@@ -349,7 +382,7 @@
 function requestViaAudio(url) {
   return createMediaElement("audio",
                             {},
-                            {type: "audio/wav", src: url}).eventPromise;
+                            {"type": "audio/wav", "src": url}).eventPromise;
 }
 
 /**
@@ -372,7 +405,7 @@
  * @return {Promise} The promise for success/error events.
  */
 function requestViaObject(url) {
-  return createRequestViaElement("object", {"data": url}, document.body);
+  return createRequestViaElement("object", {"data": url, "type": "text/html"}, document.body);
 }
 
 /**
diff --git a/mixed-content/generic/mixed-content-test-case.js b/mixed-content/generic/mixed-content-test-case.js
index 1dcf27e..f0e2d34 100644
--- a/mixed-content/generic/mixed-content-test-case.js
+++ b/mixed-content/generic/mixed-content-test-case.js
@@ -59,6 +59,7 @@
   var resourceMap = {
     "a-tag": requestViaAnchor,
     "area-tag": requestViaArea,
+    "beacon-request": requestViaSendBeacon,
     "fetch-request": requestViaFetch,
     "form-tag": requestViaForm,
     "iframe-tag": requestViaIframe,
@@ -81,6 +82,7 @@
   var contentType = {
     "a-tag": "text/html",
     "area-tag": "text/html",
+    "beacon-request": "text/plain",
     "fetch-request": "application/json",
     "form-tag": "text/html",
     "iframe-tag": "text/html",
@@ -125,25 +127,10 @@
         return resourceMap[scenario.subresource](resourceRequestUrl);
       }))
       .then(mixed_content_test.step_func(_ => {
-        mixed_content_test.step(function() {
-          assert_equals("allowed", scenario.expectation,
-                        "The triggered event should match '" +
-                        scenario.expectation + "'.");
-        }, "Check if success event was triggered.");
-
         // Send request to check if the key has been torn down.
         return xhrRequest(assertResourceRequestUrl);
       }))
       .catch(mixed_content_test.step_func(e => {
-        mixed_content_test.step(function() {
-          assert_equals("blocked", scenario.expectation,
-                        "The triggered event should match '" +
-                        scenario.expectation + "'.");
-          // TODO(kristijanburnik): param "error" can be an event or error.
-          // Map assertion by resource.
-          // e.g.: assert_equals(e.type, "error");
-        }, "Check if error event was triggered.");
-
         // When requestResource fails, we also check the key state.
         return xhrRequest(assertResourceRequestUrl);
       }))
diff --git a/mixed-content/generic/tools/common_paths.py b/mixed-content/generic/tools/common_paths.py
index 0bf41f2..5475cb6 100644
--- a/mixed-content/generic/tools/common_paths.py
+++ b/mixed-content/generic/tools/common_paths.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 import os, sys, json, re
 
 script_directory = os.path.dirname(os.path.abspath(__file__))
@@ -49,11 +51,11 @@
     with open(path_to_spec, "r") as f:
         try:
           return json.load(f)
-        except ValueError, ex:
-          print ex.message
+        except ValueError as ex:
+          print(ex.message)
           match = re_error_location.search(ex.message)
           if match:
             line_number, column = int(match.group(1)), int(match.group(2))
-            print read_nth_line(f, line_number).rstrip()
-            print " " * (column - 1) + "^"
+            print(read_nth_line(f, line_number).rstrip())
+            print(" " * (column - 1) + "^")
           sys.exit(1)
diff --git a/mixed-content/generic/tools/generate.py b/mixed-content/generic/tools/generate.py
index 6dcaebd..e7a315d 100755
--- a/mixed-content/generic/tools/generate.py
+++ b/mixed-content/generic/tools/generate.py
@@ -1,5 +1,7 @@
 #!/usr/bin/env python
 
+from __future__ import print_function
+
 import os, sys, json
 from common_paths import *
 import spec_validator
@@ -136,7 +138,7 @@
                                        spec,
                                        html_template)
                 else:
-                    print 'Excluding selection:', selection_path
+                    print('Excluding selection:', selection_path)
 
 
 def main(target, spec_filename):
diff --git a/mixed-content/generic/tools/spec_validator.py b/mixed-content/generic/tools/spec_validator.py
index a6acc10..0ae2990 100755
--- a/mixed-content/generic/tools/spec_validator.py
+++ b/mixed-content/generic/tools/spec_validator.py
@@ -1,5 +1,7 @@
 #!/usr/bin/env python
 
+from __future__ import print_function
+
 import json, sys
 from common_paths import *
 
@@ -143,16 +145,16 @@
     error_details = {}
     try:
         validate(spec_json, error_details)
-    except AssertionError, err:
-        print 'ERROR:', err.message
-        print json.dumps(error_details, indent=4)
+    except AssertionError as err:
+        print('ERROR:', err.message)
+        print(json.dumps(error_details, indent=4))
         sys.exit(1)
 
 
 def main():
     spec_json = load_spec_json();
     assert_valid_spec_json(spec_json)
-    print "Spec JSON is valid."
+    print("Spec JSON is valid.")
 
 
 if __name__ == '__main__':
diff --git a/mixed-content/spec.src.json b/mixed-content/spec.src.json
index e59fa9f..54479ef 100644
--- a/mixed-content/spec.src.json
+++ b/mixed-content/spec.src.json
@@ -240,7 +240,8 @@
         "object-tag",
         "picture-tag",
         "websocket-request",
-        "link-prefetch-tag"
+        "link-prefetch-tag",
+        "beacon-request"
       ],
       "optionally-blockable": [
         "img-tag",
diff --git a/mixed-content/spec_json.js b/mixed-content/spec_json.js
index f70e3cc..1bd1ecc 100644
--- a/mixed-content/spec_json.js
+++ b/mixed-content/spec_json.js
@@ -1 +1 @@
-var SPEC_JSON = {"test_expansion_schema": {"origin": ["same-host-https", "same-host-http", "cross-origin-https", "cross-origin-http", "same-host-wss", "same-host-ws", "cross-origin-wss", "cross-origin-ws"], "subresource": {"blockable": ["script-tag", "link-css-tag", "xhr-request", "worker-request", "fetch-request", "a-tag", "object-tag", "picture-tag", "websocket-request", "link-prefetch-tag"], "optionally-blockable": ["img-tag", "audio-tag", "video-tag"]}, "context_nesting": ["top-level", "sub-level"], "expectation": ["allowed", "blocked"], "expansion": ["default", "override"], "redirection": ["no-redirect", "keep-scheme-redirect", "swap-scheme-redirect"], "opt_in_method": ["no-opt-in", "http-csp", "meta-csp", "img-crossorigin"], "source_scheme": ["http", "https"]}, "specification": [{"test_expansion": [{"origin": ["cross-origin-http", "same-host-http"], "name": "opt-in-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": ["http-csp", "meta-csp"], "source_scheme": "https", "subresource": {"blockable": [], "optionally-blockable": "*"}}, {"origin": ["cross-origin-http", "same-host-http"], "name": "no-opt-in-allows", "redirection": "*", "expectation": "allowed", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "no-opt-in", "source_scheme": "https", "subresource": {"blockable": [], "optionally-blockable": "*"}}], "description": "Test behavior of optionally-blockable content", "specification_url": "http://www.w3.org/TR/mixed-content/#category-optionally-blockable", "name": "optionally-blockable", "title": "Optionally-blockable content"}, {"test_expansion": [{"origin": ["cross-origin-http", "same-host-http"], "name": "opt-in-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": ["http-csp", "meta-csp"], "source_scheme": "https", "subresource": {"blockable": "*", "optionally-blockable": []}}, {"origin": ["cross-origin-http", "same-host-http"], "name": "no-opt-in-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "no-opt-in", "source_scheme": "https", "subresource": {"blockable": "*", "optionally-blockable": []}}, {"origin": ["cross-origin-ws", "same-host-ws"], "name": "ws-downgrade-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": ["no-opt-in", "http-csp", "meta-csp"], "source_scheme": "https", "subresource": {"blockable": "websocket-request", "optionally-blockable": []}}], "description": "Test behavior of blockable content.", "specification_url": "http://www.w3.org/TR/mixed-content/#category-blockable", "name": "blockable", "title": "Blockable content"}, {"test_expansion": [{"origin": ["same-host-https"], "name": "allowed", "redirection": ["no-redirect", "keep-scheme-redirect"], "expectation": "allowed", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "*", "source_scheme": "https", "subresource": {"blockable": "*", "optionally-blockable": "*"}}, {"origin": ["same-host-wss"], "name": "websocket-allowed", "redirection": ["no-redirect", "keep-scheme-redirect"], "expectation": "allowed", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "*", "source_scheme": "https", "subresource": {"blockable": "websocket-request", "optionally-blockable": []}}], "description": "Test behavior of allowed content.", "specification_url": "http://www.w3.org/TR/mixed-content/", "name": "allowed", "title": "Allowed content"}], "excluded_tests": [{"origin": "*", "name": "Redundant-subresources", "redirection": "*", "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": "*", "source_scheme": "*", "subresource": {"blockable": ["a-tag"], "optionally-blockable": []}}, {"origin": ["same-host-https", "same-host-http", "cross-origin-https", "cross-origin-http"], "name": "Skip-origins-not-applicable-to-websockets", "redirection": "*", "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": "*", "source_scheme": "*", "subresource": {"blockable": ["websocket-request"], "optionally-blockable": []}}, {"origin": "*", "name": "TODO-opt-in-method-img-cross-origin", "redirection": "*", "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": "img-crossorigin", "source_scheme": "*", "subresource": {"blockable": "*", "optionally-blockable": "*"}}, {"origin": "*", "name": "Skip-redundant-for-opt-in-method", "redirection": ["keep-scheme-redirect", "swap-scheme-redirect"], "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": ["meta-csp", "img-crossorigin"], "source_scheme": "*", "subresource": {"blockable": "*", "optionally-blockable": "*"}}]};
+var SPEC_JSON = {"test_expansion_schema": {"origin": ["same-host-https", "same-host-http", "cross-origin-https", "cross-origin-http", "same-host-wss", "same-host-ws", "cross-origin-wss", "cross-origin-ws"], "subresource": {"blockable": ["script-tag", "link-css-tag", "xhr-request", "worker-request", "fetch-request", "a-tag", "object-tag", "picture-tag", "websocket-request", "link-prefetch-tag", "beacon-request"], "optionally-blockable": ["img-tag", "audio-tag", "video-tag"]}, "context_nesting": ["top-level", "sub-level"], "expectation": ["allowed", "blocked"], "expansion": ["default", "override"], "redirection": ["no-redirect", "keep-scheme-redirect", "swap-scheme-redirect"], "opt_in_method": ["no-opt-in", "http-csp", "meta-csp", "img-crossorigin"], "source_scheme": ["http", "https"]}, "specification": [{"test_expansion": [{"origin": ["cross-origin-http", "same-host-http"], "name": "opt-in-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": ["http-csp", "meta-csp"], "source_scheme": "https", "subresource": {"blockable": [], "optionally-blockable": "*"}}, {"origin": ["cross-origin-http", "same-host-http"], "name": "no-opt-in-allows", "redirection": "*", "expectation": "allowed", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "no-opt-in", "source_scheme": "https", "subresource": {"blockable": [], "optionally-blockable": "*"}}], "description": "Test behavior of optionally-blockable content", "specification_url": "http://www.w3.org/TR/mixed-content/#category-optionally-blockable", "name": "optionally-blockable", "title": "Optionally-blockable content"}, {"test_expansion": [{"origin": ["cross-origin-http", "same-host-http"], "name": "opt-in-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": ["http-csp", "meta-csp"], "source_scheme": "https", "subresource": {"blockable": "*", "optionally-blockable": []}}, {"origin": ["cross-origin-http", "same-host-http"], "name": "no-opt-in-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "no-opt-in", "source_scheme": "https", "subresource": {"blockable": "*", "optionally-blockable": []}}, {"origin": ["cross-origin-ws", "same-host-ws"], "name": "ws-downgrade-blocks", "redirection": "*", "expectation": "blocked", "expansion": "default", "context_nesting": "top-level", "opt_in_method": ["no-opt-in", "http-csp", "meta-csp"], "source_scheme": "https", "subresource": {"blockable": "websocket-request", "optionally-blockable": []}}], "description": "Test behavior of blockable content.", "specification_url": "http://www.w3.org/TR/mixed-content/#category-blockable", "name": "blockable", "title": "Blockable content"}, {"test_expansion": [{"origin": ["same-host-https"], "name": "allowed", "redirection": ["no-redirect", "keep-scheme-redirect"], "expectation": "allowed", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "*", "source_scheme": "https", "subresource": {"blockable": "*", "optionally-blockable": "*"}}, {"origin": ["same-host-wss"], "name": "websocket-allowed", "redirection": ["no-redirect", "keep-scheme-redirect"], "expectation": "allowed", "expansion": "default", "context_nesting": "top-level", "opt_in_method": "*", "source_scheme": "https", "subresource": {"blockable": "websocket-request", "optionally-blockable": []}}], "description": "Test behavior of allowed content.", "specification_url": "http://www.w3.org/TR/mixed-content/", "name": "allowed", "title": "Allowed content"}], "excluded_tests": [{"origin": "*", "name": "Redundant-subresources", "redirection": "*", "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": "*", "source_scheme": "*", "subresource": {"blockable": ["a-tag"], "optionally-blockable": []}}, {"origin": ["same-host-https", "same-host-http", "cross-origin-https", "cross-origin-http"], "name": "Skip-origins-not-applicable-to-websockets", "redirection": "*", "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": "*", "source_scheme": "*", "subresource": {"blockable": ["websocket-request"], "optionally-blockable": []}}, {"origin": "*", "name": "TODO-opt-in-method-img-cross-origin", "redirection": "*", "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": "img-crossorigin", "source_scheme": "*", "subresource": {"blockable": "*", "optionally-blockable": "*"}}, {"origin": "*", "name": "Skip-redundant-for-opt-in-method", "redirection": ["keep-scheme-redirect", "swap-scheme-redirect"], "expectation": "*", "expansion": "*", "context_nesting": "*", "opt_in_method": ["meta-csp", "img-crossorigin"], "source_scheme": "*", "subresource": {"blockable": "*", "optionally-blockable": "*"}}]};
diff --git a/navigation-timing/nav2_idlharness.html b/navigation-timing/nav2_idlharness.html
index eb7d94b..6d1a124 100644
--- a/navigation-timing/nav2_idlharness.html
+++ b/navigation-timing/nav2_idlharness.html
@@ -32,6 +32,7 @@
     [MeasureAs=PerformanceResourceTimingSizes] readonly attribute unsigned long long encodedBodySize;
     [MeasureAs=PerformanceResourceTimingSizes] readonly attribute unsigned long long decodedBodySize;
 };
+interface PerformanceEntry {};
 </pre>
 
 <pre id='idl'>
diff --git a/navigation-timing/nav2_test_redirect_server.html b/navigation-timing/nav2_test_redirect_server.html
index 41b24dd..1d7f6c9 100644
--- a/navigation-timing/nav2_test_redirect_server.html
+++ b/navigation-timing/nav2_test_redirect_server.html
@@ -11,7 +11,7 @@
         <script>
 
             function verifyTimingEventOrder(eventOrder, timingEntry) {
-                for (var i = 0; i < eventOrder.length - 1; i++) {
+                for (let i = 0; i < eventOrder.length - 1; i++) {
                     assert_true(timingEntry[eventOrder[i]] < timingEntry[eventOrder[i + 1]],
                         "Expected " + eventOrder[i] + " to be no greater than " + eventOrder[i + 1] + ".");
                 }
@@ -19,11 +19,13 @@
 
             function onload_test()
             {
-                var frame_performance = document.getElementById("frameContext").contentWindow.performance;
-                assert_equals(frame_performance.getEntriesByType("navigation")[0].type,
+                const frame_performance = document.getElementById("frameContext").contentWindow.performance;
+                const navigation_entry = frame_performance.getEntriesByType("navigation")[0];
+                assert_equals(navigation_entry.type,
                         "navigate",
                         "Expected navigation type  to be navigate.");
-                assert_equals(frame_performance.getEntriesByType("navigation")[0].redirectCount, 1, "Expected redirectCount to be 1.");
+                assert_equals(navigation_entry.redirectCount, 1, "Expected redirectCount to be 1.");
+                assert_equals(navigation_entry.name, 'http://' + document.location.host + '/navigation-timing/resources/blank_page_green.html');
 
                 var timgingEvents = [
                     'startTime',
diff --git a/navigation-timing/po-navigation.html b/navigation-timing/po-navigation.html
new file mode 100644
index 0000000..a54fb2a
--- /dev/null
+++ b/navigation-timing/po-navigation.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>PerformanceObservers: navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1>PerformanceObservers: navigation</h1>
+<p>
+Navigation will <a href="https://w3c.github.io/performance-timeline/#dfn-queue-a-performanceentry">queue a PerformanceEntry</a>.
+</p>
+<div id="log"></div>
+<script>
+  async_test(function (t) {
+    function checkEntry(pes) {
+      assert_equals(pes.length, 1, "Only one navigation timing entry");
+      assert_equals(pes[0].entryType, "navigation", "entryType is \"navigation\"");
+      assert_equals(pes[0].name, window.location.toString(), "name is the address of the document");
+    }
+    var observer = new PerformanceObserver(
+        t.step_func(function (entryList, obs) {
+          checkEntry(entryList.getEntries());
+          checkEntry(entryList.getEntriesByType("navigation"));
+          checkEntry(entryList.getEntriesByName(window.location.toString()));
+          observer.disconnect();
+          t.done();
+        })
+      );
+    observer.observe({entryTypes: ["navigation"]});
+  }, "navigation entry is observable");
+</script>
diff --git a/navigation-timing/resources/performance_attribute_sender.html b/navigation-timing/resources/performance_attribute_sender.html
new file mode 100644
index 0000000..99f5446
--- /dev/null
+++ b/navigation-timing/resources/performance_attribute_sender.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script>
+parent.postMessage([
+  performance.timing.connectStart,
+  performance.timing.navigationStart,
+  performance.timing.secureConnectionStart,
+  performance.timing.connectEnd
+], '*');
+</script>
+</body>
+</html>
diff --git a/navigation-timing/test_performance_attributes.sub.html b/navigation-timing/test_performance_attributes.sub.html
new file mode 100644
index 0000000..4c9ce6e
--- /dev/null
+++ b/navigation-timing/test_performance_attributes.sub.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!-- Use https to ensure secureConnectionStart is nontrivial, if it is supported. -->
+<iframe src="https://{{host}}:{{ports[https][0]}}/navigation-timing/resources/performance_attribute_sender.html"></iframe>
+<body>
+<script>
+async_test(function(t) {
+  window.addEventListener('message', t.step_func_done(function(event) {
+    const connectStart = event.data[0];
+    const navigationStart = event.data[1];
+    const secureConnectionStart = event.data[2];
+    const connectEnd = event.data[3];
+    assert_greater_than_equal(connectStart, navigationStart,
+      'performance.timing.connectStart >= performance.timing.navigationStart');
+    // secureConnectionStart is an optional attribute.
+    if (secureConnectionStart == undefined) {
+      return;
+    }
+    assert_greater_than_equal(secureConnectionStart, connectStart,
+      'performance.timing.secureConnectionStart >= performance.timing.connectStart');
+    assert_greater_than_equal(connectEnd, secureConnectionStart,
+      'performance.timing.connectEnd >= performance.timing.secureConnectionStart');
+  }));
+}, 'Check that performance.timing has reasonable values for secureConnectionStart and other attributes');
+</script>
+</body>
+</html>
diff --git a/navigation-timing/test_timing_server_redirect.html b/navigation-timing/test_timing_server_redirect.html
index 19147be..16567e8 100644
--- a/navigation-timing/test_timing_server_redirect.html
+++ b/navigation-timing/test_timing_server_redirect.html
@@ -32,6 +32,7 @@
                 test_timing_order('redirectStart', 'navigationStart');
                 test_timing_order('redirectEnd', 'redirectStart');
                 test_timing_order('fetchStart', 'redirectEnd');
+                test_timing_order('requestStart', 'fetchStart');
             }
         </script>
 
diff --git a/netinfo/OWNERS b/netinfo/OWNERS
new file mode 100644
index 0000000..095bbbd
--- /dev/null
+++ b/netinfo/OWNERS
@@ -0,0 +1,4 @@
+@jkarlin
+@igrigorik
+@marcoscaceres
+@tarunban
diff --git a/notifications/interfaces.html b/notifications/interfaces.html
index 95d648d..a348450 100644
--- a/notifications/interfaces.html
+++ b/notifications/interfaces.html
@@ -17,10 +17,13 @@
 typedef EventHandlerNonNull? EventHandler;
 </script>
 <script type=text/plain>
-[Constructor(DOMString title, optional NotificationOptions options)]
+[Constructor(DOMString title, optional NotificationOptions options),
+ Exposed=(Window,Worker)]
 interface Notification : EventTarget {
   static readonly attribute NotificationPermission permission;
-  static Promise<NotificationPermission> requestPermission(optional NotificationPermissionCallback callback);
+  [Exposed=Window] static Promise<NotificationPermission> requestPermission(optional NotificationPermissionCallback deprecatedCallback);
+
+  static readonly attribute unsigned long maxActions;
 
   attribute EventHandler onclick;
   attribute EventHandler onshow;
@@ -32,7 +35,16 @@
   readonly attribute DOMString lang;
   readonly attribute DOMString body;
   readonly attribute DOMString tag;
-  readonly attribute DOMString icon;
+  readonly attribute USVString image;
+  readonly attribute USVString icon;
+  readonly attribute USVString badge;
+  [SameObject] readonly attribute FrozenArray<unsigned long> vibrate;
+  readonly attribute DOMTimeStamp timestamp;
+  readonly attribute boolean renotify;
+  readonly attribute boolean silent;
+  readonly attribute boolean requireInteraction;
+  [SameObject] readonly attribute any data;
+  [SameObject] readonly attribute FrozenArray<NotificationAction> actions;
 
   void close();
 };
@@ -40,13 +52,18 @@
 dictionary NotificationOptions {
   NotificationDirection dir = "auto";
   DOMString lang = "";
-  DOMString body;
-  DOMString tag;
-  DOMString icon;
-};
-
-dictionary GetNotificationsOptions {
-  DOMString tag;
+  DOMString body = "";
+  DOMString tag = "";
+  USVString image;
+  USVString icon;
+  USVString badge;
+  VibratePattern vibrate;
+  DOMTimeStamp timestamp;
+  boolean renotify = false;
+  boolean silent = false;
+  boolean requireInteraction = false;
+  any data = null;
+  sequence<NotificationAction> actions = [];
 };
 
 enum NotificationPermission {
@@ -55,13 +72,19 @@
   "granted"
 };
 
-callback NotificationPermissionCallback = void (NotificationPermission permission);
-
 enum NotificationDirection {
   "auto",
   "ltr",
   "rtl"
 };
+
+dictionary NotificationAction {
+  required DOMString action;
+  required DOMString title;
+  USVString icon;
+};
+
+callback NotificationPermissionCallback = void (NotificationPermission permission);
 </script>
 <script>
 "use strict";
diff --git a/offscreen-canvas/OWNERS b/offscreen-canvas/OWNERS
new file mode 100644
index 0000000..3bb6edf
--- /dev/null
+++ b/offscreen-canvas/OWNERS
@@ -0,0 +1,5 @@
+@AmeliaBR
+@annevk
+@kenrussell
+@jdashg
+@fserb
diff --git a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html b/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html
similarity index 100%
rename from offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html
rename to offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html
diff --git a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html b/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html
similarity index 100%
rename from offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html
rename to offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html
diff --git a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
index 57210ef..ef66d5e 100644
--- a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
+++ b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
@@ -16,17 +16,16 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.fillStyle = '#0f0';
-ctx.fillRect(0, 0, 100, 50);
 var offscreenCanvas2 = new OffscreenCanvas(0, 10);
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 10;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 0;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
-_assertPixelApprox(offscreenCanvas, 50,25, 0,255,0,255, "50,25", "0,255,0,255", 2);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
 
 t.done();
 
diff --git a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js
index 45dfff6..3a17cfe 100644
--- a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js
+++ b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js
@@ -12,17 +12,16 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.fillStyle = '#0f0';
-ctx.fillRect(0, 0, 100, 50);
 var offscreenCanvas2 = new OffscreenCanvas(0, 10);
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 10;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 0;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
-_assertPixelApprox(offscreenCanvas, 50,25, 0,255,0,255, "50,25", "0,255,0,255", 2);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
 
 t.done();
 
diff --git a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.commit.w.html b/offscreen-canvas/the-offscreen-canvas/offscreencanvas.commit.w.html
index 3268021..380686f 100644
--- a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.commit.w.html
+++ b/offscreen-canvas/the-offscreen-canvas/offscreencanvas.commit.w.html
@@ -8,10 +8,15 @@
 
 function testCommitPushesContents(offscreenCanvas)
 {
+  try {
     var ctx = offscreenCanvas.getContext('2d');
     ctx.fillStyle = "#0f0";
     ctx.fillRect(0, 0, 10, 10);
     ctx.commit();
+    return true;
+  } catch(e) {
+    return false;
+  }
 }
 
 function isInvalidStateError(funcStr, ctx)
@@ -37,8 +42,7 @@
 self.onmessage = function(e) {
     switch (e.data.msg) {
         case 'test1':
-            testCommitPushesContents(e.data.data);
-            self.postMessage('worker finished');
+            self.postMessage(testCommitPushesContents(e.data.data));
             break;
         case 'test2':
             self.postMessage(testCommitException());
@@ -71,7 +75,8 @@
     var offscreenCanvas = placeholder.transferControlToOffscreen();
     var worker = makeWorker(document.getElementById("myWorker").textContent);
     worker.addEventListener('message', t.step_func_done(function(msg) {
-        verifyPlaceholder(placeholder);
+      assert_true(msg.data);
+      verifyPlaceholder(placeholder);
     }));
     worker.postMessage({msg: 'test1', data: offscreenCanvas}, [offscreenCanvas]);
 }, "Test that calling OffscreenCanvas's commit pushes its contents to its placeholder.");
@@ -85,4 +90,3 @@
 }, "Test that calling commit on an OffscreenCanvas that is not transferred from a HTMLCanvasElement throws an exception in a worker.");
 
 </script>
-
diff --git a/offscreen-canvas/tools/tests2d.yaml b/offscreen-canvas/tools/tests2d.yaml
index fc3b158..f542d3c 100644
--- a/offscreen-canvas/tools/tests2d.yaml
+++ b/offscreen-canvas/tools/tests2d.yaml
@@ -4417,17 +4417,16 @@
   testing:
     - 2d.drawImage.zerocanvas
   code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
     var offscreenCanvas2 = new OffscreenCanvas(0, 10);
-    ctx.drawImage(offscreenCanvas2, 0, 0);
+    @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0);
+
     offscreenCanvas2.width = 10;
     offscreenCanvas2.height = 0;
-    ctx.drawImage(offscreenCanvas2, 0, 0);
+    @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0);
+
     offscreenCanvas2.width = 0;
     offscreenCanvas2.height = 0;
-    ctx.drawImage(offscreenCanvas2, 0, 0);
-    @assert pixel 50,25 ==~ 0,255,0,255;
+    @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0);
 
 - name: 2d.drawImage.wrongtype
   desc: Incorrect image types in drawImage do not match any defined overloads, so WebIDL throws a TypeError
diff --git a/old-tests/OWNERS b/old-tests/OWNERS
new file mode 100644
index 0000000..04a7557
--- /dev/null
+++ b/old-tests/OWNERS
@@ -0,0 +1,2 @@
+@foolip
+@Ms2ger
diff --git a/old-tests/submission/Microsoft/history/history_000.htm b/old-tests/submission/Microsoft/history/history_000.htm
index 771ed15..855612b 100644
--- a/old-tests/submission/Microsoft/history/history_000.htm
+++ b/old-tests/submission/Microsoft/history/history_000.htm
@@ -322,8 +322,25 @@
         }
 
         add_result_callback(testFinished);
-        //start the first test manually
-        queue(testCollection[testIndex]);
+
+        function startTestsWhenIframesLoaded() {
+          if (testframe1.contentWindow.document.readyState != 'complete' ||
+              testframe2.contentWindow.document.readyState != 'complete') {
+            return;
+          }
+          testframe1.removeEventListener('load', startTestsWhenIframesLoaded, false);
+          testframe2.removeEventListener('load', startTestsWhenIframesLoaded, false);
+
+          //start the first test
+          queue(testCollection[testIndex]);
+        }
+
+        // add listeners to trigger the tests once the iframes are loaded,
+        // but also try to start it right away in case the load events have
+        // already fired and we missed them.
+        testframe1.addEventListener('load', startTestsWhenIframesLoaded, false);
+        testframe2.addEventListener('load', startTestsWhenIframesLoaded, false);
+        startTestsWhenIframesLoaded();
      </script>
 </body>
 </html>
diff --git a/old-tests/submission/migration.txt b/old-tests/submission/migration.txt
deleted file mode 100644
index 9480d75..0000000
--- a/old-tests/submission/migration.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-
-TODO:
-
-    Google          (not sure how much of that is what's already in)
-    Infraware       (these come bundled with resources and require lots of rewriting)
-    Intel           (need to check how much overlap there is with existing stuff)
-    Microsoft       (lots of things, need to check overlap)
-    Ms2ger          (Ms2ger is doing those)
-    Opera           (split into multiple PRs)
-    TestTWF_Paris
-    W3C
-
-DONE:
-
-    Apple
-    AryehGregor
-    Baidu
-    Comcast
-    DavidCarlisle
-    html5bidi
-    MathiasBynens
-    PhilipTaylor
-    MOSQUITO
-    Mozilla
-    WebKit
-    
\ No newline at end of file
diff --git a/orientation-event/OWNERS b/orientation-event/OWNERS
new file mode 100644
index 0000000..77a3bbc
--- /dev/null
+++ b/orientation-event/OWNERS
@@ -0,0 +1,2 @@
+@reillyeon
+@timvolodine
diff --git a/orientation-sensor/AbsoluteOrientationSensor-disabled-by-feature-policy.https.html b/orientation-sensor/AbsoluteOrientationSensor-disabled-by-feature-policy.https.html
new file mode 100644
index 0000000..7f42571
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-disabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AbsoluteOrientationSensor Feature Policy Test: Disabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_disabled('AbsoluteOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/AbsoluteOrientationSensor-disabled-by-feature-policy.https.html.headers b/orientation-sensor/AbsoluteOrientationSensor-disabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..47d7617
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-disabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer 'none'; gyroscope 'none'; magnetometer 'none'
diff --git a/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
new file mode 100644
index 0000000..ab34f5b
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AbsoluteOrientationSensor Feature Policy Test: Enabled by attribute redirect on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute_redirect_on_load('AbsoluteOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy-attribute.https.html b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy-attribute.https.html
new file mode 100644
index 0000000..cb23e8b
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy-attribute.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AbsoluteOrientationSensor Feature Policy Test: Enabled by attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute('AbsoluteOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy.https.html b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy.https.html
new file mode 100644
index 0000000..cc09eea
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AbsoluteOrientationSensor Feature Policy Test: Enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled('AbsoluteOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy.https.html.headers b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..58366d9
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-enabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer *; gyroscope *; magnetometer *
diff --git a/orientation-sensor/AbsoluteOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html b/orientation-sensor/AbsoluteOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html
new file mode 100644
index 0000000..62e21be
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>AbsoluteOrientationSensor Feature Policy Test: Enabled on self origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_on_self_origin('AbsoluteOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/AbsoluteOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers b/orientation-sensor/AbsoluteOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..31cab0d
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer 'self'; gyroscope 'self'; magnetometer 'self'
diff --git a/orientation-sensor/AbsoluteOrientationSensor.https.html b/orientation-sensor/AbsoluteOrientationSensor.https.html
new file mode 100644
index 0000000..c476047
--- /dev/null
+++ b/orientation-sensor/AbsoluteOrientationSensor.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>OrientationSensor Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/orientation-sensor/">
+<link rel="help" href="https://w3c.github.io/sensors/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/generic-sensor/generic-sensor-tests.js"></script>
+<script src="/orientation-sensor/orientation-sensor-tests.js"></script>
+<div id="log"></div>
+
+<script>
+runOrienationSensorTests('AbsoluteOrientationSensor');
+runGenericSensorTests('AbsoluteOrientationSensor');
+</script>
diff --git a/orientation-sensor/OrientationSensor.https.html b/orientation-sensor/OrientationSensor.https.html
deleted file mode 100644
index 5e728a6..0000000
--- a/orientation-sensor/OrientationSensor.https.html
+++ /dev/null
@@ -1,102 +0,0 @@
- <!DOCTYPE html>
-<meta charset="utf-8">
-<title>OrientationSensor Test</title>
-<link rel="author" title="Intel" href="http://www.intel.com">
-<link rel="help" href="https://w3c.github.io/orientation-sensor/">
-<link rel="help" href="https://w3c.github.io/sensors/">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/generic-sensor/generic-sensor-tests.js"></script>
-<div id="log"></div>
-
-<script>
-
-//IEEE 754: single precision retricts to 7 decimal digits
-const float_precision = 1e-7;
-
-function create_matrix(quat) {
-  const X = quat[0];
-  const Y = quat[1];
-  const Z = quat[2];
-  const W = quat[3];
-  const mat = new Array(
-    1-2*Y*Y-2*Z*Z, 2*X*Y-2*Z*W, 2*X*Z+2*Y*W, 0,
-    2*X*Y+2*Z*W, 1-2*X*X-2*Z*Z, 2*Y*Z-2*X*W, 0,
-    2*X*Z-2*Y*W, 2*Y*Z+2*W*X, 1-2*X*X-2*Y*Y, 0,
-    0, 0, 0, 1
-  );
-  return mat;
-}
-
-async function checkQuaternion(t, sensorType) {
-  const sensor = new sensorType();
-  const eventWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
-  sensor.start();
-
-  await eventWatcher.wait_for("reading");
-  assert_equals(sensor.quaternion.length, 4);
-  assert_true(sensor.quaternion instanceof Array);
-  sensor.stop();
-};
-
-async function checkPopulateMatrix(t, sensorType) {
-  const sensor = new sensorType();
-  const eventWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
-
-  //Throws with insufficient buffer space.
-  assert_throws({ name: 'TypeError' }, () => sensor.populateMatrix(new Float32Array(15)));
-
-  //Throws if no orientation data available.
-  assert_throws({ name: 'NotReadableError' }, () => sensor.populateMatrix(new Float32Array(16)));
-
-  if (window.SharedArrayBuffer) {
-    // Throws if passed SharedArrayBuffer view.
-    assert_throws({ name: 'TypeError' }, () => sensor.populateMatrix(new Float32Array(new SharedArrayBuffer(16))));
-  }
-
-  sensor.start();
-  await eventWatcher.wait_for("reading");
-  const quat = sensor.quaternion;
-  const mat_expect = create_matrix(quat);
-
-  // Works for all supported types.
-  const mat_32 = new Float32Array(16);
-  sensor.populateMatrix(mat_32);
-  assert_array_approx_equals(mat_32, mat_expect, float_precision);
-
-  const mat_64 = new Float64Array(16);
-  sensor.populateMatrix(mat_64);
-  assert_array_equals(mat_64, mat_expect);
-
-  const mat_dom = new DOMMatrix();
-  sensor.populateMatrix(mat_dom);
-  assert_array_equals(mat_dom.toFloat64Array(), mat_expect);
-
-  // Sets every matrix element.
-  mat_64.fill(123);
-  sensor.populateMatrix(mat_64);
-  assert_array_equals(mat_64, mat_expect);
-
-  sensor.stop();
-}
-
-promise_test(t => {
-  return checkQuaternion(t, AbsoluteOrientationSensor);
-}, "Test AbsoluteOrientationSensor.quaternion return a four-element FrozenArray.");
-
-promise_test(t => {
-  return checkQuaternion(t, RelativeOrientationSensor);
-}, "Test RelativeOrientationSensor.quaternion return a four-element FrozenArray.");
-
-promise_test(t => {
-  return checkPopulateMatrix(t, AbsoluteOrientationSensor);
-}, "Test AbsoluteOrientationSensor.populateMatrix() method works correctly.");
-
-promise_test(t => {
-  return checkPopulateMatrix(t, RelativeOrientationSensor);
-}, "Test RelativeOrientationSensor.populateMatrix() method works correctly.");
-
-runGenericSensorTests(AbsoluteOrientationSensor);
-runGenericSensorTests(RelativeOrientationSensor);
-
-</script>
diff --git a/orientation-sensor/OrientationSensor_onerror-manual.https.html b/orientation-sensor/OrientationSensor_onerror-manual.https.html
index d70ea30..415a63c 100644
--- a/orientation-sensor/OrientationSensor_onerror-manual.https.html
+++ b/orientation-sensor/OrientationSensor_onerror-manual.https.html
@@ -16,7 +16,7 @@
 </ol>
 <script>
 
-runGenericSensorOnerror(AbsoluteOrientationSensor);
-runGenericSensorOnerror(RelativeOrientationSensor);
+runGenericSensorOnerror('AbsoluteOrientationSensor');
+runGenericSensorOnerror('RelativeOrientationSensor');
 
 </script>
diff --git a/orientation-sensor/RelativeOrientationSensor-disabled-by-feature-policy.https.html b/orientation-sensor/RelativeOrientationSensor-disabled-by-feature-policy.https.html
new file mode 100644
index 0000000..d97d40b
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-disabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>RelativeOrientationSensor Feature Policy Test: Disabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_disabled('RelativeOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/RelativeOrientationSensor-disabled-by-feature-policy.https.html.headers b/orientation-sensor/RelativeOrientationSensor-disabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..01f1b4b
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-disabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer 'none'; gyroscope 'none'
diff --git a/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
new file mode 100644
index 0000000..3a6173a
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy-attribute-redirect-on-load.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>RelativeOrientationSensor Feature Policy Test: Enabled by attribute redirect on load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute_redirect_on_load('RelativeOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy-attribute.https.html b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy-attribute.https.html
new file mode 100644
index 0000000..e8d3002
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy-attribute.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>RelativeOrientationSensor Feature Policy Test: Enabled by attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_by_attribute('RelativeOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy.https.html b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy.https.html
new file mode 100644
index 0000000..d9f38f2
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>RelativeOrientationSensor Feature Policy Test: Enabled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled('RelativeOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy.https.html.headers b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..717e3dd
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-enabled-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer *; gyroscope *
diff --git a/orientation-sensor/RelativeOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html b/orientation-sensor/RelativeOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html
new file mode 100644
index 0000000..d63bf4e
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<title>RelativeOrientationSensor Feature Policy Test: Enabled on self origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script src="/generic-sensor/generic-sensor-feature-policy-test.sub.js"></script>
+<script>
+"use strict";
+
+run_fp_tests_enabled_on_self_origin('RelativeOrientationSensor');
+</script>
+</body>
diff --git a/orientation-sensor/RelativeOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers b/orientation-sensor/RelativeOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
new file mode 100644
index 0000000..ad428b2
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor-enabled-on-self-origin-by-feature-policy.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: accelerometer 'self'; gyroscope 'self'
diff --git a/orientation-sensor/RelativeOrientationSensor.https.html b/orientation-sensor/RelativeOrientationSensor.https.html
new file mode 100644
index 0000000..9521a15
--- /dev/null
+++ b/orientation-sensor/RelativeOrientationSensor.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>OrientationSensor Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/orientation-sensor/">
+<link rel="help" href="https://w3c.github.io/sensors/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/generic-sensor/generic-sensor-tests.js"></script>
+<script src="/orientation-sensor/orientation-sensor-tests.js"></script>
+<div id="log"></div>
+
+<script>
+runOrienationSensorTests('RelativeOrientationSensor');
+runGenericSensorTests('RelativeOrientationSensor');
+</script>
diff --git a/orientation-sensor/idlharness.https.html b/orientation-sensor/idlharness.https.html
index d765861..c87d99f 100644
--- a/orientation-sensor/idlharness.https.html
+++ b/orientation-sensor/idlharness.https.html
@@ -15,7 +15,7 @@
   const idl_array = new IdlArray();
   idl_array.add_untested_idls(dom);
   idl_array.add_untested_idls('interface EventHandler {};');
-  idl_array.add_idls(generic_sensor, { only: ['Sensor'] });
+  idl_array.add_idls(generic_sensor, { only: ['Sensor', 'SensorOptions'] });
   idl_array.add_idls(orientation_sensor);
   idl_array.add_objects({
     AbsoluteOrientationSensor: ['new AbsoluteOrientationSensor();'],
@@ -31,7 +31,7 @@
 promise_test(() => {
   return Promise.all([
     "/interfaces/dom.idl",
-    "/interfaces/generic-sensor.idl",
+    "/interfaces/sensors.idl",
     "/interfaces/orientation-sensor.idl",
   ].map(fetchText)).then(doTest);
 }, "Test IDL implementation of Orientation Sensor");
diff --git a/orientation-sensor/orientation-sensor-tests.js b/orientation-sensor/orientation-sensor-tests.js
new file mode 100644
index 0000000..74e360a
--- /dev/null
+++ b/orientation-sensor/orientation-sensor-tests.js
@@ -0,0 +1,83 @@
+//IEEE 754: single precision retricts to 7 decimal digits
+const float_precision = 1e-7;
+
+function create_matrix(quat) {
+  const X = quat[0];
+  const Y = quat[1];
+  const Z = quat[2];
+  const W = quat[3];
+  const mat = new Array(
+    1-2*Y*Y-2*Z*Z, 2*X*Y-2*Z*W, 2*X*Z+2*Y*W, 0,
+    2*X*Y+2*Z*W, 1-2*X*X-2*Z*Z, 2*Y*Z-2*X*W, 0,
+    2*X*Z-2*Y*W, 2*Y*Z+2*W*X, 1-2*X*X-2*Y*Y, 0,
+    0, 0, 0, 1
+  );
+  return mat;
+}
+
+async function checkQuaternion(t, sensorType) {
+  const sensor = new sensorType();
+  const eventWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+  sensor.start();
+
+  await eventWatcher.wait_for("reading");
+  assert_equals(sensor.quaternion.length, 4);
+  assert_true(sensor.quaternion instanceof Array);
+  sensor.stop();
+};
+
+async function checkPopulateMatrix(t, sensorType) {
+  const sensor = new sensorType();
+  const eventWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+
+  //Throws with insufficient buffer space.
+  assert_throws({ name: 'TypeError' }, () => sensor.populateMatrix(new Float32Array(15)));
+
+  //Throws if no orientation data available.
+  assert_throws({ name: 'NotReadableError' }, () => sensor.populateMatrix(new Float32Array(16)));
+
+  if (window.SharedArrayBuffer) {
+    // Throws if passed SharedArrayBuffer view.
+    assert_throws({ name: 'TypeError' }, () => sensor.populateMatrix(new Float32Array(new SharedArrayBuffer(16))));
+  }
+
+  sensor.start();
+  await eventWatcher.wait_for("reading");
+  const quat = sensor.quaternion;
+  const mat_expect = create_matrix(quat);
+
+  // Works for all supported types.
+  const mat_32 = new Float32Array(16);
+  sensor.populateMatrix(mat_32);
+  assert_array_approx_equals(mat_32, mat_expect, float_precision);
+
+  const mat_64 = new Float64Array(16);
+  sensor.populateMatrix(mat_64);
+  assert_array_equals(mat_64, mat_expect);
+
+  const mat_dom = new DOMMatrix();
+  sensor.populateMatrix(mat_dom);
+  assert_array_equals(mat_dom.toFloat64Array(), mat_expect);
+
+  // Sets every matrix element.
+  mat_64.fill(123);
+  sensor.populateMatrix(mat_64);
+  assert_array_equals(mat_64, mat_expect);
+
+  sensor.stop();
+}
+
+function runOrienationSensorTests(sensorName) {
+  const sensorType = self[sensorName];
+
+  sensor_test(async t => {
+    assert_true(sensorName in self);
+    return checkQuaternion(t, sensorType);
+  }, `${sensorName}.quaternion return a four-element FrozenArray.`);
+
+  sensor_test(async t => {
+    assert_true(sensorName in self);
+    return checkPopulateMatrix(t, sensorType);
+  }, `${sensorName}.populateMatrix() method works correctly.`);
+}
+
diff --git a/page-visibility/test_minimize-manual.html b/page-visibility/test_minimize-manual.html
index 28e8486..27fd0d7 100644
--- a/page-visibility/test_minimize-manual.html
+++ b/page-visibility/test_minimize-manual.html
@@ -5,6 +5,7 @@
         <title>Page Visibility API Operation While Minimizing Browser Window</title>
         <meta name="flags" content="interact" />
         <script type="text/javascript" src="/resources/testharness.js"></script>
+        <script type="text/javascript" src="/resources/testharnessreport.js"></script>
         <script type="text/javascript" src="resources/pagevistestharness.js"></script>
 
     </head>
diff --git a/page-visibility/test_tab_state_change-manual.html b/page-visibility/test_tab_state_change-manual.html
index c701e50..75d1da6 100644
--- a/page-visibility/test_tab_state_change-manual.html
+++ b/page-visibility/test_tab_state_change-manual.html
@@ -5,6 +5,7 @@
         <title>Page Visibility API Operation While Changing Tabs</title>
         <meta name="flags" content="interact" />
         <script type="text/javascript" src="/resources/testharness.js"></script>
+        <script type="text/javascript" src="/resources/testharnessreport.js"></script>
         <script type="text/javascript" src="resources/pagevistestharness.js"></script>
 
     </head>
diff --git a/paint-timing/child-painting-first-image.html b/paint-timing/child-painting-first-image.html
index 99f761f..4671604 100644
--- a/paint-timing/child-painting-first-image.html
+++ b/paint-timing/child-painting-first-image.html
@@ -7,9 +7,9 @@
 async_test(function (t) {
     window.addEventListener('message', t.step_func(e => {
         assert_equals(e.data, '2 paint first-paint paint first-contentful-paint');
-        const bufferedEntries = performance.getEntriesByType('paint');
         // When only child frame paints, expect only first-paint.
         t.step_timeout( function() {
+            const bufferedEntries = performance.getEntriesByType('paint');
             assert_equals(bufferedEntries.length, 1);
             assert_equals(bufferedEntries[0].entryType, 'paint');
             assert_equals(bufferedEntries[0].name, 'first-paint');
diff --git a/paint-timing/sibling-painting-first-image.html b/paint-timing/sibling-painting-first-image.html
new file mode 100644
index 0000000..d4197e8
--- /dev/null
+++ b/paint-timing/sibling-painting-first-image.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ <!-- This iframe will have a sibling that paints, we want to ensure it does not detect that paint. -->
+<iframe id="listening-iframe" src="resources/subframe-sending-paint.html"></iframe>
+<script>
+async_test(function (t) {
+    let paintingIframeHasDispatchedEntries = false;
+    window.addEventListener('message', t.step_func(e => {
+        if (!paintingIframeHasDispatchedEntries) {
+            // Check paint-timing entries from the painting iframe.
+            assert_equals(e.data, '2 paint first-paint paint first-contentful-paint');
+            paintingIframeHasDispatchedEntries = true;
+            // Ask the listening iframe to send its paint-timing entries.
+            document.getElementById('listening-iframe').
+                contentWindow.postMessage('', '*');
+            return;
+        }
+        // Check the paint-timing entries from the listening iframe.
+        assert_equals(e.data, '0');
+        // Check that current frame receives first-paint but not first-contentful-paint.
+        const bufferedEntries = performance.getEntriesByType('paint');
+        assert_equals(bufferedEntries.length, 1);
+        assert_equals(bufferedEntries[0].entryType, 'paint');
+        assert_equals(bufferedEntries[0].name, 'first-paint');
+        t.done();
+    }));
+}, 'Frame ignores paint-timing events fired from sibling frame.');
+</script>
+<!-- This iframe is where all of the painting occurs. -->
+<iframe id="painting-iframe" src="resources/subframe-painting.html"></iframe>
+</body>
+</html>
diff --git a/payment-handler/app-can-make-payment.js b/payment-handler/app-can-make-payment.js
new file mode 100644
index 0000000..0bb9490
--- /dev/null
+++ b/payment-handler/app-can-make-payment.js
@@ -0,0 +1,114 @@
+self.addEventListener('canmakepayment', event => {
+  if (event.methodData.length !== 1) {
+    const msg = 'Expected exactly one method data.';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const [method] = event.methodData;
+  if (!method || method.supportedMethods.length !== 1) {
+    const msg = 'Expected exactly one supported method name';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (method.data.defaultParameter !== 'defaultValue') {
+    const msg = `Unexpected value for "defaultParameter": ${
+      method.data.defaultParameter
+    }`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if ('defaultUnsupportedParameter' in method.data) {
+    const msg = 'Unexpected "defaultUnsupportedParameter"';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (event.modifiers.length !== 1) {
+    const msg = 'Expected exactly one modifier';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const [modifier] = event.modifiers;
+
+  if (!modifier || modifier.supportedMethods.length !== 1) {
+    const msg = 'Expected exactly one supported method name in modifier';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  for (const member of [
+    'additionalDisplayItems',
+    'modifiedUnsupportedParameter',
+    'total',
+  ]) {
+    if (member in modifier) {
+      const msg = `Unexpected member "${member}" in modifier`;
+      event.respondWith(Promise.reject(new Error(msg)));
+      return;
+    }
+  }
+
+  const [methodName] = method.supportedMethods;
+  if (methodName === 'basic-card') {
+    const msg =
+      '"basic-card" payment method must never be checked in CanMakePaymentEvent';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const [modifierMethodName] = modifier.supportedMethods;
+  if (modifierMethodName !== methodName) {
+    const msg = `Unexpected modifier method name: "${modifierMethodName}". Expected "${methodName}".`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (modifier.data.modifiedParameter !== 'modifiedValue') {
+    const msg = `Unexpected value for 'modifiedParameter': ${
+      modifier.data.modifiedParameter
+    }`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const methodAsURL = new URL(methodName);
+  if (event.topOrigin !== methodAsURL.origin) {
+    const msg = `Unexpected event.topOrigin: "${
+      event.topOrigin
+    }". Expected "${methodAsURL.origin}".`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (event.paymentRequestOrigin !== methodAsURL.origin) {
+    const msg = `Unexpected iframe origin ${event.paymentRequestOrigin}`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  switch (methodAsURL.pathname.substr(1)) {
+    case 'canMakePayment-true':
+      event.respondWith(true);
+      break;
+    case 'canMakePayment-false':
+      event.respondWith(false);
+      break;
+    case 'canMakePayment-promise-true':
+      event.respondWith(Promise.resolve(true));
+      break;
+    case 'canMakePayment-promise-false':
+      event.respondWith(Promise.resolve(false));
+      break;
+    case 'canMakePayment-custom-error':
+      event.respondWith(Promise.reject(new Error('Custom error')));
+      break;
+    default:
+      const msg = `Unrecognized payment method name "${methodName}".`;
+      event.respondWith(Promise.reject(new Error(msg)));
+      break;
+  }
+});
diff --git a/payment-handler/basic-card.js b/payment-handler/basic-card.js
new file mode 100644
index 0000000..86fcea7
--- /dev/null
+++ b/payment-handler/basic-card.js
@@ -0,0 +1,117 @@
+self.addEventListener('paymentrequest', event => {
+  const expectedId = 'test-payment-request-identifier';
+  if (event.paymentRequestId !== expectedId) {
+    const msg = `Expected payment request identifier "${expectedId}", but got "${
+      event.paymentRequestId
+    }"`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (event.methodData.length !== 1) {
+    const msg = `Expected one method data, but got ${
+      event.methodData.length
+    } instead`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const methodData = event.methodData[0];
+  const expectedMethodName = 'basic-card';
+  if (methodData.supportedMethods !== expectedMethodName) {
+    const msg = `Expected payment method name "${expectedMethodName}", but got "${
+      methodData.supportedMethods
+    }"`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (methodData.data.supportedNetworks) {
+    const msg =
+      'Expected no supported networks in payment method specific data';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const supportedTypes = methodData.data.supportedTypes;
+  if (!supportedTypes) {
+    const msg = 'Expected supported types in payment method specific data';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (supportedTypes.length !== 1) {
+    const msg = `Expected one supported type, but got ${
+      supportedTypes.length
+    } instead`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const supportedType = supportedTypes[0];
+  const expectedSupportedType = 'prepaid';
+  if (supportedType !== expectedSupportedType) {
+    const msg = `Expected supported type "${expectedSupportedType}", but got "${supportedType}"`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (methodData.displayItems) {
+    const msg = 'Expected no display items';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const total = event.total;
+  if (!total) {
+    const msg = 'Expected total';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  if (total.label) {
+    const msg = 'Expected no total label';
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const expectedCurrency = 'USD';
+  if (total.currency !== expectedCurrency) {
+    const msg = `Expected currency "${expectedCurrency}", but got "${
+      total.currency
+    }"`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  const expectedValue = '0.01';
+  if (total.value !== expectedValue) {
+    const msg = `Expected value "${expectedValue}", but got "${total.value}"`;
+    event.respondWith(Promise.reject(new Error(msg)));
+    return;
+  }
+
+  event.respondWith({
+    methodName: 'basic-card',
+    details: {
+      billingAddress: {
+        addressLine: ['1875 Explorer St #1000'],
+        city: 'Reston',
+        country: 'US',
+        dependentLocality: '',
+        languageCode: '',
+        organization: 'Google',
+        phone: '+15555555555',
+        postalCode: '20190',
+        recipient: 'Jon Doe',
+        region: 'VA',
+        sortingCode: '',
+      },
+      cardNumber: '4111111111111111',
+      cardSecurityCode: '123',
+      cardholderName: 'Jon Doe',
+      expiryMonth: '12',
+      expiryYear: '2028',
+    },
+  });
+});
diff --git a/payment-handler/basic-card.json b/payment-handler/basic-card.json
new file mode 100644
index 0000000..002dd87
--- /dev/null
+++ b/payment-handler/basic-card.json
@@ -0,0 +1,10 @@
+{
+  "name": "Test Basic Card Payment Handler",
+  "icons": [
+    {
+      "src": "/images/rgrg-256x256.png",
+      "sizes": "256x256",
+      "type": "image/png"
+    }
+  ]
+}
diff --git a/payment-handler/can-make-payment-event-constructor.https.html b/payment-handler/can-make-payment-event-constructor.https.html
new file mode 100644
index 0000000..d8480a2
--- /dev/null
+++ b/payment-handler/can-make-payment-event-constructor.https.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for CanMakePaymentEvent Constructor (window)</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#dom-canmakepaymentevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+  assert_false('CanMakePaymentEvent' in window);
+}, 'CanMakePaymentEvent constructor must not be exposed in window');
+</script>
diff --git a/payment-handler/can-make-payment-event-constructor.https.worker.js b/payment-handler/can-make-payment-event-constructor.https.worker.js
new file mode 100644
index 0000000..8ae05d7
--- /dev/null
+++ b/payment-handler/can-make-payment-event-constructor.https.worker.js
@@ -0,0 +1,49 @@
+// https://w3c.github.io/payment-handler/#the-canmakepaymentevent
+
+'use strict';
+
+if (self.importScripts) {
+  importScripts('/resources/testharness.js');
+}
+
+test(() => {
+  try {
+    new CanMakePaymentEvent('test');
+  } catch (err) {
+    assert_unreached(`Unexpected exception: ${err.message}`);
+  }
+}, 'CanMakePaymentEvent can be constructed in service worker.');
+
+test(() => {
+  const ev = new CanMakePaymentEvent('test', {
+    bubbles: true,
+    cancelabel: true,
+    composed: true,
+  });
+  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+  assert_true(ev.bubbles, 'set by EventInitDict');
+  assert_true(ev.cancelable, 'set by EventInitDict');
+  assert_true(ev.composed, 'set by EventInitDict');
+  assert_equals(ev.target, null, 'initially null');
+  assert_equals(ev.type, 'test');
+}, 'CanMakePaymentEvent can be constructed with an EventInitDict, even if not trusted');
+
+test(() => {
+  const ev = new CanMakePaymentEvent('test', {
+    topOrigin: 'https://foo.com',
+    paymentRequestOrigin: 'https://bar.com',
+    methodData: [],
+    modifiers: [],
+  });
+  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+  assert_equals(ev.topOrigin, 'https://foo.com');
+  assert_equals(ev.paymentRequestOrigin, 'https://bar.com');
+}, 'CanMakePaymentEvent can be constructed with a CanMakePaymentEventInit, even if not trusted');
+
+test(() => {
+  const ev = new CanMakePaymentEvent('test');
+  self.addEventListener('test', evt => {
+    assert_equals(ev, evt);
+  });
+  self.dispatchEvent(ev);
+}, 'CanMakePaymentEvent can be dispatched, even if not trusted');
diff --git a/payment-handler/can-make-payment-event.https.html b/payment-handler/can-make-payment-event.https.html
new file mode 100644
index 0000000..c10851e
--- /dev/null
+++ b/payment-handler/can-make-payment-event.https.html
@@ -0,0 +1,386 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for CanMakePaymentEvent</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-canmakepaymentevent">
+<link rel="manifest" href="manifest.json">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p>The "basic-card" test requires that you don't have a prepaid MIR card stored
+in the browser. If you do, please remove it for the duration of the test.</p>
+<script>
+const instrumentKey = 'instrument-key';
+
+async function registerApp(methodName) {
+  await navigator.serviceWorker.register('app-can-make-payment.js');
+  const registration = await navigator.serviceWorker.ready;
+  if (!registration.paymentManager) {
+    return;
+  }
+  if (registration.paymentManager.requestPermission) {
+    const permission = await registration.paymentManager.requestPermission();
+    if (permission !== 'granted') {
+      return;
+    }
+  }
+  await registration.paymentManager.instruments.set(instrumentKey, {
+    name: 'Test Payment Method',
+    method: methodName,
+  });
+  return registration;
+}
+
+function buildPaymentRequest(methodName) {
+  const unsupportedMethodName = methodName + '-unsupported';
+  return new PaymentRequest(
+    [
+      {
+        supportedMethods: methodName,
+        data: {
+          defaultParameter: 'defaultValue',
+        },
+      },
+      {
+        supportedMethods: unsupportedMethodName,
+        data: {
+          defaultUnsupportedParameter: 'defaultUnsupportedValue',
+        },
+      },
+    ],
+    {
+      total: {
+        label: 'Total',
+        amount: {
+          currency: 'USD',
+          value: '0',
+        },
+      },
+      displayItems: [
+        {
+          label: 'Nada',
+          amount: {currency: 'USD', value: '0'},
+        },
+      ],
+      modifiers: [
+        {
+          supportedMethods: [methodName],
+          data: {
+            modifiedParameter: 'modifiedValue',
+          },
+          total: {
+            label: 'Modified Total',
+            amount: {
+              currency: 'USD',
+              value: '0.0001',
+            },
+          },
+          additionalDisplayItems: [
+            {
+              label: 'Something',
+              amount: {currency: 'USD', value: '0.0001'},
+            },
+          ],
+        },
+        {
+          supportedMethods: [unsupportedMethodName],
+          data: {
+            modifiedUnsupportedParameter: 'modifiedUnsupportedValue',
+          },
+          total: {
+            label: 'Modified Unsupported Total',
+            amount: {
+              currency: 'USD',
+              value: '10',
+            },
+          },
+          additionalDisplayItems: [
+            {
+              label: 'Something Unsupported',
+              amount: {currency: 'USD', value: '10'},
+            },
+          ],
+        },
+      ],
+    },
+  );
+}
+
+promise_test(async t => {
+  const methodName = window.location.origin + '/canMakePayment-true';
+  // Intentionally do not install the payment app.
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_false(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return false.',
+  );
+  await promise_rejects(t, 'NotSupportedError', request.show());
+}, 'If a payment handler is not installed, then the payment method is not supported.');
+
+promise_test(async t => {
+  const methodName = window.location.origin + '/canMakePayment-false';
+  await registerApp(methodName);
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_false(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return false.',
+  );
+  await promise_rejects(t, 'NotSupportedError', request.show());
+}, 'If CanMakePaymentEvent.respondWith(false) is called, then the payment method is not supported.');
+
+promise_test(async t => {
+  const methodName = window.location.origin + '/canMakePayment-promise-false';
+  await registerApp(methodName);
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_false(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return false.',
+  );
+  await promise_rejects(t, 'NotSupportedError', request.show());
+}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(false)) is called, then the payment method is not supported.');
+
+promise_test(async t => {
+  const methodName = window.location.origin + '/canMakePayment-true';
+  await registerApp(methodName);
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_true(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return true.',
+  );
+  const acceptPromise = request.show();
+  await request.abort();
+  await promise_rejects(t, 'AbortError', acceptPromise);
+}, 'If CanMakePaymentEvent.respondWith(true) is called, then the payment method is supported.');
+
+promise_test(async t => {
+  const methodName = window.location.origin + '/canMakePayment-promise-true';
+  await registerApp(methodName);
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_true(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return true.',
+  );
+  const acceptPromise = request.show();
+  await request.abort();
+  await promise_rejects(t, 'AbortError', acceptPromise);
+}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(true)) is called, then the payment method is supported.');
+
+promise_test(async t => {
+  const methodName = window.location.origin + '/canMakePayment-custom-error';
+  await registerApp(methodName);
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_false(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return false.',
+  );
+  await promise_rejects(t, 'NotSupportedError', request.show());
+}, 'If CanMakePaymentEvent.respondWith(Promise.reject(error)) is called, then the payment method is not supported.');
+
+promise_test(async t => {
+  const methodName = 'basic-card';
+  await registerApp(methodName);
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_true(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return true due to capability matching in the browser.',
+  );
+}, 'If an app supports "basic-card" in general and that\'s what merchant requests as well, then capability filtering should make the app available for use. CanMakePaymentEvent should not be fired for "basic-card".');
+
+promise_test(async t => {
+  const methodName = 'basic-card';
+  await registerApp(methodName);
+  const cardType = 'prepaid';
+  const cardNetwork = 'mir';
+  const request = new PaymentRequest(
+    [
+      {
+        supportedMethods: methodName,
+        data: {
+          supportedTypes: [cardType],
+          supportedNetworks: [cardNetwork],
+        },
+      },
+    ],
+    {
+      total: {
+        label: 'Total',
+        amount: {
+          currency: 'USD',
+          value: '0',
+        },
+      },
+    },
+  );
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_false(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return false due to capability matching in the browser.',
+  );
+}, 'If an app has less specific "basic-card" capabilites than merchant\'s request, capability filtering should not make the app available for use. CanMakePaymentEvent should not be fired for "basic-card". ');
+
+promise_test(async t => {
+  const methodName = 'basic-card';
+  const cardType = 'prepaid';
+  const cardNetwork = 'mir';
+  const registration = await registerApp(methodName);
+  await registration.paymentManager.instruments.set(instrumentKey, {
+    name: 'Test Payment Method',
+    method: methodName,
+    capabilities: {
+      supportedTypes: [cardType],
+      supportedNetworks: [cardNetwork],
+    },
+  });
+  const request = new PaymentRequest(
+    [
+      {
+        supportedMethods: methodName,
+        data: {
+          supportedTypes: [cardType],
+          supportedNetworks: [cardNetwork],
+        },
+      },
+    ],
+    {
+      total: {
+        label: 'Total',
+        amount: {
+          currency: 'USD',
+          value: '0',
+        },
+      },
+    },
+  );
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_true(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return true due to capability matching in the browser.',
+  );
+}, 'If an app has the exact "basic-card" capabilities that a merchant requested, capability filtering should make the app available for use. CanMakePaymentEvent should not be fired for "basic-card".');
+
+promise_test(async t => {
+  const methodName = 'basic-card';
+  const cardType = 'prepaid';
+  const cardNetwork = 'mir';
+  const registration = await registerApp(methodName);
+  await registration.paymentManager.instruments.set(instrumentKey, {
+    name: 'Test Payment Method',
+    method: methodName,
+    capabilities: {
+      supportedTypes: [cardType],
+      supportedNetworks: [cardNetwork],
+    },
+  });
+  const request = buildPaymentRequest(methodName);
+  assert_not_equals(request, undefined);
+  let paymentRequestCanMakePaymentResult;
+  try {
+    paymentRequestCanMakePaymentResult = await request.canMakePayment();
+  } catch (err) {
+    assert_equals(
+      err.name,
+      'NotAllowedError',
+      'If it throws, then it must be NotAllowedError',
+    );
+  }
+  assert_true(
+    paymentRequestCanMakePaymentResult,
+    'canMakePayment() must return true due to capability matching in the browser.',
+  );
+}, 'If an app has more specific "basic-card" capabilities than merchant\'s request, capability filtering should make the app available for use. CanMakePaymentEvent should not be fired for "basic-card".');
+</script>
diff --git a/payment-handler/interfaces.https.any.js b/payment-handler/interfaces.https.any.js
index 21f8d35..7367930 100644
--- a/payment-handler/interfaces.https.any.js
+++ b/payment-handler/interfaces.https.any.js
@@ -1,20 +1,16 @@
 // META: script=/resources/WebIDLParser.js
 // META: script=/resources/idlharness.js
 
-"use strict";
-
-if (self.importScripts) {
-    importScripts("/resources/testharness.js");
-    importScripts("/resources/WebIDLParser.js", "/resources/idlharness.js");
-}
+'use strict';
 
 // https://w3c.github.io/payment-handler/
 
-promise_test(async() => {
-    const text = await fetch("/interfaces/payment-handler.idl")
-        .then(response => response.text());
-    const idlArray = new IdlArray();
-    idlArray.add_idls(text);
-    idlArray.test();
-    done();
-}, "Payment handler interfaces.");
+promise_test(async () => {
+  const idl = await fetch('/interfaces/payment-handler.idl').then(r => r.text());
+  const idlArray = new IdlArray();
+  idlArray.add_idls(idl);
+  idlArray.add_untested_idls('interface ExtendableEvent {};');
+  idlArray.add_untested_idls('dictionary ExtendableEventInit {};');
+  idlArray.test();
+  done();
+}, 'Payment handler interfaces.');
diff --git a/payment-handler/manifest.json b/payment-handler/manifest.json
new file mode 100644
index 0000000..875d74b
--- /dev/null
+++ b/payment-handler/manifest.json
@@ -0,0 +1,10 @@
+{
+  "name": "Test Payment Handler",
+  "icons": [
+    {
+      "src": "/images/rgrg-256x256.png",
+      "sizes": "256x256",
+      "type": "image/png"
+    }
+  ]
+}
diff --git a/payment-handler/payment-app/payment.html b/payment-handler/payment-app/payment.html
new file mode 100644
index 0000000..37d2452
--- /dev/null
+++ b/payment-handler/payment-app/payment.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test Payment App</title>
+<p>Account balance: $10.00</p>
+<button>Authorize</button>
diff --git a/payment-handler/payment-instruments.https.html b/payment-handler/payment-instruments.https.html
new file mode 100644
index 0000000..13a2787
--- /dev/null
+++ b/payment-handler/payment-instruments.https.html
@@ -0,0 +1,356 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for PaymentInstruments interface</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#paymentinstruments-interface">
+<link rel="manifest" href="basic-card.json">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<script>
+function runTests(registration) {
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    await registration.paymentManager.instruments.set('instrument-key-1', {
+      name: 'Instrument Name 1',
+    });
+    await registration.paymentManager.instruments.set('instrument-key-2', {
+      name: 'Instrument Name 2',
+    });
+    await registration.paymentManager.instruments.delete('instrument-key-1');
+    await registration.paymentManager.instruments.set('instrument-key-1', {
+      name: 'Instrument Name 1',
+    });
+    const keys = await registration.paymentManager.instruments.keys();
+    assert_array_equals(keys, ['instrument-key-2', 'instrument-key-1']);
+  }, 'Instrument keys are returned in the original insertion order');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    await registration.paymentManager.instruments.set(
+      'existing-instrument-key',
+      {
+        name: 'Instrument Name',
+      },
+    );
+    const result = await registration.paymentManager.instruments.delete(
+      'existing-instrument-key',
+    );
+    assert_true(result);
+  }, 'Deleting an existing instrument returns true');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    await registration.paymentManager.instruments.set(
+      'existing-instrument-key',
+      {
+        name: 'Instrument Name',
+      },
+    );
+    await registration.paymentManager.instruments.delete(
+      'existing-instrument-key',
+    );
+    const result = await registration.paymentManager.instruments.delete(
+      'existing-instrument-key',
+    );
+    assert_false(result);
+  }, 'Deleting an existing instrument the second time returns false');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const result = await registration.paymentManager.instruments.delete(
+      'non-existing-instrument-key',
+    );
+    assert_false(result);
+  }, 'Deleting a non-existing instrument returns false');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    await registration.paymentManager.instruments.set(
+      'existing-instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src: '/images/rgrg-256x256.png',
+            sizes: '256x256',
+            type: 'image/png',
+          },
+        ],
+        method: 'basic-card',
+        capabilities: {supportedNetworks: ['mir'], supportedTypes: ['prepaid']},
+      },
+    );
+    const result = await registration.paymentManager.instruments.get(
+      'existing-instrument-key',
+    );
+    assert_equals(result.name, 'Instrument Name');
+  }, 'Getting an existing instrument returns the instrument');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const result = await registration.paymentManager.instruments.get(
+      'non-existing-instrument-key',
+    );
+    assert_equals(result, undefined);
+  }, 'Getting a non-existing instrument returns undefined');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    await registration.paymentManager.instruments.set(
+      'existing-instrument-key',
+      {
+        name: 'Instrument Name v1',
+        icons: [
+          {src: '/images/green-16x16.png', sizes: '16x16', type: 'image/png'},
+        ],
+        method: 'basic-card',
+        capabilities: {supportedNetworks: ['mir'], supportedTypes: ['prepaid']},
+      },
+    );
+    let result = await registration.paymentManager.instruments.get(
+      'existing-instrument-key',
+    );
+    assert_equals(result.name, 'Instrument Name v1');
+    assert_equals(result.icons.length, 1);
+    assert_equals(
+      result.icons[0].src,
+      new URL('/images/green-16x16.png', window.location.href).href,
+    );
+    assert_equals(result.icons[0].sizes, '16x16');
+    assert_equals(result.icons[0].type, 'image/png');
+    assert_equals(result.method, 'basic-card');
+    assert_array_equals(result.capabilities.supportedNetworks, ['mir']);
+    assert_array_equals(result.capabilities.supportedTypes, ['prepaid']);
+    await registration.paymentManager.instruments.set(
+      'existing-instrument-key',
+      {
+        name: 'Instrument Name v2',
+        icons: [
+          {
+            src: '/images/rgrg-256x256.png',
+            sizes: '256x256',
+            type: 'image/png',
+          },
+        ],
+        method: 'basic-card',
+        capabilities: {supportedNetworks: ['visa'], supportedTypes: ['credit']},
+      },
+    );
+    result = await registration.paymentManager.instruments.get(
+      'existing-instrument-key',
+    );
+    assert_equals(result.name, 'Instrument Name v2');
+    assert_equals(result.icons.length, 1);
+    assert_equals(
+      result.icons[0].src,
+      new URL('/images/rgrg-256x256.png', window.location.href).href,
+    );
+    assert_equals(result.icons[0].sizes, '256x256');
+    assert_equals(result.icons[0].type, 'image/png');
+    assert_equals(result.method, 'basic-card');
+    assert_array_equals(result.capabilities.supportedNetworks, ['visa']);
+    assert_array_equals(result.capabilities.supportedTypes, ['credit']);
+  }, 'Resetting an existing instrument updates the instrument');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    await registration.paymentManager.instruments.set(
+      'existing-instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src: '/images/rgrg-256x256.png',
+            sizes: '256x256',
+            type: 'image/png',
+          },
+        ],
+        method: 'basic-card',
+        capabilities: {supportedNetworks: ['mir'], supportedTypes: ['prepaid']},
+      },
+    );
+    await registration.paymentManager.instruments.clear();
+    const result = await registration.paymentManager.instruments.get(
+      'existing-instrument-key',
+    );
+    assert_equals(result, undefined);
+  }, 'Clearing the instruments');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const setPromise = registration.paymentManager.instruments.set(
+      'instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src: '/images/rgrg-256x256.png',
+            sizes: '256x256',
+            type: 'image/jif',
+          },
+        ],
+        method: 'basic-card',
+      },
+    );
+    return promise_rejects(t, new TypeError(), setPromise);
+  }, 'Cannot register instruments with invalid icon media type image/jif');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const setPromise = registration.paymentManager.instruments.set(
+      'instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src: '/images/rgrg-256x256.png',
+            sizes: '256x256',
+            type: 'image/pn' + 'g'.repeat(1000),
+          },
+        ],
+        method: 'basic-card',
+      },
+    );
+    return promise_rejects(t, new TypeError(), setPromise);
+  }, "Don't crash when registering instruments with very long icon media type image/pngggggg...");
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    return registration.paymentManager.instruments.set('instrument-key', {
+      name: 'Instrument Name',
+      icons: [
+        {
+          src: '/images/rgrg-256x256.png',
+          sizes: '8'.repeat(100000) + 'x' + '8'.repeat(100000),
+          type: 'image/png',
+        },
+      ],
+      method: 'basic-card',
+    });
+  }, "Don't crash when registering an instrument with a very long icon size 888...x888...");
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const setPromise = registration.paymentManager.instruments.set(
+      'instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src: '/images/rgrg-256x256.png',
+            sizes: '256 256',
+            type: 'image/png',
+          },
+        ],
+        method: 'basic-card',
+      },
+    );
+    return promise_rejects(t, new TypeError(), setPromise);
+  }, 'Cannot register instruments with invalid icon size "256 256" (missing "x")');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const setPromise = registration.paymentManager.instruments.set(
+      'instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src: '/images/rg\0rg-256x256.png',
+            sizes: '256x256',
+            type: 'image/png',
+          },
+        ],
+        method: 'basic-card',
+      },
+    );
+    return promise_rejects(t, new TypeError(), setPromise);
+  }, 'Cannot register instruments with invalid icon URL (has a null character)');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const setPromise = registration.paymentManager.instruments.set(
+      'instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src: 'http://test.example/images/rgrg-256x256.png',
+            sizes: '256x256',
+            type: 'image/png',
+          },
+        ],
+        method: 'basic-card',
+      },
+    );
+    return promise_rejects(t, new TypeError(), setPromise);
+  }, 'Cannot register instruments with non-existing non-https icon URL');
+
+  promise_test(async t => {
+    await registration.paymentManager.instruments.clear();
+    const setPromise = registration.paymentManager.instruments.set(
+      'instrument-key',
+      {
+        name: 'Instrument Name',
+        icons: [
+          {
+            src:
+              'http://www.chromium.org/_/rsrc/1438879449147/config/customLogo.gif',
+            sizes: '48x48',
+            type: 'image/gif',
+          },
+        ],
+        method: 'basic-card',
+      },
+    );
+    return promise_rejects(t, new TypeError(), setPromise);
+  }, 'Cannot register instruments with an existing non-https icon URL');
+
+  async function testUnusualStrings(existingKey, nonExistingKey) {
+    await registration.paymentManager.instruments.clear();
+    await registration.paymentManager.instruments.set(existingKey, {
+      name: existingKey,
+      icons: [
+        {src: '/images/rgrg-256x256.png', sizes: '256x256', type: 'image/png'},
+      ],
+      method: existingKey,
+      capabilities: {aCapabilityName: existingKey},
+    });
+    const hasExistingInstrument = await registration.paymentManager.instruments.has(
+      existingKey,
+    );
+    assert_true(hasExistingInstrument);
+    const hasNonExistingInstrument = await registration.paymentManager.instruments.has(
+      nonExistingKey,
+    );
+    assert_false(hasNonExistingInstrument);
+    const existingInstrument = await registration.paymentManager.instruments.get(
+      existingKey,
+    );
+    assert_equals(existingInstrument.name, existingKey);
+    const nonExistingInstrument = await registration.paymentManager.instruments.get(
+      nonExistingKey,
+    );
+    assert_equals(nonExistingInstrument, undefined);
+    const deletedExistingInstrument = await registration.paymentManager.instruments.delete(
+      existingKey,
+    );
+    assert_true(deletedExistingInstrument);
+    const deletedNonExistingInstrument = await registration.paymentManager.instruments.delete(
+      nonExistingKey,
+    );
+    assert_false(deletedNonExistingInstrument);
+  }
+
+  promise_test(async t => {
+    const length = 100000;
+    await testUnusualStrings('0'.repeat(length), '1'.repeat(length));
+  }, "Don't crash on very long key, name, method, and capability strings.");
+
+  promise_test(async t => {
+    await testUnusualStrings('foo\0bar', 'foo\0baz');
+  }, "Don't crash on null characters in key, name, method, and capability strings.");
+}
+
+registerAndActiveServiceWorker('basic-card.js', 'payment-app/', runTests);
+</script>
diff --git a/payment-handler/payment-request-event.https.html b/payment-handler/payment-request-event.https.html
new file mode 100644
index 0000000..4e9cc21
--- /dev/null
+++ b/payment-handler/payment-request-event.https.html
@@ -0,0 +1,105 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for PaymentRequestEvent</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent">
+<link rel="manifest" href="/payment-handler/basic-card.json">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<p>When the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+async function setInstrumentsAndRunTests(registration) {
+  await registration.paymentManager.instruments.clear();
+  await registration.paymentManager.instruments.set('instrument-key', {
+    name: 'Instrument Name',
+    icons: [
+      {src: '/images/rgrg-256x256.png', sizes: '256x256', type: 'image/png'},
+    ],
+    method: 'basic-card',
+    capabilities: {supportedNetworks: ['mir'], supportedTypes: ['prepaid']},
+  });
+  runTests();
+}
+
+function runTests() {
+  promise_test(async t => {
+    const response = await new PaymentRequest(
+      [
+        {supportedMethods: 'basic-card', data: {supportedTypes: ['prepaid']}},
+        {supportedMethods: 'interledger', data: {supportedNetworks: ['mir']}},
+      ],
+      {
+        id: 'test-payment-request-identifier',
+        total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+        displayItems: [
+          {label: 'Item 1', amount: {currency: 'CAD', value: '0.005'}},
+          {label: 'Item 2', amount: {currency: 'EUR', value: '0.005'}},
+        ],
+        modifiers: [
+          {
+            supportedMethods: 'basic-card',
+            data: {supportedNetworks: ['mir']},
+            total: {
+              label: 'MIR total',
+              amount: {currency: 'USD', value: '0.0099'},
+            },
+            additionalDisplayItems: [
+              {label: 'Item 3', amount: {currency: 'GBP', value: '-0.0001'}},
+            ],
+          },
+          {
+            supportedMethods: 'basic-card',
+            data: {supportedNetworks: ['visa']},
+            total: {
+              label: 'VISA total',
+              amount: {currency: 'USD', value: '0.0098'},
+            },
+            additionalDisplayItems: [
+              {label: 'Item 4', amount: {currency: 'CNY', value: '-0.0002'}},
+            ],
+          },
+          {
+            supportedMethods: 'interledger',
+            data: {supportedTypes: ['prepaid']},
+            total: {
+              label: 'Prepaid total',
+              amount: {currency: 'USD', value: '0.0097'},
+            },
+            additionalDisplayItems: [
+              {label: 'Item 5', amount: {currency: 'JPY', value: '-0.0003'}},
+            ],
+          },
+        ],
+      },
+    ).show();
+    const promise = response.complete('success');
+    assert_equals(response.requestId, 'test-payment-request-identifier');
+    assert_equals(response.methodName, 'basic-card');
+    assert_array_equals(response.details.billingAddress.addressLine, [
+      '1875 Explorer St #1000',
+    ]);
+    assert_equals(response.details.billingAddress.city, 'Reston');
+    assert_equals(response.details.billingAddress.country, 'US');
+    assert_equals(response.details.billingAddress.dependentLocality, '');
+    assert_equals(response.details.billingAddress.languageCode, '');
+    assert_equals(response.details.billingAddress.organization, 'Google');
+    assert_equals(response.details.billingAddress.phone, '+15555555555');
+    assert_equals(response.details.billingAddress.postalCode, '20190');
+    assert_equals(response.details.billingAddress.recipient, 'Jon Doe');
+    assert_equals(response.details.billingAddress.region, 'VA');
+    assert_equals(response.details.billingAddress.sortingCode, '');
+    assert_equals(response.details.cardNumber, '4111111111111111');
+    assert_equals(response.details.cardSecurityCode, '123');
+    assert_equals(response.details.cardholderName, 'Jon Doe');
+    assert_equals(response.details.expiryMonth, '12');
+    assert_equals(response.details.expiryYear, '2028');
+    return promise;
+  }, 'Can perform payment');
+}
+
+registerAndActiveServiceWorker(
+  'basic-card.js',
+  'payment-app/',
+  setInstrumentsAndRunTests,
+);
+</script>
diff --git a/payment-handler/register-and-activate-service-worker.js b/payment-handler/register-and-activate-service-worker.js
new file mode 100644
index 0000000..fb54c5c
--- /dev/null
+++ b/payment-handler/register-and-activate-service-worker.js
@@ -0,0 +1,28 @@
+async function registerAndActiveServiceWorker(script, scope, callback) {
+  const registration = await navigator.serviceWorker.register(script, {scope});
+  const serviceWorker =
+    registration.installing || registration.waiting || registration.active;
+  if (serviceWorker) {
+    waitForServiceWorkerActivation(scope, callback);
+    return;
+  }
+
+  registration.addEventListener('updatefound', event => {
+    waitForServiceWorkerActivation(scope, callback);
+  });
+}
+
+async function waitForServiceWorkerActivation(scope, callback) {
+  const registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration.active) {
+    callback(registration);
+    return;
+  }
+
+  const serviceWorker = registration.installing || registration.waiting;
+  serviceWorker.addEventListener('statechange', event => {
+    if (event.target.state == 'activated') {
+      callback(registration);
+    }
+  });
+}
diff --git a/payment-handler/same-object-attributes.https.html b/payment-handler/same-object-attributes.https.html
new file mode 100644
index 0000000..b9a9dd8
--- /dev/null
+++ b/payment-handler/same-object-attributes.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<link rel="help" href="https://w3c.github.io/payment-handler/">
+<title>Test for [SameObject] attributes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(async t => {
+  const registration = await service_worker_unregister_and_register(
+      t, 'basic-card.js', 'payment-app/');
+  await wait_for_state(t, registration.installing, 'activated');
+
+  assert_equals(registration.paymentManager, registration.paymentManager);
+  assert_equals(registration.paymentManager.instruments, registration.paymentManager.instruments);
+});
+
+</script>
diff --git a/payment-handler/untrusted-event.https.html b/payment-handler/untrusted-event.https.html
new file mode 100644
index 0000000..900ac79
--- /dev/null
+++ b/payment-handler/untrusted-event.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<link rel="help" href="https://w3c.github.io/payment-handler/">
+<title>Test for untrusted event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+async function getResultFromSW(serviceWorkerContainer) {
+  return new Promise((resolve, reject) => {
+    serviceWorkerContainer.addEventListener('message', listener = e => {
+      serviceWorkerContainer.removeEventListener('message', listener);
+      if (e.data) {
+        resolve(e.data);
+      } else {
+        reject();
+      }
+    });
+  });
+}
+
+promise_test(async t => {
+  const registration = await service_worker_unregister_and_register(
+      t, 'untrusted-event.js', 'payment-app/');
+  await wait_for_state(t, registration.installing, 'activated');
+
+  const controlled_window = (await with_iframe('payment-app/payment.html')).contentWindow;
+
+  // Test for untrusted PaymentRequestEvent
+  {
+    const result = getResultFromSW(controlled_window.navigator.serviceWorker);
+    controlled_window.navigator.serviceWorker.controller.postMessage('paymentrequest');
+
+    const expected = [
+      "InvalidStateError", /* respondWith */
+      "InvalidStateError"  /* openWindow */
+    ];
+
+    assert_array_equals(await result, expected);
+  }
+
+  // Test for untrusted CanMakePaymentEvent
+  {
+    const result = getResultFromSW(controlled_window.navigator.serviceWorker);
+    controlled_window.navigator.serviceWorker.controller.postMessage('canmakepayment');
+
+    const expected = [
+      "InvalidStateError", /* respondWith */
+    ];
+
+    assert_array_equals(await result, expected);
+  }
+});
+
+</script>
diff --git a/payment-handler/untrusted-event.js b/payment-handler/untrusted-event.js
new file mode 100644
index 0000000..2407029
--- /dev/null
+++ b/payment-handler/untrusted-event.js
@@ -0,0 +1,59 @@
+let sender = null;
+
+self.addEventListener('message', e => {
+  sender = e.source;
+
+  if (e.data == 'paymentrequest') {
+    self.dispatchEvent(new PaymentRequestEvent('paymentrequest', {
+      methodData: [{
+        supportedMethods: 'basic-card'
+      }],
+      total: {
+        currency: 'USD',
+        value: '100'
+      },
+      modifiers: [{
+        supportedMethods: 'basic-card'
+      }]
+    }));
+  } else if (e.data == 'canmakepayment') {
+    self.dispatchEvent(new CanMakePaymentEvent('canmakepayment', {
+      methodData: [{
+        supportedMethods: 'basic-card'
+      }],
+      modifiers: [{
+        supportedMethods: 'basic-card'
+      }]
+    }));
+  }
+});
+
+self.addEventListener('paymentrequest', async e => {
+  const result = [];
+
+  try {
+    e.respondWith({});
+  } catch (exception) {
+    result.push(exception.name);
+  }
+
+  try {
+    await e.openWindow('payment-app/payment.html');
+  } catch (exception) {
+    result.push(exception.name);
+  }
+
+  sender.postMessage(result);
+});
+
+self.addEventListener('canmakepayment', async e => {
+  const result = [];
+
+  try {
+    e.respondWith({});
+  } catch (exception) {
+    result.push(exception.name);
+  }
+
+  sender.postMessage(result);
+});
diff --git a/payment-request/OWNERS b/payment-request/OWNERS
index 6aa9e2b..7f3eca7 100644
--- a/payment-request/OWNERS
+++ b/payment-request/OWNERS
@@ -1,6 +1,6 @@
-@edenchuang
 @marcoscaceres
 @rsolomakhin
 @domenic
-@AmazingJaze
 @MSFTkihans
+@mnoorenberghe
+@edenchuang
diff --git a/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html b/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html
index 6f14e8a..f767044 100644
--- a/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html
+++ b/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html
@@ -67,23 +67,40 @@
   <li>
     <button onclick="
     const expectedAddress = {
-      country: 'AF',
-      addressLine: '1 wpt street',
-      region: '',
-      city: 'Kabul',
+      country: 'AU',
+      regionCode: 'QLD',
+      addressLine: '55 test st',
+      city: 'Chapel Hill',
       dependentLocality: '',
-      postalCode: '1001',
+      postalCode: '6095',
       sortingCode: '',
-      languageCode: 'fa',
+      languageCode: 'en',
       organization: 'w3c',
       recipient: 'web platform test',
-      phone: '+93555555555',
+      phone: '+61733780000',
     };
     runManualTest(this, expectedAddress);">
       If the requestShipping member is true, then shippingAddress's PaymentAddress must match the expected values.
     </button>
-    "web platform test" as recipient, at address "1 wpt street" in "Kabul, Afghanistan", zip/postal code 1001.
-    Set the organization to "w3c". Set the phone number to "+93 55 555 5555"
+    Please use:
+    <dl>
+      <dt>Recipient:</dt>
+      <dd>web platform test</dd>
+      <dt>Address line:</dt>
+      <dd>55 test st</dd>
+      <dt>Country</dt>
+      <dd>Australia</dd>
+      <dt>City</dt>
+      <dd>Chapel Hill</dd>
+      <dd>State/Region</dd>
+      <dd>Queensland</dd>
+      <dt>postal code </dt>
+      <dd>6095</dd>
+      <dt>organization</dt>
+      <dd>w3c</dd>
+      <dt>Phone number</dt>
+      <dd>+61 7 3378 0000</dd>
+    </dl>
   </li>
 </ol>
 <small>
diff --git a/payment-request/PaymentItem/type_member.https.html b/payment-request/PaymentItem/type_member.https.html
new file mode 100644
index 0000000..dc62a83
--- /dev/null
+++ b/payment-request/PaymentItem/type_member.https.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentitem-type">
+<title>
+  PaymentItem type member
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const validMethods = [
+  { supportedMethods: "basic-card" },
+  { supportedMethods: "https://apple.com/apple-pay" },
+];
+const validTotal = {
+  label: "Total",
+  amount: {
+    currency: "USD",
+    value: "5.00",
+  },
+};
+const validDisplayItem = {
+  label: "Item",
+  amount: {
+    currency: "USD",
+    value: "1.00",
+  },
+};
+const validDetails = {
+  total: validTotal,
+  displayItems: [validDisplayItem],
+};
+
+test(() => {
+  new PaymentRequest(validMethods, validDetails);
+}, "Smoke test");
+
+test(() => {
+  // Let's make an invalid DisplayItem for the total
+  const invalidTotal = Object.assign({}, validTotal, {
+    type: "this is not valid",
+  });
+  const invalidDetails = Object.assign({}, validDetails, {
+    total: invalidTotal,
+  });
+  assert_throws(new TypeError(), () => {
+    new PaymentRequest(validMethods, invalidDetails);
+  });
+}, "An invalid enum value for PaymentDetailsInit.total's type throws TypeError");
+
+test(() => {
+  // Let's make an invalid DisplayItem to add to displayItems
+  const invalidDisplayItem = Object.assign({}, validDisplayItem, {
+    type: "this is not valid",
+  });
+  const invalidDetails = Object.assign({}, validDetails, {
+    displayItems: [invalidDisplayItem, validDisplayItem],
+  });
+  assert_throws(new TypeError(), () => {
+    new PaymentRequest(validMethods, invalidDetails);
+  });
+}, "Invalid enum value for PaymentItem.type member throws a TypeError");
+
+test(() => {
+  // Let's make an invalid DisplayItem to add to displayItems
+  const taxDisplayItem = Object.assign({}, validDisplayItem, { type: "tax" });
+  const taxTotal = Object.assign({}, validTotal, { type: "tax" });
+  const validDetailsWithType = Object.assign({}, validDetails, {
+    total: taxTotal,
+    displayItems: [taxDisplayItem],
+  });
+  try {
+    new PaymentRequest(validMethods, validDetailsWithType);
+  } catch (err) {
+    assert_unexpected(err.message);
+  }
+}, "Valid enum values for PaymentItem.type member does not throw");
+</script>
diff --git a/payment-request/PaymentRequestUpdateEvent/constructor.https.html b/payment-request/PaymentRequestUpdateEvent/constructor.https.html
index eb58b6a..fd66493 100644
--- a/payment-request/PaymentRequestUpdateEvent/constructor.https.html
+++ b/payment-request/PaymentRequestUpdateEvent/constructor.https.html
@@ -22,7 +22,7 @@
   try {
     new PaymentRequestUpdateEvent("test");
   } catch (err) {
-    assert_true(false, `Unexpected exception: ${err.message}`);
+    assert_unreached(`Unexpected exception: ${err.message}`);
   }
 }, "PaymentRequestUpdateEvent can be constructed in secure-context");
 
diff --git a/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html b/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html
new file mode 100644
index 0000000..6451715
--- /dev/null
+++ b/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html
@@ -0,0 +1,187 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#updatewith-method">
+<title>
+  Incremental updates via updateWith()
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({
+  explicit_done: true,
+  explicit_timeout: true,
+});
+
+const methods = [{
+  supportedMethods: "basic-card",
+}];
+
+const options = {
+  requestShipping: true,
+};
+
+const initialDetails = {
+  total: {
+    label: "Initial total",
+    amount: {
+      currency: "USD",
+      value: "1.0",
+    },
+  },
+  shippingOptions: [
+    {
+      id: "neutral",
+      label: "NEUTRAL SHIPPING OPTION",
+      selected: true,
+      amount: {
+        currency: "USD",
+        value: "0.00",
+      },
+    },
+  ],
+};
+
+function testFireEvent(button, updateDetails) {
+  button.disabled = true;
+  const request = new PaymentRequest(methods, initialDetails, options);
+  const handlerPromise = new Promise(resolve => {
+    request.onshippingaddresschange = event => {
+      event.updateWith(updateDetails);
+      resolve(event);
+    };
+  });
+  promise_test(async t => {
+    const response = await request.show();
+    const event = await handlerPromise;
+    await response.complete("success");
+  }, button.textContent.trim());
+}
+
+</script>
+<h2>
+  Incremental updates
+</h2>
+<p>
+  Click on each button in sequence from top to bottom without refreshing the page.
+  Each button will bring up the Payment Request UI window.
+</p>
+<p>
+  Unless stated otherwise, each test will update some part of the displayed payment sheet in
+  a manner indicated below. When prompted, please change or enter a new
+  shipping address, look for the tested change, and complete the payment.
+</p>
+<p>
+  If the payment request locks up or otherwise aborts, the test has failed.
+</p>
+<ol>
+  <li>
+    <button onclick="testFireEvent(this, {});">
+      Passing an empty dictionary does not cause the sheet to change.
+      No values in the sheet must change.
+    </button>
+  </li>
+</ol>
+
+<section>
+  <h3>Incremental updates via PaymentDetailsUpdate.total</h3>
+  <ol>
+    <li>
+      <button onclick="
+      const total = {
+        label: 'PASS',
+        amount: {
+          currency: 'XXX',
+          value: '20',
+        },
+      };
+      const updatedDetails = { total };
+      testFireEvent(this, updatedDetails);">
+        After changing shipping address, the total becomes XXX20, with the label "PASS".
+      </button>
+    </li>
+  </ol>
+</section>
+
+<section>
+  <h3>Incremental updates via PaymentDetailsBase.displayItems</h3>
+  <ol>
+    <li>
+      <button onclick="
+      const item = {
+        label: 'PASS',
+        amount: { currency: 'ABC', value: '55.00' },
+      };
+      const updatedDetails = {
+        displayItems: [ item ]
+      };
+      testFireEvent(this, updatedDetails);">
+        After changing shipping address, a new display item is shown
+        with a with label PASS, and value of ABC55.00.
+      </button>
+    </li>
+  </ol>
+</section>
+
+<section>
+  <h3>Incremental updates via PaymentDetailsBase.shippingOptions</h3>
+  <ol>
+    <li>
+      <button onclick="
+      const shippingOptions = [
+        {
+          id: 'pass',
+          label: 'PASS',
+          amount: { currency: 'USD', value: '1.00' },
+          selected: true,
+        },
+        {
+          id: 'fail',
+          label: 'FAIL IF THIS IS SELECTED',
+          amount: { currency: 'USD', value: '25.00' }
+        },
+      ];
+      const updatedDetails = {
+        shippingOptions
+      };
+      testFireEvent(this, updatedDetails);">
+        After changing shipping address, two new shipping options appear.
+        The shipping option labelled "PASS" with a value of USD1.0 is selected.
+      </button>
+    </li>
+  </ol>
+</section>
+
+<section>
+  <h3>Incremental updates via PaymentDetailsBase.modifiers</h3>
+  <ol>
+    <li>
+      <button onclick="
+      const additionalItem = {
+        label: 'PASS-DISPLAY-ITEM',
+        amount: { currency: 'USD', value: '3.00' },
+      };
+      const modifiers = [{
+        additionalDisplayItems: [ additionalItem ],
+        supportedMethods: 'basic-card',
+        total: {
+          label: 'PASS-TOTAL',
+          amount: { currency: 'USD', value: '123.00' },
+        },
+      }];
+      const updatedDetails = { modifiers };
+      testFireEvent(this, updatedDetails);">
+        After changing shipping address, a new display item is shown
+        with a with label PASS-DISPLAY-ITEM, and value of ABC55.00 and the total is
+        labelled PASS-TOTAL with a value of USD123.0
+      </button>
+    </li>
+    <li>
+      <button onclick="done()">DONE - see results</button>
+    </li>
+  </ol>
+</section>
+
+<small>
+  If you find a buggy test, please <a href="https://github.com/w3c/web-platform-tests/issues">file a bug</a>
+  and tag one of the <a href="https://github.com/w3c/web-platform-tests/blob/master/payment-request/OWNERS">owners</a>.
+</small>
diff --git a/payment-request/change-shipping-option-select-last-manual.https.html b/payment-request/change-shipping-option-select-last-manual.https.html
new file mode 100644
index 0000000..eb1957f
--- /dev/null
+++ b/payment-request/change-shipping-option-select-last-manual.https.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for PaymentDetailsBase's shippingOptions member</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentdetailsbase-shippingoptions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const validMethods = Object.freeze([
+  { supportedMethods: "basic-card" },
+  { supportedMethods: "https://apple.com/apple-pay" },
+]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+  label: "label",
+  amount: validAmount,
+});
+const validDetails = Object.freeze({ total: validTotal });
+
+const validShippingOption1 = Object.freeze({
+  id: "fail-if-selected-1",
+  label: "FAIL if selected 1",
+  amount: validAmount,
+  selected: true,
+});
+
+const validShippingOption2 = Object.freeze({
+  id: "fail-if-selected-2",
+  label: "FAIL if selected 2",
+  amount: validAmount,
+  selected: false,
+});
+
+const validShippingOption3 = Object.freeze({
+  id: "pass-if-selected",
+  label: "THIS MUST BE AUTOMATICALLY SELECTED",
+  amount: validAmount,
+  selected: true,
+});
+
+function testShippingOptionChanged(button) {
+  button.disabled = true;
+  promise_test(async t => {
+    const detailsWithShippingOptions = {
+      ...validDetails,
+      shippingOptions: [
+        validShippingOption1,
+        validShippingOption2,
+        validShippingOption3,
+      ],
+    };
+    const request = new PaymentRequest(
+      validMethods,
+      detailsWithShippingOptions,
+      { requestShipping: true }
+    );
+    assert_equals(
+      request.shippingOption,
+      "pass-if-selected",
+      "Must be 'pass-if-selected', as the selected member is true"
+    );
+    request.onshippingoptionchange = () => {
+      assert_unreached("onshippingoptionchange fired unexpectedly");
+    };
+    const response = await request.show();
+    assert_equals(response.shippingOption, "pass-if-selected");
+    response.complete();
+  }, button.textContent.trim());
+  done();
+}
+</script>
+
+<h2>PaymentRequest shippingOption attribute</h2>
+<p>
+  Click on each button in sequence from top to bottom without refreshing the page.
+  Each button will bring up the Payment Request UI window.
+</p>
+<p>
+  When the payment sheet is presented, hit pay.
+</p>
+<ol>
+  <li>
+    <button onclick="testShippingOptionChanged(this)">
+      When default shipping option is pre-selected, must not fire onshippingoptionchange
+      and PaymentResponse must reflect the pre-selected option.
+    </button>
+  </li>
+</ol>
+<small>
+  If you find a buggy test, please <a href="https://github.com/w3c/web-platform-tests/issues">file a bug</a>
+  and tag one of the <a href="https://github.com/w3c/web-platform-tests/blob/master/payment-request/OWNERS">owners</a>.
+</small>
diff --git a/payment-request/interfaces.https.html b/payment-request/interfaces.https.html
index bfbcad4..a7ec323 100644
--- a/payment-request/interfaces.https.html
+++ b/payment-request/interfaces.https.html
@@ -7,12 +7,13 @@
 <script src=/resources/idlharness.js></script>
 <script>
 promise_test(async () => {
-  const idlURLs = ["/interfaces/dom.idl", "/interfaces/payment-request.idl"];
+  const urls =  ["/interfaces/dom.idl", "/interfaces/payment-request.idl"];
+  const [dom, payment_request] = await Promise.all(
+    urls.map(url => fetch(url).then(r => r.text())));
   const idlArray = new IdlArray();
-  for(const url of idlURLs){
-    const idlText = await fetch(url).then(r => r.text());
-    idlArray.add_idls(idlText);
-  }
+  idlArray.add_untested_idls(dom);
+  idlArray.add_idls(payment_request);
+
   // typedef EventHandler from HTML
   // https://html.spec.whatwg.org/#eventhandler
   idlArray.add_idls(`
diff --git a/payment-request/payment-request-abort-method.https.html b/payment-request/payment-request-abort-method.https.html
index 8fc4baf..d1f693a 100644
--- a/payment-request/payment-request-abort-method.https.html
+++ b/payment-request/payment-request-abort-method.https.html
@@ -1,16 +1,18 @@
 <!DOCTYPE html>
-<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
 <meta charset="utf-8">
 <title>Test for PaymentRequest.abort() method</title>
 <link rel="help" href="https://w3c.github.io/browser-payment-api/#abort-method">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src='/resources/testdriver-vendor.js'></script>
+<script src="/resources/testdriver.js"></script>
 <script>
 "use strict";
-setup(() => {}, {
+setup({
   // Ignore unhandled rejections resulting from .show()'s acceptPromise
   // not being explicitly handled.
   allow_uncaught_exception: true,
+  explicit_timeout: true,
 });
 const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
@@ -24,42 +26,53 @@
   },
 });
 
-promise_test(async t => {
-  // request is in "created" state
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  await promise_rejects(t, "InvalidStateError", request.abort());
-}, `Throws if the promise [[state]] is not "interactive"`);
+window.onload = async () => {
+  promise_test(async t => {
+    // request is in "created" state
+    const request = new PaymentRequest(defaultMethods, defaultDetails);
+    await promise_rejects(t, "InvalidStateError", request.abort());
+  }, `Throws if the promise [[state]] is not "interactive"`);
 
-promise_test(async t => {
-  // request is in "created" state.
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  await promise_rejects(t, "InvalidStateError", request.abort());
-  // Call it again, for good measure.
-  await promise_rejects(t, "InvalidStateError", request.abort());
-  // The request's state is "created", so let's show it
-  // which changes the state to "interactive.".
-  request.show();
-  // Let's set request the state to "closed" by calling .abort()
-  try {
-    await request.abort();
-  } catch (err) {
-    assert_true(false, "Unexpected promise rejection: " + err.message);
-  }
-  // The request is now "closed", so...
-  await promise_rejects(t, "InvalidStateError", request.abort());
-}, `Calling abort must not change the [[state]] until after "interactive"`);
+  const button = document.getElementById("button");
 
-promise_test(async t => {
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  const acceptPromise = request.show();
-  try {
-    await request.abort();
-  } catch (err) {
-    assert_true(false, "Unexpected promise rejection: " + err.message);
-  }
-  await promise_rejects(t, "AbortError", acceptPromise);
-  // As request is now "closed", trying to show it will fail
-  await promise_rejects(t, "InvalidStateError", request.show());
-}, "calling .abort() causes acceptPromise to reject and closes the request.");
+  promise_test(async t => {
+    button.onclick = async () => {
+      const request = new PaymentRequest(defaultMethods, defaultDetails);
+      const acceptPromise = request.show();
+      try {
+        await request.abort();
+      } catch (err) {
+        assert_unreached("Unexpected promise rejection: " + err.message);
+      }
+      await promise_rejects(t, "AbortError", acceptPromise);
+      // As request is now "closed", trying to show it will fail
+      await promise_rejects(t, "InvalidStateError", request.show());
+    };
+    await test_driver.click(button);
+  });
 
+  promise_test(async t => {
+    button.onclick = async () => {
+      // request is in "created" state.
+      const request = new PaymentRequest(defaultMethods, defaultDetails);
+      await promise_rejects(t, "InvalidStateError", request.abort());
+      // Call it again, for good measure.
+      await promise_rejects(t, "InvalidStateError", request.abort());
+      // The request's state is "created", so let's show it
+      // which changes the state to "interactive.".
+      const acceptPromise = request.show();
+      // Let's set request the state to "closed" by calling .abort()
+      try {
+        await request.abort();
+      } catch (err) {
+        assert_unreached("Unexpected promise rejection: " + err.message);
+      }
+      // The request is now "closed", so...
+      await promise_rejects(t, "InvalidStateError", request.abort());
+      await promise_rejects(t, "AbortError", acceptPromise);
+    };
+    await test_driver.click(button);
+  });
+};
 </script>
+<button id="button"></button>
diff --git a/payment-request/payment-request-canmakepayment-method-manual.https.html b/payment-request/payment-request-canmakepayment-method-manual.https.html
new file mode 100644
index 0000000..caf43d1
--- /dev/null
+++ b/payment-request/payment-request-canmakepayment-method-manual.https.html
@@ -0,0 +1,196 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests for PaymentRequest.canMakePayment() method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({
+  explicit_done: true,
+  explicit_timeout: true,
+});
+
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
+const defaultMethods = Object.freeze([basicCard]);
+const defaultDetails = Object.freeze({
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  try {
+    assert_true(
+      await request.canMakePayment(),
+      `canMakePaymentPromise should be true`
+    );
+    assert_true(
+      await request.canMakePayment(),
+      `canMakePaymentPromise should be true`
+    );
+  } catch (err) {
+    assert_equals(
+      err.name,
+      "NotAllowedError",
+      "if it throws, then it must be a NotAllowedError."
+    );
+  }
+}, `If request.[[state]] is "created", then return a promise that resolves to true for known method.`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  assert_true(await request.canMakePayment(), "basic-card should be supported");
+}, `If payment method identifier and serialized parts are supported, resolve promise with true.`);
+
+promise_test(async t => {
+  const unsupportedMethods = [
+    "this-is-not-supported",
+    "https://not.supported",
+    "e",
+    "n6jzof05mk2g4lhxr-u-q-w1-c-i-pa-ty-bdvs9-ho-ae7-p-md8-s-wq3-h-qd-e-q-sa",
+    "a-b-q-n-s-pw0",
+    "m-u",
+    "s-l5",
+    "k9-f",
+    "m-l",
+    "u4-n-t",
+    "i488jh6-g18-fck-yb-v7-i",
+    "x-x-t-t-c34-o",
+    "https://wpt",
+    "https://wpt.fyi/",
+    "https://wpt.fyi/payment",
+    "https://wpt.fyi/payment-request",
+    "https://wpt.fyi/payment-request?",
+    "https://wpt.fyi/payment-request?this=is",
+    "https://wpt.fyi/payment-request?this=is&totally",
+    "https://wpt.fyi:443/payment-request?this=is&totally",
+    "https://wpt.fyi:443/payment-request?this=is&totally#fine",
+    "https://:@wpt.fyi:443/payment-request?this=is&totally#👍",
+    " \thttps://wpt\n ",
+    "https://xn--c1yn36f",
+    "https://點看",
+  ];
+  for (const method of unsupportedMethods) {
+    try {
+      const request = new PaymentRequest(
+        [{ supportedMethods: method }],
+        defaultDetails
+      );
+      assert_false(
+        await request.canMakePayment(),
+        `method "${method}" must not be supported`
+      );
+    } catch (err) {
+      assert_equals(
+        err.name,
+        "NotAllowedError",
+        "if it throws, then it must be a NotAllowedError."
+      );
+    }
+  }
+}, `If payment method identifier is unknown, resolve promise with false.`);
+
+promise_test(async t => {
+  // This test might never actually hit its assertion, but that's allowed.
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  for (let i = 0; i < 1000; i++) {
+    try {
+      await request.canMakePayment();
+    } catch (err) {
+      assert_equals(
+        err.name,
+        "NotAllowedError",
+        "if it throws, then it must be a NotAllowedError."
+      );
+      break;
+    }
+  }
+  for (let i = 0; i < 1000; i++) {
+    try {
+      await new PaymentRequest(defaultMethods, defaultDetails).canMakePayment();
+    } catch (err) {
+      assert_equals(
+        err.name,
+        "NotAllowedError",
+        "if it throws, then it must be a NotAllowedError."
+      );
+      break;
+    }
+  }
+}, `Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException.`);
+
+function manualTest1(elem){
+  elem.disabled = true;
+  promise_test(async t => {
+    const request = new PaymentRequest(defaultMethods, defaultDetails);
+    const acceptPromise = request.show(); // Sets state to "interactive"
+    const canMakePaymentPromise = request.canMakePayment();
+    try {
+      const result = await canMakePaymentPromise;
+      assert_true(
+        false,
+        `canMakePaymentPromise should have thrown InvalidStateError`
+      );
+    } catch (err) {
+      await promise_rejects(t, "InvalidStateError", canMakePaymentPromise);
+    } finally {
+      await request.abort();
+      await promise_rejects(t, "AbortError", acceptPromise);
+    }
+    // The state should be "closed"
+    await promise_rejects(t, "InvalidStateError", request.canMakePayment());
+  }, elem.textContent.trim());
+}
+
+function manualTest2(elem){
+ elem.disabled = true;
+  promise_test(async t => {
+    const request = new PaymentRequest(defaultMethods, defaultDetails);
+    const acceptPromise = request.show(); // The state is now "interactive"
+    acceptPromise.catch(() => {}); // no-op, just to silence unhandled rejection in devtools.
+    await request.abort(); // The state is now "closed"
+    await promise_rejects(t, "InvalidStateError", request.canMakePayment());
+    try {
+      const result = await request.canMakePayment();
+      assert_true(
+        false,
+        `should have thrown InvalidStateError, but instead returned "${result}"`
+      );
+    } catch (err) {
+      assert_equals(
+        err.name,
+        "InvalidStateError",
+        "must be an InvalidStateError."
+      );
+    }
+  }, elem.textContent.trim());
+  done();
+}
+</script>
+
+<h2>Tests for PaymentRequest.canMakePayment() method</h2>
+<p>
+  Click on each button in sequence from top to bottom without refreshing the page.
+  No payment sheet will be shown, but the tests will run in the background.
+</p>
+<ol>
+  <li>
+    <button onclick="manualTest1(this)">
+      If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException.
+    </button>
+  </li>
+  <li>
+    <button onclick="manualTest2(this)">
+      If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException.
+    </button>
+  </li>
+</ol>
+<small>
+  If you find a buggy test, please <a href="https://github.com/w3c/web-platform-tests/issues">file a bug</a>
+  and tag one of the <a href="https://github.com/w3c/web-platform-tests/blob/master/payment-request/OWNERS">owners</a>.
+</small>
diff --git a/payment-request/payment-request-canmakepayment-method.https.html b/payment-request/payment-request-canmakepayment-method.https.html
deleted file mode 100644
index 9fe0c4a..0000000
--- a/payment-request/payment-request-canmakepayment-method.https.html
+++ /dev/null
@@ -1,163 +0,0 @@
-<!DOCTYPE html>
-<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
-<meta charset="utf-8">
-<title>Tests for PaymentRequest.canMakePayment() method</title>
-<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-const basicCard = Object.freeze({ supportedMethods: "basic-card" });
-const defaultMethods = Object.freeze([basicCard]);
-const defaultDetails = Object.freeze({
-  total: {
-    label: "Total",
-    amount: {
-      currency: "USD",
-      value: "1.00",
-    },
-  },
-});
-
-promise_test(async t => {
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  try {
-    assert_true(
-      await request.canMakePayment(),
-      `canMakePaymentPromise should be true`
-    );
-    assert_true(
-      await request.canMakePayment(),
-      `canMakePaymentPromise should be true`
-    );
-  } catch (err) {
-    assert_equals(
-      err.name,
-      "NotAllowedError",
-      "if it throws, then it must be a NotAllowedError."
-    );
-  }
-}, `If request.[[state]] is "created", then return a promise that resolves to true for known method.`);
-
-promise_test(async t => {
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  const acceptPromise = request.show(); // Sets state to "interactive"
-  const canMakePaymentPromise = request.canMakePayment();
-  try {
-    const result = await canMakePaymentPromise;
-    assert_true(
-      false,
-      `canMakePaymentPromise should have thrown InvalidStateError`
-    );
-  } catch (err) {
-    await promise_rejects(t, "InvalidStateError", canMakePaymentPromise);
-  } finally {
-    await request.abort();
-    await promise_rejects(t, "AbortError", acceptPromise);
-  }
-  // The state should be "closed"
-  await promise_rejects(t, "InvalidStateError", request.canMakePayment());
-}, `If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException.`);
-
-promise_test(async t => {
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  const acceptPromise = request.show(); // The state is now "interactive"
-  acceptPromise.catch(() => {}); // no-op, just to silence unhandled rejection in devtools.
-  await request.abort(); // The state is now "closed"
-  await promise_rejects(t, "InvalidStateError", request.canMakePayment());
-  try {
-    const result = await request.canMakePayment();
-    assert_true(
-      false,
-      `should have thrown InvalidStateError, but instead returned "${result}"`
-    );
-  } catch (err) {
-    assert_equals(
-      err.name,
-      "InvalidStateError",
-      "must be an InvalidStateError."
-    );
-  }
-}, `If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException.`);
-
-promise_test(async t => {
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  assert_true(await request.canMakePayment(), "basic-card should be supported");
-}, `If payment method identifier and serialized parts are supported, resolve promise with true.`);
-
-promise_test(async t => {
-  const unsupportedMethods = [
-    "this-is-not-supported",
-    "https://not.supported",
-    "e",
-    "n6jzof05mk2g4lhxr-u-q-w1-c-i-pa-ty-bdvs9-ho-ae7-p-md8-s-wq3-h-qd-e-q-sa",
-    "a-b-q-n-s-pw0",
-    "m-u",
-    "s-l5",
-    "k9-f",
-    "m-l",
-    "u4-n-t",
-    "i488jh6-g18-fck-yb-v7-i",
-    "x-x-t-t-c34-o",
-    "https://wpt",
-    "https://wpt.fyi/",
-    "https://wpt.fyi/payment",
-    "https://wpt.fyi/payment-request",
-    "https://wpt.fyi/payment-request?",
-    "https://wpt.fyi/payment-request?this=is",
-    "https://wpt.fyi/payment-request?this=is&totally",
-    "https://wpt.fyi:443/payment-request?this=is&totally",
-    "https://wpt.fyi:443/payment-request?this=is&totally#fine",
-    "https://:@wpt.fyi:443/payment-request?this=is&totally#👍",
-    " \thttps://wpt\n ",
-    "https://xn--c1yn36f",
-    "https://點看",
-  ];
-  for (const method of unsupportedMethods) {
-    try {
-      const request = new PaymentRequest(
-        [{ supportedMethods: method }],
-        defaultDetails
-      );
-      assert_false(
-        await request.canMakePayment(),
-        `method "${method}" must not be supported`
-      );
-    } catch (err) {
-      assert_equals(
-        err.name,
-        "NotAllowedError",
-        "if it throws, then it must be a NotAllowedError."
-      );
-    }
-  }
-}, `If payment method identifier is unknown, resolve promise with false.`);
-
-promise_test(async t => {
-  // This test might never actually hit its assertion, but that's allowed.
-  const request = new PaymentRequest(defaultMethods, defaultDetails);
-  for (let i = 0; i < 1000; i++) {
-    try {
-      await request.canMakePayment();
-    } catch (err) {
-      assert_equals(
-        err.name,
-        "NotAllowedError",
-        "if it throws, then it must be a NotAllowedError."
-      );
-      break;
-    }
-  }
-  for (let i = 0; i < 1000; i++) {
-    try {
-      await new PaymentRequest(defaultMethods, defaultDetails).canMakePayment();
-    } catch (err) {
-      assert_equals(
-        err.name,
-        "NotAllowedError",
-        "if it throws, then it must be a NotAllowedError."
-      );
-      break;
-    }
-  }
-}, `Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException.`);
-</script>
diff --git a/payment-request/payment-request-constructor-crash.https.html b/payment-request/payment-request-constructor-crash.https.html
index 1d0b88d..1325681 100644
--- a/payment-request/payment-request-constructor-crash.https.html
+++ b/payment-request/payment-request-constructor-crash.https.html
@@ -81,7 +81,7 @@
   try {
     new PaymentRequest(evilMethods, defaultDetails);
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, let's add an abusive amount of methods.
   while (evilMethods.length < ABUSIVE_AMOUNT) {
@@ -92,7 +92,6 @@
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if there is an abusive number of payment methods in the methodData sequence");
 
 // PaymentMethodData.supportedMethods
@@ -102,18 +101,19 @@
   try {
     new PaymentRequest([{ supportedMethods }], defaultDetails);
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, we make supportedMethods super large
-  const evilMethodData = [{
-    supportedMethods: supportedMethods.repeat(ABUSIVE_AMOUNT),
-  }];
+  const evilMethodData = [
+    {
+      supportedMethods: supportedMethods.repeat(ABUSIVE_AMOUNT),
+    },
+  ];
   try {
     new PaymentRequest(evilMethodData, defaultDetails);
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if PaymentMethodData.supportedMethods is an abusive length");
 
 // PaymentDetailsInit.id
@@ -126,7 +126,7 @@
       Object.assign({}, defaultDetails, { id })
     );
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, we make the id super large;
   const evilDetails = Object.assign({}, defaultDetails, {
@@ -137,7 +137,6 @@
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if the request id has an abusive length");
 
 // PaymentDetailsInit.total.label
@@ -147,7 +146,7 @@
   try {
     new PaymentRequest(defaultMethods, evilDetails);
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, we make the label super large;
   evilDetails.total = {
@@ -159,7 +158,6 @@
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if PaymentDetailsInit.total.label is an abusive length");
 
 test(() => {
@@ -168,7 +166,7 @@
   try {
     new PaymentRequest(defaultMethods, evilDetails);
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, we can use evilAmount
   evilDetails.total = evilAmount;
@@ -177,7 +175,6 @@
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if total.amount.value is an abusive length");
 
 for (const [prop, defaultValue] of [
@@ -191,7 +188,7 @@
     try {
       new PaymentRequest(defaultMethods, evilDetails);
     } catch (err) {
-      assert_true(false, "failed smoke test: " + err.stack);
+      assert_unreached("failed smoke test: " + err.stack);
     }
     while (evilDetails[prop].length < ABUSIVE_AMOUNT) {
       evilDetails[prop] = evilDetails[prop].concat(evilDetails[prop]);
@@ -202,7 +199,6 @@
     } catch (err) {
       assert_equals(err.name, "TypeError", "must be a TypeError");
     }
-    assert_true(true, "Didn't crash");
   }, `Don't crash if details.${prop} has an abusive number of items`);
 }
 
@@ -214,7 +210,7 @@
   try {
     new PaymentRequest(defaultMethods, evilDetails);
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, we make the label super large;
   evilShippingOption.label = "l".repeat(ABUSIVE_AMOUNT);
@@ -223,7 +219,6 @@
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if PaymentShippingOptions.label is an abusive length");
 
 test(() => {
@@ -234,7 +229,7 @@
   try {
     new PaymentRequest(defaultMethods, evilDetails);
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, we make use of evilAmount;
   evilShippingOption.amount = evilAmount;
@@ -243,7 +238,6 @@
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if the PaymentShippingOptions.amount.value is an abusive length");
 
 test(() => {
@@ -254,7 +248,7 @@
   try {
     new PaymentRequest(defaultMethods, evilDetails);
   } catch (err) {
-    assert_true(false, "failed smoke test: " + err.stack);
+    assert_unreached("failed smoke test: " + err.stack);
   }
   // Now, we make the label super large;
   evilDisplayItem.label = "l".repeat(ABUSIVE_AMOUNT);
@@ -263,8 +257,5 @@
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
-  assert_true(true, "Didn't crash");
 }, "Don't crash if PaymentItem.label is an abusive length");
-
-
 </script>
diff --git a/payment-request/payment-request-constructor.https.html b/payment-request/payment-request-constructor.https.html
index e4e1292..b5997a0 100644
--- a/payment-request/payment-request-constructor.https.html
+++ b/payment-request/payment-request-constructor.https.html
@@ -15,17 +15,41 @@
   currency: "USD",
   value: "1.0",
 });
+const defaultNumberAmount = Object.freeze({
+  currency: "USD",
+  value: 1.0,
+});
 const defaultTotal = Object.freeze({
   label: "Default Total",
   amount: defaultAmount,
 });
+const defaultNumberTotal = Object.freeze({
+  label: "Default Number Total",
+  amount: defaultNumberAmount,
+});
 const defaultDetails = Object.freeze({
   total: defaultTotal,
+  displayItems: [
+    {
+      label: "Default Display Item",
+      amount: defaultAmount,
+    },
+  ],
+});
+const defaultNumberDetails = Object.freeze({
+  total: defaultNumberTotal,
+  displayItems: [
+    {
+      label: "Default Display Item",
+      amount: defaultNumberAmount,
+    },
+  ],
 });
 
 // Avoid false positives, this should always pass
 function smokeTest() {
   new PaymentRequest(defaultMethods, defaultDetails);
+  new PaymentRequest(defaultMethods, defaultNumberDetails);
 }
 test(() => {
   smokeTest();
@@ -177,6 +201,7 @@
   "-1.0",
   "-1.00",
   "-1000.000",
+  -10,
 ]);
 test(() => {
   smokeTest();
diff --git a/payment-request/payment-request-ctor-pmi-handling.https.html b/payment-request/payment-request-ctor-pmi-handling.https.html
index ee9e2c0..5f888f0 100644
--- a/payment-request/payment-request-ctor-pmi-handling.https.html
+++ b/payment-request/payment-request-ctor-pmi-handling.https.html
@@ -60,6 +60,8 @@
     "i488jh6-g18-fck-yb-v7-i",
     "x-x-t-t-c34-o",
     "basic-card",
+    // gets coerced to "basic-card", for compat with old version of spec
+    ["basic-card"],
   ];
   for (const validMethod of validMethods) {
     try {
@@ -105,6 +107,8 @@
     " basic-card ",
     "this is not supported",
     " ",
+    "foo,var",
+    ["visa","mastercard"], // stringifies to "visa,mastercard"
   ];
   for (const invalidMethod of invalidMethods) {
     assert_throws(
@@ -116,7 +120,7 @@
       `expected RangeError processing invalid standardized PMI "${invalidMethod}"`
     );
   }
-});
+}, "Must throw on syntactically invalid standardized payment method identifiers");
 
 test(() => {
   const invalidMethods = [
diff --git a/payment-request/payment-request-id-attribute.https.html b/payment-request/payment-request-id-attribute.https.html
index 9d77a88..455b65a 100644
--- a/payment-request/payment-request-id-attribute.https.html
+++ b/payment-request/payment-request-id-attribute.https.html
@@ -2,24 +2,38 @@
 <!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
 <meta charset="utf-8">
 <title>Test for PaymentRequest id attribute</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#constructor">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
+const methods = [{ supportedMethods: "foo" }];
+const total = { label: "label", amount: { currency: "USD", value: "5.00" } };
+
 test(() => {
-  const methods = [{ supportedMethods: "foo" }];
-  const total = { label: "label", amount: { currency: "USD", value: "5.00" } };
   const request1 = new PaymentRequest(methods, {
     id: "pass",
     total,
   });
   assert_idl_attribute(request1, "id");
   assert_equals(request1.id, "pass", "Expected PaymentRequest.id to be 'pass'");
+}, "PaymentRequest's id attribute's value can be set via PaymentDetailsInit dictionary");
+
+// Test for https://github.com/w3c/payment-request/pull/665
+test(() => {
+  const uuidRegExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-4][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+  const request1 = new PaymentRequest(methods, {
+    total,
+  });
   const request2 = new PaymentRequest(methods, {
     total,
   });
   assert_true(
-    request2.id && typeof request2.id === "string",
-    "Expected PaymentRequest.id to be some auto-generated truthy string"
+    uuidRegExp.test(request1.id) && uuidRegExp.test(request2.id) ,
+    "Expected PaymentRequest.id be a UUID"
   );
-}, "PaymentRequest id attribute");
+  assert_not_equals(
+    request1.id, request2.id,
+    "Expected PaymentRequest.id be unique per instance"
+  );
+}, "PaymentRequest's id attribute must be a UUID when PaymentDetailsInit.id is missing");
 </script>
diff --git a/payment-request/payment-request-insecure.http.html b/payment-request/payment-request-insecure.http.html
new file mode 100644
index 0000000..0212220
--- /dev/null
+++ b/payment-request/payment-request-insecure.http.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest Constructor (insecure)</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#paymentrequest-interface">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+  assert_false(isSecureContext);
+  assert_false("PaymentRequest" in window);
+}, "PaymentRequest constructor must not be exposed in insecure context");
+</script>
diff --git a/payment-request/payment-request-not-exposed.https.worker.js b/payment-request/payment-request-not-exposed.https.worker.js
new file mode 100644
index 0000000..e5576e6
--- /dev/null
+++ b/payment-request/payment-request-not-exposed.https.worker.js
@@ -0,0 +1,7 @@
+importScripts("/resources/testharness.js");
+
+test(() => {
+  assert_true(isSecureContext);
+  assert_false('PaymentRequest' in self);
+}, "PaymentRequest constructor must not be exposed in worker global scope");
+done();
diff --git a/payment-request/payment-request-show-method-manual.https.html b/payment-request/payment-request-show-method-manual.https.html
new file mode 100644
index 0000000..fab78f4
--- /dev/null
+++ b/payment-request/payment-request-show-method-manual.https.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for PaymentRequest.show() method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+setup({
+  explicit_done: true,
+  explicit_timeout: true,
+});
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
+const defaultMethods = Object.freeze([basicCard]);
+const defaultDetails = Object.freeze({
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
+
+test(() => {
+  try {
+    new PaymentRequest(defaultMethods, defaultDetails);
+  } catch (err) {
+    done();
+    throw err;
+  }
+}, "Must be possible to construct a payment request");
+
+function manualTest1(button){
+  button.disabled = true;
+  promise_test(async t => {
+    const request = new PaymentRequest(defaultMethods, defaultDetails);
+    const acceptPromise = request.show(); // Sets state to "interactive"
+    await promise_rejects(t, "InvalidStateError", request.show());
+    await request.abort();
+    await promise_rejects(t, "AbortError", acceptPromise);
+  }, button.textContent.trim());
+}
+
+function manualTest2(button){
+  button.disabled = true;
+  promise_test(async t => {
+    const request1 = new PaymentRequest(defaultMethods, defaultDetails);
+    const request2 = new PaymentRequest(defaultMethods, defaultDetails);
+    const acceptPromise1 = request1.show();
+    const acceptPromise2 = request2.show();
+    await promise_rejects(t, "AbortError", acceptPromise2);
+    await request1.abort();
+    await promise_rejects(t, "AbortError", acceptPromise1);
+  }, button.textContent.trim());
+}
+
+function manualTest3(button){
+  button.disabled = true;
+  promise_test(async t => {
+    const request = new PaymentRequest(
+      [{ supportedMethods: "this-is-not-supported" }],
+      defaultDetails);
+    const acceptPromise = request.show();
+    await promise_rejects(t, "NotSupportedError", acceptPromise);
+  }, button.textContent.trim());
+  done();
+}
+</script>
+
+<h2>Test for PaymentRequest.show() method</h2>
+<p>
+  Click on each button in sequence from top to bottom without refreshing the page.
+</p>
+<ol>
+  <li>
+    <button onclick="manualTest1(this)">
+      Throws if the promise [[state]] is not "created"
+    </button>
+  </li>
+  <li>
+    <button onclick="manualTest2(this)">
+      If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException.
+    </button>
+  </li>
+  <li>
+    <button onclick="manualTest3(this)">
+      If payment method consultation produces no supported method of payment, then return a promise rejected with a "NotSupportedError" DOMException.
+    </button>
+  </li>
+</ol>
+<small>
+  If you find a buggy test, please <a href="https://github.com/w3c/web-platform-tests/issues">file a bug</a>
+  and tag one of the <a href="https://github.com/w3c/web-platform-tests/blob/master/payment-request/OWNERS">owners</a>.
+</small>
diff --git a/payment-request/payment-request-show-method.https.html b/payment-request/payment-request-show-method.https.html
index 555c493..147e55d 100644
--- a/payment-request/payment-request-show-method.https.html
+++ b/payment-request/payment-request-show-method.https.html
@@ -7,8 +7,11 @@
 <script src="/resources/testharnessreport.js"></script>
 <script>
 'use strict';
-const basicCard = Object.freeze({ supportedMethods: "basic-card" });
-const defaultMethods = Object.freeze([basicCard]);
+const defaultMethods = Object.freeze([
+  { supportedMethods: "basic-card" },
+  { supportedMethods: "https://apple.com/apple-pay" }
+]);
+
 const defaultDetails = Object.freeze({
   total: {
     label: "Total",
@@ -19,39 +22,9 @@
   },
 });
 
-test(() => {
-  try {
-    new PaymentRequest(defaultMethods, defaultDetails);
-  } catch (err) {
-    done();
-    throw err;
-  }
-}, "Must be possible to construct a payment request");
-
-
 promise_test(async t => {
   const request = new PaymentRequest(defaultMethods, defaultDetails);
-  const acceptPromise = request.show(); // Sets state to "interactive"
-  await promise_rejects(t, "InvalidStateError", request.show());
-  await request.abort();
-  await promise_rejects(t, "AbortError", acceptPromise);
-}, `Throws if the promise [[state]] is not "created"`);
-
-promise_test(async t => {
-  const request1 = new PaymentRequest(defaultMethods, defaultDetails);
-  const request2 = new PaymentRequest(defaultMethods, defaultDetails);
-  const acceptPromise1 = request1.show();
-  const acceptPromise2 = request2.show();
-  await promise_rejects(t, "AbortError", acceptPromise2);
-  await request1.abort();
-  await promise_rejects(t, "AbortError", acceptPromise1);
-}, `If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException.`);
-
-promise_test(async t => {
-  const request = new PaymentRequest(
-    [{ supportedMethods: "this-is-not-supported" }],
-    defaultDetails);
   const acceptPromise = request.show();
-  await promise_rejects(t, "NotSupportedError", acceptPromise);
-}, `If payment method consultation produces no supported method of payment, then return a promise rejected with a "NotSupportedError" DOMException.`);
+  await promise_rejects(t, "SecurityError", acceptPromise);
+}, `Calling show() without being triggered by user interaction throws`);
 </script>
diff --git a/payment-request/show-method-optional-promise-rejects-manual.https.html b/payment-request/show-method-optional-promise-rejects-manual.https.html
new file mode 100644
index 0000000..1a3b5fe
--- /dev/null
+++ b/payment-request/show-method-optional-promise-rejects-manual.https.html
@@ -0,0 +1,281 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for PaymentRequest.show(optional detailsPromise) method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  // See function testBadUpdate() for test details!
+  setup({
+    allow_uncaught_exception: true,
+    explicit_done: true,
+    explicit_timeout: true,
+  });
+
+  // == TEST DATA ===
+  // PaymentMethod
+  const validMethod = Object.freeze({
+    supportedMethods: "valid-but-wont-ever-match",
+  });
+
+  const validMethodBasicCard = Object.freeze({
+    supportedMethods: "basic-card",
+  });
+
+  const validMethodApplePay = Object.freeze({
+    supportedMethods: "https://apple.com/apple-pay",
+  });
+
+  // Methods
+  const validMethods = Object.freeze([
+    validMethodBasicCard,
+    validMethod,
+    validMethodApplePay,
+  ]);
+
+  // Amounts
+  const validAmount = Object.freeze({
+    currency: "USD",
+    value: "1.00",
+  });
+
+  const invalidAmount = Object.freeze({
+    currency: "¡INVALID!",
+    value: "A1.0",
+  });
+
+  const negativeAmount = Object.freeze({
+    currency: "USD",
+    value: "-1.00",
+  });
+
+  // Totals
+  const validTotal = Object.freeze({
+    label: "Valid Total",
+    amount: validAmount,
+  });
+
+  const invalidTotal = Object.freeze({
+    label: "Invalid Total",
+    amount: invalidAmount,
+  });
+
+  const invalidNegativeTotal = Object.freeze({
+    label: "Invalid negative total",
+    amount: negativeAmount,
+  });
+
+  // PaymentDetailsInit
+  const validDetails = Object.freeze({
+    total: validTotal,
+  });
+
+  const invalidDetailsNegativeTotal = Object.freeze({
+    total: invalidNegativeTotal,
+  });
+
+  // PaymentOptions
+  const validOptions = Object.freeze({
+    requestShipping: true,
+  });
+
+  // PaymentItem
+  const validPaymentItem = Object.freeze({
+    amount: validAmount,
+    label: "Valid payment item",
+  });
+
+  const invalidPaymentItem = Object.freeze({
+    amount: invalidAmount,
+    label: "Invalid payment item",
+  });
+
+  // PaymentItem
+  const validPaymentItems = Object.freeze([validPaymentItem]);
+  const invalidPaymentItems = Object.freeze([invalidPaymentItem]);
+
+  // PaymentShippingOption
+  const invalidShippingOption = Object.freeze({
+    id: "abc",
+    label: "Invalid shipping option",
+    amount: invalidAmount,
+    selected: true,
+  });
+
+  // PaymentShippingOptions
+  const validShippingOption = Object.freeze({
+    id: "abc",
+    label: "valid shipping option",
+    amount: validAmount,
+  });
+
+  const validShippingOptions = Object.freeze([validShippingOption]);
+  const invalidShippingOptions = Object.freeze([invalidShippingOption]);
+
+  // PaymentDetailsModifier
+  const validModifier = Object.freeze({
+    additionalDisplayItems: validPaymentItems,
+    supportedMethods: "valid-but-wont-ever-match",
+    total: validTotal,
+  });
+
+  const modifierWithInvalidDisplayItems = Object.freeze({
+    additionalDisplayItems: invalidPaymentItems,
+    supportedMethods: "basic-card",
+    total: validTotal,
+  });
+
+  const modifierWithValidDisplayItems = Object.freeze({
+    additionalDisplayItems: validPaymentItems,
+    supportedMethods: "basic-card",
+    total: validTotal,
+  });
+
+  const modifierWithInvalidTotal = Object.freeze({
+    additionalDisplayItems: validPaymentItems,
+    supportedMethods: "basic-card",
+    total: invalidTotal,
+  });
+
+  const recursiveData = {};
+  recursiveData.foo = recursiveData;
+  Object.freeze(recursiveData);
+
+  const modifierWithRecursiveData = Object.freeze({
+    supportedMethods: validMethodBasicCard,
+    total: validTotal,
+    data: recursiveData,
+  });
+  // == END OF TEST DATA ===
+  /*
+    These test work by creating a "valid" payment request and then
+    performing a bad update via `show(detailsPromise)`.
+    The `badDetails` cause detailsPromise to reject with `expectedError`.
+    */
+  function testBadUpdate(testAssertion, badDetails, expectedError) {
+    promise_test(async t => {
+      const request = new PaymentRequest(
+        validMethods,
+        validDetails,
+        validOptions
+      );
+      const detailsPromise = Promise.resolve(badDetails);
+      const acceptPromise = request.show(detailsPromise);
+      await promise_rejects(
+        t,
+        expectedError,
+        acceptPromise,
+        "badDetails must cause acceptPromise to reject with expectedError"
+      );
+    }, testAssertion);
+  }
+</script>
+<h2>
+  PaymentRequest <code>.show(optional detailsPromise)</code> tests
+</h2>
+<h3>
+  Bad details - causes `detailsPromise` to reject.
+</h3>
+<p>
+  Click on each button in sequence from top to bottom without refreshing the page.
+  No payment sheet should be shown, as all provided values cause an error.
+</p>
+<p>
+  <strong>
+    If you see a payment sheet, it means the test has failed.
+  </strong>
+</p>
+<ol>
+  <li><button onclick="
+    const rejectedPromise = Promise.reject(new SyntaxError('test'))
+      .catch(err => err);
+    testBadUpdate(this.textContent, rejectedPromise, 'AbortError');
+  ">
+  Rejection of detailsPromise must abort the update with an 'AbortError' DOMException.
+  </button></li>
+
+  <li><button onclick="
+    const invalidDetails = { total: `this will cause a TypeError!` };
+    testBadUpdate(this.textContent, invalidDetails, new TypeError());
+  ">
+  Total in the update is a string, so converting to IDL must abort the update with a TypeError.
+  </button></li>
+
+  <li><button onclick="
+    const invalidDetails = { total: recursiveData };
+    testBadUpdate(this.textContent, invalidDetails, new TypeError());
+  ">
+  Total is recursive, so converting to IDL must abort the update with a TypeError.
+  </button></li>
+
+  <li><button onclick="
+    testBadUpdate(this.textContent, invalidDetailsNegativeTotal, new TypeError());
+  ">
+  Updating with a negative total results in a TypeError.
+  </button></li>
+
+  <li><button onclick="
+    const badDetails = Object.assign({}, validDetails, {
+      displayItems: invalidPaymentItems
+    });
+    testBadUpdate(this.textContent, badDetails, new RangeError());
+  ">
+  Updating with a displayItem with an invalid currency results in RangeError.
+  </button></li>
+
+  <li><button onclick="
+    const duplicateShippingOptions = [validShippingOption, validShippingOption];
+    const badDetails = Object.assign({}, validDetails, {
+      shippingOptions: duplicateShippingOptions,
+    });
+    testBadUpdate(this.textContent, badDetails, new TypeError());
+  ">
+  Updating with duplicate shippingOptions (same IDs) results in a TypeError.
+  </button></li>
+  <li><button onclick="
+    const badDetails = Object.assign({}, validDetails, {
+      shippingOptions: invalidShippingOptions,
+    });
+    testBadUpdate(this.textContent, badDetails, new RangeError());
+  ">
+  Updating with a shippingOption with an invalid currency value results in a RangError.
+  </button></li>
+
+  <li><button onclick="
+    // validModifier is there as to avoid false positives
+    const badModifiers = { modifiers: [modifierWithInvalidTotal, validModifier] };
+    const badDetails = Object.assign({}, validDetails, badModifiers);
+    testBadUpdate(this.textContent, badDetails, new RangeError());
+  ">
+  Must throw a RangeError when a modifier's total item has an invalid currency.
+  </button></li>
+
+  <li><button onclick="
+    // validModifier is there as to avoid false positives
+    const badModifiers = {
+      modifiers: [modifierWithInvalidDisplayItems, validModifier],
+    };
+    const badDetails = Object.assign({}, validDetails, badModifiers);
+    testBadUpdate(this.textContent, badDetails, new RangeError());
+  ">
+  Must throw a RangeError when a modifier display item has an invalid currency.
+  </button></li>
+
+  <li><button onclick="
+    // validModifier is there as to avoid false positives
+    const badModifiers = {
+      modifiers: [modifierWithRecursiveData, validModifier],
+    };
+    const badDetails = Object.assign({}, validDetails, badModifiers);
+    testBadUpdate(this.textContent, badDetails, new TypeError());
+  ">
+  Must throw as Modifier has a recursive dictionary.
+  </button></li>
+
+  <li><button onclick="done();">Done!</button></li>
+</ol>
+
+<small>
+  If you find a buggy test, please <a href="https://github.com/w3c/web-platform-tests/issues">file a bug</a>
+  and tag one of the <a href="https://github.com/w3c/web-platform-tests/blob/master/payment-request/OWNERS">owners</a>.
+</small>
diff --git a/payment-request/show-method-optional-promise-resolves-manual.https.html b/payment-request/show-method-optional-promise-resolves-manual.https.html
new file mode 100644
index 0000000..6ec74bb
--- /dev/null
+++ b/payment-request/show-method-optional-promise-resolves-manual.https.html
@@ -0,0 +1,319 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for PaymentRequest.show(optional promise) method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+setup({
+  allow_uncaught_exception: true,
+  explicit_done: true,
+  explicit_timeout: true,
+});
+
+// DATA USED BY TESTS
+// PaymentMethods
+const validMethods = Object.freeze([
+  {
+    supportedMethods: "valid-but-wont-ever-match",
+  },
+  {
+    supportedMethods: "basic-card",
+  },
+  {
+    supportedMethods: "https://apple.com/apple-pay",
+  },
+]);
+
+// Amounts
+const failAmount = Object.freeze({
+  currency: "USD",
+  value: "1.00",
+});
+const passAmount = Object.freeze({
+  currency: "CAD",
+  value: "50.00",
+});
+const neutralAmount = Object.freeze({
+  currency: "AUD",
+  value: "0.00",
+});
+
+// Labels
+const failLabel = "💥 TEST HAS FAILED 💥";
+const passLabel = "✅ TEST HAS PASSED ✅";
+const neutralLabel = "Ignore this label";
+// Totals
+const failTotal = Object.freeze({
+  label: failLabel,
+  amount: failAmount,
+});
+const passTotal = Object.freeze({
+  label: passLabel,
+  amount: passAmount,
+});
+const neutralTotal = Object.freeze({
+  label: neutralLabel,
+  amount: passAmount,
+});
+
+// PaymentItem
+const failPaymentItem = Object.freeze({
+  amount: failAmount,
+  label: failLabel,
+});
+const failPaymentItems = Object.freeze([failPaymentItem]);
+
+const passPaymentItem = Object.freeze({
+  amount: passAmount,
+  label: passLabel,
+});
+const passPaymentItems = Object.freeze([passPaymentItem]);
+
+// PaymentShippingOptions
+const failShippingOption = Object.freeze({
+  id: "fail",
+  label: failLabel,
+  amount: failAmount,
+});
+const failShippingOptions = Object.freeze([failShippingOption]);
+
+const neutralShippingOption = Object.freeze({
+  id: "neutral",
+  label: neutralLabel,
+  amount: neutralAmount,
+});
+
+const updatedShippingOption1 = Object.freeze({
+  id: "updated-1",
+  label: `${passLabel} - option 1`,
+  amount: passAmount,
+});
+const updatedShippingOption2 = Object.freeze({
+  id: "updated-2",
+  label: `${passLabel} - option 2 (MUST BE SELECTED!)`,
+  amount: passAmount,
+  selected: true,
+});
+const passShippingOptions = Object.freeze([
+  updatedShippingOption1,
+  updatedShippingOption2,
+]);
+
+// Modifiers
+// create a modifier objects for each validMethods
+// and single additional display item
+const failModifiers = validMethods.map(modifier => {
+  const label = `${failLabel} - (${modifier.supportedMethods})`;
+  return {
+    ...modifier,
+    total: {
+      ...failTotal,
+      label,
+    },
+    additionalDisplayItems: [
+      {
+        ...failPaymentItem,
+        label,
+      },
+    ],
+  };
+});
+// Updates the total for each, and changes the additionalDisplayItems
+const passModifiers = failModifiers.map(modifier => {
+  const label = `${passLabel} - (${modifier.supportedMethods})`;
+  return {
+    ...modifier,
+    total: {
+      ...passTotal,
+      label,
+    },
+    additionalDisplayItems: [
+      {
+        ...passPaymentItem,
+        label,
+      },
+    ],
+  };
+});
+
+// PaymentDetailsInit
+const failDetails = Object.freeze({
+  displayItems: failPaymentItems,
+  id: "this cannot be changed",
+  modifiers: failModifiers,
+  shippingOptions: failShippingOptions,
+  total: failTotal,
+});
+
+const neutralDetails = Object.freeze({
+  displayItems: [],
+  modifiers: [],
+  shippingOptions: [neutralShippingOption],
+  total: neutralTotal,
+});
+
+function smokeTest() {
+  promise_test(async t => {
+    const request = new PaymentRequest(validMethods, failDetails);
+    await promise_rejects(
+      t,
+      new TypeError(),
+      request.show({
+        total: "This throws a TypeError",
+      }),
+      "expected TypeError"
+    );
+  }, "smoke test - checks if the optional details are supported on show() method");
+}
+
+function runUpdateDetailsAlgorithm(
+  buttonElement,
+  details,
+  options = {
+    requestShipping: true,
+  }
+) {
+  const testAssertion = buttonElement.textContent.trim();
+  buttonElement.disabled = true;
+  promise_test(async t => {
+    const request = new PaymentRequest(validMethods, failDetails, options);
+    const detailsPromise = Promise.resolve(details);
+    const acceptPromise = request.show(detailsPromise);
+    assert_equals(request.id, "this cannot be changed", "id must never change.");
+    await promise_rejects(
+      t,
+      "AbortError",
+      acceptPromise,
+      "expected AbortError"
+    );
+  }, testAssertion);
+}
+</script>
+<h2>
+  PaymentRequest <code>.show(optional detailsPromise)</code> tests
+</h2>
+<p>
+  These test cause <code>detailsPromise</code> to resolve successfully with some updated value. As such, that will cause
+  something in the payment sheet to change. Each test describes what is expected to change - if anything.
+</p>
+<p>
+  <strong>Instructions:</strong> Click on each button in sequence from top to bottom without refreshing the page. The payment
+  sheet will be shown. If required, confirm that the expected value appears in the payment sheet. Finally, manually abort/cancel
+  the payment request by closing the payment sheet.
+</p>
+<ol>
+  <li><button onclick="smokeTest()">If the payment sheet is shown, the test has failed.</button></li>
+  <li><button onclick="
+    const details = {
+      ...neutralDetails,
+      id: 'fail',
+    };
+    runUpdateDetailsAlgorithm(this, details);
+  ">
+    When the payment sheet is shown, the provided `id` must have no effect on the payment request.
+  </button></li>
+  <li><button onclick="
+    const details = {
+      ...neutralDetails,
+      total: passTotal
+    };
+    runUpdateDetailsAlgorithm(this, details);
+  ">
+    When the payment sheet is shown, the total must be CAD$50 with the label "✅ TEST HAS PASSED ✅".
+  </button></li>
+  <li><button onclick="
+    const details = {
+      ...neutralDetails,
+      displayItems: passPaymentItems,
+    };
+    runUpdateDetailsAlgorithm(this, details);
+    ">
+    When the payment sheet is shown, there must be a one display item with a value of CAD$50 with the label "✅ TEST HAS PASSED ✅".
+    </button>
+  </li>
+  <li><button onclick="
+    const auItem = {
+      ...passPaymentItem,
+      amount: { value: '40', currency: 'AUD'},
+      pending: true
+    }
+    const details = {
+      ...neutralDetails,
+      displayItems: passPaymentItems.concat(auItem),
+    };
+    runUpdateDetailsAlgorithm(this, details);
+    ">
+    When the payment sheet is shown, there must be
+    two display items: One with a value of CAD$50, another with
+    value AUD$40 that is pending.
+    </button>
+  </li>
+  <li><button onclick="
+    const details = {
+      ...neutralDetails,
+      shippingOptions: [updatedShippingOption1],
+    };
+    runUpdateDetailsAlgorithm(this, details);
+    ">
+    When the payment sheet is shown, there must be a one shipping option
+    with a value of CAD$50.
+    </button>
+  </li>
+  <li><button onclick="
+    const details = {
+      ...neutralDetails,
+      shippingOptions: passShippingOptions,
+    };
+    runUpdateDetailsAlgorithm(this, details);
+    ">
+    When the payment sheet is shown, there must be
+    two shipping options: One with a value of CAD$50, another with
+    value AUD$40 that is selected.
+    </button>
+  </li>
+  <li><button onclick="
+    const details = {
+      ...neutralDetails,
+      modifiers: passModifiers,
+    };
+    runUpdateDetailsAlgorithm(this, details);
+    ">
+    When the payment sheet is shown, the total should be CAD$50.
+  </button>
+  </li>
+  <li>
+    <button onclick="
+      const details = {
+        ...neutralDetails,
+        shippingOptions: [],
+        error: passLabel,
+      };
+      runUpdateDetailsAlgorithm(this, details);
+    ">
+    When the payment sheet is shown, the string "✅ TEST HAS PASSED ✅" should be shown
+    somewhere in the user interface. Alternatively, the payment sheet must indicate to the
+    end user that it's not possible to ship their order.
+    </button>
+  </li>
+  <li>
+    <button onclick="
+      const details = {
+        ...neutralDetails,
+        error: failLabel,
+      };
+      runUpdateDetailsAlgorithm(this, details, {requestShipping: false});
+    ">
+    When the payment sheet is shown, there should not be any errors shown.
+    </button>
+  </li>
+  <li>
+    <button onclick="done();">Done!</button>
+  </li>
+</ol>
+
+<small>
+  If you find a buggy test, please <a href="https://github.com/w3c/web-platform-tests/issues">file a bug</a>
+  and tag one of the <a href="https://github.com/w3c/web-platform-tests/blob/master/payment-request/OWNERS">owners</a>.
+</small>
diff --git a/payment-request/show-method-postmessage-iframe.html b/payment-request/show-method-postmessage-iframe.html
new file mode 100644
index 0000000..12a1e0c
--- /dev/null
+++ b/payment-request/show-method-postmessage-iframe.html
@@ -0,0 +1,40 @@
+<h1>This iframe calls shows() via postMessage()</h1>
+<script>
+"use strict";
+const defaultMethods = Object.freeze([
+  { supportedMethods: "basic-card" },
+  { supportedMethods: "https://apple.com/apple-pay" },
+]);
+
+const defaultDetails = Object.freeze({
+  id: "fail",
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
+
+// We are going to use the id to prove that this works
+// which we will pass back to the caller
+window.onmessage = async event => {
+  const { source, data: { id, request } } = event;
+  switch (request) {
+    case "show-payment-request": {
+      const details = Object.assign({}, defaultDetails, { id });
+      const request = new PaymentRequest(defaultMethods, details);
+      try {
+        const response = await request.show();
+        source.postMessage(response.toJSON(), window.location.origin);
+        await response.complete();
+      } catch (err) {
+        source.postMessage({ requestId: "fail" }, window.location.origin);
+        await request.abort();
+      }
+    }
+  }
+};
+
+</script>
diff --git a/payment-request/show-method-postmessage-manual.https.html b/payment-request/show-method-postmessage-manual.https.html
new file mode 100644
index 0000000..0a7f37f
--- /dev/null
+++ b/payment-request/show-method-postmessage-manual.https.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for PaymentRequest.show() method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+setup({
+  explicit_done: true,
+  explicit_timeout: true,
+  allow_uncaught_exception: true,
+});
+
+const defaultMethods = Object.freeze([
+  { supportedMethods: "basic-card" },
+  { supportedMethods: "https://apple.com/apple-pay" },
+]);
+
+const defaultDetails = Object.freeze({
+  id: "fail",
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
+
+test(() => {
+  assert_throws(
+    "SecurityError",
+    () => {
+      const request = new PaymentRequest(defaultMethods, defaultDetails);
+      request.show(); // <--- should throw here
+      request.abort();
+    },
+    "throws a SecurityError if not triggered by user activation"
+  );
+});
+
+async function runUserActivation(button) {
+  button.disabled = true;
+  const { contentWindow: iframeWindow } = document.getElementById("iframe");
+  const expectedId = "pass123";
+  await Promise.resolve(); // next tick
+  const promiseForResponse = new Promise(resolve => {
+    window.onmessage = ({ data: { requestId } }) => resolve(requestId);
+  });
+  const ops = { id: expectedId, request: "show-payment-request" };
+  iframeWindow.postMessage(ops, window.location.origin);
+  promise_test(async () => {
+    const actualId = await promiseForResponse;
+    assert_equals(actualId, expectedId, "ids must match");
+  }, button.textContent.trim());
+  done();
+}
+
+</script>
+<h2>Test PaymentRequest.show() triggered by user activation using postMessage()</h2>
+<p>
+  Tests that user activation works over postMessage().
+</p>
+<p>
+  Click on bottom below. Hit "Pay".
+</p>
+<ol>
+  <li>
+    <button onclick="runUserActivation(this)">
+      show() is triggered by user activation passed through postMessage() and a promise
+    </button>
+  </li>
+</ol>
+<iframe width="100%" id="iframe" src="show-method-postmessage-iframe.html" allowpaymentrequest></iframe>
+<p>
+  <small>
+    If you find a buggy test, please <a href="https://github.com/w3c/web-platform-tests/issues">file a bug</a>
+    and tag one of the <a href="https://github.com/w3c/web-platform-tests/blob/master/payment-request/OWNERS">owners</a>.
+  </small>
+</p>
diff --git a/performance-timeline/idlharness.html b/performance-timeline/idlharness.html
index 8185386..8cef4fb 100644
--- a/performance-timeline/idlharness.html
+++ b/performance-timeline/idlharness.html
@@ -67,6 +67,7 @@
 interface PerformanceObserver {
     void observe(PerformanceObserverInit options);
     void disconnect();
+    PerformanceEntryList takeRecords();
 };
 </pre>
 
diff --git a/performance-timeline/performanceentry-tojson.html b/performance-timeline/performanceentry-tojson.html
new file mode 100644
index 0000000..8576872
--- /dev/null
+++ b/performance-timeline/performanceentry-tojson.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+test(() => {
+  performance.mark('markName');
+  performance.measure('measureName');
+
+  const entries = performance.getEntries();
+  const performanceEntryKeys = [
+    'name',
+    'entryType',
+    'startTime',
+    'duration'
+  ];
+  for (let i = 0; i < entries.length; ++i) {
+    assert_equals(typeof(entries[i].toJSON), 'function');
+    const json = entries[i].toJSON();
+    assert_equals(typeof(json), 'object');
+    for (const key of performanceEntryKeys) {
+      assert_equals(json[key], entries[i][key],
+        `entries[${i}].toJSON().${key} should match entries[${i}].${key}`);
+    }
+  }
+}, 'Test toJSON() in PerformanceEntry');
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/performance-timeline/po-navigation.html b/performance-timeline/po-navigation.html
deleted file mode 100644
index ba3af64..0000000
--- a/performance-timeline/po-navigation.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<title>PerformanceObservers: navigation</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="performanceobservers.js"></script>
-<h1>PerformanceObservers: navigation</h1>
-<p>
-Navigation will <a href="https://w3c.github.io/performance-timeline/#dfn-queue-a-performanceentry">queue a PerformanceEntry</a>.
-</p>
-<div id="log"></div>
-<script>
-  async_test(function (t) {
-    var observer = new PerformanceObserver(
-        t.step_func(function (entryList, obs) {
-          checkEntries(entryList.getEntries(),
-            [{ entryType: "navigation", name: "document"}]);
-          checkEntries(entryList.getEntriesByType("navigation"),
-            [{ entryType: "navigation", name: "document"}]);
-          checkEntries(entryList.getEntriesByName("document"),
-            [{ entryType: "navigation", name: "document"}]);
-          observer.disconnect();
-          t.done();
-        })
-      );
-    observer.observe({entryTypes: ["navigation"]});
-  }, "navigation entry is observable");
-</script>
diff --git a/performance-timeline/po-takeRecords.html b/performance-timeline/po-takeRecords.html
new file mode 100644
index 0000000..eb660f7
--- /dev/null
+++ b/performance-timeline/po-takeRecords.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>PerformanceObserver: takeRecords</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="performanceobservers.js"></script>
+<script>
+  async_test(function (t) {
+    const observer = new PerformanceObserver(function (entryList, observer) {
+      assert_unreached('This callback should not have been called.')
+    });
+    let entries = observer.takeRecords();
+    checkEntries(entries, [], 'No records before observe');
+    observer.observe({entryTypes: ['mark']});
+    assert_equals(typeof(observer.takeRecords), 'function');
+    entries = observer.takeRecords();
+    checkEntries(entries, [], 'No records just from observe');
+    performance.mark('a');
+    performance.mark('b');
+    entries = observer.takeRecords();
+    checkEntries(entries, [
+      {entryType: 'mark', name: 'a'},
+      {entryType: 'mark', name: 'b'}
+    ]);
+    performance.mark('c');
+    performance.mark('d');
+    performance.mark('e');
+    entries = observer.takeRecords();
+    checkEntries(entries, [
+      {entryType: 'mark', name: 'c'},
+      {entryType: 'mark', name: 'd'},
+      {entryType: 'mark', name: 'e'}
+    ]);
+    entries = observer.takeRecords();
+    checkEntries(entries, [], 'No entries right after takeRecords');
+    observer.disconnect();
+    t.done();
+  }, "Test PerformanceObserver's takeRecords()");
+</script>
diff --git a/performance-timeline/webtiming-resolution.html b/performance-timeline/webtiming-resolution.html
new file mode 100644
index 0000000..1723c29
--- /dev/null
+++ b/performance-timeline/webtiming-resolution.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+function testTimeResolution(highResTimeFunc, funcString) {
+    test(() => {
+        const t0 = highResTimeFunc();
+        let t1 = highResTimeFunc();
+        while (t0 == t1) {
+            t1 = highResTimeFunc();
+        }
+        assert_greater_than_equal(t1 - t0, 0.02, 'The second ' + funcString + ' should be much greater than the first');
+    }, 'Verifies the resolution of ' + funcString + ' is at least 20 microseconds.');
+}
+
+function timeByPerformanceNow() {
+    return performance.now();
+}
+
+function timeByUserTiming() {
+    performance.mark('timer');
+    const time = performance.getEntriesByName('timer')[0].startTime;
+    performance.clearMarks('timer');
+    return time;
+}
+
+testTimeResolution(timeByPerformanceNow, 'performance.now()');
+testTimeResolution(timeByUserTiming, 'entry.startTime');
+</script>
+</body>
+</html>
diff --git a/picture-in-picture/OWNERS b/picture-in-picture/OWNERS
new file mode 100644
index 0000000..8f88b61
--- /dev/null
+++ b/picture-in-picture/OWNERS
@@ -0,0 +1,2 @@
+@beaufortfrancois
+@mounirlamouri
diff --git a/picture-in-picture/disable-picture-in-picture.html b/picture-in-picture/disable-picture-in-picture.html
new file mode 100644
index 0000000..1a59b7a
--- /dev/null
+++ b/picture-in-picture/disable-picture-in-picture.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<title>Test disable Picture-in-Picture</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+test(t => {
+  const video = document.createElement('video');
+  assert_false(video.disablePictureInPicture); // default value
+
+  video.setAttribute('disablepictureinpicture', 'foo');
+  assert_true(video.disablePictureInPicture);
+
+  video.removeAttribute('disablepictureinpicture');
+  assert_false(video.disablePictureInPicture);
+
+  video.disablePictureInPicture = true;
+  assert_equals(video.getAttribute('disablepictureinpicture'), '');
+
+  video.disablePictureInPicture = false;
+  assert_equals(video.getAttribute('disablepictureinpicture'), null);
+}, 'Test disablePictureInPicture IDL attribute');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  video.disablePictureInPicture = true;
+  return promise_rejects(t, 'InvalidStateError',
+      requestPictureInPictureWithTrustedClick(video));
+}, 'Request Picture-in-Picture rejects if disablePictureInPicture is true');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  return callWithTrustedClick(async () => {
+    const promise = video.requestPictureInPicture();
+    video.disablePictureInPicture = true;
+    await promise_rejects(t, 'InvalidStateError', promise);
+    assert_equals(document.pictureInPictureElement, null);
+  });
+}, 'Request Picture-in-Picture rejects if disablePictureInPicture becomes ' +
+   'true before promise resolves.');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(() => {
+    video.disablePictureInPicture = true;
+    assert_equals(document.pictureInPictureElement, null);
+  });
+}, 'pictureInPictureElement is unset if disablePictureInPicture becomes true');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(() => {
+    video.disablePictureInPicture = false;
+    assert_equals(document.pictureInPictureElement, video);
+  });
+}, 'pictureInPictureElement is unchanged if disablePictureInPicture becomes false');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(() => {
+    document.createElement('video').disablePictureInPicture = true;
+    assert_equals(document.pictureInPictureElement, video);
+  });
+}, 'pictureInPictureElement is unchanged if disablePictureInPicture becomes ' +
+   'true for another video');
+</script>
diff --git a/picture-in-picture/enter-picture-in-picture.html b/picture-in-picture/enter-picture-in-picture.html
new file mode 100644
index 0000000..fc109e4
--- /dev/null
+++ b/picture-in-picture/enter-picture-in-picture.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Test enterpictureinpicture event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+  const video = await loadVideo();
+
+  video.addEventListener('enterpictureinpicture', t.step_func_done(event => {
+    assert_equals(event.target, video);
+    assert_equals(event.bubbles, true);
+    assert_equals(event.cancelable, false);
+    assert_equals(event.composed, false);
+    assert_equals(document.pictureInPictureElement, video);
+  }));
+
+  return requestPictureInPictureWithTrustedClick(video);
+});
+</script>
diff --git a/picture-in-picture/exit-picture-in-picture.html b/picture-in-picture/exit-picture-in-picture.html
new file mode 100644
index 0000000..9a5eedd
--- /dev/null
+++ b/picture-in-picture/exit-picture-in-picture.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Test exit Picture-in-Picture</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+  const video = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(() => document.exitPictureInPicture());
+}, 'Exit Picture-in-Picture resolves when there is a Picture-in-Picture video');
+
+promise_test(t => {
+  return promise_rejects(t, 'InvalidStateError',
+      document.exitPictureInPicture());
+}, 'Exit Picture-in-Picture rejects when there is no Picture-in-Picture video');
+</script>
diff --git a/picture-in-picture/leave-picture-in-picture.html b/picture-in-picture/leave-picture-in-picture.html
new file mode 100644
index 0000000..5a5182b
--- /dev/null
+++ b/picture-in-picture/leave-picture-in-picture.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Test leavepictureinpicture event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+  const video = await loadVideo();
+
+  video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
+    assert_equals(event.target, video);
+    assert_equals(event.bubbles, true);
+    assert_equals(event.cancelable, false);
+    assert_equals(event.composed, false);
+    assert_equals(document.pictureInPictureElement, null);
+  }));
+
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(() => document.exitPictureInPicture());
+}, 'leavepictureinpicture event is fired if document.exitPictureInPicture');
+
+promise_test(async t => {
+  const video = await loadVideo();
+
+  video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
+    assert_equals(event.target, video);
+    assert_equals(event.bubbles, true);
+    assert_equals(event.cancelable, false);
+    assert_equals(event.composed, false);
+    assert_equals(document.pictureInPictureElement, null);
+  }));
+
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(() => {
+    video.disablePictureInPicture = true;
+  });
+}, 'leavepictureinpicture event is fired if video.disablePictureInPicture is set to true');
+</script>
diff --git a/picture-in-picture/picture-in-picture-element.html b/picture-in-picture/picture-in-picture-element.html
new file mode 100644
index 0000000..809b3ee
--- /dev/null
+++ b/picture-in-picture/picture-in-picture-element.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Test Picture-in-Picture element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+  assert_equals(document.pictureInPictureElement, null);
+  const video = await loadVideo();
+
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(() => {
+    assert_equals(document.pictureInPictureElement, video);
+    return document.exitPictureInPicture();
+  })
+  .then(() => {
+    assert_equals(document.pictureInPictureElement, null);
+  });
+});
+</script>
diff --git a/picture-in-picture/picture-in-picture-window.html b/picture-in-picture/picture-in-picture-window.html
new file mode 100644
index 0000000..82d6457
--- /dev/null
+++ b/picture-in-picture/picture-in-picture-window.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Test Picture-in-Picture window</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+  const video = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(pipWindow => {
+    assert_not_equals(pipWindow.width, 0);
+    assert_not_equals(pipWindow.height, 0);
+  });
+}, 'Picture-in-Picture window dimensions are set after entering Picture-in-Picture');
+
+promise_test(async t => {
+  const video1 = await loadVideo();
+  const video2 = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video1)
+  .then(pipWindow1 => {
+    return requestPictureInPictureWithTrustedClick(video2)
+    .then(pipWindow2 => {
+      assert_equals(pipWindow1.width, 0);
+      assert_equals(pipWindow1.height, 0);
+      assert_not_equals(pipWindow2.width, 0);
+      assert_not_equals(pipWindow2.height, 0);
+    });
+  });
+}, 'Picture-in-Picture window dimensions are set to 0 after entering ' +
+   'Picture-in-Picture for another video');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(pipWindow => {
+    return document.exitPictureInPicture()
+    .then(() => {
+      assert_equals(pipWindow.width, 0);
+      assert_equals(pipWindow.height, 0);
+    });
+  });
+}, 'Picture-in-Picture window dimensions are set to 0 after exiting Picture-in-Picture');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  let thePipWindow;
+
+  video.addEventListener('leavepictureinpicture', t.step_func_done(event => {
+    assert_equals(thePipWindow.width, 0);
+    assert_equals(thePipWindow.height, 0);
+  }));
+
+  return requestPictureInPictureWithTrustedClick(video)
+  .then(pipWindow => {
+    thePipWindow = pipWindow;
+    video.disablePictureInPicture = true;
+  });
+}, 'Picture-in-Picture window dimensions are set to 0 if ' +
+   'disablePictureInPicture becomes true');
+</script>
diff --git a/picture-in-picture/request-picture-in-picture.html b/picture-in-picture/request-picture-in-picture.html
new file mode 100644
index 0000000..d2a5941
--- /dev/null
+++ b/picture-in-picture/request-picture-in-picture.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>Test request Picture-in-Picture</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<body></body>
+<script>
+promise_test(async t => {
+  const video = await loadVideo();
+  return requestPictureInPictureWithTrustedClick(video);
+}, 'request Picture-in-Picture resolves on user click');
+
+promise_test(t => {
+  const video = document.createElement('video');
+  return promise_rejects(t, 'InvalidStateError',
+      requestPictureInPictureWithTrustedClick(video));
+}, 'request Picture-in-Picture requires loaded metadata for the video element');
+
+promise_test(async t => {
+  const video = document.createElement('video');
+  await new Promise(resolve => {
+    video.src = '/media/sound_5.oga';
+    video.onloadeddata = resolve;
+  }).then(() => {
+    return promise_rejects(t, 'InvalidStateError',
+      requestPictureInPictureWithTrustedClick(video));
+  })
+}, 'request Picture-in-Picture requires video track for the video element');
+
+promise_test(async t => {
+  const video = await loadVideo();
+  return promise_rejects(t, 'NotAllowedError', video.requestPictureInPicture());
+}, 'request Picture-in-Picture requires a user gesture');
+
+promise_test(async t => {
+  const video1 = await loadVideo();
+  const video2 = await loadVideo();
+  return callWithTrustedClick(() => {
+    const first = video1.requestPictureInPicture();
+    const second = video2.requestPictureInPicture();
+    return Promise.all([
+      first,
+      promise_rejects(t, 'NotAllowedError', second)
+    ]);
+  });
+}, 'request Picture-in-Picture consumes user gesture');
+</script>
diff --git a/picture-in-picture/resources/picture-in-picture-helpers.js b/picture-in-picture/resources/picture-in-picture-helpers.js
new file mode 100644
index 0000000..4538c83
--- /dev/null
+++ b/picture-in-picture/resources/picture-in-picture-helpers.js
@@ -0,0 +1,31 @@
+function callWithTrustedClick(callback) {
+  return new Promise(resolve => {
+    let button = document.createElement('button');
+    button.textContent = 'click to continue test';
+    button.style.display = 'block';
+    button.style.fontSize = '20px';
+    button.style.padding = '10px';
+    button.onclick = () => {
+      document.body.removeChild(button);
+      resolve(callback());
+    };
+    document.body.appendChild(button);
+    test_driver.click(button);
+  });
+}
+
+function loadVideo() {
+  return new Promise(resolve => {
+    let video = document.createElement('video');
+    video.src = '/media/movie_5.ogv';
+    video.onloadedmetadata = () => {
+      resolve(video);
+    };
+  });
+}
+
+// Calls requestPictureInPicture() in a context that's 'allowed to request PiP'.
+function requestPictureInPictureWithTrustedClick(videoElement) {
+  return callWithTrustedClick(
+      () => videoElement.requestPictureInPicture());
+}
\ No newline at end of file
diff --git a/picture-in-picture/shadow-dom.html b/picture-in-picture/shadow-dom.html
new file mode 100644
index 0000000..e36acc0
--- /dev/null
+++ b/picture-in-picture/shadow-dom.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>Test for pictureInPictureElement adjustment for Shadow DOM</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/picture-in-picture-helpers.js"></script>
+<script src='../shadow-dom/resources/shadow-dom.js'></script>
+<body>
+<div id='host'>
+  <template data-mode='open' id='root'>
+    <slot></slot>
+  </template>
+  <div id='host2'>
+    <template data-mode='open' id='root2'>
+      <div id='host3'>
+        <template data-mode='open' id='root3'>
+          <video id='video'></video>
+          <div id='host4'>
+            <template data-mode='open' id='root4'>
+              <div></div>
+            </template>
+          </div>
+        </template>
+      </div>
+      <div id='host5'>
+        <template data-mode='open' id='root5'>
+          <div></div>
+        </template>
+      </div>
+    </template>
+  </div>
+</div>
+</body>
+<script>
+promise_test(async t => {
+  const ids = createTestTree(host);
+  document.body.appendChild(ids.host);
+
+  assert_equals(document.pictureInPictureElement, null);
+  assert_equals(ids.root.pictureInPictureElement, null);
+  assert_equals(ids.root2.pictureInPictureElement, null);
+  assert_equals(ids.root3.pictureInPictureElement, null);
+  assert_equals(ids.root4.pictureInPictureElement, null);
+  assert_equals(ids.root5.pictureInPictureElement, null);
+
+  await new Promise(resolve => {
+    ids.video.src = '/media/movie_5.ogv';
+    ids.video.onloadeddata = resolve;
+  })
+  .then(() => requestPictureInPictureWithTrustedClick(ids.video))
+  .then(() => {
+    assert_equals(document.pictureInPictureElement, ids.host2);
+    assert_equals(ids.root.pictureInPictureElement, null);
+    assert_equals(ids.root2.pictureInPictureElement, ids.host3);
+    assert_equals(ids.root3.pictureInPictureElement, ids.video);
+    assert_equals(ids.root4.pictureInPictureElement, null);
+    assert_equals(ids.root5.pictureInPictureElement, null);
+  })
+});
+</script>
\ No newline at end of file
diff --git a/pointerevents/extension/idlharness.html b/pointerevents/extension/idlharness.html
index 985712e..e2835f8 100644
--- a/pointerevents/extension/idlharness.html
+++ b/pointerevents/extension/idlharness.html
@@ -7,7 +7,7 @@
 <script src="/resources/idlharness.js"></script>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global=Window, Exposed=Window]
 interface Window {
 };
 
@@ -48,8 +48,20 @@
 
 </pre>
 <script>
-  var idl_array = new IdlArray();
-  idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
+promise_test(async function () {
+  const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+  const uievents = await fetch('/interfaces/uievents.idl').then(r => r.text());
+
+  const idl_array = new IdlArray();
+  idl_array.add_untested_idls(dom, { only: ['EventInit'] });
+  idl_array.add_untested_idls(uievents, { only: [
+    'UIEventInit',
+    'MouseEventInit',
+    'EventModifierInit']
+  });
+  idl_array.add_untested_idls(
+      document.getElementById("untested_idl").textContent);
   idl_array.add_idls(document.getElementById("idl").textContent);
   idl_array.test();
+}, 'pointerevents extension interfaces');
 </script>
diff --git a/pointerevents/extension/pointerevent_touch-action-pan-down-css_touch-manual.html b/pointerevents/extension/pointerevent_touch-action-pan-down-css_touch-manual.html
new file mode 100644
index 0000000..592139f
--- /dev/null
+++ b/pointerevents/extension/pointerevent_touch-action-pan-down-css_touch-manual.html
@@ -0,0 +1,114 @@
+<!doctype html>
+<html>
+    <head>
+        <title>touch-action: pan-down</title>
+        <meta name="assert" content="TA15.4 - With `touch-action: pan-down` on a swiped or click/dragged element, only panning in the y-axis down direction should be possible.">
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script src="../pointerevent_support.js"></script>
+        <style>
+            #target0 {
+            width: 700px;
+            height: 430px;
+            touch-action: pan-down;
+            }
+        </style>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events touch-action attribute support</h1>
+        <h4 id="desc">Test Description: Try to scroll element UP (drag down), then RIGHT (drag left), then DOWN (drag up). Tap Complete button under the rectangle when done. Expected: only pans in down direction.</h4>
+        <p>Note: this test is for touch-devices only</p>
+        <div id="target0">
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+        </div>
+        <input type="button" id="btnComplete" value="Complete test">
+        <script type='text/javascript'>
+            var detected_pointertypes = {};
+            var test_touchaction = async_test("touch-action attribute test");
+            add_completion_callback(showPointerTypes);
+
+            function run() {
+                var target0 = document.getElementById("target0");
+                var btnComplete = document.getElementById("btnComplete");
+                target0.scrollTop = 200;
+
+                var scrollListenerExecuted = false;
+                target0.addEventListener("scroll", function(event) {
+                    scrollListenerExecuted = true;
+                    assert_greater_than_equal(target0.scrollTop, 200);
+                });
+
+                // Check if "touch-action: pan-down" attribute works properly
+                //TA: 15.4
+                on_event(btnComplete, 'click', function(event) {
+                    detected_pointertypes[event.pointerType] = true;
+                    test_touchaction.step(function() {
+                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
+                        assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
+                        assert_greater_than(target0.scrollTop, 200, "scroll y offset should be greater than 200 in the end of the test");
+                    });
+                    test_touchaction.done();
+                    updateDescriptionComplete();
+                });
+            }
+        </script>
+        <h1>touch-action: pan-down</h1>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
diff --git a/pointerevents/extension/pointerevent_touch-action-pan-left-css_touch-manual.html b/pointerevents/extension/pointerevent_touch-action-pan-left-css_touch-manual.html
new file mode 100644
index 0000000..7030d5e
--- /dev/null
+++ b/pointerevents/extension/pointerevent_touch-action-pan-left-css_touch-manual.html
@@ -0,0 +1,114 @@
+<!doctype html>
+<html>
+    <head>
+        <title>touch-action: pan-left</title>
+        <meta name="assert" content="TA15.3 - With `touch-action: pan-left` on a swiped or click/dragged element, only panning on the x-axis left direction should be possible.">
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script src="../pointerevent_support.js"></script>
+        <style>
+            #target0 {
+            width: 700px;
+            height: 430px;
+            touch-action: pan-left;
+            }
+        </style>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events touch-action attribute support</h1>
+        <h4 id="desc">Test Description: Try to scroll element DOWN (drag up), then RIGHT (drag left), then LEFT (drag right). Tap Complete button under the rectangle when done. Expected: only pans in left direction.</h4>
+        <p>Note: this test is for touch-devices only</p>
+        <div id="target0">
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+        </div>
+        <input type="button" id="btnComplete" value="Complete test">
+        <script type='text/javascript'>
+            var detected_pointertypes = {};
+            var test_touchaction = async_test("touch-action attribute test");
+            add_completion_callback(showPointerTypes);
+
+            function run() {
+                var target0 = document.getElementById("target0");
+                var btnComplete = document.getElementById("btnComplete");
+                target0.scrollLeft = 200;
+
+                var scrollListenerExecuted = false;
+                target0.addEventListener("scroll", function(event) {
+                    scrollListenerExecuted = true;
+                    assert_less_than_equal(target0.scrollLeft, 200);
+                });
+
+                // Check if "touch-action: pan-left" attribute works properly
+                //TA: 15.3
+                on_event(btnComplete, 'click', function(event) {
+                    detected_pointertypes[event.pointerType] = true;
+                    test_touchaction.step(function() {
+                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
+                        assert_less_than(target0.scrollLeft, 200, "scroll x offset should be less than 200 in the end of the test");
+                        assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
+                    });
+                    test_touchaction.done();
+                    updateDescriptionComplete();
+                });
+            }
+        </script>
+        <h1>touch-action: pan-left</h1>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
diff --git a/pointerevents/extension/pointerevent_touch-action-pan-right-css_touch-manual.html b/pointerevents/extension/pointerevent_touch-action-pan-right-css_touch-manual.html
new file mode 100644
index 0000000..e711236
--- /dev/null
+++ b/pointerevents/extension/pointerevent_touch-action-pan-right-css_touch-manual.html
@@ -0,0 +1,114 @@
+<!doctype html>
+<html>
+    <head>
+        <title>touch-action: pan-right</title>
+        <meta name="assert" content="TA15.3 - With `touch-action: pan-right` on a swiped or click/dragged element, only panning on the x-axis right direction should be possible.">
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script src="../pointerevent_support.js"></script>
+        <style>
+            #target0 {
+            width: 700px;
+            height: 430px;
+            touch-action: pan-right;
+            }
+        </style>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events touch-action attribute support</h1>
+        <h4 id="desc">Test Description: Try to scroll element DOWN (drag up), then LEFT (drag right), then RIGHT (drag left). Tap Complete button under the rectangle when done. Expected: only pans in right direction.</h4>
+        <p>Note: this test is for touch-devices only</p>
+        <div id="target0">
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+        </div>
+        <input type="button" id="btnComplete" value="Complete test">
+        <script type='text/javascript'>
+            var detected_pointertypes = {};
+            var test_touchaction = async_test("touch-action attribute test");
+            add_completion_callback(showPointerTypes);
+
+            function run() {
+                var target0 = document.getElementById("target0");
+                var btnComplete = document.getElementById("btnComplete");
+                target0.scrollLeft = 200;
+
+                var scrollListenerExecuted = false;
+                target0.addEventListener("scroll", function(event) {
+                    scrollListenerExecuted = true;
+                    assert_greater_than_equal(target0.scrollLeft, 200);
+                });
+
+                // Check if "touch-action: pan-right" attribute works properly
+                //TA: 15.3
+                on_event(btnComplete, 'click', function(event) {
+                    detected_pointertypes[event.pointerType] = true;
+                    test_touchaction.step(function() {
+                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
+                        assert_greater_than(target0.scrollLeft, 200, "scroll x offset should be greater than 200 in the end of the test");
+                        assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
+                    });
+                    test_touchaction.done();
+                    updateDescriptionComplete();
+                });
+            }
+        </script>
+        <h1>touch-action: pan-right</h1>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
diff --git a/pointerevents/extension/pointerevent_touch-action-pan-up-css_touch-manual.html b/pointerevents/extension/pointerevent_touch-action-pan-up-css_touch-manual.html
new file mode 100644
index 0000000..46ddaa2
--- /dev/null
+++ b/pointerevents/extension/pointerevent_touch-action-pan-up-css_touch-manual.html
@@ -0,0 +1,114 @@
+<!doctype html>
+<html>
+    <head>
+        <title>touch-action: pan-up</title>
+        <meta name="assert" content="TA15.4 - With `touch-action: pan-up` on a swiped or click/dragged element, only panning in the y-axis up direction should be possible.">
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script src="../pointerevent_support.js"></script>
+        <style>
+            #target0 {
+            width: 700px;
+            height: 430px;
+            touch-action: pan-up;
+            }
+        </style>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events touch-action attribute support</h1>
+        <h4 id="desc">Test Description: Try to scroll element DOWN (drag up), then RIGHT (drag left), then UP (drag down). Tap Complete button under the rectangle when done. Expected: only pans in up direction.</h4>
+        <p>Note: this test is for touch-devices only</p>
+        <div id="target0">
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>
+                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
+                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
+                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
+                lobortis nisl ut aliquip ex ea commodo consequat.
+            </p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+            <p>Lorem ipsum dolor sit amet...</p>
+        </div>
+        <input type="button" id="btnComplete" value="Complete test">
+        <script type='text/javascript'>
+            var detected_pointertypes = {};
+            var test_touchaction = async_test("touch-action attribute test");
+            add_completion_callback(showPointerTypes);
+
+            function run() {
+                var target0 = document.getElementById("target0");
+                var btnComplete = document.getElementById("btnComplete");
+                target0.scrollTop = 200;
+
+                var scrollListenerExecuted = false;
+                target0.addEventListener("scroll", function(event) {
+                    scrollListenerExecuted = true;
+                    assert_less_than_equal(target0.scrollTop, 200);
+                });
+
+                // Check if "touch-action: pan-up" attribute works properly
+                //TA: 15.4
+                on_event(btnComplete, 'click', function(event) {
+                    detected_pointertypes[event.pointerType] = true;
+                    test_touchaction.step(function() {
+                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
+                        assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
+                        assert_less_than(target0.scrollTop, 200, "scroll y offset should be less than 200 in the end of the test");
+                    });
+                    test_touchaction.done();
+                    updateDescriptionComplete();
+                });
+            }
+        </script>
+        <h1>touch-action: pan-up</h1>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
diff --git a/pointerevents/extension/pointerevent_touch-action-verification.html b/pointerevents/extension/pointerevent_touch-action-verification.html
new file mode 100644
index 0000000..178f7a6
--- /dev/null
+++ b/pointerevents/extension/pointerevent_touch-action-verification.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<html>
+    <head>
+<!-- This is a fork of the main to support new additions to touch-action.
+     It should be integrated into web-platform-test when they are accepted into the
+     specification. -->
+        <title>touch-action: basic verification</title>
+        <meta name="assert" content="TA15.20 - The touch-action CSS property determines whether touch input MAY trigger default behavior supplied by the user agent.
+        auto: The user agent MAY determine any permitted touch behaviors, such as panning and zooming manipulations of the viewport, for touches that begin on the element.
+        none: Touches that begin on the element MUST NOT trigger default touch behaviors.
+        pan-x: The user agent MAY consider touches that begin on the element only for the purposes of horizontally scrolling the element's nearest ancestor with horizontally scrollable content.
+        pan-y: The user agent MAY consider touches that begin on the element only for the purposes of vertically scrolling the element's nearest ancestor with vertically scrollable content.
+        manipulation: The user agent MAY consider touches that begin on the element only for the purposes of scrolling and continuous zooming. Any additional behaviors supported by auto are out of scope for this specification.">
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <style>
+          /*
+          Give some rules below something to override in order to test
+          that they really are being parsed
+          */
+          .defnone {
+          touch-action: none;
+          }
+        </style>
+    </head>
+    <body onload="run()">
+        <h2>Pointer Events touch-action attribute support</h2>
+        <h4 id="desc">Test Description: Test will automatically check parsing behaviour of various touch-action combinations.</h4>
+        <script type='text/javascript'>
+            var detected_pointertypes = {};
+
+            setup({ explicit_done: true });
+
+            function run() {
+                var tests = document.querySelectorAll('.test');
+                //TA 15.20
+                for (var i = 0; i < tests.length; i++) {
+                    test(function() {
+                      var style = window.getComputedStyle(tests[i]);
+                      assert_equals(tests[i].attributes.expected.value, style.touchAction);
+                    }, tests[i].id);
+                }
+                done();
+            }
+        </script>
+        <h1>touch-action: basic verification</h1>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+        </div>
+        <div id="log"></div>
+        <div class="test" id="default" expected="auto"></div>
+        <div class="test defnone" id="stylesheet-none" expected="none"></div>
+        <div class="test defnone" id="explicit-auto" style="touch-action: auto;" expected="auto"></div>
+        <div class="test" id="explicit-pan-x" style="touch-action: pan-x;" expected="pan-x"></div>
+        <div class="test" id="explicit-pan-left" style="touch-action: pan-left;" expected="pan-left"></div>
+        <div class="test" id="explicit-pan-right" style="touch-action: pan-right;" expected="pan-right"></div>
+        <div class="test" id="explicit-pan-y" style="touch-action: pan-y;" expected="pan-y"></div>
+        <div class="test" id="explicit-pan-up" style="touch-action: pan-up;" expected="pan-up"></div>
+        <div class="test" id="explicit-pan-down" style="touch-action: pan-down;" expected="pan-down"></div>
+        <div class="test" id="explicit-pinch-zoom" style="touch-action: pinch-zoom;" expected="pinch-zoom"></div>
+        <div class="test" id="explicit-pan-x-pan-y" style="touch-action: pan-x pan-y;" expected="pan-x pan-y"></div>
+        <div class="test" id="explicit-pan-y-pan-x" style="touch-action: pan-y pan-x;" expected="pan-x pan-y"></div>
+        <div class="test" id="explicit-pan-left-pan-up" style="touch-action: pan-left pan-up;" expected="pan-left pan-up"></div>
+        <div class="test" id="explicit-pan-left-pan-down" style="touch-action: pan-left pan-down;" expected="pan-left pan-down"></div>
+        <div class="test" id="explicit-pan-right-pan-up" style="touch-action: pan-right pan-up;" expected="pan-right pan-up"></div>
+        <div class="test" id="explicit-pan-right-pan-down" style="touch-action: pan-right pan-down;" expected="pan-right pan-down"></div>
+        <div class="test" id="explicit-pan-up-pan-left" style="touch-action: pan-up pan-left;" expected="pan-left pan-up"></div>
+        <div class="test" id="explicit-pan-up-pan-right" style="touch-action: pan-up pan-right;" expected="pan-right pan-up"></div>
+        <div class="test" id="explicit-pan-down-pan-left" style="touch-action: pan-down pan-left;" expected="pan-left pan-down"></div>
+        <div class="test" id="explicit-pan-down-pan-right" style="touch-action: pan-down pan-right;" expected="pan-right pan-down"></div>
+        <div class="test" id="explicit-pinch-zoom-pan-x-pan-up" style="touch-action: pinch-zoom pan-x pan-up;" expected="pan-x pan-up pinch-zoom"></div>
+        <div class="test" id="explicit-pinch-zoom-pan-x-pan-y" style="touch-action: pinch-zoom pan-x pan-y;" expected="manipulation"></div>
+        <div class="test" id="explicit-manipulation" style="touch-action: manipulation;" expected="manipulation"></div>
+        <div class="test" id="explicit-none" style="touch-action: none;" expected="none"></div>
+        <div class="test" id="explicit-invalid-1" style="touch-action: bogus;" expected="auto"></div>
+        <div class="test defnone" id="explicit-invalid-2" style="touch-action: auto pan-x;" expected="none"></div>
+        <div class="test" id="explicit-invalid-3" style="touch-action: pan-y none;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-4" style="touch-action: pan-x pan-x;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-5" style="touch-action: manipulation pan-x;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-6" style="touch-action: pan-x pan-left;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-7" style="touch-action: auto pan-left;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-8" style="touch-action: none pan-left;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-9" style="touch-action: pan-x pan-right;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-10" style="touch-action: pan-y pan-up;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-11" style="touch-action: pan-y pan-down;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-12" style="touch-action: pan-left pan-right;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-13" style="touch-action: pan-up pan-down;" expected="auto"></div>
+        <div class="test" id="explicit-invalid-14" style="touch-action: pinch-zoom none;" expected="auto"></div>
+        <div style="touch-action: none;">
+          <div class="test" id="not-inherited" expected="auto"></div>
+          <div class="test" id="inherit" style="touch-action: inherit;" expected="none"></div>
+        </div>
+        <div class="test defnone" id="initial" style="touch-action: initial;" expected="auto"></div>
+    </body>
+</html>
diff --git a/pointerevents/idlharness.html b/pointerevents/idlharness.html
index a4ba4c3..6afcb21 100644
--- a/pointerevents/idlharness.html
+++ b/pointerevents/idlharness.html
@@ -7,7 +7,7 @@
 <script src="/resources/idlharness.js"></script>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global=Window, Exposed=Window]
 interface Window {
 };
 
@@ -91,14 +91,26 @@
 };
 </pre>
 <script>
-  var idl_array = new IdlArray();
-  idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
-  idl_array.add_idls(document.getElementById("idl").textContent);
+  promise_test(async function() {
+    const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+    const uievents = await fetch('/interfaces/uievents.idl').then(r => r.text());
 
-  // Note that I don't bother including Document here because there are still
-  // a bunch of differences between browsers around Document vs HTMLDocument.
-  idl_array.add_objects({
+    const idl_array = new IdlArray();
+    idl_array.add_untested_idls(dom, { only: ['EventInit'] });
+    idl_array.add_untested_idls(uievents, { only: [
+      'UIEventInit',
+      'MouseEventInit',
+      'EventModifierInit']
+    });
+    idl_array.add_untested_idls(
+        document.getElementById("untested_idl").textContent);
+    idl_array.add_idls(document.getElementById("idl").textContent);
+
+    // Note that I don't bother including Document here because there are still
+    // a bunch of differences between browsers around Document vs HTMLDocument.
+    idl_array.add_objects({
     Window: ["window"],
     Navigator: ["navigator"]});
-  idl_array.test();
+    idl_array.test();
+  }, 'pointerevents interfaces');
 </script>
diff --git a/pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers-manual.html b/pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers-manual.html
new file mode 100644
index 0000000..62a0a6d
--- /dev/null
+++ b/pointerevents/pointerevent_boundary_events_at_implicit_release_hoverable_pointers-manual.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Pointer Event: Boundary event sequence at implicit capture release</title>
+    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
+    <link rel="author" title="Google" href="http://www.google.com "/>
+    <meta name="assert" content="When a captured pointer is implicitly released after a click, the boundary events should follow the lostpointercapture event."/>
+    <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script type="text/javascript" src="pointerevent_support.js"></script>
+    <script type="text/javascript">
+      var detected_pointertypes = {};
+      var event_log = [];
+      var start_logging = false;
+
+      function resetTestState() {
+        detected_eventTypes = {};
+        event_log = [];
+        start_logging = false;
+      }
+
+      function run() {
+        var test_pointer_event = setup_pointerevent_test("Event sequence at implicit release on click", ["mouse"]);
+
+        var target = document.getElementById("target");
+        var capture_target = document.getElementById("capture-target");
+
+        All_Pointer_Events.forEach(function(eventName) {
+          on_event(target, eventName, function (event) {
+            detected_pointertypes[event.pointerType] = true;
+
+            if (event.type == "pointerdown") {
+              capture_target.setPointerCapture(event.pointerId);
+              start_logging = true;
+            } else if (start_logging) {
+              event_log.push(event.type + '@' + event.target.id);
+            }
+          });
+
+          on_event(capture_target, eventName, function (event) {
+            detected_pointertypes[event.pointerType] = true;
+            event_log.push(event.type + '@' + event.target.id);
+            if (event.type == 'lostpointercapture') {
+              setTimeout(function() {
+                test_pointer_event.step(function () {
+                  var expected_events = "pointerup, lostpointercapture, pointerout, pointerleave";
+                  assert_equals(event_log.join(", "), "pointerout@target, pointerleave@target, pointerover@capture-target, pointerenter@capture-target, gotpointercapture@capture-target, pointerup@capture-target, lostpointercapture@capture-target, pointerout@capture-target, pointerleave@capture-target, pointerover@target, pointerenter@target");
+                });
+                test_pointer_event.done();
+              }, 200);
+            }
+          });
+        });
+      }
+    </script>
+    <style>
+      #target {
+        margin: 20px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body onload="run()">
+    <h1>Pointer Event: Boundary event sequence at implicit capture release</h1>
+    <h2 id="pointerTypeDescription"></h2>
+    <h4>
+      When a captured pointer is implicitly released after a click, the boundary events should follow the lostpointercapture event.
+    </h4>
+    <ol>
+      <li>Click on the black box with mouse and do not move the mouse after or during the click.</li>
+    </ol>
+    <div id="capture-target"></div>
+    <div id="target"></div>
+    <div id="complete-notice">
+      <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+      <p>The following events were logged: <span id="event-log"></span>.</p>
+    </div>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/pointerevents/pointerevent_fractional_coordinates-manual.html b/pointerevents/pointerevent_fractional_coordinates-manual.html
new file mode 100644
index 0000000..e0e2fdc
--- /dev/null
+++ b/pointerevents/pointerevent_fractional_coordinates-manual.html
@@ -0,0 +1,98 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Pointer Events coordinates can have fractional value<</title>
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script type="text/javascript" src="pointerevent_support.js"></script>
+        <style>
+            #innerFrame {
+                transform: scale(5);
+                width: 60px;
+                height: 60px;
+                margin-left: 120px;
+                margin-top: 120px;
+                border: 0.01px solid black;
+            }
+        </style>
+        <script>
+            var eventList = [
+                  "pointerdown",
+                  "pointerup",
+                  "pointermove",
+                  "pointerover",
+                  "pointerout",
+                  "pointerenter",
+                  "pointerleave"];
+            var eventsRecieved = {};
+            var clickedTargetList = [];
+
+            function resetTestState() {
+                eventsRecieved = {};
+                clickedTargetList = [];
+                ['s1', 's2', 's3'].forEach(function(id){
+                    var target = document.getElementById('innerFrame').contentDocument.getElementById(id);
+                    target.style.background = "black"
+                });
+            }
+
+            function checkPointerEventCoordinates(event) {
+              if (event.clientX != Math.floor(event.clientX) || event.clientY != Math.floor(event.clientY))
+                eventsRecieved[event.type] = true;
+            }
+
+            function run() {
+                var test_pointerEvent = setup_pointerevent_test("pointerevent events in capturing", ALL_POINTERS);
+                var innerFrame = document.getElementById('innerFrame');
+                var innerDocument = innerFrame.contentDocument;
+                ['s1', 's2', 's3'].forEach(function(id){
+                    var target = innerDocument.getElementById(id);
+                    eventList.forEach(function(eventName) {
+                        on_event(target, eventName, checkPointerEventCoordinates);
+                    });
+
+                    on_event(target, "click", function (event) {
+                      if (!(event.target.id in clickedTargetList)) {
+                          clickedTargetList.push(event.target.id);
+                          event.target.style.background = "red"
+                      }
+                      if (clickedTargetList.length == 3) {
+                          test(function () {
+                              if (Object.keys(eventsRecieved).length != eventList.length){
+                                  eventList.forEach(function(eventName){
+                                     assert_true(eventName in eventsRecieved, eventName + " should have fractional coordinates");
+                                  });
+                              }
+                          }, expectedPointerType);
+                          test_pointerEvent.done();
+                      }
+                    });
+                });
+            }
+        </script>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events coordinates support fractional value</h1>
+        <h2 id="pointerTypeDescription"></h2>
+        <h4>
+            Test Description: This test checks pointer events has fractional client coordinates
+            <ol>
+                 <li>Move your pointer over one black square</li>
+                 <li>Press down the pointer (i.e. press left button with mouse or touch the screen with finger or pen).</li>
+                 <li>Release the pointer.</li>
+                 <li>Move to next black square and repreat 2 and 3</li>
+            </ol>
+
+            Test passes if pointer events has fractional coordinates.
+        </h4>
+        <iframe id=innerFrame src="resources/pointerevent_fractional_coordinates-iframe.html"></iframe>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+            <p>Refresh the page to run the tests again with a different pointer type.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
+
diff --git a/pointerevents/pointerevent_support.js b/pointerevents/pointerevent_support.js
index 64f1348..bf68f85 100644
--- a/pointerevents/pointerevent_support.js
+++ b/pointerevents/pointerevent_support.js
@@ -39,7 +39,8 @@
         "long": function (v) { return typeof v === "number" && Math.round(v) === v; },
         "float": function (v) { return typeof v === "number"; },
         "string": function (v) { return typeof v === "string"; },
-        "boolean": function (v) { return typeof v === "boolean" }
+        "boolean": function (v) { return typeof v === "boolean" },
+        "object": function (v) { return typeof v === "object" }
     };
     [
         ["readonly", "long", "pointerId"],
@@ -50,7 +51,9 @@
         ["readonly", "long", "tiltY"],
         ["readonly", "string", "pointerType"],
         ["readonly", "boolean", "isPrimary"],
-        ["readonly", "long", "detail", 0]
+        ["readonly", "long", "detail", 0],
+        ["readonly", "object", "fromElement", null],
+        ["readonly", "object", "toElement", null]
     ].forEach(function (attr) {
         var readonly = attr[0];
         var type = attr[1];
@@ -75,7 +78,7 @@
         }, pointerTestName + "." + name + " IDL type " + type + " (JS type was " + typeof event[name] + ")");
 
         // value check if defined
-        if (value != undefined) {
+        if (value !== undefined) {
             test(function () {
                 assert_equals(event[name], value, name + " attribute value");
             }, pointerTestName + "." + name + " value is " + value + ".");
diff --git a/pointerevents/pointerevent_touch-action-pan-down-css_touch-manual.html b/pointerevents/pointerevent_touch-action-pan-down-css_touch-manual.html
deleted file mode 100644
index 16e1cb2..0000000
--- a/pointerevents/pointerevent_touch-action-pan-down-css_touch-manual.html
+++ /dev/null
@@ -1,114 +0,0 @@
-<!doctype html>
-<html>
-    <head>
-        <title>touch-action: pan-down</title>
-        <meta name="assert" content="TA15.4 - With `touch-action: pan-down` on a swiped or click/dragged element, only panning in the y-axis down direction should be possible.">
-        <meta name="viewport" content="width=device-width">
-        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
-        <script src="/resources/testharness.js"></script>
-        <script src="/resources/testharnessreport.js"></script>
-        <script src="pointerevent_support.js"></script>
-        <style>
-            #target0 {
-            width: 700px;
-            height: 430px;
-            touch-action: pan-down;
-            }
-        </style>
-    </head>
-    <body onload="run()">
-        <h1>Pointer Events touch-action attribute support</h1>
-        <h4 id="desc">Test Description: Try to scroll element UP (drag down), then RIGHT (drag left), then DOWN (drag up). Tap Complete button under the rectangle when done. Expected: only pans in down direction.</h4>
-        <p>Note: this test is for touch-devices only</p>
-        <div id="target0">
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-        </div>
-        <input type="button" id="btnComplete" value="Complete test">
-        <script type='text/javascript'>
-            var detected_pointertypes = {};
-            var test_touchaction = async_test("touch-action attribute test");
-            add_completion_callback(showPointerTypes);
-
-            function run() {
-                var target0 = document.getElementById("target0");
-                var btnComplete = document.getElementById("btnComplete");
-                target0.scrollTop = 200;
-
-                var scrollListenerExecuted = false;
-                target0.addEventListener("scroll", function(event) {
-                    scrollListenerExecuted = true;
-                    assert_greater_than_equal(target0.scrollTop, 200);
-                });
-
-                // Check if "touch-action: pan-down" attribute works properly
-                //TA: 15.4
-                on_event(btnComplete, 'click', function(event) {
-                    detected_pointertypes[event.pointerType] = true;
-                    test_touchaction.step(function() {
-                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
-                        assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
-                        assert_greater_than(target0.scrollTop, 200, "scroll y offset should be greater than 200 in the end of the test");
-                    });
-                    test_touchaction.done();
-                    updateDescriptionComplete();
-                });
-            }
-        </script>
-        <h1>touch-action: pan-down</h1>
-        <div id="complete-notice">
-            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
-        </div>
-        <div id="log"></div>
-    </body>
-</html>
diff --git a/pointerevents/pointerevent_touch-action-pan-left-css_touch-manual.html b/pointerevents/pointerevent_touch-action-pan-left-css_touch-manual.html
deleted file mode 100644
index 53fd2de..0000000
--- a/pointerevents/pointerevent_touch-action-pan-left-css_touch-manual.html
+++ /dev/null
@@ -1,114 +0,0 @@
-<!doctype html>
-<html>
-    <head>
-        <title>touch-action: pan-left</title>
-        <meta name="assert" content="TA15.3 - With `touch-action: pan-left` on a swiped or click/dragged element, only panning on the x-axis left direction should be possible.">
-        <meta name="viewport" content="width=device-width">
-        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
-        <script src="/resources/testharness.js"></script>
-        <script src="/resources/testharnessreport.js"></script>
-        <script src="pointerevent_support.js"></script>
-        <style>
-            #target0 {
-            width: 700px;
-            height: 430px;
-            touch-action: pan-left;
-            }
-        </style>
-    </head>
-    <body onload="run()">
-        <h1>Pointer Events touch-action attribute support</h1>
-        <h4 id="desc">Test Description: Try to scroll element DOWN (drag up), then RIGHT (drag left), then LEFT (drag right). Tap Complete button under the rectangle when done. Expected: only pans in left direction.</h4>
-        <p>Note: this test is for touch-devices only</p>
-        <div id="target0">
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-        </div>
-        <input type="button" id="btnComplete" value="Complete test">
-        <script type='text/javascript'>
-            var detected_pointertypes = {};
-            var test_touchaction = async_test("touch-action attribute test");
-            add_completion_callback(showPointerTypes);
-
-            function run() {
-                var target0 = document.getElementById("target0");
-                var btnComplete = document.getElementById("btnComplete");
-                target0.scrollLeft = 200;
-
-                var scrollListenerExecuted = false;
-                target0.addEventListener("scroll", function(event) {
-                    scrollListenerExecuted = true;
-                    assert_less_than_equal(target0.scrollLeft, 200);
-                });
-
-                // Check if "touch-action: pan-left" attribute works properly
-                //TA: 15.3
-                on_event(btnComplete, 'click', function(event) {
-                    detected_pointertypes[event.pointerType] = true;
-                    test_touchaction.step(function() {
-                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
-                        assert_less_than(target0.scrollLeft, 200, "scroll x offset should be less than 200 in the end of the test");
-                        assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
-                    });
-                    test_touchaction.done();
-                    updateDescriptionComplete();
-                });
-            }
-        </script>
-        <h1>touch-action: pan-left</h1>
-        <div id="complete-notice">
-            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
-        </div>
-        <div id="log"></div>
-    </body>
-</html>
diff --git a/pointerevents/pointerevent_touch-action-pan-right-css_touch-manual.html b/pointerevents/pointerevent_touch-action-pan-right-css_touch-manual.html
deleted file mode 100644
index 53bbac6..0000000
--- a/pointerevents/pointerevent_touch-action-pan-right-css_touch-manual.html
+++ /dev/null
@@ -1,114 +0,0 @@
-<!doctype html>
-<html>
-    <head>
-        <title>touch-action: pan-right</title>
-        <meta name="assert" content="TA15.3 - With `touch-action: pan-right` on a swiped or click/dragged element, only panning on the x-axis right direction should be possible.">
-        <meta name="viewport" content="width=device-width">
-        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
-        <script src="/resources/testharness.js"></script>
-        <script src="/resources/testharnessreport.js"></script>
-        <script src="pointerevent_support.js"></script>
-        <style>
-            #target0 {
-            width: 700px;
-            height: 430px;
-            touch-action: pan-right;
-            }
-        </style>
-    </head>
-    <body onload="run()">
-        <h1>Pointer Events touch-action attribute support</h1>
-        <h4 id="desc">Test Description: Try to scroll element DOWN (drag up), then LEFT (drag right), then RIGHT (drag left). Tap Complete button under the rectangle when done. Expected: only pans in right direction.</h4>
-        <p>Note: this test is for touch-devices only</p>
-        <div id="target0">
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-        </div>
-        <input type="button" id="btnComplete" value="Complete test">
-        <script type='text/javascript'>
-            var detected_pointertypes = {};
-            var test_touchaction = async_test("touch-action attribute test");
-            add_completion_callback(showPointerTypes);
-
-            function run() {
-                var target0 = document.getElementById("target0");
-                var btnComplete = document.getElementById("btnComplete");
-                target0.scrollLeft = 200;
-
-                var scrollListenerExecuted = false;
-                target0.addEventListener("scroll", function(event) {
-                    scrollListenerExecuted = true;
-                    assert_greater_than_equal(target0.scrollLeft, 200);
-                });
-
-                // Check if "touch-action: pan-right" attribute works properly
-                //TA: 15.3
-                on_event(btnComplete, 'click', function(event) {
-                    detected_pointertypes[event.pointerType] = true;
-                    test_touchaction.step(function() {
-                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
-                        assert_greater_than(target0.scrollLeft, 200, "scroll x offset should be greater than 200 in the end of the test");
-                        assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
-                    });
-                    test_touchaction.done();
-                    updateDescriptionComplete();
-                });
-            }
-        </script>
-        <h1>touch-action: pan-right</h1>
-        <div id="complete-notice">
-            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
-        </div>
-        <div id="log"></div>
-    </body>
-</html>
diff --git a/pointerevents/pointerevent_touch-action-pan-up-css_touch-manual.html b/pointerevents/pointerevent_touch-action-pan-up-css_touch-manual.html
deleted file mode 100644
index 0902700..0000000
--- a/pointerevents/pointerevent_touch-action-pan-up-css_touch-manual.html
+++ /dev/null
@@ -1,114 +0,0 @@
-<!doctype html>
-<html>
-    <head>
-        <title>touch-action: pan-up</title>
-        <meta name="assert" content="TA15.4 - With `touch-action: pan-up` on a swiped or click/dragged element, only panning in the y-axis up direction should be possible.">
-        <meta name="viewport" content="width=device-width">
-        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
-        <script src="/resources/testharness.js"></script>
-        <script src="/resources/testharnessreport.js"></script>
-        <script src="pointerevent_support.js"></script>
-        <style>
-            #target0 {
-            width: 700px;
-            height: 430px;
-            touch-action: pan-up;
-            }
-        </style>
-    </head>
-    <body onload="run()">
-        <h1>Pointer Events touch-action attribute support</h1>
-        <h4 id="desc">Test Description: Try to scroll element DOWN (drag up), then RIGHT (drag left), then UP (drag down). Tap Complete button under the rectangle when done. Expected: only pans in up direction.</h4>
-        <p>Note: this test is for touch-devices only</p>
-        <div id="target0">
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>
-                Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diem
-                nonummy nibh euismod tincidunt ut lacreet dolore magna aliguam erat volutpat.
-                Ut wisis enim ad minim veniam, quis nostrud exerci tution ullamcorper suscipit
-                lobortis nisl ut aliquip ex ea commodo consequat.
-            </p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-            <p>Lorem ipsum dolor sit amet...</p>
-        </div>
-        <input type="button" id="btnComplete" value="Complete test">
-        <script type='text/javascript'>
-            var detected_pointertypes = {};
-            var test_touchaction = async_test("touch-action attribute test");
-            add_completion_callback(showPointerTypes);
-
-            function run() {
-                var target0 = document.getElementById("target0");
-                var btnComplete = document.getElementById("btnComplete");
-                target0.scrollTop = 200;
-
-                var scrollListenerExecuted = false;
-                target0.addEventListener("scroll", function(event) {
-                    scrollListenerExecuted = true;
-                    assert_less_than_equal(target0.scrollTop, 200);
-                });
-
-                // Check if "touch-action: pan-up" attribute works properly
-                //TA: 15.4
-                on_event(btnComplete, 'click', function(event) {
-                    detected_pointertypes[event.pointerType] = true;
-                    test_touchaction.step(function() {
-                        assert_true(scrollListenerExecuted, "scroll listener should have been executed by the end of the test");
-                        assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
-                        assert_less_than(target0.scrollTop, 200, "scroll y offset should be less than 200 in the end of the test");
-                    });
-                    test_touchaction.done();
-                    updateDescriptionComplete();
-                });
-            }
-        </script>
-        <h1>touch-action: pan-up</h1>
-        <div id="complete-notice">
-            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
-        </div>
-        <div id="log"></div>
-    </body>
-</html>
diff --git a/pointerevents/pointerevent_touch-action-verification.html b/pointerevents/pointerevent_touch-action-verification.html
index 7800f2c..f42d9f6 100644
--- a/pointerevents/pointerevent_touch-action-verification.html
+++ b/pointerevents/pointerevent_touch-action-verification.html
@@ -12,7 +12,6 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
-        <script src="pointerevent_support.js"></script>
         <style>
           /*
           Give some rules below something to override in order to test
@@ -52,21 +51,9 @@
         <div class="test defnone" id="stylesheet-none" expected="none"></div>
         <div class="test defnone" id="explicit-auto" style="touch-action: auto;" expected="auto"></div>
         <div class="test" id="explicit-pan-x" style="touch-action: pan-x;" expected="pan-x"></div>
-        <div class="test" id="explicit-pan-left" style="touch-action: pan-left;" expected="pan-left"></div>
-        <div class="test" id="explicit-pan-right" style="touch-action: pan-right;" expected="pan-right"></div>
         <div class="test" id="explicit-pan-y" style="touch-action: pan-y;" expected="pan-y"></div>
-        <div class="test" id="explicit-pan-up" style="touch-action: pan-up;" expected="pan-up"></div>
-        <div class="test" id="explicit-pan-down" style="touch-action: pan-down;" expected="pan-down"></div>
         <div class="test" id="explicit-pan-x-pan-y" style="touch-action: pan-x pan-y;" expected="pan-x pan-y"></div>
         <div class="test" id="explicit-pan-y-pan-x" style="touch-action: pan-y pan-x;" expected="pan-x pan-y"></div>
-        <div class="test" id="explicit-pan-left-pan-up" style="touch-action: pan-left pan-up;" expected="pan-left pan-up"></div>
-        <div class="test" id="explicit-pan-left-pan-down" style="touch-action: pan-left pan-down;" expected="pan-left pan-down"></div>
-        <div class="test" id="explicit-pan-right-pan-up" style="touch-action: pan-right pan-up;" expected="pan-right pan-up"></div>
-        <div class="test" id="explicit-pan-right-pan-down" style="touch-action: pan-right pan-down;" expected="pan-right pan-down"></div>
-        <div class="test" id="explicit-pan-up-pan-left" style="touch-action: pan-up pan-left;" expected="pan-left pan-up"></div>
-        <div class="test" id="explicit-pan-up-pan-right" style="touch-action: pan-up pan-right;" expected="pan-right pan-up"></div>
-        <div class="test" id="explicit-pan-down-pan-left" style="touch-action: pan-down pan-left;" expected="pan-left pan-down"></div>
-        <div class="test" id="explicit-pan-down-pan-right" style="touch-action: pan-down pan-right;" expected="pan-right pan-down"></div>
         <div class="test" id="explicit-manipulation" style="touch-action: manipulation;" expected="manipulation"></div>
         <div class="test" id="explicit-none" style="touch-action: none;" expected="none"></div>
         <div class="test" id="explicit-invalid-1" style="touch-action: bogus;" expected="auto"></div>
diff --git a/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual.html b/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual.html
new file mode 100644
index 0000000..ccb8c27
--- /dev/null
+++ b/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Pointer Events pointer lock tests</title>
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="/external/wpt/pointerevents/pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script type="text/javascript" src="../pointerevent_support.js"></script>
+        <style>
+          #testContainer {
+            touch-action: none;
+            user-select: none;
+            position: relative;
+          }
+        </style>
+        <script>
+            var lock_change_count = 0;
+            var mouseeventMovements = []
+            var pointereventMovements = []
+
+            function resetTestState() {
+            }
+
+            function run() {
+                var test_pointerEvent = setup_pointerevent_test("pointerevent movementX/Y when lock test", ['mouse']);
+                var div1 = document.getElementById("target");
+
+                on_event(div1, 'pointerdown', function(event) {
+                    if (lock_change_count == 0)
+                       div1.requestPointerLock();
+                });
+                on_event(div1, 'pointerup', function(event) {
+                    if (lock_change_count == 1)
+                        document.exitPointerLock();
+                });
+                on_event(div1, 'pointermove', function(event) {
+                    if (lock_change_count == 1) {
+                        pointereventMovements.push(`${event.movementX}, ${event.movementY}`);
+                    }
+                });
+                on_event(div1, 'mousemove', function(event) {
+                    if (lock_change_count == 1) {
+                        mouseeventMovements.push(`${event.movementX}, ${event.movementY}`);
+                    }
+                });
+                on_event(document, 'pointerlockchange', function(event) {
+                    lock_change_count++;
+                    if (lock_change_count == 1) {
+                        test_pointerEvent.step(function() {
+                            assert_equals(document.pointerLockElement, div1, "document.pointerLockElement should be div1.");
+                        });
+                    } else if (lock_change_count == 2) {
+                        test_pointerEvent.step(function() {
+                            assert_equals(document.pointerLockElement, null, "document.pointerLockElement should be null.");
+                            assert_not_equals(mouseeventMovements.length, 0);
+                            assert_array_equals(pointereventMovements, mouseeventMovements, "pointermove should have movementX/Y same as mousemove");
+                        });
+                        test_pointerEvent.done();
+                    }
+                });
+            }
+        </script>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events movement in locked state test</h1>
+        <h2 id="pointerTypeDescription"></h2>
+        <h4>
+            Test Description: This test checks if pointermove.movementX/Y matches mousemove.movementX/Y when pointer is locked.
+            <ol>
+                 <li>Press left button down on the green rectangle and hold it.</li>
+                 <li>Move the mouse inside the green rectangle.</li>
+            </ol>
+            </ol>
+
+            Test passes if the proper behavior of the events is observed.
+        </h4>
+        <div id="testContainer">
+            <div id="target" style="width:800px;height:250px;background:green"></div>
+        </div>
+        <div class="spacer"></div>
+    </body>
+</html>
diff --git a/pointerevents/pointerlock/pointerevent_pointerlock_after_pointercapture-manual.html b/pointerevents/pointerlock/pointerevent_pointerlock_after_pointercapture-manual.html
index 92fe7f2..8ac35f8 100644
--- a/pointerevents/pointerlock/pointerevent_pointerlock_after_pointercapture-manual.html
+++ b/pointerevents/pointerlock/pointerevent_pointerlock_after_pointercapture-manual.html
@@ -20,7 +20,7 @@
                 var test_pointerEvent = setup_pointerevent_test("no pointercapture while pointerlock", ['mouse']);
                 var div1 = document.getElementById("div1");
                 var div2 = document.getElementById("div2");
-                
+
                 on_event(div1, 'pointerdown', function(event) {
                     div2.setPointerCapture(event.pointerId);
                 });
@@ -33,8 +33,11 @@
                         test_pointerEvent.done(); 
                     }
                 });
+                on_event(document, 'contextmenu', function(event) {
+                    event.preventDefault();
+                });
                 on_event(div2, 'pointermove', function(event) {
-                    if (got_capture && !lock_requested) {
+                    if (event.button == 2 && got_capture && !lock_requested) {
                         div1.requestPointerLock();
                         lock_requested = true;
                     }
@@ -45,6 +48,9 @@
                 on_event(div2, 'lostpointercapture', function(event) {
                     lost_capture = true;
                 });
+                on_event(document,"pointerlockerror", function() {
+                    assert_unreached("Pointer lock error");
+                })
             }
         </script>
     </head>
@@ -56,6 +62,8 @@
             <ol>
                  <li>Press left button down on the green rectangle and hold it.</li>
                  <li>Move the mouse inside the green rectangle.</li>
+                 <li>Click right button while keeping left button down</li>
+                 <li>Keep moving the mouse inside the green rectangle.</li>
             </ol>
 
             Test passes if the pointer capture is released on the yellow rectangle when the green rectangle gets the pointer lock.
diff --git a/pointerevents/resources/pointerevent_fractional_coordinates-iframe.html b/pointerevents/resources/pointerevent_fractional_coordinates-iframe.html
new file mode 100644
index 0000000..5245a3f
--- /dev/null
+++ b/pointerevents/resources/pointerevent_fractional_coordinates-iframe.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+    <head>
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
+    </head>
+    <style>
+        .square {
+            width: 3px;
+            height:3px;
+            background: black;
+            cursor: pointer;
+        }
+        #s1 {
+            top: 10px;
+            left: 10px;
+        }
+        #s2 {
+            top: 30px;
+            left: 50px;
+        }
+        #s3 {
+            top: 50px;
+            left: 30px;
+        }
+    </style>
+    <body>
+        <div id="s1" class="square"></div>
+        <div id="s2" class="square"></div>
+        <div id="s3" class="square"></div>
+    </body>
+</html>
diff --git a/pointerlock/movementX_Y_basic-manual.html b/pointerlock/movementX_Y_basic-manual.html
index 1e8fc1f..083b707 100644
--- a/pointerlock/movementX_Y_basic-manual.html
+++ b/pointerlock/movementX_Y_basic-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     #status-log {
         margin: 10px 0;
@@ -30,14 +30,14 @@
 
     <div id="status-log">Waiting... Click to start loging.</div>
     <div class="data-log">
-	    <table>
-	    	<tr><td></td><td>X</td><td>Y</td></tr>
-	    	<tr><td>client_init:</td><td id="clientX_init-log">X</td><td id="clientY_init-log">Y</td></tr>
-	    	<tr><td>client_last:</td><td id="clientX_last-log">X</td><td id="clientY_last-log">Y</td></tr>
-	    	<tr><td>client_delta:</td><td id="clientX_delta-log">X</td><td id="clientY_delta-log">Y</td></tr>
-	    	<tr><td>movement_sum:</td><td id="movementX_sum-log">X</td><td id="movementY_sum-log">Y</td></tr>
-	    	<tr><td>movement:</td><td id="movementX-log">X</td><td id="movementY-log">Y</td></tr>
-	    </table>
+        <table>
+            <tr><td></td><td>X</td><td>Y</td></tr>
+            <tr><td>screen_init:</td><td id="screenX_init-log">X</td><td id="screenY_init-log">Y</td></tr>
+            <tr><td>screen_last:</td><td id="screenX_last-log">X</td><td id="screenY_last-log">Y</td></tr>
+            <tr><td>screen_delta:</td><td id="screenX_delta-log">X</td><td id="screenY_delta-log">Y</td></tr>
+            <tr><td>movement_sum:</td><td id="movementX_sum-log">X</td><td id="movementY_sum-log">Y</td></tr>
+            <tr><td>movement:</td><td id="movementX-log">X</td><td id="movementY-log">Y</td></tr>
+        </table>
     </div>
     <hr/>
 
@@ -49,16 +49,16 @@
             movementY_log = document.querySelector('#movementY-log'),
             movementX_sum_log = document.querySelector('#movementX_sum-log'),
             movementY_sum_log = document.querySelector('#movementY_sum-log'),
-            clientX_init_log = document.querySelector('#clientX_init-log'),
-            clientY_init_log = document.querySelector('#clientY_init-log'),
-            clientX_last_log = document.querySelector('#clientX_last-log'),
-            clientY_last_log = document.querySelector('#clientY_last-log');
-            clientX_delta_log = document.querySelector('#clientX_delta-log'),
-            clientY_delta_log = document.querySelector('#clientY_delta-log');
+            screenX_init_log = document.querySelector('#screenX_init-log'),
+            screenY_init_log = document.querySelector('#screenY_init-log'),
+            screenX_last_log = document.querySelector('#screenX_last-log'),
+            screenY_last_log = document.querySelector('#screenY_last-log');
+            screenX_delta_log = document.querySelector('#screenX_delta-log'),
+            screenY_delta_log = document.querySelector('#screenY_delta-log');
 
         var click_counter = 0;
 
-        var clientX_init, clientY_init, movementX, movementY, movementX_sum, movementY_sum, clientX_last, clientY_last;
+        var screenX_init, screenY_init, movementX, movementY, movementX_sum, movementY_sum, screenX_last, screenY_last;
 
         var movementX_Y_inside_window_Test = async_test("Test that movementX/Y = eNow.screenX/Y-ePrevious.screenX/Y.");
 
@@ -72,11 +72,9 @@
                 case 2:
                     status_log.innerHTML = "inside window: done";
 
-                    // approximately(+/- 10)
-                    // a little drift should be tollerated
                     movementX_Y_inside_window_Test.step(function() {
-                        assert_equals(movementX_sum, clientX_last - clientX_init, "sum of movementX = clientX_init - clientX_last");
-                        assert_equals(movementY_sum, clientY_last - clientY_init, "sum of movementY = clientY_init - clientY_last");
+                        assert_equals(movementX_sum, screenX_last - screenX_init, "sum of movementX = screenX_last - screenX_init");
+                        assert_equals(movementY_sum, screenY_last - screenY_init, "sum of movementY = screenY_last - screenY_init");
                     });
                     movementX_Y_inside_window_Test.done();
                 break;
@@ -88,32 +86,39 @@
             movementY = e.movementY;
 
             if(click_counter === 1) {
-                if(!clientX_init) {
-                    clientX_init = e.clientX;
-                    clientY_init = e.clientY;
-                    movementX_sum = movementX;
-                    movementY_sum = movementY;
+                if(!screenX_init) {
+                    screenX_init = screenX_last = e.screenX;
+                    screenY_init = screenY_last = e.screenY;
+                    movementX_sum = 0;
+                    movementY_sum = 0;
                 }
+                else {
+                    movementX_sum += movementX;
+                    movementY_sum += movementY;
 
-                movementX_sum += movementX;
-                movementY_sum += movementY;
+                    screenX_delta = e.screenX - screenX_last;
+                    screenY_delta = e.screenY - screenY_last;
 
-                clientX_last = e.clientX;
-                clientY_last = e.clientY;
-                clientX_delta = clientX_last - clientX_init;
-                clientY_delta = clientY_last - clientY_init;
+                    movementX_Y_inside_window_Test.step(function() {
+                        assert_equals(movementX, screenX_delta, "movementX = screen_delta");
+                        assert_equals(movementY, screenY_delta, "movementY = screen_delta");
+                    });
 
-                updateData();
+                    screenX_last = e.screenX;
+                    screenY_last = e.screenY;
+
+                    updateData();
+                }
             }
         });
 
         function updateData() {
-            clientX_init_log.innerHTML = clientX_init;
-            clientY_init_log.innerHTML = clientY_init;
-            clientX_last_log.innerHTML = clientX_last;
-            clientY_last_log.innerHTML = clientY_last;
-            clientX_delta_log.innerHTML = clientX_delta;
-            clientY_delta_log.innerHTML = clientY_delta;
+            screenX_init_log.innerHTML = screenX_init;
+            screenY_init_log.innerHTML = screenY_init;
+            screenX_last_log.innerHTML = screenX_last;
+            screenY_last_log.innerHTML = screenY_last;
+            screenX_delta_log.innerHTML = screenX_delta;
+            screenY_delta_log.innerHTML = screenY_delta;
             movementX_log.innerHTML = movementX;
             movementY_log.innerHTML = movementY;
             movementX_sum_log.innerHTML = movementX_sum;
diff --git a/pointerlock/movementX_Y_no-jumps-manual.html b/pointerlock/movementX_Y_no-jumps-manual.html
index 9c8fabd..11b8d2a 100644
--- a/pointerlock/movementX_Y_no-jumps-manual.html
+++ b/pointerlock/movementX_Y_no-jumps-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     #status-log {
         margin: 10px 0;
diff --git a/pointerlock/pointerlock_basic-manual.html b/pointerlock/pointerlock_basic-manual.html
index d926318..141b349 100644
--- a/pointerlock/pointerlock_basic-manual.html
+++ b/pointerlock/pointerlock_basic-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     button {
         color: blue;
diff --git a/pointerlock/pointerlock_fullscreen-manual.html b/pointerlock/pointerlock_fullscreen-manual.html
index 7ce91ad..19f4cbe 100644
--- a/pointerlock/pointerlock_fullscreen-manual.html
+++ b/pointerlock/pointerlock_fullscreen-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     button {
         color: blue;
diff --git a/pointerlock/pointerlock_indefinite-manual.html b/pointerlock/pointerlock_indefinite-manual.html
index 0db1227..96d5f94 100644
--- a/pointerlock/pointerlock_indefinite-manual.html
+++ b/pointerlock/pointerlock_indefinite-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     button {
         color: blue;
diff --git a/pointerlock/pointerlock_leave_Tab-manual.html b/pointerlock/pointerlock_leave_Tab-manual.html
index 198e2fe..f32c71a 100644
--- a/pointerlock/pointerlock_leave_Tab-manual.html
+++ b/pointerlock/pointerlock_leave_Tab-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     button {
         color: blue;
diff --git a/pointerlock/pointerlock_leave_UA-manual.html b/pointerlock/pointerlock_leave_UA-manual.html
index e144008..8373a34 100644
--- a/pointerlock/pointerlock_leave_UA-manual.html
+++ b/pointerlock/pointerlock_leave_UA-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     button {
         color: blue;
diff --git a/pointerlock/pointerlock_remove_target-manual.html b/pointerlock/pointerlock_remove_target-manual.html
index 2103650..a38b06e 100644
--- a/pointerlock/pointerlock_remove_target-manual.html
+++ b/pointerlock/pointerlock_remove_target-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <style type="text/css">
     button {
         color: blue;
diff --git a/preload/download-resources.html b/preload/download-resources.html
index ee8fdd0..dc2b469 100644
--- a/preload/download-resources.html
+++ b/preload/download-resources.html
@@ -8,7 +8,7 @@
 <link rel=preload href="resources/dummy.js" as=script>
 <link rel=preload href="resources/dummy.css" as=style>
 <link rel=preload href="resources/square.png" as=image>
-<link rel=preload href="resources/CanvasTest.ttf" as=font crossorigin>
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin>
 <link rel=preload href="resources/white.mp4" as=video>
 <link rel=preload href="resources/sound_5.oga" as=audio>
 <link rel=preload href="resources/foo.vtt" as=track>
@@ -22,7 +22,7 @@
         verifyPreloadAndRTSupport()
         verifyNumberOfDownloads("resources/dummy.js", 1);
         verifyNumberOfDownloads("resources/dummy.css", 1);
-        verifyNumberOfDownloads("resources/CanvasTest.ttf", 1);
+        verifyNumberOfDownloads("/fonts/CanvasTest.ttf", 1);
         verifyNumberOfDownloads("resources/white.mp4", 1);
         verifyNumberOfDownloads("resources/sound_5.oga", 1);
         verifyNumberOfDownloads("resources/foo.vtt", 1);
diff --git a/preload/fetch-destination.https.html b/preload/fetch-destination.https.html
deleted file mode 100644
index c7d5142..0000000
--- a/preload/fetch-destination.https.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/preload/resources/preload_helper.js"></script>
-<script src="../service-workers/service-worker/resources/test-helpers.sub.js"></script>
-<script>
-async_test(function(t) {
-    var worker_url = 'resources/fetch-destination-worker.js';
-    var scope = 'resources/empty.html';
-    var registration;
-    verifyPreloadAndRTSupport();
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(t.step_func(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        }))
-      .then(t.step_func(function() {
-          return with_iframe(scope);
-        }))
-      .then(t.step_func(function(frame) {
-          var link = frame.contentWindow.document.createElement("link");
-          link.as = "fetch";
-          link.href = "resources/dummy.xml";
-          link.rel = "preload";
-          link.addEventListener("load", t.step_func_done(function() { registration.unregister(); }));
-          link.addEventListener("error", t.step_func_done(function() {
-              registration.unregister();
-              assert_unreached("Fetch destination preload errored");
-          }));
-          frame.contentWindow.document.body.appendChild(link);
-        }))
-      .catch(unreached_rejection(t));
-}, 'Fetch destination preload');
-</script>
diff --git a/preload/link-header-on-subresource.html b/preload/link-header-on-subresource.html
index 22351e3..a02bc7c 100644
--- a/preload/link-header-on-subresource.html
+++ b/preload/link-header-on-subresource.html
@@ -10,7 +10,7 @@
 <script>
     window.addEventListener("load", t.step_func(function() {
         verifyPreloadAndRTSupport();
-        verifyNumberOfDownloads("/preload/resources/CanvasTest.ttf?link-header-on-subresource", 1);
+        verifyNumberOfDownloads("/fonts/CanvasTest.ttf?link-header-on-subresource", 1);
         t.done();
     }));
 </script>
diff --git a/preload/link-header-preload-srcset.tentative.html b/preload/link-header-preload-srcset.tentative.html
new file mode 100644
index 0000000..0a364b2
--- /dev/null
+++ b/preload/link-header-preload-srcset.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel="help" href="https://github.com/w3c/preload/issues/120">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/preload/resources/preload_helper.js"></script>
+<script>
+    var t = async_test('Makes sure that Link headers preload images with (experimental) srcset/imgsizes attributes.');
+</script>
+<body>
+<script src="resources/dummy.js?pipe=trickle(d3)&link-header-preload-srcset"></script>
+<script>
+    window.addEventListener("load", t.step_func(function() {
+        verifyPreloadAndRTSupport();
+        verifyNumberOfDownloads('resources/square.png?1x', 1);
+        verifyNumberOfDownloads('resources/square.png?2x', 0);
+        verifyNumberOfDownloads('resources/square.png?3x', 0);
+        verifyNumberOfDownloads('resources/square.png?base', 0);
+        verifyNumberOfDownloads('resources/square.png?200', 0);
+        verifyNumberOfDownloads('resources/square.png?400', 1);
+        verifyNumberOfDownloads('resources/square.png?800', 0);
+        verifyNumberOfDownloads('resources/square.png?150', 0);
+        verifyNumberOfDownloads('resources/square.png?300', 1);
+        verifyNumberOfDownloads('resources/square.png?600', 0);
+        t.done();
+    }));
+</script>
+</body>
diff --git a/preload/link-header-preload-srcset.tentative.html.headers b/preload/link-header-preload-srcset.tentative.html.headers
new file mode 100644
index 0000000..b29f725
--- /dev/null
+++ b/preload/link-header-preload-srcset.tentative.html.headers
@@ -0,0 +1,3 @@
+Link: <resources/square.png?1x>; rel=preload; as=image; srcset="resources/square.png?2x 2x, resources/square.png?3x 3x"
+Link: <resources/square.png?base>; rel=preload; as=image; srcset="resources/square.png?200 200w, resources/square.png?400 400w, resources/square.png?800 800w"; imgsizes=400px
+Link: <resources/square.png?base>; rel=preload; as=image; srcset="resources/square.png?150 150w, resources/square.png?300 300w, resources/square.png?600 600w"; imgsizes="(min-width: 300px) 300px, 150px"
diff --git a/preload/modulepreload.html b/preload/modulepreload.html
index 6a17050..addd67d 100644
--- a/preload/modulepreload.html
+++ b/preload/modulepreload.html
@@ -53,8 +53,8 @@
     link.rel = 'modulepreload';
     link.href = 'resources/module1.js';
     return attachAndWaitForLoad(link).then(() => {
-        // Currently, modulepreload doesn't perform submodules fetch.
         verifyNumberOfDownloads('resources/module1.js', 1);
+        // The load event fires before (optional) submodules fetch.
         verifyNumberOfDownloads('resources/module2.js', 0);
 
         var script = document.createElement('script');
@@ -71,7 +71,7 @@
     var link = document.createElement('link');
     link.rel = 'modulepreload';
     link.href = 'resources/syntax-error.js';
-    return attachAndWaitForError(link);
+    return attachAndWaitForLoad(link);
 }, 'link rel=modulepreload for a module with syntax error');
 
 promise_test(function(t) {
diff --git a/preload/onload-event.html b/preload/onload-event.html
index 35ba2eb..6af2d64 100644
--- a/preload/onload-event.html
+++ b/preload/onload-event.html
@@ -19,7 +19,7 @@
 <link rel=preload href="resources/dummy.js" as=script onload="scriptLoaded = true;">
 <link rel=preload href="resources/dummy.css" as=style onload="styleLoaded = true;">
 <link rel=preload href="resources/square.png" as=image onload="imageLoaded = true;">
-<link rel=preload href="resources/CanvasTest.ttf" as=font crossorigin onload="fontLoaded = true;">
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin onload="fontLoaded = true;">
 <link rel=preload href="resources/white.mp4" as=video onload="videoLoaded = true;">
 <link rel=preload href="resources/sound_5.oga" as=audio onload="audioLoaded = true;">
 <link rel=preload href="resources/foo.vtt" as=track onload="trackLoaded = true;">
diff --git a/preload/preload-csp.sub.html b/preload/preload-csp.sub.html
index 70db5f4..8e5e45b 100644
--- a/preload/preload-csp.sub.html
+++ b/preload/preload-csp.sub.html
@@ -9,7 +9,7 @@
 <link rel=preload href="{{host}}:{{ports[http][1]}}/preload/resources/dummy.js" as=style>
 <link rel=preload href="resources/dummy.css" as=style>
 <link rel=preload href="resources/square.png" as=image>
-<link rel=preload href="resources/CanvasTest.ttf" as=font crossorigin>
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin>
 <link rel=preload href="resources/white.mp4" as=video>
 <link rel=preload href="resources/sound_5.oga" as=audio>
 <link rel=preload href="resources/foo.vtt" as=track>
@@ -23,7 +23,7 @@
         verifyNumberOfDownloads("{{host}}:{{ports[http][1]}}/preload/resources/dummy.js", 0);
         verifyNumberOfDownloads("resources/dummy.css", 0);
         verifyNumberOfDownloads("resources/square.png", 0);
-        verifyNumberOfDownloads("resources/CanvasTest.ttf", 0);
+        verifyNumberOfDownloads("/fonts/CanvasTest.ttf", 0);
         verifyNumberOfDownloads("resources/white.mp4", 0);
         verifyNumberOfDownloads("resources/sound_5.oga", 0);
         verifyNumberOfDownloads("resources/foo.vtt", 0);
diff --git a/preload/preload-default-csp.sub.html b/preload/preload-default-csp.sub.html
index bde6b98..cb080e6 100644
--- a/preload/preload-default-csp.sub.html
+++ b/preload/preload-default-csp.sub.html
@@ -9,7 +9,7 @@
 <link rel=preload href="{{host}}:{{ports[http][1]}}/preload/resources/dummy.js" as=style>
 <link rel=preload href="resources/dummy.css" as=style>
 <link rel=preload href="resources/square.png" as=image>
-<link rel=preload href="resources/CanvasTest.ttf" as=font crossorigin>
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin>
 <link rel=preload href="resources/white.mp4" as=video>
 <link rel=preload href="resources/sound_5.oga" as=audio>
 <link rel=preload href="resources/foo.vtt" as=track>
@@ -23,7 +23,7 @@
         verifyNumberOfDownloads("{{host}}:{{ports[http][1]}}/preload/resources/dummy.js", 0);
         verifyNumberOfDownloads("resources/dummy.css", 0);
         verifyNumberOfDownloads("resources/square.png", 0);
-        verifyNumberOfDownloads("resources/CanvasTest.ttf", 0);
+        verifyNumberOfDownloads("/fonts/CanvasTest.ttf", 0);
         verifyNumberOfDownloads("resources/white.mp4", 0);
         verifyNumberOfDownloads("resources/sound_5.oga", 0);
         verifyNumberOfDownloads("resources/foo.vtt", 0);
diff --git a/preload/preload-with-type.html b/preload/preload-with-type.html
index cf79e77..8578143 100644
--- a/preload/preload-with-type.html
+++ b/preload/preload-with-type.html
@@ -31,7 +31,7 @@
 <link rel=preload href="resources/dummy.js" as=script type="text/javascript" onload="scriptLoaded = true;">
 <link rel=preload href="resources/dummy.css" as=style type="text/css" onload="styleLoaded = true;">
 <link rel=preload href="resources/square.png" as=image type="image/png" onload="imageLoaded = true;">
-<link rel=preload href="resources/CanvasTest.ttf" as=font type="font/ttf" crossorigin onload="fontLoaded = true;">
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font type="font/ttf" crossorigin onload="fontLoaded = true;">
 <script>
     document.write('<link rel=preload href="' + videoURL + '" as=video type="video/' + videoFormat + '" onload="videoLoaded = true;">');
     document.write('<link rel=preload href="' + audioURL + '" as=audio type="audio/' + audioFormat + '" onload="audioLoaded = true;">');
@@ -40,7 +40,7 @@
 <link rel=preload href="resources/dummy.js" as=script type="application/foobar" onload="gibberishLoaded++;">
 <link rel=preload href="resources/dummy.css" as=style type="text/foobar" onload="gibberishLoaded++;">
 <link rel=preload href="resources/square.png" as=image type="image/foobar" onload="gibberishLoaded++;">
-<link rel=preload href="resources/CanvasTest.ttf" as=font type="font/foobar" crossorigin onload="gibberishLoaded++;">
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font type="font/foobar" crossorigin onload="gibberishLoaded++;">
 <script>
     document.write('<link rel=preload href="' + videoURL + '" as=video type="video/foobar" onload="gibberishLoaded++;">');
     document.write('<link rel=preload href="' + audioURL + '" as=audio type="audio/foobar" onload="gibberishLoaded++;">');
diff --git a/preload/resources/dummy-preloads-subresource.css.sub.headers b/preload/resources/dummy-preloads-subresource.css.sub.headers
index 4ada08c..f6b4b49 100644
--- a/preload/resources/dummy-preloads-subresource.css.sub.headers
+++ b/preload/resources/dummy-preloads-subresource.css.sub.headers
@@ -1,2 +1,2 @@
 Cache-Control: max-age=1000
-Link: </preload/resources/CanvasTest.ttf?link-header-on-subresource>; rel=preload;as=font;crossorigin
+Link: </fonts/CanvasTest.ttf?link-header-on-subresource>; rel=preload;as=font;crossorigin
diff --git a/preload/resources/fetch-destination-worker.js b/preload/resources/fetch-destination-worker.js
deleted file mode 100644
index 57b259e..0000000
--- a/preload/resources/fetch-destination-worker.js
+++ /dev/null
@@ -1,9 +0,0 @@
-self.addEventListener('fetch', function(event) {
-    if (event.request.url.indexOf('dummy.xml') != -1) {
-        if (!event.request.destination || event.request.destination == "")
-            event.respondWith(new Response());
-        else
-            event.respondWith(Response.error());
-    }
-});
-
diff --git a/preload/single-download-preload.html b/preload/single-download-preload.html
index 83151c2..e8f2617 100644
--- a/preload/single-download-preload.html
+++ b/preload/single-download-preload.html
@@ -9,7 +9,7 @@
 <link rel=preload href="resources/dummy.css?single-download-preload" as=style>
 <link rel=preload href="resources/square.png?single-download-preload" as=image>
 <link rel=preload href="resources/square.png?background&single-download-preload" as=image>
-<link rel=preload href="resources/CanvasTest.ttf?single-download-preload" as=font crossorigin>
+<link rel=preload href="/fonts/CanvasTest.ttf?single-download-preload" as=font crossorigin>
 <link rel=preload href="resources/white.mp4?single-download-preload" as=video>
 <link rel=preload href="resources/sound_5.oga?single-download-preload" as=audio>
 <link rel=preload href="resources/foo.vtt?single-download-preload" as=track>
@@ -25,7 +25,7 @@
     }
     @font-face {
       font-family:ahem;
-      src: url(resources/CanvasTest.ttf?single-download-preload);
+      src: url(/fonts/CanvasTest.ttf?single-download-preload);
     }
     span { font-family: ahem, Arial; }
 </style>
@@ -49,7 +49,7 @@
             verifyNumberOfDownloads("resources/dummy.css?single-download-preload", 1);
             verifyNumberOfDownloads("resources/square.png?single-download-preload", 1);
             verifyNumberOfDownloads("resources/square.png?background&single-download-preload", 1);
-            verifyNumberOfDownloads("resources/CanvasTest.ttf?single-download-preload", 1);
+            verifyNumberOfDownloads("/fonts/CanvasTest.ttf?single-download-preload", 1);
             verifyNumberOfDownloads("resources/dummy.xml?foobar", 0);
             verifyNumberOfDownloads("resources/foo.vtt?single-download-preload", 1);
             verifyNumberOfDownloads("resources/dummy.xml?single-download-preload", 1);
diff --git a/presentation-api/controlling-ua/PresentationRequest_error.https.html b/presentation-api/controlling-ua/PresentationRequest_error.https.html
index d6324ea..c5ab466 100644
--- a/presentation-api/controlling-ua/PresentationRequest_error.https.html
+++ b/presentation-api/controlling-ua/PresentationRequest_error.https.html
@@ -1,33 +1,36 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Presentation API PresentationRequest for Controlling User Agent (Error)</title>
+<title>Constructing a PresentationRequest (Error)</title>
 <link rel="author" title="Franck William Taffo" href="http://www.fokus.fraunhofer.de">
-<link rel="help" href="http://w3c.github.io/presentation-api/#dfn-controlling-user-agent">
+<link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs">
+<link rel="help" href="http://w3c.github.io/presentation-api/#constructing-a-presentationrequest">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
 
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new PresentationRequest();
-        });
+  test(() => {
+    assert_throws(new TypeError(), () => {
+      new PresentationRequest();
     }, 'Call PresentationRequest() constructor without presentation URL. TypeError Exception expected.');
 
-    test(function() {
-        assert_throws('NotSupportedError', function() {
-            new PresentationRequest([]);
-        });
+    assert_throws('NotSupportedError', () => {
+      new PresentationRequest([]);
     }, 'Call PresentationRequest constructor with an empty sequence. NotSupportedError Exception expected.');
 
-    test(function() {
-        assert_throws('SyntaxError', function() {
-            new PresentationRequest('http://@');
-        });
+    assert_throws('SyntaxError', () => {
+      new PresentationRequest('http://@');
     }, 'Call PresentationRequest constructor with an invalid URL. SyntaxError Exception expected.');
 
-    test(function() {
-        assert_throws('SyntaxError', function() {
-            new PresentationRequest(['presentation.html', 'http://@']);
-        });
+    assert_throws('NotSupportedError', () => {
+        new PresentationRequest('unsupported://example.com');
+    }, 'Call PresentationRequest constructor with an unsupported URL. NotSupportedError expected.');
+
+    assert_throws('SyntaxError', function() {
+      new PresentationRequest(['presentation.html', 'http://@']);
     }, 'Call PresentationRequest constructor with a sequence of URLs, one of them invalid. SyntaxError Exception expected.');
+
+    assert_throws('NotSupportedError', function() {
+      new PresentationRequest(['unsupported://example.com', 'invalid://example.com']);
+    }, 'Call PresentationRequest constructor only with a sequence of unsupported URLs. NotSupportedError Exception expected.');
+  });
 </script>
diff --git a/presentation-api/controlling-ua/PresentationRequest_success.https.html b/presentation-api/controlling-ua/PresentationRequest_success.https.html
index 70ea5a8..890e0ed 100644
--- a/presentation-api/controlling-ua/PresentationRequest_success.https.html
+++ b/presentation-api/controlling-ua/PresentationRequest_success.https.html
@@ -8,17 +8,24 @@
 <script src="/resources/testharnessreport.js"></script>
 
 <script>
-    test(() => {
-        let request = new PresentationRequest('presentation.html');
-        assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with a relative presentation URL is constructed successfully.');
+  test(() => {
+    let request = new PresentationRequest('presentation.html');
+    assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with a relative presentation URL is constructed successfully.');
 
-        request = new PresentationRequest('https://example.org/');
-        assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with an absolute presentation URL is constructed successfully.');
+    request = new PresentationRequest('https://example.org/');
+    assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with an absolute presentation URL is constructed successfully.');
 
-        request = new PresentationRequest([
-            'presentation.html',
-            'https://example.org/presentation/'
-        ]);
-        assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with an array of presentation URLs is constructed successfully.');
-    });
+    request = new PresentationRequest([
+      'presentation.html',
+      'https://example.org/presentation/'
+    ]);
+    assert_true(request instanceof PresentationRequest, 'An instance of PresentationRequest with an array of presentation URLs is constructed successfully.');
+
+    request = new PresentationRequest([
+      'unsupported://example.com',
+      'presentation.html',
+      'https://example.org/presentation/'
+    ]);
+    assert_true(request instanceof PresentationRequest, 'An unsupported URL in an array of presentation URLs is ignored successfully.');
+  });
 </script>
\ No newline at end of file
diff --git a/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html b/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html
index 84971cb..9b75a85 100644
--- a/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html
+++ b/presentation-api/receiving-ua/PresentationConnectionList_onconnectionavailable-manual.https.html
@@ -3,7 +3,6 @@
 <title>Monitoring incoming presentation connections</title>
 <link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs">
 <link rel="help" href="https://w3c.github.io/presentation-api/#monitoring-incoming-presentation-connections">
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="common.js"></script>
 <script src="support/stash.js"></script>
 <style>
@@ -75,4 +74,4 @@
         connection.terminate();
     });
   };
-</script>
\ No newline at end of file
+</script>
diff --git a/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html b/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html
index d6baad1..50585f7 100644
--- a/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html
+++ b/presentation-api/receiving-ua/PresentationConnection_onclose-manual.https.html
@@ -3,7 +3,6 @@
 <title>Closing a PresentationConnection</title>
 <link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs">
 <link rel="help" href="https://w3c.github.io/presentation-api/#closing-a-presentationconnection">
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="common.js"></script>
 <script src="support/stash.js"></script>
 <style>
@@ -82,4 +81,4 @@
         connection.terminate();
     });
   };
-</script>
\ No newline at end of file
+</script>
diff --git a/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html b/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html
index 4b05cdc..53451a6 100644
--- a/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html
+++ b/presentation-api/receiving-ua/PresentationConnection_onmessage-manual.https.html
@@ -3,7 +3,6 @@
 <title>Receiving a message through PresentationConnection</title>
 <link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs/">
 <link rel="help" href="http://w3c.github.io/presentation-api/#receiving-a-message-through-presentationconnection">
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="common.js"></script>
 <script src="support/stash.js"></script>
 
diff --git a/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html b/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html
index 67b8c43..96fc6f3 100644
--- a/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html
+++ b/presentation-api/receiving-ua/PresentationReceiver_create-manual.https.html
@@ -25,8 +25,14 @@
         }
     });
 
-    let connection, db;
+    let connection;
     const presentBtn = document.getElementById('presentBtn');
+
+    const dbName = {
+        controller: 'db-presentation-api-controlling-ua',
+        receiver: 'db-presentation-api-receiving-ua'
+    };
+
     const main = () => {
         promise_test(t => {
             presentBtn.disabled = true;
@@ -38,14 +44,14 @@
                 notice.parentNode.removeChild(notice);
                 stash.stop();
 
-                //history.back();
+                // history.back();
                 document.cookie = 'PresentationApiTest=true; Expires=' + new Date().toUTCString();
                 sessionStorage.removeItem('presentation_api_test');
                 localStorage.removeItem('presentation_api_test');
 
-                if (db)
-                    db.close();
-                indexedDB.deleteDatabase(dbName);
+                Object.values(dbName).forEach(name => {
+                    indexedDB.deleteDatabase(name);
+                });
 
                 if (connection) {
                     connection.onconnect = () => { connection.terminate(); };
@@ -75,28 +81,12 @@
             sessionStorage.setItem(storageName, storageValue);
             localStorage.setItem(storageName, storageValue);
 
-            const dbName = 'db-presentation-api';
-            const storeName = 'store-controlling-ua';
-            const storeData = {
-                id: 'controller',
-                data: 'controlling user agent'
-            };
             const openIndexedDB = () => {
                 if ('indexedDB' in window) {
-                    const dbReq = indexedDB.open(dbName, 1);
-                    return new Promise((resolve, reject) => {
-                        dbReq.onupgradeneeded = () => {
-                            db = dbReq.result;
-                            const store = db.createObjectStore(storeName, { keyPath: 'id' });
-                            store.add(storeData);
-                        };
-                        dbReq.onsuccess = () => {
-                            db = dbReq.result;
-                            db.close();
-                            db = null;
-                            resolve();
-                        };
-                        dbReq.onerror = reject;
+                    const req = indexedDB.open(dbName.controller, 1);
+                    const eventWatcher = new EventWatcher(t, req, 'upgradeneeded');
+                    return eventWatcher.wait_for('upgradeneeded').then(evt => {
+                        evt.target.result.close();
                     });
                 }
                 else
@@ -115,6 +105,8 @@
                             };
                             reg.active.postMessage('', [channel.port2]);
                         }
+                        else
+                            resolve([]);
                     });
                 });
             };
@@ -153,29 +145,19 @@
             // Indexed Database
             const checkIndexedDB = t => {
                 if ('indexedDB' in window) {
-                    const message = 'Indexed Database is not shared with a receiving user agent.';
-                    let req = indexedDB.open(dbName, 1), store;
-                    let eventWatcher = new EventWatcher(t, req, 'success');
-                    return eventWatcher.wait_for('success').then(() => {
-                        db = req.result;
-                        const transaction = db.transaction(storeName, 'readwrite');
-                        store = transaction.objectStore(storeName);
-                        req = store.openCursor();
-                        eventWatcher = new EventWatcher(t, req, 'success');
-                        return eventWatcher.wait_for('success');
-                    }).then(() => {
-                        assert_true(req.result instanceof IDBCursorWithValue, message);
-                        const cursor = req.result;
-                        const item = cursor.value;
-                        assert_equals(item.id, storeData.id, message);
-                        assert_equals(item.data, storeData.data, message);
-                        cursor.continue();
-                        return eventWatcher.wait_for('success');
-                    }).then(() => {
-                        assert_equals(req.result, null, message);
-                        db.close();
-                        db = null;
-                    });
+                    const req = indexedDB.open(dbName.receiver);
+                    const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded');
+                    const successWatcher = new EventWatcher(t, req, 'success');
+                    return Promise.race([
+                        upgradeneededWatcher.wait_for('upgradeneeded').then(evt => {
+                            evt.target.result.close();
+                        }),
+                        successWatcher.wait_for('success').then(evt => {
+                            evt.target.result.close();
+                            // This would fail if the database created by the receiving UA is visible to the controlling UA
+                            assert_unreached('Indexed Database is not shared with a receiving user agent.');
+                        })
+                    ]);
                 }
                 else
                     return Promise.resolve();
@@ -231,6 +213,7 @@
             return request.start().then(c => {
                 connection = c;
                 enableTimeout();
+
                 // This Promise.race will be rejected if a receiving side terminates/closes the connection when window.close() is invoked
                 return Promise.race([
                     openIndexedDB()
@@ -249,7 +232,7 @@
                 // Check accessibility to window clients before terminating a presentation
                 return getClientUrls().then(urls => {
                     assert_true(urls.length === clientUrls.length && urls.every((value, index) => { return clientUrls[index] === value}),
-                        'A window client in a receiving user agent is not accessible to a service worker on a controlling user agent.')
+                        'A window client in a receiving user agent is not accessible to a service worker on a controlling user agent.');
                     const eventWatcher = new EventWatcher(t, connection, 'terminate');
                     connection.terminate();
                     return eventWatcher.wait_for('terminate');
diff --git a/presentation-api/receiving-ua/idlharness-manual.https.html b/presentation-api/receiving-ua/idlharness-manual.https.html
index 079bb09..c7bd734 100644
--- a/presentation-api/receiving-ua/idlharness-manual.https.html
+++ b/presentation-api/receiving-ua/idlharness-manual.https.html
@@ -3,7 +3,6 @@
 <title>Presentation API IDL tests for Receiving User Agent</title>
 <link rel="author" title="Tomoyuki Shimizu" href="https://github.com/tomoyukilabs">
 <link rel="help" href="http://w3c.github.io/presentation-api/#dfn-receiving-user-agent">
-<link rel="stylesheet" href="/resources/testharness.css">
 <script src="common.js"></script>
 <script src="support/stash.js"></script>
 
diff --git a/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html b/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html
index f1afb68..74d8dfa 100644
--- a/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html
+++ b/presentation-api/receiving-ua/support/PresentationConnectionList_onconnectionavailable_receiving-ua.html
@@ -31,7 +31,7 @@
         return receiver.connectionList.then(list => {
           connections = list.connections;
           if (action === 'close') {
-            assert_true(connections.length === number - 1 && connections.includes(connection),
+            assert_true(connections.length === number - 1 && !connections.includes(connection),
               'A closed presentation connection is removed from the set of presentation controllers.');
           } else if (action === 'connect') {
             assert_true(connections.length === number + 1 && connections.includes(connection),
diff --git a/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html b/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html
index 8edb88e..53f7601 100644
--- a/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html
+++ b/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua.html
@@ -37,6 +37,11 @@
     const notice = document.getElementById('notice');
     const modal = document.getElementById('modal');
 
+    const dbName = {
+        controller: 'db-presentation-api-controlling-ua',
+        receiver: 'db-presentation-api-receiving-ua'
+    };
+
     promise_test(t => {
         t.add_cleanup(() => {
             document.cookie = cookieName + '=False;Expires=' + new Date().toUTCString();
@@ -46,9 +51,10 @@
             sessionStorage.removeItem(storageNameChild);
             localStorage.removeItem(storageNameChild);
 
-            if (db)
-                db.close();
-            indexedDB.deleteDatabase(dbName);
+            Object.values(dbName).forEach(name => {
+                indexedDB.deleteDatabase(name);
+            });
+
             if ('serviceWorker' in navigator) {
                 navigator.serviceWorker.getRegistrations().then(registrations => {
                     return Promise.all(registrations.map(reg => reg.unregister()));
@@ -97,37 +103,27 @@
         assert_equals(document.cookie, '', 'A cookie store is set to an empty store.')
 
         // Indexed Database
-        const dbName = 'db-presentation-api';
-        const storeName = 'store-controlling-ua';
-        const storeData = {
-            parent: { id: 'parent', data: 'receiving user agent' },
-            child: { id: 'child', data: 'nested browsing context' }
-        };
-        let db;
         const checkIndexedDB = () => {
             if ('indexedDB' in window) {
-                let req = indexedDB.open(dbName, 1), store;
-                let eventWatcher = new EventWatcher(t, req, ['upgradeneeded', 'success']);
-                return eventWatcher.wait_for('upgradeneeded').then(() => {
-                    db = req.result;
-                    const store = db.createObjectStore(storeName, { keyPath: 'id' });
-                    return eventWatcher.wait_for('success');
-                }).then(() => {
-                    db = req.result;
-                    const transaction = db.transaction(storeName, 'readwrite');
-                    store = transaction.objectStore(storeName);
-                    req = store.openCursor();
-                    eventWatcher = new EventWatcher(t, req, 'success');
-                    return eventWatcher.wait_for('success');
-                }).then(() => {
-                    assert_equals(req.result, null, 'Indexed Database is set to an empty storage.');
-                    req = store.put(storeData.parent);
-                    eventWatcher = new EventWatcher(t, req, 'success');
-                    return eventWatcher.wait_for('success');
-                }).then(() => {
-                    db.close();
-                    db = null;
-                });
+                // The test would fail when the database is already created by the controlling UA
+                const req = indexedDB.open(dbName.controller);
+                const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded');
+                const successWatcher = new EventWatcher(t, req, 'success');
+                return Promise.race([
+                    upgradeneededWatcher.wait_for('upgradeneeded').then(evt => {
+                        evt.target.result.close();
+                        const req = indexedDB.open(dbName.receiver, 2);
+                        const eventWatcher = new EventWatcher(t, req, 'upgradeneeded');
+                        return eventWatcher.wait_for('upgradeneeded');
+                    }).then(evt => {
+                        evt.target.result.close();
+                    }),
+                    successWatcher.wait_for('success').then(evt => {
+                        evt.target.result.close();
+                        // This would fail if the database created by the controlling UA is visible to the receiving UA
+                        assert_unreached('Indexed Database is set to an empty storage.');
+                    })
+                ]);
             }
             else
                 return Promise.resolve();
@@ -253,33 +249,24 @@
         // Indexed Database
         const checkUpdatedIndexedDB = () => {
             if ('indexedDB' in window) {
-                let req = indexedDB.open(dbName, 1), store;
-                let eventWatcher = new EventWatcher(t, req, 'success');
                 message = 'Indexed Database is shared by top-level and nested browsing contexts.';
-                const checkItem = event => {
-                    assert_true(event.target.result instanceof IDBCursorWithValue, message);
-                    const cursor = event.target.result;
-                    const item = cursor.value;
-                    assert_equals(storeData[item.id].data, item.data, message);
-                    delete storeData[item.id]
-                    cursor.continue();
-                    return eventWatcher.wait_for('success');
-                };
-                return eventWatcher.wait_for('success').then(() => {
-                    db = req.result;
-                    const transaction = db.transaction(storeName, 'readwrite');
-                    store = transaction.objectStore(storeName);
-                    req = store.openCursor();
-                    eventWatcher = new EventWatcher(t, req, 'success');
-                    return eventWatcher.wait_for('success');
-                }).then(checkItem)
-                .then(checkItem)
-                .then(event => {
-                    assert_equals(event.target.result, null, message);
-                    assert_equals(Object.keys(storeData).length, 0, message);
-                    db.close();
-                    db = null;
-                });
+                const req = indexedDB.open(dbName.receiver);
+                const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded');
+                const successWatcher = new EventWatcher(t, req, 'success');
+                return Promise.race([
+                    upgradeneededWatcher.wait_for('upgradeneeded').then(evt => {
+                        evt.target.result.close();
+                        // Check if the version of the database is upgraded to 3 by the nested browsing context
+                        assert_unreached(message);
+                    }),
+                    successWatcher.wait_for('success').then(evt => {
+                        const db = evt.target.result;
+                        const version = db.version;
+                        db.close();
+                        // Check if the version of the database is upgraded to 3 by the nested browsing context
+                        assert_equals(version, 3, message);
+                    })
+                ]);
             }
             else
                 return Promise.resolve();
diff --git a/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html b/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html
index aa0e191..2f67f7f 100644
--- a/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html
+++ b/presentation-api/receiving-ua/support/PresentationReceiver_create_receiving-ua_child.html
@@ -44,7 +44,7 @@
         modal.textContent = 'confirm()';
         confirm(message);
         modal.textContent = 'print()';
-        //print();
+        print();
         modal.textContent = 'prompt()';
         prompt(message);
         notice.style.display = 'none';
@@ -60,36 +60,36 @@
         assert_equals(document.cookie, 'PresentationApiTest=Receiving-UA', 'A cookie store is shared by top-level and nested browsing contexts.');
 
         // Indexed Database
-        let db;
+        const dbName = 'db-presentation-api-receiving-ua';
         const checkIndexedDB = () => {
             if ('indexedDB' in window) {
                 message = 'Indexed Database is shared by top-level and nested browsing contexts.';
-                let req = indexedDB.open('db-presentation-api', 1), store;
-                let eventWatcher = new EventWatcher(t, req, 'success');
-                return eventWatcher.wait_for('success').then(() => {
-                    db = req.result;
-                    const transaction = db.transaction('store-controlling-ua', 'readwrite');
-                    store = transaction.objectStore('store-controlling-ua');
-                    req = store.openCursor();
-                    eventWatcher = new EventWatcher(t, req, 'success');
-                    return eventWatcher.wait_for('success');
-                }).then(() => {
-                    assert_true(req.result instanceof IDBCursorWithValue, message);
-                    const cursor = req.result;
-                    const item = cursor.value;
-                    assert_equals(item.id, 'parent', message);
-                    assert_equals(item.data, 'receiving user agent', message);
-                    cursor.continue();
-                    return eventWatcher.wait_for('success');
-                }).then(() => {
-                    assert_equals(req.result, null, message);
-                    req = store.put({ id: 'child', data: 'nested browsing context' });
-                    eventWatcher = new EventWatcher(t, req, 'success');
-                    return eventWatcher.wait_for('success');
-                }).then(() => {
-                    db.close();
-                    db = null;
-                });
+
+                const req = indexedDB.open(dbName);
+                const upgradeneededWatcher = new EventWatcher(t, req, 'upgradeneeded');
+                const successWatcher = new EventWatcher(t, req, 'success');
+                return Promise.race([
+                    upgradeneededWatcher.wait_for('upgradeneeded').then(evt => {
+                        evt.target.result.close();
+                        // This would fail if the database is not created by the top-level browsing context
+                        assert_unreached(message);
+                    }),
+                    successWatcher.wait_for('success').then(evt => {
+                        evt.target.result.close();
+                        const db = evt.target.result;
+                        const version = db.version;
+                        db.close();
+                        // Check if the version of the database is 2 as specified by the top-level browsing context
+                        assert_equals(version, 2, message);
+
+                        // Upgrade the version
+                        const req = indexedDB.open(dbName, 3);
+                        const eventWatcher = new EventWatcher(t, req, 'upgradeneeded');
+                        return eventWatcher.wait_for('upgradeneeded');
+                    }).then(evt => {
+                        evt.target.result.close();
+                    })
+                ]);
             }
             else
                 return Promise.resolve();
diff --git a/proximity/DeviceProximityEvent_tests.html b/proximity/DeviceProximityEvent_tests.html
deleted file mode 100644
index d4d08fd..0000000
--- a/proximity/DeviceProximityEvent_tests.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!doctype html>
-<meta charset=utf8>
-<meta content="width=device-width, initial-scale=1" name=viewport>
-<title>Proximity Events Test Suite</title>
-<h1>Test Suite for Proximity Events</h1>
-<div id="log"></div>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="DeviceProximityEvent_tests.js"></script>
-
diff --git a/proximity/DeviceProximityEvent_tests.js b/proximity/DeviceProximityEvent_tests.js
deleted file mode 100644
index f433482..0000000
--- a/proximity/DeviceProximityEvent_tests.js
+++ /dev/null
@@ -1,441 +0,0 @@
-(function() {
-    //inheritance tests
-    test(function() {
-        var event = new DeviceProximityEvent('');
-        assert_true(event instanceof window.DeviceProximityEvent);
-    }, 'the event is an instance of DeviceProximityEvent');
-
-    test(function() {
-        var event = new DeviceProximityEvent('');
-        assert_true(event instanceof window.Event);
-    }, 'the event inherits from Event');
-
-    //Type attribute tests
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new DeviceProximityEvent();
-        }, 'First argument is required, so was expecting a TypeError.');
-    }, 'Missing type argument');
-
-    test(function() {
-        var event = new DeviceProximityEvent(undefined);
-        assert_equals(event.type, 'undefined');
-    }, 'Event type set to undefined');
-
-    test(function() {
-        var event = new DeviceProximityEvent(null);
-        assert_equals(event.type, 'null');
-    }, 'type argument is null');
-
-    test(function() {
-        var event = new DeviceProximityEvent(123);
-        assert_equals(event.type, '123');
-    }, 'type argument is number');
-
-    test(function() {
-        var event = new DeviceProximityEvent(new Number(123));
-        assert_equals(event.type, '123');
-    }, 'type argument is Number');
-
-    test(function() {
-        var event = new DeviceProximityEvent([]);
-        assert_equals(event.type, '');
-    }, 'type argument is array');
-
-    test(function() {
-        var event = new DeviceProximityEvent(new Array());
-        assert_equals(event.type, '');
-    }, 'type argument is instance of Array');
-
-    test(function() {
-        var event = new DeviceProximityEvent(['t', ['e', ['s', ['t']]]]);
-        assert_equals(event.type, 't,e,s,t');
-    }, 'type argument is nested array');
-
-    test(function() {
-        var event = new DeviceProximityEvent(Math);
-        assert_equals(event.type, '[object Math]');
-    }, 'type argument is host object');
-
-    test(function() {
-        var event = new DeviceProximityEvent(true);
-        assert_equals(event.type, 'true');
-    }, 'type argument is boolean (true)');
-
-    test(function() {
-        var event = new DeviceProximityEvent(new Boolean(true));
-        assert_equals(event.type, 'true');
-    }, 'type argument is instance of Boolean (true)');
-
-    test(function() {
-        var event = new DeviceProximityEvent(false);
-        assert_equals(event.type, 'false');
-    }, 'type argument is boolean (false)');
-
-    test(function() {
-        var event = new DeviceProximityEvent(new Boolean(false));
-        assert_equals(event.type, 'false');
-    }, 'type argument is instance of Boolean (false)');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_equals(event.type, 'test');
-    }, 'type argument is string');
-
-    test(function() {
-        var event = new DeviceProximityEvent(new String('test'));
-        assert_equals(event.type, 'test');
-    }, 'type argument is instance of String');
-
-    test(function() {
-        var event = new DeviceProximityEvent(function test() {});
-        assert_regexp_match(event.type, /function test.+{\s?}/);
-    }, 'type argument is function');
-
-    test(function() {
-        var event = new DeviceProximityEvent({
-            toString: function() {
-                return '123';
-            }
-        });
-        assert_equals(event.type, '123');
-    }, 'type argument is complext object, with toString method');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new DeviceProximityEvent({
-                toString: function() {
-                    return function() {}
-                }
-            });
-        });
-    }, 'toString is of type function');
-
-    //eventInitDict attribute tests
-    test(function() {
-        var event = new DeviceProximityEvent('test', undefined);
-        assert_equals(event.value, Infinity);
-        assert_equals(event.min, -Infinity);
-        assert_equals(event.max, Infinity);
-    }, 'eventInitDict argument sets to undefined');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', null);
-        assert_equals(event.value, Infinity);
-        assert_equals(event.min, -Infinity);
-        assert_equals(event.max, Infinity);
-    }, 'eventInitDict argument is null');
-
-    test(function() {
-        var date = new Date();
-        var event = new DeviceProximityEvent('test', date);
-        assert_equals(event.value, Infinity);
-        assert_equals(event.min, -Infinity);
-        assert_equals(event.max, Infinity);
-    }, 'eventInitDict argument is Date object');
-
-    test(function() {
-        var regexp = /abc/;
-        var event = new DeviceProximityEvent('test', regexp);
-        assert_equals(event.value, Infinity);
-        assert_equals(event.min, -Infinity);
-        assert_equals(event.max, Infinity);
-    }, 'eventInitDict argument is RegExp object');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new DeviceProximityEvent('test', false);
-        });
-    }, 'eventInitDict argument is boolean');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new DeviceProximityEvent('test', 123);
-        });
-    }, 'eventInitDict argument is number');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new DeviceProximityEvent('test', 'hello');
-        });
-    }, 'eventInitDict argument is string');
-
-    //test readonly attribute unrestricted double value;
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_idl_attribute(event, 'value', 'must have attribute value');
-    }, 'value attribute exist');
-
-     test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_readonly(event, 'value', 'readonly attribute value');
-    }, 'value attribute is readonly');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_equals(event.value, Infinity);
-    }, 'value initializes to positive Infinity');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: Infinity
-        });
-        assert_equals(event.value, Infinity);
-    }, 'value set to positive Infinity');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: -Infinity
-        });
-        assert_equals(event.value, -Infinity);
-    }, 'value set to negative Infinity');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: 0
-        });
-        assert_equals(event.value, 0);
-    }, 'value set to 0');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: 1
-        });
-        assert_equals(event.value, 1);
-    }, 'value set to 1');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: 0.5
-        });
-        assert_equals(event.value, 0.5);
-    }, 'value set to 0.5');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: false
-        });
-        assert_equals(event.value, 0, 'value set to false, converts to 0.');
-    }, 'value set to false');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: true
-        });
-        assert_equals(event.value, 1, 'value set to true, converts to 1.');
-    }, 'value set to true');
-
-
-    test(function() {
-        var prop = {
-            value: undefined
-        };
-        try {
-            var event = new DeviceProximityEvent('test', prop);
-            assert_true(isNaN(event.value));
-        } catch(e) {
-            assert_unreached('error message: ' + e.message);
-        }
-    }, 'value of undefined resolves to NaN');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: null
-        });
-        assert_equals(event.value, 0, 'value resolves to 0');
-    }, 'value of null resolves to 0');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: ''
-        });
-        assert_equals(event.value, 0, 'value must resolve to 0');
-    }, 'value of empty string must resolve to 0');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: '\u0020'
-        });
-        assert_equals(event.value, 0, 'value must resolve to 0');
-    }, 'value of U+0020 must resolve to 0');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: '\u0020\u0020\u0020\u0020\u0020\u0020'
-        });
-        assert_equals(event.value, 0, 'value must resolve to 0');
-    }, 'value of multiple U+0020 must resolve to 0');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: '\u0020\u0020\u00201234\u0020\u0020\u0020'
-        });
-        assert_equals(event.value, 1234, 'converts to 1234');
-    }, 'value converts to 1234');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test', {
-            value: []
-        });
-        assert_equals(event.value, 0, 'converts to 0');
-    }, 'value converts to 0');
-
-
-    test(function() {
-        var prop = {
-            value: {}
-        };
-        try {
-            var event = new DeviceProximityEvent('test', prop);
-            assert_true(isNaN(event.value));
-        } catch(e) {
-            assert_unreached('error message: ' + e.message);
-        }
-    }, 'value of {} resolves to NaN');
-
-    test(function() {
-        var prop = {
-            get value() {
-                return NaN;
-            }
-        };
-        try {
-            var event = new DeviceProximityEvent('test', prop);
-            assert_true(isNaN(event.value));
-        } catch(e) {
-            assert_unreached('error message: ' + e.message);
-        }
-    }, 'value resolves to NaN');
-
-    test(function() {
-        var prop = {
-            get value() {
-                return '123';
-            }
-        };
-        var event = new DeviceProximityEvent('test', prop);
-        assert_equals(event.value, 123, 'converts to 123');
-    }, 'value resolves 123');
-
-    //test readonly attribute unrestricted double min
-   test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_idl_attribute(event, 'min', 'must have attribute min');
-    }, 'min attribute exist');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_readonly(event, 'min', 'readonly attribute min');
-    }, 'min attribute is readonly');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_equals(event.min, -Infinity);
-    }, 'min initializes to negative Infinity');
-
-    //test readonly attribute unrestricted double max;
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_idl_attribute(event, 'max', 'must have attribute max');
-    }, 'max attribute exist');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_readonly(event, 'max', 'readonly attribute max');
-    }, 'max attribute is readonly');
-
-    test(function() {
-        var event = new DeviceProximityEvent('test');
-        assert_equals(event.max, Infinity);
-    }, 'max initializes to positive Infinity');
-
-    //test attribute EventHandler ondeviceproximity;
-    test(function() {
-        var desc = 'Expected to find ondeviceproximity attribute on window object';
-        assert_idl_attribute(window, 'ondeviceproximity', desc);
-    }, 'ondeviceproximity exists');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity must be null';
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'ondeviceproximity is null');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not accept callable object',
-            func = function() {},
-            descidl = 'Expected to find ondeviceproximity attribute on window object';
-        assert_idl_attribute(window, 'ondeviceproximity', descidl);
-        window.ondeviceproximity = func;
-        assert_equals(window.ondeviceproximity, func, desc);
-    }, 'ondeviceproximity is set to function');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not treat noncallable as null';
-        window.ondeviceproximity = function() {};
-        window.ondeviceproximity = {};
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'treat object as null');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not treat noncallable as null';
-        window.ondeviceproximity = function() {};
-        window.ondeviceproximity = {
-            call: 'test'
-        };
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'treat object with non-callable call property as null');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not treat noncallable (string) as null';
-        window.ondeviceproximity = function() {};
-        window.ondeviceproximity = 'string';
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'treat string as null');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not treat noncallable (number) as null';
-        window.ondeviceproximity = function() {};
-        window.ondeviceproximity = 123;
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'treat number as null');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not treat noncallable (undefined) as null';
-        window.ondeviceproximity = function() {};
-        window.ondeviceproximity = undefined;
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'treat undefined as null');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not treat noncallable (array) as null';
-        window.ondeviceproximity = function() {};
-        window.ondeviceproximity = [];
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'treat array as null');
-
-    test(function() {
-        var desc = 'window.ondeviceproximity did not treat noncallable host object as null';
-        window.ondeviceproximity = function() {};
-        window.ondeviceproximity = Node;
-        assert_equals(window.ondeviceproximity, null, desc);
-    }, 'treat non-callable host object as null');
-
-    //Async tests
-    var t = async_test('test if device proximity event received');
-    window.addEventListener('deviceproximity', function(e) {
-        t.step(function() {
-            var msg = 'expected instance of DeviceProximityEvent: ';
-            assert_true(e instanceof window.DeviceProximityEvent, msg);
-        });
-        t.done();
-    });
-
-    var t2 = async_test('test if user proximity event received');
-    window.ondeviceproximity = function(e) {
-        t2.step(function() {
-            var msg = 'expected instance of DeviceProximityEvent: ';
-            assert_true(e instanceof window.DeviceProximityEvent, msg);
-        });
-        t2.done();
-    };
-})();
diff --git a/proximity/ProximitySensor.https.html b/proximity/ProximitySensor.https.html
new file mode 100644
index 0000000..ee7f684
--- /dev/null
+++ b/proximity/ProximitySensor.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>ProximitySensor Test</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/proximity/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/generic-sensor/generic-sensor-tests.js"></script>
+<div id="log"></div>
+<script>
+
+runGenericSensorTests(ProximitySensor);
+
+</script>
diff --git a/proximity/ProximitySensor_insecure_context.html b/proximity/ProximitySensor_insecure_context.html
new file mode 100644
index 0000000..b524cdc
--- /dev/null
+++ b/proximity/ProximitySensor_insecure_context.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>ProximitySensor Test: insecure context</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/proximity/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/generic-sensor/generic-sensor-tests.js"></script>
+<div id="log"></div>
+<h2>Precondition</h2>
+<ol>
+  <li>
+    Run test in an insecure context, e.g. http://example.com/.
+  </li>
+</ol>
+<script>
+
+runGenericSensorInsecureContext("ProximitySensor");
+
+</script>
diff --git a/proximity/ProximitySensor_onerror-manual.https.html b/proximity/ProximitySensor_onerror-manual.https.html
new file mode 100644
index 0000000..e1ee380
--- /dev/null
+++ b/proximity/ProximitySensor_onerror-manual.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>ProximitySensor Test: onerror</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/proximity/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/generic-sensor/generic-sensor-tests.js"></script>
+<div id="log"></div>
+<h2>Precondition</h2>
+<ol>
+  <li>
+    Disable the Proximity Sensor or run test on a device without Proximity Sensor.
+  </li>
+</ol>
+<script>
+
+runGenericSensorOnerror(ProximitySensor);
+
+</script>
diff --git a/proximity/UserProximityEvent_tests.html b/proximity/UserProximityEvent_tests.html
deleted file mode 100644
index f58550d..0000000
--- a/proximity/UserProximityEvent_tests.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!doctype html>
-<meta charset=utf8>
-<meta content="width=device-width, initial-scale=1" name=viewport>
-<title>Proximity Events Test Suite</title>
-<h1>Test Suite for Proximity Events</h1>
-<div id="log"></div>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="UserProximityEvent_tests.js"></script>
-
diff --git a/proximity/UserProximityEvent_tests.js b/proximity/UserProximityEvent_tests.js
deleted file mode 100644
index 36ef930..0000000
--- a/proximity/UserProximityEvent_tests.js
+++ /dev/null
@@ -1,340 +0,0 @@
-(function() {
-    //inheritance tests
-    test(function() {
-        var event = new UserProximityEvent('');
-        assert_true(event instanceof window.UserProximityEvent);
-    }, 'the event is an instance of UserProximityEvent');
-
-    test(function() {
-        var event = new UserProximityEvent('');
-        assert_true(event instanceof window.Event);
-    }, 'the event inherits from Event');
-
-    //Type attribute tests
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new UserProximityEvent();
-        }, 'First argument is required, so was expecting a TypeError.');
-    }, 'Missing type argument');
-
-    test(function() {
-        var event = new UserProximityEvent(undefined);
-        assert_equals(event.type, 'undefined');
-    }, 'Event type set to undefined');
-
-    test(function() {
-        var event = new UserProximityEvent(null);
-        assert_equals(event.type, 'null');
-    }, 'type argument is null');
-
-    test(function() {
-        var event = new UserProximityEvent(123);
-        assert_equals(event.type, '123');
-    }, 'type argument is number');
-
-    test(function() {
-        var event = new UserProximityEvent(new Number(123));
-        assert_equals(event.type, '123');
-    }, 'type argument is Number');
-
-    test(function() {
-        var event = new UserProximityEvent([]);
-        assert_equals(event.type, '');
-    }, 'type argument is array');
-
-    test(function() {
-        var event = new UserProximityEvent(new Array());
-        assert_equals(event.type, '');
-    }, 'type argument is instance of Array');
-
-    test(function() {
-        var event = new UserProximityEvent(['t', ['e', ['s', ['t']]]]);
-        assert_equals(event.type, 't,e,s,t');
-    }, 'type argument is nested array');
-
-    test(function() {
-        var event = new UserProximityEvent(Math);
-        assert_equals(event.type, '[object Math]');
-    }, 'type argument is host object');
-
-    test(function() {
-        var event = new UserProximityEvent(true);
-        assert_equals(event.type, 'true');
-    }, 'type argument is boolean (true)');
-
-    test(function() {
-        var event = new UserProximityEvent(new Boolean(true));
-        assert_equals(event.type, 'true');
-    }, 'type argument is instance of Boolean (true)');
-
-    test(function() {
-        var event = new UserProximityEvent(false);
-        assert_equals(event.type, 'false');
-    }, 'type argument is boolean (false)');
-
-    test(function() {
-        var event = new UserProximityEvent(new Boolean(false));
-        assert_equals(event.type, 'false');
-    }, 'type argument is instance of Boolean (false)');
-
-    test(function() {
-        var event = new UserProximityEvent('test');
-        assert_equals(event.type, 'test');
-    }, 'type argument is string');
-
-    test(function() {
-        var event = new UserProximityEvent(new String('test'));
-        assert_equals(event.type, 'test');
-    }, 'type argument is instance of String');
-
-    test(function() {
-        var event = new UserProximityEvent(function test() {});
-        assert_regexp_match(event.type, /function test.+{\s?}/);
-    }, 'type argument is function');
-
-    test(function() {
-        var event = new UserProximityEvent({
-            toString: function() {
-                return '123';
-            }
-        });
-        assert_equals(event.type, '123');
-    }, 'type argument is complext object, with toString method');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new UserProximityEvent({
-                toString: function() {
-                    return function() {}
-                }
-            });
-        });
-    }, 'toString is of type function');
-
-    //eventInitDict attribute tests
-    test(function() {
-        var event = new UserProximityEvent('test', undefined);
-        assert_false(event.near);
-    }, 'eventInitDict argument sets to undefined');
-
-    test(function() {
-        var event = new UserProximityEvent('test', null);
-        assert_false(event.near);
-    }, 'eventInitDict argument is null');
-
-    test(function() {
-        var date = new Date();
-        var event = new UserProximityEvent('test', date);
-        assert_false(event.near);
-    }, 'eventInitDict argument is Date object');
-
-    test(function() {
-        var regexp = /abc/;
-        var event = new UserProximityEvent('test', regexp);
-        assert_false(event.near);
-    }, 'eventInitDict argument is RegExp object');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new UserProximityEvent('test', false);
-        });
-    }, 'eventInitDict argument is boolean');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new UserProximityEvent('test', 123);
-        });
-    }, 'eventInitDict argument is number');
-
-    test(function() {
-        assert_throws(new TypeError(), function() {
-            new UserProximityEvent('test', 'hello');
-        });
-    }, 'eventInitDict argument is string');
-
-    //test readonly attribute boolean near;
-    test(function() {
-        var event = new UserProximityEvent('test');
-        assert_idl_attribute(event, 'near', 'must have attribute near');
-    }, 'must have attribute near');
-
-    test(function() {
-        var event = new UserProximityEvent('test');
-        assert_readonly(event, 'near', 'readonly attribute near');
-    }, 'near is readonly');
-
-    test(function() {
-        var event = new UserProximityEvent('test');
-        assert_false(event.near, 'near initializes to false');
-    }, 'near initializes to false');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: false
-        });
-        assert_false(event.near, 'near set to false');
-    }, 'near set to false');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: true
-        });
-        assert_true(event.near, 'near set to true');
-    }, 'near set to true');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: undefined
-        });
-        assert_false(event.near, 'argument is truthy');
-    }, 'near set to undefined');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: null
-        });
-        assert_false(event.near, 'argument is flasy');
-    }, 'near set to null');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: 0
-        });
-        assert_false(event.near, 'argument is flasy');
-    }, 'near set to 0');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: ''
-        });
-        assert_false(event.near, 'argument is flasy');
-    }, 'near set to empty string');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: '\u0020'
-        });
-        assert_true(event.near, 'argument is truthy');
-    }, 'near set to U+0020');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: 1
-        });
-        assert_true(event.near, 'argument is truthy');
-    }, 'near set to 1');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: []
-        });
-        assert_true(event.near, 'argument is truthy');
-    }, 'near set to []');
-
-    test(function() {
-        var event = new UserProximityEvent('test', {
-            near: {}
-        });
-        assert_true(event.near, 'argument is truthy');
-    }, 'near set to {}');
-
-    test(function() {
-        var dict = {
-            get test() {
-                return false;
-            }
-        };
-        var event = new UserProximityEvent('test', {
-            near: dict.test
-        });
-        assert_false(event.near, 'explict false');
-    }, 'near set to object that resolves to false');
-
-    test(function() {
-        var desc = 'Expected to find onuserproximity attribute on window object';
-        assert_idl_attribute(window, 'onuserproximity', desc);
-    }, 'onuserproximity exists');
-
-    test(function() {
-        var desc = 'window.onuserproximity must be null';
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'onuserproximity is null');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not accept callable object',
-            func = function() {},
-            descidl = 'onuserproximity does not exist';
-        window.onuserproximity = func;
-        assert_equals(window.onuserproximity, func, descidl);
-    }, 'onuserproximity exists and can be set to a function');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not treat noncallable as null';
-        window.onuserproximity = function() {};
-        window.onuserproximity = {};
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'treat object as null');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not treat noncallable as null';
-        window.onuserproximity = function() {};
-        window.onuserproximity = {
-            call: 'test'
-        };
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'treat object with non-callable call property as null');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not treat noncallable (string) as null';
-        window.onuserproximity = function() {};
-        window.onuserproximity = 'string';
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'treat string as null');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not treat noncallable (number) as null';
-        window.onuserproximity = function() {};
-        window.onuserproximity = 123;
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'treat number as null');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not treat noncallable (undefined) as null';
-        window.onuserproximity = function() {};
-        window.onuserproximity = undefined;
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'treat undefined as null');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not treat noncallable (array) as null';
-        window.onuserproximity = function() {};
-        window.onuserproximity = [];
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'treat array as null');
-
-    test(function() {
-        var desc = 'window.onuserproximity did not treat noncallable host object as null';
-        window.onuserproximity = function() {};
-        window.onuserproximity = Node;
-        assert_equals(window.onuserproximity, null, desc);
-    }, 'treat non-callable host object as null');
-
-    //Async tests
-    var t = async_test('test if user proximity event received');
-    window.addEventListener('userproximity', function(e) {
-        t.step(function() {
-            var msg = 'expected instance of UserProximityEvent: ';
-            assert_true(e instanceof window.UserProximityEvent, msg);
-        });
-        t.done();
-    });
-
-    var t2 = async_test('test if user proximity event received (idl attribute)');
-    window.onuserproximity = function(e) {
-        t2.step(function() {
-            var msg = 'expected instance of UserProximityEvent: ';
-            assert_true(e instanceof window.UserProximityEvent, msg);
-        });
-        t2.done();
-    };
-})();
diff --git a/proximity/idlharness.html b/proximity/idlharness.html
deleted file mode 100644
index 817aa5c..0000000
--- a/proximity/idlharness.html
+++ /dev/null
@@ -1,77 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Proximity Events IDL tests</title>
-<link rel="author" title="Intel" href="http://www.intel.com">
-<link rel="help" href="http://www.w3.org/TR/proximity/">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/resources/idlharness.js"></script>
-<style>
-  pre {
-    display: none;
-  }
-</style>
-<div id="log"></div>
-
-<pre id="untested_idl">
-[PrimaryGlobal]
-interface Window {
-};
-
-interface Event {
-};
-
-dictionary EventInit {
-};
-</pre>
-
-<pre id='idl'>
-partial interface Window {
-                attribute EventHandler ondeviceproximity;
-                attribute EventHandler onuserproximity;
-};
-
-dictionary DeviceProximityEventInit : EventInit {
-    double value;
-    double min;
-    double max;
-};
-
-[Constructor (DOMString type, optional DeviceProximityEventInit eventInitDict)]
-interface DeviceProximityEvent : Event {
-    readonly    attribute unrestricted double value;
-    readonly    attribute unrestricted double min;
-    readonly    attribute unrestricted double max;
-};
-
-dictionary UserProximityEventInit : EventInit {
-    boolean near;
-};
-
-[Constructor (DOMString type, optional UserProximityEventInit eventInitDict)]
-interface UserProximityEvent : Event {
-    readonly    attribute boolean near;
-};
-</pre>
-
-<script>
-(function() {
-  "use strict";
-  var idl_array = new IdlArray();
-  // replace 'EventHandler' and 'unrestricted double' unrecognised by idlharness.js
-  var idls = document.getElementById('idl').textContent.replace(/EventHandler/g, 'Function?').replace(/unrestricted double/g, 'double');
-
-  idl_array.add_untested_idls(document.getElementById('untested_idl').textContent);
-  idl_array.add_idls(idls);
-
-  idl_array.add_objects({
-    Window: ['window'],
-    DeviceProximityEvent: ['new DeviceProximityEvent("foo")'],
-    UserProximityEvent: ['new UserProximityEvent("foo")']
-  });
-
-  idl_array.test();
-})();
-</script>
-
diff --git a/proximity/idlharness.https.html b/proximity/idlharness.https.html
new file mode 100644
index 0000000..ee0dbcd
--- /dev/null
+++ b/proximity/idlharness.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Proximity Sensor IDL tests</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/proximity/">
+<link rel="help" href="https://w3c.github.io/sensors/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+"use strict";
+
+function doTest([dom, generic_sensor, proximity]) {
+  const idl_array = new IdlArray();
+  idl_array.add_untested_idls(dom);
+  idl_array.add_untested_idls('interface EventHandler {};');
+  idl_array.add_idls(generic_sensor, { only: ['Sensor'] });
+  idl_array.add_idls(proximity);
+  idl_array.add_objects({
+    ProximitySensor: ['new ProximitySensor();']
+  });
+  idl_array.test();
+}
+
+function fetchText(url) {
+  return fetch(url).then((response) => response.text());
+}
+
+promise_test(() => {
+  return Promise.all([
+    "/interfaces/dom.idl",
+    "/interfaces/sensors.idl",
+    "/interfaces/proximity.idl",
+  ].map(fetchText)).then(doTest);
+}, "Test IDL implementation of Proximity Sensor");
+</script>
diff --git a/quirks-mode/OWNERS b/quirks-mode/OWNERS
deleted file mode 100644
index 8b13789..0000000
--- a/quirks-mode/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/quirks/OWNERS b/quirks/OWNERS
new file mode 100644
index 0000000..6385161
--- /dev/null
+++ b/quirks/OWNERS
@@ -0,0 +1 @@
+@zcorpan
diff --git a/quirks-mode/active-and-hover-manual.html b/quirks/active-and-hover-manual.html
similarity index 100%
rename from quirks-mode/active-and-hover-manual.html
rename to quirks/active-and-hover-manual.html
diff --git a/quirks-mode/blocks-ignore-line-height.html b/quirks/blocks-ignore-line-height.html
similarity index 100%
rename from quirks-mode/blocks-ignore-line-height.html
rename to quirks/blocks-ignore-line-height.html
diff --git a/quirks-mode/classname-query-after-sibling-adoption.html b/quirks/classname-query-after-sibling-adoption.html
similarity index 100%
rename from quirks-mode/classname-query-after-sibling-adoption.html
rename to quirks/classname-query-after-sibling-adoption.html
diff --git a/quirks-mode/hashless-hex-color.html b/quirks/hashless-hex-color.html
similarity index 100%
rename from quirks-mode/hashless-hex-color.html
rename to quirks/hashless-hex-color.html
diff --git a/quirks-mode/historical/list-item-bullet-size-ref.html b/quirks/historical/list-item-bullet-size-ref.html
similarity index 100%
rename from quirks-mode/historical/list-item-bullet-size-ref.html
rename to quirks/historical/list-item-bullet-size-ref.html
diff --git a/quirks-mode/historical/list-item-bullet-size.html b/quirks/historical/list-item-bullet-size.html
similarity index 100%
rename from quirks-mode/historical/list-item-bullet-size.html
rename to quirks/historical/list-item-bullet-size.html
diff --git a/quirks-mode/line-height-calculation.html b/quirks/line-height-calculation.html
similarity index 100%
rename from quirks-mode/line-height-calculation.html
rename to quirks/line-height-calculation.html
diff --git a/quirks-mode/percentage-height-calculation.html b/quirks/percentage-height-calculation.html
similarity index 100%
rename from quirks-mode/percentage-height-calculation.html
rename to quirks/percentage-height-calculation.html
diff --git a/quirks-mode/supports.html b/quirks/supports.html
similarity index 100%
rename from quirks-mode/supports.html
rename to quirks/supports.html
diff --git a/quirks-mode/table-cell-nowrap-minimum-width-calculation.html b/quirks/table-cell-nowrap-minimum-width-calculation.html
similarity index 100%
rename from quirks-mode/table-cell-nowrap-minimum-width-calculation.html
rename to quirks/table-cell-nowrap-minimum-width-calculation.html
diff --git a/quirks-mode/table-cell-width-calculation.html b/quirks/table-cell-width-calculation.html
similarity index 100%
rename from quirks-mode/table-cell-width-calculation.html
rename to quirks/table-cell-width-calculation.html
diff --git a/quirks-mode/unitless-length.html b/quirks/unitless-length.html
similarity index 100%
rename from quirks-mode/unitless-length.html
rename to quirks/unitless-length.html
diff --git a/referrer-policy/OWNERS b/referrer-policy/OWNERS
index 68fa260..db1faa0 100644
--- a/referrer-policy/OWNERS
+++ b/referrer-policy/OWNERS
@@ -1,3 +1,4 @@
 @estark37
 @jeisinger
 @kristijanburnik
+@domfarolino
diff --git a/referrer-policy/generic/common.js b/referrer-policy/generic/common.js
index 3459401..1ccdaab 100644
--- a/referrer-policy/generic/common.js
+++ b/referrer-policy/generic/common.js
@@ -248,7 +248,3 @@
 function SanityChecker() {}
 SanityChecker.prototype.checkScenario = function() {};
 SanityChecker.prototype.checkSubresourceResult = function() {};
-
-// TODO(kristijanburnik): document.origin is supported since Chrome 41,
-// other browsers still don't support it. Remove once they do.
-document.origin = document.origin || (location.protocol + "//" + location.host);
diff --git a/referrer-policy/generic/referrer-policy-test-case.js b/referrer-policy/generic/referrer-policy-test-case.js
index b90ecfd..7c8cba4 100644
--- a/referrer-policy/generic/referrer-policy-test-case.js
+++ b/referrer-policy/generic/referrer-policy-test-case.js
@@ -29,7 +29,7 @@
       return undefined;
     },
     "origin": function() {
-      return document.origin + "/";
+      return self.origin + "/";
     },
     "stripped-referrer": function() {
       return stripUrlForUseAsReferrer(location.toString());
diff --git a/referrer-policy/generic/tools/common_paths.py b/referrer-policy/generic/tools/common_paths.py
index dc303f3..5b495c6 100644
--- a/referrer-policy/generic/tools/common_paths.py
+++ b/referrer-policy/generic/tools/common_paths.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 import os, sys, json, re
 
 script_directory = os.path.dirname(os.path.abspath(__file__))
@@ -39,13 +41,13 @@
     with open(spec_filename, "r") as f:
         try:
           spec_json = json.load(f)
-        except ValueError, ex:
-          print ex.message
+        except ValueError as ex:
+          print(ex.message)
           match = re_error_location.search(ex.message)
           if match:
             line_number, column = int(match.group(1)), int(match.group(2))
-            print read_nth_line(f, line_number).rstrip()
-            print " " * (column - 1) + "^"
+            print(read_nth_line(f, line_number).rstrip())
+            print(" " * (column - 1) + "^")
 
           sys.exit(1)
 
diff --git a/referrer-policy/generic/tools/generate.py b/referrer-policy/generic/tools/generate.py
index 10fc11c..30e5b61 100755
--- a/referrer-policy/generic/tools/generate.py
+++ b/referrer-policy/generic/tools/generate.py
@@ -1,5 +1,7 @@
 #!/usr/bin/env python
 
+from __future__ import print_function
+
 import os, sys, json
 from common_paths import *
 import spec_validator
@@ -153,7 +155,7 @@
                                        subresource_path,
                                        html_template)
                 else:
-                    print 'Excluding selection:', selection_path
+                    print('Excluding selection:', selection_path)
 
 
 def main(target):
diff --git a/referrer-policy/generic/tools/spec_validator.py b/referrer-policy/generic/tools/spec_validator.py
index 8641bbc..b595320 100755
--- a/referrer-policy/generic/tools/spec_validator.py
+++ b/referrer-policy/generic/tools/spec_validator.py
@@ -1,5 +1,7 @@
 #!/usr/bin/env python
 
+from __future__ import print_function
+
 import json, sys
 from common_paths import *
 
@@ -150,16 +152,16 @@
     error_details = {}
     try:
         validate(spec_json, error_details)
-    except AssertionError, err:
-        print 'ERROR:', err.message
-        print json.dumps(error_details, indent=4)
+    except AssertionError as err:
+        print('ERROR:', err.message)
+        print(json.dumps(error_details, indent=4))
         sys.exit(1)
 
 
 def main():
     spec_json = load_spec_json();
     assert_valid_spec_json(spec_json)
-    print "Spec JSON is valid."
+    print("Spec JSON is valid.")
 
 
 if __name__ == '__main__':
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..f8712fd
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
new file mode 100644
index 0000000..701ec1d
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..d1b0482
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..e6863a1
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..9a2dd53
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..058e087
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..c2c4ccd
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
new file mode 100644
index 0000000..e5da881
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..a0e3be1
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..389921d
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..dfbabf2
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..233611e
--- /dev/null
+++ b/referrer-policy/no-referrer-when-downgrade/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer-when-downgrade'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer-when-downgrade",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..542395d
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..178f813
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..ff9462e
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..890ab5b
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..1dd2d1c
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..4c9e155
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..6edeebf
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..0276285
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..2760fdc
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..d3a2a47
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..393a92a
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..2f2926b
--- /dev/null
+++ b/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'no-referrer'</title>
+    <meta name="description" content="Check that sub-resource never gets the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "no-referrer",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.keep-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.keep-origin-redirect.http.html
new file mode 100644
index 0000000..d34b4ae
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.no-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.no-redirect.http.html
new file mode 100644
index 0000000..f8a2002
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.swap-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.swap-origin-redirect.http.html
new file mode 100644
index 0000000..38a61d2
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.keep-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.keep-origin-redirect.http.html
new file mode 100644
index 0000000..098b987
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.no-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.no-redirect.http.html
new file mode 100644
index 0000000..2d82170
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.swap-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.swap-origin-redirect.http.html
new file mode 100644
index 0000000..fce7ad9
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.keep-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.keep-origin-redirect.http.html
new file mode 100644
index 0000000..318ffd5
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.no-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.no-redirect.http.html
new file mode 100644
index 0000000..dea7ad2
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.swap-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.swap-origin-redirect.http.html
new file mode 100644
index 0000000..46c715d
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.keep-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.keep-origin-redirect.http.html
new file mode 100644
index 0000000..9063bbd
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.no-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.no-redirect.http.html
new file mode 100644
index 0000000..56bc742
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.swap-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.swap-origin-redirect.http.html
new file mode 100644
index 0000000..3209c5b
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-downgrade.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-insecure.swap-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-insecure.swap-origin-redirect.http.html
new file mode 100644
index 0000000..3209c5b
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-insecure.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.keep-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.keep-origin-redirect.http.html
new file mode 100644
index 0000000..9063bbd
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.no-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.no-redirect.http.html
new file mode 100644
index 0000000..56bc742
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.swap-origin-redirect.http.html b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.swap-origin-redirect.http.html
new file mode 100644
index 0000000..3209c5b
--- /dev/null
+++ b/referrer-policy/origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-upgrade.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin-when-cross-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..9be1730
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html b/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..a7d72f5
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..9bfb081
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..0a24543
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html b/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..463b67e
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..10ef951
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..84333fb
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html b/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..15cd9b2
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..10984fd
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..554665c
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html b/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..6237b64
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..6eae7c5
--- /dev/null
+++ b/referrer-policy/origin/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'origin'</title>
+    <meta name="description" content="Check that all subresources in all casses get only the origin portion of the referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.keep-origin-redirect.http.html b/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.keep-origin-redirect.http.html
new file mode 100644
index 0000000..0c7b712
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.no-redirect.http.html b/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.no-redirect.http.html
new file mode 100644
index 0000000..eca111b
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.swap-origin-redirect.http.html b/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.swap-origin-redirect.http.html
new file mode 100644
index 0000000..f4629963
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/script-tag/cross-origin.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.keep-origin-redirect.http.html b/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.keep-origin-redirect.http.html
new file mode 100644
index 0000000..7b61c26
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.no-redirect.http.html b/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.no-redirect.http.html
new file mode 100644
index 0000000..aa3b6aa
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.swap-origin-redirect.http.html b/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.swap-origin-redirect.http.html
new file mode 100644
index 0000000..b993d95
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/script-tag/cross-origin.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.keep-origin-redirect.http.html b/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.keep-origin-redirect.http.html
new file mode 100644
index 0000000..0a4d82c
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.no-redirect.http.html b/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.no-redirect.http.html
new file mode 100644
index 0000000..7eee32a
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.swap-origin-redirect.http.html b/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.swap-origin-redirect.http.html
new file mode 100644
index 0000000..f92bf34
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/same-origin/http-http/script-tag/same-origin-insecure.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/same-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-insecure.swap-origin-redirect.http.html b/referrer-policy/same-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-insecure.swap-origin-redirect.http.html
new file mode 100644
index 0000000..ddd0fd4
--- /dev/null
+++ b/referrer-policy/same-origin/attr-referrer/same-origin/http-https/script-tag/same-origin-insecure.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'same-origin'</title>
+    <meta name="description" content="Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin">
+    <meta name="assert" content="The referrer URL is omitted when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "same-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "omitted"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/spec.src.json b/referrer-policy/spec.src.json
index 6d76af2..1ee2b70 100644
--- a/referrer-policy/spec.src.json
+++ b/referrer-policy/spec.src.json
@@ -489,7 +489,6 @@
       "redirection": "*",
       "origin": "*",
       "subresource": [
-        "script-tag",
         "xhr-request",
         "worker-request",
         "fetch-request"
diff --git a/referrer-policy/spec_json.js b/referrer-policy/spec_json.js
index 1da04bd..dd1962d 100644
--- a/referrer-policy/spec_json.js
+++ b/referrer-policy/spec_json.js
@@ -1 +1 @@
-var SPEC_JSON = {"subresource_path": {"img-tag": "/referrer-policy/generic/subresource/image.py", "fetch-request": "/referrer-policy/generic/subresource/xhr.py", "a-tag": "/referrer-policy/generic/subresource/document.py", "area-tag": "/referrer-policy/generic/subresource/document.py", "iframe-tag": "/referrer-policy/generic/subresource/document.py", "xhr-request": "/referrer-policy/generic/subresource/xhr.py", "worker-request": "/referrer-policy/generic/subresource/worker.py", "script-tag": "/referrer-policy/generic/subresource/script.py"}, "test_expansion_schema": {"origin": ["same-origin", "cross-origin"], "subresource": ["iframe-tag", "img-tag", "script-tag", "a-tag", "area-tag", "xhr-request", "worker-request", "fetch-request"], "target_protocol": ["http", "https"], "expansion": ["default", "override"], "delivery_method": ["http-rp", "meta-referrer", "attr-referrer", "rel-noreferrer"], "redirection": ["no-redirect", "keep-origin-redirect", "swap-origin-redirect"], "referrer_url": ["omitted", "origin", "stripped-referrer"], "source_protocol": ["http", "https"]}, "specification": [{"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policies", "referrer_policy": null, "title": "Referrer Policy is not explicitly defined", "test_expansion": [{"origin": "*", "name": "insecure-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "secure-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}], "name": "unset-referrer-policy", "description": "Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer", "referrer_policy": "no-referrer", "title": "Referrer Policy is set to 'no-referrer'", "test_expansion": [{"origin": "*", "name": "generic", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "*", "subresource": "*"}], "name": "no-referrer", "description": "Check that sub-resource never gets the referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade", "referrer_policy": "no-referrer-when-downgrade", "title": "Referrer Policy is set to 'no-referrer-when-downgrade'", "test_expansion": [{"origin": "*", "name": "insecure-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "secure-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}], "name": "no-referrer-when-downgrade", "description": "Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin", "referrer_policy": "origin", "title": "Referrer Policy is set to 'origin'", "test_expansion": [{"origin": "*", "name": "generic", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "*", "subresource": "*"}], "name": "origin", "description": "Check that all subresources in all casses get only the origin portion of the referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin", "referrer_policy": "same-origin", "title": "Referrer Policy is set to 'same-origin'", "test_expansion": [{"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-secure-default", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "*", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "omitted", "source_protocol": "*", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-origin", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "*", "subresource": "*"}], "name": "same-origin", "description": "Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin", "referrer_policy": "origin-when-cross-origin", "title": "Referrer Policy is set to 'origin-when-cross-origin'", "test_expansion": [{"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-secure-default", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-upgrade", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-downgrade", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "*", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "origin", "source_protocol": "*", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-origin", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "*", "subresource": "*"}], "name": "origin-when-cross-origin", "description": "Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin", "referrer_policy": "strict-origin", "title": "Referrer Policy is set to 'strict-origin'", "test_expansion": [{"origin": "*", "name": "insecure-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "secure-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "https", "subresource": "*"}], "name": "strict-origin", "description": "Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin", "referrer_policy": "strict-origin-when-cross-origin", "title": "Referrer Policy is set to 'strict-origin-when-cross-origin'", "test_expansion": [{"origin": "same-origin", "name": "same-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-insecure", "target_protocol": "http", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-secure", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-secure", "target_protocol": "https", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "origin", "source_protocol": "https", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-secure", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "https", "subresource": "*"}], "name": "strict-origin-when-cross-origin", "description": "Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url", "referrer_policy": "unsafe-url", "title": "Referrer Policy is set to 'unsafe-url'", "test_expansion": [{"origin": "*", "name": "generic", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "*", "subresource": "*"}], "name": "unsafe-url", "description": "Check that all sub-resources get the stripped referrer URL."}], "referrer_policy_schema": [null, "no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "origin-when-cross-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url"], "excluded_tests": [{"origin": "cross-origin", "name": "cross-origin-workers", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": "worker-request"}, {"origin": "*", "name": "upgraded-protocol-workers", "target_protocol": "https", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "http", "subresource": "worker-request"}, {"origin": "*", "name": "mixed-content-insecure-subresources", "target_protocol": "http", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "elements-not-supporting-attr-referrer", "target_protocol": "*", "expansion": "*", "delivery_method": ["attr-referrer"], "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": ["script-tag", "xhr-request", "worker-request", "fetch-request"]}, {"origin": "*", "name": "elements-not-supporting-rel-noreferrer", "target_protocol": "*", "expansion": "*", "delivery_method": ["rel-noreferrer"], "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": ["iframe-tag", "img-tag", "script-tag", "xhr-request", "worker-request", "fetch-request", "area-tag"]}, {"origin": "*", "name": "area-tag", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": "area-tag"}, {"origin": "*", "name": "worker-requests-with-swap-origin-redirect", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "swap-origin-redirect", "referrer_url": "*", "source_protocol": "*", "subresource": ["worker-request"]}, {"origin": "*", "name": "overhead-for-redirection", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": ["keep-origin-redirect", "swap-origin-redirect"], "referrer_url": "*", "source_protocol": "*", "subresource": ["a-tag", "area-tag"]}, {"origin": "*", "name": "source-https-unsupported-by-web-platform-tests-runners", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "https", "subresource": "*"}]};
+var SPEC_JSON = {"subresource_path": {"img-tag": "/referrer-policy/generic/subresource/image.py", "fetch-request": "/referrer-policy/generic/subresource/xhr.py", "a-tag": "/referrer-policy/generic/subresource/document.py", "area-tag": "/referrer-policy/generic/subresource/document.py", "iframe-tag": "/referrer-policy/generic/subresource/document.py", "xhr-request": "/referrer-policy/generic/subresource/xhr.py", "worker-request": "/referrer-policy/generic/subresource/worker.py", "script-tag": "/referrer-policy/generic/subresource/script.py"}, "test_expansion_schema": {"origin": ["same-origin", "cross-origin"], "subresource": ["iframe-tag", "img-tag", "script-tag", "a-tag", "area-tag", "xhr-request", "worker-request", "fetch-request"], "target_protocol": ["http", "https"], "expansion": ["default", "override"], "delivery_method": ["http-rp", "meta-referrer", "attr-referrer", "rel-noreferrer"], "redirection": ["no-redirect", "keep-origin-redirect", "swap-origin-redirect"], "referrer_url": ["omitted", "origin", "stripped-referrer"], "source_protocol": ["http", "https"]}, "specification": [{"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policies", "referrer_policy": null, "title": "Referrer Policy is not explicitly defined", "test_expansion": [{"origin": "*", "name": "insecure-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "secure-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}], "name": "unset-referrer-policy", "description": "Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer", "referrer_policy": "no-referrer", "title": "Referrer Policy is set to 'no-referrer'", "test_expansion": [{"origin": "*", "name": "generic", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "*", "subresource": "*"}], "name": "no-referrer", "description": "Check that sub-resource never gets the referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-no-referrer-when-downgrade", "referrer_policy": "no-referrer-when-downgrade", "title": "Referrer Policy is set to 'no-referrer-when-downgrade'", "test_expansion": [{"origin": "*", "name": "insecure-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "secure-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}], "name": "no-referrer-when-downgrade", "description": "Check that non a priori insecure subresource gets the full Referrer URL. A priori insecure subresource gets no referrer information."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin", "referrer_policy": "origin", "title": "Referrer Policy is set to 'origin'", "test_expansion": [{"origin": "*", "name": "generic", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "*", "subresource": "*"}], "name": "origin", "description": "Check that all subresources in all casses get only the origin portion of the referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin", "referrer_policy": "same-origin", "title": "Referrer Policy is set to 'same-origin'", "test_expansion": [{"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-secure-default", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "*", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "omitted", "source_protocol": "*", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-origin", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "*", "subresource": "*"}], "name": "same-origin", "description": "Check that cross-origin subresources get no referrer information and same-origin get the stripped referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin", "referrer_policy": "origin-when-cross-origin", "title": "Referrer Policy is set to 'origin-when-cross-origin'", "test_expansion": [{"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-secure-default", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-upgrade", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-downgrade", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-origin-insecure", "target_protocol": "*", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "origin", "source_protocol": "*", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-origin", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "*", "subresource": "*"}], "name": "origin-when-cross-origin", "description": "Check that cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin", "referrer_policy": "strict-origin", "title": "Referrer Policy is set to 'strict-origin'", "test_expansion": [{"origin": "*", "name": "insecure-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "secure-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "https", "subresource": "*"}], "name": "strict-origin", "description": "Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin", "referrer_policy": "strict-origin-when-cross-origin", "title": "Referrer Policy is set to 'strict-origin-when-cross-origin'", "test_expansion": [{"origin": "same-origin", "name": "same-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "http", "subresource": "*"}, {"origin": "same-origin", "name": "same-insecure", "target_protocol": "http", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-insecure", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "upgrade-protocol", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "http", "subresource": "*"}, {"origin": "*", "name": "downgrade-protocol", "target_protocol": "http", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "omitted", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-secure", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "https", "subresource": "*"}, {"origin": "same-origin", "name": "same-secure", "target_protocol": "https", "expansion": "override", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "swap-origin-redirect", "referrer_url": "origin", "source_protocol": "https", "subresource": "*"}, {"origin": "cross-origin", "name": "cross-secure", "target_protocol": "https", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "origin", "source_protocol": "https", "subresource": "*"}], "name": "strict-origin-when-cross-origin", "description": "Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL."}, {"specification_url": "https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url", "referrer_policy": "unsafe-url", "title": "Referrer Policy is set to 'unsafe-url'", "test_expansion": [{"origin": "*", "name": "generic", "target_protocol": "*", "expansion": "default", "delivery_method": ["http-rp", "meta-referrer", "attr-referrer"], "redirection": "*", "referrer_url": "stripped-referrer", "source_protocol": "*", "subresource": "*"}], "name": "unsafe-url", "description": "Check that all sub-resources get the stripped referrer URL."}], "referrer_policy_schema": [null, "no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "origin-when-cross-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url"], "excluded_tests": [{"origin": "cross-origin", "name": "cross-origin-workers", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": "worker-request"}, {"origin": "*", "name": "upgraded-protocol-workers", "target_protocol": "https", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "http", "subresource": "worker-request"}, {"origin": "*", "name": "mixed-content-insecure-subresources", "target_protocol": "http", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "https", "subresource": "*"}, {"origin": "*", "name": "elements-not-supporting-attr-referrer", "target_protocol": "*", "expansion": "*", "delivery_method": ["attr-referrer"], "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": ["xhr-request", "worker-request", "fetch-request"]}, {"origin": "*", "name": "elements-not-supporting-rel-noreferrer", "target_protocol": "*", "expansion": "*", "delivery_method": ["rel-noreferrer"], "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": ["iframe-tag", "img-tag", "script-tag", "xhr-request", "worker-request", "fetch-request", "area-tag"]}, {"origin": "*", "name": "area-tag", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "*", "subresource": "area-tag"}, {"origin": "*", "name": "worker-requests-with-swap-origin-redirect", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "swap-origin-redirect", "referrer_url": "*", "source_protocol": "*", "subresource": ["worker-request"]}, {"origin": "*", "name": "overhead-for-redirection", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": ["keep-origin-redirect", "swap-origin-redirect"], "referrer_url": "*", "source_protocol": "*", "subresource": ["a-tag", "area-tag"]}, {"origin": "*", "name": "source-https-unsupported-by-web-platform-tests-runners", "target_protocol": "*", "expansion": "*", "delivery_method": "*", "redirection": "*", "referrer_url": "*", "source_protocol": "https", "subresource": "*"}]};
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.keep-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.keep-origin-redirect.http.html
new file mode 100644
index 0000000..aa2b89c
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.no-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.no-redirect.http.html
new file mode 100644
index 0000000..3d35d85
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.swap-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.swap-origin-redirect.http.html
new file mode 100644
index 0000000..6185377
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/script-tag/cross-insecure.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..ad88c2d
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..62cfcfb
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..867f86f
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.keep-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.keep-origin-redirect.http.html
new file mode 100644
index 0000000..6466b7f
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.no-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.no-redirect.http.html
new file mode 100644
index 0000000..a86dca2
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.swap-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.swap-origin-redirect.http.html
new file mode 100644
index 0000000..6936906
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/script-tag/same-insecure.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..81f8677
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..79b6a08
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..0517a5d
--- /dev/null
+++ b/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin-when-cross-origin'</title>
+    <meta name="description" content="Check that a priori insecure subresource gets no referrer information. Otherwise, cross-origin subresources get the origin portion of the referrer URL and same-origin get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin-when-cross-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..966f10b
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
new file mode 100644
index 0000000..3001dbf
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..308d22d
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..5e202a4
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..437c1a50
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..adb575e
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..6ee4480
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
new file mode 100644
index 0000000..1e97bb8
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..bf2a213
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..e409624
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..d968e80
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..9b6ea47
--- /dev/null
+++ b/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'strict-origin'</title>
+    <meta name="description" content="Check that non a priori insecure subresource gets only the origin portion of the referrer URL. A priori insecure subresource gets no referrer information.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin">
+    <meta name="assert" content="The referrer URL is origin when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "strict-origin",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "origin"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..edfdd5a
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..19bb254
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..4521600
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..9c4d681
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..2c445f1
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..384d6b4
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/cross-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..3fae7fd
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..6ad3c31
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..5976b8f
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-http/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
new file mode 100644
index 0000000..6171619
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html
new file mode 100644
index 0000000..7c900a7
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
new file mode 100644
index 0000000..4b307df
--- /dev/null
+++ b/referrer-policy/unsafe-url/attr-referrer/same-origin/http-https/script-tag/generic.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is set to 'unsafe-url'</title>
+    <meta name="description" content="Check that all sub-resources get the stripped referrer URL.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-unsafe-url">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": "unsafe-url",
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..ce4ea6b
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
new file mode 100644
index 0000000..2dc068a
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..87e94c6
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..67f0f03
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..fcfa56d
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..52400d6
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/cross-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is cross-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "cross-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..3668aee
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
new file mode 100644
index 0000000..2cd7df0
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..3345a9e
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-http/script-tag/insecure-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an http
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "http",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
new file mode 100644
index 0000000..ceb5586
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.keep-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with keep-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "keep-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
new file mode 100644
index 0000000..3ea9074
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.no-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with no-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "no-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
new file mode 100644
index 0000000..5b89eb7
--- /dev/null
+++ b/referrer-policy/unset-referrer-policy/attr-referrer/same-origin/http-https/script-tag/upgrade-protocol.swap-origin-redirect.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by referrer-policy/generic/tools/generate.py using referrer-policy/generic/template/test.release.html.template. -->
+<html>
+  <head>
+    <title>Referrer-Policy: Referrer Policy is not explicitly defined</title>
+    <meta name="description" content="Check that referrer URL follows no-referrer-when-downgrade policy when no explicit Referrer Policy is set.">
+    <link rel="author" title="Kristijan Burnik" href="burnik@chromium.org">
+    <link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/#referrer-policies">
+    <meta name="assert" content="The referrer URL is stripped-referrer when a
+                                 document served over http requires an https
+                                 sub-resource via script-tag using the attr-referrer
+                                 delivery method with swap-origin-redirect and when
+                                 the target request is same-origin.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <!-- TODO(kristijanburnik): Minify and merge both: -->
+    <script src="/referrer-policy/generic/common.js"></script>
+    <script src="/referrer-policy/generic/referrer-policy-test-case.js?pipe=sub"></script>
+  </head>
+  <body>
+    <script>
+      ReferrerPolicyTestCase(
+        {
+          "referrer_policy": null,
+          "delivery_method": "attr-referrer",
+          "redirection": "swap-origin-redirect",
+          "origin": "same-origin",
+          "source_protocol": "http",
+          "target_protocol": "https",
+          "subresource": "script-tag",
+          "subresource_path": "/referrer-policy/generic/subresource/script.py",
+          "referrer_url": "stripped-referrer"
+        },
+        document.querySelector("meta[name=assert]").content,
+        new SanityChecker()
+      ).start();
+      </script>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/remote-playback/OWNERS b/remote-playback/OWNERS
new file mode 100644
index 0000000..b05a482
--- /dev/null
+++ b/remote-playback/OWNERS
@@ -0,0 +1 @@
+@mounirlamouri
diff --git a/remote-playback/idlharness.html b/remote-playback/idlharness.html
index bd9cbf6..274771a 100644
--- a/remote-playback/idlharness.html
+++ b/remote-playback/idlharness.html
@@ -19,20 +19,32 @@
 </pre>
 <script>
 "use strict"
-var idl_array = new IdlArray();
-function doTest(idl) {
-  idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
-  idl_array.add_idls(idl);
-  idl_array.add_objects({
-    HTMLVideoElement: [document.getElementById("media")],
-    RemotePlayback: [document.getElementById("media").remote]
-  });
-  idl_array.test();
-}
 
-promise_test(function() {
-  return fetch("/interfaces/remoteplayback.idl")
-      .then(response => response.text())
+promise_test(async function() {
+  var idl_array = new IdlArray();
+  function doTest([html, idl]) {
+    idl_array.add_untested_idls('interface Element {};');
+    idl_array.add_untested_idls(html, { only: [
+      'HTMLElement',
+      'HTMLOrSVGElement',
+      'GlobalEventHandlers',
+      'DocumentAndElementEventHandlers',
+      'ElementContentEditable',
+    ] });
+    idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
+    idl_array.add_idls(idl);
+    idl_array.add_objects({
+      HTMLVideoElement: [document.getElementById("media")],
+      RemotePlayback: [document.getElementById("media").remote]
+    });
+    idl_array.test();
+  }
+
+  return Promise.all(
+    [
+      "/interfaces/html.idl",
+      "/interfaces/remoteplayback.idl",
+    ].map(url => fetch(url).then(r => r.text())))
       .then(doTest);
 }, "Test driver");
 </script>
diff --git a/requestidlecallback/idlharness.html b/requestidlecallback/idlharness.html
index 4007d09..459ce36 100644
--- a/requestidlecallback/idlharness.html
+++ b/requestidlecallback/idlharness.html
@@ -7,7 +7,7 @@
 <script src="/resources/idlharness.js"></script>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global=Window, Exposed=Window]
 interface Window {
 };
 </pre>
diff --git a/resize-observer/eventloop.html b/resize-observer/eventloop.html
index 4ef1d3d..559f52a 100644
--- a/resize-observer/eventloop.html
+++ b/resize-observer/eventloop.html
@@ -129,7 +129,7 @@
         let t3 = document.createElement('div');
         resizers.push(t3);
         t2.appendChild(t3);
-        let shadow = t3.createShadowRoot();
+        let shadow = t3.attachShadow({ mode: "open" });
         let t4 = document.createElement('div');
         resizers.push(t4);
         shadow.appendChild(t4);
diff --git a/resource-timing/clear_resource_timing_functionality.html b/resource-timing/clear_resource_timing_functionality.html
new file mode 100644
index 0000000..eaf21b4
--- /dev/null
+++ b/resource-timing/clear_resource_timing_functionality.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the functionality of clearResourceTimings method in resource timing.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+    setup({ explicit_done: true });
+    const context = new PerformanceContext(performance);
+    function onload_test()
+    {
+        test_equals(context.getEntriesByType('resource').length, 4, 4 + ' resource timing entries should be stored in this page.');
+        context.clearResourceTimings();
+        test_equals(context.getEntriesByType('resource').length, 0, 'No resource timing entries should be stored after clearResourceTimings.');
+        done();
+    }
+</script>
+</head>
+<body onload=onload_test()>
+</body>
+</html>
diff --git a/resource-timing/resource-timing-tojson.html b/resource-timing/resource-timing-tojson.html
new file mode 100644
index 0000000..77094f4
--- /dev/null
+++ b/resource-timing/resource-timing-tojson.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+  const img_url = "resources/blue.png";
+  const img = document.createElement("img");
+  img.src = img_url;
+  window.onload = function() {
+    test(() => {
+      const entries = performance.getEntriesByType('resource');
+      assert_greater_than_equal(entries.length, 1);
+      const entry = entries[0];
+      assert_equals(typeof(entry.toJSON), 'function');
+      const json = entry.toJSON();
+      assert_equals(typeof(json), 'object');
+
+      const performanceResourceTimingKeys = [
+        'name',
+        'entryType',
+        'startTime',
+        'duration',
+        'initiatorType',
+        'nextHopProtocol',
+        'workerStart',
+        'redirectStart',
+        'fetchStart',
+        'domainLookupStart',
+        'domainLookupEnd',
+        'connectStart',
+        'connectEnd',
+        'secureConnectionStart',
+        'requestStart',
+        'responseStart',
+        'responseEnd',
+        'transferSize',
+        'encodedBodySize',
+        'decodedBodySize'
+      ];
+      for (const key of performanceResourceTimingKeys) {
+        assert_equals(json[key], entry[key],
+          `entry.toJSON().${key} should match entry.${key}`);
+      }
+    }, 'Test toJSON() in PerformanceResourceTiming');
+  };
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/resource-timing/resource_TAO_cross_origin_redirect_chain.html b/resource-timing/resource_TAO_cross_origin_redirect_chain.html
new file mode 100644
index 0000000..af3d31e
--- /dev/null
+++ b/resource-timing/resource_TAO_cross_origin_redirect_chain.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates resource timing information for a timing allowed cross-origin redirect chain.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+
+<script>
+    setup({explicit_done: true});
+    test_namespace('getEntriesByName');
+    const pageOrigin = document.location.host;
+    const crossOrigin = 'www.' + pageOrigin;
+
+    function onload_test()
+    {
+        const context = new PerformanceContext(performance);
+        const entries = context.getEntriesByName(document.getElementById('frameContext').src, 'resource');
+        test_equals(entries.length, 1, 'There should be one entry.');
+        const entry = entries[0];
+
+        test_greater_than(entry.redirectStart, 0, 'redirectStart > 0 in timing allowed cross-origin redirect.');
+        test_equals(entry.redirectStart, entry.startTime, 'redirectStart == startTime in timing allowed cross-origin redirect.');
+        test_greater_than(entry.redirectEnd, entry.redirectStart, 'redirectEnd > redirectStart in timing allowed cross-origin redirect.');
+        test_greater_or_equals(entry.fetchStart, entry.redirectEnd, 'fetchStart >= redirectEnd in timing allowed cross-origin redirect.');
+        done();
+    }
+</script>
+
+</head>
+<body>
+<iframe id="frameContext" src="" style="width: 250px; height: 250px;"></iframe>
+<script>
+    let destUrl = 'http://' + crossOrigin + '/resource-timing/resources/multi_redirect.py?';
+    destUrl += 'page_origin=' + 'http://' + pageOrigin;
+    destUrl += '&cross_origin=' + 'http://' + crossOrigin;
+    destUrl += '&timing_allow=1';
+    const frameContext = document.getElementById('frameContext');
+    frameContext.onload = onload_test;
+    frameContext.src = destUrl;
+</script>
+</body>
+</html>
diff --git a/resource-timing/resource_TAO_origin.htm b/resource-timing/resource_TAO_origin.htm
index dc5efa4..a0c0465 100644
--- a/resource-timing/resource_TAO_origin.htm
+++ b/resource-timing/resource_TAO_origin.htm
@@ -12,19 +12,12 @@
 <script>
 setup({explicit_done: true});
 
-// explicitly test the namespace before we start testing
-test_namespace("getEntriesByType");
+// Explicitly test the namespace before we start testing.
+test_namespace('getEntriesByType');
 
-var d;
-var iframe;
-var iframeBody;
-var image;
-var random = Math.random();
-
+let iframe;
 function setup_iframe() {
     iframe = document.getElementById('frameContext');
-    d = iframe.contentWindow.document;
-    iframeBody = d.body;
     iframe.addEventListener('load', onload_test, false);
 }
 function onload_test() {
@@ -32,15 +25,22 @@
       done();
       return;
     }
-    var context = new PerformanceContext(iframe.contentWindow.performance);
-    var entries = context.getEntriesByType('resource');
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
 
-    if(entries.length > 0) {
-        entry = entries[0];
-
-        test_not_equals((entry.redirectStart + entry.redirectEnd + entry.domainLookupStart + entry.domainLookupEnd + entry.connectStart + entry.connectEnd + entry.secureConnectionStart + entry.requestStart + entry.responseStart), 0, 'redirectStart, redirectEnd, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, and responseStart -- should NOT be all returned as 0 when the value of Timing-Allow-Origin is a case-sensitive match for the value of the origin of the current document and TAO algorithm passes');
-    }
-
+    test_equals(entries.length, 1, 'The iframe should have one resource timing entry.');
+    const entry = entries[0];
+    test_equals(entry.redirectStart, 0, 'redirectStart should be 0 in cross-origin request since no redirect.');
+    test_equals(entry.redirectEnd, 0, 'redirectEnd should be 0 in cross-origin request since no redirect.');
+    test_greater_than(entry.domainLookupStart, 0, 'domainLookupStart should not be 0 in timing-allow cross-origin request.');
+    test_greater_than(entry.domainLookupEnd, 0, 'domainLookupEnd should not be 0 in timing-allow cross-origin request.');
+    test_greater_than(entry.connectStart, 0, 'connectStart should not be 0 in timing-allow cross-origin request.');
+    test_greater_than(entry.connectEnd, 0, 'connectEnd should not be 0 in timing-allow cross-origin request.');
+    test_greater_than(entry.requestStart, 0, 'requestStart should not be 0 in timing-allow cross-origin request.');
+    test_greater_than(entry.responseStart, 0, 'responseStart should not be 0 in timing-allow cross-origin request.');
+    test_equals(entry.secureConnectionStart, 0, 'secureConnectionStart should be 0 in cross-origin request since no ssl!');
+    test_greater_than(entry.fetchStart, 0, 'fetchStart should not be 0 in timing-allow cross-origin request.');
+    test_greater_than(entry.responseEnd, 0, 'responseEnd should not be 0 in timing-allow cross-origin request.');
     done();
 }
 window.setup_iframe = setup_iframe;
diff --git a/resource-timing/resource_TAO_zero.htm b/resource-timing/resource_TAO_zero.htm
index 0fe16c0..a15f54d 100644
--- a/resource-timing/resource_TAO_zero.htm
+++ b/resource-timing/resource_TAO_zero.htm
@@ -13,18 +13,11 @@
 setup({explicit_done: true});
 
 // explicitly test the namespace before we start testing
-test_namespace("getEntriesByType");
+test_namespace('getEntriesByType');
 
-var d;
-var iframe;
-var iframeBody;
-var image;
-var random = Math.random();
-
+let iframe;
 function setup_iframe() {
     iframe = document.getElementById('frameContext');
-    d = iframe.contentWindow.document;
-    iframeBody = d.body;
     iframe.addEventListener('load', onload_test, false);
 }
 function onload_test() {
@@ -32,15 +25,21 @@
       done();
       return;
     }
-    var context = new PerformanceContext(iframe.contentWindow.performance);
-    var entries = context.getEntriesByType('resource');
-
-    if(entries.length > 0) {
-        entry = entries[0];
-
-        test_equals((entry.redirectStart + entry.redirectEnd + entry.domainLookupStart + entry.domainLookupEnd + entry.connectStart + entry.connectEnd + entry.secureConnectionStart + entry.requestStart + entry.responseStart), 0, "redirectStart, redirectEnd, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, and responseStart -- should be all returned as 0 when the HTTP response includes zero Timing-Allow-Origin header value and TAO algorithm fails");
-    }
-
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+    test_equals(entries.length, 1, 'There should be one resource timing entry.');
+    const entry = entries[0];
+    test_equals(entry.redirectStart, 0, 'redirectStart should be 0 in cross-origin request.');
+    test_equals(entry.redirectEnd, 0, 'redirectEnd should be 0 in cross-origin request.');
+    test_equals(entry.domainLookupStart, 0, 'domainLookupStart should be 0 in cross-origin request.');
+    test_equals(entry.domainLookupEnd, 0, 'domainLookupEnd should be 0 in cross-origin request.');
+    test_equals(entry.connectStart, 0, 'connectStart should be 0 in cross-origin request.');
+    test_equals(entry.connectEnd, 0, 'connectEnd should be 0 in cross-origin request.');
+    test_equals(entry.requestStart, 0, 'requestStart should be 0 in cross-origin request.');
+    test_equals(entry.responseStart, 0, 'responseStart should be 0 in cross-origin request.');
+    test_equals(entry.secureConnectionStart, 0, 'secureConnectionStart should be 0 in cross-origin request.');
+    test_greater_than(entry.fetchStart, 0, 'fetchStart should be greater than 0 in cross-origin request.');
+    test_greater_than(entry.responseEnd, 0, 'responseEnd should be greater than 0 in cross-origin request.');
     done();
 }
 window.setup_iframe = setup_iframe;
diff --git a/resource-timing/resource_connection_reuse.html b/resource-timing/resource_connection_reuse.html
index c918802..d2bcab9 100644
--- a/resource-timing/resource_connection_reuse.html
+++ b/resource-timing/resource_connection_reuse.html
@@ -11,12 +11,12 @@
 <script src="resources/webperftestharnessextension.js"></script>
 <script>
 setup({explicit_done: true});
-var iframe;
-var d;
-var body;
+let iframe;
+let d;
+let body;
 
-// explicitly test the namespace before we start testing
-test_namespace("getEntriesByType");
+// Explicitly test the namespace before we start testing.
+test_namespace('getEntriesByType');
 
 function setup_iframe() {
     iframe = document.getElementById('frameContext');
@@ -30,17 +30,17 @@
       done();
       return;
     }
-    var context = new PerformanceContext(iframe.contentWindow.performance);
-    var entries = context.getEntriesByType('resource');
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
 
-    // when a persistent connection is used, follow-on resources should be included as PerformanceResourceTiming objects
+    // When a persistent connection is used, follow-on resources should be included as PerformanceResourceTiming objects.
     test_equals(entries.length, 2, 'There should be 2 PerformanceEntries');
 
     if (entries.length >= 2) {
-        // when a persistent connection is used, for the resource that reuses the socket, connectStart and connectEnd should have the same value as fetchStart
-        var entry = entries[1];
-        test_equals(entry.fetchStart, entry.connectStart, "connectStart and fetchStart should be the same");
-        test_equals(entry.fetchStart, entry.connectEnd, "connectEnd and fetchStart should be the same");
+        // When a persistent connection is used, for the resource that reuses the socket, connectStart and connectEnd should have the same value as fetchStart.
+        const entry = entries[1];
+        test_equals(entry.fetchStart, entry.connectStart, 'connectStart and fetchStart should be the same');
+        test_equals(entry.fetchStart, entry.connectEnd, 'connectEnd and fetchStart should be the same');
     }
 
     done();
diff --git a/resource-timing/resource_dedicated_worker.html b/resource-timing/resource_dedicated_worker.html
new file mode 100644
index 0000000..6d27245
--- /dev/null
+++ b/resource-timing/resource_dedicated_worker.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing in dedicated workers</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<link rel="stylesheet" href="resources/resource_timing_test0.css" />
+<script>
+setup({explicit_done: true});
+const worker = new Worker("resources/worker_with_images.js");
+worker.onmessage = function(event) {
+  const context = new PerformanceContext(window.performance);
+  const entries = context.getEntriesByType('resource');
+  test_equals(entries.length, 6, "There should be six entries: 4 scripts, 1 stylesheet, and the worker itself");
+  done();
+}
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that resources requested by dedicated workers don't appear in the main document.</p>
+</body>
+</html>
diff --git a/resource-timing/resource_dynamic_insertion.html b/resource-timing/resource_dynamic_insertion.html
index c669a38..de3c3d0 100644
--- a/resource-timing/resource_dynamic_insertion.html
+++ b/resource-timing/resource_dynamic_insertion.html
@@ -10,27 +10,27 @@
 <script src="resources/webperftestharness.js"></script>
 <script src="resources/webperftestharnessextension.js"></script>
 <script>
-// explicitly test the namespace before we start testing
+// Explicitly test the namespace before we start testing.
 test_namespace("getEntriesByType");
 
 
-var iframe;
+let iframe;
 function setup_iframe() {
     iframe = document.getElementById('frameContext');
-    var d = iframe.contentWindow.document;
-    var body = d.createElement('body');
+    const d = iframe.contentWindow.document;
+    const body = d.createElement('body');
     d.getElementsByTagName('html')[0].appendChild(body);
 
-    var style = d.createElement('link');
+    const style = d.createElement('link');
     style.rel = 'stylesheet';
     style.href = 'resource_timing_test0.css';
     body.appendChild(style);
 
-    var image = d.createElement('img');
+    const image = d.createElement('img');
     image.src = 'resource_timing_test0.png';
     body.appendChild(image);
 
-    var subframe = d.createElement('iframe');
+    const subframe = d.createElement('iframe');
     subframe.src = 'inject_resource_test.html';
     body.appendChild(subframe);
 }
@@ -39,13 +39,13 @@
       done();
       return;
     }
-    var context = new PerformanceContext(iframe.contentWindow.performance);
-    var entries = context.getEntriesByType('resource');
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
 
-    var index = window.location.pathname.lastIndexOf('/');
-    var pathname = window.location.pathname.substring(0, index) + '/';
+    const index = window.location.pathname.lastIndexOf('/');
+    const pathname = window.location.pathname.substring(0, index) + '/';
 
-    var expected_entries = { };
+    let expected_entries = { };
     expected_entries[ pathname + 'resources/resource_timing_test0.css' ] = 'link',
     expected_entries[ pathname + 'resources/resource_timing_test0.png' ] = 'img',
     expected_entries[ pathname + 'resources/inject_resource_test.html' ] = 'iframe',
diff --git a/resource-timing/resource_ignore_data_url.html b/resource-timing/resource_ignore_data_url.html
new file mode 100644
index 0000000..f8ca2f1
--- /dev/null
+++ b/resource-timing/resource_ignore_data_url.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing ignores requests for data: URIs</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+let iframe;
+function setup_iframe() {
+    const iframe_content = '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAOTm7AAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="></img>';
+    iframe = document.getElementById('frameContext');
+    iframe.contentWindow.document.write(iframe_content);
+}
+function onload_test() {
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+    test_true(entries.length == 0, "entries.length == 0");
+}
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that resources with data: URIs aren't present in the Resource Timing buffer.</p>
+<div id="log"></div>
+<iframe id="frameContext" onload="onload_test();" src="resources/inject_resource_test.html"></iframe>
+</body>
+</html>
diff --git a/resource-timing/resource_ignore_failures.html b/resource-timing/resource_ignore_failures.html
new file mode 100644
index 0000000..fa9e411
--- /dev/null
+++ b/resource-timing/resource_ignore_failures.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing ignores failed resources</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+let iframe;
+function setup_iframe() {
+    const iframe_content = '<img src="resources/non-existing-file.png"></img>';
+    iframe = document.getElementById('frameContext');
+    iframe.contentWindow.document.write(iframe_content);
+}
+function onload_test() {
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+    test_true(entries.length == 0, "entries.length == 0");
+}
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that failed resources aren't present in the Resource Timing buffer.</p>
+<div id="log"></div>
+<iframe id="frameContext" onload="onload_test();" src="resources/inject_resource_test.html"></iframe>
+</body>
+</html>
diff --git a/resource-timing/resource_initiator_types.html b/resource-timing/resource_initiator_types.html
new file mode 100644
index 0000000..d593f13
--- /dev/null
+++ b/resource-timing/resource_initiator_types.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing initiator types</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({explicit_done: true, timeout: 30000});
+
+let background_loaded = false;
+let page_loaded = false;
+let ol_font_loaded = false;
+let ul_font_loaded = false;
+let xhr_loaded = false;
+let tests_run = false;
+let frameset_loaded = false;
+
+function check_finished() {
+    if (!ul_font_loaded) {
+        ul_font_loaded = check_font_loaded('ul');
+    }
+    if (!ol_font_loaded) {
+        ol_font_loaded = check_font_loaded('ol');
+    }
+    if (page_loaded && ol_font_loaded && ul_font_loaded && background_loaded && xhr_loaded && frameset_loaded) {
+        perform_test();
+    } else {
+        step_timeout(check_finished, 100);
+    }
+}
+
+function check_font_loaded(type) {
+    const width_var_name = 'original_width_' + type;
+    const element_var_name = 'element_' + type;
+    if (!this.hasOwnProperty(width_var_name)) {
+        const d = document.getElementById('frameContext').contentWindow.document;
+        const list = d.createElement(type);
+        const li = d.createElement('li');
+        li.innerHTML = 'width_test';
+        list.appendChild(li);
+        d.getElementsByTagName('body')[0].appendChild(list);
+        this[element_var_name] = list;
+        this[width_var_name] = li.offsetHeight;
+    }
+    return this[width_var_name] != this[element_var_name].offsetHeight;
+}
+
+function onload_test() {
+    page_loaded = true;
+
+    const image = document.createElement('img');
+    image.src = 'resources/blue.png?id=n1';
+    background_loaded = image.complete;
+    if (!background_loaded) {
+        image.onload = function() {
+            background_loaded = true;
+        }
+    }
+
+    step_timeout(check_finished, 100);
+}
+
+function frameset_onload() {
+    frameset_loaded = true;
+
+    step_timeout(check_finished, 100);
+}
+
+function perform_test() {
+    if (tests_run) {
+        return;
+    }
+    tests_run = true;
+    const context = new PerformanceContext(document.getElementById('frameContext').contentWindow.performance);
+    let entries = context.getEntriesByType('resource');
+
+    // check for frameset
+    if (document.getElementById('frameContext2') &&
+        document.getElementById('frameContext2').contentWindow) {
+        const context2 = new PerformanceContext(document.getElementById('frameContext2').contentWindow.performance);
+        entries = entries.concat(context2.getEntriesByType('resource'));
+    }
+
+    const index = window.location.pathname.lastIndexOf('/');
+    const pathname = window.location.pathname.substring(0, index) + '/resources/';
+    const font_pathname = window.location.pathname.substring(0, index - 15) + 'fonts/Ahem.ttf';
+
+    let expected_entries = {};
+    addEntryIfExists(entries, expected_entries, font_pathname, 'css');
+    addEntryIfExists(entries, expected_entries, pathname + 'resource_timing_test0.png', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'resource_timing_test0.png?id=srcset-srcset', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'resource_timing_test0.png?id=srcset-src', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'blank_page_green.htm', 'iframe');
+    addEntryIfExists(entries, expected_entries, pathname + 'blank_page_green.htm?id=frame', 'frame');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty_script.js', 'script');
+    addEntryIfExists(entries, expected_entries, pathname + 'resource_timing_test0.css?id=embed', 'embed');
+    addEntryIfExists(entries, expected_entries, pathname + 'resource_timing_test0.css?id=n1', 'css');
+    addEntryIfExists(entries, expected_entries, font_pathname + '?id=n1', 'css');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=cursor', 'css');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=1', 'css');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=2', 'css');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=async_xhr', 'xmlhttprequest');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=body', 'body');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=input', 'input');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=n1', 'css');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=object', 'object');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=video-poster', 'video');
+    addEntryIfExists(entries, expected_entries, '/media/test.mp4?id=video-src', 'video');
+    addEntryIfExists(entries, expected_entries, '/media/test.mp4?id=video-source', 'source');
+    addEntryIfExists(entries, expected_entries, '/media/test.ogv?id=video-source', 'source');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=video-track', 'track');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=audio-src', 'audio');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=audio-source-wav', 'source');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=audio-source-mpeg', 'source');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=audio-source-ogg', 'source');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=picture-source', 'source');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=picture-img', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=picture-notsupported-img', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=picture-img-src', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=picture-img-srcset', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=picture-99x-img-src', 'img');
+    addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=svg-image', 'image');
+    addEntryIfExists(entries, expected_entries, pathname + 'nested.css', 'link');
+    addEntryIfExists(entries, expected_entries, pathname + 'nested.css?id=prefetch', 'link');
+    addEntryIfExists(entries, expected_entries, pathname + 'nested.css?id=preload', 'link');
+    addEntryIfExists(entries, expected_entries, pathname + 'blank_page_green.htm?id=prerender', 'link');
+    addEntryIfExists(entries, expected_entries, pathname + 'manifest.json', 'link');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=beacon', 'beacon');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=fetch', 'fetch');
+    addEntryIfExists(entries, expected_entries, pathname + 'empty.py?favicon', 'link');
+    addEntryIfExists(entries, expected_entries, pathname + 'eventsource.py?id=eventsource', 'eventsource');
+
+    test_resource_entries(entries, expected_entries);
+    done();
+}
+
+function addEntryIfExists(entries, expected_entries, path, initiatorType) {
+    const url = window.location.protocol + "//" + window.location.host + path;
+
+    if (entries.find(function(entry) { return entry.name === url; })) {
+        expected_entries[path] = initiatorType;
+    }
+}
+
+window.on_test_body_created = check_finished;
+window.on_async_xhr_done = function() {
+    xhr_loaded = true;
+    check_finished();
+}
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that all of the initiator types are represented.</p>
+<div id="log"></div>
+<iframe id="frameContext" onload="onload_test();" src="resources/all_resource_types.htm" style="width: 250px; height: 250px;"></iframe>
+<iframe id="frameContext2" onload="frameset_onload();" src="resources/green_frame.htm" style="width: 250px; height: 250px;"></iframe>
+</body>
+</html>
diff --git a/resource-timing/resource_memory_cached.sub.html b/resource-timing/resource_memory_cached.sub.html
new file mode 100644
index 0000000..236f69f
--- /dev/null
+++ b/resource-timing/resource_memory_cached.sub.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing memory cached resources</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({explicit_done: true});
+let d;
+let iframe;
+let iframeBody;
+let count = 0;
+function onload_prep() {
+    iframe = document.getElementById('frameContext');
+    d = iframe.contentWindow.document;
+    iframeBody = d.body;
+
+    const image = d.createElement('img');
+    image.addEventListener('load', function() {
+        step_timeout(onload_test, 0); });
+    image.src = 'blue.png?id=cached';
+    iframeBody.appendChild(image);
+
+    const image2 = d.createElement('img');
+    image2.addEventListener('load', function() {
+        step_timeout(onload_test, 0); });
+    image2.src = 'blue.png?id=cached';
+    iframeBody.appendChild(image2);
+}
+
+function onload_test() {
+    ++count;
+    if (count < 2)
+        return;
+
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+    test_equals(entries.length, 1, "There should be only one entry");
+
+    const index = window.location.pathname.lastIndexOf('/');
+    const pathname = window.location.pathname.substring(0, index);
+    let expected_entries = {};
+    expected_entries[pathname + '/resources/blue.png?id=cached'] = 'img';
+    test_resource_entries(entries, expected_entries);
+    test_greater_than(entries[0].requestStart, 0, 'requestStart should be non-zero on the same-origin request');
+    test_greater_or_equals(entries[0].responseEnd, entries[0].startTime, 'responseEnd should not be before startTime');
+    test_greater_or_equals(entries[0].duration, 0, 'duration should not be negative');
+
+    context.clearResourceTimings();
+    start_crossorigin_test();
+}
+function start_crossorigin_test() {
+    const image3 = d.createElement('img');
+    image3.addEventListener("load", function() { step_timeout(finish_crossorigin_test, 0); });
+    image3.src = 'http://{{domains[www1]}}:{{ports[http][1]}}{{location[path]}}/../resources/blue.png?id=cached';
+    iframeBody.appendChild(image3);
+}
+function finish_crossorigin_test() {
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+    test_equals(entries.length, 1, 'There should be one entry in second test');
+    test_true(entries[0].name.startsWith('http://{{domains[www1]}}:{{ports[http][1]}}'), 'Entry name should start with cross-origin domain');
+    test_true(entries[0].name.endsWith('/resources/blue.png?id=cached'), 'Entry name should end with file name');
+    test_equals(entries[0].requestStart, 0, 'requestStart should be 0 on the cross-origin request');
+    done();
+}
+window.setup_iframe = () => {};
+window.addEventListener('load', onload_prep);
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that a memory cached resource appears in the buffer once.</p>
+<div id="log"></div>
+<iframe id="frameContext" src="resources/inject_resource_test.html"></iframe>
+<img src="resources/blue.png?id=cached"></img>
+<img src="http://{{domains[www1]}}:{{ports[http][1]}}{{location[path]}}/../resources/blue.png?id=cached"></img>
+</body>
+</html>
diff --git a/resource-timing/resource_redirects.html b/resource-timing/resource_redirects.html
new file mode 100644
index 0000000..606662a
--- /dev/null
+++ b/resource-timing/resource_redirects.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing redirect names</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+let iframe;
+function setup_iframe() {
+    const iframe_content =
+        '<link rel="stylesheet" href="/common/redirect.py?location=/resource-timing/resources/resource_timing_test0.css"></link>' +
+        '<img src="/common/redirect.py?location=/resource-timing/resources/blue.png"></img>' +
+        '<iframe src="/common/redirect.py?location=/resource-timing/resources/blank_page_green.htm"></iframe>' +
+        '<script src="/common/redirect.py?location=/resource-timing/resources/empty_script.js"></scr' + 'ipt>' +
+        '<scr' + 'ipt>' +
+        'const xhr = new XMLHttpRequest;' +
+        'xhr.open("GET", "/common/redirect.py?location=/resource-timing/resources/blank_page_green.htm?id=xhr", false);' +
+        'xhr.send();' +
+        '</scr' + 'ipt>';
+    iframe = document.getElementById('frameContext');
+    iframe.contentWindow.document.write(iframe_content);
+}
+function onload_test() {
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+
+    const index = window.location.pathname.lastIndexOf('resource-timing');
+    const pathname = window.location.pathname.substring(0, index) +
+        'common/redirect.py?location=/resource-timing/resources/';
+    let expected_entries = {};
+    expected_entries[pathname + 'resource_timing_test0.css'] = 'link';
+    expected_entries[pathname + 'blue.png'] = 'img';
+    expected_entries[pathname + 'blank_page_green.htm'] = 'iframe';
+    expected_entries[pathname + 'empty_script.js'] = 'script';
+    expected_entries[pathname + 'blank_page_green.htm?id=xhr'] = 'xmlhttprequest';
+
+    test_resource_entries(entries, expected_entries);
+}
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that redirects do not alter the URL.</p>
+<div id="log"></div>
+<iframe id="frameContext" onload="onload_test();" src="resources/inject_resource_test.html"></iframe>
+</body>
+</html>
diff --git a/resource-timing/resource_reparenting.html b/resource-timing/resource_reparenting.html
new file mode 100644
index 0000000..7d4947f
--- /dev/null
+++ b/resource-timing/resource_reparenting.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing reparenting elements</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+let iframe;
+function setup_iframe() {
+    iframe = document.getElementById('frameContext');
+    const d = iframe.contentWindow.document;
+    const iframeBody = d.createElement('body');
+
+    const move_to_parent = d.createElement('img');
+    move_to_parent.src = 'blue.png?id=move_to_parent';
+    iframeBody.appendChild(move_to_parent);
+    iframeBody.removeChild(move_to_parent);
+
+    const parentBody = document.getElementsByTagName('body')[0];
+    parentBody.appendChild(move_to_parent);
+
+    const move_to_child = document.createElement('img');
+    move_to_child.src = 'blue.png?id=move_to_child';
+    parentBody.appendChild(move_to_child);
+    parentBody.removeChild(move_to_child);
+    iframeBody.appendChild(move_to_child);
+}
+function onload_test() {
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+
+    const index = window.location.pathname.lastIndexOf('/');
+    const pathname = window.location.pathname.substring(0, index);
+    let expected_entries = {};
+    expected_entries[pathname + '/resources/blue.png?id=move_to_child'] = 'img';
+
+    test_resource_entries(entries, expected_entries);
+}
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that reparenting an element doesn't change the initiator document.</p>
+<div id="log"></div>
+<iframe id="frameContext" onload="onload_test();" src="resources/inject_resource_test.html"></iframe>
+</body>
+</html>
diff --git a/resource-timing/resource_script_types.html b/resource-timing/resource_script_types.html
new file mode 100644
index 0000000..898fa0c
--- /dev/null
+++ b/resource-timing/resource_script_types.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing script initiator types</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({explicit_done: true});
+let iframe;
+function setup_iframe() {
+    const iframe_content =
+        '<script src="empty_script.js?id=1"></scr' + 'ipt>' +
+        '<script src="empty_script.js?id=2" async></scr' + 'ipt>' +
+        '<script src="empty_script.js?id=3" async=false></scr' + 'ipt>' +
+        '<script src="empty_script.js?id=4" defer></scr' + 'ipt>' +
+        '<script>' +
+        'document.write("<script src=\\"empty_script.js?id=5\\"></scr" + "ipt>");' +
+        'const s1 = document.createElement("script");' +
+        's1.src = "empty_script.js?id=6";' +
+        'document.getElementsByTagName("head")[0].appendChild(s1);' +
+        'const s2 = document.createElement("script");' +
+        's2.src = "empty_script.js?id=7";' +
+        's2.async = true;' +
+        'document.getElementsByTagName("head")[0].appendChild(s2);' +
+        'const s3 = document.createElement("script");' +
+        's3.src = "empty_script.js?id=8";' +
+        's3.async = false;' +
+        'document.getElementsByTagName("head")[0].appendChild(s3);' +
+        'const s4 = document.createElement("script");' +
+        's4.src = "empty_script.js?id=9";' +
+        's4.defer = true;' +
+        'document.getElementsByTagName("head")[0].appendChild(s4);' +
+        '</scr' + 'ipt>';
+    iframe = document.getElementById('frameContext');
+    iframe.contentWindow.document.write(iframe_content);
+}
+function onload_test() {
+    const context = new PerformanceContext(iframe.contentWindow.performance);
+    const entries = context.getEntriesByType('resource');
+
+    const index = window.location.pathname.lastIndexOf('/');
+    const pathname = window.location.pathname.substring(0, index) +
+        '/resources/empty_script.js?id=';
+    let expected_entries = {};
+    expected_entries[pathname + '1'] = 'script';
+    expected_entries[pathname + '2'] = 'script';
+    expected_entries[pathname + '3'] = 'script';
+    expected_entries[pathname + '4'] = 'script';
+    expected_entries[pathname + '5'] = 'script';
+    expected_entries[pathname + '6'] = 'script';
+    expected_entries[pathname + '7'] = 'script';
+    expected_entries[pathname + '8'] = 'script';
+    expected_entries[pathname + '9'] = 'script';
+
+    test_resource_entries(entries, expected_entries);
+    done();
+}
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that all of the different types of script loads are reported with the correct initiator.</p>
+<div id="log"></div>
+<iframe id="frameContext" onload="onload_test();" src="resources/inject_resource_test.html"></iframe>
+</body>
+</html>
diff --git a/resource-timing/resource_timing.worker.js b/resource-timing/resource_timing.worker.js
new file mode 100644
index 0000000..92687f0
--- /dev/null
+++ b/resource-timing/resource_timing.worker.js
@@ -0,0 +1,64 @@
+importScripts("/resources/testharness.js");
+
+function check(initiatorType, protocol) {
+  let entries = performance.getEntries();
+  assert_equals(entries.length, 1);
+
+  assert_true(entries[0] instanceof PerformanceEntry);
+  assert_equals(entries[0].entryType, "resource");
+  assert_true(entries[0].startTime > 0);
+  assert_true(entries[0].duration > 0);
+
+  assert_true(entries[0] instanceof PerformanceResourceTiming);
+  assert_equals(entries[0].initiatorType, initiatorType);
+  assert_equals(entries[0].nextHopProtocol, protocol);
+}
+
+async_test(t => {
+  performance.clearResourceTimings();
+
+  // Fetch
+  fetch("resources/empty.js")
+  .then(r => r.blob())
+  .then(blob => {
+    check("fetch", "http/1.1");
+  })
+
+  // XMLHttpRequest
+  .then(() => {
+    return new Promise(resolve => {
+      performance.clearResourceTimings();
+      let xhr = new XMLHttpRequest();
+      xhr.onload = () => {
+        check("xmlhttprequest", "http/1.1");
+        resolve();
+      };
+      xhr.open("GET", "resources/empty.js");
+      xhr.send();
+    });
+  })
+
+  // Sync XMLHttpREquest
+  .then(() => {
+    performance.clearResourceTimings();
+    let xhr = new XMLHttpRequest();
+    xhr.open("GET", "resources/empty.js", false);
+    xhr.send();
+
+    check("xmlhttprequest", "http/1.1");
+  })
+
+  // ImportScripts
+  .then(() => {
+    performance.clearResourceTimings();
+    importScripts(["resources/empty.js"]);
+    check("other", "http/1.1");
+  })
+
+  // All done.
+  .then(() => {
+    t.done();
+  });
+}, "Performance Resouce Entries in workers");
+
+done();
diff --git a/resource-timing/resource_timing_TAO_cross_origin_redirect.html b/resource-timing/resource_timing_TAO_cross_origin_redirect.html
new file mode 100644
index 0000000..a91eaf8
--- /dev/null
+++ b/resource-timing/resource_timing_TAO_cross_origin_redirect.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the values in resource timing for a timing allowed cross-origin redirect.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+
+<script>
+    setup({explicit_done: true});
+    function onload_test() {
+        const context = new PerformanceContext(performance);
+        const entry = context.getEntriesByName(document.getElementById('frameContext').src, 'resource')[0];
+
+        test_greater_than(entry.redirectStart, 0, 'redirectStart should be greater than 0 in timing allowed cross-origin redirect.');
+        test_equals(entry.redirectStart, entry.startTime, 'redirectStart should be equal to startTime in timing allowed cross-origin redirect.');
+        test_greater_or_equals(entry.redirectEnd, entry.redirectStart, 'redirectEnd should be no less than redirectStart in timing allowed cross-origin redirect.');
+        test_greater_or_equals(entry.fetchStart, entry.redirectEnd, 'fetchStart should be no less than redirectEnd in timing allowed cross-origin redirect.');
+        done();
+    }
+</script>
+
+</head>
+<body>
+<iframe id="frameContext" src="" style="width: 250px; height: 250px;"></iframe>
+<script>
+    const destUrl = '/common/redirect.py?location=/resource-timing/resources/iframe_TAO_match_origin.html';
+
+    const frameContext = document.getElementById('frameContext');
+    frameContext.onload = onload_test;
+    frameContext.src = destUrl;
+</script>
+</body>
+</html>
diff --git a/resource-timing/resource_timing_buffer_full_when_populate_entries.html b/resource-timing/resource_timing_buffer_full_when_populate_entries.html
new file mode 100644
index 0000000..3e62b19
--- /dev/null
+++ b/resource-timing/resource_timing_buffer_full_when_populate_entries.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<title>This test validates the functionality of onresourcetimingbufferfull in resource timing.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+</head>
+<body onload=onload_test()>
+<script>
+    const context = new PerformanceContext(performance);
+    const bufferSize = 5;
+    context.setResourceTimingBufferSize(bufferSize);
+    let bufferFullCount = 0;
+    function buffer_full_callback() {
+        bufferFullCount++;
+    }
+    context.registerResourceTimingBufferFullCallback(buffer_full_callback);
+    // Scripts appended in JS to ensure setResourceTimingBufferSize is called before.
+    function appendScript(src) {
+        const script = document.createElement('script');
+        script.type = 'text/javascript';
+        script.src = src;
+        document.body.appendChild(script);
+    }
+    appendScript('resources/empty.js');
+    appendScript('resources/empty_script.js');
+    appendScript('resources/resource_timing_test0.js');
+    setup({ explicit_done: true });
+    function onload_test() {
+        test_equals(context.getEntriesByType('resource').length, bufferSize, 'There should only be |bufferSize| resource entries.');
+        test_equals(bufferFullCount, 1, 'onresourcetimingbufferfull should have been invoked once buffer is full.');
+        done();
+    }
+</script>
+</body>
+</html>
diff --git a/resource-timing/resource_timing_buffer_full_when_shrink_buffer_size.html b/resource-timing/resource_timing_buffer_full_when_shrink_buffer_size.html
new file mode 100644
index 0000000..e42c19d
--- /dev/null
+++ b/resource-timing/resource_timing_buffer_full_when_shrink_buffer_size.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the functionality of onresourcetimingbufferfull in resource timing.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+    const context = new PerformanceContext(performance);
+    let bufferFullCount = 0;
+    function buffer_full_call_back() {
+        bufferFullCount++;
+    }
+    context.registerResourceTimingBufferFullCallback(buffer_full_call_back);
+    setup({ explicit_done: true });
+    function onload_test() {
+        context.setResourceTimingBufferSize(3);
+        context.setResourceTimingBufferSize(0);
+        test_equals(context.getEntriesByType('resource').length, 4, 'There are 4 scripts, and setResourceTimingBufferSize does not reduce the size.');
+        test_equals(bufferFullCount, 0, 'onresourcetimingbufferfull should not be invoked during setResourceTimingBufferSize.');
+        done();
+    }
+</script>
+</head>
+<body onload=onload_test()>
+</body>
+</html>
diff --git a/resource-timing/resource_timing_cross_origin_redirect.html b/resource-timing/resource_timing_cross_origin_redirect.html
new file mode 100644
index 0000000..9342f5b
--- /dev/null
+++ b/resource-timing/resource_timing_cross_origin_redirect.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the values in resource timing for a cross-origin redirect.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+
+<script>
+    setup({explicit_done: true});
+    function onload_test() {
+        const context = new PerformanceContext(performance);
+        const entry = context.getEntriesByName(document.getElementById('frameContext').src, 'resource')[0];
+
+        test_equals(entry.redirectStart, 0, 'redirectStart should be 0 in cross-origin redirect.');
+        test_equals(entry.redirectEnd, 0, 'redirectEnd should be 0 in cross-origin redirect.');
+        test_equals(entry.domainLookupStart, 0, 'domainLookupStart should be 0 in cross-origin redirect.');
+        test_equals(entry.domainLookupEnd, 0, 'domainLookupEnd should be 0 in cross-origin redirect.');
+        test_equals(entry.connectStart, 0, 'connectStart should be 0 in cross-origin redirect.');
+        test_equals(entry.connectEnd, 0, 'connectEnd should be 0 in cross-origin redirect.');
+        test_equals(entry.requestStart, 0, 'requestStart should be 0 in cross-origin redirect.');
+        test_equals(entry.responseStart, 0, 'responseStart should be 0 in cross-origin redirect.');
+        test_equals(entry.secureConnectionStart, 0, 'secureConnectionStart should be 0 in cross-origin redirect.');
+        test_greater_than(entry.fetchStart, 0, 'fetchStart should be greater than 0 in cross-origin redirect.');
+        test_greater_than(entry.responseEnd, 0, 'responseEnd should be greater than 0 in cross-origin redirect.');
+        done();
+    }
+</script>
+</head>
+<body>
+<iframe id="frameContext" src="" style="width: 250px; height: 250px;"></iframe>
+<script>
+    let destUrl = '/common/redirect.py?location=';
+    // Add www to get a cross origin frame.
+    destUrl += 'http://www.' + document.location.host + '/resource-timing/resources/blank_page_green.htm';
+
+    const frameContext = document.getElementById('frameContext');
+    frameContext.onload = onload_test;
+    frameContext.src = destUrl;
+</script>
+</body>
+</html>
diff --git a/resource-timing/resource_timing_cross_origin_redirect_chain.html b/resource-timing/resource_timing_cross_origin_redirect_chain.html
new file mode 100644
index 0000000..2a7b2f7
--- /dev/null
+++ b/resource-timing/resource_timing_cross_origin_redirect_chain.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates resource timing information for a cross-origin redirect chain.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+
+<script>
+    setup({explicit_done: true});
+    test_namespace('getEntriesByName');
+    const pageOrigin = document.location.host;
+    const crossOrigin = 'www.' + pageOrigin;
+
+    function onload_test()
+    {
+        const context = new PerformanceContext(performance);
+        const entries = context.getEntriesByName(document.getElementById('frameContext').src, 'resource');
+        test_equals(entries.length, 1, 'There should be one entry.');
+        const entry = entries[0];
+
+        test_equals(entry.redirectStart, 0, 'redirectStart == 0 in cross-origin redirect.');
+        test_equals(entry.redirectEnd, 0, 'redirectEnd == 0 in cross-origin redirect.');
+        test_greater_than(entry.fetchStart, 0, 'fetchStart > 0 in cross-origin redirect.');
+        test_equals(entry.startTime, entry.fetchStart, 'startTime == fetchStart in cross-origin redirect.');
+        done();
+    }
+</script>
+
+</head>
+<body>
+<iframe id="frameContext" src="" style="width: 250px; height: 250px;"></iframe>
+<script>
+    let destUrl = 'http://' + crossOrigin + '/resource-timing/resources/multi_redirect.py?';
+    destUrl += 'page_origin=' + 'http://' + pageOrigin;
+    destUrl += '&cross_origin=' + 'http://' + crossOrigin;
+    const frameContext = document.getElementById('frameContext');
+    frameContext.onload = onload_test;
+    frameContext.src = destUrl;
+</script>
+</body>
+</html>
diff --git a/resource-timing/resource_timing_same_origin_redirect.html b/resource-timing/resource_timing_same_origin_redirect.html
new file mode 100644
index 0000000..d9fbf94
--- /dev/null
+++ b/resource-timing/resource_timing_same_origin_redirect.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the values of the redirectStart/End in resource timing for a same-origin resource redirect.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+
+<script>
+    setup({explicit_done: true});
+    function onload_test() {
+        const context = new PerformanceContext(performance);
+        const entry = context.getEntriesByName(document.getElementById('frameContext').src, 'resource')[0];
+
+        test_greater_than(entry.redirectStart, 0, 'redirectStart should be greater than 0 in same-origin redirect.');
+        test_equals(entry.redirectStart, entry.startTime, 'redirectStart should be equal to startTime in same-origin redirect.');
+        test_noless_than(entry.redirectEnd, entry.redirectStart, 'redirectEnd should be no less than redirectStart in same-origin redirect.');
+        test_noless_than(entry.fetchStart, entry.redirectEnd, 'fetchStart should be no less than redirectEnd in same-origin redirect.');
+        done();
+    }
+</script>
+
+</head>
+<body>
+<iframe id="frameContext" src="" style="width: 250px; height: 250px;"></iframe>
+<script>
+    let destUrl = '/common/redirect.py';
+    destUrl += '?location=/resource-timing/resources/blank_page_green.htm';
+
+    const frameContext = document.getElementById('frameContext');
+    frameContext.onload = onload_test;
+    frameContext.src = destUrl;
+</script>
+</body>
+</html>
diff --git a/resource-timing/resource_timing_store_and_clear_during_callback.html b/resource-timing/resource_timing_store_and_clear_during_callback.html
new file mode 100644
index 0000000..218fc0c
--- /dev/null
+++ b/resource-timing/resource_timing_store_and_clear_during_callback.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head onload>
+<meta charset="utf-8" />
+<title>This test validates the behavior of read and clear operation in onresourcetimingbufferfull callback of resource timing.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+</head>
+<body onload=onload_test()>
+<script>
+    const context = new PerformanceContext(performance);
+    const resource_timing_buffer_size = 1;
+    let global_buffer = [];
+    function store_and_clear() {
+        const entryList = context.getEntriesByType('resource');
+        entryList.forEach(function (entry) {
+            global_buffer.push(entry);
+        });
+        context.clearResourceTimings();
+    }
+    context.registerResourceTimingBufferFullCallback(store_and_clear);
+    context.setResourceTimingBufferSize(resource_timing_buffer_size);
+    // Scripts appended in JS to ensure setResourceTimingBufferSize is called before.
+    function appendScript(src) {
+        const script = document.createElement('script');
+        script.type = 'text/javascript';
+        script.src = src;
+        document.body.appendChild(script);
+    }
+    appendScript('resources/empty.js');
+    appendScript('resources/empty_script.js');
+    appendScript('resources/resource_timing_test0.js');
+    setup({ explicit_done: true });
+    function onload_test() {
+        test_equals(context.getEntriesByType('resource').length, 0, 'No entry should be stored in resource timing buffer since its cleared once an item arrived.');
+        // The entry for empty.js must not be in the global buffer, but all others should be.
+        test_equals(global_buffer.length, 6, '6 resource timing entries should be moved to global buffer.');
+        const index = window.location.pathname.lastIndexOf('resource-timing');
+        const pathname = window.location.pathname.substring(0, index);
+        let expected_entries = {};
+        expected_entries[pathname + 'resources/testharness.js'] = 'script';
+        expected_entries[pathname + 'resources/testharnessreport.js'] = 'script';
+        expected_entries[pathname + 'resource-timing/resources/webperftestharness.js'] = 'script';
+        expected_entries[pathname + 'resource-timing/resources/webperftestharnessextension.js'] = 'script';
+        expected_entries[pathname + 'resource-timing/resources/empty_script.js'] = 'script';
+        expected_entries[pathname + 'resource-timing/resources/resource_timing_test0.js'] = 'script';
+        test_resource_entries(global_buffer, expected_entries);
+        done();
+    }
+</script>
+</body>
+</html>
diff --git a/resource-timing/resources/TAOResponse.py b/resource-timing/resources/TAOResponse.py
index cc8fa5f..dcd75a4 100644
--- a/resource-timing/resources/TAOResponse.py
+++ b/resource-timing/resources/TAOResponse.py
@@ -20,19 +20,19 @@
     # space seperated list of origin and wildcard, fail
         response.headers.set('Timing-Allow-Origin', (origin + ' *'))
     elif tao == 'multi':
-    # more than one TAO values, seperated by common, pass
+    # more than one TAO values, seperated by comma, pass
         response.headers.set('Timing-Allow-Origin', origin)
         response.headers.append('Timing-Allow-Origin', '*')
     elif tao == 'match_origin':
-    # contains a match of origin, seperated by common, pass
+    # contains a match of origin, seperated by comma, pass
         response.headers.set('Timing-Allow-Origin', origin)
         response.headers.append('Timing-Allow-Origin', "fake")
     elif tao == 'match_wildcard':
-    # contains a wildcard, seperated by common, pass
+    # contains a wildcard, seperated by comma, pass
         response.headers.set('Timing-Allow-Origin', "fake")
         response.headers.append('Timing-Allow-Origin', '*')
     elif tao == 'uppercase':
     # non-case-sensitive match for origin, fail
         response.headers.set('Timing-Allow-Origin', origin.upper())
     else:
-        pass
\ No newline at end of file
+        pass
diff --git a/resource-timing/resources/all_resource_types.htm b/resource-timing/resources/all_resource_types.htm
new file mode 100644
index 0000000..bc7101c
--- /dev/null
+++ b/resource-timing/resources/all_resource_types.htm
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+        <title>All Resource Types Test Page</title>
+        <link rel="shortcut icon" href="empty.py?favicon">
+    </head>
+    <body background='blue.png?id=body'>
+        <script>
+            if (window.parent.hasOwnProperty('on_test_body_created'))
+                window.parent.on_test_body_created();
+        </script>
+        <link rel="stylesheet" href="nested.css"></link>
+        <link rel="prefetch" href="nested.css?id=prefetch"></link>
+        <link rel="preload" href="nested.css?id=preload" as="style"></link>
+        <link rel="prerender" href="blank_page_green.htm?id=prerender"></link>
+        <link rel="manifest" href="manifest.json"></link>
+        <img src="resource_timing_test0.png"></img>
+        <img src="resource_timing_test0.png?id=srcset-src"
+            srcset="resource_timing_test0.png?id=srcset-srcset 67w"
+            sizes="67px"></img>
+        <iframe src="blank_page_green.htm" width="100px" height="100px"></iframe>
+        <script src="empty_script.js"></script>
+        <script>
+            var async_xhr = new XMLHttpRequest;
+            async_xhr.open('GET', 'blue.png?id=async_xhr', true);
+            async_xhr.onreadystatechange = function() {
+                if (async_xhr.readyState == 4 && async_xhr.status == 200 && parent.hasOwnProperty('on_async_xhr_done'))
+                    parent.on_async_xhr_done();
+            }
+            async_xhr.send();
+
+            if (window.navigator && navigator.sendBeacon) {
+                navigator.sendBeacon('empty.py?id=beacon');
+            }
+
+            if (window.fetch) {
+                fetch('empty.py?id=fetch');
+            }
+
+            if (window.EventSource) {
+                var evtSource = new EventSource('eventsource.py?id=eventsource');
+            }
+        </script>
+        <style>
+            @font-face {
+                font-family: remoteFontAhem;
+                src: url('/fonts/Ahem.ttf');
+            }
+            iframe {
+                background: url('blue.png?id=1');
+            }
+            body {
+                cursor: url('blue.png?id=cursor'), pointer;
+            }
+            ul {
+                font-family: remoteFontAhem;
+                list-style-image: url('blue.png?id=2');
+            }
+        </style>
+        <ul>
+            <li>Test</li>
+        </ul>
+        <ol>
+            <li>Test</li>
+        </ol>
+        <embed src="resource_timing_test0.css?id=embed" type="text/css"></embed>
+        <input type="image" src="blue.png?id=input"></input>
+        <object type="image/png" data="blue.png?id=object"></object>
+        <video poster="blue.png?id=video-poster"></video>
+        <video src="/media/test.mp4?id=video-src" autoplay="true"></video>
+        <video autoplay="true">
+            <source src="/media/test.mp4?id=video-source" type="video/mp4">
+            <source src="/media/test.ogv?id=video-source" type="video/ogg">
+            <track kind="subtitles" src="empty.py?id=video-track" srclang="en" default>
+        </video>
+        <audio src="empty.py?id=audio-src"></audio>
+        <audio>
+            <source src="empty.py?id=audio-source-wav" type="audio/wav" />
+            <source src="empty.py?id=audio-source-mpeg" type="audio/mpeg" />
+            <source src="empty.py?id=audio-source-ogg" type="audio/ogg" />
+        </audio>
+        <svg width=200 height=200
+            xmlns="http://www.w3.org/2000/svg"
+            xmlns:xlink="http://www.w3.org/1999/xlink">
+            <image href="blue.png?id=svg-image" height="200" width="200"/>
+        </svg>
+        <picture>
+            <source srcset="blue.png?id=picture-source" type="image/png" />
+            <img src="blue.png?id=picture-img" />
+        </picture>
+        <picture>
+            <source srcset="blue.png?id=picture-notsupported-source" type="image/notsupported" />
+            <img src="blue.png?id=picture-notsupported-img" />
+        </picture>
+        <picture>
+            <img src="blue.png?id=picture-img-src"
+                srcset="blue.png?id=picture-img-srcset"
+                sizes="67px"></img>
+        </picture>
+        <picture>
+            <img src="blue.png?id=picture-99x-img-src"
+                srcset="blue.png?id=picture-99x-img-srcset 99x"
+                sizes="67px"></img>
+        </picture>
+    </body>
+</html>
diff --git a/resource-timing/resources/blank_page_green.htm b/resource-timing/resources/blank_page_green.htm
new file mode 100644
index 0000000..b8a1947
--- /dev/null
+++ b/resource-timing/resources/blank_page_green.htm
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+        <title>Green Test Page</title>
+    </head>
+    <body style="background-color:#00FF00;">
+        <h1>Placeholder</h1>
+    </body>
+</html>
diff --git a/resource-timing/resources/empty.js b/resource-timing/resources/empty.js
new file mode 100644
index 0000000..3b44754
--- /dev/null
+++ b/resource-timing/resources/empty.js
@@ -0,0 +1 @@
+/* Nothing here */
diff --git a/resource-timing/resources/empty.py b/resource-timing/resources/empty.py
new file mode 100644
index 0000000..e5ccfbe
--- /dev/null
+++ b/resource-timing/resources/empty.py
@@ -0,0 +1,3 @@
+def main(request, response):
+    response.headers.set("Content-Type", "text/plain")
+    return ""
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/resource-timing/resources/empty_script.js
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to resource-timing/resources/empty_script.js
diff --git a/resource-timing/resources/eventsource.py b/resource-timing/resources/eventsource.py
new file mode 100644
index 0000000..5095ea9
--- /dev/null
+++ b/resource-timing/resources/eventsource.py
@@ -0,0 +1,3 @@
+def main(request, response):
+    response.headers.set("Content-Type", "text/event-stream")
+    return ""
diff --git a/resource-timing/resources/fake_responses.py b/resource-timing/resources/fake_responses.py
index 9f2e43d..f716938 100644
--- a/resource-timing/resources/fake_responses.py
+++ b/resource-timing/resources/fake_responses.py
@@ -1,4 +1,4 @@
-# XMLHttpRequest/resources/conditional.py -- to fake a 304 response
+# /xhr/resources/conditional.py -- to fake a 304 response
 
 def main(request, response):
     tag = request.GET.first("tag", None)
diff --git a/resource-timing/resources/green_frame.htm b/resource-timing/resources/green_frame.htm
new file mode 100644
index 0000000..f3f0324
--- /dev/null
+++ b/resource-timing/resources/green_frame.htm
@@ -0,0 +1,7 @@
+<html>
+  <head>
+    <frameset>
+      <frame src="blank_page_green.htm?id=frame">
+    </frameset>
+  </head>
+</html>
diff --git a/resource-timing/resources/multi_redirect.py b/resource-timing/resources/multi_redirect.py
new file mode 100644
index 0000000..d1802a1
--- /dev/null
+++ b/resource-timing/resources/multi_redirect.py
@@ -0,0 +1,40 @@
+def main(request, response):
+    """Handler that causes multiple redirections.
+    The request has two mandatory and one optional query parameters:
+    page_origin - The page origin, used for redirection and to set TAO. This is a mandatory parameter.
+    cross_origin - The cross origin used to make this a cross-origin redirect. This is a mandatory parameter.
+    timing_allow - Whether TAO should be set or not in the redirect chain. This is an optional parameter. Default: not set.
+    Note that |step| is a parameter used internally for the multi-redirect. It's the step we're at in the redirect chain.
+    """
+    step = 1
+    if "step" in request.GET:
+        try:
+            step = int(request.GET.first("step"))
+        except ValueError:
+            pass
+
+    page_origin = request.GET.first("page_origin")
+    cross_origin = request.GET.first("cross_origin")
+    timing_allow = "0"
+    if "timing_allow" in request.GET:
+        timing_allow = request.GET.first("timing_allow")
+
+    redirect_url = "/resource-timing/resources/multi_redirect.py?"
+    redirect_url += "page_origin=" + page_origin
+    redirect_url += "&cross_origin=" + cross_origin
+    redirect_url += "&timing_allow=" + timing_allow
+    redirect_url += "&step="
+
+    if step == 1:
+        redirect_url = cross_origin + redirect_url + "2"
+        if timing_allow != "0":
+            response.headers.set("timing-allow-origin", page_origin)
+    elif step == 2:
+        redirect_url = page_origin + redirect_url + "3"
+        if timing_allow != "0":
+            response.headers.set("timing-allow-origin", page_origin)
+    else:
+        redirect_url = page_origin + "/resource-timing/resources/blank_page_green.htm"
+
+    response.status = 302
+    response.headers.set("Location", redirect_url)
diff --git a/resource-timing/resources/nested.css b/resource-timing/resources/nested.css
new file mode 100644
index 0000000..90d61b0
--- /dev/null
+++ b/resource-timing/resources/nested.css
@@ -0,0 +1,10 @@
+@import "resource_timing_test0.css?id=n1";
+
+@font-face {
+    font-family: remoteFont;
+    src: url('/fonts/Ahem.ttf?id=n1');
+}
+ol {
+    font-family: remoteFont;
+    list-style-image: url('blue.png?id=n1');
+}
diff --git a/resource-timing/resources/worker_with_images.js b/resource-timing/resources/worker_with_images.js
new file mode 100644
index 0000000..2d7688f
--- /dev/null
+++ b/resource-timing/resources/worker_with_images.js
@@ -0,0 +1,21 @@
+let numComplete = 0;
+
+function checkDone() {
+  ++numComplete;
+  if (numComplete == 2) {
+    postMessage('');
+  }
+}
+
+function makeRequest(request) {
+  var xhr = new XMLHttpRequest;
+  xhr.open('get', request, true);
+  xhr.onreadystatechange = function() {
+    if (xhr.readyState == 4) {
+      checkDone();
+    }
+  }
+  xhr.send();
+}
+makeRequest('blue.png');
+makeRequest('resource_timing_test0.png');
diff --git a/resource-timing/test_resource_timing.js b/resource-timing/test_resource_timing.js
index 51a3d2c..8e729e0 100644
--- a/resource-timing/test_resource_timing.js
+++ b/resource-timing/test_resource_timing.js
@@ -183,7 +183,9 @@
     });
 
     t["timing_attrs"].step(function test() {
-        var actual = window.performance.getEntriesByName(expected.name)[0];
+        const entries = window.performance.getEntriesByName(expected.name);
+        assert_equals(entries.length, 1, 'There should be a single matching entry');
+        const actual = entries[0];
 
         // Debugging bug 1263428
         // Feel free to remove/overwrite this piece of code
@@ -191,18 +193,18 @@
             assert_true(false, "actual: "+JSON.stringify(actual));
         }
 
-        assert_equals(actual.redirectStart, 0, "redirectStart time");
-        assert_equals(actual.redirectEnd, 0, "redirectEnd time");
+        assert_equals(actual.redirectStart, 0, 'redirectStart should be 0');
+        assert_equals(actual.redirectEnd, 0, 'redirectEnd should be 0');
         assert_true(actual.secureConnectionStart == undefined ||
-                    actual.secureConnectionStart == 0, "secureConnectionStart time");
-        assert_equals(actual.fetchStart, actual.startTime, "fetchStart is equal to startTime");
-        assert_greater_than_equal(actual.domainLookupStart, actual.fetchStart, "domainLookupStart after fetchStart");
-        assert_greater_than_equal(actual.domainLookupEnd, actual.domainLookupStart, "domainLookupEnd after domainLookupStart");
-        assert_greater_than_equal(actual.connectStart, actual.domainLookupEnd, "connectStart after domainLookupEnd");
-        assert_greater_than_equal(actual.connectEnd, actual.connectStart, "connectEnd after connectStart");
-        assert_greater_than_equal(actual.requestStart, actual.connectEnd, "requestStart after connectEnd");
-        assert_greater_than_equal(actual.responseStart, actual.requestStart, "responseStart after requestStart");
-        assert_greater_than_equal(actual.responseEnd, actual.responseStart, "responseEnd after responseStart");
+                    actual.secureConnectionStart == 0, 'secureConnectionStart should be 0 or undefined');
+        assert_equals(actual.fetchStart, actual.startTime, 'fetchStart is equal to startTime');
+        assert_greater_than_equal(actual.domainLookupStart, actual.fetchStart, 'domainLookupStart after fetchStart');
+        assert_greater_than_equal(actual.domainLookupEnd, actual.domainLookupStart, 'domainLookupEnd after domainLookupStart');
+        assert_greater_than_equal(actual.connectStart, actual.domainLookupEnd, 'connectStart after domainLookupEnd');
+        assert_greater_than_equal(actual.connectEnd, actual.connectStart, 'connectEnd after connectStart');
+        assert_greater_than_equal(actual.requestStart, actual.connectEnd, 'requestStart after connectEnd');
+        assert_greater_than_equal(actual.responseStart, actual.requestStart, 'responseStart after requestStart');
+        assert_greater_than_equal(actual.responseEnd, actual.responseStart, 'responseEnd after responseStart');
         this.done();
     });
 
diff --git a/resources/check-layout-th.js b/resources/check-layout-th.js
index 9b83df7..928b0cf 100644
--- a/resources/check-layout-th.js
+++ b/resources/check-layout-th.js
@@ -171,7 +171,6 @@
     }
     var nodes = document.querySelectorAll(selectorList);
     nodes = Array.prototype.slice.call(nodes);
-    nodes.reverse();
     var checkedLayout = false;
     Array.prototype.forEach.call(nodes, function(node) {
         test(function(t) {
diff --git a/resources/chromium/chooser_service.mojom.js b/resources/chromium/chooser_service.mojom.js
index 30bba4b..fd344e7 100644
--- a/resources/chromium/chooser_service.mojom.js
+++ b/resources/chromium/chooser_service.mojom.js
@@ -2,19 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-
 'use strict';
 
 (function() {
-  var mojomId = 'device/usb/public/interfaces/chooser_service.mojom';
+  var mojomId = 'device/usb/public/mojom/chooser_service.mojom';
   if (mojo.internal.isMojomLoaded(mojomId)) {
     console.warn('The following mojom is loaded multiple times: ' + mojomId);
     return;
   }
   mojo.internal.markMojomLoaded(mojomId);
-
-  // TODO(yzshen): Define these aliases to minimize the differences between the
-  // old/new modes. Remove them when the old mode goes away.
   var bindings = mojo;
   var associatedBindings = mojo;
   var codec = mojo.internal;
@@ -25,13 +21,13 @@
       mojo.internal.exposeNamespace('device.mojom');
   if (mojo.config.autoLoadMojomDeps) {
     mojo.internal.loadMojomIfNecessary(
-        'device/usb/public/interfaces/device.mojom', 'device.mojom.js');
+        'device/usb/public/mojom/device.mojom', 'device.mojom.js');
   }
   var device_manager$ =
       mojo.internal.exposeNamespace('device.mojom');
   if (mojo.config.autoLoadMojomDeps) {
     mojo.internal.loadMojomIfNecessary(
-        'device/usb/public/interfaces/device_manager.mojom', 'device_manager.mojom.js');
+        'device/usb/public/mojom/device_manager.mojom', 'device_manager.mojom.js');
   }
 
 
@@ -66,7 +62,6 @@
         return err;
 
 
-    
     // validate UsbChooserService_GetPermission_Params.deviceFilters
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(device_manager$.UsbDeviceFilter), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -122,7 +117,6 @@
         return err;
 
 
-    
     // validate UsbChooserService_GetPermission_ResponseParams.result
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, device$.UsbDeviceInfo, true);
     if (err !== validator.validationError.NONE)
@@ -263,7 +257,7 @@
   }
 
   var UsbChooserService = {
-    name: 'device::mojom::UsbChooserService',
+    name: 'device.mojom.UsbChooserService',
     kVersion: 0,
     ptrClass: UsbChooserServicePtr,
     proxyClass: UsbChooserServiceProxy,
@@ -276,4 +270,4 @@
   exports.UsbChooserService = UsbChooserService;
   exports.UsbChooserServicePtr = UsbChooserServicePtr;
   exports.UsbChooserServiceAssociatedPtr = UsbChooserServiceAssociatedPtr;
-})();
\ No newline at end of file
+})();
diff --git a/resources/chromium/device.mojom.js b/resources/chromium/device.mojom.js
index ce5660c..435fc1f 100644
--- a/resources/chromium/device.mojom.js
+++ b/resources/chromium/device.mojom.js
@@ -2,19 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-
 'use strict';
 
 (function() {
-  var mojomId = 'device/usb/public/interfaces/device.mojom';
+  var mojomId = 'device/usb/public/mojom/device.mojom';
   if (mojo.internal.isMojomLoaded(mojomId)) {
     console.warn('The following mojom is loaded multiple times: ' + mojomId);
     return;
   }
   mojo.internal.markMojomLoaded(mojomId);
-
-  // TODO(yzshen): Define these aliases to minimize the differences between the
-  // old/new modes. Remove them when the old mode goes away.
   var bindings = mojo;
   var associatedBindings = mojo;
   var codec = mojo.internal;
@@ -25,7 +21,7 @@
       mojo.internal.exposeNamespace('mojo.common.mojom');
   if (mojo.config.autoLoadMojomDeps) {
     mojo.internal.loadMojomIfNecessary(
-        'mojo/common/string16.mojom', '../../../../mojo/common/string16.mojom.js');
+        'mojo/public/mojom/base/string16.mojom', '../../../../mojo/public/mojom/base/string16.mojom.js');
   }
 
 
@@ -212,14 +208,12 @@
 
 
 
-    
     // validate UsbEndpointInfo.direction
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 4, UsbTransferDirection);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbEndpointInfo.type
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 8, UsbTransferType);
     if (err !== validator.validationError.NONE)
@@ -297,14 +291,12 @@
 
 
 
-    
     // validate UsbAlternateInterfaceInfo.interfaceName
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, string16$.String16, true);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbAlternateInterfaceInfo.endpoints
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 8, new codec.PointerTo(UsbEndpointInfo), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -380,7 +372,6 @@
 
 
 
-    
     // validate UsbInterfaceInfo.alternates
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 8, new codec.PointerTo(UsbAlternateInterfaceInfo), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -455,14 +446,12 @@
 
 
 
-    
     // validate UsbConfigurationInfo.configurationName
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, string16$.String16, true);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbConfigurationInfo.interfaces
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 8, new codec.PointerTo(UsbInterfaceInfo), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -552,7 +541,6 @@
         return err;
 
 
-    
     // validate UsbDeviceInfo.guid
     err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
     if (err !== validator.validationError.NONE)
@@ -570,21 +558,18 @@
 
 
 
-    
     // validate UsbDeviceInfo.manufacturerName
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 24, string16$.String16, true);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDeviceInfo.productName
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 32, string16$.String16, true);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDeviceInfo.serialNumber
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 40, string16$.String16, true);
     if (err !== validator.validationError.NONE)
@@ -592,7 +577,6 @@
 
 
 
-    
     // validate UsbDeviceInfo.configurations
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 48, 8, new codec.PointerTo(UsbConfigurationInfo), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -688,14 +672,12 @@
         return err;
 
 
-    
     // validate UsbControlTransferParams.type
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbControlTransferType);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbControlTransferParams.recipient
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 4, UsbControlTransferRecipient);
     if (err !== validator.validationError.NONE)
@@ -772,7 +754,6 @@
 
 
 
-    
     // validate UsbIsochronousPacket.status
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 8, UsbTransferStatus);
     if (err !== validator.validationError.NONE)
@@ -886,7 +867,6 @@
         return err;
 
 
-    
     // validate UsbDevice_Open_ResponseParams.error
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbOpenDeviceError);
     if (err !== validator.validationError.NONE)
@@ -1814,7 +1794,6 @@
         return err;
 
 
-    
     // validate UsbDevice_ControlTransferIn_Params.params
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, UsbControlTransferParams, false);
     if (err !== validator.validationError.NONE)
@@ -1877,16 +1856,14 @@
         return err;
 
 
-    
     // validate UsbDevice_ControlTransferIn_ResponseParams.status
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDevice_ControlTransferIn_ResponseParams.data
-    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
     if (err !== validator.validationError.NONE)
         return err;
 
@@ -1952,14 +1929,12 @@
         return err;
 
 
-    
     // validate UsbDevice_ControlTransferOut_Params.params
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, UsbControlTransferParams, false);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDevice_ControlTransferOut_Params.data
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -2028,7 +2003,6 @@
         return err;
 
 
-    
     // validate UsbDevice_ControlTransferOut_ResponseParams.status
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
     if (err !== validator.validationError.NONE)
@@ -2165,16 +2139,14 @@
         return err;
 
 
-    
     // validate UsbDevice_GenericTransferIn_ResponseParams.status
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDevice_GenericTransferIn_ResponseParams.data
-    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
     if (err !== validator.validationError.NONE)
         return err;
 
@@ -2241,7 +2213,6 @@
 
 
 
-    
     // validate UsbDevice_GenericTransferOut_Params.data
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -2308,7 +2279,6 @@
         return err;
 
 
-    
     // validate UsbDevice_GenericTransferOut_ResponseParams.status
     err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
     if (err !== validator.validationError.NONE)
@@ -2375,7 +2345,6 @@
 
 
 
-    
     // validate UsbDevice_IsochronousTransferIn_Params.packetLengths
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 4, codec.Uint32, false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -2443,14 +2412,12 @@
         return err;
 
 
-    
     // validate UsbDevice_IsochronousTransferIn_ResponseParams.data
-    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 1, codec.Uint8, true, [0], 0);
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 1, codec.Uint8, false, [0], 0);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDevice_IsochronousTransferIn_ResponseParams.packets
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 8, new codec.PointerTo(UsbIsochronousPacket), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -2512,14 +2479,12 @@
 
 
 
-    
     // validate UsbDevice_IsochronousTransferOut_Params.data
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDevice_IsochronousTransferOut_Params.packetLengths
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 4, codec.Uint32, false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -2588,7 +2553,6 @@
         return err;
 
 
-    
     // validate UsbDevice_IsochronousTransferOut_ResponseParams.packets
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(UsbIsochronousPacket), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -3431,7 +3395,7 @@
   }
 
   var UsbDevice = {
-    name: 'device::mojom::UsbDevice',
+    name: 'device.mojom.UsbDevice',
     kVersion: 0,
     ptrClass: UsbDevicePtr,
     proxyClass: UsbDeviceProxy,
@@ -3457,4 +3421,4 @@
   exports.UsbDevice = UsbDevice;
   exports.UsbDevicePtr = UsbDevicePtr;
   exports.UsbDeviceAssociatedPtr = UsbDeviceAssociatedPtr;
-})();
\ No newline at end of file
+})();
diff --git a/resources/chromium/device_manager.mojom.js b/resources/chromium/device_manager.mojom.js
index 99ebd85..2d76263 100644
--- a/resources/chromium/device_manager.mojom.js
+++ b/resources/chromium/device_manager.mojom.js
@@ -2,19 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-
 'use strict';
 
 (function() {
-  var mojomId = 'device/usb/public/interfaces/device_manager.mojom';
+  var mojomId = 'device/usb/public/mojom/device_manager.mojom';
   if (mojo.internal.isMojomLoaded(mojomId)) {
     console.warn('The following mojom is loaded multiple times: ' + mojomId);
     return;
   }
   mojo.internal.markMojomLoaded(mojomId);
-
-  // TODO(yzshen): Define these aliases to minimize the differences between the
-  // old/new modes. Remove them when the old mode goes away.
   var bindings = mojo;
   var associatedBindings = mojo;
   var codec = mojo.internal;
@@ -25,13 +21,13 @@
       mojo.internal.exposeNamespace('device.mojom');
   if (mojo.config.autoLoadMojomDeps) {
     mojo.internal.loadMojomIfNecessary(
-        'device/usb/public/interfaces/device.mojom', 'device.mojom.js');
+        'device/usb/public/mojom/device.mojom', 'device.mojom.js');
   }
   var string16$ =
       mojo.internal.exposeNamespace('mojo.common.mojom');
   if (mojo.config.autoLoadMojomDeps) {
     mojo.internal.loadMojomIfNecessary(
-        'mojo/common/string16.mojom', '../../../../mojo/common/string16.mojom.js');
+        'mojo/public/mojom/base/string16.mojom', '../../../../mojo/public/mojom/base/string16.mojom.js');
   }
 
 
@@ -86,7 +82,6 @@
 
 
 
-    
     // validate UsbDeviceFilter.serialNumber
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, string16$.String16, true);
     if (err !== validator.validationError.NONE)
@@ -165,7 +160,6 @@
         return err;
 
 
-    
     // validate UsbEnumerationOptions.filters
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(UsbDeviceFilter), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -221,7 +215,6 @@
         return err;
 
 
-    
     // validate UsbDeviceManager_GetDevices_Params.options
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, UsbEnumerationOptions, true);
     if (err !== validator.validationError.NONE)
@@ -277,7 +270,6 @@
         return err;
 
 
-    
     // validate UsbDeviceManager_GetDevices_ResponseParams.results
     err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(device$.UsbDeviceInfo), false, [0], 0);
     if (err !== validator.validationError.NONE)
@@ -334,14 +326,12 @@
         return err;
 
 
-    
     // validate UsbDeviceManager_GetDevice_Params.guid
     err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
     if (err !== validator.validationError.NONE)
         return err;
 
 
-    
     // validate UsbDeviceManager_GetDevice_Params.deviceRequest
     err = messageValidator.validateInterfaceRequest(offset + codec.kStructHeaderSize + 8, false)
     if (err !== validator.validationError.NONE)
@@ -407,7 +397,6 @@
         return err;
 
 
-    
     // validate UsbDeviceManager_SetClient_Params.client
     err = messageValidator.validateInterface(offset + codec.kStructHeaderSize + 0, false);
     if (err !== validator.validationError.NONE)
@@ -463,7 +452,6 @@
         return err;
 
 
-    
     // validate UsbDeviceManagerClient_OnDeviceAdded_Params.deviceInfo
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, device$.UsbDeviceInfo, false);
     if (err !== validator.validationError.NONE)
@@ -519,7 +507,6 @@
         return err;
 
 
-    
     // validate UsbDeviceManagerClient_OnDeviceRemoved_Params.deviceInfo
     err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, device$.UsbDeviceInfo, false);
     if (err !== validator.validationError.NONE)
@@ -715,7 +702,7 @@
   }
 
   var UsbDeviceManager = {
-    name: 'device::mojom::UsbDeviceManager',
+    name: 'device.mojom.UsbDeviceManager',
     kVersion: 0,
     ptrClass: UsbDeviceManagerPtr,
     proxyClass: UsbDeviceManagerProxy,
@@ -835,7 +822,7 @@
   }
 
   var UsbDeviceManagerClient = {
-    name: 'device::mojom::UsbDeviceManagerClient',
+    name: 'device.mojom.UsbDeviceManagerClient',
     kVersion: 0,
     ptrClass: UsbDeviceManagerClientPtr,
     proxyClass: UsbDeviceManagerClientProxy,
@@ -853,4 +840,4 @@
   exports.UsbDeviceManagerClient = UsbDeviceManagerClient;
   exports.UsbDeviceManagerClientPtr = UsbDeviceManagerClientPtr;
   exports.UsbDeviceManagerClientAssociatedPtr = UsbDeviceManagerClientAssociatedPtr;
-})();
\ No newline at end of file
+})();
diff --git a/resources/chromium/fake_bluetooth.mojom.js b/resources/chromium/fake_bluetooth.mojom.js
new file mode 100644
index 0000000..da44336
--- /dev/null
+++ b/resources/chromium/fake_bluetooth.mojom.js
@@ -0,0 +1,5323 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+  var mojomId = 'device/bluetooth/public/mojom/test/fake_bluetooth.mojom';
+  if (mojo.internal.isMojomLoaded(mojomId)) {
+    console.warn('The following mojom is loaded multiple times: ' + mojomId);
+    return;
+  }
+  mojo.internal.markMojomLoaded(mojomId);
+  var bindings = mojo;
+  var associatedBindings = mojo;
+  var codec = mojo.internal;
+  var validator = mojo.internal;
+
+  var exports = mojo.internal.exposeNamespace('bluetooth.mojom');
+  var uuid$ =
+      mojo.internal.exposeNamespace('bluetooth.mojom');
+  if (mojo.config.autoLoadMojomDeps) {
+    mojo.internal.loadMojomIfNecessary(
+        'device/bluetooth/public/mojom/uuid.mojom', '../uuid.mojom.js');
+  }
+
+
+  var kHCISuccess = 0x0000;
+  var kHCIConnectionTimeout = 0x0008;
+  var kGATTSuccess = 0x0000;
+  var kGATTInvalidHandle = 0x0001;
+  var CentralState = {};
+  CentralState.ABSENT = 0;
+  CentralState.POWERED_ON = CentralState.ABSENT + 1;
+  CentralState.POWERED_OFF = CentralState.POWERED_ON + 1;
+
+  CentralState.isKnownEnumValue = function(value) {
+    switch (value) {
+    case 0:
+    case 1:
+    case 2:
+      return true;
+    }
+    return false;
+  };
+
+  CentralState.validate = function(enumValue) {
+    var isExtensible = false;
+    if (isExtensible || this.isKnownEnumValue(enumValue))
+      return validator.validationError.NONE;
+
+    return validator.validationError.UNKNOWN_ENUM_VALUE;
+  };
+
+  function Appearance(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Appearance.prototype.initDefaults_ = function() {
+    this.hasValue = false;
+    this.value = 0;
+  };
+  Appearance.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Appearance.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    return validator.validationError.NONE;
+  };
+
+  Appearance.encodedSize = codec.kStructHeaderSize + 8;
+
+  Appearance.decode = function(decoder) {
+    var packed;
+    var val = new Appearance();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.hasValue = (packed >> 0) & 1 ? true : false;
+    val.value = decoder.decodeStruct(codec.Int8);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  Appearance.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Appearance.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.hasValue & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.encodeStruct(codec.Int8, val.value);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function Power(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Power.prototype.initDefaults_ = function() {
+    this.hasValue = false;
+    this.value = 0;
+  };
+  Power.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Power.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    return validator.validationError.NONE;
+  };
+
+  Power.encodedSize = codec.kStructHeaderSize + 8;
+
+  Power.decode = function(decoder) {
+    var packed;
+    var val = new Power();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.hasValue = (packed >> 0) & 1 ? true : false;
+    val.value = decoder.decodeStruct(codec.Int8);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  Power.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Power.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.hasValue & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.encodeStruct(codec.Int8, val.value);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function ServiceDataMap(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  ServiceDataMap.prototype.initDefaults_ = function() {
+    this.serviceData = null;
+  };
+  ServiceDataMap.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  ServiceDataMap.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ServiceDataMap.serviceData
+    err = messageValidator.validateMapPointer(offset + codec.kStructHeaderSize + 0, false, codec.String, new codec.ArrayOf(codec.Uint8), false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  ServiceDataMap.encodedSize = codec.kStructHeaderSize + 8;
+
+  ServiceDataMap.decode = function(decoder) {
+    var packed;
+    var val = new ServiceDataMap();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.serviceData = decoder.decodeMapPointer(codec.String, new codec.ArrayOf(codec.Uint8));
+    return val;
+  };
+
+  ServiceDataMap.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(ServiceDataMap.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeMapPointer(codec.String, new codec.ArrayOf(codec.Uint8), val.serviceData);
+  };
+  function ScanRecord(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  ScanRecord.prototype.initDefaults_ = function() {
+    this.name = null;
+    this.uuids = null;
+    this.appearance = null;
+    this.txPower = null;
+    this.manufacturerData = null;
+    this.serviceData = null;
+  };
+  ScanRecord.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  ScanRecord.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 56}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ScanRecord.name
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ScanRecord.uuids
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 8, new codec.PointerTo(uuid$.UUID), true, [0], 0);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ScanRecord.appearance
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 16, Appearance, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ScanRecord.txPower
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 24, Power, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ScanRecord.manufacturerData
+    err = messageValidator.validateMapPointer(offset + codec.kStructHeaderSize + 32, true, codec.Uint8, new codec.ArrayOf(codec.Uint8), false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ScanRecord.serviceData
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 40, ServiceDataMap, true);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  ScanRecord.encodedSize = codec.kStructHeaderSize + 48;
+
+  ScanRecord.decode = function(decoder) {
+    var packed;
+    var val = new ScanRecord();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.name = decoder.decodeStruct(codec.NullableString);
+    val.uuids = decoder.decodeArrayPointer(new codec.PointerTo(uuid$.UUID));
+    val.appearance = decoder.decodeStructPointer(Appearance);
+    val.txPower = decoder.decodeStructPointer(Power);
+    val.manufacturerData = decoder.decodeMapPointer(codec.Uint8, new codec.ArrayOf(codec.Uint8));
+    val.serviceData = decoder.decodeStructPointer(ServiceDataMap);
+    return val;
+  };
+
+  ScanRecord.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(ScanRecord.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.NullableString, val.name);
+    encoder.encodeArrayPointer(new codec.PointerTo(uuid$.UUID), val.uuids);
+    encoder.encodeStructPointer(Appearance, val.appearance);
+    encoder.encodeStructPointer(Power, val.txPower);
+    encoder.encodeMapPointer(codec.Uint8, new codec.ArrayOf(codec.Uint8), val.manufacturerData);
+    encoder.encodeStructPointer(ServiceDataMap, val.serviceData);
+  };
+  function ScanResult(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  ScanResult.prototype.initDefaults_ = function() {
+    this.deviceAddress = null;
+    this.rssi = 0;
+    this.scanRecord = null;
+  };
+  ScanResult.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  ScanResult.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 32}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate ScanResult.deviceAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate ScanResult.scanRecord
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 16, ScanRecord, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  ScanResult.encodedSize = codec.kStructHeaderSize + 24;
+
+  ScanResult.decode = function(decoder) {
+    var packed;
+    var val = new ScanResult();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.deviceAddress = decoder.decodeStruct(codec.String);
+    val.rssi = decoder.decodeStruct(codec.Int8);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.scanRecord = decoder.decodeStructPointer(ScanRecord);
+    return val;
+  };
+
+  ScanResult.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(ScanResult.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.deviceAddress);
+    encoder.encodeStruct(codec.Int8, val.rssi);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStructPointer(ScanRecord, val.scanRecord);
+  };
+  function CharacteristicProperties(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  CharacteristicProperties.prototype.initDefaults_ = function() {
+    this.broadcast = false;
+    this.read = false;
+    this.writeWithoutResponse = false;
+    this.write = false;
+    this.notify = false;
+    this.indicate = false;
+    this.authenticatedSignedWrites = false;
+    this.extendedProperties = false;
+  };
+  CharacteristicProperties.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  CharacteristicProperties.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+
+
+
+
+
+
+    return validator.validationError.NONE;
+  };
+
+  CharacteristicProperties.encodedSize = codec.kStructHeaderSize + 8;
+
+  CharacteristicProperties.decode = function(decoder) {
+    var packed;
+    var val = new CharacteristicProperties();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.broadcast = (packed >> 0) & 1 ? true : false;
+    val.read = (packed >> 1) & 1 ? true : false;
+    val.writeWithoutResponse = (packed >> 2) & 1 ? true : false;
+    val.write = (packed >> 3) & 1 ? true : false;
+    val.notify = (packed >> 4) & 1 ? true : false;
+    val.indicate = (packed >> 5) & 1 ? true : false;
+    val.authenticatedSignedWrites = (packed >> 6) & 1 ? true : false;
+    val.extendedProperties = (packed >> 7) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  CharacteristicProperties.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(CharacteristicProperties.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.broadcast & 1) << 0
+    packed |= (val.read & 1) << 1
+    packed |= (val.writeWithoutResponse & 1) << 2
+    packed |= (val.write & 1) << 3
+    packed |= (val.notify & 1) << 4
+    packed |= (val.indicate & 1) << 5
+    packed |= (val.authenticatedSignedWrites & 1) << 6
+    packed |= (val.extendedProperties & 1) << 7
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeBluetooth_SetLESupported_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetooth_SetLESupported_Params.prototype.initDefaults_ = function() {
+    this.available = false;
+  };
+  FakeBluetooth_SetLESupported_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetooth_SetLESupported_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetooth_SetLESupported_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeBluetooth_SetLESupported_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetooth_SetLESupported_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.available = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeBluetooth_SetLESupported_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetooth_SetLESupported_Params.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.available & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeBluetooth_SetLESupported_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetooth_SetLESupported_ResponseParams.prototype.initDefaults_ = function() {
+  };
+  FakeBluetooth_SetLESupported_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetooth_SetLESupported_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetooth_SetLESupported_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeBluetooth_SetLESupported_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetooth_SetLESupported_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeBluetooth_SetLESupported_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetooth_SetLESupported_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeBluetooth_SimulateCentral_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetooth_SimulateCentral_Params.prototype.initDefaults_ = function() {
+    this.state = 0;
+  };
+  FakeBluetooth_SimulateCentral_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetooth_SimulateCentral_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeBluetooth_SimulateCentral_Params.state
+    err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, CentralState);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetooth_SimulateCentral_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeBluetooth_SimulateCentral_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetooth_SimulateCentral_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.state = decoder.decodeStruct(codec.Int32);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeBluetooth_SimulateCentral_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetooth_SimulateCentral_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Int32, val.state);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeBluetooth_SimulateCentral_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetooth_SimulateCentral_ResponseParams.prototype.initDefaults_ = function() {
+    this.fakeCentral = new FakeCentralPtr();
+  };
+  FakeBluetooth_SimulateCentral_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetooth_SimulateCentral_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeBluetooth_SimulateCentral_ResponseParams.fakeCentral
+    err = messageValidator.validateInterface(offset + codec.kStructHeaderSize + 0, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetooth_SimulateCentral_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeBluetooth_SimulateCentral_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetooth_SimulateCentral_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.fakeCentral = decoder.decodeStruct(new codec.Interface(FakeCentralPtr));
+    return val;
+  };
+
+  FakeBluetooth_SimulateCentral_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetooth_SimulateCentral_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(new codec.Interface(FakeCentralPtr), val.fakeCentral);
+  };
+  function FakeBluetooth_AllResponsesConsumed_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetooth_AllResponsesConsumed_Params.prototype.initDefaults_ = function() {
+  };
+  FakeBluetooth_AllResponsesConsumed_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetooth_AllResponsesConsumed_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetooth_AllResponsesConsumed_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeBluetooth_AllResponsesConsumed_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetooth_AllResponsesConsumed_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeBluetooth_AllResponsesConsumed_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetooth_AllResponsesConsumed_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeBluetooth_AllResponsesConsumed_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetooth_AllResponsesConsumed_ResponseParams.prototype.initDefaults_ = function() {
+    this.consumed = false;
+  };
+  FakeBluetooth_AllResponsesConsumed_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetooth_AllResponsesConsumed_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetooth_AllResponsesConsumed_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeBluetooth_AllResponsesConsumed_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetooth_AllResponsesConsumed_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.consumed = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeBluetooth_AllResponsesConsumed_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetooth_AllResponsesConsumed_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.consumed & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SimulatePreconnectedPeripheral_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulatePreconnectedPeripheral_Params.prototype.initDefaults_ = function() {
+    this.address = null;
+    this.name = null;
+    this.knownServiceUuids = null;
+  };
+  FakeCentral_SimulatePreconnectedPeripheral_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulatePreconnectedPeripheral_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 32}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SimulatePreconnectedPeripheral_Params.address
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SimulatePreconnectedPeripheral_Params.name
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SimulatePreconnectedPeripheral_Params.knownServiceUuids
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 8, new codec.PointerTo(uuid$.UUID), false, [0], 0);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulatePreconnectedPeripheral_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+  FakeCentral_SimulatePreconnectedPeripheral_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulatePreconnectedPeripheral_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.address = decoder.decodeStruct(codec.String);
+    val.name = decoder.decodeStruct(codec.String);
+    val.knownServiceUuids = decoder.decodeArrayPointer(new codec.PointerTo(uuid$.UUID));
+    return val;
+  };
+
+  FakeCentral_SimulatePreconnectedPeripheral_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulatePreconnectedPeripheral_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.address);
+    encoder.encodeStruct(codec.String, val.name);
+    encoder.encodeArrayPointer(new codec.PointerTo(uuid$.UUID), val.knownServiceUuids);
+  };
+  function FakeCentral_SimulatePreconnectedPeripheral_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.prototype.initDefaults_ = function() {
+  };
+  FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulatePreconnectedPeripheral_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeCentral_SimulateAdvertisementReceived_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulateAdvertisementReceived_Params.prototype.initDefaults_ = function() {
+    this.result = null;
+  };
+  FakeCentral_SimulateAdvertisementReceived_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulateAdvertisementReceived_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SimulateAdvertisementReceived_Params.result
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, ScanResult, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulateAdvertisementReceived_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SimulateAdvertisementReceived_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulateAdvertisementReceived_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.result = decoder.decodeStructPointer(ScanResult);
+    return val;
+  };
+
+  FakeCentral_SimulateAdvertisementReceived_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulateAdvertisementReceived_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStructPointer(ScanResult, val.result);
+  };
+  function FakeCentral_SimulateAdvertisementReceived_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulateAdvertisementReceived_ResponseParams.prototype.initDefaults_ = function() {
+  };
+  FakeCentral_SimulateAdvertisementReceived_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulateAdvertisementReceived_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulateAdvertisementReceived_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeCentral_SimulateAdvertisementReceived_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulateAdvertisementReceived_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeCentral_SimulateAdvertisementReceived_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulateAdvertisementReceived_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeCentral_SetNextGATTConnectionResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextGATTConnectionResponse_Params.prototype.initDefaults_ = function() {
+    this.address = null;
+    this.code = 0;
+  };
+  FakeCentral_SetNextGATTConnectionResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextGATTConnectionResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 24}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextGATTConnectionResponse_Params.address
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextGATTConnectionResponse_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+  FakeCentral_SetNextGATTConnectionResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextGATTConnectionResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.address = decoder.decodeStruct(codec.String);
+    val.code = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextGATTConnectionResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextGATTConnectionResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.address);
+    encoder.encodeStruct(codec.Uint16, val.code);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextGATTConnectionResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextGATTConnectionResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextGATTConnectionResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextGATTConnectionResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextGATTConnectionResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextGATTConnectionResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextGATTDiscoveryResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextGATTDiscoveryResponse_Params.prototype.initDefaults_ = function() {
+    this.address = null;
+    this.code = 0;
+  };
+  FakeCentral_SetNextGATTDiscoveryResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextGATTDiscoveryResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 24}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextGATTDiscoveryResponse_Params.address
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextGATTDiscoveryResponse_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+  FakeCentral_SetNextGATTDiscoveryResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextGATTDiscoveryResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.address = decoder.decodeStruct(codec.String);
+    val.code = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextGATTDiscoveryResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextGATTDiscoveryResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.address);
+    encoder.encodeStruct(codec.Uint16, val.code);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SimulateGATTDisconnection_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulateGATTDisconnection_Params.prototype.initDefaults_ = function() {
+    this.address = null;
+  };
+  FakeCentral_SimulateGATTDisconnection_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulateGATTDisconnection_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SimulateGATTDisconnection_Params.address
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulateGATTDisconnection_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SimulateGATTDisconnection_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulateGATTDisconnection_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.address = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SimulateGATTDisconnection_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulateGATTDisconnection_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.address);
+  };
+  function FakeCentral_SimulateGATTDisconnection_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulateGATTDisconnection_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SimulateGATTDisconnection_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulateGATTDisconnection_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulateGATTDisconnection_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SimulateGATTDisconnection_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulateGATTDisconnection_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SimulateGATTDisconnection_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulateGATTDisconnection_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SimulateGATTServicesChanged_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulateGATTServicesChanged_Params.prototype.initDefaults_ = function() {
+    this.address = null;
+  };
+  FakeCentral_SimulateGATTServicesChanged_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulateGATTServicesChanged_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SimulateGATTServicesChanged_Params.address
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulateGATTServicesChanged_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SimulateGATTServicesChanged_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulateGATTServicesChanged_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.address = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SimulateGATTServicesChanged_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulateGATTServicesChanged_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.address);
+  };
+  function FakeCentral_SimulateGATTServicesChanged_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SimulateGATTServicesChanged_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SimulateGATTServicesChanged_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SimulateGATTServicesChanged_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SimulateGATTServicesChanged_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SimulateGATTServicesChanged_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SimulateGATTServicesChanged_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SimulateGATTServicesChanged_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SimulateGATTServicesChanged_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_AddFakeService_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_AddFakeService_Params.prototype.initDefaults_ = function() {
+    this.peripheralAddress = null;
+    this.serviceUuid = null;
+  };
+  FakeCentral_AddFakeService_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_AddFakeService_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 24}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeService_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeService_Params.serviceUuid
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, uuid$.UUID, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_AddFakeService_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+  FakeCentral_AddFakeService_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_AddFakeService_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    val.serviceUuid = decoder.decodeStructPointer(uuid$.UUID);
+    return val;
+  };
+
+  FakeCentral_AddFakeService_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_AddFakeService_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+    encoder.encodeStructPointer(uuid$.UUID, val.serviceUuid);
+  };
+  function FakeCentral_AddFakeService_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_AddFakeService_ResponseParams.prototype.initDefaults_ = function() {
+    this.serviceId = null;
+  };
+  FakeCentral_AddFakeService_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_AddFakeService_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeService_ResponseParams.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_AddFakeService_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_AddFakeService_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_AddFakeService_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.serviceId = decoder.decodeStruct(codec.NullableString);
+    return val;
+  };
+
+  FakeCentral_AddFakeService_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_AddFakeService_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.NullableString, val.serviceId);
+  };
+  function FakeCentral_RemoveFakeService_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_RemoveFakeService_Params.prototype.initDefaults_ = function() {
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_RemoveFakeService_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_RemoveFakeService_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 24}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeService_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeService_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_RemoveFakeService_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+  FakeCentral_RemoveFakeService_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_RemoveFakeService_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_RemoveFakeService_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_RemoveFakeService_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_RemoveFakeService_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_RemoveFakeService_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_RemoveFakeService_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_RemoveFakeService_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_RemoveFakeService_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_RemoveFakeService_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_RemoveFakeService_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_RemoveFakeService_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_RemoveFakeService_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_AddFakeCharacteristic_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_AddFakeCharacteristic_Params.prototype.initDefaults_ = function() {
+    this.characteristicUuid = null;
+    this.properties = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_AddFakeCharacteristic_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_AddFakeCharacteristic_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 40}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeCharacteristic_Params.characteristicUuid
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, uuid$.UUID, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeCharacteristic_Params.properties
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, CharacteristicProperties, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeCharacteristic_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeCharacteristic_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_AddFakeCharacteristic_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+  FakeCentral_AddFakeCharacteristic_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_AddFakeCharacteristic_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.characteristicUuid = decoder.decodeStructPointer(uuid$.UUID);
+    val.properties = decoder.decodeStructPointer(CharacteristicProperties);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_AddFakeCharacteristic_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_AddFakeCharacteristic_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStructPointer(uuid$.UUID, val.characteristicUuid);
+    encoder.encodeStructPointer(CharacteristicProperties, val.properties);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_AddFakeCharacteristic_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_AddFakeCharacteristic_ResponseParams.prototype.initDefaults_ = function() {
+    this.characteristicId = null;
+  };
+  FakeCentral_AddFakeCharacteristic_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_AddFakeCharacteristic_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeCharacteristic_ResponseParams.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_AddFakeCharacteristic_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_AddFakeCharacteristic_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_AddFakeCharacteristic_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.characteristicId = decoder.decodeStruct(codec.NullableString);
+    return val;
+  };
+
+  FakeCentral_AddFakeCharacteristic_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_AddFakeCharacteristic_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.NullableString, val.characteristicId);
+  };
+  function FakeCentral_RemoveFakeCharacteristic_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_RemoveFakeCharacteristic_Params.prototype.initDefaults_ = function() {
+    this.identifier = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_RemoveFakeCharacteristic_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_RemoveFakeCharacteristic_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 32}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeCharacteristic_Params.identifier
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeCharacteristic_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeCharacteristic_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_RemoveFakeCharacteristic_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+  FakeCentral_RemoveFakeCharacteristic_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_RemoveFakeCharacteristic_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.identifier = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_RemoveFakeCharacteristic_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_RemoveFakeCharacteristic_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.identifier);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_RemoveFakeCharacteristic_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_RemoveFakeCharacteristic_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_RemoveFakeCharacteristic_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_RemoveFakeCharacteristic_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_RemoveFakeCharacteristic_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_RemoveFakeCharacteristic_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_RemoveFakeCharacteristic_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_RemoveFakeCharacteristic_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_RemoveFakeCharacteristic_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_AddFakeDescriptor_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_AddFakeDescriptor_Params.prototype.initDefaults_ = function() {
+    this.descriptorUuid = null;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_AddFakeDescriptor_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_AddFakeDescriptor_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 40}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeDescriptor_Params.descriptorUuid
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, uuid$.UUID, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeDescriptor_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeDescriptor_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeDescriptor_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_AddFakeDescriptor_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+  FakeCentral_AddFakeDescriptor_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_AddFakeDescriptor_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.descriptorUuid = decoder.decodeStructPointer(uuid$.UUID);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_AddFakeDescriptor_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_AddFakeDescriptor_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStructPointer(uuid$.UUID, val.descriptorUuid);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_AddFakeDescriptor_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_AddFakeDescriptor_ResponseParams.prototype.initDefaults_ = function() {
+    this.descriptorId = null;
+  };
+  FakeCentral_AddFakeDescriptor_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_AddFakeDescriptor_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_AddFakeDescriptor_ResponseParams.descriptorId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_AddFakeDescriptor_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_AddFakeDescriptor_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_AddFakeDescriptor_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.descriptorId = decoder.decodeStruct(codec.NullableString);
+    return val;
+  };
+
+  FakeCentral_AddFakeDescriptor_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_AddFakeDescriptor_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.NullableString, val.descriptorId);
+  };
+  function FakeCentral_RemoveFakeDescriptor_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_RemoveFakeDescriptor_Params.prototype.initDefaults_ = function() {
+    this.descriptorId = null;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_RemoveFakeDescriptor_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_RemoveFakeDescriptor_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 40}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeDescriptor_Params.descriptorId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeDescriptor_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeDescriptor_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_RemoveFakeDescriptor_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_RemoveFakeDescriptor_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+  FakeCentral_RemoveFakeDescriptor_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_RemoveFakeDescriptor_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.descriptorId = decoder.decodeStruct(codec.String);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_RemoveFakeDescriptor_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_RemoveFakeDescriptor_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.descriptorId);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_RemoveFakeDescriptor_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_RemoveFakeDescriptor_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_RemoveFakeDescriptor_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_RemoveFakeDescriptor_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_RemoveFakeDescriptor_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_RemoveFakeDescriptor_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_RemoveFakeDescriptor_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_RemoveFakeDescriptor_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_RemoveFakeDescriptor_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextReadCharacteristicResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextReadCharacteristicResponse_Params.prototype.initDefaults_ = function() {
+    this.gattCode = 0;
+    this.value = null;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_SetNextReadCharacteristicResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextReadCharacteristicResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 48}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_SetNextReadCharacteristicResponse_Params.value
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextReadCharacteristicResponse_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextReadCharacteristicResponse_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextReadCharacteristicResponse_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 32, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextReadCharacteristicResponse_Params.encodedSize = codec.kStructHeaderSize + 40;
+
+  FakeCentral_SetNextReadCharacteristicResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextReadCharacteristicResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.gattCode = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.value = decoder.decodeArrayPointer(codec.Uint8);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SetNextReadCharacteristicResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextReadCharacteristicResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Uint16, val.gattCode);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeArrayPointer(codec.Uint8, val.value);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_SetNextReadCharacteristicResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextReadCharacteristicResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextWriteCharacteristicResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextWriteCharacteristicResponse_Params.prototype.initDefaults_ = function() {
+    this.gattCode = 0;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_SetNextWriteCharacteristicResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextWriteCharacteristicResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 40}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_SetNextWriteCharacteristicResponse_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextWriteCharacteristicResponse_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextWriteCharacteristicResponse_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextWriteCharacteristicResponse_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+  FakeCentral_SetNextWriteCharacteristicResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextWriteCharacteristicResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.gattCode = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SetNextWriteCharacteristicResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextWriteCharacteristicResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Uint16, val.gattCode);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextSubscribeToNotificationsResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_Params.prototype.initDefaults_ = function() {
+    this.gattCode = 0;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_SetNextSubscribeToNotificationsResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 40}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_SetNextSubscribeToNotificationsResponse_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextSubscribeToNotificationsResponse_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextSubscribeToNotificationsResponse_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextSubscribeToNotificationsResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.gattCode = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Uint16, val.gattCode);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.prototype.initDefaults_ = function() {
+    this.gattCode = 0;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 40}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.gattCode = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Uint16, val.gattCode);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_IsNotifying_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_IsNotifying_Params.prototype.initDefaults_ = function() {
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_IsNotifying_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_IsNotifying_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 32}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_IsNotifying_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_IsNotifying_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_IsNotifying_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_IsNotifying_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+  FakeCentral_IsNotifying_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_IsNotifying_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_IsNotifying_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_IsNotifying_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_IsNotifying_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_IsNotifying_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+    this.isNotifying = false;
+  };
+  FakeCentral_IsNotifying_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_IsNotifying_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_IsNotifying_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_IsNotifying_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_IsNotifying_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    val.isNotifying = (packed >> 1) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_IsNotifying_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_IsNotifying_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    packed |= (val.isNotifying & 1) << 1
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_GetLastWrittenCharacteristicValue_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_GetLastWrittenCharacteristicValue_Params.prototype.initDefaults_ = function() {
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_GetLastWrittenCharacteristicValue_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_GetLastWrittenCharacteristicValue_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 32}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_GetLastWrittenCharacteristicValue_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_GetLastWrittenCharacteristicValue_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_GetLastWrittenCharacteristicValue_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_GetLastWrittenCharacteristicValue_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+  FakeCentral_GetLastWrittenCharacteristicValue_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_GetLastWrittenCharacteristicValue_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_GetLastWrittenCharacteristicValue_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_GetLastWrittenCharacteristicValue_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+    this.value = null;
+  };
+  FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 24}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.value
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+  FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.value = decoder.decodeArrayPointer(codec.Uint8);
+    return val;
+  };
+
+  FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeArrayPointer(codec.Uint8, val.value);
+  };
+  function FakeCentral_SetNextReadDescriptorResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextReadDescriptorResponse_Params.prototype.initDefaults_ = function() {
+    this.gattCode = 0;
+    this.value = null;
+    this.descriptorId = null;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_SetNextReadDescriptorResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextReadDescriptorResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 56}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_SetNextReadDescriptorResponse_Params.value
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextReadDescriptorResponse_Params.descriptorId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextReadDescriptorResponse_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextReadDescriptorResponse_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 32, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextReadDescriptorResponse_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 40, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextReadDescriptorResponse_Params.encodedSize = codec.kStructHeaderSize + 48;
+
+  FakeCentral_SetNextReadDescriptorResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextReadDescriptorResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.gattCode = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.value = decoder.decodeArrayPointer(codec.Uint8);
+    val.descriptorId = decoder.decodeStruct(codec.String);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SetNextReadDescriptorResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextReadDescriptorResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Uint16, val.gattCode);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeArrayPointer(codec.Uint8, val.value);
+    encoder.encodeStruct(codec.String, val.descriptorId);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_SetNextReadDescriptorResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextReadDescriptorResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextReadDescriptorResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextReadDescriptorResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextReadDescriptorResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextReadDescriptorResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_SetNextWriteDescriptorResponse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextWriteDescriptorResponse_Params.prototype.initDefaults_ = function() {
+    this.gattCode = 0;
+    this.descriptorId = null;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_SetNextWriteDescriptorResponse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextWriteDescriptorResponse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 48}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_SetNextWriteDescriptorResponse_Params.descriptorId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextWriteDescriptorResponse_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextWriteDescriptorResponse_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_SetNextWriteDescriptorResponse_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 32, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextWriteDescriptorResponse_Params.encodedSize = codec.kStructHeaderSize + 40;
+
+  FakeCentral_SetNextWriteDescriptorResponse_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextWriteDescriptorResponse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.gattCode = decoder.decodeStruct(codec.Uint16);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.descriptorId = decoder.decodeStruct(codec.String);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_SetNextWriteDescriptorResponse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextWriteDescriptorResponse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Uint16, val.gattCode);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStruct(codec.String, val.descriptorId);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_SetNextWriteDescriptorResponse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_SetNextWriteDescriptorResponse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeCentral_GetLastWrittenDescriptorValue_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_GetLastWrittenDescriptorValue_Params.prototype.initDefaults_ = function() {
+    this.descriptorId = null;
+    this.characteristicId = null;
+    this.serviceId = null;
+    this.peripheralAddress = null;
+  };
+  FakeCentral_GetLastWrittenDescriptorValue_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_GetLastWrittenDescriptorValue_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 40}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_GetLastWrittenDescriptorValue_Params.descriptorId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_GetLastWrittenDescriptorValue_Params.characteristicId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_GetLastWrittenDescriptorValue_Params.serviceId
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeCentral_GetLastWrittenDescriptorValue_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_GetLastWrittenDescriptorValue_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+  FakeCentral_GetLastWrittenDescriptorValue_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_GetLastWrittenDescriptorValue_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.descriptorId = decoder.decodeStruct(codec.String);
+    val.characteristicId = decoder.decodeStruct(codec.String);
+    val.serviceId = decoder.decodeStruct(codec.String);
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeCentral_GetLastWrittenDescriptorValue_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_GetLastWrittenDescriptorValue_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.descriptorId);
+    encoder.encodeStruct(codec.String, val.characteristicId);
+    encoder.encodeStruct(codec.String, val.serviceId);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeCentral_GetLastWrittenDescriptorValue_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+    this.value = null;
+  };
+  FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 24}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.value
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+  FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeCentral_GetLastWrittenDescriptorValue_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.value = decoder.decodeArrayPointer(codec.Uint8);
+    return val;
+  };
+
+  FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeArrayPointer(codec.Uint8, val.value);
+  };
+  var kFakeBluetooth_SetLESupported_Name = 0;
+  var kFakeBluetooth_SimulateCentral_Name = 1;
+  var kFakeBluetooth_AllResponsesConsumed_Name = 2;
+
+  function FakeBluetoothPtr(handleOrPtrInfo) {
+    this.ptr = new bindings.InterfacePtrController(FakeBluetooth,
+                                                   handleOrPtrInfo);
+  }
+
+  function FakeBluetoothAssociatedPtr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        FakeBluetooth, associatedInterfacePtrInfo);
+  }
+
+  FakeBluetoothAssociatedPtr.prototype =
+      Object.create(FakeBluetoothPtr.prototype);
+  FakeBluetoothAssociatedPtr.prototype.constructor =
+      FakeBluetoothAssociatedPtr;
+
+  function FakeBluetoothProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+  FakeBluetoothPtr.prototype.setLESupported = function() {
+    return FakeBluetoothProxy.prototype.setLESupported
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeBluetoothProxy.prototype.setLESupported = function(available) {
+    var params = new FakeBluetooth_SetLESupported_Params();
+    params.available = available;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeBluetooth_SetLESupported_Name,
+          codec.align(FakeBluetooth_SetLESupported_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeBluetooth_SetLESupported_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeBluetooth_SetLESupported_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeBluetoothPtr.prototype.simulateCentral = function() {
+    return FakeBluetoothProxy.prototype.simulateCentral
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeBluetoothProxy.prototype.simulateCentral = function(state) {
+    var params = new FakeBluetooth_SimulateCentral_Params();
+    params.state = state;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeBluetooth_SimulateCentral_Name,
+          codec.align(FakeBluetooth_SimulateCentral_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeBluetooth_SimulateCentral_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeBluetooth_SimulateCentral_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeBluetoothPtr.prototype.allResponsesConsumed = function() {
+    return FakeBluetoothProxy.prototype.allResponsesConsumed
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeBluetoothProxy.prototype.allResponsesConsumed = function() {
+    var params = new FakeBluetooth_AllResponsesConsumed_Params();
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeBluetooth_AllResponsesConsumed_Name,
+          codec.align(FakeBluetooth_AllResponsesConsumed_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeBluetooth_AllResponsesConsumed_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeBluetooth_AllResponsesConsumed_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+
+  function FakeBluetoothStub(delegate) {
+    this.delegate_ = delegate;
+  }
+  FakeBluetoothStub.prototype.setLESupported = function(available) {
+    return this.delegate_ && this.delegate_.setLESupported && this.delegate_.setLESupported(available);
+  }
+  FakeBluetoothStub.prototype.simulateCentral = function(state) {
+    return this.delegate_ && this.delegate_.simulateCentral && this.delegate_.simulateCentral(state);
+  }
+  FakeBluetoothStub.prototype.allResponsesConsumed = function() {
+    return this.delegate_ && this.delegate_.allResponsesConsumed && this.delegate_.allResponsesConsumed();
+  }
+
+  FakeBluetoothStub.prototype.accept = function(message) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    default:
+      return false;
+    }
+  };
+
+  FakeBluetoothStub.prototype.acceptWithResponder =
+      function(message, responder) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kFakeBluetooth_SetLESupported_Name:
+      var params = reader.decodeStruct(FakeBluetooth_SetLESupported_Params);
+      this.setLESupported(params.available).then(function(response) {
+        var responseParams =
+            new FakeBluetooth_SetLESupported_ResponseParams();
+        var builder = new codec.MessageV1Builder(
+            kFakeBluetooth_SetLESupported_Name,
+            codec.align(FakeBluetooth_SetLESupported_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeBluetooth_SetLESupported_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeBluetooth_SimulateCentral_Name:
+      var params = reader.decodeStruct(FakeBluetooth_SimulateCentral_Params);
+      this.simulateCentral(params.state).then(function(response) {
+        var responseParams =
+            new FakeBluetooth_SimulateCentral_ResponseParams();
+        responseParams.fakeCentral = response.fakeCentral;
+        var builder = new codec.MessageV1Builder(
+            kFakeBluetooth_SimulateCentral_Name,
+            codec.align(FakeBluetooth_SimulateCentral_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeBluetooth_SimulateCentral_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeBluetooth_AllResponsesConsumed_Name:
+      var params = reader.decodeStruct(FakeBluetooth_AllResponsesConsumed_Params);
+      this.allResponsesConsumed().then(function(response) {
+        var responseParams =
+            new FakeBluetooth_AllResponsesConsumed_ResponseParams();
+        responseParams.consumed = response.consumed;
+        var builder = new codec.MessageV1Builder(
+            kFakeBluetooth_AllResponsesConsumed_Name,
+            codec.align(FakeBluetooth_AllResponsesConsumed_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeBluetooth_AllResponsesConsumed_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  function validateFakeBluetoothRequest(messageValidator) {
+    var message = messageValidator.message;
+    var paramsClass = null;
+    switch (message.getName()) {
+      case kFakeBluetooth_SetLESupported_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeBluetooth_SetLESupported_Params;
+      break;
+      case kFakeBluetooth_SimulateCentral_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeBluetooth_SimulateCentral_Params;
+      break;
+      case kFakeBluetooth_AllResponsesConsumed_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeBluetooth_AllResponsesConsumed_Params;
+      break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  function validateFakeBluetoothResponse(messageValidator) {
+   var message = messageValidator.message;
+   var paramsClass = null;
+   switch (message.getName()) {
+      case kFakeBluetooth_SetLESupported_Name:
+        if (message.isResponse())
+          paramsClass = FakeBluetooth_SetLESupported_ResponseParams;
+        break;
+      case kFakeBluetooth_SimulateCentral_Name:
+        if (message.isResponse())
+          paramsClass = FakeBluetooth_SimulateCentral_ResponseParams;
+        break;
+      case kFakeBluetooth_AllResponsesConsumed_Name:
+        if (message.isResponse())
+          paramsClass = FakeBluetooth_AllResponsesConsumed_ResponseParams;
+        break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  var FakeBluetooth = {
+    name: 'bluetooth.mojom.FakeBluetooth',
+    kVersion: 0,
+    ptrClass: FakeBluetoothPtr,
+    proxyClass: FakeBluetoothProxy,
+    stubClass: FakeBluetoothStub,
+    validateRequest: validateFakeBluetoothRequest,
+    validateResponse: validateFakeBluetoothResponse,
+  };
+  FakeBluetoothStub.prototype.validator = validateFakeBluetoothRequest;
+  FakeBluetoothProxy.prototype.validator = validateFakeBluetoothResponse;
+  var kFakeCentral_SimulatePreconnectedPeripheral_Name = 0;
+  var kFakeCentral_SimulateAdvertisementReceived_Name = 1;
+  var kFakeCentral_SetNextGATTConnectionResponse_Name = 2;
+  var kFakeCentral_SetNextGATTDiscoveryResponse_Name = 3;
+  var kFakeCentral_SimulateGATTDisconnection_Name = 4;
+  var kFakeCentral_SimulateGATTServicesChanged_Name = 5;
+  var kFakeCentral_AddFakeService_Name = 6;
+  var kFakeCentral_RemoveFakeService_Name = 7;
+  var kFakeCentral_AddFakeCharacteristic_Name = 8;
+  var kFakeCentral_RemoveFakeCharacteristic_Name = 9;
+  var kFakeCentral_AddFakeDescriptor_Name = 10;
+  var kFakeCentral_RemoveFakeDescriptor_Name = 11;
+  var kFakeCentral_SetNextReadCharacteristicResponse_Name = 12;
+  var kFakeCentral_SetNextWriteCharacteristicResponse_Name = 13;
+  var kFakeCentral_SetNextSubscribeToNotificationsResponse_Name = 14;
+  var kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name = 15;
+  var kFakeCentral_IsNotifying_Name = 16;
+  var kFakeCentral_GetLastWrittenCharacteristicValue_Name = 17;
+  var kFakeCentral_SetNextReadDescriptorResponse_Name = 18;
+  var kFakeCentral_SetNextWriteDescriptorResponse_Name = 19;
+  var kFakeCentral_GetLastWrittenDescriptorValue_Name = 20;
+
+  function FakeCentralPtr(handleOrPtrInfo) {
+    this.ptr = new bindings.InterfacePtrController(FakeCentral,
+                                                   handleOrPtrInfo);
+  }
+
+  function FakeCentralAssociatedPtr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        FakeCentral, associatedInterfacePtrInfo);
+  }
+
+  FakeCentralAssociatedPtr.prototype =
+      Object.create(FakeCentralPtr.prototype);
+  FakeCentralAssociatedPtr.prototype.constructor =
+      FakeCentralAssociatedPtr;
+
+  function FakeCentralProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+  FakeCentralPtr.prototype.simulatePreconnectedPeripheral = function() {
+    return FakeCentralProxy.prototype.simulatePreconnectedPeripheral
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.simulatePreconnectedPeripheral = function(address, name, knownServiceUuids) {
+    var params = new FakeCentral_SimulatePreconnectedPeripheral_Params();
+    params.address = address;
+    params.name = name;
+    params.knownServiceUuids = knownServiceUuids;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SimulatePreconnectedPeripheral_Name,
+          codec.align(FakeCentral_SimulatePreconnectedPeripheral_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SimulatePreconnectedPeripheral_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.simulateAdvertisementReceived = function() {
+    return FakeCentralProxy.prototype.simulateAdvertisementReceived
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.simulateAdvertisementReceived = function(result) {
+    var params = new FakeCentral_SimulateAdvertisementReceived_Params();
+    params.result = result;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SimulateAdvertisementReceived_Name,
+          codec.align(FakeCentral_SimulateAdvertisementReceived_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SimulateAdvertisementReceived_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SimulateAdvertisementReceived_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextGATTConnectionResponse = function() {
+    return FakeCentralProxy.prototype.setNextGATTConnectionResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextGATTConnectionResponse = function(address, code) {
+    var params = new FakeCentral_SetNextGATTConnectionResponse_Params();
+    params.address = address;
+    params.code = code;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextGATTConnectionResponse_Name,
+          codec.align(FakeCentral_SetNextGATTConnectionResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextGATTConnectionResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextGATTConnectionResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextGATTDiscoveryResponse = function() {
+    return FakeCentralProxy.prototype.setNextGATTDiscoveryResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextGATTDiscoveryResponse = function(address, code) {
+    var params = new FakeCentral_SetNextGATTDiscoveryResponse_Params();
+    params.address = address;
+    params.code = code;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextGATTDiscoveryResponse_Name,
+          codec.align(FakeCentral_SetNextGATTDiscoveryResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.simulateGATTDisconnection = function() {
+    return FakeCentralProxy.prototype.simulateGATTDisconnection
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.simulateGATTDisconnection = function(address) {
+    var params = new FakeCentral_SimulateGATTDisconnection_Params();
+    params.address = address;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SimulateGATTDisconnection_Name,
+          codec.align(FakeCentral_SimulateGATTDisconnection_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SimulateGATTDisconnection_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SimulateGATTDisconnection_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.simulateGATTServicesChanged = function() {
+    return FakeCentralProxy.prototype.simulateGATTServicesChanged
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.simulateGATTServicesChanged = function(address) {
+    var params = new FakeCentral_SimulateGATTServicesChanged_Params();
+    params.address = address;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SimulateGATTServicesChanged_Name,
+          codec.align(FakeCentral_SimulateGATTServicesChanged_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SimulateGATTServicesChanged_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SimulateGATTServicesChanged_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.addFakeService = function() {
+    return FakeCentralProxy.prototype.addFakeService
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.addFakeService = function(peripheralAddress, serviceUuid) {
+    var params = new FakeCentral_AddFakeService_Params();
+    params.peripheralAddress = peripheralAddress;
+    params.serviceUuid = serviceUuid;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_AddFakeService_Name,
+          codec.align(FakeCentral_AddFakeService_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_AddFakeService_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_AddFakeService_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.removeFakeService = function() {
+    return FakeCentralProxy.prototype.removeFakeService
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.removeFakeService = function(serviceId, peripheralAddress) {
+    var params = new FakeCentral_RemoveFakeService_Params();
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_RemoveFakeService_Name,
+          codec.align(FakeCentral_RemoveFakeService_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_RemoveFakeService_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_RemoveFakeService_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.addFakeCharacteristic = function() {
+    return FakeCentralProxy.prototype.addFakeCharacteristic
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.addFakeCharacteristic = function(characteristicUuid, properties, serviceId, peripheralAddress) {
+    var params = new FakeCentral_AddFakeCharacteristic_Params();
+    params.characteristicUuid = characteristicUuid;
+    params.properties = properties;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_AddFakeCharacteristic_Name,
+          codec.align(FakeCentral_AddFakeCharacteristic_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_AddFakeCharacteristic_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_AddFakeCharacteristic_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.removeFakeCharacteristic = function() {
+    return FakeCentralProxy.prototype.removeFakeCharacteristic
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.removeFakeCharacteristic = function(identifier, serviceId, peripheralAddress) {
+    var params = new FakeCentral_RemoveFakeCharacteristic_Params();
+    params.identifier = identifier;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_RemoveFakeCharacteristic_Name,
+          codec.align(FakeCentral_RemoveFakeCharacteristic_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_RemoveFakeCharacteristic_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_RemoveFakeCharacteristic_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.addFakeDescriptor = function() {
+    return FakeCentralProxy.prototype.addFakeDescriptor
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.addFakeDescriptor = function(descriptorUuid, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_AddFakeDescriptor_Params();
+    params.descriptorUuid = descriptorUuid;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_AddFakeDescriptor_Name,
+          codec.align(FakeCentral_AddFakeDescriptor_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_AddFakeDescriptor_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_AddFakeDescriptor_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.removeFakeDescriptor = function() {
+    return FakeCentralProxy.prototype.removeFakeDescriptor
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.removeFakeDescriptor = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_RemoveFakeDescriptor_Params();
+    params.descriptorId = descriptorId;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_RemoveFakeDescriptor_Name,
+          codec.align(FakeCentral_RemoveFakeDescriptor_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_RemoveFakeDescriptor_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_RemoveFakeDescriptor_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextReadCharacteristicResponse = function() {
+    return FakeCentralProxy.prototype.setNextReadCharacteristicResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextReadCharacteristicResponse = function(gattCode, value, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_SetNextReadCharacteristicResponse_Params();
+    params.gattCode = gattCode;
+    params.value = value;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextReadCharacteristicResponse_Name,
+          codec.align(FakeCentral_SetNextReadCharacteristicResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextReadCharacteristicResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextWriteCharacteristicResponse = function() {
+    return FakeCentralProxy.prototype.setNextWriteCharacteristicResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextWriteCharacteristicResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_SetNextWriteCharacteristicResponse_Params();
+    params.gattCode = gattCode;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextWriteCharacteristicResponse_Name,
+          codec.align(FakeCentral_SetNextWriteCharacteristicResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextSubscribeToNotificationsResponse = function() {
+    return FakeCentralProxy.prototype.setNextSubscribeToNotificationsResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextSubscribeToNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_SetNextSubscribeToNotificationsResponse_Params();
+    params.gattCode = gattCode;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextSubscribeToNotificationsResponse_Name,
+          codec.align(FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextUnsubscribeFromNotificationsResponse = function() {
+    return FakeCentralProxy.prototype.setNextUnsubscribeFromNotificationsResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextUnsubscribeFromNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params();
+    params.gattCode = gattCode;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name,
+          codec.align(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.isNotifying = function() {
+    return FakeCentralProxy.prototype.isNotifying
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.isNotifying = function(characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_IsNotifying_Params();
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_IsNotifying_Name,
+          codec.align(FakeCentral_IsNotifying_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_IsNotifying_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_IsNotifying_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.getLastWrittenCharacteristicValue = function() {
+    return FakeCentralProxy.prototype.getLastWrittenCharacteristicValue
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.getLastWrittenCharacteristicValue = function(characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_GetLastWrittenCharacteristicValue_Params();
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_GetLastWrittenCharacteristicValue_Name,
+          codec.align(FakeCentral_GetLastWrittenCharacteristicValue_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextReadDescriptorResponse = function() {
+    return FakeCentralProxy.prototype.setNextReadDescriptorResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextReadDescriptorResponse = function(gattCode, value, descriptorId, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_SetNextReadDescriptorResponse_Params();
+    params.gattCode = gattCode;
+    params.value = value;
+    params.descriptorId = descriptorId;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextReadDescriptorResponse_Name,
+          codec.align(FakeCentral_SetNextReadDescriptorResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextReadDescriptorResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextReadDescriptorResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.setNextWriteDescriptorResponse = function() {
+    return FakeCentralProxy.prototype.setNextWriteDescriptorResponse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.setNextWriteDescriptorResponse = function(gattCode, descriptorId, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_SetNextWriteDescriptorResponse_Params();
+    params.gattCode = gattCode;
+    params.descriptorId = descriptorId;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_SetNextWriteDescriptorResponse_Name,
+          codec.align(FakeCentral_SetNextWriteDescriptorResponse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_SetNextWriteDescriptorResponse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeCentralPtr.prototype.getLastWrittenDescriptorValue = function() {
+    return FakeCentralProxy.prototype.getLastWrittenDescriptorValue
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeCentralProxy.prototype.getLastWrittenDescriptorValue = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+    var params = new FakeCentral_GetLastWrittenDescriptorValue_Params();
+    params.descriptorId = descriptorId;
+    params.characteristicId = characteristicId;
+    params.serviceId = serviceId;
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeCentral_GetLastWrittenDescriptorValue_Name,
+          codec.align(FakeCentral_GetLastWrittenDescriptorValue_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeCentral_GetLastWrittenDescriptorValue_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+
+  function FakeCentralStub(delegate) {
+    this.delegate_ = delegate;
+  }
+  FakeCentralStub.prototype.simulatePreconnectedPeripheral = function(address, name, knownServiceUuids) {
+    return this.delegate_ && this.delegate_.simulatePreconnectedPeripheral && this.delegate_.simulatePreconnectedPeripheral(address, name, knownServiceUuids);
+  }
+  FakeCentralStub.prototype.simulateAdvertisementReceived = function(result) {
+    return this.delegate_ && this.delegate_.simulateAdvertisementReceived && this.delegate_.simulateAdvertisementReceived(result);
+  }
+  FakeCentralStub.prototype.setNextGATTConnectionResponse = function(address, code) {
+    return this.delegate_ && this.delegate_.setNextGATTConnectionResponse && this.delegate_.setNextGATTConnectionResponse(address, code);
+  }
+  FakeCentralStub.prototype.setNextGATTDiscoveryResponse = function(address, code) {
+    return this.delegate_ && this.delegate_.setNextGATTDiscoveryResponse && this.delegate_.setNextGATTDiscoveryResponse(address, code);
+  }
+  FakeCentralStub.prototype.simulateGATTDisconnection = function(address) {
+    return this.delegate_ && this.delegate_.simulateGATTDisconnection && this.delegate_.simulateGATTDisconnection(address);
+  }
+  FakeCentralStub.prototype.simulateGATTServicesChanged = function(address) {
+    return this.delegate_ && this.delegate_.simulateGATTServicesChanged && this.delegate_.simulateGATTServicesChanged(address);
+  }
+  FakeCentralStub.prototype.addFakeService = function(peripheralAddress, serviceUuid) {
+    return this.delegate_ && this.delegate_.addFakeService && this.delegate_.addFakeService(peripheralAddress, serviceUuid);
+  }
+  FakeCentralStub.prototype.removeFakeService = function(serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.removeFakeService && this.delegate_.removeFakeService(serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.addFakeCharacteristic = function(characteristicUuid, properties, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.addFakeCharacteristic && this.delegate_.addFakeCharacteristic(characteristicUuid, properties, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.removeFakeCharacteristic = function(identifier, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.removeFakeCharacteristic && this.delegate_.removeFakeCharacteristic(identifier, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.addFakeDescriptor = function(descriptorUuid, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.addFakeDescriptor && this.delegate_.addFakeDescriptor(descriptorUuid, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.removeFakeDescriptor = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.removeFakeDescriptor && this.delegate_.removeFakeDescriptor(descriptorId, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.setNextReadCharacteristicResponse = function(gattCode, value, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.setNextReadCharacteristicResponse && this.delegate_.setNextReadCharacteristicResponse(gattCode, value, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.setNextWriteCharacteristicResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.setNextWriteCharacteristicResponse && this.delegate_.setNextWriteCharacteristicResponse(gattCode, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.setNextSubscribeToNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.setNextSubscribeToNotificationsResponse && this.delegate_.setNextSubscribeToNotificationsResponse(gattCode, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.setNextUnsubscribeFromNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.setNextUnsubscribeFromNotificationsResponse && this.delegate_.setNextUnsubscribeFromNotificationsResponse(gattCode, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.isNotifying = function(characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.isNotifying && this.delegate_.isNotifying(characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.getLastWrittenCharacteristicValue = function(characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.getLastWrittenCharacteristicValue && this.delegate_.getLastWrittenCharacteristicValue(characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.setNextReadDescriptorResponse = function(gattCode, value, descriptorId, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.setNextReadDescriptorResponse && this.delegate_.setNextReadDescriptorResponse(gattCode, value, descriptorId, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.setNextWriteDescriptorResponse = function(gattCode, descriptorId, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.setNextWriteDescriptorResponse && this.delegate_.setNextWriteDescriptorResponse(gattCode, descriptorId, characteristicId, serviceId, peripheralAddress);
+  }
+  FakeCentralStub.prototype.getLastWrittenDescriptorValue = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+    return this.delegate_ && this.delegate_.getLastWrittenDescriptorValue && this.delegate_.getLastWrittenDescriptorValue(descriptorId, characteristicId, serviceId, peripheralAddress);
+  }
+
+  FakeCentralStub.prototype.accept = function(message) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    default:
+      return false;
+    }
+  };
+
+  FakeCentralStub.prototype.acceptWithResponder =
+      function(message, responder) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kFakeCentral_SimulatePreconnectedPeripheral_Name:
+      var params = reader.decodeStruct(FakeCentral_SimulatePreconnectedPeripheral_Params);
+      this.simulatePreconnectedPeripheral(params.address, params.name, params.knownServiceUuids).then(function(response) {
+        var responseParams =
+            new FakeCentral_SimulatePreconnectedPeripheral_ResponseParams();
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SimulatePreconnectedPeripheral_Name,
+            codec.align(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SimulateAdvertisementReceived_Name:
+      var params = reader.decodeStruct(FakeCentral_SimulateAdvertisementReceived_Params);
+      this.simulateAdvertisementReceived(params.result).then(function(response) {
+        var responseParams =
+            new FakeCentral_SimulateAdvertisementReceived_ResponseParams();
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SimulateAdvertisementReceived_Name,
+            codec.align(FakeCentral_SimulateAdvertisementReceived_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SimulateAdvertisementReceived_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextGATTConnectionResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextGATTConnectionResponse_Params);
+      this.setNextGATTConnectionResponse(params.address, params.code).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextGATTConnectionResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextGATTConnectionResponse_Name,
+            codec.align(FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextGATTConnectionResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextGATTDiscoveryResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_Params);
+      this.setNextGATTDiscoveryResponse(params.address, params.code).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextGATTDiscoveryResponse_Name,
+            codec.align(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SimulateGATTDisconnection_Name:
+      var params = reader.decodeStruct(FakeCentral_SimulateGATTDisconnection_Params);
+      this.simulateGATTDisconnection(params.address).then(function(response) {
+        var responseParams =
+            new FakeCentral_SimulateGATTDisconnection_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SimulateGATTDisconnection_Name,
+            codec.align(FakeCentral_SimulateGATTDisconnection_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SimulateGATTDisconnection_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SimulateGATTServicesChanged_Name:
+      var params = reader.decodeStruct(FakeCentral_SimulateGATTServicesChanged_Params);
+      this.simulateGATTServicesChanged(params.address).then(function(response) {
+        var responseParams =
+            new FakeCentral_SimulateGATTServicesChanged_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SimulateGATTServicesChanged_Name,
+            codec.align(FakeCentral_SimulateGATTServicesChanged_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SimulateGATTServicesChanged_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_AddFakeService_Name:
+      var params = reader.decodeStruct(FakeCentral_AddFakeService_Params);
+      this.addFakeService(params.peripheralAddress, params.serviceUuid).then(function(response) {
+        var responseParams =
+            new FakeCentral_AddFakeService_ResponseParams();
+        responseParams.serviceId = response.serviceId;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_AddFakeService_Name,
+            codec.align(FakeCentral_AddFakeService_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_AddFakeService_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_RemoveFakeService_Name:
+      var params = reader.decodeStruct(FakeCentral_RemoveFakeService_Params);
+      this.removeFakeService(params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_RemoveFakeService_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_RemoveFakeService_Name,
+            codec.align(FakeCentral_RemoveFakeService_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_RemoveFakeService_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_AddFakeCharacteristic_Name:
+      var params = reader.decodeStruct(FakeCentral_AddFakeCharacteristic_Params);
+      this.addFakeCharacteristic(params.characteristicUuid, params.properties, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_AddFakeCharacteristic_ResponseParams();
+        responseParams.characteristicId = response.characteristicId;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_AddFakeCharacteristic_Name,
+            codec.align(FakeCentral_AddFakeCharacteristic_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_AddFakeCharacteristic_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_RemoveFakeCharacteristic_Name:
+      var params = reader.decodeStruct(FakeCentral_RemoveFakeCharacteristic_Params);
+      this.removeFakeCharacteristic(params.identifier, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_RemoveFakeCharacteristic_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_RemoveFakeCharacteristic_Name,
+            codec.align(FakeCentral_RemoveFakeCharacteristic_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_RemoveFakeCharacteristic_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_AddFakeDescriptor_Name:
+      var params = reader.decodeStruct(FakeCentral_AddFakeDescriptor_Params);
+      this.addFakeDescriptor(params.descriptorUuid, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_AddFakeDescriptor_ResponseParams();
+        responseParams.descriptorId = response.descriptorId;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_AddFakeDescriptor_Name,
+            codec.align(FakeCentral_AddFakeDescriptor_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_AddFakeDescriptor_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_RemoveFakeDescriptor_Name:
+      var params = reader.decodeStruct(FakeCentral_RemoveFakeDescriptor_Params);
+      this.removeFakeDescriptor(params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_RemoveFakeDescriptor_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_RemoveFakeDescriptor_Name,
+            codec.align(FakeCentral_RemoveFakeDescriptor_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_RemoveFakeDescriptor_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextReadCharacteristicResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextReadCharacteristicResponse_Params);
+      this.setNextReadCharacteristicResponse(params.gattCode, params.value, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextReadCharacteristicResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextReadCharacteristicResponse_Name,
+            codec.align(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextWriteCharacteristicResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_Params);
+      this.setNextWriteCharacteristicResponse(params.gattCode, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextWriteCharacteristicResponse_Name,
+            codec.align(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextSubscribeToNotificationsResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_Params);
+      this.setNextSubscribeToNotificationsResponse(params.gattCode, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextSubscribeToNotificationsResponse_Name,
+            codec.align(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params);
+      this.setNextUnsubscribeFromNotificationsResponse(params.gattCode, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name,
+            codec.align(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_IsNotifying_Name:
+      var params = reader.decodeStruct(FakeCentral_IsNotifying_Params);
+      this.isNotifying(params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_IsNotifying_ResponseParams();
+        responseParams.success = response.success;
+        responseParams.isNotifying = response.isNotifying;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_IsNotifying_Name,
+            codec.align(FakeCentral_IsNotifying_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_IsNotifying_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_GetLastWrittenCharacteristicValue_Name:
+      var params = reader.decodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_Params);
+      this.getLastWrittenCharacteristicValue(params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams();
+        responseParams.success = response.success;
+        responseParams.value = response.value;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_GetLastWrittenCharacteristicValue_Name,
+            codec.align(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextReadDescriptorResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextReadDescriptorResponse_Params);
+      this.setNextReadDescriptorResponse(params.gattCode, params.value, params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextReadDescriptorResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextReadDescriptorResponse_Name,
+            codec.align(FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextReadDescriptorResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_SetNextWriteDescriptorResponse_Name:
+      var params = reader.decodeStruct(FakeCentral_SetNextWriteDescriptorResponse_Params);
+      this.setNextWriteDescriptorResponse(params.gattCode, params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_SetNextWriteDescriptorResponse_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_SetNextWriteDescriptorResponse_Name,
+            codec.align(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeCentral_GetLastWrittenDescriptorValue_Name:
+      var params = reader.decodeStruct(FakeCentral_GetLastWrittenDescriptorValue_Params);
+      this.getLastWrittenDescriptorValue(params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeCentral_GetLastWrittenDescriptorValue_ResponseParams();
+        responseParams.success = response.success;
+        responseParams.value = response.value;
+        var builder = new codec.MessageV1Builder(
+            kFakeCentral_GetLastWrittenDescriptorValue_Name,
+            codec.align(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  function validateFakeCentralRequest(messageValidator) {
+    var message = messageValidator.message;
+    var paramsClass = null;
+    switch (message.getName()) {
+      case kFakeCentral_SimulatePreconnectedPeripheral_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SimulatePreconnectedPeripheral_Params;
+      break;
+      case kFakeCentral_SimulateAdvertisementReceived_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SimulateAdvertisementReceived_Params;
+      break;
+      case kFakeCentral_SetNextGATTConnectionResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextGATTConnectionResponse_Params;
+      break;
+      case kFakeCentral_SetNextGATTDiscoveryResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextGATTDiscoveryResponse_Params;
+      break;
+      case kFakeCentral_SimulateGATTDisconnection_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SimulateGATTDisconnection_Params;
+      break;
+      case kFakeCentral_SimulateGATTServicesChanged_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SimulateGATTServicesChanged_Params;
+      break;
+      case kFakeCentral_AddFakeService_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_AddFakeService_Params;
+      break;
+      case kFakeCentral_RemoveFakeService_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_RemoveFakeService_Params;
+      break;
+      case kFakeCentral_AddFakeCharacteristic_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_AddFakeCharacteristic_Params;
+      break;
+      case kFakeCentral_RemoveFakeCharacteristic_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_RemoveFakeCharacteristic_Params;
+      break;
+      case kFakeCentral_AddFakeDescriptor_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_AddFakeDescriptor_Params;
+      break;
+      case kFakeCentral_RemoveFakeDescriptor_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_RemoveFakeDescriptor_Params;
+      break;
+      case kFakeCentral_SetNextReadCharacteristicResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextReadCharacteristicResponse_Params;
+      break;
+      case kFakeCentral_SetNextWriteCharacteristicResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextWriteCharacteristicResponse_Params;
+      break;
+      case kFakeCentral_SetNextSubscribeToNotificationsResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextSubscribeToNotificationsResponse_Params;
+      break;
+      case kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params;
+      break;
+      case kFakeCentral_IsNotifying_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_IsNotifying_Params;
+      break;
+      case kFakeCentral_GetLastWrittenCharacteristicValue_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_GetLastWrittenCharacteristicValue_Params;
+      break;
+      case kFakeCentral_SetNextReadDescriptorResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextReadDescriptorResponse_Params;
+      break;
+      case kFakeCentral_SetNextWriteDescriptorResponse_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_SetNextWriteDescriptorResponse_Params;
+      break;
+      case kFakeCentral_GetLastWrittenDescriptorValue_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeCentral_GetLastWrittenDescriptorValue_Params;
+      break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  function validateFakeCentralResponse(messageValidator) {
+   var message = messageValidator.message;
+   var paramsClass = null;
+   switch (message.getName()) {
+      case kFakeCentral_SimulatePreconnectedPeripheral_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SimulatePreconnectedPeripheral_ResponseParams;
+        break;
+      case kFakeCentral_SimulateAdvertisementReceived_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SimulateAdvertisementReceived_ResponseParams;
+        break;
+      case kFakeCentral_SetNextGATTConnectionResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextGATTConnectionResponse_ResponseParams;
+        break;
+      case kFakeCentral_SetNextGATTDiscoveryResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams;
+        break;
+      case kFakeCentral_SimulateGATTDisconnection_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SimulateGATTDisconnection_ResponseParams;
+        break;
+      case kFakeCentral_SimulateGATTServicesChanged_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SimulateGATTServicesChanged_ResponseParams;
+        break;
+      case kFakeCentral_AddFakeService_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_AddFakeService_ResponseParams;
+        break;
+      case kFakeCentral_RemoveFakeService_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_RemoveFakeService_ResponseParams;
+        break;
+      case kFakeCentral_AddFakeCharacteristic_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_AddFakeCharacteristic_ResponseParams;
+        break;
+      case kFakeCentral_RemoveFakeCharacteristic_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_RemoveFakeCharacteristic_ResponseParams;
+        break;
+      case kFakeCentral_AddFakeDescriptor_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_AddFakeDescriptor_ResponseParams;
+        break;
+      case kFakeCentral_RemoveFakeDescriptor_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_RemoveFakeDescriptor_ResponseParams;
+        break;
+      case kFakeCentral_SetNextReadCharacteristicResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextReadCharacteristicResponse_ResponseParams;
+        break;
+      case kFakeCentral_SetNextWriteCharacteristicResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams;
+        break;
+      case kFakeCentral_SetNextSubscribeToNotificationsResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams;
+        break;
+      case kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams;
+        break;
+      case kFakeCentral_IsNotifying_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_IsNotifying_ResponseParams;
+        break;
+      case kFakeCentral_GetLastWrittenCharacteristicValue_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams;
+        break;
+      case kFakeCentral_SetNextReadDescriptorResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextReadDescriptorResponse_ResponseParams;
+        break;
+      case kFakeCentral_SetNextWriteDescriptorResponse_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_SetNextWriteDescriptorResponse_ResponseParams;
+        break;
+      case kFakeCentral_GetLastWrittenDescriptorValue_Name:
+        if (message.isResponse())
+          paramsClass = FakeCentral_GetLastWrittenDescriptorValue_ResponseParams;
+        break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  var FakeCentral = {
+    name: 'bluetooth.mojom.FakeCentral',
+    kVersion: 0,
+    ptrClass: FakeCentralPtr,
+    proxyClass: FakeCentralProxy,
+    stubClass: FakeCentralStub,
+    validateRequest: validateFakeCentralRequest,
+    validateResponse: validateFakeCentralResponse,
+  };
+  FakeCentralStub.prototype.validator = validateFakeCentralRequest;
+  FakeCentralProxy.prototype.validator = validateFakeCentralResponse;
+  exports.kHCISuccess = kHCISuccess;
+  exports.kHCIConnectionTimeout = kHCIConnectionTimeout;
+  exports.kGATTSuccess = kGATTSuccess;
+  exports.kGATTInvalidHandle = kGATTInvalidHandle;
+  exports.CentralState = CentralState;
+  exports.Appearance = Appearance;
+  exports.Power = Power;
+  exports.ServiceDataMap = ServiceDataMap;
+  exports.ScanRecord = ScanRecord;
+  exports.ScanResult = ScanResult;
+  exports.CharacteristicProperties = CharacteristicProperties;
+  exports.FakeBluetooth = FakeBluetooth;
+  exports.FakeBluetoothPtr = FakeBluetoothPtr;
+  exports.FakeBluetoothAssociatedPtr = FakeBluetoothAssociatedPtr;
+  exports.FakeCentral = FakeCentral;
+  exports.FakeCentralPtr = FakeCentralPtr;
+  exports.FakeCentralAssociatedPtr = FakeCentralAssociatedPtr;
+})();
diff --git a/resources/chromium/fake_bluetooth.mojom.js.headers b/resources/chromium/fake_bluetooth.mojom.js.headers
new file mode 100644
index 0000000..6805c32
--- /dev/null
+++ b/resources/chromium/fake_bluetooth.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/resources/chromium/fake_bluetooth_chooser.mojom.js b/resources/chromium/fake_bluetooth_chooser.mojom.js
new file mode 100644
index 0000000..4273939
--- /dev/null
+++ b/resources/chromium/fake_bluetooth_chooser.mojom.js
@@ -0,0 +1,822 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+  var mojomId = 'content/shell/common/layout_test/fake_bluetooth_chooser.mojom';
+  if (mojo.internal.isMojomLoaded(mojomId)) {
+    console.warn('The following mojom is loaded multiple times: ' + mojomId);
+    return;
+  }
+  mojo.internal.markMojomLoaded(mojomId);
+  var bindings = mojo;
+  var associatedBindings = mojo;
+  var codec = mojo.internal;
+  var validator = mojo.internal;
+
+  var exports = mojo.internal.exposeNamespace('content.mojom');
+
+
+  var ChooserEventType = {};
+  ChooserEventType.CHOOSER_OPENED = 0;
+  ChooserEventType.SCAN_STARTED = ChooserEventType.CHOOSER_OPENED + 1;
+  ChooserEventType.DEVICE_UPDATE = ChooserEventType.SCAN_STARTED + 1;
+  ChooserEventType.ADAPTER_REMOVED = ChooserEventType.DEVICE_UPDATE + 1;
+  ChooserEventType.ADAPTER_DISABLED = ChooserEventType.ADAPTER_REMOVED + 1;
+  ChooserEventType.ADAPTER_ENABLED = ChooserEventType.ADAPTER_DISABLED + 1;
+  ChooserEventType.DISCOVERY_FAILED_TO_START = ChooserEventType.ADAPTER_ENABLED + 1;
+  ChooserEventType.DISCOVERING = ChooserEventType.DISCOVERY_FAILED_TO_START + 1;
+  ChooserEventType.DISCOVERY_IDLE = ChooserEventType.DISCOVERING + 1;
+  ChooserEventType.ADD_DEVICE = ChooserEventType.DISCOVERY_IDLE + 1;
+
+  ChooserEventType.isKnownEnumValue = function(value) {
+    switch (value) {
+    case 0:
+    case 1:
+    case 2:
+    case 3:
+    case 4:
+    case 5:
+    case 6:
+    case 7:
+    case 8:
+    case 9:
+      return true;
+    }
+    return false;
+  };
+
+  ChooserEventType.validate = function(enumValue) {
+    var isExtensible = false;
+    if (isExtensible || this.isKnownEnumValue(enumValue))
+      return validator.validationError.NONE;
+
+    return validator.validationError.UNKNOWN_ENUM_VALUE;
+  };
+
+  function FakeBluetoothChooserEvent(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooserEvent.prototype.initDefaults_ = function() {
+    this.type = 0;
+    this.origin = null;
+    this.peripheralAddress = null;
+  };
+  FakeBluetoothChooserEvent.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooserEvent.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 32}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeBluetoothChooserEvent.type
+    err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, ChooserEventType);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeBluetoothChooserEvent.origin
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, true)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeBluetoothChooserEvent.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, true)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooserEvent.encodedSize = codec.kStructHeaderSize + 24;
+
+  FakeBluetoothChooserEvent.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooserEvent();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.type = decoder.decodeStruct(codec.Int32);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.origin = decoder.decodeStruct(codec.NullableString);
+    val.peripheralAddress = decoder.decodeStruct(codec.NullableString);
+    return val;
+  };
+
+  FakeBluetoothChooserEvent.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooserEvent.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Int32, val.type);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStruct(codec.NullableString, val.origin);
+    encoder.encodeStruct(codec.NullableString, val.peripheralAddress);
+  };
+  function FakeBluetoothChooser_WaitForEvents_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_WaitForEvents_Params.prototype.initDefaults_ = function() {
+    this.numOfEvents = 0;
+  };
+  FakeBluetoothChooser_WaitForEvents_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_WaitForEvents_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_WaitForEvents_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeBluetoothChooser_WaitForEvents_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_WaitForEvents_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.numOfEvents = decoder.decodeStruct(codec.Uint32);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  FakeBluetoothChooser_WaitForEvents_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_WaitForEvents_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Uint32, val.numOfEvents);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function FakeBluetoothChooser_WaitForEvents_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_WaitForEvents_ResponseParams.prototype.initDefaults_ = function() {
+    this.events = null;
+  };
+  FakeBluetoothChooser_WaitForEvents_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_WaitForEvents_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeBluetoothChooser_WaitForEvents_ResponseParams.events
+    err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(FakeBluetoothChooserEvent), false, [0], 0);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_WaitForEvents_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeBluetoothChooser_WaitForEvents_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_WaitForEvents_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.events = decoder.decodeArrayPointer(new codec.PointerTo(FakeBluetoothChooserEvent));
+    return val;
+  };
+
+  FakeBluetoothChooser_WaitForEvents_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_WaitForEvents_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeArrayPointer(new codec.PointerTo(FakeBluetoothChooserEvent), val.events);
+  };
+  function FakeBluetoothChooser_SelectPeripheral_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_SelectPeripheral_Params.prototype.initDefaults_ = function() {
+    this.peripheralAddress = null;
+  };
+  FakeBluetoothChooser_SelectPeripheral_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_SelectPeripheral_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate FakeBluetoothChooser_SelectPeripheral_Params.peripheralAddress
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_SelectPeripheral_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  FakeBluetoothChooser_SelectPeripheral_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_SelectPeripheral_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.peripheralAddress = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  FakeBluetoothChooser_SelectPeripheral_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_SelectPeripheral_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.peripheralAddress);
+  };
+  function FakeBluetoothChooser_SelectPeripheral_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_SelectPeripheral_ResponseParams.prototype.initDefaults_ = function() {
+  };
+  FakeBluetoothChooser_SelectPeripheral_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_SelectPeripheral_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_SelectPeripheral_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeBluetoothChooser_SelectPeripheral_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_SelectPeripheral_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeBluetoothChooser_SelectPeripheral_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_SelectPeripheral_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeBluetoothChooser_Cancel_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_Cancel_Params.prototype.initDefaults_ = function() {
+  };
+  FakeBluetoothChooser_Cancel_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_Cancel_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_Cancel_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeBluetoothChooser_Cancel_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_Cancel_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeBluetoothChooser_Cancel_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_Cancel_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeBluetoothChooser_Cancel_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_Cancel_ResponseParams.prototype.initDefaults_ = function() {
+  };
+  FakeBluetoothChooser_Cancel_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_Cancel_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_Cancel_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeBluetoothChooser_Cancel_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_Cancel_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeBluetoothChooser_Cancel_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_Cancel_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeBluetoothChooser_Rescan_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_Rescan_Params.prototype.initDefaults_ = function() {
+  };
+  FakeBluetoothChooser_Rescan_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_Rescan_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_Rescan_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeBluetoothChooser_Rescan_Params.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_Rescan_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeBluetoothChooser_Rescan_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_Rescan_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function FakeBluetoothChooser_Rescan_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  FakeBluetoothChooser_Rescan_ResponseParams.prototype.initDefaults_ = function() {
+  };
+  FakeBluetoothChooser_Rescan_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  FakeBluetoothChooser_Rescan_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  FakeBluetoothChooser_Rescan_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+  FakeBluetoothChooser_Rescan_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new FakeBluetoothChooser_Rescan_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  FakeBluetoothChooser_Rescan_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(FakeBluetoothChooser_Rescan_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+  };
+  var kFakeBluetoothChooser_WaitForEvents_Name = 457051710;
+  var kFakeBluetoothChooser_SelectPeripheral_Name = 1924310743;
+  var kFakeBluetoothChooser_Cancel_Name = 1388880682;
+  var kFakeBluetoothChooser_Rescan_Name = 2112671529;
+
+  function FakeBluetoothChooserPtr(handleOrPtrInfo) {
+    this.ptr = new bindings.InterfacePtrController(FakeBluetoothChooser,
+                                                   handleOrPtrInfo);
+  }
+
+  function FakeBluetoothChooserAssociatedPtr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        FakeBluetoothChooser, associatedInterfacePtrInfo);
+  }
+
+  FakeBluetoothChooserAssociatedPtr.prototype =
+      Object.create(FakeBluetoothChooserPtr.prototype);
+  FakeBluetoothChooserAssociatedPtr.prototype.constructor =
+      FakeBluetoothChooserAssociatedPtr;
+
+  function FakeBluetoothChooserProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+  FakeBluetoothChooserPtr.prototype.waitForEvents = function() {
+    return FakeBluetoothChooserProxy.prototype.waitForEvents
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeBluetoothChooserProxy.prototype.waitForEvents = function(numOfEvents) {
+    var params = new FakeBluetoothChooser_WaitForEvents_Params();
+    params.numOfEvents = numOfEvents;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeBluetoothChooser_WaitForEvents_Name,
+          codec.align(FakeBluetoothChooser_WaitForEvents_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeBluetoothChooser_WaitForEvents_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeBluetoothChooser_WaitForEvents_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeBluetoothChooserPtr.prototype.selectPeripheral = function() {
+    return FakeBluetoothChooserProxy.prototype.selectPeripheral
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeBluetoothChooserProxy.prototype.selectPeripheral = function(peripheralAddress) {
+    var params = new FakeBluetoothChooser_SelectPeripheral_Params();
+    params.peripheralAddress = peripheralAddress;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeBluetoothChooser_SelectPeripheral_Name,
+          codec.align(FakeBluetoothChooser_SelectPeripheral_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeBluetoothChooser_SelectPeripheral_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeBluetoothChooser_SelectPeripheral_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeBluetoothChooserPtr.prototype.cancel = function() {
+    return FakeBluetoothChooserProxy.prototype.cancel
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeBluetoothChooserProxy.prototype.cancel = function() {
+    var params = new FakeBluetoothChooser_Cancel_Params();
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeBluetoothChooser_Cancel_Name,
+          codec.align(FakeBluetoothChooser_Cancel_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeBluetoothChooser_Cancel_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeBluetoothChooser_Cancel_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  FakeBluetoothChooserPtr.prototype.rescan = function() {
+    return FakeBluetoothChooserProxy.prototype.rescan
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  FakeBluetoothChooserProxy.prototype.rescan = function() {
+    var params = new FakeBluetoothChooser_Rescan_Params();
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kFakeBluetoothChooser_Rescan_Name,
+          codec.align(FakeBluetoothChooser_Rescan_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(FakeBluetoothChooser_Rescan_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(FakeBluetoothChooser_Rescan_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+
+  function FakeBluetoothChooserStub(delegate) {
+    this.delegate_ = delegate;
+  }
+  FakeBluetoothChooserStub.prototype.waitForEvents = function(numOfEvents) {
+    return this.delegate_ && this.delegate_.waitForEvents && this.delegate_.waitForEvents(numOfEvents);
+  }
+  FakeBluetoothChooserStub.prototype.selectPeripheral = function(peripheralAddress) {
+    return this.delegate_ && this.delegate_.selectPeripheral && this.delegate_.selectPeripheral(peripheralAddress);
+  }
+  FakeBluetoothChooserStub.prototype.cancel = function() {
+    return this.delegate_ && this.delegate_.cancel && this.delegate_.cancel();
+  }
+  FakeBluetoothChooserStub.prototype.rescan = function() {
+    return this.delegate_ && this.delegate_.rescan && this.delegate_.rescan();
+  }
+
+  FakeBluetoothChooserStub.prototype.accept = function(message) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    default:
+      return false;
+    }
+  };
+
+  FakeBluetoothChooserStub.prototype.acceptWithResponder =
+      function(message, responder) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kFakeBluetoothChooser_WaitForEvents_Name:
+      var params = reader.decodeStruct(FakeBluetoothChooser_WaitForEvents_Params);
+      this.waitForEvents(params.numOfEvents).then(function(response) {
+        var responseParams =
+            new FakeBluetoothChooser_WaitForEvents_ResponseParams();
+        responseParams.events = response.events;
+        var builder = new codec.MessageV1Builder(
+            kFakeBluetoothChooser_WaitForEvents_Name,
+            codec.align(FakeBluetoothChooser_WaitForEvents_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeBluetoothChooser_WaitForEvents_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeBluetoothChooser_SelectPeripheral_Name:
+      var params = reader.decodeStruct(FakeBluetoothChooser_SelectPeripheral_Params);
+      this.selectPeripheral(params.peripheralAddress).then(function(response) {
+        var responseParams =
+            new FakeBluetoothChooser_SelectPeripheral_ResponseParams();
+        var builder = new codec.MessageV1Builder(
+            kFakeBluetoothChooser_SelectPeripheral_Name,
+            codec.align(FakeBluetoothChooser_SelectPeripheral_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeBluetoothChooser_SelectPeripheral_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeBluetoothChooser_Cancel_Name:
+      var params = reader.decodeStruct(FakeBluetoothChooser_Cancel_Params);
+      this.cancel().then(function(response) {
+        var responseParams =
+            new FakeBluetoothChooser_Cancel_ResponseParams();
+        var builder = new codec.MessageV1Builder(
+            kFakeBluetoothChooser_Cancel_Name,
+            codec.align(FakeBluetoothChooser_Cancel_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeBluetoothChooser_Cancel_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kFakeBluetoothChooser_Rescan_Name:
+      var params = reader.decodeStruct(FakeBluetoothChooser_Rescan_Params);
+      this.rescan().then(function(response) {
+        var responseParams =
+            new FakeBluetoothChooser_Rescan_ResponseParams();
+        var builder = new codec.MessageV1Builder(
+            kFakeBluetoothChooser_Rescan_Name,
+            codec.align(FakeBluetoothChooser_Rescan_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(FakeBluetoothChooser_Rescan_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  function validateFakeBluetoothChooserRequest(messageValidator) {
+    var message = messageValidator.message;
+    var paramsClass = null;
+    switch (message.getName()) {
+      case kFakeBluetoothChooser_WaitForEvents_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeBluetoothChooser_WaitForEvents_Params;
+      break;
+      case kFakeBluetoothChooser_SelectPeripheral_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeBluetoothChooser_SelectPeripheral_Params;
+      break;
+      case kFakeBluetoothChooser_Cancel_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeBluetoothChooser_Cancel_Params;
+      break;
+      case kFakeBluetoothChooser_Rescan_Name:
+        if (message.expectsResponse())
+          paramsClass = FakeBluetoothChooser_Rescan_Params;
+      break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  function validateFakeBluetoothChooserResponse(messageValidator) {
+   var message = messageValidator.message;
+   var paramsClass = null;
+   switch (message.getName()) {
+      case kFakeBluetoothChooser_WaitForEvents_Name:
+        if (message.isResponse())
+          paramsClass = FakeBluetoothChooser_WaitForEvents_ResponseParams;
+        break;
+      case kFakeBluetoothChooser_SelectPeripheral_Name:
+        if (message.isResponse())
+          paramsClass = FakeBluetoothChooser_SelectPeripheral_ResponseParams;
+        break;
+      case kFakeBluetoothChooser_Cancel_Name:
+        if (message.isResponse())
+          paramsClass = FakeBluetoothChooser_Cancel_ResponseParams;
+        break;
+      case kFakeBluetoothChooser_Rescan_Name:
+        if (message.isResponse())
+          paramsClass = FakeBluetoothChooser_Rescan_ResponseParams;
+        break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  var FakeBluetoothChooser = {
+    name: 'content.mojom.FakeBluetoothChooser',
+    kVersion: 0,
+    ptrClass: FakeBluetoothChooserPtr,
+    proxyClass: FakeBluetoothChooserProxy,
+    stubClass: FakeBluetoothChooserStub,
+    validateRequest: validateFakeBluetoothChooserRequest,
+    validateResponse: validateFakeBluetoothChooserResponse,
+  };
+  FakeBluetoothChooserStub.prototype.validator = validateFakeBluetoothChooserRequest;
+  FakeBluetoothChooserProxy.prototype.validator = validateFakeBluetoothChooserResponse;
+  exports.ChooserEventType = ChooserEventType;
+  exports.FakeBluetoothChooserEvent = FakeBluetoothChooserEvent;
+  exports.FakeBluetoothChooser = FakeBluetoothChooser;
+  exports.FakeBluetoothChooserPtr = FakeBluetoothChooserPtr;
+  exports.FakeBluetoothChooserAssociatedPtr = FakeBluetoothChooserAssociatedPtr;
+})();
diff --git a/resources/chromium/fake_bluetooth_chooser.mojom.js.headers b/resources/chromium/fake_bluetooth_chooser.mojom.js.headers
new file mode 100644
index 0000000..6805c32
--- /dev/null
+++ b/resources/chromium/fake_bluetooth_chooser.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/resources/chromium/generic_sensor_mocks.js b/resources/chromium/generic_sensor_mocks.js
new file mode 100644
index 0000000..ff3a051
--- /dev/null
+++ b/resources/chromium/generic_sensor_mocks.js
@@ -0,0 +1,226 @@
+'use strict';
+
+var GenericSensorTest = (() => {
+  // Class that mocks Sensor interface defined in
+  // https://cs.chromium.org/chromium/src/services/device/public/mojom/sensor.mojom
+  class MockSensor {
+    constructor(sensorRequest, handle, offset, size, reportingMode) {
+      this.client_ = null;
+      this.reportingMode_ = reportingMode;
+      this.sensorReadingTimerId_ = null;
+      this.requestedFrequencies_ = [];
+      let rv = handle.mapBuffer(offset, size);
+      assert_equals(rv.result, Mojo.RESULT_OK, "Failed to map shared buffer");
+      this.buffer_ = new Float64Array(rv.buffer);
+      this.buffer_.fill(0);
+      this.binding_ = new mojo.Binding(device.mojom.Sensor, this,
+                                       sensorRequest);
+      this.binding_.setConnectionErrorHandler(() => {
+        this.reset();
+      });
+    }
+
+    getDefaultConfiguration() {
+      return Promise.resolve({frequency: 5});
+    }
+
+    addConfiguration(configuration) {
+      assert_not_equals(configuration, null, "Invalid sensor configuration.");
+
+      this.requestedFrequencies_.push(configuration.frequency);
+      // Sort using descending order.
+      this.requestedFrequencies_.sort(
+          (first, second) => { return second - first });
+
+      this.startReading();
+
+      return Promise.resolve({success: true});
+    }
+
+    removeConfiguration(configuration) {
+      let index = this.requestedFrequencies_.indexOf(configuration.frequency);
+      if (index == -1)
+        return;
+
+      this.requestedFrequencies_.splice(index, 1);
+
+      if (this.isReading) {
+        this.stopReading();
+        if (this.requestedFrequencies_.length !== 0)
+          this.startReading();
+      }
+    }
+
+    suspend() {
+      this.stopReading();
+    }
+
+    resume() {
+      this.startReading();
+    }
+
+    reset() {
+      this.stopReading();
+      this.requestedFrequencies_ = [];
+      this.buffer_.fill(0);
+      this.binding_.close();
+    }
+
+    startReading() {
+      if (this.isReading) {
+        console.warn("Sensor reading is already started.");
+        return;
+      }
+
+      if (this.requestedFrequencies_.length == 0) {
+        console.warn("Sensor reading cannot be started as" +
+                     "there are no configurations added.");
+        return;
+      }
+
+      const maxFrequencyHz = this.requestedFrequencies_[0];
+      const timeoutMs = (1 / maxFrequencyHz) * 1000;
+      this.sensorReadingTimerId_ = window.setInterval(() => {
+        // For all tests sensor reading should have monotonically
+        // increasing timestamp in seconds.
+        this.buffer_[1] = window.performance.now() * 0.001;
+        if (this.reportingMode_ === device.mojom.ReportingMode.ON_CHANGE) {
+          this.client_.sensorReadingChanged();
+        }
+      }, timeoutMs);
+    }
+
+    stopReading() {
+      if (this.isReading) {
+        window.clearInterval(this.sensorReadingTimerId_);
+        this.sensorReadingTimerId_ = null;
+      }
+    }
+
+     get isReading() {
+       this.sensorReadingTimerId_ !== null;
+     }
+  }
+
+  // Class that mocks SensorProvider interface defined in
+  // https://cs.chromium.org/chromium/src/services/device/public/mojom/sensor_provider.mojom
+  class MockSensorProvider {
+    constructor() {
+      this.readingSizeInBytes_ =
+          device.mojom.SensorInitParams.kReadBufferSizeForTests;
+      this.sharedBufferSizeInBytes_ = this.readingSizeInBytes_ *
+              device.mojom.SensorType.LAST;
+      let rv = Mojo.createSharedBuffer(this.sharedBufferSizeInBytes_);
+      assert_equals(rv.result, Mojo.RESULT_OK, "Failed to create buffer");
+      this.sharedBufferHandle_ = rv.handle;
+      this.activeSensor_ = null;
+      this.isContinuous_ = false;
+      this.binding_ = new mojo.Binding(device.mojom.SensorProvider, this);
+
+      this.interceptor_ = new MojoInterfaceInterceptor(
+          device.mojom.SensorProvider.name);
+      this.interceptor_.oninterfacerequest = e => {
+        this.binding_.bind(e.handle);
+        this.binding_.setConnectionErrorHandler(() => {
+          console.error("Mojo connection error");
+          this.reset();
+        });
+      };
+      this.interceptor_.start();
+    }
+
+    async getSensor(type) {
+      const offset = (device.mojom.SensorType.LAST - type) *
+                      this.readingSizeInBytes_;
+      const reportingMode = device.mojom.ReportingMode.ON_CHANGE;
+
+      let sensorPtr = new device.mojom.SensorPtr();
+      if (this.activeSensor_ == null) {
+        let mockSensor = new MockSensor(
+            mojo.makeRequest(sensorPtr), this.sharedBufferHandle_, offset,
+            this.readingSizeInBytes_, reportingMode);
+        this.activeSensor_ = mockSensor;
+        this.activeSensor_.client_ = new device.mojom.SensorClientPtr();
+      }
+
+      let rv = this.sharedBufferHandle_.duplicateBufferHandle();
+
+      assert_equals(rv.result, Mojo.RESULT_OK);
+      let maxAllowedFrequencyHz = 60;
+      if (type == device.mojom.SensorType.AMBIENT_LIGHT ||
+          type == device.mojom.SensorType.MAGNETOMETER) {
+        maxAllowedFrequencyHz = 10;
+      }
+
+      let initParams = new device.mojom.SensorInitParams({
+        sensor: sensorPtr,
+        clientRequest: mojo.makeRequest(this.activeSensor_.client_),
+        memory: rv.handle,
+        bufferOffset: offset,
+        mode: reportingMode,
+        defaultConfiguration: {frequency: 5},
+        minimumFrequency: 1,
+        maximumFrequency: maxAllowedFrequencyHz
+      });
+
+      return {result: device.mojom.SensorCreationResult.SUCCESS,
+              initParams: initParams};
+    }
+
+    async reset() {
+      if (this.activeSensor_ !== null) {
+        this.activeSensor_.reset();
+        this.activeSensor_ = null;
+      }
+      // Wait for an event loop iteration to let
+      // the pending mojo commands pass.
+      function schedule(func) {
+        return new Promise(resolve => {
+          setTimeout(() => {
+            func();
+            resolve();
+          }, 0);
+        });
+      }
+      await schedule(this.binding_.close.bind(this.binding_));
+      await schedule(this.interceptor_.stop.bind(this.interceptor_));
+    }
+  }
+
+  let testInternal = {
+    initialized: false,
+    sensorProvider: null
+  }
+
+  class GenericSensorTestChromium {
+    constructor() {
+      Object.freeze(this); // Make it immutable.
+    }
+
+    initialize() {
+      if (testInternal.initialized)
+        throw new Error('Call reset() before initialize().');
+
+      if (testRunner) { // Grant sensor permissions for Chromium testrunner.
+        ['accelerometer', 'gyroscope',
+         'magnetometer', 'ambient-light-sensor'].forEach((entry) => {
+          testRunner.setPermission(entry, 'granted',
+                                   location.origin, location.origin);
+        });
+      }
+
+      testInternal.sensorProvider = new MockSensorProvider;
+      testInternal.initialized = true;
+    }
+    // Resets state of sensor mocks between test runs.
+    async reset() {
+      if (!testInternal.initialized)
+        throw new Error('Call initialize() before reset().');
+      await testInternal.sensorProvider.reset();
+      testInternal.sensorProvider = null;
+      testInternal.initialized = false;
+    }
+  }
+
+  return GenericSensorTestChromium;
+})();
diff --git a/resources/chromium/generic_sensor_mocks.js.headers b/resources/chromium/generic_sensor_mocks.js.headers
new file mode 100644
index 0000000..6805c32
--- /dev/null
+++ b/resources/chromium/generic_sensor_mocks.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/resources/chromium/mojo_layouttest_test.mojom.js b/resources/chromium/mojo_layouttest_test.mojom.js
new file mode 100644
index 0000000..babaeda
--- /dev/null
+++ b/resources/chromium/mojo_layouttest_test.mojom.js
@@ -0,0 +1,264 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+  var mojomId = 'content/test/data/mojo_layouttest_test.mojom';
+  if (mojo.internal.isMojomLoaded(mojomId)) {
+    console.warn('The following mojom is loaded multiple times: ' + mojomId);
+    return;
+  }
+  mojo.internal.markMojomLoaded(mojomId);
+
+  // TODO(yzshen): Define these aliases to minimize the differences between the
+  // old/new modes. Remove them when the old mode goes away.
+  var bindings = mojo;
+  var associatedBindings = mojo;
+  var codec = mojo.internal;
+  var validator = mojo.internal;
+
+  var exports = mojo.internal.exposeNamespace('content.mojom');
+
+
+
+  function MojoLayoutTestHelper_Reverse_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  MojoLayoutTestHelper_Reverse_Params.prototype.initDefaults_ = function() {
+    this.message = null;
+  };
+  MojoLayoutTestHelper_Reverse_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  MojoLayoutTestHelper_Reverse_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate MojoLayoutTestHelper_Reverse_Params.message
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  MojoLayoutTestHelper_Reverse_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  MojoLayoutTestHelper_Reverse_Params.decode = function(decoder) {
+    var packed;
+    var val = new MojoLayoutTestHelper_Reverse_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.message = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  MojoLayoutTestHelper_Reverse_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(MojoLayoutTestHelper_Reverse_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.message);
+  };
+  function MojoLayoutTestHelper_Reverse_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  MojoLayoutTestHelper_Reverse_ResponseParams.prototype.initDefaults_ = function() {
+    this.reversed = null;
+  };
+  MojoLayoutTestHelper_Reverse_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  MojoLayoutTestHelper_Reverse_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate MojoLayoutTestHelper_Reverse_ResponseParams.reversed
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  MojoLayoutTestHelper_Reverse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  MojoLayoutTestHelper_Reverse_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new MojoLayoutTestHelper_Reverse_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.reversed = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  MojoLayoutTestHelper_Reverse_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(MojoLayoutTestHelper_Reverse_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.reversed);
+  };
+  var kMojoLayoutTestHelper_Reverse_Name = 0;
+
+  function MojoLayoutTestHelperPtr(handleOrPtrInfo) {
+    this.ptr = new bindings.InterfacePtrController(MojoLayoutTestHelper,
+                                                   handleOrPtrInfo);
+  }
+
+  function MojoLayoutTestHelperAssociatedPtr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        MojoLayoutTestHelper, associatedInterfacePtrInfo);
+  }
+
+  MojoLayoutTestHelperAssociatedPtr.prototype =
+      Object.create(MojoLayoutTestHelperPtr.prototype);
+  MojoLayoutTestHelperAssociatedPtr.prototype.constructor =
+      MojoLayoutTestHelperAssociatedPtr;
+
+  function MojoLayoutTestHelperProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+  MojoLayoutTestHelperPtr.prototype.reverse = function() {
+    return MojoLayoutTestHelperProxy.prototype.reverse
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  MojoLayoutTestHelperProxy.prototype.reverse = function(message) {
+    var params = new MojoLayoutTestHelper_Reverse_Params();
+    params.message = message;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kMojoLayoutTestHelper_Reverse_Name,
+          codec.align(MojoLayoutTestHelper_Reverse_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(MojoLayoutTestHelper_Reverse_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(MojoLayoutTestHelper_Reverse_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+
+  function MojoLayoutTestHelperStub(delegate) {
+    this.delegate_ = delegate;
+  }
+  MojoLayoutTestHelperStub.prototype.reverse = function(message) {
+    return this.delegate_ && this.delegate_.reverse && this.delegate_.reverse(message);
+  }
+
+  MojoLayoutTestHelperStub.prototype.accept = function(message) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    default:
+      return false;
+    }
+  };
+
+  MojoLayoutTestHelperStub.prototype.acceptWithResponder =
+      function(message, responder) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kMojoLayoutTestHelper_Reverse_Name:
+      var params = reader.decodeStruct(MojoLayoutTestHelper_Reverse_Params);
+      this.reverse(params.message).then(function(response) {
+        var responseParams =
+            new MojoLayoutTestHelper_Reverse_ResponseParams();
+        responseParams.reversed = response.reversed;
+        var builder = new codec.MessageV1Builder(
+            kMojoLayoutTestHelper_Reverse_Name,
+            codec.align(MojoLayoutTestHelper_Reverse_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(MojoLayoutTestHelper_Reverse_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  function validateMojoLayoutTestHelperRequest(messageValidator) {
+    var message = messageValidator.message;
+    var paramsClass = null;
+    switch (message.getName()) {
+      case kMojoLayoutTestHelper_Reverse_Name:
+        if (message.expectsResponse())
+          paramsClass = MojoLayoutTestHelper_Reverse_Params;
+      break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  function validateMojoLayoutTestHelperResponse(messageValidator) {
+   var message = messageValidator.message;
+   var paramsClass = null;
+   switch (message.getName()) {
+      case kMojoLayoutTestHelper_Reverse_Name:
+        if (message.isResponse())
+          paramsClass = MojoLayoutTestHelper_Reverse_ResponseParams;
+        break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  var MojoLayoutTestHelper = {
+    name: 'content.mojom.MojoLayoutTestHelper',
+    kVersion: 0,
+    ptrClass: MojoLayoutTestHelperPtr,
+    proxyClass: MojoLayoutTestHelperProxy,
+    stubClass: MojoLayoutTestHelperStub,
+    validateRequest: validateMojoLayoutTestHelperRequest,
+    validateResponse: validateMojoLayoutTestHelperResponse,
+  };
+  MojoLayoutTestHelperStub.prototype.validator = validateMojoLayoutTestHelperRequest;
+  MojoLayoutTestHelperProxy.prototype.validator = validateMojoLayoutTestHelperResponse;
+  exports.MojoLayoutTestHelper = MojoLayoutTestHelper;
+  exports.MojoLayoutTestHelperPtr = MojoLayoutTestHelperPtr;
+  exports.MojoLayoutTestHelperAssociatedPtr = MojoLayoutTestHelperAssociatedPtr;
+})();
diff --git a/resources/chromium/mojo_layouttest_test.mojom.js.headers b/resources/chromium/mojo_layouttest_test.mojom.js.headers
new file mode 100644
index 0000000..6805c32
--- /dev/null
+++ b/resources/chromium/mojo_layouttest_test.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/resources/chromium/sensor.mojom.js b/resources/chromium/sensor.mojom.js
new file mode 100644
index 0000000..daa9921
--- /dev/null
+++ b/resources/chromium/sensor.mojom.js
@@ -0,0 +1,1072 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+  var mojomId = 'services/device/public/mojom/sensor.mojom';
+  if (mojo.internal.isMojomLoaded(mojomId)) {
+    console.warn('The following mojom is loaded multiple times: ' + mojomId);
+    return;
+  }
+  mojo.internal.markMojomLoaded(mojomId);
+  var bindings = mojo;
+  var associatedBindings = mojo;
+  var codec = mojo.internal;
+  var validator = mojo.internal;
+
+  var exports = mojo.internal.exposeNamespace('device.mojom');
+
+
+  var SensorType = {};
+  SensorType.FIRST = 1;
+  SensorType.AMBIENT_LIGHT = SensorType.FIRST;
+  SensorType.PROXIMITY = SensorType.AMBIENT_LIGHT + 1;
+  SensorType.ACCELEROMETER = SensorType.PROXIMITY + 1;
+  SensorType.LINEAR_ACCELERATION = SensorType.ACCELEROMETER + 1;
+  SensorType.GYROSCOPE = SensorType.LINEAR_ACCELERATION + 1;
+  SensorType.MAGNETOMETER = SensorType.GYROSCOPE + 1;
+  SensorType.PRESSURE = SensorType.MAGNETOMETER + 1;
+  SensorType.ABSOLUTE_ORIENTATION_EULER_ANGLES = SensorType.PRESSURE + 1;
+  SensorType.ABSOLUTE_ORIENTATION_QUATERNION = SensorType.ABSOLUTE_ORIENTATION_EULER_ANGLES + 1;
+  SensorType.RELATIVE_ORIENTATION_EULER_ANGLES = SensorType.ABSOLUTE_ORIENTATION_QUATERNION + 1;
+  SensorType.RELATIVE_ORIENTATION_QUATERNION = SensorType.RELATIVE_ORIENTATION_EULER_ANGLES + 1;
+  SensorType.LAST = SensorType.RELATIVE_ORIENTATION_QUATERNION;
+
+  SensorType.isKnownEnumValue = function(value) {
+    switch (value) {
+    case 1:
+    case 2:
+    case 3:
+    case 4:
+    case 5:
+    case 6:
+    case 7:
+    case 8:
+    case 9:
+    case 10:
+    case 11:
+      return true;
+    }
+    return false;
+  };
+
+  SensorType.validate = function(enumValue) {
+    var isExtensible = false;
+    if (isExtensible || this.isKnownEnumValue(enumValue))
+      return validator.validationError.NONE;
+
+    return validator.validationError.UNKNOWN_ENUM_VALUE;
+  };
+  var ReportingMode = {};
+  ReportingMode.ON_CHANGE = 0;
+  ReportingMode.CONTINUOUS = ReportingMode.ON_CHANGE + 1;
+
+  ReportingMode.isKnownEnumValue = function(value) {
+    switch (value) {
+    case 0:
+    case 1:
+      return true;
+    }
+    return false;
+  };
+
+  ReportingMode.validate = function(enumValue) {
+    var isExtensible = false;
+    if (isExtensible || this.isKnownEnumValue(enumValue))
+      return validator.validationError.NONE;
+
+    return validator.validationError.UNKNOWN_ENUM_VALUE;
+  };
+
+  function SensorConfiguration(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  SensorConfiguration.prototype.initDefaults_ = function() {
+    this.frequency = 0;
+  };
+  SensorConfiguration.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  SensorConfiguration.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  SensorConfiguration.encodedSize = codec.kStructHeaderSize + 8;
+
+  SensorConfiguration.decode = function(decoder) {
+    var packed;
+    var val = new SensorConfiguration();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.frequency = decoder.decodeStruct(codec.Double);
+    return val;
+  };
+
+  SensorConfiguration.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(SensorConfiguration.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Double, val.frequency);
+  };
+  function Sensor_GetDefaultConfiguration_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_GetDefaultConfiguration_Params.prototype.initDefaults_ = function() {
+  };
+  Sensor_GetDefaultConfiguration_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_GetDefaultConfiguration_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_GetDefaultConfiguration_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  Sensor_GetDefaultConfiguration_Params.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_GetDefaultConfiguration_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  Sensor_GetDefaultConfiguration_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_GetDefaultConfiguration_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function Sensor_GetDefaultConfiguration_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_GetDefaultConfiguration_ResponseParams.prototype.initDefaults_ = function() {
+    this.configuration = null;
+  };
+  Sensor_GetDefaultConfiguration_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_GetDefaultConfiguration_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate Sensor_GetDefaultConfiguration_ResponseParams.configuration
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, SensorConfiguration, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_GetDefaultConfiguration_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  Sensor_GetDefaultConfiguration_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_GetDefaultConfiguration_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.configuration = decoder.decodeStructPointer(SensorConfiguration);
+    return val;
+  };
+
+  Sensor_GetDefaultConfiguration_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_GetDefaultConfiguration_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStructPointer(SensorConfiguration, val.configuration);
+  };
+  function Sensor_AddConfiguration_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_AddConfiguration_Params.prototype.initDefaults_ = function() {
+    this.configuration = null;
+  };
+  Sensor_AddConfiguration_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_AddConfiguration_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate Sensor_AddConfiguration_Params.configuration
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, SensorConfiguration, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_AddConfiguration_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  Sensor_AddConfiguration_Params.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_AddConfiguration_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.configuration = decoder.decodeStructPointer(SensorConfiguration);
+    return val;
+  };
+
+  Sensor_AddConfiguration_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_AddConfiguration_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStructPointer(SensorConfiguration, val.configuration);
+  };
+  function Sensor_AddConfiguration_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_AddConfiguration_ResponseParams.prototype.initDefaults_ = function() {
+    this.success = false;
+  };
+  Sensor_AddConfiguration_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_AddConfiguration_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_AddConfiguration_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+  Sensor_AddConfiguration_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_AddConfiguration_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.success = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  Sensor_AddConfiguration_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_AddConfiguration_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.success & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function Sensor_RemoveConfiguration_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_RemoveConfiguration_Params.prototype.initDefaults_ = function() {
+    this.configuration = null;
+  };
+  Sensor_RemoveConfiguration_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_RemoveConfiguration_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate Sensor_RemoveConfiguration_Params.configuration
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, SensorConfiguration, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_RemoveConfiguration_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  Sensor_RemoveConfiguration_Params.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_RemoveConfiguration_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.configuration = decoder.decodeStructPointer(SensorConfiguration);
+    return val;
+  };
+
+  Sensor_RemoveConfiguration_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_RemoveConfiguration_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStructPointer(SensorConfiguration, val.configuration);
+  };
+  function Sensor_Suspend_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_Suspend_Params.prototype.initDefaults_ = function() {
+  };
+  Sensor_Suspend_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_Suspend_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_Suspend_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  Sensor_Suspend_Params.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_Suspend_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  Sensor_Suspend_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_Suspend_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function Sensor_Resume_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_Resume_Params.prototype.initDefaults_ = function() {
+  };
+  Sensor_Resume_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_Resume_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_Resume_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  Sensor_Resume_Params.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_Resume_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  Sensor_Resume_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_Resume_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function Sensor_ConfigureReadingChangeNotifications_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  Sensor_ConfigureReadingChangeNotifications_Params.prototype.initDefaults_ = function() {
+    this.enabled = false;
+  };
+  Sensor_ConfigureReadingChangeNotifications_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  Sensor_ConfigureReadingChangeNotifications_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    return validator.validationError.NONE;
+  };
+
+  Sensor_ConfigureReadingChangeNotifications_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  Sensor_ConfigureReadingChangeNotifications_Params.decode = function(decoder) {
+    var packed;
+    var val = new Sensor_ConfigureReadingChangeNotifications_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    packed = decoder.readUint8();
+    val.enabled = (packed >> 0) & 1 ? true : false;
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  Sensor_ConfigureReadingChangeNotifications_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(Sensor_ConfigureReadingChangeNotifications_Params.encodedSize);
+    encoder.writeUint32(0);
+    packed = 0;
+    packed |= (val.enabled & 1) << 0
+    encoder.writeUint8(packed);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function SensorClient_RaiseError_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  SensorClient_RaiseError_Params.prototype.initDefaults_ = function() {
+  };
+  SensorClient_RaiseError_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  SensorClient_RaiseError_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  SensorClient_RaiseError_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  SensorClient_RaiseError_Params.decode = function(decoder) {
+    var packed;
+    var val = new SensorClient_RaiseError_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  SensorClient_RaiseError_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(SensorClient_RaiseError_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  function SensorClient_SensorReadingChanged_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  SensorClient_SensorReadingChanged_Params.prototype.initDefaults_ = function() {
+  };
+  SensorClient_SensorReadingChanged_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  SensorClient_SensorReadingChanged_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 8}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  SensorClient_SensorReadingChanged_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+  SensorClient_SensorReadingChanged_Params.decode = function(decoder) {
+    var packed;
+    var val = new SensorClient_SensorReadingChanged_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    return val;
+  };
+
+  SensorClient_SensorReadingChanged_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(SensorClient_SensorReadingChanged_Params.encodedSize);
+    encoder.writeUint32(0);
+  };
+  var kSensor_GetDefaultConfiguration_Name = 0;
+  var kSensor_AddConfiguration_Name = 1;
+  var kSensor_RemoveConfiguration_Name = 2;
+  var kSensor_Suspend_Name = 3;
+  var kSensor_Resume_Name = 4;
+  var kSensor_ConfigureReadingChangeNotifications_Name = 5;
+
+  function SensorPtr(handleOrPtrInfo) {
+    this.ptr = new bindings.InterfacePtrController(Sensor,
+                                                   handleOrPtrInfo);
+  }
+
+  function SensorAssociatedPtr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        Sensor, associatedInterfacePtrInfo);
+  }
+
+  SensorAssociatedPtr.prototype =
+      Object.create(SensorPtr.prototype);
+  SensorAssociatedPtr.prototype.constructor =
+      SensorAssociatedPtr;
+
+  function SensorProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+  SensorPtr.prototype.getDefaultConfiguration = function() {
+    return SensorProxy.prototype.getDefaultConfiguration
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorProxy.prototype.getDefaultConfiguration = function() {
+    var params = new Sensor_GetDefaultConfiguration_Params();
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kSensor_GetDefaultConfiguration_Name,
+          codec.align(Sensor_GetDefaultConfiguration_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(Sensor_GetDefaultConfiguration_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(Sensor_GetDefaultConfiguration_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  SensorPtr.prototype.addConfiguration = function() {
+    return SensorProxy.prototype.addConfiguration
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorProxy.prototype.addConfiguration = function(configuration) {
+    var params = new Sensor_AddConfiguration_Params();
+    params.configuration = configuration;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kSensor_AddConfiguration_Name,
+          codec.align(Sensor_AddConfiguration_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(Sensor_AddConfiguration_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(Sensor_AddConfiguration_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+  SensorPtr.prototype.removeConfiguration = function() {
+    return SensorProxy.prototype.removeConfiguration
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorProxy.prototype.removeConfiguration = function(configuration) {
+    var params = new Sensor_RemoveConfiguration_Params();
+    params.configuration = configuration;
+    var builder = new codec.MessageV0Builder(
+        kSensor_RemoveConfiguration_Name,
+        codec.align(Sensor_RemoveConfiguration_Params.encodedSize));
+    builder.encodeStruct(Sensor_RemoveConfiguration_Params, params);
+    var message = builder.finish();
+    this.receiver_.accept(message);
+  };
+  SensorPtr.prototype.suspend = function() {
+    return SensorProxy.prototype.suspend
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorProxy.prototype.suspend = function() {
+    var params = new Sensor_Suspend_Params();
+    var builder = new codec.MessageV0Builder(
+        kSensor_Suspend_Name,
+        codec.align(Sensor_Suspend_Params.encodedSize));
+    builder.encodeStruct(Sensor_Suspend_Params, params);
+    var message = builder.finish();
+    this.receiver_.accept(message);
+  };
+  SensorPtr.prototype.resume = function() {
+    return SensorProxy.prototype.resume
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorProxy.prototype.resume = function() {
+    var params = new Sensor_Resume_Params();
+    var builder = new codec.MessageV0Builder(
+        kSensor_Resume_Name,
+        codec.align(Sensor_Resume_Params.encodedSize));
+    builder.encodeStruct(Sensor_Resume_Params, params);
+    var message = builder.finish();
+    this.receiver_.accept(message);
+  };
+  SensorPtr.prototype.configureReadingChangeNotifications = function() {
+    return SensorProxy.prototype.configureReadingChangeNotifications
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorProxy.prototype.configureReadingChangeNotifications = function(enabled) {
+    var params = new Sensor_ConfigureReadingChangeNotifications_Params();
+    params.enabled = enabled;
+    var builder = new codec.MessageV0Builder(
+        kSensor_ConfigureReadingChangeNotifications_Name,
+        codec.align(Sensor_ConfigureReadingChangeNotifications_Params.encodedSize));
+    builder.encodeStruct(Sensor_ConfigureReadingChangeNotifications_Params, params);
+    var message = builder.finish();
+    this.receiver_.accept(message);
+  };
+
+  function SensorStub(delegate) {
+    this.delegate_ = delegate;
+  }
+  SensorStub.prototype.getDefaultConfiguration = function() {
+    return this.delegate_ && this.delegate_.getDefaultConfiguration && this.delegate_.getDefaultConfiguration();
+  }
+  SensorStub.prototype.addConfiguration = function(configuration) {
+    return this.delegate_ && this.delegate_.addConfiguration && this.delegate_.addConfiguration(configuration);
+  }
+  SensorStub.prototype.removeConfiguration = function(configuration) {
+    return this.delegate_ && this.delegate_.removeConfiguration && this.delegate_.removeConfiguration(configuration);
+  }
+  SensorStub.prototype.suspend = function() {
+    return this.delegate_ && this.delegate_.suspend && this.delegate_.suspend();
+  }
+  SensorStub.prototype.resume = function() {
+    return this.delegate_ && this.delegate_.resume && this.delegate_.resume();
+  }
+  SensorStub.prototype.configureReadingChangeNotifications = function(enabled) {
+    return this.delegate_ && this.delegate_.configureReadingChangeNotifications && this.delegate_.configureReadingChangeNotifications(enabled);
+  }
+
+  SensorStub.prototype.accept = function(message) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kSensor_RemoveConfiguration_Name:
+      var params = reader.decodeStruct(Sensor_RemoveConfiguration_Params);
+      this.removeConfiguration(params.configuration);
+      return true;
+    case kSensor_Suspend_Name:
+      var params = reader.decodeStruct(Sensor_Suspend_Params);
+      this.suspend();
+      return true;
+    case kSensor_Resume_Name:
+      var params = reader.decodeStruct(Sensor_Resume_Params);
+      this.resume();
+      return true;
+    case kSensor_ConfigureReadingChangeNotifications_Name:
+      var params = reader.decodeStruct(Sensor_ConfigureReadingChangeNotifications_Params);
+      this.configureReadingChangeNotifications(params.enabled);
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  SensorStub.prototype.acceptWithResponder =
+      function(message, responder) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kSensor_GetDefaultConfiguration_Name:
+      var params = reader.decodeStruct(Sensor_GetDefaultConfiguration_Params);
+      this.getDefaultConfiguration().then(function(response) {
+        var responseParams =
+            new Sensor_GetDefaultConfiguration_ResponseParams();
+        responseParams.configuration = response.configuration;
+        var builder = new codec.MessageV1Builder(
+            kSensor_GetDefaultConfiguration_Name,
+            codec.align(Sensor_GetDefaultConfiguration_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(Sensor_GetDefaultConfiguration_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    case kSensor_AddConfiguration_Name:
+      var params = reader.decodeStruct(Sensor_AddConfiguration_Params);
+      this.addConfiguration(params.configuration).then(function(response) {
+        var responseParams =
+            new Sensor_AddConfiguration_ResponseParams();
+        responseParams.success = response.success;
+        var builder = new codec.MessageV1Builder(
+            kSensor_AddConfiguration_Name,
+            codec.align(Sensor_AddConfiguration_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(Sensor_AddConfiguration_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  function validateSensorRequest(messageValidator) {
+    var message = messageValidator.message;
+    var paramsClass = null;
+    switch (message.getName()) {
+      case kSensor_GetDefaultConfiguration_Name:
+        if (message.expectsResponse())
+          paramsClass = Sensor_GetDefaultConfiguration_Params;
+      break;
+      case kSensor_AddConfiguration_Name:
+        if (message.expectsResponse())
+          paramsClass = Sensor_AddConfiguration_Params;
+      break;
+      case kSensor_RemoveConfiguration_Name:
+        if (!message.expectsResponse() && !message.isResponse())
+          paramsClass = Sensor_RemoveConfiguration_Params;
+      break;
+      case kSensor_Suspend_Name:
+        if (!message.expectsResponse() && !message.isResponse())
+          paramsClass = Sensor_Suspend_Params;
+      break;
+      case kSensor_Resume_Name:
+        if (!message.expectsResponse() && !message.isResponse())
+          paramsClass = Sensor_Resume_Params;
+      break;
+      case kSensor_ConfigureReadingChangeNotifications_Name:
+        if (!message.expectsResponse() && !message.isResponse())
+          paramsClass = Sensor_ConfigureReadingChangeNotifications_Params;
+      break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  function validateSensorResponse(messageValidator) {
+   var message = messageValidator.message;
+   var paramsClass = null;
+   switch (message.getName()) {
+      case kSensor_GetDefaultConfiguration_Name:
+        if (message.isResponse())
+          paramsClass = Sensor_GetDefaultConfiguration_ResponseParams;
+        break;
+      case kSensor_AddConfiguration_Name:
+        if (message.isResponse())
+          paramsClass = Sensor_AddConfiguration_ResponseParams;
+        break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  var Sensor = {
+    name: 'device.mojom.Sensor',
+    kVersion: 0,
+    ptrClass: SensorPtr,
+    proxyClass: SensorProxy,
+    stubClass: SensorStub,
+    validateRequest: validateSensorRequest,
+    validateResponse: validateSensorResponse,
+  };
+  SensorStub.prototype.validator = validateSensorRequest;
+  SensorProxy.prototype.validator = validateSensorResponse;
+  var kSensorClient_RaiseError_Name = 0;
+  var kSensorClient_SensorReadingChanged_Name = 1;
+
+  function SensorClientPtr(handleOrPtrInfo) {
+    this.ptr = new bindings.InterfacePtrController(SensorClient,
+                                                   handleOrPtrInfo);
+  }
+
+  function SensorClientAssociatedPtr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        SensorClient, associatedInterfacePtrInfo);
+  }
+
+  SensorClientAssociatedPtr.prototype =
+      Object.create(SensorClientPtr.prototype);
+  SensorClientAssociatedPtr.prototype.constructor =
+      SensorClientAssociatedPtr;
+
+  function SensorClientProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+  SensorClientPtr.prototype.raiseError = function() {
+    return SensorClientProxy.prototype.raiseError
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorClientProxy.prototype.raiseError = function() {
+    var params = new SensorClient_RaiseError_Params();
+    var builder = new codec.MessageV0Builder(
+        kSensorClient_RaiseError_Name,
+        codec.align(SensorClient_RaiseError_Params.encodedSize));
+    builder.encodeStruct(SensorClient_RaiseError_Params, params);
+    var message = builder.finish();
+    this.receiver_.accept(message);
+  };
+  SensorClientPtr.prototype.sensorReadingChanged = function() {
+    return SensorClientProxy.prototype.sensorReadingChanged
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorClientProxy.prototype.sensorReadingChanged = function() {
+    var params = new SensorClient_SensorReadingChanged_Params();
+    var builder = new codec.MessageV0Builder(
+        kSensorClient_SensorReadingChanged_Name,
+        codec.align(SensorClient_SensorReadingChanged_Params.encodedSize));
+    builder.encodeStruct(SensorClient_SensorReadingChanged_Params, params);
+    var message = builder.finish();
+    this.receiver_.accept(message);
+  };
+
+  function SensorClientStub(delegate) {
+    this.delegate_ = delegate;
+  }
+  SensorClientStub.prototype.raiseError = function() {
+    return this.delegate_ && this.delegate_.raiseError && this.delegate_.raiseError();
+  }
+  SensorClientStub.prototype.sensorReadingChanged = function() {
+    return this.delegate_ && this.delegate_.sensorReadingChanged && this.delegate_.sensorReadingChanged();
+  }
+
+  SensorClientStub.prototype.accept = function(message) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kSensorClient_RaiseError_Name:
+      var params = reader.decodeStruct(SensorClient_RaiseError_Params);
+      this.raiseError();
+      return true;
+    case kSensorClient_SensorReadingChanged_Name:
+      var params = reader.decodeStruct(SensorClient_SensorReadingChanged_Params);
+      this.sensorReadingChanged();
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  SensorClientStub.prototype.acceptWithResponder =
+      function(message, responder) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    default:
+      return false;
+    }
+  };
+
+  function validateSensorClientRequest(messageValidator) {
+    var message = messageValidator.message;
+    var paramsClass = null;
+    switch (message.getName()) {
+      case kSensorClient_RaiseError_Name:
+        if (!message.expectsResponse() && !message.isResponse())
+          paramsClass = SensorClient_RaiseError_Params;
+      break;
+      case kSensorClient_SensorReadingChanged_Name:
+        if (!message.expectsResponse() && !message.isResponse())
+          paramsClass = SensorClient_SensorReadingChanged_Params;
+      break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  function validateSensorClientResponse(messageValidator) {
+    return validator.validationError.NONE;
+  }
+
+  var SensorClient = {
+    name: 'device.mojom.SensorClient',
+    kVersion: 0,
+    ptrClass: SensorClientPtr,
+    proxyClass: SensorClientProxy,
+    stubClass: SensorClientStub,
+    validateRequest: validateSensorClientRequest,
+    validateResponse: null,
+  };
+  SensorClientStub.prototype.validator = validateSensorClientRequest;
+  SensorClientProxy.prototype.validator = null;
+  exports.SensorType = SensorType;
+  exports.ReportingMode = ReportingMode;
+  exports.SensorConfiguration = SensorConfiguration;
+  exports.Sensor = Sensor;
+  exports.SensorPtr = SensorPtr;
+  exports.SensorAssociatedPtr = SensorAssociatedPtr;
+  exports.SensorClient = SensorClient;
+  exports.SensorClientPtr = SensorClientPtr;
+  exports.SensorClientAssociatedPtr = SensorClientAssociatedPtr;
+})();
diff --git a/resources/chromium/sensor_provider.mojom.js b/resources/chromium/sensor_provider.mojom.js
new file mode 100644
index 0000000..d74ad97
--- /dev/null
+++ b/resources/chromium/sensor_provider.mojom.js
@@ -0,0 +1,429 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+  var mojomId = 'services/device/public/mojom/sensor_provider.mojom';
+  if (mojo.internal.isMojomLoaded(mojomId)) {
+    console.warn('The following mojom is loaded multiple times: ' + mojomId);
+    return;
+  }
+  mojo.internal.markMojomLoaded(mojomId);
+  var bindings = mojo;
+  var associatedBindings = mojo;
+  var codec = mojo.internal;
+  var validator = mojo.internal;
+
+  var exports = mojo.internal.exposeNamespace('device.mojom');
+  var sensor$ =
+      mojo.internal.exposeNamespace('device.mojom');
+  if (mojo.config.autoLoadMojomDeps) {
+    mojo.internal.loadMojomIfNecessary(
+        'services/device/public/mojom/sensor.mojom', 'sensor.mojom.js');
+  }
+
+
+  var SensorCreationResult = {};
+  SensorCreationResult.SUCCESS = 0;
+  SensorCreationResult.ERROR_NOT_AVAILABLE = SensorCreationResult.SUCCESS + 1;
+  SensorCreationResult.ERROR_NOT_ALLOWED = SensorCreationResult.ERROR_NOT_AVAILABLE + 1;
+
+  SensorCreationResult.isKnownEnumValue = function(value) {
+    switch (value) {
+    case 0:
+    case 1:
+    case 2:
+      return true;
+    }
+    return false;
+  };
+
+  SensorCreationResult.validate = function(enumValue) {
+    var isExtensible = false;
+    if (isExtensible || this.isKnownEnumValue(enumValue))
+      return validator.validationError.NONE;
+
+    return validator.validationError.UNKNOWN_ENUM_VALUE;
+  };
+
+  function SensorInitParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  SensorInitParams.kReadBufferSizeForTests = 48;
+  SensorInitParams.prototype.initDefaults_ = function() {
+    this.sensor = new sensor$.SensorPtr();
+    this.clientRequest = new bindings.InterfaceRequest();
+    this.memory = null;
+    this.bufferOffset = 0;
+    this.mode = 0;
+    this.defaultConfiguration = null;
+    this.maximumFrequency = 0;
+    this.minimumFrequency = 0;
+  };
+  SensorInitParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  SensorInitParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 64}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate SensorInitParams.sensor
+    err = messageValidator.validateInterface(offset + codec.kStructHeaderSize + 0, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate SensorInitParams.clientRequest
+    err = messageValidator.validateInterfaceRequest(offset + codec.kStructHeaderSize + 8, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate SensorInitParams.memory
+    err = messageValidator.validateHandle(offset + codec.kStructHeaderSize + 12, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    // validate SensorInitParams.mode
+    err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 24, sensor$.ReportingMode);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate SensorInitParams.defaultConfiguration
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 32, sensor$.SensorConfiguration, false);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+
+    return validator.validationError.NONE;
+  };
+
+  SensorInitParams.encodedSize = codec.kStructHeaderSize + 56;
+
+  SensorInitParams.decode = function(decoder) {
+    var packed;
+    var val = new SensorInitParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.sensor = decoder.decodeStruct(new codec.Interface(sensor$.SensorPtr));
+    val.clientRequest = decoder.decodeStruct(codec.InterfaceRequest);
+    val.memory = decoder.decodeStruct(codec.Handle);
+    val.bufferOffset = decoder.decodeStruct(codec.Uint64);
+    val.mode = decoder.decodeStruct(codec.Int32);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.defaultConfiguration = decoder.decodeStructPointer(sensor$.SensorConfiguration);
+    val.maximumFrequency = decoder.decodeStruct(codec.Double);
+    val.minimumFrequency = decoder.decodeStruct(codec.Double);
+    return val;
+  };
+
+  SensorInitParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(SensorInitParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(new codec.Interface(sensor$.SensorPtr), val.sensor);
+    encoder.encodeStruct(codec.InterfaceRequest, val.clientRequest);
+    encoder.encodeStruct(codec.Handle, val.memory);
+    encoder.encodeStruct(codec.Uint64, val.bufferOffset);
+    encoder.encodeStruct(codec.Int32, val.mode);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStructPointer(sensor$.SensorConfiguration, val.defaultConfiguration);
+    encoder.encodeStruct(codec.Double, val.maximumFrequency);
+    encoder.encodeStruct(codec.Double, val.minimumFrequency);
+  };
+  function SensorProvider_GetSensor_Params(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  SensorProvider_GetSensor_Params.prototype.initDefaults_ = function() {
+    this.type = 0;
+  };
+  SensorProvider_GetSensor_Params.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  SensorProvider_GetSensor_Params.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate SensorProvider_GetSensor_Params.type
+    err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, sensor$.SensorType);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  SensorProvider_GetSensor_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+  SensorProvider_GetSensor_Params.decode = function(decoder) {
+    var packed;
+    var val = new SensorProvider_GetSensor_Params();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.type = decoder.decodeStruct(codec.Int32);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    return val;
+  };
+
+  SensorProvider_GetSensor_Params.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(SensorProvider_GetSensor_Params.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Int32, val.type);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+  };
+  function SensorProvider_GetSensor_ResponseParams(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  SensorProvider_GetSensor_ResponseParams.prototype.initDefaults_ = function() {
+    this.result = 0;
+    this.initParams = null;
+  };
+  SensorProvider_GetSensor_ResponseParams.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  SensorProvider_GetSensor_ResponseParams.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 24}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate SensorProvider_GetSensor_ResponseParams.result
+    err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, SensorCreationResult);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate SensorProvider_GetSensor_ResponseParams.initParams
+    err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, SensorInitParams, true);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  SensorProvider_GetSensor_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+  SensorProvider_GetSensor_ResponseParams.decode = function(decoder) {
+    var packed;
+    var val = new SensorProvider_GetSensor_ResponseParams();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.result = decoder.decodeStruct(codec.Int32);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    decoder.skip(1);
+    val.initParams = decoder.decodeStructPointer(SensorInitParams);
+    return val;
+  };
+
+  SensorProvider_GetSensor_ResponseParams.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(SensorProvider_GetSensor_ResponseParams.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.Int32, val.result);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.skip(1);
+    encoder.encodeStructPointer(SensorInitParams, val.initParams);
+  };
+  var kSensorProvider_GetSensor_Name = 0;
+
+  function SensorProviderPtr(handleOrPtrInfo) {
+    this.ptr = new bindings.InterfacePtrController(SensorProvider,
+                                                   handleOrPtrInfo);
+  }
+
+  function SensorProviderAssociatedPtr(associatedInterfacePtrInfo) {
+    this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+        SensorProvider, associatedInterfacePtrInfo);
+  }
+
+  SensorProviderAssociatedPtr.prototype =
+      Object.create(SensorProviderPtr.prototype);
+  SensorProviderAssociatedPtr.prototype.constructor =
+      SensorProviderAssociatedPtr;
+
+  function SensorProviderProxy(receiver) {
+    this.receiver_ = receiver;
+  }
+  SensorProviderPtr.prototype.getSensor = function() {
+    return SensorProviderProxy.prototype.getSensor
+        .apply(this.ptr.getProxy(), arguments);
+  };
+
+  SensorProviderProxy.prototype.getSensor = function(type) {
+    var params = new SensorProvider_GetSensor_Params();
+    params.type = type;
+    return new Promise(function(resolve, reject) {
+      var builder = new codec.MessageV1Builder(
+          kSensorProvider_GetSensor_Name,
+          codec.align(SensorProvider_GetSensor_Params.encodedSize),
+          codec.kMessageExpectsResponse, 0);
+      builder.encodeStruct(SensorProvider_GetSensor_Params, params);
+      var message = builder.finish();
+      this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+        var reader = new codec.MessageReader(message);
+        var responseParams =
+            reader.decodeStruct(SensorProvider_GetSensor_ResponseParams);
+        resolve(responseParams);
+      }).catch(function(result) {
+        reject(Error("Connection error: " + result));
+      });
+    }.bind(this));
+  };
+
+  function SensorProviderStub(delegate) {
+    this.delegate_ = delegate;
+  }
+  SensorProviderStub.prototype.getSensor = function(type) {
+    return this.delegate_ && this.delegate_.getSensor && this.delegate_.getSensor(type);
+  }
+
+  SensorProviderStub.prototype.accept = function(message) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    default:
+      return false;
+    }
+  };
+
+  SensorProviderStub.prototype.acceptWithResponder =
+      function(message, responder) {
+    var reader = new codec.MessageReader(message);
+    switch (reader.messageName) {
+    case kSensorProvider_GetSensor_Name:
+      var params = reader.decodeStruct(SensorProvider_GetSensor_Params);
+      this.getSensor(params.type).then(function(response) {
+        var responseParams =
+            new SensorProvider_GetSensor_ResponseParams();
+        responseParams.result = response.result;
+        responseParams.initParams = response.initParams;
+        var builder = new codec.MessageV1Builder(
+            kSensorProvider_GetSensor_Name,
+            codec.align(SensorProvider_GetSensor_ResponseParams.encodedSize),
+            codec.kMessageIsResponse, reader.requestID);
+        builder.encodeStruct(SensorProvider_GetSensor_ResponseParams,
+                             responseParams);
+        var message = builder.finish();
+        responder.accept(message);
+      });
+      return true;
+    default:
+      return false;
+    }
+  };
+
+  function validateSensorProviderRequest(messageValidator) {
+    var message = messageValidator.message;
+    var paramsClass = null;
+    switch (message.getName()) {
+      case kSensorProvider_GetSensor_Name:
+        if (message.expectsResponse())
+          paramsClass = SensorProvider_GetSensor_Params;
+      break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  function validateSensorProviderResponse(messageValidator) {
+   var message = messageValidator.message;
+   var paramsClass = null;
+   switch (message.getName()) {
+      case kSensorProvider_GetSensor_Name:
+        if (message.isResponse())
+          paramsClass = SensorProvider_GetSensor_ResponseParams;
+        break;
+    }
+    if (paramsClass === null)
+      return validator.validationError.NONE;
+    return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+  }
+
+  var SensorProvider = {
+    name: 'device.mojom.SensorProvider',
+    kVersion: 0,
+    ptrClass: SensorProviderPtr,
+    proxyClass: SensorProviderProxy,
+    stubClass: SensorProviderStub,
+    validateRequest: validateSensorProviderRequest,
+    validateResponse: validateSensorProviderResponse,
+  };
+  SensorProviderStub.prototype.validator = validateSensorProviderRequest;
+  SensorProviderProxy.prototype.validator = validateSensorProviderResponse;
+  exports.SensorCreationResult = SensorCreationResult;
+  exports.SensorInitParams = SensorInitParams;
+  exports.SensorProvider = SensorProvider;
+  exports.SensorProviderPtr = SensorProviderPtr;
+  exports.SensorProviderAssociatedPtr = SensorProviderAssociatedPtr;
+})();
diff --git a/resources/chromium/string16.mojom.js b/resources/chromium/string16.mojom.js
index 058956b..9dbb356 100644
--- a/resources/chromium/string16.mojom.js
+++ b/resources/chromium/string16.mojom.js
@@ -7,7 +7,7 @@
 if ((typeof mojo !== 'undefined')  && mojo.internal && mojo.config) {
 
 (function() {
-  var mojomId = 'mojo/common/string16.mojom';
+  var mojomId = 'mojo/public/mojom/base/string16.mojom';
   if (mojo.internal.isMojomLoaded(mojomId)) {
     console.warn('The following mojom is loaded multiple times: ' + mojomId);
     return;
@@ -87,7 +87,7 @@
 
 if ((typeof mojo === 'undefined') || !mojo.internal || !mojo.config) {
 
-define("mojo/common/string16.mojom", [
+define("mojo/public/mojom/base/string16.mojom", [
     "mojo/public/js/associated_bindings",
     "mojo/public/js/bindings",
     "mojo/public/js/codec",
diff --git a/resources/chromium/uuid.mojom.js b/resources/chromium/uuid.mojom.js
new file mode 100644
index 0000000..3b1b616
--- /dev/null
+++ b/resources/chromium/uuid.mojom.js
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+  var mojomId = 'device/bluetooth/public/mojom/uuid.mojom';
+  if (mojo.internal.isMojomLoaded(mojomId)) {
+    console.warn('The following mojom is loaded multiple times: ' + mojomId);
+    return;
+  }
+  mojo.internal.markMojomLoaded(mojomId);
+  var bindings = mojo;
+  var associatedBindings = mojo;
+  var codec = mojo.internal;
+  var validator = mojo.internal;
+
+  var exports = mojo.internal.exposeNamespace('bluetooth.mojom');
+
+
+
+  function UUID(values) {
+    this.initDefaults_();
+    this.initFields_(values);
+  }
+
+
+  UUID.prototype.initDefaults_ = function() {
+    this.uuid = null;
+  };
+  UUID.prototype.initFields_ = function(fields) {
+    for(var field in fields) {
+        if (this.hasOwnProperty(field))
+          this[field] = fields[field];
+    }
+  };
+
+  UUID.validate = function(messageValidator, offset) {
+    var err;
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    var kVersionSizes = [
+      {version: 0, numBytes: 16}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+
+    // validate UUID.uuid
+    err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+    if (err !== validator.validationError.NONE)
+        return err;
+
+    return validator.validationError.NONE;
+  };
+
+  UUID.encodedSize = codec.kStructHeaderSize + 8;
+
+  UUID.decode = function(decoder) {
+    var packed;
+    var val = new UUID();
+    var numberOfBytes = decoder.readUint32();
+    var version = decoder.readUint32();
+    val.uuid = decoder.decodeStruct(codec.String);
+    return val;
+  };
+
+  UUID.encode = function(encoder, val) {
+    var packed;
+    encoder.writeUint32(UUID.encodedSize);
+    encoder.writeUint32(0);
+    encoder.encodeStruct(codec.String, val.uuid);
+  };
+  exports.UUID = UUID;
+})();
\ No newline at end of file
diff --git a/resources/chromium/uuid.mojom.js.headers b/resources/chromium/uuid.mojom.js.headers
new file mode 100644
index 0000000..6805c32
--- /dev/null
+++ b/resources/chromium/uuid.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/resources/chromium/web-bluetooth-test.js b/resources/chromium/web-bluetooth-test.js
new file mode 100644
index 0000000..1700b04
--- /dev/null
+++ b/resources/chromium/web-bluetooth-test.js
@@ -0,0 +1,526 @@
+'use strict';
+
+function toMojoCentralState(state) {
+  switch (state) {
+    case 'absent':
+      return bluetooth.mojom.CentralState.ABSENT;
+    case 'powered-off':
+      return bluetooth.mojom.CentralState.POWERED_OFF;
+    case 'powered-on':
+      return bluetooth.mojom.CentralState.POWERED_ON;
+    default:
+      throw `Unsupported value ${state} for state.`;
+  }
+}
+
+// Canonicalizes UUIDs and converts them to Mojo UUIDs.
+function canonicalizeAndConvertToMojoUUID(uuids) {
+  let canonicalUUIDs = uuids.map(val => ({uuid: BluetoothUUID.getService(val)}));
+  return canonicalUUIDs;
+}
+
+// Converts WebIDL a record<DOMString, BufferSource> to a map<K, array<uint8>> to
+// use for Mojo, where the value for K is calculated using keyFn.
+function convertToMojoMap(record, keyFn) {
+  let map = new Map();
+  for (const [key, value] of Object.entries(record)) {
+    let buffer = ArrayBuffer.isView(value) ? value.buffer : value;
+    map.set(keyFn(key), Array.from(new Uint8Array(buffer)));
+  }
+  return map;
+}
+
+// Mapping of the property names of
+// BluetoothCharacteristicProperties defined in
+// https://webbluetoothcg.github.io/web-bluetooth/#characteristicproperties
+// to property names of the CharacteristicProperties mojo struct.
+const CHARACTERISTIC_PROPERTIES_WEB_TO_MOJO = {
+  broadcast: 'broadcast',
+  read: 'read',
+  write_without_response: 'write_without_response',
+  write: 'write',
+  notify: 'notify',
+  indicate: 'indicate',
+  authenticatedSignedWrites: 'authenticated_signed_writes',
+  extended_properties: 'extended_properties',
+};
+
+function ArrayToMojoCharacteristicProperties(arr) {
+  let struct = new bluetooth.mojom.CharacteristicProperties();
+
+  arr.forEach(val => {
+    let mojo_property =
+      CHARACTERISTIC_PROPERTIES_WEB_TO_MOJO[val];
+
+    if (struct.hasOwnProperty(mojo_property))
+      struct[mojo_property] = true;
+    else
+      throw `Invalid member '${val}' for CharacteristicProperties`;
+  });
+
+  return struct;
+}
+
+class FakeBluetooth {
+  constructor() {
+    this.fake_bluetooth_ptr_ = new bluetooth.mojom.FakeBluetoothPtr();
+    Mojo.bindInterface(bluetooth.mojom.FakeBluetooth.name,
+        mojo.makeRequest(this.fake_bluetooth_ptr_).handle, 'process');
+  }
+
+  // Set it to indicate whether the platform supports BLE. For example,
+  // Windows 7 is a platform that doesn't support Low Energy. On the other
+  // hand Windows 10 is a platform that does support LE, even if there is no
+  // Bluetooth radio present.
+  async setLESupported(supported) {
+    if (typeof supported !== 'boolean') throw 'Type Not Supported';
+    await this.fake_bluetooth_ptr_.setLESupported(supported);
+  }
+
+  // Returns a promise that resolves with a FakeCentral that clients can use
+  // to simulate events that a device in the Central/Observer role would
+  // receive as well as monitor the operations performed by the device in the
+  // Central/Observer role.
+  // Calls sets LE as supported.
+  //
+  // A "Central" object would allow its clients to receive advertising events
+  // and initiate connections to peripherals i.e. operations of two roles
+  // defined by the Bluetooth Spec: Observer and Central.
+  // See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an
+  // LE Physical Transport".
+  async simulateCentral({state}) {
+    await this.setLESupported(true);
+
+    let {fakeCentral: fake_central_ptr} =
+      await this.fake_bluetooth_ptr_.simulateCentral(
+        toMojoCentralState(state));
+    return new FakeCentral(fake_central_ptr);
+  }
+
+  // Returns true if there are no pending responses.
+  async allResponsesConsumed() {
+    let {consumed} = await this.fake_bluetooth_ptr_.allResponsesConsumed();
+    return consumed;
+  }
+
+  // Returns a promise that resolves with a FakeChooser that clients can use to
+  // simulate chooser events.
+  async getManualChooser() {
+    if (typeof this.fake_chooser_ === 'undefined') {
+      this.fake_chooser_ = new FakeChooser();
+    }
+    return this.fake_chooser_;
+  }
+}
+
+// FakeCentral allows clients to simulate events that a device in the
+// Central/Observer role would receive as well as monitor the operations
+// performed by the device in the Central/Observer role.
+class FakeCentral {
+  constructor(fake_central_ptr) {
+    this.fake_central_ptr_ = fake_central_ptr;
+    this.peripherals_ = new Map();
+  }
+
+  // Simulates a peripheral with |address|, |name| and |known_service_uuids|
+  // that has already been connected to the system. If the peripheral existed
+  // already it updates its name and known UUIDs. |known_service_uuids| should
+  // be an array of BluetoothServiceUUIDs
+  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
+  //
+  // Platforms offer methods to retrieve devices that have already been
+  // connected to the system or weren't connected through the UA e.g. a user
+  // connected a peripheral through the system's settings. This method is
+  // intended to simulate peripherals that those methods would return.
+  async simulatePreconnectedPeripheral({
+    address, name, knownServiceUUIDs = []}) {
+
+    await this.fake_central_ptr_.simulatePreconnectedPeripheral(
+      address, name, canonicalizeAndConvertToMojoUUID(knownServiceUUIDs));
+
+    return this.fetchOrCreatePeripheral_(address);
+  }
+
+  // Simulates an advertisement packet described by |scanResult| being received
+  // from a device. If central is currently scanning, the device will appear on
+  // the list of discovered devices.
+  async simulateAdvertisementReceived(scanResult) {
+    if ('uuids' in scanResult.scanRecord) {
+      scanResult.scanRecord.uuids =
+          canonicalizeAndConvertToMojoUUID(scanResult.scanRecord.uuids);
+    }
+
+    // Convert the optional appearance and txPower fields to the corresponding
+    // Mojo structures, since Mojo does not support optional interger values. If
+    // the fields are undefined, set the hasValue field as false and value as 0.
+    // Otherwise, set the hasValue field as true and value with the field value.
+    const has_appearance = 'appearance' in scanResult.scanRecord;
+    scanResult.scanRecord.appearance = {
+      hasValue: has_appearance,
+      value: (has_appearance ? scanResult.scanRecord.appearance : 0)
+    }
+
+    const has_tx_power = 'txPower' in scanResult.scanRecord;
+    scanResult.scanRecord.txPower = {
+      hasValue: has_tx_power,
+      value: (has_tx_power ? scanResult.scanRecord.txPower : 0)
+    }
+
+    // Convert manufacturerData from a record<DOMString, BufferSource> into a
+    // map<uint8, array<uint8>> for Mojo.
+    if ('manufacturerData' in scanResult.scanRecord) {
+      scanResult.scanRecord.manufacturerData = convertToMojoMap(
+          scanResult.scanRecord.manufacturerData, Number);
+    }
+
+    // Convert serviceData from a record<DOMString, BufferSource> into a
+    // map<string, array<uint8>> for Mojo.
+    if ('serviceData' in scanResult.scanRecord) {
+      scanResult.scanRecord.serviceData.serviceData = convertToMojoMap(
+          scanResult.scanRecord.serviceData, BluetoothUUID.getService);
+    }
+
+    await this.fake_central_ptr_.simulateAdvertisementReceived(
+        new bluetooth.mojom.ScanResult(scanResult));
+
+    return this.fetchOrCreatePeripheral_(scanResult.deviceAddress);
+  }
+
+  // Create a fake_peripheral object from the given address.
+  fetchOrCreatePeripheral_(address) {
+    let peripheral = this.peripherals_.get(address);
+    if (peripheral === undefined) {
+      peripheral = new FakePeripheral(address, this.fake_central_ptr_);
+      this.peripherals_.set(address, peripheral);
+    }
+    return peripheral;
+  }
+}
+
+class FakePeripheral {
+  constructor(address, fake_central_ptr) {
+    this.address = address;
+    this.fake_central_ptr_ = fake_central_ptr;
+  }
+
+  // Adds a fake GATT Service with |uuid| to be discovered when discovering
+  // the peripheral's GATT Attributes. Returns a FakeRemoteGATTService
+  // corresponding to this service. |uuid| should be a BluetoothServiceUUIDs
+  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
+  async addFakeService({uuid}) {
+    let {serviceId: service_id} = await this.fake_central_ptr_.addFakeService(
+      this.address, {uuid: BluetoothUUID.getService(uuid)});
+
+    if (service_id === null) throw 'addFakeService failed';
+
+    return new FakeRemoteGATTService(
+      service_id, this.address, this.fake_central_ptr_);
+  }
+
+  // Sets the next GATT Connection request response to |code|. |code| could be
+  // an HCI Error Code from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a
+  // number outside that range returned by specific platforms e.g. Android
+  // returns 0x101 to signal a GATT failure
+  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+  async setNextGATTConnectionResponse({code}) {
+    let {success} =
+      await this.fake_central_ptr_.setNextGATTConnectionResponse(
+        this.address, code);
+
+    if (success !== true) throw 'setNextGATTConnectionResponse failed.';
+  }
+
+  // Sets the next GATT Discovery request response for peripheral with
+  // |address| to |code|. |code| could be an HCI Error Code from
+  // BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a number outside that
+  // range returned by specific platforms e.g. Android returns 0x101 to signal
+  // a GATT failure
+  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+  //
+  // The following procedures defined at BT 4.2 Vol 3 Part G Section 4.
+  // "GATT Feature Requirements" are used to discover attributes of the
+  // GATT Server:
+  //  - Primary Service Discovery
+  //  - Relationship Discovery
+  //  - Characteristic Discovery
+  //  - Characteristic Descriptor Discovery
+  // This method aims to simulate the response once all of these procedures
+  // have completed or if there was an error during any of them.
+  async setNextGATTDiscoveryResponse({code}) {
+    let {success} =
+      await this.fake_central_ptr_.setNextGATTDiscoveryResponse(
+        this.address, code);
+
+    if (success !== true) throw 'setNextGATTDiscoveryResponse failed.';
+  }
+
+  // Simulates a GATT disconnection from the peripheral with |address|.
+  async simulateGATTDisconnection() {
+    let {success} =
+      await this.fake_central_ptr_.simulateGATTDisconnection(this.address);
+
+    if (success !== true) throw 'simulateGATTDisconnection failed.';
+  }
+
+  // Simulates an Indication from the peripheral's GATT `Service Changed`
+  // Characteristic from BT 4.2 Vol 3 Part G 7.1. This Indication is signaled
+  // when services, characteristics, or descriptors are changed, added, or
+  // removed.
+  //
+  // The value for `Service Changed` is a range of attribute handles that have
+  // changed. However, this testing specification works at an abstracted
+  // level and does not expose setting attribute handles when adding
+  // attributes. Consequently, this simulate method should include the full
+  // range of all the peripheral's attribute handle values.
+  async simulateGATTServicesChanged() {
+    let {success} =
+      await this.fake_central_ptr_.simulateGATTServicesChanged(this.address);
+
+    if (success !== true) throw 'simulateGATTServicesChanged failed.';
+  }
+}
+
+class FakeRemoteGATTService {
+  constructor(service_id, peripheral_address, fake_central_ptr) {
+    this.service_id_ = service_id;
+    this.peripheral_address_ = peripheral_address;
+    this.fake_central_ptr_ = fake_central_ptr;
+  }
+
+  // Adds a fake GATT Characteristic with |uuid| and |properties|
+  // to this fake service. The characteristic will be found when discovering
+  // the peripheral's GATT Attributes. Returns a FakeRemoteGATTCharacteristic
+  // corresponding to the added characteristic.
+  async addFakeCharacteristic({uuid, properties}) {
+    let {characteristicId: characteristic_id} =
+        await this.fake_central_ptr_.addFakeCharacteristic(
+            {uuid: BluetoothUUID.getCharacteristic(uuid)},
+            ArrayToMojoCharacteristicProperties(properties),
+            this.service_id_,
+            this.peripheral_address_);
+
+    if (characteristic_id === null) throw 'addFakeCharacteristic failed';
+
+    return new FakeRemoteGATTCharacteristic(
+      characteristic_id, this.service_id_,
+      this.peripheral_address_, this.fake_central_ptr_);
+  }
+
+  // Removes the fake GATT service from its fake peripheral.
+  async remove() {
+    let {success} =
+        await this.fake_central_ptr_.removeFakeService(
+            this.service_id_,
+            this.peripheral_address_);
+
+    if (!success) throw 'remove failed';
+  }
+}
+
+class FakeRemoteGATTCharacteristic {
+  constructor(characteristic_id, service_id, peripheral_address,
+      fake_central_ptr) {
+    this.ids_ = [characteristic_id, service_id, peripheral_address];
+    this.descriptors_ = [];
+    this.fake_central_ptr_ = fake_central_ptr;
+  }
+
+  // Adds a fake GATT Descriptor with |uuid| to be discovered when
+  // discovering the peripheral's GATT Attributes. Returns a
+  // FakeRemoteGATTDescriptor corresponding to this descriptor. |uuid| should
+  // be a BluetoothDescriptorUUID
+  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothdescriptoruuid
+  async addFakeDescriptor({uuid}) {
+    let {descriptorId: descriptor_id} =
+        await this.fake_central_ptr_.addFakeDescriptor(
+            {uuid: BluetoothUUID.getDescriptor(uuid)}, ...this.ids_);
+
+    if (descriptor_id === null) throw 'addFakeDescriptor failed';
+
+    let fake_descriptor = new FakeRemoteGATTDescriptor(
+      descriptor_id, ...this.ids_, this.fake_central_ptr_);
+    this.descriptors_.push(fake_descriptor);
+
+    return fake_descriptor;
+  }
+
+  // Sets the next read response for characteristic to |code| and |value|.
+  // |code| could be a GATT Error Response from
+  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+  // failure.
+  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+  async setNextReadResponse(gatt_code, value=null) {
+    if (gatt_code === 0 && value === null) {
+      throw '|value| can\'t be null if read should success.';
+    }
+    if (gatt_code !== 0 && value !== null) {
+      throw '|value| must be null if read should fail.';
+    }
+
+    let {success} =
+      await this.fake_central_ptr_.setNextReadCharacteristicResponse(
+        gatt_code, value, ...this.ids_);
+
+    if (!success) throw 'setNextReadCharacteristicResponse failed';
+  }
+
+  // Sets the next write response for this characteristic to |code|. If
+  // writing to a characteristic that only supports 'write_without_response'
+  // the set response will be ignored.
+  // |code| could be a GATT Error Response from
+  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+  // failure.
+  async setNextWriteResponse(gatt_code) {
+    let {success} =
+      await this.fake_central_ptr_.setNextWriteCharacteristicResponse(
+        gatt_code, ...this.ids_);
+
+    if (!success) throw 'setNextWriteCharacteristicResponse failed';
+  }
+
+  // Sets the next subscribe to notifications response for characteristic with
+  // |characteristic_id| in |service_id| and in |peripheral_address| to
+  // |code|. |code| could be a GATT Error Response from BT 4.2 Vol 3 Part F
+  // 3.4.1.1 Error Response or a number outside that range returned by
+  // specific platforms e.g. Android returns 0x101 to signal a GATT failure.
+  async setNextSubscribeToNotificationsResponse(gatt_code) {
+    let {success} =
+      await this.fake_central_ptr_.setNextSubscribeToNotificationsResponse(
+        gatt_code, ...this.ids_);
+
+    if (!success) throw 'setNextSubscribeToNotificationsResponse failed';
+  }
+
+  // Sets the next unsubscribe to notifications response for characteristic with
+  // |characteristic_id| in |service_id| and in |peripheral_address| to
+  // |code|. |code| could be a GATT Error Response from BT 4.2 Vol 3 Part F
+  // 3.4.1.1 Error Response or a number outside that range returned by
+  // specific platforms e.g. Android returns 0x101 to signal a GATT failure.
+  async setNextUnsubscribeFromNotificationsResponse(gatt_code) {
+    let {success} =
+      await this.fake_central_ptr_.setNextUnsubscribeFromNotificationsResponse(
+        gatt_code, ...this.ids_);
+
+    if (!success) throw 'setNextUnsubscribeToNotificationsResponse failed';
+  }
+
+  // Returns true if notifications from the characteristic have been subscribed
+  // to.
+  async isNotifying() {
+    let {success, isNotifying} =
+        await this.fake_central_ptr_.isNotifying(...this.ids_);
+
+    if (!success) throw 'isNotifying failed';
+
+    return isNotifying;
+  }
+
+  // Gets the last successfully written value to the characteristic.
+  // Returns null if no value has yet been written to the characteristic.
+  async getLastWrittenValue() {
+    let {success, value} =
+      await this.fake_central_ptr_.getLastWrittenCharacteristicValue(
+          ...this.ids_);
+
+    if (!success) throw 'getLastWrittenCharacteristicValue failed';
+
+    return value;
+  }
+
+  // Removes the fake GATT Characteristic from its fake service.
+  async remove() {
+    let {success} =
+        await this.fake_central_ptr_.removeFakeCharacteristic(...this.ids_);
+
+    if (!success) throw 'remove failed';
+  }
+}
+
+class FakeRemoteGATTDescriptor {
+  constructor(descriptor_id,
+              characteristic_id,
+              service_id,
+              peripheral_address,
+              fake_central_ptr) {
+    this.ids_ = [
+      descriptor_id, characteristic_id, service_id, peripheral_address];
+    this.fake_central_ptr_ = fake_central_ptr;
+  }
+
+  // Sets the next read response for descriptor to |code| and |value|.
+  // |code| could be a GATT Error Response from
+  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+  // failure.
+  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+  async setNextReadResponse(gatt_code, value=null) {
+    if (gatt_code === 0 && value === null) {
+      throw '|value| cannot be null if read should succeed.';
+    }
+    if (gatt_code !== 0 && value !== null) {
+      throw '|value| must be null if read should fail.';
+    }
+
+    let {success} =
+      await this.fake_central_ptr_.setNextReadDescriptorResponse(
+        gatt_code, value, ...this.ids_);
+
+    if (!success) throw 'setNextReadDescriptorResponse failed';
+  }
+
+  // Sets the next write response for this descriptor to |code|.
+  // |code| could be a GATT Error Response from
+  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
+  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+  // failure.
+  async setNextWriteResponse(gatt_code) {
+    let {success} =
+      await this.fake_central_ptr_.setNextWriteDescriptorResponse(
+        gatt_code, ...this.ids_);
+
+    if (!success) throw 'setNextWriteDescriptorResponse failed';
+  }
+
+  // Gets the last successfully written value to the descriptor.
+  // Returns null if no value has yet been written to the descriptor.
+  async getLastWrittenValue() {
+    let {success, value} =
+      await this.fake_central_ptr_.getLastWrittenDescriptorValue(
+          ...this.ids_);
+
+    if (!success) throw 'getLastWrittenDescriptorValue failed';
+
+    return value;
+  }
+
+  // Removes the fake GATT Descriptor from its fake characteristic.
+  async remove() {
+    let {success} =
+        await this.fake_central_ptr_.removeFakeDescriptor(...this.ids_);
+
+    if (!success) throw 'remove failed';
+  }
+}
+
+// FakeChooser allows clients to simulate events that a user would trigger when
+// using the Bluetooth chooser, and monitor the events that are produced.
+class FakeChooser {
+  constructor() {
+    this.fake_bluetooth_chooser_ptr_ =
+        new content.mojom.FakeBluetoothChooserPtr();
+    Mojo.bindInterface(content.mojom.FakeBluetoothChooser.name,
+        mojo.makeRequest(this.fake_bluetooth_chooser_ptr_).handle, 'process');
+  }
+}
+
+// If this line fails, it means that current environment does not support the
+// Web Bluetooth Test API.
+try {
+  navigator.bluetooth.test = new FakeBluetooth();
+} catch {
+    throw 'Web Bluetooth Test API is not implemented on this ' +
+        'environment. See the bluetooth README at ' +
+        'https://github.com/w3c/web-platform-tests/blob/master/bluetooth/README.md#web-bluetooth-testing';
+}
diff --git a/resources/chromium/web-bluetooth-test.js.headers b/resources/chromium/web-bluetooth-test.js.headers
new file mode 100644
index 0000000..6805c32
--- /dev/null
+++ b/resources/chromium/web-bluetooth-test.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/resources/chromium/webusb-test.js b/resources/chromium/webusb-test.js
index 08c8254..630a7da 100644
--- a/resources/chromium/webusb-test.js
+++ b/resources/chromium/webusb-test.js
@@ -20,12 +20,12 @@
 };
 
 // Converts an ECMAScript String object to an instance of
-// mojo.common.mojom.String16.
+// mojo_base.mojom.String16.
 function mojoString16ToString(string16) {
   return String.fromCharCode.apply(null, string16.data);
 }
 
-// Converts an instance of mojo.common.mojom.String16 to an ECMAScript String.
+// Converts an instance of mojo_base.mojom.String16 to an ECMAScript String.
 function stringToMojoString16(string) {
   let array = new Array(string.length);
   for (var i = 0; i < string.length; ++i) {
@@ -360,15 +360,17 @@
   }
 
   getDevice(guid, request) {
-    let device = this.devicesByGuid_.get(guid);
-    if (device) {
+    let retrievedDevice = this.devicesByGuid_.get(guid);
+    if (retrievedDevice) {
       let binding = new mojo.Binding(
-          window.device.mojom.UsbDevice, new FakeDevice(device.info), request);
+          device.mojom.UsbDevice,
+          new FakeDevice(retrievedDevice.info),
+          request);
       binding.setConnectionErrorHandler(() => {
-        if (device.fakeDevice.onclose)
-          device.fakeDevice.onclose();
+        if (retrievedDevice.fakeDevice.onclose)
+          retrievedDevice.fakeDevice.onclose();
       });
-      device.bindingArray.push(binding);
+      retrievedDevice.bindingArray.push(binding);
     } else {
       request.close();
     }
diff --git a/resources/idlharness.js b/resources/idlharness.js
index 731352c..d413c51 100644
--- a/resources/idlharness.js
+++ b/resources/idlharness.js
@@ -123,6 +123,28 @@
 })();
 //@}
 
+/// IdlHarnessError ///
+// Entry point
+self.IdlHarnessError = function(message)
+//@{
+{
+    /**
+     * Message to be printed as the error's toString invocation.
+     */
+    this.message = message;
+};
+
+IdlHarnessError.prototype = Object.create(Error.prototype);
+
+//@}
+IdlHarnessError.prototype.toString = function()
+//@{
+{
+    return this.message;
+};
+
+//@}
+
 /// IdlArray ///
 // Entry point
 self.IdlArray = function()
@@ -161,10 +183,19 @@
      *   A implements C;
      *   D implements E;
      *
-     * results in { A: ["B", "C"], D: ["E"] }.
+     * results in this["implements"] = { A: ["B", "C"], D: ["E"] }.
+     *
+     * Similarly,
+     *
+     *   interface A : B {};
+     *   interface B : C {};
+     *
+     * results in this["inheritance"] = { A: "B", B: "C" }
      */
     this.partials = [];
     this["implements"] = {};
+    this["includes"] = {};
+    this["inheritance"] = {};
 };
 
 //@}
@@ -176,24 +207,120 @@
 };
 
 //@}
-IdlArray.prototype.add_untested_idls = function(raw_idls)
+IdlArray.prototype.add_untested_idls = function(raw_idls, options)
 //@{
 {
     /** Entry point.  See documentation at beginning of file. */
     var parsed_idls = WebIDL2.parse(raw_idls);
-    for (var i = 0; i < parsed_idls.length; i++)
-    {
+    this.mark_as_untested(parsed_idls);
+    this.internal_add_idls(parsed_idls, options);
+};
+
+//@}
+IdlArray.prototype.mark_as_untested = function (parsed_idls)
+//@{
+{
+    for (var i = 0; i < parsed_idls.length; i++) {
         parsed_idls[i].untested = true;
-        if ("members" in parsed_idls[i])
-        {
-            for (var j = 0; j < parsed_idls[i].members.length; j++)
-            {
+        if ("members" in parsed_idls[i]) {
+            for (var j = 0; j < parsed_idls[i].members.length; j++) {
                 parsed_idls[i].members[j].untested = true;
             }
         }
     }
-    this.internal_add_idls(parsed_idls);
 };
+//@}
+
+//@}
+IdlArray.prototype.is_excluded_by_options = function (name, options)
+//@{
+{
+    return options &&
+        (options.except && options.except.includes(name)
+         || options.only && !options.only.includes(name));
+};
+//@}
+
+//@}
+IdlArray.prototype.add_dependency_idls = function(raw_idls, options)
+//@{
+{
+    const parsed_idls = WebIDL2.parse(raw_idls);
+    const new_options = { only: [] }
+
+    const all_deps = new Set();
+    Object.values(this.inheritance).forEach(v => all_deps.add(v));
+    Object.values(this.implements).forEach(v => all_deps.add(v));
+    // NOTE: If 'A includes B' for B that we care about, then A is also a dep.
+    Object.keys(this.includes).forEach(k => {
+        all_deps.add(k);
+        this.includes[k].forEach(v => all_deps.add(v));
+    });
+    this.partials.map(p => p.name).forEach(v => all_deps.add(v));
+    // Add the attribute idlTypes of all the nested members of all tested idls.
+    Object.values(this.members).filter(m => !m.untested && m.members).forEach(parsed => {
+        Object.values(parsed.members).filter(m => m.type === 'attribute').forEach(m => {
+            all_deps.add(m.idlType.idlType);
+        });
+    });
+
+    if (options && options.except && options.only) {
+        throw new IdlHarnessError("The only and except options can't be used together.");
+    }
+
+    const should_skip = name => {
+        // NOTE: Deps are untested, so we're lenient, and skip re-encountered definitions.
+        // e.g. for 'idl' containing A:B, B:C, C:D
+        //      array.add_idls(idl, {only: ['A','B']}).
+        //      array.add_dependency_idls(idl);
+        // B would be encountered as tested, and encountered as a dep, so we ignore.
+        return name in this.members
+            || this.is_excluded_by_options(name, options);
+    }
+    // Record of skipped items, in case we later determine they are a dependency.
+    // Maps name -> [parsed_idl, ...]
+    const skipped = new Map();
+    const process = function(parsed) {
+        let name = parsed.name
+            || parsed.type == "implements" && parsed.target
+            || parsed.type == "includes" && parsed.target;
+        if (!name || should_skip(name) || !all_deps.has(name)) {
+            name &&
+                skipped.has(name)
+                    ? skipped.get(name).push(parsed)
+                    : skipped.set(name, [parsed]);
+            return;
+        }
+
+        new_options.only.push(name);
+        const follow_up = [];
+        for (const dep_type of ["inheritance", "implements", "includes"]) {
+            if (parsed[dep_type]) {
+                const dep = parsed[dep_type];
+                new_options.only.push(dep);
+                all_deps.add(dep);
+                follow_up.push(dep);
+            }
+        }
+
+        for (const deferred of follow_up) {
+            if (skipped.has(deferred)) {
+                const next = skipped.get(deferred);
+                skipped.delete(deferred);
+                next.forEach(process);
+            }
+        }
+    }
+    for (let parsed of parsed_idls) {
+        process(parsed);
+    }
+
+    this.mark_as_untested(parsed_idls);
+
+    if (new_options.only.length) {
+        this.internal_add_idls(parsed_idls, new_options);
+    }
+}
 
 //@}
 IdlArray.prototype.internal_add_idls = function(parsed_idls, options)
@@ -216,25 +343,16 @@
 
     if (options && options.only && options.except)
     {
-        throw "The only and except options can't be used together."
+        throw new IdlHarnessError("The only and except options can't be used together.");
     }
 
-    function should_skip(name)
-    {
-        if (options && options.only && options.only.indexOf(name) == -1)
-        {
-            return true;
-        }
-        if (options && options.except && options.except.indexOf(name) != -1)
-        {
-            return true;
-        }
-        return false;
+    var should_skip = name => {
+        return this.is_excluded_by_options(name, options);
     }
 
     parsed_idls.forEach(function(parsed_idl)
     {
-        if (parsed_idl.type == "interface" && parsed_idl.partial)
+        if (parsed_idl.partial && ["interface", "dictionary"].includes(parsed_idl.type))
         {
             if (should_skip(parsed_idl.name))
             {
@@ -258,20 +376,50 @@
             return;
         }
 
-        parsed_idl.array = this;
-        if (parsed_idl.name in this.members)
+        if (parsed_idl.type == "includes")
         {
-            throw "Duplicate identifier " + parsed_idl.name;
+            if (should_skip(parsed_idl.target))
+            {
+                return;
+            }
+            if (!(parsed_idl.target in this["includes"]))
+            {
+                this["includes"][parsed_idl.target] = [];
+            }
+            this["includes"][parsed_idl.target].push(parsed_idl["includes"]);
+            return;
         }
+
+        parsed_idl.array = this;
         if (should_skip(parsed_idl.name))
         {
             return;
         }
+        if (parsed_idl.name in this.members)
+        {
+            throw new IdlHarnessError("Duplicate identifier " + parsed_idl.name);
+        }
+
+        if (parsed_idl["inheritance"]) {
+            // NOTE: Clash should be impossible (would require redefinition of parsed_idl.name).
+            if (parsed_idl.name in this["inheritance"]
+                && parsed_idl["inheritance"] != this["inheritance"][parsed_idl.name]) {
+                throw new IdlHarnessError(
+                    `Inheritance for ${parsed_idl.name} was already defined`);
+            }
+            this["inheritance"][parsed_idl.name] = parsed_idl["inheritance"];
+        }
+
         switch(parsed_idl.type)
         {
         case "interface":
             this.members[parsed_idl.name] =
-                new IdlInterface(parsed_idl, /* is_callback = */ false);
+                new IdlInterface(parsed_idl, /* is_callback = */ false, /* is_mixin = */ false);
+            break;
+
+        case "interface mixin":
+            this.members[parsed_idl.name] =
+                new IdlInterface(parsed_idl, /* is_callback = */ false, /* is_mixin = */ true);
             break;
 
         case "dictionary":
@@ -295,7 +443,7 @@
 
         case "callback interface":
             this.members[parsed_idl.name] =
-                new IdlInterface(parsed_idl, /* is_callback = */ true);
+                new IdlInterface(parsed_idl, /* is_callback = */ true, /* is_mixin = */ false);
             break;
 
         default:
@@ -354,7 +502,37 @@
         ret = ret.concat(this.recursively_get_implements(ret[i]));
         if (ret.indexOf(ret[i]) != ret.lastIndexOf(ret[i]))
         {
-            throw "Circular implements statements involving " + ret[i];
+            throw new IdlHarnessError("Circular implements statements involving " + ret[i]);
+        }
+    }
+    return ret;
+};
+
+//@}
+IdlArray.prototype.recursively_get_includes = function(interface_name)
+//@{
+{
+    /**
+     * Helper function for test().  Returns an array of things that implement
+     * interface_name, so if the IDL contains
+     *
+     *   A includes B;
+     *   B includes C;
+     *   B includes D;
+     *
+     * then recursively_get_includes("A") should return ["B", "C", "D"].
+     */
+    var ret = this["includes"][interface_name];
+    if (ret === undefined)
+    {
+        return [];
+    }
+    for (var i = 0; i < this["includes"][interface_name].length; i++)
+    {
+        ret = ret.concat(this.recursively_get_includes(ret[i]));
+        if (ret.indexOf(ret[i]) != ret.lastIndexOf(ret[i]))
+        {
+            throw new IdlHarnessError("Circular includes statements involving " + ret[i]);
         }
     }
     return ret;
@@ -456,7 +634,7 @@
                while (thing)
                {
                    if (thing.has_to_json_regular_operation()) { return true; }
-                   var mixins = this.implements[thing.name];
+                   var mixins = this.implements[thing.name] || this.includes[thing.name];
                    if (mixins) {
                        mixins = mixins.map(function(id) {
                            var mixin = this.members[id];
@@ -482,8 +660,9 @@
 
 function exposure_set(object, default_set) {
     var exposed = object.extAttrs.filter(function(a) { return a.name == "Exposed" });
-    if (exposed.length > 1 || exposed.length < 0) {
-        throw "Unexpected Exposed extended attributes on " + memberName + ": " + exposed;
+    if (exposed.length > 1) {
+        throw new IdlHarnessError(
+            `Multiple 'Exposed' extended attributes on ${object.name}`);
     }
 
     if (exposed.length === 0) {
@@ -517,7 +696,34 @@
         return globals.indexOf("Worker") >= 0 ||
                globals.indexOf("ServiceWorker") >= 0;
     }
-    throw "Unexpected global object";
+    throw new IdlHarnessError("Unexpected global object");
+}
+
+//@}
+/**
+ * Asserts that the given error message is thrown for the given function.
+ * @param {string|IdlHarnessError} error Expected Error message.
+ * @param {Function} idlArrayFunc Function operating on an IdlArray that should throw.
+ */
+IdlArray.prototype.assert_throws = function(error, idlArrayFunc)
+//@{
+{
+    try {
+        idlArrayFunc.call(this, this);
+    } catch (e) {
+        if (e instanceof AssertionError) {
+            throw e;
+        }
+        // Assertions for behaviour of the idlharness.js engine.
+        if (error instanceof IdlHarnessError) {
+            error = error.message;
+        }
+        if (e.message !== error) {
+            throw new IdlHarnessError(`${idlArrayFunc} threw "${e}", not the expected IdlHarnessError "${error}"`);
+        }
+        return;
+    }
+    throw new IdlHarnessError(`${idlArrayFunc} did not throw the expected IdlHarnessError`);
 }
 
 //@}
@@ -531,9 +737,10 @@
     this.partials.forEach(function(parsed_idl)
     {
         if (!(parsed_idl.name in this.members)
-        || !(this.members[parsed_idl.name] instanceof IdlInterface))
+            || !(this.members[parsed_idl.name] instanceof IdlInterface
+                 || this.members[parsed_idl.name] instanceof IdlDictionary))
         {
-            throw "Partial interface " + parsed_idl.name + " with no original interface";
+            throw new IdlHarnessError(`Partial ${parsed_idl.type} ${parsed_idl.name} with no original ${parsed_idl.type}`);
         }
         if (parsed_idl.extAttrs)
         {
@@ -566,6 +773,38 @@
     }
     this["implements"] = {};
 
+    for (var lhs in this["includes"])
+    {
+        this.recursively_get_includes(lhs).forEach(function(rhs)
+        {
+            var errStr = lhs + " includes " + rhs + ", but ";
+            if (!(lhs in this.members)) throw errStr + lhs + " is undefined.";
+            if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface.";
+            if (!(rhs in this.members)) throw errStr + rhs + " is undefined.";
+            if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface.";
+            this.members[rhs].members.forEach(function(member)
+            {
+                this.members[lhs].members.push(new IdlInterfaceMember(member));
+            }.bind(this));
+        }.bind(this));
+    }
+    this["includes"] = {};
+
+    // Assert B defined for A : B
+    for (var member of Object.values(this.members).filter(m => m.base)) {
+        const lhs = member.name;
+        const rhs = member.base;
+        if (!(rhs in this.members)) throw new IdlHarnessError(`${lhs} inherits ${rhs}, but ${rhs} is undefined.`);
+        const lhs_is_interface = this.members[lhs] instanceof IdlInterface;
+        const rhs_is_interface = this.members[rhs] instanceof IdlInterface;
+        if (rhs_is_interface != lhs_is_interface) {
+            if (!lhs_is_interface) throw new IdlHarnessError(`${lhs} inherits ${rhs}, but ${lhs} is not an interface.`);
+            if (!rhs_is_interface) throw new IdlHarnessError(`${lhs} inherits ${rhs}, but ${rhs} is not an interface.`);
+        }
+        // Check for circular dependencies.
+        member.get_inheritance_stack();
+    }
+
     Object.getOwnPropertyNames(this.members).forEach(function(memberName) {
         var member = this.members[memberName];
         if (!(member instanceof IdlInterface)) {
@@ -781,7 +1020,7 @@
 
     if (!(type in this.members))
     {
-        throw "Unrecognized type " + type;
+        throw new IdlHarnessError("Unrecognized type " + type);
     }
 
     if (this.members[type] instanceof IdlInterface)
@@ -809,7 +1048,7 @@
     }
     else
     {
-        throw "Type " + type + " isn't an interface or dictionary";
+        throw new IdlHarnessError("Type " + type + " isn't an interface or dictionary");
     }
 };
 //@}
@@ -875,7 +1114,7 @@
 };
 
 /// IdlInterface ///
-function IdlInterface(obj, is_callback)
+function IdlInterface(obj, is_callback, is_mixin)
 //@{
 {
     /**
@@ -913,6 +1152,7 @@
     this.base = obj.inheritance;
 
     this._is_callback = is_callback;
+    this._is_mixin = is_mixin;
 }
 //@}
 IdlInterface.prototype = Object.create(IdlObject.prototype);
@@ -923,6 +1163,13 @@
 };
 //@}
 
+IdlInterface.prototype.is_mixin = function()
+//@{
+{
+    return this._is_mixin;
+};
+//@}
+
 IdlInterface.prototype.has_constants = function()
 //@{
 {
@@ -932,12 +1179,18 @@
 };
 //@}
 
+IdlInterface.prototype.get_unscopables = function()
+{
+    return this.members.filter(function(member) {
+        return member.isUnscopable;
+    });
+};
+
 IdlInterface.prototype.is_global = function()
 //@{
 {
     return this.extAttrs.some(function(attribute) {
-        return attribute.name === "Global" ||
-               attribute.name === "PrimaryGlobal";
+        return attribute.name === "Global";
     });
 };
 //@}
@@ -980,6 +1233,10 @@
         var base = this.array.members[idl_interface.base];
         if (!base) {
             throw new Error(idl_interface.type + " " + idl_interface.base + " not found (inherited by " + idl_interface.name + ")");
+        } else if (stack.indexOf(base) > -1) {
+            stack.push(base);
+            let dep_chain = stack.map(i => i.name).join(',');
+            throw new IdlHarnessError(`${this.name} has a circular dependency: ${dep_chain}`);
         }
         idl_interface = base;
         stack.push(idl_interface);
@@ -1047,7 +1304,7 @@
 function _traverse_inherited_and_consequential_interfaces(stack, callback) {
     var I = stack.pop();
     callback(I);
-    var mixins = I.array["implements"][I.name];
+    var mixins = I.array["implements"][I.name] || I.array["includes"][I.name];
     if (mixins) {
         mixins.forEach(function(id) {
             var mixin = I.array.members[id];
@@ -1066,7 +1323,7 @@
 IdlInterface.prototype.test = function()
 //@{
 {
-    if (this.has_extended_attribute("NoInterfaceObject"))
+    if (this.has_extended_attribute("NoInterfaceObject") || this.is_mixin())
     {
         // No tests to do without an instance.  TODO: We should still be able
         // to run tests on the prototype object, if we obtain one through some
@@ -1264,13 +1521,13 @@
         {
             var aliasAttrs = this.extAttrs.filter(function(o) { return o.name === "LegacyWindowAlias"; });
             if (aliasAttrs.length > 1) {
-                throw "Invalid IDL: multiple LegacyWindowAlias extended attributes on " + this.name;
+                throw new IdlHarnessError("Invalid IDL: multiple LegacyWindowAlias extended attributes on " + this.name);
             }
             if (this.is_callback()) {
-                throw "Invalid IDL: LegacyWindowAlias extended attribute on non-interface " + this.name;
+                throw new IdlHarnessError("Invalid IDL: LegacyWindowAlias extended attribute on non-interface " + this.name);
             }
             if (this.exposureSet.indexOf("Window") === -1) {
-                throw "Invalid IDL: LegacyWindowAlias extended attribute on " + this.name + " which is not exposed in Window";
+                throw new IdlHarnessError("Invalid IDL: LegacyWindowAlias extended attribute on " + this.name + " which is not exposed in Window");
             }
             // TODO: when testing of [NoInterfaceObject] interfaces is supported,
             // check that it's not specified together with LegacyWindowAlias.
@@ -1279,7 +1536,7 @@
 
             var rhs = aliasAttrs[0].rhs;
             if (!rhs) {
-                throw "Invalid IDL: LegacyWindowAlias extended attribute on " + this.name + " without identifier";
+                throw new IdlHarnessError("Invalid IDL: LegacyWindowAlias extended attribute on " + this.name + " without identifier");
             }
             var aliases;
             if (rhs.type === "identifier-list") {
@@ -1356,14 +1613,12 @@
         // "The interface prototype object for a given interface A must have an
         // internal [[Prototype]] property whose value is returned from the
         // following steps:
-        // "If A is declared with the [Global] or [PrimaryGlobal] extended
+        // "If A is declared with the [Global] extended
         // attribute, and A supports named properties, then return the named
         // properties object for A, as defined in §3.6.4 Named properties
         // object.
         // "Otherwise, if A is declared to inherit from another interface, then
         // return the interface prototype object for the inherited interface.
-        // "Otherwise, if A is declared with the [LegacyArrayClass] extended
-        // attribute, then return %ArrayPrototype%.
         // "Otherwise, return %ObjectPrototype%.
         //
         // "In the ECMAScript binding, the DOMException type has some additional
@@ -1386,9 +1641,6 @@
                     !this.array
                          .members[inherit_interface]
                          .has_extended_attribute("NoInterfaceObject");
-            } else if (this.has_extended_attribute('LegacyArrayClass')) {
-                inherit_interface = 'Array';
-                inherit_interface_has_interface_object = true;
             } else if (this.name === "DOMException") {
                 inherit_interface = 'Error';
                 inherit_interface_has_interface_object = true;
@@ -1434,7 +1686,7 @@
         }
     }.bind(this), this.name + " interface: existence and properties of interface prototype object");
 
-    // "If the interface is declared with the [Global] or [PrimaryGlobal]
+    // "If the interface is declared with the [Global]
     // extended attribute, or the interface is in the set of inherited
     // interfaces for any other interface that is declared with one of these
     // attributes, then the interface prototype object must be an immutable
@@ -1478,6 +1730,68 @@
         assert_equals(self[this.name].prototype.constructor, self[this.name],
                       this.name + '.prototype.constructor is not the same object as ' + this.name);
     }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
+
+
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        // If the interface has any member declared with the [Unscopable] extended
+        // attribute, then there must be a property on the interface prototype object
+        // whose name is the @@unscopables symbol, which has the attributes
+        // { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true },
+        // and whose value is an object created as follows...
+        var unscopables = this.get_unscopables().map(m => m.name);
+        var proto = self[this.name].prototype;
+        if (unscopables.length != 0) {
+            assert_own_property(
+                proto, Symbol.unscopables,
+                this.name + '.prototype should have an @@unscopables property');
+            var desc = Object.getOwnPropertyDescriptor(proto, Symbol.unscopables);
+            assert_false("get" in desc,
+                         this.name + ".prototype[Symbol.unscopables] should not have a getter");
+            assert_false("set" in desc, this.name + ".prototype[Symbol.unscopables] should not have a setter");
+            assert_false(desc.writable, this.name + ".prototype[Symbol.unscopables] should not be writable");
+            assert_false(desc.enumerable, this.name + ".prototype[Symbol.unscopables] should not be enumerable");
+            assert_true(desc.configurable, this.name + ".prototype[Symbol.unscopables] should be configurable");
+            assert_equals(desc.value, proto[Symbol.unscopables],
+                          this.name + '.prototype[Symbol.unscopables] should be in the descriptor');
+            assert_equals(typeof desc.value, "object",
+                          this.name + '.prototype[Symbol.unscopables] should be an object');
+            assert_equals(Object.getPrototypeOf(desc.value), null,
+                          this.name + '.prototype[Symbol.unscopables] should have a null prototype');
+            assert_equals(Object.getOwnPropertySymbols(desc.value).length,
+                          0,
+                          this.name + '.prototype[Symbol.unscopables] should have the right number of symbol-named properties');
+
+            // Check that we do not have _extra_ unscopables.  Checking that we
+            // have all the ones we should will happen in the per-member tests.
+            var observed = Object.getOwnPropertyNames(desc.value);
+            for (var prop of observed) {
+                assert_not_equals(unscopables.indexOf(prop),
+                                  -1,
+                                  this.name + '.prototype[Symbol.unscopables] has unexpected property "' + prop + '"');
+            }
+        } else {
+            assert_equals(Object.getOwnPropertyDescriptor(self[this.name].prototype, Symbol.unscopables),
+                          undefined,
+                          this.name + '.prototype should not have @@unscopables');
+        }
+    }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s @@unscopables property');
 };
 
 //@}
@@ -1589,7 +1903,7 @@
 //@{
 {
     if (!this.has_constants()) {
-        throw "Internal error: test_member_const called without any constants";
+        throw new IdlHarnessError("Internal error: test_member_const called without any constants");
     }
 
     test(function()
@@ -1731,6 +2045,10 @@
 
         }
     }.bind(this));
+
+    test(function () {
+        this.do_member_unscopable_asserts(member);
+    }.bind(this), 'Unscopable handled correctly for ' + member.name + ' property on ' + this.name);
 };
 
 //@}
@@ -1780,7 +2098,7 @@
                     "interface object missing static operation");
             memberHolderObject = self[this.name];
         // "* Otherwise, [...] if the interface was declared with the [Global]
-        //    or [PrimaryGlobal] extended attribute, then the property exists
+        //    extended attribute, then the property exists
         //    on every object that implements the interface."
         } else if (this.is_global()) {
             assert_own_property(self, member.name,
@@ -1795,6 +2113,41 @@
         }
         this.do_member_operation_asserts(memberHolderObject, member, a_test);
     }.bind(this));
+
+    test(function () {
+        this.do_member_unscopable_asserts(member);
+    }.bind(this),
+         'Unscopable handled correctly for ' + member.name + "(" +
+         member.arguments.map(
+             function(m) {return m.idlType.idlType; } ).join(", ")
+         + ")" + ' on ' + this.name);
+};
+
+IdlInterface.prototype.do_member_unscopable_asserts = function(member)
+{
+    // Check that if the member is unscopable then it's in the
+    // @@unscopables object properly.
+    if (!member.isUnscopable) {
+        return;
+    }
+
+    var unscopables = self[this.name].prototype[Symbol.unscopables];
+    var prop = member.name;
+    var propDesc = Object.getOwnPropertyDescriptor(unscopables, prop);
+    assert_equals(typeof propDesc, "object",
+                  this.name + '.prototype[Symbol.unscopables].' + prop + ' must exist')
+    assert_false("get" in propDesc,
+                 this.name + '.prototype[Symbol.unscopables].' + prop + ' must have no getter');
+    assert_false("set" in propDesc,
+                 this.name + '.prototype[Symbol.unscopables].' + prop + ' must have no setter');
+    assert_true(propDesc.writable,
+                this.name + '.prototype[Symbol.unscopables].' + prop + ' must be writable');
+    assert_true(propDesc.enumerable,
+                this.name + '.prototype[Symbol.unscopables].' + prop + ' must be enumerable');
+    assert_true(propDesc.configurable,
+                this.name + '.prototype[Symbol.unscopables].' + prop + ' must be configurable');
+    assert_equals(propDesc.value, true,
+                  this.name + '.prototype[Symbol.unscopables].' + prop + ' must have the value `true`');
 };
 
 //@}
@@ -1886,6 +2239,7 @@
 };
 
 IdlInterface.prototype.test_to_json_operation = function(memberHolderObject, member) {
+    var instanceName = memberHolderObject.constructor.name;
     if (member.has_extended_attribute("Default")) {
         var map = this.default_to_json_operation();
         test(function() {
@@ -1899,12 +2253,12 @@
                 this.array.assert_type_is(json[k], type);
                 delete json[k];
             }, this);
-        }.bind(this), "Test default toJSON operation of " + this.name);
+        }.bind(this), "Test default toJSON operation of " + instanceName);
     } else {
         test(function() {
-            assert_true(this.array.is_json_type(member.idlType), JSON.stringify(member.idlType) + " is not an appropriate return value for the toJSON operation of " + this.name);
+            assert_true(this.array.is_json_type(member.idlType), JSON.stringify(member.idlType) + " is not an appropriate return value for the toJSON operation of " + instanceName);
             this.array.assert_type_is(memberHolderObject.toJSON(), member.idlType);
-        }.bind(this), "Test toJSON operation of " + this.name);
+        }.bind(this), "Test toJSON operation of " + instanceName);
     }
 };
 
@@ -1913,7 +2267,7 @@
 //@{
 {
     var interfaceName = this.name;
-    var isPairIterator = member.idlType instanceof Array;
+    var isPairIterator = member.idlType.length === 2;
     test(function()
     {
         var descriptor = Object.getOwnPropertyDescriptor(self[interfaceName].prototype, Symbol.iterator);
@@ -2105,7 +2459,7 @@
     {
         if (!(current_interface.name in this.array.members))
         {
-            throw "Interface " + current_interface.name + " not found (inherited by " + this.name + ")";
+            throw new IdlHarnessError("Interface " + current_interface.name + " not found (inherited by " + this.name + ")");
         }
         if (current_interface.prevent_multiple_testing && current_interface.already_tested)
         {
@@ -2128,7 +2482,7 @@
     }
 
     // "The internal [[SetPrototypeOf]] method of every platform object that
-    // implements an interface with the [Global] or [PrimaryGlobal] extended
+    // implements an interface with the [Global] extended
     // attribute must execute the same algorithm as is defined for the
     // [[SetPrototypeOf]] internal method of an immutable prototype exotic
     // object."
@@ -2195,8 +2549,9 @@
         }
         if (!exposed_in(exposure_set(member, this.exposureSet))) {
             test(function() {
+                assert_equals(exception, null, "Unexpected exception when evaluating object");
                 assert_false(member.name in obj);
-            }.bind(this), this.name + "interface: " + desc + 'must not have property "' + member.name + '"');
+            }.bind(this), this.name + " interface: " + desc + ' must not have property "' + member.name + '"');
             continue;
         }
         if (member.type == "attribute" && member.isUnforgeable)
@@ -2478,6 +2833,7 @@
     }
 
     this.isUnforgeable = this.has_extended_attribute("Unforgeable");
+    this.isUnscopable = this.has_extended_attribute("Unscopable");
 }
 
 //@}
diff --git a/resources/idlharness.js.headers b/resources/idlharness.js.headers
index 6805c32..5e8f640 100644
--- a/resources/idlharness.js.headers
+++ b/resources/idlharness.js.headers
@@ -1 +1,2 @@
 Content-Type: text/javascript; charset=utf-8
+Cache-Control: max-age=3600
diff --git a/resources/readme.md b/resources/readme.md
index 94f3210..97d2e6f 100644
--- a/resources/readme.md
+++ b/resources/readme.md
@@ -18,9 +18,9 @@
 
 ### Full documentation
 
-For detailed API documentation please visit [http://web-platform-tests.org/writing-tests/testharness-api.html](http://web-platform-tests.org/writing-tests/testharness-api.html).
+For detailed API documentation please visit [https://web-platform-tests.org/writing-tests/testharness-api.html](https://web-platform-tests.org/writing-tests/testharness-api.html).
 
 ### Tutorials
 
-You can also read a tutorial on 
+You can also read a tutorial on
 [Using testharness.js](http://darobin.github.com/test-harness-tutorial/docs/using-testharness.html).
diff --git a/resources/test/README.md b/resources/test/README.md
index 1010516..c228372 100644
--- a/resources/test/README.md
+++ b/resources/test/README.md
@@ -6,22 +6,24 @@
 
 Install the following dependencies:
 
-- [Python 2.7](https://www.python.org/)
+- [Python 2.7.9+](https://www.python.org/)
 - [the tox Python package](https://tox.readthedocs.io/en/latest/)
 - [the Mozilla Firefox web browser](https://mozilla.org/firefox)
 - [the GeckoDriver server](https://github.com/mozilla/geckodriver)
 
-Once these dependencies are satisfied, the tests may be run from a command line
-by executing the following command from this directory:
+Make sure `geckodriver` can be found in your `PATH`.
 
-    tox
-
-Currently, the tests should be run with Firefox Nightly.
-
-In order to specify the path to Firefox Nightly, use the following command-line option:
+Currently, the tests should be run with the latest *Firefox Nightly*. In order to
+specify the path to Firefox Nightly, use the following command-line option:
 
     tox -- --binary=/path/to/FirefoxNightly
 
+### Automated Script
+
+Alternatively, you may run `tools/ci/ci_resources_unittest.sh`, which only depends on
+Python 2. The script will install other dependencies automatically and start `tox` with
+the correct arguments.
+
 ## Authoring Tests
 
 Test cases are expressed as `.html` files located within the `tests/`
@@ -64,4 +66,4 @@
     </script>
 
 This is useful to test, for example, whether asserations that should fail or
-throw actually do.
\ No newline at end of file
+throw actually do.
diff --git a/resources/test/config.test.json b/resources/test/config.test.json
deleted file mode 100644
index df664fa..0000000
--- a/resources/test/config.test.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{"host": "web-platform.test",
- "doc_root": null,
- "ws_doc_root": null,
- "external_host": null,
- "ports":{"http":[8000, "auto"],
-          "https":[9003],
-          "ws":["auto"],
-          "wss":["auto"]},
- "check_subdomains": true,
- "log_level":"debug",
- "bind_hostname": true,
- "ssl": {"type": "openssl",
-         "encrypt_after_connect": false,
-         "openssl": {
-             "openssl_binary": "openssl",
-             "base_path": "_certs",
-             "force_regenerate": false,
-             "base_conf_path": null
-         },
-         "pregenerated": {
-             "host_key_path": null,
-             "host_cert_path": null
-         },
-         "none": {}
-        },
- "aliases": []
-}
diff --git a/resources/test/conftest.py b/resources/test/conftest.py
index 047ca1c..abc3b6c 100644
--- a/resources/test/conftest.py
+++ b/resources/test/conftest.py
@@ -24,8 +24,8 @@
     config.driver = webdriver.Firefox(firefox_binary=config.getoption("--binary"))
     config.server = WPTServer(WPT_ROOT)
     config.server.start()
-    config.add_cleanup(lambda: config.server.stop())
-    config.add_cleanup(lambda: config.driver.quit())
+    config.add_cleanup(config.server.stop)
+    config.add_cleanup(config.driver.quit)
 
 class HTMLItem(pytest.Item, pytest.Collector):
     def __init__(self, filename, parent):
@@ -80,24 +80,20 @@
         if not self.expected:
             assert summarized[u'summarized_status'][u'status_string'] == u'OK', summarized[u'summarized_status'][u'message']
             for test in summarized[u'summarized_tests']:
-                msg = "%s\n%s:\n%s" % (test[u'name'], test[u'message'], test[u'stack'])
+                msg = "%s\n%s" % (test[u'name'], test[u'message'])
                 assert test[u'status_string'] == u'PASS', msg
         else:
             assert summarized == self.expected
 
     @staticmethod
     def _assert_sequence(nums):
-        assert nums == range(1, nums[-1] + 1)
+        if nums and len(nums) > 0:
+            assert nums == range(1, nums[-1] + 1)
 
     @staticmethod
     def _scrub_stack(test_obj):
         copy = dict(test_obj)
-
-        assert 'stack' in copy
-
-        if copy['stack'] is not None:
-            copy['stack'] = u'(implementation-defined)'
-
+        del copy['stack']
         return copy
 
     @staticmethod
diff --git a/resources/test/tests/add_cleanup.html b/resources/test/tests/add_cleanup.html
index d47e7ff..c5e0ad7 100644
--- a/resources/test/tests/add_cleanup.html
+++ b/resources/test/tests/add_cleanup.html
@@ -56,37 +56,32 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Cleanup methods are invoked exactly once and in the expected sequence.",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Cleanup methods are invoked following the completion of asynchronous tests",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "probe asynchronous",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "probe synchronous",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/add_cleanup_count.html b/resources/test/tests/add_cleanup_count.html
index a079c9b..3ea30a3 100644
--- a/resources/test/tests/add_cleanup_count.html
+++ b/resources/test/tests/add_cleanup_count.html
@@ -22,14 +22,12 @@
 {
   "summarized_status": {
     "status_string": "ERROR",
-    "message": "Test named 'test with 3 user-defined cleanup functions' specified 3 'cleanup' functions, and 1 failed.",
-    "stack": null
+    "message": "Test named 'test with 3 user-defined cleanup functions' specified 3 'cleanup' functions, and 1 failed."
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "test with 3 user-defined cleanup functions",
-      "stack": null,
       "message": null,
       "properties": {}
     }
diff --git a/resources/test/tests/add_cleanup_err.html b/resources/test/tests/add_cleanup_err.html
index 66efb07..bae1eb5 100644
--- a/resources/test/tests/add_cleanup_err.html
+++ b/resources/test/tests/add_cleanup_err.html
@@ -22,23 +22,20 @@
 {
   "summarized_status": {
     "status_string": "ERROR",
-    "message": "Test named 'Exception in cleanup function causes harness failure.' specified 1 'cleanup' function, and 1 failed.",
-    "stack": null
+    "message": "Test named 'Exception in cleanup function causes harness failure.' specified 1 'cleanup' function, and 1 failed."
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Exception in cleanup function causes harness failure.",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "NOTRUN",
       "name": "This test should not be run.",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/add_cleanup_err_multi.html b/resources/test/tests/add_cleanup_err_multi.html
index b60668c..3f116c5 100644
--- a/resources/test/tests/add_cleanup_err_multi.html
+++ b/resources/test/tests/add_cleanup_err_multi.html
@@ -29,23 +29,20 @@
 {
   "summarized_status": {
     "status_string": "ERROR",
-    "message": "Test named 'Test with multiple cleanup functions' specified 2 'cleanup' functions, and 1 failed.",
-    "stack": null
+    "message": "Test named 'Test with multiple cleanup functions' specified 2 'cleanup' functions, and 1 failed."
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Test with multiple cleanup functions",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "NOTRUN",
       "name": "Verification test",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/api-tests-1.html b/resources/test/tests/api-tests-1.html
index 53d91c4..3b5234f 100644
--- a/resources/test/tests/api-tests-1.html
+++ b/resources/test/tests/api-tests-1.html
@@ -1,462 +1,427 @@
-<!DOCTYPE HTML>

-<html>

-<head>

-<title>Sample HTML5 API Tests</title>

-<meta name="timeout" content="6000">

-</head>

-<body onload="load_test_attr.done()">

-<h1>Sample HTML5 API Tests</h1>

-<div id="log"></div>

-<script src="../../testharness.js"></script>

-<script src="../../testharnessreport.js"></script>

-<script>

-    setup_run = false;

-    setup(function() {

-            setup_run = true;

-          });

-    test(function() {assert_true(setup_run)}, "Setup function ran");

-

-    // Two examples for testing events from handler and attributes

-    var load_test_event = async_test("window onload event fires when set from the handler");

-

-    function windowLoad()

-    {

-        load_test_event.done();

-    }

-    on_event(window, "load", windowLoad);

-

-    // see the body onload below

-    var load_test_attr = async_test("body element fires the onload event set from the attribute");

-</script>

-<script>

-    function bodyElement()

-    {

-        assert_equals(document.body, document.getElementsByTagName("body")[0]);

-    }

-    test(bodyElement, "document.body should be the first body element in the document");

-

-    test(function() {

-        assert_equals(1,1);

-        assert_equals(NaN, NaN, "NaN case");

-        assert_equals(0, 0, "Zero case");

-    }, "assert_equals tests")

-

-    test(function() {

-        assert_equals(-0, 0, "Zero case");

-    }, "assert_equals tests expected to fail")

-

-    test(function() {

-        assert_not_equals({}, {}, "object case");

-        assert_not_equals(-0, 0, "Zero case");

-    }, "assert_not_equals tests")

-

-    function testAssertPass()

-    {

-        assert_true(true);

-    }

-    test(testAssertPass, "assert_true expected to pass");

-

-    function testAssertFalse()

-    {

-        assert_true(false, "false should not be true");

-    }

-    test(testAssertFalse, "assert_true expected to fail");

-

-    function basicAssertArrayEquals()

-    {

-        assert_array_equals([1, NaN], [1, NaN], "[1, NaN] is equal to [1, NaN]");

-    }

-    test(basicAssertArrayEquals, "basic assert_array_equals test");

-

-    function assertArrayEqualsUndefined()

-    {

-        assert_array_equals(undefined, [1], "undefined equals [1]?");

-    }

-    test(assertArrayEqualsUndefined, "assert_array_equals with first param undefined");

-

-    function assertArrayEqualsTrue()

-    {

-        assert_array_equals(true, [1], "true equals [1]?");

-    }

-    test(assertArrayEqualsTrue, "assert_array_equals with first param true");

-

-    function assertArrayEqualsFalse()

-    {

-        assert_array_equals(false, [1], "false equals [1]?");

-    }

-    test(assertArrayEqualsFalse, "assert_array_equals with first param false");

-

-    function assertArrayEqualsNull()

-    {

-        assert_array_equals(null, [1], "null equals [1]?");

-    }

-    test(assertArrayEqualsNull, "assert_array_equals with first param null");

-

-    function assertArrayEqualsNumeric()

-    {

-        assert_array_equals(1, [1], "1 equals [1]?");

-    }

-    test(assertArrayEqualsNumeric, "assert_array_equals with first param 1");

-

-    function basicAssertObjectEquals()

-    {

-        assert_object_equals([1, 2, [1, 2]], { 0: 1, 1: 2, 2: { 0: 1, 1: 2} }, "array is equal to object")

-    }

-    test(basicAssertObjectEquals, "basic assert_object_equals test");

-

-    function basicAssertArrayApproxEquals()

-    {

-        assert_array_approx_equals([10, 11], [11, 10], 1, "[10, 11] is approximately (+/- 1) [11, 10]")

-    }

-    test(basicAssertArrayApproxEquals, "basic assert_array_approx_equals test");

-

-    function basicAssertApproxEquals()

-    {

-        assert_approx_equals(10, 11, 1, "10 is approximately (+/- 1) 11")

-    }

-    test(basicAssertApproxEquals, "basic assert_approx_equals test");

-

-    function basicAssertLessThan()

-    {

-        assert_less_than(10, 11, "10 is less than 11")

-    }

-    test(basicAssertApproxEquals, "basic assert_less_than test");

-

-    function basicAssertGreaterThan()

-    {

-        assert_greater_than(10, 11, "10 is not greater than 11");

-    }

-    test(basicAssertGreaterThan, "assert_greater_than expected to fail");

-

-    function basicAssertGreaterThanEqual()

-    {

-        assert_greater_than_equal(10, 10, "10 is greater than or equal to 10")

-    }

-    test(basicAssertGreaterThanEqual, "basic assert_greater_than_equal test");

-

-    function basicAssertLessThanEqual()

-    {

-        assert_greater_than_equal('10', 10, "'10' is not a number")

-    }

-    test(basicAssertLessThanEqual, "assert_less_than_equal expected to fail");

-

-    function testAssertInherits() {

-        var A = function(){this.a = "a"}

-        A.prototype = {b:"b"}

-        var a = new A();

-        assert_exists(a, "a");

-        assert_not_exists(a, "b");

-        assert_inherits(a, "b");

-    }

-    test(testAssertInherits, "test for assert[_not]_exists and insert_inherits")

-

-    test(function()

-    {

-        var a = document.createElement("a")

-        var b = document.createElement("b")

-        assert_throws("NOT_FOUND_ERR", function () {a.removeChild(b)});

-    }, "Test throw DOM exception")

-

-    test(function()

-    {

-        var a = document.createTextNode("a")

-        var b = document.createElement("b")

-        assert_throws("NOT_FOUND_ERR", function () {a.appendChild(b)});

-    }, "Test throw DOM exception expected to fail")

-

-    test(function()

-    {

-        var e = {code:0, name:"TEST_ERR", TEST_ERR:0}

-        assert_throws("TEST_ERR", function() {throw e});

-    }, "Test assert_throws with non-DOM-exception expected to Fail");

-

-    var t = async_test("Test step_func")

-    setTimeout(

-      t.step_func(

-        function () {

-          assert_true(true); t.done();

-        }), 0);

-

-    async_test(function(t) {

-        setTimeout(t.step_func(function (){assert_true(true); t.done();}), 0);

-    }, "Test async test with callback");

-

-    async_test(function() {

-        setTimeout(this.step_func(function (){assert_true(true); this.done();}), 0);

-    }, "Test async test with callback and `this` obj.");

-

-    async_test("test should timeout (fail) with the default of 2 seconds").step(function(){});

-

-    async_test("test should timeout (fail) with a custom set timeout value of 1 second",

-               {timeout:1000}).step(function(){});

-

-    async_test("async test that is never started, should have status Not Run", {timeout:1000});

-

-

-    test(function(t) {

-             window.global = 1;

-             t.add_cleanup(function() {delete window.global});

-             assert_equals(window.global, 1);

-         },

-         "Test that defines a global and cleans it up");

-

-    test(function() {assert_equals(window.global, undefined)},

-         "Test that cleanup handlers from previous test ran");

-

-</script>

-<script type="text/json" id="expected">

-{

-  "summarized_status": {

-    "status_string": "TIMEOUT",

-    "message": null,

-    "stack": null

-  },

-  "summarized_tests": [

-    {

-      "status_string": "PASS", 

-      "name": "Setup function ran", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "Test assert_throws with non-DOM-exception expected to Fail", 

-      "stack": "(implementation-defined)", 

-      "message": "Test bug: unrecognized DOMException code \"TEST_ERR\" passed to assert_throws()", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "Test async test with callback", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "Test async test with callback and `this` obj.", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "Test step_func", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "Test that cleanup handlers from previous test ran", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "Test that defines a global and cleans it up", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "Test throw DOM exception", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "Test throw DOM exception expected to fail", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_throws: function \"function () {a.appendChild(b)}\" threw object \"HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy\" that is not a DOMException NOT_FOUND_ERR: property \"code\" is equal to 3, expected 8", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_array_equals with first param 1", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_array_equals: 1 equals [1]? value is 1, expected array", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_array_equals with first param false", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_array_equals: false equals [1]? value is false, expected array", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_array_equals with first param null", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_array_equals: null equals [1]? value is null, expected array", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_array_equals with first param true", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_array_equals: true equals [1]? value is true, expected array", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_array_equals with first param undefined", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_array_equals: undefined equals [1]? value is undefined, expected array", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "assert_equals tests", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_equals tests expected to fail", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_equals: Zero case expected 0 but got -0", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_greater_than expected to fail", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_greater_than: 10 is not greater than 11 expected a number greater than 11 but got 10", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_less_than_equal expected to fail", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_greater_than_equal: '10' is not a number expected a number but got a \"string\"", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "assert_not_equals tests", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "FAIL", 

-      "name": "assert_true expected to fail", 

-      "stack": "(implementation-defined)", 

-      "message": "assert_true: false should not be true expected true got false", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "assert_true expected to pass", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "NOTRUN", 

-      "name": "async test that is never started, should have status Not Run", 

-      "stack": null, 

-      "message": null, 

-      "properties": {

-        "timeout": 1000

-      }

-    }, 

-    {

-      "status_string": "PASS",

-      "name": "basic assert_approx_equals test",

-      "stack": null,

-      "message": null,

-      "properties": {}

-    },

-    {

-      "status_string": "PASS",

-      "name": "basic assert_array_approx_equals test",

-      "stack": null,

-      "message": null,

-      "properties": {}

-    },

-    {

-      "status_string": "PASS", 

-      "name": "basic assert_array_equals test", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "basic assert_greater_than_equal test", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "basic assert_less_than test", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "basic assert_object_equals test", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "body element fires the onload event set from the attribute", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "document.body should be the first body element in the document", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "test for assert[_not]_exists and insert_inherits", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }, 

-    {

-      "status_string": "TIMEOUT", 

-      "name": "test should timeout (fail) with a custom set timeout value of 1 second", 

-      "stack": null, 

-      "message": "Test timed out", 

-      "properties": {

-        "timeout": 1000

-      }

-    }, 

-    {

-      "status_string": "TIMEOUT", 

-      "name": "test should timeout (fail) with the default of 2 seconds", 

-      "stack": null, 

-      "message": "Test timed out", 

-      "properties": {}

-    }, 

-    {

-      "status_string": "PASS", 

-      "name": "window onload event fires when set from the handler", 

-      "stack": null, 

-      "message": null, 

-      "properties": {}

-    }

-  ],

-  "type": "complete"

-}

-</script>

-</body>

-</html>

+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Sample HTML5 API Tests</title>
+<meta name="timeout" content="6000">
+</head>
+<body onload="load_test_attr.done()">
+<h1>Sample HTML5 API Tests</h1>
+<div id="log"></div>
+<script src="../../testharness.js"></script>
+<script src="../../testharnessreport.js"></script>
+<script>
+    setup_run = false;
+    setup(function() {
+            setup_run = true;
+          });
+    test(function() {assert_true(setup_run)}, "Setup function ran");
+
+    // Two examples for testing events from handler and attributes
+    var load_test_event = async_test("window onload event fires when set from the handler");
+
+    function windowLoad()
+    {
+        load_test_event.done();
+    }
+    on_event(window, "load", windowLoad);
+
+    // see the body onload below
+    var load_test_attr = async_test("body element fires the onload event set from the attribute");
+</script>
+<script>
+    function bodyElement()
+    {
+        assert_equals(document.body, document.getElementsByTagName("body")[0]);
+    }
+    test(bodyElement, "document.body should be the first body element in the document");
+
+    test(function() {
+        assert_equals(1,1);
+        assert_equals(NaN, NaN, "NaN case");
+        assert_equals(0, 0, "Zero case");
+    }, "assert_equals tests")
+
+    test(function() {
+        assert_equals(-0, 0, "Zero case");
+    }, "assert_equals tests expected to fail")
+
+    test(function() {
+        assert_not_equals({}, {}, "object case");
+        assert_not_equals(-0, 0, "Zero case");
+    }, "assert_not_equals tests")
+
+    function testAssertPass()
+    {
+        assert_true(true);
+    }
+    test(testAssertPass, "assert_true expected to pass");
+
+    function testAssertFalse()
+    {
+        assert_true(false, "false should not be true");
+    }
+    test(testAssertFalse, "assert_true expected to fail");
+
+    function basicAssertArrayEquals()
+    {
+        assert_array_equals([1, NaN], [1, NaN], "[1, NaN] is equal to [1, NaN]");
+    }
+    test(basicAssertArrayEquals, "basic assert_array_equals test");
+
+    function assertArrayEqualsUndefined()
+    {
+        assert_array_equals(undefined, [1], "undefined equals [1]?");
+    }
+    test(assertArrayEqualsUndefined, "assert_array_equals with first param undefined");
+
+    function assertArrayEqualsTrue()
+    {
+        assert_array_equals(true, [1], "true equals [1]?");
+    }
+    test(assertArrayEqualsTrue, "assert_array_equals with first param true");
+
+    function assertArrayEqualsFalse()
+    {
+        assert_array_equals(false, [1], "false equals [1]?");
+    }
+    test(assertArrayEqualsFalse, "assert_array_equals with first param false");
+
+    function assertArrayEqualsNull()
+    {
+        assert_array_equals(null, [1], "null equals [1]?");
+    }
+    test(assertArrayEqualsNull, "assert_array_equals with first param null");
+
+    function assertArrayEqualsNumeric()
+    {
+        assert_array_equals(1, [1], "1 equals [1]?");
+    }
+    test(assertArrayEqualsNumeric, "assert_array_equals with first param 1");
+
+    function basicAssertObjectEquals()
+    {
+        assert_object_equals([1, 2, [1, 2]], { 0: 1, 1: 2, 2: { 0: 1, 1: 2} }, "array is equal to object")
+    }
+    test(basicAssertObjectEquals, "basic assert_object_equals test");
+
+    function basicAssertArrayApproxEquals()
+    {
+        assert_array_approx_equals([10, 11], [11, 10], 1, "[10, 11] is approximately (+/- 1) [11, 10]")
+    }
+    test(basicAssertArrayApproxEquals, "basic assert_array_approx_equals test");
+
+    function basicAssertApproxEquals()
+    {
+        assert_approx_equals(10, 11, 1, "10 is approximately (+/- 1) 11")
+    }
+    test(basicAssertApproxEquals, "basic assert_approx_equals test");
+
+    function basicAssertLessThan()
+    {
+        assert_less_than(10, 11, "10 is less than 11")
+    }
+    test(basicAssertApproxEquals, "basic assert_less_than test");
+
+    function basicAssertGreaterThan()
+    {
+        assert_greater_than(10, 11, "10 is not greater than 11");
+    }
+    test(basicAssertGreaterThan, "assert_greater_than expected to fail");
+
+    function basicAssertGreaterThanEqual()
+    {
+        assert_greater_than_equal(10, 10, "10 is greater than or equal to 10")
+    }
+    test(basicAssertGreaterThanEqual, "basic assert_greater_than_equal test");
+
+    function basicAssertLessThanEqual()
+    {
+        assert_greater_than_equal('10', 10, "'10' is not a number")
+    }
+    test(basicAssertLessThanEqual, "assert_less_than_equal expected to fail");
+
+    function testAssertInherits() {
+        var A = function(){this.a = "a"}
+        A.prototype = {b:"b"}
+        var a = new A();
+        assert_exists(a, "a");
+        assert_not_exists(a, "b");
+        assert_inherits(a, "b");
+    }
+    test(testAssertInherits, "test for assert[_not]_exists and insert_inherits")
+
+    test(function()
+    {
+        var a = document.createElement("a")
+        var b = document.createElement("b")
+        assert_throws("NOT_FOUND_ERR", function () {a.removeChild(b)});
+    }, "Test throw DOM exception")
+
+    test(function()
+    {
+        var a = document.createTextNode("a")
+        var b = document.createElement("b")
+        assert_throws("NOT_FOUND_ERR", function () {a.appendChild(b)});
+    }, "Test throw DOM exception expected to fail")
+
+    test(function()
+    {
+        var e = {code:0, name:"TEST_ERR", TEST_ERR:0}
+        assert_throws("TEST_ERR", function() {throw e});
+    }, "Test assert_throws with non-DOM-exception expected to Fail");
+
+    var t = async_test("Test step_func")
+    setTimeout(
+      t.step_func(
+        function () {
+          assert_true(true); t.done();
+        }), 0);
+
+    async_test(function(t) {
+        setTimeout(t.step_func(function (){assert_true(true); t.done();}), 0);
+    }, "Test async test with callback");
+
+    async_test(function() {
+        setTimeout(this.step_func(function (){assert_true(true); this.done();}), 0);
+    }, "Test async test with callback and `this` obj.");
+
+    async_test("test should timeout (fail) with the default of 2 seconds").step(function(){});
+
+    async_test("test should timeout (fail) with a custom set timeout value of 1 second",
+               {timeout:1000}).step(function(){});
+
+    async_test("async test that is never started, should have status Not Run", {timeout:1000});
+
+
+    test(function(t) {
+             window.global = 1;
+             t.add_cleanup(function() {delete window.global});
+             assert_equals(window.global, 1);
+         },
+         "Test that defines a global and cleans it up");
+
+    test(function() {assert_equals(window.global, undefined)},
+         "Test that cleanup handlers from previous test ran");
+
+</script>
+<script type="text/json" id="expected">
+{
+  "summarized_status": {
+    "status_string": "TIMEOUT",
+    "message": null
+  },
+  "summarized_tests": [
+    {
+      "status_string": "PASS",
+      "name": "Setup function ran",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "Test assert_throws with non-DOM-exception expected to Fail",
+      "message": "Test bug: unrecognized DOMException code \"TEST_ERR\" passed to assert_throws()",
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "Test async test with callback",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "Test async test with callback and `this` obj.",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "Test step_func",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "Test that cleanup handlers from previous test ran",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "Test that defines a global and cleans it up",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "Test throw DOM exception",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "Test throw DOM exception expected to fail",
+      "message": "assert_throws: function \"function () {a.appendChild(b)}\" threw object \"HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy\" that is not a DOMException NOT_FOUND_ERR: property \"code\" is equal to 3, expected 8",
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_array_equals with first param 1",
+      "message": "assert_array_equals: 1 equals [1]? value is 1, expected array",
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_array_equals with first param false",
+      "message": "assert_array_equals: false equals [1]? value is false, expected array",
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_array_equals with first param null",
+      "message": "assert_array_equals: null equals [1]? value is null, expected array",
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_array_equals with first param true",
+      "message": "assert_array_equals: true equals [1]? value is true, expected array",
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_array_equals with first param undefined",
+      "message": "assert_array_equals: undefined equals [1]? value is undefined, expected array",
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "assert_equals tests",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_equals tests expected to fail",
+      "message": "assert_equals: Zero case expected 0 but got -0",
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_greater_than expected to fail",
+      "message": "assert_greater_than: 10 is not greater than 11 expected a number greater than 11 but got 10",
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_less_than_equal expected to fail",
+      "message": "assert_greater_than_equal: '10' is not a number expected a number but got a \"string\"",
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "assert_not_equals tests",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "FAIL",
+      "name": "assert_true expected to fail",
+      "message": "assert_true: false should not be true expected true got false",
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "assert_true expected to pass",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "NOTRUN",
+      "name": "async test that is never started, should have status Not Run",
+      "message": null,
+      "properties": {
+        "timeout": 1000
+      }
+    },
+    {
+      "status_string": "PASS",
+      "name": "basic assert_approx_equals test",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "basic assert_array_approx_equals test",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "basic assert_array_equals test",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "basic assert_greater_than_equal test",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "basic assert_less_than test",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "basic assert_object_equals test",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "body element fires the onload event set from the attribute",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "document.body should be the first body element in the document",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "test for assert[_not]_exists and insert_inherits",
+      "message": null,
+      "properties": {}
+    },
+    {
+      "status_string": "TIMEOUT",
+      "name": "test should timeout (fail) with a custom set timeout value of 1 second",
+      "message": "Test timed out",
+      "properties": {
+        "timeout": 1000
+      }
+    },
+    {
+      "status_string": "TIMEOUT",
+      "name": "test should timeout (fail) with the default of 2 seconds",
+      "message": "Test timed out",
+      "properties": {}
+    },
+    {
+      "status_string": "PASS",
+      "name": "window onload event fires when set from the handler",
+      "message": null,
+      "properties": {}
+    }
+  ],
+  "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/resources/test/tests/api-tests-2.html b/resources/test/tests/api-tests-2.html
index 99fa5be..164106b 100644
--- a/resources/test/tests/api-tests-2.html
+++ b/resources/test/tests/api-tests-2.html
@@ -3,7 +3,7 @@
 <head>
 <title>Sample HTML5 API Tests</title>
 </head>
-<body onload="load_test_attr.done()">
+<body>
 <h1>Sample HTML5 API Tests</h1>
 <p>There should be two results</p>
 <div id="log"></div>
@@ -21,23 +21,20 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Test defined after onload",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test defined before onload",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/api-tests-3.html b/resources/test/tests/api-tests-3.html
index fb571c8..5dbeb2d 100644
--- a/resources/test/tests/api-tests-3.html
+++ b/resources/test/tests/api-tests-3.html
@@ -5,7 +5,7 @@
 </head>
 <script src="../../testharness.js"></script>
 
-<body onload="load_test_attr.done()">
+<body>
 <h1>Sample HTML5 API Tests</h1>
 <div id="log"></div>
 <script>
@@ -17,16 +17,14 @@
 {
   "summarized_status": {
     "status_string": "TIMEOUT",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "NOTRUN",
       "name": "This test should give a status of 'Not Run' without a delay",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/force_timeout.html b/resources/test/tests/force_timeout.html
new file mode 100644
index 0000000..7827068
--- /dev/null
+++ b/resources/test/tests/force_timeout.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#force_timeout</title>
+</head>
+<body>
+<h1>Test#force_timeout</h1>
+<div id="log"></div>
+<script src="../../testharness.js"></script>
+<script src="../../testharnessreport.js"></script>
+<script>
+setup({ explicit_timeout: true });
+
+test(function(t) {
+    t.force_timeout();
+  }, 'test (synchronous)');
+
+async_test(function(t) {
+    t.step_timeout(function() {
+        t.force_timeout();
+      }, 0);
+  }, 'async_test');
+
+promise_test(function(t) {
+    t.force_timeout();
+
+    return new Promise(function() {});
+  }, 'promise_test');
+</script>
+<script type="text/json" id="expected">
+{
+  "summarized_status": {
+    "status_string": "OK",
+    "message": null
+  },
+  "summarized_tests": [
+    {
+      "status_string": "TIMEOUT",
+      "name": "async_test",
+      "message": "Test timed out",
+      "properties": {}
+    },
+    {
+      "status_string": "TIMEOUT",
+      "name": "promise_test",
+      "message": "Test timed out",
+      "properties": {}
+    },
+    {
+      "status_string": "TIMEOUT",
+      "name": "test (synchronous)",
+      "message": "Test timed out",
+      "properties": {}
+    }
+  ],
+  "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/resources/test/tests/generate-callback.html b/resources/test/tests/generate-callback.html
index a72ad62..574f430 100644
--- a/resources/test/tests/generate-callback.html
+++ b/resources/test/tests/generate-callback.html
@@ -10,34 +10,34 @@
 // generate_tests takes an array of arrays that define tests
 // but lets pass it an empty array and verify it does nothing.
 function null_callback() {
-	throw "null_callback should not be called.";
+  throw "null_callback should not be called.";
 }
 generate_tests(null_callback, []);
 
 // Generate 3 tests specifying the name and one parameter
 function validate_arguments(arg1) {
-	assert_equals(arg1, 1, "Ensure that we get our expected argument");
+  assert_equals(arg1, 1, "Ensure that we get our expected argument");
 }
 generate_tests(validate_arguments, [
-	["first test", 1],
-	["second test", 1],
-	["third test", 1],
+  ["first test", 1],
+  ["second test", 1],
+  ["third test", 1],
 ]);
 
 // Generate a test passing in a properties object that is shared across tests.
 function validate_properties() {
-	assert_true(this.properties.sentinel, "Ensure that we got the right properties object.");
+  assert_true(this.properties.sentinel, "Ensure that we got the right properties object.");
 }
 generate_tests(validate_properties, [["sentinel check 1"], ["sentinel check 2"]], {sentinel: true});
 
 // Generate a test passing in a properties object that is shared across tests.
 function validate_separate_properties() {
-	if (this.name === "sentinel check 1 unique properties") {
-		assert_true(this.properties.sentinel, "Ensure that we got the right properties object. Expect sentinel: true.");
-	}
-	else {
-		assert_false(this.properties.sentinel, "Ensure that we got the right properties object. Expect sentinel: false.");
-	}
+  if (this.name === "sentinel check 1 unique properties") {
+    assert_true(this.properties.sentinel, "Ensure that we got the right properties object. Expect sentinel: true.");
+  }
+  else {
+    assert_false(this.properties.sentinel, "Ensure that we got the right properties object. Expect sentinel: false.");
+  }
 }
 generate_tests(validate_separate_properties, [["sentinel check 1 unique properties"], ["sentinel check 2 unique properties"]], [{sentinel: true}, {sentinel: false}]);
 
@@ -45,10 +45,10 @@
 var letters = ["a", "b", "c", "d", "e", "f"];
 var numbers = [0, 1, 2, 3, 4, 5];
 function validate_related_arguments(arg1, arg2) {
-	assert_equals(arg1.charCodeAt(0) - "a".charCodeAt(0), arg2, "Ensure that we can map letters to numbers.");
+  assert_equals(arg1.charCodeAt(0) - "a".charCodeAt(0), arg2, "Ensure that we can map letters to numbers.");
 }
 function format_as_test(letter, index, letters) {
-	return ["Test to map " + letter + " to " + numbers[index], letter, numbers[index]];
+  return ["Test to map " + letter + " to " + numbers[index], letter, numbers[index]];
 }
 generate_tests(validate_related_arguments, letters.map(format_as_test));
 </script>
@@ -56,65 +56,56 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Test to map a to 0",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test to map b to 1",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test to map c to 2",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test to map d to 3",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test to map e to 4",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test to map f to 5",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "first test",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "second test",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
@@ -122,8 +113,7 @@
       "properties": {
         "sentinel": true
       },
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
@@ -131,8 +121,7 @@
       "properties": {
         "sentinel": true
       },
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
@@ -140,8 +129,7 @@
       "properties": {
         "sentinel": true
       },
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
@@ -149,15 +137,13 @@
       "properties": {
         "sentinel": false
       },
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "third test",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/idlharness/IdlArray/is_json_type.html b/resources/test/tests/idlharness/IdlArray/is_json_type.html
index 42f9dd5..f7ebc9d 100644
--- a/resources/test/tests/idlharness/IdlArray/is_json_type.html
+++ b/resources/test/tests/idlharness/IdlArray/is_json_type.html
@@ -73,7 +73,7 @@
         assert_false(idl.is_json_type(typeFrom("sequence<DOMException>")));
         assert_true(idl.is_json_type(typeFrom("sequence<DOMString>")));
     }, 'should handle sequences according to their inner types');
-    
+
     test(function() {
         var idl = new IdlArray();
         assert_false(idl.is_json_type(typeFrom("FrozenArray<DOMException>")));
@@ -192,4 +192,3 @@
 </script>
 </body>
 </html>
-
diff --git a/resources/test/tests/idlharness/IdlDictionary/get_inheritance_stack.html b/resources/test/tests/idlharness/IdlDictionary/get_inheritance_stack.html
index 83863fb..98168ee1 100644
--- a/resources/test/tests/idlharness/IdlDictionary/get_inheritance_stack.html
+++ b/resources/test/tests/idlharness/IdlDictionary/get_inheritance_stack.html
@@ -16,12 +16,12 @@
         var stack = dictionaryFrom('dictionary A { };').get_inheritance_stack();
         assert_array_equals(stack.map(d => d.name), ["A"]);
     }, 'should return an array that includes itself.');
-    
+
     test(function() {
         var d = dictionaryFrom('dictionary A : B { };');
         assert_throws(new Error(), _ => d.get_inheritance_stack());
     }, "should throw for dictionaries which inherit from another dictionary which wasn't added to the IdlArray");
-    
+
     test(function() {
         var idl = new IdlArray();
         idl.add_idls('dictionary A : B { };');
@@ -29,7 +29,19 @@
         var A = idl.members["A"];
         assert_array_equals(A.get_inheritance_stack().map(d => d.name), ["A", "B", "C"]);
     }, 'should return an array of inherited dictionaries in order of inheritance, starting with itself.');
+
+    test(function () {
+      let i = new IdlArray();
+      i.add_untested_idls('dictionary A : B {};');
+      i.assert_throws(new IdlHarnessError('A inherits B, but B is undefined.'), i => i.test());
+    }, 'A : B with B undeclared should throw IdlHarnessError');
+
+    test(function () {
+      let i = new IdlArray();
+      i.add_untested_idls('dictionary A : B {};');
+      i.add_untested_idls('interface B {};');
+      i.assert_throws(new IdlHarnessError('A inherits B, but A is not an interface.'), i => i.test());
+    }, 'dictionary A : B with B interface should throw IdlHarnessError');
 </script>
 </body>
 </html>
-
diff --git a/resources/test/tests/idlharness/IdlDictionary/test_partial_dictionary.html b/resources/test/tests/idlharness/IdlDictionary/test_partial_dictionary.html
new file mode 100644
index 0000000..f716b93
--- /dev/null
+++ b/resources/test/tests/idlharness/IdlDictionary/test_partial_dictionary.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title>idlharness: partial dictionaries</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/WebIDLParser.js"></script>
+  <script src="/resources/idlharness.js"></script>
+  <script src="../helper.js"></script>
+</head>
+
+<body>
+<pre id='idl'>
+dictionary A {};
+partial dictionary A {
+  boolean B;
+};
+partial dictionary A {
+  boolean C;
+};
+</pre>
+
+<script>
+'use strict';
+
+test(() => {
+  let idlArray = new IdlArray();
+  idlArray.add_idls(document.getElementById('idl').textContent);
+  idlArray.test();
+
+  let members = idlArray.members["A"].members.map(m => m.name);
+  assert_array_equals(members, ["B", "C"], 'A should contain B, C');
+}, 'Partial dictionaries');
+
+test(() => {
+  let idlArray = new IdlArray();
+  idlArray.add_idls('partial dictionary D {};');
+  idlArray.assert_throws('Partial dictionary D with no original dictionary', i => i.test());
+}, 'Partial-only dictionary definition')
+</script>
+
+</body>
+</html>
diff --git a/resources/test/tests/idlharness/IdlInterface/get_inheritance_stack.html b/resources/test/tests/idlharness/IdlInterface/get_inheritance_stack.html
index c1d6141..fed6f92 100644
--- a/resources/test/tests/idlharness/IdlInterface/get_inheritance_stack.html
+++ b/resources/test/tests/idlharness/IdlInterface/get_inheritance_stack.html
@@ -16,12 +16,12 @@
         var stack = interfaceFrom('interface A { };').get_inheritance_stack();
         assert_array_equals(stack.map(i => i.name), ["A"]);
     }, 'should return an array that includes itself.');
-    
+
     test(function() {
         var i = interfaceFrom('interface A : B { };');
         assert_throws(new Error(), _ => i.get_inheritance_stack());
     }, "should throw for interfaces which inherit from another interface which wasn't added to the IdlArray");
-    
+
     test(function() {
         var idl = new IdlArray();
         idl.add_idls('interface A : B { };');
@@ -29,7 +29,21 @@
         var A = idl.members["A"];
         assert_array_equals(A.get_inheritance_stack().map(i => i.name), ["A", "B", "C"]);
     }, 'should return an array of inherited interfaces in order of inheritance, starting with itself.');
+
+    test(function () {
+        var idl = new IdlArray();
+        idl.add_untested_idls('interface A : B { };');
+        idl.add_untested_idls('interface B : A { };');
+        idl.assert_throws('A has a circular dependency: A,B,A', i => i.test());
+    }, 'should throw when inheritance is circular');
+
+    test(function () {
+        var idl = new IdlArray();
+        idl.add_untested_idls('interface A : B { };');
+        idl.assert_throws(
+            'Duplicate identifier A',
+            i => i.add_untested_idls('interface A : C { };'));
+    }, 'should throw when multiple inheritances defined');
 </script>
 </body>
 </html>
-
diff --git a/resources/test/tests/idlharness/IdlInterface/has_to_json_regular_operation.html b/resources/test/tests/idlharness/IdlInterface/has_to_json_regular_operation.html
index a4b4e91..bf7d926 100644
--- a/resources/test/tests/idlharness/IdlInterface/has_to_json_regular_operation.html
+++ b/resources/test/tests/idlharness/IdlInterface/has_to_json_regular_operation.html
@@ -16,12 +16,12 @@
         var i = interfaceFrom('interface A { };');
         assert_false(i.has_to_json_regular_operation());
     }, 'should return false when the interface declares no toJSON operation.');
-    
+
     test(function() {
         var i = interfaceFrom('interface A { static object toJSON(); };');
         assert_false(i.has_to_json_regular_operation());
     }, 'should return false when the interface declares a static toJSON operation.');
-    
+
     test(function() {
         var i = interfaceFrom('interface A { object toJSON(); };');
         assert_true(i.has_to_json_regular_operation());
@@ -29,4 +29,3 @@
 </script>
 </body>
 </html>
-
diff --git a/resources/test/tests/idlharness/IdlInterface/test_immutable_prototype.html b/resources/test/tests/idlharness/IdlInterface/test_immutable_prototype.html
index 16214c4..a9d631d 100644
--- a/resources/test/tests/idlharness/IdlInterface/test_immutable_prototype.html
+++ b/resources/test/tests/idlharness/IdlInterface/test_immutable_prototype.html
@@ -31,7 +31,7 @@
     "interface Window : EventTarget {};\n" +
 
     "[Global=Window, Exposed=Window, Constructor()]\n" +
-	"interface Foo {};"
+    "interface Foo {};"
   );
 idlArray.add_objects({
   Foo: ["new Foo()"],
@@ -43,275 +43,248 @@
 {
     "summarized_status": {
         "message": null,
-        "status_string": "OK",
-        "stack": null
+        "status_string": "OK"
     },
     "summarized_tests": [
         {
             "name": "Foo interface object length",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface object name",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: existence and properties of interface object",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: existence and properties of interface prototype object",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: existence and properties of interface prototype object's \"constructor\" property",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
+        },
+        {
+            "name": "Foo interface: existence and properties of interface prototype object's @@unscopables property",
+            "status_string": "PASS",
+            "properties": {},
+            "message": null
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
             "status_string": "FAIL",
             "properties": {},
-            "message": "assert_throws: function \"function() {\n            Object.setPrototypeOf(obj, newValue);\n        }\" did not throw",
-            "stack": "(implementation-defined)"
+            "message": "assert_throws: function \"function() {\n            Object.setPrototypeOf(obj, newValue);\n        }\" did not throw"
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Reflect.setPrototypeOf should return false",
             "status_string": "FAIL",
             "properties": {},
-            "message": "assert_false: expected false got true",
-            "stack": "(implementation-defined)"
+            "message": "assert_false: expected false got true"
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via __proto__ should throw a TypeError",
             "status_string": "FAIL",
             "properties": {},
-            "message": "assert_throws: function \"function() {\n            obj.__proto__ = newValue;\n        }\" did not throw",
-            "stack": "(implementation-defined)"
+            "message": "assert_throws: function \"function() {\n            obj.__proto__ = newValue;\n        }\" did not throw"
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Object.setPrototypeOf should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Reflect.setPrototypeOf should return true",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via __proto__ should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
             "status_string": "FAIL",
             "properties": {},
-            "message": "assert_throws: function \"function() {\n            Object.setPrototypeOf(obj, newValue);\n        }\" did not throw",
-            "stack": "(implementation-defined)"
+            "message": "assert_throws: function \"function() {\n            Object.setPrototypeOf(obj, newValue);\n        }\" did not throw"
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Reflect.setPrototypeOf should return false",
             "status_string": "FAIL",
             "properties": {},
-            "message": "assert_false: expected false got true",
-            "stack": "(implementation-defined)"
+            "message": "assert_false: expected false got true"
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via __proto__ should throw a TypeError",
             "status_string": "FAIL",
             "properties": {},
-            "message": "assert_throws: function \"function() {\n            obj.__proto__ = newValue;\n        }\" did not throw",
-            "stack": "(implementation-defined)"
+            "message": "assert_throws: function \"function() {\n            obj.__proto__ = newValue;\n        }\" did not throw"
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Object.setPrototypeOf should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Reflect.setPrototypeOf should return true",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via __proto__ should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Foo must be primary interface of new Foo()",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Stringification of new Foo()",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Stringification of window",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface object length",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface object name",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: existence and properties of interface object",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: existence and properties of interface prototype object",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: existence and properties of interface prototype object's \"constructor\" property",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
+        },
+        {
+            "name": "Window interface: existence and properties of interface prototype object's @@unscopables property",
+            "status_string": "PASS",
+            "properties": {},
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Reflect.setPrototypeOf should return false",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via __proto__ should throw a TypeError",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Object.setPrototypeOf should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Reflect.setPrototypeOf should return true",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via __proto__ should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Reflect.setPrototypeOf should return false",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via __proto__ should throw a TypeError",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Object.setPrototypeOf should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Reflect.setPrototypeOf should return true",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via __proto__ should not throw",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         },
         {
             "name": "Window must be primary interface of window",
             "status_string": "PASS",
             "properties": {},
-            "message": null,
-            "stack": null
+            "message": null
         }
     ],
     "type": "complete"
diff --git a/resources/test/tests/idlharness/IdlInterface/test_primary_interface_of.html b/resources/test/tests/idlharness/IdlInterface/test_primary_interface_of.html
index 859340a..69542f1 100644
--- a/resources/test/tests/idlharness/IdlInterface/test_primary_interface_of.html
+++ b/resources/test/tests/idlharness/IdlInterface/test_primary_interface_of.html
@@ -50,58 +50,56 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "name": "Foo interface object length",
       "status_string": "PASS",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "name": "Foo interface object name",
       "status_string": "PASS",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "name": "Foo interface: existence and properties of interface object",
       "status_string": "PASS",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "name": "Foo interface: existence and properties of interface prototype object",
       "status_string": "PASS",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "name": "Foo interface: existence and properties of interface prototype object's \"constructor\" property",
       "status_string": "PASS",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
+    },
+    {
+      "name": "Foo interface: existence and properties of interface prototype object's @@unscopables property",
+      "status_string": "PASS",
+      "properties": {},
+      "message": null
     },
     {
       "name": "Foo must be primary interface of new Foo()",
       "status_string": "PASS",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "name": "Stringification of new Foo()",
       "status_string": "PASS",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/idlharness/IdlInterface/test_primary_interface_of_undefined.html b/resources/test/tests/idlharness/IdlInterface/test_primary_interface_of_undefined.html
new file mode 100644
index 0000000..0031558
--- /dev/null
+++ b/resources/test/tests/idlharness/IdlInterface/test_primary_interface_of_undefined.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+  <title>idlharness test_primary_interface_of_undefined</title>
+</head>
+
+<body>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/WebIDLParser.js"></script>
+  <script src="/resources/idlharness.js"></script>
+  <script>
+    'use strict';
+    test(function () {
+      let i = new IdlArray();
+      i.add_untested_idls('interface A : B {};');
+      i.assert_throws(new IdlHarnessError('A inherits B, but B is undefined.'), i => i.test());
+    }, 'A : B with B undeclared should throw IdlHarnessError');
+
+    test(function () {
+      let i = new IdlArray();
+      i.add_untested_idls('interface A : B {};');
+      i.add_untested_idls('dictionary B {};');
+      i.assert_throws(new IdlHarnessError('A inherits B, but B is not an interface.'), i => i.test());
+    }, 'interface A : B with B dictionary should throw IdlHarnessError');
+  </script>
+</body>
+
+</html>
diff --git a/resources/test/tests/idlharness/IdlInterface/test_to_json_operation.html b/resources/test/tests/idlharness/IdlInterface/test_to_json_operation.html
index c758d5b..ef8fcc9 100644
--- a/resources/test/tests/idlharness/IdlInterface/test_to_json_operation.html
+++ b/resources/test/tests/idlharness/IdlInterface/test_to_json_operation.html
@@ -12,7 +12,7 @@
 <body>
 <script>
     "use strict";
-    function wrap(obj) {
+    function wrap(member, obj) {
         function F(obj) {
             this._obj = obj;
         }
@@ -20,25 +20,25 @@
         F.prototype.toJSON = function() {
             return this._obj;
         }
-
+        Object.defineProperty(F, 'name', { value: member.name });
         return new F(obj);
     }
 
     var i, obj;
     i = interfaceFrom("interface A { [Default] object toJSON(); attribute long foo; };");
-    i.test_to_json_operation(wrap({ foo: 123 }), i.members[0]);
+    i.test_to_json_operation(wrap(i, { foo: 123 }), i.members[0]);
 
     // should fail (wrong type)
     i = interfaceFrom("interface B { [Default] object toJSON(); attribute long foo; };");
-    i.test_to_json_operation(wrap({ foo: "a value" }), i.members[0]);
+    i.test_to_json_operation(wrap(i, { foo: "a value" }), i.members[0]);
 
     // should handle extraneous attributes (e.g. from an extension specification)
     i = interfaceFrom("interface C { [Default] object toJSON(); attribute long foo; };");
-    i.test_to_json_operation(wrap({ foo: 123, bar: 456 }), i.members[0]);
+    i.test_to_json_operation(wrap(i, { foo: 123, bar: 456 }), i.members[0]);
 
     // should fail (missing property)
     i = interfaceFrom("interface D { [Default] object toJSON(); attribute long foo; };");
-    i.test_to_json_operation(wrap({ }), i.members[0]);
+    i.test_to_json_operation(wrap(i, { }), i.members[0]);
 
     // should fail (should be writable)
     obj = Object.defineProperties({}, { foo: {
@@ -48,7 +48,7 @@
         value: 123
     }});
     i = interfaceFrom("interface F { [Default] object toJSON(); attribute long foo; };");
-    i.test_to_json_operation(wrap(obj), i.members[0]);
+    i.test_to_json_operation(wrap(i, obj), i.members[0]);
 
     // should fail (should be enumerable)
     obj = Object.defineProperties({}, { foo: {
@@ -58,7 +58,7 @@
         value: 123
     }});
     i = interfaceFrom("interface G { [Default] object toJSON(); attribute long foo; };");
-    i.test_to_json_operation(wrap(obj), i.members[0]);
+    i.test_to_json_operation(wrap(i, obj), i.members[0]);
 
     // should fail (should be configurable)
     obj = Object.defineProperties({}, { foo: {
@@ -68,33 +68,32 @@
         value: 123
     }});
     i = interfaceFrom("interface H { [Default] object toJSON(); attribute long foo; };");
-    i.test_to_json_operation(wrap(obj), i.members[0]);
+    i.test_to_json_operation(wrap(i, obj), i.members[0]);
 
     var idl = new IdlArray();
     idl.add_idls("interface I : J { [Default] object toJSON(); attribute long foo; };");
     idl.add_idls("interface J { [Default] object toJSON(); attribute DOMString foo;};");
     var i = idl.members.I;
-    i.test_to_json_operation(wrap({ foo: 123 }), i.members[0]);
+    i.test_to_json_operation(wrap(i, { foo: 123 }), i.members[0]);
 
     i = interfaceFrom("interface K { [Default] object toJSON(); };");
-    i.test_to_json_operation(wrap({}), i.members[0]);
+    i.test_to_json_operation(wrap(i, {}), i.members[0]);
 
     i = interfaceFrom("interface L { DOMString toJSON(); };");
-    i.test_to_json_operation(wrap("a string"), i.members[0]);
+    i.test_to_json_operation(wrap(i, "a string"), i.members[0]);
 
     // should fail (wrong output type)
     i = interfaceFrom("interface M { DOMString toJSON(); };");
-    i.test_to_json_operation(wrap({}), i.members[0]);
+    i.test_to_json_operation(wrap(i, {}), i.members[0]);
 
     // should fail (not an IDL type)
     i = interfaceFrom("interface N { DOMException toJSON(); };");
-    i.test_to_json_operation(wrap({}), i.members[0]);
+    i.test_to_json_operation(wrap(i, {}), i.members[0]);
 </script>
 <script type="text/json" id="expected">
     {
         "summarized_status": {
             "message": null,
-            "stack": null,
             "status_string": "OK"
         },
         "summarized_tests": [
@@ -102,84 +101,72 @@
                 "message": null,
                 "name": "Test default toJSON operation of A",
                 "properties": {},
-                "stack": null,
                 "status_string": "PASS"
             },
             {
                 "message": "assert_equals: expected \"number\" but got \"string\"",
                 "name": "Test default toJSON operation of B",
                 "properties": {},
-                "stack": "(implementation-defined)",
                 "status_string": "FAIL"
             },
             {
                 "message": null,
                 "name": "Test default toJSON operation of C",
                 "properties": {},
-                "stack": null,
                 "status_string": "PASS"
             },
             {
                 "message": "assert_true: property \"foo\" should be present in the output of D.prototype.toJSON() expected true got false",
                 "name": "Test default toJSON operation of D",
                 "properties": {},
-                "stack": "(implementation-defined)",
                 "status_string": "FAIL"
             },
             {
                 "message": "assert_true: property foo should be writable expected true got false",
                 "name": "Test default toJSON operation of F",
                 "properties": {},
-                "stack": "(implementation-defined)",
                 "status_string": "FAIL"
             },
             {
                 "message": "assert_true: property foo should be enumerable expected true got false",
                 "name": "Test default toJSON operation of G",
                 "properties": {},
-                "stack": "(implementation-defined)",
                 "status_string": "FAIL"
             },
             {
                 "message": "assert_true: property foo should be configurable expected true got false",
                 "name": "Test default toJSON operation of H",
                 "properties": {},
-                "stack": "(implementation-defined)",
                 "status_string": "FAIL"
             },
             {
                 "message": null,
                 "name": "Test default toJSON operation of I",
                 "properties": {},
-                "stack": null,
                 "status_string": "PASS"
             },
             {
                 "message": null,
                 "name": "Test default toJSON operation of K",
                 "properties": {},
-                "stack": null,
                 "status_string": "PASS"
             },
             {
                 "message": null,
                 "name": "Test toJSON operation of L",
                 "properties": {},
-                "stack": null,
                 "status_string": "PASS"
             },
             {
                 "message": "assert_equals: expected \"string\" but got \"object\"",
                 "name": "Test toJSON operation of M",
                 "properties": {},
-                "stack": "(implementation-defined)",
                 "status_string": "FAIL"
             },
             {
-                "message": "assert_true: {\"sequence\":false,\"generic\":null,\"nullable\":false,\"union\":false,\"idlType\":\"DOMException\"} is not an appropriate return value for the toJSON operation of N expected true got false",
+                "message": "assert_true: {\"type\":\"return-type\",\"sequence\":false,\"generic\":null,\"nullable\":false,\"union\":false,\"idlType\":\"DOMException\"} is not an appropriate return value for the toJSON operation of N expected true got false",
                 "name": "Test toJSON operation of N",
                 "properties": {},
-                "stack": "(implementation-defined)",
                 "status_string": "FAIL"
             }
         ],
diff --git a/resources/test/tests/idlharness/IdlInterface/traverse_inherited_and_consequential_interfaces.html b/resources/test/tests/idlharness/IdlInterface/traverse_inherited_and_consequential_interfaces.html
index 1a19291..667c791 100644
--- a/resources/test/tests/idlharness/IdlInterface/traverse_inherited_and_consequential_interfaces.html
+++ b/resources/test/tests/idlharness/IdlInterface/traverse_inherited_and_consequential_interfaces.html
@@ -37,7 +37,7 @@
         });
         assert_array_equals(interfaces, ["A"]);
     }, 'should return an array that includes itself.');
-    
+
     test(function() {
         var context = new IdlArray();
         context.add_idls("interface A { }; A implements B;");
@@ -49,7 +49,7 @@
         context.add_idls("interface A { };");
         assert_throws(new TypeError(), _ => context.members["A"].traverse_inherited_and_consequential_interfaces());
     }, "should throw if not passed a callback");
-    
+
     test(function() {
         var context = new IdlArray();
         context.add_idls(document.getElementById('fragments').textContent);
@@ -62,4 +62,3 @@
 </script>
 </body>
 </html>
-
diff --git a/resources/test/tests/idlharness/basic.html b/resources/test/tests/idlharness/basic.html
index da3c3fa..439c203 100644
--- a/resources/test/tests/idlharness/basic.html
+++ b/resources/test/tests/idlharness/basic.html
@@ -29,6 +29,27 @@
     test(function() {
         assert_equals(typeof WebIDL2.parse("interface Foo {};"), "object");
     }, 'WebIDL2 parse method should produce an AST for correct WebIDL');
+    for (let type of ['dictionary', 'interface']) {
+        test(function() {
+            let i = new IdlArray();
+            i.add_untested_idls(`partial ${type} A {};`);
+            i.assert_throws(new IdlHarnessError(`Partial ${type} A with no original ${type}`), i => i.test());
+        }, `assert_throws should handle ${type} IdlHarnessError`);
+        test(function() {
+            let i = new IdlArray();
+            i.add_untested_idls(`partial ${type} A {};`);
+            i.assert_throws(`Partial ${type} A with no original ${type}`, i => i.test());
+        }, `assert_throws should handle ${type} IdlHarnessError from message`);
+        test(function () {
+            try {
+                let i = new IdlArray();
+                i.add_untested_idls(`${type} A {};`);
+                i.assert_throws(`Partial ${type} A with no original ${type}`, i => i.test());
+            } catch (e) {
+                assert_true(e instanceof IdlHarnessError);
+            }
+        }, `assert_throws should throw if no ${type} IdlHarnessError thrown`);
+    }
 </script>
 </body>
 </html>
diff --git a/resources/test/tests/iframe-callback.html b/resources/test/tests/iframe-callback.html
index 1a8b2cb..b6b751b 100644
--- a/resources/test/tests/iframe-callback.html
+++ b/resources/test/tests/iframe-callback.html
@@ -100,16 +100,14 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Example with iframe that notifies containing document via callbacks",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/iframe-consolidate-errors.html b/resources/test/tests/iframe-consolidate-errors.html
index fd20d4f..a44d034 100644
--- a/resources/test/tests/iframe-consolidate-errors.html
+++ b/resources/test/tests/iframe-consolidate-errors.html
@@ -20,23 +20,21 @@
 <!-- apisample4.html is a failing suite due to an unhandled Error. -->
 
 <script>
-	var childContext = document.getElementById("childContext");
-	fetch_tests_from_window(childContext.contentWindow);
+  var childContext = document.getElementById("childContext");
+  fetch_tests_from_window(childContext.contentWindow);
 </script>
 <script type="text/json" id="expected">
 {
   "summarized_status": {
     "status_string": "ERROR",
-    "message": "Error in remote: Error: Example Error",
-    "stack": null
+    "message": "Error in remote: Error: Example Error"
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Test executing in parent context",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/iframe-consolidate-tests.html b/resources/test/tests/iframe-consolidate-tests.html
index 90c5f8e..7b8a897 100644
--- a/resources/test/tests/iframe-consolidate-tests.html
+++ b/resources/test/tests/iframe-consolidate-tests.html
@@ -20,72 +20,63 @@
 <!-- promise-async.html has async tests with promises -->
 
 <script>
-	var childContext = document.getElementById("childContext");
-	fetch_tests_from_window(childContext.contentWindow);
+  var childContext = document.getElementById("childContext");
+  fetch_tests_from_window(childContext.contentWindow);
 </script>
 <script type="text/json" id="expected">
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Promise rejection",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Promise resolution",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "FAIL",
       "name": "Promises and test assertion failures (should fail)",
       "properties": {},
-      "message": "assert_true: This failure is expected expected true got false",
-      "stack": "(implementation-defined)"
+      "message": "assert_true: This failure is expected expected true got false"
     },
     {
       "status_string": "PASS",
       "name": "Promises are supported in your browser",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Promises resolution chaining",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test executing in parent context",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Use of step_func with Promises",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "FAIL",
       "name": "Use of unreached_func with Promises (should fail)",
       "properties": {},
-      "message": "assert_unreached: This failure is expected Reached unreachable code",
-      "stack": "(implementation-defined)"
+      "message": "assert_unreached: This failure is expected Reached unreachable code"
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/iframe-msg.html b/resources/test/tests/iframe-msg.html
index 13a3fff..c6bc97b 100644
--- a/resources/test/tests/iframe-msg.html
+++ b/resources/test/tests/iframe-msg.html
@@ -68,16 +68,14 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Containing document receives messages",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/order.html b/resources/test/tests/order.html
index d20eb6b..1604f43 100644
--- a/resources/test/tests/order.html
+++ b/resources/test/tests/order.html
@@ -16,19 +16,16 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [{
     "status_string": "PASS",
     "name": "first",
-    "stack": null,
     "message": null,
     "properties": {}
   }, {
     "status_string": "PASS",
     "name": "second",
-    "stack": null,
     "message": null,
     "properties": {}
   }],
diff --git a/resources/test/tests/promise-async.html b/resources/test/tests/promise-async.html
index 88b6972..18101ee 100644
--- a/resources/test/tests/promise-async.html
+++ b/resources/test/tests/promise-async.html
@@ -121,58 +121,50 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Promise rejection",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Promise resolution",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "FAIL",
       "name": "Promises and test assertion failures (should fail)",
       "properties": {},
-      "message": "assert_true: This failure is expected expected true got false",
-      "stack": "(implementation-defined)"
+      "message": "assert_true: This failure is expected expected true got false"
     },
     {
       "status_string": "PASS",
       "name": "Promises are supported in your browser",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Promises resolution chaining",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Use of step_func with Promises",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "FAIL",
       "name": "Use of unreached_func with Promises (should fail)",
       "properties": {},
-      "message": "assert_unreached: This failure is expected Reached unreachable code",
-      "stack": "(implementation-defined)"
+      "message": "assert_unreached: This failure is expected Reached unreachable code"
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/promise.html b/resources/test/tests/promise.html
index 61ef15d..4825a38 100644
--- a/resources/test/tests/promise.html
+++ b/resources/test/tests/promise.html
@@ -134,84 +134,72 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "FAIL",
       "name": "Assertion failure in a fulfill reaction (should FAIL with an expected failure)",
-      "stack": "(implementation-defined)",
       "message": "assert_true: Expected failure. expected true got false",
       "properties": {}
     },
     {
       "status_string": "PASS",
       "name": "Chain of promise resolutions",
-      "stack": null,
       "message": null,
       "properties": {}
     },
     {
       "status_string": "PASS",
       "name": "Promise fulfillment with result",
-      "stack": null,
       "message": null,
       "properties": {}
     },
     {
       "status_string": "PASS",
       "name": "Promise rejection with result",
-      "stack": null,
       "message": null,
       "properties": {}
     },
     {
       "status_string": "PASS",
       "name": "Promises are supported in your browser",
-      "stack": null,
       "message": null,
       "properties": {}
     },
     {
       "status_string": "PASS",
       "name": "promise_test with function that doesn't return a Promise",
-      "stack": null,
       "message": null,
       "properties": {}
     },
     {
       "status_string": "FAIL",
       "name": "promise_test with function that doesn't return anything",
-      "stack": "(implementation-defined)",
       "message": "assert_not_equals: got disallowed value undefined",
       "properties": {}
     },
     {
       "status_string": "FAIL",
       "name": "promise_test with unhandled exception in fulfill reaction (should FAIL)",
-      "stack": "(implementation-defined)",
       "message": "promise_test: Unhandled rejection with value: object \"Error: Expected exception.\"",
       "properties": {}
     },
     {
       "status_string": "FAIL",
       "name": "promise_test with unhandled exception in reject reaction (should FAIL)",
-      "stack": "(implementation-defined)",
       "message": "promise_test: Unhandled rejection with value: object \"Error: Expected exception.\"",
       "properties": {}
     },
     {
       "status_string": "FAIL",
       "name": "promise_test with unhandled rejection (should FAIL)",
-      "stack": "(implementation-defined)",
       "message": "promise_test: Unhandled rejection with value: \"Expected rejection\"",
       "properties": {}
     },
     {
       "status_string": "FAIL",
       "name": "unreached_func as reactor (should FAIL with an expected failure)",
-      "stack": "(implementation-defined)",
       "message": "assert_unreached: Expected failure. Reached unreachable code",
       "properties": {}
     }
diff --git a/resources/test/tests/single-page-test-fail.html b/resources/test/tests/single-page-test-fail.html
index a1f8810..a8f247d 100644
--- a/resources/test/tests/single-page-test-fail.html
+++ b/resources/test/tests/single-page-test-fail.html
@@ -12,16 +12,14 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "FAIL",
       "name": "Example with file_is_test (should fail)",
       "properties": {},
-      "message": "uncaught exception: Error: assert_true: expected true got false",
-      "stack": "(implementation-defined)"
+      "message": "uncaught exception: Error: assert_true: expected true got false"
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/single-page-test-no-assertions.html b/resources/test/tests/single-page-test-no-assertions.html
index 3402a46..d1a7532 100644
--- a/resources/test/tests/single-page-test-no-assertions.html
+++ b/resources/test/tests/single-page-test-no-assertions.html
@@ -9,16 +9,14 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Example single page test with no asserts",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/single-page-test-no-body.html b/resources/test/tests/single-page-test-no-body.html
index 065091a..c62ee18 100644
--- a/resources/test/tests/single-page-test-no-body.html
+++ b/resources/test/tests/single-page-test-no-body.html
@@ -10,16 +10,14 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Example single page test with no body",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/single-page-test-pass.html b/resources/test/tests/single-page-test-pass.html
index 31e4e64..91d26b0 100644
--- a/resources/test/tests/single-page-test-pass.html
+++ b/resources/test/tests/single-page-test-pass.html
@@ -12,16 +12,14 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Example with file_is_test",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/uncaught-exception-handle.html b/resources/test/tests/uncaught-exception-handle.html
index 7d8244b..061fb80 100644
--- a/resources/test/tests/uncaught-exception-handle.html
+++ b/resources/test/tests/uncaught-exception-handle.html
@@ -16,16 +16,14 @@
 {
   "summarized_status": {
     "status_string": "ERROR",
-    "message": "Error: Example Error",
-    "stack": "(implementation-defined)"
+    "message": "Error: Example Error"
   },
   "summarized_tests": [
     {
       "status_string": "NOTRUN",
       "name": "This should show a harness status of 'Error' and a test status of 'Not Run'",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/uncaught-exception-ignore.html b/resources/test/tests/uncaught-exception-ignore.html
index 9f45ddb..6624078 100644
--- a/resources/test/tests/uncaught-exception-ignore.html
+++ b/resources/test/tests/uncaught-exception-ignore.html
@@ -18,16 +18,14 @@
 {
   "summarized_status": {
     "status_string": "OK",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "setup({allow_uncaught_exception:true}) should allow tests to pass even if there is an exception",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/worker-dedicated.html b/resources/test/tests/worker-dedicated.html
index 0ca3e4d..98a010d 100644
--- a/resources/test/tests/worker-dedicated.html
+++ b/resources/test/tests/worker-dedicated.html
@@ -30,65 +30,56 @@
 {
   "summarized_status": {
     "status_string": "ERROR",
-    "message": "Error: This failure is expected.",
-    "stack": "(implementation-defined)"
+    "message": "Error: This failure is expected."
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Browser supports Workers",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Test running on main document.",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "FAIL",
       "name": "Untitled",
       "properties": {},
-      "message": "Error: This failure is expected.",
-      "stack": "(implementation-defined)"
+      "message": "Error: This failure is expected."
     },
     {
       "status_string": "PASS",
       "name": "Worker async_test that completes successfully",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Worker test that completes successfully",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "NOTRUN",
       "name": "Worker test that doesn't run ('NOT RUN')",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "FAIL",
       "name": "Worker test that fails ('FAIL')",
       "properties": {},
-      "message": "assert_true: Failing test expected true got false",
-      "stack": "(implementation-defined)"
+      "message": "assert_true: Failing test expected true got false"
     },
     {
       "status_string": "TIMEOUT",
       "name": "Worker test that times out ('TIMEOUT')",
       "properties": {},
-      "message": "Test timed out",
-      "stack": null
+      "message": "Test timed out"
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/worker-service.html b/resources/test/tests/worker-service.html
index 418f6d7..d970827 100644
--- a/resources/test/tests/worker-service.html
+++ b/resources/test/tests/worker-service.html
@@ -63,58 +63,50 @@
 {
   "summarized_status": {
     "status_string": "TIMEOUT",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Browser supports ServiceWorker",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Register ServiceWorker",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Worker async_test that completes successfully",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "PASS",
       "name": "Worker test that completes successfully",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "NOTRUN",
       "name": "Worker test that doesn't run ('NOT RUN')",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "status_string": "FAIL",
       "name": "Worker test that fails ('FAIL')",
       "properties": {},
-      "message": "assert_true: Failing test expected true got false",
-      "stack": "(implementation-defined)"
+      "message": "assert_true: Failing test expected true got false"
     },
     {
       "status_string": "TIMEOUT",
       "name": "Worker test that times out ('TIMEOUT')",
       "properties": {},
-      "message": "Test timed out",
-      "stack": null
+      "message": "Test timed out"
     }
   ],
   "type": "complete"
diff --git a/resources/test/tests/worker-shared.html b/resources/test/tests/worker-shared.html
index a5601de..9c5666b 100644
--- a/resources/test/tests/worker-shared.html
+++ b/resources/test/tests/worker-shared.html
@@ -27,50 +27,43 @@
 {
   "summarized_status": {
     "status_string": "TIMEOUT",
-    "message": null,
-    "stack": null
+    "message": null
   },
   "summarized_tests": [
     {
       "status_string": "PASS",
       "name": "Browser supports SharedWorkers",
       "properties": {},
-      "message": null,
-      "stack": null
+      "message": null
     },
     {
       "message": null,
       "name": "Worker async_test that completes successfully",
       "properties": {},
-      "stack": null,
       "status_string": "PASS"
     },
     {
       "message": null,
       "name": "Worker test that completes successfully",
       "properties": {},
-      "stack": null,
       "status_string": "PASS"
     },
     {
       "message": null,
       "name": "Worker test that doesn't run ('NOT RUN')",
       "properties": {},
-      "stack": null,
       "status_string": "NOTRUN"
     },
     {
       "message": "assert_true: Failing test expected true got false",
       "name": "Worker test that fails ('FAIL')",
       "properties": {},
-      "stack": "(implementation-defined)",
       "status_string": "FAIL"
     },
     {
       "message": "Test timed out",
       "name": "Worker test that times out ('TIMEOUT')",
       "properties": {},
-      "stack": null,
       "status_string": "TIMEOUT"
     }
   ],
diff --git a/resources/test/tox.ini b/resources/test/tox.ini
index 4f45640..d3a30f8 100644
--- a/resources/test/tox.ini
+++ b/resources/test/tox.ini
@@ -1,4 +1,6 @@
 [tox]
+# wptserve etc. are Python2-only.
+envlist = py27
 skipsdist=True
 
 [testenv]
@@ -9,5 +11,6 @@
   pytest>=2.9
   pyvirtualdisplay
   selenium
+  requests
 
 commands = pytest {posargs} -vv tests
diff --git a/resources/test/wptserver.py b/resources/test/wptserver.py
index b06f3e8..79220e8 100644
--- a/resources/test/wptserver.py
+++ b/resources/test/wptserver.py
@@ -1,48 +1,46 @@
-import json
 import os
-import ssl
 import subprocess
+import time
+import sys
 import urllib2
 
-_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
-                            'config.test.json')
-
-with open(_CONFIG_FILE, 'r') as config_handle:
-    config = json.loads(config_handle.read())
-    host = config["host"]
-    port = config["ports"]["https"][0]
 
 class WPTServer(object):
-    base_url = 'https://%s:%s' % (host, port)
-
     def __init__(self, wpt_root):
         self.wpt_root = wpt_root
+        sys.path.insert(0, os.path.join(wpt_root, "tools"))
+        from serve.serve import Config
+        config = Config()
+        self.host = config["browser_host"]
+        self.http_port = config["ports"]["http"][0]
+        self.https_port = config["ports"]["https"][0]
+        self.base_url = 'http://%s:%s' % (self.host, self.http_port)
+        self.https_base_url = 'https://%s:%s' % (self.host, self.https_port)
 
     def start(self):
         self.devnull = open(os.devnull, 'w')
         self.proc = subprocess.Popen(
-            [os.path.join(self.wpt_root, 'wpt'), 'serve', '--config=' + _CONFIG_FILE],
-            stdout=self.devnull,
+            [os.path.join(self.wpt_root, 'wpt'), 'serve'],
             stderr=self.devnull,
             cwd=self.wpt_root)
-        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
-        context.verify_mode = ssl.CERT_NONE
-        context.check_hostname = False
 
-        while True:
+        for retry in range(5):
+            # Exponential backoff.
+            time.sleep(2 ** retry)
             if self.proc.poll() != None:
-                raise Exception('Could not start wptserve.')
-
-            try:
-                urllib2.urlopen(self.base_url, timeout=1, context=context)
                 break
-            except urllib2.URLError as e:
+            try:
+                urllib2.urlopen(self.base_url, timeout=1)
+                return
+            except urllib2.URLError:
                 pass
 
+        raise Exception('Could not start wptserve.')
+
     def stop(self):
-        self.proc.kill()
+        self.proc.terminate()
         self.proc.wait()
         self.devnull.close()
 
     def url(self, abs_path):
-        return self.base_url + '/' + os.path.relpath(abs_path, self.wpt_root)
+        return self.https_base_url + '/' + os.path.relpath(abs_path, self.wpt_root)
diff --git a/resources/testdriver-vendor.js.headers b/resources/testdriver-vendor.js.headers
index 6805c32..5e8f640 100644
--- a/resources/testdriver-vendor.js.headers
+++ b/resources/testdriver-vendor.js.headers
@@ -1 +1,2 @@
 Content-Type: text/javascript; charset=utf-8
+Cache-Control: max-age=3600
diff --git a/resources/testdriver.js b/resources/testdriver.js
index a6aa298..a09a6e8 100644
--- a/resources/testdriver.js
+++ b/resources/testdriver.js
@@ -83,6 +83,42 @@
             return window.test_driver_internal.click(element,
                                                      {x: centerPoint[0],
                                                       y: centerPoint[1]});
+        },
+
+        /**
+         * Send keys to an element
+         *
+         * This matches the behaviour of the {@link
+         * https://w3c.github.io/webdriver/webdriver-spec.html#element-send-keys|WebDriver
+         * Send Keys command}.
+         *
+         * @param {Element} element - element to send keys to
+         * @param {String} keys - keys to send to the element
+         * @returns {Promise} fulfilled after keys are sent, or rejected in
+         *                    the cases the WebDriver command errors
+         */
+        send_keys: function(element, keys) {
+            if (window.top !== window) {
+                return Promise.reject(new Error("can only send keys in top-level window"));
+            }
+
+            if (!window.document.contains(element)) {
+                return Promise.reject(new Error("element in different document or shadow tree"));
+            }
+
+            if (!inView(element)) {
+                element.scrollIntoView({behavior: "instant",
+                                        block: "end",
+                                        inline: "nearest"});
+            }
+
+            var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+            if (pointerInteractablePaintTree.length === 0 ||
+                !element.contains(pointerInteractablePaintTree[0])) {
+                return Promise.reject(new Error("element send_keys intercepted error"));
+            }
+
+            return window.test_driver_internal.send_keys(element, keys);
         }
     };
 
@@ -96,6 +132,17 @@
          */
         click: function(element, coords) {
             return Promise.reject(new Error("unimplemented"));
+        },
+
+        /**
+         * Triggers a user-initated click
+         *
+         * @param {Element} element - element to be clicked
+         * @param {String} keys - keys to send to the element
+         * @returns {Promise} fulfilled after keys are sent or rejected if click fails
+         */
+        send_keys: function(element, keys) {
+            return Promise.reject(new Error("unimplemented"));
         }
     };
 })();
diff --git a/resources/testdriver.js.headers b/resources/testdriver.js.headers
index 6805c32..5e8f640 100644
--- a/resources/testdriver.js.headers
+++ b/resources/testdriver.js.headers
@@ -1 +1,2 @@
 Content-Type: text/javascript; charset=utf-8
+Cache-Control: max-age=3600
diff --git a/resources/testharness.js b/resources/testharness.js
index 2b478e9..a48a0cf 100644
--- a/resources/testharness.js
+++ b/resources/testharness.js
@@ -10,10 +10,10 @@
 [3] http://www.w3.org/2004/10/27-testcases
 */
 
-/* Documentation: http://web-platform-tests.org/writing-tests/testharness-api.html
+/* Documentation: https://web-platform-tests.org/writing-tests/testharness-api.html
  * (../docs/_writing-tests/testharness-api.md) */
 
-(function ()
+(function (global_scope)
 {
     var debug = false;
     // default timeout is 10 seconds, test can override if needed
@@ -48,9 +48,6 @@
      *
      *   // Should return the test harness timeout duration in milliseconds.
      *   float test_timeout();
-     *
-     *   // Should return the global scope object.
-     *   object global_scope();
      * };
      */
 
@@ -248,10 +245,6 @@
         return settings.harness_timeout.normal;
     };
 
-    WindowTestEnvironment.prototype.global_scope = function() {
-        return window;
-    };
-
     /*
      * Base TestEnvironment implementation for a generic web worker.
      *
@@ -344,10 +337,6 @@
         return null;
     };
 
-    WorkerTestEnvironment.prototype.global_scope = function() {
-        return self;
-    };
-
     /*
      * Dedicated web workers.
      * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope
@@ -462,40 +451,88 @@
         }
     };
 
+    /*
+     * JavaScript shells.
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a JavaScript shell.
+     */
+    function ShellTestEnvironment() {
+        this.name_counter = 0;
+        this.all_loaded = false;
+        this.on_loaded_callback = null;
+        Promise.resolve().then(function() {
+            this.all_loaded = true
+            if (this.on_loaded_callback) {
+                this.on_loaded_callback();
+            }
+        }.bind(this));
+        this.message_list = [];
+        this.message_ports = [];
+    }
+
+    ShellTestEnvironment.prototype.next_default_test_name = function() {
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+        this.name_counter++;
+        return "Untitled" + suffix;
+    };
+
+    ShellTestEnvironment.prototype.on_new_harness_properties = function() {};
+
+    ShellTestEnvironment.prototype.on_tests_ready = function() {};
+
+    ShellTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+        if (this.all_loaded) {
+            callback();
+        } else {
+            this.on_loaded_callback = callback;
+        }
+    };
+
+    ShellTestEnvironment.prototype.test_timeout = function() {
+        // Tests running in a shell don't have a default timeout, so behave as
+        // if settings.explicit_timeout is true.
+        return null;
+    };
+
     function create_test_environment() {
-        if ('document' in self) {
+        if ('document' in global_scope) {
             return new WindowTestEnvironment();
         }
-        if ('DedicatedWorkerGlobalScope' in self &&
-            self instanceof DedicatedWorkerGlobalScope) {
+        if ('DedicatedWorkerGlobalScope' in global_scope &&
+            global_scope instanceof DedicatedWorkerGlobalScope) {
             return new DedicatedWorkerTestEnvironment();
         }
-        if ('SharedWorkerGlobalScope' in self &&
-            self instanceof SharedWorkerGlobalScope) {
+        if ('SharedWorkerGlobalScope' in global_scope &&
+            global_scope instanceof SharedWorkerGlobalScope) {
             return new SharedWorkerTestEnvironment();
         }
-        if ('ServiceWorkerGlobalScope' in self &&
-            self instanceof ServiceWorkerGlobalScope) {
+        if ('ServiceWorkerGlobalScope' in global_scope &&
+            global_scope instanceof ServiceWorkerGlobalScope) {
             return new ServiceWorkerTestEnvironment();
         }
-        if ('WorkerGlobalScope' in self &&
-            self instanceof WorkerGlobalScope) {
+        if ('WorkerGlobalScope' in global_scope &&
+            global_scope instanceof WorkerGlobalScope) {
             return new DedicatedWorkerTestEnvironment();
         }
 
+        if (!('self' in global_scope)) {
+            return new ShellTestEnvironment();
+        }
+
         throw new Error("Unsupported test environment");
     }
 
     var test_environment = create_test_environment();
 
     function is_shared_worker(worker) {
-        return 'SharedWorker' in self && worker instanceof SharedWorker;
+        return 'SharedWorker' in global_scope && worker instanceof SharedWorker;
     }
 
     function is_service_worker(worker) {
         // The worker object may be from another execution context,
         // so do not use instanceof here.
-        return 'ServiceWorker' in self &&
+        return 'ServiceWorker' in global_scope &&
             Object.prototype.toString.call(worker) == '[object ServiceWorker]';
     }
 
@@ -1247,6 +1284,13 @@
     }
     expose(assert_readonly, "assert_readonly");
 
+    /**
+     * Assert an Exception with the expected code is thrown.
+     *
+     * @param {object|number|string} code The expected exception code.
+     * @param {Function} func Function which should throw.
+     * @param {string} description Error description for the case that the error is not thrown.
+     */
     function assert_throws(code, func, description)
     {
         try {
@@ -1257,11 +1301,22 @@
             if (e instanceof AssertionError) {
                 throw e;
             }
+
+            assert(typeof e === "object",
+                   "assert_throws", description,
+                   "${func} threw ${e} with type ${type}, not an object",
+                   {func:func, e:e, type:typeof e});
+
+            assert(e !== null,
+                   "assert_throws", description,
+                   "${func} threw null, not an object",
+                   {func:func});
+
             if (code === null) {
                 throw new AssertionError('Test bug: need to pass exception to assert_throws()');
             }
             if (typeof code === "object") {
-                assert(typeof e == "object" && "name" in e && e.name == code.name,
+                assert("name" in e && e.name == code.name,
                        "assert_throws", description,
                        "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
                                     {func:func, actual:e, actual_name:e.name,
@@ -1340,8 +1395,7 @@
             var required_props = { code: name_code_map[name] };
 
             if (required_props.code === 0 ||
-               (typeof e == "object" &&
-                "name" in e &&
+               ("name" in e &&
                 e.name !== e.name.toUpperCase() &&
                 e.name !== "DOMException")) {
                 // New style exception: also test the name property.
@@ -1353,13 +1407,8 @@
             //in.  It might be an instanceof the appropriate interface on some
             //unknown other window.  TODO: Work around this somehow?
 
-            assert(typeof e == "object",
-                   "assert_throws", description,
-                   "${func} threw ${e} with type ${type}, not an object",
-                   {func:func, e:e, type:typeof e});
-
             for (var prop in required_props) {
-                assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
+                assert(prop in e && e[prop] == required_props[prop],
                        "assert_throws", description,
                        "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
                        {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
@@ -1569,11 +1618,6 @@
         this._add_cleanup(callback);
     };
 
-    Test.prototype.force_timeout = function() {
-        this.set_status(this.TIMEOUT);
-        this.phase = this.phases.HAS_RESULT;
-    };
-
     Test.prototype.set_timeout = function()
     {
         if (this.timeout_length !== null) {
@@ -1600,6 +1644,8 @@
         this.done();
     };
 
+    Test.prototype.force_timeout = Test.prototype.timeout;
+
     Test.prototype.done = function()
     {
         if (this.phase == this.phases.COMPLETE) {
@@ -1612,7 +1658,9 @@
 
         this.phase = this.phases.COMPLETE;
 
-        clearTimeout(this.timeout_id);
+        if (global_scope.clearTimeout) {
+            clearTimeout(this.timeout_id);
+        }
         tests.result(this);
         this.cleanup();
     };
@@ -1711,7 +1759,13 @@
         this.tests = new Array();
 
         var this_obj = this;
-        remote.onerror = function(error) { this_obj.remote_error(error); };
+        // If remote context is cross origin assigning to onerror is not
+        // possible, so silently catch those errors.
+        try {
+          remote.onerror = function(error) { this_obj.remote_error(error); };
+        } catch (e) {
+          // Ignore.
+        }
 
         // Keeping a reference to the remote object and the message handler until
         // remote_done() is seen prevents the remote object and its message channel
@@ -1726,6 +1780,12 @@
             }
         };
 
+        if (self.Promise) {
+            this.done = new Promise(function(resolve) {
+                this_obj.doneResolve = resolve;
+            });
+        }
+
         this.message_target.addEventListener("message", this.message_handler);
     }
 
@@ -1775,6 +1835,10 @@
         this.running = false;
         this.remote = null;
         this.message_target = null;
+        if (this.doneResolve) {
+            this.doneResolve();
+        }
+
         if (tests.all_done()) {
             tests.complete();
         }
@@ -1921,12 +1985,14 @@
     };
 
     Tests.prototype.set_timeout = function() {
-        var this_obj = this;
-        clearTimeout(this.timeout_id);
-        if (this.timeout_length !== null) {
-            this.timeout_id = setTimeout(function() {
-                                             this_obj.timeout();
-                                         }, this.timeout_length);
+        if (global_scope.clearTimeout) {
+            var this_obj = this;
+            clearTimeout(this.timeout_id);
+            if (this.timeout_length !== null) {
+                this.timeout_id = setTimeout(function() {
+                                                 this_obj.timeout();
+                                             }, this.timeout_length);
+            }
         }
     };
 
@@ -2131,11 +2197,13 @@
             return;
         }
 
-        this.pending_remotes.push(this.create_remote_worker(worker));
+        var remoteContext = this.create_remote_worker(worker);
+        this.pending_remotes.push(remoteContext);
+        return remoteContext.done;
     };
 
     function fetch_tests_from_worker(port) {
-        tests.fetch_tests_from_worker(port);
+        return tests.fetch_tests_from_worker(port);
     }
     expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
 
@@ -2262,10 +2330,14 @@
             if (output_document.body) {
                 output_document.body.appendChild(node);
             } else {
+                var is_html = false;
                 var is_svg = false;
                 var output_window = output_document.defaultView;
                 if (output_window && "SVGSVGElement" in output_window) {
                     is_svg = output_document.documentElement instanceof output_window.SVGSVGElement;
+                } else if (output_window) {
+                    is_html = (output_document.namespaceURI == "http://www.w3.org/1999/xhtml" &&
+                               output_document.localName == "html");
                 }
                 if (is_svg) {
                     var foreignObject = output_document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
@@ -2273,6 +2345,10 @@
                     foreignObject.setAttribute("height", "100%");
                     output_document.documentElement.appendChild(foreignObject);
                     foreignObject.appendChild(node);
+                } else if (is_html) {
+                    var body = output_document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+                    output_document.documentElement.appendChild(body);
+                    body.appendChild(node);
                 } else {
                     output_document.documentElement.appendChild(node);
                 }
@@ -2801,7 +2877,7 @@
     function expose(object, name)
     {
         var components = name.split(".");
-        var target = test_environment.global_scope();
+        var target = global_scope;
         for (var i = 0; i < components.length - 1; i++) {
             if (!(components[i] in target)) {
                 target[components[i]] = {};
@@ -2823,7 +2899,7 @@
     /** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */
     function get_script_url()
     {
-        if (!('document' in self)) {
+        if (!('document' in global_scope)) {
             return undefined;
         }
 
@@ -2898,38 +2974,40 @@
 
     var tests = new Tests();
 
-    var error_handler = function(e) {
-        if (tests.tests.length === 0 && !tests.allow_uncaught_exception) {
-            tests.set_file_is_test();
-        }
-
-        var stack;
-        if (e.error && e.error.stack) {
-            stack = e.error.stack;
-        } else {
-            stack = e.filename + ":" + e.lineno + ":" + e.colno;
-        }
-
-        if (tests.file_is_test) {
-            var test = tests.tests[0];
-            if (test.phase >= test.phases.HAS_RESULT) {
-                return;
+    if (global_scope.addEventListener) {
+        var error_handler = function(e) {
+            if (tests.tests.length === 0 && !tests.allow_uncaught_exception) {
+                tests.set_file_is_test();
             }
-            test.set_status(test.FAIL, e.message, stack);
-            test.phase = test.phases.HAS_RESULT;
-            test.done();
-        } else if (!tests.allow_uncaught_exception) {
-            tests.status.status = tests.status.ERROR;
-            tests.status.message = e.message;
-            tests.status.stack = stack;
-        }
-        done();
-    };
 
-    addEventListener("error", error_handler, false);
-    addEventListener("unhandledrejection", function(e){ error_handler(e.reason); }, false);
+            var stack;
+            if (e.error && e.error.stack) {
+                stack = e.error.stack;
+            } else {
+                stack = e.filename + ":" + e.lineno + ":" + e.colno;
+            }
+
+            if (tests.file_is_test) {
+                var test = tests.tests[0];
+                if (test.phase >= test.phases.HAS_RESULT) {
+                    return;
+                }
+                test.set_status(test.FAIL, e.message, stack);
+                test.phase = test.phases.HAS_RESULT;
+                test.done();
+            } else if (!tests.allow_uncaught_exception) {
+                tests.status.status = tests.status.ERROR;
+                tests.status.message = e.message;
+                tests.status.stack = stack;
+            }
+            done();
+        };
+
+        addEventListener("error", error_handler, false);
+        addEventListener("unhandledrejection", function(e){ error_handler(e.reason); }, false);
+    }
 
     test_environment.on_tests_ready();
 
-})();
+})(this);
 // vim: set expandtab shiftwidth=4 tabstop=4:
diff --git a/resources/testharnessreport.js b/resources/testharnessreport.js
index 59ad5d2..e5cb40f 100644
--- a/resources/testharnessreport.js
+++ b/resources/testharnessreport.js
@@ -2,19 +2,8 @@
 /* global setup */
 
 /*
- * This file is intended for vendors to implement
- * code needed to integrate testharness.js tests with their own test systems.
- *
- * The default implementation extracts metadata from the tests and validates
- * it against the cached version that should be present in the test source
- * file. If the cache is not found or is out of sync, source code suitable for
- * caching the metadata is optionally generated.
- *
- * The cached metadata is present for extraction by test processing tools that
- * are unable to execute javascript.
- *
- * Metadata is attached to tests via the properties parameter in the test
- * constructor. See testharness.js for details.
+ * This file is intended for vendors to implement code needed to integrate
+ * testharness.js tests with their own test systems.
  *
  * Typically test system integration will attach callbacks when each test has
  * run, using add_result_callback(callback(test)), or when the whole test file
@@ -25,358 +14,6 @@
  * parameters they are called with see testharness.js
  */
 
-var metadata_generator = {
-
-    currentMetadata: {},
-    cachedMetadata: false,
-    metadataProperties: ['help', 'assert', 'author'],
-
-    error: function(message) {
-        var messageElement = document.createElement('p');
-        messageElement.setAttribute('class', 'error');
-        this.appendText(messageElement, message);
-
-        var summary = document.getElementById('summary');
-        if (summary) {
-            summary.parentNode.insertBefore(messageElement, summary);
-        }
-        else {
-            document.body.appendChild(messageElement);
-        }
-    },
-
-    /**
-     * Ensure property value has contact information
-     */
-    validateContact: function(test, propertyName) {
-        var result = true;
-        var value = test.properties[propertyName];
-        var values = Array.isArray(value) ? value : [value];
-        for (var index = 0; index < values.length; index++) {
-            value = values[index];
-            var re = /(\S+)(\s*)<(.*)>(.*)/;
-            if (! re.test(value)) {
-                re = /(\S+)(\s+)(http[s]?:\/\/)(.*)/;
-                if (! re.test(value)) {
-                    this.error('Metadata property "' + propertyName +
-                        '" for test: "' + test.name +
-                        '" must have name and contact information ' +
-                        '("name <email>" or "name http(s)://")');
-                    result = false;
-                }
-            }
-        }
-        return result;
-    },
-
-    /**
-     * Extract metadata from test object
-     */
-    extractFromTest: function(test) {
-        var testMetadata = {};
-        // filter out metadata from other properties in test
-        for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
-             metaIndex++) {
-            var meta = this.metadataProperties[metaIndex];
-            if (test.properties.hasOwnProperty(meta)) {
-                if ('author' == meta) {
-                    this.validateContact(test, meta);
-                }
-                testMetadata[meta] = test.properties[meta];
-            }
-        }
-        return testMetadata;
-    },
-
-    /**
-     * Compare cached metadata to extracted metadata
-     */
-    validateCache: function() {
-        for (var testName in this.currentMetadata) {
-            if (! this.cachedMetadata.hasOwnProperty(testName)) {
-                return false;
-            }
-            var testMetadata = this.currentMetadata[testName];
-            var cachedTestMetadata = this.cachedMetadata[testName];
-            delete this.cachedMetadata[testName];
-
-            for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
-                 metaIndex++) {
-                var meta = this.metadataProperties[metaIndex];
-                if (cachedTestMetadata.hasOwnProperty(meta) &&
-                    testMetadata.hasOwnProperty(meta)) {
-                    if (Array.isArray(cachedTestMetadata[meta])) {
-                      if (! Array.isArray(testMetadata[meta])) {
-                          return false;
-                      }
-                      if (cachedTestMetadata[meta].length ==
-                          testMetadata[meta].length) {
-                          for (var index = 0;
-                               index < cachedTestMetadata[meta].length;
-                               index++) {
-                              if (cachedTestMetadata[meta][index] !=
-                                  testMetadata[meta][index]) {
-                                  return false;
-                              }
-                          }
-                      }
-                      else {
-                          return false;
-                      }
-                    }
-                    else {
-                      if (Array.isArray(testMetadata[meta])) {
-                        return false;
-                      }
-                      if (cachedTestMetadata[meta] != testMetadata[meta]) {
-                        return false;
-                      }
-                    }
-                }
-                else if (cachedTestMetadata.hasOwnProperty(meta) ||
-                         testMetadata.hasOwnProperty(meta)) {
-                    return false;
-                }
-            }
-        }
-        for (var testName in this.cachedMetadata) {
-            return false;
-        }
-        return true;
-    },
-
-    appendText: function(elemement, text) {
-        elemement.appendChild(document.createTextNode(text));
-    },
-
-    jsonifyArray: function(arrayValue, indent) {
-        var output = '[';
-
-        if (1 == arrayValue.length) {
-            output += JSON.stringify(arrayValue[0]);
-        }
-        else {
-            for (var index = 0; index < arrayValue.length; index++) {
-                if (0 < index) {
-                    output += ',\n  ' + indent;
-                }
-                output += JSON.stringify(arrayValue[index]);
-            }
-        }
-        output += ']';
-        return output;
-    },
-
-    jsonifyObject: function(objectValue, indent) {
-        var output = '{';
-        var value;
-
-        var count = 0;
-        for (var property in objectValue) {
-            ++count;
-            if (Array.isArray(objectValue[property]) ||
-                ('object' == typeof(value))) {
-                ++count;
-            }
-        }
-        if (1 == count) {
-            for (var property in objectValue) {
-                output += ' "' + property + '": ' +
-                    JSON.stringify(objectValue[property]) +
-                    ' ';
-            }
-        }
-        else {
-            var first = true;
-            for (var property in objectValue) {
-                if (! first) {
-                    output += ',';
-                }
-                first = false;
-                output += '\n  ' + indent + '"' + property + '": ';
-                value = objectValue[property];
-                if (Array.isArray(value)) {
-                    output += this.jsonifyArray(value, indent +
-                        '                '.substr(0, 5 + property.length));
-                }
-                else if ('object' == typeof(value)) {
-                    output += this.jsonifyObject(value, indent + '  ');
-                }
-                else {
-                    output += JSON.stringify(value);
-                }
-            }
-            if (1 < output.length) {
-                output += '\n' + indent;
-            }
-        }
-        output += '}';
-        return output;
-    },
-
-    /**
-     * Generate javascript source code for captured metadata
-     * Metadata is in pretty-printed JSON format
-     */
-    generateSource: function() {
-        /* "\/" is used instead of a plain forward slash so that the contents
-        of testharnessreport.js can (for convenience) be copy-pasted into a
-        script tag without issue. Otherwise, the HTML parser would think that
-        the script ended in the middle of that string literal. */
-        var source =
-            '<script id="metadata_cache">/*\n' +
-            this.jsonifyObject(this.currentMetadata, '') + '\n' +
-            '*/<\/script>\n';
-        return source;
-    },
-
-    /**
-     * Add element containing metadata source code
-     */
-    addSourceElement: function(event) {
-        var sourceWrapper = document.createElement('div');
-        sourceWrapper.setAttribute('id', 'metadata_source');
-
-        var instructions = document.createElement('p');
-        if (this.cachedMetadata) {
-            this.appendText(instructions,
-                'Replace the existing <script id="metadata_cache"> element ' +
-                'in the test\'s <head> with the following:');
-        }
-        else {
-            this.appendText(instructions,
-                'Copy the following into the <head> element of the test ' +
-                'or the test\'s metadata sidecar file:');
-        }
-        sourceWrapper.appendChild(instructions);
-
-        var sourceElement = document.createElement('pre');
-        this.appendText(sourceElement, this.generateSource());
-
-        sourceWrapper.appendChild(sourceElement);
-
-        var messageElement = document.getElementById('metadata_issue');
-        messageElement.parentNode.insertBefore(sourceWrapper,
-                                               messageElement.nextSibling);
-        messageElement.parentNode.removeChild(messageElement);
-
-        (event.preventDefault) ? event.preventDefault() :
-                                 event.returnValue = false;
-    },
-
-    /**
-     * Extract the metadata cache from the cache element if present
-     */
-    getCachedMetadata: function() {
-        var cacheElement = document.getElementById('metadata_cache');
-
-        if (cacheElement) {
-            var cacheText = cacheElement.firstChild.nodeValue;
-            var openBrace = cacheText.indexOf('{');
-            var closeBrace = cacheText.lastIndexOf('}');
-            if ((-1 < openBrace) && (-1 < closeBrace)) {
-                cacheText = cacheText.slice(openBrace, closeBrace + 1);
-                try {
-                    this.cachedMetadata = JSON.parse(cacheText);
-                }
-                catch (exc) {
-                    this.cachedMetadata = 'Invalid JSON in Cached metadata. ';
-                }
-            }
-            else {
-                this.cachedMetadata = 'Metadata not found in cache element. ';
-            }
-        }
-    },
-
-    /**
-     * Main entry point, extract metadata from tests, compare to cached version
-     * if present.
-     * If cache not present or differs from extrated metadata, generate an error
-     */
-    process: function(tests) {
-        for (var index = 0; index < tests.length; index++) {
-            var test = tests[index];
-            this.currentMetadata[test.name] = this.extractFromTest(test);
-        }
-
-        this.getCachedMetadata();
-
-        var message = null;
-        var messageClass = 'warning';
-        var showSource = false;
-
-        if (0 === tests.length) {
-            if (this.cachedMetadata) {
-                message = 'Cached metadata present but no tests. ';
-            }
-        }
-        else if (1 === tests.length) {
-            if (this.cachedMetadata) {
-                message = 'Single test files should not have cached metadata. ';
-            }
-            else {
-                var testMetadata = this.currentMetadata[tests[0].name];
-                for (var meta in testMetadata) {
-                    if (testMetadata.hasOwnProperty(meta)) {
-                        message = 'Single tests should not have metadata. ' +
-                                  'Move metadata to <head>. ';
-                        break;
-                    }
-                }
-            }
-        }
-        else {
-            if (this.cachedMetadata) {
-                messageClass = 'error';
-                if ('string' == typeof(this.cachedMetadata)) {
-                    message = this.cachedMetadata;
-                    showSource = true;
-                }
-                else if (! this.validateCache()) {
-                    message = 'Cached metadata out of sync. ';
-                    showSource = true;
-                }
-            }
-        }
-
-        if (message) {
-            var messageElement = document.createElement('p');
-            messageElement.setAttribute('id', 'metadata_issue');
-            messageElement.setAttribute('class', messageClass);
-            this.appendText(messageElement, message);
-
-            if (showSource) {
-                var link = document.createElement('a');
-                this.appendText(link, 'Click for source code.');
-                link.setAttribute('href', '#');
-                link.setAttribute('onclick',
-                                  'metadata_generator.addSourceElement(event)');
-                messageElement.appendChild(link);
-            }
-
-            var summary = document.getElementById('summary');
-            if (summary) {
-                summary.parentNode.insertBefore(messageElement, summary);
-            }
-            else {
-                var log = document.getElementById('log');
-                if (log) {
-                    log.appendChild(messageElement);
-                }
-            }
-        }
-    },
-
-    setup: function() {
-        add_completion_callback(
-            function (tests, harness_status) {
-                metadata_generator.process(tests, harness_status);
-                dump_test_results(tests, harness_status);
-            });
-    }
-};
-
 function dump_test_results(tests, status) {
     var results_element = document.createElement("script");
     results_element.type = "text/json";
@@ -400,7 +37,7 @@
     parent.appendChild(results_element);
 }
 
-metadata_generator.setup();
+add_completion_callback(dump_test_results);
 
 /* If the parent window has a testharness_properties object,
  * we use this to provide the test settings. This is used by the
diff --git a/resources/webidl2/.travis.yml b/resources/webidl2/.travis.yml
index fc15089..6dc8e2b 100644
--- a/resources/webidl2/.travis.yml
+++ b/resources/webidl2/.travis.yml
@@ -2,3 +2,4 @@
 node_js:
   - node
   - lts/*
+  - 6
diff --git a/resources/webidl2/CHANGELOG.md b/resources/webidl2/CHANGELOG.md
index e6fa641..1596d71 100644
--- a/resources/webidl2/CHANGELOG.md
+++ b/resources/webidl2/CHANGELOG.md
@@ -1,5 +1,73 @@
 # Change Log
 
+## [v10.2.0](https://github.com/w3c/webidl2.js/tree/v10.2.0) (2018-01-30)
+[Full Changelog](https://github.com/w3c/webidl2.js/compare/v10.1.0...v10.2.0)
+
+**Merged pull requests:**
+
+- Type on union idlType [\#135](https://github.com/w3c/webidl2.js/pull/135) ([saschanaz](https://github.com/saschanaz))
+- feat: add argument/return type [\#134](https://github.com/w3c/webidl2.js/pull/134) ([saschanaz](https://github.com/saschanaz))
+- feat: add dictionary/typedef-type [\#133](https://github.com/w3c/webidl2.js/pull/133) ([saschanaz](https://github.com/saschanaz))
+- feat: add const-type for idlTypes  [\#132](https://github.com/w3c/webidl2.js/pull/132) ([saschanaz](https://github.com/saschanaz))
+- feat: add types on idlTypes [\#131](https://github.com/w3c/webidl2.js/pull/131) ([saschanaz](https://github.com/saschanaz))
+- Auto acquisition for parser result changes [\#130](https://github.com/w3c/webidl2.js/pull/130) ([saschanaz](https://github.com/saschanaz))
+
+## [v10.1.0](https://github.com/w3c/webidl2.js/tree/v10.1.0) (2018-01-19)
+[Full Changelog](https://github.com/w3c/webidl2.js/compare/v10.0.0...v10.1.0)
+
+**Closed issues:**
+
+- Support `raises` and `setraises` [\#128](https://github.com/w3c/webidl2.js/issues/128)
+- Support `legacycaller` [\#127](https://github.com/w3c/webidl2.js/issues/127)
+- Improve "No semicolon after enum" message [\#119](https://github.com/w3c/webidl2.js/issues/119)
+
+**Merged pull requests:**
+
+- Let error messages include the current definition name [\#129](https://github.com/w3c/webidl2.js/pull/129) ([saschanaz](https://github.com/saschanaz))
+
+## [v10.0.0](https://github.com/w3c/webidl2.js/tree/v10.0.0) (2017-12-20)
+[Full Changelog](https://github.com/w3c/webidl2.js/compare/v9.0.0...v10.0.0)
+
+**Closed issues:**
+
+-  Always return an array for idlType, etc. [\#113](https://github.com/w3c/webidl2.js/issues/113)
+- Maintain writer.js or not? [\#109](https://github.com/w3c/webidl2.js/issues/109)
+
+**Merged pull requests:**
+
+- Remove typeExtAttrs from docs [\#124](https://github.com/w3c/webidl2.js/pull/124) ([saschanaz](https://github.com/saschanaz))
+- Remove iterator documentation [\#123](https://github.com/w3c/webidl2.js/pull/123) ([saschanaz](https://github.com/saschanaz))
+- Maintain writer.js [\#122](https://github.com/w3c/webidl2.js/pull/122) ([saschanaz](https://github.com/saschanaz))
+- BREAKING CHANGE: remove deprecated iterator operation [\#121](https://github.com/w3c/webidl2.js/pull/121) ([saschanaz](https://github.com/saschanaz))
+- Use for-of on tests [\#120](https://github.com/w3c/webidl2.js/pull/120) ([saschanaz](https://github.com/saschanaz))
+- docs\(README\): iterables ildType is always array [\#118](https://github.com/w3c/webidl2.js/pull/118) ([marcoscaceres](https://github.com/marcoscaceres))
+
+## [v9.0.0](https://github.com/w3c/webidl2.js/tree/v9.0.0) (2017-11-30)
+[Full Changelog](https://github.com/w3c/webidl2.js/compare/v8.1.0...v9.0.0)
+
+**Closed issues:**
+
+- Code quality [\#116](https://github.com/w3c/webidl2.js/issues/116)
+- Unable to parse HTMLAllCollection interface [\#114](https://github.com/w3c/webidl2.js/issues/114)
+- Add support for mixin syntax [\#112](https://github.com/w3c/webidl2.js/issues/112)
+- Whitespace issues [\#111](https://github.com/w3c/webidl2.js/issues/111)
+
+**Merged pull requests:**
+
+- Consistent array type for iterable.idlType [\#117](https://github.com/w3c/webidl2.js/pull/117) ([saschanaz](https://github.com/saschanaz))
+-  Revert "chore: drop Node 6 support \(\#102\)" [\#115](https://github.com/w3c/webidl2.js/pull/115) ([TimothyGu](https://github.com/TimothyGu))
+
+## [v8.1.0](https://github.com/w3c/webidl2.js/tree/v8.1.0) (2017-11-03)
+[Full Changelog](https://github.com/w3c/webidl2.js/compare/v8.0.1...v8.1.0)
+
+**Closed issues:**
+
+- Extended Attributes `rhs` should always be there [\#96](https://github.com/w3c/webidl2.js/issues/96)
+
+**Merged pull requests:**
+
+- Always add rhs property [\#110](https://github.com/w3c/webidl2.js/pull/110) ([saschanaz](https://github.com/saschanaz))
+
 ## [v8.0.1](https://github.com/w3c/webidl2.js/tree/v8.0.1) (2017-11-03)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v8.0.0...v8.0.1)
 
@@ -9,7 +77,7 @@
 
 **Merged pull requests:**
 
-- Remove m postfix from all\_ws\(\) [\#108](https://github.com/w3c/webidl2.js/pull/108) ([SaschaNaz](https://github.com/SaschaNaz))
+- Remove m postfix from all\_ws\(\) [\#108](https://github.com/w3c/webidl2.js/pull/108) ([saschanaz](https://github.com/saschanaz))
 
 ## [v8.0.0](https://github.com/w3c/webidl2.js/tree/v8.0.0) (2017-11-03)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v7.0.0...v8.0.0)
@@ -21,9 +89,9 @@
 
 **Merged pull requests:**
 
-- Support mixins + includes statements [\#105](https://github.com/w3c/webidl2.js/pull/105) ([SaschaNaz](https://github.com/SaschaNaz))
+- Support mixins + includes statements [\#105](https://github.com/w3c/webidl2.js/pull/105) ([saschanaz](https://github.com/saschanaz))
 - chore: drop Node 6 support [\#102](https://github.com/w3c/webidl2.js/pull/102) ([marcoscaceres](https://github.com/marcoscaceres))
-- BREAKING CHANGE: drop creator support [\#101](https://github.com/w3c/webidl2.js/pull/101) ([SaschaNaz](https://github.com/SaschaNaz))
+- BREAKING CHANGE: drop creator support [\#101](https://github.com/w3c/webidl2.js/pull/101) ([saschanaz](https://github.com/saschanaz))
 - Normalize some whitespace to pass wpt's lint [\#99](https://github.com/w3c/webidl2.js/pull/99) ([foolip](https://github.com/foolip))
 
 ## [v7.0.0](https://github.com/w3c/webidl2.js/tree/v7.0.0) (2017-10-27)
@@ -54,7 +122,7 @@
 
 **Merged pull requests:**
 
-- Use ES2015 syntax for tests [\#88](https://github.com/w3c/webidl2.js/pull/88) ([SaschaNaz](https://github.com/SaschaNaz))
+- Use ES2015 syntax for tests [\#88](https://github.com/w3c/webidl2.js/pull/88) ([saschanaz](https://github.com/saschanaz))
 
 ## [v6.0.0](https://github.com/w3c/webidl2.js/tree/v6.0.0) (2017-10-17)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v5.0.0...v6.0.0)
@@ -73,7 +141,7 @@
 
 **Merged pull requests:**
 
-- BREAKING CHANGE: Use ES2015 syntax [\#84](https://github.com/w3c/webidl2.js/pull/84) ([SaschaNaz](https://github.com/SaschaNaz))
+- BREAKING CHANGE: Use ES2015 syntax [\#84](https://github.com/w3c/webidl2.js/pull/84) ([saschanaz](https://github.com/saschanaz))
 
 ## [v4.2.0](https://github.com/w3c/webidl2.js/tree/v4.2.0) (2017-10-16)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v4.1.0...v4.2.0)
@@ -85,8 +153,8 @@
 
 **Merged pull requests:**
 
-- Check duplicated names [\#80](https://github.com/w3c/webidl2.js/pull/80) ([SaschaNaz](https://github.com/SaschaNaz))
-- Remove legacycaller [\#79](https://github.com/w3c/webidl2.js/pull/79) ([SaschaNaz](https://github.com/SaschaNaz))
+- Check duplicated names [\#80](https://github.com/w3c/webidl2.js/pull/80) ([saschanaz](https://github.com/saschanaz))
+- Remove legacycaller [\#79](https://github.com/w3c/webidl2.js/pull/79) ([saschanaz](https://github.com/saschanaz))
 - Add "sequence" property to IDL Type AST definition [\#76](https://github.com/w3c/webidl2.js/pull/76) ([lerouche](https://github.com/lerouche))
 
 ## [v4.1.0](https://github.com/w3c/webidl2.js/tree/v4.1.0) (2017-07-04)
@@ -98,7 +166,7 @@
 
 **Merged pull requests:**
 
-- Support TypeWithExtendedAttributes on generics [\#75](https://github.com/w3c/webidl2.js/pull/75) ([SaschaNaz](https://github.com/SaschaNaz))
+- Support TypeWithExtendedAttributes on generics [\#75](https://github.com/w3c/webidl2.js/pull/75) ([saschanaz](https://github.com/saschanaz))
 
 ## [v4.0.0](https://github.com/w3c/webidl2.js/tree/v4.0.0) (2017-06-27)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v3.0.2...v4.0.0)
@@ -112,7 +180,7 @@
 **Merged pull requests:**
 
 - BREAKING CHANGE: remove serializers \(closes \#73\) [\#74](https://github.com/w3c/webidl2.js/pull/74) ([marcoscaceres](https://github.com/marcoscaceres))
-- Add documentation for namespaces [\#70](https://github.com/w3c/webidl2.js/pull/70) ([SaschaNaz](https://github.com/SaschaNaz))
+- Add documentation for namespaces [\#70](https://github.com/w3c/webidl2.js/pull/70) ([saschanaz](https://github.com/saschanaz))
 
 ## [v3.0.2](https://github.com/w3c/webidl2.js/tree/v3.0.2) (2017-05-29)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v3.0.1...v3.0.2)
@@ -123,7 +191,7 @@
 
 **Merged pull requests:**
 
-- Test for latest LTS/stable node versions [\#69](https://github.com/w3c/webidl2.js/pull/69) ([SaschaNaz](https://github.com/SaschaNaz))
+- Test for latest LTS/stable node versions [\#69](https://github.com/w3c/webidl2.js/pull/69) ([saschanaz](https://github.com/saschanaz))
 
 ## [v3.0.1](https://github.com/w3c/webidl2.js/tree/v3.0.1) (2017-05-18)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v2.4.0...v3.0.1)
@@ -135,8 +203,8 @@
 
 **Merged pull requests:**
 
-- Fix whitespace error on parsing extended attributes [\#68](https://github.com/w3c/webidl2.js/pull/68) ([SaschaNaz](https://github.com/SaschaNaz))
-- Remove deprecated IDL arrays and exceptions [\#67](https://github.com/w3c/webidl2.js/pull/67) ([SaschaNaz](https://github.com/SaschaNaz))
+- Fix whitespace error on parsing extended attributes [\#68](https://github.com/w3c/webidl2.js/pull/68) ([saschanaz](https://github.com/saschanaz))
+- Remove deprecated IDL arrays and exceptions [\#67](https://github.com/w3c/webidl2.js/pull/67) ([saschanaz](https://github.com/saschanaz))
 
 ## [v2.4.0](https://github.com/w3c/webidl2.js/tree/v2.4.0) (2017-04-12)
 [Full Changelog](https://github.com/w3c/webidl2.js/compare/v2.1.0...v2.4.0)
@@ -157,7 +225,7 @@
 - Update webidl2.js [\#63](https://github.com/w3c/webidl2.js/pull/63) ([tqeto](https://github.com/tqeto))
 - Remove support for MapClass \(no longer valid in WebIDL\) [\#62](https://github.com/w3c/webidl2.js/pull/62) ([dontcallmedom](https://github.com/dontcallmedom))
 - Add support for annotated types [\#61](https://github.com/w3c/webidl2.js/pull/61) ([dontcallmedom](https://github.com/dontcallmedom))
-- Support namespaces [\#58](https://github.com/w3c/webidl2.js/pull/58) ([SaschaNaz](https://github.com/SaschaNaz))
+- Support namespaces [\#58](https://github.com/w3c/webidl2.js/pull/58) ([saschanaz](https://github.com/saschanaz))
 - Add support for records [\#57](https://github.com/w3c/webidl2.js/pull/57) ([TimothyGu](https://github.com/TimothyGu))
 - Refactor [\#50](https://github.com/w3c/webidl2.js/pull/50) ([marcoscaceres](https://github.com/marcoscaceres))
 - feat\(lib\): add AMD export support \(closes \#48\) [\#49](https://github.com/w3c/webidl2.js/pull/49) ([marcoscaceres](https://github.com/marcoscaceres))
diff --git a/resources/webidl2/README.md b/resources/webidl2/README.md
index 93cc78b..8791360 100644
--- a/resources/webidl2/README.md
+++ b/resources/webidl2/README.md
@@ -310,7 +310,6 @@
 ```JS
 {
   "type": "typedef",
-  "typeExtAttrs": [],
   "idlType": {
     "sequence": true,
     "generic": "sequence",
@@ -336,8 +335,6 @@
 * `name`: The typedef's name.
 * `idlType`: An [IDL Type](#idl-type) describing what typedef's type.
 * `extAttrs`: A list of [extended attributes](#extended-attributes).
-* `typeExtAttrs`: A list of [extended attributes](#extended-attributes) that apply to the
-type rather than to the typedef as a whole.
 
 ### Implements
 
@@ -489,34 +486,6 @@
 * `value`: The constant value as described by [Const Values](#default-and-const-values)
 * `extAttrs`: A list of [extended attributes](#extended-attributes).
 
-### Iterator Member
-
-Iterator members look like this
-
-```JS
-{
-  "type": "iterator",
-  "getter": false,
-  "setter": false,
-  "deleter": false,
-  "static": false,
-  "stringifier": false,
-  "idlType": {
-    "sequence": false,
-    "generic": null,
-    "nullable": false,
-    "union": false,
-    "idlType": "Session2"
-  },
-  "iteratorObject": "SessionIterator",
-  "extAttrs": []
-}
-```
-
-* `type`: Always "iterator".
-* `iteratorObject`: The string on the right-hand side; absent if there isn't one.
-* the rest: same as on [operations](#operation-member).
-
 ### Arguments
 
 The arguments (e.g. for an operation) look like this:
@@ -611,7 +580,7 @@
 The fields are as follows:
 
 * `type`: Always one of "iterable", "legacyiterable", "maplike" or "setlike".
-* `idlType`: An [IDL Type](#idl-type) (or an array of two types) representing the declared type arguments.
+* `idlType`: An array with one or more [IDL Types](#idl-type) representing the declared type arguments.
 * `readonly`: Whether the maplike or setlike is declared as read only.
 * `extAttrs`: A list of [extended attributes](#extended-attributes).
 
diff --git a/resources/webidl2/lib/webidl2.js b/resources/webidl2/lib/webidl2.js
index 2f116c4..a7a61d9 100644
--- a/resources/webidl2/lib/webidl2.js
+++ b/resources/webidl2/lib/webidl2.js
@@ -1,32 +1,82 @@
 "use strict";
 
 (() => {
+  // These regular expressions use the sticky flag so they will only match at
+  // the current location (ie. the offset of lastIndex).
+  const tokenRe = {
+    // This expression uses a lookahead assertion to catch false matches
+    // against integers early.
+    "float": /-?(?=[0-9]*\.|[0-9]+[eE])(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/y,
+    "integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y,
+    "identifier": /[A-Z_a-z][0-9A-Z_a-z-]*/y,
+    "string": /"[^"]*"/y,
+    "whitespace": /[\t\n\r ]+/y,
+    "comment": /((\/(\/.*|\*([^*]|\*[^\/])*\*\/)[\t\n\r ]*)+)/y,
+    "other": /[^\t\n\r 0-9A-Z_a-z]/y
+  };
+
+  function attemptTokenMatch(str, type, re, lastIndex, tokens) {
+    re.lastIndex = lastIndex;
+    const result = re.exec(str);
+    if (result) {
+      tokens.push({ type, value: result[0] });
+      return re.lastIndex;
+    }
+    return -1;
+  }
+
   function tokenise(str) {
     const tokens = [];
-    const re = {
-      "float": /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/,
-      "integer": /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/,
-      "identifier": /^[A-Z_a-z][0-9A-Z_a-z-]*/,
-      "string": /^"[^"]*"/,
-      "whitespace": /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/,
-      "other": /^[^\t\n\r 0-9A-Z_a-z]/
-    };
-    const types = ["float", "integer", "identifier", "string", "whitespace", "other"];
-    while (str.length > 0) {
-      let matched = false;
-      for (const type of types) {
-        str = str.replace(re[type], tok => {
-          tokens.push({ type, value: tok });
-          matched = true;
-          return "";
-        });
-        if (matched) break;
+    let lastIndex = 0;
+    while (lastIndex < str.length) {
+      const nextChar = str.charAt(lastIndex);
+      let result = -1;
+      if (/[-0-9.]/.test(nextChar)) {
+        result = attemptTokenMatch(str, "float", tokenRe.float, lastIndex,
+                                   tokens);
+        if (result === -1) {
+          result = attemptTokenMatch(str, "integer", tokenRe.integer, lastIndex,
+                                     tokens);
+        }
+        if (result === -1) {
+          // '-' and '.' can also match "other".
+          result = attemptTokenMatch(str, "other", tokenRe.other,
+                                     lastIndex, tokens);
+        }
+      } else if (/[A-Z_a-z]/.test(nextChar)) {
+        result = attemptTokenMatch(str, "identifier", tokenRe.identifier,
+                                   lastIndex, tokens);
+      } else if (nextChar === '"') {
+        result = attemptTokenMatch(str, "string", tokenRe.string,
+                                   lastIndex, tokens);
+        if (result === -1) {
+          // '"' can also match "other".
+          result = attemptTokenMatch(str, "other", tokenRe.other,
+                                     lastIndex, tokens);
+        }
+      } else if (/[\t\n\r ]/.test(nextChar)) {
+        result = attemptTokenMatch(str, "whitespace", tokenRe.whitespace,
+                                   lastIndex, tokens);
+      } else if (nextChar === '/') {
+        // The parser expects comments to be labelled as "whitespace".
+        result = attemptTokenMatch(str, "whitespace", tokenRe.comment,
+                                   lastIndex, tokens);
+        if (result === -1) {
+          // '/' can also match "other".
+          result = attemptTokenMatch(str, "other", tokenRe.other,
+                                     lastIndex, tokens);
+        }
+      } else {
+        result = attemptTokenMatch(str, "other", tokenRe.other,
+                                   lastIndex, tokens);
       }
-      if (matched) continue;
-      throw new Error("Token stream not progressing");
+      if (result === -1) {
+        throw new Error("Token stream not progressing");
+      }
+      lastIndex = result;
     }
     return tokens;
-  };
+  }
 
   class WebIDLParseError {
     constructor(str, line, input, tokens) {
@@ -45,6 +95,7 @@
     let line = 1;
     tokens = tokens.slice();
     const names = new Map();
+    let current = null;
 
     const FLOAT = "float";
     const INT = "integer";
@@ -57,7 +108,7 @@
       getter: false,
       setter: false,
       deleter: false,
-      "static": false,
+      static: false,
       stringifier: false
     });
 
@@ -69,8 +120,18 @@
         tok += tokens[numTokens].value;
         numTokens++;
       }
-      throw new WebIDLParseError(str, line, tok, tokens.slice(0, maxTokens));
-    };
+      
+      let message;
+      if (current) {
+        message = `Got an error during or right after parsing \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`: ${str}`
+      }
+      else {
+        // throwing before any valid definition
+        message = `Got an error before parsing any named definition: ${str}`;
+      }
+
+      throw new WebIDLParseError(message, line, tok, tokens.slice(0, maxTokens));
+    }
 
     function sanitize_name(name, type) {
       if (names.has(name)) {
@@ -86,23 +147,34 @@
       if (!tokens.length || tokens[0].type !== type) return;
       if (typeof value === "undefined" || tokens[0].value === value) {
         last_token = tokens.shift();
-        if (type === ID) last_token.value = last_token.value.replace(/^_/, "");
+        if (type === ID && last_token.value.startsWith('_'))
+          last_token.value = last_token.value.substring(1);
         return last_token;
       }
-    };
+    }
+
+    function count(str, char) {
+      let total = 0;
+      for (let i = str.indexOf(char); i !== -1; i = str.indexOf(char, i + 1)) {
+        ++total;
+      }
+      return total;
+    }
 
     function ws() {
       if (!tokens.length) return;
       if (tokens[0].type === "whitespace") {
         const t = tokens.shift();
-        t.value.replace(/\n/g, m => {
-          line++;
-          return m;
-        });
+        line += count(t.value, '\n');
         return t;
       }
-    };
+    }
 
+    const all_ws_re = {
+      "ws": /([\t\n\r ]+)/y,
+      "line-comment": /\/\/(.*)\r?\n?/y,
+      "multiline-comment": /\/\*((?:[^*]|\*[^/])*)\*\//y
+    };
     function all_ws(store, pea) { // pea == post extended attribute, tpea = same for types
       const t = { type: "whitespace", value: "" };
       while (true) {
@@ -113,30 +185,30 @@
       if (t.value.length > 0) {
         if (store) {
           let w = t.value;
-          const re = {
-            "ws": /^([\t\n\r ]+)/,
-            "line-comment": /^\/\/(.*)\r?\n?/,
-            "multiline-comment": /^\/\*((?:.|\n|\r)*?)\*\//
-          };
-          const wsTypes = [];
-          for (const k in re) wsTypes.push(k);
-          while (w.length) {
+          let lastIndex = 0;
+          while (lastIndex < w.length) {
             let matched = false;
-            for (const type of wsTypes) {
-              w = w.replace(re[type], (tok, m1) => {
-                store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 });
+            // Servo doesn't support using "const" in this construction yet.
+            // See https://github.com/servo/servo/issues/20231.
+            // |type| can be made const once Servo supports it.
+            for (let type in all_ws_re) {
+              const re = all_ws_re[type];
+              re.lastIndex = lastIndex;
+              const result = re.exec(w);
+              if (result) {
+                store.push({ type: type + (pea ? ("-" + pea) : ""), value: result[1] });
                 matched = true;
-                return "";
-              });
-              if (matched) break;
+                lastIndex = re.lastIndex;
+                break;
+              }
             }
-            if (matched) continue;
-            throw new Error("Surprising white space construct."); // this shouldn't happen
+            if (!matched)
+              throw new Error("Surprising white space construct."); // this shouldn't happen
           }
         }
         return t;
       }
-    };
+    }
 
     function integer_type() {
       let ret = "";
@@ -151,7 +223,7 @@
         return ret;
       }
       if (ret) error("Failed to parse integer type");
-    };
+    }
 
     function float_type() {
       let ret = "";
@@ -161,7 +233,7 @@
       if (consume(ID, "float")) return ret + "float";
       if (consume(ID, "double")) return ret + "double";
       if (ret) error("Failed to parse float type");
-    };
+    }
 
     function primitive_type() {
       const num_type = integer_type() || float_type();
@@ -170,7 +242,7 @@
       if (consume(ID, "boolean")) return "boolean";
       if (consume(ID, "byte")) return "byte";
       if (consume(ID, "octet")) return "octet";
-    };
+    }
 
     function const_value() {
       if (consume(ID, "true")) return { type: "boolean", value: true };
@@ -185,7 +257,7 @@
         if (consume(ID, "Infinity")) return { type: "Infinity", negative: true };
         else tokens.unshift(tok);
       }
-    };
+    }
 
     function type_suffix(obj) {
       while (true) {
@@ -195,11 +267,11 @@
           obj.nullable = true;
         } else return;
       }
-    };
+    }
 
-    function single_type() {
+    function single_type(typeName) {
       const prim = primitive_type();
-      const ret = { sequence: false, generic: null, nullable: false, union: false };
+      const ret = { type: typeName || null, sequence: false, generic: null, nullable: false, union: false };
       let name;
       let value;
       if (prim) {
@@ -217,7 +289,7 @@
           const types = [];
           do {
             all_ws();
-            types.push(type_with_extended_attributes() || error("Error parsing generic type " + value));
+            types.push(type_with_extended_attributes(typeName) || error("Error parsing generic type " + value));
             all_ws();
           }
           while (consume(OTHER, ","));
@@ -246,12 +318,12 @@
       type_suffix(ret);
       if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable");
       return ret;
-    };
+    }
 
-    function union_type() {
+    function union_type(typeName) {
       all_ws();
       if (!consume(OTHER, "(")) return;
-      const ret = { sequence: false, generic: null, nullable: false, union: true, idlType: [] };
+      const ret = { type: typeName || null, sequence: false, generic: null, nullable: false, union: true, idlType: [] };
       const fst = type_with_extended_attributes() || error("Union type with no content");
       ret.idlType.push(fst);
       while (true) {
@@ -263,18 +335,18 @@
       if (!consume(OTHER, ")")) error("Unterminated union type");
       type_suffix(ret);
       return ret;
-    };
+    }
 
-    function type() {
-      return single_type() || union_type();
-    };
+    function type(typeName) {
+      return single_type(typeName) || union_type(typeName);
+    }
 
-    function type_with_extended_attributes() {
+    function type_with_extended_attributes(typeName) {
       const extAttrs = extended_attrs();
-      const ret = single_type() || union_type();
+      const ret = single_type(typeName) || union_type(typeName);
       if (extAttrs.length && ret) ret.extAttrs = extAttrs;
       return ret;
-    };
+    }
 
     function argument(store) {
       const ret = { optional: false, variadic: false };
@@ -285,7 +357,7 @@
         ret.optional = true;
         all_ws();
       }
-      ret.idlType = type_with_extended_attributes();
+      ret.idlType = type_with_extended_attributes("argument-type");
       if (!ret.idlType) {
         if (opt_token) tokens.unshift(opt_token);
         return;
@@ -320,7 +392,7 @@
         }
       }
       return ret;
-    };
+    }
 
     function argument_list(store) {
       const ret = [];
@@ -333,7 +405,7 @@
         const nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
         ret.push(nxt);
       }
-    };
+    }
 
     function simple_extended_attr(store) {
       all_ws();
@@ -384,7 +456,7 @@
         consume(OTHER, ")") || error("Unexpected token in extended attribute argument list");
       }
       return ret;
-    };
+    }
 
     // Note: we parse something simpler than the official syntax. It's all that ever
     // seems to be used
@@ -404,7 +476,7 @@
       all_ws();
       consume(OTHER, "]") || error("No end of extended attribute");
       return eas;
-    };
+    }
 
     function default_() {
       all_ws();
@@ -418,11 +490,11 @@
           return { type: "sequence", value: [] };
         } else {
           const str = consume(STR) || error("No value for default");
-          str.value = str.value.replace(/^"/, "").replace(/"$/, "");
+          str.value = str.value.slice(1, -1);
           return str;
         }
       }
-    };
+    }
 
     function const_(store) {
       all_ws(store, "pea");
@@ -434,7 +506,7 @@
         typ = consume(ID) || error("No type for const");
         typ = typ.value;
       }
-      ret.idlType = typ;
+      ret.idlType = { type: "const-type", idlType: typ };
       all_ws();
       if (consume(OTHER, "?")) {
         ret.nullable = true;
@@ -451,7 +523,7 @@
       all_ws();
       consume(OTHER, ";") || error("Unterminated const");
       return ret;
-    };
+    }
 
     function inheritance() {
       all_ws();
@@ -460,7 +532,7 @@
         const inh = consume(ID) || error("No type in inheritance");
         return inh.value;
       }
-    };
+    }
 
     function operation_rest(ret, store) {
       all_ws();
@@ -475,7 +547,7 @@
       all_ws();
       consume(OTHER, ";") || error("Unterminated operation");
       return ret;
-    };
+    }
 
     function callback(store) {
       all_ws(store, "pea");
@@ -484,12 +556,11 @@
       all_ws();
       const tok = consume(ID, "interface");
       if (tok) {
-        ret = interface_rest();
-        ret.type = "callback interface";
+        ret = interface_rest(false, store, "callback interface");
         return ret;
       }
       const name = consume(ID) || error("No name for callback");
-      ret = { type: "callback", name: sanitize_name(name.value, "callback") };
+      ret = current = { type: "callback", name: sanitize_name(name.value, "callback") };
       all_ws();
       consume(OTHER, "=") || error("No assignment in callback");
       all_ws();
@@ -502,14 +573,14 @@
       all_ws();
       consume(OTHER, ";") || error("Unterminated callback");
       return ret;
-    };
+    }
 
     function attribute(store) {
       all_ws(store, "pea");
       const grabbed = [];
       const ret = {
         type: "attribute",
-        "static": false,
+        static: false,
         stringifier: false,
         inherit: false,
         readonly: false
@@ -517,7 +588,7 @@
       const w = all_ws();
       if (w) grabbed.push(w);
       if (consume(ID, "inherit")) {
-        if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit");
+        if (ret.static || ret.stringifier) error("Cannot have a static or stringifier inherit");
         ret.inherit = true;
         grabbed.push(last_token);
         const w = all_ws();
@@ -534,14 +605,14 @@
         tokens = grabbed.concat(tokens);
       }
       return rest;
-    };
+    }
 
     function attribute_rest(ret) {
       if (!consume(ID, "attribute")) {
         return;
       }
       all_ws();
-      ret.idlType = type_with_extended_attributes() || error("No type in attribute");
+      ret.idlType = type_with_extended_attributes("attribute-type") || error("No type in attribute");
       if (ret.idlType.sequence) error("Attributes cannot accept sequence types");
       if (ret.idlType.generic === "record") error("Attributes cannot accept record types");
       all_ws();
@@ -550,17 +621,17 @@
       all_ws();
       consume(OTHER, ";") || error("Unterminated attribute");
       return ret;
-    };
+    }
 
     function return_type() {
-      const typ = type();
+      const typ = type("return-type");
       if (!typ) {
         if (consume(ID, "void")) {
           return "void";
         } else error("No return type");
       }
       return typ;
-    };
+    }
 
     function operation(store) {
       all_ws(store, "pea");
@@ -580,24 +651,9 @@
       }
       ret.idlType = return_type();
       all_ws();
-      if (consume(ID, "iterator")) {
-        all_ws();
-        ret.type = "iterator";
-        if (consume(ID, "object")) {
-          ret.iteratorObject = "object";
-        } else if (consume(OTHER, "=")) {
-          all_ws();
-          var name = consume(ID) || error("No right hand side in iterator");
-          ret.iteratorObject = name.value;
-        }
-        all_ws();
-        consume(OTHER, ";") || error("Unterminated iterator");
-        return ret;
-      } else {
-        operation_rest(ret, store);
-        return ret;
-      }
-    };
+      operation_rest(ret, store);
+      return ret;
+    }
 
     function static_member(store) {
       all_ws(store, "pea");
@@ -629,7 +685,7 @@
           arr.push(name.value);
         } else break;
       }
-    };
+    }
 
     function iterable_type() {
       if (consume(ID, "iterable")) return "iterable";
@@ -637,13 +693,13 @@
       else if (consume(ID, "maplike")) return "maplike";
       else if (consume(ID, "setlike")) return "setlike";
       else return;
-    };
+    }
 
     function readonly_iterable_type() {
       if (consume(ID, "maplike")) return "maplike";
       else if (consume(ID, "setlike")) return "setlike";
       else return;
-    };
+    }
 
     function iterable(store) {
       all_ws(store, "pea");
@@ -670,17 +726,14 @@
         delete ret.readonly;
       all_ws();
       if (consume(OTHER, "<")) {
-        ret.idlType = type_with_extended_attributes() || error(`Error parsing ${ittype} declaration`);
+        ret.idlType = [type_with_extended_attributes()] || error(`Error parsing ${ittype} declaration`);
         all_ws();
         if (secondTypeAllowed) {
-          let type2 = null;
           if (consume(OTHER, ",")) {
             all_ws();
-            type2 = type_with_extended_attributes();
+            ret.idlType.push(type_with_extended_attributes());
             all_ws();
           }
-          if (type2)
-            ret.idlType = [ret.idlType, type2];
           else if (secondTypeRequired)
             error(`Missing second type argument in ${ittype} declaration`);
         }
@@ -691,16 +744,16 @@
         error(`Error parsing ${ittype} declaration`);
 
       return ret;
-    };
+    }
 
-    function interface_rest(isPartial, store) {
+    function interface_rest(isPartial, store, typeName = "interface") {
       all_ws();
       const name = consume(ID) || error("No name for interface");
       const mems = [];
-      const ret = {
-        type: "interface",
+      const ret = current = {
+        type: typeName,
         name: isPartial ? name.value : sanitize_name(name.value, "interface"),
-        partial: false,
+        partial: isPartial,
         members: mems
       };
       if (!isPartial) ret.inheritance = inheritance() || null;
@@ -731,7 +784,7 @@
         mem.extAttrs = ea;
         ret.members.push(mem);
       }
-    };
+    }
 
     function mixin_rest(isPartial, store) {
       all_ws();
@@ -739,10 +792,10 @@
       all_ws();
       const name = consume(ID) || error("No name for interface mixin");
       const mems = [];
-      const ret = {
+      const ret = current = {
         type: "interface mixin",
         name: isPartial ? name.value : sanitize_name(name.value, "interface mixin"),
-        partial: false,
+        partial: isPartial,
         members: mems
       };
       all_ws();
@@ -785,7 +838,7 @@
       all_ws();
       const name = consume(ID) || error("No name for namespace");
       const mems = [];
-      const ret = {
+      const ret = current = {
         type: "namespace",
         name: isPartial ? name.value : sanitize_name(name.value, "namespace"),
         partial: isPartial,
@@ -815,7 +868,7 @@
       const grabbed = [];
       const ret = {
         type: "attribute",
-        "static": false,
+        static: false,
         stringifier: false,
         inherit: false,
         readonly: false
@@ -854,9 +907,8 @@
         interface_(true, store) ||
         namespace(true, store) ||
         error("Partial doesn't apply to anything");
-      thing.partial = true;
       return thing;
-    };
+    }
 
     function dictionary(isPartial, store) {
       all_ws(isPartial ? null : store, "pea");
@@ -864,10 +916,10 @@
       all_ws();
       const name = consume(ID) || error("No name for dictionary");
       const mems = [];
-      const ret = {
+      const ret = current = {
         type: "dictionary",
         name: isPartial ? name.value : sanitize_name(name.value, "dictionary"),
-        partial: false,
+        partial: isPartial,
         members: mems
       };
       if (!isPartial) ret.inheritance = inheritance() || null;
@@ -883,7 +935,7 @@
         const ea = extended_attrs(store ? mems : null);
         all_ws(store ? mems : null, "pea");
         const required = consume(ID, "required");
-        const typ = type_with_extended_attributes() || error("No type for dictionary member");
+        const typ = type_with_extended_attributes("dictionary-type") || error("No type for dictionary member");
         all_ws();
         const name = consume(ID) || error("No name for dictionary member");
         const dflt = default_();
@@ -902,7 +954,7 @@
         all_ws();
         consume(OTHER, ";") || error("Unterminated dictionary member");
       }
-    };
+    }
 
     function enum_(store) {
       all_ws(store, "pea");
@@ -910,7 +962,7 @@
       all_ws();
       const name = consume(ID) || error("No name for enum");
       const vals = [];
-      const ret = {
+      const ret = current = {
         type: "enum",
         name: sanitize_name(name.value, "enum"),
         values: vals
@@ -926,7 +978,7 @@
           return ret;
         }
         const val = consume(STR) || error("Unexpected value in enum");
-        val.value = val.value.replace(/"/g, "");
+        val.value = val.value.slice(1, -1);
         ret.values.push(val);
         all_ws(store ? vals : null);
         if (consume(OTHER, ",")) {
@@ -937,7 +989,7 @@
           saw_comma = false;
         }
       }
-    };
+    }
 
     function typedef(store) {
       all_ws(store, "pea");
@@ -946,14 +998,15 @@
         type: "typedef"
       };
       all_ws();
-      ret.idlType = type_with_extended_attributes() || error("No type in typedef");
+      ret.idlType = type_with_extended_attributes("typedef-type") || error("No type in typedef");
       all_ws();
       const name = consume(ID) || error("No name in typedef");
       ret.name = sanitize_name(name.value, "typedef");
+      current = ret;
       all_ws();
       consume(OTHER, ";") || error("Unterminated typedef");
       return ret;
-    };
+    }
 
     function implements_(store) {
       all_ws(store, "pea");
@@ -976,7 +1029,7 @@
         tokens.unshift(w);
         tokens.unshift(target);
       }
-    };
+    }
 
     function includes(store) {
       all_ws(store, "pea");
@@ -999,7 +1052,7 @@
         tokens.unshift(w);
         tokens.unshift(target);
       }
-    };
+    }
 
     function definition(store) {
       return callback(store) ||
@@ -1011,7 +1064,7 @@
         implements_(store) ||
         includes(store) ||
         namespace(false, store);
-    };
+    }
 
     function definitions(store) {
       if (!tokens.length) return [];
@@ -1027,11 +1080,11 @@
         defs.push(def);
       }
       return defs;
-    };
+    }
     const res = definitions(opt.ws);
     if (tokens.length) error("Unrecognised tokens");
     return res;
-  };
+  }
 
   const obj = {
     parse(str, opt) {
diff --git a/resources/webidl2/lib/writer.js b/resources/webidl2/lib/writer.js
index 5e30e70..c00c0dd 100644
--- a/resources/webidl2/lib/writer.js
+++ b/resources/webidl2/lib/writer.js
@@ -1,78 +1,71 @@
-(function() {
+"use strict";
 
-  var write = function(ast, opt) {
-    var curPea = "",
-      curTPea = "",
-      opt = opt || {},
-      noop = function(str) {
-        return str; },
-      optNames = "type".split(" "),
-      context = [];
-    for (var i = 0, n = optNames.length; i < n; i++) {
-      var o = optNames[i];
+(() => {
+  function write(ast, opt = {}) {
+    let curPea = "";
+    let curTPea = "";
+    const noop = str => str;
+    const optNames = "type".split(" ");
+    const context = [];
+    for (const o of optNames) {
       if (!opt[o]) opt[o] = noop;
     }
 
-    var literal = function(it) {
+    function literal(it) {
       return it.value;
     };
-    var wsPea = function(it) {
+    function wsPea(it) {
       curPea += it.value;
       return "";
     };
-    var wsTPea = function(it) {
+    function wsTPea(it) {
       curTPea += it.value;
       return "";
     };
-    var lineComment = function(it) {
-      return "//" + it.value + "\n";
+    function lineComment(it) {
+      return `//${it.value}\n`;
     };
-    var multilineComment = function(it) {
-      return "/*" + it.value + "*/";
+    function multilineComment(it) {
+      return `/*${it.value}*/`;
     };
-    var type = function(it) {
+    function type(it) {
       if (typeof it === "string") return opt.type(it); // XXX should maintain some context
-      if (it.union) return "(" + it.idlType.map(type).join(" or ") + ")";
-      var ret = "";
-      if (it.generic) ret += it.generic + "<";
-      else if (it.sequence) ret += "sequence<";
-      if (Array.isArray(it.idlType)) ret += it.idlType.map(type).join(", ");
-      else ret += type(it.idlType);
-      if (it.array || it.generic === 'Array') {
-        for (var i = 0, n = it.nullableArray.length; i < n; i++) {
-          var val = it.nullableArray[i];
-          if (val) ret += "?";
-          ret += "[]";
-        }
+      let ret = extended_attributes(it.extAttrs, curPea);
+      if (it.union) ret += `(${it.idlType.map(type).join(" or ")})`;
+      else {
+        if (it.generic) ret += `${it.generic}<`;
+        if (Array.isArray(it.idlType)) ret += it.idlType.map(type).join(", ");
+        else ret += type(it.idlType);
+        if (it.generic) ret += ">";
       }
-      if (it.generic || it.sequence) ret += ">";
       if (it.nullable) ret += "?";
 
       return ret;
     };
-    var const_value = function(it) {
-      var tp = it.type;
+    function const_value(it) {
+      const tp = it.type;
       if (tp === "boolean") return it.value ? "true" : "false";
       else if (tp === "null") return "null";
       else if (tp === "Infinity") return (it.negative ? "-" : "") + "Infinity";
       else if (tp === "NaN") return "NaN";
       else if (tp === "number") return it.value;
-      else return '"' + it.value + '"';
+      else if (tp === "sequence") return "[]";
+      else return `"${it.value}"`;
     };
-    var argument = function(arg, pea) {
-      var ret = extended_attributes(arg.extAttrs, pea);
+    function argument(arg, pea) {
+      let ret = extended_attributes(arg.extAttrs, pea);
       if (arg.optional) ret += "optional ";
       ret += type(arg.idlType);
       if (arg.variadic) ret += "...";
-      ret += " " + arg.name;
-      if (arg["default"]) ret += " = " + const_value(arg["default"]);
+      ret += ` ${arg.name}`;
+      if (arg["default"]) ret += ` = ${const_value(arg["default"])}`;
       return ret;
     };
-    var args = function(its) {
-      var res = "",
-        pea = "";
-      for (var i = 0, n = its.length; i < n; i++) {
-        var arg = its[i];
+    function args(its) {
+      let res = "";
+      let pea = "";
+      for (let i = 0, n = its.length; i < n; i++) {
+        const arg = its[i];
         if (arg.type === "ws") res += arg.value;
         else if (arg.type === "ws-pea") pea += arg.value;
         else {
@@ -83,182 +76,198 @@
       }
       return res;
     };
-    var make_ext_at = function(it) {
-      if (it["arguments"] === null) return it.name;
+    function make_ext_at(it) {
       context.unshift(it);
-      var ret = it.name + "(" + (it["arguments"].length ? args(it["arguments"]) : "") + ")";
+      let ret = it.name;
+      if (it.rhs) {
+        if (it.rhs.type === "identifier-list") ret += `=(${it.rhs.value.join(',')})`;
+        else ret += `=${it.rhs.value}`;
+      }
+      if (it.arguments) ret += `(${it["arguments"].length ? args(it["arguments"]) : ""})`;
       context.shift(); // XXX need to add more contexts, but not more than needed for ReSpec
       return ret;
     };
-    var extended_attributes = function(eats, pea) {
+    function extended_attributes(eats, pea) {
       if (!eats || !eats.length) return "";
-      return "[" + eats.map(make_ext_at).join(", ") + "]" + pea;
+      return `[${eats.map(make_ext_at).join(", ")}]${pea}`;
     };
 
-    var modifiers = "getter setter creator deleter legacycaller stringifier static".split(" ");
-    var operation = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    const modifiers = "getter setter creator deleter legacycaller stringifier static".split(" ");
+    function operation(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
       if (it.stringifier && !it.idlType) return "stringifier;";
-      for (var i = 0, n = modifiers.length; i < n; i++) {
-        var mod = modifiers[i];
+      for (const mod of modifiers) {
         if (it[mod]) ret += mod + " ";
       }
       ret += type(it.idlType) + " ";
       if (it.name) ret += it.name;
-      ret += "(" + args(it["arguments"]) + ");";
+      ret += `(${args(it["arguments"])});`;
       return ret;
     };
 
-    var attribute = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function attribute(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
       if (it["static"]) ret += "static ";
       if (it.stringifier) ret += "stringifier ";
       if (it.readonly) ret += "readonly ";
       if (it.inherit) ret += "inherit ";
-      ret += "attribute " + type(it.idlType) + " " + it.name + ";";
+      ret += `attribute ${type(it.idlType)} ${it.name};`;
       return ret;
     };
 
-    var interface_ = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function interface_(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
       if (it.partial) ret += "partial ";
-      ret += "interface " + it.name + " ";
-      if (it.inheritance) ret += ": " + it.inheritance + " ";
-      ret += "{" + iterate(it.members) + "};";
+      ret += `interface ${it.name} `;
+      if (it.inheritance) ret += `: ${it.inheritance} `;
+      ret += `{${iterate(it.members)}};`;
       return ret;
     };
 
-    var dictionary = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function interface_mixin(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
       if (it.partial) ret += "partial ";
-      ret += "dictionary " + it.name + " ";
-      ret += "{" + iterate(it.members) + "};";
+      ret += `interface mixin ${it.name} `;
+      ret += `{${iterate(it.members)}};`;
+      return ret;
+    }
+
+    function namespace(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
+      curPea = "";
+      if (it.partial) ret += "partial ";
+      ret += `namespace ${it.name} `;
+      ret += `{${iterate(it.members)}};`;
+      return ret;
+    }
+
+    function dictionary(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
+      curPea = "";
+      if (it.partial) ret += "partial ";
+      ret += `dictionary ${it.name} `;
+      if (it.inheritance) ret += `: ${it.inheritance} `;
+      ret += `{${iterate(it.members)}};`;
       return ret;
     };
-    var field = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function field(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
       if (it.required) ret += "required ";
-      ret += type(it.idlType) + " " + it.name;
-      if (it["default"]) ret += " = " + const_value(it["default"]);
+      ret += `${type(it.idlType)} ${it.name}`;
+      if (it["default"]) ret += ` = ${const_value(it["default"])}`;
       ret += ";";
       return ret;
     };
-    var exception = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function const_(it) {
+      const ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
-      ret += "exception " + it.name + " ";
-      if (it.inheritance) ret += ": " + it.inheritance + " ";
-      ret += "{" + iterate(it.members) + "};";
-      return ret;
+      return `${ret}const ${type(it.idlType)}${it.nullable ? "?" : ""} ${it.name} = ${const_value(it.value)};`;
     };
-    var const_ = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function typedef(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
-      return ret + "const " + type(it.idlType) + " " + it.name + " = " + const_value(it.value) + ";";
-    };
-    var typedef = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
-      curPea = "";
-      ret += "typedef " + extended_attributes(it.typeExtAttrs, curTPea);
+      ret += `typedef ${extended_attributes(it.typeExtAttrs, curTPea)}`;
       curTPea = "";
-      return ret + type(it.idlType) + " " + it.name + ";";
+      return `${ret}${type(it.idlType)} ${it.name};`;
     };
-    var implements_ = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function implements_(it) {
+      const ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
-      return ret + it.target + " implements " + it["implements"] + ";";
+      return `${ret}${it.target} implements ${it["implements"]};`;
     };
-    var callback = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function includes(it) {
+      const ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
-      return ret + "callback " + it.name + " = " + type(it.idlType) +
-        "(" + args(it["arguments"]) + ");";
+      return `${ret}${it.target} includes ${it.includes};`;
     };
-    var enum_ = function(it) {
-      var ret = extended_attributes(it.extAttrs, curPea);
+    function callback(it) {
+      const ret = extended_attributes(it.extAttrs, curPea);
       curPea = "";
-      ret += "enum " + it.name + " {";
-      for (var i = 0, n = it.values.length; i < n; i++) {
-        var v = it.values[i];
-        if (typeof v === "string") ret += '"' + v + '"';
+      return `${ret}callback ${it.name} = ${type(it.idlType)}(${args(it["arguments"])});`;
+    };
+    function enum_(it) {
+      let ret = extended_attributes(it.extAttrs, curPea);
+      curPea = "";
+      ret += `enum ${it.name} {`;
+      for (const v of it.values) {
+        if (v.type === "string") ret += `"${v.value}"`;
         else if (v.type === "ws") ret += v.value;
         else if (v.type === ",") ret += ",";
       }
       return ret + "};";
     };
-    var iterable = function(it) {
-      return "iterable<" + (it.idlType instanceof Array ? it.idlType.map(type).join(", ") : type(it.idlType)) + ">;";
+    function iterable(it) {
+      return `iterable<${Array.isArray(it.idlType) ? it.idlType.map(type).join(", ") : type(it.idlType)}>;`;
     };
-    var legacyiterable = function(it) {
-      return "legacyiterable<" + (it.idlType instanceof Array ? it.idlType.map(type).join(", ") : type(it.idlType)) + ">;";
+    function legacyiterable(it) {
+      return `legacyiterable<${Array.isArray(it.idlType) ? it.idlType.map(type).join(", ") : type(it.idlType)}>;`;
     };
-    var maplike = function(it) {
-      return (it.readonly ? "readonly " : "") + "maplike<" +
-        it.idlType.map(type).join(", ") + ">;";
+    function maplike(it) {
+      return `${it.readonly ? "readonly " : ""}maplike<${it.idlType.map(type).join(", ")}>;`;
     };
-    var setlike = function(it) {
-      return (it.readonly ? "readonly " : "") + "setlike<" +
-        type(it.idlType) + ">;";
+    function setlike(it) {
+      return `${it.readonly ? "readonly " : ""}setlike<${type(it.idlType[0])}>;`;
     };
-    var callbackInterface = function(it) {
-      return 'callback ' + interface_(it);
+    function callbackInterface(it) {
+      return `callback ${interface_(it)}`;
     };
 
-    var table = {
+    const table = {
       ws: literal,
       "ws-pea": wsPea,
       "ws-tpea": wsTPea,
       "line-comment": lineComment,
       "multiline-comment": multilineComment,
-      "interface": interface_,
-      operation: operation,
-      attribute: attribute,
-      dictionary: dictionary,
-      field: field,
-      exception: exception,
-      "const": const_,
-      typedef: typedef,
-      "implements": implements_,
-      callback: callback,
-      "enum": enum_,
-      iterable: iterable,
-      legacyiterable: legacyiterable,
-      maplike: maplike,
-      setlike: setlike,
+      interface: interface_,
+      "interface mixin": interface_mixin,
+      namespace,
+      operation,
+      attribute,
+      dictionary,
+      field,
+      const: const_,
+      typedef,
+      implements: implements_,
+      includes,
+      callback,
+      enum: enum_,
+      iterable,
+      legacyiterable,
+      maplike,
+      setlike,
       "callback interface": callbackInterface
     };
-    var dispatch = function(it) {
+    function dispatch(it) {
+      const dispatcher = table[it.type];
+      if (!dispatcher) {
+        throw new Error(`Type "${it.type}" is unsupported`)
+      }
       return table[it.type](it);
     };
-    var iterate = function(things) {
+    function iterate(things) {
       if (!things) return;
-      var ret = "";
-      for (var i = 0, n = things.length; i < n; i++) ret += dispatch(things[i]);
+      let ret = "";
+      for (const thing of things) ret += dispatch(thing);
       return ret;
     };
     return iterate(ast);
   };
 
 
-  var obj = {
-    write: function(ast, opt) {
-      if (!opt) opt = {};
-      return write(ast, opt);
-    }
+  const obj = {
+    write
   };
 
   if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
     module.exports = obj;
   } else if (typeof define === 'function' && define.amd) {
-    define([], function() {
-      return obj;
-    });
+    define([], () => obj);
   } else {
     (self || window).WebIDL2Writer = obj;
   }
-}());
+})();
diff --git a/resources/webidl2/package-lock.json b/resources/webidl2/package-lock.json
index 5352871..a6f529d 100644
--- a/resources/webidl2/package-lock.json
+++ b/resources/webidl2/package-lock.json
@@ -1,25 +1,52 @@
 {
   "name": "webidl2",
-  "version": "4.2.0",
+  "version": "10.2.1",
   "lockfileVersion": 1,
+  "requires": true,
   "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.0.0-beta.40",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz",
+      "integrity": "sha512-eVXQSbu/RimU6OKcK2/gDJVTFcxXJI4sHbIqw2mhwMZeQ2as/8AhS9DGkEDoHMBBNJZ5B0US63lF56x+KDcxiA==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "7.0.0-beta.40"
+      }
+    },
+    "@babel/highlight": {
+      "version": "7.0.0-beta.40",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.40.tgz",
+      "integrity": "sha512-mOhhTrzieV6VO7odgzFGFapiwRK0ei8RZRhfzHhb6cpX3QM8XXuCLXWjN8qBB7JReDdUR80V3LFfFrGUYevhNg==",
+      "dev": true,
+      "requires": {
+        "chalk": "2.3.2",
+        "esutils": "2.0.2",
+        "js-tokens": "3.0.2"
+      }
+    },
     "ansi-regex": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz",
-      "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+      "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
       "dev": true
     },
     "ansi-styles": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz",
-      "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=",
-      "dev": true
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "1.9.1"
+      }
     },
     "arr-diff": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
       "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "arr-flatten": "1.1.0"
+      }
     },
     "arr-flatten": {
       "version": "1.1.0",
@@ -40,34 +67,51 @@
       "dev": true
     },
     "brace-expansion": {
-      "version": "1.1.8",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
-      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
-      "dev": true
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "1.0.0",
+        "concat-map": "0.0.1"
+      }
     },
     "braces": {
       "version": "1.8.5",
       "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
       "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "expand-range": "1.8.2",
+        "preserve": "0.2.0",
+        "repeat-element": "1.1.2"
+      }
     },
     "browser-stdout": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
-      "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
       "dev": true
     },
     "chalk": {
-      "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
-      "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=",
-      "dev": true
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
+      "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "3.2.1",
+        "escape-string-regexp": "1.0.5",
+        "supports-color": "5.3.0"
+      }
     },
     "color-convert": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
-      "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
-      "dev": true
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+      "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
     },
     "color-name": {
       "version": "1.1.3",
@@ -76,9 +120,9 @@
       "dev": true
     },
     "commander": {
-      "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
-      "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+      "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
       "dev": true
     },
     "concat-map": {
@@ -88,15 +132,24 @@
       "dev": true
     },
     "debug": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz",
-      "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=",
-      "dev": true
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+      "dev": true,
+      "requires": {
+        "ms": "2.0.0"
+      }
     },
     "diff": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
-      "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+      "dev": true
+    },
+    "diff-match-patch": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.0.tgz",
+      "integrity": "sha1-HMPIOkkNZ/ldkeOfatHy4Ia2MEg=",
       "dev": true
     },
     "escape-string-regexp": {
@@ -105,37 +158,52 @@
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
       "dev": true
     },
+    "esutils": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+      "dev": true
+    },
     "expand-brackets": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
       "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-posix-bracket": "0.1.1"
+      }
     },
     "expand-range": {
       "version": "1.8.2",
       "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
       "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "fill-range": "2.2.3"
+      }
     },
     "expect": {
-      "version": "21.2.1",
-      "resolved": "https://registry.npmjs.org/expect/-/expect-21.2.1.tgz",
-      "integrity": "sha512-orfQQqFRTX0jH7znRIGi8ZMR8kTNpXklTTz8+HGTpmTKZo3Occ6JNB5FXMb8cRuiiC/GyDqsr30zUa66ACYlYw==",
+      "version": "22.4.0",
+      "resolved": "https://registry.npmjs.org/expect/-/expect-22.4.0.tgz",
+      "integrity": "sha512-Fiy862jT3qc70hwIHwwCBNISmaqBrfWKKrtqyMJ6iwZr+6KXtcnHojZFtd63TPRvRl8EQTJ+YXYy2lK6/6u+Hw==",
       "dev": true,
-      "dependencies": {
-        "ansi-styles": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
-          "dev": true
-        }
+      "requires": {
+        "ansi-styles": "3.2.1",
+        "jest-diff": "22.4.0",
+        "jest-get-type": "22.1.0",
+        "jest-matcher-utils": "22.4.0",
+        "jest-message-util": "22.4.0",
+        "jest-regex-util": "22.1.0"
       }
     },
     "extglob": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
       "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-extglob": "1.0.0"
+      }
     },
     "filename-regex": {
       "version": "2.0.1",
@@ -147,7 +215,14 @@
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
       "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-number": "2.1.0",
+        "isobject": "2.1.0",
+        "randomatic": "1.1.7",
+        "repeat-element": "1.1.2",
+        "repeat-string": "1.6.1"
+      }
     },
     "for-in": {
       "version": "1.0.2",
@@ -159,7 +234,10 @@
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
       "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "for-in": "1.0.2"
+      }
     },
     "fs.realpath": {
       "version": "1.0.0",
@@ -168,52 +246,65 @@
       "dev": true
     },
     "glob": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
-      "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
-      "dev": true
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "1.0.0",
+        "inflight": "1.0.6",
+        "inherits": "2.0.3",
+        "minimatch": "3.0.4",
+        "once": "1.4.0",
+        "path-is-absolute": "1.0.1"
+      }
     },
     "glob-base": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
       "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "glob-parent": "2.0.0",
+        "is-glob": "2.0.1"
+      }
     },
     "glob-parent": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
       "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
-      "dev": true
-    },
-    "graceful-readlink": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
-      "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-glob": "2.0.1"
+      }
     },
     "growl": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
-      "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
-      "dev": true
-    },
-    "has-ansi": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz",
-      "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=",
+      "version": "1.10.3",
+      "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
+      "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
       "dev": true
     },
     "has-flag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
-      "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "he": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+      "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
       "dev": true
     },
     "inflight": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "once": "1.4.0",
+        "wrappy": "1.0.2"
+      }
     },
     "inherits": {
       "version": "2.0.3",
@@ -222,9 +313,9 @@
       "dev": true
     },
     "is-buffer": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
-      "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
       "dev": true
     },
     "is-dotfile": {
@@ -237,7 +328,10 @@
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
       "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-primitive": "2.0.0"
+      }
     },
     "is-extendable": {
       "version": "0.1.1",
@@ -255,13 +349,19 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
       "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-extglob": "1.0.0"
+      }
     },
     "is-number": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
       "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "kind-of": "3.2.2"
+      }
     },
     "is-posix-bracket": {
       "version": "0.1.1",
@@ -285,199 +385,113 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
       "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "isarray": "1.0.0"
+      }
     },
     "jest-diff": {
-      "version": "21.2.1",
-      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-21.2.1.tgz",
-      "integrity": "sha512-E5fu6r7PvvPr5qAWE1RaUwIh/k6Zx/3OOkZ4rk5dBJkEWRrUuSgbMt2EO8IUTPTd6DOqU3LW6uTIwX5FRvXoFA==",
+      "version": "22.4.0",
+      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-22.4.0.tgz",
+      "integrity": "sha512-+/t20WmnkOkB8MOaGaPziI8zWKxquMvYw4Ub+wOzi7AUhmpFXz43buWSxVoZo4J5RnCozpGbX3/FssjJ5KV9Nw==",
       "dev": true,
-      "dependencies": {
-        "ansi-styles": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
-          "dev": true
-        },
-        "chalk": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz",
-          "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==",
-          "dev": true
-        },
-        "has-flag": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
-          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "4.4.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
-          "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
-          "dev": true
-        }
+      "requires": {
+        "chalk": "2.3.2",
+        "diff": "3.5.0",
+        "jest-get-type": "22.1.0",
+        "pretty-format": "22.4.0"
       }
     },
     "jest-get-type": {
-      "version": "21.2.0",
-      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz",
-      "integrity": "sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==",
+      "version": "22.1.0",
+      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.1.0.tgz",
+      "integrity": "sha512-nD97IVOlNP6fjIN5i7j5XRH+hFsHL7VlauBbzRvueaaUe70uohrkz7pL/N8lx/IAwZRTJ//wOdVgh85OgM7g3w==",
       "dev": true
     },
     "jest-matcher-utils": {
-      "version": "21.2.1",
-      "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz",
-      "integrity": "sha512-kn56My+sekD43dwQPrXBl9Zn9tAqwoy25xxe7/iY4u+mG8P3ALj5IK7MLHZ4Mi3xW7uWVCjGY8cm4PqgbsqMCg==",
+      "version": "22.4.0",
+      "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.0.tgz",
+      "integrity": "sha512-03m3issxUXpWMwDYTfmL8hRNewUB0yCRTeXPm+eq058rZxLHD9f5NtSSO98CWHqe4UyISIxd9Ao9iDVjHWd2qg==",
       "dev": true,
-      "dependencies": {
-        "ansi-styles": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
-          "dev": true
-        },
-        "chalk": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz",
-          "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==",
-          "dev": true
-        },
-        "has-flag": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
-          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "4.4.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
-          "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
-          "dev": true
-        }
+      "requires": {
+        "chalk": "2.3.2",
+        "jest-get-type": "22.1.0",
+        "pretty-format": "22.4.0"
       }
     },
     "jest-message-util": {
-      "version": "21.2.1",
-      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-21.2.1.tgz",
-      "integrity": "sha512-EbC1X2n0t9IdeMECJn2BOg7buOGivCvVNjqKMXTzQOu7uIfLml+keUfCALDh8o4rbtndIeyGU8/BKfoTr/LVDQ==",
+      "version": "22.4.0",
+      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.0.tgz",
+      "integrity": "sha512-eyCJB0T3hrlpFF2FqQoIB093OulP+1qvATQmD3IOgJgMGqPL6eYw8TbC5P/VCWPqKhGL51xvjIIhow5eZ2wHFw==",
       "dev": true,
-      "dependencies": {
-        "ansi-styles": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
-          "dev": true
-        },
-        "chalk": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz",
-          "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==",
-          "dev": true
-        },
-        "has-flag": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
-          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "4.4.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
-          "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
-          "dev": true
-        }
+      "requires": {
+        "@babel/code-frame": "7.0.0-beta.40",
+        "chalk": "2.3.2",
+        "micromatch": "2.3.11",
+        "slash": "1.0.0",
+        "stack-utils": "1.0.1"
       }
     },
     "jest-regex-util": {
-      "version": "21.2.0",
-      "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-21.2.0.tgz",
-      "integrity": "sha512-BKQ1F83EQy0d9Jen/mcVX7D+lUt2tthhK/2gDWRgLDJRNOdRgSp1iVqFxP8EN1ARuypvDflRfPzYT8fQnoBQFQ==",
+      "version": "22.1.0",
+      "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-22.1.0.tgz",
+      "integrity": "sha512-on0LqVS6Xeh69sw3d1RukVnur+lVOl3zkmb0Q54FHj9wHoq6dbtWqb3TSlnVUyx36hqjJhjgs/QLqs07Bzu72Q==",
       "dev": true
     },
-    "json3": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
-      "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
+    "js-tokens": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+      "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
       "dev": true
     },
     "jsondiffpatch": {
-      "version": "0.2.4",
-      "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.2.4.tgz",
-      "integrity": "sha1-1LbFOz/H2htLkcHCrsi5MrdRHVw=",
-      "dev": true
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.3.5.tgz",
+      "integrity": "sha512-v7eaGLDMCHXH+fsIaZhptEUJmS8EJpunq7IM4cc4vIT/kSRAkaZ6ZF4ebiNcyUelL0znbvj6o2B5Gh9v7Og0BQ==",
+      "dev": true,
+      "requires": {
+        "chalk": "2.3.2",
+        "diff-match-patch": "1.0.0"
+      }
     },
     "kind-of": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
       "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-      "dev": true
-    },
-    "lodash._baseassign": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
-      "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
-      "dev": true
-    },
-    "lodash._basecopy": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
-      "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
-      "dev": true
-    },
-    "lodash._basecreate": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz",
-      "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
-      "dev": true
-    },
-    "lodash._getnative": {
-      "version": "3.9.1",
-      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
-      "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
-      "dev": true
-    },
-    "lodash._isiterateecall": {
-      "version": "3.0.9",
-      "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
-      "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
-      "dev": true
-    },
-    "lodash.create": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
-      "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=",
-      "dev": true
-    },
-    "lodash.isarguments": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
-      "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
-      "dev": true
-    },
-    "lodash.isarray": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
-      "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
-      "dev": true
-    },
-    "lodash.keys": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
-      "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-buffer": "1.1.6"
+      }
     },
     "micromatch": {
       "version": "2.3.11",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
       "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "arr-diff": "2.0.0",
+        "array-unique": "0.2.1",
+        "braces": "1.8.5",
+        "expand-brackets": "0.1.5",
+        "extglob": "0.3.2",
+        "filename-regex": "2.0.1",
+        "is-extglob": "1.0.0",
+        "is-glob": "2.0.1",
+        "kind-of": "3.2.2",
+        "normalize-path": "2.1.1",
+        "object.omit": "2.0.1",
+        "parse-glob": "3.0.4",
+        "regex-cache": "0.4.4"
+      }
     },
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "brace-expansion": "1.1.11"
+      }
     },
     "minimist": {
       "version": "0.0.8",
@@ -489,51 +503,91 @@
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "minimist": "0.0.8"
+      }
     },
     "mocha": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.4.1.tgz",
-      "integrity": "sha1-o4ArSqOBk0yss43nDPdxYh2o+a8=",
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.4.tgz",
+      "integrity": "sha512-nMOpAPFosU1B4Ix1jdhx5e3q7XO55ic5a8cgYvW27CequcEY+BabS0kUVL1Cw1V5PuVHZWeNRWFLmEPexo79VA==",
       "dev": true,
+      "requires": {
+        "browser-stdout": "1.3.1",
+        "commander": "2.11.0",
+        "debug": "3.1.0",
+        "diff": "3.5.0",
+        "escape-string-regexp": "1.0.5",
+        "glob": "7.1.2",
+        "growl": "1.10.3",
+        "he": "1.1.1",
+        "mkdirp": "0.5.1",
+        "supports-color": "4.4.0"
+      },
       "dependencies": {
-        "supports-color": {
-          "version": "3.1.2",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
-          "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
+        "has-flag": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
           "dev": true
+        },
+        "supports-color": {
+          "version": "4.4.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
+          "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
+          "dev": true,
+          "requires": {
+            "has-flag": "2.0.0"
+          }
         }
       }
     },
     "ms": {
-      "version": "0.7.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
-      "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
       "dev": true
     },
     "normalize-path": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
       "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "remove-trailing-separator": "1.1.0"
+      }
     },
     "object.omit": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
       "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "for-own": "0.1.5",
+        "is-extendable": "0.1.1"
+      }
     },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "wrappy": "1.0.2"
+      }
     },
     "parse-glob": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
       "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "glob-base": "0.3.0",
+        "is-dotfile": "1.0.3",
+        "is-extglob": "1.0.0",
+        "is-glob": "2.0.1"
+      }
     },
     "path-is-absolute": {
       "version": "1.0.1",
@@ -548,23 +602,13 @@
       "dev": true
     },
     "pretty-format": {
-      "version": "21.2.1",
-      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz",
-      "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==",
+      "version": "22.4.0",
+      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.0.tgz",
+      "integrity": "sha512-pvCxP2iODIIk9adXlo4S3GRj0BrJiil68kByAa1PrgG97c1tClh9dLMgp3Z6cHFZrclaABt0UH8PIhwHuFLqYA==",
       "dev": true,
-      "dependencies": {
-        "ansi-regex": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
-          "dev": true
-        },
-        "ansi-styles": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
-          "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
-          "dev": true
-        }
+      "requires": {
+        "ansi-regex": "3.0.0",
+        "ansi-styles": "3.2.1"
       }
     },
     "randomatic": {
@@ -572,18 +616,28 @@
       "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
       "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
       "dev": true,
+      "requires": {
+        "is-number": "3.0.0",
+        "kind-of": "4.0.0"
+      },
       "dependencies": {
         "is-number": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
           "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
           "dev": true,
+          "requires": {
+            "kind-of": "3.2.2"
+          },
           "dependencies": {
             "kind-of": {
               "version": "3.2.2",
               "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
               "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-              "dev": true
+              "dev": true,
+              "requires": {
+                "is-buffer": "1.1.6"
+              }
             }
           }
         },
@@ -591,7 +645,10 @@
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
           "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "is-buffer": "1.1.6"
+          }
         }
       }
     },
@@ -599,7 +656,10 @@
       "version": "0.4.4",
       "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
       "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-equal-shallow": "0.1.3"
+      }
     },
     "remove-trailing-separator": {
       "version": "1.1.0",
@@ -625,17 +685,20 @@
       "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
       "dev": true
     },
-    "strip-ansi": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz",
-      "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=",
+    "stack-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz",
+      "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=",
       "dev": true
     },
     "supports-color": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz",
-      "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=",
-      "dev": true
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
+      "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
+      "dev": true,
+      "requires": {
+        "has-flag": "3.0.0"
+      }
     },
     "wrappy": {
       "version": "1.0.2",
diff --git a/resources/webidl2/package.json b/resources/webidl2/package.json
index d7b8a42..ab282d1 100644
--- a/resources/webidl2/package.json
+++ b/resources/webidl2/package.json
@@ -1,22 +1,24 @@
 {
   "name": "webidl2",
   "description": "A WebIDL Parser",
-  "version": "8.1.0",
+  "version": "10.2.1",
   "contributors": [
     "Robin Berjon <robin@berjon.com> (https://berjon.com)",
     "Marcos Caceres <marcos@marcosc.com> (https://marcosc.com)",
-    "Kagami Sascha Rosylight <saschaplas@outlook.com>"
+    "Kagami Sascha Rosylight <saschaplas@outlook.com>",
+    "Timothy Gu <timothygu99@gmail.com>"
   ],
   "license": "W3C",
   "dependencies": {},
   "devDependencies": {
-    "expect": "21.2.1",
-    "jsondiffpatch": "0.2.5",
-    "mocha": "4.0.1"
+    "expect": "22.4.0",
+    "jsondiffpatch": "0.3.5",
+    "mocha": "5.0.4"
   },
   "scripts": {
-    "test": "mocha"
+    "test": "mocha",
+    "acquire": "node test/util/acquire.js"
   },
-  "repository": "git://github.com/darobin/webidl2.js",
+  "repository": "git://github.com/w3c/webidl2.js",
   "main": "index"
 }
diff --git a/resources/webidl2/test/invalid.js b/resources/webidl2/test/invalid.js
index 548f0ea..19bbf00 100644
--- a/resources/webidl2/test/invalid.js
+++ b/resources/webidl2/test/invalid.js
@@ -4,37 +4,17 @@
 
 "use strict";
 
-const wp = require("../lib/webidl2");
-const expect = require("expect");
-const pth = require("path");
+const { collect } = require("./util/collect");
 const fs = require("fs");
+const expect = require("expect");
 
 describe("Parses all of the invalid IDLs to check that they blow up correctly", () => {
-  const dir = pth.join(__dirname, "invalid/idl");
-  const skip = {};
-  const idls = fs.readdirSync(dir)
-    .filter(it => (/\.w?idl$/).test(it) && !skip[it])
-    .map(it => pth.join(dir, it));
-  const errors = idls.map(it => pth.join(__dirname, "invalid", "json", pth.basename(it).replace(/\.w?idl/, ".json")));
-
-  for (let i = 0, n = idls.length; i < n; i++) {
-    const idl = idls[i];
-    const err = JSON.parse(fs.readFileSync(errors[i], "utf8"));
-
-    it(`should produce the right error for ${idl}`, () => {
-      let error;
-      try {
-        var ast = wp.parse(fs.readFileSync(idl, "utf8"));
-        console.log(JSON.stringify(ast, null, 4));
-      }
-      catch (e) {
-        error = e;
-      }
-      finally {
-        expect(error).toBeTruthy();
-        expect(error.message).toEqual(err.message);
-        expect(error.line).toEqual(err.line);
-      }
+  for (const test of collect("invalid", { expectError: true })) {
+    it(`should produce the right error for ${test.path}`, () => {
+      const err = test.readJSON();
+      expect(test.error).toBeTruthy();
+      expect(test.error.message).toEqual(err.message);
+      expect(test.error.line).toEqual(err.line);
     });
   }
 });
diff --git a/resources/webidl2/test/syntax/idl/iterator.widl b/resources/webidl2/test/invalid/idl/iterator.widl
similarity index 100%
rename from resources/webidl2/test/syntax/idl/iterator.widl
rename to resources/webidl2/test/invalid/idl/iterator.widl
diff --git a/resources/webidl2/test/invalid/idl/no-semicolon-callback.widl b/resources/webidl2/test/invalid/idl/no-semicolon-callback.widl
new file mode 100644
index 0000000..cb20557
--- /dev/null
+++ b/resources/webidl2/test/invalid/idl/no-semicolon-callback.widl
@@ -0,0 +1,7 @@
+callback interface NoSemicolon {
+  attribute boolean noSemiColon;
+}
+
+enum YouNeedOne {
+  "really"
+}
diff --git a/resources/webidl2/test/invalid/idl/no-semicolon.widl b/resources/webidl2/test/invalid/idl/no-semicolon.widl
new file mode 100644
index 0000000..10bc716
--- /dev/null
+++ b/resources/webidl2/test/invalid/idl/no-semicolon.widl
@@ -0,0 +1,7 @@
+partial interface NoSemicolon {
+  attribute boolean noSemiColon;
+}
+
+enum YouNeedOne {
+  "really"
+}
diff --git a/resources/webidl2/test/invalid/idl/stray-slash.widl b/resources/webidl2/test/invalid/idl/stray-slash.widl
new file mode 100644
index 0000000..b673aa9
--- /dev/null
+++ b/resources/webidl2/test/invalid/idl/stray-slash.widl
@@ -0,0 +1,2 @@
+// This is a comment.
+/ This is not.
diff --git a/resources/webidl2/test/invalid/idl/stringconstants.idl b/resources/webidl2/test/invalid/idl/stringconstants.widl
similarity index 100%
rename from resources/webidl2/test/invalid/idl/stringconstants.idl
rename to resources/webidl2/test/invalid/idl/stringconstants.widl
diff --git a/resources/webidl2/test/invalid/json/array.json b/resources/webidl2/test/invalid/json/array.json
index 30b377f..898b2d8 100644
--- a/resources/webidl2/test/invalid/json/array.json
+++ b/resources/webidl2/test/invalid/json/array.json
@@ -1,4 +1,4 @@
 {
-    "message": "No name in attribute",
+    "message": "Got an error during or right after parsing `interface LotteryResults`: No name in attribute",
     "line": 5
-}
\ No newline at end of file
+}
diff --git a/resources/webidl2/test/invalid/json/caller.json b/resources/webidl2/test/invalid/json/caller.json
index 79b8eca..567fa33 100644
--- a/resources/webidl2/test/invalid/json/caller.json
+++ b/resources/webidl2/test/invalid/json/caller.json
@@ -1,4 +1,4 @@
 {
-    "message": "Invalid operation",
+    "message": "Got an error during or right after parsing `interface NumberQuadrupler`: Invalid operation",
     "line": 6
 }
diff --git a/resources/webidl2/test/invalid/json/dict-required-default.json b/resources/webidl2/test/invalid/json/dict-required-default.json
index c5afeca..82b6b2a 100644
--- a/resources/webidl2/test/invalid/json/dict-required-default.json
+++ b/resources/webidl2/test/invalid/json/dict-required-default.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Required member must not have a default"
+    "message":  "Got an error during or right after parsing `dictionary Dict`: Required member must not have a default"
 ,   "line":     4
 }
diff --git a/resources/webidl2/test/invalid/json/duplicate.json b/resources/webidl2/test/invalid/json/duplicate.json
index cef3358..e88a715 100644
--- a/resources/webidl2/test/invalid/json/duplicate.json
+++ b/resources/webidl2/test/invalid/json/duplicate.json
@@ -1,4 +1,4 @@
 {
-    "message": "The name \"Test\" of type \"typedef\" is already seen",
+    "message": "Got an error during or right after parsing `typedef Test`: The name \"Test\" of type \"typedef\" is already seen",
     "line": 3
-}
\ No newline at end of file
+}
diff --git a/resources/webidl2/test/invalid/json/enum.json b/resources/webidl2/test/invalid/json/enum.json
index 1661158..073ff6c 100644
--- a/resources/webidl2/test/invalid/json/enum.json
+++ b/resources/webidl2/test/invalid/json/enum.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Unexpected value in enum"
+    "message":  "Got an error during or right after parsing `enum foo`: Unexpected value in enum"
 ,   "line":     1
 }
diff --git a/resources/webidl2/test/invalid/json/exception.json b/resources/webidl2/test/invalid/json/exception.json
index f52cda1..ad9fac6 100644
--- a/resources/webidl2/test/invalid/json/exception.json
+++ b/resources/webidl2/test/invalid/json/exception.json
@@ -1,4 +1,4 @@
 {
-    "message": "Unrecognised tokens",
+    "message": "Got an error before parsing any named definition: Unrecognised tokens",
     "line": 4
 }
diff --git a/resources/webidl2/test/invalid/json/iterator.json b/resources/webidl2/test/invalid/json/iterator.json
new file mode 100644
index 0000000..e46d653
--- /dev/null
+++ b/resources/webidl2/test/invalid/json/iterator.json
@@ -0,0 +1,4 @@
+{
+    "message": "Got an error during or right after parsing `interface SessionManager`: Invalid operation",
+    "line": 5
+}
diff --git a/resources/webidl2/test/invalid/json/maplike-1type.json b/resources/webidl2/test/invalid/json/maplike-1type.json
index 859a820..75e7a35 100644
--- a/resources/webidl2/test/invalid/json/maplike-1type.json
+++ b/resources/webidl2/test/invalid/json/maplike-1type.json
@@ -1,4 +1,4 @@
 {
-    "message": "Missing second type argument in maplike declaration",
+    "message": "Got an error during or right after parsing `interface MapLikeOneType`: Missing second type argument in maplike declaration",
     "line": 2
-}
\ No newline at end of file
+}
diff --git a/resources/webidl2/test/invalid/json/module.json b/resources/webidl2/test/invalid/json/module.json
index 3b0984d..9c071cd 100644
--- a/resources/webidl2/test/invalid/json/module.json
+++ b/resources/webidl2/test/invalid/json/module.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Unrecognised tokens"
+    "message":  "Got an error before parsing any named definition: Unrecognised tokens"
 ,   "line":     2
 }
diff --git a/resources/webidl2/test/invalid/json/no-semicolon-callback.json b/resources/webidl2/test/invalid/json/no-semicolon-callback.json
new file mode 100644
index 0000000..1db9d14
--- /dev/null
+++ b/resources/webidl2/test/invalid/json/no-semicolon-callback.json
@@ -0,0 +1,4 @@
+{
+    "message": "Got an error during or right after parsing `callback interface NoSemicolon`: Missing semicolon after interface",
+    "line": 5
+}
diff --git a/resources/webidl2/test/invalid/json/no-semicolon.json b/resources/webidl2/test/invalid/json/no-semicolon.json
new file mode 100644
index 0000000..087532a
--- /dev/null
+++ b/resources/webidl2/test/invalid/json/no-semicolon.json
@@ -0,0 +1,4 @@
+{
+    "message": "Got an error during or right after parsing `partial interface NoSemicolon`: Missing semicolon after interface",
+    "line": 5
+}
diff --git a/resources/webidl2/test/invalid/json/nonnullableany.json b/resources/webidl2/test/invalid/json/nonnullableany.json
index cf5229e..8a1f900 100644
--- a/resources/webidl2/test/invalid/json/nonnullableany.json
+++ b/resources/webidl2/test/invalid/json/nonnullableany.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Type any cannot be made nullable"
+    "message":  "Got an error during or right after parsing `interface NonNullable`: Type any cannot be made nullable"
 ,   "line":     2
 }
diff --git a/resources/webidl2/test/invalid/json/nonnullableobjects.json b/resources/webidl2/test/invalid/json/nonnullableobjects.json
index 23cbb3e..52bd8da 100644
--- a/resources/webidl2/test/invalid/json/nonnullableobjects.json
+++ b/resources/webidl2/test/invalid/json/nonnullableobjects.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Can't nullable more than once"
+    "message":  "Got an error during or right after parsing `interface NonNullable`: Can't nullable more than once"
 ,   "line":     4
 }
diff --git a/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json b/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json
index e9623ce..71212d4 100644
--- a/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json
+++ b/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json
@@ -1,4 +1,4 @@
 {
-    "message": "Promise type cannot have extended attribute",
+    "message": "Got an error during or right after parsing `interface Foo`: Promise type cannot have extended attribute",
     "line": 2
 }
diff --git a/resources/webidl2/test/invalid/json/raises.json b/resources/webidl2/test/invalid/json/raises.json
index 8b67afe..3165b87 100644
--- a/resources/webidl2/test/invalid/json/raises.json
+++ b/resources/webidl2/test/invalid/json/raises.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Unterminated attribute"
+    "message":  "Got an error during or right after parsing `interface Person`: Unterminated attribute"
 ,   "line":     5
 }
diff --git a/resources/webidl2/test/invalid/json/readonly-iterable.json b/resources/webidl2/test/invalid/json/readonly-iterable.json
index c6f52a2..1a09264 100644
--- a/resources/webidl2/test/invalid/json/readonly-iterable.json
+++ b/resources/webidl2/test/invalid/json/readonly-iterable.json
@@ -1,4 +1,4 @@
 {
-    "message": "Invalid operation",
+    "message": "Got an error during or right after parsing `interface ReadonlyIterable`: Invalid operation",
     "line": 2
-}
\ No newline at end of file
+}
diff --git a/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json b/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json
index 3945825..4002e7f 100644
--- a/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json
+++ b/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json
@@ -1,4 +1,4 @@
 {
-    "message": "Record key cannot have extended attribute",
+    "message": "Got an error during or right after parsing `interface Foo`: Record key cannot have extended attribute",
     "line": 2
 }
diff --git a/resources/webidl2/test/invalid/json/record-key.json b/resources/webidl2/test/invalid/json/record-key.json
index 3b929b9..179d645 100644
--- a/resources/webidl2/test/invalid/json/record-key.json
+++ b/resources/webidl2/test/invalid/json/record-key.json
@@ -1,4 +1,4 @@
 {
-    "message": "Record key must be DOMString, USVString, or ByteString",
+    "message": "Got an error during or right after parsing `interface Foo`: Record key must be DOMString, USVString, or ByteString",
     "line": 2
 }
diff --git a/resources/webidl2/test/invalid/json/scopedname.json b/resources/webidl2/test/invalid/json/scopedname.json
index 8e2cd80..4620d2d 100644
--- a/resources/webidl2/test/invalid/json/scopedname.json
+++ b/resources/webidl2/test/invalid/json/scopedname.json
@@ -1,4 +1,4 @@
 {
-    "message":  "No name in typedef"
+    "message":  "Got an error before parsing any named definition: No name in typedef"
 ,   "line":     2
 }
diff --git a/resources/webidl2/test/invalid/json/sequenceAsAttribute.json b/resources/webidl2/test/invalid/json/sequenceAsAttribute.json
index b714f5d..5b4314a 100644
--- a/resources/webidl2/test/invalid/json/sequenceAsAttribute.json
+++ b/resources/webidl2/test/invalid/json/sequenceAsAttribute.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Attributes cannot accept sequence types"
+    "message":  "Got an error during or right after parsing `interface sequenceAsAttribute`: Attributes cannot accept sequence types"
 ,   "line":     2
 }
diff --git a/resources/webidl2/test/invalid/json/setlike-2types.json b/resources/webidl2/test/invalid/json/setlike-2types.json
index c9e49b6..2900e1b 100644
--- a/resources/webidl2/test/invalid/json/setlike-2types.json
+++ b/resources/webidl2/test/invalid/json/setlike-2types.json
@@ -1,4 +1,4 @@
 {
-    "message": "Unterminated setlike declaration",
+    "message": "Got an error during or right after parsing `interface SetLikeTwoTypes`: Unterminated setlike declaration",
     "line": 2
-}
\ No newline at end of file
+}
diff --git a/resources/webidl2/test/invalid/json/setter-creator.json b/resources/webidl2/test/invalid/json/setter-creator.json
index 07b9f1f..25decb3 100644
--- a/resources/webidl2/test/invalid/json/setter-creator.json
+++ b/resources/webidl2/test/invalid/json/setter-creator.json
@@ -1,4 +1,4 @@
 {
-    "message": "Invalid operation",
+    "message": "Got an error during or right after parsing `interface OrderedMap`: Invalid operation",
     "line": 3
 }
diff --git a/resources/webidl2/test/invalid/json/special-omittable.json b/resources/webidl2/test/invalid/json/special-omittable.json
index 7acb088..c20b28e 100644
--- a/resources/webidl2/test/invalid/json/special-omittable.json
+++ b/resources/webidl2/test/invalid/json/special-omittable.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Invalid operation"
+    "message":  "Got an error during or right after parsing `interface Dictionary`: Invalid operation"
 ,   "line":     6
 }
diff --git a/resources/webidl2/test/invalid/json/stray-slash.json b/resources/webidl2/test/invalid/json/stray-slash.json
new file mode 100644
index 0000000..9c071cd
--- /dev/null
+++ b/resources/webidl2/test/invalid/json/stray-slash.json
@@ -0,0 +1,4 @@
+{
+    "message":  "Got an error before parsing any named definition: Unrecognised tokens"
+,   "line":     2
+}
diff --git a/resources/webidl2/test/invalid/json/stringconstants.json b/resources/webidl2/test/invalid/json/stringconstants.json
index d5bf1a8..1eeb31c 100644
--- a/resources/webidl2/test/invalid/json/stringconstants.json
+++ b/resources/webidl2/test/invalid/json/stringconstants.json
@@ -1,4 +1,4 @@
 {
-    "message":  "No value for const"
+    "message":  "Got an error during or right after parsing `interface Util`: No value for const"
 ,   "line":     2
 }
diff --git a/resources/webidl2/test/invalid/json/typedef-nested.json b/resources/webidl2/test/invalid/json/typedef-nested.json
index d7fb918..8c60814 100644
--- a/resources/webidl2/test/invalid/json/typedef-nested.json
+++ b/resources/webidl2/test/invalid/json/typedef-nested.json
@@ -1,4 +1,4 @@
 {
-    "message":  "Invalid operation"
+    "message":  "Got an error during or right after parsing `interface Widget`: Invalid operation"
 ,   "line":     14
-}
\ No newline at end of file
+}
diff --git a/resources/webidl2/test/syntax.js b/resources/webidl2/test/syntax.js
index 0838f35..05d647e 100644
--- a/resources/webidl2/test/syntax.js
+++ b/resources/webidl2/test/syntax.js
@@ -1,34 +1,14 @@
 "use strict";
 
-const wp = require("../lib/webidl2");
+const { collect } = require("./util/collect");
 const expect = require("expect");
-const pth = require("path");
-const fs = require("fs");
-const jdp = require("jsondiffpatch");
 const debug = true;
 
 describe("Parses all of the IDLs to produce the correct ASTs", () => {
-  const dir = pth.join(__dirname, "syntax/idl");
-  const skip = {}; // use if we have a broken test
-  const idls = fs.readdirSync(dir)
-    .filter(it => (/\.widl$/).test(it) && !skip[it])
-    .map(it => pth.join(dir, it));
-  const jsons = idls.map(it => pth.join(__dirname, "syntax/json", pth.basename(it).replace(".widl", ".json")));
-
-  for (let i = 0, n = idls.length; i < n; i++) {
-    const idl = idls[i];
-    const json = jsons[i];
-
-    it(`should produce the same AST for ${idl}`, () => {
+  for (const test of collect("syntax")) {
+    it(`should produce the same AST for ${test.path}`, () => {
       try {
-        const optFile = pth.join(__dirname, "syntax/opt", pth.basename(json));
-        let opt = undefined;
-        if (fs.existsSync(optFile))
-          opt = JSON.parse(fs.readFileSync(optFile, "utf8"));
-        const diff = jdp.diff(JSON.parse(fs.readFileSync(json, "utf8")),
-          wp.parse(fs.readFileSync(idl, "utf8").replace(/\r\n/g, "\n"), opt));
-        if (diff && debug) console.log(JSON.stringify(diff, null, 4));
-        expect(diff).toBe(undefined);
+        expect(test.diff()).toBeFalsy();
       }
       catch (e) {
         console.log(e.toString());
diff --git a/resources/webidl2/test/syntax/idl/extended-attributes.widl b/resources/webidl2/test/syntax/idl/extended-attributes.widl
index a49463b..57d4f97 100644
--- a/resources/webidl2/test/syntax/idl/extended-attributes.widl
+++ b/resources/webidl2/test/syntax/idl/extended-attributes.widl
@@ -20,4 +20,10 @@
   attribute double cx;
   attribute double cy;
   readonly attribute double circumference;
-};
\ No newline at end of file
+};
+
+// Extracted from https://heycam.github.io/webidl/#idl-annotated-types on 2017-12-15
+[Exposed=Window]
+interface I {
+    attribute [XAttr] (long or Node) attrib;
+};
diff --git a/resources/webidl2/test/syntax/idl/typedef-union.idl b/resources/webidl2/test/syntax/idl/typedef-union.widl
similarity index 100%
rename from resources/webidl2/test/syntax/idl/typedef-union.idl
rename to resources/webidl2/test/syntax/idl/typedef-union.widl
diff --git a/resources/webidl2/test/syntax/json/allowany.json b/resources/webidl2/test/syntax/json/allowany.json
index ee898d6..cd5c6e0 100644
--- a/resources/webidl2/test/syntax/json/allowany.json
+++ b/resources/webidl2/test/syntax/json/allowany.json
@@ -12,6 +12,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -30,6 +31,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -43,6 +45,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -62,6 +65,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -82,6 +86,7 @@
                             }
                         ],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/attributes.json b/resources/webidl2/test/syntax/json/attributes.json
index 86fe9d5..c90c70d 100644
--- a/resources/webidl2/test/syntax/json/attributes.json
+++ b/resources/webidl2/test/syntax/json/attributes.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -24,4 +25,4 @@
         "inheritance": null,
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/callback.json b/resources/webidl2/test/syntax/json/callback.json
index 5301c40..f31067d 100644
--- a/resources/webidl2/test/syntax/json/callback.json
+++ b/resources/webidl2/test/syntax/json/callback.json
@@ -3,6 +3,7 @@
         "type": "callback",
         "name": "AsyncOperationCallback",
         "idlType": {
+            "type": "return-type",
             "sequence": false,
             "generic": null,
             "nullable": false,
@@ -15,6 +16,7 @@
                 "variadic": false,
                 "extAttrs": [],
                 "idlType": {
+                    "type": "argument-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -39,6 +41,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -52,6 +55,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -71,6 +75,7 @@
         "type": "callback",
         "name": "SortCallback",
         "idlType": {
+            "type": "return-type",
             "sequence": false,
             "generic": null,
             "nullable": false,
@@ -83,6 +88,7 @@
                 "variadic": false,
                 "extAttrs": [],
                 "idlType": {
+                    "type": "argument-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -96,6 +102,7 @@
                 "variadic": false,
                 "extAttrs": [],
                 "idlType": {
+                    "type": "argument-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/constants.json b/resources/webidl2/test/syntax/json/constants.json
index 7522286..4b98751 100644
--- a/resources/webidl2/test/syntax/json/constants.json
+++ b/resources/webidl2/test/syntax/json/constants.json
@@ -7,7 +7,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "boolean",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "boolean"
+                },
                 "name": "DEBUG",
                 "value": {
                     "type": "boolean",
@@ -18,7 +21,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "short",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "short"
+                },
                 "name": "negative",
                 "value": {
                     "type": "number",
@@ -29,7 +35,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "octet",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "octet"
+                },
                 "name": "LF",
                 "value": {
                     "type": "number",
@@ -40,7 +49,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "unsigned long",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "unsigned long"
+                },
                 "name": "BIT_MASK",
                 "value": {
                     "type": "number",
@@ -51,7 +63,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "float",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "float"
+                },
                 "name": "AVOGADRO",
                 "value": {
                     "type": "number",
@@ -62,7 +77,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "unrestricted float",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "unrestricted float"
+                },
                 "name": "sobig",
                 "value": {
                     "type": "Infinity",
@@ -73,7 +91,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "unrestricted double",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "unrestricted double"
+                },
                 "name": "minusonedividedbyzero",
                 "value": {
                     "type": "Infinity",
@@ -84,7 +105,10 @@
             {
                 "type": "const",
                 "nullable": false,
-                "idlType": "short",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "short"
+                },
                 "name": "notanumber",
                 "value": {
                     "type": "NaN"
@@ -95,4 +119,4 @@
         "inheritance": null,
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/constructor.json b/resources/webidl2/test/syntax/json/constructor.json
index ee55ef0..292236f 100644
--- a/resources/webidl2/test/syntax/json/constructor.json
+++ b/resources/webidl2/test/syntax/json/constructor.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -43,6 +45,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -59,6 +62,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -85,6 +89,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/dictionary-inherits.json b/resources/webidl2/test/syntax/json/dictionary-inherits.json
index ef30b1a..9b928f4 100644
--- a/resources/webidl2/test/syntax/json/dictionary-inherits.json
+++ b/resources/webidl2/test/syntax/json/dictionary-inherits.json
@@ -9,6 +9,7 @@
                 "name": "fillPattern",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": true,
@@ -26,6 +27,7 @@
                 "name": "strokePattern",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": true,
@@ -42,6 +44,7 @@
                 "name": "position",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -64,6 +67,7 @@
                 "name": "hydrometry",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -76,4 +80,4 @@
         "inheritance": "PaintOptions",
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/dictionary.json b/resources/webidl2/test/syntax/json/dictionary.json
index 28494ce..f74fedc 100644
--- a/resources/webidl2/test/syntax/json/dictionary.json
+++ b/resources/webidl2/test/syntax/json/dictionary.json
@@ -9,6 +9,7 @@
                 "name": "fillPattern",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": true,
@@ -26,6 +27,7 @@
                 "name": "strokePattern",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": true,
@@ -42,6 +44,7 @@
                 "name": "position",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -55,11 +58,13 @@
                 "name": "seq",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": true,
                     "generic": "sequence",
                     "nullable": false,
                     "union": false,
                     "idlType": {
+                        "type": "dictionary-type",
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -78,6 +83,7 @@
                 "name": "reqSeq",
                 "required": true,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -100,6 +106,7 @@
                 "name": "h",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -113,6 +120,7 @@
                 "name": "d",
                 "required": false,
                 "idlType": {
+                    "type": "dictionary-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -124,4 +132,4 @@
         ],
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/documentation-dos.json b/resources/webidl2/test/syntax/json/documentation-dos.json
index 340e039..baa0b5a 100644
--- a/resources/webidl2/test/syntax/json/documentation-dos.json
+++ b/resources/webidl2/test/syntax/json/documentation-dos.json
@@ -7,4 +7,4 @@
         "inheritance": null,
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/documentation.json b/resources/webidl2/test/syntax/json/documentation.json
index 340e039..baa0b5a 100644
--- a/resources/webidl2/test/syntax/json/documentation.json
+++ b/resources/webidl2/test/syntax/json/documentation.json
@@ -7,4 +7,4 @@
         "inheritance": null,
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/enum.json b/resources/webidl2/test/syntax/json/enum.json
index 88a608b..29d1c86 100644
--- a/resources/webidl2/test/syntax/json/enum.json
+++ b/resources/webidl2/test/syntax/json/enum.json
@@ -3,9 +3,18 @@
         "type": "enum",
         "name": "MealType",
         "values": [
-            { "type": "string", "value": "rice" },
-            { "type": "string", "value": "noodles" },
-            { "type": "string", "value": "other" }
+            {
+                "type": "string",
+                "value": "rice"
+            },
+            {
+                "type": "string",
+                "value": "noodles"
+            },
+            {
+                "type": "string",
+                "value": "other"
+            }
         ],
         "extAttrs": []
     },
@@ -21,6 +30,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -37,6 +47,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -54,6 +65,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -67,6 +79,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -80,6 +93,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -99,9 +113,18 @@
         "type": "enum",
         "name": "AltMealType",
         "values": [
-            { "type": "string", "value": "rice" },
-            { "type": "string", "value": "noodles" },
-            { "type": "string", "value": "other" }
+            {
+                "type": "string",
+                "value": "rice"
+            },
+            {
+                "type": "string",
+                "value": "noodles"
+            },
+            {
+                "type": "string",
+                "value": "other"
+            }
         ],
         "extAttrs": []
     }
diff --git a/resources/webidl2/test/syntax/json/equivalent-decl.json b/resources/webidl2/test/syntax/json/equivalent-decl.json
index 7ef08bc..de8d4db 100644
--- a/resources/webidl2/test/syntax/json/equivalent-decl.json
+++ b/resources/webidl2/test/syntax/json/equivalent-decl.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -28,6 +29,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +43,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -60,6 +63,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -73,6 +77,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -86,6 +91,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -113,6 +119,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -130,6 +137,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -143,6 +151,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -162,6 +171,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -175,6 +185,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -188,6 +199,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -207,6 +219,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -220,6 +233,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -239,6 +253,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -252,6 +267,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -265,6 +281,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/extended-attributes.json b/resources/webidl2/test/syntax/json/extended-attributes.json
index 6526e2a..e0dc236 100644
--- a/resources/webidl2/test/syntax/json/extended-attributes.json
+++ b/resources/webidl2/test/syntax/json/extended-attributes.json
@@ -30,139 +30,205 @@
         ]
     },
     {
-      "type": "interface",
-      "name": "IdInterface",
-      "partial": false,
-      "members": [],
-      "inheritance": null,
-      "extAttrs": [
-        {
-          "name": "IntAttr",
-          "arguments": null,
-          "type": "extended-attribute",
-          "rhs": {
-            "type": "integer",
-            "value": "0"
-          }
-        },
-        {
-          "name": "FloatAttr",
-          "arguments": null,
-          "type": "extended-attribute",
-          "rhs": {
-            "type": "float",
-            "value": "3.14"
-          }
-        },
-        {
-          "name": "StringAttr",
-          "arguments": null,
-          "type": "extended-attribute",
-          "rhs": {
-            "type": "string",
-            "value": "\"abc\""
-          }
-        }
-      ]
+        "type": "interface",
+        "name": "IdInterface",
+        "partial": false,
+        "members": [],
+        "inheritance": null,
+        "extAttrs": [
+            {
+                "name": "IntAttr",
+                "arguments": null,
+                "type": "extended-attribute",
+                "rhs": {
+                    "type": "integer",
+                    "value": "0"
+                }
+            },
+            {
+                "name": "FloatAttr",
+                "arguments": null,
+                "type": "extended-attribute",
+                "rhs": {
+                    "type": "float",
+                    "value": "3.14"
+                }
+            },
+            {
+                "name": "StringAttr",
+                "arguments": null,
+                "type": "extended-attribute",
+                "rhs": {
+                    "type": "string",
+                    "value": "\"abc\""
+                }
+            }
+        ]
     },
     {
-      "type": "interface",
-      "name": "Circle",
-      "partial": false,
-      "members": [
-        {
-          "type": "attribute",
-          "static": false,
-          "stringifier": false,
-          "inherit": false,
-          "readonly": false,
-          "idlType": {
-            "sequence": false,
-            "generic": null,
-            "nullable": false,
-            "union": false,
-            "idlType": "double"
-          },
-          "name": "r",
-          "extAttrs": []
-        },
-        {
-          "type": "attribute",
-          "static": false,
-          "stringifier": false,
-          "inherit": false,
-          "readonly": false,
-          "idlType": {
-            "sequence": false,
-            "generic": null,
-            "nullable": false,
-            "union": false,
-            "idlType": "double"
-          },
-          "name": "cx",
-          "extAttrs": []
-        },
-        {
-          "type": "attribute",
-          "static": false,
-          "stringifier": false,
-          "inherit": false,
-          "readonly": false,
-          "idlType": {
-            "sequence": false,
-            "generic": null,
-            "nullable": false,
-            "union": false,
-            "idlType": "double"
-          },
-          "name": "cy",
-          "extAttrs": []
-        },
-        {
-          "type": "attribute",
-          "static": false,
-          "stringifier": false,
-          "inherit": false,
-          "readonly": true,
-          "idlType": {
-            "sequence": false,
-            "generic": null,
-            "nullable": false,
-            "union": false,
-            "idlType": "double"
-          },
-          "name": "circumference",
-          "extAttrs": []
-        }
-      ],
-      "inheritance": null,
-      "extAttrs": [
-        {
-          "name": "Constructor",
-          "arguments": null,
-          "type": "extended-attribute",
-          "rhs": null
-        },
-        {
-          "name": "Constructor",
-          "arguments": [
+        "type": "interface",
+        "name": "Circle",
+        "partial": false,
+        "members": [
             {
-              "optional": false,
-              "variadic": false,
-              "extAttrs": [],
-              "idlType": {
-                "sequence": false,
-                "generic": null,
-                "nullable": false,
-                "union": false,
-                "idlType": "double"
-              },
-              "name": "radius"
+                "type": "attribute",
+                "static": false,
+                "stringifier": false,
+                "inherit": false,
+                "readonly": false,
+                "idlType": {
+                    "type": "attribute-type",
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "double"
+                },
+                "name": "r",
+                "extAttrs": []
+            },
+            {
+                "type": "attribute",
+                "static": false,
+                "stringifier": false,
+                "inherit": false,
+                "readonly": false,
+                "idlType": {
+                    "type": "attribute-type",
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "double"
+                },
+                "name": "cx",
+                "extAttrs": []
+            },
+            {
+                "type": "attribute",
+                "static": false,
+                "stringifier": false,
+                "inherit": false,
+                "readonly": false,
+                "idlType": {
+                    "type": "attribute-type",
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "double"
+                },
+                "name": "cy",
+                "extAttrs": []
+            },
+            {
+                "type": "attribute",
+                "static": false,
+                "stringifier": false,
+                "inherit": false,
+                "readonly": true,
+                "idlType": {
+                    "type": "attribute-type",
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "double"
+                },
+                "name": "circumference",
+                "extAttrs": []
             }
-          ],
-          "type": "extended-attribute",
-          "rhs": null
-        }
-      ]
+        ],
+        "inheritance": null,
+        "extAttrs": [
+            {
+                "name": "Constructor",
+                "arguments": null,
+                "type": "extended-attribute",
+                "rhs": null
+            },
+            {
+                "name": "Constructor",
+                "arguments": [
+                    {
+                        "optional": false,
+                        "variadic": false,
+                        "extAttrs": [],
+                        "idlType": {
+                            "type": "argument-type",
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": false,
+                            "union": false,
+                            "idlType": "double"
+                        },
+                        "name": "radius"
+                    }
+                ],
+                "type": "extended-attribute",
+                "rhs": null
+            }
+        ]
+    },
+    {
+        "type": "interface",
+        "name": "I",
+        "partial": false,
+        "members": [
+            {
+                "type": "attribute",
+                "static": false,
+                "stringifier": false,
+                "inherit": false,
+                "readonly": false,
+                "idlType": {
+                    "type": "attribute-type",
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": true,
+                    "idlType": [
+                        {
+                            "type": null,
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": false,
+                            "union": false,
+                            "idlType": "long"
+                        },
+                        {
+                            "type": null,
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": false,
+                            "union": false,
+                            "idlType": "Node"
+                        }
+                    ],
+                    "extAttrs": [
+                        {
+                            "name": "XAttr",
+                            "arguments": null,
+                            "type": "extended-attribute",
+                            "rhs": null
+                        }
+                    ]
+                },
+                "name": "attrib",
+                "extAttrs": []
+            }
+        ],
+        "inheritance": null,
+        "extAttrs": [
+            {
+                "name": "Exposed",
+                "arguments": null,
+                "type": "extended-attribute",
+                "rhs": {
+                    "type": "identifier",
+                    "value": "Window"
+                }
+            }
+        ]
     }
 ]
diff --git a/resources/webidl2/test/syntax/json/generic.json b/resources/webidl2/test/syntax/json/generic.json
index 214ddd8..6259385 100644
--- a/resources/webidl2/test/syntax/json/generic.json
+++ b/resources/webidl2/test/syntax/json/generic.json
@@ -12,21 +12,25 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": "Promise",
                     "nullable": false,
                     "union": false,
                     "idlType": {
+                        "type": "return-type",
                         "sequence": false,
                         "generic": "ResponsePromise",
                         "nullable": false,
                         "union": false,
                         "idlType": {
+                            "type": "return-type",
                             "sequence": true,
                             "generic": "sequence",
                             "nullable": false,
                             "union": false,
                             "idlType": {
+                                "type": "return-type",
                                 "sequence": false,
                                 "generic": null,
                                 "nullable": true,
@@ -47,11 +51,13 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": "Promise",
                     "nullable": false,
                     "union": false,
                     "idlType": {
+                        "type": "attribute-type",
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -79,11 +85,13 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": "Promise",
                     "nullable": false,
                     "union": false,
                     "idlType": {
+                        "type": "return-type",
                         "sequence": false,
                         "generic": null,
                         "nullable": true,
@@ -103,11 +111,13 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": "Promise",
                     "nullable": false,
                     "union": false,
                     "idlType": {
+                        "type": "return-type",
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -136,11 +146,13 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": "ResponsePromise",
                     "nullable": false,
                     "union": false,
                     "idlType": {
+                        "type": "return-type",
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/getter-setter.json b/resources/webidl2/test/syntax/json/getter-setter.json
index 6268a53..1213307 100644
--- a/resources/webidl2/test/syntax/json/getter-setter.json
+++ b/resources/webidl2/test/syntax/json/getter-setter.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -28,6 +29,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +43,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -60,6 +63,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -73,6 +77,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -86,6 +91,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/identifier-qualified-names.json b/resources/webidl2/test/syntax/json/identifier-qualified-names.json
index 11568f8..f4e295a 100644
--- a/resources/webidl2/test/syntax/json/identifier-qualified-names.json
+++ b/resources/webidl2/test/syntax/json/identifier-qualified-names.json
@@ -2,6 +2,7 @@
     {
         "type": "typedef",
         "idlType": {
+            "type": "typedef-type",
             "sequence": false,
             "generic": null,
             "nullable": false,
@@ -24,6 +25,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -37,6 +39,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -56,6 +59,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -69,6 +73,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -96,6 +101,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -112,6 +118,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": true,
@@ -138,6 +145,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -151,6 +159,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/implements.json b/resources/webidl2/test/syntax/json/implements.json
index fba0542..69879d9 100644
--- a/resources/webidl2/test/syntax/json/implements.json
+++ b/resources/webidl2/test/syntax/json/implements.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -37,6 +38,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -50,6 +52,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -63,6 +66,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -76,6 +80,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/indexed-properties.json b/resources/webidl2/test/syntax/json/indexed-properties.json
index 2676c0e..697b595 100644
--- a/resources/webidl2/test/syntax/json/indexed-properties.json
+++ b/resources/webidl2/test/syntax/json/indexed-properties.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -28,6 +29,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +43,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -60,6 +63,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -73,6 +77,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -86,6 +91,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -105,6 +111,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -118,6 +125,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -137,6 +145,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -150,6 +159,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -169,6 +179,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -182,6 +193,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -195,6 +207,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -214,6 +227,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -227,6 +241,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/inherits-getter.json b/resources/webidl2/test/syntax/json/inherits-getter.json
index ba6bafd..818d8b6 100644
--- a/resources/webidl2/test/syntax/json/inherits-getter.json
+++ b/resources/webidl2/test/syntax/json/inherits-getter.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -36,6 +37,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -52,6 +54,7 @@
                 "inherit": true,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -65,4 +68,4 @@
         "inheritance": "Animal",
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/interface-inherits.json b/resources/webidl2/test/syntax/json/interface-inherits.json
index e78c1cc..74a4c39 100644
--- a/resources/webidl2/test/syntax/json/interface-inherits.json
+++ b/resources/webidl2/test/syntax/json/interface-inherits.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -36,6 +37,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -61,6 +63,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -74,4 +77,4 @@
         "inheritance": "Animal",
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/iterable.json b/resources/webidl2/test/syntax/json/iterable.json
index 1e92307..7126a4e 100644
--- a/resources/webidl2/test/syntax/json/iterable.json
+++ b/resources/webidl2/test/syntax/json/iterable.json
@@ -6,13 +6,16 @@
         "members": [
             {
                 "type": "iterable",
-                "idlType": {
-                    "sequence": false,
-                    "generic": null,
-                    "nullable": false,
-                    "union": false,
-                    "idlType": "long"
-                },
+                "idlType": [
+                    {
+                        "type": null,
+                        "sequence": false,
+                        "generic": null,
+                        "nullable": false,
+                        "union": false,
+                        "idlType": "long"
+                    }
+                ],
                 "extAttrs": []
             }
         ],
@@ -28,6 +31,7 @@
                 "type": "iterable",
                 "idlType": [
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -35,6 +39,7 @@
                         "idlType": "short"
                     },
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": true,
@@ -55,21 +60,24 @@
         "members": [
             {
                 "type": "iterable",
-                "idlType": {
-                    "sequence": false,
-                    "generic": null,
-                    "nullable": false,
-                    "union": false,
-                    "idlType": "long",
-                    "extAttrs": [
-                        {
-                            "name": "XAttr",
-                            "arguments": null,
-                            "type": "extended-attribute",
-                            "rhs": null
-                        }
-                    ]
-                },
+                "idlType": [
+                    {
+                        "type": null,
+                        "sequence": false,
+                        "generic": null,
+                        "nullable": false,
+                        "union": false,
+                        "idlType": "long",
+                        "extAttrs": [
+                            {
+                                "name": "XAttr",
+                                "arguments": null,
+                                "type": "extended-attribute",
+                                "rhs": null
+                            }
+                        ]
+                    }
+                ],
                 "extAttrs": []
             }
         ],
@@ -77,4 +85,3 @@
         "extAttrs": []
     }
 ]
-
diff --git a/resources/webidl2/test/syntax/json/legacyiterable.json b/resources/webidl2/test/syntax/json/legacyiterable.json
index 059ccc1..5a1e526 100644
--- a/resources/webidl2/test/syntax/json/legacyiterable.json
+++ b/resources/webidl2/test/syntax/json/legacyiterable.json
@@ -6,13 +6,16 @@
         "members": [
             {
                 "type": "legacyiterable",
-                "idlType": {
-                    "sequence": false,
-                    "generic": null,
-                    "nullable": false,
-                    "union": false,
-                    "idlType": "long"
-                },
+                "idlType": [
+                    {
+                        "type": null,
+                        "sequence": false,
+                        "generic": null,
+                        "nullable": false,
+                        "union": false,
+                        "idlType": "long"
+                    }
+                ],
                 "extAttrs": []
             }
         ],
@@ -20,4 +23,3 @@
         "extAttrs": []
     }
 ]
-
diff --git a/resources/webidl2/test/syntax/json/maplike.json b/resources/webidl2/test/syntax/json/maplike.json
index 97e76d5..b86e104 100644
--- a/resources/webidl2/test/syntax/json/maplike.json
+++ b/resources/webidl2/test/syntax/json/maplike.json
@@ -8,6 +8,7 @@
                 "type": "maplike",
                 "idlType": [
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -15,6 +16,7 @@
                         "idlType": "long"
                     },
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -38,6 +40,7 @@
                 "type": "maplike",
                 "idlType": [
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -45,6 +48,7 @@
                         "idlType": "long"
                     },
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -68,6 +72,7 @@
                 "type": "maplike",
                 "idlType": [
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -83,6 +88,7 @@
                         ]
                     },
                     {
+                        "type": null,
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -106,4 +112,3 @@
         "extAttrs": []
     }
 ]
-
diff --git a/resources/webidl2/test/syntax/json/mixin.json b/resources/webidl2/test/syntax/json/mixin.json
index a2afae3..f0458e2 100644
--- a/resources/webidl2/test/syntax/json/mixin.json
+++ b/resources/webidl2/test/syntax/json/mixin.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -47,6 +48,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/namedconstructor.json b/resources/webidl2/test/syntax/json/namedconstructor.json
index cee1630..f895461 100644
--- a/resources/webidl2/test/syntax/json/namedconstructor.json
+++ b/resources/webidl2/test/syntax/json/namedconstructor.json
@@ -23,6 +23,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -40,4 +41,4 @@
             }
         ]
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/namespace.json b/resources/webidl2/test/syntax/json/namespace.json
index 8ee7796..0611b71 100644
--- a/resources/webidl2/test/syntax/json/namespace.json
+++ b/resources/webidl2/test/syntax/json/namespace.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -28,6 +29,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +43,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -54,6 +57,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -73,6 +77,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -86,6 +91,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -99,6 +105,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/nointerfaceobject.json b/resources/webidl2/test/syntax/json/nointerfaceobject.json
index 85987e8..03bb994 100644
--- a/resources/webidl2/test/syntax/json/nointerfaceobject.json
+++ b/resources/webidl2/test/syntax/json/nointerfaceobject.json
@@ -12,6 +12,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -25,6 +26,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/nullable.json b/resources/webidl2/test/syntax/json/nullable.json
index 2c83c3d..ae4d2aa 100644
--- a/resources/webidl2/test/syntax/json/nullable.json
+++ b/resources/webidl2/test/syntax/json/nullable.json
@@ -7,7 +7,10 @@
             {
                 "type": "const",
                 "nullable": true,
-                "idlType": "boolean",
+                "idlType": {
+                    "type": "const-type",
+                    "idlType": "boolean"
+                },
                 "name": "ARE_WE_THERE_YET",
                 "value": {
                     "type": "boolean",
@@ -31,6 +34,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": true,
@@ -44,4 +48,4 @@
         "inheritance": null,
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/nullableobjects.json b/resources/webidl2/test/syntax/json/nullableobjects.json
index 2c87560..29d1314 100644
--- a/resources/webidl2/test/syntax/json/nullableobjects.json
+++ b/resources/webidl2/test/syntax/json/nullableobjects.json
@@ -28,6 +28,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +42,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": true,
@@ -60,6 +62,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -73,6 +76,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": true,
diff --git a/resources/webidl2/test/syntax/json/operation-optional-arg.json b/resources/webidl2/test/syntax/json/operation-optional-arg.json
index 7588d63..44c3a16 100644
--- a/resources/webidl2/test/syntax/json/operation-optional-arg.json
+++ b/resources/webidl2/test/syntax/json/operation-optional-arg.json
@@ -12,6 +12,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -25,6 +26,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -38,6 +40,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -51,6 +54,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -64,6 +68,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/overloading.json b/resources/webidl2/test/syntax/json/overloading.json
index 8d7e8e2..87169e9 100644
--- a/resources/webidl2/test/syntax/json/overloading.json
+++ b/resources/webidl2/test/syntax/json/overloading.json
@@ -28,6 +28,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +42,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -60,6 +62,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -73,6 +76,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -101,6 +105,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -114,6 +119,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -133,6 +139,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -153,6 +160,7 @@
                             }
                         ],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -166,6 +174,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -179,6 +188,7 @@
                         "variadic": true,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -198,6 +208,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -216,6 +227,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -229,6 +241,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -242,6 +255,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -255,6 +269,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -268,6 +283,7 @@
                         "variadic": true,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/overridebuiltins.json b/resources/webidl2/test/syntax/json/overridebuiltins.json
index 4ded17c..d63f8f7 100644
--- a/resources/webidl2/test/syntax/json/overridebuiltins.json
+++ b/resources/webidl2/test/syntax/json/overridebuiltins.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -28,6 +29,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +43,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/partial-interface.json b/resources/webidl2/test/syntax/json/partial-interface.json
index e95af2b..c16c64d 100644
--- a/resources/webidl2/test/syntax/json/partial-interface.json
+++ b/resources/webidl2/test/syntax/json/partial-interface.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -36,6 +37,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -48,4 +50,4 @@
         ],
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/primitives.json b/resources/webidl2/test/syntax/json/primitives.json
index 7568542..cf96539 100644
--- a/resources/webidl2/test/syntax/json/primitives.json
+++ b/resources/webidl2/test/syntax/json/primitives.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -43,6 +45,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -59,6 +62,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -75,6 +79,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -91,6 +96,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -107,6 +113,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -123,6 +130,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -139,6 +147,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -155,6 +164,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -171,6 +181,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -187,6 +198,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -203,6 +215,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -219,6 +232,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -235,6 +249,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -251,6 +266,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -267,6 +283,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -280,4 +297,4 @@
         "inheritance": null,
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/prototyperoot.json b/resources/webidl2/test/syntax/json/prototyperoot.json
index c75f00e..eda7f14 100644
--- a/resources/webidl2/test/syntax/json/prototyperoot.json
+++ b/resources/webidl2/test/syntax/json/prototyperoot.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/putforwards.json b/resources/webidl2/test/syntax/json/putforwards.json
index 1b0a7b6..4b809d2 100644
--- a/resources/webidl2/test/syntax/json/putforwards.json
+++ b/resources/webidl2/test/syntax/json/putforwards.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -37,6 +38,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -50,4 +52,4 @@
         "inheritance": null,
         "extAttrs": []
     }
-]
\ No newline at end of file
+]
diff --git a/resources/webidl2/test/syntax/json/record.json b/resources/webidl2/test/syntax/json/record.json
index 339ae08..dd00b5d 100644
--- a/resources/webidl2/test/syntax/json/record.json
+++ b/resources/webidl2/test/syntax/json/record.json
@@ -12,6 +12,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -25,17 +26,20 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": true,
                             "generic": "sequence",
                             "nullable": false,
                             "union": false,
                             "idlType": {
+                                "type": "argument-type",
                                 "sequence": false,
                                 "generic": "record",
                                 "nullable": false,
                                 "union": false,
                                 "idlType": [
                                     {
+                                        "type": "argument-type",
                                         "sequence": false,
                                         "generic": null,
                                         "nullable": false,
@@ -43,6 +47,7 @@
                                         "idlType": "ByteString"
                                     },
                                     {
+                                        "type": "argument-type",
                                         "sequence": false,
                                         "generic": null,
                                         "nullable": false,
@@ -65,12 +70,14 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": "record",
                     "nullable": false,
                     "union": false,
                     "idlType": [
                         {
+                            "type": "return-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -78,12 +85,14 @@
                             "idlType": "DOMString"
                         },
                         {
+                            "type": "return-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": true,
                             "union": true,
                             "idlType": [
                                 {
+                                    "type": null,
                                     "sequence": false,
                                     "generic": null,
                                     "nullable": false,
@@ -91,6 +100,7 @@
                                     "idlType": "float"
                                 },
                                 {
+                                    "type": null,
                                     "sequence": false,
                                     "generic": null,
                                     "nullable": false,
@@ -113,6 +123,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -134,12 +145,14 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": "record",
                             "nullable": false,
                             "union": false,
                             "idlType": [
                                 {
+                                    "type": "argument-type",
                                     "sequence": false,
                                     "generic": null,
                                     "nullable": false,
@@ -147,6 +160,7 @@
                                     "idlType": "USVString"
                                 },
                                 {
+                                    "type": "argument-type",
                                     "sequence": false,
                                     "generic": null,
                                     "nullable": false,
@@ -176,12 +190,14 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": "record",
                     "nullable": false,
                     "union": false,
                     "idlType": [
                         {
+                            "type": "return-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -189,6 +205,7 @@
                             "idlType": "DOMString"
                         },
                         {
+                            "type": "return-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/reg-operations.json b/resources/webidl2/test/syntax/json/reg-operations.json
index c1c13c7..d696e90 100644
--- a/resources/webidl2/test/syntax/json/reg-operations.json
+++ b/resources/webidl2/test/syntax/json/reg-operations.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -53,6 +55,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -71,6 +74,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -84,6 +88,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -103,6 +108,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -116,6 +122,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -129,6 +136,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/replaceable.json b/resources/webidl2/test/syntax/json/replaceable.json
index f53009b..a10b0bf 100644
--- a/resources/webidl2/test/syntax/json/replaceable.json
+++ b/resources/webidl2/test/syntax/json/replaceable.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -35,6 +36,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/sequence.json b/resources/webidl2/test/syntax/json/sequence.json
index 7982118..c2d1765 100644
--- a/resources/webidl2/test/syntax/json/sequence.json
+++ b/resources/webidl2/test/syntax/json/sequence.json
@@ -12,6 +12,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -25,11 +26,13 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": true,
                             "generic": "sequence",
                             "nullable": false,
                             "union": false,
                             "idlType": {
+                                "type": "argument-type",
                                 "sequence": false,
                                 "generic": null,
                                 "nullable": false,
@@ -50,11 +53,13 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": true,
                     "generic": "sequence",
                     "nullable": false,
                     "union": false,
                     "idlType": {
+                        "type": "return-type",
                         "sequence": false,
                         "generic": null,
                         "nullable": false,
@@ -75,7 +80,6 @@
         "name": "Foo",
         "partial": false,
         "members": [
-
             {
                 "type": "operation",
                 "getter": false,
@@ -84,6 +88,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -98,7 +103,6 @@
         "inheritance": null,
         "extAttrs": []
     },
-
     {
         "type": "interface",
         "name": "I",
@@ -112,6 +116,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -125,11 +130,13 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": true,
                             "generic": "sequence",
                             "nullable": false,
                             "union": false,
                             "idlType": {
+                                "type": "argument-type",
                                 "sequence": false,
                                 "generic": null,
                                 "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/setlike.json b/resources/webidl2/test/syntax/json/setlike.json
index 3c1591b..12299a7 100644
--- a/resources/webidl2/test/syntax/json/setlike.json
+++ b/resources/webidl2/test/syntax/json/setlike.json
@@ -6,13 +6,16 @@
         "members": [
             {
                 "type": "setlike",
-                "idlType": {
-                    "sequence": false,
-                    "generic": null,
-                    "nullable": false,
-                    "union": false,
-                    "idlType": "long"
-                },
+                "idlType": [
+                    {
+                        "type": null,
+                        "sequence": false,
+                        "generic": null,
+                        "nullable": false,
+                        "union": false,
+                        "idlType": "long"
+                    }
+                ],
                 "readonly": false,
                 "extAttrs": []
             }
@@ -27,13 +30,16 @@
         "members": [
             {
                 "type": "setlike",
-                "idlType": {
-                    "sequence": false,
-                    "generic": null,
-                    "nullable": false,
-                    "union": false,
-                    "idlType": "long"
-                },
+                "idlType": [
+                    {
+                        "type": null,
+                        "sequence": false,
+                        "generic": null,
+                        "nullable": false,
+                        "union": false,
+                        "idlType": "long"
+                    }
+                ],
                 "readonly": true,
                 "extAttrs": []
             }
@@ -48,21 +54,24 @@
         "members": [
             {
                 "type": "setlike",
-                "idlType": {
-                    "sequence": false,
-                    "generic": null,
-                    "nullable": false,
-                    "union": false,
-                    "idlType": "long",
-                    "extAttrs": [
-                        {
-                            "name": "XAttr",
-                            "arguments": null,
-                            "type": "extended-attribute",
-                            "rhs": null
-                        }
-                    ]
-                },
+                "idlType": [
+                    {
+                        "type": null,
+                        "sequence": false,
+                        "generic": null,
+                        "nullable": false,
+                        "union": false,
+                        "idlType": "long",
+                        "extAttrs": [
+                            {
+                                "name": "XAttr",
+                                "arguments": null,
+                                "type": "extended-attribute",
+                                "rhs": null
+                            }
+                        ]
+                    }
+                ],
                 "readonly": false,
                 "extAttrs": []
             }
@@ -71,4 +80,3 @@
         "extAttrs": []
     }
 ]
-
diff --git a/resources/webidl2/test/syntax/json/static.json b/resources/webidl2/test/syntax/json/static.json
index 034ffda..0951b2a 100644
--- a/resources/webidl2/test/syntax/json/static.json
+++ b/resources/webidl2/test/syntax/json/static.json
@@ -19,6 +19,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -35,6 +36,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -51,6 +53,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -67,6 +70,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -84,6 +88,7 @@
                 "static": true,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -97,6 +102,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -110,6 +116,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -123,6 +130,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/stringifier-attribute.json b/resources/webidl2/test/syntax/json/stringifier-attribute.json
index acb26c2..36e2b6d 100644
--- a/resources/webidl2/test/syntax/json/stringifier-attribute.json
+++ b/resources/webidl2/test/syntax/json/stringifier-attribute.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/stringifier-custom.json b/resources/webidl2/test/syntax/json/stringifier-custom.json
index 3c84305..3dc3ac1 100644
--- a/resources/webidl2/test/syntax/json/stringifier-custom.json
+++ b/resources/webidl2/test/syntax/json/stringifier-custom.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": true,
@@ -43,6 +45,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -60,6 +63,7 @@
                 "static": false,
                 "stringifier": true,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/stringifier.json b/resources/webidl2/test/syntax/json/stringifier.json
index 6b2d133..1a70276 100644
--- a/resources/webidl2/test/syntax/json/stringifier.json
+++ b/resources/webidl2/test/syntax/json/stringifier.json
@@ -12,6 +12,7 @@
                 "static": false,
                 "stringifier": true,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/treatasnull.json b/resources/webidl2/test/syntax/json/treatasnull.json
index 03dec60..611d974 100644
--- a/resources/webidl2/test/syntax/json/treatasnull.json
+++ b/resources/webidl2/test/syntax/json/treatasnull.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -44,6 +46,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -67,6 +70,7 @@
                             }
                         ],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/treatasundefined.json b/resources/webidl2/test/syntax/json/treatasundefined.json
index 8699b73..258acda 100644
--- a/resources/webidl2/test/syntax/json/treatasundefined.json
+++ b/resources/webidl2/test/syntax/json/treatasundefined.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -44,6 +46,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -67,6 +70,7 @@
                             }
                         ],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/typedef-union.json b/resources/webidl2/test/syntax/json/typedef-union.json
index 9c87672..06735a8 100644
--- a/resources/webidl2/test/syntax/json/typedef-union.json
+++ b/resources/webidl2/test/syntax/json/typedef-union.json
@@ -1,49 +1,48 @@
 [
-   {
-      "type" : "typedef",
-      "idlType" : {
-         "nullable" : false,
-         "generic" : null,
-         "union" : true,
-         "idlType" : [
-            {
-               "union" : false,
-               "generic" : null,
-               "nullable" : false,
-               "array" : false,
-               "sequence" : false,
-               "idlType" : "ImageData"
-            },
-            {
-               "generic" : null,
-               "union" : false,
-               "nullable" : false,
-               "array" : false,
-               "idlType" : "HTMLImageElement",
-               "sequence" : false
-            },
-            {
-               "array" : false,
-               "sequence" : false,
-               "idlType" : "HTMLCanvasElement",
-               "generic" : null,
-               "union" : false,
-               "nullable" : false
-            },
-            {
-               "union" : false,
-               "generic" : null,
-               "nullable" : false,
-               "array" : false,
-               "sequence" : false,
-               "idlType" : "HTMLVideoElement"
-            }
-         ],
-         "sequence" : false,
-         "array" : false
-      },
-      "name" : "TexImageSource",
-      "extAttrs" : [],
-      "typeExtAttrs" : []
-   }
+    {
+        "type": "typedef",
+        "idlType": {
+            "type": "typedef-type",
+            "sequence": false,
+            "generic": null,
+            "nullable": false,
+            "union": true,
+            "idlType": [
+                {
+                    "type": null,
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "ImageData"
+                },
+                {
+                    "type": null,
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "HTMLImageElement"
+                },
+                {
+                    "type": null,
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "HTMLCanvasElement"
+                },
+                {
+                    "type": null,
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": false,
+                    "idlType": "HTMLVideoElement"
+                }
+            ]
+        },
+        "name": "TexImageSource",
+        "extAttrs": []
+    }
 ]
diff --git a/resources/webidl2/test/syntax/json/typedef.json b/resources/webidl2/test/syntax/json/typedef.json
index 612cfe7..5e9715d 100644
--- a/resources/webidl2/test/syntax/json/typedef.json
+++ b/resources/webidl2/test/syntax/json/typedef.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -27,6 +28,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -43,11 +45,13 @@
     {
         "type": "typedef",
         "idlType": {
+            "type": "typedef-type",
             "sequence": true,
             "generic": "sequence",
             "nullable": false,
             "union": false,
             "idlType": {
+                "type": "typedef-type",
                 "sequence": false,
                 "generic": null,
                 "nullable": false,
@@ -70,6 +74,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -86,6 +91,7 @@
                 "inherit": false,
                 "readonly": false,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -111,6 +117,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -128,6 +135,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -141,6 +149,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -160,6 +169,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -173,6 +183,7 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -191,6 +202,7 @@
     {
         "type": "typedef",
         "idlType": {
+            "type": "typedef-type",
             "sequence": false,
             "generic": null,
             "nullable": false,
diff --git a/resources/webidl2/test/syntax/json/typesuffixes.json b/resources/webidl2/test/syntax/json/typesuffixes.json
index 0b30830..be0b1f2 100644
--- a/resources/webidl2/test/syntax/json/typesuffixes.json
+++ b/resources/webidl2/test/syntax/json/typesuffixes.json
@@ -12,6 +12,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -25,11 +26,13 @@
                         "variadic": false,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": true,
                             "generic": "sequence",
                             "nullable": true,
                             "union": false,
                             "idlType": {
+                                "type": "argument-type",
                                 "sequence": false,
                                 "generic": null,
                                 "nullable": true,
diff --git a/resources/webidl2/test/syntax/json/uniontype.json b/resources/webidl2/test/syntax/json/uniontype.json
index c4725d4..87735c7 100644
--- a/resources/webidl2/test/syntax/json/uniontype.json
+++ b/resources/webidl2/test/syntax/json/uniontype.json
@@ -1,118 +1,129 @@
 [
-   {
-      "partial": false,
-      "members": [
-         {
-            "idlType": {
-               "idlType": [
-                  {
-                     "union": false,
-                     "sequence": false,
-                     "generic": null,
-                     "idlType": "float",
-                     "nullable": false
-                  },
-                  {
-                     "idlType": [
+    {
+        "type": "interface",
+        "name": "Union",
+        "partial": false,
+        "members": [
+            {
+                "type": "attribute",
+                "static": false,
+                "stringifier": false,
+                "inherit": false,
+                "readonly": false,
+                "idlType": {
+                    "type": "attribute-type",
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": true,
+                    "idlType": [
                         {
-                           "nullable": false,
-                           "idlType": "Date",
-                           "sequence": false,
-                           "generic": null,
-                           "union": false
+                            "type": null,
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": false,
+                            "union": false,
+                            "idlType": "float"
                         },
                         {
-                           "nullable": false,
-                           "idlType": "Event",
-                           "generic": null,
-                           "sequence": false,
-                           "union": false
-                        }
-                     ],
-                     "nullable": false,
-                     "sequence": false,
-                     "generic": null,
-                     "union": true
-                  },
-                  {
-                     "generic": null,
-                     "sequence": false,
-                     "idlType": [
-                        {
-                           "union": false,
-                           "sequence": false,
-                           "generic": null,
-                           "nullable": false,
-                           "idlType": "Node"
+                            "type": null,
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": false,
+                            "union": true,
+                            "idlType": [
+                                {
+                                    "type": null,
+                                    "sequence": false,
+                                    "generic": null,
+                                    "nullable": false,
+                                    "union": false,
+                                    "idlType": "Date"
+                                },
+                                {
+                                    "type": null,
+                                    "sequence": false,
+                                    "generic": null,
+                                    "nullable": false,
+                                    "union": false,
+                                    "idlType": "Event"
+                                }
+                            ]
                         },
                         {
-                           "nullable": false,
-                           "idlType": "DOMString",
-                           "sequence": false,
-                           "generic": null,
-                           "union": false
+                            "type": null,
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": true,
+                            "union": true,
+                            "idlType": [
+                                {
+                                    "type": null,
+                                    "sequence": false,
+                                    "generic": null,
+                                    "nullable": false,
+                                    "union": false,
+                                    "idlType": "Node"
+                                },
+                                {
+                                    "type": null,
+                                    "sequence": false,
+                                    "generic": null,
+                                    "nullable": false,
+                                    "union": false,
+                                    "idlType": "DOMString"
+                                }
+                            ]
                         }
-                     ],
-                     "nullable": true,
-                     "union": true
-                  }
-               ],
-               "nullable": false,
-               "generic": null,
-               "sequence": false,
-               "union": true
+                    ]
+                },
+                "name": "test",
+                "extAttrs": []
             },
-            "name": "test",
-            "inherit": false,
-            "type": "attribute",
-            "extAttrs": [],
-            "readonly": false,
-            "stringifier": false,
-            "static": false
-         },
-         {
-            "readonly": false,
-            "extAttrs": [],
-            "stringifier": false,
-            "static": false,
-            "name": "test2",
-            "idlType": {
-               "nullable": false,
-               "idlType": [
-                  {
-                     "extAttrs": [
+            {
+                "type": "attribute",
+                "static": false,
+                "stringifier": false,
+                "inherit": false,
+                "readonly": false,
+                "idlType": {
+                    "type": "attribute-type",
+                    "sequence": false,
+                    "generic": null,
+                    "nullable": false,
+                    "union": true,
+                    "idlType": [
                         {
-                           "name": "EnforceRange",
-                           "arguments": null,
-                           "type": "extended-attribute",
-                           "rhs": null
+                            "type": null,
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": false,
+                            "union": false,
+                            "idlType": "long",
+                            "extAttrs": [
+                                {
+                                    "name": "EnforceRange",
+                                    "arguments": null,
+                                    "type": "extended-attribute",
+                                    "rhs": null
+                                }
+                            ]
+                        },
+                        {
+                            "type": null,
+                            "sequence": false,
+                            "generic": null,
+                            "nullable": false,
+                            "union": false,
+                            "idlType": "Date"
                         }
-                     ],
-                     "nullable": false,
-                     "idlType": "long",
-                     "generic": null,
-                     "sequence": false,
-                     "union": false
-                  },
-                  {
-                     "union": false,
-                     "sequence": false,
-                     "generic": null,
-                     "idlType": "Date",
-                     "nullable": false
-                  }
-               ],
-               "generic": null,
-               "sequence": false,
-               "union": true
-            },
-            "inherit": false,
-            "type": "attribute"
-         }
-      ],
-      "inheritance": null,
-      "name": "Union",
-      "extAttrs": [],
-      "type": "interface"
-   }
+                    ]
+                },
+                "name": "test2",
+                "extAttrs": []
+            }
+        ],
+        "inheritance": null,
+        "extAttrs": []
+    }
 ]
diff --git a/resources/webidl2/test/syntax/json/variadic-operations.json b/resources/webidl2/test/syntax/json/variadic-operations.json
index a9560da..53b02df 100644
--- a/resources/webidl2/test/syntax/json/variadic-operations.json
+++ b/resources/webidl2/test/syntax/json/variadic-operations.json
@@ -11,6 +11,7 @@
                 "inherit": false,
                 "readonly": true,
                 "idlType": {
+                    "type": "attribute-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -28,6 +29,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -41,6 +43,7 @@
                         "variadic": true,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
@@ -60,6 +63,7 @@
                 "static": false,
                 "stringifier": false,
                 "idlType": {
+                    "type": "return-type",
                     "sequence": false,
                     "generic": null,
                     "nullable": false,
@@ -73,6 +77,7 @@
                         "variadic": true,
                         "extAttrs": [],
                         "idlType": {
+                            "type": "argument-type",
                             "sequence": false,
                             "generic": null,
                             "nullable": false,
diff --git a/resources/webidl2/test/util/acquire.js b/resources/webidl2/test/util/acquire.js
new file mode 100644
index 0000000..6f37dd6
--- /dev/null
+++ b/resources/webidl2/test/util/acquire.js
@@ -0,0 +1,8 @@
+"use strict";
+
+const { collect } = require("./collect");
+const fs = require("fs");
+
+for (const test of collect("syntax")) {
+  fs.writeFileSync(test.jsonPath, `${JSON.stringify(test.ast, null, 4)}\n`)
+}
diff --git a/resources/webidl2/test/util/collect.js b/resources/webidl2/test/util/collect.js
new file mode 100644
index 0000000..7e3d9d3
--- /dev/null
+++ b/resources/webidl2/test/util/collect.js
@@ -0,0 +1,59 @@
+"use strict";
+
+const wp = require("../../lib/webidl2");
+const pth = require("path");
+const fs = require("fs");
+const jdp = require("jsondiffpatch");
+
+/**
+ * Collects test items from the specified directory
+ * @param {string} base
+ */
+function* collect(base, { expectError } = {}) {
+  base = pth.join(__dirname, "..", base);
+  const dir = pth.join(base, "idl");
+  const idls = fs.readdirSync(dir)
+    .filter(it => (/\.widl$/).test(it))
+    .map(it => pth.join(dir, it));
+
+  for (const path of idls) {
+    const optFile = pth.join(base, "opt", pth.basename(path)).replace(".widl", ".json");
+    let opt;
+    if (fs.existsSync(optFile))
+      opt = JSON.parse(fs.readFileSync(optFile, "utf8"));
+
+    try {
+      const ast = wp.parse(fs.readFileSync(path, "utf8").replace(/\r\n/g, "\n"), opt);
+      yield new TestItem({ ast, path, opt });
+    }
+    catch (error) {
+      if (expectError) {
+        yield new TestItem({ path, error });
+      }
+      else {
+        throw error;
+      }
+    }
+  }
+};
+
+
+class TestItem {
+  constructor({ ast, path, error, opt }) {
+    this.ast = ast;
+    this.path = path;
+    this.error = error;
+    this.opt = opt;
+    this.jsonPath = pth.join(pth.dirname(path), "../json", pth.basename(path).replace(".widl", ".json"));
+  }
+
+  readJSON() {
+    return JSON.parse(fs.readFileSync(this.jsonPath, "utf8"));
+  }
+
+  diff(target = this.readJSON()) {
+    return jdp.diff(target, this.ast);
+  }
+}
+
+module.exports.collect = collect;
diff --git a/resources/webidl2/test/writer.js b/resources/webidl2/test/writer.js
new file mode 100644
index 0000000..e84076b
--- /dev/null
+++ b/resources/webidl2/test/writer.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const { collect } = require("./util/collect");
+const wp = require("../lib/webidl2");
+const writer = require("../lib/writer");
+const expect = require("expect");
+const debug = true;
+
+describe("Rewrite and parses all of the IDLs to produce the same ASTs", () => {
+  for (const test of collect("syntax")) {
+    it(`should produce the same AST for ${test.path}`, () => {
+      try {
+        const diff = test.diff(wp.parse(writer.write(test.ast), test.opt));
+        if (diff && debug) console.log(JSON.stringify(diff, null, 4));
+        expect(diff).toBe(undefined);
+      }
+      catch (e) {
+        console.log(e.toString());
+        throw e;
+      }
+    });
+  }
+});
diff --git a/screen-orientation/interfaces.html b/screen-orientation/interfaces.html
new file mode 100644
index 0000000..26d46ae
--- /dev/null
+++ b/screen-orientation/interfaces.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Screen Orientation API IDL tests</title>
+<link rel="help" href="https://w3c.github.io/screen-orientation/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+"use strict";
+
+promise_test(async () => {
+  const idl_array = new IdlArray();
+  const dom_idl = await fetch("/interfaces/dom.idl").then(r => r.text());
+  const screenorientation_idl = await fetch("/interfaces/screen-orientation.idl").then(r => r.text());
+
+  idl_array.add_untested_idls(dom_idl);
+  idl_array.add_untested_idls('interface EventHandler {};');
+  idl_array.add_untested_idls('interface Screen {};');
+  idl_array.add_idls(screenorientation_idl);
+
+  idl_array.add_objects({
+    Screen: ['screen'],
+    ScreenOrientation: ['screen.orientation']
+  });
+  idl_array.test();
+}, "Test IDL implementation of Screen Orientation API");
+</script>
diff --git a/screen-orientation/lock-bad-argument.html b/screen-orientation/lock-bad-argument.html
index df27ac2..222b9ab 100644
--- a/screen-orientation/lock-bad-argument.html
+++ b/screen-orientation/lock-bad-argument.html
@@ -4,69 +4,24 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
+promise_test(t => {
+  const invalid_lock_types = [
+    "invalid-orientation",
+    null,
+    undefined,
+    123,
+    window,
+    ["portrait-primary", "landscape-primary"],
+  ];
+  const promisesToReject = invalid_lock_types.map(type =>
+    promise_rejects(t, new TypeError(), screen.orientation.lock(type))
+  );
+  return Promise.all(promisesToReject);
+}, "screen.orientation.lock() must throw given invalid input.");
 
-var test = async_test("Test that screen.orientation.lock() throws when the input isn't valid.");
-
-function onOrientationChangeEvent(ev) {
-    assert_unreached('Unexpected orientation change');
-}
-
-window.screen.orientation.addEventListener('change', test.step_func(onOrientationChangeEvent));
-
-test.step(function() {
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock('invalid-orientation');
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock(null);
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock(undefined);
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock(undefined);
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock(123);
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock(window);
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock(['portrait-primary']);
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock(['portrait-primary', 'landscape-primary']);
-    });
-
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    assert_throws(new TypeError(), function() {
-        screen.orientation.lock();
-    });
-});
-
-// Finish asynchronously to give events a chance to fire.
-setTimeout(test.step_func(function() {
-    assert_equals(screen.orientation.type, 'portrait-primary');
-    screen.orientation.unlock();
-    test.done();
-}));
-
+promise_test(t => {
+  return promise_rejects(t, new TypeError(), screen.orientation.lock());
+}, "screen.orientation.lock() must throw when the input is missing.");
 </script>
 </body>
 </html>
diff --git a/screen-orientation/orientation-api.html b/screen-orientation/orientation-api.html
deleted file mode 100644
index 80910a3..0000000
--- a/screen-orientation/orientation-api.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-
-test(function() {
-    assert_true('orientation' in window.screen);
-    assert_true('angle' in window.screen.orientation);
-    assert_true('type' in window.screen.orientation);
-    assert_true('lock' in window.screen.orientation);
-    assert_true('unlock' in window.screen.orientation);
-    assert_true('onchange' in window.screen.orientation);
-}, "Test that the Screen Orientation API is present.")
-
-test(function() {
-  assert_equals(typeof(screen.orientation), "object");
-  assert_equals(typeof(screen.orientation.angle), "number");
-  assert_equals(typeof(screen.orientation.type), "string");
-  assert_equals(typeof(screen.orientation.lock), "function");
-  assert_equals(typeof(screen.orientation.unlock), "function");
-  assert_equals(typeof(screen.orientation.onchange), "object");
-}, "Test Screen Orientation API property types.");
-
-test(function() {
-  assert_true('addEventListener' in screen.orientation);
-  assert_true('removeEventListener' in screen.orientation);
-  assert_true('dispatchEvent' in screen.orientation);
-  assert_true(screen.orientation instanceof EventTarget)
-}, "Test that screen.orientation is an EventTarget.");
-
-</script>
-</body>
-</html>
diff --git a/screen-orientation/page-visibility-manual.html b/screen-orientation/page-visibility-manual.html
index 95633ba..cc63279 100644
--- a/screen-orientation/page-visibility-manual.html
+++ b/screen-orientation/page-visibility-manual.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <meta name='flags' content='interact'>
-<meta name="timeout" content="long">
 <p>Switch the page to background, then switch back in a minute.</p>
 <iframe src='about:blank'></iframe>
 <script>
diff --git a/screen-orientation/resources/sandboxed-iframe-locking.html b/screen-orientation/resources/sandboxed-iframe-locking.html
index 74dac82..48f408b 100644
--- a/screen-orientation/resources/sandboxed-iframe-locking.html
+++ b/screen-orientation/resources/sandboxed-iframe-locking.html
@@ -6,13 +6,8 @@
         msg = screen.orientation.type;
     }, function(error) {
         msg = error.name;
-    });
-
-    // FIXME: for the moment, tests gets notified when there is a failure but
-    // not a success, this hack should allow us to still have the test running.
-    // That should be removed as soon as we get a resolved promise in tests.
-    setTimeout(function() {
+    }).then(function() {
         parent.window.postMessage(msg, "*");
-    })
+    });
 </script>
 </html>
diff --git a/secure-contexts/OWNERS b/secure-contexts/OWNERS
new file mode 100644
index 0000000..12f907c
--- /dev/null
+++ b/secure-contexts/OWNERS
@@ -0,0 +1 @@
+@mikewest
diff --git a/selection/interfaces.html b/selection/interfaces.html
index 21b3f3e..6b72f28 100644
--- a/selection/interfaces.html
+++ b/selection/interfaces.html
@@ -5,58 +5,14 @@
 <script src=/resources/testharnessreport.js></script>
 <script src=/resources/WebIDLParser.js></script>
 <script src=/resources/idlharness.js></script>
-<script type=text/plain>
-// http://w3c.github.io/selection-api/#selection-interface
-interface Selection {
-    readonly attribute Node?         anchorNode;
-    readonly attribute unsigned long anchorOffset;
-    readonly attribute Node?         focusNode;
-    readonly attribute unsigned long focusOffset;
-    readonly attribute boolean       isCollapsed;
-    readonly attribute unsigned long rangeCount;
-    readonly attribute DOMString     type;
-    Range     getRangeAt(unsigned long index);
-    void      addRange(Range range);
-    void      removeRange(Range range);
-    void      removeAllRanges();
-    void      empty();
-    void      collapse(Node? node, optional unsigned long offset = 0);
-    void      setPosition(Node? node, optional unsigned long offset = 0);
-    void      collapseToStart();
-    void      collapseToEnd();
-    void      extend(Node node, optional unsigned long offset = 0);
-    void      setBaseAndExtent(Node anchorNode,
-                               unsigned long anchorOffset,
-                               Node focusNode,
-                               unsigned long focusOffset);
-    void      selectAllChildren(Node node);
-    [CEReactions]
-    void      deleteFromDocument();
-    boolean   containsNode(Node node,
-                           optional boolean allowPartialContainment = false);
-    stringifier DOMString ();
-};
-
-partial interface Document {
-    Selection? getSelection();
-};
-
-partial interface Window {
-    Selection? getSelection();
-};
-
-partial interface GlobalEventHandlers {
-    attribute EventHandler onselectstart;
-    attribute EventHandler onselectionchange;
-};
-</script>
 <script>
 "use strict";
 
-function doTest([dom, cssom, touchevents, uievents, html]) {
+function doTest([selection, dom, cssom, touchevents, uievents, html]) {
   var idlArray = new IdlArray();
+  idlArray.add_untested_idls('interface SVGElement {};');
   idlArray.add_untested_idls(dom + cssom + touchevents + uievents + html);
-  idlArray.add_idls(document.querySelector("script[type=text\\/plain]").textContent);
+  idlArray.add_idls(selection);
   idlArray.add_objects({Selection: ['getSelection()']});
   idlArray.test();
 }
@@ -66,7 +22,8 @@
 }
 
 promise_test(function() {
-  return Promise.all([fetchData("/interfaces/dom.idl"),
+  return Promise.all([fetchData("/interfaces/selection-api.idl"),
+                      fetchData("/interfaces/dom.idl"),
                       fetchData("/interfaces/cssom.idl"),
                       fetchData("/interfaces/touchevents.idl"),
                       fetchData("/interfaces/uievents.idl"),
diff --git a/server-timing/OWNERS b/server-timing/OWNERS
new file mode 100644
index 0000000..f1882ce
--- /dev/null
+++ b/server-timing/OWNERS
@@ -0,0 +1 @@
+@igrigorik
diff --git a/server-timing/cross_origin.html b/server-timing/cross_origin.html
new file mode 100644
index 0000000..94c502e
--- /dev/null
+++ b/server-timing/cross_origin.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script src="/common/performance-timeline-utils.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script>
+      setup({explicit_done: true})
+
+      const hostInfo = get_host_info()
+      const urls = {
+        'same-origin': `${hostInfo.HTTP_ORIGIN}/server-timing/resources/blue.png`,
+        'cross-origin': `${hostInfo.HTTP_REMOTE_ORIGIN}/server-timing/resources/blue.png`,
+        'cross-origin-tao': `${hostInfo.HTTP_REMOTE_ORIGIN}/server-timing/resources/blue_tao.png`
+      }
+      Object.keys(urls).forEach(function(key) {
+        const img = document.createElement('img')
+        img.src = urls[key]
+        document.getElementsByTagName('script')[0].parentNode.appendChild(img)
+      })
+
+      window.addEventListener('load', function() {
+        function assertServerTimingEntries(url, expectedEntryCount) {
+          test_equals(performance.getEntriesByName(url)[0].serverTiming.length,
+            expectedEntryCount,
+            `Expected entry count for ${url}: ${expectedEntryCount}`)
+        }
+        assertServerTimingEntries(urls['same-origin'], 1)
+        assertServerTimingEntries(urls['cross-origin'], 0)
+        assertServerTimingEntries(urls['cross-origin-tao'], 1)
+        done()
+      })
+    </script>
+</head>
diff --git a/server-timing/navigation_timing_idl.html b/server-timing/navigation_timing_idl.html
new file mode 100644
index 0000000..290bb88
--- /dev/null
+++ b/server-timing/navigation_timing_idl.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script>
+      setup({explicit_done: true})
+      window.addEventListener('load', function(){
+        assert_not_equals(typeof performance.getEntriesByType('navigation')[0].serverTiming, 'undefined',
+          'An instance of `PerformanceNavigationTiming` should have a `serverTiming` attribute.')
+        done()
+      })
+    </script>
+</head>
diff --git a/server-timing/navigation_timing_idl.https.html b/server-timing/navigation_timing_idl.https.html
new file mode 100644
index 0000000..290bb88
--- /dev/null
+++ b/server-timing/navigation_timing_idl.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script>
+      setup({explicit_done: true})
+      window.addEventListener('load', function(){
+        assert_not_equals(typeof performance.getEntriesByType('navigation')[0].serverTiming, 'undefined',
+          'An instance of `PerformanceNavigationTiming` should have a `serverTiming` attribute.')
+        done()
+      })
+    </script>
+</head>
diff --git a/server-timing/resource_timing_idl.html b/server-timing/resource_timing_idl.html
new file mode 100644
index 0000000..d2c3c92
--- /dev/null
+++ b/server-timing/resource_timing_idl.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script>
+      setup({explicit_done: true})
+      window.addEventListener('load', function(){
+        assert_not_equals(typeof performance.getEntriesByType('resource')[0].serverTiming, 'undefined',
+          'An instance of `PerformanceResourceTiming` should have a `serverTiming` attribute.')
+        done()
+      })
+    </script>
+</head>
diff --git a/server-timing/resource_timing_idl.https.html b/server-timing/resource_timing_idl.https.html
new file mode 100644
index 0000000..d2c3c92
--- /dev/null
+++ b/server-timing/resource_timing_idl.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script>
+      setup({explicit_done: true})
+      window.addEventListener('load', function(){
+        assert_not_equals(typeof performance.getEntriesByType('resource')[0].serverTiming, 'undefined',
+          'An instance of `PerformanceResourceTiming` should have a `serverTiming` attribute.')
+        done()
+      })
+    </script>
+</head>
diff --git a/server-timing/resources/blue_tao.png b/server-timing/resources/blue_tao.png
new file mode 100644
index 0000000..4498dd2
--- /dev/null
+++ b/server-timing/resources/blue_tao.png
Binary files differ
diff --git a/server-timing/resources/blue_tao.png.sub.headers b/server-timing/resources/blue_tao.png.sub.headers
new file mode 100644
index 0000000..3ca09d6
--- /dev/null
+++ b/server-timing/resources/blue_tao.png.sub.headers
@@ -0,0 +1,2 @@
+Timing-Allow-Origin: *
+Server-Timing: metric2; dur=2.1; desc=blue.png
diff --git a/server-timing/resources/parsing/0.js b/server-timing/resources/parsing/0.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/0.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/0.js.sub.headers b/server-timing/resources/parsing/0.js.sub.headers
new file mode 100644
index 0000000..6f9cb19
--- /dev/null
+++ b/server-timing/resources/parsing/0.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: 
diff --git a/server-timing/resources/parsing/1.js b/server-timing/resources/parsing/1.js
new file mode 100644
index 0000000..03b778b
--- /dev/null
+++ b/server-timing/resources/parsing/1.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric"}])
diff --git a/server-timing/resources/parsing/1.js.sub.headers b/server-timing/resources/parsing/1.js.sub.headers
new file mode 100644
index 0000000..d5ebb5f
--- /dev/null
+++ b/server-timing/resources/parsing/1.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric
diff --git a/server-timing/resources/parsing/10.js b/server-timing/resources/parsing/10.js
new file mode 100644
index 0000000..03b778b
--- /dev/null
+++ b/server-timing/resources/parsing/10.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric"}])
diff --git a/server-timing/resources/parsing/10.js.sub.headers b/server-timing/resources/parsing/10.js.sub.headers
new file mode 100644
index 0000000..fc2300f
--- /dev/null
+++ b/server-timing/resources/parsing/10.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric ; 
diff --git a/server-timing/resources/parsing/11.js b/server-timing/resources/parsing/11.js
new file mode 100644
index 0000000..03b778b
--- /dev/null
+++ b/server-timing/resources/parsing/11.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric"}])
diff --git a/server-timing/resources/parsing/11.js.sub.headers b/server-timing/resources/parsing/11.js.sub.headers
new file mode 100644
index 0000000..d5ed699
--- /dev/null
+++ b/server-timing/resources/parsing/11.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric , 
diff --git a/server-timing/resources/parsing/12.js b/server-timing/resources/parsing/12.js
new file mode 100644
index 0000000..fc827f8
--- /dev/null
+++ b/server-timing/resources/parsing/12.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":123.4,"desc":"description"}])
diff --git a/server-timing/resources/parsing/12.js.sub.headers b/server-timing/resources/parsing/12.js.sub.headers
new file mode 100644
index 0000000..7b89b47
--- /dev/null
+++ b/server-timing/resources/parsing/12.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric ; dur = 123.4 ; desc = description
diff --git a/server-timing/resources/parsing/13.js b/server-timing/resources/parsing/13.js
new file mode 100644
index 0000000..02f8c3c
--- /dev/null
+++ b/server-timing/resources/parsing/13.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"description","dur":123.4}])
diff --git a/server-timing/resources/parsing/13.js.sub.headers b/server-timing/resources/parsing/13.js.sub.headers
new file mode 100644
index 0000000..dcb056f
--- /dev/null
+++ b/server-timing/resources/parsing/13.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric ; desc = description ; dur = 123.4
diff --git a/server-timing/resources/parsing/14.js b/server-timing/resources/parsing/14.js
new file mode 100644
index 0000000..966e963
--- /dev/null
+++ b/server-timing/resources/parsing/14.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"description"}])
diff --git a/server-timing/resources/parsing/14.js.sub.headers b/server-timing/resources/parsing/14.js.sub.headers
new file mode 100644
index 0000000..7ebf8c3
--- /dev/null
+++ b/server-timing/resources/parsing/14.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc = "description"
diff --git a/server-timing/resources/parsing/15.js b/server-timing/resources/parsing/15.js
new file mode 100644
index 0000000..afef77d
--- /dev/null
+++ b/server-timing/resources/parsing/15.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric1","dur":12.3,"desc":"description1"},{"name":"metric2","dur":45.6,"desc":"description2"},{"name":"metric3","dur":78.9,"desc":"description3"}])
diff --git a/server-timing/resources/parsing/15.js.sub.headers b/server-timing/resources/parsing/15.js.sub.headers
new file mode 100644
index 0000000..0389e99
--- /dev/null
+++ b/server-timing/resources/parsing/15.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric1;dur=12.3;desc=description1,metric2;dur=45.6;desc=description2,metric3;dur=78.9;desc=description3
diff --git a/server-timing/resources/parsing/16.js b/server-timing/resources/parsing/16.js
new file mode 100644
index 0000000..b49b178
--- /dev/null
+++ b/server-timing/resources/parsing/16.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric1"},{"name":"metric2"},{"name":"metric3"},{"name":"metric4"},{"name":"metric5"}])
diff --git a/server-timing/resources/parsing/16.js.sub.headers b/server-timing/resources/parsing/16.js.sub.headers
new file mode 100644
index 0000000..b618a89
--- /dev/null
+++ b/server-timing/resources/parsing/16.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric1,metric2 ,metric3, metric4 , metric5
diff --git a/server-timing/resources/parsing/17.js b/server-timing/resources/parsing/17.js
new file mode 100644
index 0000000..966e963
--- /dev/null
+++ b/server-timing/resources/parsing/17.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"description"}])
diff --git a/server-timing/resources/parsing/17.js.sub.headers b/server-timing/resources/parsing/17.js.sub.headers
new file mode 100644
index 0000000..c083f60
--- /dev/null
+++ b/server-timing/resources/parsing/17.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="description"
diff --git a/server-timing/resources/parsing/18.js b/server-timing/resources/parsing/18.js
new file mode 100644
index 0000000..3c47d76
--- /dev/null
+++ b/server-timing/resources/parsing/18.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"\t description \t"}])
diff --git a/server-timing/resources/parsing/18.js.sub.headers b/server-timing/resources/parsing/18.js.sub.headers
new file mode 100644
index 0000000..aa51ae0
--- /dev/null
+++ b/server-timing/resources/parsing/18.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="	 description 	"
diff --git a/server-timing/resources/parsing/19.js b/server-timing/resources/parsing/19.js
new file mode 100644
index 0000000..83fb4f3
--- /dev/null
+++ b/server-timing/resources/parsing/19.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"descr\"iption"}])
diff --git a/server-timing/resources/parsing/19.js.sub.headers b/server-timing/resources/parsing/19.js.sub.headers
new file mode 100644
index 0000000..d097b92
--- /dev/null
+++ b/server-timing/resources/parsing/19.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="descr\"iption"
diff --git a/server-timing/resources/parsing/2.js b/server-timing/resources/parsing/2.js
new file mode 100644
index 0000000..b763b81
--- /dev/null
+++ b/server-timing/resources/parsing/2.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":123.4}])
diff --git a/server-timing/resources/parsing/2.js.sub.headers b/server-timing/resources/parsing/2.js.sub.headers
new file mode 100644
index 0000000..14eb2f0
--- /dev/null
+++ b/server-timing/resources/parsing/2.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur=123.4
diff --git a/server-timing/resources/parsing/20.js b/server-timing/resources/parsing/20.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/20.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/20.js.sub.headers b/server-timing/resources/parsing/20.js.sub.headers
new file mode 100644
index 0000000..9b131fc
--- /dev/null
+++ b/server-timing/resources/parsing/20.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\
diff --git a/server-timing/resources/parsing/21.js b/server-timing/resources/parsing/21.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/21.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/21.js.sub.headers b/server-timing/resources/parsing/21.js.sub.headers
new file mode 100644
index 0000000..7134be2
--- /dev/null
+++ b/server-timing/resources/parsing/21.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="
diff --git a/server-timing/resources/parsing/22.js b/server-timing/resources/parsing/22.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/22.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/22.js.sub.headers b/server-timing/resources/parsing/22.js.sub.headers
new file mode 100644
index 0000000..465a36b
--- /dev/null
+++ b/server-timing/resources/parsing/22.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\\
diff --git a/server-timing/resources/parsing/23.js b/server-timing/resources/parsing/23.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/23.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/23.js.sub.headers b/server-timing/resources/parsing/23.js.sub.headers
new file mode 100644
index 0000000..3b76de4
--- /dev/null
+++ b/server-timing/resources/parsing/23.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\"
diff --git a/server-timing/resources/parsing/24.js b/server-timing/resources/parsing/24.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/24.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/24.js.sub.headers b/server-timing/resources/parsing/24.js.sub.headers
new file mode 100644
index 0000000..c54a4d8
--- /dev/null
+++ b/server-timing/resources/parsing/24.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="\
diff --git a/server-timing/resources/parsing/25.js b/server-timing/resources/parsing/25.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/25.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/25.js.sub.headers b/server-timing/resources/parsing/25.js.sub.headers
new file mode 100644
index 0000000..752441e
--- /dev/null
+++ b/server-timing/resources/parsing/25.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=""
diff --git a/server-timing/resources/parsing/26.js b/server-timing/resources/parsing/26.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/26.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/26.js.sub.headers b/server-timing/resources/parsing/26.js.sub.headers
new file mode 100644
index 0000000..60dbdbb
--- /dev/null
+++ b/server-timing/resources/parsing/26.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\\\
diff --git a/server-timing/resources/parsing/27.js b/server-timing/resources/parsing/27.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/27.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/27.js.sub.headers b/server-timing/resources/parsing/27.js.sub.headers
new file mode 100644
index 0000000..feff2c0
--- /dev/null
+++ b/server-timing/resources/parsing/27.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\\"
diff --git a/server-timing/resources/parsing/28.js b/server-timing/resources/parsing/28.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/28.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/28.js.sub.headers b/server-timing/resources/parsing/28.js.sub.headers
new file mode 100644
index 0000000..3ca9b6b
--- /dev/null
+++ b/server-timing/resources/parsing/28.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\"\
diff --git a/server-timing/resources/parsing/29.js b/server-timing/resources/parsing/29.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/29.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/29.js.sub.headers b/server-timing/resources/parsing/29.js.sub.headers
new file mode 100644
index 0000000..ae0b891
--- /dev/null
+++ b/server-timing/resources/parsing/29.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\""
diff --git a/server-timing/resources/parsing/3.js b/server-timing/resources/parsing/3.js
new file mode 100644
index 0000000..b763b81
--- /dev/null
+++ b/server-timing/resources/parsing/3.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":123.4}])
diff --git a/server-timing/resources/parsing/3.js.sub.headers b/server-timing/resources/parsing/3.js.sub.headers
new file mode 100644
index 0000000..8c6fb60
--- /dev/null
+++ b/server-timing/resources/parsing/3.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur="123.4"
diff --git a/server-timing/resources/parsing/30.js b/server-timing/resources/parsing/30.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/30.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/30.js.sub.headers b/server-timing/resources/parsing/30.js.sub.headers
new file mode 100644
index 0000000..24906f3
--- /dev/null
+++ b/server-timing/resources/parsing/30.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="\\
diff --git a/server-timing/resources/parsing/31.js b/server-timing/resources/parsing/31.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/31.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/31.js.sub.headers b/server-timing/resources/parsing/31.js.sub.headers
new file mode 100644
index 0000000..aaceb6b
--- /dev/null
+++ b/server-timing/resources/parsing/31.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="\"
diff --git a/server-timing/resources/parsing/32.js b/server-timing/resources/parsing/32.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/32.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/32.js.sub.headers b/server-timing/resources/parsing/32.js.sub.headers
new file mode 100644
index 0000000..59e01cf
--- /dev/null
+++ b/server-timing/resources/parsing/32.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=""\
diff --git a/server-timing/resources/parsing/33.js b/server-timing/resources/parsing/33.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/33.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/33.js.sub.headers b/server-timing/resources/parsing/33.js.sub.headers
new file mode 100644
index 0000000..d81429e
--- /dev/null
+++ b/server-timing/resources/parsing/33.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="""
diff --git a/server-timing/resources/parsing/34.js b/server-timing/resources/parsing/34.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/34.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/34.js.sub.headers b/server-timing/resources/parsing/34.js.sub.headers
new file mode 100644
index 0000000..bfb8895
--- /dev/null
+++ b/server-timing/resources/parsing/34.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\\\\
diff --git a/server-timing/resources/parsing/35.js b/server-timing/resources/parsing/35.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/35.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/35.js.sub.headers b/server-timing/resources/parsing/35.js.sub.headers
new file mode 100644
index 0000000..ff41d80
--- /dev/null
+++ b/server-timing/resources/parsing/35.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\\\"
diff --git a/server-timing/resources/parsing/36.js b/server-timing/resources/parsing/36.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/36.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/36.js.sub.headers b/server-timing/resources/parsing/36.js.sub.headers
new file mode 100644
index 0000000..a702069
--- /dev/null
+++ b/server-timing/resources/parsing/36.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\\"\
diff --git a/server-timing/resources/parsing/37.js b/server-timing/resources/parsing/37.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/37.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/37.js.sub.headers b/server-timing/resources/parsing/37.js.sub.headers
new file mode 100644
index 0000000..ecc3756
--- /dev/null
+++ b/server-timing/resources/parsing/37.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\\""
diff --git a/server-timing/resources/parsing/38.js b/server-timing/resources/parsing/38.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/38.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/38.js.sub.headers b/server-timing/resources/parsing/38.js.sub.headers
new file mode 100644
index 0000000..b13d9f4
--- /dev/null
+++ b/server-timing/resources/parsing/38.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\"\\
diff --git a/server-timing/resources/parsing/39.js b/server-timing/resources/parsing/39.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/39.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/39.js.sub.headers b/server-timing/resources/parsing/39.js.sub.headers
new file mode 100644
index 0000000..0297223
--- /dev/null
+++ b/server-timing/resources/parsing/39.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\"\"
diff --git a/server-timing/resources/parsing/4.js b/server-timing/resources/parsing/4.js
new file mode 100644
index 0000000..966e963
--- /dev/null
+++ b/server-timing/resources/parsing/4.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"description"}])
diff --git a/server-timing/resources/parsing/4.js.sub.headers b/server-timing/resources/parsing/4.js.sub.headers
new file mode 100644
index 0000000..e929723
--- /dev/null
+++ b/server-timing/resources/parsing/4.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=description
diff --git a/server-timing/resources/parsing/40.js b/server-timing/resources/parsing/40.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/40.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/40.js.sub.headers b/server-timing/resources/parsing/40.js.sub.headers
new file mode 100644
index 0000000..9dd5cd4
--- /dev/null
+++ b/server-timing/resources/parsing/40.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\""\
diff --git a/server-timing/resources/parsing/41.js b/server-timing/resources/parsing/41.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/41.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/41.js.sub.headers b/server-timing/resources/parsing/41.js.sub.headers
new file mode 100644
index 0000000..c5a006c
--- /dev/null
+++ b/server-timing/resources/parsing/41.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=\"""
diff --git a/server-timing/resources/parsing/42.js b/server-timing/resources/parsing/42.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/42.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/42.js.sub.headers b/server-timing/resources/parsing/42.js.sub.headers
new file mode 100644
index 0000000..8a04d7c
--- /dev/null
+++ b/server-timing/resources/parsing/42.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="\\\
diff --git a/server-timing/resources/parsing/43.js b/server-timing/resources/parsing/43.js
new file mode 100644
index 0000000..f3ac7dc
--- /dev/null
+++ b/server-timing/resources/parsing/43.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"\\"}])
diff --git a/server-timing/resources/parsing/43.js.sub.headers b/server-timing/resources/parsing/43.js.sub.headers
new file mode 100644
index 0000000..8208c16
--- /dev/null
+++ b/server-timing/resources/parsing/43.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="\\"
diff --git a/server-timing/resources/parsing/44.js b/server-timing/resources/parsing/44.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/44.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/44.js.sub.headers b/server-timing/resources/parsing/44.js.sub.headers
new file mode 100644
index 0000000..e50f42b
--- /dev/null
+++ b/server-timing/resources/parsing/44.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="\"\
diff --git a/server-timing/resources/parsing/45.js b/server-timing/resources/parsing/45.js
new file mode 100644
index 0000000..82de6a4
--- /dev/null
+++ b/server-timing/resources/parsing/45.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"\""}])
diff --git a/server-timing/resources/parsing/45.js.sub.headers b/server-timing/resources/parsing/45.js.sub.headers
new file mode 100644
index 0000000..055df4f
--- /dev/null
+++ b/server-timing/resources/parsing/45.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="\""
diff --git a/server-timing/resources/parsing/46.js b/server-timing/resources/parsing/46.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/46.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/46.js.sub.headers b/server-timing/resources/parsing/46.js.sub.headers
new file mode 100644
index 0000000..5d7ef80
--- /dev/null
+++ b/server-timing/resources/parsing/46.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=""\\
diff --git a/server-timing/resources/parsing/47.js b/server-timing/resources/parsing/47.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/47.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/47.js.sub.headers b/server-timing/resources/parsing/47.js.sub.headers
new file mode 100644
index 0000000..f52c6e8
--- /dev/null
+++ b/server-timing/resources/parsing/47.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=""\"
diff --git a/server-timing/resources/parsing/48.js b/server-timing/resources/parsing/48.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/48.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/48.js.sub.headers b/server-timing/resources/parsing/48.js.sub.headers
new file mode 100644
index 0000000..9eeadd3
--- /dev/null
+++ b/server-timing/resources/parsing/48.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="""\
diff --git a/server-timing/resources/parsing/49.js b/server-timing/resources/parsing/49.js
new file mode 100644
index 0000000..349a7e0
--- /dev/null
+++ b/server-timing/resources/parsing/49.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":""}])
diff --git a/server-timing/resources/parsing/49.js.sub.headers b/server-timing/resources/parsing/49.js.sub.headers
new file mode 100644
index 0000000..2dbff3c
--- /dev/null
+++ b/server-timing/resources/parsing/49.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=""""
diff --git a/server-timing/resources/parsing/5.js b/server-timing/resources/parsing/5.js
new file mode 100644
index 0000000..966e963
--- /dev/null
+++ b/server-timing/resources/parsing/5.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"description"}])
diff --git a/server-timing/resources/parsing/5.js.sub.headers b/server-timing/resources/parsing/5.js.sub.headers
new file mode 100644
index 0000000..c083f60
--- /dev/null
+++ b/server-timing/resources/parsing/5.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="description"
diff --git a/server-timing/resources/parsing/50.js b/server-timing/resources/parsing/50.js
new file mode 100644
index 0000000..413d9b6
--- /dev/null
+++ b/server-timing/resources/parsing/50.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":12.3,"desc":"description1"},{"name":"metric","dur":45.6,"desc":"description2"}])
diff --git a/server-timing/resources/parsing/50.js.sub.headers b/server-timing/resources/parsing/50.js.sub.headers
new file mode 100644
index 0000000..c8ac573
--- /dev/null
+++ b/server-timing/resources/parsing/50.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur=12.3;desc=description1,metric;dur=45.6;desc=description2
diff --git a/server-timing/resources/parsing/51.js b/server-timing/resources/parsing/51.js
new file mode 100644
index 0000000..fc827f8
--- /dev/null
+++ b/server-timing/resources/parsing/51.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":123.4,"desc":"description"}])
diff --git a/server-timing/resources/parsing/51.js.sub.headers b/server-timing/resources/parsing/51.js.sub.headers
new file mode 100644
index 0000000..5825a54
--- /dev/null
+++ b/server-timing/resources/parsing/51.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;DuR=123.4;DeSc=description
diff --git a/server-timing/resources/parsing/52.js b/server-timing/resources/parsing/52.js
new file mode 100644
index 0000000..a97e9d0
--- /dev/null
+++ b/server-timing/resources/parsing/52.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"MeTrIc","desc":"DeScRiPtIoN"}])
diff --git a/server-timing/resources/parsing/52.js.sub.headers b/server-timing/resources/parsing/52.js.sub.headers
new file mode 100644
index 0000000..f5fcff9
--- /dev/null
+++ b/server-timing/resources/parsing/52.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: MeTrIc;desc=DeScRiPtIoN
diff --git a/server-timing/resources/parsing/53.js b/server-timing/resources/parsing/53.js
new file mode 100644
index 0000000..adf74fa
--- /dev/null
+++ b/server-timing/resources/parsing/53.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":0}])
diff --git a/server-timing/resources/parsing/53.js.sub.headers b/server-timing/resources/parsing/53.js.sub.headers
new file mode 100644
index 0000000..3de0f19
--- /dev/null
+++ b/server-timing/resources/parsing/53.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur=foo
diff --git a/server-timing/resources/parsing/54.js b/server-timing/resources/parsing/54.js
new file mode 100644
index 0000000..adf74fa
--- /dev/null
+++ b/server-timing/resources/parsing/54.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":0}])
diff --git a/server-timing/resources/parsing/54.js.sub.headers b/server-timing/resources/parsing/54.js.sub.headers
new file mode 100644
index 0000000..78f6704
--- /dev/null
+++ b/server-timing/resources/parsing/54.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur="foo"
diff --git a/server-timing/resources/parsing/55.js b/server-timing/resources/parsing/55.js
new file mode 100644
index 0000000..429b528
--- /dev/null
+++ b/server-timing/resources/parsing/55.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric1","desc":"description","dur":123.4},{"name":"metric2"}])
diff --git a/server-timing/resources/parsing/55.js.sub.headers b/server-timing/resources/parsing/55.js.sub.headers
new file mode 100644
index 0000000..578232a
--- /dev/null
+++ b/server-timing/resources/parsing/55.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric1;foo=bar;desc=description;foo=bar;dur=123.4;foo=bar,metric2
diff --git a/server-timing/resources/parsing/56.js b/server-timing/resources/parsing/56.js
new file mode 100644
index 0000000..b763b81
--- /dev/null
+++ b/server-timing/resources/parsing/56.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":123.4}])
diff --git a/server-timing/resources/parsing/56.js.sub.headers b/server-timing/resources/parsing/56.js.sub.headers
new file mode 100644
index 0000000..8a220ab
--- /dev/null
+++ b/server-timing/resources/parsing/56.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur=123.4;dur=567.8
diff --git a/server-timing/resources/parsing/57.js b/server-timing/resources/parsing/57.js
new file mode 100644
index 0000000..adf74fa
--- /dev/null
+++ b/server-timing/resources/parsing/57.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":0}])
diff --git a/server-timing/resources/parsing/57.js.sub.headers b/server-timing/resources/parsing/57.js.sub.headers
new file mode 100644
index 0000000..2614e20
--- /dev/null
+++ b/server-timing/resources/parsing/57.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur=foo;dur=567.8
diff --git a/server-timing/resources/parsing/58.js b/server-timing/resources/parsing/58.js
new file mode 100644
index 0000000..05004e5
--- /dev/null
+++ b/server-timing/resources/parsing/58.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"description1"}])
diff --git a/server-timing/resources/parsing/58.js.sub.headers b/server-timing/resources/parsing/58.js.sub.headers
new file mode 100644
index 0000000..e5ef569
--- /dev/null
+++ b/server-timing/resources/parsing/58.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=description1;desc=description2
diff --git a/server-timing/resources/parsing/59.js b/server-timing/resources/parsing/59.js
new file mode 100644
index 0000000..c9a9a98
--- /dev/null
+++ b/server-timing/resources/parsing/59.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":0,"desc":"description"}])
diff --git a/server-timing/resources/parsing/59.js.sub.headers b/server-timing/resources/parsing/59.js.sub.headers
new file mode 100644
index 0000000..5b44836
--- /dev/null
+++ b/server-timing/resources/parsing/59.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur;dur=123.4;desc=description
diff --git a/server-timing/resources/parsing/6.js b/server-timing/resources/parsing/6.js
new file mode 100644
index 0000000..fc827f8
--- /dev/null
+++ b/server-timing/resources/parsing/6.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":123.4,"desc":"description"}])
diff --git a/server-timing/resources/parsing/6.js.sub.headers b/server-timing/resources/parsing/6.js.sub.headers
new file mode 100644
index 0000000..5dbc9d6
--- /dev/null
+++ b/server-timing/resources/parsing/6.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur=123.4;desc=description
diff --git a/server-timing/resources/parsing/60.js b/server-timing/resources/parsing/60.js
new file mode 100644
index 0000000..c9a9a98
--- /dev/null
+++ b/server-timing/resources/parsing/60.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":0,"desc":"description"}])
diff --git a/server-timing/resources/parsing/60.js.sub.headers b/server-timing/resources/parsing/60.js.sub.headers
new file mode 100644
index 0000000..bfbddf1
--- /dev/null
+++ b/server-timing/resources/parsing/60.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur=;dur=123.4;desc=description
diff --git a/server-timing/resources/parsing/61.js b/server-timing/resources/parsing/61.js
new file mode 100644
index 0000000..ce7d800
--- /dev/null
+++ b/server-timing/resources/parsing/61.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"","dur":123.4}])
diff --git a/server-timing/resources/parsing/61.js.sub.headers b/server-timing/resources/parsing/61.js.sub.headers
new file mode 100644
index 0000000..6d605ad
--- /dev/null
+++ b/server-timing/resources/parsing/61.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc;desc=description;dur=123.4
diff --git a/server-timing/resources/parsing/62.js b/server-timing/resources/parsing/62.js
new file mode 100644
index 0000000..ce7d800
--- /dev/null
+++ b/server-timing/resources/parsing/62.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"","dur":123.4}])
diff --git a/server-timing/resources/parsing/62.js.sub.headers b/server-timing/resources/parsing/62.js.sub.headers
new file mode 100644
index 0000000..182a81a
--- /dev/null
+++ b/server-timing/resources/parsing/62.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=;desc=description;dur=123.4
diff --git a/server-timing/resources/parsing/63.js b/server-timing/resources/parsing/63.js
new file mode 100644
index 0000000..d6842ba
--- /dev/null
+++ b/server-timing/resources/parsing/63.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"d1","dur":123.4}])
diff --git a/server-timing/resources/parsing/63.js.sub.headers b/server-timing/resources/parsing/63.js.sub.headers
new file mode 100644
index 0000000..8e9b117
--- /dev/null
+++ b/server-timing/resources/parsing/63.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=d1 d2;dur=123.4
diff --git a/server-timing/resources/parsing/64.js b/server-timing/resources/parsing/64.js
new file mode 100644
index 0000000..88037d1
--- /dev/null
+++ b/server-timing/resources/parsing/64.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric1","desc":"d1"},{"name":"metric2"}])
diff --git a/server-timing/resources/parsing/64.js.sub.headers b/server-timing/resources/parsing/64.js.sub.headers
new file mode 100644
index 0000000..b1dd0cc
--- /dev/null
+++ b/server-timing/resources/parsing/64.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric1;desc=d1 d2,metric2
diff --git a/server-timing/resources/parsing/65.js b/server-timing/resources/parsing/65.js
new file mode 100644
index 0000000..d6842ba
--- /dev/null
+++ b/server-timing/resources/parsing/65.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"d1","dur":123.4}])
diff --git a/server-timing/resources/parsing/65.js.sub.headers b/server-timing/resources/parsing/65.js.sub.headers
new file mode 100644
index 0000000..63946cd
--- /dev/null
+++ b/server-timing/resources/parsing/65.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="d1" d2;dur=123.4
diff --git a/server-timing/resources/parsing/66.js b/server-timing/resources/parsing/66.js
new file mode 100644
index 0000000..88037d1
--- /dev/null
+++ b/server-timing/resources/parsing/66.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric1","desc":"d1"},{"name":"metric2"}])
diff --git a/server-timing/resources/parsing/66.js.sub.headers b/server-timing/resources/parsing/66.js.sub.headers
new file mode 100644
index 0000000..bddbb2c
--- /dev/null
+++ b/server-timing/resources/parsing/66.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric1;desc="d1" d2,metric2
diff --git a/server-timing/resources/parsing/67.js b/server-timing/resources/parsing/67.js
new file mode 100644
index 0000000..03b778b
--- /dev/null
+++ b/server-timing/resources/parsing/67.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric"}])
diff --git a/server-timing/resources/parsing/67.js.sub.headers b/server-timing/resources/parsing/67.js.sub.headers
new file mode 100644
index 0000000..5fe55cc
--- /dev/null
+++ b/server-timing/resources/parsing/67.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric==   ""foo;dur=123.4
diff --git a/server-timing/resources/parsing/68.js b/server-timing/resources/parsing/68.js
new file mode 100644
index 0000000..e6946c3
--- /dev/null
+++ b/server-timing/resources/parsing/68.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric1"}])
diff --git a/server-timing/resources/parsing/68.js.sub.headers b/server-timing/resources/parsing/68.js.sub.headers
new file mode 100644
index 0000000..27dabbe
--- /dev/null
+++ b/server-timing/resources/parsing/68.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric1==   ""foo,metric2
diff --git a/server-timing/resources/parsing/69.js b/server-timing/resources/parsing/69.js
new file mode 100644
index 0000000..adf74fa
--- /dev/null
+++ b/server-timing/resources/parsing/69.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","dur":0}])
diff --git a/server-timing/resources/parsing/69.js.sub.headers b/server-timing/resources/parsing/69.js.sub.headers
new file mode 100644
index 0000000..5528241
--- /dev/null
+++ b/server-timing/resources/parsing/69.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;dur foo=12
diff --git a/server-timing/resources/parsing/7.js b/server-timing/resources/parsing/7.js
new file mode 100644
index 0000000..02f8c3c
--- /dev/null
+++ b/server-timing/resources/parsing/7.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"description","dur":123.4}])
diff --git a/server-timing/resources/parsing/7.js.sub.headers b/server-timing/resources/parsing/7.js.sub.headers
new file mode 100644
index 0000000..c636900
--- /dev/null
+++ b/server-timing/resources/parsing/7.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc=description;dur=123.4
diff --git a/server-timing/resources/parsing/70.js b/server-timing/resources/parsing/70.js
new file mode 100644
index 0000000..03b778b
--- /dev/null
+++ b/server-timing/resources/parsing/70.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric"}])
diff --git a/server-timing/resources/parsing/70.js.sub.headers b/server-timing/resources/parsing/70.js.sub.headers
new file mode 100644
index 0000000..aa8f70b
--- /dev/null
+++ b/server-timing/resources/parsing/70.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;foo dur=12
diff --git a/server-timing/resources/parsing/71.js b/server-timing/resources/parsing/71.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/71.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/71.js.sub.headers b/server-timing/resources/parsing/71.js.sub.headers
new file mode 100644
index 0000000..26fcf28
--- /dev/null
+++ b/server-timing/resources/parsing/71.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing:  
diff --git a/server-timing/resources/parsing/72.js b/server-timing/resources/parsing/72.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/72.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/72.js.sub.headers b/server-timing/resources/parsing/72.js.sub.headers
new file mode 100644
index 0000000..74e059f
--- /dev/null
+++ b/server-timing/resources/parsing/72.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: =
diff --git a/server-timing/resources/parsing/73.js b/server-timing/resources/parsing/73.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/73.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/73.js.sub.headers b/server-timing/resources/parsing/73.js.sub.headers
new file mode 100644
index 0000000..eeaebdf
--- /dev/null
+++ b/server-timing/resources/parsing/73.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: [
diff --git a/server-timing/resources/parsing/74.js b/server-timing/resources/parsing/74.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/74.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/74.js.sub.headers b/server-timing/resources/parsing/74.js.sub.headers
new file mode 100644
index 0000000..b4f3c56
--- /dev/null
+++ b/server-timing/resources/parsing/74.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: ]
diff --git a/server-timing/resources/parsing/75.js b/server-timing/resources/parsing/75.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/75.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/75.js.sub.headers b/server-timing/resources/parsing/75.js.sub.headers
new file mode 100644
index 0000000..9a3684d
--- /dev/null
+++ b/server-timing/resources/parsing/75.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: ;
diff --git a/server-timing/resources/parsing/76.js b/server-timing/resources/parsing/76.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/76.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/76.js.sub.headers b/server-timing/resources/parsing/76.js.sub.headers
new file mode 100644
index 0000000..79cbd0c
--- /dev/null
+++ b/server-timing/resources/parsing/76.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: ,
diff --git a/server-timing/resources/parsing/77.js b/server-timing/resources/parsing/77.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/77.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/77.js.sub.headers b/server-timing/resources/parsing/77.js.sub.headers
new file mode 100644
index 0000000..888fa71
--- /dev/null
+++ b/server-timing/resources/parsing/77.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: =;
diff --git a/server-timing/resources/parsing/78.js b/server-timing/resources/parsing/78.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/78.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/78.js.sub.headers b/server-timing/resources/parsing/78.js.sub.headers
new file mode 100644
index 0000000..fc40e70
--- /dev/null
+++ b/server-timing/resources/parsing/78.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: ;=
diff --git a/server-timing/resources/parsing/79.js b/server-timing/resources/parsing/79.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/79.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/79.js.sub.headers b/server-timing/resources/parsing/79.js.sub.headers
new file mode 100644
index 0000000..0516c4c
--- /dev/null
+++ b/server-timing/resources/parsing/79.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: =,
diff --git a/server-timing/resources/parsing/8.js b/server-timing/resources/parsing/8.js
new file mode 100644
index 0000000..711e381
--- /dev/null
+++ b/server-timing/resources/parsing/8.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"aB3!#$%&'*+-.^_`|~"}])
diff --git a/server-timing/resources/parsing/8.js.sub.headers b/server-timing/resources/parsing/8.js.sub.headers
new file mode 100644
index 0000000..1d1bb02
--- /dev/null
+++ b/server-timing/resources/parsing/8.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: aB3!#$%&'*+-.^_`|~
diff --git a/server-timing/resources/parsing/80.js b/server-timing/resources/parsing/80.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/80.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/80.js.sub.headers b/server-timing/resources/parsing/80.js.sub.headers
new file mode 100644
index 0000000..03b3909
--- /dev/null
+++ b/server-timing/resources/parsing/80.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: ,=
diff --git a/server-timing/resources/parsing/81.js b/server-timing/resources/parsing/81.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/81.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/81.js.sub.headers b/server-timing/resources/parsing/81.js.sub.headers
new file mode 100644
index 0000000..680d2a2
--- /dev/null
+++ b/server-timing/resources/parsing/81.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: ;,
diff --git a/server-timing/resources/parsing/82.js b/server-timing/resources/parsing/82.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/82.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/82.js.sub.headers b/server-timing/resources/parsing/82.js.sub.headers
new file mode 100644
index 0000000..7ab1448
--- /dev/null
+++ b/server-timing/resources/parsing/82.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: ,;
diff --git a/server-timing/resources/parsing/83.js b/server-timing/resources/parsing/83.js
new file mode 100644
index 0000000..2848a1c
--- /dev/null
+++ b/server-timing/resources/parsing/83.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [])
diff --git a/server-timing/resources/parsing/83.js.sub.headers b/server-timing/resources/parsing/83.js.sub.headers
new file mode 100644
index 0000000..bd079bc
--- /dev/null
+++ b/server-timing/resources/parsing/83.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: =;,
diff --git a/server-timing/resources/parsing/9.js b/server-timing/resources/parsing/9.js
new file mode 100644
index 0000000..3048f3f
--- /dev/null
+++ b/server-timing/resources/parsing/9.js
@@ -0,0 +1 @@
+testServerTiming(document.currentScript, [{"name":"metric","desc":"descr;,=iption","dur":123.4}])
diff --git a/server-timing/resources/parsing/9.js.sub.headers b/server-timing/resources/parsing/9.js.sub.headers
new file mode 100644
index 0000000..c385706
--- /dev/null
+++ b/server-timing/resources/parsing/9.js.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric;desc="descr;,=iption";dur=123.4
diff --git a/server-timing/server_timing_header-parsing.html b/server-timing/server_timing_header-parsing.html
new file mode 100644
index 0000000..50396ed
--- /dev/null
+++ b/server-timing/server_timing_header-parsing.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+tests generated by:
+  https://github.com/cvazac/generate-server-timing-tests
+-->
+
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script src="/common/performance-timeline-utils.js"></script>
+    <script>
+      setup({explicit_done: true})
+      const tests = []
+      const urlToIndex = {}
+      function testServerTiming(script, expectedResults) {
+        const url = script.src
+        tests[urlToIndex[url]] = {url, expectedResults}
+      }
+      function runTests() {
+        tests.forEach(function({url, expectedResults}) {
+          const {serverTiming} = performance.getEntriesByName(url)[0]
+          const fileName = url.substring(url.lastIndexOf('/') + 1)
+
+          test_equals(serverTiming.length, expectedResults.length, `${fileName} - count (${serverTiming.length} ?== ${expectedResults.length})`)
+
+          expectedResults.forEach(function(expectedResult, i) {
+            const dur = expectedResult.dur || 0
+            const desc = expectedResult.desc || ''
+            const index = expectedResults.length === 1 ? '' : `[${i}].`
+            test_equals(expectedResult.name, serverTiming[i].name,
+                `${fileName} - ${index}name (${expectedResult.name} ?== ${serverTiming[i].name})`)
+            test_equals(dur, serverTiming[i].duration,
+                `${fileName} - ${index}duration (${dur} ?== ${serverTiming[i].duration})`)
+            test_equals(desc, serverTiming[i].description,
+                `${fileName} - ${index}description (${desc} ?== ${serverTiming[i].description})`)
+          })
+        })
+        done()
+      }
+      for (let i = 0; i <= 83; i++) {
+        const script = document.createElement('script')
+        script.src = `./resources/parsing/${i}.js`
+        document.getElementsByTagName('head')[0].appendChild(script)
+        urlToIndex[script.src] = i
+      }
+      window.addEventListener('load', runTests)
+    </script>
+</head>
diff --git a/server-timing/server_timing_header-parsing.https.html b/server-timing/server_timing_header-parsing.https.html
new file mode 100644
index 0000000..50396ed
--- /dev/null
+++ b/server-timing/server_timing_header-parsing.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+tests generated by:
+  https://github.com/cvazac/generate-server-timing-tests
+-->
+
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script src="/common/performance-timeline-utils.js"></script>
+    <script>
+      setup({explicit_done: true})
+      const tests = []
+      const urlToIndex = {}
+      function testServerTiming(script, expectedResults) {
+        const url = script.src
+        tests[urlToIndex[url]] = {url, expectedResults}
+      }
+      function runTests() {
+        tests.forEach(function({url, expectedResults}) {
+          const {serverTiming} = performance.getEntriesByName(url)[0]
+          const fileName = url.substring(url.lastIndexOf('/') + 1)
+
+          test_equals(serverTiming.length, expectedResults.length, `${fileName} - count (${serverTiming.length} ?== ${expectedResults.length})`)
+
+          expectedResults.forEach(function(expectedResult, i) {
+            const dur = expectedResult.dur || 0
+            const desc = expectedResult.desc || ''
+            const index = expectedResults.length === 1 ? '' : `[${i}].`
+            test_equals(expectedResult.name, serverTiming[i].name,
+                `${fileName} - ${index}name (${expectedResult.name} ?== ${serverTiming[i].name})`)
+            test_equals(dur, serverTiming[i].duration,
+                `${fileName} - ${index}duration (${dur} ?== ${serverTiming[i].duration})`)
+            test_equals(desc, serverTiming[i].description,
+                `${fileName} - ${index}description (${desc} ?== ${serverTiming[i].description})`)
+          })
+        })
+        done()
+      }
+      for (let i = 0; i <= 83; i++) {
+        const script = document.createElement('script')
+        script.src = `./resources/parsing/${i}.js`
+        document.getElementsByTagName('head')[0].appendChild(script)
+        urlToIndex[script.src] = i
+      }
+      window.addEventListener('load', runTests)
+    </script>
+</head>
diff --git a/server-timing/service_worker_idl.html b/server-timing/service_worker_idl.html
new file mode 100644
index 0000000..1c1be89
--- /dev/null
+++ b/server-timing/service_worker_idl.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script>
+      (async () => {
+        const scope = 'does/not/exist'
+
+        let registration = await navigator.serviceWorker.getRegistration(scope)
+        if (registration)
+          await registration.unregister()
+        registration = await navigator.serviceWorker.register('./sw.js', {scope})
+
+        fetch_tests_from_worker(registration.installing)
+      })()
+    </script>
+</head>
diff --git a/server-timing/sw.js b/server-timing/sw.js
new file mode 100644
index 0000000..a6fd72a
--- /dev/null
+++ b/server-timing/sw.js
@@ -0,0 +1,19 @@
+importScripts('/resources/testharness.js')
+
+promise_test((test) => {
+  return fetch('./sw.js').then((response) => {
+    return new Promise((resolve, reject) => {
+      step_timeout(() => {
+        const entry = performance.getEntriesByName(response.url)[0]
+        if (!entry) {
+          reject('no entry: ' + response.url)
+        }
+
+        assert_not_equals(typeof entry.serverTiming,
+          'undefined',
+          'An instance of `PerformanceResourceTiming` should have a `serverTiming` attribute in the Service Worker context.')
+        resolve()
+      }, 250)
+    })
+  })
+})
diff --git a/server-timing/test_server_timing.html b/server-timing/test_server_timing.html
index fa10030..2d43aa2 100644
--- a/server-timing/test_server_timing.html
+++ b/server-timing/test_server_timing.html
@@ -3,7 +3,6 @@
     <meta charset='utf-8' />
     <script src="/resources/testharness.js"></script>
     <script src='/resources/testharnessreport.js'></script>
-    <script src='resources/webperftestharness.js'></script>
     <script src="/common/performance-timeline-utils.js"></script>
     <script>
       setup({explicit_done: true})
diff --git a/server-timing/test_server_timing.https.html b/server-timing/test_server_timing.https.html
new file mode 100644
index 0000000..2d43aa2
--- /dev/null
+++ b/server-timing/test_server_timing.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+    <meta charset='utf-8' />
+    <script src="/resources/testharness.js"></script>
+    <script src='/resources/testharnessreport.js'></script>
+    <script src="/common/performance-timeline-utils.js"></script>
+    <script>
+      setup({explicit_done: true})
+
+      window.addEventListener('load', function() {
+        // there should be exactly three server-timing entries, 2 for document, 1 for img#one
+        test_entries(performance.getEntriesByType('navigation')[0].serverTiming, [{
+          duration: 1.1,
+          name: 'metric1',
+          description: 'document',
+        }, {
+          duration: 1.2,
+          name: 'metric1',
+          description: 'document',
+        }])
+        test_entries(performance.getEntriesByName(document.querySelector('img#one').src)[0].serverTiming, [{
+          duration: 2.1,
+          name: 'metric2',
+          description: 'blue.png',
+        }])
+
+        new PerformanceObserver(function(entryList, observer) {
+          // there should be exactly one server-timing entry, 1 for img#two
+          test_entries(entryList.getEntriesByName(document.querySelector('img#two').src)[0].serverTiming, [{
+            duration: 3.1,
+            name: 'metric3',
+            description: 'green.png',
+          }])
+          observer.disconnect()
+          done()
+        }).observe({entryTypes: ['resource']})
+
+        var img = document.createElement('img')
+        img.id = 'two'
+        img.src = './resources/green.png'
+        document.getElementsByTagName('script')[0].parentNode.appendChild(img)
+      })
+    </script>
+</head>
+<img id='one' src='resources/blue.png'>
diff --git a/server-timing/test_server_timing.https.html.sub.headers b/server-timing/test_server_timing.https.html.sub.headers
new file mode 100644
index 0000000..ad018b7
--- /dev/null
+++ b/server-timing/test_server_timing.https.html.sub.headers
@@ -0,0 +1 @@
+Server-Timing: metric1; dur=1.1; desc=document, metric1; dur=1.2; desc=document
diff --git a/service-workers/OWNERS b/service-workers/OWNERS
index f429364..7870c50 100644
--- a/service-workers/OWNERS
+++ b/service-workers/OWNERS
@@ -1,3 +1,5 @@
-@ehsan
+@asutherland
+@beidson
 @mkruisselbrink
 @mattto
+@wanderview
diff --git a/service-workers/cache-storage/resources/cache-keys-attributes-for-service-worker.js b/service-workers/cache-storage/resources/cache-keys-attributes-for-service-worker.js
new file mode 100644
index 0000000..ee574d2
--- /dev/null
+++ b/service-workers/cache-storage/resources/cache-keys-attributes-for-service-worker.js
@@ -0,0 +1,22 @@
+self.addEventListener('fetch', (event) => {
+    const params = new URL(event.request.url).searchParams;
+    if (params.has('ignore')) {
+      return;
+    }
+    if (!params.has('name')) {
+      event.respondWith(Promise.reject(TypeError('No name is provided.')));
+      return;
+    }
+
+    event.respondWith(Promise.resolve().then(async () => {
+        const name = params.get('name');
+        await caches.delete('foo');
+        const cache = await caches.open('foo');
+        await cache.put(event.request, new Response('hello'));
+        const keys = await cache.keys();
+
+        const original = event.request[name];
+        const stored = keys[0][name];
+        return new Response(`original: ${original}, stored: ${stored}`);
+      }));
+  });
diff --git a/service-workers/cache-storage/script-tests/cache-abort.js b/service-workers/cache-storage/script-tests/cache-abort.js
new file mode 100644
index 0000000..ec4130f
--- /dev/null
+++ b/service-workers/cache-storage/script-tests/cache-abort.js
@@ -0,0 +1,81 @@
+if (self.importScripts) {
+  importScripts('/resources/testharness.js');
+  importScripts('../resources/test-helpers.js');
+  importScripts('/common/utils.js');
+}
+
+// We perform the same tests on put, add, addAll. Parameterise the tests to
+// reduce repetition.
+const methodsToTest = {
+  put: async (cache, request) => {
+    const response = await fetch(request);
+    return cache.put(request, response);
+  },
+  add: async (cache, request) => cache.add(request),
+  addAll: async (cache, request) => cache.addAll([request]),
+};
+
+for (const method in methodsToTest) {
+  const perform = methodsToTest[method];
+
+  cache_test(async (cache, test) => {
+    const controller = new AbortController();
+    const signal = controller.signal;
+    controller.abort();
+    const request = new Request('../resources/simple.txt', { signal });
+    return promise_rejects(test, 'AbortError', perform(cache, request),
+                          `${method} should reject`);
+  }, `${method}() on an already-aborted request should reject with AbortError`);
+
+  cache_test(async (cache, test) => {
+    const controller = new AbortController();
+    const signal = controller.signal;
+    const request = new Request('../resources/simple.txt', { signal });
+    const promise = perform(cache, request);
+    controller.abort();
+    return promise_rejects(test, 'AbortError', promise,
+                          `${method} should reject`);
+  }, `${method}() synchronously followed by abort should reject with ` +
+     `AbortError`);
+
+  cache_test(async (cache, test) => {
+    const controller = new AbortController();
+    const signal = controller.signal;
+    const stateKey = token();
+    const abortKey = token();
+    const request = new Request(
+        `../../../fetch/api/resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`,
+        { signal });
+
+    const promise = perform(cache, request);
+
+    // Wait for the server to start sending the response body.
+    let opened = false;
+    do {
+      // Normally only one fetch to 'stash-take' is needed, but the fetches
+      // will be served in reverse order sometimes
+      // (i.e., 'stash-take' gets served before 'infinite-slow-response').
+
+      const response =
+            await fetch(`../../../fetch/api/resources/stash-take.py?key=${stateKey}`);
+      const body = await response.json();
+      if (body === 'open') opened = true;
+    } while (!opened);
+
+    // Sadly the above loop cannot guarantee that the browser has started
+    // processing the response body. This delay is needed to make the test
+    // failures non-flaky in Chrome version 66. My deepest apologies.
+    await new Promise(resolve => setTimeout(resolve, 250));
+
+    controller.abort();
+
+    await promise_rejects(test, 'AbortError', promise,
+                          `${method} should reject`);
+
+    // infinite-slow-response.py doesn't know when to stop.
+    return fetch(`../../../fetch/api/resources/stash-put.py?key=${abortKey}`);
+  }, `${method}() followed by abort after headers received should reject ` +
+     `with AbortError`);
+}
+
+done();
diff --git a/service-workers/cache-storage/script-tests/cache-match.js b/service-workers/cache-storage/script-tests/cache-match.js
index 3d00f0f..ba359fe 100644
--- a/service-workers/cache-storage/script-tests/cache-match.js
+++ b/service-workers/cache-storage/script-tests/cache-match.js
@@ -1,6 +1,7 @@
 if (self.importScripts) {
     importScripts('/resources/testharness.js');
     importScripts('../resources/test-helpers.js');
+    importScripts('/common/get-host-info.sub.js');
 }
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
@@ -318,4 +319,22 @@
         });
   }, 'Cache produces large Responses that can be cloned and read correctly.');
 
+cache_test(async (cache) => {
+    const url = get_host_info().HTTPS_REMOTE_ORIGIN +
+      '/service-workers/cache-storage/resources/simple.txt?pipe=' +
+      'header(access-control-allow-origin,*)|' +
+      'header(access-control-expose-headers,*)|' +
+      'header(foo,bar)|' +
+      'header(set-cookie,X)';
+
+    const response = await fetch(url);
+    await cache.put(new Request(url), response);
+    const cached_response = await cache.match(url);
+
+    const headers = cached_response.headers;
+    assert_equals(headers.get('access-control-expose-headers'), '*');
+    assert_equals(headers.get('foo'), 'bar');
+    assert_equals(headers.get('set-cookie'), null);
+  }, 'cors-exposed header should be stored correctly.');
+
 done();
diff --git a/service-workers/cache-storage/script-tests/cache-put.js b/service-workers/cache-storage/script-tests/cache-put.js
index 467e0b3..38d2564 100644
--- a/service-workers/cache-storage/script-tests/cache-put.js
+++ b/service-workers/cache-storage/script-tests/cache-put.js
@@ -335,4 +335,12 @@
         });
   }, 'Cache.put should store Response.redirect() correctly');
 
+cache_test(async (cache) => {
+    var request = new Request(test_url);
+    var response = new Response(new Blob([test_body]));
+    await cache.put(request, response);
+    var cachedResponse = await cache.match(request);
+    assert_equals(await cachedResponse.text(), test_body);
+  }, 'Cache.put called with simple Request and blob Response');
+
 done();
diff --git a/service-workers/cache-storage/serviceworker/cache-abort.https.html b/service-workers/cache-storage/serviceworker/cache-abort.https.html
new file mode 100644
index 0000000..5a8b539
--- /dev/null
+++ b/service-workers/cache-storage/serviceworker/cache-abort.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Cache Storage: Abort</title>
+<link rel="help" href="https://fetch.spec.whatwg.org/#request-signal">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+service_worker_test('../script-tests/cache-abort.js');
+</script>
diff --git a/service-workers/cache-storage/serviceworker/cache-keys-attributes-for-service-worker.https.html b/service-workers/cache-storage/serviceworker/cache-keys-attributes-for-service-worker.https.html
new file mode 100644
index 0000000..2024076
--- /dev/null
+++ b/service-workers/cache-storage/serviceworker/cache-keys-attributes-for-service-worker.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Cache.keys (checking request attributes that can be set only on service workers)</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-keys">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const worker = '../resources/cache-keys-attributes-for-service-worker.js';
+
+promise_test(async (t) => {
+    const scope = '../resources/blank.html?name=isReloadNavigation';
+    let frame;
+    let reg;
+
+    try {
+      reg = await service_worker_unregister_and_register(t, worker, scope);
+      await wait_for_state(t, reg.installing, 'activated');
+      frame = await with_iframe(scope);
+      assert_equals(frame.contentDocument.body.textContent,
+                    'original: false, stored: false');
+      await new Promise((resolve) => {
+        frame.onload = resolve;
+        frame.contentWindow.location.reload();
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'original: true, stored: true');
+    } finally {
+      if (frame) {
+        frame.remove();
+      }
+      if (reg) {
+        await reg.unregister();
+      }
+    }
+}, 'Request.IsReloadNavigation should persist.');
+</script>
diff --git a/service-workers/cache-storage/window/cache-abort.https.html b/service-workers/cache-storage/window/cache-abort.https.html
new file mode 100644
index 0000000..405d34d
--- /dev/null
+++ b/service-workers/cache-storage/window/cache-abort.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Cache Storage: Abort</title>
+<link rel="help" href="https://fetch.spec.whatwg.org/#request-signal">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="/common/utils.js"></script>
+<script src="../script-tests/cache-abort.js"></script>
diff --git a/service-workers/cache-storage/window/cache-match.https.html b/service-workers/cache-storage/window/cache-match.https.html
index ffc6498..f28efad 100644
--- a/service-workers/cache-storage/window/cache-match.https.html
+++ b/service-workers/cache-storage/window/cache-match.https.html
@@ -4,5 +4,6 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
 <script src="../resources/test-helpers.js"></script>
 <script src="../script-tests/cache-match.js"></script>
diff --git a/service-workers/cache-storage/window/cache-put.https.html b/service-workers/cache-storage/window/cache-put.https.html
index ded6e84..1ee14bd 100644
--- a/service-workers/cache-storage/window/cache-put.https.html
+++ b/service-workers/cache-storage/window/cache-put.https.html
@@ -6,3 +6,16 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="../resources/test-helpers.js"></script>
 <script src="../script-tests/cache-put.js"></script>
+<script>
+cache_test(async (cache) => {
+    var formData = new FormData();
+    formData.append("name", "value");
+
+    var request = new Request(test_url);
+    var response = new Response(formData);
+    await cache.put(request, response);
+    var cachedResponse = await cache.match(request);
+    var cachedResponseText = await cachedResponse.text();
+    assert_true(cachedResponseText.indexOf("name=\"name\"\r\n\r\nvalue") !== -1);
+  }, 'Cache.put called with simple Request and form data Response');
+</script>
diff --git a/service-workers/cache-storage/worker/cache-abort.https.html b/service-workers/cache-storage/worker/cache-abort.https.html
new file mode 100644
index 0000000..68bbade
--- /dev/null
+++ b/service-workers/cache-storage/worker/cache-abort.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>>Cache Storage: Abort</title>
+<link rel="help" href="https://fetch.spec.whatwg.org/#request-signal">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker('../script-tests/cache-abort.js'));
+</script>
diff --git a/service-workers/service-worker/Service-Worker-Allowed-header.https.html b/service-workers/service-worker/Service-Worker-Allowed-header.https.html
new file mode 100644
index 0000000..316067c
--- /dev/null
+++ b/service-workers/service-worker/Service-Worker-Allowed-header.https.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<title>Service Worker: Service-Worker-Allowed header</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+const host_info = get_host_info();
+
+// Returns a URL for a service worker script whose Service-Worker-Allowed
+// header value is set to |allowed_path|. If |origin| is specified, that origin
+// is used.
+function build_script_url(allowed_path, origin) {
+  const script = 'resources/empty-worker.js';
+  const url = origin ? `${origin}${base_path()}${script}` : script;
+  return `${url}?pipe=header(Service-Worker-Allowed,${allowed_path})`;
+}
+
+promise_test(async t => {
+  const script = build_script_url('/allowed-path');
+  const scope = '/allowed-path';
+  const registration = await service_worker_unregister_and_register(
+      t, script, scope);
+  assert_true(registration instanceof ServiceWorkerRegistration, 'registered');
+  assert_equals(registration.scope, normalizeURL(scope));
+  return registration.unregister();
+}, 'Registering within Service-Worker-Allowed path');
+
+promise_test(async t => {
+  const script = build_script_url(new URL('/allowed-path', document.location));
+  const scope = '/allowed-path';
+  const registration = await service_worker_unregister_and_register(
+      t, script, scope);
+  assert_true(registration instanceof ServiceWorkerRegistration, 'registered');
+  assert_equals(registration.scope, normalizeURL(scope));
+  return registration.unregister();
+}, 'Registering within Service-Worker-Allowed path (absolute URL)');
+
+promise_test(async t => {
+  const script = build_script_url('../allowed-path-with-parent');
+  const scope = 'allowed-path-with-parent';
+  const registration = await service_worker_unregister_and_register(
+      t, script, scope);
+  assert_true(registration instanceof ServiceWorkerRegistration, 'registered');
+  assert_equals(registration.scope, normalizeURL(scope));
+  return registration.unregister();
+}, 'Registering within Service-Worker-Allowed path with parent reference');
+
+promise_test(async t => {
+  const script = build_script_url('../allowed-path');
+  const scope = '/disallowed-path';
+  await service_worker_unregister(t, scope);
+  return promise_rejects(t,
+      'SecurityError',
+      navigator.serviceWorker.register(script, {scope: scope}),
+      'register should fail');
+}, 'Registering outside Service-Worker-Allowed path');
+
+promise_test(async t => {
+  const script = build_script_url('../allowed-path-with-parent');
+  const scope = '/allowed-path-with-parent';
+  await service_worker_unregister(t, scope);
+  return promise_rejects(t,
+      'SecurityError',
+      navigator.serviceWorker.register(script, {scope: scope}),
+      'register should fail');
+}, 'Registering outside Service-Worker-Allowed path with parent reference');
+
+promise_test(async t => {
+  const script = build_script_url(
+      host_info.HTTPS_REMOTE_ORIGIN + '/');
+  const scope = 'resources/this-scope-is-normally-allowed'
+  const registration = await service_worker_unregister_and_register(
+      t, script, scope);
+  assert_true(registration instanceof ServiceWorkerRegistration, 'registered');
+  assert_equals(registration.scope, normalizeURL(scope));
+  return registration.unregister();
+}, 'Service-Worker-Allowed is cross-origin to script, registering on a normally allowed scope');
+
+promise_test(async t => {
+  const script = build_script_url(
+      host_info.HTTPS_REMOTE_ORIGIN + '/');
+  const scope = '/this-scope-is-normally-disallowed'
+  const registration = await service_worker_unregister_and_register(
+      t, script, scope);
+  assert_true(registration instanceof ServiceWorkerRegistration, 'registered');
+  assert_equals(registration.scope, normalizeURL(scope));
+  return registration.unregister();
+}, 'Service-Worker-Allowed is cross-origin to script, registering on a normally disallowed scope');
+
+promise_test(async t => {
+  const script = build_script_url(
+      host_info.HTTPS_REMOTE_ORIGIN + '/cross-origin/',
+      host_info.HTTPS_REMOTE_ORIGIN);
+  const scope = '/cross-origin/';
+  await service_worker_unregister(t, scope);
+  return promise_rejects(t,
+      'SecurityError',
+      navigator.serviceWorker.register(script, {scope: scope}),
+      'register should fail');
+}, 'Service-Worker-Allowed is cross-origin to page, same-origin to script');
+
+</script>
diff --git a/service-workers/service-worker/about-blank-replacement.https.html b/service-workers/service-worker/about-blank-replacement.https.html
index 3acfe1b..e1fefaf 100644
--- a/service-workers/service-worker/about-blank-replacement.https.html
+++ b/service-workers/service-worker/about-blank-replacement.https.html
@@ -58,7 +58,7 @@
   });
 }
 
-async function doAsyncTest(t, scope, extraSearchParams) {
+async function doAsyncTest(t, scope) {
   let reg = await service_worker_unregister_and_register(t, worker, scope);
   await wait_for_state(t, reg.installing, 'activated');
 
@@ -70,20 +70,30 @@
   let initialResult = frame.contentWindow.nested().document.body.textContent;
   assert_false(initialResult.startsWith('failure:'), `result: ${initialResult}`);
 
+  assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+                frame.contentWindow.nested().navigator.serviceWorker.controller.scriptURL,
+                'nested about:blank should have same controlling service worker');
+
   // Next, ask the service worker to find the final client ID for the fully
   // loaded nested frame.
-  let nestedURL = new URL(scope, window.location);
-  nestedURL.searchParams.set('nested', true);
-  extraSearchParams = extraSearchParams || {};
-  for (let p in extraSearchParams) {
-    nestedURL.searchParams.set(p, extraSearchParams[p]);
-  }
+  let nestedURL = new URL(frame.contentWindow.nested().location);
   let finalResult = await getClientIdByURL(reg.active, nestedURL);
   assert_false(finalResult.startsWith('failure:'), `result: ${finalResult}`);
 
-  // The initial about:blank client and the final loaded client should have
-  // the same ID value.
-  assert_equals(initialResult, finalResult, 'client ID values should match');
+  // If the nested frame doesn't have a URL to load, then there is no fetch
+  // event and the body should be empty.  We can't verify the final client ID
+  // against anything.
+  if (nestedURL.href === 'about:blank' ||
+      nestedURL.href === 'about:srcdoc') {
+    assert_equals('', initialResult, 'about:blank text content should be blank');
+  }
+
+  // If the nested URL is not about:blank, though, then the fetch event handler
+  // should have populated the body with the client id of the initial about:blank.
+  // Verify the final client id matches.
+  else {
+    assert_equals(initialResult, finalResult, 'client ID values should match');
+  }
 
   frame.remove();
   await service_worker_unregister_and_done(t, scope);
@@ -101,8 +111,7 @@
   // worker can ping the client to verify its existence.  This ping-pong
   // check is performed during the initial load and when verifying the
   // final loaded client.
-  await doAsyncTest(t, 'resources/about-blank-replacement-ping-frame.py',
-                    { 'ping': true });
+  await doAsyncTest(t, 'resources/about-blank-replacement-ping-frame.py');
 }, 'Initial about:blank modified by parent is controlled, exposed to ' +
    'clients.matchAll(), and matches final Client.');
 
@@ -142,5 +151,27 @@
 }, 'Initial about:blank is controlled, exposed to clients.matchAll(), and ' +
    'final Client is not controlled by a service worker.');
 
+promise_test(async function(t) {
+  // Execute a test where the nested frame is an iframe without a src
+  // attribute.  This simple nested about:blank should still inherit the
+  // controller and be visible to clients.matchAll().
+  await doAsyncTest(t, 'resources/about-blank-replacement-blank-nested-frame.html');
+}, 'Simple about:blank is controlled and is exposed to clients.matchAll().');
+
+promise_test(async function(t) {
+  // Execute a test where the nested frame is an iframe using a non-empty
+  // srcdoc containing only a tag pair so its textContent is still empty.
+  // This nested iframe should still inherit the controller and be visible
+  // to clients.matchAll().
+  await doAsyncTest(t, 'resources/about-blank-replacement-srcdoc-nested-frame.html');
+}, 'Nested about:srcdoc is controlled and is exposed to clients.matchAll().');
+
+promise_test(async function(t) {
+  // Execute a test where the nested frame is dynamically added without a src
+  // attribute.  This simple nested about:blank should still inherit the
+  // controller and be visible to clients.matchAll().
+  await doAsyncTest(t, 'resources/about-blank-replacement-blank-dynamic-nested-frame.html');
+}, 'Dynamic about:blank is controlled and is exposed to clients.matchAll().');
+
 </script>
 </body>
diff --git a/service-workers/service-worker/activation.https.html b/service-workers/service-worker/activation.https.html
index 82138dd..5755758 100644
--- a/service-workers/service-worker/activation.https.html
+++ b/service-workers/service-worker/activation.https.html
@@ -6,24 +6,6 @@
 <script src="resources/test-helpers.sub.js"></script>
 <body>
 <script>
-// Registers, waits for activation, then unregisters on a dummy scope.
-//
-// This helper can be used in tests that assert that activation doesn't happen.
-// It would not be sufficient to check the .waiting/.active properties once,
-// since activation could be scheduled and just hasn't happened yet. Since this
-// helper shows that activation of another registration completed, we can be
-// sure that activation really will not happen.
-function wait_for_activation_on_dummy_scope(t) {
-  var dummy_scope = 'resources/there/is/no/there/there';
-  var registration;
-  return navigator.serviceWorker.register('resources/empty-worker.js',
-      { scope: dummy_scope })
-    .then(r => {
-        registration = r;
-        return wait_for_state(t, registration.installing, 'activated');
-      })
-    .then(() => registration.unregister());
-}
 // Returns {registration, iframe}, where |registration| has an active and
 // waiting worker. The active worker controls |iframe| and has an inflight
 // message event that can be finished by calling
@@ -67,7 +49,7 @@
         return wait_for_state(t, registration.installing, 'installed');
       })
     .then(() => {
-        return wait_for_activation_on_dummy_scope(t);
+        return wait_for_activation_on_dummy_scope(t, self);
       })
     .then(() => {
         assert_not_equals(registration.waiting, null);
@@ -87,7 +69,7 @@
           iframe = result.iframe;
           // Finish the in-flight request.
           registration.active.postMessage('go');
-          return wait_for_activation_on_dummy_scope(t);
+          return wait_for_activation_on_dummy_scope(t, self);
         })
       .then(() => {
           // The new worker is still waiting. Remove the frame and it should
diff --git a/service-workers/service-worker/detached-context.https.html b/service-workers/service-worker/detached-context.https.html
index d345982..5ae4de8 100644
--- a/service-workers/service-worker/detached-context.https.html
+++ b/service-workers/service-worker/detached-context.https.html
@@ -16,6 +16,7 @@
     const scope_for_main = 'resources/' + scope_for_iframe;
     const script = 'resources/empty-worker.js';
     let frame;
+    let resolvedCount = 0;
 
     return service_worker_unregister(t, scope_for_main)
       .then(() => {
@@ -41,9 +42,23 @@
           assert_equals(r.active.state, 'activated');
           assert_equals(r.scope, normalizeURL(scope_for_main));
           r.onupdatefound = () => { /* empty */ };
-          return Promise.all([
-              promise_rejects(t, 'InvalidStateError', r.unregister()),
-              promise_rejects(t, 'InvalidStateError', r.update())]);
+
+          // We want to verify that unregister() and update() do not
+          // resolve on a detached registration.  We can't check for
+          // an explicit rejection, though, because not all browsers
+          // fire rejection callbacks on detached promises.  Instead
+          // we wait for a dummy scope to install, activate, and
+          // unregister before declaring that the promises did not
+          // resolve.
+          r.unregister().then(() => resolvedCount += 1,
+                              () => {});
+          r.update().then(() => resolvedCount += 1,
+                          () => {});
+          return wait_for_activation_on_dummy_scope(t, window);
+        })
+      .then(() => {
+          assert_equals(resolvedCount, 0,
+                        'methods called on a detached registration should not resolve');
         })
   }, 'accessing a ServiceWorkerRegistration from a removed iframe');
 
diff --git a/service-workers/service-worker/embed-and-object-are-not-intercepted.https.html b/service-workers/service-worker/embed-and-object-are-not-intercepted.https.html
new file mode 100644
index 0000000..34556a7
--- /dev/null
+++ b/service-workers/service-worker/embed-and-object-are-not-intercepted.https.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>embed and object are not intercepted</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body>
+<script>
+let registration;
+
+const kScript = 'resources/embed-and-object-are-not-intercepted-worker.js';
+const kScope = 'resources/';
+
+promise_test(t => {
+    return service_worker_unregister_and_register(t, kScript, kScope)
+      .then(registration => {
+          promise_test(() => {
+              return registration.unregister();
+            }, 'restore global state');
+
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+  }, 'initialize global state');
+
+promise_test(t => {
+    let frame;
+    return with_iframe('resources/embed-is-not-intercepted-iframe.html')
+      .then(f => {
+          frame = f;
+          t.add_cleanup(() => { frame.remove(); });
+          return frame.contentWindow.test_promise;
+        })
+      .then(result => {
+          assert_equals(result, 'request for embedded content was not intercepted');
+        });
+  }, 'requests for EMBED elements of embedded HTML content should not be intercepted by service workers');
+
+promise_test(t => {
+    let frame;
+    return with_iframe('resources/object-is-not-intercepted-iframe.html')
+      .then(f => {
+          frame = f;
+          t.add_cleanup(() => { frame.remove(); });
+          return frame.contentWindow.test_promise;
+        })
+      .then(result => {
+          assert_equals(result, 'request for embedded content was not intercepted');
+        });
+  }, 'requests for OBJECT elements of embedded HTML content should not be intercepted by service workers');
+
+promise_test(t => {
+    let frame;
+    return with_iframe('resources/embed-image-is-not-intercepted-iframe.html')
+      .then(f => {
+          frame = f;
+          t.add_cleanup(() => { frame.remove(); });
+          return frame.contentWindow.test_promise;
+        })
+      .then(result => {
+          assert_equals(result, 'request was not intercepted');
+        });
+  }, 'requests for EMBED elements of an image should not be intercepted by service workers');
+
+promise_test(t => {
+    let frame;
+    return with_iframe('resources/object-image-is-not-intercepted-iframe.html')
+      .then(f => {
+          frame = f;
+          t.add_cleanup(() => { frame.remove(); });
+          return frame.contentWindow.test_promise;
+        })
+      .then(result => {
+          assert_equals(result, 'request was not intercepted');
+        });
+  }, 'requests for OBJECT elements of an image should not be intercepted by service workers');
+
+</script>
diff --git a/service-workers/service-worker/fetch-canvas-tainting-cache.https.html b/service-workers/service-worker/fetch-canvas-tainting-cache.https.html
deleted file mode 100644
index 7475493..0000000
--- a/service-workers/service-worker/fetch-canvas-tainting-cache.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: canvas tainting of the fetched image using cached responses</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/common/get-host-info.sub.js"></script>
-<script src="resources/test-helpers.sub.js?pipe=sub"></script>
-<body>
-<script>
-async_test(function(t) {
-    var SCOPE = 'resources/fetch-canvas-tainting-iframe.html?cache';
-    var SCRIPT = 'resources/fetch-rewrite-worker.js';
-    var host_info = get_host_info();
-
-    login_https(t)
-      .then(function() {
-          return service_worker_unregister_and_register(t, SCRIPT, SCOPE);
-        })
-      .then(function(registration) {
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() { return with_iframe(SCOPE); })
-      .then(function(frame) {
-          return new Promise(function(resolve, reject) {
-              var channel = new MessageChannel();
-              channel.port1.onmessage = t.step_func(function(e) {
-                  assert_equals(e.data.results, 'finish');
-                  frame.remove();
-                  service_worker_unregister_and_done(t, SCOPE);
-                });
-              frame.contentWindow.postMessage({},
-                                              host_info['HTTPS_ORIGIN'],
-                                              [channel.port2]);
-            });
-        })
-      .catch(unreached_rejection(t));
-  }, 'Verify canvas tainting of fetched image in a Service Worker');
-</script>
-</body>
diff --git a/service-workers/service-worker/fetch-canvas-tainting-image-cache.https.html b/service-workers/service-worker/fetch-canvas-tainting-image-cache.https.html
new file mode 100644
index 0000000..2132381
--- /dev/null
+++ b/service-workers/service-worker/fetch-canvas-tainting-image-cache.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Service Worker: canvas tainting of the fetched image using cached responses</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script src="resources/fetch-canvas-tainting-tests.js"></script>
+<body>
+<script>
+do_canvas_tainting_tests({
+  resource_path: base_path() + 'resources/fetch-access-control.py?PNGIMAGE',
+  cache: true
+});
+</script>
+</body>
diff --git a/service-workers/service-worker/fetch-canvas-tainting-image.https.html b/service-workers/service-worker/fetch-canvas-tainting-image.https.html
new file mode 100644
index 0000000..57dc7d9
--- /dev/null
+++ b/service-workers/service-worker/fetch-canvas-tainting-image.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Service Worker: canvas tainting of the fetched image</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script src="resources/fetch-canvas-tainting-tests.js"></script>
+<body>
+<script>
+do_canvas_tainting_tests({
+  resource_path: base_path() + 'resources/fetch-access-control.py?PNGIMAGE',
+  cache: false
+});
+</script>
+</body>
diff --git a/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html b/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html
new file mode 100644
index 0000000..ef3d12b
--- /dev/null
+++ b/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Service Worker: canvas tainting of the fetched video using cache responses</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script src="resources/fetch-canvas-tainting-tests.js"></script>
+<body>
+<script>
+do_canvas_tainting_tests({
+  resource_path: base_path() + 'resources/fetch-access-control.py?VIDEO',
+  cache: true
+});
+</script>
+</body>
diff --git a/service-workers/service-worker/fetch-canvas-tainting-video-with-range-request.https.html b/service-workers/service-worker/fetch-canvas-tainting-video-with-range-request.https.html
new file mode 100644
index 0000000..f1ff7ae
--- /dev/null
+++ b/service-workers/service-worker/fetch-canvas-tainting-video-with-range-request.https.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Canvas tainting due to video whose responses are fetched via a service worker including range requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+// These tests try to test canvas tainting due to a <video> element. The video
+// src URL is same-origin as the page, but the response is fetched via a service
+// worker that does tricky things like returning opaque responses from another
+// origin. Furthermore, this tests range requests so there are multiple
+// responses.
+//
+// We test range requests by having the server return 206 Partial Content to the
+// first request (which doesn't necessarily have a "Range" header or one with a
+// byte range). Then the <video> element automatically makes ranged requests
+// (the "Range" HTTP request header specifies a byte range). The server responds
+// to these with 206 Partial Content for the given range.
+function range_request_test(script, expected, description) {
+  promise_test(t => {
+      let frame;
+      let registration;
+      add_result_callback(() => {
+          if (frame) frame.remove();
+          if (registration) registration.unregister();
+        });
+
+      const scope = 'resources/fetch-canvas-tainting-iframe.html';
+      return service_worker_unregister_and_register(t, script, scope)
+        .then(r => {
+            registration = r;
+            return wait_for_state(t, registration.installing, 'activated');
+          })
+        .then(() => {
+            return with_iframe(scope);
+          })
+        .then(f => {
+            frame = f;
+            // Add "?VIDEO&PartialContent" to get a video resource from the
+            // server using range requests.
+            const video_url = 'fetch-access-control.py?VIDEO&PartialContent';
+            return frame.contentWindow.create_test_case_promise(video_url);
+          })
+        .then(result => {
+            assert_equals(result, expected);
+          });
+    }, description);
+}
+
+// We want to consider a number of scenarios:
+// (1) Range responses come from a single origin, the same-origin as the page.
+//     The canvas should not be tainted.
+range_request_test(
+  'resources/fetch-event-network-fallback-worker.js',
+  'NOT_TAINTED',
+  'range responses from single origin (same-origin)');
+
+// (2) Range responses come from a single origin, cross-origin from the page
+//     (and without CORS sharing). This is not possible to test, since service
+//     worker can't make a request with a "Range" HTTP header in no-cors mode.
+
+// (3) Range responses come from multiple origins. The first response comes from
+//     cross-origin (and without CORS sharing, so is opaque). Subsequent
+//     responses come from same-origin. The canvas should be tainted (but in
+//     Chrome this is a LOAD_ERROR since it disallows range responses from
+//     multiple origins, period).
+range_request_test(
+  'resources/range-request-to-different-origins-worker.js',
+  'TAINTED',
+  'range responses from multiple origins (cross-origin first)');
+
+// (4) Range responses come from multiple origins. The first response comes from
+//     same-origin. Subsequent responses come from cross-origin (and without
+//     CORS sharing). Like (2) this is not possible since the service worker
+//     cannot make range requests cross-origin.
+
+// (5) Range responses come from a single origin, with a mix of opaque and
+//     non-opaque responses. The first request uses 'no-cors' mode to
+//     receive an opaque response, and subsequent range requests use 'cors'
+//     to receive non-opaque responses. The canvas should be tainted.
+range_request_test(
+  'resources/range-request-with-different-cors-modes-worker.js',
+  'TAINTED',
+  'range responses from single origin with both opaque and non-opaque responses');
+
+// (6) Range responses come from a single origin, with a mix of opaque and
+//     non-opaque responses. The first request uses 'cors' mode to
+//     receive an non-opaque response, and subsequent range requests use
+//     'no-cors' to receive non-opaque responses. Like (2) this is not possible.
+</script>
+</body>
diff --git a/service-workers/service-worker/fetch-canvas-tainting-video.https.html b/service-workers/service-worker/fetch-canvas-tainting-video.https.html
new file mode 100644
index 0000000..5776508
--- /dev/null
+++ b/service-workers/service-worker/fetch-canvas-tainting-video.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Service Worker: canvas tainting of the fetched video</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script src="resources/fetch-canvas-tainting-tests.js"></script>
+<body>
+<script>
+do_canvas_tainting_tests({
+  resource_path: base_path() + 'resources/fetch-access-control.py?VIDEO',
+  cache: false
+});
+</script>
+</body>
diff --git a/service-workers/service-worker/fetch-canvas-tainting.https.html b/service-workers/service-worker/fetch-canvas-tainting.https.html
deleted file mode 100644
index 55faa9d..0000000
--- a/service-workers/service-worker/fetch-canvas-tainting.https.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: canvas tainting of the fetched image</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/common/get-host-info.sub.js"></script>
-<script src="resources/test-helpers.sub.js?pipe=sub"></script>
-<body>
-<script>
-async_test(function(t) {
-    var SCOPE = 'resources/fetch-canvas-tainting-iframe.html';
-    var SCRIPT = 'resources/fetch-rewrite-worker.js';
-    var host_info = get_host_info();
-
-    login_https(t)
-      .then(function() {
-          return service_worker_unregister_and_register(t, SCRIPT, SCOPE);
-        })
-      .then(function(registration) {
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() { return with_iframe(SCOPE); })
-      .then(function(frame) {
-          return new Promise(function(resolve, reject) {
-              var channel = new MessageChannel();
-              channel.port1.onmessage = t.step_func(function(e) {
-                  assert_equals(e.data.results, 'finish');
-                  frame.remove();
-                  service_worker_unregister_and_done(t, SCOPE);
-                });
-              frame.contentWindow.postMessage({},
-                                              host_info['HTTPS_ORIGIN'],
-                                              [channel.port2]);
-            });
-        })
-      .catch(unreached_rejection(t));
-  }, 'Verify canvas tainting of fetched image in a Service Worker');
-</script>
-</body>
diff --git a/service-workers/service-worker/fetch-cors-exposed-header-names.https.html b/service-workers/service-worker/fetch-cors-exposed-header-names.https.html
new file mode 100644
index 0000000..317b021
--- /dev/null
+++ b/service-workers/service-worker/fetch-cors-exposed-header-names.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Service Worker: CORS-exposed header names should be transferred correctly</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+promise_test(async function(t) {
+    const SCOPE = 'resources/simple.html';
+    const SCRIPT = 'resources/fetch-cors-exposed-header-names-worker.js';
+    const host_info = get_host_info();
+
+    const URL = get_host_info().HTTPS_REMOTE_ORIGIN +
+      '/service-workers/service-worker/resources/simple.txt?pipe=' +
+      'header(access-control-allow-origin,*)|' +
+      'header(access-control-expose-headers,*)|' +
+      'header(foo,bar)|' +
+      'header(set-cookie,X)';
+
+    const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+    await wait_for_state(t, reg.installing, 'activated');
+    const frame = await with_iframe(SCOPE);
+
+    const response = await frame.contentWindow.fetch(URL);
+    const headers = response.headers;
+    assert_equals(headers.get('foo'), 'bar');
+    assert_equals(headers.get('set-cookie'), null);
+    assert_equals(headers.get('access-control-expose-headers'), '*');
+  }, 'CORS-exposed header names for a response from sw');
+</script>
diff --git a/service-workers/service-worker/fetch-event-is-reload-iframe-navigation-manual.https.html b/service-workers/service-worker/fetch-event-is-reload-iframe-navigation-manual.https.html
new file mode 100644
index 0000000..cf1fecc
--- /dev/null
+++ b/service-workers/service-worker/fetch-event-is-reload-iframe-navigation-manual.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+const worker = 'resources/fetch-event-test-worker.js';
+
+promise_test(async (t) => {
+  const scope = 'resources/simple.html?isReloadNavigation';
+
+  const reg = await service_worker_unregister_and_register(t, worker, scope);
+  await wait_for_state(t, reg.installing, 'activated');
+  const frame = await with_iframe(scope);
+  assert_equals(frame.contentDocument.body.textContent,
+                'method = GET, isReloadNavigation = false');
+  await new Promise((resolve) => {
+    frame.addEventListener('load', resolve);
+    frame.contentDocument.body.innerText =
+      'Reload this frame manually!';
+  });
+  assert_equals(frame.contentDocument.body.textContent,
+      'method = GET, isReloadNavigation = true');
+  frame.remove();
+  await reg.unregister();
+}, 'FetchEvent#request.isReloadNavigation is true for manual reload.');
+
+</script>
+</body>
+</html>
diff --git a/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html b/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html
new file mode 100644
index 0000000..a349f07
--- /dev/null
+++ b/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<body>
+<p>Click <a href="resources/install-worker.html?isReloadNavigation&script=fetch-event-test-worker.js">this link</a>.
+   Once you see &quot;method = GET,...&quot; in the page, reload the page.
+   You will see &quot;method = GET, isReloadNavigation = true&quot;.
+</p>
+</body>
+</html>
diff --git a/service-workers/service-worker/fetch-event-redirect.https.html b/service-workers/service-worker/fetch-event-redirect.https.html
index c72be76..c42d7f7 100644
--- a/service-workers/service-worker/fetch-event-redirect.https.html
+++ b/service-workers/service-worker/fetch-event-redirect.https.html
@@ -229,10 +229,14 @@
       redirect: 'manual',
       mode: 'no-cors'
     },
+    // This should succeed because its redirecting from same-origin to
+    // cross-origin.  Since the same-origin URL provides the location
+    // header the manual redirect mode should result in an opaqueredirect
+    // response.
     should_reject: false
   });
 }, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
-   'no-cors without credentials should succeed interception ' +
+   'no-cors without credentials should succeed opaqueredirect interception ' +
    'and response should not be redirected');
 
 promise_test(function(t) {
@@ -246,10 +250,14 @@
       redirect: 'manual',
       mode: 'no-cors'
     },
+    // This should succeed because its redirecting from same-origin to
+    // cross-origin.  Since the same-origin URL provides the location
+    // header the manual redirect mode should result in an opaqueredirect
+    // response.
     should_reject: false
   });
 }, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
-   'cors without credentials should succeed interception ' +
+   'cors without credentials should succeed opaqueredirect interception ' +
    'and response should not be redirected');
 
 promise_test(function(t) {
@@ -382,10 +390,14 @@
       redirect: 'manual',
       mode: 'no-cors'
     },
+    // This should succeed because its redirecting from same-origin to
+    // cross-origin.  Since the same-origin URL provides the location
+    // header the manual redirect mode should result in an opaqueredirect
+    // response.
     should_reject: false
   });
 }, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
-   'no-cors with credentials should succeed interception ' +
+   'no-cors with credentials should succeed opaqueredirect interception ' +
    'and response should not be redirected');
 
 promise_test(function(t) {
@@ -399,10 +411,14 @@
       redirect: 'manual',
       mode: 'no-cors'
     },
+    // This should succeed because its redirecting from same-origin to
+    // cross-origin.  Since the same-origin URL provides the location
+    // header the manual redirect mode should result in an opaqueredirect
+    // response.
     should_reject: false
   });
 }, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' +
-   'cors with credentials should succeed interception ' +
+   'cors with credentials should succeed opaqueredirect interception ' +
    'and response should not be redirected');
 
 promise_test(function(t) {
diff --git a/service-workers/service-worker/fetch-event-respond-with-body-loaded-in-chunk.https.html b/service-workers/service-worker/fetch-event-respond-with-body-loaded-in-chunk.https.html
new file mode 100644
index 0000000..824b6f3
--- /dev/null
+++ b/service-workers/service-worker/fetch-event-respond-with-body-loaded-in-chunk.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>respondWith with a response whose body is being loaded from the network by chunks</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+'use strict';
+
+const WORKER = 'resources/fetch-event-respond-with-body-loaded-in-chunk-worker.js';
+const SCOPE = 'resources/fetch-event-respond-with-body-loaded-in-chunk-iframe.html';
+
+promise_test(async t => {
+    var reg = await service_worker_unregister_and_register(t, WORKER, SCOPE);
+    add_completion_callback(() => reg.unregister());
+    await wait_for_state(t, reg.installing, 'activated');
+    let iframe = await with_iframe(SCOPE);
+    t.add_cleanup(() => iframe.remove());
+
+    let response = await iframe.contentWindow.fetch('body-in-chunk');
+    assert_equals(await response.text(), 'TEST_TRICKLE\nTEST_TRICKLE\nTEST_TRICKLE\nTEST_TRICKLE\n');
+}, 'Respond by chunks with a Response being loaded');
+</script>
diff --git a/service-workers/service-worker/fetch-event-respond-with-custom-response.https.html b/service-workers/service-worker/fetch-event-respond-with-custom-response.https.html
new file mode 100644
index 0000000..645a29c
--- /dev/null
+++ b/service-workers/service-worker/fetch-event-respond-with-custom-response.https.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>respondWith with a new Response</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+'use strict';
+
+const WORKER =
+  'resources/fetch-event-respond-with-custom-response-worker.js';
+const SCOPE =
+  'resources/blank.html';
+
+// Register a service worker, then create an iframe at url.
+function iframeTest(url, callback, name) {
+  return promise_test(async t => {
+    const reg = await service_worker_unregister_and_register(t, WORKER, SCOPE);
+    add_completion_callback(() => reg.unregister());
+    await wait_for_state(t, reg.installing, 'activated');
+    const iframe = await with_iframe(url);
+    const iwin = iframe.contentWindow;
+    t.add_cleanup(() => iframe.remove());
+    await callback(t, iwin);
+  }, name);
+}
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?type=string');
+  assert_equals(await response.text(), 'PASS');
+}, 'Subresource built from a string');
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?type=blob');
+  assert_equals(await response.text(), 'PASS');
+}, 'Subresource built from a blob');
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?type=buffer');
+  assert_equals(await response.text(), 'PASS');
+}, 'Subresource built from a buffer');
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?type=buffer-view');
+  assert_equals(await response.text(), 'PASS');
+}, 'Subresource built from a buffer-view');
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?type=form-data');
+  const data = await response.formData();
+  assert_equals(data.get('result'), 'PASS');
+}, 'Subresource built from form-data');
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?type=search-params');
+  assert_equals(await response.text(), 'result=PASS');
+}, 'Subresource built from search-params');
+
+// As above, but navigations
+
+iframeTest(SCOPE + '?type=string', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'PASS');
+}, 'Navigation resource built from a string');
+
+iframeTest(SCOPE + '?type=blob', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'PASS');
+}, 'Navigation resource built from a blob');
+
+iframeTest(SCOPE + '?type=buffer', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'PASS');
+}, 'Navigation resource built from a buffer');
+
+iframeTest(SCOPE + '?type=buffer-view', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'PASS');
+}, 'Navigation resource built from a buffer-view');
+
+// Note: not testing form data for a navigation as the boundary header is lost.
+
+iframeTest(SCOPE + '?type=search-params', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'result=PASS');
+}, 'Navigation resource built from search-params');
+</script>
diff --git a/service-workers/service-worker/fetch-event-respond-with-readable-stream-chunk.https.html b/service-workers/service-worker/fetch-event-respond-with-readable-stream-chunk.https.html
new file mode 100644
index 0000000..4544a9e
--- /dev/null
+++ b/service-workers/service-worker/fetch-event-respond-with-readable-stream-chunk.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>respondWith with a response built from a ReadableStream</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+'use strict';
+
+const WORKER = 'resources/fetch-event-respond-with-readable-stream-chunk-worker.js';
+const SCOPE = 'resources/fetch-event-respond-with-readable-stream-chunk-iframe.html';
+
+promise_test(async t => {
+    var reg = await service_worker_unregister_and_register(t, WORKER, SCOPE);
+    add_completion_callback(() => reg.unregister());
+    await wait_for_state(t, reg.installing, 'activated');
+    let iframe = await with_iframe(SCOPE);
+    t.add_cleanup(() => iframe.remove());
+
+    let response = await iframe.contentWindow.fetch('body-stream');
+    assert_equals(await response.text(), 'chunk #1 chunk #2 chunk #3 chunk #4');
+}, 'Respond by chunks with a Response built from a ReadableStream');
+</script>
diff --git a/service-workers/service-worker/fetch-event-respond-with-readable-stream.https.html b/service-workers/service-worker/fetch-event-respond-with-readable-stream.https.html
index 05afb6b..75545ad 100644
--- a/service-workers/service-worker/fetch-event-respond-with-readable-stream.https.html
+++ b/service-workers/service-worker/fetch-event-respond-with-readable-stream.https.html
@@ -10,22 +10,45 @@
 const WORKER =
   'resources/fetch-event-respond-with-readable-stream-worker.js';
 const SCOPE =
-  'resources/fetch-event-respond-with-readable-stream-iframe.html';
-// Called by the iframe when done.
-var done;
-var done_was_called = new Promise(resolve => done = resolve);
+  'resources/blank.html';
 
-promise_test(t => {
-    return service_worker_unregister_and_register(t, WORKER, SCOPE)
-      .then(reg => {
-           add_completion_callback(() => reg.unregister());
-           return wait_for_state(t, reg.installing, 'activated');
-         })
-      .then(() => with_iframe(SCOPE))
-      .then(iframe => {
-          t.add_cleanup(() => iframe.remove())
-        })
-      .then(() => done_was_called)
-      .then(result => assert_equals(result, 'PASS'));
-  }, 'Respond with a Response built from a ReadableStream');
+// Register a service worker, then create an iframe at url.
+function iframeTest(url, callback, name) {
+  return promise_test(async t => {
+    const reg = await service_worker_unregister_and_register(t, WORKER, SCOPE);
+    add_completion_callback(() => reg.unregister());
+    await wait_for_state(t, reg.installing, 'activated');
+    const iframe = await with_iframe(url);
+    const iwin = iframe.contentWindow;
+    t.add_cleanup(() => iframe.remove());
+    await callback(t, iwin);
+  }, name);
+}
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?stream');
+  assert_equals(await response.text(), 'PASS');
+}, 'Subresource built from a ReadableStream');
+
+iframeTest(SCOPE + '?stream', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'PASS');
+}, 'Main resource built from a ReadableStream');
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?stream&delay');
+  assert_equals(await response.text(), 'PASS');
+}, 'Subresource built from a ReadableStream - delayed');
+
+iframeTest(SCOPE + '?stream&delay', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'PASS');
+}, 'Main resource built from a ReadableStream - delayed');
+
+iframeTest(SCOPE, async (t, iwin) => {
+  const response = await iwin.fetch('?stream&use-fetch-stream');
+  assert_equals(await response.text(), 'PASS\n');
+}, 'Subresource built from a ReadableStream - fetch stream');
+
+iframeTest(SCOPE + '?stream&use-fetch-stream', (t, iwin) => {
+  assert_equals(iwin.document.body.textContent, 'PASS\n');
+}, 'Main resource built from a ReadableStream - fetch stream');
 </script>
diff --git a/service-workers/service-worker/fetch-event.https.html b/service-workers/service-worker/fetch-event.https.html
index cb60dcf..02f8d0e 100644
--- a/service-workers/service-worker/fetch-event.https.html
+++ b/service-workers/service-worker/fetch-event.https.html
@@ -6,6 +6,9 @@
 <body>
 <script>
 var worker = 'resources/fetch-event-test-worker.js';
+function wait(ms) {
+    return new Promise(resolve => step_timeout(resolve, ms));
+}
 
 async_test(function(t) {
     const scope = 'resources/simple.html?headers';
@@ -26,10 +29,6 @@
             header_names.hasOwnProperty('accept'),
             'request includes "Accept" header as inserted by Fetch'
           );
-          assert_true(
-            header_names.hasOwnProperty('upgrade-insecure-requests'),
-            'request specifies "Upgrade-Insecure Requests header as inserted by Fetch'
-          );
 
           return service_worker_unregister_and_done(t, scope);
         })
@@ -63,6 +62,30 @@
   }, 'Service Worker responds to fetch event with string');
 
 async_test(function(t) {
+    var scope = 'resources/simple.html?string';
+    var frame;
+    service_worker_unregister_and_register(t, worker, scope)
+      .then(function(reg) {
+          return wait_for_state(t, reg.installing, 'activated');
+        })
+      .then(function() { return with_iframe(scope) })
+      .then(function(f) {
+        frame = f;
+        return frame.contentWindow.fetch(scope + "#foo")
+      })
+      .then(function(response) { return response.text() })
+      .then(function(text) {
+          assert_equals(
+            text,
+            'Test string',
+            'Service Worker should respond to fetch with a test string');
+          frame.remove();
+          return service_worker_unregister_and_done(t, scope);
+        })
+      .catch(unreached_rejection(t));
+  }, 'Service Worker responds to fetch event using request fragment with string');
+
+async_test(function(t) {
     var scope = 'resources/simple.html?blob';
     service_worker_unregister_and_register(t, worker, scope)
       .then(function(reg) {
@@ -178,50 +201,78 @@
       .catch(unreached_rejection(t));
   }, 'Service Worker fetches other file in fetch event');
 
-async_test(function(t) {
-    var scope = 'resources/simple.html?form-post';
-    var frame_name = 'xhr-post-frame';
-    service_worker_unregister_and_register(t, worker, scope)
-      .then(function(reg) {
+// Creates a form and an iframe and does a form submission that navigates the
+// frame to |action_url|. Returns the frame after navigation.
+function submit_form(action_url) {
+  return new Promise(resolve => {
+      const frame = document.createElement('iframe');
+      frame.name = 'post-frame';
+      document.body.appendChild(frame);
+      const form = document.createElement('form');
+      form.target = frame.name;
+      form.action = action_url;
+      form.method = 'post';
+      const input1 = document.createElement('input');
+      input1.type = 'text';
+      input1.value = 'testValue1';
+      input1.name = 'testName1'
+      form.appendChild(input1);
+      const input2 = document.createElement('input');
+      input2.type = 'text';
+      input2.value = 'testValue2';
+      input2.name = 'testName2'
+      form.appendChild(input2);
+      document.body.appendChild(form);
+      frame.onload = function() {
+        form.remove();
+        resolve(frame);
+      };
+      form.submit();
+    });
+}
+
+promise_test(t => {
+    const scope = 'resources/simple.html?form-post';
+    let registration;
+    return service_worker_unregister_and_register(t, worker, scope)
+      .then(reg => {
+          registration = reg;
+          add_completion_callback(() => { reg.unregister(); });
           return wait_for_state(t, reg.installing, 'activated');
         })
-      .then(function(sw) {
-         return new Promise(function(resolve) {
-            var frame = document.createElement('iframe');
-            frame.name = frame_name;
-            document.body.appendChild(frame);
-            var form = document.createElement('form');
-            form.target = frame_name;
-            form.action = scope;
-            form.method = 'post';
-            var input1 = document.createElement('input');
-            input1.type = 'text';
-            input1.value = 'testValue1';
-            input1.name = 'testName1'
-            form.appendChild(input1);
-            var input2 = document.createElement('input');
-            input2.type = 'text';
-            input2.value = 'testValue2';
-            input2.name = 'testName2'
-            form.appendChild(input2);
-            document.body.appendChild(form);
-            frame.onload = function() {
-              document.body.removeChild(form);
-              resolve(frame);
-            };
-            form.submit();
-          });
+      .then(() => {
+          return submit_form(scope);
         })
-      .then(function(frame) {
+      .then(frame => {
           assert_equals(frame.contentDocument.body.textContent,
                         'POST:application/x-www-form-urlencoded:' +
                         'testName1=testValue1&testName2=testValue2');
           frame.remove();
-          return service_worker_unregister_and_done(t, scope);
-        })
-      .catch(unreached_rejection(t));
+          return registration.unregister();
+        });
   }, 'Service Worker responds to fetch event with POST form');
 
+promise_test(t => {
+    // Add '?ignore' to scope so the service worker falls back to network.
+    const scope = 'resources/echo-content.py?ignore';
+    let registration;
+    return service_worker_unregister_and_register(t, worker, scope)
+      .then(reg => {
+          registration = reg;
+          add_completion_callback(() => { reg.unregister(); });
+          return wait_for_state(t, reg.installing, 'activated');
+        })
+      .then(() => {
+          return submit_form(scope);
+        })
+      .then(frame => {
+          assert_equals(frame.contentDocument.body.textContent,
+                        'testName1=testValue1&testName2=testValue2');
+          frame.remove();
+          return registration.unregister();
+        });
+  }, 'Service Worker falls back to network in fetch event with POST form');
+
 async_test(function(t) {
     var scope = 'resources/simple.html?multiple-respond-with';
     service_worker_unregister_and_register(t, worker, scope)
@@ -460,6 +511,43 @@
         });
   }, 'FetchEvent#body is a string');
 
+// Test that the request body is sent to network upon network fallback,
+// for a string body.
+promise_test(t => {
+    // Set scope to "?ignore" so the service worker falls back to network
+    // for the main resource request, and add a suffix to avoid colliding
+    // with other tests.
+    const scope = 'resources/?ignore-for-request-body-fallback-string';
+    let frame;
+
+    return service_worker_unregister_and_register(t, worker, scope)
+      .then(reg => {
+          add_completion_callback(() => { reg.unregister(); });
+          return wait_for_state(t, reg.installing, 'activated');
+        })
+      .then(() => {
+          return with_iframe(scope); })
+      .then(f => {
+          frame = f;
+          // Add "?ignore" so the service worker falls back to echo-content.py.
+          const echo_url = '/fetch/api/resources/echo-content.py?ignore';
+          return frame.contentWindow.fetch(echo_url, {
+              method: 'POST',
+              body: 'i am the request body'
+            });
+        })
+      .then(response => {
+          return response.text();
+        })
+      .then(response_text => {
+          frame.remove();
+          assert_equals(
+              response_text,
+              'i am the request body',
+              'the network fallback request should include the request body');
+        });
+  }, 'FetchEvent#body is a string and is passed to network fallback');
+
 // Test that the service worker can read FetchEvent#body when it is a blob.
 // It responds with request body it read.
 promise_test(t => {
@@ -493,6 +581,44 @@
         });
   }, 'FetchEvent#body is a blob');
 
+// Test that the request body is sent to network upon network fallback,
+// for a blob body.
+promise_test(t => {
+    // Set scope to "?ignore" so the service worker falls back to network
+    // for the main resource request, and add a suffix to avoid colliding
+    // with other tests.
+    const scope = 'resources/simple.html?ignore-for-request-body-fallback-blob';
+    let frame;
+
+    return service_worker_unregister_and_register(t, worker, scope)
+      .then(reg => {
+          add_completion_callback(() => { reg.unregister(); });
+          return wait_for_state(t, reg.installing, 'activated');
+        })
+      .then(() => {
+          return with_iframe(scope); })
+      .then(f => {
+          frame = f;
+          const blob = new Blob(['it\'s me the blob', ' ', 'and more blob!']);
+          // Add "?ignore" so the service worker falls back to echo-content.py.
+          const echo_url = '/fetch/api/resources/echo-content.py?ignore';
+          return frame.contentWindow.fetch(echo_url, {
+              method: 'POST',
+              body: blob
+            });
+        })
+      .then(response => {
+          return response.text();
+        })
+      .then(response_text => {
+          frame.remove();
+          assert_equals(
+              response_text,
+              'it\'s me the blob and more blob!',
+              'the network fallback request should include the request body');
+        });
+  }, 'FetchEvent#body is a blob and is passed to network fallback');
+
 promise_test(async (t) => {
     const scope = 'resources/simple.html?keepalive';
 
@@ -506,5 +632,143 @@
     frame.remove();
     await service_worker_unregister_and_done(t, scope);
   }, 'Service Worker responds to fetch event with the correct keepalive value');
+
+promise_test(async (t) => {
+    const scope = 'resources/simple.html?isReloadNavigation';
+    let frame;
+    let reg;
+    try {
+      reg = await service_worker_unregister_and_register(t, worker, scope);
+      await wait_for_state(t, reg.installing, 'activated');
+      frame = await with_iframe(scope);
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = false');
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        frame.contentWindow.location.reload();
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = true');
+    } finally {
+      if (frame) {
+        frame.remove();
+      }
+      if (reg) {
+        await reg.unregister();
+      }
+    }
+  }, 'FetchEvent#request.isReloadNavigation is true (location.reload())');
+
+promise_test(async (t) => {
+    const scope = 'resources/simple.html?isReloadNavigation';
+    let frame;
+    let reg;
+    try {
+      reg = await service_worker_unregister_and_register(t, worker, scope);
+      await wait_for_state(t, reg.installing, 'activated');
+      frame = await with_iframe(scope);
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = false');
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        frame.contentWindow.history.go(0);
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = true');
+    } finally {
+      if (frame) {
+        frame.remove();
+      }
+      if (reg) {
+        await reg.unregister();
+      }
+    }
+  }, 'FetchEvent#request.isReloadNavigation is true (history.go(0))');
+
+promise_test(async (t) => {
+    const scope = 'resources/simple.html?isReloadNavigation';
+    let frame;
+    let reg;
+
+    try {
+      reg = await service_worker_unregister_and_register(t, worker, scope);
+      await wait_for_state(t, reg.installing, 'activated');
+      frame = await with_iframe(scope);
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = false');
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        const form = frame.contentDocument.createElement('form');
+        form.method = 'POST';
+        form.name = 'form';
+        form.action = new Request(scope).url;
+        frame.contentDocument.body.appendChild(form);
+        form.submit();
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = POST, isReloadNavigation = false');
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        frame.contentWindow.location.reload();
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = POST, isReloadNavigation = true');
+    } finally {
+      if (frame) {
+        frame.remove();
+      }
+      if (reg) {
+        await reg.unregister();
+      }
+    }
+  }, 'FetchEvent#request.isReloadNavigation is true (POST + location.reload())');
+
+promise_test(async (t) => {
+    const scope = 'resources/simple.html?isReloadNavigation';
+    const anotherUrl = new Request('resources/simple.html').url;
+    let frame;
+    let reg;
+
+    try {
+      reg = await service_worker_unregister_and_register(t, worker, scope);
+      await wait_for_state(t, reg.installing, 'activated');
+      frame = await with_iframe(scope);
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = false');
+      // Use step_timeout(0) to ensure the history entry is created for Blink
+      // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
+      await wait(0);
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        frame.src = anotherUrl;
+      });
+      assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        frame.contentWindow.history.go(-1);
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = false');
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        frame.contentWindow.history.go(0);
+      });
+      assert_equals(frame.contentDocument.body.textContent,
+                    'method = GET, isReloadNavigation = true');
+      await new Promise((resolve) => {
+        frame.addEventListener('load', resolve);
+        frame.contentWindow.history.go(1);
+      });
+      assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
+    } finally {
+      if (frame) {
+        frame.remove();
+      }
+      if (reg) {
+        await reg.unregister();
+      }
+    }
+  }, 'FetchEvent#request.isReloadNavigation is true (with history traversal)');
+
 </script>
 </body>
diff --git a/service-workers/service-worker/fetch-request-redirect.https.html b/service-workers/service-worker/fetch-request-redirect.https.html
index 18bfa61..e8e90cf 100644
--- a/service-workers/service-worker/fetch-request-redirect.https.html
+++ b/service-workers/service-worker/fetch-request-redirect.https.html
@@ -4,19 +4,22 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="/common/media.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
 <script>
-
+var test_scope = ""
 function assert_resolves(promise, description) {
-  return promise.catch(function(reason) {
-      throw new Error(description + ' - ' + reason.message);
-  });
+    return promise.then(
+        () => test(() => {}, description + " - " + test_scope),
+        (e) => test(() => { throw e; }, description + " - " + test_scope)
+    );
 }
 
 function assert_rejects(promise, description) {
-  return promise.then(
-      function() { throw new Error(description); },
-      function() {});
+    return promise.then(
+        () => test(() => { assert_unreached(); }, description + " - " + test_scope),
+        () => test(() => {}, description + " - " + test_scope)
+    );
 }
 
 function iframe_test(url, timeout_enabled) {
@@ -52,11 +55,13 @@
 }
 
 promise_test(function(t) {
+    test_scope = "default";
+
     var SCOPE = 'resources/fetch-request-redirect-iframe.html';
     var SCRIPT = 'resources/fetch-rewrite-worker.js';
     var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect=';
     var IMAGE_URL = base_path() + 'resources/square.png';
-    var AUDIO_URL = base_path() + 'resources/silence.oga';
+    var AUDIO_URL = getAudioURI("/media/sound_5");
     var XHR_URL = base_path() + 'resources/simple.txt';
     var HTML_URL = base_path() + 'resources/dummy.html';
 
@@ -73,108 +78,106 @@
           return wait_for_state(t, worker, 'activated');
         })
       .then(function() { return with_iframe(SCOPE); })
-      .then(function(f) {
-          frame = f;
-          return Promise.all([
+      .then(async function(f) {
+            frame = f;
             // XMLHttpRequest tests.
-            assert_resolves(frame.contentWindow.xhr(XHR_URL),
-                            'Normal XHR should succeed.'),
-            assert_resolves(frame.contentWindow.xhr(REDIRECT_TO_XHR_URL),
-                            'Redirected XHR should succeed.'),
-            assert_resolves(
+            await assert_resolves(frame.contentWindow.xhr(XHR_URL),
+                            'Normal XHR should succeed.');
+            await assert_resolves(frame.contentWindow.xhr(REDIRECT_TO_XHR_URL),
+                            'Redirected XHR should succeed.');
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&redirect-mode=follow'),
-                'Redirected XHR with Request.redirect=follow should succeed.'),
-            assert_rejects(
+                'Redirected XHR with Request.redirect=follow should succeed.');
+            await assert_rejects(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&redirect-mode=error'),
-                'Redirected XHR with Request.redirect=error should fail.'),
-            assert_rejects(
+                'Redirected XHR with Request.redirect=error should fail.');
+            await assert_rejects(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&redirect-mode=manual'),
-                'Redirected XHR with Request.redirect=manual should fail.'),
+                'Redirected XHR with Request.redirect=manual should fail.');
 
             // Image loading tests.
-            assert_resolves(frame.contentWindow.load_image(IMAGE_URL),
-                            'Normal image resource should be loaded.'),
-            assert_resolves(
+            await assert_resolves(frame.contentWindow.load_image(IMAGE_URL),
+                            'Normal image resource should be loaded.');
+            await assert_resolves(
                 frame.contentWindow.load_image(REDIRECT_TO_IMAGE_URL),
-                'Redirected image resource should be loaded.'),
-            assert_resolves(
+                'Redirected image resource should be loaded.');
+            await assert_resolves(
                 frame.contentWindow.load_image(
                     './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) +
                     '&redirect-mode=follow'),
                 'Loading redirected image with Request.redirect=follow should' +
-                ' succeed.'),
-            assert_rejects(
+                ' succeed.');
+            await assert_rejects(
                 frame.contentWindow.load_image(
                     './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) +
                     '&redirect-mode=error'),
                 'Loading redirected image with Request.redirect=error should ' +
-                'fail.'),
-            assert_rejects(
+                'fail.');
+            await assert_rejects(
                 frame.contentWindow.load_image(
                     './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) +
                     '&redirect-mode=manual'),
                 'Loading redirected image with Request.redirect=manual should' +
-                ' fail.'),
+                ' fail.');
 
             // Audio loading tests.
-            assert_resolves(frame.contentWindow.load_audio(AUDIO_URL),
-                            'Normal audio resource should be loaded.'),
-            assert_resolves(
+            await assert_resolves(frame.contentWindow.load_audio(AUDIO_URL),
+                            'Normal audio resource should be loaded.');
+            await assert_resolves(
                 frame.contentWindow.load_audio(REDIRECT_TO_AUDIO_URL),
-                'Redirected audio resource should be loaded.'),
-            assert_resolves(
+                'Redirected audio resource should be loaded.');
+            await assert_resolves(
                 frame.contentWindow.load_audio(
                     './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) +
                     '&redirect-mode=follow'),
                 'Loading redirected audio with Request.redirect=follow should' +
-                ' succeed.'),
-            assert_rejects(
+                ' succeed.');
+            await assert_rejects(
                 frame.contentWindow.load_audio(
                     './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) +
                     '&redirect-mode=error'),
                 'Loading redirected audio with Request.redirect=error should ' +
-                'fail.'),
-            assert_rejects(
+                'fail.');
+            await assert_rejects(
                 frame.contentWindow.load_audio(
                     './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) +
                     '&redirect-mode=manual'),
                 'Loading redirected audio with Request.redirect=manual should' +
-                ' fail.'),
+                ' fail.');
 
             // Iframe tests.
-            assert_resolves(iframe_test(HTML_URL),
-                            'Normal iframe loading should succeed.'),
-            assert_resolves(
+            await assert_resolves(iframe_test(HTML_URL),
+                            'Normal iframe loading should succeed.');
+            await assert_resolves(
                 iframe_test(REDIRECT_TO_HTML_URL),
-                'Normal redirected iframe loading should succeed.'),
-            assert_rejects(
+                'Normal redirected iframe loading should succeed.');
+            await assert_rejects(
                 iframe_test(SCOPE + '?url=' +
                             encodeURIComponent(REDIRECT_TO_HTML_URL) +
                             '&redirect-mode=follow',
                             true /* timeout_enabled */),
                 'Redirected iframe loading with Request.redirect=follow should'+
-                ' fail.'),
-            assert_rejects(
+                ' fail.');
+            await assert_rejects(
                 iframe_test(SCOPE + '?url=' +
                             encodeURIComponent(REDIRECT_TO_HTML_URL) +
                             '&redirect-mode=error',
                             true /* timeout_enabled */),
                 'Redirected iframe loading with Request.redirect=error should '+
-                'fail.'),
-            assert_resolves(
+                'fail.');
+            await assert_resolves(
                 iframe_test(SCOPE + '?url=' +
                             encodeURIComponent(REDIRECT_TO_HTML_URL) +
                             '&redirect-mode=manual',
                             true /* timeout_enabled */),
                 'Redirected iframe loading with Request.redirect=manual should'+
-                ' succeed.'),
-          ]);
+                ' succeed.');
         })
       .then(function() {
           frame.remove();
@@ -184,6 +187,8 @@
 
 // test for reponse.redirected
 promise_test(function(t) {
+    test_scope = "redirected";
+
     var SCOPE = 'resources/fetch-request-redirect-iframe.html';
     var SCRIPT = 'resources/fetch-rewrite-worker.js';
     var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect=';
@@ -207,35 +212,34 @@
           return wait_for_state(t, worker, 'activated');
         })
       .then(function() { return with_iframe(SCOPE); })
-      .then(function(f) {
+      .then(async function(f) {
           frame = f;
-          return Promise.all([
             // XMLHttpRequest tests.
-            assert_resolves(
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(XHR_URL) +
                     '&expected_redirected=false' +
                     '&expected_resolves=true'),
                 'Normal XHR should be resolved and response should not be ' +
-                'redirected.'),
-            assert_resolves(
+                'redirected.');
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&expected_redirected=true' +
                     '&expected_resolves=true'),
                 'Redirected XHR should be resolved and response should be ' +
-                'redirected.'),
+                'redirected.');
 
             // tests for request's mode = cors
-            assert_resolves(
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(XHR_URL) +
                     '&mode=cors' +
                     '&expected_redirected=false' +
                     '&expected_resolves=true'),
                 'Normal XHR should be resolved and response should not be ' +
-                'redirected even with CORS mode.'),
-            assert_resolves(
+                'redirected even with CORS mode.');
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&mode=cors' +
@@ -243,12 +247,12 @@
                     '&expected_redirected=true' +
                     '&expected_resolves=true'),
                 'Redirected XHR should be resolved and response.redirected ' +
-                'should be redirected with CORS mode.'),
+                'should be redirected with CORS mode.');
 
             // tests for request's mode = no-cors
             // The response.redirect should be false since we will not add
             // redirected url list when redirect-mode is not follow.
-            assert_rejects(
+            await assert_rejects(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&mode=no-cors' +
@@ -256,19 +260,18 @@
                     '&expected_redirected=false' +
                     '&expected_resolves=false'),
                 'Redirected XHR should be reject and response should be ' +
-                'redirected with NO-CORS mode and redirect-mode=manual.'),
+                'redirected with NO-CORS mode and redirect-mode=manual.');
 
             // tests for redirecting to a cors
-            assert_resolves(
+            await assert_resolves(
                 frame.contentWindow.load_image(
                     './?url=' + encodeURIComponent(REDIRECT_TO_CROSS_ORIGIN) +
                     '&mode=no-cors' +
                     '&redirect-mode=follow' +
                     '&expected_redirected=false' +
                     '&expected_resolves=true'),
-                'Redirected COS image should be reject and response should ' +
-                'not be redirected with NO-CORS mode.'),
-          ]);
+                'Redirected CORS image should be reject and response should ' +
+                'not be redirected with NO-CORS mode.');
         })
       .then(function() {
           frame.remove();
@@ -278,6 +281,8 @@
 
 // test for reponse.redirected after cached
 promise_test(function(t) {
+    test_scope = "cache";
+
     var SCOPE = 'resources/fetch-request-redirect-iframe.html';
     var SCRIPT = 'resources/fetch-rewrite-worker.js';
     var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect=';
@@ -301,29 +306,28 @@
           return wait_for_state(t, worker, 'activated');
         })
       .then(function() { return with_iframe(SCOPE); })
-      .then(function(f) {
+      .then(async function(f) {
           frame = f;
-          return Promise.all([
             // XMLHttpRequest tests.
-            assert_resolves(
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(XHR_URL) +
                     '&expected_redirected=false' +
                     '&expected_resolves=true' +
                     '&cache'),
                 'Normal XHR should be resolved and response should not be ' +
-                'redirected.'),
-            assert_resolves(
+                'redirected.');
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&expected_redirected=true' +
                     '&expected_resolves=true' +
                     '&cache'),
                 'Redirected XHR should be resolved and response should be ' +
-                'redirected.'),
+                'redirected.');
 
             // tests for request's mode = cors
-            assert_resolves(
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(XHR_URL) +
                     '&mode=cors' +
@@ -331,8 +335,8 @@
                     '&expected_resolves=true' +
                     '&cache'),
                 'Normal XHR should be resolved and response should not be ' +
-                'redirected even with CORS mode.'),
-            assert_resolves(
+                'redirected even with CORS mode.');
+            await assert_resolves(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&mode=cors' +
@@ -341,12 +345,12 @@
                     '&expected_resolves=true' +
                     '&cache'),
                 'Redirected XHR should be resolved and response.redirected ' +
-                'should be redirected with CORS mode.'),
+                'should be redirected with CORS mode.');
 
             // tests for request's mode = no-cors
             // The response.redirect should be false since we will not add
             // redirected url list when redirect-mode is not follow.
-            assert_rejects(
+            await assert_rejects(
                 frame.contentWindow.xhr(
                     './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) +
                     '&mode=no-cors' +
@@ -355,10 +359,10 @@
                     '&expected_resolves=false' +
                     '&cache'),
                 'Redirected XHR should be reject and response should be ' +
-                'redirected with NO-CORS mode and redirect-mode=manual.'),
+                'redirected with NO-CORS mode and redirect-mode=manual.');
 
             // tests for redirecting to a cors
-            assert_resolves(
+            await assert_resolves(
                 frame.contentWindow.load_image(
                     './?url=' + encodeURIComponent(REDIRECT_TO_CROSS_ORIGIN) +
                     '&mode=no-cors' +
@@ -366,9 +370,8 @@
                     '&expected_redirected=false' +
                     '&expected_resolves=true' +
                     '&cache'),
-                'Redirected COS image should be reject and response should ' +
-                'not be redirected with NO-CORS mode.'),
-          ]);
+                'Redirected CORS image should be reject and response should ' +
+                'not be redirected with NO-CORS mode.');
         })
       .then(function() {
           frame.remove();
diff --git a/service-workers/service-worker/fetch-request-resources.https.html b/service-workers/service-worker/fetch-request-resources.https.html
index 92ef468..50421b4 100644
--- a/service-workers/service-worker/fetch-request-resources.https.html
+++ b/service-workers/service-worker/fetch-request-resources.https.html
@@ -8,6 +8,14 @@
 var url_count = 0;
 var expected_results = {};
 
+function add_promise_to_test(url)
+{
+  var expected = expected_results[url];
+  return new Promise((resolve) => {
+    expected.resolve = resolve;
+  });
+}
+
 function image_test(frame, url, cross_origin, expected_mode,
                     expected_credentials) {
   var actual_url = url + (++url_count);
@@ -17,10 +25,12 @@
       credentials: expected_credentials,
       redirect: 'follow',
       integrity: '',
+      destination: 'image',
       message: 'Image load (url:' +
                actual_url + ' cross_origin:' + cross_origin + ')'
     };
-  return frame.contentWindow.load_image(actual_url, cross_origin);
+  frame.contentWindow.load_image(actual_url, cross_origin);
+  return add_promise_to_test(actual_url);
 }
 
 function script_test(frame, url, cross_origin, expected_mode,
@@ -32,10 +42,12 @@
       credentials: expected_credentials,
       redirect: 'follow',
       integrity: '',
+      destination: 'script',
       message: 'Script load (url:' +
                actual_url + ' cross_origin:' + cross_origin + ')'
     };
-  return frame.contentWindow.load_script(actual_url, cross_origin);
+  frame.contentWindow.load_script(actual_url, cross_origin);
+  return add_promise_to_test(actual_url);
 }
 
 function css_test(frame, url, cross_origin, expected_mode,
@@ -47,10 +59,12 @@
       credentials: expected_credentials,
       redirect: 'follow',
       integrity: '',
+      destination: 'style',
       message: 'CSS load (url:' +
                actual_url + ' cross_origin:' + cross_origin + ')'
     };
-  return frame.contentWindow.load_css(actual_url, cross_origin);
+  frame.contentWindow.load_css(actual_url, cross_origin);
+  return add_promise_to_test(actual_url);
 }
 
 function font_face_test(frame, url, expected_mode, expected_credentials) {
@@ -61,9 +75,11 @@
       credentials: expected_credentials,
       redirect: 'follow',
       integrity: '',
+      destination: 'font',
       message: 'FontFace load (url:' + actual_url + ')'
     };
-  return frame.contentWindow.load_font(actual_url);
+  frame.contentWindow.load_font(actual_url);
+  return add_promise_to_test(actual_url);
 }
 
 function script_integrity_test(frame, url, integrity, expected_integrity) {
@@ -74,9 +90,11 @@
       credentials: 'include',
       redirect: 'follow',
       integrity: expected_integrity,
+      destination: 'script',
       message: 'Script load (url:' + actual_url + ')'
     };
-  return frame.contentWindow.load_script_with_integrity(actual_url, integrity);
+  frame.contentWindow.load_script_with_integrity(actual_url, integrity);
+  return add_promise_to_test(actual_url);
 }
 
 function css_integrity_test(frame, url, integrity, expected_integrity) {
@@ -87,9 +105,11 @@
       credentials: 'include',
       redirect: 'follow',
       integrity: expected_integrity,
+      destination: 'style',
       message: 'CSS load (url:' + actual_url + ')'
     };
-  return frame.contentWindow.load_css_with_integrity(actual_url, integrity);
+  frame.contentWindow.load_css_with_integrity(actual_url, integrity);
+  return add_promise_to_test(actual_url);
 }
 
 function fetch_test(frame, url, mode, credentials,
@@ -100,11 +120,14 @@
       credentials: expected_credentials,
       redirect: 'follow',
       integrity: '',
+      destination: '',
       message: 'fetch (url:' + actual_url + ' mode:' + mode + ' credentials:' +
                credentials + ')'
     };
-  return frame.contentWindow.fetch(
-      new Request(actual_url, {mode: mode, credentials: credentials}));
+  frame.contentWindow.fetch(
+      new Request(actual_url, {mode: mode, credentials: credentials})).then(() => {
+      }, () => { });
+  return add_promise_to_test(actual_url);
 }
 
 function audio_test(frame, url, cross_origin,
@@ -115,13 +138,32 @@
       credentials: expected_credentials,
       redirect: 'follow',
       integrity: '',
+      destination: 'audio',
       message: 'Audio load (url:' + actual_url + ' cross_origin:' +
                cross_origin + ')'
     };
-  return frame.contentWindow.load_audio(actual_url, cross_origin);
+  frame.contentWindow.load_audio(actual_url, cross_origin);
+  return add_promise_to_test(actual_url);
 }
 
-async_test(function(t) {
+
+function video_test(frame, url, cross_origin,
+                    expected_mode, expected_credentials) {
+  var actual_url = url + (++url_count);
+  expected_results[actual_url] = {
+      mode: expected_mode,
+      credentials: expected_credentials,
+      redirect: 'follow',
+      integrity: '',
+      destination: 'video',
+      message: 'Video load (url:' + actual_url + ' cross_origin:' +
+               cross_origin + ')'
+    };
+  frame.contentWindow.load_video(actual_url, cross_origin);
+  return add_promise_to_test(actual_url);
+}
+
+promise_test(function(t) {
     var SCOPE = 'resources/fetch-request-resources-iframe.https.html';
     var SCRIPT = 'resources/fetch-request-resources-worker.js';
     var host_info = get_host_info();
@@ -131,13 +173,13 @@
       host_info['HTTPS_REMOTE_ORIGIN'] + base_path() + 'resources/dummy?test';
     var worker;
     var frame;
-    service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+    return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
       .then(function(registration) {
           worker = registration.installing;
           return wait_for_state(t, worker, 'activated');
         })
       .then(function() {
-          return new Promise(function(resolve) {
+          return new Promise(function(resolve, reject) {
               var channel = new MessageChannel();
               channel.port1.onmessage = t.step_func(function(msg) {
                 if (msg.data.ready) {
@@ -149,110 +191,121 @@
                 if (!expected) {
                   return;
                 }
-                assert_equals(
+                test(() => {
+                  assert_equals(
                     result.mode, expected.mode,
                     'mode of ' + expected.message +  ' must be ' +
                     expected.mode + '.');
-                assert_equals(
+                  assert_equals(
                     result.credentials, expected.credentials,
                     'credentials of ' + expected.message +  ' must be ' +
                     expected.credentials + '.');
-                 assert_equals(
+                   assert_equals(
                     result.redirect, expected.redirect,
                     'redirect mode of ' + expected.message +  ' must be ' +
                     expected.redirect + '.');
-                assert_equals(
+                  assert_equals(
                     result.integrity, expected.integrity,
                     'integrity of ' + expected.message +  ' must be ' +
                     expected.integrity + '.');
-                --url_count;
+                  assert_equals(
+                    result.destination, expected.destination,
+                    'destination of ' + expected.message +  ' must be ' +
+                    expected.destination + '.');
+                }, expected.message);
+                expected.resolve();
                 delete expected_results[result.url];
-                if (url_count == 0) {
-                  frame.remove();
-                  service_worker_unregister_and_done(t, SCOPE);
-                }
               });
               worker.postMessage(
                 {port: channel.port2}, [channel.port2]);
             });
         })
       .then(function() { return with_iframe(SCOPE); })
-      .then(function(f) {
+      .then(async function(f) {
         frame = f;
 
-        image_test(f, LOCAL_URL, '', 'no-cors', 'include');
-        image_test(f, REMOTE_URL, '', 'no-cors', 'include');
-        css_test(f, LOCAL_URL, '', 'no-cors', 'include');
-        css_test(f, REMOTE_URL, '', 'no-cors', 'include');
+        await image_test(f, LOCAL_URL, '', 'no-cors', 'include');
+        await image_test(f, REMOTE_URL, '', 'no-cors', 'include');
+        await css_test(f, LOCAL_URL, '', 'no-cors', 'include');
+        await css_test(f, REMOTE_URL, '', 'no-cors', 'include');
 
-        image_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
-        image_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
-        image_test(f, REMOTE_URL, '', 'no-cors', 'include');
-        image_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
-        image_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+        await image_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+        await image_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+        await image_test(f, REMOTE_URL, '', 'no-cors', 'include');
+        await image_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+        await image_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
 
-        script_test(f, LOCAL_URL, '', 'no-cors', 'include');
-        script_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
-        script_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
-        script_test(f, REMOTE_URL, '', 'no-cors', 'include');
-        script_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
-        script_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+        await script_test(f, LOCAL_URL, '', 'no-cors', 'include');
+        await script_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+        await script_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+        await script_test(f, REMOTE_URL, '', 'no-cors', 'include');
+        await script_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+        await script_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
 
-        css_test(f, LOCAL_URL, '', 'no-cors', 'include');
-        css_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
-        css_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
-        css_test(f, REMOTE_URL, '', 'no-cors', 'include');
-        css_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
-        css_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+        await css_test(f, LOCAL_URL, '', 'no-cors', 'include');
+        await css_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+        await css_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+        await css_test(f, REMOTE_URL, '', 'no-cors', 'include');
+        await css_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+        await css_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
 
-        font_face_test(f, LOCAL_URL, 'cors', 'same-origin');
-        font_face_test(f, REMOTE_URL, 'cors', 'same-origin');
+        await font_face_test(f, LOCAL_URL, 'cors', 'same-origin');
+        await font_face_test(f, REMOTE_URL, 'cors', 'same-origin');
 
-        script_integrity_test(f, LOCAL_URL, '     ', '     ');
-        script_integrity_test(f, LOCAL_URL,
-                              'This is not a valid integrity because it has no dashes',
-                              'This is not a valid integrity because it has no dashes');
-        script_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-');
-        script_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123');
-        script_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc ');
-        script_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc');
+        await script_integrity_test(f, LOCAL_URL, '     ', '     ');
+        await script_integrity_test(f, LOCAL_URL,
+                               'This is not a valid integrity because it has no dashes',
+                               'This is not a valid integrity because it has no dashes');
+        await script_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-');
+        await script_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123');
+        await script_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc ');
+        await script_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc');
 
-        css_integrity_test(f, LOCAL_URL, '     ', '     ');
-        css_integrity_test(f, LOCAL_URL,
-                           'This is not a valid integrity because it has no dashes',
-                           'This is not a valid integrity because it has no dashes');
-        css_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-');
-        css_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123');
-        css_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc ');
-        css_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc');
+        await css_integrity_test(f, LOCAL_URL, '     ', '     ');
+        await css_integrity_test(f, LOCAL_URL,
+                            'This is not a valid integrity because it has no dashes',
+                            'This is not a valid integrity because it has no dashes');
+        await css_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-');
+        await css_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123');
+        await css_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc ');
+        await css_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc');
 
-        fetch_test(f, LOCAL_URL, 'same-origin', 'omit', 'same-origin', 'omit');
-        fetch_test(f, LOCAL_URL, 'same-origin', 'same-origin',
-                   'same-origin', 'same-origin');
-        fetch_test(f, LOCAL_URL, 'same-origin', 'include',
-                   'same-origin', 'include');
-        fetch_test(f, LOCAL_URL, 'no-cors', 'omit', 'no-cors', 'omit');
-        fetch_test(f, LOCAL_URL, 'no-cors', 'same-origin',
-                   'no-cors', 'same-origin');
-        fetch_test(f, LOCAL_URL, 'no-cors', 'include', 'no-cors', 'include');
-        fetch_test(f, LOCAL_URL, 'cors', 'omit', 'cors', 'omit');
-        fetch_test(f, LOCAL_URL, 'cors', 'same-origin', 'cors', 'same-origin');
-        fetch_test(f, LOCAL_URL, 'cors', 'include', 'cors', 'include');
-        fetch_test(f, REMOTE_URL, 'no-cors', 'omit', 'no-cors', 'omit');
-        fetch_test(f, REMOTE_URL, 'no-cors', 'same-origin',
-                   'no-cors', 'same-origin');
-        fetch_test(f, REMOTE_URL, 'no-cors', 'include', 'no-cors', 'include');
-        fetch_test(f, REMOTE_URL, 'cors', 'omit', 'cors', 'omit');
-        fetch_test(f, REMOTE_URL, 'cors', 'same-origin', 'cors', 'same-origin');
-        fetch_test(f, REMOTE_URL, 'cors', 'include', 'cors', 'include');
+        await fetch_test(f, LOCAL_URL, 'same-origin', 'omit', 'same-origin', 'omit');
+        await fetch_test(f, LOCAL_URL, 'same-origin', 'same-origin',
+                    'same-origin', 'same-origin');
+        await fetch_test(f, LOCAL_URL, 'same-origin', 'include',
+                    'same-origin', 'include');
+        await fetch_test(f, LOCAL_URL, 'no-cors', 'omit', 'no-cors', 'omit');
+        await fetch_test(f, LOCAL_URL, 'no-cors', 'same-origin',
+                    'no-cors', 'same-origin');
+        await fetch_test(f, LOCAL_URL, 'no-cors', 'include', 'no-cors', 'include');
+        await fetch_test(f, LOCAL_URL, 'cors', 'omit', 'cors', 'omit');
+        await fetch_test(f, LOCAL_URL, 'cors', 'same-origin', 'cors', 'same-origin');
+        await fetch_test(f, LOCAL_URL, 'cors', 'include', 'cors', 'include');
+        await fetch_test(f, REMOTE_URL, 'no-cors', 'omit', 'no-cors', 'omit');
+        await fetch_test(f, REMOTE_URL, 'no-cors', 'same-origin',
+                    'no-cors', 'same-origin');
+        await fetch_test(f, REMOTE_URL, 'no-cors', 'include', 'no-cors', 'include');
+        await fetch_test(f, REMOTE_URL, 'cors', 'omit', 'cors', 'omit');
+        await fetch_test(f, REMOTE_URL, 'cors', 'same-origin', 'cors', 'same-origin');
+        await fetch_test(f, REMOTE_URL, 'cors', 'include', 'cors', 'include');
 
-        audio_test(f, LOCAL_URL, '', 'no-cors', 'include');
-        audio_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
-        audio_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
-        audio_test(f, REMOTE_URL, '', 'no-cors', 'include');
-        audio_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
-        audio_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
-      })
-      .catch(unreached_rejection(t));
+        await audio_test(f, LOCAL_URL, '', 'no-cors', 'include');
+        await audio_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+        await audio_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+        await audio_test(f, REMOTE_URL, '', 'no-cors', 'include');
+        await audio_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+        await audio_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+
+        await video_test(f, LOCAL_URL, '', 'no-cors', 'include');
+        await video_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin');
+        await video_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include');
+        await video_test(f, REMOTE_URL, '', 'no-cors', 'include');
+        await video_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin');
+        await video_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include');
+
+        frame.remove();
+        service_worker_unregister(t, SCOPE);
+      }).catch(unreached_rejection(t));
   }, 'Verify FetchEvent for resources.');
 </script>
diff --git a/service-workers/service-worker/fetch-request-xhr-sync-on-worker.https.html b/service-workers/service-worker/fetch-request-xhr-sync-on-worker.https.html
new file mode 100644
index 0000000..9f18096
--- /dev/null
+++ b/service-workers/service-worker/fetch-request-xhr-sync-on-worker.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Service Worker: Synchronous XHR on Worker is intercepted</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+'use strict';
+
+promise_test((t) => {
+    const url = 'resources/fetch-request-xhr-sync-on-worker-worker.js';
+    const scope = 'resources/fetch-request-xhr-sync-on-worker-scope/';
+    const non_existent_file = 'non-existent-file.txt';
+
+    // In Chromium, the service worker scope matching for workers is based on
+    // the URL of the parent HTML. So this test creates an iframe which is
+    // controlled by the service worker first, and creates a worker from the
+    // iframe.
+    return service_worker_unregister_and_register(t, url, scope)
+      .then((registration) => {
+          t.add_cleanup(() => registration.unregister());
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(() => { return with_iframe(scope + 'iframe_page'); })
+      .then((frame) => {
+          t.add_cleanup(() => frame.remove());
+          return frame.contentWindow.performSyncXHROnWorker(non_existent_file);
+        })
+      .then((result) => {
+          assert_equals(
+              result.status,
+              200,
+              'HTTP response status code for intercepted request'
+            );
+          assert_equals(
+            result.responseText,
+              'Response from service worker',
+              'HTTP response text for intercepted request'
+            );
+        });
+  }, 'Verify SyncXHR on Worker is intercepted');
+</script>
diff --git a/service-workers/service-worker/fetch-response-taint.https.html b/service-workers/service-worker/fetch-response-taint.https.html
index a6e7f98..8ebee0c 100644
--- a/service-workers/service-worker/fetch-response-taint.https.html
+++ b/service-workers/service-worker/fetch-response-taint.https.html
@@ -193,6 +193,9 @@
   // Fetch to the other origin with same-origin mode should fail.
   if (origin == OTHER_ORIGIN && mode == 'same-origin') {
     ng_test(url, mode, credentials);
+  } else if (origin == BASE_ORIGIN && mode == 'same-origin') {
+    // Cors type response to a same-origin mode request should fail
+    ng_test(url, mode, credentials);
   } else {
     // The response from the SW should be cors.
     ok_test(url, mode, credentials, 'cors', 'undefined');
@@ -208,6 +211,9 @@
   // Fetch to the other origin with same-origin mode should fail.
   if (origin == OTHER_ORIGIN && mode == 'same-origin') {
     ng_test(url, mode, credentials);
+  } else if (origin == BASE_ORIGIN && mode == 'same-origin') {
+    // Cors type response to a same-origin mode request should fail
+    ng_test(url, mode, credentials);
   } else {
     // The response from the SW should be cors.
     ok_test(url, mode, credentials, 'cors', 'username1s');
diff --git a/service-workers/service-worker/http-to-https-redirect-and-register.https.html b/service-workers/service-worker/http-to-https-redirect-and-register.https.html
index d78b23a..d1c6678 100644
--- a/service-workers/service-worker/http-to-https-redirect-and-register.https.html
+++ b/service-workers/service-worker/http-to-https-redirect-and-register.https.html
@@ -8,11 +8,13 @@
 <script>
 'use strict';
 
+var host_info = get_host_info();
+
 // Loads a non-secure url in a new window, which redirects to |target_url|.
 // That page then registers a service worker, and messages back with the result.
 // Returns a promise that resolves with the result.
 function redirect_and_register(target_url) {
-  var redirect_url = get_host_info()['UNAUTHENTICATED_ORIGIN'] + base_path() +
+  var redirect_url = host_info.HTTP_REMOTE_ORIGIN + base_path() +
     'resources/redirect.py?Redirect=';
   var child = window.open(redirect_url + encodeURIComponent(target_url));
   return new Promise(resolve => {
@@ -35,7 +37,7 @@
   }, 'register on a secure page after redirect from an non-secure url');
 
 promise_test(function(t) {
-    var target_url = get_host_info()['UNAUTHENTICATED_ORIGIN'] + base_path() +
+    var target_url = host_info.HTTP_REMOTE_ORIGIN + base_path() +
       'resources/http-to-https-redirect-and-register-iframe.html';
 
     return redirect_and_register(target_url)
diff --git a/service-workers/service-worker/iframe-sandbox-register-link-element.https.html b/service-workers/service-worker/iframe-sandbox-register-link-element.https.html
deleted file mode 100644
index 1d1c54e..0000000
--- a/service-workers/service-worker/iframe-sandbox-register-link-element.https.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<title>Service worker registration from within sandboxed iframe</title>
-<body>
-<script>
-'use strict';
-function waitForMsg(id) {
-    return new Promise(function(resolve) {
-        window.addEventListener('message', function onMessage(event) {
-            if (!event.data || event.data.id !== id) {
-              return;
-            }
-            window.removeEventListener('message', onMessage);
-            resolve(event.data.result);
-          });
-      });
-}
-
-promise_test(function(t) {
-    var iframe = document.createElement('iframe');
-    iframe.setAttribute('src', 'resources/iframe-register-link-element.html?subtest1');
-    iframe.setAttribute('sandbox', 'allow-scripts');
-
-    document.body.appendChild(iframe);
-    t.add_cleanup(function() {
-        iframe.remove();
-      });
-
-    return waitForMsg('?subtest1')
-      .then(function(data) {
-          assert_equals(data, 'error');
-        });
-  }, 'registration via <link> element `rel` rejected');
-
-promise_test(function(t) {
-    var iframe = document.createElement('iframe');
-    iframe.setAttribute('src', 'resources/iframe-register-link-element.html?subtest2');
-    iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
-
-    document.body.appendChild(iframe);
-    t.add_cleanup(function() {
-        iframe.remove();
-      });
-
-    return waitForMsg('?subtest2')
-      .then(function(data) {
-          assert_equals(data, 'loaded');
-        });
-  }, 'registration via <link> element `rel` accepted in presence of `allow-same-origin` directive');
-</script>
-</body>
diff --git a/service-workers/service-worker/interfaces-window.https.html b/service-workers/service-worker/interfaces-window.https.html
index 7097df0..54f83f2 100644
--- a/service-workers/service-worker/interfaces-window.https.html
+++ b/service-workers/service-worker/interfaces-window.https.html
@@ -9,29 +9,45 @@
 <script>
 'use strict';
 
-var idlArray = new IdlArray();
-idlArray.add_untested_idls(idls.untested);
-idlArray.add_idls(idls.tested);
-idlArray.add_objects({
+promise_test(async (t) => {
+  var idlArray = new IdlArray();
+  const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+  const serviceWorkerIdl = await fetch('/interfaces/ServiceWorker.idl').then(r => r.text());
+
+  idlArray.add_untested_idls(idls.untested);
+  idlArray.add_untested_idls(dom, { only: ['EventTarget'] });
+  idlArray.add_idls(serviceWorkerIdl, { only: [
+    'ServiceWorkerGlobalScope',
+    'Client',
+    'WindowClient',
+    'Clients',
+    'ServiceWorker',
+    'ServiceWorkerState',
+    'ServiceWorkerUpdateViaCache',
+    'ServiceWorkerRegistration',
+    'EventTarget',
+    'NavigationPreloadManager',
+    'Cache',
+    'CacheStorage',
+  ]});
+  idlArray.add_objects({
     ServiceWorkerContainer: ['navigator.serviceWorker']
   });
+  var scope = 'resources/scope/interfaces-and-attributes';
 
-promise_test(function(t) {
-    var scope = 'resources/scope/interfaces-and-attributes';
+  return service_worker_unregister_and_register(
+      t, 'resources/empty-worker.js', scope)
+    .then(function(registration) {
+        t.add_cleanup(function() {
+            registration.unregister();
+          });
 
-    return service_worker_unregister_and_register(
-        t, 'resources/empty-worker.js', scope)
-      .then(function(registration) {
-          t.add_cleanup(function() {
-              registration.unregister();
-            });
-
-          window.registrationInstance = registration;
-          idlArray.add_objects({
-              ServiceWorkerRegistration: ['window.registrationInstance'],
-              ServiceWorker: ['window.registrationInstance.installing']
-            });
-          idlArray.test();
-        });
-  }, 'test setup (worker registration)');
+        window.registrationInstance = registration;
+        idlArray.add_objects({
+            ServiceWorkerRegistration: ['window.registrationInstance'],
+            ServiceWorker: ['window.registrationInstance.installing']
+          });
+        idlArray.test();
+      });
+}, 'test setup (worker registration)');
 </script>
diff --git a/service-workers/service-worker/link-element-register-basic.https.html b/service-workers/service-worker/link-element-register-basic.https.html
deleted file mode 100644
index 83f3f48..0000000
--- a/service-workers/service-worker/link-element-register-basic.https.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Register via link element (basic)</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests-basic.js"></script>
-<body>
-<script>
-registration_tests_basic(register_using_link, false);
-</script>
-</body>
diff --git a/service-workers/service-worker/link-element-register-mime-types.https.html b/service-workers/service-worker/link-element-register-mime-types.https.html
deleted file mode 100644
index beb7d58..0000000
--- a/service-workers/service-worker/link-element-register-mime-types.https.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Register via link element (MIME types)</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests-mime-types.js"></script>
-<body>
-<script>
-registration_tests_mime_types(register_using_link, false);
-</script>
-</body>
diff --git a/service-workers/service-worker/link-element-register-scope.https.html b/service-workers/service-worker/link-element-register-scope.https.html
deleted file mode 100644
index 1b75c38..0000000
--- a/service-workers/service-worker/link-element-register-scope.https.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Register via link element (scope)</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests-scope.js"></script>
-<body>
-<script>
-registration_tests_scope(register_using_link, false);
-</script>
-</body>
-
diff --git a/service-workers/service-worker/link-element-register-script-url.https.html b/service-workers/service-worker/link-element-register-script-url.https.html
deleted file mode 100644
index 48cb935..0000000
--- a/service-workers/service-worker/link-element-register-script-url.https.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Register via link element (scriptURL)</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests-script-url.js"></script>
-<body>
-<script>
-registration_tests_script_url(register_using_link, false);
-</script>
-</body>
diff --git a/service-workers/service-worker/link-element-register-script.https.html b/service-workers/service-worker/link-element-register-script.https.html
deleted file mode 100644
index 4a2818d..0000000
--- a/service-workers/service-worker/link-element-register-script.https.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Register via link element (script)</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests-script.js"></script>
-<body>
-<script>
-registration_tests_script(register_using_link, false);
-</script>
-</body>
diff --git a/service-workers/service-worker/link-element-register-security-error.https.html b/service-workers/service-worker/link-element-register-security-error.https.html
deleted file mode 100644
index eae421d..0000000
--- a/service-workers/service-worker/link-element-register-security-error.https.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Register via link element (SecurityError)</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests-security-error.js"></script>
-<body>
-<script>
-registration_tests_security_error(register_using_link, false);
-</script>
-</body>
diff --git a/service-workers/service-worker/local-url-inherit-controller.https.html b/service-workers/service-worker/local-url-inherit-controller.https.html
new file mode 100644
index 0000000..df25051
--- /dev/null
+++ b/service-workers/service-worker/local-url-inherit-controller.https.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<title>Service Worker: local URL windows and workers inherit controller</title>
+<meta name=timeout content=long>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+const SCRIPT = 'resources/local-url-inherit-controller-worker.js';
+const SCOPE = 'resources/local-url-inherit-controller-frame.html';
+
+async function doAsyncTest(t, opts) {
+  let name = `${opts.scheme}-${opts.child}-${opts.check}`;
+  let scope = SCOPE + '?name=' + name;
+  let reg = await service_worker_unregister_and_register(t, SCRIPT, scope);
+  add_completion_callback(_ => reg.unregister());
+  await wait_for_state(t, reg.installing, 'activated');
+
+  let frame = await with_iframe(scope);
+  add_completion_callback(_ => frame.remove());
+  assert_not_equals(frame.contentWindow.navigator.serviceWorker.controller, null,
+                    'frame should be controlled');
+
+  let result = await frame.contentWindow.checkChildController(opts);
+  result = result.data;
+
+  let expect = 'unexpected';
+  if (opts.check === 'controller') {
+    expect = opts.expect === 'inherit'
+               ? frame.contentWindow.navigator.serviceWorker.controller.scriptURL
+               : null;
+  } else if (opts.check === 'fetch') {
+    // The service worker FetchEvent handler will provide an "intercepted"
+    // body.  If the local URL ends up with an opaque origin and is not
+    // intercepted then it will get an opaque Response.  In that case it
+    // should see an empty string body.
+    expect = opts.expect === 'intercept' ? 'intercepted' : '';
+  }
+
+  assert_equals(result, expect,
+                `${opts.scheme} URL ${opts.child} should ${opts.expect} ${opts.check}`);
+}
+
+promise_test(function(t) {
+  return doAsyncTest(t, {
+    scheme: 'blob',
+    child: 'iframe',
+    check: 'controller',
+    expect: 'inherit',
+  });
+}, 'Same-origin blob URL iframe should inherit service worker controller.');
+
+promise_test(function(t) {
+  return doAsyncTest(t, {
+    scheme: 'blob',
+    child: 'iframe',
+    check: 'fetch',
+    expect: 'intercept',
+  });
+}, 'Same-origin blob URL iframe should intercept fetch().');
+
+promise_test(function(t) {
+  return doAsyncTest(t, {
+    scheme: 'blob',
+    child: 'worker',
+    check: 'controller',
+    expect: 'inherit',
+  });
+}, 'Same-origin blob URL worker should inherit service worker controller.');
+
+promise_test(function(t) {
+  return doAsyncTest(t, {
+    scheme: 'blob',
+    child: 'worker',
+    check: 'fetch',
+    expect: 'intercept',
+  });
+}, 'Same-origin blob URL worker should intercept fetch().');
+
+promise_test(function(t) {
+  // Data URLs should result in an opaque origin and should probably not
+  // have access to a cross-origin service worker.  See:
+  //
+  // https://github.com/w3c/ServiceWorker/issues/1262
+  //
+  return doAsyncTest(t, {
+    scheme: 'data',
+    child: 'iframe',
+    check: 'controller',
+    expect: 'not inherit',
+  });
+}, 'Data URL iframe should not inherit service worker controller.');
+
+promise_test(function(t) {
+  return doAsyncTest(t, {
+    scheme: 'data',
+    child: 'iframe',
+    check: 'fetch',
+    expect: 'not intercept',
+  });
+}, 'Data URL iframe should not intercept fetch().');
+
+promise_test(function(t) {
+  // Data URLs should result in an opaque origin and should probably not
+  // have access to a cross-origin service worker.  See:
+  //
+  // https://github.com/w3c/ServiceWorker/issues/1262
+  //
+  return doAsyncTest(t, {
+    scheme: 'data',
+    child: 'worker',
+    check: 'controller',
+    expect: 'not inherit',
+  });
+}, 'Data URL worker should not inherit service worker controller.');
+
+promise_test(function(t) {
+  return doAsyncTest(t, {
+    scheme: 'data',
+    child: 'worker',
+    check: 'fetch',
+    expect: 'not intercept',
+  });
+}, 'Data URL worker should not intercept fetch().');
+
+</script>
+</body>
diff --git a/service-workers/service-worker/multipart-image.https.html b/service-workers/service-worker/multipart-image.https.html
new file mode 100644
index 0000000..9bdadff
--- /dev/null
+++ b/service-workers/service-worker/multipart-image.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Tests for cross-origin multipart image returned by service worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+
+<script>
+// This tests loading a multipart image via service worker. The service worker responds with
+// an opaque or a non-opaque response. The content of opaque response should not be readable.
+
+const script = 'resources/multipart-image-worker.js';
+const scope = 'resources/multipart-image-iframe.html';
+let frame;
+
+function check_image_data(data) {
+    assert_equals(data[0], 255);
+    assert_equals(data[1], 0);
+    assert_equals(data[2], 0);
+    assert_equals(data[3], 255);
+}
+
+promise_test(t => {
+    return service_worker_unregister_and_register(t, script, scope)
+      .then(registration => {
+          promise_test(() => {
+              if (frame) {
+                  frame.remove();
+              }
+              return registration.unregister();
+            }, 'restore global state');
+
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(() => with_iframe(scope))
+      .then(f => {
+          frame = f;
+      });
+  }, 'initialize global state');
+
+promise_test(t => {
+    return frame.contentWindow.load_multipart_image('same-origin-multipart-image')
+      .then(img => frame.contentWindow.get_image_data(img))
+      .then(img_data => {
+          check_image_data(img_data.data);
+      });
+  }, 'same-origin multipart image via SW should be readable');
+
+promise_test(t => {
+    return frame.contentWindow.load_multipart_image('cross-origin-multipart-image-with-cors-approved')
+      .then(img => frame.contentWindow.get_image_data(img))
+      .then(img_data => {
+          check_image_data(img_data.data);
+      });
+  }, 'cross-origin multipart image via SW with approved CORS should be readable');
+
+promise_test(t => {
+    return frame.contentWindow.load_multipart_image('cross-origin-multipart-image-with-no-cors')
+      .then(img => {
+        assert_throws('SecurityError', () => frame.contentWindow.get_image_data(img));
+      });
+  }, 'cross-origin multipart image with no-cors via SW should not be readable');
+
+promise_test(t => {
+    const promise = frame.contentWindow.load_multipart_image('cross-origin-multipart-image-with-cors-rejected');
+    return promise_rejects(t, new DOMException('load failed', 'NetworkError'), promise);
+  }, 'cross-origin multipart image via SW with rejected CORS should fail to load');
+</script>
diff --git a/service-workers/service-worker/navigate-window.https.html b/service-workers/service-worker/navigate-window.https.html
index 3c8533a..46d32a4 100644
--- a/service-workers/service-worker/navigate-window.https.html
+++ b/service-workers/service-worker/navigate-window.https.html
@@ -94,9 +94,9 @@
         resultList.sort(compare_urls);
 
         for (var i = 0; i < resultList.length; ++i) {
-          assert_equals(expected[i].url, resultList[i].url,
+          assert_equals(resultList[i].url, expected[i].url,
                         'client should have expected url');
-          assert_equals(expected[i].frameType, resultList[i].frameType,
+          assert_equals(resultList[i].frameType, expected[i].frameType,
                         'client should have expected frame type');
         }
         return win;
diff --git a/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html b/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html
index f8b088a..ec74282 100644
--- a/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html
+++ b/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html
@@ -18,8 +18,25 @@
       .then(frame => {
           assert_equals(
             frame.contentDocument.body.textContent,
-            'Done');
+            'PASS: preloadResponse resolved');
         });
-  }, 'Navigation Preload with broken chunked encoding must fail.');
+  }, 'FetchEvent#preloadResponse resolves even if the body is sent with broken chunked encoding.');
+
+promise_test(t => {
+    var script = 'resources/broken-chunked-encoding-worker.js';
+    var scope = 'resources/chunked-encoding-scope.py?use_broken_body';
+    return service_worker_unregister_and_register(t, script, scope)
+      .then(registration => {
+          add_completion_callback(_ => registration.unregister());
+          var worker = registration.installing;
+          return wait_for_state(t, worker, 'activated');
+        })
+      .then(_ => with_iframe(scope))
+      .then(frame => {
+          assert_equals(
+            frame.contentDocument.body.textContent,
+            'PASS: preloadResponse resolved');
+        });
+  }, 'FetchEvent#preloadResponse resolves even if the body is sent with broken chunked encoding with some delays');
 
 </script>
diff --git a/service-workers/service-worker/navigation-preload/resource-timing.https.html b/service-workers/service-worker/navigation-preload/resource-timing.https.html
index 5f0953c..b4756d0 100644
--- a/service-workers/service-worker/navigation-preload/resource-timing.https.html
+++ b/service-workers/service-worker/navigation-preload/resource-timing.https.html
@@ -14,9 +14,9 @@
     'The entryType of preload response timing entry must be "resource' +
     '" :' + url);
   assert_equals(
-    entry.initiatorType, 'other',
+    entry.initiatorType, 'navigation',
     'The initiatorType of preload response timing entry must be ' +
-    '"other":' + url);
+    '"navigation":' + url);
 
   // If the server returns the redirect response, |decodedBodySize| is null and
   // |entry.decodedBodySize| shuld be 0. Otherwise |entry.decodedBodySize| must
diff --git a/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js b/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js
index 27268e8..7a453e4 100644
--- a/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js
+++ b/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js
@@ -6,6 +6,6 @@
 self.addEventListener('fetch', event => {
     event.respondWith(event.preloadResponse
       .then(
-        _ => new Response('Fail: got a response'),
-        _ => new Response('Done')));
+        _ => new Response('PASS: preloadResponse resolved'),
+        _ => new Response('FAIL: preloadResponse rejected')));
   });
diff --git a/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py b/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py
index c0a5ddc..3521b64 100644
--- a/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py
+++ b/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py
@@ -1,7 +1,7 @@
 import time
 
 def main(request, response):
-    body = "hello\nworld\n\n"
+    use_broken_body = 'use_broken_body' in request.GET
 
     response.add_required_headers = False
     response.writer.write_status(200)
@@ -10,7 +10,10 @@
     response.writer.end_headers()
 
     for idx in range(10):
-        response.writer.write("%s\r\n%s\r\n" % (len(str(idx)), idx))
+        if use_broken_body:
+            response.writer.write("%s\n%s\n" % (len(str(idx)), idx))
+        else:
+            response.writer.write("%s\r\n%s\r\n" % (len(str(idx)), idx))
         response.writer.flush()
         time.sleep(0.001)
 
diff --git a/service-workers/service-worker/navigation-redirect.https.html b/service-workers/service-worker/navigation-redirect.https.html
index d840666..3f12c2f 100644
--- a/service-workers/service-worker/navigation-redirect.https.html
+++ b/service-workers/service-worker/navigation-redirect.https.html
@@ -156,6 +156,22 @@
 promise_test(function(t) {
     return setup_environment(t).then(function() {
         return test_redirect(
+            OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1) + '#ref',
+            SCOPE1 + '#ref',
+            [[SCOPE1 + '#ref'], [], []]);
+      });
+  }, 'Normal redirect to same-origin scope with a hash fragment.');
+promise_test(function(t) {
+    return setup_environment(t).then(function() {
+        return test_redirect(
+            OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1 + '#ref2') + '#ref',
+            SCOPE1 + '#ref2',
+            [[SCOPE1 + '#ref2'], [], []]);
+      });
+  }, 'Normal redirect to same-origin scope with different hash fragments.');
+promise_test(function(t) {
+    return setup_environment(t).then(function() {
+        return test_redirect(
             OUT_SCOPE + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE),
             OTHER_ORIGIN_SCOPE,
             [[], [], [OTHER_ORIGIN_SCOPE]]);
@@ -182,6 +198,27 @@
 promise_test(function(t) {
     return setup_environment(t).then(function() {
         return test_redirect(
+            SCOPE1 + 'url=' + encodeURIComponent(SCOPE1) + '#ref',
+            SCOPE1 + '#ref',
+            [[SCOPE1 + 'url=' + encodeURIComponent(SCOPE1) + '#ref',
+              SCOPE1 + '#ref'],
+             [], []]);
+      });
+  }, 'SW-fallbacked redirect to same-origin same-scope with a hash fragment.');
+promise_test(function(t) {
+    return setup_environment(t).then(function() {
+        return test_redirect(
+            SCOPE1 + 'url=' + encodeURIComponent(SCOPE1 + '#ref2') + '#ref',
+            SCOPE1 + '#ref2',
+            [[SCOPE1 + 'url=' + encodeURIComponent(SCOPE1 + '#ref2') + '#ref',
+              SCOPE1 + '#ref2'],
+             [], []]);
+      });
+  }, 'SW-fallbacked redirect to same-origin same-scope with different hash ' +
+     'fragments.');
+promise_test(function(t) {
+    return setup_environment(t).then(function() {
+        return test_redirect(
             SCOPE1 + 'url=' + encodeURIComponent(SCOPE2),
             SCOPE2,
             [[SCOPE1 + 'url=' + encodeURIComponent(SCOPE2)], [SCOPE2], []]);
@@ -221,6 +258,27 @@
 promise_test(function(t) {
     return setup_environment(t).then(function() {
         return test_redirect(
+            SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE) + '#ref',
+            OUT_SCOPE + '#ref',
+            [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE) + '#ref'],
+              [], []]);
+      });
+  }, 'SW-generated redirect to same-origin out-scope with a hash fragment.');
+promise_test(function(t) {
+    return setup_environment(t).then(function() {
+        return test_redirect(
+            SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE + '#ref2') +
+            '#ref',
+            OUT_SCOPE + '#ref2',
+            [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE + '#ref2') +
+              '#ref'],
+             [], []]);
+      });
+  }, 'SW-generated redirect to same-origin out-scope with different hash' +
+     'fragments.');
+promise_test(function(t) {
+    return setup_environment(t).then(function() {
+        return test_redirect(
             SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE1),
             SCOPE1,
             [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE1), SCOPE1],
diff --git a/service-workers/service-worker/navigation-timing.https.html b/service-workers/service-worker/navigation-timing.https.html
new file mode 100644
index 0000000..ac3456e
--- /dev/null
+++ b/service-workers/service-worker/navigation-timing.https.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+
+<script>
+const timingEventOrder = [
+    'startTime',
+    'workerStart',
+    'fetchStart',
+    'requestStart',
+    'responseStart',
+    'responseEnd',
+];
+
+function verify(timing) {
+    for (let i = 0; i < timingEventOrder.length - 1; i++) {
+        assert_true(timing[timingEventOrder[i]] <= timing[timingEventOrder[i + 1]],
+                `Expected ${timingEventOrder[i]} <= ${timingEventOrder[i + 1]}`);
+    }
+}
+
+function navigate_in_frame(frame, url) {
+    frame.contentWindow.location = url;
+    return new Promise((resolve) => {
+        frame.addEventListener('load', () => {
+            const timing = frame.contentWindow.performance.getEntriesByType('navigation')[0];
+            resolve(timing);
+        });
+    });
+}
+
+const worker_url = 'resources/navigation-timing-worker.js';
+
+promise_test(t => {
+    const scope = 'resources/empty.html';
+    let frame;
+
+    return service_worker_unregister_and_register(t, worker_url, scope)
+      .then(r => {
+        return wait_for_state(t, r.installing, 'activated');
+      })
+      .then(() => with_iframe(scope))
+      .then(f => {
+        frame = f;
+        return navigate_in_frame(frame, 'resources/empty.html');
+      })
+      .then(timing => {
+        verify(timing);
+      })
+      .catch(unreached_rejection(t))
+      .then(() => {
+        if (frame)
+            frame.remove();
+        return service_worker_unregister(t, scope);
+      });
+}, 'Service worker controlled navigation timing');
+
+promise_test(t => {
+    const scope = 'resources/empty.html?network-fallback';
+    let frame;
+
+    return service_worker_unregister_and_register(t, worker_url, scope)
+      .then(r => {
+        return wait_for_state(t, r.installing, 'activated');
+      })
+      .then(() => with_iframe(scope))
+      .then(f => {
+        frame = f;
+        return navigate_in_frame(frame, 'resources/empty.html?network-fallback');
+      })
+      .then(timing => {
+        verify(timing);
+      })
+      .catch(unreached_rejection(t))
+      .then(() => {
+        if (frame)
+            frame.remove();
+        return service_worker_unregister(t, scope);
+      });
+}, 'Service worker controlled navigation timing network fallback');
+
+promise_test(t => {
+    const scope = 'resources/redirect.py?Redirect=empty.html';
+    let frame;
+
+    return service_worker_unregister_and_register(t, worker_url, scope)
+      .then(r => {
+        return wait_for_state(t, r.installing, 'activated');
+      })
+      .then(() => with_iframe(scope))
+      .then(f => {
+        frame = f;
+        return navigate_in_frame(frame, 'resources/redirect.py?Redirect=empty.html');
+      })
+      .then(timing => {
+        verify(timing);
+        // Additional checks for redirected navigation.
+        assert_true(timing.redirectStart <= timing.redirectEnd,
+            'Expected redirectStart <= redirectEnd');
+        assert_true(timing.redirectEnd <= timing.fetchStart,
+            'Expected redirectEnd <= fetchStart');
+      })
+      .catch(unreached_rejection(t))
+      .then(() => {
+        if (frame)
+            frame.remove();
+        return service_worker_unregister(t, scope);
+      });
+}, 'Service worker controlled navigation timing redirect');
+</script>
diff --git a/service-workers/service-worker/postmessage.https.html b/service-workers/service-worker/postmessage.https.html
index 3a6487e..5c43b95 100644
--- a/service-workers/service-worker/postmessage.https.html
+++ b/service-workers/service-worker/postmessage.https.html
@@ -13,7 +13,11 @@
 
     return service_worker_unregister_and_register(t, script, scope)
       .then(r => {
-          t.add_cleanup(() => r.unregister());
+          // TODO: return the Promise created by `r.unregister`once
+          // `testharness.js` has been updated to honor thenables returned by
+          // cleanup functions.
+          // See https://github.com/w3c/web-platform-tests/pull/8748
+          t.add_cleanup(() => { r.unregister(); });
           registration = r;
           worker = registration.installing;
 
@@ -62,7 +66,11 @@
 
     return service_worker_unregister_and_register(t, script, scope)
       .then(r => {
-          t.add_cleanup(() => r.unregister());
+          // TODO: return the Promise created by `r.unregister`once
+          // `testharness.js` has been updated to honor thenables returned by
+          // cleanup functions.
+          // See https://github.com/w3c/web-platform-tests/pull/8748
+          t.add_cleanup(() => { r.unregister(); });
 
           var ab = text_encoder.encode(message);
           assert_equals(ab.byteLength, message.length);
@@ -102,7 +110,11 @@
 
     return service_worker_unregister_and_register(t, script, scope)
       .then(r => {
-          t.add_cleanup(() => r.unregister());
+          // TODO: return the Promise created by `r.unregister`once
+          // `testharness.js` has been updated to honor thenables returned by
+          // cleanup functions.
+          // See https://github.com/w3c/web-platform-tests/pull/8748
+          t.add_cleanup(() => { r.unregister(); });
 
           var channel = new MessageChannel;
           port = channel.port1;
diff --git a/service-workers/service-worker/register-link-header.https.html b/service-workers/service-worker/register-link-header.https.html
deleted file mode 100644
index dd71c9b..0000000
--- a/service-workers/service-worker/register-link-header.https.html
+++ /dev/null
@@ -1,73 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Registration using Link header</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<body>
-<script>
-function get_newest_worker(registration) {
-  if (registration.installing)
-    return registration.installing;
-  if (registration.waiting)
-    return registration.waiting;
-  if (registration.active)
-    return registration.active;
-}
-
-promise_test(function(t) {
-    var scope = normalizeURL('resources/blank.html?fetch');
-    var header = '<empty-worker.js>; rel=serviceworker; scope="' + scope + '"';
-    var resource = 'resources/link-header.py?Link=' +
-        encodeURIComponent(header);
-    return with_iframe(scope)
-      .then(frame =>
-          Promise.all([frame.contentWindow.navigator.serviceWorker.ready,
-                      fetch(resource)]))
-      .then(([registration, response]) => {
-          assert_equals(registration.scope, scope);
-          assert_equals(get_newest_worker(registration).scriptURL,
-                        normalizeURL('resources/empty-worker.js'));
-          return registration.unregister();
-        });
-  }, 'fetch can trigger service worker installation');
-
-promise_test(function(t) {
-    var scope = normalizeURL('resources/blank.html?iframe');
-    var header = '<empty-worker.js>; rel=serviceworker; scope="' + scope + '"';
-    var resource = 'resources/link-header.py?Link=' +
-        encodeURIComponent(header);
-    return with_iframe(scope)
-      .then(frame =>
-          Promise.all([frame.contentWindow.navigator.serviceWorker.ready,
-                       with_iframe(resource)]))
-      .then(([registration, frame]) => {
-          assert_equals(registration.scope, scope);
-          assert_equals(get_newest_worker(registration).scriptURL,
-                        normalizeURL('resources/empty-worker.js'));
-          return registration.unregister();
-        });
-  }, 'An iframe can trigger service worker installation');
-
-promise_test(function(t) {
-    var scope = normalizeURL('resources/blank.html?css');
-    var header = '<empty-worker.js>; rel=serviceworker; scope="' + scope + '"';
-    var resource = 'resources/link-header.py?Link=' +
-        encodeURIComponent(header);
-    return with_iframe(scope)
-      .then(frame => {
-          var link = document.createElement('link');
-          link.setAttribute('rel', 'stylesheet');
-          link.setAttribute('type', 'text/css');
-          link.setAttribute('href', resource);
-          document.getElementsByTagName('head')[0].appendChild(link);
-          return frame.contentWindow.navigator.serviceWorker.ready;
-        })
-      .then(registration => {
-          assert_equals(registration.scope, scope);
-          assert_equals(get_newest_worker(registration).scriptURL,
-                        normalizeURL('resources/empty-worker.js'));
-          return registration.unregister();
-        });
-  }, 'A stylesheet can trigger service worker installation');
-
-</script>
diff --git a/service-workers/service-worker/registration-updateviacache.https.html b/service-workers/service-worker/registration-updateviacache.https.html
index 0df401e..64c277d 100644
--- a/service-workers/service-worker/registration-updateviacache.https.html
+++ b/service-workers/service-worker/registration-updateviacache.https.html
@@ -32,130 +32,54 @@
     });
   }
 
-  function registerViaApi(scriptUrl, opts) {
-    return navigator.serviceWorker.register(scriptUrl, opts);
-  }
+  // Test creating registrations & triggering an update.
+  for (const updateViaCache of UPDATE_VIA_CACHE_VALUES) {
+    const testName = `register-with-updateViaCache-${updateViaCache}`;
 
-  function registerViaLinkElement(scriptUrl, opts) {
-    return new Promise((resolve, reject) => {
-      const link = document.createElement('link');
+    promise_test(async t => {
+      await cleanup();
 
-      if (link.relList.supports('serviceworker') == false) throw Error("link rel=serviceworker not supported");
+      const opts = {scope: SCOPE};
 
-      link.rel = 'serviceworker';
-      link.href = scriptUrl;
-      link.scope = opts.scope;
+      if (updateViaCache) opts.updateViaCache = updateViaCache;
 
-      if (opts.updateViaCache) {
-        link.updateViaCache = opts.updateViaCache;
+      const reg = await navigator.serviceWorker.register(
+        `${SCRIPT_URL}?test=${testName}`,
+        opts
+      );
+
+      assert_equals(reg.updateViaCache, updateViaCache || 'imports', "reg.updateViaCache");
+
+      const sw = reg.installing || reg.waiting || reg.active;
+      await wait_for_state(t, sw, 'activated');
+      const values = await getScriptTimes(sw, testName);
+      await reg.update();
+
+      if (updateViaCache == 'all') {
+        assert_equals(reg.installing, null, "No new service worker");
+      }
+      else {
+        const newWorker = reg.installing;
+        assert_true(!!newWorker, "New worker installing");
+        const newValues = await getScriptTimes(newWorker, testName);
+
+        if (!updateViaCache || updateViaCache == 'imports') {
+          assert_not_equals(values.mainTime, newValues.mainTime, "Main script should have updated");
+          assert_equals(values.importTime, newValues.importTime, "Imported script should be the same");
+        }
+        else if (updateViaCache == 'none') {
+          assert_not_equals(values.mainTime, newValues.mainTime, "Main script should have updated");
+          assert_not_equals(values.importTime, newValues.importTime, "Imported script should have updated");
+        }
+        else {
+          // We should have handled all of the possible values for updateViaCache.
+          // If this runs, something's gone very wrong.
+          throw Error(`Unexpected updateViaCache value: ${updateViaCache}`);
+        }
       }
 
-      link.onload = async () => {
-        const fullScope = new URL(opts.scope, window.location).href;
-
-        const regs = await navigator.serviceWorker.getRegistrations();
-        const reg = regs.find(r => r.scope == fullScope && (r.installing || r.waiting || r.active));
-
-        if (reg) {
-          document.head.removeChild(link);
-          resolve(reg);
-        }
-        else {
-          reject(Error('Service worker not registered'));
-        }
-      };
-
-      document.head.appendChild(link);
-    });
-  }
-
-  async function registerViaLinkHeader(scriptUrl, opts) {
-    const link = document.createElement('link');
-
-    const fullScope = new URL(opts.scope, window.location).href;
-    scriptUrl = new URL(scriptUrl, location).href;
-
-    // Assume that if the link element doesn't support serviceworker, the header doesn't either.
-    if (link.relList.supports('serviceworker') == false) throw Error("link rel=serviceworker not supported");
-
-    let linkHeader = `<${scriptUrl}>; rel=serviceworker; scope="${fullScope}"`;
-
-    if (opts.updateViaCache) {
-      linkHeader += `; updateviacache="${opts.updateViaCache}"`;
-    }
-
-    const linkHeaderSenderURL = new URL('resources/link-header.py', location);
-    linkHeaderSenderURL.searchParams.set('Link', linkHeader);
-
-    await fetch(linkHeaderSenderURL);
-    const frame = await with_iframe(fullScope);
-    await frame.contentWindow.navigator.serviceWorker.ready;
-
-    const regs = await navigator.serviceWorker.getRegistrations();
-    const reg = regs.find(r => r.scope == fullScope && (r.installing || r.waiting || r.active));
-
-    if (!reg) throw Error('Service worker not registered');
-
-    frame.parentNode.removeChild(frame);
-    return reg;
-  }
-
-  const registrationMethods = [
-    [registerViaApi, 'via-api'],
-    [registerViaLinkElement, 'via-link-element'],
-    [registerViaLinkHeader, 'via-link-header']
-  ];
-
-  // Test creating registrations & triggering an update.
-  for (const [registrationMethod, registrationMethodName] of registrationMethods) {
-    for (const updateViaCache of UPDATE_VIA_CACHE_VALUES) {
-      const testName = `register-${registrationMethodName}-updateViaCache-${updateViaCache}`;
-
-      promise_test(async t => {
-        await cleanup();
-
-        const opts = {scope: SCOPE};
-
-        if (updateViaCache) opts.updateViaCache = updateViaCache;
-
-        const reg = await registrationMethod(
-          `${SCRIPT_URL}?test=${testName}`,
-          opts
-        );
-
-        assert_equals(reg.updateViaCache, updateViaCache || 'imports', "reg.updateViaCache");
-
-        const sw = reg.installing || reg.waiting || reg.active;
-        await wait_for_state(t, sw, 'activated');
-        const values = await getScriptTimes(sw, testName);
-        await reg.update();
-
-        if (updateViaCache == 'all') {
-          assert_equals(reg.installing, null, "No new service worker");
-        }
-        else {
-          const newWorker = reg.installing;
-          assert_true(!!newWorker, "New worker installing");
-          const newValues = await getScriptTimes(newWorker, testName);
-
-          if (!updateViaCache || updateViaCache == 'imports') {
-            assert_not_equals(values.mainTime, newValues.mainTime, "Main script should have updated");
-            assert_equals(values.importTime, newValues.importTime, "Imported script should be the same");
-          }
-          else if (updateViaCache == 'none') {
-            assert_not_equals(values.mainTime, newValues.mainTime, "Main script should have updated");
-            assert_not_equals(values.importTime, newValues.importTime, "Imported script should have updated");
-          }
-          else {
-            // We should have handled all of the possible values for updateViaCache.
-            // If this runs, something's gone very wrong.
-            throw Error(`Unexpected updateViaCache value: ${updateViaCache}`);
-          }
-        }
-
-        await cleanup();
-      }, testName);
-    }
+      await cleanup();
+    }, testName);
   }
 
   // Test changing the updateViaCache value of an existing registration.
@@ -176,13 +100,18 @@
         await wait_for_state(t, sw, 'activated');
         const values = await getScriptTimes(sw, testName);
 
+        const frame = await with_iframe(SCOPE);
+        const reg_in_frame = await frame.contentWindow.navigator.serviceWorker.getRegistration(normalizeURL(SCOPE));
+        assert_equals(reg_in_frame.updateViaCache, updateViaCache1 || 'imports', "reg_in_frame.updateViaCache");
+
         opts = {scope: SCOPE};
         if (updateViaCache2) opts.updateViaCache = updateViaCache2;
 
         await navigator.serviceWorker.register(fullScriptUrl, opts);
 
-        assert_equals(reg.updateViaCache, updateViaCache2 || 'imports', "reg.updateViaCache updated");
+        const expected_updateViaCache = updateViaCache2 || 'imports';
 
+        assert_equals(reg.updateViaCache, expected_updateViaCache, "reg.updateViaCache updated");
         // If the update happens via the cache, the scripts will come back byte-identical.
         // We bypass the byte-identical check if the script URL has changed, but not if
         // only the updateViaCache value has changed.
@@ -214,9 +143,44 @@
           }
         }
 
+        // Wait for all registration related tasks on |frame| to complete.
+        await wait_for_activation_on_dummy_scope(t, frame.contentWindow);
+        // The updateViaCache change should have been propagated to all
+        // corresponding JS registration objects.
+        assert_equals(reg_in_frame.updateViaCache, expected_updateViaCache, "reg_in_frame.updateViaCache updated");
+        frame.remove();
+
         await cleanup();
       }, testName);
     }
   }
 
+  // Test accessing updateViaCache of an unregistered registration.
+  for (const updateViaCache of UPDATE_VIA_CACHE_VALUES) {
+    const testName = `access-updateViaCache-after-unregister-${updateViaCache}`;
+
+    promise_test(async t => {
+      await cleanup();
+
+      const opts = {scope: SCOPE};
+
+      if (updateViaCache) opts.updateViaCache = updateViaCache;
+
+      const reg = await navigator.serviceWorker.register(
+        `${SCRIPT_URL}?test=${testName}`,
+        opts
+      );
+
+      const expected_updateViaCache = updateViaCache || 'imports';
+      assert_equals(reg.updateViaCache, expected_updateViaCache, "reg.updateViaCache");
+
+      await reg.unregister();
+
+      // Keep the original value.
+      assert_equals(reg.updateViaCache, expected_updateViaCache, "reg.updateViaCache");
+
+      await cleanup();
+    }, testName);
+  }
+
 </script>
diff --git a/service-workers/service-worker/request-end-to-end.https.html b/service-workers/service-worker/request-end-to-end.https.html
index 7b594aa..a39cead 100644
--- a/service-workers/service-worker/request-end-to-end.https.html
+++ b/service-workers/service-worker/request-end-to-end.https.html
@@ -23,9 +23,9 @@
           assert_equals(result.method, 'GET', 'request.method');
           assert_equals(result.referrer, location.href, 'request.referrer');
           assert_equals(result.mode, 'navigate', 'request.mode');
-          assert_equals(result.request_construct_error, 'TypeError',
+          assert_equals(result.request_construct_error, '',
                         'Constructing a Request with a Request whose mode ' +
-                        'is navigate and non-empty RequestInit must throw a ' +
+                        'is navigate and non-empty RequestInit must not throw a ' +
                         'TypeError.')
           assert_equals(result.credentials, 'include', 'request.credentials');
           assert_equals(result.redirect, 'manual', 'request.redirect');
diff --git a/service-workers/service-worker/resource-timing-cross-origin.https.html b/service-workers/service-worker/resource-timing-cross-origin.https.html
new file mode 100644
index 0000000..827688d
--- /dev/null
+++ b/service-workers/service-worker/resource-timing-cross-origin.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates Resource Timing for cross origin content fetched by Service Worker from an originally same-origin URL.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+</head>
+
+<body>
+<script>
+async_test(function(t) {
+    const worker_url = 'resources/worker-fetching-cross-origin.js';
+    const scope = 'resources/iframe-with-image.html';
+    let registration;
+    service_worker_unregister_and_register(t, worker_url, scope)
+    .then(function(r) {
+        registration = r;
+        return wait_for_state(t, r.installing, 'activated');
+    })
+    .then(function() {
+        return with_iframe(scope);
+    })
+    .then(function(frame) {
+        const frame_performance = frame.contentWindow.performance;
+        // Check that there is one entry for which the timing allow check algorithm failed.
+        const entries = frame_performance.getEntriesByType('resource');
+        assert_equals(entries.length, 1);
+        const entry = entries[0];
+        assert_equals(entry.redirectStart, 0, 'redirectStart should be 0 in cross-origin request.');
+        assert_equals(entry.redirectEnd, 0, 'redirectEnd should be 0 in cross-origin request.');
+        assert_equals(entry.domainLookupStart, 0, 'domainLookupStart should be 0 in cross-origin request.');
+        assert_equals(entry.domainLookupEnd, 0, 'domainLookupEnd should be 0 in cross-origin request.');
+        assert_equals(entry.connectStart, 0, 'connectStart should be 0 in cross-origin request.');
+        assert_equals(entry.connectEnd, 0, 'connectEnd should be 0 in cross-origin request.');
+        assert_equals(entry.requestStart, 0, 'requestStart should be 0 in cross-origin request.');
+        assert_equals(entry.responseStart, 0, 'responseStart should be 0 in cross-origin request.');
+        assert_equals(entry.secureConnectionStart, 0, 'secureConnectionStart should be 0 in cross-origin request.');
+        assert_equals(entry.decodedBodySize, 0, 'decodedBodySize should be 0 in cross-origin request.');
+        frame.remove();
+        return registration.unregister();
+      })
+    .then(function() {
+        t.done();
+      })
+    .catch(unreached_rejection(t));
+}, 'Test that timing allow check fails when service worker changes origin from same to cross origin.');
+</script>
+</body>
+</html>
diff --git a/service-workers/service-worker/resource-timing.https.html b/service-workers/service-worker/resource-timing.https.html
index da898e2..f6e197c 100644
--- a/service-workers/service-worker/resource-timing.https.html
+++ b/service-workers/service-worker/resource-timing.https.html
@@ -12,28 +12,33 @@
     return get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() + path;
 }
 
-function verify(performance, resource, mode, description) {
-    var url = mode === 'cross-origin' ? crossOriginUrl(resource)
-                                      : resourceUrl(resource);
-    var entryList = performance.getEntries();
-    var entry = performance.getEntriesByName(url)[0];
-    assert_greater_than(entry.workerStart, 0, description);
-    assert_greater_than_equal(entry.workerStart, entry.startTime, description);
-    assert_less_than_equal(entry.workerStart, entry.fetchStart, description);
-    if (mode === 'cross-origin') {
-      assert_equals(entry.responseStart, 0, description);
-      assert_greater_than_equal(entry.responseEnd, entry.fetchStart, description);
-    } else {
-      assert_greater_than_equal(entry.responseStart, entry.fetchStart, description);
-      assert_greater_than_equal(entry.responseEnd, entry.responseStart, description);
+function verify(options) {
+    var url = options.mode === 'cross-origin' ? crossOriginUrl(options.resource)
+                                      : resourceUrl(options.resource);
+    var entryList = options.performance.getEntriesByName(url);
+    if (entryList.length === 0 && options.allow_no_performance_entry) {
+        // The performance timeline may not have an entry for a resource
+        // which failed to load.
+        return;
     }
-    assert_greater_than(entry.responseEnd, entry.fetchStart, description);
-    assert_greater_than(entry.duration, 0, description);
-    if (resource.indexOf('redirect.py') != -1) {
-        assert_less_than_equal(entry.workerStart, entry.redirectStart,
-                               description);
+    var entry = entryList[0];
+    assert_greater_than(entry.workerStart, 0, options.description);
+    assert_greater_than_equal(entry.workerStart, entry.startTime, options.description);
+    assert_less_than_equal(entry.workerStart, entry.fetchStart, options.description);
+    if (options.mode === 'cross-origin') {
+      assert_equals(entry.responseStart, 0, options.description);
+      assert_greater_than_equal(entry.responseEnd, entry.fetchStart, options.description);
     } else {
-        assert_equals(entry.redirectStart, 0, description);
+      assert_greater_than_equal(entry.responseStart, entry.fetchStart, options.description);
+      assert_greater_than_equal(entry.responseEnd, entry.responseStart, options.description);
+    }
+    assert_greater_than(entry.responseEnd, entry.fetchStart, options.description);
+    assert_greater_than(entry.duration, 0, options.description);
+    if (options.resource.indexOf('redirect.py') != -1) {
+        assert_less_than_equal(entry.workerStart, entry.redirectStart,
+                               options.description);
+    } else {
+        assert_equals(entry.redirectStart, 0, options.description);
     }
 }
 
@@ -52,19 +57,54 @@
         })
       .then(function(frame) {
           var performance = frame.contentWindow.performance;
-          verify(performance, 'resources/dummy.js', 'same-origin',
-                 'Generated response');
-          verify(performance, 'resources/empty.js', 'same-origin',
-                 'Network fallback');
-          verify(performance, 'resources/redirect.py?Redirect=empty.js',
-                 'same-origin', 'Redirect');
-          verify(performance, 'resources/missing.jpg', 'same-origin',
-                 'Network fallback image');
-
+          verify({
+              performance: performance,
+              resource: 'resources/dummy.js',
+              mode: 'same-origin',
+              description: 'Generated response',
+          });
+          verify({
+              performance: performance,
+              resource: 'resources/empty.js',
+              mode: 'same-origin',
+              description: 'Network fallback',
+          });
+          verify({
+              performance: performance,
+              resource: 'resources/redirect.py?Redirect=empty.js',
+              mode: 'same-origin',
+              description: 'Redirect',
+          });
+          verify({
+              performance: performance,
+              resource: 'resources/square.png',
+              mode: 'same-origin',
+              description: 'Network fallback image',
+          });
           // Test that worker start is available on cross-origin no-cors
           // subresources.
-          verify(performance, 'resources/missing.jpg', 'cross-origin',
-                 'Network fallback cross-origin image');
+          verify({
+              performance: performance,
+              resource: 'resources/square.png',
+              mode: 'cross-origin',
+              description: 'Network fallback cross-origin image',
+          });
+
+          // Tests for resouces which failed to load.
+          verify({
+              performance: performance,
+              resource: 'resources/missing.jpg',
+              mode: 'same-origin',
+              description: 'Network fallback load failure',
+              allow_no_performance_entry: true,
+          });
+          verify({
+              performance: performance,
+              resource: 'resources/missing.jpg',
+              mode: 'cross-origin',
+              description: 'Network fallback cross-origin load failure',
+              allow_no_performance_entry: true,
+          });
 
           frame.remove();
           return registration.unregister();
diff --git a/service-workers/service-worker/resources/about-blank-replacement-blank-dynamic-nested-frame.html b/service-workers/service-worker/resources/about-blank-replacement-blank-dynamic-nested-frame.html
new file mode 100644
index 0000000..1e0c620
--- /dev/null
+++ b/service-workers/service-worker/resources/about-blank-replacement-blank-dynamic-nested-frame.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+<body>
+<script>
+function nestedLoaded() {
+  parent.postMessage({ type: 'NESTED_LOADED' }, '*');
+}
+
+// dynamically add an about:blank iframe
+var f = document.createElement('iframe');
+f.onload = nestedLoaded;
+document.body.appendChild(f);
+
+// Helper routine to make it slightly easier for our parent to find
+// the nested frame.
+function nested() {
+  return f.contentWindow;
+}
+</script>
+</body>
+</html>
diff --git a/service-workers/service-worker/resources/about-blank-replacement-blank-nested-frame.html b/service-workers/service-worker/resources/about-blank-replacement-blank-nested-frame.html
new file mode 100644
index 0000000..99d07a4
--- /dev/null
+++ b/service-workers/service-worker/resources/about-blank-replacement-blank-nested-frame.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<body>
+<script>
+function nestedLoaded() {
+  parent.postMessage({ type: 'NESTED_LOADED' }, '*');
+}
+</script>
+<iframe id="nested" onload="nestedLoaded()"></iframe>
+<script>
+// Helper routine to make it slightly easier for our parent to find
+// the nested frame.
+function nested() {
+  return document.getElementById('nested').contentWindow;
+}
+
+// NOTE: Make sure not to touch the iframe directly here.  We want to
+//       test the case where the initial about:blank document is not
+//       directly accessed before load.
+</script>
+</body>
+</html>
diff --git a/service-workers/service-worker/resources/about-blank-replacement-popup-frame.py b/service-workers/service-worker/resources/about-blank-replacement-popup-frame.py
index be52945..f0b8cd5 100644
--- a/service-workers/service-worker/resources/about-blank-replacement-popup-frame.py
+++ b/service-workers/service-worker/resources/about-blank-replacement-popup-frame.py
@@ -12,12 +12,15 @@
 <script>
 function nestedLoaded() {
   parent.postMessage({ type: 'NESTED_LOADED' }, '*');
-  popup.close();
 }
 
 let popup = window.open('?nested=true');
 popup.onload = nestedLoaded;
 
+addEventListener('unload', evt => {
+  popup.close();
+}, { once: true });
+
 // Helper routine to make it slightly easier for our parent to find
 // the nested popup window.
 function nested() {
diff --git a/service-workers/service-worker/resources/about-blank-replacement-srcdoc-nested-frame.html b/service-workers/service-worker/resources/about-blank-replacement-srcdoc-nested-frame.html
new file mode 100644
index 0000000..0122a00
--- /dev/null
+++ b/service-workers/service-worker/resources/about-blank-replacement-srcdoc-nested-frame.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<body>
+<script>
+function nestedLoaded() {
+  parent.postMessage({ type: 'NESTED_LOADED' }, '*');
+}
+</script>
+<iframe id="nested" srcdoc="<div></div>" onload="nestedLoaded()"></iframe>
+<script>
+// Helper routine to make it slightly easier for our parent to find
+// the nested frame.
+function nested() {
+  return document.getElementById('nested').contentWindow;
+}
+
+// NOTE: Make sure not to touch the iframe directly here.  We want to
+//       test the case where the initial about:blank document is not
+//       directly accessed before load.
+</script>
+</body>
+</html>
diff --git a/service-workers/service-worker/resources/client-navigate-worker.js b/service-workers/service-worker/resources/client-navigate-worker.js
index 09d11fe..aff2b37 100644
--- a/service-workers/service-worker/resources/client-navigate-worker.js
+++ b/service-workers/service-worker/resources/client-navigate-worker.js
@@ -5,11 +5,6 @@
 
 self.onfetch = function(e) {
   if (e.request.url.indexOf("client-navigate-frame.html") >= 0) {
-    if (e.clientId === null) {
-      e.respondWith(fetch(e.request));
-    } else {
-      e.respondWith(Response.error());
-    }
     return;
   }
   e.respondWith(new Response(e.clientId));
diff --git a/service-workers/service-worker/resources/clients-get-worker.js b/service-workers/service-worker/resources/clients-get-worker.js
index 77c99e8..87368ae 100644
--- a/service-workers/service-worker/resources/clients-get-worker.js
+++ b/service-workers/service-worker/resources/clients-get-worker.js
@@ -4,13 +4,6 @@
 // the `event` object. In the case of the `onmessage` handler, it provides the
 // Client instance attributes of the requested clients.
 self.onfetch = function(e) {
-  if (e.request.mode === 'navigate' && e.clientId !== null) {
-    e.respondWith(Response.error(
-      '`clientId` incorrectly set to non-null value for request with mode `navigate`'
-    ));
-    return;
-  }
-
   if (/\/clientId$/.test(e.request.url)) {
     e.respondWith(new Response(e.clientId));
     return;
diff --git a/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js b/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js
index 82b2445..e996da6 100644
--- a/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js
+++ b/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js
@@ -1,7 +1,9 @@
 var worker_text = 'onconnect = function(e) { e.ports[0].postMessage("worker loading intercepted by service worker"); };';
 
 self.onfetch = function(event) {
-  if (event.request.url.indexOf('dummy-shared-worker.js') != -1)
-    event.respondWith(new Response(worker_text));
+  if (event.request.url.indexOf('dummy-shared-worker.js') != -1) {
+    event.respondWith(new Response(
+        worker_text, {headers: {"Content-Type": "application/javascript"}}));
+  }
 };
 
diff --git a/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js.headers b/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js.headers
new file mode 100644
index 0000000..a17a9a3
--- /dev/null
+++ b/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js.headers
@@ -0,0 +1 @@
+Content-Type: application/javascript
diff --git a/service-workers/service-worker/resources/echo-content.py b/service-workers/service-worker/resources/echo-content.py
new file mode 100644
index 0000000..c40ef0c
--- /dev/null
+++ b/service-workers/service-worker/resources/echo-content.py
@@ -0,0 +1,14 @@
+# This is a copy of fetch/api/resources/echo-content.py since it's more
+# convenient in this directory due to service worker's path restriction.
+def main(request, response):
+
+    headers = [("X-Request-Method", request.method),
+               ("X-Request-Content-Length", request.headers.get("Content-Length", "NO")),
+               ("X-Request-Content-Type", request.headers.get("Content-Type", "NO")),
+
+               # Avoid any kind of content sniffing on the response.
+               ("Content-Type", "text/plain")]
+
+    content = request.body
+
+    return headers, content
diff --git a/service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js b/service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js
new file mode 100644
index 0000000..ffcdb75
--- /dev/null
+++ b/service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js
@@ -0,0 +1,14 @@
+// This worker intercepts a request for EMBED/OBJECT and responds with a
+// response that indicates that interception occurred. The tests expect
+// that interception does not occur.
+self.addEventListener('fetch', e => {
+    if (e.request.url.indexOf('embedded-content-from-server.html') != -1) {
+      e.respondWith(fetch('embedded-content-from-service-worker.html'));
+      return;
+    }
+
+    if (e.request.url.indexOf('green.png') != -1) {
+      e.respondWith(Promise.reject('network error to show interception occurred'));
+      return;
+    }
+  });
diff --git a/service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html b/service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html
new file mode 100644
index 0000000..7b8b257
--- /dev/null
+++ b/service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe for embed-and-object-are-not-intercepted test</title>
+<body>
+<embed type="image/png" src="/images/green.png"></embed>
+<script>
+// Our parent (the root frame of the test) will examine this to get the result.
+var test_promise = new Promise(resolve => {
+    if (!navigator.serviceWorker.controller)
+      resolve('FAIL: this iframe is not controlled');
+
+    const elem = document.querySelector('embed');
+    elem.addEventListener('load', e => {
+        resolve('request was not intercepted');
+      });
+    elem.addEventListener('error', e => {
+        resolve('FAIL: request was intercepted');
+      });
+  });
+</script>
+</body>
diff --git a/service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html b/service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html
new file mode 100644
index 0000000..3914991
--- /dev/null
+++ b/service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe for embed-and-object-are-not-intercepted test</title>
+<body>
+<script>
+// The EMBED element will call this with the result about whether the EMBED
+// request was intercepted by the service worker.
+var report_result;
+
+// Our parent (the root frame of the test) will examine this to get the result.
+var test_promise = new Promise(resolve => {
+    report_result = resolve;
+  });
+</script>
+
+<embed src="embedded-content-from-server.html"></embed>
+</body>
diff --git a/service-workers/service-worker/resources/embedded-content-from-server.html b/service-workers/service-worker/resources/embedded-content-from-server.html
new file mode 100644
index 0000000..ff50a9c
--- /dev/null
+++ b/service-workers/service-worker/resources/embedded-content-from-server.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>embed for embed-and-object-are-not-intercepted test</title>
+<script>
+window.parent.report_result('request for embedded content was not intercepted');
+</script>
diff --git a/service-workers/service-worker/resources/embedded-content-from-service-worker.html b/service-workers/service-worker/resources/embedded-content-from-service-worker.html
new file mode 100644
index 0000000..2e2b923
--- /dev/null
+++ b/service-workers/service-worker/resources/embedded-content-from-service-worker.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>embed for embed-and-object-are-not-intercepted test</title>
+<script>
+window.parent.report_result('request for embedded content was intercepted by service worker');
+</script>
+
diff --git a/service-workers/service-worker/resources/fetch-access-control.py b/service-workers/service-worker/resources/fetch-access-control.py
index 93ea0e0..61b89cb 100644
--- a/service-workers/service-worker/resources/fetch-access-control.py
+++ b/service-workers/service-worker/resources/fetch-access-control.py
@@ -1,5 +1,7 @@
 import base64
 import json
+import os
+import sys
 
 def main(request, response):
     headers = []
@@ -31,6 +33,43 @@
                                    "jBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII=")
         return headers, body
 
+    if "VIDEO" in request.GET:
+        headers.append(("Content-Type", "video/webm"))
+        body = open(os.path.join(request.doc_root, "media", "movie_5.ogv"), "rb").read()
+        length = len(body)
+        # If "PartialContent" is specified, the requestor wants to test range
+        # requests. For the initial request, respond with "206 Partial Content"
+        # and don't send the entire content. Then expect subsequent requests to
+        # have a "Range" header with a byte range. Respond with that range.
+        if "PartialContent" in request.GET:
+          if length < 1:
+            return 500, headers, "file is too small for range requests"
+          start = 0
+          end = length - 1
+          if "Range" in request.headers:
+            range_header = request.headers["Range"]
+            prefix = "bytes="
+            split_header = range_header[len(prefix):].split("-")
+            # The first request might be "bytes=0-". We want to force a range
+            # request, so just return the first byte.
+            if split_header[0] == "0" and split_header[1] == "":
+              end = start
+            # Otherwise, it is a range request. Respect the values sent.
+            if split_header[0] != "":
+              start = int(split_header[0])
+            if split_header[1] != "":
+              end = int(split_header[1])
+          else:
+            # The request doesn't have a range. Force a range request by
+            # returning the first byte.
+            end = start
+
+          headers.append(("Accept-Ranges", "bytes"))
+          headers.append(("Content-Length", str(end -start + 1)))
+          headers.append(("Content-Range", "bytes %d-%d/%d" % (start, end, length)))
+          chunk = body[start:(end + 1)]
+          return 206, headers, chunk
+        return headers, body
 
     username = request.auth.username if request.auth.username else "undefined"
     password = request.auth.password if request.auth.username else "undefined"
diff --git a/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html b/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html
index 9b06869..a65d250 100644
--- a/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html
+++ b/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html
@@ -1,291 +1,69 @@
-<script src="/common/get-host-info.sub.js"></script>
-<script src="test-helpers.sub.js?pipe=sub"></script>
+<html>
+<title>iframe for fetch canvas tainting test</title>
 <script>
-var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE';
-var host_info = get_host_info();
-var params = get_query_params(location.href);
+const NOT_TAINTED = 'NOT_TAINTED';
+const TAINTED = 'TAINTED';
+const LOAD_ERROR = 'LOAD_ERROR';
 
-var NOT_TAINTED = 'NOT_TAINTED';
-var TAINTED = 'TAINTED';
-var LOAD_ERROR = 'LOAD_ERROR';
-
-function get_query_params(url) {
-  var search = (new URL(url)).search;
-  if (!search) {
-    return {};
-  }
-  var ret = {};
-  var params = search.substring(1).split('&');
-  params.forEach(function(param) {
-      var element = param.split('=');
-      ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]);
-    });
-  return ret;
-}
-
+// Creates an image/video element with src=|url| and an optional |cross_origin|
+// attibute. Tries to read from the image/video using a canvas element. Returns
+// NOT_TAINTED if it could be read, TAINTED if it could not be read, and
+// LOAD_ERROR if loading the image/video failed.
 function create_test_case_promise(url, cross_origin) {
-  return new Promise(function(resolve) {
-      var img = new Image();
-      if (cross_origin != '') {
-        img.crossOrigin = cross_origin;
-      }
-      img.onload = function() {
-        try {
-          var canvas = document.createElement('canvas');
-          canvas.width = 100;
-          canvas.height = 100;
-          var context = canvas.getContext('2d');
-          context.drawImage(img, 0, 0);
-          context.getImageData(0, 0, 100, 100);
-          resolve(NOT_TAINTED);
-        } catch (e) {
-          resolve(TAINTED);
+  return new Promise(resolve => {
+      if (url.indexOf('PNGIMAGE') != -1) {
+        const img = document.createElement('img');
+        if (cross_origin != '') {
+          img.crossOrigin = cross_origin;
         }
-      };
-      img.onerror = function() {
-        resolve(LOAD_ERROR);
-      }
-      img.src = url;
-    });
-}
-
-function create_test_promise(url, cross_origin, expected_result) {
-  if (params['cache']) {
-    url += "&cache";
-  }
-
-  return new Promise(function(resolve, reject) {
-      create_test_case_promise(url, cross_origin)
-        .then(function(result) {
-          if (result == expected_result) {
-            resolve();
-          } else {
-            reject('Result of url:' + url + ' ' +
-                   ' cross_origin: ' + cross_origin + ' must be ' +
-                   expected_result + ' but ' + result);
+        img.onload = function() {
+          try {
+            const canvas = document.createElement('canvas');
+            canvas.width = 100;
+            canvas.height = 100;
+            const context = canvas.getContext('2d');
+            context.drawImage(img, 0, 0);
+            context.getImageData(0, 0, 100, 100);
+            resolve(NOT_TAINTED);
+          } catch (e) {
+            resolve(TAINTED);
           }
-        })
-    });
+        };
+        img.onerror = function() {
+          resolve(LOAD_ERROR);
+        }
+        img.src = url;
+        return;
+      }
+
+      if (url.indexOf('VIDEO') != -1) {
+        const video = document.createElement('video');
+        video.autoplay = true;
+        if (cross_origin != '') {
+          video.crossOrigin = cross_origin;
+        }
+        video.onplay = function() {
+          try {
+            const canvas = document.createElement('canvas');
+            canvas.width = 100;
+            canvas.height = 100;
+            const context = canvas.getContext('2d');
+            context.drawImage(video, 0, 0);
+            context.getImageData(0, 0, 100, 100);
+            resolve(NOT_TAINTED);
+          } catch (e) {
+            resolve(TAINTED);
+          }
+        };
+        video.onerror = function() {
+          resolve(LOAD_ERROR);
+        }
+        video.src = url;
+        return;
+      }
+
+      resolve('unknown resource type');
+  });
 }
-
-window.addEventListener('message', function(evt) {
-    var port = evt.ports[0];
-    var image_url = host_info['HTTPS_ORIGIN'] + image_path;
-    var remote_image_url = host_info['HTTPS_REMOTE_ORIGIN'] + image_path;
-    Promise.all([
-        // Reject tests
-        create_test_promise(image_url + '&reject', '', LOAD_ERROR),
-        create_test_promise(image_url + '&reject', 'anonymous', LOAD_ERROR),
-        create_test_promise(
-            image_url + '&reject', 'use-credentials', LOAD_ERROR),
-        // Fallback tests
-        create_test_promise(
-            image_url + '&ignore',
-            '',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url + '&ignore',
-            '',
-            TAINTED),
-        create_test_promise(
-            remote_image_url + '&ignore',
-            'anonymous',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
-            '&ignore',
-            'anonymous',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url + '&ignore',
-            'use-credentials',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
-            '&ignore',
-            'use-credentials',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
-            '&ACACredentials=true&ignore',
-            'use-credentials',
-            NOT_TAINTED),
-
-        // Credential test (fallback)
-        create_test_promise(
-            image_url + '&Auth&ignore',
-            '',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url + '&Auth&ignore',
-            '',
-            TAINTED),
-        create_test_promise(
-            remote_image_url + '&Auth&ignore',
-            'anonymous',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url + '&Auth&ignore',
-            'use-credentials',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
-            '&ignore',
-            'use-credentials',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
-            '&ACACredentials=true&ignore',
-            'use-credentials',
-            NOT_TAINTED),
-
-        // Basic response
-        create_test_promise(
-            image_url +
-            '&mode=same-origin&url=' + encodeURIComponent(image_url),
-            '',
-            NOT_TAINTED),
-        create_test_promise(
-            image_url +
-            '&mode=same-origin&url=' + encodeURIComponent(image_url),
-            'anonymous',
-            NOT_TAINTED),
-        create_test_promise(
-            image_url +
-            '&mode=same-origin&url=' + encodeURIComponent(image_url),
-            'use-credentials',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url +
-            '&mode=same-origin&url=' + encodeURIComponent(image_url),
-            '',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url +
-            '&mode=same-origin&url=' + encodeURIComponent(image_url),
-            'anonymous',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url +
-            '&mode=same-origin&url=' + encodeURIComponent(image_url),
-            'use-credentials',
-            NOT_TAINTED),
-
-        // Opaque response
-        create_test_promise(
-            image_url +
-            '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
-            '',
-            TAINTED),
-        create_test_promise(
-            image_url +
-            '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
-            'anonymous',
-            LOAD_ERROR),
-        create_test_promise(
-            image_url +
-            '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
-            'use-credentials',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url +
-            '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
-            '',
-            TAINTED),
-        create_test_promise(
-            remote_image_url +
-            '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
-            'anonymous',
-            LOAD_ERROR),
-        create_test_promise(
-            remote_image_url +
-            '&mode=no-cors&url=' + encodeURIComponent(remote_image_url),
-            'use-credentials',
-            LOAD_ERROR),
-
-        // CORS response
-        create_test_promise(
-            image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            '',
-            LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
-                         // with an Access-Control-Allow-Credentials header.
-        create_test_promise(
-            image_url +
-            '&mode=cors&credentials=same-origin&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            '',
-            NOT_TAINTED),
-        create_test_promise(
-            image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            'anonymous',
-            NOT_TAINTED),
-        create_test_promise(
-            image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            'use-credentials',
-            LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
-                         // with an Access-Control-Allow-Credentials header.
-        create_test_promise(
-            image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(
-                remote_image_url +
-                '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            'use-credentials',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            '',
-            LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
-                         // with an Access-Control-Allow-Credentials header.
-        create_test_promise(
-            remote_image_url +
-            '&mode=cors&credentials=same-origin&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            '',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            'anonymous',
-            NOT_TAINTED),
-        create_test_promise(
-            remote_image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(remote_image_url +
-                               '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            'use-credentials',
-            LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond
-                         // with an Access-Control-Allow-Credentials header.
-        create_test_promise(
-            remote_image_url +
-            '&mode=cors&url=' +
-            encodeURIComponent(
-                remote_image_url +
-                '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
-            'use-credentials',
-            NOT_TAINTED)
-      ])
-      .then(function() {
-          port.postMessage({results: 'finish'});
-        })
-      .catch(function(e) {
-          port.postMessage({results: 'failure:' + e});
-        });
-  }, false);
 </script>
+</html>
diff --git a/service-workers/service-worker/resources/fetch-canvas-tainting-tests.js b/service-workers/service-worker/resources/fetch-canvas-tainting-tests.js
new file mode 100644
index 0000000..2aada36
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-canvas-tainting-tests.js
@@ -0,0 +1,241 @@
+// This is the main driver of the canvas tainting tests.
+const NOT_TAINTED = 'NOT_TAINTED';
+const TAINTED = 'TAINTED';
+const LOAD_ERROR = 'LOAD_ERROR';
+
+let frame;
+
+// Creates a single promise_test.
+function canvas_taint_test(url, cross_origin, expected_result) {
+  promise_test(t => {
+      return frame.contentWindow.create_test_case_promise(url, cross_origin)
+        .then(result => {
+          assert_equals(result, expected_result);
+        });
+    }, 'url "' + url + '" with crossOrigin "' + cross_origin + '" should be ' +
+           expected_result);
+}
+
+
+// Runs all the tests. The given |params| has these properties:
+// * |resource_path|: the relative path to the (image/video) resource to test.
+// * |cache|: when true, the service worker bounces responses into
+//   Cache Storage and back out before responding with them.
+function do_canvas_tainting_tests(params) {
+  const host_info = get_host_info();
+  let resource_path = params.resource_path;
+  if (params.cache)
+    resource_path += "&cache=true";
+  const resource_url = host_info['HTTPS_ORIGIN'] + resource_path;
+  const remote_resource_url = host_info['HTTPS_REMOTE_ORIGIN'] + resource_path;
+
+  // Set up the service worker and the frame.
+  promise_test(function(t) {
+      const SCOPE = 'resources/fetch-canvas-tainting-iframe.html';
+      const SCRIPT = 'resources/fetch-rewrite-worker.js';
+      const host_info = get_host_info();
+
+      // login_https() is needed because some test cases use credentials.
+      return login_https(t)
+        .then(function() {
+            return service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+          })
+        .then(function(registration) {
+            promise_test(() => {
+                if (frame)
+                  frame.remove();
+                return registration.unregister();
+              }, 'restore global state');
+
+            return wait_for_state(t, registration.installing, 'activated');
+          })
+        .then(function() { return with_iframe(SCOPE); })
+        .then(f => {
+            frame = f;
+          });
+    }, 'initialize global state');
+
+  // Reject tests. Add '&reject' so the service worker responds with a rejected promise.
+  // A load error is expected.
+  canvas_taint_test(resource_url + '&reject', '', LOAD_ERROR);
+  canvas_taint_test(resource_url + '&reject', 'anonymous', LOAD_ERROR);
+  canvas_taint_test(resource_url + '&reject', 'use-credentials', LOAD_ERROR);
+
+  // Fallback tests. Add '&ignore' so the service worker does not respond to the fetch
+  // request, and we fall back to network.
+  canvas_taint_test(resource_url + '&ignore', '', NOT_TAINTED);
+  canvas_taint_test(remote_resource_url + '&ignore', '', TAINTED);
+  canvas_taint_test(remote_resource_url + '&ignore', 'anonymous', LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+          '&ignore',
+      'anonymous',
+      NOT_TAINTED);
+  canvas_taint_test(remote_resource_url + '&ignore', 'use-credentials', LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+          '&ignore',
+      'use-credentials',
+      LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+          '&ACACredentials=true&ignore',
+      'use-credentials',
+      NOT_TAINTED);
+
+  // Credential tests (with fallback). Add '&Auth' so the server requires authentication.
+  // Furthermore, add '&ignore' so the service worker falls back to network.
+  canvas_taint_test(resource_url + '&Auth&ignore', '', NOT_TAINTED);
+  canvas_taint_test(remote_resource_url + '&Auth&ignore', '', TAINTED);
+  canvas_taint_test(
+      remote_resource_url + '&Auth&ignore', 'anonymous', LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url + '&Auth&ignore',
+      'use-credentials',
+      LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+      '&ignore',
+      'use-credentials',
+      LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] +
+      '&ACACredentials=true&ignore',
+      'use-credentials',
+      NOT_TAINTED);
+
+  // In the following tests, the service worker provides a response.
+  // Add '&url' so the service worker responds with fetch(url).
+  // Add '&mode' to configure the fetch request options.
+
+  // Basic response tests. Set &url to the original url.
+  canvas_taint_test(
+      resource_url + '&mode=same-origin&url=' + encodeURIComponent(resource_url),
+      '',
+      NOT_TAINTED);
+  canvas_taint_test(
+      resource_url + '&mode=same-origin&url=' + encodeURIComponent(resource_url),
+      'anonymous',
+      NOT_TAINTED);
+  canvas_taint_test(
+      resource_url + '&mode=same-origin&url=' + encodeURIComponent(resource_url),
+      'use-credentials',
+      NOT_TAINTED);
+  canvas_taint_test(
+      remote_resource_url + '&mode=same-origin&url=' +
+          encodeURIComponent(resource_url),
+      '',
+      NOT_TAINTED);
+  canvas_taint_test(
+      remote_resource_url + '&mode=same-origin&url=' +
+          encodeURIComponent(resource_url),
+      'anonymous',
+      NOT_TAINTED);
+  canvas_taint_test(
+      remote_resource_url + '&mode=same-origin&url=' +
+          encodeURIComponent(resource_url),
+      'use-credentials',
+      NOT_TAINTED);
+
+  // Opaque response tests. Set &url to the cross-origin URL, and &mode to
+  // 'no-cors' so we expect an opaque response.
+  canvas_taint_test(
+      resource_url +
+          '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url),
+      '',
+      TAINTED);
+  canvas_taint_test(
+      resource_url +
+          '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url),
+      'anonymous',
+      LOAD_ERROR);
+  canvas_taint_test(
+      resource_url +
+          '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url),
+      'use-credentials',
+      LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url +
+          '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url),
+      '',
+      TAINTED);
+  canvas_taint_test(
+      remote_resource_url +
+          '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url),
+      'anonymous',
+      LOAD_ERROR);
+  canvas_taint_test(
+      remote_resource_url +
+          '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url),
+      'use-credentials',
+      LOAD_ERROR);
+
+  // CORS response tests. Set &url to the cross-origin URL, and &mode
+  // to 'cors' to attempt a CORS request.
+  canvas_taint_test(
+      resource_url + '&mode=cors&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      '',
+      LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond
+                   // with an Access-Control-Allow-Credentials header.
+  canvas_taint_test(
+      resource_url + '&mode=cors&credentials=same-origin&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      '',
+      NOT_TAINTED);
+  canvas_taint_test(
+      resource_url + '&mode=cors&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      'anonymous',
+      NOT_TAINTED);
+  canvas_taint_test(
+      resource_url + '&mode=cors&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      'use-credentials',
+      LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond
+                   // with an Access-Control-Allow-Credentials header.
+  canvas_taint_test(
+      resource_url + '&mode=cors&url=' +
+          encodeURIComponent(
+              remote_resource_url +
+              '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      'use-credentials',
+      NOT_TAINTED);
+  canvas_taint_test(
+      remote_resource_url + '&mode=cors&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      '',
+      LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond
+                   // with an Access-Control-Allow-Credentials header.
+  canvas_taint_test(
+      remote_resource_url + '&mode=cors&credentials=same-origin&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      '',
+      NOT_TAINTED);
+  canvas_taint_test(
+      remote_resource_url + '&mode=cors&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      'anonymous',
+      NOT_TAINTED);
+  canvas_taint_test(
+      remote_resource_url + '&mode=cors&url=' +
+          encodeURIComponent(remote_resource_url +
+                             '&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      'use-credentials',
+      LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond
+                   // with an Access-Control-Allow-Credentials header.
+  canvas_taint_test(
+      remote_resource_url + '&mode=cors&url=' +
+          encodeURIComponent(
+              remote_resource_url +
+              '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']),
+      'use-credentials',
+      NOT_TAINTED);
+}
diff --git a/service-workers/service-worker/resources/fetch-cors-exposed-header-names-worker.js b/service-workers/service-worker/resources/fetch-cors-exposed-header-names-worker.js
new file mode 100644
index 0000000..145952a
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-cors-exposed-header-names-worker.js
@@ -0,0 +1,3 @@
+self.addEventListener('fetch', (e) => {
+    e.respondWith(fetch(e.request));
+  });
diff --git a/service-workers/service-worker/resources/fetch-event-network-fallback-worker.js b/service-workers/service-worker/resources/fetch-event-network-fallback-worker.js
new file mode 100644
index 0000000..daa200c
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-event-network-fallback-worker.js
@@ -0,0 +1,3 @@
+self.addEventListener('fetch', () => {
+    // Do nothing.
+  });
diff --git a/service-workers/service-worker/resources/fetch-event-respond-with-body-loaded-in-chunk-worker.js b/service-workers/service-worker/resources/fetch-event-respond-with-body-loaded-in-chunk-worker.js
new file mode 100644
index 0000000..d3ba8a8
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-event-respond-with-body-loaded-in-chunk-worker.js
@@ -0,0 +1,7 @@
+'use strict';
+
+self.addEventListener('fetch', event => {
+    if (!event.request.url.match(/body-in-chunk$/))
+        return;
+    event.respondWith(fetch("../../../fetch/api/resources/trickle.py?count=4&delay=50"));
+});
diff --git a/service-workers/service-worker/resources/fetch-event-respond-with-custom-response-worker.js b/service-workers/service-worker/resources/fetch-event-respond-with-custom-response-worker.js
new file mode 100644
index 0000000..ff24aed
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-event-respond-with-custom-response-worker.js
@@ -0,0 +1,45 @@
+'use strict';
+
+addEventListener('fetch', event => {
+  const url = new URL(event.request.url);
+  const type = url.searchParams.get('type');
+
+  if (!type) return;
+
+  if (type === 'string') {
+    event.respondWith(new Response('PASS'));
+  }
+  else if (type === 'blob') {
+    event.respondWith(
+      new Response(new Blob(['PASS']))
+    );
+  }
+  else if (type === 'buffer-view') {
+    const encoder = new TextEncoder();
+    event.respondWith(
+      new Response(encoder.encode('PASS'))
+    );
+  }
+  else if (type === 'buffer') {
+    const encoder = new TextEncoder();
+    event.respondWith(
+      new Response(encoder.encode('PASS').buffer)
+    );
+  }
+  else if (type === 'form-data') {
+    const body = new FormData();
+    body.set('result', 'PASS');
+    event.respondWith(
+      new Response(body)
+    );
+  }
+  else if (type === 'search-params') {
+    const body = new URLSearchParams();
+    body.set('result', 'PASS');
+    event.respondWith(
+      new Response(body, {
+        headers: { 'Content-Type': 'text/plain' }
+      })
+    );
+  }
+});
diff --git a/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-chunk-worker.js b/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-chunk-worker.js
new file mode 100644
index 0000000..f954e3a
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-chunk-worker.js
@@ -0,0 +1,40 @@
+'use strict';
+
+self.addEventListener('fetch', event => {
+    if (!event.request.url.match(/body-stream$/))
+        return;
+
+    var counter = 0;
+    const encoder = new TextEncoder();
+    const stream = new ReadableStream({ pull: controller => {
+        switch (++counter) {
+        case 1:
+            controller.enqueue(encoder.encode(''));
+            return;
+        case 2:
+            controller.enqueue(encoder.encode('chunk #1'));
+            return;
+        case 3:
+            controller.enqueue(encoder.encode(' '));
+            return;
+        case 4:
+            controller.enqueue(encoder.encode('chunk #2'));
+            return;
+        case 5:
+            controller.enqueue(encoder.encode(' '));
+            return;
+        case 6:
+            controller.enqueue(encoder.encode('chunk #3'));
+            return;
+        case 7:
+            controller.enqueue(encoder.encode(' '));
+            return;
+        case 8:
+            controller.enqueue(encoder.encode('chunk #4'));
+            return;
+        default:
+            controller.close();
+        }
+    }});
+    event.respondWith(new Response(stream));
+});
diff --git a/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-iframe.html b/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-iframe.html
deleted file mode 100644
index 1904d75..0000000
--- a/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-iframe.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<script>
-'use strict';
-
-fetch('body-stream').then(resp => {
-    return resp.text();
-  }).then(text => parent.done(text))
-  .catch(e => parent.done('FAIL: ' + e));
-</script>
diff --git a/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-worker.js b/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-worker.js
index ddaba55..aaabdc3 100644
--- a/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-worker.js
+++ b/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-worker.js
@@ -1,13 +1,37 @@
 'use strict';
+importScripts("/resources/testharness.js");
 
 self.addEventListener('fetch', event => {
-    if (!event.request.url.match(/body-stream$/))
-      return;
+  const url = new URL(event.request.url);
+  if (!url.searchParams.has('stream')) return;
 
-    const stream = new ReadableStream({start: controller => {
-        const encoder = new TextEncoder();
+  if (url.searchParams.has('use-fetch-stream')) {
+    event.respondWith(async function() {
+      const response = await fetch('pass.txt');
+      return new Response(response.body);
+    }());
+    return;
+  }
+
+  const delayEnqueue = url.searchParams.has('delay');
+
+  const stream = new ReadableStream({
+    start(controller) {
+      const encoder = new TextEncoder();
+
+      const populate = () => {
         controller.enqueue(encoder.encode('PASS'));
         controller.close();
-      }});
-    event.respondWith(new Response(stream));
+      }
+
+      if (delayEnqueue) {
+        step_timeout(populate, 16);
+      }
+      else {
+        populate();
+      }
+    }
   });
+
+  event.respondWith(new Response(stream));
+});
diff --git a/service-workers/service-worker/resources/fetch-event-test-worker.js b/service-workers/service-worker/resources/fetch-event-test-worker.js
index 3d8eb0b..a313094 100644
--- a/service-workers/service-worker/resources/fetch-event-test-worker.js
+++ b/service-workers/service-worker/resources/fetch-event-test-worker.js
@@ -29,7 +29,7 @@
 
 function handleClientId(event) {
   var body;
-  if (event.clientId !== null) {
+  if (event.clientId !== "") {
     body = 'Client ID Found: ' + event.clientId;
   } else {
     body = 'Client ID Not Found';
@@ -129,6 +129,14 @@
   event.respondWith(new Response(event.request.keepalive));
 }
 
+function handleIsReloadNavigation(event) {
+  const request = event.request;
+  const body =
+    `method = ${request.method}, ` +
+    `isReloadNavigation = ${request.isReloadNavigation}`;
+  event.respondWith(new Response(body));
+}
+
 self.addEventListener('fetch', function(event) {
     var url = event.request.url;
     var handlers = [
@@ -151,6 +159,7 @@
       { pattern: '?integrity', fn: handleIntegrity },
       { pattern: '?request-body', fn: handleRequestBody },
       { pattern: '?keepalive', fn: handleKeepalive },
+      { pattern: '?isReloadNavigation', fn: handleIsReloadNavigation },
     ];
 
     var handler = null;
diff --git a/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html b/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html
index 68a1a86..e6e9380 100644
--- a/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html
+++ b/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html
@@ -1 +1 @@
-<script src="empty.js"></script>
+<script src="./fetch-request-no-freshness-headers-script.py"></script>
diff --git a/service-workers/service-worker/resources/fetch-request-no-freshness-headers-script.py b/service-workers/service-worker/resources/fetch-request-no-freshness-headers-script.py
new file mode 100644
index 0000000..e6a392c
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-request-no-freshness-headers-script.py
@@ -0,0 +1,6 @@
+def main(request, response):
+    headers = []
+    # Sets an ETag header to check the cache revalidation behavior.
+    headers.append(("ETag", "abc123"))
+    headers.append(("Content-Type", "text/javascript"))
+    return headers, "/* empty script */"
diff --git a/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html b/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html
index 2e5d7df..fca5722 100644
--- a/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html
+++ b/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html
@@ -71,5 +71,14 @@
   audio.src = url;
   document.body.appendChild(audio);
 }
+
+function load_video(url, cross_origin) {
+  var video = document.createElement('video');
+  if (cross_origin != '') {
+    video.crossOrigin = cross_origin;
+  }
+  video.src = url;
+  document.body.appendChild(video);
+}
 </script>
 </body>
diff --git a/service-workers/service-worker/resources/fetch-request-resources-worker.js b/service-workers/service-worker/resources/fetch-request-resources-worker.js
index e732da0..d85f714 100644
--- a/service-workers/service-worker/resources/fetch-request-resources-worker.js
+++ b/service-workers/service-worker/resources/fetch-request-resources-worker.js
@@ -19,7 +19,8 @@
         mode: event.request.mode,
         redirect: event.request.redirect,
         credentials: event.request.credentials,
-        integrity: event.request.integrity
+        integrity: event.request.integrity,
+        destination: event.request.destination
       });
     event.respondWith(Promise.reject());
   });
diff --git a/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html b/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
index 867bf23..65fa517 100644
--- a/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
+++ b/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
@@ -19,24 +19,6 @@
   return '';
 }
 
-function create_file_system_file(file_name, data) {
-  return new Promise(function(resolve, reject) {
-      webkitRequestFileSystem(TEMPORARY, 1024, function(fs) {
-          fs.root.getFile(
-            file_name, {create: true, exclusive: true},
-            function(fileEntry) {
-              fileEntry.createWriter(function(fileWriter) {
-                  fileWriter.onwriteend = function(e) {
-                    fileEntry.file(function(file) { resolve(file); });
-                  };
-                  var blob = new Blob([data], {type: 'text/plain'});
-                  fileWriter.write(blob);
-                });
-            }, function(e) { reject(e); });
-        }, function(e) { reject(e); });
-    });
-}
-
 function xhr_send(url_base, method, data, with_credentials) {
   return new Promise(function(resolve, reject) {
       var xhr = new XMLHttpRequest();
@@ -179,7 +161,7 @@
           '\r\n' +
           'file content\r\n' +
           '--' + boundary + '--\r\n';
-        assert_equals(response.body, expected_body);
+        assert_true(response.body === expected_body, "form data response content is as expected");
       });
 }
 
diff --git a/service-workers/service-worker/resources/fetch-request-xhr-sync-on-worker-worker.js b/service-workers/service-worker/resources/fetch-request-xhr-sync-on-worker-worker.js
new file mode 100644
index 0000000..0d24ffc
--- /dev/null
+++ b/service-workers/service-worker/resources/fetch-request-xhr-sync-on-worker-worker.js
@@ -0,0 +1,41 @@
+'use strict';
+
+self.onfetch = function(event) {
+  if (event.request.url.indexOf('non-existent-file.txt') !== -1) {
+    event.respondWith(new Response('Response from service worker'));
+  } else if (event.request.url.indexOf('/iframe_page') !== -1) {
+    event.respondWith(new Response(
+        '<!DOCTYPE html>\n' +
+        '<script>\n' +
+        'function performSyncXHROnWorker(url) {\n' +
+        '  return new Promise((resolve) => {\n' +
+        '    var worker =\n' +
+        '        new Worker(\'./worker_script\');\n' +
+        '    worker.addEventListener(\'message\', (msg) => {\n' +
+        '      resolve(msg.data);\n' +
+        '    });\n' +
+        '    worker.postMessage({\n' +
+        '      url: url\n' +
+        '    });\n' +
+        '  });\n' +
+        '}\n' +
+        '</script>',
+        {
+          headers: [['content-type', 'text/html']]
+        }));
+  } else if (event.request.url.indexOf('/worker_script') !== -1) {
+    event.respondWith(new Response(
+        'self.onmessage = (msg) => {' +
+        '  const syncXhr = new XMLHttpRequest();' +
+        '  syncXhr.open(\'GET\', msg.data.url, false);' +
+        '  syncXhr.send();' +
+        '  self.postMessage({' +
+        '    status: syncXhr.status,' +
+        '    responseText: syncXhr.responseText' +
+        '  });' +
+        '}',
+        {
+          headers: [['content-type', 'application/javascript']]
+        }));
+  }
+};
diff --git a/service-workers/service-worker/resources/fetch-rewrite-worker.js b/service-workers/service-worker/resources/fetch-rewrite-worker.js
index 9806f2b..f2d49e2 100644
--- a/service-workers/service-worker/resources/fetch-rewrite-worker.js
+++ b/service-workers/service-worker/resources/fetch-rewrite-worker.js
@@ -1,3 +1,11 @@
+// By default, this worker responds to fetch events with
+// respondWith(fetch(request)). Additionally, if the request has a &url
+// parameter, it fetches the provided URL instead. Because it forwards fetch
+// events to this other URL, it is called the "fetch rewrite" worker.
+//
+// The worker also looks for other params on the request to do more custom
+// behavior, like falling back to network or throwing an error.
+
 function get_query_params(url) {
   var search = (new URL(url)).search;
   if (!search) {
@@ -125,6 +133,7 @@
             }
           }
 
+          // |cache| means to bounce responses through Cache Storage and back.
           if (params['cache']) {
             var cacheName = "cached-fetches-" + performance.now() + "-" +
                             event.request.url;
diff --git a/service-workers/service-worker/resources/iframe-register-link-element.html b/service-workers/service-worker/resources/iframe-register-link-element.html
deleted file mode 100644
index db5bf23..0000000
--- a/service-workers/service-worker/resources/iframe-register-link-element.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<script>
-'use strict';
-var link = document.createElement('link');
-link.setAttribute('rel', 'serviceworker');
-link.setAttribute('href', 'empty-worker.js');
-function report(result) {
-    top.postMessage({id: location.search, result: result}, '*');
-};
-link.onload = function() {
-    report('loaded');
-};
-link.onerror = function() {
-    report('error');
-};
-document.getElementsByTagName('head')[0].appendChild(link);
-</script>
diff --git a/service-workers/service-worker/resources/iframe-with-image.html b/service-workers/service-worker/resources/iframe-with-image.html
new file mode 100644
index 0000000..ce78840
--- /dev/null
+++ b/service-workers/service-worker/resources/iframe-with-image.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="square">
diff --git a/service-workers/service-worker/resources/install-worker.html b/service-workers/service-worker/resources/install-worker.html
new file mode 100644
index 0000000..ed20cd4
--- /dev/null
+++ b/service-workers/service-worker/resources/install-worker.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>Loading...</p>
+<script>
+async function install() {
+  let script;
+  for (const q of location.search.slice(1).split('&')) {
+    if (q.split('=')[0] === 'script') {
+      script = q.split('=')[1];
+    }
+  }
+  const scope = location.href;
+  const reg = await navigator.serviceWorker.register(script, {scope});
+  await navigator.serviceWorker.ready;
+  location.reload();
+}
+
+install();
+</script>
+</body>
+</html>
diff --git a/service-workers/service-worker/resources/interfaces-idls.js b/service-workers/service-worker/resources/interfaces-idls.js
index 9f5c5ed..3db45c6 100644
--- a/service-workers/service-worker/resources/interfaces-idls.js
+++ b/service-workers/service-worker/resources/interfaces-idls.js
@@ -12,124 +12,3 @@
   attribute EventHandler onerror;
 };
 `;
-idls.tested = `
-[Global=(Worker,ServiceWorker), Exposed=ServiceWorker]
-interface ServiceWorkerGlobalScope : WorkerGlobalScope {
-  [SameObject] readonly attribute Clients clients;
-  [SameObject] readonly attribute ServiceWorkerRegistration registration;
-
-  [NewObject] Promise<void> skipWaiting();
-
-  attribute EventHandler oninstall;
-  attribute EventHandler onactivate;
-  attribute EventHandler onfetch;
-
-  // event
-  attribute EventHandler onmessage; // event.source of the message events is Client object
-  attribute EventHandler onmessageerror;
-};
-
-[Exposed=ServiceWorker]
-interface Client {
-  readonly attribute USVString url;
-  readonly attribute DOMString id;
-  readonly attribute ClientType type;
-  readonly attribute boolean reserved;
-  void postMessage(any message, optional sequence<object> transfer = []);
-};
-
-[Exposed=ServiceWorker]
-interface WindowClient : Client {
-  readonly attribute VisibilityState visibilityState;
-  readonly attribute boolean focused;
-  [SameObject] readonly attribute FrozenArray<USVString> ancestorOrigins;
-  [NewObject] Promise<WindowClient> focus();
-  [NewObject] Promise<WindowClient> navigate(USVString url);
-};
-
-[Exposed=ServiceWorker]
-interface Clients {
-  // The objects returned will be new instances every time
-  [NewObject] Promise<any> get(DOMString id);
-  [NewObject] Promise<sequence<Client>> matchAll(optional ClientQueryOptions options);
-  [NewObject] Promise<WindowClient?> openWindow(USVString url);
-  [NewObject] Promise<void> claim();
-};
-
-[SecureContext, Exposed=(Window,Worker)]
-interface ServiceWorker : EventTarget {
-  readonly attribute USVString scriptURL;
-  readonly attribute ServiceWorkerState state;
-  void postMessage(any message, optional sequence<object> transfer = []);
-
-  // event
-  attribute EventHandler onstatechange;
-};
-ServiceWorker implements AbstractWorker;
-
-enum ServiceWorkerState {
-  "installing",
-  "installed",
-  "activating",
-  "activated",
-  "redundant"
-};
-
-enum ServiceWorkerUpdateViaCache {
-    "imports",
-    "all",
-    "none"
-};
-
-[SecureContext, Exposed=(Window,Worker)]
-interface ServiceWorkerRegistration : EventTarget {
-  readonly attribute ServiceWorker? installing;
-  readonly attribute ServiceWorker? waiting;
-  readonly attribute ServiceWorker? active;
-  [SameObject] readonly attribute NavigationPreloadManager navigationPreload;
-
-  readonly attribute USVString scope;
-  readonly attribute ServiceWorkerUpdateViaCache updateViaCache;
-
-  [NewObject] Promise<void> update();
-  [NewObject] Promise<boolean> unregister();
-
-  // event
-  attribute EventHandler onupdatefound;
-};
-
-[Constructor(), Exposed=(Window,Worker)]
-interface EventTarget {
-  void addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options);
-  void removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
-  boolean dispatchEvent(Event event);
-};
-
-[SecureContext, Exposed=(Window,Worker)]
-interface NavigationPreloadManager {
-  Promise<void> enable();
-  Promise<void> disable();
-  Promise<void> setHeaderValue(ByteString value);
-  Promise<NavigationPreloadState> getState();
-};
-
-[SecureContext, Exposed=(Window,Worker)]
-interface Cache {
-  [NewObject] Promise<any> match(RequestInfo request, optional CacheQueryOptions options);
-  [NewObject] Promise<sequence<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options);
-  [NewObject] Promise<void> add(RequestInfo request);
-  [NewObject] Promise<void> addAll(sequence<RequestInfo> requests);
-  [NewObject] Promise<void> put(RequestInfo request, Response response);
-  [NewObject] Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options);
-  [NewObject] Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
-};
-
-[SecureContext, Exposed=(Window,Worker)]
-interface CacheStorage {
-  [NewObject] Promise<any> match(RequestInfo request, optional CacheQueryOptions options);
-  [NewObject] Promise<boolean> has(DOMString cacheName);
-  [NewObject] Promise<Cache> open(DOMString cacheName);
-  [NewObject] Promise<boolean> delete(DOMString cacheName);
-  [NewObject] Promise<sequence<DOMString>> keys();
-};
-`;
diff --git a/service-workers/service-worker/resources/interfaces-worker.sub.js b/service-workers/service-worker/resources/interfaces-worker.sub.js
index 89cf503..29c859b 100644
--- a/service-workers/service-worker/resources/interfaces-worker.sub.js
+++ b/service-workers/service-worker/resources/interfaces-worker.sub.js
@@ -5,10 +5,28 @@
 importScripts('/resources/WebIDLParser.js');
 importScripts('/resources/idlharness.js');
 
-var idlArray = new IdlArray();
-idlArray.add_untested_idls(idls.untested);
-idlArray.add_idls(idls.tested);
-idlArray.add_objects({
+promise_test(async (t) => {
+  var idlArray = new IdlArray();
+  const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+  const serviceWorkerIdl = await fetch('/interfaces/ServiceWorker.idl').then(r => r.text());
+
+  idlArray.add_untested_idls(idls.untested);
+  idlArray.add_untested_idls(dom, { only: ['EventTarget'] });
+  idlArray.add_idls(serviceWorkerIdl, { only: [
+    'ServiceWorkerGlobalScope',
+    'Client',
+    'WindowClient',
+    'Clients',
+    'ServiceWorker',
+    'ServiceWorkerState',
+    'ServiceWorkerUpdateViaCache',
+    'ServiceWorkerRegistration',
+    'EventTarget',
+    'NavigationPreloadManager',
+    'Cache',
+    'CacheStorage',
+  ]});
+  idlArray.add_objects({
     ServiceWorkerGlobalScope: ['self'],
     Clients: ['self.clients'],
     ServiceWorkerRegistration: ['self.registration'],
@@ -17,16 +35,14 @@
     // Client: ['self.clientInstance'],
     // WindowClient: ['self.windowClientInstance']
   });
+  return create_temporary_cache(t)
+    .then(function(cache) {
+        self.cacheInstance = cache;
 
-promise_test(function(t) {
-    return create_temporary_cache(t)
-      .then(function(cache) {
-          self.cacheInstance = cache;
-
-          idlArray.add_objects({ Cache: ['self.cacheInstance'] });
-          idlArray.test();
-        });
-  }, 'test setup (cache creation)');
+        idlArray.add_objects({ Cache: ['self.cacheInstance'] });
+        idlArray.test();
+      });
+}, 'test setup (cache creation)');
 
 test(function() {
     var req = new Request('http://{{host}}/',
@@ -55,10 +71,7 @@
       false, 'Default FetchEvent.bubbles should be false');
     assert_equals(
       new FetchEvent('FetchEvent', {request: req}).clientId,
-      null, 'Default FetchEvent.clientId should be null');
-    assert_equals(
-      new FetchEvent('FetchEvent', {request: req}).isReload,
-      false, 'Default FetchEvent.isReload should be false');
+      '', 'Default FetchEvent.clientId should be the empty string');
     assert_equals(
       new FetchEvent('FetchEvent', {request: req, cancelable: false}).cancelable,
       false, 'FetchEvent.cancelable should be false');
@@ -66,12 +79,14 @@
       new FetchEvent('FetchEvent', {request: req, clientId : 'test-client-id'}).clientId, 'test-client-id',
       'FetchEvent.clientId with option {clientId : "test-client-id"} should be "test-client-id"');
     assert_equals(
-      new FetchEvent('FetchEvent', {request: req, isReload : true}).isReload, true,
-      'FetchEvent.isReload with option {isReload : true} should be true');
-    assert_equals(
-      new FetchEvent('FetchEvent', {request : req, isReload : true}).request.url,
+      new FetchEvent('FetchEvent', {request : req}).request.url,
       'http://{{host}}/',
       'FetchEvent.request.url should return the value it was initialized to');
+    assert_equals(
+      new FetchEvent('FetchEvent', {request : req}).isReload,
+      undefined,
+      'FetchEvent.isReload should not exist');
+
   }, 'Event constructors');
 
 test(() => {
diff --git a/service-workers/service-worker/resources/link-header.py b/service-workers/service-worker/resources/link-header.py
deleted file mode 100644
index 1ea37c3..0000000
--- a/service-workers/service-worker/resources/link-header.py
+++ /dev/null
@@ -1,4 +0,0 @@
-def main(request, response):
-    if 'Link' in request.GET:
-        return [('Link', request.GET['Link'])], ""
-    return [], ""
diff --git a/service-workers/service-worker/resources/local-url-inherit-controller-frame.html b/service-workers/service-worker/resources/local-url-inherit-controller-frame.html
new file mode 100644
index 0000000..b331f88
--- /dev/null
+++ b/service-workers/service-worker/resources/local-url-inherit-controller-frame.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<script>
+
+const fetchURL = new URL('dummy.txt', window.location).href;
+
+const frameControllerText =
+`<script>
+  let t = null;
+  try {
+    if (navigator.serviceWorker.controller) {
+      t = navigator.serviceWorker.controller.scriptURL;
+    }
+  } catch (e) {
+    t = e.message;
+  } finally {
+    parent.postMessage({ data: t }, '*');
+  }
+</` + `script>`;
+
+const frameFetchText =
+`<script>
+  fetch('${fetchURL}', { mode: 'no-cors' }).then(response => {
+    return response.text();
+  }).then(text => {
+    parent.postMessage({ data: text }, '*');
+  }).catch(e => {
+    parent.postMessage({ data: e.message }, '*');
+  });
+</` + `script>`;
+
+const workerControllerText =
+`let t = navigator.serviceWorker.controller
+       ? navigator.serviceWorker.controller.scriptURL
+       : null;
+self.postMessage(t);`;
+
+const workerFetchText =
+`fetch('${fetchURL}', { mode: 'no-cors' }).then(response => {
+  return response.text();
+}).then(text => {
+  self.postMessage(text);
+}).catch(e => {
+  self.postMessage(e.message);
+});`
+
+function getChildText(opts) {
+  if (opts.child === 'iframe') {
+    if (opts.check === 'controller') {
+      return frameControllerText;
+    }
+
+    if (opts.check === 'fetch') {
+      return frameFetchText;
+    }
+
+    throw('unexpected feature to check: ' + opts.check);
+  }
+
+  if (opts.child === 'worker') {
+    if (opts.check === 'controller') {
+      return workerControllerText;
+    }
+
+    if (opts.check === 'fetch') {
+      return workerFetchText;
+    }
+
+    throw('unexpected feature to check: ' + opts.check);
+  }
+
+  throw('unexpected child type ' + opts.child);
+}
+
+function makeURL(opts) {
+  let mimetype = opts.child === 'iframe' ? 'text/html'
+                                         : 'text/javascript';
+
+  if (opts.scheme === 'blob') {
+    let blob = new Blob([getChildText(opts)], { type: mimetype });
+    return URL.createObjectURL(blob);
+  }
+
+  if (opts.scheme === 'data') {
+    return `data:${mimetype},${getChildText(opts)}`;
+  }
+
+  throw(`unexpected URL scheme ${opts.scheme}`);
+}
+
+function testWorkerChild(url) {
+  let w = new Worker(url);
+  return new Promise((resolve, reject) => {
+    w.onmessage = resolve;
+    w.onerror = evt => {
+      reject(evt.message);
+    }
+  });
+}
+
+function testIframeChild(url) {
+  let frame = document.createElement('iframe');
+  frame.src = url;
+  document.body.appendChild(frame);
+
+  return new Promise(resolve => {
+    addEventListener('message', evt => {
+      resolve(evt.data);
+    }, { once: true });
+  });
+}
+
+function testURL(opts, url) {
+  if (opts.child === 'worker') {
+    return testWorkerChild(url);
+  }
+
+  if (opts.child === 'iframe') {
+    return testIframeChild(url);
+  }
+
+  throw(`unexpected child type ${opts.child}`);
+}
+
+function checkChildController(opts) {
+  let url = makeURL(opts);
+  return testURL(opts, url);
+}
+</script>
+</html>
diff --git a/service-workers/service-worker/resources/local-url-inherit-controller-worker.js b/service-workers/service-worker/resources/local-url-inherit-controller-worker.js
new file mode 100644
index 0000000..9d8955f
--- /dev/null
+++ b/service-workers/service-worker/resources/local-url-inherit-controller-worker.js
@@ -0,0 +1,5 @@
+addEventListener('fetch', evt => {
+  if (evt.request.url.includes('dummy')) {
+    evt.respondWith(new Response('intercepted'));
+  }
+});
diff --git a/service-workers/service-worker/resources/multipart-image-iframe.html b/service-workers/service-worker/resources/multipart-image-iframe.html
new file mode 100644
index 0000000..c59b955
--- /dev/null
+++ b/service-workers/service-worker/resources/multipart-image-iframe.html
@@ -0,0 +1,19 @@
+<script>
+function load_multipart_image(src) {
+    return new Promise((resolve, reject) => {
+        const img = document.createElement('img');
+        img.addEventListener('load', () => resolve(img));
+        img.addEventListener('error', (e) => reject(new DOMException('load failed', 'NetworkError')));
+        img.src = src;
+    });
+}
+
+function get_image_data(img) {
+    const canvas = document.createElement('canvas');
+    const context = canvas.getContext('2d');
+    context.drawImage(img, 0, 0);
+    // When |img.src| is cross origin, this should throw a SecurityError.
+    const imageData = context.getImageData(0, 0, 1, 1);
+    return imageData;
+}
+</script>
diff --git a/service-workers/service-worker/resources/multipart-image-worker.js b/service-workers/service-worker/resources/multipart-image-worker.js
new file mode 100644
index 0000000..a38fe54
--- /dev/null
+++ b/service-workers/service-worker/resources/multipart-image-worker.js
@@ -0,0 +1,21 @@
+importScripts('/common/get-host-info.sub.js');
+importScripts('test-helpers.sub.js');
+
+const host_info = get_host_info();
+
+const multipart_image_path = base_path() + 'multipart-image.py';
+const sameorigin_url = host_info['HTTPS_ORIGIN'] + multipart_image_path;
+const cross_origin_url = host_info['HTTPS_REMOTE_ORIGIN'] + multipart_image_path;
+
+self.addEventListener('fetch', event => {
+    const url = event.request.url;
+    if (url.indexOf('cross-origin-multipart-image-with-no-cors') >= 0) {
+        event.respondWith(fetch(cross_origin_url, {mode: 'no-cors'}));
+    } else if (url.indexOf('cross-origin-multipart-image-with-cors-rejected') >= 0) {
+        event.respondWith(fetch(cross_origin_url, {mode: 'cors'}));
+    } else if (url.indexOf('cross-origin-multipart-image-with-cors-approved') >= 0) {
+        event.respondWith(fetch(cross_origin_url + '?approvecors', {mode: 'cors'}));
+    } else if (url.indexOf('same-origin-multipart-image') >= 0) {
+        event.respondWith(fetch(sameorigin_url));
+    }
+});
diff --git a/service-workers/service-worker/resources/multipart-image.py b/service-workers/service-worker/resources/multipart-image.py
new file mode 100644
index 0000000..f94ee1c
--- /dev/null
+++ b/service-workers/service-worker/resources/multipart-image.py
@@ -0,0 +1,23 @@
+# A request handler that serves a multipart image.
+
+import os
+
+
+BOUNDARY = 'cutHere'
+
+
+def create_part(path):
+    with open(path, 'rb') as f:
+        return 'Content-Type: image/png\r\n\r\n' + f.read() + '--%s' % BOUNDARY
+
+
+def main(request, response):
+    content_type = 'multipart/x-mixed-replace; boundary=%s' % BOUNDARY
+    headers = [('Content-Type', content_type)]
+    if 'approvecors' in request.GET:
+        headers.append(('Access-Control-Allow-Origin', '*'))
+
+    image_path = os.path.join(request.doc_root, 'images')
+    body = create_part(os.path.join(image_path, 'red.png'))
+    body = body + create_part(os.path.join(image_path, 'red-16x16.png'))
+    return headers, body
diff --git a/service-workers/service-worker/resources/navigation-timing-worker.js b/service-workers/service-worker/resources/navigation-timing-worker.js
new file mode 100644
index 0000000..8539b40
--- /dev/null
+++ b/service-workers/service-worker/resources/navigation-timing-worker.js
@@ -0,0 +1,15 @@
+self.addEventListener('fetch', (event) => {
+    const url = event.request.url;
+
+    // Network fallback.
+    if (url.indexOf('network-fallback') >= 0) {
+        return;
+    }
+
+    // Don't intercept redirect.
+    if (url.indexOf('redirect.py') >= 0) {
+        return;
+    }
+
+    event.respondWith(fetch(url));
+});
diff --git a/service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html b/service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html
new file mode 100644
index 0000000..5a20a58
--- /dev/null
+++ b/service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe for embed-and-object-are-not-intercepted test</title>
+<body>
+<object type="image/png" data="/images/green.png"></embed>
+<script>
+// Our parent (the root frame of the test) will examine this to get the result.
+var test_promise = new Promise(resolve => {
+    if (!navigator.serviceWorker.controller)
+      resolve('FAIL: this iframe is not controlled');
+
+    const elem = document.querySelector('object');
+    elem.addEventListener('load', e => {
+        resolve('request was not intercepted');
+      });
+    elem.addEventListener('error', e => {
+        resolve('FAIL: request was intercepted');
+      });
+  });
+</script>
+</body>
diff --git a/service-workers/service-worker/resources/object-is-not-intercepted-iframe.html b/service-workers/service-worker/resources/object-is-not-intercepted-iframe.html
new file mode 100644
index 0000000..0aeb819
--- /dev/null
+++ b/service-workers/service-worker/resources/object-is-not-intercepted-iframe.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>iframe for embed-and-object-are-not-intercepted test</title>
+<body>
+<script>
+// The OBJECT element will call this with the result about whether the OBJECT
+// request was intercepted by the service worker.
+var report_result;
+
+// Our parent (the root frame of the test) will examine this to get the result.
+var test_promise = new Promise(resolve => {
+    report_result = resolve;
+  });
+</script>
+
+<object data="embedded-content-from-server.html"></object>
+</body>
+
diff --git a/service-workers/service-worker/resources/pass-through-worker.js b/service-workers/service-worker/resources/pass-through-worker.js
new file mode 100644
index 0000000..5eaf48d
--- /dev/null
+++ b/service-workers/service-worker/resources/pass-through-worker.js
@@ -0,0 +1,3 @@
+addEventListener('fetch', evt => {
+  evt.respondWith(fetch(evt.request));
+});
diff --git a/XMLHttpRequest/resources/pass.txt b/service-workers/service-worker/resources/pass.txt
similarity index 100%
copy from XMLHttpRequest/resources/pass.txt
copy to service-workers/service-worker/resources/pass.txt
diff --git a/service-workers/service-worker/resources/range-request-to-different-origins-worker.js b/service-workers/service-worker/resources/range-request-to-different-origins-worker.js
new file mode 100644
index 0000000..cab6058
--- /dev/null
+++ b/service-workers/service-worker/resources/range-request-to-different-origins-worker.js
@@ -0,0 +1,40 @@
+// This worker is meant to test range requests where the responses come from
+// multiple origins. It forwards the first request to a cross-origin URL
+// (generating an opaque response). The server is expected to return a 206
+// Partial Content response.  Then the worker lets subsequent range requests
+// fall back to network (generating same-origin responses). The intent is to try
+// to trick the browser into treating the resource as same-origin.
+//
+// It would also be interesting to do the reverse test where the first request
+// goes to the same-origin URL, and subsequent range requests go cross-origin in
+// 'no-cors' mode to receive opaque responses. But the service worker cannot do
+// this, because in 'no-cors' mode the 'range' HTTP header is disallowed.
+
+importScripts('/common/get-host-info.sub.js')
+
+let initial = true;
+function is_initial_request() {
+  const old = initial;
+  initial = false;
+  return old;
+}
+
+self.addEventListener('fetch', e => {
+    const url = new URL(e.request.url);
+    if (url.search.indexOf('VIDEO') == -1) {
+      // Fall back for non-video.
+      return;
+    }
+
+    // Make the first request go cross-origin.
+    if (is_initial_request()) {
+      const cross_origin_url = get_host_info().HTTPS_REMOTE_ORIGIN +
+          url.pathname + url.search;
+      const cross_origin_request = new Request(cross_origin_url,
+          {mode: 'no-cors', headers: e.request.headers});
+      e.respondWith(fetch(cross_origin_request));
+      return;
+    }
+
+    // Fall back to same origin for subsequent range requests.
+  });
diff --git a/service-workers/service-worker/resources/range-request-with-different-cors-modes-worker.js b/service-workers/service-worker/resources/range-request-with-different-cors-modes-worker.js
new file mode 100644
index 0000000..7580b0b
--- /dev/null
+++ b/service-workers/service-worker/resources/range-request-with-different-cors-modes-worker.js
@@ -0,0 +1,60 @@
+// This worker is meant to test range requests where the responses are a mix of
+// opaque ones and non-opaque ones. It forwards the first request to a
+// cross-origin URL (generating an opaque response). The server is expected to
+// return a 206 Partial Content response.  Then the worker forwards subsequent
+// range requests to that URL, with CORS sharing generating a non-opaque
+// responses. The intent is to try to trick the browser into treating the
+// resource as non-opaque.
+//
+// It would also be interesting to do the reverse test where the first request
+// uses 'cors', and subsequent range requests use 'no-cors' mode. But the
+// service worker cannot do this, because in 'no-cors' mode the 'range' HTTP
+// header is disallowed.
+
+importScripts('/common/get-host-info.sub.js')
+
+let initial = true;
+function is_initial_request() {
+  const old = initial;
+  initial = false;
+  return old;
+}
+
+self.addEventListener('fetch', e => {
+    const url = new URL(e.request.url);
+    if (url.search.indexOf('VIDEO') == -1) {
+      // Fall back for non-video.
+      return;
+    }
+
+    let cross_origin_url = get_host_info().HTTPS_REMOTE_ORIGIN +
+        url.pathname + url.search;
+
+    // The first request is no-cors.
+    if (is_initial_request()) {
+      const init = { mode: 'no-cors', headers: e.request.headers };
+      const cross_origin_request = new Request(cross_origin_url, init);
+      e.respondWith(fetch(cross_origin_request));
+      return;
+    }
+
+    // Subsequent range requests are cors.
+
+    // Copy headers needed for range requests.
+    let my_headers = new Headers;
+    if (e.request.headers.get('accept'))
+      my_headers.append('accept', e.request.headers.get('accept'));
+    if (e.request.headers.get('range'))
+    my_headers.append('range', e.request.headers.get('range'));
+
+    // Add &ACAOrigin to allow CORS.
+    cross_origin_url += '&ACAOrigin=' + get_host_info().HTTPS_ORIGIN;
+    // Add &ACAHeaders to allow range requests.
+    cross_origin_url += '&ACAHeaders=accept,range';
+
+    // Make the CORS request.
+    const init = { mode: 'cors', headers: my_headers };
+    const cross_origin_request = new Request(cross_origin_url, init);
+    e.respondWith(fetch(cross_origin_request));
+  });
+
diff --git a/service-workers/service-worker/resources/register-closed-window-iframe.html b/service-workers/service-worker/resources/register-closed-window-iframe.html
index ed743ea..117f254 100644
--- a/service-workers/service-worker/resources/register-closed-window-iframe.html
+++ b/service-workers/service-worker/resources/register-closed-window-iframe.html
@@ -1,14 +1,17 @@
 <html>
 <head>
 <script>
-window.addEventListener('message', function(evt) {
+window.addEventListener('message', async function(evt) {
   if (evt.data === 'START') {
     var w = window.open('./');
     var sw = w.navigator.serviceWorker;
     w.close();
     w = null;
-    sw.register('doesntmatter.js');
-    parent.postMessage('OK', '*');
+    try {
+      await sw.register('doesntmatter.js');
+    } finally {
+      parent.postMessage('OK', '*');
+    }
   }
 });
 </script>
diff --git a/service-workers/service-worker/resources/resource-timing-iframe.sub.html b/service-workers/service-worker/resources/resource-timing-iframe.sub.html
index c9308ea..69cff7c 100644
--- a/service-workers/service-worker/resources/resource-timing-iframe.sub.html
+++ b/service-workers/service-worker/resources/resource-timing-iframe.sub.html
@@ -2,5 +2,7 @@
 <script src="empty.js"></script>
 <script src="dummy.js"></script>
 <script src="redirect.py?Redirect=empty.js"></script>
+<img src="square.png">
+<img src="https://{{domains[www1]}}:{{ports[https][0]}}/service-workers/service-worker/resources/square.png">
 <img src="missing.jpg">
 <img src="https://{{domains[www1]}}:{{ports[https][0]}}/service-workers/service-worker/resources/missing.jpg">
diff --git a/service-workers/service-worker/resources/resource-timing-worker.js b/service-workers/service-worker/resources/resource-timing-worker.js
index 4528768..8b30467 100644
--- a/service-workers/service-worker/resources/resource-timing-worker.js
+++ b/service-workers/service-worker/resources/resource-timing-worker.js
@@ -1,9 +1,9 @@
 self.addEventListener('fetch', function(event) {
-    if (event.request.url.indexOf('dummy.js') != -1) {
-      event.respondWith(new Promise(resolve => {
-        // Slightly delay the response so we ensure we get a non-zero
-        // duration.
-        setTimeout(_ => resolve(new Response()), 100);
-      }));
-    }
-  });
+  if (event.request.url.indexOf('dummy.js') != -1) {
+    event.respondWith(new Promise(resolve => {
+      // Slightly delay the response so we ensure we get a non-zero
+      // duration.
+      setTimeout(_ => resolve(new Response('// Empty javascript')), 50);
+    }));
+  }
+});
diff --git a/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.html b/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.html
index 155dbdd..239fa73 100644
--- a/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.html
+++ b/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.html
@@ -1,56 +1,63 @@
 <script>
 function with_iframe(url) {
-  return new Promise(function(resolve) {
-      var frame = document.createElement('iframe');
-      frame.src = url;
-      frame.onload = function() { resolve(frame); };
-      document.body.appendChild(frame);
-    });
+  return new Promise(resolve => {
+    let frame = document.createElement('iframe');
+    frame.src = url;
+    frame.onload = () => { resolve(frame); };
+    document.body.appendChild(frame);
+  });
 }
 
 function with_sandboxed_iframe(url, sandbox) {
-  return new Promise(function(resolve) {
-      var frame = document.createElement('iframe');
-      frame.sandbox = sandbox;
-      frame.src = url;
-      frame.onload = function() { resolve(frame); };
-      document.body.appendChild(frame);
-    });
+  return new Promise(resolve => {
+    let frame = document.createElement('iframe');
+    frame.sandbox = sandbox;
+    frame.src = url;
+    frame.onload = () => { resolve(frame); };
+    document.body.appendChild(frame);
+  });
 }
 
-function fetch_in_worker() {
-  return new Promise((resolve) => {
-    var blob = new Blob([
-      "fetch('" + location.href + "_workerfetch', {mode: 'no-cors'})" +
+function fetch_from_worker(url) {
+  return new Promise(resolve => {
+    let blob = new Blob([
+      `fetch('${url}', {mode: 'no-cors'})` +
       "  .then(() => { self.postMessage('OK'); });"]);
-    var url = URL.createObjectURL(blob);
-    var worker = new Worker(url);
+    let worker_url = URL.createObjectURL(blob);
+    let worker = new Worker(worker_url);
     worker.onmessage = resolve;
   });
 }
 
-window.onmessage = function (e) {
-  var id = e.data['id'];
-  fetch(location.href + "_fetch", {mode: 'no-cors'})
-    .then(function() {
-        return fetch_in_worker();
-      })
-    .then(function() {
-        return with_iframe(location.href + "_iframe");
-      })
-    .then(function() {
-        return with_sandboxed_iframe(location.href + "_script",
-                                     "allow-scripts");
-      })
-    .then(function() {
-        return with_sandboxed_iframe(location.href + "_script-origin",
-                                     "allow-scripts allow-same-origin");
-      })
-    .then(function() {
-        window.top.postMessage({id: id, result: 'done'}, '*');
-      })
-    .catch(function(e) {
-        window.top.postMessage({id: id, result: 'error: ' + e.toString()}, '*');
-      });
+function run_test(type) {
+  const base_path = location.href;
+  switch (type) {
+    case 'fetch':
+      return fetch(`${base_path}&test=fetch`, {mode: 'no-cors'});
+    case 'fetch-from-worker':
+      return fetch_from_worker(`${base_path}&test=fetch-from-worker`);
+    case 'iframe':
+      return with_iframe(`${base_path}&test=iframe`);
+    case 'sandboxed-iframe':
+      return with_sandboxed_iframe(`${base_path}&test=sandboxed-iframe`,
+                                   "allow-scripts");
+    case 'sandboxed-iframe-same-origin':
+      return with_sandboxed_iframe(
+        `${base_path}&test=sandboxed-iframe-same-origin`,
+        "allow-scripts allow-same-origin");
+    default:
+      return Promise.reject(`Unknown type: ${type}`);
+  }
+}
+
+window.onmessage = event => {
+  let id = event.data['id'];
+  run_test(event.data['type'])
+    .then(() => {
+      window.top.postMessage({id: id, result: 'done'}, '*');
+    })
+    .catch(e => {
+      window.top.postMessage({id: id, result: 'error: ' + e.toString()}, '*');
+    });
 };
 </script>
diff --git a/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.py b/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.py
new file mode 100644
index 0000000..8b2244b
--- /dev/null
+++ b/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.py
@@ -0,0 +1,16 @@
+import os.path
+
+def main(request, response):
+  header = [('Content-Type', 'text/html')]
+  if 'test' in request.GET:
+    with open(os.path.join(os.path.dirname(__file__),'blank.html'), 'r') as f:
+      body = f.read()
+    return (header, body)
+
+  if 'sandbox' in request.GET:
+    header.append(('Content-Security-Policy',
+                   'sandbox %s' % request.GET['sandbox']))
+  with open(os.path.join(os.path.dirname(__file__),
+                         'sandboxed-iframe-fetch-event-iframe.html'), 'r') as f:
+    body = f.read()
+  return (header, body)
diff --git a/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-worker.js b/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-worker.js
index ffa0aba..4035a8b 100644
--- a/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-worker.js
+++ b/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-worker.js
@@ -10,6 +10,7 @@
           client_urls = client_urls.sort();
           event.data.port.postMessage(
               {clients: client_urls, requests: requests});
+          requests = [];
         }));
   });
 
diff --git a/service-workers/service-worker/resources/scope1/redirect.py b/service-workers/service-worker/resources/scope1/redirect.py
new file mode 100644
index 0000000..bb4c874
--- /dev/null
+++ b/service-workers/service-worker/resources/scope1/redirect.py
@@ -0,0 +1,6 @@
+import os
+import imp
+# Use the file from the parent directory.
+mod = imp.load_source("_parent", os.path.join(os.path.dirname(os.path.dirname(__file__)),
+                                              os.path.basename(__file__)))
+main = mod.main
diff --git a/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py b/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py
new file mode 100644
index 0000000..bb4c874
--- /dev/null
+++ b/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py
@@ -0,0 +1,6 @@
+import os
+import imp
+# Use the file from the parent directory.
+mod = imp.load_source("_parent", os.path.join(os.path.dirname(os.path.dirname(__file__)),
+                                              os.path.basename(__file__)))
+main = mod.main
diff --git a/service-workers/service-worker/resources/simple-intercept-worker.js.headers b/service-workers/service-worker/resources/simple-intercept-worker.js.headers
new file mode 100644
index 0000000..a17a9a3
--- /dev/null
+++ b/service-workers/service-worker/resources/simple-intercept-worker.js.headers
@@ -0,0 +1 @@
+Content-Type: application/javascript
diff --git a/service-workers/service-worker/resources/square.png.sub.headers b/service-workers/service-worker/resources/square.png.sub.headers
new file mode 100644
index 0000000..7341132
--- /dev/null
+++ b/service-workers/service-worker/resources/square.png.sub.headers
@@ -0,0 +1,2 @@
+Content-Type: image/png
+Access-Control-Allow-Origin: *
diff --git a/service-workers/service-worker/resources/svg-target-reftest-001-frame.html b/service-workers/service-worker/resources/svg-target-reftest-001-frame.html
new file mode 100644
index 0000000..59fb524
--- /dev/null
+++ b/service-workers/service-worker/resources/svg-target-reftest-001-frame.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<img src="/images/green.svg">
diff --git a/service-workers/service-worker/resources/svg-target-reftest-001.html b/service-workers/service-worker/resources/svg-target-reftest-001.html
new file mode 100644
index 0000000..9a93d3b
--- /dev/null
+++ b/service-workers/service-worker/resources/svg-target-reftest-001.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Green svg box reference file</title>
+<p>Pass if you see a green box below.</p>
+<iframe src="svg-target-reftest-001-frame.html">
diff --git a/service-workers/service-worker/resources/svg-target-reftest-frame.html b/service-workers/service-worker/resources/svg-target-reftest-frame.html
new file mode 100644
index 0000000..d6fc820
--- /dev/null
+++ b/service-workers/service-worker/resources/svg-target-reftest-frame.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="/images/colors.svg#green">
diff --git a/service-workers/service-worker/resources/test-helpers.sub.js b/service-workers/service-worker/resources/test-helpers.sub.js
index 1f6bbaa..8fa3e3b 100644
--- a/service-workers/service-worker/resources/test-helpers.sub.js
+++ b/service-workers/service-worker/resources/test-helpers.sub.js
@@ -201,20 +201,12 @@
     });
 }
 
-function login(test) {
-  return test_login(test, 'http://{{domains[www1]}}:{{ports[http][0]}}',
-                    'username1', 'password1', 'cookie1')
-    .then(function() {
-        return test_login(test, 'http://{{host}}:{{ports[http][0]}}',
-                          'username2', 'password2', 'cookie2');
-      });
-}
-
 function login_https(test) {
-  return test_login(test, 'https://{{domains[www1]}}:{{ports[https][0]}}',
+  var host_info = get_host_info();
+  return test_login(test, host_info.HTTPS_REMOTE_ORIGIN,
                     'username1s', 'password1s', 'cookie1')
     .then(function() {
-        return test_login(test, 'https://{{host}}:{{ports[https][0]}}',
+        return test_login(test, host_info.HTTPS_ORIGIN,
                           'username2s', 'password2s', 'cookie2');
       });
 }
@@ -265,3 +257,17 @@
       document.body.appendChild(frame);
     });
 }
+
+// Registers, waits for activation, then unregisters on a dummy scope.
+//
+// This can be used to wait for a period of time needed to register,
+// activate, and then unregister a service worker.  When checking that
+// certain behavior does *NOT* happen, this is preferable to using an
+// arbitrary delay.
+async function wait_for_activation_on_dummy_scope(t, window_or_workerglobalscope) {
+  const script = '/service-workers/service-worker/resources/empty-worker.js';
+  const scope = 'resources/there/is/no/there/there?' + Date.now();
+  let registration = await window_or_workerglobalscope.navigator.serviceWorker.register(script, { scope });
+  await wait_for_state(t, registration.installing, 'activated');
+  await registration.unregister();
+}
diff --git a/service-workers/service-worker/resources/vtt-frame.html b/service-workers/service-worker/resources/vtt-frame.html
new file mode 100644
index 0000000..c3ac803
--- /dev/null
+++ b/service-workers/service-worker/resources/vtt-frame.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Page Title</title>
+<video>
+  <track>
+</video>
diff --git a/service-workers/service-worker/resources/worker-client-id-worker.js b/service-workers/service-worker/resources/worker-client-id-worker.js
new file mode 100644
index 0000000..f592629
--- /dev/null
+++ b/service-workers/service-worker/resources/worker-client-id-worker.js
@@ -0,0 +1,25 @@
+addEventListener('fetch', evt => {
+  if (evt.request.url.includes('worker-echo-client-id.js')) {
+    evt.respondWith(new Response(
+      'fetch("fetch-echo-client-id").then(r => r.text()).then(t => self.postMessage(t));',
+      { headers: { 'Content-Type': 'application/javascript' }}));
+    return;
+  }
+
+  if (evt.request.url.includes('fetch-echo-client-id')) {
+    evt.respondWith(new Response(evt.clientId));
+    return;
+  }
+
+  if (evt.request.url.includes('frame.html')) {
+    evt.respondWith(new Response(''));
+    return;
+  }
+});
+
+addEventListener('message', evt => {
+  if (evt.data === 'echo-client-id') {
+    evt.ports[0].postMessage(evt.source.id);
+    return;
+  }
+});
diff --git a/service-workers/service-worker/resources/worker-fetching-cross-origin.js b/service-workers/service-worker/resources/worker-fetching-cross-origin.js
new file mode 100644
index 0000000..79f0899
--- /dev/null
+++ b/service-workers/service-worker/resources/worker-fetching-cross-origin.js
@@ -0,0 +1,10 @@
+importScripts('/common/get-host-info.sub.js');
+importScripts('test-helpers.sub.js');
+
+self.addEventListener('fetch', event => {
+  const host_info = get_host_info();
+  // The sneaky Service Worker changes the same-origin 'square' request for a cross-origin image.
+  if (event.request.url.indexOf('square') != -1) {
+    event.respondWith(fetch(host_info['HTTPS_REMOTE_ORIGIN'] + base_path() + 'square.png', {mode: 'cors'}));
+  }
+});
diff --git a/service-workers/service-worker/resources/worker-interception-redirect-serviceworker.js b/service-workers/service-worker/resources/worker-interception-redirect-serviceworker.js
new file mode 100644
index 0000000..f5ba5a3
--- /dev/null
+++ b/service-workers/service-worker/resources/worker-interception-redirect-serviceworker.js
@@ -0,0 +1,48 @@
+let name;
+if (self.registration.scope.indexOf('scope1') != -1)
+  name = 'sw1';
+if (self.registration.scope.indexOf('scope2') != -1)
+  name = 'sw2';
+
+
+self.addEventListener('fetch', evt => {
+  // There are three types of requests this service worker handles.
+
+  // (1) The first request for the worker, which will redirect elsewhere.
+  // "redirect.py" means to test network redirect, so let network handle it.
+  if (evt.request.url.indexOf('redirect.py') != -1) {
+    return;
+  }
+  // "sw-redirect" means to test service worker redirect, so respond with a
+  // redirect.
+  if (evt.request.url.indexOf('sw-redirect') != -1) {
+    const url = new URL(evt.request.url);
+    const redirect_to = url.searchParams.get('Redirect');
+    evt.respondWith(Response.redirect(redirect_to));
+    return;
+  }
+
+  // (2) After redirect, the request is for a "webworker.py" URL.
+  // Add a search parameter to indicate this service worker handled the
+  // final request for the worker.
+  if (evt.request.url.indexOf('webworker.py') != -1) {
+    const greeting = encodeURIComponent(`${name} saw the request for the worker script`);
+    evt.respondWith(fetch(`worker_interception_redirect_webworker.py?greeting=${greeting}`));
+    return;
+  }
+
+  // (3) The worker does an importScripts() to import-scripts-echo.py. Indicate
+  // that this service worker handled the request.
+  if (evt.request.url.indexOf('import-scripts-echo.py') != -1) {
+    const msg = encodeURIComponent(`${name} saw importScripts from the worker`);
+    evt.respondWith(fetch(`import-scripts-echo.py?msg=${msg}`));
+    return;
+  }
+
+  // (4) The worker does a fetch() to simple.txt. Indicate that this service
+  // worker handled the request.
+  if (evt.request.url.indexOf('simple.txt') != -1) {
+    evt.respondWith(new Response(`${name} saw the fetch from the worker`));
+    return;
+  }
+});
diff --git a/service-workers/service-worker/resources/worker-interception-redirect-webworker.js b/service-workers/service-worker/resources/worker-interception-redirect-webworker.js
new file mode 100644
index 0000000..d602f23
--- /dev/null
+++ b/service-workers/service-worker/resources/worker-interception-redirect-webworker.js
@@ -0,0 +1,42 @@
+// This is the (shared or dedicated) worker file for the
+// worker-interception-redirect test. It should be served by the corresponding
+// .py file instead of being served directly.
+//
+// This file is served from both resources/*webworker.py and
+// resources/scope2/*webworker.py, hence some of the complexity
+// below about paths.
+const resources_url = new URL("/service-workers/service-worker/resources/",
+                              self.location);
+
+// This greeting text is meant to be injected by the Python script that serves
+// this file, to indicate how the script was served (from network or from
+// service worker).
+//
+// We can't just use a sub pipe and name this file .sub.js since we want
+// to serve the file from multiple URLs (see above).
+let greeting = '%GREETING_TEXT%';
+if (!greeting)
+  greeting = 'the shared worker script was served from network';
+
+// Call importScripts() which fills |echo_output| with a string indicating
+// whether a service worker intercepted the importScripts() request.
+let echo_output;
+const import_scripts_msg = encodeURIComponent(
+    'importScripts: served from network');
+const import_scripts_url =
+    new URL(`import-scripts-echo.py?msg=${import_scripts_msg}`, resources_url);
+importScripts(import_scripts_url);
+const import_scripts_greeting = echo_output;
+
+self.onconnect = async function(e) {
+  const port = e.ports[0];
+  port.start();
+  port.postMessage(greeting);
+
+  port.postMessage(import_scripts_greeting);
+
+  const fetch_url = new URL('simple.txt', resources_url);
+  const response = await fetch(fetch_url);
+  const text = await response.text();
+  port.postMessage('fetch(): ' + text);
+};
diff --git a/service-workers/service-worker/resources/worker_interception_redirect_webworker.py b/service-workers/service-worker/resources/worker_interception_redirect_webworker.py
new file mode 100644
index 0000000..e388e46
--- /dev/null
+++ b/service-workers/service-worker/resources/worker_interception_redirect_webworker.py
@@ -0,0 +1,19 @@
+# This serves the worker JavaScript file. It takes a |greeting| request
+# parameter to inject into the JavaScript to indicate how the request
+# reached the server.
+import os
+import sys
+
+def main(request, response):
+  path = os.path.join(os.path.dirname(__file__),
+                      "worker-interception-redirect-webworker.js")
+  body = open(path, "rb").read()
+  if "greeting" in request.GET:
+    body = body.replace("%GREETING_TEXT%", request.GET["greeting"])
+  else:
+    body = body.replace("%GREETING_TEXT%", "")
+
+  headers = []
+  headers.append(("Content-Type", "text/javascript"))
+
+  return headers, body
diff --git a/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html b/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html
index 8b361bd..e08b716 100644
--- a/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html
+++ b/service-workers/service-worker/sandboxed-iframe-fetch-event.https.html
@@ -7,210 +7,529 @@
 <script>
 var lastCallbackId = 0;
 var callbacks = {};
-function postMessageAndWaitResult(frame) {
+function doTest(frame, type) {
   return new Promise(function(resolve) {
     var id = ++lastCallbackId;
     callbacks[id] = resolve;
-    frame.contentWindow.postMessage({id:id}, '*');
+    frame.contentWindow.postMessage({id: id, type: type}, '*');
+  });
+}
+
+// Asks the service worker for data about requests and clients seen. The
+// worker posts a message back with |data| where:
+// |data.requests|: the requests the worker received FetchEvents for
+// |data.clients|: the URLs of all the worker's clients
+// The worker clears its data after responding.
+function getResultsFromWorker(worker) {
+  return new Promise(resolve => {
+    let channel = new MessageChannel();
+    channel.port1.onmessage = msg => {
+      resolve(msg.data);
+    };
+    worker.postMessage({port: channel.port2}, [channel.port2]);
   });
 }
 
 window.onmessage = function (e) {
   message = e.data;
   var id = message['id'];
-  var calback = callbacks[id];
+  var callback = callbacks[id];
   delete callbacks[id];
-  calback(message['result']);
+  callback(message['result']);
 };
 
-promise_test(function(t) {
-    var SCOPE = 'resources/sandboxed-iframe-fetch-event-iframe.html';
-    var SCRIPT = 'resources/sandboxed-iframe-fetch-event-worker.js';
-    var frames = [];
-    var worker;
-    return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
-      .then(function(registration) {
-          worker = registration.installing;
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(SCOPE + '?iframe');
-        })
-      .then(function(frame) {
-          frames.push(frame);
-          return postMessageAndWaitResult(frame);
-        })
-      .then(function(result) {
-          assert_equals(result, 'done');
-          return with_sandboxed_iframe(SCOPE + '?script', 'allow-scripts');
-        })
-      .then(function(frame) {
-          frames.push(frame);
-          return postMessageAndWaitResult(frame);
-        })
-      .then(function(result) {
-          assert_equals(result, 'done');
-          return with_sandboxed_iframe(SCOPE + '?script-origin',
-                                       'allow-scripts allow-same-origin');
-        })
-      .then(function(frame) {
-          frames.push(frame);
-          return postMessageAndWaitResult(frame);
-        })
-      .then(function(result) {
-          assert_equals(result, 'done');
-          return new Promise(function(resolve) {
-              var channel = new MessageChannel();
-              channel.port1.onmessage = function(msg) {
-                  resolve(msg);
-                };
-              worker.postMessage({port: channel.port2}, [channel.port2]);
-            });
-        })
-      .then(function(msg) {
-          for (var frame of frames) {
-            frame.remove();
-          }
-          var expected_base_url = new URL(SCOPE, location.href).href;
-          var request_set = {};
-          for (var request of msg.data.requests) {
-            request_set[request] = true;
-          }
-          assert_true(
-              expected_base_url + '?iframe' in request_set,
-              'The request for normal iframe should be handled by SW.');
-          assert_true(
-              expected_base_url + '?iframe_fetch' in request_set,
-              'The fetch request from normal iframe should be handled by SW.');
-          assert_true(
-              expected_base_url + '?iframe_workerfetch' in request_set,
-              'The fetch request from worker in normal iframe should be ' +
-              'handled by SW.');
-          assert_true(
-              expected_base_url + '?iframe_iframe' in request_set,
-              'The request for normal iframe inside normal iframe should be ' +
-              'handled by SW.');
-          assert_false(
-              expected_base_url + '?iframe_script' in request_set,
-              'The request for sandboxed iframe with allow-scripts flag ' +
-              'inside normal iframe should not be handled by SW.');
-          assert_true(
-              expected_base_url + '?iframe_script-origin' in request_set,
-              'The request for sandboxed iframe with allow-scripts and ' +
-              'allow-same-origin flag inside normal iframe should be handled ' +
-              'by SW.');
-          assert_false(
-              expected_base_url + '?script' in request_set,
-              'The request for sandboxed iframe with allow-scripts flag ' +
-              'should not be handled by SW.');
-          assert_false(
-              expected_base_url + '?script_fetch' in request_set,
-              'The fetch request from sandboxed iframe with allow-scripts ' +
-              'flag should not be handled by SW.');
-          assert_false(
-              expected_base_url + '?script_workerfetch' in request_set,
-              'The fetch request from worker from sandboxed iframe with ' +
-              'allow-scripts flag should not be handled by SW.');
-          assert_false(
-              expected_base_url + '?script_iframe' in request_set,
-              'The request for normal iframe inside sandboxed iframe with ' +
-              'allow-scripts flag should not be handled by SW.');
-          assert_false(
-              expected_base_url + '?script_script' in request_set,
-              'The request for sandboxed iframe with allow-scripts flag ' +
-              'inside sandboxed iframe with allow-scripts flag should not be ' +
-              'handled by SW.');
-          assert_false(
-              expected_base_url + '?script_script-origin' in request_set,
-              'The request for sandboxed iframe with allow-scripts and ' +
-              'allow-same-origin flag inside sandboxed iframe with ' +
-              'allow-scripts flag should not be handled by SW.');
-          assert_true(
-              expected_base_url + '?script-origin' in request_set,
-              'The request for sandboxed iframe with allow-scripts and ' +
-              'allow-same-origin flag should be handled by SW.');
-          assert_true(
-              expected_base_url + '?script-origin_fetch' in request_set,
-              'The fetch request from sandboxed iframe with allow-scripts ' +
-              'and allow-same-origin flag should be handled by SW.');
-          assert_true(
-              expected_base_url + '?script-origin_workerfetch' in request_set,
-              'The fetch request from worker in sandboxed iframe with ' +
-              'allow-scripts and allow-same-origin flag should be handled ' +
-              'by SW.');
-          assert_true(
-              expected_base_url + '?script-origin_iframe' in request_set,
-              'The request for normal iframe inside sandboxed iframe with ' +
-              'allow-scripts and allow-same-origin flag should be handled by' +
-              'SW.');
-          assert_false(
-              expected_base_url + '?script-origin_script' in request_set,
-              'The request for sandboxed iframe with allow-scripts flag ' +
-              'inside sandboxed iframe with allow-scripts and ' +
-              'allow-same-origin flag should be handled by SW.');
-          assert_true(
-              expected_base_url + '?script-origin_script-origin' in request_set,
-              'The request for sandboxed iframe with allow-scripts and' +
-              'allow-same-origin flag inside sandboxed iframe with ' +
-              'allow-scripts and allow-same-origin flag should be handled by' +
-              'SW.');
+const SCOPE = 'resources/sandboxed-iframe-fetch-event-iframe.py';
+const SCRIPT = 'resources/sandboxed-iframe-fetch-event-worker.js';
+const expected_base_url = new URL(SCOPE, location.href);
+// Service worker controlling |SCOPE|.
+let worker;
+// A normal iframe.
+// This should be controlled by a service worker.
+let normal_frame;
+// An iframe created by <iframe sandbox='allow-scripts'>.
+// This should NOT be controlled by a service worker.
+let sandboxed_frame;
+// An iframe created by <iframe sandbox='allow-scripts allow-same-origin'>.
+// This should be controlled by a service worker.
+let sandboxed_same_origin_frame;
+// An iframe whose response header has
+// 'Content-Security-Policy: allow-scripts'.
+// This should NOT be controlled by a service worker.
+let sandboxed_frame_by_header;
+// An iframe whose response header has
+// 'Content-Security-Policy: allow-scripts allow-same-origin'.
+// This should be controlled by a service worker.
+let sandboxed_same_origin_frame_by_header;
 
-          var client_set = {};
-          for (var client of msg.data.clients) {
-            client_set[client] = true;
-          }
-          assert_true(
-              expected_base_url + '?iframe' in client_set,
-              'The normal iframe should be controlled by SW.');
-          assert_true(
-              expected_base_url + '?iframe_iframe' in client_set,
-              'The normal iframe inside normal iframe should be controlled ' +
-              'by SW.');
-          assert_false(
-              expected_base_url + '?iframe_script' in client_set,
-              'The sandboxed iframe with allow-scripts flag inside normal ' +
-              'iframe should not be controlled by SW.');
-          assert_true(
-              expected_base_url + '?iframe_script-origin' in client_set,
-              'The sandboxed iframe with allow-scripts and allow-same-origin' +
-              'flag inside normal iframe should be controlled by SW.');
-          assert_false(
-              expected_base_url + '?script' in client_set,
-              'The sandboxed iframe with allow-scripts flag should not be ' +
-              'controlled by SW.');
-          assert_false(
-              expected_base_url + '?script_iframe' in client_set,
-              'The normal iframe inside sandboxed iframe with allow-scripts' +
-              'flag should not be controlled by SW.');
-          assert_false(
-              expected_base_url + '?script_script' in client_set,
-              'The sandboxed iframe with allow-scripts flag inside sandboxed ' +
-              'iframe with allow-scripts flag should not be controlled by SW.');
-          assert_false(
-              expected_base_url + '?script_script-origin' in client_set,
-              'The sandboxed iframe with allow-scripts and allow-same-origin ' +
-              'flag inside sandboxed iframe with allow-scripts flag should ' +
-              'not be controlled by SW.');
-          assert_true(
-              expected_base_url + '?script-origin' in client_set,
-              'The sandboxed iframe with allow-scripts and allow-same-origin ' +
-              'flag should be controlled by SW.');
-          assert_true(
-              expected_base_url + '?script-origin_iframe' in client_set,
-              'The normal iframe inside sandboxed iframe with allow-scripts ' +
-              'and allow-same-origin flag should be controlled by SW.');
-          assert_false(
-              expected_base_url + '?script-origin_script' in client_set,
-              'The sandboxed iframe with allow-scripts flag inside sandboxed ' +
-              'iframe with allow-scripts and allow-same-origin flag should ' +
-              'be controlled by SW.');
-          assert_true(
-              expected_base_url + '?script-origin_script-origin' in client_set,
-              'The sandboxed iframe with allow-scripts and allow-same-origin ' +
-              'flag inside sandboxed iframe with allow-scripts and ' +
-              'allow-same-origin flag should be controlled by SW.');
-          return service_worker_unregister_and_done(t, SCOPE);
-        });
-  }, 'ServiceWorker FetchEvent for sandboxed iframe.');
+promise_test(t => {
+  return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+    .then(function(registration) {
+      add_completion_callback(() => registration.unregister());
+      worker = registration.installing;
+      return wait_for_state(t, registration.installing, 'activated');
+    });
+}, 'Prepare a service worker.');
+
+promise_test(t => {
+  return with_iframe(SCOPE + '?iframe')
+    .then(f => {
+      normal_frame = f;
+      add_completion_callback(() => f.remove());
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1);
+      assert_equals(requests[0], expected_base_url + '?iframe');
+      assert_true(data.clients.includes(expected_base_url + '?iframe'));
+    });
+}, 'Prepare a normal iframe.');
+
+promise_test(t => {
+  return with_sandboxed_iframe(SCOPE + '?sandboxed-iframe', 'allow-scripts')
+    .then(f => {
+      sandboxed_frame = f;
+      add_completion_callback(() => f.remove());
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0);
+      assert_false(data.clients.includes(expected_base_url +
+                                         '?sandboxed-iframe'));
+    });
+}, 'Prepare an iframe sandboxed by <iframe sandbox="allow-scripts">.');
+
+promise_test(t => {
+  return with_sandboxed_iframe(SCOPE + '?sandboxed-iframe-same-origin',
+                               'allow-scripts allow-same-origin')
+    .then(f => {
+      sandboxed_same_origin_frame = f;
+      add_completion_callback(() => f.remove());
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1);
+      assert_equals(requests[0],
+                    expected_base_url + '?sandboxed-iframe-same-origin');
+      assert_true(data.clients.includes(
+        expected_base_url + '?sandboxed-iframe-same-origin'));
+    })
+}, 'Prepare an iframe sandboxed by ' +
+   '<iframe sandbox="allow-scripts allow-same-origin">.');
+
+promise_test(t => {
+  const iframe_full_url = expected_base_url + '?sandbox=allow-scripts&' +
+                          'sandboxed-frame-by-header';
+  return with_iframe(iframe_full_url)
+    .then(f => {
+      sandboxed_frame_by_header = f;
+      add_completion_callback(() => f.remove());
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'Service worker should provide the response');
+      assert_equals(requests[0], iframe_full_url);
+      assert_false(data.clients.includes(iframe_full_url),
+                   'Service worker should NOT control the sandboxed page');
+    });
+}, 'Prepare an iframe sandboxed by CSP HTTP header with allow-scripts.');
+
+promise_test(t => {
+  const iframe_full_url =
+    expected_base_url + '?sandbox=allow-scripts%20allow-same-origin&' +
+    'sandboxed-iframe-same-origin-by-header';
+  return with_iframe(iframe_full_url)
+    .then(f => {
+      sandboxed_same_origin_frame_by_header = f;
+      add_completion_callback(() => f.remove());
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1);
+      assert_equals(requests[0], iframe_full_url);
+      assert_true(data.clients.includes(iframe_full_url));
+    })
+}, 'Prepare an iframe sandboxed by CSP HTTP header with allow-scripts and ' +
+   'allow-same-origin.');
+
+promise_test(t => {
+  let frame = normal_frame;
+  return doTest(frame, 'fetch')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The fetch request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=fetch');
+    });
+}, 'Fetch request from a normal iframe');
+
+promise_test(t => {
+  let frame = normal_frame;
+  return doTest(frame, 'fetch-from-worker')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The fetch request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=fetch-from-worker');
+    });
+}, 'Fetch request from a worker in a normal iframe');
+
+promise_test(t => {
+  let frame = normal_frame;
+  return doTest(frame, 'iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=iframe');
+      assert_true(data.clients.includes(frame.src + '&test=iframe'));
+
+    });
+}, 'Request for an iframe in the normal iframe');
+
+promise_test(t => {
+  let frame = normal_frame;
+  return doTest(frame, 'sandboxed-iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts flag in the normal ' +
+   'iframe');
+
+promise_test(t => {
+  let frame = normal_frame;
+  return doTest(frame, 'sandboxed-iframe-same-origin')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0],
+                    frame.src + '&test=sandboxed-iframe-same-origin');
+      assert_true(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe-same-origin'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts and ' +
+   'allow-same-origin flag in the normal iframe');
+
+promise_test(t => {
+  let frame = sandboxed_frame;
+  return doTest(frame, 'fetch')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The fetch request should NOT be handled by SW.');
+    });
+}, 'Fetch request from iframe sandboxed by an attribute with allow-scripts ' +
+   'flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame;
+  return doTest(frame, 'fetch-from-worker')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The fetch request should NOT be handled by SW.');
+    });
+}, 'Fetch request from a worker in iframe sandboxed by an attribute with ' +
+   'allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame;
+  return doTest(frame, 'iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(frame.src + '&test=iframe'));
+    });
+}, 'Request for an iframe in the iframe sandboxed by an attribute with ' +
+   'allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame;
+  return doTest(frame, 'sandboxed-iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts flag in the iframe ' +
+   'sandboxed by an attribute with allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame;
+  return doTest(frame, 'sandboxed-iframe-same-origin')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe-same-origin'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts and ' +
+   'allow-same-origin flag in the iframe sandboxed by an attribute with ' +
+   'allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame;
+  return doTest(frame, 'fetch')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The fetch request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=fetch');
+    });
+}, 'Fetch request from iframe sandboxed by an attribute with allow-scripts ' +
+   'and allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame;
+  return doTest(frame, 'fetch-from-worker')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The fetch request should be handled by SW.');
+      assert_equals(requests[0],
+                    frame.src + '&test=fetch-from-worker');
+    });
+}, 'Fetch request from a worker in iframe sandboxed by an attribute with ' +
+   'allow-scripts and allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame;
+  return doTest(frame, 'iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=iframe');
+      assert_true(data.clients.includes(frame.src + '&test=iframe'));
+    });
+}, 'Request for an iframe in the iframe sandboxed by an attribute with ' +
+   'allow-scripts and allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame;
+  return doTest(frame, 'sandboxed-iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts flag in the iframe ' +
+   'sandboxed by attribute with allow-scripts and allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame;
+  return doTest(frame, 'sandboxed-iframe-same-origin')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0],
+                    frame.src + '&test=sandboxed-iframe-same-origin');
+      assert_true(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe-same-origin'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts and ' +
+   'allow-same-origin flag in the iframe sandboxed by attribute with ' +
+   'allow-scripts and allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame_by_header;
+  return doTest(frame, 'fetch')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+    });
+}, 'Fetch request from iframe sandboxed by CSP HTTP header with ' +
+   'allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame_by_header;
+  return doTest(frame, 'iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(frame.src + '&test=iframe'));
+    });
+}, 'Request for an iframe in the iframe sandboxed by CSP HTTP header with ' +
+   'allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame_by_header;
+  return doTest(frame, 'sandboxed-iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts flag in the iframe ' +
+   'sandboxed by CSP HTTP header with allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_frame_by_header;
+  return doTest(frame, 'sandboxed-iframe-same-origin')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe-same-origin'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts and ' +
+   'allow-same-origin flag in the iframe sandboxed by CSP HTTP header with ' +
+   'allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame_by_header;
+  return doTest(frame, 'fetch')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=fetch');
+    });
+}, 'Fetch request from iframe sandboxed by CSP HTTP header with ' +
+   'allow-scripts and allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame_by_header;
+  return doTest(frame, 'iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=iframe');
+      assert_true(data.clients.includes(frame.src + '&test=iframe'));
+    });
+}, 'Request for an iframe in the iframe sandboxed by CSP HTTP header with ' +
+   'allow-scripts and allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame_by_header;
+  return doTest(frame, 'sandboxed-iframe')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+      assert_false(
+        data.clients.includes(frame.src + '&test=sandboxed-iframe'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts flag in the ' +
+   'iframe sandboxed by CSP HTTP header with allow-scripts and ' +
+   'allow-same-origin flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame_by_header;
+  return doTest(frame, 'sandboxed-iframe-same-origin')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0],
+                    frame.src + '&test=sandboxed-iframe-same-origin');
+      assert_true(data.clients.includes(
+        frame.src + '&test=sandboxed-iframe-same-origin'));
+    });
+}, 'Request for an sandboxed iframe with allow-scripts and ' +
+   'allow-same-origin flag in the iframe sandboxed by CSP HTTP header with ' +
+   'allow-scripts and allow-same-origin flag');
 </script>
 </body>
diff --git a/service-workers/service-worker/svg-target-reftest.https.html b/service-workers/service-worker/svg-target-reftest.https.html
new file mode 100644
index 0000000..3710ee6
--- /dev/null
+++ b/service-workers/service-worker/svg-target-reftest.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Service worker interception does not break SVG fragment targets</title>
+<meta name="assert" content="SVG with link fragment should render correctly when intercepted by a service worker.">
+<script src="resources/test-helpers.sub.js"></script>
+<link rel="match" href="resources/svg-target-reftest-001.html">
+<p>Pass if you see a green box below.</p>
+<script>
+// We want to use utility functions designed for testharness.js where
+// there is a test object.  We don't have a test object in reftests
+// so fake one for now.
+const fake_test = { step_func: f => f };
+
+async function runTest() {
+  const script = './resources/pass-through-worker.js';
+  const scope = './resources/svg-target-reftest-frame.html';
+  let reg = await navigator.serviceWorker.register(script, { scope });
+  await wait_for_state(fake_test, reg.installing, 'activated');
+  let f = await with_iframe(scope);
+  document.documentElement.classList.remove('reftest-wait');
+  await reg.unregister();
+  // Note, we cannot remove the frame explicitly because we can't
+  // tell when the reftest completes.
+}
+runTest();
+</script>
+</html>
diff --git a/service-workers/service-worker/unregister-then-register-new-script.https.html b/service-workers/service-worker/unregister-then-register-new-script.https.html
index 385430c..582132a 100644
--- a/service-workers/service-worker/unregister-then-register-new-script.https.html
+++ b/service-workers/service-worker/unregister-then-register-new-script.https.html
@@ -90,9 +90,12 @@
           return registration.unregister();
         })
       .then(function() {
+          // Step 5.1 of Register clears the uninstall flag before fetching
+          // the script:
+          //
+          //  https://w3c.github.io/ServiceWorker/#register-algorithm
           var promise = navigator.serviceWorker.register('this-will-404',
                                                          { scope: scope });
-          iframe.remove();
           return promise;
         })
       .then(
@@ -100,17 +103,28 @@
           assert_unreached('register should reject the promise');
         },
         function() {
+          assert_equals(registration.installing, null,
+                        'registration.installing');
+          assert_equals(registration.waiting, null,
+                        'registration.waiting');
+          assert_equals(registration.active.scriptURL, normalizeURL(worker_url),
+                        'registration.active');
+          iframe.remove();
           return with_iframe(scope);
         })
       .then(function(frame) {
-          assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
-                        null,
-                        'document should not load with a controller');
+          assert_equals(
+              frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+              normalizeURL(worker_url),
+              'the original worker should control a new document');
           frame.remove();
+          return registration.unregister();
+        })
+      .then(function() {
           t.done();
         })
       .catch(unreached_rejection(t));
-}, 'Registering a new script URL that 404s does not resurrect an ' +
+}, 'Registering a new script URL that 404s does resurrect an ' +
        'unregistered registration');
 
 async_test(function(t) {
@@ -131,9 +145,12 @@
           return registration.unregister();
         })
       .then(function() {
+          // Step 5.1 of Register clears the uninstall flag before firing
+          // the install event:
+          //
+          //  https://w3c.github.io/ServiceWorker/#register-algorithm
           var promise = navigator.serviceWorker.register(
               'resources/reject-install-worker.js', { scope: scope });
-          iframe.remove();
           return promise;
         })
       .then(function(r) {
@@ -141,12 +158,20 @@
           return wait_for_state(t, r.installing, 'redundant');
         })
       .then(function() {
+          assert_equals(registration.installing, null,
+                        'registration.installing');
+          assert_equals(registration.waiting, null,
+                        'registration.waiting');
+          assert_equals(registration.active.scriptURL, normalizeURL(worker_url),
+                        'registration.active');
+          iframe.remove();
           return with_iframe(scope);
         })
       .then(function(frame) {
-          assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
-                        null,
-                        'document should not load with a controller');
+          assert_equals(
+              frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
+              normalizeURL(worker_url),
+              'the original worker should control a new document');
           frame.remove();
           return registration.unregister();
         })
@@ -154,6 +179,6 @@
           t.done();
         })
       .catch(unreached_rejection(t));
-  }, 'Registering a new script URL that fails to install does not resurrect ' +
+  }, 'Registering a new script URL that fails to install does resurrect ' +
        'an unregistered registration');
 </script>
diff --git a/service-workers/service-worker/update-bytecheck.https.html b/service-workers/service-worker/update-bytecheck.https.html
index 6e4c6ec..ec3d15a 100644
--- a/service-workers/service-worker/update-bytecheck.https.html
+++ b/service-workers/service-worker/update-bytecheck.https.html
@@ -5,6 +5,7 @@
 <script src="resources/testharness-helpers.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
 <script>
 
 /*
@@ -26,11 +27,12 @@
                   {cors: true,  main: 'time',    imported: 'default'},
                   {cors: true,  main: 'time',    imported: 'time'   }];
 
+const host_info = get_host_info();
 settings.reduce((p, s) => {
   return p.then(promise_test(function(t) {
     var path = !s.cors ? ''
-                       : 'https://www1.web-platform.test:8443/' +
-                         'service-workers/service-worker/resources/';
+                       : host_info.HTTPS_REMOTE_ORIGIN +
+                         '/service-workers/service-worker/resources/';
     var script = 'resources/bytecheck-worker.py' +
                  '?main=' + s.main +
                  '&imported=' + s.imported +
diff --git a/service-workers/service-worker/update-result.https.html b/service-workers/service-worker/update-result.https.html
new file mode 100644
index 0000000..d8ed94f
--- /dev/null
+++ b/service-workers/service-worker/update-result.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Service Worker: update() should resolve a ServiceWorkerRegistration</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+promise_test(async function(t) {
+  const script = './resources/empty.js';
+  const scope = './resources/empty.html?update-result';
+
+  let reg = await navigator.serviceWorker.register(script, { scope });
+  t.add_cleanup(async _ => await reg.unregister());
+  await wait_for_state(t, reg.installing, 'activated');
+
+  let result = await reg.update();
+  assert_true(result instanceof ServiceWorkerRegistration,
+              'update() should resolve a ServiceWorkerRegistration');
+}, 'ServiceWorkerRegistration.update() should resolve a registration object');
+
+</script>
+</body>
diff --git a/service-workers/service-worker/webvtt-cross-origin.https.html b/service-workers/service-worker/webvtt-cross-origin.https.html
new file mode 100644
index 0000000..637f494
--- /dev/null
+++ b/service-workers/service-worker/webvtt-cross-origin.https.html
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>cross-origin webvtt returned by service worker is detected</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body>
+<script>
+// This file tests opaque responses for WebVTT text track. It creates
+// an iframe with a <track> element, controlled by a service worker.
+// Each test tries to load a text track, the service worker intercepts the
+// requests and responds with opaque or non-opaque responses. The opaque
+// responses should result in load errors.
+
+const host_info = get_host_info();
+const kScript = 'resources/fetch-rewrite-worker.js';
+// Add '?ignore' so the service worker falls back for the navigation.
+const kScope = 'resources/vtt-frame.html?ignore';
+let frame;
+
+function load_track(url) {
+  const track = frame.contentDocument.querySelector('track');
+  const result = new Promise((resolve, reject) => {
+      track.onload = (e => {
+          resolve('load event');
+        });
+      track.onerror = (e => {
+          resolve('error event');
+        });
+    });
+
+  track.src = url;
+  // Setting mode to hidden seems needed, or else the text track requests don't
+  // occur.
+  track.track.mode = 'hidden';
+  return result;
+}
+
+promise_test(t => {
+    return service_worker_unregister_and_register(t, kScript, kScope)
+      .then(registration => {
+          promise_test(() => {
+              frame.remove();
+              return registration.unregister();
+            }, 'restore global state');
+
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(() => {
+          return with_iframe(kScope);
+        })
+      .then(f => {
+          frame = f;
+        })
+  }, 'initialize global state');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a same-origin URL.
+    url += '?url=' + host_info.HTTPS_ORIGIN + '/media/foo.vtt';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'load event');
+        });
+  }, 'same-origin text track should load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a cross-origin URL.
+    url += '?url=' + get_host_info().HTTPS_REMOTE_ORIGIN + '/media/foo.vtt';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'error event');
+        });
+  }, 'cross-origin text track with no-cors request should not load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a cross-origin URL that
+    // doesn't support CORS.
+    url += '?url=' + get_host_info().HTTPS_REMOTE_ORIGIN +
+        '/media/foo-no-cors.vtt';
+    // Add '&mode' to tell the service worker to do a CORS request.
+    url += '&mode=cors';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'error event');
+        });
+  }, 'cross-origin text track with rejected cors request should not load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a cross-origin URL.
+    url += '?url=' + get_host_info().HTTPS_REMOTE_ORIGIN + '/media/foo.vtt';
+    // Add '&mode' to tell the service worker to do a CORS request.
+    url += '&mode=cors';
+    // Add '&credentials=same-origin' to allow Access-Control-Allow-Origin=* so
+    // that CORS will succeed if the service approves it.
+    url += '&credentials=same-origin';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'load event');
+        });
+  }, 'cross-origin text track with approved cors request should load');
+
+// Redirect tests.
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a same-origin URL that redirects...
+    redirector_url = host_info.HTTPS_ORIGIN + base_path() + 'resources/redirect.py?Redirect=';
+    // ... to a same-origin URL.
+    redirect_target = host_info.HTTPS_ORIGIN + '/media/foo.vtt';
+    url += '?url=' + encodeURIComponent(redirector_url + encodeURIComponent(redirect_target));
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'load event');
+        });
+  }, 'same-origin text track that redirects same-origin should load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a same-origin URL that redirects...
+    redirector_url = host_info.HTTPS_ORIGIN + base_path() + 'resources/redirect.py?Redirect=';
+    // ... to a cross-origin URL.
+    redirect_target = host_info.HTTPS_REMOTE_ORIGIN + '/media/foo.vtt';
+    url += '?url=' + encodeURIComponent(redirector_url + encodeURIComponent(redirect_target));
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'error event');
+        });
+  }, 'same-origin text track that redirects cross-origin should not load');
+
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a same-origin URL that redirects...
+    redirector_url = host_info.HTTPS_ORIGIN + base_path() + 'resources/redirect.py?Redirect=';
+    // ... to a cross-origin URL.
+    redirect_target = host_info.HTTPS_REMOTE_ORIGIN + '/media/foo-no-cors.vtt';
+    url += '?url=' + encodeURIComponent(redirector_url + encodeURIComponent(redirect_target));
+    // Add '&mode' to tell the service worker to do a CORS request.
+    url += '&mode=cors';
+    // Add '&credentials=same-origin' to allow Access-Control-Allow-Origin=* so
+    // that CORS will succeed if the server approves it.
+    url += '&credentials=same-origin';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'error event');
+        });
+  }, 'same-origin text track that redirects to a cross-origin text track with rejected cors should not load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to fetch a same-origin URL that redirects...
+    redirector_url = host_info.HTTPS_ORIGIN + base_path() + 'resources/redirect.py?Redirect=';
+    // ... to a cross-origin URL.
+    redirect_target = host_info.HTTPS_REMOTE_ORIGIN + '/media/foo.vtt';
+    url += '?url=' + encodeURIComponent(redirector_url + encodeURIComponent(redirect_target));
+    // Add '&mode' to tell the service worker to do a CORS request.
+    url += '&mode=cors';
+    // Add '&credentials=same-origin' to allow Access-Control-Allow-Origin=* so
+    // that CORS will succeed if the server approves it.
+    url += '&credentials=same-origin';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'load event');
+        });
+  }, 'same-origin text track that redirects to a cross-origin text track with approved cors should load');
+</script>
+</body>
diff --git a/service-workers/service-worker/worker-client-id.https.html b/service-workers/service-worker/worker-client-id.https.html
new file mode 100644
index 0000000..4e4d316
--- /dev/null
+++ b/service-workers/service-worker/worker-client-id.https.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<title>Service Worker: Workers should have their own unique client Id</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+// Get the iframe client ID by calling postMessage() on its controlling
+// worker.  This will cause the service worker to post back the
+// MessageEvent.source.id value.
+function getFrameClientId(frame) {
+  return new Promise(resolve => {
+    let mc = new MessageChannel();
+    frame.contentWindow.navigator.serviceWorker.controller.postMessage(
+      'echo-client-id', [mc.port2]);
+    mc.port1.onmessage = evt => {
+      resolve(evt.data);
+    };
+  });
+}
+
+// Get the worker client ID by creating a worker that performs an intercepted
+// fetch().  The synthetic fetch() response will contain the FetchEvent.clientId
+// value.  This is then posted back to here.
+function getWorkerClientId(frame) {
+  return new Promise(resolve => {
+    let w = new frame.contentWindow.Worker('worker-echo-client-id.js');
+    w.onmessage = evt => {
+      resolve(evt.data);
+    };
+  });
+}
+
+promise_test(async function(t) {
+  const script = './resources/worker-client-id-worker.js';
+  const scope = './resources/worker-client-id';
+  const frame = scope + '/frame.html';
+
+  let reg = await navigator.serviceWorker.register(script, { scope });
+  t.add_cleanup(async _ => await reg.unregister());
+  await wait_for_state(t, reg.installing, 'activated');
+
+  let f = await with_iframe(frame);
+  t.add_cleanup(_ => f.remove());
+
+  let frameClientId = await getFrameClientId(f);
+  assert_not_equals(frameClientId, null, 'frame client id should exist');
+
+  let workerClientId = await getWorkerClientId(f);
+  assert_not_equals(workerClientId, null, 'worker client id should exist');
+
+  assert_not_equals(frameClientId, workerClientId,
+                    'frame and worker client ids should be different');
+}, 'Verify workers have a unique client id separate from their owning documents window');
+
+</script>
+</body>
diff --git a/service-workers/service-worker/worker-in-sandboxed-iframe-by-csp-fetch-event.https.html b/service-workers/service-worker/worker-in-sandboxed-iframe-by-csp-fetch-event.https.html
new file mode 100644
index 0000000..c8480bf
--- /dev/null
+++ b/service-workers/service-worker/worker-in-sandboxed-iframe-by-csp-fetch-event.https.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<title>ServiceWorker FetchEvent issued from workers in an iframe sandboxed via CSP HTTP response header.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+let lastCallbackId = 0;
+let callbacks = {};
+function doTest(frame, type) {
+  return new Promise(function(resolve) {
+    var id = ++lastCallbackId;
+    callbacks[id] = resolve;
+    frame.contentWindow.postMessage({id: id, type: type}, '*');
+  });
+}
+
+// Asks the service worker for data about requests and clients seen. The
+// worker posts a message back with |data| where:
+// |data.requests|: the requests the worker received FetchEvents for
+// |data.clients|: the URLs of all the worker's clients
+// The worker clears its data after responding.
+function getResultsFromWorker(worker) {
+  return new Promise(resolve => {
+    let channel = new MessageChannel();
+    channel.port1.onmessage = msg => {
+      resolve(msg.data);
+    };
+    worker.postMessage({port: channel.port2}, [channel.port2]);
+  });
+}
+
+window.onmessage = function (e) {
+  message = e.data;
+  let id = message['id'];
+  let callback = callbacks[id];
+  delete callbacks[id];
+  callback(message['result']);
+};
+
+const SCOPE = 'resources/sandboxed-iframe-fetch-event-iframe.py';
+const SCRIPT = 'resources/sandboxed-iframe-fetch-event-worker.js';
+const expected_base_url = new URL(SCOPE, location.href);
+// A service worker controlling |SCOPE|.
+let worker;
+// An iframe whose response header has
+// 'Content-Security-Policy: allow-scripts'.
+// This should NOT be controlled by a service worker.
+let sandboxed_frame_by_header;
+// An iframe whose response header has
+// 'Content-Security-Policy: allow-scripts allow-same-origin'.
+// This should be controlled by a service worker.
+let sandboxed_same_origin_frame_by_header;
+
+promise_test(t => {
+  return service_worker_unregister_and_register(t, SCRIPT, SCOPE)
+    .then(function(registration) {
+      add_completion_callback(() => registration.unregister());
+      worker = registration.installing;
+      return wait_for_state(t, registration.installing, 'activated');
+    });
+}, 'Prepare a service worker.');
+
+promise_test(t => {
+  const iframe_full_url = expected_base_url + '?sandbox=allow-scripts&' +
+                          'sandboxed-frame-by-header';
+  return with_iframe(iframe_full_url)
+    .then(f => {
+      sandboxed_frame_by_header = f;
+      add_completion_callback(() => f.remove());
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'Service worker should provide the response');
+      assert_equals(requests[0], iframe_full_url);
+      assert_false(data.clients.includes(iframe_full_url),
+                   'Service worker should NOT control the sandboxed page');
+    });
+}, 'Prepare an iframe sandboxed by CSP HTTP header with allow-scripts.');
+
+promise_test(t => {
+  const iframe_full_url =
+    expected_base_url + '?sandbox=allow-scripts%20allow-same-origin&' +
+    'sandboxed-iframe-same-origin-by-header';
+  return with_iframe(iframe_full_url)
+    .then(f => {
+      sandboxed_same_origin_frame_by_header = f;
+      add_completion_callback(() => f.remove());
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1);
+      assert_equals(requests[0], iframe_full_url);
+      assert_true(data.clients.includes(iframe_full_url));
+    })
+}, 'Prepare an iframe sandboxed by CSP HTTP header with allow-scripts and ' +
+   'allow-same-origin.');
+
+promise_test(t => {
+  let frame = sandboxed_frame_by_header;
+  return doTest(frame, 'fetch-from-worker')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      assert_equals(data.requests.length, 0,
+                    'The request should NOT be handled by SW.');
+    });
+}, 'Fetch request from a worker in iframe sandboxed by CSP HTTP header ' +
+   'allow-scripts flag');
+
+promise_test(t => {
+  let frame = sandboxed_same_origin_frame_by_header;
+  return doTest(frame, 'fetch-from-worker')
+    .then(result => {
+      assert_equals(result, 'done');
+      return getResultsFromWorker(worker);
+    })
+    .then(data => {
+      let requests = data.requests;
+      assert_equals(requests.length, 1,
+                    'The request should be handled by SW.');
+      assert_equals(requests[0], frame.src + '&test=fetch-from-worker');
+    });
+}, 'Fetch request from a worker in iframe sandboxed by CSP HTTP header ' +
+   'with allow-scripts and allow-same-origin flag');
+</script>
+</body>
diff --git a/service-workers/service-worker/worker-interception-redirect.https.html b/service-workers/service-worker/worker-interception-redirect.https.html
new file mode 100644
index 0000000..654495f
--- /dev/null
+++ b/service-workers/service-worker/worker-interception-redirect.https.html
@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+<title>Service Worker: controlling a SharedWorker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+// This tests service worker interception for worker clients, when the request
+// for the worker script goes through redirects. For example, a request can go
+// through a chain of URLs like A -> B -> C -> D and each URL might fall in the
+// scope of a different service worker, if any.
+// The two key questions are:
+// 1. Upon a redirect from A -> B, should a service worker for scope B
+//    intercept the request?
+// 2. After the final response, which service worker controls the resulting
+//    client?
+//
+// The standard prescribes the following:
+// 1. The service worker for scope B intercepts the redirect. *However*, once a
+//    request falls back to network (i.e., a service worker did not call
+//    respondWith()) and a redirect is then received from network, no service
+//    worker should intercept that redirect or any subsequent redirects.
+// 2. The final service worker that got a fetch event (or would have, in the
+//    case of a non-fetch-event worker) becomes the controller of the client.
+//
+// The standard may change later, see:
+// https://github.com/w3c/ServiceWorker/issues/1289
+//
+// The basic test setup is:
+// 1. Page registers service workers for scope1 and scope2.
+// 2. Page requests a worker from scope1.
+// 3. The request is redirected to scope2 or out-of-scope.
+// 4. The worker posts message to the page describing where the final response
+//   was served from (service worker or network).
+// 5. The worker does an importScripts() and fetch(), and posts back the
+//   responses, which describe where the responses where served from.
+//
+// Currently this only tests shared worker but dedicated worker tests should be
+// added in a future patch.
+
+// Globals for easier cleanup.
+const scope1 = 'resources/scope1';
+const scope2 = 'resources/scope2';
+let frame;
+
+function get_message_from_worker(worker) {
+  return new Promise(resolve => {
+      worker.port.onmessage = evt => {
+        resolve(evt.data);
+      }
+    });
+}
+
+async function cleanup() {
+  if (frame)
+    frame.remove();
+
+  const reg1 = await navigator.serviceWorker.getRegistration(scope1);
+  if (reg1)
+    await reg1.unregister();
+  const reg2 = await navigator.serviceWorker.getRegistration(scope2);
+  if (reg2)
+    await reg2.unregister();
+}
+
+// Builds the worker script URL, which encodes information about where
+// to redirect to. The URL falls in sw1's scope.
+//
+// - |redirector| is "network" or "serviceworker". If "serviceworker", sw1 will
+// respondWith() a redirect. Otherwise, it falls back to network and the server
+// responds with a redirect.
+// - |redirect_location| is "scope2" or "out-of-scope". If "scope2", the
+// redirect ends up in sw2's scope2. Otherwise it's out of scope.
+function build_worker_url(redirector, redirect_location) {
+  let redirect_path;
+  // Set path to redirect.py, a file on the server that serves
+  // a redirect. When sw1 sees this URL, it falls back to network.
+  if (redirector == 'network')
+    redirector_path = 'redirect.py';
+  // Set path to 'sw-redirect', to tell the service worker
+  // to respond with redirect.
+  else if (redirector == 'serviceworker')
+    redirector_path = 'sw-redirect';
+
+  let redirect_to = base_path() + 'resources/';
+  // Append "scope2/" to redirect_to, so the redirect falls in scope2.
+  // Otherwise no change is needed, as the parent "resources/" directory is
+  // used, and is out-of-scope.
+  if (redirect_location == 'scope2')
+    redirect_to += 'scope2/';
+  // Append the name of the file which serves the worker script.
+  redirect_to += 'worker_interception_redirect_webworker.py';
+
+  return `scope1/${redirector_path}?Redirect=${redirect_to}`
+}
+
+promise_test(async t => {
+  await cleanup();
+  const service_worker = 'resources/worker-interception-redirect-serviceworker.js';
+  const registration1 = await navigator.serviceWorker.register(service_worker, {scope: scope1});
+  await wait_for_state(t, registration1.installing, 'activated');
+  const registration2 = await navigator.serviceWorker.register(service_worker, {scope: scope2});
+  await wait_for_state(t, registration2.installing, 'activated');
+
+  promise_test(t => {
+    return cleanup();
+  }, 'cleanup global state');
+}, 'initialize global state');
+
+function worker_redirect_test(worker_url,
+                              expected_main_resource_message,
+                              expected_import_scripts_message,
+                              expected_fetch_message,
+                              description) {
+  promise_test(async t => {
+    // Create a frame to load the worker from. This way we can remove the frame
+    // to destroy the worker client when the test is done.
+    frame = await with_iframe('resources/blank.html');
+    t.add_cleanup(() => { frame.remove(); });
+
+    // Start the worker.
+    const w = new frame.contentWindow.SharedWorker(worker_url);
+    w.port.start();
+
+    // Expect a message from the worker indicating which service worker
+    // provided the response for the worker script request, if any.
+    const data = await get_message_from_worker(w);
+    assert_equals(data, expected_main_resource_message);
+
+    // The worker does an importScripts(). Expect a message from the worker
+    // indicating which service worker provided the response for the
+    // importScripts(), if any.
+    const import_scripts_message = await get_message_from_worker(w);
+    assert_equals(import_scripts_message, expected_import_scripts_message);
+
+    // The worker does a fetch(). Expect a message from the worker indicating
+    // which service worker provided the response for the fetch(), if any.
+    const fetch_message = await get_message_from_worker(w);
+    assert_equals(fetch_message, expected_fetch_message);
+  }, description);
+}
+
+worker_redirect_test(
+    build_worker_url('network', 'scope2'),
+    'the shared worker script was served from network',
+    'sw1 saw importScripts from the worker',
+    'fetch(): sw1 saw the fetch from the worker',
+    'request to sw1 scope gets network redirect to sw2 scope');
+
+worker_redirect_test(
+    build_worker_url('network', 'out-scope'),
+    'the shared worker script was served from network',
+    'sw1 saw importScripts from the worker',
+    'fetch(): sw1 saw the fetch from the worker',
+    'request to sw1 scope gets network redirect to out-of-scope');
+
+worker_redirect_test(
+    build_worker_url('serviceworker', 'scope2'),
+    'sw2 saw the request for the worker script',
+    'sw2 saw importScripts from the worker',
+    'fetch(): sw2 saw the fetch from the worker',
+    'request to sw1 scope gets service-worker redirect to sw2 scope');
+
+worker_redirect_test(
+    build_worker_url('serviceworker', 'out-scope'),
+    'the shared worker script was served from network',
+    'sw1 saw importScripts from the worker',
+    'fetch(): sw1 saw the fetch from the worker',
+    'request to sw1 scope gets service-worker redirect to out-of-scope');
+</script>
+</body>
diff --git a/service-workers/service-worker/worker-interception.https.html b/service-workers/service-worker/worker-interception.https.html
index 3ec66a5..4f14746 100644
--- a/service-workers/service-worker/worker-interception.https.html
+++ b/service-workers/service-worker/worker-interception.https.html
@@ -81,10 +81,16 @@
             });
         })
       .then(function(data) {
-          assert_equals(data, 'dummy-worker-script loaded');
+          assert_unreached('intercepted cors response to a same-origin mode ' +
+                           'worker load should fail');
           service_worker_unregister_and_done(t, scope);
-        });
-  }, 'Verify worker script intercepted by cors response succeeds');
+        })
+      .catch(function(e) {
+          assert_true(true, 'intercepted cors response to a same-origin mode ' +
+                            'worker load should fail');
+          service_worker_unregister_and_done(t, scope);
+       });
+  }, 'Verify worker script intercepted by cors response fails');
 
 promise_test(function(t) {
     var worker_url = 'resources/dummy-no-cors-worker.js';
diff --git a/service-workers/tools/blink-import.py b/service-workers/tools/blink-import.py
index 355df07..552fdc0 100644
--- a/service-workers/tools/blink-import.py
+++ b/service-workers/tools/blink-import.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 import os
 import re
 import shutil
@@ -184,7 +186,7 @@
 
     for k, v in path_changes.iteritems():
         if os.path.basename(k) in filename_changes:
-            print "Got duplicate name:" + os.path.basename(k)
+            print("Got duplicate name:" + os.path.basename(k))
         filename_changes[os.path.basename(k)] = os.path.basename(v)
 
     for path in source_paths(work_path):
diff --git a/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html b/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html
new file mode 100644
index 0000000..8cefa41
--- /dev/null
+++ b/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html
@@ -0,0 +1,236 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Shadow DOM and CSSOM View: Document.prototype.elementFromPoint</title>
+    <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+    <meta name="assert" content="DocumentOrShadowRoot must have elementFromPoint and must return retarget the result against the context object.">
+    <link rel="help" href="https://www.w3.org/TR/cssom-view-1/#dom-document-elementfrompoint">
+    <link rel="help" href="https://www.w3.org/TR/shadow-dom/#extensions-to-the-documentorshadowroot-mixin">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <div id="container"></div>
+    <style>
+test-element { display: block; width: 100px; height: 100px; }
+    </style>
+    <script>
+
+function pointInElement(node) {
+  let x = 5;
+  let y = 5;
+  do {
+    x += node.offsetLeft;
+    y += node.offsetTop;
+    node = node.offsetParent;
+  } while (node);
+  return [x, y];
+}
+
+const displayValues = ['inline', 'block', 'inline-block'];
+var container = document.getElementById('container');
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = 'hello';
+    container.appendChild(host);
+    assert_equals(document.elementFromPoint(...pointInElement(host)), host);
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), host);
+  }, 'document.elementFromPoint and shadow.ElementFromPoint must return the shadow host of the hit-tested text node when the hit-tested text node is a direct child of the root and the host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<slot></slot>';
+    host.innerHTML = 'text';
+    container.appendChild(host);
+    assert_equals(document.elementFromPoint(...pointInElement(host)), host);
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), host);
+  }, 'document.elementFromPoint and shadowRoot.elementFromPoint must return the shadow host when the hit-tested text node is assigned to a slot and the host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<slot></slot>';
+    host.innerHTML = '<span>text</span>';
+    container.appendChild(host);
+    assert_equals(document.elementFromPoint(...pointInElement(host)), host.querySelector('span'));
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), host.querySelector('span'));
+  }, 'document.elementFromPoint and shadowRoot.elementFromPoint must return the element assigned to a slot when hit-tested text node under an element is assigned to a slot in the shadow tree and the shadow host of the slot has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<span>text</span>';
+    container.appendChild(host);
+    assert_equals(document.elementFromPoint(...pointInElement(host)), host);
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), shadow.querySelector('span'));
+  }, 'document.elementFromPoint must return the shadow host of the hit-tested element under a shadow root and shadowRoot.elementFromPoint must return the element parent of the hit-tested text node under the point when the shadow host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<slot>fallback</slot>';
+    container.appendChild(host);
+    assert_equals(document.elementFromPoint(...pointInElement(host)), host);
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), shadow.querySelector('slot'));
+  }, 'document.elementFromPoint must return the shadow host and shadowRoot.elementFromPoint must return the slot parent of the fallback text when the hit-tested text node is a fallback content and the host has display: ' + displayValue);
+});
+
+
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<slot></slot>';
+    host.innerHTML = '<inner-host>hello</inner-host>';
+    container.appendChild(host);
+
+    let innerHost = host.querySelector('inner-host');
+    let innerShadow = innerHost.attachShadow({mode: 'closed'});
+    innerShadow.innerHTML = '<slot></slot>';
+    assert_equals(document.elementFromPoint(...pointInElement(host)), innerHost);
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), innerHost);
+    assert_equals(innerShadow.elementFromPoint(...pointInElement(host)), innerHost);
+  }, 'document.elementFromPoint, shadowRoot.elementFromPoint, innerShadow.elementFromPoint must return a child element assigned to a slot'
+  + ' when the hit-tested text node is assigned to a slot in the shadow tree of the child element and the outer shadow host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<slot></slot>';
+    host.innerHTML = '<inner-host></inner-host>';
+    container.appendChild(host);
+
+    let innerHost = host.querySelector('inner-host');
+    let innerShadow = innerHost.attachShadow({mode: 'closed'});
+    innerShadow.innerHTML = 'hello';
+    assert_equals(document.elementFromPoint(...pointInElement(host)), innerHost);
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), innerHost);
+    assert_equals(innerShadow.elementFromPoint(...pointInElement(host)), innerHost);
+  }, 'document.elementFromPoint, shadowRoot.elementFromPoint, innerShadow.elementFromPoint must return a child element with its own shadow tree assigned to a slot'
+  + ' when the hit-tested text node is its direct child and the outer shadow host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<slot></slot>';
+    host.innerHTML = '<inner-host></inner-host>';
+    container.appendChild(host);
+
+    let innerHost = host.querySelector('inner-host');
+    let innerShadow = innerHost.attachShadow({mode: 'closed'});
+    innerShadow.innerHTML = '<span>hello</span>';
+
+    assert_equals(document.elementFromPoint(...pointInElement(host)), innerHost);
+    assert_equals(shadow.elementFromPoint(...pointInElement(host)), innerHost);
+    assert_equals(innerShadow.elementFromPoint(...pointInElement(host)), innerShadow.querySelector('span'));
+  }, 'document.elementFromPoint, shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot'
+  + ' when the hit-tested text node is a child of another element and innerShadow.elementFromPoint must return the parent element of the hit-tested text node under it when the outer shadow host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = 'hello';
+    container.appendChild(host);
+    assert_array_equals(document.elementsFromPoint(...pointInElement(host)), [host, container, document.body, document.documentElement]);
+    assert_array_equals(shadow.elementsFromPoint(...pointInElement(host)), [host, container, document.body, document.documentElement]);
+  }, 'document.elementsFromPoint and shadow.elementsFromPoint must return the shadow host and its ancestors of the hit-tested text node when the hit-tested text node is a direct child of the root and the host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<slot></slot>';
+    host.innerHTML = 'text';
+    container.appendChild(host);
+    assert_array_equals(document.elementsFromPoint(...pointInElement(host)), [host, container, document.body, document.documentElement]);
+    assert_array_equals(shadow.elementsFromPoint(...pointInElement(host)), [host, container, document.body, document.documentElement]);
+  },'document.elementsFromPoint and shadowRoot.elementsFromPoint must return the shadow host and its ancestors when the hit-tested text node is assigned to a slot and the host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<div><slot></slot></div>';
+    host.innerHTML = '<span>text</span>';
+    container.appendChild(host);
+    assert_array_equals(document.elementsFromPoint(...pointInElement(host)), [host.querySelector('span'), host, container, document.body, document.documentElement]);
+    assert_array_equals(shadow.elementsFromPoint(...pointInElement(host)), [host.querySelector('span'), shadow.querySelector('div'),  host, container, document.body, document.documentElement]);
+  }, 'document.elementsFromPoint and shadowRoot.elementsFromPoint must return the element assigned to a slot and its non-shadow ancestors when hit-tested text node under an element is assigned to a slot in the shadow tree and the shadow host of the slot has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<span>text</span>';
+    container.appendChild(host);
+    assert_array_equals(document.elementsFromPoint(...pointInElement(host)), [host, container, document.body, document.documentElement]);
+    assert_array_equals(shadow.elementsFromPoint(...pointInElement(host)), [shadow.querySelector('span'), host, container, document.body, document.documentElement]);
+  }, 'document.elementsFromPoint must return the shadow host and its ancestors of the hit-tested element under a shadow root and'
+  + 'shadowRoot.elementsFromPoint must return the element parent and its non-shadow ancestors of the hit-tested text node under the point when the shadow host has display: ' + displayValue);
+});
+
+displayValues.forEach(function (displayValue) {
+  test(function () {
+    container.innerHTML = '';
+    let host = document.createElement('test-element');
+    host.style.display = displayValue;
+    let shadow = host.attachShadow({mode: 'closed'});
+    shadow.innerHTML = '<div><slot>fallback</slot></div>';
+    container.appendChild(host);
+    assert_array_equals(document.elementsFromPoint(...pointInElement(host)), [host, container, document.body, document.documentElement]);
+    assert_array_equals(shadow.elementsFromPoint(...pointInElement(host)), [shadow.querySelector('slot'), shadow.querySelector('div'), host, container, document.body, document.documentElement]);
+  }, 'document.elementsFromPoint must return the shadow host and its ancestors and shadowRoot.elementsFromPoint must return the slot parent of the fallback text and its non-shadow ancestors when the hit-tested text node is a fallback content and the host has display: ' + displayValue);
+});
+
+container.innerHTML = '';
+
+    </script>
+  </body>
+</html>
+
diff --git a/shadow-dom/Element-interface-attachShadow-custom-element.html b/shadow-dom/Element-interface-attachShadow-custom-element.html
new file mode 100644
index 0000000..b59460e
--- /dev/null
+++ b/shadow-dom/Element-interface-attachShadow-custom-element.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Shadow DOM: Attaching a ShadowRoot for custom elements</title>
+<meta name="author" title="Hayato Ito" href="mailto:hayato@chromium.org">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-element-attachshadow">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+class MyAutonomousCustomElement extends HTMLElement {
+}
+
+customElements.define('my-custom', MyAutonomousCustomElement);
+
+test(() => {
+  assert_true(document.createElement('my-custom').attachShadow({mode: "open"}) instanceof ShadowRoot);
+}, 'Element.attachShadow must create an instance of ShadowRoot for autonomous custom elements');
+
+class MyCustomizedBuiltinElement extends HTMLInputElement {
+}
+
+customElements.define('my-input', MyCustomizedBuiltinElement, { extends: 'input' });
+
+test(() => {
+  assert_throws({'name': 'NotSupportedError'}, () => {
+    document.createElement('input', {is: 'my-input'}).attachShadow({mode: "open"});
+  });
+}, 'Element.attachShadow must throw a NotSupportedError for customized built-in elements');
+</script>
+</body>
+</html>
diff --git a/shadow-dom/Extensions-to-Event-Interface.html b/shadow-dom/Extensions-to-Event-Interface.html
index 37d07a7..52cda0b 100644
--- a/shadow-dom/Extensions-to-Event-Interface.html
+++ b/shadow-dom/Extensions-to-Event-Interface.html
@@ -14,11 +14,6 @@
 <script>
 
 test(function () {
-    assert_true('composedPath' in Event.prototype);
-    assert_true('composedPath' in new Event('my-event'));
-}, 'composedPath() must exist on Event');
-
-test(function () {
     var event = new Event('my-event');
     assert_array_equals(event.composedPath(), []);
 }, 'composedPath() must return an empty array when the event has not been dispatched');
@@ -30,11 +25,6 @@
 }, 'composedPath() must return an empty array when the event is no longer dispatched');
 
 test(function () {
-    assert_true('composed' in Event.prototype);
-    assert_true('composed' in new Event('my-event'));
-}, 'composed must exist on Event');
-
-test(function () {
     var event = new Event('my-event');
     assert_false(event.composed);
 }, 'composed on EventInit must default to false');
diff --git a/shadow-dom/event-composed-path-with-related-target.html b/shadow-dom/event-composed-path-with-related-target.html
index 675a7d7..f6dff13 100644
--- a/shadow-dom/event-composed-path-with-related-target.html
+++ b/shadow-dom/event-composed-path-with-related-target.html
@@ -131,14 +131,12 @@
                                  ['sr2', 'target', 'host1', path],
                                  ['host2', 'host2', 'host1', path],
                                  ['sr1', 'host2', 'host1', path]]);
-}, 'Event path for an event with a relatedTarget. relaterTarget is a shadow-including ancestor of target.');
+}, 'Event path for an event with a relatedTarget. relatedTarget is a shadow-including ancestor of target.');
 
 test(() => {
   let n = createTestTree(test4);
   let log = dispatchEventWithLog(n, n.host1, new FocusEvent('my-focus', { bubbles: true, composed: true, relatedTarget: n.target }));
-  let path = ['host1', 'test4'];
-  assert_event_path_equals(log, [['host1', 'host1', 'host1', path],
-                                 ['test4', 'host1', 'host1', path]]);
+  assert_event_path_equals(log, []);
 }, 'Event path for an event with a relatedTarget. target is a shadow-including ancestor of relatedTarget.');
 </script>
 
diff --git a/shadow-dom/form-control-form-attribute.html b/shadow-dom/form-control-form-attribute.html
new file mode 100644
index 0000000..2e782b2
--- /dev/null
+++ b/shadow-dom/form-control-form-attribute.html
@@ -0,0 +1,80 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Form controls' form attribute</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="testcontent">
+  <form id="form">form</form>
+  <input id="input" form="form">
+</div>
+
+<script>
+
+test(() => {
+  assert_equals(document.getElementById('input').form,
+                document.getElementById('form'));
+}, "Form control's form attribute should point to the form element.");
+
+test(() => {
+  var testcontent = document.getElementById("testcontent");
+  var host = document.createElement("div");
+  var sr = host.attachShadow({mode: "open"});
+  sr.innerHTML = testcontent.innerHTML;
+  var input = sr.getElementById("input");
+  var form = sr.getElementById("form");
+
+  // Should have null form when shadow DOM isn't connected.
+  assert_equals(input.form, null);
+
+  testcontent.appendChild(host);
+  assert_equals(input.form, form);
+
+  host.remove();
+  assert_equals(input.form, null);
+
+  testcontent.appendChild(host);
+  assert_equals(input.form, form);
+
+  input.remove();
+  assert_equals(input.form, null);
+
+  sr.appendChild(input);
+  assert_equals(input.form, form);
+
+  form.id = "foobar";
+  assert_equals(input.form, null);
+
+  form.id = "form";
+  assert_equals(input.form, form);
+
+  form.remove();
+  assert_equals(input.form, null);
+
+  sr.appendChild(form);
+  assert_equals(input.form, form);
+
+  host.remove();
+}, "Shadow form control's form attribute should work also in shadow DOM.");
+
+test(() => {
+  var testcontent = document.getElementById("testcontent");
+  var host = document.createElement("div");
+  var sr = host.attachShadow({mode: "open"});
+  sr.innerHTML = "<form id='form'><input id='input'></form>";
+  var input = sr.getElementById("input");
+  var form = sr.getElementById("form");
+
+  assert_equals(input.form, form);
+
+  input.remove();
+  assert_equals(input.form, null);
+
+  form.appendChild(input);
+  assert_equals(input.form, form);
+
+  form.remove();
+  assert_equals(input.form, form);
+
+  host.remove();
+}, "Form element as form control's ancestor should work also in shadow DOM.");
+</script>
diff --git a/shadow-dom/layout-slot-no-longer-assigned.html b/shadow-dom/layout-slot-no-longer-assigned.html
new file mode 100644
index 0000000..dfcac99
--- /dev/null
+++ b/shadow-dom/layout-slot-no-longer-assigned.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Layout using slot elements</title>
+<link rel="author" title="Hayato Ito" href="mailto:hayato@google.com"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#shadow-tree-slots">
+<link rel="match" href="reference/empty.html"/>
+<div id="host"></div>
+<script>
+const host = document.querySelector('#host');
+const sr = host.attachShadow({ mode: 'open' });
+sr.innerHTML = '<slot name=s1></slot>'
+host.innerHTML = '<div id=d1 slot=s1></div>';
+
+document.body.offsetLeft;
+document.querySelector('#d1').setAttribute('slot', 's2');
+</script>
diff --git a/shadow-dom/layout-slot-no-longer-fallback.html b/shadow-dom/layout-slot-no-longer-fallback.html
new file mode 100644
index 0000000..7507f11
--- /dev/null
+++ b/shadow-dom/layout-slot-no-longer-fallback.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Layout using slot elements</title>
+<link rel="author" title="Hayato Ito" href="mailto:hayato@google.com"/>
+<link rel="help" href="https://dom.spec.whatwg.org/#shadow-tree-slots">
+<link rel="match" href="reference/empty.html"/>
+<div id="host"></div>
+<script>
+const host = document.querySelector('#host');
+const sr = host.attachShadow({ mode: 'open' });
+
+sr.innerHTML = '<slot><div id="fallback">Should not be displayed</div></slot>'
+
+document.body.offsetLeft;
+host.appendChild(document.createElement('div'));
+</script>
diff --git a/shadow-dom/reference/empty.html b/shadow-dom/reference/empty.html
new file mode 100644
index 0000000..0e76edd
--- /dev/null
+++ b/shadow-dom/reference/empty.html
@@ -0,0 +1 @@
+<!DOCTYPE html>
diff --git a/shadow-dom/resources/event-path-test-helpers.js b/shadow-dom/resources/event-path-test-helpers.js
index 17d6fff..f0e8ec3 100644
--- a/shadow-dom/resources/event-path-test-helpers.js
+++ b/shadow-dom/resources/event-path-test-helpers.js
@@ -16,9 +16,6 @@
                 eventPath.push(this.label);
                 relatedTargets.push(event.relatedTarget ? event.relatedTarget.label : null);
 
-                if (!event.composedPath) // Don't fail all tests just for the lack of composedPath.
-                    return;
-
                 pathAtTargets.push(event.composedPath().map(function (node) { return node.label; }));
                 targets.push(event.target);
             }).bind(node));
diff --git a/shadow-dom/slots-fallback.html b/shadow-dom/slots-fallback.html
index a8d60e8..e705e18 100644
--- a/shadow-dom/slots-fallback.html
+++ b/shadow-dom/slots-fallback.html
@@ -25,6 +25,13 @@
   assert_array_equals(n.s1.assignedNodes(), []);
   assert_array_equals(n.s1.assignedNodes({ flatten: true }), [n.f1]);
 }, 'Slots fallback: Basic.');
+
+test(() => {
+  let n = createTestTree(test1);
+
+  assert_array_equals(n.s1.assignedElements(), []);
+  assert_array_equals(n.s1.assignedElements({ flatten: true }), [n.f1]);
+}, 'Slots fallback: Basic, elements only.');
 </script>
 
 <div id="test2">
@@ -52,6 +59,16 @@
   assert_array_equals(n.s1.assignedNodes({ flatten: true }), [n.f1]);
   assert_array_equals(n.s2.assignedNodes({ flatten: true }), [n.f1]);
 }, 'Slots fallback: Slots in Slots.');
+
+test(() => {
+  let n = createTestTree(test2);
+
+  assert_array_equals(n.s1.assignedElements(), []);
+  assert_array_equals(n.s2.assignedElements(), []);
+
+  assert_array_equals(n.s1.assignedElements({ flatten: true }), [n.f1]);
+  assert_array_equals(n.s2.assignedElements({ flatten: true }), [n.f1]);
+}, 'Slots fallback: Slots in Slots, elements only.');
 </script>
 
 <div id="test3">
@@ -109,7 +126,7 @@
 
   assert_array_equals(n.s1.assignedNodes({ flatten: true }), [n.c1]);
   assert_array_equals(n.s2.assignedNodes({ flatten: true }), [n.c1]);
-}, 'Slots fallback: Slots in Slots: Assinged nodes should be used as fallback contents of another slot');
+}, 'Slots fallback: Slots in Slots: Assigned nodes should be used as fallback contents of another slot');
 </script>
 
 <div id="test5">
@@ -154,6 +171,20 @@
 
 test(() => {
   let n = createTestTree(test5);
+
+  assert_array_equals(n.s1.assignedElements(), [n.c1]);
+  assert_array_equals(n.s2.assignedElements(), []);
+  assert_array_equals(n.s3.assignedElements(), [n.s2]);
+  assert_array_equals(n.s4.assignedElements(), []);
+
+  assert_array_equals(n.s1.assignedElements({ flatten: true }), [n.c1]);
+  assert_array_equals(n.s2.assignedElements({ flatten: true }), [n.c1, n.f2]);
+  assert_array_equals(n.s3.assignedElements({ flatten: true }), [n.c1, n.f2]);
+  assert_array_equals(n.s4.assignedElements({ flatten: true }), [n.c1, n.f2, n.f4]);
+}, 'Slots fallback: Complex case, elements only.');
+
+test(() => {
+  let n = createTestTree(test5);
   removeWhiteSpaceOnlyTextNodes(n.test5);
 
   let d1 = document.createElement('div');
diff --git a/shadow-dom/slots.html b/shadow-dom/slots.html
index 550d7d4..948d389 100644
--- a/shadow-dom/slots.html
+++ b/shadow-dom/slots.html
@@ -22,6 +22,12 @@
   assert_equals(n.c1.assignedSlot, n.s1);
   assert_array_equals(n.s1.assignedNodes(), [n.c1]);
 }, 'Slots: Basic.');
+
+test(() => {
+  let n = createTestTree(test_basic);
+
+  assert_array_equals(n.s1.assignedElements(), [n.c1]);
+}, 'Slots: Basic, elements only.');
 </script>
 
 <div id="test_basic_closed">
@@ -41,6 +47,12 @@
   assert_equals(n.c1.assignedSlot, null);
   assert_array_equals(n.s1.assignedNodes(), [n.c1]);
 }, 'Slots: Slots in closed.');
+
+test(() => {
+  let n = createTestTree(test_basic_closed);
+
+  assert_array_equals(n.s1.assignedElements(), [n.c1]);
+}, 'Slots: Slots in closed, elements only.');
 </script>
 
 <div id="test_slot_not_in_shadow">
@@ -54,6 +66,12 @@
 
   assert_array_equals(n.s1.assignedNodes(), []);
 }, 'Slots: Slots not in a shadow tree.');
+
+test(() => {
+  let n = createTestTree(test_slot_not_in_shadow);
+
+  assert_array_equals(n.s1.assignedElements(), []);
+}, 'Slots: Slots not in a shadow tree, elements only.');
 </script>
 
 <div id="test_slot_not_in_shadow_2">
@@ -86,7 +104,7 @@
   assert_array_equals(n.s1.assignedNodes({ flatten: true }), []);
   assert_array_equals(n.s2.assignedNodes({ flatten: true }), []);
   assert_array_equals(n.s3.assignedNodes({ flatten: true }), []);
-}, 'Slots: Distributed nooes for Slots not in a shadow tree.');
+}, 'Slots: Distributed nodes for Slots not in a shadow tree.');
 </script>
 
 <div id="test_slot_name_matching">
diff --git a/shadow-dom/untriaged/elements-and-dom-objects/extensions-to-event-interface/event-path-001.html b/shadow-dom/untriaged/elements-and-dom-objects/extensions-to-event-interface/event-path-001.html
deleted file mode 100644
index 455ee40..0000000
--- a/shadow-dom/untriaged/elements-and-dom-objects/extensions-to-event-interface/event-path-001.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<!DOCTYPE html>
-<!--
-Distributed under both the W3C Test Suite License [1] and the W3C
-3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
-policies and contribution forms [3].
-
-[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
-[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
-[3] http://www.w3.org/2004/10/27-testcases
--->
-<html>
-<head>
-<title>Shadow DOM Test - event path</title>
-<link rel="author" title="Kazuhito Hokamura" href="mailto:k.hokamura@gmail.com">
-<link rel="help" href="https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#extensions-to-event">
-<meta name="assert" content="Extensions to Event Interface: event.composedPath() cross the shadow boundary">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../../../html/resources/common.js"></script>
-<script src="../../../resources/shadow-dom-utils.js"></script>
-</head>
-<body>
-<div id="log"></div>
-<script>
-var t = async_test('event.composedPath() cross the shadow boundary');
-
-t.step(unit(function(ctx) {
-    var doc = newRenderedHTMLDocument(ctx);
-    var host = doc.createElement('div');
-
-    var shadowRoot = host.attachShadow({mode: 'open'});
-    var child = doc.createElement('div');
-
-    doc.body.appendChild(host);
-    shadowRoot.appendChild(child);
-
-    child.addEventListener('click', t.step_func(function(e) {
-        assert_equals(e.composedPath().length, 7, 'composedPath().length');
-        assert_equals(e.composedPath()[0], child, 'composedPath()[0] should be child');
-        assert_equals(e.composedPath()[1], shadowRoot, 'composedPath()[1] should be shadowRoot');
-        assert_equals(e.composedPath()[2], host, 'composedPath()[2] should be host');
-        assert_equals(e.composedPath()[3], doc.body, 'composedPath()[3] should be body');
-        assert_equals(e.composedPath()[4], doc.documentElement, 'composedPath()[4] should be html');
-        assert_equals(e.composedPath()[5], doc, 'composedPath()[5] should be document');
-        assert_equals(e.composedPath()[6], ctx.iframes[0].contentWindow, 'composedPath()[6] should be window');
-
-        t.done();
-    }));
-
-    var event = doc.createEvent('HTMLEvents');
-    event.initEvent('click', true, false);
-    child.dispatchEvent(event);
-}));
-</script>
-</body>
-</html>
diff --git a/shadow-dom/untriaged/shadow-trees/upper-boundary-encapsulation/test-011.html b/shadow-dom/untriaged/shadow-trees/upper-boundary-encapsulation/test-011.html
index 14d3f82..58135b1 100644
--- a/shadow-dom/untriaged/shadow-trees/upper-boundary-encapsulation/test-011.html
+++ b/shadow-dom/untriaged/shadow-trees/upper-boundary-encapsulation/test-011.html
@@ -44,8 +44,6 @@
 
 
 }), 'A_04_01_11_T2');
-
-// TODO check selectedStyleSheetSet, lastStyleSheetSet, preferredStyleSheetSet, styleSheetSets
 </script>
 </body>
 </html>
diff --git a/staticrange/OWNERS b/staticrange/OWNERS
deleted file mode 100644
index 5692d1b..0000000
--- a/staticrange/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-@garykac
-@siusin
diff --git a/staticrange/idlharness.html b/staticrange/idlharness.html
deleted file mode 100644
index e35bca1..0000000
--- a/staticrange/idlharness.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<title>Static Range IDL tests</title>
-<link rel="help" href="http://garykac.github.io/staticrange/index.html#interface-staticrange"/>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/resources/idlharness.js"></script>
-
-<pre id="untested_idl">
-interface Node {
-};
-</pre>
-
-<pre id='idl'>
-interface StaticRange {
-    readonly attribute Node startContainer;
-    readonly attribute unsigned long startOffset;
-    readonly attribute Node endContainer;
-    readonly attribute unsigned long endOffset;
-    readonly attribute boolean collapsed;
-};
-</pre>
-
-<script>
-(function(){
-    "use strict";
-    const idl_array = new IdlArray();
-    idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
-    idl_array.add_idls(document.getElementById("idl").textContent);
-    idl_array.test();
-})();
-</script>
diff --git a/storage/interfaces.https.html b/storage/interfaces.https.html
index c390f32..4e79ae2 100644
--- a/storage/interfaces.https.html
+++ b/storage/interfaces.https.html
@@ -9,7 +9,7 @@
 <script>
 'use strict';
 promise_test(t => {
-  return fetch('interfaces.idl')
+  return fetch('/interfaces/storage.idl')
     .then(response => response.text())
     .then(idls => {
       var idl_array = new IdlArray();
diff --git a/storage/interfaces.https.worker.js b/storage/interfaces.https.worker.js
index e6feed7..c0d9e03 100644
--- a/storage/interfaces.https.worker.js
+++ b/storage/interfaces.https.worker.js
@@ -4,7 +4,7 @@
 importScripts('/resources/WebIDLParser.js', '/resources/idlharness.js');
 
 promise_test(t => {
-  return fetch('interfaces.idl')
+  return fetch('/interfaces/storage.idl')
     .then(response => response.text())
     .then(idls => {
       var idl_array = new IdlArray();
diff --git a/storage/interfaces.idl b/storage/interfaces.idl
deleted file mode 100644
index 4967404..0000000
--- a/storage/interfaces.idl
+++ /dev/null
@@ -1,22 +0,0 @@
-[SecureContext,
- NoInterfaceObject,
- Exposed=(Window,Worker)]
-interface NavigatorStorage {
-  readonly attribute StorageManager storage;
-};
-Navigator implements NavigatorStorage;
-WorkerNavigator implements NavigatorStorage;
-
-[SecureContext,
- Exposed=(Window,Worker)]
-interface StorageManager {
-  Promise<boolean> persisted();
-  [Exposed=Window] Promise<boolean> persist();
-
-  Promise<StorageEstimate> estimate();
-};
-
-dictionary StorageEstimate {
-  unsigned long long usage;
-  unsigned long long quota;
-};
diff --git a/streams/piping/close-propagation-forward.js b/streams/piping/close-propagation-forward.js
index 35eac87..0f0b7bb 100644
--- a/streams/piping/close-propagation-forward.js
+++ b/streams/piping/close-propagation-forward.js
@@ -448,14 +448,115 @@
   // Flush async events and verify that no shutdown occurs.
   return flushAsyncEvents().then(() => {
     assert_array_equals(ws.events, ['write', 'a'],
-      'the chunk must have been written, but close must not have happened yet');
+      'the chunk must have been written, but close must not have happened');
     assert_equals(pipeComplete, false, 'the pipe must not be complete');
 
     resolveWritePromise();
 
     return pipePromise;
+  }).then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a'],
+      'the chunk must have been written, but close must not have happened');
   });
 
 }, 'Closing must be propagated forward: shutdown must not occur until the final write completes; preventClose = true');
 
+promise_test(() => {
+
+  const rs = recordingReadableStream();
+
+  let resolveWriteCalled;
+  const writeCalledPromise = new Promise(resolve => {
+    resolveWriteCalled = resolve;
+  });
+
+  let resolveWritePromise;
+  const ws = recordingWritableStream({
+    write() {
+      resolveWriteCalled();
+
+      return new Promise(resolve => {
+        resolveWritePromise = resolve;
+      });
+    }
+  }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+  let pipeComplete = false;
+  const pipePromise = rs.pipeTo(ws).then(() => {
+    pipeComplete = true;
+  });
+
+  rs.controller.enqueue('a');
+  rs.controller.enqueue('b');
+
+  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a'],
+      'the first chunk must have been written, but close must not have happened yet');
+    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+    rs.controller.close();
+    resolveWritePromise();
+  }).then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+      'the second chunk must have been written, but close must not have happened yet');
+    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+    resolveWritePromise();
+    return pipePromise;
+  }).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'close'],
+      'all chunks must have been written and close must have happened');
+  });
+
+}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write');
+
+promise_test(() => {
+
+  const rs = recordingReadableStream();
+
+  let resolveWriteCalled;
+  const writeCalledPromise = new Promise(resolve => {
+    resolveWriteCalled = resolve;
+  });
+
+  let resolveWritePromise;
+  const ws = recordingWritableStream({
+    write() {
+      resolveWriteCalled();
+
+      return new Promise(resolve => {
+        resolveWritePromise = resolve;
+      });
+    }
+  }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+  let pipeComplete = false;
+  const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
+    pipeComplete = true;
+  });
+
+  rs.controller.enqueue('a');
+  rs.controller.enqueue('b');
+
+  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a'],
+      'the first chunk must have been written, but close must not have happened');
+    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+    rs.controller.close();
+    resolveWritePromise();
+  }).then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+      'the second chunk must have been written, but close must not have happened');
+    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+    resolveWritePromise();
+    return pipePromise;
+  }).then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+      'all chunks must have been written, but close must not have happened');
+  });
+
+}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write; preventClose = true');
+
 done();
diff --git a/streams/piping/error-propagation-backward.js b/streams/piping/error-propagation-backward.js
index 5dd9fea..dda5774 100644
--- a/streams/piping/error-propagation-backward.js
+++ b/streams/piping/error-propagation-backward.js
@@ -548,7 +548,7 @@
   return rs.pipeTo(ws).then(
     () => assert_unreached('the promise must not fulfill'),
     err => {
-      assert_equals(err.name, 'TypeError', 'the promise must reject with a TypeError (_not_ with error1)');
+      assert_equals(err, error1, 'the promise must reject with error1');
 
       assert_array_equals(rs.eventsWithoutPulls, ['cancel', err]);
       assert_array_equals(ws.events, ['abort', error1]);
@@ -575,7 +575,7 @@
       return ws.getWriter().closed.then(
         () => assert_unreached('the promise must not fulfill'),
         err => {
-          assert_equals(err.name, 'TypeError', 'the promise must reject with a TypeError (_not_ with error1)');
+          assert_equals(err, error1, 'the promise must reject with error1');
 
           assert_array_equals(rs.eventsWithoutPulls, ['cancel', err]);
           assert_array_equals(ws.events, ['abort', error1]);
@@ -594,7 +594,7 @@
 
   ws.abort(error1);
 
-  return promise_rejects(t, new TypeError(), rs.pipeTo(ws, { preventCancel: true })).then(() => {
+  return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true })).then(() => {
     assert_array_equals(rs.eventsWithoutPulls, []);
     assert_array_equals(ws.events, ['abort', error1]);
   });
diff --git a/streams/piping/error-propagation-forward.js b/streams/piping/error-propagation-forward.js
index a6d30fe..983b92b 100644
--- a/streams/piping/error-propagation-forward.js
+++ b/streams/piping/error-propagation-forward.js
@@ -428,4 +428,147 @@
 
 }, 'Errors must be propagated forward: shutdown must not occur until the final write completes');
 
+promise_test(t => {
+
+  const rs = recordingReadableStream();
+
+  let resolveWriteCalled;
+  const writeCalledPromise = new Promise(resolve => {
+    resolveWriteCalled = resolve;
+  });
+
+  let resolveWritePromise;
+  const ws = recordingWritableStream({
+    write() {
+      resolveWriteCalled();
+
+      return new Promise(resolve => {
+        resolveWritePromise = resolve;
+      });
+    }
+  });
+
+  let pipeComplete = false;
+  const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true })).then(() => {
+    pipeComplete = true;
+  });
+
+  rs.controller.enqueue('a');
+
+  return writeCalledPromise.then(() => {
+    rs.controller.error(error1);
+
+    // Flush async events and verify that no shutdown occurs.
+    return flushAsyncEvents();
+  }).then(() => {
+    assert_array_equals(ws.events, ['write', 'a']); // no 'abort'
+    assert_equals(pipeComplete, false, 'the pipe must not be complete');
+
+    resolveWritePromise();
+    return pipePromise;
+  }).then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a']); // no 'abort'
+  });
+
+}, 'Errors must be propagated forward: shutdown must not occur until the final write completes; preventAbort = true');
+
+promise_test(t => {
+
+  const rs = recordingReadableStream();
+
+  let resolveWriteCalled;
+  const writeCalledPromise = new Promise(resolve => {
+    resolveWriteCalled = resolve;
+  });
+
+  let resolveWritePromise;
+  const ws = recordingWritableStream({
+    write() {
+      resolveWriteCalled();
+
+      return new Promise(resolve => {
+        resolveWritePromise = resolve;
+      });
+    }
+  }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+  let pipeComplete = false;
+  const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws)).then(() => {
+    pipeComplete = true;
+  });
+
+  rs.controller.enqueue('a');
+  rs.controller.enqueue('b');
+
+  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a'],
+      'the first chunk must have been written, but abort must not have happened yet');
+    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+    rs.controller.error(error1);
+    resolveWritePromise();
+    return flushAsyncEvents();
+  }).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+      'the second chunk must have been written, but abort must not have happened yet');
+    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+    resolveWritePromise();
+    return pipePromise;
+  }).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'abort', error1],
+      'all chunks must have been written and abort must have happened');
+  });
+
+}, 'Errors must be propagated forward: shutdown must not occur until the final write completes; becomes errored after first write');
+
+promise_test(t => {
+
+  const rs = recordingReadableStream();
+
+  let resolveWriteCalled;
+  const writeCalledPromise = new Promise(resolve => {
+    resolveWriteCalled = resolve;
+  });
+
+  let resolveWritePromise;
+  const ws = recordingWritableStream({
+    write() {
+      resolveWriteCalled();
+
+      return new Promise(resolve => {
+        resolveWritePromise = resolve;
+      });
+    }
+  }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+  let pipeComplete = false;
+  const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true })).then(() => {
+    pipeComplete = true;
+  });
+
+  rs.controller.enqueue('a');
+  rs.controller.enqueue('b');
+
+  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a'],
+      'the first chunk must have been written, but abort must not have happened');
+    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+    rs.controller.error(error1);
+    resolveWritePromise();
+  }).then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+      'the second chunk must have been written, but abort must not have happened');
+    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+    resolveWritePromise();
+    return pipePromise;
+  }).then(() => flushAsyncEvents()).then(() => {
+    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+      'all chunks must have been written, but abort must not have happened');
+  });
+
+}, 'Errors must be propagated forward: shutdown must not occur until the final write completes; becomes errored after first write; preventAbort = true');
+
 done();
diff --git a/streams/piping/multiple-propagation.js b/streams/piping/multiple-propagation.js
index 447433f..4edc7c1 100644
--- a/streams/piping/multiple-propagation.js
+++ b/streams/piping/multiple-propagation.js
@@ -80,10 +80,10 @@
 
     return Promise.all([
       promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1'),
-      promise_rejects(t, new TypeError(), ws.getWriter().closed,
-        'closed must reject with a TypeError indicating the writable stream was aborted'),
-      promise_rejects(t, new TypeError(), closePromise,
-        'close() must reject with a TypeError indicating the writable stream was aborted'),
+      promise_rejects(t, error1, ws.getWriter().closed,
+        'closed must reject with error1'),
+      promise_rejects(t, error1, closePromise,
+        'close() must reject with error1')
     ]);
   });
 
diff --git a/streams/readable-byte-streams/construct-byob-request.dedicatedworker.html b/streams/readable-byte-streams/construct-byob-request.dedicatedworker.html
new file mode 100644
index 0000000..887e334
--- /dev/null
+++ b/streams/readable-byte-streams/construct-byob-request.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>construct-byob-request.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('construct-byob-request.js'));
+</script>
diff --git a/streams/readable-byte-streams/construct-byob-request.html b/streams/readable-byte-streams/construct-byob-request.html
new file mode 100644
index 0000000..4e071e7
--- /dev/null
+++ b/streams/readable-byte-streams/construct-byob-request.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>construct-byob-request.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/rs-utils.js"></script>
+
+<script src="construct-byob-request.js"></script>
diff --git a/streams/readable-byte-streams/construct-byob-request.js b/streams/readable-byte-streams/construct-byob-request.js
new file mode 100644
index 0000000..29fdac5
--- /dev/null
+++ b/streams/readable-byte-streams/construct-byob-request.js
@@ -0,0 +1,82 @@
+'use strict';
+
+// Prior to whatwg/stream#870 it was possible to construct a ReadableStreamBYOBRequest directly. This made it possible
+// to construct requests that were out-of-sync with the state of the ReadableStream. They could then be used to call
+// internal operations, resulting in asserts or bad behaviour. This file contains regression tests for the change.
+
+if (self.importScripts) {
+  self.importScripts('../resources/rs-utils.js');
+  self.importScripts('/resources/testharness.js');
+}
+
+function getRealByteStreamController() {
+  let controller;
+  new ReadableStream({
+    start(c) {
+      controller = c;
+    },
+    type: 'bytes'
+  });
+  return controller;
+}
+
+const ReadableByteStreamController = getRealByteStreamController().constructor;
+
+// Create an object pretending to have prototype |prototype|, of type |type|. |type| is one of "undefined", "null",
+// "fake", or "real". "real" will call the realObjectCreator function to get a real instance of the object.
+function createDummyObject(prototype, type, realObjectCreator) {
+  switch (type) {
+    case 'undefined':
+      return undefined;
+
+    case 'null':
+      return null;
+
+    case 'fake':
+      return Object.create(prototype);
+
+    case 'real':
+      return realObjectCreator();
+  }
+
+  throw new Error('not reached');
+}
+
+const dummyTypes = ['undefined', 'null', 'fake', 'real'];
+
+function runTests(ReadableStreamBYOBRequest) {
+  for (const controllerType of dummyTypes) {
+    const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType,
+                                       getRealByteStreamController);
+    for (const viewType of dummyTypes) {
+      const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16));
+      test(() => {
+        assert_throws(new TypeError(), () => new ReadableStreamBYOBRequest(controller, view),
+                      'constructor should throw');
+      }, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` +
+         `ReadableByteStreamController and a ${viewType} view`);
+    }
+  }
+}
+
+function getConstructorAndRunTests() {
+  let ReadableStreamBYOBRequest;
+  const rs = new ReadableStream({
+    pull(controller) {
+      const byobRequest = controller.byobRequest;
+      ReadableStreamBYOBRequest = byobRequest.constructor;
+      byobRequest.respond(4);
+    },
+    type: 'bytes'
+  });
+  rs.getReader({ mode: 'byob' }).read(new Uint8Array(8)).then(() => {
+    runTests(ReadableStreamBYOBRequest);
+    done();
+  });
+}
+
+// We can only get at the ReadableStreamBYOBRequest constructor asynchronously, so we need to make the test harness wait
+// for us to explicitly tell it all our tests have run.
+setup({ explicit_done: true });
+
+getConstructorAndRunTests();
diff --git a/streams/readable-byte-streams/construct-byob-request.serviceworker.https.html b/streams/readable-byte-streams/construct-byob-request.serviceworker.https.html
new file mode 100644
index 0000000..abf70cb
--- /dev/null
+++ b/streams/readable-byte-streams/construct-byob-request.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>construct-byob-request.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('construct-byob-request.js', 'Service worker test setup');
+</script>
diff --git a/streams/readable-byte-streams/construct-byob-request.sharedworker.html b/streams/readable-byte-streams/construct-byob-request.sharedworker.html
new file mode 100644
index 0000000..6f7ef5c
--- /dev/null
+++ b/streams/readable-byte-streams/construct-byob-request.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>construct-byob-request.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('construct-byob-request.js'));
+</script>
diff --git a/streams/readable-byte-streams/constructor.dedicatedworker.html b/streams/readable-byte-streams/constructor.dedicatedworker.html
new file mode 100644
index 0000000..aebe97e
--- /dev/null
+++ b/streams/readable-byte-streams/constructor.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('constructor.js'));
+</script>
diff --git a/streams/readable-byte-streams/constructor.html b/streams/readable-byte-streams/constructor.html
new file mode 100644
index 0000000..a548e08
--- /dev/null
+++ b/streams/readable-byte-streams/constructor.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/constructor-ordering.js"></script>
+
+<script src="constructor.js"></script>
diff --git a/streams/readable-byte-streams/constructor.js b/streams/readable-byte-streams/constructor.js
new file mode 100644
index 0000000..3405e23
--- /dev/null
+++ b/streams/readable-byte-streams/constructor.js
@@ -0,0 +1,53 @@
+'use strict';
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
+}
+
+const operations = [
+  op('get', 'size'),
+  op('get', 'highWaterMark'),
+  op('get', 'type'),
+  op('validate', 'type'),
+  op('validate', 'size'),
+  op('tonumber', 'highWaterMark'),
+  op('validate', 'highWaterMark'),
+  op('get', 'pull'),
+  op('validate', 'pull'),
+  op('get', 'cancel'),
+  op('validate', 'cancel'),
+  op('get', 'autoAllocateChunkSize'),
+  op('tonumber', 'autoAllocateChunkSize'),
+  op('validate', 'autoAllocateChunkSize'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const underlyingSource = createRecordingObjectWithProperties(record, ['start', 'pull', 'cancel']);
+
+    // The valid value for "type" is "bytes", so set it separately.
+    defineCheckedProperty(record, underlyingSource, 'type', () => record.check('type') ? 'invalid' : 'bytes');
+
+    // autoAllocateChunkSize is a special case because it has a tonumber step.
+    defineCheckedProperty(record, underlyingSource, 'autoAllocateChunkSize',
+                          () => createRecordingNumberObject(record, 'autoAllocateChunkSize'));
+
+    const strategy = createRecordingStrategy(record);
+
+    try {
+      new ReadableStream(underlyingSource, strategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `ReadableStream constructor should stop after ${failureOp} fails`);
+}
+
+done();
diff --git a/streams/readable-byte-streams/constructor.serviceworker.https.html b/streams/readable-byte-streams/constructor.serviceworker.https.html
new file mode 100644
index 0000000..ddddf06
--- /dev/null
+++ b/streams/readable-byte-streams/constructor.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('constructor.js', 'Service worker test setup');
+</script>
diff --git a/streams/readable-byte-streams/constructor.sharedworker.html b/streams/readable-byte-streams/constructor.sharedworker.html
new file mode 100644
index 0000000..52b1a5d
--- /dev/null
+++ b/streams/readable-byte-streams/constructor.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('constructor.js'));
+</script>
diff --git a/streams/readable-byte-streams/general.html b/streams/readable-byte-streams/general.html
index 32de3b2..717f386 100644
--- a/streams/readable-byte-streams/general.html
+++ b/streams/readable-byte-streams/general.html
@@ -6,5 +6,6 @@
 <script src="/resources/testharnessreport.js"></script>
 
 <script src="../resources/rs-utils.js"></script>
+<script src="../resources/test-utils.js"></script>
 
 <script src="general.js"></script>
diff --git a/streams/readable-byte-streams/general.js b/streams/readable-byte-streams/general.js
index 74115a6..39dd708 100644
--- a/streams/readable-byte-streams/general.js
+++ b/streams/readable-byte-streams/general.js
@@ -2,6 +2,7 @@
 
 if (self.importScripts) {
   self.importScripts('../resources/rs-utils.js');
+  self.importScripts('../resources/test-utils.js');
   self.importScripts('/resources/testharness.js');
 }
 
@@ -299,22 +300,28 @@
 promise_test(() => {
   let pullCount = 0;
   let controller;
-  let byobRequest;
-  let viewDefined = false;
-  let viewInfo;
+  const byobRequests = [];
 
   const stream = new ReadableStream({
     start(c) {
       controller = c;
     },
     pull() {
-      byobRequest = controller.byobRequest;
+      const byobRequest = controller.byobRequest;
       const view = byobRequest.view;
-      viewDefined = view !== undefined;
-      viewInfo = extractViewInfo(view);
-
-      view[0] = 0x01;
-      byobRequest.respond(1);
+      byobRequests[pullCount] = {
+        defined: byobRequest !== undefined,
+        viewDefined: view !== undefined,
+        viewInfo: extractViewInfo(view)
+      };
+      if (pullCount === 0) {
+        view[0] = 0x01;
+        byobRequest.respond(1);
+      } else if (pullCount === 1) {
+        view[0] = 0x02;
+        view[1] = 0x03;
+        byobRequest.respond(2);
+      }
 
       ++pullCount;
     },
@@ -325,28 +332,52 @@
   });
 
   const reader = stream.getReader();
-  const readPromise = reader.read();
-  const ignoredReadPromise = reader.read();
+  const p0 = reader.read();
+  const p1 = reader.read();
 
   assert_equals(pullCount, 0, 'No pull() as start() just finished and is not yet reflected to the state of the stream');
 
   return Promise.resolve().then(() => {
     assert_equals(pullCount, 1, 'pull() must have been invoked once');
-    assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
-    assert_true(viewDefined, 'byobRequest.view must not be undefined');
-    assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
-    assert_equals(viewInfo.bufferByteLength, 16, 'view.buffer.byteLength should be 16');
-    assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
-    assert_equals(viewInfo.byteLength, 16, 'view.byteLength should be 16');
-    return readPromise;
+    const byobRequest = byobRequests[0];
+    assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
+    assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
+    const viewInfo = byobRequest.viewInfo;
+    assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+    assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
+    assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+    assert_equals(viewInfo.byteLength, 16, 'first view.byteLength should be 16');
+
+    return p0;
   }).then(result => {
-    assert_not_equals(result.value, undefined);
-    assert_equals(result.value.constructor, Uint8Array);
-    assert_equals(result.value.buffer.byteLength, 16);
-    assert_equals(result.value.byteOffset, 0);
-    assert_equals(result.value.byteLength, 1);
-    assert_equals(result.value[0], 0x01);
-    assert_equals(pullCount, 1, 'pull() should only be invoked once');
+    assert_equals(pullCount, 2, 'pull() must have been invoked twice');
+    const value = result.value;
+    assert_not_equals(value, undefined, 'first read should have a value');
+    assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
+    assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
+    assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
+    assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
+    assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
+    const byobRequest = byobRequests[1];
+    assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
+    assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
+    const viewInfo = byobRequest.viewInfo;
+    assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+    assert_equals(viewInfo.bufferByteLength, 16, 'second view.buffer.byteLength should be 16');
+    assert_equals(viewInfo.byteOffset, 0, 'second view.byteOffset should be 0');
+    assert_equals(viewInfo.byteLength, 16, 'second view.byteLength should be 16');
+
+    return p1;
+  }).then(result => {
+    assert_equals(pullCount, 2, 'pull() should only be invoked twice');
+    const value = result.value;
+    assert_not_equals(value, undefined, 'second read should have a value');
+    assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
+    assert_equals(value.buffer.byteLength, 16, 'second value.buffer.byteLength should be 16');
+    assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
+    assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
+    assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
+    assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
   });
 }, 'ReadableStream with byte source: autoAllocateChunkSize');
 
@@ -386,12 +417,13 @@
 
   const reader = stream.getReader();
   return reader.read().then(result => {
-    assert_not_equals(result.value, undefined);
-    assert_equals(result.value.constructor, Uint8Array);
-    assert_equals(result.value.buffer.byteLength, 16);
-    assert_equals(result.value.byteOffset, 0);
-    assert_equals(result.value.byteLength, 1);
-    assert_equals(result.value[0], 0x01);
+    const value = result.value;
+    assert_not_equals(value, undefined, 'first read should have a value');
+    assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
+    assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
+    assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
+    assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
+    assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
     const byobRequest = byobRequests[0];
     assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
     assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
@@ -405,13 +437,14 @@
     const byobReader = stream.getReader({ mode: 'byob' });
     return byobReader.read(new Uint8Array(32));
   }).then(result => {
-    assert_not_equals(result.value, undefined);
-    assert_equals(result.value.constructor, Uint8Array);
-    assert_equals(result.value.buffer.byteLength, 32);
-    assert_equals(result.value.byteOffset, 0);
-    assert_equals(result.value.byteLength, 2);
-    assert_equals(result.value[0], 0x02);
-    assert_equals(result.value[1], 0x03);
+    const value = result.value;
+    assert_not_equals(value, undefined, 'second read should have a value');
+    assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
+    assert_equals(value.buffer.byteLength, 32, 'second value.buffer.byteLength should be 32');
+    assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
+    assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
+    assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
+    assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
     const byobRequest = byobRequests[1];
     assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
     assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
@@ -520,27 +553,12 @@
   return promise;
 }, 'ReadableStream with byte source: Push source that doesn\'t understand pull signal');
 
-promise_test(t => {
-  const stream = new ReadableStream({
+test(() => {
+  assert_throws(new TypeError(), () => new ReadableStream({
     pull: 'foo',
     type: 'bytes'
-  });
-
-  const reader = stream.getReader();
-
-  return promise_rejects(t, new TypeError(), reader.read(), 'read() must fail');
-}, 'ReadableStream with byte source: read(), but pull() function is not callable');
-
-promise_test(t => {
-  const stream = new ReadableStream({
-    pull: 'foo',
-    type: 'bytes'
-  });
-
-  const reader = stream.getReader({ mode: 'byob' });
-
-  return promise_rejects(t, new TypeError(), reader.read(new Uint8Array(1)), 'read() must fail');
-}, 'ReadableStream with byte source: read(view), but pull() function is not callable');
+  }), 'constructor should throw');
+}, 'ReadableStream with byte source: pull() function is not callable');
 
 promise_test(() => {
   const stream = new ReadableStream({
@@ -707,7 +725,7 @@
     },
     type: 'bytes'
   }, {
-    highWaterMark: 256
+    highWaterMark: 0
   });
 
   const reader = stream.getReader();
@@ -731,14 +749,63 @@
     assert_equals(result[2].done, false, 'result[2].done');
     assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
     assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
-    assert_equals(desiredSizes[0], 256, 'desiredSize on pull should be 256');
-    assert_equals(desiredSizes[1], 256, 'desiredSize after 1st enqueue() should be 256');
-    assert_equals(desiredSizes[2], 256, 'desiredSize after 2nd enqueue() should be 256');
+    assert_equals(desiredSizes[0], 0, 'desiredSize on pull should be 0');
+    assert_equals(desiredSizes[1], 0, 'desiredSize after 1st enqueue() should be 0');
+    assert_equals(desiredSizes[2], 0, 'desiredSize after 2nd enqueue() should be 0');
     assert_equals(pullCount, 1, 'pull() should only be called once');
   });
 }, 'ReadableStream with byte source: Respond to pull() by enqueue() asynchronously');
 
 promise_test(() => {
+  let pullCount = 0;
+
+  let byobRequest;
+  const desiredSizes = [];
+
+  const stream = new ReadableStream({
+    pull(c) {
+      byobRequest = c.byobRequest;
+      desiredSizes.push(c.desiredSize);
+
+      if (pullCount < 3) {
+        c.enqueue(new Uint8Array(1));
+      } else {
+        c.close();
+      }
+
+      ++pullCount;
+    },
+    type: 'bytes'
+  }, {
+    highWaterMark: 256
+  });
+
+  const reader = stream.getReader();
+
+  const p0 = reader.read();
+  const p1 = reader.read();
+  const p2 = reader.read();
+
+  assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+  return Promise.all([p0, p1, p2]).then(result => {
+    assert_equals(pullCount, 4, 'pullCount after completion of all read()s');
+
+    assert_equals(result[0].done, false, 'result[0].done');
+    assert_equals(result[0].value.byteLength, 1, 'result[0].value.byteLength');
+    assert_equals(result[1].done, false, 'result[1].done');
+    assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
+    assert_equals(result[2].done, false, 'result[2].done');
+    assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
+    assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
+    assert_equals(desiredSizes[0], 256, 'desiredSize on pull should be 256');
+    assert_equals(desiredSizes[1], 256, 'desiredSize after 1st enqueue() should be 256');
+    assert_equals(desiredSizes[2], 256, 'desiredSize after 2nd enqueue() should be 256');
+    assert_equals(desiredSizes[3], 256, 'desiredSize after 3rd enqueue() should be 256');
+  });
+}, 'ReadableStream with byte source: Respond to multiple pull() by separate enqueue()');
+
+promise_test(() => {
   let controller;
 
   let pullCount = 0;
@@ -1898,6 +1965,93 @@
    'errored in it');
 
 promise_test(() => {
+  let byobRequest;
+  const rs = new ReadableStream({
+    pull(controller) {
+      byobRequest = controller.byobRequest;
+      byobRequest.respond(4);
+    },
+    type: 'bytes'
+  });
+  const reader = rs.getReader({ mode: 'byob' });
+  const view = new Uint8Array(16);
+  return reader.read(view).then(() => {
+    assert_throws(new TypeError(), () => byobRequest.respond(4), 'respond() should throw a TypeError');
+  });
+}, 'calling respond() twice on the same byobRequest should throw');
+
+promise_test(() => {
+  let byobRequest;
+  const newView = () => new Uint8Array(16);
+  const rs = new ReadableStream({
+    pull(controller) {
+      byobRequest = controller.byobRequest;
+      byobRequest.respondWithNewView(newView());
+    },
+    type: 'bytes'
+  });
+  const reader = rs.getReader({ mode: 'byob' });
+  return reader.read(newView()).then(() => {
+    assert_throws(new TypeError(), () => byobRequest.respondWithNewView(newView()),
+                  'respondWithNewView() should throw a TypeError');
+  });
+}, 'calling respondWithNewView() twice on the same byobRequest should throw');
+
+promise_test(() => {
+  let byobRequest;
+  let resolvePullCalledPromise;
+  const pullCalledPromise = new Promise(resolve => {
+    resolvePullCalledPromise = resolve;
+  });
+  let resolvePull;
+  const rs = new ReadableStream({
+    pull(controller) {
+      byobRequest = controller.byobRequest;
+      resolvePullCalledPromise();
+      return new Promise(resolve => {
+        resolvePull = resolve;
+      });
+    },
+    type: 'bytes'
+  });
+  const reader = rs.getReader({ mode: 'byob' });
+  const readPromise = reader.read(new Uint8Array(16));
+  return pullCalledPromise.then(() => {
+    const cancelPromise = reader.cancel('meh');
+    resolvePull();
+    byobRequest.respond(0);
+    return Promise.all([readPromise, cancelPromise]).then(() => {
+      assert_throws(new TypeError(), () => byobRequest.respond(0), 'respond() should throw');
+    });
+  });
+}, 'calling respond(0) twice on the same byobRequest should throw even when closed');
+
+promise_test(() => {
+  let resolvePullCalledPromise;
+  const pullCalledPromise = new Promise(resolve => {
+    resolvePullCalledPromise = resolve;
+  });
+  let resolvePull;
+  const rs = new ReadableStream({
+    pull() {
+      resolvePullCalledPromise();
+      return new Promise(resolve => {
+        resolvePull = resolve;
+      });
+    },
+    type: 'bytes'
+  });
+  const reader = rs.getReader({ mode: 'byob' });
+  reader.read(new Uint8Array(16));
+  return pullCalledPromise.then(() => {
+    resolvePull();
+    return delay(0).then(() => {
+      assert_throws(new TypeError(), () => reader.releaseLock(), 'releaseLock() should throw');
+    });
+  });
+}, 'pull() resolving should not make releaseLock() possible');
+
+promise_test(() => {
   // Tests https://github.com/whatwg/streams/issues/686
 
   let controller;
diff --git a/streams/readable-byte-streams/properties.js b/streams/readable-byte-streams/properties.js
index 21549bc..975fba7 100644
--- a/streams/readable-byte-streams/properties.js
+++ b/streams/readable-byte-streams/properties.js
@@ -88,7 +88,7 @@
         assert_not_equals(viewPropDesc.get, undefined, 'view should have a getter');
         assert_equals(viewPropDesc.set, undefined, 'view should not have a setter');
         assert_not_equals(byobRequest.view, undefined, 'has a non-undefined view property');
-        assert_equals(byobRequest.constructor.length, 2, 'constructor has 1 parameter');
+        assert_equals(byobRequest.constructor.length, 0, 'constructor has 0 parameters');
         assert_equals(byobRequest.respond.length, 1, 'respond has 1 parameter');
         assert_equals(byobRequest.respondWithNewView.length, 1, 'releaseLock has 1 parameter');
 
@@ -138,7 +138,7 @@
   }
 
   assert_equals(controller.close.length, 0, 'cancel has no parameters');
-  assert_equals(controller.constructor.length, 3, 'constructor has 3 parameters');
+  assert_equals(controller.constructor.length, 0, 'constructor has no parameters');
   assert_equals(controller.enqueue.length, 1, 'enqueue has 1 parameter');
   assert_equals(controller.error.length, 1, 'releaseLock has 1 parameter');
 
diff --git a/streams/readable-streams/bad-underlying-sources.js b/streams/readable-streams/bad-underlying-sources.js
index 2f2dec4..6fce7b1 100644
--- a/streams/readable-streams/bad-underlying-sources.js
+++ b/streams/readable-streams/bad-underlying-sources.js
@@ -35,16 +35,14 @@
 }, 'Underlying source start: throwing method');
 
 
-promise_test(t => {
+test(() => {
 
   const theError = new Error('a unique string');
-  const rs = new ReadableStream({
+  assert_throws(theError, () => new ReadableStream({
     get pull() {
       throw theError;
     }
-  });
-
-  return promise_rejects(t, theError, rs.getReader().closed);
+  }), 'constructor should throw');
 
 }, 'Underlying source: throwing pull getter (initial pull)');
 
@@ -82,13 +80,15 @@
 
   return Promise.all([
     reader.read().then(r => {
-      assert_object_equals(r, { value: 'a', done: false }, 'the chunk read should be correct');
+      assert_object_equals(r, { value: 'a', done: false }, 'the first chunk read should be correct');
     }),
-    promise_rejects(t, theError, reader.closed)
+    reader.read().then(r => {
+      assert_object_equals(r, { value: 'a', done: false }, 'the second chunk read should be correct');
+      assert_equals(counter, 1, 'counter should be 1');
+    })
   ]);
 
-}, 'Underlying source pull: throwing getter (second pull)');
-
+}, 'Underlying source pull: throwing getter (second pull does not result in a second get)');
 
 promise_test(t => {
 
@@ -117,16 +117,14 @@
 
 }, 'Underlying source pull: throwing method (second pull)');
 
-promise_test(t => {
+test(() => {
 
   const theError = new Error('a unique string');
-  const rs = new ReadableStream({
+  assert_throws(theError, () => new ReadableStream({
     get cancel() {
       throw theError;
     }
-  });
-
-  return promise_rejects(t, theError, rs.cancel());
+  }), 'constructor should throw');
 
 }, 'Underlying source cancel: throwing getter');
 
@@ -308,7 +306,7 @@
   const closed = new ReadableStream({
     start(c) {
       c.error(theError);
-      assert_throws(new TypeError(), () => c.error(), 'second call to error should throw a TypeError');
+      c.error();
       startCalled = true;
     }
   }).getReader().closed;
@@ -318,7 +316,7 @@
     assert_equals(e, theError, 'closed should reject with the error');
   });
 
-}, 'Underlying source: calling error twice should throw the second time');
+}, 'Underlying source: calling error twice should not throw');
 
 promise_test(() => {
 
@@ -327,14 +325,14 @@
   const closed = new ReadableStream({
     start(c) {
       c.close();
-      assert_throws(new TypeError(), () => c.error(), 'second call to error should throw a TypeError');
+      c.error();
       startCalled = true;
     }
   }).getReader().closed;
 
   return closed.then(() => assert_true(startCalled));
 
-}, 'Underlying source: calling error after close should throw');
+}, 'Underlying source: calling error after close should not throw');
 
 promise_test(() => {
 
diff --git a/streams/readable-streams/constructor.dedicatedworker.html b/streams/readable-streams/constructor.dedicatedworker.html
new file mode 100644
index 0000000..aebe97e
--- /dev/null
+++ b/streams/readable-streams/constructor.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('constructor.js'));
+</script>
diff --git a/streams/readable-streams/constructor.html b/streams/readable-streams/constructor.html
new file mode 100644
index 0000000..a548e08
--- /dev/null
+++ b/streams/readable-streams/constructor.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/constructor-ordering.js"></script>
+
+<script src="constructor.js"></script>
diff --git a/streams/readable-streams/constructor.js b/streams/readable-streams/constructor.js
new file mode 100644
index 0000000..c202f3b
--- /dev/null
+++ b/streams/readable-streams/constructor.js
@@ -0,0 +1,42 @@
+'use strict';
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
+}
+
+const operations = [
+  op('get', 'size'),
+  op('get', 'highWaterMark'),
+  op('get', 'type'),
+  op('validate', 'type'),
+  op('validate', 'size'),
+  op('tonumber', 'highWaterMark'),
+  op('validate', 'highWaterMark'),
+  op('get', 'pull'),
+  op('validate', 'pull'),
+  op('get', 'cancel'),
+  op('validate', 'cancel'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const underlyingSource = createRecordingObjectWithProperties(record, ['type', 'start', 'pull', 'cancel']);
+    const strategy = createRecordingStrategy(record);
+
+    try {
+      new ReadableStream(underlyingSource, strategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `ReadableStream constructor should stop after ${failureOp} fails`);
+}
+
+done();
diff --git a/streams/readable-streams/constructor.serviceworker.https.html b/streams/readable-streams/constructor.serviceworker.https.html
new file mode 100644
index 0000000..ddddf06
--- /dev/null
+++ b/streams/readable-streams/constructor.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('constructor.js', 'Service worker test setup');
+</script>
diff --git a/streams/readable-streams/constructor.sharedworker.html b/streams/readable-streams/constructor.sharedworker.html
new file mode 100644
index 0000000..52b1a5d
--- /dev/null
+++ b/streams/readable-streams/constructor.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('constructor.js'));
+</script>
diff --git a/streams/readable-streams/default-reader.js b/streams/readable-streams/default-reader.js
index 7543545..9b645e2 100644
--- a/streams/readable-streams/default-reader.js
+++ b/streams/readable-streams/default-reader.js
@@ -485,4 +485,17 @@
 
 }, 'Reading twice on a stream that gets errored');
 
+test(() => {
+  const rs = new ReadableStream();
+  let toStringCalled = false;
+  const mode = {
+    toString() {
+      toStringCalled = true;
+      return '';
+    }
+  };
+  assert_throws(new RangeError(), () => rs.getReader({ mode }), 'getReader() should throw');
+  assert_true(toStringCalled, 'toString() should be called');
+}, 'getReader() should call ToString() on mode');
+
 done();
diff --git a/streams/readable-streams/garbage-collection.js b/streams/readable-streams/garbage-collection.js
index ebc2d76..2d16526 100644
--- a/streams/readable-streams/garbage-collection.js
+++ b/streams/readable-streams/garbage-collection.js
@@ -19,7 +19,7 @@
   return delay(50).then(() => {
     controller.close();
     assert_throws(new TypeError(), () => controller.close(), 'close should throw a TypeError the second time');
-    assert_throws(new TypeError(), () => controller.error(), 'error should throw a TypeError on a closed stream');
+    controller.error();
   });
 
 }, 'ReadableStreamController methods should continue working properly when scripts lose their reference to the ' +
diff --git a/streams/readable-streams/general.js b/streams/readable-streams/general.js
index 5150c36..05382d4 100644
--- a/streams/readable-streams/general.js
+++ b/streams/readable-streams/general.js
@@ -86,15 +86,15 @@
 
 test(() => {
 
-  new ReadableStream({ cancel: '2' });
+  assert_throws(new TypeError(), () => new ReadableStream({ cancel: '2' }), 'constructor should throw');
 
-}, 'ReadableStream constructor can get initial garbage as cancel argument');
+}, 'ReadableStream constructor will not tolerate initial garbage as cancel argument');
 
 test(() => {
 
-  new ReadableStream({ pull: { } });
+  assert_throws(new TypeError(), () => new ReadableStream({ pull: { } }), 'constructor should throw');
 
-}, 'ReadableStream constructor can get initial garbage as pull argument');
+}, 'ReadableStream constructor will not tolerate initial garbage as pull argument');
 
 test(() => {
 
@@ -129,7 +129,7 @@
       assert_true(desiredSizePropDesc.configurable, 'desiredSize should be configurable');
 
       assert_equals(controller.close.length, 0, 'close should have no parameters');
-      assert_equals(controller.constructor.length, 4, 'constructor should have 4 parameter');
+      assert_equals(controller.constructor.length, 0, 'constructor should have no parameters');
       assert_equals(controller.enqueue.length, 1, 'enqueue should have 1 parameter');
       assert_equals(controller.error.length, 1, 'error should have 1 parameter');
 
diff --git a/streams/readable-streams/patched-global.dedicatedworker.html b/streams/readable-streams/patched-global.dedicatedworker.html
new file mode 100644
index 0000000..327bc92
--- /dev/null
+++ b/streams/readable-streams/patched-global.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>patched-global.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('patched-global.js'));
+</script>
diff --git a/streams/readable-streams/patched-global.html b/streams/readable-streams/patched-global.html
new file mode 100644
index 0000000..869e910
--- /dev/null
+++ b/streams/readable-streams/patched-global.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>patched-global.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+
+
+<script src="patched-global.js"></script>
diff --git a/streams/readable-streams/patched-global.js b/streams/readable-streams/patched-global.js
new file mode 100644
index 0000000..e8117c4
--- /dev/null
+++ b/streams/readable-streams/patched-global.js
@@ -0,0 +1,67 @@
+'use strict';
+
+// Tests which patch the global environment are kept separate to avoid
+// interfering with other tests.
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+}
+
+const ReadableStream_prototype_locked_get =
+      Object.getOwnPropertyDescriptor(ReadableStream.prototype, 'locked').get;
+
+// Verify that |rs| passes the brand check as a readable stream.
+function isReadableStream(rs) {
+  try {
+    ReadableStream_prototype_locked_get.call(rs);
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+test(t => {
+  const rs = new ReadableStream();
+
+  const trappedProperties = ['highWaterMark', 'size', 'start', 'type', 'mode'];
+  for (const property of trappedProperties) {
+    // eslint-disable-next-line no-extend-native, accessor-pairs
+    Object.defineProperty(Object.prototype, property, {
+      get() { throw new Error(`${property} getter called`); },
+      configurable: true
+    });
+  }
+  t.add_cleanup(() => {
+    for (const property of trappedProperties) {
+      delete Object.prototype[property];
+    }
+  });
+
+  const [branch1, branch2] = rs.tee();
+  assert_true(isReadableStream(branch1), 'branch1 should be a ReadableStream');
+  assert_true(isReadableStream(branch2), 'branch2 should be a ReadableStream');
+}, 'ReadableStream tee() should not touch Object.prototype properties');
+
+test(t => {
+  const rs = new ReadableStream();
+
+  const oldReadableStream = self.ReadableStream;
+
+  /* eslint-disable no-native-reassign */
+  self.ReadableStream = function() {
+    throw new Error('ReadableStream called on global object');
+  };
+
+  t.add_cleanup(() => {
+    self.ReadableStream = oldReadableStream;
+  });
+
+  const [branch1, branch2] = rs.tee();
+
+  assert_true(isReadableStream(branch1), 'branch1 should be a ReadableStream');
+  assert_true(isReadableStream(branch2), 'branch2 should be a ReadableStream');
+
+  /* eslint-enable no-native-reassign */
+}, 'ReadableStream tee() should not call the global ReadableStream');
+
+done();
diff --git a/streams/readable-streams/patched-global.serviceworker.https.html b/streams/readable-streams/patched-global.serviceworker.https.html
new file mode 100644
index 0000000..addb438
--- /dev/null
+++ b/streams/readable-streams/patched-global.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>patched-global.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('patched-global.js', 'Service worker test setup');
+</script>
diff --git a/streams/readable-streams/patched-global.sharedworker.html b/streams/readable-streams/patched-global.sharedworker.html
new file mode 100644
index 0000000..6dba52f
--- /dev/null
+++ b/streams/readable-streams/patched-global.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>patched-global.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('patched-global.js'));
+</script>
diff --git a/streams/resources/constructor-ordering.js b/streams/resources/constructor-ordering.js
new file mode 100644
index 0000000..79862e0
--- /dev/null
+++ b/streams/resources/constructor-ordering.js
@@ -0,0 +1,129 @@
+'use strict';
+
+// Helpers for tests that constructors perform getting and validation of properties in the standard order.
+// See ../readable-streams/constructor.js for an example of how to use them.
+
+// Describes an operation on a property. |type| is "get", "validate" or "tonumber". |name| is the name of the property
+// in question. |side| is usually undefined, but is used by TransformStream to distinguish between the readable and
+// writable strategies.
+class Op {
+  constructor(type, name, side) {
+    this.type = type;
+    this.name = name;
+    this.side = side;
+  }
+
+  toString() {
+    return this.side === undefined ? `${this.type} on ${this.name}` : `${this.type} on ${this.name} (${this.side})`;
+  }
+
+  equals(otherOp) {
+    return this.type === otherOp.type && this.name === otherOp.name && this.side === otherOp.side;
+  }
+}
+
+// Provides a concise syntax to create an Op object. |side| is used by TransformStream to distinguish between the two
+// strategies.
+function op(type, name, side = undefined) {
+  return new Op(type, name, side);
+}
+
+// Records a sequence of operations. Also checks each operation against |failureOp| to see if it should fail.
+class OpRecorder {
+  constructor(failureOp) {
+    this.ops = [];
+    this.failureOp = failureOp;
+    this.matched = false;
+  }
+
+  // Record an operation. Returns true if this operation should fail.
+  recordAndCheck(type, name, side = undefined) {
+    const recordedOp = op(type, name, side);
+    this.ops.push(recordedOp);
+    return this.failureOp.equals(recordedOp);
+  }
+
+  // Returns true if validation of this property should fail.
+  check(name, side = undefined) {
+    return this.failureOp.equals(op('validate', name, side));
+  }
+
+  // Returns the sequence of recorded operations as a string.
+  actual() {
+    return this.ops.toString();
+  }
+}
+
+// Creates an object with the list of properties named in |properties|. Every property access will be recorded in
+// |record|, which will also be used to determine whether a particular property access should fail, or whether it should
+// return an invalid value that will fail validation.
+function createRecordingObjectWithProperties(record, properties) {
+  const recordingObject = {};
+  for (const property of properties) {
+    defineCheckedProperty(record, recordingObject, property, () => record.check(property) ? 'invalid' : undefined);
+  }
+  return recordingObject;
+}
+
+// Add a getter to |object| named |property| which throws if op('get', property) should fail, and otherwise calls
+// getter() to get the return value.
+function defineCheckedProperty(record, object, property, getter) {
+  Object.defineProperty(object, property, {
+    get() {
+      if (record.recordAndCheck('get', property)) {
+        throw new Error(`intentional failure of get ${property}`);
+      }
+      return getter();
+    }
+  });
+}
+
+// Similar to createRecordingObjectWithProperties(), but with specific functionality for "highWaterMark" so that numeric
+// conversion can be recorded. Permits |side| to be specified so that TransformStream can distinguish between its two
+// strategies.
+function createRecordingStrategy(record, side = undefined) {
+  return {
+    get size() {
+      if (record.recordAndCheck('get', 'size', side)) {
+        throw new Error(`intentional failure of get size`);
+      }
+      return record.check('size', side) ? 'invalid' : undefined;
+    },
+    get highWaterMark() {
+      if (record.recordAndCheck('get', 'highWaterMark', side)) {
+        throw new Error(`intentional failure of get highWaterMark`);
+      }
+      return createRecordingNumberObject(record, 'highWaterMark', side);
+    }
+  };
+}
+
+// Creates an object which will record when it is converted to a number. It will assert if the conversion is to some
+// other type, and will fail if op('tonumber', property, side) is set as the failure step. The object will convert to -1
+// if 'validate' is set as the failure step, and 1 otherwise.
+function createRecordingNumberObject(record, property, side = undefined) {
+  return {
+    [Symbol.toPrimitive](hint) {
+      assert_equals(hint, 'number', `hint for ${property} should be 'number'`);
+      if (record.recordAndCheck('tonumber', property, side)) {
+        throw new Error(`intentional failure of ${op('tonumber', property, side)}`);
+      }
+      return record.check(property, side) ? -1 : 1;
+    }
+  };
+}
+
+// Creates a string from everything in |operations| up to and including |failureOp|. "validate" steps are excluded from
+// the output, as we cannot record them except by making them fail.
+function expectedAsString(operations, failureOp) {
+  const expected = [];
+  for (const step of operations) {
+    if (step.type !== 'validate') {
+      expected.push(step);
+    }
+    if (step.equals(failureOp)) {
+      break;
+    }
+  }
+  return expected.toString();
+}
diff --git a/streams/transform-streams/constructor.dedicatedworker.html b/streams/transform-streams/constructor.dedicatedworker.html
new file mode 100644
index 0000000..aebe97e
--- /dev/null
+++ b/streams/transform-streams/constructor.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('constructor.js'));
+</script>
diff --git a/streams/transform-streams/constructor.html b/streams/transform-streams/constructor.html
new file mode 100644
index 0000000..a548e08
--- /dev/null
+++ b/streams/transform-streams/constructor.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/constructor-ordering.js"></script>
+
+<script src="constructor.js"></script>
diff --git a/streams/transform-streams/constructor.js b/streams/transform-streams/constructor.js
new file mode 100644
index 0000000..6277003
--- /dev/null
+++ b/streams/transform-streams/constructor.js
@@ -0,0 +1,51 @@
+'use strict';
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
+}
+
+const operations = [
+  op('get', 'size', 'writable'),
+  op('get', 'highWaterMark', 'writable'),
+  op('get', 'size', 'readable'),
+  op('get', 'highWaterMark', 'readable'),
+  op('get', 'writableType'),
+  op('validate', 'writableType'),
+  op('validate', 'size', 'writable'),
+  op('tonumber', 'highWaterMark', 'writable'),
+  op('validate', 'highWaterMark', 'writable'),
+  op('get', 'readableType'),
+  op('validate', 'readableType'),
+  op('validate', 'size', 'readable'),
+  op('tonumber', 'highWaterMark', 'readable'),
+  op('validate', 'highWaterMark', 'readable'),
+  op('get', 'transform'),
+  op('validate', 'transform'),
+  op('get', 'flush'),
+  op('validate', 'flush'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const transformer = createRecordingObjectWithProperties(
+        record, ['readableType', 'writableType', 'start', 'transform', 'flush']);
+    const writableStrategy = createRecordingStrategy(record, 'writable');
+    const readableStrategy = createRecordingStrategy(record, 'readable');
+
+    try {
+      new TransformStream(transformer, writableStrategy, readableStrategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `TransformStream constructor should stop after ${failureOp} fails`);
+}
+
+done();
diff --git a/streams/transform-streams/constructor.serviceworker.https.html b/streams/transform-streams/constructor.serviceworker.https.html
new file mode 100644
index 0000000..ddddf06
--- /dev/null
+++ b/streams/transform-streams/constructor.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('constructor.js', 'Service worker test setup');
+</script>
diff --git a/streams/transform-streams/constructor.sharedworker.html b/streams/transform-streams/constructor.sharedworker.html
new file mode 100644
index 0000000..52b1a5d
--- /dev/null
+++ b/streams/transform-streams/constructor.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('constructor.js'));
+</script>
diff --git a/streams/transform-streams/errors.js b/streams/transform-streams/errors.js
index 83f4984..fdb2154 100644
--- a/streams/transform-streams/errors.js
+++ b/streams/transform-streams/errors.js
@@ -205,7 +205,7 @@
   return Promise.all([
     abortPromise,
     cancelPromise,
-    promise_rejects(t, new TypeError(), writer.closed, 'writer.closed should reject with a TypeError')]);
+    promise_rejects(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]);
 }, 'abort should set the close reason for the writable when it happens before cancel during start, but cancel should ' +
              'still succeed');
 
@@ -229,7 +229,7 @@
       writePromise,
       abortPromise,
       cancelPromise,
-      promise_rejects(t, new TypeError(), writer.closed, 'writer.closed should reject with a TypeError')]);
+      promise_rejects(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]);
   });
 }, 'abort should set the close reason for the writable when it happens before cancel during underlying sink write, ' +
              'but cancel should still succeed');
@@ -269,9 +269,9 @@
       controller = c;
     }
   });
-  return ts.writable.abort().then(() => {
+  return ts.writable.abort(thrownError).then(() => {
     controller.error(ignoredError);
-    return promise_rejects(t, new TypeError(), ts.writable.getWriter().closed, 'closed should reject with a TypeError');
+    return promise_rejects(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError');
   });
 }, 'controller.error() should do nothing after writable.abort() has completed');
 
@@ -325,15 +325,22 @@
     // write should start synchronously
     const writePromise = writer.write(0);
     // The underlying sink's abort() is not called until the write() completes.
-    const abortPromise = writer.abort();
+    const abortPromise = writer.abort(thrownError);
     // Perform a read to relieve backpressure and permit the write() to complete.
     const readPromise = ts.readable.getReader().read();
     return Promise.all([
-      promise_rejects(t, new TypeError(), readPromise, 'read() should reject'),
-      promise_rejects(t, new TypeError(), writePromise, 'write() should reject'),
+      promise_rejects(t, thrownError, readPromise, 'read() should reject'),
+      promise_rejects(t, thrownError, writePromise, 'write() should reject'),
       abortPromise
     ]);
   });
 }, 'a write() that was waiting for backpressure should reject if the writable is aborted');
 
+promise_test(t => {
+  const ts = new TransformStream();
+  ts.writable.abort(thrownError);
+  const reader = ts.readable.getReader();
+  return promise_rejects(t, thrownError, reader.read(), 'read() should reject with thrownError');
+}, 'the readable should be errored with the reason passed to the writable abort() method');
+
 done();
diff --git a/streams/transform-streams/properties.dedicatedworker.html b/streams/transform-streams/properties.dedicatedworker.html
new file mode 100644
index 0000000..50d63ed
--- /dev/null
+++ b/streams/transform-streams/properties.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('properties.js'));
+</script>
diff --git a/streams/transform-streams/properties.html b/streams/transform-streams/properties.html
new file mode 100644
index 0000000..ad410a5
--- /dev/null
+++ b/streams/transform-streams/properties.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+
+
+<script src="properties.js"></script>
diff --git a/streams/transform-streams/properties.js b/streams/transform-streams/properties.js
new file mode 100644
index 0000000..f8d8fac
--- /dev/null
+++ b/streams/transform-streams/properties.js
@@ -0,0 +1,194 @@
+'use strict';
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+}
+
+// The purpose of this file is to test for objects, attributes and arguments that should not exist.
+// The test cases are generated from data tables to reduce duplication.
+
+// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11.
+function IsConstructor(o) {
+  try {
+    new new Proxy(o, { construct: () => ({}) })();
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+test(() => {
+  assert_equals(self['TransformStreamDefaultController'], undefined,
+                `TransformStreamDefaultController should not be defined`);
+}, `TransformStreamDefaultController should not be exported on the global object`);
+
+// Now get hold of the symbol so we can test its properties.
+self.TransformStreamDefaultController = (() => {
+  let controller;
+  new TransformStream({
+    start(c) {
+      controller = c;
+    }
+  });
+  return controller.constructor;
+})();
+
+const expected = {
+  TransformStream: {
+    constructor: {
+      type: 'constructor',
+      length: 0
+    },
+    readable: {
+      type: 'getter'
+    },
+    writable: {
+      type: 'getter'
+    }
+  },
+  TransformStreamDefaultController: {
+    constructor: {
+      type: 'constructor',
+      length: 0
+    },
+    desiredSize: {
+      type: 'getter'
+    },
+    enqueue: {
+      type: 'method',
+      length: 1
+    },
+    error: {
+      type: 'method',
+      length: 1
+    },
+    terminate: {
+      type: 'method',
+      length: 0
+    }
+  }
+};
+
+for (const c in expected) {
+  const properties = expected[c];
+  const prototype = self[c].prototype;
+  for (const name in properties) {
+    const fullName = `${c}.prototype.${name}`;
+    const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
+    test(() => {
+      const { configurable, enumerable } = descriptor;
+      assert_true(configurable, `${name} should be configurable`);
+      assert_false(enumerable, `${name} should not be enumerable`);
+    }, `${fullName} should have standard properties`);
+    const type = properties[name].type;
+    switch (type) {
+      case 'getter':
+        test(() => {
+          const { writable, get, set } = descriptor;
+          assert_equals(writable, undefined, `${name} should not be a data descriptor`);
+          assert_equals(typeof get, 'function', `${name} should have a getter`);
+          assert_equals(set, undefined, `${name} should not have a setter`);
+        }, `${fullName} should be a getter`);
+        break;
+
+      case 'constructor':
+      case 'method':
+        test(() => {
+          assert_true(descriptor.writable, `${name} should be writable`);
+          assert_equals(typeof prototype[name], 'function', `${name} should be a function`);
+          assert_equals(prototype[name].length, properties[name].length,
+                        `${name} should take ${properties[name].length} arguments`);
+          if (type === 'constructor') {
+            assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`);
+            assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`);
+          } else {
+            assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`);
+            assert_equals(prototype[name].name, name, `${name}.name should be '${name}`);
+          }
+        }, `${fullName} should be a ${type}`);
+        break;
+    }
+  }
+  test(() => {
+    const expectedPropertyNames = Object.keys(properties).sort();
+    const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort();
+    assert_array_equals(actualPropertyNames, expectedPropertyNames,
+                        `${c} properties should match expected properties`);
+  }, `${c}.prototype should have exactly the expected properties`);
+}
+
+const transformerMethods = {
+  start: {
+    length: 1,
+    trigger: () => Promise.resolve()
+  },
+  transform: {
+    length: 2,
+    trigger: ts => ts.writable.getWriter().write()
+  },
+  flush: {
+    length: 1,
+    trigger: ts => ts.writable.getWriter().close()
+  }
+};
+
+for (const method in transformerMethods) {
+  const { length, trigger } = transformerMethods[method];
+
+  // Some semantic tests of how transformer methods are called can be found in general.js, as well as in the test files
+  // specific to each method.
+  promise_test(() => {
+    let argCount;
+    const ts = new TransformStream({
+      [method](...args) {
+        argCount = args.length;
+      }
+    }, undefined, { highWaterMark: Infinity });
+    return Promise.resolve(trigger(ts)).then(() => {
+      assert_equals(argCount, length, `${method} should be called with ${length} arguments`);
+    });
+  }, `transformer method ${method} should be called with the right number of arguments`);
+
+  promise_test(() => {
+    let methodWasCalled = false;
+    function Transformer() {}
+    Transformer.prototype = {
+      [method]() {
+        methodWasCalled = true;
+      }
+    };
+    const ts = new TransformStream(new Transformer(), undefined, { highWaterMark: Infinity });
+    return Promise.resolve(trigger(ts)).then(() => {
+      assert_true(methodWasCalled, `${method} should be called`);
+    });
+  }, `transformer method ${method} should be called even when it's located on the prototype chain`);
+
+  promise_test(t => {
+    const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
+                            'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
+                            'apply', 'construct'];
+    const touchedProperties = [];
+    const handler = {
+      get: t.step_func((target, property) => {
+        touchedProperties.push(property);
+        if (property === 'readableType' || property === 'writableType') {
+          return undefined;
+        }
+        return () => Promise.resolve();
+      })
+    };
+    for (const trap of unreachedTraps) {
+      handler[trap] = t.unreached_func(`${trap} should not be trapped`);
+    }
+    const transformer = new Proxy({}, handler);
+    const ts = new TransformStream(transformer, undefined, { highWaterMark: Infinity });
+    assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'],
+                        'expected properties should be got');
+    return trigger(ts).then(() => {
+      assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'],
+                          'no properties should be accessed on method call');
+    });
+  }, `unexpected properties should not be accessed when calling transformer method ${method}`);
+}
+
+done();
diff --git a/streams/transform-streams/properties.serviceworker.https.html b/streams/transform-streams/properties.serviceworker.https.html
new file mode 100644
index 0000000..ba5c513
--- /dev/null
+++ b/streams/transform-streams/properties.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('properties.js', 'Service worker test setup');
+</script>
diff --git a/streams/transform-streams/properties.sharedworker.html b/streams/transform-streams/properties.sharedworker.html
new file mode 100644
index 0000000..42fb3e5
--- /dev/null
+++ b/streams/transform-streams/properties.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('properties.js'));
+</script>
diff --git a/streams/transform-streams/reentrant-strategies.js b/streams/transform-streams/reentrant-strategies.js
index 436552b..50285bb 100644
--- a/streams/transform-streams/reentrant-strategies.js
+++ b/streams/transform-streams/reentrant-strategies.js
@@ -317,7 +317,7 @@
   // call to TransformStreamDefaultSink.
   return delay(0).then(() => {
     controller.enqueue('a');
-    return Promise.all([promise_rejects(t, new TypeError(), reader.read(), 'read() should reject'), abortPromise]);
+    return Promise.all([promise_rejects(t, error1, reader.read(), 'read() should reject'), abortPromise]);
   });
 }, 'writer.abort() inside size() should work');
 
diff --git a/streams/transform-streams/strategies.js b/streams/transform-streams/strategies.js
index 60481a9..1775b7f 100644
--- a/streams/transform-streams/strategies.js
+++ b/streams/transform-streams/strategies.js
@@ -67,7 +67,6 @@
   const ts = new TransformStream();
   const writer = ts.writable.getWriter();
   assert_equals(writer.desiredSize, 1, 'default writable HWM is 1');
-  // There should be no size function, but a size function that always returns 1 is indistinguishable.
   writer.write(undefined);
   assert_equals(writer.desiredSize, 0, 'default chunk size is 1');
 }, 'default writable strategy should be equivalent to { highWaterMark: 1 }');
@@ -89,4 +88,68 @@
   return ts.readable.getReader().read().then(() => writePromise);
 }, 'default readable strategy should be equivalent to { highWaterMark: 0 }');
 
+test(() => {
+  assert_throws(new RangeError(), () => new TransformStream(undefined, { highWaterMark: -1 }),
+                'should throw RangeError for negative writableHighWaterMark');
+  assert_throws(new RangeError(), () => new TransformStream(undefined, undefined, { highWaterMark: -1 }),
+                'should throw RangeError for negative readableHighWaterMark');
+  assert_throws(new RangeError(), () => new TransformStream(undefined, { highWaterMark: NaN }),
+                'should throw RangeError for NaN writableHighWaterMark');
+  assert_throws(new RangeError(), () => new TransformStream(undefined, undefined, { highWaterMark: NaN }),
+                'should throw RangeError for NaN readableHighWaterMark');
+}, 'a RangeError should be thrown for an invalid highWaterMark');
+
+const objectThatConvertsTo42 = {
+  toString() {
+    return '42';
+  }
+};
+
+test(() => {
+  const ts = new TransformStream(undefined, { highWaterMark: objectThatConvertsTo42 });
+  const writer = ts.writable.getWriter();
+  assert_equals(writer.desiredSize, 42, 'writable HWM is 42');
+}, 'writableStrategy highWaterMark should be converted to a number');
+
+test(() => {
+  const ts = new TransformStream({
+    start(controller) {
+      assert_equals(controller.desiredSize, 42, 'desiredSize should be 42');
+    }
+  }, undefined, { highWaterMark: objectThatConvertsTo42 });
+}, 'readableStrategy highWaterMark should be converted to a number');
+
+promise_test(t => {
+  const ts = new TransformStream(undefined, undefined, {
+    size() { return NaN; },
+    highWaterMark: 1
+  });
+  const writer = ts.writable.getWriter();
+  return promise_rejects(t, new RangeError(), writer.write(), 'write should reject');
+}, 'a bad readableStrategy size function should cause writer.write() to reject on an identity transform');
+
+promise_test(t => {
+  const ts = new TransformStream({
+    transform(chunk, controller) {
+      // This assert has the important side-effect of catching the error, so transform() does not throw.
+      assert_throws(new RangeError(), () => controller.enqueue(chunk), 'enqueue should throw');
+    }
+  }, undefined, {
+    size() {
+      return -1;
+    },
+    highWaterMark: 1
+  });
+
+  const writer = ts.writable.getWriter();
+  return writer.write().then(() => {
+    return Promise.all([
+      promise_rejects(t, new RangeError(), writer.ready, 'ready should reject'),
+      promise_rejects(t, new RangeError(), writer.closed, 'closed should reject'),
+      promise_rejects(t, new RangeError(), ts.readable.getReader().closed, 'readable closed should reject')
+    ]);
+  });
+}, 'a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() ' +
+   'catches the exception');
+
 done();
diff --git a/streams/writable-streams/aborting.js b/streams/writable-streams/aborting.js
index a04befd..9fb175f 100644
--- a/streams/writable-streams/aborting.js
+++ b/streams/writable-streams/aborting.js
@@ -27,8 +27,8 @@
   assert_equals(writer.ready, readyPromise, 'the ready promise property should not change');
 
   return Promise.all([
-    promise_rejects(t, new TypeError(), readyPromise, 'the ready promise should reject with a TypeError'),
-    promise_rejects(t, new TypeError(), writePromise, 'the write() promise should reject with a TypeError')
+    promise_rejects(t, error1, readyPromise, 'the ready promise should reject with error1'),
+    promise_rejects(t, error1, writePromise, 'the write() promise should reject with error1')
   ]);
 }, 'Aborting a WritableStream before it starts should cause the writer\'s unsettled ready promise to reject');
 
@@ -44,7 +44,7 @@
     writer.abort(error1);
 
     assert_not_equals(writer.ready, readyPromise, 'the ready promise property should change');
-    return promise_rejects(t, new TypeError(), writer.ready, 'the ready promise should reject with a TypeError');
+    return promise_rejects(t, error1, writer.ready, 'the ready promise should reject with error1');
   });
 }, 'Aborting a WritableStream should cause the writer\'s fulfilled ready promise to reset to a rejected one');
 
@@ -64,16 +64,16 @@
     .then(() => {
       const writer = ws.getWriter();
 
-      const abortPromise = writer.abort();
+      const abortPromise = writer.abort(error1);
 
       return Promise.all([
-        promise_rejects(t, new TypeError(), writer.write(1), 'write(1) must reject with a TypeError'),
-        promise_rejects(t, new TypeError(), writer.write(2), 'write(2) must reject with a TypeError'),
+        promise_rejects(t, error1, writer.write(1), 'write(1) must reject with error1'),
+        promise_rejects(t, error1, writer.write(2), 'write(2) must reject with error1'),
         abortPromise
       ]);
     })
     .then(() => {
-      assert_array_equals(ws.events, ['abort', undefined]);
+      assert_array_equals(ws.events, ['abort', error1]);
     });
 }, 'Aborting a WritableStream immediately prevents future writes');
 
@@ -87,20 +87,20 @@
 
       results.push(
         writer.write(1),
-        promise_rejects(t, new TypeError(), writer.write(2), 'write(2) must reject with a TypeError'),
-        promise_rejects(t, new TypeError(), writer.write(3), 'write(3) must reject with a TypeError')
+        promise_rejects(t, error1, writer.write(2), 'write(2) must reject with error1'),
+        promise_rejects(t, error1, writer.write(3), 'write(3) must reject with error1')
       );
 
-      const abortPromise = writer.abort();
+      const abortPromise = writer.abort(error1);
 
       results.push(
-        promise_rejects(t, new TypeError(), writer.write(4), 'write(4) must reject with a TypeError'),
-        promise_rejects(t, new TypeError(), writer.write(5), 'write(5) must reject with a TypeError')
+        promise_rejects(t, error1, writer.write(4), 'write(4) must reject with error1'),
+        promise_rejects(t, error1, writer.write(5), 'write(5) must reject with error1')
       );
 
       return abortPromise;
     }).then(() => {
-      assert_array_equals(ws.events, ['write', 1, 'abort', undefined]);
+      assert_array_equals(ws.events, ['write', 1, 'abort', error1]);
 
       return Promise.all(results);
     });
@@ -137,6 +137,22 @@
       throw error1;
     }
   });
+  const writer = ws.getWriter();
+
+  const abortPromise1 = writer.abort(undefined);
+  const abortPromise2 = writer.abort(undefined);
+
+  assert_equals(abortPromise1, abortPromise2, 'the promises must be the same');
+
+  return promise_rejects(t, error1, abortPromise1, 'promise must have matching rejection');
+}, 'WritableStream if sink\'s abort throws, the promise returned by multiple writer.abort()s is the same and rejects');
+
+promise_test(t => {
+  const ws = new WritableStream({
+    abort() {
+      throw error1;
+    }
+  });
 
   return promise_rejects(t, error1, ws.abort(undefined),
     'rejection reason of abortPromise must be the error thrown by abort');
@@ -193,27 +209,26 @@
 
   return Promise.all([
     abortPromise,
-    promise_rejects(t, new TypeError(), writer.write(), 'writing should reject with a TypeError'),
-    promise_rejects(t, new TypeError(), writer.close(), 'closing should reject with a TypeError'),
-    promise_rejects(t, new TypeError(), writer.abort(), 'aborting should reject with a TypeError'),
-    promise_rejects(t, new TypeError(), writer.ready, 'ready should reject with a TypeError'),
-    promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError')
+    promise_rejects(t, error1, writer.write(), 'writing should reject with error1'),
+    promise_rejects(t, error1, writer.close(), 'closing should reject with error1'),
+    promise_rejects(t, error1, writer.ready, 'ready should reject with error1'),
+    promise_rejects(t, error1, writer.closed, 'closed should reject with error1')
   ]).then(() => {
     assert_array_equals(['ready', 'closed'], events, 'ready should reject before closed');
   });
-}, 'Aborting a WritableStream puts it in an errored state, with a TypeError as the stored error');
+}, 'Aborting a WritableStream puts it in an errored state with the error passed to abort()');
 
 promise_test(t => {
   const ws = new WritableStream();
   const writer = ws.getWriter();
 
-  const writePromise = promise_rejects(t, new TypeError(), writer.write('a'),
-    'writing should reject with a TypeError');
+  const writePromise = promise_rejects(t, error1, writer.write('a'),
+    'writing should reject with error1');
 
   writer.abort(error1);
 
   return writePromise;
-}, 'Aborting a WritableStream causes any outstanding write() promises to be rejected with a TypeError');
+}, 'Aborting a WritableStream causes any outstanding write() promises to be rejected with the reason supplied');
 
 promise_test(t => {
   const ws = recordingWritableStream();
@@ -223,8 +238,8 @@
   const abortPromise = writer.abort(error1);
 
   return Promise.all([
-    promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError'),
-    promise_rejects(t, new TypeError(), closePromise, 'close() should reject with a TypeError'),
+    promise_rejects(t, error1, writer.closed, 'closed should reject with error1'),
+    promise_rejects(t, error1, closePromise, 'close() should reject with error1'),
     abortPromise
   ]).then(() => {
     assert_array_equals(ws.events, ['abort', error1]);
@@ -278,9 +293,9 @@
 
   const writer = ws.getWriter();
 
-  writer.abort();
+  writer.abort(error1);
 
-  return promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError').then(() => {
+  return promise_rejects(t, error1, writer.closed, 'closed should reject with error1').then(() => {
     assert_false(closeCalled, 'close must not have been called');
   });
 }, 'WritableStream should NOT call underlying sink\'s close if no abort is supplied (historical)');
@@ -314,7 +329,7 @@
     let closedRejected = false;
     return Promise.all([
       writePromise.then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
-      promise_rejects(t, new TypeError(), writer.closed, '.closed should reject').then(() => {
+      promise_rejects(t, error1, writer.closed, '.closed should reject').then(() => {
         closedRejected = true;
       })
     ]);
@@ -335,7 +350,7 @@
     return Promise.all([
       promise_rejects(t, error1, writePromise, 'write() should reject')
           .then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
-      promise_rejects(t, new TypeError(), writer.closed, '.closed should reject')
+      promise_rejects(t, error2, writer.closed, '.closed should reject')
           .then(() => {
             closedRejected = true;
           }),
@@ -355,9 +370,9 @@
     const settlementOrder = [];
     return Promise.all([
       writer.write('1').then(() => settlementOrder.push(1)),
-      promise_rejects(t, new TypeError(), writer.write('2'), 'first queued write should be rejected')
+      promise_rejects(t, error1, writer.write('2'), 'first queued write should be rejected')
           .then(() => settlementOrder.push(2)),
-      promise_rejects(t, new TypeError(), writer.write('3'), 'second queued write should be rejected')
+      promise_rejects(t, error1, writer.write('3'), 'second queued write should be rejected')
           .then(() => settlementOrder.push(3)),
       writer.abort(error1)
     ]).then(() => assert_array_equals([1, 2, 3], settlementOrder, 'writes should be satisfied in order'));
@@ -376,9 +391,9 @@
     return Promise.all([
       promise_rejects(t, error1, writer.write('1'), 'in-flight write should be rejected')
           .then(() => settlementOrder.push(1)),
-      promise_rejects(t, new TypeError(), writer.write('2'), 'first queued write should be rejected')
+      promise_rejects(t, error2, writer.write('2'), 'first queued write should be rejected')
           .then(() => settlementOrder.push(2)),
-      promise_rejects(t, new TypeError(), writer.write('3'), 'second queued write should be rejected')
+      promise_rejects(t, error2, writer.write('3'), 'second queued write should be rejected')
           .then(() => settlementOrder.push(3)),
       writer.abort(error2)
     ]).then(() => assert_array_equals([1, 2, 3], settlementOrder, 'writes should be satisfied in order'));
@@ -395,12 +410,12 @@
   return writer.ready.then(() => {
     return Promise.all([
       promise_rejects(t, error1, writer.write('a'), 'writer.write() should reject with error from underlying write()'),
-      promise_rejects(t, new TypeError(), writer.close(),
+      promise_rejects(t, error2, writer.close(),
                       'writer.close() should reject with error from underlying write()'),
-      writer.abort()
+      writer.abort(error2)
     ]);
   });
-}, 'close() should reject with TypeError when abort() is first error');
+}, 'close() should reject with abort reason why abort() is first error');
 
 promise_test(() => {
   let resolveWrite;
@@ -495,14 +510,14 @@
   return writer.ready.then(() => {
     writer.write('a');
     const closePromise = writer.close();
-    const abortPromise = writer.abort('b');
+    const abortPromise = writer.abort(error1);
 
     return flushAsyncEvents().then(() => {
       assert_array_equals(ws.events, ['write', 'a'], 'abort should not be called while write is in-flight');
       resolveWrite();
       return abortPromise.then(() => {
-        assert_array_equals(ws.events, ['write', 'a', 'abort', 'b'], 'abort should be called after write completes');
-        return promise_rejects(t, new TypeError(), closePromise, 'promise returned by close() should be rejected');
+        assert_array_equals(ws.events, ['write', 'a', 'abort', error1], 'abort should be called after write completes');
+        return promise_rejects(t, error1, closePromise, 'promise returned by close() should be rejected');
       });
     });
   });
@@ -518,13 +533,13 @@
   const writer = ws.getWriter();
   return writer.ready.then(() => {
     writer.write('a');
-    writer.abort();
+    writer.abort(error1);
     writer.releaseLock();
     const writer2 = ws.getWriter();
-    return promise_rejects(t, new TypeError(), writer2.ready,
-                           'ready of the second writer should be rejected with a TypeError');
+    return promise_rejects(t, error1, writer2.ready,
+                           'ready of the second writer should be rejected with error1');
   });
-}, 'if a writer is created for a stream with a pending abort, its ready should be rejected with a TypeError');
+}, 'if a writer is created for a stream with a pending abort, its ready should be rejected with the abort error');
 
 promise_test(() => {
   const ws = new WritableStream();
@@ -592,8 +607,8 @@
     const writePromise2 = writer.write('a');
 
     return Promise.all([
-      promise_rejects(t, new TypeError(), writePromise2, 'writePromise2 must reject with an error indicating abort'),
-      promise_rejects(t, new TypeError(), writer.ready, 'writer.ready must reject with an error indicating abort'),
+      promise_rejects(t, error1, writePromise2, 'writePromise2 must reject with the error from abort'),
+      promise_rejects(t, error1, writer.ready, 'writer.ready must reject with the error from abort'),
       flushAsyncEvents()
     ]);
   }).then(() => {
@@ -605,8 +620,8 @@
       promise_rejects(t, error2, writePromise,
                       'writePromise must reject with the error returned from the sink\'s write method'),
       abortPromise,
-      promise_rejects(t, new TypeError(), writer.closed,
-                      'writer.closed must reject with an error indicating abort'),
+      promise_rejects(t, error1, writer.closed,
+                      'writer.closed must reject with the error from abort'),
       flushAsyncEvents()
     ]);
   }).then(() => {
@@ -616,9 +631,9 @@
     const writePromise3 = writer.write('a');
 
     return Promise.all([
-      promise_rejects(t, new TypeError(), writePromise3,
-                      'writePromise3 must reject with an error indicating abort'),
-      promise_rejects(t, new TypeError(), writer.ready,
+      promise_rejects(t, error1, writePromise3,
+                      'writePromise3 must reject with the error from abort'),
+      promise_rejects(t, error1, writer.ready,
                       'writer.ready must be still rejected with the error indicating abort')
     ]);
   }).then(() => {
@@ -671,8 +686,8 @@
     const writePromise2 = writer.write('a');
 
     return Promise.all([
-      promise_rejects(t, new TypeError(), writePromise2, 'writePromise2 must reject with an error indicating abort'),
-      promise_rejects(t, new TypeError(), writer.ready, 'writer.ready must reject with an error indicating abort'),
+      promise_rejects(t, error1, writePromise2, 'writePromise2 must reject with the error from abort'),
+      promise_rejects(t, error1, writer.ready, 'writer.ready must reject with the error from abort'),
       flushAsyncEvents()
     ]);
   }).then(() => {
@@ -684,9 +699,9 @@
     const writePromise3 = writer.write('a');
 
     return Promise.all([
-      promise_rejects(t, new TypeError(), writePromise3,
-                      'writePromise3 must reject with an error indicating abort'),
-      promise_rejects(t, new TypeError(), writer.ready,
+      promise_rejects(t, error1, writePromise3,
+                      'writePromise3 must reject with the error from abort'),
+      promise_rejects(t, error1, writer.ready,
                       'writer.ready must be still rejected with the error indicating abort'),
       flushAsyncEvents()
     ]);
@@ -701,8 +716,8 @@
     return Promise.all([
       writePromise,
       abortPromise,
-      promise_rejects(t, new TypeError(), writer.closed,
-                      'writer.closed must reject with an error indicating abort'),
+      promise_rejects(t, error1, writer.closed,
+                      'writer.closed must reject with the error from abort'),
       flushAsyncEvents()
     ]);
   }).then(() => {
@@ -713,9 +728,9 @@
 
     return Promise.all([
       writePromise,
-      promise_rejects(t, new TypeError(), writePromise4,
-                      'writePromise4 must reject with an error indicating abort'),
-      promise_rejects(t, new TypeError(), writer.ready,
+      promise_rejects(t, error1, writePromise4,
+                      'writePromise4 must reject with the error from abort'),
+      promise_rejects(t, error1, writer.ready,
                       'writer.ready must be still rejected with the error indicating abort')
     ]);
   }).then(() => {
@@ -770,7 +785,7 @@
     return Promise.all([
       promise_rejects(t, new TypeError(), writer.close(),
         'writer.close() must reject with an error indicating already closing'),
-      promise_rejects(t, new TypeError(), writer.ready, 'writer.ready must reject with an error indicating abort'),
+      promise_rejects(t, error1, writer.ready, 'writer.ready must reject with the error from abort'),
       flushAsyncEvents()
     ]);
   }).then(() => {
@@ -781,7 +796,7 @@
     return Promise.all([
       promise_rejects(t, new TypeError(), writer.close(),
         'writer.close() must reject with an error indicating already closing'),
-      promise_rejects(t, new TypeError(), writer.ready,
+      promise_rejects(t, error1, writer.ready,
                       'writer.ready must be still rejected with the error indicating abort'),
       flushAsyncEvents()
     ]);
@@ -806,7 +821,7 @@
     return Promise.all([
       promise_rejects(t, new TypeError(), writer.close(),
         'writer.close() must reject with an error indicating already closing'),
-      promise_rejects(t, new TypeError(), writer.ready,
+      promise_rejects(t, error1, writer.ready,
                       'writer.ready must be still rejected with the error indicating abort')
     ]);
   }).then(() => {
@@ -1110,11 +1125,11 @@
   const ws = new WritableStream();
   const writer = ws.getWriter();
   const writerReady1 = writer.ready;
-  writer.abort('a');
+  writer.abort(error1);
   const writerReady2 = writer.ready;
   assert_not_equals(writerReady1, writerReady2, 'abort() should replace the ready promise with a rejected one');
   return Promise.all([writerReady1,
-                      promise_rejects(t, new TypeError(), writerReady2, 'writerReady2 should reject')]);
+                      promise_rejects(t, error1, writerReady2, 'writerReady2 should reject')]);
 }, 'writer abort() during sink start() should replace the writer.ready promise synchronously');
 
 promise_test(t => {
@@ -1122,7 +1137,7 @@
   const ws = recordingWritableStream();
   const writer = ws.getWriter();
   const writePromise1 = writer.write(1);
-  const abortPromise = writer.abort('a');
+  const abortPromise = writer.abort(error1);
   const writePromise2 = writer.write(2);
   const closePromise = writer.close();
   writePromise1.catch(() => events.push('write1'));
@@ -1130,15 +1145,15 @@
   writePromise2.catch(() => events.push('write2'));
   closePromise.catch(() => events.push('close'));
   return Promise.all([
-    promise_rejects(t, new TypeError(), writePromise1, 'first write() should reject'),
+    promise_rejects(t, error1, writePromise1, 'first write() should reject'),
     abortPromise,
-    promise_rejects(t, new TypeError(), writePromise2, 'second write() should reject'),
-    promise_rejects(t, new TypeError(), closePromise, 'close() should reject')
+    promise_rejects(t, error1, writePromise2, 'second write() should reject'),
+    promise_rejects(t, error1, closePromise, 'close() should reject')
   ])
   .then(() => {
     assert_array_equals(events, ['write2', 'write1', 'abort', 'close'],
                         'promises should resolve in the standard order');
-    assert_array_equals(ws.events, ['abort', 'a'], 'underlying sink write() should not be called');
+    assert_array_equals(ws.events, ['abort', error1], 'underlying sink write() should not be called');
   });
 }, 'promises returned from other writer methods should be rejected when writer abort() happens during sink start()');
 
@@ -1209,7 +1224,7 @@
     return Promise.all([
       promise_rejects(t, error1, writePromise, 'write should reject'),
       abortPromise,
-      promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with TypeError')
+      promise_rejects(t, error2, writer.closed, 'closed should reject with error2')
     ]);
   }).then(() => {
     assert_array_equals(ws.events, ['write', '1', 'abort', error2], 'abort sink method should be called');
@@ -1228,15 +1243,47 @@
       });
 }, 'when start errors after stream abort(), underlying sink abort() should be called anyway');
 
-promise_test(t => {
+promise_test(() => {
   const ws = new WritableStream();
   const abortPromise1 = ws.abort();
   const abortPromise2 = ws.abort();
-  return Promise.all([
-    abortPromise1,
-    promise_rejects(t, new TypeError(), abortPromise2, 'second abort() should reject')
-  ]);
-}, 'when calling abort() twice on the same stream, the second call should reject');
+  assert_equals(abortPromise1, abortPromise2, 'the promises must be the same');
+
+  return abortPromise1.then(
+      v => assert_equals(v, undefined, 'abort() should fulfill with undefined'));
+}, 'when calling abort() twice on the same stream, both should give the same promise that fulfills with undefined');
+
+promise_test(() => {
+  const ws = new WritableStream();
+  const abortPromise1 = ws.abort();
+
+  return abortPromise1.then(v1 => {
+    assert_equals(v1, undefined, 'first abort() should fulfill with undefined');
+
+    const abortPromise2 = ws.abort();
+    assert_not_equals(abortPromise2, abortPromise1, 'because we waited, the second promise should be a new promise');
+
+    return abortPromise2.then(v2 => {
+      assert_equals(v2, undefined, 'second abort() should fulfill with undefined');
+    });
+  });
+}, 'when calling abort() twice on the same stream, but sequentially so so there\'s no pending abort the second time, ' +
+   'both should fulfill with undefined');
+
+promise_test(t => {
+  const ws = new WritableStream({
+    start(c) {
+      c.error(error1);
+    }
+  });
+
+  const writer = ws.getWriter();
+
+  return promise_rejects(t, error1, writer.closed, 'writer.closed should reject').then(() => {
+    return writer.abort().then(
+      v => assert_equals(v, undefined, 'abort() should fulfill with undefined'));
+  });
+}, 'calling abort() on an errored stream should fulfill with undefined');
 
 promise_test(t => {
   let controller;
@@ -1298,4 +1345,31 @@
   });
 }, 'sink abort() should not be called if stream was erroring due to bad strategy before abort() was called');
 
+promise_test(t => {
+  const ws = new WritableStream();
+  return ws.abort().then(() => {
+    const writer = ws.getWriter();
+    return writer.closed.then(t.unreached_func('closed promise should not fulfill'),
+                              e => assert_equals(e, undefined, 'e should be undefined'));
+  });
+}, 'abort with no arguments should set the stored error to undefined');
+
+promise_test(t => {
+  const ws = new WritableStream();
+  return ws.abort(undefined).then(() => {
+    const writer = ws.getWriter();
+    return writer.closed.then(t.unreached_func('closed promise should not fulfill'),
+                              e => assert_equals(e, undefined, 'e should be undefined'));
+  });
+}, 'abort with an undefined argument should set the stored error to undefined');
+
+promise_test(t => {
+  const ws = new WritableStream();
+  return ws.abort('string argument').then(() => {
+    const writer = ws.getWriter();
+    return writer.closed.then(t.unreached_func('closed promise should not fulfill'),
+                              e => assert_equals(e, 'string argument', 'e should be \'string argument\''));
+  });
+}, 'abort with a string argument should set the stored error to that argument');
+
 done();
diff --git a/streams/writable-streams/bad-underlying-sinks.js b/streams/writable-streams/bad-underlying-sinks.js
index c519aad..2c7c448 100644
--- a/streams/writable-streams/bad-underlying-sinks.js
+++ b/streams/writable-streams/bad-underlying-sinks.js
@@ -74,29 +74,20 @@
 
 }, 'close: returning a rejected promise should cause writer close() and ready to reject');
 
-promise_test(t => {
-  const ws = new WritableStream({
+test(() => {
+  assert_throws(error1, () => new WritableStream({
     get close() {
       throw error1;
     }
-  });
+  }), 'constructor should throw');
+}, 'close: throwing getter should cause constructor to throw');
 
-  const writer = ws.getWriter();
-
-  return promise_rejects(t, error1, writer.close(), 'close should reject with the thrown error');
-}, 'close: throwing getter should cause writer close() to reject');
-
-promise_test(t => {
-  const ws = new WritableStream({
+test(() => {
+  assert_throws(error1, () => new WritableStream({
     get write() {
       throw error1;
     }
-  });
-
-  const writer = ws.getWriter();
-
-  return promise_rejects(t, error1, writer.write('a'), 'write should reject with the thrown error')
-  .then(() => promise_rejects(t, error1, writer.closed, 'closed should reject with the thrown error'));
+  }), 'constructor should throw');
 }, 'write: throwing getter should cause write() and closed to reject');
 
 promise_test(t => {
@@ -173,29 +164,18 @@
 
 }, 'write: returning a rejected promise (second write) should cause writer write() and ready to reject');
 
-promise_test(t => {
-  const ws = new WritableStream({
+test(() => {
+  assert_throws(new TypeError(), () => new WritableStream({
     abort: { apply() {} }
-  });
-
-  return promise_rejects(t, new TypeError(), ws.abort(error1), 'abort should reject with TypeError').then(() => {
-    const writer = ws.getWriter();
-    return promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError');
-  });
+  }), 'constructor should throw');
 }, 'abort: non-function abort method with .apply');
 
-promise_test(t => {
-  const abortReason = new Error('different string');
-  const ws = new WritableStream({
+test(() => {
+  assert_throws(error1, () => new WritableStream({
     get abort() {
       throw error1;
     }
-  });
-
-  const writer = ws.getWriter();
-
-  return promise_rejects(t, error1, writer.abort(abortReason), 'abort should reject with the thrown error')
-  .then(() => promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError'));
+  }), 'constructor should throw');
 }, 'abort: throwing getter should cause abort() and closed to reject');
 
 promise_test(t => {
@@ -209,7 +189,7 @@
   const writer = ws.getWriter();
 
   return promise_rejects(t, error1, writer.abort(abortReason), 'abort should reject with the thrown error')
-  .then(() => promise_rejects(t, new TypeError(), writer.closed, 'closed should reject with a TypeError'));
+  .then(() => promise_rejects(t, abortReason, writer.closed, 'closed should reject with abortReason'));
 }, 'abort: throwing method should cause abort() and closed to reject');
 
 done();
diff --git a/streams/writable-streams/close.js b/streams/writable-streams/close.js
index bf9472e..5cbe570 100644
--- a/streams/writable-streams/close.js
+++ b/streams/writable-streams/close.js
@@ -361,8 +361,8 @@
                       'closePromise must reject with the error returned from the sink\'s close method'),
       promise_rejects(t, error1, abortPromise,
                       'abortPromise must reject with the error returned from the sink\'s close method'),
-      promise_rejects(t, new TypeError(), writer.closed,
-                      'writer.closed must reject with a TypeError indicating the stream was aborted')
+      promise_rejects(t, error2, writer.closed,
+                      'writer.closed must reject with error2')
     ]).then(() => {
       assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
                           'promises must fulfill/reject in the expected order');
diff --git a/streams/writable-streams/constructor.html b/streams/writable-streams/constructor.html
index ec5a0fa..a548e08 100644
--- a/streams/writable-streams/constructor.html
+++ b/streams/writable-streams/constructor.html
@@ -5,6 +5,6 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 
-
+<script src="../resources/constructor-ordering.js"></script>
 
 <script src="constructor.js"></script>
diff --git a/streams/writable-streams/constructor.js b/streams/writable-streams/constructor.js
index a2d986c..5f28c8b 100644
--- a/streams/writable-streams/constructor.js
+++ b/streams/writable-streams/constructor.js
@@ -2,6 +2,7 @@
 
 if (self.importScripts) {
   self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
 }
 
 const error1 = new Error('error1');
@@ -119,7 +120,7 @@
 
   assert_throws(new TypeError(), () => new WritableStreamDefaultController({}),
                 'constructor should throw a TypeError exception');
-}, 'WritableStreamDefaultController constructor should throw unless passed a WritableStream');
+}, 'WritableStreamDefaultController constructor should throw');
 
 test(() => {
   let WritableStreamDefaultController;
@@ -150,4 +151,40 @@
                 'constructor should throw a TypeError exception');
 }, 'WritableStreamDefaultWriter constructor should throw when stream argument is locked');
 
+const operations = [
+  op('get', 'size'),
+  op('get', 'highWaterMark'),
+  op('get', 'type'),
+  op('validate', 'type'),
+  op('validate', 'size'),
+  op('tonumber', 'highWaterMark'),
+  op('validate', 'highWaterMark'),
+  op('get', 'write'),
+  op('validate', 'write'),
+  op('get', 'close'),
+  op('validate', 'close'),
+  op('get', 'abort'),
+  op('validate', 'abort'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const underlyingSink = createRecordingObjectWithProperties(record, ['type', 'start', 'write', 'close', 'abort']);
+    const strategy = createRecordingStrategy(record);
+
+    try {
+      new WritableStream(underlyingSink, strategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `WritableStream constructor should stop after ${failureOp} fails`);
+}
+
 done();
diff --git a/streams/writable-streams/general.js b/streams/writable-streams/general.js
index 1702479..7187572 100644
--- a/streams/writable-streams/general.js
+++ b/streams/writable-streams/general.js
@@ -165,8 +165,8 @@
 
 promise_test(t => {
   function functionWithOverloads() {}
-  functionWithOverloads.apply = () => assert_unreached('apply() should not be called');
-  functionWithOverloads.call = () => assert_unreached('call() should not be called');
+  functionWithOverloads.apply = t.unreached_func('apply() should not be called');
+  functionWithOverloads.call = t.unreached_func('call() should not be called');
   const underlyingSink = {
     start: functionWithOverloads,
     write: functionWithOverloads,
@@ -180,9 +180,12 @@
   writer1.close();
 
   // Test abort().
+  const abortError = new Error();
+  abortError.name = 'abort error';
+
   const ws2 = new WritableStream(underlyingSink);
   const writer2 = ws2.getWriter();
-  writer2.abort();
+  writer2.abort(abortError);
 
   // Test abort() with a close underlying sink method present. (Historical; see
   // https://github.com/whatwg/streams/issues/620#issuecomment-263483953 for what used to be
@@ -193,11 +196,11 @@
     close: functionWithOverloads
   });
   const writer3 = ws3.getWriter();
-  writer3.abort();
+  writer3.abort(abortError);
 
   return writer1.closed
-      .then(() => promise_rejects(t, new TypeError(), writer2.closed, 'writer2.closed should be rejected'))
-      .then(() => promise_rejects(t, new TypeError(), writer3.closed, 'writer3.closed should be rejected'));
+      .then(() => promise_rejects(t, abortError, writer2.closed, 'writer2.closed should be rejected'))
+      .then(() => promise_rejects(t, abortError, writer3.closed, 'writer3.closed should be rejected'));
 }, 'methods should not not have .apply() or .call() called');
 
 promise_test(() => {
diff --git a/streams/writable-streams/properties.js b/streams/writable-streams/properties.js
index 99bd09a..7f420a7 100644
--- a/streams/writable-streams/properties.js
+++ b/streams/writable-streams/properties.js
@@ -56,7 +56,7 @@
   WritableStreamDefaultController: {
     constructor: {
       type: 'constructor',
-      length: 4
+      length: 0
     },
     error: {
       type: 'method',
@@ -147,7 +147,7 @@
 const sinkMethods = {
   start: {
     length: 1,
-    trigger: () => {}
+    trigger: () => Promise.resolve()
   },
   write: {
     length: 2,
@@ -194,28 +194,32 @@
     });
   }, `sink method ${method} should be called even when it's located on the prototype chain`);
 
-  if (method !== 'start') {
-    promise_test(t => {
-      const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
-                              'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
-                              'apply', 'construct'];
-      const handler = {
-        get: t.step_func((target, property) => {
-          if (property === 'type') {
-            return undefined;
-          }
-          assert_in_array(property, ['start', method], `only start() and ${method}() should be called`);
-          return () => Promise.resolve();
-        })
-      };
-      for (const trap of unreachedTraps) {
-        handler[trap] = t.unreached_func(`${trap} should not be trapped`);
-      }
-      const sink = new Proxy({}, handler);
-      const ws = new WritableStream(sink);
-      return trigger(ws.getWriter());
-    }, `unexpected properties should not be accessed when calling sink method ${method}`);
-  }
+  promise_test(t => {
+    const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
+                            'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
+                            'apply', 'construct'];
+    const touchedProperties = [];
+    const handler = {
+      get: t.step_func((target, property) => {
+        touchedProperties.push(property);
+        if (property === 'type') {
+          return undefined;
+        }
+        return () => Promise.resolve();
+      })
+    };
+    for (const trap of unreachedTraps) {
+      handler[trap] = t.unreached_func(`${trap} should not be trapped`);
+    }
+    const sink = new Proxy({}, handler);
+    const ws = new WritableStream(sink);
+    assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'],
+                        'expected properties should be got');
+    return trigger(ws.getWriter()).then(() => {
+      assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'],
+                          'no properties should be accessed on method call');
+    });
+  }, `unexpected properties should not be accessed when calling sink method ${method}`);
 }
 
 done();
diff --git a/streams/writable-streams/reentrant-strategy.js b/streams/writable-streams/reentrant-strategy.js
index 0dc9786..6e1b52c 100644
--- a/streams/writable-streams/reentrant-strategy.js
+++ b/streams/writable-streams/reentrant-strategy.js
@@ -114,16 +114,16 @@
   let writer;
   const strategy = {
     size() {
-      writer.abort('nice');
+      writer.abort(error1);
       return 1;
     }
   };
 
   const ws = recordingWritableStream({}, strategy);
   writer = ws.getWriter();
-  return promise_rejects(t, new TypeError(), writer.write('a'), 'write() promise should reject')
+  return promise_rejects(t, error1, writer.write('a'), 'write() promise should reject')
       .then(() => {
-        assert_array_equals(ws.events, ['abort', 'nice'], 'sink.write() should not be called');
+        assert_array_equals(ws.events, ['abort', error1], 'sink.write() should not be called');
       });
 }, 'abort() should work when called from within strategy.size()');
 
diff --git a/subresource-integrity/tools/generate_javascript.py b/subresource-integrity/tools/generate_javascript.py
index 184a394..300e170 100644
--- a/subresource-integrity/tools/generate_javascript.py
+++ b/subresource-integrity/tools/generate_javascript.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from os import path, listdir
 from hashlib import sha512, sha256, md5
 from base64 import b64encode
@@ -39,14 +41,14 @@
 
 def main():
   for file in js_files():
-    print "Generating content for %s" % file
+    print("Generating content for %s" % file)
     base = path.splitext(path.basename(file))[0]
     var_name = re.sub(r"[^a-z0-9]", "_", base)
     content = "%s=true;" % var_name
     with open(file, "w") as f: f.write(content)
-    print "\tSHA512 integrity: %s" % sha512_uri(content)
-    print "\tSHA256 integrity: %s" % sha256_uri(content)
-    print "\tMD5 integrity:    %s" % md5_uri(content)
+    print("\tSHA512 integrity: %s" % sha512_uri(content))
+    print("\tSHA256 integrity: %s" % sha256_uri(content))
+    print("\tMD5 integrity:    %s" % md5_uri(content))
 
 if __name__ == "__main__":
   main()
diff --git a/subresource-integrity/tools/list_hashes.py b/subresource-integrity/tools/list_hashes.py
index 44fb940..0db1985 100644
--- a/subresource-integrity/tools/list_hashes.py
+++ b/subresource-integrity/tools/list_hashes.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from os import path, listdir
 from hashlib import sha512, sha384, sha256, md5
 from base64 import b64encode
@@ -63,15 +65,15 @@
 def main():
   ed25519_key = ed25519_key_pair()
   for file in js_and_css_files():
-    print "Listing hash values for %s" % file
+    print("Listing hash values for %s" % file)
     with open(file, "r") as content_file:
       content = content_file.read()
-      print "\tSHA512 integrity:  %s" % sha512_uri(content)
-      print "\tSHA384 integrity:  %s" % sha384_uri(content)
-      print "\tSHA256 integrity:  %s" % sha256_uri(content)
-      print "\tMD5 integrity:     %s" % md5_uri(content)
-      print "\tEd25519 integrity: %s" % ed25519_signature(ed25519_key, content)
-  print "\nEd25519 public key (used above): %s" % format_digest(ed25519_key[1])
+      print("\tSHA512 integrity:  %s" % sha512_uri(content))
+      print("\tSHA384 integrity:  %s" % sha384_uri(content))
+      print("\tSHA256 integrity:  %s" % sha256_uri(content))
+      print("\tMD5 integrity:     %s" % md5_uri(content))
+      print("\tEd25519 integrity: %s" % ed25519_signature(ed25519_key, content))
+  print("\nEd25519 public key (used above): %s" % format_digest(ed25519_key[1]))
 
 if __name__ == "__main__":
   main()
diff --git a/svg/OWNERS b/svg/OWNERS
index 852a747..e815ad8 100644
--- a/svg/OWNERS
+++ b/svg/OWNERS
@@ -2,3 +2,5 @@
 @nikosandronikos
 @boggydigital
 @ewilligers
+@AmeliaBR
+@svgeesus
diff --git a/svg/extensibility/foreignObject/containing-block.html b/svg/extensibility/foreignObject/containing-block.html
new file mode 100644
index 0000000..da0728c
--- /dev/null
+++ b/svg/extensibility/foreignObject/containing-block.html
@@ -0,0 +1,46 @@
+<!doctype HTML>
+<link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#embedded-ForeignObjectElement"/>
+<style>
+  * {
+    margin: 5px;
+  }
+  .el {
+    background: lightblue;
+    width: 50px;
+    height: 60px;
+  }
+  .pos {
+    top: 5px;
+    left: 6px;
+  }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<svg>
+  <foreignObject id="first" width=100 height=100>
+    <div id=contained class="el" style="width: 50px; height: 60px;"></div>
+    <div id=containedrel class="el pos" style="position: relative"></div>
+    <div id=containedabs class="el pos" style="position: absolute"></div>
+    <div id=containedfixed class="el pos" style="position: fixed"></div>
+  </foreignObject>
+</svg>
+<script>
+function checkPosition(el, offsetLeftVal, offsetTopVal, boundingRectLeft, boundingRectTop) {
+    assert_equals(el.offsetLeft, offsetLeftVal, "offsetLeft");
+    assert_equals(el.offsetTop, offsetTopVal, "offsetTop");
+    assert_equals(el.getBoundingClientRect().left, boundingRectLeft, "boundingRectLeft");
+    assert_equals(el.getBoundingClientRect().top, boundingRectTop, "boundingRectTop");
+}
+
+test(function() {
+  // Test that #first is a containing block for all descendants.
+  var contained = document.getElementById('contained');
+  var containedrel = document.getElementById('containedrel');
+  var containedabs = document.getElementById('containedabs');
+  var containedfixed = document.getElementById('containedfixed');
+  checkPosition(contained, 5, 5, 20, 20);
+  checkPosition(containedrel, 11, 75, 26, 90);
+  checkPosition(containedabs, 11, 10, 26, 25);
+  checkPosition(containedfixed, 11, 10, 26, 25);
+}, "position");
+</script>
diff --git a/svg/extensibility/foreignObject/foreign-object-circular-filter-reference-crash.html b/svg/extensibility/foreignObject/foreign-object-circular-filter-reference-crash.html
new file mode 100644
index 0000000..1629994
--- /dev/null
+++ b/svg/extensibility/foreignObject/foreign-object-circular-filter-reference-crash.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Should not crash on circular filter reference containing a foreignObject.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>test(()=>{})</script>
+<svg id="svg" filter="url(#filter)">
+  <foreignObject overflow="hidden"></foreignObject>
+</svg>
+<svg>
+  <filter id="filter">
+    <feImage xlink:href="#svg"></feImage>
+  </filter>
+</svg>
diff --git a/svg/extensibility/foreignObject/foreign-object-paints-before-rect-expected.html b/svg/extensibility/foreignObject/foreign-object-paints-before-rect-expected.html
new file mode 100644
index 0000000..a0a8692
--- /dev/null
+++ b/svg/extensibility/foreignObject/foreign-object-paints-before-rect-expected.html
@@ -0,0 +1,12 @@
+<!doctype HTML>
+<!doctype HTML>
+<style>
+* {
+  margin: 0
+}
+</style>
+<svg style="width: 500px; height: 500px">
+  <rect x="0" y="0" width="100%" height="100%" style="fill: blue"/>
+  <rect x="0" y="0" width="50%" height="50%"/>
+</svg>
+
diff --git a/svg/extensibility/foreignObject/foreign-object-paints-before-rect.html b/svg/extensibility/foreignObject/foreign-object-paints-before-rect.html
new file mode 100644
index 0000000..928a136
--- /dev/null
+++ b/svg/extensibility/foreignObject/foreign-object-paints-before-rect.html
@@ -0,0 +1,16 @@
+<!doctype HTML>
+<link rel="match" href="foreign-object-paints-before-rect-expected.html">
+<link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#embedded-ForeignObjectElement"/>
+<style>
+* {
+  margin: 0
+}
+</style>
+<!-- Test that the <foreignObject> root element paints in element order
+within the SVG, but content within it is atomic. -->
+<svg style="width: 500px; height: 500px">
+  <foreignObject width="100%" height="100%">
+    <div style="width: 500px; height: 500px; background: blue"></div>
+  </foreignObject>
+  <rect x="0" y="0" width="50%" height="50%"/>
+</svg>
diff --git a/svg/extensibility/foreignObject/foreign-object-size-ref.html b/svg/extensibility/foreignObject/foreign-object-size-ref.html
new file mode 100644
index 0000000..e0b17a2
--- /dev/null
+++ b/svg/extensibility/foreignObject/foreign-object-size-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Reference</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<style>
+div { width: 100px; height: 100px; background-color: green; }
+</style>
+<div></div>
diff --git a/svg/extensibility/foreignObject/foreign-object-size.html b/svg/extensibility/foreignObject/foreign-object-size.html
new file mode 100644
index 0000000..91fc3ef
--- /dev/null
+++ b/svg/extensibility/foreignObject/foreign-object-size.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test: auto values for width and height properties on foreignObject compute to zero</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://svgwg.org/svg2-draft/geometry.html#Sizing">
+<link rel="match" href="foreign-object-size-ref.html">
+<style>
+div { width: 100px; height: 100px; }
+</style>
+<svg>
+  <foreignObject width="100" height="100">
+    <div style="background-color: green;"></div>
+  </foreignObject>
+  <!--
+    The foreignObject takes its size from its width and height properties.
+    Unlike HTML elements, a value of auto for these properties (as omitting
+    the width="" and height="" presentation attributes will do) will compute
+    to zero, causing it not to be rendered.
+    -->
+  <foreignObject>
+    <div style="background-color: red;"></div>
+  </foreignObject>
+</svg>
diff --git a/svg/extensibility/foreignObject/position-svg-root-in-foreign-object-ref.html b/svg/extensibility/foreignObject/position-svg-root-in-foreign-object-ref.html
new file mode 100644
index 0000000..668eb9b
--- /dev/null
+++ b/svg/extensibility/foreignObject/position-svg-root-in-foreign-object-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SVG Reftest Reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<p>You should see the word PASS and no red below.</p>
+PASS
diff --git a/svg/extensibility/foreignObject/position-svg-root-in-foreign-object.html b/svg/extensibility/foreignObject/position-svg-root-in-foreign-object.html
new file mode 100644
index 0000000..fd32d2a
--- /dev/null
+++ b/svg/extensibility/foreignObject/position-svg-root-in-foreign-object.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SVG: svg root child of foreignObject should be positionable</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://svgwg.org/svg2-draft/embedded.html#ForeignObjectElement">
+<link rel="match" href="position-svg-root-in-foreign-object-ref.html">
+<p>You should see the word PASS and no red below.</p>
+<svg>
+  <foreignObject width="200" height="200">
+    PASS
+    <svg style="position:absolute; left: -1000px; width:100px; height: 100px; background: red"></svg>
+  </foreignObject>
+</svg>
diff --git a/svg/extensibility/foreignObject/properties.svg b/svg/extensibility/foreignObject/properties.svg
new file mode 100644
index 0000000..b92a6d1
--- /dev/null
+++ b/svg/extensibility/foreignObject/properties.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#embedded-ForeignObjectElement"/>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#styling-PresentationAttributes"/>
+    <h:meta name="assert" content="x y width height are presentation attributes of foreignObject"/>
+  </metadata>
+  <style>
+    .c {
+      x: 10px;
+      y: 20px;
+      width: 30px;
+      height: 40px;
+    }
+  </style>
+  <foreignObject id="first"></foreignObject>
+  <foreignObject id="second" class="c"></foreignObject>
+  <foreignObject id="third" x="50" y="60" width="70" height="80"></foreignObject>
+  <foreignObject id="fourth" x="50" y="60" width="70" height="80" class="c"></foreignObject>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+  test(function() {
+    var first = document.getElementById('first');
+    assert_equals(getComputedStyle(first).x, "0px");
+    assert_equals(getComputedStyle(first).y, "0px");
+    assert_equals(getComputedStyle(first).width, "auto");
+    assert_equals(getComputedStyle(first).height, "auto");
+  }, 'width and height default to auto');
+
+  test(function() {
+    var second = document.getElementById('second');
+    assert_equals(getComputedStyle(second).x, "10px");
+    assert_equals(getComputedStyle(second).y, "20px");
+    assert_equals(getComputedStyle(second).width, "30px");
+    assert_equals(getComputedStyle(second).height, "40px");
+  }, 'style rules are applied');
+
+  test(function() {
+    var third = document.getElementById('third');
+    assert_equals(getComputedStyle(third).x, "50px");
+    assert_equals(getComputedStyle(third).y, "60px");
+    assert_equals(getComputedStyle(third).width, "70px");
+    assert_equals(getComputedStyle(third).height, "80px");
+  }, 'attributes set properties');
+
+  test(function() {
+    var fourth = document.getElementById('fourth');
+    assert_equals(getComputedStyle(fourth).x, "10px");
+    assert_equals(getComputedStyle(fourth).y, "20px");
+    assert_equals(getComputedStyle(fourth).width, "30px");
+    assert_equals(getComputedStyle(fourth).height, "40px");
+  }, 'style rules override attributes');
+  ]]></script>
+</svg>
diff --git a/svg/extensibility/foreignObject/stacking-context-ref.html b/svg/extensibility/foreignObject/stacking-context-ref.html
new file mode 100644
index 0000000..28e7e31
--- /dev/null
+++ b/svg/extensibility/foreignObject/stacking-context-ref.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+<style>
+  * {
+    margin: 0;
+  }
+  .el {
+    width: 50px;
+    height: 60px;
+  }
+
+</style>
+<div style="isolation: isolate">
+  <div class="el"
+      style="position: absolute; z-index: 1; top: 40px; left: 10px; border: 1px solid black; background: lightblue"></div>
+  <div class="el"
+      style="position: absolute; z-index: 2; top: 5px; left: 5px; border: 1px solid black; background: lightgreen"></div>
+</div>
+<div id=top class="el" style="position: relative; background: lightgray; top: 50px"></div>
diff --git a/svg/extensibility/foreignObject/stacking-context.html b/svg/extensibility/foreignObject/stacking-context.html
new file mode 100644
index 0000000..dfad5a1
--- /dev/null
+++ b/svg/extensibility/foreignObject/stacking-context.html
@@ -0,0 +1,26 @@
+<!doctype HTML>
+<title>Test that the foreignObject element is a stacking context</title>
+<link rel="match" href="stacking-context-ref.html">
+<link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#embedded-ForeignObjectElement"/>
+<style>
+  * {
+    margin: 0;
+  }
+  .el {
+    width: 50px;
+    height: 60px;
+  }
+
+</style>
+  <!-- Test that the <foreignObject> root element is a stacking context, so z-index here
+    has no effect on order w.r.t. #top, but still does for stacking under
+    foreignObject -->
+<svg style="width: 50px; height: 50px; overflow: visible; display: block">
+  <foreignObject width=100 height=200>
+    <div  class="el"
+        style="position: absolute; z-index: 1; top: 40px; left: 10px; border: 1px solid black; background: lightblue"></div>
+    <div  class="el"
+        style="position: absolute; z-index: 2; top: 5px; left: 5px; border: 1px solid black; background: lightgreen"></div>
+  </foreignObject>
+</svg>
+<div id=top class="el" style="position: relative; background: lightgray"></div>
diff --git a/svg/import/animate-dom-01-f-manual.svg b/svg/import/animate-dom-01-f-manual.svg
index 663a07f..5c4d8bc 100644
--- a/svg/import/animate-dom-01-f-manual.svg
+++ b/svg/import/animate-dom-01-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -44,7 +44,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
+
     <text x='30' y='30'>Testing SVGAnimationElement.getStartTime()</text>
 
     <text x='340' y='340' display='none'>Test running...
@@ -177,14 +177,14 @@
     ]]></script>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.11 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/animate-dom-02-f-manual.svg b/svg/import/animate-dom-02-f-manual.svg
index c9bf327..68c30f0 100644
--- a/svg/import/animate-dom-02-f-manual.svg
+++ b/svg/import/animate-dom-02-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -12,22 +12,22 @@
     template-version="1.3" reviewer="DAS" author="CM" status="accepted"
     version="$Revision: 1.7 $" testname="$RCSfile: animate-dom-02-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/animate.html#InterfaceSVGAnimationElement">
-	      <p>
-	        This tests that the methods on the ElementTimeControl
-	        interface return the undefined value, since the IDL
-	        operations are declared to return void.
-	      </p>
-	      <p>
-	       After the loading the document, a rectangle is shown
-	       indicating whether all four methods from the ElementTimeControl
-	       interface returned undefined when invoked.  The rectangle
-	       is black if the test did not run, red if the test failed
-	       and green if the test succeeded.
-			</p>
+        <p>
+          This tests that the methods on the ElementTimeControl
+          interface return the undefined value, since the IDL
+          operations are declared to return void.
+        </p>
+        <p>
+         After the loading the document, a rectangle is shown
+         indicating whether all four methods from the ElementTimeControl
+         interface returned undefined when invoked.  The rectangle
+         is black if the test did not run, red if the test failed
+         and green if the test succeeded.
+      </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -47,7 +47,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
+
     <text x='10' y='30'>Testing ElementTimeControl method return values</text>
 
     <rect id='r' x='10' y='50' width='50' height='50'/>
@@ -70,14 +70,14 @@
     ]]></script>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.7 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/animate-elem-02-t-manual.svg b/svg/import/animate-elem-02-t-manual.svg
index 0dc0045..976edbf 100644
--- a/svg/import/animate-elem-02-t-manual.svg
+++ b/svg/import/animate-elem-02-t-manual.svg
@@ -31,7 +31,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
         <p>The test has passed if the four green rectangles each animate their height according to the following:</p>
-       	<p>The leftmost rect:</p>
+         <p>The leftmost rect:</p>
         <ul>
            <li>when the animation starts the height of the green rect should make it align with the bottommost red indicator line</li>
            <li>after two seconds the height should jump to be 10% of the height of the gray rect it overlaps</li>
@@ -39,21 +39,21 @@
            <li>after six seconds the height should jump to its final position, 10% of the height of the gray rect</li>
          </ul>
         <p>The next to leftmost rect:</p>
-       	<ul>
+         <ul>
            <li>when the animation starts the height of the green rect should be 110% of the height of the gray rect</li>
            <li>after two seconds the height should jump to be 20% of the height of the gray rect</li>
            <li>after four seconds the height should jump to be 110% of the height of the gray rect</li>
            <li>after six seconds the height should jump to its final position, 20% of the height of the gray rect</li>
          </ul>
         <p>The next to rightmost rect:</p>
-       	<ul>
+         <ul>
            <li>when the animation starts the height of the green rect should make it align with the bottommost red indicator line</li>
            <li>after two seconds the height should jump to be 10% of the height of the gray rect</li>
            <li>after four seconds the height should jump to be 110% of the height of the gray rect</li>
            <li>after six seconds the height should jump to its final position, 20% of the height of the gray rect</li>
          </ul>
         <p>The rightmost rect:</p>
-       	<ul>
+         <ul>
            <li>when the animation starts the height of the green rect should be 110% of the height of the gray rect</li>
            <li>after two seconds the height should jump to be 20% of the height of the gray rect</li>
            <li>after four seconds the height should jump to be 120% of the height of the gray rect</li>
diff --git a/svg/import/animate-elem-03-t-manual.svg b/svg/import/animate-elem-03-t-manual.svg
index 2c28362..433251a 100644
--- a/svg/import/animate-elem-03-t-manual.svg
+++ b/svg/import/animate-elem-03-t-manual.svg
@@ -40,11 +40,11 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test has passed if:
-      </p> 
+      </p>
       <ul>
         <li>the topmost line shows the text "Sample 123" that animates its fill-color smoothly from blue to green over the course of six seconds</li>
         <li>the middle line shows the text "Sample 123" in a larger font-size than the first line, in blue fill-color that doesn't animate</li>
-        <li>the bottommost line shows the text "Sample 123" in the same font-size as the topmost line, then smoothly animating the font-size 
+        <li>the bottommost line shows the text "Sample 123" in the same font-size as the topmost line, then smoothly animating the font-size
           to be larger than the middle line over the course of six seconds. At the same time the fill-color is smoothly animated from blue to green</li>
         <li>after six seconds the rendered result matches the reference image</li>
       </ul>
diff --git a/svg/import/animate-elem-05-t-manual.svg b/svg/import/animate-elem-05-t-manual.svg
index 3e1be14..1fcfc21 100644
--- a/svg/import/animate-elem-05-t-manual.svg
+++ b/svg/import/animate-elem-05-t-manual.svg
@@ -33,7 +33,7 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test has passed if a triangle is animated smoothly along the path indicated by the black line, and
-        it passes over the pink rectangles at the indicated times. 
+        it passes over the pink rectangles at the indicated times.
         When the animation starts the triangle should be positioned on top of the leftmost pink rectangle, after
         3 seconds it should reach the middle pink rectangle, and after 6 seconds it should be positioned on top
         of the rightmost pink rectangle where it should stop.
diff --git a/svg/import/animate-elem-10-t-manual.svg b/svg/import/animate-elem-10-t-manual.svg
index 23b1502..80e48b7 100644
--- a/svg/import/animate-elem-10-t-manual.svg
+++ b/svg/import/animate-elem-10-t-manual.svg
@@ -35,7 +35,7 @@
       <p>
         The test is passed if the two orange rects are animated so that the bottom part of each rectangle is at the position
         indicated by the ruler lines at the particular time noted next to each ruler line. Between two noted times the
-        bottom part of each rect must be between the two corresponding ruler lines.  
+        bottom part of each rect must be between the two corresponding ruler lines.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -65,7 +65,7 @@
           <animate attributeName="height" values="210;177;121;10" begin="0s" dur="9s" fill="freeze"/>
         </rect>
       </g>
-      
+
       <g transform="translate(250,50)">
         <text x="0" y="203">at 0 sec.</text>
         <text x="0" y="170">at 3 sec. </text>
diff --git a/svg/import/animate-elem-11-t-manual.svg b/svg/import/animate-elem-11-t-manual.svg
index 001fb7d..49e3d45 100644
--- a/svg/import/animate-elem-11-t-manual.svg
+++ b/svg/import/animate-elem-11-t-manual.svg
@@ -28,12 +28,12 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The test is passed if the two orange rects are animated so that the bottom part of each rectangle is at the position indicated by the ruler lines at the particular time noted next to each ruler line. Between two noted times the bottom part of each rect must be between the two corresponding ruler lines. 
+        The test is passed if the two orange rects are animated so that the bottom part of each rectangle is at the position indicated by the ruler lines at the particular time noted next to each ruler line. Between two noted times the bottom part of each rect must be between the two corresponding ruler lines.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -62,7 +62,7 @@
           <animate attributeName="height" calcMode="paced" values="210;177;121;10" begin="0s" dur="9s" fill="freeze"/>
         </rect>
       </g>
-      
+
       <g transform="translate(250,50)">
         <text x="0" y="203">at 0 sec.</text>
         <text x="0" y="136.33">at 3 sec. </text>
diff --git a/svg/import/animate-elem-12-t-manual.svg b/svg/import/animate-elem-12-t-manual.svg
index ff0cc49..72dd94c 100644
--- a/svg/import/animate-elem-12-t-manual.svg
+++ b/svg/import/animate-elem-12-t-manual.svg
@@ -27,13 +27,13 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
-    		Run the test. No interaction required.
-    	</p>
+      <p>
+        Run the test. No interaction required.
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The test is passed if the two orange rects are animated so that the bottom part of each rectangle is at the position indicated by the ruler lines at the particular time noted next to each ruler line. Between two noted times the bottom part of each rect must be between the two corresponding ruler lines. The bottom of the left rectangles and the right rectangle must always be the same throughout the animation. 
+        The test is passed if the two orange rects are animated so that the bottom part of each rectangle is at the position indicated by the ruler lines at the particular time noted next to each ruler line. Between two noted times the bottom part of each rect must be between the two corresponding ruler lines. The bottom of the left rectangles and the right rectangle must always be the same throughout the animation.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -62,7 +62,7 @@
           <animate attributeName="height" calcMode="spline" keySplines="0,0,1,1;0,0,1,1;.75,0,0,.75" values="210;177;121;10" begin="0s" dur="9s" fill="freeze"/>
         </rect>
       </g>
-      
+
       <g transform="translate(250,50)">
         <text x="0" y="203">at 0 sec.</text>
         <text x="0" y="170">at 3 sec. </text>
diff --git a/svg/import/animate-elem-13-t-manual.svg b/svg/import/animate-elem-13-t-manual.svg
index 52c4b6f..855c5e5 100644
--- a/svg/import/animate-elem-13-t-manual.svg
+++ b/svg/import/animate-elem-13-t-manual.svg
@@ -28,7 +28,7 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/animate-elem-14-t-manual.svg b/svg/import/animate-elem-14-t-manual.svg
index b85b279..dd35fd5 100644
--- a/svg/import/animate-elem-14-t-manual.svg
+++ b/svg/import/animate-elem-14-t-manual.svg
@@ -28,12 +28,12 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Run the test. No interaction required.
-      </p>      
+        Run the test. No interaction required.
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The right edge of the blue rectangle should line up with the ruler lines at the indicated times, and should jump directly to each position with no animation in between.  
+        The right edge of the blue rectangle should line up with the ruler lines at the indicated times, and should jump directly to each position with no animation in between.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/animate-elem-15-t-manual.svg b/svg/import/animate-elem-15-t-manual.svg
index 4ef4a7a..3afab97 100644
--- a/svg/import/animate-elem-15-t-manual.svg
+++ b/svg/import/animate-elem-15-t-manual.svg
@@ -29,12 +29,12 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The blue rectangle should animate its width at a constant speed so that the right edge of the rectangle lines up with the ruler line at the indicated times.
+        The blue rectangle should animate its width at a constant speed so that the right edge of the rectangle lines up with the ruler line at the indicated times.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/animate-elem-17-t-manual.svg b/svg/import/animate-elem-17-t-manual.svg
index c9936b0..3b1aed6 100644
--- a/svg/import/animate-elem-17-t-manual.svg
+++ b/svg/import/animate-elem-17-t-manual.svg
@@ -28,13 +28,13 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
+      <p>
         Run the test. No interaction required.
-      </p>  
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The blue rectangle should animate its width so that the right edge of the rectangle lines up with the ruler line at the indicated times. Between 4 and 8 seconds the animation should show an ease-in/ease-out motion (i.e. a gradual change in speed).
+        The blue rectangle should animate its width so that the right edge of the rectangle lines up with the ruler line at the indicated times. Between 4 and 8 seconds the animation should show an ease-in/ease-out motion (i.e. a gradual change in speed).
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/animate-elem-19-t-manual.svg b/svg/import/animate-elem-19-t-manual.svg
index e379e92..3390945 100644
--- a/svg/import/animate-elem-19-t-manual.svg
+++ b/svg/import/animate-elem-19-t-manual.svg
@@ -14,7 +14,7 @@
     template-version="1.4" reviewer="SVGWG" author="Jon Ferraiolo" status="accepted"
     version="$Revision: 1.6 $" testname="$RCSfile: animate-elem-19-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/animate.html#Animation">
-    	<p>
+      <p>
         Test 'calcMode'=linear.
       </p>
       <p>
@@ -33,7 +33,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The blue rectangle should animate its width so that the right edge of the rectangle lines up with the ruler line at the indicated times. The rate of change will increase after each ruler line is passed.
+        The blue rectangle should animate its width so that the right edge of the rectangle lines up with the ruler line at the indicated times. The rate of change will increase after each ruler line is passed.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/animate-elem-20-t-manual.svg b/svg/import/animate-elem-20-t-manual.svg
index 0301032..6da8584 100644
--- a/svg/import/animate-elem-20-t-manual.svg
+++ b/svg/import/animate-elem-20-t-manual.svg
@@ -20,16 +20,16 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Click "fade in", wait 3 seconds. Click "fade out", wait 3 seconds. Click "fade in" again, wait 6 seconds.
+        Click "fade in", wait 3 seconds. Click "fade out", wait 3 seconds. Click "fade in" again, wait 6 seconds.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
-    	<p>The test is passed if:</p>
+      <p>The test is passed if:</p>
       <ul>
-      	<li>The first time "fade in" is clicked, a blue rectangle should smoothly fade from white to blue over the course of three seconds.</li>
-      	<li>When "fade out" is clicked, the blue rectangle should smoothly fade from blue to white over the course of three seconds.</li>
-      	<li>When "fade in" is clicked the second time, the blue rectangle should smoothly fade from white to blue over the course of three seconds, and then directly fade out from blue to white over the course of three seconds.</li>
-      	<li>The rendered picture matches the reference image, (except
+        <li>The first time "fade in" is clicked, a blue rectangle should smoothly fade from white to blue over the course of three seconds.</li>
+        <li>When "fade out" is clicked, the blue rectangle should smoothly fade from blue to white over the course of three seconds.</li>
+        <li>When "fade in" is clicked the second time, the blue rectangle should smoothly fade from white to blue over the course of three seconds, and then directly fade out from blue to white over the course of three seconds.</li>
+        <li>The rendered picture matches the reference image, (except
         for possible variations in the labeling text (per CSS2 rules))
         after activating the link on the fade-in button the first time
         and waiting three seconds for the animation to complete. The picture
diff --git a/svg/import/animate-elem-21-t-manual.svg b/svg/import/animate-elem-21-t-manual.svg
index f68aed0..0459896 100644
--- a/svg/import/animate-elem-21-t-manual.svg
+++ b/svg/import/animate-elem-21-t-manual.svg
@@ -34,7 +34,7 @@
         waiting three seconds, and then immediately perform a first click
         on "fade out", waiting three seconds, and then immediately perform
         a second click on "fade in", you should see the following. After
-        the first click on "fade in", the blue square goes from white to blue. 
+        the first click on "fade in", the blue square goes from white to blue.
         After the first click on "fade out", the blue square goes
         from blue to white. After the second click on "fade in",
         however, the blue square goes from white to blue, and then
@@ -45,16 +45,16 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Click "fade in", wait 3 seconds. Click "fade out", wait 3 seconds. Click "fade in" again, wait 6 seconds.
+        Click "fade in", wait 3 seconds. Click "fade out", wait 3 seconds. Click "fade in" again, wait 6 seconds.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>The test is passed if:</p>
       <ul>
-      	<li>The first time "fade in" is clicked, a blue rectangle should smoothly fade from white to blue, and two circles should fade from white to gray, all over the course of three seconds</li>
-      	<li>When "fade out" is clicked, the blue rectangle should smoothly fade from blue to white, and the two circles should fade from gray to white, all over the course of three seconds.</li>
-      	<li>When "fade in" is clicked the second time, it should behave as the first time "fade in" was clicked but immediately followed by the "fade out" behaviour described above, so that the shapes all fade in over the course of three seconds, and then out again over the course of three seconds.</li>
-      	<li>The rendered picture matches the reference image, (except
+        <li>The first time "fade in" is clicked, a blue rectangle should smoothly fade from white to blue, and two circles should fade from white to gray, all over the course of three seconds</li>
+        <li>When "fade out" is clicked, the blue rectangle should smoothly fade from blue to white, and the two circles should fade from gray to white, all over the course of three seconds.</li>
+        <li>When "fade in" is clicked the second time, it should behave as the first time "fade in" was clicked but immediately followed by the "fade out" behaviour described above, so that the shapes all fade in over the course of three seconds, and then out again over the course of three seconds.</li>
+        <li>The rendered picture matches the reference image, (except
         for possible variations in the labeling text (per CSS2 rules))
         after activating the link on the fade-in button the first time
         and waiting three seconds for the animation to compete. The picture
diff --git a/svg/import/animate-elem-22-b-manual.svg b/svg/import/animate-elem-22-b-manual.svg
index 4935e92..3b5887f 100644
--- a/svg/import/animate-elem-22-b-manual.svg
+++ b/svg/import/animate-elem-22-b-manual.svg
@@ -40,9 +40,9 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>The test is passed if:</p>
       <ul>
-      	<li>At the start of the animation the innermost rectangle is filled by a yellow semitransparent color</li>
-      	<li>Over the course of three seconds the yellow rect smoothly animates its width and height so that at time t=3s fully fills the middle rectangle</li>
-      	<li>The animation then continues in the same fashion and at time t=9s fully fills the largest rectangle with blue stroke</li>
+        <li>At the start of the animation the innermost rectangle is filled by a yellow semitransparent color</li>
+        <li>Over the course of three seconds the yellow rect smoothly animates its width and height so that at time t=3s fully fills the middle rectangle</li>
+        <li>The animation then continues in the same fashion and at time t=9s fully fills the largest rectangle with blue stroke</li>
       </ul>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -70,10 +70,10 @@
       <animate attributeName="width" attributeType="XML" begin="0s" dur="9s" fill="freeze" from="50" to="400"/>
       <animate attributeName="height" attributeType="XML" begin="0s" dur="9s" fill="freeze" from="50" to="240"/>
     </rect>
-    <!-- Set up a new user coordinate system so that the text string's 
-			origin is at (0,0), allowing rotation and scale relative to 
-			the new origin 
-		-->
+    <!-- Set up a new user coordinate system so that the text string's
+      origin is at (0,0), allowing rotation and scale relative to
+      the new origin
+    -->
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.6 $</text>
diff --git a/svg/import/animate-elem-23-t-manual.svg b/svg/import/animate-elem-23-t-manual.svg
index 84e8c59..391c234 100644
--- a/svg/import/animate-elem-23-t-manual.svg
+++ b/svg/import/animate-elem-23-t-manual.svg
@@ -34,19 +34,19 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	    <p>
+      <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The test is passed if:
+        The test is passed if:
       </p>
       <ul>
-				<li>From time t=0 seconds to t=3 seconds the large rectangle is filled with black</li>
-				<li>At t=3 seconds a blue circle appears inside the black rectangle</li>
-				<li>Between time t=3 seconds and t=6 seconds the fill of the circle is animated between blue and bluegreen</li>
-				<li>Between time t=6 seconds and t=9 seconds the fill of the circle is animated between bluegreen and green</li>   
+        <li>From time t=0 seconds to t=3 seconds the large rectangle is filled with black</li>
+        <li>At t=3 seconds a blue circle appears inside the black rectangle</li>
+        <li>Between time t=3 seconds and t=6 seconds the fill of the circle is animated between blue and bluegreen</li>
+        <li>Between time t=6 seconds and t=9 seconds the fill of the circle is animated between bluegreen and green</li>
       </ul>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/animate-elem-24-t-manual.svg b/svg/import/animate-elem-24-t-manual.svg
index 2ea2a34..df2293a 100644
--- a/svg/import/animate-elem-24-t-manual.svg
+++ b/svg/import/animate-elem-24-t-manual.svg
@@ -46,23 +46,23 @@
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: animate-elem-24-t.svg,v $</title>
-	<defs>
-		<font id="MyFont" horiz-adv-x="416">
-			<font-face font-family="MyFont" units-per-em="1000" panose-1="2 0 0 6 3 0 0 2 0 4" ascent="700" descent="-127" alphabetic="0"/>
-			<missing-glyph horiz-adv-x="233"/>
-			<glyph unicode=" " glyph-name="space" horiz-adv-x="233"/>
-			<glyph unicode="I" glyph-name="I" horiz-adv-x="330" d="M30 700V550H90V150H30V0H300V150H240V550H300V700H30Z"/>
-			<glyph unicode="t" glyph-name="t" horiz-adv-x="417" d="M5 550V410H137V0H280V410H412V550H5Z"/>
-			<glyph unicode="s" glyph-name="s" horiz-adv-x="468" d="M34 550V410V218H291V120H34V0H434V338H177V430H434V550H34Z"/>
-			<glyph unicode="&apos;" glyph-name="quotesingle" horiz-adv-x="198" d="M35 700L73 483H125L163 700H35Z"/>
-			<glyph unicode="a" glyph-name="a" horiz-adv-x="435" d="M71 550L3 0H143L154 119H282L293 0H433L365 550H71ZM168 259L182 410H254L268 259H168Z"/>
-			<glyph unicode="l" glyph-name="l" horiz-adv-x="435" d="M37 0H425V130H180V550H37V410V0Z"/>
-			<glyph unicode="i" glyph-name="i" horiz-adv-x="217" d="M37 550V410V0H180V550H37Z"/>
-			<glyph unicode="v" glyph-name="v" horiz-adv-x="430" d="M73 0H357L430 550H282L235 140H195L148 550H0L19 410L73 0Z"/>
-			<glyph unicode="e" glyph-name="e" horiz-adv-x="442" d="M37 550V410V0H419V130H180V210H299V340H180V420H419V550H37Z"/>
-			<glyph unicode="!" glyph-name="exclam" horiz-adv-x="237" d="M46 145V0H191V145H46ZM58 220H179L194 700H43L58 220Z"/>
-		</font>
-	</defs>
+  <defs>
+    <font id="MyFont" horiz-adv-x="416">
+      <font-face font-family="MyFont" units-per-em="1000" panose-1="2 0 0 6 3 0 0 2 0 4" ascent="700" descent="-127" alphabetic="0"/>
+      <missing-glyph horiz-adv-x="233"/>
+      <glyph unicode=" " glyph-name="space" horiz-adv-x="233"/>
+      <glyph unicode="I" glyph-name="I" horiz-adv-x="330" d="M30 700V550H90V150H30V0H300V150H240V550H300V700H30Z"/>
+      <glyph unicode="t" glyph-name="t" horiz-adv-x="417" d="M5 550V410H137V0H280V410H412V550H5Z"/>
+      <glyph unicode="s" glyph-name="s" horiz-adv-x="468" d="M34 550V410V218H291V120H34V0H434V338H177V430H434V550H34Z"/>
+      <glyph unicode="&apos;" glyph-name="quotesingle" horiz-adv-x="198" d="M35 700L73 483H125L163 700H35Z"/>
+      <glyph unicode="a" glyph-name="a" horiz-adv-x="435" d="M71 550L3 0H143L154 119H282L293 0H433L365 550H71ZM168 259L182 410H254L268 259H168Z"/>
+      <glyph unicode="l" glyph-name="l" horiz-adv-x="435" d="M37 0H425V130H180V550H37V410V0Z"/>
+      <glyph unicode="i" glyph-name="i" horiz-adv-x="217" d="M37 550V410V0H180V550H37Z"/>
+      <glyph unicode="v" glyph-name="v" horiz-adv-x="430" d="M73 0H357L430 550H282L235 140H195L148 550H0L19 410L73 0Z"/>
+      <glyph unicode="e" glyph-name="e" horiz-adv-x="442" d="M37 550V410V0H419V130H180V210H299V340H180V420H419V550H37Z"/>
+      <glyph unicode="!" glyph-name="exclam" horiz-adv-x="237" d="M46 145V0H191V145H46ZM58 220H179L194 700H43L58 220Z"/>
+    </font>
+  </defs>
   <defs>
     <font-face font-family="SVGFreeSansASCII" unicode-range="U+0-7F">
       <font-face-src>
diff --git a/svg/import/animate-elem-25-t-manual.svg b/svg/import/animate-elem-25-t-manual.svg
index 8d2e0b6..bea4d99 100644
--- a/svg/import/animate-elem-25-t-manual.svg
+++ b/svg/import/animate-elem-25-t-manual.svg
@@ -60,21 +60,21 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <text font-family="Arial" font-size="14" x="30" y="40">Test animation options for specifying the target attribute/property.</text>
     <g xml:space="preserve" font-family="Arial" font-size="14" stroke-width="3" transform="translate(0,50)">
-			<text x="20" y="164">0-3 sec. </text>
-			<line x1="80" y1="160" x2="200" y2="160" fill="none" stroke="green"/>
-			<text x="20" y="124">at 6 sec. </text>
-			<line x1="80" y1="120" x2="200" y2="120" fill="none" stroke="green"/>
-			<rect id="rect1" x="100" y="80" width="100" height="80" fill="#FFFF00" stroke="#FF00FF" stroke-width="4">
-				<animate attributeName="height" from="80" to="40" begin="3s" dur="3s" fill="freeze"/>
-			</rect>
-			<text x="240" y="164">0-6 sec. </text>
-			<line x1="305" y1="160" x2="425" y2="160" fill="none" stroke="green"/>
-			<text x="240" y="124">at 9 sec. </text>
-			<line x1="305" y1="120" x2="425" y2="120" fill="none" stroke="green"/>
-			<rect id="rect2" x="325" y="80" width="100" height="80" fill="#FFFF00" stroke="#FF00FF" stroke-width="4">
-				<animate attributeName="height" attributeType="XML" from="80" to="40" begin="6s" dur="3s" fill="freeze"/>
-			</rect>
-		</g>
+      <text x="20" y="164">0-3 sec. </text>
+      <line x1="80" y1="160" x2="200" y2="160" fill="none" stroke="green"/>
+      <text x="20" y="124">at 6 sec. </text>
+      <line x1="80" y1="120" x2="200" y2="120" fill="none" stroke="green"/>
+      <rect id="rect1" x="100" y="80" width="100" height="80" fill="#FFFF00" stroke="#FF00FF" stroke-width="4">
+        <animate attributeName="height" from="80" to="40" begin="3s" dur="3s" fill="freeze"/>
+      </rect>
+      <text x="240" y="164">0-6 sec. </text>
+      <line x1="305" y1="160" x2="425" y2="160" fill="none" stroke="green"/>
+      <text x="240" y="124">at 9 sec. </text>
+      <line x1="305" y1="120" x2="425" y2="120" fill="none" stroke="green"/>
+      <rect id="rect2" x="325" y="80" width="100" height="80" fill="#FFFF00" stroke="#FF00FF" stroke-width="4">
+        <animate attributeName="height" attributeType="XML" from="80" to="40" begin="6s" dur="3s" fill="freeze"/>
+      </rect>
+    </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.7 $</text>
diff --git a/svg/import/animate-elem-27-t-manual.svg b/svg/import/animate-elem-27-t-manual.svg
index 939fbd7..088e01f 100644
--- a/svg/import/animate-elem-27-t-manual.svg
+++ b/svg/import/animate-elem-27-t-manual.svg
@@ -65,20 +65,20 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <text font-family="Arial" font-size="16" text-anchor="middle" x="225" y="40">Test animation options for specifying the target element.</text>
     <g xml:space="preserve" font-family="Arial" font-size="14" text-anchor="end" stroke-width="3">
-			<text x="80" y="244">0 to 3 sec. </text>
-			<line x1="80" y1="240" x2="200" y2="240" fill="none" stroke="green"/>
-			<text x="80" y="124">at 6 sec. </text>
-			<line x1="80" y1="120" x2="200" y2="120" fill="none" stroke="green"/>
-			<rect id="rect1" x="100" y="80" width="100" height="160" fill="blue" stroke="#36e" stroke-width="4"/>
-			<animate xlink:href="#rect1" attributeName="height" attributeType="XML" from="160" to="40" begin="3s" dur="3s" fill="freeze"/>
-			<text x="305" y="244">0 to 6 sec. </text>
-			<line x1="305" y1="240" x2="425" y2="240" fill="none" stroke="green"/>
-			<text x="305" y="124">at 9 sec. </text>
-			<line x1="305" y1="120" x2="425" y2="120" fill="none" stroke="green"/>
-			<rect id="rect2" x="325" y="80" width="100" height="160" fill="blue" stroke="#36e" stroke-width="4">
-				<animate attributeName="height" attributeType="XML" from="160" to="40" begin="6s" dur="3s" fill="freeze"/>
-			</rect>
-		</g>
+      <text x="80" y="244">0 to 3 sec. </text>
+      <line x1="80" y1="240" x2="200" y2="240" fill="none" stroke="green"/>
+      <text x="80" y="124">at 6 sec. </text>
+      <line x1="80" y1="120" x2="200" y2="120" fill="none" stroke="green"/>
+      <rect id="rect1" x="100" y="80" width="100" height="160" fill="blue" stroke="#36e" stroke-width="4"/>
+      <animate xlink:href="#rect1" attributeName="height" attributeType="XML" from="160" to="40" begin="3s" dur="3s" fill="freeze"/>
+      <text x="305" y="244">0 to 6 sec. </text>
+      <line x1="305" y1="240" x2="425" y2="240" fill="none" stroke="green"/>
+      <text x="305" y="124">at 9 sec. </text>
+      <line x1="305" y1="120" x2="425" y2="120" fill="none" stroke="green"/>
+      <rect id="rect2" x="325" y="80" width="100" height="160" fill="blue" stroke="#36e" stroke-width="4">
+        <animate attributeName="height" attributeType="XML" from="160" to="40" begin="6s" dur="3s" fill="freeze"/>
+      </rect>
+    </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.7 $</text>
diff --git a/svg/import/animate-elem-29-b-manual.svg b/svg/import/animate-elem-29-b-manual.svg
index 265f4b6..4d2da5a 100644
--- a/svg/import/animate-elem-29-b-manual.svg
+++ b/svg/import/animate-elem-29-b-manual.svg
@@ -21,7 +21,7 @@
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         First click once on "fade in" and
-        then, once the animation has completed, click once on "fade out". 
+        then, once the animation has completed, click once on "fade out".
         </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -43,7 +43,7 @@
         should remain looking the same way indefinitely, until another
         link is activated.
       </p>
-      	<!--
+        <!--
         With a second click on "fade in", however, the behavior might
         be different. In the case of having a first click on "fade in",
         waiting three seconds, and then immediately perform a first click
diff --git a/svg/import/animate-elem-31-t-manual.svg b/svg/import/animate-elem-31-t-manual.svg
index 60ff894..97780b7 100644
--- a/svg/import/animate-elem-31-t-manual.svg
+++ b/svg/import/animate-elem-31-t-manual.svg
@@ -18,9 +18,9 @@
         The purpose of this test is to test animation of the display attribute.
       </p>
       <p>
-        The test shows two gray rectangles which are filled with colored circles during the length of the animation (8 sec). 
+        The test shows two gray rectangles which are filled with colored circles during the length of the animation (8 sec).
         The circles in the top rectangle are displayed/hidden by animating the display attribute.
-        The circles in the bottom rectangle are serving as the reference and are displayed/hidden by animating the visibility attribute. 
+        The circles in the bottom rectangle are serving as the reference and are displayed/hidden by animating the visibility attribute.
         A correct implementation should display/hide circles with the same color from the top and bottom rectangle at the same time.
       </p>
       <p>
diff --git a/svg/import/animate-elem-33-t-manual.svg b/svg/import/animate-elem-33-t-manual.svg
index 4b50ae3..8854612 100644
--- a/svg/import/animate-elem-33-t-manual.svg
+++ b/svg/import/animate-elem-33-t-manual.svg
@@ -86,12 +86,12 @@
       </circle>
     </g>
     <!-- THIRD TEST-->
-    <!-- The lenght of the lines in the polyline (motionpath) are 100,50 and 100.
-		 The animated circle starts at the midpoint of one of the "legs" and also pass 
-		 this point at time 1.4 since 
-		 1.4 = 4*(0.25) + (4*(0.75-0.25))*(1/5).
-		 Where 0.25 is the time at keyPoint 1 (2nd) and 1/5 since the control circle is at 
-		 1/5 of the distance between keyPoint 1 and 0 (2nd and 3rd).-->
+    <!-- The length of the lines in the polyline (motionpath) are 100,50 and 100.
+     The animated circle starts at the midpoint of one of the "legs" and also pass
+     this point at time 1.4 since
+     1.4 = 4*(0.25) + (4*(0.75-0.25))*(1/5).
+     Where 0.25 is the time at keyPoint 1 (2nd) and 1/5 since the control circle is at
+     1/5 of the distance between keyPoint 1 and 0 (2nd and 3rd).-->
     <g transform="translate(110, 180) scale(0.6)">
       <text x="-115" y="-30" font-size="30" stroke="none" fill="#ccc">3</text>
       <text x="-85" y="-60" font-size="30" stroke="none" fill="#ccc">2.6</text>
diff --git a/svg/import/animate-elem-34-t-manual.svg b/svg/import/animate-elem-34-t-manual.svg
index ac78931..227f472 100644
--- a/svg/import/animate-elem-34-t-manual.svg
+++ b/svg/import/animate-elem-34-t-manual.svg
@@ -77,7 +77,6 @@
 
       </polygon>
 
-
       <!-- SECOND TEST, FILL-RULE-->
       <g transform="scale(0.3) translate(-450, 500)">
         <path fill-rule="nonzero" fill="#ccc" stroke="none" d="M 500,100 L 500,300 700,300 700,100 550,100 550,250 650,250 650,100 500,100 z">
@@ -105,7 +104,6 @@
       </g>
     </g>
 
-
     <text x="5" y="225" font-size="28">Animation on: 'points' and 'fill-rule'.</text>
     <text x="5" y="255" font-size="18">Digit should match outline at indicated time.</text>
     <text x="5" y="275" font-size="18">Filled square should follow morphing digit discretely.</text>
diff --git a/svg/import/animate-elem-36-t-manual.svg b/svg/import/animate-elem-36-t-manual.svg
index 460f76b..4719b5b 100644
--- a/svg/import/animate-elem-36-t-manual.svg
+++ b/svg/import/animate-elem-36-t-manual.svg
@@ -100,7 +100,6 @@
         <text y="60" text-anchor="middle">&lt;switch&gt;</text>
       </g>
 
-
       <g transform="translate(60,220)">
         <a id="animatedAnchor" xlink:href="" fill="rgb(230,230,230)" stroke="rgb(255,180,0)">
           <g>
@@ -130,7 +129,6 @@
         <text id="textID" fill="rgb(230,230,230)" stroke="rgb(255,180,0)" font-family="MyDecFont" font-size="40" text-anchor="middle">123</text>
         <animateTransform attributeName="transform" xlink:href="#textID" type="rotate" values="0;360;180;360" dur="3s"/>
 
-
         <text y="60" text-anchor="middle">&lt;text&gt;</text>
       </g>
 
diff --git a/svg/import/animate-elem-37-t-manual.svg b/svg/import/animate-elem-37-t-manual.svg
index c6c6ee3..cf6abec 100644
--- a/svg/import/animate-elem-37-t-manual.svg
+++ b/svg/import/animate-elem-37-t-manual.svg
@@ -32,7 +32,7 @@
       <p>The test passes if:</p>
       <ul>
         <li>each of the seven shapes rotates clockwise, then anti-clockwise, then clockwise again,</li>
-	<li>and the circle is scaled down, then up, then down again</li>
+  <li>and the circle is scaled down, then up, then down again</li>
       </ul>
       <p>over the course of three seconds.</p>
       <p>The static reference image shows the final state of the animation.</p>
diff --git a/svg/import/animate-elem-40-t-manual.svg b/svg/import/animate-elem-40-t-manual.svg
index c019e97..727ce3a 100644
--- a/svg/import/animate-elem-40-t-manual.svg
+++ b/svg/import/animate-elem-40-t-manual.svg
@@ -122,7 +122,6 @@
         <text text-anchor="middle" y="60">x/y on &lt;rect&gt;</text>
       </g>
 
-
       <g transform="translate(300,90)">
         <rect x="-15" y="-15" width="50" height="20" fill="black" stroke="rgb(255,180,0)">
           <animate attributeName="x" values="-15;15;15;-15;-15" begin="0s" dur="4s"/>
@@ -149,13 +148,11 @@
         <animate xlink:href="#textID" attributeName="x" values="-15;15;15;-15;-15" begin="0s" dur="4s"/>
         <animate xlink:href="#textID" attributeName="y" values="-15;-15;15;15;-15" begin="0s" dur="4s"/>
 
-
         <use xlink:href="#markerGroup"/>
 
         <text text-anchor="middle" y="60">x/y on &lt;text&gt;</text>
       </g>
 
-
       <g transform="translate(60,210)">
         <defs>
           <g id="markerGroup2" fill="rgb(230,230,230)" stroke="rgb(255,180,0)">
@@ -202,7 +199,6 @@
         <text text-anchor="middle" y="75">on &lt;image&gt;</text>
       </g>
 
-
       <g transform="translate(300,210)">
         <rect x="-40" y="-40" width="50" height="50" fill="gray">
           <animate attributeName="width" values="50;80;20;50" begin="0s" dur="3s"/>
diff --git a/svg/import/animate-elem-46-t-manual.svg b/svg/import/animate-elem-46-t-manual.svg
index 7fc7397..8f1e3e9 100644
--- a/svg/import/animate-elem-46-t-manual.svg
+++ b/svg/import/animate-elem-46-t-manual.svg
@@ -131,7 +131,6 @@
           <animate xlink:href="#textID_0" attributeName="text-anchor" values="end;middle;start" dur="3s" fill="freeze"/>
           <animateTransform xlink:href="#textID_0" attributeName="transform" type="translate" values="0;30;60" dur="3s" fill="freeze" calcMode="discrete"/>
 
-
           <g transform="translate(110, 0)" text-anchor="end" font-size="30" font-weight="bold">
             <animate attributeName="text-anchor" values="end;middle;start" dur="3s" fill="freeze"/>
             <animateTransform attributeName="transform" type="translate" values="0;30;60" calcMode="discrete" dur="3s" fill="freeze" additive="sum"/>
diff --git a/svg/import/animate-elem-62-t-manual.svg b/svg/import/animate-elem-62-t-manual.svg
index 43590af..e7a764d 100644
--- a/svg/import/animate-elem-62-t-manual.svg
+++ b/svg/import/animate-elem-62-t-manual.svg
@@ -145,7 +145,7 @@
         <text y="150">accessKey()</text>
         <text y="175">wallclock()</text>
       </g>
-      
+
       <g id="timeMarkersText" transform="translate(100, -15)" text-anchor="middle" font-size="8">
         <text>0s</text>
         <text x="12">1s</text>
diff --git a/svg/import/animate-elem-64-t-manual.svg b/svg/import/animate-elem-64-t-manual.svg
index f13a1e9..451e068 100644
--- a/svg/import/animate-elem-64-t-manual.svg
+++ b/svg/import/animate-elem-64-t-manual.svg
@@ -47,7 +47,7 @@
       <p>The test passes if after three seconds, in each of the three rows,
       the red rectangle is in the column at the times indicated.
       Thus, from the document load until 2s afterwards, the red
-      square in the first row must be in the right column, 
+      square in the first row must be in the right column,
       and after the 2s it must be in the left column.  In the
       other two rows, the red square must remain in the
       right column.</p>
diff --git a/svg/import/animate-elem-66-t-manual.svg b/svg/import/animate-elem-66-t-manual.svg
index ed11acd..0f6f279 100644
--- a/svg/import/animate-elem-66-t-manual.svg
+++ b/svg/import/animate-elem-66-t-manual.svg
@@ -106,7 +106,7 @@
         <text>&gt; 5s</text>
         <text x="80">0s-5s</text>
       </g>
-      
+
       <g transform="translate(150,40)" stroke="black">
         <defs>
           <rect id="shadow" x="-6" y="-6" width="12" height="12" fill="#ccc" stroke="black" stroke-width="1"/>
diff --git a/svg/import/animate-elem-70-t-manual.svg b/svg/import/animate-elem-70-t-manual.svg
index 7941ae5..8c91faf 100644
--- a/svg/import/animate-elem-70-t-manual.svg
+++ b/svg/import/animate-elem-70-t-manual.svg
@@ -127,7 +127,6 @@
           </rect>
         </g>
 
-
         <g id="setFour" transform="translate(0, 125)">
           <use xlink:href="#shadow" x="0"/>
           <use xlink:href="#shadow" x="80"/>
diff --git a/svg/import/animate-elem-77-t-manual.svg b/svg/import/animate-elem-77-t-manual.svg
index f71a033..5d0e146 100644
--- a/svg/import/animate-elem-77-t-manual.svg
+++ b/svg/import/animate-elem-77-t-manual.svg
@@ -85,7 +85,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The document is animated such that it alternates between two states, an alternation occurring every second. 
+        The document is animated such that it alternates between two states, an alternation occurring every second.
         For the test to pass each row must show a colored letter A that alternates between the two exact shapes and positions shown
         by the gray silhouettes.
       </p>
diff --git a/svg/import/animate-elem-80-t-manual.svg b/svg/import/animate-elem-80-t-manual.svg
index ba831cb..07426e1 100644
--- a/svg/import/animate-elem-80-t-manual.svg
+++ b/svg/import/animate-elem-80-t-manual.svg
@@ -190,7 +190,6 @@
         <text y="75" text-anchor="middle">(sx and sy)</text>
       </g>
 
-
       <g transform="translate(20,180)">
         <use xlink:href="#ref" x="40" y="40"/>
 
diff --git a/svg/import/animate-elem-81-t-manual.svg b/svg/import/animate-elem-81-t-manual.svg
index e6fd01c..9ec52ce 100644
--- a/svg/import/animate-elem-81-t-manual.svg
+++ b/svg/import/animate-elem-81-t-manual.svg
@@ -60,19 +60,19 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
+      <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>The test is passed if:</p>
       <ul>
-      	<li>the scale of the upper leftmost grey rectangle is smoothly animated over the course of 5 seconds to completely fill the upper leftmost yellow rectangle</li>
-      	<li>the scale and rotation of the second upper grey rectangle from the left is smoothly animated over the course of 5 seconds to fill the second upper yellow rectangle from the left</li>
-      	<li>the scale of the upper third grey rectangle from the left is smoothly animated over the course of 2.5 seconds to completely fill the upper third yellow rectangle from the left, and then repeated once so that at time t=5 seconds it completely fills the same yellow rectangle</li>
-      	<li>the scale of the upper rightmost grey rectangle is smoothly animated over the course of 5 seconds to completely fill the upper rightmost yellow rectangle</li>
-      	<li>the scale of the lower leftmost grey rectangle is smoothly animated over the course of 2.5 seconds to completely fill the lower leftmost yellow rectangle, and then repeated once so that at time t=5 seconds it completely fills the same yellow rectangle</li>
-      	<li>the scale of the lower rightmost grey rectangle is smoothly animated over the course of 5 seconds to completely fill the lower rightmost yellow rectangle</li>
+        <li>the scale of the upper leftmost grey rectangle is smoothly animated over the course of 5 seconds to completely fill the upper leftmost yellow rectangle</li>
+        <li>the scale and rotation of the second upper grey rectangle from the left is smoothly animated over the course of 5 seconds to fill the second upper yellow rectangle from the left</li>
+        <li>the scale of the upper third grey rectangle from the left is smoothly animated over the course of 2.5 seconds to completely fill the upper third yellow rectangle from the left, and then repeated once so that at time t=5 seconds it completely fills the same yellow rectangle</li>
+        <li>the scale of the upper rightmost grey rectangle is smoothly animated over the course of 5 seconds to completely fill the upper rightmost yellow rectangle</li>
+        <li>the scale of the lower leftmost grey rectangle is smoothly animated over the course of 2.5 seconds to completely fill the lower leftmost yellow rectangle, and then repeated once so that at time t=5 seconds it completely fills the same yellow rectangle</li>
+        <li>the scale of the lower rightmost grey rectangle is smoothly animated over the course of 5 seconds to completely fill the lower rightmost yellow rectangle</li>
       </ul>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/animate-elem-82-t-manual.svg b/svg/import/animate-elem-82-t-manual.svg
index b0c29a8..90a21f4 100644
--- a/svg/import/animate-elem-82-t-manual.svg
+++ b/svg/import/animate-elem-82-t-manual.svg
@@ -206,7 +206,7 @@
         <text y="75" text-anchor="middle">linear translation</text>
 
       </g>
-      
+
       <g transform="translate(60,220)">
         <rect x="-15" y="-15" width="30" height="30" stroke="none" fill="rgb(230,230,230)">
           <animateTransform attributeName="transform" attributeType="XML" type="scale" values="1,2;3,2;1,1" dur="3s" calcMode="paced" fill="freeze"/>
@@ -285,7 +285,6 @@
         <text y="75" text-anchor="middle">linear rotation</text>
       </g>
 
-
     </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
diff --git a/svg/import/animate-elem-83-t-manual.svg b/svg/import/animate-elem-83-t-manual.svg
index b64a7c6..2de478b 100644
--- a/svg/import/animate-elem-83-t-manual.svg
+++ b/svg/import/animate-elem-83-t-manual.svg
@@ -79,11 +79,11 @@
       gray shapes morphs appropriately according to the following descriptions:</p>
       <ol>
         <li>The gray shape must morph from the diamond to the flower-like shape.</li>
-	<li>The gray shape must morph just like sub-test #1, from the diamond to the flower-like shape.</li>
-	<li>The gray shape must morph from the diamond, to the flower-like shape, and then to the large, rounded diamond shape.</li>
-	<li>The gray shape must morph from the lower-right pointing kite shape to the upper-left pointing kite shape.</li>
-	<li>The gray shape must morph from the tall shape to the wide shape.</li>
-	<li>The gray shape must morph from the wide "D" shape to the narrow "D" shape.</li>
+  <li>The gray shape must morph just like sub-test #1, from the diamond to the flower-like shape.</li>
+  <li>The gray shape must morph from the diamond, to the flower-like shape, and then to the large, rounded diamond shape.</li>
+  <li>The gray shape must morph from the lower-right pointing kite shape to the upper-left pointing kite shape.</li>
+  <li>The gray shape must morph from the tall shape to the wide shape.</li>
+  <li>The gray shape must morph from the wide "D" shape to the narrow "D" shape.</li>
       </ol>
       <p>In addition, during the animations whenever the gray shape has the same shape as
       a reference shape, the stroke of the reference shape must be shown thicker momentarily.</p>
diff --git a/svg/import/animate-elem-84-t-manual.svg b/svg/import/animate-elem-84-t-manual.svg
index cdf35fb..4288f7a 100644
--- a/svg/import/animate-elem-84-t-manual.svg
+++ b/svg/import/animate-elem-84-t-manual.svg
@@ -15,17 +15,17 @@
     version="$Revision: 1.6 $" testname="$RCSfile: animate-elem-84-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/animate.html#Animation">
         <p>
-	Test animation of color keywords that resolve to animatable RGB values.
+  Test animation of color keywords that resolve to animatable RGB values.
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        Run the test. No interaction required. 
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-	The test is passed if five
+  The test is passed if five
         black squares are shown, after two seconds, all five squares  turn red and
         then smoothly animate the fill color to green over the next five seconds.
       </p>
diff --git a/svg/import/animate-elem-85-t-manual.svg b/svg/import/animate-elem-85-t-manual.svg
index cecac6a..6c09ffb 100644
--- a/svg/import/animate-elem-85-t-manual.svg
+++ b/svg/import/animate-elem-85-t-manual.svg
@@ -15,9 +15,9 @@
     version="$Revision: 1.8 $" testname="$RCSfile: animate-elem-85-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/animate.html#Animation">
         <p>
-          The first subtest tests animateColor with 'to' and 'from' values including 
+          The first subtest tests animateColor with 'to' and 'from' values including
           currentColor. The second subtest checks that the value of currentColor is the
-           current animated value of the color property, by animating the color property 
+           current animated value of the color property, by animating the color property
            at the same time as animating fill with a 'from' or 'to' value of currentColor.
         </p>
         <!-- moved from accepted to issue pending verification of correct behaviour for lower rect on second subtest -->
@@ -31,9 +31,9 @@
         The first subtest is passed if all
          four  rectangles at the top  smoothly animate from black to green over 5 seconds.
         During this time the bottom two rectangles must be blue.</p>
-        <p>The second subtest, which starts after the first one completes,  is passed if 
-        the bottom two rectangles  smoothly animate from green (at five seconds), through 
-        dark cyan (at 7.5 seconds), to cyan (at 10 seconds and above). Colored circles 
+        <p>The second subtest, which starts after the first one completes,  is passed if
+        the bottom two rectangles  smoothly animate from green (at five seconds), through
+        dark cyan (at 7.5 seconds), to cyan (at 10 seconds and above). Colored circles
         indicate the appropriate colors at these times.
       </p>
     </d:passCriteria>
@@ -75,8 +75,8 @@
       <animateColor attributeName="color" to="cyan" from="blue" begin="5s" dur="5s" fill="freeze"/>
     </rect>
     <!--
-            color     fill      
-      4s    #000000   #0000ff   
+            color     fill
+      4s    #000000   #0000ff
       5s    #0000ff   #008000
       7.5s  #0080ff   #008080
       10s   #00ffff   #00ffff
diff --git a/svg/import/animate-elem-87-t-manual.svg b/svg/import/animate-elem-87-t-manual.svg
index 812c98e..544ff9c 100644
--- a/svg/import/animate-elem-87-t-manual.svg
+++ b/svg/import/animate-elem-87-t-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -56,7 +56,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
+
     <text x='10' y='40'>Test zero value of a scale transform animation</text>
 
     <g transform='translate(150,150)'>
@@ -73,17 +73,17 @@
       </circle>
       <text y='100' text-anchor='middle' font-size='12'>&lt;animateTransform type='scale' by='1'/&gt;</text>
     </g>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.5 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved -->
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
 </svg>
diff --git a/svg/import/animate-elem-88-t-manual.svg b/svg/import/animate-elem-88-t-manual.svg
index d523f77..00c7eca 100644
--- a/svg/import/animate-elem-88-t-manual.svg
+++ b/svg/import/animate-elem-88-t-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -16,7 +16,7 @@
         This tests that any which space before semicolon separators in
         a <code>values=""</code> attribute on an animation element is ignored.
       </p>
-    	<p>
+      <p>
         The test consists of a single rectangle whose height is animated
         with a <code>values=" 0 ; 50 "</code> attribute.
       </p>
@@ -45,7 +45,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
+
     <text x='10' y='40'>Test values attribute list syntax</text>
 
     <rect x='10' y='50' width='50' height='0'>
@@ -54,14 +54,14 @@
 
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.6 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/animate-elem-89-t-manual.svg b/svg/import/animate-elem-89-t-manual.svg
index 07aee20..fa63c97 100644
--- a/svg/import/animate-elem-89-t-manual.svg
+++ b/svg/import/animate-elem-89-t-manual.svg
@@ -21,13 +21,13 @@
         Test possible values for 'calcMode="spline"', with both commas, whitespace, and mixed separators
       </p>
       <p>
-        Six animations (three sets of two) have been defined. The three green ones on the left show rectangles which get smaller. The three orange ones on the right show rectangles of constant size, which move. 
+        Six animations (three sets of two) have been defined. The three green ones on the left show rectangles which get smaller. The three orange ones on the right show rectangles of constant size, which move.
         The black text and grey ruler lines help show the sizes and movement of the rectangles over time.
 <!-- derived from animate-elem-12-t.svg: by duplicating the animated rectangles, then changing the list separators -->
       </p>
-  	</d:testDescription>
+    </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>
+      <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
@@ -60,11 +60,11 @@
           <line x1="70" y1="111" x2="450" y2="111"/>
           <line x1="70" y1="0" x2="450" y2="0"/>
         </g>
-	</g>
+  </g>
 
 <!-- first set of three subtests, keySplines on animation element -->
       <g transform="translate(60,50)">
-       
+
         <rect x="140" y="-10" width="50" height="210" fill="rgb(34, 139, 34)" stroke="#555" stroke-width="4">
           <animate attributeName="height" calcMode="spline" keySplines="0,0,1,1;0,0,1,1;.75,0,0,.75" values="210;177;121;10" begin="0s" dur="9s" fill="freeze"/>
 <!-- commas -->
@@ -78,7 +78,7 @@
 <!-- commas and spaces -->
         </rect>
       </g>
-      
+
 <!-- second set of three subtests, keySplines on animateMotion element -->
       <g transform="translate(250,50)">
 
diff --git a/svg/import/animate-elem-90-b-manual.svg b/svg/import/animate-elem-90-b-manual.svg
index 1d0d1b0..55d53dd 100644
--- a/svg/import/animate-elem-90-b-manual.svg
+++ b/svg/import/animate-elem-90-b-manual.svg
@@ -15,8 +15,8 @@
     version="$Revision: 1.3 $" testname="$RCSfile: animate-elem-90-b.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/animate.html#Animation">
         <p>
-          Test that the class attribute is animatable and that style 
-	sheets select on the animated value.
+          Test that the class attribute is animatable and that style
+  sheets select on the animated value.
 <!-- not clear whether to link to dev.w3.org or to /TR
 http://dev.w3.org/SVG/profiles/1.1F2/publish/styling.html#ClassAttribute
 -->
@@ -29,28 +29,28 @@
         and 'animate'. It requires that CSS style sheets are supported.
       </p>
       <p>
-        The test shows a circle which is initially hidden, becomes visible and blue at 
-	3s, abruptly changing to dark red at 5s. Two overlapping animations both animate the 
-	class attribute. The class attribute, as a string value, does not support 
-	linear interpolation so a discrete animation is produced, changing from the 
-	start to the end value midway through the animation duration.
+        The test shows a circle which is initially hidden, becomes visible and blue at
+  3s, abruptly changing to dark red at 5s. Two overlapping animations both animate the
+  class attribute. The class attribute, as a string value, does not support
+  linear interpolation so a discrete animation is produced, changing from the
+  start to the end value midway through the animation duration.
       </p>
       <p>
-	The first animation starts at 2s and lasts for 4s so  the mid point is at 3s.
-	The second animation starts at 3s and lasts for 4s so the midpoint is at 5s.
+  The first animation starts at 2s and lasts for 4s so  the mid point is at 3s.
+  The second animation starts at 3s and lasts for 4s so the midpoint is at 5s.
         The file includes various guides that can be used to verify the
         correctness of the animation. The value of the class attribute
-	at 02 is "start" so the first CSS rule matches. At 3s it becomes "midway" 
-	so the second rule matches. At 5s it becomes "final midway" so the second and 
-	third rules match; the third rule has higher specificity so determines the fill color.
-        
+  at 02 is "start" so the first CSS rule matches. At 3s it becomes "midway"
+  so the second rule matches. At 5s it becomes "final midway" so the second and
+  third rules match; the third rule has higher specificity so determines the fill color.
+
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The color of the large circle must match the colour of the smaller guide 
-	boxes on the left  at times 0s, 3s and 5s. If the text "CSS not supported"
-	is visible, the test does not apply.
+        The color of the large circle must match the colour of the smaller guide
+  boxes on the left  at times 0s, 3s and 5s. If the text "CSS not supported"
+  is visible, the test does not apply.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -62,10 +62,10 @@
       </font-face-src>
     </font-face>
     <style type="text/css">
-	.start {visibility: hidden }
-	.midway {visibility: visible; fill: rgb(0,0,255); }
-	#test-body-content .final {fill: rgb(128,0,0); }
-	.hideme {display: none;}
+  .start {visibility: hidden }
+  .midway {visibility: visible; fill: rgb(0,0,255); }
+  #test-body-content .final {fill: rgb(128,0,0); }
+  .hideme {display: none;}
     </style>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
diff --git a/svg/import/animate-interact-events-01-t-manual.svg b/svg/import/animate-interact-events-01-t-manual.svg
index a0014c5..6f5195a 100644
--- a/svg/import/animate-interact-events-01-t-manual.svg
+++ b/svg/import/animate-interact-events-01-t-manual.svg
@@ -22,9 +22,9 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
-    		Mouseover each of the red rectangles, and then click on the bottommost rectangle.
-    	</p>
+      <p>
+        Mouseover each of the red rectangles, and then click on the bottommost rectangle.
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
@@ -52,13 +52,13 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	
-		<defs>
-			<!-- SVGElementInstance animates fill on mouseover -->
-			<rect id="rect" width="50" height="50" fill="red">
-				<set attributeName="fill" begin="mouseover" end="mouseout" to="blue"/>
-			</rect>
-		</defs>
+
+    <defs>
+      <!-- SVGElementInstance animates fill on mouseover -->
+      <rect id="rect" width="50" height="50" fill="red">
+        <set attributeName="fill" begin="mouseover" end="mouseout" to="blue"/>
+      </rect>
+    </defs>
 
     <text x="120" y="20" font-size="15">Shadow tree event listener chain</text>
 
diff --git a/svg/import/animate-interact-pevents-02-t-manual.svg b/svg/import/animate-interact-pevents-02-t-manual.svg
index 9993ec6..4629019 100644
--- a/svg/import/animate-interact-pevents-02-t-manual.svg
+++ b/svg/import/animate-interact-pevents-02-t-manual.svg
@@ -107,7 +107,7 @@
       fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/animate-interact-pevents-03-t-manual.svg b/svg/import/animate-interact-pevents-03-t-manual.svg
index 6fff4f9..90cb9e1 100644
--- a/svg/import/animate-interact-pevents-03-t-manual.svg
+++ b/svg/import/animate-interact-pevents-03-t-manual.svg
@@ -210,7 +210,7 @@
       fill="black">$Revision: 1.2 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/animate-interact-pevents-04-t-manual.svg b/svg/import/animate-interact-pevents-04-t-manual.svg
index c0a45e8..be6ea43 100644
--- a/svg/import/animate-interact-pevents-04-t-manual.svg
+++ b/svg/import/animate-interact-pevents-04-t-manual.svg
@@ -199,7 +199,7 @@
       fill="black">$Revision: 1.2 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/animate-pservers-grad-01-b-manual.svg b/svg/import/animate-pservers-grad-01-b-manual.svg
index 8f0fdb9..98456fb 100644
--- a/svg/import/animate-pservers-grad-01-b-manual.svg
+++ b/svg/import/animate-pservers-grad-01-b-manual.svg
@@ -20,7 +20,7 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Load the svg and wait 5 seconds for the animation to run, then compare the image to the reference.
+        Load the svg and wait 5 seconds for the animation to run, then compare the image to the reference.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/animate-script-elem-01-b-manual.svg b/svg/import/animate-script-elem-01-b-manual.svg
index c6da8c5..4148881 100644
--- a/svg/import/animate-script-elem-01-b-manual.svg
+++ b/svg/import/animate-script-elem-01-b-manual.svg
@@ -28,7 +28,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
+      <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
diff --git a/svg/import/color-prof-01-f-manual.svg b/svg/import/color-prof-01-f-manual.svg
index 7211dd0..6919c04 100644
--- a/svg/import/color-prof-01-f-manual.svg
+++ b/svg/import/color-prof-01-f-manual.svg
@@ -21,13 +21,13 @@
         </p>
         <!-- the issue is that SVG 1.1 does not require ICC color profile support, it is optional.
         So the pass criteria are incorrect. In fact, this is untestable as the feature is purely
-        optional and does not correspond to a named conformance class. 
+        optional and does not correspond to a named conformance class.
         Fixed in the SVG Color module.
         -->
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        
+
       </p>
       <p>
         Run the test. No interaction required.
@@ -36,8 +36,8 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         If the two images (each of 9 colored squares) look identical, the test fails.
-	If the colours in the lower right image are more saturated, brighter versions of 
-	those in the top left image, as shown by the reference image, the test is passed.
+  If the colours in the lower right image are more saturated, brighter versions of
+  those in the top left image, as shown by the reference image, the test is passed.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/color-prop-01-b-manual.svg b/svg/import/color-prop-01-b-manual.svg
index 5b7366a..ba5f108 100644
--- a/svg/import/color-prop-01-b-manual.svg
+++ b/svg/import/color-prop-01-b-manual.svg
@@ -29,7 +29,7 @@
          There are three subtests. The first subtest, to the top left, is passed if the circle has a green fill. The second subtest,
         to the top right, is passed if the circle has a green stroke. The third subtest shows a rectangle
         with a gradient fill, which has three stops. The subtest is passed if central stop is green,
-        fading off to blue to the left and pale 	yellow to the right.
+        fading off to blue to the left and pale   yellow to the right.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/color-prop-02-f-manual.svg b/svg/import/color-prop-02-f-manual.svg
index 3fcc04d..3f1870d 100644
--- a/svg/import/color-prop-02-f-manual.svg
+++ b/svg/import/color-prop-02-f-manual.svg
@@ -16,9 +16,9 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/color.html#ColorProperty">
         <p>
           Tests if the color datatype is supported. There are multiple syntaxes for
-        specifying the same color, such as #37F and #3377FF. This test is focussed on the 
-	X11 color names, which are not part of the tiny profile.
-	Each group of circles uses four forms - 6-digit hex, rbg() integer form, rgb() percentage form,
+        specifying the same color, such as #37F and #3377FF. This test is focussed on the
+  X11 color names, which are not part of the tiny profile.
+  Each group of circles uses four forms - 6-digit hex, rbg() integer form, rgb() percentage form,
         and named ('X11') colors. It does not use 3-digit hex, because the colors used in this test
         cannot be represented in three digit form.
         </p>
@@ -31,7 +31,7 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         For each of the nine groups of circles shown here, all circles must
-        be identical in color, and the same color as in the reference image. 
+        be identical in color, and the same color as in the reference image.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/color-prop-03-t-manual.svg b/svg/import/color-prop-03-t-manual.svg
index 466542e..c8c9508 100644
--- a/svg/import/color-prop-03-t-manual.svg
+++ b/svg/import/color-prop-03-t-manual.svg
@@ -18,7 +18,7 @@
           Tests if the color datatype is supported. There are multiple syntaxes for
         specifying the same color, such as #37F and #3377FF.
 For each of the six groups shown here,
-        each of the circles in the group uses one of the syntactical forms 
+        each of the circles in the group uses one of the syntactical forms
         </p>
  <p>
         The first row uses five forms - 3-digit hex, 6-digit hex, rbg() integer form, rgb() percentage form,
@@ -33,12 +33,12 @@
       <p>
         Run the test. No interaction required.
       </p>
-     
+
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         For each of the six groups of circles shown here, all circles must
-        be identical in color, and the same color as in the reference image. 
+        be identical in color, and the same color as in the reference image.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -75,7 +75,7 @@
       <polygon points="345,40 365,60 345,80 325,60" fill="blue"/>
     </g>
     <!-- no names for three digit colors except for 00 and ff -->
-    <!-- 11=17 22=34 33=51 44=68 55=85 66=102 77=119 
+    <!-- 11=17 22=34 33=51 44=68 55=85 66=102 77=119
      88=136 99=153 aa=170 bb=187 cc=204 dd=221 ee=238 -->
     <g>
       <circle cx="75" cy="135" r="20" fill="#a01"/>
diff --git a/svg/import/color-prop-04-t-manual.svg b/svg/import/color-prop-04-t-manual.svg
index e481fc9..3452176 100644
--- a/svg/import/color-prop-04-t-manual.svg
+++ b/svg/import/color-prop-04-t-manual.svg
@@ -19,7 +19,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>Run the test. No interaction required.</p>
+  <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/conform-viewers-02-f-manual.svg b/svg/import/conform-viewers-02-f-manual.svg
index 53e570e..70ce7f6 100644
--- a/svg/import/conform-viewers-02-f-manual.svg
+++ b/svg/import/conform-viewers-02-f-manual.svg
@@ -45,9 +45,9 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-		<text x="240" y="40" text-anchor="middle">Test data uri with svgz content</text>
-		
-		<text fill="black" x="50%" y="200" text-anchor="middle">FAILED</text>
+    <text x="240" y="40" text-anchor="middle">Test data uri with svgz content</text>
+
+    <text fill="black" x="50%" y="200" text-anchor="middle">FAILED</text>
     <image xlink:href="data:image/svg+xml;base64,H4sICA/BlksCA3N0YXIuc3ZnAMVUwW7bMAw9d8D+QVAvLeDIkiha0mDn0A7oZcMO
 Wz/Aa+zUgGsHttq0+/pRTtwkwLBuwIAhcPRIihT5RCofn9bsqam2V/1zwSWTTGfT
 x9nzQ9uNBb8PYfMhTbfbrdiC6Id1qqWUKfnx5ft3jOWrqh4nRLhu2lANrFkV/Hv7
diff --git a/svg/import/conform-viewers-03-f-manual.svg b/svg/import/conform-viewers-03-f-manual.svg
index 4cfb4e7..61f92bf 100644
--- a/svg/import/conform-viewers-03-f-manual.svg
+++ b/svg/import/conform-viewers-03-f-manual.svg
@@ -57,7 +57,7 @@
       /**
        * Generates a 20 character string consisting of characters from the inchars parameter.
        * The output string will have no repetitions of a character.
-       */	 
+       */
       function generatePrefix(inchars)
       {
         var ncNameStart = "_abcdefghijklmnopqrstuvwxyzåäöQWERTYUIOPÅÄÖLKJHGFDSAZXCVBNM";
@@ -74,7 +74,7 @@
         return prefix;
       }
 
-      /** 
+      /**
        * The main body of the test.
        */
       function test()
@@ -114,7 +114,7 @@
       fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/coords-dom-01-f-manual.svg b/svg/import/coords-dom-01-f-manual.svg
index c254ae7..e8507d9 100644
--- a/svg/import/coords-dom-01-f-manual.svg
+++ b/svg/import/coords-dom-01-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -42,27 +42,26 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
-    
+
     <g transform="translate(240 180)">
       <g id="reference">
         <circle r="40" fill="red"/>
       </g>
-    
+
       <g id="g" transform="translate(20 20)">
         <circle id="c" r="41" fill="blue"/>
       </g>
-    </g>   
-    
+    </g>
+
     <script type="text/ecmascript"><![CDATA[
       var eps = 1 / 65535; // 16.16 fixpoint epsilon
       var passed = false;
-      
+
       function isequal( value, expected, epsilon )
       {
         return(Math.abs(value - expected) < epsilon);
       }
- 
+
       try
       {
         var g = document.getElementById("g");
@@ -89,7 +88,7 @@
         }
       }
       catch(e) {}
-      
+
       if(passed)
       {
         c.setAttribute("fill", "lime");
@@ -99,17 +98,17 @@
         c.setAttribute("fill", "red");
       }
     ]]></script>
-        
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.8 $</text>
   </g>
   <rect xml:id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/coords-dom-02-f-manual.svg b/svg/import/coords-dom-02-f-manual.svg
index 42cac3b..00b56f5 100644
--- a/svg/import/coords-dom-02-f-manual.svg
+++ b/svg/import/coords-dom-02-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -42,21 +42,20 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
-    
+
     <g transform="translate(220 160)">
       <g id="reference">
         <circle r="41" fill="red" transform="translate(20 20) scale(2 1)"/>
       </g>
-    
+
       <g id="g" transform="translate(20 20)">
         <circle id="c" r="41" fill="blue"/>
       </g>
-    </g>   
-    
+    </g>
+
     <script type="text/ecmascript"><![CDATA[
       var passed = false;
-      
+
       try
       {
         var g = document.getElementById("g");
@@ -70,7 +69,7 @@
         }
       }
       catch(e) {}
-      
+
       if(passed)
       {
         c.setAttribute("fill", "lime");
@@ -80,17 +79,17 @@
         c.setAttribute("fill", "red");
       }
     ]]></script>
-        
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.9 $</text>
   </g>
   <rect xml:id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/coords-dom-04-f-manual.svg b/svg/import/coords-dom-04-f-manual.svg
index 9e620bc..da360e0 100644
--- a/svg/import/coords-dom-04-f-manual.svg
+++ b/svg/import/coords-dom-04-f-manual.svg
@@ -13,7 +13,7 @@
     version="$Revision: 1.5 $" testname="$RCSfile: coords-dom-04-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGTransformList">
       <p>
-	  	The test checks the SVGTransformList.consolidate method.
+      The test checks the SVGTransformList.consolidate method.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -23,10 +23,10 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-		There must be 13 green rectangles visible. 
-		The text next to the first rectangle must say "Scripting enabled".
-		The other 12 lines must each say "Passed subtest #n" where n is the subtest number 1..12.
-		If anything red shows, the test has failed.
+    There must be 13 green rectangles visible.
+    The text next to the first rectangle must say "Scripting enabled".
+    The other 12 lines must each say "Passed subtest #n" where n is the subtest number 1..12.
+    If anything red shows, the test has failed.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -41,116 +41,116 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	<defs>
-		<style>
-			#test-body-content rect { stroke: black; }
-		</style>
-		<script type="text/ecmascript"><![CDATA[
-		var pos = { "x": 20, "y": 40 };
-		var subtestCounter = 1;
-		var passed = true;
-		
-		function toString(m)
-		{	
-			var decimals = 0;
-			return m.a.toFixed(decimals) + "," + 
-				   m.b.toFixed(decimals) + "," + 
-				   m.c.toFixed(decimals) + "," + 
-				   m.d.toFixed(decimals) + "," + 
-				   m.e.toFixed(decimals) + "," + 
-				   m.f.toFixed(decimals);
-		}
+  <defs>
+    <style>
+      #test-body-content rect { stroke: black; }
+    </style>
+    <script type="text/ecmascript"><![CDATA[
+    var pos = { "x": 20, "y": 40 };
+    var subtestCounter = 1;
+    var passed = true;
 
-		function referenceEqual(m1,ref,eps)
-		{
-			return (Math.abs(m1.a-ref[0]) < eps &&
-					Math.abs(m1.b-ref[1]) < eps &&
-					Math.abs(m1.c-ref[2]) < eps &&
-					Math.abs(m1.d-ref[3]) < eps &&
-					Math.abs(m1.e-ref[4]) < eps &&
-					Math.abs(m1.f-ref[5]) < eps);
-		}
+    function toString(m)
+    {
+      var decimals = 0;
+      return m.a.toFixed(decimals) + "," +
+           m.b.toFixed(decimals) + "," +
+           m.c.toFixed(decimals) + "," +
+           m.d.toFixed(decimals) + "," +
+           m.e.toFixed(decimals) + "," +
+           m.f.toFixed(decimals);
+    }
 
-		function assertEquals(m, ref, eps)
-		{
-			var result = document.createElementNS("http://www.w3.org/2000/svg", "text");
-			var resultrect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
-			result.setAttribute("x", pos.x);
-			result.setAttribute("y", pos.y);
-			resultrect.setAttribute("fill", "lime");
-			resultrect.setAttribute("width", 15);
-			resultrect.setAttribute("height", 15);
-			resultrect.setAttribute("y", pos.y-15);
-			
-			if(referenceEqual(m, ref, eps))
-			{	
-				result.textContent = "Passed subtest #" + subtestCounter;
-			}
-			else
-			{
-				passed = false;
-				result.textContent = "Failed subtest #" + subtestCounter + ". Expected " + ref + " but got " + toString(m);
-			}
-			pos.y += 20;
-			subtestCounter++;
-			var results = document.getElementById("subteststatus");
-			results.appendChild(resultrect)
-			results.appendChild(result);
-		}
-		
-		function test()
-		{
-			eps = 0.005; // "close enough"
-			r = document.getElementById("r");
+    function referenceEqual(m1,ref,eps)
+    {
+      return (Math.abs(m1.a-ref[0]) < eps &&
+          Math.abs(m1.b-ref[1]) < eps &&
+          Math.abs(m1.c-ref[2]) < eps &&
+          Math.abs(m1.d-ref[3]) < eps &&
+          Math.abs(m1.e-ref[4]) < eps &&
+          Math.abs(m1.f-ref[5]) < eps);
+    }
 
-			t1 = r.transform.baseVal.getItem(0);
-			t2 = r.transform.baseVal.getItem(1);
-			
-			// check that matrices are as specified in the markup
-			assertEquals(t1.matrix, [1, 0, 0, 1, 10, 10], eps);
-			assertEquals(t2.matrix, [0, 1, -1, 0, 0, 0], eps);
+    function assertEquals(m, ref, eps)
+    {
+      var result = document.createElementNS("http://www.w3.org/2000/svg", "text");
+      var resultrect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+      result.setAttribute("x", pos.x);
+      result.setAttribute("y", pos.y);
+      resultrect.setAttribute("fill", "lime");
+      resultrect.setAttribute("width", 15);
+      resultrect.setAttribute("height", 15);
+      resultrect.setAttribute("y", pos.y-15);
 
-			// consolidate
-			tfm = r.transform.baseVal.consolidate();
-			
-			// check that the consolidation is ok
-			assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 10], eps);
-			
-			// check that t1 and t2 were not affected by the consolidation
-			assertEquals(t1.matrix, [1, 0, 0, 1, 10, 10], eps);
-			assertEquals(t2.matrix, [0, 1, -1, 0, 0, 0], eps);
-			
-			// check that modifying t1 has no effect on the consolidated transform
-			t1.setTranslate(10,200);
-			assertEquals(t1.matrix, [1, 0, 0, 1, 10, 200], eps);
-			assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 10], eps);
-			
-			// check that modifying t2 has no effect on the consolidated transform
-			t2.setRotate(-90, 0, 0);
-			assertEquals(t2.matrix, [0, -1, 1, 0, 0, 0], eps);
-			assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 10], eps);
-			
-			// check that modifying the consolidated transform has no effect on the t1 and t2 transforms
-			tfm.matrix.f = 400;
-			assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 400], eps);
-			assertEquals(t1.matrix, [1, 0, 0, 1, 10, 200], eps);
-			assertEquals(t2.matrix, [0, -1, 1, 0, 0, 0], eps);
-		
-			document.getElementById("status").setAttributeNS(null, "fill", passed ? "lime" : "red");
-			document.getElementById("scriptstatus").textContent = "Scripting enabled";
-		}
-	
-	]]></script>
-	</defs>
+      if(referenceEqual(m, ref, eps))
+      {
+        result.textContent = "Passed subtest #" + subtestCounter;
+      }
+      else
+      {
+        passed = false;
+        result.textContent = "Failed subtest #" + subtestCounter + ". Expected " + ref + " but got " + toString(m);
+      }
+      pos.y += 20;
+      subtestCounter++;
+      var results = document.getElementById("subteststatus");
+      results.appendChild(resultrect)
+      results.appendChild(result);
+    }
 
-	<g transform="translate(20 -10)">
-		<g id="subteststatus" transform="translate(0,40)">
-			<rect id="status" y="5" width="15" height="15" fill="red"/>
-			<text id="scriptstatus" y="20" x="20" >Scripting disabled</text>
-		</g>
-	
-		<polyline id="r" fill="none" stroke="green" display="none" transform="translate(10 10) rotate(90)" points="0 0 30 40 80 -20" stroke-width="10"/>
-	</g>
+    function test()
+    {
+      eps = 0.005; // "close enough"
+      r = document.getElementById("r");
+
+      t1 = r.transform.baseVal.getItem(0);
+      t2 = r.transform.baseVal.getItem(1);
+
+      // check that matrices are as specified in the markup
+      assertEquals(t1.matrix, [1, 0, 0, 1, 10, 10], eps);
+      assertEquals(t2.matrix, [0, 1, -1, 0, 0, 0], eps);
+
+      // consolidate
+      tfm = r.transform.baseVal.consolidate();
+
+      // check that the consolidation is ok
+      assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 10], eps);
+
+      // check that t1 and t2 were not affected by the consolidation
+      assertEquals(t1.matrix, [1, 0, 0, 1, 10, 10], eps);
+      assertEquals(t2.matrix, [0, 1, -1, 0, 0, 0], eps);
+
+      // check that modifying t1 has no effect on the consolidated transform
+      t1.setTranslate(10,200);
+      assertEquals(t1.matrix, [1, 0, 0, 1, 10, 200], eps);
+      assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 10], eps);
+
+      // check that modifying t2 has no effect on the consolidated transform
+      t2.setRotate(-90, 0, 0);
+      assertEquals(t2.matrix, [0, -1, 1, 0, 0, 0], eps);
+      assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 10], eps);
+
+      // check that modifying the consolidated transform has no effect on the t1 and t2 transforms
+      tfm.matrix.f = 400;
+      assertEquals(tfm.matrix, [0, 1, -1, 0, 10, 400], eps);
+      assertEquals(t1.matrix, [1, 0, 0, 1, 10, 200], eps);
+      assertEquals(t2.matrix, [0, -1, 1, 0, 0, 0], eps);
+
+      document.getElementById("status").setAttributeNS(null, "fill", passed ? "lime" : "red");
+      document.getElementById("scriptstatus").textContent = "Scripting enabled";
+    }
+
+  ]]></script>
+  </defs>
+
+  <g transform="translate(20 -10)">
+    <g id="subteststatus" transform="translate(0,40)">
+      <rect id="status" y="5" width="15" height="15" fill="red"/>
+      <text id="scriptstatus" y="20" x="20" >Scripting disabled</text>
+    </g>
+
+    <polyline id="r" fill="none" stroke="green" display="none" transform="translate(10 10) rotate(90)" points="0 0 30 40 80 -20" stroke-width="10"/>
+  </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/coords-trans-10-f-manual.svg b/svg/import/coords-trans-10-f-manual.svg
index b50b4c3..b862af0 100644
--- a/svg/import/coords-trans-10-f-manual.svg
+++ b/svg/import/coords-trans-10-f-manual.svg
@@ -45,14 +45,14 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
     <g transform="translate(40 20)" fill="red">
-        <path d="M 20 20 L 70 20 L 45 60 z" /> 
+        <path d="M 20 20 L 70 20 L 45 60 z" />
         <ellipse cx="120" cy="35" rx="30" ry="10" />
         <text x="160" y="40">Filler Text</text>
         <rect x="250" y="20" width="30" height="50" />
         <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
     </g>
     <g transform="matrix(1 0 0 1 40 20)" fill="black" stroke="black">
-        <path d="M 20 20 L 70 20 L 45 60 z" /> 
+        <path d="M 20 20 L 70 20 L 45 60 z" />
         <ellipse cx="120" cy="35" rx="30" ry="10" />
         <text x="160" y="40">Filler Text</text>
         <rect x="250" y="20" width="30" height="50" />
@@ -60,14 +60,14 @@
     </g>
     <g transform="translate(0 100)">
         <g transform="matrix(1 0 0 1 40 20)" fill="red">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
             <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
         </g>
         <g transform="translate(40 20)" fill="black" stroke="black">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
diff --git a/svg/import/coords-trans-11-f-manual.svg b/svg/import/coords-trans-11-f-manual.svg
index a5ed7d5..501ee1c 100644
--- a/svg/import/coords-trans-11-f-manual.svg
+++ b/svg/import/coords-trans-11-f-manual.svg
@@ -14,7 +14,7 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/coords.html#EstablishingANewUserSpace">
       <p>
         Scaling is equivalent to the matrix [sx 0 0 xy 0 0], where one unit in the X and Y directions in the new coordinate system equals 'sx' and 'sy' units in the previous coordinate system respectively.The test  overlays a group of black graphics elements with a 'scale' transform specified on top of an identical group of red elements
-        with the equivalent 'matrix' transform and vice versa. 
+        with the equivalent 'matrix' transform and vice versa.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -41,14 +41,14 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
     <g transform="scale(1.2 2.5)" fill="red">
-        <path d="M 20 20 L 70 20 L 45 60 z" /> 
+        <path d="M 20 20 L 70 20 L 45 60 z" />
         <ellipse cx="120" cy="35" rx="30" ry="10" />
         <text x="160" y="40">Filler Text</text>
         <rect x="250" y="20" width="30" height="50" />
         <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
     </g>
     <g transform="matrix(1.2 0 0 2.5 0 0)" fill="black" stroke="black">
-        <path d="M 20 20 L 70 20 L 45 60 z" /> 
+        <path d="M 20 20 L 70 20 L 45 60 z" />
         <ellipse cx="120" cy="35" rx="30" ry="10" />
         <text x="160" y="40">Filler Text</text>
         <rect x="250" y="20" width="30" height="50" />
@@ -56,14 +56,14 @@
     </g>
     <g transform="translate(0 150)">
         <g transform="matrix(1.2 0 0 2.5 0 0)" fill="red">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
             <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
         </g>
         <g transform="scale(1.2 2.5)" fill="black" stroke="black">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
diff --git a/svg/import/coords-trans-12-f-manual.svg b/svg/import/coords-trans-12-f-manual.svg
index b2f9b90..6984bb1 100644
--- a/svg/import/coords-trans-12-f-manual.svg
+++ b/svg/import/coords-trans-12-f-manual.svg
@@ -19,7 +19,7 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-	Run the test. No interaction required.
+  Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -42,14 +42,14 @@
 
     <g transform="translate(200)">
         <g transform="rotate(90)" fill="red">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
             <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
         </g>
         <g transform="matrix(0 1 -1 0 0 0)" fill="black" stroke="black">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
@@ -58,14 +58,14 @@
     </g>
     <g transform="translate(310)">
         <g transform="matrix(0 1 -1 0 0 0)" fill="red">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
             <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
         </g>
         <g transform="rotate(90)" fill="black" stroke="black">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
diff --git a/svg/import/coords-trans-13-f-manual.svg b/svg/import/coords-trans-13-f-manual.svg
index 0dba073..5638c90 100644
--- a/svg/import/coords-trans-13-f-manual.svg
+++ b/svg/import/coords-trans-13-f-manual.svg
@@ -15,12 +15,12 @@
       <p>
         A skew transformation along the x-axis is equivalent to the matrix [1 0 tan(a) 1 0 0], which has the effect of skewing X coordinates by angle 'a'.
 The test overlays a group of black graphics elements with a 'skewX' transform specified on top of an identical group of red elements
-        with the equivalent 'matrix' transform and vice versa. 
+        with the equivalent 'matrix' transform and vice versa.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-	Run the test. No interaction required.
+  Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -42,14 +42,14 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
     <g transform="skewX(45)" fill="red">
-        <path d="M 20 20 L 70 20 L 45 60 z" /> 
+        <path d="M 20 20 L 70 20 L 45 60 z" />
         <ellipse cx="120" cy="35" rx="30" ry="10" />
         <text x="160" y="40">Filler Text</text>
         <rect x="250" y="20" width="30" height="50" />
         <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
     </g>
     <g transform="matrix(1 0 1 1 0 0)" fill="black" stroke="black">
-        <path d="M 20 20 L 70 20 L 45 60 z" /> 
+        <path d="M 20 20 L 70 20 L 45 60 z" />
         <ellipse cx="120" cy="35" rx="30" ry="10" />
         <text x="160" y="40">Filler Text</text>
         <rect x="250" y="20" width="30" height="50" />
@@ -57,14 +57,14 @@
     </g>
     <g transform="translate(0 150)">
         <g transform="matrix(1 0 1 1 0 0)" fill="red">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
             <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
         </g>
         <g transform="skewX(45)" fill="black" stroke="black">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
diff --git a/svg/import/coords-trans-14-f-manual.svg b/svg/import/coords-trans-14-f-manual.svg
index a224a38..c05e9da 100644
--- a/svg/import/coords-trans-14-f-manual.svg
+++ b/svg/import/coords-trans-14-f-manual.svg
@@ -20,7 +20,7 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-	Run the test. No interaction required.
+  Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -43,14 +43,14 @@
 
     <g transform="scale(0.75) rotate(-20)">
         <g transform="skewY(45)" fill="red">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
             <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
         </g>
         <g transform="matrix(1 1 0 1 0 0)" fill="black" stroke="black">
-            <path d="M 20 20 L 70 20 L 45 60 z" /> 
+            <path d="M 20 20 L 70 20 L 45 60 z" />
             <ellipse cx="120" cy="35" rx="30" ry="10" />
             <text x="160" y="40">Filler Text</text>
             <rect x="250" y="20" width="30" height="50" />
@@ -58,14 +58,14 @@
         </g>
         <g transform="translate(0 150)">
             <g transform="matrix(1 1 0 1 0 0)" fill="red">
-                <path d="M 20 20 L 70 20 L 45 60 z" /> 
+                <path d="M 20 20 L 70 20 L 45 60 z" />
                 <ellipse cx="120" cy="35" rx="30" ry="10" />
                 <text x="160" y="40">Filler Text</text>
                 <rect x="250" y="20" width="30" height="50" />
                 <line x1="310" y1="20" x2="350" y2="70" stroke-width="5" />
             </g>
             <g transform="skewY(45)" fill="black" stroke="black">
-                <path d="M 20 20 L 70 20 L 45 60 z" /> 
+                <path d="M 20 20 L 70 20 L 45 60 z" />
                 <ellipse cx="120" cy="35" rx="30" ry="10" />
                 <text x="160" y="40">Filler Text</text>
                 <rect x="250" y="20" width="30" height="50" />
diff --git a/svg/import/coords-transformattr-01-f-manual.svg b/svg/import/coords-transformattr-01-f-manual.svg
index b3914f3..5d8dd8a 100644
--- a/svg/import/coords-transformattr-01-f-manual.svg
+++ b/svg/import/coords-transformattr-01-f-manual.svg
@@ -15,7 +15,7 @@
       <p>
         Tests that separating transform definitions by whitespace and/or a comma is supported. The test draws a red 'rect' element with a valid, non-delimited transform list. It overlays it with an identical black rectangle with
         equivalent transform list delimted by commas and numerical Unicode references of space (U+0020), tab (U+0009), carriage
-        return (U+000D), line feed (U+000A), and combination of all five, 
+        return (U+000D), line feed (U+000A), and combination of all five,
 so that no red is visible.
       </p>
     </d:testDescription>
diff --git a/svg/import/coords-transformattr-03-f-manual.svg b/svg/import/coords-transformattr-03-f-manual.svg
index ce5f7a7..08d3a6b 100644
--- a/svg/import/coords-transformattr-03-f-manual.svg
+++ b/svg/import/coords-transformattr-03-f-manual.svg
@@ -20,7 +20,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>Run the test. No interaction required.</p>
+      <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
@@ -59,4 +59,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/coords-transformattr-04-f-manual.svg b/svg/import/coords-transformattr-04-f-manual.svg
index 031c89e..8c613c7 100644
--- a/svg/import/coords-transformattr-04-f-manual.svg
+++ b/svg/import/coords-transformattr-04-f-manual.svg
@@ -14,11 +14,11 @@
       <p>
         Specify a series of various red graphics elements. Specify an equivalent series of black graphics elements that are defined to have dimensions
         that are half the size as the red elements. Specify a 'transform' value of 'scale' with only the 'sx' value specified  (i.e., 'scale(2)'). If the 'sy'
-        parameter takes the same value as the 'sx', there will be no red on the page. 
+        parameter takes the same value as the 'sx', there will be no red on the page.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>Run the test. No interaction required.</p>
+      <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
@@ -57,4 +57,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/coords-transformattr-05-f-manual.svg b/svg/import/coords-transformattr-05-f-manual.svg
index 2f14c18..4fe26e5 100644
--- a/svg/import/coords-transformattr-05-f-manual.svg
+++ b/svg/import/coords-transformattr-05-f-manual.svg
@@ -20,13 +20,13 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>Run the test. No interaction required.</p>
+  <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Test passes if there is no red visible on the page.
       </p>
-            
+
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: coords-transformattr-05-f.svg,v $</title>
@@ -68,4 +68,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/coords-units-02-b-manual.svg b/svg/import/coords-units-02-b-manual.svg
index b83db37..30e7520 100644
--- a/svg/import/coords-units-02-b-manual.svg
+++ b/svg/import/coords-units-02-b-manual.svg
@@ -32,7 +32,7 @@
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
-    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">      
+    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         In the first two tests, that validate coordinate processing, the circles
         should have the same center. In the following two tests, the rectangles should have
diff --git a/svg/import/coords-viewattr-03-b-manual.svg b/svg/import/coords-viewattr-03-b-manual.svg
index 02c3160..a76ba4e 100644
--- a/svg/import/coords-viewattr-03-b-manual.svg
+++ b/svg/import/coords-viewattr-03-b-manual.svg
@@ -22,7 +22,7 @@
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
-      </p>      
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/filters-background-01-f-manual.svg b/svg/import/filters-background-01-f-manual.svg
index 72510dd..6d07c2b 100644
--- a/svg/import/filters-background-01-f-manual.svg
+++ b/svg/import/filters-background-01-f-manual.svg
@@ -16,36 +16,36 @@
         Test background image processing.
       </p>
       <p>
-        The first subtest enables background image processing and adds an empty ‘g’ element 
-        which invokes the ShiftBGAndBlur filter. This filter takes the current accumulated 
-        background image (i.e., the entire reference graphic) as input, shifts its offscreen 
+        The first subtest enables background image processing and adds an empty ‘g’ element
+        which invokes the ShiftBGAndBlur filter. This filter takes the current accumulated
+        background image (i.e., the entire reference graphic) as input, shifts its offscreen
         down, blurs it, and then writes the result to the canvas. Note that the offscreen for
-        the filter is initialized to transparent black, which allows the already rendered 
-        rectangle, circle and triangle to show through after the filter renders its own 
+        the filter is initialized to transparent black, which allows the already rendered
+        rectangle, circle and triangle to show through after the filter renders its own
         result to the canvas.
       </p>
       <p>
-        The second subtest enables background image processing and instead invokes the 
-        ShiftBGAndBlur filter on the inner ‘g’ element. The accumulated background at the 
-        time the filter is applied contains only the rectangle. Because the children 
-        of the inner ‘g’ (i.e., the circle and triangle) are not part of the inner ‘g’ element's 
+        The second subtest enables background image processing and instead invokes the
+        ShiftBGAndBlur filter on the inner ‘g’ element. The accumulated background at the
+        time the filter is applied contains only the rectangle. Because the children
+        of the inner ‘g’ (i.e., the circle and triangle) are not part of the inner ‘g’ element's
         background and because ShiftBGAndBlur ignores SourceGraphic, the children of the inner ‘g’
         do not appear in the result.
       </p>
       <p>
-        The third subtest enables background image processing and invokes the ShiftBGAndBlur on the 
-        ‘polygon’ element that draws the triangle. The accumulated background at the time the filter 
-        is applied contains the rectangle plus the circle ignoring the effect of the ‘opacity’ 
-        property on the inner ‘g’ element. (Note that the blurred circle at the bottom does not 
-        let the rectangle show through on its left side. This is due to ignoring the effect of 
-        the ‘opacity’ property.) Because the triangle itself is not part of the accumulated background 
+        The third subtest enables background image processing and invokes the ShiftBGAndBlur on the
+        ‘polygon’ element that draws the triangle. The accumulated background at the time the filter
+        is applied contains the rectangle plus the circle ignoring the effect of the ‘opacity’
+        property on the inner ‘g’ element. (Note that the blurred circle at the bottom does not
+        let the rectangle show through on its left side. This is due to ignoring the effect of
+        the ‘opacity’ property.) Because the triangle itself is not part of the accumulated background
         and because ShiftBGAndBlur ignores SourceGraphic, the triangle does not appear in the result.
       </p>
       <p>
-        The fourth subtest is the same as the third except that filter ShiftBGAndBlur_WithSourceGraphic is 
-        invoked instead of ShiftBGAndBlur. ShiftBGAndBlur_WithSourceGraphic performs the same effect as 
-        ShiftBGAndBlur, but then renders the SourceGraphic on top of the shifted, blurred background 
-        image. In this case, SourceGraphic is the blue triangle; thus, the result is the same as in 
+        The fourth subtest is the same as the third except that filter ShiftBGAndBlur_WithSourceGraphic is
+        invoked instead of ShiftBGAndBlur. ShiftBGAndBlur_WithSourceGraphic performs the same effect as
+        ShiftBGAndBlur, but then renders the SourceGraphic on top of the shifted, blurred background
+        image. In this case, SourceGraphic is the blue triangle; thus, the result is the same as in
         the fourth case except that the triangle now appears.
       </p>
     </d:testDescription>
@@ -85,7 +85,7 @@
           <feOffset in="BackgroundImage" dx="0" dy="125" />
           <feGaussianBlur stdDeviation="8" />
         </filter>
-        <filter id="ShiftBGAndBlur_WithSourceGraphic" 
+        <filter id="ShiftBGAndBlur_WithSourceGraphic"
                 filterUnits="userSpaceOnUse" x="0" y="0" width="1200" height="400">
           <desc>
             This filter takes the BackgroundImage, shifts it down 125 units, blurs it,
diff --git a/svg/import/filters-blend-01-b-manual.svg b/svg/import/filters-blend-01-b-manual.svg
index 685a59e..60f4c0e 100644
--- a/svg/import/filters-blend-01-b-manual.svg
+++ b/svg/import/filters-blend-01-b-manual.svg
@@ -95,14 +95,14 @@
     </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.9 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/filters-composite-02-b-manual.svg b/svg/import/filters-composite-02-b-manual.svg
index 23e4b9a..3b41144 100644
--- a/svg/import/filters-composite-02-b-manual.svg
+++ b/svg/import/filters-composite-02-b-manual.svg
@@ -31,7 +31,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-     	<p>
+       <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
diff --git a/svg/import/filters-composite-03-f-manual.svg b/svg/import/filters-composite-03-f-manual.svg
index 39c9dca..d752e26 100644
--- a/svg/import/filters-composite-03-f-manual.svg
+++ b/svg/import/filters-composite-03-f-manual.svg
@@ -13,7 +13,7 @@
     version="$Revision: 1.4 $" testname="$RCSfile: filters-composite-03-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/filters.html#feCompositeElement">
       <p>
-        Tests the arithmetic operator in feComposite. 
+        Tests the arithmetic operator in feComposite.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -53,7 +53,7 @@
         <feComposite operator="arithmetic" in="FillPaint" in2="StrokePaint" k1="0" k2="10" k3="20" k4="0"/>
       </filter>
     </defs>
-    
+
     <g transform="translate(100 30)">
       <rect id="reference1" fill="lime" width="120" height="120"/>
       <rect fill="red" stroke="red" filter="url(#composite-ident)" x="10" y="10" width="100" height="100"/>
diff --git a/svg/import/filters-composite-04-f-manual.svg b/svg/import/filters-composite-04-f-manual.svg
index 78404f1..ae5b0b4 100644
--- a/svg/import/filters-composite-04-f-manual.svg
+++ b/svg/import/filters-composite-04-f-manual.svg
@@ -58,7 +58,7 @@
         <feComposite operator="arithmetic" in="SourceGraphic" in2="bird" k1="0" k2="0.25" k3="0.75" k4="0"/>
       </filter>
     </defs>
-    
+
     <image width="120" height="80" xlink:href="../images/tree.jpg" filter="url(#dissolve1)" transform="translate(100 80)"/>
     <image width="120" height="80" xlink:href="../images/tree.jpg" filter="url(#dissolve2)" transform="translate(250 80)"/>
     <image width="120" height="80" xlink:href="../images/tree.jpg" filter="url(#dissolve3)" transform="translate(100 190)"/>
diff --git a/svg/import/filters-composite-05-f-manual.svg b/svg/import/filters-composite-05-f-manual.svg
index 0d4ffe8..c838484 100644
--- a/svg/import/filters-composite-05-f-manual.svg
+++ b/svg/import/filters-composite-05-f-manual.svg
@@ -51,7 +51,7 @@
         </feComposite>
       </filter>
     </defs>
-    
+
     <image id="image" width="320" height="160" xlink:href="../images/tree.jpg" filter="url(#dissolve)" transform="translate(80 80)"/>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
diff --git a/svg/import/filters-conv-02-f-manual.svg b/svg/import/filters-conv-02-f-manual.svg
index 5912f42..ec5a617 100644
--- a/svg/import/filters-conv-02-f-manual.svg
+++ b/svg/import/filters-conv-02-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -18,7 +18,7 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.
-        
+
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -40,38 +40,38 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-		<defs>
-			<filter id="convolve-without-order" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
-				<feConvolveMatrix kernelMatrix="1 1 1 1 -8 1 1 1 1" preserveAlpha="true"/>
-			</filter>
-			<filter id="convolve-with-order1" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
-				<feConvolveMatrix order="3" kernelMatrix="1 1 1 1 -8 1 1 1 1" preserveAlpha="true"/>
-			</filter>
-			<filter id="convolve-with-order2" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
-				<feConvolveMatrix order="3 3" kernelMatrix="1 1 1 1 -8 1 1 1 1" preserveAlpha="true"/>
-			</filter>
-		</defs>
+    <defs>
+      <filter id="convolve-without-order" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
+        <feConvolveMatrix kernelMatrix="1 1 1 1 -8 1 1 1 1" preserveAlpha="true"/>
+      </filter>
+      <filter id="convolve-with-order1" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
+        <feConvolveMatrix order="3" kernelMatrix="1 1 1 1 -8 1 1 1 1" preserveAlpha="true"/>
+      </filter>
+      <filter id="convolve-with-order2" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
+        <feConvolveMatrix order="3 3" kernelMatrix="1 1 1 1 -8 1 1 1 1" preserveAlpha="true"/>
+      </filter>
+    </defs>
 
-		<text x="50%" y="3em" style="font-size:18px; text-anchor:middle">feConvolveMatrix 'order' attribute</text>
+    <text x="50%" y="3em" style="font-size:18px; text-anchor:middle">feConvolveMatrix 'order' attribute</text>
 
-		<image x="90" y="100" width="100" height="100" xlink:href="../images/image1.jpg" filter="url(#convolve-without-order)"/>
-		<text x="135" y="220" style="font-size:9px; text-anchor:middle">without order</text>
-		
-		<image x="190" y="100" width="100" height="100" xlink:href="../images/image1.jpg" filter="url(#convolve-with-order1)"/>
-		<text x="235" y="220" style="font-size:9px; text-anchor:middle">order="3"</text>
-		
-		<image x="290" y="100" width="100" height="100" xlink:href="../images/image1.jpg" filter="url(#convolve-with-order2)"/>
-		<text x="335" y="220" style="font-size:9px; text-anchor:middle">order="3 3"</text>
-	</g>
+    <image x="90" y="100" width="100" height="100" xlink:href="../images/image1.jpg" filter="url(#convolve-without-order)"/>
+    <text x="135" y="220" style="font-size:9px; text-anchor:middle">without order</text>
+
+    <image x="190" y="100" width="100" height="100" xlink:href="../images/image1.jpg" filter="url(#convolve-with-order1)"/>
+    <text x="235" y="220" style="font-size:9px; text-anchor:middle">order="3"</text>
+
+    <image x="290" y="100" width="100" height="100" xlink:href="../images/image1.jpg" filter="url(#convolve-with-order2)"/>
+    <text x="335" y="220" style="font-size:9px; text-anchor:middle">order="3 3"</text>
+  </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.9 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/filters-conv-03-f-manual.svg b/svg/import/filters-conv-03-f-manual.svg
index cb9d844..99e8898 100644
--- a/svg/import/filters-conv-03-f-manual.svg
+++ b/svg/import/filters-conv-03-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -22,7 +22,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
+      <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
@@ -48,55 +48,55 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-		<defs>
-			<filter id="sharpenAnim" x="0" y="0" width="100%" height="100%">
-				<feImage xlink:href="../images/stefan_252_tRNS_opti.png" result="passimg"/>
-				<feConvolveMatrix in="SourceGraphic" order="3" kernelMatrix="0  -1  0
-																																		-1   5 -1
-																																		 0  -1  0" preserveAlpha="true">
-					<set attributeName="in" to="passimg" begin="3s" fill="freeze" onbegin="runtest()"/>
-				</feConvolveMatrix>
-			</filter>
-			<filter id="sharpenScript" x="0" y="0" width="100%" height="100%">
-				<feImage xlink:href="../images/stefan_252_tRNS_opti.png" result="passimg"/>
-				<feConvolveMatrix id="prim" in="SourceGraphic" order="3" kernelMatrix="0  -1  0
-																																							-1   5 -1
-																																							 0  -1  0" preserveAlpha="true"/>
-			</filter>
+    <defs>
+      <filter id="sharpenAnim" x="0" y="0" width="100%" height="100%">
+        <feImage xlink:href="../images/stefan_252_tRNS_opti.png" result="passimg"/>
+        <feConvolveMatrix in="SourceGraphic" order="3" kernelMatrix="0  -1  0
+                                                                    -1   5 -1
+                                                                     0  -1  0" preserveAlpha="true">
+          <set attributeName="in" to="passimg" begin="3s" fill="freeze" onbegin="runtest()"/>
+        </feConvolveMatrix>
+      </filter>
+      <filter id="sharpenScript" x="0" y="0" width="100%" height="100%">
+        <feImage xlink:href="../images/stefan_252_tRNS_opti.png" result="passimg"/>
+        <feConvolveMatrix id="prim" in="SourceGraphic" order="3" kernelMatrix="0  -1  0
+                                                                              -1   5 -1
+                                                                               0  -1  0" preserveAlpha="true"/>
+      </filter>
 
-			<text id="pass" x="240" y="150" text-anchor="middle" font-size="30px">PASS</text>
-		</defs>
-		
-		<script type="application/ecmascript">
-			function runtest()
-			{
-				var img = document.getElementById("img");
-				img.href.baseVal = "../images/stefan_252_tRNS_opti.png";
-				var prim = document.getElementById("prim");
-				prim.in1.baseVal = "passimg";
-			}
-		</script>	
+      <text id="pass" x="240" y="150" text-anchor="middle" font-size="30px">PASS</text>
+    </defs>
 
-		<text x="50%" y="3em" style="font-size:18px; text-anchor:middle">feConvolveMatrix 'in1' DOM</text>
-		<image id="img" xlink:href="../images/purplesquidj.png" x="80" y="100" width="100" height="100"/>
-		<image xlink:href="../images/purplesquidj.png" x="190" y="100" width="100" height="100" filter="url(#sharpenAnim)"/>
-		<image xlink:href="../images/purplesquidj.png" x="300" y="100" width="100" height="100" filter="url(#sharpenScript)"/>
-		<rect x="80" y="100" width="100" height="100" stroke="black" fill="none"/>
-		<rect x="190" y="100" width="100" height="100" stroke="blue" fill="none"/>
-		<rect x="300" y="100" width="100" height="100" stroke="blue" fill="none"/>
-		<text x="130" y="220" style="font-size:9px; text-anchor:middle">Original image</text>
-		<text x="240" y="220" style="font-size:9px; text-anchor:middle">Animated filter</text>
-		<text x="350" y="220" style="font-size:9px; text-anchor:middle">Scripted filter</text>
-	</g>
+    <script type="application/ecmascript">
+      function runtest()
+      {
+        var img = document.getElementById("img");
+        img.href.baseVal = "../images/stefan_252_tRNS_opti.png";
+        var prim = document.getElementById("prim");
+        prim.in1.baseVal = "passimg";
+      }
+    </script>
+
+    <text x="50%" y="3em" style="font-size:18px; text-anchor:middle">feConvolveMatrix 'in1' DOM</text>
+    <image id="img" xlink:href="../images/purplesquidj.png" x="80" y="100" width="100" height="100"/>
+    <image xlink:href="../images/purplesquidj.png" x="190" y="100" width="100" height="100" filter="url(#sharpenAnim)"/>
+    <image xlink:href="../images/purplesquidj.png" x="300" y="100" width="100" height="100" filter="url(#sharpenScript)"/>
+    <rect x="80" y="100" width="100" height="100" stroke="black" fill="none"/>
+    <rect x="190" y="100" width="100" height="100" stroke="blue" fill="none"/>
+    <rect x="300" y="100" width="100" height="100" stroke="blue" fill="none"/>
+    <text x="130" y="220" style="font-size:9px; text-anchor:middle">Original image</text>
+    <text x="240" y="220" style="font-size:9px; text-anchor:middle">Animated filter</text>
+    <text x="350" y="220" style="font-size:9px; text-anchor:middle">Scripted filter</text>
+  </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.8 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/filters-conv-04-f-manual.svg b/svg/import/filters-conv-04-f-manual.svg
index e1291c0..90638ed 100644
--- a/svg/import/filters-conv-04-f-manual.svg
+++ b/svg/import/filters-conv-04-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -76,7 +76,7 @@
           more faded for each instance further to the right.
           <ul>
             <li>
-              The first image (left most) must be a rect filled with a linear gradient that 
+              The first image (left most) must be a rect filled with a linear gradient that
               transitions from solid green to transparent green.
             </li>
             <li>The second image (second from the left) must be identical to the first image.</li>
@@ -142,7 +142,7 @@
       <image x="126" y="51" width="98" height="98" xlink:href="../images/DisplaceChecker.png"/>
       <image x="241" y="51" width="98" height="98" xlink:href="../images/DisplaceChecker.png"/>
       <image x="356" y="51" width="98" height="98" xlink:href="../images/DisplaceChecker.png"/>
-            
+
       <rect fill="url(#test_linear_gradient)" stroke="black" x="10" y="50" width="100" height="100"/>
       <rect fill="url(#test_linear_gradient)" stroke="black" x="125" y="50" width="100" height="100" filter="url(#convolve-without-bias)"/>
       <rect fill="url(#test_linear_gradient)" stroke="black" x="240" y="50" width="100" height="100" filter="url(#convolve-with-bias05)"/>
@@ -157,7 +157,7 @@
   <!-- comment out this watermark once the test is approved -->
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
 </svg>
diff --git a/svg/import/filters-conv-05-f-manual.svg b/svg/import/filters-conv-05-f-manual.svg
index b60f774..78d7f30 100644
--- a/svg/import/filters-conv-05-f-manual.svg
+++ b/svg/import/filters-conv-05-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -39,45 +39,45 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-		<defs>
-			<filter id="emNone" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
+    <defs>
+      <filter id="emNone" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
         <feConvolveMatrix kernelMatrix="1 1 1 1 -7 1 1 1 1" preserveAlpha="false" edgeMode="none"/>
-			</filter>
-			<filter id="emWrap" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
-				<feConvolveMatrix kernelMatrix="1 1 1 1 -7 1 1 1 1" preserveAlpha="false" edgeMode="wrap"/>
-			</filter>
-			<filter id="emDuplicate" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
-				<feConvolveMatrix kernelMatrix="1 1 1 1 -7 1 1 1 1" preserveAlpha="false" edgeMode="duplicate"/>
-			</filter>
-	
+      </filter>
+      <filter id="emWrap" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
+        <feConvolveMatrix kernelMatrix="1 1 1 1 -7 1 1 1 1" preserveAlpha="false" edgeMode="wrap"/>
+      </filter>
+      <filter id="emDuplicate" filterUnits="objectBoundingBox" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
+        <feConvolveMatrix kernelMatrix="1 1 1 1 -7 1 1 1 1" preserveAlpha="false" edgeMode="duplicate"/>
+      </filter>
+
       <g id="box">
         <rect width="10" height="100" fill="blue"/>
         <rect x="10" width="100" height="10" fill="yellow"/>
         <rect x="110" width="10" height="100" fill="lime"/>
         <rect x="10" y="90" width="100" height="10" fill="black"/>
       </g>
-  	</defs>
+    </defs>
 
-		<text x="50%" y="3em" style="font-size:18px; text-anchor:middle">feConvolveMatrix 'edgeMode'</text>
-    
+    <text x="50%" y="3em" style="font-size:18px; text-anchor:middle">feConvolveMatrix 'edgeMode'</text>
+
     <use xlink:href="#box" filter="url(#emNone)" transform="translate(40 100)"/>
     <text x="100" y="220" font-size="14" text-anchor="middle">none</text>
-    
+
     <use xlink:href="#box" filter="url(#emWrap)" transform="translate(180 100)"/>
     <text x="240" y="220" font-size="14" text-anchor="middle">wrap</text>
-    
+
     <use xlink:href="#box" filter="url(#emDuplicate)" transform="translate(320 100)"/>
     <text x="380" y="220" font-size="14" text-anchor="middle">duplicate</text>
-	</g>
+  </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.2 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved -->
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
 </svg>
diff --git a/svg/import/filters-displace-01-f-manual.svg b/svg/import/filters-displace-01-f-manual.svg
index 2ff17a7..b77557f 100644
--- a/svg/import/filters-displace-01-f-manual.svg
+++ b/svg/import/filters-displace-01-f-manual.svg
@@ -52,7 +52,7 @@
       <p>
         The rendered image should match the reference image. The edges
         of the blue rectangle must be parallel to the grid lines in the
-        displaced image. The center of the bottommost right distorted image 
+        displaced image. The center of the bottommost right distorted image
         should be on a gridpoint.
       </p>
     </d:passCriteria>
@@ -100,8 +100,6 @@
         <tspan x="222" y="138">(20 deg. rotation)</tspan>
       </text>
 
-
-
       <rect x="301" y="15" width="128" height="128" filter="url(#RotateMapTest)"/>
       <rect x="-1" y="-1" width="2" height="2" transform="translate(365,79) rotate(20) scale(32)" fill="blue" fill-opacity="0.5"/>
       <text>
@@ -110,12 +108,10 @@
         <tspan x="365" y="184">rotated blue rectangle)</tspan>
       </text>
 
-
       <rect x="15" y="158" width="128" height="128" filter="url(#DispMapChecker)"/>
       <rect x="20" y="258" width="118" height="20" fill="#DDD"/>
       <text x="79" y="273">Checkerboard Image</text>
 
-
       <rect x="158" y="158" width="128" height="128" filter="url(#SphereMap)"/>
       <rect x="163" y="255" width="118" height="30" fill="#DDD"/>
       <text>
@@ -123,7 +119,6 @@
         <tspan x="222" y="283">(spherical distortion)</tspan>
       </text>
 
-
       <rect x="301" y="200" width="128" height="128" filter="url(#SphereMapTest)"/>
       <text x="365" y="345">Result</text>
     </g>
diff --git a/svg/import/filters-displace-02-f-manual.svg b/svg/import/filters-displace-02-f-manual.svg
index 1fc1d1b..f714b0bf 100644
--- a/svg/import/filters-displace-02-f-manual.svg
+++ b/svg/import/filters-displace-02-f-manual.svg
@@ -21,7 +21,7 @@
         The bottom subtest tests that not specifying the 'xChannelSelector' attribute has the same effect as if 'A' was specified.
       </p>
       <p>
-        In both cases the filter input image consists of a gradient that is rendered using the default 'color-interpolation' which is 'sRGB'. 
+        In both cases the filter input image consists of a gradient that is rendered using the default 'color-interpolation' which is 'sRGB'.
         The default colorspace for filter primitives is 'linearRGB'. The filtering operation happens in 'linearRGB' space and the
         result is then transformed back to 'sRGB' space for display.
       </p>
diff --git a/svg/import/filters-felem-01-b-manual.svg b/svg/import/filters-felem-01-b-manual.svg
index 4ad9586..659253e 100644
--- a/svg/import/filters-felem-01-b-manual.svg
+++ b/svg/import/filters-felem-01-b-manual.svg
@@ -18,17 +18,17 @@
         Test which verifies null filters and filter regions.
       </p>
       <p>
-        Four subtests each consist of a small red circle overdrawn with a larger green circle. 
+        Four subtests each consist of a small red circle overdrawn with a larger green circle.
         Filters are applied to three of the red circles, hiding them and showing the green circle.
       </p>
       <p>
-        The top left subtest has no filter applied to the circle, so the green circle is visible and the red one is not. 
-        The top right subtest applies a filter to the red circle, but there is no corresponding filter element. 
+        The top left subtest has no filter applied to the circle, so the green circle is visible and the red one is not.
+        The top right subtest applies a filter to the red circle, but there is no corresponding filter element.
         Thus, a null filter is applied and the red circle is not shown, allowing the green circle to be seen.
       </p>
       <p>
-        The bottom left subtest applies an empty filter element with the default filterRegion, and the bottom right 
-        subtest applies an empty filter with a non-default filterRegion. In both cases where empty filters are applied, 
+        The bottom left subtest applies an empty filter element with the default filterRegion, and the bottom right
+        subtest applies an empty filter with a non-default filterRegion. In both cases where empty filters are applied,
         the result of the filter is a transparent black offscreen, thus showing the green circle underneath.
       </p>
     </d:testDescription>
diff --git a/svg/import/filters-felem-02-f-manual.svg b/svg/import/filters-felem-02-f-manual.svg
index ebcdac5..1b87786 100644
--- a/svg/import/filters-felem-02-f-manual.svg
+++ b/svg/import/filters-felem-02-f-manual.svg
@@ -25,12 +25,12 @@
       <p>
         The test has passed if:
       </p>
-			<ul>
-				<li>There is no red visible anywhere</li>
-				<li>The first row has three green rectangles</li>
-				<li>The second row has three black circles, and the middle one has more blurred edges than the other two.</li>
-				<li>The third row has three green stars.</li>
-			</ul>
+      <ul>
+        <li>There is no red visible anywhere</li>
+        <li>The first row has three green rectangles</li>
+        <li>The second row has three black circles, and the middle one has more blurred edges than the other two.</li>
+        <li>The third row has three green stars.</li>
+      </ul>
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: filters-felem-02-f.svg,v $</title>
@@ -49,68 +49,67 @@
       <filter id="usou1" primitiveUnits="userSpaceOnUse" x="0" y="0" width="100%" height="100%">
         <feFlood flood-color="lime" x="25" y="25" width="50" height="50"/>
       </filter>
-			<filter id="obb1" primitiveUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">
+      <filter id="obb1" primitiveUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">
         <feFlood flood-color="lime" x="25%" y="25%" width="50%" height="50%"/>
       </filter>
-			<filter id="default1" x="0" y="0" width="100%" height="100%">
+      <filter id="default1" x="0" y="0" width="100%" height="100%">
         <feFlood flood-color="lime" x="25" y="25" width="50" height="50"/>
       </filter>
-			
-			<filter id="usou2" primitiveUnits="userSpaceOnUse" x="-50%" y="-50%" width="200%" height="200%">
+
+      <filter id="usou2" primitiveUnits="userSpaceOnUse" x="-50%" y="-50%" width="200%" height="200%">
         <feGaussianBlur stdDeviation="0.2"/>
       </filter>
-			<filter id="obb2" primitiveUnits="objectBoundingBox" x="-50%" y="-50%" width="200%" height="200%">
+      <filter id="obb2" primitiveUnits="objectBoundingBox" x="-50%" y="-50%" width="200%" height="200%">
         <feGaussianBlur stdDeviation="0.2"/>
       </filter>
-			<filter id="default2" x="-50%" y="-50%" width="200%" height="200%">
+      <filter id="default2" x="-50%" y="-50%" width="200%" height="200%">
         <feGaussianBlur stdDeviation="0.2"/>
       </filter>
-			
-			<filter id="usou3" primitiveUnits="userSpaceOnUse" x="-50%" y="-50%" width="200%" height="200%">
+
+      <filter id="usou3" primitiveUnits="userSpaceOnUse" x="-50%" y="-50%" width="200%" height="200%">
         <feOffset dx="2" dy="2"/>
       </filter>
-			<filter id="obb3" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox" x="-50%" y="-50%" width="200%" height="200%">
+      <filter id="obb3" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox" x="-50%" y="-50%" width="200%" height="200%">
         <feOffset dx="2" dy="2"/>
       </filter>
-			<filter id="default3" x="-50%" y="-50%" width="200%" height="200%">
+      <filter id="default3" x="-50%" y="-50%" width="200%" height="200%">
         <feOffset dx="2" dy="2"/>
       </filter>
 
-			<polygon id="star" points="300,60 311.755,83.819 338.042,87.639 319.021,106.180 323.511,132.360 300,120 276.488,132.360 280.978,106.180 261.957,87.639 288.244,83.819 300,60 311.755,83.819"/>
-			
+      <polygon id="star" points="300,60 311.755,83.819 338.042,87.639 319.021,106.180 323.511,132.360 300,120 276.488,132.360 280.978,106.180 261.957,87.639 288.244,83.819 300,60 311.755,83.819"/>
+
     </defs>
-		
-		<g transform="translate(80 0)">
 
-			<rect fill="red" width="50" height="50" transform="translate(25 25)"/>
-			<rect fill="red" width="100" height="100" filter="url(#usou1)" transform="translate(0 0)"/>
-			
-			<rect fill="red" width="50" height="50" transform="translate(125 25)"/>
-			<rect fill="red" width="100" height="100" filter="url(#obb1)" transform="translate(100 0)"/>
-			
-			<rect fill="red" width="50" height="50" transform="translate(225 25)"/>
-			<rect fill="red" width="100" height="100" filter="url(#default1)" transform="translate(200 0)"/>
-			
-			<g transform="translate(50 40)">
-				<circle r="30" filter="url(#usou2)" transform="translate(0 100)"/>
-				<circle r="30" filter="url(#obb2)" transform="translate(100 100)"/>
-				<circle r="30" filter="url(#default2)" transform="translate(200 100)"/>
-			</g>
-			
-			
-			<use xlink:href="#star" transform="translate(-250 150)" fill="red"/>
-			<use xlink:href="#star" transform="translate(-252 148)" fill="lime" filter="url(#usou3)"/>
-			<use xlink:href="#star" transform="translate(-250 150)" fill="none" stroke-width="2" stroke="lime"/>
-						
-			<use xlink:href="#star" transform="translate(-150 150)" fill="red"/>
-			<use xlink:href="#star" transform="translate(-302 5.1)" fill="lime" filter="url(#obb3)"/>
-			<use xlink:href="#star" transform="translate(-150 150)" fill="none" stroke-width="2" stroke="lime"/>
-			
-			<use xlink:href="#star" transform="translate(-50 150)" fill="red"/>
-			<use xlink:href="#star" transform="translate(-52 148)" fill="lime" filter="url(#default3)"/>
-			<use xlink:href="#star" transform="translate(-50 150)" fill="none" stroke-width="2" stroke="lime"/>
-		
-		</g>
+    <g transform="translate(80 0)">
+
+      <rect fill="red" width="50" height="50" transform="translate(25 25)"/>
+      <rect fill="red" width="100" height="100" filter="url(#usou1)" transform="translate(0 0)"/>
+
+      <rect fill="red" width="50" height="50" transform="translate(125 25)"/>
+      <rect fill="red" width="100" height="100" filter="url(#obb1)" transform="translate(100 0)"/>
+
+      <rect fill="red" width="50" height="50" transform="translate(225 25)"/>
+      <rect fill="red" width="100" height="100" filter="url(#default1)" transform="translate(200 0)"/>
+
+      <g transform="translate(50 40)">
+        <circle r="30" filter="url(#usou2)" transform="translate(0 100)"/>
+        <circle r="30" filter="url(#obb2)" transform="translate(100 100)"/>
+        <circle r="30" filter="url(#default2)" transform="translate(200 100)"/>
+      </g>
+
+      <use xlink:href="#star" transform="translate(-250 150)" fill="red"/>
+      <use xlink:href="#star" transform="translate(-252 148)" fill="lime" filter="url(#usou3)"/>
+      <use xlink:href="#star" transform="translate(-250 150)" fill="none" stroke-width="2" stroke="lime"/>
+
+      <use xlink:href="#star" transform="translate(-150 150)" fill="red"/>
+      <use xlink:href="#star" transform="translate(-302 5.1)" fill="lime" filter="url(#obb3)"/>
+      <use xlink:href="#star" transform="translate(-150 150)" fill="none" stroke-width="2" stroke="lime"/>
+
+      <use xlink:href="#star" transform="translate(-50 150)" fill="red"/>
+      <use xlink:href="#star" transform="translate(-52 148)" fill="lime" filter="url(#default3)"/>
+      <use xlink:href="#star" transform="translate(-50 150)" fill="none" stroke-width="2" stroke="lime"/>
+
+    </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
@@ -124,4 +123,3 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
-
diff --git a/svg/import/filters-image-02-b-manual.svg b/svg/import/filters-image-02-b-manual.svg
index ee30a73..83adad7 100644
--- a/svg/import/filters-image-02-b-manual.svg
+++ b/svg/import/filters-image-02-b-manual.svg
@@ -13,15 +13,15 @@
     version="$Revision: 1.9 $" testname="$RCSfile: filters-image-02-b.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/filters.html#feImage">
       <p>
-        Tests the animatability of 'xlink:href' on the 'feImage' element. 
-        The test will first show two blue images that should look exactly the same, 
+        Tests the animatability of 'xlink:href' on the 'feImage' element.
+        The test will first show two blue images that should look exactly the same,
         then after two seconds both images should simultaneously change to show two
         pink images that also look exactly the same.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-      <p>Run the test. No interaction required. 
-        
+      <p>Run the test. No interaction required.
+
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -58,7 +58,7 @@
     <image xlink:href="../images/bluesquidj.png" x="260" y="60" width="150" height="200">
       <set attributeName="xlink:href" to="../images/pinksquidj.png" begin="2s"/>
     </image>
-    
+
     <g text-anchor="middle">
       <text x="50%" y="60" font-size="30">Animation in filters</text>
       <text x="145" y="220">'feImage'</text>
diff --git a/svg/import/filters-image-03-f-manual.svg b/svg/import/filters-image-03-f-manual.svg
index d8b2b92..149049b 100644
--- a/svg/import/filters-image-03-f-manual.svg
+++ b/svg/import/filters-image-03-f-manual.svg
@@ -47,7 +47,7 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
       <!-- A breakdown of this testcase:
-      
+
         All filters:
           filterUnits not specified, defaults to objectBoundingBox.
           x=0, y=0, width=1, height=1 -> filter region = bounding box.
@@ -65,7 +65,7 @@
 
         2. All specified relative
            x = 20.8%        = 100/480 (0.2083)
-           y =    0%	   =   0/360 (0.0)
+           y =    0%     =   0/360 (0.0)
            width  = 10.4%   =  50/480 (0.1042)
            height = 13.8%   =  50/360 (0.1388)
 
@@ -139,13 +139,13 @@
            xlink:href="../images/smiley.png"/>
       </filter>
     </defs>
-    
+
     <g transform="translate(60 100)">
       <rect fill="red" width="50" height="50" filter="url(#default)"/>
       <rect fill="red" x="100" width="50" height="50" filter="url(#all_specified_relative)"/>
       <rect fill="red" x="200" width="50" height="50" filter="url(#all_specified_absolute)"/>
       <rect fill="red" x="300" width="50" height="50" filter="url(#y_specified)"/>
-      
+
       <rect fill="red" y="100" width="50" height="50" filter="url(#width_specified)"/>
       <rect fill="red" x="100" y="100" width="50" height="50" filter="url(#height_specified)"/>
       <rect fill="red" x="200" y="100" width="50" height="50" filter="url(#width_height_specified)"/>
diff --git a/svg/import/filters-image-04-f-manual.svg b/svg/import/filters-image-04-f-manual.svg
index ab2475d..69da73c 100644
--- a/svg/import/filters-image-04-f-manual.svg
+++ b/svg/import/filters-image-04-f-manual.svg
@@ -80,14 +80,14 @@
            xlink:href="../images/smiley.png"/>
       </filter>
     </defs>
-    
+
     <text text-anchor="middle" x="240" y="40" font-size="16">primitiveUnits = "objectBoundingBox"</text>
     <g transform="translate(60 100)">
       <rect fill="red" width="50" height="50" filter="url(#default)"/>
       <rect fill="red" x="100" width="50" height="50" filter="url(#all_specified_relative)"/>
       <rect fill="red" x="200" width="50" height="50" filter="url(#all_specified_absolute)"/>
       <rect fill="red" x="300" width="50" height="50" filter="url(#y_specified)"/>
-      
+
       <rect fill="red" y="100" width="50" height="50" filter="url(#width_specified)"/>
       <rect fill="red" x="100" y="100" width="50" height="50" filter="url(#height_specified)"/>
       <rect fill="red" x="200" y="100" width="50" height="50" filter="url(#width_height_specified)"/>
diff --git a/svg/import/filters-image-05-f-manual.svg b/svg/import/filters-image-05-f-manual.svg
index eab4872..67b4a3a 100644
--- a/svg/import/filters-image-05-f-manual.svg
+++ b/svg/import/filters-image-05-f-manual.svg
@@ -23,7 +23,7 @@
       <p>
         This test copies coords-viewattr-02-b, substituting feImage for image.
         It exercises the various preserveAspectRatio values. An external bitmap
-	is referenced.
+  is referenced.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -41,73 +41,73 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	<defs>
-	  <filter id="default" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png"/>
-	  </filter>
-	  <!-- Meet -->
-	  <filter id="xMinYMinMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMin meet"/>
-	  </filter>
-	  <filter id="xMidYMinMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMin meet"/>
-	  </filter>
-	  <filter id="xMaxYMinMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMin meet"/>
-	  </filter>
-	  <filter id="xMinYMidMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMid meet"/>
-	  </filter>
-	  <filter id="xMidYMidMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMid meet"/>
-	  </filter>
-	  <filter id="xMaxYMidMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMid meet"/>
-	  </filter>
-	  <filter id="xMinYMaxMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMax meet"/>
-	  </filter>
-	  <filter id="xMidYMaxMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMax meet"/>
-	  </filter>
-	  <filter id="xMaxYMaxMeet" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMax meet"/>
-	  </filter>
-	  <!-- Slice -->
-	  <filter id="xMinYMinSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMin slice"/>
-	  </filter>
-	  <filter id="xMidYMinSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMin slice"/>
-	  </filter>
-	  <filter id="xMaxYMinSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMin slice"/>
-	  </filter>
-	  <filter id="xMinYMidSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMid slice"/>
-	  </filter>
-	  <filter id="xMidYMidSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMid slice"/>
-	  </filter>
-	  <filter id="xMaxYMidSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMid slice"/>
-	  </filter>
-	  <filter id="xMinYMaxSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMax slice"/>
-	  </filter>
-	  <filter id="xMidYMaxSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMax slice"/>
-	  </filter>
-	  <filter id="xMaxYMaxSlice" x="0" y="0" width="1" height="1">
-	    <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMax slice"/>
-	  </filter>
-	</defs>
+  <defs>
+    <filter id="default" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png"/>
+    </filter>
+    <!-- Meet -->
+    <filter id="xMinYMinMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMin meet"/>
+    </filter>
+    <filter id="xMidYMinMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMin meet"/>
+    </filter>
+    <filter id="xMaxYMinMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMin meet"/>
+    </filter>
+    <filter id="xMinYMidMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMid meet"/>
+    </filter>
+    <filter id="xMidYMidMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMid meet"/>
+    </filter>
+    <filter id="xMaxYMidMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMid meet"/>
+    </filter>
+    <filter id="xMinYMaxMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMax meet"/>
+    </filter>
+    <filter id="xMidYMaxMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMax meet"/>
+    </filter>
+    <filter id="xMaxYMaxMeet" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMax meet"/>
+    </filter>
+    <!-- Slice -->
+    <filter id="xMinYMinSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMin slice"/>
+    </filter>
+    <filter id="xMidYMinSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMin slice"/>
+    </filter>
+    <filter id="xMaxYMinSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMin slice"/>
+    </filter>
+    <filter id="xMinYMidSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMid slice"/>
+    </filter>
+    <filter id="xMidYMidSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMid slice"/>
+    </filter>
+    <filter id="xMaxYMidSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMid slice"/>
+    </filter>
+    <filter id="xMinYMaxSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMinYMax slice"/>
+    </filter>
+    <filter id="xMidYMaxSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMidYMax slice"/>
+    </filter>
+    <filter id="xMaxYMaxSlice" x="0" y="0" width="1" height="1">
+      <feImage xlink:href="../images/smiley.png" preserveAspectRatio="xMaxYMax slice"/>
+    </filter>
+  </defs>
     <g font-size="9" transform="translate(0, 30)">
       <desc>Example PreserveAspectRatio - demonstrate available options</desc>
       <text text-anchor="middle" x="240" font-size="16">Test preserveAspectRatio on an feImage element.</text>
       <text x="40" y="30" text-anchor="middle">Raster to fit</text>
       <g transform="translate(20,40)">
-	<rect fill="red" width="40" height="40" filter="url(#default)"/>
+  <rect fill="red" width="40" height="40" filter="url(#default)"/>
       </g>
       <text x="35.5" y="110" text-anchor="middle">Viewport 1</text>
       <g transform="translate(10,120)"><rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/></g>
@@ -117,66 +117,66 @@
         <text x="0" y="-20">---------- meet --------------------</text>
         <g>
           <text y="-5">xMin*</text>
-	  <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
-	  <rect fill="red" width="50" height="30" filter="url(#xMinYMinMeet)"/>
+    <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
+    <rect fill="red" width="50" height="30" filter="url(#xMinYMinMeet)"/>
         </g>
         <g transform="translate(70,0)">
           <text y="-5">xMid*</text>
-	  <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
-	  <rect fill="red" width="50" height="30" filter="url(#xMidYMidMeet)"/>
+    <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
+    <rect fill="red" width="50" height="30" filter="url(#xMidYMidMeet)"/>
         </g>
         <g transform="translate(0,50)">
           <text y="-5">xMax*</text>
-	  <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
-	  <rect fill="red" width="50" height="30" filter="url(#xMaxYMaxMeet)"/>
+    <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
+    <rect fill="red" width="50" height="30" filter="url(#xMaxYMaxMeet)"/>
         </g>
       </g>
       <g id="meet-group-2" transform="translate(300, 50)">
         <text x="0" y="-20">---------- meet ------------------------</text>
         <g>
           <text y="-5">*YMin</text><rect x='.5' y='.5' width='29' height='59' fill='none' stroke='blue'/>
-	  <rect fill="red" width="30" height="60" filter="url(#xMinYMinMeet)"/>
+    <rect fill="red" width="30" height="60" filter="url(#xMinYMinMeet)"/>
         </g>
         <g transform="translate(50, 0)">
           <text y="-5">*YMid</text><rect x='.5' y='.5' width='29' height='59' fill='none' stroke='blue'/>
-	  <rect fill="red" width="30" height="60" filter="url(#xMidYMidMeet)"/>
+    <rect fill="red" width="30" height="60" filter="url(#xMidYMidMeet)"/>
         </g>
         <g transform="translate(100, 0)">
           <text y="-5">*YMax</text><rect x='.5' y='.5' width='29' height='59' fill='none' stroke='blue'/>
-	  <rect fill="red" width="30" height="60" filter="url(#xMaxYMaxMeet)"/>
+    <rect fill="red" width="30" height="60" filter="url(#xMaxYMaxMeet)"/>
         </g>
       </g>
       <g id="slice-group-1" transform="translate(120, 185)">
         <text x="0" y="-20">---------- slice -------------------------</text>
         <g>
           <text y="-5">xMin*</text><rect x='.5' y='.5' width='29' height='59' fill='none' stroke='blue'/>
-	  <rect fill="red" width="30" height="60" filter="url(#xMinYMinSlice)"/>
+    <rect fill="red" width="30" height="60" filter="url(#xMinYMinSlice)"/>
         </g>
         <g transform="translate(50,0)">
           <text y="-5">xMid*</text><rect x='.5' y='.5' width='29' height='59' fill='none' stroke='blue'/>
-	  <rect fill="red" width="30" height="60" filter="url(#xMidYMidSlice)"/>
+    <rect fill="red" width="30" height="60" filter="url(#xMidYMidSlice)"/>
         </g>
         <g transform="translate(100,0)">
           <text y="-5">xMax*</text><rect x='.5' y='.5' width='29' height='59' fill='none' stroke='blue'/>
-	  <rect fill="red" width="30" height="60" filter="url(#xMaxYMaxSlice)"/>
+    <rect fill="red" width="30" height="60" filter="url(#xMaxYMaxSlice)"/>
         </g>
       </g>
       <g id="slide-group-2" transform="translate(300, 155) translate(0, 30)">
         <text x="0" y="-20">---------- slice ---------------------</text>
         <g>
           <text y="-5">*YMin</text>
-	  <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
-	  <rect fill="red" width="50" height="30" filter="url(#xMinYMinSlice)"/>
+    <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
+    <rect fill="red" width="50" height="30" filter="url(#xMinYMinSlice)"/>
         </g>
         <g transform="translate(70,0)">
           <text y="-5">*YMid</text>
-	  <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
-	  <rect fill="red" width="50" height="30" filter="url(#xMidYMidSlice)"/>
+    <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
+    <rect fill="red" width="50" height="30" filter="url(#xMidYMidSlice)"/>
         </g>
         <g transform="translate(0,50)">
           <text y="-5">*YMax</text>
-	  <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
-	  <rect fill="red" width="50" height="30" filter="url(#xMaxYMaxSlice)"/>
+    <rect x='.5' y='.5' width='49' height='29' fill='none' stroke='blue'/>
+    <rect fill="red" width="50" height="30" filter="url(#xMaxYMaxSlice)"/>
         </g>
       </g>
     </g>
diff --git a/svg/import/filters-light-02-f-manual.svg b/svg/import/filters-light-02-f-manual.svg
index c5b6132..2ff25c1 100644
--- a/svg/import/filters-light-02-f-manual.svg
+++ b/svg/import/filters-light-02-f-manual.svg
@@ -26,7 +26,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test has passed if the shaded arcs are displayed only on the side indicated by the arrows. 
+        The test has passed if the shaded arcs are displayed only on the side indicated by the arrows.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -62,12 +62,12 @@
           <feDistantLight azimuth="270" elevation="30"/>
         </feSpecularLighting>
       </filter>
-    
+
       <marker id="arrow" markerWidth="10" markerHeight="10" viewBox="0 0 10 40" overflow="visible" orient="auto">
         <path d="M-1 5l10 -5l-10 -5z"/>
       </marker>
     </defs>
-    
+
     <text x="50%" y="3em" style="font-size:18px; text-anchor:middle">'feDistantLight' azimuth</text>
 
     <g transform="scale(4) translate(0 50)">
@@ -81,7 +81,7 @@
     <line marker-end="url(#arrow)" x1="0" y1="-25" x2="0" y2="25" stroke="black" stroke-width="3" transform="rotate(90) translate(200 -350)"/>
     <line marker-end="url(#arrow)" x1="0" y1="-25" x2="0" y2="25" stroke="black" stroke-width="3" transform="rotate(180) translate(-440 -225)"/>
     <line marker-end="url(#arrow)" x1="0" y1="-25" x2="0" y2="25" stroke="black" stroke-width="3" transform="rotate(270) translate(-200 50)"/>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/filters-light-03-f-manual.svg b/svg/import/filters-light-03-f-manual.svg
index 65238a9..f12c580 100644
--- a/svg/import/filters-light-03-f-manual.svg
+++ b/svg/import/filters-light-03-f-manual.svg
@@ -52,7 +52,7 @@
     <defs>
       <filter id="light-primobjbbox" primitiveUnits="objectBoundingBox">
           <feSpecularLighting lighting-color="blue" surfaceScale="5" specularConstant="10" specularExponent="6">
-            <!-- Note: for z this assumes that the scalefactor is 
+            <!-- Note: for z this assumes that the scalefactor is
                        sqrt(bbox.width*bbox.width + bbox.height*bbox.height)/sqrt(2) -->
             <fePointLight x="0.875" y="0.875" z="-0.0625"/>
           </feSpecularLighting>
@@ -86,7 +86,7 @@
       <rect width="40" height="20" filter="url(#light-primobjbbox-rect)" fill="black" transform="translate(-20 60)"/>
       <rect width="40" height="20" fill="none" transform="translate(-20 60)"/>
       <text y="100" font-size="8" stroke="none" text-anchor="middle">primitiveUnits="objectBoundingBox"</text>
-			<circle cx="30" cy="30" r="5" fill="white"/>
+      <circle cx="30" cy="30" r="5" fill="white"/>
     </g>
 
     <g stroke="black" transform="translate(245 100)">
@@ -121,4 +121,3 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
-
diff --git a/svg/import/filters-light-04-f-manual.svg b/svg/import/filters-light-04-f-manual.svg
index 807380c..c3ef143 100644
--- a/svg/import/filters-light-04-f-manual.svg
+++ b/svg/import/filters-light-04-f-manual.svg
@@ -29,8 +29,8 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-				The rendered image should look approximately like the reference image, and the third rectangle from the left
-				in each row must be animated.
+        The rendered image should look approximately like the reference image, and the third rectangle from the left
+        in each row must be animated.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -65,7 +65,7 @@
       <filter id="spotLightC" filterUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
         <feDiffuseLighting in="SourceGraphic" diffuseConstant="1" surfaceScale="10" lighting-color="white">
           <feSpotLight x="25" y="0" z="35" pointsAtX="25" pointsAtY="30" pointsAtZ="0" specularExponent="0" limitingConeAngle="0">
-          	<animate attributeName="limitingConeAngle" from="0" to="50" dur="10s" fill="freeze"/>
+            <animate attributeName="limitingConeAngle" from="0" to="50" dur="10s" fill="freeze"/>
           </feSpotLight>
         </feDiffuseLighting>
       </filter>
@@ -87,7 +87,7 @@
       <filter id="spotLightG" filterUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
         <feDiffuseLighting in="SourceGraphic" diffuseConstant="1" surfaceScale="10" lighting-color="white">
           <feSpotLight x="25" y="0" z="35" pointsAtX="25" pointsAtY="30" pointsAtZ="0" specularExponent="0" limitingConeAngle="0">
-          	<animate attributeName="limitingConeAngle" from="0" to="-50" dur="10s" fill="freeze"/>
+            <animate attributeName="limitingConeAngle" from="0" to="-50" dur="10s" fill="freeze"/>
           </feSpotLight>
         </feDiffuseLighting>
       </filter>
@@ -99,29 +99,29 @@
     </defs>
     <!-- feSpotLight -->
     <g transform="translate(0 -20)">
-	    <g transform="translate(0 110)" font-size="9" font-family="Arial">
-	      <text x="70" y="30">30</text>
-	      <text x="165" y="30">20</text>
-	      <text x="260" y="30">Animated (0..50)</text>
-	      <text x="355" y="30">5</text>
-				<g transform="translate(0 100)">
-		      <text x="70" y="30">-30</text>
-		      <text x="165" y="30">-20</text>
-		      <text x="260" y="30">Animated (0..-50)</text>
-		      <text x="355" y="30">-5</text>
-		    </g>
-	    </g>
-	    <g>
-	      <rect transform="translate(70, 145)" width="50" height="30" filter="url(#spotLightA)"/>
-	      <rect transform="translate(165, 145)" width="50" height="30" filter="url(#spotLightB)"/>
-	      <rect transform="translate(260, 145)" width="50" height="30" filter="url(#spotLightC)"/>
-	      <rect transform="translate(355, 145)" width="50" height="30" filter="url(#spotLightD)"/>
-	      <rect transform="translate(70, 245)" width="50" height="30" filter="url(#spotLightE)"/>
-	      <rect transform="translate(165, 245)" width="50" height="30" filter="url(#spotLightF)"/>
-	      <rect transform="translate(260, 245)" width="50" height="30" filter="url(#spotLightG)"/>
-	      <rect transform="translate(355, 245)" width="50" height="30" filter="url(#spotLightH)"/>
-	    </g>
-	  </g>
+      <g transform="translate(0 110)" font-size="9" font-family="Arial">
+        <text x="70" y="30">30</text>
+        <text x="165" y="30">20</text>
+        <text x="260" y="30">Animated (0..50)</text>
+        <text x="355" y="30">5</text>
+        <g transform="translate(0 100)">
+          <text x="70" y="30">-30</text>
+          <text x="165" y="30">-20</text>
+          <text x="260" y="30">Animated (0..-50)</text>
+          <text x="355" y="30">-5</text>
+        </g>
+      </g>
+      <g>
+        <rect transform="translate(70, 145)" width="50" height="30" filter="url(#spotLightA)"/>
+        <rect transform="translate(165, 145)" width="50" height="30" filter="url(#spotLightB)"/>
+        <rect transform="translate(260, 145)" width="50" height="30" filter="url(#spotLightC)"/>
+        <rect transform="translate(355, 145)" width="50" height="30" filter="url(#spotLightD)"/>
+        <rect transform="translate(70, 245)" width="50" height="30" filter="url(#spotLightE)"/>
+        <rect transform="translate(165, 245)" width="50" height="30" filter="url(#spotLightF)"/>
+        <rect transform="translate(260, 245)" width="50" height="30" filter="url(#spotLightG)"/>
+        <rect transform="translate(355, 245)" width="50" height="30" filter="url(#spotLightH)"/>
+      </g>
+    </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.4 $</text>
diff --git a/svg/import/filters-light-05-f-manual.svg b/svg/import/filters-light-05-f-manual.svg
index 339837e..3712ef1 100644
--- a/svg/import/filters-light-05-f-manual.svg
+++ b/svg/import/filters-light-05-f-manual.svg
@@ -236,7 +236,7 @@
     <text x="305" y="290" font-size="10" stroke="none" fill="black">270 deg</text>
     <text x="365" y="290" font-size="10" stroke="none" fill="black">180 deg</text>
     <text x="430" y="290" font-size="10" stroke="none" fill="black">0 deg</text>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/filters-offset-01-b-manual.svg b/svg/import/filters-offset-01-b-manual.svg
index d00ad44..425564b 100644
--- a/svg/import/filters-offset-01-b-manual.svg
+++ b/svg/import/filters-offset-01-b-manual.svg
@@ -83,7 +83,7 @@
 
     <circle cx="160" cy="50" r="40" fill="#000" filter="url(#FOMTest)"/>
 
-    <!--  Add some crosshairs of the same color of the 
+    <!--  Add some crosshairs of the same color of the
             flood at the same location as the offset -->
 
     <!-- 20 pixel cross hair at 120,90 -->
diff --git a/svg/import/filters-offset-02-b-manual.svg b/svg/import/filters-offset-02-b-manual.svg
index bb933b9e..6ec08a7 100644
--- a/svg/import/filters-offset-02-b-manual.svg
+++ b/svg/import/filters-offset-02-b-manual.svg
@@ -13,20 +13,20 @@
     version="$Revision: 1.4 $" testname="$RCSfile: filters-offset-02-b.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/filters.html">
         <p>
-					Tests primitiveUnits="objectBoundingBox" and relative values.
+          Tests primitiveUnits="objectBoundingBox" and relative values.
 There should be three green rectangles with thick black stroke.
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.
-				
+
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-				The test has passed if there is nothing red visible and there are three
-				green rectangles with black stroke. If any green is visible outside the
-				black stroked rectangles the test has failed.
+        The test has passed if there is nothing red visible and there are three
+        green rectangles with black stroke. If any green is visible outside the
+        black stroked rectangles the test has failed.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -44,30 +44,30 @@
 
         <defs>
             <filter id="feoffset1" primitiveUnits="objectBoundingBox" x="0%" y="0%" width="200%" height="200%">
-								<feFlood width="100%" height="100%" flood-color="lime"/>
+                <feFlood width="100%" height="100%" flood-color="lime"/>
                 <feOffset dx="0.1" dy="0.2"/>
             </filter>
-						<filter id="feoffset2" primitiveUnits="objectBoundingBox" x="0%" y="0%" width="200%" height="200%">
-								<feOffset dx="1" dy="1"/>
+            <filter id="feoffset2" primitiveUnits="objectBoundingBox" x="0%" y="0%" width="200%" height="200%">
+                <feOffset dx="1" dy="1"/>
             </filter>
-						<filter id="feoffset3" primitiveUnits="objectBoundingBox" x="-100%" y="0%" width="200%" height="100%">
-								<feOffset dx="-1"/>
+            <filter id="feoffset3" primitiveUnits="objectBoundingBox" x="-100%" y="0%" width="200%" height="100%">
+                <feOffset dx="-1"/>
             </filter>
         </defs>
-				
-				<g transform="translate(-10 60)">
-					<rect x="60" y="70" width="100" height="100" fill="red"/>
-					<rect x="50" y="50" width="100" height="100" fill="red" filter="url(#feoffset1)"/>
-					<rect x="60" y="70" width="100" height="100" fill="none" stroke="black" stroke-width="2"/>
 
-					<rect x="200" y="70" width="100" height="100" fill="red"/>
-					<rect x="100" y="-30" width="100" height="100" fill="lime" filter="url(#feoffset2)"/>
-					<rect x="200" y="70" width="100" height="100" fill="none" stroke="black" stroke-width="2"/>
+        <g transform="translate(-10 60)">
+          <rect x="60" y="70" width="100" height="100" fill="red"/>
+          <rect x="50" y="50" width="100" height="100" fill="red" filter="url(#feoffset1)"/>
+          <rect x="60" y="70" width="100" height="100" fill="none" stroke="black" stroke-width="2"/>
 
-					<rect x="340" y="70" width="100" height="100" fill="red"/>
-					<rect x="440" y="70" width="100" height="100" fill="lime" filter="url(#feoffset3)"/>
-					<rect x="340" y="70" width="100" height="100" fill="none" stroke="black" stroke-width="2"/>
-			</g>
+          <rect x="200" y="70" width="100" height="100" fill="red"/>
+          <rect x="100" y="-30" width="100" height="100" fill="lime" filter="url(#feoffset2)"/>
+          <rect x="200" y="70" width="100" height="100" fill="none" stroke="black" stroke-width="2"/>
+
+          <rect x="340" y="70" width="100" height="100" fill="red"/>
+          <rect x="440" y="70" width="100" height="100" fill="lime" filter="url(#feoffset3)"/>
+          <rect x="340" y="70" width="100" height="100" fill="none" stroke="black" stroke-width="2"/>
+      </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
@@ -80,4 +80,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/filters-overview-02-b-manual.svg b/svg/import/filters-overview-02-b-manual.svg
index 89f48d7..5d67d42 100644
--- a/svg/import/filters-overview-02-b-manual.svg
+++ b/svg/import/filters-overview-02-b-manual.svg
@@ -1,178 +1,178 @@
-<svg version="1.1" baseProfile="basic" id="svg-root"

-  width="100%" height="100%" viewBox="0 0 480 360"

-  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

-  <!--======================================================================-->

-  <!--=  SVG 1.1 2nd Edition Test Case                                     =-->

-  <!--======================================================================-->

-  <!--=  Copyright 2009 World Wide Web Consortium, (Massachusetts          =-->

-  <!--=  Institute of Technology, European Research Consortium for         =-->

-  <!--=  Informatics and Mathematics (ERCIM), Keio University).            =-->

-  <!--=  All Rights Reserved.                                              =-->

-  <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->

-  <!--======================================================================-->

-  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/"

-    template-version="1.4" reviewer="CL" author="BB" status="created"

-    version="$Revision: 1.1 $" testname="$RCSfile: filters-overview-02-b.svg,v $">

-    <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/filters.html#FilterPrimitivesOverview">

-      <p>

-        The purpose of this file is to test the 'in' attribute on filter primitives.

-        This test is the same as filters-overview-01-b.svg but uses gradients with gradientUnits="userSpaceOnUse" instead for the

-        FillPaint/StrokePaint.

-      </p>

-    </d:testDescription>

-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">

-      <p>

-        Run the test. No interaction required.

-      </p>

-    </d:operatorScript>

-    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">

-      <p>

-        To pass this test, the UA must render all 6 cases (SourceGraphic, SourceAlpha, BackgroundImage, BackgroundAlpha, FillPaint, StrokePaint) correctly.

-      </p>

-      <ol>

-        <li>The result for in="SourceGraphic" is a non blurred vertical rectangle (green with dashed stroke) overlayed with three blurred circles (red/green/blue with dashed stroke).</li>

-        <li>The result for in="SourceAlpha" is a non blurred vertical rectangle (green with dashed stroke) overlayed with three blurred circles (dark gray with dashed stroke).</li>

-        <li>The result for in="BackgroundImage" is a blurred vertical rectangle (green with dashed stroke).</li>

-        <li>The result for in="BackgroundAlpha" is blurred vertical rectangle (dark gray with dashed stroke).</li>

-        <li>The results for in="FillPaint" and in="StrokePaint" are the same.  They consists of a non blurred vertical rectangle (green with dashed stroke) overlayed with a blurred gradiant (blue/white/red/yellow).</li>

-        <li>The size of the gradients are bigger than the blurred circles.</li>

-      </ol>

-    </d:passCriteria>

-  </d:SVGTestCase>

-  <title id="test-title">$RCSfile: filters-overview-02-b.svg,v $</title>

-  <defs>

-    <font-face font-family="SVGFreeSansASCII" unicode-range="U+0-7F">

-      <font-face-src>

-        <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>

-      </font-face-src>

-    </font-face>

-  </defs>

-  <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">

-    <defs>

-      <linearGradient id="grad1" gradientUnits="userSpaceOnUse" x1="140.5" y1="57.75" x2="140.5" y2="139.5">

-        <stop offset="0.00" stop-color="#0000ff"/>

-        <stop offset="0.33" stop-color="#ffffff"/>

-        <stop offset="0.67" stop-color="#ff0000"/>

-        <stop offset="1.00" stop-color="#ffff00"/>

-      </linearGradient>

-      

-      <linearGradient id="grad2" gradientUnits="userSpaceOnUse" x1="140.5" y1="192.75" x2="140.5" y2="274.5">

-        <stop offset="0.00" stop-color="#0000ff"/>

-        <stop offset="0.33" stop-color="#ffffff"/>

-        <stop offset="0.67" stop-color="#ff0000"/>

-        <stop offset="1.00" stop-color="#ffff00"/>

-      </linearGradient>

-

-      <filter id="GaussianBlur1" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">

-        <feGaussianBlur in="SourceAlpha" stdDeviation="2"/>

-      </filter>

-

-      <filter id="GaussianBlur2" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">

-        <feGaussianBlur in="SourceGraphic" stdDeviation="2"/>

-      </filter>

-

-      <filter id="GaussianBlur3" filterUnits="objectBoundingBox" x="-30%" y="-30%" width="160%" height="160%">

-        <feFlood flood-color="white" result="flood"/>

-        <feGaussianBlur in="BackgroundAlpha" stdDeviation="2" result="blur"/>

-        <feMerge>

-          <feMergeNode in="flood"/>

-          <feMergeNode in="blur"/>

-        </feMerge>

-      </filter>

-

-      <filter id="GaussianBlur4" filterUnits="objectBoundingBox" x="-40%" y="-40%" width="180%" height="180%">

-        <feFlood flood-color="white" result="flood"/>

-        <feGaussianBlur in="BackgroundImage" stdDeviation="2" result="blur"/>

-        <feMerge>

-          <feMergeNode in="flood"/>

-          <feMergeNode in="blur"/>

-        </feMerge>

-      </filter>

-

-      <filter id="GaussianBlur5" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">

-        <feGaussianBlur in="StrokePaint" stdDeviation="5"/>

-      </filter>

-

-      <filter id="GaussianBlur6" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">

-        <feGaussianBlur in="FillPaint" stdDeviation="5"/>

-      </filter>

-    </defs>

-

-    <g enable-background="new" stroke-dasharray="25 5" stroke="black" stroke-width="3" font-size="14" fill="black">

-

-      <rect x="90" y="50" width="16" height="95" fill="green"/>

-      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur1)">

-        <circle cx="098" cy="085" r="25" fill="red"/>

-        <circle cx="080" cy="110" r="25" fill="blue"/>

-        <circle cx="116" cy="110" r="25" fill="green"/>

-      </g>

-      <text x="98" y="160" stroke="none" text-anchor="middle">SourceAlpha</text>

-

-      <desc> =========================================================================================== </desc>

-

-      <rect x="90" y="185" width="16" height="95" fill="green"/>

-      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur2)">

-        <circle cx="098" cy="220" r="25" fill="red"/>

-        <circle cx="080" cy="245" r="25" fill="blue"/>

-        <circle cx="116" cy="245" r="25" fill="green"/>

-      </g>

-      <text x="98" y="295" stroke="none" text-anchor="middle">SourceGraphic</text>

-

-      <desc> =========================================================================================== </desc>

-

-      <rect x="230" y="50" width="16" height="95" fill="green"/>

-      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur3)">

-        <circle cx="238" cy="085" r="25" fill="red"/>

-        <circle cx="220" cy="110" r="25" fill="blue"/>

-        <circle cx="256" cy="110" r="25" fill="green"/>

-      </g>

-      <text x="238" y="160" stroke="none" text-anchor="middle">BackgroundAlpha</text>

-

-      <desc> =========================================================================================== </desc>

-

-      <rect x="230" y="185" width="16" height="95" fill="green"/>

-      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur4)">

-        <circle cx="238" cy="220" r="25" fill="red"/>

-        <circle cx="220" cy="245" r="25" fill="blue"/>

-        <circle cx="256" cy="245" r="25" fill="green"/>

-      </g>

-      <text x="238" y="295" stroke="none" text-anchor="middle">BackgroundImage</text>

-

-      <desc> =========================================================================================== </desc>

-

-      <rect x="370" y="50" width="16" height="95" fill="green"/>

-      <!-- bbox: 335 60 86 75 -->

-      <g stroke-opacity="0.6" stroke="url(#grad1)" stroke-width="3" filter="url(#GaussianBlur5)">

-        <circle cx="378" cy="085" r="25" fill="red"/>

-        <circle cx="360" cy="110" r="25" fill="blue"/>

-        <circle cx="396" cy="110" r="25" fill="green"/>

-      </g>

-      <text x="378" y="160" stroke="none" text-anchor="middle">FillPaint</text>

-

-      <desc> =========================================================================================== </desc>

-

-      <rect x="370" y="185" width="16" height="95" fill="green"/>

-      <!-- bbox: 335 195 86 75 -->

-      <g fill-opacity="0.6" fill="url(#grad2)" stroke="black" stroke-width="3" filter="url(#GaussianBlur6)">

-        <circle cx="378" cy="220" r="25"/>

-        <circle cx="360" cy="245" r="25"/>

-        <circle cx="396" cy="245" r="25"/>

-      </g>

-      <text x="378" y="295" stroke="none" text-anchor="middle">StrokePaint</text>

-

-    </g>

-

-    <text x="240" y="30" font-size="20" text-anchor="middle">Filter input test</text>

-

-  </g>

-  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">

-    <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.1 $</text>

-  </g>

-  <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>

-  <!-- comment out this watermark once the test is approved -->

-  <g id="draft-watermark">

-    <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>

-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"

-      text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>

-  </g>

-</svg>

+<svg version="1.1" baseProfile="basic" id="svg-root"
+  width="100%" height="100%" viewBox="0 0 480 360"
+  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <!--======================================================================-->
+  <!--=  SVG 1.1 2nd Edition Test Case                                     =-->
+  <!--======================================================================-->
+  <!--=  Copyright 2009 World Wide Web Consortium, (Massachusetts          =-->
+  <!--=  Institute of Technology, European Research Consortium for         =-->
+  <!--=  Informatics and Mathematics (ERCIM), Keio University).            =-->
+  <!--=  All Rights Reserved.                                              =-->
+  <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->
+  <!--======================================================================-->
+  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/"
+    template-version="1.4" reviewer="CL" author="BB" status="created"
+    version="$Revision: 1.1 $" testname="$RCSfile: filters-overview-02-b.svg,v $">
+    <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/filters.html#FilterPrimitivesOverview">
+      <p>
+        The purpose of this file is to test the 'in' attribute on filter primitives.
+        This test is the same as filters-overview-01-b.svg but uses gradients with gradientUnits="userSpaceOnUse" instead for the
+        FillPaint/StrokePaint.
+      </p>
+    </d:testDescription>
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
+      <p>
+        Run the test. No interaction required.
+      </p>
+    </d:operatorScript>
+    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
+      <p>
+        To pass this test, the UA must render all 6 cases (SourceGraphic, SourceAlpha, BackgroundImage, BackgroundAlpha, FillPaint, StrokePaint) correctly.
+      </p>
+      <ol>
+        <li>The result for in="SourceGraphic" is a non blurred vertical rectangle (green with dashed stroke) overlayed with three blurred circles (red/green/blue with dashed stroke).</li>
+        <li>The result for in="SourceAlpha" is a non blurred vertical rectangle (green with dashed stroke) overlayed with three blurred circles (dark gray with dashed stroke).</li>
+        <li>The result for in="BackgroundImage" is a blurred vertical rectangle (green with dashed stroke).</li>
+        <li>The result for in="BackgroundAlpha" is blurred vertical rectangle (dark gray with dashed stroke).</li>
+        <li>The results for in="FillPaint" and in="StrokePaint" are the same.  They consists of a non blurred vertical rectangle (green with dashed stroke) overlayed with a blurred gradiant (blue/white/red/yellow).</li>
+        <li>The size of the gradients are bigger than the blurred circles.</li>
+      </ol>
+    </d:passCriteria>
+  </d:SVGTestCase>
+  <title id="test-title">$RCSfile: filters-overview-02-b.svg,v $</title>
+  <defs>
+    <font-face font-family="SVGFreeSansASCII" unicode-range="U+0-7F">
+      <font-face-src>
+        <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>
+      </font-face-src>
+    </font-face>
+  </defs>
+  <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
+    <defs>
+      <linearGradient id="grad1" gradientUnits="userSpaceOnUse" x1="140.5" y1="57.75" x2="140.5" y2="139.5">
+        <stop offset="0.00" stop-color="#0000ff"/>
+        <stop offset="0.33" stop-color="#ffffff"/>
+        <stop offset="0.67" stop-color="#ff0000"/>
+        <stop offset="1.00" stop-color="#ffff00"/>
+      </linearGradient>
+
+      <linearGradient id="grad2" gradientUnits="userSpaceOnUse" x1="140.5" y1="192.75" x2="140.5" y2="274.5">
+        <stop offset="0.00" stop-color="#0000ff"/>
+        <stop offset="0.33" stop-color="#ffffff"/>
+        <stop offset="0.67" stop-color="#ff0000"/>
+        <stop offset="1.00" stop-color="#ffff00"/>
+      </linearGradient>
+
+      <filter id="GaussianBlur1" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">
+        <feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
+      </filter>
+
+      <filter id="GaussianBlur2" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">
+        <feGaussianBlur in="SourceGraphic" stdDeviation="2"/>
+      </filter>
+
+      <filter id="GaussianBlur3" filterUnits="objectBoundingBox" x="-30%" y="-30%" width="160%" height="160%">
+        <feFlood flood-color="white" result="flood"/>
+        <feGaussianBlur in="BackgroundAlpha" stdDeviation="2" result="blur"/>
+        <feMerge>
+          <feMergeNode in="flood"/>
+          <feMergeNode in="blur"/>
+        </feMerge>
+      </filter>
+
+      <filter id="GaussianBlur4" filterUnits="objectBoundingBox" x="-40%" y="-40%" width="180%" height="180%">
+        <feFlood flood-color="white" result="flood"/>
+        <feGaussianBlur in="BackgroundImage" stdDeviation="2" result="blur"/>
+        <feMerge>
+          <feMergeNode in="flood"/>
+          <feMergeNode in="blur"/>
+        </feMerge>
+      </filter>
+
+      <filter id="GaussianBlur5" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">
+        <feGaussianBlur in="StrokePaint" stdDeviation="5"/>
+      </filter>
+
+      <filter id="GaussianBlur6" filterUnits="objectBoundingBox" x="-20%" y="-20%" width="140%" height="140%">
+        <feGaussianBlur in="FillPaint" stdDeviation="5"/>
+      </filter>
+    </defs>
+
+    <g enable-background="new" stroke-dasharray="25 5" stroke="black" stroke-width="3" font-size="14" fill="black">
+
+      <rect x="90" y="50" width="16" height="95" fill="green"/>
+      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur1)">
+        <circle cx="098" cy="085" r="25" fill="red"/>
+        <circle cx="080" cy="110" r="25" fill="blue"/>
+        <circle cx="116" cy="110" r="25" fill="green"/>
+      </g>
+      <text x="98" y="160" stroke="none" text-anchor="middle">SourceAlpha</text>
+
+      <desc> =========================================================================================== </desc>
+
+      <rect x="90" y="185" width="16" height="95" fill="green"/>
+      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur2)">
+        <circle cx="098" cy="220" r="25" fill="red"/>
+        <circle cx="080" cy="245" r="25" fill="blue"/>
+        <circle cx="116" cy="245" r="25" fill="green"/>
+      </g>
+      <text x="98" y="295" stroke="none" text-anchor="middle">SourceGraphic</text>
+
+      <desc> =========================================================================================== </desc>
+
+      <rect x="230" y="50" width="16" height="95" fill="green"/>
+      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur3)">
+        <circle cx="238" cy="085" r="25" fill="red"/>
+        <circle cx="220" cy="110" r="25" fill="blue"/>
+        <circle cx="256" cy="110" r="25" fill="green"/>
+      </g>
+      <text x="238" y="160" stroke="none" text-anchor="middle">BackgroundAlpha</text>
+
+      <desc> =========================================================================================== </desc>
+
+      <rect x="230" y="185" width="16" height="95" fill="green"/>
+      <g fill-opacity="0.6" stroke="black" stroke-width="3" filter="url(#GaussianBlur4)">
+        <circle cx="238" cy="220" r="25" fill="red"/>
+        <circle cx="220" cy="245" r="25" fill="blue"/>
+        <circle cx="256" cy="245" r="25" fill="green"/>
+      </g>
+      <text x="238" y="295" stroke="none" text-anchor="middle">BackgroundImage</text>
+
+      <desc> =========================================================================================== </desc>
+
+      <rect x="370" y="50" width="16" height="95" fill="green"/>
+      <!-- bbox: 335 60 86 75 -->
+      <g stroke-opacity="0.6" stroke="url(#grad1)" stroke-width="3" filter="url(#GaussianBlur5)">
+        <circle cx="378" cy="085" r="25" fill="red"/>
+        <circle cx="360" cy="110" r="25" fill="blue"/>
+        <circle cx="396" cy="110" r="25" fill="green"/>
+      </g>
+      <text x="378" y="160" stroke="none" text-anchor="middle">FillPaint</text>
+
+      <desc> =========================================================================================== </desc>
+
+      <rect x="370" y="185" width="16" height="95" fill="green"/>
+      <!-- bbox: 335 195 86 75 -->
+      <g fill-opacity="0.6" fill="url(#grad2)" stroke="black" stroke-width="3" filter="url(#GaussianBlur6)">
+        <circle cx="378" cy="220" r="25"/>
+        <circle cx="360" cy="245" r="25"/>
+        <circle cx="396" cy="245" r="25"/>
+      </g>
+      <text x="378" y="295" stroke="none" text-anchor="middle">StrokePaint</text>
+
+    </g>
+
+    <text x="240" y="30" font-size="20" text-anchor="middle">Filter input test</text>
+
+  </g>
+  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
+    <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.1 $</text>
+  </g>
+  <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
+  <!-- comment out this watermark once the test is approved -->
+  <g id="draft-watermark">
+    <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
+      text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
+  </g>
+</svg>
diff --git a/svg/import/filters-turb-02-f-manual.svg b/svg/import/filters-turb-02-f-manual.svg
index 779bacc..d219b14 100644
--- a/svg/import/filters-turb-02-f-manual.svg
+++ b/svg/import/filters-turb-02-f-manual.svg
@@ -19,7 +19,7 @@
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
           Run the test. No interaction required.
-        
+
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -59,17 +59,17 @@
       </filter>
       <filter id="turbneg2" x="0" y="0" width="100%" height="100%">
         <feTurbulence seed="-0.5" baseFrequency="0.01" type="turbulence"/>
-      </filter>      
+      </filter>
       <filter id="turbneg3" x="0" y="0" width="100%" height="100%">
         <feTurbulence seed="-0.8" baseFrequency="0.01" type="turbulence"/>
       </filter>
-      
+
       <!-- the reference for seed="0" -->
       <filter id="turbzero" x="0" y="0" width="100%" height="100%">
         <feTurbulence seed="0" baseFrequency="0.01" type="turbulence"/>
       </filter>
-      
-      <!-- seed="0" is transformed by the setup_seed method to be equal to seed="1" --> 
+
+      <!-- seed="0" is transformed by the setup_seed method to be equal to seed="1" -->
       <filter id="turbpos1" x="0" y="0" width="100%" height="100%">
         <feTurbulence seed="0.2" baseFrequency="0.01" type="turbulence"/>
       </filter>
@@ -87,7 +87,7 @@
       <filter id="turbneg5" x="0" y="0" width="100%" height="100%">
         <feTurbulence seed="-1.5" baseFrequency="0.01" type="turbulence"/>
       </filter>
-      
+
       <!-- These should be the same -->
       <filter id="turbneg6" x="0" y="0" width="100%" height="100%">
         <feTurbulence seed="-2" baseFrequency="0.01" type="turbulence"/>
@@ -95,14 +95,14 @@
       <filter id="turbneg7" x="0" y="0" width="100%" height="100%">
         <feTurbulence seed="-2.6" baseFrequency="0.01" type="turbulence"/>
       </filter>
-            
+
       <style type="text/css">
         #subtests text { fill: black }
       </style>
     </defs>
-    
+
     <text x="50%" y="2em" style="font-size:24px; text-anchor:middle">feTurbulence seed</text>
-    
+
     <g id="subtests" transform="translate(65 80)" text-anchor="middle" fill="red">
       <rect width="50" height="50" filter="url(#turbneg3)"/>
       <text x="25" y="80">-0.8</text>
@@ -119,13 +119,13 @@
       <rect width="50" height="50" filter="url(#turbpos3)" transform="translate(300 0)"/>
       <text x="325" y="80">1.5</text>
       <rect x="-5" y="-5" width="360" height="100" stroke="black" fill="none"/>
-      
+
       <rect width="50" height="50" filter="url(#turbneg4)" transform="translate(0 120)"/>
       <text x="25" y="200">-1</text>
       <rect width="50" height="50" filter="url(#turbneg5)" transform="translate(50 120)"/>
       <text x="75" y="200">-1.5</text>
       <rect x="-5" y="115" width="110" height="100" stroke="black" fill="none"/>
-      
+
       <rect width="50" height="50" filter="url(#turbneg6)" transform="translate(250 120)"/>
       <text x="275" y="200">-2</text>
       <rect width="50" height="50" filter="url(#turbneg7)" transform="translate(300 120)"/>
diff --git a/svg/import/fonts-desc-01-t-manual.svg b/svg/import/fonts-desc-01-t-manual.svg
index e5e4c73..80123f4 100644
--- a/svg/import/fonts-desc-01-t-manual.svg
+++ b/svg/import/fonts-desc-01-t-manual.svg
@@ -19,7 +19,7 @@
         based on the font-size attribute.
       </p>
       <!-- the test creator appears to have misunderstood the specification here.
-      None of the supplied fonts specifies a font-size descriptor. 
+      None of the supplied fonts specifies a font-size descriptor.
       Its not clear what this test is trying to test.
       -->
     </d:testDescription>
diff --git a/svg/import/fonts-desc-02-t-manual.svg b/svg/import/fonts-desc-02-t-manual.svg
index 7bd720b..fce1a30 100644
--- a/svg/import/fonts-desc-02-t-manual.svg
+++ b/svg/import/fonts-desc-02-t-manual.svg
@@ -82,7 +82,6 @@
         <glyph unicode="a" glyph-name="upward-triangle" d="M0 0L500 0L250 900Z"/>
       </font>
 
-
       <font horiz-adv-x="500">
         <font-face font-family="SVGFont2" font-variant="small-caps" units-per-em="1000" ascent="800" descent="200" alphabetic="200"/>
         <missing-glyph horiz-adv-x="500" d="M0 0L500 0L500 1000L0 1000M50 50L50 950L450 950L450 50Z"/>
@@ -97,7 +96,6 @@
         <glyph unicode="a" glyph-name="square" d="M0 250L500 250L500 750L0 750Z"/>
       </font>
 
-
       <font horiz-adv-x="500">
         <font-face font-family="SVGFont4" font-variant="normal" units-per-em="1000" ascent="800" descent="200" alphabetic="200"/>
         <missing-glyph horiz-adv-x="500" d="M0 0L500 0L500 1000L0 1000M50 50L50 950L450 950L450 50Z"/>
diff --git a/svg/import/fonts-desc-03-t-manual.svg b/svg/import/fonts-desc-03-t-manual.svg
index d6de08d..2382fb4 100644
--- a/svg/import/fonts-desc-03-t-manual.svg
+++ b/svg/import/fonts-desc-03-t-manual.svg
@@ -71,7 +71,6 @@
         <glyph unicode="a" glyph-name="upward-triangle" d="M0 0L500 0L250 900Z"/>
       </font>
 
-
       <font horiz-adv-x="500">
         <font-face font-family="SVGFont2" font-weight="bold" units-per-em="1000" ascent="800" descent="200" alphabetic="200"/>
         <missing-glyph horiz-adv-x="500" d="M0 0L500 0L500 1000L0 1000M50 50L50 950L450 950L450 50Z"/>
@@ -86,7 +85,6 @@
         <glyph unicode="a" glyph-name="upward-triangle" d="M0 0L500 0L250 900Z"/>
       </font>
 
-
       <font horiz-adv-x="500">
         <font-face font-family="SVGFont3" font-weight="300" units-per-em="1000" ascent="800" descent="200" alphabetic="200"/>
         <missing-glyph horiz-adv-x="500" d="M0 0L500 0L500 1000L0 1000M50 50L50 950L450 950L450 50Z"/>
@@ -119,7 +117,7 @@
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.6 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/fonts-desc-04-t-manual.svg b/svg/import/fonts-desc-04-t-manual.svg
index fb77128..b7c4929 100644
--- a/svg/import/fonts-desc-04-t-manual.svg
+++ b/svg/import/fonts-desc-04-t-manual.svg
@@ -79,7 +79,6 @@
         <glyph unicode="a" glyph-name="upward-triangle" d="M0 0L500 0L250 900Z"/>
       </font>
 
-
       <font horiz-adv-x="500">
         <font-face font-family="SVGFont2" font-style="italic" units-per-em="1000" ascent="800" descent="200" alphabetic="200"/>
         <missing-glyph horiz-adv-x="500" d="M0 0L500 0L500 1000L0 1000M50 50L50 950L450 950L450 50Z"/>
@@ -94,7 +93,6 @@
         <glyph unicode="a" glyph-name="square" d="M0 250L500 250L500 750L0 750Z"/>
       </font>
 
-
       <font horiz-adv-x="500">
         <font-face font-family="SVGFont3" font-style="italic" units-per-em="1000" ascent="800" descent="200" alphabetic="200"/>
         <missing-glyph horiz-adv-x="500" d="M0 0L500 0L500 1000L0 1000M50 50L50 950L450 950L450 50Z"/>
@@ -109,7 +107,6 @@
         <glyph unicode="a" glyph-name="upward-triangle" d="M0 0L500 0L250 900Z"/>
       </font>
 
-
       <font horiz-adv-x="500">
         <font-face font-family="SVGFont4" font-style="italic" units-per-em="1000" ascent="800" descent="200" alphabetic="200"/>
         <missing-glyph horiz-adv-x="500" d="M0 0L500 0L500 1000L0 1000M50 50L50 950L450 950L450 50Z"/>
diff --git a/svg/import/fonts-elem-01-t-manual.svg b/svg/import/fonts-elem-01-t-manual.svg
index 9ff421c..6c68c24 100644
--- a/svg/import/fonts-elem-01-t-manual.svg
+++ b/svg/import/fonts-elem-01-t-manual.svg
@@ -38,9 +38,9 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test passes if the string "AyÖ@ç" is visible and fontsize, 
+        The test passes if the string "AyÖ@ç" is visible and fontsize,
         character baseline and horizontal advances are the same on both lines,
-	as shown in the reference image.
+  as shown in the reference image.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/fonts-elem-02-t-manual.svg b/svg/import/fonts-elem-02-t-manual.svg
index 7a8e347..0c55fac 100644
--- a/svg/import/fonts-elem-02-t-manual.svg
+++ b/svg/import/fonts-elem-02-t-manual.svg
@@ -23,7 +23,7 @@
       <p>
         The upper area has the placed glyphs as path elements filled with
         white over a solid black background (creating a white cutout). The
-        embedded SVG font text is then drawn over the cutout. 
+        embedded SVG font text is then drawn over the cutout.
       </p>
       <p>
         The lower area is the reverse of the upper area, with the placed
@@ -34,13 +34,13 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>Run the test. No interaction required.</p>
+  <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-	The test is passed iff the black text exactly overlays the white text 
-	on black, giving a solid black area. Some slight antialiasing effects may 
-	remain and do not cause the test to fail.
+  The test is passed iff the black text exactly overlays the white text
+  on black, giving a solid black area. Some slight antialiasing effects may
+  remain and do not cause the test to fail.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/fonts-elem-03-b-manual.svg b/svg/import/fonts-elem-03-b-manual.svg
index 6d73067..2a0e92b 100644
--- a/svg/import/fonts-elem-03-b-manual.svg
+++ b/svg/import/fonts-elem-03-b-manual.svg
@@ -35,14 +35,14 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>
-	Run the test. No interaction required.
-	</p>
+  <p>
+  Run the test. No interaction required.
+  </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test passes if the upper and lower lines show the same glyphs with 
-	the same glyph positioing and inter-glyph spacing.
+        The test passes if the upper and lower lines show the same glyphs with
+  the same glyph positioing and inter-glyph spacing.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -57,15 +57,15 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
       <!--style type="text/css">
-     			 <![CDATA[
-		        @font-face {
-			          font-family: 'TestComic'; 
-			          font-weight: normal;
-			          font-style: normal;
-			          src: url("../images/ext-TestComic.svg#Font") format("svg")
-			        }
-    			  ]]>
-  			 </style-->
+            <![CDATA[
+            @font-face {
+                font-family: 'TestComic';
+                font-weight: normal;
+                font-style: normal;
+                src: url("../images/ext-TestComic.svg#Font") format("svg")
+              }
+            ]]>
+         </style-->
       <font-face font-family="TestComic">
         <font-face-src>
           <font-face-uri xlink:href="../images/ext-TestComic.svg#Font"/>
diff --git a/svg/import/fonts-elem-04-b-manual.svg b/svg/import/fonts-elem-04-b-manual.svg
index 07362e5..b12baa6 100644
--- a/svg/import/fonts-elem-04-b-manual.svg
+++ b/svg/import/fonts-elem-04-b-manual.svg
@@ -35,14 +35,14 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>
-	Run the test. No interaction required.
-	</p>
+  <p>
+  Run the test. No interaction required.
+  </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test passes if the upper and lower lines show the same glyphs with 
-	the same glyph positioing and inter-glyph spacing.
+        The test passes if the upper and lower lines show the same glyphs with
+  the same glyph positioing and inter-glyph spacing.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -67,10 +67,10 @@
 
       </style>
       <!--font-face font-family="TestComic">
-				<font-face-src>
-					<font-face-uri xlink:href="../images/ext-TestComic.svg#Font"/>
-				</font-face-src>
-			</font-face-->
+        <font-face-src>
+          <font-face-uri xlink:href="../images/ext-TestComic.svg#Font"/>
+        </font-face-src>
+      </font-face-->
     </defs>
     <text fill="black" stroke="none" font-size="30" x="26" y="35">External SVG font element (css)</text>
     <!-- ====================================================================== -->
diff --git a/svg/import/fonts-elem-05-t-manual.svg b/svg/import/fonts-elem-05-t-manual.svg
index 7460c15..7d7c54b 100644
--- a/svg/import/fonts-elem-05-t-manual.svg
+++ b/svg/import/fonts-elem-05-t-manual.svg
@@ -15,7 +15,7 @@
     version="$Revision: 1.7 $" testname="$RCSfile: fonts-elem-05-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/fonts.html#FontElement">
         <p>
-				 This tests the horiz-origin-x attributes on the font and glyph elements.
+         This tests the horiz-origin-x attributes on the font and glyph elements.
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -66,7 +66,6 @@
         <glyph unicode="4" glyph-name="gl_4" horiz-adv-x="1500" d="M 0 0 L 1000 0 L 1000 1000 L 0 1000 Z"/>
       </font>
 
-
       <g id="marker">
         <line y2="-40" stroke="blue"/>
         <rect x="-4" y="-4" width="8" height="8" fill="#8888ff"/>
@@ -107,7 +106,6 @@
         <text x="240" font-family="origin500" font-size="30">1234</text>
       </g>
 
-
       <g transform="translate(0, 120)">
         <text>horiz-adv-x=1000 but ignored</text>
         <use xlink:href="#marker1000" x="240"/>
diff --git a/svg/import/fonts-elem-06-t-manual.svg b/svg/import/fonts-elem-06-t-manual.svg
index a6116e1..5a0f39d 100644
--- a/svg/import/fonts-elem-06-t-manual.svg
+++ b/svg/import/fonts-elem-06-t-manual.svg
@@ -99,7 +99,6 @@
         <text x="240" font-family="advance2000" font-size="30">12</text>
       </g>
 
-
       <g transform="translate(0, 80)">
         <text>horiz-adv-x=1000 but ignored</text>
         <use xlink:href="#marker" x="240"/>
diff --git a/svg/import/fonts-elem-07-b-manual.svg b/svg/import/fonts-elem-07-b-manual.svg
index f9167ae..e7e0c81 100644
--- a/svg/import/fonts-elem-07-b-manual.svg
+++ b/svg/import/fonts-elem-07-b-manual.svg
@@ -39,9 +39,9 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test passes if the string "AyÖ@ç" is visible and fontsize, 
+        The test passes if the string "AyÖ@ç" is visible and fontsize,
         character baseline and horizontal advances are the same on both lines,
-	as shown in the reference image.
+  as shown in the reference image.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/fonts-glyph-02-t-manual.svg b/svg/import/fonts-glyph-02-t-manual.svg
index 68ce4fa..5ef9f19 100644
--- a/svg/import/fonts-glyph-02-t-manual.svg
+++ b/svg/import/fonts-glyph-02-t-manual.svg
@@ -15,14 +15,14 @@
     version="$Revision: 1.8 $" testname="$RCSfile: fonts-glyph-02-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/fonts.html#GlyphElement">
       <p>
-        The first subtest tests the arabic-form attribute on the glyph element, 
-	the second subtest is the same, but with glyphs for the letter khah.
+        The first subtest tests the arabic-form attribute on the glyph element,
+  the second subtest is the same, but with glyphs for the letter khah.
         It should match the reference image.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-	Run the test. No interaction required.
+  Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -32,9 +32,9 @@
         and then an 'upward triangle' in this order. Remembering
         that arabic text is right to left.
       </p>
-			<p>The second subtest must produce the isolated, initial, medial, final and 
-	 			glyphs of the letter khah. Again in the writing direction, from right to left.
-	 		</p>
+      <p>The second subtest must produce the isolated, initial, medial, final and
+         glyphs of the letter khah. Again in the writing direction, from right to left.
+       </p>
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: fonts-glyph-02-t.svg,v $</title>
diff --git a/svg/import/fonts-glyph-03-t-manual.svg b/svg/import/fonts-glyph-03-t-manual.svg
index e7bcc10..929a737 100644
--- a/svg/import/fonts-glyph-03-t-manual.svg
+++ b/svg/import/fonts-glyph-03-t-manual.svg
@@ -14,7 +14,7 @@
     template-version="1.4" reviewer="CL" author="CN" status="issue"
     version="$Revision: 1.6 $" testname="$RCSfile: fonts-glyph-03-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/fonts.html#GlyphElement">
-    	<p>ISSUE: http://www.w3.org/2011/02/27-svg-irc#T22-20-51 - unapprove test for now</p>
+      <p>ISSUE: http://www.w3.org/2011/02/27-svg-irc#T22-20-51 - unapprove test for now</p>
       <p>
         This files tests the lang attribute support of the glyph
         element. The test should produce an upward-triangle for
diff --git a/svg/import/fonts-glyph-04-t-manual.svg b/svg/import/fonts-glyph-04-t-manual.svg
index 498244b..b6aed2b 100644
--- a/svg/import/fonts-glyph-04-t-manual.svg
+++ b/svg/import/fonts-glyph-04-t-manual.svg
@@ -27,8 +27,8 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test is passed if on the first line you see two upward pointed triangles 
-	followed by the letter "l". On the second line, a single square.
+        The test is passed if on the first line you see two upward pointed triangles
+  followed by the letter "l". On the second line, a single square.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/fonts-overview-201-t-manual.svg b/svg/import/fonts-overview-201-t-manual.svg
index 65de3f4..090bd25 100644
--- a/svg/import/fonts-overview-201-t-manual.svg
+++ b/svg/import/fonts-overview-201-t-manual.svg
@@ -25,12 +25,12 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The test is passed if the three letter β are all the same size.
+        The test is passed if the three letter β are all the same size.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -119,4 +119,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/imp-path-01-f-manual.svg b/svg/import/imp-path-01-f-manual.svg
index 1cae432..f18fbb9 100644
--- a/svg/import/imp-path-01-f-manual.svg
+++ b/svg/import/imp-path-01-f-manual.svg
@@ -8,17 +8,17 @@
   <!--======================================================================-->
   <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/" template-version="1.4" reviewer="CL" author="Microsoft" status="reviewed" version="$Revision: 1.5 $" testname="$RCSfile: imp-path-01-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes">
-			<p>
+      <p>
         Tests that markers are drawn on zero-length 'path' and 'line' segments.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>Run the test. No interaction required.</p>
+      <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Test passes if there are two blue boxes, positioned as in the reference image.
-      </p>  
+      </p>
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: imp-path-01-f.svg,v $</title>
diff --git a/svg/import/interact-cursor-01-f-manual.svg b/svg/import/interact-cursor-01-f-manual.svg
index ff7d716..3ae9836 100644
--- a/svg/import/interact-cursor-01-f-manual.svg
+++ b/svg/import/interact-cursor-01-f-manual.svg
@@ -15,8 +15,8 @@
     version="$Revision: 1.5 $" testname="$RCSfile: interact-cursor-01-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/interact.html#Cursors">
         <p>
-	Purpose of test is to determine if the cursor property and cursor element are
-        supported. 
+  Purpose of test is to determine if the cursor property and cursor element are
+        supported.
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/interact-dom-01-b-manual.svg b/svg/import/interact-dom-01-b-manual.svg
index 907cc20..32cf2f5 100644
--- a/svg/import/interact-dom-01-b-manual.svg
+++ b/svg/import/interact-dom-01-b-manual.svg
@@ -34,15 +34,15 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>This test requires user interaction. Run the test, then click on the grey rectangle.
-	  If it turns green, click it again.
-	</p>
+  <p>This test requires user interaction. Run the test, then click on the grey rectangle.
+    If it turns green, click it again.
+  </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        After clicking once on the button, the rectangle should have a green fill 
-	and the text "Event listeners supported" should appear, once.
-        
+        After clicking once on the button, the rectangle should have a green fill
+  and the text "Event listeners supported" should appear, once.
+
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/interact-events-202-f-manual.svg b/svg/import/interact-events-202-f-manual.svg
index 61ed1c0..6fbabe2 100644
--- a/svg/import/interact-events-202-f-manual.svg
+++ b/svg/import/interact-events-202-f-manual.svg
@@ -1,83 +1,83 @@
-<svg id="svg-root" width="100%" height="100%"

-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"

-  xmlns:xlink="http://www.w3.org/1999/xlink">

-  <!--======================================================================-->

-  <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->

-  <!--=  Institute of Technology, European Research Consortium for         =-->

-  <!--=  Informatics and Mathematics (ERCIM), Keio University).            =-->

-  <!--=  All Rights Reserved.                                              =-->

-  <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->

-  <!--======================================================================-->

-  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/"

-    template-version="1.4" reviewer="AE" author="ASl" status="accepted"

-    version="$Revision: 1.3 $" testname="$RCSfile: interact-events-202-f.svg,v $">

-    <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/interact.html#SVGEvents">

-      <p>

-        Testing event bubbling through 'use' element.

-      </p>

-    </d:testDescription>

-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">

-      <p>

-      	Mouseover the blue rect, then the green rect and then away from the rects.

-      </p>

-    </d:operatorScript>

-    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">

-      <p>

-        Moving the mouse over the blue rect should make a yellow rect visible underneath it.

-        Moving the mouse over the green rect should make a purple rect visible underneath it.

-        Moving the mouse away from the blue/green rect should hide the rect underneath it again.

-      </p>

-    </d:passCriteria>

-  </d:SVGTestCase>

-  <title id="test-title">$RCSfile: interact-events-202-f.svg,v $</title>

-  <defs>

-    <font-face

-      font-family="SVGFreeSansASCII"

-      unicode-range="U+0-7F">

-      <font-face-src>

-        <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>

-      </font-face-src>

-    </font-face>

-  </defs>

-  <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">

-  	<defs>

-  		<rect id="r1" x="10" y="20" width="90" height="65" visibility="inherit" fill="inherit"/>

-  	</defs>

-  	

-		<g id="g1" transform="translate( 0,  0)" visibility="visible">

-      <use xlink:href="#r1" visibility="inherit" fill="blue" 

-           onmouseover="g3.setAttribute('visibility', 'visible')"

-           onmouseout="g3.setAttribute('visibility', 'hidden')"/>

-    </g>

-    <g id="g2" transform="translate(90,  0)" visibility="visible" 

-    	 onmouseover="g4.setAttribute('visibility', 'visible')"

-    	 onmouseout="g4.setAttribute('visibility', 'hidden')">

-      <use xlink:href="#r1" visibility="inherit" fill="green"/>

-    </g>

-    <g id="g3" transform="translate( 0, 65)" visibility="hidden">

-      <use xlink:href="#r1" visibility="inherit" fill="yellow"/>

-    </g>

-    <g id="g4" transform="translate(90, 65)" visibility="hidden">

-      <use xlink:href="#r1" visibility="inherit" fill="purple"/>

-    </g>

-

-    <script type="text/ecmascript">

-      var g1 = document.getElementById("g1");

-      var g2 = document.getElementById("g2");

-      var g3 = document.getElementById("g3");

-      var g4 = document.getElementById("g4");

-    </script>

-

-  </g>

-  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">

-    <text id="revision" x="10" y="340" stroke="none"

-      fill="black">$Revision: 1.3 $</text>

-  </g>

-  <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>

-  <!-- comment out this watermark once the test is approved

-  <g id="draft-watermark">

-    <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>

-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"

-      text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>

-  </g>-->

-</svg>
\ No newline at end of file
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
+  xmlns:xlink="http://www.w3.org/1999/xlink">
+  <!--======================================================================-->
+  <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
+  <!--=  Institute of Technology, European Research Consortium for         =-->
+  <!--=  Informatics and Mathematics (ERCIM), Keio University).            =-->
+  <!--=  All Rights Reserved.                                              =-->
+  <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->
+  <!--======================================================================-->
+  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/"
+    template-version="1.4" reviewer="AE" author="ASl" status="accepted"
+    version="$Revision: 1.3 $" testname="$RCSfile: interact-events-202-f.svg,v $">
+    <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/interact.html#SVGEvents">
+      <p>
+        Testing event bubbling through 'use' element.
+      </p>
+    </d:testDescription>
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
+      <p>
+        Mouseover the blue rect, then the green rect and then away from the rects.
+      </p>
+    </d:operatorScript>
+    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
+      <p>
+        Moving the mouse over the blue rect should make a yellow rect visible underneath it.
+        Moving the mouse over the green rect should make a purple rect visible underneath it.
+        Moving the mouse away from the blue/green rect should hide the rect underneath it again.
+      </p>
+    </d:passCriteria>
+  </d:SVGTestCase>
+  <title id="test-title">$RCSfile: interact-events-202-f.svg,v $</title>
+  <defs>
+    <font-face
+      font-family="SVGFreeSansASCII"
+      unicode-range="U+0-7F">
+      <font-face-src>
+        <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>
+      </font-face-src>
+    </font-face>
+  </defs>
+  <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
+    <defs>
+      <rect id="r1" x="10" y="20" width="90" height="65" visibility="inherit" fill="inherit"/>
+    </defs>
+
+    <g id="g1" transform="translate( 0,  0)" visibility="visible">
+      <use xlink:href="#r1" visibility="inherit" fill="blue"
+           onmouseover="g3.setAttribute('visibility', 'visible')"
+           onmouseout="g3.setAttribute('visibility', 'hidden')"/>
+    </g>
+    <g id="g2" transform="translate(90,  0)" visibility="visible"
+       onmouseover="g4.setAttribute('visibility', 'visible')"
+       onmouseout="g4.setAttribute('visibility', 'hidden')">
+      <use xlink:href="#r1" visibility="inherit" fill="green"/>
+    </g>
+    <g id="g3" transform="translate( 0, 65)" visibility="hidden">
+      <use xlink:href="#r1" visibility="inherit" fill="yellow"/>
+    </g>
+    <g id="g4" transform="translate(90, 65)" visibility="hidden">
+      <use xlink:href="#r1" visibility="inherit" fill="purple"/>
+    </g>
+
+    <script type="text/ecmascript">
+      var g1 = document.getElementById("g1");
+      var g2 = document.getElementById("g2");
+      var g3 = document.getElementById("g3");
+      var g4 = document.getElementById("g4");
+    </script>
+
+  </g>
+  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
+    <text id="revision" x="10" y="340" stroke="none"
+      fill="black">$Revision: 1.3 $</text>
+  </g>
+  <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
+  <!-- comment out this watermark once the test is approved
+  <g id="draft-watermark">
+    <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
+      text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
+  </g>-->
+</svg>
diff --git a/svg/import/interact-events-203-t-manual.svg b/svg/import/interact-events-203-t-manual.svg
index 6b3f944..2e11b27 100644
--- a/svg/import/interact-events-203-t-manual.svg
+++ b/svg/import/interact-events-203-t-manual.svg
@@ -22,16 +22,16 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
-    		Mouseover each of the red rectangles, and then click on the bottommost rectangle.
-    	</p>
+      <p>
+        Mouseover each of the red rectangles, and then click on the bottommost rectangle.
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         This test contains four cases. The cases must produce the following results for the test to pass.
       </p>
       <ul>
-      	<li>Case 1: On a mouseover event on the top square, all four squares must turn blue.</li>
+        <li>Case 1: On a mouseover event on the top square, all four squares must turn blue.</li>
         <li>Case 2: On a mouseover event on the top middle square, all four squares must turn blue and a black stroke
         must appear on the referencing square (element).</li>
         <li>Case 3: On a mouseover event on the bottom middle square, all four squares must turn blue and a black
@@ -52,11 +52,11 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	
-		<defs>
-			<!-- SVGElementInstance animates fill on mouseover -->
-			<rect id="rect" width="50" height="50" fill="red" onmouseover="document.getElementById('rect').setAttribute('fill','blue')" onmouseout="document.getElementById('rect').setAttribute('fill','red')" />
-		</defs>
+
+    <defs>
+      <!-- SVGElementInstance animates fill on mouseover -->
+      <rect id="rect" width="50" height="50" fill="red" onmouseover="document.getElementById('rect').setAttribute('fill','blue')" onmouseout="document.getElementById('rect').setAttribute('fill','red')" />
+    </defs>
 
     <text x="120" y="20" font-size="15">Shadow tree event listener chain</text>
 
@@ -68,7 +68,7 @@
     <!-- all rectangles turn blue and second rectangle displays black stroke  -->
     <use id="use2" fill="red" x="30" y="100" xlink:href="#rect" onmouseover="document.getElementById('rect1').setAttribute('stroke','black')" onmouseout="document.getElementById('rect1').setAttribute('stroke','none')"/>
     <rect id="rect1" pointer-events="none" x="30" y="100" width="50" height="50" fill="none" stroke-width="5" stroke="none"/>
-    
+
     <g font-size="12">
       <text x="95" y="115">Case 2: on mouseover all squares must turn blue</text>
       <text x="95" y="130">and a black stroke must appear on reference square</text>
diff --git a/svg/import/interact-order-01-b-manual.svg b/svg/import/interact-order-01-b-manual.svg
index c552a96..1b54c49 100644
--- a/svg/import/interact-order-01-b-manual.svg
+++ b/svg/import/interact-order-01-b-manual.svg
@@ -26,14 +26,14 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>This test requires user interaction. Firstly, move the pointer
-	over the top circle. Then, move it over the bottom circle.</p>
+  over the top circle. Then, move it over the bottom circle.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test is passed if two black circles are displayed. The top circle 
-must turn pink when the pointer is over the circle, and go back to black once 
-the pointer leaves. The second circle  must turn blue when the pointer is over 
-the circle, and go back to black once the pointer leaves. 
+        The test is passed if two black circles are displayed. The top circle
+must turn pink when the pointer is over the circle, and go back to black once
+the pointer leaves. The second circle  must turn blue when the pointer is over
+the circle, and go back to black once the pointer leaves.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -72,7 +72,7 @@
     <g fill="#000" font-family="Arial" font-size="40">
       <!-- Event bubbling (i.e., propagation) is not turned off, so
               event if first processed by 'circle', but then processed by 'g',
-              with result that circle turns blue, but then instantaneously 
+              with result that circle turns blue, but then instantaneously
               turns pink. -->
       <g onmouseover="bubbleYes(evt, '#F08')" onmouseout="bubbleYes(evt, 'inherit')">
         <circle onmouseover="bubbleYes(evt, '#00F')" onmouseout="bubbleYes(evt, 'inherit')" cx="70" cy="120" r="50"/>
diff --git a/svg/import/interact-order-02-b-manual.svg b/svg/import/interact-order-02-b-manual.svg
index 175a739..7b439da 100644
--- a/svg/import/interact-order-02-b-manual.svg
+++ b/svg/import/interact-order-02-b-manual.svg
@@ -21,7 +21,7 @@
         The two circles test whether events are handled in the
         proper order. Events listeners and event attributes are processed
         before hyperlink processing, which is processed before text selection.
-       
+
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -29,9 +29,9 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        Clicking on the first circle should change the circle from black to red.  Clicking 
-        on the second circle should take you to another SVG file titled "Hyperlink target for 
-        interact-order-02.svg.  Cliking on the circle in this SVG file should return you to the orginal 
+        Clicking on the first circle should change the circle from black to red.  Clicking
+        on the second circle should take you to another SVG file titled "Hyperlink target for
+        interact-order-02.svg.  Cliking on the circle in this SVG file should return you to the orginal
         two circles.
       </p>
     </d:passCriteria>
diff --git a/svg/import/interact-pevents-05-b-manual.svg b/svg/import/interact-pevents-05-b-manual.svg
index 7a6f681..3ece8a1 100644
--- a/svg/import/interact-pevents-05-b-manual.svg
+++ b/svg/import/interact-pevents-05-b-manual.svg
@@ -15,7 +15,7 @@
     version="$Revision: 1.7 $" testname="$RCSfile: interact-pevents-05-b.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty">
         <p>
-          Tests when text is considered hit by pointer-events. 
+          Tests when text is considered hit by pointer-events.
           According to SVG 1.1 pointer-events on text is not supposed to use the text boundingbox, instead it should use the 'character cells'.
         </p>
     </d:testDescription>
diff --git a/svg/import/interact-pevents-07-t-manual.svg b/svg/import/interact-pevents-07-t-manual.svg
index e78f68e..aff0212 100644
--- a/svg/import/interact-pevents-07-t-manual.svg
+++ b/svg/import/interact-pevents-07-t-manual.svg
@@ -42,7 +42,7 @@
       {
         document.getElementById(id).setAttribute("fill", fill);
       }
-      
+
       function changePointerEvents(value)
       {
         if ('on' == value)
@@ -65,7 +65,7 @@
             document.getElementById('c11').setAttribute("pointer-events", 'none');
             document.getElementById('c12').setAttribute("pointer-events", 'none');
         }
-        
+
       }
     ]]></script>
     <rect x="15" y="15" height="280" width="450" fill="none" stroke="black" stroke-width="0.25"/>
@@ -105,7 +105,7 @@
       fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/interact-pevents-08-f-manual.svg b/svg/import/interact-pevents-08-f-manual.svg
index f3a3d3b..e020a75 100644
--- a/svg/import/interact-pevents-08-f-manual.svg
+++ b/svg/import/interact-pevents-08-f-manual.svg
@@ -65,7 +65,7 @@
       {
         document.getElementById(id).setAttribute("fill-opacity", "0.4");
       }
-      
+
       function hide(id)
       {
         document.getElementById(id).setAttribute("fill-opacity", "0");
@@ -181,5 +181,5 @@
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
-  </g>--> 
+  </g>-->
 </svg>
diff --git a/svg/import/interact-pevents-09-f-manual.svg b/svg/import/interact-pevents-09-f-manual.svg
index 44d7372..0354ce3 100644
--- a/svg/import/interact-pevents-09-f-manual.svg
+++ b/svg/import/interact-pevents-09-f-manual.svg
@@ -33,8 +33,8 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test is passed if the following conditions are met:
-     	</p>
-     	<ul>
+       </p>
+       <ul>
         <li>In the first row of squares, the fill and stroke of squares 1, 3 and 4 only must trigger a pale red rectangle to appear
         over the squares on mouseover.</li>
         <li>In the second row of squares, the fill only of all squares must trigger a pale red rectangle to appear
@@ -63,7 +63,7 @@
       {
         document.getElementById(id).setAttribute("fill-opacity", "0.4");
       }
-      
+
       function hide(id)
       {
         document.getElementById(id).setAttribute("fill-opacity", "0");
@@ -170,5 +170,5 @@
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
-  </g>--> 
+  </g>-->
 </svg>
diff --git a/svg/import/interact-pevents-10-f-manual.svg b/svg/import/interact-pevents-10-f-manual.svg
index c05e2b3..1be6143 100644
--- a/svg/import/interact-pevents-10-f-manual.svg
+++ b/svg/import/interact-pevents-10-f-manual.svg
@@ -16,9 +16,9 @@
         An element with 'display' set to 'none' or an element whose parent has 'display' set to 'none' is not a target of pointer events.
       </p>
       <p>
-        Stack a 'circle' element with 'display' equal to 'none' on another 'circle' element. 
-        Specify an 'onclick' event handler on the 'circle' with 'display' set to 'none' that will change the 'visibility' of 'FAIL' text to 'visible'. 
-        Verify that the event handler does not fire which clicking on the top 'circle' element. 
+        Stack a 'circle' element with 'display' equal to 'none' on another 'circle' element.
+        Specify an 'onclick' event handler on the 'circle' with 'display' set to 'none' that will change the 'visibility' of 'FAIL' text to 'visible'.
+        Verify that the event handler does not fire which clicking on the top 'circle' element.
         Repeat with another set of 'circle' elements with the parent of one of the 'circle' elements having its 'display' set to 'none'.
       </p>
     </d:testDescription>
diff --git a/svg/import/interact-zoom-03-t-manual.svg b/svg/import/interact-zoom-03-t-manual.svg
index 6821e1a..2f1aaab 100644
--- a/svg/import/interact-zoom-03-t-manual.svg
+++ b/svg/import/interact-zoom-03-t-manual.svg
@@ -22,14 +22,13 @@
       <p>
         The test consists of a set of black circles with a  blue stroke.
       </p>
-     
-      
+
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         After the initial picture is displayed, the user should attempt to use
         the magnify controls that are required on conforming Dynamic SVG
-        viewers. 
+        viewers.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/linking-a-01-b-manual.svg b/svg/import/linking-a-01-b-manual.svg
index cf397ad..cf84f5f 100644
--- a/svg/import/linking-a-01-b-manual.svg
+++ b/svg/import/linking-a-01-b-manual.svg
@@ -28,7 +28,7 @@
       <p>
         The (blue) arrow uses the "bare name" fragment identifier
         form, "#circle-2", to target the circle with id "circle-2" in the external
-        file. 
+        file.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/linking-a-03-b-manual.svg b/svg/import/linking-a-03-b-manual.svg
index 8e5ef76..3cd643a 100644
--- a/svg/import/linking-a-03-b-manual.svg
+++ b/svg/import/linking-a-03-b-manual.svg
@@ -29,7 +29,7 @@
         The (green) arrow uses the SVG view specification form,
         "linkingCircle-f.svg#svgView(viewBox(63,226,74,74))",
         to target the circle with id "circle-2"
-        in the external file.  
+        in the external file.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -62,7 +62,7 @@
       <a xlink:href="../images/linkingCircle-f.svg#svgView(viewBox(64,227,72,72))">
         <path fill="lime" stroke="blue" d="M 185,40 h -100 v -12 l -45 36 l 45 36 v -12 h 100 z"/>
       </a>
-      <!-- Code from target file, linkingCircle-f.svg...        
+      <!-- Code from target file, linkingCircle-f.svg...
         <circle id="circle-2" cx="85" cy="153" r="20"  stroke="lime" fill="yellow" stroke-width="4"  />
         <text font-family="Arial" font-size="12"  x="65" y="189">circle-2</text>
         <rect x="49" y="227" width="72" height="72" fill="none" stroke="black" />
diff --git a/svg/import/linking-a-04-t-manual.svg b/svg/import/linking-a-04-t-manual.svg
index 02db16b..e936e60 100644
--- a/svg/import/linking-a-04-t-manual.svg
+++ b/svg/import/linking-a-04-t-manual.svg
@@ -23,7 +23,7 @@
         The initial view of this test contains the three arrows, a colored
         circle, labelling text, and the usual template legend and frame.
       </p>
-    
+
       <p>
         There are several reference images associated with this test case.  The first
         illustrates the correct "start" or initial state of the rendered SVG file.
@@ -43,7 +43,7 @@
         The user should interact with each of the arrows activating each of the links,
         using the UA's back mechanism to restart each link test.
       </p>
-     
+
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/linking-a-07-t-manual.svg b/svg/import/linking-a-07-t-manual.svg
index 7ea4441..f799d73 100644
--- a/svg/import/linking-a-07-t-manual.svg
+++ b/svg/import/linking-a-07-t-manual.svg
@@ -27,13 +27,13 @@
         The top-most (yellow) arrows link to an external SVG file, which is
         local (in the same directory).  The target file contains SVG 'text' elements
         which comprise a TOC and brief description of all of the test files
-        for Linking. 
+        for Linking.
       </p>
       <p>
-        The middle (green) arrows links to the same external SVG file, but with xlink:show="new". 
+        The middle (green) arrows links to the same external SVG file, but with xlink:show="new".
       </p>
       <p>
-        The bottom-most (blue) arrows links to the same external SVG file, but with xlink:show="replace". 
+        The bottom-most (blue) arrows links to the same external SVG file, but with xlink:show="replace".
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -46,13 +46,13 @@
         The test has passed if:
       </p>
       <ul>
-	      <li>Upon clicking the left-most yellow arrow, the image of the linkingToc-t.svg
-	      file replaces the initial view of this test case in the viewer frame. On clicking the right-most yellow arrow,
-	      the image of the linkingToc-t.svg appears in a new frame.</li>
-	      <li>Upon clicking any of the green arrows the image of the linkingToc-t.svg appears in a new frame.</li>
-	      <li>Upon clicking the left-most blue arrow, the image of the linkingToc-t.svg file replaces the initial 
-	      view of this test case in the viewer frame. 
-	      On clicking the right blue arrow produces the image of the linkingToc-t.svg in a new frame.</li>
+        <li>Upon clicking the left-most yellow arrow, the image of the linkingToc-t.svg
+        file replaces the initial view of this test case in the viewer frame. On clicking the right-most yellow arrow,
+        the image of the linkingToc-t.svg appears in a new frame.</li>
+        <li>Upon clicking any of the green arrows the image of the linkingToc-t.svg appears in a new frame.</li>
+        <li>Upon clicking the left-most blue arrow, the image of the linkingToc-t.svg file replaces the initial
+        view of this test case in the viewer frame.
+        On clicking the right blue arrow produces the image of the linkingToc-t.svg in a new frame.</li>
       </ul>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/linking-a-08-t-manual.svg b/svg/import/linking-a-08-t-manual.svg
index c0c2ccd..b94d9d0 100644
--- a/svg/import/linking-a-08-t-manual.svg
+++ b/svg/import/linking-a-08-t-manual.svg
@@ -21,12 +21,12 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
- 				Click each of the links once.
+         Click each of the links once.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Both lines of text must be working links, and must take you to <a href="../../images/linkingToc-t.svg">the linking TOC svg</a> in the same frame.
+        Both lines of text must be working links, and must take you to <a href="../../images/linkingToc-t.svg">the linking TOC svg</a> in the same frame.
        </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/linking-a-09-b-manual.svg b/svg/import/linking-a-09-b-manual.svg
index 1099cd7..6efc82f 100644
--- a/svg/import/linking-a-09-b-manual.svg
+++ b/svg/import/linking-a-09-b-manual.svg
@@ -26,7 +26,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Both lines of text must be working links, and must take you to <a href="../../images/linkingToc-t.svg">the linking TOC svg</a> in the same frame.
+        Both lines of text must be working links, and must take you to <a href="../../images/linkingToc-t.svg">the linking TOC svg</a> in the same frame.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/linking-a-10-f-manual.svg b/svg/import/linking-a-10-f-manual.svg
index 5138de8..458f712 100644
--- a/svg/import/linking-a-10-f-manual.svg
+++ b/svg/import/linking-a-10-f-manual.svg
@@ -13,7 +13,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>Run the test. No interaction required.</p>
+      <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
@@ -80,4 +80,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/masking-mask-02-f-manual.svg b/svg/import/masking-mask-02-f-manual.svg
index 38cd942..c96a456 100644
--- a/svg/import/masking-mask-02-f-manual.svg
+++ b/svg/import/masking-mask-02-f-manual.svg
@@ -13,7 +13,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>Run the test. No interaction required.</p>
+      <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
@@ -47,4 +47,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/masking-opacity-01-b-manual.svg b/svg/import/masking-opacity-01-b-manual.svg
index ce34cae..35477ef 100644
--- a/svg/import/masking-opacity-01-b-manual.svg
+++ b/svg/import/masking-opacity-01-b-manual.svg
@@ -28,20 +28,19 @@
       </p>
       <p>
         In the top test, the opacities of the group and the individual rectangles are
-        all set to 1. 
+        all set to 1.
       </p>
       <p>
-        In the second test, the group is given an opacity of 0.5. 
+        In the second test, the group is given an opacity of 0.5.
       </p>
       <p>
         In the third test, the group maintains a group opacity of 1 whereas each individual
-        rectangle is given an opacity of 0.5 in the group. 
+        rectangle is given an opacity of 0.5 in the group.
       </p>
       <p>
         Lastly, the group and individual rectangles are all given an opacity of 0.5.
       </p>
 
-
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/masking-path-02-b-manual.svg b/svg/import/masking-path-02-b-manual.svg
index d952df1..e2b9b4c 100644
--- a/svg/import/masking-path-02-b-manual.svg
+++ b/svg/import/masking-path-02-b-manual.svg
@@ -23,12 +23,12 @@
       <p>
         The test at the top shows a pink rectangle that has been clipped by a
         rectangular clipping path. The clipping path is defined using clipPathUnits=objectBoundingBox.
-        
+
       </p>
       <p>
         The example at the bottom a rotated blue rectangle that has been clipped by a
         rectangular clipping path. The clipping path is defined using clipPathUnits=userSpaceOnUse.
-        
+
       </p>
       <p>
         The rendered picture should match the reference image exactly, except for possible
diff --git a/svg/import/masking-path-03-b-manual.svg b/svg/import/masking-path-03-b-manual.svg
index 92186a6..51d0da2 100644
--- a/svg/import/masking-path-03-b-manual.svg
+++ b/svg/import/masking-path-03-b-manual.svg
@@ -39,7 +39,7 @@
         interior, a light blue border and a black string that says "Clip to
         inner 'svg'" is painted four times such that it will overflow each of
         the top, left, right and bottom sides of the bounds of an inner 'svg'
-        element, respectively.  
+        element, respectively.
       </p>
       <p>
         Note that minor text layout differences, as are permissible under CSS2
diff --git a/svg/import/masking-path-06-b-manual.svg b/svg/import/masking-path-06-b-manual.svg
index 6d0425a..b3c78f0 100644
--- a/svg/import/masking-path-06-b-manual.svg
+++ b/svg/import/masking-path-06-b-manual.svg
@@ -17,7 +17,7 @@
       <p>
         The intent of this file to test the 'clip' property.  In this test, the clipped objects are
         raster and SVG images.
-        
+
         <!-- ED: The bottom right subtest is the source of the issue, since CSS2.1 changed how 'clip' should be interpreted compared to CSS2.0. -->
       </p>
     </d:testDescription>
diff --git a/svg/import/masking-path-07-b-manual.svg b/svg/import/masking-path-07-b-manual.svg
index 63f73d8..6a2a82f 100644
--- a/svg/import/masking-path-07-b-manual.svg
+++ b/svg/import/masking-path-07-b-manual.svg
@@ -31,24 +31,24 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test has passed if the following conditions are met:
-			</p>
-			<ul>
-				<li>There is no red visible.</li>
-				<li>No shapes extend outside of the rects that have a thick black border.</li>
-				<li>For the left subtest:
-					<ul>
-						<li>There must be a large blue rect with a transparent smaller rect in it, and the intersection of two circles.</li>
-						<li>The borders of the clipregions are shown with black stroke.</li>
-						<li>The blue shapes must be visible only inside of these stroked regions.</li>
-				 	</ul>
-				</li>
-				<li>For the right subtest:
-					<ul>
-						<li>The test on the right must show part of the large blue rect shape with a transparent rect in it, and part of a circle.</li>
-						<li>The blue shapes must only be visible inside of the circle that has black stroke.</li>
-					</ul>
-				</li>
-			</ul>
+      </p>
+      <ul>
+        <li>There is no red visible.</li>
+        <li>No shapes extend outside of the rects that have a thick black border.</li>
+        <li>For the left subtest:
+          <ul>
+            <li>There must be a large blue rect with a transparent smaller rect in it, and the intersection of two circles.</li>
+            <li>The borders of the clipregions are shown with black stroke.</li>
+            <li>The blue shapes must be visible only inside of these stroked regions.</li>
+           </ul>
+        </li>
+        <li>For the right subtest:
+          <ul>
+            <li>The test on the right must show part of the large blue rect shape with a transparent rect in it, and part of a circle.</li>
+            <li>The blue shapes must only be visible inside of the circle that has black stroke.</li>
+          </ul>
+        </li>
+      </ul>
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: masking-path-07-b.svg,v $</title>
@@ -74,9 +74,9 @@
         <path id="p1" d="M10 10l100 0 0 100 -100 0ZM50 50l40 0 0 40 -40 0Z" clip-rule="evenodd"/>
       </clipPath>
 
-      <!-- "If a valid 'clip-path' reference is placed on one of the children of a 'clipPath' element, 
-		      then the given child element is clipped by the referenced clipping path before OR'ing the 
-			  silhouette of the child element with the silhouettes of the other child elements." -->
+      <!-- "If a valid 'clip-path' reference is placed on one of the children of a 'clipPath' element,
+          then the given child element is clipped by the referenced clipping path before OR'ing the
+        silhouette of the child element with the silhouettes of the other child elements." -->
       <clipPath id="clipRects1">
         <rect x="50" y="30" width="25" height="100"/>
         <rect x="25" y="50" width="10" height="10" clip-path="url(#clipTwoCircles)"/>
@@ -105,7 +105,7 @@
     </defs>
 
     <rect x="20" y="70" width="210" height="210" fill="url(#pattern)" stroke="black" stroke-width="4"/>
-		<rect x="250" y="70" width="210" height="210" fill="url(#pattern)" stroke="black" stroke-width="4"/>
+    <rect x="250" y="70" width="210" height="210" fill="url(#pattern)" stroke="black" stroke-width="4"/>
 
     <text x="240" y="2em" text-anchor="middle">Test clip unions and intersections</text>
 
diff --git a/svg/import/masking-path-08-b-manual.svg b/svg/import/masking-path-08-b-manual.svg
index 17291ad..c32b6b0 100644
--- a/svg/import/masking-path-08-b-manual.svg
+++ b/svg/import/masking-path-08-b-manual.svg
@@ -28,10 +28,10 @@
       <p>
         The test has passed if:
       </p>
-			<ul>
-				<li>There is no red visible.</li>
-				<li>Each of the nine small rectangles are green.</li>
-			</ul>
+      <ul>
+        <li>There is no red visible.</li>
+        <li>Each of the nine small rectangles are green.</li>
+      </ul>
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: masking-path-08-b.svg,v $</title>
@@ -45,86 +45,85 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	
-		<defs>
-			<clipPath id="emptyclip">
-			</clipPath>
-			<clipPath id="hiddenclip">
-				<rect width="100" height="100" visibility="hidden"/>
-			</clipPath>
-			<clipPath id="displayclip">
-				<rect width="100" height="100" display="none"/>
-			</clipPath>
-			<clipPath id="opacityclip">
-				<rect width="100" height="100" opacity="0"/>
-			</clipPath>
-			<clipPath id="strokefillclip">
-				<rect width="100" height="100" stroke="none" fill="none"/>
-			</clipPath>
-			<clipPath id="strokewidthclip">
-				<rect x="40" y="40" width="20" height="20" stroke="black" stroke-width="80"/>
-			</clipPath>
-			<clipPath id="strokeopacityclip">
-				<rect x="40" y="40" width="20" height="20" stroke="black" stroke-opacity="0" stroke-width="80"/>
-			</clipPath>
-			<clipPath id="fillopacityclip">
-				<rect width="100" height="100" fill-opacity="0"/>
-			</clipPath>
-		</defs>
-		
-		<text x="240" y="50" text-anchor="middle">Establishing a new clipping path</text>
-		
-		<g transform="scale(0.5) translate(320 200)">
-			<rect width="100" height="100" fill="lime"/>
-			<rect width="100" height="100" fill="red" clip-path="url(#emptyclip)"/>
 
-			<g transform="translate(110 0)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" clip-path="url(#hiddenclip)"/>
-			</g>
-			
-			<g transform="translate(220 0)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" clip-path="url(#displayclip)"/>
-			</g>
+    <defs>
+      <clipPath id="emptyclip">
+      </clipPath>
+      <clipPath id="hiddenclip">
+        <rect width="100" height="100" visibility="hidden"/>
+      </clipPath>
+      <clipPath id="displayclip">
+        <rect width="100" height="100" display="none"/>
+      </clipPath>
+      <clipPath id="opacityclip">
+        <rect width="100" height="100" opacity="0"/>
+      </clipPath>
+      <clipPath id="strokefillclip">
+        <rect width="100" height="100" stroke="none" fill="none"/>
+      </clipPath>
+      <clipPath id="strokewidthclip">
+        <rect x="40" y="40" width="20" height="20" stroke="black" stroke-width="80"/>
+      </clipPath>
+      <clipPath id="strokeopacityclip">
+        <rect x="40" y="40" width="20" height="20" stroke="black" stroke-opacity="0" stroke-width="80"/>
+      </clipPath>
+      <clipPath id="fillopacityclip">
+        <rect width="100" height="100" fill-opacity="0"/>
+      </clipPath>
+    </defs>
 
-			<!-- This has an intentionally broken clip-path url -->
-			<g transform="translate(0 110)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
-				<rect width="100" height="100" fill="lime" clip-path="url(#unknown)"/>
-			</g>
-			
-			<g transform="translate(110 110)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
-				<rect width="100" height="100" fill="lime" clip-path="url(#opacityclip)"/>
-			</g>
-			
-			<g transform="translate(220 110)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
-				<rect width="100" height="100" fill="lime" clip-path="url(#strokefillclip)"/>
-			</g>
-			
-			<g transform="translate(0 220)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" clip-path="url(#strokewidthclip)"/>
-				<rect x="39" y="39" width="22" height="22" fill="lime"/>
-			</g>
-			
-			<g transform="translate(110 220)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" clip-path="url(#strokeopacityclip)"/>
-				<rect x="39" y="39" width="22" height="22" fill="lime"/>
-			</g>
-			
-			<g transform="translate(220 220)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
-				<rect width="100" height="100" fill="lime" clip-path="url(#fillopacityclip)"/>
-			</g>
-			
-			<rect width="320" height="320" fill="none" stroke="black"/>
-		</g>
-		
-		
+    <text x="240" y="50" text-anchor="middle">Establishing a new clipping path</text>
+
+    <g transform="scale(0.5) translate(320 200)">
+      <rect width="100" height="100" fill="lime"/>
+      <rect width="100" height="100" fill="red" clip-path="url(#emptyclip)"/>
+
+      <g transform="translate(110 0)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" clip-path="url(#hiddenclip)"/>
+      </g>
+
+      <g transform="translate(220 0)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" clip-path="url(#displayclip)"/>
+      </g>
+
+      <!-- This has an intentionally broken clip-path url -->
+      <g transform="translate(0 110)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
+        <rect width="100" height="100" fill="lime" clip-path="url(#unknown)"/>
+      </g>
+
+      <g transform="translate(110 110)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
+        <rect width="100" height="100" fill="lime" clip-path="url(#opacityclip)"/>
+      </g>
+
+      <g transform="translate(220 110)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
+        <rect width="100" height="100" fill="lime" clip-path="url(#strokefillclip)"/>
+      </g>
+
+      <g transform="translate(0 220)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" clip-path="url(#strokewidthclip)"/>
+        <rect x="39" y="39" width="22" height="22" fill="lime"/>
+      </g>
+
+      <g transform="translate(110 220)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" clip-path="url(#strokeopacityclip)"/>
+        <rect x="39" y="39" width="22" height="22" fill="lime"/>
+      </g>
+
+      <g transform="translate(220 220)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
+        <rect width="100" height="100" fill="lime" clip-path="url(#fillopacityclip)"/>
+      </g>
+
+      <rect width="320" height="320" fill="none" stroke="black"/>
+    </g>
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/masking-path-10-b-manual.svg b/svg/import/masking-path-10-b-manual.svg
index 251d34b..f359bf2 100644
--- a/svg/import/masking-path-10-b-manual.svg
+++ b/svg/import/masking-path-10-b-manual.svg
@@ -28,10 +28,10 @@
       <p>
         The test has passed if:
       </p>
-			<ul>
-				<li>There is no red visible.</li>
-				<li>Each of the nine small rectangles are green.</li>
-			</ul>
+      <ul>
+        <li>There is no red visible.</li>
+        <li>Each of the nine small rectangles are green.</li>
+      </ul>
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: masking-path-10-b.svg,v $</title>
@@ -45,84 +45,83 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	
-		<defs>
-			<mask id="emptymask"/>
-			<mask id="hiddenmask">
-				<rect width="100" height="100" visibility="hidden"/>
-			</mask>
-			<mask id="displaymask">
-				<rect width="100" height="100" display="none"/>
-			</mask>
-			<mask id="opacitymask">
-				<rect width="100" height="100" opacity="0"/>
-			</mask>
-			<mask id="strokefillmask">
-				<rect width="100" height="100" stroke="none" fill="none"/>
-			</mask>
-			<mask id="strokewidthmask">
-				<rect x="40" y="40" width="20" height="20" stroke="white" stroke-width="20"/>
-			</mask>
-			<mask id="strokeopacitymask">
-				<rect x="40" y="40" width="20" height="20" stroke="white" stroke-opacity="0" stroke-width="20"/>
-			</mask>
-			<mask id="fillopacitymask">
-				<rect width="100" height="100" fill-opacity="0"/>
-			</mask>
-		</defs>
-		
-		<text x="240" y="50" text-anchor="middle">Establishing a new masking path</text>
-		
-		<g transform="scale(0.5) translate(320 200)">
-			<rect width="100" height="100" fill="lime"/>
-			<rect width="100" height="100" fill="red" mask="url(#emptymask)"/>
 
-			<g transform="translate(110 0)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" mask="url(#hiddenmask)"/>
-			</g>
-			
-			<g transform="translate(220 0)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" mask="url(#displaymask)"/>
-			</g>
+    <defs>
+      <mask id="emptymask"/>
+      <mask id="hiddenmask">
+        <rect width="100" height="100" visibility="hidden"/>
+      </mask>
+      <mask id="displaymask">
+        <rect width="100" height="100" display="none"/>
+      </mask>
+      <mask id="opacitymask">
+        <rect width="100" height="100" opacity="0"/>
+      </mask>
+      <mask id="strokefillmask">
+        <rect width="100" height="100" stroke="none" fill="none"/>
+      </mask>
+      <mask id="strokewidthmask">
+        <rect x="40" y="40" width="20" height="20" stroke="white" stroke-width="20"/>
+      </mask>
+      <mask id="strokeopacitymask">
+        <rect x="40" y="40" width="20" height="20" stroke="white" stroke-opacity="0" stroke-width="20"/>
+      </mask>
+      <mask id="fillopacitymask">
+        <rect width="100" height="100" fill-opacity="0"/>
+      </mask>
+    </defs>
 
-			<!-- This has an intentionally broken mask url -->
-			<g transform="translate(0 110)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
-				<rect width="100" height="100" fill="lime" mask="url(#unknown)"/>
-			</g>
-			
-			<g transform="translate(110 110)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="lime"/>
-				<rect width="100" height="100" fill="red" mask="url(#opacitymask)"/>
-			</g>
-			
-			<g transform="translate(220 110)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="lime"/>
-				<rect width="100" height="100" fill="red" mask="url(#strokefillmask)"/>
-			</g>
-			
-			<g transform="translate(0 220)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" mask="url(#strokewidthmask)"/>
-				<rect x="29" y="29" width="42" height="42" fill="lime"/>
-			</g>
-			
-			<g transform="translate(110 220)">
-				<rect width="100" height="100" fill="lime"/>
-				<rect width="100" height="100" fill="red" mask="url(#strokeopacitymask)"/>
-			</g>
-			
-			<g transform="translate(220 220)">
-				<rect x="0.5" y="0.5" width="99" height="99" fill="lime"/>
-				<rect width="100" height="100" fill="red" mask="url(#fillopacitymask)"/>
-			</g>
-			
-			<rect width="320" height="320" fill="none" stroke="black"/>
-		</g>
-		
-		
+    <text x="240" y="50" text-anchor="middle">Establishing a new masking path</text>
+
+    <g transform="scale(0.5) translate(320 200)">
+      <rect width="100" height="100" fill="lime"/>
+      <rect width="100" height="100" fill="red" mask="url(#emptymask)"/>
+
+      <g transform="translate(110 0)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" mask="url(#hiddenmask)"/>
+      </g>
+
+      <g transform="translate(220 0)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" mask="url(#displaymask)"/>
+      </g>
+
+      <!-- This has an intentionally broken mask url -->
+      <g transform="translate(0 110)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
+        <rect width="100" height="100" fill="lime" mask="url(#unknown)"/>
+      </g>
+
+      <g transform="translate(110 110)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="lime"/>
+        <rect width="100" height="100" fill="red" mask="url(#opacitymask)"/>
+      </g>
+
+      <g transform="translate(220 110)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="lime"/>
+        <rect width="100" height="100" fill="red" mask="url(#strokefillmask)"/>
+      </g>
+
+      <g transform="translate(0 220)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" mask="url(#strokewidthmask)"/>
+        <rect x="29" y="29" width="42" height="42" fill="lime"/>
+      </g>
+
+      <g transform="translate(110 220)">
+        <rect width="100" height="100" fill="lime"/>
+        <rect width="100" height="100" fill="red" mask="url(#strokeopacitymask)"/>
+      </g>
+
+      <g transform="translate(220 220)">
+        <rect x="0.5" y="0.5" width="99" height="99" fill="lime"/>
+        <rect width="100" height="100" fill="red" mask="url(#fillopacitymask)"/>
+      </g>
+
+      <rect width="320" height="320" fill="none" stroke="black"/>
+    </g>
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/masking-path-11-b-manual.svg b/svg/import/masking-path-11-b-manual.svg
index 957f853..6a43712 100644
--- a/svg/import/masking-path-11-b-manual.svg
+++ b/svg/import/masking-path-11-b-manual.svg
@@ -48,7 +48,7 @@
         <text x="50%" y="55%" text-anchor="middle" font-size="48">ABC</text>
       </mask>
     </defs>
-    
+
     <image xlink:href="../images/DisplaceChecker.png" x="140" y="80" width="200" height="200"/>
     <rect width="100" height="100" x="190" y="130" fill="blue" mask="url(#maskedtext)"/>
 
diff --git a/svg/import/masking-path-12-f-manual.svg b/svg/import/masking-path-12-f-manual.svg
index 60c6894..90005b9 100644
--- a/svg/import/masking-path-12-f-manual.svg
+++ b/svg/import/masking-path-12-f-manual.svg
@@ -15,7 +15,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>Run the test. No interaction required.</p>
+      <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
@@ -39,10 +39,10 @@
             <rect x="100" y="100" width="50" height="50" display="inherit" />
         </clipPath>
     </g>
-    
+
     <rect id="pass" x="200" y="100" width="50" height="50" fill="green"/>
     <rect id="fail" x="200" y="100" width="50" height="50" fill="red"/>
-    
+
     <script type="text/javascript"><![CDATA[
         try
         {
@@ -75,4 +75,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/masking-path-13-f-manual.svg b/svg/import/masking-path-13-f-manual.svg
index 89b8542..82132e7 100644
--- a/svg/import/masking-path-13-f-manual.svg
+++ b/svg/import/masking-path-13-f-manual.svg
@@ -49,4 +49,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/masking-path-14-f-manual.svg b/svg/import/masking-path-14-f-manual.svg
index b0c39c0..bc7e1da 100644
--- a/svg/import/masking-path-14-f-manual.svg
+++ b/svg/import/masking-path-14-f-manual.svg
@@ -69,4 +69,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/metadata-example-01-t-manual.svg b/svg/import/metadata-example-01-t-manual.svg
index 69eda51..199fd98 100644
--- a/svg/import/metadata-example-01-t-manual.svg
+++ b/svg/import/metadata-example-01-t-manual.svg
@@ -16,10 +16,10 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/metadata.html#Example">
         <p>
           Check that metadata in a variety of namespaces, inside a metadata
-          element, does not affect rendering in any way. The file is not valid to 
+          element, does not affect rendering in any way. The file is not valid to
           the SVG 1.1 DTD, but is well formed.
         </p>
-        <p>The diagram on the table is, by the way, a visualization of the 
+        <p>The diagram on the table is, by the way, a visualization of the
         RDF metadata in the graphic.</p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -44,15 +44,15 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
   <metadata>
-    <rdf:RDF 
+    <rdf:RDF
       xmlns:prism="http://prismstandard.org/namespaces/1.0/basic/"
       xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
       xmlns:dc="http://purl.org/dc/elements/1.1/"
       xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
-      <!-- The empty value for the rdf:about attribute means it is describing  
+      <!-- The empty value for the rdf:about attribute means it is describing
             the file this is contained in. -->
          <rdf:Description rdf:about="">
-           <dc:description>Line drawing of woman in antique attire, 
+           <dc:description>Line drawing of woman in antique attire,
              which looks legal or perhaps mystical.</dc:description>
            <dc:format>image/svg+xml</dc:format>
            <dc:subject>Sibyll Trelawney</dc:subject>
diff --git a/svg/import/painting-control-01-f-manual.svg b/svg/import/painting-control-01-f-manual.svg
index 07c1261..332f705 100644
--- a/svg/import/painting-control-01-f-manual.svg
+++ b/svg/import/painting-control-01-f-manual.svg
@@ -16,15 +16,15 @@
         Elements are rendered when the 'display' attribute is set to any valid value other than 'none'.
       </p>
       <p>
-        For each valid 'display' value (except none), the test creates a 'rect' element with that 'display' value assigned. Under that 
-        element, a red 'rect' is placed at the exact same 'x', 'y' position with the same height and width. Test passes if the 'rect' 
+        For each valid 'display' value (except none), the test creates a 'rect' element with that 'display' value assigned. Under that
+        element, a red 'rect' is placed at the exact same 'x', 'y' position with the same height and width. Test passes if the 'rect'
         with 'display' covers the red 'rect'.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>
-	Run the test. No interaction required.
-	</p>
+  <p>
+  Run the test. No interaction required.
+  </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/painting-control-02-f-manual.svg b/svg/import/painting-control-02-f-manual.svg
index 8177e60..4391319 100644
--- a/svg/import/painting-control-02-f-manual.svg
+++ b/svg/import/painting-control-02-f-manual.svg
@@ -22,9 +22,9 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-	<p>
-	Run the test. No interaction required.
-	</p>
+  <p>
+  Run the test. No interaction required.
+  </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/painting-control-04-f-manual.svg b/svg/import/painting-control-04-f-manual.svg
index 95b5262..5952414 100644
--- a/svg/import/painting-control-04-f-manual.svg
+++ b/svg/import/painting-control-04-f-manual.svg
@@ -20,7 +20,7 @@
       <p>
         Run the test. No interaction required.
       </p>
-    </d:operatorScript> 
+    </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Test passes if there is a blue circle, a blue square, and no red on the page.
diff --git a/svg/import/painting-control-05-f-manual.svg b/svg/import/painting-control-05-f-manual.svg
index 48a984d..d9c40b9 100644
--- a/svg/import/painting-control-05-f-manual.svg
+++ b/svg/import/painting-control-05-f-manual.svg
@@ -39,7 +39,7 @@
         <rect id="displaynone_rect" width="100%" height="100%" display="none" />
         <rect id="rect" width="100%" height="100%" />
         <use id="displaynone_use_rect" xlink:href="#displaynone_rect"/>
-        
+
         <!-- an empty mask where the basic shape element has display=none -->
         <mask id="testMask1">
             <rect width="100%" height="100%" display="none" fill="white" />
@@ -77,7 +77,7 @@
             <rect width="0" height="0"/>
         </clipPath>
     </defs>
-    
+
     <g id="reference" fill="lime" stroke="black">
       <rect x="50" y="50" width="100" height="100"/>
       <rect x="150" y="50" width="100" height="100"/>
@@ -88,12 +88,12 @@
       <rect x="250" y="200" width="100" height="100"/>
       <rect x="350" y="200" width="100" height="100"/>
     </g>
-    
+
     <rect x="50" y="50" width="100" height="100" fill="red" mask="url(#testMask1)"/>
     <rect x="150" y="50" width="100" height="100" fill="red" mask="url(#testMask2)"/>
     <rect x="250" y="50" width="100" height="100" fill="red" mask="url(#testMask3)"/>
     <rect x="350" y="50" width="100" height="100" fill="red" mask="url(#testMask4)"/>
-    
+
     <rect x="50" y="200" width="100" height="100" fill="red" clip-path="url(#testClip1)"/>
     <rect x="150" y="200" width="100" height="100" fill="red" clip-path="url(#testClip2)"/>
     <rect x="250" y="200" width="100" height="100" fill="red" clip-path="url(#testClip3)"/>
diff --git a/svg/import/painting-control-06-f-manual.svg b/svg/import/painting-control-06-f-manual.svg
index 58e4efd..66b20af 100644
--- a/svg/import/painting-control-06-f-manual.svg
+++ b/svg/import/painting-control-06-f-manual.svg
@@ -6,7 +6,7 @@
   <!--=  All Rights Reserved.                                              =-->
   <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->
   <!--======================================================================-->
-  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/" template-version="1.4" 
+  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/" template-version="1.4"
     reviewer="ED" author="Microsoft" status="accepted" version="$Revision: 1.4 $" testname="$RCSfile: painting-control-06-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/painting.html#VisibilityControl">
       <p>
diff --git a/svg/import/painting-fill-01-t-manual.svg b/svg/import/painting-fill-01-t-manual.svg
index bb66cb0..edf6d2e 100644
--- a/svg/import/painting-fill-01-t-manual.svg
+++ b/svg/import/painting-fill-01-t-manual.svg
@@ -16,7 +16,7 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/painting.html#FillProperties">
         <p>
           Verify the basic capability to handle the fill properties fill:none,
-          and fill with a color			(fill:green)
+          and fill with a color      (fill:green)
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/painting-marker-01-f-manual.svg b/svg/import/painting-marker-01-f-manual.svg
index b48c390..bedf37f 100644
--- a/svg/import/painting-marker-01-f-manual.svg
+++ b/svg/import/painting-marker-01-f-manual.svg
@@ -15,7 +15,7 @@
     version="$Revision: 1.7 $" testname="$RCSfile: painting-marker-01-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/painting.html#Markers">
         <p>
-          Tests the basic support for markers. 
+          Tests the basic support for markers.
         </p>
         <p>
           The top test examines the basic support for the marker element and style. The markers are purple rectangles.
@@ -43,9 +43,9 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        For the three tests, there should be two identical paths with markers drawn. 
-        The path on the left is rendered using the marker elements. The path on the 
-        right is rendered using the equivalent SVG, showing what the marked path should 
+        For the three tests, there should be two identical paths with markers drawn.
+        The path on the left is rendered using the marker elements. The path on the
+        right is rendered using the equivalent SVG, showing what the marked path should
         look like.
       </p>
       <p>
diff --git a/svg/import/painting-marker-02-f-manual.svg b/svg/import/painting-marker-02-f-manual.svg
index bab08fc..ee37ddc 100644
--- a/svg/import/painting-marker-02-f-manual.svg
+++ b/svg/import/painting-marker-02-f-manual.svg
@@ -16,7 +16,7 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/painting.html#Markers">
         <p>
           Tests the rendering of markers, specifically property inheritance. For the four tests, there should
-          be two identical paths with markers drawn. 
+          be two identical paths with markers drawn.
         </p>
         <p>
           The top two tests examine the rendering of markers when the marker and the path
@@ -47,9 +47,9 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The path on the left is rendered using the marker elements. 
-        The path on the right is rendered using the equivalent SVG, 
-        showing what the marked path should look like.  These should be 
+        The path on the left is rendered using the marker elements.
+        The path on the right is rendered using the equivalent SVG,
+        showing what the marked path should look like.  These should be
         identical and match the image to the right.
       </p>
     </d:passCriteria>
diff --git a/svg/import/painting-marker-06-f-manual.svg b/svg/import/painting-marker-06-f-manual.svg
index c1929f8..3a4ebc6 100644
--- a/svg/import/painting-marker-06-f-manual.svg
+++ b/svg/import/painting-marker-06-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -65,95 +65,95 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <defs>
-		<!-- ===================================================================== -->
-		<!-- Define a few simple marker elements                                   -->
-		<!-- ===================================================================== -->
+    <!-- ===================================================================== -->
+    <!-- Define a few simple marker elements                                   -->
+    <!-- ===================================================================== -->
     <marker id="marker1" viewBox="20 20 10 10" markerWidth="2" markerHeight="2" refX="25" refY="25" markerUnits="strokeWidth">
       <rect x="20" y="20" width="10" height="10" fill="purple" stroke="none"/>
     </marker>
     <marker id="marker2" viewBox="-5 -5 10 10" markerWidth="2" markerHeight="2" markerUnits="strokeWidth" orient="auto">
       <path d="M 0 -5 L 5 5 L -5 5 Z" fill="blue" stroke="none"/>
     </marker>
-  	<marker id="markerStart" viewBox="0 0 10 10" markerWidth="2" markerHeight="2" refX="5" refY="5" markerUnits="strokeWidth">
-			<rect width="10" height="10" fill="purple" stroke="none"/>
-		</marker>
-		<marker id="markerMiddle" viewBox="0 0 10 10" markerWidth="2" markerHeight="2" refX="5" refY="5" markerUnits="strokeWidth">
-			<circle cx="5" cy="5" r="5" fill="green" stroke="none"/>
-		</marker>
-		<marker id="markerEnd" viewBox="0 0 10 10" markerWidth="2" markerHeight="2" refX="5" refY="5" markerUnits="strokeWidth">
-			<path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
-		</marker>
-	</defs>
-		<!-- ===================================================================== -->
-		<!-- Basic Marker Test                                                     -->
-		<!-- ===================================================================== -->
-		<text x="170" y="30" font-size="14">Basic Markers</text>
-		<path fill="none" stroke="black" stroke-width="8" marker-start="url(#marker1)" marker-mid="url(#marker1)" marker-end="url(#marker1)" d="M 130 40 L 180 40 L 180 90"/>
-		<!-- ===================================================================== -->
-		<!-- Generate the equivalent SVG                                           -->
-		<!-- ===================================================================== -->
-		<g transform="translate(120,0)">
-			<path fill="none" stroke="black" stroke-width="8" d="M 130 40 L 180 40 L 180 90"/>
-			<g transform="translate(130,40) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<rect width="10" height="10" fill="purple" stroke="none"/>
-			</g>
-			<g transform="translate(180,40) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<rect width="10" height="10" fill="purple" stroke="none"/>
-			</g>
-			<g transform="translate(180,90) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<rect width="10" height="10" fill="purple" stroke="none"/>
-			</g>
-		</g>
-		<!-- ===================================================================== -->
-		<!-- Start, Middle and End Marker Test                                     -->
-		<!-- ===================================================================== -->
-		<text x="145" y="125" font-size="14">Start, Middle and End</text>
-		<path fill="none" stroke="black" stroke-width="8" marker-start="url(#markerStart)" marker-mid="url(#markerMiddle)" marker-end="url(#markerEnd)" d="M 130 135 L 180 135 L 180 185"/>
-		<!-- ===================================================================== -->
-		<!-- Generate the equivalent SVG                                           -->
-		<!-- ===================================================================== -->
-		<g transform="translate(120,0)">
-			<path fill="none" stroke="black" stroke-width="8" d="M 130 135 L 180 135 L 180 185"/>
-			<g transform="translate(130,135) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<rect width="10" height="10" fill="purple" stroke="none"/>
-			</g>
-			<g transform="translate(180,135) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<circle cx="5" cy="5" r="5" fill="green" stroke="none"/>
-			</g>
-			<g transform="translate(180,185) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
-			</g>
-		</g>
-		<!-- ===================================================================== -->
-		<!-- Auto Orientation Marker Test                                          -->
-		<!-- ===================================================================== -->
-		<text x="145" y="220" font-size="14">Automatic Orientation</text>
-		<path fill="none" stroke="black" stroke-width="8" marker-start="url(#marker2)" marker-mid="url(#marker2)" marker-end="url(#marker2)" d="M 130 230 L 180 230 L 180 280"/>
-		<!-- ===================================================================== -->
-		<!-- Generate the equivalent SVG                                           -->
-		<!-- ===================================================================== -->
-		<g transform="translate(120,0)">
-			<path fill="none" stroke="black" stroke-width="8" d="M 130 230 L 180 230 L 180 280"/>
-			<g transform="translate(130,230) rotate(0) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
-			</g>
-			<g transform="translate(180,230) rotate(45) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
-			</g>
-			<g transform="translate(180,280) rotate(90) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
-				<path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
-			</g>
-		</g>    
+    <marker id="markerStart" viewBox="0 0 10 10" markerWidth="2" markerHeight="2" refX="5" refY="5" markerUnits="strokeWidth">
+      <rect width="10" height="10" fill="purple" stroke="none"/>
+    </marker>
+    <marker id="markerMiddle" viewBox="0 0 10 10" markerWidth="2" markerHeight="2" refX="5" refY="5" markerUnits="strokeWidth">
+      <circle cx="5" cy="5" r="5" fill="green" stroke="none"/>
+    </marker>
+    <marker id="markerEnd" viewBox="0 0 10 10" markerWidth="2" markerHeight="2" refX="5" refY="5" markerUnits="strokeWidth">
+      <path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
+    </marker>
+  </defs>
+    <!-- ===================================================================== -->
+    <!-- Basic Marker Test                                                     -->
+    <!-- ===================================================================== -->
+    <text x="170" y="30" font-size="14">Basic Markers</text>
+    <path fill="none" stroke="black" stroke-width="8" marker-start="url(#marker1)" marker-mid="url(#marker1)" marker-end="url(#marker1)" d="M 130 40 L 180 40 L 180 90"/>
+    <!-- ===================================================================== -->
+    <!-- Generate the equivalent SVG                                           -->
+    <!-- ===================================================================== -->
+    <g transform="translate(120,0)">
+      <path fill="none" stroke="black" stroke-width="8" d="M 130 40 L 180 40 L 180 90"/>
+      <g transform="translate(130,40) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <rect width="10" height="10" fill="purple" stroke="none"/>
+      </g>
+      <g transform="translate(180,40) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <rect width="10" height="10" fill="purple" stroke="none"/>
+      </g>
+      <g transform="translate(180,90) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <rect width="10" height="10" fill="purple" stroke="none"/>
+      </g>
+    </g>
+    <!-- ===================================================================== -->
+    <!-- Start, Middle and End Marker Test                                     -->
+    <!-- ===================================================================== -->
+    <text x="145" y="125" font-size="14">Start, Middle and End</text>
+    <path fill="none" stroke="black" stroke-width="8" marker-start="url(#markerStart)" marker-mid="url(#markerMiddle)" marker-end="url(#markerEnd)" d="M 130 135 L 180 135 L 180 185"/>
+    <!-- ===================================================================== -->
+    <!-- Generate the equivalent SVG                                           -->
+    <!-- ===================================================================== -->
+    <g transform="translate(120,0)">
+      <path fill="none" stroke="black" stroke-width="8" d="M 130 135 L 180 135 L 180 185"/>
+      <g transform="translate(130,135) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <rect width="10" height="10" fill="purple" stroke="none"/>
+      </g>
+      <g transform="translate(180,135) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <circle cx="5" cy="5" r="5" fill="green" stroke="none"/>
+      </g>
+      <g transform="translate(180,185) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
+      </g>
+    </g>
+    <!-- ===================================================================== -->
+    <!-- Auto Orientation Marker Test                                          -->
+    <!-- ===================================================================== -->
+    <text x="145" y="220" font-size="14">Automatic Orientation</text>
+    <path fill="none" stroke="black" stroke-width="8" marker-start="url(#marker2)" marker-mid="url(#marker2)" marker-end="url(#marker2)" d="M 130 230 L 180 230 L 180 280"/>
+    <!-- ===================================================================== -->
+    <!-- Generate the equivalent SVG                                           -->
+    <!-- ===================================================================== -->
+    <g transform="translate(120,0)">
+      <path fill="none" stroke="black" stroke-width="8" d="M 130 230 L 180 230 L 180 280"/>
+      <g transform="translate(130,230) rotate(0) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
+      </g>
+      <g transform="translate(180,230) rotate(45) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
+      </g>
+      <g transform="translate(180,280) rotate(90) scale(8) scale(0.2, 0.2) translate(-5, -5)" fill="none" stroke="black" stroke-width="8">
+        <path d="M 5 0 L 10 10 L 0 10 Z" fill="blue" stroke="none"/>
+      </g>
+    </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.9 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved -->
   <!--<g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/painting-marker-07-f-manual.svg b/svg/import/painting-marker-07-f-manual.svg
index fc08673..19aaa47 100644
--- a/svg/import/painting-marker-07-f-manual.svg
+++ b/svg/import/painting-marker-07-f-manual.svg
@@ -63,4 +63,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/painting-stroke-08-t-manual.svg b/svg/import/painting-stroke-08-t-manual.svg
index a16b296..28d9f28 100644
--- a/svg/import/painting-stroke-08-t-manual.svg
+++ b/svg/import/painting-stroke-08-t-manual.svg
@@ -69,7 +69,7 @@
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.6 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/paths-data-02-t-manual.svg b/svg/import/paths-data-02-t-manual.svg
index cad5865..a7d1f94 100644
--- a/svg/import/paths-data-02-t-manual.svg
+++ b/svg/import/paths-data-02-t-manual.svg
@@ -26,7 +26,7 @@
           both, using simple style properties and colors.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-03-f-manual.svg b/svg/import/paths-data-03-f-manual.svg
index 3f087fd..7a37389 100644
--- a/svg/import/paths-data-03-f-manual.svg
+++ b/svg/import/paths-data-03-f-manual.svg
@@ -26,7 +26,7 @@
           both, using simple style properties and colors.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-04-t-manual.svg b/svg/import/paths-data-04-t-manual.svg
index 63fef43..e0f395f 100644
--- a/svg/import/paths-data-04-t-manual.svg
+++ b/svg/import/paths-data-04-t-manual.svg
@@ -23,7 +23,7 @@
           the inner triangle is hollow.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
@@ -47,7 +47,7 @@
     <!-- First Group of Triangles using M, L & Z commands ===================== -->
     <!-- ====================================================================== -->
     <g transform="scale(1.8)">
-      <!--		<text font-size="12"  x="160" y="14">Lines drawn with commands:</text> -->
+      <!--    <text font-size="12"  x="160" y="14">Lines drawn with commands:</text> -->
       <text font-size="12" x="88" y="30">M, L, L, L, Z,</text>
       <text font-size="12" x="98" y="46">subpath</text>
       <text font-size="12" x="88" y="61">M, L, L, L, Z</text>
diff --git a/svg/import/paths-data-05-t-manual.svg b/svg/import/paths-data-05-t-manual.svg
index cea68ca..1351179 100644
--- a/svg/import/paths-data-05-t-manual.svg
+++ b/svg/import/paths-data-05-t-manual.svg
@@ -23,7 +23,7 @@
           the inner triangle is hollow.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-06-t-manual.svg b/svg/import/paths-data-06-t-manual.svg
index 5e0fa31..07a8f44 100644
--- a/svg/import/paths-data-06-t-manual.svg
+++ b/svg/import/paths-data-06-t-manual.svg
@@ -22,7 +22,7 @@
           one filled.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-08-t-manual.svg b/svg/import/paths-data-08-t-manual.svg
index 5b2af84..632a183 100644
--- a/svg/import/paths-data-08-t-manual.svg
+++ b/svg/import/paths-data-08-t-manual.svg
@@ -24,7 +24,7 @@
           the inner triangle is hollow.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-09-t-manual.svg b/svg/import/paths-data-09-t-manual.svg
index d2908c2..c65d584 100644
--- a/svg/import/paths-data-09-t-manual.svg
+++ b/svg/import/paths-data-09-t-manual.svg
@@ -24,7 +24,7 @@
           the inner triangle is hollow.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-10-t-manual.svg b/svg/import/paths-data-10-t-manual.svg
index ebfcda8..2f3a7f3 100644
--- a/svg/import/paths-data-10-t-manual.svg
+++ b/svg/import/paths-data-10-t-manual.svg
@@ -23,7 +23,7 @@
           same.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-13-t-manual.svg b/svg/import/paths-data-13-t-manual.svg
index 8525043..487dae7 100644
--- a/svg/import/paths-data-13-t-manual.svg
+++ b/svg/import/paths-data-13-t-manual.svg
@@ -15,10 +15,10 @@
     version="$Revision: 1.5 $" testname="$RCSfile: paths-data-13-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/paths.html#PathData">
         <p>
-          Test multiple coordinates for V and H. 
+          Test multiple coordinates for V and H.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
       Run the test. No interaction required.
       </p>
diff --git a/svg/import/paths-data-17-f-manual.svg b/svg/import/paths-data-17-f-manual.svg
index d6801a8..2d18e56 100644
--- a/svg/import/paths-data-17-f-manual.svg
+++ b/svg/import/paths-data-17-f-manual.svg
@@ -29,7 +29,7 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test is passed if two black-stroked, unfilled squares are visible and
-	there is no red visible on the page.
+  there is no red visible on the page.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -56,7 +56,7 @@
       fill="black">$Revision: 1.4 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/paths-data-18-f-manual.svg b/svg/import/paths-data-18-f-manual.svg
index e50a850..097473d 100644
--- a/svg/import/paths-data-18-f-manual.svg
+++ b/svg/import/paths-data-18-f-manual.svg
@@ -59,7 +59,7 @@
       <path d="M 20 40 H 40" stroke-width="2" stroke="red" />
       <path d="M 20 40
                  H 40" stroke-width="4" stroke="black" />
-  
+
       <path d="M 20 60 H 40" stroke-width="2" stroke="red" />
       <path d="
                   M
@@ -68,19 +68,19 @@
                   H
                   40
                   " stroke-width="4" stroke="black" />
-  
+
       <path d="M 20 80 H40" stroke-width="2" stroke="red" />
       <path d="M       20,80          H    40" stroke-width="4" stroke="black" />
-  
+
       <path d="M 20 100 H 40#90" stroke-width="2" stroke="red" />
       <path d="M 20 100 H 40" stroke-width="4" stroke="black" />
-  
+
       <path d="M 20 120 H 40.5 0.6" stroke-width="2" stroke="red" />
       <path d="M 20 120 H 40.5.6" stroke-width="4" stroke="black" />
-  
+
       <path d="M 20 140 h 10 -20" stroke-width="2" stroke="red" />
       <path d="M 20 140 h 10-20" stroke-width="4" stroke="black" />
-  
+
       <path d="M 20 160 H 40" stroke-width="2" stroke="red" />
       <path d="M 20 160 H 40#90" stroke-width="4" stroke="black" />
     </g>
@@ -91,7 +91,7 @@
       fill="black">$Revision: 1.6 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/paths-data-19-f-manual.svg b/svg/import/paths-data-19-f-manual.svg
index 5d6d49a..ef0c116 100644
--- a/svg/import/paths-data-19-f-manual.svg
+++ b/svg/import/paths-data-19-f-manual.svg
@@ -98,7 +98,7 @@
       fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/paths-data-20-f-manual.svg b/svg/import/paths-data-20-f-manual.svg
index b0827e5..ed643bc 100644
--- a/svg/import/paths-data-20-f-manual.svg
+++ b/svg/import/paths-data-20-f-manual.svg
@@ -41,26 +41,26 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-    <!-- no commawsp between arc flags --> 
+    <!-- no commawsp between arc flags -->
     <path d="M120,120 h25 a25,25 0 1,0 -25,25 z" fill="red" stroke="lime"/>
     <path d="M120,120 h25 a25,25 0 10 -25,25z" fill="lime"/>
 
     <!-- no commawsp between arc flags and no commawsp after arc flags -->
     <path d="M200,120 h-25 a25,25 0 1,1 25,25 z" fill="red" stroke="lime"/>
     <path d="M200,120 h-25 a25,25 0 1125,25 z" fill="lime"/>
-  
+
     <!-- out of range large-arc-flag value -->
     <path d="M280,120 h25 a25,25 0 1,0 -25,25 z" fill="lime" stroke="lime"/>
     <path d="M280,120 h25 a25,25 0 6 0 -25,25 z" fill="red"/>
-  
+
     <!-- negative sweep-flag value -->
     <path d="M360,120 h-25 a25,25 0 1,1 25,25 z" fill="lime" stroke="lime"/>
     <path d="M360,120 h-25 a25,25 0 1 -1 25,25 z" fill="red"/>
-   
+
     <!-- no commawsp between sweep-flag and following coordinate-pair -->
     <path d="M120,200 h25 a25,25 0 1,1 -25,-25 z" fill="red" stroke="lime"/>
     <path d="M120,200 h25 a25,25 0 1 1-25,-25 z" fill="lime"/>
-  
+
     <!-- no commawsp before arc flags -->
     <path d="M200,200 h-25 a25,25 0 1,0 25,-25 z" fill="lime" stroke="lime"/>
     <path d="M200,200 h-25 a25,2501 025,-25 z" fill="red"/>
diff --git a/svg/import/pservers-grad-01-b-manual.svg b/svg/import/pservers-grad-01-b-manual.svg
index 77b7565..6b3903c 100644
--- a/svg/import/pservers-grad-01-b-manual.svg
+++ b/svg/import/pservers-grad-01-b-manual.svg
@@ -23,14 +23,14 @@
         reference to the first gradient, without modifying any attribute.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test is passed if there are two rectangles, both with a blue to lime gradient. 
+        The test is passed if there are two rectangles, both with a blue to lime gradient.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/pservers-grad-02-b-manual.svg b/svg/import/pservers-grad-02-b-manual.svg
index bbdfb9b..7268f2e 100644
--- a/svg/import/pservers-grad-02-b-manual.svg
+++ b/svg/import/pservers-grad-02-b-manual.svg
@@ -29,7 +29,7 @@
         stops from the gradient to the left, with a different geometry. The radial gradient appears circular.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-03-b-manual.svg b/svg/import/pservers-grad-03-b-manual.svg
index f3dfc25..aa33de2 100644
--- a/svg/import/pservers-grad-03-b-manual.svg
+++ b/svg/import/pservers-grad-03-b-manual.svg
@@ -29,7 +29,7 @@
         edges of the respective rectangles is identical.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
       </p>
diff --git a/svg/import/pservers-grad-04-b-manual.svg b/svg/import/pservers-grad-04-b-manual.svg
index 876a07a..02ebbf6 100644
--- a/svg/import/pservers-grad-04-b-manual.svg
+++ b/svg/import/pservers-grad-04-b-manual.svg
@@ -32,9 +32,9 @@
         to outermost in the following order: black, yellow, orange, blue, white, green.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">           
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-				Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-05-b-manual.svg b/svg/import/pservers-grad-05-b-manual.svg
index d08e1f7..c607322 100644
--- a/svg/import/pservers-grad-05-b-manual.svg
+++ b/svg/import/pservers-grad-05-b-manual.svg
@@ -42,7 +42,7 @@
         Also a stop opacity is given to the colors in the following order: 1, 0.2, 0.5, 0, 0.8, 1
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-06-b-manual.svg b/svg/import/pservers-grad-06-b-manual.svg
index 374b1e3..49b3287 100644
--- a/svg/import/pservers-grad-06-b-manual.svg
+++ b/svg/import/pservers-grad-06-b-manual.svg
@@ -38,7 +38,7 @@
         consists of a 2 by 2 array of colored rectangles.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-07-b-manual.svg b/svg/import/pservers-grad-07-b-manual.svg
index 9e9b32d..87bae00 100644
--- a/svg/import/pservers-grad-07-b-manual.svg
+++ b/svg/import/pservers-grad-07-b-manual.svg
@@ -34,7 +34,7 @@
         applied.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-10-b-manual.svg b/svg/import/pservers-grad-10-b-manual.svg
index 52d893e..6970ab1 100644
--- a/svg/import/pservers-grad-10-b-manual.svg
+++ b/svg/import/pservers-grad-10-b-manual.svg
@@ -18,9 +18,9 @@
         Test that the viewer can handle the spreadMethod attribute on linear gradients.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-				Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-11-b-manual.svg b/svg/import/pservers-grad-11-b-manual.svg
index dc5e212..79c73a5 100644
--- a/svg/import/pservers-grad-11-b-manual.svg
+++ b/svg/import/pservers-grad-11-b-manual.svg
@@ -42,7 +42,7 @@
         (out) linear gradient is applied.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-12-b-manual.svg b/svg/import/pservers-grad-12-b-manual.svg
index d00f73a..6572b66 100644
--- a/svg/import/pservers-grad-12-b-manual.svg
+++ b/svg/import/pservers-grad-12-b-manual.svg
@@ -38,7 +38,7 @@
         The gradient should appear in the center of the rectangle as a radial gradient from yellow (center) to blue (edge).
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-14-b-manual.svg b/svg/import/pservers-grad-14-b-manual.svg
index 63045cf..e3eda15 100644
--- a/svg/import/pservers-grad-14-b-manual.svg
+++ b/svg/import/pservers-grad-14-b-manual.svg
@@ -94,7 +94,6 @@
     <rect x="10" y="270" width="225" height="40" fill="url(#lgSpreadDefault)" stroke="none"/>
     <text font-size="16" x="10" y="265">spreadMethod="default"</text>
 
-
     <rect x="245" y="060" width="225" height="40" fill="url(#rgSpreadPad)" stroke="none"/>
     <text font-size="16" x="245" y="55">spreadMethod="pad"</text>
 
diff --git a/svg/import/pservers-grad-15-b-manual.svg b/svg/import/pservers-grad-15-b-manual.svg
index 84ecd8f..d83cac3 100644
--- a/svg/import/pservers-grad-15-b-manual.svg
+++ b/svg/import/pservers-grad-15-b-manual.svg
@@ -20,7 +20,7 @@
           and testing defaults for radial grad cx,cy,r = 50%, fx,fy = cx,cy.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
       </p>
diff --git a/svg/import/pservers-grad-18-b-manual.svg b/svg/import/pservers-grad-18-b-manual.svg
index 3bb4411..6c042ac 100644
--- a/svg/import/pservers-grad-18-b-manual.svg
+++ b/svg/import/pservers-grad-18-b-manual.svg
@@ -19,27 +19,27 @@
           Several gradients are defined, with two stops:
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         For the top-left rectangle's gradient:
         The first stop defines a fully-opaque green color.
-        The second stop explicitly inherits (i.e. using the 'inherit' keyword) its stop-color.        
+        The second stop explicitly inherits (i.e. using the 'inherit' keyword) its stop-color.
       </p>
       <p>
         For the top-right rectangle's gradient:
         The first stop defines a fully-opaque green color.
-        The second stop defines a green stop-color but explicitly inherits (i.e. using the 'inherit' keyword) the stop-opacity.        
+        The second stop defines a green stop-color but explicitly inherits (i.e. using the 'inherit' keyword) the stop-opacity.
       </p>
       <p>
         For the bottom-left rectangle's gradient:
         The first stop defines a fully-opaque green color.
         The second stop does not specify the stop-color and the stop-opacity.
-        Since both properties are not inherited, the initial value should be used.        
+        Since both properties are not inherited, the initial value should be used.
       </p>
       <p>
         For the bottom-right rectangle's gradient:
         The first stop defines a fully-opaque green color.
-        The second stop specifies the stop-color using the 'currentColor' keyword.        
+        The second stop specifies the stop-color using the 'currentColor' keyword.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/pservers-grad-20-b-manual.svg b/svg/import/pservers-grad-20-b-manual.svg
index e1812a1..53e690b 100644
--- a/svg/import/pservers-grad-20-b-manual.svg
+++ b/svg/import/pservers-grad-20-b-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -57,7 +57,7 @@
         <stop offset="1" stop-color="white" />
       </linearGradient>
     </defs>
-    
+
     <!-- sub-test 1: stroke with solid color fallback -->
     <g transform="translate(50, 100) scale(0.3)">
       <path d="M0 180L480 180" stroke="url(#grad) #0f0" stroke-width="20"/>
@@ -67,10 +67,10 @@
       <rect x="0" y="-60" width="480" height="480" fill="none" stroke="black"/>
     </g>
     <text x="120" y="250" text-anchor="middle">With fallback</text>
-    
+
     <!-- sub-test 2: stroke without a fallback -->
     <g transform="translate(280,100) scale(0.3)">
-      <path d="M0 180L480 180"	stroke="url(#grad)" stroke-width="20"/>
+      <path d="M0 180L480 180"  stroke="url(#grad)" stroke-width="20"/>
       <path d="M0 180L480 180" stroke="url(#grad)" stroke-width="20" transform="rotate(45 240 180)"/>
       <path d="M0 180L480 180" stroke="url(#grad)" stroke-width="20" transform="rotate(90 240 180)"/>
       <path d="M410 10L70 350" stroke="url(#grad)" stroke-width="20"/>
@@ -79,14 +79,14 @@
     <text x="350" y="250" text-anchor="middle">Without fallback</text>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.7 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved -->
   <!--<g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/pservers-grad-21-b-manual.svg b/svg/import/pservers-grad-21-b-manual.svg
index 8024d52..df4bbd6 100644
--- a/svg/import/pservers-grad-21-b-manual.svg
+++ b/svg/import/pservers-grad-21-b-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -14,7 +14,7 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/pservers.html#Gradients">
       <p>
         Test the inheritance of radial gradient attributes. The test has six ellipses with blue stroke, each filled
-        with two gradients. 
+        with two gradients.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -59,7 +59,7 @@
         <stop offset="1" stop-opacity="0"/>
       </radialGradient>
       <radialGradient id="grad6" xlink:href="#grad5" cx="0%"/>
-      
+
       <radialGradient id="grad1-ref" cx="100%" r="20%">
         <stop offset="0" stop-color="black" />
         <stop offset="1" stop-opacity="0"/>
@@ -85,7 +85,7 @@
         <stop offset="1" stop-opacity="0"/>
       </radialGradient>
     </defs>
-    
+
     <g transform="translate(100 60)">
       <!-- first line of tests -->
       <rect fill="url(#grad1)" x="-60" width="120" height="90"/>
@@ -105,7 +105,7 @@
         <rect fill="url(#grad4)" x="60" width="120" height="90"/>
         <ellipse cx="60" cy="45" rx="24" ry="18" fill="none" stroke="blue"/>
       </g>
-      
+
       <!-- reference for second line -->
       <g transform="translate(100 50)">
         <rect fill="url(#grad3-ref)" x="-60" width="120" height="90"/>
@@ -119,28 +119,28 @@
         <rect fill="url(#grad6)" x="60" width="120" height="90"/>
         <ellipse cx="60" cy="45" rx="24" ry="18" fill="none" stroke="blue"/>
       </g>
-      
+
       <!-- reference for third line -->
       <g transform="translate(100 100)">
         <rect fill="url(#grad5-ref)" x="-60" width="120" height="90"/>
         <rect fill="url(#grad6-ref)" x="60" width="120" height="90"/>
         <ellipse cx="60" cy="45" rx="24" ry="18" fill="none" stroke="blue"/>
       </g>
-      
+
       <rect x="120" y="20" width="80" height="200" fill="none" stroke="green"/>
-      <text y="200" x="160" text-anchor="middle" font-size="12">Reference</text> 
+      <text y="200" x="160" text-anchor="middle" font-size="12">Reference</text>
     </g>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.4 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/pservers-grad-23-f-manual.svg b/svg/import/pservers-grad-23-f-manual.svg
index 3029214..ee336e4 100644
--- a/svg/import/pservers-grad-23-f-manual.svg
+++ b/svg/import/pservers-grad-23-f-manual.svg
@@ -17,8 +17,8 @@
       </p>
       <p>
         <!--[[
-        Specify a 'stop-color' attribute equal to 'red' on a 'g' element ancestor of a 'rect' element. Specify a 'linearGradient' with a single 
-        'stop' that has a 'stop-color' of 'inherit'. Reference the 'linearGradient' from the 'rect' element and verify that the 'rect' is not red. 
+        Specify a 'stop-color' attribute equal to 'red' on a 'g' element ancestor of a 'rect' element. Specify a 'linearGradient' with a single
+        'stop' that has a 'stop-color' of 'inherit'. Reference the 'linearGradient' from the 'rect' element and verify that the 'rect' is not red.
         Repeat the same process with a sibling 'rect' element referencing a 'radialGradient' element.
         ]]-->
       </p>
@@ -29,7 +29,7 @@
       </p>
       <p>
         <!--[[
-        
+
         ]]-->
       </p>
     </d:operatorScript>
@@ -64,7 +64,7 @@
         <rect x="30" y="30" width="100" height="100" fill="url(#testLinear)" stop-color="inherit" />
         <rect x="150" y="30" width="100" height="100" fill="url(#testRadial)" stop-color="inherit" />
     </g>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
@@ -77,4 +77,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/pservers-pattern-03-f-manual.svg b/svg/import/pservers-pattern-03-f-manual.svg
index dbf9f47..a363e5e 100644
--- a/svg/import/pservers-pattern-03-f-manual.svg
+++ b/svg/import/pservers-pattern-03-f-manual.svg
@@ -72,7 +72,7 @@
       <rect y="100" width="100" height="100" fill="url(#invisible5) lime"/>
       <rect x="100" y="100" width="100" height="100" fill="url(#invisible6) lime"/>
       <rect x="200" y="100" width="100" height="100" fill="url(#invisible7) lime"/>
-      
+
       <!-- note that this is on purpose testing a broken uri -->
       <rect x="300" y="100" width="100" height="100" fill="url(#invisible8) lime"/>
     </g>
diff --git a/svg/import/pservers-pattern-08-f-manual.svg b/svg/import/pservers-pattern-08-f-manual.svg
index e15cb84..74702cd 100644
--- a/svg/import/pservers-pattern-08-f-manual.svg
+++ b/svg/import/pservers-pattern-08-f-manual.svg
@@ -14,7 +14,7 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/pservers.html#Patterns">
       <p>
         Test that an invalid xlink:href on a 'pattern' element has no effect on the pattern.
-        This test specifies only 'width' and 'height' on the pattern that is tested in order to catch 
+        This test specifies only 'width' and 'height' on the pattern that is tested in order to catch
         incorrectly overridden values from a non-existant pattern. The result is tested
         with a reference pattern using slightly different syntax.
       </p>
diff --git a/svg/import/pservers-pattern-09-f-manual.svg b/svg/import/pservers-pattern-09-f-manual.svg
index 4d0ec1a..f98cdc2 100644
--- a/svg/import/pservers-pattern-09-f-manual.svg
+++ b/svg/import/pservers-pattern-09-f-manual.svg
@@ -15,7 +15,7 @@
       <p>
         Test that an invalid xlink:href on a 'pattern' element has no effect on the pattern, and that the
         pattern isn't rendered since the default 'width' and 'height' is 0.
-        A subtest that explicitly specifies 'width' and 'height' as 0 is added as a reference. 
+        A subtest that explicitly specifies 'width' and 'height' as 0 is added as a reference.
         Both of these cases should result in the fallback color being used.
       </p>
     </d:testDescription>
diff --git a/svg/import/render-elems-01-t-manual.svg b/svg/import/render-elems-01-t-manual.svg
index ec391f8..230a862 100644
--- a/svg/import/render-elems-01-t-manual.svg
+++ b/svg/import/render-elems-01-t-manual.svg
@@ -18,7 +18,7 @@
           Verifies that shapes can be filled.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/render-elems-02-t-manual.svg b/svg/import/render-elems-02-t-manual.svg
index 30646c6..6e92825 100644
--- a/svg/import/render-elems-02-t-manual.svg
+++ b/svg/import/render-elems-02-t-manual.svg
@@ -18,7 +18,7 @@
           Verifies that shapes can be stroked.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/render-elems-03-t-manual.svg b/svg/import/render-elems-03-t-manual.svg
index b5a6b1a..5f3cabd 100644
--- a/svg/import/render-elems-03-t-manual.svg
+++ b/svg/import/render-elems-03-t-manual.svg
@@ -21,7 +21,7 @@
           Verifies that shapes can be filled, stroked and the order of filling and stroking.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/render-elems-06-t-manual.svg b/svg/import/render-elems-06-t-manual.svg
index ef2002b..e7d109a 100644
--- a/svg/import/render-elems-06-t-manual.svg
+++ b/svg/import/render-elems-06-t-manual.svg
@@ -15,10 +15,10 @@
     version="$Revision: 1.9 $" testname="$RCSfile: render-elems-06-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/render.html#Elements">
         <p>
-          Verifies that text can be filled. 
+          Verifies that text can be filled.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
@@ -26,7 +26,7 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
 The
-          test shows two 'G' characters that are filled 
+          test shows two 'G' characters that are filled
           (green to the left, and with navy to the right) and not stroked.
       </p>
     </d:passCriteria>
diff --git a/svg/import/render-elems-07-t-manual.svg b/svg/import/render-elems-07-t-manual.svg
index 427e626..a5ae1d5 100644
--- a/svg/import/render-elems-07-t-manual.svg
+++ b/svg/import/render-elems-07-t-manual.svg
@@ -16,10 +16,10 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/render.html#Elements">
         <p>
           Verifies that text can be stroked. The
-          
+
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/render-elems-08-t-manual.svg b/svg/import/render-elems-08-t-manual.svg
index fb946e7..758bca7 100644
--- a/svg/import/render-elems-08-t-manual.svg
+++ b/svg/import/render-elems-08-t-manual.svg
@@ -16,10 +16,10 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/render.html#Elements">
         <p>
           Verifies that text can be stroked.
-          
+
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/render-groups-01-b-manual.svg b/svg/import/render-groups-01-b-manual.svg
index 035f30c..3eaa8dc 100644
--- a/svg/import/render-groups-01-b-manual.svg
+++ b/svg/import/render-groups-01-b-manual.svg
@@ -19,7 +19,7 @@
           It also validates basic Shape, Image and text rendering.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/render-groups-03-t-manual.svg b/svg/import/render-groups-03-t-manual.svg
index 92f5da0..5cfc890 100644
--- a/svg/import/render-groups-03-t-manual.svg
+++ b/svg/import/render-groups-03-t-manual.svg
@@ -19,7 +19,7 @@
           It also validates basic Shape, Image and text rendering.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">            
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/script-handle-01-b-manual.svg b/svg/import/script-handle-01-b-manual.svg
index b99bb86..6f9d3bb 100644
--- a/svg/import/script-handle-01-b-manual.svg
+++ b/svg/import/script-handle-01-b-manual.svg
@@ -26,7 +26,7 @@
         message indicating that the test was successful is displayed.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Load the test. Click on the blue square.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/script-handle-02-b-manual.svg b/svg/import/script-handle-02-b-manual.svg
index b26e98a..783cc6c 100644
--- a/svg/import/script-handle-02-b-manual.svg
+++ b/svg/import/script-handle-02-b-manual.svg
@@ -25,14 +25,14 @@
         The test shows a target that can be used to generate the various
         kinds of events supported in SVG. Below the
         target, the list of events is shown with red markers next to each.
-      </p>      
+      </p>
       <p>
         If the test passes, all the markers should have turned to green
         after the events have been triggered on the target. If any event
         has not triggered, its marker will remain red.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Load the test. Focus the gray circle, activate it, then move the focus away from the circle.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/script-handle-03-b-manual.svg b/svg/import/script-handle-03-b-manual.svg
index f2280fc..2ffa33e 100644
--- a/svg/import/script-handle-03-b-manual.svg
+++ b/svg/import/script-handle-03-b-manual.svg
@@ -21,14 +21,14 @@
         The test shows a target that can be used to generate the various
         kinds of mouse events supported in SVG. Below the
         target, the list of events is shown with red markers next to each.
-      </p>      
+      </p>
       <p>
         If the test passes, all the markers should have turned to green
         after the events have been triggered on the target. If any event
         has not triggered, its marker will remain red.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Load the test.  Click on the gray circle.</p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/script-handle-04-b-manual.svg b/svg/import/script-handle-04-b-manual.svg
index 2859a4d..3cd97bf 100644
--- a/svg/import/script-handle-04-b-manual.svg
+++ b/svg/import/script-handle-04-b-manual.svg
@@ -21,7 +21,7 @@
         The test shows a target that can be used to generate the various
         kinds of mouse events supported in SVG. Below the
         target, the list of events is shown with red markers next to each.
-      </p>      
+      </p>
       <p>
         If the test passes, all the markers should have turned to green
         after the events have been triggered on the target. If any event
diff --git a/svg/import/script-specify-01-f-manual.svg b/svg/import/script-specify-01-f-manual.svg
index a4ea296..a50d75b 100644
--- a/svg/import/script-specify-01-f-manual.svg
+++ b/svg/import/script-specify-01-f-manual.svg
@@ -1,8 +1,8 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/noSuchLanguage"
-	onload="document.getElementById('testPassed').setAttribute('visibility', 'hidden');
-	        document.getElementById('testFailed').setAttribute('visibility', 'visible');">
+  onload="document.getElementById('testPassed').setAttribute('visibility', 'hidden');
+          document.getElementById('testFailed').setAttribute('visibility', 'visible');">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
   <!--=  Institute of Technology, European Research Consortium for         =-->
@@ -25,7 +25,7 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test is passed if string "Good, script didn't run" is displayed.
-				It fails if the string "No! This is not ECMAScript!" is displayed.
+        It fails if the string "No! This is not ECMAScript!" is displayed.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -40,28 +40,27 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-		<!-- Displayed Title -->
-		<text x="140" y="20" fill="black">
-			contentScriptType Test
-		</text>
+    <!-- Displayed Title -->
+    <text x="140" y="20" fill="black">
+      contentScriptType Test
+    </text>
 
-		<!-- The onload handler would only be run if the user agent implemented the
-		     bogus scripting language MIME type specified in contentScriptType="". -->
-		<g font-size="24" font-weight="bold" transform="translate(240,170)" text-anchor="middle">
-			<text id="testPassed" fill="green">Good, script didn't run.</text>
-			<text id="testFailed" fill="red" visibility="hidden">No! This is not ECMAScript!</text>
-		</g>
-	</g>
-	<g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+    <!-- The onload handler would only be run if the user agent implemented the
+         bogus scripting language MIME type specified in contentScriptType="". -->
+    <g font-size="24" font-weight="bold" transform="translate(240,170)" text-anchor="middle">
+      <text id="testPassed" fill="green">Good, script didn't run.</text>
+      <text id="testFailed" fill="red" visibility="hidden">No! This is not ECMAScript!</text>
+    </g>
+  </g>
+  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.6 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved -->
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
 </svg>
-
diff --git a/svg/import/script-specify-02-f-manual.svg b/svg/import/script-specify-02-f-manual.svg
index f575b73..bfd5f89 100644
--- a/svg/import/script-specify-02-f-manual.svg
+++ b/svg/import/script-specify-02-f-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -23,7 +23,7 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test is passed if string "Good, script didn't run" is displayed.
-				It fails if the string "No! This is not ECMAScript!" is displayed.
+        It fails if the string "No! This is not ECMAScript!" is displayed.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -38,32 +38,31 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-		<!-- Displayed Title -->
-		<text x="240" y="20" fill="black" text-anchor="middle">
-			Test unknown type attribute value on script element
-		</text>
+    <!-- Displayed Title -->
+    <text x="240" y="20" fill="black" text-anchor="middle">
+      Test unknown type attribute value on script element
+    </text>
 
-		<g font-size="24" font-weight="bold" transform="translate(240,170)" text-anchor="middle">
-			<text id="testPassed" fill="green">Good, script didn't run.</text>
-			<text id="testFailed" fill="red" visibility="hidden">No! This is not ECMAScript!</text>
-		</g>
+    <g font-size="24" font-weight="bold" transform="translate(240,170)" text-anchor="middle">
+      <text id="testPassed" fill="green">Good, script didn't run.</text>
+      <text id="testFailed" fill="red" visibility="hidden">No! This is not ECMAScript!</text>
+    </g>
 
-		<!-- The script should be run only if the user agent implemented the bogus language. -->
-		<script type="application/noSuchLanguage"><![CDATA[
-			document.getElementById('testPassed').setAttribute('visibility', 'hidden');
-			document.getElementById('testFailed').setAttribute('visibility', 'visible');
-		]]></script>
-	</g>
-	<g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+    <!-- The script should be run only if the user agent implemented the bogus language. -->
+    <script type="application/noSuchLanguage"><![CDATA[
+      document.getElementById('testPassed').setAttribute('visibility', 'hidden');
+      document.getElementById('testFailed').setAttribute('visibility', 'visible');
+    ]]></script>
+  </g>
+  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.7 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
-
diff --git a/svg/import/shapes-ellipse-03-f-manual.svg b/svg/import/shapes-ellipse-03-f-manual.svg
index 21e7aec..c5beafe 100644
--- a/svg/import/shapes-ellipse-03-f-manual.svg
+++ b/svg/import/shapes-ellipse-03-f-manual.svg
@@ -17,8 +17,8 @@
       </p>
       <p>
         The test shows an 'ellipse' element originating at (0,0) of the current user coordinate system, which has been altered via 'transform' from
-        the initial user coordinate system. Two perpendicular lines which also originate at (0,0) and advance along the x and y axes of 
-        the current user coordinate system are shown. These lines overlap the top and left edges of the ellipse and verifies that the ellipse is 
+        the initial user coordinate system. Two perpendicular lines which also originate at (0,0) and advance along the x and y axes of
+        the current user coordinate system are shown. These lines overlap the top and left edges of the ellipse and verifies that the ellipse is
         thus axis-aligned with its current user coordinate system.
       </p>
     </d:testDescription>
@@ -29,7 +29,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test is passed if both ellipses are divided into four equal parts by two sets of crossing lines, and the rightmost ellipse and crossing lines are rotated together. 
+        The test is passed if both ellipses are divided into four equal parts by two sets of crossing lines, and the rightmost ellipse and crossing lines are rotated together.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -63,7 +63,7 @@
       fill="black">$Revision: 1.5 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/shapes-grammar-01-f-manual.svg b/svg/import/shapes-grammar-01-f-manual.svg
index 50183a9..2051370 100644
--- a/svg/import/shapes-grammar-01-f-manual.svg
+++ b/svg/import/shapes-grammar-01-f-manual.svg
@@ -40,18 +40,18 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
     <g transform="translate(0,400)">
-        <g stroke="#070" stroke-width="8">  
-            <polyline fill="#396" 
-                points="270,-225 300,-245 320,-225 340,-245 280,-280                   
+        <g stroke="#070" stroke-width="8">
+            <polyline fill="#396"
+                points="270,-225 300,-245 320,-225 340,-245 280,-280
                 390,-280 420,-240 280,-185"/>
-            <polygon fill="#5F9"    
+            <polygon fill="#5F9"
                 points="179,-185,218,-203,228,-245,202,-279,159,-280,131,-247,139,-205"/>
         </g>
         <g stroke="#3C6" stroke-width="3" fill="none">
-           <polyline 
-                points="270-225 300-245 320-225 340-245 280-280                   
+           <polyline
+                points="270-225 300-245 320-225 340-245 280-280
                 390-280 420-240 280-185"/>
-            <polygon     
+            <polygon
                 points="179-185,218-203,228-245,202-279,159-280,131-247,139-205"/>
         </g>
     </g>
diff --git a/svg/import/shapes-intro-02-f-manual.svg b/svg/import/shapes-intro-02-f-manual.svg
index 668ced4..af947c2 100644
--- a/svg/import/shapes-intro-02-f-manual.svg
+++ b/svg/import/shapes-intro-02-f-manual.svg
@@ -17,7 +17,7 @@
       </p>
       <p>
         For each basic shape, a 'path' reference element that is red is created.
-        A basic shape is then placed on top of the 'path' element. 
+        A basic shape is then placed on top of the 'path' element.
         For each basic shape there's also a reverse test that uses the shape as a reference for the 'path' element.
       </p>
     </d:testDescription>
@@ -80,7 +80,7 @@
       fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/shapes-line-02-f-manual.svg b/svg/import/shapes-line-02-f-manual.svg
index f6f2bbe..137f816 100644
--- a/svg/import/shapes-line-02-f-manual.svg
+++ b/svg/import/shapes-line-02-f-manual.svg
@@ -15,7 +15,7 @@
       <p>
         The 'fill' attribute has no effect on the 'line' element.
       </p>
-    </d:testDescription>    
+    </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
@@ -54,4 +54,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/shapes-polygon-02-t-manual.svg b/svg/import/shapes-polygon-02-t-manual.svg
index d0e4e7a..1ab6c72 100644
--- a/svg/import/shapes-polygon-02-t-manual.svg
+++ b/svg/import/shapes-polygon-02-t-manual.svg
@@ -38,20 +38,20 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
- 
-    <g stroke="#070" stroke-width="8">    
+
+    <g stroke="#070" stroke-width="8">
     <polygon id="polygon-01" fill="none"
         points="59,45,95,63,108,105,82,139,39,140,11,107,19,65"/>
-    <polygon id="polygon-02" fill="#592" 
+    <polygon id="polygon-02" fill="#592"
         points="179,45,218,63,228,105,202,139,159,140,131,107,139,65,179,45"/>
-    <polygon id="polygon-03" fill="#3D8" 
+    <polygon id="polygon-03" fill="#3D8"
         points="350,45 375,80 410,95 375,110 350,145 325,120 290,95 325,70,350,45"/>
-    <polygon id="polygon-05" fill="none"  
+    <polygon id="polygon-05" fill="none"
         points="59,185,98,203,108,245,82,279,39,280,11,247,19,205,59,185"/>
-    <polygon id="polygon-06" fill="#5F9"    
+    <polygon id="polygon-06" fill="#5F9"
         points="179,185,218,203,228,245,202,279,159,280,131,247,139,205"/>
-    <polygon id="polygon-07" fill="none" 
-       points="270,225 300,245 320,225 340,245 280,280                   
+    <polygon id="polygon-07" fill="none"
+       points="270,225 300,245 320,225 340,245 280,280
        390,280 420,240 280,185"/>
     </g>
     <!-- now the equivalent paths-->
@@ -61,7 +61,7 @@
         <path d="M350,45 L375,80 410,95 375,110 350,145 325,120 290,95 325,70,350,45z"/>
         <path d="M59,185 L98,203,108,245,82,279,39,280,11,247,19,205,59,185z"/>
         <path d="M179,185 L218,203,228,245,202,279,159,280,131,247,139,205z"/>
-        <path d="M270,225 L300,245 320,225 340,245 280,280                   
+        <path d="M270,225 L300,245 320,225 340,245 280,280
        390,280 420,240 280,185z"/>
     </g>
 
diff --git a/svg/import/shapes-polyline-02-t-manual.svg b/svg/import/shapes-polyline-02-t-manual.svg
index 7cc146e..a3f0968 100644
--- a/svg/import/shapes-polyline-02-t-manual.svg
+++ b/svg/import/shapes-polyline-02-t-manual.svg
@@ -39,7 +39,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-    <g stroke="#070" stroke-width="8">    
+    <g stroke="#070" stroke-width="8">
     <!-- Open sawtooth line. -->
     <polyline id="polyline-01" fill="none"   points="10,50,35,150,60,50,85,150,110,50,135,150"/>
     <!-- Closed pentagon shaped polyline (5 segments). -->
@@ -50,11 +50,11 @@
     <polyline id="polyline-04" fill="none"  points="59,185,98,203,108,245,82,279,39,280,11,247,19,205"/>
     <!-- Same shape, filled, shows difference with 'polygon'. -->
     <polyline id="polyline-05" fill="#396"  points="189,185,228,203,238,245,212,279,169,280,141,247,149,205"/>
-    <polyline id="polyline-06" fill="#396" 
-    points="270,225 300,245 320,225 340,245 280,280                   
+    <polyline id="polyline-06" fill="#396"
+    points="270,225 300,245 320,225 340,245 280,280
     390,280 420,240 280,185"/>
     </g>
-    
+
     <!-- now the equivalent paths-->
     <g stroke="#3C6" stroke-width="3" fill="none">
       <path d="M10,50 L35,150,60,50,85,150,110,50,135,150"/>
@@ -62,7 +62,7 @@
       <path d="M310,50 L335,150,360,50,385,150,410,50,435,150"/>
       <path d="M59,185 L98,203,108,245,82,279,39,280,11,247,19,205"/>
       <path d="M189,185 L228,203,238,245,212,279,169,280,141,247,149,205"/>
-      <path d="M270,225 L300,245 320,225 340,245 280,280                   
+      <path d="M270,225 L300,245 320,225 340,245 280,280
         390,280 420,240 280,185"/>
     </g>
   </g>
diff --git a/svg/import/shapes-rect-03-t-manual.svg b/svg/import/shapes-rect-03-t-manual.svg
index 4e51412..8e7fbec 100644
--- a/svg/import/shapes-rect-03-t-manual.svg
+++ b/svg/import/shapes-rect-03-t-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -42,98 +42,98 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
+
     <g transform="translate(0, 30)">
       <g fill="red">
         <g id="references">
           <rect x="100" width="20" height="100" rx="50" ry="20"/>
           <rect x="130" width="20" height="100" rx="10" ry="20"/>
           <rect x="160" width="20" height="100" rx="10" ry="20"/>
-          
+
           <rect x="190" width="20" height="100" rx="10" ry="50"/>
           <rect x="220" width="20" height="100" rx="10" ry="20"/>
           <rect x="250" width="20" height="100" rx="10" ry="10"/>
-          
+
           <rect x="280" width="20" height="100" rx="5" ry="5"/>
           <rect x="310" width="20" height="100" rx="0" ry="0"/>
-          <rect x="340" width="20" height="100" rx="0" ry="0"/>    
-    
+          <rect x="340" width="20" height="100" rx="0" ry="0"/>
+
           <g transform="translate(45,0)">
             <rect x="10" y="120" width="100" height="20" rx="50" ry="10"/>
             <rect x="10" y="150" width="100" height="20" rx="15" ry="10"/>
             <rect x="10" y="180" width="100" height="20" rx="10" ry="10"/>
-            
+
             <rect x="130" y="120" width="100" height="20" rx="50" ry="10"/>
             <rect x="130" y="150" width="100" height="20" rx="20" ry="10"/>
             <rect x="130" y="180" width="100" height="20" rx="10" ry="10"/>
-            
+
             <rect x="250" y="120" width="100" height="20" rx="5" ry="5"/>
             <rect x="250" y="150" width="100" height="20" rx="0" ry="0"/>
             <rect x="250" y="180" width="100" height="20" rx="0" ry="0"/>
           </g>
-    
+
           <g transform="translate(100,100)">
             <rect x="10" y="120" width="50" height="20" rx="25" ry="10"/>
             <rect x="80" y="120" width="20" height="50" rx="10" ry="25"/>
-            
+
             <rect x="120" y="120" width="50" height="20" rx="25" ry="10"/>
-            
+
             <rect stroke-width="0.5" width="20" height="30" rx="10" ry="15" transform="translate(-10 -15) scale(2) translate(10 15) translate(85 52.5)"/>
             <rect x="230" y="120" width="20" height="30" rx="10" ry="25"/>
           </g>
         </g>
-      </g>    
+      </g>
       <g id="tests" fill="lime">
         <rect x="100" width="20" height="100" rx="50" ry="20"/>
         <rect x="130" width="20" height="100" rx="15" ry="20"/>
         <rect x="160" width="20" height="100" rx="10" ry="20"/>
-        
+
         <rect x="190" width="20" height="100" rx="80"/>
         <rect x="220" width="20" height="100" rx="20"/>
         <rect x="250" width="20" height="100" rx="10"/>
-        
+
         <rect x="280" width="20" height="100" rx="5"/>
         <rect x="310" width="20" height="100" rx="0"/>
         <rect x="340" width="20" height="100" rx="0" ry="0"/>
-        
+
         <g transform="translate(45,0)">
           <rect x="10" y="120" width="100" height="20" rx="50" ry="20"/>
           <rect x="10" y="150" width="100" height="20" rx="15" ry="20"/>
           <rect x="10" y="180" width="100" height="20" rx="10" ry="20"/>
-          
+
           <rect x="130" y="120" width="100" height="20" ry="50"/>
           <rect x="130" y="150" width="100" height="20" ry="20"/>
           <rect x="130" y="180" width="100" height="20" ry="10"/>
-          
+
           <rect x="250" y="120" width="100" height="20" ry="5"/>
           <rect x="250" y="150" width="100" height="20" ry="0"/>
           <rect x="250" y="180" width="100" height="20" rx="0" ry="0"/>
         </g>
-        
+
         <g transform="translate(100,100)">
           <rect x="10" y="120" width="50" height="20" rx="50"/>
           <rect x="80" y="120" width="20" height="50" ry="50"/>
-          
+
           <rect x="120" y="120" width="50" height="20" rx="200" ry="200"/>
-          
+
           <rect width="20" height="30" rx="50" transform="translate(-10 -15) scale(2) translate(10 15) translate(85 52.5)"/>
           <rect x="230" y="120" width="20" height="30" ry="50"/>
         </g>
       </g>
-      
+
       <use xlink:href="#references" fill="none" stroke="black"/>
-      
-    </g>    
+
+    </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.9 $</text>
   </g>
   <rect xml:id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/shapes-rect-04-f-manual.svg b/svg/import/shapes-rect-04-f-manual.svg
index fdd8305..eadbe7f 100644
--- a/svg/import/shapes-rect-04-f-manual.svg
+++ b/svg/import/shapes-rect-04-f-manual.svg
@@ -13,7 +13,7 @@
     version="$Revision: 1.3 $" testname="$RCSfile: shapes-rect-04-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/shapes.html#RectElement">
       <p>
-        'Rect' elements with unspecified 'rx' and 'ry' attributes will use the specified 'rx' and 'ry' value if the other one is specified; if neither is specified, the 'rect' has square edges. 
+        'Rect' elements with unspecified 'rx' and 'ry' attributes will use the specified 'rx' and 'ry' value if the other one is specified; if neither is specified, the 'rect' has square edges.
       </p>
       <p>
         Creates one 'rect' element with an unspecified 'ry'. Places it over a red 'rect' element with both 'rx' and 'ry' specified.  Repeat with unspecified 'rx'. Finally creates a 'rect' element that has neither 'rx' or
@@ -63,4 +63,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/shapes-rect-05-f-manual.svg b/svg/import/shapes-rect-05-f-manual.svg
index 85e82a2..acea039 100644
--- a/svg/import/shapes-rect-05-f-manual.svg
+++ b/svg/import/shapes-rect-05-f-manual.svg
@@ -71,4 +71,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/shapes-rect-06-f-manual.svg b/svg/import/shapes-rect-06-f-manual.svg
index 0f9d4ea..7b4d209 100644
--- a/svg/import/shapes-rect-06-f-manual.svg
+++ b/svg/import/shapes-rect-06-f-manual.svg
@@ -67,4 +67,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/shapes-rect-07-f-manual.svg b/svg/import/shapes-rect-07-f-manual.svg
index 7dbe046..ac9e9fb 100644
--- a/svg/import/shapes-rect-07-f-manual.svg
+++ b/svg/import/shapes-rect-07-f-manual.svg
@@ -58,4 +58,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/struct-cond-02-t-manual.svg b/svg/import/struct-cond-02-t-manual.svg
index 6883e0a..c43e63f 100644
--- a/svg/import/struct-cond-02-t-manual.svg
+++ b/svg/import/struct-cond-02-t-manual.svg
@@ -21,7 +21,7 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	Run the test. No interaction required.
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
@@ -84,14 +84,14 @@
           <text x="20" y="220" xml:lang="bn" font-family="'Arial Unicode MS','UT Bengali Khulna'" font-size="28">ওরা েকন বাংলা বলেত পাের না ?</text>
           <text x="230" y="150" xml:lang="en">Bengali</text>
         </g>
-        <!-- 
-Tibetan needs complex layout: this will not work correctly though the font has the right glyphs: 
+        <!--
+Tibetan needs complex layout: this will not work correctly though the font has the right glyphs:
 <g systemLanguage="bo">
-					<text x="20" y="220" xml:lang="bo" font-family="'Arial Unicode MS'" font-size="18">
-						
+          <text x="20" y="220" xml:lang="bo" font-family="'Arial Unicode MS'" font-size="18">
+
 ག་རེ་བྱས་ཁོ་རང་ཚོས་ བོད་ སྐད་ཆ་དེ་ག་རང་བཤད་ཀྱི་མ་རེད།</text>
-					<text x="230" y="150" xml:lang="en">Tibetan</text>
-				</g>-->
+          <text x="230" y="150" xml:lang="en">Tibetan</text>
+        </g>-->
         <g systemLanguage="ca">
           <text x="20" y="220" xml:lang="bg" font-size="20">Per què no poden simplement parlar en català ?</text>
           <text x="230" y="150" xml:lang="en">Catalan</text>
@@ -186,10 +186,10 @@
           <text x="230" y="150" xml:lang="en">Italian</text>
         </g>
         <g systemLanguage="iu">
-          <!-- unable to distinguish North Baffin Island Inuktitut from 
-			     South/Central Baffin Island Inuktitut
-			     because ISO 3166 codes do not offer any finer 
-			     resolution than 'Canadian' -->
+          <!-- unable to distinguish North Baffin Island Inuktitut from
+           South/Central Baffin Island Inuktitut
+           because ISO 3166 codes do not offer any finer
+           resolution than 'Canadian' -->
           <text x="20" y="220" xml:lang="iu" font-family="NunacomU,'Ballymun RO','Arial Unicode MS'" font-size="28">ᓱᒻᒪᓂᒃᑯᐊ ᐃᓄᒃᑎᑐ ᑐᐃᓐᓇᔭᙱᓚᑦ</text>
           <text x="230" y="150" xml:lang="en">Inuktitut</text>
         </g>
@@ -281,8 +281,8 @@
         <g systemLanguage="si">
           <text x="20" y="220" xml:lang="si" font-family="'Andale Mono WT J'" font-size="26">අැයි ඔවුන්ට ඉංගරිස කතා ෛනබ ?</text>
           <text x="230" y="150" xml:lang="en">Sinhalese</text>
-          <!-- Sinhalese needs somewhat complex layout (many paired special cases, see 
-http://www-texdev.mpce.mq.edu.au/l2h/indic/Sinhala/lreport/node1.html 
+          <!-- Sinhalese needs somewhat complex layout (many paired special cases, see
+http://www-texdev.mpce.mq.edu.au/l2h/indic/Sinhala/lreport/node1.html
 so this 'chart' font is not entirely suitable-->
         </g>
         <g systemLanguage="sl">
@@ -369,10 +369,10 @@
         </g>
       </switch>
       <!-- action item was to make a switch "for all ISO 639-1 language codes",
-     a large task not completed. But there is enough here to make a good test case, 
+     a large task not completed. But there is enough here to make a good test case,
      including 19 of the top 20 langiuages by number of speakers (except Punjabi).
-     
-     Here is the full list (trailing * indicates language included in this test), 
+
+     Here is the full list (trailing * indicates language included in this test),
      accurate as of October 22, 2002 from the registration authority:
       http://lcweb.loc.gov/standards/iso639-2/langcodes.html
 
@@ -397,7 +397,7 @@
   bs Bosnian
 
   ca Catalan *
-  ce Chechen 
+  ce Chechen
   ch Chamorro
   co Corsican
   cs Czech *
@@ -493,7 +493,7 @@
   no Norwegian *
   nr Ndebele, South
   nv Navajo
-  ny Chichewa~@Nyanja  
+  ny Chichewa~@Nyanja
 
   oc Occitan (post 1500); Provencal
   om Oromo [Afan]
@@ -541,7 +541,7 @@
   ti Tigrinya
   tk Turkmen
   tl Tagalog *
-  tn Tswana [Setswana]  
+  tn Tswana [Setswana]
   to Tonga
   tr Turkish *
   ts Tsonga
diff --git a/svg/import/struct-cond-03-t-manual.svg b/svg/import/struct-cond-03-t-manual.svg
index 93d5490..60e232e 100644
--- a/svg/import/struct-cond-03-t-manual.svg
+++ b/svg/import/struct-cond-03-t-manual.svg
@@ -25,7 +25,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        On the bottom half of the test, there is a first switch. 
+        On the bottom half of the test, there is a first switch.
         Because SVG Tiny does not support DOM, an SVG Tiny implementation
         which does not support other SVG Profiles should show a green
         rectangle. If the application supports the DOM, meaning that
diff --git a/svg/import/struct-cond-overview-05-f-manual.svg b/svg/import/struct-cond-overview-05-f-manual.svg
index 68f026c..3f78ff2 100644
--- a/svg/import/struct-cond-overview-05-f-manual.svg
+++ b/svg/import/struct-cond-overview-05-f-manual.svg
@@ -14,8 +14,8 @@
         Elements whose parent elements have failing conditional processing attributes are able to be referenced and rendered by 'use' elements.
       </p>
       <p>
-        Define three 'rect' elements that have a 'g' parent with either an invalid 'requiredFeature', 'requiredExtension', or 'systemLanguage'. 
-        Then define three 'use' elements that reference the 'rect' elements. Verify that the 'use' elements render. 
+        Define three 'rect' elements that have a 'g' parent with either an invalid 'requiredFeature', 'requiredExtension', or 'systemLanguage'.
+        Then define three 'use' elements that reference the 'rect' elements. Verify that the 'use' elements render.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/struct-defs-01-t-manual.svg b/svg/import/struct-defs-01-t-manual.svg
index d808880..9dd905f 100644
--- a/svg/import/struct-defs-01-t-manual.svg
+++ b/svg/import/struct-defs-01-t-manual.svg
@@ -24,7 +24,7 @@
           a red fill and the other to obscure most of the green rectangle.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The rendered picture should match the reference image, except for possible
         variations in the labelling text (per CSS2 rules).
diff --git a/svg/import/struct-dom-01-b-manual.svg b/svg/import/struct-dom-01-b-manual.svg
index a7c379e..d484f5f 100644
--- a/svg/import/struct-dom-01-b-manual.svg
+++ b/svg/import/struct-dom-01-b-manual.svg
@@ -1,188 +1,188 @@
-<svg version="1.1" baseProfile="basic" onload="domTest(evt)" id="svg-root"

-  width="100%" height="100%" viewBox="0 0 480 360"

-  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

-  <!--======================================================================-->

-  <!--=  SVG 1.1 2nd Edition Test Case                                     =-->

-  <!--======================================================================-->

-  <!--=  Copyright 2009 World Wide Web Consortium, (Massachusetts          =-->

-  <!--=  Institute of Technology, European Research Consortium for         =-->

-  <!--=  Informatics and Mathematics (ERCIM), Keio University).            =-->

-  <!--=  All Rights Reserved.                                              =-->

-  <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->

-  <!--======================================================================-->

-  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/"

-    template-version="1.4" reviewer="SVGWG" author="VH" status="accepted"

-    version="$Revision: 1.8 $" testname="$RCSfile: struct-dom-01-b.svg,v $">

-    <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#DOMInterfaces">

-        <p>

-          Verify the basic capability to handle the SVG DOM API.

-        </p>

-      <p>

-        The test is composed of a top

-        level svg element with an 'onload' event handler and a rect element. Both

-        the svg and the rect elements have an identifier. The 'onload' handler

-        invokes SVG-specific DOM API methods which use these identifiers.

-      </p>

-      <p>

-        First, the handler gets the SVG element owner of the rect element and checks it has

-        the expected identifier. Then, the handler accesses the coordinates of the rect element

-        and uses them to build a 'shadow' rectangle under the existing one. Finally, the 'shadow'

-        rectangle is created using the SVGSVGElement's createSVGRect method.

-      </p>

-    </d:testDescription>

-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">

-      <p>

-        Run the test. No interaction required.

-      </p>

-    </d:operatorScript>

-    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">

-      <p>The test passes if:</p>

-      <ul>

-        <li>The text "This document's root identifier is: svg-root" is shown.</li>

-        <li>A green rectangle with a black shadow is shown.</li>

-      </ul>

-    </d:passCriteria>

-  </d:SVGTestCase>

-  <title id="test-title">$RCSfile: struct-dom-01-b.svg,v $</title>

-  <defs>

-    <font-face font-family="SVGFreeSansASCII" unicode-range="U+0-7F">

-      <font-face-src>

-        <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>

-      </font-face-src>

-    </font-face>

-  </defs>

-  <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">

-    <script type="text/ecmascript" xlink:href="../resources/testharness.js"></script>

-    <script type="text/ecmascript"><![CDATA[

-      function domTest(evt) {

-

-      var svg_ns = "http://www.w3.org/2000/svg";

-

-      // Get Document

-      var target = evt.target;

-      var doc = target.ownerDocument;

-

-      //

-      // Test that our rectangle is an SVGElement instance

-      //

-      var rect = doc.getElementById("rectId");

-      var rootSVG = rect.ownerSVGElement;

-      var rootId = rootSVG.getAttribute( "id" );

-

-      // Insert a new text element to the DOM tree using the id

-      var newText = doc.createElementNS(svg_ns, 'text');

-      newText.setAttribute('x', '50');

-      newText.setAttribute('y', '100');

-      var message = "This document's root identifier is: " + rootId;

-      var textContent = doc.createTextNode(message);

-      newText.appendChild(textContent);

-      rect.parentNode.appendChild(newText);

-

-      //

-      // Now, check that our rectangle is an instance of SVGRect by accessing

-      // specific methods in order to get its x, y, width and height attributes.

-      //

-      var x = rect.x.baseVal.value; // SVGRect -&gt; SVGAnimatedLenght -&gt; SVGLength -&gt; long

-      var y = rect.y.baseVal.value;

-      var width = rect.width.baseVal.value;

-      var height = rect.height.baseVal.value;

-

-      //

-      // Now, build a new SVGRect through the SVGSVGElement interface.

-      //

-      var newRect = doc.createElementNS(svg_ns, 'rect');

-

-      //

-      // Set the x, y, width and height of this element

-      //

-      newRect.x.baseVal.value = x + 10;

-      newRect.y.baseVal.value = y + 10;

-      newRect.setAttribute("width", width);

-      newRect.setAttribute("height", height);

-

-      //

-      // Insert new element in DOM tree

-      //

-      rect.parentNode.insertBefore(newRect, rect);

-

-      //

-      // Check the pass criteria using the JS framework

-      //

-      test(function() {assert_equals(rootId, "svg-root")}, "Assert that the document's root identifier is 'svg-root'.");

-      test(function() {

-           assert_true(newRect instanceof SVGRectElement);

-           assert_equals(newRect.getAttribute('x'), String(x+10));

-           assert_equals(newRect.getAttribute('y'), String(y+10));

-           assert_equals(newRect.getAttribute('width'), String(width));

-           assert_equals(newRect.getAttribute('height'), String(height));

-      }, "Assert that 'newRect' is replica (ignoring fill) of 'rect' with different x and y.");

-      }

-

-    ]]></script>

-    <!--======================================================================-->

-    <!-- Since this test is examining the SVG DOM, it could use any language  -->

-    <!-- binding. Here is the equivalent code for the Java binding            -->

-    <!--

-

-        //

-        // Test that our rectangle is an SVGElement instance

-        //

-        SVGRectElement rect = (SVGRectElement) doc.getElementById("rectId");

-        SVGElement rootSVG = rect.getOwnerSVGElement();

-        String rootId = rootSVG.getId();

-

-        // Insert a new text element to the DOM tree using the id

-        Element newText = doc.createElement("text");

-        newText.setAttribute("x", "50");

-        newText.setAttribute("y", "100");

-        String message = "This document's root identifier is=" " + rootId" 

-        Text textContent = doc.createTextNode(message);

-        newText.appendChild(textContent);

-        rect.getParentNode().appendChild(newText);

-

-        //

-        // Now, check that our rectangle is an instance of SVGRect by accessing

-        // specific methods in order to get its x, y, width and height attributes.

-        //

-        float x = rect.getX().getBaseVal().getValue();

-        float y = rect.getY().getBaseVal().getValue();

-        float width = rect.getWidth().getBaseVal().getValue();

-        float height = rect.getHeight().getBaseVal().getValue();

-

-        //

-        // Now, build a new SVGRect through the SVGSVGElement interface.

-        //

-        SVGRectElement newRect = (SVGRectElement) doc.createElement("rect");

-

-        //

-        // Set the x, y, width and height of this element

-        //

-        newRect.getX().getBaseVal().setValue(x + 10);

-        newRect.getY().getBaseVal().setValue(y + 10);

-        newRect.getWidth().getBaseVal().setValue(width);

-        newRect.getHeight().getBaseVal().setValue(height);

-

-        //

-        // Insert new element in DOM tree

-        //

-	alert(newRect)

-        rect.getParentNode().insertBefore(newRect, rect);

-

--->

-    <!-- ===================================================================== -->

-    <!-- The following rectangle's is accessed in the 'domTest' ECMA Script    -->

-    <!-- handler.                                                              -->

-    <!-- ===================================================================== -->

-    <rect id="rectId" x="40" y="150" width="50" height="50" fill="green"/>

-  </g>

-  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">

-    <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.8 $</text>

-  </g>

-  <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>

-  <!-- comment out this watermark once the test is approved -->

-  <!--<g id="draft-watermark">

-    <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>

-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"

-      text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>

-  </g>-->

-</svg>

+<svg version="1.1" baseProfile="basic" onload="domTest(evt)" id="svg-root"
+  width="100%" height="100%" viewBox="0 0 480 360"
+  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <!--======================================================================-->
+  <!--=  SVG 1.1 2nd Edition Test Case                                     =-->
+  <!--======================================================================-->
+  <!--=  Copyright 2009 World Wide Web Consortium, (Massachusetts          =-->
+  <!--=  Institute of Technology, European Research Consortium for         =-->
+  <!--=  Informatics and Mathematics (ERCIM), Keio University).            =-->
+  <!--=  All Rights Reserved.                                              =-->
+  <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->
+  <!--======================================================================-->
+  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/"
+    template-version="1.4" reviewer="SVGWG" author="VH" status="accepted"
+    version="$Revision: 1.8 $" testname="$RCSfile: struct-dom-01-b.svg,v $">
+    <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#DOMInterfaces">
+        <p>
+          Verify the basic capability to handle the SVG DOM API.
+        </p>
+      <p>
+        The test is composed of a top
+        level svg element with an 'onload' event handler and a rect element. Both
+        the svg and the rect elements have an identifier. The 'onload' handler
+        invokes SVG-specific DOM API methods which use these identifiers.
+      </p>
+      <p>
+        First, the handler gets the SVG element owner of the rect element and checks it has
+        the expected identifier. Then, the handler accesses the coordinates of the rect element
+        and uses them to build a 'shadow' rectangle under the existing one. Finally, the 'shadow'
+        rectangle is created using the SVGSVGElement's createSVGRect method.
+      </p>
+    </d:testDescription>
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
+      <p>
+        Run the test. No interaction required.
+      </p>
+    </d:operatorScript>
+    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
+      <p>The test passes if:</p>
+      <ul>
+        <li>The text "This document's root identifier is: svg-root" is shown.</li>
+        <li>A green rectangle with a black shadow is shown.</li>
+      </ul>
+    </d:passCriteria>
+  </d:SVGTestCase>
+  <title id="test-title">$RCSfile: struct-dom-01-b.svg,v $</title>
+  <defs>
+    <font-face font-family="SVGFreeSansASCII" unicode-range="U+0-7F">
+      <font-face-src>
+        <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>
+      </font-face-src>
+    </font-face>
+  </defs>
+  <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
+    <script type="text/ecmascript" xlink:href="../resources/testharness.js"></script>
+    <script type="text/ecmascript"><![CDATA[
+      function domTest(evt) {
+
+      var svg_ns = "http://www.w3.org/2000/svg";
+
+      // Get Document
+      var target = evt.target;
+      var doc = target.ownerDocument;
+
+      //
+      // Test that our rectangle is an SVGElement instance
+      //
+      var rect = doc.getElementById("rectId");
+      var rootSVG = rect.ownerSVGElement;
+      var rootId = rootSVG.getAttribute( "id" );
+
+      // Insert a new text element to the DOM tree using the id
+      var newText = doc.createElementNS(svg_ns, 'text');
+      newText.setAttribute('x', '50');
+      newText.setAttribute('y', '100');
+      var message = "This document's root identifier is: " + rootId;
+      var textContent = doc.createTextNode(message);
+      newText.appendChild(textContent);
+      rect.parentNode.appendChild(newText);
+
+      //
+      // Now, check that our rectangle is an instance of SVGRect by accessing
+      // specific methods in order to get its x, y, width and height attributes.
+      //
+      var x = rect.x.baseVal.value; // SVGRect -&gt; SVGAnimatedLenght -&gt; SVGLength -&gt; long
+      var y = rect.y.baseVal.value;
+      var width = rect.width.baseVal.value;
+      var height = rect.height.baseVal.value;
+
+      //
+      // Now, build a new SVGRect through the SVGSVGElement interface.
+      //
+      var newRect = doc.createElementNS(svg_ns, 'rect');
+
+      //
+      // Set the x, y, width and height of this element
+      //
+      newRect.x.baseVal.value = x + 10;
+      newRect.y.baseVal.value = y + 10;
+      newRect.setAttribute("width", width);
+      newRect.setAttribute("height", height);
+
+      //
+      // Insert new element in DOM tree
+      //
+      rect.parentNode.insertBefore(newRect, rect);
+
+      //
+      // Check the pass criteria using the JS framework
+      //
+      test(function() {assert_equals(rootId, "svg-root")}, "Assert that the document's root identifier is 'svg-root'.");
+      test(function() {
+           assert_true(newRect instanceof SVGRectElement);
+           assert_equals(newRect.getAttribute('x'), String(x+10));
+           assert_equals(newRect.getAttribute('y'), String(y+10));
+           assert_equals(newRect.getAttribute('width'), String(width));
+           assert_equals(newRect.getAttribute('height'), String(height));
+      }, "Assert that 'newRect' is replica (ignoring fill) of 'rect' with different x and y.");
+      }
+
+    ]]></script>
+    <!--======================================================================-->
+    <!-- Since this test is examining the SVG DOM, it could use any language  -->
+    <!-- binding. Here is the equivalent code for the Java binding            -->
+    <!--
+
+        //
+        // Test that our rectangle is an SVGElement instance
+        //
+        SVGRectElement rect = (SVGRectElement) doc.getElementById("rectId");
+        SVGElement rootSVG = rect.getOwnerSVGElement();
+        String rootId = rootSVG.getId();
+
+        // Insert a new text element to the DOM tree using the id
+        Element newText = doc.createElement("text");
+        newText.setAttribute("x", "50");
+        newText.setAttribute("y", "100");
+        String message = "This document's root identifier is=" " + rootId"
+        Text textContent = doc.createTextNode(message);
+        newText.appendChild(textContent);
+        rect.getParentNode().appendChild(newText);
+
+        //
+        // Now, check that our rectangle is an instance of SVGRect by accessing
+        // specific methods in order to get its x, y, width and height attributes.
+        //
+        float x = rect.getX().getBaseVal().getValue();
+        float y = rect.getY().getBaseVal().getValue();
+        float width = rect.getWidth().getBaseVal().getValue();
+        float height = rect.getHeight().getBaseVal().getValue();
+
+        //
+        // Now, build a new SVGRect through the SVGSVGElement interface.
+        //
+        SVGRectElement newRect = (SVGRectElement) doc.createElement("rect");
+
+        //
+        // Set the x, y, width and height of this element
+        //
+        newRect.getX().getBaseVal().setValue(x + 10);
+        newRect.getY().getBaseVal().setValue(y + 10);
+        newRect.getWidth().getBaseVal().setValue(width);
+        newRect.getHeight().getBaseVal().setValue(height);
+
+        //
+        // Insert new element in DOM tree
+        //
+  alert(newRect)
+        rect.getParentNode().insertBefore(newRect, rect);
+
+-->
+    <!-- ===================================================================== -->
+    <!-- The following rectangle's is accessed in the 'domTest' ECMA Script    -->
+    <!-- handler.                                                              -->
+    <!-- ===================================================================== -->
+    <rect id="rectId" x="40" y="150" width="50" height="50" fill="green"/>
+  </g>
+  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
+    <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.8 $</text>
+  </g>
+  <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
+  <!-- comment out this watermark once the test is approved -->
+  <!--<g id="draft-watermark">
+    <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
+      text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
+  </g>-->
+</svg>
diff --git a/svg/import/struct-dom-02-b-manual.svg b/svg/import/struct-dom-02-b-manual.svg
index 6ac422b..76ad07f 100644
--- a/svg/import/struct-dom-02-b-manual.svg
+++ b/svg/import/struct-dom-02-b-manual.svg
@@ -27,7 +27,7 @@
         Note that this test uses the 'onload' event on the root svg element.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
       </p>
diff --git a/svg/import/struct-dom-04-b-manual.svg b/svg/import/struct-dom-04-b-manual.svg
index afc0efe..a20504e 100644
--- a/svg/import/struct-dom-04-b-manual.svg
+++ b/svg/import/struct-dom-04-b-manual.svg
@@ -63,7 +63,7 @@
       featureStrings[length++] = "views";
       featureStrings[length++] = "css2";
       featureStrings[length++] = "events";
-      featureStrings[length++] = "uievents	";
+      featureStrings[length++] = "uievents  ";
       featureStrings[length++] = "mouseevents";
       featureStrings[length++] = "mutationevents";
       featureStrings[length++] = "traversal";
diff --git a/svg/import/struct-dom-05-b-manual.svg b/svg/import/struct-dom-05-b-manual.svg
index 4667a14..9d101a6 100644
--- a/svg/import/struct-dom-05-b-manual.svg
+++ b/svg/import/struct-dom-05-b-manual.svg
@@ -63,7 +63,7 @@
       featureStrings[length++] = "views";
       featureStrings[length++] = "css2";
       featureStrings[length++] = "events";
-      featureStrings[length++] = "uievents	";
+      featureStrings[length++] = "uievents  ";
       featureStrings[length++] = "mouseevents";
       featureStrings[length++] = "mutationevents";
       featureStrings[length++] = "traversal";
diff --git a/svg/import/struct-dom-08-f-manual.svg b/svg/import/struct-dom-08-f-manual.svg
index fd248ce..5cf8186 100644
--- a/svg/import/struct-dom-08-f-manual.svg
+++ b/svg/import/struct-dom-08-f-manual.svg
@@ -66,7 +66,7 @@
         }
         document.getElementById('r').setAttribute('fill', 'red');
       }
-      
+
       begin();
     </script>
   </g>
diff --git a/svg/import/struct-dom-11-f-manual.svg b/svg/import/struct-dom-11-f-manual.svg
index 869f8c5..fe88225 100644
--- a/svg/import/struct-dom-11-f-manual.svg
+++ b/svg/import/struct-dom-11-f-manual.svg
@@ -24,7 +24,7 @@
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
+      <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
@@ -60,64 +60,64 @@
     </g>
 
     <script><![CDATA[
-			function userspace2viewport(elm, r)
-			{
-				var ctm = elm.getCTM();
+      function userspace2viewport(elm, r)
+      {
+        var ctm = elm.getCTM();
 
-				var corners = [];
-				for(var i = 0; i < 4; i++)
-					corners.push(svg.createSVGPoint());
-				
-				corners[0].x = r.x;
-				corners[0].y = r.y;
-				corners[1].x = r.x + r.width;
-				corners[1].y = r.y;
-				corners[2].x = r.x + r.width;
-				corners[2].y = r.y + r.height;
-				corners[3].x = r.x;
-				corners[3].y = r.y + r.height;
+        var corners = [];
+        for(var i = 0; i < 4; i++)
+          corners.push(svg.createSVGPoint());
 
-				for(var i = 0; i < 4; i++)
-					corners[i] = corners[i].matrixTransform(ctm);
-				
-				var min = svg.createSVGPoint();
-				var max = svg.createSVGPoint();
-				min.x = corners[0].x;
-				min.y = corners[0].y;
-				max.x = corners[0].x;
-				max.y = corners[0].y;
-				
-				for(var i = 1; i < 4; i++)
-				{
-					var x = corners[i].x;
-					var y = corners[i].y;
-					
-					if(x < min.x)
-						min.x = x;
-					else if(x > max.x)
-						max.x = x;
-					
-					if(y < min.y)
-						min.y = y;
-					else if(y > max.y)
-						max.y = y;
-				}
-				
-				var res = svg.createSVGRect();
-				res.x = min.x;
-				res.y = min.y;
-				res.width = max.x - min.x;
-				res.height = max.y - min.y;
-				return res;
-			}
-		
+        corners[0].x = r.x;
+        corners[0].y = r.y;
+        corners[1].x = r.x + r.width;
+        corners[1].y = r.y;
+        corners[2].x = r.x + r.width;
+        corners[2].y = r.y + r.height;
+        corners[3].x = r.x;
+        corners[3].y = r.y + r.height;
+
+        for(var i = 0; i < 4; i++)
+          corners[i] = corners[i].matrixTransform(ctm);
+
+        var min = svg.createSVGPoint();
+        var max = svg.createSVGPoint();
+        min.x = corners[0].x;
+        min.y = corners[0].y;
+        max.x = corners[0].x;
+        max.y = corners[0].y;
+
+        for(var i = 1; i < 4; i++)
+        {
+          var x = corners[i].x;
+          var y = corners[i].y;
+
+          if(x < min.x)
+            min.x = x;
+          else if(x > max.x)
+            max.x = x;
+
+          if(y < min.y)
+            min.y = y;
+          else if(y > max.y)
+            max.y = y;
+        }
+
+        var res = svg.createSVGRect();
+        res.x = min.x;
+        res.y = min.y;
+        res.width = max.x - min.x;
+        res.height = max.y - min.y;
+        return res;
+      }
+
       var svg = document.documentElement,
           c = document.getElementById('c'),
           r1 = document.getElementById('r1'),
           r2 = document.getElementById('r2'),
           c1 = document.getElementById('c1'),
           c2 = document.getElementById('c2'),
-					g1 = document.getElementById('g1'),
+          g1 = document.getElementById('g1'),
           r = svg.createSVGRect(),
           nl;
 
@@ -126,9 +126,9 @@
         r.y = 95;
         r.width = 5;
         r.height = 5;
-		
-				r = userspace2viewport(g1, r);
-		
+
+        r = userspace2viewport(g1, r);
+
         nl = svg.getIntersectionList(r, null);
         if (nl && nl.length == 1) {
           c1.parentNode.removeChild(c1);
@@ -143,8 +143,8 @@
         r.width = 20;
         r.height = 20;
 
-				r = userspace2viewport(g1, r);
-				
+        r = userspace2viewport(g1, r);
+
         nl = svg.getEnclosureList(r, null);
         if (nl && nl.length == 1) {
           c2.parentNode.removeChild(c2);
diff --git a/svg/import/struct-dom-12-b-manual.svg b/svg/import/struct-dom-12-b-manual.svg
index c5746ff..831a132 100644
--- a/svg/import/struct-dom-12-b-manual.svg
+++ b/svg/import/struct-dom-12-b-manual.svg
@@ -70,7 +70,6 @@
         use.setAttributeNS(null, 'fill', colors[pass]);
         }
 
-
       ]]></script>
     </defs>
 
@@ -85,7 +84,7 @@
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/struct-dom-13-f-manual.svg b/svg/import/struct-dom-13-f-manual.svg
index 028288e..654c3cd 100644
--- a/svg/import/struct-dom-13-f-manual.svg
+++ b/svg/import/struct-dom-13-f-manual.svg
@@ -38,120 +38,120 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	<defs>
-		<style type="text/css">
-			.passed { fill: lime }
-			.failed { fill: red }
-			.result { font: 9px monospace; fill: black }
-		</style>
-	</defs>
+  <defs>
+    <style type="text/css">
+      .passed { fill: lime }
+      .failed { fill: red }
+      .result { font: 9px monospace; fill: black }
+    </style>
+  </defs>
 
-	<g visibility="hidden">
-		<circle id="c1" cx="40" cy="40" r="10" fill="blue" stroke="lime"/>
-		<circle id="c2" cx="10" cy="50" r="10" fill="red" stroke="lime"/>
-		<circle id="c3" cx="20" cy="20" r="20" fill="green" stroke="lime"/>
-		<line id="l1" x1="5" y1="5" x2="40" y2="20" stroke="black"/>
-		<line id="l2" x1="20" y1="20" x2="40" y2="30" stroke="red"/>
+  <g visibility="hidden">
+    <circle id="c1" cx="40" cy="40" r="10" fill="blue" stroke="lime"/>
+    <circle id="c2" cx="10" cy="50" r="10" fill="red" stroke="lime"/>
+    <circle id="c3" cx="20" cy="20" r="20" fill="green" stroke="lime"/>
+    <line id="l1" x1="5" y1="5" x2="40" y2="20" stroke="black"/>
+    <line id="l2" x1="20" y1="20" x2="40" y2="30" stroke="red"/>
 
-		<rect id="r1" x="10" y="10" width="50" height="50" fill="none" stroke="red"/>
+    <rect id="r1" x="10" y="10" width="50" height="50" fill="none" stroke="red"/>
 
-		<circle id="c4" cx="80" cy="50" r="10" fill="yellow"/>
-	</g>
-	
-	<script><![CDATA[
-	var ypos = 100;
-	
-	function verify(str, result)
-	{
-		var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
-		var r = document.createElementNS("http://www.w3.org/2000/svg", "rect");
-		var t = document.createElementNS("http://www.w3.org/2000/svg", "text");
-		t.textContent = str + ": " + (result ? "PASSED" : "FAILED");
-		t.setAttribute("class", "result");
-		t.setAttribute("x", "10");
-		t.setAttribute("y", "7");
-		r.setAttribute("y", "1");
-		r.setAttribute("width", "5");
-		r.setAttribute("height", "5");
-		g.setAttribute("class", result ? "passed" : "failed");
-		g.appendChild(r);
-		g.appendChild(t);
-		g.setAttribute("transform", "translate(280 " + ypos + ")");
-		ypos += 10;
-		document.getElementById("test-body-content").appendChild(g);
-	}
-		
-	function test()
-	{
-		var root = document.documentElement; //document.getElementById("test-root");
-		var rect = root.createSVGRect();
-		rect.x = 10;
-		rect.y = 10;
-		rect.width = 50;
-		rect.height = 50;
-		
-		var tests = { "c1" : true, "c2" : true, "c3" : true, "l1" : true, "l2" : true, "r1" : true, "c4" : false };
-		
-		verify("checkIntersection", root.checkIntersection);
-		
-		if(root.checkIntersection)
-		{
-			for(var test in tests)
-			{
-				try 
-				{
-					var elm = document.getElementById(test);
-					var result = root.checkIntersection(elm, rect);
-				
-					verify(test, result == tests[test]);
-				} 
-				catch(e)
-				{
-					verify(test, false);
-				}
-			}
-		}
-		
-		verify("getIntersectionList", root.getIntersectionList);
-		
-		if(root.getIntersectionList)
-		{
-			var expected = [ "c1", "c2", "c3", "l1", "l2", "r1" ];
-			try 
-			{
-				var list = root.getIntersectionList(rect, null);
-				verify("call", true);
-			}
-			catch(e)
-			{
-				verify("call", false);
-			}
-			
-			try
-			{
-				verify("length", expected.length == list.length);
-			}
-			catch(e)
-			{
-				verify("length", false);
-			}
-			
-			for(var i = 0; i < expected.length; i++)
-			{
-				var elm = document.getElementById(expected[i]);
-				try
-				{
-					verify("has " + expected[i], elm === list.item(i))
-				}
-				catch(e)
-				{
-					verify("has " + expected[i], false);
-				}
-			}
-		}
-	}
+    <circle id="c4" cx="80" cy="50" r="10" fill="yellow"/>
+  </g>
 
-	]]></script>
+  <script><![CDATA[
+  var ypos = 100;
+
+  function verify(str, result)
+  {
+    var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
+    var r = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+    var t = document.createElementNS("http://www.w3.org/2000/svg", "text");
+    t.textContent = str + ": " + (result ? "PASSED" : "FAILED");
+    t.setAttribute("class", "result");
+    t.setAttribute("x", "10");
+    t.setAttribute("y", "7");
+    r.setAttribute("y", "1");
+    r.setAttribute("width", "5");
+    r.setAttribute("height", "5");
+    g.setAttribute("class", result ? "passed" : "failed");
+    g.appendChild(r);
+    g.appendChild(t);
+    g.setAttribute("transform", "translate(280 " + ypos + ")");
+    ypos += 10;
+    document.getElementById("test-body-content").appendChild(g);
+  }
+
+  function test()
+  {
+    var root = document.documentElement; //document.getElementById("test-root");
+    var rect = root.createSVGRect();
+    rect.x = 10;
+    rect.y = 10;
+    rect.width = 50;
+    rect.height = 50;
+
+    var tests = { "c1" : true, "c2" : true, "c3" : true, "l1" : true, "l2" : true, "r1" : true, "c4" : false };
+
+    verify("checkIntersection", root.checkIntersection);
+
+    if(root.checkIntersection)
+    {
+      for(var test in tests)
+      {
+        try
+        {
+          var elm = document.getElementById(test);
+          var result = root.checkIntersection(elm, rect);
+
+          verify(test, result == tests[test]);
+        }
+        catch(e)
+        {
+          verify(test, false);
+        }
+      }
+    }
+
+    verify("getIntersectionList", root.getIntersectionList);
+
+    if(root.getIntersectionList)
+    {
+      var expected = [ "c1", "c2", "c3", "l1", "l2", "r1" ];
+      try
+      {
+        var list = root.getIntersectionList(rect, null);
+        verify("call", true);
+      }
+      catch(e)
+      {
+        verify("call", false);
+      }
+
+      try
+      {
+        verify("length", expected.length == list.length);
+      }
+      catch(e)
+      {
+        verify("length", false);
+      }
+
+      for(var i = 0; i < expected.length; i++)
+      {
+        var elm = document.getElementById(expected[i]);
+        try
+        {
+          verify("has " + expected[i], elm === list.item(i))
+        }
+        catch(e)
+        {
+          verify("has " + expected[i], false);
+        }
+      }
+    }
+  }
+
+  ]]></script>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/struct-dom-14-f-manual.svg b/svg/import/struct-dom-14-f-manual.svg
index 1f0bb86..6ecf6dc 100644
--- a/svg/import/struct-dom-14-f-manual.svg
+++ b/svg/import/struct-dom-14-f-manual.svg
@@ -30,8 +30,8 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test is passed if there is a green rect visible,
-        and there is a yellow or dark green circle in the middle. 
-        If there's any red visible the test has failed. 
+        and there is a yellow or dark green circle in the middle.
+        If there's any red visible the test has failed.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -57,11 +57,11 @@
         <![CDATA[
         function runTest()
         {
-          try 
+          try
           {
             var use = document.getElementById("use1");
             var instanceroot = use.instanceRoot;
-            
+
             if (instanceroot.firstChild.correspondingElement !== instanceroot.childNodes.item(0).correspondingElement)
               throw("failed");
             if (instanceroot.firstChild.nextSibling.correspondingElement !== instanceroot.childNodes.item(1).correspondingElement)
@@ -71,8 +71,8 @@
 
             // indicate test passed
             document.getElementById("status").setAttribute("fill", "lime");
-              
-            // This is an optional part of the test, which indicates whether SVGElementInstance(List) follows the normal DOM NodeList conventions and 
+
+            // This is an optional part of the test, which indicates whether SVGElementInstance(List) follows the normal DOM NodeList conventions and
             // whether the returned objects are actually the same objects as those that the SVGElementInstance traversal attributes return.
             if (instanceroot.firstChild !== instanceroot.childNodes.item(0))
               throw("failed");
@@ -80,8 +80,8 @@
               throw("failed");
             if (instanceroot.lastChild !== instanceroot.childNodes.item(instanceroot.childNodes.length-1))
               throw("failed");
-            
-            // indicate status of the optional part of the test 
+
+            // indicate status of the optional part of the test
             document.getElementById("optional").setAttribute("fill", "green");
           }
           catch(e)
diff --git a/svg/import/struct-dom-15-f-manual.svg b/svg/import/struct-dom-15-f-manual.svg
index b5329b6..76fe3b0 100644
--- a/svg/import/struct-dom-15-f-manual.svg
+++ b/svg/import/struct-dom-15-f-manual.svg
@@ -13,7 +13,7 @@
     version="$Revision: 1.3 $" testname="$RCSfile: struct-dom-15-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#InterfaceSVGElementInstance">
       <p>
-        Test SVGElementInstance and EventTarget.dispatchEvent. 
+        Test SVGElementInstance and EventTarget.dispatchEvent.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -53,7 +53,7 @@
             dc.setAttribute("fill", "lime");
           }
         }
-        
+
         // checks that event.target and event.currentTarget point to an SVGElementInstance (the instanceRoot)
         function verifyDefs2(e)
         {
@@ -64,7 +64,7 @@
             instance.correspondingElement.setAttribute("fill", "lime");
           }
         }
-                
+
         function verify(e)
         {
           var c = document.getElementById("circle");
@@ -74,17 +74,17 @@
             c.setAttribute("fill", "lime");
           }
         }
-        
+
         function dispatchMouseClick(target)
         {
            var mouseevent = document.createEvent("MouseEvents");
-           mouseevent.initMouseEvent("click", 
+           mouseevent.initMouseEvent("click",
                                       true,                 // canbubble
                                       true,                 // cancellable
                                       document.defaultView, // the AbstractView
                                       1,                    // mouseclick count
                                       0,                    // screen x coordinate
-                                      0,                    // screen y coordinate 
+                                      0,                    // screen y coordinate
                                       0,                    // client x coordinate
                                       0,                    // client y coordinate
                                       false,                // ctrlKey
@@ -95,21 +95,21 @@
                                       null);                // related target
            target.dispatchEvent(mouseevent);
         }
-        
+
         function runTest()
         {
           // test simple circle inside <defs>
-          try { 
+          try {
             var defsCircle = document.getElementById("defscircle1");
             dispatchMouseClick(defsCircle);
           } catch(e) {}
-          
+
           // test SVGElementInstance
           try {
             var elementInstance2 = document.getElementById("use2").instanceRoot;
             dispatchMouseClick(elementInstance2);
           } catch(e) {}
-          
+
           // test simple circle outside of <defs>
           try {
             var circle = document.getElementById("circle");
@@ -120,7 +120,7 @@
     </defs>
 
     <circle id="circle" cx="240" cy="180" r="25" onclick="verify(evt)" fill="red"/>
-    
+
     <use id="use1" xlink:href="#defscircle1" transform="translate(80,0)"/>
     <use id="use2" xlink:href="#defscircle2" transform="translate(-80,0)"/>
     <use id="use3" xlink:href="#defsg1"/>
diff --git a/svg/import/struct-dom-16-f-manual.svg b/svg/import/struct-dom-16-f-manual.svg
index 7747c72..daf2ed7 100644
--- a/svg/import/struct-dom-16-f-manual.svg
+++ b/svg/import/struct-dom-16-f-manual.svg
@@ -14,8 +14,8 @@
         The 'SVGSVGElement' interface allows for creation of references to various primitive SVG interface types with explicitly defined default values.
       </p>
       <p>
-        A reference to an 'SVGSVGElement' is obtained through an 'svg' element in the page's markup. Each of the 'CreateSVG*' methods is called from this 
-        reference and initial values are verified. A counter is used to determine whether all conditions are satisfied. The word 'fail' in red via an SVG 
+        A reference to an 'SVGSVGElement' is obtained through an 'svg' element in the page's markup. Each of the 'CreateSVG*' methods is called from this
+        reference and initial values are verified. A counter is used to determine whether all conditions are satisfied. The word 'fail' in red via an SVG
         'text' element is used to indicate failure and the word 'pass' in black is used to indicate passing.
       </p>
     </d:testDescription>
@@ -44,7 +44,7 @@
     <svg id="testSVG" />
     <text id="failText" x="350" y="350" font-size="50" fill="red">FAIL</text>
     <text id="passText" x="350" y="300" font-size="50" display="none">PASS</text>
-    <script type="text/javascript">    
+    <script type="text/javascript">
       <![CDATA[
         try
         {
@@ -76,7 +76,7 @@
             }
 
             var testSVGMatrix = testSVG.createSVGMatrix();
-            if (1 == testSVGMatrix.a && 0 == testSVGMatrix.b && 0 == testSVGMatrix.c  && 1 == testSVGMatrix.d && 0 == testSVGMatrix.e && 0 == testSVGMatrix.f) 
+            if (1 == testSVGMatrix.a && 0 == testSVGMatrix.b && 0 == testSVGMatrix.c  && 1 == testSVGMatrix.d && 0 == testSVGMatrix.e && 0 == testSVGMatrix.f)
             {
                 passCount++;
             }
@@ -108,7 +108,7 @@
         catch(ex)
         {
           alert("ERROR: " + ex.message);
-      
+
         }
         ]]>
 
diff --git a/svg/import/struct-dom-17-f-manual.svg b/svg/import/struct-dom-17-f-manual.svg
index 26121e7..3b82505 100644
--- a/svg/import/struct-dom-17-f-manual.svg
+++ b/svg/import/struct-dom-17-f-manual.svg
@@ -69,13 +69,13 @@
             // Test that "getElementById" on svgTree1 can't access children in other subtrees
             if (null == svgTree1.getElementById("subElem2"))
             {
-                passedCount++;    
+                passedCount++;
             }
 
             // Test that "getElementById" on svgTree1 can't access elements that are not its children
             if (null == svgTree1.getElementById("siblingElem1"))
             {
-                passedCount++;    
+                passedCount++;
             }
 
             if (4 == passedCount)
diff --git a/svg/import/struct-dom-18-f-manual.svg b/svg/import/struct-dom-18-f-manual.svg
index 27977a2..94e0ae3 100644
--- a/svg/import/struct-dom-18-f-manual.svg
+++ b/svg/import/struct-dom-18-f-manual.svg
@@ -22,7 +22,7 @@
         and 'height' attributes set to intersect each of the renderable elements. For 'getEnclosureList', the same 'SVGRect' is modified to
         completely enclose all of the renderable elements. For both 'getIntersectionList' and 'getEnclosureList', the returned 'NodeList' element
         is iterated through and each element is verified through their respective 'id' attributes. For the 'use' element, the 'correspondingUseElement'
-        attribute must be used because an 'SVGElementInstance' reference is obtained via 'getEnclosureList' and 'getIntersectionList'. 
+        attribute must be used because an 'SVGElementInstance' reference is obtained via 'getEnclosureList' and 'getIntersectionList'.
         ]]-->
       </p>
     </d:operatorScript>
diff --git a/svg/import/struct-dom-20-f-manual.svg b/svg/import/struct-dom-20-f-manual.svg
index 09018ae..a006c3b 100644
--- a/svg/import/struct-dom-20-f-manual.svg
+++ b/svg/import/struct-dom-20-f-manual.svg
@@ -13,7 +13,7 @@
     version="$Revision: 1.2 $" testname="$RCSfile: struct-dom-20-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#InterfaceSVGElementInstance">
       <p>
-        Test SVGElementInstance and EventTarget.dispatchEvent. 
+        Test SVGElementInstance and EventTarget.dispatchEvent.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -45,8 +45,8 @@
         <use id="defsuse1" xlink:href="#defscircle4" transform="translate(-600, 0)" onclick="verifyDefs4(evt)"/>
       </g>
       <circle id="defscircle4" cx="1000" cy="180" r="25" fill="red"/>
-      
-      <script><![CDATA[        
+
+      <script><![CDATA[
         function verifyDefs3(e)
         {
           var instance = document.getElementById("use3").instanceRoot;
@@ -58,7 +58,7 @@
             dc3instance.correspondingElement.setAttribute("fill", "lime");
           }
         }
-        
+
         function verifyDefs4(e)
         {
           var useElement = document.getElementById("use3");
@@ -73,7 +73,7 @@
             document.getElementById("defscircle4").setAttribute("fill", "lime");
           }
         }
-        
+
         function verify(e)
         {
           var c = document.getElementById("circle");
@@ -83,17 +83,17 @@
             c.setAttribute("fill", "lime");
           }
         }
-        
+
         function dispatchMouseClick(target)
         {
            var mouseevent = document.createEvent("MouseEvents");
-           mouseevent.initMouseEvent("click", 
+           mouseevent.initMouseEvent("click",
                                       true,                 // canbubble
                                       true,                 // cancellable
                                       document.defaultView, // the AbstractView
                                       1,                    // mouseclick count
                                       0,                    // screen x coordinate
-                                      0,                    // screen y coordinate 
+                                      0,                    // screen y coordinate
                                       0,                    // client x coordinate
                                       0,                    // client y coordinate
                                       false,                // ctrlKey
@@ -104,7 +104,7 @@
                                       null);                // related target
            target.dispatchEvent(mouseevent);
         }
-        
+
         function runTest()
         {
           // test bubbling click
@@ -112,7 +112,7 @@
             var instanceCircle = document.getElementById("use3").instanceRoot.firstChild.nextSibling;
             dispatchMouseClick(instanceCircle);
           } catch(e) {}
-          
+
           // test nested use with bubbling click
           try {
             var nestedUseInstance = document.getElementById("use3").instanceRoot.childNodes.item(3);
@@ -122,7 +122,7 @@
         }
       ]]></script>
     </defs>
-    
+
     <use id="use3" xlink:href="#defsg1"/>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
diff --git a/svg/import/struct-frag-01-t-manual.svg b/svg/import/struct-frag-01-t-manual.svg
index 6fe9bcc..d1defe4 100644
--- a/svg/import/struct-frag-01-t-manual.svg
+++ b/svg/import/struct-frag-01-t-manual.svg
@@ -15,7 +15,7 @@
     version="$Revision: 1.5 $" testname="$RCSfile: struct-frag-01-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#NewDocument">
         <p>
-         This is an empty SVG document.  
+         This is an empty SVG document.
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/struct-frag-03-t-manual.svg b/svg/import/struct-frag-03-t-manual.svg
index 9d73f8b..160265d 100644
--- a/svg/import/struct-frag-03-t-manual.svg
+++ b/svg/import/struct-frag-03-t-manual.svg
@@ -28,7 +28,7 @@
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
-      </p>      
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/struct-frag-04-t-manual.svg b/svg/import/struct-frag-04-t-manual.svg
index bd2fdc7..d85ee48 100644
--- a/svg/import/struct-frag-04-t-manual.svg
+++ b/svg/import/struct-frag-04-t-manual.svg
@@ -1,5 +1,5 @@
 <svg version="1.1" baseProfile="tiny" preserveAspectRatio="xMidYMid meet" id="svg-root"
-  width="100%" height="100%" x="1000" y="1000" 
+  width="100%" height="100%" x="1000" y="1000"
   xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  SVG 1.1 2nd Edition Test Case                                     =-->
@@ -16,16 +16,16 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#NewDocument">
         <p>
           This test validates the operation of the svg element when there is no
-          viewbox. 
+          viewbox.
         </p>
         <p>
-          The document has x/y attributes set to (1000, 1000). Because 
-          x/y are ignored on the root svg element, the x/y origin should have no 
+          The document has x/y attributes set to (1000, 1000). Because
+          x/y are ignored on the root svg element, the x/y origin should have no
           effect on the drawing.
         </p>
         <p>
           The document contains squares and circles between the
-          (100,100) and (200, 200) coordinates. 
+          (100,100) and (200, 200) coordinates.
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/struct-group-01-t-manual.svg b/svg/import/struct-group-01-t-manual.svg
index 3326f1d..42f0564 100644
--- a/svg/import/struct-group-01-t-manual.svg
+++ b/svg/import/struct-group-01-t-manual.svg
@@ -19,7 +19,7 @@
         All the g elements for this test are in the g element whose id=allGs.
       </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         The test uses the 'rect' element, as well as basic fill (solid primary colors),
         stroke (black 1-pixel lines), font-family and font-size properties.
diff --git a/svg/import/struct-image-02-b-manual.svg b/svg/import/struct-image-02-b-manual.svg
index 403fdef..2e4e2be 100644
--- a/svg/import/struct-image-02-b-manual.svg
+++ b/svg/import/struct-image-02-b-manual.svg
@@ -25,7 +25,7 @@
         S4 tests a switch statement, if there is not a green rectangle showing in
         S4 there is probably a problem processing a switch.
       </p>
-    	<p>
+      <p>
         The test uses the 'rect' element, as well as basic fill (solid primary colors),
         stroke (black 1-pixel lines), font-family and font-size properties.
       </p>
diff --git a/svg/import/struct-image-13-f-manual.svg b/svg/import/struct-image-13-f-manual.svg
index 880907a..bb7c306 100644
--- a/svg/import/struct-image-13-f-manual.svg
+++ b/svg/import/struct-image-13-f-manual.svg
@@ -16,9 +16,9 @@
         Tests that different PNG image types are correctly handled. These images are non-interlaced.
         </p>
         <p>
-        This test uses the 
+        This test uses the
         <a href="http://www.schaik.com/pngsuite/">PNG Group test suite</a>
-        created by Willem van Schaik.    
+        created by Willem van Schaik.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -43,68 +43,68 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-  
+
     <!-- something to show transparency -->
     <rect x="16" y="32" width="320" height="8" fill="#3C3"/>
     <rect x="16" y="48" width="320" height="8" fill="#9F9"/>
 
     <!-- 1 bit black and white -->
     <image xlink:href="../images/PngSuite/basn0g01.png" x="16" y="32" width="32" height="32"/>
-    
+
     <!-- 2 bit greyscale -->
     <image xlink:href="../images/PngSuite/basn0g02.png" x="64" y="32" width="32" height="32"/>
-    
+
     <!-- 4 bit greyscale -->
     <image xlink:href="../images/PngSuite/basn0g04.png" x="112" y="32" width="32" height="32"/>
-    
+
     <!-- 8 bit greyscale -->
     <image xlink:href="../images/PngSuite/basn0g08.png" x="160" y="32" width="32" height="32"/>
-    
+
     <!-- 16 bit greyscale -->
     <image xlink:href="../images/PngSuite/basn0g16.png" x="208" y="32" width="32" height="32"/>
-  
+
     <!-- 8 bit greyscale plus alpha -->
     <image xlink:href="../images/PngSuite/basn4a08.png" x="256" y="32" width="32" height="32"/>
-    
+
     <!-- 16 bit greyscale plus alpha -->
     <image xlink:href="../images/PngSuite/basn4a16.png" x="304" y="32" width="32" height="32"/>
-    
+
     <text x="16" y="100" font-size="16" fill="#3C3">Greyscale, various bit depths, two with alpha</text>
-    
+
     <!-- something to show transparency -->
     <rect x="16" y="132" width="368" height="8" fill="#9F9"/>
     <rect x="16" y="148" width="368" height="8" fill="#3C3"/>
 
     <!-- 8 bits RGB -->
     <image xlink:href="../images/PngSuite/basn2c08.png" x="16" y="132" width="32" height="32"/>
-    
+
     <!-- 16 bits RGB -->
     <image xlink:href="../images/PngSuite/basn2c16.png" x="64" y="132" width="32" height="32"/>
-    
+
     <!-- 1 bit indexed -->
     <image xlink:href="../images/PngSuite/basn3p01.png" x="112" y="132" width="32" height="32"/>
-    
+
     <!-- 2 bit indexed -->
     <image xlink:href="../images/PngSuite/basn3p02.png" x="160" y="132" width="32" height="32"/>
-    
+
     <!-- 4 bit indexed -->
     <image xlink:href="../images/PngSuite/basn3p04.png" x="208" y="132" width="32" height="32"/>
-  
+
     <!-- 8 bit indexed -->
     <image xlink:href="../images/PngSuite/basn3p08.png" x="256" y="132" width="32" height="32"/>
-    
+
     <!-- 8 bits RGB plus alpha -->
     <image xlink:href="../images/PngSuite/basn6a08.png" x="304" y="132" width="32" height="32"/>
-    
+
     <!-- 16 bits RGB plus alpha -->
     <image xlink:href="../images/PngSuite/basn6a16.png" x="352" y="132" width="32" height="32"/>
-    
+
     <text x="16" y="200" font-size="16" fill="#3C3">Truecolor and indexed, various bit depths, two with alpha</text>
-    
+
      <text x="16" y="260" font-size="24" fill="#3C3">Non-interlaced images</text>
-    
+
     <!--
-    <image xlink:href="../images/PngSuite/" x="" y="" width="32" height="32"/> 
+    <image xlink:href="../images/PngSuite/" x="" y="" width="32" height="32"/>
     -->
 
   </g>
diff --git a/svg/import/struct-image-14-f-manual.svg b/svg/import/struct-image-14-f-manual.svg
index f653164..4af742f 100644
--- a/svg/import/struct-image-14-f-manual.svg
+++ b/svg/import/struct-image-14-f-manual.svg
@@ -16,9 +16,9 @@
         Tests that different PNG image types are correctly handled. These images are interlaced.
         </p>
         <p>
-        This test uses the 
+        This test uses the
         <a href="http://www.schaik.com/pngsuite/">PNG Group test suite</a>
-        created by Willem van Schaik.    
+        created by Willem van Schaik.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -43,68 +43,68 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-  
+
     <!-- something to show transparency -->
     <rect x="16" y="32" width="320" height="8" fill="#3C3"/>
     <rect x="16" y="48" width="320" height="8" fill="#9F9"/>
 
     <!-- 1 bit black and white -->
     <image xlink:href="../images/PngSuite/basi0g01.png" x="16" y="32" width="32" height="32"/>
-    
+
     <!-- 2 bit greyscale -->
     <image xlink:href="../images/PngSuite/basi0g02.png" x="64" y="32" width="32" height="32"/>
-    
+
     <!-- 4 bit greyscale -->
     <image xlink:href="../images/PngSuite/basi0g04.png" x="112" y="32" width="32" height="32"/>
-    
+
     <!-- 8 bit greyscale -->
     <image xlink:href="../images/PngSuite/basi0g08.png" x="160" y="32" width="32" height="32"/>
-    
+
     <!-- 16 bit greyscale -->
     <image xlink:href="../images/PngSuite/basi0g16.png" x="208" y="32" width="32" height="32"/>
-  
+
     <!-- 8 bit greyscale plus alpha -->
     <image xlink:href="../images/PngSuite/basi4a08.png" x="256" y="32" width="32" height="32"/>
-    
+
     <!-- 16 bit greyscale plus alpha -->
     <image xlink:href="../images/PngSuite/basi4a16.png" x="304" y="32" width="32" height="32"/>
-    
+
     <text x="16" y="100" font-size="16" fill="#3C3">Greyscale, various bit depths, two with alpha</text>
-    
+
     <!-- something to show transparency -->
     <rect x="16" y="132" width="368" height="8" fill="#9F9"/>
     <rect x="16" y="148" width="368" height="8" fill="#3C3"/>
 
     <!-- 8 bits RGB -->
     <image xlink:href="../images/PngSuite/basi2c08.png" x="16" y="132" width="32" height="32"/>
-    
+
     <!-- 16 bits RGB -->
     <image xlink:href="../images/PngSuite/basi2c16.png" x="64" y="132" width="32" height="32"/>
-    
+
     <!-- 1 bit indexed -->
     <image xlink:href="../images/PngSuite/basi3p01.png" x="112" y="132" width="32" height="32"/>
-    
+
     <!-- 2 bit indexed -->
     <image xlink:href="../images/PngSuite/basi3p02.png" x="160" y="132" width="32" height="32"/>
-    
+
     <!-- 4 bit indexed -->
     <image xlink:href="../images/PngSuite/basi3p04.png" x="208" y="132" width="32" height="32"/>
-  
+
     <!-- 8 bit indexed -->
     <image xlink:href="../images/PngSuite/basi3p08.png" x="256" y="132" width="32" height="32"/>
-    
+
     <!-- 8 bits RGB plus alpha -->
     <image xlink:href="../images/PngSuite/basi6a08.png" x="304" y="132" width="32" height="32"/>
-    
+
     <!-- 16 bits RGB plus alpha -->
     <image xlink:href="../images/PngSuite/basi6a16.png" x="352" y="132" width="32" height="32"/>
-    
+
     <text x="16" y="200" font-size="16" fill="#3C3">Truecolor and indexed, various bit depths, two with alpha</text>
-    
+
         <text x="16" y="260" font-size="24" fill="#3C3">Interlaced images</text>
-    
+
     <!--
-    <image xlink:href="../images/PngSuite/" x="" y="" width="32" height="32"/> 
+    <image xlink:href="../images/PngSuite/" x="" y="" width="32" height="32"/>
     -->
 
   </g>
diff --git a/svg/import/struct-image-15-f-manual.svg b/svg/import/struct-image-15-f-manual.svg
index 6b406cf..6bbbd41 100644
--- a/svg/import/struct-image-15-f-manual.svg
+++ b/svg/import/struct-image-15-f-manual.svg
@@ -14,16 +14,16 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/[chapter]#[section]">
       <p>
         The first row tests that alpha PNG images are correctly displayed as part of an SVG image,
-        ignoring the background colour in the image which is only used to display the 
+        ignoring the background colour in the image which is only used to display the
         PNG image stand-alone.
         </p>
-        <p>The second row tests indexed PNG transparency (tRNs), again checking that 
+        <p>The second row tests indexed PNG transparency (tRNs), again checking that
         the background color is ignored when displayed as part of an SVG image.
         </p>
         <p>
-        This test uses the 
+        This test uses the
         <a href="http://www.schaik.com/pngsuite/">PNG Group test suite</a>
-        created by Willem van Schaik.    
+        created by Willem van Schaik.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
@@ -48,77 +48,77 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-  
+
     <!-- something to show transparency -->
     <rect x="16" y="32" width="368" height="8" fill="#3C3"/>
     <rect x="16" y="48" width="368" height="8" fill="#9F9"/>
 
     <!-- 8 bit grayscale, alpha, no background chunk, interlaced  -->
     <image xlink:href="../images/PngSuite/bgai4a08.png" x="16" y="32" width="32" height="32"/>
-    
+
     <!-- 16 bit grayscale, alpha, no background chunk, interlaced  -->
     <image xlink:href="../images/PngSuite/bgai4a16.png" x="64" y="32" width="32" height="32"/>
-    
+
     <!-- 8 bits rgb color, alpha, no background chunk  -->
     <image xlink:href="../images/PngSuite/bgan6a08.png" x="112" y="32" width="32" height="32"/>
-    
+
     <!-- 16 bits rgb color, alpha, no background chunk  -->
     <image xlink:href="../images/PngSuite/bgan6a16.png" x="160" y="32" width="32" height="32"/>
-    
+
     <!-- 8 bit grayscale, alpha, black background chunk -->
     <image xlink:href="../images/PngSuite/bgbn4a08.png" x="208" y="32" width="32" height="32"/>
-  
+
     <!-- 16 bit grayscale, alpha, gray background chunk  -->
     <image xlink:href="../images/PngSuite/bggn4a16.png" x="256" y="32" width="32" height="32"/>
-    
+
     <!-- 8 bits rgb color, alpha, white background chunk  -->
     <image xlink:href="../images/PngSuite/bgwn6a08.png" x="304" y="32" width="32" height="32"/>
-    
+
     <!-- 16 bits rgb color, alpha, yellow background chunk  -->
     <image xlink:href="../images/PngSuite/bgyn6a16.png" x="352" y="32" width="32" height="32"/>
-    
+
     <text x="16" y="100" font-size="16" fill="#3C3">Background color, with alpha</text>
-    
+
     <!-- something to show transparency -->
     <rect x="16" y="132" width="464" height="8" fill="#9F9"/>
     <rect x="16" y="148" width="464" height="8" fill="#3C3"/>
 
     <!-- transparent, black background chunk  -->
     <image xlink:href="../images/PngSuite/tbbn1g04.png" x="16" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, blue background chunk  -->
     <image xlink:href="../images/PngSuite/tbbn2c16.png" x="64" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, black background chunk  -->
     <image xlink:href="../images/PngSuite/tbbn3p08.png" x="112" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, green background chunk  -->
     <image xlink:href="../images/PngSuite/tbgn2c16.png" x="160" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, light-gray background chunk  -->
     <image xlink:href="../images/PngSuite/tbgn3p08.png" x="208" y="132" width="32" height="32"/>
-  
+
     <!-- transparent, red background chunk  -->
     <image xlink:href="../images/PngSuite/tbrn2c08.png" x="256" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, white background chunk  -->
     <image xlink:href="../images/PngSuite/tbwn1g16.png" x="304" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, white background chunk  -->
     <image xlink:href="../images/PngSuite/tbwn3p08.png" x="352" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, yellow background chunk -->
     <image xlink:href="../images/PngSuite/tbyn3p08.png" x="400" y="132" width="32" height="32"/>
-    
+
     <!-- transparent, but no background chunk -->
     <image xlink:href="../images/PngSuite/tp1n3p08.png" x="448" y="132" width="32" height="32"/>
-    
+
     <text x="16" y="200" font-size="16" fill="#3C3">Background color, with transparency</text>
-    
+
         <text x="16" y="260" font-size="24" fill="#3C3">Handling bKGd and tRNs</text>
-    
+
     <!--
-    <image xlink:href="../images/PngSuite/" x="" y="" width="32" height="32"/> 
+    <image xlink:href="../images/PngSuite/" x="" y="" width="32" height="32"/>
     -->
 
   </g>
diff --git a/svg/import/struct-image-18-f-manual.svg b/svg/import/struct-image-18-f-manual.svg
index 2e5f99c..5ed8c4c 100644
--- a/svg/import/struct-image-18-f-manual.svg
+++ b/svg/import/struct-image-18-f-manual.svg
@@ -16,7 +16,7 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#ImageElement">
       <p>
         Test referencing an svg from an 'image' element, where the referenced
-        svg has no viewBox and a larger width and height than the 'image' 
+        svg has no viewBox and a larger width and height than the 'image'
         element viewport.
       </p>
     </d:testDescription>
@@ -27,7 +27,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test is passed if a green quarter circle with black stroke is displayed, and no red.  
+        The test is passed if a green quarter circle with black stroke is displayed, and no red.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/struct-image-19-f-manual.svg b/svg/import/struct-image-19-f-manual.svg
index 9a52ccd..186efb8 100644
--- a/svg/import/struct-image-19-f-manual.svg
+++ b/svg/import/struct-image-19-f-manual.svg
@@ -16,7 +16,7 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#ImageElement">
       <p>
         Test referencing an svg from an 'image' element, where the referenced
-        svg has a viewBox and a larger width and height than the 'image' 
+        svg has a viewBox and a larger width and height than the 'image'
         element viewport.
       </p>
       <p>
diff --git a/svg/import/struct-svg-01-f-manual.svg b/svg/import/struct-svg-01-f-manual.svg
index efa06ed..eebe868 100644
--- a/svg/import/struct-svg-01-f-manual.svg
+++ b/svg/import/struct-svg-01-f-manual.svg
@@ -96,7 +96,7 @@
 
             if(pass)
             {
-                document.getElementById("failText").setAttribute("display", "none");    
+                document.getElementById("failText").setAttribute("display", "none");
             }
         }
         catch(ex)
@@ -120,4 +120,3 @@
   </g>
   -->
 </svg>
-
diff --git a/svg/import/struct-svg-02-f-manual.svg b/svg/import/struct-svg-02-f-manual.svg
index 5fff676..f396997 100644
--- a/svg/import/struct-svg-02-f-manual.svg
+++ b/svg/import/struct-svg-02-f-manual.svg
@@ -40,19 +40,19 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <script type="text/ecmascript" xlink:href="../resources/testharness.js"/>
-    
+
     <!-- The testroot is here only to get a predictable 480x360 viewport -->
     <svg id="testroot" width="480" height="360">
-	    <svg id="testSVG1" />
-	    <svg id="testSVG2" />
-	    <svg id="subSVG" width="300" height="175"/>
-		</svg>
+      <svg id="testSVG1" />
+      <svg id="testSVG2" />
+      <svg id="subSVG" width="300" height="175"/>
+    </svg>
 
     <g id="testoutput"/>
-    
+
     <!-- Cover the whole testframe with a simple visual result -->
     <rect id="status" width="100%" height="100%" fill="none"/>
-    
+
     <script type="text/javascript"><![CDATA[
       var passes = 0;
       var ypos = 50;
@@ -62,7 +62,7 @@
         var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
         var r = document.createElementNS("http://www.w3.org/2000/svg", "rect");
         var t = document.createElementNS("http://www.w3.org/2000/svg", "text");
-        var tc = document.createTextNode(str); 
+        var tc = document.createTextNode(str);
         t.appendChild(tc);
         t.setAttribute("class", "result");
         t.setAttribute("font-family", "monospace");
@@ -80,7 +80,7 @@
         g.setAttribute("transform", "translate(10 " + ypos + ")");
         ypos += 10;
         document.getElementById("testoutput").appendChild(g);
-        
+
         if (result)
           passes++;
       }
@@ -90,29 +90,29 @@
           var testSVG = document.getElementById("testSVG1");
           var subSVG = document.getElementById("subSVG");
           var status = document.getElementById("status");
-          
+
           var base = testSVG.width.baseVal;
-          
+
           verify("value: " + base.value, base.value == 480);
           test(function() {assert_equals(base.value, 480)}, "Assert that the width baseVal is 100% computed to user units.");
 
           verify("valueInSpecifiedUnits: " + base.valueInSpecifiedUnits, base.valueInSpecifiedUnits == 100);
           test(function() {assert_equals(base.value, 100)}, "Assert that the default width baseVal is 100.");
-          
+
           // set the value in user units and then read it back
           base.value = 240;
           verify("value: " + base.value, base.value == 240);
           test(function() {assert_equals(base.value, 240)}, "Assert that the width baseVal is 240 after setting it.");
           verify("valueInSpecifiedUnits: " + base.valueInSpecifiedUnits, base.valueInSpecifiedUnits == 50);
           test(function() {assert_equals(base.valueInSpecifiedUnits, 50)}, "Assert that the value in specified units is now 50.");
-          
+
           // move the svg to be child of another viewport and read values again
           subSVG.appendChild(testSVG);
           verify("value: " + base.value, base.value == 150);
           test(function() {assert_equals(base.value, 150)}, "Assert that the value changes to 150 after being moved to a new viewport.");
           verify("valueInSpecifiedUnits: " + base.valueInSpecifiedUnits, base.valueInSpecifiedUnits == 50);
           test(function() {assert_equals(base.valueInSpecifiedUnits, 50)}, "Assert that the value in specified units is still 50 after being moved to a new viewport.");
-          
+
           // move an svg that hasn't been modified to another viewport and read values
           testSVG = document.getElementById("testSVG2");
           subSVG.appendChild(testSVG);
@@ -123,7 +123,7 @@
       catch(ex)
       {
       }
-      
+
       status.setAttribute("fill", passes == 7 ? "lime" : "red");
       test(function() {assert_equals(passes,7)}, "Assert that all subtests passed.");
   ]]></script>
diff --git a/svg/import/struct-svg-03-f-manual.svg b/svg/import/struct-svg-03-f-manual.svg
index 0b03bb4..aa1705c 100644
--- a/svg/import/struct-svg-03-f-manual.svg
+++ b/svg/import/struct-svg-03-f-manual.svg
@@ -45,7 +45,7 @@
     <svg width="100" height="100" viewBox="0 0 50 50">
       <rect width="50" height="50" fill="lime"/>
     </svg>
-    
+
     <g transform="translate(120 0)">
       <rect x="0.5" y="0.5" width="99" height="99" fill="red"/>
       <svg width="100" height="100" viewBox="0 0 50 50">
diff --git a/svg/import/struct-use-01-t-manual.svg b/svg/import/struct-use-01-t-manual.svg
index 0317230..92e5175 100644
--- a/svg/import/struct-use-01-t-manual.svg
+++ b/svg/import/struct-use-01-t-manual.svg
@@ -43,12 +43,12 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	    <defs>
-	    	<font-face font-family="FreeSerif" unicode-range="U+0020-U+00FF">
-	      <font-face-src>
-	        <font-face-uri xlink:href="../resources/FreeSerifBoldItalic.svg#FreeSerifBoldItalic"/>
-	      </font-face-src>
-	    </font-face>
+      <defs>
+        <font-face font-family="FreeSerif" unicode-range="U+0020-U+00FF">
+        <font-face-src>
+          <font-face-uri xlink:href="../resources/FreeSerifBoldItalic.svg#FreeSerifBoldItalic"/>
+        </font-face-src>
+      </font-face>
       <g fill="red" stroke="yellow" stroke-width="3">
         <rect id="usedRect" width="20" height="20"/>
         <circle id="usedCircle" cx="10" cy="10" r="10"/>
@@ -67,7 +67,6 @@
       </g>
     </defs>
 
-
     <g>
       <g id="labels" transform="translate(130, 40)" font-size="20" text-anchor="end">
         <text>&lt;rect&gt;</text>
diff --git a/svg/import/struct-use-05-b-manual.svg b/svg/import/struct-use-05-b-manual.svg
index 804a061..c58bf61 100644
--- a/svg/import/struct-use-05-b-manual.svg
+++ b/svg/import/struct-use-05-b-manual.svg
@@ -28,7 +28,7 @@
         steps requied to run the test or any manual operation that need
         to be performed to run the test.
         ]]
-      </p>      
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/struct-use-06-b-manual.svg b/svg/import/struct-use-06-b-manual.svg
index 958eabc..57c80a8 100644
--- a/svg/import/struct-use-06-b-manual.svg
+++ b/svg/import/struct-use-06-b-manual.svg
@@ -16,9 +16,9 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#UseElement">
         <p>
           Test onlick handlers in externally referenced content.
-          
+
           There are two 'use' elements, each of them is referencing an external file showing a rectangle.
-          
+
           The rect elements in the external file have onclick attributes, and the handler will attempt to change the fill of the
           referenced rect element to red.
         </p>
@@ -43,11 +43,11 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
+
     <!-- if external use isn't implemented, show red rects -->
     <rect x="40.5" y="120" width="119" height="119" fill="red"/>
     <rect x="180.5" y="120" width="119" height="119" fill="red"/>
-    
+
     <use id="use-elm1" x="40" y="120" width="122" height="122" xlink:href="../images/struct-use-06-b-1.svg#rect1"/>
     <use id="use-elm2" x="180" y="120" width="122" height="122" xlink:href="../images/struct-use-06-b-1.svg#svg-root"/>
   </g>
diff --git a/svg/import/struct-use-07-b-manual.svg b/svg/import/struct-use-07-b-manual.svg
index eb1018a..bfa3f1b 100644
--- a/svg/import/struct-use-07-b-manual.svg
+++ b/svg/import/struct-use-07-b-manual.svg
@@ -16,8 +16,8 @@
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/struct.html#UseElement">
       <p>
         This tests interactivity and event handlers on use elements. It also tests
-        that the <code>SVGElementInstance.correspondingElement</code> property and the 
-        <code>CSSStyleDeclaration.setProperty()</code> method defined in 
+        that the <code>SVGElementInstance.correspondingElement</code> property and the
+        <code>CSSStyleDeclaration.setProperty()</code> method defined in
         DOM Level 2 Style. By testing different ways of setting the fill on a rectangle
         it verifies that the result is consistent, and that CSS properly overrides
         the specified values.
@@ -34,7 +34,7 @@
         If the useragent doesn't support CSS, this test does not apply.
       </p>
       <p>
-        The test has passed if when clicking each of the rects the clicked rect turns blue - 
+        The test has passed if when clicking each of the rects the clicked rect turns blue -
         note that only the clicked rect must turn blue, if any other rect turns blue too then the test has failed.
       </p>
       <p>
diff --git a/svg/import/struct-use-10-f-manual.svg b/svg/import/struct-use-10-f-manual.svg
index ebadc03..b956cbd 100644
--- a/svg/import/struct-use-10-f-manual.svg
+++ b/svg/import/struct-use-10-f-manual.svg
@@ -17,12 +17,12 @@
       <p>
         Properties are inherited according to the 'use' element rules, CSS selectors only apply to the original elements
         and not the (conceptually) cloned DOM tree.
-      </p>      
+      </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-    	<p>
-    		Run the test. No interaction required.
-    	</p>
+      <p>
+        Run the test. No interaction required.
+      </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/struct-use-11-f-manual.svg b/svg/import/struct-use-11-f-manual.svg
index 56afadd..236494a 100644
--- a/svg/import/struct-use-11-f-manual.svg
+++ b/svg/import/struct-use-11-f-manual.svg
@@ -89,7 +89,7 @@
         {
             fill: blue;
         }
-	.test12 {
+  .test12 {
             fill: blue
          }
         .twochildren:first-child
@@ -105,10 +105,10 @@
             <circle cx="50" cy="50" r="48" id="testDescendant" class="test4" />
         </g>
         <g class="child">
-            <circle cx="50" cy="50" r="48" id="testChild" class="test5" />    
+            <circle cx="50" cy="50" r="48" id="testChild" class="test5" />
         </g>
         <g>
-            <circle cx="50" cy="50" r="48" id="testFirstChild" class="test6" /> 
+            <circle cx="50" cy="50" r="48" id="testFirstChild" class="test6" />
         </g>
         <g class="sibling"></g>
         <circle cx="50" cy="50" r="48" id="testSibling" class="test7" />
@@ -117,8 +117,8 @@
         <circle cx="50" cy="50" r="48" id="testAttributeWithSpaceSeparatedValueList" class="test10" foo="warning1 warning2" />
         <circle cx="50" cy="50" r="48" id="testAttributeWithHyphenSeparatedValueList" class="test11" lang="en-us" />
         <g class="twochildren">
-	    <circle cx="50" cy="50" r="48" id="notFirstChild" display="none" />
-            <circle cx="50" cy="50" r="48" id="testFirstChild2" class="test12" /> 
+      <circle cx="50" cy="50" r="48" id="notFirstChild" display="none" />
+            <circle cx="50" cy="50" r="48" id="testFirstChild2" class="test12" />
         </g>
     </defs>
     <use xlink:href="#testId" x="0" y="0" />
@@ -148,4 +148,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/struct-use-12-f-manual.svg b/svg/import/struct-use-12-f-manual.svg
index 01660cc..8c8d8e0 100644
--- a/svg/import/struct-use-12-f-manual.svg
+++ b/svg/import/struct-use-12-f-manual.svg
@@ -16,12 +16,12 @@
         Tests that recursive 'use' instances do not block rendering.
       </p>
       <p>
-        Various scenarios that directly and indirectly create circular references via the 'use' tag. A 'g' element is used when 
-        structural elements are necessary. None of the 'use' scenarios render anything. 'useLongCycle' tests a chain of recursive 
-        'use' instances that eventually cycles back to the first element. In 'useNested' 'use' elements are nested, with the child 
-        referring to the parent. In 'useNestedGroup' a 'use' instance references a parent 'g' element. In 'useIndirectNestedGroup' 
-        a 'use' instance indirectly references its own parent 'g'. In 'useMultipleIndirectNestedGroup', two 'use' instances 
-        reference their parent 'g' elements, and additional 'use' instances refer to these self-referencing 'use' elements. A green 
+        Various scenarios that directly and indirectly create circular references via the 'use' tag. A 'g' element is used when
+        structural elements are necessary. None of the 'use' scenarios render anything. 'useLongCycle' tests a chain of recursive
+        'use' instances that eventually cycles back to the first element. In 'useNested' 'use' elements are nested, with the child
+        referring to the parent. In 'useNestedGroup' a 'use' instance references a parent 'g' element. In 'useIndirectNestedGroup'
+        a 'use' instance indirectly references its own parent 'g'. In 'useMultipleIndirectNestedGroup', two 'use' instances
+        reference their parent 'g' elements, and additional 'use' instances refer to these self-referencing 'use' elements. A green
         'rect' is used to verify that rendering was processed up to that point.
       </p>
     </d:testDescription>
@@ -101,4 +101,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/struct-use-14-f-manual.svg b/svg/import/struct-use-14-f-manual.svg
index 597de79..950e35d 100644
--- a/svg/import/struct-use-14-f-manual.svg
+++ b/svg/import/struct-use-14-f-manual.svg
@@ -19,7 +19,7 @@
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        Run the test. No interaction required. 
+        Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/struct-use-15-f-manual.svg b/svg/import/struct-use-15-f-manual.svg
index 90a79d7..5cab3b8 100644
--- a/svg/import/struct-use-15-f-manual.svg
+++ b/svg/import/struct-use-15-f-manual.svg
@@ -60,7 +60,7 @@
         }
         catch(ex)
         {
-            alert("ERROR: " + ex.message);   
+            alert("ERROR: " + ex.message);
         }]]>
     </script>
   </g>
diff --git a/svg/import/styling-class-01-f-manual.svg b/svg/import/styling-class-01-f-manual.svg
index 0eed402..a4d2023 100644
--- a/svg/import/styling-class-01-f-manual.svg
+++ b/svg/import/styling-class-01-f-manual.svg
@@ -72,4 +72,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/styling-css-01-b-manual.svg b/svg/import/styling-css-01-b-manual.svg
index c1e629e..43af6eb 100644
--- a/svg/import/styling-css-01-b-manual.svg
+++ b/svg/import/styling-css-01-b-manual.svg
@@ -18,7 +18,7 @@
           Test element and class selectors.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
 Run the test. No interaction required.
       </p>
diff --git a/svg/import/styling-css-02-b-manual.svg b/svg/import/styling-css-02-b-manual.svg
index 87b5fba..bae161d 100644
--- a/svg/import/styling-css-02-b-manual.svg
+++ b/svg/import/styling-css-02-b-manual.svg
@@ -18,7 +18,7 @@
           Test ID and attribute selectors
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>Run the test. No interaction required.
       </p>
     </d:operatorScript>
diff --git a/svg/import/styling-css-03-b-manual.svg b/svg/import/styling-css-03-b-manual.svg
index 7a830bd..e14ddbb 100644
--- a/svg/import/styling-css-03-b-manual.svg
+++ b/svg/import/styling-css-03-b-manual.svg
@@ -18,7 +18,7 @@
           Test ancestor, child and sibling selectors.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
       </p>
@@ -40,7 +40,7 @@
         this makes the rectangle in the second test green. The first-child pseudoclass matches
         circles which are the first child of elements of class 'mummy' and if correctly
         selected this circle will be filled green. (The circle in the upper test is not
-        the first child). 
+        the first child).
         -->
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: styling-css-03-b.svg,v $</title>
diff --git a/svg/import/styling-css-04-f-manual.svg b/svg/import/styling-css-04-f-manual.svg
index 90025a2..a04ee73 100644
--- a/svg/import/styling-css-04-f-manual.svg
+++ b/svg/import/styling-css-04-f-manual.svg
@@ -26,7 +26,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      The test is passed if a grid of 6x3 squares is shown, the colors in each column 
+      The test is passed if a grid of 6x3 squares is shown, the colors in each column
       are the same and are those of the reference image (blue, green, orange, gold, purple and silver)
       </p>
       <p>
diff --git a/svg/import/styling-css-06-b-manual.svg b/svg/import/styling-css-06-b-manual.svg
index a315145..c7e44ca 100644
--- a/svg/import/styling-css-06-b-manual.svg
+++ b/svg/import/styling-css-06-b-manual.svg
@@ -72,7 +72,7 @@
       :hover {fill: rgb(255, 140, 0) }
       rect:hover {fill: none }
       text#revision:hover {fill: black}
-      :focus { fill:  	rgb( 0, 255, 127); stroke:  rgb( 0, 255, 127); stroke-width:1.5px }
+      :focus { fill:    rgb( 0, 255, 127); stroke:  rgb( 0, 255, 127); stroke-width:1.5px }
     </style>
     <defs>
       <font horiz-adv-x="481">
diff --git a/svg/import/styling-css-07-f-manual.svg b/svg/import/styling-css-07-f-manual.svg
index f036f6d..551cec3 100644
--- a/svg/import/styling-css-07-f-manual.svg
+++ b/svg/import/styling-css-07-f-manual.svg
@@ -59,4 +59,4 @@
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>
   -->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/styling-css-08-f-manual.svg b/svg/import/styling-css-08-f-manual.svg
index 8f18acd..56493f1 100644
--- a/svg/import/styling-css-08-f-manual.svg
+++ b/svg/import/styling-css-08-f-manual.svg
@@ -92,7 +92,7 @@
     </g>
     <circle class="reference" cx="300" cy="80" r="35" fill="lime" visibility="hidden"/>
     <circle id="testAttributeSelector" cx="300" cy="80" r="35" fill="red" />
-    
+
     <circle class="reference" cx="400" cy="140" r="50" fill="lime" visibility="hidden"/>
     <circle id="testIdSelector" cx="400" cy="140" r="50" fill="red" />
 
diff --git a/svg/import/styling-css-09-f-manual.svg b/svg/import/styling-css-09-f-manual.svg
index f4be4a1..f4b229c 100644
--- a/svg/import/styling-css-09-f-manual.svg
+++ b/svg/import/styling-css-09-f-manual.svg
@@ -17,7 +17,7 @@
       </p>
       <p>
         For each of a representative sampling of selectors, a 'visibility: hidden' style rule is specified
-        to match a corresponding element in the markup. Identically shaped and sized elements (but which are not 
+        to match a corresponding element in the markup. Identically shaped and sized elements (but which are not
         applicable to any of the style selectors) are placed beneath them and should be visible
         if the style sheet was applied correctly.
       </p>
diff --git a/svg/import/styling-css-10-f-manual.svg b/svg/import/styling-css-10-f-manual.svg
index 66884a2..7018ab1 100644
--- a/svg/import/styling-css-10-f-manual.svg
+++ b/svg/import/styling-css-10-f-manual.svg
@@ -13,12 +13,12 @@
     version="$Revision: 1.3 $" testname="$RCSfile: styling-css-10-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG/styling.html#CaseSensitivity">
       <p>
-        Checks that stylesheets (style attributes, style elements, 
+        Checks that stylesheets (style attributes, style elements,
 external style sheets) are case-insensitive, unlike presentational attributes. </p>
 
 <p>Subtest a checks that the invalid attribute
-FiLl is ignored. Subtest b checks that the style attribute is 
-applied, the values being case-insensitive. Subtests c and d check 
+FiLl is ignored. Subtest b checks that the style attribute is
+applied, the values being case-insensitive. Subtests c and d check
 the same for style elements and imported external style sheets.
       </p>
     </d:testDescription>
@@ -30,7 +30,7 @@
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
         If any red shows, the test fails. If four orange circles are shown,
-        the test passes and the user agent supports CSS style sheets. If 
+        the test passes and the user agent supports CSS style sheets. If
         the top two circles are orange while the bottom two are blue, and the user agent does
         not claim to support CSS style sheets, the test also passes.
       </p>
diff --git a/svg/import/styling-elem-01-b-manual.svg b/svg/import/styling-elem-01-b-manual.svg
index 718b231..b6ece4e 100644
--- a/svg/import/styling-elem-01-b-manual.svg
+++ b/svg/import/styling-elem-01-b-manual.svg
@@ -17,15 +17,15 @@
         honored.
       </p>
       <p>
-	      Once the test is loaded, two rectangles are presented,
-	      the upper indicating the result of a sub-test that checks
-	      whether the 'type' attribute on a 'style' element correctly
-	      defaults to "text/css", and the lower indicating the result
-	      of a sub-test that checks whether a bogus value for 'type'
-	      does not cause the 'style' element contents to be interpreted
-	      as CSS.  Each rectangle is green if the sub-test is passed
-	      or red if it failed.
-    	</p>
+        Once the test is loaded, two rectangles are presented,
+        the upper indicating the result of a sub-test that checks
+        whether the 'type' attribute on a 'style' element correctly
+        defaults to "text/css", and the lower indicating the result
+        of a sub-test that checks whether a bogus value for 'type'
+        does not cause the 'style' element contents to be interpreted
+        as CSS.  Each rectangle is green if the sub-test is passed
+        or red if it failed.
+      </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
diff --git a/svg/import/styling-pres-01-t-manual.svg b/svg/import/styling-pres-01-t-manual.svg
index 72ee060..2fac3ab 100644
--- a/svg/import/styling-pres-01-t-manual.svg
+++ b/svg/import/styling-pres-01-t-manual.svg
@@ -15,7 +15,7 @@
     version="$Revision: 1.7 $" testname="$RCSfile: styling-pres-01-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/styling.html#UsingPresentationAttributes">
       <p>
-				Tests that !important in a presentation attribute is an unsupported value
+        Tests that !important in a presentation attribute is an unsupported value
       </p>
       <p>
         A fill attribute is set to red with !important. This is an unsupported attribute value,
@@ -30,7 +30,7 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-				The rectangle should be filled with black, with a lime green border.
+        The rectangle should be filled with black, with a lime green border.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/styling-pres-02-f-manual.svg b/svg/import/styling-pres-02-f-manual.svg
index 3e116fd..40d81b8 100644
--- a/svg/import/styling-pres-02-f-manual.svg
+++ b/svg/import/styling-pres-02-f-manual.svg
@@ -64,7 +64,7 @@
     <text x='10' y='20'>Testing inapplicable presentation attributes</text>
 
     <g visibility='hidden'>
-    	<path id="path" d="M0,0"/>
+      <path id="path" d="M0,0"/>
       <image id='image' xlink:href='../images/20x20.png' width='10' height='10' fill='rgb(0, 0, 255)'/>
       <rect id='rect' width='10' height='10' font-size='123px'/>
       <circle id='circle' r='10' font-style='italic'/>
@@ -129,7 +129,7 @@
         }
         return hex;
       }
-  
+
       function checkSVGColor(e, p, r, g, b) {
         var v;
         try {
@@ -190,7 +190,7 @@
       report(5, checkSVGColor($('text'), 'stop-color', 0, 255, 0));
       report(6, checkIdent($('ellipse'), 'font-variant', 'small-caps'));
       report(7, checkPx($('polyline'), 'letter-spacing', '20px'));
-      
+
       report(11, checkSVGColor($('tref'), 'flood-color', 255, 0, 0));
       report(12, checkFloat($('textPath'), 'flood-opacity', 0.25));
       report(13, checkIdent($('altGlyph'), 'color-interpolation-filters', 'auto'));
diff --git a/svg/import/styling-pres-04-f-manual.svg b/svg/import/styling-pres-04-f-manual.svg
index 8ef604d..6e441af 100644
--- a/svg/import/styling-pres-04-f-manual.svg
+++ b/svg/import/styling-pres-04-f-manual.svg
@@ -82,7 +82,7 @@
         <circle id="testAdjacentSiblingSelector" cx="100" cy="300" r="40" fill="red" />
     </g>
     <circle id="testAttributeSelector" cx="300" cy="80" r="35" fill="red" />
-    <circle id="testIdSelector" cx="400" cy="140" r="50" fill="red" /> 
+    <circle id="testIdSelector" cx="400" cy="140" r="50" fill="red" />
     <g>
         <rect id="testPseudoClassSelector" x="270" y="240" width="150" height="80" fill="red" />
     </g>
diff --git a/svg/import/styling-pres-05-f-manual.svg b/svg/import/styling-pres-05-f-manual.svg
index 38e1c42..78af2d4 100644
--- a/svg/import/styling-pres-05-f-manual.svg
+++ b/svg/import/styling-pres-05-f-manual.svg
@@ -55,7 +55,7 @@
         <circle id="testAdjacentSiblingSelector" cx="100" cy="300" r="40" fill="red" />
     </g>
     <circle id="testAttributeSelector" cx="300" cy="80" r="35" fill="red" />
-    <circle id="testIdSelector" cx="400" cy="140" r="50" fill="red" /> 
+    <circle id="testIdSelector" cx="400" cy="140" r="50" fill="red" />
     <g>
         <rect id="testPseudoClassSelector" x="270" y="240" width="150" height="80" fill="red" />
     </g>
diff --git a/svg/import/svgdom-over-01-f-manual.svg b/svg/import/svgdom-over-01-f-manual.svg
index bf03243..681a13a 100644
--- a/svg/import/svgdom-over-01-f-manual.svg
+++ b/svg/import/svgdom-over-01-f-manual.svg
@@ -13,21 +13,21 @@
     version="$Revision: 1.9 $" testname="$RCSfile: svgdom-over-01-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/svgdom.html#SVGDOMOverview">
       <p>
-      	This tests how unspecified attributes affect the return values from the
-      	SVG DOM methods related to attributes.
+        This tests how unspecified attributes affect the return values from the
+        SVG DOM methods related to attributes.
       </p>
       <p>
-      	After loading the test, you should see a list of red or green rectangles followed by some text describing each subtest.
+        After loading the test, you should see a list of red or green rectangles followed by some text describing each subtest.
       </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
-			<p>
+      <p>
         Run the test. No interaction required.
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-      	The test has passed if there is a line of text saying "Test status: PASSED", and there is a green rectangle to the left of that text.
+        The test has passed if there is a line of text saying "Test status: PASSED", and there is a green rectangle to the left of that text.
       </p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -55,7 +55,7 @@
       <filter id="f"/>
       <polygon id="p"/>
     </defs>
-    
+
     <script><![CDATA[
       var ypos = 50;
       var r = document.getElementById("r");
@@ -77,7 +77,7 @@
         this.str = str;
         this.code = code ? code : str;
       }
-      
+
       Subtest.prototype.getStr = function()
       {
         return this.str;
@@ -87,7 +87,7 @@
       {
         return this.code;
       }
-      
+
       function verify(str, result)
       {
         var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
@@ -109,82 +109,82 @@
         ypos += 10;
         document.getElementById("test-body-content").appendChild(g);
         if(result)
-        	passes++;	
+          passes++;
       }
-      
+
       var passes = 0;
-      var tests = [ 
+      var tests = [
         // test SVGLength
         new Subtest("r.x.baseVal.value == 0"),
         new Subtest("r.x.baseVal.unitType == 1 (SVG_LENGTHTYPE_NUMBER)", "r.x.baseVal.unitType == 1"),
         new Subtest("v.width.baseVal.valueInSpecifiedUnits == 100"),
         new Subtest("v.width.baseVal.unitType == 2 (SVG_LENGTHTYPE_PERCENTAGE)", "v.width.baseVal.unitType == 2"),
         new Subtest("tt.textLength.baseVal.value == tt.getComputedTextLength()"),
-        
+
         // test SVGAnimatedNumber
         new Subtest("s.offset.baseVal == 0"),
-        
+
         // test SVGAnimatedBoolean
         new Subtest("c.preserveAlpha.baseVal == false"),
-        
+
         // test SVGAnimatedString / DOMString
         new Subtest("c.className.baseVal == \"\""),
         new Subtest("i.href.baseVal == \"\""),
-        
+
         // test SVGStringList
         new Subtest("i.requiredFeatures.numberOfItems == 0"),
-      
+
         // SVGAnimatedNumberList
         new Subtest("c.kernelMatrix.baseVal.numberOfItems == 0"),
 
         // SVGAnimatedLengthList
         new Subtest("t.x.baseVal.numberOfItems == 0"),
-        
+
         // SVGAnimatedPointsList / SVGPointList
         new Subtest("p.points.numberOfItems == 0"),
-      
+
         // SVGAnimatedEnumeration
         new Subtest("g.gradientUnits.baseVal == 2 (SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)", "g.gradientUnits.baseVal == 2"),
 
         // SVGAnimatedInteger
         new Subtest("f.filterResX.baseVal == 0"),
-        
+
         // SVGAnimatedAngle
         new Subtest("m.orientAngle.baseVal.value == 0"),
         new Subtest("m.orientAngle.baseVal.unitType == 1 (SVG_ANGLETYPE_UNSPECIFIED)", "m.orientAngle.baseVal.unitType == 1"),
-        
+
         // SVGAnimatedRect / SVGRect
-        new Subtest("v.viewBox.baseVal.[x,y,width,height] == 0", 
+        new Subtest("v.viewBox.baseVal.[x,y,width,height] == 0",
                 "v.viewBox.baseVal.x == 0 && v.viewBox.baseVal.y == 0 && v.viewBox.baseVal.width == 0 && v.viewBox.baseVal.height == 0"),
-        
+
         // SVGAnimatedTransformList
         new Subtest("t.transform.baseVal.numberOfItems == 0"),
 
         // SVGAnimatedPreserveAspectRatio / SVGPreserveAspectRatio
         new Subtest("v.preserveAspectRatio.baseVal.align == 6 (SVG_PRESERVEASPECTRATIO_XMIDYMID)", "v.preserveAspectRatio.baseVal.align == 6"),
         new Subtest("v.preserveAspectRatio.baseVal.meetOrSlice == 1 (SVG_MEETORSLICE_MEET)", "v.preserveAspectRatio.baseVal.meetOrSlice == 1"),
-      
+
         // SVGZoomAndPan
         new Subtest("v.zoomAndPan == 2 (SVG_ZOOMANDPAN_MAGNIFY)", "v.zoomAndPan == 2")
       ];
-            
+
       try {
-	      for (var k = 0; k < tests.length; k++)
-	      {
-	        try 
-	        {
-	          verify(tests[k].getStr(), eval(tests[k].getCode()));
-	        }
-	        catch(e)
-	        {
-	          verify(tests[k].getStr(), false);
-	        }
-	      }
+        for (var k = 0; k < tests.length; k++)
+        {
+          try
+          {
+            verify(tests[k].getStr(), eval(tests[k].getCode()));
+          }
+          catch(e)
+          {
+            verify(tests[k].getStr(), false);
+          }
+        }
       }
       finally
       {
-      	verify("Test status", tests.length == passes);
-      }      
+        verify("Test status", tests.length == passes);
+      }
     ]]></script>
 
   </g>
diff --git a/svg/import/text-align-07-t-manual.svg b/svg/import/text-align-07-t-manual.svg
index 34121eb..ea99452 100644
--- a/svg/import/text-align-07-t-manual.svg
+++ b/svg/import/text-align-07-t-manual.svg
@@ -14,13 +14,13 @@
     template-version="1.4" reviewer="AG" author="CN" status="issue"
     version="$Revision: 1.9 $" testname="$RCSfile: text-align-07-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/text.html#AlignmentProperties">
-    		<p>ISSUE - http://www.w3.org/2011/02/27-svg-irc#T22-20-51</p>
+        <p>ISSUE - http://www.w3.org/2011/02/27-svg-irc#T22-20-51</p>
         <p>
           Test horizontal baselines across script and font size changes.
         </p>
         <p>
           Original test authored by Rodney Hardy at CISRA and modified by
-          Anthony Grasso. 
+          Anthony Grasso.
         </p>
     </d:testDescription>
     <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
diff --git a/svg/import/text-align-08-b-manual.svg b/svg/import/text-align-08-b-manual.svg
index 9541f57..d0ad30c 100644
--- a/svg/import/text-align-08-b-manual.svg
+++ b/svg/import/text-align-08-b-manual.svg
@@ -14,7 +14,7 @@
     template-version="1.4" reviewer="CL" author="CN" status="issue"
     version="$Revision: 1.10 $" testname="$RCSfile: text-align-08-b.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/text.html#AlignmentProperties">
-    	<p>ISSUE - http://www.w3.org/2011/02/27-svg-irc#T22-20-51</p>
+      <p>ISSUE - http://www.w3.org/2011/02/27-svg-irc#T22-20-51</p>
       <p>
         Test horizontal baselines across script and font size changes. It uses an SVG Font, where
         the Latin letter "a" is a rectangle, the Japanese letter "犜" is an upward-pointing triangle,
diff --git a/svg/import/text-altglyph-02-b-manual.svg b/svg/import/text-altglyph-02-b-manual.svg
index d12e917..e53ea74 100644
--- a/svg/import/text-altglyph-02-b-manual.svg
+++ b/svg/import/text-altglyph-02-b-manual.svg
@@ -38,7 +38,7 @@
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
-      <p>The test passes if each pair of (actual,expected) text strings 
+      <p>The test passes if each pair of (actual,expected) text strings
       render identically.</p>
     </d:passCriteria>
   </d:SVGTestCase>
@@ -335,7 +335,7 @@
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.10 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/text-altglyph-03-b-manual.svg b/svg/import/text-altglyph-03-b-manual.svg
index 4327c9f..854e9bc 100644
--- a/svg/import/text-altglyph-03-b-manual.svg
+++ b/svg/import/text-altglyph-03-b-manual.svg
@@ -42,7 +42,7 @@
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
-      <p>The test passes if each pair of (actual,expected) text strings 
+      <p>The test passes if each pair of (actual,expected) text strings
       render identically.</p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/text-bidi-01-t-manual.svg b/svg/import/text-bidi-01-t-manual.svg
index aff70f3..162591f 100644
--- a/svg/import/text-bidi-01-t-manual.svg
+++ b/svg/import/text-bidi-01-t-manual.svg
@@ -43,7 +43,7 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
     <text x="240" y="120" text-anchor="middle">مفتاح معايير الويب</text>
-    <image id="reference" xlink:href="../images/inline2.png" width="200" height="30" transform="translate(120 180)"/>  
+    <image id="reference" xlink:href="../images/inline2.png" width="200" height="30" transform="translate(120 180)"/>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/text-deco-01-b-manual.svg b/svg/import/text-deco-01-b-manual.svg
index 609162a..feef7e2 100644
--- a/svg/import/text-deco-01-b-manual.svg
+++ b/svg/import/text-deco-01-b-manual.svg
@@ -24,23 +24,23 @@
       </p>
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
-    	<p>The test has passed if:</p>
+      <p>The test has passed if:</p>
       <ul>
-      	<li>
-        	The first line of text has no decoration.
-     		</li>
-	      <li>
-	        The second line of text has a line through it.
-	      </li>
-	      <li>
-	        The third line of text is underlined.
-	      </li>
-	      <li>
-	        The fourth line of text has a blue underline with a green stroke under
-	        all characters, except the word "different" which has a yellow underline
-	        with a green stroke.
-	      </li>
-	    </ul>
+        <li>
+          The first line of text has no decoration.
+         </li>
+        <li>
+          The second line of text has a line through it.
+        </li>
+        <li>
+          The third line of text is underlined.
+        </li>
+        <li>
+          The fourth line of text has a blue underline with a green stroke under
+          all characters, except the word "different" which has a yellow underline
+          with a green stroke.
+        </li>
+      </ul>
     </d:passCriteria>
   </d:SVGTestCase>
   <title id="test-title">$RCSfile: text-deco-01-b.svg,v $</title>
diff --git a/svg/import/text-dom-02-f-manual.svg b/svg/import/text-dom-02-f-manual.svg
index bcbbe10..235254b 100644
--- a/svg/import/text-dom-02-f-manual.svg
+++ b/svg/import/text-dom-02-f-manual.svg
@@ -18,8 +18,8 @@
         This tests that methods on the SVGTextContentElement interface
         that take an index to a character or a number of characters
         actually interpret these as indexes to or numbers of UTF-16 code
-        units. To test this, a character from outside the Basic Multilingual Plane 
-        (U+10000; LINEAR B SYLLABLE B008) is used in a text string. 
+        units. To test this, a character from outside the Basic Multilingual Plane
+        (U+10000; LINEAR B SYLLABLE B008) is used in a text string.
         This character is stored in UTF-16 as a surrogate pair.
       </p>
       <p>
diff --git a/svg/import/text-dom-04-f-manual.svg b/svg/import/text-dom-04-f-manual.svg
index 67d3950..a4e7e56 100644
--- a/svg/import/text-dom-04-f-manual.svg
+++ b/svg/import/text-dom-04-f-manual.svg
@@ -37,111 +37,111 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-	<defs>
-		<font horiz-adv-x="4000" id="mini">
-			<font-face font-family="simplesvgfont" units-per-em="4000" ascent="800" descent="-200" alphabetic="0"/>
-			<missing-glyph horiz-adv-x="10000" d="M0 0 4000 0"/>
-			<glyph unicode="a" glyph-name="a" horiz-adv-x="4100" d="M0 4000l2000 -4000l2000 4000Z"/>
-			<glyph unicode="b" glyph-name="b" horiz-adv-x="2000" d="M0 0l2000 0000l0 2000l-2000 0Z"/>
-			<glyph unicode="cb" d="M0 0l2000 2000l-2000 2000Z" horiz-adv-x="800"/>
-			<glyph unicode="c" glyph-name="c" horiz-adv-x="1900" d="M0 0L2000 2000l-2000 0L2000 0Z"/>
-			<glyph unicode="&#x1D352;" glyph-name="compliance" d="M1000,0 h1000 v-1000 h-1000 z" horiz-adv-x="2100"/>
-			<hkern u1="c" u2="a" k="500"/>
-		</font>
-	</defs>
+  <defs>
+    <font horiz-adv-x="4000" id="mini">
+      <font-face font-family="simplesvgfont" units-per-em="4000" ascent="800" descent="-200" alphabetic="0"/>
+      <missing-glyph horiz-adv-x="10000" d="M0 0 4000 0"/>
+      <glyph unicode="a" glyph-name="a" horiz-adv-x="4100" d="M0 4000l2000 -4000l2000 4000Z"/>
+      <glyph unicode="b" glyph-name="b" horiz-adv-x="2000" d="M0 0l2000 0000l0 2000l-2000 0Z"/>
+      <glyph unicode="cb" d="M0 0l2000 2000l-2000 2000Z" horiz-adv-x="800"/>
+      <glyph unicode="c" glyph-name="c" horiz-adv-x="1900" d="M0 0L2000 2000l-2000 0L2000 0Z"/>
+      <glyph unicode="&#x1D352;" glyph-name="compliance" d="M1000,0 h1000 v-1000 h-1000 z" horiz-adv-x="2100"/>
+      <hkern u1="c" u2="a" k="500"/>
+    </font>
+  </defs>
 
-	<script type="text/ecmascript"><![CDATA[
-		function test()
-		{
-			var t = document.getElementById("t");
-			var r = document.getElementById("r");
-			var res = document.getElementById("res");
-			var isPassed = true;
-			
-			// string:  abca&#x1D352;cb
-			// indices: 0123   4  5  67
-			
-			var tests = new Array("getSubStringLength(0,1)", "4100", 	// 'a'
-								  "getSubStringLength(0,2)", "6100", 	// 'ab'
-								  "getSubStringLength(0,3)", "7500", 	// 'abc'
-								  "getSubStringLength(0,4)", "11600", 	// 'abca'
-								  "getSubStringLength(0,5)", "13700",	// 'abca' and high surrogate of 1D352
-								  "getSubStringLength(0,6)", "13700",	// 'abca&#x1D352'
-								  "getSubStringLength(0,7)", "14500",	// 'abca&#x1D352' and half of ligature 'cb'
-								  "getSubStringLength(0,8)", "14500",	// 'abca&#x1D352cb'
-								  "getSubStringLength(0,9)", "14500",	// 'abca&#x1D352cb'
-								  "getSubStringLength(3,1)", "4100",	// 'a'
-								  "getSubStringLength(1,1)", "2000",	// 'b'
-								  "getSubStringLength(4,1)", "2100",	// high surrogate of 1D352
-								  "getSubStringLength(4,2)", "2100",	// '&#x1D352'
-								  "getSubStringLength(5,1)", "2100",	// low surrogate of 1D352
-								  "getSubStringLength(6,1)", "800",		// first half of ligature 'cb'
-								  "getSubStringLength(6,2)", "800",		// ligature 'cb'
-								  "getSubStringLength(7,1)", "800",		// second half of ligature 'cb'
-								  "getSubStringLength(8,1)", "INDEX_SIZE_ERR",
-								  "getSubStringLength(9,0)", "INDEX_SIZE_ERR",
-								  "getSubStringLength(-1,1)", "INDEX_SIZE_ERR",
-								  "getSubStringLength(-57,0)", "INDEX_SIZE_ERR",
-								  "getSubStringLength(0,-52)", "INDEX_SIZE_ERR",
-								  "getSubStringLength(-1,-5)", "INDEX_SIZE_ERR",
-								  "getSubStringLength(7,0)", "0", 		// second half of ligature 'cb'
-								  "getSubStringLength(5,0)", "0",		// high surrogate of 1D352
-								  "getSubStringLength(4,0)", "0"		// low surrogate of 1D352
-								  );
-			
-			try
-			{
-				for(var i = 0; i < tests.length; i=i+2)
-				{
-					try
-					{
-						val = eval("t."+tests[i]);
-					}
-					catch(e)
-					{
-						if(e.code == DOMException.INDEX_SIZE_ERR)
-						{
-							val = "INDEX_SIZE_ERR"
-						}
-						else
-						{
-							val = e;
-						}
-					}
-					
-					if(val != tests[i+1])
-					{
-						throw "Failed " + tests[i] + " got " + val + " but expected " + tests[i+1];
-					}
-				}
-			}
-			catch(f)
-			{
-				isPassed = false;
-				res.textContent = f;
-			}
-			
-			if(isPassed)
-			{
-				r.setAttribute("fill", "none");
-			}
-		}
-	]]></script>
-	
-	<g transform="scale(0.02 0.02) translate(4000 2000)">
-		<g id="reference" fill="red">
-			<path d="M0 4000l2000 -4000l2000 4000Z" transform="scale(1 -1) translate(0 -8000)"/>
-			<path d="M0 0l2000 0000l0 2000l-2000 0Z" transform="translate(4100 6000)"/>
-			<path d="M0 0L2000 2000l-2000 0L2000 0Z" transform="translate(6100 6000)"/>
-			<path d="M0 4000l2000 -4000l2000 4000Z" transform="scale(1 -1) translate(7500 -8000)"/>
-			<path d="M1000,0 h1000 v-1000 h-1000 z" transform="scale(1 -1) translate(11600 -8000)"/>
-			<path d="M0 0l2000 2000l-2000 2000Z" transform="scale(1 -1) translate(13700 -8000)"/>
-		</g>
-		
-		<text id="t" font-family="simplesvgfont" font-size="4000" y="2em" fill="lime">abca&#x1D352;cb</text>
-		<rect id="r" x="5000" y="10%" width="2000" height="2000" fill="red"/>
-		<text id="res" x="50%" text-anchor="middle" font-family="sans-serif" font-size="200" y="80%"></text>
-	</g>
+  <script type="text/ecmascript"><![CDATA[
+    function test()
+    {
+      var t = document.getElementById("t");
+      var r = document.getElementById("r");
+      var res = document.getElementById("res");
+      var isPassed = true;
+
+      // string:  abca&#x1D352;cb
+      // indices: 0123   4  5  67
+
+      var tests = new Array("getSubStringLength(0,1)", "4100",   // 'a'
+                  "getSubStringLength(0,2)", "6100",   // 'ab'
+                  "getSubStringLength(0,3)", "7500",   // 'abc'
+                  "getSubStringLength(0,4)", "11600",   // 'abca'
+                  "getSubStringLength(0,5)", "13700",  // 'abca' and high surrogate of 1D352
+                  "getSubStringLength(0,6)", "13700",  // 'abca&#x1D352'
+                  "getSubStringLength(0,7)", "14500",  // 'abca&#x1D352' and half of ligature 'cb'
+                  "getSubStringLength(0,8)", "14500",  // 'abca&#x1D352cb'
+                  "getSubStringLength(0,9)", "14500",  // 'abca&#x1D352cb'
+                  "getSubStringLength(3,1)", "4100",  // 'a'
+                  "getSubStringLength(1,1)", "2000",  // 'b'
+                  "getSubStringLength(4,1)", "2100",  // high surrogate of 1D352
+                  "getSubStringLength(4,2)", "2100",  // '&#x1D352'
+                  "getSubStringLength(5,1)", "2100",  // low surrogate of 1D352
+                  "getSubStringLength(6,1)", "800",    // first half of ligature 'cb'
+                  "getSubStringLength(6,2)", "800",    // ligature 'cb'
+                  "getSubStringLength(7,1)", "800",    // second half of ligature 'cb'
+                  "getSubStringLength(8,1)", "INDEX_SIZE_ERR",
+                  "getSubStringLength(9,0)", "INDEX_SIZE_ERR",
+                  "getSubStringLength(-1,1)", "INDEX_SIZE_ERR",
+                  "getSubStringLength(-57,0)", "INDEX_SIZE_ERR",
+                  "getSubStringLength(0,-52)", "INDEX_SIZE_ERR",
+                  "getSubStringLength(-1,-5)", "INDEX_SIZE_ERR",
+                  "getSubStringLength(7,0)", "0",     // second half of ligature 'cb'
+                  "getSubStringLength(5,0)", "0",    // high surrogate of 1D352
+                  "getSubStringLength(4,0)", "0"    // low surrogate of 1D352
+                  );
+
+      try
+      {
+        for(var i = 0; i < tests.length; i=i+2)
+        {
+          try
+          {
+            val = eval("t."+tests[i]);
+          }
+          catch(e)
+          {
+            if(e.code == DOMException.INDEX_SIZE_ERR)
+            {
+              val = "INDEX_SIZE_ERR"
+            }
+            else
+            {
+              val = e;
+            }
+          }
+
+          if(val != tests[i+1])
+          {
+            throw "Failed " + tests[i] + " got " + val + " but expected " + tests[i+1];
+          }
+        }
+      }
+      catch(f)
+      {
+        isPassed = false;
+        res.textContent = f;
+      }
+
+      if(isPassed)
+      {
+        r.setAttribute("fill", "none");
+      }
+    }
+  ]]></script>
+
+  <g transform="scale(0.02 0.02) translate(4000 2000)">
+    <g id="reference" fill="red">
+      <path d="M0 4000l2000 -4000l2000 4000Z" transform="scale(1 -1) translate(0 -8000)"/>
+      <path d="M0 0l2000 0000l0 2000l-2000 0Z" transform="translate(4100 6000)"/>
+      <path d="M0 0L2000 2000l-2000 0L2000 0Z" transform="translate(6100 6000)"/>
+      <path d="M0 4000l2000 -4000l2000 4000Z" transform="scale(1 -1) translate(7500 -8000)"/>
+      <path d="M1000,0 h1000 v-1000 h-1000 z" transform="scale(1 -1) translate(11600 -8000)"/>
+      <path d="M0 0l2000 2000l-2000 2000Z" transform="scale(1 -1) translate(13700 -8000)"/>
+    </g>
+
+    <text id="t" font-family="simplesvgfont" font-size="4000" y="2em" fill="lime">abca&#x1D352;cb</text>
+    <rect id="r" x="5000" y="10%" width="2000" height="2000" fill="red"/>
+    <text id="res" x="50%" text-anchor="middle" font-family="sans-serif" font-size="200" y="80%"></text>
+  </g>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/text-dom-05-f-manual.svg b/svg/import/text-dom-05-f-manual.svg
index 017d9ff..1dfcb67 100644
--- a/svg/import/text-dom-05-f-manual.svg
+++ b/svg/import/text-dom-05-f-manual.svg
@@ -18,8 +18,8 @@
         This tests that methods on the SVGTextContentElement interface
         that take an index to a character or a number of characters
         actually interpret these as indexes to or numbers of UTF-16 code
-        units. To test this, a character from outside the Basic Multilingual Plane 
-        (U+10000; LINEAR B SYLLABLE B008) is used in a text string. 
+        units. To test this, a character from outside the Basic Multilingual Plane
+        (U+10000; LINEAR B SYLLABLE B008) is used in a text string.
         This character is stored in UTF-16 as a surrogate pair.
       </p>
       <p>
diff --git a/svg/import/text-fonts-05-f-manual.svg b/svg/import/text-fonts-05-f-manual.svg
index b615e80..36017c3 100644
--- a/svg/import/text-fonts-05-f-manual.svg
+++ b/svg/import/text-fonts-05-f-manual.svg
@@ -41,7 +41,7 @@
         <tspan x="160" line-height="100">FillerText</tspan>
         <tspan x="270" line-height="200">FillerText</tspan>
     </text>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/text-fonts-202-t-manual.svg b/svg/import/text-fonts-202-t-manual.svg
index a26092a..b38d36d 100644
--- a/svg/import/text-fonts-202-t-manual.svg
+++ b/svg/import/text-fonts-202-t-manual.svg
@@ -1,4 +1,4 @@
-<svg version="1.1" baseProfile="tiny" id="svg-root"
+<svg version="1.1" baseProfile="tiny" id="svg-root"
   width="100%" height="100%" viewBox="0 0 480 360"
   xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
diff --git a/svg/import/text-fonts-203-t-manual.svg b/svg/import/text-fonts-203-t-manual.svg
index 0f8d376..011ad8a 100644
--- a/svg/import/text-fonts-203-t-manual.svg
+++ b/svg/import/text-fonts-203-t-manual.svg
@@ -19,8 +19,8 @@
         font family with five weights is  specified, with a fallback to 'serif'.
       </p>
       <p>
-        The specified font family has five weights - 300, 400, 600, 700 and 800. 
-        See the CSS3 Font specification 
+        The specified font family has five weights - 300, 400, 600, 700 and 800.
+        See the CSS3 Font specification
         for how these are allocated to the nine weight numbers.
       </p>
       <p>
@@ -28,11 +28,11 @@
         right hand side of the test, the third line of text tests the to 'bolder' relative
         keyword and the fourth tests the 'lighter' relative keyword.
       </p>
-      <p>The fonts are SVG fonts convertted, with the author's explicit permission, 
+      <p>The fonts are SVG fonts convertted, with the author's explicit permission,
        from  <a href="http://justanotherfoundry.com/Zalamander/index.htm">Zalamander Caps</a>
         by Tim Ahrens of <a href="http://justanotherfoundry.com/">Just Another Foundry</a>.
-        An ASCII subset has been generated for this test. The font names have been 
-        obfuscated, to deter 
+        An ASCII subset has been generated for this test. The font names have been
+        obfuscated, to deter
         user agent sniffing for keywords like "Ultrabold". All weights in this generated
         family are multiples of 100 and greater or equal to 300.</p>
     </d:testDescription>
diff --git a/svg/import/text-fonts-204-t-manual.svg b/svg/import/text-fonts-204-t-manual.svg
index 31f464a..4a5859c 100644
--- a/svg/import/text-fonts-204-t-manual.svg
+++ b/svg/import/text-fonts-204-t-manual.svg
@@ -19,8 +19,8 @@
         font family with five weights is  specified, with a fallback to 'serif'.
       </p>
       <p>
-        The specified font family has five weights - 300, 400, 600, 700 and 800. 
-        See the CSS3 Font specification 
+        The specified font family has five weights - 300, 400, 600, 700 and 800.
+        See the CSS3 Font specification
         for how these are allocated to the nine weight numbers.
       </p>
       <p>
@@ -28,10 +28,10 @@
         right hand side of the test, the third line of text tests the to 'bolder' relative
         keyword and the fourth tests the 'lighter' relative keyword.
       </p>
-      <p>The fonts are WOFF fonts convertted, with the author's explicit permission, 
+      <p>The fonts are WOFF fonts convertted, with the author's explicit permission,
        from  <a href="http://justanotherfoundry.com/zalamander">Zalamander Caps</a>
         by Tim Ahrens of <a href="http://justanotherfoundry.com/">Just Another Foundry</a>.
-        The font names have been obfuscated, to deter 
+        The font names have been obfuscated, to deter
         user agent sniffing for keywords like "Ultrabold". All weights in this generated
         family are multiples of 100 and greater or equal to 300.</p>
     </d:testDescription>
diff --git a/svg/import/text-intro-02-b-manual.svg b/svg/import/text-intro-02-b-manual.svg
index 1b36814..76497c6 100644
--- a/svg/import/text-intro-02-b-manual.svg
+++ b/svg/import/text-intro-02-b-manual.svg
@@ -31,7 +31,7 @@
         the various international characters used in this test case. A
         suitable font should be used by the SVG renderer if none of the
         specified font families are available (or if they are available but do
-        not have the required glyphs). 
+        not have the required glyphs).
       </p>
       <p>
         Run the test. No interaction required.
diff --git a/svg/import/text-intro-05-t-manual.svg b/svg/import/text-intro-05-t-manual.svg
index 9f23827..5605996 100644
--- a/svg/import/text-intro-05-t-manual.svg
+++ b/svg/import/text-intro-05-t-manual.svg
@@ -49,9 +49,9 @@
     <!-- Diwani fonts come with Microsoft Arabic fonts extension for Office XP
 http://www.microsoft.com/downloads/details.aspx?FamilyID=A83C0E03-8913-47A3-ACB7-8AC357627620&displaylang=AR -->
     <text x="460" y="240" text-anchor="start" xml:lang="ar" font-size="30" font-family="'Nafees Naskh'">لماذا لا يتكلمون اللّغة العربية فحسب؟</text>
-    <!-- Nafees Nastaleeq and Nafees Naskh are  freely available from 
-  the Center for Research in Urdu Language Processing (CRULP http://www.crulp.org  ) 
-  at the National University of Computer and Emerging Sciences, Pakistan 
+    <!-- Nafees Nastaleeq and Nafees Naskh are  freely available from
+  the Center for Research in Urdu Language Processing (CRULP http://www.crulp.org  )
+  at the National University of Computer and Emerging Sciences, Pakistan
   ( http://www.nu.edu.pk ) -->
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
diff --git a/svg/import/text-intro-06-t-manual.svg b/svg/import/text-intro-06-t-manual.svg
index 0e5ac1f..f14bbb9 100644
--- a/svg/import/text-intro-06-t-manual.svg
+++ b/svg/import/text-intro-06-t-manual.svg
@@ -52,12 +52,12 @@
         <li>The first subtest has the word for 'tools', آلات</li>
         <li>The second subtest has the word for 'three', ثلاثة</li>
       </ul>
-      <p>In the first subtest, there must be an isolate lam-alef ligature 
-      and in the second subtest there must be  a right-joining lam-alef 
+      <p>In the first subtest, there must be an isolate lam-alef ligature
+      and in the second subtest there must be  a right-joining lam-alef
       ligature, (so that them, lam and alef are all connected), as in the reference image.
       </p>
       <p>The precise glyph shapes will depend on which font was used for rendering,
-      and do not affect the pass criteria. Only the presence of the 
+      and do not affect the pass criteria. Only the presence of the
       mandatory ligatures is tested here.</p>
     </d:passCriteria>
   </d:SVGTestCase>
diff --git a/svg/import/text-intro-07-t-manual.svg b/svg/import/text-intro-07-t-manual.svg
index f474aa9..ca045a5 100644
--- a/svg/import/text-intro-07-t-manual.svg
+++ b/svg/import/text-intro-07-t-manual.svg
@@ -62,4 +62,4 @@
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
-</svg>
\ No newline at end of file
+</svg>
diff --git a/svg/import/text-intro-10-f-manual.svg b/svg/import/text-intro-10-f-manual.svg
index db0b0fb..1ac3dd8 100644
--- a/svg/import/text-intro-10-f-manual.svg
+++ b/svg/import/text-intro-10-f-manual.svg
@@ -51,9 +51,9 @@
     <!-- Diwani fonts come with Microsoft Arabic fonts extension for Office XP
 http://www.microsoft.com/downloads/details.aspx?FamilyID=A83C0E03-8913-47A3-ACB7-8AC357627620&displaylang=AR -->
     <text x="460" y="240" text-anchor="start" xml:lang="ar" font-size="30" font-family="'Nafees Naskh'">لماذا لا يتكلمون اللّغة العربية فحسب؟</text>
-    <!-- Nafees Nastaleeq and Nafees Naskh are  freely available from 
-  the Center for Research in Urdu Language Processing (CRULP http://www.crulp.org  ) 
-  at the National University of Computer and Emerging Sciences, Pakistan 
+    <!-- Nafees Nastaleeq and Nafees Naskh are  freely available from
+  the Center for Research in Urdu Language Processing (CRULP http://www.crulp.org  )
+  at the National University of Computer and Emerging Sciences, Pakistan
   ( http://www.nu.edu.pk ) -->
   </g>
   </g>
diff --git a/svg/import/text-intro-12-t-manual.svg b/svg/import/text-intro-12-t-manual.svg
index 0d0902f..a2d2809 100644
--- a/svg/import/text-intro-12-t-manual.svg
+++ b/svg/import/text-intro-12-t-manual.svg
@@ -52,8 +52,8 @@
         <li>The first subtest has the word for 'tools', آلات</li>
         <li>The second subtest has the word for 'three', ثلاثة</li>
       </ul>
-      <p>In the first subtest, there must be an isolate lam-alef ligature 
-      and in the second subtest there must be  a right-joining lam-alef 
+      <p>In the first subtest, there must be an isolate lam-alef ligature
+      and in the second subtest there must be  a right-joining lam-alef
       ligature, as in the reference image.
       </p>
     </d:passCriteria>
diff --git a/svg/import/text-text-10-t-manual.svg b/svg/import/text-text-10-t-manual.svg
index 57b0e1b..e77b0e1 100644
--- a/svg/import/text-text-10-t-manual.svg
+++ b/svg/import/text-text-10-t-manual.svg
@@ -67,7 +67,7 @@
       fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/text-text-11-t-manual.svg b/svg/import/text-text-11-t-manual.svg
index 0316cbd..b0ed1dd 100644
--- a/svg/import/text-text-11-t-manual.svg
+++ b/svg/import/text-text-11-t-manual.svg
@@ -39,7 +39,7 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-  
+
     <!-- Note that picking a platform font (sans-serif) is one of the main purposes of this test -->
     <g font-family="sans-serif">
       <text transform="rotate(90) translate(120 -100)" text-anchor="middle">Rotated 90 degrees</text>
@@ -65,14 +65,14 @@
       <text transform="translate(250 180) rotate(-75 0 0)">Rotated by 10 degrees</text>
       <text transform="translate(250 180) rotate(-85 0 0)">Rotated by 10 degrees</text>
     </g>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
       fill="black">$Revision: 1.3 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
-  <!-- comment out this watermark once the test is approved 
+  <!-- comment out this watermark once the test is approved
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
     <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
diff --git a/svg/import/text-text-12-t-manual.svg b/svg/import/text-text-12-t-manual.svg
index 0fb521c..3fdedb0 100644
--- a/svg/import/text-text-12-t-manual.svg
+++ b/svg/import/text-text-12-t-manual.svg
@@ -14,7 +14,7 @@
     template-version="1.4" reviewer="SVGWG" author="VH" status="issue"
     version="$Revision: 1.1 $" testname="$RCSfile: text-text-12-t.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/text.html#TextElement">
-    		<p>ISSUE: the glyphs don't cover the entire em-cell, and the spec doesn't require visual alignment - text adjustments are based on advances</p>
+        <p>ISSUE: the glyphs don't cover the entire em-cell, and the spec doesn't require visual alignment - text adjustments are based on advances</p>
         <p>
           The purpose of this test is to validate the interaction of text-anchor
           and x/y glyph positioning.
@@ -86,77 +86,77 @@
         <text y="240">4.x 2.y</text>
         <text y="270">2.x 2.y</text>
       </g>
-			
-			<defs>
-	      <g id="textContent">
-	        <!-- 1.x 1.y -->
-	        <use y="30" xlink:href="#marker" fill="#8888ff"/>
-	        <text x="0" y="30" font-family="embeded" font-size="10">1234</text>
-	
-	        <!-- 4.x 1.y : four text chunks -->
-	        <use x="10" y="60" xlink:href="#marker" fill="#8888ff"/>
-	        <use x="20" y="60" xlink:href="#marker" fill="#8888ff"/>
-	        <use x="30" y="60" xlink:href="#marker" fill="#8888ff"/>
-	        <use x="40" y="60" xlink:href="#marker" fill="#8888ff"/>
-	        <text x="10 20 30 40" y="60" font-family="embeded" font-size="10">1234</text>
-	
-	        <!-- 2.x 1.y : two text chunks -->
-	        <use x="10" y="90" xlink:href="#marker" fill="#8888ff"/>
-	        <use x="60" y="90" xlink:href="#marker" fill="#8888ff"/>
-	        <text x="10 60" y="90" font-family="embeded" font-size="10">1234</text>
-	
-	        <!-- 1.x 4.y : four text chunks -->
-	        <g transform="translate(0, 120)">
-	          <use x="0" y="-10" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="15" y="-5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="30" y="5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="45" y="10" xlink:href="#marker" fill="#8888ff"/>
-	          <text x="0" y="-10 -5 5 10" font-family="embeded" font-size="10">1234</text>
-	        </g>
-	
-	        <!-- 4.x 4.y : four text chunks -->
-	        <g transform="translate(0, 150)">
-	          <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="20" y="-5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="30" y="5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="40" y="10" xlink:href="#marker" fill="#8888ff"/>
-	          <text x="10 20 30 40" y="-10 -5 5 10" font-family="embeded" font-size="10">1234</text>
-	        </g>
-	
-	        <!-- 2.x 4.y : four text chunks -->
-	        <g transform="translate(0, 180)">
-	          <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="20" y="-5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="35" y="5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="50" y="10" xlink:href="#marker" fill="#8888ff"/>
-	          <text x="10 20" y="-10 -5 5 10" font-family="embeded" font-size="10">1234</text>
-	        </g>
-	
-	        <!-- 1.x 2.y : two text chunks -->
-	        <g transform="translate(0, 210)">
-	          <use x="0" y="-10" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="15" y="5" xlink:href="#marker" fill="#8888ff"/>
-	          <text x="0" y="-10 5" font-family="embeded" font-size="10">1234</text>
-	        </g>
-	
-	        <!-- 4.x 2.y : four text chunks -->
-	        <g transform="translate(0, 240)">
-	          <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="20" y="-5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="30" y="-5" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="40" y="-5" xlink:href="#marker" fill="#8888ff"/>
-	          <text x="10 20 30 40" y="-10 -5" font-family="embeded" font-size="10">1234</text>
-	        </g>
-	
-	        <!-- 2.x 2.y : two text chunks -->
-	        <g transform="translate(0, 270)">
-	          <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
-	          <use x="60" y="-5" xlink:href="#marker" fill="#8888ff"/>
-	          <text x="10 60" y="-10 -5" font-family="embeded" font-size="10">1234</text>
-	        </g>
-	      </g>
-			</defs>
-			
+
+      <defs>
+        <g id="textContent">
+          <!-- 1.x 1.y -->
+          <use y="30" xlink:href="#marker" fill="#8888ff"/>
+          <text x="0" y="30" font-family="embeded" font-size="10">1234</text>
+
+          <!-- 4.x 1.y : four text chunks -->
+          <use x="10" y="60" xlink:href="#marker" fill="#8888ff"/>
+          <use x="20" y="60" xlink:href="#marker" fill="#8888ff"/>
+          <use x="30" y="60" xlink:href="#marker" fill="#8888ff"/>
+          <use x="40" y="60" xlink:href="#marker" fill="#8888ff"/>
+          <text x="10 20 30 40" y="60" font-family="embeded" font-size="10">1234</text>
+
+          <!-- 2.x 1.y : two text chunks -->
+          <use x="10" y="90" xlink:href="#marker" fill="#8888ff"/>
+          <use x="60" y="90" xlink:href="#marker" fill="#8888ff"/>
+          <text x="10 60" y="90" font-family="embeded" font-size="10">1234</text>
+
+          <!-- 1.x 4.y : four text chunks -->
+          <g transform="translate(0, 120)">
+            <use x="0" y="-10" xlink:href="#marker" fill="#8888ff"/>
+            <use x="15" y="-5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="30" y="5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="45" y="10" xlink:href="#marker" fill="#8888ff"/>
+            <text x="0" y="-10 -5 5 10" font-family="embeded" font-size="10">1234</text>
+          </g>
+
+          <!-- 4.x 4.y : four text chunks -->
+          <g transform="translate(0, 150)">
+            <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
+            <use x="20" y="-5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="30" y="5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="40" y="10" xlink:href="#marker" fill="#8888ff"/>
+            <text x="10 20 30 40" y="-10 -5 5 10" font-family="embeded" font-size="10">1234</text>
+          </g>
+
+          <!-- 2.x 4.y : four text chunks -->
+          <g transform="translate(0, 180)">
+            <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
+            <use x="20" y="-5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="35" y="5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="50" y="10" xlink:href="#marker" fill="#8888ff"/>
+            <text x="10 20" y="-10 -5 5 10" font-family="embeded" font-size="10">1234</text>
+          </g>
+
+          <!-- 1.x 2.y : two text chunks -->
+          <g transform="translate(0, 210)">
+            <use x="0" y="-10" xlink:href="#marker" fill="#8888ff"/>
+            <use x="15" y="5" xlink:href="#marker" fill="#8888ff"/>
+            <text x="0" y="-10 5" font-family="embeded" font-size="10">1234</text>
+          </g>
+
+          <!-- 4.x 2.y : four text chunks -->
+          <g transform="translate(0, 240)">
+            <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
+            <use x="20" y="-5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="30" y="-5" xlink:href="#marker" fill="#8888ff"/>
+            <use x="40" y="-5" xlink:href="#marker" fill="#8888ff"/>
+            <text x="10 20 30 40" y="-10 -5" font-family="embeded" font-size="10">1234</text>
+          </g>
+
+          <!-- 2.x 2.y : two text chunks -->
+          <g transform="translate(0, 270)">
+            <use x="10" y="-10" xlink:href="#marker" fill="#8888ff"/>
+            <use x="60" y="-5" xlink:href="#marker" fill="#8888ff"/>
+            <text x="10 60" y="-10 -5" font-family="embeded" font-size="10">1234</text>
+          </g>
+        </g>
+      </defs>
+
       <g id="anchorMiddle" text-anchor="middle" transform="translate(250, 40)">
         <text y="-17" text-anchor="middle">text-anchor</text>
         <text y="-3" text-anchor="middle">middle</text>
diff --git a/svg/import/text-tref-01-b-manual.svg b/svg/import/text-tref-01-b-manual.svg
index 10a53a0..d8690ee 100644
--- a/svg/import/text-tref-01-b-manual.svg
+++ b/svg/import/text-tref-01-b-manual.svg
@@ -55,9 +55,9 @@
     <text x="73" y="217" font-size="12" fill="black">'tref' to a string in another file</text>
     <!-- Patch code -->
     <!--
-	<text x="75" y="200" font-size="18" fill="green" >
-	Simple external referenced text.
-	</text>
+  <text x="75" y="200" font-size="18" fill="green" >
+  Simple external referenced text.
+  </text>
 -->
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
diff --git a/svg/import/text-tref-02-b-manual.svg b/svg/import/text-tref-02-b-manual.svg
index 7773c95..073834f 100644
--- a/svg/import/text-tref-02-b-manual.svg
+++ b/svg/import/text-tref-02-b-manual.svg
@@ -56,7 +56,7 @@
 <defs>
   <foreignObject>
     <svg>
-	<text id="hello">Hello</text>
+  <text id="hello">Hello</text>
     </svg>
   </foreignObject>
 </defs>
diff --git a/svg/import/text-tref-03-b-manual.svg b/svg/import/text-tref-03-b-manual.svg
index ba05e5d..848cef5 100644
--- a/svg/import/text-tref-03-b-manual.svg
+++ b/svg/import/text-tref-03-b-manual.svg
@@ -25,7 +25,7 @@
           The test case consists of one sub-test; it results in the word "Flattened" being displayed.
         </p>
     </d:testDescription>
-    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">      
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
       <p>
         Run the test. No interaction required.
       </p>
@@ -53,10 +53,9 @@
     </text>
 <!-- now make a new SVG document fragment, and hide it from being rendered -->
 <defs>
-	<text id="hello">F<tspan fill="blue">l</tspan><tspan fill="red" font-size="100">a</tspan><tspan fill="yellow">t<tspan fill="lime">t</tspan></tspan><tspan fill="orange" dx="50">e</tspan><tspan fill="purple">n</tspan><tspan fill="red">e</tspan><tspan fill="black">d</tspan></text>
+  <text id="hello">F<tspan fill="blue">l</tspan><tspan fill="red" font-size="100">a</tspan><tspan fill="yellow">t<tspan fill="lime">t</tspan></tspan><tspan fill="orange" dx="50">e</tspan><tspan fill="purple">n</tspan><tspan fill="red">e</tspan><tspan fill="black">d</tspan></text>
 </defs>
 
-
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.4 $</text>
diff --git a/svg/import/text-tselect-03-f-manual.svg b/svg/import/text-tselect-03-f-manual.svg
index 9d05108..c107e48 100644
--- a/svg/import/text-tselect-03-f-manual.svg
+++ b/svg/import/text-tselect-03-f-manual.svg
@@ -76,10 +76,10 @@
   <title id="test-title">$RCSfile: text-tselect-03-f.svg,v $</title>
   <defs>
     <style type="text/css">
-	@font-face {
-	    font-family: Ezra SIL SR;
-	    src: url(woffs/EzraSILSR.woff) format("woff");
-	}
+  @font-face {
+      font-family: Ezra SIL SR;
+      src: url(woffs/EzraSILSR.woff) format("woff");
+  }
     </style>
     <font-face font-family="SVGFreeSansASCII" unicode-range="U+0-7F">
       <font-face-src>
diff --git a/svg/import/text-tspan-01-b-manual.svg b/svg/import/text-tspan-01-b-manual.svg
index 5d10f95..40eb471 100644
--- a/svg/import/text-tspan-01-b-manual.svg
+++ b/svg/import/text-tspan-01-b-manual.svg
@@ -43,28 +43,28 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-  	<defs>
-	  	<font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="400">
-	      <font-face-src>
-	        <font-face-uri xlink:href="../resources/FreeSerif.svg#FreeSerif"/>
-	      </font-face-src>
-	    </font-face>
-	    <font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="700">
-	      <font-face-src>
-	        <font-face-uri xlink:href="../resources/FreeSerifBold.svg#FreeSerifBold"/>
-	      </font-face-src>
-	    </font-face>
-	    <font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="400" font-style="italic">
-	      <font-face-src>
-	        <font-face-uri xlink:href="../resources/FreeSerifItalic.svg#FreeSerifItalic"/>
-	      </font-face-src>
-	    </font-face>
-	    <font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="700"  font-style="italic">
-	      <font-face-src>
-	        <font-face-uri xlink:href="../resources/FreeSerifBoldItalic.svg#FreeSerifBoldItalic"/>
-	      </font-face-src>
-	    </font-face>
-  	</defs>
+    <defs>
+      <font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="400">
+        <font-face-src>
+          <font-face-uri xlink:href="../resources/FreeSerif.svg#FreeSerif"/>
+        </font-face-src>
+      </font-face>
+      <font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="700">
+        <font-face-src>
+          <font-face-uri xlink:href="../resources/FreeSerifBold.svg#FreeSerifBold"/>
+        </font-face-src>
+      </font-face>
+      <font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="400" font-style="italic">
+        <font-face-src>
+          <font-face-uri xlink:href="../resources/FreeSerifItalic.svg#FreeSerifItalic"/>
+        </font-face-src>
+      </font-face>
+      <font-face font-family="FreeSerif" unicode-range="U+0-7F" font-weight="700"  font-style="italic">
+        <font-face-src>
+          <font-face-uri xlink:href="../resources/FreeSerifBoldItalic.svg#FreeSerifBoldItalic"/>
+        </font-face-src>
+      </font-face>
+    </defs>
     <text x="25" y="20" font-size="16">Basics of tspan: changing visual properties and positioning.</text>
     <!-- From example tspan01 - using tspan to change visual attributes -->
     <g id="tspan01" font-size="16" font-family="FreeSerif, serif">
@@ -76,7 +76,7 @@
       <text x="65.25" y="108" font-size="16">'tspan' changes visual attributes of "not",</text>
       <text x="65.25" y="125.25" font-size="16">to green, bold.</text>
     </g>
-    <!-- From example tspan02 - using tspan's dx and dy attributes 
+    <!-- From example tspan02 - using tspan's dx and dy attributes
                  for incremental positioning adjustments -->
     <g id="tspan02" font-size="16" font-family="FreeSerif, serif">
       <text x="257.5" y="153.75" fill="blue">
@@ -87,7 +87,7 @@
       <text x="238" y="212.25" font-size="16">Using dx,dy, 'tspan' raises "are",</text>
       <text x="238" y="229.5" font-size="16">'tspan' lowers "a peach!"</text>
     </g>
-    <!-- Example tspan03 - using tspan's x and y attributes 
+    <!-- Example tspan03 - using tspan's x and y attributes
                      for multiline text and precise glyph positioning -->
     <g id="tspan03" fill="#000000" font-size="16" font-family="FreeSerif, serif">
       <text fill="rgb(255,164,0)">
diff --git a/svg/import/text-tspan-02-b-manual.svg b/svg/import/text-tspan-02-b-manual.svg
index 735b41e..71436a3 100644
--- a/svg/import/text-tspan-02-b-manual.svg
+++ b/svg/import/text-tspan-02-b-manual.svg
@@ -1,5 +1,5 @@
-<svg id="svg-root" width="100%" height="100%" 
-  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" 
+<svg id="svg-root" width="100%" height="100%"
+  viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
   <!--======================================================================-->
   <!--=  Copyright 2008 World Wide Web Consortium, (Massachusetts          =-->
@@ -103,7 +103,6 @@
       <tspan x="20" y="180">text have a specified rotation</tspan>
     </text>
 
-
     <text font-size="35" fill="green" x="20" y="120" rotate="5,15,25,35,45,55" stroke="green" stroke-width="0.5">
       Not
 
@@ -161,17 +160,17 @@
   55    55  55  55   55 55  55   55
       </tspan>
     </text>
-    
+
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
-  <text id="revision" x="10" y="340" stroke="none" 
+  <text id="revision" x="10" y="340" stroke="none"
     fill="black">$Revision: 1.11 $</text>
   </g>
   <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000"/>
   <!-- comment out this watermark once the test is approved --><!--
   <g id="draft-watermark">
     <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
-    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240" 
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
       text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
   </g>-->
 </svg>
diff --git a/svg/import/types-basic-01-f-manual.svg b/svg/import/types-basic-01-f-manual.svg
index 2be4195..842996d 100644
--- a/svg/import/types-basic-01-f-manual.svg
+++ b/svg/import/types-basic-01-f-manual.svg
@@ -13,7 +13,7 @@
     version="$Revision: 1.5 $" testname="$RCSfile: types-basic-01-f.svg,v $">
     <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/types.html#BasicDataTypes">
       <p>
-        Tests scientific notation in attribute values; in particular, that numbers 
+        Tests scientific notation in attribute values; in particular, that numbers
         of the form .n with a leading decimal point, are supported
       </p>
     </d:testDescription>
@@ -24,8 +24,8 @@
     </d:operatorScript>
     <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
       <p>
-        The test is passed if all the coloured rectangles are of the same height 
-        and line up with each other and 
+        The test is passed if all the coloured rectangles are of the same height
+        and line up with each other and
         with the grey marker lines. If any red is visible, the test fails.
       </p>
     </d:passCriteria>
@@ -41,19 +41,18 @@
     </font-face>
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
-    
+
     <polyline points="20,75 380,75" stroke="#999" stroke-width="1"/>
     <polyline points="20,125 380,125" stroke="#999" stroke-width="1"/>
-   
+
     <rect width="300" height="50" x="50" y="75" fill="red"/>
 
     <polyline points="50,100 150,100" stroke-width="50" stroke="#6F3"/>
     <polyline points="150,100 250,100" stroke-width="5e1" stroke="#6C6"/>
     <polyline points="250,100 350,100" stroke-width=".5e2" stroke="#39F"/>
-   
-    
+
     <text x="50" y="200" font-size="20">Different forms of the &lt;number&gt; type</text>
-    
+
     <g text-anchor="middle" fill="#555">
       <text x="100" y="110" font-size="20">50</text>
       <text x="200" y="110" font-size="20" >5e1</text>
diff --git a/svg/import/types-basic-02-f-manual.svg b/svg/import/types-basic-02-f-manual.svg
index 53fb163..314dda5 100644
--- a/svg/import/types-basic-02-f-manual.svg
+++ b/svg/import/types-basic-02-f-manual.svg
@@ -45,22 +45,22 @@
         #swUnit       { stroke-width: 20px }
         #swUpperCaseUnit { stroke-width: 20PX }
       </style>
-      
+
       <g id="references" fill="none" stroke="red" stroke-width="19">
         <circle r="40" transform="translate(100 100)"/>
         <circle r="40" transform="translate(240 100)"/>
         <circle r="40" transform="translate(380 100)"/>
-        
+
         <circle r="40" transform="translate(100 240)"/>
         <circle r="40" transform="translate(240 240)"/>
         <circle r="40" transform="translate(380 240)"/>
       </g>
-      
+
       <!-- "For length values in SVG-specific properties and their corresponding presentation attributes, the length unit identifier is optional." -->
       <circle r="40" id="swNoUnit" fill="none" stroke="lime" transform="translate(100 100)"/>
       <circle r="40" id="swUnit" fill="none" stroke="lime" transform="translate(240 100)"/>
       <circle r="40" id="swPresAttr" fill="none" stroke="lime" stroke-width="20" transform="translate(380 100)"/>
-            
+
       <!-- "In presentation attributes for all properties, whether defined in this specification or in CSS2, the length identifier, if specified, must be in lower case." -->
       <g stroke-width="20">
         <circle r="40" id="swUpperCaseUnitPresAttr" stroke-width="10PX" fill="none" stroke="lime" transform="translate(100 240)"/>
@@ -68,7 +68,7 @@
 
       <!-- Test that units are case-insensitive when specified in CSS -->
       <circle r="40" id="swUpperCaseUnit" fill="none" stroke="lime" transform="translate(240 240)"/>
-      <circle r="40" id="swUpperCaseUnitInline" style="stroke-width:20PX" fill="none" stroke="lime" transform="translate(380 240)"/>      
+      <circle r="40" id="swUpperCaseUnitInline" style="stroke-width:20PX" fill="none" stroke="lime" transform="translate(380 240)"/>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/types-dom-05-b-manual.svg b/svg/import/types-dom-05-b-manual.svg
index 52ca383..72754cc 100644
--- a/svg/import/types-dom-05-b-manual.svg
+++ b/svg/import/types-dom-05-b-manual.svg
@@ -68,7 +68,7 @@
       var b,
           c = document.getElementById('c');
           m = document.getElementById('m');
-      
+
       b = false;
       try {
         if (c.r.baseVal.unitType != 3) { /* SVGLength.SVG_LENGTHTYPE_EMS */
diff --git a/svg/import/types-dom-06-f-manual.svg b/svg/import/types-dom-06-f-manual.svg
index 09ed98f..c012c7d 100644
--- a/svg/import/types-dom-06-f-manual.svg
+++ b/svg/import/types-dom-06-f-manual.svg
@@ -42,79 +42,79 @@
   </defs>
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
-		<defs>
-			<script type="application/ecmascript">
-				function runtest()
-				{
-					try {
-						var r1 = document.getElementById("r1");
-						var r2 = document.getElementById("r2");
-						var r3 = document.getElementById("r3");
-						var i0 = r1.requiredFeatures.getItem(0);
-						var i1 = r1.requiredFeatures.getItem(1);
-						
-						if(i0 != "http://www.w3.org/TR/SVG11/feature#Shape")
-						{
-							r1.removeAttribute("requiredFeatures");
-							return;
-						}
-						if(i1 != "this.is.a.bogus.feature.string")
-						{
-							r1.removeAttribute("requiredFeatures");
-							return;
-						}
-						if(r1.requiredFeatures.numberOfItems != 2)
-						{
-							r1.removeAttribute("requiredFeatures");
-							return;
-						}
-						r2.requiredFeatures.appendItem(i1);
-						if(r1.requiredFeatures.numberOfItems != 2)
-						{
-							r1.removeAttribute("requiredFeatures");
-							return;
-						}
-						if(r2.requiredFeatures.numberOfItems != 1)
-						{
-							r1.removeAttribute("requiredFeatures");
-							return;
-						}
-						r3.requiredFeatures.insertItemBefore(i0,0);
-						if(r3.requiredFeatures.numberOfItems != 2)
-						{
-							r3.setAttribute("fill", "red");
-							return;
-						}
-						if(r1.requiredFeatures.numberOfItems != 2)
-						{
-							r1.removeAttribute("requiredFeatures");
-							return;
-						}
-					}
-					catch(e)
-					{
-						var f = document.getElementById("fail");
-						f.setAttribute("fill", "red");
-					}				
-				}
-			</script>
-		</defs>
-		
-		<text x="50%" y="70" text-anchor="middle">SVGStringList DOM</text>
-		
-		<!-- background images -->
-		<g>
-			<rect width="100" height="100" fill="lime" transform="translate(80 100)"/>
-			<rect width="100" height="100" fill="lime" transform="translate(190 100)"/>
-			<rect width="100" height="100" fill="red" transform="translate(300 100)"/>
-		</g>
-		
-		<!-- tests -->
-		<rect id="r1" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Shape this.is.a.bogus.feature.string" width="100" height="100" fill="red" transform="translate(100 100)"/>
-		<rect id="r2" width="100" height="100" fill="red" transform="translate(190 100)"/>
-		<rect id="r3" width="100" height="100" fill="lime" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Shape" transform="translate(300 100)"/>
-		
-		<rect id="fail" width="100%" height="100%" fill="none"/>
+    <defs>
+      <script type="application/ecmascript">
+        function runtest()
+        {
+          try {
+            var r1 = document.getElementById("r1");
+            var r2 = document.getElementById("r2");
+            var r3 = document.getElementById("r3");
+            var i0 = r1.requiredFeatures.getItem(0);
+            var i1 = r1.requiredFeatures.getItem(1);
+
+            if(i0 != "http://www.w3.org/TR/SVG11/feature#Shape")
+            {
+              r1.removeAttribute("requiredFeatures");
+              return;
+            }
+            if(i1 != "this.is.a.bogus.feature.string")
+            {
+              r1.removeAttribute("requiredFeatures");
+              return;
+            }
+            if(r1.requiredFeatures.numberOfItems != 2)
+            {
+              r1.removeAttribute("requiredFeatures");
+              return;
+            }
+            r2.requiredFeatures.appendItem(i1);
+            if(r1.requiredFeatures.numberOfItems != 2)
+            {
+              r1.removeAttribute("requiredFeatures");
+              return;
+            }
+            if(r2.requiredFeatures.numberOfItems != 1)
+            {
+              r1.removeAttribute("requiredFeatures");
+              return;
+            }
+            r3.requiredFeatures.insertItemBefore(i0,0);
+            if(r3.requiredFeatures.numberOfItems != 2)
+            {
+              r3.setAttribute("fill", "red");
+              return;
+            }
+            if(r1.requiredFeatures.numberOfItems != 2)
+            {
+              r1.removeAttribute("requiredFeatures");
+              return;
+            }
+          }
+          catch(e)
+          {
+            var f = document.getElementById("fail");
+            f.setAttribute("fill", "red");
+          }
+        }
+      </script>
+    </defs>
+
+    <text x="50%" y="70" text-anchor="middle">SVGStringList DOM</text>
+
+    <!-- background images -->
+    <g>
+      <rect width="100" height="100" fill="lime" transform="translate(80 100)"/>
+      <rect width="100" height="100" fill="lime" transform="translate(190 100)"/>
+      <rect width="100" height="100" fill="red" transform="translate(300 100)"/>
+    </g>
+
+    <!-- tests -->
+    <rect id="r1" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Shape this.is.a.bogus.feature.string" width="100" height="100" fill="red" transform="translate(100 100)"/>
+    <rect id="r2" width="100" height="100" fill="red" transform="translate(190 100)"/>
+    <rect id="r3" width="100" height="100" fill="lime" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Shape" transform="translate(300 100)"/>
+
+    <rect id="fail" width="100%" height="100%" fill="none"/>
   </g>
   <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
     <text id="revision" x="10" y="340" stroke="none"
diff --git a/svg/import/types-dom-07-f-manual.svg b/svg/import/types-dom-07-f-manual.svg
index 6abf6ca..4313039 100644
--- a/svg/import/types-dom-07-f-manual.svg
+++ b/svg/import/types-dom-07-f-manual.svg
@@ -53,7 +53,7 @@
   <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
 
     <text x='210' y='55' text-anchor='end'>animVal is read only:</text>
- 
+
     <g font-size='14' transform='translate(500)rotate(90)'>
       <rect id='r1a' x='40' y='60' width='20' height='20'/>
       <text x='70' y='75'>SVGAnimatedNumberList</text>
diff --git a/svg/import/types-dom-08-f-manual.svg b/svg/import/types-dom-08-f-manual.svg
index b2d1eaf..cd6bbcc 100644
--- a/svg/import/types-dom-08-f-manual.svg
+++ b/svg/import/types-dom-08-f-manual.svg
@@ -67,18 +67,18 @@
         <text id="TestStatus" x="333" y="350" font-size="40">failed</text>
       </g>
     </g>
-    
+
         <script><![CDATA[
       var svg_ns = "http://www.w3.org/2000/svg";
       var xlink_ns = "http://www.w3.org/1999/xlink";
-  
+
       var count=0;
       var topsvg = document.documentElement;
       var startY = 80;
       var rowHeight = 12;
       var fontSize = "10";
       var isPassed = true;
-  
+
       function drawString( text, color )
       {
         node_to_insert=document.createElementNS(svg_ns,"text");
@@ -89,10 +89,10 @@
         node_to_insert.setAttributeNS(null, "y",  yVal.toString());
         node_to_insert.setAttributeNS(null, "fill",  color  );
         node_to_insert.appendChild(document.createTextNode(text));
-        document.getElementById( "test-body-content" ).appendChild(node_to_insert);          
-       
+        document.getElementById( "test-body-content" ).appendChild(node_to_insert);
+
       }
-  
+
       function verifyBBox( bbox, x, y, width, height, epsilon )
       {
         if
@@ -100,7 +100,7 @@
           ( Math.abs(bbox.x - x ) < epsilon ) &&
           ( Math.abs(bbox.y - y ) < epsilon ) &&
           ( Math.abs(bbox.width - width ) < epsilon ) &&
-          ( Math.abs(bbox.height - height ) < epsilon ) 
+          ( Math.abs(bbox.height - height ) < epsilon )
         )
         {
           return "green";
@@ -112,28 +112,28 @@
           return "red";
         }
       }
-  
+
       function displayGetBBox( )
       {
-        drawString( "getBBox()", "black" );             
+        drawString( "getBBox()", "black" );
         var bbox = document.getElementById( "group1" ).getBBox( );
-        drawString( "[group1] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height, verifyBBox( bbox, -70, -60, 230, 200, 1 / 65535 ) );            
-  
+        drawString( "[group1] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height, verifyBBox( bbox, -70, -60, 230, 200, 1 / 65535 ) );
+
         bbox = document.getElementById( "rect1" ).getBBox( );
         drawString( "[rect1] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height, verifyBBox( bbox, 10, 10, 50, 50, 1 / 65535 ));
-  
+
         bbox = document.getElementById( "rect2" ).getBBox( );
         drawString( "[rect2] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height , verifyBBox( bbox, 10, 10, 100, 100, 1 / 65535 ));
-  
+
         bbox = document.getElementById( "group2" ).getBBox( );
         drawString( "[group2] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height, verifyBBox( bbox, -80, -80, 230, 200, 1 / 65535 ));
-  
+
         bbox = document.getElementById( "rect3" ).getBBox( );
         drawString( "[rect3] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height ,  verifyBBox( bbox, 0, 10, 150, 50, 1 / 65535 ));
-  
+
         bbox = document.getElementById( "circle1" ).getBBox( );
         drawString( "[circle1] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height , verifyBBox( bbox, -80, -80, 200, 200, 1 / 65535 ));
-        
+
         bbox = document.getElementById( "rect4" ).getBBox( );
         drawString( "[rect4] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height, verifyBBox( bbox, 10, 10, 400, 0, 1 / 65535 ));
 
@@ -144,9 +144,9 @@
         drawString( "[thickLine] " + bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height , verifyBBox( bbox, 0, 0, 100, 0, 1 / 65535 ));
 
       }
-  
+
       displayGetBBox();
-  
+
       // remove from tree, get bbox
       drawString( "node removed from tree, should still have bounding box", "black" );
       circle = document.getElementById( "circle2" );
@@ -166,7 +166,6 @@
         drawString( "Results do not match expected values (null)" , "red");
       }
 
-
       if( isPassed )
       {
         var status = document.getElementById("TestStatus");
diff --git a/svg/import/types-dom-svgfittoviewbox-01-f-manual.svg b/svg/import/types-dom-svgfittoviewbox-01-f-manual.svg
index e787925..1071399 100644
--- a/svg/import/types-dom-svgfittoviewbox-01-f-manual.svg
+++ b/svg/import/types-dom-svgfittoviewbox-01-f-manual.svg
@@ -39,9 +39,9 @@
     <svg id="testSvg" viewBox="0 0 300 200" preserveAspectRatio="xMinYMin slice" />
     <text id="failText" x="100" y="100" font-size="80" fill="red">FAIL</text>
     <text id="passText" x="350" y="300" font-size="50" display="none">PASS</text>
-    <script type="text/javascript">    
+    <script type="text/javascript">
       <![CDATA[
- 
+
         try
         {
             var testElement = document.getElementById("testSvg");
@@ -68,7 +68,7 @@
         catch(ex)
         {
           alert("ERROR: " + ex.message);
-      
+
         }
         ]]>
 
diff --git a/svg/import/types-dom-svgnumberlist-01-f-manual.svg b/svg/import/types-dom-svgnumberlist-01-f-manual.svg
index 96e230c..7f6705a 100644
--- a/svg/import/types-dom-svgnumberlist-01-f-manual.svg
+++ b/svg/import/types-dom-svgnumberlist-01-f-manual.svg
@@ -48,7 +48,7 @@
             var testElement = document.getElementById("test");
             var svgList = testElement.rotate.baseVal;
             var passingTests = 0;
-             
+
             try
             {
                 svgList.getItem(5);
diff --git a/svg/import/types-dom-svgstringlist-01-f-manual.svg b/svg/import/types-dom-svgstringlist-01-f-manual.svg
index 17a457c..89ccb1d 100644
--- a/svg/import/types-dom-svgstringlist-01-f-manual.svg
+++ b/svg/import/types-dom-svgstringlist-01-f-manual.svg
@@ -47,7 +47,7 @@
             var testElement = document.getElementById("test");
             var svgList = testElement.requiredExtensions;
             var passingTests = 0;
-             
+
             try
             {
                 svgList.getItem(5);
@@ -94,7 +94,7 @@
         {
 
             alert("ERROR: " + ex.message);
-      
+
         }]]>
     </script>
   </g>
diff --git a/svg/interfaces.html b/svg/interfaces.html
index b171185..698e6e7 100644
--- a/svg/interfaces.html
+++ b/svg/interfaces.html
@@ -13,7 +13,7 @@
 interface ProcessingInstruction : Node {};
 interface Element : Node {};
 interface HTMLElement : Element {};
-[PrimaryGlobal] interface Window {};
+[Global=Window, Exposed=Window] interface Window {};
 
 // Just need to know that DOMStringMap exists; its details are tested elsewhere.
 interface DOMStringMap { };
diff --git a/svg/linking/reftests/href-a-element-attr-change.html b/svg/linking/reftests/href-a-element-attr-change.html
index ceb0f18..c74b2e0 100644
--- a/svg/linking/reftests/href-a-element-attr-change.html
+++ b/svg/linking/reftests/href-a-element-attr-change.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html class="retest-wait">
+<html class="reftest-wait">
 <meta charset="utf-8">
 <title>href - a element</title>
 <meta name="assert"
diff --git a/svg/path/bearing/absolute-ref.svg b/svg/path/bearing/absolute-ref.svg
new file mode 100644
index 0000000..85b2027
--- /dev/null
+++ b/svg/path/bearing/absolute-ref.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+  </style>
+  <path d="M 20 150 v -120 h 140 v 120 z" />
+</svg>
diff --git a/svg/path/bearing/absolute.svg b/svg/path/bearing/absolute.svg
new file mode 100644
index 0000000..0efc295
--- /dev/null
+++ b/svg/path/bearing/absolute.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
+  <metadata>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://www.w3.org/TR/svg-paths/#PathDataBearingCommands"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://www.w3.org/TR/SVG2/paths.html#PathDataBearingCommands"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://svgwg.org/svg2-draft/changes.html#paths"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="match" href="absolute-ref.svg"/>
+    <meta xmlns="http://www.w3.org/1999/xhtml" name="assert" content="path element with B commands renders correctly."/>
+  </metadata>
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+  </style>
+  <path d="M 20 150 B -90 h 120 B 0 h 140 B 90 h 120 z" />
+</svg>
diff --git a/svg/path/bearing/relative-ref.svg b/svg/path/bearing/relative-ref.svg
new file mode 100644
index 0000000..85b2027
--- /dev/null
+++ b/svg/path/bearing/relative-ref.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+  </style>
+  <path d="M 20 150 v -120 h 140 v 120 z" />
+</svg>
diff --git a/svg/path/bearing/relative.svg b/svg/path/bearing/relative.svg
new file mode 100644
index 0000000..75b87ff
--- /dev/null
+++ b/svg/path/bearing/relative.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
+  <metadata>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://www.w3.org/TR/svg-paths/#PathDataBearingCommands"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://www.w3.org/TR/SVG2/paths.html#PathDataBearingCommands"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://svgwg.org/svg2-draft/changes.html#paths"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="match" href="absolute-ref.svg"/>
+    <meta xmlns="http://www.w3.org/1999/xhtml" name="assert" content="path element with b commands renders correctly."/>
+  </metadata>
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+  </style>
+  <path d="M 20 150 b -90 h 120 b 90 h 140 b 90 h 120 z" />
+</svg>
diff --git a/svg/path/bearing/zero-ref.svg b/svg/path/bearing/zero-ref.svg
new file mode 100644
index 0000000..3558de3
--- /dev/null
+++ b/svg/path/bearing/zero-ref.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+  </style>
+  <path d="M 25 50 h 10 m 10 0 h 10 m 10 0 h 10" />
+</svg>
diff --git a/svg/path/bearing/zero.svg b/svg/path/bearing/zero.svg
new file mode 100644
index 0000000..8025642
--- /dev/null
+++ b/svg/path/bearing/zero.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+  <metadata>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://www.w3.org/TR/svg-paths/#PathDataBearingCommands"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://www.w3.org/TR/SVG2/paths.html#PathDataBearingCommands"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="help" href="https://svgwg.org/svg2-draft/changes.html#paths"/>
+    <link xmlns="http://www.w3.org/1999/xhtml" rel="match" href="zero-ref.svg"/>
+    <meta xmlns="http://www.w3.org/1999/xhtml" name="assert" content="path element with bearing 0 renders correctly."/>
+  </metadata>
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+  </style>
+  <path d="M 25 50 h 10 B 0 m 10 0 h 10 b 0 m 10 0 h 10" />
+</svg>
diff --git a/svg/path/closepath/segment-completing-ref.svg b/svg/path/closepath/segment-completing-ref.svg
new file mode 100644
index 0000000..cc068ce
--- /dev/null
+++ b/svg/path/closepath/segment-completing-ref.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="300" height="200">
+  <style>
+    path {
+      stroke: blue;
+    }
+  </style>
+  <path d="M 10 10 z m 20 70 h 10 v 10 h -10 l 0 -10 M 70 30 q 20 0 20 20 t -20 20 t -20 -20 T 70 30" />
+</svg>
diff --git a/svg/path/closepath/segment-completing.svg b/svg/path/closepath/segment-completing.svg
new file mode 100644
index 0000000..5b72c49
--- /dev/null
+++ b/svg/path/closepath/segment-completing.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     width="300" height="200">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#paths-PathDataClosePathCommand"/>
+    <h:link rel="match" href="segment-completing-ref.svg"/>
+    <h:meta name="assert" content="initial subpath point used to complete segment."/>
+  </metadata>
+  <style>
+    path {
+      stroke: blue;
+    }
+  </style>
+  <path d="M 10 10 z m 20 70 h 10 v 10 h -10 l z M 70 30 q 20 0 20 20 t -20 20 t -20 -20 T z" />
+</svg>
diff --git a/svg/path/distance/pathLength-positive-percentage.svg b/svg/path/distance/pathLength-positive-percentage.svg
new file mode 100644
index 0000000..d8e28b0
--- /dev/null
+++ b/svg/path/distance/pathLength-positive-percentage.svg
@@ -0,0 +1,36 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="300" height="200" viewBox="0 0 300 200"
+     font-family="sans-serif" font-size="28">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#PathLengthAttribute"/>
+    <h:link rel="match" href="pathLength-positive-ref.svg"/>
+    <h:meta name="assert" content="pathLength scales distance along the path"/>
+  </metadata>
+
+  <defs>
+    <path id="track" stroke="lightblue" fill="none" d="M 50 50 h 200" pathLength="2"/>
+  </defs>
+
+  <g>
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track" startOffset="0%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,50)">
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track" startOffset="50%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,100)">
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track" startOffset="-50%">The quick brown fox</textPath>
+    </text>
+  </g>
+</svg>
diff --git a/svg/path/distance/pathLength-positive-ref.svg b/svg/path/distance/pathLength-positive-ref.svg
new file mode 100644
index 0000000..9a9a1a8
--- /dev/null
+++ b/svg/path/distance/pathLength-positive-ref.svg
@@ -0,0 +1,32 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="300" height="200" viewBox="0 0 300 200">
+
+  <defs>
+    <path id="track" stroke="lightblue" fill="none"
+    d="M 50 50 h 200"/>
+  </defs>
+
+  <g>
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="0%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,50)">
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="50%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,100)">
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="-50%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+</svg>
+
diff --git a/svg/path/distance/pathLength-positive.svg b/svg/path/distance/pathLength-positive.svg
new file mode 100644
index 0000000..b40d6b8
--- /dev/null
+++ b/svg/path/distance/pathLength-positive.svg
@@ -0,0 +1,39 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="300" height="200" viewBox="0 0 300 200">
+
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#PathLengthAttribute"/>
+    <h:link rel="match" href="pathLength-positive-ref.svg"/>
+    <h:meta name="assert" content="pathLength scales distance along the path"/>
+  </metadata>
+
+  <defs>
+    <path id="track" stroke="lightblue" fill="none"
+    d="M 50 50 h 200" pathLength="2"/>
+  </defs>
+
+  <g>
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="0">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,50)">
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="1">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,100)">
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="-1">The quick brown fox</textPath>
+    </text>
+  </g>
+
+</svg>
+
diff --git a/svg/path/distance/pathLength-zero-percentage-ref.svg b/svg/path/distance/pathLength-zero-percentage-ref.svg
new file mode 100644
index 0000000..dca91c1
--- /dev/null
+++ b/svg/path/distance/pathLength-zero-percentage-ref.svg
@@ -0,0 +1,30 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="300" height="200" viewBox="0 0 300 200"
+     font-family="sans-serif" font-size="28">
+  <defs>
+    <path id="track" stroke="lightblue" fill="none" d="M 50 50 h 200"/>
+  </defs>
+
+  <g>
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,50)">
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,100)">
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track">The quick brown fox</textPath>
+    </text>
+  </g>
+</svg>
+
diff --git a/svg/path/distance/pathLength-zero-percentage.svg b/svg/path/distance/pathLength-zero-percentage.svg
new file mode 100644
index 0000000..24213e4
--- /dev/null
+++ b/svg/path/distance/pathLength-zero-percentage.svg
@@ -0,0 +1,36 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="300" height="200" viewBox="0 0 300 200"
+     font-family="sans-serif" font-size="28">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#PathLengthAttribute"/>
+    <h:link rel="match" href="pathLength-zero-percentage-ref.svg"/>
+    <h:meta name="assert" content="A value of zero is valid and must be treated as a scaling factor of infinity."/>
+  </metadata>
+
+  <defs>
+    <path id="track" stroke="lightblue" fill="none" d="M 50 50 h 200" pathLength="0"/>
+  </defs>
+
+  <g>
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track" startOffset="0%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,50)">
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track" startOffset="50%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,100)">
+    <use xlink:href="#track"/>
+    <text>
+      <textPath xlink:href="#track" startOffset="-50%">The quick brown fox</textPath>
+    </text>
+  </g>
+</svg>
diff --git a/svg/path/distance/pathLength-zero-ref.svg b/svg/path/distance/pathLength-zero-ref.svg
new file mode 100644
index 0000000..d5556ad
--- /dev/null
+++ b/svg/path/distance/pathLength-zero-ref.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="300" height="200" viewBox="0 0 300 200">
+
+  <defs>
+    <path id="track" stroke="lightblue" fill="none"
+    d="M 50 50 h 200"/>
+  </defs>
+
+  <g>
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="0%">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,50)">
+    <use xlink:href="#track"/>
+  </g>
+
+  <g transform="translate(0,100)">
+    <use xlink:href="#track"/>
+  </g>
+
+</svg>
+
diff --git a/svg/path/distance/pathLength-zero.svg b/svg/path/distance/pathLength-zero.svg
new file mode 100644
index 0000000..4ae0b83
--- /dev/null
+++ b/svg/path/distance/pathLength-zero.svg
@@ -0,0 +1,39 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="300" height="200" viewBox="0 0 300 200">
+
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#PathLengthAttribute"/>
+    <h:link rel="match" href="pathLength-zero-ref.svg"/>
+    <h:meta name="assert" content="A value of zero is valid and must be treated as a scaling factor of infinity."/>
+  </metadata>
+
+  <defs>
+    <path id="track" stroke="lightblue" fill="none"
+    d="M 50 50 h 200" pathLength="0"/>
+  </defs>
+
+  <g>
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="0">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,50)">
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="1">The quick brown fox</textPath>
+    </text>
+  </g>
+
+  <g transform="translate(0,100)">
+    <use xlink:href="#track"/>
+    <text font-family="sans-serif" font-size="28">
+      <textPath xlink:href="#track" startOffset="-1">The quick brown fox</textPath>
+    </text>
+  </g>
+
+</svg>
+
diff --git a/svg/path/error-handling/bounding.svg b/svg/path/error-handling/bounding.svg
new file mode 100644
index 0000000..85c089b
--- /dev/null
+++ b/svg/path/error-handling/bounding.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#paths-PathDataErrorHandling"/>
+    <h:meta name="assert" content="render up to (but not including) the first error"/>
+  </metadata>
+  <g id="container">
+    <path transform="translate(90,10)" />
+    <path transform="translate(80,20)" d="none" />
+    <path transform="translate(70,30)" d="# invalid" />
+    <path transform="translate(60,40)" d="m 0 0 l 3 -4 z # ignored suffix v 123" />
+    <path transform="translate(50,50)" d="" />
+    <path transform="translate(40,60)" d="m 0 0 l -9 11 -123 z # ignore last l parameter" />
+    <polygon transform="translate(20,80)" />
+    <polyline transform="translate(10,90)" />
+  </g>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+  test(function() {
+    var container = document.getElementById('container');
+    var bbox = container.getBBox();
+
+    // The rendered paths are "m 0 0 l 3 -4 z" and "m 0 0 l -9 11"
+    assert_equals(bbox.x, 31);
+    assert_equals(bbox.y, 36);
+
+    assert_equals(bbox.width, 32);
+    assert_equals(bbox.height, 35);
+  });
+  ]]></script>
+</svg>
diff --git a/svg/path/error-handling/render-until-error-ref.svg b/svg/path/error-handling/render-until-error-ref.svg
new file mode 100644
index 0000000..dd2d7c3
--- /dev/null
+++ b/svg/path/error-handling/render-until-error-ref.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="100" height="100">
+  <style>
+    path {
+      stroke: lime;
+    }
+  </style>
+  <g id="container">
+    <path transform="translate(60,40)" d="m 0 0 l 3 -4 z" />
+    <path transform="translate(40,60)" d="m 0 0 l -9 11" />
+  </g>
+</svg>
diff --git a/svg/path/error-handling/render-until-error.svg b/svg/path/error-handling/render-until-error.svg
new file mode 100644
index 0000000..6b8a50b
--- /dev/null
+++ b/svg/path/error-handling/render-until-error.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     width="100" height="100">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#paths-PathDataErrorHandling"/>
+    <h:link rel="match" href="render-until-error-ref.svg"/>
+    <h:meta name="assert" content="render up to (but not including) the first error"/>
+  </metadata>
+  <style>
+    path {
+      stroke: lime;
+    }
+  </style>
+  <g id="container">
+    <path transform="translate(90,10)" />
+    <path transform="translate(80,20)" d="none" />
+    <path transform="translate(70,30)" d="# invalid" />
+    <path transform="translate(60,40)" d="m 0 0 l 3 -4 z # ignored suffix v 123" />
+    <path transform="translate(50,50)" d="" />
+    <path transform="translate(40,60)" d="m 0 0 l -9 11 -123 z # ignore last l parameter" />
+    <polygon transform="translate(20,80)" />
+    <polyline transform="translate(10,90)" />
+  </g>
+</svg>
diff --git a/svg/path/interfaces/SVGAnimatedPathData-removed.svg b/svg/path/interfaces/SVGAnimatedPathData-removed.svg
new file mode 100644
index 0000000..d321b8b
--- /dev/null
+++ b/svg/path/interfaces/SVGAnimatedPathData-removed.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#changes-paths"/>
+    <h:link rel="help" href="https://www.w3.org/TR/SVG11/paths.html#InterfaceSVGAnimatedPathData"/>
+    <h:meta name="assert" content="SVGAnimatedPathData interface is not supported."/>
+  </metadata>
+  <path id="track" d="m 10 20 h 30"/>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+  test(function() {
+    assert_true(window.SVGAnimatedPathData === undefined);
+
+    var track = document.getElementById('track');
+    assert_equals(track.__proto__, SVGPathElement.prototype);
+    assert_true(track.pathSegList === undefined);
+    assert_true(track.normalizedPathSegList === undefined);
+    assert_true(track.animatedPathSegList === undefined);
+    assert_true(track.animatedNormalizedPathSegList === undefined);
+  });
+  ]]></script>
+</svg>
diff --git a/svg/path/property/d-interpolation-discrete.svg b/svg/path/property/d-interpolation-discrete.svg
new file mode 100644
index 0000000..aa90873
--- /dev/null
+++ b/svg/path/property/d-interpolation-discrete.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#TheDProperty"/>
+    <h:meta name="assert" content="d falls back to step interpolation when no interpolation is possible."/>
+  </metadata>
+
+  <g id="container"/>
+
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <h:script src="resources/interpolation-test-common.js"/>
+  <script><![CDATA[
+      'use strict';
+
+      // Distinct number of path segments
+      test_no_interpolation({
+        property: 'd',
+        from: "path('M 0 0 H 1 H 2')",
+        to: "path('M 0 0 H 3')"
+      });
+
+      test_no_interpolation({
+        property: 'd',
+        from: "path('M 1 2 L 3 4 Z')",
+        to: "none"
+      });
+
+      // Distinct segment types
+      test_no_interpolation({
+        property: 'd',
+        from: "path('M 10 0 H 11')",
+        to: "path('M 20 0 V 2')"
+      });
+
+      test_no_interpolation({
+        property: 'd',
+        from: "path('M 1 2 L 4 6 Z')",
+        to: "path('M 1 2 H 4 V 6')"
+      });
+  ]]></script>
+</svg>
diff --git a/svg/path/property/d-interpolation-relative-absolute.svg b/svg/path/property/d-interpolation-relative-absolute.svg
new file mode 100644
index 0000000..65d2f3b
--- /dev/null
+++ b/svg/path/property/d-interpolation-relative-absolute.svg
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#TheDProperty"/>
+    <h:meta name="assert" content="d can interpolate between absolute and relative paths."/>
+  </metadata>
+
+  <g id="container"/>
+
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <h:script src="resources/interpolation-test-common.js"/>
+  <script><![CDATA[
+      'use strict';
+
+      // Mix relative and non-relative
+      test_interpolation({
+        property: 'd',
+        from: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')",
+        to: "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 300 100 z')"
+      }, [
+        {at: -1, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 0 -100 Z')"},
+        {at: 0, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')"},
+        {at: 0.125, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 225 125 Z')"},
+        {at: 0.875, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 375 275 Z')"},
+        {at: 1, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 400 300 Z')"},
+        {at: 2, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 600 500 Z')"},
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')",
+        to: "path('M 0 0 L 100 100 m 0 100 l 100 0 z l 100 -100 z')"
+      }, [
+        {at: -1, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')"},
+        {at: 0, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')"},
+        {at: 0.125, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')"},
+        {at: 0.875, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')"},
+        {at: 1, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')"},
+        {at: 2, expect: "path('M 0 0 L 100 100 M 100 200 L 200 200 Z L 200 100 Z')"},
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 10 20 l 40 50 z l 40 60 z m 60 70 l 90 60 z t 70 130')",
+        to: "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')"
+      }, [
+        {at: -1, expect: "path('M -190 -180 L -70 -50 Z L 10 40 Z M 30 50 L 120 70 Z T 60 220')"},
+        {at: 0, expect: "path('M 10 20 L 50 70 Z L 50 80 Z M 70 90 L 160 150 Z T 140 220')"},
+        {at: 0.125, expect: "path('M 35 45 L 65 85 Z L 55 85 Z M 75 95 L 165 160 Z T 150 220')"},
+        {at: 0.875, expect: "path('M 185 195 L 155 175 Z L 85 115 Z M 105 125 L 195 220 Z T 210 220')"},
+        {at: 1, expect: "path('M 210 220 L 170 190 Z L 90 120 Z M 110 130 L 200 230 Z T 220 220')"},
+        {at: 2, expect: "path('M 410 420 L 290 310 Z L 130 160 Z M 150 170 L 240 310 Z T 300 220')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 10 20 c 40 50 30 60 80 70 c 120 130 170 140 110 160')",
+        to: "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')"
+      }, [
+        {at: -1, expect: "path('M -110 -60 C -30 -10 -40 0 -30 10 C 130 140 180 150 80 170')"},
+        {at: 0, expect: "path('M 10 20 C 50 70 40 80 90 90 C 210 220 260 230 200 250')"},
+        {at: 0.125, expect: "path('M 25 30 C 60 80 50 90 105 100 C 220 230 270 240 215 260')"},
+        {at: 0.875, expect: "path('M 115 90 C 120 140 110 150 195 160 C 280 290 330 300 305 320')"},
+        {at: 1, expect: "path('M 130 100 C 130 150 120 160 210 170 C 290 300 340 310 320 330')"},
+        {at: 2, expect: "path('M 250 180 C 210 230 200 240 330 250 C 370 380 420 390 440 410')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 10 20 q 30 60 40 50 q 110 80 90 80')",
+        to: "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')"
+      }, [
+        {at: -1, expect: "path('M -110 -60 Q -40 0 -30 -10 Q 120 150 100 110')"},
+        {at: 0, expect: "path('M 10 20 Q 40 80 50 70 Q 160 150 140 150')"},
+        {at: 0.125, expect: "path('M 25 30 Q 50 90 60 80 Q 165 150 145 155')"},
+        {at: 0.875, expect: "path('M 115 90 Q 110 150 120 140 Q 195 150 175 185')"},
+        {at: 1, expect: "path('M 130 100 Q 120 160 130 150 Q 200 150 180 190')"},
+        {at: 2, expect: "path('M 250 180 Q 200 240 210 230 Q 240 150 220 230')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 10 20 s 30 60 40 50 s 110 60 90 70')",
+        to: "path('M 130 140 S 120 160 130 150 S 200 170 140 180')"
+      }, [
+        {at: -1, expect: "path('M -110 -100 S -40 0 -30 -10 S 120 90 140 100')"},
+        {at: 0, expect: "path('M 10 20 S 40 80 50 70 S 160 130 140 140')"},
+        {at: 0.125, expect: "path('M 25 35 S 50 90 60 80 S 165 135 140 145')"},
+        {at: 0.875, expect: "path('M 115 125 S 110 150 120 140 S 195 165 140 175')"},
+        {at: 1, expect: "path('M 130 140 S 120 160 130 150 S 200 170 140 180')"},
+        {at: 2, expect: "path('M 250 260 S 200 240 210 230 S 240 210 140 220')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60 Z')",
+        to: "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170 Z')"
+      }, [
+        {at: -1, expect: "path('M -110 -100 H -40 V 0 H -30 V -10 L 120 90 Z')"},
+        {at: 0, expect: "path('M 10 20 H 40 V 80 H 50 V 70 L 160 130 Z')"},
+        {at: 0.125, expect: "path('M 25 35 H 50 V 90 H 60 V 80 L 165 135 Z')"},
+        {at: 0.875, expect: "path('M 115 125 H 110 V 150 H 120 V 140 L 195 165 Z')"},
+        {at: 1, expect: "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170 Z')"},
+        {at: 2, expect: "path('M 250 260 H 200 V 240 H 210 V 230 L 240 210 Z')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 10 20 a 10 20 30 1 0 40 50 a 110 120 30 1 1 140 50')",
+        to: "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')"
+      }, [
+        {at: -1, expect: "path('M 2 28 A -30 -60 -10 1 0 10 30 A 70 80 -10 1 1 310 160')"},
+        {at: 0, expect: "path('M 10 20 A 10 20 30 1 0 50 70 A 110 120 30 1 1 190 120')"},
+        {at: 0.125, expect: "path('M 11 19 A 15 30 35 1 0 55 75 A 115 125 35 1 1 175 115')"},
+        {at: 0.875, expect: "path('M 17 13 A 45 90 65 0 1 85 105 A 145 155 65 0 1 85 85')"},
+        {at: 1, expect: "path('M 18 12 A 50 100 70 0 1 90 110 A 150 160 70 0 1 70 80')"},
+        {at: 2, expect: "path('M 26 4 A 90 180 110 0 1 130 150 A 190 200 110 0 1 -50 40')"}
+      ]);
+  ]]></script>
+</svg>
diff --git a/svg/path/property/d-interpolation-single.svg b/svg/path/property/d-interpolation-single.svg
new file mode 100644
index 0000000..25c5231
--- /dev/null
+++ b/svg/path/property/d-interpolation-single.svg
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#TheDProperty"/>
+    <h:meta name="assert" content="Each path command interpolates."/>
+  </metadata>
+
+  <g id="container"/>
+
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <h:script src="resources/interpolation-test-common.js"/>
+  <script><![CDATA[
+      'use strict';
+
+      // Exercise each segment type
+      test_interpolation({
+        property: 'd',
+        from: "path('M 20 70')",
+        to: "path('M 100 30')"
+      }, [
+        {at: -1, expect: "path('M -60 110')"},
+        {at: 0, expect: "path('M 20 70')"},
+        {at: 0.125, expect: "path('M 30 65')"},
+        {at: 0.875, expect: "path('M 90 35')"},
+        {at: 1, expect: "path('M 100 30')"},
+        {at: 2, expect: "path('M 180 -10')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 20 70')",
+        to: "path('m 100 30')"
+      }, [
+        {at: -1, expect: "path('M -60 110')"},
+        {at: 0, expect: "path('M 20 70')"},
+        {at: 0.125, expect: "path('M 30 65')"},
+        {at: 0.875, expect: "path('M 90 35')"},
+        {at: 1, expect: "path('M 100 30')"},
+        {at: 2, expect: "path('M 180 -10')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 100 200 L 120 270')",
+        to: "path('m 100 200 L 200 230')"
+      }, [
+        {at: -1, expect: "path('M 100 200 L 40 310')"},
+        {at: 0, expect: "path('M 100 200 L 120 270')"},
+        {at: 0.125, expect: "path('M 100 200 L 130 265')"},
+        {at: 0.875, expect: "path('M 100 200 L 190 235')"},
+        {at: 1, expect: "path('M 100 200 L 200 230')"},
+        {at: 2, expect: "path('M 100 200 L 280 190')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('m 100 200 l 20 70')",
+        to: "path('m 100 200 l 100 30')"
+      }, [
+        {at: -1, expect: "path('M 100 200 L 40 310')"},
+        {at: 0, expect: "path('M 100 200 L 120 270')"},
+        {at: 0.125, expect: "path('M 100 200 L 130 265')"},
+        {at: 0.875, expect: "path('M 100 200 L 190 235')"},
+        {at: 1, expect: "path('M 100 200 L 200 230')"},
+        {at: 2, expect: "path('M 100 200 L 280 190')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 20 10 C 32 42 52 62 120 2200')",
+        to: "path('M 20 10 C 40 50 60 70 200 3000')",
+      }, [
+        {at: -1, expect: "path('M 20 10 C 24 34 44 54 40 1400')"},
+        {at: 0, expect: "path('M 20 10 C 32 42 52 62 120 2200')"},
+        {at: 0.125, expect: "path('M 20 10 C 33 43 53 63 130 2300')"},
+        {at: 0.875, expect: "path('M 20 10 C 39 49 59 69 190 2900')"},
+        {at: 1, expect: "path('M 20 10 C 40 50 60 70 200 3000')"},
+        {at: 2, expect: "path('M 20 10 C 48 58 68 78 280 3800')"}
+      ]);
+      test_interpolation({
+        property: 'd',
+        from: "path('m 20 10 c 12 32 32 52 100 2190')",
+        to: "path('m 20 10 c 20 40 40 60 180 2990')"
+      }, [
+        {at: -1, expect: "path('M 20 10 C 24 34 44 54 40 1400')"},
+        {at: 0, expect: "path('M 20 10 C 32 42 52 62 120 2200')"},
+        {at: 0.125, expect: "path('M 20 10 C 33 43 53 63 130 2300')"},
+        {at: 0.875, expect: "path('M 20 10 C 39 49 59 69 190 2900')"},
+        {at: 1, expect: "path('M 20 10 C 40 50 60 70 200 3000')"},
+        {at: 2, expect: "path('M 20 10 C 48 58 68 78 280 3800')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 20 10 Q 32 42 120 2200')",
+        to: "path('M 20 10 Q 40 50 200 3000')"
+      }, [
+        {at: -1, expect: "path('M 20 10 Q 24 34 40 1400')"},
+        {at: 0, expect: "path('M 20 10 Q 32 42 120 2200')"},
+        {at: 0.125, expect: "path('M 20 10 Q 33 43 130 2300')"},
+        {at: 0.875, expect: "path('M 20 10 Q 39 49 190 2900')"},
+        {at: 1, expect: "path('M 20 10 Q 40 50 200 3000')"},
+        {at: 2, expect: "path('M 20 10 Q 48 58 280 3800')"}
+      ]);
+      test_interpolation({
+        property: 'd',
+        from: "path('m 20 10 q 12 32 100 2190')",
+        to: "path('m 20 10 q 20 40 180 2990')"
+      }, [
+        {at: -1, expect: "path('M 20 10 Q 24 34 40 1400')"},
+        {at: 0, expect: "path('M 20 10 Q 32 42 120 2200')"},
+        {at: 0.125, expect: "path('M 20 10 Q 33 43 130 2300')"},
+        {at: 0.875, expect: "path('M 20 10 Q 39 49 190 2900')"},
+        {at: 1, expect: "path('M 20 10 Q 40 50 200 3000')"},
+        {at: 2, expect: "path('M 20 10 Q 48 58 280 3800')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 100 400 A 10 20 30 1 0 140 450')",
+        to: "path('M 300 200 A 50 60 70 0 1 380 290')"
+      }, [
+        {at: -1, expect: "path('M -100 600 A -30 -20 -10 1 0 -100 610')"},
+        {at: 0, expect: "path('M 100 400 A 10 20 30 1 0 140 450')"},
+        {at: 0.125, expect: "path('M 125 375 A 15 25 35 1 0 170 430')"},
+        {at: 0.875, expect: "path('M 275 225 A 45 55 65 0 1 350 310')"},
+        {at: 1, expect: "path('M 300 200 A 50 60 70 0 1 380 290')"},
+        {at: 2, expect: "path('M 500 0 A 90 100 110 0 1 620 130')"}
+      ]);
+      test_interpolation({
+        property: 'd',
+        from: "path('m 100 400 a 10 20 30 1 0 40 50')",
+        to: "path('m 300 200 a 50 60 70 0 1 80 90')"
+      }, [
+        {at: -1, expect: "path('M -100 600 A -30 -20 -10 1 0 -100 610')"},
+        {at: 0, expect: "path('M 100 400 A 10 20 30 1 0 140 450')"},
+        {at: 0.125, expect: "path('M 125 375 A 15 25 35 1 0 170 430')"},
+        {at: 0.875, expect: "path('M 275 225 A 45 55 65 0 1 350 310')"},
+        {at: 1, expect: "path('M 300 200 A 50 60 70 0 1 380 290')"},
+        {at: 2, expect: "path('M 500 0 A 90 100 110 0 1 620 130')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 50 60 H 70')",
+        to: "path('M 10 140 H 270')"
+      }, [
+        {at: -1, expect: "path('M 90 -20 H -130')"},
+        {at: 0, expect: "path('M 50 60 H 70')"},
+        {at: 0.125, expect: "path('M 45 70 H 95')"},
+        {at: 0.875, expect: "path('M 15 130 H 245')"},
+        {at: 1, expect: "path('M 10 140 H 270')"},
+        {at: 2, expect: "path('M -30 220 H 470')"}
+      ]);
+      test_interpolation({
+        property: 'd',
+        from: "path('m 50 60 h 20')",
+        to: "path('m 10 140 h 260')"
+      }, [
+        {at: -1, expect: "path('M 90 -20 H -130')"},
+        {at: 0, expect: "path('M 50 60 H 70')"},
+        {at: 0.125, expect: "path('M 45 70 H 95')"},
+        {at: 0.875, expect: "path('M 15 130 H 245')"},
+        {at: 1, expect: "path('M 10 140 H 270')"},
+        {at: 2, expect: "path('M -30 220 H 470')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 50 60 V 70')",
+        to: "path('M 10 140 V 270')"
+      }, [
+        {at: -1, expect: "path('M 90 -20 V -130')"},
+        {at: 0, expect: "path('M 50 60 V 70')"},
+        {at: 0.125, expect: "path('M 45 70 V 95')"},
+        {at: 0.875, expect: "path('M 15 130 V 245')"},
+        {at: 1, expect: "path('M 10 140 V 270')"},
+        {at: 2, expect: "path('M -30 220 V 470')"}
+      ]);
+      test_interpolation({
+        property: 'd',
+        from: "path('m 50 60 v 10')",
+        to: "path('m 10 140 v 130')"
+      }, [
+        {at: -1, expect: "path('M 90 -20 V -130')"},
+        {at: 0, expect: "path('M 50 60 V 70')"},
+        {at: 0.125, expect: "path('M 45 70 V 95')"},
+        {at: 0.875, expect: "path('M 15 130 V 245')"},
+        {at: 1, expect: "path('M 10 140 V 270')"},
+        {at: 2, expect: "path('M -30 220 V 470')"}
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 12 34 S 45 67 89 123')",
+        to: "path('M 20 26 S 61 51 113 99')"
+      }, [
+        {at: -1, expect: "path('M 4 42 S 29 83 65 147')"},
+        {at: 0, expect: "path('M 12 34 S 45 67 89 123')"},
+        {at: 0.125, expect: "path('M 13 33 S 47 65 92 120')"},
+        {at: 0.875, expect: "path('M 19 27 S 59 53 110 102')"},
+        {at: 1, expect: "path('M 20 26 S 61 51 113 99')"},
+        {at: 2, expect: "path('M 28 18 S 77 35 137 75')"},
+      ]);
+      test_interpolation({
+        property: 'd',
+        from: "path('m 12 34 s 33 33 77 89')",
+        to: "path('m 20 26 s 41 25 93 73')"
+      }, [
+        {at: -1, expect: "path('M 4 42 S 29 83 65 147')"},
+        {at: 0, expect: "path('M 12 34 S 45 67 89 123')"},
+        {at: 0.125, expect: "path('M 13 33 S 47 65 92 120')"},
+        {at: 0.875, expect: "path('M 19 27 S 59 53 110 102')"},
+        {at: 1, expect: "path('M 20 26 S 61 51 113 99')"},
+        {at: 2, expect: "path('M 28 18 S 77 35 137 75')"},
+      ]);
+
+      test_interpolation({
+        property: 'd',
+        from: "path('M 12 34 T 45 67')",
+        to: "path('M 20 26 T 61 51')"
+      }, [
+        {at: -1, expect: "path('M 4 42 T 29 83')"},
+        {at: 0, expect: "path('M 12 34 T 45 67')"},
+        {at: 0.125, expect: "path('M 13 33 T 47 65')"},
+        {at: 0.875, expect: "path('M 19 27 T 59 53')"},
+        {at: 1, expect: "path('M 20 26 T 61 51')"},
+        {at: 2, expect: "path('M 28 18 T 77 35')"},
+      ]);
+      test_interpolation({
+        property: 'd',
+        from: "path('m 12 34 t 33 33')",
+        to: "path('m 20 26 t 41 25')"
+      }, [
+        {at: -1, expect: "path('M 4 42 T 29 83')"},
+        {at: 0, expect: "path('M 12 34 T 45 67')"},
+        {at: 0.125, expect: "path('M 13 33 T 47 65')"},
+        {at: 0.875, expect: "path('M 19 27 T 59 53')"},
+        {at: 1, expect: "path('M 20 26 T 61 51')"},
+        {at: 2, expect: "path('M 28 18 T 77 35')"},
+      ]);
+  ]]></script>
+</svg>
diff --git a/svg/path/property/getComputedStyle.svg b/svg/path/property/getComputedStyle.svg
new file mode 100644
index 0000000..af30862
--- /dev/null
+++ b/svg/path/property/getComputedStyle.svg
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#TheDProperty"/>
+    <h:meta name="assert" content="d is a property"/>
+  </metadata>
+  <style>
+    .p3 {
+      d: path('M 10 3 H 30');
+    }
+    .g5 {
+      d: path('M 10 5 H 50');
+    }
+    .p6 {
+      d: inherit;
+    }
+  </style>
+  <g id="g0">
+    <path id="p1"></path>
+    <path id="p2" d="M 10 2 H 20"></path>
+    <path id="p3" class="p3"></path>
+    <path id="p4" style="d: path('M 10 4 H 40')"></path>
+  </g>
+  <g id="g5" class="g5">
+    <path id="p6" class="p6"></path>
+    <path id="p7"></path>
+  </g>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+  test(function() {
+    var g0 = document.getElementById('g0');
+    var p1 = document.getElementById('p1');
+    var p2 = document.getElementById('p2');
+    var p3 = document.getElementById('p3');
+    var p4 = document.getElementById('p4');
+    var g5 = document.getElementById('g5');
+    var p6 = document.getElementById('p6');
+    var p7 = document.getElementById('p7');
+
+    assert_equals(getComputedStyle(g0).d, "none");
+    assert_equals(getComputedStyle(p1).d, "none");
+    assert_equals(getComputedStyle(p2).d, "path('M 10 2 H 20')");
+    assert_equals(getComputedStyle(p3).d, "path('M 10 3 H 30')");
+    assert_equals(getComputedStyle(p4).d, "path('M 10 4 H 40')");
+    assert_equals(getComputedStyle(g5).d, "path('M 10 5 H 50')");
+    assert_equals(getComputedStyle(p6).d, "path('M 10 5 H 50')");
+    assert_equals(getComputedStyle(p7).d, "none");
+  });
+  ]]></script>
+</svg>
diff --git a/svg/path/property/priority-ref.svg b/svg/path/property/priority-ref.svg
new file mode 100644
index 0000000..cbc7c38
--- /dev/null
+++ b/svg/path/property/priority-ref.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+  </style>
+  <path d="M 20 10 h 70" />
+  <path d="M 20 80 h 70" />
+</svg>
diff --git a/svg/path/property/priority.svg b/svg/path/property/priority.svg
new file mode 100644
index 0000000..ef187c1
--- /dev/null
+++ b/svg/path/property/priority.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml" width="100" height="100">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/paths.html#TheDProperty"/>
+    <h:link rel="match" href="priority-ref.svg"/>
+    <h:meta name="assert" content="Property overrides attribute."/>
+  </metadata>
+  <style>
+    path {
+      stroke-width: 3;
+      stroke: blue;
+    }
+    #top {
+      d: path('M 20 10 h 70');
+    }
+  </style>
+  <path id="top" d="M 10 20 h 70" />
+  <path d="M 10 90 h 70" style="d: path('M 20 80 h 70')" />
+</svg>
diff --git a/svg/path/property/resources/interpolation-test-common.js b/svg/path/property/resources/interpolation-test-common.js
new file mode 100644
index 0000000..b7f8cd3
--- /dev/null
+++ b/svg/path/property/resources/interpolation-test-common.js
@@ -0,0 +1,71 @@
+'use strict';
+function test_interpolation(settings, expectations) {
+
+  test(function(){
+    assert_true(CSS.supports(settings.property, settings.from), 'Value "' + settings.from + '" is supported by ' + settings.property);
+    assert_true(CSS.supports(settings.property, settings.to), 'Value "' + settings.to + '" is supported by ' + settings.property);
+  }, '"' + settings.from + '" and "' + settings.to + '" are valid ' + settings.property + ' values');
+
+  const container = document.getElementById('container');
+  for (let i = 0; i < expectations.length; ++i) {
+    const progress = expectations[i].at;
+    const expectation = expectations[i].expect;
+    const animationId = 'anim' + i;
+    const targetId = 'target' + i;
+    const referenceId = 'reference' + i;
+
+    test(function(){
+      assert_true(CSS.supports(settings.property, expectation), 'Value "' + expectation + '" is supported by ' + settings.property);
+
+      const target = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+      target.id = targetId;
+      container.appendChild(target);
+
+      const reference = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+      reference.id = referenceId;
+      container.appendChild(reference);
+
+      assert_equals(getComputedStyle(target)[settings.property], getComputedStyle(reference)[settings.property]);
+
+      // Create an animation of length 2s that starts at -1s so the current time of 0s is
+      // exactly halfway through the animation. A cubic bezier timing function is used that
+      // evaluates to |progress| at the current time (halfway through the animation).
+
+      // Cubic bezier evaluates to |progress| at 50%.
+      const y = (8 * progress - 1) / 6;
+      const timing_function = 'cubic-bezier(0, ' + y + ', 1, ' + y + ')';
+
+      const stylesheet = document.createElementNS('http://www.w3.org/2000/svg', 'style');
+      stylesheet.textContent =
+        '#' + targetId + ' {\n' +
+        '  animation: 2s ' + timing_function + ' -1s paused ' + animationId + ';\n' +
+        '}\n' +
+        '@keyframes ' + animationId + ' {\n' +
+        '  0% { ' + settings.property + ': ' + settings.from + '; }\n' +
+        '  100% { ' + settings.property + ': ' + settings.to + '; }\n' +
+        '}\n' +
+        '#' + referenceId + ' {\n' +
+        '  ' + settings.property + ': ' + expectation + ';\n' +
+        '}\n';
+      container.appendChild(stylesheet);
+
+      assert_equals(getComputedStyle(target)[settings.property], getComputedStyle(reference)[settings.property]);
+      assert_equals(getComputedStyle(target)[settings.property], expectation);
+
+      container.removeChild(target);
+      container.removeChild(reference);
+      container.removeChild(stylesheet);
+    }, 'Animation between "' + settings.from + '" and "' + settings.to + '" at progress ' + progress);
+  }
+}
+
+function test_no_interpolation(settings) {
+  const expectFrom = [-1, 0, 0.125].map(function (progress) {
+    return {at: progress, expect: settings.from};
+  });
+  const expectTo = [0.875, 1, 2].map(function (progress) {
+    return {at: progress, expect: settings.to};
+  });
+
+  test_interpolation(settings, expectFrom.concat(expectTo));
+}
diff --git a/svg/scripted/text-attrs-dxdy-have-length.svg b/svg/scripted/text-attrs-dxdy-have-length.svg
new file mode 100644
index 0000000..a5f07e2
--- /dev/null
+++ b/svg/scripted/text-attrs-dxdy-have-length.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#text-TSpanNotes"/>
+    <h:meta name="assert" content="dx and dy attributes on text elements are lists that support length()"/>
+</metadata>
+  <text id="text" font-family="Verdana" font-size="55" fill="blue"
+    y="150"
+    x="120"
+    dx="60 90 -30 120 60 -257"
+    dy="0 12 24 12 0 -12 -24 -12 0"
+    >His socks are black.</text>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+    test(function() {
+      var text = document.getElementById('text');
+      assert_equals(text.dx.baseVal.length, 6);
+      assert_equals(text.dy.baseVal.length, 9);
+    });
+  ]]></script>
+</svg>
diff --git a/svg/scripted/text-attrs-xyrotate-have-length.svg b/svg/scripted/text-attrs-xyrotate-have-length.svg
new file mode 100644
index 0000000..79d365d
--- /dev/null
+++ b/svg/scripted/text-attrs-xyrotate-have-length.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#text-TSpanNotes"/>
+    <h:meta name="assert" content="x y and rotate attributes on text elements are lists that support length()"/>
+</metadata>
+  <text id="text" font-family="Verdana" font-size="55" fill="blue"
+    y="150 130 160"
+    x="120 160 200 240"
+    rotate="0 0 10, -10, 0"
+    >My socks are blue.</text>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+    /* The SVG spec requires (at least) readonly support for length */
+    test(function() {
+      var text = document.getElementById('text');
+      assert_equals(text.y.baseVal.length, 3);
+      assert_equals(text.x.baseVal.length, 4);
+      assert_equals(text.rotate.baseVal.length, 5);
+    });
+  ]]></script>
+</svg>
diff --git a/svg/scripted/text-tspan-attrs-have-length.svg b/svg/scripted/text-tspan-attrs-have-length.svg
new file mode 100644
index 0000000..d189b53
--- /dev/null
+++ b/svg/scripted/text-tspan-attrs-have-length.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#text-TSpanNotes"/>
+    <h:meta name="assert" content="x y dx dy and rotate attributes on text and tspan elements are lists that support length()"/>
+</metadata>
+  <text id="text" font-family="Verdana" font-size="55" fill="blue"
+  y="150 130 160" x="120 160 200 240" rotate="0 0 10, -10, 0">
+    My <tspan id="tspan" x="280 325" y="150 155 160 165 170" rotate="-30,0,30">socks</tspan>
+    are blue.
+  </text>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+    test(function() {
+      /* Make sure that text.y is a list, (presumably
+       * SVGAnimatedLengthList) and that the list has a
+       * length() method and that it returns the right thing.
+       *
+       * This test is for a change in SVG for 2.0, since 1.x, to add
+       * a length property.
+       */
+      var text = document.getElementById('text');
+      assert_equals(text.y.baseVal.length, 3);
+      assert_equals(text.x.baseVal.length, 4);
+      assert_equals(text.rotate.baseVal.length, 5);
+
+      /* same for tspan */
+      var tspan = document.getElementById('tspan');
+      assert_equals(tspan.x.baseVal.length, 2);
+      assert_equals(tspan.y.baseVal.length, 5);
+      assert_equals(tspan.rotate.baseVal.length, 3);
+
+      /* Note, we only have to test that the length property
+       * is there (and interoperable) when there is a list given;
+       * in practice it's there with value 0 when the attributes are
+       * absent, but that's not required, the attribute could be implemented
+       * as a plain string in that case, or absent entirely, I think
+       * - Liam Quin 2018
+       */
+     });
+  ]]></script>
+</svg>
diff --git a/svg/scripted/text-tspan-attrs-indexed-access.svg b/svg/scripted/text-tspan-attrs-indexed-access.svg
new file mode 100644
index 0000000..b14b6bf
--- /dev/null
+++ b/svg/scripted/text-tspan-attrs-indexed-access.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#text-TSpanNotes"/>
+    <h:meta name="assert" content="x y dx dy and rotate attributes on text and tspan elements are lists that support length()"/>
+</metadata>
+  <text id="text" font-family="Verdana" font-size="55" fill="blue"
+  y="150 130 160" x="120 160 200 240" rotate="0 0 10, -10, 0">
+    My <tspan id="tspan" x="280 325" y="150 155 160 165 170" rotate="-30,0,30">socks</tspan>
+    are blue.
+  </text>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+    test(function() {
+      /* Make sure that we can get at list items with indexed access.
+       */
+      var text = document.getElementById('text');
+      assert_equals(text.x.baseVal[0].value, 120);
+      assert_equals(text.y.baseVal[2].value, 160);
+      assert_equals(text.rotate.baseVal[3].value, -10);
+      assert_equals(text.rotate.baseVal[0].value, 0);
+
+      /* same for tspan */
+      var tspan = document.getElementById('tspan');
+      assert_equals(tspan.x.baseVal[1].value, 325);
+      assert_equals(tspan.y.baseVal[4].value, 170);
+      assert_equals(tspan.rotate.baseVal[0].value, -30);
+    });
+
+  ]]></script>
+</svg>
diff --git a/svg/scripted/tspan-attrs-dxdy-have-length.svg b/svg/scripted/tspan-attrs-dxdy-have-length.svg
new file mode 100644
index 0000000..c8d002f
--- /dev/null
+++ b/svg/scripted/tspan-attrs-dxdy-have-length.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#text-TSpanNotes"/>
+    <h:meta name="assert" content="dx, dy attrs on tspan support length()"/>
+</metadata>
+  <text id="text" font-family="Verdana" font-size="55" fill="blue"
+    y="150"
+    x="120"
+    >His socks are <tspan id="tsp"
+    dx="60 90 -30 120 60 -257"
+    dy="0 12 24 12 0 -12 -24 -12 0"
+    >very</tspan> clean.</text>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+    test(function() {
+      var tspan = document.getElementById('tsp');
+      assert_equals(tspan.dx.baseVal.length, 6);
+      assert_equals(tspan.dy.baseVal.length, 9);
+    });
+  ]]></script>
+</svg>
diff --git a/svg/scripted/tspan-attrs-xyrotate-have-length.svg b/svg/scripted/tspan-attrs-xyrotate-have-length.svg
new file mode 100644
index 0000000..11086aa
--- /dev/null
+++ b/svg/scripted/tspan-attrs-xyrotate-have-length.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#text-TSpanNotes"/>
+    <h:meta name="assert" content="dx and dy attributes on text elements are lists that support length()"/>
+</metadata>
+  <text id="text" font-family="Verdana" font-size="55" fill="blue"
+    y="150"
+    x="120"
+    >Her socks were <tspan id="tsp"
+    x="280 325" y="150 155 160 165 170" rotate="-30,0,30"
+    >stained</tspan> with blood.</text>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+    test(function() {
+      var tspan = document.getElementById('tsp');
+      assert_equals(tspan.x.baseVal.length, 2);
+      assert_equals(tspan.y.baseVal.length, 5);
+      assert_equals(tspan.rotate.baseVal.length, 3);
+    });
+  ]]></script>
+</svg>
diff --git a/svg/shapes/line-dasharray-ref.svg b/svg/shapes/line-dasharray-ref.svg
new file mode 100644
index 0000000..5b1caaa
--- /dev/null
+++ b/svg/shapes/line-dasharray-ref.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="300" height="200">
+  <style>
+    line {
+      stroke: blue;
+      stroke-dasharray: 24 8;
+    }
+  </style>
+  <line id="interval" x1="50" y1="100" x2="250" y2="100"/>
+</svg>
diff --git a/svg/shapes/line-dasharray.svg b/svg/shapes/line-dasharray.svg
new file mode 100644
index 0000000..3ac8b19
--- /dev/null
+++ b/svg/shapes/line-dasharray.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml"
+     width="300" height="200">
+  <metadata>
+    <h:link rel="help" href="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing"/>
+    <h:link rel="match" href="line-dasharray-ref.svg"/>
+    <h:meta name="assert" content="The ‘pathLength’ attribute on a ‘path’ element affects stroke-dasharray."/>
+  </metadata>
+  <style>
+    line {
+      stroke: blue;
+      stroke-dasharray: 3 1;
+    }
+  </style>
+  <line id="interval" x1="50" y1="100" x2="250" y2="100" pathLength="25"/>
+</svg>
diff --git a/svg/shapes/line-getPointAtLength.svg b/svg/shapes/line-getPointAtLength.svg
new file mode 100644
index 0000000..51b12ca
--- /dev/null
+++ b/svg/shapes/line-getPointAtLength.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://www.w3.org/TR/SVG2/shapes.html#InterfaceSVGLineElement"/>
+    <h:link rel="help" href="https://www.w3.org/TR/SVG2/types.html#InterfaceSVGGeometryElement"/>
+    <h:meta name="assert" content="The line element supports getPointAtLength."/>
+  </metadata>
+  <line id="interval" x1="300" y1="400" x2="500" y2="400"/>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+    test(function() {
+      var interval = document.getElementById('interval');
+      assert_true(interval.getTotalLength !== undefined, 'getTotalLength is defined for SVGLineElement');
+      assert_true(interval.getPointAtLength !== undefined, 'getPointAtLength is defined for SVGLineElement');
+      assert_equals(interval.getTotalLength(), 200, 'total length');
+
+      var point = interval.getPointAtLength(10);
+      assert_equals(point.x, 310, 'x');
+      assert_equals(point.y, 400, 'y');
+    }, 'line supports getPointAtLength');
+
+    test(function() {
+      var interval = document.getElementById('interval');
+      interval.setAttribute('pathLength', '25');
+
+      assert_equals(interval.getTotalLength(), 200, 'total length');
+
+      var point = interval.getPointAtLength(10);
+      assert_equals(point.x, 310, 'x');
+      assert_equals(point.y, 400, 'y');
+    }, 'line getPointAtLength ignores pathLength');
+  ]]></script>
+</svg>
diff --git a/svg/shapes/line-pathLength.svg b/svg/shapes/line-pathLength.svg
new file mode 100644
index 0000000..f2f4354
--- /dev/null
+++ b/svg/shapes/line-pathLength.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://www.w3.org/TR/SVG2/shapes.html#InterfaceSVGLineElement"/>
+    <h:link rel="help" href="https://www.w3.org/TR/SVG2/types.html#InterfaceSVGGeometryElement"/>
+    <h:meta name="assert" content="The pathLength IDL attribute reflects the ‘pathLength’ content attribute."/>
+  </metadata>
+  <line id="interval" x1="300" y1="400" x2="500" y2="400" pathLength="25"/>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+  test(function() {
+    var interval = document.getElementById('interval');
+    assert_true(interval.pathLength !== undefined, "pathLength is defined for SVGLineElement");
+    assert_equals(interval.pathLength.baseVal, 25);
+  }, 'line supports pathLength attribute');
+  ]]></script>
+</svg>
diff --git a/svg/svg-in-svg/svg-in-svg-circular-filter-reference-crash.html b/svg/svg-in-svg/svg-in-svg-circular-filter-reference-crash.html
new file mode 100644
index 0000000..5130317
--- /dev/null
+++ b/svg/svg-in-svg/svg-in-svg-circular-filter-reference-crash.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Should not crash on circular filter reference containing an svg under svg.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>test(()=>{})</script>
+<svg id="svg" filter="url(#filter)">
+  <filter id="filter">
+    <feImage xlink:href="#svg"></feImage>
+  </filter>
+  <svg overflow="hidden" width="20" height="20">
+    <rect fill="black" width="20" height="20"></rect>
+  </svg>
+</svg>
diff --git a/svg/types/elements/SVGGeometryElement-rect.svg b/svg/types/elements/SVGGeometryElement-rect.svg
new file mode 100644
index 0000000..5391d91
--- /dev/null
+++ b/svg/types/elements/SVGGeometryElement-rect.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:h="http://www.w3.org/1999/xhtml">
+  <metadata>
+    <h:link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#types-InterfaceSVGGeometryElement"/>
+    <h:meta name="assert" content="SVGGeometryElement members work for rect elements."/>
+  </metadata>
+  <style>
+    rect {
+      stroke-width: 10;
+    }
+  </style>
+  <rect id="box" x="50" y="50" width="200" height="100" pathLength="6"/>
+  <h:script src="/resources/testharness.js"/>
+  <h:script src="/resources/testharnessreport.js"/>
+  <script><![CDATA[
+  test(function() {
+    var box = document.getElementById('box');
+
+    assert_equals(box.pathLength.baseVal, 6);
+
+    assert_equals(box.getTotalLength(), 600);
+
+    assert_equals(box.getPointAtLength(210).x, 250);
+    assert_equals(box.getPointAtLength(210).y, 60);
+  }, 'getTotalLength and getPointAtLength do not take pathLength into account');
+  ]]></script>
+</svg>
diff --git a/svg/types/scripted/SVGAnimatedAngle.html b/svg/types/scripted/SVGAnimatedAngle.html
new file mode 100644
index 0000000..5fdc9f3
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedAngle.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedAngle interface - utilizing the orientAngle property of SVGMarkerElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedAngle API - utilizing the orientAngle property of SVGMarkerElement.
+
+  var markerElement = document.createElementNS("http://www.w3.org/2000/svg", "marker");
+
+  // Check initial orientAngle value.
+  assert_true(markerElement.orientAngle instanceof SVGAnimatedAngle);
+  assert_true(markerElement.orientAngle.baseVal instanceof SVGAngle);
+  assert_equals(markerElement.orientAngle.baseVal.value, 0);
+
+  // Check that angles are dynamic, caching value in a local variable and modifying it, should take effect.
+  var numRef = markerElement.orientAngle.baseVal;
+  numRef.value = 100;
+  assert_equals(numRef.value, 100);
+  assert_equals(markerElement.orientAngle.baseVal.value, 100);
+
+  // Check that assigning to baseVal has no effect, as no setter is defined.
+  markerElement.orientAngle.baseVal = -1;
+  assert_equals(markerElement.orientAngle.baseVal.value, 100);
+  markerElement.orientAngle.baseVal = 'aString';
+  assert_equals(markerElement.orientAngle.baseVal.value, 100);
+  markerElement.orientAngle.baseVal = markerElement;
+  assert_equals(markerElement.orientAngle.baseVal.value, 100);
+
+  // Check that the orientAngle baseVal type has not been changed.
+  assert_true(markerElement.orientAngle.baseVal instanceof SVGAngle);
+});
+</script>
diff --git a/svg/types/scripted/SVGAnimatedBoolean.html b/svg/types/scripted/SVGAnimatedBoolean.html
new file mode 100644
index 0000000..ba52922
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedBoolean.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedBoolean interface - utilizing the preserveAlpha property of SVGFEConvolveMatrixElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedBoolean API - utilizing the preserveAlpha property of SVGFEConvolveMatrixElement.
+
+  var convElement = document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix");
+  // Check initial preserveAlpha value.
+  assert_false(convElement.preserveAlpha.baseVal);
+
+  // Set value to true.
+  convElement.preserveAlpha.baseVal = true;
+  assert_true(convElement.preserveAlpha.baseVal);
+
+  // Caching baseVal in local variable.
+  var baseVal = convElement.preserveAlpha.baseVal;
+  assert_true(baseVal);
+
+  // Modify local baseVal variable to false.
+  baseVal = false;
+
+  // Assure that convElement.preserveAlpha has not been changed, but the local baseVal variable.
+  assert_false(baseVal);
+  assert_true(convElement.preserveAlpha.baseVal);
+
+  // Check assigning values of various types.
+  // ECMA-262, 9.2, "ToBoolean"
+  convElement.preserveAlpha.baseVal = convElement.preserveAlpha;
+  assert_true(convElement.preserveAlpha.baseVal);
+
+  convElement.preserveAlpha.baseVal = null;
+  assert_false(convElement.preserveAlpha.baseVal);
+
+  convElement.preserveAlpha.baseVal = 'aString';
+  assert_true(convElement.preserveAlpha.baseVal);
+
+  convElement.preserveAlpha.baseVal = false;
+  assert_false(convElement.preserveAlpha.baseVal);
+
+  convElement.preserveAlpha.baseVal = convElement;
+  assert_true(convElement.preserveAlpha.baseVal);
+});
+</script>
\ No newline at end of file
diff --git a/svg/types/scripted/SVGAnimatedInteger.html b/svg/types/scripted/SVGAnimatedInteger.html
new file mode 100644
index 0000000..849c13d
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedInteger.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedInteger interface - utilizing the targetX property of SVGFEConvolveMatrix</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedInteger API - utilizing the targetX property of SVGFEConvolveMatrix.
+
+  var feConvolveMatrix = document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix");
+
+  // Check initial targetX value.
+  assert_true(feConvolveMatrix.targetX instanceof SVGAnimatedInteger);
+  assert_equals(typeof(feConvolveMatrix.targetX.baseVal), "number");
+  assert_equals(feConvolveMatrix.targetX.baseVal, 0);
+
+  // Check that integers are static, caching value in a local variable and modifying it, should have no effect.
+  var numRef = feConvolveMatrix.targetX.baseVal;
+  numRef = 100;
+  assert_equals(numRef, 100);
+  assert_equals(feConvolveMatrix.targetX.baseVal, 0);
+
+  // Check assigning various valid and invalid values.
+  feConvolveMatrix.targetX.baseVal = -1;
+  assert_equals(feConvolveMatrix.targetX.baseVal, -1); // Negative values are allowed from SVG DOM, but should lead to an error when rendering (disable the filter)
+  feConvolveMatrix.targetX.baseVal = 300;
+  assert_equals(feConvolveMatrix.targetX.baseVal, 300);
+  // ECMA-262, 9.5, "ToInt32"
+  feConvolveMatrix.targetX.baseVal = 'aString';
+  assert_equals(feConvolveMatrix.targetX.baseVal, 0);
+  feConvolveMatrix.targetX.baseVal = feConvolveMatrix;
+  assert_equals(feConvolveMatrix.targetX.baseVal, 0);
+  feConvolveMatrix.targetX.baseVal = 300;
+  assert_equals(feConvolveMatrix.targetX.baseVal, 300);
+});
+</script>
diff --git a/svg/types/scripted/SVGAnimatedLength.html b/svg/types/scripted/SVGAnimatedLength.html
new file mode 100644
index 0000000..788849d
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedLength.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedLength interface - utilizing the width property of SVGRectElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedLength API - utilizing the width property of SVGRectElement.
+
+  var rectElement = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+
+  // Check initial width value.
+  assert_true(rectElement.width instanceof SVGAnimatedLength);
+  assert_true(rectElement.width.baseVal instanceof SVGLength);
+  assert_equals(rectElement.width.baseVal.value, 0);
+
+  // Check that lengths are dynamic, caching value in a local variable and modifying it, should take effect.
+  var numRef = rectElement.width.baseVal;
+  numRef.value = 100;
+  assert_equals(numRef.value, 100);
+  assert_equals(rectElement.width.baseVal.value, 100);
+
+  // Check that assigning to baseVal has no effect, as no setter is defined.
+  rectElement.width.baseVal = -1;
+  assert_equals(rectElement.width.baseVal.value, 100);
+  rectElement.width.baseVal = 'aString';
+  assert_equals(rectElement.width.baseVal.value, 100);
+  rectElement.width.baseVal = rectElement;
+  assert_equals(rectElement.width.baseVal.value, 100);
+
+  // Check that the width baseVal type has not been changed.
+  assert_true(rectElement.width.baseVal instanceof SVGLength);
+});
+</script>
\ No newline at end of file
diff --git a/svg/types/scripted/SVGAnimatedLengthList.html b/svg/types/scripted/SVGAnimatedLengthList.html
new file mode 100644
index 0000000..763d804
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedLengthList.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedLengthList interface - utilizing the dx property of SVGTextElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedLengthList API - utilizing the dx property of SVGTextElement.
+
+  var textElement = document.createElementNS("http://www.w3.org/2000/svg", "text");
+  textElement.setAttribute("dx", "50");
+
+  // Check initial dx value.
+  assert_true(textElement.dx instanceof SVGAnimatedLengthList);
+  assert_true(textElement.dx.baseVal instanceof SVGLengthList);
+  assert_equals(textElement.dx.baseVal.getItem(0).value, 50);
+
+  // Check that length lists are dynamic, caching value in a local variable and modifying it, should take effect.
+  var numRef = textElement.dx.baseVal;
+  numRef.getItem(0).value = 100;
+  assert_equals(numRef.getItem(0).value, 100);
+  assert_equals(textElement.dx.baseVal.getItem(0).value, 100);
+
+  // Check that assigning to baseVal has no effect, as no setter is defined.
+  textElement.dx.baseVal = -1;
+  assert_equals(textElement.dx.baseVal.getItem(0).value, 100);
+  textElement.dx.baseVal = 'aString';
+  assert_equals(textElement.dx.baseVal.getItem(0).value, 100);
+  textElement.dx.baseVal = textElement;
+  assert_equals(textElement.dx.baseVal.getItem(0).value, 100);
+
+  // Check that the dx baseVal type has not been changed.
+  assert_true(textElement.dx.baseVal instanceof SVGLengthList);
+});
+</script>
diff --git a/svg/types/scripted/SVGAnimatedNumber.html b/svg/types/scripted/SVGAnimatedNumber.html
new file mode 100644
index 0000000..a1697cb
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedNumber.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedNumber interface - utilizing the surfaceScale property of SVGFESpecularLightingElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedNumber API - utilizing the surfaceScale property of SVGFESpecularLightingElement.
+
+  var feSpecularLightingElement = document.createElementNS("http://www.w3.org/2000/svg", "feSpecularLighting");
+
+  // Check initial surfaceScale value.
+  assert_true(feSpecularLightingElement.surfaceScale instanceof SVGAnimatedNumber);
+  assert_equals(typeof(feSpecularLightingElement.surfaceScale.baseVal), "number");
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 1);
+
+  // Check that integers are static, caching value in a local variable and modifying it, should have no effect.
+  var numRef = feSpecularLightingElement.surfaceScale.baseVal;
+  numRef = 100;
+  assert_equals(numRef, 100);
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 1);
+
+  // Check assigning various valid and invalid values.
+  feSpecularLightingElement.surfaceScale.baseVal = -1; // Negative values are allowed from SVG DOM, but should lead to an error when rendering (disable the filter)
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, -1);
+  feSpecularLightingElement.surfaceScale.baseVal = 300;
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 300);
+
+  // ECMA-262, 9.3, "ToNumber"
+  assert_throws(new TypeError(), function() { feSpecularLightingElement.surfaceScale.baseVal = 'aString'; });
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 300);
+  feSpecularLightingElement.surfaceScale.baseVal = 0;
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 0);
+  assert_throws(new TypeError(), function() { feSpecularLightingElement.surfaceScale.baseVal = NaN; });
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 0);
+  assert_throws(new TypeError(), function() { feSpecularLightingElement.surfaceScale.baseVal = Infinity; });
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 0);
+  assert_throws(new TypeError(), function() { feSpecularLightingElement.surfaceScale.baseVal = feSpecularLightingElement; });
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 0);
+  feSpecularLightingElement.surfaceScale.baseVal = 300;
+  assert_equals(feSpecularLightingElement.surfaceScale.baseVal, 300);
+});
+</script>
diff --git a/svg/types/scripted/SVGAnimatedNumberList.html b/svg/types/scripted/SVGAnimatedNumberList.html
new file mode 100644
index 0000000..5737cd4
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedNumberList.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedNumberList interface - utilizing the rotate property of SVGTextElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedNumberList API - utilizing the rotate property of SVGTextElement.
+
+  var textElement = document.createElementNS("http://www.w3.org/2000/svg", "text");
+  textElement.setAttribute("rotate", "50");
+
+  // Check initial rotate value.
+  assert_true(textElement.rotate instanceof SVGAnimatedNumberList);
+  assert_true(textElement.rotate.baseVal instanceof SVGNumberList);
+  assert_equals(textElement.rotate.baseVal.getItem(0).value, 50);
+
+  // Check that number lists are dynamic, caching value in a local variable and modifying it, should take effect.
+  var numRef = textElement.rotate.baseVal;
+  numRef.getItem(0).value = 100;
+  assert_equals(numRef.getItem(0).value, 100);
+  assert_equals(textElement.rotate.baseVal.getItem(0).value, 100);
+
+  // Check that assigning to baseVal has no effect, as no setter is defined.
+  textElement.rotate.baseVal = -1;
+  assert_equals(textElement.rotate.baseVal.getItem(0).value, 100);
+  textElement.rotate.baseVal = 'aString';
+  assert_equals(textElement.rotate.baseVal.getItem(0).value, 100);
+  textElement.rotate.baseVal = textElement;
+  assert_equals(textElement.rotate.baseVal.getItem(0).value, 100);
+
+  // Check that the rotate baseVal type has not been changed.
+  assert_true(textElement.rotate.baseVal instanceof SVGNumberList);
+});
+</script>
\ No newline at end of file
diff --git a/svg/types/scripted/SVGAnimatedPreserveAspectRatio.html b/svg/types/scripted/SVGAnimatedPreserveAspectRatio.html
new file mode 100644
index 0000000..80b96e9
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedPreserveAspectRatio.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedPreserveAspectRatio interface - utilizing the preserveAspectRatio property of SVGSVGElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedPreserveAspectRatio API - utilizing the preserveAspectRatio property of SVGSVGElement.
+
+  var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+
+  // Check initial preserveAspectRatio value.
+  assert_true(svgElement.preserveAspectRatio instanceof SVGAnimatedPreserveAspectRatio);
+  assert_true(svgElement.preserveAspectRatio.baseVal instanceof SVGPreserveAspectRatio);
+  assert_equals(svgElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID);
+  assert_equals(svgElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET);
+
+  // Check that preserveAspectRatios are dynamic, caching value in a local variable and modifying it, should take effect;
+  var aspectRef = svgElement.preserveAspectRatio.baseVal;
+  aspectRef.align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN;
+  aspectRef.meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE;
+  assert_equals(aspectRef.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN);
+  assert_equals(aspectRef.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE);
+  assert_equals(svgElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN);
+  assert_equals(svgElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE);
+
+  // Check that assigning to baseVal has no effect, as no setter is defined.
+  // And the preserveAspectRatio align/meetOrSlice remained xMaxYMin/slice.
+  svgElement.preserveAspectRatio.baseVal = -1;
+  assert_equals(svgElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN);
+  assert_equals(svgElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE);
+  svgElement.preserveAspectRatio.baseVal = 'aString';
+  assert_equals(svgElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN);
+  assert_equals(svgElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE);
+  svgElement.preserveAspectRatio.baseVal = svgElement;
+  assert_equals(svgElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN);
+  assert_equals(svgElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE);
+
+  // Check that the preserveAspectRatio baseVal type has not been changed.
+  assert_true(svgElement.preserveAspectRatio.baseVal instanceof SVGPreserveAspectRatio);
+});
+</script>
diff --git a/svg/types/scripted/SVGAnimatedRect.html b/svg/types/scripted/SVGAnimatedRect.html
new file mode 100644
index 0000000..011f44e
--- /dev/null
+++ b/svg/types/scripted/SVGAnimatedRect.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<title>SVGAnimatedRect interface - utilizing the viewBox property of SVGSVGElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  // This test checks the SVGAnimatedRect API - utilizing the viewBox property of SVGSVGElement.
+
+  var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+
+  // Check initial viewBox value.
+  assert_true(svgElement.viewBox instanceof SVGAnimatedRect);
+  assert_true(svgElement.viewBox.baseVal instanceof SVGRect);
+  assert_equals(svgElement.viewBox.baseVal.x, 0);
+
+  // Check that rects are dynamic, caching value in a local variable and modifying it, should take effect.
+  var numRef = svgElement.viewBox.baseVal;
+  numRef.x = 100;
+  assert_equals(numRef.x, 100);
+  assert_equals(svgElement.viewBox.baseVal.x, 100);
+
+  // Check that assigning to baseVal has no effect, as no setter is defined.
+  svgElement.viewBox.baseVal = -1;
+  assert_equals(svgElement.viewBox.baseVal.x, 100);
+  svgElement.viewBox.baseVal = 'aString';
+  assert_equals(svgElement.viewBox.baseVal.x, 100);
+  svgElement.viewBox.baseVal = svgElement;
+  assert_equals(svgElement.viewBox.baseVal.x, 100);
+
+  // Check that the viewBox baseVal type has not been changed.
+  assert_true(svgElement.viewBox.baseVal instanceof SVGRect);
+});
+</script>
diff --git a/tools/.gitignore b/tools/.gitignore
index cd45bce..f888ce9 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -1,8 +1,8 @@
 *#
-.coverage*
+.coverage
+.coverage.*
 htmlcov/
 coverage.xml
-.tox/
 .cache/
 .hypothesis/
 *.py[co]
diff --git a/tools/certs/README.md b/tools/certs/README.md
new file mode 100644
index 0000000..f5ae741
--- /dev/null
+++ b/tools/certs/README.md
@@ -0,0 +1,7 @@
+To enable https://web-platform.test:8443/, add cacert.pem to your browser as Certificate Authority.
+
+For Firefox, go to about:preferences and search for "certificates".
+
+For browsers that use the Certificate Authorities of the underlying OS, such as Chrome and Safari,
+you need to adjust the OS. For macOS, go to Keychain Access and add the certificate under
+**login**.
diff --git a/tools/certs/cakey.pem b/tools/certs/cacert.key
similarity index 100%
rename from tools/certs/cakey.pem
rename to tools/certs/cacert.key
diff --git a/tools/ci/before_install.sh b/tools/ci/before_install.sh
old mode 100755
new mode 100644
index bf7d48c..9b80fd9
--- a/tools/ci/before_install.sh
+++ b/tools/ci/before_install.sh
@@ -1,13 +1,13 @@
 #!/bin/bash
 set -e
 
-if [[ $(./wpt test-jobs --includes $JOB; echo $?) -eq 0 ]]; then
+if [[ -z ${RUN_JOB+x} && $(./wpt test-jobs --includes $JOB; echo $?) -eq 0 ]] || [[ $RUN_JOB -eq 1 ]]; then
     export RUN_JOB=1
     git submodule update --init --recursive 1>&2
     export DISPLAY=:99.0
     sh -e /etc/init.d/xvfb start 1>&2
     # For uploading the manifest
     export WPT_MANIFEST_FILE=$HOME/meta/MANIFEST-$(git rev-parse HEAD).json
-else
+elif [[ -z ${RUN_JOB+x} ]]; then
     export RUN_JOB=0
 fi
diff --git a/tools/ci/check_stability.py b/tools/ci/check_stability.py
index 169962a..d781bae 100644
--- a/tools/ci/check_stability.py
+++ b/tools/ci/check_stability.py
@@ -7,8 +7,6 @@
 import sys
 from ConfigParser import SafeConfigParser
 
-import requests
-
 here = os.path.dirname(__file__)
 wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir))
 sys.path.insert(0, wpt_root)
@@ -22,7 +20,7 @@
 from tools import localpaths
 
 logger = None
-run, write_inconsistent, write_results = None, None, None
+stability_run, write_inconsistent, write_results = None, None, None
 wptrunner = None
 
 def setup_logging():
@@ -37,8 +35,9 @@
 
 
 def do_delayed_imports():
-    global run, write_inconsistent, write_results, wptrunner
-    from tools.wpt.stability import run, write_inconsistent, write_results
+    global stability_run, write_inconsistent, write_results, wptrunner
+    from tools.wpt.stability import run as stability_run
+    from tools.wpt.stability import write_inconsistent, write_results
     from wptrunner import wptrunner
 
 
@@ -132,11 +131,6 @@
     return git("rev-parse", "HEAD").strip()
 
 
-def install_wptrunner():
-    """Install wptrunner."""
-    call("pip", "install", wptrunner_root)
-
-
 def deepen_checkout(user):
     """Convert from a shallow checkout to a full one"""
     fetch_args = [user, "+refs/heads/*:refs/remotes/origin/*"]
@@ -177,73 +171,11 @@
     return parser
 
 
-def set_default_args(kwargs):
-    kwargs.set_if_none("sauce_platform",
-                       os.environ.get("PLATFORM"))
-    kwargs.set_if_none("sauce_build",
-                       os.environ.get("TRAVIS_BUILD_NUMBER"))
-    python_version = os.environ.get("TRAVIS_PYTHON_VERSION")
-    kwargs.set_if_none("sauce_tags",
-                       [python_version] if python_version else [])
-    kwargs.set_if_none("sauce_tunnel_id",
-                       os.environ.get("TRAVIS_JOB_NUMBER"))
-    kwargs.set_if_none("sauce_user",
-                       os.environ.get("SAUCE_USERNAME"))
-    kwargs.set_if_none("sauce_key",
-                       os.environ.get("SAUCE_ACCESS_KEY"))
-
-
 def pr():
     pr = os.environ.get("TRAVIS_PULL_REQUEST", "false")
     return pr if pr != "false" else None
 
 
-def post_results(results, pr_number, iterations, product, url, status):
-    """Post stability results to a given URL."""
-    payload_results = []
-
-    for test_name, test in results.iteritems():
-        subtests = []
-        for subtest_name, subtest in test['subtests'].items():
-            subtests.append({
-                'test': subtest_name,
-                'result': {
-                    'messages': list(subtest['messages']),
-                    'status': subtest['status']
-                },
-            })
-        payload_results.append({
-            'test': test_name,
-            'result': {
-                'status': test['status'],
-                'subtests': subtests
-            }
-        })
-
-    payload = {
-        "pull": {
-            "number": int(pr_number),
-            "sha": os.environ.get("TRAVIS_PULL_REQUEST_SHA"),
-        },
-        "job": {
-            "id": int(os.environ.get("TRAVIS_JOB_ID")),
-            "number": os.environ.get("TRAVIS_JOB_NUMBER"),
-            "allow_failure": os.environ.get("TRAVIS_ALLOW_FAILURE") == 'true',
-            "status": status,
-        },
-        "build": {
-            "id": int(os.environ.get("TRAVIS_BUILD_ID")),
-            "number": os.environ.get("TRAVIS_BUILD_NUMBER"),
-        },
-        "product": product,
-        "iterations": iterations,
-        "message": "All results were stable." if status == "passed" else "Unstable results.",
-        "results": payload_results,
-    }
-
-    requests.post(url, json=payload)
-
-
 def get_changed_files(manifest_path, rev, ignore_changes, skip_tests):
     if not rev:
         branch_point = testfiles.branch_point()
@@ -268,19 +200,15 @@
 
     venv = Virtualenv(os.environ.get("VIRTUAL_ENV", os.path.join(wpt_root, "_venv")))
     venv.install_requirements(os.path.join(wpt_root, "tools", "wptrunner", "requirements.txt"))
-    venv.install("requests")
 
     args, wpt_args = get_parser().parse_known_args()
     return run(venv, wpt_args, **vars(args))
 
 
 def run(venv, wpt_args, **kwargs):
-    global logger
-
     do_delayed_imports()
 
     retcode = 0
-    parser = get_parser()
 
     wpt_args = create_parser().parse_args(wpt_args)
 
@@ -289,7 +217,6 @@
         config.readfp(config_fp)
         skip_tests = config.get("file detection", "skip_tests").split()
         ignore_changes = set(config.get("file detection", "ignore_changes").split())
-        results_url = config.get("file detection", "results_url")
 
     if kwargs["output_bytes"] is not None:
         replace_streams(kwargs["output_bytes"],
@@ -304,12 +231,6 @@
 
     setup_logging()
 
-    browser_name = wpt_args.product.split(":")[0]
-
-    if browser_name == "sauce" and not wpt_args.sauce_key:
-        logger.warning("Cannot run tests on Sauce Labs. No access key.")
-        return retcode
-
     pr_number = pr()
 
     with TravisFold("browser_setup"):
@@ -343,8 +264,6 @@
 
             wpt_kwargs["test_list"] = list(tests_changed | files_affected)
 
-        set_default_args(wpt_kwargs)
-
         do_delayed_imports()
 
         wpt_kwargs["stability"] = True
@@ -362,7 +281,7 @@
 
 
         wpt_logger = wptrunner.logger
-        iterations, results, inconsistent = run(venv, wpt_logger, **wpt_kwargs)
+        iterations, results, inconsistent = stability_run(venv, wpt_logger, **wpt_kwargs)
 
     if results:
         if inconsistent:
@@ -374,22 +293,19 @@
             write_results(logger.info, results, iterations,
                           pr_number=pr_number,
                           use_details=True)
-            if pr_number:
-                post_results(results, iterations=iterations, url=results_url,
-                             product=wpt_args.product, pr_number=pr_number,
-                             status="failed" if inconsistent else "passed")
     else:
         logger.info("No tests run.")
+        # Be conservative and only return errors when we know for sure tests are changed.
+        if tests_changed:
+            retcode = 3
 
     return retcode
 
 
 if __name__ == "__main__":
     try:
-        retcode = main()
+        sys.exit(main())
     except Exception:
         import traceback
         traceback.print_exc()
         sys.exit(1)
-    else:
-        sys.exit(retcode)
diff --git a/tools/ci/ci_lint.sh b/tools/ci/ci_lint.sh
old mode 100644
new mode 100755
diff --git a/tools/ci/ci_manifest.sh b/tools/ci/ci_manifest.sh
old mode 100644
new mode 100755
diff --git a/tools/ci/ci_resources_unittest.sh b/tools/ci/ci_resources_unittest.sh
index fd28db5..78868b9 100755
--- a/tools/ci/ci_resources_unittest.sh
+++ b/tools/ci/ci_resources_unittest.sh
@@ -5,16 +5,19 @@
 WPT_ROOT=$(readlink -f $SCRIPT_DIR/../..)
 cd $WPT_ROOT
 
+source tools/ci/lib.sh
+
 main() {
+    hosts_fixup
+
     cd $WPT_ROOT
     pip install -U tox
-    pip install --requirement tools/wpt/requirements.txt
     ./wpt install firefox browser --destination $HOME
     ./wpt install firefox webdriver --destination $HOME/firefox
     export PATH=$HOME/firefox:$PATH
 
     cd $WPT_ROOT/resources/test
-    tox
+    tox -- --binary=$HOME/browsers/firefox/firefox
 }
 
 main
diff --git a/tools/ci/ci_stability.sh b/tools/ci/ci_stability.sh
old mode 100644
new mode 100755
index 1b814ee..a23b057
--- a/tools/ci/ci_stability.sh
+++ b/tools/ci/ci_stability.sh
@@ -8,15 +8,23 @@
 source tools/ci/lib.sh
 
 test_stability() {
-    ./wpt check-stability $PRODUCT --output-bytes $((1024 * 1024 * 3)) --metadata ~/meta/ --install-fonts
+    local extra_arg=$1
+    ./wpt check-stability $PRODUCT $extra_arg --output-bytes $((1024 * 1024 * 3)) --metadata ~/meta/ --install-fonts
 }
 
 main() {
     hosts_fixup
+    local extra_arg=""
     if [ $(echo $PRODUCT | grep '^chrome:') ]; then
-       install_chrome $(echo $PRODUCT | grep --only-matching '\w\+$')
+        local channel=$(echo $PRODUCT | grep --only-matching '\w\+$')
+        if [[ $channel == "dev" ]]; then
+            # The package name for Google Chrome Dev uses "unstable", not "dev".
+            channel="unstable"
+        fi
+        install_chrome $channel
+        extra_arg="--binary=$(which google-chrome-$channel)"
     fi
-    test_stability
+    test_stability $extra_arg
 }
 
 main
diff --git a/tools/ci/ci_taskcluster.sh b/tools/ci/ci_taskcluster.sh
new file mode 100755
index 0000000..8d00a5c
--- /dev/null
+++ b/tools/ci/ci_taskcluster.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+./wpt manifest-download
+
+if [ $1 == "firefox" ]; then
+    ./wpt run firefox --log-tbpl=../artifacts/log_tbpl.log --log-tbpl-level=info --log-wptreport=../artifacts/wpt_report.json --log-mach=- --this-chunk=$3 --total-chunks=$4 --test-type=$2 -y --install-browser --no-pause --no-restart-on-unexpected --reftest-internal --install-fonts --no-fail-on-unexpected
+elif [ $1 == "chrome" ]; then
+    ./wpt run chrome --log-tbpl=../artifacts/log_tbpl.log --log-tbpl-level=info --log-wptreport=../artifacts/wpt_report.json --log-mach=- --this-chunk=$3 --total-chunks=$4 --test-type=$2 -y  --no-pause --no-restart-on-unexpected --install-fonts --no-fail-on-unexpected
+fi
diff --git a/tools/ci/ci_tools_unittest.sh b/tools/ci/ci_tools_unittest.sh
index 6c74a25..a8e4c41 100755
--- a/tools/ci/ci_tools_unittest.sh
+++ b/tools/ci/ci_tools_unittest.sh
@@ -5,20 +5,33 @@
 WPT_ROOT=$(readlink -f $SCRIPT_DIR/../..)
 cd $WPT_ROOT
 
+run_applicable_tox () {
+    # instead of just running TOXENV (e.g., py27)
+    # run all environments that start with TOXENV
+    # (e.g., py27-firefox as well as py27)
+    local OLD_TOXENV="$TOXENV"
+    unset TOXENV
+    local RUN_ENVS=$(tox -l | grep "^${OLD_TOXENV}\(\-\|\$\)" | tr "\n" ",")
+    if [[ -n "$RUN_ENVS" ]]; then
+        tox -e "$RUN_ENVS"
+    fi
+    export TOXENV="$OLD_TOXENV"
+}
+
+
 if [[ $(./wpt test-jobs --includes tools_unittest; echo $?) -eq 0 ]]; then
     pip install -U tox codecov
     cd tools
-    tox
+    run_applicable_tox
     cd $WPT_ROOT
 else
     echo "Skipping tools unittest"
 fi
 
 if [[ $(./wpt test-jobs --includes wptrunner_unittest; echo $?) -eq 0 ]]; then
-    if [ $TOXENV == "py27" ] || [ $TOXENV == "pypy" ]; then
-        cd tools/wptrunner
-        tox
-    fi
+    cd tools/wptrunner
+    run_applicable_tox
+    cd $WPT_ROOT
 else
     echo "Skipping wptrunner unittest"
 fi
diff --git a/tools/ci/ci_wpt.sh b/tools/ci/ci_wpt.sh
old mode 100644
new mode 100755
diff --git a/tools/ci/ci_wptrunner_infrastructure.sh b/tools/ci/ci_wptrunner_infrastructure.sh
new file mode 100755
index 0000000..d6d6803
--- /dev/null
+++ b/tools/ci/ci_wptrunner_infrastructure.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -ex
+
+SCRIPT_DIR=$(dirname $(readlink -f "$0"))
+WPT_ROOT=$(readlink -f $SCRIPT_DIR/../..)
+cd $WPT_ROOT
+
+source tools/ci/lib.sh
+
+test_infrastructure() {
+    local ARGS="";
+    if [ $PRODUCT == "firefox" ]; then
+        ARGS="--install-browser"
+    else
+        ARGS=$1
+    fi
+    ./wpt run --yes --manifest ~/meta/MANIFEST.json --metadata infrastructure/metadata/ --install-fonts $ARGS $PRODUCT infrastructure/
+}
+
+main() {
+    PRODUCTS=( "firefox" "chrome" )
+    for PRODUCT in "${PRODUCTS[@]}"; do
+        if [ "$PRODUCT" != "firefox" ]; then
+            # Firefox is expected to work using pref settings for DNS
+            # Don't adjust the hostnames in that case to ensure this keeps working
+            hosts_fixup
+        fi
+        if [ "$PRODUCT" == "chrome" ]; then
+            install_chrome unstable
+            test_infrastructure "--binary=$(which google-chrome-unstable)"
+        else
+            test_infrastructure
+        fi
+    done
+}
+
+main
diff --git a/tools/ci/commands.json b/tools/ci/commands.json
index d682d2a..51dbbd2 100644
--- a/tools/ci/commands.json
+++ b/tools/ci/commands.json
@@ -2,5 +2,8 @@
     "test-jobs": {"path": "jobs.py", "script": "run", "parser": "create_parser", "help": "List test jobs that should run for a set of commits",
                   "virtualenv": false},
     "check-stability": {"path": "check_stability.py", "script": "run", "parser": "get_parser", "parse_known": true, "help": "Check test stability",
-                        "virtualenv": true, "install": ["requests"], "requirements": ["../wptrunner/requirements.txt"]}
+                        "virtualenv": true, "install": ["requests"], "requirements": ["../wptrunner/requirements.txt"]},
+    "make-hosts-file": {"path": "make_hosts_file.py", "script": "run", "parser": "create_parser", "help": "Output a hosts file to stdout", "virtualenv": false},
+    "make-tasks": {"path": "taskgraph.py", "script": "run", "parser": "get_parser", "parse_known": true, "help": "Generate taskcluster.yml file containing the run tasks",
+                        "virtualenv": true, "install": ["pyyaml"]}
 }
diff --git a/tools/ci/jobs.py b/tools/ci/jobs.py
index 9ec29a8..b58ab92 100644
--- a/tools/ci/jobs.py
+++ b/tools/ci/jobs.py
@@ -27,10 +27,10 @@
     "wptrunner_unittest": ["tools/wptrunner/*"],
     "build_css": ["css/"],
     "update_built": ["2dcontext/",
-                     "assumptions/",
                      "html/",
                      "offscreen-canvas/"],
     "wpt_integration": ["tools/"],
+    "wptrunner_infrastructure": ["infrastructure/", "tools/"],
 }
 
 
diff --git a/tools/ci/lib.sh b/tools/ci/lib.sh
index e80499a..7c0b825 100644
--- a/tools/ci/lib.sh
+++ b/tools/ci/lib.sh
@@ -6,15 +6,7 @@
     echo "## /etc/hosts ##"
     cat /etc/hosts
     sudo sed -i 's/^::1\s*localhost/::1/' /etc/hosts
-    sudo sh -c 'echo "
-127.0.0.1 web-platform.test
-127.0.0.1 www.web-platform.test
-127.0.0.1 www1.web-platform.test
-127.0.0.1 www2.web-platform.test
-127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1 xn--lve-6lad.web-platform.test
-0.0.0.0 nonexistent-origin.web-platform.test
-" >> /etc/hosts'
+    ./wpt make-hosts-file | sudo tee -a /etc/hosts
     echo "== /etc/hosts =="
     cat /etc/hosts
     echo "----------------"
@@ -24,7 +16,7 @@
 install_chrome() {
     channel=$1
     deb_archive=google-chrome-${channel}_current_amd64.deb
-    wget https://dl.google.com/linux/direct/$deb_archive
+    wget -q https://dl.google.com/linux/direct/$deb_archive
 
     # If the environment provides an installation of Google Chrome, the
     # existing binary may take precedence over the one introduced in this
diff --git a/tools/ci/make_hosts_file.py b/tools/ci/make_hosts_file.py
new file mode 100644
index 0000000..f6f38a5
--- /dev/null
+++ b/tools/ci/make_hosts_file.py
@@ -0,0 +1,16 @@
+import argparse
+import os
+
+from ..localpaths import repo_root
+
+from ..serve.serve import load_config, make_hosts_file
+
+def create_parser():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("address", default="127.0.0.1", nargs="?", help="Address that hosts should point at")
+    return parser
+
+def run(**kwargs):
+    config = load_config(os.path.join(repo_root, "config.json"))
+
+    print(make_hosts_file(config, kwargs["address"]))
diff --git a/tools/ci/run.sh b/tools/ci/run.sh
index d126dbe..ae58f58 100755
--- a/tools/ci/run.sh
+++ b/tools/ci/run.sh
@@ -6,5 +6,5 @@
 cd $WPT_ROOT
 
 if [[ $RUN_JOB -eq 1 ]]; then
-    . $SCRIPT
+    $SCRIPT
 fi
diff --git a/tools/ci/tag_master.py b/tools/ci/tag_master.py
index 4404a19..f049671 100644
--- a/tools/ci/tag_master.py
+++ b/tools/ci/tag_master.py
@@ -13,35 +13,38 @@
 
 from tools.wpt.testfiles import get_git_cmd
 
-logging.basicConfig()
+logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)
 
 
-def get_pr(repo, owner, rev):
-    url = ("https://api.github.com/search/issues?q=type:pr+is:merged+repo:%s/%s+%s" %
-           (repo, owner, rev))
+def get_pr(repo, owner, sha):
+    url = ("https://api.github.com/search/issues?q=type:pr+is:merged+repo:%s/%s+sha:%s" %
+           (repo, owner, sha))
     try:
         resp = urllib2.urlopen(url)
+        body = resp.read()
     except Exception as e:
         logger.error(e)
         return None
 
     if resp.code != 200:
-        logger.error("Got HTTP status %s" % resp.code)
+        logger.error("Got HTTP status %s. Response:" % resp.code)
+        logger.error(body)
         return None
 
     try:
-        data = json.loads(resp.read())
+        data = json.loads(body)
     except ValueError:
-        logger.error("Failed to read response as JSON")
+        logger.error("Failed to read response as JSON:")
+        logger.error(body)
         return None
 
     items = data["items"]
     if len(items) == 0:
-        logger.error("No PR found for master")
+        logger.error("No PR found for %s" % sha)
         return None
     if len(items) > 1:
-        logger.warning("Found multiple PRs for master")
+        logger.warning("Found multiple PRs for %s" % sha)
 
     pr = items[0]
 
@@ -63,12 +66,15 @@
         resp = opener.open(req)
     except Exception as e:
         logger.error("Tag creation failed:\n%s" % e)
-        return
+        return False
 
     if resp.code != 201:
-        logger.error("Got HTTP status %s" % resp.code)
-    else:
-        logger.info("Tagged master as %s" % tag)
+        logger.error("Got HTTP status %s. Response:" % resp.code)
+        logger.error(resp.read())
+        return False
+
+    logger.info("Tagged %s as %s" % (sha, tag))
+    return True
 
 
 def main():
@@ -84,8 +90,11 @@
     head_rev = git("rev-parse", "HEAD")
 
     pr = get_pr(owner, repo, head_rev)
-    if pr is not None:
-        tag(owner, repo, head_rev, "merge_pr_%s" % pr)
+    if pr is None:
+        sys.exit(1)
+    tagged = tag(owner, repo, head_rev, "merge_pr_%s" % pr)
+    if not tagged:
+        sys.exit(1)
 
 
 if __name__ == "__main__":
diff --git a/tools/ci/taskgraph.py b/tools/ci/taskgraph.py
new file mode 100644
index 0000000..894071d
--- /dev/null
+++ b/tools/ci/taskgraph.py
@@ -0,0 +1,121 @@
+import argparse
+import copy
+import os
+import six
+
+import yaml
+
+
+here = os.path.dirname(__file__)
+wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir))
+
+docker_image = "harjgam/web-platform-tests:0.11"
+
+task_template = {
+    "provisionerId": "{{ taskcluster.docker.provisionerId }}",
+    "workerType": "{{ taskcluster.docker.workerType }}",
+    "extra": {
+        "github": {
+            "events": ["push"],
+            "branches": ["master"],
+        },
+    },
+    "payload": {
+        "maxRunTime": 7200,
+        "image": docker_image,
+        "command":[
+            "/bin/bash",
+            "--login",
+            "-c",
+            """>-
+            ~/start.sh {{event.head.repo.url}} {{ event.head.repo.branch }} {{event.head.sha}} %(browser_name)s &&
+            cd ~/web-platform-tests &&
+            %(command)s"""],
+        "artifacts": {
+            "public/results": {
+                "path": "/home/test/artifacts",
+                "type": "directory"
+            }
+        }
+    },
+    "metadata": {
+        "name": "wpt-%(browser_name)s-%(suite)s-%(chunk)s",
+        "description": "",
+        "owner": "{{ event.head.user.email }}",
+        "source": "{{ event.head.repo.url }}",
+    }
+}
+
+
+file_template = {
+    "version": 0,
+    "tasks": [],
+    "allowPullRequests": "collaborators"
+}
+
+suites = {
+    "testharness": {"chunks": 15},
+    "reftest": {"chunks": 10},
+    "wdspec": {"chunks": 1}
+}
+
+browsers = {
+    "firefox": {"name": "firefox-nightly"},
+    "chrome": {"name": "chrome-dev"}
+}
+
+
+def get_parser():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--dest",
+                        action="store",
+                        default=wpt_root,
+                        help="Directory to write the .taskcluster.yml file to")
+    return parser
+
+
+def fill(template, data):
+    rv = {}
+    for key, value in template.iteritems():
+        rv[key] = fill_inner(value, data)
+    return rv
+
+
+def fill_inner(value, data):
+    if isinstance(value, six.string_types):
+        return value % data
+    elif isinstance(value, dict):
+        return fill(value, data)
+    elif isinstance(value, list):
+        return [fill_inner(item, data) for item in value]
+    elif isinstance(value, (float,) + six.integer_types):
+        return value
+    else:
+        raise ValueError
+
+
+def run(venv, *args, **kwargs):
+    if not os.path.isdir(kwargs["dest"]):
+        raise ValueError("Invalid directory %s" % kwargs["dest"])
+
+    task_config = copy.deepcopy(file_template)
+    for browser, browser_props in browsers.iteritems():
+        for suite, suite_props in suites.iteritems():
+            total_chunks = suite_props.get("chunks", 1)
+            for chunk in six.moves.xrange(1, total_chunks + 1):
+                data = {
+                    "suite": suite,
+                    "chunk": chunk,
+                    "browser_name": browser_props["name"],
+                    "command": ("./tools/ci/ci_taskcluster.sh %s %s %s %s" %
+                                (browser, suite, chunk, total_chunks))
+                }
+
+                task_data = fill(task_template, data)
+                task_config["tasks"].append(task_data)
+
+    with open(os.path.join(kwargs["dest"], ".taskcluster.yml"), "w") as f:
+        f.write("""# GENERATED FILE DO NOT EDIT
+# To regenerate this file run ./wpt make-tasks
+""")
+        yaml.dump(task_config, f)
diff --git a/tools/ci/tests/test_jobs.py b/tools/ci/tests/test_jobs.py
index 1b1ed36..ac3729c 100644
--- a/tools/ci/tests/test_jobs.py
+++ b/tools/ci/tests/test_jobs.py
@@ -69,8 +69,6 @@
 def test_update_built():
     assert jobs.get_jobs(["2dcontext/foo.html"],
                          includes=["update_built"]) == set(["update_built"])
-    assert jobs.get_jobs(["assumptions/foo.html"],
-                         includes=["update_built"]) == set(["update_built"])
     assert jobs.get_jobs(["html/foo.html"],
                          includes=["update_built"]) == set(["update_built"])
     assert jobs.get_jobs(["offscreen-canvas/foo.html"],
@@ -82,3 +80,9 @@
                          includes=["wpt_integration"]) == set(["wpt_integration"])
     assert jobs.get_jobs(["tools/wptrunner/wptrunner/wptrunner.py"],
                          includes=["wpt_integration"]) == set(["wpt_integration"])
+
+def test_wpt_infrastructure():
+    assert jobs.get_jobs(["tools/hammer.html"],
+                         includes=["wptrunner_infrastructure"]) == set(["wptrunner_infrastructure"])
+    assert jobs.get_jobs(["infrastructure/assumptions/ahem.html"],
+                         includes=["wptrunner_infrastructure"]) == set(["wptrunner_infrastructure"])
diff --git a/tools/conftest.py b/tools/conftest.py
index 894fe62..27a259c 100644
--- a/tools/conftest.py
+++ b/tools/conftest.py
@@ -1,5 +1,6 @@
 import platform
 import os
+import sys
 
 from hypothesis import settings, HealthCheck
 
@@ -11,3 +12,14 @@
 
 settings.load_profile(os.getenv("HYPOTHESIS_PROFILE",
                                 "default" if impl != "PyPy" else "pypy"))
+
+# these can't even be imported on Py3, so totally ignore it even from collection
+ignore_dirs = ["serve", "wptserve"]
+
+collect_ignore = []
+if sys.version_info[0] >= 3:
+    for d in ignore_dirs:
+        path = os.path.join(os.path.dirname(__file__), d)
+        collect_ignore.extend([os.path.join(root, f)
+                               for root, _, files in os.walk(path)
+                               for f in files])
diff --git a/tools/docker/.bashrc b/tools/docker/.bashrc
new file mode 100644
index 0000000..bbe03c4
--- /dev/null
+++ b/tools/docker/.bashrc
@@ -0,0 +1,4 @@
+function xvfb_start() {
+    GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH"
+    xvfb-run --server-args="-screen 0 $GEOMETRY -ac +extension RANDR" $@
+}
diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile
new file mode 100644
index 0000000..b0541dc
--- /dev/null
+++ b/tools/docker/Dockerfile
@@ -0,0 +1,72 @@
+FROM ubuntu:16.04
+
+# No interactive frontend during docker build
+ENV DEBIAN_FRONTEND=noninteractive \
+    DEBCONF_NONINTERACTIVE_SEEN=true
+
+# General requirements not in the base image
+RUN apt-get -qqy update \
+  && apt-get -qqy install \
+    bzip2 \
+    ca-certificates \
+    dbus-x11 \
+    gdebi \
+    git \
+    locales \
+    pulseaudio \
+    python \
+    python-pip \
+    tzdata \
+    sudo \
+    unzip \
+    wget \
+    xvfb
+
+# Installing just the deps of firefox and chrome is moderately tricky, so
+# just install the default versions of them, and some extra deps we happen
+# to know that chrome requires
+
+RUN apt-get -qqy install \
+    firefox \
+    libnss3-tools \
+    fonts-liberation \
+    indicator-application \
+    libappindicator1 \
+    libappindicator3-1 \
+    libdbusmenu-gtk3-4 \
+    libindicator3-7 \
+    libindicator7
+
+RUN pip install --upgrade pip
+RUN pip install virtualenv
+
+ENV TZ "UTC"
+RUN echo "${TZ}" > /etc/timezone \
+  && dpkg-reconfigure --frontend noninteractive tzdata
+
+RUN useradd test \
+         --shell /bin/bash  \
+         --create-home \
+  && usermod -a -G sudo test \
+  && echo 'ALL ALL = (ALL) NOPASSWD: ALL' >> /etc/sudoers \
+  && echo 'test:secret' | chpasswd
+
+ENV SCREEN_WIDTH 1280
+ENV SCREEN_HEIGHT 1024
+ENV SCREEN_DEPTH 24
+ENV DISPLAY :99.0
+
+USER test
+
+WORKDIR /home/test
+
+# Remove information on how to use sudo on login
+RUN sudo echo ""
+
+RUN mkdir -p /home/test/artifacts
+
+WORKDIR /home/test/
+
+COPY .bashrc /home/test/.bashrc
+
+COPY start.sh /home/test/start.sh
diff --git a/tools/docker/start.sh b/tools/docker/start.sh
new file mode 100755
index 0000000..2fa1204
--- /dev/null
+++ b/tools/docker/start.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+set -ex
+
+REMOTE=${1:-https://github.com/w3c/web-platform-tests}
+BRANCH=${2:-master}
+REV=${3:-FETCH_HEAD}
+BROWSER=${4:-all}
+
+cd ~
+
+# Initially we just fetch 50 commits in order to save several minutes of fetching
+git clone ${REMOTE} --single-branch --branch ${BRANCH} --no-checkout -q --depth=50 web-platform-tests
+cd web-platform-tests
+if [[ ! `git rev-parse --verify -q ${REV}` ]];
+then
+    # But if for some reason the commit under test isn't in that range, we give in and
+    # fetch everything
+    git fetch -q --unshallow ${REMOTE}
+    git rev-parse --verify ${REV}
+fi
+git checkout -b build ${REV}
+
+sudo sh -c './wpt make-hosts-file >> /etc/hosts'
+
+if [[ $BROWSER == "chrome"* ]] || [[ "$BROWSER" == all ]]
+then
+    # Install Chrome dev
+    deb_archive=google-chrome-unstable_current_amd64.deb
+    wget https://dl.google.com/linux/direct/$deb_archive
+
+    sudo gdebi -n $deb_archive
+fi
+
+sudo Xvfb $DISPLAY -screen 0 ${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH} &
diff --git a/tools/gitignore/gitignore.py b/tools/gitignore/gitignore.py
index 629ed6b..0be6f8f 100644
--- a/tools/gitignore/gitignore.py
+++ b/tools/gitignore/gitignore.py
@@ -17,6 +17,12 @@
     else:
         any_char = "."
         parts.append("^(?:.*/)?")
+    if pat[-1] == "/":
+        # If the last character is / match this directory or any subdirectory
+        pat = pat[:-1]
+        suffix = "(?:/|$)"
+    else:
+        suffix = "$"
     while i < len(pat):
         c = pat[i]
         if c == "\\":
@@ -63,7 +69,7 @@
 
     if seq:
         raise ValueError
-    parts.append("$")
+    parts.append(suffix)
     try:
         return re.compile("".join(parts))
     except Exception:
@@ -84,7 +90,7 @@
     if dir_only:
         line = line[:-1]
 
-    return invert, dir_only, fnmatch_translate(line, "/" in line)
+    return invert, dir_only, fnmatch_translate(line, dir_only)
 
 
 class PathFilter(object):
diff --git a/tools/gitignore/tests/test_gitignore.py b/tools/gitignore/tests/test_gitignore.py
index 44f3353..75a0d58 100644
--- a/tools/gitignore/tests/test_gitignore.py
+++ b/tools/gitignore/tests/test_gitignore.py
@@ -11,7 +11,8 @@
     ("/*.c", False, ["a.c", ".c"]),
     ("**/b", False, ["a/b", "a/c/b"]),
     ("*b", True, ["ab"]),
-    ("**/b", True, ["a/b"])
+    ("**/b", True, ["a/b"]),
+    ("a/", True, ["a", "a/b", "a/b/c"])
 ]
 
 mismatch_data = [
@@ -23,6 +24,7 @@
     ("**b", True, ["a/b"]),
     ("a[/]b", True, ["a/b"]),
     ("**/b", True, ["a/c/b"]),
+    ("a", True, ["ab"])
 ]
 
 invalid_data = [
@@ -40,21 +42,25 @@
     ("c/b", True)
 ]
 
+
 def expand_data(compact_data):
     for pattern, path_name, inputs in compact_data:
         for input in inputs:
             yield pattern, input, path_name
 
+
 @pytest.mark.parametrize("pattern, input, path_name", expand_data(match_data))
 def tests_match(pattern, input, path_name):
     regexp = fnmatch_translate(pattern, path_name)
     assert regexp.match(input) is not None
 
+
 @pytest.mark.parametrize("pattern, input, path_name", expand_data(mismatch_data))
 def tests_no_match(pattern, input, path_name):
     regexp = fnmatch_translate(pattern, path_name)
     assert regexp.match(input) is None
 
+
 @pytest.mark.parametrize("pattern", invalid_data)
 def tests_invalid(pattern):
     with pytest.raises(ValueError):
@@ -62,6 +68,7 @@
     with pytest.raises(ValueError):
         fnmatch_translate(pattern, True)
 
+
 @pytest.mark.parametrize("path, expected", filter_data)
 def test_path_filter(path, expected):
     extras = [
diff --git a/tools/lint/lint.py b/tools/lint/lint.py
index 271b9f1..c1ffd50 100644
--- a/tools/lint/lint.py
+++ b/tools/lint/lint.py
@@ -9,6 +9,7 @@
 import re
 import subprocess
 import sys
+import tempfile
 
 from collections import defaultdict
 
@@ -17,7 +18,7 @@
 from ..gitignore.gitignore import PathFilter
 from ..wpt import testfiles
 
-from manifest.sourcefile import SourceFile, js_meta_re, python_meta_re, space_chars
+from manifest.sourcefile import SourceFile, js_meta_re, python_meta_re, space_chars, get_any_variants, get_default_any_variants
 from six import binary_type, iteritems, itervalues
 from six.moves import range
 from six.moves.urllib.parse import urlsplit, urljoin
@@ -31,7 +32,10 @@
     if logger is None:
         logger = logging.getLogger(os.path.basename(os.path.splitext(__file__)[0]))
         handler = logging.StreamHandler(sys.stdout)
-        logger.addHandler(handler)
+        # Only add a handler if the parent logger is missing a handler
+        if logger.parent and len(logger.parent.handlers) == 0:
+            handler = logging.StreamHandler(sys.stdout)
+            logger.addHandler(handler)
     if prefix:
         format = logging.BASIC_FORMAT
     else:
@@ -46,7 +50,7 @@
 
 
 ERROR_MSG = """You must fix all errors; for details on how to fix them, see
-http://web-platform-tests.org/writing-tests/lint-tool.html
+https://web-platform-tests.org/writing-tests/lint-tool.html
 
 However, instead of fixing a particular error, it's sometimes
 OK to add a line to the lint.whitelist file in the root of the
@@ -56,19 +60,22 @@
 errors in the %s file,
 you could add the following line to the lint.whitelist file.
 
-%s:%s"""
+%s: %s"""
 
-def all_filesystem_paths(repo_root):
-    path_filter = PathFilter(repo_root, extras=[".git/*"])
-    for dirpath, dirnames, filenames in os.walk(repo_root):
+def all_filesystem_paths(repo_root, subdir=None):
+    path_filter = PathFilter(repo_root, extras=[".git/"])
+    if subdir:
+        expanded_path = subdir
+    else:
+        expanded_path = repo_root
+    for dirpath, dirnames, filenames in os.walk(expanded_path):
         for filename in filenames:
             path = os.path.relpath(os.path.join(dirpath, filename), repo_root)
             if path_filter(path):
                 yield path
         dirnames[:] = [item for item in dirnames if
                        path_filter(os.path.relpath(os.path.join(dirpath, item) + "/",
-                                                   repo_root))]
-
+                                                   repo_root)+"/")]
 
 def _all_files_equal(paths):
     """
@@ -108,13 +115,13 @@
     return True
 
 
-def check_path_length(repo_root, path, css_mode):
+def check_path_length(repo_root, path):
     if len(path) + 1 > 150:
         return [("PATH LENGTH", "/%s longer than maximum path length (%d > 150)" % (path, len(path) + 1), path, None)]
     return []
 
 
-def check_worker_collision(repo_root, path, css_mode):
+def check_worker_collision(repo_root, path):
     endings = [(".any.html", ".any.js"),
                (".any.worker.html", ".any.js"),
                (".worker.html", ".worker.js")]
@@ -127,19 +134,41 @@
     return []
 
 
-def check_ahem_copy(repo_root, path, css_mode):
+def check_ahem_copy(repo_root, path):
     lpath = path.lower()
     if "ahem" in lpath and lpath.endswith(".ttf"):
         return [("AHEM COPY", "Don't add extra copies of Ahem, use /fonts/Ahem.ttf", path, None)]
     return []
 
 
+def check_git_ignore(repo_root, paths):
+    errors = []
+    with tempfile.TemporaryFile('w+') as f:
+        f.write('\n'.join(paths))
+        f.seek(0)
+        try:
+            matches = subprocess.check_output(
+                ["git", "check-ignore", "--verbose", "--no-index", "--stdin"], stdin=f)
+            for match in matches.strip().split('\n'):
+                match_filter, path = match.split()
+                _, _, filter_string = match_filter.split(':')
+                # If the matching filter reported by check-ignore is a special-case exception,
+                # that's fine. Otherwise, it requires a new special-case exception.
+                if filter_string[0] != '!':
+                    errors += [("IGNORED PATH", "%s matches an ignore filter in .gitignore - "
+                                "please add a .gitignore exception" % path, path, None)]
+        except subprocess.CalledProcessError as e:
+            # Nonzero return code means that no match exists.
+            pass
+    return errors
+
+
 drafts_csswg_re = re.compile(r"https?\:\/\/drafts\.csswg\.org\/([^/?#]+)")
 w3c_tr_re = re.compile(r"https?\:\/\/www\.w3c?\.org\/TR\/([^/?#]+)")
 w3c_dev_re = re.compile(r"https?\:\/\/dev\.w3c?\.org\/[^/?#]+\/([^/?#]+)")
 
 
-def check_css_globally_unique(repo_root, paths, css_mode):
+def check_css_globally_unique(repo_root, paths):
     """
     Checks that CSS filenames are sufficiently unique
 
@@ -154,7 +183,6 @@
 
     :param repo_root: the repository root
     :param paths: list of all paths
-    :param css_mode: whether we're in CSS testsuite mode
     :returns: a list of errors found in ``paths``
 
     """
@@ -166,11 +194,9 @@
         if os.name == "nt":
             path = path.replace("\\", "/")
 
-        if not css_mode:
-            if not path.startswith("css/"):
-                continue
+        if not path.startswith("css/"):
+            continue
 
-        # we're within css or in css_mode after all that
         source_file = SourceFile(repo_root, path, "/")
         if source_file.name_is_non_test:
             # If we're name_is_non_test for a reason apart from support, ignore it.
@@ -281,7 +307,9 @@
 
     for i, (error_type, msg, path, line) in enumerate(errors):
         normpath = os.path.normcase(path)
-        if error_type in data:
+        # Allow whitelisting all lint errors except the IGNORED PATH lint,
+        # which explains how to fix it correctly and shouldn't be ignored.
+        if error_type in data and error_type != "IGNORED PATH":
             wl_files = data[error_type]
             for file_match, allowed_lines in iteritems(wl_files):
                 if None in allowed_lines or line in allowed_lines:
@@ -332,6 +360,11 @@
     error = "W3C-TEST.ORG"
     description = "External w3c-test.org domain used"
 
+class WebPlatformTestRegexp(Regexp):
+    pattern = b"web\-platform\.test"
+    error = "WEB-PLATFORM.TEST"
+    description = "Internal web-platform.test domain used"
+
 class Webidl2Regexp(Regexp):
     pattern = b"webidl2\.js"
     error = "WEBIDL2.JS"
@@ -374,6 +407,7 @@
             CRRegexp,
             SetTimeoutRegexp,
             W3CTestOrgRegexp,
+            WebPlatformTestRegexp,
             Webidl2Regexp,
             ConsoleRegexp,
             GenerateTestsRegexp,
@@ -381,7 +415,7 @@
             LayoutTestsRegexp,
             SpecialPowersRegexp]]
 
-def check_regexp_line(repo_root, path, f, css_mode):
+def check_regexp_line(repo_root, path, f):
     errors = []
 
     applicable_regexps = [regexp for regexp in regexps if regexp.applies(path)]
@@ -393,12 +427,12 @@
 
     return errors
 
-def check_parsed(repo_root, path, f, css_mode):
+def check_parsed(repo_root, path, f):
     source_file = SourceFile(repo_root, path, "/", contents=f.read())
 
     errors = []
 
-    if css_mode or path.startswith("css/"):
+    if path.startswith("css/"):
         if (source_file.type == "support" and
             not source_file.name_is_non_test and
             not source_file.name_is_reference):
@@ -409,7 +443,7 @@
             not source_file.spec_links):
             return [("MISSING-LINK", "Testcase file must have a link to a spec", path, None)]
 
-    if source_file.name_is_non_test or source_file.name_is_manual:
+    if source_file.name_is_non_test:
         return []
 
     if source_file.markup_type is None:
@@ -419,15 +453,15 @@
         return [("PARSE-FAILED", "Unable to parse file", path, None)]
 
     if source_file.type == "manual" and not source_file.name_is_manual:
-        return [("CONTENT-MANUAL", "Manual test whose filename doesn't end in '-manual'", path, None)]
+        errors.append(("CONTENT-MANUAL", "Manual test whose filename doesn't end in '-manual'", path, None))
 
     if source_file.type == "visual" and not source_file.name_is_visual:
-        return [("CONTENT-VISUAL", "Visual test whose filename doesn't end in '-visual'", path, None)]
+        errors.append(("CONTENT-VISUAL", "Visual test whose filename doesn't end in '-visual'", path, None))
 
     for reftest_node in source_file.reftest_nodes:
         href = reftest_node.attrib.get("href", "").strip(space_chars)
         parts = urlsplit(href)
-        if parts.scheme or parts.netloc:
+        if (parts.scheme or parts.netloc) and parts != urlsplit("about:blank"):
             errors.append(("ABSOLUTE-URL-REF",
                      "Reference test with a reference file specified via an absolute URL: '%s'" % href, path, None))
             continue
@@ -562,7 +596,7 @@
 
 ast_checkers = [item() for item in [OpenModeCheck]]
 
-def check_python_ast(repo_root, path, f, css_mode):
+def check_python_ast(repo_root, path, f):
     if not path.endswith(".py"):
         return []
 
@@ -582,7 +616,32 @@
 broken_python_metadata = re.compile(b"#\s*META:")
 
 
-def check_script_metadata(repo_root, path, f, css_mode):
+def check_global_metadata(value):
+    global_values = {item.strip() for item in value.split(b",") if item.strip()}
+
+    included_variants = set.union(get_default_any_variants(),
+                                  *(get_any_variants(v) for v in global_values if not v.startswith(b"!")))
+
+    for global_value in global_values:
+        if global_value.startswith(b"!"):
+            excluded_value = global_value[1:]
+            if not get_any_variants(excluded_value):
+                yield ("UNKNOWN-GLOBAL-METADATA", "Unexpected value for global metadata")
+
+            elif excluded_value in global_values:
+                yield ("BROKEN-GLOBAL-METADATA", "Cannot specify both %s and %s" % (global_value, excluded_value))
+
+            else:
+                excluded_variants = get_any_variants(excluded_value)
+                if not (excluded_variants & included_variants):
+                    yield ("BROKEN-GLOBAL-METADATA", "Cannot exclude %s if it is not included" % (excluded_value,))
+
+        else:
+            if not get_any_variants(global_value):
+                yield ("UNKNOWN-GLOBAL-METADATA", "Unexpected value for global metadata")
+
+
+def check_script_metadata(repo_root, path, f):
     if path.endswith((".worker.js", ".any.js")):
         meta_re = js_meta_re
         broken_metadata = broken_js_metadata
@@ -600,11 +659,15 @@
         m = meta_re.match(line)
         if m:
             key, value = m.groups()
-            if key == b"timeout":
+            if key == b"global":
+                errors.extend((kind, message, path, idx + 1) for (kind, message) in check_global_metadata(value))
+            elif key == b"timeout":
                 if value != b"long":
                     errors.append(("UNKNOWN-TIMEOUT-METADATA", "Unexpected value for timeout metadata", path, idx + 1))
             elif key == b"script":
                 pass
+            elif key == b"variant":
+                pass
             else:
                 errors.append(("UNKNOWN-METADATA", "Unexpected kind of metadata", path, idx + 1))
         else:
@@ -621,52 +684,49 @@
     return errors
 
 
-def check_path(repo_root, path, css_mode):
+def check_path(repo_root, path):
     """
     Runs lints that check the file path.
 
     :param repo_root: the repository root
     :param path: the path of the file within the repository
-    :param css_mode: whether we're in CSS testsuite mode
     :returns: a list of errors found in ``path``
     """
 
     errors = []
     for path_fn in path_lints:
-        errors.extend(path_fn(repo_root, path, css_mode))
+        errors.extend(path_fn(repo_root, path))
     return errors
 
 
-def check_all_paths(repo_root, paths, css_mode):
+def check_all_paths(repo_root, paths):
     """
     Runs lints that check all paths globally.
 
     :param repo_root: the repository root
     :param paths: a list of all the paths within the repository
-    :param css_mode: whether we're in CSS testsuite mode
     :returns: a list of errors found in ``f``
     """
 
     errors = []
     for paths_fn in all_paths_lints:
-        errors.extend(paths_fn(repo_root, paths, css_mode))
+        errors.extend(paths_fn(repo_root, paths))
     return errors
 
 
-def check_file_contents(repo_root, path, f, css_mode):
+def check_file_contents(repo_root, path, f):
     """
     Runs lints that check the file contents.
 
     :param repo_root: the repository root
     :param path: the path of the file within the repository
     :param f: a file-like object with the file contents
-    :param css_mode: whether we're in CSS testsuite mode
     :returns: a list of errors found in ``f``
     """
 
     errors = []
     for file_fn in file_lints:
-        errors.extend(file_fn(repo_root, path, f, css_mode))
+        errors.extend(file_fn(repo_root, path, f))
         f.seek(0)
     return errors
 
@@ -722,13 +782,20 @@
 
 def lint_paths(kwargs, wpt_root):
     if kwargs.get("paths"):
-        paths = kwargs["paths"]
+        paths = []
+        for path in kwargs.get("paths"):
+            if os.path.isdir(path):
+                path_dir = list(all_filesystem_paths(wpt_root, path))
+                paths.extend(path_dir)
+            elif os.path.isfile(path):
+                paths.append(os.path.relpath(os.path.abspath(path), wpt_root))
+
+
     elif kwargs["all"]:
         paths = list(all_filesystem_paths(wpt_root))
     else:
         changed_paths = changed_files(wpt_root)
         force_all = False
-        # If we changed the lint itself ensure that we retest everything
         for path in changed_paths:
             path = path.replace(os.path.sep, "/")
             if path == "lint.whitelist" or path.startswith("tools/lint/"):
@@ -748,8 +815,6 @@
                         help="Output machine-readable JSON format")
     parser.add_argument("--markdown", action="store_true",
                         help="Output markdown")
-    parser.add_argument("--css-mode", action="store_true",
-                        help="Run CSS testsuite specific lints")
     parser.add_argument("--repo-root", help="The WPT directory. Use this"
                         "option if the lint script exists outside the repository")
     parser.add_argument("--all", action="store_true", help="If no paths are passed, try to lint the whole "
@@ -773,10 +838,10 @@
 
     paths = lint_paths(kwargs, repo_root)
 
-    return lint(repo_root, paths, output_format, kwargs.get("css_mode", False))
+    return lint(repo_root, paths, output_format)
 
 
-def lint(repo_root, paths, output_format, css_mode):
+def lint(repo_root, paths, output_format):
     error_count = defaultdict(int)
     last = None
 
@@ -817,15 +882,15 @@
             paths.remove(path)
             continue
 
-        errors = check_path(repo_root, path, css_mode)
+        errors = check_path(repo_root, path)
         last = process_errors(errors) or last
 
         if not os.path.isdir(abs_path):
             with open(abs_path, 'rb') as f:
-                errors = check_file_contents(repo_root, path, f, css_mode)
+                errors = check_file_contents(repo_root, path, f)
                 last = process_errors(errors) or last
 
-    errors = check_all_paths(repo_root, paths, css_mode)
+    errors = check_all_paths(repo_root, paths)
     last = process_errors(errors) or last
 
     if output_format in ("normal", "markdown"):
@@ -839,6 +904,13 @@
 all_paths_lints = [check_css_globally_unique]
 file_lints = [check_regexp_line, check_parsed, check_python_ast, check_script_metadata]
 
+# Don't break users of the lint that don't have git installed.
+try:
+    subprocess.check_output(["git", "--version"])
+    all_paths_lints += [check_git_ignore]
+except subprocess.CalledProcessError:
+    print('No git present; skipping .gitignore lint.')
+
 if __name__ == "__main__":
     args = create_parser().parse_args()
     error_count = main(**vars(args))
diff --git a/tools/lint/tests/dummy/css-unique/a-ref.html b/tools/lint/tests/dummy/css/css-unique/a-ref.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/a-ref.html
rename to tools/lint/tests/dummy/css/css-unique/a-ref.html
diff --git a/tools/lint/tests/dummy/css-unique/a.html b/tools/lint/tests/dummy/css/css-unique/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/a.html
rename to tools/lint/tests/dummy/css/css-unique/a.html
diff --git a/tools/lint/tests/dummy/css-unique/match/a-ref.html b/tools/lint/tests/dummy/css/css-unique/match/a-ref.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/match/a-ref.html
rename to tools/lint/tests/dummy/css/css-unique/match/a-ref.html
diff --git a/tools/lint/tests/dummy/css-unique/match/a.html b/tools/lint/tests/dummy/css/css-unique/match/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/match/a.html
rename to tools/lint/tests/dummy/css/css-unique/match/a.html
diff --git a/tools/lint/tests/dummy/css-unique/match/support/a.html b/tools/lint/tests/dummy/css/css-unique/match/support/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/match/support/a.html
rename to tools/lint/tests/dummy/css/css-unique/match/support/a.html
diff --git a/tools/lint/tests/dummy/css-unique/match/support/tools/a.html b/tools/lint/tests/dummy/css/css-unique/match/support/tools/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/match/support/tools/a.html
rename to tools/lint/tests/dummy/css/css-unique/match/support/tools/a.html
diff --git a/tools/lint/tests/dummy/css-unique/match/tools/a.html b/tools/lint/tests/dummy/css/css-unique/match/tools/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/match/tools/a.html
rename to tools/lint/tests/dummy/css/css-unique/match/tools/a.html
diff --git a/tools/lint/tests/dummy/css-unique/not-match/a-ref.html b/tools/lint/tests/dummy/css/css-unique/not-match/a-ref.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/not-match/a-ref.html
rename to tools/lint/tests/dummy/css/css-unique/not-match/a-ref.html
diff --git a/tools/lint/tests/dummy/css-unique/not-match/a.html b/tools/lint/tests/dummy/css/css-unique/not-match/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/not-match/a.html
rename to tools/lint/tests/dummy/css/css-unique/not-match/a.html
diff --git a/tools/lint/tests/dummy/css-unique/not-match/support/a.html b/tools/lint/tests/dummy/css/css-unique/not-match/support/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/not-match/support/a.html
rename to tools/lint/tests/dummy/css/css-unique/not-match/support/a.html
diff --git a/tools/lint/tests/dummy/css-unique/not-match/tools/a.html b/tools/lint/tests/dummy/css/css-unique/not-match/tools/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/not-match/tools/a.html
rename to tools/lint/tests/dummy/css/css-unique/not-match/tools/a.html
diff --git a/tools/lint/tests/dummy/css/css-unique/relative-testharness-interact.html b/tools/lint/tests/dummy/css/css-unique/relative-testharness-interact.html
new file mode 100644
index 0000000..a50ef98
--- /dev/null
+++ b/tools/lint/tests/dummy/css/css-unique/relative-testharness-interact.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<link rel=help href="https://www.w3.org/TR/CSS2/visufx.html#overflow-clipping">
+<meta name=flags content=interact>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
diff --git a/tools/lint/tests/dummy/css/css-unique/relative-testharness.html b/tools/lint/tests/dummy/css/css-unique/relative-testharness.html
new file mode 100644
index 0000000..f15649e
--- /dev/null
+++ b/tools/lint/tests/dummy/css/css-unique/relative-testharness.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<link rel=help href="https://www.w3.org/TR/CSS2/visufx.html#overflow-clipping">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
diff --git a/tools/lint/tests/dummy/css-unique/selectors/a.html b/tools/lint/tests/dummy/css/css-unique/selectors/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/selectors/a.html
rename to tools/lint/tests/dummy/css/css-unique/selectors/a.html
diff --git a/tools/lint/tests/dummy/css-unique/support/a.html b/tools/lint/tests/dummy/css/css-unique/support/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/support/a.html
rename to tools/lint/tests/dummy/css/css-unique/support/a.html
diff --git a/tools/lint/tests/dummy/css-unique/support/tools/a.html b/tools/lint/tests/dummy/css/css-unique/support/tools/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/support/tools/a.html
rename to tools/lint/tests/dummy/css/css-unique/support/tools/a.html
diff --git a/tools/lint/tests/dummy/css-unique/tools/a.html b/tools/lint/tests/dummy/css/css-unique/tools/a.html
similarity index 100%
rename from tools/lint/tests/dummy/css-unique/tools/a.html
rename to tools/lint/tests/dummy/css/css-unique/tools/a.html
diff --git a/tools/lint/tests/dummy/ref/about_blank-ref.html b/tools/lint/tests/dummy/ref/about_blank-ref.html
new file mode 100644
index 0000000..8f940b5
--- /dev/null
+++ b/tools/lint/tests/dummy/ref/about_blank-ref.html
@@ -0,0 +1 @@
+<link rel="match" href=about:blank>
diff --git a/tools/lint/tests/dummy/tests/relative-testharness-manual.html b/tools/lint/tests/dummy/tests/relative-testharness-manual.html
new file mode 100644
index 0000000..15693b9
--- /dev/null
+++ b/tools/lint/tests/dummy/tests/relative-testharness-manual.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
diff --git a/tools/lint/tests/test_file_lints.py b/tools/lint/tests/test_file_lints.py
index 1406de2..539022b 100644
--- a/tools/lint/tests/test_file_lints.py
+++ b/tools/lint/tests/test_file_lints.py
@@ -26,7 +26,7 @@
 
 def check_with_files(input_bytes):
     return {
-        filename: (check_file_contents("", filename, six.BytesIO(input_bytes), False), kind)
+        filename: (check_file_contents("", filename, six.BytesIO(input_bytes)), kind)
         for (filename, kind) in
         (
             (os.path.join("html", filename), kind)
@@ -100,6 +100,19 @@
             expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
         assert errors == expected
 
+def test_web_platform_test():
+    error_map = check_with_files(b"import('http://web-platform.test/')")
+
+    for (filename, (errors, kind)) in error_map.items():
+        check_errors(errors)
+
+        expected = [("WEB-PLATFORM.TEST", "Internal web-platform.test domain used", filename, 1)]
+        if kind == "python":
+            expected.append(("PARSE-FAILED", "Unable to parse file", filename, 1))
+        elif kind == "web-strict":
+            expected.append(("PARSE-FAILED", "Unable to parse file", filename, None))
+        assert errors == expected
+
 
 def test_webidl2_js():
     error_map = check_with_files(b"<script src=/resources/webidl2.js>")
@@ -440,7 +453,7 @@
 def test_open_mode():
     for method in ["open", "file"]:
         code = open_mode_code.format(method).encode("utf-8")
-        errors = check_file_contents("", "test.py", six.BytesIO(code), False)
+        errors = check_file_contents("", "test.py", six.BytesIO(code))
         check_errors(errors)
 
         message = ("File opened without providing an explicit mode (note: " +
@@ -453,15 +466,13 @@
 
 
 @pytest.mark.parametrize(
-    "filename,css_mode,expect_error",
+    "filename,expect_error",
     [
-        ("foo/bar.html", False, False),
-        ("foo/bar.html", True, True),
-        ("css/bar.html", False, True),
-        ("css/bar.html", True, True),
+        ("foo/bar.html", False),
+        ("css/bar.html", True),
     ])
-def test_css_support_file(filename, css_mode, expect_error):
-    errors = check_file_contents("", filename, six.BytesIO(b""), css_mode)
+def test_css_support_file(filename, expect_error):
+    errors = check_file_contents("", filename, six.BytesIO(b""))
     check_errors(errors)
 
     if expect_error:
@@ -475,24 +486,6 @@
         assert errors == []
 
 
-def test_css_missing_file_css_mode():
-    code = b"""\
-<html xmlns="http://www.w3.org/1999/xhtml">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-</html>
-"""
-    errors = check_file_contents("", "foo/bar.html", six.BytesIO(code), True)
-    check_errors(errors)
-
-    assert errors == [
-        ('MISSING-LINK',
-         'Testcase file must have a link to a spec',
-         "foo/bar.html",
-         None),
-    ]
-
-
 def test_css_missing_file_in_css():
     code = b"""\
 <html xmlns="http://www.w3.org/1999/xhtml">
@@ -500,7 +493,7 @@
 <script src="/resources/testharnessreport.js"></script>
 </html>
 """
-    errors = check_file_contents("", "css/foo/bar.html", six.BytesIO(code), False)
+    errors = check_file_contents("", "css/foo/bar.html", six.BytesIO(code))
     check_errors(errors)
 
     assert errors == [
@@ -512,7 +505,7 @@
 
 
 def test_css_missing_file_manual():
-    errors = check_file_contents("", "css/foo/bar-manual.html", six.BytesIO(b""), False)
+    errors = check_file_contents("", "css/foo/bar-manual.html", six.BytesIO(b""))
     check_errors(errors)
 
     assert errors == [
@@ -532,6 +525,8 @@
     (b"""// META: timeout=long\n""", None),
     (b"""//  META: timeout=long\n""", None),
     (b"""// META: script=foo.js\n""", None),
+    (b"""// META: variant=\n""", None),
+    (b"""// META: variant=?wss\n""", None),
     (b"""# META:\n""", None),
     (b"""\n// META: timeout=long\n""", (2, "STRAY-METADATA")),
     (b""" // META: timeout=long\n""", (1, "INDENTED-METADATA")),
@@ -544,7 +539,7 @@
     (b"""// META: timeout=bar\n""", (1, "UNKNOWN-TIMEOUT-METADATA")),
 ])
 def test_script_metadata(filename, input, error):
-    errors = check_file_contents("", filename, six.BytesIO(input), False)
+    errors = check_file_contents("", filename, six.BytesIO(input))
     check_errors(errors)
 
     if error is not None:
@@ -566,6 +561,39 @@
         assert errors == []
 
 
+@pytest.mark.parametrize("globals,error", [
+    (b"", None),
+    (b"default", None),
+    (b"!default", None),
+    (b"window", None),
+    (b"!window", None),
+    (b"!dedicatedworker", None),
+    (b"window, !window", "BROKEN-GLOBAL-METADATA"),
+    (b"!serviceworker", "BROKEN-GLOBAL-METADATA"),
+    (b"serviceworker, !serviceworker", "BROKEN-GLOBAL-METADATA"),
+    (b"worker, !dedicatedworker", None),
+    (b"worker, !serviceworker", None),
+    (b"!worker", None),
+    (b"foo", "UNKNOWN-GLOBAL-METADATA"),
+    (b"!foo", "UNKNOWN-GLOBAL-METADATA"),
+])
+def test_script_globals_metadata(globals, error):
+    filename = "foo.any.js"
+    input = b"""// META: global=%s\n""" % globals
+    errors = check_file_contents("", filename, six.BytesIO(input))
+    check_errors(errors)
+
+    if error is not None:
+        errors = [(k, f, l) for (k, _, f, l) in errors]
+        assert errors == [
+            (error,
+             filename,
+             1),
+        ]
+    else:
+        assert errors == []
+
+
 @pytest.mark.parametrize("input,error", [
     (b"""#META: timeout=long\n""", None),
     (b"""# META: timeout=long\n""", None),
@@ -583,7 +611,7 @@
 ])
 def test_python_metadata(input, error):
     filename = "test.py"
-    errors = check_file_contents("", filename, six.BytesIO(input), False)
+    errors = check_file_contents("", filename, six.BytesIO(input))
     check_errors(errors)
 
     if error is not None:
diff --git a/tools/lint/tests/test_lint.py b/tools/lint/tests/test_lint.py
index 68c97b4..2899b1d 100644
--- a/tools/lint/tests/test_lint.py
+++ b/tools/lint/tests/test_lint.py
@@ -108,7 +108,7 @@
 
 
 def test_lint_no_files(caplog):
-    rv = lint(_dummy_repo, [], "normal", False)
+    rv = lint(_dummy_repo, [], "normal")
     assert rv == 0
     assert caplog.text == ""
 
@@ -116,7 +116,7 @@
 def test_lint_ignored_file(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["broken_ignored.html"], "normal", False)
+            rv = lint(_dummy_repo, ["broken_ignored.html"], "normal")
             assert rv == 0
             assert not mocked_check_path.called
             assert not mocked_check_file_contents.called
@@ -128,7 +128,7 @@
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
             # really long path-linted filename
             name = "a" * 256 + ".html"
-            rv = lint(_dummy_repo, [name], "normal", False)
+            rv = lint(_dummy_repo, [name], "normal")
             assert rv == 0
             assert not mocked_check_path.called
             assert not mocked_check_file_contents.called
@@ -138,7 +138,7 @@
 def test_lint_passing(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["okay.html"], "normal", False)
+            rv = lint(_dummy_repo, ["okay.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -148,7 +148,7 @@
 def test_lint_failing(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["broken.html"], "normal", False)
+            rv = lint(_dummy_repo, ["broken.html"], "normal")
             assert rv == 1
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -159,7 +159,7 @@
 def test_ref_existent_relative(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["ref/existent_relative.html"], "normal", False)
+            rv = lint(_dummy_repo, ["ref/existent_relative.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -169,7 +169,7 @@
 def test_ref_existent_root_relative(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["ref/existent_root_relative.html"], "normal", False)
+            rv = lint(_dummy_repo, ["ref/existent_root_relative.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -179,7 +179,7 @@
 def test_ref_non_existent_relative(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["ref/non_existent_relative.html"], "normal", False)
+            rv = lint(_dummy_repo, ["ref/non_existent_relative.html"], "normal")
             assert rv == 1
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -191,7 +191,7 @@
 def test_ref_non_existent_root_relative(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["ref/non_existent_root_relative.html"], "normal", False)
+            rv = lint(_dummy_repo, ["ref/non_existent_root_relative.html"], "normal")
             assert rv == 1
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -204,7 +204,7 @@
 def test_ref_absolute_url(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["ref/absolute.html"], "normal", False)
+            rv = lint(_dummy_repo, ["ref/absolute.html"], "normal")
             assert rv == 1
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -213,10 +213,20 @@
     assert "ref/absolute.html" in caplog.text
 
 
+def test_about_blank_as_ref(caplog):
+    with _mock_lint("check_path") as mocked_check_path:
+        with _mock_lint("check_file_contents") as mocked_check_file_contents:
+            rv = lint(_dummy_repo, ["about_blank-ref.html"], "normal")
+            assert rv == 0
+            assert not mocked_check_path.called
+            assert not mocked_check_file_contents.called
+    assert caplog.text == ""
+
+
 def test_ref_same_file_empty(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["ref/same_file_empty.html"], "normal", False)
+            rv = lint(_dummy_repo, ["ref/same_file_empty.html"], "normal")
             assert rv == 1
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -227,7 +237,7 @@
 def test_ref_same_file_path(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["ref/same_file_path.html"], "normal", False)
+            rv = lint(_dummy_repo, ["ref/same_file_path.html"], "normal")
             assert rv == 1
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -235,10 +245,33 @@
     assert "same_file_path.html" in caplog.text
 
 
+def test_manual_path_testharness(caplog):
+    rv = lint(_dummy_repo, ["tests/relative-testharness-manual.html"], "normal")
+    assert rv == 2
+    assert "TESTHARNESS-PATH" in caplog.text
+    assert "TESTHARNESSREPORT-PATH" in caplog.text
+
+
+def test_css_visual_path_testharness(caplog):
+    rv = lint(_dummy_repo, ["css/css-unique/relative-testharness.html"], "normal")
+    assert rv == 3
+    assert "CONTENT-VISUAL" in caplog.text
+    assert "TESTHARNESS-PATH" in caplog.text
+    assert "TESTHARNESSREPORT-PATH" in caplog.text
+
+
+def test_css_manual_path_testharness(caplog):
+    rv = lint(_dummy_repo, ["css/css-unique/relative-testharness-interact.html"], "normal")
+    assert rv == 3
+    assert "CONTENT-MANUAL" in caplog.text
+    assert "TESTHARNESS-PATH" in caplog.text
+    assert "TESTHARNESSREPORT-PATH" in caplog.text
+
+
 def test_lint_passing_and_failing(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["broken.html", "okay.html"], "normal", False)
+            rv = lint(_dummy_repo, ["broken.html", "okay.html"], "normal")
             assert rv == 1
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -250,7 +283,7 @@
 def test_check_css_globally_unique_identical_test(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/match/a.html", "css-unique/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/match/a.html", "css/css-unique/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -260,7 +293,7 @@
 def test_check_css_globally_unique_different_test(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/not-match/a.html", "css-unique/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/not-match/a.html", "css/css-unique/a.html"], "normal")
             assert rv == 2
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -270,7 +303,7 @@
 def test_check_css_globally_unique_different_spec_test(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/selectors/a.html", "css-unique/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/selectors/a.html", "css/css-unique/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -280,7 +313,7 @@
 def test_check_css_globally_unique_support_ignored(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/support/a.html", "css-unique/support/tools/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/support/a.html", "css/css-unique/support/tools/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -290,7 +323,7 @@
 def test_check_css_globally_unique_support_identical(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/support/a.html", "css-unique/match/support/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/support/a.html", "css/css-unique/match/support/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -300,7 +333,7 @@
 def test_check_css_globally_unique_support_different(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/not-match/support/a.html", "css-unique/support/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/not-match/support/a.html", "css/css-unique/support/a.html"], "normal")
             assert rv == 2
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -310,7 +343,7 @@
 def test_check_css_globally_unique_test_support(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/support/a.html", "css-unique/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/support/a.html", "css/css-unique/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -320,7 +353,7 @@
 def test_check_css_globally_unique_ref_identical(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/a-ref.html", "css-unique/match/a-ref.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/a-ref.html", "css/css-unique/match/a-ref.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -330,7 +363,7 @@
 def test_check_css_globally_unique_ref_different(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/not-match/a-ref.html", "css-unique/a-ref.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/not-match/a-ref.html", "css/css-unique/a-ref.html"], "normal")
             assert rv == 2
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -340,7 +373,7 @@
 def test_check_css_globally_unique_test_ref(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/a-ref.html", "css-unique/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/a-ref.html", "css/css-unique/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -350,7 +383,7 @@
 def test_check_css_globally_unique_ignored(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/tools/a.html", "css-unique/not-match/tools/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/tools/a.html", "css/css-unique/not-match/tools/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 2
             assert mocked_check_file_contents.call_count == 2
@@ -360,7 +393,7 @@
 def test_check_css_globally_unique_ignored_dir(caplog):
     with _mock_lint("check_path") as mocked_check_path:
         with _mock_lint("check_file_contents") as mocked_check_file_contents:
-            rv = lint(_dummy_repo, ["css-unique/support/a.html"], "normal", True)
+            rv = lint(_dummy_repo, ["css/css-unique/support/a.html"], "normal")
             assert rv == 0
             assert mocked_check_path.call_count == 1
             assert mocked_check_file_contents.call_count == 1
@@ -388,9 +421,14 @@
     orig_argv = sys.argv
     try:
         sys.argv = ['./lint', 'a', 'b', 'c']
-        with _mock_lint('lint', return_value=True) as m:
-            lint_mod.main(**vars(create_parser().parse_args()))
-            m.assert_called_once_with(repo_root, ['a', 'b', 'c'], "normal", False)
+        with mock.patch(lint_mod.__name__ + ".os.path.isfile") as mock_isfile:
+            mock_isfile.return_value = True
+            with _mock_lint('lint', return_value=True) as m:
+                lint_mod.main(**vars(create_parser().parse_args()))
+                m.assert_called_once_with(repo_root,
+                                          [os.path.relpath(os.path.join(os.getcwd(), x), repo_root)
+                                           for x in ['a', 'b', 'c']],
+                                          "normal")
     finally:
         sys.argv = orig_argv
 
@@ -402,7 +440,7 @@
         with _mock_lint('lint', return_value=True) as m:
             with _mock_lint('changed_files', return_value=['foo', 'bar']) as m2:
                 lint_mod.main(**vars(create_parser().parse_args()))
-                m.assert_called_once_with(repo_root, ['foo', 'bar'], "normal", False)
+                m.assert_called_once_with(repo_root, ['foo', 'bar'], "normal")
     finally:
         sys.argv = orig_argv
 
@@ -414,6 +452,6 @@
         with _mock_lint('lint', return_value=True) as m:
             with _mock_lint('all_filesystem_paths', return_value=['foo', 'bar']) as m2:
                 lint_mod.main(**vars(create_parser().parse_args()))
-                m.assert_called_once_with(repo_root, ['foo', 'bar'], "normal", False)
+                m.assert_called_once_with(repo_root, ['foo', 'bar'], "normal")
     finally:
         sys.argv = orig_argv
diff --git a/tools/lint/tests/test_path_lints.py b/tools/lint/tests/test_path_lints.py
index 7af4706..b581828 100644
--- a/tools/lint/tests/test_path_lints.py
+++ b/tools/lint/tests/test_path_lints.py
@@ -11,7 +11,7 @@
     for idx in range(5):
         filename = basename + idx * "a"
 
-        errors = check_path("/foo/", filename, False)
+        errors = check_path("/foo/", filename)
         check_errors(errors)
         assert errors == []
 
@@ -23,7 +23,7 @@
         filename = basename + idx * "a"
         message = "/%s longer than maximum path length (%s > 150)" % (filename, 146 + idx)
 
-        errors = check_path("/foo/", filename, False)
+        errors = check_path("/foo/", filename)
         check_errors(errors)
         assert errors == [("PATH LENGTH", message, filename, None)]
 
@@ -36,6 +36,6 @@
     message = ("path ends with %s which collides with generated tests from %s files" %
                (path_ending, generated))
 
-    errors = check_path("/foo/", path, False)
+    errors = check_path("/foo/", path)
     check_errors(errors)
     assert errors == [("WORKER COLLISION", message, path, None)]
diff --git a/tools/localpaths.py b/tools/localpaths.py
index 9d59cf8..8509114 100644
--- a/tools/localpaths.py
+++ b/tools/localpaths.py
@@ -8,8 +8,11 @@
 sys.path.insert(0, os.path.join(here, "six"))
 sys.path.insert(0, os.path.join(here, "html5lib"))
 sys.path.insert(0, os.path.join(here, "wptserve"))
-sys.path.insert(0, os.path.join(here, "pywebsocket", "src"))
-sys.path.insert(0, os.path.join(here, "py"))
-sys.path.insert(0, os.path.join(here, "pytest"))
+sys.path.insert(0, os.path.join(here, "pywebsocket"))
+sys.path.insert(0, os.path.join(here, "third_party", "attrs", "src"))
+sys.path.insert(0, os.path.join(here, "third_party", "funcsigs"))
+sys.path.insert(0, os.path.join(here, "third_party", "pluggy"))
+sys.path.insert(0, os.path.join(here, "third_party", "py"))
+sys.path.insert(0, os.path.join(here, "third_party", "pytest"))
 sys.path.insert(0, os.path.join(here, "webdriver"))
 sys.path.insert(0, os.path.join(here, "wptrunner"))
diff --git a/tools/manifest/XMLParser.py b/tools/manifest/XMLParser.py
index 5ceeb0b..523f544 100644
--- a/tools/manifest/XMLParser.py
+++ b/tools/manifest/XMLParser.py
@@ -70,8 +70,8 @@
     def _end(self, tag):
         return self._target.end(_fixname(tag))
 
-    def _external(self, context, base, systemId, publicId):
-        if publicId in {
+    def _external(self, context, base, system_id, public_id):
+        if public_id in {
                 "-//W3C//DTD XHTML 1.0 Transitional//EN",
                 "-//W3C//DTD XHTML 1.1//EN",
                 "-//W3C//DTD XHTML 1.0 Strict//EN",
diff --git a/tools/manifest/download.py b/tools/manifest/download.py
index 42998a2..611ada0 100644
--- a/tools/manifest/download.py
+++ b/tools/manifest/download.py
@@ -1,14 +1,17 @@
+from __future__ import absolute_import
+
 import argparse
 import gzip
 import json
 import io
-import log
 import os
 from datetime import datetime, timedelta
 
-import urllib2
+from six.moves.urllib.request import urlopen
 
-from vcs import Git
+from .vcs import Git
+
+from . import log
 
 here = os.path.dirname(__file__)
 
@@ -37,7 +40,7 @@
 
 def github_url(commits):
     try:
-        resp = urllib2.urlopen("https://api.github.com/repos/w3c/web-platform-tests/releases")
+        resp = urlopen("https://api.github.com/repos/w3c/web-platform-tests/releases")
     except Exception:
         return None
 
@@ -76,7 +79,7 @@
 
     logger.info("Downloading manifest from %s" % url)
     try:
-        resp = urllib2.urlopen(url)
+        resp = urlopen(url)
     except Exception:
         logger.warning("Downloading pregenerated manifest failed")
         return False
diff --git a/tools/manifest/item.py b/tools/manifest/item.py
index 06ae3c2..5a87d28 100644
--- a/tools/manifest/item.py
+++ b/tools/manifest/item.py
@@ -102,10 +102,11 @@
 class TestharnessTest(URLManifestItem):
     item_type = "testharness"
 
-    def __init__(self, source_file, url, url_base="/", timeout=None, testdriver=False, manifest=None):
+    def __init__(self, source_file, url, url_base="/", timeout=None, testdriver=False, jsshell=False, manifest=None):
         URLManifestItem.__init__(self, source_file, url, url_base=url_base, manifest=manifest)
         self.timeout = timeout
         self.testdriver = testdriver
+        self.jsshell = jsshell
 
     def meta_key(self):
         return (self.timeout, self.testdriver)
@@ -116,6 +117,8 @@
             rv[-1]["timeout"] = self.timeout
         if self.testdriver:
             rv[-1]["testdriver"] = self.testdriver
+        if self.jsshell:
+            rv[-1]["jsshell"] = True
         return rv
 
     @classmethod
@@ -128,6 +131,7 @@
                    url_base=manifest.url_base,
                    timeout=extras.get("timeout"),
                    testdriver=bool(extras.get("testdriver")),
+                   jsshell=bool(extras.get("jsshell")),
                    manifest=manifest)
 
 
diff --git a/tools/manifest/manifest.py b/tools/manifest/manifest.py
index b153798..815ce9d 100644
--- a/tools/manifest/manifest.py
+++ b/tools/manifest/manifest.py
@@ -1,8 +1,8 @@
+import itertools
 import json
 import os
-import re
 from collections import defaultdict
-from six import iteritems, itervalues, viewkeys
+from six import iteritems, itervalues, viewkeys, string_types
 
 from .item import ManualTest, WebdriverSpecTest, Stub, RefTestNode, RefTest, TestharnessTest, SupportFile, ConformanceCheckerTest, VisualTest
 from .log import get_logger
@@ -20,14 +20,6 @@
     pass
 
 
-def sourcefile_items(args):
-    tests_root, url_base, rel_path, status = args
-    source_file = SourceFile(tests_root,
-                             rel_path,
-                             url_base)
-    return rel_path, source_file.manifest_items()
-
-
 class Manifest(object):
     def __init__(self, url_base="/"):
         assert url_base is not None
@@ -64,7 +56,8 @@
     def reftest_nodes_by_url(self):
         if self._reftest_nodes_by_url is None:
             by_url = {}
-            for path, nodes in iteritems(self._data.get("reftests", {})):
+            for path, nodes in itertools.chain(iteritems(self._data.get("reftest", {})),
+                                               iteritems(self._data.get("reftest_node", {}))):
                 for node in nodes:
                     by_url[node.url] = node
             self._reftest_nodes_by_url = by_url
@@ -98,6 +91,8 @@
                     hash_changed = True
                 else:
                     new_type, manifest_items = old_type, self._data[old_type][rel_path]
+                if old_type in ("reftest", "reftest_node") and new_type != old_type:
+                    reftest_changes = True
             else:
                 new_type, manifest_items = source_file.manifest_items()
 
@@ -149,13 +144,13 @@
                     changed_hashes[item.source_file.rel_path] = (item.source_file.hash,
                                                                  item.item_type)
                 references[item.source_file.rel_path].add(item)
-                self._reftest_nodes_by_url[item.url] = item
             else:
                 if isinstance(item, RefTestNode):
                     item = item.to_RefTest()
                     changed_hashes[item.source_file.rel_path] = (item.source_file.hash,
                                                                  item.item_type)
                 reftests[item.source_file.rel_path].add(item)
+            self._reftest_nodes_by_url[item.url] = item
 
         return reftests, references, changed_hashes
 
@@ -221,7 +216,7 @@
     logger = get_logger()
 
     # "manifest" is a path or file-like object.
-    if isinstance(manifest, basestring):
+    if isinstance(manifest, string_types):
         if os.path.exists(manifest):
             logger.debug("Opening manifest at %s" % manifest)
         else:
@@ -231,6 +226,9 @@
                 rv = Manifest.from_json(tests_root, json.load(f))
         except IOError:
             return None
+        except ValueError:
+            logger.warning("%r may be corrupted", manifest)
+            return None
         return rv
 
     return Manifest.from_json(tests_root, json.load(manifest))
diff --git a/tools/manifest/sourcefile.py b/tools/manifest/sourcefile.py
index 093fc2b..3b4abbc 100644
--- a/tools/manifest/sourcefile.py
+++ b/tools/manifest/sourcefile.py
@@ -49,6 +49,89 @@
         yield (m.groups()[0], m.groups()[1])
 
 
+_any_variants = {
+    b"default": {"longhand": {b"window", b"dedicatedworker"}},
+    b"window": {"suffix": ".any.html"},
+    b"serviceworker": {"force_https": True},
+    b"sharedworker": {},
+    b"dedicatedworker": {"suffix": ".any.worker.html"},
+    b"worker": {"longhand": {b"dedicatedworker", b"sharedworker", b"serviceworker"}},
+    b"jsshell": {"suffix": ".any.js"},
+}
+
+
+def get_any_variants(item):
+    """
+    Returns a set of variants (bytestrings) defined by the given keyword.
+    """
+    assert isinstance(item, binary_type), item
+    assert not item.startswith(b"!"), item
+
+    variant = _any_variants.get(item, None)
+    if variant is None:
+        return set()
+
+    return variant.get("longhand", {item})
+
+
+def get_default_any_variants():
+    """
+    Returns a set of variants (bytestrings) that will be used by default.
+    """
+    return set(_any_variants[b"default"]["longhand"])
+
+
+def parse_variants(value):
+    """
+    Returns a set of variants (bytestrings) defined by a comma-separated value.
+    """
+    assert isinstance(value, binary_type), value
+
+    globals = get_default_any_variants()
+
+    for item in value.split(b","):
+        item = item.strip()
+        if item.startswith(b"!"):
+            globals -= get_any_variants(item[1:])
+        else:
+            globals |= get_any_variants(item)
+
+    return globals
+
+
+def global_suffixes(value):
+    """
+    Yields tuples of the relevant filename suffix (a string) and whether the
+    variant is intended to run in a JS shell, for the variants defined by the
+    given comma-separated value.
+    """
+    assert isinstance(value, binary_type), value
+
+    rv = set()
+
+    global_types = parse_variants(value)
+    for global_type in global_types:
+        variant = _any_variants[global_type]
+        suffix = variant.get("suffix", ".any.%s.html" % global_type.decode("utf-8"))
+        if variant.get("force_https", False):
+            suffix = ".https" + suffix
+        rv.add((suffix, global_type == b"jsshell"))
+
+    return rv
+
+
+def global_variant_url(url, suffix):
+    """
+    Returns a url created from the given url and suffix (all strings).
+    """
+    url = url.replace(".any.", ".")
+    # If the url must be loaded over https, ensure that it will have
+    # the form .https.any.js
+    if ".https." in url and suffix.startswith(".https."):
+        url = url.replace(".https.", ".")
+    return replace_end(url, ".js", suffix)
+
+
 class SourceFile(object):
     parsers = {"html":lambda x:html5lib.parse(x, treebuilder="etree"),
                "xhtml":lambda x:ElementTree.parse(x, XMLParser.XMLParser()),
@@ -225,8 +308,9 @@
         # wdspec tests are in subdirectories of /webdriver excluding __init__.py
         # files.
         rel_dir_tree = self.rel_path.split(os.path.sep)
-        return (rel_dir_tree[0] == "webdriver" and
-                len(rel_dir_tree) > 1 and
+        return (((rel_dir_tree[0] == "webdriver" and len(rel_dir_tree) > 1) or
+                 (rel_dir_tree[:2] == ["infrastructure", "webdriver"] and
+                  len(rel_dir_tree) > 2)) and
                 self.filename not in ("__init__.py", "conftest.py") and
                 fnmatch(self.filename, wd_pattern))
 
@@ -369,11 +453,18 @@
     @cached_property
     def test_variants(self):
         rv = []
-        for element in self.variant_nodes:
-            if "content" in element.attrib:
-                variant = element.attrib["content"]
-                assert variant == "" or variant[0] in ["#", "?"]
-                rv.append(variant)
+        if self.ext == ".js":
+            for (key, value) in self.script_metadata:
+                if key == b"variant":
+                    rv.append(value.decode("utf-8"))
+        else:
+            for element in self.variant_nodes:
+                if "content" in element.attrib:
+                    variant = element.attrib["content"]
+                    rv.append(variant)
+
+        for variant in rv:
+            assert variant == "" or variant[0] in ["#", "?"], variant
 
         if not rv:
             rv = [""]
@@ -471,7 +562,7 @@
     @cached_property
     def content_is_css_visual(self):
         """Boolean indicating whether the file content represents a
-        CSS WG-style manual test"""
+        CSS WG-style visual test"""
         if self.root is None:
             return None
         return bool(self.ext in {'.xht', '.html', '.xhtml', '.htm', '.xml', '.svg'} and
@@ -509,22 +600,34 @@
             rv = VisualTest.item_type, [VisualTest(self, self.url)]
 
         elif self.name_is_multi_global:
-            rv = TestharnessTest.item_type, [
-                TestharnessTest(self, replace_end(self.url, ".any.js", ".any.html"),
-                                timeout=self.timeout),
-                TestharnessTest(self, replace_end(self.url, ".any.js", ".any.worker.html"),
-                                timeout=self.timeout),
+            globals = b""
+            for (key, value) in self.script_metadata:
+                if key == b"global":
+                    globals = value
+                    break
+
+            tests = [
+                TestharnessTest(self, global_variant_url(self.url, suffix) + variant, timeout=self.timeout, jsshell=jsshell)
+                for (suffix, jsshell) in sorted(global_suffixes(globals))
+                for variant in self.test_variants
             ]
+            rv = TestharnessTest.item_type, tests
 
         elif self.name_is_worker:
-            rv = (TestharnessTest.item_type,
-                  [TestharnessTest(self, replace_end(self.url, ".worker.js", ".worker.html"),
-                                   timeout=self.timeout)])
+            test_url = replace_end(self.url, ".worker.js", ".worker.html")
+            tests = [
+                TestharnessTest(self, test_url + variant, timeout=self.timeout)
+                for variant in self.test_variants
+            ]
+            rv = TestharnessTest.item_type, tests
 
         elif self.name_is_window:
-            rv = (TestharnessTest.item_type,
-                  [TestharnessTest(self, replace_end(self.url, ".window.js", ".window.html"),
-                                   timeout=self.timeout)])
+            test_url = replace_end(self.url, ".window.js", ".window.html")
+            tests = [
+                TestharnessTest(self, test_url + variant, timeout=self.timeout)
+                for variant in self.test_variants
+            ]
+            rv = TestharnessTest.item_type, tests
 
         elif self.name_is_webdriver:
             rv = WebdriverSpecTest.item_type, [WebdriverSpecTest(self, self.url,
diff --git a/tools/manifest/tests/test_manifest.py b/tools/manifest/tests/test_manifest.py
index e9a67b8..770433d 100644
--- a/tools/manifest/tests/test_manifest.py
+++ b/tools/manifest/tests/test_manifest.py
@@ -232,6 +232,52 @@
     assert list(m) == [("reftest", test2.path, {test2})]
 
 
+def test_reftest_computation_chain_update_test_type():
+    m = manifest.Manifest()
+
+    s1 = SourceFileWithTest("test", "0"*40, item.RefTest, [("/test-ref", "==")])
+
+    assert m.update([s1]) is True
+
+    test1 = s1.manifest_items()[1][0]
+
+    assert list(m) == [("reftest", test1.path, {test1})]
+
+    # test becomes a testharness test (hash change because that is determined
+    # based on the file contents). The updated manifest should not includes the
+    # old reftest.
+    s2 = SourceFileWithTest("test", "1"*40, item.TestharnessTest)
+    assert m.update([s2]) is True
+
+    test2 = s2.manifest_items()[1][0]
+
+    assert list(m) == [("testharness", test2.path, {test2})]
+
+
+def test_reftest_computation_chain_update_node_change():
+    m = manifest.Manifest()
+
+    s1 = SourceFileWithTest("test1", "0"*40, item.RefTest, [("/test2", "==")])
+    s2 = SourceFileWithTest("test2", "0"*40, item.RefTestNode, [("/test3", "==")])
+
+    assert m.update([s1, s2]) is True
+
+    test1 = s1.manifest_items()[1][0]
+    test2 = s2.manifest_items()[1][0]
+
+    assert list(m) == [("reftest", test1.path, {test1}),
+                       ("reftest_node", test2.path, {test2})]
+
+    #test2 changes to support type
+    s2 = SourceFileWithTest("test2", "1"*40, item.SupportFile)
+
+    assert m.update([s1,s2]) is True
+    test3 = s2.manifest_items()[1][0]
+
+    assert list(m) == [("reftest", test1.path, {test1}),
+                       ("support", test3.path, {test3})]
+
+
 def test_iterpath():
     m = manifest.Manifest()
 
@@ -246,3 +292,22 @@
                                                                  "/test2-1.html",
                                                                  "/test2-2.html"])
     assert set(m.iterpath("missing")) == set()
+
+
+def test_reftest_node_by_url():
+    m = manifest.Manifest()
+
+    s1 = SourceFileWithTest("test1", "0"*40, item.RefTest, [("/test2", "==")])
+    s2 = SourceFileWithTest("test2", "0"*40, item.RefTest, [("/test3", "==")])
+
+    m.update([s1, s2])
+
+    test1 = s1.manifest_items()[1][0]
+    test2 = s2.manifest_items()[1][0]
+    test2_node = test2.to_RefTestNode()
+
+    assert m.reftest_nodes_by_url == {"/test1": test1,
+                                      "/test2": test2_node}
+    m._reftest_nodes_by_url = None
+    assert m.reftest_nodes_by_url == {"/test1": test1,
+                                      "/test2": test2_node}
diff --git a/tools/manifest/tests/test_sourcefile.py b/tools/manifest/tests/test_sourcefile.py
index 57863bf..c7093b8 100644
--- a/tools/manifest/tests/test_sourcefile.py
+++ b/tools/manifest/tests/test_sourcefile.py
@@ -3,6 +3,7 @@
 import pytest
 
 from six import BytesIO
+from ...lint.lint import check_global_metadata
 from ..sourcefile import SourceFile, read_script_metadata, js_meta_re, python_meta_re
 
 def create(filename, contents=b""):
@@ -201,6 +202,66 @@
         assert item.timeout == "long"
 
 
+def test_worker_with_variants():
+    contents = b"""// META: variant=
+// META: variant=?wss
+test()"""
+
+    s = create("html/test.worker.js", contents=contents)
+    assert not s.name_is_non_test
+    assert not s.name_is_manual
+    assert not s.name_is_visual
+    assert not s.name_is_multi_global
+    assert s.name_is_worker
+    assert not s.name_is_window
+    assert not s.name_is_reference
+
+    assert not s.content_is_testharness
+
+    item_type, items = s.manifest_items()
+    assert item_type == "testharness"
+
+    expected_urls = [
+        "/html/test.worker.html" + suffix
+        for suffix in ["", "?wss"]
+    ]
+    assert len(items) == len(expected_urls)
+
+    for item, url in zip(items, expected_urls):
+        assert item.url == url
+        assert item.timeout is None
+
+
+def test_window_with_variants():
+    contents = b"""// META: variant=
+// META: variant=?wss
+test()"""
+
+    s = create("html/test.window.js", contents=contents)
+    assert not s.name_is_non_test
+    assert not s.name_is_manual
+    assert not s.name_is_visual
+    assert not s.name_is_multi_global
+    assert not s.name_is_worker
+    assert s.name_is_window
+    assert not s.name_is_reference
+
+    assert not s.content_is_testharness
+
+    item_type, items = s.manifest_items()
+    assert item_type == "testharness"
+
+    expected_urls = [
+        "/html/test.window.html" + suffix
+        for suffix in ["", "?wss"]
+    ]
+    assert len(items) == len(expected_urls)
+
+    for item, url in zip(items, expected_urls):
+        assert item.url == url
+        assert item.timeout is None
+
+
 def test_python_long_timeout():
     contents = b"""# META: timeout=long
 
@@ -264,6 +325,130 @@
 
 
 @pytest.mark.parametrize("input,expected", [
+    (b"", {"dedicatedworker", "window"}),
+    (b"default", {"dedicatedworker", "window"}),
+    (b"!default", {}),
+    (b"!default,window", {"window"}),
+    (b"window,!default", {}),
+    (b"!default,dedicatedworker", {"dedicatedworker"}),
+    (b"dedicatedworker,!default", {}),
+    (b"!default,worker", {"dedicatedworker", "serviceworker", "sharedworker"}),
+    (b"worker,!default", {"serviceworker", "sharedworker"}),
+    (b"!dedicatedworker", {"window"}),
+    (b"!worker", {"window"}),
+    (b"!window", {"dedicatedworker"}),
+    (b"!window,worker", {"dedicatedworker", "serviceworker", "sharedworker"}),
+    (b"worker,!dedicatedworker", {"serviceworker", "sharedworker", "window"}),
+    (b"!dedicatedworker,worker", {"dedicatedworker", "serviceworker", "sharedworker", "window"}),
+    (b"worker,!sharedworker", {"dedicatedworker", "serviceworker", "window"}),
+    (b"!sharedworker,worker", {"dedicatedworker", "serviceworker", "sharedworker", "window"}),
+    (b"sharedworker", {"dedicatedworker", "sharedworker", "window"}),
+    (b"sharedworker,serviceworker", {"dedicatedworker", "serviceworker", "sharedworker", "window"}),
+])
+def test_multi_global_with_custom_globals(input, expected):
+    contents = b"""// META: global=%s
+test()""" % input
+
+    assert list(check_global_metadata(input)) == []
+
+    s = create("html/test.any.js", contents=contents)
+    assert not s.name_is_non_test
+    assert not s.name_is_manual
+    assert not s.name_is_visual
+    assert s.name_is_multi_global
+    assert not s.name_is_worker
+    assert not s.name_is_reference
+
+    assert not s.content_is_testharness
+
+    item_type, items = s.manifest_items()
+    assert item_type == "testharness"
+
+    urls = {
+        "dedicatedworker": "/html/test.any.worker.html",
+        "serviceworker": "/html/test.https.any.serviceworker.html",
+        "sharedworker": "/html/test.any.sharedworker.html",
+        "window": "/html/test.any.html",
+    }
+
+    expected_urls = sorted(urls[ty] for ty in expected)
+    assert len(items) == len(expected_urls)
+
+    for item, url in zip(items, expected_urls):
+        assert item.url == url
+        assert item.jsshell is False
+        assert item.timeout is None
+
+
+def test_multi_global_with_jsshell_globals():
+    contents = b"""// META: global=jsshell
+test()"""
+
+    s = create("html/test.any.js", contents=contents)
+    assert not s.name_is_non_test
+    assert not s.name_is_manual
+    assert not s.name_is_visual
+    assert s.name_is_multi_global
+    assert not s.name_is_worker
+    assert not s.name_is_reference
+
+    assert not s.content_is_testharness
+
+    item_type, items = s.manifest_items()
+    assert item_type == "testharness"
+
+    expected = [
+        ("/html/test.any.html", False),
+        ("/html/test.any.js", True),
+        ("/html/test.any.worker.html", False),
+    ]
+    assert len(items) == len(expected)
+
+    for item, (url, jsshell) in zip(items, expected):
+        assert item.url == url
+        assert item.jsshell == jsshell
+        assert item.timeout is None
+
+
+def test_multi_global_with_variants():
+    contents = b"""// META: global=window,worker
+// META: variant=
+// META: variant=?wss
+test()"""
+
+    s = create("html/test.any.js", contents=contents)
+    assert not s.name_is_non_test
+    assert not s.name_is_manual
+    assert not s.name_is_visual
+    assert s.name_is_multi_global
+    assert not s.name_is_worker
+    assert not s.name_is_reference
+
+    assert not s.content_is_testharness
+
+    item_type, items = s.manifest_items()
+    assert item_type == "testharness"
+
+    urls = {
+        "dedicatedworker": "/html/test.any.worker.html",
+        "serviceworker": "/html/test.https.any.serviceworker.html",
+        "sharedworker": "/html/test.any.sharedworker.html",
+        "window": "/html/test.any.html",
+    }
+
+    expected_urls = sorted(
+        urls[ty] + suffix
+        for ty in ["dedicatedworker", "serviceworker", "sharedworker", "window"]
+        for suffix in ["", "?wss"]
+    )
+    assert len(items) == len(expected_urls)
+
+    for item, url in zip(items, expected_urls):
+        assert item.url == url
+        assert item.timeout is None
+
+
+@pytest.mark.parametrize("input,expected", [
     (b"""//META: foo=bar\n""", [(b"foo", b"bar")]),
     (b"""// META: foo=bar\n""", [(b"foo", b"bar")]),
     (b"""//  META: foo=bar\n""", [(b"foo", b"bar")]),
@@ -395,7 +580,7 @@
     assert not s.name_is_worker
     assert not s.name_is_reference
 
-    assert s.root
+    assert s.root is not None
     assert s.content_is_testharness
 
     assert items(s) == [("testharness", "/" + filename)]
@@ -424,7 +609,7 @@
     assert not s.name_is_worker
     assert not s.name_is_reference
 
-    assert s.root
+    assert s.root is not None
     assert not s.content_is_testharness
 
     assert items(s) == []
diff --git a/tools/manifest/vcs.py b/tools/manifest/vcs.py
index 010bce8..8d5e785 100644
--- a/tools/manifest/vcs.py
+++ b/tools/manifest/vcs.py
@@ -14,7 +14,11 @@
     def get_func(repo_path):
         def git(cmd, *args):
             full_cmd = ["git", cmd] + list(args)
-            return subprocess.check_output(full_cmd, cwd=repo_path, stderr=subprocess.STDOUT)
+            try:
+                return subprocess.check_output(full_cmd, cwd=repo_path, stderr=subprocess.STDOUT)
+            except WindowsError:
+                full_cmd[0] = "git.bat"
+                return subprocess.check_output(full_cmd, cwd=repo_path, stderr=subprocess.STDOUT)
         return git
 
     @classmethod
diff --git a/tools/py/.hgtags b/tools/py/.hgtags
deleted file mode 100644
index d45ebb5..0000000
--- a/tools/py/.hgtags
+++ /dev/null
@@ -1,67 +0,0 @@
-52c6d9e78777a5a34e813123997dfc614a1a4767 1.0.0b3
-1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
-1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
-0000000000000000000000000000000000000000 1.0.0b4
-0000000000000000000000000000000000000000 1.0.0b4
-8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
-8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
-0000000000000000000000000000000000000000 1.0.0b4
-2cc0507f117ffe721dff7ee026648cfce00ec92f 1.0.0b6
-86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
-86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
-c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
-c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
-0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8
-e2a60653cb490aeed81bbbd83c070b99401c211c 1.0.0b9
-5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
-5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
-7acde360d94b6a2690ce3d03ff39301da84c0a2b 1.0.0
-6bd221981ac99103002c1cb94fede400d23a96a1 1.0.1
-4816e8b80602a3fd3a0a120333ad85fbe7d8bab4 1.0.2
-60c44bdbf093285dc69d5462d4dbb4acad325ca6 1.1.0
-319187fcda66714c5eb1353492babeec3d3c826f 1.1.1
-4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0
-c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1
-eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0
-d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
-d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
-8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
-3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
-c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
-79ef6377705184c55633d456832eea318fedcf61 1.3.4
-79ef6377705184c55633d456832eea318fedcf61 1.3.4
-90fffd35373e9f125af233f78b19416f0938d841 1.3.4
-5346ab41b059c95a48cbe1e8a7bae96ce6e0da27 1.4.0
-1f3125cba7976538952be268f107c1d0c36c5ce8 1.4.1
-04ab22db4ff737cf31e91d75a0f5d7077f324167 1.4.2
-9950bf9d684a984d511795013421c89c5cf88bef 1.4.3
-d9951e3bdbc765e73835ae13012f6a074d13d8bf 1.4.4
-b827dd156a36753e32c7f3f15ce82d6fe9e356c8 1.4.6
-f15726f9e5a67cc6221c499affa4840e9d591763 1.4.7
-abfabd07a1d328f13c730e8a50d80d2e470afd3b 1.4.9
-7f37ee0aff9be4b839d6759cfee336f60e8393a4 1.4.10
-fe4593263efa10ea7ba014db6e3379e0b82368a2 1.4.11
-f07af25a26786e4825b5170e17ad693245cb3426 1.4.12
-d3730d84ba7eda92fd3469a3f63fd6d8cb22c975 1.4.13
-12c1ae8e7c5345721e9ec9f8e27b1e36c07f74dc 1.4.14
-12c1ae8e7c5345721e9ec9f8e27b1e36c07f74dc 1.4.14
-0000000000000000000000000000000000000000 1.4.14
-0000000000000000000000000000000000000000 1.4.14
-1497e2efd0f8c73a0e3d529debf0c489e4cd6cab 1.4.14
-e065014c1ce8ad110a381e9baaaa5d647ba7ac6b 1.4.15
-e9e5b38f53dc35b35aa1f9ee9a9be9bbd2d2c3b1 1.4.16
-c603503945f52b78522d96a423605cbc953236d3 1.4.17
-c59201105a29801cc858eb9160b7a19791b91a35 1.4.18
-284cc172e294d48edc840012e1451c32c3963d92 1.4.19
-a3e0626aa0c5aecf271367dc77e476ab216ea3c8 1.4.20
-5e48016c4a3af8e7358a1267d33d021e71765bed 1.4.21
-01ae2cfcc61c4fcb3aa5031349adb5b467c31018 1.4.23
-5ffd982f4dff60b588f309cd9bdc61036547282a 1.4.24
-dc9ffbcaf1f7d72e96be3f68c11deebb7e7193c5 1.4.25
-6de1a44bf75de7af4fcae947c235e9072bbdbb9a 1.4.26
-7d650ba2657890a2253c8c4a83f170febebd90fa 1.4.27
-7d650ba2657890a2253c8c4a83f170febebd90fa 1.4.27
-1810003dec63dd1b506a23849861fffa5bc3ba13 1.4.27
-ba08706f08ddea1b77a426f00dfe2bdc244345e8 1.4.28
-4e8054ada63f3327bcf759ae7cd36c7c8652bc9b 1.4.29
-366ab346610c6de8aaa7617e24011794b40236c6 1.4.30
diff --git a/tools/py/CHANGELOG b/tools/py/CHANGELOG
deleted file mode 100644
index 712fc4c..0000000
--- a/tools/py/CHANGELOG
+++ /dev/null
@@ -1,1089 +0,0 @@
-1.4.31
-==================================================
-
-- fix local().copy(dest, mode=True) to also work
-  with unicode.
-
-- pass better error message with svn EEXIST paths
-
-1.4.30
-==================================================
-
-- fix issue68 an assert with a  multiline list comprehension 
-  was not reported correctly. Thanks Henrik Heibuerger.
-
-
-1.4.29
-==================================================
-
-- fix issue55: revert a change to the statement finding algorithm
-  which is used by pytest for generating tracebacks.
-  Thanks Daniel Hahler for initial analysis.
-
-- fix pytest issue254 for when traceback rendering can't
-  find valid source code.  Thanks Ionel Cristian Maries.
-
-
-1.4.28
-==================================================
-
-- fix issue64 -- dirpath regression when "abs=True" is passed.
-  Thanks Gilles Dartiguelongue.
-
-1.4.27
-==================================================
-
-- fix issue59: point to new repo site
-
-- allow a new ensuresyspath="append" mode for py.path.local.pyimport()
-  so that a neccessary import path is appended instead of prepended to
-  sys.path 
-
-- strike undocumented, untested argument to py.path.local.pypkgpath
-
-- speed up py.path.local.dirpath by a factor of 10
-
-1.4.26
-==================================================
-
-- avoid calling normpath twice in py.path.local
-
-- py.builtin._reraise properly reraises under Python3 now.
-
-- fix issue53 - remove module index, thanks jenisys.
-
-- allow posix path separators when "fnmatch" is called.
-  Thanks Christian Long for the complete PR.
-
-1.4.25
-==================================================
-
-- fix issue52: vaguely fix py25 compat of py.path.local (it's not
-  officially supported), also fix docs
-
-- fix pytest issue 589: when checking if we have a recursion error
-  check for the specific "maximum recursion depth" text of the exception.
-
-1.4.24
-==================================================
-
-- Fix retrieving source when an else: line has an other statement on
-  the same line.
-
-- add localpath read_text/write_text/read_bytes/write_bytes methods
-  as shortcuts and clearer bytes/text interfaces for read/write.
-  Adapted from a PR from Paul Moore.
-
-
-1.4.23
-==================================================
-
-- use newer apipkg version which makes attribute access on
-  alias modules resolve to None rather than an ImportError.
-  This helps with code that uses inspect.getframeinfo()
-  on py34 which causes a complete walk on sys.modules
-  thus triggering the alias module to resolve and blowing
-  up with ImportError.  The negative side is that something
-  like "py.test.X" will now result in None instead of "importerror: pytest"
-  if pytest is not installed.  But you shouldn't import "py.test" 
-  anyway anymore.
-
-- adapt one svn test to only check for any exception instead 
-  of specific ones because different svn versions cause different
-  errors and we don't care.
-
-
-1.4.22
-==================================================
-
-- refactor class-level registry on ForkedFunc child start/finish
-  event to become instance based (i.e. passed into the constructor)
-
-1.4.21
-==================================================
-
-- ForkedFunc now has class-level register_on_start/on_exit()
-  methods to allow adding information in the boxed process.
-  Thanks Marc Schlaich.
-
-- ForkedFunc in the child opens in "auto-flush" mode for
-  stdout/stderr so that when a subprocess dies you can see
-  its output even if it didn't flush itself.
-
-- refactor traceback generation in light of pytest issue 364
-  (shortening tracebacks).   you can now set a new traceback style 
-  on a per-entry basis such that a caller can force entries to be 
-  isplayed as short or long entries.
-
-- win32: py.path.local.sysfind(name) will preferrably return files with
-  extensions so that if "X" and "X.bat" or "X.exe" is on the PATH,
-  one of the latter two will be returned.
-
-1.4.20
-==================================================
-
-- ignore unicode decode errors in xmlescape.  Thanks Anatoly Bubenkoff.
-
-- on python2 modify traceback.format_exception_only to match python3 
-  behaviour, namely trying to print unicode for Exception instances
-
-- use a safer way for serializing exception reports (helps to fix
-  pytest issue413)
-
-Changes between 1.4.18 and 1.4.19
-==================================================
-
-- merge in apipkg fixes
-
-- some micro-optimizations in py/_code/code.py for speeding
-  up pytest runs.  Thanks Alex Gaynor for initiative.
-
-- check PY_COLORS=1 or PY_COLORS=0 to force coloring/not-coloring
-  for py.io.TerminalWriter() independently from capabilities
-  of the output file.  Thanks Marc Abramowitz for the PR.
-
-- some fixes to unicode handling in assertion handling.
-  Thanks for the PR to Floris Bruynooghe.  (This helps
-  to fix pytest issue 319).
-
-- depend on setuptools presence, remove distribute_setup
-
-Changes between 1.4.17 and 1.4.18
-==================================================
-
-- introduce path.ensure_dir() as a synonym for ensure(..., dir=1)
-
-- some unicode/python3 related fixes wrt to path manipulations
-  (if you start passing unicode particular in py2 you might 
-  still get problems, though)
-
-Changes between 1.4.16 and 1.4.17
-==================================================
-
-- make py.io.TerminalWriter() prefer colorama if it is available
-  and avoid empty lines when separator-lines are printed by
-  being defensive and reducing the working terminalwidth by 1
-
-- introduce optional "expanduser" argument to py.path.local
-  to that local("~", expanduser=True) gives the home
-  directory of "user".
-
-Changes between 1.4.15 and 1.4.16
-==================================================
-
-- fix issue35 - define __gt__ ordering between a local path
-  and strings
-
-- fix issue36 - make chdir() work even if os.getcwd() fails.
-
-- add path.exists/isdir/isfile/islink shortcuts
-
-- introduce local path.as_cwd() context manager.
-
-- introduce p.write(ensure=1) and p.open(ensure=1)
-  where ensure triggers creation of neccessary parent
-  dirs.
-
-
-Changes between 1.4.14 and 1.4.15
-==================================================
-
-- majorly speed up some common calling patterns with
-  LocalPath.listdir()/join/check/stat functions considerably.
-
-- fix an edge case with fnmatch where a glob style pattern appeared
-  in an absolute path.
-
-Changes between 1.4.13 and 1.4.14
-==================================================
-
-- fix dupfile to work with files that don't
-  carry a mode. Thanks Jason R. Coombs.
-
-Changes between 1.4.12 and 1.4.13
-==================================================
-
-- fix getting statementrange/compiling a file ending
-  in a comment line without newline (on python2.5)
-- for local paths you can pass "mode=True" to a copy()
-  in order to copy permission bits (underlying mechanism
-  is using shutil.copymode)
-- add paths arguments to py.path.local.sysfind to restrict
-  search to the diretories in the path.
-- add isdir/isfile/islink to path.stat() objects allowing to perform
-  multiple checks without calling out multiple times
-- drop py.path.local.__new__ in favour of a simpler __init__
-- iniconfig: allow "name:value" settings in config files, no space after
-  "name" required
-- fix issue 27 - NameError in unlikely untested case of saferepr
-
-
-Changes between 1.4.11 and 1.4.12
-==================================================
-
-- fix python2.4 support - for pre-AST interpreters re-introduce 
-  old way to find statements in exceptions (closes pytest issue 209)
-- add tox.ini to distribution
-- fix issue23 - print *,** args information in tracebacks,
-  thanks Manuel Jacob
-
-
-Changes between 1.4.10 and 1.4.11
-==================================================
-
-- use _ast to determine statement ranges when printing tracebacks -
-  avoiding multi-second delays on some large test modules
-- fix an internal test to not use class-denoted pytest_funcarg__
-- fix a doc link to bug tracker
-- try to make terminal.write() printing more robust against
-  unicodeencode/decode problems, amend according test
-- introduce py.builtin.text and py.builtin.bytes
-  to point to respective str/unicode (py2) and bytes/str (py3) types
-- fix error handling on win32/py33 for ENODIR 
-
-Changes between 1.4.9 and 1.4.10
-==================================================
-
-- terminalwriter: default to encode to UTF8 if no encoding is defined
-  on the output stream
-- issue22: improve heuristic for finding the statementrange in exceptions
-
-Changes between 1.4.8 and 1.4.9
-==================================================
-
-- fix bug of path.visit() which would not recognize glob-style patterns
-  for the "rec" recursion argument
-- changed iniconfig parsing to better conform, now the chars ";"
-  and "#" only mark a comment at the stripped start of a line
-- include recent apipkg-1.2
-- change internal terminalwriter.line/reline logic to more nicely
-  support file spinners
-
-Changes between 1.4.7 and 1.4.8
-==================================================
-
-- fix issue 13 - correct handling of the tag name object in xmlgen
-- fix issue 14 - support raw attribute values in xmlgen
-- fix windows terminalwriter printing/re-line problem
-- update distribute_setup.py to 0.6.27
-
-Changes between 1.4.6 and 1.4.7
-==================================================
-
-- fix issue11 - own test failure with python3.3 / Thanks Benjamin Peterson
-- help fix pytest issue 102
-
-Changes between 1.4.5 and 1.4.6
-==================================================
-
-- help to fix pytest issue99: unify output of 
-  ExceptionInfo.getrepr(style="native") with ...(style="long")
-- fix issue7: source.getstatementrange() now raises proper error
-  if no valid statement can be found
-- fix issue8: fix code and tests of svnurl/svnwc to work on subversion 1.7 - 
-  note that path.status(updates=1) will not properly work svn-17's status 
-  --xml output is broken.
-- make source.getstatementrange() more resilent about non-python code frames
-  (as seen from jnja2)
-- make trackeback recursion detection more resilent
-  about the eval magic of a decorator library
-- iniconfig: add support for ; as comment starter
-- properly handle lists in xmlgen on python3
-- normalize py.code.getfslineno(obj) to always return a (string, int) tuple
-  defaulting to ("", -1) respectively if no source code can be found for obj.
-
-Changes between 1.4.4 and 1.4.5
-==================================================
-
-- improve some unicode handling in terminalwriter and capturing
-  (used by pytest)
-
-Changes between 1.4.3 and 1.4.4
-==================================================
-
-- a few fixes and assertion related refinements for pytest-2.1
-- guard py.code.Code and getfslineno against bogus input
-  and make py.code.Code objects for object instance
-  by looking up their __call__ function.
-- make exception presentation robust against invalid current cwd
-
-Changes between 1.4.2 and 1.4.3
-==================================================
-
-- fix terminal coloring issue for skipped tests (thanks Amaury)
-- fix issue4 - large calls to ansi_print (thanks Amaury)
-
-Changes between 1.4.1 and 1.4.2
-==================================================
-
-- fix (pytest) issue23 - tmpdir argument now works on Python3.2 and WindowsXP
-  (which apparently starts to offer os.symlink now)
-
-- better error message for syntax errors from compiled code
-
-- small fix to better deal with (un-)colored terminal output on windows
-
-Changes between 1.4.0 and 1.4.1
-==================================================
-
-- fix issue1 - py.error.* classes to be pickleable
-
-- fix issue2 - on windows32 use PATHEXT as the list of potential
-  extensions to find find binaries with py.path.local.sysfind(commandname)
-
-- fix (pytest-) issue10 and refine assertion reinterpretation
-  to avoid breaking if the __nonzero__ of an object fails
-
-- fix (pytest-) issue17 where python3 does not like "import *"
-  leading to misrepresentation of import-errors in test modules
-
-- fix py.error.* attribute pypy access issue
-
-- allow path.samefile(arg) to succeed when arg is a relative filename
-
-- fix (pytest-) issue20 path.samefile(relpath) works as expected now
-
-- fix (pytest-) issue8 len(long_list) now shows the lenght of the list
-
-Changes between 1.3.4 and 1.4.0
-==================================================
-
-- py.test was moved to a separate "pytest" package. What remains is
-  a stub hook which will proxy ``import py.test`` to ``pytest``.
-- all command line tools ("py.cleanup/lookup/countloc/..." moved 
-  to "pycmd" package)
-- removed the old and deprecated "py.magic" namespace
-- use apipkg-1.1 and make py.apipkg.initpkg|ApiModule available
-- add py.iniconfig module for brain-dead easy ini-config file parsing
-- introduce py.builtin.any()
-- path objects have a .dirname attribute now (equivalent to
-  os.path.dirname(path))
-- path.visit() accepts breadthfirst (bf) and sort options
-- remove deprecated py.compat namespace
-
-Changes between 1.3.3 and 1.3.4
-==================================================
-
-- fix issue111: improve install documentation for windows
-- fix issue119: fix custom collectability of __init__.py as a module
-- fix issue116: --doctestmodules work with __init__.py files as well
-- fix issue115: unify internal exception passthrough/catching/GeneratorExit
-- fix issue118: new --tb=native for presenting cpython-standard exceptions
-
-Changes between 1.3.2 and 1.3.3
-==================================================
-
-- fix issue113: assertion representation problem with triple-quoted strings
-  (and possibly other cases)
-- make conftest loading detect that a conftest file with the same
-  content was already loaded, avoids surprises in nested directory structures
-  which can be produced e.g. by Hudson. It probably removes the need to use
-  --confcutdir in most cases.
-- fix terminal coloring for win32
-  (thanks Michael Foord for reporting)
-- fix weirdness: make terminal width detection work on stdout instead of stdin
-  (thanks Armin Ronacher for reporting)
-- remove trailing whitespace in all py/text distribution files
-
-Changes between 1.3.1 and 1.3.2
-==================================================
-
-New features
-++++++++++++++++++
-
-- fix issue103:  introduce py.test.raises as context manager, examples::
-
-    with py.test.raises(ZeroDivisionError):
-        x = 0
-        1 / x
-
-    with py.test.raises(RuntimeError) as excinfo:
-        call_something()
-
-    # you may do extra checks on excinfo.value|type|traceback here
-
-  (thanks Ronny Pfannschmidt)
-
-- Funcarg factories can now dynamically apply a marker to a
-  test invocation.  This is for example useful if a factory
-  provides parameters to a test which are expected-to-fail::
-
-    def pytest_funcarg__arg(request):
-        request.applymarker(py.test.mark.xfail(reason="flaky config"))
-        ...
-
-    def test_function(arg):
-        ...
-
-- improved error reporting on collection and import errors. This makes
-  use of a more general mechanism, namely that for custom test item/collect
-  nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
-  override it to return a string error representation of your choice
-  which is going to be reported as a (red) string.
-
-- introduce '--junitprefix=STR' option to prepend a prefix
-  to all reports in the junitxml file.
-
-Bug fixes / Maintenance
-++++++++++++++++++++++++++
-
-- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
-  to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
-  you can properly check for their existence in a cross-python manner).
-- refine --pdb: ignore xfailed tests, unify its TB-reporting and
-  don't display failures again at the end.
-- fix assertion interpretation with the ** operator (thanks Benjamin Peterson)
-- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson)
-- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous)
-- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
-- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
-- fix py.code.compile(source) to generate unique filenames
-- fix assertion re-interp problems on PyPy, by defering code
-  compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
-- fix py.path.local.pyimport() to work with directories
-- streamline py.path.local.mkdtemp implementation and usage
-- don't print empty lines when showing junitxml-filename
-- add optional boolean ignore_errors parameter to py.path.local.remove
-- fix terminal writing on win32/python2.4
-- py.process.cmdexec() now tries harder to return properly encoded unicode objects
-  on all python versions
-- install plain py.test/py.which scripts also for Jython, this helps to
-  get canonical script paths in virtualenv situations
-- make path.bestrelpath(path) return ".", note that when calling
-  X.bestrelpath the assumption is that X is a directory.
-- make initial conftest discovery ignore "--" prefixed arguments
-- fix resultlog plugin when used in an multicpu/multihost xdist situation
-  (thanks Jakub Gustak)
-- perform distributed testing related reporting in the xdist-plugin
-  rather than having dist-related code in the generic py.test
-  distribution
-- fix homedir detection on Windows
-- ship distribute_setup.py version 0.6.13
-
-Changes between 1.3.0 and 1.3.1
-==================================================
-
-New features
-++++++++++++++++++
-
-- issue91: introduce new py.test.xfail(reason) helper
-  to imperatively mark a test as expected to fail. Can
-  be used from within setup and test functions. This is
-  useful especially for parametrized tests when certain
-  configurations are expected-to-fail.  In this case the
-  declarative approach with the @py.test.mark.xfail cannot
-  be used as it would mark all configurations as xfail.
-
-- issue102: introduce new --maxfail=NUM option to stop
-  test runs after NUM failures.  This is a generalization
-  of the '-x' or '--exitfirst' option which is now equivalent
-  to '--maxfail=1'.  Both '-x' and '--maxfail' will
-  now also print a line near the end indicating the Interruption.
-
-- issue89: allow py.test.mark decorators to be used on classes
-  (class decorators were introduced with python2.6) and
-  also allow to have multiple markers applied at class/module level
-  by specifying a list.
-
-- improve and refine letter reporting in the progress bar:
-  .  pass
-  f  failed test
-  s  skipped tests (reminder: use for dependency/platform mismatch only)
-  x  xfailed test (test that was expected to fail)
-  X  xpassed test (test that was expected to fail but passed)
-
-  You can use any combination of 'fsxX' with the '-r' extended
-  reporting option. The xfail/xpass results will show up as
-  skipped tests in the junitxml output - which also fixes
-  issue99.
-
-- make py.test.cmdline.main() return the exitstatus instead of raising
-  SystemExit and also allow it to be called multiple times.  This of
-  course requires that your application and tests are properly teared
-  down and don't have global state.
-
-Fixes / Maintenance
-++++++++++++++++++++++
-
-- improved traceback presentation:
-  - improved and unified reporting for "--tb=short" option
-  - Errors during test module imports are much shorter, (using --tb=short style)
-  - raises shows shorter more relevant tracebacks
-  - --fulltrace now more systematically makes traces longer / inhibits cutting
-
-- improve support for raises and other dynamically compiled code by
-  manipulating python's linecache.cache instead of the previous
-  rather hacky way of creating custom code objects.  This makes
-  it seemlessly work on Jython and PyPy where it previously didn't.
-
-- fix issue96: make capturing more resilient against Control-C
-  interruptions (involved somewhat substantial refactoring
-  to the underlying capturing functionality to avoid race
-  conditions).
-
-- fix chaining of conditional skipif/xfail decorators - so it works now
-  as expected to use multiple @py.test.mark.skipif(condition) decorators,
-  including specific reporting which of the conditions lead to skipping.
-
-- fix issue95: late-import zlib so that it's not required
-  for general py.test startup.
-
-- fix issue94: make reporting more robust against bogus source code
-  (and internally be more careful when presenting unexpected byte sequences)
-
-
-Changes between 1.2.1 and 1.3.0
-==================================================
-
-- deprecate --report option in favour of a new shorter and easier to
-  remember -r option: it takes a string argument consisting of any
-  combination of 'xfsX' characters.  They relate to the single chars
-  you see during the dotted progress printing and will print an extra line
-  per test at the end of the test run.  This extra line indicates the exact
-  position or test ID that you directly paste to the py.test cmdline in order
-  to re-run a particular test.
-
-- allow external plugins to register new hooks via the new
-  pytest_addhooks(pluginmanager) hook.  The new release of
-  the pytest-xdist plugin for distributed and looponfailing
-  testing requires this feature.
-
-- add a new pytest_ignore_collect(path, config) hook to allow projects and
-  plugins to define exclusion behaviour for their directory structure -
-  for example you may define in a conftest.py this method::
-
-        def pytest_ignore_collect(path):
-            return path.check(link=1)
-
-  to prevent even a collection try of any tests in symlinked dirs.
-
-- new pytest_pycollect_makemodule(path, parent) hook for
-  allowing customization of the Module collection object for a
-  matching test module.
-
-- extend and refine xfail mechanism:
-  ``@py.test.mark.xfail(run=False)`` do not run the decorated test
-  ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries
-  specifiying ``--runxfail`` on command line virtually ignores xfail markers
-
-- expose (previously internal) commonly useful methods:
-  py.io.get_terminal_with() -> return terminal width
-  py.io.ansi_print(...) -> print colored/bold text on linux/win32
-  py.io.saferepr(obj) -> return limited representation string
-
-- expose test outcome related exceptions as py.test.skip.Exception,
-  py.test.raises.Exception etc., useful mostly for plugins
-  doing special outcome interpretation/tweaking
-
-- (issue85) fix junitxml plugin to handle tests with non-ascii output
-
-- fix/refine python3 compatibility (thanks Benjamin Peterson)
-
-- fixes for making the jython/win32 combination work, note however:
-  jython2.5.1/win32 does not provide a command line launcher, see
-  http://bugs.jython.org/issue1491 . See pylib install documentation
-  for how to work around.
-
-- fixes for handling of unicode exception values and unprintable objects
-
-- (issue87) fix unboundlocal error in assertionold code
-
-- (issue86) improve documentation for looponfailing
-
-- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
-
-- ship distribute_setup.py version 0.6.10
-
-- added links to the new capturelog and coverage plugins
-
-
-Changes between 1.2.1 and 1.2.0
-=====================================
-
-- refined usage and options for "py.cleanup"::
-
-    py.cleanup     # remove "*.pyc" and "*$py.class" (jython) files
-    py.cleanup -e .swp -e .cache # also remove files with these extensions
-    py.cleanup -s  # remove "build" and "dist" directory next to setup.py files
-    py.cleanup -d  # also remove empty directories
-    py.cleanup -a  # synonym for "-s -d -e 'pip-log.txt'"
-    py.cleanup -n  # dry run, only show what would be removed
-
-- add a new option "py.test --funcargs" which shows available funcargs
-  and their help strings (docstrings on their respective factory function)
-  for a given test path
-
-- display a short and concise traceback if a funcarg lookup fails
-
-- early-load "conftest.py" files in non-dot first-level sub directories.
-  allows to conveniently keep and access test-related options in a ``test``
-  subdir and still add command line options.
-
-- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
-
-- fix issue78: always call python-level teardown functions even if the
-  according setup failed.  This includes refinements for calling setup_module/class functions
-  which will now only be called once instead of the previous behaviour where they'd be called
-  multiple times if they raise an exception (including a Skipped exception).  Any exception
-  will be re-corded and associated with all tests in the according module/class scope.
-
-- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
-
-- fix pdb debugging to be in the correct frame on raises-related errors
-
-- update apipkg.py to fix an issue where recursive imports might
-  unnecessarily break importing
-
-- fix plugin links
-
-Changes between 1.2 and 1.1.1
-=====================================
-
-- moved dist/looponfailing from py.test core into a new
-  separately released pytest-xdist plugin.
-
-- new junitxml plugin: --junitxml=path will generate a junit style xml file
-  which is processable e.g. by the Hudson CI system.
-
-- new option: --genscript=path will generate a standalone py.test script
-  which will not need any libraries installed.  thanks to Ralf Schmitt.
-
-- new option: --ignore will prevent specified path from collection.
-  Can be specified multiple times.
-
-- new option: --confcutdir=dir will make py.test only consider conftest
-  files that are relative to the specified dir.
-
-- new funcarg: "pytestconfig" is the pytest config object for access
-  to command line args and can now be easily used in a test.
-
-- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to
-  disambiguate between Python3, python2.X, Jython and PyPy installed versions.
-
-- new "pytestconfig" funcarg allows access to test config object
-
-- new "pytest_report_header" hook can return additional lines
-  to be displayed at the header of a test run.
-
-- (experimental) allow "py.test path::name1::name2::..." for pointing
-  to a test within a test collection directly.  This might eventually
-  evolve as a full substitute to "-k" specifications.
-
-- streamlined plugin loading: order is now as documented in
-  customize.html: setuptools, ENV, commandline, conftest.
-  also setuptools entry point names are turned to canonical namees ("pytest_*")
-
-- automatically skip tests that need 'capfd' but have no os.dup
-
-- allow pytest_generate_tests to be defined in classes as well
-
-- deprecate usage of 'disabled' attribute in favour of pytestmark
-- deprecate definition of Directory, Module, Class and Function nodes
-  in conftest.py files.  Use pytest collect hooks instead.
-
-- collection/item node specific runtest/collect hooks are only called exactly
-  on matching conftest.py files, i.e. ones which are exactly below
-  the filesystem path of an item
-
-- change: the first pytest_collect_directory hook to return something
-  will now prevent further hooks to be called.
-
-- change: figleaf plugin now requires --figleaf to run.  Also
-  change its long command line options to be a bit shorter (see py.test -h).
-
-- change: pytest doctest plugin is now enabled by default and has a
-  new option --doctest-glob to set a pattern for file matches.
-
-- change: remove internal py._* helper vars, only keep py._pydir
-
-- robustify capturing to survive if custom pytest_runtest_setup
-  code failed and prevented the capturing setup code from running.
-
-- make py.test.* helpers provided by default plugins visible early -
-  works transparently both for pydoc and for interactive sessions
-  which will regularly see e.g. py.test.mark and py.test.importorskip.
-
-- simplify internal plugin manager machinery
-- simplify internal collection tree by introducing a RootCollector node
-
-- fix assert reinterpreation that sees a call containing "keyword=..."
-
-- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
-  hooks on slaves during dist-testing, report module/session teardown
-  hooks correctly.
-
-- fix issue65: properly handle dist-testing if no
-  execnet/py lib installed remotely.
-
-- skip some install-tests if no execnet is available
-
-- fix docs, fix internal bin/ script generation
-
-
-Changes between 1.1.1 and 1.1.0
-=====================================
-
-- introduce automatic plugin registration via 'pytest11'
-  entrypoints via setuptools' pkg_resources.iter_entry_points
-
-- fix py.test dist-testing to work with execnet >= 1.0.0b4
-
-- re-introduce py.test.cmdline.main() for better backward compatibility
-
-- svn paths: fix a bug with path.check(versioned=True) for svn paths,
-  allow '%' in svn paths, make svnwc.update() default to interactive mode
-  like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
-
-- refine distributed tarball to contain test and no pyc files
-
-- try harder to have deprecation warnings for py.compat.* accesses
-  report a correct location
-
-Changes between 1.1.0 and 1.0.2
-=====================================
-
-* adjust and improve docs
-
-* remove py.rest tool and internal namespace - it was
-  never really advertised and can still be used with
-  the old release if needed.  If there is interest
-  it could be revived into its own tool i guess.
-
-* fix issue48 and issue59: raise an Error if the module
-  from an imported test file does not seem to come from
-  the filepath - avoids "same-name" confusion that has
-  been reported repeatedly
-
-* merged Ronny's nose-compatibility hacks: now
-  nose-style setup_module() and setup() functions are
-  supported
-
-* introduce generalized py.test.mark function marking
-
-* reshuffle / refine command line grouping
-
-* deprecate parser.addgroup in favour of getgroup which creates option group
-
-* add --report command line option that allows to control showing of skipped/xfailed sections
-
-* generalized skipping: a new way to mark python functions with skipif or xfail
-  at function, class and modules level based on platform or sys-module attributes.
-
-* extend py.test.mark decorator to allow for positional args
-
-* introduce and test "py.cleanup -d" to remove empty directories
-
-* fix issue #59 - robustify unittest test collection
-
-* make bpython/help interaction work by adding an __all__ attribute
-  to ApiModule, cleanup initpkg
-
-* use MIT license for pylib, add some contributors
-
-* remove py.execnet code and substitute all usages with 'execnet' proper
-
-* fix issue50 - cached_setup now caches more to expectations
-  for test functions with multiple arguments.
-
-* merge Jarko's fixes, issue #45 and #46
-
-* add the ability to specify a path for py.lookup to search in
-
-* fix a funcarg cached_setup bug probably only occuring
-  in distributed testing and "module" scope with teardown.
-
-* many fixes and changes for making the code base python3 compatible,
-  many thanks to Benjamin Peterson for helping with this.
-
-* consolidate builtins implementation to be compatible with >=2.3,
-  add helpers to ease keeping 2 and 3k compatible code
-
-* deprecate py.compat.doctest|subprocess|textwrap|optparse
-
-* deprecate py.magic.autopath, remove py/magic directory
-
-* move pytest assertion handling to py/code and a pytest_assertion
-  plugin, add "--no-assert" option, deprecate py.magic namespaces
-  in favour of (less) py.code ones.
-
-* consolidate and cleanup py/code classes and files
-
-* cleanup py/misc, move tests to bin-for-dist
-
-* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
-
-* consolidate py.log implementation, remove old approach.
-
-* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
-  text/unicode and byte-streams (uses underlying standard lib io.*
-  if available)
-
-* make py.unittest_convert helper script available which converts "unittest.py"
-  style files into the simpler assert/direct-test-classes py.test/nosetests
-  style.  The script was written by Laura Creighton.
-
-* simplified internal localpath implementation
-
-Changes between 1.0.1 and 1.0.2
-=====================================
-
-* fixing packaging issues, triggered by fedora redhat packaging,
-  also added doc, examples and contrib dirs to the tarball.
-
-* added a documentation link to the new django plugin.
-
-Changes between 1.0.0 and 1.0.1
-=====================================
-
-* added a 'pytest_nose' plugin which handles nose.SkipTest,
-  nose-style function/method/generator setup/teardown and
-  tries to report functions correctly.
-
-* capturing of unicode writes or encoded strings to sys.stdout/err
-  work better, also terminalwriting was adapted and somewhat
-  unified between windows and linux.
-
-* improved documentation layout and content a lot
-
-* added a "--help-config" option to show conftest.py / ENV-var names for
-  all longopt cmdline options, and some special conftest.py variables.
-  renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
-
-* fix issue #27: better reporting on non-collectable items given on commandline
-  (e.g. pyc files)
-
-* fix issue #33: added --version flag (thanks Benjamin Peterson)
-
-* fix issue #32: adding support for "incomplete" paths to wcpath.status()
-
-* "Test" prefixed classes are *not* collected by default anymore if they
-  have an __init__ method
-
-* monkeypatch setenv() now accepts a "prepend" parameter
-
-* improved reporting of collection error tracebacks
-
-* simplified multicall mechanism and plugin architecture,
-  renamed some internal methods and argnames
-
-Changes between 1.0.0b9 and 1.0.0
-=====================================
-
-* more terse reporting try to show filesystem path relatively to current dir
-* improve xfail output a bit
-
-Changes between 1.0.0b8 and 1.0.0b9
-=====================================
-
-* cleanly handle and report final teardown of test setup
-
-* fix svn-1.6 compat issue with py.path.svnwc().versioned()
-  (thanks Wouter Vanden Hove)
-
-* setup/teardown or collection problems now show as ERRORs
-  or with big "E"'s in the progress lines.  they are reported
-  and counted separately.
-
-* dist-testing: properly handle test items that get locally
-  collected but cannot be collected on the remote side - often
-  due to platform/dependency reasons
-
-* simplified py.test.mark API - see keyword plugin documentation
-
-* integrate better with logging: capturing now by default captures
-  test functions and their immediate setup/teardown in a single stream
-
-* capsys and capfd funcargs now have a readouterr() and a close() method
-  (underlyingly py.io.StdCapture/FD objects are used which grew a
-  readouterr() method as well to return snapshots of captured out/err)
-
-* make assert-reinterpretation work better with comparisons not
-  returning bools (reported with numpy from thanks maciej fijalkowski)
-
-* reworked per-test output capturing into the pytest_iocapture.py plugin
-  and thus removed capturing code from config object
-
-* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
-
-
-Changes between 1.0.0b7 and 1.0.0b8
-=====================================
-
-* pytest_unittest-plugin is now enabled by default
-
-* introduced pytest_keyboardinterrupt hook and
-  refined pytest_sessionfinish hooked, added tests.
-
-* workaround a buggy logging module interaction ("closing already closed
-  files").  Thanks to Sridhar Ratnakumar for triggering.
-
-* if plugins use "py.test.importorskip" for importing
-  a dependency only a warning will be issued instead
-  of exiting the testing process.
-
-* many improvements to docs:
-  - refined funcargs doc , use the term "factory" instead of "provider"
-  - added a new talk/tutorial doc page
-  - better download page
-  - better plugin docstrings
-  - added new plugins page and automatic doc generation script
-
-* fixed teardown problem related to partially failing funcarg setups
-  (thanks MrTopf for reporting), "pytest_runtest_teardown" is now
-  always invoked even if the "pytest_runtest_setup" failed.
-
-* tweaked doctest output for docstrings in py modules,
-  thanks Radomir.
-
-Changes between 1.0.0b3 and 1.0.0b7
-=============================================
-
-* renamed py.test.xfail back to py.test.mark.xfail to avoid
-  two ways to decorate for xfail
-
-* re-added py.test.mark decorator for setting keywords on functions
-  (it was actually documented so removing it was not nice)
-
-* remove scope-argument from request.addfinalizer() because
-  request.cached_setup has the scope arg. TOOWTDI.
-
-* perform setup finalization before reporting failures
-
-* apply modified patches from Andreas Kloeckner to allow
-  test functions to have no func_code (#22) and to make
-  "-k" and function keywords work  (#20)
-
-* apply patch from Daniel Peolzleithner (issue #23)
-
-* resolve issue #18, multiprocessing.Manager() and
-  redirection clash
-
-* make __name__ == "__channelexec__" for remote_exec code
-
-Changes between 1.0.0b1 and 1.0.0b3
-=============================================
-
-* plugin classes are removed: one now defines
-  hooks directly in conftest.py or global pytest_*.py
-  files.
-
-* added new pytest_namespace(config) hook that allows
-  to inject helpers directly to the py.test.* namespace.
-
-* documented and refined many hooks
-
-* added new style of generative tests via
-  pytest_generate_tests hook that integrates
-  well with function arguments.
-
-
-Changes between 0.9.2 and 1.0.0b1
-=============================================
-
-* introduced new "funcarg" setup method,
-  see doc/test/funcarg.txt
-
-* introduced plugin architecuture and many
-  new py.test plugins, see
-  doc/test/plugins.txt
-
-* teardown_method is now guaranteed to get
-  called after a test method has run.
-
-* new method: py.test.importorskip(mod,minversion)
-  will either import or call py.test.skip()
-
-* completely revised internal py.test architecture
-
-* new py.process.ForkedFunc object allowing to
-  fork execution of a function to a sub process
-  and getting a result back.
-
-XXX lots of things missing here XXX
-
-Changes between 0.9.1 and 0.9.2
-===============================
-
-* refined installation and metadata, created new setup.py,
-  now based on setuptools/ez_setup (thanks to Ralf Schmitt
-  for his support).
-
-* improved the way of making py.* scripts available in
-  windows environments, they are now added to the
-  Scripts directory as ".cmd" files.
-
-* py.path.svnwc.status() now is more complete and
-  uses xml output from the 'svn' command if available
-  (Guido Wesdorp)
-
-* fix for py.path.svn* to work with svn 1.5
-  (Chris Lamb)
-
-* fix path.relto(otherpath) method on windows to
-  use normcase for checking if a path is relative.
-
-* py.test's traceback is better parseable from editors
-  (follows the filenames:LINENO: MSG convention)
-  (thanks to Osmo Salomaa)
-
-* fix to javascript-generation, "py.test --runbrowser"
-  should work more reliably now
-
-* removed previously accidentally added
-  py.test.broken and py.test.notimplemented helpers.
-
-* there now is a py.__version__ attribute
-
-Changes between 0.9.0 and 0.9.1
-===============================
-
-This is a fairly complete list of changes between 0.9 and 0.9.1, which can
-serve as a reference for developers.
-
-* allowing + signs in py.path.svn urls [39106]
-* fixed support for Failed exceptions without excinfo in py.test [39340]
-* added support for killing processes for Windows (as well as platforms that
-  support os.kill) in py.misc.killproc [39655]
-* added setup/teardown for generative tests to py.test [40702]
-* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739]
-* fixed problem with calling .remove() on wcpaths of non-versioned files in
-  py.path [44248]
-* fixed some import and inheritance issues in py.test [41480, 44648, 44655]
-* fail to run greenlet tests when pypy is available, but without stackless
-  [45294]
-* small fixes in rsession tests [45295]
-* fixed issue with 2.5 type representations in py.test [45483, 45484]
-* made that internal reporting issues displaying is done atomically in py.test
-  [45518]
-* made that non-existing files are igored by the py.lookup script [45519]
-* improved exception name creation in py.test [45535]
-* made that less threads are used in execnet [merge in 45539]
-* removed lock required for atomical reporting issue displaying in py.test
-  [45545]
-* removed globals from execnet [45541, 45547]
-* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit
-  get called in 2.5 (py.execnet) [45548]
-* fixed bug in joining threads in py.execnet's servemain [45549]
-* refactored py.test.rsession tests to not rely on exact output format anymore
-  [45646]
-* using repr() on test outcome [45647]
-* added 'Reason' classes for py.test.skip() [45648, 45649]
-* killed some unnecessary sanity check in py.test.collect [45655]
-* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only
-  usable by Administrators [45901]
-* added support for locking and non-recursive commits to py.path.svnwc [45994]
-* locking files in py.execnet to prevent CPython from segfaulting [46010]
-* added export() method to py.path.svnurl
-* fixed -d -x in py.test [47277]
-* fixed argument concatenation problem in py.path.svnwc [49423]
-* restore py.test behaviour that it exits with code 1 when there are failures
-  [49974]
-* don't fail on html files that don't have an accompanying .txt file [50606]
-* fixed 'utestconvert.py < input' [50645]
-* small fix for code indentation in py.code.source [50755]
-* fix _docgen.py documentation building [51285]
-* improved checks for source representation of code blocks in py.test [51292]
-* added support for passing authentication to py.path.svn* objects [52000,
-  52001]
-* removed sorted() call for py.apigen tests in favour of [].sort() to support
-  Python 2.3 [52481]
diff --git a/tools/py/MANIFEST.in b/tools/py/MANIFEST.in
deleted file mode 100644
index 31fb010..0000000
--- a/tools/py/MANIFEST.in
+++ /dev/null
@@ -1,9 +0,0 @@
-include CHANGELOG
-include AUTHORS
-include README.txt
-include setup.py
-include LICENSE
-include conftest.py
-include tox.ini
-graft doc
-graft testing
diff --git a/tools/py/README.txt b/tools/py/README.txt
deleted file mode 100644
index e327e93..0000000
--- a/tools/py/README.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-.. image:: https://drone.io/bitbucket.org/pytest-dev/py/status.png

-   :target: https://drone.io/bitbucket.org/pytest-dev/py/latest

-.. image:: https://pypip.in/v/py/badge.png

-   :target: https://pypi.python.org/pypi/py

-

-The py lib is a Python development support library featuring

-the following tools and modules:

-

-* py.path:  uniform local and svn path objects

-* py.apipkg:  explicit API control and lazy-importing

-* py.iniconfig:  easy parsing of .ini files

-* py.code: dynamic code generation and introspection

-

-NOTE: prior to the 1.4 release this distribution used to

-contain py.test which is now its own package, see http://pytest.org

-

-For questions and more information please visit http://pylib.readthedocs.org

-

-Bugs and issues: http://bitbucket.org/pytest-dev/py/issues/

-

-Authors: Holger Krekel and others, 2004-2015

diff --git a/tools/py/conftest.py b/tools/py/conftest.py
deleted file mode 100644
index 11c2d44..0000000
--- a/tools/py/conftest.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import py
-import sys
-
-pytest_plugins = 'doctest pytester'.split()
-
-collect_ignore = ['build', 'doc/_build']
-
-
-import os, py
-pid = os.getpid()
-
-def pytest_addoption(parser):
-    group = parser.getgroup("pylib", "py lib testing options")
-    group.addoption('--runslowtests',
-           action="store_true", dest="runslowtests", default=False,
-           help=("run slow tests"))
-
-def pytest_funcarg__sshhost(request):
-    val = request.config.getvalue("sshhost")
-    if val:
-        return val
-    py.test.skip("need --sshhost option")
-def pytest_generate_tests(metafunc):
-    multi = getattr(metafunc.function, 'multi', None)
-    if multi is not None:
-        assert len(multi.kwargs) == 1
-        for name, l in multi.kwargs.items():
-            for val in l:
-                metafunc.addcall(funcargs={name: val})
-    elif 'anypython' in metafunc.funcargnames:
-        for name in ('python2.4', 'python2.5', 'python2.6',
-                     'python2.7', 'python3.1', 'pypy-c', 'jython'):
-            metafunc.addcall(id=name, param=name)
-
-# XXX copied from execnet's conftest.py - needs to be merged
-winpymap = {
-    'python2.7': r'C:\Python27\python.exe',
-    'python2.6': r'C:\Python26\python.exe',
-    'python2.5': r'C:\Python25\python.exe',
-    'python2.4': r'C:\Python24\python.exe',
-    'python3.1': r'C:\Python31\python.exe',
-}
-
-def getexecutable(name, cache={}):
-    try:
-        return cache[name]
-    except KeyError:
-        executable = py.path.local.sysfind(name)
-        if executable:
-            if name == "jython":
-                import subprocess
-                popen = subprocess.Popen([str(executable), "--version"],
-                    universal_newlines=True, stderr=subprocess.PIPE)
-                out, err = popen.communicate()
-                if not err or "2.5" not in err:
-                    executable = None
-        cache[name] = executable
-        return executable
-
-def pytest_funcarg__anypython(request):
-    name = request.param
-    executable = getexecutable(name)
-    if executable is None:
-        if sys.platform == "win32":
-            executable = winpymap.get(name, None)
-            if executable:
-                executable = py.path.local(executable)
-                if executable.check():
-                    return executable
-        py.test.skip("no %s found" % (name,))
-    return executable
diff --git a/tools/py/doc/index.txt b/tools/py/doc/index.txt
deleted file mode 100644
index 7eb5c63..0000000
--- a/tools/py/doc/index.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-.. py documentation master file, created by
-   sphinx-quickstart on Thu Oct 21 08:30:10 2010.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
-
-Welcome to py's documentation!
-=================================
-
-see :ref:`CHANGELOG <changelog>` for latest changes.
-
-.. note::
-
-   Since version 1.4, the testing tool "py.test" is part of its own `pytest distribution`_.
-
-.. _`pytest distribution`: http://pytest.org
-
-Contents:
-
-.. toctree::
-
-   install
-   path
-   code
-   io
-   log
-   xml
-   misc
-
-   :maxdepth: 2
-
-.. toctree::
-    :hidden:
-
-    announce/release-2.0.0
-    changelog
-    announce/*
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`search`
-
diff --git a/tools/py/doc/install.txt b/tools/py/doc/install.txt
deleted file mode 100644
index d0e981d..0000000
--- a/tools/py/doc/install.txt
+++ /dev/null
@@ -1,88 +0,0 @@
-
-.. _`py`:
-.. _`index page`: http://pypi.python.org/pypi/py/
-
-installation info in a nutshell
-===================================================
-
-**PyPI name**: py_
-
-**Pythons**: CPython 2.6, 2.7, 3.3, 3.4, PyPy-2.3
-
-**Operating systems**: Linux, Windows, OSX, Unix
-
-**Requirements**: setuptools_ or Distribute_
-
-**Installers**: ``easy_install`` and ``pip``
-
-**hg repository**: https://bitbucket.org/hpk42/py
-
-easy install or pip ``py``
------------------------------
-
-Both `Distribute`_ and setuptools_ provide the ``easy_install``
-installation tool with which you can type into a command line window::
-
-    easy_install -U py
-
-to install the latest release of the py lib.  The ``-U`` switch
-will trigger an upgrade if you already have an older version installed.
-
-.. note::
-
-   As of version 1.4 py does not contain py.test anymore - you
-   need to install the new `pytest`_ distribution.
-
-.. _pytest: http://pytest.org
-
-Working from version control or a tarball
------------------------------------------------
-
-To follow development or start experiments, checkout the
-complete code and documentation source with mercurial_::
-
-    hg clone https://bitbucket.org/hpk42/py
-
-Development takes place on the 'trunk' branch.
-
-You can also go to the python package index and
-download and unpack a TAR file::
-
-    http://pypi.python.org/pypi/py/
-
-activating a checkout with setuptools
---------------------------------------------
-
-With a working `Distribute`_ or setuptools_ installation you can type::
-
-    python setup.py develop
-
-in order to work inline with the tools and the lib of your checkout.
-
-.. _`no-setuptools`:
-
-.. _`directly use a checkout`:
-
-.. _`setuptools`: http://pypi.python.org/pypi/setuptools
-
-
-Mailing list and issue tracker
---------------------------------------
-
-- `py-dev developers list`_ and `commit mailing list`_.
-
-- #pylib on irc.freenode.net IRC channel for random questions.
-
-- `bitbucket issue tracker`_ use this bitbucket issue tracker to report
-  bugs or request features.
-
-.. _`bitbucket issue tracker`: http://bitbucket.org/hpk42/py/issues/
-
-.. _codespeak: http://codespeak.net/
-.. _`py-dev`:
-.. _`development mailing list`:
-.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev
-.. _`py-svn`:
-.. _`commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn
-
-.. include:: links.inc
diff --git a/tools/py/doc/misc.txt b/tools/py/doc/misc.txt
deleted file mode 100644
index 8c3c0b3..0000000
--- a/tools/py/doc/misc.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-====================================
-Miscellaneous features of the py lib
-====================================
-
-Mapping the standard python library into py
-===========================================
-
-The ``py.std`` object allows lazy access to
-standard library modules.  For example, to get to the print-exception
-functionality of the standard library you can write::
-
-    py.std.traceback.print_exc()
-
-without having to do anything else than the usual ``import py``
-at the beginning.  You can access any other top-level standard
-library module this way.  This means that you will only trigger
-imports of modules that are actually needed.  Note that no attempt
-is made to import submodules.
-
-Support for interaction with system utilities/binaries
-======================================================
-
-Currently, the py lib offers two ways to interact with
-system executables. ``py.process.cmdexec()`` invokes
-the shell in order to execute a string.  The other
-one, ``py.path.local``'s 'sysexec()' method lets you
-directly execute a binary.
-
-Both approaches will raise an exception in case of a return-
-code other than 0 and otherwise return the stdout-output
-of the child process.
-
-The shell based approach
-------------------------
-
-You can execute a command via your system shell
-by doing something like::
-
-    out = py.process.cmdexec('ls -v')
-
-However, the ``cmdexec`` approach has a few shortcomings:
-
-- it relies on the underlying system shell
-- it neccessitates shell-escaping for expressing arguments
-- it does not easily allow to "fix" the binary you want to run.
-- it only allows to execute executables from the local
-  filesystem
-
-.. _sysexec:
-
-local paths have ``sysexec``
-----------------------------
-
-In order to synchronously execute an executable file you
-can use ``sysexec``::
-
-    binsvn.sysexec('ls', 'http://codespeak.net/svn')
-
-where ``binsvn`` is a path that points to the ``svn`` commandline
-binary. Note that this function does not offer any shell-escaping
-so you have to pass in already separated arguments.
-
-finding an executable local path
---------------------------------
-
-Finding an executable is quite different on multiple platforms.
-Currently, the ``PATH`` environment variable based search on
-unix platforms is supported::
-
-    py.path.local.sysfind('svn')
-
-which returns the first path whose ``basename`` matches ``svn``.
-In principle, `sysfind` deploys platform specific algorithms
-to perform the search.  On Windows, for example, it may look
-at the registry (XXX).
-
-To make the story complete, we allow to pass in a second ``checker``
-argument that is called for each found executable.  For example, if
-you have multiple binaries available you may want to select the
-right version::
-
-    def mysvn(p):
-        """ check that the given svn binary has version 1.1. """
-        line = p.execute('--version'').readlines()[0]
-        if line.find('version 1.1'):
-            return p
-    binsvn = py.path.local.sysfind('svn', checker=mysvn)
-
-
-Cross-Python Version compatibility helpers
-=============================================
-
-The ``py.builtin`` namespace provides a number of helpers that help to write python code compatible across Python interpreters, mainly Python2 and Python3.  Type ``help(py.builtin)`` on a Python prompt for a the selection of builtins.
diff --git a/tools/py/doc/path.txt b/tools/py/doc/path.txt
deleted file mode 100644
index 837c1d1..0000000
--- a/tools/py/doc/path.txt
+++ /dev/null
@@ -1,260 +0,0 @@
-=======
-py.path
-=======
-
-The 'py' lib provides a uniform high-level api to deal with filesystems
-and filesystem-like interfaces: ``py.path``.  It aims to offer a central
-object to fs-like object trees (reading from and writing to files, adding
-files/directories, examining the types and structure, etc.), and out-of-the-box
-provides a number of implementations of this API.
-
-py.path.local - local file system path
-===============================================
-
-.. _`local`:
-
-basic interactive example
--------------------------------------
-
-The first and most obvious of the implementations is a wrapper around a local
-filesystem. It's just a bit nicer in usage than the regular Python APIs, and
-of course all the functionality is bundled together rather than spread over a
-number of modules.
-
-Example usage, here we use the ``py.test.ensuretemp()`` function to create
-a ``py.path.local`` object for us (which wraps a directory):
-
-.. sourcecode:: pycon
-
-  >>> import py
-  >>> temppath = py.test.ensuretemp('py.path_documentation')
-  >>> foopath = temppath.join('foo') # get child 'foo' (lazily)
-  >>> foopath.check() # check if child 'foo' exists
-  False
-  >>> foopath.write('bar') # write some data to it
-  >>> foopath.check()
-  True
-  >>> foopath.read()
-  'bar'
-  >>> foofile = foopath.open() # return a 'real' file object
-  >>> foofile.read(1)
-  'b'
-
-reference documentation
----------------------------------
-
-.. autoclass:: py._path.local.LocalPath
-    :members:
-    :inherited-members:
-
-``py.path.svnurl`` and ``py.path.svnwc``
-==================================================
-
-Two other ``py.path`` implementations that the py lib provides wrap the
-popular `Subversion`_ revision control system: the first (called 'svnurl')
-by interfacing with a remote server, the second by wrapping a local checkout.
-Both allow you to access relatively advanced features such as metadata and
-versioning, and both in a way more user-friendly manner than existing other
-solutions.
-
-Some example usage of ``py.path.svnurl``:
-
-.. sourcecode:: pycon
-
-  .. >>> import py
-  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
-  >>> url = py.path.svnurl('http://codespeak.net/svn/py')
-  >>> info = url.info()
-  >>> info.kind
-  'dir'
-  >>> firstentry = url.log()[-1]
-  >>> import time
-  >>> time.strftime('%Y-%m-%d', time.gmtime(firstentry.date))
-  '2004-10-02'
-
-Example usage of ``py.path.svnwc``:
-
-.. sourcecode:: pycon
-
-  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
-  >>> temp = py.test.ensuretemp('py.path_documentation')
-  >>> wc = py.path.svnwc(temp.join('svnwc'))
-  >>> wc.checkout('http://codespeak.net/svn/py/dist/py/path/local')
-  >>> wc.join('local.py').check()
-  True
-
-.. _`Subversion`: http://subversion.tigris.org/
-
-svn path related API reference
------------------------------------------
-
-.. autoclass:: py._path.svnwc.SvnWCCommandPath
-    :members:
-    :inherited-members:
-
-.. autoclass:: py._path.svnurl.SvnCommandPath
-    :members:
-    :inherited-members:
-
-.. autoclass:: py._path.svnwc.SvnAuth
-    :members:
-    :inherited-members:
-
-Common vs. specific API, Examples
-========================================
-
-All Path objects support a common set of operations, suitable
-for many use cases and allowing to transparently switch the
-path object within an application (e.g. from "local" to "svnwc").
-The common set includes functions such as `path.read()` to read all data
-from a file, `path.write()` to write data, `path.listdir()` to get a list
-of directory entries, `path.check()` to check if a node exists
-and is of a particular type, `path.join()` to get
-to a (grand)child, `path.visit()` to recursively walk through a node's
-children, etc.  Only things that are not common on 'normal' filesystems (yet),
-such as handling metadata (e.g. the Subversion "properties") require
-using specific APIs.
-
-A quick 'cookbook' of small examples that will be useful 'in real life',
-which also presents parts of the 'common' API, and shows some non-common
-methods:
-
-Searching `.txt` files
---------------------------------
-
-Search for a particular string inside all files with a .txt extension in a
-specific directory.
-
-.. sourcecode:: pycon
-
-  >>> dirpath = temppath.ensure('testdir', dir=True)
-  >>> dirpath.join('textfile1.txt').write('foo bar baz')
-  >>> dirpath.join('textfile2.txt').write('frob bar spam eggs')
-  >>> subdir = dirpath.ensure('subdir', dir=True)
-  >>> subdir.join('textfile1.txt').write('foo baz')
-  >>> subdir.join('textfile2.txt').write('spam eggs spam foo bar spam')
-  >>> results = []
-  >>> for fpath in dirpath.visit('*.txt'):
-  ...     if 'bar' in fpath.read():
-  ...         results.append(fpath.basename)
-  >>> results.sort()
-  >>> results
-  ['textfile1.txt', 'textfile2.txt', 'textfile2.txt']
-
-Working with Paths
-----------------------------
-
-This example shows the ``py.path`` features to deal with
-filesystem paths Note that the filesystem is never touched,
-all operations are performed on a string level (so the paths
-don't have to exist, either):
-
-.. sourcecode:: pycon
-
-  >>> p1 = py.path.local('/foo/bar')
-  >>> p2 = p1.join('baz/qux')
-  >>> p2 == py.path.local('/foo/bar/baz/qux')
-  True
-  >>> sep = py.path.local.sep
-  >>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string
-  'baz/qux'
-  >>> p2.bestrelpath(p1).replace(sep, '/')
-  '../..'
-  >>> p2.join(p2.bestrelpath(p1)) == p1
-  True
-  >>> p3 = p1 / 'baz/qux' # the / operator allows joining, too
-  >>> p2 == p3
-  True
-  >>> p4 = p1 + ".py"
-  >>> p4.basename == "bar.py"
-  True
-  >>> p4.ext == ".py"
-  True
-  >>> p4.purebasename == "bar"
-  True
-
-This should be possible on every implementation of ``py.path``, so
-regardless of whether the implementation wraps a UNIX filesystem, a Windows
-one, or a database or object tree, these functions should be available (each
-with their own notion of path seperators and dealing with conversions, etc.).
-
-Checking path types
--------------------------------
-
-Now we will show a bit about the powerful 'check()' method on paths, which
-allows you to check whether a file exists, what type it is, etc.:
-
-.. sourcecode:: pycon
-
-  >>> file1 = temppath.join('file1')
-  >>> file1.check() # does it exist?
-  False
-  >>> file1 = file1.ensure(file=True) # 'touch' the file
-  >>> file1.check()
-  True
-  >>> file1.check(dir=True) # is it a dir?
-  False
-  >>> file1.check(file=True) # or a file?
-  True
-  >>> file1.check(ext='.txt') # check the extension
-  False
-  >>> textfile = temppath.ensure('text.txt', file=True)
-  >>> textfile.check(ext='.txt')
-  True
-  >>> file1.check(basename='file1') # we can use all the path's properties here
-  True
-
-Setting svn-properties
---------------------------------
-
-As an example of 'uncommon' methods, we'll show how to read and write
-properties in an ``py.path.svnwc`` instance:
-
-.. sourcecode:: pycon
-
-  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
-  >>> wc.propget('foo')
-  ''
-  >>> wc.propset('foo', 'bar')
-  >>> wc.propget('foo')
-  'bar'
-  >>> len(wc.status().prop_modified) # our own props
-  1
-  >>> msg = wc.revert() # roll back our changes
-  >>> len(wc.status().prop_modified)
-  0
-
-SVN authentication
-----------------------------
-
-Some uncommon functionality can also be provided as extensions, such as SVN
-authentication:
-
-.. sourcecode:: pycon
-
-  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
-  >>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False,
-  ...             interactive=False)
-  >>> wc.auth = auth
-  >>> wc.update() # this should work
-  >>> path = wc.ensure('thisshouldnotexist.txt')
-  >>> try:
-  ...     path.commit('testing')
-  ... except py.process.cmdexec.Error, e:
-  ...     pass
-  >>> 'authorization failed' in str(e)
-  True
-
-Known problems / limitations
-===================================
-
-* The SVN path objects require the "svn" command line,
-  there is currently no support for python bindings.
-  Parsing the svn output can lead to problems, particularly
-  regarding if you have a non-english "locales" setting.
-
-* While the path objects basically work on windows,
-  there is no attention yet on making unicode paths
-  work or deal with the famous "8.3" filename issues.
-
-
diff --git a/tools/py/py/__init__.py b/tools/py/py/__init__.py
deleted file mode 100644
index bdb9aa2..0000000
--- a/tools/py/py/__init__.py
+++ /dev/null
@@ -1,150 +0,0 @@
-"""
-py.test and pylib: rapid testing and development utils
-
-this module uses apipkg.py for lazy-loading sub modules
-and classes.  The initpkg-dictionary  below specifies
-name->value mappings where value can be another namespace
-dictionary or an import path.
-
-(c) Holger Krekel and others, 2004-2014
-"""
-__version__ = '1.4.31'
-
-from py import _apipkg
-
-# so that py.error.* instances are picklable
-import sys
-sys.modules['py.error'] = _apipkg.AliasModule("py.error", "py._error", 'error')
-
-_apipkg.initpkg(__name__, attr={'_apipkg': _apipkg}, exportdefs={
-    # access to all standard lib modules
-    'std': '._std:std',
-    # access to all posix errno's as classes
-    'error': '._error:error',
-
-    '_pydir' : '.__metainfo:pydir',
-    'version': 'py:__version__', # backward compatibility
-
-    # pytest-2.0 has a flat namespace, we use alias modules
-    # to keep old references compatible
-    'test' : 'pytest',
-    'test.collect' : 'pytest',
-    'test.cmdline' : 'pytest',
-
-    # hook into the top-level standard library
-    'process' : {
-        '__doc__'        : '._process:__doc__',
-        'cmdexec'        : '._process.cmdexec:cmdexec',
-        'kill'           : '._process.killproc:kill',
-        'ForkedFunc'     : '._process.forkedfunc:ForkedFunc',
-    },
-
-    'apipkg' : {
-        'initpkg'   : '._apipkg:initpkg',
-        'ApiModule' : '._apipkg:ApiModule',
-    },
-
-    'iniconfig' : {
-        'IniConfig'      : '._iniconfig:IniConfig',
-        'ParseError'     : '._iniconfig:ParseError',
-    },
-
-    'path' : {
-        '__doc__'        : '._path:__doc__',
-        'svnwc'          : '._path.svnwc:SvnWCCommandPath',
-        'svnurl'         : '._path.svnurl:SvnCommandPath',
-        'local'          : '._path.local:LocalPath',
-        'SvnAuth'        : '._path.svnwc:SvnAuth',
-    },
-
-    # python inspection/code-generation API
-    'code' : {
-        '__doc__'           : '._code:__doc__',
-        'compile'           : '._code.source:compile_',
-        'Source'            : '._code.source:Source',
-        'Code'              : '._code.code:Code',
-        'Frame'             : '._code.code:Frame',
-        'ExceptionInfo'     : '._code.code:ExceptionInfo',
-        'Traceback'         : '._code.code:Traceback',
-        'getfslineno'       : '._code.source:getfslineno',
-        'getrawcode'        : '._code.code:getrawcode',
-        'patch_builtins'    : '._code.code:patch_builtins',
-        'unpatch_builtins'  : '._code.code:unpatch_builtins',
-        '_AssertionError'   : '._code.assertion:AssertionError',
-        '_reinterpret_old'  : '._code.assertion:reinterpret_old',
-        '_reinterpret'      : '._code.assertion:reinterpret',
-        '_reprcompare'      : '._code.assertion:_reprcompare',
-        '_format_explanation' : '._code.assertion:_format_explanation',
-    },
-
-    # backports and additions of builtins
-    'builtin' : {
-        '__doc__'        : '._builtin:__doc__',
-        'enumerate'      : '._builtin:enumerate',
-        'reversed'       : '._builtin:reversed',
-        'sorted'         : '._builtin:sorted',
-        'any'            : '._builtin:any',
-        'all'            : '._builtin:all',
-        'set'            : '._builtin:set',
-        'frozenset'      : '._builtin:frozenset',
-        'BaseException'  : '._builtin:BaseException',
-        'GeneratorExit'  : '._builtin:GeneratorExit',
-        '_sysex'         : '._builtin:_sysex',
-        'print_'         : '._builtin:print_',
-        '_reraise'       : '._builtin:_reraise',
-        '_tryimport'     : '._builtin:_tryimport',
-        'exec_'          : '._builtin:exec_',
-        '_basestring'    : '._builtin:_basestring',
-        '_totext'        : '._builtin:_totext',
-        '_isbytes'       : '._builtin:_isbytes',
-        '_istext'        : '._builtin:_istext',
-        '_getimself'     : '._builtin:_getimself',
-        '_getfuncdict'   : '._builtin:_getfuncdict',
-        '_getcode'       : '._builtin:_getcode',
-        'builtins'       : '._builtin:builtins',
-        'execfile'       : '._builtin:execfile',
-        'callable'       : '._builtin:callable',
-        'bytes'       : '._builtin:bytes',
-        'text'       : '._builtin:text',
-    },
-
-    # input-output helping
-    'io' : {
-        '__doc__'             : '._io:__doc__',
-        'dupfile'             : '._io.capture:dupfile',
-        'TextIO'              : '._io.capture:TextIO',
-        'BytesIO'             : '._io.capture:BytesIO',
-        'FDCapture'           : '._io.capture:FDCapture',
-        'StdCapture'          : '._io.capture:StdCapture',
-        'StdCaptureFD'        : '._io.capture:StdCaptureFD',
-        'TerminalWriter'      : '._io.terminalwriter:TerminalWriter',
-        'ansi_print'          : '._io.terminalwriter:ansi_print',
-        'get_terminal_width'  : '._io.terminalwriter:get_terminal_width',
-        'saferepr'            : '._io.saferepr:saferepr',
-    },
-
-    # small and mean xml/html generation
-    'xml' : {
-        '__doc__'            : '._xmlgen:__doc__',
-        'html'               : '._xmlgen:html',
-        'Tag'                : '._xmlgen:Tag',
-        'raw'                : '._xmlgen:raw',
-        'Namespace'          : '._xmlgen:Namespace',
-        'escape'             : '._xmlgen:escape',
-    },
-
-    'log' : {
-        # logging API ('producers' and 'consumers' connected via keywords)
-        '__doc__'            : '._log:__doc__',
-        '_apiwarn'           : '._log.warning:_apiwarn',
-        'Producer'           : '._log.log:Producer',
-        'setconsumer'        : '._log.log:setconsumer',
-        '_setstate'          : '._log.log:setstate',
-        '_getstate'          : '._log.log:getstate',
-        'Path'               : '._log.log:Path',
-        'STDOUT'             : '._log.log:STDOUT',
-        'STDERR'             : '._log.log:STDERR',
-        'Syslog'             : '._log.log:Syslog',
-    },
-
-})
diff --git a/tools/py/py/_apipkg.py b/tools/py/py/_apipkg.py
deleted file mode 100644
index a73b8f6..0000000
--- a/tools/py/py/_apipkg.py
+++ /dev/null
@@ -1,181 +0,0 @@
-"""
-apipkg: control the exported namespace of a python package.
-
-see http://pypi.python.org/pypi/apipkg
-
-(c) holger krekel, 2009 - MIT license
-"""
-import os
-import sys
-from types import ModuleType
-
-__version__ = '1.3.dev'
-
-def _py_abspath(path):
-    """
-    special version of abspath
-    that will leave paths from jython jars alone
-    """
-    if path.startswith('__pyclasspath__'):
-
-        return path
-    else:
-        return os.path.abspath(path)
-
-def initpkg(pkgname, exportdefs, attr=dict()):
-    """ initialize given package from the export definitions. """
-    oldmod = sys.modules.get(pkgname)
-    d = {}
-    f = getattr(oldmod, '__file__', None)
-    if f:
-        f = _py_abspath(f)
-    d['__file__'] = f
-    if hasattr(oldmod, '__version__'):
-        d['__version__'] = oldmod.__version__
-    if hasattr(oldmod, '__loader__'):
-        d['__loader__'] = oldmod.__loader__
-    if hasattr(oldmod, '__path__'):
-        d['__path__'] = [_py_abspath(p) for p in oldmod.__path__]
-    if '__doc__' not in exportdefs and getattr(oldmod, '__doc__', None):
-        d['__doc__'] = oldmod.__doc__
-    d.update(attr)
-    if hasattr(oldmod, "__dict__"):
-        oldmod.__dict__.update(d)
-    mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d)
-    sys.modules[pkgname] = mod
-
-def importobj(modpath, attrname):
-    module = __import__(modpath, None, None, ['__doc__'])
-    if not attrname:
-        return module
-
-    retval = module
-    names = attrname.split(".")
-    for x in names:
-        retval = getattr(retval, x)
-    return retval
-
-class ApiModule(ModuleType):
-    def __docget(self):
-        try:
-            return self.__doc
-        except AttributeError:
-            if '__doc__' in self.__map__:
-                return self.__makeattr('__doc__')
-    def __docset(self, value):
-        self.__doc = value
-    __doc__ = property(__docget, __docset)
-
-    def __init__(self, name, importspec, implprefix=None, attr=None):
-        self.__name__ = name
-        self.__all__ = [x for x in importspec if x != '__onfirstaccess__']
-        self.__map__ = {}
-        self.__implprefix__ = implprefix or name
-        if attr:
-            for name, val in attr.items():
-                # print "setting", self.__name__, name, val
-                setattr(self, name, val)
-        for name, importspec in importspec.items():
-            if isinstance(importspec, dict):
-                subname = '%s.%s' % (self.__name__, name)
-                apimod = ApiModule(subname, importspec, implprefix)
-                sys.modules[subname] = apimod
-                setattr(self, name, apimod)
-            else:
-                parts = importspec.split(':')
-                modpath = parts.pop(0)
-                attrname = parts and parts[0] or ""
-                if modpath[0] == '.':
-                    modpath = implprefix + modpath
-
-                if not attrname:
-                    subname = '%s.%s' % (self.__name__, name)
-                    apimod = AliasModule(subname, modpath)
-                    sys.modules[subname] = apimod
-                    if '.' not in name:
-                        setattr(self, name, apimod)
-                else:
-                    self.__map__[name] = (modpath, attrname)
-
-    def __repr__(self):
-        l = []
-        if hasattr(self, '__version__'):
-            l.append("version=" + repr(self.__version__))
-        if hasattr(self, '__file__'):
-            l.append('from ' + repr(self.__file__))
-        if l:
-            return '<ApiModule %r %s>' % (self.__name__, " ".join(l))
-        return '<ApiModule %r>' % (self.__name__,)
-
-    def __makeattr(self, name):
-        """lazily compute value for name or raise AttributeError if unknown."""
-        # print "makeattr", self.__name__, name
-        target = None
-        if '__onfirstaccess__' in self.__map__:
-            target = self.__map__.pop('__onfirstaccess__')
-            importobj(*target)()
-        try:
-            modpath, attrname = self.__map__[name]
-        except KeyError:
-            if target is not None and name != '__onfirstaccess__':
-                # retry, onfirstaccess might have set attrs
-                return getattr(self, name)
-            raise AttributeError(name)
-        else:
-            result = importobj(modpath, attrname)
-            setattr(self, name, result)
-            try:
-                del self.__map__[name]
-            except KeyError:
-                pass  # in a recursive-import situation a double-del can happen
-            return result
-
-    __getattr__ = __makeattr
-
-    def __dict__(self):
-        # force all the content of the module to be loaded when __dict__ is read
-        dictdescr = ModuleType.__dict__['__dict__']
-        dict = dictdescr.__get__(self)
-        if dict is not None:
-            hasattr(self, 'some')
-            for name in self.__all__:
-                try:
-                    self.__makeattr(name)
-                except AttributeError:
-                    pass
-        return dict
-    __dict__ = property(__dict__)
-
-
-def AliasModule(modname, modpath, attrname=None):
-    mod = []
-
-    def getmod():
-        if not mod:
-            x = importobj(modpath, None)
-            if attrname is not None:
-                x = getattr(x, attrname)
-            mod.append(x)
-        return mod[0]
-
-    class AliasModule(ModuleType):
-
-        def __repr__(self):
-            x = modpath
-            if attrname:
-                x += "." + attrname
-            return '<AliasModule %r for %r>' % (modname, x)
-
-        def __getattribute__(self, name):
-            try:
-                return getattr(getmod(), name)
-            except ImportError:
-                return None
-
-        def __setattr__(self, name, value):
-            setattr(getmod(), name, value)
-
-        def __delattr__(self, name):
-            delattr(getmod(), name)
-
-    return AliasModule(str(modname))
diff --git a/tools/py/py/_code/_assertionnew.py b/tools/py/py/_code/_assertionnew.py
deleted file mode 100644
index afb1b31..0000000
--- a/tools/py/py/_code/_assertionnew.py
+++ /dev/null
@@ -1,339 +0,0 @@
-"""
-Find intermediate evalutation results in assert statements through builtin AST.
-This should replace _assertionold.py eventually.
-"""
-
-import sys
-import ast
-
-import py
-from py._code.assertion import _format_explanation, BuiltinAssertionError
-
-
-if sys.platform.startswith("java") and sys.version_info < (2, 5, 2):
-    # See http://bugs.jython.org/issue1497
-    _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
-              "ListComp", "GeneratorExp", "Yield", "Compare", "Call",
-              "Repr", "Num", "Str", "Attribute", "Subscript", "Name",
-              "List", "Tuple")
-    _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
-              "AugAssign", "Print", "For", "While", "If", "With", "Raise",
-              "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
-              "Exec", "Global", "Expr", "Pass", "Break", "Continue")
-    _expr_nodes = set(getattr(ast, name) for name in _exprs)
-    _stmt_nodes = set(getattr(ast, name) for name in _stmts)
-    def _is_ast_expr(node):
-        return node.__class__ in _expr_nodes
-    def _is_ast_stmt(node):
-        return node.__class__ in _stmt_nodes
-else:
-    def _is_ast_expr(node):
-        return isinstance(node, ast.expr)
-    def _is_ast_stmt(node):
-        return isinstance(node, ast.stmt)
-
-
-class Failure(Exception):
-    """Error found while interpreting AST."""
-
-    def __init__(self, explanation=""):
-        self.cause = sys.exc_info()
-        self.explanation = explanation
-
-
-def interpret(source, frame, should_fail=False):
-    mod = ast.parse(source)
-    visitor = DebugInterpreter(frame)
-    try:
-        visitor.visit(mod)
-    except Failure:
-        failure = sys.exc_info()[1]
-        return getfailure(failure)
-    if should_fail:
-        return ("(assertion failed, but when it was re-run for "
-                "printing intermediate values, it did not fail.  Suggestions: "
-                "compute assert expression before the assert or use --no-assert)")
-
-def run(offending_line, frame=None):
-    if frame is None:
-        frame = py.code.Frame(sys._getframe(1))
-    return interpret(offending_line, frame)
-
-def getfailure(failure):
-    explanation = _format_explanation(failure.explanation)
-    value = failure.cause[1]
-    if str(value):
-        lines = explanation.splitlines()
-        if not lines:
-            lines.append("")
-        lines[0] += " << %s" % (value,)
-        explanation = "\n".join(lines)
-    text = "%s: %s" % (failure.cause[0].__name__, explanation)
-    if text.startswith("AssertionError: assert "):
-        text = text[16:]
-    return text
-
-
-operator_map = {
-    ast.BitOr : "|",
-    ast.BitXor : "^",
-    ast.BitAnd : "&",
-    ast.LShift : "<<",
-    ast.RShift : ">>",
-    ast.Add : "+",
-    ast.Sub : "-",
-    ast.Mult : "*",
-    ast.Div : "/",
-    ast.FloorDiv : "//",
-    ast.Mod : "%",
-    ast.Eq : "==",
-    ast.NotEq : "!=",
-    ast.Lt : "<",
-    ast.LtE : "<=",
-    ast.Gt : ">",
-    ast.GtE : ">=",
-    ast.Pow : "**",
-    ast.Is : "is",
-    ast.IsNot : "is not",
-    ast.In : "in",
-    ast.NotIn : "not in"
-}
-
-unary_map = {
-    ast.Not : "not %s",
-    ast.Invert : "~%s",
-    ast.USub : "-%s",
-    ast.UAdd : "+%s"
-}
-
-
-class DebugInterpreter(ast.NodeVisitor):
-    """Interpret AST nodes to gleam useful debugging information. """
-
-    def __init__(self, frame):
-        self.frame = frame
-
-    def generic_visit(self, node):
-        # Fallback when we don't have a special implementation.
-        if _is_ast_expr(node):
-            mod = ast.Expression(node)
-            co = self._compile(mod)
-            try:
-                result = self.frame.eval(co)
-            except Exception:
-                raise Failure()
-            explanation = self.frame.repr(result)
-            return explanation, result
-        elif _is_ast_stmt(node):
-            mod = ast.Module([node])
-            co = self._compile(mod, "exec")
-            try:
-                self.frame.exec_(co)
-            except Exception:
-                raise Failure()
-            return None, None
-        else:
-            raise AssertionError("can't handle %s" %(node,))
-
-    def _compile(self, source, mode="eval"):
-        return compile(source, "<assertion interpretation>", mode)
-
-    def visit_Expr(self, expr):
-        return self.visit(expr.value)
-
-    def visit_Module(self, mod):
-        for stmt in mod.body:
-            self.visit(stmt)
-
-    def visit_Name(self, name):
-        explanation, result = self.generic_visit(name)
-        # See if the name is local.
-        source = "%r in locals() is not globals()" % (name.id,)
-        co = self._compile(source)
-        try:
-            local = self.frame.eval(co)
-        except Exception:
-            # have to assume it isn't
-            local = False
-        if not local:
-            return name.id, result
-        return explanation, result
-
-    def visit_Compare(self, comp):
-        left = comp.left
-        left_explanation, left_result = self.visit(left)
-        for op, next_op in zip(comp.ops, comp.comparators):
-            next_explanation, next_result = self.visit(next_op)
-            op_symbol = operator_map[op.__class__]
-            explanation = "%s %s %s" % (left_explanation, op_symbol,
-                                        next_explanation)
-            source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
-            co = self._compile(source)
-            try:
-                result = self.frame.eval(co, __exprinfo_left=left_result,
-                                         __exprinfo_right=next_result)
-            except Exception:
-                raise Failure(explanation)
-            try:
-                if not result:
-                    break
-            except KeyboardInterrupt:
-                raise
-            except:
-                break
-            left_explanation, left_result = next_explanation, next_result
-
-        rcomp = py.code._reprcompare
-        if rcomp:
-            res = rcomp(op_symbol, left_result, next_result)
-            if res:
-                explanation = res
-        return explanation, result
-
-    def visit_BoolOp(self, boolop):
-        is_or = isinstance(boolop.op, ast.Or)
-        explanations = []
-        for operand in boolop.values:
-            explanation, result = self.visit(operand)
-            explanations.append(explanation)
-            if result == is_or:
-                break
-        name = is_or and " or " or " and "
-        explanation = "(" + name.join(explanations) + ")"
-        return explanation, result
-
-    def visit_UnaryOp(self, unary):
-        pattern = unary_map[unary.op.__class__]
-        operand_explanation, operand_result = self.visit(unary.operand)
-        explanation = pattern % (operand_explanation,)
-        co = self._compile(pattern % ("__exprinfo_expr",))
-        try:
-            result = self.frame.eval(co, __exprinfo_expr=operand_result)
-        except Exception:
-            raise Failure(explanation)
-        return explanation, result
-
-    def visit_BinOp(self, binop):
-        left_explanation, left_result = self.visit(binop.left)
-        right_explanation, right_result = self.visit(binop.right)
-        symbol = operator_map[binop.op.__class__]
-        explanation = "(%s %s %s)" % (left_explanation, symbol,
-                                      right_explanation)
-        source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
-        co = self._compile(source)
-        try:
-            result = self.frame.eval(co, __exprinfo_left=left_result,
-                                     __exprinfo_right=right_result)
-        except Exception:
-            raise Failure(explanation)
-        return explanation, result
-
-    def visit_Call(self, call):
-        func_explanation, func = self.visit(call.func)
-        arg_explanations = []
-        ns = {"__exprinfo_func" : func}
-        arguments = []
-        for arg in call.args:
-            arg_explanation, arg_result = self.visit(arg)
-            arg_name = "__exprinfo_%s" % (len(ns),)
-            ns[arg_name] = arg_result
-            arguments.append(arg_name)
-            arg_explanations.append(arg_explanation)
-        for keyword in call.keywords:
-            arg_explanation, arg_result = self.visit(keyword.value)
-            arg_name = "__exprinfo_%s" % (len(ns),)
-            ns[arg_name] = arg_result
-            keyword_source = "%s=%%s" % (keyword.arg)
-            arguments.append(keyword_source % (arg_name,))
-            arg_explanations.append(keyword_source % (arg_explanation,))
-        if call.starargs:
-            arg_explanation, arg_result = self.visit(call.starargs)
-            arg_name = "__exprinfo_star"
-            ns[arg_name] = arg_result
-            arguments.append("*%s" % (arg_name,))
-            arg_explanations.append("*%s" % (arg_explanation,))
-        if call.kwargs:
-            arg_explanation, arg_result = self.visit(call.kwargs)
-            arg_name = "__exprinfo_kwds"
-            ns[arg_name] = arg_result
-            arguments.append("**%s" % (arg_name,))
-            arg_explanations.append("**%s" % (arg_explanation,))
-        args_explained = ", ".join(arg_explanations)
-        explanation = "%s(%s)" % (func_explanation, args_explained)
-        args = ", ".join(arguments)
-        source = "__exprinfo_func(%s)" % (args,)
-        co = self._compile(source)
-        try:
-            result = self.frame.eval(co, **ns)
-        except Exception:
-            raise Failure(explanation)
-        pattern = "%s\n{%s = %s\n}"
-        rep = self.frame.repr(result)
-        explanation = pattern % (rep, rep, explanation)
-        return explanation, result
-
-    def _is_builtin_name(self, name):
-        pattern = "%r not in globals() and %r not in locals()"
-        source = pattern % (name.id, name.id)
-        co = self._compile(source)
-        try:
-            return self.frame.eval(co)
-        except Exception:
-            return False
-
-    def visit_Attribute(self, attr):
-        if not isinstance(attr.ctx, ast.Load):
-            return self.generic_visit(attr)
-        source_explanation, source_result = self.visit(attr.value)
-        explanation = "%s.%s" % (source_explanation, attr.attr)
-        source = "__exprinfo_expr.%s" % (attr.attr,)
-        co = self._compile(source)
-        try:
-            result = self.frame.eval(co, __exprinfo_expr=source_result)
-        except Exception:
-            raise Failure(explanation)
-        explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
-                                              self.frame.repr(result),
-                                              source_explanation, attr.attr)
-        # Check if the attr is from an instance.
-        source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
-        source = source % (attr.attr,)
-        co = self._compile(source)
-        try:
-            from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
-        except Exception:
-            from_instance = True
-        if from_instance:
-            rep = self.frame.repr(result)
-            pattern = "%s\n{%s = %s\n}"
-            explanation = pattern % (rep, rep, explanation)
-        return explanation, result
-
-    def visit_Assert(self, assrt):
-        test_explanation, test_result = self.visit(assrt.test)
-        if test_explanation.startswith("False\n{False =") and \
-                test_explanation.endswith("\n"):
-            test_explanation = test_explanation[15:-2]
-        explanation = "assert %s" % (test_explanation,)
-        if not test_result:
-            try:
-                raise BuiltinAssertionError
-            except Exception:
-                raise Failure(explanation)
-        return explanation, test_result
-
-    def visit_Assign(self, assign):
-        value_explanation, value_result = self.visit(assign.value)
-        explanation = "... = %s" % (value_explanation,)
-        name = ast.Name("__exprinfo_expr", ast.Load(),
-                        lineno=assign.value.lineno,
-                        col_offset=assign.value.col_offset)
-        new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
-                                col_offset=assign.col_offset)
-        mod = ast.Module([new_assign])
-        co = self._compile(mod, "exec")
-        try:
-            self.frame.exec_(co, __exprinfo_expr=value_result)
-        except Exception:
-            raise Failure(explanation)
-        return explanation, value_result
diff --git a/tools/py/py/_code/_assertionold.py b/tools/py/py/_code/_assertionold.py
deleted file mode 100644
index 4e81fb3..0000000
--- a/tools/py/py/_code/_assertionold.py
+++ /dev/null
@@ -1,555 +0,0 @@
-import py
-import sys, inspect
-from compiler import parse, ast, pycodegen
-from py._code.assertion import BuiltinAssertionError, _format_explanation
-
-passthroughex = py.builtin._sysex
-
-class Failure:
-    def __init__(self, node):
-        self.exc, self.value, self.tb = sys.exc_info()
-        self.node = node
-
-class View(object):
-    """View base class.
-
-    If C is a subclass of View, then C(x) creates a proxy object around
-    the object x.  The actual class of the proxy is not C in general,
-    but a *subclass* of C determined by the rules below.  To avoid confusion
-    we call view class the class of the proxy (a subclass of C, so of View)
-    and object class the class of x.
-
-    Attributes and methods not found in the proxy are automatically read on x.
-    Other operations like setting attributes are performed on the proxy, as
-    determined by its view class.  The object x is available from the proxy
-    as its __obj__ attribute.
-
-    The view class selection is determined by the __view__ tuples and the
-    optional __viewkey__ method.  By default, the selected view class is the
-    most specific subclass of C whose __view__ mentions the class of x.
-    If no such subclass is found, the search proceeds with the parent
-    object classes.  For example, C(True) will first look for a subclass
-    of C with __view__ = (..., bool, ...) and only if it doesn't find any
-    look for one with __view__ = (..., int, ...), and then ..., object,...
-    If everything fails the class C itself is considered to be the default.
-
-    Alternatively, the view class selection can be driven by another aspect
-    of the object x, instead of the class of x, by overriding __viewkey__.
-    See last example at the end of this module.
-    """
-
-    _viewcache = {}
-    __view__ = ()
-
-    def __new__(rootclass, obj, *args, **kwds):
-        self = object.__new__(rootclass)
-        self.__obj__ = obj
-        self.__rootclass__ = rootclass
-        key = self.__viewkey__()
-        try:
-            self.__class__ = self._viewcache[key]
-        except KeyError:
-            self.__class__ = self._selectsubclass(key)
-        return self
-
-    def __getattr__(self, attr):
-        # attributes not found in the normal hierarchy rooted on View
-        # are looked up in the object's real class
-        return getattr(self.__obj__, attr)
-
-    def __viewkey__(self):
-        return self.__obj__.__class__
-
-    def __matchkey__(self, key, subclasses):
-        if inspect.isclass(key):
-            keys = inspect.getmro(key)
-        else:
-            keys = [key]
-        for key in keys:
-            result = [C for C in subclasses if key in C.__view__]
-            if result:
-                return result
-        return []
-
-    def _selectsubclass(self, key):
-        subclasses = list(enumsubclasses(self.__rootclass__))
-        for C in subclasses:
-            if not isinstance(C.__view__, tuple):
-                C.__view__ = (C.__view__,)
-        choices = self.__matchkey__(key, subclasses)
-        if not choices:
-            return self.__rootclass__
-        elif len(choices) == 1:
-            return choices[0]
-        else:
-            # combine the multiple choices
-            return type('?', tuple(choices), {})
-
-    def __repr__(self):
-        return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
-
-
-def enumsubclasses(cls):
-    for subcls in cls.__subclasses__():
-        for subsubclass in enumsubclasses(subcls):
-            yield subsubclass
-    yield cls
-
-
-class Interpretable(View):
-    """A parse tree node with a few extra methods."""
-    explanation = None
-
-    def is_builtin(self, frame):
-        return False
-
-    def eval(self, frame):
-        # fall-back for unknown expression nodes
-        try:
-            expr = ast.Expression(self.__obj__)
-            expr.filename = '<eval>'
-            self.__obj__.filename = '<eval>'
-            co = pycodegen.ExpressionCodeGenerator(expr).getCode()
-            result = frame.eval(co)
-        except passthroughex:
-            raise
-        except:
-            raise Failure(self)
-        self.result = result
-        self.explanation = self.explanation or frame.repr(self.result)
-
-    def run(self, frame):
-        # fall-back for unknown statement nodes
-        try:
-            expr = ast.Module(None, ast.Stmt([self.__obj__]))
-            expr.filename = '<run>'
-            co = pycodegen.ModuleCodeGenerator(expr).getCode()
-            frame.exec_(co)
-        except passthroughex:
-            raise
-        except:
-            raise Failure(self)
-
-    def nice_explanation(self):
-        return _format_explanation(self.explanation)
-
-
-class Name(Interpretable):
-    __view__ = ast.Name
-
-    def is_local(self, frame):
-        source = '%r in locals() is not globals()' % self.name
-        try:
-            return frame.is_true(frame.eval(source))
-        except passthroughex:
-            raise
-        except:
-            return False
-
-    def is_global(self, frame):
-        source = '%r in globals()' % self.name
-        try:
-            return frame.is_true(frame.eval(source))
-        except passthroughex:
-            raise
-        except:
-            return False
-
-    def is_builtin(self, frame):
-        source = '%r not in locals() and %r not in globals()' % (
-            self.name, self.name)
-        try:
-            return frame.is_true(frame.eval(source))
-        except passthroughex:
-            raise
-        except:
-            return False
-
-    def eval(self, frame):
-        super(Name, self).eval(frame)
-        if not self.is_local(frame):
-            self.explanation = self.name
-
-class Compare(Interpretable):
-    __view__ = ast.Compare
-
-    def eval(self, frame):
-        expr = Interpretable(self.expr)
-        expr.eval(frame)
-        for operation, expr2 in self.ops:
-            if hasattr(self, 'result'):
-                # shortcutting in chained expressions
-                if not frame.is_true(self.result):
-                    break
-            expr2 = Interpretable(expr2)
-            expr2.eval(frame)
-            self.explanation = "%s %s %s" % (
-                expr.explanation, operation, expr2.explanation)
-            source = "__exprinfo_left %s __exprinfo_right" % operation
-            try:
-                self.result = frame.eval(source,
-                                         __exprinfo_left=expr.result,
-                                         __exprinfo_right=expr2.result)
-            except passthroughex:
-                raise
-            except:
-                raise Failure(self)
-            expr = expr2
-
-class And(Interpretable):
-    __view__ = ast.And
-
-    def eval(self, frame):
-        explanations = []
-        for expr in self.nodes:
-            expr = Interpretable(expr)
-            expr.eval(frame)
-            explanations.append(expr.explanation)
-            self.result = expr.result
-            if not frame.is_true(expr.result):
-                break
-        self.explanation = '(' + ' and '.join(explanations) + ')'
-
-class Or(Interpretable):
-    __view__ = ast.Or
-
-    def eval(self, frame):
-        explanations = []
-        for expr in self.nodes:
-            expr = Interpretable(expr)
-            expr.eval(frame)
-            explanations.append(expr.explanation)
-            self.result = expr.result
-            if frame.is_true(expr.result):
-                break
-        self.explanation = '(' + ' or '.join(explanations) + ')'
-
-
-# == Unary operations ==
-keepalive = []
-for astclass, astpattern in {
-    ast.Not    : 'not __exprinfo_expr',
-    ast.Invert : '(~__exprinfo_expr)',
-    }.items():
-
-    class UnaryArith(Interpretable):
-        __view__ = astclass
-
-        def eval(self, frame, astpattern=astpattern):
-            expr = Interpretable(self.expr)
-            expr.eval(frame)
-            self.explanation = astpattern.replace('__exprinfo_expr',
-                                                  expr.explanation)
-            try:
-                self.result = frame.eval(astpattern,
-                                         __exprinfo_expr=expr.result)
-            except passthroughex:
-                raise
-            except:
-                raise Failure(self)
-
-    keepalive.append(UnaryArith)
-
-# == Binary operations ==
-for astclass, astpattern in {
-    ast.Add    : '(__exprinfo_left + __exprinfo_right)',
-    ast.Sub    : '(__exprinfo_left - __exprinfo_right)',
-    ast.Mul    : '(__exprinfo_left * __exprinfo_right)',
-    ast.Div    : '(__exprinfo_left / __exprinfo_right)',
-    ast.Mod    : '(__exprinfo_left % __exprinfo_right)',
-    ast.Power  : '(__exprinfo_left ** __exprinfo_right)',
-    }.items():
-
-    class BinaryArith(Interpretable):
-        __view__ = astclass
-
-        def eval(self, frame, astpattern=astpattern):
-            left = Interpretable(self.left)
-            left.eval(frame)
-            right = Interpretable(self.right)
-            right.eval(frame)
-            self.explanation = (astpattern
-                                .replace('__exprinfo_left',  left .explanation)
-                                .replace('__exprinfo_right', right.explanation))
-            try:
-                self.result = frame.eval(astpattern,
-                                         __exprinfo_left=left.result,
-                                         __exprinfo_right=right.result)
-            except passthroughex:
-                raise
-            except:
-                raise Failure(self)
-
-    keepalive.append(BinaryArith)
-
-
-class CallFunc(Interpretable):
-    __view__ = ast.CallFunc
-
-    def is_bool(self, frame):
-        source = 'isinstance(__exprinfo_value, bool)'
-        try:
-            return frame.is_true(frame.eval(source,
-                                            __exprinfo_value=self.result))
-        except passthroughex:
-            raise
-        except:
-            return False
-
-    def eval(self, frame):
-        node = Interpretable(self.node)
-        node.eval(frame)
-        explanations = []
-        vars = {'__exprinfo_fn': node.result}
-        source = '__exprinfo_fn('
-        for a in self.args:
-            if isinstance(a, ast.Keyword):
-                keyword = a.name
-                a = a.expr
-            else:
-                keyword = None
-            a = Interpretable(a)
-            a.eval(frame)
-            argname = '__exprinfo_%d' % len(vars)
-            vars[argname] = a.result
-            if keyword is None:
-                source += argname + ','
-                explanations.append(a.explanation)
-            else:
-                source += '%s=%s,' % (keyword, argname)
-                explanations.append('%s=%s' % (keyword, a.explanation))
-        if self.star_args:
-            star_args = Interpretable(self.star_args)
-            star_args.eval(frame)
-            argname = '__exprinfo_star'
-            vars[argname] = star_args.result
-            source += '*' + argname + ','
-            explanations.append('*' + star_args.explanation)
-        if self.dstar_args:
-            dstar_args = Interpretable(self.dstar_args)
-            dstar_args.eval(frame)
-            argname = '__exprinfo_kwds'
-            vars[argname] = dstar_args.result
-            source += '**' + argname + ','
-            explanations.append('**' + dstar_args.explanation)
-        self.explanation = "%s(%s)" % (
-            node.explanation, ', '.join(explanations))
-        if source.endswith(','):
-            source = source[:-1]
-        source += ')'
-        try:
-            self.result = frame.eval(source, **vars)
-        except passthroughex:
-            raise
-        except:
-            raise Failure(self)
-        if not node.is_builtin(frame) or not self.is_bool(frame):
-            r = frame.repr(self.result)
-            self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
-
-class Getattr(Interpretable):
-    __view__ = ast.Getattr
-
-    def eval(self, frame):
-        expr = Interpretable(self.expr)
-        expr.eval(frame)
-        source = '__exprinfo_expr.%s' % self.attrname
-        try:
-            self.result = frame.eval(source, __exprinfo_expr=expr.result)
-        except passthroughex:
-            raise
-        except:
-            raise Failure(self)
-        self.explanation = '%s.%s' % (expr.explanation, self.attrname)
-        # if the attribute comes from the instance, its value is interesting
-        source = ('hasattr(__exprinfo_expr, "__dict__") and '
-                  '%r in __exprinfo_expr.__dict__' % self.attrname)
-        try:
-            from_instance = frame.is_true(
-                frame.eval(source, __exprinfo_expr=expr.result))
-        except passthroughex:
-            raise
-        except:
-            from_instance = True
-        if from_instance:
-            r = frame.repr(self.result)
-            self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
-
-# == Re-interpretation of full statements ==
-
-class Assert(Interpretable):
-    __view__ = ast.Assert
-
-    def run(self, frame):
-        test = Interpretable(self.test)
-        test.eval(frame)
-        # simplify 'assert False where False = ...'
-        if (test.explanation.startswith('False\n{False = ') and
-            test.explanation.endswith('\n}')):
-            test.explanation = test.explanation[15:-2]
-        # print the result as  'assert <explanation>'
-        self.result = test.result
-        self.explanation = 'assert ' + test.explanation
-        if not frame.is_true(test.result):
-            try:
-                raise BuiltinAssertionError
-            except passthroughex:
-                raise
-            except:
-                raise Failure(self)
-
-class Assign(Interpretable):
-    __view__ = ast.Assign
-
-    def run(self, frame):
-        expr = Interpretable(self.expr)
-        expr.eval(frame)
-        self.result = expr.result
-        self.explanation = '... = ' + expr.explanation
-        # fall-back-run the rest of the assignment
-        ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
-        mod = ast.Module(None, ast.Stmt([ass]))
-        mod.filename = '<run>'
-        co = pycodegen.ModuleCodeGenerator(mod).getCode()
-        try:
-            frame.exec_(co, __exprinfo_expr=expr.result)
-        except passthroughex:
-            raise
-        except:
-            raise Failure(self)
-
-class Discard(Interpretable):
-    __view__ = ast.Discard
-
-    def run(self, frame):
-        expr = Interpretable(self.expr)
-        expr.eval(frame)
-        self.result = expr.result
-        self.explanation = expr.explanation
-
-class Stmt(Interpretable):
-    __view__ = ast.Stmt
-
-    def run(self, frame):
-        for stmt in self.nodes:
-            stmt = Interpretable(stmt)
-            stmt.run(frame)
-
-
-def report_failure(e):
-    explanation = e.node.nice_explanation()
-    if explanation:
-        explanation = ", in: " + explanation
-    else:
-        explanation = ""
-    sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
-
-def check(s, frame=None):
-    if frame is None:
-        frame = sys._getframe(1)
-        frame = py.code.Frame(frame)
-    expr = parse(s, 'eval')
-    assert isinstance(expr, ast.Expression)
-    node = Interpretable(expr.node)
-    try:
-        node.eval(frame)
-    except passthroughex:
-        raise
-    except Failure:
-        e = sys.exc_info()[1]
-        report_failure(e)
-    else:
-        if not frame.is_true(node.result):
-            sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
-
-
-###########################################################
-# API / Entry points
-# #########################################################
-
-def interpret(source, frame, should_fail=False):
-    module = Interpretable(parse(source, 'exec').node)
-    #print "got module", module
-    if isinstance(frame, py.std.types.FrameType):
-        frame = py.code.Frame(frame)
-    try:
-        module.run(frame)
-    except Failure:
-        e = sys.exc_info()[1]
-        return getfailure(e)
-    except passthroughex:
-        raise
-    except:
-        import traceback
-        traceback.print_exc()
-    if should_fail:
-        return ("(assertion failed, but when it was re-run for "
-                "printing intermediate values, it did not fail.  Suggestions: "
-                "compute assert expression before the assert or use --nomagic)")
-    else:
-        return None
-
-def getmsg(excinfo):
-    if isinstance(excinfo, tuple):
-        excinfo = py.code.ExceptionInfo(excinfo)
-    #frame, line = gettbline(tb)
-    #frame = py.code.Frame(frame)
-    #return interpret(line, frame)
-
-    tb = excinfo.traceback[-1]
-    source = str(tb.statement).strip()
-    x = interpret(source, tb.frame, should_fail=True)
-    if not isinstance(x, str):
-        raise TypeError("interpret returned non-string %r" % (x,))
-    return x
-
-def getfailure(e):
-    explanation = e.node.nice_explanation()
-    if str(e.value):
-        lines = explanation.split('\n')
-        lines[0] += "  << %s" % (e.value,)
-        explanation = '\n'.join(lines)
-    text = "%s: %s" % (e.exc.__name__, explanation)
-    if text.startswith('AssertionError: assert '):
-        text = text[16:]
-    return text
-
-def run(s, frame=None):
-    if frame is None:
-        frame = sys._getframe(1)
-        frame = py.code.Frame(frame)
-    module = Interpretable(parse(s, 'exec').node)
-    try:
-        module.run(frame)
-    except Failure:
-        e = sys.exc_info()[1]
-        report_failure(e)
-
-
-if __name__ == '__main__':
-    # example:
-    def f():
-        return 5
-    def g():
-        return 3
-    def h(x):
-        return 'never'
-    check("f() * g() == 5")
-    check("not f()")
-    check("not (f() and g() or 0)")
-    check("f() == g()")
-    i = 4
-    check("i == f()")
-    check("len(f()) == 0")
-    check("isinstance(2+3+4, float)")
-
-    run("x = i")
-    check("x == 5")
-
-    run("assert not f(), 'oops'")
-    run("a, b, c = 1, 2")
-    run("a, b, c = f()")
-
-    check("max([f(),g()]) == 4")
-    check("'hello'[g()] == 'h'")
-    run("'guk%d' % h(f())")
diff --git a/tools/py/py/_code/assertion.py b/tools/py/py/_code/assertion.py
deleted file mode 100644
index 4ce80c7..0000000
--- a/tools/py/py/_code/assertion.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import sys
-import py
-
-BuiltinAssertionError = py.builtin.builtins.AssertionError
-
-_reprcompare = None # if set, will be called by assert reinterp for comparison ops
-
-def _format_explanation(explanation):
-    """This formats an explanation
-
-    Normally all embedded newlines are escaped, however there are
-    three exceptions: \n{, \n} and \n~.  The first two are intended
-    cover nested explanations, see function and attribute explanations
-    for examples (.visit_Call(), visit_Attribute()).  The last one is
-    for when one explanation needs to span multiple lines, e.g. when
-    displaying diffs.
-    """
-    raw_lines = (explanation or '').split('\n')
-    # escape newlines not followed by {, } and ~
-    lines = [raw_lines[0]]
-    for l in raw_lines[1:]:
-        if l.startswith('{') or l.startswith('}') or l.startswith('~'):
-            lines.append(l)
-        else:
-            lines[-1] += '\\n' + l
-
-    result = lines[:1]
-    stack = [0]
-    stackcnt = [0]
-    for line in lines[1:]:
-        if line.startswith('{'):
-            if stackcnt[-1]:
-                s = 'and   '
-            else:
-                s = 'where '
-            stack.append(len(result))
-            stackcnt[-1] += 1
-            stackcnt.append(0)
-            result.append(' +' + '  '*(len(stack)-1) + s + line[1:])
-        elif line.startswith('}'):
-            assert line.startswith('}')
-            stack.pop()
-            stackcnt.pop()
-            result[stack[-1]] += line[1:]
-        else:
-            assert line.startswith('~')
-            result.append('  '*len(stack) + line[1:])
-    assert len(stack) == 1
-    return '\n'.join(result)
-
-
-class AssertionError(BuiltinAssertionError):
-    def __init__(self, *args):
-        BuiltinAssertionError.__init__(self, *args)
-        if args:
-            try:
-                self.msg = str(args[0])
-            except py.builtin._sysex:
-                raise
-            except:
-                self.msg = "<[broken __repr__] %s at %0xd>" %(
-                    args[0].__class__, id(args[0]))
-        else:
-            f = py.code.Frame(sys._getframe(1))
-            try:
-                source = f.code.fullsource
-                if source is not None:
-                    try:
-                        source = source.getstatement(f.lineno, assertion=True)
-                    except IndexError:
-                        source = None
-                    else:
-                        source = str(source.deindent()).strip()
-            except py.error.ENOENT:
-                source = None
-                # this can also occur during reinterpretation, when the
-                # co_filename is set to "<run>".
-            if source:
-                self.msg = reinterpret(source, f, should_fail=True)
-            else:
-                self.msg = "<could not determine information>"
-            if not self.args:
-                self.args = (self.msg,)
-
-if sys.version_info > (3, 0):
-    AssertionError.__module__ = "builtins"
-    reinterpret_old = "old reinterpretation not available for py3"
-else:
-    from py._code._assertionold import interpret as reinterpret_old
-if sys.version_info >= (2, 6) or (sys.platform.startswith("java")):
-    from py._code._assertionnew import interpret as reinterpret
-else:
-    reinterpret = reinterpret_old
-
diff --git a/tools/py/py/_code/code.py b/tools/py/py/_code/code.py
deleted file mode 100644
index f14c562..0000000
--- a/tools/py/py/_code/code.py
+++ /dev/null
@@ -1,787 +0,0 @@
-import py
-import sys
-from inspect import CO_VARARGS, CO_VARKEYWORDS
-
-builtin_repr = repr
-
-reprlib = py.builtin._tryimport('repr', 'reprlib')
-
-if sys.version_info[0] >= 3:
-    from traceback import format_exception_only
-else:
-    from py._code._py2traceback import format_exception_only
-
-class Code(object):
-    """ wrapper around Python code objects """
-    def __init__(self, rawcode):
-        if not hasattr(rawcode, "co_filename"):
-            rawcode = py.code.getrawcode(rawcode)
-        try:
-            self.filename = rawcode.co_filename
-            self.firstlineno = rawcode.co_firstlineno - 1
-            self.name = rawcode.co_name
-        except AttributeError:
-            raise TypeError("not a code object: %r" %(rawcode,))
-        self.raw = rawcode
-
-    def __eq__(self, other):
-        return self.raw == other.raw
-
-    def __ne__(self, other):
-        return not self == other
-
-    @property
-    def path(self):
-        """ return a path object pointing to source code (note that it
-        might not point to an actually existing file). """
-        p = py.path.local(self.raw.co_filename)
-        # maybe don't try this checking
-        if not p.check():
-            # XXX maybe try harder like the weird logic
-            # in the standard lib [linecache.updatecache] does?
-            p = self.raw.co_filename
-        return p
-
-    @property
-    def fullsource(self):
-        """ return a py.code.Source object for the full source file of the code
-        """
-        from py._code import source
-        full, _ = source.findsource(self.raw)
-        return full
-
-    def source(self):
-        """ return a py.code.Source object for the code object's source only
-        """
-        # return source only for that part of code
-        return py.code.Source(self.raw)
-
-    def getargs(self, var=False):
-        """ return a tuple with the argument names for the code object
-
-            if 'var' is set True also return the names of the variable and
-            keyword arguments when present
-        """
-        # handfull shortcut for getting args
-        raw = self.raw
-        argcount = raw.co_argcount
-        if var:
-            argcount += raw.co_flags & CO_VARARGS
-            argcount += raw.co_flags & CO_VARKEYWORDS
-        return raw.co_varnames[:argcount]
-
-class Frame(object):
-    """Wrapper around a Python frame holding f_locals and f_globals
-    in which expressions can be evaluated."""
-
-    def __init__(self, frame):
-        self.lineno = frame.f_lineno - 1
-        self.f_globals = frame.f_globals
-        self.f_locals = frame.f_locals
-        self.raw = frame
-        self.code = py.code.Code(frame.f_code)
-
-    @property
-    def statement(self):
-        """ statement this frame is at """
-        if self.code.fullsource is None:
-            return py.code.Source("")
-        return self.code.fullsource.getstatement(self.lineno)
-
-    def eval(self, code, **vars):
-        """ evaluate 'code' in the frame
-
-            'vars' are optional additional local variables
-
-            returns the result of the evaluation
-        """
-        f_locals = self.f_locals.copy()
-        f_locals.update(vars)
-        return eval(code, self.f_globals, f_locals)
-
-    def exec_(self, code, **vars):
-        """ exec 'code' in the frame
-
-            'vars' are optiona; additional local variables
-        """
-        f_locals = self.f_locals.copy()
-        f_locals.update(vars)
-        py.builtin.exec_(code, self.f_globals, f_locals )
-
-    def repr(self, object):
-        """ return a 'safe' (non-recursive, one-line) string repr for 'object'
-        """
-        return py.io.saferepr(object)
-
-    def is_true(self, object):
-        return object
-
-    def getargs(self, var=False):
-        """ return a list of tuples (name, value) for all arguments
-
-            if 'var' is set True also include the variable and keyword
-            arguments when present
-        """
-        retval = []
-        for arg in self.code.getargs(var):
-            try:
-                retval.append((arg, self.f_locals[arg]))
-            except KeyError:
-                pass     # this can occur when using Psyco
-        return retval
-
-class TracebackEntry(object):
-    """ a single entry in a traceback """
-
-    _repr_style = None
-    exprinfo = None
-
-    def __init__(self, rawentry):
-        self._rawentry = rawentry
-        self.lineno = rawentry.tb_lineno - 1
-
-    def set_repr_style(self, mode):
-        assert mode in ("short", "long")
-        self._repr_style = mode
-
-    @property
-    def frame(self):
-        return py.code.Frame(self._rawentry.tb_frame)
-
-    @property
-    def relline(self):
-        return self.lineno - self.frame.code.firstlineno
-
-    def __repr__(self):
-        return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
-
-    @property
-    def statement(self):
-        """ py.code.Source object for the current statement """
-        source = self.frame.code.fullsource
-        return source.getstatement(self.lineno)
-
-    @property
-    def path(self):
-        """ path to the source code """
-        return self.frame.code.path
-
-    def getlocals(self):
-        return self.frame.f_locals
-    locals = property(getlocals, None, None, "locals of underlaying frame")
-
-    def reinterpret(self):
-        """Reinterpret the failing statement and returns a detailed information
-           about what operations are performed."""
-        if self.exprinfo is None:
-            source = str(self.statement).strip()
-            x = py.code._reinterpret(source, self.frame, should_fail=True)
-            if not isinstance(x, str):
-                raise TypeError("interpret returned non-string %r" % (x,))
-            self.exprinfo = x
-        return self.exprinfo
-
-    def getfirstlinesource(self):
-        # on Jython this firstlineno can be -1 apparently
-        return max(self.frame.code.firstlineno, 0)
-
-    def getsource(self, astcache=None):
-        """ return failing source code. """
-        # we use the passed in astcache to not reparse asttrees
-        # within exception info printing
-        from py._code.source import getstatementrange_ast
-        source = self.frame.code.fullsource
-        if source is None:
-            return None
-        key = astnode = None
-        if astcache is not None:
-            key = self.frame.code.path
-            if key is not None:
-                astnode = astcache.get(key, None)
-        start = self.getfirstlinesource()
-        try:
-            astnode, _, end = getstatementrange_ast(self.lineno, source,
-                                                    astnode=astnode)
-        except SyntaxError:
-            end = self.lineno + 1
-        else:
-            if key is not None:
-                astcache[key] = astnode
-        return source[start:end]
-
-    source = property(getsource)
-
-    def ishidden(self):
-        """ return True if the current frame has a var __tracebackhide__
-            resolving to True
-
-            mostly for internal use
-        """
-        try:
-            return self.frame.f_locals['__tracebackhide__']
-        except KeyError:
-            try:
-                return self.frame.f_globals['__tracebackhide__']
-            except KeyError:
-                return False
-
-    def __str__(self):
-        try:
-            fn = str(self.path)
-        except py.error.Error:
-            fn = '???'
-        name = self.frame.code.name
-        try:
-            line = str(self.statement).lstrip()
-        except KeyboardInterrupt:
-            raise
-        except:
-            line = "???"
-        return "  File %r:%d in %s\n  %s\n" %(fn, self.lineno+1, name, line)
-
-    def name(self):
-        return self.frame.code.raw.co_name
-    name = property(name, None, None, "co_name of underlaying code")
-
-class Traceback(list):
-    """ Traceback objects encapsulate and offer higher level
-        access to Traceback entries.
-    """
-    Entry = TracebackEntry
-    def __init__(self, tb):
-        """ initialize from given python traceback object. """
-        if hasattr(tb, 'tb_next'):
-            def f(cur):
-                while cur is not None:
-                    yield self.Entry(cur)
-                    cur = cur.tb_next
-            list.__init__(self, f(tb))
-        else:
-            list.__init__(self, tb)
-
-    def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
-        """ return a Traceback instance wrapping part of this Traceback
-
-            by provding any combination of path, lineno and firstlineno, the
-            first frame to start the to-be-returned traceback is determined
-
-            this allows cutting the first part of a Traceback instance e.g.
-            for formatting reasons (removing some uninteresting bits that deal
-            with handling of the exception/traceback)
-        """
-        for x in self:
-            code = x.frame.code
-            codepath = code.path
-            if ((path is None or codepath == path) and
-                (excludepath is None or not hasattr(codepath, 'relto') or
-                 not codepath.relto(excludepath)) and
-                (lineno is None or x.lineno == lineno) and
-                (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
-                return Traceback(x._rawentry)
-        return self
-
-    def __getitem__(self, key):
-        val = super(Traceback, self).__getitem__(key)
-        if isinstance(key, type(slice(0))):
-            val = self.__class__(val)
-        return val
-
-    def filter(self, fn=lambda x: not x.ishidden()):
-        """ return a Traceback instance with certain items removed
-
-            fn is a function that gets a single argument, a TracebackItem
-            instance, and should return True when the item should be added
-            to the Traceback, False when not
-
-            by default this removes all the TracebackItems which are hidden
-            (see ishidden() above)
-        """
-        return Traceback(filter(fn, self))
-
-    def getcrashentry(self):
-        """ return last non-hidden traceback entry that lead
-        to the exception of a traceback.
-        """
-        for i in range(-1, -len(self)-1, -1):
-            entry = self[i]
-            if not entry.ishidden():
-                return entry
-        return self[-1]
-
-    def recursionindex(self):
-        """ return the index of the frame/TracebackItem where recursion
-            originates if appropriate, None if no recursion occurred
-        """
-        cache = {}
-        for i, entry in enumerate(self):
-            # id for the code.raw is needed to work around
-            # the strange metaprogramming in the decorator lib from pypi
-            # which generates code objects that have hash/value equality
-            #XXX needs a test
-            key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
-            #print "checking for recursion at", key
-            l = cache.setdefault(key, [])
-            if l:
-                f = entry.frame
-                loc = f.f_locals
-                for otherloc in l:
-                    if f.is_true(f.eval(co_equal,
-                        __recursioncache_locals_1=loc,
-                        __recursioncache_locals_2=otherloc)):
-                        return i
-            l.append(entry.frame.f_locals)
-        return None
-
-co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
-                   '?', 'eval')
-
-class ExceptionInfo(object):
-    """ wraps sys.exc_info() objects and offers
-        help for navigating the traceback.
-    """
-    _striptext = ''
-    def __init__(self, tup=None, exprinfo=None):
-        if tup is None:
-            tup = sys.exc_info()
-            if exprinfo is None and isinstance(tup[1], AssertionError):
-                exprinfo = getattr(tup[1], 'msg', None)
-                if exprinfo is None:
-                    exprinfo = str(tup[1])
-                if exprinfo and exprinfo.startswith('assert '):
-                    self._striptext = 'AssertionError: '
-        self._excinfo = tup
-        #: the exception class
-        self.type = tup[0]
-        #: the exception instance
-        self.value = tup[1]
-        #: the exception raw traceback
-        self.tb = tup[2]
-        #: the exception type name
-        self.typename = self.type.__name__
-        #: the exception traceback (py.code.Traceback instance)
-        self.traceback = py.code.Traceback(self.tb)
-
-    def __repr__(self):
-        return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
-
-    def exconly(self, tryshort=False):
-        """ return the exception as a string
-
-            when 'tryshort' resolves to True, and the exception is a
-            py.code._AssertionError, only the actual exception part of
-            the exception representation is returned (so 'AssertionError: ' is
-            removed from the beginning)
-        """
-        lines = format_exception_only(self.type, self.value)
-        text = ''.join(lines)
-        text = text.rstrip()
-        if tryshort:
-            if text.startswith(self._striptext):
-                text = text[len(self._striptext):]
-        return text
-
-    def errisinstance(self, exc):
-        """ return True if the exception is an instance of exc """
-        return isinstance(self.value, exc)
-
-    def _getreprcrash(self):
-        exconly = self.exconly(tryshort=True)
-        entry = self.traceback.getcrashentry()
-        path, lineno = entry.frame.code.raw.co_filename, entry.lineno
-        return ReprFileLocation(path, lineno+1, exconly)
-
-    def getrepr(self, showlocals=False, style="long",
-            abspath=False, tbfilter=True, funcargs=False):
-        """ return str()able representation of this exception info.
-            showlocals: show locals per traceback entry
-            style: long|short|no|native traceback style
-            tbfilter: hide entries (where __tracebackhide__ is true)
-
-            in case of style==native, tbfilter and showlocals is ignored.
-        """
-        if style == 'native':
-            return ReprExceptionInfo(ReprTracebackNative(
-                py.std.traceback.format_exception(
-                    self.type,
-                    self.value,
-                    self.traceback[0]._rawentry,
-                )), self._getreprcrash())
-
-        fmt = FormattedExcinfo(showlocals=showlocals, style=style,
-            abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
-        return fmt.repr_excinfo(self)
-
-    def __str__(self):
-        entry = self.traceback[-1]
-        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
-        return str(loc)
-
-    def __unicode__(self):
-        entry = self.traceback[-1]
-        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
-        return unicode(loc)
-
-
-class FormattedExcinfo(object):
-    """ presenting information about failing Functions and Generators. """
-    # for traceback entries
-    flow_marker = ">"
-    fail_marker = "E"
-
-    def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
-        self.showlocals = showlocals
-        self.style = style
-        self.tbfilter = tbfilter
-        self.funcargs = funcargs
-        self.abspath = abspath
-        self.astcache = {}
-
-    def _getindent(self, source):
-        # figure out indent for given source
-        try:
-            s = str(source.getstatement(len(source)-1))
-        except KeyboardInterrupt:
-            raise
-        except:
-            try:
-                s = str(source[-1])
-            except KeyboardInterrupt:
-                raise
-            except:
-                return 0
-        return 4 + (len(s) - len(s.lstrip()))
-
-    def _getentrysource(self, entry):
-        source = entry.getsource(self.astcache)
-        if source is not None:
-            source = source.deindent()
-        return source
-
-    def _saferepr(self, obj):
-        return py.io.saferepr(obj)
-
-    def repr_args(self, entry):
-        if self.funcargs:
-            args = []
-            for argname, argvalue in entry.frame.getargs(var=True):
-                args.append((argname, self._saferepr(argvalue)))
-            return ReprFuncArgs(args)
-
-    def get_source(self, source, line_index=-1, excinfo=None, short=False):
-        """ return formatted and marked up source lines. """
-        lines = []
-        if source is None or line_index >= len(source.lines):
-            source = py.code.Source("???")
-            line_index = 0
-        if line_index < 0:
-            line_index += len(source)
-        space_prefix = "    "
-        if short:
-            lines.append(space_prefix + source.lines[line_index].strip())
-        else:
-            for line in source.lines[:line_index]:
-                lines.append(space_prefix + line)
-            lines.append(self.flow_marker + "   " + source.lines[line_index])
-            for line in source.lines[line_index+1:]:
-                lines.append(space_prefix + line)
-        if excinfo is not None:
-            indent = 4 if short else self._getindent(source)
-            lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
-        return lines
-
-    def get_exconly(self, excinfo, indent=4, markall=False):
-        lines = []
-        indent = " " * indent
-        # get the real exception information out
-        exlines = excinfo.exconly(tryshort=True).split('\n')
-        failindent = self.fail_marker + indent[1:]
-        for line in exlines:
-            lines.append(failindent + line)
-            if not markall:
-                failindent = indent
-        return lines
-
-    def repr_locals(self, locals):
-        if self.showlocals:
-            lines = []
-            keys = [loc for loc in locals if loc[0] != "@"]
-            keys.sort()
-            for name in keys:
-                value = locals[name]
-                if name == '__builtins__':
-                    lines.append("__builtins__ = <builtins>")
-                else:
-                    # This formatting could all be handled by the
-                    # _repr() function, which is only reprlib.Repr in
-                    # disguise, so is very configurable.
-                    str_repr = self._saferepr(value)
-                    #if len(str_repr) < 70 or not isinstance(value,
-                    #                            (list, tuple, dict)):
-                    lines.append("%-10s = %s" %(name, str_repr))
-                    #else:
-                    #    self._line("%-10s =\\" % (name,))
-                    #    # XXX
-                    #    py.std.pprint.pprint(value, stream=self.excinfowriter)
-            return ReprLocals(lines)
-
-    def repr_traceback_entry(self, entry, excinfo=None):
-        source = self._getentrysource(entry)
-        if source is None:
-            source = py.code.Source("???")
-            line_index = 0
-        else:
-            # entry.getfirstlinesource() can be -1, should be 0 on jython
-            line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
-
-        lines = []
-        style = entry._repr_style
-        if style is None:
-            style = self.style
-        if style in ("short", "long"):
-            short = style == "short"
-            reprargs = self.repr_args(entry) if not short else None
-            s = self.get_source(source, line_index, excinfo, short=short)
-            lines.extend(s)
-            if short:
-                message = "in %s" %(entry.name)
-            else:
-                message = excinfo and excinfo.typename or ""
-            path = self._makepath(entry.path)
-            filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
-            localsrepr = None
-            if not short:
-                localsrepr =  self.repr_locals(entry.locals)
-            return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
-        if excinfo:
-            lines.extend(self.get_exconly(excinfo, indent=4))
-        return ReprEntry(lines, None, None, None, style)
-
-    def _makepath(self, path):
-        if not self.abspath:
-            try:
-                np = py.path.local().bestrelpath(path)
-            except OSError:
-                return path
-            if len(np) < len(str(path)):
-                path = np
-        return path
-
-    def repr_traceback(self, excinfo):
-        traceback = excinfo.traceback
-        if self.tbfilter:
-            traceback = traceback.filter()
-        recursionindex = None
-        if excinfo.errisinstance(RuntimeError):
-            if "maximum recursion depth exceeded" in str(excinfo.value):
-                recursionindex = traceback.recursionindex()
-        last = traceback[-1]
-        entries = []
-        extraline = None
-        for index, entry in enumerate(traceback):
-            einfo = (last == entry) and excinfo or None
-            reprentry = self.repr_traceback_entry(entry, einfo)
-            entries.append(reprentry)
-            if index == recursionindex:
-                extraline = "!!! Recursion detected (same locals & position)"
-                break
-        return ReprTraceback(entries, extraline, style=self.style)
-
-    def repr_excinfo(self, excinfo):
-        reprtraceback = self.repr_traceback(excinfo)
-        reprcrash = excinfo._getreprcrash()
-        return ReprExceptionInfo(reprtraceback, reprcrash)
-
-class TerminalRepr:
-    def __str__(self):
-        s = self.__unicode__()
-        if sys.version_info[0] < 3:
-            s = s.encode('utf-8')
-        return s
-
-    def __unicode__(self):
-        # FYI this is called from pytest-xdist's serialization of exception
-        # information.
-        io = py.io.TextIO()
-        tw = py.io.TerminalWriter(file=io)
-        self.toterminal(tw)
-        return io.getvalue().strip()
-
-    def __repr__(self):
-        return "<%s instance at %0x>" %(self.__class__, id(self))
-
-
-class ReprExceptionInfo(TerminalRepr):
-    def __init__(self, reprtraceback, reprcrash):
-        self.reprtraceback = reprtraceback
-        self.reprcrash = reprcrash
-        self.sections = []
-
-    def addsection(self, name, content, sep="-"):
-        self.sections.append((name, content, sep))
-
-    def toterminal(self, tw):
-        self.reprtraceback.toterminal(tw)
-        for name, content, sep in self.sections:
-            tw.sep(sep, name)
-            tw.line(content)
-
-class ReprTraceback(TerminalRepr):
-    entrysep = "_ "
-
-    def __init__(self, reprentries, extraline, style):
-        self.reprentries = reprentries
-        self.extraline = extraline
-        self.style = style
-
-    def toterminal(self, tw):
-        # the entries might have different styles
-        last_style = None
-        for i, entry in enumerate(self.reprentries):
-            if entry.style == "long":
-                tw.line("")
-            entry.toterminal(tw)
-            if i < len(self.reprentries) - 1:
-                next_entry = self.reprentries[i+1]
-                if entry.style == "long" or \
-                   entry.style == "short" and next_entry.style == "long":
-                    tw.sep(self.entrysep)
-
-        if self.extraline:
-            tw.line(self.extraline)
-
-class ReprTracebackNative(ReprTraceback):
-    def __init__(self, tblines):
-        self.style = "native"
-        self.reprentries = [ReprEntryNative(tblines)]
-        self.extraline = None
-
-class ReprEntryNative(TerminalRepr):
-    style = "native"
-
-    def __init__(self, tblines):
-        self.lines = tblines
-
-    def toterminal(self, tw):
-        tw.write("".join(self.lines))
-
-class ReprEntry(TerminalRepr):
-    localssep = "_ "
-
-    def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
-        self.lines = lines
-        self.reprfuncargs = reprfuncargs
-        self.reprlocals = reprlocals
-        self.reprfileloc = filelocrepr
-        self.style = style
-
-    def toterminal(self, tw):
-        if self.style == "short":
-            self.reprfileloc.toterminal(tw)
-            for line in self.lines:
-                red = line.startswith("E   ")
-                tw.line(line, bold=True, red=red)
-            #tw.line("")
-            return
-        if self.reprfuncargs:
-            self.reprfuncargs.toterminal(tw)
-        for line in self.lines:
-            red = line.startswith("E   ")
-            tw.line(line, bold=True, red=red)
-        if self.reprlocals:
-            #tw.sep(self.localssep, "Locals")
-            tw.line("")
-            self.reprlocals.toterminal(tw)
-        if self.reprfileloc:
-            if self.lines:
-                tw.line("")
-            self.reprfileloc.toterminal(tw)
-
-    def __str__(self):
-        return "%s\n%s\n%s" % ("\n".join(self.lines),
-                               self.reprlocals,
-                               self.reprfileloc)
-
-class ReprFileLocation(TerminalRepr):
-    def __init__(self, path, lineno, message):
-        self.path = str(path)
-        self.lineno = lineno
-        self.message = message
-
-    def toterminal(self, tw):
-        # filename and lineno output for each entry,
-        # using an output format that most editors unterstand
-        msg = self.message
-        i = msg.find("\n")
-        if i != -1:
-            msg = msg[:i]
-        tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
-
-class ReprLocals(TerminalRepr):
-    def __init__(self, lines):
-        self.lines = lines
-
-    def toterminal(self, tw):
-        for line in self.lines:
-            tw.line(line)
-
-class ReprFuncArgs(TerminalRepr):
-    def __init__(self, args):
-        self.args = args
-
-    def toterminal(self, tw):
-        if self.args:
-            linesofar = ""
-            for name, value in self.args:
-                ns = "%s = %s" %(name, value)
-                if len(ns) + len(linesofar) + 2 > tw.fullwidth:
-                    if linesofar:
-                        tw.line(linesofar)
-                    linesofar =  ns
-                else:
-                    if linesofar:
-                        linesofar += ", " + ns
-                    else:
-                        linesofar = ns
-            if linesofar:
-                tw.line(linesofar)
-            tw.line("")
-
-
-
-oldbuiltins = {}
-
-def patch_builtins(assertion=True, compile=True):
-    """ put compile and AssertionError builtins to Python's builtins. """
-    if assertion:
-        from py._code import assertion
-        l = oldbuiltins.setdefault('AssertionError', [])
-        l.append(py.builtin.builtins.AssertionError)
-        py.builtin.builtins.AssertionError = assertion.AssertionError
-    if compile:
-        l = oldbuiltins.setdefault('compile', [])
-        l.append(py.builtin.builtins.compile)
-        py.builtin.builtins.compile = py.code.compile
-
-def unpatch_builtins(assertion=True, compile=True):
-    """ remove compile and AssertionError builtins from Python builtins. """
-    if assertion:
-        py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
-    if compile:
-        py.builtin.builtins.compile = oldbuiltins['compile'].pop()
-
-def getrawcode(obj, trycall=True):
-    """ return code object for given function. """
-    try:
-        return obj.__code__
-    except AttributeError:
-        obj = getattr(obj, 'im_func', obj)
-        obj = getattr(obj, 'func_code', obj)
-        obj = getattr(obj, 'f_code', obj)
-        obj = getattr(obj, '__code__', obj)
-        if trycall and not hasattr(obj, 'co_firstlineno'):
-            if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
-                x = getrawcode(obj.__call__, trycall=False)
-                if hasattr(x, 'co_firstlineno'):
-                    return x
-        return obj
-
diff --git a/tools/py/py/_code/source.py b/tools/py/py/_code/source.py
deleted file mode 100644
index 3a648e6..0000000
--- a/tools/py/py/_code/source.py
+++ /dev/null
@@ -1,419 +0,0 @@
-from __future__ import generators
-
-from bisect import bisect_right
-import sys
-import inspect, tokenize
-import py
-from types import ModuleType
-cpy_compile = compile
-
-try:
-    import _ast
-    from _ast import PyCF_ONLY_AST as _AST_FLAG
-except ImportError:
-    _AST_FLAG = 0
-    _ast = None
-
-
-class Source(object):
-    """ a immutable object holding a source code fragment,
-        possibly deindenting it.
-    """
-    _compilecounter = 0
-    def __init__(self, *parts, **kwargs):
-        self.lines = lines = []
-        de = kwargs.get('deindent', True)
-        rstrip = kwargs.get('rstrip', True)
-        for part in parts:
-            if not part:
-                partlines = []
-            if isinstance(part, Source):
-                partlines = part.lines
-            elif isinstance(part, (tuple, list)):
-                partlines = [x.rstrip("\n") for x in part]
-            elif isinstance(part, py.builtin._basestring):
-                partlines = part.split('\n')
-                if rstrip:
-                    while partlines:
-                        if partlines[-1].strip():
-                            break
-                        partlines.pop()
-            else:
-                partlines = getsource(part, deindent=de).lines
-            if de:
-                partlines = deindent(partlines)
-            lines.extend(partlines)
-
-    def __eq__(self, other):
-        try:
-            return self.lines == other.lines
-        except AttributeError:
-            if isinstance(other, str):
-                return str(self) == other
-            return False
-
-    def __getitem__(self, key):
-        if isinstance(key, int):
-            return self.lines[key]
-        else:
-            if key.step not in (None, 1):
-                raise IndexError("cannot slice a Source with a step")
-            return self.__getslice__(key.start, key.stop)
-
-    def __len__(self):
-        return len(self.lines)
-
-    def __getslice__(self, start, end):
-        newsource = Source()
-        newsource.lines = self.lines[start:end]
-        return newsource
-
-    def strip(self):
-        """ return new source object with trailing
-            and leading blank lines removed.
-        """
-        start, end = 0, len(self)
-        while start < end and not self.lines[start].strip():
-            start += 1
-        while end > start and not self.lines[end-1].strip():
-            end -= 1
-        source = Source()
-        source.lines[:] = self.lines[start:end]
-        return source
-
-    def putaround(self, before='', after='', indent=' ' * 4):
-        """ return a copy of the source object with
-            'before' and 'after' wrapped around it.
-        """
-        before = Source(before)
-        after = Source(after)
-        newsource = Source()
-        lines = [ (indent + line) for line in self.lines]
-        newsource.lines = before.lines + lines +  after.lines
-        return newsource
-
-    def indent(self, indent=' ' * 4):
-        """ return a copy of the source object with
-            all lines indented by the given indent-string.
-        """
-        newsource = Source()
-        newsource.lines = [(indent+line) for line in self.lines]
-        return newsource
-
-    def getstatement(self, lineno, assertion=False):
-        """ return Source statement which contains the
-            given linenumber (counted from 0).
-        """
-        start, end = self.getstatementrange(lineno, assertion)
-        return self[start:end]
-
-    def getstatementrange(self, lineno, assertion=False):
-        """ return (start, end) tuple which spans the minimal
-            statement region which containing the given lineno.
-        """
-        if not (0 <= lineno < len(self)):
-            raise IndexError("lineno out of range")
-        ast, start, end = getstatementrange_ast(lineno, self)
-        return start, end
-
-    def deindent(self, offset=None):
-        """ return a new source object deindented by offset.
-            If offset is None then guess an indentation offset from
-            the first non-blank line.  Subsequent lines which have a
-            lower indentation offset will be copied verbatim as
-            they are assumed to be part of multilines.
-        """
-        # XXX maybe use the tokenizer to properly handle multiline
-        #     strings etc.pp?
-        newsource = Source()
-        newsource.lines[:] = deindent(self.lines, offset)
-        return newsource
-
-    def isparseable(self, deindent=True):
-        """ return True if source is parseable, heuristically
-            deindenting it by default.
-        """
-        try:
-            import parser
-        except ImportError:
-            syntax_checker = lambda x: compile(x, 'asd', 'exec')
-        else:
-            syntax_checker = parser.suite
-
-        if deindent:
-            source = str(self.deindent())
-        else:
-            source = str(self)
-        try:
-            #compile(source+'\n', "x", "exec")
-            syntax_checker(source+'\n')
-        except KeyboardInterrupt:
-            raise
-        except Exception:
-            return False
-        else:
-            return True
-
-    def __str__(self):
-        return "\n".join(self.lines)
-
-    def compile(self, filename=None, mode='exec',
-                flag=generators.compiler_flag,
-                dont_inherit=0, _genframe=None):
-        """ return compiled code object. if filename is None
-            invent an artificial filename which displays
-            the source/line position of the caller frame.
-        """
-        if not filename or py.path.local(filename).check(file=0):
-            if _genframe is None:
-                _genframe = sys._getframe(1) # the caller
-            fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
-            base = "<%d-codegen " % self._compilecounter
-            self.__class__._compilecounter += 1
-            if not filename:
-                filename = base + '%s:%d>' % (fn, lineno)
-            else:
-                filename = base + '%r %s:%d>' % (filename, fn, lineno)
-        source = "\n".join(self.lines) + '\n'
-        try:
-            co = cpy_compile(source, filename, mode, flag)
-        except SyntaxError:
-            ex = sys.exc_info()[1]
-            # re-represent syntax errors from parsing python strings
-            msglines = self.lines[:ex.lineno]
-            if ex.offset:
-                msglines.append(" "*ex.offset + '^')
-            msglines.append("(code was compiled probably from here: %s)" % filename)
-            newex = SyntaxError('\n'.join(msglines))
-            newex.offset = ex.offset
-            newex.lineno = ex.lineno
-            newex.text = ex.text
-            raise newex
-        else:
-            if flag & _AST_FLAG:
-                return co
-            lines = [(x + "\n") for x in self.lines]
-            if sys.version_info[0] >= 3:
-                # XXX py3's inspect.getsourcefile() checks for a module
-                # and a pep302 __loader__ ... we don't have a module
-                # at code compile-time so we need to fake it here
-                m = ModuleType("_pycodecompile_pseudo_module")
-                py.std.inspect.modulesbyfile[filename] = None
-                py.std.sys.modules[None] = m
-                m.__loader__ = 1
-            py.std.linecache.cache[filename] = (1, None, lines, filename)
-            return co
-
-#
-# public API shortcut functions
-#
-
-def compile_(source, filename=None, mode='exec', flags=
-            generators.compiler_flag, dont_inherit=0):
-    """ compile the given source to a raw code object,
-        and maintain an internal cache which allows later
-        retrieval of the source code for the code object
-        and any recursively created code objects.
-    """
-    if _ast is not None and isinstance(source, _ast.AST):
-        # XXX should Source support having AST?
-        return cpy_compile(source, filename, mode, flags, dont_inherit)
-    _genframe = sys._getframe(1) # the caller
-    s = Source(source)
-    co = s.compile(filename, mode, flags, _genframe=_genframe)
-    return co
-
-
-def getfslineno(obj):
-    """ Return source location (path, lineno) for the given object.
-    If the source cannot be determined return ("", -1)
-    """
-    try:
-        code = py.code.Code(obj)
-    except TypeError:
-        try:
-            fn = (py.std.inspect.getsourcefile(obj) or
-                  py.std.inspect.getfile(obj))
-        except TypeError:
-            return "", -1
-
-        fspath = fn and py.path.local(fn) or None
-        lineno = -1
-        if fspath:
-            try:
-                _, lineno = findsource(obj)
-            except IOError:
-                pass
-    else:
-        fspath = code.path
-        lineno = code.firstlineno
-    assert isinstance(lineno, int)
-    return fspath, lineno
-
-#
-# helper functions
-#
-
-def findsource(obj):
-    try:
-        sourcelines, lineno = py.std.inspect.findsource(obj)
-    except py.builtin._sysex:
-        raise
-    except:
-        return None, -1
-    source = Source()
-    source.lines = [line.rstrip() for line in sourcelines]
-    return source, lineno
-
-def getsource(obj, **kwargs):
-    obj = py.code.getrawcode(obj)
-    try:
-        strsrc = inspect.getsource(obj)
-    except IndentationError:
-        strsrc = "\"Buggy python version consider upgrading, cannot get source\""
-    assert isinstance(strsrc, str)
-    return Source(strsrc, **kwargs)
-
-def deindent(lines, offset=None):
-    if offset is None:
-        for line in lines:
-            line = line.expandtabs()
-            s = line.lstrip()
-            if s:
-                offset = len(line)-len(s)
-                break
-        else:
-            offset = 0
-    if offset == 0:
-        return list(lines)
-    newlines = []
-    def readline_generator(lines):
-        for line in lines:
-            yield line + '\n'
-        while True:
-            yield ''
-
-    it = readline_generator(lines)
-
-    try:
-        for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
-            if sline > len(lines):
-                break # End of input reached
-            if sline > len(newlines):
-                line = lines[sline - 1].expandtabs()
-                if line.lstrip() and line[:offset].isspace():
-                    line = line[offset:] # Deindent
-                newlines.append(line)
-
-            for i in range(sline, eline):
-                # Don't deindent continuing lines of
-                # multiline tokens (i.e. multiline strings)
-                newlines.append(lines[i])
-    except (IndentationError, tokenize.TokenError):
-        pass
-    # Add any lines we didn't see. E.g. if an exception was raised.
-    newlines.extend(lines[len(newlines):])
-    return newlines
-
-
-def get_statement_startend2(lineno, node):
-    import ast
-    # flatten all statements and except handlers into one lineno-list
-    # AST's line numbers start indexing at 1
-    l = []
-    for x in ast.walk(node):
-        if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
-            l.append(x.lineno - 1)
-            for name in "finalbody", "orelse":
-                val = getattr(x, name, None)
-                if val:
-                    # treat the finally/orelse part as its own statement
-                    l.append(val[0].lineno - 1 - 1)
-    l.sort()
-    insert_index = bisect_right(l, lineno)
-    start = l[insert_index - 1]
-    if insert_index >= len(l):
-        end = None
-    else:
-        end = l[insert_index]
-    return start, end
-
-
-def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
-    if astnode is None:
-        content = str(source)
-        if sys.version_info < (2,7):
-            content += "\n"
-        try:
-            astnode = compile(content, "source", "exec", 1024)  # 1024 for AST
-        except ValueError:
-            start, end = getstatementrange_old(lineno, source, assertion)
-            return None, start, end
-    start, end = get_statement_startend2(lineno, astnode)
-    # we need to correct the end:
-    # - ast-parsing strips comments
-    # - there might be empty lines
-    # - we might have lesser indented code blocks at the end
-    if end is None:
-        end = len(source.lines)
-
-    if end > start + 1:
-        # make sure we don't span differently indented code blocks
-        # by using the BlockFinder helper used which inspect.getsource() uses itself
-        block_finder = inspect.BlockFinder()
-        # if we start with an indented line, put blockfinder to "started" mode
-        block_finder.started = source.lines[start][0].isspace()
-        it = ((x + "\n") for x in source.lines[start:end])
-        try:
-            for tok in tokenize.generate_tokens(lambda: next(it)):
-                block_finder.tokeneater(*tok)
-        except (inspect.EndOfBlock, IndentationError):
-            end = block_finder.last + start
-        except Exception:
-            pass
-
-    # the end might still point to a comment or empty line, correct it
-    while end:
-        line = source.lines[end - 1].lstrip()
-        if line.startswith("#") or not line:
-            end -= 1
-        else:
-            break
-    return astnode, start, end
-
-
-def getstatementrange_old(lineno, source, assertion=False):
-    """ return (start, end) tuple which spans the minimal
-        statement region which containing the given lineno.
-        raise an IndexError if no such statementrange can be found.
-    """
-    # XXX this logic is only used on python2.4 and below
-    # 1. find the start of the statement
-    from codeop import compile_command
-    for start in range(lineno, -1, -1):
-        if assertion:
-            line = source.lines[start]
-            # the following lines are not fully tested, change with care
-            if 'super' in line and 'self' in line and '__init__' in line:
-                raise IndexError("likely a subclass")
-            if "assert" not in line and "raise" not in line:
-                continue
-        trylines = source.lines[start:lineno+1]
-        # quick hack to prepare parsing an indented line with
-        # compile_command() (which errors on "return" outside defs)
-        trylines.insert(0, 'def xxx():')
-        trysource = '\n '.join(trylines)
-        #              ^ space here
-        try:
-            compile_command(trysource)
-        except (SyntaxError, OverflowError, ValueError):
-            continue
-
-        # 2. find the end of the statement
-        for end in range(lineno+1, len(source)+1):
-            trysource = source[start:end]
-            if trysource.isparseable():
-                return start, end
-    raise SyntaxError("no valid source range around line %d " % (lineno,))
-
-
diff --git a/tools/py/py/_error.py b/tools/py/py/_error.py
deleted file mode 100644
index 550fb52..0000000
--- a/tools/py/py/_error.py
+++ /dev/null
@@ -1,88 +0,0 @@
-"""
-create errno-specific classes for IO or os calls.
-
-"""
-import sys, os, errno
-
-class Error(EnvironmentError):
-    def __repr__(self):
-        return "%s.%s %r: %s " %(self.__class__.__module__,
-                               self.__class__.__name__,
-                               self.__class__.__doc__,
-                               " ".join(map(str, self.args)),
-                               #repr(self.args)
-                                )
-
-    def __str__(self):
-        s = "[%s]: %s" %(self.__class__.__doc__,
-                          " ".join(map(str, self.args)),
-                          )
-        return s
-
-_winerrnomap = {
-    2: errno.ENOENT,
-    3: errno.ENOENT,
-    17: errno.EEXIST,
-    13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable
-    22: errno.ENOTDIR,
-    20: errno.ENOTDIR,
-    267: errno.ENOTDIR,
-    5: errno.EACCES,  # anything better?
-}
-
-class ErrorMaker(object):
-    """ lazily provides Exception classes for each possible POSIX errno
-        (as defined per the 'errno' module).  All such instances
-        subclass EnvironmentError.
-    """
-    Error = Error
-    _errno2class = {}
-
-    def __getattr__(self, name):
-        if name[0] == "_":
-            raise AttributeError(name)
-        eno = getattr(errno, name)
-        cls = self._geterrnoclass(eno)
-        setattr(self, name, cls)
-        return cls
-
-    def _geterrnoclass(self, eno):
-        try:
-            return self._errno2class[eno]
-        except KeyError:
-            clsname = errno.errorcode.get(eno, "UnknownErrno%d" %(eno,))
-            errorcls = type(Error)(clsname, (Error,),
-                    {'__module__':'py.error',
-                     '__doc__': os.strerror(eno)})
-            self._errno2class[eno] = errorcls
-            return errorcls
-
-    def checked_call(self, func, *args, **kwargs):
-        """ call a function and raise an errno-exception if applicable. """
-        __tracebackhide__ = True
-        try:
-            return func(*args, **kwargs)
-        except self.Error:
-            raise
-        except (OSError, EnvironmentError):
-            cls, value, tb = sys.exc_info()
-            if not hasattr(value, 'errno'):
-                raise
-            __tracebackhide__ = False
-            errno = value.errno
-            try:
-                if not isinstance(value, WindowsError):
-                    raise NameError
-            except NameError:
-                # we are not on Windows, or we got a proper OSError
-                cls = self._geterrnoclass(errno)
-            else:
-                try:
-                    cls = self._geterrnoclass(_winerrnomap[errno])
-                except KeyError:
-                    raise value
-            raise cls("%s%r" % (func.__name__, args))
-            __tracebackhide__ = True
-            
-
-error = ErrorMaker()
diff --git a/tools/py/py/_iniconfig.py b/tools/py/py/_iniconfig.py
deleted file mode 100644
index 92b50bd..0000000
--- a/tools/py/py/_iniconfig.py
+++ /dev/null
@@ -1,162 +0,0 @@
-""" brain-dead simple parser for ini-style files.
-(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed
-"""
-__version__ = "0.2.dev2"
-
-__all__ = ['IniConfig', 'ParseError']
-
-COMMENTCHARS = "#;"
-
-class ParseError(Exception):
-    def __init__(self, path, lineno, msg):
-        Exception.__init__(self, path, lineno, msg)
-        self.path = path
-        self.lineno = lineno
-        self.msg = msg
-
-    def __str__(self):
-        return "%s:%s: %s" %(self.path, self.lineno+1, self.msg)
-
-class SectionWrapper(object):
-    def __init__(self, config, name):
-        self.config = config
-        self.name = name
-
-    def lineof(self, name):
-        return self.config.lineof(self.name, name)
-
-    def get(self, key, default=None, convert=str):
-        return self.config.get(self.name, key, convert=convert, default=default)
-
-    def __getitem__(self, key):
-        return self.config.sections[self.name][key]
-
-    def __iter__(self):
-        section = self.config.sections.get(self.name, [])
-        def lineof(key):
-            return self.config.lineof(self.name, key)
-        for name in sorted(section, key=lineof):
-            yield name
-
-    def items(self):
-        for name in self:
-            yield name, self[name]
-
-
-class IniConfig(object):
-    def __init__(self, path, data=None):
-        self.path = str(path) # convenience
-        if data is None:
-            f = open(self.path)
-            try:
-                tokens = self._parse(iter(f))
-            finally:
-                f.close()
-        else:
-            tokens = self._parse(data.splitlines(True))
-
-        self._sources = {}
-        self.sections = {}
-
-        for lineno, section, name, value in tokens:
-            if section is None:
-                self._raise(lineno, 'no section header defined')
-            self._sources[section, name] = lineno
-            if name is None:
-                if section in self.sections:
-                    self._raise(lineno, 'duplicate section %r'%(section, ))
-                self.sections[section] = {}
-            else:
-                if name in self.sections[section]:
-                    self._raise(lineno, 'duplicate name %r'%(name, ))
-                self.sections[section][name] = value
-
-    def _raise(self, lineno, msg):
-        raise ParseError(self.path, lineno, msg)
-
-    def _parse(self, line_iter):
-        result = []
-        section = None
-        for lineno, line in enumerate(line_iter):
-            name, data = self._parseline(line, lineno)
-            # new value
-            if name is not None and data is not None:
-                result.append((lineno, section, name, data))
-            # new section
-            elif name is not None and data is None:
-                if not name:
-                    self._raise(lineno, 'empty section name')
-                section = name
-                result.append((lineno, section, None, None))
-            # continuation
-            elif name is None and data is not None:
-                if not result:
-                    self._raise(lineno, 'unexpected value continuation')
-                last = result.pop()
-                last_name, last_data = last[-2:]
-                if last_name is None:
-                    self._raise(lineno, 'unexpected value continuation')
-
-                if last_data:
-                    data = '%s\n%s' % (last_data, data)
-                result.append(last[:-1] + (data,))
-        return result
-
-    def _parseline(self, line, lineno):
-        # blank lines
-        if iscommentline(line):
-            line = ""
-        else:
-            line = line.rstrip()
-        if not line:
-            return None, None
-        # section
-        if line[0] == '[':
-            realline = line
-            for c in COMMENTCHARS:
-                line = line.split(c)[0].rstrip()
-            if line[-1] == "]":
-                return line[1:-1], None
-            return None, realline.strip()
-        # value
-        elif not line[0].isspace():
-            try:
-                name, value = line.split('=', 1)
-                if ":" in name:
-                    raise ValueError()
-            except ValueError:
-                try:
-                    name, value = line.split(":", 1)
-                except ValueError:
-                    self._raise(lineno, 'unexpected line: %r' % line)
-            return name.strip(), value.strip()
-        # continuation
-        else:
-            return None, line.strip()
-
-    def lineof(self, section, name=None):
-        lineno = self._sources.get((section, name))
-        if lineno is not None:
-            return lineno + 1
-
-    def get(self, section, name, default=None, convert=str):
-        try:
-            return convert(self.sections[section][name])
-        except KeyError:
-            return default
-
-    def __getitem__(self, name):
-        if name not in self.sections:
-            raise KeyError(name)
-        return SectionWrapper(self, name)
-
-    def __iter__(self):
-        for name in sorted(self.sections, key=self.lineof):
-            yield SectionWrapper(self, name)
-
-    def __contains__(self, arg):
-        return arg in self.sections
-
-def iscommentline(line):
-    c = line.lstrip()[:1]
-    return c in COMMENTCHARS
diff --git a/tools/py/py/_io/terminalwriter.py b/tools/py/py/_io/terminalwriter.py
deleted file mode 100644
index cef1ff5..0000000
--- a/tools/py/py/_io/terminalwriter.py
+++ /dev/null
@@ -1,348 +0,0 @@
-"""
-
-Helper functions for writing to terminals and files.
-
-"""
-
-
-import sys, os
-import py
-py3k = sys.version_info[0] >= 3
-from py.builtin import text, bytes
-
-win32_and_ctypes = False
-colorama = None
-if sys.platform == "win32":
-    try:
-        import colorama
-    except ImportError:
-        try:
-            import ctypes
-            win32_and_ctypes = True
-        except ImportError:
-            pass
-
-
-def _getdimensions():
-    import termios,fcntl,struct
-    call = fcntl.ioctl(1,termios.TIOCGWINSZ,"\000"*8)
-    height,width = struct.unpack( "hhhh", call ) [:2]
-    return height, width
-
-
-def get_terminal_width():
-    height = width = 0
-    try:
-        height, width = _getdimensions()
-    except py.builtin._sysex:
-        raise
-    except:
-        # pass to fallback below
-        pass
-
-    if width == 0:
-        # FALLBACK:
-        # * some exception happened
-        # * or this is emacs terminal which reports (0,0)
-        width = int(os.environ.get('COLUMNS', 80))
-
-    # XXX the windows getdimensions may be bogus, let's sanify a bit
-    if width < 40:
-        width = 80
-    return width
-
-terminal_width = get_terminal_width()
-
-# XXX unify with _escaped func below
-def ansi_print(text, esc, file=None, newline=True, flush=False):
-    if file is None:
-        file = sys.stderr
-    text = text.rstrip()
-    if esc and not isinstance(esc, tuple):
-        esc = (esc,)
-    if esc and sys.platform != "win32" and file.isatty():
-        text = (''.join(['\x1b[%sm' % cod for cod in esc])  +
-                text +
-                '\x1b[0m')     # ANSI color code "reset"
-    if newline:
-        text += '\n'
-
-    if esc and win32_and_ctypes and file.isatty():
-        if 1 in esc:
-            bold = True
-            esc = tuple([x for x in esc if x != 1])
-        else:
-            bold = False
-        esctable = {()   : FOREGROUND_WHITE,                 # normal
-                    (31,): FOREGROUND_RED,                   # red
-                    (32,): FOREGROUND_GREEN,                 # green
-                    (33,): FOREGROUND_GREEN|FOREGROUND_RED,  # yellow
-                    (34,): FOREGROUND_BLUE,                  # blue
-                    (35,): FOREGROUND_BLUE|FOREGROUND_RED,   # purple
-                    (36,): FOREGROUND_BLUE|FOREGROUND_GREEN, # cyan
-                    (37,): FOREGROUND_WHITE,                 # white
-                    (39,): FOREGROUND_WHITE,                 # reset
-                    }
-        attr = esctable.get(esc, FOREGROUND_WHITE)
-        if bold:
-            attr |= FOREGROUND_INTENSITY
-        STD_OUTPUT_HANDLE = -11
-        STD_ERROR_HANDLE = -12
-        if file is sys.stderr:
-            handle = GetStdHandle(STD_ERROR_HANDLE)
-        else:
-            handle = GetStdHandle(STD_OUTPUT_HANDLE)
-        oldcolors = GetConsoleInfo(handle).wAttributes
-        attr |= (oldcolors & 0x0f0)
-        SetConsoleTextAttribute(handle, attr)
-        while len(text) > 32768:
-            file.write(text[:32768])
-            text = text[32768:]
-        if text:
-            file.write(text)
-        SetConsoleTextAttribute(handle, oldcolors)
-    else:
-        file.write(text)
-
-    if flush:
-        file.flush()
-
-def should_do_markup(file):
-    if os.environ.get('PY_COLORS') == '1':
-        return True
-    if os.environ.get('PY_COLORS') == '0':
-        return False
-    return hasattr(file, 'isatty') and file.isatty() \
-           and os.environ.get('TERM') != 'dumb' \
-           and not (sys.platform.startswith('java') and os._name == 'nt')
-
-class TerminalWriter(object):
-    _esctable = dict(black=30, red=31, green=32, yellow=33,
-                     blue=34, purple=35, cyan=36, white=37,
-                     Black=40, Red=41, Green=42, Yellow=43,
-                     Blue=44, Purple=45, Cyan=46, White=47,
-                     bold=1, light=2, blink=5, invert=7)
-
-    # XXX deprecate stringio argument
-    def __init__(self, file=None, stringio=False, encoding=None):
-        if file is None:
-            if stringio:
-                self.stringio = file = py.io.TextIO()
-            else:
-                file = py.std.sys.stdout
-        elif py.builtin.callable(file) and not (
-             hasattr(file, "write") and hasattr(file, "flush")):
-            file = WriteFile(file, encoding=encoding)
-        if hasattr(file, "isatty") and file.isatty() and colorama:
-            file = colorama.AnsiToWin32(file).stream
-        self.encoding = encoding or getattr(file, 'encoding', "utf-8")
-        self._file = file
-        self.fullwidth = get_terminal_width()
-        self.hasmarkup = should_do_markup(file)
-        self._lastlen = 0
-
-    def _escaped(self, text, esc):
-        if esc and self.hasmarkup:
-            text = (''.join(['\x1b[%sm' % cod for cod in esc])  +
-                text +'\x1b[0m')
-        return text
-
-    def markup(self, text, **kw):
-        esc = []
-        for name in kw:
-            if name not in self._esctable:
-                raise ValueError("unknown markup: %r" %(name,))
-            if kw[name]:
-                esc.append(self._esctable[name])
-        return self._escaped(text, tuple(esc))
-
-    def sep(self, sepchar, title=None, fullwidth=None, **kw):
-        if fullwidth is None:
-            fullwidth = self.fullwidth
-        # the goal is to have the line be as long as possible
-        # under the condition that len(line) <= fullwidth
-        if sys.platform == "win32":
-            # if we print in the last column on windows we are on a
-            # new line but there is no way to verify/neutralize this
-            # (we may not know the exact line width)
-            # so let's be defensive to avoid empty lines in the output
-            fullwidth -= 1
-        if title is not None:
-            # we want 2 + 2*len(fill) + len(title) <= fullwidth
-            # i.e.    2 + 2*len(sepchar)*N + len(title) <= fullwidth
-            #         2*len(sepchar)*N <= fullwidth - len(title) - 2
-            #         N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
-            N = (fullwidth - len(title) - 2) // (2*len(sepchar))
-            fill = sepchar * N
-            line = "%s %s %s" % (fill, title, fill)
-        else:
-            # we want len(sepchar)*N <= fullwidth
-            # i.e.    N <= fullwidth // len(sepchar)
-            line = sepchar * (fullwidth // len(sepchar))
-        # in some situations there is room for an extra sepchar at the right,
-        # in particular if we consider that with a sepchar like "_ " the
-        # trailing space is not important at the end of the line
-        if len(line) + len(sepchar.rstrip()) <= fullwidth:
-            line += sepchar.rstrip()
-
-        self.line(line, **kw)
-
-    def write(self, msg, **kw):
-        if msg:
-            if not isinstance(msg, (bytes, text)):
-                msg = text(msg)
-            if self.hasmarkup and kw:
-                markupmsg = self.markup(msg, **kw)
-            else:
-                markupmsg = msg
-            write_out(self._file, markupmsg)
-
-    def line(self, s='', **kw):
-        self.write(s, **kw)
-        self._checkfill(s)
-        self.write('\n')
-
-    def reline(self, line, **kw):
-        if not self.hasmarkup:
-            raise ValueError("cannot use rewrite-line without terminal")
-        self.write(line, **kw)
-        self._checkfill(line)
-        self.write('\r')
-        self._lastlen = len(line)
-
-    def _checkfill(self, line):
-        diff2last = self._lastlen - len(line)
-        if diff2last > 0:
-            self.write(" " * diff2last)
-
-class Win32ConsoleWriter(TerminalWriter):
-    def write(self, msg, **kw):
-        if msg:
-            if not isinstance(msg, (bytes, text)):
-                msg = text(msg)
-            oldcolors = None
-            if self.hasmarkup and kw:
-                handle = GetStdHandle(STD_OUTPUT_HANDLE)
-                oldcolors = GetConsoleInfo(handle).wAttributes
-                default_bg = oldcolors & 0x00F0
-                attr = default_bg
-                if kw.pop('bold', False):
-                    attr |= FOREGROUND_INTENSITY
-
-                if kw.pop('red', False):
-                    attr |= FOREGROUND_RED
-                elif kw.pop('blue', False):
-                    attr |= FOREGROUND_BLUE
-                elif kw.pop('green', False):
-                    attr |= FOREGROUND_GREEN
-                elif kw.pop('yellow', False):
-                    attr |= FOREGROUND_GREEN|FOREGROUND_RED
-                else:
-                    attr |= oldcolors & 0x0007
-
-                SetConsoleTextAttribute(handle, attr)
-            write_out(self._file, msg)
-            if oldcolors:
-                SetConsoleTextAttribute(handle, oldcolors)
-
-class WriteFile(object):
-    def __init__(self, writemethod, encoding=None):
-        self.encoding = encoding
-        self._writemethod = writemethod
-
-    def write(self, data):
-        if self.encoding:
-            data = data.encode(self.encoding, "replace")
-        self._writemethod(data)
-
-    def flush(self):
-        return
-
-
-if win32_and_ctypes:
-    TerminalWriter = Win32ConsoleWriter
-    import ctypes
-    from ctypes import wintypes
-
-    # ctypes access to the Windows console
-    STD_OUTPUT_HANDLE = -11
-    STD_ERROR_HANDLE  = -12
-    FOREGROUND_BLACK     = 0x0000 # black text
-    FOREGROUND_BLUE      = 0x0001 # text color contains blue.
-    FOREGROUND_GREEN     = 0x0002 # text color contains green.
-    FOREGROUND_RED       = 0x0004 # text color contains red.
-    FOREGROUND_WHITE     = 0x0007
-    FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
-    BACKGROUND_BLACK     = 0x0000 # background color black
-    BACKGROUND_BLUE      = 0x0010 # background color contains blue.
-    BACKGROUND_GREEN     = 0x0020 # background color contains green.
-    BACKGROUND_RED       = 0x0040 # background color contains red.
-    BACKGROUND_WHITE     = 0x0070
-    BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
-
-    SHORT = ctypes.c_short
-    class COORD(ctypes.Structure):
-        _fields_ = [('X', SHORT),
-                    ('Y', SHORT)]
-    class SMALL_RECT(ctypes.Structure):
-        _fields_ = [('Left', SHORT),
-                    ('Top', SHORT),
-                    ('Right', SHORT),
-                    ('Bottom', SHORT)]
-    class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
-        _fields_ = [('dwSize', COORD),
-                    ('dwCursorPosition', COORD),
-                    ('wAttributes', wintypes.WORD),
-                    ('srWindow', SMALL_RECT),
-                    ('dwMaximumWindowSize', COORD)]
-
-    _GetStdHandle = ctypes.windll.kernel32.GetStdHandle
-    _GetStdHandle.argtypes = [wintypes.DWORD]
-    _GetStdHandle.restype = wintypes.HANDLE
-    def GetStdHandle(kind):
-        return _GetStdHandle(kind)
-
-    SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute
-    SetConsoleTextAttribute.argtypes = [wintypes.HANDLE, wintypes.WORD]
-    SetConsoleTextAttribute.restype = wintypes.BOOL
-
-    _GetConsoleScreenBufferInfo = \
-        ctypes.windll.kernel32.GetConsoleScreenBufferInfo
-    _GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE,
-                                ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO)]
-    _GetConsoleScreenBufferInfo.restype = wintypes.BOOL
-    def GetConsoleInfo(handle):
-        info = CONSOLE_SCREEN_BUFFER_INFO()
-        _GetConsoleScreenBufferInfo(handle, ctypes.byref(info))
-        return info
-
-    def _getdimensions():
-        handle = GetStdHandle(STD_OUTPUT_HANDLE)
-        info = GetConsoleInfo(handle)
-        # Substract one from the width, otherwise the cursor wraps
-        # and the ending \n causes an empty line to display.
-        return info.dwSize.Y, info.dwSize.X - 1
-
-def write_out(fil, msg):
-    # XXX sometimes "msg" is of type bytes, sometimes text which
-    # complicates the situation.  Should we try to enforce unicode?
-    try:
-        # on py27 and above writing out to sys.stdout with an encoding
-        # should usually work for unicode messages (if the encoding is
-        # capable of it)
-        fil.write(msg)
-    except UnicodeEncodeError:
-        # on py26 it might not work because stdout expects bytes
-        if fil.encoding:
-            try:
-                fil.write(msg.encode(fil.encoding))
-            except UnicodeEncodeError:
-                # it might still fail if the encoding is not capable
-                pass
-            else:
-                fil.flush()
-                return
-        # fallback: escape all unicode characters
-        msg = msg.encode("unicode-escape").decode("ascii")
-        fil.write(msg)
-    fil.flush()
diff --git a/tools/py/py/_log/log.py b/tools/py/py/_log/log.py
deleted file mode 100644
index ce47e8c..0000000
--- a/tools/py/py/_log/log.py
+++ /dev/null
@@ -1,186 +0,0 @@
-"""
-basic logging functionality based on a producer/consumer scheme.
-
-XXX implement this API: (maybe put it into slogger.py?)
-
-        log = Logger(
-                    info=py.log.STDOUT,
-                    debug=py.log.STDOUT,
-                    command=None)
-        log.info("hello", "world")
-        log.command("hello", "world")
-
-        log = Logger(info=Logger(something=...),
-                     debug=py.log.STDOUT,
-                     command=None)
-"""
-import py, sys
-
-class Message(object):
-    def __init__(self, keywords, args):
-        self.keywords = keywords
-        self.args = args
-
-    def content(self):
-        return " ".join(map(str, self.args))
-
-    def prefix(self):
-        return "[%s] " % (":".join(self.keywords))
-
-    def __str__(self):
-        return self.prefix() + self.content()
-
-
-class Producer(object):
-    """ (deprecated) Log producer API which sends messages to be logged
-        to a 'consumer' object, which then prints them to stdout,
-        stderr, files, etc. Used extensively by PyPy-1.1.
-    """
-
-    Message = Message  # to allow later customization
-    keywords2consumer = {}
-
-    def __init__(self, keywords, keywordmapper=None, **kw):
-        if hasattr(keywords, 'split'):
-            keywords = tuple(keywords.split())
-        self._keywords = keywords
-        if keywordmapper is None:
-            keywordmapper = default_keywordmapper
-        self._keywordmapper = keywordmapper
-
-    def __repr__(self):
-        return "<py.log.Producer %s>" % ":".join(self._keywords)
-
-    def __getattr__(self, name):
-        if '_' in name:
-            raise AttributeError(name)
-        producer = self.__class__(self._keywords + (name,))
-        setattr(self, name, producer)
-        return producer
-
-    def __call__(self, *args):
-        """ write a message to the appropriate consumer(s) """
-        func = self._keywordmapper.getconsumer(self._keywords)
-        if func is not None:
-            func(self.Message(self._keywords, args))
-
-class KeywordMapper:
-    def __init__(self):
-        self.keywords2consumer = {}
-
-    def getstate(self):
-        return self.keywords2consumer.copy()
-    def setstate(self, state):
-        self.keywords2consumer.clear()
-        self.keywords2consumer.update(state)
-
-    def getconsumer(self, keywords):
-        """ return a consumer matching the given keywords.
-
-            tries to find the most suitable consumer by walking, starting from
-            the back, the list of keywords, the first consumer matching a
-            keyword is returned (falling back to py.log.default)
-        """
-        for i in range(len(keywords), 0, -1):
-            try:
-                return self.keywords2consumer[keywords[:i]]
-            except KeyError:
-                continue
-        return self.keywords2consumer.get('default', default_consumer)
-
-    def setconsumer(self, keywords, consumer):
-        """ set a consumer for a set of keywords. """
-        # normalize to tuples
-        if isinstance(keywords, str):
-            keywords = tuple(filter(None, keywords.split()))
-        elif hasattr(keywords, '_keywords'):
-            keywords = keywords._keywords
-        elif not isinstance(keywords, tuple):
-            raise TypeError("key %r is not a string or tuple" % (keywords,))
-        if consumer is not None and not py.builtin.callable(consumer):
-            if not hasattr(consumer, 'write'):
-                raise TypeError(
-                    "%r should be None, callable or file-like" % (consumer,))
-            consumer = File(consumer)
-        self.keywords2consumer[keywords] = consumer
-
-def default_consumer(msg):
-    """ the default consumer, prints the message to stdout (using 'print') """
-    sys.stderr.write(str(msg)+"\n")
-
-default_keywordmapper = KeywordMapper()
-
-def setconsumer(keywords, consumer):
-    default_keywordmapper.setconsumer(keywords, consumer)
-
-def setstate(state):
-    default_keywordmapper.setstate(state)
-def getstate():
-    return default_keywordmapper.getstate()
-
-#
-# Consumers
-#
-
-class File(object):
-    """ log consumer wrapping a file(-like) object """
-    def __init__(self, f):
-        assert hasattr(f, 'write')
-        #assert isinstance(f, file) or not hasattr(f, 'open')
-        self._file = f
-
-    def __call__(self, msg):
-        """ write a message to the log """
-        self._file.write(str(msg) + "\n")
-        if hasattr(self._file, 'flush'):
-            self._file.flush()
-
-class Path(object):
-    """ log consumer that opens and writes to a Path """
-    def __init__(self, filename, append=False,
-                 delayed_create=False, buffering=False):
-        self._append = append
-        self._filename = str(filename)
-        self._buffering = buffering
-        if not delayed_create:
-            self._openfile()
-
-    def _openfile(self):
-        mode = self._append and 'a' or 'w'
-        f = open(self._filename, mode)
-        self._file = f
-
-    def __call__(self, msg):
-        """ write a message to the log """
-        if not hasattr(self, "_file"):
-            self._openfile()
-        self._file.write(str(msg) + "\n")
-        if not self._buffering:
-            self._file.flush()
-
-def STDOUT(msg):
-    """ consumer that writes to sys.stdout """
-    sys.stdout.write(str(msg)+"\n")
-
-def STDERR(msg):
-    """ consumer that writes to sys.stderr """
-    sys.stderr.write(str(msg)+"\n")
-
-class Syslog:
-    """ consumer that writes to the syslog daemon """
-
-    def __init__(self, priority = None):
-        if priority is None:
-            priority = self.LOG_INFO
-        self.priority = priority
-
-    def __call__(self, msg):
-        """ write a message to the log """
-        py.std.syslog.syslog(self.priority, str(msg))
-
-for _prio in "EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG".split():
-    _prio = "LOG_" + _prio
-    try:
-        setattr(Syslog, _prio, getattr(py.std.syslog, _prio))
-    except AttributeError:
-        pass
diff --git a/tools/py/py/_log/warning.py b/tools/py/py/_log/warning.py
deleted file mode 100644
index 722e31e..0000000
--- a/tools/py/py/_log/warning.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import py, sys
-
-class DeprecationWarning(DeprecationWarning):
-    def __init__(self, msg, path, lineno):
-        self.msg = msg
-        self.path = path
-        self.lineno = lineno
-    def __repr__(self):
-        return "%s:%d: %s" %(self.path, self.lineno+1, self.msg)
-    def __str__(self):
-        return self.msg
-
-def _apiwarn(startversion, msg, stacklevel=2, function=None):
-    # below is mostly COPIED from python2.4/warnings.py's def warn()
-    # Get context information
-    if isinstance(stacklevel, str):
-        frame = sys._getframe(1)
-        level = 1
-        found = frame.f_code.co_filename.find(stacklevel) != -1
-        while frame:
-            co = frame.f_code
-            if co.co_filename.find(stacklevel) == -1:
-                if found:
-                    stacklevel = level
-                    break
-            else:
-                found = True
-            level += 1
-            frame = frame.f_back
-        else:
-            stacklevel = 1
-    msg = "%s (since version %s)" %(msg, startversion)
-    warn(msg, stacklevel=stacklevel+1, function=function)
-
-def warn(msg, stacklevel=1, function=None):
-    if function is not None:
-        filename = py.std.inspect.getfile(function)
-        lineno = py.code.getrawcode(function).co_firstlineno
-    else:
-        try:
-            caller = sys._getframe(stacklevel)
-        except ValueError:
-            globals = sys.__dict__
-            lineno = 1
-        else:
-            globals = caller.f_globals
-            lineno = caller.f_lineno
-        if '__name__' in globals:
-            module = globals['__name__']
-        else:
-            module = "<string>"
-        filename = globals.get('__file__')
-    if filename:
-        fnl = filename.lower()
-        if fnl.endswith(".pyc") or fnl.endswith(".pyo"):
-            filename = filename[:-1]
-        elif fnl.endswith("$py.class"):
-            filename = filename.replace('$py.class', '.py')
-    else:
-        if module == "__main__":
-            try:
-                filename = sys.argv[0]
-            except AttributeError:
-                # embedded interpreters don't have sys.argv, see bug #839151
-                filename = '__main__'
-        if not filename:
-            filename = module
-    path = py.path.local(filename)
-    warning = DeprecationWarning(msg, path, lineno)
-    py.std.warnings.warn_explicit(warning, category=Warning,
-        filename=str(warning.path),
-        lineno=warning.lineno,
-        registry=py.std.warnings.__dict__.setdefault(
-            "__warningsregistry__", {})
-    )
-
diff --git a/tools/py/py/_path/common.py b/tools/py/py/_path/common.py
deleted file mode 100644
index d407434..0000000
--- a/tools/py/py/_path/common.py
+++ /dev/null
@@ -1,403 +0,0 @@
-"""
-"""
-import os, sys, posixpath
-import py
-
-# Moved from local.py.
-iswin32 = sys.platform == "win32" or (getattr(os, '_name', False) == 'nt')
-
-class Checkers:
-    _depend_on_existence = 'exists', 'link', 'dir', 'file'
-
-    def __init__(self, path):
-        self.path = path
-
-    def dir(self):
-        raise NotImplementedError
-
-    def file(self):
-        raise NotImplementedError
-
-    def dotfile(self):
-        return self.path.basename.startswith('.')
-
-    def ext(self, arg):
-        if not arg.startswith('.'):
-            arg = '.' + arg
-        return self.path.ext == arg
-
-    def exists(self):
-        raise NotImplementedError
-
-    def basename(self, arg):
-        return self.path.basename == arg
-
-    def basestarts(self, arg):
-        return self.path.basename.startswith(arg)
-
-    def relto(self, arg):
-        return self.path.relto(arg)
-
-    def fnmatch(self, arg):
-        return self.path.fnmatch(arg)
-
-    def endswith(self, arg):
-        return str(self.path).endswith(arg)
-
-    def _evaluate(self, kw):
-        for name, value in kw.items():
-            invert = False
-            meth = None
-            try:
-                meth = getattr(self, name)
-            except AttributeError:
-                if name[:3] == 'not':
-                    invert = True
-                    try:
-                        meth = getattr(self, name[3:])
-                    except AttributeError:
-                        pass
-            if meth is None:
-                raise TypeError(
-                    "no %r checker available for %r" % (name, self.path))
-            try:
-                if py.code.getrawcode(meth).co_argcount > 1:
-                    if (not meth(value)) ^ invert:
-                        return False
-                else:
-                    if bool(value) ^ bool(meth()) ^ invert:
-                        return False
-            except (py.error.ENOENT, py.error.ENOTDIR, py.error.EBUSY):
-                # EBUSY feels not entirely correct,
-                # but its kind of necessary since ENOMEDIUM
-                # is not accessible in python
-                for name in self._depend_on_existence:
-                    if name in kw:
-                        if kw.get(name):
-                            return False
-                    name = 'not' + name
-                    if name in kw:
-                        if not kw.get(name):
-                            return False
-        return True
-
-class NeverRaised(Exception):
-    pass
-
-class PathBase(object):
-    """ shared implementation for filesystem path objects."""
-    Checkers = Checkers
-
-    def __div__(self, other):
-        return self.join(str(other))
-    __truediv__ = __div__ # py3k
-
-    def basename(self):
-        """ basename part of path. """
-        return self._getbyspec('basename')[0]
-    basename = property(basename, None, None, basename.__doc__)
-
-    def dirname(self):
-        """ dirname part of path. """
-        return self._getbyspec('dirname')[0]
-    dirname = property(dirname, None, None, dirname.__doc__)
-
-    def purebasename(self):
-        """ pure base name of the path."""
-        return self._getbyspec('purebasename')[0]
-    purebasename = property(purebasename, None, None, purebasename.__doc__)
-
-    def ext(self):
-        """ extension of the path (including the '.')."""
-        return self._getbyspec('ext')[0]
-    ext = property(ext, None, None, ext.__doc__)
-
-    def dirpath(self, *args, **kwargs):
-        """ return the directory path joined with any given path arguments.  """
-        return self.new(basename='').join(*args, **kwargs)
-
-    def read_binary(self):
-        """ read and return a bytestring from reading the path. """
-        with self.open('rb') as f:
-            return f.read()
-
-    def read_text(self, encoding):
-        """ read and return a Unicode string from reading the path. """
-        with self.open("r", encoding=encoding) as f:
-            return f.read()
-
-
-    def read(self, mode='r'):
-        """ read and return a bytestring from reading the path. """
-        with self.open(mode) as f:
-            return f.read()
-
-    def readlines(self, cr=1):
-        """ read and return a list of lines from the path. if cr is False, the
-newline will be removed from the end of each line. """
-        if not cr:
-            content = self.read('rU')
-            return content.split('\n')
-        else:
-            f = self.open('rU')
-            try:
-                return f.readlines()
-            finally:
-                f.close()
-
-    def load(self):
-        """ (deprecated) return object unpickled from self.read() """
-        f = self.open('rb')
-        try:
-            return py.error.checked_call(py.std.pickle.load, f)
-        finally:
-            f.close()
-
-    def move(self, target):
-        """ move this path to target. """
-        if target.relto(self):
-            raise py.error.EINVAL(target,
-                "cannot move path into a subdirectory of itself")
-        try:
-            self.rename(target)
-        except py.error.EXDEV:  # invalid cross-device link
-            self.copy(target)
-            self.remove()
-
-    def __repr__(self):
-        """ return a string representation of this path. """
-        return repr(str(self))
-
-    def check(self, **kw):
-        """ check a path for existence and properties.
-
-            Without arguments, return True if the path exists, otherwise False.
-
-            valid checkers::
-
-                file=1    # is a file
-                file=0    # is not a file (may not even exist)
-                dir=1     # is a dir
-                link=1    # is a link
-                exists=1  # exists
-
-            You can specify multiple checker definitions, for example::
-
-                path.check(file=1, link=1)  # a link pointing to a file
-        """
-        if not kw:
-            kw = {'exists' : 1}
-        return self.Checkers(self)._evaluate(kw)
-
-    def fnmatch(self, pattern):
-        """return true if the basename/fullname matches the glob-'pattern'.
-
-        valid pattern characters::
-
-            *       matches everything
-            ?       matches any single character
-            [seq]   matches any character in seq
-            [!seq]  matches any char not in seq
-
-        If the pattern contains a path-separator then the full path
-        is used for pattern matching and a '*' is prepended to the
-        pattern.
-
-        if the pattern doesn't contain a path-separator the pattern
-        is only matched against the basename.
-        """
-        return FNMatcher(pattern)(self)
-
-    def relto(self, relpath):
-        """ return a string which is the relative part of the path
-        to the given 'relpath'.
-        """
-        if not isinstance(relpath, (str, PathBase)):
-            raise TypeError("%r: not a string or path object" %(relpath,))
-        strrelpath = str(relpath)
-        if strrelpath and strrelpath[-1] != self.sep:
-            strrelpath += self.sep
-        #assert strrelpath[-1] == self.sep
-        #assert strrelpath[-2] != self.sep
-        strself = self.strpath
-        if sys.platform == "win32" or getattr(os, '_name', None) == 'nt':
-            if os.path.normcase(strself).startswith(
-               os.path.normcase(strrelpath)):
-                return strself[len(strrelpath):]
-        elif strself.startswith(strrelpath):
-            return strself[len(strrelpath):]
-        return ""
-
-    def ensure_dir(self, *args):
-        """ ensure the path joined with args is a directory. """
-        return self.ensure(*args, **{"dir": True})
-
-    def bestrelpath(self, dest):
-        """ return a string which is a relative path from self
-            (assumed to be a directory) to dest such that
-            self.join(bestrelpath) == dest and if not such
-            path can be determined return dest.
-        """
-        try:
-            if self == dest:
-                return os.curdir
-            base = self.common(dest)
-            if not base:  # can be the case on windows
-                return str(dest)
-            self2base = self.relto(base)
-            reldest = dest.relto(base)
-            if self2base:
-                n = self2base.count(self.sep) + 1
-            else:
-                n = 0
-            l = [os.pardir] * n
-            if reldest:
-                l.append(reldest)
-            target = dest.sep.join(l)
-            return target
-        except AttributeError:
-            return str(dest)
-
-    def exists(self):
-        return self.check()
-
-    def isdir(self):
-        return self.check(dir=1)
-
-    def isfile(self):
-        return self.check(file=1)
-
-    def parts(self, reverse=False):
-        """ return a root-first list of all ancestor directories
-            plus the path itself.
-        """
-        current = self
-        l = [self]
-        while 1:
-            last = current
-            current = current.dirpath()
-            if last == current:
-                break
-            l.append(current)
-        if not reverse:
-            l.reverse()
-        return l
-
-    def common(self, other):
-        """ return the common part shared with the other path
-            or None if there is no common part.
-        """
-        last = None
-        for x, y in zip(self.parts(), other.parts()):
-            if x != y:
-                return last
-            last = x
-        return last
-
-    def __add__(self, other):
-        """ return new path object with 'other' added to the basename"""
-        return self.new(basename=self.basename+str(other))
-
-    def __cmp__(self, other):
-        """ return sort value (-1, 0, +1). """
-        try:
-            return cmp(self.strpath, other.strpath)
-        except AttributeError:
-            return cmp(str(self), str(other)) # self.path, other.path)
-
-    def __lt__(self, other):
-        try:
-            return self.strpath < other.strpath
-        except AttributeError:
-            return str(self) < str(other)
-
-    def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False):
-        """ yields all paths below the current one
-
-            fil is a filter (glob pattern or callable), if not matching the
-            path will not be yielded, defaulting to None (everything is
-            returned)
-
-            rec is a filter (glob pattern or callable) that controls whether
-            a node is descended, defaulting to None
-
-            ignore is an Exception class that is ignoredwhen calling dirlist()
-            on any of the paths (by default, all exceptions are reported)
-
-            bf if True will cause a breadthfirst search instead of the
-            default depthfirst. Default: False
-
-            sort if True will sort entries within each directory level.
-        """
-        for x in Visitor(fil, rec, ignore, bf, sort).gen(self):
-            yield x
-
-    def _sortlist(self, res, sort):
-        if sort:
-            if hasattr(sort, '__call__'):
-                res.sort(sort)
-            else:
-                res.sort()
-
-    def samefile(self, other):
-        """ return True if other refers to the same stat object as self. """
-        return self.strpath == str(other)
-
-class Visitor:
-    def __init__(self, fil, rec, ignore, bf, sort):
-        if isinstance(fil, str):
-            fil = FNMatcher(fil)
-        if isinstance(rec, str):
-            self.rec = FNMatcher(rec)
-        elif not hasattr(rec, '__call__') and rec:
-            self.rec = lambda path: True
-        else:
-            self.rec = rec
-        self.fil = fil
-        self.ignore = ignore
-        self.breadthfirst = bf
-        self.optsort = sort and sorted or (lambda x: x)
-
-    def gen(self, path):
-        try:
-            entries = path.listdir()
-        except self.ignore:
-            return
-        rec = self.rec
-        dirs = self.optsort([p for p in entries
-                    if p.check(dir=1) and (rec is None or rec(p))])
-        if not self.breadthfirst:
-            for subdir in dirs:
-                for p in self.gen(subdir):
-                    yield p
-        for p in self.optsort(entries):
-            if self.fil is None or self.fil(p):
-                yield p
-        if self.breadthfirst:
-            for subdir in dirs:
-                for p in self.gen(subdir):
-                    yield p
-
-class FNMatcher:
-    def __init__(self, pattern):
-        self.pattern = pattern
-
-    def __call__(self, path):
-        pattern = self.pattern
-
-        if (pattern.find(path.sep) == -1 and
-        iswin32 and
-        pattern.find(posixpath.sep) != -1):
-            # Running on Windows, the pattern has no Windows path separators,
-            # and the pattern has one or more Posix path separators. Replace
-            # the Posix path separators with the Windows path separator.
-            pattern = pattern.replace(posixpath.sep, path.sep)
-
-        if pattern.find(path.sep) == -1:
-            name = path.basename
-        else:
-            name = str(path) # path.strpath # XXX svn?
-            if not os.path.isabs(pattern):
-                pattern = '*' + path.sep + pattern
-        return py.std.fnmatch.fnmatch(name, pattern)
-
diff --git a/tools/py/py/_path/local.py b/tools/py/py/_path/local.py
deleted file mode 100644
index d569404..0000000
--- a/tools/py/py/_path/local.py
+++ /dev/null
@@ -1,911 +0,0 @@
-"""
-local path implementation.
-"""
-from __future__ import with_statement
-
-from contextlib import contextmanager
-import sys, os, re, atexit, io
-import py
-from py._path import common
-from py._path.common import iswin32
-from stat import S_ISLNK, S_ISDIR, S_ISREG
-
-from os.path import abspath, normpath, isabs, exists, isdir, isfile, islink, dirname
-
-if sys.version_info > (3,0):
-    def map_as_list(func, iter):
-        return list(map(func, iter))
-else:
-    map_as_list = map
-
-class Stat(object):
-    def __getattr__(self, name):
-        return getattr(self._osstatresult, "st_" + name)
-
-    def __init__(self, path, osstatresult):
-        self.path = path
-        self._osstatresult = osstatresult
-
-    @property
-    def owner(self):
-        if iswin32:
-            raise NotImplementedError("XXX win32")
-        import pwd
-        entry = py.error.checked_call(pwd.getpwuid, self.uid)
-        return entry[0]
-
-    @property
-    def group(self):
-        """ return group name of file. """
-        if iswin32:
-            raise NotImplementedError("XXX win32")
-        import grp
-        entry = py.error.checked_call(grp.getgrgid, self.gid)
-        return entry[0]
-
-    def isdir(self):
-        return S_ISDIR(self._osstatresult.st_mode)
-
-    def isfile(self):
-        return S_ISREG(self._osstatresult.st_mode)
-
-    def islink(self):
-        st = self.path.lstat()
-        return S_ISLNK(self._osstatresult.st_mode)
-
-class PosixPath(common.PathBase):
-    def chown(self, user, group, rec=0):
-        """ change ownership to the given user and group.
-            user and group may be specified by a number or
-            by a name.  if rec is True change ownership
-            recursively.
-        """
-        uid = getuserid(user)
-        gid = getgroupid(group)
-        if rec:
-            for x in self.visit(rec=lambda x: x.check(link=0)):
-                if x.check(link=0):
-                    py.error.checked_call(os.chown, str(x), uid, gid)
-        py.error.checked_call(os.chown, str(self), uid, gid)
-
-    def readlink(self):
-        """ return value of a symbolic link. """
-        return py.error.checked_call(os.readlink, self.strpath)
-
-    def mklinkto(self, oldname):
-        """ posix style hard link to another name. """
-        py.error.checked_call(os.link, str(oldname), str(self))
-
-    def mksymlinkto(self, value, absolute=1):
-        """ create a symbolic link with the given value (pointing to another name). """
-        if absolute:
-            py.error.checked_call(os.symlink, str(value), self.strpath)
-        else:
-            base = self.common(value)
-            # with posix local paths '/' is always a common base
-            relsource = self.__class__(value).relto(base)
-            reldest = self.relto(base)
-            n = reldest.count(self.sep)
-            target = self.sep.join(('..', )*n + (relsource, ))
-            py.error.checked_call(os.symlink, target, self.strpath)
-
-def getuserid(user):
-    import pwd
-    if not isinstance(user, int):
-        user = pwd.getpwnam(user)[2]
-    return user
-
-def getgroupid(group):
-    import grp
-    if not isinstance(group, int):
-        group = grp.getgrnam(group)[2]
-    return group
-
-FSBase = not iswin32 and PosixPath or common.PathBase
-
-class LocalPath(FSBase):
-    """ object oriented interface to os.path and other local filesystem
-        related information.
-    """
-    class ImportMismatchError(ImportError):
-        """ raised on pyimport() if there is a mismatch of __file__'s"""
-
-    sep = os.sep
-    class Checkers(common.Checkers):
-        def _stat(self):
-            try:
-                return self._statcache
-            except AttributeError:
-                try:
-                    self._statcache = self.path.stat()
-                except py.error.ELOOP:
-                    self._statcache = self.path.lstat()
-                return self._statcache
-
-        def dir(self):
-            return S_ISDIR(self._stat().mode)
-
-        def file(self):
-            return S_ISREG(self._stat().mode)
-
-        def exists(self):
-            return self._stat()
-
-        def link(self):
-            st = self.path.lstat()
-            return S_ISLNK(st.mode)
-
-    def __init__(self, path=None, expanduser=False):
-        """ Initialize and return a local Path instance.
-
-        Path can be relative to the current directory.
-        If path is None it defaults to the current working directory.
-        If expanduser is True, tilde-expansion is performed.
-        Note that Path instances always carry an absolute path.
-        Note also that passing in a local path object will simply return
-        the exact same path object. Use new() to get a new copy.
-        """
-        if path is None:
-            self.strpath = py.error.checked_call(os.getcwd)
-        elif isinstance(path, common.PathBase):
-            self.strpath = path.strpath
-        elif isinstance(path, py.builtin._basestring):
-            if expanduser:
-                path = os.path.expanduser(path)
-            self.strpath = abspath(path)
-        else:
-            raise ValueError("can only pass None, Path instances "
-                             "or non-empty strings to LocalPath")
-
-    def __hash__(self):
-        return hash(self.strpath)
-
-    def __eq__(self, other):
-        s1 = self.strpath
-        s2 = getattr(other, "strpath", other)
-        if iswin32:
-            s1 = s1.lower()
-            try:
-                s2 = s2.lower()
-            except AttributeError:
-                return False
-        return s1 == s2
-
-    def __ne__(self, other):
-        return not (self == other)
-
-    def __lt__(self, other):
-        return self.strpath < getattr(other, "strpath", other)
-
-    def __gt__(self, other):
-        return self.strpath > getattr(other, "strpath", other)
-
-    def samefile(self, other):
-        """ return True if 'other' references the same file as 'self'.
-        """
-        other = getattr(other, "strpath", other)
-        if not isabs(other):
-            other = abspath(other)
-        if self == other:
-            return True
-        if iswin32:
-            return False # there is no samefile
-        return py.error.checked_call(
-                os.path.samefile, self.strpath, other)
-
-    def remove(self, rec=1, ignore_errors=False):
-        """ remove a file or directory (or a directory tree if rec=1).
-        if ignore_errors is True, errors while removing directories will
-        be ignored.
-        """
-        if self.check(dir=1, link=0):
-            if rec:
-                # force remove of readonly files on windows
-                if iswin32:
-                    self.chmod(448, rec=1) # octcal 0700
-                py.error.checked_call(py.std.shutil.rmtree, self.strpath,
-                    ignore_errors=ignore_errors)
-            else:
-                py.error.checked_call(os.rmdir, self.strpath)
-        else:
-            if iswin32:
-                self.chmod(448) # octcal 0700
-            py.error.checked_call(os.remove, self.strpath)
-
-    def computehash(self, hashtype="md5", chunksize=524288):
-        """ return hexdigest of hashvalue for this file. """
-        try:
-            try:
-                import hashlib as mod
-            except ImportError:
-                if hashtype == "sha1":
-                    hashtype = "sha"
-                mod = __import__(hashtype)
-            hash = getattr(mod, hashtype)()
-        except (AttributeError, ImportError):
-            raise ValueError("Don't know how to compute %r hash" %(hashtype,))
-        f = self.open('rb')
-        try:
-            while 1:
-                buf = f.read(chunksize)
-                if not buf:
-                    return hash.hexdigest()
-                hash.update(buf)
-        finally:
-            f.close()
-
-    def new(self, **kw):
-        """ create a modified version of this path.
-            the following keyword arguments modify various path parts::
-
-              a:/some/path/to/a/file.ext
-              xx                           drive
-              xxxxxxxxxxxxxxxxx            dirname
-                                xxxxxxxx   basename
-                                xxxx       purebasename
-                                     xxx   ext
-        """
-        obj = object.__new__(self.__class__)
-        if not kw:
-            obj.strpath = self.strpath
-            return obj
-        drive, dirname, basename, purebasename,ext = self._getbyspec(
-             "drive,dirname,basename,purebasename,ext")
-        if 'basename' in kw:
-            if 'purebasename' in kw or 'ext' in kw:
-                raise ValueError("invalid specification %r" % kw)
-        else:
-            pb = kw.setdefault('purebasename', purebasename)
-            try:
-                ext = kw['ext']
-            except KeyError:
-                pass
-            else:
-                if ext and not ext.startswith('.'):
-                    ext = '.' + ext
-            kw['basename'] = pb + ext
-
-        if ('dirname' in kw and not kw['dirname']):
-            kw['dirname'] = drive
-        else:
-            kw.setdefault('dirname', dirname)
-        kw.setdefault('sep', self.sep)
-        obj.strpath = normpath(
-            "%(dirname)s%(sep)s%(basename)s" % kw)
-        return obj
-
-    def _getbyspec(self, spec):
-        """ see new for what 'spec' can be. """
-        res = []
-        parts = self.strpath.split(self.sep)
-
-        args = filter(None, spec.split(',') )
-        append = res.append
-        for name in args:
-            if name == 'drive':
-                append(parts[0])
-            elif name == 'dirname':
-                append(self.sep.join(parts[:-1]))
-            else:
-                basename = parts[-1]
-                if name == 'basename':
-                    append(basename)
-                else:
-                    i = basename.rfind('.')
-                    if i == -1:
-                        purebasename, ext = basename, ''
-                    else:
-                        purebasename, ext = basename[:i], basename[i:]
-                    if name == 'purebasename':
-                        append(purebasename)
-                    elif name == 'ext':
-                        append(ext)
-                    else:
-                        raise ValueError("invalid part specification %r" % name)
-        return res
-
-    def dirpath(self, *args, **kwargs):
-        """ return the directory path joined with any given path arguments.  """
-        if not kwargs:
-            path = object.__new__(self.__class__)
-            path.strpath = dirname(self.strpath)
-            if args:
-                path = path.join(*args)
-            return path
-        return super(LocalPath, self).dirpath(*args, **kwargs)
-
-    def join(self, *args, **kwargs):
-        """ return a new path by appending all 'args' as path
-        components.  if abs=1 is used restart from root if any
-        of the args is an absolute path.
-        """
-        sep = self.sep
-        strargs = [getattr(arg, "strpath", arg) for arg in args]
-        strpath = self.strpath
-        if kwargs.get('abs'):
-            newargs = []
-            for arg in reversed(strargs):
-                if isabs(arg):
-                    strpath = arg
-                    strargs = newargs
-                    break
-                newargs.insert(0, arg)
-        for arg in strargs:
-            arg = arg.strip(sep)
-            if iswin32:
-                # allow unix style paths even on windows.
-                arg = arg.strip('/')
-                arg = arg.replace('/', sep)
-            strpath = strpath + sep + arg
-        obj = object.__new__(self.__class__)
-        obj.strpath = normpath(strpath)
-        return obj
-
-    def open(self, mode='r', ensure=False, encoding=None):
-        """ return an opened file with the given mode.
-
-        If ensure is True, create parent directories if needed.
-        """
-        if ensure:
-            self.dirpath().ensure(dir=1)
-        if encoding:
-            return py.error.checked_call(io.open, self.strpath, mode, encoding=encoding)
-        return py.error.checked_call(open, self.strpath, mode)
-
-    def _fastjoin(self, name):
-        child = object.__new__(self.__class__)
-        child.strpath = self.strpath + self.sep + name
-        return child
-
-    def islink(self):
-        return islink(self.strpath)
-
-    def check(self, **kw):
-        if not kw:
-            return exists(self.strpath)
-        if len(kw) == 1:
-            if "dir" in kw:
-                return not kw["dir"] ^ isdir(self.strpath)
-            if "file" in kw:
-                return not kw["file"] ^ isfile(self.strpath)
-        return super(LocalPath, self).check(**kw)
-
-    _patternchars = set("*?[" + os.path.sep)
-    def listdir(self, fil=None, sort=None):
-        """ list directory contents, possibly filter by the given fil func
-            and possibly sorted.
-        """
-        if fil is None and sort is None:
-            names = py.error.checked_call(os.listdir, self.strpath)
-            return map_as_list(self._fastjoin, names)
-        if isinstance(fil, py.builtin._basestring):
-            if not self._patternchars.intersection(fil):
-                child = self._fastjoin(fil)
-                if exists(child.strpath):
-                    return [child]
-                return []
-            fil = common.FNMatcher(fil)
-        names = py.error.checked_call(os.listdir, self.strpath)
-        res = []
-        for name in names:
-            child = self._fastjoin(name)
-            if fil is None or fil(child):
-                res.append(child)
-        self._sortlist(res, sort)
-        return res
-
-    def size(self):
-        """ return size of the underlying file object """
-        return self.stat().size
-
-    def mtime(self):
-        """ return last modification time of the path. """
-        return self.stat().mtime
-
-    def copy(self, target, mode=False):
-        """ copy path to target."""
-        if self.check(file=1):
-            if target.check(dir=1):
-                target = target.join(self.basename)
-            assert self!=target
-            copychunked(self, target)
-            if mode:
-                copymode(self.strpath, target.strpath)
-        else:
-            def rec(p):
-                return p.check(link=0)
-            for x in self.visit(rec=rec):
-                relpath = x.relto(self)
-                newx = target.join(relpath)
-                newx.dirpath().ensure(dir=1)
-                if x.check(link=1):
-                    newx.mksymlinkto(x.readlink())
-                    continue
-                elif x.check(file=1):
-                    copychunked(x, newx)
-                elif x.check(dir=1):
-                    newx.ensure(dir=1)
-                if mode:
-                    copymode(x.strpath, newx.strpath)
-
-    def rename(self, target):
-        """ rename this path to target. """
-        target = getattr(target, "strpath", target)
-        return py.error.checked_call(os.rename, self.strpath, target)
-
-    def dump(self, obj, bin=1):
-        """ pickle object into path location"""
-        f = self.open('wb')
-        try:
-            py.error.checked_call(py.std.pickle.dump, obj, f, bin)
-        finally:
-            f.close()
-
-    def mkdir(self, *args):
-        """ create & return the directory joined with args. """
-        p = self.join(*args)
-        py.error.checked_call(os.mkdir, getattr(p, "strpath", p))
-        return p
-
-    def write_binary(self, data, ensure=False):
-        """ write binary data into path.   If ensure is True create
-        missing parent directories.
-        """
-        if ensure:
-            self.dirpath().ensure(dir=1)
-        with self.open('wb') as f:
-            f.write(data)
-
-    def write_text(self, data, encoding, ensure=False):
-        """ write text data into path using the specified encoding.
-        If ensure is True create missing parent directories.
-        """
-        if ensure:
-            self.dirpath().ensure(dir=1)
-        with self.open('w', encoding=encoding) as f:
-            f.write(data)
-
-    def write(self, data, mode='w', ensure=False):
-        """ write data into path.   If ensure is True create
-        missing parent directories.
-        """
-        if ensure:
-            self.dirpath().ensure(dir=1)
-        if 'b' in mode:
-            if not py.builtin._isbytes(data):
-                raise ValueError("can only process bytes")
-        else:
-            if not py.builtin._istext(data):
-                if not py.builtin._isbytes(data):
-                    data = str(data)
-                else:
-                    data = py.builtin._totext(data, sys.getdefaultencoding())
-        f = self.open(mode)
-        try:
-            f.write(data)
-        finally:
-            f.close()
-
-    def _ensuredirs(self):
-        parent = self.dirpath()
-        if parent == self:
-            return self
-        if parent.check(dir=0):
-            parent._ensuredirs()
-        if self.check(dir=0):
-            try:
-                self.mkdir()
-            except py.error.EEXIST:
-                # race condition: file/dir created by another thread/process.
-                # complain if it is not a dir
-                if self.check(dir=0):
-                    raise
-        return self
-
-    def ensure(self, *args, **kwargs):
-        """ ensure that an args-joined path exists (by default as
-            a file). if you specify a keyword argument 'dir=True'
-            then the path is forced to be a directory path.
-        """
-        p = self.join(*args)
-        if kwargs.get('dir', 0):
-            return p._ensuredirs()
-        else:
-            p.dirpath()._ensuredirs()
-            if not p.check(file=1):
-                p.open('w').close()
-            return p
-
-    def stat(self, raising=True):
-        """ Return an os.stat() tuple. """
-        if raising == True:
-            return Stat(self, py.error.checked_call(os.stat, self.strpath))
-        try:
-            return Stat(self, os.stat(self.strpath))
-        except KeyboardInterrupt:
-            raise
-        except Exception:
-            return None
-
-    def lstat(self):
-        """ Return an os.lstat() tuple. """
-        return Stat(self, py.error.checked_call(os.lstat, self.strpath))
-
-    def setmtime(self, mtime=None):
-        """ set modification time for the given path.  if 'mtime' is None
-        (the default) then the file's mtime is set to current time.
-
-        Note that the resolution for 'mtime' is platform dependent.
-        """
-        if mtime is None:
-            return py.error.checked_call(os.utime, self.strpath, mtime)
-        try:
-            return py.error.checked_call(os.utime, self.strpath, (-1, mtime))
-        except py.error.EINVAL:
-            return py.error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
-
-    def chdir(self):
-        """ change directory to self and return old current directory """
-        try:
-            old = self.__class__()
-        except py.error.ENOENT:
-            old = None
-        py.error.checked_call(os.chdir, self.strpath)
-        return old
-
-
-    @contextmanager
-    def as_cwd(self):
-        """ return context manager which changes to current dir during the
-        managed "with" context. On __enter__ it returns the old dir.
-        """
-        old = self.chdir()
-        try:
-            yield old
-        finally:
-            old.chdir()
-
-    def realpath(self):
-        """ return a new path which contains no symbolic links."""
-        return self.__class__(os.path.realpath(self.strpath))
-
-    def atime(self):
-        """ return last access time of the path. """
-        return self.stat().atime
-
-    def __repr__(self):
-        return 'local(%r)' % self.strpath
-
-    def __str__(self):
-        """ return string representation of the Path. """
-        return self.strpath
-
-    def chmod(self, mode, rec=0):
-        """ change permissions to the given mode. If mode is an
-            integer it directly encodes the os-specific modes.
-            if rec is True perform recursively.
-        """
-        if not isinstance(mode, int):
-            raise TypeError("mode %r must be an integer" % (mode,))
-        if rec:
-            for x in self.visit(rec=rec):
-                py.error.checked_call(os.chmod, str(x), mode)
-        py.error.checked_call(os.chmod, self.strpath, mode)
-
-    def pypkgpath(self):
-        """ return the Python package path by looking for the last
-        directory upwards which still contains an __init__.py.
-        Return None if a pkgpath can not be determined.
-        """
-        pkgpath = None
-        for parent in self.parts(reverse=True):
-            if parent.isdir():
-                if not parent.join('__init__.py').exists():
-                    break
-                if not isimportable(parent.basename):
-                    break
-                pkgpath = parent
-        return pkgpath
-
-    def _ensuresyspath(self, ensuremode, path):
-        if ensuremode:
-            s = str(path)
-            if ensuremode == "append":
-                if s not in sys.path:
-                    sys.path.append(s)
-            else:
-                if s != sys.path[0]:
-                    sys.path.insert(0, s)
-
-    def pyimport(self, modname=None, ensuresyspath=True):
-        """ return path as an imported python module.
-
-        If modname is None, look for the containing package
-        and construct an according module name.
-        The module will be put/looked up in sys.modules.
-        if ensuresyspath is True then the root dir for importing
-        the file (taking __init__.py files into account) will
-        be prepended to sys.path if it isn't there already.
-        If ensuresyspath=="append" the root dir will be appended
-        if it isn't already contained in sys.path.
-        if ensuresyspath is False no modification of syspath happens.
-        """
-        if not self.check():
-            raise py.error.ENOENT(self)
-
-        pkgpath = None
-        if modname is None:
-            pkgpath = self.pypkgpath()
-            if pkgpath is not None:
-                pkgroot = pkgpath.dirpath()
-                names = self.new(ext="").relto(pkgroot).split(self.sep)
-                if names[-1] == "__init__":
-                    names.pop()
-                modname = ".".join(names)
-            else:
-                pkgroot = self.dirpath()
-                modname = self.purebasename
-
-            self._ensuresyspath(ensuresyspath, pkgroot)
-            __import__(modname)
-            mod = sys.modules[modname]
-            if self.basename == "__init__.py":
-                return mod # we don't check anything as we might
-                       # we in a namespace package ... too icky to check
-            modfile = mod.__file__
-            if modfile[-4:] in ('.pyc', '.pyo'):
-                modfile = modfile[:-1]
-            elif modfile.endswith('$py.class'):
-                modfile = modfile[:-9] + '.py'
-            if modfile.endswith(os.path.sep + "__init__.py"):
-                if self.basename != "__init__.py":
-                    modfile = modfile[:-12]
-            try:
-                issame = self.samefile(modfile)
-            except py.error.ENOENT:
-                issame = False
-            if not issame:
-                raise self.ImportMismatchError(modname, modfile, self)
-            return mod
-        else:
-            try:
-                return sys.modules[modname]
-            except KeyError:
-                # we have a custom modname, do a pseudo-import
-                mod = py.std.types.ModuleType(modname)
-                mod.__file__ = str(self)
-                sys.modules[modname] = mod
-                try:
-                    py.builtin.execfile(str(self), mod.__dict__)
-                except:
-                    del sys.modules[modname]
-                    raise
-                return mod
-
-    def sysexec(self, *argv, **popen_opts):
-        """ return stdout text from executing a system child process,
-            where the 'self' path points to executable.
-            The process is directly invoked and not through a system shell.
-        """
-        from subprocess import Popen, PIPE
-        argv = map_as_list(str, argv)
-        popen_opts['stdout'] = popen_opts['stderr'] = PIPE
-        proc = Popen([str(self)] + argv, **popen_opts)
-        stdout, stderr = proc.communicate()
-        ret = proc.wait()
-        if py.builtin._isbytes(stdout):
-            stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
-        if ret != 0:
-            if py.builtin._isbytes(stderr):
-                stderr = py.builtin._totext(stderr, sys.getdefaultencoding())
-            raise py.process.cmdexec.Error(ret, ret, str(self),
-                                           stdout, stderr,)
-        return stdout
-
-    def sysfind(cls, name, checker=None, paths=None):
-        """ return a path object found by looking at the systems
-            underlying PATH specification. If the checker is not None
-            it will be invoked to filter matching paths.  If a binary
-            cannot be found, None is returned
-            Note: This is probably not working on plain win32 systems
-            but may work on cygwin.
-        """
-        if isabs(name):
-            p = py.path.local(name)
-            if p.check(file=1):
-                return p
-        else:
-            if paths is None:
-                if iswin32:
-                    paths = py.std.os.environ['Path'].split(';')
-                    if '' not in paths and '.' not in paths:
-                        paths.append('.')
-                    try:
-                        systemroot = os.environ['SYSTEMROOT']
-                    except KeyError:
-                        pass
-                    else:
-                        paths = [re.sub('%SystemRoot%', systemroot, path)
-                                 for path in paths]
-                else:
-                    paths = py.std.os.environ['PATH'].split(':')
-            tryadd = []
-            if iswin32:
-                tryadd += os.environ['PATHEXT'].split(os.pathsep)
-            tryadd.append("")
-
-            for x in paths:
-                for addext in tryadd:
-                    p = py.path.local(x).join(name, abs=True) + addext
-                    try:
-                        if p.check(file=1):
-                            if checker:
-                                if not checker(p):
-                                    continue
-                            return p
-                    except py.error.EACCES:
-                        pass
-        return None
-    sysfind = classmethod(sysfind)
-
-    def _gethomedir(cls):
-        try:
-            x = os.environ['HOME']
-        except KeyError:
-            try:
-                x = os.environ["HOMEDRIVE"] + os.environ['HOMEPATH']
-            except KeyError:
-                return None
-        return cls(x)
-    _gethomedir = classmethod(_gethomedir)
-
-    #"""
-    #special class constructors for local filesystem paths
-    #"""
-    def get_temproot(cls):
-        """ return the system's temporary directory
-            (where tempfiles are usually created in)
-        """
-        return py.path.local(py.std.tempfile.gettempdir())
-    get_temproot = classmethod(get_temproot)
-
-    def mkdtemp(cls, rootdir=None):
-        """ return a Path object pointing to a fresh new temporary directory
-            (which we created ourself).
-        """
-        import tempfile
-        if rootdir is None:
-            rootdir = cls.get_temproot()
-        return cls(py.error.checked_call(tempfile.mkdtemp, dir=str(rootdir)))
-    mkdtemp = classmethod(mkdtemp)
-
-    def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3,
-                          lock_timeout = 172800):   # two days
-        """ return unique directory with a number greater than the current
-            maximum one.  The number is assumed to start directly after prefix.
-            if keep is true directories with a number less than (maxnum-keep)
-            will be removed.
-        """
-        if rootdir is None:
-            rootdir = cls.get_temproot()
-
-        def parse_num(path):
-            """ parse the number out of a path (if it matches the prefix) """
-            bn = path.basename
-            if bn.startswith(prefix):
-                try:
-                    return int(bn[len(prefix):])
-                except ValueError:
-                    pass
-
-        # compute the maximum number currently in use with the
-        # prefix
-        lastmax = None
-        while True:
-            maxnum = -1
-            for path in rootdir.listdir():
-                num = parse_num(path)
-                if num is not None:
-                    maxnum = max(maxnum, num)
-
-            # make the new directory
-            try:
-                udir = rootdir.mkdir(prefix + str(maxnum+1))
-            except py.error.EEXIST:
-                # race condition: another thread/process created the dir
-                # in the meantime.  Try counting again
-                if lastmax == maxnum:
-                    raise
-                lastmax = maxnum
-                continue
-            break
-
-        # put a .lock file in the new directory that will be removed at
-        # process exit
-        if lock_timeout:
-            lockfile = udir.join('.lock')
-            mypid = os.getpid()
-            if hasattr(lockfile, 'mksymlinkto'):
-                lockfile.mksymlinkto(str(mypid))
-            else:
-                lockfile.write(str(mypid))
-            def try_remove_lockfile():
-                # in a fork() situation, only the last process should
-                # remove the .lock, otherwise the other processes run the
-                # risk of seeing their temporary dir disappear.  For now
-                # we remove the .lock in the parent only (i.e. we assume
-                # that the children finish before the parent).
-                if os.getpid() != mypid:
-                    return
-                try:
-                    lockfile.remove()
-                except py.error.Error:
-                    pass
-            atexit.register(try_remove_lockfile)
-
-        # prune old directories
-        if keep:
-            for path in rootdir.listdir():
-                num = parse_num(path)
-                if num is not None and num <= (maxnum - keep):
-                    lf = path.join('.lock')
-                    try:
-                        t1 = lf.lstat().mtime
-                        t2 = lockfile.lstat().mtime
-                        if not lock_timeout or abs(t2-t1) < lock_timeout:
-                            continue   # skip directories still locked
-                    except py.error.Error:
-                        pass   # assume that it means that there is no 'lf'
-                    try:
-                        path.remove(rec=1)
-                    except KeyboardInterrupt:
-                        raise
-                    except: # this might be py.error.Error, WindowsError ...
-                        pass
-
-        # make link...
-        try:
-            username = os.environ['USER']           #linux, et al
-        except KeyError:
-            try:
-                username = os.environ['USERNAME']   #windows
-            except KeyError:
-                username = 'current'
-
-        src  = str(udir)
-        dest = src[:src.rfind('-')] + '-' + username
-        try:
-            os.unlink(dest)
-        except OSError:
-            pass
-        try:
-            os.symlink(src, dest)
-        except (OSError, AttributeError, NotImplementedError):
-            pass
-
-        return udir
-    make_numbered_dir = classmethod(make_numbered_dir)
-
-def copymode(src, dest):
-    py.std.shutil.copymode(src, dest)
-
-def copychunked(src, dest):
-    chunksize = 524288 # half a meg of bytes
-    fsrc = src.open('rb')
-    try:
-        fdest = dest.open('wb')
-        try:
-            while 1:
-                buf = fsrc.read(chunksize)
-                if not buf:
-                    break
-                fdest.write(buf)
-        finally:
-            fdest.close()
-    finally:
-        fsrc.close()
-
-def isimportable(name):
-    if name and (name[0].isalpha() or name[0] == '_'):
-        name = name.replace("_", '')
-        return not name or name.isalnum()
diff --git a/tools/py/py/_path/svnurl.py b/tools/py/py/_path/svnurl.py
deleted file mode 100644
index 78d7131..0000000
--- a/tools/py/py/_path/svnurl.py
+++ /dev/null
@@ -1,380 +0,0 @@
-"""
-module defining a subversion path object based on the external
-command 'svn'. This modules aims to work with svn 1.3 and higher
-but might also interact well with earlier versions.
-"""
-
-import os, sys, time, re
-import py
-from py import path, process
-from py._path import common
-from py._path import svnwc as svncommon
-from py._path.cacheutil import BuildcostAccessCache, AgingCache
-
-DEBUG=False
-
-class SvnCommandPath(svncommon.SvnPathBase):
-    """ path implementation that offers access to (possibly remote) subversion
-    repositories. """
-
-    _lsrevcache = BuildcostAccessCache(maxentries=128)
-    _lsnorevcache = AgingCache(maxentries=1000, maxseconds=60.0)
-
-    def __new__(cls, path, rev=None, auth=None):
-        self = object.__new__(cls)
-        if isinstance(path, cls):
-            rev = path.rev
-            auth = path.auth
-            path = path.strpath
-        svncommon.checkbadchars(path)
-        path = path.rstrip('/')
-        self.strpath = path
-        self.rev = rev
-        self.auth = auth
-        return self
-
-    def __repr__(self):
-        if self.rev == -1:
-            return 'svnurl(%r)' % self.strpath
-        else:
-            return 'svnurl(%r, %r)' % (self.strpath, self.rev)
-
-    def _svnwithrev(self, cmd, *args):
-        """ execute an svn command, append our own url and revision """
-        if self.rev is None:
-            return self._svnwrite(cmd, *args)
-        else:
-            args = ['-r', self.rev] + list(args)
-            return self._svnwrite(cmd, *args)
-
-    def _svnwrite(self, cmd, *args):
-        """ execute an svn command, append our own url """
-        l = ['svn %s' % cmd]
-        args = ['"%s"' % self._escape(item) for item in args]
-        l.extend(args)
-        l.append('"%s"' % self._encodedurl())
-        # fixing the locale because we can't otherwise parse
-        string = " ".join(l)
-        if DEBUG:
-            print("execing %s" % string)
-        out = self._svncmdexecauth(string)
-        return out
-
-    def _svncmdexecauth(self, cmd):
-        """ execute an svn command 'as is' """
-        cmd = svncommon.fixlocale() + cmd
-        if self.auth is not None:
-            cmd += ' ' + self.auth.makecmdoptions()
-        return self._cmdexec(cmd)
-
-    def _cmdexec(self, cmd):
-        try:
-            out = process.cmdexec(cmd)
-        except py.process.cmdexec.Error:
-            e = sys.exc_info()[1]
-            if (e.err.find('File Exists') != -1 or
-                            e.err.find('File already exists') != -1):
-                raise py.error.EEXIST(self)
-            raise
-        return out
-
-    def _svnpopenauth(self, cmd):
-        """ execute an svn command, return a pipe for reading stdin """
-        cmd = svncommon.fixlocale() + cmd
-        if self.auth is not None:
-            cmd += ' ' + self.auth.makecmdoptions()
-        return self._popen(cmd)
-
-    def _popen(self, cmd):
-        return os.popen(cmd)
-
-    def _encodedurl(self):
-        return self._escape(self.strpath)
-
-    def _norev_delentry(self, path):
-        auth = self.auth and self.auth.makecmdoptions() or None
-        self._lsnorevcache.delentry((str(path), auth))
-
-    def open(self, mode='r'):
-        """ return an opened file with the given mode. """
-        if mode not in ("r", "rU",):
-            raise ValueError("mode %r not supported" % (mode,))
-        assert self.check(file=1) # svn cat returns an empty file otherwise
-        if self.rev is None:
-            return self._svnpopenauth('svn cat "%s"' % (
-                                      self._escape(self.strpath), ))
-        else:
-            return self._svnpopenauth('svn cat -r %s "%s"' % (
-                                      self.rev, self._escape(self.strpath)))
-
-    def dirpath(self, *args, **kwargs):
-        """ return the directory path of the current path joined
-            with any given path arguments.
-        """
-        l = self.strpath.split(self.sep)
-        if len(l) < 4:
-            raise py.error.EINVAL(self, "base is not valid")
-        elif len(l) == 4:
-            return self.join(*args, **kwargs)
-        else:
-            return self.new(basename='').join(*args, **kwargs)
-
-    # modifying methods (cache must be invalidated)
-    def mkdir(self, *args, **kwargs):
-        """ create & return the directory joined with args.
-        pass a 'msg' keyword argument to set the commit message.
-        """
-        commit_msg = kwargs.get('msg', "mkdir by py lib invocation")
-        createpath = self.join(*args)
-        createpath._svnwrite('mkdir', '-m', commit_msg)
-        self._norev_delentry(createpath.dirpath())
-        return createpath
-
-    def copy(self, target, msg='copied by py lib invocation'):
-        """ copy path to target with checkin message msg."""
-        if getattr(target, 'rev', None) is not None:
-            raise py.error.EINVAL(target, "revisions are immutable")
-        self._svncmdexecauth('svn copy -m "%s" "%s" "%s"' %(msg,
-                             self._escape(self), self._escape(target)))
-        self._norev_delentry(target.dirpath())
-
-    def rename(self, target, msg="renamed by py lib invocation"):
-        """ rename this path to target with checkin message msg. """
-        if getattr(self, 'rev', None) is not None:
-            raise py.error.EINVAL(self, "revisions are immutable")
-        self._svncmdexecauth('svn move -m "%s" --force "%s" "%s"' %(
-                             msg, self._escape(self), self._escape(target)))
-        self._norev_delentry(self.dirpath())
-        self._norev_delentry(self)
-
-    def remove(self, rec=1, msg='removed by py lib invocation'):
-        """ remove a file or directory (or a directory tree if rec=1) with
-checkin message msg."""
-        if self.rev is not None:
-            raise py.error.EINVAL(self, "revisions are immutable")
-        self._svncmdexecauth('svn rm -m "%s" "%s"' %(msg, self._escape(self)))
-        self._norev_delentry(self.dirpath())
-
-    def export(self, topath):
-        """ export to a local path
-
-            topath should not exist prior to calling this, returns a
-            py.path.local instance
-        """
-        topath = py.path.local(topath)
-        args = ['"%s"' % (self._escape(self),),
-                '"%s"' % (self._escape(topath),)]
-        if self.rev is not None:
-            args = ['-r', str(self.rev)] + args
-        self._svncmdexecauth('svn export %s' % (' '.join(args),))
-        return topath
-
-    def ensure(self, *args, **kwargs):
-        """ ensure that an args-joined path exists (by default as
-            a file). If you specify a keyword argument 'dir=True'
-            then the path is forced to be a directory path.
-        """
-        if getattr(self, 'rev', None) is not None:
-            raise py.error.EINVAL(self, "revisions are immutable")
-        target = self.join(*args)
-        dir = kwargs.get('dir', 0)
-        for x in target.parts(reverse=True):
-            if x.check():
-                break
-        else:
-            raise py.error.ENOENT(target, "has not any valid base!")
-        if x == target:
-            if not x.check(dir=dir):
-                raise dir and py.error.ENOTDIR(x) or py.error.EISDIR(x)
-            return x
-        tocreate = target.relto(x)
-        basename = tocreate.split(self.sep, 1)[0]
-        tempdir = py.path.local.mkdtemp()
-        try:
-            tempdir.ensure(tocreate, dir=dir)
-            cmd = 'svn import -m "%s" "%s" "%s"' % (
-                    "ensure %s" % self._escape(tocreate),
-                    self._escape(tempdir.join(basename)),
-                    x.join(basename)._encodedurl())
-            self._svncmdexecauth(cmd)
-            self._norev_delentry(x)
-        finally:
-            tempdir.remove()
-        return target
-
-    # end of modifying methods
-    def _propget(self, name):
-        res = self._svnwithrev('propget', name)
-        return res[:-1] # strip trailing newline
-
-    def _proplist(self):
-        res = self._svnwithrev('proplist')
-        lines = res.split('\n')
-        lines = [x.strip() for x in lines[1:]]
-        return svncommon.PropListDict(self, lines)
-
-    def info(self):
-        """ return an Info structure with svn-provided information. """
-        parent = self.dirpath()
-        nameinfo_seq = parent._listdir_nameinfo()
-        bn = self.basename
-        for name, info in nameinfo_seq:
-            if name == bn:
-                return info
-        raise py.error.ENOENT(self)
-
-
-    def _listdir_nameinfo(self):
-        """ return sequence of name-info directory entries of self """
-        def builder():
-            try:
-                res = self._svnwithrev('ls', '-v')
-            except process.cmdexec.Error:
-                e = sys.exc_info()[1]
-                if e.err.find('non-existent in that revision') != -1:
-                    raise py.error.ENOENT(self, e.err)
-                elif e.err.find("E200009:") != -1:
-                    raise py.error.ENOENT(self, e.err)
-                elif e.err.find('File not found') != -1:
-                    raise py.error.ENOENT(self, e.err)
-                elif e.err.find('not part of a repository')!=-1:
-                    raise py.error.ENOENT(self, e.err)
-                elif e.err.find('Unable to open')!=-1:
-                    raise py.error.ENOENT(self, e.err)
-                elif e.err.lower().find('method not allowed')!=-1:
-                    raise py.error.EACCES(self, e.err)
-                raise py.error.Error(e.err)
-            lines = res.split('\n')
-            nameinfo_seq = []
-            for lsline in lines:
-                if lsline:
-                    info = InfoSvnCommand(lsline)
-                    if info._name != '.':  # svn 1.5 produces '.' dirs,
-                        nameinfo_seq.append((info._name, info))
-            nameinfo_seq.sort()
-            return nameinfo_seq
-        auth = self.auth and self.auth.makecmdoptions() or None
-        if self.rev is not None:
-            return self._lsrevcache.getorbuild((self.strpath, self.rev, auth),
-                                               builder)
-        else:
-            return self._lsnorevcache.getorbuild((self.strpath, auth),
-                                                 builder)
-
-    def listdir(self, fil=None, sort=None):
-        """ list directory contents, possibly filter by the given fil func
-            and possibly sorted.
-        """
-        if isinstance(fil, str):
-            fil = common.FNMatcher(fil)
-        nameinfo_seq = self._listdir_nameinfo()
-        if len(nameinfo_seq) == 1:
-            name, info = nameinfo_seq[0]
-            if name == self.basename and info.kind == 'file':
-                #if not self.check(dir=1):
-                raise py.error.ENOTDIR(self)
-        paths = [self.join(name) for (name, info) in nameinfo_seq]
-        if fil:
-            paths = [x for x in paths if fil(x)]
-        self._sortlist(paths, sort)
-        return paths
-
-
-    def log(self, rev_start=None, rev_end=1, verbose=False):
-        """ return a list of LogEntry instances for this path.
-rev_start is the starting revision (defaulting to the first one).
-rev_end is the last revision (defaulting to HEAD).
-if verbose is True, then the LogEntry instances also know which files changed.
-"""
-        assert self.check() #make it simpler for the pipe
-        rev_start = rev_start is None and "HEAD" or rev_start
-        rev_end = rev_end is None and "HEAD" or rev_end
-
-        if rev_start == "HEAD" and rev_end == 1:
-            rev_opt = ""
-        else:
-            rev_opt = "-r %s:%s" % (rev_start, rev_end)
-        verbose_opt = verbose and "-v" or ""
-        xmlpipe =  self._svnpopenauth('svn log --xml %s %s "%s"' %
-                                      (rev_opt, verbose_opt, self.strpath))
-        from xml.dom import minidom
-        tree = minidom.parse(xmlpipe)
-        result = []
-        for logentry in filter(None, tree.firstChild.childNodes):
-            if logentry.nodeType == logentry.ELEMENT_NODE:
-                result.append(svncommon.LogEntry(logentry))
-        return result
-
-#01234567890123456789012345678901234567890123467
-#   2256      hpk        165 Nov 24 17:55 __init__.py
-# XXX spotted by Guido, SVN 1.3.0 has different aligning, breaks the code!!!
-#   1312 johnny           1627 May 05 14:32 test_decorators.py
-#
-class InfoSvnCommand:
-    # the '0?' part in the middle is an indication of whether the resource is
-    # locked, see 'svn help ls'
-    lspattern = re.compile(
-        r'^ *(?P<rev>\d+) +(?P<author>.+?) +(0? *(?P<size>\d+))? '
-            '*(?P<date>\w+ +\d{2} +[\d:]+) +(?P<file>.*)$')
-    def __init__(self, line):
-        # this is a typical line from 'svn ls http://...'
-        #_    1127      jum        0 Jul 13 15:28 branch/
-        match = self.lspattern.match(line)
-        data = match.groupdict()
-        self._name = data['file']
-        if self._name[-1] == '/':
-            self._name = self._name[:-1]
-            self.kind = 'dir'
-        else:
-            self.kind = 'file'
-        #self.has_props = l.pop(0) == 'P'
-        self.created_rev = int(data['rev'])
-        self.last_author = data['author']
-        self.size = data['size'] and int(data['size']) or 0
-        self.mtime = parse_time_with_missing_year(data['date'])
-        self.time = self.mtime * 1000000
-
-    def __eq__(self, other):
-        return self.__dict__ == other.__dict__
-
-
-#____________________________________________________
-#
-# helper functions
-#____________________________________________________
-def parse_time_with_missing_year(timestr):
-    """ analyze the time part from a single line of "svn ls -v"
-    the svn output doesn't show the year makes the 'timestr'
-    ambigous.
-    """
-    import calendar
-    t_now = time.gmtime()
-
-    tparts = timestr.split()
-    month = time.strptime(tparts.pop(0), '%b')[1]
-    day = time.strptime(tparts.pop(0), '%d')[2]
-    last = tparts.pop(0) # year or hour:minute
-    try:
-        if ":" in last:
-            raise ValueError()
-        year = time.strptime(last, '%Y')[0]
-        hour = minute = 0
-    except ValueError:
-        hour, minute = time.strptime(last, '%H:%M')[3:5]
-        year = t_now[0]
-
-        t_result = (year, month, day, hour, minute, 0,0,0,0)
-        if t_result > t_now:
-            year -= 1
-    t_result = (year, month, day, hour, minute, 0,0,0,0)
-    return calendar.timegm(t_result)
-
-class PathEntry:
-    def __init__(self, ppart):
-        self.strpath = ppart.firstChild.nodeValue.encode('UTF-8')
-        self.action = ppart.getAttribute('action').encode('UTF-8')
-        if self.action == 'A':
-            self.copyfrom_path = ppart.getAttribute('copyfrom-path').encode('UTF-8')
-            if self.copyfrom_path:
-                self.copyfrom_rev = int(ppart.getAttribute('copyfrom-rev'))
-
diff --git a/tools/py/py/_path/svnwc.py b/tools/py/py/_path/svnwc.py
deleted file mode 100644
index 00d3b4bba..0000000
--- a/tools/py/py/_path/svnwc.py
+++ /dev/null
@@ -1,1240 +0,0 @@
-"""
-svn-Command based Implementation of a Subversion WorkingCopy Path.
-
-  SvnWCCommandPath  is the main class.
-
-"""
-
-import os, sys, time, re, calendar
-import py
-import subprocess
-from py._path import common
-
-#-----------------------------------------------------------
-# Caching latest repository revision and repo-paths
-# (getting them is slow with the current implementations)
-#
-# XXX make mt-safe
-#-----------------------------------------------------------
-
-class cache:
-    proplist = {}
-    info = {}
-    entries = {}
-    prop = {}
-
-class RepoEntry:
-    def __init__(self, url, rev, timestamp):
-        self.url = url
-        self.rev = rev
-        self.timestamp = timestamp
-
-    def __str__(self):
-        return "repo: %s;%s  %s" %(self.url, self.rev, self.timestamp)
-
-class RepoCache:
-    """ The Repocache manages discovered repository paths
-    and their revisions.  If inside a timeout the cache
-    will even return the revision of the root.
-    """
-    timeout = 20 # seconds after which we forget that we know the last revision
-
-    def __init__(self):
-        self.repos = []
-
-    def clear(self):
-        self.repos = []
-
-    def put(self, url, rev, timestamp=None):
-        if rev is None:
-            return
-        if timestamp is None:
-            timestamp = time.time()
-
-        for entry in self.repos:
-            if url == entry.url:
-                entry.timestamp = timestamp
-                entry.rev = rev
-                #print "set repo", entry
-                break
-        else:
-            entry = RepoEntry(url, rev, timestamp)
-            self.repos.append(entry)
-            #print "appended repo", entry
-
-    def get(self, url):
-        now = time.time()
-        for entry in self.repos:
-            if url.startswith(entry.url):
-                if now < entry.timestamp + self.timeout:
-                    #print "returning immediate Etrny", entry
-                    return entry.url, entry.rev
-                return entry.url, -1
-        return url, -1
-
-repositories = RepoCache()
-
-
-# svn support code
-
-ALLOWED_CHARS = "_ -/\\=$.~+%" #add characters as necessary when tested
-if sys.platform == "win32":
-    ALLOWED_CHARS += ":"
-ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:'
-
-def _getsvnversion(ver=[]):
-    try:
-        return ver[0]
-    except IndexError:
-        v = py.process.cmdexec("svn -q --version")
-        v.strip()
-        v = '.'.join(v.split('.')[:2])
-        ver.append(v)
-        return v
-
-def _escape_helper(text):
-    text = str(text)
-    if py.std.sys.platform != 'win32':
-        text = str(text).replace('$', '\\$')
-    return text
-
-def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS):
-    for c in str(text):
-        if c.isalnum():
-            continue
-        if c in allowed_chars:
-            continue
-        return True
-    return False
-
-def checkbadchars(url):
-    # (hpk) not quite sure about the exact purpose, guido w.?
-    proto, uri = url.split("://", 1)
-    if proto != "file":
-        host, uripath = uri.split('/', 1)
-        # only check for bad chars in the non-protocol parts
-        if (_check_for_bad_chars(host, ALLOWED_CHARS_HOST) \
-            or _check_for_bad_chars(uripath, ALLOWED_CHARS)):
-            raise ValueError("bad char in %r" % (url, ))
-
-
-#_______________________________________________________________
-
-class SvnPathBase(common.PathBase):
-    """ Base implementation for SvnPath implementations. """
-    sep = '/'
-
-    def _geturl(self):
-        return self.strpath
-    url = property(_geturl, None, None, "url of this svn-path.")
-
-    def __str__(self):
-        """ return a string representation (including rev-number) """
-        return self.strpath
-
-    def __hash__(self):
-        return hash(self.strpath)
-
-    def new(self, **kw):
-        """ create a modified version of this path. A 'rev' argument
-            indicates a new revision.
-            the following keyword arguments modify various path parts::
-
-              http://host.com/repo/path/file.ext
-              |-----------------------|          dirname
-                                        |------| basename
-                                        |--|     purebasename
-                                            |--| ext
-        """
-        obj = object.__new__(self.__class__)
-        obj.rev = kw.get('rev', self.rev)
-        obj.auth = kw.get('auth', self.auth)
-        dirname, basename, purebasename, ext = self._getbyspec(
-             "dirname,basename,purebasename,ext")
-        if 'basename' in kw:
-            if 'purebasename' in kw or 'ext' in kw:
-                raise ValueError("invalid specification %r" % kw)
-        else:
-            pb = kw.setdefault('purebasename', purebasename)
-            ext = kw.setdefault('ext', ext)
-            if ext and not ext.startswith('.'):
-                ext = '.' + ext
-            kw['basename'] = pb + ext
-
-        kw.setdefault('dirname', dirname)
-        kw.setdefault('sep', self.sep)
-        if kw['basename']:
-            obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw
-        else:
-            obj.strpath = "%(dirname)s" % kw
-        return obj
-
-    def _getbyspec(self, spec):
-        """ get specified parts of the path.  'arg' is a string
-            with comma separated path parts. The parts are returned
-            in exactly the order of the specification.
-
-            you may specify the following parts:
-
-            http://host.com/repo/path/file.ext
-            |-----------------------|          dirname
-                                      |------| basename
-                                      |--|     purebasename
-                                          |--| ext
-        """
-        res = []
-        parts = self.strpath.split(self.sep)
-        for name in spec.split(','):
-            name = name.strip()
-            if name == 'dirname':
-                res.append(self.sep.join(parts[:-1]))
-            elif name == 'basename':
-                res.append(parts[-1])
-            else:
-                basename = parts[-1]
-                i = basename.rfind('.')
-                if i == -1:
-                    purebasename, ext = basename, ''
-                else:
-                    purebasename, ext = basename[:i], basename[i:]
-                if name == 'purebasename':
-                    res.append(purebasename)
-                elif name == 'ext':
-                    res.append(ext)
-                else:
-                    raise NameError("Don't know part %r" % name)
-        return res
-
-    def __eq__(self, other):
-        """ return true if path and rev attributes each match """
-        return (str(self) == str(other) and
-               (self.rev == other.rev or self.rev == other.rev))
-
-    def __ne__(self, other):
-        return not self == other
-
-    def join(self, *args):
-        """ return a new Path (with the same revision) which is composed
-            of the self Path followed by 'args' path components.
-        """
-        if not args:
-            return self
-
-        args = tuple([arg.strip(self.sep) for arg in args])
-        parts = (self.strpath, ) + args
-        newpath = self.__class__(self.sep.join(parts), self.rev, self.auth)
-        return newpath
-
-    def propget(self, name):
-        """ return the content of the given property. """
-        value = self._propget(name)
-        return value
-
-    def proplist(self):
-        """ list all property names. """
-        content = self._proplist()
-        return content
-
-    def size(self):
-        """ Return the size of the file content of the Path. """
-        return self.info().size
-
-    def mtime(self):
-        """ Return the last modification time of the file. """
-        return self.info().mtime
-
-    # shared help methods
-
-    def _escape(self, cmd):
-        return _escape_helper(cmd)
-
-
-    #def _childmaxrev(self):
-    #    """ return maximum revision number of childs (or self.rev if no childs) """
-    #    rev = self.rev
-    #    for name, info in self._listdir_nameinfo():
-    #        rev = max(rev, info.created_rev)
-    #    return rev
-
-    #def _getlatestrevision(self):
-    #    """ return latest repo-revision for this path. """
-    #    url = self.strpath
-    #    path = self.__class__(url, None)
-    #
-    #    # we need a long walk to find the root-repo and revision
-    #    while 1:
-    #        try:
-    #            rev = max(rev, path._childmaxrev())
-    #            previous = path
-    #            path = path.dirpath()
-    #        except (IOError, process.cmdexec.Error):
-    #            break
-    #    if rev is None:
-    #        raise IOError, "could not determine newest repo revision for %s" % self
-    #    return rev
-
-    class Checkers(common.Checkers):
-        def dir(self):
-            try:
-                return self.path.info().kind == 'dir'
-            except py.error.Error:
-                return self._listdirworks()
-
-        def _listdirworks(self):
-            try:
-                self.path.listdir()
-            except py.error.ENOENT:
-                return False
-            else:
-                return True
-
-        def file(self):
-            try:
-                return self.path.info().kind == 'file'
-            except py.error.ENOENT:
-                return False
-
-        def exists(self):
-            try:
-                return self.path.info()
-            except py.error.ENOENT:
-                return self._listdirworks()
-
-def parse_apr_time(timestr):
-    i = timestr.rfind('.')
-    if i == -1:
-        raise ValueError("could not parse %s" % timestr)
-    timestr = timestr[:i]
-    parsedtime = time.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
-    return time.mktime(parsedtime)
-
-class PropListDict(dict):
-    """ a Dictionary which fetches values (InfoSvnCommand instances) lazily"""
-    def __init__(self, path, keynames):
-        dict.__init__(self, [(x, None) for x in keynames])
-        self.path = path
-
-    def __getitem__(self, key):
-        value = dict.__getitem__(self, key)
-        if value is None:
-            value = self.path.propget(key)
-            dict.__setitem__(self, key, value)
-        return value
-
-def fixlocale():
-    if sys.platform != 'win32':
-        return 'LC_ALL=C '
-    return ''
-
-# some nasty chunk of code to solve path and url conversion and quoting issues
-ILLEGAL_CHARS = '* | \ / : < > ? \t \n \x0b \x0c \r'.split(' ')
-if os.sep in ILLEGAL_CHARS:
-    ILLEGAL_CHARS.remove(os.sep)
-ISWINDOWS = sys.platform == 'win32'
-_reg_allow_disk = re.compile(r'^([a-z]\:\\)?[^:]+$', re.I)
-def _check_path(path):
-    illegal = ILLEGAL_CHARS[:]
-    sp = path.strpath
-    if ISWINDOWS:
-        illegal.remove(':')
-        if not _reg_allow_disk.match(sp):
-            raise ValueError('path may not contain a colon (:)')
-    for char in sp:
-        if char not in string.printable or char in illegal:
-            raise ValueError('illegal character %r in path' % (char,))
-
-def path_to_fspath(path, addat=True):
-    _check_path(path)
-    sp = path.strpath
-    if addat and path.rev != -1:
-        sp = '%s@%s' % (sp, path.rev)
-    elif addat:
-        sp = '%s@HEAD' % (sp,)
-    return sp
-
-def url_from_path(path):
-    fspath = path_to_fspath(path, False)
-    quote = py.std.urllib.quote
-    if ISWINDOWS:
-        match = _reg_allow_disk.match(fspath)
-        fspath = fspath.replace('\\', '/')
-        if match.group(1):
-            fspath = '/%s%s' % (match.group(1).replace('\\', '/'),
-                                quote(fspath[len(match.group(1)):]))
-        else:
-            fspath = quote(fspath)
-    else:
-        fspath = quote(fspath)
-    if path.rev != -1:
-        fspath = '%s@%s' % (fspath, path.rev)
-    else:
-        fspath = '%s@HEAD' % (fspath,)
-    return 'file://%s' % (fspath,)
-
-class SvnAuth(object):
-    """ container for auth information for Subversion """
-    def __init__(self, username, password, cache_auth=True, interactive=True):
-        self.username = username
-        self.password = password
-        self.cache_auth = cache_auth
-        self.interactive = interactive
-
-    def makecmdoptions(self):
-        uname = self.username.replace('"', '\\"')
-        passwd = self.password.replace('"', '\\"')
-        ret = []
-        if uname:
-            ret.append('--username="%s"' % (uname,))
-        if passwd:
-            ret.append('--password="%s"' % (passwd,))
-        if not self.cache_auth:
-            ret.append('--no-auth-cache')
-        if not self.interactive:
-            ret.append('--non-interactive')
-        return ' '.join(ret)
-
-    def __str__(self):
-        return "<SvnAuth username=%s ...>" %(self.username,)
-
-rex_blame = re.compile(r'\s*(\d+)\s*(\S+) (.*)')
-
-class SvnWCCommandPath(common.PathBase):
-    """ path implementation offering access/modification to svn working copies.
-        It has methods similar to the functions in os.path and similar to the
-        commands of the svn client.
-    """
-    sep = os.sep
-
-    def __new__(cls, wcpath=None, auth=None):
-        self = object.__new__(cls)
-        if isinstance(wcpath, cls):
-            if wcpath.__class__ == cls:
-                return wcpath
-            wcpath = wcpath.localpath
-        if _check_for_bad_chars(str(wcpath),
-                                          ALLOWED_CHARS):
-            raise ValueError("bad char in wcpath %s" % (wcpath, ))
-        self.localpath = py.path.local(wcpath)
-        self.auth = auth
-        return self
-
-    strpath = property(lambda x: str(x.localpath), None, None, "string path")
-    rev = property(lambda x: x.info(usecache=0).rev, None, None, "revision")
-
-    def __eq__(self, other):
-        return self.localpath == getattr(other, 'localpath', None)
-
-    def _geturl(self):
-        if getattr(self, '_url', None) is None:
-            info = self.info()
-            self._url = info.url #SvnPath(info.url, info.rev)
-        assert isinstance(self._url, py.builtin._basestring)
-        return self._url
-
-    url = property(_geturl, None, None, "url of this WC item")
-
-    def _escape(self, cmd):
-        return _escape_helper(cmd)
-
-    def dump(self, obj):
-        """ pickle object into path location"""
-        return self.localpath.dump(obj)
-
-    def svnurl(self):
-        """ return current SvnPath for this WC-item. """
-        info = self.info()
-        return py.path.svnurl(info.url)
-
-    def __repr__(self):
-        return "svnwc(%r)" % (self.strpath) # , self._url)
-
-    def __str__(self):
-        return str(self.localpath)
-
-    def _makeauthoptions(self):
-        if self.auth is None:
-            return ''
-        return self.auth.makecmdoptions()
-
-    def _authsvn(self, cmd, args=None):
-        args = args and list(args) or []
-        args.append(self._makeauthoptions())
-        return self._svn(cmd, *args)
-
-    def _svn(self, cmd, *args):
-        l = ['svn %s' % cmd]
-        args = [self._escape(item) for item in args]
-        l.extend(args)
-        l.append('"%s"' % self._escape(self.strpath))
-        # try fixing the locale because we can't otherwise parse
-        string = fixlocale() + " ".join(l)
-        try:
-            try:
-                key = 'LC_MESSAGES'
-                hold = os.environ.get(key)
-                os.environ[key] = 'C'
-                out = py.process.cmdexec(string)
-            finally:
-                if hold:
-                    os.environ[key] = hold
-                else:
-                    del os.environ[key]
-        except py.process.cmdexec.Error:
-            e = sys.exc_info()[1]
-            strerr = e.err.lower()
-            if strerr.find('not found') != -1:
-                raise py.error.ENOENT(self)
-            elif strerr.find("E200009:") != -1:
-                raise py.error.ENOENT(self)
-            if (strerr.find('file exists') != -1 or
-                strerr.find('file already exists') != -1 or
-                strerr.find('w150002:') != -1 or
-                strerr.find("can't create directory") != -1):
-                raise py.error.EEXIST(strerr) #self)
-            raise
-        return out
-
-    def switch(self, url):
-        """ switch to given URL. """
-        self._authsvn('switch', [url])
-
-    def checkout(self, url=None, rev=None):
-        """ checkout from url to local wcpath. """
-        args = []
-        if url is None:
-            url = self.url
-        if rev is None or rev == -1:
-            if (py.std.sys.platform != 'win32' and
-                    _getsvnversion() == '1.3'):
-                url += "@HEAD"
-        else:
-            if _getsvnversion() == '1.3':
-                url += "@%d" % rev
-            else:
-                args.append('-r' + str(rev))
-        args.append(url)
-        self._authsvn('co', args)
-
-    def update(self, rev='HEAD', interactive=True):
-        """ update working copy item to given revision. (None -> HEAD). """
-        opts = ['-r', rev]
-        if not interactive:
-            opts.append("--non-interactive")
-        self._authsvn('up', opts)
-
-    def write(self, content, mode='w'):
-        """ write content into local filesystem wc. """
-        self.localpath.write(content, mode)
-
-    def dirpath(self, *args):
-        """ return the directory Path of the current Path. """
-        return self.__class__(self.localpath.dirpath(*args), auth=self.auth)
-
-    def _ensuredirs(self):
-        parent = self.dirpath()
-        if parent.check(dir=0):
-            parent._ensuredirs()
-        if self.check(dir=0):
-            self.mkdir()
-        return self
-
-    def ensure(self, *args, **kwargs):
-        """ ensure that an args-joined path exists (by default as
-            a file). if you specify a keyword argument 'directory=True'
-            then the path is forced  to be a directory path.
-        """
-        p = self.join(*args)
-        if p.check():
-            if p.check(versioned=False):
-                p.add()
-            return p
-        if kwargs.get('dir', 0):
-            return p._ensuredirs()
-        parent = p.dirpath()
-        parent._ensuredirs()
-        p.write("")
-        p.add()
-        return p
-
-    def mkdir(self, *args):
-        """ create & return the directory joined with args. """
-        if args:
-            return self.join(*args).mkdir()
-        else:
-            self._svn('mkdir')
-            return self
-
-    def add(self):
-        """ add ourself to svn """
-        self._svn('add')
-
-    def remove(self, rec=1, force=1):
-        """ remove a file or a directory tree. 'rec'ursive is
-            ignored and considered always true (because of
-            underlying svn semantics.
-        """
-        assert rec, "svn cannot remove non-recursively"
-        if not self.check(versioned=True):
-            # not added to svn (anymore?), just remove
-            py.path.local(self).remove()
-            return
-        flags = []
-        if force:
-            flags.append('--force')
-        self._svn('remove', *flags)
-
-    def copy(self, target):
-        """ copy path to target."""
-        py.process.cmdexec("svn copy %s %s" %(str(self), str(target)))
-
-    def rename(self, target):
-        """ rename this path to target. """
-        py.process.cmdexec("svn move --force %s %s" %(str(self), str(target)))
-
-    def lock(self):
-        """ set a lock (exclusive) on the resource """
-        out = self._authsvn('lock').strip()
-        if not out:
-            # warning or error, raise exception
-            raise ValueError("unknown error in svn lock command")
-
-    def unlock(self):
-        """ unset a previously set lock """
-        out = self._authsvn('unlock').strip()
-        if out.startswith('svn:'):
-            # warning or error, raise exception
-            raise Exception(out[4:])
-
-    def cleanup(self):
-        """ remove any locks from the resource """
-        # XXX should be fixed properly!!!
-        try:
-            self.unlock()
-        except:
-            pass
-
-    def status(self, updates=0, rec=0, externals=0):
-        """ return (collective) Status object for this file. """
-        # http://svnbook.red-bean.com/book.html#svn-ch-3-sect-4.3.1
-        #             2201     2192        jum   test
-        # XXX
-        if externals:
-            raise ValueError("XXX cannot perform status() "
-                             "on external items yet")
-        else:
-            #1.2 supports: externals = '--ignore-externals'
-            externals = ''
-        if rec:
-            rec= ''
-        else:
-            rec = '--non-recursive'
-
-        # XXX does not work on all subversion versions
-        #if not externals:
-        #    externals = '--ignore-externals'
-
-        if updates:
-            updates = '-u'
-        else:
-            updates = ''
-
-        try:
-            cmd = 'status -v --xml --no-ignore %s %s %s' % (
-                    updates, rec, externals)
-            out = self._authsvn(cmd)
-        except py.process.cmdexec.Error:
-            cmd = 'status -v --no-ignore %s %s %s' % (
-                    updates, rec, externals)
-            out = self._authsvn(cmd)
-            rootstatus = WCStatus(self).fromstring(out, self)
-        else:
-            rootstatus = XMLWCStatus(self).fromstring(out, self)
-        return rootstatus
-
-    def diff(self, rev=None):
-        """ return a diff of the current path against revision rev (defaulting
-            to the last one).
-        """
-        args = []
-        if rev is not None:
-            args.append("-r %d" % rev)
-        out = self._authsvn('diff', args)
-        return out
-
-    def blame(self):
-        """ return a list of tuples of three elements:
-            (revision, commiter, line)
-        """
-        out = self._svn('blame')
-        result = []
-        blamelines = out.splitlines()
-        reallines = py.path.svnurl(self.url).readlines()
-        for i, (blameline, line) in enumerate(
-                zip(blamelines, reallines)):
-            m = rex_blame.match(blameline)
-            if not m:
-                raise ValueError("output line %r of svn blame does not match "
-                                 "expected format" % (line, ))
-            rev, name, _ = m.groups()
-            result.append((int(rev), name, line))
-        return result
-
-    _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL)
-    def commit(self, msg='', rec=1):
-        """ commit with support for non-recursive commits """
-        # XXX i guess escaping should be done better here?!?
-        cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),)
-        if not rec:
-            cmd += ' -N'
-        out = self._authsvn(cmd)
-        try:
-            del cache.info[self]
-        except KeyError:
-            pass
-        if out:
-            m = self._rex_commit.match(out)
-            return int(m.group(1))
-
-    def propset(self, name, value, *args):
-        """ set property name to value on this path. """
-        d = py.path.local.mkdtemp()
-        try:
-            p = d.join('value')
-            p.write(value)
-            self._svn('propset', name, '--file', str(p), *args)
-        finally:
-            d.remove()
-
-    def propget(self, name):
-        """ get property name on this path. """
-        res = self._svn('propget', name)
-        return res[:-1] # strip trailing newline
-
-    def propdel(self, name):
-        """ delete property name on this path. """
-        res = self._svn('propdel', name)
-        return res[:-1] # strip trailing newline
-
-    def proplist(self, rec=0):
-        """ return a mapping of property names to property values.
-If rec is True, then return a dictionary mapping sub-paths to such mappings.
-"""
-        if rec:
-            res = self._svn('proplist -R')
-            return make_recursive_propdict(self, res)
-        else:
-            res = self._svn('proplist')
-            lines = res.split('\n')
-            lines = [x.strip() for x in lines[1:]]
-            return PropListDict(self, lines)
-
-    def revert(self, rec=0):
-        """ revert the local changes of this path. if rec is True, do so
-recursively. """
-        if rec:
-            result = self._svn('revert -R')
-        else:
-            result = self._svn('revert')
-        return result
-
-    def new(self, **kw):
-        """ create a modified version of this path. A 'rev' argument
-            indicates a new revision.
-            the following keyword arguments modify various path parts:
-
-              http://host.com/repo/path/file.ext
-              |-----------------------|          dirname
-                                        |------| basename
-                                        |--|     purebasename
-                                            |--| ext
-        """
-        if kw:
-            localpath = self.localpath.new(**kw)
-        else:
-            localpath = self.localpath
-        return self.__class__(localpath, auth=self.auth)
-
-    def join(self, *args, **kwargs):
-        """ return a new Path (with the same revision) which is composed
-            of the self Path followed by 'args' path components.
-        """
-        if not args:
-            return self
-        localpath = self.localpath.join(*args, **kwargs)
-        return self.__class__(localpath, auth=self.auth)
-
-    def info(self, usecache=1):
-        """ return an Info structure with svn-provided information. """
-        info = usecache and cache.info.get(self)
-        if not info:
-            try:
-                output = self._svn('info')
-            except py.process.cmdexec.Error:
-                e = sys.exc_info()[1]
-                if e.err.find('Path is not a working copy directory') != -1:
-                    raise py.error.ENOENT(self, e.err)
-                elif e.err.find("is not under version control") != -1:
-                    raise py.error.ENOENT(self, e.err)
-                raise
-            # XXX SVN 1.3 has output on stderr instead of stdout (while it does
-            # return 0!), so a bit nasty, but we assume no output is output
-            # to stderr...
-            if (output.strip() == '' or
-                    output.lower().find('not a versioned resource') != -1):
-                raise py.error.ENOENT(self, output)
-            info = InfoSvnWCCommand(output)
-
-            # Can't reliably compare on Windows without access to win32api
-            if py.std.sys.platform != 'win32':
-                if info.path != self.localpath:
-                    raise py.error.ENOENT(self, "not a versioned resource:" +
-                            " %s != %s" % (info.path, self.localpath))
-            cache.info[self] = info
-        return info
-
-    def listdir(self, fil=None, sort=None):
-        """ return a sequence of Paths.
-
-        listdir will return either a tuple or a list of paths
-        depending on implementation choices.
-        """
-        if isinstance(fil, str):
-            fil = common.FNMatcher(fil)
-        # XXX unify argument naming with LocalPath.listdir
-        def notsvn(path):
-            return path.basename != '.svn'
-
-        paths = []
-        for localpath in self.localpath.listdir(notsvn):
-            p = self.__class__(localpath, auth=self.auth)
-            if notsvn(p) and (not fil or fil(p)):
-                paths.append(p)
-        self._sortlist(paths, sort)
-        return paths
-
-    def open(self, mode='r'):
-        """ return an opened file with the given mode. """
-        return open(self.strpath, mode)
-
-    def _getbyspec(self, spec):
-        return self.localpath._getbyspec(spec)
-
-    class Checkers(py.path.local.Checkers):
-        def __init__(self, path):
-            self.svnwcpath = path
-            self.path = path.localpath
-        def versioned(self):
-            try:
-                s = self.svnwcpath.info()
-            except (py.error.ENOENT, py.error.EEXIST):
-                return False
-            except py.process.cmdexec.Error:
-                e = sys.exc_info()[1]
-                if e.err.find('is not a working copy')!=-1:
-                    return False
-                if e.err.lower().find('not a versioned resource') != -1:
-                    return False
-                raise
-            else:
-                return True
-
-    def log(self, rev_start=None, rev_end=1, verbose=False):
-        """ return a list of LogEntry instances for this path.
-rev_start is the starting revision (defaulting to the first one).
-rev_end is the last revision (defaulting to HEAD).
-if verbose is True, then the LogEntry instances also know which files changed.
-"""
-        assert self.check()   # make it simpler for the pipe
-        rev_start = rev_start is None and "HEAD" or rev_start
-        rev_end = rev_end is None and "HEAD" or rev_end
-        if rev_start == "HEAD" and rev_end == 1:
-                rev_opt = ""
-        else:
-            rev_opt = "-r %s:%s" % (rev_start, rev_end)
-        verbose_opt = verbose and "-v" or ""
-        locale_env = fixlocale()
-        # some blather on stderr
-        auth_opt = self._makeauthoptions()
-        #stdin, stdout, stderr  = os.popen3(locale_env +
-        #                                   'svn log --xml %s %s %s "%s"' % (
-        #                                    rev_opt, verbose_opt, auth_opt,
-        #                                    self.strpath))
-        cmd = locale_env + 'svn log --xml %s %s %s "%s"' % (
-            rev_opt, verbose_opt, auth_opt, self.strpath)
-
-        popen = subprocess.Popen(cmd,
-                    stdout=subprocess.PIPE,
-                    stderr=subprocess.PIPE,
-                    shell=True,
-        )
-        stdout, stderr = popen.communicate()
-        stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
-        minidom,ExpatError = importxml()
-        try:
-            tree = minidom.parseString(stdout)
-        except ExpatError:
-            raise ValueError('no such revision')
-        result = []
-        for logentry in filter(None, tree.firstChild.childNodes):
-            if logentry.nodeType == logentry.ELEMENT_NODE:
-                result.append(LogEntry(logentry))
-        return result
-
-    def size(self):
-        """ Return the size of the file content of the Path. """
-        return self.info().size
-
-    def mtime(self):
-        """ Return the last modification time of the file. """
-        return self.info().mtime
-
-    def __hash__(self):
-        return hash((self.strpath, self.__class__, self.auth))
-
-
-class WCStatus:
-    attrnames = ('modified','added', 'conflict', 'unchanged', 'external',
-                'deleted', 'prop_modified', 'unknown', 'update_available',
-                'incomplete', 'kindmismatch', 'ignored', 'locked', 'replaced'
-                )
-
-    def __init__(self, wcpath, rev=None, modrev=None, author=None):
-        self.wcpath = wcpath
-        self.rev = rev
-        self.modrev = modrev
-        self.author = author
-
-        for name in self.attrnames:
-            setattr(self, name, [])
-
-    def allpath(self, sort=True, **kw):
-        d = {}
-        for name in self.attrnames:
-            if name not in kw or kw[name]:
-                for path in getattr(self, name):
-                    d[path] = 1
-        l = d.keys()
-        if sort:
-            l.sort()
-        return l
-
-    # XXX a bit scary to assume there's always 2 spaces between username and
-    # path, however with win32 allowing spaces in user names there doesn't
-    # seem to be a more solid approach :(
-    _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)')
-
-    def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
-        """ return a new WCStatus object from data 's'
-        """
-        rootstatus = WCStatus(rootwcpath, rev, modrev, author)
-        update_rev = None
-        for line in data.split('\n'):
-            if not line.strip():
-                continue
-            #print "processing %r" % line
-            flags, rest = line[:8], line[8:]
-            # first column
-            c0,c1,c2,c3,c4,c5,x6,c7 = flags
-            #if '*' in line:
-            #    print "flags", repr(flags), "rest", repr(rest)
-
-            if c0 in '?XI':
-                fn = line.split(None, 1)[1]
-                if c0 == '?':
-                    wcpath = rootwcpath.join(fn, abs=1)
-                    rootstatus.unknown.append(wcpath)
-                elif c0 == 'X':
-                    wcpath = rootwcpath.__class__(
-                        rootwcpath.localpath.join(fn, abs=1),
-                        auth=rootwcpath.auth)
-                    rootstatus.external.append(wcpath)
-                elif c0 == 'I':
-                    wcpath = rootwcpath.join(fn, abs=1)
-                    rootstatus.ignored.append(wcpath)
-
-                continue
-
-            #elif c0 in '~!' or c4 == 'S':
-            #    raise NotImplementedError("received flag %r" % c0)
-
-            m = WCStatus._rex_status.match(rest)
-            if not m:
-                if c7 == '*':
-                    fn = rest.strip()
-                    wcpath = rootwcpath.join(fn, abs=1)
-                    rootstatus.update_available.append(wcpath)
-                    continue
-                if line.lower().find('against revision:')!=-1:
-                    update_rev = int(rest.split(':')[1].strip())
-                    continue
-                if line.lower().find('status on external') > -1:
-                    # XXX not sure what to do here... perhaps we want to
-                    # store some state instead of just continuing, as right
-                    # now it makes the top-level external get added twice
-                    # (once as external, once as 'normal' unchanged item)
-                    # because of the way SVN presents external items
-                    continue
-                # keep trying
-                raise ValueError("could not parse line %r" % line)
-            else:
-                rev, modrev, author, fn = m.groups()
-            wcpath = rootwcpath.join(fn, abs=1)
-            #assert wcpath.check()
-            if c0 == 'M':
-                assert wcpath.check(file=1), "didn't expect a directory with changed content here"
-                rootstatus.modified.append(wcpath)
-            elif c0 == 'A' or c3 == '+' :
-                rootstatus.added.append(wcpath)
-            elif c0 == 'D':
-                rootstatus.deleted.append(wcpath)
-            elif c0 == 'C':
-                rootstatus.conflict.append(wcpath)
-            elif c0 == '~':
-                rootstatus.kindmismatch.append(wcpath)
-            elif c0 == '!':
-                rootstatus.incomplete.append(wcpath)
-            elif c0 == 'R':
-                rootstatus.replaced.append(wcpath)
-            elif not c0.strip():
-                rootstatus.unchanged.append(wcpath)
-            else:
-                raise NotImplementedError("received flag %r" % c0)
-
-            if c1 == 'M':
-                rootstatus.prop_modified.append(wcpath)
-            # XXX do we cover all client versions here?
-            if c2 == 'L' or c5 == 'K':
-                rootstatus.locked.append(wcpath)
-            if c7 == '*':
-                rootstatus.update_available.append(wcpath)
-
-            if wcpath == rootwcpath:
-                rootstatus.rev = rev
-                rootstatus.modrev = modrev
-                rootstatus.author = author
-                if update_rev:
-                    rootstatus.update_rev = update_rev
-                continue
-        return rootstatus
-    fromstring = staticmethod(fromstring)
-
-class XMLWCStatus(WCStatus):
-    def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
-        """ parse 'data' (XML string as outputted by svn st) into a status obj
-        """
-        # XXX for externals, the path is shown twice: once
-        # with external information, and once with full info as if
-        # the item was a normal non-external... the current way of
-        # dealing with this issue is by ignoring it - this does make
-        # externals appear as external items as well as 'normal',
-        # unchanged ones in the status object so this is far from ideal
-        rootstatus = WCStatus(rootwcpath, rev, modrev, author)
-        update_rev = None
-        minidom, ExpatError = importxml()
-        try:
-            doc = minidom.parseString(data)
-        except ExpatError:
-            e = sys.exc_info()[1]
-            raise ValueError(str(e))
-        urevels = doc.getElementsByTagName('against')
-        if urevels:
-            rootstatus.update_rev = urevels[-1].getAttribute('revision')
-        for entryel in doc.getElementsByTagName('entry'):
-            path = entryel.getAttribute('path')
-            statusel = entryel.getElementsByTagName('wc-status')[0]
-            itemstatus = statusel.getAttribute('item')
-
-            if itemstatus == 'unversioned':
-                wcpath = rootwcpath.join(path, abs=1)
-                rootstatus.unknown.append(wcpath)
-                continue
-            elif itemstatus == 'external':
-                wcpath = rootwcpath.__class__(
-                    rootwcpath.localpath.join(path, abs=1),
-                    auth=rootwcpath.auth)
-                rootstatus.external.append(wcpath)
-                continue
-            elif itemstatus == 'ignored':
-                wcpath = rootwcpath.join(path, abs=1)
-                rootstatus.ignored.append(wcpath)
-                continue
-            elif itemstatus == 'incomplete':
-                wcpath = rootwcpath.join(path, abs=1)
-                rootstatus.incomplete.append(wcpath)
-                continue
-
-            rev = statusel.getAttribute('revision')
-            if itemstatus == 'added' or itemstatus == 'none':
-                rev = '0'
-                modrev = '?'
-                author = '?'
-                date = ''
-            elif itemstatus == "replaced":
-                pass
-            else:
-                #print entryel.toxml()
-                commitel = entryel.getElementsByTagName('commit')[0]
-                if commitel:
-                    modrev = commitel.getAttribute('revision')
-                    author = ''
-                    author_els = commitel.getElementsByTagName('author')
-                    if author_els:
-                        for c in author_els[0].childNodes:
-                            author += c.nodeValue
-                    date = ''
-                    for c in commitel.getElementsByTagName('date')[0]\
-                            .childNodes:
-                        date += c.nodeValue
-
-            wcpath = rootwcpath.join(path, abs=1)
-
-            assert itemstatus != 'modified' or wcpath.check(file=1), (
-                'did\'t expect a directory with changed content here')
-
-            itemattrname = {
-                'normal': 'unchanged',
-                'unversioned': 'unknown',
-                'conflicted': 'conflict',
-                'none': 'added',
-            }.get(itemstatus, itemstatus)
-
-            attr = getattr(rootstatus, itemattrname)
-            attr.append(wcpath)
-
-            propsstatus = statusel.getAttribute('props')
-            if propsstatus not in ('none', 'normal'):
-                rootstatus.prop_modified.append(wcpath)
-
-            if wcpath == rootwcpath:
-                rootstatus.rev = rev
-                rootstatus.modrev = modrev
-                rootstatus.author = author
-                rootstatus.date = date
-
-            # handle repos-status element (remote info)
-            rstatusels = entryel.getElementsByTagName('repos-status')
-            if rstatusels:
-                rstatusel = rstatusels[0]
-                ritemstatus = rstatusel.getAttribute('item')
-                if ritemstatus in ('added', 'modified'):
-                    rootstatus.update_available.append(wcpath)
-
-            lockels = entryel.getElementsByTagName('lock')
-            if len(lockels):
-                rootstatus.locked.append(wcpath)
-
-        return rootstatus
-    fromstring = staticmethod(fromstring)
-
-class InfoSvnWCCommand:
-    def __init__(self, output):
-        # Path: test
-        # URL: http://codespeak.net/svn/std.path/trunk/dist/std.path/test
-        # Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
-        # Revision: 2151
-        # Node Kind: directory
-        # Schedule: normal
-        # Last Changed Author: hpk
-        # Last Changed Rev: 2100
-        # Last Changed Date: 2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)
-        # Properties Last Updated: 2003-11-03 14:47:48 +0100 (Mon, 03 Nov 2003)
-
-        d = {}
-        for line in output.split('\n'):
-            if not line.strip():
-                continue
-            key, value = line.split(':', 1)
-            key = key.lower().replace(' ', '')
-            value = value.strip()
-            d[key] = value
-        try:
-            self.url = d['url']
-        except KeyError:
-            raise  ValueError("Not a versioned resource")
-            #raise ValueError, "Not a versioned resource %r" % path
-        self.kind = d['nodekind'] == 'directory' and 'dir' or d['nodekind']
-        try:
-            self.rev = int(d['revision'])
-        except KeyError:
-            self.rev = None
-
-        self.path = py.path.local(d['path'])
-        self.size = self.path.size()
-        if 'lastchangedrev' in d:
-            self.created_rev = int(d['lastchangedrev'])
-        if 'lastchangedauthor' in d:
-            self.last_author = d['lastchangedauthor']
-        if 'lastchangeddate' in d:
-            self.mtime = parse_wcinfotime(d['lastchangeddate'])
-            self.time = self.mtime * 1000000
-
-    def __eq__(self, other):
-        return self.__dict__ == other.__dict__
-
-def parse_wcinfotime(timestr):
-    """ Returns seconds since epoch, UTC. """
-    # example: 2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)
-    m = re.match(r'(\d+-\d+-\d+ \d+:\d+:\d+) ([+-]\d+) .*', timestr)
-    if not m:
-        raise ValueError("timestring %r does not match" % timestr)
-    timestr, timezone = m.groups()
-    # do not handle timezone specially, return value should be UTC
-    parsedtime = time.strptime(timestr, "%Y-%m-%d %H:%M:%S")
-    return calendar.timegm(parsedtime)
-
-def make_recursive_propdict(wcroot,
-                            output,
-                            rex = re.compile("Properties on '(.*)':")):
-    """ Return a dictionary of path->PropListDict mappings. """
-    lines = [x for x in output.split('\n') if x]
-    pdict = {}
-    while lines:
-        line = lines.pop(0)
-        m = rex.match(line)
-        if not m:
-            raise ValueError("could not parse propget-line: %r" % line)
-        path = m.groups()[0]
-        wcpath = wcroot.join(path, abs=1)
-        propnames = []
-        while lines and lines[0].startswith('  '):
-            propname = lines.pop(0).strip()
-            propnames.append(propname)
-        assert propnames, "must have found properties!"
-        pdict[wcpath] = PropListDict(wcpath, propnames)
-    return pdict
-
-
-def importxml(cache=[]):
-    if cache:
-        return cache
-    from xml.dom import minidom
-    from xml.parsers.expat import ExpatError
-    cache.extend([minidom, ExpatError])
-    return cache
-
-class LogEntry:
-    def __init__(self, logentry):
-        self.rev = int(logentry.getAttribute('revision'))
-        for lpart in filter(None, logentry.childNodes):
-            if lpart.nodeType == lpart.ELEMENT_NODE:
-                if lpart.nodeName == 'author':
-                    self.author = lpart.firstChild.nodeValue
-                elif lpart.nodeName == 'msg':
-                    if lpart.firstChild:
-                        self.msg = lpart.firstChild.nodeValue
-                    else:
-                        self.msg = ''
-                elif lpart.nodeName == 'date':
-                    #2003-07-29T20:05:11.598637Z
-                    timestr = lpart.firstChild.nodeValue
-                    self.date = parse_apr_time(timestr)
-                elif lpart.nodeName == 'paths':
-                    self.strpaths = []
-                    for ppart in filter(None, lpart.childNodes):
-                        if ppart.nodeType == ppart.ELEMENT_NODE:
-                            self.strpaths.append(PathEntry(ppart))
-    def __repr__(self):
-        return '<Logentry rev=%d author=%s date=%s>' % (
-            self.rev, self.author, self.date)
-
-
diff --git a/tools/py/py/_std.py b/tools/py/py/_std.py
deleted file mode 100644
index 97a9853..0000000
--- a/tools/py/py/_std.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import sys
-
-class Std(object):
-    """ makes top-level python modules available as an attribute,
-        importing them on first access.
-    """
-
-    def __init__(self):
-        self.__dict__ = sys.modules
-
-    def __getattr__(self, name):
-        try:
-            m = __import__(name)
-        except ImportError:
-            raise AttributeError("py.std: could not import %s" % name)
-        return m
-
-std = Std()
diff --git a/tools/py/py/_xmlgen.py b/tools/py/py/_xmlgen.py
deleted file mode 100644
index 2ffcaa1..0000000
--- a/tools/py/py/_xmlgen.py
+++ /dev/null
@@ -1,253 +0,0 @@
-"""
-module for generating and serializing xml and html structures
-by using simple python objects.
-
-(c) holger krekel, holger at merlinux eu. 2009
-"""
-import sys, re
-
-if sys.version_info >= (3,0):
-    def u(s):
-        return s
-    def unicode(x, errors=None):
-        if hasattr(x, '__unicode__'):
-            return x.__unicode__()
-        return str(x)
-else:
-    def u(s):
-        return unicode(s)
-    unicode = unicode
-
-
-class NamespaceMetaclass(type):
-    def __getattr__(self, name):
-        if name[:1] == '_':
-            raise AttributeError(name)
-        if self == Namespace:
-            raise ValueError("Namespace class is abstract")
-        tagspec = self.__tagspec__
-        if tagspec is not None and name not in tagspec:
-            raise AttributeError(name)
-        classattr = {}
-        if self.__stickyname__:
-            classattr['xmlname'] = name
-        cls = type(name, (self.__tagclass__,), classattr)
-        setattr(self, name, cls)
-        return cls
-
-class Tag(list):
-    class Attr(object):
-        def __init__(self, **kwargs):
-            self.__dict__.update(kwargs)
-
-    def __init__(self, *args, **kwargs):
-        super(Tag, self).__init__(args)
-        self.attr = self.Attr(**kwargs)
-
-    def __unicode__(self):
-        return self.unicode(indent=0)
-    __str__ = __unicode__
-
-    def unicode(self, indent=2):
-        l = []
-        SimpleUnicodeVisitor(l.append, indent).visit(self)
-        return u("").join(l)
-
-    def __repr__(self):
-        name = self.__class__.__name__
-        return "<%r tag object %d>" % (name, id(self))
-
-Namespace = NamespaceMetaclass('Namespace', (object, ), {
-    '__tagspec__': None,
-    '__tagclass__': Tag,
-    '__stickyname__': False,
-})
-
-class HtmlTag(Tag):
-    def unicode(self, indent=2):
-        l = []
-        HtmlVisitor(l.append, indent, shortempty=False).visit(self)
-        return u("").join(l)
-
-# exported plain html namespace
-class html(Namespace):
-    __tagclass__ = HtmlTag
-    __stickyname__ = True
-    __tagspec__ = dict([(x,1) for x in (
-        'a,abbr,acronym,address,applet,area,b,bdo,big,blink,'
-        'blockquote,body,br,button,caption,center,cite,code,col,'
-        'colgroup,comment,dd,del,dfn,dir,div,dl,dt,em,embed,'
-        'fieldset,font,form,frameset,h1,h2,h3,h4,h5,h6,head,html,'
-        'i,iframe,img,input,ins,kbd,label,legend,li,link,listing,'
-        'map,marquee,menu,meta,multicol,nobr,noembed,noframes,'
-        'noscript,object,ol,optgroup,option,p,pre,q,s,script,'
-        'select,small,span,strike,strong,style,sub,sup,table,'
-        'tbody,td,textarea,tfoot,th,thead,title,tr,tt,u,ul,xmp,'
-        'base,basefont,frame,hr,isindex,param,samp,var'
-    ).split(',') if x])
-
-    class Style(object):
-        def __init__(self, **kw):
-            for x, y in kw.items():
-                x = x.replace('_', '-')
-                setattr(self, x, y)
-
-
-class raw(object):
-    """just a box that can contain a unicode string that will be
-    included directly in the output"""
-    def __init__(self, uniobj):
-        self.uniobj = uniobj
-
-class SimpleUnicodeVisitor(object):
-    """ recursive visitor to write unicode. """
-    def __init__(self, write, indent=0, curindent=0, shortempty=True):
-        self.write = write
-        self.cache = {}
-        self.visited = {} # for detection of recursion
-        self.indent = indent
-        self.curindent = curindent
-        self.parents = []
-        self.shortempty = shortempty  # short empty tags or not
-
-    def visit(self, node):
-        """ dispatcher on node's class/bases name. """
-        cls = node.__class__
-        try:
-            visitmethod = self.cache[cls]
-        except KeyError:
-            for subclass in cls.__mro__:
-                visitmethod = getattr(self, subclass.__name__, None)
-                if visitmethod is not None:
-                    break
-            else:
-                visitmethod = self.__object
-            self.cache[cls] = visitmethod
-        visitmethod(node)
-
-    # the default fallback handler is marked private
-    # to avoid clashes with the tag name object
-    def __object(self, obj):
-        #self.write(obj)
-        self.write(escape(unicode(obj)))
-
-    def raw(self, obj):
-        self.write(obj.uniobj)
-
-    def list(self, obj):
-        assert id(obj) not in self.visited
-        self.visited[id(obj)] = 1
-        for elem in obj:
-            self.visit(elem)
-
-    def Tag(self, tag):
-        assert id(tag) not in self.visited
-        try:
-            tag.parent = self.parents[-1]
-        except IndexError:
-            tag.parent = None
-        self.visited[id(tag)] = 1
-        tagname = getattr(tag, 'xmlname', tag.__class__.__name__)
-        if self.curindent and not self._isinline(tagname):
-            self.write("\n" + u(' ') * self.curindent)
-        if tag:
-            self.curindent += self.indent
-            self.write(u('<%s%s>') % (tagname, self.attributes(tag)))
-            self.parents.append(tag)
-            for x in tag:
-                self.visit(x)
-            self.parents.pop()
-            self.write(u('</%s>') % tagname)
-            self.curindent -= self.indent
-        else:
-            nameattr = tagname+self.attributes(tag)
-            if self._issingleton(tagname):
-                self.write(u('<%s/>') % (nameattr,))
-            else:
-                self.write(u('<%s></%s>') % (nameattr, tagname))
-
-    def attributes(self, tag):
-        # serialize attributes
-        attrlist = dir(tag.attr)
-        attrlist.sort()
-        l = []
-        for name in attrlist:
-            res = self.repr_attribute(tag.attr, name)
-            if res is not None:
-                l.append(res)
-        l.extend(self.getstyle(tag))
-        return u("").join(l)
-
-    def repr_attribute(self, attrs, name):
-        if name[:2] != '__':
-            value = getattr(attrs, name)
-            if name.endswith('_'):
-                name = name[:-1]
-            if isinstance(value, raw):
-                insert = value.uniobj
-            else:
-                insert = escape(unicode(value))
-            return ' %s="%s"' % (name, insert)
-
-    def getstyle(self, tag):
-        """ return attribute list suitable for styling. """
-        try:
-            styledict = tag.style.__dict__
-        except AttributeError:
-            return []
-        else:
-            stylelist = [x+': ' + y for x,y in styledict.items()]
-            return [u(' style="%s"') % u('; ').join(stylelist)]
-
-    def _issingleton(self, tagname):
-        """can (and will) be overridden in subclasses"""
-        return self.shortempty
-
-    def _isinline(self, tagname):
-        """can (and will) be overridden in subclasses"""
-        return False
-
-class HtmlVisitor(SimpleUnicodeVisitor):
-
-    single = dict([(x, 1) for x in
-                ('br,img,area,param,col,hr,meta,link,base,'
-                    'input,frame').split(',')])
-    inline = dict([(x, 1) for x in
-                ('a abbr acronym b basefont bdo big br cite code dfn em font '
-                 'i img input kbd label q s samp select small span strike '
-                 'strong sub sup textarea tt u var'.split(' '))])
-
-    def repr_attribute(self, attrs, name):
-        if name == 'class_':
-            value = getattr(attrs, name)
-            if value is None:
-                return
-        return super(HtmlVisitor, self).repr_attribute(attrs, name)
-
-    def _issingleton(self, tagname):
-        return tagname in self.single
-
-    def _isinline(self, tagname):
-        return tagname in self.inline
-
-
-class _escape:
-    def __init__(self):
-        self.escape = {
-            u('"') : u('&quot;'), u('<') : u('&lt;'), u('>') : u('&gt;'),
-            u('&') : u('&amp;'), u("'") : u('&apos;'),
-            }
-        self.charef_rex = re.compile(u("|").join(self.escape.keys()))
-
-    def _replacer(self, match):
-        return self.escape[match.group(0)]
-
-    def __call__(self, ustring):
-        """ xml-escape the given unicode string. """
-        try:
-            ustring = unicode(ustring)
-        except UnicodeDecodeError:
-            ustring = unicode(ustring, 'utf-8', errors='replace')
-        return self.charef_rex.sub(self._replacer, ustring)
-
-escape = _escape()
diff --git a/tools/py/setup.cfg b/tools/py/setup.cfg
deleted file mode 100644
index 272e488..0000000
--- a/tools/py/setup.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[wheel]
-universal = 1
-
-[devpi:upload]
-formats=sdist.tgz,bdist_wheel
diff --git a/tools/py/setup.py b/tools/py/setup.py
deleted file mode 100644
index 06f0885..0000000
--- a/tools/py/setup.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import os, sys
-
-from setuptools import setup
-
-def main():
-    setup(
-        name='py',
-        description='library with cross-python path, ini-parsing, io, code, log facilities',
-        long_description = open('README.txt').read(),
-        version='1.4.31',
-        url='http://pylib.readthedocs.org/',
-        license='MIT license',
-        platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
-        author='holger krekel, Ronny Pfannschmidt, Benjamin Peterson and others',
-        author_email='pytest-dev@python.org',
-        classifiers=['Development Status :: 6 - Mature',
-                     'Intended Audience :: Developers',
-                     'License :: OSI Approved :: MIT License',
-                     'Operating System :: POSIX',
-                     'Operating System :: Microsoft :: Windows',
-                     'Operating System :: MacOS :: MacOS X',
-                     'Topic :: Software Development :: Testing',
-                     'Topic :: Software Development :: Libraries',
-                     'Topic :: Utilities',
-                     'Programming Language :: Python',
-                     'Programming Language :: Python :: 3'],
-        packages=['py',
-                  'py._code',
-                  'py._io',
-                  'py._log',
-                  'py._path',
-                  'py._process',
-        ],
-        zip_safe=False,
-    )
-
-if __name__ == '__main__':
-    main()
diff --git a/tools/py/testing/code/test_assertion.py b/tools/py/testing/code/test_assertion.py
deleted file mode 100644
index e2154d0..0000000
--- a/tools/py/testing/code/test_assertion.py
+++ /dev/null
@@ -1,308 +0,0 @@
-import pytest, py
-
-def exvalue():
-    return py.std.sys.exc_info()[1]
-
-def f():
-    return 2
-
-def test_assert():
-    try:
-        assert f() == 3
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith('assert 2 == 3\n')
-
-
-def test_assert_within_finally():
-    excinfo = py.test.raises(ZeroDivisionError, """
-        try:
-            1/0
-        finally:
-            i = 42
-    """)
-    s = excinfo.exconly()
-    assert py.std.re.search("division.+by zero", s) is not None
-
-    #def g():
-    #    A.f()
-    #excinfo = getexcinfo(TypeError, g)
-    #msg = getmsg(excinfo)
-    #assert msg.find("must be called with A") != -1
-
-
-def test_assert_multiline_1():
-    try:
-        assert (f() ==
-                3)
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith('assert 2 == 3\n')
-
-def test_assert_multiline_2():
-    try:
-        assert (f() == (4,
-                   3)[-1])
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith('assert 2 ==')
-
-def test_in():
-    try:
-        assert "hi" in [1, 2]
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 'hi' in")
-
-def test_is():
-    try:
-        assert 1 is 2
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 1 is 2")
-
-
-@py.test.mark.skipif("sys.version_info < (2,6)")
-def test_attrib():
-    class Foo(object):
-        b = 1
-    i = Foo()
-    try:
-        assert i.b == 2
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 1 == 2")
-
-@py.test.mark.skipif("sys.version_info < (2,6)")
-def test_attrib_inst():
-    class Foo(object):
-        b = 1
-    try:
-        assert Foo().b == 2
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 1 == 2")
-
-def test_len():
-    l = list(range(42))
-    try:
-        assert len(l) == 100
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 42 == 100")
-        assert "where 42 = len([" in s
-
-
-def test_assert_keyword_arg():
-    def f(x=3):
-        return False
-    try:
-        assert f(x=5)
-    except AssertionError:
-        e = exvalue()
-        assert "x=5" in e.msg
-
-# These tests should both fail, but should fail nicely...
-class WeirdRepr:
-    def __repr__(self):
-        return '<WeirdRepr\nsecond line>'
-
-def bug_test_assert_repr():
-    v = WeirdRepr()
-    try:
-        assert v == 1
-    except AssertionError:
-        e = exvalue()
-        assert e.msg.find('WeirdRepr') != -1
-        assert e.msg.find('second line') != -1
-        assert 0
-
-def test_assert_non_string():
-    try:
-        assert 0, ['list']
-    except AssertionError:
-        e = exvalue()
-        assert e.msg.find("list") != -1
-
-def test_assert_implicit_multiline():
-    try:
-        x = [1,2,3]
-        assert x != [1,
-           2, 3]
-    except AssertionError:
-        e = exvalue()
-        assert e.msg.find('assert [1, 2, 3] !=') != -1
-
-
-def test_assert_with_brokenrepr_arg():
-    class BrokenRepr:
-        def __repr__(self): 0 / 0
-    e = AssertionError(BrokenRepr())
-    if e.msg.find("broken __repr__") == -1:
-        py.test.fail("broken __repr__ not handle correctly")
-
-def test_multiple_statements_per_line():
-    try:
-        a = 1; assert a == 2
-    except AssertionError:
-        e = exvalue()
-        assert "assert 1 == 2" in e.msg
-
-def test_power():
-    try:
-        assert 2**3 == 7
-    except AssertionError:
-        e = exvalue()
-        assert "assert (2 ** 3) == 7" in e.msg
-
-
-class TestView:
-
-    def setup_class(cls):
-        cls.View = py.test.importorskip("py._code._assertionold").View
-
-    def test_class_dispatch(self):
-        ### Use a custom class hierarchy with existing instances
-
-        class Picklable(self.View):
-            pass
-
-        class Simple(Picklable):
-            __view__ = object
-            def pickle(self):
-                return repr(self.__obj__)
-
-        class Seq(Picklable):
-            __view__ = list, tuple, dict
-            def pickle(self):
-                return ';'.join(
-                    [Picklable(item).pickle() for item in self.__obj__])
-
-        class Dict(Seq):
-            __view__ = dict
-            def pickle(self):
-                return Seq.pickle(self) + '!' + Seq(self.values()).pickle()
-
-        assert Picklable(123).pickle() == '123'
-        assert Picklable([1,[2,3],4]).pickle() == '1;2;3;4'
-        assert Picklable({1:2}).pickle() == '1!2'
-
-    def test_viewtype_class_hierarchy(self):
-        # Use a custom class hierarchy based on attributes of existing instances
-        class Operation:
-            "Existing class that I don't want to change."
-            def __init__(self, opname, *args):
-                self.opname = opname
-                self.args = args
-
-        existing = [Operation('+', 4, 5),
-                    Operation('getitem', '', 'join'),
-                    Operation('setattr', 'x', 'y', 3),
-                    Operation('-', 12, 1)]
-
-        class PyOp(self.View):
-            def __viewkey__(self):
-                return self.opname
-            def generate(self):
-                return '%s(%s)' % (self.opname, ', '.join(map(repr, self.args)))
-
-        class PyBinaryOp(PyOp):
-            __view__ = ('+', '-', '*', '/')
-            def generate(self):
-                return '%s %s %s' % (self.args[0], self.opname, self.args[1])
-
-        codelines = [PyOp(op).generate() for op in existing]
-        assert codelines == ["4 + 5", "getitem('', 'join')",
-            "setattr('x', 'y', 3)", "12 - 1"]
-
-def test_underscore_api():
-    py.code._AssertionError
-    py.code._reinterpret_old # used by pypy
-    py.code._reinterpret
-
-@py.test.mark.skipif("sys.version_info < (2,6)")
-def test_assert_customizable_reprcompare(monkeypatch):
-    util = pytest.importorskip("_pytest.assertion.util")
-    monkeypatch.setattr(util, '_reprcompare', lambda *args: 'hello')
-    try:
-        assert 3 == 4
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert "hello" in s
-
-def test_assert_long_source_1():
-    try:
-        assert len == [
-            (None, ['somet text', 'more text']),
-        ]
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert 're-run' not in s
-        assert 'somet text' in s
-
-def test_assert_long_source_2():
-    try:
-        assert(len == [
-            (None, ['somet text', 'more text']),
-        ])
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert 're-run' not in s
-        assert 'somet text' in s
-
-def test_assert_raise_alias(testdir):
-    testdir.makepyfile("""
-    import sys
-    EX = AssertionError
-    def test_hello():
-        raise EX("hello"
-            "multi"
-            "line")
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*def test_hello*",
-        "*raise EX*",
-        "*1 failed*",
-    ])
-
-
-@pytest.mark.skipif("sys.version_info < (2,5)")
-def test_assert_raise_subclass():
-    class SomeEx(AssertionError):
-        def __init__(self, *args):
-            super(SomeEx, self).__init__()
-    try:
-        raise SomeEx("hello")
-    except AssertionError:
-        s = str(exvalue())
-        assert 're-run' not in s
-        assert 'could not determine' in s
-
-def test_assert_raises_in_nonzero_of_object_pytest_issue10():
-    class A(object):
-        def __nonzero__(self):
-            raise ValueError(42)
-        def __lt__(self, other):
-            return A()
-        def __repr__(self):
-            return "<MY42 object>"
-    def myany(x):
-        return True
-    try:
-        assert not(myany(A() < 0))
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert "<MY42 object> < 0" in s
diff --git a/tools/py/testing/code/test_excinfo.py b/tools/py/testing/code/test_excinfo.py
deleted file mode 100644
index 65742c6..0000000
--- a/tools/py/testing/code/test_excinfo.py
+++ /dev/null
@@ -1,909 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import py
-from py._code.code import FormattedExcinfo, ReprExceptionInfo
-queue = py.builtin._tryimport('queue', 'Queue')
-
-failsonjython = py.test.mark.xfail("sys.platform.startswith('java')")
-from test_source import astonly
-
-try:
-    import importlib
-except ImportError:
-    invalidate_import_caches = None
-else:
-    invalidate_import_caches = getattr(importlib, "invalidate_caches", None)
-
-import pytest
-pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
-
-class TWMock:
-    def __init__(self):
-        self.lines = []
-    def sep(self, sep, line=None):
-        self.lines.append((sep, line))
-    def line(self, line, **kw):
-        self.lines.append(line)
-    def markup(self, text, **kw):
-        return text
-
-    fullwidth = 80
-
-def test_excinfo_simple():
-    try:
-        raise ValueError
-    except ValueError:
-        info = py.code.ExceptionInfo()
-    assert info.type == ValueError
-
-def test_excinfo_getstatement():
-    def g():
-        raise ValueError
-    def f():
-        g()
-    try:
-        f()
-    except ValueError:
-        excinfo = py.code.ExceptionInfo()
-    linenumbers = [py.code.getrawcode(f).co_firstlineno-1+3,
-                   py.code.getrawcode(f).co_firstlineno-1+1,
-                   py.code.getrawcode(g).co_firstlineno-1+1,]
-    l = list(excinfo.traceback)
-    foundlinenumbers = [x.lineno for x in l]
-    assert foundlinenumbers == linenumbers
-    #for x in info:
-    #    print "%s:%d  %s" %(x.path.relto(root), x.lineno, x.statement)
-    #xxx
-
-# testchain for getentries test below
-def f():
-    #
-    raise ValueError
-    #
-def g():
-    #
-    __tracebackhide__ = True
-    f()
-    #
-def h():
-    #
-    g()
-    #
-
-class TestTraceback_f_g_h:
-    def setup_method(self, method):
-        try:
-            h()
-        except ValueError:
-            self.excinfo = py.code.ExceptionInfo()
-
-    def test_traceback_entries(self):
-        tb = self.excinfo.traceback
-        entries = list(tb)
-        assert len(tb) == 4 # maybe fragile test
-        assert len(entries) == 4 # maybe fragile test
-        names = ['f', 'g', 'h']
-        for entry in entries:
-            try:
-                names.remove(entry.frame.code.name)
-            except ValueError:
-                pass
-        assert not names
-
-    def test_traceback_entry_getsource(self):
-        tb = self.excinfo.traceback
-        s = str(tb[-1].getsource() )
-        assert s.startswith("def f():")
-        assert s.endswith("raise ValueError")
-
-    @astonly
-    @failsonjython
-    def test_traceback_entry_getsource_in_construct(self):
-        source = py.code.Source("""\
-            def xyz():
-                try:
-                    raise ValueError
-                except somenoname:
-                    pass
-            xyz()
-        """)
-        try:
-            exec (source.compile())
-        except NameError:
-            tb = py.code.ExceptionInfo().traceback
-            print (tb[-1].getsource())
-            s = str(tb[-1].getsource())
-            assert s.startswith("def xyz():\n    try:")
-            assert s.strip().endswith("except somenoname:")
-
-    def test_traceback_cut(self):
-        co = py.code.Code(f)
-        path, firstlineno = co.path, co.firstlineno
-        traceback = self.excinfo.traceback
-        newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
-        assert len(newtraceback) == 1
-        newtraceback = traceback.cut(path=path, lineno=firstlineno+2)
-        assert len(newtraceback) == 1
-
-    def test_traceback_cut_excludepath(self, testdir):
-        p = testdir.makepyfile("def f(): raise ValueError")
-        excinfo = py.test.raises(ValueError, "p.pyimport().f()")
-        basedir = py.path.local(py.test.__file__).dirpath()
-        newtraceback = excinfo.traceback.cut(excludepath=basedir)
-        for x in newtraceback:
-            if hasattr(x, 'path'):
-                assert not py.path.local(x.path).relto(basedir)
-        assert newtraceback[-1].frame.code.path == p
-
-    def test_traceback_filter(self):
-        traceback = self.excinfo.traceback
-        ntraceback = traceback.filter()
-        assert len(ntraceback) == len(traceback) - 1
-
-    def test_traceback_recursion_index(self):
-        def f(n):
-            if n < 10:
-                n += 1
-            f(n)
-        excinfo = py.test.raises(RuntimeError, f, 8)
-        traceback = excinfo.traceback
-        recindex = traceback.recursionindex()
-        assert recindex == 3
-
-    def test_traceback_only_specific_recursion_errors(self, monkeypatch):
-        def f(n):
-            if n == 0:
-                raise RuntimeError("hello")
-            f(n-1)
-
-        excinfo = pytest.raises(RuntimeError, f, 100)
-        monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
-        repr = excinfo.getrepr()
-        assert "RuntimeError: hello" in str(repr.reprcrash)
-
-    def test_traceback_no_recursion_index(self):
-        def do_stuff():
-            raise RuntimeError
-        def reraise_me():
-            import sys
-            exc, val, tb = sys.exc_info()
-            py.builtin._reraise(exc, val, tb)
-        def f(n):
-            try:
-                do_stuff()
-            except:
-                reraise_me()
-        excinfo = py.test.raises(RuntimeError, f, 8)
-        traceback = excinfo.traceback
-        recindex = traceback.recursionindex()
-        assert recindex is None
-
-    def test_traceback_messy_recursion(self):
-        #XXX: simplified locally testable version
-        decorator = py.test.importorskip('decorator').decorator
-
-        def log(f, *k, **kw):
-            print('%s %s' % (k, kw))
-            f(*k, **kw)
-        log = decorator(log)
-
-        def fail():
-            raise ValueError('')
-
-        fail = log(log(fail))
-
-        excinfo = py.test.raises(ValueError, fail)
-        assert excinfo.traceback.recursionindex() is None
-
-
-
-    def test_traceback_getcrashentry(self):
-        def i():
-            __tracebackhide__ = True
-            raise ValueError
-        def h():
-            i()
-        def g():
-            __tracebackhide__ = True
-            h()
-        def f():
-            g()
-
-        excinfo = py.test.raises(ValueError, f)
-        tb = excinfo.traceback
-        entry = tb.getcrashentry()
-        co = py.code.Code(h)
-        assert entry.frame.code.path == co.path
-        assert entry.lineno == co.firstlineno + 1
-        assert entry.frame.code.name == 'h'
-
-    def test_traceback_getcrashentry_empty(self):
-        def g():
-            __tracebackhide__ = True
-            raise ValueError
-        def f():
-            __tracebackhide__ = True
-            g()
-
-        excinfo = py.test.raises(ValueError, f)
-        tb = excinfo.traceback
-        entry = tb.getcrashentry()
-        co = py.code.Code(g)
-        assert entry.frame.code.path == co.path
-        assert entry.lineno == co.firstlineno + 2
-        assert entry.frame.code.name == 'g'
-
-def hello(x):
-    x + 5
-
-def test_tbentry_reinterpret():
-    try:
-        hello("hello")
-    except TypeError:
-        excinfo = py.code.ExceptionInfo()
-    tbentry = excinfo.traceback[-1]
-    msg = tbentry.reinterpret()
-    assert msg.startswith("TypeError: ('hello' + 5)")
-
-def test_excinfo_exconly():
-    excinfo = py.test.raises(ValueError, h)
-    assert excinfo.exconly().startswith('ValueError')
-    excinfo = py.test.raises(ValueError,
-        "raise ValueError('hello\\nworld')")
-    msg = excinfo.exconly(tryshort=True)
-    assert msg.startswith('ValueError')
-    assert msg.endswith("world")
-
-def test_excinfo_repr():
-    excinfo = py.test.raises(ValueError, h)
-    s = repr(excinfo)
-    assert s == "<ExceptionInfo ValueError tblen=4>"
-
-def test_excinfo_str():
-    excinfo = py.test.raises(ValueError, h)
-    s = str(excinfo)
-    assert s.startswith(__file__[:-9]) # pyc file and $py.class
-    assert s.endswith("ValueError")
-    assert len(s.split(":")) >= 3 # on windows it's 4
-
-def test_excinfo_errisinstance():
-    excinfo = py.test.raises(ValueError, h)
-    assert excinfo.errisinstance(ValueError)
-
-def test_excinfo_no_sourcecode():
-    try:
-        exec ("raise ValueError()")
-    except ValueError:
-        excinfo = py.code.ExceptionInfo()
-    s = str(excinfo.traceback[-1])
-    if py.std.sys.version_info < (2,5):
-        assert s == "  File '<string>':1 in ?\n  ???\n"
-    else:
-        assert s == "  File '<string>':1 in <module>\n  ???\n"
-
-def test_excinfo_no_python_sourcecode(tmpdir):
-    #XXX: simplified locally testable version
-    tmpdir.join('test.txt').write("{{ h()}}:")
-
-    jinja2 = py.test.importorskip('jinja2')
-    loader = jinja2.FileSystemLoader(str(tmpdir))
-    env = jinja2.Environment(loader=loader)
-    template = env.get_template('test.txt')
-    excinfo = py.test.raises(ValueError,
-                             template.render, h=h)
-    for item in excinfo.traceback:
-        print(item) #XXX: for some reason jinja.Template.render is printed in full
-        item.source # shouldnt fail
-        if item.path.basename == 'test.txt':
-            assert str(item.source) == '{{ h()}}:'
-
-
-def test_entrysource_Queue_example():
-    try:
-        queue.Queue().get(timeout=0.001)
-    except queue.Empty:
-        excinfo = py.code.ExceptionInfo()
-    entry = excinfo.traceback[-1]
-    source = entry.getsource()
-    assert source is not None
-    s = str(source).strip()
-    assert s.startswith("def get")
-
-def test_codepath_Queue_example():
-    try:
-        queue.Queue().get(timeout=0.001)
-    except queue.Empty:
-        excinfo = py.code.ExceptionInfo()
-    entry = excinfo.traceback[-1]
-    path = entry.path
-    assert isinstance(path, py.path.local)
-    assert path.basename.lower() == "queue.py"
-    assert path.check()
-
-class TestFormattedExcinfo:
-    def pytest_funcarg__importasmod(self, request):
-        def importasmod(source):
-            source = py.code.Source(source)
-            tmpdir = request.getfuncargvalue("tmpdir")
-            modpath = tmpdir.join("mod.py")
-            tmpdir.ensure("__init__.py")
-            modpath.write(source)
-            if invalidate_import_caches is not None:
-                invalidate_import_caches()
-            return modpath.pyimport()
-        return importasmod
-
-    def excinfo_from_exec(self, source):
-        source = py.code.Source(source).strip()
-        try:
-            exec (source.compile())
-        except KeyboardInterrupt:
-            raise
-        except:
-            return py.code.ExceptionInfo()
-        assert 0, "did not raise"
-
-    def test_repr_source(self):
-        pr = FormattedExcinfo()
-        source = py.code.Source("""
-            def f(x):
-                pass
-        """).strip()
-        pr.flow_marker = "|"
-        lines = pr.get_source(source, 0)
-        assert len(lines) == 2
-        assert lines[0] == "|   def f(x):"
-        assert lines[1] == "        pass"
-
-    def test_repr_source_excinfo(self):
-        """ check if indentation is right """
-        pr = FormattedExcinfo()
-        excinfo = self.excinfo_from_exec("""
-                def f():
-                    assert 0
-                f()
-        """)
-        pr = FormattedExcinfo()
-        source = pr._getentrysource(excinfo.traceback[-1])
-        lines = pr.get_source(source, 1, excinfo)
-        assert lines == [
-            '    def f():',
-            '>       assert 0',
-            'E       assert 0'
-        ]
-
-
-    def test_repr_source_not_existing(self):
-        pr = FormattedExcinfo()
-        co = compile("raise ValueError()", "", "exec")
-        try:
-            exec (co)
-        except ValueError:
-            excinfo = py.code.ExceptionInfo()
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
-
-    def test_repr_many_line_source_not_existing(self):
-        pr = FormattedExcinfo()
-        co = compile("""
-a = 1
-raise ValueError()
-""", "", "exec")
-        try:
-            exec (co)
-        except ValueError:
-            excinfo = py.code.ExceptionInfo()
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
-
-    def test_repr_source_failing_fullsource(self):
-        pr = FormattedExcinfo()
-
-        class FakeCode(object):
-            class raw:
-                co_filename = '?'
-            path = '?'
-            firstlineno = 5
-
-            def fullsource(self):
-                return None
-            fullsource = property(fullsource)
-
-        class FakeFrame(object):
-            code = FakeCode()
-            f_locals = {}
-            f_globals = {}
-
-        class FakeTracebackEntry(py.code.Traceback.Entry):
-            def __init__(self, tb):
-                self.lineno = 5+3
-
-            @property
-            def frame(self):
-                return FakeFrame()
-
-        class Traceback(py.code.Traceback):
-            Entry = FakeTracebackEntry
-
-        class FakeExcinfo(py.code.ExceptionInfo):
-            typename = "Foo"
-            def __init__(self):
-                pass
-
-            def exconly(self, tryshort):
-                return "EXC"
-            def errisinstance(self, cls):
-                return False
-
-        excinfo = FakeExcinfo()
-        class FakeRawTB(object):
-            tb_next = None
-        tb = FakeRawTB()
-        excinfo.traceback = Traceback(tb)
-
-        fail = IOError()
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
-
-        fail = py.error.ENOENT
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
-
-
-    def test_repr_local(self):
-        p = FormattedExcinfo(showlocals=True)
-        loc = {'y': 5, 'z': 7, 'x': 3, '@x': 2, '__builtins__': {}}
-        reprlocals = p.repr_locals(loc)
-        assert reprlocals.lines
-        assert reprlocals.lines[0] == '__builtins__ = <builtins>'
-        assert reprlocals.lines[1] == 'x          = 3'
-        assert reprlocals.lines[2] == 'y          = 5'
-        assert reprlocals.lines[3] == 'z          = 7'
-
-    def test_repr_tracebackentry_lines(self, importasmod):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello\\nworld")
-        """)
-        excinfo = py.test.raises(ValueError, mod.func1)
-        excinfo.traceback = excinfo.traceback.filter()
-        p = FormattedExcinfo()
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
-
-        # test as intermittent entry
-        lines = reprtb.lines
-        assert lines[0] == '    def func1():'
-        assert lines[1] == '>       raise ValueError("hello\\nworld")'
-
-        # test as last entry
-        p = FormattedExcinfo(showlocals=True)
-        repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = repr_entry.lines
-        assert lines[0] == '    def func1():'
-        assert lines[1] == '>       raise ValueError("hello\\nworld")'
-        assert lines[2] == 'E       ValueError: hello'
-        assert lines[3] == 'E       world'
-        assert not lines[4:]
-
-        loc = repr_entry.reprlocals is not None
-        loc = repr_entry.reprfileloc
-        assert loc.path == mod.__file__
-        assert loc.lineno == 3
-        #assert loc.message == "ValueError: hello"
-
-    def test_repr_tracebackentry_lines(self, importasmod):
-        mod = importasmod("""
-            def func1(m, x, y, z):
-                raise ValueError("hello\\nworld")
-        """)
-        excinfo = py.test.raises(ValueError, mod.func1, "m"*90, 5, 13, "z"*120)
-        excinfo.traceback = excinfo.traceback.filter()
-        entry = excinfo.traceback[-1]
-        p = FormattedExcinfo(funcargs=True)
-        reprfuncargs = p.repr_args(entry)
-        assert reprfuncargs.args[0] == ('m', repr("m"*90))
-        assert reprfuncargs.args[1] == ('x', '5')
-        assert reprfuncargs.args[2] == ('y', '13')
-        assert reprfuncargs.args[3] == ('z', repr("z" * 120))
-
-        p = FormattedExcinfo(funcargs=True)
-        repr_entry = p.repr_traceback_entry(entry)
-        assert repr_entry.reprfuncargs.args == reprfuncargs.args
-        tw = TWMock()
-        repr_entry.toterminal(tw)
-        assert tw.lines[0] == "m = " + repr('m' * 90)
-        assert tw.lines[1] == "x = 5, y = 13"
-        assert tw.lines[2] == "z = " + repr('z' * 120)
-
-    def test_repr_tracebackentry_lines_var_kw_args(self, importasmod):
-        mod = importasmod("""
-            def func1(x, *y, **z):
-                raise ValueError("hello\\nworld")
-        """)
-        excinfo = py.test.raises(ValueError, mod.func1, 'a', 'b', c='d')
-        excinfo.traceback = excinfo.traceback.filter()
-        entry = excinfo.traceback[-1]
-        p = FormattedExcinfo(funcargs=True)
-        reprfuncargs = p.repr_args(entry)
-        assert reprfuncargs.args[0] == ('x', repr('a'))
-        assert reprfuncargs.args[1] == ('y', repr(('b',)))
-        assert reprfuncargs.args[2] == ('z', repr({'c': 'd'}))
-
-        p = FormattedExcinfo(funcargs=True)
-        repr_entry = p.repr_traceback_entry(entry)
-        assert repr_entry.reprfuncargs.args == reprfuncargs.args
-        tw = TWMock()
-        repr_entry.toterminal(tw)
-        assert tw.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"
-
-    def test_repr_tracebackentry_short(self, importasmod):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello")
-            def entry():
-                func1()
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-        p = FormattedExcinfo(style="short")
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
-        lines = reprtb.lines
-        basename = py.path.local(mod.__file__).basename
-        assert lines[0] == '    func1()'
-        assert basename in str(reprtb.reprfileloc.path)
-        assert reprtb.reprfileloc.lineno == 5
-
-        # test last entry
-        p = FormattedExcinfo(style="short")
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = reprtb.lines
-        assert lines[0] == '    raise ValueError("hello")'
-        assert lines[1] == 'E   ValueError: hello'
-        assert basename in str(reprtb.reprfileloc.path)
-        assert reprtb.reprfileloc.lineno == 3
-
-    def test_repr_tracebackentry_no(self, importasmod):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello")
-            def entry():
-                func1()
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-        p = FormattedExcinfo(style="no")
-        p.repr_traceback_entry(excinfo.traceback[-2])
-
-        p = FormattedExcinfo(style="no")
-        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = reprentry.lines
-        assert lines[0] == 'E   ValueError: hello'
-        assert not lines[1:]
-
-    def test_repr_traceback_tbfilter(self, importasmod):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-        p = FormattedExcinfo(tbfilter=True)
-        reprtb = p.repr_traceback(excinfo)
-        assert len(reprtb.reprentries) == 2
-        p = FormattedExcinfo(tbfilter=False)
-        reprtb = p.repr_traceback(excinfo)
-        assert len(reprtb.reprentries) == 3
-
-    def test_traceback_short_no_source(self, importasmod, monkeypatch):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello")
-            def entry():
-                func1()
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-        from py._code.code import Code
-        monkeypatch.setattr(Code, 'path', 'bogus')
-        excinfo.traceback[0].frame.code.path = "bogus"
-        p = FormattedExcinfo(style="short")
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
-        lines = reprtb.lines
-        last_p = FormattedExcinfo(style="short")
-        last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        last_lines = last_reprtb.lines
-        monkeypatch.undo()
-        basename = py.path.local(mod.__file__).basename
-        assert lines[0] == '    func1()'
-
-        assert last_lines[0] == '    raise ValueError("hello")'
-        assert last_lines[1] == 'E   ValueError: hello'
-
-    def test_repr_traceback_and_excinfo(self, importasmod):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-
-        for style in ("long", "short"):
-            p = FormattedExcinfo(style=style)
-            reprtb = p.repr_traceback(excinfo)
-            assert len(reprtb.reprentries) == 2
-            assert reprtb.style == style
-            assert not reprtb.extraline
-            repr = p.repr_excinfo(excinfo)
-            assert repr.reprtraceback
-            assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries)
-            assert repr.reprcrash.path.endswith("mod.py")
-            assert repr.reprcrash.message == "ValueError: 0"
-
-    def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-
-        p = FormattedExcinfo()
-        def raiseos():
-            raise OSError(2)
-        monkeypatch.setattr(py.std.os, 'getcwd', raiseos)
-        assert p._makepath(__file__) == __file__
-        reprtb = p.repr_traceback(excinfo)
-
-    def test_repr_excinfo_addouterr(self, importasmod):
-        mod = importasmod("""
-            def entry():
-                raise ValueError()
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-        repr = excinfo.getrepr()
-        repr.addsection("title", "content")
-        twmock = TWMock()
-        repr.toterminal(twmock)
-        assert twmock.lines[-1] == "content"
-        assert twmock.lines[-2] == ("-", "title")
-
-    def test_repr_excinfo_reprcrash(self, importasmod):
-        mod = importasmod("""
-            def entry():
-                raise ValueError()
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-        repr = excinfo.getrepr()
-        assert repr.reprcrash.path.endswith("mod.py")
-        assert repr.reprcrash.lineno == 3
-        assert repr.reprcrash.message == "ValueError"
-        assert str(repr.reprcrash).endswith("mod.py:3: ValueError")
-
-    def test_repr_traceback_recursion(self, importasmod):
-        mod = importasmod("""
-            def rec2(x):
-                return rec1(x+1)
-            def rec1(x):
-                return rec2(x-1)
-            def entry():
-                rec1(42)
-        """)
-        excinfo = py.test.raises(RuntimeError, mod.entry)
-
-        for style in ("short", "long", "no"):
-            p = FormattedExcinfo(style="short")
-            reprtb = p.repr_traceback(excinfo)
-            assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
-            assert str(reprtb)
-
-    def test_tb_entry_AssertionError(self, importasmod):
-        # probably this test is a bit redundant
-        # as py/magic/testing/test_assertion.py
-        # already tests correctness of
-        # assertion-reinterpretation  logic
-        mod = importasmod("""
-            def somefunc():
-                x = 1
-                assert x == 2
-        """)
-        excinfo = py.test.raises(AssertionError, mod.somefunc)
-
-        p = FormattedExcinfo()
-        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = reprentry.lines
-        assert lines[-1] == "E       assert 1 == 2"
-
-    def test_reprexcinfo_getrepr(self, importasmod):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = py.test.raises(ValueError, mod.entry)
-
-        for style in ("short", "long", "no"):
-            for showlocals in (True, False):
-                repr = excinfo.getrepr(style=style, showlocals=showlocals)
-                assert isinstance(repr, ReprExceptionInfo)
-                assert repr.reprtraceback.style == style
-
-    def test_reprexcinfo_unicode(self):
-        from py._code.code import TerminalRepr
-        class MyRepr(TerminalRepr):
-            def toterminal(self, tw):
-                tw.line(py.builtin._totext("я", "utf-8"))
-        x = py.builtin._totext(MyRepr())
-        assert x == py.builtin._totext("я", "utf-8")
-
-    def test_toterminal_long(self, importasmod):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = py.test.raises(ValueError, mod.f)
-        excinfo.traceback = excinfo.traceback.filter()
-        repr = excinfo.getrepr()
-        tw = TWMock()
-        repr.toterminal(tw)
-        assert tw.lines[0] == ""
-        tw.lines.pop(0)
-        assert tw.lines[0] == "    def f():"
-        assert tw.lines[1] == ">       g(3)"
-        assert tw.lines[2] == ""
-        assert tw.lines[3].endswith("mod.py:5: ")
-        assert tw.lines[4] == ("_ ", None)
-        assert tw.lines[5] == ""
-        assert tw.lines[6] == "    def g(x):"
-        assert tw.lines[7] == ">       raise ValueError(x)"
-        assert tw.lines[8] == "E       ValueError: 3"
-        assert tw.lines[9] == ""
-        assert tw.lines[10].endswith("mod.py:3: ValueError")
-
-    def test_toterminal_long_missing_source(self, importasmod, tmpdir):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = py.test.raises(ValueError, mod.f)
-        tmpdir.join('mod.py').remove()
-        excinfo.traceback = excinfo.traceback.filter()
-        repr = excinfo.getrepr()
-        tw = TWMock()
-        repr.toterminal(tw)
-        assert tw.lines[0] == ""
-        tw.lines.pop(0)
-        assert tw.lines[0] == ">   ???"
-        assert tw.lines[1] == ""
-        assert tw.lines[2].endswith("mod.py:5: ")
-        assert tw.lines[3] == ("_ ", None)
-        assert tw.lines[4] == ""
-        assert tw.lines[5] == ">   ???"
-        assert tw.lines[6] == "E   ValueError: 3"
-        assert tw.lines[7] == ""
-        assert tw.lines[8].endswith("mod.py:3: ValueError")
-
-    def test_toterminal_long_incomplete_source(self, importasmod, tmpdir):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = py.test.raises(ValueError, mod.f)
-        tmpdir.join('mod.py').write('asdf')
-        excinfo.traceback = excinfo.traceback.filter()
-        repr = excinfo.getrepr()
-        tw = TWMock()
-        repr.toterminal(tw)
-        assert tw.lines[0] == ""
-        tw.lines.pop(0)
-        assert tw.lines[0] == ">   ???"
-        assert tw.lines[1] == ""
-        assert tw.lines[2].endswith("mod.py:5: ")
-        assert tw.lines[3] == ("_ ", None)
-        assert tw.lines[4] == ""
-        assert tw.lines[5] == ">   ???"
-        assert tw.lines[6] == "E   ValueError: 3"
-        assert tw.lines[7] == ""
-        assert tw.lines[8].endswith("mod.py:3: ValueError")
-
-    def test_toterminal_long_filenames(self, importasmod):
-        mod = importasmod("""
-            def f():
-                raise ValueError()
-        """)
-        excinfo = py.test.raises(ValueError, mod.f)
-        tw = TWMock()
-        path = py.path.local(mod.__file__)
-        old = path.dirpath().chdir()
-        try:
-            repr = excinfo.getrepr(abspath=False)
-            repr.toterminal(tw)
-            line = tw.lines[-1]
-            x = py.path.local().bestrelpath(path)
-            if len(x) < len(str(path)):
-                assert line == "mod.py:3: ValueError"
-
-            repr = excinfo.getrepr(abspath=True)
-            repr.toterminal(tw)
-            line = tw.lines[-1]
-            assert line == "%s:3: ValueError" %(path,)
-        finally:
-            old.chdir()
-
-    @py.test.mark.multi(reproptions=[
-        {'style': style, 'showlocals': showlocals,
-         'funcargs': funcargs, 'tbfilter': tbfilter
-        } for style in ("long", "short", "no")
-            for showlocals in (True, False)
-                for tbfilter in (True, False)
-                    for funcargs in (True, False)])
-    def test_format_excinfo(self, importasmod, reproptions):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = py.test.raises(ValueError, mod.f)
-        tw = py.io.TerminalWriter(stringio=True)
-        repr = excinfo.getrepr(**reproptions)
-        repr.toterminal(tw)
-        assert tw.stringio.getvalue()
-
-
-    def test_native_style(self):
-        excinfo = self.excinfo_from_exec("""
-            assert 0
-        """)
-        repr = excinfo.getrepr(style='native')
-        assert "assert 0" in str(repr.reprcrash)
-        s = str(repr)
-        assert s.startswith('Traceback (most recent call last):\n  File')
-        assert s.endswith('\nAssertionError: assert 0')
-        assert 'exec (source.compile())' in s
-        # python 2.4 fails to get the source line for the assert
-        if py.std.sys.version_info >= (2, 5):
-            assert s.count('assert 0') == 2
-
-    def test_traceback_repr_style(self, importasmod):
-        mod = importasmod("""
-            def f():
-                g()
-            def g():
-                h()
-            def h():
-                i()
-            def i():
-                raise ValueError()
-        """)
-        excinfo = py.test.raises(ValueError, mod.f)
-        excinfo.traceback = excinfo.traceback.filter()
-        excinfo.traceback[1].set_repr_style("short")
-        excinfo.traceback[2].set_repr_style("short")
-        r = excinfo.getrepr(style="long")
-        tw = TWMock()
-        r.toterminal(tw)
-        for line in tw.lines: print (line)
-        assert tw.lines[0] == ""
-        assert tw.lines[1] == "    def f():"
-        assert tw.lines[2] == ">       g()"
-        assert tw.lines[3] == ""
-        assert tw.lines[4].endswith("mod.py:3: ")
-        assert tw.lines[5] == ("_ ", None)
-        assert tw.lines[6].endswith("in g")
-        assert tw.lines[7] == "    h()"
-        assert tw.lines[8].endswith("in h")
-        assert tw.lines[9] == "    i()"
-        assert tw.lines[10] == ("_ ", None)
-        assert tw.lines[11] == ""
-        assert tw.lines[12] == "    def i():"
-        assert tw.lines[13] == ">       raise ValueError()"
-        assert tw.lines[14] == "E       ValueError"
-        assert tw.lines[15] == ""
-        assert tw.lines[16].endswith("mod.py:9: ValueError")
diff --git a/tools/py/testing/code/test_source.py b/tools/py/testing/code/test_source.py
deleted file mode 100644
index 830de2c..0000000
--- a/tools/py/testing/code/test_source.py
+++ /dev/null
@@ -1,651 +0,0 @@
-from py.code import Source
-import py
-import sys
-
-from py._code.source import _ast
-if _ast is not None:
-    astonly = py.test.mark.nothing
-else:
-    astonly = py.test.mark.xfail("True", reason="only works with AST-compile")
-
-failsonjython = py.test.mark.xfail("sys.platform.startswith('java')")
-
-def test_source_str_function():
-    x = Source("3")
-    assert str(x) == "3"
-
-    x = Source("   3")
-    assert str(x) == "3"
-
-    x = Source("""
-        3
-    """, rstrip=False)
-    assert str(x) == "\n3\n    "
-
-    x = Source("""
-        3
-    """, rstrip=True)
-    assert str(x) == "\n3"
-
-def test_unicode():
-    try:
-        unicode
-    except NameError:
-        return
-    x = Source(unicode("4"))
-    assert str(x) == "4"
-    co = py.code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval')
-    val = eval(co)
-    assert isinstance(val, unicode)
-
-def test_source_from_function():
-    source = py.code.Source(test_source_str_function)
-    assert str(source).startswith('def test_source_str_function():')
-
-def test_source_from_method():
-    class TestClass:
-        def test_method(self):
-            pass
-    source = py.code.Source(TestClass().test_method)
-    assert source.lines == ["def test_method(self):",
-                            "    pass"]
-
-def test_source_from_lines():
-    lines = ["a \n", "b\n", "c"]
-    source = py.code.Source(lines)
-    assert source.lines == ['a ', 'b', 'c']
-
-def test_source_from_inner_function():
-    def f():
-        pass
-    source = py.code.Source(f, deindent=False)
-    assert str(source).startswith('    def f():')
-    source = py.code.Source(f)
-    assert str(source).startswith('def f():')
-
-def test_source_putaround_simple():
-    source = Source("raise ValueError")
-    source = source.putaround(
-        "try:", """\
-        except ValueError:
-            x = 42
-        else:
-            x = 23""")
-    assert str(source)=="""\
-try:
-    raise ValueError
-except ValueError:
-    x = 42
-else:
-    x = 23"""
-
-def test_source_putaround():
-    source = Source()
-    source = source.putaround("""
-        if 1:
-            x=1
-    """)
-    assert str(source).strip() == "if 1:\n    x=1"
-
-def test_source_strips():
-    source = Source("")
-    assert source == Source()
-    assert str(source) == ''
-    assert source.strip() == source
-
-def test_source_strip_multiline():
-    source = Source()
-    source.lines = ["", " hello", "  "]
-    source2 = source.strip()
-    assert source2.lines == [" hello"]
-
-def test_syntaxerror_rerepresentation():
-    ex = py.test.raises(SyntaxError, py.code.compile, 'xyz xyz')
-    assert ex.value.lineno == 1
-    assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython?
-    assert ex.value.text.strip(), 'x x'
-
-def test_isparseable():
-    assert Source("hello").isparseable()
-    assert Source("if 1:\n  pass").isparseable()
-    assert Source(" \nif 1:\n  pass").isparseable()
-    assert not Source("if 1:\n").isparseable()
-    assert not Source(" \nif 1:\npass").isparseable()
-    assert not Source(chr(0)).isparseable()
-
-class TestAccesses:
-    source = Source("""\
-        def f(x):
-            pass
-        def g(x):
-            pass
-    """)
-    def test_getrange(self):
-        x = self.source[0:2]
-        assert x.isparseable()
-        assert len(x.lines) == 2
-        assert str(x) == "def f(x):\n    pass"
-
-    def test_getline(self):
-        x = self.source[0]
-        assert x == "def f(x):"
-
-    def test_len(self):
-        assert len(self.source) == 4
-
-    def test_iter(self):
-        l = [x for x in self.source]
-        assert len(l) == 4
-
-class TestSourceParsingAndCompiling:
-    source = Source("""\
-        def f(x):
-            assert (x ==
-                    3 +
-                    4)
-    """).strip()
-
-    def test_compile(self):
-        co = py.code.compile("x=3")
-        d = {}
-        exec (co, d)
-        assert d['x'] == 3
-
-    def test_compile_and_getsource_simple(self):
-        co = py.code.compile("x=3")
-        exec (co)
-        source = py.code.Source(co)
-        assert str(source) == "x=3"
-
-    def test_compile_and_getsource_through_same_function(self):
-        def gensource(source):
-            return py.code.compile(source)
-        co1 = gensource("""
-            def f():
-                raise KeyError()
-        """)
-        co2 = gensource("""
-            def f():
-                raise ValueError()
-        """)
-        source1 = py.std.inspect.getsource(co1)
-        assert 'KeyError' in source1
-        source2 = py.std.inspect.getsource(co2)
-        assert 'ValueError' in source2
-
-    def test_getstatement(self):
-        #print str(self.source)
-        ass = str(self.source[1:])
-        for i in range(1, 4):
-            #print "trying start in line %r" % self.source[i]
-            s = self.source.getstatement(i)
-            #x = s.deindent()
-            assert str(s) == ass
-
-    def test_getstatementrange_triple_quoted(self):
-        #print str(self.source)
-        source = Source("""hello('''
-        ''')""")
-        s = source.getstatement(0)
-        assert s == str(source)
-        s = source.getstatement(1)
-        assert s == str(source)
-
-    @astonly
-    def test_getstatementrange_within_constructs(self):
-        source = Source("""\
-            try:
-                try:
-                    raise ValueError
-                except SomeThing:
-                    pass
-            finally:
-                42
-        """)
-        assert len(source) == 7
-        # check all lineno's that could occur in a traceback
-        #assert source.getstatementrange(0) == (0, 7)
-        #assert source.getstatementrange(1) == (1, 5)
-        assert source.getstatementrange(2) == (2, 3)
-        assert source.getstatementrange(3) == (3, 4)
-        assert source.getstatementrange(4) == (4, 5)
-        #assert source.getstatementrange(5) == (0, 7)
-        assert source.getstatementrange(6) == (6, 7)
-
-    def test_getstatementrange_bug(self):
-        source = Source("""\
-            try:
-                x = (
-                   y +
-                   z)
-            except:
-                pass
-        """)
-        assert len(source) == 6
-        assert source.getstatementrange(2) == (1, 4)
-
-    def test_getstatementrange_bug2(self):
-        source = Source("""\
-            assert (
-                33
-                ==
-                [
-                  X(3,
-                      b=1, c=2
-                   ),
-                ]
-              )
-        """)
-        assert len(source) == 9
-        assert source.getstatementrange(5) == (0, 9)
-
-    def test_getstatementrange_ast_issue58(self):
-        source = Source("""\
-
-            def test_some():
-                for a in [a for a in
-                    CAUSE_ERROR]: pass
-
-            x = 3
-        """)
-        assert getstatement(2, source).lines == source.lines[2:3]
-        assert getstatement(3, source).lines == source.lines[3:4]
-
-    @py.test.mark.skipif("sys.version_info < (2,6)")
-    def test_getstatementrange_out_of_bounds_py3(self):
-        source = Source("if xxx:\n   from .collections import something")
-        r = source.getstatementrange(1)
-        assert r == (1,2)
-
-    def test_getstatementrange_with_syntaxerror_issue7(self):
-        source = Source(":")
-        py.test.raises(SyntaxError, lambda: source.getstatementrange(0))
-
-    @py.test.mark.skipif("sys.version_info < (2,6)")
-    def test_compile_to_ast(self):
-        import ast
-        source = Source("x = 4")
-        mod = source.compile(flag=ast.PyCF_ONLY_AST)
-        assert isinstance(mod, ast.Module)
-        compile(mod, "<filename>", "exec")
-
-    def test_compile_and_getsource(self):
-        co = self.source.compile()
-        py.builtin.exec_(co, globals())
-        f(7)
-        excinfo = py.test.raises(AssertionError, "f(6)")
-        frame = excinfo.traceback[-1].frame
-        stmt = frame.code.fullsource.getstatement(frame.lineno)
-        #print "block", str(block)
-        assert str(stmt).strip().startswith('assert')
-
-    def test_compilefuncs_and_path_sanity(self):
-        def check(comp, name):
-            co = comp(self.source, name)
-            if not name:
-                expected = "codegen %s:%d>" %(mypath, mylineno+2+1)
-            else:
-                expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+1)
-            fn = co.co_filename
-            assert fn.endswith(expected)
-
-        mycode = py.code.Code(self.test_compilefuncs_and_path_sanity)
-        mylineno = mycode.firstlineno
-        mypath = mycode.path
-
-        for comp in py.code.compile, py.code.Source.compile:
-            for name in '', None, 'my':
-                yield check, comp, name
-
-    def test_offsetless_synerr(self):
-        py.test.raises(SyntaxError, py.code.compile, "lambda a,a: 0", mode='eval')
-
-def test_getstartingblock_singleline():
-    class A:
-        def __init__(self, *args):
-            frame = sys._getframe(1)
-            self.source = py.code.Frame(frame).statement
-
-    x = A('x', 'y')
-
-    l = [i for i in x.source.lines if i.strip()]
-    assert len(l) == 1
-
-def test_getstartingblock_multiline():
-    class A:
-        def __init__(self, *args):
-            frame = sys._getframe(1)
-            self.source = py.code.Frame(frame).statement
-
-    x = A('x',
-          'y' \
-          ,
-          'z')
-
-    l = [i for i in x.source.lines if i.strip()]
-    assert len(l) == 4
-
-def test_getline_finally():
-    def c(): pass
-    excinfo = py.test.raises(TypeError, """
-           teardown = None
-           try:
-                c(1)
-           finally:
-                if teardown:
-                    teardown()
-    """)
-    source = excinfo.traceback[-1].statement
-    assert str(source).strip() == 'c(1)'
-
-def test_getfuncsource_dynamic():
-    source = """
-        def f():
-            raise ValueError
-
-        def g(): pass
-    """
-    co = py.code.compile(source)
-    py.builtin.exec_(co, globals())
-    assert str(py.code.Source(f)).strip() == 'def f():\n    raise ValueError'
-    assert str(py.code.Source(g)).strip() == 'def g(): pass'
-
-
-def test_getfuncsource_with_multine_string():
-    def f():
-        c = '''while True:
-    pass
-'''
-    assert str(py.code.Source(f)).strip() == "def f():\n    c = '''while True:\n    pass\n'''"
-
-
-def test_deindent():
-    from py._code.source import deindent as deindent
-    assert deindent(['\tfoo', '\tbar', ]) == ['foo', 'bar']
-
-    def f():
-        c = '''while True:
-    pass
-'''
-    import inspect
-    lines = deindent(inspect.getsource(f).splitlines())
-    assert lines == ["def f():", "    c = '''while True:", "    pass", "'''"]
-
-    source = """
-        def f():
-            def g():
-                pass
-    """
-    lines = deindent(source.splitlines())
-    assert lines == ['', 'def f():', '    def g():', '        pass', '    ']
-
-@py.test.mark.xfail("sys.version_info[:3] < (2,7,0) or "
-    "((3,0) <= sys.version_info[:2] < (3,2))")
-def test_source_of_class_at_eof_without_newline(tmpdir):
-    # this test fails because the implicit inspect.getsource(A) below
-    # does not return the "x = 1" last line.
-    source = py.code.Source('''
-        class A(object):
-            def method(self):
-                x = 1
-    ''')
-    path = tmpdir.join("a.py")
-    path.write(source)
-    s2 = py.code.Source(tmpdir.join("a.py").pyimport().A)
-    assert str(source).strip() == str(s2).strip()
-
-if True:
-    def x():
-        pass
-
-def test_getsource_fallback():
-    from py._code.source import getsource
-    expected = """def x():
-    pass"""
-    src = getsource(x)
-    assert src == expected
-
-def test_idem_compile_and_getsource():
-    from py._code.source import getsource
-    expected = "def x(): pass"
-    co = py.code.compile(expected)
-    src = getsource(co)
-    assert src == expected
-
-def test_findsource_fallback():
-    from py._code.source import findsource
-    src, lineno = findsource(x)
-    assert 'test_findsource_simple' in str(src)
-    assert src[lineno] == '    def x():'
-
-def test_findsource():
-    from py._code.source import findsource
-    co = py.code.compile("""if 1:
-    def x():
-        pass
-""")
-
-    src, lineno = findsource(co)
-    assert 'if 1:' in str(src)
-
-    d = {}
-    eval(co, d)
-    src, lineno = findsource(d['x'])
-    assert 'if 1:' in str(src)
-    assert src[lineno] == "    def x():"
-
-
-def test_getfslineno():
-    from py.code import getfslineno
-
-    def f(x):
-        pass
-
-    fspath, lineno = getfslineno(f)
-
-    assert fspath.basename == "test_source.py"
-    assert lineno == py.code.getrawcode(f).co_firstlineno-1 # see findsource
-
-    class A(object):
-        pass
-
-    fspath, lineno = getfslineno(A)
-
-    _, A_lineno = py.std.inspect.findsource(A)
-    assert fspath.basename == "test_source.py"
-    assert lineno == A_lineno
-
-    assert getfslineno(3) == ("", -1)
-    class B:
-        pass
-    B.__name__ = "B2"
-    assert getfslineno(B)[1] == -1
-
-def test_code_of_object_instance_with_call():
-    class A:
-        pass
-    py.test.raises(TypeError, lambda: py.code.Source(A()))
-    class WithCall:
-        def __call__(self):
-            pass
-
-    code = py.code.Code(WithCall())
-    assert 'pass' in str(code.source())
-
-    class Hello(object):
-        def __call__(self):
-            pass
-    py.test.raises(TypeError, lambda: py.code.Code(Hello))
-
-
-def getstatement(lineno, source):
-    from py._code.source import getstatementrange_ast
-    source = py.code.Source(source, deindent=False)
-    ast, start, end = getstatementrange_ast(lineno, source)
-    return source[start:end]
-
-def test_oneline():
-    source = getstatement(0, "raise ValueError")
-    assert str(source) == "raise ValueError"
-
-def test_comment_and_no_newline_at_end():
-    from py._code.source import getstatementrange_ast
-    source = Source(['def test_basic_complex():',
-                     '    assert 1 == 2',
-                     '# vim: filetype=pyopencl:fdm=marker'])
-    ast, start, end = getstatementrange_ast(1, source)
-    assert end == 2
-
-def test_oneline_and_comment():
-    source = getstatement(0, "raise ValueError\n#hello")
-    assert str(source) == "raise ValueError"
-
-def test_comments():
-    source = '''def test():
-    "comment 1"
-    x = 1
-      # comment 2
-    # comment 3
-
-    assert False
-
-"""
-comment 4
-"""
-'''
-    for line in range(2,6):
-        assert str(getstatement(line, source)) == '    x = 1'
-    for line in range(6,10):
-        assert str(getstatement(line, source)) == '    assert False'
-    assert str(getstatement(10, source)) == '"""'
-
-def test_comment_in_statement():
-    source = '''test(foo=1,
-    # comment 1
-    bar=2)
-'''
-    for line in range(1,3):
-        assert str(getstatement(line, source)) == \
-               'test(foo=1,\n    # comment 1\n    bar=2)'
-
-def test_single_line_else():
-    source = getstatement(1, "if False: 2\nelse: 3")
-    assert str(source) == "else: 3"
-
-def test_single_line_finally():
-    source = getstatement(1, "try: 1\nfinally: 3")
-    assert str(source) == "finally: 3"
-
-def test_issue55():
-    source = ('def round_trip(dinp):\n  assert 1 == dinp\n'
-              'def test_rt():\n  round_trip("""\n""")\n')
-    s = getstatement(3, source)
-    assert str(s) == '  round_trip("""\n""")'
-
-
-def XXXtest_multiline():
-    source = getstatement(0, """\
-raise ValueError(
-    23
-)
-x = 3
-""")
-    assert str(source) == "raise ValueError(\n    23\n)"
-
-class TestTry:
-    pytestmark = astonly
-    source = """\
-try:
-    raise ValueError
-except Something:
-    raise IndexError(1)
-else:
-    raise KeyError()
-"""
-
-    def test_body(self):
-        source = getstatement(1, self.source)
-        assert str(source) == "    raise ValueError"
-
-    def test_except_line(self):
-        source = getstatement(2, self.source)
-        assert str(source) == "except Something:"
-
-    def test_except_body(self):
-        source = getstatement(3, self.source)
-        assert str(source) == "    raise IndexError(1)"
-
-    def test_else(self):
-        source = getstatement(5, self.source)
-        assert str(source) == "    raise KeyError()"
-
-class TestTryFinally:
-    source = """\
-try:
-    raise ValueError
-finally:
-    raise IndexError(1)
-"""
-
-    def test_body(self):
-        source = getstatement(1, self.source)
-        assert str(source) == "    raise ValueError"
-
-    def test_finally(self):
-        source = getstatement(3, self.source)
-        assert str(source) == "    raise IndexError(1)"
-
-
-
-class TestIf:
-    pytestmark = astonly
-    source = """\
-if 1:
-    y = 3
-elif False:
-    y = 5
-else:
-    y = 7
-"""
-
-    def test_body(self):
-        source = getstatement(1, self.source)
-        assert str(source) == "    y = 3"
-
-    def test_elif_clause(self):
-        source = getstatement(2, self.source)
-        assert str(source) == "elif False:"
-
-    def test_elif(self):
-        source = getstatement(3, self.source)
-        assert str(source) == "    y = 5"
-
-    def test_else(self):
-        source = getstatement(5, self.source)
-        assert str(source) == "    y = 7"
-
-def test_semicolon():
-    s = """\
-hello ; pytest.skip()
-"""
-    source = getstatement(0, s)
-    assert str(source) == s.strip()
-
-def test_def_online():
-    s = """\
-def func(): raise ValueError(42)
-
-def something():
-    pass
-"""
-    source = getstatement(0, s)
-    assert str(source) == "def func(): raise ValueError(42)"
-
-def XXX_test_expression_multiline():
-    source = """\
-something
-'''
-'''"""
-    result = getstatement(1, source)
-    assert str(result) == "'''\n'''"
-
diff --git a/tools/py/testing/io_/test_capture.py b/tools/py/testing/io_/test_capture.py
deleted file mode 100644
index 5745e12..0000000
--- a/tools/py/testing/io_/test_capture.py
+++ /dev/null
@@ -1,501 +0,0 @@
-from __future__ import with_statement
-
-import os, sys
-import py
-
-needsdup = py.test.mark.skipif("not hasattr(os, 'dup')")
-
-from py.builtin import print_
-
-if sys.version_info >= (3,0):
-    def tobytes(obj):
-        if isinstance(obj, str):
-            obj = obj.encode('UTF-8')
-        assert isinstance(obj, bytes)
-        return obj
-    def totext(obj):
-        if isinstance(obj, bytes):
-            obj = str(obj, 'UTF-8')
-        assert isinstance(obj, str)
-        return obj
-else:
-    def tobytes(obj):
-        if isinstance(obj, unicode):
-            obj = obj.encode('UTF-8')
-        assert isinstance(obj, str)
-        return obj
-    def totext(obj):
-        if isinstance(obj, str):
-            obj = unicode(obj, 'UTF-8')
-        assert isinstance(obj, unicode)
-        return obj
-
-def oswritebytes(fd, obj):
-    os.write(fd, tobytes(obj))
-
-class TestTextIO:
-    def test_text(self):
-        f = py.io.TextIO()
-        f.write("hello")
-        s = f.getvalue()
-        assert s == "hello"
-        f.close()
-
-    def test_unicode_and_str_mixture(self):
-        f = py.io.TextIO()
-        if sys.version_info >= (3,0):
-            f.write("\u00f6")
-            py.test.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))")
-        else:
-            f.write(unicode("\u00f6", 'UTF-8'))
-            f.write("hello") # bytes
-            s = f.getvalue()
-            f.close()
-            assert isinstance(s, unicode)
-
-def test_bytes_io():
-    f = py.io.BytesIO()
-    f.write(tobytes("hello"))
-    py.test.raises(TypeError, "f.write(totext('hello'))")
-    s = f.getvalue()
-    assert s == tobytes("hello")
-
-def test_dontreadfrominput():
-    from py._io.capture import DontReadFromInput
-    f = DontReadFromInput()
-    assert not f.isatty()
-    py.test.raises(IOError, f.read)
-    py.test.raises(IOError, f.readlines)
-    py.test.raises(IOError, iter, f)
-    py.test.raises(ValueError, f.fileno)
-    f.close() # just for completeness
-
-def pytest_funcarg__tmpfile(request):
-    testdir = request.getfuncargvalue("testdir")
-    f = testdir.makepyfile("").open('wb+')
-    request.addfinalizer(f.close)
-    return f
-
-@needsdup
-def test_dupfile(tmpfile):
-    flist = []
-    for i in range(5):
-        nf = py.io.dupfile(tmpfile, encoding="utf-8")
-        assert nf != tmpfile
-        assert nf.fileno() != tmpfile.fileno()
-        assert nf not in flist
-        print_(i, end="", file=nf)
-        flist.append(nf)
-    for i in range(5):
-        f = flist[i]
-        f.close()
-    tmpfile.seek(0)
-    s = tmpfile.read()
-    assert "01234" in repr(s)
-    tmpfile.close()
-
-def test_dupfile_no_mode():
-    """
-    dupfile should trap an AttributeError and return f if no mode is supplied.
-    """
-    class SomeFileWrapper(object):
-        "An object with a fileno method but no mode attribute"
-        def fileno(self):
-            return 1
-    tmpfile = SomeFileWrapper()
-    assert py.io.dupfile(tmpfile) is tmpfile
-    with py.test.raises(AttributeError):
-        py.io.dupfile(tmpfile, raising=True)
-
-def lsof_check(func):
-    pid = os.getpid()
-    try:
-        out = py.process.cmdexec("lsof -p %d" % pid)
-    except py.process.cmdexec.Error:
-        py.test.skip("could not run 'lsof'")
-    func()
-    out2 = py.process.cmdexec("lsof -p %d" % pid)
-    len1 = len([x for x in out.split("\n") if "REG" in x])
-    len2 = len([x for x in out2.split("\n") if "REG" in x])
-    assert len2 < len1 + 3, out2
-
-class TestFDCapture:
-    pytestmark = needsdup
-
-    def test_not_now(self, tmpfile):
-        fd = tmpfile.fileno()
-        cap = py.io.FDCapture(fd, now=False)
-        data = tobytes("hello")
-        os.write(fd, data)
-        f = cap.done()
-        s = f.read()
-        assert not s
-        cap = py.io.FDCapture(fd, now=False)
-        cap.start()
-        os.write(fd, data)
-        f = cap.done()
-        s = f.read()
-        assert s == "hello"
-
-    def test_simple(self, tmpfile):
-        fd = tmpfile.fileno()
-        cap = py.io.FDCapture(fd)
-        data = tobytes("hello")
-        os.write(fd, data)
-        f = cap.done()
-        s = f.read()
-        assert s == "hello"
-        f.close()
-
-    def test_simple_many(self, tmpfile):
-        for i in range(10):
-            self.test_simple(tmpfile)
-
-    def test_simple_many_check_open_files(self, tmpfile):
-        lsof_check(lambda: self.test_simple_many(tmpfile))
-
-    def test_simple_fail_second_start(self, tmpfile):
-        fd = tmpfile.fileno()
-        cap = py.io.FDCapture(fd)
-        f = cap.done()
-        py.test.raises(ValueError, cap.start)
-        f.close()
-
-    def test_stderr(self):
-        cap = py.io.FDCapture(2, patchsys=True)
-        print_("hello", file=sys.stderr)
-        f = cap.done()
-        s = f.read()
-        assert s == "hello\n"
-
-    def test_stdin(self, tmpfile):
-        tmpfile.write(tobytes("3"))
-        tmpfile.seek(0)
-        cap = py.io.FDCapture(0, tmpfile=tmpfile)
-        # check with os.read() directly instead of raw_input(), because
-        # sys.stdin itself may be redirected (as py.test now does by default)
-        x = os.read(0, 100).strip()
-        f = cap.done()
-        assert x == tobytes("3")
-
-    def test_writeorg(self, tmpfile):
-        data1, data2 = tobytes("foo"), tobytes("bar")
-        try:
-            cap = py.io.FDCapture(tmpfile.fileno())
-            tmpfile.write(data1)
-            cap.writeorg(data2)
-        finally:
-            tmpfile.close()
-        f = cap.done()
-        scap = f.read()
-        assert scap == totext(data1)
-        stmp = open(tmpfile.name, 'rb').read()
-        assert stmp == data2
-
-
-class TestStdCapture:
-    def getcapture(self, **kw):
-        return py.io.StdCapture(**kw)
-
-    def test_capturing_done_simple(self):
-        cap = self.getcapture()
-        sys.stdout.write("hello")
-        sys.stderr.write("world")
-        outfile, errfile = cap.done()
-        s = outfile.read()
-        assert s == "hello"
-        s = errfile.read()
-        assert s == "world"
-
-    def test_capturing_reset_simple(self):
-        cap = self.getcapture()
-        print("hello world")
-        sys.stderr.write("hello error\n")
-        out, err = cap.reset()
-        assert out == "hello world\n"
-        assert err == "hello error\n"
-
-    def test_capturing_readouterr(self):
-        cap = self.getcapture()
-        try:
-            print ("hello world")
-            sys.stderr.write("hello error\n")
-            out, err = cap.readouterr()
-            assert out == "hello world\n"
-            assert err == "hello error\n"
-            sys.stderr.write("error2")
-        finally:
-            out, err = cap.reset()
-        assert err == "error2"
-
-    def test_capturing_readouterr_unicode(self):
-        cap = self.getcapture()
-        print ("hx\xc4\x85\xc4\x87")
-        out, err = cap.readouterr()
-        assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
-
-    @py.test.mark.skipif('sys.version_info >= (3,)',
-                      reason='text output different for bytes on python3')
-    def test_capturing_readouterr_decode_error_handling(self):
-        cap = self.getcapture()
-        # triggered a internal error in pytest
-        print('\xa6')
-        out, err = cap.readouterr()
-        assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
-
-    def test_capturing_mixed(self):
-        cap = self.getcapture(mixed=True)
-        sys.stdout.write("hello ")
-        sys.stderr.write("world")
-        sys.stdout.write(".")
-        out, err = cap.reset()
-        assert out.strip() == "hello world."
-        assert not err
-
-    def test_reset_twice_error(self):
-        cap = self.getcapture()
-        print ("hello")
-        out, err = cap.reset()
-        py.test.raises(ValueError, cap.reset)
-        assert out == "hello\n"
-        assert not err
-
-    def test_capturing_modify_sysouterr_in_between(self):
-        oldout = sys.stdout
-        olderr = sys.stderr
-        cap = self.getcapture()
-        sys.stdout.write("hello")
-        sys.stderr.write("world")
-        sys.stdout = py.io.TextIO()
-        sys.stderr = py.io.TextIO()
-        print ("not seen")
-        sys.stderr.write("not seen\n")
-        out, err = cap.reset()
-        assert out == "hello"
-        assert err == "world"
-        assert sys.stdout == oldout
-        assert sys.stderr == olderr
-
-    def test_capturing_error_recursive(self):
-        cap1 = self.getcapture()
-        print ("cap1")
-        cap2 = self.getcapture()
-        print ("cap2")
-        out2, err2 = cap2.reset()
-        out1, err1 = cap1.reset()
-        assert out1 == "cap1\n"
-        assert out2 == "cap2\n"
-
-    def test_just_out_capture(self):
-        cap = self.getcapture(out=True, err=False)
-        sys.stdout.write("hello")
-        sys.stderr.write("world")
-        out, err = cap.reset()
-        assert out == "hello"
-        assert not err
-
-    def test_just_err_capture(self):
-        cap = self.getcapture(out=False, err=True)
-        sys.stdout.write("hello")
-        sys.stderr.write("world")
-        out, err = cap.reset()
-        assert err == "world"
-        assert not out
-
-    def test_stdin_restored(self):
-        old = sys.stdin
-        cap = self.getcapture(in_=True)
-        newstdin = sys.stdin
-        out, err = cap.reset()
-        assert newstdin != sys.stdin
-        assert sys.stdin is old
-
-    def test_stdin_nulled_by_default(self):
-        print ("XXX this test may well hang instead of crashing")
-        print ("XXX which indicates an error in the underlying capturing")
-        print ("XXX mechanisms")
-        cap = self.getcapture()
-        py.test.raises(IOError, "sys.stdin.read()")
-        out, err = cap.reset()
-
-    def test_suspend_resume(self):
-        cap = self.getcapture(out=True, err=False, in_=False)
-        try:
-            print ("hello")
-            sys.stderr.write("error\n")
-            out, err = cap.suspend()
-            assert out == "hello\n"
-            assert not err
-            print ("in between")
-            sys.stderr.write("in between\n")
-            cap.resume()
-            print ("after")
-            sys.stderr.write("error_after\n")
-        finally:
-            out, err = cap.reset()
-        assert out == "after\n"
-        assert not err
-
-class TestStdCaptureNotNow(TestStdCapture):
-    def getcapture(self, **kw):
-        kw['now'] = False
-        cap = py.io.StdCapture(**kw)
-        cap.startall()
-        return cap
-
-class TestStdCaptureFD(TestStdCapture):
-    pytestmark = needsdup
-
-    def getcapture(self, **kw):
-        return py.io.StdCaptureFD(**kw)
-
-    def test_intermingling(self):
-        cap = self.getcapture()
-        oswritebytes(1, "1")
-        sys.stdout.write(str(2))
-        sys.stdout.flush()
-        oswritebytes(1, "3")
-        oswritebytes(2, "a")
-        sys.stderr.write("b")
-        sys.stderr.flush()
-        oswritebytes(2, "c")
-        out, err = cap.reset()
-        assert out == "123"
-        assert err == "abc"
-
-    def test_callcapture(self):
-        def func(x, y):
-            print (x)
-            py.std.sys.stderr.write(str(y))
-            return 42
-
-        res, out, err = py.io.StdCaptureFD.call(func, 3, y=4)
-        assert res == 42
-        assert out.startswith("3")
-        assert err.startswith("4")
-
-    def test_many(self, capfd):
-        def f():
-            for i in range(10):
-                cap = py.io.StdCaptureFD()
-                cap.reset()
-        lsof_check(f)
-
-class TestStdCaptureFDNotNow(TestStdCaptureFD):
-    pytestmark = needsdup
-
-    def getcapture(self, **kw):
-        kw['now'] = False
-        cap = py.io.StdCaptureFD(**kw)
-        cap.startall()
-        return cap
-
-@needsdup
-def test_stdcapture_fd_tmpfile(tmpfile):
-    capfd = py.io.StdCaptureFD(out=tmpfile)
-    os.write(1, "hello".encode("ascii"))
-    os.write(2, "world".encode("ascii"))
-    outf, errf = capfd.done()
-    assert outf == tmpfile
-
-class TestStdCaptureFDinvalidFD:
-    pytestmark = needsdup
-    def test_stdcapture_fd_invalid_fd(self, testdir):
-        testdir.makepyfile("""
-            import py, os
-            def test_stdout():
-                os.close(1)
-                cap = py.io.StdCaptureFD(out=True, err=False, in_=False)
-                cap.done()
-            def test_stderr():
-                os.close(2)
-                cap = py.io.StdCaptureFD(out=False, err=True, in_=False)
-                cap.done()
-            def test_stdin():
-                os.close(0)
-                cap = py.io.StdCaptureFD(out=False, err=False, in_=True)
-                cap.done()
-        """)
-        result = testdir.runpytest("--capture=fd")
-        assert result.ret == 0
-        assert result.parseoutcomes()['passed'] == 3
-
-def test_capture_not_started_but_reset():
-    capsys = py.io.StdCapture(now=False)
-    capsys.done()
-    capsys.done()
-    capsys.reset()
-
-@needsdup
-def test_capture_no_sys():
-    capsys = py.io.StdCapture()
-    try:
-        cap = py.io.StdCaptureFD(patchsys=False)
-        sys.stdout.write("hello")
-        sys.stderr.write("world")
-        oswritebytes(1, "1")
-        oswritebytes(2, "2")
-        out, err = cap.reset()
-        assert out == "1"
-        assert err == "2"
-    finally:
-        capsys.reset()
-
-@needsdup
-def test_callcapture_nofd():
-    def func(x, y):
-        oswritebytes(1, "hello")
-        oswritebytes(2, "hello")
-        print (x)
-        sys.stderr.write(str(y))
-        return 42
-
-    capfd = py.io.StdCaptureFD(patchsys=False)
-    try:
-        res, out, err = py.io.StdCapture.call(func, 3, y=4)
-    finally:
-        capfd.reset()
-    assert res == 42
-    assert out.startswith("3")
-    assert err.startswith("4")
-
-@needsdup
-@py.test.mark.multi(use=[True, False])
-def test_fdcapture_tmpfile_remains_the_same(tmpfile, use):
-    if not use:
-        tmpfile = True
-    cap = py.io.StdCaptureFD(out=False, err=tmpfile, now=False)
-    cap.startall()
-    capfile = cap.err.tmpfile
-    cap.suspend()
-    cap.resume()
-    capfile2 = cap.err.tmpfile
-    assert capfile2 == capfile
-
-@py.test.mark.multi(method=['StdCapture', 'StdCaptureFD'])
-def test_capturing_and_logging_fundamentals(testdir, method):
-    if method == "StdCaptureFD" and not hasattr(os, 'dup'):
-        py.test.skip("need os.dup")
-    # here we check a fundamental feature
-    p = testdir.makepyfile("""
-        import sys, os
-        import py, logging
-        cap = py.io.%s(out=False, in_=False)
-
-        logging.warn("hello1")
-        outerr = cap.suspend()
-        print ("suspend, captured %%s" %%(outerr,))
-        logging.warn("hello2")
-
-        cap.resume()
-        logging.warn("hello3")
-
-        outerr = cap.suspend()
-        print ("suspend2, captured %%s" %% (outerr,))
-    """ % (method,))
-    result = testdir.runpython(p)
-    result.stdout.fnmatch_lines([
-        "suspend, captured*hello1*",
-        "suspend2, captured*hello2*WARNING:root:hello3*",
-    ])
-    assert "atexit" not in result.stderr.str()
diff --git a/tools/py/testing/io_/test_saferepr.py b/tools/py/testing/io_/test_saferepr.py
deleted file mode 100644
index 1ed9c4f..0000000
--- a/tools/py/testing/io_/test_saferepr.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import generators
-import py
-import sys
-
-saferepr = py.io.saferepr
-
-class TestSafeRepr:
-    def test_simple_repr(self):
-        assert saferepr(1) == '1'
-        assert saferepr(None) == 'None'
-
-    def test_maxsize(self):
-        s = saferepr('x'*50, maxsize=25)
-        assert len(s) == 25
-        expected = repr('x'*10 + '...' + 'x'*10)
-        assert s == expected
-
-    def test_maxsize_error_on_instance(self):
-        class A:
-            def __repr__(self):
-                raise ValueError('...')
-
-        s = saferepr(('*'*50, A()), maxsize=25)
-        assert len(s) == 25
-        assert s[0] == '(' and s[-1] == ')'
-
-    def test_exceptions(self):
-        class BrokenRepr:
-            def __init__(self, ex):
-                self.ex = ex
-                foo = 0
-            def __repr__(self):
-                raise self.ex
-        class BrokenReprException(Exception):
-            __str__ = None
-            __repr__ = None
-        assert 'Exception' in saferepr(BrokenRepr(Exception("broken")))
-        s = saferepr(BrokenReprException("really broken"))
-        assert 'TypeError' in s
-        if py.std.sys.version_info < (2,6):
-            assert 'unknown' in saferepr(BrokenRepr("string"))
-        else:
-            assert 'TypeError' in saferepr(BrokenRepr("string"))
-
-        s2 = saferepr(BrokenRepr(BrokenReprException('omg even worse')))
-        assert 'NameError' not in s2
-        assert 'unknown' in s2
-
-    def test_big_repr(self):
-        from py._io.saferepr import SafeRepr
-        assert len(saferepr(range(1000))) <= \
-               len('[' + SafeRepr().maxlist * "1000" + ']')
-
-    def test_repr_on_newstyle(self):
-        class Function(object):
-            def __repr__(self):
-                return "<%s>" %(self.name)
-        try:
-            s = saferepr(Function())
-        except Exception:
-            py.test.fail("saferepr failed for newstyle class")
-
-    def test_unicode(self):
-        val = py.builtin._totext('£€', 'utf-8')
-        reprval = py.builtin._totext("'£€'", 'utf-8')
-        assert saferepr(val) == reprval
-
-def test_unicode_handling():
-    value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
-    def f():
-        raise Exception(value)
-    excinfo = py.test.raises(Exception, f)
-    s = str(excinfo)
-    if sys.version_info[0] < 3:
-        u = unicode(excinfo)
-
diff --git a/tools/py/testing/io_/test_terminalwriter.py b/tools/py/testing/io_/test_terminalwriter.py
deleted file mode 100644
index 0a15541..0000000
--- a/tools/py/testing/io_/test_terminalwriter.py
+++ /dev/null
@@ -1,271 +0,0 @@
-
-import py
-import os, sys
-from py._io import terminalwriter
-import codecs
-import pytest
-
-def test_get_terminal_width():
-    x = py.io.get_terminal_width
-    assert x == terminalwriter.get_terminal_width
-
-def test_getdimensions(monkeypatch):
-    fcntl = py.test.importorskip("fcntl")
-    import struct
-    l = []
-    monkeypatch.setattr(fcntl, 'ioctl', lambda *args: l.append(args))
-    try:
-        terminalwriter._getdimensions()
-    except (TypeError, struct.error):
-        pass
-    assert len(l) == 1
-    assert l[0][0] == 1
-
-def test_terminal_width_COLUMNS(monkeypatch):
-    """ Dummy test for get_terminal_width
-    """
-    fcntl = py.test.importorskip("fcntl")
-    monkeypatch.setattr(fcntl, 'ioctl', lambda *args: int('x'))
-    monkeypatch.setenv('COLUMNS', '42')
-    assert terminalwriter.get_terminal_width() == 42
-    monkeypatch.delenv('COLUMNS', raising=False)
-
-def test_terminalwriter_defaultwidth_80(monkeypatch):
-    monkeypatch.setattr(terminalwriter, '_getdimensions', lambda: 0/0)
-    monkeypatch.delenv('COLUMNS', raising=False)
-    tw = py.io.TerminalWriter()
-    assert tw.fullwidth == 80
-
-def test_terminalwriter_getdimensions_bogus(monkeypatch):
-    monkeypatch.setattr(terminalwriter, '_getdimensions', lambda: (10,10))
-    monkeypatch.delenv('COLUMNS', raising=False)
-    tw = py.io.TerminalWriter()
-    assert tw.fullwidth == 80
-
-def test_terminalwriter_getdimensions_emacs(monkeypatch):
-    # emacs terminal returns (0,0) but set COLUMNS properly
-    monkeypatch.setattr(terminalwriter, '_getdimensions', lambda: (0,0))
-    monkeypatch.setenv('COLUMNS', '42')
-    tw = py.io.TerminalWriter()
-    assert tw.fullwidth == 42
-
-def test_terminalwriter_computes_width(monkeypatch):
-    monkeypatch.setattr(terminalwriter, 'get_terminal_width', lambda: 42)
-    tw = py.io.TerminalWriter()
-    assert tw.fullwidth == 42
-
-def test_terminalwriter_default_instantiation():
-    tw = py.io.TerminalWriter(stringio=True)
-    assert hasattr(tw, 'stringio')
-
-def test_terminalwriter_dumb_term_no_markup(monkeypatch):
-    monkeypatch.setattr(os, 'environ', {'TERM': 'dumb', 'PATH': ''})
-    class MyFile:
-        closed = False
-        def isatty(self):
-            return True
-    monkeypatch.setattr(sys, 'stdout', MyFile())
-    try:
-        assert sys.stdout.isatty()
-        tw = py.io.TerminalWriter()
-        assert not tw.hasmarkup
-    finally:
-        monkeypatch.undo()
-
-def test_terminalwriter_file_unicode(tmpdir):
-    f = py.std.codecs.open(str(tmpdir.join("xyz")), "wb", "utf8")
-    tw = py.io.TerminalWriter(file=f)
-    assert tw.encoding == "utf8"
-
-def test_unicode_encoding():
-    msg = py.builtin._totext('b\u00f6y', 'utf8')
-    for encoding in 'utf8', 'latin1':
-        l = []
-        tw = py.io.TerminalWriter(l.append, encoding=encoding)
-        tw.line(msg)
-        assert l[0].strip() == msg.encode(encoding)
-
-@pytest.mark.parametrize("encoding", ["ascii"])
-def test_unicode_on_file_with_ascii_encoding(tmpdir, monkeypatch, encoding):
-    msg = py.builtin._totext('hell\xf6', "latin1")
-    #pytest.raises(UnicodeEncodeError, lambda: bytes(msg))
-    f = py.std.codecs.open(str(tmpdir.join("x")), "w", encoding)
-    tw = py.io.TerminalWriter(f)
-    tw.line(msg)
-    f.close()
-    s = tmpdir.join("x").open("rb").read().strip()
-    assert encoding == "ascii"
-    assert s == msg.encode("unicode-escape")
-
-
-win32 = int(sys.platform == "win32")
-class TestTerminalWriter:
-    def pytest_generate_tests(self, metafunc):
-        if "tw" in metafunc.funcargnames:
-            metafunc.addcall(id="path", param="path")
-            metafunc.addcall(id="stringio", param="stringio")
-            metafunc.addcall(id="callable", param="callable")
-    def pytest_funcarg__tw(self, request):
-        if request.param == "path":
-            tmpdir = request.getfuncargvalue("tmpdir")
-            p = tmpdir.join("tmpfile")
-            f = codecs.open(str(p), 'w+', encoding='utf8')
-            tw = py.io.TerminalWriter(f)
-            def getlines():
-                tw._file.flush()
-                return codecs.open(str(p), 'r',
-                    encoding='utf8').readlines()
-        elif request.param == "stringio":
-            tw = py.io.TerminalWriter(stringio=True)
-            def getlines():
-                tw.stringio.seek(0)
-                return tw.stringio.readlines()
-        elif request.param == "callable":
-            writes = []
-            tw = py.io.TerminalWriter(writes.append)
-            def getlines():
-                io = py.io.TextIO()
-                io.write("".join(writes))
-                io.seek(0)
-                return io.readlines()
-        tw.getlines = getlines
-        tw.getvalue = lambda: "".join(getlines())
-        return tw
-
-    def test_line(self, tw):
-        tw.line("hello")
-        l = tw.getlines()
-        assert len(l) == 1
-        assert l[0] == "hello\n"
-
-    def test_line_unicode(self, tw):
-        for encoding in 'utf8', 'latin1':
-            tw._encoding = encoding
-            msg = py.builtin._totext('b\u00f6y', 'utf8')
-            tw.line(msg)
-            l = tw.getlines()
-            assert l[0] == msg + "\n"
-
-    def test_sep_no_title(self, tw):
-        tw.sep("-", fullwidth=60)
-        l = tw.getlines()
-        assert len(l) == 1
-        assert l[0] == "-" * (60-win32) + "\n"
-
-    def test_sep_with_title(self, tw):
-        tw.sep("-", "hello", fullwidth=60)
-        l = tw.getlines()
-        assert len(l) == 1
-        assert l[0] == "-" * 26 + " hello " + "-" * (27-win32) + "\n"
-
-    @py.test.mark.skipif("sys.platform == 'win32'")
-    def test__escaped(self, tw):
-        text2 = tw._escaped("hello", (31))
-        assert text2.find("hello") != -1
-
-    @py.test.mark.skipif("sys.platform == 'win32'")
-    def test_markup(self, tw):
-        for bold in (True, False):
-            for color in ("red", "green"):
-                text2 = tw.markup("hello", **{color: True, 'bold': bold})
-                assert text2.find("hello") != -1
-        py.test.raises(ValueError, "tw.markup('x', wronkw=3)")
-        py.test.raises(ValueError, "tw.markup('x', wronkw=0)")
-
-    def test_line_write_markup(self, tw):
-        tw.hasmarkup = True
-        tw.line("x", bold=True)
-        tw.write("x\n", red=True)
-        l = tw.getlines()
-        if sys.platform != "win32":
-            assert len(l[0]) >= 2, l
-            assert len(l[1]) >= 2, l
-
-    def test_attr_fullwidth(self, tw):
-        tw.sep("-", "hello", fullwidth=70)
-        tw.fullwidth = 70
-        tw.sep("-", "hello")
-        l = tw.getlines()
-        assert len(l[0]) == len(l[1])
-
-    def test_reline(self, tw):
-        tw.line("hello")
-        tw.hasmarkup = False
-        pytest.raises(ValueError, lambda: tw.reline("x"))
-        tw.hasmarkup = True
-        tw.reline("0 1 2")
-        tw.getlines()
-        l = tw.getvalue().split("\n")
-        assert len(l) == 2
-        tw.reline("0 1 3")
-        l = tw.getvalue().split("\n")
-        assert len(l) == 2
-        assert l[1].endswith("0 1 3\r")
-        tw.line("so")
-        l = tw.getvalue().split("\n")
-        assert len(l) == 3
-        assert l[-1] == ""
-        assert l[1] == ("0 1 2\r0 1 3\rso   ")
-        assert l[0] == "hello"
-
-
-def test_terminal_with_callable_write_and_flush():
-    l = set()
-    class fil:
-        flush = lambda self: l.add("1")
-        write = lambda self, x: l.add("1")
-        __call__ = lambda self, x: l.add("2")
-
-    tw = py.io.TerminalWriter(fil())
-    tw.line("hello")
-    assert l == set(["1"])
-    del fil.flush
-    l.clear()
-    tw = py.io.TerminalWriter(fil())
-    tw.line("hello")
-    assert l == set(["2"])
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi")
-def test_attr_hasmarkup():
-    tw = py.io.TerminalWriter(stringio=True)
-    assert not tw.hasmarkup
-    tw.hasmarkup = True
-    tw.line("hello", bold=True)
-    s = tw.stringio.getvalue()
-    assert len(s) > len("hello\n")
-    assert '\x1b[1m' in s
-    assert '\x1b[0m' in s
-
-@pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi")
-def test_ansi_print():
-    # we have no easy way to construct a file that
-    # represents a terminal
-    f = py.io.TextIO()
-    f.isatty = lambda: True
-    py.io.ansi_print("hello", 0x32, file=f)
-    text2 = f.getvalue()
-    assert text2.find("hello") != -1
-    assert len(text2) >= len("hello\n")
-    assert '\x1b[50m' in text2
-    assert '\x1b[0m' in text2
-
-def test_should_do_markup_PY_COLORS_eq_1(monkeypatch):
-    monkeypatch.setitem(os.environ, 'PY_COLORS', '1')
-    tw = py.io.TerminalWriter(stringio=True)
-    assert tw.hasmarkup
-    tw.line("hello", bold=True)
-    s = tw.stringio.getvalue()
-    assert len(s) > len("hello\n")
-    assert '\x1b[1m' in s
-    assert '\x1b[0m' in s
-
-def test_should_do_markup_PY_COLORS_eq_0(monkeypatch):
-    monkeypatch.setitem(os.environ, 'PY_COLORS', '0')
-    f = py.io.TextIO()
-    f.isatty = lambda: True
-    tw = py.io.TerminalWriter(file=f)
-    assert not tw.hasmarkup
-    tw.line("hello", bold=True)
-    s = f.getvalue()
-    assert s == "hello\n"
diff --git a/tools/py/testing/log/test_log.py b/tools/py/testing/log/test_log.py
deleted file mode 100644
index b41bc3a..0000000
--- a/tools/py/testing/log/test_log.py
+++ /dev/null
@@ -1,190 +0,0 @@
-import py
-import sys
-
-from py._log.log import default_keywordmapper
-
-callcapture = py.io.StdCapture.call
-
-def setup_module(mod):
-    mod._oldstate = default_keywordmapper.getstate()
-
-def teardown_module(mod):
-    default_keywordmapper.setstate(mod._oldstate)
-
-class TestLogProducer:
-    def setup_method(self, meth):
-        default_keywordmapper.setstate(_oldstate)
-
-    def test_getstate_setstate(self):
-        state = py.log._getstate()
-        py.log.setconsumer("hello", [].append)
-        state2 = py.log._getstate()
-        assert state2 != state
-        py.log._setstate(state)
-        state3 = py.log._getstate()
-        assert state3 == state
-
-    def test_producer_repr(self):
-        d = py.log.Producer("default")
-        assert repr(d).find('default') != -1
-
-    def test_produce_one_keyword(self):
-        l = []
-        py.log.setconsumer('s1', l.append)
-        py.log.Producer('s1')("hello world")
-        assert len(l) == 1
-        msg = l[0]
-        assert msg.content().startswith('hello world')
-        assert msg.prefix() == '[s1] '
-        assert str(msg) == "[s1] hello world"
-
-    def test_producer_class(self):
-        p = py.log.Producer('x1')
-        l = []
-        py.log.setconsumer(p._keywords, l.append)
-        p("hello")
-        assert len(l) == 1
-        assert len(l[0].keywords) == 1
-        assert 'x1' == l[0].keywords[0]
-
-    def test_producer_caching(self):
-        p = py.log.Producer('x1')
-        x2 = p.x2
-        assert x2 is p.x2
-
-class TestLogConsumer:
-    def setup_method(self, meth):
-        default_keywordmapper.setstate(_oldstate)
-    def test_log_none(self):
-        log = py.log.Producer("XXX")
-        l = []
-        py.log.setconsumer('XXX', l.append)
-        log("1")
-        assert l
-        l[:] = []
-        py.log.setconsumer('XXX', None)
-        log("2")
-        assert not l
-
-    def test_log_default_stderr(self):
-        res, out, err = callcapture(py.log.Producer("default"), "hello")
-        assert err.strip() == "[default] hello"
-
-    def test_simple_consumer_match(self):
-        l = []
-        py.log.setconsumer("x1", l.append)
-        p = py.log.Producer("x1 x2")
-        p("hello")
-        assert l
-        assert l[0].content() == "hello"
-
-    def test_simple_consumer_match_2(self):
-        l = []
-        p = py.log.Producer("x1 x2")
-        py.log.setconsumer(p._keywords, l.append)
-        p("42")
-        assert l
-        assert l[0].content() == "42"
-
-    def test_no_auto_producer(self):
-        p = py.log.Producer('x')
-        py.test.raises(AttributeError, "p._x")
-        py.test.raises(AttributeError, "p.x_y")
-
-    def test_setconsumer_with_producer(self):
-        l = []
-        p = py.log.Producer("hello")
-        py.log.setconsumer(p, l.append)
-        p("world")
-        assert str(l[0]) == "[hello] world"
-
-    def test_multi_consumer(self):
-        l = []
-        py.log.setconsumer("x1", l.append)
-        py.log.setconsumer("x1 x2", None)
-        p = py.log.Producer("x1 x2")
-        p("hello")
-        assert not l
-        py.log.Producer("x1")("hello")
-        assert l
-        assert l[0].content() == "hello"
-
-    def test_log_stderr(self):
-        py.log.setconsumer("xyz", py.log.STDOUT)
-        res, out, err = callcapture(py.log.Producer("xyz"), "hello")
-        assert not err
-        assert out.strip() == '[xyz] hello'
-
-    def test_log_file(self, tmpdir):
-        customlog = tmpdir.join('log.out')
-        py.log.setconsumer("default", open(str(customlog), 'w', 1))
-        py.log.Producer("default")("hello world #1")
-        assert customlog.readlines() == ['[default] hello world #1\n']
-
-        py.log.setconsumer("default", py.log.Path(customlog, buffering=False))
-        py.log.Producer("default")("hello world #2")
-        res = customlog.readlines()
-        assert res == ['[default] hello world #2\n'] # no append by default!
-
-    def test_log_file_append_mode(self, tmpdir):
-        logfilefn = tmpdir.join('log_append.out')
-
-        # The append mode is on by default, so we don't need to specify it for File
-        py.log.setconsumer("default", py.log.Path(logfilefn, append=True,
-                                                    buffering=0))
-        assert logfilefn.check()
-        py.log.Producer("default")("hello world #1")
-        lines = logfilefn.readlines()
-        assert lines == ['[default] hello world #1\n']
-        py.log.setconsumer("default", py.log.Path(logfilefn, append=True,
-                                                    buffering=0))
-        py.log.Producer("default")("hello world #1")
-        lines = logfilefn.readlines()
-        assert lines == ['[default] hello world #1\n',
-                         '[default] hello world #1\n']
-
-    def test_log_file_delayed_create(self, tmpdir):
-        logfilefn = tmpdir.join('log_create.out')
-
-        py.log.setconsumer("default", py.log.Path(logfilefn,
-                                        delayed_create=True, buffering=0))
-        assert not logfilefn.check()
-        py.log.Producer("default")("hello world #1")
-        lines = logfilefn.readlines()
-        assert lines == ['[default] hello world #1\n']
-
-    def test_keyword_based_log_files(self, tmpdir):
-        logfiles = []
-        keywords = 'k1 k2 k3'.split()
-        for key in keywords:
-            path = tmpdir.join(key)
-            py.log.setconsumer(key, py.log.Path(path, buffering=0))
-
-        py.log.Producer('k1')('1')
-        py.log.Producer('k2')('2')
-        py.log.Producer('k3')('3')
-
-        for key in keywords:
-            path = tmpdir.join(key)
-            assert path.read().strip() == '[%s] %s' % (key, key[-1])
-
-    # disabled for now; the syslog log file can usually be read only by root
-    # I manually inspected /var/log/messages and the entries were there
-    def no_test_log_syslog(self):
-        py.log.setconsumer("default", py.log.Syslog())
-        py.log.default("hello world #1")
-
-    # disabled for now until I figure out how to read entries in the
-    # Event Logs on Windows
-    # I manually inspected the Application Log and the entries were there
-    def no_test_log_winevent(self):
-        py.log.setconsumer("default", py.log.WinEvent())
-        py.log.default("hello world #1")
-
-    # disabled for now until I figure out how to properly pass the parameters
-    def no_test_log_email(self):
-        py.log.setconsumer("default", py.log.Email(mailhost="gheorghiu.net",
-                                                   fromaddr="grig",
-                                                   toaddrs="grig",
-                                                   subject = "py.log email"))
-        py.log.default("hello world #1")
diff --git a/tools/py/testing/log/test_warning.py b/tools/py/testing/log/test_warning.py
deleted file mode 100644
index 8c89cf8..0000000
--- a/tools/py/testing/log/test_warning.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import pytest
-import py
-
-mypath = py.path.local(__file__).new(ext=".py")
-
-@pytest.mark.xfail
-def test_forwarding_to_warnings_module():
-    pytest.deprecated_call(py.log._apiwarn, "1.3", "..")
-
-def test_apiwarn_functional(recwarn):
-    capture = py.io.StdCapture()
-    py.log._apiwarn("x.y.z", "something", stacklevel=1)
-    out, err = capture.reset()
-    py.builtin.print_("out", out)
-    py.builtin.print_("err", err)
-    assert err.find("x.y.z") != -1
-    lno = py.code.getrawcode(test_apiwarn_functional).co_firstlineno + 2
-    exp = "%s:%s" % (mypath, lno)
-    assert err.find(exp) != -1
-
-def test_stacklevel(recwarn):
-    def f():
-        py.log._apiwarn("x", "some", stacklevel=2)
-    # 3
-    # 4
-    capture = py.io.StdCapture()
-    f()
-    out, err = capture.reset()
-    lno = py.code.getrawcode(test_stacklevel).co_firstlineno + 6
-    warning = str(err)
-    assert warning.find(":%s" % lno) != -1
-
-def test_stacklevel_initpkg_with_resolve(testdir, recwarn):
-    testdir.makepyfile(modabc="""
-        import py
-        def f():
-            py.log._apiwarn("x", "some", stacklevel="apipkg123")
-    """)
-    testdir.makepyfile(apipkg123="""
-        def __getattr__():
-            import modabc
-            modabc.f()
-    """)
-    p = testdir.makepyfile("""
-        import apipkg123
-        apipkg123.__getattr__()
-    """)
-    capture = py.io.StdCapture()
-    p.pyimport()
-    out, err = capture.reset()
-    warning = str(err)
-    loc = 'test_stacklevel_initpkg_with_resolve.py:2'
-    assert warning.find(loc) != -1
-
-def test_stacklevel_initpkg_no_resolve(recwarn):
-    def f():
-        py.log._apiwarn("x", "some", stacklevel="apipkg")
-    capture = py.io.StdCapture()
-    f()
-    out, err = capture.reset()
-    lno = py.code.getrawcode(test_stacklevel_initpkg_no_resolve).co_firstlineno + 2
-    warning = str(err)
-    assert warning.find(":%s" % lno) != -1
-
-
-def test_function(recwarn):
-    capture = py.io.StdCapture()
-    py.log._apiwarn("x.y.z", "something", function=test_function)
-    out, err = capture.reset()
-    py.builtin.print_("out", out)
-    py.builtin.print_("err", err)
-    assert err.find("x.y.z") != -1
-    lno = py.code.getrawcode(test_function).co_firstlineno
-    exp = "%s:%s" % (mypath, lno)
-    assert err.find(exp) != -1
-
diff --git a/tools/py/testing/path/common.py b/tools/py/testing/path/common.py
deleted file mode 100644
index 4834fba..0000000
--- a/tools/py/testing/path/common.py
+++ /dev/null
@@ -1,470 +0,0 @@
-import py
-import sys
-
-class CommonFSTests(object):
-    def test_constructor_equality(self, path1):
-        p = path1.__class__(path1)
-        assert p == path1
-
-    def test_eq_nonstring(self, path1):
-        p1 = path1.join('sampledir')
-        p2 = path1.join('sampledir')
-        assert p1 == p2
-
-    def test_new_identical(self, path1):
-        assert path1 == path1.new()
-
-    def test_join(self, path1):
-        p = path1.join('sampledir')
-        strp = str(p)
-        assert strp.endswith('sampledir')
-        assert strp.startswith(str(path1))
-
-    def test_join_normalized(self, path1):
-        newpath = path1.join(path1.sep+'sampledir')
-        strp = str(newpath)
-        assert strp.endswith('sampledir')
-        assert strp.startswith(str(path1))
-        newpath = path1.join((path1.sep*2) + 'sampledir')
-        strp = str(newpath)
-        assert strp.endswith('sampledir')
-        assert strp.startswith(str(path1))
-
-    def test_join_noargs(self, path1):
-        newpath = path1.join()
-        assert path1 == newpath
-
-    def test_add_something(self, path1):
-        p = path1.join('sample')
-        p = p + 'dir'
-        assert p.check()
-        assert p.exists()
-        assert p.isdir()
-        assert not p.isfile()
-
-    def test_parts(self, path1):
-        newpath = path1.join('sampledir', 'otherfile')
-        par = newpath.parts()[-3:]
-        assert par == [path1, path1.join('sampledir'), newpath]
-
-        revpar = newpath.parts(reverse=True)[:3]
-        assert revpar == [newpath, path1.join('sampledir'), path1]
-
-    def test_common(self, path1):
-        other = path1.join('sampledir')
-        x = other.common(path1)
-        assert x == path1
-
-    #def test_parents_nonexisting_file(self, path1):
-    #    newpath = path1 / 'dirnoexist' / 'nonexisting file'
-    #    par = list(newpath.parents())
-    #    assert par[:2] == [path1 / 'dirnoexist', path1]
-
-    def test_basename_checks(self, path1):
-        newpath = path1.join('sampledir')
-        assert newpath.check(basename='sampledir')
-        assert newpath.check(notbasename='xyz')
-        assert newpath.basename == 'sampledir'
-
-    def test_basename(self, path1):
-        newpath = path1.join('sampledir')
-        assert newpath.check(basename='sampledir')
-        assert newpath.basename, 'sampledir'
-
-    def test_dirname(self, path1):
-        newpath = path1.join('sampledir')
-        assert newpath.dirname == str(path1)
-
-    def test_dirpath(self, path1):
-        newpath = path1.join('sampledir')
-        assert newpath.dirpath() == path1
-
-    def test_dirpath_with_args(self, path1):
-        newpath = path1.join('sampledir')
-        assert newpath.dirpath('x') == path1.join('x')
-
-    def test_newbasename(self, path1):
-        newpath = path1.join('samplefile')
-        newbase = newpath.new(basename="samplefile2")
-        assert newbase.basename == "samplefile2"
-        assert newbase.dirpath() == newpath.dirpath()
-
-    def test_not_exists(self, path1):
-        assert not path1.join('does_not_exist').check()
-        assert path1.join('does_not_exist').check(exists=0)
-
-    def test_exists(self, path1):
-        assert path1.join("samplefile").check()
-        assert path1.join("samplefile").check(exists=1)
-        assert path1.join("samplefile").exists()
-        assert path1.join("samplefile").isfile()
-        assert not path1.join("samplefile").isdir()
-
-    def test_dir(self, path1):
-        #print repr(path1.join("sampledir"))
-        assert path1.join("sampledir").check(dir=1)
-        assert path1.join('samplefile').check(notdir=1)
-        assert not path1.join("samplefile").check(dir=1)
-        assert path1.join("samplefile").exists()
-        assert not path1.join("samplefile").isdir()
-        assert path1.join("samplefile").isfile()
-
-    def test_fnmatch_file(self, path1):
-        assert path1.join("samplefile").check(fnmatch='s*e')
-        assert path1.join("samplefile").fnmatch('s*e')
-        assert not path1.join("samplefile").fnmatch('s*x')
-        assert not path1.join("samplefile").check(fnmatch='s*x')
-
-    #def test_fnmatch_dir(self, path1):
-
-    #    pattern = path1.sep.join(['s*file'])
-    #    sfile = path1.join("samplefile")
-    #    assert sfile.check(fnmatch=pattern)
-
-    def test_relto(self, path1):
-        l=path1.join("sampledir", "otherfile")
-        assert l.relto(path1) == l.sep.join(["sampledir", "otherfile"])
-        assert l.check(relto=path1)
-        assert path1.check(notrelto=l)
-        assert not path1.check(relto=l)
-
-    def test_bestrelpath(self, path1):
-        curdir = path1
-        sep = curdir.sep
-        s = curdir.bestrelpath(curdir)
-        assert s == "."
-        s = curdir.bestrelpath(curdir.join("hello", "world"))
-        assert s == "hello" + sep + "world"
-
-        s = curdir.bestrelpath(curdir.dirpath().join("sister"))
-        assert s == ".." + sep + "sister"
-        assert curdir.bestrelpath(curdir.dirpath()) == ".."
-
-        assert curdir.bestrelpath("hello") == "hello"
-
-    def test_relto_not_relative(self, path1):
-        l1=path1.join("bcde")
-        l2=path1.join("b")
-        assert not l1.relto(l2)
-        assert not l2.relto(l1)
-
-    @py.test.mark.xfail("sys.platform.startswith('java')")
-    def test_listdir(self, path1):
-        l = path1.listdir()
-        assert path1.join('sampledir') in l
-        assert path1.join('samplefile') in l
-        py.test.raises(py.error.ENOTDIR,
-                       "path1.join('samplefile').listdir()")
-
-    def test_listdir_fnmatchstring(self, path1):
-        l = path1.listdir('s*dir')
-        assert len(l)
-        assert l[0], path1.join('sampledir')
-
-    def test_listdir_filter(self, path1):
-        l = path1.listdir(lambda x: x.check(dir=1))
-        assert path1.join('sampledir') in l
-        assert not path1.join('samplefile') in l
-
-    def test_listdir_sorted(self, path1):
-        l = path1.listdir(lambda x: x.check(basestarts="sample"), sort=True)
-        assert path1.join('sampledir') == l[0]
-        assert path1.join('samplefile') == l[1]
-        assert path1.join('samplepickle') == l[2]
-
-    def test_visit_nofilter(self, path1):
-        l = []
-        for i in path1.visit():
-            l.append(i.relto(path1))
-        assert "sampledir" in l
-        assert path1.sep.join(["sampledir", "otherfile"]) in l
-
-    def test_visit_norecurse(self, path1):
-        l = []
-        for i in path1.visit(None, lambda x: x.basename != "sampledir"):
-            l.append(i.relto(path1))
-        assert "sampledir" in l
-        assert not path1.sep.join(["sampledir", "otherfile"]) in l
-
-    def test_visit_filterfunc_is_string(self, path1):
-        l = []
-        for i in path1.visit('*dir'):
-            l.append(i.relto(path1))
-        assert len(l), 2
-        assert "sampledir" in l
-        assert "otherdir" in l
-
-    @py.test.mark.xfail("sys.platform.startswith('java')")
-    def test_visit_ignore(self, path1):
-        p = path1.join('nonexisting')
-        assert list(p.visit(ignore=py.error.ENOENT)) == []
-
-    def test_visit_endswith(self, path1):
-        l = []
-        for i in path1.visit(lambda x: x.check(endswith="file")):
-            l.append(i.relto(path1))
-        assert path1.sep.join(["sampledir", "otherfile"]) in l
-        assert "samplefile" in l
-
-    def test_endswith(self, path1):
-        assert path1.check(notendswith='.py')
-        x = path1.join('samplefile')
-        assert x.check(endswith='file')
-
-    def test_cmp(self, path1):
-        path1 = path1.join('samplefile')
-        path2 = path1.join('samplefile2')
-        assert (path1 < path2) == ('samplefile' < 'samplefile2')
-        assert not (path1 < path1)
-
-    def test_simple_read(self, path1):
-        x = path1.join('samplefile').read('r')
-        assert x == 'samplefile\n'
-
-    def test_join_div_operator(self, path1):
-        newpath = path1 / '/sampledir' / '/test//'
-        newpath2 = path1.join('sampledir', 'test')
-        assert newpath == newpath2
-
-    def test_ext(self, path1):
-        newpath = path1.join('sampledir.ext')
-        assert newpath.ext == '.ext'
-        newpath = path1.join('sampledir')
-        assert not newpath.ext
-
-    def test_purebasename(self, path1):
-        newpath = path1.join('samplefile.py')
-        assert newpath.purebasename == 'samplefile'
-
-    def test_multiple_parts(self, path1):
-        newpath = path1.join('samplefile.py')
-        dirname, purebasename, basename, ext = newpath._getbyspec(
-            'dirname,purebasename,basename,ext')
-        assert str(path1).endswith(dirname) # be careful with win32 'drive'
-        assert purebasename == 'samplefile'
-        assert basename == 'samplefile.py'
-        assert ext == '.py'
-
-    def test_dotted_name_ext(self, path1):
-        newpath = path1.join('a.b.c')
-        ext = newpath.ext
-        assert ext == '.c'
-        assert newpath.ext == '.c'
-
-    def test_newext(self, path1):
-        newpath = path1.join('samplefile.py')
-        newext = newpath.new(ext='.txt')
-        assert newext.basename == "samplefile.txt"
-        assert newext.purebasename == "samplefile"
-
-    def test_readlines(self, path1):
-        fn = path1.join('samplefile')
-        contents = fn.readlines()
-        assert contents == ['samplefile\n']
-
-    def test_readlines_nocr(self, path1):
-        fn = path1.join('samplefile')
-        contents = fn.readlines(cr=0)
-        assert contents == ['samplefile', '']
-
-    def test_file(self, path1):
-        assert path1.join('samplefile').check(file=1)
-
-    def test_not_file(self, path1):
-        assert not path1.join("sampledir").check(file=1)
-        assert path1.join("sampledir").check(file=0)
-
-    def test_non_existent(self, path1):
-        assert path1.join("sampledir.nothere").check(dir=0)
-        assert path1.join("sampledir.nothere").check(file=0)
-        assert path1.join("sampledir.nothere").check(notfile=1)
-        assert path1.join("sampledir.nothere").check(notdir=1)
-        assert path1.join("sampledir.nothere").check(notexists=1)
-        assert not path1.join("sampledir.nothere").check(notfile=0)
-
-    #    pattern = path1.sep.join(['s*file'])
-    #    sfile = path1.join("samplefile")
-    #    assert sfile.check(fnmatch=pattern)
-
-    def test_size(self, path1):
-        url = path1.join("samplefile")
-        assert url.size() > len("samplefile")
-
-    def test_mtime(self, path1):
-        url = path1.join("samplefile")
-        assert url.mtime() > 0
-
-    def test_relto_wrong_type(self, path1):
-        py.test.raises(TypeError, "path1.relto(42)")
-
-    def test_load(self, path1):
-        p = path1.join('samplepickle')
-        obj = p.load()
-        assert type(obj) is dict
-        assert obj.get('answer',None) == 42
-
-    def test_visit_filesonly(self, path1):
-        l = []
-        for i in path1.visit(lambda x: x.check(file=1)):
-            l.append(i.relto(path1))
-        assert not "sampledir" in l
-        assert path1.sep.join(["sampledir", "otherfile"]) in l
-
-    def test_visit_nodotfiles(self, path1):
-        l = []
-        for i in path1.visit(lambda x: x.check(dotfile=0)):
-            l.append(i.relto(path1))
-        assert "sampledir" in l
-        assert path1.sep.join(["sampledir", "otherfile"]) in l
-        assert not ".dotfile" in l
-
-    def test_visit_breadthfirst(self, path1):
-        l = []
-        for i in path1.visit(bf=True):
-            l.append(i.relto(path1))
-        for i, p in enumerate(l):
-            if path1.sep in p:
-                for j in range(i, len(l)):
-                    assert path1.sep in l[j]
-                break
-        else:
-            py.test.fail("huh")
-
-    def test_visit_sort(self, path1):
-        l = []
-        for i in path1.visit(bf=True, sort=True):
-            l.append(i.relto(path1))
-        for i, p in enumerate(l):
-            if path1.sep in p:
-                break
-        assert l[:i] == sorted(l[:i])
-        assert l[i:] == sorted(l[i:])
-
-    def test_endswith(self, path1):
-        def chk(p):
-            return p.check(endswith="pickle")
-        assert not chk(path1)
-        assert not chk(path1.join('samplefile'))
-        assert chk(path1.join('somepickle'))
-
-    def test_copy_file(self, path1):
-        otherdir = path1.join('otherdir')
-        initpy = otherdir.join('__init__.py')
-        copied = otherdir.join('copied')
-        initpy.copy(copied)
-        try:
-            assert copied.check()
-            s1 = initpy.read()
-            s2 = copied.read()
-            assert s1 == s2
-        finally:
-            if copied.check():
-                copied.remove()
-
-    def test_copy_dir(self, path1):
-        otherdir = path1.join('otherdir')
-        copied = path1.join('newdir')
-        try:
-            otherdir.copy(copied)
-            assert copied.check(dir=1)
-            assert copied.join('__init__.py').check(file=1)
-            s1 = otherdir.join('__init__.py').read()
-            s2 = copied.join('__init__.py').read()
-            assert s1 == s2
-        finally:
-            if copied.check(dir=1):
-                copied.remove(rec=1)
-
-    def test_remove_file(self, path1):
-        d = path1.ensure('todeleted')
-        assert d.check()
-        d.remove()
-        assert not d.check()
-
-    def test_remove_dir_recursive_by_default(self, path1):
-        d = path1.ensure('to', 'be', 'deleted')
-        assert d.check()
-        p = path1.join('to')
-        p.remove()
-        assert not p.check()
-
-    def test_ensure_dir(self, path1):
-        b = path1.ensure_dir("001", "002")
-        assert b.basename == "002"
-        assert b.isdir()
-
-    def test_mkdir_and_remove(self, path1):
-        tmpdir = path1
-        py.test.raises(py.error.EEXIST, tmpdir.mkdir, 'sampledir')
-        new = tmpdir.join('mktest1')
-        new.mkdir()
-        assert new.check(dir=1)
-        new.remove()
-
-        new = tmpdir.mkdir('mktest')
-        assert new.check(dir=1)
-        new.remove()
-        assert tmpdir.join('mktest') == new
-
-    def test_move_file(self, path1):
-        p = path1.join('samplefile')
-        newp = p.dirpath('moved_samplefile')
-        p.move(newp)
-        try:
-            assert newp.check(file=1)
-            assert not p.check()
-        finally:
-            dp = newp.dirpath()
-            if hasattr(dp, 'revert'):
-                dp.revert()
-            else:
-                newp.move(p)
-                assert p.check()
-
-    def test_move_dir(self, path1):
-        source = path1.join('sampledir')
-        dest = path1.join('moveddir')
-        source.move(dest)
-        assert dest.check(dir=1)
-        assert dest.join('otherfile').check(file=1)
-        assert not source.join('sampledir').check()
-
-def setuptestfs(path):
-    if path.join('samplefile').check():
-        return
-    #print "setting up test fs for", repr(path)
-    samplefile = path.ensure('samplefile')
-    samplefile.write('samplefile\n')
-
-    execfile = path.ensure('execfile')
-    execfile.write('x=42')
-
-    execfilepy = path.ensure('execfile.py')
-    execfilepy.write('x=42')
-
-    d = {1:2, 'hello': 'world', 'answer': 42}
-    path.ensure('samplepickle').dump(d)
-
-    sampledir = path.ensure('sampledir', dir=1)
-    sampledir.ensure('otherfile')
-
-    otherdir = path.ensure('otherdir', dir=1)
-    otherdir.ensure('__init__.py')
-
-    module_a = otherdir.ensure('a.py')
-    if sys.version_info >= (2,6):
-        module_a.write('from .b import stuff as result\n')
-    else:
-        module_a.write('from b import stuff as result\n')
-    module_b = otherdir.ensure('b.py')
-    module_b.write('stuff="got it"\n')
-    module_c = otherdir.ensure('c.py')
-    module_c.write('''import py;
-import otherdir.a
-value = otherdir.a.result
-''')
-    module_d = otherdir.ensure('d.py')
-    module_d.write('''import py;
-from otherdir import a
-value2 = a.result
-''')
diff --git a/tools/py/testing/path/conftest.py b/tools/py/testing/path/conftest.py
deleted file mode 100644
index a9711b2..0000000
--- a/tools/py/testing/path/conftest.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import py
-import sys
-from py._path import svnwc as svncommon
-
-svnbin = py.path.local.sysfind('svn')
-repodump = py.path.local(__file__).dirpath('repotest.dump')
-from py.builtin import print_
-
-def pytest_funcarg__repowc1(request):
-    if svnbin is None:
-        py.test.skip("svn binary not found")
-
-    tmpdir = request.getfuncargvalue("tmpdir")
-    repo, repourl, wc = request.cached_setup(
-        setup=lambda: getrepowc(tmpdir, "path1repo", "path1wc"),
-        scope="module",
-    )
-    for x in ('test_remove', 'test_move', 'test_status_deleted'):
-        if request.function.__name__.startswith(x):
-            #print >>sys.stderr, ("saving repo", repo, "for", request.function)
-            _savedrepowc = save_repowc(repo, wc)
-            request.addfinalizer(lambda: restore_repowc(_savedrepowc))
-    return repo, repourl, wc
-
-def pytest_funcarg__repowc2(request):
-    tmpdir = request.getfuncargvalue("tmpdir")
-    name = request.function.__name__
-    repo, url, wc = getrepowc(tmpdir, "%s-repo-2" % name, "%s-wc-2" % name)
-    return repo, url, wc
-
-def getsvnbin():
-    if svnbin is None:
-        py.test.skip("svn binary not found")
-    return svnbin
-
-# make a wc directory out of a given root url
-# cache previously obtained wcs!
-#
-def getrepowc(tmpdir, reponame='basetestrepo', wcname='wc'):
-    repo = tmpdir.mkdir(reponame)
-    wcdir = tmpdir.mkdir(wcname)
-    repo.ensure(dir=1)
-    py.process.cmdexec('svnadmin create "%s"' %
-            svncommon._escape_helper(repo))
-    py.process.cmdexec('svnadmin load -q "%s" <"%s"' %
-            (svncommon._escape_helper(repo), repodump))
-    print_("created svn repository", repo)
-    wcdir.ensure(dir=1)
-    wc = py.path.svnwc(wcdir)
-    if py.std.sys.platform == 'win32':
-        repourl = "file://" + '/' + str(repo).replace('\\', '/')
-    else:
-        repourl = "file://%s" % repo
-    wc.checkout(repourl)
-    print_("checked out new repo into", wc)
-    return (repo, repourl, wc)
-
-
-def save_repowc(repo, wc):
-    assert not str(repo).startswith("file://"), repo
-    assert repo.check()
-    savedrepo = repo.dirpath(repo.basename+".1")
-    savedwc = wc.dirpath(wc.basename+".1")
-    repo.copy(savedrepo)
-    wc.localpath.copy(savedwc.localpath)
-    return savedrepo, savedwc
-
-def restore_repowc(obj):
-    savedrepo, savedwc = obj
-    #print >>sys.stderr, ("restoring", savedrepo)
-    repo = savedrepo.new(basename=savedrepo.basename[:-2])
-    assert repo.check()
-    wc = savedwc.new(basename=savedwc.basename[:-2])
-    assert wc.check()
-    wc.localpath.remove()
-    repo.remove()
-    savedrepo.move(repo)
-    savedwc.localpath.move(wc.localpath)
-    py.path.svnurl._lsnorevcache.clear()
-    py.path.svnurl._lsrevcache.clear()
diff --git a/tools/py/testing/path/test_cacheutil.py b/tools/py/testing/path/test_cacheutil.py
deleted file mode 100644
index 0b5cd31..0000000
--- a/tools/py/testing/path/test_cacheutil.py
+++ /dev/null
@@ -1,84 +0,0 @@
-import py
-from py._path import cacheutil
-
-class BasicCacheAPITest:
-    cache = None
-    def test_getorbuild(self):
-        val = self.cache.getorbuild(-42, lambda: 42)
-        assert val == 42
-        val = self.cache.getorbuild(-42, lambda: 23)
-        assert val == 42
-
-    def test_cache_get_key_error(self):
-        py.test.raises(KeyError, "self.cache._getentry(-23)")
-
-    def test_delentry_non_raising(self):
-        val = self.cache.getorbuild(100, lambda: 100)
-        self.cache.delentry(100)
-        py.test.raises(KeyError, "self.cache._getentry(100)")
-
-    def test_delentry_raising(self):
-        val = self.cache.getorbuild(100, lambda: 100)
-        self.cache.delentry(100)
-        py.test.raises(KeyError, "self.cache.delentry(100, raising=True)")
-
-    def test_clear(self):
-        self.cache.clear()
-
-class TestBuildcostAccess(BasicCacheAPITest):
-    cache = cacheutil.BuildcostAccessCache(maxentries=128)
-
-    def test_cache_works_somewhat_simple(self, monkeypatch):
-        cache = cacheutil.BuildcostAccessCache()
-        # the default gettime
-        # BuildcostAccessCache.build can
-        # result into time()-time() == 0 which makes the below
-        # test fail randomly.  Let's rather use incrementing
-        # numbers instead.
-        l = [0]
-        def counter():
-            l[0] = l[0] + 1
-            return l[0]
-        monkeypatch.setattr(cacheutil, 'gettime', counter)
-        for x in range(cache.maxentries):
-            y = cache.getorbuild(x, lambda: x)
-            assert x == y
-        for x in range(cache.maxentries):
-            assert cache.getorbuild(x, None) == x
-        halfentries = int(cache.maxentries / 2)
-        for x in range(halfentries):
-            assert cache.getorbuild(x, None) == x
-            assert cache.getorbuild(x, None) == x
-        # evict one entry
-        val = cache.getorbuild(-1, lambda: 42)
-        assert val == 42
-        # check that recently used ones are still there
-        # and are not build again
-        for x in range(halfentries):
-            assert cache.getorbuild(x, None) == x
-        assert cache.getorbuild(-1, None) == 42
-
-
-class TestAging(BasicCacheAPITest):
-    maxsecs = 0.10
-    cache = cacheutil.AgingCache(maxentries=128, maxseconds=maxsecs)
-
-    def test_cache_eviction(self):
-        self.cache.getorbuild(17, lambda: 17)
-        endtime = py.std.time.time() + self.maxsecs * 10
-        while py.std.time.time() < endtime:
-            try:
-                self.cache._getentry(17)
-            except KeyError:
-                break
-            py.std.time.sleep(self.maxsecs*0.3)
-        else:
-            py.test.fail("waiting for cache eviction failed")
-
-def test_prune_lowestweight():
-    maxsecs = 0.05
-    cache = cacheutil.AgingCache(maxentries=10, maxseconds=maxsecs)
-    for x in range(cache.maxentries):
-        cache.getorbuild(x, lambda: x)
-    py.std.time.sleep(maxsecs*1.1)
-    cache.getorbuild(cache.maxentries+1, lambda: 42)
diff --git a/tools/py/testing/path/test_local.py b/tools/py/testing/path/test_local.py
deleted file mode 100644
index bcf131f..0000000
--- a/tools/py/testing/path/test_local.py
+++ /dev/null
@@ -1,860 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-import py
-import pytest
-import os, sys
-from py.path import local
-import common
-
-failsonjython = py.test.mark.xfail("sys.platform.startswith('java')")
-failsonjywin32 = py.test.mark.xfail("sys.platform.startswith('java') "
-        "and getattr(os, '_name', None) == 'nt'")
-win32only = py.test.mark.skipif(
-        "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')")
-skiponwin32 = py.test.mark.skipif(
-        "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'")
-
-
-def pytest_funcarg__path1(request):
-    def setup():
-        path1 = request.getfuncargvalue("tmpdir")
-        common.setuptestfs(path1)
-        return path1
-    def teardown(path1):
-        # post check
-        assert path1.join("samplefile").check()
-    return request.cached_setup(setup, teardown, scope="session")
-
-class TestLocalPath(common.CommonFSTests):
-    def test_join_normpath(self, tmpdir):
-        assert tmpdir.join(".") == tmpdir
-        p = tmpdir.join("../%s" % tmpdir.basename)
-        assert p == tmpdir
-        p = tmpdir.join("..//%s/" % tmpdir.basename)
-        assert p == tmpdir
-
-    @skiponwin32
-    def test_dirpath_abs_no_abs(self, tmpdir):
-        p = tmpdir.join('foo')
-        assert p.dirpath('/bar') == tmpdir.join('bar')
-        assert tmpdir.dirpath('/bar', abs=True) == py.path.local('/bar')
-
-    def test_gethash(self, tmpdir):
-        md5 = py.builtin._tryimport('md5', 'hashlib').md5
-        lib = py.builtin._tryimport('sha', 'hashlib')
-        sha = getattr(lib, 'sha1', getattr(lib, 'sha', None))
-        fn = tmpdir.join("testhashfile")
-        data = 'hello'.encode('ascii')
-        fn.write(data, mode="wb")
-        assert fn.computehash("md5") == md5(data).hexdigest()
-        assert fn.computehash("sha1") == sha(data).hexdigest()
-        py.test.raises(ValueError, fn.computehash, "asdasd")
-
-    def test_remove_removes_readonly_file(self, tmpdir):
-        readonly_file = tmpdir.join('readonly').ensure()
-        readonly_file.chmod(0)
-        readonly_file.remove()
-        assert not readonly_file.check(exists=1)
-
-    def test_remove_removes_readonly_dir(self, tmpdir):
-        readonly_dir = tmpdir.join('readonlydir').ensure(dir=1)
-        readonly_dir.chmod(int("500", 8))
-        readonly_dir.remove()
-        assert not readonly_dir.check(exists=1)
-
-    def test_remove_removes_dir_and_readonly_file(self, tmpdir):
-        readonly_dir = tmpdir.join('readonlydir').ensure(dir=1)
-        readonly_file = readonly_dir.join('readonlyfile').ensure()
-        readonly_file.chmod(0)
-        readonly_dir.remove()
-        assert not readonly_dir.check(exists=1)
-
-    def test_remove_routes_ignore_errors(self, tmpdir, monkeypatch):
-        l = []
-        monkeypatch.setattr(py.std.shutil, 'rmtree',
-            lambda *args, **kwargs: l.append(kwargs))
-        tmpdir.remove()
-        assert not l[0]['ignore_errors']
-        for val in (True, False):
-            l[:] = []
-            tmpdir.remove(ignore_errors=val)
-            assert l[0]['ignore_errors'] == val
-
-    def test_initialize_curdir(self):
-        assert str(local()) == py.std.os.getcwd()
-
-    @skiponwin32
-    def test_chdir_gone(self, path1):
-        p = path1.ensure("dir_to_be_removed", dir=1)
-        p.chdir()
-        p.remove()
-        pytest.raises(py.error.ENOENT, py.path.local)
-        assert path1.chdir() is None
-        assert os.getcwd() == str(path1)
-
-    def test_as_cwd(self, path1):
-        dir = path1.ensure("subdir", dir=1)
-        old = py.path.local()
-        with dir.as_cwd() as x:
-            assert x == old
-            assert py.path.local() == dir
-        assert os.getcwd() == str(old)
-
-    def test_as_cwd_exception(self, path1):
-        old = py.path.local()
-        dir = path1.ensure("subdir", dir=1)
-        with pytest.raises(ValueError):
-            with dir.as_cwd():
-                raise ValueError()
-        assert old == py.path.local()
-
-    def test_initialize_reldir(self, path1):
-        with path1.as_cwd():
-            p = local('samplefile')
-            assert p.check()
-
-    @pytest.mark.xfail("sys.version_info < (2,6) and sys.platform == 'win32'")
-    def test_tilde_expansion(self, monkeypatch, tmpdir):
-        monkeypatch.setenv("HOME", str(tmpdir))
-        p = py.path.local("~", expanduser=True)
-        assert p == os.path.expanduser("~")
-
-    def test_eq_with_strings(self, path1):
-        path1 = path1.join('sampledir')
-        path2 = str(path1)
-        assert path1 == path2
-        assert path2 == path1
-        path3 = path1.join('samplefile')
-        assert path3 != path2
-        assert path2 != path3
-
-    def test_eq_with_none(self, path1):
-        assert path1 != None
-
-    def test_gt_with_strings(self, path1):
-        path2 = path1.join('sampledir')
-        path3 = str(path1.join("ttt"))
-        assert path3 > path2
-        assert path2 < path3
-        assert path2 < "ttt"
-        assert "ttt" > path2
-        path4 = path1.join("aaa")
-        l = [path2, path4,path3]
-        assert sorted(l) == [path4, path2, path3]
-
-    def test_open_and_ensure(self, path1):
-        p = path1.join("sub1", "sub2", "file")
-        with p.open("w", ensure=1) as f:
-            f.write("hello")
-        assert p.read() == "hello"
-
-    def test_write_and_ensure(self, path1):
-        p = path1.join("sub1", "sub2", "file")
-        p.write("hello", ensure=1)
-        assert p.read() == "hello"
-
-    @py.test.mark.multi(bin=(False, True))
-    def test_dump(self, tmpdir, bin):
-        path = tmpdir.join("dumpfile%s" % int(bin))
-        try:
-            d = {'answer' : 42}
-            path.dump(d, bin=bin)
-            f = path.open('rb+')
-            dnew = py.std.pickle.load(f)
-            assert d == dnew
-        finally:
-            f.close()
-
-    @failsonjywin32
-    def test_setmtime(self):
-        import tempfile
-        import time
-        try:
-            fd, name = tempfile.mkstemp()
-            py.std.os.close(fd)
-        except AttributeError:
-            name = tempfile.mktemp()
-            open(name, 'w').close()
-        try:
-            mtime = int(time.time())-100
-            path = local(name)
-            assert path.mtime() != mtime
-            path.setmtime(mtime)
-            assert path.mtime() == mtime
-            path.setmtime()
-            assert path.mtime() != mtime
-        finally:
-            py.std.os.remove(name)
-
-    def test_normpath(self, path1):
-        new1 = path1.join("/otherdir")
-        new2 = path1.join("otherdir")
-        assert str(new1) == str(new2)
-
-    def test_mkdtemp_creation(self):
-        d = local.mkdtemp()
-        try:
-            assert d.check(dir=1)
-        finally:
-            d.remove(rec=1)
-
-    def test_tmproot(self):
-        d = local.mkdtemp()
-        tmproot = local.get_temproot()
-        try:
-            assert d.check(dir=1)
-            assert d.dirpath() == tmproot
-        finally:
-            d.remove(rec=1)
-
-    def test_chdir(self, tmpdir):
-        old = local()
-        try:
-            res = tmpdir.chdir()
-            assert str(res) == str(old)
-            assert py.std.os.getcwd() == str(tmpdir)
-        finally:
-            old.chdir()
-
-    def test_ensure_filepath_withdir(self, tmpdir):
-        newfile = tmpdir.join('test1','test')
-        newfile.ensure()
-        assert newfile.check(file=1)
-        newfile.write("42")
-        newfile.ensure()
-        s = newfile.read()
-        assert s == "42"
-
-    def test_ensure_filepath_withoutdir(self, tmpdir):
-        newfile = tmpdir.join('test1file')
-        t = newfile.ensure()
-        assert t == newfile
-        assert newfile.check(file=1)
-
-    def test_ensure_dirpath(self, tmpdir):
-        newfile = tmpdir.join('test1','testfile')
-        t = newfile.ensure(dir=1)
-        assert t == newfile
-        assert newfile.check(dir=1)
-
-    def test_init_from_path(self, tmpdir):
-        l = local()
-        l2 = local(l)
-        assert l2 == l
-
-        wc = py.path.svnwc('.')
-        l3 = local(wc)
-        assert l3 is not wc
-        assert l3.strpath == wc.strpath
-        assert not hasattr(l3, 'commit')
-
-    @py.test.mark.xfail(run=False, reason="unreliable est for long filenames")
-    def test_long_filenames(self, tmpdir):
-        if sys.platform == "win32":
-            py.test.skip("win32: work around needed for path length limit")
-        # see http://codespeak.net/pipermail/py-dev/2008q2/000922.html
-
-        # testing paths > 260 chars (which is Windows' limitation, but
-        # depending on how the paths are used), but > 4096 (which is the
-        # Linux' limitation) - the behaviour of paths with names > 4096 chars
-        # is undetermined
-        newfilename = '/test' * 60
-        l = tmpdir.join(newfilename)
-        l.ensure(file=True)
-        l.write('foo')
-        l2 = tmpdir.join(newfilename)
-        assert l2.read() == 'foo'
-
-    def test_visit_depth_first(self, tmpdir):
-        p1 = tmpdir.ensure("a","1")
-        p2 = tmpdir.ensure("b","2")
-        p3 = tmpdir.ensure("breadth")
-        l = list(tmpdir.visit(lambda x: x.check(file=1)))
-        assert len(l) == 3
-        # check that breadth comes last
-        assert l[2] == p3
-
-    def test_visit_rec_fnmatch(self, tmpdir):
-        p1 = tmpdir.ensure("a","123")
-        p2 = tmpdir.ensure(".b","345")
-        l = list(tmpdir.visit("???", rec="[!.]*"))
-        assert len(l) == 1
-        # check that breadth comes last
-        assert l[0] == p1
-
-    def test_fnmatch_file_abspath(self, tmpdir):
-        b = tmpdir.join("a", "b")
-        assert b.fnmatch(os.sep.join("ab"))
-        pattern = os.sep.join([str(tmpdir), "*", "b"])
-        assert b.fnmatch(pattern)
-
-    def test_sysfind(self):
-        name = sys.platform == "win32" and "cmd" or "test"
-        x = py.path.local.sysfind(name)
-        assert x.check(file=1)
-        assert py.path.local.sysfind('jaksdkasldqwe') is None
-        assert py.path.local.sysfind(name, paths=[]) is None
-        x2 = py.path.local.sysfind(name, paths=[x.dirpath()])
-        assert x2 == x
-
-
-class TestExecutionOnWindows:
-    pytestmark = win32only
-
-    def test_sysfind_bat_exe_before(self, tmpdir, monkeypatch):
-        monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep)
-        tmpdir.ensure("hello")
-        h = tmpdir.ensure("hello.bat")
-        x = py.path.local.sysfind("hello")
-        assert x == h
-
-
-class TestExecution:
-    pytestmark = skiponwin32
-
-    def test_sysfind_no_permisson_ignored(self, monkeypatch, tmpdir):
-        noperm = tmpdir.ensure('noperm', dir=True)
-        monkeypatch.setenv("PATH", noperm, prepend=":")
-        noperm.chmod(0)
-        assert py.path.local.sysfind('jaksdkasldqwe') is None
-
-    def test_sysfind_absolute(self):
-        x = py.path.local.sysfind('test')
-        assert x.check(file=1)
-        y = py.path.local.sysfind(str(x))
-        assert y.check(file=1)
-        assert y == x
-
-    def test_sysfind_multiple(self, tmpdir, monkeypatch):
-        monkeypatch.setenv('PATH',
-                          "%s:%s" % (tmpdir.ensure('a'),
-                                       tmpdir.join('b')),
-                          prepend=":")
-        tmpdir.ensure('b', 'a')
-        checker = lambda x: x.dirpath().basename == 'b'
-        x = py.path.local.sysfind('a', checker=checker)
-        assert x.basename == 'a'
-        assert x.dirpath().basename == 'b'
-        checker = lambda x: None
-        assert py.path.local.sysfind('a', checker=checker) is None
-
-    def test_sysexec(self):
-        x = py.path.local.sysfind('ls')
-        out = x.sysexec('-a')
-        for x in py.path.local().listdir():
-            assert out.find(x.basename) != -1
-
-    def test_sysexec_failing(self):
-        x = py.path.local.sysfind('false')
-        py.test.raises(py.process.cmdexec.Error, """
-            x.sysexec('aksjdkasjd')
-        """)
-
-    def test_make_numbered_dir(self, tmpdir):
-        tmpdir.ensure('base.not_an_int', dir=1)
-        for i in range(10):
-            numdir = local.make_numbered_dir(prefix='base.', rootdir=tmpdir,
-                                             keep=2, lock_timeout=0)
-            assert numdir.check()
-            assert numdir.basename == 'base.%d' %i
-            if i>=1:
-                assert numdir.new(ext=str(i-1)).check()
-            if i>=2:
-                assert numdir.new(ext=str(i-2)).check()
-            if i>=3:
-                assert not numdir.new(ext=str(i-3)).check()
-
-    def test_make_numbered_dir_NotImplemented_Error(self, tmpdir, monkeypatch):
-        def notimpl(x, y):
-            raise NotImplementedError(42)
-        monkeypatch.setattr(py.std.os, 'symlink', notimpl)
-        x = tmpdir.make_numbered_dir(rootdir=tmpdir, lock_timeout=0)
-        assert x.relto(tmpdir)
-        assert x.check()
-
-    def test_locked_make_numbered_dir(self, tmpdir):
-        for i in range(10):
-            numdir = local.make_numbered_dir(prefix='base2.', rootdir=tmpdir,
-                                             keep=2)
-            assert numdir.check()
-            assert numdir.basename == 'base2.%d' %i
-            for j in range(i):
-                assert numdir.new(ext=str(j)).check()
-
-    def test_error_preservation(self, path1):
-        py.test.raises (EnvironmentError, path1.join('qwoeqiwe').mtime)
-        py.test.raises (EnvironmentError, path1.join('qwoeqiwe').read)
-
-    #def test_parentdirmatch(self):
-    #    local.parentdirmatch('std', startmodule=__name__)
-    #
-
-
-class TestImport:
-    def test_pyimport(self, path1):
-        obj = path1.join('execfile.py').pyimport()
-        assert obj.x == 42
-        assert obj.__name__ == 'execfile'
-
-    def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir):
-        p = tmpdir.ensure("a", "test_x123.py")
-        p.pyimport()
-        tmpdir.join("a").move(tmpdir.join("b"))
-        pytest.raises(tmpdir.ImportMismatchError,
-            lambda: tmpdir.join("b", "test_x123.py").pyimport())
-
-    def test_pyimport_messy_name(self, tmpdir):
-        # http://bitbucket.org/hpk42/py-trunk/issue/129
-        path = tmpdir.ensure('foo__init__.py')
-        obj = path.pyimport()
-
-    def test_pyimport_dir(self, tmpdir):
-        p = tmpdir.join("hello_123")
-        p_init = p.ensure("__init__.py")
-        m = p.pyimport()
-        assert m.__name__ == "hello_123"
-        m = p_init.pyimport()
-        assert m.__name__ == "hello_123"
-
-    def test_pyimport_execfile_different_name(self, path1):
-        obj = path1.join('execfile.py').pyimport(modname="0x.y.z")
-        assert obj.x == 42
-        assert obj.__name__ == '0x.y.z'
-
-    def test_pyimport_a(self, path1):
-        otherdir = path1.join('otherdir')
-        mod = otherdir.join('a.py').pyimport()
-        assert mod.result == "got it"
-        assert mod.__name__ == 'otherdir.a'
-
-    def test_pyimport_b(self, path1):
-        otherdir = path1.join('otherdir')
-        mod = otherdir.join('b.py').pyimport()
-        assert mod.stuff == "got it"
-        assert mod.__name__ == 'otherdir.b'
-
-    def test_pyimport_c(self, path1):
-        otherdir = path1.join('otherdir')
-        mod = otherdir.join('c.py').pyimport()
-        assert mod.value == "got it"
-
-    def test_pyimport_d(self, path1):
-        otherdir = path1.join('otherdir')
-        mod = otherdir.join('d.py').pyimport()
-        assert mod.value2 == "got it"
-
-    def test_pyimport_and_import(self, tmpdir):
-        tmpdir.ensure('xxxpackage', '__init__.py')
-        mod1path = tmpdir.ensure('xxxpackage', 'module1.py')
-        mod1 = mod1path.pyimport()
-        assert mod1.__name__ == 'xxxpackage.module1'
-        from xxxpackage import module1
-        assert module1 is mod1
-
-    def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir):
-        name = 'pointsback123'
-        ModuleType = type(py.std.os)
-        p = tmpdir.ensure(name + '.py')
-        for ending in ('.pyc', '$py.class', '.pyo'):
-            mod = ModuleType(name)
-            pseudopath = tmpdir.ensure(name+ending)
-            mod.__file__ = str(pseudopath)
-            monkeypatch.setitem(sys.modules, name, mod)
-            newmod = p.pyimport()
-            assert mod == newmod
-        monkeypatch.undo()
-        mod = ModuleType(name)
-        pseudopath = tmpdir.ensure(name+"123.py")
-        mod.__file__ = str(pseudopath)
-        monkeypatch.setitem(sys.modules, name, mod)
-        excinfo = py.test.raises(pseudopath.ImportMismatchError,
-            "p.pyimport()")
-        modname, modfile, orig = excinfo.value.args
-        assert modname == name
-        assert modfile == pseudopath
-        assert orig == p
-        assert issubclass(pseudopath.ImportMismatchError, ImportError)
-
-    def test_issue131_pyimport_on__init__(self, tmpdir):
-        # __init__.py files may be namespace packages, and thus the
-        # __file__ of an imported module may not be ourselves
-        # see issue
-        p1 = tmpdir.ensure("proja", "__init__.py")
-        p2 = tmpdir.ensure("sub", "proja", "__init__.py")
-        m1 = p1.pyimport()
-        m2 = p2.pyimport()
-        assert m1 == m2
-
-    def test_ensuresyspath_append(self, tmpdir):
-        root1 = tmpdir.mkdir("root1")
-        file1 = root1.ensure("x123.py")
-        assert str(root1) not in sys.path
-        file1.pyimport(ensuresyspath="append")
-        assert str(root1) == sys.path[-1]
-        assert str(root1) not in sys.path[:-1]
-
-
-def test_pypkgdir(tmpdir):
-    pkg = tmpdir.ensure('pkg1', dir=1)
-    pkg.ensure("__init__.py")
-    pkg.ensure("subdir/__init__.py")
-    assert pkg.pypkgpath() == pkg
-    assert pkg.join('subdir', '__init__.py').pypkgpath() == pkg
-
-def test_pypkgdir_unimportable(tmpdir):
-    pkg = tmpdir.ensure('pkg1-1', dir=1) # unimportable
-    pkg.ensure("__init__.py")
-    subdir = pkg.ensure("subdir/__init__.py").dirpath()
-    assert subdir.pypkgpath() == subdir
-    assert subdir.ensure("xyz.py").pypkgpath() == subdir
-    assert not pkg.pypkgpath()
-
-def test_isimportable():
-    from py._path.local import isimportable
-    assert not isimportable("")
-    assert isimportable("x")
-    assert isimportable("x1")
-    assert isimportable("x_1")
-    assert isimportable("_")
-    assert isimportable("_1")
-    assert not isimportable("x-1")
-    assert not isimportable("x:1")
-
-def test_homedir_from_HOME(monkeypatch):
-    path = os.getcwd()
-    monkeypatch.setenv("HOME", path)
-    assert py.path.local._gethomedir() == py.path.local(path)
-
-def test_homedir_not_exists(monkeypatch):
-    monkeypatch.delenv("HOME", raising=False)
-    monkeypatch.delenv("HOMEDRIVE", raising=False)
-    homedir = py.path.local._gethomedir()
-    assert homedir is None
-
-def test_samefile(tmpdir):
-    assert tmpdir.samefile(tmpdir)
-    p = tmpdir.ensure("hello")
-    assert p.samefile(p)
-    with p.dirpath().as_cwd():
-        assert p.samefile(p.basename)
-    if sys.platform == "win32":
-        p1 = p.__class__(str(p).lower())
-        p2 = p.__class__(str(p).upper())
-        assert p1.samefile(p2)
-
-def test_listdir_single_arg(tmpdir):
-    tmpdir.ensure("hello")
-    assert tmpdir.listdir("hello")[0].basename == "hello"
-
-def test_mkdtemp_rootdir(tmpdir):
-    dtmp = local.mkdtemp(rootdir=tmpdir)
-    assert tmpdir.listdir() == [dtmp]
-
-class TestWINLocalPath:
-    pytestmark = win32only
-
-    def test_owner_group_not_implemented(self, path1):
-        py.test.raises(NotImplementedError, "path1.stat().owner")
-        py.test.raises(NotImplementedError, "path1.stat().group")
-
-    def test_chmod_simple_int(self, path1):
-        py.builtin.print_("path1 is", path1)
-        mode = path1.stat().mode
-        # Ensure that we actually change the mode to something different.
-        path1.chmod(mode == 0 and 1 or 0)
-        try:
-            print(path1.stat().mode)
-            print(mode)
-            assert path1.stat().mode != mode
-        finally:
-            path1.chmod(mode)
-            assert path1.stat().mode == mode
-
-    def test_path_comparison_lowercase_mixed(self, path1):
-        t1 = path1.join("a_path")
-        t2 = path1.join("A_path")
-        assert t1 == t1
-        assert t1 == t2
-
-    def test_relto_with_mixed_case(self, path1):
-        t1 = path1.join("a_path", "fiLe")
-        t2 = path1.join("A_path")
-        assert t1.relto(t2) == "fiLe"
-
-    def test_allow_unix_style_paths(self, path1):
-        t1 = path1.join('a_path')
-        assert t1 == str(path1) + '\\a_path'
-        t1 = path1.join('a_path/')
-        assert t1 == str(path1) + '\\a_path'
-        t1 = path1.join('dir/a_path')
-        assert t1 == str(path1) + '\\dir\\a_path'
-
-    def test_sysfind_in_currentdir(self, path1):
-        cmd = py.path.local.sysfind('cmd')
-        root = cmd.new(dirname='', basename='') # c:\ in most installations
-        with root.as_cwd():
-            x = py.path.local.sysfind(cmd.relto(root))
-            assert x.check(file=1)
-
-    def test_fnmatch_file_abspath_posix_pattern_on_win32(self, tmpdir):
-        # path-matching patterns might contain a posix path separator '/'
-        # Test that we can match that pattern on windows.
-        import posixpath
-        b = tmpdir.join("a", "b")
-        assert b.fnmatch(posixpath.sep.join("ab"))
-        pattern = posixpath.sep.join([str(tmpdir), "*", "b"])
-        assert b.fnmatch(pattern)
-
-class TestPOSIXLocalPath:
-    pytestmark = skiponwin32
-
-    def test_hardlink(self, tmpdir):
-        linkpath = tmpdir.join('test')
-        filepath = tmpdir.join('file')
-        filepath.write("Hello")
-        nlink = filepath.stat().nlink
-        linkpath.mklinkto(filepath)
-        assert filepath.stat().nlink == nlink + 1
-
-    def test_symlink_are_identical(self, tmpdir):
-        filepath = tmpdir.join('file')
-        filepath.write("Hello")
-        linkpath = tmpdir.join('test')
-        linkpath.mksymlinkto(filepath)
-        assert linkpath.readlink() == str(filepath)
-
-    def test_symlink_isfile(self, tmpdir):
-        linkpath = tmpdir.join('test')
-        filepath = tmpdir.join('file')
-        filepath.write("")
-        linkpath.mksymlinkto(filepath)
-        assert linkpath.check(file=1)
-        assert not linkpath.check(link=0, file=1)
-        assert linkpath.islink()
-
-    def test_symlink_relative(self, tmpdir):
-        linkpath = tmpdir.join('test')
-        filepath = tmpdir.join('file')
-        filepath.write("Hello")
-        linkpath.mksymlinkto(filepath, absolute=False)
-        assert linkpath.readlink() == "file"
-        assert filepath.read() == linkpath.read()
-
-    def test_symlink_not_existing(self, tmpdir):
-        linkpath = tmpdir.join('testnotexisting')
-        assert not linkpath.check(link=1)
-        assert linkpath.check(link=0)
-
-    def test_relto_with_root(self, path1, tmpdir):
-        y = path1.join('x').relto(py.path.local('/'))
-        assert y[0] == str(path1)[1]
-
-    def test_visit_recursive_symlink(self, tmpdir):
-        linkpath = tmpdir.join('test')
-        linkpath.mksymlinkto(tmpdir)
-        visitor = tmpdir.visit(None, lambda x: x.check(link=0))
-        assert list(visitor) == [linkpath]
-
-    def test_symlink_isdir(self, tmpdir):
-        linkpath = tmpdir.join('test')
-        linkpath.mksymlinkto(tmpdir)
-        assert linkpath.check(dir=1)
-        assert not linkpath.check(link=0, dir=1)
-
-    def test_symlink_remove(self, tmpdir):
-        linkpath = tmpdir.join('test')
-        linkpath.mksymlinkto(linkpath) # point to itself
-        assert linkpath.check(link=1)
-        linkpath.remove()
-        assert not linkpath.check()
-
-    def test_realpath_file(self, tmpdir):
-        linkpath = tmpdir.join('test')
-        filepath = tmpdir.join('file')
-        filepath.write("")
-        linkpath.mksymlinkto(filepath)
-        realpath = linkpath.realpath()
-        assert realpath.basename == 'file'
-
-    def test_owner(self, path1, tmpdir):
-        from pwd import getpwuid
-        from grp import getgrgid
-        stat = path1.stat()
-        assert stat.path == path1
-
-        uid = stat.uid
-        gid = stat.gid
-        owner = getpwuid(uid)[0]
-        group = getgrgid(gid)[0]
-
-        assert uid == stat.uid
-        assert owner == stat.owner
-        assert gid == stat.gid
-        assert group == stat.group
-
-    def test_stat_helpers(self, tmpdir, monkeypatch):
-        path1 = tmpdir.ensure("file")
-        stat1 = path1.stat()
-        stat2 = tmpdir.stat()
-        assert stat1.isfile()
-        assert stat2.isdir()
-        assert not stat1.islink()
-        assert not stat2.islink()
-
-    def test_stat_non_raising(self, tmpdir):
-        path1 = tmpdir.join("file")
-        pytest.raises(py.error.ENOENT, lambda: path1.stat())
-        res = path1.stat(raising=False)
-        assert res is None
-
-    def test_atime(self, tmpdir):
-        import time
-        path = tmpdir.ensure('samplefile')
-        now = time.time()
-        atime1 = path.atime()
-        # we could wait here but timer resolution is very
-        # system dependent
-        path.read()
-        time.sleep(0.01)
-        atime2 = path.atime()
-        time.sleep(0.01)
-        duration = time.time() - now
-        assert (atime2-atime1) <= duration
-
-    def test_commondir(self, path1):
-        # XXX This is here in local until we find a way to implement this
-        #     using the subversion command line api.
-        p1 = path1.join('something')
-        p2 = path1.join('otherthing')
-        assert p1.common(p2) == path1
-        assert p2.common(p1) == path1
-
-    def test_commondir_nocommon(self, path1):
-        # XXX This is here in local until we find a way to implement this
-        #     using the subversion command line api.
-        p1 = path1.join('something')
-        p2 = py.path.local(path1.sep+'blabla')
-        assert p1.common(p2) == '/'
-
-    def test_join_to_root(self, path1):
-        root = path1.parts()[0]
-        assert len(str(root)) == 1
-        assert str(root.join('a')) == '//a'  # posix allows two slashes
-
-    def test_join_root_to_root_with_no_abs(self, path1):
-        nroot = path1.join('/')
-        assert str(path1) == str(nroot)
-        assert path1 == nroot
-
-    def test_chmod_simple_int(self, path1):
-        mode = path1.stat().mode
-        path1.chmod(int(mode/2))
-        try:
-            assert path1.stat().mode != mode
-        finally:
-            path1.chmod(mode)
-            assert path1.stat().mode == mode
-
-    def test_chmod_rec_int(self, path1):
-        # XXX fragile test
-        recfilter = lambda x: x.check(dotfile=0, link=0)
-        oldmodes = {}
-        for x in path1.visit(rec=recfilter):
-            oldmodes[x] = x.stat().mode
-        path1.chmod(int("772", 8), rec=recfilter)
-        try:
-            for x in path1.visit(rec=recfilter):
-                assert x.stat().mode & int("777", 8) == int("772", 8)
-        finally:
-            for x,y in oldmodes.items():
-                x.chmod(y)
-
-    def test_copy_archiving(self, tmpdir):
-        unicode_fn = u"something-\342\200\223.txt"
-        f = tmpdir.ensure("a", unicode_fn)
-        a = f.dirpath()
-        oldmode = f.stat().mode
-        newmode = oldmode ^ 1
-        f.chmod(newmode)
-        b = tmpdir.join("b")
-        a.copy(b, mode=True)
-        assert b.join(f.basename).stat().mode == newmode
-
-    @failsonjython
-    def test_chown_identity(self, path1):
-        owner = path1.stat().owner
-        group = path1.stat().group
-        path1.chown(owner, group)
-
-    @failsonjython
-    def test_chown_dangling_link(self, path1):
-        owner = path1.stat().owner
-        group = path1.stat().group
-        x = path1.join('hello')
-        x.mksymlinkto('qlwkejqwlek')
-        try:
-            path1.chown(owner, group, rec=1)
-        finally:
-            x.remove(rec=0)
-
-    @failsonjython
-    def test_chown_identity_rec_mayfail(self, path1):
-        owner = path1.stat().owner
-        group = path1.stat().group
-        path1.chown(owner, group)
-
-
-class TestUnicodePy2Py3:
-    def test_join_ensure(self, tmpdir, monkeypatch):
-        if sys.version_info >= (3,0) and "LANG" not in os.environ:
-            pytest.skip("cannot run test without locale")
-        x = py.path.local(tmpdir.strpath)
-        part = "hällo"
-        y = x.ensure(part)
-        assert x.join(part) == y
-
-    def test_listdir(self, tmpdir):
-        if sys.version_info >= (3,0) and "LANG" not in os.environ:
-            pytest.skip("cannot run test without locale")
-        x = py.path.local(tmpdir.strpath)
-        part = "hällo"
-        y = x.ensure(part)
-        assert x.listdir(part)[0] == y
-
-    @pytest.mark.xfail(reason="changing read/write might break existing usages")
-    def test_read_write(self, tmpdir):
-        x = tmpdir.join("hello")
-        part = py.builtin._totext("hällo", "utf8")
-        x.write(part)
-        assert x.read() == part
-        x.write(part.encode(sys.getdefaultencoding()))
-        assert x.read() == part.encode(sys.getdefaultencoding())
-
-class TestBinaryAndTextMethods:
-    def test_read_binwrite(self, tmpdir):
-        x = tmpdir.join("hello")
-        part = py.builtin._totext("hällo", "utf8")
-        part_utf8 = part.encode("utf8")
-        x.write_binary(part_utf8)
-        assert x.read_binary() == part_utf8
-        s = x.read_text(encoding="utf8")
-        assert s == part
-        assert py.builtin._istext(s)
-
-    def test_read_textwrite(self, tmpdir):
-        x = tmpdir.join("hello")
-        part = py.builtin._totext("hällo", "utf8")
-        part_utf8 = part.encode("utf8")
-        x.write_text(part, encoding="utf8")
-        assert x.read_binary() == part_utf8
-        assert x.read_text(encoding="utf8") == part
-
-    def test_default_encoding(self, tmpdir):
-        x = tmpdir.join("hello")
-        # Can't use UTF8 as the default encoding (ASCII) doesn't support it
-        part = py.builtin._totext("hello", "ascii")
-        x.write_text(part, "ascii")
-        s = x.read_text("ascii")
-        assert s == part
-        assert type(s) == type(part)
diff --git a/tools/py/testing/path/test_svnauth.py b/tools/py/testing/path/test_svnauth.py
deleted file mode 100644
index b3f3665..0000000
--- a/tools/py/testing/path/test_svnauth.py
+++ /dev/null
@@ -1,454 +0,0 @@
-import py
-import svntestbase
-from py.path import SvnAuth
-import time
-import sys
-
-svnbin = py.path.local.sysfind('svn')
-
-def make_repo_auth(repo, userdata):
-    """ write config to repo
-
-        user information in userdata is used for auth
-        userdata has user names as keys, and a tuple (password, readwrite) as
-        values, where 'readwrite' is either 'r' or 'rw'
-    """
-    confdir = py.path.local(repo).join('conf')
-    confdir.join('svnserve.conf').write('''\
-[general]
-anon-access = none
-password-db = passwd
-authz-db = authz
-realm = TestRepo
-''')
-    authzdata = '[/]\n'
-    passwddata = '[users]\n'
-    for user in userdata:
-        authzdata += '%s = %s\n' % (user, userdata[user][1])
-        passwddata += '%s = %s\n' % (user, userdata[user][0])
-    confdir.join('authz').write(authzdata)
-    confdir.join('passwd').write(passwddata)
-
-def serve_bg(repopath):
-    pidfile = py.path.local(repopath).join('pid')
-    port = 10000
-    e = None
-    while port < 10010:
-        cmd = 'svnserve -d -T --listen-port=%d --pid-file=%s -r %s' % (
-               port, pidfile, repopath)
-        print(cmd)
-        try:
-            py.process.cmdexec(cmd)
-        except py.process.cmdexec.Error:
-            e = sys.exc_info()[1]
-        else:
-            # XXX we assume here that the pid file gets written somewhere, I
-            # guess this should be relatively safe... (I hope, at least?)
-            counter = pid = 0
-            while counter < 10:
-                counter += 1
-                try:
-                    pid = pidfile.read()
-                except py.error.ENOENT:
-                    pass
-                if pid:
-                    break
-                time.sleep(0.2)
-            return port, int(pid)
-        port += 1
-    raise IOError('could not start svnserve: %s' % (e,))
-
-class TestSvnAuth(object):
-    def test_basic(self):
-        auth = SvnAuth('foo', 'bar')
-        assert auth.username == 'foo'
-        assert auth.password == 'bar'
-        assert str(auth)
-
-    def test_makecmdoptions_uname_pw_makestr(self):
-        auth = SvnAuth('foo', 'bar')
-        assert auth.makecmdoptions() == '--username="foo" --password="bar"'
-
-    def test_makecmdoptions_quote_escape(self):
-        auth = SvnAuth('fo"o', '"ba\'r"')
-        assert auth.makecmdoptions() == '--username="fo\\"o" --password="\\"ba\'r\\""'
-
-    def test_makecmdoptions_no_cache_auth(self):
-        auth = SvnAuth('foo', 'bar', cache_auth=False)
-        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
-                                         '--no-auth-cache')
-
-    def test_makecmdoptions_no_interactive(self):
-        auth = SvnAuth('foo', 'bar', interactive=False)
-        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
-                                         '--non-interactive')
-
-    def test_makecmdoptions_no_interactive_no_cache_auth(self):
-        auth = SvnAuth('foo', 'bar', cache_auth=False,
-                               interactive=False)
-        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
-                                         '--no-auth-cache --non-interactive')
-
-class svnwc_no_svn(py.path.svnwc):
-    def __new__(cls, *args, **kwargs):
-        self = super(svnwc_no_svn, cls).__new__(cls, *args, **kwargs)
-        self.commands = []
-        return self
-
-    def _svn(self, *args):
-        self.commands.append(args)
-
-class TestSvnWCAuth(object):
-    def setup_method(self, meth):
-        if not svnbin:
-            py.test.skip("svn binary required")
-        self.auth = SvnAuth('user', 'pass', cache_auth=False)
-
-    def test_checkout(self):
-        wc = svnwc_no_svn('foo', auth=self.auth)
-        wc.checkout('url')
-        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
-                                      '--no-auth-cache')
-
-    def test_commit(self):
-        wc = svnwc_no_svn('foo', auth=self.auth)
-        wc.commit('msg')
-        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
-                                      '--no-auth-cache')
-
-    def test_checkout_no_cache_auth(self):
-        wc = svnwc_no_svn('foo', auth=self.auth)
-        wc.checkout('url')
-        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
-                                      '--no-auth-cache')
-
-    def test_checkout_auth_from_constructor(self):
-        wc = svnwc_no_svn('foo', auth=self.auth)
-        wc.checkout('url')
-        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
-                                      '--no-auth-cache')
-
-class svnurl_no_svn(py.path.svnurl):
-    cmdexec_output = 'test'
-    popen_output = 'test'
-    def __new__(cls, *args, **kwargs):
-        self = super(svnurl_no_svn, cls).__new__(cls, *args, **kwargs)
-        self.commands = []
-        return self
-
-    def _cmdexec(self, cmd):
-        self.commands.append(cmd)
-        return self.cmdexec_output
-
-    def _popen(self, cmd):
-        self.commands.append(cmd)
-        return self.popen_output
-
-class TestSvnURLAuth(object):
-    def setup_method(self, meth):
-        self.auth = SvnAuth('foo', 'bar')
-
-    def test_init(self):
-        u = svnurl_no_svn('http://foo.bar/svn')
-        assert u.auth is None
-
-        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
-        assert u.auth is self.auth
-
-    def test_new(self):
-        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
-        new = u.new(basename='bar')
-        assert new.auth is self.auth
-        assert new.url == 'http://foo.bar/svn/bar'
-
-    def test_join(self):
-        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
-        new = u.join('foo')
-        assert new.auth is self.auth
-        assert new.url == 'http://foo.bar/svn/foo'
-
-    def test_listdir(self):
-        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
-        u.cmdexec_output = '''\
-   1717 johnny           1529 Nov 04 14:32 LICENSE.txt
-   1716 johnny           5352 Nov 04 14:28 README.txt
-'''
-        paths = u.listdir()
-        assert paths[0].auth is self.auth
-        assert paths[1].auth is self.auth
-        assert paths[0].basename == 'LICENSE.txt'
-
-    def test_info(self):
-        u = svnurl_no_svn('http://foo.bar/svn/LICENSE.txt', auth=self.auth)
-        def dirpath(self):
-            return self
-        u.cmdexec_output = '''\
-   1717 johnny           1529 Nov 04 14:32 LICENSE.txt
-   1716 johnny           5352 Nov 04 14:28 README.txt
-'''
-        org_dp = u.__class__.dirpath
-        u.__class__.dirpath = dirpath
-        try:
-            info = u.info()
-        finally:
-            u.dirpath = org_dp
-        assert info.size == 1529
-
-    def test_open(self):
-        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
-        foo = u.join('foo')
-        foo.check = lambda *args, **kwargs: True
-        ret = foo.open()
-        assert ret == 'test'
-        assert '--username="foo" --password="bar"' in foo.commands[0]
-
-    def test_dirpath(self):
-        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
-        parent = u.dirpath()
-        assert parent.auth is self.auth
-
-    def test_mkdir(self):
-        u = svnurl_no_svn('http://foo.bar/svn/qweqwe', auth=self.auth)
-        assert not u.commands
-        u.mkdir(msg='created dir foo')
-        assert u.commands
-        assert '--username="foo" --password="bar"' in u.commands[0]
-
-    def test_copy(self):
-        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
-        u2 = svnurl_no_svn('http://foo.bar/svn2')
-        u.copy(u2, 'copied dir')
-        assert '--username="foo" --password="bar"' in u.commands[0]
-
-    def test_rename(self):
-        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
-        u.rename('http://foo.bar/svn/bar', 'moved foo to bar')
-        assert '--username="foo" --password="bar"' in u.commands[0]
-
-    def test_remove(self):
-        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
-        u.remove(msg='removing foo')
-        assert '--username="foo" --password="bar"' in u.commands[0]
-
-    def test_export(self):
-        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
-        target = py.path.local('/foo')
-        u.export(target)
-        assert '--username="foo" --password="bar"' in u.commands[0]
-
-    def test_log(self):
-        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
-        u.popen_output = py.io.TextIO(py.builtin._totext('''\
-<?xml version="1.0"?>
-<log>
-<logentry revision="51381">
-<author>guido</author>
-<date>2008-02-11T12:12:18.476481Z</date>
-<msg>Creating branch to work on auth support for py.path.svn*.
-</msg>
-</logentry>
-</log>
-''', 'ascii'))
-        u.check = lambda *args, **kwargs: True
-        ret = u.log(10, 20, verbose=True)
-        assert '--username="foo" --password="bar"' in u.commands[0]
-        assert len(ret) == 1
-        assert int(ret[0].rev) == 51381
-        assert ret[0].author == 'guido'
-
-    def test_propget(self):
-        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
-        u.propget('foo')
-        assert '--username="foo" --password="bar"' in u.commands[0]
-
-def pytest_funcarg__setup(request):
-    return Setup(request)
-
-class Setup:
-    def __init__(self, request):
-        if not svnbin:
-            py.test.skip("svn binary required")
-        if not request.config.option.runslowtests:
-            py.test.skip('use --runslowtests to run these tests')
-
-        tmpdir = request.getfuncargvalue("tmpdir")
-        repodir = tmpdir.join("repo")
-        py.process.cmdexec('svnadmin create %s' % repodir)
-        if sys.platform == 'win32':
-            repodir = '/' + str(repodir).replace('\\', '/')
-        self.repo = py.path.svnurl("file://%s" % repodir)
-        if py.std.sys.platform == 'win32':
-            # remove trailing slash...
-            repodir = repodir[1:]
-        self.repopath = py.path.local(repodir)
-        self.temppath = tmpdir.mkdir("temppath")
-        self.auth = SvnAuth('johnny', 'foo', cache_auth=False,
-                                    interactive=False)
-        make_repo_auth(self.repopath, {'johnny': ('foo', 'rw')})
-        self.port, self.pid = serve_bg(self.repopath.dirpath())
-        # XXX caching is too global
-        py.path.svnurl._lsnorevcache._dict.clear()
-        request.addfinalizer(lambda: py.process.kill(self.pid))
-
-class TestSvnWCAuthFunctional:
-    def test_checkout_constructor_arg(self, setup):
-        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
-        wc.checkout(
-            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
-        assert wc.join('.svn').check()
-
-    def test_checkout_function_arg(self, setup):
-        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
-        wc.checkout(
-            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
-        assert wc.join('.svn').check()
-
-    def test_checkout_failing_non_interactive(self, setup):
-        auth = SvnAuth('johnny', 'bar', cache_auth=False,
-                               interactive=False)
-        wc = py.path.svnwc(setup.temppath, auth)
-        py.test.raises(Exception,
-           ("wc.checkout('svn://localhost:%(port)s/%(repopath)s')" %
-             setup.__dict__))
-
-    def test_log(self, setup):
-        wc = py.path.svnwc(setup.temppath, setup.auth)
-        wc.checkout(
-            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
-        foo = wc.ensure('foo.txt')
-        wc.commit('added foo.txt')
-        log = foo.log()
-        assert len(log) == 1
-        assert log[0].msg == 'added foo.txt'
-
-    def test_switch(self, setup):
-        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
-        svnurl = 'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename)
-        wc.checkout(svnurl)
-        wc.ensure('foo', dir=True).ensure('foo.txt').write('foo')
-        wc.commit('added foo dir with foo.txt file')
-        wc.ensure('bar', dir=True)
-        wc.commit('added bar dir')
-        bar = wc.join('bar')
-        bar.switch(svnurl + '/foo')
-        assert bar.join('foo.txt')
-
-    def test_update(self, setup):
-        wc1 = py.path.svnwc(setup.temppath.ensure('wc1', dir=True),
-                            auth=setup.auth)
-        wc2 = py.path.svnwc(setup.temppath.ensure('wc2', dir=True),
-                            auth=setup.auth)
-        wc1.checkout(
-            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
-        wc2.checkout(
-            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
-        wc1.ensure('foo', dir=True)
-        wc1.commit('added foo dir')
-        wc2.update()
-        assert wc2.join('foo').check()
-
-        auth = SvnAuth('unknown', 'unknown', interactive=False)
-        wc2.auth = auth
-        py.test.raises(Exception, 'wc2.update()')
-
-    def test_lock_unlock_status(self, setup):
-        port = setup.port
-        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
-        wc.checkout(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename,))
-        wc.ensure('foo', file=True)
-        wc.commit('added foo file')
-        foo = wc.join('foo')
-        foo.lock()
-        status = foo.status()
-        assert status.locked
-        foo.unlock()
-        status = foo.status()
-        assert not status.locked
-
-        auth = SvnAuth('unknown', 'unknown', interactive=False)
-        foo.auth = auth
-        py.test.raises(Exception, 'foo.lock()')
-        py.test.raises(Exception, 'foo.unlock()')
-
-    def test_diff(self, setup):
-        port = setup.port
-        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
-        wc.checkout(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename,))
-        wc.ensure('foo', file=True)
-        wc.commit('added foo file')
-        wc.update()
-        rev = int(wc.status().rev)
-        foo = wc.join('foo')
-        foo.write('bar')
-        diff = foo.diff()
-        assert '\n+bar\n' in diff
-        foo.commit('added some content')
-        diff = foo.diff()
-        assert not diff
-        diff = foo.diff(rev=rev)
-        assert '\n+bar\n' in diff
-
-        auth = SvnAuth('unknown', 'unknown', interactive=False)
-        foo.auth = auth
-        py.test.raises(Exception, 'foo.diff(rev=rev)')
-
-class TestSvnURLAuthFunctional:
-    def test_listdir(self, setup):
-        port = setup.port
-        u = py.path.svnurl(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
-            auth=setup.auth)
-        u.ensure('foo')
-        paths = u.listdir()
-        assert len(paths) == 1
-        assert paths[0].auth is setup.auth
-
-        auth = SvnAuth('foo', 'bar', interactive=False)
-        u = py.path.svnurl(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
-            auth=auth)
-        py.test.raises(Exception, 'u.listdir()')
-
-    def test_copy(self, setup):
-        port = setup.port
-        u = py.path.svnurl(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
-            auth=setup.auth)
-        foo = u.mkdir('foo')
-        assert foo.check()
-        bar = u.join('bar')
-        foo.copy(bar)
-        assert bar.check()
-        assert bar.auth is setup.auth
-
-        auth = SvnAuth('foo', 'bar', interactive=False)
-        u = py.path.svnurl(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
-            auth=auth)
-        foo = u.join('foo')
-        bar = u.join('bar')
-        py.test.raises(Exception, 'foo.copy(bar)')
-
-    def test_write_read(self, setup):
-        port = setup.port
-        u = py.path.svnurl(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
-            auth=setup.auth)
-        foo = u.ensure('foo')
-        fp = foo.open()
-        try:
-            data = fp.read()
-        finally:
-            fp.close()
-        assert data == ''
-
-        auth = SvnAuth('foo', 'bar', interactive=False)
-        u = py.path.svnurl(
-            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
-            auth=auth)
-        foo = u.join('foo')
-        py.test.raises(Exception, 'foo.open()')
-
-    # XXX rinse, repeat... :|
diff --git a/tools/py/testing/path/test_svnwc.py b/tools/py/testing/path/test_svnwc.py
deleted file mode 100644
index 9e6b524..0000000
--- a/tools/py/testing/path/test_svnwc.py
+++ /dev/null
@@ -1,549 +0,0 @@
-import py
-import os, sys
-import pytest
-from py._path.svnwc import InfoSvnWCCommand, XMLWCStatus, parse_wcinfotime
-from py._path import svnwc as svncommon
-from svntestbase import CommonSvnTests
-
-def test_make_repo(path1, tmpdir):
-    repo = tmpdir.join("repo")
-    py.process.cmdexec('svnadmin create %s' % repo)
-    if sys.platform == 'win32':
-        repo = '/' + str(repo).replace('\\', '/')
-    repo = py.path.svnurl("file://%s" % repo)
-    wc = py.path.svnwc(tmpdir.join("wc"))
-    wc.checkout(repo)
-    assert wc.rev == 0
-    assert len(wc.listdir()) == 0
-    p = wc.join("a_file")
-    p.write("test file")
-    p.add()
-    rev = wc.commit("some test")
-    assert p.info().rev == 1
-    assert rev == 1
-    rev = wc.commit()
-    assert rev is None
-
-def pytest_funcarg__path1(request):
-    repo, repourl, wc = request.getfuncargvalue("repowc1")
-    return wc
-
-class TestWCSvnCommandPath(CommonSvnTests):
-    def test_status_attributes_simple(self, path1):
-        def assert_nochange(p):
-            s = p.status()
-            assert not s.modified
-            assert not s.prop_modified
-            assert not s.added
-            assert not s.deleted
-            assert not s.replaced
-
-        dpath = path1.join('sampledir')
-        assert_nochange(path1.join('sampledir'))
-        assert_nochange(path1.join('samplefile'))
-
-    def test_status_added(self, path1):
-        nf = path1.join('newfile')
-        nf.write('hello')
-        nf.add()
-        try:
-            s = nf.status()
-            assert s.added
-            assert not s.modified
-            assert not s.prop_modified
-            assert not s.replaced
-        finally:
-            nf.revert()
-
-    def test_status_change(self, path1):
-        nf = path1.join('samplefile')
-        try:
-            nf.write(nf.read() + 'change')
-            s = nf.status()
-            assert not s.added
-            assert s.modified
-            assert not s.prop_modified
-            assert not s.replaced
-        finally:
-            nf.revert()
-
-    def test_status_added_ondirectory(self, path1):
-        sampledir = path1.join('sampledir')
-        try:
-            t2 = sampledir.mkdir('t2')
-            t1 = t2.join('t1')
-            t1.write('test')
-            t1.add()
-            s = sampledir.status(rec=1)
-            # Comparing just the file names, because paths are unpredictable
-            # on Windows. (long vs. 8.3 paths)
-            assert t1.basename in [item.basename for item in s.added]
-            assert t2.basename in [item.basename for item in s.added]
-        finally:
-            t2.revert(rec=1)
-            t2.localpath.remove(rec=1)
-
-    def test_status_unknown(self, path1):
-        t1 = path1.join('un1')
-        try:
-            t1.write('test')
-            s = path1.status()
-            # Comparing just the file names, because paths are unpredictable
-            # on Windows. (long vs. 8.3 paths)
-            assert t1.basename in [item.basename for item in s.unknown]
-        finally:
-            t1.localpath.remove()
-
-    def test_status_unchanged(self, path1):
-        r = path1
-        s = path1.status(rec=1)
-        # Comparing just the file names, because paths are unpredictable
-        # on Windows. (long vs. 8.3 paths)
-        assert r.join('samplefile').basename in [item.basename
-                                                    for item in s.unchanged]
-        assert r.join('sampledir').basename in [item.basename
-                                                    for item in s.unchanged]
-        assert r.join('sampledir/otherfile').basename in [item.basename
-                                                    for item in s.unchanged]
-
-    @pytest.mark.xfail(reason="svn-1.7 has buggy 'status --xml' output")
-    def test_status_update(self, path1):
-        r = path1
-        try:
-            r.update(rev=1)
-            s = r.status(updates=1, rec=1)
-            # Comparing just the file names, because paths are unpredictable
-            # on Windows. (long vs. 8.3 paths)
-            py.std.pprint.pprint(s.allpath())
-            assert r.join('anotherfile').basename in [item.basename for
-                                                    item in s.update_available]
-            #assert len(s.update_available) == 1
-        finally:
-            r.update()
-
-    def test_status_replaced(self, path1):
-        p = path1.join("samplefile")
-        p.remove()
-        p.ensure(dir=0)
-        try:
-            s = path1.status()
-            assert p.basename in [item.basename for item in s.replaced]
-        finally:
-            path1.revert(rec=1)
-
-    def test_status_ignored(self, path1):
-        try:
-            d = path1.join('sampledir')
-            p = py.path.local(d).join('ignoredfile')
-            p.ensure(file=True)
-            s = d.status()
-            assert [x.basename for x in s.unknown] == ['ignoredfile']
-            assert [x.basename for x in s.ignored] == []
-            d.propset('svn:ignore', 'ignoredfile')
-            s = d.status()
-            assert [x.basename for x in s.unknown] == []
-            assert [x.basename for x in s.ignored] == ['ignoredfile']
-        finally:
-            path1.revert(rec=1)
-
-    def test_status_conflict(self, path1, tmpdir):
-        wc = path1
-        wccopy = py.path.svnwc(tmpdir.join("conflict_copy"))
-        wccopy.checkout(wc.url)
-        p = wc.ensure('conflictsamplefile', file=1)
-        p.write('foo')
-        wc.commit('added conflictsamplefile')
-        wccopy.update()
-        assert wccopy.join('conflictsamplefile').check()
-        p.write('bar')
-        wc.commit('wrote some data')
-        wccopy.join('conflictsamplefile').write('baz')
-        wccopy.update(interactive=False)
-        s = wccopy.status()
-        assert [x.basename for x in s.conflict] == ['conflictsamplefile']
-
-    def test_status_external(self, path1, repowc2):
-        otherrepo, otherrepourl, otherwc = repowc2
-        d = path1.ensure('sampledir', dir=1)
-        try:
-            d.update()
-            d.propset('svn:externals', 'otherwc %s' % (otherwc.url,))
-            d.update()
-            s = d.status()
-            assert [x.basename for x in s.external] == ['otherwc']
-            assert 'otherwc' not in [x.basename for x in s.unchanged]
-            s = d.status(rec=1)
-            assert [x.basename for x in s.external] == ['otherwc']
-            assert 'otherwc' in [x.basename for x in s.unchanged]
-        finally:
-            path1.revert(rec=1)
-
-    def test_status_deleted(self, path1):
-        d = path1.ensure('sampledir', dir=1)
-        d.remove()
-        d.ensure(dir=1)
-        path1.commit()
-        d.ensure('deletefile', dir=0)
-        d.commit()
-        s = d.status()
-        assert 'deletefile' in [x.basename for x in s.unchanged]
-        assert not s.deleted
-        p = d.join('deletefile')
-        p.remove()
-        s = d.status()
-        assert 'deletefile' not in s.unchanged
-        assert [x.basename for x in s.deleted] == ['deletefile']
-
-    def test_status_noauthor(self, path1):
-        # testing for XML without author - this used to raise an exception
-        xml = '''\
-        <entry path="/tmp/pytest-23/wc">
-        <wc-status item="normal" props="none" revision="0">
-        <commit revision="0">
-        <date>2008-08-19T16:50:53.400198Z</date>
-        </commit>
-        </wc-status>
-        </entry>
-        '''
-        XMLWCStatus.fromstring(xml, path1)
-
-    def test_status_wrong_xml(self, path1):
-        # testing for XML without author - this used to raise an exception
-        xml = '<entry path="/home/jean/zope/venv/projectdb/parts/development-products/DataGridField">\n<wc-status item="incomplete" props="none" revision="784">\n</wc-status>\n</entry>'
-        st = XMLWCStatus.fromstring(xml, path1)
-        assert len(st.incomplete) == 1
-
-    def test_diff(self, path1):
-        p = path1 / 'anotherfile'
-        out = p.diff(rev=2)
-        assert out.find('hello') != -1
-
-    def test_blame(self, path1):
-        p = path1.join('samplepickle')
-        lines = p.blame()
-        assert sum([l[0] for l in lines]) == len(lines)
-        for l1, l2 in zip(p.readlines(), [l[2] for l in lines]):
-            assert l1 == l2
-        assert [l[1] for l in lines] == ['hpk'] * len(lines)
-        p = path1.join('samplefile')
-        lines = p.blame()
-        assert sum([l[0] for l in lines]) == len(lines)
-        for l1, l2 in zip(p.readlines(), [l[2] for l in lines]):
-            assert l1 == l2
-        assert [l[1] for l in lines] == ['hpk'] * len(lines)
-
-    def test_join_abs(self, path1):
-        s = str(path1.localpath)
-        n = path1.join(s, abs=1)
-        assert path1 == n
-
-    def test_join_abs2(self, path1):
-        assert path1.join('samplefile', abs=1) == path1.join('samplefile')
-
-    def test_str_gives_localpath(self, path1):
-        assert str(path1) == str(path1.localpath)
-
-    def test_versioned(self, path1):
-        assert path1.check(versioned=1)
-        # TODO: Why does my copy of svn think .svn is versioned?
-        #assert path1.join('.svn').check(versioned=0)
-        assert path1.join('samplefile').check(versioned=1)
-        assert not path1.join('notexisting').check(versioned=1)
-        notexisting = path1.join('hello').localpath
-        try:
-            notexisting.write("")
-            assert path1.join('hello').check(versioned=0)
-        finally:
-            notexisting.remove()
-
-    def test_listdir_versioned(self, path1):
-        assert path1.check(versioned=1)
-        p = path1.localpath.ensure("not_a_versioned_file")
-        l = [x.localpath
-                for x in path1.listdir(lambda x: x.check(versioned=True))]
-        assert p not in l
-
-    def test_nonversioned_remove(self, path1):
-        assert path1.check(versioned=1)
-        somefile = path1.join('nonversioned/somefile')
-        nonwc = py.path.local(somefile)
-        nonwc.ensure()
-        assert somefile.check()
-        assert not somefile.check(versioned=True)
-        somefile.remove() # this used to fail because it tried to 'svn rm'
-
-    def test_properties(self, path1):
-        try:
-            path1.propset('gaga', 'this')
-            assert path1.propget('gaga') == 'this'
-            # Comparing just the file names, because paths are unpredictable
-            # on Windows. (long vs. 8.3 paths)
-            assert path1.basename in [item.basename for item in
-                                        path1.status().prop_modified]
-            assert 'gaga' in path1.proplist()
-            assert path1.proplist()['gaga'] == 'this'
-
-        finally:
-            path1.propdel('gaga')
-
-    def test_proplist_recursive(self, path1):
-        s = path1.join('samplefile')
-        s.propset('gugu', 'that')
-        try:
-            p = path1.proplist(rec=1)
-            # Comparing just the file names, because paths are unpredictable
-            # on Windows. (long vs. 8.3 paths)
-            assert (path1 / 'samplefile').basename in [item.basename
-                                                                for item in p]
-        finally:
-            s.propdel('gugu')
-
-    def test_long_properties(self, path1):
-        value = """
-        vadm:posix : root root 0100755
-        Properties on 'chroot/dns/var/bind/db.net.xots':
-                """
-        try:
-            path1.propset('gaga', value)
-            backvalue = path1.propget('gaga')
-            assert backvalue == value
-            #assert len(backvalue.split('\n')) == 1
-        finally:
-            path1.propdel('gaga')
-
-
-    def test_ensure(self, path1):
-        newpath = path1.ensure('a', 'b', 'c')
-        try:
-            assert newpath.check(exists=1, versioned=1)
-            newpath.write("hello")
-            newpath.ensure()
-            assert newpath.read() == "hello"
-        finally:
-            path1.join('a').remove(force=1)
-
-    def test_not_versioned(self, path1):
-        p = path1.localpath.mkdir('whatever')
-        f = path1.localpath.ensure('testcreatedfile')
-        try:
-            assert path1.join('whatever').check(versioned=0)
-            assert path1.join('testcreatedfile').check(versioned=0)
-            assert not path1.join('testcreatedfile').check(versioned=1)
-        finally:
-            p.remove(rec=1)
-            f.remove()
-
-    def test_lock_unlock(self, path1):
-        root = path1
-        somefile = root.join('somefile')
-        somefile.ensure(file=True)
-        # not yet added to repo
-        py.test.raises(Exception, 'somefile.lock()')
-        somefile.write('foo')
-        somefile.commit('test')
-        assert somefile.check(versioned=True)
-        somefile.lock()
-        try:
-            locked = root.status().locked
-            assert len(locked) == 1
-            assert locked[0].basename == somefile.basename
-            assert locked[0].dirpath().basename == somefile.dirpath().basename
-            #assert somefile.locked()
-            py.test.raises(Exception, 'somefile.lock()')
-        finally:
-            somefile.unlock()
-        #assert not somefile.locked()
-        locked = root.status().locked
-        assert locked == []
-        py.test.raises(Exception, 'somefile,unlock()')
-        somefile.remove()
-
-    def test_commit_nonrecursive(self, path1):
-        somedir = path1.join('sampledir')
-        somedir.mkdir("subsubdir")
-        somedir.propset('foo', 'bar')
-        status = somedir.status()
-        assert len(status.prop_modified) == 1
-        assert len(status.added) == 1
-
-        somedir.commit('non-recursive commit', rec=0)
-        status = somedir.status()
-        assert len(status.prop_modified) == 0
-        assert len(status.added) == 1
-
-        somedir.commit('recursive commit')
-        status = somedir.status()
-        assert len(status.prop_modified) == 0
-        assert len(status.added) == 0
-
-    def test_commit_return_value(self, path1):
-        testfile = path1.join('test.txt').ensure(file=True)
-        testfile.write('test')
-        rev = path1.commit('testing')
-        assert type(rev) == int
-
-        anotherfile = path1.join('another.txt').ensure(file=True)
-        anotherfile.write('test')
-        rev2 = path1.commit('testing more')
-        assert type(rev2) == int
-        assert rev2 == rev + 1
-
-    #def test_log(self, path1):
-    #   l = path1.log()
-    #   assert len(l) == 3  # might need to be upped if more tests are added
-
-class XTestWCSvnCommandPathSpecial:
-
-    rooturl = 'http://codespeak.net/svn/py.path/trunk/dist/py.path/test/data'
-    #def test_update_none_rev(self, path1):
-    #    path = tmpdir.join('checkouttest')
-    #    wcpath = newpath(xsvnwc=str(path), url=path1url)
-    #    try:
-    #        wcpath.checkout(rev=2100)
-    #        wcpath.update()
-    #        assert wcpath.info().rev > 2100
-    #    finally:
-    #        wcpath.localpath.remove(rec=1)
-
-def test_parse_wcinfotime():
-    assert (parse_wcinfotime('2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)') ==
-            1149021926)
-    assert (parse_wcinfotime('2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)') ==
-            1067287394)
-
-class TestInfoSvnWCCommand:
-
-    def test_svn_1_2(self, path1):
-        output = """
-        Path: test_svnwc.py
-        Name: test_svnwc.py
-        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
-        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
-        Revision: 28137
-        Node Kind: file
-        Schedule: normal
-        Last Changed Author: jan
-        Last Changed Rev: 27939
-        Last Changed Date: 2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)
-        Text Last Updated: 2006-06-01 00:42:53 +0200 (Thu, 01 Jun 2006)
-        Properties Last Updated: 2006-05-23 11:54:59 +0200 (Tue, 23 May 2006)
-        Checksum: 357e44880e5d80157cc5fbc3ce9822e3
-        """
-        path = py.path.local(__file__).dirpath().chdir()
-        try:
-            info = InfoSvnWCCommand(output)
-        finally:
-            path.chdir()
-        assert info.last_author == 'jan'
-        assert info.kind == 'file'
-        assert info.mtime == 1149021926.0
-        assert info.url == 'http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py'
-        assert info.time == 1149021926000000.0
-        assert info.rev == 28137
-
-
-    def test_svn_1_3(self, path1):
-        output = """
-        Path: test_svnwc.py
-        Name: test_svnwc.py
-        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
-        Repository Root: http://codespeak.net/svn
-        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
-        Revision: 28124
-        Node Kind: file
-        Schedule: normal
-        Last Changed Author: jan
-        Last Changed Rev: 27939
-        Last Changed Date: 2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)
-        Text Last Updated: 2006-06-02 23:46:11 +0200 (Fri, 02 Jun 2006)
-        Properties Last Updated: 2006-06-02 23:45:28 +0200 (Fri, 02 Jun 2006)
-        Checksum: 357e44880e5d80157cc5fbc3ce9822e3
-        """
-        path = py.path.local(__file__).dirpath().chdir()
-        try:
-            info = InfoSvnWCCommand(output)
-        finally:
-            path.chdir()
-        assert info.last_author == 'jan'
-        assert info.kind == 'file'
-        assert info.mtime == 1149021926.0
-        assert info.url == 'http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py'
-        assert info.rev == 28124
-        assert info.time == 1149021926000000.0
-
-
-def test_characters_at():
-    py.test.raises(ValueError, "py.path.svnwc('/tmp/@@@:')")
-
-def test_characters_tilde():
-    py.path.svnwc('/tmp/test~')
-
-
-class TestRepo:
-    def test_trailing_slash_is_stripped(self, path1):
-        # XXX we need to test more normalizing properties
-        url = path1.join("/")
-        assert path1 == url
-
-    #def test_different_revs_compare_unequal(self, path1):
-    #    newpath = path1.new(rev=1199)
-    #    assert newpath != path1
-
-    def test_exists_svn_root(self, path1):
-        assert path1.check()
-
-    #def test_not_exists_rev(self, path1):
-    #    url = path1.__class__(path1url, rev=500)
-    #    assert url.check(exists=0)
-
-    #def test_nonexisting_listdir_rev(self, path1):
-    #    url = path1.__class__(path1url, rev=500)
-    #    raises(py.error.ENOENT, url.listdir)
-
-    #def test_newrev(self, path1):
-    #    url = path1.new(rev=None)
-    #    assert url.rev == None
-    #    assert url.strpath == path1.strpath
-    #    url = path1.new(rev=10)
-    #    assert url.rev == 10
-
-    #def test_info_rev(self, path1):
-    #    url = path1.__class__(path1url, rev=1155)
-    #    url = url.join("samplefile")
-    #    res = url.info()
-    #    assert res.size > len("samplefile") and res.created_rev == 1155
-
-    # the following tests are easier if we have a path class
-    def test_repocache_simple(self, path1):
-        repocache = svncommon.RepoCache()
-        repocache.put(path1.strpath, 42)
-        url, rev = repocache.get(path1.join('test').strpath)
-        assert rev == 42
-        assert url == path1.strpath
-
-    def test_repocache_notimeout(self, path1):
-        repocache = svncommon.RepoCache()
-        repocache.timeout = 0
-        repocache.put(path1.strpath, path1.rev)
-        url, rev = repocache.get(path1.strpath)
-        assert rev == -1
-        assert url == path1.strpath
-
-    def test_repocache_outdated(self, path1):
-        repocache = svncommon.RepoCache()
-        repocache.put(path1.strpath, 42, timestamp=0)
-        url, rev = repocache.get(path1.join('test').strpath)
-        assert rev == -1
-        assert url == path1.strpath
-
-    def _test_getreporev(self):
-        """ this test runs so slow it's usually disabled """
-        old = svncommon.repositories.repos
-        try:
-            _repocache.clear()
-            root = path1.new(rev=-1)
-            url, rev = cache.repocache.get(root.strpath)
-            assert rev>=0
-            assert url == svnrepourl
-        finally:
-            repositories.repos = old
diff --git a/tools/py/testing/process/test_cmdexec.py b/tools/py/testing/process/test_cmdexec.py
deleted file mode 100644
index b539e0a..0000000
--- a/tools/py/testing/process/test_cmdexec.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import py
-from py.process import cmdexec
-
-def exvalue():
-    return py.std.sys.exc_info()[1]
-
-class Test_exec_cmd:
-    def test_simple(self):
-        out = cmdexec('echo hallo')
-        assert out.strip() == 'hallo'
-        assert py.builtin._istext(out)
-
-    def test_simple_newline(self):
-        import sys
-        out = cmdexec(r"""%s -c "print ('hello')" """ % sys.executable)
-        assert out == 'hello\n'
-        assert py.builtin._istext(out)
-
-    def test_simple_error(self):
-        py.test.raises (cmdexec.Error, cmdexec, 'exit 1')
-
-    def test_simple_error_exact_status(self):
-        try:
-            cmdexec('exit 1')
-        except cmdexec.Error:
-            e = exvalue()
-            assert e.status == 1
-            assert py.builtin._istext(e.out)
-            assert py.builtin._istext(e.err)
-
-    def test_err(self):
-        try:
-            cmdexec('echoqweqwe123 hallo')
-            raise AssertionError("command succeeded but shouldn't")
-        except cmdexec.Error:
-            e = exvalue()
-            assert hasattr(e, 'err')
-            assert hasattr(e, 'out')
-            assert e.err or e.out
diff --git a/tools/py/testing/process/test_forkedfunc.py b/tools/py/testing/process/test_forkedfunc.py
deleted file mode 100644
index d4f9f98..0000000
--- a/tools/py/testing/process/test_forkedfunc.py
+++ /dev/null
@@ -1,177 +0,0 @@
-import pytest
-import py, sys, os
-
-pytestmark = py.test.mark.skipif("not hasattr(os, 'fork')")
-
-
-def test_waitfinish_removes_tempdir():
-    ff = py.process.ForkedFunc(boxf1)
-    assert ff.tempdir.check()
-    ff.waitfinish()
-    assert not ff.tempdir.check()
-
-def test_tempdir_gets_gc_collected(monkeypatch):
-    monkeypatch.setattr(os, 'fork', lambda: os.getpid())
-    ff = py.process.ForkedFunc(boxf1)
-    assert ff.tempdir.check()
-    ff.__del__()
-    assert not ff.tempdir.check()
-
-def test_basic_forkedfunc():
-    result = py.process.ForkedFunc(boxf1).waitfinish()
-    assert result.out == "some out\n"
-    assert result.err == "some err\n"
-    assert result.exitstatus == 0
-    assert result.signal == 0
-    assert result.retval == 1
-
-def test_exitstatus():
-    def func():
-        os._exit(4)
-    result = py.process.ForkedFunc(func).waitfinish()
-    assert result.exitstatus == 4
-    assert result.signal == 0
-    assert not result.out
-    assert not result.err
-
-def test_execption_in_func():
-    def fun():
-        raise ValueError(42)
-    ff = py.process.ForkedFunc(fun)
-    result = ff.waitfinish()
-    assert result.exitstatus == ff.EXITSTATUS_EXCEPTION
-    assert result.err.find("ValueError: 42") != -1
-    assert result.signal == 0
-    assert not result.retval
-
-def test_forkedfunc_on_fds():
-    result = py.process.ForkedFunc(boxf2).waitfinish()
-    assert result.out == "someout"
-    assert result.err == "someerr"
-    assert result.exitstatus == 0
-    assert result.signal == 0
-    assert result.retval == 2
-
-def test_forkedfunc_on_fds_output():
-    result = py.process.ForkedFunc(boxf3).waitfinish()
-    assert result.signal == 11
-    assert result.out == "s"
-
-
-def test_forkedfunc_on_stdout():
-    def boxf3():
-        import sys
-        sys.stdout.write("hello\n")
-        os.kill(os.getpid(), 11)
-    result = py.process.ForkedFunc(boxf3).waitfinish()
-    assert result.signal == 11
-    assert result.out == "hello\n"
-
-def test_forkedfunc_signal():
-    result = py.process.ForkedFunc(boxseg).waitfinish()
-    assert result.retval is None
-    if sys.version_info < (2,4):
-        py.test.skip("signal detection does not work with python prior 2.4")
-    assert result.signal == 11
-
-def test_forkedfunc_huge_data():
-    result = py.process.ForkedFunc(boxhuge).waitfinish()
-    assert result.out
-    assert result.exitstatus == 0
-    assert result.signal == 0
-    assert result.retval == 3
-
-def test_box_seq():
-    # we run many boxes with huge data, just one after another
-    for i in range(50):
-        result = py.process.ForkedFunc(boxhuge).waitfinish()
-        assert result.out
-        assert result.exitstatus == 0
-        assert result.signal == 0
-        assert result.retval == 3
-
-def test_box_in_a_box():
-    def boxfun():
-        result = py.process.ForkedFunc(boxf2).waitfinish()
-        print (result.out)
-        sys.stderr.write(result.err + "\n")
-        return result.retval
-
-    result = py.process.ForkedFunc(boxfun).waitfinish()
-    assert result.out == "someout\n"
-    assert result.err == "someerr\n"
-    assert result.exitstatus == 0
-    assert result.signal == 0
-    assert result.retval == 2
-
-def test_kill_func_forked():
-    class A:
-        pass
-    info = A()
-    import time
-
-    def box_fun():
-        time.sleep(10) # we don't want to last forever here
-
-    ff = py.process.ForkedFunc(box_fun)
-    os.kill(ff.pid, 15)
-    result = ff.waitfinish()
-    if py.std.sys.version_info < (2,4):
-        py.test.skip("signal detection does not work with python prior 2.4")
-    assert result.signal == 15
-
-
-def test_hooks(monkeypatch):
-    def _boxed():
-        return 1
-
-    def _on_start():
-        sys.stdout.write("some out\n")
-        sys.stdout.flush()
-
-    def _on_exit():
-        sys.stderr.write("some err\n")
-        sys.stderr.flush()
-
-    result = py.process.ForkedFunc(_boxed, child_on_start=_on_start,
-                                   child_on_exit=_on_exit).waitfinish()
-    assert result.out == "some out\n"
-    assert result.err == "some err\n"
-    assert result.exitstatus == 0
-    assert result.signal == 0
-    assert result.retval == 1
-
-
-# ======================================================================
-# examples
-# ======================================================================
-#
-
-def boxf1():
-    sys.stdout.write("some out\n")
-    sys.stderr.write("some err\n")
-    return 1
-
-def boxf2():
-    os.write(1, "someout".encode('ascii'))
-    os.write(2, "someerr".encode('ascii'))
-    return 2
-
-def boxf3():
-    os.write(1, "s".encode('ascii'))
-    os.kill(os.getpid(), 11)
-
-def boxseg():
-    os.kill(os.getpid(), 11)
-
-def boxhuge():
-    s = " ".encode('ascii')
-    os.write(1, s * 10000)
-    os.write(2, s * 10000)
-    os.write(1, s * 10000)
-
-    os.write(1, s * 10000)
-    os.write(2, s * 10000)
-    os.write(2, s * 10000)
-    os.write(1, s * 10000)
-    return 3
diff --git a/tools/py/testing/process/test_killproc.py b/tools/py/testing/process/test_killproc.py
deleted file mode 100644
index 57088e1..0000000
--- a/tools/py/testing/process/test_killproc.py
+++ /dev/null
@@ -1,16 +0,0 @@
-
-import py, sys
-
-@py.test.mark.skipif("sys.platform.startswith('java')")
-def test_kill(tmpdir):
-    subprocess = py.test.importorskip("subprocess")
-    t = tmpdir.join("t.py")
-    t.write("import time ; time.sleep(100)")
-    proc = py.std.subprocess.Popen([sys.executable, str(t)])
-    assert proc.poll() is None # no return value yet
-    py.process.kill(proc.pid)
-    ret = proc.wait()
-    if sys.platform == "win32" and ret == 0:
-        py.test.skip("XXX on win32, subprocess.Popen().wait() on a killed "
-                     "process does not yield return value != 0")
-    assert ret != 0
diff --git a/tools/py/testing/root/test_error.py b/tools/py/testing/root/test_error.py
deleted file mode 100644
index a34e006..0000000
--- a/tools/py/testing/root/test_error.py
+++ /dev/null
@@ -1,37 +0,0 @@
-
-import py
-
-import errno
-
-def test_error_classes():
-    for name in errno.errorcode.values():
-        x = getattr(py.error, name)
-        assert issubclass(x, py.error.Error)
-        assert issubclass(x, EnvironmentError)
-
-def test_picklability_issue1():
-    e1 = py.error.ENOENT()
-    s = py.std.pickle.dumps(e1)
-    e2 = py.std.pickle.loads(s)
-    assert isinstance(e2, py.error.ENOENT)
-
-def test_unknown_error():
-    num = 3999
-    cls = py.error._geterrnoclass(num)
-    assert cls.__name__ == 'UnknownErrno%d' % (num,)
-    assert issubclass(cls, py.error.Error)
-    assert issubclass(cls, EnvironmentError)
-    cls2 = py.error._geterrnoclass(num)
-    assert cls is cls2
-
-def test_error_conversion_ENOTDIR(testdir):
-    p = testdir.makepyfile("")
-    excinfo = py.test.raises(py.error.Error, py.error.checked_call, p.listdir)
-    assert isinstance(excinfo.value, EnvironmentError)
-    assert isinstance(excinfo.value, py.error.Error)
-    assert "ENOTDIR" in repr(excinfo.value)
-
-
-def test_checked_call_supports_kwargs(tmpdir):
-    import tempfile
-    py.error.checked_call(tempfile.mkdtemp, dir=str(tmpdir))
diff --git a/tools/py/testing/root/test_py_imports.py b/tools/py/testing/root/test_py_imports.py
deleted file mode 100644
index 5f5954e..0000000
--- a/tools/py/testing/root/test_py_imports.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import py
-import types
-import sys
-
-def checksubpackage(name):
-    obj = getattr(py, name)
-    if hasattr(obj, '__map__'): # isinstance(obj, Module):
-        keys = dir(obj)
-        assert len(keys) > 0
-        print (obj.__map__)
-        for name in list(obj.__map__):
-            assert hasattr(obj, name), (obj, name)
-
-def test_dir():
-    for name in dir(py):
-        if not name.startswith('_'):
-            yield checksubpackage, name
-
-def test_virtual_module_identity():
-    from py import path as path1
-    from py import path as path2
-    assert path1 is path2
-    from py.path import local as local1
-    from py.path import local as local2
-    assert local1 is local2
-
-def test_importall():
-    base = py._pydir
-    nodirs = [
-    ]
-    if sys.version_info >= (3,0):
-        nodirs.append(base.join('_code', '_assertionold.py'))
-    else:
-        nodirs.append(base.join('_code', '_assertionnew.py'))
-
-    def recurse(p):
-        return p.check(dotfile=0) and p.basename != "attic"
-
-    for p in base.visit('*.py', recurse):
-        if p.basename == '__init__.py':
-            continue
-        relpath = p.new(ext='').relto(base)
-        if base.sep in relpath: # not py/*.py itself
-            for x in nodirs:
-                if p == x or p.relto(x):
-                    break
-            else:
-                relpath = relpath.replace(base.sep, '.')
-                modpath = 'py.%s' % relpath
-                try:
-                    check_import(modpath)
-                except py.test.skip.Exception:
-                    pass
-
-def check_import(modpath):
-    py.builtin.print_("checking import", modpath)
-    assert __import__(modpath)
-
-def test_all_resolves():
-    seen = py.builtin.set([py])
-    lastlength = None
-    while len(seen) != lastlength:
-        lastlength = len(seen)
-        for item in py.builtin.frozenset(seen):
-            for value in item.__dict__.values():
-                if isinstance(value, type(py.test)):
-                    seen.add(value)
-
diff --git a/tools/py/testing/root/test_xmlgen.py b/tools/py/testing/root/test_xmlgen.py
deleted file mode 100644
index 704d149..0000000
--- a/tools/py/testing/root/test_xmlgen.py
+++ /dev/null
@@ -1,145 +0,0 @@
-
-import py
-from py._xmlgen import unicode, html, raw
-
-class ns(py.xml.Namespace):
-    pass
-
-def test_escape():
-    uvalue = py.builtin._totext('\xc4\x85\xc4\x87\n\xe2\x82\xac\n', 'utf-8')
-    class A:
-        def __unicode__(self):
-            return uvalue
-        def __str__(self):
-            x = self.__unicode__()
-            if py.std.sys.version_info[0] < 3:
-                return x.encode('utf-8')
-            return x
-    y = py.xml.escape(uvalue)
-    assert y == uvalue
-    x = py.xml.escape(A())
-    assert x == uvalue
-    if py.std.sys.version_info[0] < 3:
-        assert isinstance(x, unicode)
-        assert isinstance(y, unicode)
-        y = py.xml.escape(uvalue.encode('utf-8'))
-        assert y == uvalue
-
-
-def test_tag_with_text():
-    x = ns.hello("world")
-    u = unicode(x)
-    assert u == "<hello>world</hello>"
-
-def test_class_identity():
-    assert ns.hello is ns.hello
-
-def test_tag_with_text_and_attributes():
-    x = ns.some(name="hello", value="world")
-    assert x.attr.name == 'hello'
-    assert x.attr.value == 'world'
-    u = unicode(x)
-    assert u == '<some name="hello" value="world"/>'
-
-def test_tag_with_subclassed_attr_simple():
-    class my(ns.hello):
-        class Attr(ns.hello.Attr):
-            hello="world"
-    x = my()
-    assert x.attr.hello == 'world'
-    assert unicode(x) == '<my hello="world"/>'
-
-def test_tag_with_raw_attr():
-    x = html.object(data=raw('&'))
-    assert unicode(x) == '<object data="&"></object>'
-
-def test_tag_nested():
-    x = ns.hello(ns.world())
-    unicode(x) # triggers parentifying
-    assert x[0].parent is x
-    u = unicode(x)
-    assert u == '<hello><world/></hello>'
-
-def test_list_nested():
-    x = ns.hello([ns.world()]) #pass in a list here
-    u = unicode(x)
-    assert u == '<hello><world/></hello>'
-
-def test_tag_xmlname():
-    class my(ns.hello):
-        xmlname = 'world'
-    u = unicode(my())
-    assert u == '<world/>'
-
-def test_tag_with_text_entity():
-    x = ns.hello('world & rest')
-    u = unicode(x)
-    assert u == "<hello>world &amp; rest</hello>"
-
-def test_tag_with_text_and_attributes_entity():
-    x = ns.some(name="hello & world")
-    assert x.attr.name == "hello & world"
-    u = unicode(x)
-    assert u == '<some name="hello &amp; world"/>'
-
-def test_raw():
-    x = ns.some(py.xml.raw("<p>literal</p>"))
-    u = unicode(x)
-    assert u == "<some><p>literal</p></some>"
-
-
-def test_html_name_stickyness():
-    class my(html.p):
-        pass
-    x = my("hello")
-    assert unicode(x) == '<p>hello</p>'
-
-def test_stylenames():
-    class my:
-        class body(html.body):
-            style = html.Style(font_size = "12pt")
-    u = unicode(my.body())
-    assert u == '<body style="font-size: 12pt"></body>'
-
-def test_class_None():
-    t = html.body(class_=None)
-    u = unicode(t)
-    assert u == '<body></body>'
-
-def test_alternating_style():
-    alternating = (
-        html.Style(background="white"),
-        html.Style(background="grey"),
-    )
-    class my(html):
-        class li(html.li):
-            def style(self):
-                i = self.parent.index(self)
-                return alternating[i%2]
-            style = property(style)
-
-    x = my.ul(
-            my.li("hello"),
-            my.li("world"),
-            my.li("42"))
-    u = unicode(x)
-    assert u == ('<ul><li style="background: white">hello</li>'
-                     '<li style="background: grey">world</li>'
-                     '<li style="background: white">42</li>'
-                 '</ul>')
-
-def test_singleton():
-    h = html.head(html.link(href="foo"))
-    assert unicode(h) == '<head><link href="foo"/></head>'
-
-    h = html.head(html.script(src="foo"))
-    assert unicode(h) == '<head><script src="foo"></script></head>'
-
-def test_inline():
-    h = html.div(html.span('foo'), html.span('bar'))
-    assert (h.unicode(indent=2) ==
-            '<div><span>foo</span><span>bar</span></div>')
-
-def test_object_tags():
-    o = html.object(html.object())
-    assert o.unicode(indent=0) == '<object><object></object></object>'
diff --git a/tools/py/testing/test_iniconfig.py b/tools/py/testing/test_iniconfig.py
deleted file mode 100644
index 9a7f72c..0000000
--- a/tools/py/testing/test_iniconfig.py
+++ /dev/null
@@ -1,299 +0,0 @@
-import py
-import pytest
-from py._iniconfig import IniConfig, ParseError, __all__ as ALL
-from py._iniconfig import iscommentline
-from textwrap import dedent
-
-def pytest_generate_tests(metafunc):
-    if 'input' in metafunc.funcargnames:
-        for name, (input, expected) in check_tokens.items():
-            metafunc.addcall(id=name, funcargs={
-                'input': input,
-                'expected': expected,
-            })
-    elif hasattr(metafunc.function, 'multi'):
-        kwargs = metafunc.function.multi.kwargs
-        names, values = zip(*kwargs.items())
-        values = cartesian_product(*values)
-        for p in values:
-            metafunc.addcall(funcargs=dict(zip(names, p)))
-
-def cartesian_product(L,*lists):
-    # copied from http://bit.ly/cyIXjn
-    if not lists:
-        for x in L:
-            yield (x,)
-    else:
-        for x in L:
-            for y in cartesian_product(lists[0],*lists[1:]):
-                yield (x,)+y
-
-check_tokens = {
-    'section': (
-        '[section]',
-        [(0, 'section', None, None)]
-    ),
-    'value': (
-        'value = 1',
-        [(0, None, 'value', '1')]
-    ),
-    'value in section': (
-        '[section]\nvalue=1',
-        [(0, 'section', None, None), (1, 'section', 'value', '1')]
-    ),
-    'value with continuation': (
-        'names =\n Alice\n Bob',
-        [(0, None, 'names', 'Alice\nBob')]
-    ),
-    'value with aligned continuation': (
-        'names = Alice\n'
-        '        Bob',
-        [(0, None, 'names', 'Alice\nBob')]
-    ),
-    'blank line':(
-        '[section]\n\nvalue=1',
-        [(0, 'section', None, None), (2, 'section', 'value', '1')]
-    ),
-    'comment': (
-        '# comment',
-        []
-    ),
-    'comment on value': (
-        'value = 1',
-        [(0, None, 'value', '1')]
-    ),
-
-    'comment on section': (
-        '[section] #comment',
-        [(0, 'section', None, None)]
-    ),
-    'comment2': (
-        '; comment',
-        []
-    ),
-
-    'comment2 on section': (
-        '[section] ;comment',
-        [(0, 'section', None, None)]
-    ),
-    'pseudo section syntax in value': (
-        'name = value []',
-        [(0, None, 'name', 'value []')]
-    ),
-    'assignment in value': (
-        'value = x = 3',
-        [(0, None, 'value', 'x = 3')]
-    ),
-    'use of colon for name-values': (
-        'name: y',
-        [(0, None, 'name', 'y')]
-    ),
-    'use of colon without space': (
-        'value:y=5',
-        [(0, None, 'value', 'y=5')]
-    ),
-    'equality gets precedence': (
-        'value=xyz:5',
-        [(0, None, 'value', 'xyz:5')]
-    ),
-
-}
-
-def parse(input):
-    # only for testing purposes - _parse() does not use state except path
-    ini = object.__new__(IniConfig)
-    ini.path = "sample"
-    return ini._parse(input.splitlines(True))
-
-def parse_a_error(input):
-    return py.test.raises(ParseError, parse, input)
-
-def test_tokenize(input, expected):
-    parsed = parse(input)
-    assert parsed == expected
-
-def test_parse_empty():
-    parsed = parse("")
-    assert not parsed
-    ini = IniConfig("sample", "")
-    assert not ini.sections
-
-def test_ParseError():
-    e = ParseError("filename", 0, "hello")
-    assert str(e) == "filename:1: hello"
-
-def test_continuation_needs_perceeding_token():
-    excinfo = parse_a_error(' Foo')
-    assert excinfo.value.lineno == 0
-
-def test_continuation_cant_be_after_section():
-    excinfo = parse_a_error('[section]\n Foo')
-    assert excinfo.value.lineno == 1
-
-def test_section_cant_be_empty():
-    excinfo = parse_a_error('[]')
-
-@py.test.mark.multi(line=[
-    '!!',
-    ])
-def test_error_on_weird_lines(line):
-    parse_a_error(line)
-
-def test_iniconfig_from_file(tmpdir):
-    path = tmpdir/'test.txt'
-    path.write('[metadata]\nname=1')
-
-    config = IniConfig(path=path)
-    assert list(config.sections) == ['metadata']
-    config = IniConfig(path, "[diff]")
-    assert list(config.sections) == ['diff']
-    py.test.raises(TypeError, "IniConfig(data=path.read())")
-
-def test_iniconfig_section_first(tmpdir):
-    excinfo = py.test.raises(ParseError, """
-        IniConfig("x", data='name=1')
-    """)
-    assert excinfo.value.msg == "no section header defined"
-
-def test_iniconig_section_duplicate_fails():
-    excinfo = py.test.raises(ParseError, r"""
-        IniConfig("x", data='[section]\n[section]')
-    """)
-    assert 'duplicate section' in str(excinfo.value)
-
-def test_iniconfig_duplicate_key_fails():
-    excinfo = py.test.raises(ParseError, r"""
-        IniConfig("x", data='[section]\nname = Alice\nname = bob')
-    """)
-
-    assert 'duplicate name' in str(excinfo.value)
-
-def test_iniconfig_lineof():
-    config = IniConfig("x.ini", data=
-        '[section]\n'
-        'value = 1\n'
-        '[section2]\n'
-        '# comment\n'
-        'value =2'
-    )
-
-    assert config.lineof('missing') is None
-    assert config.lineof('section') == 1
-    assert config.lineof('section2') == 3
-    assert config.lineof('section', 'value') == 2
-    assert config.lineof('section2','value') == 5
-
-    assert config['section'].lineof('value') == 2
-    assert config['section2'].lineof('value') == 5
-
-def test_iniconfig_get_convert():
-    config= IniConfig("x", data='[section]\nint = 1\nfloat = 1.1')
-    assert config.get('section', 'int') == '1'
-    assert config.get('section', 'int', convert=int) == 1
-
-def test_iniconfig_get_missing():
-    config= IniConfig("x", data='[section]\nint = 1\nfloat = 1.1')
-    assert config.get('section', 'missing', default=1) == 1
-    assert config.get('section', 'missing') is None
-
-def test_section_get():
-    config = IniConfig("x", data='[section]\nvalue=1')
-    section = config['section']
-    assert section.get('value', convert=int) == 1
-    assert section.get('value', 1) == "1"
-    assert section.get('missing', 2) == 2
-
-def test_missing_section():
-    config = IniConfig("x", data='[section]\nvalue=1')
-    py.test.raises(KeyError,'config["other"]')
-
-def test_section_getitem():
-    config = IniConfig("x", data='[section]\nvalue=1')
-    assert config['section']['value'] == '1'
-    assert config['section']['value'] == '1'
-
-def test_section_iter():
-    config = IniConfig("x", data='[section]\nvalue=1')
-    names = list(config['section'])
-    assert names == ['value']
-    items = list(config['section'].items())
-    assert items==[('value', '1')]
-
-def test_config_iter():
-    config = IniConfig("x.ini", data=dedent('''
-          [section1]
-          value=1
-          [section2]
-          value=2
-    '''))
-    l = list(config)
-    assert len(l) == 2
-    assert l[0].name == 'section1'
-    assert l[0]['value'] == '1'
-    assert l[1].name == 'section2'
-    assert l[1]['value'] == '2'
-
-def test_config_contains():
-    config = IniConfig("x.ini", data=dedent('''
-          [section1]
-          value=1
-          [section2]
-          value=2
-    '''))
-    assert 'xyz' not in config
-    assert 'section1' in config
-    assert 'section2' in config
-
-def test_iter_file_order():
-    config = IniConfig("x.ini", data="""
-[section2] #cpython dict ordered before section
-value = 1
-value2 = 2 # dict ordered before value
-[section]
-a = 1
-b = 2
-""")
-    l = list(config)
-    secnames = [x.name for x in l]
-    assert secnames == ['section2', 'section']
-    assert list(config['section2']) == ['value', 'value2']
-    assert list(config['section']) == ['a', 'b']
-
-def test_example_pypirc():
-    config = IniConfig("pypirc", data=dedent('''
-        [distutils]
-        index-servers =
-            pypi
-            other
-
-        [pypi]
-        repository: <repository-url>
-        username: <username>
-        password: <password>
-
-        [other]
-        repository: http://example.com/pypi
-        username: <username>
-        password: <password>
-    '''))
-    distutils, pypi, other = list(config)
-    assert distutils["index-servers"] == "pypi\nother"
-    assert pypi['repository'] == '<repository-url>'
-    assert pypi['username'] == '<username>'
-    assert pypi['password'] == '<password>'
-    assert ['repository', 'username', 'password'] == list(other)
-
-
-def test_api_import():
-    assert ALL == ['IniConfig', 'ParseError']
-
-@pytest.mark.parametrize("line", [
-    "#qwe",
-    "  #qwe",
-    ";qwe",
-    " ;qwe",
-])
-def test_iscommentline_true(line):
-    assert iscommentline(line)
-
-
diff --git a/tools/py/tox.ini b/tools/py/tox.ini
deleted file mode 100644
index 8c0c79d..0000000
--- a/tools/py/tox.ini
+++ /dev/null
@@ -1,39 +0,0 @@
-[tox]
-envlist=py26,py27,py33,py34,external
-# py27-xdist causes problems with svn, py25 requires virtualenv==1.9.1
-#indexserver=
-#    default=http://pypi.testrun.org
-
-[testenv]
-changedir=testing
-commands=py.test --confcutdir=.. -rfsxX --junitxml={envlogdir}/junit-{envname}.xml []
-deps=pytest
-
-[testenv:py27-xdist]
-basepython=python2.7
-deps=
-    pytest
-    pytest-xdist
-commands=
-  py.test -n3 -rfsxX --confcutdir=.. --runslowtests \
-        --junitxml={envlogdir}/junit-{envname}.xml []
-
-[testenv:jython]
-changedir=testing
-commands=
-    {envpython} -m pytest --confcutdir=.. -rfsxX --junitxml={envlogdir}/junit-{envname}0.xml {posargs:io_ code}
-
-[testenv:py25]
-setenv = PIP_INSECURE=1
-
-[testenv:external]
-deps=
-    pytest
-    jinja2
-    decorator
-commands=
-    py.test --confcutdir=.. -rfsxX --junitxml={envlogdir}/junit-{envname}.xml {posargs:code}
-
-[pytest]
-rsyncdirs = conftest.py py doc testing
-addopts = -rxXf
diff --git a/tools/pytest.ini b/tools/pytest.ini
index e1ad451..b52e465 100644
--- a/tools/pytest.ini
+++ b/tools/pytest.ini
@@ -1,2 +1,3 @@
 [pytest]
-norecursedirs = .* {arch} *.egg html5lib py pytest pywebsocket six wpt wptrunner
+norecursedirs = .* {arch} *.egg html5lib third_party pywebsocket six wpt wptrunner
+xfail_strict=true
diff --git a/tools/pytest/.coveragerc b/tools/pytest/.coveragerc
deleted file mode 100644
index 27db64e..0000000
--- a/tools/pytest/.coveragerc
+++ /dev/null
@@ -1,7 +0,0 @@
-[run]
-omit = 
-    # standlonetemplate is read dynamically and tested by test_genscript
-    *standalonetemplate.py
-    # oldinterpret could be removed, as it is no longer used in py26+
-    *oldinterpret.py
-    vendored_packages
diff --git a/tools/pytest/.github/ISSUE_TEMPLATE.md b/tools/pytest/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index bc62e8a..0000000
--- a/tools/pytest/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,8 +0,0 @@
-Thanks for submitting an issue!
-
-Here's a quick checklist in what to include:
-
-- [ ] Include a detailed description of the bug or suggestion
-- [ ] `pip list` of the virtual environment you are using
-- [ ] py.test and operating system versions
-- [ ] Minimal example if possible
diff --git a/tools/pytest/.github/PULL_REQUEST_TEMPLATE.md b/tools/pytest/.github/PULL_REQUEST_TEMPLATE.md
deleted file mode 100644
index d09edce..0000000
--- a/tools/pytest/.github/PULL_REQUEST_TEMPLATE.md
+++ /dev/null
@@ -1,8 +0,0 @@
-Thanks for submitting a PR, your contribution is really appreciated!
-
-Here's a quick checklist that should be present in PRs:
-
-- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`
-- [ ] Make sure to include one or more tests for your change
-- [ ] Add yourself to `AUTHORS`
-- [ ] Add a new entry to the `CHANGELOG` (choose any open position to avoid merge conflicts with other PRs) 
diff --git a/tools/pytest/.gitignore b/tools/pytest/.gitignore
deleted file mode 100644
index e4355b8..0000000
--- a/tools/pytest/.gitignore
+++ /dev/null
@@ -1,34 +0,0 @@
-# Automatically generated by `hgimportsvn`
-.svn
-.hgsvn
-
-# Ignore local virtualenvs
-lib/
-bin/
-include/
-.Python/
-
-# These lines are suggested according to the svn:ignore property
-# Feel free to enable them by uncommenting them
-*.pyc
-*.pyo
-*.swp
-*.class
-*.orig
-*~
-
-.eggs/
-
-doc/*/_build
-build/
-dist/
-*.egg-info
-issue/
-env/
-.env/
-3rdparty/
-.tox
-.cache
-.coverage
-.ropeproject
-.idea
diff --git a/tools/pytest/.travis.yml b/tools/pytest/.travis.yml
deleted file mode 100644
index 3a8f36e..0000000
--- a/tools/pytest/.travis.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-sudo: false
-language: python
-python:
-    - '3.5'
-# command to install dependencies
-install: "pip install -U tox"
-# # command to run tests
-env:
-  matrix:
-    # coveralls is not listed in tox's envlist, but should run in travis
-    - TESTENV=coveralls
-    # note: please use "tox --listenvs" to populate the build matrix below
-    - TESTENV=linting
-    - TESTENV=py26
-    - TESTENV=py27
-    - TESTENV=py33
-    - TESTENV=py34
-    - TESTENV=py35
-    - TESTENV=pypy
-    - TESTENV=py27-pexpect
-    - TESTENV=py27-xdist
-    - TESTENV=py27-trial
-    - TESTENV=py35-pexpect
-    - TESTENV=py35-xdist
-    - TESTENV=py35-trial
-    - TESTENV=py27-nobyte
-    - TESTENV=doctesting
-    - TESTENV=py27-cxfreeze
-
-script: tox --recreate -e $TESTENV
-
-notifications:
-  irc:
-    channels:
-      - "chat.freenode.net#pytest"
-    on_success: change
-    on_failure: change
-    skip_join: true
-  email:
-    - pytest-commit@python.org
diff --git a/tools/pytest/AUTHORS b/tools/pytest/AUTHORS
deleted file mode 100644
index dfc0a54..0000000
--- a/tools/pytest/AUTHORS
+++ /dev/null
@@ -1,87 +0,0 @@
-Holger Krekel, holger at merlinux eu
-merlinux GmbH, Germany, office at merlinux eu
-
-Contributors include::
-
-Abhijeet Kasurde
-Anatoly Bubenkoff
-Andreas Zeidler
-Andy Freeland
-Anthon van der Neut
-Armin Rigo
-Aron Curzon
-Aviv Palivoda
-Benjamin Peterson
-Bob Ippolito
-Brian Dorsey
-Brian Okken
-Brianna Laugher
-Bruno Oliveira
-Carl Friedrich Bolz
-Charles Cloud
-Chris Lamb
-Christian Theunert
-Christian Tismer
-Christopher Gilling
-Daniel Grana
-Daniel Hahler
-Daniel Nuri
-Dave Hunt
-David Mohr
-David Vierra
-Edison Gustavo Muenz
-Eduardo Schettino
-Endre Galaczi
-Elizaveta Shashkova
-Eric Hunsberger
-Eric Siegerman
-Erik M. Bray
-Florian Bruhin
-Floris Bruynooghe
-Gabriel Reis
-Georgy Dyuldin
-Graham Horler
-Grig Gheorghiu
-Guido Wesdorp
-Harald Armin Massa
-Ian Bicking
-Jaap Broekhuizen
-Jan Balster
-Janne Vanhala
-Jason R. Coombs
-Joshua Bronson
-Jurko Gospodnetić
-Katarzyna Jachim
-Kevin Cox
-Lee Kamentsky
-Lukas Bednar
-Maciek Fijalkowski
-Maho
-Marc Schlaich
-Mark Abramowitz
-Markus Unterwaditzer
-Martijn Faassen
-Matt Bachmann
-Michael Aquilina
-Michael Birtwell
-Michael Droettboom
-Nicolas Delaby
-Pieter Mulder
-Piotr Banaszkiewicz
-Punyashloka Biswal
-Ralf Schmitt
-Raphael Pierzina
-Ronny Pfannschmidt
-Ross Lawley
-Ryan Wooden
-Samuele Pedroni
-Tom Viner
-Trevor Bekolay
-Wouter van Ackooy
-David Díaz-Barquero
-Eric Hunsberger
-Simon Gomizelj
-Russel Winder
-Ben Webb
-Alexei Kozlenok
-Cal Leeming
diff --git a/tools/pytest/CHANGELOG.rst b/tools/pytest/CHANGELOG.rst
deleted file mode 100644
index f18f646..0000000
--- a/tools/pytest/CHANGELOG.rst
+++ /dev/null
@@ -1,2586 +0,0 @@
-2.9.1
-=====
-
-**Bug Fixes**
-
-* Improve error message when a plugin fails to load.
-  Thanks `@nicoddemus`_ for the PR.
-
-* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_):
-  ``pytest.fail`` with non-ascii characters raises an internal pytest error.
-  Thanks `@nicoddemus`_ for the PR.
-
-* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs
-  contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_).
-
-* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors
-  containing non-ascii lines at the point of failure generated an internal
-  py.test error.
-  Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR.
-
-* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize
-  attempt to decode it as utf-8 ignoring errors.
-
-* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line.
-
-
-.. _#1437: https://github.com/pytest-dev/pytest/issues/1437
-.. _#469: https://github.com/pytest-dev/pytest/issues/469
-.. _#1431: https://github.com/pytest-dev/pytest/pull/1431
-.. _#649: https://github.com/pytest-dev/pytest/issues/649
-
-.. _@asottile: https://github.com/asottile
-
-
-2.9.0
-=====
-
-**New Features**
-
-* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
-  Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_).
-
-* ``--doctest-glob`` may now be passed multiple times in the command-line.
-  Thanks `@jab`_ and `@nicoddemus`_ for the PR.
-
-* New ``-rp`` and ``-rP`` reporting options give the summary and full output
-  of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR.
-
-* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS``
-  tests to fail the test suite (defaulting to ``False``). There's also a
-  ``xfail_strict`` ini option that can be used to configure it project-wise.
-  Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_).
-
-* ``Parser.addini`` now supports options of type ``bool``. 
-  Thanks `@nicoddemus`_ for the PR.
-
-* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
-  in doctest output (similar to ``ALLOW_UNICODE``).
-  Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_).
-
-* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors.
-  Fixes `#1366`_.
-  Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR.
-
-* Catch ``IndexError`` exceptions when getting exception source location. 
-  Fixes a pytest internal error for dynamically generated code (fixtures and tests)
-  where source lines are fake by intention.
-
-**Changes**
-
-* **Important**: `py.code <http://pylib.readthedocs.org/en/latest/code.html>`_ has been
-  merged into the ``pytest`` repository as ``pytest._code``. This decision 
-  was made because ``py.code`` had very few uses outside ``pytest`` and the 
-  fact that it was in a different repository made it difficult to fix bugs on 
-  its code in a timely manner. The team hopes with this to be able to better
-  refactor out and improve that code.
-  This change shouldn't affect users, but it is useful to let users aware
-  if they encounter any strange behavior.
-  
-  Keep in mind that the code for ``pytest._code`` is **private** and 
-  **experimental**, so you definitely should not import it explicitly!
-
-  Please note that the original ``py.code`` is still available in 
-  `pylib <http://pylib.readthedocs.org>`_.
-
-* ``pytest_enter_pdb`` now optionally receives the pytest config object.
-  Thanks `@nicoddemus`_ for the PR.
-
-* Removed code and documentation for Python 2.5 or lower versions,
-  including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
-  Thanks `@nicoddemus`_ for the PR (`#1226`_).
-
-* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
-  found in the environment, even when ``-vv`` isn't used.
-  Thanks `@The-Compiler`_ for the PR.
-
-* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and
-  ``--failed-first`` respectively.
-  Thanks `@MichaelAquilina`_ for the PR.
-
-* Added expected exceptions to ``pytest.raises`` fail message.
-
-* Collection only displays progress ("collecting X items") when in a terminal.
-  This avoids cluttering the output when using ``--color=yes`` to obtain
-  colors in CI integrations systems (`#1397`_).
-
-**Bug Fixes**
-
-* The ``-s`` and ``-c`` options should now work under ``xdist``;
-  ``Config.fromdictargs`` now represents its input much more faithfully.
-  Thanks to `@bukzor`_ for the complete PR (`#680`_).
-
-* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting.
-  Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR.
-
-* Fix formatting utf-8 explanation messages (`#1379`_).
-  Thanks `@biern`_ for the PR.
-
-* Fix `traceback style docs`_ to describe all of the available options
-  (auto/long/short/line/native/no), with `auto` being the default since v2.6.
-  Thanks `@hackebrot`_ for the PR.
-
-* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
-  with same name.
-
-.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
-
-.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
-.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
-.. _#1366: https://github.com/pytest-dev/pytest/issues/1366
-.. _#1040: https://github.com/pytest-dev/pytest/pull/1040
-.. _#680: https://github.com/pytest-dev/pytest/issues/680
-.. _#1287: https://github.com/pytest-dev/pytest/pull/1287
-.. _#1226: https://github.com/pytest-dev/pytest/pull/1226
-.. _#1290: https://github.com/pytest-dev/pytest/pull/1290
-.. _#1355: https://github.com/pytest-dev/pytest/pull/1355
-.. _#1397: https://github.com/pytest-dev/pytest/issues/1397
-.. _@biern: https://github.com/biern
-.. _@MichaelAquilina: https://github.com/MichaelAquilina
-.. _@bukzor: https://github.com/bukzor
-.. _@hpk42: https://github.com/hpk42
-.. _@nicoddemus: https://github.com/nicoddemus
-.. _@jab: https://github.com/jab
-.. _@codewarrior0: https://github.com/codewarrior0
-.. _@jaraco: https://github.com/jaraco
-.. _@The-Compiler: https://github.com/The-Compiler
-.. _@Shinkenjoe: https://github.com/Shinkenjoe
-.. _@tomviner: https://github.com/tomviner
-.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
-.. _@rabbbit: https://github.com/rabbbit
-.. _@hackebrot: https://github.com/hackebrot
-
-2.8.7
-=====
-
-- fix #1338: use predictable object resolution for monkeypatch
-
-2.8.6
-=====
-
-- fix #1259: allow for double nodeids in junitxml,
-  this was a regression failing plugins combinations
-  like pytest-pep8 + pytest-flakes
-
-- Workaround for exception that occurs in pyreadline when using
-  ``--pdb`` with standard I/O capture enabled.
-  Thanks Erik M. Bray for the PR.
-
-- fix #900: Better error message in case the target of a ``monkeypatch`` call
-  raises an ``ImportError``.
-
-- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1).
-  Thanks David R. MacIver for the report and Bruno Oliveira for the PR.
-
-- fix #1223: captured stdout and stderr are now properly displayed before 
-  entering pdb when ``--pdb`` is used instead of being thrown away.
-  Thanks Cal Leeming for the PR.
-
-- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now
-  properly displayed.
-  Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
-
-- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
-  Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
-
-- fix #1334: Add captured stdout to jUnit XML report on setup error.
-  Thanks Georgy Dyuldin for the PR.
-
-
-2.8.5
-=====
-
-- fix #1243: fixed issue where class attributes injected during collection could break pytest.
-  PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help.
-
-- fix #1074: precompute junitxml chunks instead of storing the whole tree in objects
-  Thanks Bruno Oliveira for the report and Ronny Pfannschmidt for the PR
-
-- fix #1238: fix ``pytest.deprecated_call()`` receiving multiple arguments
-  (Regression introduced in 2.8.4). Thanks Alex Gaynor for the report and
-  Bruno Oliveira for the PR.
-
-
-2.8.4
-=====
-
-- fix #1190: ``deprecated_call()`` now works when the deprecated
-  function has been already called by another test in the same
-  module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the
-  PR.
-
-- fix #1198: ``--pastebin`` option now works on Python 3. Thanks
-  Mehdy Khoshnoody for the PR.
-
-- fix #1219: ``--pastebin`` now works correctly when captured output contains
-  non-ascii characters. Thanks Bruno Oliveira for the PR.
-
-- fix #1204: another error when collecting with a nasty __getattr__().
-  Thanks Florian Bruhin for the PR.
-
-- fix the summary printed when no tests did run.
-  Thanks Florian Bruhin for the PR.
-- fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist
-
-- a number of documentation modernizations wrt good practices.
-  Thanks Bruno Oliveira for the PR.
-
-2.8.3
-=====
-
-- fix #1169: add __name__ attribute to testcases in TestCaseFunction to
-  support the @unittest.skip decorator on functions and methods.
-  Thanks Lee Kamentsky for the PR.
-
-- fix #1035: collecting tests if test module level obj has __getattr__().
-  Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR.
-
-- fix #331: don't collect tests if their failure cannot be reported correctly
-  e.g. they are a callable instance of a class.
-
-- fix #1133: fixed internal error when filtering tracebacks where one entry
-  belongs to a file which is no longer available.
-  Thanks Bruno Oliveira for the PR.
-
-- enhancement made to highlight in red the name of the failing tests so
-  they stand out in the output.
-  Thanks Gabriel Reis for the PR.
-
-- add more talks to the documentation
-- extend documentation on the --ignore cli option 
-- use pytest-runner for setuptools integration 
-- minor fixes for interaction with OS X El Capitan
-  system integrity protection (thanks Florian)
-
-
-2.8.2
-=====
-
-- fix #1085: proper handling of encoding errors when passing encoded byte
-  strings to pytest.parametrize in Python 2.
-  Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR.
-
-- fix #1087: handling SystemError when passing empty byte strings to
-  pytest.parametrize in Python 3.
-  Thanks Paul Kehrer for the report and Bruno Oliveira for the PR.
-
-- fix #995: fixed internal error when filtering tracebacks where one entry
-  was generated by an exec() statement.
-  Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
-  for contributing and Bruno Oliveira for the PR.
-
-- fix #1100 and #1057: errors when using autouse fixtures and doctest modules.
-  Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno
-  Oliveira for the PR.
-
-2.8.1
-=====
-
-- fix #1034: Add missing nodeid on pytest_logwarning call in
-  addhook.  Thanks Simon Gomizelj for the PR.
-
-- 'deprecated_call' is now only satisfied with a DeprecationWarning or
-  PendingDeprecationWarning. Before 2.8.0, it accepted any warning, and 2.8.0
-  made it accept only DeprecationWarning (but not PendingDeprecationWarning).
-  Thanks Alex Gaynor for the issue and Eric Hunsberger for the PR.
-
-- fix issue #1073: avoid calling __getattr__ on potential plugin objects.
-  This fixes an incompatibility with pytest-django.  Thanks Andreas Pelme,
-  Bruno Oliveira and Ronny Pfannschmidt for contributing and Holger Krekel
-  for the fix.
-
-- Fix issue #704: handle versionconflict during plugin loading more
-  gracefully.  Thanks Bruno Oliveira for the PR.
-
-- Fix issue #1064: ""--junitxml" regression when used with the
-  "pytest-xdist" plugin, with test reports being assigned to the wrong tests.
-  Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
-
-- (experimental) adapt more SEMVER style versioning and change meaning of
-  master branch in git repo: "master" branch now keeps the bugfixes, changes
-  aimed for micro releases.  "features" branch will only be be released
-  with minor or major pytest releases.
-
-- Fix issue #766 by removing documentation references to distutils.
-  Thanks Russel Winder.
-
-- Fix issue #1030: now byte-strings are escaped to produce item node ids
-  to make them always serializable.
-  Thanks Andy Freeland for the report and Bruno Oliveira for the PR.
-
-- Python 2: if unicode parametrized values are convertible to ascii, their
-  ascii representation is used for the node id.
-
-- Fix issue #411: Add __eq__ method to assertion comparison example.
-  Thanks Ben Webb.
-- Fix issue #653: deprecated_call can be used as context manager.
-
-- fix issue 877: properly handle assertion explanations with non-ascii repr
-  Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR.
-
-- fix issue 1029: transform errors when writing cache values into pytest-warnings
-
-2.8.0
-=====
-
-- new ``--lf`` and ``-ff`` options to run only the last failing tests or
-  "failing tests first" from the last run.  This functionality is provided
-  through porting the formerly external pytest-cache plugin into pytest core.
-  BACKWARD INCOMPAT: if you used pytest-cache's functionality to persist
-  data between test runs be aware that we don't serialize sets anymore.
-  Thanks Ronny Pfannschmidt for most of the merging work.
-
-- "-r" option now accepts "a" to include all possible reports, similar
-  to passing "fEsxXw" explicitly (isse960).
-  Thanks Abhijeet Kasurde for the PR.
-
-- avoid python3.5 deprecation warnings by introducing version
-  specific inspection helpers, thanks Michael Droettboom.
-
-- fix issue562: @nose.tools.istest now fully respected.
-
-- fix issue934: when string comparison fails and a diff is too large to display
-  without passing -vv, still show a few lines of the diff.
-  Thanks Florian Bruhin for the report and Bruno Oliveira for the PR.
-
-- fix issue736: Fix a bug where fixture params would be discarded when combined
-  with parametrization markers.
-  Thanks to Markus Unterwaditzer for the PR.
-
-- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the
-  ``u`` prefix is stripped from unicode strings in expected doctest output. This
-  allows doctests which use unicode to run in Python 2 and 3 unchanged.
-  Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
-
-- parametrize now also generates meaningful test IDs for enum, regex and class
-  objects (as opposed to class instances).
-  Thanks to Florian Bruhin for the PR.
-
-- Add 'warns' to assert that warnings are thrown (like 'raises').
-  Thanks to Eric Hunsberger for the PR.
-
-- Fix issue683: Do not apply an already applied mark.  Thanks ojake for the PR.
-
-- Deal with capturing failures better so fewer exceptions get lost to
-  /dev/null.  Thanks David Szotten for the PR.
-
-- fix issue730: deprecate and warn about the --genscript option.
-  Thanks Ronny Pfannschmidt for the report and Christian Pommranz for the PR.
-
-- fix issue751: multiple parametrize with ids bug if it parametrizes class with
-  two or more test methods. Thanks Sergey Chipiga for reporting and Jan
-  Bednarik for PR.
-
-- fix issue82: avoid loading conftest files from setup.cfg/pytest.ini/tox.ini
-  files and upwards by default (--confcutdir can still be set to override this).
-  Thanks Bruno Oliveira for the PR.
-
-- fix issue768: docstrings found in python modules were not setting up session
-  fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
-
-- added ``tmpdir_factory``, a session-scoped fixture that can be used to create
-  directories under the base temporary directory. Previously this object was
-  installed as a ``_tmpdirhandler`` attribute of the ``config`` object, but now it
-  is part of the official API and using ``config._tmpdirhandler`` is
-  deprecated.
-  Thanks Bruno Oliveira for the PR.
-
-- fix issue808: pytest's internal assertion rewrite hook now implements the
-  optional PEP302 get_data API so tests can access data files next to them.
-  Thanks xmo-odoo for request and example and Bruno Oliveira for
-  the PR.
-
-- rootdir and inifile are now displayed during usage errors to help
-  users diagnose problems such as unexpected ini files which add
-  unknown options being picked up by pytest. Thanks to Pavel Savchenko for
-  bringing the problem to attention in #821 and Bruno Oliveira for the PR.
-
-- Summary bar now is colored yellow for warning
-  situations such as: all tests either were skipped or xpass/xfailed,
-  or no tests were run at all (this is a partial fix for issue500).
-
-- fix issue812: pytest now exits with status code 5 in situations where no
-  tests were run at all, such as the directory given in the command line does
-  not contain any tests or as result of a command line option filters
-  all out all tests (-k for example).
-  Thanks Eric Siegerman (issue812) and Bruno Oliveira for the PR.
-
-- Summary bar now is colored yellow for warning
-  situations such as: all tests either were skipped or xpass/xfailed,
-  or no tests were run at all (related to issue500).
-  Thanks Eric Siegerman.
-
-- New ``testpaths`` ini option: list of directories to search for tests
-  when executing pytest from the root directory. This can be used
-  to speed up test collection when a project has well specified directories
-  for tests, being usually more practical than configuring norecursedirs for
-  all directories that do not contain tests.
-  Thanks to Adrian for idea (#694) and Bruno Oliveira for the PR.
-
-- fix issue713: JUnit XML reports for doctest failures.
-  Thanks Punyashloka Biswal.
-
-- fix issue970: internal pytest warnings now appear as "pytest-warnings" in
-  the terminal instead of "warnings", so it is clear for users that those
-  warnings are from pytest and not from the builtin "warnings" module.
-  Thanks Bruno Oliveira.
-
-- Include setup and teardown in junitxml test durations.
-  Thanks Janne Vanhala.
-
-- fix issue735: assertion failures on debug versions of Python 3.4+
-
-- new option ``--import-mode`` to allow to change test module importing
-  behaviour to append to sys.path instead of prepending.  This better allows
-  to run test modules against installated versions of a package even if the
-  package under test has the same import root.  In this example::
-
-        testing/__init__.py
-        testing/test_pkg_under_test.py
-        pkg_under_test/
-
-  the tests will run against the installed version
-  of pkg_under_test when ``--import-mode=append`` is used whereas
-  by default they would always pick up the local version.  Thanks Holger Krekel.
-
-- pytester: add method ``TmpTestdir.delete_loaded_modules()``, and call it
-  from ``inline_run()`` to allow temporary modules to be reloaded.
-  Thanks Eduardo Schettino.
-
-- internally refactor pluginmanager API and code so that there
-  is a clear distinction between a pytest-agnostic rather simple
-  pluginmanager and the PytestPluginManager which adds a lot of
-  behaviour, among it handling of the local conftest files.
-  In terms of documented methods this is a backward compatible
-  change but it might still break 3rd party plugins which relied on
-  details like especially the pluginmanager.add_shutdown() API.
-  Thanks Holger Krekel.
-
-- pluginmanagement: introduce ``pytest.hookimpl`` and
-  ``pytest.hookspec`` decorators for setting impl/spec
-  specific parameters.  This substitutes the previous
-  now deprecated use of ``pytest.mark`` which is meant to
-  contain markers for test functions only.
-
-- write/refine docs for "writing plugins" which now have their
-  own page and are separate from the "using/installing plugins`` page.
-
-- fix issue732: properly unregister plugins from any hook calling
-  sites allowing to have temporary plugins during test execution.
-
-- deprecate and warn about ``__multicall__`` argument in hook
-  implementations.  Use the ``hookwrapper`` mechanism instead already
-  introduced with pytest-2.7.
-
-- speed up pytest's own test suite considerably by using inprocess
-  tests by default (testrun can be modified with --runpytest=subprocess
-  to create subprocesses in many places instead).  The main
-  APIs to run pytest in a test is "runpytest()" or "runpytest_subprocess"
-  and "runpytest_inprocess" if you need a particular way of running
-  the test.  In all cases you get back a RunResult but the inprocess
-  one will also have a "reprec" attribute with the recorded events/reports.
-
-- fix monkeypatch.setattr("x.y", raising=False) to actually not raise
-  if "y" is not a pre-existing attribute. Thanks Florian Bruhin.
-
-- fix issue741: make running output from testdir.run copy/pasteable
-  Thanks Bruno Oliveira.
-
-- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files.
-
-- add ``file`` and ``line`` attributes to JUnit-XML output.
-
-- fix issue890: changed extension of all documentation files from ``txt`` to
-  ``rst``. Thanks to Abhijeet for the PR.
-
-- fix issue714: add ability to apply indirect=True parameter on particular argnames.
-  Thanks Elizaveta239.
-
-- fix issue890: changed extension of all documentation files from ``txt`` to
-  ``rst``. Thanks to Abhijeet for the PR.
-
-- fix issue957: "# doctest: SKIP" option will now register doctests as SKIPPED
-  rather than PASSED.
-  Thanks Thomas Grainger for the report and Bruno Oliveira for the PR.
-
-- issue951: add new record_xml_property fixture, that supports logging
-  additional information on xml output. Thanks David Diaz for the PR.
-
-- issue949: paths after normal options (for example ``-s``, ``-v``, etc) are now
-  properly used to discover ``rootdir`` and ``ini`` files.
-  Thanks Peter Lauri for the report and Bruno Oliveira for the PR.
-
-2.7.3 (compared to 2.7.2)
-=============================
-
-- Allow 'dev', 'rc', or other non-integer version strings in ``importorskip``.
-  Thanks to Eric Hunsberger for the PR.
-
-- fix issue856: consider --color parameter in all outputs (for example
-  --fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR.
-
-- fix issue855: passing str objects as ``plugins`` argument to pytest.main
-  is now interpreted as a module name to be imported and registered as a
-  plugin, instead of silently having no effect.
-  Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
-
-- fix issue744: fix for ast.Call changes in Python 3.5+.  Thanks
-  Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and
-  Thomas Kluyver.
-
-- fix issue842: applying markers in classes no longer propagate this markers
-  to superclasses which also have markers.
-  Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
-
-- preserve warning functions after call to pytest.deprecated_call. Thanks
-  Pieter Mulder for PR.
-
-- fix issue854: autouse yield_fixtures defined as class members of
-  unittest.TestCase subclasses now work as expected.
-  Thannks xmo-odoo for the report and Bruno Oliveira for the PR.
-
-- fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the
-  fixtures declared on the first one.
-  Thanks Florian Bruhin for reporting and Bruno Oliveira for the PR.
-
-- fix issue863: skipped tests now report the correct reason when a skip/xfail
-  condition is met when using multiple markers.
-  Thanks Raphael Pierzina for reporting and Bruno Oliveira for the PR.
-
-- optimized tmpdir fixture initialization, which should make test sessions
-  faster (specially when using pytest-xdist). The only visible effect
-  is that now pytest uses a subdirectory in the $TEMP directory for all
-  directories created by this fixture (defaults to $TEMP/pytest-$USER).
-  Thanks Bruno Oliveira for the PR.
-
-2.7.2 (compared to 2.7.1)
-=============================
-
-- fix issue767: pytest.raises value attribute does not contain the exception
-  instance on Python 2.6. Thanks Eric Siegerman for providing the test
-  case and Bruno Oliveira for PR.
-
-- Automatically create directory for junitxml and results log.
-  Thanks Aron Curzon.
-
-- fix issue713: JUnit XML reports for doctest failures.
-  Thanks Punyashloka Biswal.
-
-- fix issue735: assertion failures on debug versions of Python 3.4+
-  Thanks Benjamin Peterson.
-
-- fix issue114: skipif marker reports to internal skipping plugin;
-  Thanks Floris Bruynooghe for reporting and Bruno Oliveira for the PR.
-
-- fix issue748: unittest.SkipTest reports to internal pytest unittest plugin.
-  Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR.
-
-- fix issue718: failed to create representation of sets containing unsortable
-  elements in python 2. Thanks Edison Gustavo Muenz.
-
-- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29
-  which has a refined algorithm for traceback generation.
-
-
-2.7.1 (compared to 2.7.0)
-=============================
-
-- fix issue731: do not get confused by the braces which may be present
-  and unbalanced in an object's repr while collapsing False
-  explanations.  Thanks Carl Meyer for the report and test case.
-
-- fix issue553: properly handling inspect.getsourcelines failures in
-  FixtureLookupError which would lead to to an internal error,
-  obfuscating the original problem. Thanks talljosh for initial
-  diagnose/patch and Bruno Oliveira for final patch.
-
-- fix issue660: properly report scope-mismatch-access errors
-  independently from ordering of fixture arguments.  Also
-  avoid the pytest internal traceback which does not provide
-  information to the user. Thanks Holger Krekel.
-
-- streamlined and documented release process.  Also all versions
-  (in setup.py and documentation generation) are now read
-  from _pytest/__init__.py. Thanks Holger Krekel.
-
-- fixed docs to remove the notion that yield-fixtures are experimental.
-  They are here to stay :)  Thanks Bruno Oliveira.
-
-- Support building wheels by using environment markers for the
-  requirements.  Thanks Ionel Maries Cristian.
-
-- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
-  when tests raised SystemExit. Thanks Holger Krekel.
-
-- reintroduced _pytest fixture of the pytester plugin which is used
-  at least by pytest-xdist.
-
-2.7.0 (compared to 2.6.4)
-=============================
-
-- fix issue435: make reload() work when assert rewriting is active.
-  Thanks Daniel Hahler.
-
-- fix issue616: conftest.py files and their contained fixutres are now
-  properly considered for visibility, independently from the exact
-  current working directory and test arguments that are used.
-  Many thanks to Eric Siegerman and his PR235 which contains
-  systematic tests for conftest visibility and now passes.
-  This change also introduces the concept of a ``rootdir`` which
-  is printed as a new pytest header and documented in the pytest
-  customize web page.
-
-- change reporting of "diverted" tests, i.e. tests that are collected
-  in one file but actually come from another (e.g. when tests in a test class
-  come from a base class in a different file).  We now show the nodeid
-  and indicate via a postfix the other file.
-
-- add ability to set command line options by environment variable PYTEST_ADDOPTS.
-
-- added documentation on the new pytest-dev teams on bitbucket and
-  github.  See https://pytest.org/latest/contributing.html .
-  Thanks to Anatoly for pushing and initial work on this.
-
-- fix issue650: new option ``--docttest-ignore-import-errors`` which
-  will turn import errors in doctests into skips.  Thanks Charles Cloud
-  for the complete PR.
-
-- fix issue655: work around different ways that cause python2/3
-  to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
-
-- fix issue615: assertion re-writing did not correctly escape % signs
-  when formatting boolean operations, which tripped over mixing
-  booleans with modulo operators.  Thanks to Tom Viner for the report,
-  triaging and fix.
-
-- implement issue351: add ability to specify parametrize ids as a callable
-  to generate custom test ids.  Thanks Brianna Laugher for the idea and
-  implementation.
-
-- introduce and document new hookwrapper mechanism useful for plugins
-  which want to wrap the execution of certain hooks for their purposes.
-  This supersedes the undocumented ``__multicall__`` protocol which
-  pytest itself and some external plugins use.  Note that pytest-2.8
-  is scheduled to drop supporting the old ``__multicall__``
-  and only support the hookwrapper protocol.
-
-- majorly speed up invocation of plugin hooks
-
-- use hookwrapper mechanism in builtin pytest plugins.
-
-- add a doctest ini option for doctest flags, thanks Holger Peters.
-
-- add note to docs that if you want to mark a parameter and the
-  parameter is a callable, you also need to pass in a reason to disambiguate
-  it from the "decorator" case.  Thanks Tom Viner.
-
-- "python_classes" and "python_functions" options now support glob-patterns
-  for test discovery, as discussed in issue600. Thanks Ldiary Translations.
-
-- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
-
-- fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise).
-
-- On failure, the ``sys.last_value``, ``sys.last_type`` and
-  ``sys.last_traceback`` are set, so that a user can inspect the error
-  via postmortem debugging (almarklein).
-
-2.6.4
-=====
-
-- Improve assertion failure reporting on iterables, by using ndiff and
-  pprint.
-
-- removed outdated japanese docs from source tree.
-
-- docs for "pytest_addhooks" hook.  Thanks Bruno Oliveira.
-
-- updated plugin index docs.  Thanks Bruno Oliveira.
-
-- fix issue557: with "-k" we only allow the old style "-" for negation
-  at the beginning of strings and even that is deprecated.  Use "not" instead.
-  This should allow to pick parametrized tests where "-" appeared in the parameter.
-
-- fix issue604: Escape % character in the assertion message.
-
-- fix issue620: add explanation in the --genscript target about what
-  the binary blob means. Thanks Dinu Gherman.
-
-- fix issue614: fixed pastebin support.
-
-
-- fix issue620: add explanation in the --genscript target about what
-  the binary blob means. Thanks Dinu Gherman.
-
-- fix issue614: fixed pastebin support.
-
-2.6.3
-=====
-
-- fix issue575: xunit-xml was reporting collection errors as failures
-  instead of errors, thanks Oleg Sinyavskiy.
-
-- fix issue582: fix setuptools example, thanks Laszlo Papp and Ronny
-  Pfannschmidt.
-
-- Fix infinite recursion bug when pickling capture.EncodedFile, thanks
-  Uwe Schmitt.
-
-- fix issue589: fix bad interaction with numpy and others when showing
-  exceptions.  Check for precise "maximum recursion depth exceed" exception
-  instead of presuming any RuntimeError is that one (implemented in py
-  dep).  Thanks Charles Cloud for analysing the issue.
-
-- fix conftest related fixture visibility issue: when running with a
-  CWD outside a test package pytest would get fixture discovery wrong.
-  Thanks to Wolfgang Schnerring for figuring out a reproducable example.
-
-- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the
-  timeout when interactively entering pdb).  Thanks Wolfgang Schnerring.
-
-- check xfail/skip also with non-python function test items. Thanks
-  Floris Bruynooghe.
-
-2.6.2
-=====
-
-- Added function pytest.freeze_includes(), which makes it easy to embed
-  pytest into executables using tools like cx_freeze.
-  See docs for examples and rationale. Thanks Bruno Oliveira.
-
-- Improve assertion rewriting cache invalidation precision.
-
-- fixed issue561: adapt autouse fixture example for python3.
-
-- fixed issue453: assertion rewriting issue with __repr__ containing
-  "\n{", "\n}" and "\n~".
-
-- fix issue560: correctly display code if an "else:" or "finally:" is
-  followed by statements on the same line.
-
-- Fix example in monkeypatch documentation, thanks t-8ch.
-
-- fix issue572: correct tmpdir doc example for python3.
-
-- Do not mark as universal wheel because Python 2.6 is different from
-  other builds due to the extra argparse dependency.  Fixes issue566.
-  Thanks sontek.
-
-- Implement issue549: user-provided assertion messages now no longer
-  replace the py.test introspection message but are shown in addition
-  to them.
-
-2.6.1
-=====
-
-- No longer show line numbers in the --verbose output, the output is now
-  purely the nodeid.  The line number is still shown in failure reports.
-  Thanks Floris Bruynooghe.
-
-- fix issue437 where assertion rewriting could cause pytest-xdist slaves
-  to collect different tests. Thanks Bruno Oliveira.
-
-- fix issue555: add "errors" attribute to capture-streams to satisfy
-  some distutils and possibly other code accessing sys.stdout.errors.
-
-- fix issue547 capsys/capfd also work when output capturing ("-s") is disabled.
-
-- address issue170: allow pytest.mark.xfail(...) to specify expected exceptions via
-  an optional "raises=EXC" argument where EXC can be a single exception
-  or a tuple of exception classes.  Thanks David Mohr for the complete
-  PR.
-
-- fix integration of pytest with unittest.mock.patch decorator when
-  it uses the "new" argument.  Thanks Nicolas Delaby for test and PR.
-
-- fix issue with detecting conftest files if the arguments contain
-  "::" node id specifications (copy pasted from "-v" output)
-
-- fix issue544 by only removing "@NUM" at the end of "::" separated parts
-  and if the part has an ".py" extension
-
-- don't use py.std import helper, rather import things directly.
-  Thanks Bruno Oliveira.
-
-2.6
-===
-
-- Cache exceptions from fixtures according to their scope (issue 467).
-
-- fix issue537: Avoid importing old assertion reinterpretation code by default.
-
-- fix issue364: shorten and enhance tracebacks representation by default.
-  The new "--tb=auto" option (default) will only display long tracebacks
-  for the first and last entry.  You can get the old behaviour of printing
-  all entries as long entries with "--tb=long".  Also short entries by
-  default are now printed very similarly to "--tb=native" ones.
-
-- fix issue514: teach assertion reinterpretation about private class attributes
-
-- change -v output to include full node IDs of tests.  Users can copy
-  a node ID from a test run, including line number, and use it as a
-  positional argument in order to run only a single test.
-
-- fix issue 475: fail early and comprehensible if calling
-  pytest.raises with wrong exception type.
-
-- fix issue516: tell in getting-started about current dependencies.
-
-- cleanup setup.py a bit and specify supported versions. Thanks Jurko
-  Gospodnetic for the PR.
-
-- change XPASS colour to yellow rather then red when tests are run
-  with -v.
-
-- fix issue473: work around mock putting an unbound method into a class
-  dict when double-patching.
-
-- fix issue498: if a fixture finalizer fails, make sure that
-  the fixture is still invalidated.
-
-- fix issue453: the result of the pytest_assertrepr_compare hook now gets
-  it's newlines escaped so that format_exception does not blow up.
-
-- internal new warning system: pytest will now produce warnings when
-  it detects oddities in your test collection or execution.
-  Warnings are ultimately sent to a new pytest_logwarning hook which is
-  currently only implemented by the terminal plugin which displays
-  warnings in the summary line and shows more details when -rw (report on
-  warnings) is specified.
-
-- change skips into warnings for test classes with an __init__ and
-  callables in test modules which look like a test but are not functions.
-
-- fix issue436: improved finding of initial conftest files from command
-  line arguments by using the result of parse_known_args rather than
-  the previous flaky heuristics.  Thanks Marc Abramowitz for tests
-  and initial fixing approaches in this area.
-
-- fix issue #479: properly handle nose/unittest(2) SkipTest exceptions
-  during collection/loading of test modules.  Thanks to Marc Schlaich
-  for the complete PR.
-
-- fix issue490: include pytest_load_initial_conftests in documentation
-  and improve docstring.
-
-- fix issue472: clarify that ``pytest.config.getvalue()`` cannot work
-  if it's triggered ahead of command line parsing.
-
-- merge PR123: improved integration with mock.patch decorator on tests.
-
-- fix issue412: messing with stdout/stderr FD-level streams is now
-  captured without crashes.
-
-- fix issue483: trial/py33 works now properly.  Thanks Daniel Grana for PR.
-
-- improve example for pytest integration with "python setup.py test"
-  which now has a generic "-a" or "--pytest-args" option where you
-  can pass additional options as a quoted string.  Thanks Trevor Bekolay.
-
-- simplified internal capturing mechanism and made it more robust
-  against tests or setups changing FD1/FD2, also better integrated
-  now with pytest.pdb() in single tests.
-
-- improvements to pytest's own test-suite leakage detection, courtesy of PRs
-  from Marc Abramowitz
-
-- fix issue492: avoid leak in test_writeorg.  Thanks Marc Abramowitz.
-
-- fix issue493: don't run tests in doc directory with ``python setup.py test``
-  (use tox -e doctesting for that)
-
-- fix issue486: better reporting and handling of early conftest loading failures
-
-- some cleanup and simplification of internal conftest handling.
-
-- work a bit harder to break reference cycles when catching exceptions.
-  Thanks Jurko Gospodnetic.
-
-- fix issue443: fix skip examples to use proper comparison.  Thanks Alex
-  Groenholm.
-
-- support nose-style ``__test__`` attribute on modules, classes and
-  functions, including unittest-style Classes.  If set to False, the
-  test will not be collected.
-
-- fix issue512: show "<notset>" for arguments which might not be set
-  in monkeypatch plugin.  Improves output in documentation.
-
-
-2.5.2
-=====
-
-- fix issue409 -- better interoperate with cx_freeze by not
-  trying to import from collections.abc which causes problems
-  for py27/cx_freeze.  Thanks Wolfgang L. for reporting and tracking it down.
-
-- fixed docs and code to use "pytest" instead of "py.test" almost everywhere.
-  Thanks Jurko Gospodnetic for the complete PR.
-
-- fix issue425: mention at end of "py.test -h" that --markers
-  and --fixtures work according to specified test path (or current dir)
-
-- fix issue413: exceptions with unicode attributes are now printed
-  correctly also on python2 and with pytest-xdist runs. (the fix
-  requires py-1.4.20)
-
-- copy, cleanup and integrate py.io capture
-  from pylib 1.4.20.dev2 (rev 13d9af95547e)
-
-- address issue416: clarify docs as to conftest.py loading semantics
-
-- fix issue429: comparing byte strings with non-ascii chars in assert
-  expressions now work better.  Thanks Floris Bruynooghe.
-
-- make capfd/capsys.capture private, its unused and shouldnt be exposed
-
-
-2.5.1
-=====
-
-- merge new documentation styling PR from Tobias Bieniek.
-
-- fix issue403: allow parametrize of multiple same-name functions within
-  a collection node.  Thanks Andreas Kloeckner and Alex Gaynor for reporting
-  and analysis.
-
-- Allow parameterized fixtures to specify the ID of the parameters by
-  adding an ids argument to pytest.fixture() and pytest.yield_fixture().
-  Thanks Floris Bruynooghe.
-
-- fix issue404 by always using the binary xml escape in the junitxml
-  plugin.  Thanks Ronny Pfannschmidt.
-
-- fix issue407: fix addoption docstring to point to argparse instead of
-  optparse. Thanks Daniel D. Wright.
-
-
-
-2.5.0
-=====
-
-- dropped python2.5 from automated release testing of pytest itself
-  which means it's probably going to break soon (but still works
-  with this release we believe).
-
-- simplified and fixed implementation for calling finalizers when
-  parametrized fixtures or function arguments are involved.  finalization
-  is now performed lazily at setup time instead of in the "teardown phase".
-  While this might sound odd at first, it helps to ensure that we are
-  correctly handling setup/teardown even in complex code.  User-level code
-  should not be affected unless it's implementing the pytest_runtest_teardown
-  hook and expecting certain fixture instances are torn down within (very
-  unlikely and would have been unreliable anyway).
-
-- PR90: add --color=yes|no|auto option to force terminal coloring
-  mode ("auto" is default).  Thanks Marc Abramowitz.
-
-- fix issue319 - correctly show unicode in assertion errors.  Many
-  thanks to Floris Bruynooghe for the complete PR.  Also means
-  we depend on py>=1.4.19 now.
-
-- fix issue396 - correctly sort and finalize class-scoped parametrized
-  tests independently from number of methods on the class.
-
-- refix issue323 in a better way -- parametrization should now never
-  cause Runtime Recursion errors because the underlying algorithm
-  for re-ordering tests per-scope/per-fixture is not recursive
-  anymore (it was tail-call recursive before which could lead
-  to problems for more than >966 non-function scoped parameters).
-
-- fix issue290 - there is preliminary support now for parametrizing
-  with repeated same values (sometimes useful to to test if calling
-  a second time works as with the first time).
-
-- close issue240 - document precisely how pytest module importing
-  works, discuss the two common test directory layouts, and how it
-  interacts with PEP420-namespace packages.
-
-- fix issue246 fix finalizer order to be LIFO on independent fixtures
-  depending on a parametrized higher-than-function scoped fixture.
-  (was quite some effort so please bear with the complexity of this sentence :)
-  Thanks Ralph Schmitt for the precise failure example.
-
-- fix issue244 by implementing special index for parameters to only use
-  indices for paramentrized test ids
-
-- fix issue287 by running all finalizers but saving the exception
-  from the first failing finalizer and re-raising it so teardown will
-  still have failed.  We reraise the first failing exception because
-  it might be the cause for other finalizers to fail.
-
-- fix ordering when mock.patch or other standard decorator-wrappings
-  are used with test methods.  This fixues issue346 and should
-  help with random "xdist" collection failures.  Thanks to
-  Ronny Pfannschmidt and Donald Stufft for helping to isolate it.
-
-- fix issue357 - special case "-k" expressions to allow for
-  filtering with simple strings that are not valid python expressions.
-  Examples: "-k 1.3" matches all tests parametrized with 1.3.
-  "-k None" filters all tests that have "None" in their name
-  and conversely "-k 'not None'".
-  Previously these examples would raise syntax errors.
-
-- fix issue384 by removing the trial support code
-  since the unittest compat enhancements allow
-  trial to handle it on its own
-
-- don't hide an ImportError when importing a plugin produces one.
-  fixes issue375.
-
-- fix issue275 - allow usefixtures and autouse fixtures
-  for running doctest text files.
-
-- fix issue380 by making --resultlog only rely on longrepr instead
-  of the "reprcrash" attribute which only exists sometimes.
-
-- address issue122: allow @pytest.fixture(params=iterator) by exploding
-  into a list early on.
-
-- fix pexpect-3.0 compatibility for pytest's own tests.
-  (fixes issue386)
-
-- allow nested parametrize-value markers, thanks James Lan for the PR.
-
-- fix unicode handling with new monkeypatch.setattr(import_path, value)
-  API.  Thanks Rob Dennis.  Fixes issue371.
-
-- fix unicode handling with junitxml, fixes issue368.
-
-- In assertion rewriting mode on Python 2, fix the detection of coding
-  cookies. See issue #330.
-
-- make "--runxfail" turn imperative pytest.xfail calls into no ops
-  (it already did neutralize pytest.mark.xfail markers)
-
-- refine pytest / pkg_resources interactions: The AssertionRewritingHook
-  PEP302 compliant loader now registers itself with setuptools/pkg_resources
-  properly so that the pkg_resources.resource_stream method works properly.
-  Fixes issue366.  Thanks for the investigations and full PR to Jason R. Coombs.
-
-- pytestconfig fixture is now session-scoped as it is the same object during the
-  whole test run.  Fixes issue370.
-
-- avoid one surprising case of marker malfunction/confusion::
-
-      @pytest.mark.some(lambda arg: ...)
-      def test_function():
-
-  would not work correctly because pytest assumes @pytest.mark.some
-  gets a function to be decorated already.  We now at least detect if this
-  arg is an lambda and thus the example will work.  Thanks Alex Gaynor
-  for bringing it up.
-
-- xfail a test on pypy that checks wrong encoding/ascii (pypy does
-  not error out). fixes issue385.
-
-- internally make varnames() deal with classes's __init__,
-  although it's not needed by pytest itself atm.  Also
-  fix caching.  Fixes issue376.
-
-- fix issue221 - handle importing of namespace-package with no
-  __init__.py properly.
-
-- refactor internal FixtureRequest handling to avoid monkeypatching.
-  One of the positive user-facing effects is that the "request" object
-  can now be used in closures.
-
-- fixed version comparison in pytest.importskip(modname, minverstring)
-
-- fix issue377 by clarifying in the nose-compat docs that pytest
-  does not duplicate the unittest-API into the "plain" namespace.
-
-- fix verbose reporting for @mock'd test functions
-
-2.4.2
-=====
-
-- on Windows require colorama and a newer py lib so that py.io.TerminalWriter()
-  now uses colorama instead of its own ctypes hacks. (fixes issue365)
-  thanks Paul Moore for bringing it up.
-
-- fix "-k" matching of tests where "repr" and "attr" and other names would
-  cause wrong matches because of an internal implementation quirk
-  (don't ask) which is now properly implemented. fixes issue345.
-
-- avoid tmpdir fixture to create too long filenames especially
-  when parametrization is used (issue354)
-
-- fix pytest-pep8 and pytest-flakes / pytest interactions
-  (collection names in mark plugin was assuming an item always
-  has a function which is not true for those plugins etc.)
-  Thanks Andi Zeidler.
-
-- introduce node.get_marker/node.add_marker API for plugins
-  like pytest-pep8 and pytest-flakes to avoid the messy
-  details of the node.keywords  pseudo-dicts.  Adapted
-  docs.
-
-- remove attempt to "dup" stdout at startup as it's icky.
-  the normal capturing should catch enough possibilities
-  of tests messing up standard FDs.
-
-- add pluginmanager.do_configure(config) as a link to
-  config.do_configure() for plugin-compatibility
-
-2.4.1
-=====
-
-- When using parser.addoption() unicode arguments to the
-  "type" keyword should also be converted to the respective types.
-  thanks Floris Bruynooghe, @dnozay. (fixes issue360 and issue362)
-
-- fix dotted filename completion when using argcomplete
-  thanks Anthon van der Neuth. (fixes issue361)
-
-- fix regression when a 1-tuple ("arg",) is used for specifying
-  parametrization (the values of the parametrization were passed
-  nested in a tuple).  Thanks Donald Stufft.
-
-- merge doc typo fixes, thanks Andy Dirnberger
-
-2.4
-===
-
-known incompatibilities:
-
-- if calling --genscript from python2.7 or above, you only get a
-  standalone script which works on python2.7 or above.  Use Python2.6
-  to also get a python2.5 compatible version.
-
-- all xunit-style teardown methods (nose-style, pytest-style,
-  unittest-style) will not be called if the corresponding setup method failed,
-  see issue322 below.
-
-- the pytest_plugin_unregister hook wasn't ever properly called
-  and there is no known implementation of the hook - so it got removed.
-
-- pytest.fixture-decorated functions cannot be generators (i.e. use
-  yield) anymore.  This change might be reversed in 2.4.1 if it causes
-  unforeseen real-life issues.  However, you can always write and return
-  an inner function/generator and change the fixture consumer to iterate
-  over the returned generator.  This change was done in lieu of the new
-  ``pytest.yield_fixture`` decorator, see below.
-
-new features:
-
-- experimentally introduce a new ``pytest.yield_fixture`` decorator
-  which accepts exactly the same parameters as pytest.fixture but
-  mandates a ``yield`` statement instead of a ``return statement`` from
-  fixture functions.  This allows direct integration with "with-style"
-  context managers in fixture functions and generally avoids registering
-  of finalization callbacks in favour of treating the "after-yield" as
-  teardown code.  Thanks Andreas Pelme, Vladimir Keleshev, Floris
-  Bruynooghe, Ronny Pfannschmidt and many others for discussions.
-
-- allow boolean expression directly with skipif/xfail
-  if a "reason" is also specified.  Rework skipping documentation
-  to recommend "condition as booleans" because it prevents surprises
-  when importing markers between modules.  Specifying conditions
-  as strings will remain fully supported.
-
-- reporting: color the last line red or green depending if
-  failures/errors occurred or everything passed.  thanks Christian
-  Theunert.
-
-- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no
-  "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut.
-
-- fix issue181: --pdb now also works on collect errors (and
-  on internal errors) .  This was implemented by a slight internal
-  refactoring and the introduction of a new hook
-  ``pytest_exception_interact`` hook (see next item).
-
-- fix issue341: introduce new experimental hook for IDEs/terminals to
-  intercept debugging: ``pytest_exception_interact(node, call, report)``.
-
-- new monkeypatch.setattr() variant to provide a shorter
-  invocation for patching out classes/functions from modules:
-
-     monkeypatch.setattr("requests.get", myfunc)
-
-  will replace the "get" function of the "requests" module with ``myfunc``.
-
-- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
-  Mathieu Agopian for the initial fix.  Also make all of pytest/nose
-  finalizer mimick the same generic behaviour: if a setupX exists and
-  fails, don't run teardownX.  This internally introduces a new method
-  "node.addfinalizer()" helper which can only be called during the setup
-  phase of a node.
-
-- simplify pytest.mark.parametrize() signature: allow to pass a
-  CSV-separated string to specify argnames.  For example:
-  ``pytest.mark.parametrize("input,expected",  [(1,2), (2,3)])``
-  works as well as the previous:
-  ``pytest.mark.parametrize(("input", "expected"), ...)``.
-
-- add support for setUpModule/tearDownModule detection, thanks Brian Okken.
-
-- integrate tab-completion on options through use of "argcomplete".
-  Thanks Anthon van der Neut for the PR.
-
-- change option names to be hyphen-separated long options but keep the
-  old spelling backward compatible.  py.test -h will only show the
-  hyphenated version, for example "--collect-only" but "--collectonly"
-  will remain valid as well (for backward-compat reasons).  Many thanks to
-  Anthon van der Neut for the implementation and to Hynek Schlawack for
-  pushing us.
-
-- fix issue 308 - allow to mark/xfail/skip individual parameter sets
-  when parametrizing.  Thanks Brianna Laugher.
-
-- call new experimental pytest_load_initial_conftests hook to allow
-  3rd party plugins to do something before a conftest is loaded.
-
-Bug fixes:
-
-- fix issue358 - capturing options are now parsed more properly
-  by using a new parser.parse_known_args method.
-
-- pytest now uses argparse instead of optparse (thanks Anthon) which
-  means that "argparse" is added as a dependency if installing into python2.6
-  environments or below.
-
-- fix issue333: fix a case of bad unittest/pytest hook interaction.
-
-- PR27: correctly handle nose.SkipTest during collection.  Thanks
-  Antonio Cuni, Ronny Pfannschmidt.
-
-- fix issue355: junitxml puts name="pytest" attribute to testsuite tag.
-
-- fix issue336: autouse fixture in plugins should work again.
-
-- fix issue279: improve object comparisons on assertion failure
-  for standard datatypes and recognise collections.abc.  Thanks to
-  Brianna Laugher and Mathieu Agopian.
-
-- fix issue317: assertion rewriter support for the is_package method
-
-- fix issue335: document py.code.ExceptionInfo() object returned
-  from pytest.raises(), thanks Mathieu Agopian.
-
-- remove implicit distribute_setup support from setup.py.
-
-- fix issue305: ignore any problems when writing pyc files.
-
-- SO-17664702: call fixture finalizers even if the fixture function
-  partially failed (finalizers would not always be called before)
-
-- fix issue320 - fix class scope for fixtures when mixed with
-  module-level functions.  Thanks Anatloy Bubenkoff.
-
-- you can specify "-q" or "-qq" to get different levels of "quieter"
-  reporting (thanks Katarzyna Jachim)
-
-- fix issue300 - Fix order of conftest loading when starting py.test
-  in a subdirectory.
-
-- fix issue323 - sorting of many module-scoped arg parametrizations
-
-- make sessionfinish hooks execute with the same cwd-context as at
-  session start (helps fix plugin behaviour which write output files
-  with relative path such as pytest-cov)
-
-- fix issue316 - properly reference collection hooks in docs
-
-- fix issue 306 - cleanup of -k/-m options to only match markers/test
-  names/keywords respectively.  Thanks Wouter van Ackooy.
-
-- improved doctest counting for doctests in python modules --
-  files without any doctest items will not show up anymore
-  and doctest examples are counted as separate test items.
-  thanks Danilo Bellini.
-
-- fix issue245 by depending on the released py-1.4.14
-  which fixes py.io.dupfile to work with files with no
-  mode. Thanks Jason R. Coombs.
-
-- fix junitxml generation when test output contains control characters,
-  addressing issue267, thanks Jaap Broekhuizen
-
-- fix issue338: honor --tb style for setup/teardown errors as well.  Thanks Maho.
-
-- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin.
-
-- better parametrize error messages, thanks Brianna Laugher
-
-- pytest_terminal_summary(terminalreporter) hooks can now use
-  ".section(title)" and ".line(msg)" methods to print extra
-  information at the end of a test run.
-
-2.3.5
-=====
-
-- fix issue169: respect --tb=style with setup/teardown errors as well.
-
-- never consider a fixture function for test function collection
-
-- allow re-running of test items / helps to fix pytest-reruntests plugin
-  and also help to keep less fixture/resource references alive
-
-- put captured stdout/stderr into junitxml output even for passing tests
-  (thanks Adam Goucher)
-
-- Issue 265 - integrate nose setup/teardown with setupstate
-  so it doesnt try to teardown if it did not setup
-
-- issue 271 - dont write junitxml on slave nodes
-
-- Issue 274 - dont try to show full doctest example
-  when doctest does not know the example location
-
-- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
-
-- inject "getfixture()" helper to retrieve fixtures from doctests,
-  thanks Andreas Zeidler
-
-- issue 259 - when assertion rewriting, be consistent with the default
-  source encoding of ASCII on Python 2
-
-- issue 251 - report a skip instead of ignoring classes with init
-
-- issue250 unicode/str mixes in parametrization names and values now works
-
-- issue257, assertion-triggered compilation of source ending in a
-  comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6)
-
-- fix --genscript option to generate standalone scripts that also
-  work with python3.3 (importer ordering)
-
-- issue171 - in assertion rewriting, show the repr of some
-  global variables
-
-- fix option help for "-k"
-
-- move long description of distribution into README.rst
-
-- improve docstring for metafunc.parametrize()
-
-- fix bug where using capsys with pytest.set_trace() in a test
-  function would break when looking at capsys.readouterr()
-
-- allow to specify prefixes starting with "_" when
-  customizing python_functions test discovery. (thanks Graham Horler)
-
-- improve PYTEST_DEBUG tracing output by puting
-  extra data on a new lines with additional indent
-
-- ensure OutcomeExceptions like skip/fail have initialized exception attributes
-
-- issue 260 - don't use nose special setup on plain unittest cases
-
-- fix issue134 - print the collect errors that prevent running specified test items
-
-- fix issue266 - accept unicode in MarkEvaluator expressions
-
-2.3.4
-=====
-
-- yielded test functions will now have autouse-fixtures active but
-  cannot accept fixtures as funcargs - it's anyway recommended to
-  rather use the post-2.0 parametrize features instead of yield, see:
-  http://pytest.org/latest/example/parametrize.html
-- fix autouse-issue where autouse-fixtures would not be discovered
-  if defined in a a/conftest.py file and tests in a/tests/test_some.py
-- fix issue226 - LIFO ordering for fixture teardowns
-- fix issue224 - invocations with >256 char arguments now work
-- fix issue91 - add/discuss package/directory level setups in example
-- allow to dynamically define markers via
-  item.keywords[...]=assignment integrating with "-m" option
-- make "-k" accept an expressions the same as with "-m" so that one
-  can write: -k "name1 or name2" etc.  This is a slight incompatibility
-  if you used special syntax like "TestClass.test_method" which you now
-  need to write as -k "TestClass and test_method" to match a certain
-  method in a certain test class.
-
-2.3.3
-=====
-
-- fix issue214 - parse modules that contain special objects like e. g.
-  flask's request object which blows up on getattr access if no request
-  is active. thanks Thomas Waldmann.
-
-- fix issue213 - allow to parametrize with values like numpy arrays that
-  do not support an __eq__ operator
-
-- fix issue215 - split test_python.org into multiple files
-
-- fix issue148 - @unittest.skip on classes is now recognized and avoids
-  calling setUpClass/tearDownClass, thanks Pavel Repin
-
-- fix issue209 - reintroduce python2.4 support by depending on newer
-  pylib which re-introduced statement-finding for pre-AST interpreters
-
-- nose support: only call setup if its a callable, thanks Andrew
-  Taumoefolau
-
-- fix issue219 - add py2.4-3.3 classifiers to TROVE list
-
-- in tracebacks *,** arg values are now shown next to normal arguments
-  (thanks Manuel Jacob)
-
-- fix issue217 - support mock.patch with pytest's fixtures - note that
-  you need either mock-1.0.1 or the python3.3 builtin unittest.mock.
-
-- fix issue127 - improve documentation for pytest_addoption() and
-  add a ``config.getoption(name)`` helper function for consistency.
-
-2.3.2
-=====
-
-- fix issue208 and fix issue29 use new py version to avoid long pauses
-  when printing tracebacks in long modules
-
-- fix issue205 - conftests in subdirs customizing
-  pytest_pycollect_makemodule and pytest_pycollect_makeitem
-  now work properly
-
-- fix teardown-ordering for parametrized setups
-
-- fix issue127 - better documentation for pytest_addoption
-  and related objects.
-
-- fix unittest behaviour: TestCase.runtest only called if there are
-  test methods defined
-
-- improve trial support: don't collect its empty
-  unittest.TestCase.runTest() method
-
-- "python setup.py test" now works with pytest itself
-
-- fix/improve internal/packaging related bits:
-
-  - exception message check of test_nose.py now passes on python33 as well
-
-  - issue206 - fix test_assertrewrite.py to work when a global
-    PYTHONDONTWRITEBYTECODE=1 is present
-
-  - add tox.ini to pytest distribution so that ignore-dirs and others config
-    bits are properly distributed for maintainers who run pytest-own tests
-
-2.3.1
-=====
-
-- fix issue202 - fix regression: using "self" from fixture functions now
-  works as expected (it's the same "self" instance that a test method
-  which uses the fixture sees)
-
-- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems
-  due to pexpect not supporting it properly (hanging)
-
-- link to web pages from --markers output which provides help for
-  pytest.mark.* usage.
-
-2.3.0
-=====
-
-- fix issue202 - better automatic names for parametrized test functions
-- fix issue139 - introduce @pytest.fixture which allows direct scoping
-  and parametrization of funcarg factories.
-- fix issue198 - conftest fixtures were not found on windows32 in some
-  circumstances with nested directory structures due to path manipulation issues
-- fix issue193 skip test functions with were parametrized with empty
-  parameter sets
-- fix python3.3 compat, mostly reporting bits that previously depended
-  on dict ordering
-- introduce re-ordering of tests by resource and parametrization setup
-  which takes precedence to the usual file-ordering
-- fix issue185 monkeypatching time.time does not cause pytest to fail
-- fix issue172 duplicate call of pytest.fixture decoratored setup_module
-  functions
-- fix junitxml=path construction so that if tests change the
-  current working directory and the path is a relative path
-  it is constructed correctly from the original current working dir.
-- fix "python setup.py test" example to cause a proper "errno" return
-- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
-- catch unicode-issues when writing failure representations
-  to terminal to prevent the whole session from crashing
-- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
-  will now take precedence before xfail-markers because we
-  can't determine xfail/xpass status in case of a skip. see also:
-  http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
-
-- always report installed 3rd party plugins in the header of a test run
-
-- fix issue160: a failing setup of an xfail-marked tests should
-  be reported as xfail (not xpass)
-
-- fix issue128: show captured output when capsys/capfd are used
-
-- fix issue179: propperly show the dependency chain of factories
-
-- pluginmanager.register(...) now raises ValueError if the
-  plugin has been already registered or the name is taken
-
-- fix issue159: improve http://pytest.org/latest/faq.html
-  especially with respect to the "magic" history, also mention
-  pytest-django, trial and unittest integration.
-
-- make request.keywords and node.keywords writable.  All descendant
-  collection nodes will see keyword values.  Keywords are dictionaries
-  containing markers and other info.
-
-- fix issue 178: xml binary escapes are now wrapped in py.xml.raw
-
-- fix issue 176: correctly catch the builtin AssertionError
-  even when we replaced AssertionError with a subclass on the
-  python level
-
-- factory discovery no longer fails with magic global callables
-  that provide no sane __code__ object (mock.call for example)
-
-- fix issue 182: testdir.inprocess_run now considers passed plugins
-
-- fix issue 188: ensure sys.exc_info is clear on python2
-                 before calling into a test
-
-- fix issue 191: add unittest TestCase runTest method support
-- fix issue 156: monkeypatch correctly handles class level descriptors
-
-- reporting refinements:
-
-  - pytest_report_header now receives a "startdir" so that
-    you can use startdir.bestrelpath(yourpath) to show
-    nice relative path
-
-  - allow plugins to implement both pytest_report_header and
-    pytest_sessionstart (sessionstart is invoked first).
-
-  - don't show deselected reason line if there is none
-
-  - py.test -vv will show all of assert comparisations instead of truncating
-
-2.2.4
-=====
-
-- fix error message for rewritten assertions involving the % operator
-- fix issue 126: correctly match all invalid xml characters for junitxml
-  binary escape
-- fix issue with unittest: now @unittest.expectedFailure markers should
-  be processed correctly (you can also use @pytest.mark markers)
-- document integration with the extended distribute/setuptools test commands
-- fix issue 140: propperly get the real functions
-  of bound classmethods for setup/teardown_class
-- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
-- fix issue #143: call unconfigure/sessionfinish always when
-  configure/sessionstart where called
-- fix issue #144: better mangle test ids to junitxml classnames
-- upgrade distribute_setup.py to 0.6.27
-
-2.2.3
-=====
-
-- fix uploaded package to only include neccesary files
-
-2.2.2
-=====
-
-- fix issue101: wrong args to unittest.TestCase test function now
-  produce better output
-- fix issue102: report more useful errors and hints for when a
-  test directory was renamed and some pyc/__pycache__ remain
-- fix issue106: allow parametrize to be applied multiple times
-  e.g. from module, class and at function level.
-- fix issue107: actually perform session scope finalization
-- don't check in parametrize if indirect parameters are funcarg names
-- add chdir method to monkeypatch funcarg
-- fix crash resulting from calling monkeypatch undo a second time
-- fix issue115: make --collectonly robust against early failure
-  (missing files/directories)
-- "-qq --collectonly" now shows only files and the number of tests in them
-- "-q --collectonly" now shows test ids
-- allow adding of attributes to test reports such that it also works
-  with distributed testing (no upgrade of pytest-xdist needed)
-
-2.2.1
-=====
-
-- fix issue99 (in pytest and py) internallerrors with resultlog now
-  produce better output - fixed by normalizing pytest_internalerror
-  input arguments.
-- fix issue97 / traceback issues (in pytest and py) improve traceback output
-  in conjunction with jinja2 and cython which hack tracebacks
-- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
-  the final test in a test node will now run its teardown directly
-  instead of waiting for the end of the session. Thanks Dave Hunt for
-  the good reporting and feedback.  The pytest_runtest_protocol as well
-  as the pytest_runtest_teardown hooks now have "nextitem" available
-  which will be None indicating the end of the test run.
-- fix collection crash due to unknown-source collected items, thanks
-  to Ralf Schmitt (fixed by depending on a more recent pylib)
-
-2.2.0
-=====
-
-- fix issue90: introduce eager tearing down of test items so that
-  teardown function are called earlier.
-- add an all-powerful metafunc.parametrize function which allows to
-  parametrize test function arguments in multiple steps and therefore
-  from indepdenent plugins and palces.
-- add a @pytest.mark.parametrize helper which allows to easily
-  call a test function with different argument values
-- Add examples to the "parametrize" example page, including a quick port
-  of Test scenarios and the new parametrize function and decorator.
-- introduce registration for "pytest.mark.*" helpers via ini-files
-  or through plugin hooks.  Also introduce a "--strict" option which
-  will treat unregistered markers as errors
-  allowing to avoid typos and maintain a well described set of markers
-  for your test suite.  See exaples at http://pytest.org/latest/mark.html
-  and its links.
-- issue50: introduce "-m marker" option to select tests based on markers
-  (this is a stricter and more predictable version of '-k' in that "-m"
-  only matches complete markers and has more obvious rules for and/or
-  semantics.
-- new feature to help optimizing the speed of your tests:
-  --durations=N option for displaying N slowest test calls
-  and setup/teardown methods.
-- fix issue87: --pastebin now works with python3
-- fix issue89: --pdb with unexpected exceptions in doctest work more sensibly
-- fix and cleanup pytest's own test suite to not leak FDs
-- fix issue83: link to generated funcarg list
-- fix issue74: pyarg module names are now checked against imp.find_module false positives
-- fix compatibility with twisted/trial-11.1.0 use cases
-- simplify Node.listchain
-- simplify junitxml output code by relying on py.xml
-- add support for skip properties on unittest classes and functions
-
-2.1.3
-=====
-
-- fix issue79: assertion rewriting failed on some comparisons in boolops
-- correctly handle zero length arguments (a la pytest '')
-- fix issue67 / junitxml now contains correct test durations, thanks ronny
-- fix issue75 / skipping test failure on jython
-- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
-
-2.1.2
-=====
-
-- fix assertion rewriting on files with windows newlines on some Python versions
-- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
-- fix issue69 / assertion rewriting fixed on some boolean operations
-- fix issue68 / packages now work with assertion rewriting
-- fix issue66: use different assertion rewriting caches when the -O option is passed
-- don't try assertion rewriting on Jython, use reinterp
-
-2.1.1
-=====
-
-- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
-- fix issue60 / fix error conditions involving the creation of __pycache__
-- fix issue63 / assertion rewriting on inserts involving strings containing '%'
-- fix assertion rewriting on calls with a ** arg
-- don't cache rewritten modules if bytecode generation is disabled
-- fix assertion rewriting in read-only directories
-- fix issue59: provide system-out/err tags for junitxml output
-- fix issue61: assertion rewriting on boolean operations with 3 or more operands
-- you can now build a man page with "cd doc ; make man"
-
-2.1.0
-=====
-
-- fix issue53 call nosestyle setup functions with correct ordering
-- fix issue58 and issue59: new assertion code fixes
-- merge Benjamin's assertionrewrite branch: now assertions
-  for test modules on python 2.6 and above are done by rewriting
-  the AST and saving the pyc file before the test module is imported.
-  see doc/assert.txt for more info.
-- fix issue43: improve doctests with better traceback reporting on
-  unexpected exceptions
-- fix issue47: timing output in junitxml for test cases is now correct
-- fix issue48: typo in MarkInfo repr leading to exception
-- fix issue49: avoid confusing error when initizaliation partially fails
-- fix issue44: env/username expansion for junitxml file path
-- show releaselevel information in test runs for pypy
-- reworked doc pages for better navigation and PDF generation
-- report KeyboardInterrupt even if interrupted during session startup
-- fix issue 35 - provide PDF doc version and download link from index page
-
-2.0.3
-=====
-
-- fix issue38: nicer tracebacks on calls to hooks, particularly early
-  configure/sessionstart ones
-
-- fix missing skip reason/meta information in junitxml files, reported
-  via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html
-
-- fix issue34: avoid collection failure with "test" prefixed classes
-  deriving from object.
-
-- don't require zlib (and other libs) for genscript plugin without
-  --genscript actually being used.
-
-- speed up skips (by not doing a full traceback represenation
-  internally)
-
-- fix issue37: avoid invalid characters in junitxml's output
-
-2.0.2
-=====
-
-- tackle issue32 - speed up test runs of very quick test functions
-  by reducing the relative overhead
-
-- fix issue30 - extended xfail/skipif handling and improved reporting.
-  If you have a syntax error in your skip/xfail
-  expressions you now get nice error reports.
-
-  Also you can now access module globals from xfail/skipif
-  expressions so that this for example works now::
-
-    import pytest
-    import mymodule
-    @pytest.mark.skipif("mymodule.__version__[0] == "1")
-    def test_function():
-        pass
-
-  This will not run the test function if the module's version string
-  does not start with a "1".  Note that specifying a string instead
-  of a boolean expressions allows py.test to report meaningful information
-  when summarizing a test run as to what conditions lead to skipping
-  (or xfail-ing) tests.
-
-- fix issue28 - setup_method and pytest_generate_tests work together
-  The setup_method fixture method now gets called also for
-  test function invocations generated from the pytest_generate_tests
-  hook.
-
-- fix issue27 - collectonly and keyword-selection (-k) now work together
-  Also, if you do "py.test --collectonly -q" you now get a flat list
-  of test ids that you can use to paste to the py.test commandline
-  in order to execute a particular test.
-
-- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
-
-- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
-  Starting with Python3.2 os.symlink may be supported. By requiring
-  a newer py lib version the py.path.local() implementation acknowledges
-  this.
-
-- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
-  thanks to Laura Creighton who also revieved parts of the documentation.
-
-- fix slighly wrong output of verbose progress reporting for classes
-  (thanks Amaury)
-
-- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
-
-- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
-
-2.0.1
-=====
-
-- refine and unify initial capturing so that it works nicely
-  even if the logging module is used on an early-loaded conftest.py
-  file or plugin.
-- allow to omit "()" in test ids to allow for uniform test ids
-  as produced by Alfredo's nice pytest.vim plugin.
-- fix issue12 - show plugin versions with "--version" and
-  "--traceconfig" and also document how to add extra information
-  to reporting test header
-- fix issue17 (import-* reporting issue on python3) by
-  requiring py>1.4.0 (1.4.1 is going to include it)
-- fix issue10 (numpy arrays truth checking) by refining
-  assertion interpretation in py lib
-- fix issue15: make nose compatibility tests compatible
-  with python3 (now that nose-1.0 supports python3)
-- remove somewhat surprising "same-conftest" detection because
-  it ignores conftest.py when they appear in several subdirs.
-- improve assertions ("not in"), thanks Floris Bruynooghe
-- improve behaviour/warnings when running on top of "python -OO"
-  (assertions and docstrings are turned off, leading to potential
-  false positives)
-- introduce a pytest_cmdline_processargs(args) hook
-  to allow dynamic computation of command line arguments.
-  This fixes a regression because py.test prior to 2.0
-  allowed to set command line options from conftest.py
-  files which so far pytest-2.0 only allowed from ini-files now.
-- fix issue7: assert failures in doctest modules.
-  unexpected failures in doctests will not generally
-  show nicer, i.e. within the doctest failing context.
-- fix issue9: setup/teardown functions for an xfail-marked
-  test will report as xfail if they fail but report as normally
-  passing (not xpassing) if they succeed.  This only is true
-  for "direct" setup/teardown invocations because teardown_class/
-  teardown_module cannot closely relate to a single test.
-- fix issue14: no logging errors at process exit
-- refinements to "collecting" output on non-ttys
-- refine internal plugin registration and --traceconfig output
-- introduce a mechanism to prevent/unregister plugins from the
-  command line, see http://pytest.org/plugins.html#cmdunregister
-- activate resultlog plugin by default
-- fix regression wrt yielded tests which due to the
-  collection-before-running semantics were not
-  setup as with pytest 1.3.4.  Note, however, that
-  the recommended and much cleaner way to do test
-  parametraization remains the "pytest_generate_tests"
-  mechanism, see the docs.
-
-2.0.0
-=====
-
-- pytest-2.0 is now its own package and depends on pylib-2.0
-- new ability: python -m pytest / python -m pytest.main ability
-- new python invcation: pytest.main(args, plugins) to load
-  some custom plugins early.
-- try harder to run unittest test suites in a more compatible manner
-  by deferring setup/teardown semantics to the unittest package.
-  also work harder to run twisted/trial and Django tests which
-  should now basically work by default.
-- introduce a new way to set config options via ini-style files,
-  by default setup.cfg and tox.ini files are searched.  The old
-  ways (certain environment variables, dynamic conftest.py reading
-  is removed).
-- add a new "-q" option which decreases verbosity and prints a more
-  nose/unittest-style "dot" output.
-- fix issue135 - marks now work with unittest test cases as well
-- fix issue126 - introduce py.test.set_trace() to trace execution via
-  PDB during the running of tests even if capturing is ongoing.
-- fix issue123 - new "python -m py.test" invocation for py.test
-  (requires Python 2.5 or above)
-- fix issue124 - make reporting more resilient against tests opening
-  files on filedescriptor 1 (stdout).
-- fix issue109 - sibling conftest.py files will not be loaded.
-  (and Directory collectors cannot be customized anymore from a Directory's
-  conftest.py - this needs to happen at least one level up).
-- introduce (customizable) assertion failure representations and enhance
-  output on assertion failures for comparisons and other cases (Floris Bruynooghe)
-- nose-plugin: pass through type-signature failures in setup/teardown
-  functions instead of not calling them (Ed Singleton)
-- remove py.test.collect.Directory (follows from a major refactoring
-  and simplification of the collection process)
-- majorly reduce py.test core code, shift function/python testing to own plugin
-- fix issue88 (finding custom test nodes from command line arg)
-- refine 'tmpdir' creation, will now create basenames better associated
-  with test names (thanks Ronny)
-- "xpass" (unexpected pass) tests don't cause exitcode!=0
-- fix issue131 / issue60 - importing doctests in __init__ files used as namespace packages
-- fix issue93 stdout/stderr is captured while importing conftest.py
-- fix bug: unittest collected functions now also can have "pytestmark"
-  applied at class/module level
-- add ability to use "class" level for cached_setup helper
-- fix strangeness: mark.* objects are now immutable, create new instances
-
-1.3.4
-=====
-
-- fix issue111: improve install documentation for windows
-- fix issue119: fix custom collectability of __init__.py as a module
-- fix issue116: --doctestmodules work with __init__.py files as well
-- fix issue115: unify internal exception passthrough/catching/GeneratorExit
-- fix issue118: new --tb=native for presenting cpython-standard exceptions
-
-1.3.3
-=====
-
-- fix issue113: assertion representation problem with triple-quoted strings
-  (and possibly other cases)
-- make conftest loading detect that a conftest file with the same
-  content was already loaded, avoids surprises in nested directory structures
-  which can be produced e.g. by Hudson. It probably removes the need to use
-  --confcutdir in most cases.
-- fix terminal coloring for win32
-  (thanks Michael Foord for reporting)
-- fix weirdness: make terminal width detection work on stdout instead of stdin
-  (thanks Armin Ronacher for reporting)
-- remove trailing whitespace in all py/text distribution files
-
-1.3.2
-=====
-
-**New features**
-
-- fix issue103:  introduce py.test.raises as context manager, examples::
-
-    with py.test.raises(ZeroDivisionError):
-        x = 0
-        1 / x
-
-    with py.test.raises(RuntimeError) as excinfo:
-        call_something()
-
-    # you may do extra checks on excinfo.value|type|traceback here
-
-  (thanks Ronny Pfannschmidt)
-
-- Funcarg factories can now dynamically apply a marker to a
-  test invocation.  This is for example useful if a factory
-  provides parameters to a test which are expected-to-fail::
-
-    def pytest_funcarg__arg(request):
-        request.applymarker(py.test.mark.xfail(reason="flaky config"))
-        ...
-
-    def test_function(arg):
-        ...
-
-- improved error reporting on collection and import errors. This makes
-  use of a more general mechanism, namely that for custom test item/collect
-  nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
-  override it to return a string error representation of your choice
-  which is going to be reported as a (red) string.
-
-- introduce '--junitprefix=STR' option to prepend a prefix
-  to all reports in the junitxml file.
-
-**Bug fixes**
-
-- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
-  to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
-  you can properly check for their existence in a cross-python manner).
-- refine --pdb: ignore xfailed tests, unify its TB-reporting and
-  don't display failures again at the end.
-- fix assertion interpretation with the ** operator (thanks Benjamin Peterson)
-- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson)
-- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous)
-- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
-- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
-- fix py.code.compile(source) to generate unique filenames
-- fix assertion re-interp problems on PyPy, by defering code
-  compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
-- fix py.path.local.pyimport() to work with directories
-- streamline py.path.local.mkdtemp implementation and usage
-- don't print empty lines when showing junitxml-filename
-- add optional boolean ignore_errors parameter to py.path.local.remove
-- fix terminal writing on win32/python2.4
-- py.process.cmdexec() now tries harder to return properly encoded unicode objects
-  on all python versions
-- install plain py.test/py.which scripts also for Jython, this helps to
-  get canonical script paths in virtualenv situations
-- make path.bestrelpath(path) return ".", note that when calling
-  X.bestrelpath the assumption is that X is a directory.
-- make initial conftest discovery ignore "--" prefixed arguments
-- fix resultlog plugin when used in an multicpu/multihost xdist situation
-  (thanks Jakub Gustak)
-- perform distributed testing related reporting in the xdist-plugin
-  rather than having dist-related code in the generic py.test
-  distribution
-- fix homedir detection on Windows
-- ship distribute_setup.py version 0.6.13
-
-1.3.1
-=====
-
-**New features**
-
-- issue91: introduce new py.test.xfail(reason) helper
-  to imperatively mark a test as expected to fail. Can
-  be used from within setup and test functions. This is
-  useful especially for parametrized tests when certain
-  configurations are expected-to-fail.  In this case the
-  declarative approach with the @py.test.mark.xfail cannot
-  be used as it would mark all configurations as xfail.
-
-- issue102: introduce new --maxfail=NUM option to stop
-  test runs after NUM failures.  This is a generalization
-  of the '-x' or '--exitfirst' option which is now equivalent
-  to '--maxfail=1'.  Both '-x' and '--maxfail' will
-  now also print a line near the end indicating the Interruption.
-
-- issue89: allow py.test.mark decorators to be used on classes
-  (class decorators were introduced with python2.6) and
-  also allow to have multiple markers applied at class/module level
-  by specifying a list.
-
-- improve and refine letter reporting in the progress bar:
-  .  pass
-  f  failed test
-  s  skipped tests (reminder: use for dependency/platform mismatch only)
-  x  xfailed test (test that was expected to fail)
-  X  xpassed test (test that was expected to fail but passed)
-
-  You can use any combination of 'fsxX' with the '-r' extended
-  reporting option. The xfail/xpass results will show up as
-  skipped tests in the junitxml output - which also fixes
-  issue99.
-
-- make py.test.cmdline.main() return the exitstatus instead of raising
-  SystemExit and also allow it to be called multiple times.  This of
-  course requires that your application and tests are properly teared
-  down and don't have global state.
-
-**Bug Fixes**
-
-- improved traceback presentation:
-  - improved and unified reporting for "--tb=short" option
-  - Errors during test module imports are much shorter, (using --tb=short style)
-  - raises shows shorter more relevant tracebacks
-  - --fulltrace now more systematically makes traces longer / inhibits cutting
-
-- improve support for raises and other dynamically compiled code by
-  manipulating python's linecache.cache instead of the previous
-  rather hacky way of creating custom code objects.  This makes
-  it seemlessly work on Jython and PyPy where it previously didn't.
-
-- fix issue96: make capturing more resilient against Control-C
-  interruptions (involved somewhat substantial refactoring
-  to the underlying capturing functionality to avoid race
-  conditions).
-
-- fix chaining of conditional skipif/xfail decorators - so it works now
-  as expected to use multiple @py.test.mark.skipif(condition) decorators,
-  including specific reporting which of the conditions lead to skipping.
-
-- fix issue95: late-import zlib so that it's not required
-  for general py.test startup.
-
-- fix issue94: make reporting more robust against bogus source code
-  (and internally be more careful when presenting unexpected byte sequences)
-
-
-1.3.0
-=====
-
-- deprecate --report option in favour of a new shorter and easier to
-  remember -r option: it takes a string argument consisting of any
-  combination of 'xfsX' characters.  They relate to the single chars
-  you see during the dotted progress printing and will print an extra line
-  per test at the end of the test run.  This extra line indicates the exact
-  position or test ID that you directly paste to the py.test cmdline in order
-  to re-run a particular test.
-
-- allow external plugins to register new hooks via the new
-  pytest_addhooks(pluginmanager) hook.  The new release of
-  the pytest-xdist plugin for distributed and looponfailing
-  testing requires this feature.
-
-- add a new pytest_ignore_collect(path, config) hook to allow projects and
-  plugins to define exclusion behaviour for their directory structure -
-  for example you may define in a conftest.py this method::
-
-        def pytest_ignore_collect(path):
-            return path.check(link=1)
-
-  to prevent even a collection try of any tests in symlinked dirs.
-
-- new pytest_pycollect_makemodule(path, parent) hook for
-  allowing customization of the Module collection object for a
-  matching test module.
-
-- extend and refine xfail mechanism:
-  ``@py.test.mark.xfail(run=False)`` do not run the decorated test
-  ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries
-  specifiying ``--runxfail`` on command line virtually ignores xfail markers
-
-- expose (previously internal) commonly useful methods:
-  py.io.get_terminal_with() -> return terminal width
-  py.io.ansi_print(...) -> print colored/bold text on linux/win32
-  py.io.saferepr(obj) -> return limited representation string
-
-- expose test outcome related exceptions as py.test.skip.Exception,
-  py.test.raises.Exception etc., useful mostly for plugins
-  doing special outcome interpretation/tweaking
-
-- (issue85) fix junitxml plugin to handle tests with non-ascii output
-
-- fix/refine python3 compatibility (thanks Benjamin Peterson)
-
-- fixes for making the jython/win32 combination work, note however:
-  jython2.5.1/win32 does not provide a command line launcher, see
-  http://bugs.jython.org/issue1491 . See pylib install documentation
-  for how to work around.
-
-- fixes for handling of unicode exception values and unprintable objects
-
-- (issue87) fix unboundlocal error in assertionold code
-
-- (issue86) improve documentation for looponfailing
-
-- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
-
-- ship distribute_setup.py version 0.6.10
-
-- added links to the new capturelog and coverage plugins
-
-
-1.2.0
-=====
-
-- refined usage and options for "py.cleanup"::
-
-    py.cleanup     # remove "*.pyc" and "*$py.class" (jython) files
-    py.cleanup -e .swp -e .cache # also remove files with these extensions
-    py.cleanup -s  # remove "build" and "dist" directory next to setup.py files
-    py.cleanup -d  # also remove empty directories
-    py.cleanup -a  # synonym for "-s -d -e 'pip-log.txt'"
-    py.cleanup -n  # dry run, only show what would be removed
-
-- add a new option "py.test --funcargs" which shows available funcargs
-  and their help strings (docstrings on their respective factory function)
-  for a given test path
-
-- display a short and concise traceback if a funcarg lookup fails
-
-- early-load "conftest.py" files in non-dot first-level sub directories.
-  allows to conveniently keep and access test-related options in a ``test``
-  subdir and still add command line options.
-
-- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
-
-- fix issue78: always call python-level teardown functions even if the
-  according setup failed.  This includes refinements for calling setup_module/class functions
-  which will now only be called once instead of the previous behaviour where they'd be called
-  multiple times if they raise an exception (including a Skipped exception).  Any exception
-  will be re-corded and associated with all tests in the according module/class scope.
-
-- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
-
-- fix pdb debugging to be in the correct frame on raises-related errors
-
-- update apipkg.py to fix an issue where recursive imports might
-  unnecessarily break importing
-
-- fix plugin links
-
-1.1.1
-=====
-
-- moved dist/looponfailing from py.test core into a new
-  separately released pytest-xdist plugin.
-
-- new junitxml plugin: --junitxml=path will generate a junit style xml file
-  which is processable e.g. by the Hudson CI system.
-
-- new option: --genscript=path will generate a standalone py.test script
-  which will not need any libraries installed.  thanks to Ralf Schmitt.
-
-- new option: --ignore will prevent specified path from collection.
-  Can be specified multiple times.
-
-- new option: --confcutdir=dir will make py.test only consider conftest
-  files that are relative to the specified dir.
-
-- new funcarg: "pytestconfig" is the pytest config object for access
-  to command line args and can now be easily used in a test.
-
-- install ``py.test`` and ``py.which`` with a ``-$VERSION`` suffix to
-  disambiguate between Python3, python2.X, Jython and PyPy installed versions.
-
-- new "pytestconfig" funcarg allows access to test config object
-
-- new "pytest_report_header" hook can return additional lines
-  to be displayed at the header of a test run.
-
-- (experimental) allow "py.test path::name1::name2::..." for pointing
-  to a test within a test collection directly.  This might eventually
-  evolve as a full substitute to "-k" specifications.
-
-- streamlined plugin loading: order is now as documented in
-  customize.html: setuptools, ENV, commandline, conftest.
-  also setuptools entry point names are turned to canonical namees ("pytest_*")
-
-- automatically skip tests that need 'capfd' but have no os.dup
-
-- allow pytest_generate_tests to be defined in classes as well
-
-- deprecate usage of 'disabled' attribute in favour of pytestmark
-- deprecate definition of Directory, Module, Class and Function nodes
-  in conftest.py files.  Use pytest collect hooks instead.
-
-- collection/item node specific runtest/collect hooks are only called exactly
-  on matching conftest.py files, i.e. ones which are exactly below
-  the filesystem path of an item
-
-- change: the first pytest_collect_directory hook to return something
-  will now prevent further hooks to be called.
-
-- change: figleaf plugin now requires --figleaf to run.  Also
-  change its long command line options to be a bit shorter (see py.test -h).
-
-- change: pytest doctest plugin is now enabled by default and has a
-  new option --doctest-glob to set a pattern for file matches.
-
-- change: remove internal py._* helper vars, only keep py._pydir
-
-- robustify capturing to survive if custom pytest_runtest_setup
-  code failed and prevented the capturing setup code from running.
-
-- make py.test.* helpers provided by default plugins visible early -
-  works transparently both for pydoc and for interactive sessions
-  which will regularly see e.g. py.test.mark and py.test.importorskip.
-
-- simplify internal plugin manager machinery
-- simplify internal collection tree by introducing a RootCollector node
-
-- fix assert reinterpreation that sees a call containing "keyword=..."
-
-- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
-  hooks on slaves during dist-testing, report module/session teardown
-  hooks correctly.
-
-- fix issue65: properly handle dist-testing if no
-  execnet/py lib installed remotely.
-
-- skip some install-tests if no execnet is available
-
-- fix docs, fix internal bin/ script generation
-
-
-1.1.0
-=====
-
-- introduce automatic plugin registration via 'pytest11'
-  entrypoints via setuptools' pkg_resources.iter_entry_points
-
-- fix py.test dist-testing to work with execnet >= 1.0.0b4
-
-- re-introduce py.test.cmdline.main() for better backward compatibility
-
-- svn paths: fix a bug with path.check(versioned=True) for svn paths,
-  allow '%' in svn paths, make svnwc.update() default to interactive mode
-  like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
-
-- refine distributed tarball to contain test and no pyc files
-
-- try harder to have deprecation warnings for py.compat.* accesses
-  report a correct location
-
-1.0.3
-=====
-
-* adjust and improve docs
-
-* remove py.rest tool and internal namespace - it was
-  never really advertised and can still be used with
-  the old release if needed.  If there is interest
-  it could be revived into its own tool i guess.
-
-* fix issue48 and issue59: raise an Error if the module
-  from an imported test file does not seem to come from
-  the filepath - avoids "same-name" confusion that has
-  been reported repeatedly
-
-* merged Ronny's nose-compatibility hacks: now
-  nose-style setup_module() and setup() functions are
-  supported
-
-* introduce generalized py.test.mark function marking
-
-* reshuffle / refine command line grouping
-
-* deprecate parser.addgroup in favour of getgroup which creates option group
-
-* add --report command line option that allows to control showing of skipped/xfailed sections
-
-* generalized skipping: a new way to mark python functions with skipif or xfail
-  at function, class and modules level based on platform or sys-module attributes.
-
-* extend py.test.mark decorator to allow for positional args
-
-* introduce and test "py.cleanup -d" to remove empty directories
-
-* fix issue #59 - robustify unittest test collection
-
-* make bpython/help interaction work by adding an __all__ attribute
-  to ApiModule, cleanup initpkg
-
-* use MIT license for pylib, add some contributors
-
-* remove py.execnet code and substitute all usages with 'execnet' proper
-
-* fix issue50 - cached_setup now caches more to expectations
-  for test functions with multiple arguments.
-
-* merge Jarko's fixes, issue #45 and #46
-
-* add the ability to specify a path for py.lookup to search in
-
-* fix a funcarg cached_setup bug probably only occuring
-  in distributed testing and "module" scope with teardown.
-
-* many fixes and changes for making the code base python3 compatible,
-  many thanks to Benjamin Peterson for helping with this.
-
-* consolidate builtins implementation to be compatible with >=2.3,
-  add helpers to ease keeping 2 and 3k compatible code
-
-* deprecate py.compat.doctest|subprocess|textwrap|optparse
-
-* deprecate py.magic.autopath, remove py/magic directory
-
-* move pytest assertion handling to py/code and a pytest_assertion
-  plugin, add "--no-assert" option, deprecate py.magic namespaces
-  in favour of (less) py.code ones.
-
-* consolidate and cleanup py/code classes and files
-
-* cleanup py/misc, move tests to bin-for-dist
-
-* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
-
-* consolidate py.log implementation, remove old approach.
-
-* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
-  text/unicode and byte-streams (uses underlying standard lib io.*
-  if available)
-
-* make py.unittest_convert helper script available which converts "unittest.py"
-  style files into the simpler assert/direct-test-classes py.test/nosetests
-  style.  The script was written by Laura Creighton.
-
-* simplified internal localpath implementation
-
-1.0.2
-=====
-
-* fixing packaging issues, triggered by fedora redhat packaging,
-  also added doc, examples and contrib dirs to the tarball.
-
-* added a documentation link to the new django plugin.
-
-1.0.1
-=====
-
-* added a 'pytest_nose' plugin which handles nose.SkipTest,
-  nose-style function/method/generator setup/teardown and
-  tries to report functions correctly.
-
-* capturing of unicode writes or encoded strings to sys.stdout/err
-  work better, also terminalwriting was adapted and somewhat
-  unified between windows and linux.
-
-* improved documentation layout and content a lot
-
-* added a "--help-config" option to show conftest.py / ENV-var names for
-  all longopt cmdline options, and some special conftest.py variables.
-  renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
-
-* fix issue #27: better reporting on non-collectable items given on commandline
-  (e.g. pyc files)
-
-* fix issue #33: added --version flag (thanks Benjamin Peterson)
-
-* fix issue #32: adding support for "incomplete" paths to wcpath.status()
-
-* "Test" prefixed classes are *not* collected by default anymore if they
-  have an __init__ method
-
-* monkeypatch setenv() now accepts a "prepend" parameter
-
-* improved reporting of collection error tracebacks
-
-* simplified multicall mechanism and plugin architecture,
-  renamed some internal methods and argnames
-
-1.0.0
-=====
-
-* more terse reporting try to show filesystem path relatively to current dir
-* improve xfail output a bit
-
-1.0.0b9
-=======
-
-* cleanly handle and report final teardown of test setup
-
-* fix svn-1.6 compat issue with py.path.svnwc().versioned()
-  (thanks Wouter Vanden Hove)
-
-* setup/teardown or collection problems now show as ERRORs
-  or with big "E"'s in the progress lines.  they are reported
-  and counted separately.
-
-* dist-testing: properly handle test items that get locally
-  collected but cannot be collected on the remote side - often
-  due to platform/dependency reasons
-
-* simplified py.test.mark API - see keyword plugin documentation
-
-* integrate better with logging: capturing now by default captures
-  test functions and their immediate setup/teardown in a single stream
-
-* capsys and capfd funcargs now have a readouterr() and a close() method
-  (underlyingly py.io.StdCapture/FD objects are used which grew a
-  readouterr() method as well to return snapshots of captured out/err)
-
-* make assert-reinterpretation work better with comparisons not
-  returning bools (reported with numpy from thanks maciej fijalkowski)
-
-* reworked per-test output capturing into the pytest_iocapture.py plugin
-  and thus removed capturing code from config object
-
-* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
-
-
-1.0.0b8
-=======
-
-* pytest_unittest-plugin is now enabled by default
-
-* introduced pytest_keyboardinterrupt hook and
-  refined pytest_sessionfinish hooked, added tests.
-
-* workaround a buggy logging module interaction ("closing already closed
-  files").  Thanks to Sridhar Ratnakumar for triggering.
-
-* if plugins use "py.test.importorskip" for importing
-  a dependency only a warning will be issued instead
-  of exiting the testing process.
-
-* many improvements to docs:
-  - refined funcargs doc , use the term "factory" instead of "provider"
-  - added a new talk/tutorial doc page
-  - better download page
-  - better plugin docstrings
-  - added new plugins page and automatic doc generation script
-
-* fixed teardown problem related to partially failing funcarg setups
-  (thanks MrTopf for reporting), "pytest_runtest_teardown" is now
-  always invoked even if the "pytest_runtest_setup" failed.
-
-* tweaked doctest output for docstrings in py modules,
-  thanks Radomir.
-
-1.0.0b7
-=======
-
-* renamed py.test.xfail back to py.test.mark.xfail to avoid
-  two ways to decorate for xfail
-
-* re-added py.test.mark decorator for setting keywords on functions
-  (it was actually documented so removing it was not nice)
-
-* remove scope-argument from request.addfinalizer() because
-  request.cached_setup has the scope arg. TOOWTDI.
-
-* perform setup finalization before reporting failures
-
-* apply modified patches from Andreas Kloeckner to allow
-  test functions to have no func_code (#22) and to make
-  "-k" and function keywords work  (#20)
-
-* apply patch from Daniel Peolzleithner (issue #23)
-
-* resolve issue #18, multiprocessing.Manager() and
-  redirection clash
-
-* make __name__ == "__channelexec__" for remote_exec code
-
-1.0.0b3
-=======
-
-* plugin classes are removed: one now defines
-  hooks directly in conftest.py or global pytest_*.py
-  files.
-
-* added new pytest_namespace(config) hook that allows
-  to inject helpers directly to the py.test.* namespace.
-
-* documented and refined many hooks
-
-* added new style of generative tests via
-  pytest_generate_tests hook that integrates
-  well with function arguments.
-
-
-1.0.0b1
-=======
-
-* introduced new "funcarg" setup method,
-  see doc/test/funcarg.txt
-
-* introduced plugin architecture and many
-  new py.test plugins, see
-  doc/test/plugins.txt
-
-* teardown_method is now guaranteed to get
-  called after a test method has run.
-
-* new method: py.test.importorskip(mod,minversion)
-  will either import or call py.test.skip()
-
-* completely revised internal py.test architecture
-
-* new py.process.ForkedFunc object allowing to
-  fork execution of a function to a sub process
-  and getting a result back.
-
-XXX lots of things missing here XXX
-
-0.9.2
-=====
-
-* refined installation and metadata, created new setup.py,
-  now based on setuptools/ez_setup (thanks to Ralf Schmitt
-  for his support).
-
-* improved the way of making py.* scripts available in
-  windows environments, they are now added to the
-  Scripts directory as ".cmd" files.
-
-* py.path.svnwc.status() now is more complete and
-  uses xml output from the 'svn' command if available
-  (Guido Wesdorp)
-
-* fix for py.path.svn* to work with svn 1.5
-  (Chris Lamb)
-
-* fix path.relto(otherpath) method on windows to
-  use normcase for checking if a path is relative.
-
-* py.test's traceback is better parseable from editors
-  (follows the filenames:LINENO: MSG convention)
-  (thanks to Osmo Salomaa)
-
-* fix to javascript-generation, "py.test --runbrowser"
-  should work more reliably now
-
-* removed previously accidentally added
-  py.test.broken and py.test.notimplemented helpers.
-
-* there now is a py.__version__ attribute
-
-0.9.1
-=====
-
-This is a fairly complete list of v0.9.1, which can
-serve as a reference for developers.
-
-* allowing + signs in py.path.svn urls [39106]
-* fixed support for Failed exceptions without excinfo in py.test [39340]
-* added support for killing processes for Windows (as well as platforms that
-  support os.kill) in py.misc.killproc [39655]
-* added setup/teardown for generative tests to py.test [40702]
-* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739]
-* fixed problem with calling .remove() on wcpaths of non-versioned files in
-  py.path [44248]
-* fixed some import and inheritance issues in py.test [41480, 44648, 44655]
-* fail to run greenlet tests when pypy is available, but without stackless
-  [45294]
-* small fixes in rsession tests [45295]
-* fixed issue with 2.5 type representations in py.test [45483, 45484]
-* made that internal reporting issues displaying is done atomically in py.test
-  [45518]
-* made that non-existing files are igored by the py.lookup script [45519]
-* improved exception name creation in py.test [45535]
-* made that less threads are used in execnet [merge in 45539]
-* removed lock required for atomical reporting issue displaying in py.test
-  [45545]
-* removed globals from execnet [45541, 45547]
-* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit
-  get called in 2.5 (py.execnet) [45548]
-* fixed bug in joining threads in py.execnet's servemain [45549]
-* refactored py.test.rsession tests to not rely on exact output format anymore
-  [45646]
-* using repr() on test outcome [45647]
-* added 'Reason' classes for py.test.skip() [45648, 45649]
-* killed some unnecessary sanity check in py.test.collect [45655]
-* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only
-  usable by Administrators [45901]
-* added support for locking and non-recursive commits to py.path.svnwc [45994]
-* locking files in py.execnet to prevent CPython from segfaulting [46010]
-* added export() method to py.path.svnurl
-* fixed -d -x in py.test [47277]
-* fixed argument concatenation problem in py.path.svnwc [49423]
-* restore py.test behaviour that it exits with code 1 when there are failures
-  [49974]
-* don't fail on html files that don't have an accompanying .txt file [50606]
-* fixed 'utestconvert.py < input' [50645]
-* small fix for code indentation in py.code.source [50755]
-* fix _docgen.py documentation building [51285]
-* improved checks for source representation of code blocks in py.test [51292]
-* added support for passing authentication to py.path.svn* objects [52000,
-  52001]
-* removed sorted() call for py.apigen tests in favour of [].sort() to support
-  Python 2.3 [52481]
diff --git a/tools/pytest/CONTRIBUTING.rst b/tools/pytest/CONTRIBUTING.rst
deleted file mode 100644
index 75ee3ec..0000000
--- a/tools/pytest/CONTRIBUTING.rst
+++ /dev/null
@@ -1,253 +0,0 @@
-============================
-Contribution getting started
-============================
-
-Contributions are highly welcomed and appreciated.  Every little help counts,
-so do not hesitate!
-
-.. contents:: Contribution links
-   :depth: 2
-
-
-.. _submitfeedback:
-
-Feature requests and feedback
------------------------------
-
-Do you like pytest?  Share some love on Twitter or in your blog posts!
-
-We'd also like to hear about your propositions and suggestions.  Feel free to
-`submit them as issues <https://github.com/pytest-dev/pytest/issues>`_ and:
-
-* Explain in detail how they should work.
-* Keep the scope as narrow as possible.  This will make it easier to implement.
-
-
-.. _reportbugs:
-
-Report bugs
------------
-
-Report bugs for pytest in the `issue tracker <https://github.com/pytest-dev/pytest/issues>`_.
-
-If you are reporting a bug, please include:
-
-* Your operating system name and version.
-* Any details about your local setup that might be helpful in troubleshooting,
-  specifically Python interpreter version,
-  installed libraries and pytest version.
-* Detailed steps to reproduce the bug.
-
-If you can write a demonstration test that currently fails but should pass (xfail),
-that is a very useful commit to make as well, even if you can't find how
-to fix the bug yet.
-
-
-.. _fixbugs:
-
-Fix bugs
---------
-
-Look through the GitHub issues for bugs.  Here is sample filter you can use:
-https://github.com/pytest-dev/pytest/labels/bug
-
-:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
-
-Don't forget to check the issue trackers of your favourite plugins, too!
-
-.. _writeplugins:
-
-Implement features
-------------------
-
-Look through the GitHub issues for enhancements.  Here is sample filter you
-can use:
-https://github.com/pytest-dev/pytest/labels/enhancement
-
-:ref:`Talk <contact>` to developers to find out how you can implement specific
-features.
-
-Write documentation
--------------------
-
-pytest could always use more documentation.  What exactly is needed?
-
-* More complementary documentation.  Have you perhaps found something unclear?
-* Documentation translations.  We currently have only English.
-* Docstrings.  There can never be too many of them.
-* Blog posts, articles and such -- they're all very appreciated.
-
-You can also edit documentation files directly in the Github web interface
-without needing to make a fork and local copy. This can be convenient for
-small fixes.
-
-
-.. _submitplugin:
-
-Submitting Plugins to pytest-dev
---------------------------------
-
-Pytest development of the core, some plugins and support code happens
-in repositories living under the ``pytest-dev`` organisations:
-
-- `pytest-dev on GitHub <https://github.com/pytest-dev>`_
-
-- `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_
-
-All pytest-dev Contributors team members have write access to all contained
-repositories.  pytest core and plugins are generally developed
-using `pull requests`_ to respective repositories.
-
-The objectives of the ``pytest-dev`` organisation are:
-
-* Having a central location for popular pytest plugins
-* Sharing some of the maintenance responsibility (in case a maintainer no longer whishes to maintain a plugin)
-
-You can submit your plugin by subscribing to the `pytest-dev mail list
-<https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a
-mail pointing to your existing pytest plugin repository which must have
-the following:
-
-- PyPI presence with a ``setup.py`` that contains a license, ``pytest-``
-  prefixed name, version number, authors, short and long description.
-
-- a ``tox.ini`` for running tests using `tox <http://tox.testrun.org>`_.
-
-- a ``README.txt`` describing how to use the plugin and on which
-  platforms it runs.
-
-- a ``LICENSE.txt`` file or equivalent containing the licensing
-  information, with matching info in ``setup.py``.
-
-- an issue tracker for bug reports and enhancement requests.
-
-If no contributor strongly objects and two agree, the repository can then be
-transferred to the ``pytest-dev`` organisation.
-
-Here's a rundown of how a repository transfer usually proceeds
-(using a repository named ``joedoe/pytest-xyz`` as example):
-
-* One of the ``pytest-dev`` administrators creates:
-
-  - ``pytest-xyz-admin`` team, with full administration rights to
-    ``pytest-dev/pytest-xyz``.
-  - ``pytest-xyz-developers`` team, with write access to
-    ``pytest-dev/pytest-xyz``.
-
-* ``joedoe`` is invited to the ``pytest-xyz-admin`` team;
-
-* After accepting the invitation, ``joedoe`` transfers the repository from its
-  original location to ``pytest-dev/pytest-xyz`` (A nice feature is that GitHub handles URL redirection from
-  the old to the new location automatically).
-
-* ``joedoe`` is free to add any other collaborators to the
-  ``pytest-xyz-admin`` or ``pytest-xyz-developers`` team as desired.
-
-The ``pytest-dev/Contributors`` team has write access to all projects, and
-every project administrator is in it. We recommend that each plugin has at least three
-people who have the right to release to PyPI.
-
-Repository owners can be assured that no ``pytest-dev`` administrator will ever make
-releases of your repository or take ownership in any way, except in rare cases
-where someone becomes unresponsive after months of contact attempts.
-As stated, the objective is to share maintenance and avoid "plugin-abandon".
-
-
-.. _`pull requests`:
-.. _pull-requests:
-
-Preparing Pull Requests on GitHub
----------------------------------
-
-There's an excellent tutorial on how Pull Requests work in the
-`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_
-
-
-.. note::
-  What is a "pull request"?  It informs project's core developers about the
-  changes you want to review and merge.  Pull requests are stored on
-  `GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
-  Once you send pull request, we can discuss it's potential modifications and
-  even add more commits to it later on.
-
-There's an excellent tutorial on how Pull Requests work in the
-`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_,
-but here is a simple overview:
-
-#. Fork the
-   `pytest GitHub repository <https://github.com/pytest-dev/pytest>`__.  It's
-   fine to use ``pytest`` as your fork repository name because it will live
-   under your user.
-
-#. Clone your fork locally using `git <https://git-scm.com/>`_ and create a branch::
-
-    $ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
-    $ cd pytest
-    # now, to fix a bug create your own branch off "master":
-    
-        $ git checkout -b your-bugfix-branch-name master
-
-    # or to instead add a feature create your own branch off "features":
-    
-        $ git checkout -b your-feature-branch-name features
-
-   Given we have "major.minor.micro" version numbers, bugfixes will usually 
-   be released in micro releases whereas features will be released in 
-   minor releases and incompatible changes in major releases.
-
-   If you need some help with Git, follow this quick start
-   guide: https://git.wiki.kernel.org/index.php/QuickStart
-
-#. Install tox
-
-   Tox is used to run all the tests and will automatically setup virtualenvs
-   to run the tests in.
-   (will implicitly use http://www.virtualenv.org/en/latest/)::
-
-    $ pip install tox
-
-#. Run all the tests
-
-   You need to have Python 2.7 and 3.5 available in your system.  Now
-   running tests is as simple as issuing this command::
-
-    $ python runtox.py -e linting,py27,py35
-
-   This command will run tests via the "tox" tool against Python 2.7 and 3.5
-   and also perform "lint" coding-style checks.  ``runtox.py`` is
-   a thin wrapper around ``tox`` which installs from a development package
-   index where newer (not yet released to pypi) versions of dependencies
-   (especially ``py``) might be present.
-
-#. You can now edit your local working copy.
-
-   You can now make the changes you want and run the tests again as necessary.
-
-   To run tests on py27 and pass options to pytest (e.g. enter pdb on failure)
-   to pytest you can do::
-
-    $ python runtox.py -e py27 -- --pdb
-
-   or to only run tests in a particular test module on py35::
-
-    $ python runtox.py -e py35 -- testing/test_config.py
-
-#. Commit and push once your tests pass and you are happy with your change(s)::
-
-    $ git commit -a -m "<commit message>"
-    $ git push -u
-
-   Make sure you add a CHANGELOG message, and add yourself to AUTHORS. If you
-   are unsure about either of these steps, submit your pull request and we'll
-   help you fix it up.
-
-#. Finally, submit a pull request through the GitHub website using this data::
-
-    head-fork: YOUR_GITHUB_USERNAME/pytest
-    compare: your-branch-name
-
-    base-fork: pytest-dev/pytest
-    base: master          # if it's a bugfix
-    base: feature         # if it's a feature
-
-
diff --git a/tools/pytest/HOWTORELEASE.rst b/tools/pytest/HOWTORELEASE.rst
deleted file mode 100644
index 3ebfa28..0000000
--- a/tools/pytest/HOWTORELEASE.rst
+++ /dev/null
@@ -1,92 +0,0 @@
-How to release pytest
---------------------------------------------
-
-Note: this assumes you have already registered on pypi.
-
-0. create the branch release-VERSION
-   use features as base for minor/major releases
-   and master as base for bugfix releases
-
-1. Bump version numbers in _pytest/__init__.py (setup.py reads it)
-
-2. Check and finalize CHANGELOG
-
-3. Write doc/en/announce/release-VERSION.txt and include
-   it in doc/en/announce/index.txt::
-
-        git log 2.8.2..HEAD --format='%aN' | sort -u # lists the names of authors involved
-
-4. Use devpi for uploading a release tarball to a staging area::
-
-     devpi use https://devpi.net/USER/dev
-     devpi upload --formats sdist,bdist_wheel
-
-5. Run from multiple machines::
-
-     devpi use https://devpi.net/USER/dev
-     devpi test pytest==VERSION
-
-6. Check that tests pass for relevant combinations with::
-
-       devpi list pytest
-
-   or look at failures with "devpi list -f pytest".
-
-7. Regenerate the docs examples using tox, and check for regressions::
-
-      tox -e regen
-      git diff
-
-
-8. Build the docs, you need a virtualenv with py and sphinx
-   installed::
-
-      cd doc/en      
-      make html
-
-   Commit any changes before tagging the release.
-
-9. Tag the release::
-
-      git tag VERSION
-      git push
-
-10. Upload the docs using doc/en/Makefile::
-
-      cd doc/en
-      make install  # or "installall" if you have LaTeX installed for PDF
-
-    This requires ssh-login permission on pytest.org because it uses
-    rsync.
-    Note that the ``install`` target of ``doc/en/Makefile`` defines where the
-    rsync goes to, typically to the "latest" section of pytest.org.
-
-    If you are making a minor release (e.g. 5.4), you also need to manually
-    create a symlink for "latest"::
-
-       ssh pytest-dev@pytest.org
-       ln -s 5.4 latest
-
-    Browse to pytest.org to verify.
-
-11. Publish to pypi::
-
-      devpi push pytest-VERSION pypi:NAME
-
-    where NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
-    file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
-
-
-12. Send release announcement to mailing lists:
-
-    - pytest-dev
-    - testing-in-python
-    - python-announce-list@python.org
-
-
-13. **after the release** Bump the version number in ``_pytest/__init__.py``,
-    to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
-    set it to ``pytest-2.9.0.dev1``).
-
-14. merge the actual release into the master branch and do a pull request against it
-15. merge from master to features
diff --git a/tools/pytest/ISSUES.txt b/tools/pytest/ISSUES.txt
deleted file mode 100644
index 081d727..0000000
--- a/tools/pytest/ISSUES.txt
+++ /dev/null
@@ -1,365 +0,0 @@
-
-
-recorder = monkeypatch.function(".......")
--------------------------------------------------------------
-tags: nice feature 
-
-Like monkeypatch.replace but sets a mock-like call recorder:
-
-    recorder = monkeypatch.function("os.path.abspath")
-    recorder.set_return("/hello")
-    os.path.abspath("hello")
-    call, = recorder.calls
-    assert call.args.path == "hello"
-    assert call.returned == "/hello"
-    ...
-
-Unlike mock, "args.path" acts on the parsed auto-spec'ed ``os.path.abspath``
-so it's independent from if the client side called "os.path.abspath(path=...)"
-or "os.path.abspath('positional')".
-
-
-refine parametrize API
--------------------------------------------------------------
-tags: critical feature 
-
-extend metafunc.parametrize to directly support indirection, example:
-
-    def setupdb(request, config):
-        # setup "resource" based on test request and the values passed 
-        # in to parametrize.  setupfunc is called for each such value.
-        # you may use request.addfinalizer() or request.cached_setup ...
-        return dynamic_setup_database(val)
-
-    @pytest.mark.parametrize("db", ["pg", "mysql"], setupfunc=setupdb)
-    def test_heavy_functional_test(db):
-        ...
-
-There would be no need to write or explain funcarg factories and
-their special __ syntax.
-
-The examples and improvements should also show how to put the parametrize
-decorator to a class, to a module or even to a directory.  For the directory
-part a conftest.py content like this::
-
-    pytestmark = [
-        @pytest.mark.parametrize_setup("db", ...),
-    ]
-
-probably makes sense in order to keep the declarative nature.   This mirrors
-the marker-mechanism with respect to a test module but puts it to a directory
-scale.
-
-When doing larger scoped parametrization it probably becomes necessary
-to allow parametrization to be ignored if the according parameter is not
-used (currently any parametrized argument that is not present in a function will cause a ValueError). Example:
-
-        @pytest.mark.parametrize("db", ..., mustmatch=False)
-
-means to not raise an error but simply ignore the parametrization
-if the signature of a decorated function does not match. XXX is it
-not sufficient to always allow non-matches?
-
-
-allow parametrized attributes on classes
--------------------------------------------------- 
-
-tags: wish 2.4
-
-example:
-
-    @pytest.mark.parametrize_attr("db", setupfunc, [1,2,3], scope="class")
-    @pytest.mark.parametrize_attr("tmp", setupfunc, scope="...")
-    class TestMe:
-        def test_hello(self):
-            access self.db ...
-
-this would run the test_hello() function three times with three
-different values for self.db. This could also work with unittest/nose
-style tests, i.e. it leverages existing test suites without needing
-to rewrite them. Together with the previously mentioned setup_test()
-maybe the setupfunc could be omitted?
-
-optimizations 
----------------------------------------------------------------
-tags: 2.4 core
-
-- look at ihook optimization such that all lookups for
-  hooks relating to the same fspath are cached.
-
-fix start/finish partial finailization problem
----------------------------------------------------------------
-tags: bug core
-
-if a configure/runtest_setup/sessionstart/... hook invocation partially
-fails the sessionfinishes is not called.  Each hook implementation
-should better be repsonsible for registering a cleanup/finalizer
-appropriately to avoid this issue.  Moreover/Alternatively, we could 
-record which implementations of a hook succeeded and only call their
-teardown.
-
-
-relax requirement to have tests/testing contain an __init__
-----------------------------------------------------------------
-tags: feature 
-bb: http://bitbucket.org/hpk42/py-trunk/issue/64
-
-A local test run of a "tests" directory may work
-but a remote one fail because the tests directory
-does not contain an "__init__.py". Either give
-an error or make it work without the __init__.py
-i.e. port the nose-logic of unloading a test module.
-
-customize test function collection
--------------------------------------------------------
-tags: feature 
-
-- introduce pytest.mark.nocollect for not considering a function for
-  test collection at all.  maybe also introduce a pytest.mark.test to
-  explicitly mark a function to become a tested one.  Lookup JUnit ways
-  of tagging tests.
-
-introduce pytest.mark.importorskip
--------------------------------------------------------
-tags: feature 
-
-in addition to the imperative pytest.importorskip also introduce
-a pytest.mark.importorskip so that the test count is more correct.
-
-
-introduce pytest.mark.platform
--------------------------------------------------------
-tags: feature 
-
-Introduce nice-to-spell platform-skipping, examples:
-
-    @pytest.mark.platform("python3")
-    @pytest.mark.platform("not python3")
-    @pytest.mark.platform("win32 and not python3")
-    @pytest.mark.platform("darwin")
-    @pytest.mark.platform("not (jython and win32)")
-    @pytest.mark.platform("not (jython and win32)", xfail=True)
-
-etc. Idea is to allow Python expressions which can operate
-on common spellings for operating systems and python
-interpreter versions.
-
-pytest.mark.xfail signature change
--------------------------------------------------------
-tags: feature 
-
-change to pytest.mark.xfail(reason, (optional)condition)
-to better implement the word meaning.  It also signals
-better that we always have some kind of an implementation
-reason that can be formualated.
-Compatibility? how to introduce a new name/keep compat?
-
-allow to non-intrusively apply skipfs/xfail/marks
----------------------------------------------------
-tags: feature 
-
-use case: mark a module or directory structures
-to be skipped on certain platforms (i.e. no import
-attempt will be made).
-
-consider introducing a hook/mechanism that allows to apply marks
-from conftests or plugins. (See extended parametrization)
-
-
-explicit referencing of conftest.py files
------------------------------------------
-tags: feature 
-
-allow to name conftest.py files (in sub directories) that should
-be imported early, as to include command line options.
-
-improve central pytest ini file
--------------------------------
-tags: feature 
-
-introduce more declarative configuration options:
-- (to-be-collected test directories)
-- required plugins
-- test func/class/file matching patterns
-- skip/xfail (non-intrusive)
-- pytest.ini and tox.ini and setup.cfg configuration in the same file
-
-new documentation
-----------------------------------
-tags: feature 
-
-- logo pytest
-- examples for unittest or functional testing
-- resource management for functional testing
-- patterns: page object
-
-have imported module mismatch honour relative paths
---------------------------------------------------------
-tags: bug 
-
-With 1.1.1 pytest fails at least on windows if an import
-is relative and compared against an absolute conftest.py
-path. Normalize.
-
-consider globals: pytest.ensuretemp and config
---------------------------------------------------------------
-tags: experimental-wish 
-
-consider deprecating pytest.ensuretemp and pytest.config
-to further reduce pytest globality.  Also consider
-having pytest.config and ensuretemp coming from
-a plugin rather than being there from the start.
-
-
-consider pytest_addsyspath hook
------------------------------------------
-tags: wish
-
-pytest could call a new pytest_addsyspath() in order to systematically
-allow manipulation of sys.path and to inhibit it via --no-addsyspath
-in order to more easily run against installed packages.
-
-Alternatively it could also be done via the config object
-and pytest_configure.
-
-
-
-deprecate global pytest.config usage
-----------------------------------------------------------------
-tags: feature 
-
-pytest.ensuretemp and pytest.config are probably the last
-objects containing global state.  Often using them is not
-necessary.  This is about trying to get rid of them, i.e.
-deprecating them and checking with PyPy's usages as well
-as others.
-
-remove deprecated bits in collect.py
--------------------------------------------------------------------
-tags: feature 
-
-In an effort to further simplify code, review and remove deprecated bits
-in collect.py.  Probably good:
-- inline consider_file/dir methods, no need to have them
-  subclass-overridable because of hooks
-
-implement fslayout decorator
----------------------------------
-tags: feature 
-
-Improve the way how tests can work with pre-made examples,
-keeping the layout close to the test function:
-
-@pytest.mark.fslayout("""
-    conftest.py:
-        #  empty
-    tests/
-        test_%(NAME)s:  # becomes test_run1.py
-            def test_function(self):
-                pass
-""")
-def test_run(pytester, fslayout):
-    p = fslayout.findone("test_*.py")
-    result = pytester.runpytest(p)
-    assert result.ret == 0
-    assert result.passed == 1
-
-Another idea is to allow to define a full scenario including the run
-in one content string::
-
-    runscenario("""
-        test_{TESTNAME}.py:
-            import pytest
-            @pytest.mark.xfail
-            def test_that_fails():
-                assert 0
-
-            @pytest.mark.skipif("True")
-            def test_hello():
-                pass
-
-        conftest.py:
-            import pytest
-            def pytest_runsetup_setup(item):
-                pytest.skip("abc")
-
-        runpytest -rsxX
-        *SKIP*{TESTNAME}*
-        *1 skipped* 
-    """)
-
-This could be run with at least three different ways to invoke pytest:
-through the shell, through "python -m pytest" and inlined. As inlined
-would be the fastest it could be run first (or "--fast" mode).
-
-
-Create isolate plugin
----------------------
-tags: feature
-
-The idea is that you can e.g. import modules in a test and afterwards
-sys.modules, sys.meta_path etc would be reverted.  It can go further
-then just importing however, e.g. current working directory, file
-descriptors, ...
-
-This would probably be done by marking::
-
-    @pytest.mark.isolate(importing=True, cwd=True, fds=False)
-    def test_foo():
-        ...
-
-With the possibility of doing this globally in an ini-file.
-
-
-fnmatch for test names
-----------------------
-tags: feature-wish
-
-various testsuites use suffixes instead of prefixes for test classes
-also it lends itself to bdd style test names::
-
-    class UserBehaviour:
-        def anonymous_should_not_have_inbox(user):
-            ...
-        def registred_should_have_inbox(user):
-            ..
-
-using the following in pytest.ini::
-
-    [pytest]
-    python_classes = Test *Behaviour *Test
-    python_functions = test *_should_*
-
-
-mechanism for running named parts of tests with different reporting behaviour
-------------------------------------------------------------------------------
-tags: feature-wish-incomplete
-
-a few use-cases come to mind:
-
-* fail assertions and record that without stopping a complete test
-
-  * this is in particular hepfull if a small bit of a test is known to fail/xfail::
-
-    def test_fun():
-        with pytest.section('fdcheck, marks=pytest.mark.xfail_if(...)):
-            breaks_on_windows()
-
-* divide functional/acceptance tests into sections
-* provide a different mechanism for generators, maybe something like::
-
-    def pytest_runtest_call(item)
-        if not generator:
-            ...
-        prepare_check = GeneratorCheckprepare()
-
-        gen = item.obj(**fixtures)
-        for check in gen
-            id, call = prepare_check(check)
-            # bubble should only prevent exception propagation after a failure
-            # the whole test should still fail
-            # there might be need for a lower level api and taking custom markers into account
-            with pytest.section(id, bubble=False):
-                call()
-
-
diff --git a/tools/pytest/LICENSE b/tools/pytest/LICENSE
deleted file mode 100644
index 9e27bd78..0000000
--- a/tools/pytest/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2004-2016 Holger Krekel and others
-
-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.
diff --git a/tools/pytest/MANIFEST.in b/tools/pytest/MANIFEST.in
deleted file mode 100644
index 266a918..0000000
--- a/tools/pytest/MANIFEST.in
+++ /dev/null
@@ -1,34 +0,0 @@
-include CHANGELOG.rst
-include LICENSE
-include AUTHORS
-
-include README.rst
-include CONTRIBUTING.rst
-
-include tox.ini
-include setup.py
-
-include .coveragerc
-
-include plugin-test.sh
-include requirements-docs.txt
-include runtox.py
-
-recursive-include bench *.py
-recursive-include extra *.py
-
-graft testing
-graft doc
-
-exclude _pytest/impl
-
-graft _pytest/vendored_packages
-
-recursive-exclude * *.pyc *.pyo
-
-exclude appveyor/install.ps1
-exclude appveyor.yml
-exclude appveyor
-
-exclude ISSUES.txt
-exclude HOWTORELEASE.rst
diff --git a/tools/pytest/README.rst b/tools/pytest/README.rst
deleted file mode 100644
index 68fc922..0000000
--- a/tools/pytest/README.rst
+++ /dev/null
@@ -1,102 +0,0 @@
-.. image:: http://pytest.org/latest/_static/pytest1.png
-   :target: http://pytest.org
-   :align: center
-   :alt: pytest
-
-------
-
-.. image:: https://img.shields.io/pypi/v/pytest.svg
-   :target: https://pypi.python.org/pypi/pytest
-.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
-  :target: https://pypi.python.org/pypi/pytest
-.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
-   :target: https://coveralls.io/r/pytest-dev/pytest
-.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
-    :target: https://travis-ci.org/pytest-dev/pytest
-.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
-    :target: https://ci.appveyor.com/project/pytestbot/pytest
-
-The ``pytest`` framework makes it easy to write small tests, yet
-scales to support complex functional testing for applications and libraries.    
-
-An example of a simple test:
-
-.. code-block:: python
-
-    # content of test_sample.py
-    def func(x):
-        return x + 1
-
-    def test_answer():
-        assert func(3) == 5
-
-
-To execute it::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1    
-    collected 1 items
-
-    test_sample.py F
-
-    ======= FAILURES ========
-    _______ test_answer ________
-
-        def test_answer():
-    >       assert func(3) == 5
-    E       assert 4 == 5
-    E        +  where 4 = func(3)
-
-    test_sample.py:5: AssertionError
-    ======= 1 failed in 0.12 seconds ========
-
-Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
-        
-
-Features
---------
-
-- Detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
-
-- `Auto-discovery
-  <http://pytest.org/latest/goodpractices.html#python-test-discovery>`_
-  of test modules and functions;
-
-- `Modular fixtures <http://pytest.org/latest/fixture.html>`_  for
-  managing small or parametrized long-lived test resources;
-
-- Can run `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
-  `nose <http://pytest.org/latest/nose.html>`_ test suites out of the box;
-
-- Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested);
-
-- Rich plugin architecture, with over 150+ `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_ and thriving community;
-
-
-Documentation
--------------
-
-For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org.
-
-
-Bugs/Requests
--------------
-
-Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
-
-
-Changelog
----------
-
-Consult the `Changelog <http://pytest.org/latest/changelog.html>`_ page for fixes and enhancements of each version.
-
-
-License
--------
-
-Copyright Holger Krekel and others, 2004-2016.
-
-Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
-
-.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
diff --git a/tools/pytest/_pytest/__init__.py b/tools/pytest/_pytest/__init__.py
deleted file mode 100644
index 723fb9e..0000000
--- a/tools/pytest/_pytest/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-#
-__version__ = '2.9.1'
diff --git a/tools/pytest/_pytest/_argcomplete.py b/tools/pytest/_pytest/_argcomplete.py
deleted file mode 100644
index 955855a..0000000
--- a/tools/pytest/_pytest/_argcomplete.py
+++ /dev/null
@@ -1,101 +0,0 @@
-
-"""allow bash-completion for argparse with argcomplete if installed
-needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
-to find the magic string, so _ARGCOMPLETE env. var is never set, and
-this does not need special code.
-
-argcomplete does not support python 2.5 (although the changes for that
-are minor).
-
-Function try_argcomplete(parser) should be called directly before
-the call to ArgumentParser.parse_args().
-
-The filescompleter is what you normally would use on the positional
-arguments specification, in order to get "dirname/" after "dirn<TAB>"
-instead of the default "dirname ":
-
-   optparser.add_argument(Config._file_or_dir, nargs='*'
-                               ).completer=filescompleter
-
-Other, application specific, completers should go in the file
-doing the add_argument calls as they need to be specified as .completer
-attributes as well. (If argcomplete is not installed, the function the
-attribute points to will not be used).
-
-SPEEDUP
-=======
-The generic argcomplete script for bash-completion
-(/etc/bash_completion.d/python-argcomplete.sh )
-uses a python program to determine startup script generated by pip.
-You can speed up completion somewhat by changing this script to include
-  # PYTHON_ARGCOMPLETE_OK
-so the the python-argcomplete-check-easy-install-script does not
-need to be called to find the entry point of the code and see if that is
-marked  with PYTHON_ARGCOMPLETE_OK
-
-INSTALL/DEBUGGING
-=================
-To include this support in another application that has setup.py generated
-scripts:
-- add the line:
-    # PYTHON_ARGCOMPLETE_OK
-  near the top of the main python entry point
-- include in the file calling parse_args():
-    from _argcomplete import try_argcomplete, filescompleter
-   , call try_argcomplete just before parse_args(), and optionally add
-   filescompleter to the positional arguments' add_argument()
-If things do not work right away:
-- switch on argcomplete debugging with (also helpful when doing custom
-  completers):
-    export _ARC_DEBUG=1
-- run:
-    python-argcomplete-check-easy-install-script $(which appname)
-    echo $?
-  will echo 0 if the magic line has been found, 1 if not
-- sometimes it helps to find early on errors using:
-    _ARGCOMPLETE=1 _ARC_DEBUG=1 appname
-  which should throw a KeyError: 'COMPLINE' (which is properly set by the
-  global argcomplete script).
-"""
-
-import sys
-import os
-from glob import glob
-
-class FastFilesCompleter:
-    'Fast file completer class'
-    def __init__(self, directories=True):
-        self.directories = directories
-
-    def __call__(self, prefix, **kwargs):
-        """only called on non option completions"""
-        if os.path.sep in prefix[1:]: #
-            prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
-        else:
-            prefix_dir = 0
-        completion = []
-        globbed = []
-        if '*' not in prefix and '?' not in prefix:
-            if prefix[-1] == os.path.sep:  # we are on unix, otherwise no bash
-                globbed.extend(glob(prefix + '.*'))
-            prefix += '*'
-        globbed.extend(glob(prefix))
-        for x in sorted(globbed):
-            if os.path.isdir(x):
-                x += '/'
-            # append stripping the prefix (like bash, not like compgen)
-            completion.append(x[prefix_dir:])
-        return completion
-
-if os.environ.get('_ARGCOMPLETE'):
-    try:
-        import argcomplete.completers
-    except ImportError:
-        sys.exit(-1)
-    filescompleter = FastFilesCompleter()
-
-    def try_argcomplete(parser):
-        argcomplete.autocomplete(parser)
-else:
-    def try_argcomplete(parser): pass
-    filescompleter = None
diff --git a/tools/pytest/_pytest/_code/__init__.py b/tools/pytest/_pytest/_code/__init__.py
deleted file mode 100644
index c046b97..0000000
--- a/tools/pytest/_pytest/_code/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-""" python inspection/code generation API """
-from .code import Code  # noqa
-from .code import ExceptionInfo  # noqa
-from .code import Frame  # noqa
-from .code import Traceback  # noqa
-from .code import getrawcode  # noqa
-from .code import patch_builtins  # noqa
-from .code import unpatch_builtins  # noqa
-from .source import Source  # noqa
-from .source import compile_ as compile  # noqa
-from .source import getfslineno  # noqa
-
diff --git a/tools/pytest/_pytest/_code/_py2traceback.py b/tools/pytest/_pytest/_code/_py2traceback.py
deleted file mode 100644
index a830d98..0000000
--- a/tools/pytest/_pytest/_code/_py2traceback.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# copied from python-2.7.3's traceback.py
-# CHANGES:
-# - some_str is replaced, trying to create unicode strings
-#
-import types
-
-def format_exception_only(etype, value):
-    """Format the exception part of a traceback.
-
-    The arguments are the exception type and value such as given by
-    sys.last_type and sys.last_value. The return value is a list of
-    strings, each ending in a newline.
-
-    Normally, the list contains a single string; however, for
-    SyntaxError exceptions, it contains several lines that (when
-    printed) display detailed information about where the syntax
-    error occurred.
-
-    The message indicating which exception occurred is always the last
-    string in the list.
-
-    """
-
-    # An instance should not have a meaningful value parameter, but
-    # sometimes does, particularly for string exceptions, such as
-    # >>> raise string1, string2  # deprecated
-    #
-    # Clear these out first because issubtype(string1, SyntaxError)
-    # would throw another exception and mask the original problem.
-    if (isinstance(etype, BaseException) or
-        isinstance(etype, types.InstanceType) or
-        etype is None or type(etype) is str):
-        return [_format_final_exc_line(etype, value)]
-
-    stype = etype.__name__
-
-    if not issubclass(etype, SyntaxError):
-        return [_format_final_exc_line(stype, value)]
-
-    # It was a syntax error; show exactly where the problem was found.
-    lines = []
-    try:
-        msg, (filename, lineno, offset, badline) = value.args
-    except Exception:
-        pass
-    else:
-        filename = filename or "<string>"
-        lines.append('  File "%s", line %d\n' % (filename, lineno))
-        if badline is not None:
-            if isinstance(badline, bytes):  # python 2 only
-                badline = badline.decode('utf-8', 'replace')
-            lines.append(u'    %s\n' % badline.strip())
-            if offset is not None:
-                caretspace = badline.rstrip('\n')[:offset].lstrip()
-                # non-space whitespace (likes tabs) must be kept for alignment
-                caretspace = ((c.isspace() and c or ' ') for c in caretspace)
-                # only three spaces to account for offset1 == pos 0
-                lines.append('   %s^\n' % ''.join(caretspace))
-        value = msg
-
-    lines.append(_format_final_exc_line(stype, value))
-    return lines
-
-def _format_final_exc_line(etype, value):
-    """Return a list of a single line -- normal case for format_exception_only"""
-    valuestr = _some_str(value)
-    if value is None or not valuestr:
-        line = "%s\n" % etype
-    else:
-        line = "%s: %s\n" % (etype, valuestr)
-    return line
-
-def _some_str(value):
-    try:
-        return unicode(value)
-    except Exception:
-        try:
-            return str(value)
-        except Exception:
-            pass
-    return '<unprintable %s object>' % type(value).__name__
diff --git a/tools/pytest/_pytest/_code/code.py b/tools/pytest/_pytest/_code/code.py
deleted file mode 100644
index bc68aac..0000000
--- a/tools/pytest/_pytest/_code/code.py
+++ /dev/null
@@ -1,795 +0,0 @@
-import sys
-from inspect import CO_VARARGS, CO_VARKEYWORDS
-
-import py
-
-builtin_repr = repr
-
-reprlib = py.builtin._tryimport('repr', 'reprlib')
-
-if sys.version_info[0] >= 3:
-    from traceback import format_exception_only
-else:
-    from ._py2traceback import format_exception_only
-
-class Code(object):
-    """ wrapper around Python code objects """
-    def __init__(self, rawcode):
-        if not hasattr(rawcode, "co_filename"):
-            rawcode = getrawcode(rawcode)
-        try:
-            self.filename = rawcode.co_filename
-            self.firstlineno = rawcode.co_firstlineno - 1
-            self.name = rawcode.co_name
-        except AttributeError:
-            raise TypeError("not a code object: %r" %(rawcode,))
-        self.raw = rawcode
-
-    def __eq__(self, other):
-        return self.raw == other.raw
-
-    def __ne__(self, other):
-        return not self == other
-
-    @property
-    def path(self):
-        """ return a path object pointing to source code (note that it
-        might not point to an actually existing file). """
-        p = py.path.local(self.raw.co_filename)
-        # maybe don't try this checking
-        if not p.check():
-            # XXX maybe try harder like the weird logic
-            # in the standard lib [linecache.updatecache] does?
-            p = self.raw.co_filename
-        return p
-
-    @property
-    def fullsource(self):
-        """ return a _pytest._code.Source object for the full source file of the code
-        """
-        from _pytest._code import source
-        full, _ = source.findsource(self.raw)
-        return full
-
-    def source(self):
-        """ return a _pytest._code.Source object for the code object's source only
-        """
-        # return source only for that part of code
-        import _pytest._code
-        return _pytest._code.Source(self.raw)
-
-    def getargs(self, var=False):
-        """ return a tuple with the argument names for the code object
-
-            if 'var' is set True also return the names of the variable and
-            keyword arguments when present
-        """
-        # handfull shortcut for getting args
-        raw = self.raw
-        argcount = raw.co_argcount
-        if var:
-            argcount += raw.co_flags & CO_VARARGS
-            argcount += raw.co_flags & CO_VARKEYWORDS
-        return raw.co_varnames[:argcount]
-
-class Frame(object):
-    """Wrapper around a Python frame holding f_locals and f_globals
-    in which expressions can be evaluated."""
-
-    def __init__(self, frame):
-        self.lineno = frame.f_lineno - 1
-        self.f_globals = frame.f_globals
-        self.f_locals = frame.f_locals
-        self.raw = frame
-        self.code = Code(frame.f_code)
-
-    @property
-    def statement(self):
-        """ statement this frame is at """
-        import _pytest._code
-        if self.code.fullsource is None:
-            return _pytest._code.Source("")
-        return self.code.fullsource.getstatement(self.lineno)
-
-    def eval(self, code, **vars):
-        """ evaluate 'code' in the frame
-
-            'vars' are optional additional local variables
-
-            returns the result of the evaluation
-        """
-        f_locals = self.f_locals.copy()
-        f_locals.update(vars)
-        return eval(code, self.f_globals, f_locals)
-
-    def exec_(self, code, **vars):
-        """ exec 'code' in the frame
-
-            'vars' are optiona; additional local variables
-        """
-        f_locals = self.f_locals.copy()
-        f_locals.update(vars)
-        py.builtin.exec_(code, self.f_globals, f_locals )
-
-    def repr(self, object):
-        """ return a 'safe' (non-recursive, one-line) string repr for 'object'
-        """
-        return py.io.saferepr(object)
-
-    def is_true(self, object):
-        return object
-
-    def getargs(self, var=False):
-        """ return a list of tuples (name, value) for all arguments
-
-            if 'var' is set True also include the variable and keyword
-            arguments when present
-        """
-        retval = []
-        for arg in self.code.getargs(var):
-            try:
-                retval.append((arg, self.f_locals[arg]))
-            except KeyError:
-                pass     # this can occur when using Psyco
-        return retval
-
-class TracebackEntry(object):
-    """ a single entry in a traceback """
-
-    _repr_style = None
-    exprinfo = None
-
-    def __init__(self, rawentry):
-        self._rawentry = rawentry
-        self.lineno = rawentry.tb_lineno - 1
-
-    def set_repr_style(self, mode):
-        assert mode in ("short", "long")
-        self._repr_style = mode
-
-    @property
-    def frame(self):
-        import _pytest._code
-        return _pytest._code.Frame(self._rawentry.tb_frame)
-
-    @property
-    def relline(self):
-        return self.lineno - self.frame.code.firstlineno
-
-    def __repr__(self):
-        return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
-
-    @property
-    def statement(self):
-        """ _pytest._code.Source object for the current statement """
-        source = self.frame.code.fullsource
-        return source.getstatement(self.lineno)
-
-    @property
-    def path(self):
-        """ path to the source code """
-        return self.frame.code.path
-
-    def getlocals(self):
-        return self.frame.f_locals
-    locals = property(getlocals, None, None, "locals of underlaying frame")
-
-    def reinterpret(self):
-        """Reinterpret the failing statement and returns a detailed information
-           about what operations are performed."""
-        from _pytest.assertion.reinterpret import reinterpret
-        if self.exprinfo is None:
-            source = py.builtin._totext(self.statement).strip()
-            x = reinterpret(source, self.frame, should_fail=True)
-            if not py.builtin._istext(x):
-                raise TypeError("interpret returned non-string %r" % (x,))
-            self.exprinfo = x
-        return self.exprinfo
-
-    def getfirstlinesource(self):
-        # on Jython this firstlineno can be -1 apparently
-        return max(self.frame.code.firstlineno, 0)
-
-    def getsource(self, astcache=None):
-        """ return failing source code. """
-        # we use the passed in astcache to not reparse asttrees
-        # within exception info printing
-        from _pytest._code.source import getstatementrange_ast
-        source = self.frame.code.fullsource
-        if source is None:
-            return None
-        key = astnode = None
-        if astcache is not None:
-            key = self.frame.code.path
-            if key is not None:
-                astnode = astcache.get(key, None)
-        start = self.getfirstlinesource()
-        try:
-            astnode, _, end = getstatementrange_ast(self.lineno, source,
-                                                    astnode=astnode)
-        except SyntaxError:
-            end = self.lineno + 1
-        else:
-            if key is not None:
-                astcache[key] = astnode
-        return source[start:end]
-
-    source = property(getsource)
-
-    def ishidden(self):
-        """ return True if the current frame has a var __tracebackhide__
-            resolving to True
-
-            mostly for internal use
-        """
-        try:
-            return self.frame.f_locals['__tracebackhide__']
-        except KeyError:
-            try:
-                return self.frame.f_globals['__tracebackhide__']
-            except KeyError:
-                return False
-
-    def __str__(self):
-        try:
-            fn = str(self.path)
-        except py.error.Error:
-            fn = '???'
-        name = self.frame.code.name
-        try:
-            line = str(self.statement).lstrip()
-        except KeyboardInterrupt:
-            raise
-        except:
-            line = "???"
-        return "  File %r:%d in %s\n  %s\n" %(fn, self.lineno+1, name, line)
-
-    def name(self):
-        return self.frame.code.raw.co_name
-    name = property(name, None, None, "co_name of underlaying code")
-
-class Traceback(list):
-    """ Traceback objects encapsulate and offer higher level
-        access to Traceback entries.
-    """
-    Entry = TracebackEntry
-    def __init__(self, tb):
-        """ initialize from given python traceback object. """
-        if hasattr(tb, 'tb_next'):
-            def f(cur):
-                while cur is not None:
-                    yield self.Entry(cur)
-                    cur = cur.tb_next
-            list.__init__(self, f(tb))
-        else:
-            list.__init__(self, tb)
-
-    def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
-        """ return a Traceback instance wrapping part of this Traceback
-
-            by provding any combination of path, lineno and firstlineno, the
-            first frame to start the to-be-returned traceback is determined
-
-            this allows cutting the first part of a Traceback instance e.g.
-            for formatting reasons (removing some uninteresting bits that deal
-            with handling of the exception/traceback)
-        """
-        for x in self:
-            code = x.frame.code
-            codepath = code.path
-            if ((path is None or codepath == path) and
-                (excludepath is None or not hasattr(codepath, 'relto') or
-                 not codepath.relto(excludepath)) and
-                (lineno is None or x.lineno == lineno) and
-                (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
-                return Traceback(x._rawentry)
-        return self
-
-    def __getitem__(self, key):
-        val = super(Traceback, self).__getitem__(key)
-        if isinstance(key, type(slice(0))):
-            val = self.__class__(val)
-        return val
-
-    def filter(self, fn=lambda x: not x.ishidden()):
-        """ return a Traceback instance with certain items removed
-
-            fn is a function that gets a single argument, a TracebackItem
-            instance, and should return True when the item should be added
-            to the Traceback, False when not
-
-            by default this removes all the TracebackItems which are hidden
-            (see ishidden() above)
-        """
-        return Traceback(filter(fn, self))
-
-    def getcrashentry(self):
-        """ return last non-hidden traceback entry that lead
-        to the exception of a traceback.
-        """
-        for i in range(-1, -len(self)-1, -1):
-            entry = self[i]
-            if not entry.ishidden():
-                return entry
-        return self[-1]
-
-    def recursionindex(self):
-        """ return the index of the frame/TracebackItem where recursion
-            originates if appropriate, None if no recursion occurred
-        """
-        cache = {}
-        for i, entry in enumerate(self):
-            # id for the code.raw is needed to work around
-            # the strange metaprogramming in the decorator lib from pypi
-            # which generates code objects that have hash/value equality
-            #XXX needs a test
-            key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
-            #print "checking for recursion at", key
-            l = cache.setdefault(key, [])
-            if l:
-                f = entry.frame
-                loc = f.f_locals
-                for otherloc in l:
-                    if f.is_true(f.eval(co_equal,
-                        __recursioncache_locals_1=loc,
-                        __recursioncache_locals_2=otherloc)):
-                        return i
-            l.append(entry.frame.f_locals)
-        return None
-
-co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
-                   '?', 'eval')
-
-class ExceptionInfo(object):
-    """ wraps sys.exc_info() objects and offers
-        help for navigating the traceback.
-    """
-    _striptext = ''
-    def __init__(self, tup=None, exprinfo=None):
-        import _pytest._code
-        if tup is None:
-            tup = sys.exc_info()
-            if exprinfo is None and isinstance(tup[1], AssertionError):
-                exprinfo = getattr(tup[1], 'msg', None)
-                if exprinfo is None:
-                    exprinfo = str(tup[1])
-                if exprinfo and exprinfo.startswith('assert '):
-                    self._striptext = 'AssertionError: '
-        self._excinfo = tup
-        #: the exception class
-        self.type = tup[0]
-        #: the exception instance
-        self.value = tup[1]
-        #: the exception raw traceback
-        self.tb = tup[2]
-        #: the exception type name
-        self.typename = self.type.__name__
-        #: the exception traceback (_pytest._code.Traceback instance)
-        self.traceback = _pytest._code.Traceback(self.tb)
-
-    def __repr__(self):
-        return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
-
-    def exconly(self, tryshort=False):
-        """ return the exception as a string
-
-            when 'tryshort' resolves to True, and the exception is a
-            _pytest._code._AssertionError, only the actual exception part of
-            the exception representation is returned (so 'AssertionError: ' is
-            removed from the beginning)
-        """
-        lines = format_exception_only(self.type, self.value)
-        text = ''.join(lines)
-        text = text.rstrip()
-        if tryshort:
-            if text.startswith(self._striptext):
-                text = text[len(self._striptext):]
-        return text
-
-    def errisinstance(self, exc):
-        """ return True if the exception is an instance of exc """
-        return isinstance(self.value, exc)
-
-    def _getreprcrash(self):
-        exconly = self.exconly(tryshort=True)
-        entry = self.traceback.getcrashentry()
-        path, lineno = entry.frame.code.raw.co_filename, entry.lineno
-        return ReprFileLocation(path, lineno+1, exconly)
-
-    def getrepr(self, showlocals=False, style="long",
-            abspath=False, tbfilter=True, funcargs=False):
-        """ return str()able representation of this exception info.
-            showlocals: show locals per traceback entry
-            style: long|short|no|native traceback style
-            tbfilter: hide entries (where __tracebackhide__ is true)
-
-            in case of style==native, tbfilter and showlocals is ignored.
-        """
-        if style == 'native':
-            return ReprExceptionInfo(ReprTracebackNative(
-                py.std.traceback.format_exception(
-                    self.type,
-                    self.value,
-                    self.traceback[0]._rawentry,
-                )), self._getreprcrash())
-
-        fmt = FormattedExcinfo(showlocals=showlocals, style=style,
-            abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
-        return fmt.repr_excinfo(self)
-
-    def __str__(self):
-        entry = self.traceback[-1]
-        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
-        return str(loc)
-
-    def __unicode__(self):
-        entry = self.traceback[-1]
-        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
-        return unicode(loc)
-
-
-class FormattedExcinfo(object):
-    """ presenting information about failing Functions and Generators. """
-    # for traceback entries
-    flow_marker = ">"
-    fail_marker = "E"
-
-    def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
-        self.showlocals = showlocals
-        self.style = style
-        self.tbfilter = tbfilter
-        self.funcargs = funcargs
-        self.abspath = abspath
-        self.astcache = {}
-
-    def _getindent(self, source):
-        # figure out indent for given source
-        try:
-            s = str(source.getstatement(len(source)-1))
-        except KeyboardInterrupt:
-            raise
-        except:
-            try:
-                s = str(source[-1])
-            except KeyboardInterrupt:
-                raise
-            except:
-                return 0
-        return 4 + (len(s) - len(s.lstrip()))
-
-    def _getentrysource(self, entry):
-        source = entry.getsource(self.astcache)
-        if source is not None:
-            source = source.deindent()
-        return source
-
-    def _saferepr(self, obj):
-        return py.io.saferepr(obj)
-
-    def repr_args(self, entry):
-        if self.funcargs:
-            args = []
-            for argname, argvalue in entry.frame.getargs(var=True):
-                args.append((argname, self._saferepr(argvalue)))
-            return ReprFuncArgs(args)
-
-    def get_source(self, source, line_index=-1, excinfo=None, short=False):
-        """ return formatted and marked up source lines. """
-        import _pytest._code
-        lines = []
-        if source is None or line_index >= len(source.lines):
-            source = _pytest._code.Source("???")
-            line_index = 0
-        if line_index < 0:
-            line_index += len(source)
-        space_prefix = "    "
-        if short:
-            lines.append(space_prefix + source.lines[line_index].strip())
-        else:
-            for line in source.lines[:line_index]:
-                lines.append(space_prefix + line)
-            lines.append(self.flow_marker + "   " + source.lines[line_index])
-            for line in source.lines[line_index+1:]:
-                lines.append(space_prefix + line)
-        if excinfo is not None:
-            indent = 4 if short else self._getindent(source)
-            lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
-        return lines
-
-    def get_exconly(self, excinfo, indent=4, markall=False):
-        lines = []
-        indent = " " * indent
-        # get the real exception information out
-        exlines = excinfo.exconly(tryshort=True).split('\n')
-        failindent = self.fail_marker + indent[1:]
-        for line in exlines:
-            lines.append(failindent + line)
-            if not markall:
-                failindent = indent
-        return lines
-
-    def repr_locals(self, locals):
-        if self.showlocals:
-            lines = []
-            keys = [loc for loc in locals if loc[0] != "@"]
-            keys.sort()
-            for name in keys:
-                value = locals[name]
-                if name == '__builtins__':
-                    lines.append("__builtins__ = <builtins>")
-                else:
-                    # This formatting could all be handled by the
-                    # _repr() function, which is only reprlib.Repr in
-                    # disguise, so is very configurable.
-                    str_repr = self._saferepr(value)
-                    #if len(str_repr) < 70 or not isinstance(value,
-                    #                            (list, tuple, dict)):
-                    lines.append("%-10s = %s" %(name, str_repr))
-                    #else:
-                    #    self._line("%-10s =\\" % (name,))
-                    #    # XXX
-                    #    py.std.pprint.pprint(value, stream=self.excinfowriter)
-            return ReprLocals(lines)
-
-    def repr_traceback_entry(self, entry, excinfo=None):
-        import _pytest._code
-        source = self._getentrysource(entry)
-        if source is None:
-            source = _pytest._code.Source("???")
-            line_index = 0
-        else:
-            # entry.getfirstlinesource() can be -1, should be 0 on jython
-            line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
-
-        lines = []
-        style = entry._repr_style
-        if style is None:
-            style = self.style
-        if style in ("short", "long"):
-            short = style == "short"
-            reprargs = self.repr_args(entry) if not short else None
-            s = self.get_source(source, line_index, excinfo, short=short)
-            lines.extend(s)
-            if short:
-                message = "in %s" %(entry.name)
-            else:
-                message = excinfo and excinfo.typename or ""
-            path = self._makepath(entry.path)
-            filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
-            localsrepr = None
-            if not short:
-                localsrepr =  self.repr_locals(entry.locals)
-            return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
-        if excinfo:
-            lines.extend(self.get_exconly(excinfo, indent=4))
-        return ReprEntry(lines, None, None, None, style)
-
-    def _makepath(self, path):
-        if not self.abspath:
-            try:
-                np = py.path.local().bestrelpath(path)
-            except OSError:
-                return path
-            if len(np) < len(str(path)):
-                path = np
-        return path
-
-    def repr_traceback(self, excinfo):
-        traceback = excinfo.traceback
-        if self.tbfilter:
-            traceback = traceback.filter()
-        recursionindex = None
-        if excinfo.errisinstance(RuntimeError):
-            if "maximum recursion depth exceeded" in str(excinfo.value):
-                recursionindex = traceback.recursionindex()
-        last = traceback[-1]
-        entries = []
-        extraline = None
-        for index, entry in enumerate(traceback):
-            einfo = (last == entry) and excinfo or None
-            reprentry = self.repr_traceback_entry(entry, einfo)
-            entries.append(reprentry)
-            if index == recursionindex:
-                extraline = "!!! Recursion detected (same locals & position)"
-                break
-        return ReprTraceback(entries, extraline, style=self.style)
-
-    def repr_excinfo(self, excinfo):
-        reprtraceback = self.repr_traceback(excinfo)
-        reprcrash = excinfo._getreprcrash()
-        return ReprExceptionInfo(reprtraceback, reprcrash)
-
-class TerminalRepr:
-    def __str__(self):
-        s = self.__unicode__()
-        if sys.version_info[0] < 3:
-            s = s.encode('utf-8')
-        return s
-
-    def __unicode__(self):
-        # FYI this is called from pytest-xdist's serialization of exception
-        # information.
-        io = py.io.TextIO()
-        tw = py.io.TerminalWriter(file=io)
-        self.toterminal(tw)
-        return io.getvalue().strip()
-
-    def __repr__(self):
-        return "<%s instance at %0x>" %(self.__class__, id(self))
-
-
-class ReprExceptionInfo(TerminalRepr):
-    def __init__(self, reprtraceback, reprcrash):
-        self.reprtraceback = reprtraceback
-        self.reprcrash = reprcrash
-        self.sections = []
-
-    def addsection(self, name, content, sep="-"):
-        self.sections.append((name, content, sep))
-
-    def toterminal(self, tw):
-        self.reprtraceback.toterminal(tw)
-        for name, content, sep in self.sections:
-            tw.sep(sep, name)
-            tw.line(content)
-
-class ReprTraceback(TerminalRepr):
-    entrysep = "_ "
-
-    def __init__(self, reprentries, extraline, style):
-        self.reprentries = reprentries
-        self.extraline = extraline
-        self.style = style
-
-    def toterminal(self, tw):
-        # the entries might have different styles
-        for i, entry in enumerate(self.reprentries):
-            if entry.style == "long":
-                tw.line("")
-            entry.toterminal(tw)
-            if i < len(self.reprentries) - 1:
-                next_entry = self.reprentries[i+1]
-                if entry.style == "long" or \
-                   entry.style == "short" and next_entry.style == "long":
-                    tw.sep(self.entrysep)
-
-        if self.extraline:
-            tw.line(self.extraline)
-
-class ReprTracebackNative(ReprTraceback):
-    def __init__(self, tblines):
-        self.style = "native"
-        self.reprentries = [ReprEntryNative(tblines)]
-        self.extraline = None
-
-class ReprEntryNative(TerminalRepr):
-    style = "native"
-
-    def __init__(self, tblines):
-        self.lines = tblines
-
-    def toterminal(self, tw):
-        tw.write("".join(self.lines))
-
-class ReprEntry(TerminalRepr):
-    localssep = "_ "
-
-    def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
-        self.lines = lines
-        self.reprfuncargs = reprfuncargs
-        self.reprlocals = reprlocals
-        self.reprfileloc = filelocrepr
-        self.style = style
-
-    def toterminal(self, tw):
-        if self.style == "short":
-            self.reprfileloc.toterminal(tw)
-            for line in self.lines:
-                red = line.startswith("E   ")
-                tw.line(line, bold=True, red=red)
-            #tw.line("")
-            return
-        if self.reprfuncargs:
-            self.reprfuncargs.toterminal(tw)
-        for line in self.lines:
-            red = line.startswith("E   ")
-            tw.line(line, bold=True, red=red)
-        if self.reprlocals:
-            #tw.sep(self.localssep, "Locals")
-            tw.line("")
-            self.reprlocals.toterminal(tw)
-        if self.reprfileloc:
-            if self.lines:
-                tw.line("")
-            self.reprfileloc.toterminal(tw)
-
-    def __str__(self):
-        return "%s\n%s\n%s" % ("\n".join(self.lines),
-                               self.reprlocals,
-                               self.reprfileloc)
-
-class ReprFileLocation(TerminalRepr):
-    def __init__(self, path, lineno, message):
-        self.path = str(path)
-        self.lineno = lineno
-        self.message = message
-
-    def toterminal(self, tw):
-        # filename and lineno output for each entry,
-        # using an output format that most editors unterstand
-        msg = self.message
-        i = msg.find("\n")
-        if i != -1:
-            msg = msg[:i]
-        tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
-
-class ReprLocals(TerminalRepr):
-    def __init__(self, lines):
-        self.lines = lines
-
-    def toterminal(self, tw):
-        for line in self.lines:
-            tw.line(line)
-
-class ReprFuncArgs(TerminalRepr):
-    def __init__(self, args):
-        self.args = args
-
-    def toterminal(self, tw):
-        if self.args:
-            linesofar = ""
-            for name, value in self.args:
-                ns = "%s = %s" %(name, value)
-                if len(ns) + len(linesofar) + 2 > tw.fullwidth:
-                    if linesofar:
-                        tw.line(linesofar)
-                    linesofar =  ns
-                else:
-                    if linesofar:
-                        linesofar += ", " + ns
-                    else:
-                        linesofar = ns
-            if linesofar:
-                tw.line(linesofar)
-            tw.line("")
-
-
-
-oldbuiltins = {}
-
-def patch_builtins(assertion=True, compile=True):
-    """ put compile and AssertionError builtins to Python's builtins. """
-    if assertion:
-        from _pytest.assertion import reinterpret
-        l = oldbuiltins.setdefault('AssertionError', [])
-        l.append(py.builtin.builtins.AssertionError)
-        py.builtin.builtins.AssertionError = reinterpret.AssertionError
-    if compile:
-        import _pytest._code
-        l = oldbuiltins.setdefault('compile', [])
-        l.append(py.builtin.builtins.compile)
-        py.builtin.builtins.compile = _pytest._code.compile
-
-def unpatch_builtins(assertion=True, compile=True):
-    """ remove compile and AssertionError builtins from Python builtins. """
-    if assertion:
-        py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
-    if compile:
-        py.builtin.builtins.compile = oldbuiltins['compile'].pop()
-
-def getrawcode(obj, trycall=True):
-    """ return code object for given function. """
-    try:
-        return obj.__code__
-    except AttributeError:
-        obj = getattr(obj, 'im_func', obj)
-        obj = getattr(obj, 'func_code', obj)
-        obj = getattr(obj, 'f_code', obj)
-        obj = getattr(obj, '__code__', obj)
-        if trycall and not hasattr(obj, 'co_firstlineno'):
-            if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
-                x = getrawcode(obj.__call__, trycall=False)
-                if hasattr(x, 'co_firstlineno'):
-                    return x
-        return obj
-
diff --git a/tools/pytest/_pytest/_code/source.py b/tools/pytest/_pytest/_code/source.py
deleted file mode 100644
index a1521f8..0000000
--- a/tools/pytest/_pytest/_code/source.py
+++ /dev/null
@@ -1,421 +0,0 @@
-from __future__ import generators
-
-from bisect import bisect_right
-import sys
-import inspect, tokenize
-import py
-from types import ModuleType
-cpy_compile = compile
-
-try:
-    import _ast
-    from _ast import PyCF_ONLY_AST as _AST_FLAG
-except ImportError:
-    _AST_FLAG = 0
-    _ast = None
-
-
-class Source(object):
-    """ a immutable object holding a source code fragment,
-        possibly deindenting it.
-    """
-    _compilecounter = 0
-    def __init__(self, *parts, **kwargs):
-        self.lines = lines = []
-        de = kwargs.get('deindent', True)
-        rstrip = kwargs.get('rstrip', True)
-        for part in parts:
-            if not part:
-                partlines = []
-            if isinstance(part, Source):
-                partlines = part.lines
-            elif isinstance(part, (tuple, list)):
-                partlines = [x.rstrip("\n") for x in part]
-            elif isinstance(part, py.builtin._basestring):
-                partlines = part.split('\n')
-                if rstrip:
-                    while partlines:
-                        if partlines[-1].strip():
-                            break
-                        partlines.pop()
-            else:
-                partlines = getsource(part, deindent=de).lines
-            if de:
-                partlines = deindent(partlines)
-            lines.extend(partlines)
-
-    def __eq__(self, other):
-        try:
-            return self.lines == other.lines
-        except AttributeError:
-            if isinstance(other, str):
-                return str(self) == other
-            return False
-
-    def __getitem__(self, key):
-        if isinstance(key, int):
-            return self.lines[key]
-        else:
-            if key.step not in (None, 1):
-                raise IndexError("cannot slice a Source with a step")
-            return self.__getslice__(key.start, key.stop)
-
-    def __len__(self):
-        return len(self.lines)
-
-    def __getslice__(self, start, end):
-        newsource = Source()
-        newsource.lines = self.lines[start:end]
-        return newsource
-
-    def strip(self):
-        """ return new source object with trailing
-            and leading blank lines removed.
-        """
-        start, end = 0, len(self)
-        while start < end and not self.lines[start].strip():
-            start += 1
-        while end > start and not self.lines[end-1].strip():
-            end -= 1
-        source = Source()
-        source.lines[:] = self.lines[start:end]
-        return source
-
-    def putaround(self, before='', after='', indent=' ' * 4):
-        """ return a copy of the source object with
-            'before' and 'after' wrapped around it.
-        """
-        before = Source(before)
-        after = Source(after)
-        newsource = Source()
-        lines = [ (indent + line) for line in self.lines]
-        newsource.lines = before.lines + lines +  after.lines
-        return newsource
-
-    def indent(self, indent=' ' * 4):
-        """ return a copy of the source object with
-            all lines indented by the given indent-string.
-        """
-        newsource = Source()
-        newsource.lines = [(indent+line) for line in self.lines]
-        return newsource
-
-    def getstatement(self, lineno, assertion=False):
-        """ return Source statement which contains the
-            given linenumber (counted from 0).
-        """
-        start, end = self.getstatementrange(lineno, assertion)
-        return self[start:end]
-
-    def getstatementrange(self, lineno, assertion=False):
-        """ return (start, end) tuple which spans the minimal
-            statement region which containing the given lineno.
-        """
-        if not (0 <= lineno < len(self)):
-            raise IndexError("lineno out of range")
-        ast, start, end = getstatementrange_ast(lineno, self)
-        return start, end
-
-    def deindent(self, offset=None):
-        """ return a new source object deindented by offset.
-            If offset is None then guess an indentation offset from
-            the first non-blank line.  Subsequent lines which have a
-            lower indentation offset will be copied verbatim as
-            they are assumed to be part of multilines.
-        """
-        # XXX maybe use the tokenizer to properly handle multiline
-        #     strings etc.pp?
-        newsource = Source()
-        newsource.lines[:] = deindent(self.lines, offset)
-        return newsource
-
-    def isparseable(self, deindent=True):
-        """ return True if source is parseable, heuristically
-            deindenting it by default.
-        """
-        try:
-            import parser
-        except ImportError:
-            syntax_checker = lambda x: compile(x, 'asd', 'exec')
-        else:
-            syntax_checker = parser.suite
-
-        if deindent:
-            source = str(self.deindent())
-        else:
-            source = str(self)
-        try:
-            #compile(source+'\n', "x", "exec")
-            syntax_checker(source+'\n')
-        except KeyboardInterrupt:
-            raise
-        except Exception:
-            return False
-        else:
-            return True
-
-    def __str__(self):
-        return "\n".join(self.lines)
-
-    def compile(self, filename=None, mode='exec',
-                flag=generators.compiler_flag,
-                dont_inherit=0, _genframe=None):
-        """ return compiled code object. if filename is None
-            invent an artificial filename which displays
-            the source/line position of the caller frame.
-        """
-        if not filename or py.path.local(filename).check(file=0):
-            if _genframe is None:
-                _genframe = sys._getframe(1) # the caller
-            fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
-            base = "<%d-codegen " % self._compilecounter
-            self.__class__._compilecounter += 1
-            if not filename:
-                filename = base + '%s:%d>' % (fn, lineno)
-            else:
-                filename = base + '%r %s:%d>' % (filename, fn, lineno)
-        source = "\n".join(self.lines) + '\n'
-        try:
-            co = cpy_compile(source, filename, mode, flag)
-        except SyntaxError:
-            ex = sys.exc_info()[1]
-            # re-represent syntax errors from parsing python strings
-            msglines = self.lines[:ex.lineno]
-            if ex.offset:
-                msglines.append(" "*ex.offset + '^')
-            msglines.append("(code was compiled probably from here: %s)" % filename)
-            newex = SyntaxError('\n'.join(msglines))
-            newex.offset = ex.offset
-            newex.lineno = ex.lineno
-            newex.text = ex.text
-            raise newex
-        else:
-            if flag & _AST_FLAG:
-                return co
-            lines = [(x + "\n") for x in self.lines]
-            if sys.version_info[0] >= 3:
-                # XXX py3's inspect.getsourcefile() checks for a module
-                # and a pep302 __loader__ ... we don't have a module
-                # at code compile-time so we need to fake it here
-                m = ModuleType("_pycodecompile_pseudo_module")
-                py.std.inspect.modulesbyfile[filename] = None
-                py.std.sys.modules[None] = m
-                m.__loader__ = 1
-            py.std.linecache.cache[filename] = (1, None, lines, filename)
-            return co
-
-#
-# public API shortcut functions
-#
-
-def compile_(source, filename=None, mode='exec', flags=
-            generators.compiler_flag, dont_inherit=0):
-    """ compile the given source to a raw code object,
-        and maintain an internal cache which allows later
-        retrieval of the source code for the code object
-        and any recursively created code objects.
-    """
-    if _ast is not None and isinstance(source, _ast.AST):
-        # XXX should Source support having AST?
-        return cpy_compile(source, filename, mode, flags, dont_inherit)
-    _genframe = sys._getframe(1) # the caller
-    s = Source(source)
-    co = s.compile(filename, mode, flags, _genframe=_genframe)
-    return co
-
-
-def getfslineno(obj):
-    """ Return source location (path, lineno) for the given object.
-    If the source cannot be determined return ("", -1)
-    """
-    import _pytest._code
-    try:
-        code = _pytest._code.Code(obj)
-    except TypeError:
-        try:
-            fn = (py.std.inspect.getsourcefile(obj) or
-                  py.std.inspect.getfile(obj))
-        except TypeError:
-            return "", -1
-
-        fspath = fn and py.path.local(fn) or None
-        lineno = -1
-        if fspath:
-            try:
-                _, lineno = findsource(obj)
-            except IOError:
-                pass
-    else:
-        fspath = code.path
-        lineno = code.firstlineno
-    assert isinstance(lineno, int)
-    return fspath, lineno
-
-#
-# helper functions
-#
-
-def findsource(obj):
-    try:
-        sourcelines, lineno = py.std.inspect.findsource(obj)
-    except py.builtin._sysex:
-        raise
-    except:
-        return None, -1
-    source = Source()
-    source.lines = [line.rstrip() for line in sourcelines]
-    return source, lineno
-
-def getsource(obj, **kwargs):
-    import _pytest._code
-    obj = _pytest._code.getrawcode(obj)
-    try:
-        strsrc = inspect.getsource(obj)
-    except IndentationError:
-        strsrc = "\"Buggy python version consider upgrading, cannot get source\""
-    assert isinstance(strsrc, str)
-    return Source(strsrc, **kwargs)
-
-def deindent(lines, offset=None):
-    if offset is None:
-        for line in lines:
-            line = line.expandtabs()
-            s = line.lstrip()
-            if s:
-                offset = len(line)-len(s)
-                break
-        else:
-            offset = 0
-    if offset == 0:
-        return list(lines)
-    newlines = []
-    def readline_generator(lines):
-        for line in lines:
-            yield line + '\n'
-        while True:
-            yield ''
-
-    it = readline_generator(lines)
-
-    try:
-        for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
-            if sline > len(lines):
-                break # End of input reached
-            if sline > len(newlines):
-                line = lines[sline - 1].expandtabs()
-                if line.lstrip() and line[:offset].isspace():
-                    line = line[offset:] # Deindent
-                newlines.append(line)
-
-            for i in range(sline, eline):
-                # Don't deindent continuing lines of
-                # multiline tokens (i.e. multiline strings)
-                newlines.append(lines[i])
-    except (IndentationError, tokenize.TokenError):
-        pass
-    # Add any lines we didn't see. E.g. if an exception was raised.
-    newlines.extend(lines[len(newlines):])
-    return newlines
-
-
-def get_statement_startend2(lineno, node):
-    import ast
-    # flatten all statements and except handlers into one lineno-list
-    # AST's line numbers start indexing at 1
-    l = []
-    for x in ast.walk(node):
-        if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
-            l.append(x.lineno - 1)
-            for name in "finalbody", "orelse":
-                val = getattr(x, name, None)
-                if val:
-                    # treat the finally/orelse part as its own statement
-                    l.append(val[0].lineno - 1 - 1)
-    l.sort()
-    insert_index = bisect_right(l, lineno)
-    start = l[insert_index - 1]
-    if insert_index >= len(l):
-        end = None
-    else:
-        end = l[insert_index]
-    return start, end
-
-
-def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
-    if astnode is None:
-        content = str(source)
-        if sys.version_info < (2,7):
-            content += "\n"
-        try:
-            astnode = compile(content, "source", "exec", 1024)  # 1024 for AST
-        except ValueError:
-            start, end = getstatementrange_old(lineno, source, assertion)
-            return None, start, end
-    start, end = get_statement_startend2(lineno, astnode)
-    # we need to correct the end:
-    # - ast-parsing strips comments
-    # - there might be empty lines
-    # - we might have lesser indented code blocks at the end
-    if end is None:
-        end = len(source.lines)
-
-    if end > start + 1:
-        # make sure we don't span differently indented code blocks
-        # by using the BlockFinder helper used which inspect.getsource() uses itself
-        block_finder = inspect.BlockFinder()
-        # if we start with an indented line, put blockfinder to "started" mode
-        block_finder.started = source.lines[start][0].isspace()
-        it = ((x + "\n") for x in source.lines[start:end])
-        try:
-            for tok in tokenize.generate_tokens(lambda: next(it)):
-                block_finder.tokeneater(*tok)
-        except (inspect.EndOfBlock, IndentationError):
-            end = block_finder.last + start
-        except Exception:
-            pass
-
-    # the end might still point to a comment or empty line, correct it
-    while end:
-        line = source.lines[end - 1].lstrip()
-        if line.startswith("#") or not line:
-            end -= 1
-        else:
-            break
-    return astnode, start, end
-
-
-def getstatementrange_old(lineno, source, assertion=False):
-    """ return (start, end) tuple which spans the minimal
-        statement region which containing the given lineno.
-        raise an IndexError if no such statementrange can be found.
-    """
-    # XXX this logic is only used on python2.4 and below
-    # 1. find the start of the statement
-    from codeop import compile_command
-    for start in range(lineno, -1, -1):
-        if assertion:
-            line = source.lines[start]
-            # the following lines are not fully tested, change with care
-            if 'super' in line and 'self' in line and '__init__' in line:
-                raise IndexError("likely a subclass")
-            if "assert" not in line and "raise" not in line:
-                continue
-        trylines = source.lines[start:lineno+1]
-        # quick hack to prepare parsing an indented line with
-        # compile_command() (which errors on "return" outside defs)
-        trylines.insert(0, 'def xxx():')
-        trysource = '\n '.join(trylines)
-        #              ^ space here
-        try:
-            compile_command(trysource)
-        except (SyntaxError, OverflowError, ValueError):
-            continue
-
-        # 2. find the end of the statement
-        for end in range(lineno+1, len(source)+1):
-            trysource = source[start:end]
-            if trysource.isparseable():
-                return start, end
-    raise SyntaxError("no valid source range around line %d " % (lineno,))
-
-
diff --git a/tools/pytest/_pytest/_pluggy.py b/tools/pytest/_pytest/_pluggy.py
deleted file mode 100644
index 87d32cf..0000000
--- a/tools/pytest/_pytest/_pluggy.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-imports symbols from vendored "pluggy" if available, otherwise
-falls back to importing "pluggy" from the default namespace.
-"""
-
-try:
-    from _pytest.vendored_packages.pluggy import *  # noqa
-    from _pytest.vendored_packages.pluggy import __version__  # noqa
-except ImportError:
-    from pluggy import *  # noqa
-    from pluggy import __version__  # noqa
diff --git a/tools/pytest/_pytest/assertion/__init__.py b/tools/pytest/_pytest/assertion/__init__.py
deleted file mode 100644
index 6921deb..0000000
--- a/tools/pytest/_pytest/assertion/__init__.py
+++ /dev/null
@@ -1,176 +0,0 @@
-"""
-support for presenting detailed information in failing assertions.
-"""
-import py
-import os
-import sys
-from _pytest.monkeypatch import monkeypatch
-from _pytest.assertion import util
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("debugconfig")
-    group.addoption('--assert',
-                    action="store",
-                    dest="assertmode",
-                    choices=("rewrite", "reinterp", "plain",),
-                    default="rewrite",
-                    metavar="MODE",
-                    help="""control assertion debugging tools.  'plain'
-                            performs no assertion debugging.  'reinterp'
-                            reinterprets assert statements after they failed
-                            to provide assertion expression information.
-                            'rewrite' (the default) rewrites assert
-                            statements in test modules on import to
-                            provide assert expression information. """)
-    group.addoption('--no-assert',
-                    action="store_true",
-                    default=False,
-                    dest="noassert",
-                    help="DEPRECATED equivalent to --assert=plain")
-    group.addoption('--nomagic', '--no-magic',
-                    action="store_true",
-                    default=False,
-                    help="DEPRECATED equivalent to --assert=plain")
-
-
-class AssertionState:
-    """State for the assertion plugin."""
-
-    def __init__(self, config, mode):
-        self.mode = mode
-        self.trace = config.trace.root.get("assertion")
-
-
-def pytest_configure(config):
-    mode = config.getvalue("assertmode")
-    if config.getvalue("noassert") or config.getvalue("nomagic"):
-        mode = "plain"
-    if mode == "rewrite":
-        try:
-            import ast  # noqa
-        except ImportError:
-            mode = "reinterp"
-        else:
-            # Both Jython and CPython 2.6.0 have AST bugs that make the
-            # assertion rewriting hook malfunction.
-            if (sys.platform.startswith('java') or
-                    sys.version_info[:3] == (2, 6, 0)):
-                mode = "reinterp"
-    if mode != "plain":
-        _load_modules(mode)
-        m = monkeypatch()
-        config._cleanup.append(m.undo)
-        m.setattr(py.builtin.builtins, 'AssertionError',
-                  reinterpret.AssertionError)  # noqa
-    hook = None
-    if mode == "rewrite":
-        hook = rewrite.AssertionRewritingHook()  # noqa
-        sys.meta_path.insert(0, hook)
-    warn_about_missing_assertion(mode)
-    config._assertstate = AssertionState(config, mode)
-    config._assertstate.hook = hook
-    config._assertstate.trace("configured with mode set to %r" % (mode,))
-    def undo():
-        hook = config._assertstate.hook
-        if hook is not None and hook in sys.meta_path:
-            sys.meta_path.remove(hook)
-    config.add_cleanup(undo)
-
-
-def pytest_collection(session):
-    # this hook is only called when test modules are collected
-    # so for example not in the master process of pytest-xdist
-    # (which does not collect test modules)
-    hook = session.config._assertstate.hook
-    if hook is not None:
-        hook.set_session(session)
-
-
-def _running_on_ci():
-    """Check if we're currently running on a CI system."""
-    env_vars = ['CI', 'BUILD_NUMBER']
-    return any(var in os.environ for var in env_vars)
-
-
-def pytest_runtest_setup(item):
-    """Setup the pytest_assertrepr_compare hook
-
-    The newinterpret and rewrite modules will use util._reprcompare if
-    it exists to use custom reporting via the
-    pytest_assertrepr_compare hook.  This sets up this custom
-    comparison for the test.
-    """
-    def callbinrepr(op, left, right):
-        """Call the pytest_assertrepr_compare hook and prepare the result
-
-        This uses the first result from the hook and then ensures the
-        following:
-        * Overly verbose explanations are dropped unless -vv was used or
-          running on a CI.
-        * Embedded newlines are escaped to help util.format_explanation()
-          later.
-        * If the rewrite mode is used embedded %-characters are replaced
-          to protect later % formatting.
-
-        The result can be formatted by util.format_explanation() for
-        pretty printing.
-        """
-        hook_result = item.ihook.pytest_assertrepr_compare(
-            config=item.config, op=op, left=left, right=right)
-        for new_expl in hook_result:
-            if new_expl:
-                if (sum(len(p) for p in new_expl[1:]) > 80*8 and
-                        item.config.option.verbose < 2 and
-                        not _running_on_ci()):
-                    show_max = 10
-                    truncated_lines = len(new_expl) - show_max
-                    new_expl[show_max:] = [py.builtin._totext(
-                        'Detailed information truncated (%d more lines)'
-                        ', use "-vv" to show' % truncated_lines)]
-                new_expl = [line.replace("\n", "\\n") for line in new_expl]
-                res = py.builtin._totext("\n~").join(new_expl)
-                if item.config.getvalue("assertmode") == "rewrite":
-                    res = res.replace("%", "%%")
-                return res
-    util._reprcompare = callbinrepr
-
-
-def pytest_runtest_teardown(item):
-    util._reprcompare = None
-
-
-def pytest_sessionfinish(session):
-    hook = session.config._assertstate.hook
-    if hook is not None:
-        hook.session = None
-
-
-def _load_modules(mode):
-    """Lazily import assertion related code."""
-    global rewrite, reinterpret
-    from _pytest.assertion import reinterpret  # noqa
-    if mode == "rewrite":
-        from _pytest.assertion import rewrite  # noqa
-
-
-def warn_about_missing_assertion(mode):
-    try:
-        assert False
-    except AssertionError:
-        pass
-    else:
-        if mode == "rewrite":
-            specifically = ("assertions which are not in test modules "
-                            "will be ignored")
-        else:
-            specifically = "failing tests may report as passing"
-
-        sys.stderr.write("WARNING: " + specifically +
-                         " because assert statements are not executed "
-                         "by the underlying Python interpreter "
-                         "(are you using python -O?)\n")
-
-
-# Expose this plugin's implementation for the pytest_assertrepr_compare hook
-pytest_assertrepr_compare = util.assertrepr_compare
diff --git a/tools/pytest/_pytest/assertion/reinterpret.py b/tools/pytest/_pytest/assertion/reinterpret.py
deleted file mode 100644
index f4262c3..0000000
--- a/tools/pytest/_pytest/assertion/reinterpret.py
+++ /dev/null
@@ -1,407 +0,0 @@
-"""
-Find intermediate evalutation results in assert statements through builtin AST.
-"""
-import ast
-import sys
-
-import _pytest._code
-import py
-from _pytest.assertion import util
-u = py.builtin._totext
-
-
-class AssertionError(util.BuiltinAssertionError):
-    def __init__(self, *args):
-        util.BuiltinAssertionError.__init__(self, *args)
-        if args:
-            # on Python2.6 we get len(args)==2 for: assert 0, (x,y)
-            # on Python2.7 and above we always get len(args) == 1
-            # with args[0] being the (x,y) tuple.
-            if len(args) > 1:
-                toprint = args
-            else:
-                toprint = args[0]
-            try:
-                self.msg = u(toprint)
-            except Exception:
-                self.msg = u(
-                    "<[broken __repr__] %s at %0xd>"
-                    % (toprint.__class__, id(toprint)))
-        else:
-            f = _pytest._code.Frame(sys._getframe(1))
-            try:
-                source = f.code.fullsource
-                if source is not None:
-                    try:
-                        source = source.getstatement(f.lineno, assertion=True)
-                    except IndexError:
-                        source = None
-                    else:
-                        source = str(source.deindent()).strip()
-            except py.error.ENOENT:
-                source = None
-                # this can also occur during reinterpretation, when the
-                # co_filename is set to "<run>".
-            if source:
-                self.msg = reinterpret(source, f, should_fail=True)
-            else:
-                self.msg = "<could not determine information>"
-            if not self.args:
-                self.args = (self.msg,)
-
-if sys.version_info > (3, 0):
-    AssertionError.__module__ = "builtins"
-
-if sys.platform.startswith("java"):
-    # See http://bugs.jython.org/issue1497
-    _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
-              "ListComp", "GeneratorExp", "Yield", "Compare", "Call",
-              "Repr", "Num", "Str", "Attribute", "Subscript", "Name",
-              "List", "Tuple")
-    _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
-              "AugAssign", "Print", "For", "While", "If", "With", "Raise",
-              "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
-              "Exec", "Global", "Expr", "Pass", "Break", "Continue")
-    _expr_nodes = set(getattr(ast, name) for name in _exprs)
-    _stmt_nodes = set(getattr(ast, name) for name in _stmts)
-    def _is_ast_expr(node):
-        return node.__class__ in _expr_nodes
-    def _is_ast_stmt(node):
-        return node.__class__ in _stmt_nodes
-else:
-    def _is_ast_expr(node):
-        return isinstance(node, ast.expr)
-    def _is_ast_stmt(node):
-        return isinstance(node, ast.stmt)
-
-try:
-    _Starred = ast.Starred
-except AttributeError:
-    # Python 2. Define a dummy class so isinstance() will always be False.
-    class _Starred(object): pass
-
-
-class Failure(Exception):
-    """Error found while interpreting AST."""
-
-    def __init__(self, explanation=""):
-        self.cause = sys.exc_info()
-        self.explanation = explanation
-
-
-def reinterpret(source, frame, should_fail=False):
-    mod = ast.parse(source)
-    visitor = DebugInterpreter(frame)
-    try:
-        visitor.visit(mod)
-    except Failure:
-        failure = sys.exc_info()[1]
-        return getfailure(failure)
-    if should_fail:
-        return ("(assertion failed, but when it was re-run for "
-                "printing intermediate values, it did not fail.  Suggestions: "
-                "compute assert expression before the assert or use --assert=plain)")
-
-def run(offending_line, frame=None):
-    if frame is None:
-        frame = _pytest._code.Frame(sys._getframe(1))
-    return reinterpret(offending_line, frame)
-
-def getfailure(e):
-    explanation = util.format_explanation(e.explanation)
-    value = e.cause[1]
-    if str(value):
-        lines = explanation.split('\n')
-        lines[0] += "  << %s" % (value,)
-        explanation = '\n'.join(lines)
-    text = "%s: %s" % (e.cause[0].__name__, explanation)
-    if text.startswith('AssertionError: assert '):
-        text = text[16:]
-    return text
-
-operator_map = {
-    ast.BitOr : "|",
-    ast.BitXor : "^",
-    ast.BitAnd : "&",
-    ast.LShift : "<<",
-    ast.RShift : ">>",
-    ast.Add : "+",
-    ast.Sub : "-",
-    ast.Mult : "*",
-    ast.Div : "/",
-    ast.FloorDiv : "//",
-    ast.Mod : "%",
-    ast.Eq : "==",
-    ast.NotEq : "!=",
-    ast.Lt : "<",
-    ast.LtE : "<=",
-    ast.Gt : ">",
-    ast.GtE : ">=",
-    ast.Pow : "**",
-    ast.Is : "is",
-    ast.IsNot : "is not",
-    ast.In : "in",
-    ast.NotIn : "not in"
-}
-
-unary_map = {
-    ast.Not : "not %s",
-    ast.Invert : "~%s",
-    ast.USub : "-%s",
-    ast.UAdd : "+%s"
-}
-
-
-class DebugInterpreter(ast.NodeVisitor):
-    """Interpret AST nodes to gleam useful debugging information. """
-
-    def __init__(self, frame):
-        self.frame = frame
-
-    def generic_visit(self, node):
-        # Fallback when we don't have a special implementation.
-        if _is_ast_expr(node):
-            mod = ast.Expression(node)
-            co = self._compile(mod)
-            try:
-                result = self.frame.eval(co)
-            except Exception:
-                raise Failure()
-            explanation = self.frame.repr(result)
-            return explanation, result
-        elif _is_ast_stmt(node):
-            mod = ast.Module([node])
-            co = self._compile(mod, "exec")
-            try:
-                self.frame.exec_(co)
-            except Exception:
-                raise Failure()
-            return None, None
-        else:
-            raise AssertionError("can't handle %s" %(node,))
-
-    def _compile(self, source, mode="eval"):
-        return compile(source, "<assertion interpretation>", mode)
-
-    def visit_Expr(self, expr):
-        return self.visit(expr.value)
-
-    def visit_Module(self, mod):
-        for stmt in mod.body:
-            self.visit(stmt)
-
-    def visit_Name(self, name):
-        explanation, result = self.generic_visit(name)
-        # See if the name is local.
-        source = "%r in locals() is not globals()" % (name.id,)
-        co = self._compile(source)
-        try:
-            local = self.frame.eval(co)
-        except Exception:
-            # have to assume it isn't
-            local = None
-        if local is None or not self.frame.is_true(local):
-            return name.id, result
-        return explanation, result
-
-    def visit_Compare(self, comp):
-        left = comp.left
-        left_explanation, left_result = self.visit(left)
-        for op, next_op in zip(comp.ops, comp.comparators):
-            next_explanation, next_result = self.visit(next_op)
-            op_symbol = operator_map[op.__class__]
-            explanation = "%s %s %s" % (left_explanation, op_symbol,
-                                        next_explanation)
-            source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
-            co = self._compile(source)
-            try:
-                result = self.frame.eval(co, __exprinfo_left=left_result,
-                                         __exprinfo_right=next_result)
-            except Exception:
-                raise Failure(explanation)
-            try:
-                if not self.frame.is_true(result):
-                    break
-            except KeyboardInterrupt:
-                raise
-            except:
-                break
-            left_explanation, left_result = next_explanation, next_result
-
-        if util._reprcompare is not None:
-            res = util._reprcompare(op_symbol, left_result, next_result)
-            if res:
-                explanation = res
-        return explanation, result
-
-    def visit_BoolOp(self, boolop):
-        is_or = isinstance(boolop.op, ast.Or)
-        explanations = []
-        for operand in boolop.values:
-            explanation, result = self.visit(operand)
-            explanations.append(explanation)
-            if result == is_or:
-                break
-        name = is_or and " or " or " and "
-        explanation = "(" + name.join(explanations) + ")"
-        return explanation, result
-
-    def visit_UnaryOp(self, unary):
-        pattern = unary_map[unary.op.__class__]
-        operand_explanation, operand_result = self.visit(unary.operand)
-        explanation = pattern % (operand_explanation,)
-        co = self._compile(pattern % ("__exprinfo_expr",))
-        try:
-            result = self.frame.eval(co, __exprinfo_expr=operand_result)
-        except Exception:
-            raise Failure(explanation)
-        return explanation, result
-
-    def visit_BinOp(self, binop):
-        left_explanation, left_result = self.visit(binop.left)
-        right_explanation, right_result = self.visit(binop.right)
-        symbol = operator_map[binop.op.__class__]
-        explanation = "(%s %s %s)" % (left_explanation, symbol,
-                                      right_explanation)
-        source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
-        co = self._compile(source)
-        try:
-            result = self.frame.eval(co, __exprinfo_left=left_result,
-                                     __exprinfo_right=right_result)
-        except Exception:
-            raise Failure(explanation)
-        return explanation, result
-
-    def visit_Call(self, call):
-        func_explanation, func = self.visit(call.func)
-        arg_explanations = []
-        ns = {"__exprinfo_func" : func}
-        arguments = []
-        for arg in call.args:
-            arg_explanation, arg_result = self.visit(arg)
-            if isinstance(arg, _Starred):
-                arg_name = "__exprinfo_star"
-                ns[arg_name] = arg_result
-                arguments.append("*%s" % (arg_name,))
-                arg_explanations.append("*%s" % (arg_explanation,))
-            else:
-                arg_name = "__exprinfo_%s" % (len(ns),)
-                ns[arg_name] = arg_result
-                arguments.append(arg_name)
-                arg_explanations.append(arg_explanation)
-        for keyword in call.keywords:
-            arg_explanation, arg_result = self.visit(keyword.value)
-            if keyword.arg:
-                arg_name = "__exprinfo_%s" % (len(ns),)
-                keyword_source = "%s=%%s" % (keyword.arg)
-                arguments.append(keyword_source % (arg_name,))
-                arg_explanations.append(keyword_source % (arg_explanation,))
-            else:
-                arg_name = "__exprinfo_kwds"
-                arguments.append("**%s" % (arg_name,))
-                arg_explanations.append("**%s" % (arg_explanation,))
-
-            ns[arg_name] = arg_result
-
-        if getattr(call, 'starargs', None):
-            arg_explanation, arg_result = self.visit(call.starargs)
-            arg_name = "__exprinfo_star"
-            ns[arg_name] = arg_result
-            arguments.append("*%s" % (arg_name,))
-            arg_explanations.append("*%s" % (arg_explanation,))
-
-        if getattr(call, 'kwargs', None):
-            arg_explanation, arg_result = self.visit(call.kwargs)
-            arg_name = "__exprinfo_kwds"
-            ns[arg_name] = arg_result
-            arguments.append("**%s" % (arg_name,))
-            arg_explanations.append("**%s" % (arg_explanation,))
-        args_explained = ", ".join(arg_explanations)
-        explanation = "%s(%s)" % (func_explanation, args_explained)
-        args = ", ".join(arguments)
-        source = "__exprinfo_func(%s)" % (args,)
-        co = self._compile(source)
-        try:
-            result = self.frame.eval(co, **ns)
-        except Exception:
-            raise Failure(explanation)
-        pattern = "%s\n{%s = %s\n}"
-        rep = self.frame.repr(result)
-        explanation = pattern % (rep, rep, explanation)
-        return explanation, result
-
-    def _is_builtin_name(self, name):
-        pattern = "%r not in globals() and %r not in locals()"
-        source = pattern % (name.id, name.id)
-        co = self._compile(source)
-        try:
-            return self.frame.eval(co)
-        except Exception:
-            return False
-
-    def visit_Attribute(self, attr):
-        if not isinstance(attr.ctx, ast.Load):
-            return self.generic_visit(attr)
-        source_explanation, source_result = self.visit(attr.value)
-        explanation = "%s.%s" % (source_explanation, attr.attr)
-        source = "__exprinfo_expr.%s" % (attr.attr,)
-        co = self._compile(source)
-        try:
-            try:
-                result = self.frame.eval(co, __exprinfo_expr=source_result)
-            except AttributeError:
-                # Maybe the attribute name needs to be mangled?
-                if not attr.attr.startswith("__") or attr.attr.endswith("__"):
-                    raise
-                source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
-                co = self._compile(source)
-                class_name = self.frame.eval(co, __exprinfo_expr=source_result)
-                mangled_attr = "_" + class_name +  attr.attr
-                source = "__exprinfo_expr.%s" % (mangled_attr,)
-                co = self._compile(source)
-                result = self.frame.eval(co, __exprinfo_expr=source_result)
-        except Exception:
-            raise Failure(explanation)
-        explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
-                                              self.frame.repr(result),
-                                              source_explanation, attr.attr)
-        # Check if the attr is from an instance.
-        source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
-        source = source % (attr.attr,)
-        co = self._compile(source)
-        try:
-            from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
-        except Exception:
-            from_instance = None
-        if from_instance is None or self.frame.is_true(from_instance):
-            rep = self.frame.repr(result)
-            pattern = "%s\n{%s = %s\n}"
-            explanation = pattern % (rep, rep, explanation)
-        return explanation, result
-
-    def visit_Assert(self, assrt):
-        test_explanation, test_result = self.visit(assrt.test)
-        explanation = "assert %s" % (test_explanation,)
-        if not self.frame.is_true(test_result):
-            try:
-                raise util.BuiltinAssertionError
-            except Exception:
-                raise Failure(explanation)
-        return explanation, test_result
-
-    def visit_Assign(self, assign):
-        value_explanation, value_result = self.visit(assign.value)
-        explanation = "... = %s" % (value_explanation,)
-        name = ast.Name("__exprinfo_expr", ast.Load(),
-                        lineno=assign.value.lineno,
-                        col_offset=assign.value.col_offset)
-        new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
-                                col_offset=assign.col_offset)
-        mod = ast.Module([new_assign])
-        co = self._compile(mod, "exec")
-        try:
-            self.frame.exec_(co, __exprinfo_expr=value_result)
-        except Exception:
-            raise Failure(explanation)
-        return explanation, value_result
-
diff --git a/tools/pytest/_pytest/assertion/rewrite.py b/tools/pytest/_pytest/assertion/rewrite.py
deleted file mode 100644
index 14b8e49..0000000
--- a/tools/pytest/_pytest/assertion/rewrite.py
+++ /dev/null
@@ -1,885 +0,0 @@
-"""Rewrite assertion AST to produce nice error messages"""
-
-import ast
-import errno
-import itertools
-import imp
-import marshal
-import os
-import re
-import struct
-import sys
-import types
-
-import py
-from _pytest.assertion import util
-
-
-# pytest caches rewritten pycs in __pycache__.
-if hasattr(imp, "get_tag"):
-    PYTEST_TAG = imp.get_tag() + "-PYTEST"
-else:
-    if hasattr(sys, "pypy_version_info"):
-        impl = "pypy"
-    elif sys.platform == "java":
-        impl = "jython"
-    else:
-        impl = "cpython"
-    ver = sys.version_info
-    PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
-    del ver, impl
-
-PYC_EXT = ".py" + (__debug__ and "c" or "o")
-PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
-
-REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
-ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
-
-if sys.version_info >= (3,5):
-    ast_Call = ast.Call
-else:
-    ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None)
-
-
-class AssertionRewritingHook(object):
-    """PEP302 Import hook which rewrites asserts."""
-
-    def __init__(self):
-        self.session = None
-        self.modules = {}
-        self._register_with_pkg_resources()
-
-    def set_session(self, session):
-        self.fnpats = session.config.getini("python_files")
-        self.session = session
-
-    def find_module(self, name, path=None):
-        if self.session is None:
-            return None
-        sess = self.session
-        state = sess.config._assertstate
-        state.trace("find_module called for: %s" % name)
-        names = name.rsplit(".", 1)
-        lastname = names[-1]
-        pth = None
-        if path is not None:
-            # Starting with Python 3.3, path is a _NamespacePath(), which
-            # causes problems if not converted to list.
-            path = list(path)
-            if len(path) == 1:
-                pth = path[0]
-        if pth is None:
-            try:
-                fd, fn, desc = imp.find_module(lastname, path)
-            except ImportError:
-                return None
-            if fd is not None:
-                fd.close()
-            tp = desc[2]
-            if tp == imp.PY_COMPILED:
-                if hasattr(imp, "source_from_cache"):
-                    fn = imp.source_from_cache(fn)
-                else:
-                    fn = fn[:-1]
-            elif tp != imp.PY_SOURCE:
-                # Don't know what this is.
-                return None
-        else:
-            fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
-        fn_pypath = py.path.local(fn)
-        # Is this a test file?
-        if not sess.isinitpath(fn):
-            # We have to be very careful here because imports in this code can
-            # trigger a cycle.
-            self.session = None
-            try:
-                for pat in self.fnpats:
-                    if fn_pypath.fnmatch(pat):
-                        state.trace("matched test file %r" % (fn,))
-                        break
-                else:
-                    return None
-            finally:
-                self.session = sess
-        else:
-            state.trace("matched test file (was specified on cmdline): %r" %
-                        (fn,))
-        # The requested module looks like a test file, so rewrite it. This is
-        # the most magical part of the process: load the source, rewrite the
-        # asserts, and load the rewritten source. We also cache the rewritten
-        # module code in a special pyc. We must be aware of the possibility of
-        # concurrent pytest processes rewriting and loading pycs. To avoid
-        # tricky race conditions, we maintain the following invariant: The
-        # cached pyc is always a complete, valid pyc. Operations on it must be
-        # atomic. POSIX's atomic rename comes in handy.
-        write = not sys.dont_write_bytecode
-        cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
-        if write:
-            try:
-                os.mkdir(cache_dir)
-            except OSError:
-                e = sys.exc_info()[1].errno
-                if e == errno.EEXIST:
-                    # Either the __pycache__ directory already exists (the
-                    # common case) or it's blocked by a non-dir node. In the
-                    # latter case, we'll ignore it in _write_pyc.
-                    pass
-                elif e in [errno.ENOENT, errno.ENOTDIR]:
-                    # One of the path components was not a directory, likely
-                    # because we're in a zip file.
-                    write = False
-                elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
-                    state.trace("read only directory: %r" % fn_pypath.dirname)
-                    write = False
-                else:
-                    raise
-        cache_name = fn_pypath.basename[:-3] + PYC_TAIL
-        pyc = os.path.join(cache_dir, cache_name)
-        # Notice that even if we're in a read-only directory, I'm going
-        # to check for a cached pyc. This may not be optimal...
-        co = _read_pyc(fn_pypath, pyc, state.trace)
-        if co is None:
-            state.trace("rewriting %r" % (fn,))
-            source_stat, co = _rewrite_test(state, fn_pypath)
-            if co is None:
-                # Probably a SyntaxError in the test.
-                return None
-            if write:
-                _make_rewritten_pyc(state, source_stat, pyc, co)
-        else:
-            state.trace("found cached rewritten pyc for %r" % (fn,))
-        self.modules[name] = co, pyc
-        return self
-
-    def load_module(self, name):
-        # If there is an existing module object named 'fullname' in
-        # sys.modules, the loader must use that existing module. (Otherwise,
-        # the reload() builtin will not work correctly.)
-        if name in sys.modules:
-            return sys.modules[name]
-
-        co, pyc = self.modules.pop(name)
-        # I wish I could just call imp.load_compiled here, but __file__ has to
-        # be set properly. In Python 3.2+, this all would be handled correctly
-        # by load_compiled.
-        mod = sys.modules[name] = imp.new_module(name)
-        try:
-            mod.__file__ = co.co_filename
-            # Normally, this attribute is 3.2+.
-            mod.__cached__ = pyc
-            mod.__loader__ = self
-            py.builtin.exec_(co, mod.__dict__)
-        except:
-            del sys.modules[name]
-            raise
-        return sys.modules[name]
-
-
-
-    def is_package(self, name):
-        try:
-            fd, fn, desc = imp.find_module(name)
-        except ImportError:
-            return False
-        if fd is not None:
-            fd.close()
-        tp = desc[2]
-        return tp == imp.PKG_DIRECTORY
-
-    @classmethod
-    def _register_with_pkg_resources(cls):
-        """
-        Ensure package resources can be loaded from this loader. May be called
-        multiple times, as the operation is idempotent.
-        """
-        try:
-            import pkg_resources
-            # access an attribute in case a deferred importer is present
-            pkg_resources.__name__
-        except ImportError:
-            return
-
-        # Since pytest tests are always located in the file system, the
-        #  DefaultProvider is appropriate.
-        pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider)
-
-    def get_data(self, pathname):
-        """Optional PEP302 get_data API.
-        """
-        with open(pathname, 'rb') as f:
-            return f.read()
-
-
-def _write_pyc(state, co, source_stat, pyc):
-    # Technically, we don't have to have the same pyc format as
-    # (C)Python, since these "pycs" should never be seen by builtin
-    # import. However, there's little reason deviate, and I hope
-    # sometime to be able to use imp.load_compiled to load them. (See
-    # the comment in load_module above.)
-    try:
-        fp = open(pyc, "wb")
-    except IOError:
-        err = sys.exc_info()[1].errno
-        state.trace("error writing pyc file at %s: errno=%s" %(pyc, err))
-        # we ignore any failure to write the cache file
-        # there are many reasons, permission-denied, __pycache__ being a
-        # file etc.
-        return False
-    try:
-        fp.write(imp.get_magic())
-        mtime = int(source_stat.mtime)
-        size = source_stat.size & 0xFFFFFFFF
-        fp.write(struct.pack("<ll", mtime, size))
-        marshal.dump(co, fp)
-    finally:
-        fp.close()
-    return True
-
-RN = "\r\n".encode("utf-8")
-N = "\n".encode("utf-8")
-
-cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
-BOM_UTF8 = '\xef\xbb\xbf'
-
-def _rewrite_test(state, fn):
-    """Try to read and rewrite *fn* and return the code object."""
-    try:
-        stat = fn.stat()
-        source = fn.read("rb")
-    except EnvironmentError:
-        return None, None
-    if ASCII_IS_DEFAULT_ENCODING:
-        # ASCII is the default encoding in Python 2. Without a coding
-        # declaration, Python 2 will complain about any bytes in the file
-        # outside the ASCII range. Sadly, this behavior does not extend to
-        # compile() or ast.parse(), which prefer to interpret the bytes as
-        # latin-1. (At least they properly handle explicit coding cookies.) To
-        # preserve this error behavior, we could force ast.parse() to use ASCII
-        # as the encoding by inserting a coding cookie. Unfortunately, that
-        # messes up line numbers. Thus, we have to check ourselves if anything
-        # is outside the ASCII range in the case no encoding is explicitly
-        # declared. For more context, see issue #269. Yay for Python 3 which
-        # gets this right.
-        end1 = source.find("\n")
-        end2 = source.find("\n", end1 + 1)
-        if (not source.startswith(BOM_UTF8) and
-            cookie_re.match(source[0:end1]) is None and
-            cookie_re.match(source[end1 + 1:end2]) is None):
-            if hasattr(state, "_indecode"):
-                # encodings imported us again, so don't rewrite.
-                return None, None
-            state._indecode = True
-            try:
-                try:
-                    source.decode("ascii")
-                except UnicodeDecodeError:
-                    # Let it fail in real import.
-                    return None, None
-            finally:
-                del state._indecode
-    # On Python versions which are not 2.7 and less than or equal to 3.1, the
-    # parser expects *nix newlines.
-    if REWRITE_NEWLINES:
-        source = source.replace(RN, N) + N
-    try:
-        tree = ast.parse(source)
-    except SyntaxError:
-        # Let this pop up again in the real import.
-        state.trace("failed to parse: %r" % (fn,))
-        return None, None
-    rewrite_asserts(tree)
-    try:
-        co = compile(tree, fn.strpath, "exec")
-    except SyntaxError:
-        # It's possible that this error is from some bug in the
-        # assertion rewriting, but I don't know of a fast way to tell.
-        state.trace("failed to compile: %r" % (fn,))
-        return None, None
-    return stat, co
-
-def _make_rewritten_pyc(state, source_stat, pyc, co):
-    """Try to dump rewritten code to *pyc*."""
-    if sys.platform.startswith("win"):
-        # Windows grants exclusive access to open files and doesn't have atomic
-        # rename, so just write into the final file.
-        _write_pyc(state, co, source_stat, pyc)
-    else:
-        # When not on windows, assume rename is atomic. Dump the code object
-        # into a file specific to this process and atomically replace it.
-        proc_pyc = pyc + "." + str(os.getpid())
-        if _write_pyc(state, co, source_stat, proc_pyc):
-            os.rename(proc_pyc, pyc)
-
-def _read_pyc(source, pyc, trace=lambda x: None):
-    """Possibly read a pytest pyc containing rewritten code.
-
-    Return rewritten code if successful or None if not.
-    """
-    try:
-        fp = open(pyc, "rb")
-    except IOError:
-        return None
-    with fp:
-        try:
-            mtime = int(source.mtime())
-            size = source.size()
-            data = fp.read(12)
-        except EnvironmentError as e:
-            trace('_read_pyc(%s): EnvironmentError %s' % (source, e))
-            return None
-        # Check for invalid or out of date pyc file.
-        if (len(data) != 12 or data[:4] != imp.get_magic() or
-                struct.unpack("<ll", data[4:]) != (mtime, size)):
-            trace('_read_pyc(%s): invalid or out of date pyc' % source)
-            return None
-        try:
-            co = marshal.load(fp)
-        except Exception as e:
-            trace('_read_pyc(%s): marshal.load error %s' % (source, e))
-            return None
-        if not isinstance(co, types.CodeType):
-            trace('_read_pyc(%s): not a code object' % source)
-            return None
-        return co
-
-
-def rewrite_asserts(mod):
-    """Rewrite the assert statements in mod."""
-    AssertionRewriter().run(mod)
-
-
-def _saferepr(obj):
-    """Get a safe repr of an object for assertion error messages.
-
-    The assertion formatting (util.format_explanation()) requires
-    newlines to be escaped since they are a special character for it.
-    Normally assertion.util.format_explanation() does this but for a
-    custom repr it is possible to contain one of the special escape
-    sequences, especially '\n{' and '\n}' are likely to be present in
-    JSON reprs.
-
-    """
-    repr = py.io.saferepr(obj)
-    if py.builtin._istext(repr):
-        t = py.builtin.text
-    else:
-        t = py.builtin.bytes
-    return repr.replace(t("\n"), t("\\n"))
-
-
-from _pytest.assertion.util import format_explanation as _format_explanation # noqa
-
-def _format_assertmsg(obj):
-    """Format the custom assertion message given.
-
-    For strings this simply replaces newlines with '\n~' so that
-    util.format_explanation() will preserve them instead of escaping
-    newlines.  For other objects py.io.saferepr() is used first.
-
-    """
-    # reprlib appears to have a bug which means that if a string
-    # contains a newline it gets escaped, however if an object has a
-    # .__repr__() which contains newlines it does not get escaped.
-    # However in either case we want to preserve the newline.
-    if py.builtin._istext(obj) or py.builtin._isbytes(obj):
-        s = obj
-        is_repr = False
-    else:
-        s = py.io.saferepr(obj)
-        is_repr = True
-    if py.builtin._istext(s):
-        t = py.builtin.text
-    else:
-        t = py.builtin.bytes
-    s = s.replace(t("\n"), t("\n~")).replace(t("%"), t("%%"))
-    if is_repr:
-        s = s.replace(t("\\n"), t("\n~"))
-    return s
-
-def _should_repr_global_name(obj):
-    return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
-
-def _format_boolop(explanations, is_or):
-    explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
-    if py.builtin._istext(explanation):
-        t = py.builtin.text
-    else:
-        t = py.builtin.bytes
-    return explanation.replace(t('%'), t('%%'))
-
-def _call_reprcompare(ops, results, expls, each_obj):
-    for i, res, expl in zip(range(len(ops)), results, expls):
-        try:
-            done = not res
-        except Exception:
-            done = True
-        if done:
-            break
-    if util._reprcompare is not None:
-        custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
-        if custom is not None:
-            return custom
-    return expl
-
-
-unary_map = {
-    ast.Not: "not %s",
-    ast.Invert: "~%s",
-    ast.USub: "-%s",
-    ast.UAdd: "+%s"
-}
-
-binop_map = {
-    ast.BitOr: "|",
-    ast.BitXor: "^",
-    ast.BitAnd: "&",
-    ast.LShift: "<<",
-    ast.RShift: ">>",
-    ast.Add: "+",
-    ast.Sub: "-",
-    ast.Mult: "*",
-    ast.Div: "/",
-    ast.FloorDiv: "//",
-    ast.Mod: "%%", # escaped for string formatting
-    ast.Eq: "==",
-    ast.NotEq: "!=",
-    ast.Lt: "<",
-    ast.LtE: "<=",
-    ast.Gt: ">",
-    ast.GtE: ">=",
-    ast.Pow: "**",
-    ast.Is: "is",
-    ast.IsNot: "is not",
-    ast.In: "in",
-    ast.NotIn: "not in"
-}
-# Python 3.5+ compatibility
-try:
-    binop_map[ast.MatMult] = "@"
-except AttributeError:
-    pass
-
-# Python 3.4+ compatibility
-if hasattr(ast, "NameConstant"):
-    _NameConstant = ast.NameConstant
-else:
-    def _NameConstant(c):
-        return ast.Name(str(c), ast.Load())
-
-
-def set_location(node, lineno, col_offset):
-    """Set node location information recursively."""
-    def _fix(node, lineno, col_offset):
-        if "lineno" in node._attributes:
-            node.lineno = lineno
-        if "col_offset" in node._attributes:
-            node.col_offset = col_offset
-        for child in ast.iter_child_nodes(node):
-            _fix(child, lineno, col_offset)
-    _fix(node, lineno, col_offset)
-    return node
-
-
-class AssertionRewriter(ast.NodeVisitor):
-    """Assertion rewriting implementation.
-
-    The main entrypoint is to call .run() with an ast.Module instance,
-    this will then find all the assert statements and re-write them to
-    provide intermediate values and a detailed assertion error.  See
-    http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html
-    for an overview of how this works.
-
-    The entry point here is .run() which will iterate over all the
-    statements in an ast.Module and for each ast.Assert statement it
-    finds call .visit() with it.  Then .visit_Assert() takes over and
-    is responsible for creating new ast statements to replace the
-    original assert statement: it re-writes the test of an assertion
-    to provide intermediate values and replace it with an if statement
-    which raises an assertion error with a detailed explanation in
-    case the expression is false.
-
-    For this .visit_Assert() uses the visitor pattern to visit all the
-    AST nodes of the ast.Assert.test field, each visit call returning
-    an AST node and the corresponding explanation string.  During this
-    state is kept in several instance attributes:
-
-    :statements: All the AST statements which will replace the assert
-       statement.
-
-    :variables: This is populated by .variable() with each variable
-       used by the statements so that they can all be set to None at
-       the end of the statements.
-
-    :variable_counter: Counter to create new unique variables needed
-       by statements.  Variables are created using .variable() and
-       have the form of "@py_assert0".
-
-    :on_failure: The AST statements which will be executed if the
-       assertion test fails.  This is the code which will construct
-       the failure message and raises the AssertionError.
-
-    :explanation_specifiers: A dict filled by .explanation_param()
-       with %-formatting placeholders and their corresponding
-       expressions to use in the building of an assertion message.
-       This is used by .pop_format_context() to build a message.
-
-    :stack: A stack of the explanation_specifiers dicts maintained by
-       .push_format_context() and .pop_format_context() which allows
-       to build another %-formatted string while already building one.
-
-    This state is reset on every new assert statement visited and used
-    by the other visitors.
-
-    """
-
-    def run(self, mod):
-        """Find all assert statements in *mod* and rewrite them."""
-        if not mod.body:
-            # Nothing to do.
-            return
-        # Insert some special imports at the top of the module but after any
-        # docstrings and __future__ imports.
-        aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
-                   ast.alias("_pytest.assertion.rewrite", "@pytest_ar")]
-        expect_docstring = True
-        pos = 0
-        lineno = 0
-        for item in mod.body:
-            if (expect_docstring and isinstance(item, ast.Expr) and
-                    isinstance(item.value, ast.Str)):
-                doc = item.value.s
-                if "PYTEST_DONT_REWRITE" in doc:
-                    # The module has disabled assertion rewriting.
-                    return
-                lineno += len(doc) - 1
-                expect_docstring = False
-            elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or
-                  item.module != "__future__"):
-                lineno = item.lineno
-                break
-            pos += 1
-        imports = [ast.Import([alias], lineno=lineno, col_offset=0)
-                   for alias in aliases]
-        mod.body[pos:pos] = imports
-        # Collect asserts.
-        nodes = [mod]
-        while nodes:
-            node = nodes.pop()
-            for name, field in ast.iter_fields(node):
-                if isinstance(field, list):
-                    new = []
-                    for i, child in enumerate(field):
-                        if isinstance(child, ast.Assert):
-                            # Transform assert.
-                            new.extend(self.visit(child))
-                        else:
-                            new.append(child)
-                            if isinstance(child, ast.AST):
-                                nodes.append(child)
-                    setattr(node, name, new)
-                elif (isinstance(field, ast.AST) and
-                      # Don't recurse into expressions as they can't contain
-                      # asserts.
-                      not isinstance(field, ast.expr)):
-                    nodes.append(field)
-
-    def variable(self):
-        """Get a new variable."""
-        # Use a character invalid in python identifiers to avoid clashing.
-        name = "@py_assert" + str(next(self.variable_counter))
-        self.variables.append(name)
-        return name
-
-    def assign(self, expr):
-        """Give *expr* a name."""
-        name = self.variable()
-        self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
-        return ast.Name(name, ast.Load())
-
-    def display(self, expr):
-        """Call py.io.saferepr on the expression."""
-        return self.helper("saferepr", expr)
-
-    def helper(self, name, *args):
-        """Call a helper in this module."""
-        py_name = ast.Name("@pytest_ar", ast.Load())
-        attr = ast.Attribute(py_name, "_" + name, ast.Load())
-        return ast_Call(attr, list(args), [])
-
-    def builtin(self, name):
-        """Return the builtin called *name*."""
-        builtin_name = ast.Name("@py_builtins", ast.Load())
-        return ast.Attribute(builtin_name, name, ast.Load())
-
-    def explanation_param(self, expr):
-        """Return a new named %-formatting placeholder for expr.
-
-        This creates a %-formatting placeholder for expr in the
-        current formatting context, e.g. ``%(py0)s``.  The placeholder
-        and expr are placed in the current format context so that it
-        can be used on the next call to .pop_format_context().
-
-        """
-        specifier = "py" + str(next(self.variable_counter))
-        self.explanation_specifiers[specifier] = expr
-        return "%(" + specifier + ")s"
-
-    def push_format_context(self):
-        """Create a new formatting context.
-
-        The format context is used for when an explanation wants to
-        have a variable value formatted in the assertion message.  In
-        this case the value required can be added using
-        .explanation_param().  Finally .pop_format_context() is used
-        to format a string of %-formatted values as added by
-        .explanation_param().
-
-        """
-        self.explanation_specifiers = {}
-        self.stack.append(self.explanation_specifiers)
-
-    def pop_format_context(self, expl_expr):
-        """Format the %-formatted string with current format context.
-
-        The expl_expr should be an ast.Str instance constructed from
-        the %-placeholders created by .explanation_param().  This will
-        add the required code to format said string to .on_failure and
-        return the ast.Name instance of the formatted string.
-
-        """
-        current = self.stack.pop()
-        if self.stack:
-            self.explanation_specifiers = self.stack[-1]
-        keys = [ast.Str(key) for key in current.keys()]
-        format_dict = ast.Dict(keys, list(current.values()))
-        form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
-        name = "@py_format" + str(next(self.variable_counter))
-        self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
-        return ast.Name(name, ast.Load())
-
-    def generic_visit(self, node):
-        """Handle expressions we don't have custom code for."""
-        assert isinstance(node, ast.expr)
-        res = self.assign(node)
-        return res, self.explanation_param(self.display(res))
-
-    def visit_Assert(self, assert_):
-        """Return the AST statements to replace the ast.Assert instance.
-
-        This re-writes the test of an assertion to provide
-        intermediate values and replace it with an if statement which
-        raises an assertion error with a detailed explanation in case
-        the expression is false.
-
-        """
-        self.statements = []
-        self.variables = []
-        self.variable_counter = itertools.count()
-        self.stack = []
-        self.on_failure = []
-        self.push_format_context()
-        # Rewrite assert into a bunch of statements.
-        top_condition, explanation = self.visit(assert_.test)
-        # Create failure message.
-        body = self.on_failure
-        negation = ast.UnaryOp(ast.Not(), top_condition)
-        self.statements.append(ast.If(negation, body, []))
-        if assert_.msg:
-            assertmsg = self.helper('format_assertmsg', assert_.msg)
-            explanation = "\n>assert " + explanation
-        else:
-            assertmsg = ast.Str("")
-            explanation = "assert " + explanation
-        template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
-        msg = self.pop_format_context(template)
-        fmt = self.helper("format_explanation", msg)
-        err_name = ast.Name("AssertionError", ast.Load())
-        exc = ast_Call(err_name, [fmt], [])
-        if sys.version_info[0] >= 3:
-            raise_ = ast.Raise(exc, None)
-        else:
-            raise_ = ast.Raise(exc, None, None)
-        body.append(raise_)
-        # Clear temporary variables by setting them to None.
-        if self.variables:
-            variables = [ast.Name(name, ast.Store())
-                         for name in self.variables]
-            clear = ast.Assign(variables, _NameConstant(None))
-            self.statements.append(clear)
-        # Fix line numbers.
-        for stmt in self.statements:
-            set_location(stmt, assert_.lineno, assert_.col_offset)
-        return self.statements
-
-    def visit_Name(self, name):
-        # Display the repr of the name if it's a local variable or
-        # _should_repr_global_name() thinks it's acceptable.
-        locs = ast_Call(self.builtin("locals"), [], [])
-        inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
-        dorepr = self.helper("should_repr_global_name", name)
-        test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
-        expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
-        return name, self.explanation_param(expr)
-
-    def visit_BoolOp(self, boolop):
-        res_var = self.variable()
-        expl_list = self.assign(ast.List([], ast.Load()))
-        app = ast.Attribute(expl_list, "append", ast.Load())
-        is_or = int(isinstance(boolop.op, ast.Or))
-        body = save = self.statements
-        fail_save = self.on_failure
-        levels = len(boolop.values) - 1
-        self.push_format_context()
-        # Process each operand, short-circuting if needed.
-        for i, v in enumerate(boolop.values):
-            if i:
-                fail_inner = []
-                # cond is set in a prior loop iteration below
-                self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
-                self.on_failure = fail_inner
-            self.push_format_context()
-            res, expl = self.visit(v)
-            body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
-            expl_format = self.pop_format_context(ast.Str(expl))
-            call = ast_Call(app, [expl_format], [])
-            self.on_failure.append(ast.Expr(call))
-            if i < levels:
-                cond = res
-                if is_or:
-                    cond = ast.UnaryOp(ast.Not(), cond)
-                inner = []
-                self.statements.append(ast.If(cond, inner, []))
-                self.statements = body = inner
-        self.statements = save
-        self.on_failure = fail_save
-        expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or))
-        expl = self.pop_format_context(expl_template)
-        return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
-
-    def visit_UnaryOp(self, unary):
-        pattern = unary_map[unary.op.__class__]
-        operand_res, operand_expl = self.visit(unary.operand)
-        res = self.assign(ast.UnaryOp(unary.op, operand_res))
-        return res, pattern % (operand_expl,)
-
-    def visit_BinOp(self, binop):
-        symbol = binop_map[binop.op.__class__]
-        left_expr, left_expl = self.visit(binop.left)
-        right_expr, right_expl = self.visit(binop.right)
-        explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
-        res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
-        return res, explanation
-
-    def visit_Call_35(self, call):
-        """
-        visit `ast.Call` nodes on Python3.5 and after
-        """
-        new_func, func_expl = self.visit(call.func)
-        arg_expls = []
-        new_args = []
-        new_kwargs = []
-        for arg in call.args:
-            res, expl = self.visit(arg)
-            arg_expls.append(expl)
-            new_args.append(res)
-        for keyword in call.keywords:
-            res, expl = self.visit(keyword.value)
-            new_kwargs.append(ast.keyword(keyword.arg, res))
-            if keyword.arg:
-                arg_expls.append(keyword.arg + "=" + expl)
-            else: ## **args have `arg` keywords with an .arg of None
-                arg_expls.append("**" + expl)
-
-        expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
-        new_call = ast.Call(new_func, new_args, new_kwargs)
-        res = self.assign(new_call)
-        res_expl = self.explanation_param(self.display(res))
-        outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
-        return res, outer_expl
-
-    def visit_Starred(self, starred):
-        # From Python 3.5, a Starred node can appear in a function call
-        res, expl = self.visit(starred.value)
-        return starred, '*' + expl
-
-    def visit_Call_legacy(self, call):
-        """
-        visit `ast.Call nodes on 3.4 and below`
-        """
-        new_func, func_expl = self.visit(call.func)
-        arg_expls = []
-        new_args = []
-        new_kwargs = []
-        new_star = new_kwarg = None
-        for arg in call.args:
-            res, expl = self.visit(arg)
-            new_args.append(res)
-            arg_expls.append(expl)
-        for keyword in call.keywords:
-            res, expl = self.visit(keyword.value)
-            new_kwargs.append(ast.keyword(keyword.arg, res))
-            arg_expls.append(keyword.arg + "=" + expl)
-        if call.starargs:
-            new_star, expl = self.visit(call.starargs)
-            arg_expls.append("*" + expl)
-        if call.kwargs:
-            new_kwarg, expl = self.visit(call.kwargs)
-            arg_expls.append("**" + expl)
-        expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
-        new_call = ast.Call(new_func, new_args, new_kwargs,
-                            new_star, new_kwarg)
-        res = self.assign(new_call)
-        res_expl = self.explanation_param(self.display(res))
-        outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
-        return res, outer_expl
-
-    # ast.Call signature changed on 3.5,
-    # conditionally change  which methods is named
-    # visit_Call depending on Python version
-    if sys.version_info >= (3, 5):
-        visit_Call = visit_Call_35
-    else:
-        visit_Call = visit_Call_legacy
-
-
-    def visit_Attribute(self, attr):
-        if not isinstance(attr.ctx, ast.Load):
-            return self.generic_visit(attr)
-        value, value_expl = self.visit(attr.value)
-        res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
-        res_expl = self.explanation_param(self.display(res))
-        pat = "%s\n{%s = %s.%s\n}"
-        expl = pat % (res_expl, res_expl, value_expl, attr.attr)
-        return res, expl
-
-    def visit_Compare(self, comp):
-        self.push_format_context()
-        left_res, left_expl = self.visit(comp.left)
-        res_variables = [self.variable() for i in range(len(comp.ops))]
-        load_names = [ast.Name(v, ast.Load()) for v in res_variables]
-        store_names = [ast.Name(v, ast.Store()) for v in res_variables]
-        it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
-        expls = []
-        syms = []
-        results = [left_res]
-        for i, op, next_operand in it:
-            next_res, next_expl = self.visit(next_operand)
-            results.append(next_res)
-            sym = binop_map[op.__class__]
-            syms.append(ast.Str(sym))
-            expl = "%s %s %s" % (left_expl, sym, next_expl)
-            expls.append(ast.Str(expl))
-            res_expr = ast.Compare(left_res, [op], [next_res])
-            self.statements.append(ast.Assign([store_names[i]], res_expr))
-            left_res, left_expl = next_res, next_expl
-        # Use pytest.assertion.util._reprcompare if that's available.
-        expl_call = self.helper("call_reprcompare",
-                                ast.Tuple(syms, ast.Load()),
-                                ast.Tuple(load_names, ast.Load()),
-                                ast.Tuple(expls, ast.Load()),
-                                ast.Tuple(results, ast.Load()))
-        if len(comp.ops) > 1:
-            res = ast.BoolOp(ast.And(), load_names)
-        else:
-            res = load_names[0]
-        return res, self.explanation_param(self.pop_format_context(expl_call))
diff --git a/tools/pytest/_pytest/assertion/util.py b/tools/pytest/_pytest/assertion/util.py
deleted file mode 100644
index f2f23ef..0000000
--- a/tools/pytest/_pytest/assertion/util.py
+++ /dev/null
@@ -1,332 +0,0 @@
-"""Utilities for assertion debugging"""
-import pprint
-
-import _pytest._code
-import py
-try:
-    from collections import Sequence
-except ImportError:
-    Sequence = list
-
-BuiltinAssertionError = py.builtin.builtins.AssertionError
-u = py.builtin._totext
-
-# The _reprcompare attribute on the util module is used by the new assertion
-# interpretation code and assertion rewriter to detect this plugin was
-# loaded and in turn call the hooks defined here as part of the
-# DebugInterpreter.
-_reprcompare = None
-
-
-# the re-encoding is needed for python2 repr
-# with non-ascii characters (see issue 877 and 1379)
-def ecu(s):
-    try:
-        return u(s, 'utf-8', 'replace')
-    except TypeError:
-        return s
-
-
-def format_explanation(explanation):
-    """This formats an explanation
-
-    Normally all embedded newlines are escaped, however there are
-    three exceptions: \n{, \n} and \n~.  The first two are intended
-    cover nested explanations, see function and attribute explanations
-    for examples (.visit_Call(), visit_Attribute()).  The last one is
-    for when one explanation needs to span multiple lines, e.g. when
-    displaying diffs.
-    """
-    explanation = ecu(explanation)
-    explanation = _collapse_false(explanation)
-    lines = _split_explanation(explanation)
-    result = _format_lines(lines)
-    return u('\n').join(result)
-
-
-def _collapse_false(explanation):
-    """Collapse expansions of False
-
-    So this strips out any "assert False\n{where False = ...\n}"
-    blocks.
-    """
-    where = 0
-    while True:
-        start = where = explanation.find("False\n{False = ", where)
-        if where == -1:
-            break
-        level = 0
-        prev_c = explanation[start]
-        for i, c in enumerate(explanation[start:]):
-            if prev_c + c == "\n{":
-                level += 1
-            elif prev_c + c == "\n}":
-                level -= 1
-                if not level:
-                    break
-            prev_c = c
-        else:
-            raise AssertionError("unbalanced braces: %r" % (explanation,))
-        end = start + i
-        where = end
-        if explanation[end - 1] == '\n':
-            explanation = (explanation[:start] + explanation[start+15:end-1] +
-                           explanation[end+1:])
-            where -= 17
-    return explanation
-
-
-def _split_explanation(explanation):
-    """Return a list of individual lines in the explanation
-
-    This will return a list of lines split on '\n{', '\n}' and '\n~'.
-    Any other newlines will be escaped and appear in the line as the
-    literal '\n' characters.
-    """
-    raw_lines = (explanation or u('')).split('\n')
-    lines = [raw_lines[0]]
-    for l in raw_lines[1:]:
-        if l and l[0] in ['{', '}', '~', '>']:
-            lines.append(l)
-        else:
-            lines[-1] += '\\n' + l
-    return lines
-
-
-def _format_lines(lines):
-    """Format the individual lines
-
-    This will replace the '{', '}' and '~' characters of our mini
-    formatting language with the proper 'where ...', 'and ...' and ' +
-    ...' text, taking care of indentation along the way.
-
-    Return a list of formatted lines.
-    """
-    result = lines[:1]
-    stack = [0]
-    stackcnt = [0]
-    for line in lines[1:]:
-        if line.startswith('{'):
-            if stackcnt[-1]:
-                s = u('and   ')
-            else:
-                s = u('where ')
-            stack.append(len(result))
-            stackcnt[-1] += 1
-            stackcnt.append(0)
-            result.append(u(' +') + u('  ')*(len(stack)-1) + s + line[1:])
-        elif line.startswith('}'):
-            stack.pop()
-            stackcnt.pop()
-            result[stack[-1]] += line[1:]
-        else:
-            assert line[0] in ['~', '>']
-            stack[-1] += 1
-            indent = len(stack) if line.startswith('~') else len(stack) - 1
-            result.append(u('  ')*indent + line[1:])
-    assert len(stack) == 1
-    return result
-
-
-# Provide basestring in python3
-try:
-    basestring = basestring
-except NameError:
-    basestring = str
-
-
-def assertrepr_compare(config, op, left, right):
-    """Return specialised explanations for some operators/operands"""
-    width = 80 - 15 - len(op) - 2  # 15 chars indentation, 1 space around op
-    left_repr = py.io.saferepr(left, maxsize=int(width/2))
-    right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
-
-    summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
-
-    issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) and
-                            not isinstance(x, basestring))
-    istext = lambda x: isinstance(x, basestring)
-    isdict = lambda x: isinstance(x, dict)
-    isset = lambda x: isinstance(x, (set, frozenset))
-
-    def isiterable(obj):
-        try:
-            iter(obj)
-            return not istext(obj)
-        except TypeError:
-            return False
-
-    verbose = config.getoption('verbose')
-    explanation = None
-    try:
-        if op == '==':
-            if istext(left) and istext(right):
-                explanation = _diff_text(left, right, verbose)
-            else:
-                if issequence(left) and issequence(right):
-                    explanation = _compare_eq_sequence(left, right, verbose)
-                elif isset(left) and isset(right):
-                    explanation = _compare_eq_set(left, right, verbose)
-                elif isdict(left) and isdict(right):
-                    explanation = _compare_eq_dict(left, right, verbose)
-                if isiterable(left) and isiterable(right):
-                    expl = _compare_eq_iterable(left, right, verbose)
-                    if explanation is not None:
-                        explanation.extend(expl)
-                    else:
-                        explanation = expl
-        elif op == 'not in':
-            if istext(left) and istext(right):
-                explanation = _notin_text(left, right, verbose)
-    except Exception:
-        explanation = [
-            u('(pytest_assertion plugin: representation of details failed.  '
-              'Probably an object has a faulty __repr__.)'),
-            u(_pytest._code.ExceptionInfo())]
-
-    if not explanation:
-        return None
-
-    return [summary] + explanation
-
-
-def _diff_text(left, right, verbose=False):
-    """Return the explanation for the diff between text or bytes
-
-    Unless --verbose is used this will skip leading and trailing
-    characters which are identical to keep the diff minimal.
-
-    If the input are bytes they will be safely converted to text.
-    """
-    from difflib import ndiff
-    explanation = []
-    if isinstance(left, py.builtin.bytes):
-        left = u(repr(left)[1:-1]).replace(r'\n', '\n')
-    if isinstance(right, py.builtin.bytes):
-        right = u(repr(right)[1:-1]).replace(r'\n', '\n')
-    if not verbose:
-        i = 0  # just in case left or right has zero length
-        for i in range(min(len(left), len(right))):
-            if left[i] != right[i]:
-                break
-        if i > 42:
-            i -= 10                 # Provide some context
-            explanation = [u('Skipping %s identical leading '
-                             'characters in diff, use -v to show') % i]
-            left = left[i:]
-            right = right[i:]
-        if len(left) == len(right):
-            for i in range(len(left)):
-                if left[-i] != right[-i]:
-                    break
-            if i > 42:
-                i -= 10     # Provide some context
-                explanation += [u('Skipping %s identical trailing '
-                                  'characters in diff, use -v to show') % i]
-                left = left[:-i]
-                right = right[:-i]
-    explanation += [line.strip('\n')
-                    for line in ndiff(left.splitlines(),
-                                      right.splitlines())]
-    return explanation
-
-
-def _compare_eq_iterable(left, right, verbose=False):
-    if not verbose:
-        return [u('Use -v to get the full diff')]
-    # dynamic import to speedup pytest
-    import difflib
-
-    try:
-        left_formatting = pprint.pformat(left).splitlines()
-        right_formatting = pprint.pformat(right).splitlines()
-        explanation = [u('Full diff:')]
-    except Exception:
-        # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
-        # sorted() on a list would raise. See issue #718.
-        # As a workaround, the full diff is generated by using the repr() string of each item of each container.
-        left_formatting = sorted(repr(x) for x in left)
-        right_formatting = sorted(repr(x) for x in right)
-        explanation = [u('Full diff (fallback to calling repr on each item):')]
-    explanation.extend(line.strip() for line in difflib.ndiff(left_formatting, right_formatting))
-    return explanation
-
-
-def _compare_eq_sequence(left, right, verbose=False):
-    explanation = []
-    for i in range(min(len(left), len(right))):
-        if left[i] != right[i]:
-            explanation += [u('At index %s diff: %r != %r')
-                            % (i, left[i], right[i])]
-            break
-    if len(left) > len(right):
-        explanation += [u('Left contains more items, first extra item: %s')
-                        % py.io.saferepr(left[len(right)],)]
-    elif len(left) < len(right):
-        explanation += [
-            u('Right contains more items, first extra item: %s') %
-            py.io.saferepr(right[len(left)],)]
-    return explanation
-
-
-def _compare_eq_set(left, right, verbose=False):
-    explanation = []
-    diff_left = left - right
-    diff_right = right - left
-    if diff_left:
-        explanation.append(u('Extra items in the left set:'))
-        for item in diff_left:
-            explanation.append(py.io.saferepr(item))
-    if diff_right:
-        explanation.append(u('Extra items in the right set:'))
-        for item in diff_right:
-            explanation.append(py.io.saferepr(item))
-    return explanation
-
-
-def _compare_eq_dict(left, right, verbose=False):
-    explanation = []
-    common = set(left).intersection(set(right))
-    same = dict((k, left[k]) for k in common if left[k] == right[k])
-    if same and not verbose:
-        explanation += [u('Omitting %s identical items, use -v to show') %
-                        len(same)]
-    elif same:
-        explanation += [u('Common items:')]
-        explanation += pprint.pformat(same).splitlines()
-    diff = set(k for k in common if left[k] != right[k])
-    if diff:
-        explanation += [u('Differing items:')]
-        for k in diff:
-            explanation += [py.io.saferepr({k: left[k]}) + ' != ' +
-                            py.io.saferepr({k: right[k]})]
-    extra_left = set(left) - set(right)
-    if extra_left:
-        explanation.append(u('Left contains more items:'))
-        explanation.extend(pprint.pformat(
-            dict((k, left[k]) for k in extra_left)).splitlines())
-    extra_right = set(right) - set(left)
-    if extra_right:
-        explanation.append(u('Right contains more items:'))
-        explanation.extend(pprint.pformat(
-            dict((k, right[k]) for k in extra_right)).splitlines())
-    return explanation
-
-
-def _notin_text(term, text, verbose=False):
-    index = text.find(term)
-    head = text[:index]
-    tail = text[index+len(term):]
-    correct_text = head + tail
-    diff = _diff_text(correct_text, text, verbose)
-    newdiff = [u('%s is contained here:') % py.io.saferepr(term, maxsize=42)]
-    for line in diff:
-        if line.startswith(u('Skipping')):
-            continue
-        if line.startswith(u('- ')):
-            continue
-        if line.startswith(u('+ ')):
-            newdiff.append(u('  ') + line[2:])
-        else:
-            newdiff.append(line)
-    return newdiff
diff --git a/tools/pytest/_pytest/cacheprovider.py b/tools/pytest/_pytest/cacheprovider.py
deleted file mode 100755
index 0657001..0000000
--- a/tools/pytest/_pytest/cacheprovider.py
+++ /dev/null
@@ -1,245 +0,0 @@
-"""
-merged implementation of the cache provider
-
-the name cache was not choosen to ensure pluggy automatically
-ignores the external pytest-cache
-"""
-
-import py
-import pytest
-import json
-from os.path import sep as _sep, altsep as _altsep
-
-
-class Cache(object):
-    def __init__(self, config):
-        self.config = config
-        self._cachedir = config.rootdir.join(".cache")
-        self.trace = config.trace.root.get("cache")
-        if config.getvalue("cacheclear"):
-            self.trace("clearing cachedir")
-            if self._cachedir.check():
-                self._cachedir.remove()
-            self._cachedir.mkdir()
-
-    def makedir(self, name):
-        """ return a directory path object with the given name.  If the
-        directory does not yet exist, it will be created.  You can use it
-        to manage files likes e. g. store/retrieve database
-        dumps across test sessions.
-
-        :param name: must be a string not containing a ``/`` separator.
-             Make sure the name contains your plugin or application
-             identifiers to prevent clashes with other cache users.
-        """
-        if _sep in name or _altsep is not None and _altsep in name:
-            raise ValueError("name is not allowed to contain path separators")
-        return self._cachedir.ensure_dir("d", name)
-
-    def _getvaluepath(self, key):
-        return self._cachedir.join('v', *key.split('/'))
-
-    def get(self, key, default):
-        """ return cached value for the given key.  If no value
-        was yet cached or the value cannot be read, the specified
-        default is returned.
-
-        :param key: must be a ``/`` separated value. Usually the first
-             name is the name of your plugin or your application.
-        :param default: must be provided in case of a cache-miss or
-             invalid cache values.
-
-        """
-        path = self._getvaluepath(key)
-        if path.check():
-            try:
-                with path.open("r") as f:
-                    return json.load(f)
-            except ValueError:
-                self.trace("cache-invalid at %s" % (path,))
-        return default
-
-    def set(self, key, value):
-        """ save value for the given key.
-
-        :param key: must be a ``/`` separated value. Usually the first
-             name is the name of your plugin or your application.
-        :param value: must be of any combination of basic
-               python types, including nested types
-               like e. g. lists of dictionaries.
-        """
-        path = self._getvaluepath(key)
-        try:
-            path.dirpath().ensure_dir()
-        except (py.error.EEXIST, py.error.EACCES):
-            self.config.warn(
-                code='I9', message='could not create cache path %s' % (path,)
-            )
-            return
-        try:
-            f = path.open('w')
-        except py.error.ENOTDIR:
-            self.config.warn(
-                code='I9', message='cache could not write path %s' % (path,))
-        else:
-            with f:
-                self.trace("cache-write %s: %r" % (key, value,))
-                json.dump(value, f, indent=2, sort_keys=True)
-
-
-class LFPlugin:
-    """ Plugin which implements the --lf (run last-failing) option """
-    def __init__(self, config):
-        self.config = config
-        active_keys = 'lf', 'failedfirst'
-        self.active = any(config.getvalue(key) for key in active_keys)
-        if self.active:
-            self.lastfailed = config.cache.get("cache/lastfailed", {})
-        else:
-            self.lastfailed = {}
-
-    def pytest_report_header(self):
-        if self.active:
-            if not self.lastfailed:
-                mode = "run all (no recorded failures)"
-            else:
-                mode = "rerun last %d failures%s" % (
-                    len(self.lastfailed),
-                    " first" if self.config.getvalue("failedfirst") else "")
-            return "run-last-failure: %s" % mode
-
-    def pytest_runtest_logreport(self, report):
-        if report.failed and "xfail" not in report.keywords:
-            self.lastfailed[report.nodeid] = True
-        elif not report.failed:
-            if report.when == "call":
-                self.lastfailed.pop(report.nodeid, None)
-
-    def pytest_collectreport(self, report):
-        passed = report.outcome in ('passed', 'skipped')
-        if passed:
-            if report.nodeid in self.lastfailed:
-                self.lastfailed.pop(report.nodeid)
-                self.lastfailed.update(
-                    (item.nodeid, True)
-                    for item in report.result)
-        else:
-            self.lastfailed[report.nodeid] = True
-
-    def pytest_collection_modifyitems(self, session, config, items):
-        if self.active and self.lastfailed:
-            previously_failed = []
-            previously_passed = []
-            for item in items:
-                if item.nodeid in self.lastfailed:
-                    previously_failed.append(item)
-                else:
-                    previously_passed.append(item)
-            if not previously_failed and previously_passed:
-                # running a subset of all tests with recorded failures outside
-                # of the set of tests currently executing
-                pass
-            elif self.config.getvalue("failedfirst"):
-                items[:] = previously_failed + previously_passed
-            else:
-                items[:] = previously_failed
-                config.hook.pytest_deselected(items=previously_passed)
-
-    def pytest_sessionfinish(self, session):
-        config = self.config
-        if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
-            return
-        prev_failed = config.cache.get("cache/lastfailed", None) is not None
-        if (session.testscollected and prev_failed) or self.lastfailed:
-            config.cache.set("cache/lastfailed", self.lastfailed)
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("general")
-    group.addoption(
-        '--lf', '--last-failed', action='store_true', dest="lf",
-        help="rerun only the tests that failed "
-             "at the last run (or all if none failed)")
-    group.addoption(
-        '--ff', '--failed-first', action='store_true', dest="failedfirst",
-        help="run all tests but run the last failures first.  "
-             "This may re-order tests and thus lead to "
-             "repeated fixture setup/teardown")
-    group.addoption(
-        '--cache-show', action='store_true', dest="cacheshow",
-        help="show cache contents, don't perform collection or tests")
-    group.addoption(
-        '--cache-clear', action='store_true', dest="cacheclear",
-        help="remove all cache contents at start of test run.")
-
-
-def pytest_cmdline_main(config):
-    if config.option.cacheshow:
-        from _pytest.main import wrap_session
-        return wrap_session(config, cacheshow)
-
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_configure(config):
-    config.cache = Cache(config)
-    config.pluginmanager.register(LFPlugin(config), "lfplugin")
-
-
-@pytest.fixture
-def cache(request):
-    """
-    Return a cache object that can persist state between testing sessions.
-
-    cache.get(key, default)
-    cache.set(key, value)
-
-    Keys must be a ``/`` separated value, where the first part is usually the
-    name of your plugin or application to avoid clashes with other cache users.
-
-    Values can be any object handled by the json stdlib module.
-    """
-    return request.config.cache
-
-
-def pytest_report_header(config):
-    if config.option.verbose:
-        relpath = py.path.local().bestrelpath(config.cache._cachedir)
-        return "cachedir: %s" % relpath
-
-
-def cacheshow(config, session):
-    from pprint import pprint
-    tw = py.io.TerminalWriter()
-    tw.line("cachedir: " + str(config.cache._cachedir))
-    if not config.cache._cachedir.check():
-        tw.line("cache is empty")
-        return 0
-    dummy = object()
-    basedir = config.cache._cachedir
-    vdir = basedir.join("v")
-    tw.sep("-", "cache values")
-    for valpath in vdir.visit(lambda x: x.isfile()):
-        key = valpath.relto(vdir).replace(valpath.sep, "/")
-        val = config.cache.get(key, dummy)
-        if val is dummy:
-            tw.line("%s contains unreadable content, "
-                  "will be ignored" % key)
-        else:
-            tw.line("%s contains:" % key)
-            stream = py.io.TextIO()
-            pprint(val, stream=stream)
-            for line in stream.getvalue().splitlines():
-                tw.line("  " + line)
-
-    ddir = basedir.join("d")
-    if ddir.isdir() and ddir.listdir():
-        tw.sep("-", "cache directories")
-        for p in basedir.join("d").visit():
-            #if p.check(dir=1):
-            #    print("%s/" % p.relto(basedir))
-            if p.isfile():
-                key = p.relto(basedir)
-                tw.line("%s is a file of length %d" % (
-                        key, p.size()))
-    return 0
diff --git a/tools/pytest/_pytest/capture.py b/tools/pytest/_pytest/capture.py
deleted file mode 100644
index 3895a71..0000000
--- a/tools/pytest/_pytest/capture.py
+++ /dev/null
@@ -1,472 +0,0 @@
-"""
-per-test stdout/stderr capturing mechanism.
-
-"""
-from __future__ import with_statement
-
-import sys
-import os
-from tempfile import TemporaryFile
-
-import py
-import pytest
-
-from py.io import TextIO
-unicode = py.builtin.text
-
-patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("general")
-    group._addoption(
-        '--capture', action="store",
-        default="fd" if hasattr(os, "dup") else "sys",
-        metavar="method", choices=['fd', 'sys', 'no'],
-        help="per-test capturing method: one of fd|sys|no.")
-    group._addoption(
-        '-s', action="store_const", const="no", dest="capture",
-        help="shortcut for --capture=no.")
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_load_initial_conftests(early_config, parser, args):
-    _readline_workaround()
-    ns = early_config.known_args_namespace
-    pluginmanager = early_config.pluginmanager
-    capman = CaptureManager(ns.capture)
-    pluginmanager.register(capman, "capturemanager")
-
-    # make sure that capturemanager is properly reset at final shutdown
-    early_config.add_cleanup(capman.reset_capturings)
-
-    # make sure logging does not raise exceptions at the end
-    def silence_logging_at_shutdown():
-        if "logging" in sys.modules:
-            sys.modules["logging"].raiseExceptions = False
-    early_config.add_cleanup(silence_logging_at_shutdown)
-
-    # finally trigger conftest loading but while capturing (issue93)
-    capman.init_capturings()
-    outcome = yield
-    out, err = capman.suspendcapture()
-    if outcome.excinfo is not None:
-        sys.stdout.write(out)
-        sys.stderr.write(err)
-
-
-class CaptureManager:
-    def __init__(self, method):
-        self._method = method
-
-    def _getcapture(self, method):
-        if method == "fd":
-            return MultiCapture(out=True, err=True, Capture=FDCapture)
-        elif method == "sys":
-            return MultiCapture(out=True, err=True, Capture=SysCapture)
-        elif method == "no":
-            return MultiCapture(out=False, err=False, in_=False)
-        else:
-            raise ValueError("unknown capturing method: %r" % method)
-
-    def init_capturings(self):
-        assert not hasattr(self, "_capturing")
-        self._capturing = self._getcapture(self._method)
-        self._capturing.start_capturing()
-
-    def reset_capturings(self):
-        cap = self.__dict__.pop("_capturing", None)
-        if cap is not None:
-            cap.pop_outerr_to_orig()
-            cap.stop_capturing()
-
-    def resumecapture(self):
-        self._capturing.resume_capturing()
-
-    def suspendcapture(self, in_=False):
-        self.deactivate_funcargs()
-        cap = getattr(self, "_capturing", None)
-        if cap is not None:
-            try:
-                outerr = cap.readouterr()
-            finally:
-                cap.suspend_capturing(in_=in_)
-            return outerr
-
-    def activate_funcargs(self, pyfuncitem):
-        capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
-        if capfuncarg is not None:
-            capfuncarg._start()
-            self._capfuncarg = capfuncarg
-
-    def deactivate_funcargs(self):
-        capfuncarg = self.__dict__.pop("_capfuncarg", None)
-        if capfuncarg is not None:
-            capfuncarg.close()
-
-    @pytest.hookimpl(hookwrapper=True)
-    def pytest_make_collect_report(self, collector):
-        if isinstance(collector, pytest.File):
-            self.resumecapture()
-            outcome = yield
-            out, err = self.suspendcapture()
-            rep = outcome.get_result()
-            if out:
-                rep.sections.append(("Captured stdout", out))
-            if err:
-                rep.sections.append(("Captured stderr", err))
-        else:
-            yield
-
-    @pytest.hookimpl(hookwrapper=True)
-    def pytest_runtest_setup(self, item):
-        self.resumecapture()
-        yield
-        self.suspendcapture_item(item, "setup")
-
-    @pytest.hookimpl(hookwrapper=True)
-    def pytest_runtest_call(self, item):
-        self.resumecapture()
-        self.activate_funcargs(item)
-        yield
-        #self.deactivate_funcargs() called from suspendcapture()
-        self.suspendcapture_item(item, "call")
-
-    @pytest.hookimpl(hookwrapper=True)
-    def pytest_runtest_teardown(self, item):
-        self.resumecapture()
-        yield
-        self.suspendcapture_item(item, "teardown")
-
-    @pytest.hookimpl(tryfirst=True)
-    def pytest_keyboard_interrupt(self, excinfo):
-        self.reset_capturings()
-
-    @pytest.hookimpl(tryfirst=True)
-    def pytest_internalerror(self, excinfo):
-        self.reset_capturings()
-
-    def suspendcapture_item(self, item, when):
-        out, err = self.suspendcapture()
-        item.add_report_section(when, "stdout", out)
-        item.add_report_section(when, "stderr", err)
-
-error_capsysfderror = "cannot use capsys and capfd at the same time"
-
-
-@pytest.fixture
-def capsys(request):
-    """enables capturing of writes to sys.stdout/sys.stderr and makes
-    captured output available via ``capsys.readouterr()`` method calls
-    which return a ``(out, err)`` tuple.
-    """
-    if "capfd" in request._funcargs:
-        raise request.raiseerror(error_capsysfderror)
-    request.node._capfuncarg = c = CaptureFixture(SysCapture)
-    return c
-
-@pytest.fixture
-def capfd(request):
-    """enables capturing of writes to file descriptors 1 and 2 and makes
-    captured output available via ``capfd.readouterr()`` method calls
-    which return a ``(out, err)`` tuple.
-    """
-    if "capsys" in request._funcargs:
-        request.raiseerror(error_capsysfderror)
-    if not hasattr(os, 'dup'):
-        pytest.skip("capfd funcarg needs os.dup")
-    request.node._capfuncarg = c = CaptureFixture(FDCapture)
-    return c
-
-
-class CaptureFixture:
-    def __init__(self, captureclass):
-        self.captureclass = captureclass
-
-    def _start(self):
-        self._capture = MultiCapture(out=True, err=True, in_=False,
-                                       Capture=self.captureclass)
-        self._capture.start_capturing()
-
-    def close(self):
-        cap = self.__dict__.pop("_capture", None)
-        if cap is not None:
-            self._outerr = cap.pop_outerr_to_orig()
-            cap.stop_capturing()
-
-    def readouterr(self):
-        try:
-            return self._capture.readouterr()
-        except AttributeError:
-            return self._outerr
-
-
-def safe_text_dupfile(f, mode, default_encoding="UTF8"):
-    """ return a open text file object that's a duplicate of f on the
-        FD-level if possible.
-    """
-    encoding = getattr(f, "encoding", None)
-    try:
-        fd = f.fileno()
-    except Exception:
-        if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
-            # we seem to have a text stream, let's just use it
-            return f
-    else:
-        newfd = os.dup(fd)
-        if "b" not in mode:
-            mode += "b"
-        f = os.fdopen(newfd, mode, 0)  # no buffering
-    return EncodedFile(f, encoding or default_encoding)
-
-
-class EncodedFile(object):
-    errors = "strict"  # possibly needed by py3 code (issue555)
-    def __init__(self, buffer, encoding):
-        self.buffer = buffer
-        self.encoding = encoding
-
-    def write(self, obj):
-        if isinstance(obj, unicode):
-            obj = obj.encode(self.encoding, "replace")
-        self.buffer.write(obj)
-
-    def writelines(self, linelist):
-        data = ''.join(linelist)
-        self.write(data)
-
-    def __getattr__(self, name):
-        return getattr(object.__getattribute__(self, "buffer"), name)
-
-
-class MultiCapture(object):
-    out = err = in_ = None
-
-    def __init__(self, out=True, err=True, in_=True, Capture=None):
-        if in_:
-            self.in_ = Capture(0)
-        if out:
-            self.out = Capture(1)
-        if err:
-            self.err = Capture(2)
-
-    def start_capturing(self):
-        if self.in_:
-            self.in_.start()
-        if self.out:
-            self.out.start()
-        if self.err:
-            self.err.start()
-
-    def pop_outerr_to_orig(self):
-        """ pop current snapshot out/err capture and flush to orig streams. """
-        out, err = self.readouterr()
-        if out:
-            self.out.writeorg(out)
-        if err:
-            self.err.writeorg(err)
-        return out, err
-
-    def suspend_capturing(self, in_=False):
-        if self.out:
-            self.out.suspend()
-        if self.err:
-            self.err.suspend()
-        if in_ and self.in_:
-            self.in_.suspend()
-            self._in_suspended = True
-
-    def resume_capturing(self):
-        if self.out:
-            self.out.resume()
-        if self.err:
-            self.err.resume()
-        if hasattr(self, "_in_suspended"):
-            self.in_.resume()
-            del self._in_suspended
-
-    def stop_capturing(self):
-        """ stop capturing and reset capturing streams """
-        if hasattr(self, '_reset'):
-            raise ValueError("was already stopped")
-        self._reset = True
-        if self.out:
-            self.out.done()
-        if self.err:
-            self.err.done()
-        if self.in_:
-            self.in_.done()
-
-    def readouterr(self):
-        """ return snapshot unicode value of stdout/stderr capturings. """
-        return (self.out.snap() if self.out is not None else "",
-                self.err.snap() if self.err is not None else "")
-
-class NoCapture:
-    __init__ = start = done = suspend = resume = lambda *args: None
-
-class FDCapture:
-    """ Capture IO to/from a given os-level filedescriptor. """
-
-    def __init__(self, targetfd, tmpfile=None):
-        self.targetfd = targetfd
-        try:
-            self.targetfd_save = os.dup(self.targetfd)
-        except OSError:
-            self.start = lambda: None
-            self.done = lambda: None
-        else:
-            if targetfd == 0:
-                assert not tmpfile, "cannot set tmpfile with stdin"
-                tmpfile = open(os.devnull, "r")
-                self.syscapture = SysCapture(targetfd)
-            else:
-                if tmpfile is None:
-                    f = TemporaryFile()
-                    with f:
-                        tmpfile = safe_text_dupfile(f, mode="wb+")
-                if targetfd in patchsysdict:
-                    self.syscapture = SysCapture(targetfd, tmpfile)
-                else:
-                    self.syscapture = NoCapture()
-            self.tmpfile = tmpfile
-            self.tmpfile_fd = tmpfile.fileno()
-
-    def __repr__(self):
-        return "<FDCapture %s oldfd=%s>" % (self.targetfd, self.targetfd_save)
-
-    def start(self):
-        """ Start capturing on targetfd using memorized tmpfile. """
-        try:
-            os.fstat(self.targetfd_save)
-        except (AttributeError, OSError):
-            raise ValueError("saved filedescriptor not valid anymore")
-        os.dup2(self.tmpfile_fd, self.targetfd)
-        self.syscapture.start()
-
-    def snap(self):
-        f = self.tmpfile
-        f.seek(0)
-        res = f.read()
-        if res:
-            enc = getattr(f, "encoding", None)
-            if enc and isinstance(res, bytes):
-                res = py.builtin._totext(res, enc, "replace")
-            f.truncate(0)
-            f.seek(0)
-            return res
-        return ''
-
-    def done(self):
-        """ stop capturing, restore streams, return original capture file,
-        seeked to position zero. """
-        targetfd_save = self.__dict__.pop("targetfd_save")
-        os.dup2(targetfd_save, self.targetfd)
-        os.close(targetfd_save)
-        self.syscapture.done()
-        self.tmpfile.close()
-
-    def suspend(self):
-        self.syscapture.suspend()
-        os.dup2(self.targetfd_save, self.targetfd)
-
-    def resume(self):
-        self.syscapture.resume()
-        os.dup2(self.tmpfile_fd, self.targetfd)
-
-    def writeorg(self, data):
-        """ write to original file descriptor. """
-        if py.builtin._istext(data):
-            data = data.encode("utf8") # XXX use encoding of original stream
-        os.write(self.targetfd_save, data)
-
-
-class SysCapture:
-    def __init__(self, fd, tmpfile=None):
-        name = patchsysdict[fd]
-        self._old = getattr(sys, name)
-        self.name = name
-        if tmpfile is None:
-            if name == "stdin":
-                tmpfile = DontReadFromInput()
-            else:
-                tmpfile = TextIO()
-        self.tmpfile = tmpfile
-
-    def start(self):
-        setattr(sys, self.name, self.tmpfile)
-
-    def snap(self):
-        f = self.tmpfile
-        res = f.getvalue()
-        f.truncate(0)
-        f.seek(0)
-        return res
-
-    def done(self):
-        setattr(sys, self.name, self._old)
-        del self._old
-        self.tmpfile.close()
-
-    def suspend(self):
-        setattr(sys, self.name, self._old)
-
-    def resume(self):
-        setattr(sys, self.name, self.tmpfile)
-
-    def writeorg(self, data):
-        self._old.write(data)
-        self._old.flush()
-
-
-class DontReadFromInput:
-    """Temporary stub class.  Ideally when stdin is accessed, the
-    capturing should be turned off, with possibly all data captured
-    so far sent to the screen.  This should be configurable, though,
-    because in automated test runs it is better to crash than
-    hang indefinitely.
-    """
-
-    encoding = None
-
-    def read(self, *args):
-        raise IOError("reading from stdin while output is captured")
-    readline = read
-    readlines = read
-    __iter__ = read
-
-    def fileno(self):
-        raise ValueError("redirected Stdin is pseudofile, has no fileno()")
-
-    def isatty(self):
-        return False
-
-    def close(self):
-        pass
-
-
-def _readline_workaround():
-    """
-    Ensure readline is imported so that it attaches to the correct stdio
-    handles on Windows.
-
-    Pdb uses readline support where available--when not running from the Python
-    prompt, the readline module is not imported until running the pdb REPL.  If
-    running py.test with the --pdb option this means the readline module is not
-    imported until after I/O capture has been started.
-
-    This is a problem for pyreadline, which is often used to implement readline
-    support on Windows, as it does not attach to the correct handles for stdout
-    and/or stdin if they have been redirected by the FDCapture mechanism.  This
-    workaround ensures that readline is imported before I/O capture is setup so
-    that it can attach to the actual stdin/out for the console.
-
-    See https://github.com/pytest-dev/pytest/pull/1281
-    """
-
-    if not sys.platform.startswith('win32'):
-        return
-    try:
-        import readline  # noqa
-    except ImportError:
-        pass
diff --git a/tools/pytest/_pytest/config.py b/tools/pytest/_pytest/config.py
deleted file mode 100644
index fb7b177..0000000
--- a/tools/pytest/_pytest/config.py
+++ /dev/null
@@ -1,1192 +0,0 @@
-""" command line options, ini-file and conftest.py processing. """
-import argparse
-import shlex
-import traceback
-import types
-import warnings
-
-import py
-# DON't import pytest here because it causes import cycle troubles
-import sys, os
-import _pytest._code
-import _pytest.hookspec  # the extension point definitions
-from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
-
-hookimpl = HookimplMarker("pytest")
-hookspec = HookspecMarker("pytest")
-
-# pytest startup
-#
-
-
-class ConftestImportFailure(Exception):
-    def __init__(self, path, excinfo):
-        Exception.__init__(self, path, excinfo)
-        self.path = path
-        self.excinfo = excinfo
-
-
-def main(args=None, plugins=None):
-    """ return exit code, after performing an in-process test run.
-
-    :arg args: list of command line arguments.
-
-    :arg plugins: list of plugin objects to be auto-registered during
-                  initialization.
-    """
-    try:
-        try:
-            config = _prepareconfig(args, plugins)
-        except ConftestImportFailure as e:
-            tw = py.io.TerminalWriter(sys.stderr)
-            for line in traceback.format_exception(*e.excinfo):
-                tw.line(line.rstrip(), red=True)
-            tw.line("ERROR: could not load %s\n" % (e.path), red=True)
-            return 4
-        else:
-            try:
-                config.pluginmanager.check_pending()
-                return config.hook.pytest_cmdline_main(config=config)
-            finally:
-                config._ensure_unconfigure()
-    except UsageError as e:
-        for msg in e.args:
-            sys.stderr.write("ERROR: %s\n" %(msg,))
-        return 4
-
-class cmdline:  # compatibility namespace
-    main = staticmethod(main)
-
-class UsageError(Exception):
-    """ error in pytest usage or invocation"""
-
-_preinit = []
-
-default_plugins = (
-     "mark main terminal runner python pdb unittest capture skipping "
-     "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
-     "junitxml resultlog doctest cacheprovider").split()
-
-builtin_plugins = set(default_plugins)
-builtin_plugins.add("pytester")
-
-
-def _preloadplugins():
-    assert not _preinit
-    _preinit.append(get_config())
-
-def get_config():
-    if _preinit:
-        return _preinit.pop(0)
-    # subsequent calls to main will create a fresh instance
-    pluginmanager = PytestPluginManager()
-    config = Config(pluginmanager)
-    for spec in default_plugins:
-        pluginmanager.import_plugin(spec)
-    return config
-
-def get_plugin_manager():
-    """
-    Obtain a new instance of the
-    :py:class:`_pytest.config.PytestPluginManager`, with default plugins
-    already loaded.
-
-    This function can be used by integration with other tools, like hooking
-    into pytest to run tests into an IDE.
-    """
-    return get_config().pluginmanager
-
-def _prepareconfig(args=None, plugins=None):
-    if args is None:
-        args = sys.argv[1:]
-    elif isinstance(args, py.path.local):
-        args = [str(args)]
-    elif not isinstance(args, (tuple, list)):
-        if not isinstance(args, str):
-            raise ValueError("not a string or argument list: %r" % (args,))
-        args = shlex.split(args)
-    config = get_config()
-    pluginmanager = config.pluginmanager
-    try:
-        if plugins:
-            for plugin in plugins:
-                if isinstance(plugin, py.builtin._basestring):
-                    pluginmanager.consider_pluginarg(plugin)
-                else:
-                    pluginmanager.register(plugin)
-        return pluginmanager.hook.pytest_cmdline_parse(
-                pluginmanager=pluginmanager, args=args)
-    except BaseException:
-        config._ensure_unconfigure()
-        raise
-
-
-class PytestPluginManager(PluginManager):
-    """
-    Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific
-    functionality:
-
-    * loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
-      ``pytest_plugins`` global variables found in plugins being loaded;
-    * ``conftest.py`` loading during start-up;
-    """
-    def __init__(self):
-        super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
-        self._conftest_plugins = set()
-
-        # state related to local conftest plugins
-        self._path2confmods = {}
-        self._conftestpath2mod = {}
-        self._confcutdir = None
-        self._noconftest = False
-
-        self.add_hookspecs(_pytest.hookspec)
-        self.register(self)
-        if os.environ.get('PYTEST_DEBUG'):
-            err = sys.stderr
-            encoding = getattr(err, 'encoding', 'utf8')
-            try:
-                err = py.io.dupfile(err, encoding=encoding)
-            except Exception:
-                pass
-            self.trace.root.setwriter(err.write)
-            self.enable_tracing()
-
-    def addhooks(self, module_or_class):
-        """
-        .. deprecated:: 2.8
-
-        Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
-        """
-        warning = dict(code="I2",
-                       fslocation=_pytest._code.getfslineno(sys._getframe(1)),
-                       nodeid=None,
-                       message="use pluginmanager.add_hookspecs instead of "
-                               "deprecated addhooks() method.")
-        self._warn(warning)
-        return self.add_hookspecs(module_or_class)
-
-    def parse_hookimpl_opts(self, plugin, name):
-        # pytest hooks are always prefixed with pytest_
-        # so we avoid accessing possibly non-readable attributes
-        # (see issue #1073)
-        if not name.startswith("pytest_"):
-            return
-        # ignore some historic special names which can not be hooks anyway
-        if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
-            return
-
-        method = getattr(plugin, name)
-        opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
-        if opts is not None:
-            for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
-                opts.setdefault(name, hasattr(method, name))
-        return opts
-
-    def parse_hookspec_opts(self, module_or_class, name):
-        opts = super(PytestPluginManager, self).parse_hookspec_opts(
-                                                module_or_class, name)
-        if opts is None:
-            method = getattr(module_or_class, name)
-            if name.startswith("pytest_"):
-                opts = {"firstresult": hasattr(method, "firstresult"),
-                        "historic": hasattr(method, "historic")}
-        return opts
-
-    def _verify_hook(self, hook, hookmethod):
-        super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
-        if "__multicall__" in hookmethod.argnames:
-            fslineno = _pytest._code.getfslineno(hookmethod.function)
-            warning = dict(code="I1",
-                           fslocation=fslineno,
-                           nodeid=None,
-                           message="%r hook uses deprecated __multicall__ "
-                                   "argument" % (hook.name))
-            self._warn(warning)
-
-    def register(self, plugin, name=None):
-        ret = super(PytestPluginManager, self).register(plugin, name)
-        if ret:
-            self.hook.pytest_plugin_registered.call_historic(
-                      kwargs=dict(plugin=plugin, manager=self))
-        return ret
-
-    def getplugin(self, name):
-        # support deprecated naming because plugins (xdist e.g.) use it
-        return self.get_plugin(name)
-
-    def hasplugin(self, name):
-        """Return True if the plugin with the given name is registered."""
-        return bool(self.get_plugin(name))
-
-    def pytest_configure(self, config):
-        # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
-        # we should remove tryfirst/trylast as markers
-        config.addinivalue_line("markers",
-            "tryfirst: mark a hook implementation function such that the "
-            "plugin machinery will try to call it first/as early as possible.")
-        config.addinivalue_line("markers",
-            "trylast: mark a hook implementation function such that the "
-            "plugin machinery will try to call it last/as late as possible.")
-
-    def _warn(self, message):
-        kwargs = message if isinstance(message, dict) else {
-            'code': 'I1',
-            'message': message,
-            'fslocation': None,
-            'nodeid': None,
-        }
-        self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
-
-    #
-    # internal API for local conftest plugin handling
-    #
-    def _set_initial_conftests(self, namespace):
-        """ load initial conftest files given a preparsed "namespace".
-            As conftest files may add their own command line options
-            which have arguments ('--my-opt somepath') we might get some
-            false positives.  All builtin and 3rd party plugins will have
-            been loaded, however, so common options will not confuse our logic
-            here.
-        """
-        current = py.path.local()
-        self._confcutdir = current.join(namespace.confcutdir, abs=True) \
-                                if namespace.confcutdir else None
-        self._noconftest = namespace.noconftest
-        testpaths = namespace.file_or_dir
-        foundanchor = False
-        for path in testpaths:
-            path = str(path)
-            # remove node-id syntax
-            i = path.find("::")
-            if i != -1:
-                path = path[:i]
-            anchor = current.join(path, abs=1)
-            if exists(anchor): # we found some file object
-                self._try_load_conftest(anchor)
-                foundanchor = True
-        if not foundanchor:
-            self._try_load_conftest(current)
-
-    def _try_load_conftest(self, anchor):
-        self._getconftestmodules(anchor)
-        # let's also consider test* subdirs
-        if anchor.check(dir=1):
-            for x in anchor.listdir("test*"):
-                if x.check(dir=1):
-                    self._getconftestmodules(x)
-
-    def _getconftestmodules(self, path):
-        if self._noconftest:
-            return []
-        try:
-            return self._path2confmods[path]
-        except KeyError:
-            if path.isfile():
-                clist = self._getconftestmodules(path.dirpath())
-            else:
-                # XXX these days we may rather want to use config.rootdir
-                # and allow users to opt into looking into the rootdir parent
-                # directories instead of requiring to specify confcutdir
-                clist = []
-                for parent in path.parts():
-                    if self._confcutdir and self._confcutdir.relto(parent):
-                        continue
-                    conftestpath = parent.join("conftest.py")
-                    if conftestpath.isfile():
-                        mod = self._importconftest(conftestpath)
-                        clist.append(mod)
-
-            self._path2confmods[path] = clist
-            return clist
-
-    def _rget_with_confmod(self, name, path):
-        modules = self._getconftestmodules(path)
-        for mod in reversed(modules):
-            try:
-                return mod, getattr(mod, name)
-            except AttributeError:
-                continue
-        raise KeyError(name)
-
-    def _importconftest(self, conftestpath):
-        try:
-            return self._conftestpath2mod[conftestpath]
-        except KeyError:
-            pkgpath = conftestpath.pypkgpath()
-            if pkgpath is None:
-                _ensure_removed_sysmodule(conftestpath.purebasename)
-            try:
-                mod = conftestpath.pyimport()
-            except Exception:
-                raise ConftestImportFailure(conftestpath, sys.exc_info())
-
-            self._conftest_plugins.add(mod)
-            self._conftestpath2mod[conftestpath] = mod
-            dirpath = conftestpath.dirpath()
-            if dirpath in self._path2confmods:
-                for path, mods in self._path2confmods.items():
-                    if path and path.relto(dirpath) or path == dirpath:
-                        assert mod not in mods
-                        mods.append(mod)
-            self.trace("loaded conftestmodule %r" %(mod))
-            self.consider_conftest(mod)
-            return mod
-
-    #
-    # API for bootstrapping plugin loading
-    #
-    #
-
-    def consider_preparse(self, args):
-        for opt1,opt2 in zip(args, args[1:]):
-            if opt1 == "-p":
-                self.consider_pluginarg(opt2)
-
-    def consider_pluginarg(self, arg):
-        if arg.startswith("no:"):
-            name = arg[3:]
-            self.set_blocked(name)
-            if not name.startswith("pytest_"):
-                self.set_blocked("pytest_" + name)
-        else:
-            self.import_plugin(arg)
-
-    def consider_conftest(self, conftestmodule):
-        if self.register(conftestmodule, name=conftestmodule.__file__):
-            self.consider_module(conftestmodule)
-
-    def consider_env(self):
-        self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
-
-    def consider_module(self, mod):
-        self._import_plugin_specs(getattr(mod, "pytest_plugins", None))
-
-    def _import_plugin_specs(self, spec):
-        if spec:
-            if isinstance(spec, str):
-                spec = spec.split(",")
-            for import_spec in spec:
-                self.import_plugin(import_spec)
-
-    def import_plugin(self, modname):
-        # most often modname refers to builtin modules, e.g. "pytester",
-        # "terminal" or "capture".  Those plugins are registered under their
-        # basename for historic purposes but must be imported with the
-        # _pytest prefix.
-        assert isinstance(modname, str)
-        if self.get_plugin(modname) is not None:
-            return
-        if modname in builtin_plugins:
-            importspec = "_pytest." + modname
-        else:
-            importspec = modname
-        try:
-            __import__(importspec)
-        except ImportError as e:
-            new_exc = ImportError('Error importing plugin "%s": %s' % (modname, e))
-            # copy over name and path attributes
-            for attr in ('name', 'path'):
-                if hasattr(e, attr):
-                    setattr(new_exc, attr, getattr(e, attr))
-            raise new_exc
-        except Exception as e:
-            import pytest
-            if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
-                raise
-            self._warn("skipped plugin %r: %s" %((modname, e.msg)))
-        else:
-            mod = sys.modules[importspec]
-            self.register(mod, modname)
-            self.consider_module(mod)
-
-
-class Parser:
-    """ Parser for command line arguments and ini-file values.
-
-    :ivar extra_info: dict of generic param -> value to display in case
-        there's an error processing the command line arguments.
-    """
-
-    def __init__(self, usage=None, processopt=None):
-        self._anonymous = OptionGroup("custom options", parser=self)
-        self._groups = []
-        self._processopt = processopt
-        self._usage = usage
-        self._inidict = {}
-        self._ininames = []
-        self.extra_info = {}
-
-    def processoption(self, option):
-        if self._processopt:
-            if option.dest:
-                self._processopt(option)
-
-    def getgroup(self, name, description="", after=None):
-        """ get (or create) a named option Group.
-
-        :name: name of the option group.
-        :description: long description for --help output.
-        :after: name of other group, used for ordering --help output.
-
-        The returned group object has an ``addoption`` method with the same
-        signature as :py:func:`parser.addoption
-        <_pytest.config.Parser.addoption>` but will be shown in the
-        respective group in the output of ``pytest. --help``.
-        """
-        for group in self._groups:
-            if group.name == name:
-                return group
-        group = OptionGroup(name, description, parser=self)
-        i = 0
-        for i, grp in enumerate(self._groups):
-            if grp.name == after:
-                break
-        self._groups.insert(i+1, group)
-        return group
-
-    def addoption(self, *opts, **attrs):
-        """ register a command line option.
-
-        :opts: option names, can be short or long options.
-        :attrs: same attributes which the ``add_option()`` function of the
-           `argparse library
-           <http://docs.python.org/2/library/argparse.html>`_
-           accepts.
-
-        After command line parsing options are available on the pytest config
-        object via ``config.option.NAME`` where ``NAME`` is usually set
-        by passing a ``dest`` attribute, for example
-        ``addoption("--long", dest="NAME", ...)``.
-        """
-        self._anonymous.addoption(*opts, **attrs)
-
-    def parse(self, args, namespace=None):
-        from _pytest._argcomplete import try_argcomplete
-        self.optparser = self._getparser()
-        try_argcomplete(self.optparser)
-        return self.optparser.parse_args([str(x) for x in args], namespace=namespace)
-
-    def _getparser(self):
-        from _pytest._argcomplete import filescompleter
-        optparser = MyOptionParser(self, self.extra_info)
-        groups = self._groups + [self._anonymous]
-        for group in groups:
-            if group.options:
-                desc = group.description or group.name
-                arggroup = optparser.add_argument_group(desc)
-                for option in group.options:
-                    n = option.names()
-                    a = option.attrs()
-                    arggroup.add_argument(*n, **a)
-        # bash like autocompletion for dirs (appending '/')
-        optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter
-        return optparser
-
-    def parse_setoption(self, args, option, namespace=None):
-        parsedoption = self.parse(args, namespace=namespace)
-        for name, value in parsedoption.__dict__.items():
-            setattr(option, name, value)
-        return getattr(parsedoption, FILE_OR_DIR)
-
-    def parse_known_args(self, args, namespace=None):
-        """parses and returns a namespace object with known arguments at this
-        point.
-        """
-        return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
-
-    def parse_known_and_unknown_args(self, args, namespace=None):
-        """parses and returns a namespace object with known arguments, and
-        the remaining arguments unknown at this point.
-        """
-        optparser = self._getparser()
-        args = [str(x) for x in args]
-        return optparser.parse_known_args(args, namespace=namespace)
-
-    def addini(self, name, help, type=None, default=None):
-        """ register an ini-file option.
-
-        :name: name of the ini-variable
-        :type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
-               or ``bool``.
-        :default: default value if no ini-file option exists but is queried.
-
-        The value of ini-variables can be retrieved via a call to
-        :py:func:`config.getini(name) <_pytest.config.Config.getini>`.
-        """
-        assert type in (None, "pathlist", "args", "linelist", "bool")
-        self._inidict[name] = (help, type, default)
-        self._ininames.append(name)
-
-
-class ArgumentError(Exception):
-    """
-    Raised if an Argument instance is created with invalid or
-    inconsistent arguments.
-    """
-
-    def __init__(self, msg, option):
-        self.msg = msg
-        self.option_id = str(option)
-
-    def __str__(self):
-        if self.option_id:
-            return "option %s: %s" % (self.option_id, self.msg)
-        else:
-            return self.msg
-
-
-class Argument:
-    """class that mimics the necessary behaviour of optparse.Option """
-    _typ_map = {
-        'int': int,
-        'string': str,
-        }
-    # enable after some grace period for plugin writers
-    TYPE_WARN = False
-
-    def __init__(self, *names, **attrs):
-        """store parms in private vars for use in add_argument"""
-        self._attrs = attrs
-        self._short_opts = []
-        self._long_opts = []
-        self.dest = attrs.get('dest')
-        if self.TYPE_WARN:
-            try:
-                help = attrs['help']
-                if '%default' in help:
-                    warnings.warn(
-                        'pytest now uses argparse. "%default" should be'
-                        ' changed to "%(default)s" ',
-                        FutureWarning,
-                        stacklevel=3)
-            except KeyError:
-                pass
-        try:
-            typ = attrs['type']
-        except KeyError:
-            pass
-        else:
-            # this might raise a keyerror as well, don't want to catch that
-            if isinstance(typ, py.builtin._basestring):
-                if typ == 'choice':
-                    if self.TYPE_WARN:
-                        warnings.warn(
-                            'type argument to addoption() is a string %r.'
-                            ' For parsearg this is optional and when supplied '
-                            ' should be a type.'
-                            ' (options: %s)' % (typ, names),
-                            FutureWarning,
-                            stacklevel=3)
-                    # argparse expects a type here take it from
-                    # the type of the first element
-                    attrs['type'] = type(attrs['choices'][0])
-                else:
-                    if self.TYPE_WARN:
-                        warnings.warn(
-                            'type argument to addoption() is a string %r.'
-                            ' For parsearg this should be a type.'
-                            ' (options: %s)' % (typ, names),
-                            FutureWarning,
-                            stacklevel=3)
-                    attrs['type'] = Argument._typ_map[typ]
-                # used in test_parseopt -> test_parse_defaultgetter
-                self.type = attrs['type']
-            else:
-                self.type = typ
-        try:
-            # attribute existence is tested in Config._processopt
-            self.default = attrs['default']
-        except KeyError:
-            pass
-        self._set_opt_strings(names)
-        if not self.dest:
-            if self._long_opts:
-                self.dest = self._long_opts[0][2:].replace('-', '_')
-            else:
-                try:
-                    self.dest = self._short_opts[0][1:]
-                except IndexError:
-                    raise ArgumentError(
-                        'need a long or short option', self)
-
-    def names(self):
-        return self._short_opts + self._long_opts
-
-    def attrs(self):
-        # update any attributes set by processopt
-        attrs = 'default dest help'.split()
-        if self.dest:
-            attrs.append(self.dest)
-        for attr in attrs:
-            try:
-                self._attrs[attr] = getattr(self, attr)
-            except AttributeError:
-                pass
-        if self._attrs.get('help'):
-            a = self._attrs['help']
-            a = a.replace('%default', '%(default)s')
-            #a = a.replace('%prog', '%(prog)s')
-            self._attrs['help'] = a
-        return self._attrs
-
-    def _set_opt_strings(self, opts):
-        """directly from optparse
-
-        might not be necessary as this is passed to argparse later on"""
-        for opt in opts:
-            if len(opt) < 2:
-                raise ArgumentError(
-                    "invalid option string %r: "
-                    "must be at least two characters long" % opt, self)
-            elif len(opt) == 2:
-                if not (opt[0] == "-" and opt[1] != "-"):
-                    raise ArgumentError(
-                        "invalid short option string %r: "
-                        "must be of the form -x, (x any non-dash char)" % opt,
-                        self)
-                self._short_opts.append(opt)
-            else:
-                if not (opt[0:2] == "--" and opt[2] != "-"):
-                    raise ArgumentError(
-                        "invalid long option string %r: "
-                        "must start with --, followed by non-dash" % opt,
-                        self)
-                self._long_opts.append(opt)
-
-    def __repr__(self):
-        retval = 'Argument('
-        if self._short_opts:
-            retval += '_short_opts: ' + repr(self._short_opts) + ', '
-        if self._long_opts:
-            retval += '_long_opts: ' + repr(self._long_opts) + ', '
-        retval += 'dest: ' + repr(self.dest) + ', '
-        if hasattr(self, 'type'):
-            retval += 'type: ' + repr(self.type) + ', '
-        if hasattr(self, 'default'):
-            retval += 'default: ' + repr(self.default) + ', '
-        if retval[-2:] == ', ':  # always long enough to test ("Argument(" )
-            retval = retval[:-2]
-        retval += ')'
-        return retval
-
-
-class OptionGroup:
-    def __init__(self, name, description="", parser=None):
-        self.name = name
-        self.description = description
-        self.options = []
-        self.parser = parser
-
-    def addoption(self, *optnames, **attrs):
-        """ add an option to this group.
-
-        if a shortened version of a long option is specified it will
-        be suppressed in the help. addoption('--twowords', '--two-words')
-        results in help showing '--two-words' only, but --twowords gets
-        accepted **and** the automatic destination is in args.twowords
-        """
-        option = Argument(*optnames, **attrs)
-        self._addoption_instance(option, shortupper=False)
-
-    def _addoption(self, *optnames, **attrs):
-        option = Argument(*optnames, **attrs)
-        self._addoption_instance(option, shortupper=True)
-
-    def _addoption_instance(self, option, shortupper=False):
-        if not shortupper:
-            for opt in option._short_opts:
-                if opt[0] == '-' and opt[1].islower():
-                    raise ValueError("lowercase shortoptions reserved")
-        if self.parser:
-            self.parser.processoption(option)
-        self.options.append(option)
-
-
-class MyOptionParser(argparse.ArgumentParser):
-    def __init__(self, parser, extra_info=None):
-        if not extra_info:
-            extra_info = {}
-        self._parser = parser
-        argparse.ArgumentParser.__init__(self, usage=parser._usage,
-            add_help=False, formatter_class=DropShorterLongHelpFormatter)
-        # extra_info is a dict of (param -> value) to display if there's
-        # an usage error to provide more contextual information to the user
-        self.extra_info = extra_info
-
-    def parse_args(self, args=None, namespace=None):
-        """allow splitting of positional arguments"""
-        args, argv = self.parse_known_args(args, namespace)
-        if argv:
-            for arg in argv:
-                if arg and arg[0] == '-':
-                    lines = ['unrecognized arguments: %s' % (' '.join(argv))]
-                    for k, v in sorted(self.extra_info.items()):
-                        lines.append('  %s: %s' % (k, v))
-                    self.error('\n'.join(lines))
-            getattr(args, FILE_OR_DIR).extend(argv)
-        return args
-
-
-class DropShorterLongHelpFormatter(argparse.HelpFormatter):
-    """shorten help for long options that differ only in extra hyphens
-
-    - collapse **long** options that are the same except for extra hyphens
-    - special action attribute map_long_option allows surpressing additional
-      long options
-    - shortcut if there are only two options and one of them is a short one
-    - cache result on action object as this is called at least 2 times
-    """
-    def _format_action_invocation(self, action):
-        orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
-        if orgstr and orgstr[0] != '-': # only optional arguments
-            return orgstr
-        res = getattr(action, '_formatted_action_invocation', None)
-        if res:
-            return res
-        options = orgstr.split(', ')
-        if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
-            # a shortcut for '-h, --help' or '--abc', '-a'
-            action._formatted_action_invocation = orgstr
-            return orgstr
-        return_list = []
-        option_map =  getattr(action, 'map_long_option', {})
-        if option_map is None:
-            option_map = {}
-        short_long = {}
-        for option in options:
-            if len(option) == 2 or option[2] == ' ':
-                continue
-            if not option.startswith('--'):
-                raise ArgumentError('long optional argument without "--": [%s]'
-                                    % (option), self)
-            xxoption = option[2:]
-            if xxoption.split()[0] not in option_map:
-                shortened = xxoption.replace('-', '')
-                if shortened not in short_long or \
-                   len(short_long[shortened]) < len(xxoption):
-                    short_long[shortened] = xxoption
-        # now short_long has been filled out to the longest with dashes
-        # **and** we keep the right option ordering from add_argument
-        for option in options: #
-            if len(option) == 2 or option[2] == ' ':
-                return_list.append(option)
-            if option[2:] == short_long.get(option.replace('-', '')):
-                return_list.append(option.replace(' ', '='))
-        action._formatted_action_invocation = ', '.join(return_list)
-        return action._formatted_action_invocation
-
-
-
-def _ensure_removed_sysmodule(modname):
-    try:
-        del sys.modules[modname]
-    except KeyError:
-        pass
-
-class CmdOptions(object):
-    """ holds cmdline options as attributes."""
-    def __init__(self, values=()):
-        self.__dict__.update(values)
-    def __repr__(self):
-        return "<CmdOptions %r>" %(self.__dict__,)
-    def copy(self):
-        return CmdOptions(self.__dict__)
-
-class Notset:
-    def __repr__(self):
-        return "<NOTSET>"
-
-notset = Notset()
-FILE_OR_DIR = 'file_or_dir'
-
-class Config(object):
-    """ access to configuration values, pluginmanager and plugin hooks.  """
-
-    def __init__(self, pluginmanager):
-        #: access to command line option as attributes.
-        #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
-        self.option = CmdOptions()
-        _a = FILE_OR_DIR
-        self._parser = Parser(
-            usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
-            processopt=self._processopt,
-        )
-        #: a pluginmanager instance
-        self.pluginmanager = pluginmanager
-        self.trace = self.pluginmanager.trace.root.get("config")
-        self.hook = self.pluginmanager.hook
-        self._inicache = {}
-        self._opt2dest = {}
-        self._cleanup = []
-        self._warn = self.pluginmanager._warn
-        self.pluginmanager.register(self, "pytestconfig")
-        self._configured = False
-        def do_setns(dic):
-            import pytest
-            setns(pytest, dic)
-        self.hook.pytest_namespace.call_historic(do_setns, {})
-        self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
-
-    def add_cleanup(self, func):
-        """ Add a function to be called when the config object gets out of
-        use (usually coninciding with pytest_unconfigure)."""
-        self._cleanup.append(func)
-
-    def _do_configure(self):
-        assert not self._configured
-        self._configured = True
-        self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
-
-    def _ensure_unconfigure(self):
-        if self._configured:
-            self._configured = False
-            self.hook.pytest_unconfigure(config=self)
-            self.hook.pytest_configure._call_history = []
-        while self._cleanup:
-            fin = self._cleanup.pop()
-            fin()
-
-    def warn(self, code, message, fslocation=None):
-        """ generate a warning for this test session. """
-        self.hook.pytest_logwarning.call_historic(kwargs=dict(
-            code=code, message=message,
-            fslocation=fslocation, nodeid=None))
-
-    def get_terminal_writer(self):
-        return self.pluginmanager.get_plugin("terminalreporter")._tw
-
-    def pytest_cmdline_parse(self, pluginmanager, args):
-        # REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
-        self.parse(args)
-        return self
-
-    def notify_exception(self, excinfo, option=None):
-        if option and option.fulltrace:
-            style = "long"
-        else:
-            style = "native"
-        excrepr = excinfo.getrepr(funcargs=True,
-            showlocals=getattr(option, 'showlocals', False),
-            style=style,
-        )
-        res = self.hook.pytest_internalerror(excrepr=excrepr,
-                                             excinfo=excinfo)
-        if not py.builtin.any(res):
-            for line in str(excrepr).split("\n"):
-                sys.stderr.write("INTERNALERROR> %s\n" %line)
-                sys.stderr.flush()
-
-    def cwd_relative_nodeid(self, nodeid):
-        # nodeid's are relative to the rootpath, compute relative to cwd
-        if self.invocation_dir != self.rootdir:
-            fullpath = self.rootdir.join(nodeid)
-            nodeid = self.invocation_dir.bestrelpath(fullpath)
-        return nodeid
-
-    @classmethod
-    def fromdictargs(cls, option_dict, args):
-        """ constructor useable for subprocesses. """
-        config = get_config()
-        config.option.__dict__.update(option_dict)
-        config.parse(args, addopts=False)
-        for x in config.option.plugins:
-            config.pluginmanager.consider_pluginarg(x)
-        return config
-
-    def _processopt(self, opt):
-        for name in opt._short_opts + opt._long_opts:
-            self._opt2dest[name] = opt.dest
-
-        if hasattr(opt, 'default') and opt.dest:
-            if not hasattr(self.option, opt.dest):
-                setattr(self.option, opt.dest, opt.default)
-
-    @hookimpl(trylast=True)
-    def pytest_load_initial_conftests(self, early_config):
-        self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
-
-    def _initini(self, args):
-        ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy())
-        r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args)
-        self.rootdir, self.inifile, self.inicfg = r
-        self._parser.extra_info['rootdir'] = self.rootdir
-        self._parser.extra_info['inifile'] = self.inifile
-        self.invocation_dir = py.path.local()
-        self._parser.addini('addopts', 'extra command line options', 'args')
-        self._parser.addini('minversion', 'minimally required pytest version')
-
-    def _preparse(self, args, addopts=True):
-        self._initini(args)
-        if addopts:
-            args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
-            args[:] = self.getini("addopts") + args
-        self._checkversion()
-        self.pluginmanager.consider_preparse(args)
-        try:
-            self.pluginmanager.load_setuptools_entrypoints("pytest11")
-        except ImportError as e:
-            self.warn("I2", "could not load setuptools entry import: %s" % (e,))
-        self.pluginmanager.consider_env()
-        self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
-        if self.known_args_namespace.confcutdir is None and self.inifile:
-            confcutdir = py.path.local(self.inifile).dirname
-            self.known_args_namespace.confcutdir = confcutdir
-        try:
-            self.hook.pytest_load_initial_conftests(early_config=self,
-                    args=args, parser=self._parser)
-        except ConftestImportFailure:
-            e = sys.exc_info()[1]
-            if ns.help or ns.version:
-                # we don't want to prevent --help/--version to work
-                # so just let is pass and print a warning at the end
-                self._warn("could not load initial conftests (%s)\n" % e.path)
-            else:
-                raise
-
-    def _checkversion(self):
-        import pytest
-        minver = self.inicfg.get('minversion', None)
-        if minver:
-            ver = minver.split(".")
-            myver = pytest.__version__.split(".")
-            if myver < ver:
-                raise pytest.UsageError(
-                    "%s:%d: requires pytest-%s, actual pytest-%s'" %(
-                    self.inicfg.config.path, self.inicfg.lineof('minversion'),
-                    minver, pytest.__version__))
-
-    def parse(self, args, addopts=True):
-        # parse given cmdline arguments into this config object.
-        assert not hasattr(self, 'args'), (
-                "can only parse cmdline args at most once per Config object")
-        self._origargs = args
-        self.hook.pytest_addhooks.call_historic(
-                                  kwargs=dict(pluginmanager=self.pluginmanager))
-        self._preparse(args, addopts=addopts)
-        # XXX deprecated hook:
-        self.hook.pytest_cmdline_preparse(config=self, args=args)
-        args = self._parser.parse_setoption(args, self.option, namespace=self.option)
-        if not args:
-            cwd = os.getcwd()
-            if cwd == self.rootdir:
-                args = self.getini('testpaths')
-            if not args:
-                args = [cwd]
-        self.args = args
-
-    def addinivalue_line(self, name, line):
-        """ add a line to an ini-file option. The option must have been
-        declared but might not yet be set in which case the line becomes the
-        the first line in its value. """
-        x = self.getini(name)
-        assert isinstance(x, list)
-        x.append(line) # modifies the cached list inline
-
-    def getini(self, name):
-        """ return configuration value from an :ref:`ini file <inifiles>`. If the
-        specified name hasn't been registered through a prior
-        :py:func:`parser.addini <pytest.config.Parser.addini>`
-        call (usually from a plugin), a ValueError is raised. """
-        try:
-            return self._inicache[name]
-        except KeyError:
-            self._inicache[name] = val = self._getini(name)
-            return val
-
-    def _getini(self, name):
-        try:
-            description, type, default = self._parser._inidict[name]
-        except KeyError:
-            raise ValueError("unknown configuration value: %r" %(name,))
-        try:
-            value = self.inicfg[name]
-        except KeyError:
-            if default is not None:
-                return default
-            if type is None:
-                return ''
-            return []
-        if type == "pathlist":
-            dp = py.path.local(self.inicfg.config.path).dirpath()
-            l = []
-            for relpath in shlex.split(value):
-                l.append(dp.join(relpath, abs=True))
-            return l
-        elif type == "args":
-            return shlex.split(value)
-        elif type == "linelist":
-            return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
-        elif type == "bool":
-            return bool(_strtobool(value.strip()))
-        else:
-            assert type is None
-            return value
-
-    def _getconftest_pathlist(self, name, path):
-        try:
-            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
-        except KeyError:
-            return None
-        modpath = py.path.local(mod.__file__).dirpath()
-        l = []
-        for relroot in relroots:
-            if not isinstance(relroot, py.path.local):
-                relroot = relroot.replace("/", py.path.local.sep)
-                relroot = modpath.join(relroot, abs=True)
-            l.append(relroot)
-        return l
-
-    def getoption(self, name, default=notset, skip=False):
-        """ return command line option value.
-
-        :arg name: name of the option.  You may also specify
-            the literal ``--OPT`` option instead of the "dest" option name.
-        :arg default: default value if no option of that name exists.
-        :arg skip: if True raise pytest.skip if option does not exists
-            or has a None value.
-        """
-        name = self._opt2dest.get(name, name)
-        try:
-            val = getattr(self.option, name)
-            if val is None and skip:
-                raise AttributeError(name)
-            return val
-        except AttributeError:
-            if default is not notset:
-                return default
-            if skip:
-                import pytest
-                pytest.skip("no %r option found" %(name,))
-            raise ValueError("no option named %r" % (name,))
-
-    def getvalue(self, name, path=None):
-        """ (deprecated, use getoption()) """
-        return self.getoption(name)
-
-    def getvalueorskip(self, name, path=None):
-        """ (deprecated, use getoption(skip=True)) """
-        return self.getoption(name, skip=True)
-
-def exists(path, ignore=EnvironmentError):
-    try:
-        return path.check()
-    except ignore:
-        return False
-
-def getcfg(args, inibasenames):
-    args = [x for x in args if not str(x).startswith("-")]
-    if not args:
-        args = [py.path.local()]
-    for arg in args:
-        arg = py.path.local(arg)
-        for base in arg.parts(reverse=True):
-            for inibasename in inibasenames:
-                p = base.join(inibasename)
-                if exists(p):
-                    iniconfig = py.iniconfig.IniConfig(p)
-                    if 'pytest' in iniconfig.sections:
-                        return base, p, iniconfig['pytest']
-                    elif inibasename == "pytest.ini":
-                        # allowed to be empty
-                        return base, p, {}
-    return None, None, None
-
-
-def get_common_ancestor(args):
-    # args are what we get after early command line parsing (usually
-    # strings, but can be py.path.local objects as well)
-    common_ancestor = None
-    for arg in args:
-        if str(arg)[0] == "-":
-            continue
-        p = py.path.local(arg)
-        if common_ancestor is None:
-            common_ancestor = p
-        else:
-            if p.relto(common_ancestor) or p == common_ancestor:
-                continue
-            elif common_ancestor.relto(p):
-                common_ancestor = p
-            else:
-                shared = p.common(common_ancestor)
-                if shared is not None:
-                    common_ancestor = shared
-    if common_ancestor is None:
-        common_ancestor = py.path.local()
-    elif not common_ancestor.isdir():
-        common_ancestor = common_ancestor.dirpath()
-    return common_ancestor
-
-
-def determine_setup(inifile, args):
-    if inifile:
-        iniconfig = py.iniconfig.IniConfig(inifile)
-        try:
-            inicfg = iniconfig["pytest"]
-        except KeyError:
-            inicfg = None
-        rootdir = get_common_ancestor(args)
-    else:
-        ancestor = get_common_ancestor(args)
-        rootdir, inifile, inicfg = getcfg(
-            [ancestor], ["pytest.ini", "tox.ini", "setup.cfg"])
-        if rootdir is None:
-            for rootdir in ancestor.parts(reverse=True):
-                if rootdir.join("setup.py").exists():
-                    break
-            else:
-                rootdir = ancestor
-    return rootdir, inifile, inicfg or {}
-
-
-def setns(obj, dic):
-    import pytest
-    for name, value in dic.items():
-        if isinstance(value, dict):
-            mod = getattr(obj, name, None)
-            if mod is None:
-                modname = "pytest.%s" % name
-                mod = types.ModuleType(modname)
-                sys.modules[modname] = mod
-                mod.__all__ = []
-                setattr(obj, name, mod)
-            obj.__all__.append(name)
-            setns(mod, value)
-        else:
-            setattr(obj, name, value)
-            obj.__all__.append(name)
-            #if obj != pytest:
-            #    pytest.__all__.append(name)
-            setattr(pytest, name, value)
-
-
-def create_terminal_writer(config, *args, **kwargs):
-    """Create a TerminalWriter instance configured according to the options
-    in the config object. Every code which requires a TerminalWriter object
-    and has access to a config object should use this function.
-    """
-    tw = py.io.TerminalWriter(*args, **kwargs)
-    if config.option.color == 'yes':
-        tw.hasmarkup = True
-    if config.option.color == 'no':
-        tw.hasmarkup = False
-    return tw
-
-
-def _strtobool(val):
-    """Convert a string representation of truth to true (1) or false (0).
-
-    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
-    are 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if
-    'val' is anything else.
-
-    .. note:: copied from distutils.util
-    """
-    val = val.lower()
-    if val in ('y', 'yes', 't', 'true', 'on', '1'):
-        return 1
-    elif val in ('n', 'no', 'f', 'false', 'off', '0'):
-        return 0
-    else:
-        raise ValueError("invalid truth value %r" % (val,))
diff --git a/tools/pytest/_pytest/doctest.py b/tools/pytest/_pytest/doctest.py
deleted file mode 100644
index a57f7a4..0000000
--- a/tools/pytest/_pytest/doctest.py
+++ /dev/null
@@ -1,290 +0,0 @@
-""" discover and run doctests in modules and test files."""
-from __future__ import absolute_import
-
-import traceback
-
-import pytest
-from _pytest._code.code import TerminalRepr, ReprFileLocation, ExceptionInfo
-from _pytest.python import FixtureRequest
-
-
-
-def pytest_addoption(parser):
-    parser.addini('doctest_optionflags', 'option flags for doctests',
-        type="args", default=["ELLIPSIS"])
-    group = parser.getgroup("collect")
-    group.addoption("--doctest-modules",
-        action="store_true", default=False,
-        help="run doctests in all .py modules",
-        dest="doctestmodules")
-    group.addoption("--doctest-glob",
-        action="append", default=[], metavar="pat",
-        help="doctests file matching pattern, default: test*.txt",
-        dest="doctestglob")
-    group.addoption("--doctest-ignore-import-errors",
-        action="store_true", default=False,
-        help="ignore doctest ImportErrors",
-        dest="doctest_ignore_import_errors")
-
-
-def pytest_collect_file(path, parent):
-    config = parent.config
-    if path.ext == ".py":
-        if config.option.doctestmodules:
-            return DoctestModule(path, parent)
-    elif _is_doctest(config, path, parent):
-        return DoctestTextfile(path, parent)
-
-
-def _is_doctest(config, path, parent):
-    if path.ext in ('.txt', '.rst') and parent.session.isinitpath(path):
-        return True
-    globs = config.getoption("doctestglob") or ['test*.txt']
-    for glob in globs:
-        if path.check(fnmatch=glob):
-            return True
-    return False
-
-
-class ReprFailDoctest(TerminalRepr):
-
-    def __init__(self, reprlocation, lines):
-        self.reprlocation = reprlocation
-        self.lines = lines
-
-    def toterminal(self, tw):
-        for line in self.lines:
-            tw.line(line)
-        self.reprlocation.toterminal(tw)
-
-
-class DoctestItem(pytest.Item):
-
-    def __init__(self, name, parent, runner=None, dtest=None):
-        super(DoctestItem, self).__init__(name, parent)
-        self.runner = runner
-        self.dtest = dtest
-        self.obj = None
-        self.fixture_request = None
-
-    def setup(self):
-        if self.dtest is not None:
-            self.fixture_request = _setup_fixtures(self)
-            globs = dict(getfixture=self.fixture_request.getfuncargvalue)
-            self.dtest.globs.update(globs)
-
-    def runtest(self):
-        _check_all_skipped(self.dtest)
-        self.runner.run(self.dtest)
-
-    def repr_failure(self, excinfo):
-        import doctest
-        if excinfo.errisinstance((doctest.DocTestFailure,
-                                  doctest.UnexpectedException)):
-            doctestfailure = excinfo.value
-            example = doctestfailure.example
-            test = doctestfailure.test
-            filename = test.filename
-            if test.lineno is None:
-                lineno = None
-            else:
-                lineno = test.lineno + example.lineno + 1
-            message = excinfo.type.__name__
-            reprlocation = ReprFileLocation(filename, lineno, message)
-            checker = _get_checker()
-            REPORT_UDIFF = doctest.REPORT_UDIFF
-            if lineno is not None:
-                lines = doctestfailure.test.docstring.splitlines(False)
-                # add line numbers to the left of the error message
-                lines = ["%03d %s" % (i + test.lineno + 1, x)
-                         for (i, x) in enumerate(lines)]
-                # trim docstring error lines to 10
-                lines = lines[example.lineno - 9:example.lineno + 1]
-            else:
-                lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
-                indent = '>>>'
-                for line in example.source.splitlines():
-                    lines.append('??? %s %s' % (indent, line))
-                    indent = '...'
-            if excinfo.errisinstance(doctest.DocTestFailure):
-                lines += checker.output_difference(example,
-                        doctestfailure.got, REPORT_UDIFF).split("\n")
-            else:
-                inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
-                lines += ["UNEXPECTED EXCEPTION: %s" %
-                            repr(inner_excinfo.value)]
-                lines += traceback.format_exception(*excinfo.value.exc_info)
-            return ReprFailDoctest(reprlocation, lines)
-        else:
-            return super(DoctestItem, self).repr_failure(excinfo)
-
-    def reportinfo(self):
-        return self.fspath, None, "[doctest] %s" % self.name
-
-
-def _get_flag_lookup():
-    import doctest
-    return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
-                DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
-                NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
-                ELLIPSIS=doctest.ELLIPSIS,
-                IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
-                COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
-                ALLOW_UNICODE=_get_allow_unicode_flag(),
-                ALLOW_BYTES=_get_allow_bytes_flag(),
-                )
-
-
-def get_optionflags(parent):
-    optionflags_str = parent.config.getini("doctest_optionflags")
-    flag_lookup_table = _get_flag_lookup()
-    flag_acc = 0
-    for flag in optionflags_str:
-        flag_acc |= flag_lookup_table[flag]
-    return flag_acc
-
-
-class DoctestTextfile(DoctestItem, pytest.Module):
-
-    def runtest(self):
-        import doctest
-        fixture_request = _setup_fixtures(self)
-
-        # inspired by doctest.testfile; ideally we would use it directly,
-        # but it doesn't support passing a custom checker
-        text = self.fspath.read()
-        filename = str(self.fspath)
-        name = self.fspath.basename
-        globs = dict(getfixture=fixture_request.getfuncargvalue)
-        if '__name__' not in globs:
-            globs['__name__'] = '__main__'
-
-        optionflags = get_optionflags(self)
-        runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
-                                     checker=_get_checker())
-
-        parser = doctest.DocTestParser()
-        test = parser.get_doctest(text, globs, name, filename, 0)
-        _check_all_skipped(test)
-        runner.run(test)
-
-
-def _check_all_skipped(test):
-    """raises pytest.skip() if all examples in the given DocTest have the SKIP
-    option set.
-    """
-    import doctest
-    all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
-    if all_skipped:
-        pytest.skip('all tests skipped by +SKIP option')
-
-
-class DoctestModule(pytest.Module):
-    def collect(self):
-        import doctest
-        if self.fspath.basename == "conftest.py":
-            module = self.config.pluginmanager._importconftest(self.fspath)
-        else:
-            try:
-                module = self.fspath.pyimport()
-            except ImportError:
-                if self.config.getvalue('doctest_ignore_import_errors'):
-                    pytest.skip('unable to import module %r' % self.fspath)
-                else:
-                    raise
-        # uses internal doctest module parsing mechanism
-        finder = doctest.DocTestFinder()
-        optionflags = get_optionflags(self)
-        runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
-                                     checker=_get_checker())
-        for test in finder.find(module, module.__name__):
-            if test.examples:  # skip empty doctests
-                yield DoctestItem(test.name, self, runner, test)
-
-
-def _setup_fixtures(doctest_item):
-    """
-    Used by DoctestTextfile and DoctestItem to setup fixture information.
-    """
-    def func():
-        pass
-
-    doctest_item.funcargs = {}
-    fm = doctest_item.session._fixturemanager
-    doctest_item._fixtureinfo = fm.getfixtureinfo(node=doctest_item, func=func,
-                                                  cls=None, funcargs=False)
-    fixture_request = FixtureRequest(doctest_item)
-    fixture_request._fillfixtures()
-    return fixture_request
-
-
-def _get_checker():
-    """
-    Returns a doctest.OutputChecker subclass that takes in account the
-    ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
-    to strip b'' prefixes.
-    Useful when the same doctest should run in Python 2 and Python 3.
-
-    An inner class is used to avoid importing "doctest" at the module
-    level.
-    """
-    if hasattr(_get_checker, 'LiteralsOutputChecker'):
-        return _get_checker.LiteralsOutputChecker()
-
-    import doctest
-    import re
-
-    class LiteralsOutputChecker(doctest.OutputChecker):
-        """
-        Copied from doctest_nose_plugin.py from the nltk project:
-            https://github.com/nltk/nltk
-
-        Further extended to also support byte literals.
-        """
-
-        _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
-        _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
-
-        def check_output(self, want, got, optionflags):
-            res = doctest.OutputChecker.check_output(self, want, got,
-                                                     optionflags)
-            if res:
-                return True
-
-            allow_unicode = optionflags & _get_allow_unicode_flag()
-            allow_bytes = optionflags & _get_allow_bytes_flag()
-            if not allow_unicode and not allow_bytes:
-                return False
-
-            else:  # pragma: no cover
-                def remove_prefixes(regex, txt):
-                    return re.sub(regex, r'\1\2', txt)
-
-                if allow_unicode:
-                    want = remove_prefixes(self._unicode_literal_re, want)
-                    got = remove_prefixes(self._unicode_literal_re, got)
-                if allow_bytes:
-                    want = remove_prefixes(self._bytes_literal_re, want)
-                    got = remove_prefixes(self._bytes_literal_re, got)
-                res = doctest.OutputChecker.check_output(self, want, got,
-                                                         optionflags)
-                return res
-
-    _get_checker.LiteralsOutputChecker = LiteralsOutputChecker
-    return _get_checker.LiteralsOutputChecker()
-
-
-def _get_allow_unicode_flag():
-    """
-    Registers and returns the ALLOW_UNICODE flag.
-    """
-    import doctest
-    return doctest.register_optionflag('ALLOW_UNICODE')
-
-
-def _get_allow_bytes_flag():
-    """
-    Registers and returns the ALLOW_BYTES flag.
-    """
-    import doctest
-    return doctest.register_optionflag('ALLOW_BYTES')
diff --git a/tools/pytest/_pytest/genscript.py b/tools/pytest/_pytest/genscript.py
deleted file mode 100755
index d2962d8..0000000
--- a/tools/pytest/_pytest/genscript.py
+++ /dev/null
@@ -1,132 +0,0 @@
-""" (deprecated) generate a single-file self-contained version of pytest """
-import os
-import sys
-import pkgutil
-
-import py
-import _pytest
-
-
-
-def find_toplevel(name):
-    for syspath in sys.path:
-        base = py.path.local(syspath)
-        lib = base/name
-        if lib.check(dir=1):
-            return lib
-        mod = base.join("%s.py" % name)
-        if mod.check(file=1):
-            return mod
-    raise LookupError(name)
-
-def pkgname(toplevel, rootpath, path):
-    parts = path.parts()[len(rootpath.parts()):]
-    return '.'.join([toplevel] + [x.purebasename for x in parts])
-
-def pkg_to_mapping(name):
-    toplevel = find_toplevel(name)
-    name2src = {}
-    if toplevel.check(file=1): # module
-        name2src[toplevel.purebasename] = toplevel.read()
-    else: # package
-        for pyfile in toplevel.visit('*.py'):
-            pkg = pkgname(name, toplevel, pyfile)
-            name2src[pkg] = pyfile.read()
-        # with wheels py source code might be not be installed
-        # and the resulting genscript is useless, just bail out.
-        assert name2src, "no source code found for %r at %r" %(name, toplevel)
-    return name2src
-
-def compress_mapping(mapping):
-    import base64, pickle, zlib
-    data = pickle.dumps(mapping, 2)
-    data = zlib.compress(data, 9)
-    data = base64.encodestring(data)
-    data = data.decode('ascii')
-    return data
-
-
-def compress_packages(names):
-    mapping = {}
-    for name in names:
-        mapping.update(pkg_to_mapping(name))
-    return compress_mapping(mapping)
-
-def generate_script(entry, packages):
-    data = compress_packages(packages)
-    tmpl = py.path.local(__file__).dirpath().join('standalonetemplate.py')
-    exe = tmpl.read()
-    exe = exe.replace('@SOURCES@', data)
-    exe = exe.replace('@ENTRY@', entry)
-    return exe
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("debugconfig")
-    group.addoption("--genscript", action="store", default=None,
-        dest="genscript", metavar="path",
-        help="create standalone pytest script at given target path.")
-
-def pytest_cmdline_main(config):
-    import _pytest.config
-    genscript = config.getvalue("genscript")
-    if genscript:
-        tw = _pytest.config.create_terminal_writer(config)
-        tw.line("WARNING: usage of genscript is deprecated.",
-                red=True)
-        deps =  ['py', '_pytest', 'pytest']  # pluggy is vendored
-        if sys.version_info < (2,7):
-            deps.append("argparse")
-            tw.line("generated script will run on python2.6-python3.3++")
-        else:
-            tw.line("WARNING: generated script will not run on python2.6 "
-                    "due to 'argparse' dependency. Use python2.6 "
-                    "to generate a python2.6 compatible script", red=True)
-        script = generate_script(
-            'import pytest; raise SystemExit(pytest.cmdline.main())',
-            deps,
-        )
-        genscript = py.path.local(genscript)
-        genscript.write(script)
-        tw.line("generated pytest standalone script: %s" % genscript,
-                bold=True)
-        return 0
-
-
-def pytest_namespace():
-    return {'freeze_includes': freeze_includes}
-
-
-def freeze_includes():
-    """
-    Returns a list of module names used by py.test that should be
-    included by cx_freeze.
-    """
-    result = list(_iter_all_modules(py))
-    result += list(_iter_all_modules(_pytest))
-    return result
-
-
-def _iter_all_modules(package, prefix=''):
-    """
-    Iterates over the names of all modules that can be found in the given
-    package, recursively.
-
-    Example:
-        _iter_all_modules(_pytest) ->
-            ['_pytest.assertion.newinterpret',
-             '_pytest.capture',
-             '_pytest.core',
-             ...
-            ]
-    """
-    if type(package) is not str:
-        path, prefix = package.__path__[0], package.__name__ + '.'
-    else:
-        path = package
-    for _, name, is_package in pkgutil.iter_modules([path]):
-        if is_package:
-            for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'):
-                yield prefix + m
-        else:
-            yield prefix + name
diff --git a/tools/pytest/_pytest/helpconfig.py b/tools/pytest/_pytest/helpconfig.py
deleted file mode 100644
index 1df0c56..0000000
--- a/tools/pytest/_pytest/helpconfig.py
+++ /dev/null
@@ -1,139 +0,0 @@
-""" version info, help messages, tracing configuration.  """
-import py
-import pytest
-import os, sys
-
-def pytest_addoption(parser):
-    group = parser.getgroup('debugconfig')
-    group.addoption('--version', action="store_true",
-            help="display pytest lib version and import information.")
-    group._addoption("-h", "--help", action="store_true", dest="help",
-            help="show help message and configuration info")
-    group._addoption('-p', action="append", dest="plugins", default = [],
-               metavar="name",
-               help="early-load given plugin (multi-allowed). "
-                    "To avoid loading of plugins, use the `no:` prefix, e.g. "
-                    "`no:doctest`.")
-    group.addoption('--traceconfig', '--trace-config',
-               action="store_true", default=False,
-               help="trace considerations of conftest.py files."),
-    group.addoption('--debug',
-               action="store_true", dest="debug", default=False,
-               help="store internal tracing debug information in 'pytestdebug.log'.")
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_cmdline_parse():
-    outcome = yield
-    config = outcome.get_result()
-    if config.option.debug:
-        path = os.path.abspath("pytestdebug.log")
-        debugfile = open(path, 'w')
-        debugfile.write("versions pytest-%s, py-%s, "
-                "python-%s\ncwd=%s\nargs=%s\n\n" %(
-            pytest.__version__, py.__version__,
-            ".".join(map(str, sys.version_info)),
-            os.getcwd(), config._origargs))
-        config.trace.root.setwriter(debugfile.write)
-        undo_tracing = config.pluginmanager.enable_tracing()
-        sys.stderr.write("writing pytestdebug information to %s\n" % path)
-        def unset_tracing():
-            debugfile.close()
-            sys.stderr.write("wrote pytestdebug information to %s\n" %
-                             debugfile.name)
-            config.trace.root.setwriter(None)
-            undo_tracing()
-        config.add_cleanup(unset_tracing)
-
-def pytest_cmdline_main(config):
-    if config.option.version:
-        p = py.path.local(pytest.__file__)
-        sys.stderr.write("This is pytest version %s, imported from %s\n" %
-            (pytest.__version__, p))
-        plugininfo = getpluginversioninfo(config)
-        if plugininfo:
-            for line in plugininfo:
-                sys.stderr.write(line + "\n")
-        return 0
-    elif config.option.help:
-        config._do_configure()
-        showhelp(config)
-        config._ensure_unconfigure()
-        return 0
-
-def showhelp(config):
-    reporter = config.pluginmanager.get_plugin('terminalreporter')
-    tw = reporter._tw
-    tw.write(config._parser.optparser.format_help())
-    tw.line()
-    tw.line()
-    #tw.sep( "=", "config file settings")
-    tw.line("[pytest] ini-options in the next "
-            "pytest.ini|tox.ini|setup.cfg file:")
-    tw.line()
-
-    for name in config._parser._ininames:
-        help, type, default = config._parser._inidict[name]
-        if type is None:
-            type = "string"
-        spec = "%s (%s)" % (name, type)
-        line = "  %-24s %s" %(spec, help)
-        tw.line(line[:tw.fullwidth])
-
-    tw.line()
-    tw.line("environment variables:")
-    vars = [
-        ("PYTEST_ADDOPTS", "extra command line options"),
-        ("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
-        ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals")
-    ]
-    for name, help in vars:
-        tw.line("  %-24s %s" % (name, help))
-    tw.line()
-    tw.line()
-
-    tw.line("to see available markers type: py.test --markers")
-    tw.line("to see available fixtures type: py.test --fixtures")
-    tw.line("(shown according to specified file_or_dir or current dir "
-            "if not specified)")
-
-    for warningreport in reporter.stats.get('warnings', []):
-        tw.line("warning : " + warningreport.message, red=True)
-    return
-
-
-conftest_options = [
-    ('pytest_plugins', 'list of plugin names to load'),
-]
-
-def getpluginversioninfo(config):
-    lines = []
-    plugininfo = config.pluginmanager.list_plugin_distinfo()
-    if plugininfo:
-        lines.append("setuptools registered plugins:")
-        for plugin, dist in plugininfo:
-            loc = getattr(plugin, '__file__', repr(plugin))
-            content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
-            lines.append("  " + content)
-    return lines
-
-def pytest_report_header(config):
-    lines = []
-    if config.option.debug or config.option.traceconfig:
-        lines.append("using: pytest-%s pylib-%s" %
-            (pytest.__version__,py.__version__))
-
-        verinfo = getpluginversioninfo(config)
-        if verinfo:
-            lines.extend(verinfo)
-
-    if config.option.traceconfig:
-        lines.append("active plugins:")
-        items = config.pluginmanager.list_name_plugin()
-        for name, plugin in items:
-            if hasattr(plugin, '__file__'):
-                r = plugin.__file__
-            else:
-                r = repr(plugin)
-            lines.append("    %-20s: %s" %(name, r))
-    return lines
diff --git a/tools/pytest/_pytest/hookspec.py b/tools/pytest/_pytest/hookspec.py
deleted file mode 100644
index 60e9b47..0000000
--- a/tools/pytest/_pytest/hookspec.py
+++ /dev/null
@@ -1,295 +0,0 @@
-""" hook specifications for pytest plugins, invoked from main.py and builtin plugins.  """
-
-from _pytest._pluggy import HookspecMarker
-
-hookspec = HookspecMarker("pytest")
-
-# -------------------------------------------------------------------------
-# Initialization hooks called for every plugin
-# -------------------------------------------------------------------------
-
-@hookspec(historic=True)
-def pytest_addhooks(pluginmanager):
-    """called at plugin registration time to allow adding new hooks via a call to
-    pluginmanager.add_hookspecs(module_or_class, prefix)."""
-
-
-@hookspec(historic=True)
-def pytest_namespace():
-    """return dict of name->object to be made globally available in
-    the pytest namespace.  This hook is called at plugin registration
-    time.
-    """
-
-@hookspec(historic=True)
-def pytest_plugin_registered(plugin, manager):
-    """ a new pytest plugin got registered. """
-
-
-@hookspec(historic=True)
-def pytest_addoption(parser):
-    """register argparse-style options and ini-style config values,
-    called once at the beginning of a test run.
-
-    .. note::
-
-        This function should be implemented only in plugins or ``conftest.py``
-        files situated at the tests root directory due to how py.test
-        :ref:`discovers plugins during startup <pluginorder>`.
-
-    :arg parser: To add command line options, call
-        :py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
-        To add ini-file values call :py:func:`parser.addini(...)
-        <_pytest.config.Parser.addini>`.
-
-    Options can later be accessed through the
-    :py:class:`config <_pytest.config.Config>` object, respectively:
-
-    - :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
-      retrieve the value of a command line option.
-
-    - :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
-      a value read from an ini-style file.
-
-    The config object is passed around on many internal objects via the ``.config``
-    attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
-    via (deprecated) ``pytest.config``.
-    """
-
-@hookspec(historic=True)
-def pytest_configure(config):
-    """ called after command line options have been parsed
-    and all plugins and initial conftest files been loaded.
-    This hook is called for every plugin.
-    """
-
-# -------------------------------------------------------------------------
-# Bootstrapping hooks called for plugins registered early enough:
-# internal and 3rd party plugins as well as directly
-# discoverable conftest.py local plugins.
-# -------------------------------------------------------------------------
-
-@hookspec(firstresult=True)
-def pytest_cmdline_parse(pluginmanager, args):
-    """return initialized config object, parsing the specified args. """
-
-def pytest_cmdline_preparse(config, args):
-    """(deprecated) modify command line arguments before option parsing. """
-
-@hookspec(firstresult=True)
-def pytest_cmdline_main(config):
-    """ called for performing the main command line action. The default
-    implementation will invoke the configure hooks and runtest_mainloop. """
-
-def pytest_load_initial_conftests(early_config, parser, args):
-    """ implements the loading of initial conftest files ahead
-    of command line option parsing. """
-
-
-# -------------------------------------------------------------------------
-# collection hooks
-# -------------------------------------------------------------------------
-
-@hookspec(firstresult=True)
-def pytest_collection(session):
-    """ perform the collection protocol for the given session. """
-
-def pytest_collection_modifyitems(session, config, items):
-    """ called after collection has been performed, may filter or re-order
-    the items in-place."""
-
-def pytest_collection_finish(session):
-    """ called after collection has been performed and modified. """
-
-@hookspec(firstresult=True)
-def pytest_ignore_collect(path, config):
-    """ return True to prevent considering this path for collection.
-    This hook is consulted for all files and directories prior to calling
-    more specific hooks.
-    """
-
-@hookspec(firstresult=True)
-def pytest_collect_directory(path, parent):
-    """ called before traversing a directory for collection files. """
-
-def pytest_collect_file(path, parent):
-    """ return collection Node or None for the given path. Any new node
-    needs to have the specified ``parent`` as a parent."""
-
-# logging hooks for collection
-def pytest_collectstart(collector):
-    """ collector starts collecting. """
-
-def pytest_itemcollected(item):
-    """ we just collected a test item. """
-
-def pytest_collectreport(report):
-    """ collector finished collecting. """
-
-def pytest_deselected(items):
-    """ called for test items deselected by keyword. """
-
-@hookspec(firstresult=True)
-def pytest_make_collect_report(collector):
-    """ perform ``collector.collect()`` and return a CollectReport. """
-
-# -------------------------------------------------------------------------
-# Python test function related hooks
-# -------------------------------------------------------------------------
-
-@hookspec(firstresult=True)
-def pytest_pycollect_makemodule(path, parent):
-    """ return a Module collector or None for the given path.
-    This hook will be called for each matching test module path.
-    The pytest_collect_file hook needs to be used if you want to
-    create test modules for files that do not match as a test module.
-    """
-
-@hookspec(firstresult=True)
-def pytest_pycollect_makeitem(collector, name, obj):
-    """ return custom item/collector for a python object in a module, or None.  """
-
-@hookspec(firstresult=True)
-def pytest_pyfunc_call(pyfuncitem):
-    """ call underlying test function. """
-
-def pytest_generate_tests(metafunc):
-    """ generate (multiple) parametrized calls to a test function."""
-
-# -------------------------------------------------------------------------
-# generic runtest related hooks
-# -------------------------------------------------------------------------
-
-@hookspec(firstresult=True)
-def pytest_runtestloop(session):
-    """ called for performing the main runtest loop
-    (after collection finished). """
-
-def pytest_itemstart(item, node):
-    """ (deprecated, use pytest_runtest_logstart). """
-
-@hookspec(firstresult=True)
-def pytest_runtest_protocol(item, nextitem):
-    """ implements the runtest_setup/call/teardown protocol for
-    the given test item, including capturing exceptions and calling
-    reporting hooks.
-
-    :arg item: test item for which the runtest protocol is performed.
-
-    :arg nextitem: the scheduled-to-be-next test item (or None if this
-                   is the end my friend).  This argument is passed on to
-                   :py:func:`pytest_runtest_teardown`.
-
-    :return boolean: True if no further hook implementations should be invoked.
-    """
-
-def pytest_runtest_logstart(nodeid, location):
-    """ signal the start of running a single test item. """
-
-def pytest_runtest_setup(item):
-    """ called before ``pytest_runtest_call(item)``. """
-
-def pytest_runtest_call(item):
-    """ called to execute the test ``item``. """
-
-def pytest_runtest_teardown(item, nextitem):
-    """ called after ``pytest_runtest_call``.
-
-    :arg nextitem: the scheduled-to-be-next test item (None if no further
-                   test item is scheduled).  This argument can be used to
-                   perform exact teardowns, i.e. calling just enough finalizers
-                   so that nextitem only needs to call setup-functions.
-    """
-
-@hookspec(firstresult=True)
-def pytest_runtest_makereport(item, call):
-    """ return a :py:class:`_pytest.runner.TestReport` object
-    for the given :py:class:`pytest.Item` and
-    :py:class:`_pytest.runner.CallInfo`.
-    """
-
-def pytest_runtest_logreport(report):
-    """ process a test setup/call/teardown report relating to
-    the respective phase of executing a test. """
-
-# -------------------------------------------------------------------------
-# test session related hooks
-# -------------------------------------------------------------------------
-
-def pytest_sessionstart(session):
-    """ before session.main() is called. """
-
-def pytest_sessionfinish(session, exitstatus):
-    """ whole test run finishes. """
-
-def pytest_unconfigure(config):
-    """ called before test process is exited.  """
-
-
-# -------------------------------------------------------------------------
-# hooks for customising the assert methods
-# -------------------------------------------------------------------------
-
-def pytest_assertrepr_compare(config, op, left, right):
-    """return explanation for comparisons in failing assert expressions.
-
-    Return None for no custom explanation, otherwise return a list
-    of strings.  The strings will be joined by newlines but any newlines
-    *in* a string will be escaped.  Note that all but the first line will
-    be indented sligthly, the intention is for the first line to be a summary.
-    """
-
-# -------------------------------------------------------------------------
-# hooks for influencing reporting (invoked from _pytest_terminal)
-# -------------------------------------------------------------------------
-
-def pytest_report_header(config, startdir):
-    """ return a string to be displayed as header info for terminal reporting."""
-
-@hookspec(firstresult=True)
-def pytest_report_teststatus(report):
-    """ return result-category, shortletter and verbose word for reporting."""
-
-def pytest_terminal_summary(terminalreporter):
-    """ add additional section in terminal summary reporting.  """
-
-
-@hookspec(historic=True)
-def pytest_logwarning(message, code, nodeid, fslocation):
-    """ process a warning specified by a message, a code string,
-    a nodeid and fslocation (both of which may be None
-    if the warning is not tied to a partilar node/location)."""
-
-# -------------------------------------------------------------------------
-# doctest hooks
-# -------------------------------------------------------------------------
-
-@hookspec(firstresult=True)
-def pytest_doctest_prepare_content(content):
-    """ return processed content for a given doctest"""
-
-# -------------------------------------------------------------------------
-# error handling and internal debugging hooks
-# -------------------------------------------------------------------------
-
-def pytest_internalerror(excrepr, excinfo):
-    """ called for internal errors. """
-
-def pytest_keyboard_interrupt(excinfo):
-    """ called for keyboard interrupt. """
-
-def pytest_exception_interact(node, call, report):
-    """called when an exception was raised which can potentially be
-    interactively handled.
-
-    This hook is only called if an exception was raised
-    that is not an internal exception like ``skip.Exception``.
-    """
-
-def pytest_enter_pdb(config):
-    """ called upon pdb.set_trace(), can be used by plugins to take special
-    action just before the python debugger enters in interactive mode.
-
-    :arg config: pytest config object
-    :type config: _pytest.config.Config
-    """
diff --git a/tools/pytest/_pytest/impl b/tools/pytest/_pytest/impl
deleted file mode 100644
index 889e37e..0000000
--- a/tools/pytest/_pytest/impl
+++ /dev/null
@@ -1,254 +0,0 @@
-Sorting per-resource
------------------------------
-
-for any given set of items:
-
-- collect items per session-scoped parametrized funcarg
-- re-order until items no parametrizations are mixed
-
-  examples:
-
-        test()
-        test1(s1)
-        test1(s2)     
-        test2()
-        test3(s1)
-        test3(s2)
-
-  gets sorted to:
-
-        test()
-        test2()
-        test1(s1)
-        test3(s1)
-        test1(s2)
-        test3(s2)
- 
-
-the new @setup functions 
---------------------------------------
-
-Consider a given @setup-marked function::
-
-    @pytest.mark.setup(maxscope=SCOPE)
-    def mysetup(request, arg1, arg2, ...)
-        ...
-        request.addfinalizer(fin)
-        ...
-
-then FUNCARGSET denotes the set of (arg1, arg2, ...) funcargs and
-all of its dependent funcargs.  The mysetup function will execute
-for any matching test item once per scope.  
-
-The scope is determined as the minimum scope of all scopes of the args
-in FUNCARGSET and the given "maxscope". 
-
-If mysetup has been called and no finalizers have been called it is
-called "active".
-
-Furthermore the following rules apply:
-
-- if an arg value in FUNCARGSET is about to be torn down, the 
-  mysetup-registered finalizers will execute as well.
-
-- There will never be two active mysetup invocations.
-
-Example 1, session scope::
-
-    @pytest.mark.funcarg(scope="session", params=[1,2])
-    def db(request):
-        request.addfinalizer(db_finalize)
-
-    @pytest.mark.setup
-    def mysetup(request, db):
-        request.addfinalizer(mysetup_finalize)
-        ...
-
-And a given test module:
-
-    def test_something():
-        ...
-    def test_otherthing():
-        pass
-
-Here is what happens::
-
-    db(request) executes with request.param == 1
-        mysetup(request, db) executes
-            test_something() executes
-            test_otherthing() executes
-            mysetup_finalize() executes
-    db_finalize() executes
-    db(request) executes with request.param == 2
-        mysetup(request, db) executes
-            test_something() executes
-            test_otherthing() executes
-        mysetup_finalize() executes
-    db_finalize() executes
-
-Example 2, session/function scope::
-
-    @pytest.mark.funcarg(scope="session", params=[1,2])
-    def db(request):
-        request.addfinalizer(db_finalize)
-
-    @pytest.mark.setup(scope="function")
-    def mysetup(request, db):
-        ...
-        request.addfinalizer(mysetup_finalize)
-        ...
-
-And a given test module:
-
-    def test_something():
-        ...
-    def test_otherthing():
-        pass
-
-Here is what happens::
-
-    db(request) executes with request.param == 1
-        mysetup(request, db) executes
-            test_something() executes
-        mysetup_finalize() executes
-        mysetup(request, db) executes
-            test_otherthing() executes
-        mysetup_finalize() executes
-    db_finalize() executes
-    db(request) executes with request.param == 2
-        mysetup(request, db) executes
-            test_something() executes
-        mysetup_finalize() executes
-        mysetup(request, db) executes
-            test_otherthing() executes
-        mysetup_finalize() executes
-    db_finalize() executes
-
-
-Example 3 - funcargs session-mix
-----------------------------------------
-
-Similar with funcargs, an example::
-
-    @pytest.mark.funcarg(scope="session", params=[1,2])
-    def db(request):
-        request.addfinalizer(db_finalize)
-
-    @pytest.mark.funcarg(scope="function")
-    def table(request, db):
-        ...
-        request.addfinalizer(table_finalize)
-        ...
-
-And a given test module:
-
-    def test_something(table):
-        ...
-    def test_otherthing(table):
-        pass
-    def test_thirdthing():
-        pass
-
-Here is what happens::
-
-    db(request) executes with param == 1
-        table(request, db)
-            test_something(table)
-        table_finalize()
-        table(request, db)
-            test_otherthing(table)
-        table_finalize()
-    db_finalize
-    db(request) executes with param == 2
-        table(request, db)
-            test_something(table)
-        table_finalize()
-        table(request, db)
-            test_otherthing(table)
-        table_finalize()
-    db_finalize
-    test_thirdthing()
-    
-Data structures
---------------------
-
-pytest internally maintains a dict of active funcargs with cache, param,
-finalizer, (scopeitem?) information:
-
-    active_funcargs = dict()
-
-if a parametrized "db" is activated:
-    
-    active_funcargs["db"] = FuncargInfo(dbvalue, paramindex, 
-                                        FuncargFinalize(...), scopeitem)
-
-if a test is torn down and the next test requires a differently 
-parametrized "db":
-
-    for argname in item.callspec.params:
-        if argname in active_funcargs:
-            funcarginfo = active_funcargs[argname]
-            if funcarginfo.param != item.callspec.params[argname]:
-                funcarginfo.callfinalizer()
-                del node2funcarg[funcarginfo.scopeitem]
-                del active_funcargs[argname]
-    nodes_to_be_torn_down = ...
-    for node in nodes_to_be_torn_down:
-        if node in node2funcarg:
-            argname = node2funcarg[node]
-            active_funcargs[argname].callfinalizer()
-            del node2funcarg[node]
-            del active_funcargs[argname]
-
-if a test is setup requiring a "db" funcarg:
-
-    if "db" in active_funcargs:
-        return active_funcargs["db"][0]
-    funcarginfo = setup_funcarg()
-    active_funcargs["db"] = funcarginfo
-    node2funcarg[funcarginfo.scopeitem] = "db"
-
-Implementation plan for resources
-------------------------------------------
-
-1. Revert FuncargRequest to the old form, unmerge item/request
-   (done)
-2. make funcarg factories be discovered at collection time
-3. Introduce funcarg marker
-4. Introduce funcarg scope parameter
-5. Introduce funcarg parametrize parameter
-6. make setup functions be discovered at collection time
-7. (Introduce a pytest_fixture_protocol/setup_funcargs hook)
-
-methods and data structures
---------------------------------
-
-A FuncarcManager holds all information about funcarg definitions
-including parametrization and scope definitions.  It implements
-a pytest_generate_tests hook which performs parametrization as appropriate.
-
-as a simple example, let's consider a tree where a test function requires
-a "abc" funcarg and its factory defines it as parametrized and scoped
-for Modules.  When collections hits the function item, it creates
-the metafunc object, and calls funcargdb.pytest_generate_tests(metafunc)
-which looks up available funcarg factories and their scope and parametrization.
-This information is equivalent to what can be provided today directly
-at the function site and it should thus be relatively straight forward
-to implement the additional way of defining parametrization/scoping.
-
-conftest loading:
-    each funcarg-factory will populate the session.funcargmanager
-
-When a test item is collected, it grows a dictionary 
-(funcargname2factorycalllist).  A factory lookup is performed 
-for each required funcarg.  The resulting factory call is stored 
-with the item.  If a function is parametrized multiple items are 
-created with respective factory calls. Else if a factory is parametrized
-multiple items and calls to the factory function are created as well.
-
-At setup time, an item populates a funcargs mapping, mapping names
-to values.  If a value is funcarg factories are queried for a given item
-test functions and setup functions are put in a class
-which looks up required funcarg factories.
-
-
diff --git a/tools/pytest/_pytest/junitxml.py b/tools/pytest/_pytest/junitxml.py
deleted file mode 100644
index 660d718..0000000
--- a/tools/pytest/_pytest/junitxml.py
+++ /dev/null
@@ -1,387 +0,0 @@
-"""
-    report test results in JUnit-XML format,
-    for use with Jenkins and build integration servers.
-
-
-Based on initial code from Ross Lawley.
-"""
-# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
-# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
-
-import py
-import os
-import re
-import sys
-import time
-import pytest
-
-# Python 2.X and 3.X compatibility
-if sys.version_info[0] < 3:
-    from codecs import open
-else:
-    unichr = chr
-    unicode = str
-    long = int
-
-
-class Junit(py.xml.Namespace):
-    pass
-
-# We need to get the subset of the invalid unicode ranges according to
-# XML 1.0 which are valid in this python build.  Hence we calculate
-# this dynamically instead of hardcoding it.  The spec range of valid
-# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
-#                    | [#x10000-#x10FFFF]
-_legal_chars = (0x09, 0x0A, 0x0d)
-_legal_ranges = (
-    (0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF),
-)
-_legal_xml_re = [
-    unicode("%s-%s") % (unichr(low), unichr(high))
-    for (low, high) in _legal_ranges if low < sys.maxunicode
-]
-_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
-illegal_xml_re = re.compile(unicode('[^%s]') % unicode('').join(_legal_xml_re))
-del _legal_chars
-del _legal_ranges
-del _legal_xml_re
-
-_py_ext_re = re.compile(r"\.py$")
-
-
-def bin_xml_escape(arg):
-    def repl(matchobj):
-        i = ord(matchobj.group())
-        if i <= 0xFF:
-            return unicode('#x%02X') % i
-        else:
-            return unicode('#x%04X') % i
-
-    return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
-
-
-class _NodeReporter(object):
-    def __init__(self, nodeid, xml):
-
-        self.id = nodeid
-        self.xml = xml
-        self.add_stats = self.xml.add_stats
-        self.duration = 0
-        self.properties = []
-        self.nodes = []
-        self.testcase = None
-        self.attrs = {}
-
-    def append(self, node):
-        self.xml.add_stats(type(node).__name__)
-        self.nodes.append(node)
-
-    def add_property(self, name, value):
-        self.properties.append((str(name), bin_xml_escape(value)))
-
-    def make_properties_node(self):
-        """Return a Junit node containing custom properties, if any.
-        """
-        if self.properties:
-            return Junit.properties([
-                Junit.property(name=name, value=value)
-                for name, value in self.properties
-            ])
-        return ''
-
-    def record_testreport(self, testreport):
-        assert not self.testcase
-        names = mangle_test_address(testreport.nodeid)
-        classnames = names[:-1]
-        if self.xml.prefix:
-            classnames.insert(0, self.xml.prefix)
-        attrs = {
-            "classname": ".".join(classnames),
-            "name": bin_xml_escape(names[-1]),
-            "file": testreport.location[0],
-        }
-        if testreport.location[1] is not None:
-            attrs["line"] = testreport.location[1]
-        self.attrs = attrs
-
-    def to_xml(self):
-        testcase = Junit.testcase(time=self.duration, **self.attrs)
-        testcase.append(self.make_properties_node())
-        for node in self.nodes:
-            testcase.append(node)
-        return testcase
-
-    def _add_simple(self, kind, message, data=None):
-        data = bin_xml_escape(data)
-        node = kind(data, message=message)
-        self.append(node)
-
-    def _write_captured_output(self, report):
-        for capname in ('out', 'err'):
-            allcontent = ""
-            for name, content in report.get_sections("Captured std%s" %
-                                                     capname):
-                allcontent += content
-            if allcontent:
-                tag = getattr(Junit, 'system-' + capname)
-                self.append(tag(bin_xml_escape(allcontent)))
-
-    def append_pass(self, report):
-        self.add_stats('passed')
-        self._write_captured_output(report)
-
-    def append_failure(self, report):
-        # msg = str(report.longrepr.reprtraceback.extraline)
-        if hasattr(report, "wasxfail"):
-            self._add_simple(
-                Junit.skipped,
-                "xfail-marked test passes unexpectedly")
-        else:
-            if hasattr(report.longrepr, "reprcrash"):
-                message = report.longrepr.reprcrash.message
-            elif isinstance(report.longrepr, (unicode, str)):
-                message = report.longrepr
-            else:
-                message = str(report.longrepr)
-            message = bin_xml_escape(message)
-            fail = Junit.failure(message=message)
-            fail.append(bin_xml_escape(report.longrepr))
-            self.append(fail)
-        self._write_captured_output(report)
-
-    def append_collect_error(self, report):
-        # msg = str(report.longrepr.reprtraceback.extraline)
-        self.append(Junit.error(bin_xml_escape(report.longrepr),
-                                message="collection failure"))
-
-    def append_collect_skipped(self, report):
-        self._add_simple(
-            Junit.skipped, "collection skipped", report.longrepr)
-
-    def append_error(self, report):
-        self._add_simple(
-            Junit.error, "test setup failure", report.longrepr)
-        self._write_captured_output(report)
-
-    def append_skipped(self, report):
-        if hasattr(report, "wasxfail"):
-            self._add_simple(
-                Junit.skipped, "expected test failure", report.wasxfail
-            )
-        else:
-            filename, lineno, skipreason = report.longrepr
-            if skipreason.startswith("Skipped: "):
-                skipreason = bin_xml_escape(skipreason[9:])
-            self.append(
-                Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
-                              type="pytest.skip",
-                              message=skipreason))
-        self._write_captured_output(report)
-
-    def finalize(self):
-        data = self.to_xml().unicode(indent=0)
-        self.__dict__.clear()
-        self.to_xml = lambda: py.xml.raw(data)
-
-
-@pytest.fixture
-def record_xml_property(request):
-    """Fixture that adds extra xml properties to the tag for the calling test.
-    The fixture is callable with (name, value), with value being automatically
-    xml-encoded.
-    """
-    request.node.warn(
-        code='C3',
-        message='record_xml_property is an experimental feature',
-    )
-    xml = getattr(request.config, "_xml", None)
-    if xml is not None:
-        node_reporter = xml.node_reporter(request.node.nodeid)
-        return node_reporter.add_property
-    else:
-        def add_property_noop(name, value):
-            pass
-
-        return add_property_noop
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("terminal reporting")
-    group.addoption(
-        '--junitxml', '--junit-xml',
-        action="store",
-        dest="xmlpath",
-        metavar="path",
-        default=None,
-        help="create junit-xml style report file at given path.")
-    group.addoption(
-        '--junitprefix', '--junit-prefix',
-        action="store",
-        metavar="str",
-        default=None,
-        help="prepend prefix to classnames in junit-xml output")
-
-
-def pytest_configure(config):
-    xmlpath = config.option.xmlpath
-    # prevent opening xmllog on slave nodes (xdist)
-    if xmlpath and not hasattr(config, 'slaveinput'):
-        config._xml = LogXML(xmlpath, config.option.junitprefix)
-        config.pluginmanager.register(config._xml)
-
-
-def pytest_unconfigure(config):
-    xml = getattr(config, '_xml', None)
-    if xml:
-        del config._xml
-        config.pluginmanager.unregister(xml)
-
-
-def mangle_test_address(address):
-    path, possible_open_bracket, params = address.partition('[')
-    names = path.split("::")
-    try:
-        names.remove('()')
-    except ValueError:
-        pass
-    # convert file path to dotted path
-    names[0] = names[0].replace("/", '.')
-    names[0] = _py_ext_re.sub("", names[0])
-    # put any params back
-    names[-1] += possible_open_bracket + params
-    return names
-
-
-class LogXML(object):
-    def __init__(self, logfile, prefix):
-        logfile = os.path.expanduser(os.path.expandvars(logfile))
-        self.logfile = os.path.normpath(os.path.abspath(logfile))
-        self.prefix = prefix
-        self.stats = dict.fromkeys([
-            'error',
-            'passed',
-            'failure',
-            'skipped',
-        ], 0)
-        self.node_reporters = {}  # nodeid -> _NodeReporter
-        self.node_reporters_ordered = []
-
-    def finalize(self, report):
-        nodeid = getattr(report, 'nodeid', report)
-        # local hack to handle xdist report order
-        slavenode = getattr(report, 'node', None)
-        reporter = self.node_reporters.pop((nodeid, slavenode))
-        if reporter is not None:
-            reporter.finalize()
-
-    def node_reporter(self, report):
-        nodeid = getattr(report, 'nodeid', report)
-        # local hack to handle xdist report order
-        slavenode = getattr(report, 'node', None)
-
-        key = nodeid, slavenode
-
-        if key in self.node_reporters:
-            # TODO: breasks for --dist=each
-            return self.node_reporters[key]
-        reporter = _NodeReporter(nodeid, self)
-        self.node_reporters[key] = reporter
-        self.node_reporters_ordered.append(reporter)
-        return reporter
-
-    def add_stats(self, key):
-        if key in self.stats:
-            self.stats[key] += 1
-
-    def _opentestcase(self, report):
-        reporter = self.node_reporter(report)
-        reporter.record_testreport(report)
-        return reporter
-
-    def pytest_runtest_logreport(self, report):
-        """handle a setup/call/teardown report, generating the appropriate
-        xml tags as necessary.
-
-        note: due to plugins like xdist, this hook may be called in interlaced
-        order with reports from other nodes. for example:
-
-        usual call order:
-            -> setup node1
-            -> call node1
-            -> teardown node1
-            -> setup node2
-            -> call node2
-            -> teardown node2
-
-        possible call order in xdist:
-            -> setup node1
-            -> call node1
-            -> setup node2
-            -> call node2
-            -> teardown node2
-            -> teardown node1
-        """
-        if report.passed:
-            if report.when == "call":  # ignore setup/teardown
-                reporter = self._opentestcase(report)
-                reporter.append_pass(report)
-        elif report.failed:
-            reporter = self._opentestcase(report)
-            if report.when == "call":
-                reporter.append_failure(report)
-            else:
-                reporter.append_error(report)
-        elif report.skipped:
-            reporter = self._opentestcase(report)
-            reporter.append_skipped(report)
-        self.update_testcase_duration(report)
-        if report.when == "teardown":
-            self.finalize(report)
-
-    def update_testcase_duration(self, report):
-        """accumulates total duration for nodeid from given report and updates
-        the Junit.testcase with the new total if already created.
-        """
-        reporter = self.node_reporter(report)
-        reporter.duration += getattr(report, 'duration', 0.0)
-
-    def pytest_collectreport(self, report):
-        if not report.passed:
-            reporter = self._opentestcase(report)
-            if report.failed:
-                reporter.append_collect_error(report)
-            else:
-                reporter.append_collect_skipped(report)
-
-    def pytest_internalerror(self, excrepr):
-        reporter = self.node_reporter('internal')
-        reporter.attrs.update(classname="pytest", name='internal')
-        reporter._add_simple(Junit.error, 'internal error', excrepr)
-
-    def pytest_sessionstart(self):
-        self.suite_start_time = time.time()
-
-    def pytest_sessionfinish(self):
-        dirname = os.path.dirname(os.path.abspath(self.logfile))
-        if not os.path.isdir(dirname):
-            os.makedirs(dirname)
-        logfile = open(self.logfile, 'w', encoding='utf-8')
-        suite_stop_time = time.time()
-        suite_time_delta = suite_stop_time - self.suite_start_time
-
-        numtests = self.stats['passed'] + self.stats['failure']
-
-        logfile.write('<?xml version="1.0" encoding="utf-8"?>')
-        logfile.write(Junit.testsuite(
-            [x.to_xml() for x in self.node_reporters_ordered],
-            name="pytest",
-            errors=self.stats['error'],
-            failures=self.stats['failure'],
-            skips=self.stats['skipped'],
-            tests=numtests,
-            time="%.3f" % suite_time_delta, ).unicode(indent=0))
-        logfile.close()
-
-    def pytest_terminal_summary(self, terminalreporter):
-        terminalreporter.write_sep("-",
-                                   "generated xml file: %s" % (self.logfile))
diff --git a/tools/pytest/_pytest/main.py b/tools/pytest/_pytest/main.py
deleted file mode 100644
index 8654d7a..0000000
--- a/tools/pytest/_pytest/main.py
+++ /dev/null
@@ -1,744 +0,0 @@
-""" core implementation of testing process: init, session, runtest loop. """
-import imp
-import os
-import re
-import sys
-
-import _pytest
-import _pytest._code
-import py
-import pytest
-try:
-    from collections import MutableMapping as MappingMixin
-except ImportError:
-    from UserDict import DictMixin as MappingMixin
-
-from _pytest.runner import collect_one_node
-
-tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
-
-# exitcodes for the command line
-EXIT_OK = 0
-EXIT_TESTSFAILED = 1
-EXIT_INTERRUPTED = 2
-EXIT_INTERNALERROR = 3
-EXIT_USAGEERROR = 4
-EXIT_NOTESTSCOLLECTED = 5
-
-name_re = re.compile("^[a-zA-Z_]\w*$")
-
-def pytest_addoption(parser):
-    parser.addini("norecursedirs", "directory patterns to avoid for recursion",
-        type="args", default=['.*', 'CVS', '_darcs', '{arch}', '*.egg'])
-    parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.",
-        type="args", default=[])
-    #parser.addini("dirpatterns",
-    #    "patterns specifying possible locations of test files",
-    #    type="linelist", default=["**/test_*.txt",
-    #            "**/test_*.py", "**/*_test.py"]
-    #)
-    group = parser.getgroup("general", "running and selection options")
-    group._addoption('-x', '--exitfirst', action="store_true", default=False,
-               dest="exitfirst",
-               help="exit instantly on first error or failed test."),
-    group._addoption('--maxfail', metavar="num",
-               action="store", type=int, dest="maxfail", default=0,
-               help="exit after first num failures or errors.")
-    group._addoption('--strict', action="store_true",
-               help="run pytest in strict mode, warnings become errors.")
-    group._addoption("-c", metavar="file", type=str, dest="inifilename",
-               help="load configuration from `file` instead of trying to locate one of the implicit configuration files.")
-
-    group = parser.getgroup("collect", "collection")
-    group.addoption('--collectonly', '--collect-only', action="store_true",
-        help="only collect tests, don't execute them."),
-    group.addoption('--pyargs', action="store_true",
-        help="try to interpret all arguments as python packages.")
-    group.addoption("--ignore", action="append", metavar="path",
-        help="ignore path during collection (multi-allowed).")
-    # when changing this to --conf-cut-dir, config.py Conftest.setinitial
-    # needs upgrading as well
-    group.addoption('--confcutdir', dest="confcutdir", default=None,
-        metavar="dir",
-        help="only load conftest.py's relative to specified dir.")
-    group.addoption('--noconftest', action="store_true",
-        dest="noconftest", default=False,
-        help="Don't load any conftest.py files.")
-
-    group = parser.getgroup("debugconfig",
-        "test session debugging and configuration")
-    group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
-               help="base temporary directory for this test run.")
-
-
-def pytest_namespace():
-    collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
-    return dict(collect=collect)
-
-def pytest_configure(config):
-    pytest.config = config # compatibiltiy
-    if config.option.exitfirst:
-        config.option.maxfail = 1
-
-def wrap_session(config, doit):
-    """Skeleton command line program"""
-    session = Session(config)
-    session.exitstatus = EXIT_OK
-    initstate = 0
-    try:
-        try:
-            config._do_configure()
-            initstate = 1
-            config.hook.pytest_sessionstart(session=session)
-            initstate = 2
-            session.exitstatus = doit(config, session) or 0
-        except pytest.UsageError:
-            raise
-        except KeyboardInterrupt:
-            excinfo = _pytest._code.ExceptionInfo()
-            config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
-            session.exitstatus = EXIT_INTERRUPTED
-        except:
-            excinfo = _pytest._code.ExceptionInfo()
-            config.notify_exception(excinfo, config.option)
-            session.exitstatus = EXIT_INTERNALERROR
-            if excinfo.errisinstance(SystemExit):
-                sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
-
-    finally:
-        excinfo = None  # Explicitly break reference cycle.
-        session.startdir.chdir()
-        if initstate >= 2:
-            config.hook.pytest_sessionfinish(
-                session=session,
-                exitstatus=session.exitstatus)
-        config._ensure_unconfigure()
-    return session.exitstatus
-
-def pytest_cmdline_main(config):
-    return wrap_session(config, _main)
-
-def _main(config, session):
-    """ default command line protocol for initialization, session,
-    running tests and reporting. """
-    config.hook.pytest_collection(session=session)
-    config.hook.pytest_runtestloop(session=session)
-
-    if session.testsfailed:
-        return EXIT_TESTSFAILED
-    elif session.testscollected == 0:
-        return EXIT_NOTESTSCOLLECTED
-
-def pytest_collection(session):
-    return session.perform_collect()
-
-def pytest_runtestloop(session):
-    if session.config.option.collectonly:
-        return True
-
-    def getnextitem(i):
-        # this is a function to avoid python2
-        # keeping sys.exc_info set when calling into a test
-        # python2 keeps sys.exc_info till the frame is left
-        try:
-            return session.items[i+1]
-        except IndexError:
-            return None
-
-    for i, item in enumerate(session.items):
-        nextitem = getnextitem(i)
-        item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
-        if session.shouldstop:
-            raise session.Interrupted(session.shouldstop)
-    return True
-
-def pytest_ignore_collect(path, config):
-    p = path.dirpath()
-    ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
-    ignore_paths = ignore_paths or []
-    excludeopt = config.getoption("ignore")
-    if excludeopt:
-        ignore_paths.extend([py.path.local(x) for x in excludeopt])
-    return path in ignore_paths
-
-class FSHookProxy:
-    def __init__(self, fspath, pm, remove_mods):
-        self.fspath = fspath
-        self.pm = pm
-        self.remove_mods = remove_mods
-
-    def __getattr__(self, name):
-        x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
-        self.__dict__[name] = x
-        return x
-
-def compatproperty(name):
-    def fget(self):
-        # deprecated - use pytest.name
-        return getattr(pytest, name)
-
-    return property(fget)
-
-class NodeKeywords(MappingMixin):
-    def __init__(self, node):
-        self.node = node
-        self.parent = node.parent
-        self._markers = {node.name: True}
-
-    def __getitem__(self, key):
-        try:
-            return self._markers[key]
-        except KeyError:
-            if self.parent is None:
-                raise
-            return self.parent.keywords[key]
-
-    def __setitem__(self, key, value):
-        self._markers[key] = value
-
-    def __delitem__(self, key):
-        raise ValueError("cannot delete key in keywords dict")
-
-    def __iter__(self):
-        seen = set(self._markers)
-        if self.parent is not None:
-            seen.update(self.parent.keywords)
-        return iter(seen)
-
-    def __len__(self):
-        return len(self.__iter__())
-
-    def keys(self):
-        return list(self)
-
-    def __repr__(self):
-        return "<NodeKeywords for node %s>" % (self.node, )
-
-
-class Node(object):
-    """ base class for Collector and Item the test collection tree.
-    Collector subclasses have children, Items are terminal nodes."""
-
-    def __init__(self, name, parent=None, config=None, session=None):
-        #: a unique name within the scope of the parent node
-        self.name = name
-
-        #: the parent collector node.
-        self.parent = parent
-
-        #: the pytest config object
-        self.config = config or parent.config
-
-        #: the session this node is part of
-        self.session = session or parent.session
-
-        #: filesystem path where this node was collected from (can be None)
-        self.fspath = getattr(parent, 'fspath', None)
-
-        #: keywords/markers collected from all scopes
-        self.keywords = NodeKeywords(self)
-
-        #: allow adding of extra keywords to use for matching
-        self.extra_keyword_matches = set()
-
-        # used for storing artificial fixturedefs for direct parametrization
-        self._name2pseudofixturedef = {}
-
-    @property
-    def ihook(self):
-        """ fspath sensitive hook proxy used to call pytest hooks"""
-        return self.session.gethookproxy(self.fspath)
-
-    Module = compatproperty("Module")
-    Class = compatproperty("Class")
-    Instance = compatproperty("Instance")
-    Function = compatproperty("Function")
-    File = compatproperty("File")
-    Item = compatproperty("Item")
-
-    def _getcustomclass(self, name):
-        cls = getattr(self, name)
-        if cls != getattr(pytest, name):
-            py.log._apiwarn("2.0", "use of node.%s is deprecated, "
-                "use pytest_pycollect_makeitem(...) to create custom "
-                "collection nodes" % name)
-        return cls
-
-    def __repr__(self):
-        return "<%s %r>" %(self.__class__.__name__,
-                           getattr(self, 'name', None))
-
-    def warn(self, code, message):
-        """ generate a warning with the given code and message for this
-        item. """
-        assert isinstance(code, str)
-        fslocation = getattr(self, "location", None)
-        if fslocation is None:
-            fslocation = getattr(self, "fspath", None)
-        else:
-            fslocation = "%s:%s" % fslocation[:2]
-
-        self.ihook.pytest_logwarning.call_historic(kwargs=dict(
-            code=code, message=message,
-            nodeid=self.nodeid, fslocation=fslocation))
-
-    # methods for ordering nodes
-    @property
-    def nodeid(self):
-        """ a ::-separated string denoting its collection tree address. """
-        try:
-            return self._nodeid
-        except AttributeError:
-            self._nodeid = x = self._makeid()
-            return x
-
-    def _makeid(self):
-        return self.parent.nodeid + "::" + self.name
-
-    def __hash__(self):
-        return hash(self.nodeid)
-
-    def setup(self):
-        pass
-
-    def teardown(self):
-        pass
-
-    def _memoizedcall(self, attrname, function):
-        exattrname = "_ex_" + attrname
-        failure = getattr(self, exattrname, None)
-        if failure is not None:
-            py.builtin._reraise(failure[0], failure[1], failure[2])
-        if hasattr(self, attrname):
-            return getattr(self, attrname)
-        try:
-            res = function()
-        except py.builtin._sysex:
-            raise
-        except:
-            failure = sys.exc_info()
-            setattr(self, exattrname, failure)
-            raise
-        setattr(self, attrname, res)
-        return res
-
-    def listchain(self):
-        """ return list of all parent collectors up to self,
-            starting from root of collection tree. """
-        chain = []
-        item = self
-        while item is not None:
-            chain.append(item)
-            item = item.parent
-        chain.reverse()
-        return chain
-
-    def add_marker(self, marker):
-        """ dynamically add a marker object to the node.
-
-        ``marker`` can be a string or pytest.mark.* instance.
-        """
-        from _pytest.mark import MarkDecorator
-        if isinstance(marker, py.builtin._basestring):
-            marker = MarkDecorator(marker)
-        elif not isinstance(marker, MarkDecorator):
-            raise ValueError("is not a string or pytest.mark.* Marker")
-        self.keywords[marker.name] = marker
-
-    def get_marker(self, name):
-        """ get a marker object from this node or None if
-        the node doesn't have a marker with that name. """
-        val = self.keywords.get(name, None)
-        if val is not None:
-            from _pytest.mark import MarkInfo, MarkDecorator
-            if isinstance(val, (MarkDecorator, MarkInfo)):
-                return val
-
-    def listextrakeywords(self):
-        """ Return a set of all extra keywords in self and any parents."""
-        extra_keywords = set()
-        item = self
-        for item in self.listchain():
-            extra_keywords.update(item.extra_keyword_matches)
-        return extra_keywords
-
-    def listnames(self):
-        return [x.name for x in self.listchain()]
-
-    def addfinalizer(self, fin):
-        """ register a function to be called when this node is finalized.
-
-        This method can only be called when this node is active
-        in a setup chain, for example during self.setup().
-        """
-        self.session._setupstate.addfinalizer(fin, self)
-
-    def getparent(self, cls):
-        """ get the next parent node (including ourself)
-        which is an instance of the given class"""
-        current = self
-        while current and not isinstance(current, cls):
-            current = current.parent
-        return current
-
-    def _prunetraceback(self, excinfo):
-        pass
-
-    def _repr_failure_py(self, excinfo, style=None):
-        fm = self.session._fixturemanager
-        if excinfo.errisinstance(fm.FixtureLookupError):
-            return excinfo.value.formatrepr()
-        tbfilter = True
-        if self.config.option.fulltrace:
-            style="long"
-        else:
-            self._prunetraceback(excinfo)
-            tbfilter = False  # prunetraceback already does it
-            if style == "auto":
-                style = "long"
-        # XXX should excinfo.getrepr record all data and toterminal() process it?
-        if style is None:
-            if self.config.option.tbstyle == "short":
-                style = "short"
-            else:
-                style = "long"
-
-        return excinfo.getrepr(funcargs=True,
-                               showlocals=self.config.option.showlocals,
-                               style=style, tbfilter=tbfilter)
-
-    repr_failure = _repr_failure_py
-
-class Collector(Node):
-    """ Collector instances create children through collect()
-        and thus iteratively build a tree.
-    """
-
-    class CollectError(Exception):
-        """ an error during collection, contains a custom message. """
-
-    def collect(self):
-        """ returns a list of children (items and collectors)
-            for this collection node.
-        """
-        raise NotImplementedError("abstract")
-
-    def repr_failure(self, excinfo):
-        """ represent a collection failure. """
-        if excinfo.errisinstance(self.CollectError):
-            exc = excinfo.value
-            return str(exc.args[0])
-        return self._repr_failure_py(excinfo, style="short")
-
-    def _memocollect(self):
-        """ internal helper method to cache results of calling collect(). """
-        return self._memoizedcall('_collected', lambda: list(self.collect()))
-
-    def _prunetraceback(self, excinfo):
-        if hasattr(self, 'fspath'):
-            traceback = excinfo.traceback
-            ntraceback = traceback.cut(path=self.fspath)
-            if ntraceback == traceback:
-                ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
-            excinfo.traceback = ntraceback.filter()
-
-class FSCollector(Collector):
-    def __init__(self, fspath, parent=None, config=None, session=None):
-        fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
-        name = fspath.basename
-        if parent is not None:
-            rel = fspath.relto(parent.fspath)
-            if rel:
-                name = rel
-            name = name.replace(os.sep, "/")
-        super(FSCollector, self).__init__(name, parent, config, session)
-        self.fspath = fspath
-
-    def _makeid(self):
-        relpath = self.fspath.relto(self.config.rootdir)
-        if os.sep != "/":
-            relpath = relpath.replace(os.sep, "/")
-        return relpath
-
-class File(FSCollector):
-    """ base class for collecting tests from a file. """
-
-class Item(Node):
-    """ a basic test invocation item. Note that for a single function
-    there might be multiple test invocation items.
-    """
-    nextitem = None
-
-    def __init__(self, name, parent=None, config=None, session=None):
-        super(Item, self).__init__(name, parent, config, session)
-        self._report_sections = []
-
-    def add_report_section(self, when, key, content):
-        if content:
-            self._report_sections.append((when, key, content))
-
-    def reportinfo(self):
-        return self.fspath, None, ""
-
-    @property
-    def location(self):
-        try:
-            return self._location
-        except AttributeError:
-            location = self.reportinfo()
-            # bestrelpath is a quite slow function
-            cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
-            try:
-                fspath = cache[location[0]]
-            except KeyError:
-                fspath = self.session.fspath.bestrelpath(location[0])
-                cache[location[0]] = fspath
-            location = (fspath, location[1], str(location[2]))
-            self._location = location
-            return location
-
-class NoMatch(Exception):
-    """ raised if matching cannot locate a matching names. """
-
-class Interrupted(KeyboardInterrupt):
-    """ signals an interrupted test run. """
-    __module__ = 'builtins' # for py3
-
-class Session(FSCollector):
-    Interrupted = Interrupted
-
-    def __init__(self, config):
-        FSCollector.__init__(self, config.rootdir, parent=None,
-                             config=config, session=self)
-        self._fs2hookproxy = {}
-        self.testsfailed = 0
-        self.testscollected = 0
-        self.shouldstop = False
-        self.trace = config.trace.root.get("collection")
-        self._norecursepatterns = config.getini("norecursedirs")
-        self.startdir = py.path.local()
-        self.config.pluginmanager.register(self, name="session")
-
-    def _makeid(self):
-        return ""
-
-    @pytest.hookimpl(tryfirst=True)
-    def pytest_collectstart(self):
-        if self.shouldstop:
-            raise self.Interrupted(self.shouldstop)
-
-    @pytest.hookimpl(tryfirst=True)
-    def pytest_runtest_logreport(self, report):
-        if report.failed and not hasattr(report, 'wasxfail'):
-            self.testsfailed += 1
-            maxfail = self.config.getvalue("maxfail")
-            if maxfail and self.testsfailed >= maxfail:
-                self.shouldstop = "stopping after %d failures" % (
-                    self.testsfailed)
-    pytest_collectreport = pytest_runtest_logreport
-
-    def isinitpath(self, path):
-        return path in self._initialpaths
-
-    def gethookproxy(self, fspath):
-        try:
-            return self._fs2hookproxy[fspath]
-        except KeyError:
-            # check if we have the common case of running
-            # hooks with all conftest.py filesall conftest.py
-            pm = self.config.pluginmanager
-            my_conftestmodules = pm._getconftestmodules(fspath)
-            remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
-            if remove_mods:
-                # one or more conftests are not in use at this fspath
-                proxy = FSHookProxy(fspath, pm, remove_mods)
-            else:
-                # all plugis are active for this fspath
-                proxy = self.config.hook
-
-            self._fs2hookproxy[fspath] = proxy
-            return proxy
-
-    def perform_collect(self, args=None, genitems=True):
-        hook = self.config.hook
-        try:
-            items = self._perform_collect(args, genitems)
-            hook.pytest_collection_modifyitems(session=self,
-                config=self.config, items=items)
-        finally:
-            hook.pytest_collection_finish(session=self)
-        self.testscollected = len(items)
-        return items
-
-    def _perform_collect(self, args, genitems):
-        if args is None:
-            args = self.config.args
-        self.trace("perform_collect", self, args)
-        self.trace.root.indent += 1
-        self._notfound = []
-        self._initialpaths = set()
-        self._initialparts = []
-        self.items = items = []
-        for arg in args:
-            parts = self._parsearg(arg)
-            self._initialparts.append(parts)
-            self._initialpaths.add(parts[0])
-        rep = collect_one_node(self)
-        self.ihook.pytest_collectreport(report=rep)
-        self.trace.root.indent -= 1
-        if self._notfound:
-            errors = []
-            for arg, exc in self._notfound:
-                line = "(no name %r in any of %r)" % (arg, exc.args[0])
-                errors.append("not found: %s\n%s" % (arg, line))
-                #XXX: test this
-            raise pytest.UsageError(*errors)
-        if not genitems:
-            return rep.result
-        else:
-            if rep.passed:
-                for node in rep.result:
-                    self.items.extend(self.genitems(node))
-            return items
-
-    def collect(self):
-        for parts in self._initialparts:
-            arg = "::".join(map(str, parts))
-            self.trace("processing argument", arg)
-            self.trace.root.indent += 1
-            try:
-                for x in self._collect(arg):
-                    yield x
-            except NoMatch:
-                # we are inside a make_report hook so
-                # we cannot directly pass through the exception
-                self._notfound.append((arg, sys.exc_info()[1]))
-
-            self.trace.root.indent -= 1
-
-    def _collect(self, arg):
-        names = self._parsearg(arg)
-        path = names.pop(0)
-        if path.check(dir=1):
-            assert not names, "invalid arg %r" %(arg,)
-            for path in path.visit(fil=lambda x: x.check(file=1),
-                                   rec=self._recurse, bf=True, sort=True):
-                for x in self._collectfile(path):
-                    yield x
-        else:
-            assert path.check(file=1)
-            for x in self.matchnodes(self._collectfile(path), names):
-                yield x
-
-    def _collectfile(self, path):
-        ihook = self.gethookproxy(path)
-        if not self.isinitpath(path):
-            if ihook.pytest_ignore_collect(path=path, config=self.config):
-                return ()
-        return ihook.pytest_collect_file(path=path, parent=self)
-
-    def _recurse(self, path):
-        ihook = self.gethookproxy(path.dirpath())
-        if ihook.pytest_ignore_collect(path=path, config=self.config):
-            return
-        for pat in self._norecursepatterns:
-            if path.check(fnmatch=pat):
-                return False
-        ihook = self.gethookproxy(path)
-        ihook.pytest_collect_directory(path=path, parent=self)
-        return True
-
-    def _tryconvertpyarg(self, x):
-        mod = None
-        path = [os.path.abspath('.')] + sys.path
-        for name in x.split('.'):
-            # ignore anything that's not a proper name here
-            # else something like --pyargs will mess up '.'
-            # since imp.find_module will actually sometimes work for it
-            # but it's supposed to be considered a filesystem path
-            # not a package
-            if name_re.match(name) is None:
-                return x
-            try:
-                fd, mod, type_ = imp.find_module(name, path)
-            except ImportError:
-                return x
-            else:
-                if fd is not None:
-                    fd.close()
-
-            if type_[2] != imp.PKG_DIRECTORY:
-                path = [os.path.dirname(mod)]
-            else:
-                path = [mod]
-        return mod
-
-    def _parsearg(self, arg):
-        """ return (fspath, names) tuple after checking the file exists. """
-        arg = str(arg)
-        if self.config.option.pyargs:
-            arg = self._tryconvertpyarg(arg)
-        parts = str(arg).split("::")
-        relpath = parts[0].replace("/", os.sep)
-        path = self.config.invocation_dir.join(relpath, abs=True)
-        if not path.check():
-            if self.config.option.pyargs:
-                msg = "file or package not found: "
-            else:
-                msg = "file not found: "
-            raise pytest.UsageError(msg + arg)
-        parts[0] = path
-        return parts
-
-    def matchnodes(self, matching, names):
-        self.trace("matchnodes", matching, names)
-        self.trace.root.indent += 1
-        nodes = self._matchnodes(matching, names)
-        num = len(nodes)
-        self.trace("matchnodes finished -> ", num, "nodes")
-        self.trace.root.indent -= 1
-        if num == 0:
-            raise NoMatch(matching, names[:1])
-        return nodes
-
-    def _matchnodes(self, matching, names):
-        if not matching or not names:
-            return matching
-        name = names[0]
-        assert name
-        nextnames = names[1:]
-        resultnodes = []
-        for node in matching:
-            if isinstance(node, pytest.Item):
-                if not names:
-                    resultnodes.append(node)
-                continue
-            assert isinstance(node, pytest.Collector)
-            rep = collect_one_node(node)
-            if rep.passed:
-                has_matched = False
-                for x in rep.result:
-                    # TODO: remove parametrized workaround once collection structure contains parametrization
-                    if x.name == name or x.name.split("[")[0] == name:
-                        resultnodes.extend(self.matchnodes([x], nextnames))
-                        has_matched = True
-                # XXX accept IDs that don't have "()" for class instances
-                if not has_matched and len(rep.result) == 1 and x.name == "()":
-                    nextnames.insert(0, name)
-                    resultnodes.extend(self.matchnodes([x], nextnames))
-            node.ihook.pytest_collectreport(report=rep)
-        return resultnodes
-
-    def genitems(self, node):
-        self.trace("genitems", node)
-        if isinstance(node, pytest.Item):
-            node.ihook.pytest_itemcollected(item=node)
-            yield node
-        else:
-            assert isinstance(node, pytest.Collector)
-            rep = collect_one_node(node)
-            if rep.passed:
-                for subnode in rep.result:
-                    for x in self.genitems(subnode):
-                        yield x
-            node.ihook.pytest_collectreport(report=rep)
diff --git a/tools/pytest/_pytest/mark.py b/tools/pytest/_pytest/mark.py
deleted file mode 100644
index 1a76354..0000000
--- a/tools/pytest/_pytest/mark.py
+++ /dev/null
@@ -1,311 +0,0 @@
-""" generic mechanism for marking and selecting python functions. """
-import inspect
-
-
-class MarkerError(Exception):
-
-    """Error in use of a pytest marker/attribute."""
-
-
-def pytest_namespace():
-    return {'mark': MarkGenerator()}
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("general")
-    group._addoption(
-        '-k',
-        action="store", dest="keyword", default='', metavar="EXPRESSION",
-        help="only run tests which match the given substring expression. "
-             "An expression is a python evaluatable expression "
-             "where all names are substring-matched against test names "
-             "and their parent classes. Example: -k 'test_method or test "
-             "other' matches all test functions and classes whose name "
-             "contains 'test_method' or 'test_other'. "
-             "Additionally keywords are matched to classes and functions "
-             "containing extra names in their 'extra_keyword_matches' set, "
-             "as well as functions which have names assigned directly to them."
-    )
-
-    group._addoption(
-        "-m",
-        action="store", dest="markexpr", default="", metavar="MARKEXPR",
-        help="only run tests matching given mark expression.  "
-             "example: -m 'mark1 and not mark2'."
-    )
-
-    group.addoption(
-        "--markers", action="store_true",
-        help="show markers (builtin, plugin and per-project ones)."
-    )
-
-    parser.addini("markers", "markers for test functions", 'linelist')
-
-
-def pytest_cmdline_main(config):
-    import _pytest.config
-    if config.option.markers:
-        config._do_configure()
-        tw = _pytest.config.create_terminal_writer(config)
-        for line in config.getini("markers"):
-            name, rest = line.split(":", 1)
-            tw.write("@pytest.mark.%s:" % name, bold=True)
-            tw.line(rest)
-            tw.line()
-        config._ensure_unconfigure()
-        return 0
-pytest_cmdline_main.tryfirst = True
-
-
-def pytest_collection_modifyitems(items, config):
-    keywordexpr = config.option.keyword
-    matchexpr = config.option.markexpr
-    if not keywordexpr and not matchexpr:
-        return
-    # pytest used to allow "-" for negating
-    # but today we just allow "-" at the beginning, use "not" instead
-    # we probably remove "-" alltogether soon
-    if keywordexpr.startswith("-"):
-        keywordexpr = "not " + keywordexpr[1:]
-    selectuntil = False
-    if keywordexpr[-1:] == ":":
-        selectuntil = True
-        keywordexpr = keywordexpr[:-1]
-
-    remaining = []
-    deselected = []
-    for colitem in items:
-        if keywordexpr and not matchkeyword(colitem, keywordexpr):
-            deselected.append(colitem)
-        else:
-            if selectuntil:
-                keywordexpr = None
-            if matchexpr:
-                if not matchmark(colitem, matchexpr):
-                    deselected.append(colitem)
-                    continue
-            remaining.append(colitem)
-
-    if deselected:
-        config.hook.pytest_deselected(items=deselected)
-        items[:] = remaining
-
-
-class MarkMapping:
-    """Provides a local mapping for markers where item access
-    resolves to True if the marker is present. """
-    def __init__(self, keywords):
-        mymarks = set()
-        for key, value in keywords.items():
-            if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
-                mymarks.add(key)
-        self._mymarks = mymarks
-
-    def __getitem__(self, name):
-        return name in self._mymarks
-
-
-class KeywordMapping:
-    """Provides a local mapping for keywords.
-    Given a list of names, map any substring of one of these names to True.
-    """
-    def __init__(self, names):
-        self._names = names
-
-    def __getitem__(self, subname):
-        for name in self._names:
-            if subname in name:
-                return True
-        return False
-
-
-def matchmark(colitem, markexpr):
-    """Tries to match on any marker names, attached to the given colitem."""
-    return eval(markexpr, {}, MarkMapping(colitem.keywords))
-
-
-def matchkeyword(colitem, keywordexpr):
-    """Tries to match given keyword expression to given collector item.
-
-    Will match on the name of colitem, including the names of its parents.
-    Only matches names of items which are either a :class:`Class` or a
-    :class:`Function`.
-    Additionally, matches on names in the 'extra_keyword_matches' set of
-    any item, as well as names directly assigned to test functions.
-    """
-    mapped_names = set()
-
-    # Add the names of the current item and any parent items
-    import pytest
-    for item in colitem.listchain():
-        if not isinstance(item, pytest.Instance):
-            mapped_names.add(item.name)
-
-    # Add the names added as extra keywords to current or parent items
-    for name in colitem.listextrakeywords():
-        mapped_names.add(name)
-
-    # Add the names attached to the current function through direct assignment
-    if hasattr(colitem, 'function'):
-        for name in colitem.function.__dict__:
-            mapped_names.add(name)
-
-    mapping = KeywordMapping(mapped_names)
-    if " " not in keywordexpr:
-        # special case to allow for simple "-k pass" and "-k 1.3"
-        return mapping[keywordexpr]
-    elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
-        return not mapping[keywordexpr[4:]]
-    return eval(keywordexpr, {}, mapping)
-
-
-def pytest_configure(config):
-    import pytest
-    if config.option.strict:
-        pytest.mark._config = config
-
-
-class MarkGenerator:
-    """ Factory for :class:`MarkDecorator` objects - exposed as
-    a ``pytest.mark`` singleton instance.  Example::
-
-         import pytest
-         @pytest.mark.slowtest
-         def test_function():
-            pass
-
-    will set a 'slowtest' :class:`MarkInfo` object
-    on the ``test_function`` object. """
-
-    def __getattr__(self, name):
-        if name[0] == "_":
-            raise AttributeError("Marker name must NOT start with underscore")
-        if hasattr(self, '_config'):
-            self._check(name)
-        return MarkDecorator(name)
-
-    def _check(self, name):
-        try:
-            if name in self._markers:
-                return
-        except AttributeError:
-            pass
-        self._markers = l = set()
-        for line in self._config.getini("markers"):
-            beginning = line.split(":", 1)
-            x = beginning[0].split("(", 1)[0]
-            l.add(x)
-        if name not in self._markers:
-            raise AttributeError("%r not a registered marker" % (name,))
-
-def istestfunc(func):
-    return hasattr(func, "__call__") and \
-        getattr(func, "__name__", "<lambda>") != "<lambda>"
-
-class MarkDecorator:
-    """ A decorator for test functions and test classes.  When applied
-    it will create :class:`MarkInfo` objects which may be
-    :ref:`retrieved by hooks as item keywords <excontrolskip>`.
-    MarkDecorator instances are often created like this::
-
-        mark1 = pytest.mark.NAME              # simple MarkDecorator
-        mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator
-
-    and can then be applied as decorators to test functions::
-
-        @mark2
-        def test_function():
-            pass
-
-    When a MarkDecorator instance is called it does the following:
-      1. If called with a single class as its only positional argument and no
-         additional keyword arguments, it attaches itself to the class so it
-         gets applied automatically to all test cases found in that class.
-      2. If called with a single function as its only positional argument and
-         no additional keyword arguments, it attaches a MarkInfo object to the
-         function, containing all the arguments already stored internally in
-         the MarkDecorator.
-      3. When called in any other case, it performs a 'fake construction' call,
-         i.e. it returns a new MarkDecorator instance with the original
-         MarkDecorator's content updated with the arguments passed to this
-         call.
-
-    Note: The rules above prevent MarkDecorator objects from storing only a
-    single function or class reference as their positional argument with no
-    additional keyword or positional arguments.
-
-    """
-    def __init__(self, name, args=None, kwargs=None):
-        self.name = name
-        self.args = args or ()
-        self.kwargs = kwargs or {}
-
-    @property
-    def markname(self):
-        return self.name # for backward-compat (2.4.1 had this attr)
-
-    def __repr__(self):
-        d = self.__dict__.copy()
-        name = d.pop('name')
-        return "<MarkDecorator %r %r>" % (name, d)
-
-    def __call__(self, *args, **kwargs):
-        """ if passed a single callable argument: decorate it with mark info.
-            otherwise add *args/**kwargs in-place to mark information. """
-        if args and not kwargs:
-            func = args[0]
-            is_class = inspect.isclass(func)
-            if len(args) == 1 and (istestfunc(func) or is_class):
-                if is_class:
-                    if hasattr(func, 'pytestmark'):
-                        mark_list = func.pytestmark
-                        if not isinstance(mark_list, list):
-                            mark_list = [mark_list]
-                        # always work on a copy to avoid updating pytestmark
-                        # from a superclass by accident
-                        mark_list = mark_list + [self]
-                        func.pytestmark = mark_list
-                    else:
-                        func.pytestmark = [self]
-                else:
-                    holder = getattr(func, self.name, None)
-                    if holder is None:
-                        holder = MarkInfo(
-                            self.name, self.args, self.kwargs
-                        )
-                        setattr(func, self.name, holder)
-                    else:
-                        holder.add(self.args, self.kwargs)
-                return func
-        kw = self.kwargs.copy()
-        kw.update(kwargs)
-        args = self.args + args
-        return self.__class__(self.name, args=args, kwargs=kw)
-
-
-class MarkInfo:
-    """ Marking object created by :class:`MarkDecorator` instances. """
-    def __init__(self, name, args, kwargs):
-        #: name of attribute
-        self.name = name
-        #: positional argument list, empty if none specified
-        self.args = args
-        #: keyword argument dictionary, empty if nothing specified
-        self.kwargs = kwargs.copy()
-        self._arglist = [(args, kwargs.copy())]
-
-    def __repr__(self):
-        return "<MarkInfo %r args=%r kwargs=%r>" % (
-            self.name, self.args, self.kwargs
-        )
-
-    def add(self, args, kwargs):
-        """ add a MarkInfo with the given args and kwargs. """
-        self._arglist.append((args, kwargs))
-        self.args += args
-        self.kwargs.update(kwargs)
-
-    def __iter__(self):
-        """ yield MarkInfo objects each relating to a marking-call. """
-        for args, kwargs in self._arglist:
-            yield MarkInfo(self.name, args, kwargs)
diff --git a/tools/pytest/_pytest/monkeypatch.py b/tools/pytest/_pytest/monkeypatch.py
deleted file mode 100644
index d4c169d..0000000
--- a/tools/pytest/_pytest/monkeypatch.py
+++ /dev/null
@@ -1,254 +0,0 @@
-""" monkeypatching and mocking functionality.  """
-
-import os, sys
-import re
-
-from py.builtin import _basestring
-
-RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
-
-
-def pytest_funcarg__monkeypatch(request):
-    """The returned ``monkeypatch`` funcarg provides these
-    helper methods to modify objects, dictionaries or os.environ::
-
-        monkeypatch.setattr(obj, name, value, raising=True)
-        monkeypatch.delattr(obj, name, raising=True)
-        monkeypatch.setitem(mapping, name, value)
-        monkeypatch.delitem(obj, name, raising=True)
-        monkeypatch.setenv(name, value, prepend=False)
-        monkeypatch.delenv(name, value, raising=True)
-        monkeypatch.syspath_prepend(path)
-        monkeypatch.chdir(path)
-
-    All modifications will be undone after the requesting
-    test function has finished. The ``raising``
-    parameter determines if a KeyError or AttributeError
-    will be raised if the set/deletion operation has no target.
-    """
-    mpatch = monkeypatch()
-    request.addfinalizer(mpatch.undo)
-    return mpatch
-
-
-def resolve(name):
-    # simplified from zope.dottedname
-    parts = name.split('.')
-
-    used = parts.pop(0)
-    found = __import__(used)
-    for part in parts:
-        used += '.' + part
-        try:
-            found = getattr(found, part)
-        except AttributeError:
-            pass
-        else:
-            continue
-        # we use explicit un-nesting of the handling block in order
-        # to avoid nested exceptions on python 3
-        try:
-            __import__(used)
-        except ImportError as ex:
-            # str is used for py2 vs py3
-            expected = str(ex).split()[-1]
-            if expected == used:
-                raise
-            else:
-                raise ImportError(
-                    'import error in %s: %s' % (used, ex)
-                )
-        found = annotated_getattr(found, part, used)
-    return found
-
-
-def annotated_getattr(obj, name, ann):
-    try:
-        obj = getattr(obj, name)
-    except AttributeError:
-        raise AttributeError(
-                '%r object at %s has no attribute %r' % (
-                    type(obj).__name__, ann, name
-                )
-        )
-    return obj
-
-
-def derive_importpath(import_path, raising):
-    if not isinstance(import_path, _basestring) or "." not in import_path:
-        raise TypeError("must be absolute import path string, not %r" %
-                        (import_path,))
-    module, attr = import_path.rsplit('.', 1)
-    target = resolve(module)
-    if raising:
-        annotated_getattr(target, attr, ann=module)
-    return attr, target
-
-
-class Notset:
-    def __repr__(self):
-        return "<notset>"
-
-
-notset = Notset()
-
-
-class monkeypatch:
-    """ Object keeping a record of setattr/item/env/syspath changes. """
-
-    def __init__(self):
-        self._setattr = []
-        self._setitem = []
-        self._cwd = None
-        self._savesyspath = None
-
-    def setattr(self, target, name, value=notset, raising=True):
-        """ Set attribute value on target, memorizing the old value.
-        By default raise AttributeError if the attribute did not exist.
-
-        For convenience you can specify a string as ``target`` which
-        will be interpreted as a dotted import path, with the last part
-        being the attribute name.  Example:
-        ``monkeypatch.setattr("os.getcwd", lambda x: "/")``
-        would set the ``getcwd`` function of the ``os`` module.
-
-        The ``raising`` value determines if the setattr should fail
-        if the attribute is not already present (defaults to True
-        which means it will raise).
-        """
-        __tracebackhide__ = True
-        import inspect
-
-        if value is notset:
-            if not isinstance(target, _basestring):
-                raise TypeError("use setattr(target, name, value) or "
-                                "setattr(target, value) with target being a dotted "
-                                "import string")
-            value = name
-            name, target = derive_importpath(target, raising)
-
-        oldval = getattr(target, name, notset)
-        if raising and oldval is notset:
-            raise AttributeError("%r has no attribute %r" % (target, name))
-
-        # avoid class descriptors like staticmethod/classmethod
-        if inspect.isclass(target):
-            oldval = target.__dict__.get(name, notset)
-        self._setattr.append((target, name, oldval))
-        setattr(target, name, value)
-
-    def delattr(self, target, name=notset, raising=True):
-        """ Delete attribute ``name`` from ``target``, by default raise
-        AttributeError it the attribute did not previously exist.
-
-        If no ``name`` is specified and ``target`` is a string
-        it will be interpreted as a dotted import path with the
-        last part being the attribute name.
-
-        If ``raising`` is set to False, no exception will be raised if the
-        attribute is missing.
-        """
-        __tracebackhide__ = True
-        if name is notset:
-            if not isinstance(target, _basestring):
-                raise TypeError("use delattr(target, name) or "
-                                "delattr(target) with target being a dotted "
-                                "import string")
-            name, target = derive_importpath(target, raising)
-
-        if not hasattr(target, name):
-            if raising:
-                raise AttributeError(name)
-        else:
-            self._setattr.append((target, name, getattr(target, name, notset)))
-            delattr(target, name)
-
-    def setitem(self, dic, name, value):
-        """ Set dictionary entry ``name`` to value. """
-        self._setitem.append((dic, name, dic.get(name, notset)))
-        dic[name] = value
-
-    def delitem(self, dic, name, raising=True):
-        """ Delete ``name`` from dict. Raise KeyError if it doesn't exist.
-
-        If ``raising`` is set to False, no exception will be raised if the
-        key is missing.
-        """
-        if name not in dic:
-            if raising:
-                raise KeyError(name)
-        else:
-            self._setitem.append((dic, name, dic.get(name, notset)))
-            del dic[name]
-
-    def setenv(self, name, value, prepend=None):
-        """ Set environment variable ``name`` to ``value``.  If ``prepend``
-        is a character, read the current environment variable value
-        and prepend the ``value`` adjoined with the ``prepend`` character."""
-        value = str(value)
-        if prepend and name in os.environ:
-            value = value + prepend + os.environ[name]
-        self.setitem(os.environ, name, value)
-
-    def delenv(self, name, raising=True):
-        """ Delete ``name`` from the environment. Raise KeyError it does not
-        exist.
-
-        If ``raising`` is set to False, no exception will be raised if the
-        environment variable is missing.
-        """
-        self.delitem(os.environ, name, raising=raising)
-
-    def syspath_prepend(self, path):
-        """ Prepend ``path`` to ``sys.path`` list of import locations. """
-        if self._savesyspath is None:
-            self._savesyspath = sys.path[:]
-        sys.path.insert(0, str(path))
-
-    def chdir(self, path):
-        """ Change the current working directory to the specified path.
-        Path can be a string or a py.path.local object.
-        """
-        if self._cwd is None:
-            self._cwd = os.getcwd()
-        if hasattr(path, "chdir"):
-            path.chdir()
-        else:
-            os.chdir(path)
-
-    def undo(self):
-        """ Undo previous changes.  This call consumes the
-        undo stack. Calling it a second time has no effect unless
-        you do more monkeypatching after the undo call.
-        
-        There is generally no need to call `undo()`, since it is
-        called automatically during tear-down.
-        
-        Note that the same `monkeypatch` fixture is used across a
-        single test function invocation. If `monkeypatch` is used both by
-        the test function itself and one of the test fixtures,
-        calling `undo()` will undo all of the changes made in
-        both functions.
-        """
-        for obj, name, value in reversed(self._setattr):
-            if value is not notset:
-                setattr(obj, name, value)
-            else:
-                delattr(obj, name)
-        self._setattr[:] = []
-        for dictionary, name, value in reversed(self._setitem):
-            if value is notset:
-                try:
-                    del dictionary[name]
-                except KeyError:
-                    pass  # was already deleted, so we have the desired state
-            else:
-                dictionary[name] = value
-        self._setitem[:] = []
-        if self._savesyspath is not None:
-            sys.path[:] = self._savesyspath
-            self._savesyspath = None
-
-        if self._cwd is not None:
-            os.chdir(self._cwd)
-            self._cwd = None
diff --git a/tools/pytest/_pytest/nose.py b/tools/pytest/_pytest/nose.py
deleted file mode 100644
index 0387468..0000000
--- a/tools/pytest/_pytest/nose.py
+++ /dev/null
@@ -1,71 +0,0 @@
-""" run test suites written for nose. """
-
-import sys
-
-import py
-import pytest
-from _pytest import unittest
-
-
-def get_skip_exceptions():
-    skip_classes = set()
-    for module_name in ('unittest', 'unittest2', 'nose'):
-        mod = sys.modules.get(module_name)
-        if hasattr(mod, 'SkipTest'):
-            skip_classes.add(mod.SkipTest)
-    return tuple(skip_classes)
-
-
-def pytest_runtest_makereport(item, call):
-    if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
-        # let's substitute the excinfo with a pytest.skip one
-        call2 = call.__class__(lambda:
-                    pytest.skip(str(call.excinfo.value)), call.when)
-        call.excinfo = call2.excinfo
-
-
-@pytest.hookimpl(trylast=True)
-def pytest_runtest_setup(item):
-    if is_potential_nosetest(item):
-        if isinstance(item.parent, pytest.Generator):
-            gen = item.parent
-            if not hasattr(gen, '_nosegensetup'):
-                call_optional(gen.obj, 'setup')
-                if isinstance(gen.parent, pytest.Instance):
-                    call_optional(gen.parent.obj, 'setup')
-                gen._nosegensetup = True
-        if not call_optional(item.obj, 'setup'):
-            # call module level setup if there is no object level one
-            call_optional(item.parent.obj, 'setup')
-        #XXX this implies we only call teardown when setup worked
-        item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
-
-def teardown_nose(item):
-    if is_potential_nosetest(item):
-        if not call_optional(item.obj, 'teardown'):
-            call_optional(item.parent.obj, 'teardown')
-        #if hasattr(item.parent, '_nosegensetup'):
-        #    #call_optional(item._nosegensetup, 'teardown')
-        #    del item.parent._nosegensetup
-
-
-def pytest_make_collect_report(collector):
-    if isinstance(collector, pytest.Generator):
-        call_optional(collector.obj, 'setup')
-
-
-def is_potential_nosetest(item):
-    # extra check needed since we do not do nose style setup/teardown
-    # on direct unittest style classes
-    return isinstance(item, pytest.Function) and \
-        not isinstance(item, unittest.TestCaseFunction)
-
-
-def call_optional(obj, name):
-    method = getattr(obj, name, None)
-    isfixture = hasattr(method, "_pytestfixturefunction")
-    if method is not None and not isfixture and py.builtin.callable(method):
-        # If there's any problems allow the exception to raise rather than
-        # silently ignoring them
-        method()
-        return True
diff --git a/tools/pytest/_pytest/pastebin.py b/tools/pytest/_pytest/pastebin.py
deleted file mode 100644
index 4ec62d0..0000000
--- a/tools/pytest/_pytest/pastebin.py
+++ /dev/null
@@ -1,92 +0,0 @@
-""" submit failure or test session information to a pastebin service. """
-import pytest
-import sys
-import tempfile
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("terminal reporting")
-    group._addoption('--pastebin', metavar="mode",
-        action='store', dest="pastebin", default=None,
-        choices=['failed', 'all'],
-        help="send failed|all info to bpaste.net pastebin service.")
-
-@pytest.hookimpl(trylast=True)
-def pytest_configure(config):
-    import py
-    if config.option.pastebin == "all":
-        tr = config.pluginmanager.getplugin('terminalreporter')
-        # if no terminal reporter plugin is present, nothing we can do here;
-        # this can happen when this function executes in a slave node
-        # when using pytest-xdist, for example
-        if tr is not None:
-            # pastebin file will be utf-8 encoded binary file
-            config._pastebinfile = tempfile.TemporaryFile('w+b')
-            oldwrite = tr._tw.write
-            def tee_write(s, **kwargs):
-                oldwrite(s, **kwargs)
-                if py.builtin._istext(s):
-                    s = s.encode('utf-8')
-                config._pastebinfile.write(s)
-            tr._tw.write = tee_write
-
-def pytest_unconfigure(config):
-    if hasattr(config, '_pastebinfile'):
-        # get terminal contents and delete file
-        config._pastebinfile.seek(0)
-        sessionlog = config._pastebinfile.read()
-        config._pastebinfile.close()
-        del config._pastebinfile
-        # undo our patching in the terminal reporter
-        tr = config.pluginmanager.getplugin('terminalreporter')
-        del tr._tw.__dict__['write']
-        # write summary
-        tr.write_sep("=", "Sending information to Paste Service")
-        pastebinurl = create_new_paste(sessionlog)
-        tr.write_line("pastebin session-log: %s\n" % pastebinurl)
-
-def create_new_paste(contents):
-    """
-    Creates a new paste using bpaste.net service.
-
-    :contents: paste contents as utf-8 encoded bytes
-    :returns: url to the pasted contents
-    """
-    import re
-    if sys.version_info < (3, 0):
-        from urllib import urlopen, urlencode
-    else:
-        from urllib.request import urlopen
-        from urllib.parse import urlencode
-
-    params = {
-        'code': contents,
-        'lexer': 'python3' if sys.version_info[0] == 3 else 'python',
-        'expiry': '1week',
-    }
-    url = 'https://bpaste.net'
-    response = urlopen(url, data=urlencode(params).encode('ascii')).read()
-    m = re.search(r'href="/raw/(\w+)"', response.decode('utf-8'))
-    if m:
-        return '%s/show/%s' % (url, m.group(1))
-    else:
-        return 'bad response: ' + response
-
-def pytest_terminal_summary(terminalreporter):
-    import _pytest.config
-    if terminalreporter.config.option.pastebin != "failed":
-        return
-    tr = terminalreporter
-    if 'failed' in tr.stats:
-        terminalreporter.write_sep("=", "Sending information to Paste Service")
-        for rep in terminalreporter.stats.get('failed'):
-            try:
-                msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
-            except AttributeError:
-                msg = tr._getfailureheadline(rep)
-            tw = _pytest.config.create_terminal_writer(terminalreporter.config, stringio=True)
-            rep.toterminal(tw)
-            s = tw.stringio.getvalue()
-            assert len(s)
-            pastebinurl = create_new_paste(s)
-            tr.write_line("%s --> %s" %(msg, pastebinurl))
diff --git a/tools/pytest/_pytest/pdb.py b/tools/pytest/_pytest/pdb.py
deleted file mode 100644
index 84c920d..0000000
--- a/tools/pytest/_pytest/pdb.py
+++ /dev/null
@@ -1,109 +0,0 @@
-""" interactive debugging with PDB, the Python Debugger. """
-from __future__ import absolute_import
-import pdb
-import sys
-
-import pytest
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("general")
-    group._addoption('--pdb',
-               action="store_true", dest="usepdb", default=False,
-               help="start the interactive Python debugger on errors.")
-
-def pytest_namespace():
-    return {'set_trace': pytestPDB().set_trace}
-
-def pytest_configure(config):
-    if config.getvalue("usepdb"):
-        config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
-
-    old = (pdb.set_trace, pytestPDB._pluginmanager)
-    def fin():
-        pdb.set_trace, pytestPDB._pluginmanager = old
-        pytestPDB._config = None
-    pdb.set_trace = pytest.set_trace
-    pytestPDB._pluginmanager = config.pluginmanager
-    pytestPDB._config = config
-    config._cleanup.append(fin)
-
-class pytestPDB:
-    """ Pseudo PDB that defers to the real pdb. """
-    _pluginmanager = None
-    _config = None
-
-    def set_trace(self):
-        """ invoke PDB set_trace debugging, dropping any IO capturing. """
-        import _pytest.config
-        frame = sys._getframe().f_back
-        if self._pluginmanager is not None:
-            capman = self._pluginmanager.getplugin("capturemanager")
-            if capman:
-                capman.suspendcapture(in_=True)
-            tw = _pytest.config.create_terminal_writer(self._config)
-            tw.line()
-            tw.sep(">", "PDB set_trace (IO-capturing turned off)")
-            self._pluginmanager.hook.pytest_enter_pdb(config=self._config)
-        pdb.Pdb().set_trace(frame)
-
-
-class PdbInvoke:
-    def pytest_exception_interact(self, node, call, report):
-        capman = node.config.pluginmanager.getplugin("capturemanager")
-        if capman:
-            out, err = capman.suspendcapture(in_=True)
-            sys.stdout.write(out)
-            sys.stdout.write(err)
-        _enter_pdb(node, call.excinfo, report)
-
-    def pytest_internalerror(self, excrepr, excinfo):
-        for line in str(excrepr).split("\n"):
-            sys.stderr.write("INTERNALERROR> %s\n" %line)
-            sys.stderr.flush()
-        tb = _postmortem_traceback(excinfo)
-        post_mortem(tb)
-
-
-def _enter_pdb(node, excinfo, rep):
-    # XXX we re-use the TerminalReporter's terminalwriter
-    # because this seems to avoid some encoding related troubles
-    # for not completely clear reasons.
-    tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
-    tw.line()
-    tw.sep(">", "traceback")
-    rep.toterminal(tw)
-    tw.sep(">", "entering PDB")
-    tb = _postmortem_traceback(excinfo)
-    post_mortem(tb)
-    rep._pdbshown = True
-    return rep
-
-
-def _postmortem_traceback(excinfo):
-    # A doctest.UnexpectedException is not useful for post_mortem.
-    # Use the underlying exception instead:
-    from doctest import UnexpectedException
-    if isinstance(excinfo.value, UnexpectedException):
-        return excinfo.value.exc_info[2]
-    else:
-        return excinfo._excinfo[2]
-
-
-def _find_last_non_hidden_frame(stack):
-    i = max(0, len(stack) - 1)
-    while i and stack[i][0].f_locals.get("__tracebackhide__", False):
-        i -= 1
-    return i
-
-
-def post_mortem(t):
-    class Pdb(pdb.Pdb):
-        def get_stack(self, f, t):
-            stack, i = pdb.Pdb.get_stack(self, f, t)
-            if f is None:
-                i = _find_last_non_hidden_frame(stack)
-            return stack, i
-    p = Pdb()
-    p.reset()
-    p.interaction(None, t)
diff --git a/tools/pytest/_pytest/pytester.py b/tools/pytest/_pytest/pytester.py
deleted file mode 100644
index faed7f5..0000000
--- a/tools/pytest/_pytest/pytester.py
+++ /dev/null
@@ -1,1110 +0,0 @@
-""" (disabled by default) support for testing pytest and pytest plugins. """
-import codecs
-import gc
-import os
-import platform
-import re
-import subprocess
-import sys
-import time
-import traceback
-from fnmatch import fnmatch
-
-from py.builtin import print_
-
-from _pytest._code import Source
-import py
-import pytest
-from _pytest.main import Session, EXIT_OK
-
-
-def pytest_addoption(parser):
-    # group = parser.getgroup("pytester", "pytester (self-tests) options")
-    parser.addoption('--lsof',
-           action="store_true", dest="lsof", default=False,
-           help=("run FD checks if lsof is available"))
-
-    parser.addoption('--runpytest', default="inprocess", dest="runpytest",
-           choices=("inprocess", "subprocess", ),
-           help=("run pytest sub runs in tests using an 'inprocess' "
-                 "or 'subprocess' (python -m main) method"))
-
-
-def pytest_configure(config):
-    # This might be called multiple times. Only take the first.
-    global _pytest_fullpath
-    try:
-        _pytest_fullpath
-    except NameError:
-        _pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
-        _pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
-
-    if config.getvalue("lsof"):
-        checker = LsofFdLeakChecker()
-        if checker.matching_platform():
-            config.pluginmanager.register(checker)
-
-
-class LsofFdLeakChecker(object):
-    def get_open_files(self):
-        out = self._exec_lsof()
-        open_files = self._parse_lsof_output(out)
-        return open_files
-
-    def _exec_lsof(self):
-        pid = os.getpid()
-        return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
-
-    def _parse_lsof_output(self, out):
-        def isopen(line):
-            return line.startswith('f') and ("deleted" not in line and
-                'mem' not in line and "txt" not in line and 'cwd' not in line)
-
-        open_files = []
-
-        for line in out.split("\n"):
-            if isopen(line):
-                fields = line.split('\0')
-                fd = fields[0][1:]
-                filename = fields[1][1:]
-                if filename.startswith('/'):
-                    open_files.append((fd, filename))
-
-        return open_files
-
-    def matching_platform(self):
-        try:
-            py.process.cmdexec("lsof -v")
-        except (py.process.cmdexec.Error, UnicodeDecodeError):
-            # cmdexec may raise UnicodeDecodeError on Windows systems
-            # with locale other than english:
-            # https://bitbucket.org/pytest-dev/py/issues/66
-            return False
-        else:
-            return True
-
-    @pytest.hookimpl(hookwrapper=True, tryfirst=True)
-    def pytest_runtest_item(self, item):
-        lines1 = self.get_open_files()
-        yield
-        if hasattr(sys, "pypy_version_info"):
-            gc.collect()
-        lines2 = self.get_open_files()
-
-        new_fds = set([t[0] for t in lines2]) - set([t[0] for t in lines1])
-        leaked_files = [t for t in lines2 if t[0] in new_fds]
-        if leaked_files:
-            error = []
-            error.append("***** %s FD leakage detected" % len(leaked_files))
-            error.extend([str(f) for f in leaked_files])
-            error.append("*** Before:")
-            error.extend([str(f) for f in lines1])
-            error.append("*** After:")
-            error.extend([str(f) for f in lines2])
-            error.append(error[0])
-            error.append("*** function %s:%s: %s " % item.location)
-            pytest.fail("\n".join(error), pytrace=False)
-
-
-# XXX copied from execnet's conftest.py - needs to be merged
-winpymap = {
-    'python2.7': r'C:\Python27\python.exe',
-    'python2.6': r'C:\Python26\python.exe',
-    'python3.1': r'C:\Python31\python.exe',
-    'python3.2': r'C:\Python32\python.exe',
-    'python3.3': r'C:\Python33\python.exe',
-    'python3.4': r'C:\Python34\python.exe',
-    'python3.5': r'C:\Python35\python.exe',
-}
-
-def getexecutable(name, cache={}):
-    try:
-        return cache[name]
-    except KeyError:
-        executable = py.path.local.sysfind(name)
-        if executable:
-            if name == "jython":
-                import subprocess
-                popen = subprocess.Popen([str(executable), "--version"],
-                    universal_newlines=True, stderr=subprocess.PIPE)
-                out, err = popen.communicate()
-                if not err or "2.5" not in err:
-                    executable = None
-                if "2.5.2" in err:
-                    executable = None # http://bugs.jython.org/issue1790
-        cache[name] = executable
-        return executable
-
-@pytest.fixture(params=['python2.6', 'python2.7', 'python3.3', "python3.4",
-                        'pypy', 'pypy3'])
-def anypython(request):
-    name = request.param
-    executable = getexecutable(name)
-    if executable is None:
-        if sys.platform == "win32":
-            executable = winpymap.get(name, None)
-            if executable:
-                executable = py.path.local(executable)
-                if executable.check():
-                    return executable
-        pytest.skip("no suitable %s found" % (name,))
-    return executable
-
-# used at least by pytest-xdist plugin
-@pytest.fixture
-def _pytest(request):
-    """ Return a helper which offers a gethookrecorder(hook)
-    method which returns a HookRecorder instance which helps
-    to make assertions about called hooks.
-    """
-    return PytestArg(request)
-
-class PytestArg:
-    def __init__(self, request):
-        self.request = request
-
-    def gethookrecorder(self, hook):
-        hookrecorder = HookRecorder(hook._pm)
-        self.request.addfinalizer(hookrecorder.finish_recording)
-        return hookrecorder
-
-
-def get_public_names(l):
-    """Only return names from iterator l without a leading underscore."""
-    return [x for x in l if x[0] != "_"]
-
-
-class ParsedCall:
-    def __init__(self, name, kwargs):
-        self.__dict__.update(kwargs)
-        self._name = name
-
-    def __repr__(self):
-        d = self.__dict__.copy()
-        del d['_name']
-        return "<ParsedCall %r(**%r)>" %(self._name, d)
-
-
-class HookRecorder:
-    """Record all hooks called in a plugin manager.
-
-    This wraps all the hook calls in the plugin manager, recording
-    each call before propagating the normal calls.
-
-    """
-
-    def __init__(self, pluginmanager):
-        self._pluginmanager = pluginmanager
-        self.calls = []
-
-        def before(hook_name, hook_impls, kwargs):
-            self.calls.append(ParsedCall(hook_name, kwargs))
-
-        def after(outcome, hook_name, hook_impls, kwargs):
-            pass
-
-        self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
-
-    def finish_recording(self):
-        self._undo_wrapping()
-
-    def getcalls(self, names):
-        if isinstance(names, str):
-            names = names.split()
-        return [call for call in self.calls if call._name in names]
-
-    def assert_contains(self, entries):
-        __tracebackhide__ = True
-        i = 0
-        entries = list(entries)
-        backlocals = sys._getframe(1).f_locals
-        while entries:
-            name, check = entries.pop(0)
-            for ind, call in enumerate(self.calls[i:]):
-                if call._name == name:
-                    print_("NAMEMATCH", name, call)
-                    if eval(check, backlocals, call.__dict__):
-                        print_("CHECKERMATCH", repr(check), "->", call)
-                    else:
-                        print_("NOCHECKERMATCH", repr(check), "-", call)
-                        continue
-                    i += ind + 1
-                    break
-                print_("NONAMEMATCH", name, "with", call)
-            else:
-                pytest.fail("could not find %r check %r" % (name, check))
-
-    def popcall(self, name):
-        __tracebackhide__ = True
-        for i, call in enumerate(self.calls):
-            if call._name == name:
-                del self.calls[i]
-                return call
-        lines = ["could not find call %r, in:" % (name,)]
-        lines.extend(["  %s" % str(x) for x in self.calls])
-        pytest.fail("\n".join(lines))
-
-    def getcall(self, name):
-        l = self.getcalls(name)
-        assert len(l) == 1, (name, l)
-        return l[0]
-
-    # functionality for test reports
-
-    def getreports(self,
-                   names="pytest_runtest_logreport pytest_collectreport"):
-        return [x.report for x in self.getcalls(names)]
-
-    def matchreport(self, inamepart="",
-        names="pytest_runtest_logreport pytest_collectreport", when=None):
-        """ return a testreport whose dotted import path matches """
-        l = []
-        for rep in self.getreports(names=names):
-            try:
-                if not when and rep.when != "call" and rep.passed:
-                    # setup/teardown passing reports - let's ignore those
-                    continue
-            except AttributeError:
-                pass
-            if when and getattr(rep, 'when', None) != when:
-                continue
-            if not inamepart or inamepart in rep.nodeid.split("::"):
-                l.append(rep)
-        if not l:
-            raise ValueError("could not find test report matching %r: "
-                             "no test reports at all!" % (inamepart,))
-        if len(l) > 1:
-            raise ValueError(
-                "found 2 or more testreports matching %r: %s" %(inamepart, l))
-        return l[0]
-
-    def getfailures(self,
-                    names='pytest_runtest_logreport pytest_collectreport'):
-        return [rep for rep in self.getreports(names) if rep.failed]
-
-    def getfailedcollections(self):
-        return self.getfailures('pytest_collectreport')
-
-    def listoutcomes(self):
-        passed = []
-        skipped = []
-        failed = []
-        for rep in self.getreports(
-            "pytest_collectreport pytest_runtest_logreport"):
-            if rep.passed:
-                if getattr(rep, "when", None) == "call":
-                    passed.append(rep)
-            elif rep.skipped:
-                skipped.append(rep)
-            elif rep.failed:
-                failed.append(rep)
-        return passed, skipped, failed
-
-    def countoutcomes(self):
-        return [len(x) for x in self.listoutcomes()]
-
-    def assertoutcome(self, passed=0, skipped=0, failed=0):
-        realpassed, realskipped, realfailed = self.listoutcomes()
-        assert passed == len(realpassed)
-        assert skipped == len(realskipped)
-        assert failed == len(realfailed)
-
-    def clear(self):
-        self.calls[:] = []
-
-
-@pytest.fixture
-def linecomp(request):
-    return LineComp()
-
-
-def pytest_funcarg__LineMatcher(request):
-    return LineMatcher
-
-
-@pytest.fixture
-def testdir(request, tmpdir_factory):
-    return Testdir(request, tmpdir_factory)
-
-
-rex_outcome = re.compile("(\d+) ([\w-]+)")
-class RunResult:
-    """The result of running a command.
-
-    Attributes:
-
-    :ret: The return value.
-    :outlines: List of lines captured from stdout.
-    :errlines: List of lines captures from stderr.
-    :stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
-       reconstruct stdout or the commonly used
-       ``stdout.fnmatch_lines()`` method.
-    :stderrr: :py:class:`LineMatcher` of stderr.
-    :duration: Duration in seconds.
-
-    """
-    def __init__(self, ret, outlines, errlines, duration):
-        self.ret = ret
-        self.outlines = outlines
-        self.errlines = errlines
-        self.stdout = LineMatcher(outlines)
-        self.stderr = LineMatcher(errlines)
-        self.duration = duration
-
-    def parseoutcomes(self):
-        """ Return a dictionary of outcomestring->num from parsing
-        the terminal output that the test process produced."""
-        for line in reversed(self.outlines):
-            if 'seconds' in line:
-                outcomes = rex_outcome.findall(line)
-                if outcomes:
-                    d = {}
-                    for num, cat in outcomes:
-                        d[cat] = int(num)
-                    return d
-
-    def assert_outcomes(self, passed=0, skipped=0, failed=0):
-        """ assert that the specified outcomes appear with the respective
-        numbers (0 means it didn't occur) in the text output from a test run."""
-        d = self.parseoutcomes()
-        assert passed == d.get("passed", 0)
-        assert skipped == d.get("skipped", 0)
-        assert failed == d.get("failed", 0)
-
-
-
-class Testdir:
-    """Temporary test directory with tools to test/run py.test itself.
-
-    This is based on the ``tmpdir`` fixture but provides a number of
-    methods which aid with testing py.test itself.  Unless
-    :py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
-    current working directory.
-
-    Attributes:
-
-    :tmpdir: The :py:class:`py.path.local` instance of the temporary
-       directory.
-
-    :plugins: A list of plugins to use with :py:meth:`parseconfig` and
-       :py:meth:`runpytest`.  Initially this is an empty list but
-       plugins can be added to the list.  The type of items to add to
-       the list depend on the method which uses them so refer to them
-       for details.
-
-    """
-
-    def __init__(self, request, tmpdir_factory):
-        self.request = request
-        # XXX remove duplication with tmpdir plugin
-        basetmp = tmpdir_factory.ensuretemp("testdir")
-        name = request.function.__name__
-        for i in range(100):
-            try:
-                tmpdir = basetmp.mkdir(name + str(i))
-            except py.error.EEXIST:
-                continue
-            break
-        self.tmpdir = tmpdir
-        self.plugins = []
-        self._savesyspath = (list(sys.path), list(sys.meta_path))
-        self._savemodulekeys = set(sys.modules)
-        self.chdir() # always chdir
-        self.request.addfinalizer(self.finalize)
-        method = self.request.config.getoption("--runpytest")
-        if method == "inprocess":
-            self._runpytest_method = self.runpytest_inprocess
-        elif method == "subprocess":
-            self._runpytest_method = self.runpytest_subprocess
-
-    def __repr__(self):
-        return "<Testdir %r>" % (self.tmpdir,)
-
-    def finalize(self):
-        """Clean up global state artifacts.
-
-        Some methods modify the global interpreter state and this
-        tries to clean this up.  It does not remove the temporary
-        directory however so it can be looked at after the test run
-        has finished.
-
-        """
-        sys.path[:], sys.meta_path[:] = self._savesyspath
-        if hasattr(self, '_olddir'):
-            self._olddir.chdir()
-        self.delete_loaded_modules()
-
-    def delete_loaded_modules(self):
-        """Delete modules that have been loaded during a test.
-
-        This allows the interpreter to catch module changes in case
-        the module is re-imported.
-        """
-        for name in set(sys.modules).difference(self._savemodulekeys):
-            # it seems zope.interfaces is keeping some state
-            # (used by twisted related tests)
-            if name != "zope.interface":
-                del sys.modules[name]
-
-    def make_hook_recorder(self, pluginmanager):
-        """Create a new :py:class:`HookRecorder` for a PluginManager."""
-        assert not hasattr(pluginmanager, "reprec")
-        pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
-        self.request.addfinalizer(reprec.finish_recording)
-        return reprec
-
-    def chdir(self):
-        """Cd into the temporary directory.
-
-        This is done automatically upon instantiation.
-
-        """
-        old = self.tmpdir.chdir()
-        if not hasattr(self, '_olddir'):
-            self._olddir = old
-
-    def _makefile(self, ext, args, kwargs):
-        items = list(kwargs.items())
-        if args:
-            source = py.builtin._totext("\n").join(
-                map(py.builtin._totext, args)) + py.builtin._totext("\n")
-            basename = self.request.function.__name__
-            items.insert(0, (basename, source))
-        ret = None
-        for name, value in items:
-            p = self.tmpdir.join(name).new(ext=ext)
-            source = Source(value)
-            def my_totext(s, encoding="utf-8"):
-                if py.builtin._isbytes(s):
-                    s = py.builtin._totext(s, encoding=encoding)
-                return s
-            source_unicode = "\n".join([my_totext(line) for line in source.lines])
-            source = py.builtin._totext(source_unicode)
-            content = source.strip().encode("utf-8") # + "\n"
-            #content = content.rstrip() + "\n"
-            p.write(content, "wb")
-            if ret is None:
-                ret = p
-        return ret
-
-    def makefile(self, ext, *args, **kwargs):
-        """Create a new file in the testdir.
-
-        ext: The extension the file should use, including the dot.
-           E.g. ".py".
-
-        args: All args will be treated as strings and joined using
-           newlines.  The result will be written as contents to the
-           file.  The name of the file will be based on the test
-           function requesting this fixture.
-           E.g. "testdir.makefile('.txt', 'line1', 'line2')"
-
-        kwargs: Each keyword is the name of a file, while the value of
-           it will be written as contents of the file.
-           E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
-
-        """
-        return self._makefile(ext, args, kwargs)
-
-    def makeconftest(self, source):
-        """Write a contest.py file with 'source' as contents."""
-        return self.makepyfile(conftest=source)
-
-    def makeini(self, source):
-        """Write a tox.ini file with 'source' as contents."""
-        return self.makefile('.ini', tox=source)
-
-    def getinicfg(self, source):
-        """Return the pytest section from the tox.ini config file."""
-        p = self.makeini(source)
-        return py.iniconfig.IniConfig(p)['pytest']
-
-    def makepyfile(self, *args, **kwargs):
-        """Shortcut for .makefile() with a .py extension."""
-        return self._makefile('.py', args, kwargs)
-
-    def maketxtfile(self, *args, **kwargs):
-        """Shortcut for .makefile() with a .txt extension."""
-        return self._makefile('.txt', args, kwargs)
-
-    def syspathinsert(self, path=None):
-        """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
-
-        This is undone automatically after the test.
-        """
-        if path is None:
-            path = self.tmpdir
-        sys.path.insert(0, str(path))
-        # a call to syspathinsert() usually means that the caller
-        # wants to import some dynamically created files.
-        # with python3 we thus invalidate import caches.
-        self._possibly_invalidate_import_caches()
-
-    def _possibly_invalidate_import_caches(self):
-        # invalidate caches if we can (py33 and above)
-        try:
-            import importlib
-        except ImportError:
-            pass
-        else:
-            if hasattr(importlib, "invalidate_caches"):
-                importlib.invalidate_caches()
-
-    def mkdir(self, name):
-        """Create a new (sub)directory."""
-        return self.tmpdir.mkdir(name)
-
-    def mkpydir(self, name):
-        """Create a new python package.
-
-        This creates a (sub)direcotry with an empty ``__init__.py``
-        file so that is recognised as a python package.
-
-        """
-        p = self.mkdir(name)
-        p.ensure("__init__.py")
-        return p
-
-    Session = Session
-    def getnode(self, config, arg):
-        """Return the collection node of a file.
-
-        :param config: :py:class:`_pytest.config.Config` instance, see
-           :py:meth:`parseconfig` and :py:meth:`parseconfigure` to
-           create the configuration.
-
-        :param arg: A :py:class:`py.path.local` instance of the file.
-
-        """
-        session = Session(config)
-        assert '::' not in str(arg)
-        p = py.path.local(arg)
-        config.hook.pytest_sessionstart(session=session)
-        res = session.perform_collect([str(p)], genitems=False)[0]
-        config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
-        return res
-
-    def getpathnode(self, path):
-        """Return the collection node of a file.
-
-        This is like :py:meth:`getnode` but uses
-        :py:meth:`parseconfigure` to create the (configured) py.test
-        Config instance.
-
-        :param path: A :py:class:`py.path.local` instance of the file.
-
-        """
-        config = self.parseconfigure(path)
-        session = Session(config)
-        x = session.fspath.bestrelpath(path)
-        config.hook.pytest_sessionstart(session=session)
-        res = session.perform_collect([x], genitems=False)[0]
-        config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
-        return res
-
-    def genitems(self, colitems):
-        """Generate all test items from a collection node.
-
-        This recurses into the collection node and returns a list of
-        all the test items contained within.
-
-        """
-        session = colitems[0].session
-        result = []
-        for colitem in colitems:
-            result.extend(session.genitems(colitem))
-        return result
-
-    def runitem(self, source):
-        """Run the "test_func" Item.
-
-        The calling test instance (the class which contains the test
-        method) must provide a ``.getrunner()`` method which should
-        return a runner which can run the test protocol for a single
-        item, like e.g. :py:func:`_pytest.runner.runtestprotocol`.
-
-        """
-        # used from runner functional tests
-        item = self.getitem(source)
-        # the test class where we are called from wants to provide the runner
-        testclassinstance = self.request.instance
-        runner = testclassinstance.getrunner()
-        return runner(item)
-
-    def inline_runsource(self, source, *cmdlineargs):
-        """Run a test module in process using ``pytest.main()``.
-
-        This run writes "source" into a temporary file and runs
-        ``pytest.main()`` on it, returning a :py:class:`HookRecorder`
-        instance for the result.
-
-        :param source: The source code of the test module.
-
-        :param cmdlineargs: Any extra command line arguments to use.
-
-        :return: :py:class:`HookRecorder` instance of the result.
-
-        """
-        p = self.makepyfile(source)
-        l = list(cmdlineargs) + [p]
-        return self.inline_run(*l)
-
-    def inline_genitems(self, *args):
-        """Run ``pytest.main(['--collectonly'])`` in-process.
-
-        Retuns a tuple of the collected items and a
-        :py:class:`HookRecorder` instance.
-
-        This runs the :py:func:`pytest.main` function to run all of
-        py.test inside the test process itself like
-        :py:meth:`inline_run`.  However the return value is a tuple of
-        the collection items and a :py:class:`HookRecorder` instance.
-
-        """
-        rec = self.inline_run("--collect-only", *args)
-        items = [x.item for x in rec.getcalls("pytest_itemcollected")]
-        return items, rec
-
-    def inline_run(self, *args, **kwargs):
-        """Run ``pytest.main()`` in-process, returning a HookRecorder.
-
-        This runs the :py:func:`pytest.main` function to run all of
-        py.test inside the test process itself.  This means it can
-        return a :py:class:`HookRecorder` instance which gives more
-        detailed results from then run then can be done by matching
-        stdout/stderr from :py:meth:`runpytest`.
-
-        :param args: Any command line arguments to pass to
-           :py:func:`pytest.main`.
-
-        :param plugin: (keyword-only) Extra plugin instances the
-           ``pytest.main()`` instance should use.
-
-        :return: A :py:class:`HookRecorder` instance.
-
-        """
-        rec = []
-        class Collect:
-            def pytest_configure(x, config):
-                rec.append(self.make_hook_recorder(config.pluginmanager))
-
-        plugins = kwargs.get("plugins") or []
-        plugins.append(Collect())
-        ret = pytest.main(list(args), plugins=plugins)
-        self.delete_loaded_modules()
-        if len(rec) == 1:
-            reprec = rec.pop()
-        else:
-            class reprec:
-                pass
-        reprec.ret = ret
-
-        # typically we reraise keyboard interrupts from the child run
-        # because it's our user requesting interruption of the testing
-        if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
-            calls = reprec.getcalls("pytest_keyboard_interrupt")
-            if calls and calls[-1].excinfo.type == KeyboardInterrupt:
-                raise KeyboardInterrupt()
-        return reprec
-
-    def runpytest_inprocess(self, *args, **kwargs):
-        """ Return result of running pytest in-process, providing a similar
-        interface to what self.runpytest() provides. """
-        if kwargs.get("syspathinsert"):
-            self.syspathinsert()
-        now = time.time()
-        capture = py.io.StdCapture()
-        try:
-            try:
-                reprec = self.inline_run(*args, **kwargs)
-            except SystemExit as e:
-                class reprec:
-                    ret = e.args[0]
-            except Exception:
-                traceback.print_exc()
-                class reprec:
-                    ret = 3
-        finally:
-            out, err = capture.reset()
-            sys.stdout.write(out)
-            sys.stderr.write(err)
-
-        res = RunResult(reprec.ret,
-                        out.split("\n"), err.split("\n"),
-                        time.time()-now)
-        res.reprec = reprec
-        return res
-
-    def runpytest(self, *args, **kwargs):
-        """ Run pytest inline or in a subprocess, depending on the command line
-        option "--runpytest" and return a :py:class:`RunResult`.
-
-        """
-        args = self._ensure_basetemp(args)
-        return self._runpytest_method(*args, **kwargs)
-
-    def _ensure_basetemp(self, args):
-        args = [str(x) for x in args]
-        for x in args:
-            if str(x).startswith('--basetemp'):
-                #print ("basedtemp exists: %s" %(args,))
-                break
-        else:
-            args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
-            #print ("added basetemp: %s" %(args,))
-        return args
-
-    def parseconfig(self, *args):
-        """Return a new py.test Config instance from given commandline args.
-
-        This invokes the py.test bootstrapping code in _pytest.config
-        to create a new :py:class:`_pytest.core.PluginManager` and
-        call the pytest_cmdline_parse hook to create new
-        :py:class:`_pytest.config.Config` instance.
-
-        If :py:attr:`plugins` has been populated they should be plugin
-        modules which will be registered with the PluginManager.
-
-        """
-        args = self._ensure_basetemp(args)
-
-        import _pytest.config
-        config = _pytest.config._prepareconfig(args, self.plugins)
-        # we don't know what the test will do with this half-setup config
-        # object and thus we make sure it gets unconfigured properly in any
-        # case (otherwise capturing could still be active, for example)
-        self.request.addfinalizer(config._ensure_unconfigure)
-        return config
-
-    def parseconfigure(self, *args):
-        """Return a new py.test configured Config instance.
-
-        This returns a new :py:class:`_pytest.config.Config` instance
-        like :py:meth:`parseconfig`, but also calls the
-        pytest_configure hook.
-
-        """
-        config = self.parseconfig(*args)
-        config._do_configure()
-        self.request.addfinalizer(config._ensure_unconfigure)
-        return config
-
-    def getitem(self,  source, funcname="test_func"):
-        """Return the test item for a test function.
-
-        This writes the source to a python file and runs py.test's
-        collection on the resulting module, returning the test item
-        for the requested function name.
-
-        :param source: The module source.
-
-        :param funcname: The name of the test function for which the
-           Item must be returned.
-
-        """
-        items = self.getitems(source)
-        for item in items:
-            if item.name == funcname:
-                return item
-        assert 0, "%r item not found in module:\n%s\nitems: %s" %(
-                  funcname, source, items)
-
-    def getitems(self,  source):
-        """Return all test items collected from the module.
-
-        This writes the source to a python file and runs py.test's
-        collection on the resulting module, returning all test items
-        contained within.
-
-        """
-        modcol = self.getmodulecol(source)
-        return self.genitems([modcol])
-
-    def getmodulecol(self,  source, configargs=(), withinit=False):
-        """Return the module collection node for ``source``.
-
-        This writes ``source`` to a file using :py:meth:`makepyfile`
-        and then runs the py.test collection on it, returning the
-        collection node for the test module.
-
-        :param source: The source code of the module to collect.
-
-        :param configargs: Any extra arguments to pass to
-           :py:meth:`parseconfigure`.
-
-        :param withinit: Whether to also write a ``__init__.py`` file
-           to the temporarly directory to ensure it is a package.
-
-        """
-        kw = {self.request.function.__name__: Source(source).strip()}
-        path = self.makepyfile(**kw)
-        if withinit:
-            self.makepyfile(__init__ = "#")
-        self.config = config = self.parseconfigure(path, *configargs)
-        node = self.getnode(config, path)
-        return node
-
-    def collect_by_name(self, modcol, name):
-        """Return the collection node for name from the module collection.
-
-        This will search a module collection node for a collection
-        node matching the given name.
-
-        :param modcol: A module collection node, see
-           :py:meth:`getmodulecol`.
-
-        :param name: The name of the node to return.
-
-        """
-        for colitem in modcol._memocollect():
-            if colitem.name == name:
-                return colitem
-
-    def popen(self, cmdargs, stdout, stderr, **kw):
-        """Invoke subprocess.Popen.
-
-        This calls subprocess.Popen making sure the current working
-        directory is the PYTHONPATH.
-
-        You probably want to use :py:meth:`run` instead.
-
-        """
-        env = os.environ.copy()
-        env['PYTHONPATH'] = os.pathsep.join(filter(None, [
-            str(os.getcwd()), env.get('PYTHONPATH', '')]))
-        kw['env'] = env
-        return subprocess.Popen(cmdargs,
-                                stdout=stdout, stderr=stderr, **kw)
-
-    def run(self, *cmdargs):
-        """Run a command with arguments.
-
-        Run a process using subprocess.Popen saving the stdout and
-        stderr.
-
-        Returns a :py:class:`RunResult`.
-
-        """
-        return self._run(*cmdargs)
-
-    def _run(self, *cmdargs):
-        cmdargs = [str(x) for x in cmdargs]
-        p1 = self.tmpdir.join("stdout")
-        p2 = self.tmpdir.join("stderr")
-        print_("running:", ' '.join(cmdargs))
-        print_("     in:", str(py.path.local()))
-        f1 = codecs.open(str(p1), "w", encoding="utf8")
-        f2 = codecs.open(str(p2), "w", encoding="utf8")
-        try:
-            now = time.time()
-            popen = self.popen(cmdargs, stdout=f1, stderr=f2,
-                close_fds=(sys.platform != "win32"))
-            ret = popen.wait()
-        finally:
-            f1.close()
-            f2.close()
-        f1 = codecs.open(str(p1), "r", encoding="utf8")
-        f2 = codecs.open(str(p2), "r", encoding="utf8")
-        try:
-            out = f1.read().splitlines()
-            err = f2.read().splitlines()
-        finally:
-            f1.close()
-            f2.close()
-        self._dump_lines(out, sys.stdout)
-        self._dump_lines(err, sys.stderr)
-        return RunResult(ret, out, err, time.time()-now)
-
-    def _dump_lines(self, lines, fp):
-        try:
-            for line in lines:
-                py.builtin.print_(line, file=fp)
-        except UnicodeEncodeError:
-            print("couldn't print to %s because of encoding" % (fp,))
-
-    def _getpytestargs(self):
-        # we cannot use "(sys.executable,script)"
-        # because on windows the script is e.g. a py.test.exe
-        return (sys.executable, _pytest_fullpath,) # noqa
-
-    def runpython(self, script):
-        """Run a python script using sys.executable as interpreter.
-
-        Returns a :py:class:`RunResult`.
-        """
-        return self.run(sys.executable, script)
-
-    def runpython_c(self, command):
-        """Run python -c "command", return a :py:class:`RunResult`."""
-        return self.run(sys.executable, "-c", command)
-
-    def runpytest_subprocess(self, *args, **kwargs):
-        """Run py.test as a subprocess with given arguments.
-
-        Any plugins added to the :py:attr:`plugins` list will added
-        using the ``-p`` command line option.  Addtionally
-        ``--basetemp`` is used put any temporary files and directories
-        in a numbered directory prefixed with "runpytest-" so they do
-        not conflict with the normal numberd pytest location for
-        temporary files and directories.
-
-        Returns a :py:class:`RunResult`.
-
-        """
-        p = py.path.local.make_numbered_dir(prefix="runpytest-",
-            keep=None, rootdir=self.tmpdir)
-        args = ('--basetemp=%s' % p, ) + args
-        #for x in args:
-        #    if '--confcutdir' in str(x):
-        #        break
-        #else:
-        #    pass
-        #    args = ('--confcutdir=.',) + args
-        plugins = [x for x in self.plugins if isinstance(x, str)]
-        if plugins:
-            args = ('-p', plugins[0]) + args
-        args = self._getpytestargs() + args
-        return self.run(*args)
-
-    def spawn_pytest(self, string, expect_timeout=10.0):
-        """Run py.test using pexpect.
-
-        This makes sure to use the right py.test and sets up the
-        temporary directory locations.
-
-        The pexpect child is returned.
-
-        """
-        basetemp = self.tmpdir.mkdir("pexpect")
-        invoke = " ".join(map(str, self._getpytestargs()))
-        cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
-        return self.spawn(cmd, expect_timeout=expect_timeout)
-
-    def spawn(self, cmd, expect_timeout=10.0):
-        """Run a command using pexpect.
-
-        The pexpect child is returned.
-        """
-        pexpect = pytest.importorskip("pexpect", "3.0")
-        if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
-            pytest.skip("pypy-64 bit not supported")
-        if sys.platform == "darwin":
-            pytest.xfail("pexpect does not work reliably on darwin?!")
-        if sys.platform.startswith("freebsd"):
-            pytest.xfail("pexpect does not work reliably on freebsd")
-        logfile = self.tmpdir.join("spawn.out").open("wb")
-        child = pexpect.spawn(cmd, logfile=logfile)
-        self.request.addfinalizer(logfile.close)
-        child.timeout = expect_timeout
-        return child
-
-def getdecoded(out):
-        try:
-            return out.decode("utf-8")
-        except UnicodeDecodeError:
-            return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
-                    py.io.saferepr(out),)
-
-
-class LineComp:
-    def __init__(self):
-        self.stringio = py.io.TextIO()
-
-    def assert_contains_lines(self, lines2):
-        """ assert that lines2 are contained (linearly) in lines1.
-            return a list of extralines found.
-        """
-        __tracebackhide__ = True
-        val = self.stringio.getvalue()
-        self.stringio.truncate(0)
-        self.stringio.seek(0)
-        lines1 = val.split("\n")
-        return LineMatcher(lines1).fnmatch_lines(lines2)
-
-
-class LineMatcher:
-    """Flexible matching of text.
-
-    This is a convenience class to test large texts like the output of
-    commands.
-
-    The constructor takes a list of lines without their trailing
-    newlines, i.e. ``text.splitlines()``.
-
-    """
-
-    def __init__(self,  lines):
-        self.lines = lines
-
-    def str(self):
-        """Return the entire original text."""
-        return "\n".join(self.lines)
-
-    def _getlines(self, lines2):
-        if isinstance(lines2, str):
-            lines2 = Source(lines2)
-        if isinstance(lines2, Source):
-            lines2 = lines2.strip().lines
-        return lines2
-
-    def fnmatch_lines_random(self, lines2):
-        """Check lines exist in the output.
-
-        The argument is a list of lines which have to occur in the
-        output, in any order.  Each line can contain glob whildcards.
-
-        """
-        lines2 = self._getlines(lines2)
-        for line in lines2:
-            for x in self.lines:
-                if line == x or fnmatch(x, line):
-                    print_("matched: ", repr(line))
-                    break
-            else:
-                raise ValueError("line %r not found in output" % line)
-
-    def get_lines_after(self, fnline):
-        """Return all lines following the given line in the text.
-
-        The given line can contain glob wildcards.
-        """
-        for i, line in enumerate(self.lines):
-            if fnline == line or fnmatch(line, fnline):
-                return self.lines[i+1:]
-        raise ValueError("line %r not found in output" % fnline)
-
-    def fnmatch_lines(self, lines2):
-        """Search the text for matching lines.
-
-        The argument is a list of lines which have to match and can
-        use glob wildcards.  If they do not match an pytest.fail() is
-        called.  The matches and non-matches are also printed on
-        stdout.
-
-        """
-        def show(arg1, arg2):
-            py.builtin.print_(arg1, arg2, file=sys.stderr)
-        lines2 = self._getlines(lines2)
-        lines1 = self.lines[:]
-        nextline = None
-        extralines = []
-        __tracebackhide__ = True
-        for line in lines2:
-            nomatchprinted = False
-            while lines1:
-                nextline = lines1.pop(0)
-                if line == nextline:
-                    show("exact match:", repr(line))
-                    break
-                elif fnmatch(nextline, line):
-                    show("fnmatch:", repr(line))
-                    show("   with:", repr(nextline))
-                    break
-                else:
-                    if not nomatchprinted:
-                        show("nomatch:", repr(line))
-                        nomatchprinted = True
-                    show("    and:", repr(nextline))
-                extralines.append(nextline)
-            else:
-                pytest.fail("remains unmatched: %r, see stderr" % (line,))
diff --git a/tools/pytest/_pytest/python.py b/tools/pytest/_pytest/python.py
deleted file mode 100644
index 3580eae..0000000
--- a/tools/pytest/_pytest/python.py
+++ /dev/null
@@ -1,2302 +0,0 @@
-""" Python test discovery, setup and run of test functions. """
-import fnmatch
-import functools
-import inspect
-import re
-import types
-import sys
-
-import py
-import pytest
-from _pytest._code.code import TerminalRepr
-from _pytest.mark import MarkDecorator, MarkerError
-
-try:
-    import enum
-except ImportError:  # pragma: no cover
-    # Only available in Python 3.4+ or as a backport
-    enum = None
-
-import _pytest
-import _pytest._pluggy as pluggy
-
-cutdir2 = py.path.local(_pytest.__file__).dirpath()
-cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
-
-
-NoneType = type(None)
-NOTSET = object()
-isfunction = inspect.isfunction
-isclass = inspect.isclass
-callable = py.builtin.callable
-# used to work around a python2 exception info leak
-exc_clear = getattr(sys, 'exc_clear', lambda: None)
-# The type of re.compile objects is not exposed in Python.
-REGEX_TYPE = type(re.compile(''))
-
-_PY3 = sys.version_info > (3, 0)
-_PY2 = not _PY3
-
-
-if hasattr(inspect, 'signature'):
-    def _format_args(func):
-        return str(inspect.signature(func))
-else:
-    def _format_args(func):
-        return inspect.formatargspec(*inspect.getargspec(func))
-
-if  sys.version_info[:2] == (2, 6):
-    def isclass(object):
-        """ Return true if the object is a class. Overrides inspect.isclass for
-        python 2.6 because it will return True for objects which always return
-        something on __getattr__ calls (see #1035).
-        Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc
-        """
-        return isinstance(object, (type, types.ClassType))
-
-def _has_positional_arg(func):
-    return func.__code__.co_argcount
-
-
-def filter_traceback(entry):
-    # entry.path might sometimes return a str object when the entry
-    # points to dynamically generated code
-    # see https://bitbucket.org/pytest-dev/py/issues/71
-    raw_filename = entry.frame.code.raw.co_filename
-    is_generated = '<' in raw_filename and '>' in raw_filename
-    if is_generated:
-        return False
-    # entry.path might point to an inexisting file, in which case it will
-    # alsso return a str object. see #1133
-    p = py.path.local(entry.path)
-    return p != cutdir1 and not p.relto(cutdir2)
-
-
-def get_real_func(obj):
-    """ gets the real function object of the (possibly) wrapped object by
-    functools.wraps or functools.partial.
-    """
-    while hasattr(obj, "__wrapped__"):
-        obj = obj.__wrapped__
-    if isinstance(obj, functools.partial):
-        obj = obj.func
-    return obj
-
-def getfslineno(obj):
-    # xxx let decorators etc specify a sane ordering
-    obj = get_real_func(obj)
-    if hasattr(obj, 'place_as'):
-        obj = obj.place_as
-    fslineno = _pytest._code.getfslineno(obj)
-    assert isinstance(fslineno[1], int), obj
-    return fslineno
-
-def getimfunc(func):
-    try:
-        return func.__func__
-    except AttributeError:
-        try:
-            return func.im_func
-        except AttributeError:
-            return func
-
-def safe_getattr(object, name, default):
-    """ Like getattr but return default upon any Exception.
-
-    Attribute access can potentially fail for 'evil' Python objects.
-    See issue214
-    """
-    try:
-        return getattr(object, name, default)
-    except Exception:
-        return default
-
-
-class FixtureFunctionMarker:
-    def __init__(self, scope, params,
-                 autouse=False, yieldctx=False, ids=None):
-        self.scope = scope
-        self.params = params
-        self.autouse = autouse
-        self.yieldctx = yieldctx
-        self.ids = ids
-
-    def __call__(self, function):
-        if isclass(function):
-            raise ValueError(
-                    "class fixtures not supported (may be in the future)")
-        function._pytestfixturefunction = self
-        return function
-
-
-def fixture(scope="function", params=None, autouse=False, ids=None):
-    """ (return a) decorator to mark a fixture factory function.
-
-    This decorator can be used (with or or without parameters) to define
-    a fixture function.  The name of the fixture function can later be
-    referenced to cause its invocation ahead of running tests: test
-    modules or classes can use the pytest.mark.usefixtures(fixturename)
-    marker.  Test functions can directly use fixture names as input
-    arguments in which case the fixture instance returned from the fixture
-    function will be injected.
-
-    :arg scope: the scope for which this fixture is shared, one of
-                "function" (default), "class", "module", "session".
-
-    :arg params: an optional list of parameters which will cause multiple
-                invocations of the fixture function and all of the tests
-                using it.
-
-    :arg autouse: if True, the fixture func is activated for all tests that
-                can see it.  If False (the default) then an explicit
-                reference is needed to activate the fixture.
-
-    :arg ids: list of string ids each corresponding to the params
-       so that they are part of the test id. If no ids are provided
-       they will be generated automatically from the params.
-
-    """
-    if callable(scope) and params is None and autouse == False:
-        # direct decoration
-        return FixtureFunctionMarker(
-                "function", params, autouse)(scope)
-    if params is not None and not isinstance(params, (list, tuple)):
-        params = list(params)
-    return FixtureFunctionMarker(scope, params, autouse, ids=ids)
-
-def yield_fixture(scope="function", params=None, autouse=False, ids=None):
-    """ (return a) decorator to mark a yield-fixture factory function
-    (EXPERIMENTAL).
-
-    This takes the same arguments as :py:func:`pytest.fixture` but
-    expects a fixture function to use a ``yield`` instead of a ``return``
-    statement to provide a fixture.  See
-    http://pytest.org/en/latest/yieldfixture.html for more info.
-    """
-    if callable(scope) and params is None and autouse == False:
-        # direct decoration
-        return FixtureFunctionMarker(
-                "function", params, autouse, yieldctx=True)(scope)
-    else:
-        return FixtureFunctionMarker(scope, params, autouse,
-                                     yieldctx=True, ids=ids)
-
-defaultfuncargprefixmarker = fixture()
-
-def pyobj_property(name):
-    def get(self):
-        node = self.getparent(getattr(pytest, name))
-        if node is not None:
-            return node.obj
-    doc = "python %s object this node was collected from (can be None)." % (
-          name.lower(),)
-    return property(get, None, None, doc)
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("general")
-    group.addoption('--fixtures', '--funcargs',
-               action="store_true", dest="showfixtures", default=False,
-               help="show available fixtures, sorted by plugin appearance")
-    parser.addini("usefixtures", type="args", default=[],
-        help="list of default fixtures to be used with this project")
-    parser.addini("python_files", type="args",
-        default=['test_*.py', '*_test.py'],
-        help="glob-style file patterns for Python test module discovery")
-    parser.addini("python_classes", type="args", default=["Test",],
-        help="prefixes or glob names for Python test class discovery")
-    parser.addini("python_functions", type="args", default=["test",],
-        help="prefixes or glob names for Python test function and "
-             "method discovery")
-
-    group.addoption("--import-mode", default="prepend",
-        choices=["prepend", "append"], dest="importmode",
-        help="prepend/append to sys.path when importing test modules, "
-             "default is to prepend.")
-
-
-def pytest_cmdline_main(config):
-    if config.option.showfixtures:
-        showfixtures(config)
-        return 0
-
-
-def pytest_generate_tests(metafunc):
-    # those alternative spellings are common - raise a specific error to alert
-    # the user
-    alt_spellings = ['parameterize', 'parametrise', 'parameterise']
-    for attr in alt_spellings:
-        if hasattr(metafunc.function, attr):
-            msg = "{0} has '{1}', spelling should be 'parametrize'"
-            raise MarkerError(msg.format(metafunc.function.__name__, attr))
-    try:
-        markers = metafunc.function.parametrize
-    except AttributeError:
-        return
-    for marker in markers:
-        metafunc.parametrize(*marker.args, **marker.kwargs)
-
-def pytest_configure(config):
-    config.addinivalue_line("markers",
-        "parametrize(argnames, argvalues): call a test function multiple "
-        "times passing in different arguments in turn. argvalues generally "
-        "needs to be a list of values if argnames specifies only one name "
-        "or a list of tuples of values if argnames specifies multiple names. "
-        "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
-        "decorated test function, one with arg1=1 and another with arg1=2."
-        "see http://pytest.org/latest/parametrize.html for more info and "
-        "examples."
-    )
-    config.addinivalue_line("markers",
-        "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
-        "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
-    )
-
-def pytest_sessionstart(session):
-    session._fixturemanager = FixtureManager(session)
-
-@pytest.hookimpl(trylast=True)
-def pytest_namespace():
-    raises.Exception = pytest.fail.Exception
-    return {
-        'fixture': fixture,
-        'yield_fixture': yield_fixture,
-        'raises' : raises,
-        'collect': {
-        'Module': Module, 'Class': Class, 'Instance': Instance,
-        'Function': Function, 'Generator': Generator,
-        '_fillfuncargs': fillfixtures}
-    }
-
-@fixture(scope="session")
-def pytestconfig(request):
-    """ the pytest config object with access to command line opts."""
-    return request.config
-
-
-@pytest.hookimpl(trylast=True)
-def pytest_pyfunc_call(pyfuncitem):
-    testfunction = pyfuncitem.obj
-    if pyfuncitem._isyieldedfunction():
-        testfunction(*pyfuncitem._args)
-    else:
-        funcargs = pyfuncitem.funcargs
-        testargs = {}
-        for arg in pyfuncitem._fixtureinfo.argnames:
-            testargs[arg] = funcargs[arg]
-        testfunction(**testargs)
-    return True
-
-def pytest_collect_file(path, parent):
-    ext = path.ext
-    if ext == ".py":
-        if not parent.session.isinitpath(path):
-            for pat in parent.config.getini('python_files'):
-                if path.fnmatch(pat):
-                    break
-            else:
-               return
-        ihook = parent.session.gethookproxy(path)
-        return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
-
-def pytest_pycollect_makemodule(path, parent):
-    return Module(path, parent)
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_pycollect_makeitem(collector, name, obj):
-    outcome = yield
-    res = outcome.get_result()
-    if res is not None:
-        raise StopIteration
-    # nothing was collected elsewhere, let's do it here
-    if isclass(obj):
-        if collector.istestclass(obj, name):
-            Class = collector._getcustomclass("Class")
-            outcome.force_result(Class(name, parent=collector))
-    elif collector.istestfunction(obj, name):
-        # mock seems to store unbound methods (issue473), normalize it
-        obj = getattr(obj, "__func__", obj)
-        # We need to try and unwrap the function if it's a functools.partial
-        # or a funtools.wrapped.
-        # We musn't if it's been wrapped with mock.patch (python 2 only)
-        if not (isfunction(obj) or isfunction(get_real_func(obj))):
-            collector.warn(code="C2", message=
-                "cannot collect %r because it is not a function."
-                % name, )
-        elif getattr(obj, "__test__", True):
-            if is_generator(obj):
-                res = Generator(name, parent=collector)
-            else:
-                res = list(collector._genfunctions(name, obj))
-            outcome.force_result(res)
-
-def is_generator(func):
-    try:
-        return _pytest._code.getrawcode(func).co_flags & 32 # generator function
-    except AttributeError: # builtin functions have no bytecode
-        # assume them to not be generators
-        return False
-
-class PyobjContext(object):
-    module = pyobj_property("Module")
-    cls = pyobj_property("Class")
-    instance = pyobj_property("Instance")
-
-class PyobjMixin(PyobjContext):
-    def obj():
-        def fget(self):
-            try:
-                return self._obj
-            except AttributeError:
-                self._obj = obj = self._getobj()
-                return obj
-        def fset(self, value):
-            self._obj = value
-        return property(fget, fset, None, "underlying python object")
-    obj = obj()
-
-    def _getobj(self):
-        return getattr(self.parent.obj, self.name)
-
-    def getmodpath(self, stopatmodule=True, includemodule=False):
-        """ return python path relative to the containing module. """
-        chain = self.listchain()
-        chain.reverse()
-        parts = []
-        for node in chain:
-            if isinstance(node, Instance):
-                continue
-            name = node.name
-            if isinstance(node, Module):
-                assert name.endswith(".py")
-                name = name[:-3]
-                if stopatmodule:
-                    if includemodule:
-                        parts.append(name)
-                    break
-            parts.append(name)
-        parts.reverse()
-        s = ".".join(parts)
-        return s.replace(".[", "[")
-
-    def _getfslineno(self):
-        return getfslineno(self.obj)
-
-    def reportinfo(self):
-        # XXX caching?
-        obj = self.obj
-        compat_co_firstlineno = getattr(obj, 'compat_co_firstlineno', None)
-        if isinstance(compat_co_firstlineno, int):
-            # nose compatibility
-            fspath = sys.modules[obj.__module__].__file__
-            if fspath.endswith(".pyc"):
-                fspath = fspath[:-1]
-            lineno = compat_co_firstlineno
-        else:
-            fspath, lineno = getfslineno(obj)
-        modpath = self.getmodpath()
-        assert isinstance(lineno, int)
-        return fspath, lineno, modpath
-
-class PyCollector(PyobjMixin, pytest.Collector):
-
-    def funcnamefilter(self, name):
-        return self._matches_prefix_or_glob_option('python_functions', name)
-
-    def isnosetest(self, obj):
-        """ Look for the __test__ attribute, which is applied by the
-        @nose.tools.istest decorator
-        """
-        # We explicitly check for "is True" here to not mistakenly treat
-        # classes with a custom __getattr__ returning something truthy (like a
-        # function) as test classes.
-        return safe_getattr(obj, '__test__', False) is True
-
-    def classnamefilter(self, name):
-        return self._matches_prefix_or_glob_option('python_classes', name)
-
-    def istestfunction(self, obj, name):
-        return (
-            (self.funcnamefilter(name) or self.isnosetest(obj)) and
-            safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
-        )
-
-    def istestclass(self, obj, name):
-        return self.classnamefilter(name) or self.isnosetest(obj)
-
-    def _matches_prefix_or_glob_option(self, option_name, name):
-        """
-        checks if the given name matches the prefix or glob-pattern defined
-        in ini configuration.
-        """
-        for option in self.config.getini(option_name):
-            if name.startswith(option):
-                return True
-            # check that name looks like a glob-string before calling fnmatch
-            # because this is called for every name in each collected module,
-            # and fnmatch is somewhat expensive to call
-            elif ('*' in option or '?' in option or '[' in option) and \
-                    fnmatch.fnmatch(name, option):
-                return True
-        return False
-
-    def collect(self):
-        if not getattr(self.obj, "__test__", True):
-            return []
-
-        # NB. we avoid random getattrs and peek in the __dict__ instead
-        # (XXX originally introduced from a PyPy need, still true?)
-        dicts = [getattr(self.obj, '__dict__', {})]
-        for basecls in inspect.getmro(self.obj.__class__):
-            dicts.append(basecls.__dict__)
-        seen = {}
-        l = []
-        for dic in dicts:
-            for name, obj in list(dic.items()):
-                if name in seen:
-                    continue
-                seen[name] = True
-                res = self.makeitem(name, obj)
-                if res is None:
-                    continue
-                if not isinstance(res, list):
-                    res = [res]
-                l.extend(res)
-        l.sort(key=lambda item: item.reportinfo()[:2])
-        return l
-
-    def makeitem(self, name, obj):
-        #assert self.ihook.fspath == self.fspath, self
-        return self.ihook.pytest_pycollect_makeitem(
-            collector=self, name=name, obj=obj)
-
-    def _genfunctions(self, name, funcobj):
-        module = self.getparent(Module).obj
-        clscol = self.getparent(Class)
-        cls = clscol and clscol.obj or None
-        transfer_markers(funcobj, cls, module)
-        fm = self.session._fixturemanager
-        fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
-        metafunc = Metafunc(funcobj, fixtureinfo, self.config,
-                            cls=cls, module=module)
-        methods = []
-        if hasattr(module, "pytest_generate_tests"):
-            methods.append(module.pytest_generate_tests)
-        if hasattr(cls, "pytest_generate_tests"):
-            methods.append(cls().pytest_generate_tests)
-        if methods:
-            self.ihook.pytest_generate_tests.call_extra(methods,
-                                                        dict(metafunc=metafunc))
-        else:
-            self.ihook.pytest_generate_tests(metafunc=metafunc)
-
-        Function = self._getcustomclass("Function")
-        if not metafunc._calls:
-            yield Function(name, parent=self, fixtureinfo=fixtureinfo)
-        else:
-            # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
-            add_funcarg_pseudo_fixture_def(self, metafunc, fm)
-
-            for callspec in metafunc._calls:
-                subname = "%s[%s]" %(name, callspec.id)
-                yield Function(name=subname, parent=self,
-                               callspec=callspec, callobj=funcobj,
-                               fixtureinfo=fixtureinfo,
-                               keywords={callspec.id:True})
-
-def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
-    # this function will transform all collected calls to a functions
-    # if they use direct funcargs (i.e. direct parametrization)
-    # because we want later test execution to be able to rely on
-    # an existing FixtureDef structure for all arguments.
-    # XXX we can probably avoid this algorithm  if we modify CallSpec2
-    # to directly care for creating the fixturedefs within its methods.
-    if not metafunc._calls[0].funcargs:
-        return # this function call does not have direct parametrization
-    # collect funcargs of all callspecs into a list of values
-    arg2params = {}
-    arg2scope = {}
-    for callspec in metafunc._calls:
-        for argname, argvalue in callspec.funcargs.items():
-            assert argname not in callspec.params
-            callspec.params[argname] = argvalue
-            arg2params_list = arg2params.setdefault(argname, [])
-            callspec.indices[argname] = len(arg2params_list)
-            arg2params_list.append(argvalue)
-            if argname not in arg2scope:
-                scopenum = callspec._arg2scopenum.get(argname,
-                                                      scopenum_function)
-                arg2scope[argname] = scopes[scopenum]
-        callspec.funcargs.clear()
-
-    # register artificial FixtureDef's so that later at test execution
-    # time we can rely on a proper FixtureDef to exist for fixture setup.
-    arg2fixturedefs = metafunc._arg2fixturedefs
-    for argname, valuelist in arg2params.items():
-        # if we have a scope that is higher than function we need
-        # to make sure we only ever create an according fixturedef on
-        # a per-scope basis. We thus store and cache the fixturedef on the
-        # node related to the scope.
-        scope = arg2scope[argname]
-        node = None
-        if scope != "function":
-            node = get_scope_node(collector, scope)
-            if node is None:
-                assert scope == "class" and isinstance(collector, Module)
-                # use module-level collector for class-scope (for now)
-                node = collector
-        if node and argname in node._name2pseudofixturedef:
-            arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
-        else:
-            fixturedef =  FixtureDef(fixturemanager, '', argname,
-                           get_direct_param_fixture_func,
-                           arg2scope[argname],
-                           valuelist, False, False)
-            arg2fixturedefs[argname] = [fixturedef]
-            if node is not None:
-                node._name2pseudofixturedef[argname] = fixturedef
-
-
-def get_direct_param_fixture_func(request):
-    return request.param
-
-class FuncFixtureInfo:
-    def __init__(self, argnames, names_closure, name2fixturedefs):
-        self.argnames = argnames
-        self.names_closure = names_closure
-        self.name2fixturedefs = name2fixturedefs
-
-
-def _marked(func, mark):
-    """ Returns True if :func: is already marked with :mark:, False otherwise.
-    This can happen if marker is applied to class and the test file is
-    invoked more than once.
-    """
-    try:
-        func_mark = getattr(func, mark.name)
-    except AttributeError:
-        return False
-    return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
-
-
-def transfer_markers(funcobj, cls, mod):
-    # XXX this should rather be code in the mark plugin or the mark
-    # plugin should merge with the python plugin.
-    for holder in (cls, mod):
-        try:
-            pytestmark = holder.pytestmark
-        except AttributeError:
-            continue
-        if isinstance(pytestmark, list):
-            for mark in pytestmark:
-                if not _marked(funcobj, mark):
-                    mark(funcobj)
-        else:
-            if not _marked(funcobj, pytestmark):
-                pytestmark(funcobj)
-
-class Module(pytest.File, PyCollector):
-    """ Collector for test classes and functions. """
-    def _getobj(self):
-        return self._memoizedcall('_obj', self._importtestmodule)
-
-    def collect(self):
-        self.session._fixturemanager.parsefactories(self)
-        return super(Module, self).collect()
-
-    def _importtestmodule(self):
-        # we assume we are only called once per module
-        importmode = self.config.getoption("--import-mode")
-        try:
-            mod = self.fspath.pyimport(ensuresyspath=importmode)
-        except SyntaxError:
-            raise self.CollectError(
-                _pytest._code.ExceptionInfo().getrepr(style="short"))
-        except self.fspath.ImportMismatchError:
-            e = sys.exc_info()[1]
-            raise self.CollectError(
-                "import file mismatch:\n"
-                "imported module %r has this __file__ attribute:\n"
-                "  %s\n"
-                "which is not the same as the test file we want to collect:\n"
-                "  %s\n"
-                "HINT: remove __pycache__ / .pyc files and/or use a "
-                "unique basename for your test file modules"
-                 % e.args
-            )
-        #print "imported test module", mod
-        self.config.pluginmanager.consider_module(mod)
-        return mod
-
-    def setup(self):
-        setup_module = xunitsetup(self.obj, "setUpModule")
-        if setup_module is None:
-            setup_module = xunitsetup(self.obj, "setup_module")
-        if setup_module is not None:
-            #XXX: nose compat hack, move to nose plugin
-            # if it takes a positional arg, its probably a pytest style one
-            # so we pass the current module object
-            if _has_positional_arg(setup_module):
-                setup_module(self.obj)
-            else:
-                setup_module()
-        fin = getattr(self.obj, 'tearDownModule', None)
-        if fin is None:
-            fin = getattr(self.obj, 'teardown_module', None)
-        if fin is not None:
-            #XXX: nose compat hack, move to nose plugin
-            # if it takes a positional arg, it's probably a pytest style one
-            # so we pass the current module object
-            if _has_positional_arg(fin):
-                finalizer = lambda: fin(self.obj)
-            else:
-                finalizer = fin
-            self.addfinalizer(finalizer)
-
-
-class Class(PyCollector):
-    """ Collector for test methods. """
-    def collect(self):
-        if hasinit(self.obj):
-            self.warn("C1", "cannot collect test class %r because it has a "
-                "__init__ constructor" % self.obj.__name__)
-            return []
-        return [self._getcustomclass("Instance")(name="()", parent=self)]
-
-    def setup(self):
-        setup_class = xunitsetup(self.obj, 'setup_class')
-        if setup_class is not None:
-            setup_class = getattr(setup_class, 'im_func', setup_class)
-            setup_class = getattr(setup_class, '__func__', setup_class)
-            setup_class(self.obj)
-
-        fin_class = getattr(self.obj, 'teardown_class', None)
-        if fin_class is not None:
-            fin_class = getattr(fin_class, 'im_func', fin_class)
-            fin_class = getattr(fin_class, '__func__', fin_class)
-            self.addfinalizer(lambda: fin_class(self.obj))
-
-class Instance(PyCollector):
-    def _getobj(self):
-        obj = self.parent.obj()
-        return obj
-
-    def collect(self):
-        self.session._fixturemanager.parsefactories(self)
-        return super(Instance, self).collect()
-
-    def newinstance(self):
-        self.obj = self._getobj()
-        return self.obj
-
-class FunctionMixin(PyobjMixin):
-    """ mixin for the code common to Function and Generator.
-    """
-
-    def setup(self):
-        """ perform setup for this test function. """
-        if hasattr(self, '_preservedparent'):
-            obj = self._preservedparent
-        elif isinstance(self.parent, Instance):
-            obj = self.parent.newinstance()
-            self.obj = self._getobj()
-        else:
-            obj = self.parent.obj
-        if inspect.ismethod(self.obj):
-            setup_name = 'setup_method'
-            teardown_name = 'teardown_method'
-        else:
-            setup_name = 'setup_function'
-            teardown_name = 'teardown_function'
-        setup_func_or_method = xunitsetup(obj, setup_name)
-        if setup_func_or_method is not None:
-            setup_func_or_method(self.obj)
-        fin = getattr(obj, teardown_name, None)
-        if fin is not None:
-            self.addfinalizer(lambda: fin(self.obj))
-
-    def _prunetraceback(self, excinfo):
-        if hasattr(self, '_obj') and not self.config.option.fulltrace:
-            code = _pytest._code.Code(get_real_func(self.obj))
-            path, firstlineno = code.path, code.firstlineno
-            traceback = excinfo.traceback
-            ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
-            if ntraceback == traceback:
-                ntraceback = ntraceback.cut(path=path)
-                if ntraceback == traceback:
-                    #ntraceback = ntraceback.cut(excludepath=cutdir2)
-                    ntraceback = ntraceback.filter(filter_traceback)
-                    if not ntraceback:
-                        ntraceback = traceback
-
-            excinfo.traceback = ntraceback.filter()
-            # issue364: mark all but first and last frames to
-            # only show a single-line message for each frame
-            if self.config.option.tbstyle == "auto":
-                if len(excinfo.traceback) > 2:
-                    for entry in excinfo.traceback[1:-1]:
-                        entry.set_repr_style('short')
-
-    def _repr_failure_py(self, excinfo, style="long"):
-        if excinfo.errisinstance(pytest.fail.Exception):
-            if not excinfo.value.pytrace:
-                return py._builtin._totext(excinfo.value)
-        return super(FunctionMixin, self)._repr_failure_py(excinfo,
-            style=style)
-
-    def repr_failure(self, excinfo, outerr=None):
-        assert outerr is None, "XXX outerr usage is deprecated"
-        style = self.config.option.tbstyle
-        if style == "auto":
-            style = "long"
-        return self._repr_failure_py(excinfo, style=style)
-
-
-class Generator(FunctionMixin, PyCollector):
-    def collect(self):
-        # test generators are seen as collectors but they also
-        # invoke setup/teardown on popular request
-        # (induced by the common "test_*" naming shared with normal tests)
-        self.session._setupstate.prepare(self)
-        # see FunctionMixin.setup and test_setupstate_is_preserved_134
-        self._preservedparent = self.parent.obj
-        l = []
-        seen = {}
-        for i, x in enumerate(self.obj()):
-            name, call, args = self.getcallargs(x)
-            if not callable(call):
-                raise TypeError("%r yielded non callable test %r" %(self.obj, call,))
-            if name is None:
-                name = "[%d]" % i
-            else:
-                name = "['%s']" % name
-            if name in seen:
-                raise ValueError("%r generated tests with non-unique name %r" %(self, name))
-            seen[name] = True
-            l.append(self.Function(name, self, args=args, callobj=call))
-        return l
-
-    def getcallargs(self, obj):
-        if not isinstance(obj, (tuple, list)):
-            obj = (obj,)
-        # explict naming
-        if isinstance(obj[0], py.builtin._basestring):
-            name = obj[0]
-            obj = obj[1:]
-        else:
-            name = None
-        call, args = obj[0], obj[1:]
-        return name, call, args
-
-
-def hasinit(obj):
-    init = getattr(obj, '__init__', None)
-    if init:
-        if init != object.__init__:
-            return True
-
-
-
-def fillfixtures(function):
-    """ fill missing funcargs for a test function. """
-    try:
-        request = function._request
-    except AttributeError:
-        # XXX this special code path is only expected to execute
-        # with the oejskit plugin.  It uses classes with funcargs
-        # and we thus have to work a bit to allow this.
-        fm = function.session._fixturemanager
-        fi = fm.getfixtureinfo(function.parent, function.obj, None)
-        function._fixtureinfo = fi
-        request = function._request = FixtureRequest(function)
-        request._fillfixtures()
-        # prune out funcargs for jstests
-        newfuncargs = {}
-        for name in fi.argnames:
-            newfuncargs[name] = function.funcargs[name]
-        function.funcargs = newfuncargs
-    else:
-        request._fillfixtures()
-
-
-_notexists = object()
-
-class CallSpec2(object):
-    def __init__(self, metafunc):
-        self.metafunc = metafunc
-        self.funcargs = {}
-        self._idlist = []
-        self.params = {}
-        self._globalid = _notexists
-        self._globalid_args = set()
-        self._globalparam = _notexists
-        self._arg2scopenum = {}  # used for sorting parametrized resources
-        self.keywords = {}
-        self.indices = {}
-
-    def copy(self, metafunc):
-        cs = CallSpec2(self.metafunc)
-        cs.funcargs.update(self.funcargs)
-        cs.params.update(self.params)
-        cs.keywords.update(self.keywords)
-        cs.indices.update(self.indices)
-        cs._arg2scopenum.update(self._arg2scopenum)
-        cs._idlist = list(self._idlist)
-        cs._globalid = self._globalid
-        cs._globalid_args = self._globalid_args
-        cs._globalparam = self._globalparam
-        return cs
-
-    def _checkargnotcontained(self, arg):
-        if arg in self.params or arg in self.funcargs:
-            raise ValueError("duplicate %r" %(arg,))
-
-    def getparam(self, name):
-        try:
-            return self.params[name]
-        except KeyError:
-            if self._globalparam is _notexists:
-                raise ValueError(name)
-            return self._globalparam
-
-    @property
-    def id(self):
-        return "-".join(map(str, filter(None, self._idlist)))
-
-    def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum,
-                 param_index):
-        for arg,val in zip(argnames, valset):
-            self._checkargnotcontained(arg)
-            valtype_for_arg = valtypes[arg]
-            getattr(self, valtype_for_arg)[arg] = val
-            self.indices[arg] = param_index
-            self._arg2scopenum[arg] = scopenum
-            if val is _notexists:
-                self._emptyparamspecified = True
-        self._idlist.append(id)
-        self.keywords.update(keywords)
-
-    def setall(self, funcargs, id, param):
-        for x in funcargs:
-            self._checkargnotcontained(x)
-        self.funcargs.update(funcargs)
-        if id is not _notexists:
-            self._idlist.append(id)
-        if param is not _notexists:
-            assert self._globalparam is _notexists
-            self._globalparam = param
-        for arg in funcargs:
-            self._arg2scopenum[arg] = scopenum_function
-
-
-class FuncargnamesCompatAttr:
-    """ helper class so that Metafunc, Function and FixtureRequest
-    don't need to each define the "funcargnames" compatibility attribute.
-    """
-    @property
-    def funcargnames(self):
-        """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
-        return self.fixturenames
-
-class Metafunc(FuncargnamesCompatAttr):
-    """
-    Metafunc objects are passed to the ``pytest_generate_tests`` hook.
-    They help to inspect a test function and to generate tests according to
-    test configuration or values specified in the class or module where a
-    test function is defined.
-
-    :ivar fixturenames: set of fixture names required by the test function
-
-    :ivar function: underlying python test function
-
-    :ivar cls: class object where the test function is defined in or ``None``.
-
-    :ivar module: the module object where the test function is defined in.
-
-    :ivar config: access to the :class:`_pytest.config.Config` object for the
-        test session.
-
-    :ivar funcargnames:
-        .. deprecated:: 2.3
-            Use ``fixturenames`` instead.
-    """
-    def __init__(self, function, fixtureinfo, config, cls=None, module=None):
-        self.config = config
-        self.module = module
-        self.function = function
-        self.fixturenames = fixtureinfo.names_closure
-        self._arg2fixturedefs = fixtureinfo.name2fixturedefs
-        self.cls = cls
-        self._calls = []
-        self._ids = py.builtin.set()
-
-    def parametrize(self, argnames, argvalues, indirect=False, ids=None,
-        scope=None):
-        """ Add new invocations to the underlying test function using the list
-        of argvalues for the given argnames.  Parametrization is performed
-        during the collection phase.  If you need to setup expensive resources
-        see about setting indirect to do it rather at test setup time.
-
-        :arg argnames: a comma-separated string denoting one or more argument
-                       names, or a list/tuple of argument strings.
-
-        :arg argvalues: The list of argvalues determines how often a
-            test is invoked with different argument values.  If only one
-            argname was specified argvalues is a list of values.  If N
-            argnames were specified, argvalues must be a list of N-tuples,
-            where each tuple-element specifies a value for its respective
-            argname.
-
-        :arg indirect: The list of argnames or boolean. A list of arguments'
-            names (subset of argnames). If True the list contains all names from
-            the argnames. Each argvalue corresponding to an argname in this list will
-            be passed as request.param to its respective argname fixture
-            function so that it can perform more expensive setups during the
-            setup phase of a test rather than at collection time.
-
-        :arg ids: list of string ids, or a callable.
-            If strings, each is corresponding to the argvalues so that they are
-            part of the test id.
-            If callable, it should take one argument (a single argvalue) and return
-            a string or return None. If None, the automatically generated id for that
-            argument will be used.
-            If no ids are provided they will be generated automatically from
-            the argvalues.
-
-        :arg scope: if specified it denotes the scope of the parameters.
-            The scope is used for grouping tests by parameter instances.
-            It will also override any fixture-function defined scope, allowing
-            to set a dynamic scope using test context or configuration.
-        """
-
-        # individual parametrized argument sets can be wrapped in a series
-        # of markers in which case we unwrap the values and apply the mark
-        # at Function init
-        newkeywords = {}
-        unwrapped_argvalues = []
-        for i, argval in enumerate(argvalues):
-            while isinstance(argval, MarkDecorator):
-                newmark = MarkDecorator(argval.markname,
-                                        argval.args[:-1], argval.kwargs)
-                newmarks = newkeywords.setdefault(i, {})
-                newmarks[newmark.markname] = newmark
-                argval = argval.args[-1]
-            unwrapped_argvalues.append(argval)
-        argvalues = unwrapped_argvalues
-
-        if not isinstance(argnames, (tuple, list)):
-            argnames = [x.strip() for x in argnames.split(",") if x.strip()]
-            if len(argnames) == 1:
-                argvalues = [(val,) for val in argvalues]
-        if not argvalues:
-            argvalues = [(_notexists,) * len(argnames)]
-
-        if scope is None:
-            scope = "function"
-        scopenum = scopes.index(scope)
-        valtypes = {}
-        for arg in argnames:
-            if arg not in self.fixturenames:
-                raise ValueError("%r uses no fixture %r" %(self.function, arg))
-
-        if indirect is True:
-            valtypes = dict.fromkeys(argnames, "params")
-        elif indirect is False:
-            valtypes = dict.fromkeys(argnames, "funcargs")
-        elif isinstance(indirect, (tuple, list)):
-            valtypes = dict.fromkeys(argnames, "funcargs")
-            for arg in indirect:
-                if arg not in argnames:
-                    raise ValueError("indirect given to %r: fixture %r doesn't exist" %(
-                                     self.function, arg))
-                valtypes[arg] = "params"
-        idfn = None
-        if callable(ids):
-            idfn = ids
-            ids = None
-        if ids and len(ids) != len(argvalues):
-            raise ValueError('%d tests specified with %d ids' %(
-                             len(argvalues), len(ids)))
-        if not ids:
-            ids = idmaker(argnames, argvalues, idfn)
-        newcalls = []
-        for callspec in self._calls or [CallSpec2(self)]:
-            for param_index, valset in enumerate(argvalues):
-                assert len(valset) == len(argnames)
-                newcallspec = callspec.copy(self)
-                newcallspec.setmulti(valtypes, argnames, valset, ids[param_index],
-                                     newkeywords.get(param_index, {}), scopenum,
-                                     param_index)
-                newcalls.append(newcallspec)
-        self._calls = newcalls
-
-    def addcall(self, funcargs=None, id=_notexists, param=_notexists):
-        """ (deprecated, use parametrize) Add a new call to the underlying
-        test function during the collection phase of a test run.  Note that
-        request.addcall() is called during the test collection phase prior and
-        independently to actual test execution.  You should only use addcall()
-        if you need to specify multiple arguments of a test function.
-
-        :arg funcargs: argument keyword dictionary used when invoking
-            the test function.
-
-        :arg id: used for reporting and identification purposes.  If you
-            don't supply an `id` an automatic unique id will be generated.
-
-        :arg param: a parameter which will be exposed to a later fixture function
-            invocation through the ``request.param`` attribute.
-        """
-        assert funcargs is None or isinstance(funcargs, dict)
-        if funcargs is not None:
-            for name in funcargs:
-                if name not in self.fixturenames:
-                    pytest.fail("funcarg %r not used in this function." % name)
-        else:
-            funcargs = {}
-        if id is None:
-            raise ValueError("id=None not allowed")
-        if id is _notexists:
-            id = len(self._calls)
-        id = str(id)
-        if id in self._ids:
-            raise ValueError("duplicate id %r" % id)
-        self._ids.add(id)
-
-        cs = CallSpec2(self)
-        cs.setall(funcargs, id, param)
-        self._calls.append(cs)
-
-
-if _PY3:
-    import codecs
-
-    def _escape_bytes(val):
-        """
-        If val is pure ascii, returns it as a str(), otherwise escapes
-        into a sequence of escaped bytes:
-        b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
-
-        note:
-           the obvious "v.decode('unicode-escape')" will return
-           valid utf-8 unicode if it finds them in the string, but we
-           want to return escaped bytes for any byte, even if they match
-           a utf-8 string.
-        """
-        if val:
-            # source: http://goo.gl/bGsnwC
-            encoded_bytes, _ = codecs.escape_encode(val)
-            return encoded_bytes.decode('ascii')
-        else:
-            # empty bytes crashes codecs.escape_encode (#1087)
-            return ''
-else:
-    def _escape_bytes(val):
-        """
-        In py2 bytes and str are the same type, so return it unchanged if it
-        is a full ascii string, otherwise escape it into its binary form.
-        """
-        try:
-            return val.decode('ascii')
-        except UnicodeDecodeError:
-            return val.encode('string-escape')
-
-
-def _idval(val, argname, idx, idfn):
-    if idfn:
-        try:
-            s = idfn(val)
-            if s:
-                return s
-        except Exception:
-            pass
-
-    if isinstance(val, bytes):
-        return _escape_bytes(val)
-    elif isinstance(val, (float, int, str, bool, NoneType)):
-        return str(val)
-    elif isinstance(val, REGEX_TYPE):
-        return _escape_bytes(val.pattern) if isinstance(val.pattern, bytes) else val.pattern
-    elif enum is not None and isinstance(val, enum.Enum):
-        return str(val)
-    elif isclass(val) and hasattr(val, '__name__'):
-        return val.__name__
-    elif _PY2 and isinstance(val, unicode):
-        # special case for python 2: if a unicode string is
-        # convertible to ascii, return it as an str() object instead
-        try:
-            return str(val)
-        except UnicodeError:
-            # fallthrough
-            pass
-    return str(argname)+str(idx)
-
-def _idvalset(idx, valset, argnames, idfn):
-    this_id = [_idval(val, argname, idx, idfn)
-               for val, argname in zip(valset, argnames)]
-    return "-".join(this_id)
-
-def idmaker(argnames, argvalues, idfn=None):
-    ids = [_idvalset(valindex, valset, argnames, idfn)
-           for valindex, valset in enumerate(argvalues)]
-    if len(set(ids)) < len(ids):
-        # user may have provided a bad idfn which means the ids are not unique
-        ids = [str(i) + testid for i, testid in enumerate(ids)]
-    return ids
-
-def showfixtures(config):
-    from _pytest.main import wrap_session
-    return wrap_session(config, _showfixtures_main)
-
-def _showfixtures_main(config, session):
-    import _pytest.config
-    session.perform_collect()
-    curdir = py.path.local()
-    tw = _pytest.config.create_terminal_writer(config)
-    verbose = config.getvalue("verbose")
-
-    fm = session._fixturemanager
-
-    available = []
-    for argname, fixturedefs in fm._arg2fixturedefs.items():
-        assert fixturedefs is not None
-        if not fixturedefs:
-            continue
-        fixturedef = fixturedefs[-1]
-        loc = getlocation(fixturedef.func, curdir)
-        available.append((len(fixturedef.baseid),
-                          fixturedef.func.__module__,
-                          curdir.bestrelpath(loc),
-                          fixturedef.argname, fixturedef))
-
-    available.sort()
-    currentmodule = None
-    for baseid, module, bestrel, argname, fixturedef in available:
-        if currentmodule != module:
-            if not module.startswith("_pytest."):
-                tw.line()
-                tw.sep("-", "fixtures defined from %s" %(module,))
-                currentmodule = module
-        if verbose <= 0 and argname[0] == "_":
-            continue
-        if verbose > 0:
-            funcargspec = "%s -- %s" %(argname, bestrel,)
-        else:
-            funcargspec = argname
-        tw.line(funcargspec, green=True)
-        loc = getlocation(fixturedef.func, curdir)
-        doc = fixturedef.func.__doc__ or ""
-        if doc:
-            for line in doc.strip().split("\n"):
-                tw.line("    " + line.strip())
-        else:
-            tw.line("    %s: no docstring available" %(loc,),
-                red=True)
-
-def getlocation(function, curdir):
-    import inspect
-    fn = py.path.local(inspect.getfile(function))
-    lineno = py.builtin._getcode(function).co_firstlineno
-    if fn.relto(curdir):
-        fn = fn.relto(curdir)
-    return "%s:%d" %(fn, lineno+1)
-
-# builtin pytest.raises helper
-
-def raises(expected_exception, *args, **kwargs):
-    """ assert that a code block/function call raises ``expected_exception``
-    and raise a failure exception otherwise.
-
-    This helper produces a ``ExceptionInfo()`` object (see below).
-
-    If using Python 2.5 or above, you may use this function as a
-    context manager::
-
-        >>> with raises(ZeroDivisionError):
-        ...    1/0
-
-    .. note::
-
-       When using ``pytest.raises`` as a context manager, it's worthwhile to
-       note that normal context manager rules apply and that the exception
-       raised *must* be the final line in the scope of the context manager.
-       Lines of code after that, within the scope of the context manager will
-       not be executed. For example::
-
-           >>> with raises(OSError) as exc_info:
-                   assert 1 == 1  # this will execute as expected
-                   raise OSError(errno.EEXISTS, 'directory exists')
-                   assert exc_info.value.errno == errno.EEXISTS  # this will not execute
-
-       Instead, the following approach must be taken (note the difference in
-       scope)::
-
-           >>> with raises(OSError) as exc_info:
-                   assert 1 == 1  # this will execute as expected
-                   raise OSError(errno.EEXISTS, 'directory exists')
-
-               assert exc_info.value.errno == errno.EEXISTS  # this will now execute
-
-    Or you can specify a callable by passing a to-be-called lambda::
-
-        >>> raises(ZeroDivisionError, lambda: 1/0)
-        <ExceptionInfo ...>
-
-    or you can specify an arbitrary callable with arguments::
-
-        >>> def f(x): return 1/x
-        ...
-        >>> raises(ZeroDivisionError, f, 0)
-        <ExceptionInfo ...>
-        >>> raises(ZeroDivisionError, f, x=0)
-        <ExceptionInfo ...>
-
-    A third possibility is to use a string to be executed::
-
-        >>> raises(ZeroDivisionError, "f(0)")
-        <ExceptionInfo ...>
-
-    .. autoclass:: _pytest._code.ExceptionInfo
-        :members:
-
-    .. note::
-        Similar to caught exception objects in Python, explicitly clearing
-        local references to returned ``ExceptionInfo`` objects can
-        help the Python interpreter speed up its garbage collection.
-
-        Clearing those references breaks a reference cycle
-        (``ExceptionInfo`` --> caught exception --> frame stack raising
-        the exception --> current frame stack --> local variables -->
-        ``ExceptionInfo``) which makes Python keep all objects referenced
-        from that cycle (including all local variables in the current
-        frame) alive until the next cyclic garbage collection run. See the
-        official Python ``try`` statement documentation for more detailed
-        information.
-
-    """
-    __tracebackhide__ = True
-    if expected_exception is AssertionError:
-        # we want to catch a AssertionError
-        # replace our subclass with the builtin one
-        # see https://github.com/pytest-dev/pytest/issues/176
-        from _pytest.assertion.util import BuiltinAssertionError \
-            as expected_exception
-    msg = ("exceptions must be old-style classes or"
-           " derived from BaseException, not %s")
-    if isinstance(expected_exception, tuple):
-        for exc in expected_exception:
-            if not isclass(exc):
-                raise TypeError(msg % type(exc))
-    elif not isclass(expected_exception):
-        raise TypeError(msg % type(expected_exception))
-
-    if not args:
-        return RaisesContext(expected_exception)
-    elif isinstance(args[0], str):
-        code, = args
-        assert isinstance(code, str)
-        frame = sys._getframe(1)
-        loc = frame.f_locals.copy()
-        loc.update(kwargs)
-        #print "raises frame scope: %r" % frame.f_locals
-        try:
-            code = _pytest._code.Source(code).compile()
-            py.builtin.exec_(code, frame.f_globals, loc)
-            # XXX didn'T mean f_globals == f_locals something special?
-            #     this is destroyed here ...
-        except expected_exception:
-            return _pytest._code.ExceptionInfo()
-    else:
-        func = args[0]
-        try:
-            func(*args[1:], **kwargs)
-        except expected_exception:
-            return _pytest._code.ExceptionInfo()
-    pytest.fail("DID NOT RAISE {0}".format(expected_exception))
-
-class RaisesContext(object):
-    def __init__(self, expected_exception):
-        self.expected_exception = expected_exception
-        self.excinfo = None
-
-    def __enter__(self):
-        self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
-        return self.excinfo
-
-    def __exit__(self, *tp):
-        __tracebackhide__ = True
-        if tp[0] is None:
-            pytest.fail("DID NOT RAISE")
-        if sys.version_info < (2, 7):
-            # py26: on __exit__() exc_value often does not contain the
-            # exception value.
-            # http://bugs.python.org/issue7853
-            if not isinstance(tp[1], BaseException):
-                exc_type, value, traceback = tp
-                tp = exc_type, exc_type(value), traceback
-        self.excinfo.__init__(tp)
-        return issubclass(self.excinfo.type, self.expected_exception)
-
-#
-#  the basic pytest Function item
-#
-
-class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
-    """ a Function Item is responsible for setting up and executing a
-    Python test function.
-    """
-    _genid = None
-    def __init__(self, name, parent, args=None, config=None,
-                 callspec=None, callobj=NOTSET, keywords=None, session=None,
-                 fixtureinfo=None):
-        super(Function, self).__init__(name, parent, config=config,
-                                       session=session)
-        self._args = args
-        if callobj is not NOTSET:
-            self.obj = callobj
-
-        self.keywords.update(self.obj.__dict__)
-        if callspec:
-            self.callspec = callspec
-            self.keywords.update(callspec.keywords)
-        if keywords:
-            self.keywords.update(keywords)
-
-        if fixtureinfo is None:
-            fixtureinfo = self.session._fixturemanager.getfixtureinfo(
-                self.parent, self.obj, self.cls,
-                funcargs=not self._isyieldedfunction())
-        self._fixtureinfo = fixtureinfo
-        self.fixturenames = fixtureinfo.names_closure
-        self._initrequest()
-
-    def _initrequest(self):
-        self.funcargs = {}
-        if self._isyieldedfunction():
-            assert not hasattr(self, "callspec"), (
-                "yielded functions (deprecated) cannot have funcargs")
-        else:
-            if hasattr(self, "callspec"):
-                callspec = self.callspec
-                assert not callspec.funcargs
-                self._genid = callspec.id
-                if hasattr(callspec, "param"):
-                    self.param = callspec.param
-        self._request = FixtureRequest(self)
-
-    @property
-    def function(self):
-        "underlying python 'function' object"
-        return getattr(self.obj, 'im_func', self.obj)
-
-    def _getobj(self):
-        name = self.name
-        i = name.find("[") # parametrization
-        if i != -1:
-            name = name[:i]
-        return getattr(self.parent.obj, name)
-
-    @property
-    def _pyfuncitem(self):
-        "(compatonly) for code expecting pytest-2.2 style request objects"
-        return self
-
-    def _isyieldedfunction(self):
-        return getattr(self, "_args", None) is not None
-
-    def runtest(self):
-        """ execute the underlying test function. """
-        self.ihook.pytest_pyfunc_call(pyfuncitem=self)
-
-    def setup(self):
-        # check if parametrization happend with an empty list
-        try:
-            self.callspec._emptyparamspecified
-        except AttributeError:
-            pass
-        else:
-            fs, lineno = self._getfslineno()
-            pytest.skip("got empty parameter set, function %s at %s:%d" %(
-                self.function.__name__, fs, lineno))
-        super(Function, self).setup()
-        fillfixtures(self)
-
-
-scope2props = dict(session=())
-scope2props["module"] = ("fspath", "module")
-scope2props["class"] = scope2props["module"] + ("cls",)
-scope2props["instance"] = scope2props["class"] + ("instance", )
-scope2props["function"] = scope2props["instance"] + ("function", "keywords")
-
-def scopeproperty(name=None, doc=None):
-    def decoratescope(func):
-        scopename = name or func.__name__
-        def provide(self):
-            if func.__name__ in scope2props[self.scope]:
-                return func(self)
-            raise AttributeError("%s not available in %s-scoped context" % (
-                scopename, self.scope))
-        return property(provide, None, None, func.__doc__)
-    return decoratescope
-
-
-class FixtureRequest(FuncargnamesCompatAttr):
-    """ A request for a fixture from a test or fixture function.
-
-    A request object gives access to the requesting test context
-    and has an optional ``param`` attribute in case
-    the fixture is parametrized indirectly.
-    """
-
-    def __init__(self, pyfuncitem):
-        self._pyfuncitem = pyfuncitem
-        #: fixture for which this request is being performed
-        self.fixturename = None
-        #: Scope string, one of "function", "cls", "module", "session"
-        self.scope = "function"
-        self._funcargs  = {}
-        self._fixturedefs = {}
-        fixtureinfo = pyfuncitem._fixtureinfo
-        self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
-        self._arg2index = {}
-        self.fixturenames = fixtureinfo.names_closure
-        self._fixturemanager = pyfuncitem.session._fixturemanager
-
-    @property
-    def node(self):
-        """ underlying collection node (depends on current request scope)"""
-        return self._getscopeitem(self.scope)
-
-
-    def _getnextfixturedef(self, argname):
-        fixturedefs = self._arg2fixturedefs.get(argname, None)
-        if fixturedefs is None:
-            # we arrive here because of a  a dynamic call to
-            # getfuncargvalue(argname) usage which was naturally
-            # not known at parsing/collection time
-            fixturedefs = self._fixturemanager.getfixturedefs(
-                            argname, self._pyfuncitem.parent.nodeid)
-            self._arg2fixturedefs[argname] = fixturedefs
-        # fixturedefs list is immutable so we maintain a decreasing index
-        index = self._arg2index.get(argname, 0) - 1
-        if fixturedefs is None or (-index > len(fixturedefs)):
-            raise FixtureLookupError(argname, self)
-        self._arg2index[argname] = index
-        return fixturedefs[index]
-
-    @property
-    def config(self):
-        """ the pytest config object associated with this request. """
-        return self._pyfuncitem.config
-
-
-    @scopeproperty()
-    def function(self):
-        """ test function object if the request has a per-function scope. """
-        return self._pyfuncitem.obj
-
-    @scopeproperty("class")
-    def cls(self):
-        """ class (can be None) where the test function was collected. """
-        clscol = self._pyfuncitem.getparent(pytest.Class)
-        if clscol:
-            return clscol.obj
-
-    @property
-    def instance(self):
-        """ instance (can be None) on which test function was collected. """
-        # unittest support hack, see _pytest.unittest.TestCaseFunction
-        try:
-            return self._pyfuncitem._testcase
-        except AttributeError:
-            function = getattr(self, "function", None)
-            if function is not None:
-                return py.builtin._getimself(function)
-
-    @scopeproperty()
-    def module(self):
-        """ python module object where the test function was collected. """
-        return self._pyfuncitem.getparent(pytest.Module).obj
-
-    @scopeproperty()
-    def fspath(self):
-        """ the file system path of the test module which collected this test. """
-        return self._pyfuncitem.fspath
-
-    @property
-    def keywords(self):
-        """ keywords/markers dictionary for the underlying node. """
-        return self.node.keywords
-
-    @property
-    def session(self):
-        """ pytest session object. """
-        return self._pyfuncitem.session
-
-    def addfinalizer(self, finalizer):
-        """ add finalizer/teardown function to be called after the
-        last test within the requesting test context finished
-        execution. """
-        # XXX usually this method is shadowed by fixturedef specific ones
-        self._addfinalizer(finalizer, scope=self.scope)
-
-    def _addfinalizer(self, finalizer, scope):
-        colitem = self._getscopeitem(scope)
-        self._pyfuncitem.session._setupstate.addfinalizer(
-            finalizer=finalizer, colitem=colitem)
-
-    def applymarker(self, marker):
-        """ Apply a marker to a single test function invocation.
-        This method is useful if you don't want to have a keyword/marker
-        on all function invocations.
-
-        :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
-            created by a call to ``pytest.mark.NAME(...)``.
-        """
-        try:
-            self.node.keywords[marker.markname] = marker
-        except AttributeError:
-            raise ValueError(marker)
-
-    def raiseerror(self, msg):
-        """ raise a FixtureLookupError with the given message. """
-        raise self._fixturemanager.FixtureLookupError(None, self, msg)
-
-    def _fillfixtures(self):
-        item = self._pyfuncitem
-        fixturenames = getattr(item, "fixturenames", self.fixturenames)
-        for argname in fixturenames:
-            if argname not in item.funcargs:
-                item.funcargs[argname] = self.getfuncargvalue(argname)
-
-    def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
-        """ (deprecated) Return a testing resource managed by ``setup`` &
-        ``teardown`` calls.  ``scope`` and ``extrakey`` determine when the
-        ``teardown`` function will be called so that subsequent calls to
-        ``setup`` would recreate the resource.  With pytest-2.3 you often
-        do not need ``cached_setup()`` as you can directly declare a scope
-        on a fixture function and register a finalizer through
-        ``request.addfinalizer()``.
-
-        :arg teardown: function receiving a previously setup resource.
-        :arg setup: a no-argument function creating a resource.
-        :arg scope: a string value out of ``function``, ``class``, ``module``
-            or ``session`` indicating the caching lifecycle of the resource.
-        :arg extrakey: added to internal caching key of (funcargname, scope).
-        """
-        if not hasattr(self.config, '_setupcache'):
-            self.config._setupcache = {} # XXX weakref?
-        cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
-        cache = self.config._setupcache
-        try:
-            val = cache[cachekey]
-        except KeyError:
-            self._check_scope(self.fixturename, self.scope, scope)
-            val = setup()
-            cache[cachekey] = val
-            if teardown is not None:
-                def finalizer():
-                    del cache[cachekey]
-                    teardown(val)
-                self._addfinalizer(finalizer, scope=scope)
-        return val
-
-    def getfuncargvalue(self, argname):
-        """ Dynamically retrieve a named fixture function argument.
-
-        As of pytest-2.3, it is easier and usually better to access other
-        fixture values by stating it as an input argument in the fixture
-        function.  If you only can decide about using another fixture at test
-        setup time, you may use this function to retrieve it inside a fixture
-        function body.
-        """
-        return self._get_active_fixturedef(argname).cached_result[0]
-
-    def _get_active_fixturedef(self, argname):
-        try:
-            return self._fixturedefs[argname]
-        except KeyError:
-            try:
-                fixturedef = self._getnextfixturedef(argname)
-            except FixtureLookupError:
-                if argname == "request":
-                    class PseudoFixtureDef:
-                        cached_result = (self, [0], None)
-                        scope = "function"
-                    return PseudoFixtureDef
-                raise
-        # remove indent to prevent the python3 exception
-        # from leaking into the call
-        result = self._getfuncargvalue(fixturedef)
-        self._funcargs[argname] = result
-        self._fixturedefs[argname] = fixturedef
-        return fixturedef
-
-    def _get_fixturestack(self):
-        current = self
-        l = []
-        while 1:
-            fixturedef = getattr(current, "_fixturedef", None)
-            if fixturedef is None:
-                l.reverse()
-                return l
-            l.append(fixturedef)
-            current = current._parent_request
-
-    def _getfuncargvalue(self, fixturedef):
-        # prepare a subrequest object before calling fixture function
-        # (latter managed by fixturedef)
-        argname = fixturedef.argname
-        funcitem = self._pyfuncitem
-        scope = fixturedef.scope
-        try:
-            param = funcitem.callspec.getparam(argname)
-        except (AttributeError, ValueError):
-            param = NOTSET
-            param_index = 0
-        else:
-            # indices might not be set if old-style metafunc.addcall() was used
-            param_index = funcitem.callspec.indices.get(argname, 0)
-            # if a parametrize invocation set a scope it will override
-            # the static scope defined with the fixture function
-            paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
-            if paramscopenum is not None:
-                scope = scopes[paramscopenum]
-
-        subrequest = SubRequest(self, scope, param, param_index, fixturedef)
-
-        # check if a higher-level scoped fixture accesses a lower level one
-        subrequest._check_scope(argname, self.scope, scope)
-
-        # clear sys.exc_info before invoking the fixture (python bug?)
-        # if its not explicitly cleared it will leak into the call
-        exc_clear()
-        try:
-            # call the fixture function
-            val = fixturedef.execute(request=subrequest)
-        finally:
-            # if fixture function failed it might have registered finalizers
-            self.session._setupstate.addfinalizer(fixturedef.finish,
-                                                  subrequest.node)
-        return val
-
-    def _check_scope(self, argname, invoking_scope, requested_scope):
-        if argname == "request":
-            return
-        if scopemismatch(invoking_scope, requested_scope):
-            # try to report something helpful
-            lines = self._factorytraceback()
-            pytest.fail("ScopeMismatch: You tried to access the %r scoped "
-                "fixture %r with a %r scoped request object, "
-                "involved factories\n%s" %(
-                (requested_scope, argname, invoking_scope, "\n".join(lines))),
-                pytrace=False)
-
-    def _factorytraceback(self):
-        lines = []
-        for fixturedef in self._get_fixturestack():
-            factory = fixturedef.func
-            fs, lineno = getfslineno(factory)
-            p = self._pyfuncitem.session.fspath.bestrelpath(fs)
-            args = _format_args(factory)
-            lines.append("%s:%d:  def %s%s" %(
-                p, lineno, factory.__name__, args))
-        return lines
-
-    def _getscopeitem(self, scope):
-        if scope == "function":
-            # this might also be a non-function Item despite its attribute name
-            return self._pyfuncitem
-        node = get_scope_node(self._pyfuncitem, scope)
-        if node is None and scope == "class":
-            # fallback to function item itself
-            node = self._pyfuncitem
-        assert node
-        return node
-
-    def __repr__(self):
-        return "<FixtureRequest for %r>" %(self.node)
-
-
-class SubRequest(FixtureRequest):
-    """ a sub request for handling getting a fixture from a
-    test function/fixture. """
-    def __init__(self, request, scope, param, param_index, fixturedef):
-        self._parent_request = request
-        self.fixturename = fixturedef.argname
-        if param is not NOTSET:
-            self.param = param
-        self.param_index = param_index
-        self.scope = scope
-        self._fixturedef = fixturedef
-        self.addfinalizer = fixturedef.addfinalizer
-        self._pyfuncitem = request._pyfuncitem
-        self._funcargs  = request._funcargs
-        self._fixturedefs = request._fixturedefs
-        self._arg2fixturedefs = request._arg2fixturedefs
-        self._arg2index = request._arg2index
-        self.fixturenames = request.fixturenames
-        self._fixturemanager = request._fixturemanager
-
-    def __repr__(self):
-        return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
-
-
-class ScopeMismatchError(Exception):
-    """ A fixture function tries to use a different fixture function which
-    which has a lower scope (e.g. a Session one calls a function one)
-    """
-
-scopes = "session module class function".split()
-scopenum_function = scopes.index("function")
-def scopemismatch(currentscope, newscope):
-    return scopes.index(newscope) > scopes.index(currentscope)
-
-
-class FixtureLookupError(LookupError):
-    """ could not return a requested Fixture (missing or invalid). """
-    def __init__(self, argname, request, msg=None):
-        self.argname = argname
-        self.request = request
-        self.fixturestack = request._get_fixturestack()
-        self.msg = msg
-
-    def formatrepr(self):
-        tblines = []
-        addline = tblines.append
-        stack = [self.request._pyfuncitem.obj]
-        stack.extend(map(lambda x: x.func, self.fixturestack))
-        msg = self.msg
-        if msg is not None:
-            # the last fixture raise an error, let's present
-            # it at the requesting side
-            stack = stack[:-1]
-        for function in stack:
-            fspath, lineno = getfslineno(function)
-            try:
-                lines, _ = inspect.getsourcelines(get_real_func(function))
-            except (IOError, IndexError):
-                error_msg = "file %s, line %s: source code not available"
-                addline(error_msg % (fspath, lineno+1))
-            else:
-                addline("file %s, line %s" % (fspath, lineno+1))
-                for i, line in enumerate(lines):
-                    line = line.rstrip()
-                    addline("  " + line)
-                    if line.lstrip().startswith('def'):
-                        break
-
-        if msg is None:
-            fm = self.request._fixturemanager
-            available = []
-            for name, fixturedef in fm._arg2fixturedefs.items():
-                parentid = self.request._pyfuncitem.parent.nodeid
-                faclist = list(fm._matchfactories(fixturedef, parentid))
-                if faclist:
-                    available.append(name)
-            msg = "fixture %r not found" % (self.argname,)
-            msg += "\n available fixtures: %s" %(", ".join(available),)
-            msg += "\n use 'py.test --fixtures [testpath]' for help on them."
-
-        return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
-
-class FixtureLookupErrorRepr(TerminalRepr):
-    def __init__(self, filename, firstlineno, tblines, errorstring, argname):
-        self.tblines = tblines
-        self.errorstring = errorstring
-        self.filename = filename
-        self.firstlineno = firstlineno
-        self.argname = argname
-
-    def toterminal(self, tw):
-        #tw.line("FixtureLookupError: %s" %(self.argname), red=True)
-        for tbline in self.tblines:
-            tw.line(tbline.rstrip())
-        for line in self.errorstring.split("\n"):
-            tw.line("        " + line.strip(), red=True)
-        tw.line()
-        tw.line("%s:%d" % (self.filename, self.firstlineno+1))
-
-class FixtureManager:
-    """
-    pytest fixtures definitions and information is stored and managed
-    from this class.
-
-    During collection fm.parsefactories() is called multiple times to parse
-    fixture function definitions into FixtureDef objects and internal
-    data structures.
-
-    During collection of test functions, metafunc-mechanics instantiate
-    a FuncFixtureInfo object which is cached per node/func-name.
-    This FuncFixtureInfo object is later retrieved by Function nodes
-    which themselves offer a fixturenames attribute.
-
-    The FuncFixtureInfo object holds information about fixtures and FixtureDefs
-    relevant for a particular function.  An initial list of fixtures is
-    assembled like this:
-
-    - ini-defined usefixtures
-    - autouse-marked fixtures along the collection chain up from the function
-    - usefixtures markers at module/class/function level
-    - test function funcargs
-
-    Subsequently the funcfixtureinfo.fixturenames attribute is computed
-    as the closure of the fixtures needed to setup the initial fixtures,
-    i. e. fixtures needed by fixture functions themselves are appended
-    to the fixturenames list.
-
-    Upon the test-setup phases all fixturenames are instantiated, retrieved
-    by a lookup of their FuncFixtureInfo.
-    """
-
-    _argprefix = "pytest_funcarg__"
-    FixtureLookupError = FixtureLookupError
-    FixtureLookupErrorRepr = FixtureLookupErrorRepr
-
-    def __init__(self, session):
-        self.session = session
-        self.config = session.config
-        self._arg2fixturedefs = {}
-        self._holderobjseen = set()
-        self._arg2finish = {}
-        self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
-        session.config.pluginmanager.register(self, "funcmanage")
-
-
-    def getfixtureinfo(self, node, func, cls, funcargs=True):
-        if funcargs and not hasattr(node, "nofuncargs"):
-            if cls is not None:
-                startindex = 1
-            else:
-                startindex = None
-            argnames = getfuncargnames(func, startindex)
-        else:
-            argnames = ()
-        usefixtures = getattr(func, "usefixtures", None)
-        initialnames = argnames
-        if usefixtures is not None:
-            initialnames = usefixtures.args + initialnames
-        fm = node.session._fixturemanager
-        names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
-                                                              node)
-        return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs)
-
-    def pytest_plugin_registered(self, plugin):
-        nodeid = None
-        try:
-            p = py.path.local(plugin.__file__)
-        except AttributeError:
-            pass
-        else:
-            # construct the base nodeid which is later used to check
-            # what fixtures are visible for particular tests (as denoted
-            # by their test id)
-            if p.basename.startswith("conftest.py"):
-                nodeid = p.dirpath().relto(self.config.rootdir)
-                if p.sep != "/":
-                    nodeid = nodeid.replace(p.sep, "/")
-        self.parsefactories(plugin, nodeid)
-
-    def _getautousenames(self, nodeid):
-        """ return a tuple of fixture names to be used. """
-        autousenames = []
-        for baseid, basenames in self._nodeid_and_autousenames:
-            if nodeid.startswith(baseid):
-                if baseid:
-                    i = len(baseid)
-                    nextchar = nodeid[i:i+1]
-                    if nextchar and nextchar not in ":/":
-                        continue
-                autousenames.extend(basenames)
-        # make sure autousenames are sorted by scope, scopenum 0 is session
-        autousenames.sort(
-            key=lambda x: self._arg2fixturedefs[x][-1].scopenum)
-        return autousenames
-
-    def getfixtureclosure(self, fixturenames, parentnode):
-        # collect the closure of all fixtures , starting with the given
-        # fixturenames as the initial set.  As we have to visit all
-        # factory definitions anyway, we also return a arg2fixturedefs
-        # mapping so that the caller can reuse it and does not have
-        # to re-discover fixturedefs again for each fixturename
-        # (discovering matching fixtures for a given name/node is expensive)
-
-        parentid = parentnode.nodeid
-        fixturenames_closure = self._getautousenames(parentid)
-        def merge(otherlist):
-            for arg in otherlist:
-                if arg not in fixturenames_closure:
-                    fixturenames_closure.append(arg)
-        merge(fixturenames)
-        arg2fixturedefs = {}
-        lastlen = -1
-        while lastlen != len(fixturenames_closure):
-            lastlen = len(fixturenames_closure)
-            for argname in fixturenames_closure:
-                if argname in arg2fixturedefs:
-                    continue
-                fixturedefs = self.getfixturedefs(argname, parentid)
-                if fixturedefs:
-                    arg2fixturedefs[argname] = fixturedefs
-                    merge(fixturedefs[-1].argnames)
-        return fixturenames_closure, arg2fixturedefs
-
-    def pytest_generate_tests(self, metafunc):
-        for argname in metafunc.fixturenames:
-            faclist = metafunc._arg2fixturedefs.get(argname)
-            if faclist:
-                fixturedef = faclist[-1]
-                if fixturedef.params is not None:
-                    func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
-                    # skip directly parametrized arguments
-                    argnames = func_params[0]
-                    if not isinstance(argnames, (tuple, list)):
-                        argnames = [x.strip() for x in argnames.split(",") if x.strip()]
-                    if argname not in func_params and argname not in argnames:
-                        metafunc.parametrize(argname, fixturedef.params,
-                                             indirect=True, scope=fixturedef.scope,
-                                             ids=fixturedef.ids)
-            else:
-                continue # will raise FixtureLookupError at setup time
-
-    def pytest_collection_modifyitems(self, items):
-        # separate parametrized setups
-        items[:] = reorder_items(items)
-
-    def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
-        if nodeid is not NOTSET:
-            holderobj = node_or_obj
-        else:
-            holderobj = node_or_obj.obj
-            nodeid = node_or_obj.nodeid
-        if holderobj in self._holderobjseen:
-            return
-        self._holderobjseen.add(holderobj)
-        autousenames = []
-        for name in dir(holderobj):
-            obj = getattr(holderobj, name, None)
-            # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
-            # or are "@pytest.fixture" marked
-            marker = getfixturemarker(obj)
-            if marker is None:
-                if not name.startswith(self._argprefix):
-                    continue
-                if not callable(obj):
-                    continue
-                marker = defaultfuncargprefixmarker
-                name = name[len(self._argprefix):]
-            elif not isinstance(marker, FixtureFunctionMarker):
-                # magic globals  with __getattr__ might have got us a wrong
-                # fixture attribute
-                continue
-            else:
-                assert not name.startswith(self._argprefix)
-            fixturedef = FixtureDef(self, nodeid, name, obj,
-                                    marker.scope, marker.params,
-                                    yieldctx=marker.yieldctx,
-                                    unittest=unittest, ids=marker.ids)
-            faclist = self._arg2fixturedefs.setdefault(name, [])
-            if fixturedef.has_location:
-                faclist.append(fixturedef)
-            else:
-                # fixturedefs with no location are at the front
-                # so this inserts the current fixturedef after the
-                # existing fixturedefs from external plugins but
-                # before the fixturedefs provided in conftests.
-                i = len([f for f in faclist if not f.has_location])
-                faclist.insert(i, fixturedef)
-            if marker.autouse:
-                autousenames.append(name)
-        if autousenames:
-            self._nodeid_and_autousenames.append((nodeid or '', autousenames))
-
-    def getfixturedefs(self, argname, nodeid):
-        try:
-            fixturedefs = self._arg2fixturedefs[argname]
-        except KeyError:
-            return None
-        else:
-            return tuple(self._matchfactories(fixturedefs, nodeid))
-
-    def _matchfactories(self, fixturedefs, nodeid):
-        for fixturedef in fixturedefs:
-            if nodeid.startswith(fixturedef.baseid):
-                yield fixturedef
-
-
-def fail_fixturefunc(fixturefunc, msg):
-    fs, lineno = getfslineno(fixturefunc)
-    location = "%s:%s" % (fs, lineno+1)
-    source = _pytest._code.Source(fixturefunc)
-    pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
-                pytrace=False)
-
-def call_fixture_func(fixturefunc, request, kwargs, yieldctx):
-    if yieldctx:
-        if not is_generator(fixturefunc):
-            fail_fixturefunc(fixturefunc,
-                msg="yield_fixture requires yield statement in function")
-        iter = fixturefunc(**kwargs)
-        next = getattr(iter, "__next__", None)
-        if next is None:
-            next = getattr(iter, "next")
-        res = next()
-        def teardown():
-            try:
-                next()
-            except StopIteration:
-                pass
-            else:
-                fail_fixturefunc(fixturefunc,
-                    "yield_fixture function has more than one 'yield'")
-        request.addfinalizer(teardown)
-    else:
-        if is_generator(fixturefunc):
-            fail_fixturefunc(fixturefunc,
-                msg="pytest.fixture functions cannot use ``yield``. "
-                    "Instead write and return an inner function/generator "
-                    "and let the consumer call and iterate over it.")
-        res = fixturefunc(**kwargs)
-    return res
-
-class FixtureDef:
-    """ A container for a factory definition. """
-    def __init__(self, fixturemanager, baseid, argname, func, scope, params,
-                 yieldctx, unittest=False, ids=None):
-        self._fixturemanager = fixturemanager
-        self.baseid = baseid or ''
-        self.has_location = baseid is not None
-        self.func = func
-        self.argname = argname
-        self.scope = scope
-        self.scopenum = scopes.index(scope or "function")
-        self.params = params
-        startindex = unittest and 1 or None
-        self.argnames = getfuncargnames(func, startindex=startindex)
-        self.yieldctx = yieldctx
-        self.unittest = unittest
-        self.ids = ids
-        self._finalizer = []
-
-    def addfinalizer(self, finalizer):
-        self._finalizer.append(finalizer)
-
-    def finish(self):
-        try:
-            while self._finalizer:
-                func = self._finalizer.pop()
-                func()
-        finally:
-            # even if finalization fails, we invalidate
-            # the cached fixture value
-            if hasattr(self, "cached_result"):
-                del self.cached_result
-
-    def execute(self, request):
-        # get required arguments and register our own finish()
-        # with their finalization
-        kwargs = {}
-        for argname in self.argnames:
-            fixturedef = request._get_active_fixturedef(argname)
-            result, arg_cache_key, exc = fixturedef.cached_result
-            request._check_scope(argname, request.scope, fixturedef.scope)
-            kwargs[argname] = result
-            if argname != "request":
-                fixturedef.addfinalizer(self.finish)
-
-        my_cache_key = request.param_index
-        cached_result = getattr(self, "cached_result", None)
-        if cached_result is not None:
-            result, cache_key, err = cached_result
-            if my_cache_key == cache_key:
-                if err is not None:
-                    py.builtin._reraise(*err)
-                else:
-                    return result
-            # we have a previous but differently parametrized fixture instance
-            # so we need to tear it down before creating a new one
-            self.finish()
-            assert not hasattr(self, "cached_result")
-
-        fixturefunc = self.func
-
-        if self.unittest:
-            if request.instance is not None:
-                # bind the unbound method to the TestCase instance
-                fixturefunc = self.func.__get__(request.instance)
-        else:
-            # the fixture function needs to be bound to the actual
-            # request.instance so that code working with "self" behaves
-            # as expected.
-            if request.instance is not None:
-                fixturefunc = getimfunc(self.func)
-                if fixturefunc != self.func:
-                    fixturefunc = fixturefunc.__get__(request.instance)
-
-        try:
-            result = call_fixture_func(fixturefunc, request, kwargs,
-                                       self.yieldctx)
-        except Exception:
-            self.cached_result = (None, my_cache_key, sys.exc_info())
-            raise
-        self.cached_result = (result, my_cache_key, None)
-        return result
-
-    def __repr__(self):
-        return ("<FixtureDef name=%r scope=%r baseid=%r >" %
-                (self.argname, self.scope, self.baseid))
-
-def num_mock_patch_args(function):
-    """ return number of arguments used up by mock arguments (if any) """
-    patchings = getattr(function, "patchings", None)
-    if not patchings:
-        return 0
-    mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
-    if mock is not None:
-        return len([p for p in patchings
-                        if not p.attribute_name and p.new is mock.DEFAULT])
-    return len(patchings)
-
-
-def getfuncargnames(function, startindex=None):
-    # XXX merge with main.py's varnames
-    #assert not isclass(function)
-    realfunction = function
-    while hasattr(realfunction, "__wrapped__"):
-        realfunction = realfunction.__wrapped__
-    if startindex is None:
-        startindex = inspect.ismethod(function) and 1 or 0
-    if realfunction != function:
-        startindex += num_mock_patch_args(function)
-        function = realfunction
-    if isinstance(function, functools.partial):
-        argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0]
-        partial = function
-        argnames = argnames[len(partial.args):]
-        if partial.keywords:
-            for kw in partial.keywords:
-                argnames.remove(kw)
-    else:
-        argnames = inspect.getargs(_pytest._code.getrawcode(function))[0]
-    defaults = getattr(function, 'func_defaults',
-                       getattr(function, '__defaults__', None)) or ()
-    numdefaults = len(defaults)
-    if numdefaults:
-        return tuple(argnames[startindex:-numdefaults])
-    return tuple(argnames[startindex:])
-
-# algorithm for sorting on a per-parametrized resource setup basis
-# it is called for scopenum==0 (session) first and performs sorting
-# down to the lower scopes such as to minimize number of "high scope"
-# setups and teardowns
-
-def reorder_items(items):
-    argkeys_cache = {}
-    for scopenum in range(0, scopenum_function):
-        argkeys_cache[scopenum] = d = {}
-        for item in items:
-            keys = set(get_parametrized_fixture_keys(item, scopenum))
-            if keys:
-                d[item] = keys
-    return reorder_items_atscope(items, set(), argkeys_cache, 0)
-
-def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
-    if scopenum >= scopenum_function or len(items) < 3:
-        return items
-    items_done = []
-    while 1:
-        items_before, items_same, items_other, newignore = \
-                slice_items(items, ignore, argkeys_cache[scopenum])
-        items_before = reorder_items_atscope(
-                            items_before, ignore, argkeys_cache,scopenum+1)
-        if items_same is None:
-            # nothing to reorder in this scope
-            assert items_other is None
-            return items_done + items_before
-        items_done.extend(items_before)
-        items = items_same + items_other
-        ignore = newignore
-
-
-def slice_items(items, ignore, scoped_argkeys_cache):
-    # we pick the first item which uses a fixture instance in the
-    # requested scope and which we haven't seen yet.  We slice the input
-    # items list into a list of items_nomatch, items_same and
-    # items_other
-    if scoped_argkeys_cache:  # do we need to do work at all?
-        it = iter(items)
-        # first find a slicing key
-        for i, item in enumerate(it):
-            argkeys = scoped_argkeys_cache.get(item)
-            if argkeys is not None:
-                argkeys = argkeys.difference(ignore)
-                if argkeys:  # found a slicing key
-                    slicing_argkey = argkeys.pop()
-                    items_before = items[:i]
-                    items_same = [item]
-                    items_other = []
-                    # now slice the remainder of the list
-                    for item in it:
-                        argkeys = scoped_argkeys_cache.get(item)
-                        if argkeys and slicing_argkey in argkeys and \
-                            slicing_argkey not in ignore:
-                            items_same.append(item)
-                        else:
-                            items_other.append(item)
-                    newignore = ignore.copy()
-                    newignore.add(slicing_argkey)
-                    return (items_before, items_same, items_other, newignore)
-    return items, None, None, None
-
-def get_parametrized_fixture_keys(item, scopenum):
-    """ return list of keys for all parametrized arguments which match
-    the specified scope. """
-    assert scopenum < scopenum_function  # function
-    try:
-        cs = item.callspec
-    except AttributeError:
-        pass
-    else:
-        # cs.indictes.items() is random order of argnames but
-        # then again different functions (items) can change order of
-        # arguments so it doesn't matter much probably
-        for argname, param_index in cs.indices.items():
-            if cs._arg2scopenum[argname] != scopenum:
-                continue
-            if scopenum == 0:    # session
-                key = (argname, param_index)
-            elif scopenum == 1:  # module
-                key = (argname, param_index, item.fspath)
-            elif scopenum == 2:  # class
-                key = (argname, param_index, item.fspath, item.cls)
-            yield key
-
-
-def xunitsetup(obj, name):
-    meth = getattr(obj, name, None)
-    if getfixturemarker(meth) is None:
-        return meth
-
-def getfixturemarker(obj):
-    """ return fixturemarker or None if it doesn't exist or raised
-    exceptions."""
-    try:
-        return getattr(obj, "_pytestfixturefunction", None)
-    except KeyboardInterrupt:
-        raise
-    except Exception:
-        # some objects raise errors like request (from flask import request)
-        # we don't expect them to be fixture functions
-        return None
-
-scopename2class = {
-    'class': Class,
-    'module': Module,
-    'function': pytest.Item,
-}
-def get_scope_node(node, scope):
-    cls = scopename2class.get(scope)
-    if cls is None:
-        if scope == "session":
-            return node.session
-        raise ValueError("unknown scope")
-    return node.getparent(cls)
diff --git a/tools/pytest/_pytest/recwarn.py b/tools/pytest/_pytest/recwarn.py
deleted file mode 100644
index a89474c..0000000
--- a/tools/pytest/_pytest/recwarn.py
+++ /dev/null
@@ -1,221 +0,0 @@
-""" recording warnings during test function execution. """
-
-import inspect
-
-import _pytest._code
-import py
-import sys
-import warnings
-import pytest
-
-
-@pytest.yield_fixture
-def recwarn(request):
-    """Return a WarningsRecorder instance that provides these methods:
-
-    * ``pop(category=None)``: return last warning matching the category.
-    * ``clear()``: clear list of warnings
-
-    See http://docs.python.org/library/warnings.html for information
-    on warning categories.
-    """
-    wrec = WarningsRecorder()
-    with wrec:
-        warnings.simplefilter('default')
-        yield wrec
-
-
-def pytest_namespace():
-    return {'deprecated_call': deprecated_call,
-            'warns': warns}
-
-
-def deprecated_call(func=None, *args, **kwargs):
-    """ assert that calling ``func(*args, **kwargs)`` triggers a
-    ``DeprecationWarning`` or ``PendingDeprecationWarning``.
-
-    This function can be used as a context manager::
-
-        >>> with deprecated_call():
-        ...    myobject.deprecated_method()
-
-    Note: we cannot use WarningsRecorder here because it is still subject
-    to the mechanism that prevents warnings of the same type from being
-    triggered twice for the same module. See #1190.
-    """
-    if not func:
-        return WarningsChecker(expected_warning=DeprecationWarning)
-
-    categories = []
-
-    def warn_explicit(message, category, *args, **kwargs):
-        categories.append(category)
-        old_warn_explicit(message, category, *args, **kwargs)
-
-    def warn(message, category=None, *args, **kwargs):
-        if isinstance(message, Warning):
-            categories.append(message.__class__)
-        else:
-            categories.append(category)
-        old_warn(message, category, *args, **kwargs)
-
-    old_warn = warnings.warn
-    old_warn_explicit = warnings.warn_explicit
-    warnings.warn_explicit = warn_explicit
-    warnings.warn = warn
-    try:
-        ret = func(*args, **kwargs)
-    finally:
-        warnings.warn_explicit = old_warn_explicit
-        warnings.warn = old_warn
-    deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
-    if not any(issubclass(c, deprecation_categories) for c in categories):
-        __tracebackhide__ = True
-        raise AssertionError("%r did not produce DeprecationWarning" % (func,))
-    return ret
-
-
-def warns(expected_warning, *args, **kwargs):
-    """Assert that code raises a particular class of warning.
-
-    Specifically, the input @expected_warning can be a warning class or
-    tuple of warning classes, and the code must return that warning
-    (if a single class) or one of those warnings (if a tuple).
-
-    This helper produces a list of ``warnings.WarningMessage`` objects,
-    one for each warning raised.
-
-    This function can be used as a context manager, or any of the other ways
-    ``pytest.raises`` can be used::
-
-        >>> with warns(RuntimeWarning):
-        ...    warnings.warn("my warning", RuntimeWarning)
-    """
-    wcheck = WarningsChecker(expected_warning)
-    if not args:
-        return wcheck
-    elif isinstance(args[0], str):
-        code, = args
-        assert isinstance(code, str)
-        frame = sys._getframe(1)
-        loc = frame.f_locals.copy()
-        loc.update(kwargs)
-
-        with wcheck:
-            code = _pytest._code.Source(code).compile()
-            py.builtin.exec_(code, frame.f_globals, loc)
-    else:
-        func = args[0]
-        with wcheck:
-            return func(*args[1:], **kwargs)
-
-
-class RecordedWarning(object):
-    def __init__(self, message, category, filename, lineno, file, line):
-        self.message = message
-        self.category = category
-        self.filename = filename
-        self.lineno = lineno
-        self.file = file
-        self.line = line
-
-
-class WarningsRecorder(object):
-    """A context manager to record raised warnings.
-
-    Adapted from `warnings.catch_warnings`.
-    """
-
-    def __init__(self, module=None):
-        self._module = sys.modules['warnings'] if module is None else module
-        self._entered = False
-        self._list = []
-
-    @property
-    def list(self):
-        """The list of recorded warnings."""
-        return self._list
-
-    def __getitem__(self, i):
-        """Get a recorded warning by index."""
-        return self._list[i]
-
-    def __iter__(self):
-        """Iterate through the recorded warnings."""
-        return iter(self._list)
-
-    def __len__(self):
-        """The number of recorded warnings."""
-        return len(self._list)
-
-    def pop(self, cls=Warning):
-        """Pop the first recorded warning, raise exception if not exists."""
-        for i, w in enumerate(self._list):
-            if issubclass(w.category, cls):
-                return self._list.pop(i)
-        __tracebackhide__ = True
-        raise AssertionError("%r not found in warning list" % cls)
-
-    def clear(self):
-        """Clear the list of recorded warnings."""
-        self._list[:] = []
-
-    def __enter__(self):
-        if self._entered:
-            __tracebackhide__ = True
-            raise RuntimeError("Cannot enter %r twice" % self)
-        self._entered = True
-        self._filters = self._module.filters
-        self._module.filters = self._filters[:]
-        self._showwarning = self._module.showwarning
-
-        def showwarning(message, category, filename, lineno,
-                        file=None, line=None):
-            self._list.append(RecordedWarning(
-                message, category, filename, lineno, file, line))
-
-            # still perform old showwarning functionality
-            self._showwarning(
-                message, category, filename, lineno, file=file, line=line)
-
-        self._module.showwarning = showwarning
-
-        # allow the same warning to be raised more than once
-
-        self._module.simplefilter('always')
-        return self
-
-    def __exit__(self, *exc_info):
-        if not self._entered:
-            __tracebackhide__ = True
-            raise RuntimeError("Cannot exit %r without entering first" % self)
-        self._module.filters = self._filters
-        self._module.showwarning = self._showwarning
-
-
-class WarningsChecker(WarningsRecorder):
-    def __init__(self, expected_warning=None, module=None):
-        super(WarningsChecker, self).__init__(module=module)
-
-        msg = ("exceptions must be old-style classes or "
-               "derived from Warning, not %s")
-        if isinstance(expected_warning, tuple):
-            for exc in expected_warning:
-                if not inspect.isclass(exc):
-                    raise TypeError(msg % type(exc))
-        elif inspect.isclass(expected_warning):
-            expected_warning = (expected_warning,)
-        elif expected_warning is not None:
-            raise TypeError(msg % type(expected_warning))
-
-        self.expected_warning = expected_warning
-
-    def __exit__(self, *exc_info):
-        super(WarningsChecker, self).__exit__(*exc_info)
-
-        # only check if we're not currently handling an exception
-        if all(a is None for a in exc_info):
-            if self.expected_warning is not None:
-                if not any(r.category in self.expected_warning for r in self):
-                    __tracebackhide__ = True
-                    pytest.fail("DID NOT WARN")
diff --git a/tools/pytest/_pytest/resultlog.py b/tools/pytest/_pytest/resultlog.py
deleted file mode 100644
index 3670f02..0000000
--- a/tools/pytest/_pytest/resultlog.py
+++ /dev/null
@@ -1,104 +0,0 @@
-""" log machine-parseable test session result information in a plain
-text file.
-"""
-
-import py
-import os
-
-def pytest_addoption(parser):
-    group = parser.getgroup("terminal reporting", "resultlog plugin options")
-    group.addoption('--resultlog', '--result-log', action="store",
-        metavar="path", default=None,
-        help="path for machine-readable result log.")
-
-def pytest_configure(config):
-    resultlog = config.option.resultlog
-    # prevent opening resultlog on slave nodes (xdist)
-    if resultlog and not hasattr(config, 'slaveinput'):
-        dirname = os.path.dirname(os.path.abspath(resultlog))
-        if not os.path.isdir(dirname):
-            os.makedirs(dirname)
-        logfile = open(resultlog, 'w', 1) # line buffered
-        config._resultlog = ResultLog(config, logfile)
-        config.pluginmanager.register(config._resultlog)
-
-def pytest_unconfigure(config):
-    resultlog = getattr(config, '_resultlog', None)
-    if resultlog:
-        resultlog.logfile.close()
-        del config._resultlog
-        config.pluginmanager.unregister(resultlog)
-
-def generic_path(item):
-    chain = item.listchain()
-    gpath = [chain[0].name]
-    fspath = chain[0].fspath
-    fspart = False
-    for node in chain[1:]:
-        newfspath = node.fspath
-        if newfspath == fspath:
-            if fspart:
-                gpath.append(':')
-                fspart = False
-            else:
-                gpath.append('.')
-        else:
-            gpath.append('/')
-            fspart = True
-        name = node.name
-        if name[0] in '([':
-            gpath.pop()
-        gpath.append(name)
-        fspath = newfspath
-    return ''.join(gpath)
-
-class ResultLog(object):
-    def __init__(self, config, logfile):
-        self.config = config
-        self.logfile = logfile # preferably line buffered
-
-    def write_log_entry(self, testpath, lettercode, longrepr):
-        py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile)
-        for line in longrepr.splitlines():
-            py.builtin.print_(" %s" % line, file=self.logfile)
-
-    def log_outcome(self, report, lettercode, longrepr):
-        testpath = getattr(report, 'nodeid', None)
-        if testpath is None:
-            testpath = report.fspath
-        self.write_log_entry(testpath, lettercode, longrepr)
-
-    def pytest_runtest_logreport(self, report):
-        if report.when != "call" and report.passed:
-            return
-        res = self.config.hook.pytest_report_teststatus(report=report)
-        code = res[1]
-        if code == 'x':
-            longrepr = str(report.longrepr)
-        elif code == 'X':
-            longrepr = ''
-        elif report.passed:
-            longrepr = ""
-        elif report.failed:
-            longrepr = str(report.longrepr)
-        elif report.skipped:
-            longrepr = str(report.longrepr[2])
-        self.log_outcome(report, code, longrepr)
-
-    def pytest_collectreport(self, report):
-        if not report.passed:
-            if report.failed:
-                code = "F"
-                longrepr = str(report.longrepr)
-            else:
-                assert report.skipped
-                code = "S"
-                longrepr = "%s:%d: %s" % report.longrepr
-            self.log_outcome(report, code, longrepr)
-
-    def pytest_internalerror(self, excrepr):
-        reprcrash = getattr(excrepr, 'reprcrash', None)
-        path = getattr(reprcrash, "path", None)
-        if path is None:
-            path = "cwd:%s" % py.path.local()
-        self.write_log_entry(path, '!', str(excrepr))
diff --git a/tools/pytest/_pytest/runner.py b/tools/pytest/_pytest/runner.py
deleted file mode 100644
index cde94c8..0000000
--- a/tools/pytest/_pytest/runner.py
+++ /dev/null
@@ -1,515 +0,0 @@
-""" basic collect and runtest protocol implementations """
-import bdb
-import sys
-from time import time
-
-import py
-import pytest
-from _pytest._code.code import TerminalRepr, ExceptionInfo
-
-
-def pytest_namespace():
-    return {
-        'fail'         : fail,
-        'skip'         : skip,
-        'importorskip' : importorskip,
-        'exit'         : exit,
-    }
-
-#
-# pytest plugin hooks
-
-def pytest_addoption(parser):
-    group = parser.getgroup("terminal reporting", "reporting", after="general")
-    group.addoption('--durations',
-         action="store", type=int, default=None, metavar="N",
-         help="show N slowest setup/test durations (N=0 for all)."),
-
-def pytest_terminal_summary(terminalreporter):
-    durations = terminalreporter.config.option.durations
-    if durations is None:
-        return
-    tr = terminalreporter
-    dlist = []
-    for replist in tr.stats.values():
-        for rep in replist:
-            if hasattr(rep, 'duration'):
-                dlist.append(rep)
-    if not dlist:
-        return
-    dlist.sort(key=lambda x: x.duration)
-    dlist.reverse()
-    if not durations:
-        tr.write_sep("=", "slowest test durations")
-    else:
-        tr.write_sep("=", "slowest %s test durations" % durations)
-        dlist = dlist[:durations]
-
-    for rep in dlist:
-        nodeid = rep.nodeid.replace("::()::", "::")
-        tr.write_line("%02.2fs %-8s %s" %
-            (rep.duration, rep.when, nodeid))
-
-def pytest_sessionstart(session):
-    session._setupstate = SetupState()
-def pytest_sessionfinish(session):
-    session._setupstate.teardown_all()
-
-class NodeInfo:
-    def __init__(self, location):
-        self.location = location
-
-def pytest_runtest_protocol(item, nextitem):
-    item.ihook.pytest_runtest_logstart(
-        nodeid=item.nodeid, location=item.location,
-    )
-    runtestprotocol(item, nextitem=nextitem)
-    return True
-
-def runtestprotocol(item, log=True, nextitem=None):
-    hasrequest = hasattr(item, "_request")
-    if hasrequest and not item._request:
-        item._initrequest()
-    rep = call_and_report(item, "setup", log)
-    reports = [rep]
-    if rep.passed:
-        reports.append(call_and_report(item, "call", log))
-    reports.append(call_and_report(item, "teardown", log,
-        nextitem=nextitem))
-    # after all teardown hooks have been called
-    # want funcargs and request info to go away
-    if hasrequest:
-        item._request = False
-        item.funcargs = None
-    return reports
-
-def pytest_runtest_setup(item):
-    item.session._setupstate.prepare(item)
-
-def pytest_runtest_call(item):
-    try:
-        item.runtest()
-    except Exception:
-        # Store trace info to allow postmortem debugging
-        type, value, tb = sys.exc_info()
-        tb = tb.tb_next  # Skip *this* frame
-        sys.last_type = type
-        sys.last_value = value
-        sys.last_traceback = tb
-        del tb  # Get rid of it in this namespace
-        raise
-
-def pytest_runtest_teardown(item, nextitem):
-    item.session._setupstate.teardown_exact(item, nextitem)
-
-def pytest_report_teststatus(report):
-    if report.when in ("setup", "teardown"):
-        if report.failed:
-            #      category, shortletter, verbose-word
-            return "error", "E", "ERROR"
-        elif report.skipped:
-            return "skipped", "s", "SKIPPED"
-        else:
-            return "", "", ""
-
-
-#
-# Implementation
-
-def call_and_report(item, when, log=True, **kwds):
-    call = call_runtest_hook(item, when, **kwds)
-    hook = item.ihook
-    report = hook.pytest_runtest_makereport(item=item, call=call)
-    if log:
-        hook.pytest_runtest_logreport(report=report)
-    if check_interactive_exception(call, report):
-        hook.pytest_exception_interact(node=item, call=call, report=report)
-    return report
-
-def check_interactive_exception(call, report):
-    return call.excinfo and not (
-                hasattr(report, "wasxfail") or
-                call.excinfo.errisinstance(skip.Exception) or
-                call.excinfo.errisinstance(bdb.BdbQuit))
-
-def call_runtest_hook(item, when, **kwds):
-    hookname = "pytest_runtest_" + when
-    ihook = getattr(item.ihook, hookname)
-    return CallInfo(lambda: ihook(item=item, **kwds), when=when)
-
-class CallInfo:
-    """ Result/Exception info a function invocation. """
-    #: None or ExceptionInfo object.
-    excinfo = None
-    def __init__(self, func, when):
-        #: context of invocation: one of "setup", "call",
-        #: "teardown", "memocollect"
-        self.when = when
-        self.start = time()
-        try:
-            self.result = func()
-        except KeyboardInterrupt:
-            self.stop = time()
-            raise
-        except:
-            self.excinfo = ExceptionInfo()
-        self.stop = time()
-
-    def __repr__(self):
-        if self.excinfo:
-            status = "exception: %s" % str(self.excinfo.value)
-        else:
-            status = "result: %r" % (self.result,)
-        return "<CallInfo when=%r %s>" % (self.when, status)
-
-def getslaveinfoline(node):
-    try:
-        return node._slaveinfocache
-    except AttributeError:
-        d = node.slaveinfo
-        ver = "%s.%s.%s" % d['version_info'][:3]
-        node._slaveinfocache = s = "[%s] %s -- Python %s %s" % (
-            d['id'], d['sysplatform'], ver, d['executable'])
-        return s
-
-class BaseReport(object):
-
-    def __init__(self, **kw):
-        self.__dict__.update(kw)
-
-    def toterminal(self, out):
-        if hasattr(self, 'node'):
-            out.line(getslaveinfoline(self.node))
-
-        longrepr = self.longrepr
-        if longrepr is None:
-            return
-
-        if hasattr(longrepr, 'toterminal'):
-            longrepr.toterminal(out)
-        else:
-            try:
-                out.line(longrepr)
-            except UnicodeEncodeError:
-                out.line("<unprintable longrepr>")
-
-    def get_sections(self, prefix):
-        for name, content in self.sections:
-            if name.startswith(prefix):
-                yield prefix, content
-
-    passed = property(lambda x: x.outcome == "passed")
-    failed = property(lambda x: x.outcome == "failed")
-    skipped = property(lambda x: x.outcome == "skipped")
-
-    @property
-    def fspath(self):
-        return self.nodeid.split("::")[0]
-
-def pytest_runtest_makereport(item, call):
-    when = call.when
-    duration = call.stop-call.start
-    keywords = dict([(x,1) for x in item.keywords])
-    excinfo = call.excinfo
-    sections = []
-    if not call.excinfo:
-        outcome = "passed"
-        longrepr = None
-    else:
-        if not isinstance(excinfo, ExceptionInfo):
-            outcome = "failed"
-            longrepr = excinfo
-        elif excinfo.errisinstance(pytest.skip.Exception):
-            outcome = "skipped"
-            r = excinfo._getreprcrash()
-            longrepr = (str(r.path), r.lineno, r.message)
-        else:
-            outcome = "failed"
-            if call.when == "call":
-                longrepr = item.repr_failure(excinfo)
-            else: # exception in setup or teardown
-                longrepr = item._repr_failure_py(excinfo,
-                                            style=item.config.option.tbstyle)
-    for rwhen, key, content in item._report_sections:
-        sections.append(("Captured %s %s" %(key, rwhen), content))
-    return TestReport(item.nodeid, item.location,
-                      keywords, outcome, longrepr, when,
-                      sections, duration)
-
-class TestReport(BaseReport):
-    """ Basic test report object (also used for setup and teardown calls if
-    they fail).
-    """
-    def __init__(self, nodeid, location, keywords, outcome,
-                 longrepr, when, sections=(), duration=0, **extra):
-        #: normalized collection node id
-        self.nodeid = nodeid
-
-        #: a (filesystempath, lineno, domaininfo) tuple indicating the
-        #: actual location of a test item - it might be different from the
-        #: collected one e.g. if a method is inherited from a different module.
-        self.location = location
-
-        #: a name -> value dictionary containing all keywords and
-        #: markers associated with a test invocation.
-        self.keywords = keywords
-
-        #: test outcome, always one of "passed", "failed", "skipped".
-        self.outcome = outcome
-
-        #: None or a failure representation.
-        self.longrepr = longrepr
-
-        #: one of 'setup', 'call', 'teardown' to indicate runtest phase.
-        self.when = when
-
-        #: list of (secname, data) extra information which needs to
-        #: marshallable
-        self.sections = list(sections)
-
-        #: time it took to run just the test
-        self.duration = duration
-
-        self.__dict__.update(extra)
-
-    def __repr__(self):
-        return "<TestReport %r when=%r outcome=%r>" % (
-            self.nodeid, self.when, self.outcome)
-
-class TeardownErrorReport(BaseReport):
-    outcome = "failed"
-    when = "teardown"
-    def __init__(self, longrepr, **extra):
-        self.longrepr = longrepr
-        self.sections = []
-        self.__dict__.update(extra)
-
-def pytest_make_collect_report(collector):
-    call = CallInfo(collector._memocollect, "memocollect")
-    longrepr = None
-    if not call.excinfo:
-        outcome = "passed"
-    else:
-        from _pytest import nose
-        skip_exceptions = (Skipped,) + nose.get_skip_exceptions()
-        if call.excinfo.errisinstance(skip_exceptions):
-            outcome = "skipped"
-            r = collector._repr_failure_py(call.excinfo, "line").reprcrash
-            longrepr = (str(r.path), r.lineno, r.message)
-        else:
-            outcome = "failed"
-            errorinfo = collector.repr_failure(call.excinfo)
-            if not hasattr(errorinfo, "toterminal"):
-                errorinfo = CollectErrorRepr(errorinfo)
-            longrepr = errorinfo
-    rep = CollectReport(collector.nodeid, outcome, longrepr,
-        getattr(call, 'result', None))
-    rep.call = call  # see collect_one_node
-    return rep
-
-
-class CollectReport(BaseReport):
-    def __init__(self, nodeid, outcome, longrepr, result,
-                 sections=(), **extra):
-        self.nodeid = nodeid
-        self.outcome = outcome
-        self.longrepr = longrepr
-        self.result = result or []
-        self.sections = list(sections)
-        self.__dict__.update(extra)
-
-    @property
-    def location(self):
-        return (self.fspath, None, self.fspath)
-
-    def __repr__(self):
-        return "<CollectReport %r lenresult=%s outcome=%r>" % (
-                self.nodeid, len(self.result), self.outcome)
-
-class CollectErrorRepr(TerminalRepr):
-    def __init__(self, msg):
-        self.longrepr = msg
-    def toterminal(self, out):
-        out.line(self.longrepr, red=True)
-
-class SetupState(object):
-    """ shared state for setting up/tearing down test items or collectors. """
-    def __init__(self):
-        self.stack = []
-        self._finalizers = {}
-
-    def addfinalizer(self, finalizer, colitem):
-        """ attach a finalizer to the given colitem.
-        if colitem is None, this will add a finalizer that
-        is called at the end of teardown_all().
-        """
-        assert colitem and not isinstance(colitem, tuple)
-        assert py.builtin.callable(finalizer)
-        #assert colitem in self.stack  # some unit tests don't setup stack :/
-        self._finalizers.setdefault(colitem, []).append(finalizer)
-
-    def _pop_and_teardown(self):
-        colitem = self.stack.pop()
-        self._teardown_with_finalization(colitem)
-
-    def _callfinalizers(self, colitem):
-        finalizers = self._finalizers.pop(colitem, None)
-        exc = None
-        while finalizers:
-            fin = finalizers.pop()
-            try:
-                fin()
-            except Exception:
-                # XXX Only first exception will be seen by user,
-                #     ideally all should be reported.
-                if exc is None:
-                    exc = sys.exc_info()
-        if exc:
-            py.builtin._reraise(*exc)
-
-    def _teardown_with_finalization(self, colitem):
-        self._callfinalizers(colitem)
-        if hasattr(colitem, "teardown"):
-            colitem.teardown()
-        for colitem in self._finalizers:
-            assert colitem is None or colitem in self.stack \
-             or isinstance(colitem, tuple)
-
-    def teardown_all(self):
-        while self.stack:
-            self._pop_and_teardown()
-        for key in list(self._finalizers):
-            self._teardown_with_finalization(key)
-        assert not self._finalizers
-
-    def teardown_exact(self, item, nextitem):
-        needed_collectors = nextitem and nextitem.listchain() or []
-        self._teardown_towards(needed_collectors)
-
-    def _teardown_towards(self, needed_collectors):
-        while self.stack:
-            if self.stack == needed_collectors[:len(self.stack)]:
-                break
-            self._pop_and_teardown()
-
-    def prepare(self, colitem):
-        """ setup objects along the collector chain to the test-method
-            and teardown previously setup objects."""
-        needed_collectors = colitem.listchain()
-        self._teardown_towards(needed_collectors)
-
-        # check if the last collection node has raised an error
-        for col in self.stack:
-            if hasattr(col, '_prepare_exc'):
-                py.builtin._reraise(*col._prepare_exc)
-        for col in needed_collectors[len(self.stack):]:
-            self.stack.append(col)
-            try:
-                col.setup()
-            except Exception:
-                col._prepare_exc = sys.exc_info()
-                raise
-
-def collect_one_node(collector):
-    ihook = collector.ihook
-    ihook.pytest_collectstart(collector=collector)
-    rep = ihook.pytest_make_collect_report(collector=collector)
-    call = rep.__dict__.pop("call", None)
-    if call and check_interactive_exception(call, rep):
-        ihook.pytest_exception_interact(node=collector, call=call, report=rep)
-    return rep
-
-
-# =============================================================
-# Test OutcomeExceptions and helpers for creating them.
-
-
-class OutcomeException(Exception):
-    """ OutcomeException and its subclass instances indicate and
-        contain info about test and collection outcomes.
-    """
-    def __init__(self, msg=None, pytrace=True):
-        Exception.__init__(self, msg)
-        self.msg = msg
-        self.pytrace = pytrace
-
-    def __repr__(self):
-        if self.msg:
-            val = self.msg
-            if isinstance(val, bytes):
-                val = py._builtin._totext(val, errors='replace')
-            return val
-        return "<%s instance>" %(self.__class__.__name__,)
-    __str__ = __repr__
-
-class Skipped(OutcomeException):
-    # XXX hackish: on 3k we fake to live in the builtins
-    # in order to have Skipped exception printing shorter/nicer
-    __module__ = 'builtins'
-
-class Failed(OutcomeException):
-    """ raised from an explicit call to pytest.fail() """
-    __module__ = 'builtins'
-
-class Exit(KeyboardInterrupt):
-    """ raised for immediate program exits (no tracebacks/summaries)"""
-    def __init__(self, msg="unknown reason"):
-        self.msg = msg
-        KeyboardInterrupt.__init__(self, msg)
-
-# exposed helper methods
-
-def exit(msg):
-    """ exit testing process as if KeyboardInterrupt was triggered. """
-    __tracebackhide__ = True
-    raise Exit(msg)
-
-exit.Exception = Exit
-
-def skip(msg=""):
-    """ skip an executing test with the given message.  Note: it's usually
-    better to use the pytest.mark.skipif marker to declare a test to be
-    skipped under certain conditions like mismatching platforms or
-    dependencies.  See the pytest_skipping plugin for details.
-    """
-    __tracebackhide__ = True
-    raise Skipped(msg=msg)
-skip.Exception = Skipped
-
-def fail(msg="", pytrace=True):
-    """ explicitly fail an currently-executing test with the given Message.
-
-    :arg pytrace: if false the msg represents the full failure information
-                  and no python traceback will be reported.
-    """
-    __tracebackhide__ = True
-    raise Failed(msg=msg, pytrace=pytrace)
-fail.Exception = Failed
-
-
-def importorskip(modname, minversion=None):
-    """ return imported module if it has at least "minversion" as its
-    __version__ attribute.  If no minversion is specified the a skip
-    is only triggered if the module can not be imported.
-    """
-    __tracebackhide__ = True
-    compile(modname, '', 'eval') # to catch syntaxerrors
-    try:
-        __import__(modname)
-    except ImportError:
-        skip("could not import %r" %(modname,))
-    mod = sys.modules[modname]
-    if minversion is None:
-        return mod
-    verattr = getattr(mod, '__version__', None)
-    if minversion is not None:
-        try:
-            from pkg_resources import parse_version as pv
-        except ImportError:
-            skip("we have a required version for %r but can not import "
-                 "no pkg_resources to parse version strings." %(modname,))
-        if verattr is None or pv(verattr) < pv(minversion):
-            skip("module %r has __version__ %r, required is: %r" %(
-                 modname, verattr, minversion))
-    return mod
-
diff --git a/tools/pytest/_pytest/skipping.py b/tools/pytest/_pytest/skipping.py
deleted file mode 100644
index 69157f4..0000000
--- a/tools/pytest/_pytest/skipping.py
+++ /dev/null
@@ -1,354 +0,0 @@
-""" support for skip/xfail functions and markers. """
-import os
-import sys
-import traceback
-
-import py
-import pytest
-from _pytest.mark import MarkInfo, MarkDecorator
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("general")
-    group.addoption('--runxfail',
-           action="store_true", dest="runxfail", default=False,
-           help="run tests even if they are marked xfail")
-
-    parser.addini("xfail_strict", "default for the strict parameter of xfail "
-                                  "markers when not given explicitly (default: "
-                                  "False)",
-                                  default=False,
-                                  type="bool")
-
-
-def pytest_configure(config):
-    if config.option.runxfail:
-        old = pytest.xfail
-        config._cleanup.append(lambda: setattr(pytest, "xfail", old))
-        def nop(*args, **kwargs):
-            pass
-        nop.Exception = XFailed
-        setattr(pytest, "xfail", nop)
-
-    config.addinivalue_line("markers",
-        "skipif(condition): skip the given test function if eval(condition) "
-        "results in a True value.  Evaluation happens within the "
-        "module global context. Example: skipif('sys.platform == \"win32\"') "
-        "skips the test if we are on the win32 platform. see "
-        "http://pytest.org/latest/skipping.html"
-    )
-    config.addinivalue_line("markers",
-        "xfail(condition, reason=None, run=True, raises=None): mark the the test function "
-        "as an expected failure if eval(condition) has a True value. "
-        "Optionally specify a reason for better reporting and run=False if "
-        "you don't even want to execute the test function. If only specific "
-        "exception(s) are expected, you can list them in raises, and if the test fails "
-        "in other ways, it will be reported as a true failure. "
-        "See http://pytest.org/latest/skipping.html"
-    )
-
-
-def pytest_namespace():
-    return dict(xfail=xfail)
-
-
-class XFailed(pytest.fail.Exception):
-    """ raised from an explicit call to pytest.xfail() """
-
-
-def xfail(reason=""):
-    """ xfail an executing test or setup functions with the given reason."""
-    __tracebackhide__ = True
-    raise XFailed(reason)
-xfail.Exception = XFailed
-
-
-class MarkEvaluator:
-    def __init__(self, item, name):
-        self.item = item
-        self.name = name
-
-    @property
-    def holder(self):
-        return self.item.keywords.get(self.name)
-
-    def __bool__(self):
-        return bool(self.holder)
-    __nonzero__ = __bool__
-
-    def wasvalid(self):
-        return not hasattr(self, 'exc')
-
-    def invalidraise(self, exc):
-        raises = self.get('raises')
-        if not raises:
-            return
-        return not isinstance(exc, raises)
-
-    def istrue(self):
-        try:
-            return self._istrue()
-        except Exception:
-            self.exc = sys.exc_info()
-            if isinstance(self.exc[1], SyntaxError):
-                msg = [" " * (self.exc[1].offset + 4) + "^",]
-                msg.append("SyntaxError: invalid syntax")
-            else:
-                msg = traceback.format_exception_only(*self.exc[:2])
-            pytest.fail("Error evaluating %r expression\n"
-                        "    %s\n"
-                        "%s"
-                        %(self.name, self.expr, "\n".join(msg)),
-                        pytrace=False)
-
-    def _getglobals(self):
-        d = {'os': os, 'sys': sys, 'config': self.item.config}
-        func = self.item.obj
-        try:
-            d.update(func.__globals__)
-        except AttributeError:
-            d.update(func.func_globals)
-        return d
-
-    def _istrue(self):
-        if hasattr(self, 'result'):
-            return self.result
-        if self.holder:
-            d = self._getglobals()
-            if self.holder.args:
-                self.result = False
-                # "holder" might be a MarkInfo or a MarkDecorator; only
-                # MarkInfo keeps track of all parameters it received in an
-                # _arglist attribute
-                if hasattr(self.holder, '_arglist'):
-                    arglist = self.holder._arglist
-                else:
-                    arglist = [(self.holder.args, self.holder.kwargs)]
-                for args, kwargs in arglist:
-                    for expr in args:
-                        self.expr = expr
-                        if isinstance(expr, py.builtin._basestring):
-                            result = cached_eval(self.item.config, expr, d)
-                        else:
-                            if "reason" not in kwargs:
-                                # XXX better be checked at collection time
-                                msg = "you need to specify reason=STRING " \
-                                      "when using booleans as conditions."
-                                pytest.fail(msg)
-                            result = bool(expr)
-                        if result:
-                            self.result = True
-                            self.reason = kwargs.get('reason', None)
-                            self.expr = expr
-                            return self.result
-            else:
-                self.result = True
-        return getattr(self, 'result', False)
-
-    def get(self, attr, default=None):
-        return self.holder.kwargs.get(attr, default)
-
-    def getexplanation(self):
-        expl = getattr(self, 'reason', None) or self.get('reason', None)
-        if not expl:
-            if not hasattr(self, 'expr'):
-                return ""
-            else:
-                return "condition: " + str(self.expr)
-        return expl
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_runtest_setup(item):
-    # Check if skip or skipif are specified as pytest marks
-
-    skipif_info = item.keywords.get('skipif')
-    if isinstance(skipif_info, (MarkInfo, MarkDecorator)):
-        eval_skipif = MarkEvaluator(item, 'skipif')
-        if eval_skipif.istrue():
-            item._evalskip = eval_skipif
-            pytest.skip(eval_skipif.getexplanation())
-
-    skip_info = item.keywords.get('skip')
-    if isinstance(skip_info, (MarkInfo, MarkDecorator)):
-        item._evalskip = True
-        if 'reason' in skip_info.kwargs:
-            pytest.skip(skip_info.kwargs['reason'])
-        elif skip_info.args:
-            pytest.skip(skip_info.args[0])
-        else:
-            pytest.skip("unconditional skip")
-
-    item._evalxfail = MarkEvaluator(item, 'xfail')
-    check_xfail_no_run(item)
-
-
-@pytest.mark.hookwrapper
-def pytest_pyfunc_call(pyfuncitem):
-    check_xfail_no_run(pyfuncitem)
-    outcome = yield
-    passed = outcome.excinfo is None
-    if passed:
-        check_strict_xfail(pyfuncitem)
-
-
-def check_xfail_no_run(item):
-    """check xfail(run=False)"""
-    if not item.config.option.runxfail:
-        evalxfail = item._evalxfail
-        if evalxfail.istrue():
-            if not evalxfail.get('run', True):
-                pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
-
-
-def check_strict_xfail(pyfuncitem):
-    """check xfail(strict=True) for the given PASSING test"""
-    evalxfail = pyfuncitem._evalxfail
-    if evalxfail.istrue():
-        strict_default = pyfuncitem.config.getini('xfail_strict')
-        is_strict_xfail = evalxfail.get('strict', strict_default)
-        if is_strict_xfail:
-            del pyfuncitem._evalxfail
-            explanation = evalxfail.getexplanation()
-            pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False)
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_runtest_makereport(item, call):
-    outcome = yield
-    rep = outcome.get_result()
-    evalxfail = getattr(item, '_evalxfail', None)
-    evalskip = getattr(item, '_evalskip', None)
-    # unitttest special case, see setting of _unexpectedsuccess
-    if hasattr(item, '_unexpectedsuccess') and rep.when == "call":
-        # we need to translate into how pytest encodes xpass
-        rep.wasxfail = "reason: " + repr(item._unexpectedsuccess)
-        rep.outcome = "failed"
-    elif item.config.option.runxfail:
-        pass   # don't interefere
-    elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception):
-        rep.wasxfail = "reason: " + call.excinfo.value.msg
-        rep.outcome = "skipped"
-    elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
-        evalxfail.istrue():
-        if call.excinfo:
-            if evalxfail.invalidraise(call.excinfo.value):
-                rep.outcome = "failed"
-            else:
-                rep.outcome = "skipped"
-                rep.wasxfail = evalxfail.getexplanation()
-        elif call.when == "call":
-            rep.outcome = "failed"  # xpass outcome
-            rep.wasxfail = evalxfail.getexplanation()
-    elif evalskip is not None and rep.skipped and type(rep.longrepr) is tuple:
-        # skipped by mark.skipif; change the location of the failure
-        # to point to the item definition, otherwise it will display
-        # the location of where the skip exception was raised within pytest
-        filename, line, reason = rep.longrepr
-        filename, line = item.location[:2]
-        rep.longrepr = filename, line, reason
-
-# called by terminalreporter progress reporting
-def pytest_report_teststatus(report):
-    if hasattr(report, "wasxfail"):
-        if report.skipped:
-            return "xfailed", "x", "xfail"
-        elif report.failed:
-            return "xpassed", "X", ("XPASS", {'yellow': True})
-
-# called by the terminalreporter instance/plugin
-def pytest_terminal_summary(terminalreporter):
-    tr = terminalreporter
-    if not tr.reportchars:
-        #for name in "xfailed skipped failed xpassed":
-        #    if not tr.stats.get(name, 0):
-        #        tr.write_line("HINT: use '-r' option to see extra "
-        #              "summary info about tests")
-        #        break
-        return
-
-    lines = []
-    for char in tr.reportchars:
-        if char == "x":
-            show_xfailed(terminalreporter, lines)
-        elif char == "X":
-            show_xpassed(terminalreporter, lines)
-        elif char in "fF":
-            show_simple(terminalreporter, lines, 'failed', "FAIL %s")
-        elif char in "sS":
-            show_skipped(terminalreporter, lines)
-        elif char == "E":
-            show_simple(terminalreporter, lines, 'error', "ERROR %s")
-        elif char == 'p':
-            show_simple(terminalreporter, lines, 'passed', "PASSED %s")
-
-    if lines:
-        tr._tw.sep("=", "short test summary info")
-        for line in lines:
-            tr._tw.line(line)
-
-def show_simple(terminalreporter, lines, stat, format):
-    failed = terminalreporter.stats.get(stat)
-    if failed:
-        for rep in failed:
-            pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
-            lines.append(format %(pos,))
-
-def show_xfailed(terminalreporter, lines):
-    xfailed = terminalreporter.stats.get("xfailed")
-    if xfailed:
-        for rep in xfailed:
-            pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
-            reason = rep.wasxfail
-            lines.append("XFAIL %s" % (pos,))
-            if reason:
-                lines.append("  " + str(reason))
-
-def show_xpassed(terminalreporter, lines):
-    xpassed = terminalreporter.stats.get("xpassed")
-    if xpassed:
-        for rep in xpassed:
-            pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
-            reason = rep.wasxfail
-            lines.append("XPASS %s %s" %(pos, reason))
-
-def cached_eval(config, expr, d):
-    if not hasattr(config, '_evalcache'):
-        config._evalcache = {}
-    try:
-        return config._evalcache[expr]
-    except KeyError:
-        import _pytest._code
-        exprcode = _pytest._code.compile(expr, mode="eval")
-        config._evalcache[expr] = x = eval(exprcode, d)
-        return x
-
-
-def folded_skips(skipped):
-    d = {}
-    for event in skipped:
-        key = event.longrepr
-        assert len(key) == 3, (event, key)
-        d.setdefault(key, []).append(event)
-    l = []
-    for key, events in d.items():
-        l.append((len(events),) + key)
-    return l
-
-def show_skipped(terminalreporter, lines):
-    tr = terminalreporter
-    skipped = tr.stats.get('skipped', [])
-    if skipped:
-        #if not tr.hasopt('skipped'):
-        #    tr.write_line(
-        #        "%d skipped tests, specify -rs for more info" %
-        #        len(skipped))
-        #    return
-        fskips = folded_skips(skipped)
-        if fskips:
-            #tr.write_sep("_", "skipped test summary")
-            for num, fspath, lineno, reason in fskips:
-                if reason.startswith("Skipped: "):
-                    reason = reason[9:]
-                lines.append("SKIP [%d] %s:%d: %s" %
-                    (num, fspath, lineno, reason))
diff --git a/tools/pytest/_pytest/standalonetemplate.py b/tools/pytest/_pytest/standalonetemplate.py
deleted file mode 100755
index 484d5d1..0000000
--- a/tools/pytest/_pytest/standalonetemplate.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#! /usr/bin/env python
-
-# Hi There!
-# You may be wondering what this giant blob of binary data here is, you might
-# even be worried that we're up to something nefarious (good for you for being
-# paranoid!). This is a base64 encoding of a zip file, this zip file contains
-# a fully functional basic pytest script.
-#
-# Pytest is a thing that tests packages, pytest itself is a package that some-
-# one might want to install, especially if they're looking to run tests inside
-# some package they want to install. Pytest has a lot of code to collect and
-# execute tests, and other such sort of "tribal knowledge" that has been en-
-# coded in its code base. Because of this we basically include a basic copy
-# of pytest inside this blob. We do this  because it let's you as a maintainer
-# or application developer who wants people who don't deal with python much to
-# easily run tests without installing the complete pytest package.
-#
-# If you're wondering how this is created: you can create it yourself if you
-# have a complete pytest installation by using this command on the command-
-# line: ``py.test --genscript=runtests.py``.
-
-sources = """
-@SOURCES@"""
-
-import sys
-import base64
-import zlib
-
-class DictImporter(object):
-    def __init__(self, sources):
-        self.sources = sources
-
-    def find_module(self, fullname, path=None):
-        if fullname == "argparse" and sys.version_info >= (2,7):
-            # we were generated with <python2.7 (which pulls in argparse)
-            # but we are running now on a stdlib which has it, so use that.
-            return None
-        if fullname in self.sources:
-            return self
-        if fullname + '.__init__' in self.sources:
-            return self
-        return None
-
-    def load_module(self, fullname):
-        # print "load_module:",  fullname
-        from types import ModuleType
-        try:
-            s = self.sources[fullname]
-            is_pkg = False
-        except KeyError:
-            s = self.sources[fullname + '.__init__']
-            is_pkg = True
-
-        co = compile(s, fullname, 'exec')
-        module = sys.modules.setdefault(fullname, ModuleType(fullname))
-        module.__file__ = "%s/%s" % (__file__, fullname)
-        module.__loader__ = self
-        if is_pkg:
-            module.__path__ = [fullname]
-
-        do_exec(co, module.__dict__) # noqa
-        return sys.modules[fullname]
-
-    def get_source(self, name):
-        res = self.sources.get(name)
-        if res is None:
-            res = self.sources.get(name + '.__init__')
-        return res
-
-if __name__ == "__main__":
-    try:
-        import pkg_resources  # noqa
-    except ImportError:
-        sys.stderr.write("ERROR: setuptools not installed\n")
-        sys.exit(2)
-    if sys.version_info >= (3, 0):
-        exec("def do_exec(co, loc): exec(co, loc)\n")
-        import pickle
-        sources = sources.encode("ascii") # ensure bytes
-        sources = pickle.loads(zlib.decompress(base64.decodebytes(sources)))
-    else:
-        import cPickle as pickle
-        exec("def do_exec(co, loc): exec co in loc\n")
-        sources = pickle.loads(zlib.decompress(base64.decodestring(sources)))
-
-    importer = DictImporter(sources)
-    sys.meta_path.insert(0, importer)
-    entry = "@ENTRY@"
-    do_exec(entry, locals()) # noqa
diff --git a/tools/pytest/_pytest/terminal.py b/tools/pytest/_pytest/terminal.py
deleted file mode 100644
index 825f553..0000000
--- a/tools/pytest/_pytest/terminal.py
+++ /dev/null
@@ -1,593 +0,0 @@
-""" terminal reporting of the full testing process.
-
-This is a good source for looking at the various reporting hooks.
-"""
-from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
-    EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
-import pytest
-import py
-import sys
-import time
-import platform
-
-import _pytest._pluggy as pluggy
-
-
-def pytest_addoption(parser):
-    group = parser.getgroup("terminal reporting", "reporting", after="general")
-    group._addoption('-v', '--verbose', action="count",
-               dest="verbose", default=0, help="increase verbosity."),
-    group._addoption('-q', '--quiet', action="count",
-               dest="quiet", default=0, help="decrease verbosity."),
-    group._addoption('-r',
-         action="store", dest="reportchars", default=None, metavar="chars",
-         help="show extra test summary info as specified by chars (f)ailed, "
-              "(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings "
-              "(p)passed, (P)passed with output, (a)all except pP.")
-    group._addoption('-l', '--showlocals',
-         action="store_true", dest="showlocals", default=False,
-         help="show locals in tracebacks (disabled by default).")
-    group._addoption('--report',
-         action="store", dest="report", default=None, metavar="opts",
-         help="(deprecated, use -r)")
-    group._addoption('--tb', metavar="style",
-               action="store", dest="tbstyle", default='auto',
-               choices=['auto', 'long', 'short', 'no', 'line', 'native'],
-               help="traceback print mode (auto/long/short/line/native/no).")
-    group._addoption('--fulltrace', '--full-trace',
-               action="store_true", default=False,
-               help="don't cut any tracebacks (default is to cut).")
-    group._addoption('--color', metavar="color",
-               action="store", dest="color", default='auto',
-               choices=['yes', 'no', 'auto'],
-               help="color terminal output (yes/no/auto).")
-
-def pytest_configure(config):
-    config.option.verbose -= config.option.quiet
-    reporter = TerminalReporter(config, sys.stdout)
-    config.pluginmanager.register(reporter, 'terminalreporter')
-    if config.option.debug or config.option.traceconfig:
-        def mywriter(tags, args):
-            msg = " ".join(map(str, args))
-            reporter.write_line("[traceconfig] " + msg)
-        config.trace.root.setprocessor("pytest:config", mywriter)
-
-def getreportopt(config):
-    reportopts = ""
-    optvalue = config.option.report
-    if optvalue:
-        py.builtin.print_("DEPRECATED: use -r instead of --report option.",
-            file=sys.stderr)
-        if optvalue:
-            for setting in optvalue.split(","):
-                setting = setting.strip()
-                if setting == "skipped":
-                    reportopts += "s"
-                elif setting == "xfailed":
-                    reportopts += "x"
-    reportchars = config.option.reportchars
-    if reportchars:
-        for char in reportchars:
-            if char not in reportopts and char != 'a':
-                reportopts += char
-            elif char == 'a':
-                reportopts = 'fEsxXw'
-    return reportopts
-
-def pytest_report_teststatus(report):
-    if report.passed:
-        letter = "."
-    elif report.skipped:
-        letter = "s"
-    elif report.failed:
-        letter = "F"
-        if report.when != "call":
-            letter = "f"
-    return report.outcome, letter, report.outcome.upper()
-
-class WarningReport:
-    def __init__(self, code, message, nodeid=None, fslocation=None):
-        self.code = code
-        self.message = message
-        self.nodeid = nodeid
-        self.fslocation = fslocation
-
-
-class TerminalReporter:
-    def __init__(self, config, file=None):
-        import _pytest.config
-        self.config = config
-        self.verbosity = self.config.option.verbose
-        self.showheader = self.verbosity >= 0
-        self.showfspath = self.verbosity >= 0
-        self.showlongtestinfo = self.verbosity > 0
-        self._numcollected = 0
-
-        self.stats = {}
-        self.startdir = py.path.local()
-        if file is None:
-            file = sys.stdout
-        self._tw = self.writer = _pytest.config.create_terminal_writer(config,
-                                                                       file)
-        self.currentfspath = None
-        self.reportchars = getreportopt(config)
-        self.hasmarkup = self._tw.hasmarkup
-        self.isatty = file.isatty()
-
-    def hasopt(self, char):
-        char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
-        return char in self.reportchars
-
-    def write_fspath_result(self, nodeid, res):
-        fspath = self.config.rootdir.join(nodeid.split("::")[0])
-        if fspath != self.currentfspath:
-            self.currentfspath = fspath
-            fspath = self.startdir.bestrelpath(fspath)
-            self._tw.line()
-            self._tw.write(fspath + " ")
-        self._tw.write(res)
-
-    def write_ensure_prefix(self, prefix, extra="", **kwargs):
-        if self.currentfspath != prefix:
-            self._tw.line()
-            self.currentfspath = prefix
-            self._tw.write(prefix)
-        if extra:
-            self._tw.write(extra, **kwargs)
-            self.currentfspath = -2
-
-    def ensure_newline(self):
-        if self.currentfspath:
-            self._tw.line()
-            self.currentfspath = None
-
-    def write(self, content, **markup):
-        self._tw.write(content, **markup)
-
-    def write_line(self, line, **markup):
-        if not py.builtin._istext(line):
-            line = py.builtin.text(line, errors="replace")
-        self.ensure_newline()
-        self._tw.line(line, **markup)
-
-    def rewrite(self, line, **markup):
-        line = str(line)
-        self._tw.write("\r" + line, **markup)
-
-    def write_sep(self, sep, title=None, **markup):
-        self.ensure_newline()
-        self._tw.sep(sep, title, **markup)
-
-    def section(self, title, sep="=", **kw):
-        self._tw.sep(sep, title, **kw)
-
-    def line(self, msg, **kw):
-        self._tw.line(msg, **kw)
-
-    def pytest_internalerror(self, excrepr):
-        for line in py.builtin.text(excrepr).split("\n"):
-            self.write_line("INTERNALERROR> " + line)
-        return 1
-
-    def pytest_logwarning(self, code, fslocation, message, nodeid):
-        warnings = self.stats.setdefault("warnings", [])
-        if isinstance(fslocation, tuple):
-            fslocation = "%s:%d" % fslocation
-        warning = WarningReport(code=code, fslocation=fslocation,
-                                message=message, nodeid=nodeid)
-        warnings.append(warning)
-
-    def pytest_plugin_registered(self, plugin):
-        if self.config.option.traceconfig:
-            msg = "PLUGIN registered: %s" % (plugin,)
-            # XXX this event may happen during setup/teardown time
-            #     which unfortunately captures our output here
-            #     which garbles our output if we use self.write_line
-            self.write_line(msg)
-
-    def pytest_deselected(self, items):
-        self.stats.setdefault('deselected', []).extend(items)
-
-    def pytest_runtest_logstart(self, nodeid, location):
-        # ensure that the path is printed before the
-        # 1st test of a module starts running
-        if self.showlongtestinfo:
-            line = self._locationline(nodeid, *location)
-            self.write_ensure_prefix(line, "")
-        elif self.showfspath:
-            fsid = nodeid.split("::")[0]
-            self.write_fspath_result(fsid, "")
-
-    def pytest_runtest_logreport(self, report):
-        rep = report
-        res = self.config.hook.pytest_report_teststatus(report=rep)
-        cat, letter, word = res
-        self.stats.setdefault(cat, []).append(rep)
-        self._tests_ran = True
-        if not letter and not word:
-            # probably passed setup/teardown
-            return
-        if self.verbosity <= 0:
-            if not hasattr(rep, 'node') and self.showfspath:
-                self.write_fspath_result(rep.nodeid, letter)
-            else:
-                self._tw.write(letter)
-        else:
-            if isinstance(word, tuple):
-                word, markup = word
-            else:
-                if rep.passed:
-                    markup = {'green':True}
-                elif rep.failed:
-                    markup = {'red':True}
-                elif rep.skipped:
-                    markup = {'yellow':True}
-            line = self._locationline(rep.nodeid, *rep.location)
-            if not hasattr(rep, 'node'):
-                self.write_ensure_prefix(line, word, **markup)
-                #self._tw.write(word, **markup)
-            else:
-                self.ensure_newline()
-                if hasattr(rep, 'node'):
-                    self._tw.write("[%s] " % rep.node.gateway.id)
-                self._tw.write(word, **markup)
-                self._tw.write(" " + line)
-                self.currentfspath = -2
-
-    def pytest_collection(self):
-        if not self.isatty and self.config.option.verbose >= 1:
-            self.write("collecting ... ", bold=True)
-
-    def pytest_collectreport(self, report):
-        if report.failed:
-            self.stats.setdefault("error", []).append(report)
-        elif report.skipped:
-            self.stats.setdefault("skipped", []).append(report)
-        items = [x for x in report.result if isinstance(x, pytest.Item)]
-        self._numcollected += len(items)
-        if self.isatty:
-            #self.write_fspath_result(report.nodeid, 'E')
-            self.report_collect()
-
-    def report_collect(self, final=False):
-        if self.config.option.verbose < 0:
-            return
-
-        errors = len(self.stats.get('error', []))
-        skipped = len(self.stats.get('skipped', []))
-        if final:
-            line = "collected "
-        else:
-            line = "collecting "
-        line += str(self._numcollected) + " items"
-        if errors:
-            line += " / %d errors" % errors
-        if skipped:
-            line += " / %d skipped" % skipped
-        if self.isatty:
-            if final:
-                line += " \n"
-            self.rewrite(line, bold=True)
-        else:
-            self.write_line(line)
-
-    def pytest_collection_modifyitems(self):
-        self.report_collect(True)
-
-    @pytest.hookimpl(trylast=True)
-    def pytest_sessionstart(self, session):
-        self._sessionstarttime = time.time()
-        if not self.showheader:
-            return
-        self.write_sep("=", "test session starts", bold=True)
-        verinfo = platform.python_version()
-        msg = "platform %s -- Python %s" % (sys.platform, verinfo)
-        if hasattr(sys, 'pypy_version_info'):
-            verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
-            msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
-        msg += ", pytest-%s, py-%s, pluggy-%s" % (
-               pytest.__version__, py.__version__, pluggy.__version__)
-        if self.verbosity > 0 or self.config.option.debug or \
-           getattr(self.config.option, 'pastebin', None):
-            msg += " -- " + str(sys.executable)
-        self.write_line(msg)
-        lines = self.config.hook.pytest_report_header(
-            config=self.config, startdir=self.startdir)
-        lines.reverse()
-        for line in flatten(lines):
-            self.write_line(line)
-
-    def pytest_report_header(self, config):
-        inifile = ""
-        if config.inifile:
-            inifile = config.rootdir.bestrelpath(config.inifile)
-        lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
-
-        plugininfo = config.pluginmanager.list_plugin_distinfo()
-        if plugininfo:
-
-            lines.append(
-                "plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
-        return lines
-
-    def pytest_collection_finish(self, session):
-        if self.config.option.collectonly:
-            self._printcollecteditems(session.items)
-            if self.stats.get('failed'):
-                self._tw.sep("!", "collection failures")
-                for rep in self.stats.get('failed'):
-                    rep.toterminal(self._tw)
-                return 1
-            return 0
-        if not self.showheader:
-            return
-        #for i, testarg in enumerate(self.config.args):
-        #    self.write_line("test path %d: %s" %(i+1, testarg))
-
-    def _printcollecteditems(self, items):
-        # to print out items and their parent collectors
-        # we take care to leave out Instances aka ()
-        # because later versions are going to get rid of them anyway
-        if self.config.option.verbose < 0:
-            if self.config.option.verbose < -1:
-                counts = {}
-                for item in items:
-                    name = item.nodeid.split('::', 1)[0]
-                    counts[name] = counts.get(name, 0) + 1
-                for name, count in sorted(counts.items()):
-                    self._tw.line("%s: %d" % (name, count))
-            else:
-                for item in items:
-                    nodeid = item.nodeid
-                    nodeid = nodeid.replace("::()::", "::")
-                    self._tw.line(nodeid)
-            return
-        stack = []
-        indent = ""
-        for item in items:
-            needed_collectors = item.listchain()[1:] # strip root node
-            while stack:
-                if stack == needed_collectors[:len(stack)]:
-                    break
-                stack.pop()
-            for col in needed_collectors[len(stack):]:
-                stack.append(col)
-                #if col.name == "()":
-                #    continue
-                indent = (len(stack) - 1) * "  "
-                self._tw.line("%s%s" % (indent, col))
-
-    @pytest.hookimpl(hookwrapper=True)
-    def pytest_sessionfinish(self, exitstatus):
-        outcome = yield
-        outcome.get_result()
-        self._tw.line("")
-        summary_exit_codes = (
-            EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
-            EXIT_NOTESTSCOLLECTED)
-        if exitstatus in summary_exit_codes:
-            self.config.hook.pytest_terminal_summary(terminalreporter=self)
-            self.summary_errors()
-            self.summary_failures()
-            self.summary_warnings()
-            self.summary_passes()
-        if exitstatus == EXIT_INTERRUPTED:
-            self._report_keyboardinterrupt()
-            del self._keyboardinterrupt_memo
-        self.summary_deselected()
-        self.summary_stats()
-
-    def pytest_keyboard_interrupt(self, excinfo):
-        self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
-
-    def pytest_unconfigure(self):
-        if hasattr(self, '_keyboardinterrupt_memo'):
-            self._report_keyboardinterrupt()
-
-    def _report_keyboardinterrupt(self):
-        excrepr = self._keyboardinterrupt_memo
-        msg = excrepr.reprcrash.message
-        self.write_sep("!", msg)
-        if "KeyboardInterrupt" in msg:
-            if self.config.option.fulltrace:
-                excrepr.toterminal(self._tw)
-            else:
-                self._tw.line("to show a full traceback on KeyboardInterrupt use --fulltrace", yellow=True)
-                excrepr.reprcrash.toterminal(self._tw)
-
-    def _locationline(self, nodeid, fspath, lineno, domain):
-        def mkrel(nodeid):
-            line = self.config.cwd_relative_nodeid(nodeid)
-            if domain and line.endswith(domain):
-                line = line[:-len(domain)]
-                l = domain.split("[")
-                l[0] = l[0].replace('.', '::')  # don't replace '.' in params
-                line += "[".join(l)
-            return line
-        # collect_fspath comes from testid which has a "/"-normalized path
-
-        if fspath:
-            res = mkrel(nodeid).replace("::()", "")  # parens-normalization
-            if nodeid.split("::")[0] != fspath.replace("\\", "/"):
-                res += " <- " + self.startdir.bestrelpath(fspath)
-        else:
-            res = "[location]"
-        return res + " "
-
-    def _getfailureheadline(self, rep):
-        if hasattr(rep, 'location'):
-            fspath, lineno, domain = rep.location
-            return domain
-        else:
-            return "test session" # XXX?
-
-    def _getcrashline(self, rep):
-        try:
-            return str(rep.longrepr.reprcrash)
-        except AttributeError:
-            try:
-                return str(rep.longrepr)[:50]
-            except AttributeError:
-                return ""
-
-    #
-    # summaries for sessionfinish
-    #
-    def getreports(self, name):
-        l = []
-        for x in self.stats.get(name, []):
-            if not hasattr(x, '_pdbshown'):
-                l.append(x)
-        return l
-
-    def summary_warnings(self):
-        if self.hasopt("w"):
-            warnings = self.stats.get("warnings")
-            if not warnings:
-                return
-            self.write_sep("=", "pytest-warning summary")
-            for w in warnings:
-                self._tw.line("W%s %s %s" % (w.code,
-                              w.fslocation, w.message))
-
-    def summary_passes(self):
-        if self.config.option.tbstyle != "no":
-            if self.hasopt("P"):
-                reports = self.getreports('passed')
-                if not reports:
-                    return
-                self.write_sep("=", "PASSES")
-                for rep in reports:
-                    msg = self._getfailureheadline(rep)
-                    self.write_sep("_", msg)
-                    self._outrep_summary(rep)
-
-    def summary_failures(self):
-        if self.config.option.tbstyle != "no":
-            reports = self.getreports('failed')
-            if not reports:
-                return
-            self.write_sep("=", "FAILURES")
-            for rep in reports:
-                if self.config.option.tbstyle == "line":
-                    line = self._getcrashline(rep)
-                    self.write_line(line)
-                else:
-                    msg = self._getfailureheadline(rep)
-                    markup = {'red': True, 'bold': True}
-                    self.write_sep("_", msg, **markup)
-                    self._outrep_summary(rep)
-
-    def summary_errors(self):
-        if self.config.option.tbstyle != "no":
-            reports = self.getreports('error')
-            if not reports:
-                return
-            self.write_sep("=", "ERRORS")
-            for rep in self.stats['error']:
-                msg = self._getfailureheadline(rep)
-                if not hasattr(rep, 'when'):
-                    # collect
-                    msg = "ERROR collecting " + msg
-                elif rep.when == "setup":
-                    msg = "ERROR at setup of " + msg
-                elif rep.when == "teardown":
-                    msg = "ERROR at teardown of " + msg
-                self.write_sep("_", msg)
-                self._outrep_summary(rep)
-
-    def _outrep_summary(self, rep):
-        rep.toterminal(self._tw)
-        for secname, content in rep.sections:
-            self._tw.sep("-", secname)
-            if content[-1:] == "\n":
-                content = content[:-1]
-            self._tw.line(content)
-
-    def summary_stats(self):
-        session_duration = time.time() - self._sessionstarttime
-        (line, color) = build_summary_stats_line(self.stats)
-        msg = "%s in %.2f seconds" % (line, session_duration)
-        markup = {color: True, 'bold': True}
-
-        if self.verbosity >= 0:
-            self.write_sep("=", msg, **markup)
-        if self.verbosity == -1:
-            self.write_line(msg, **markup)
-
-    def summary_deselected(self):
-        if 'deselected' in self.stats:
-            l = []
-            k = self.config.option.keyword
-            if k:
-                l.append("-k%s" % k)
-            m = self.config.option.markexpr
-            if m:
-                l.append("-m %r" % m)
-            if l:
-                self.write_sep("=", "%d tests deselected by %r" % (
-                    len(self.stats['deselected']), " ".join(l)), bold=True)
-
-def repr_pythonversion(v=None):
-    if v is None:
-        v = sys.version_info
-    try:
-        return "%s.%s.%s-%s-%s" % v
-    except (TypeError, ValueError):
-        return str(v)
-
-def flatten(l):
-    for x in l:
-        if isinstance(x, (list, tuple)):
-            for y in flatten(x):
-                yield y
-        else:
-            yield x
-
-def build_summary_stats_line(stats):
-    keys = ("failed passed skipped deselected "
-           "xfailed xpassed warnings error").split()
-    key_translation = {'warnings': 'pytest-warnings'}
-    unknown_key_seen = False
-    for key in stats.keys():
-        if key not in keys:
-            if key: # setup/teardown reports have an empty key, ignore them
-                keys.append(key)
-                unknown_key_seen = True
-    parts = []
-    for key in keys:
-        val = stats.get(key, None)
-        if val:
-            key_name = key_translation.get(key, key)
-            parts.append("%d %s" % (len(val), key_name))
-
-    if parts:
-        line = ", ".join(parts)
-    else:
-        line = "no tests ran"
-
-    if 'failed' in stats or 'error' in stats:
-        color = 'red'
-    elif 'warnings' in stats or unknown_key_seen:
-        color = 'yellow'
-    elif 'passed' in stats:
-        color = 'green'
-    else:
-        color = 'yellow'
-
-    return (line, color)
-
-
-def _plugin_nameversions(plugininfo):
-    l = []
-    for plugin, dist in plugininfo:
-        # gets us name and version!
-        name = '{dist.project_name}-{dist.version}'.format(dist=dist)
-        # questionable convenience, but it keeps things short
-        if name.startswith("pytest-"):
-            name = name[7:]
-        # we decided to print python package names
-        # they can have more than one plugin
-        if name not in l:
-            l.append(name)
-    return l
diff --git a/tools/pytest/_pytest/tmpdir.py b/tools/pytest/_pytest/tmpdir.py
deleted file mode 100644
index ebc48db..0000000
--- a/tools/pytest/_pytest/tmpdir.py
+++ /dev/null
@@ -1,123 +0,0 @@
-""" support for providing temporary directories to test functions.  """
-import re
-
-import pytest
-import py
-from _pytest.monkeypatch import monkeypatch
-
-
-class TempdirFactory:
-    """Factory for temporary directories under the common base temp directory.
-
-    The base directory can be configured using the ``--basetemp`` option.
-    """
-
-    def __init__(self, config):
-        self.config = config
-        self.trace = config.trace.get("tmpdir")
-
-    def ensuretemp(self, string, dir=1):
-        """ (deprecated) return temporary directory path with
-            the given string as the trailing part.  It is usually
-            better to use the 'tmpdir' function argument which
-            provides an empty unique-per-test-invocation directory
-            and is guaranteed to be empty.
-        """
-        #py.log._apiwarn(">1.1", "use tmpdir function argument")
-        return self.getbasetemp().ensure(string, dir=dir)
-
-    def mktemp(self, basename, numbered=True):
-        """Create a subdirectory of the base temporary directory and return it.
-        If ``numbered``, ensure the directory is unique by adding a number
-        prefix greater than any existing one.
-        """
-        basetemp = self.getbasetemp()
-        if not numbered:
-            p = basetemp.mkdir(basename)
-        else:
-            p = py.path.local.make_numbered_dir(prefix=basename,
-                keep=0, rootdir=basetemp, lock_timeout=None)
-        self.trace("mktemp", p)
-        return p
-
-    def getbasetemp(self):
-        """ return base temporary directory. """
-        try:
-            return self._basetemp
-        except AttributeError:
-            basetemp = self.config.option.basetemp
-            if basetemp:
-                basetemp = py.path.local(basetemp)
-                if basetemp.check():
-                    basetemp.remove()
-                basetemp.mkdir()
-            else:
-                temproot = py.path.local.get_temproot()
-                user = get_user()
-                if user:
-                    # use a sub-directory in the temproot to speed-up
-                    # make_numbered_dir() call
-                    rootdir = temproot.join('pytest-of-%s' % user)
-                else:
-                    rootdir = temproot
-                rootdir.ensure(dir=1)
-                basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
-                                                           rootdir=rootdir)
-            self._basetemp = t = basetemp.realpath()
-            self.trace("new basetemp", t)
-            return t
-
-    def finish(self):
-        self.trace("finish")
-
-
-def get_user():
-    """Return the current user name, or None if getuser() does not work
-    in the current environment (see #1010).
-    """
-    import getpass
-    try:
-        return getpass.getuser()
-    except (ImportError, KeyError):
-        return None
-
-# backward compatibility
-TempdirHandler = TempdirFactory
-
-
-def pytest_configure(config):
-    """Create a TempdirFactory and attach it to the config object.
-
-    This is to comply with existing plugins which expect the handler to be
-    available at pytest_configure time, but ideally should be moved entirely
-    to the tmpdir_factory session fixture.
-    """
-    mp = monkeypatch()
-    t = TempdirFactory(config)
-    config._cleanup.extend([mp.undo, t.finish])
-    mp.setattr(config, '_tmpdirhandler', t, raising=False)
-    mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
-
-
-@pytest.fixture(scope='session')
-def tmpdir_factory(request):
-    """Return a TempdirFactory instance for the test session.
-    """
-    return request.config._tmpdirhandler
-
-
-@pytest.fixture
-def tmpdir(request, tmpdir_factory):
-    """return a temporary directory path object
-    which is unique to each test function invocation,
-    created as a sub directory of the base temporary
-    directory.  The returned object is a `py.path.local`_
-    path object.
-    """
-    name = request.node.name
-    name = re.sub("[\W]", "_", name)
-    MAXVAL = 30
-    if len(name) > MAXVAL:
-        name = name[:MAXVAL]
-    x = tmpdir_factory.mktemp(name, numbered=True)
-    return x
diff --git a/tools/pytest/_pytest/unittest.py b/tools/pytest/_pytest/unittest.py
deleted file mode 100644
index 8120e94..0000000
--- a/tools/pytest/_pytest/unittest.py
+++ /dev/null
@@ -1,205 +0,0 @@
-""" discovery and running of std-library "unittest" style tests. """
-from __future__ import absolute_import
-
-import sys
-import traceback
-
-import pytest
-# for transfering markers
-import _pytest._code
-from _pytest.python import transfer_markers
-from _pytest.skipping import MarkEvaluator
-
-
-def pytest_pycollect_makeitem(collector, name, obj):
-    # has unittest been imported and is obj a subclass of its TestCase?
-    try:
-        if not issubclass(obj, sys.modules["unittest"].TestCase):
-            return
-    except Exception:
-        return
-    # yes, so let's collect it
-    return UnitTestCase(name, parent=collector)
-
-
-class UnitTestCase(pytest.Class):
-    # marker for fixturemanger.getfixtureinfo()
-    # to declare that our children do not support funcargs
-    nofuncargs = True
-                                              
-    def setup(self):
-        cls = self.obj
-        if getattr(cls, '__unittest_skip__', False):
-            return  # skipped
-        setup = getattr(cls, 'setUpClass', None)
-        if setup is not None:
-            setup()
-        teardown = getattr(cls, 'tearDownClass', None)
-        if teardown is not None:
-            self.addfinalizer(teardown)
-        super(UnitTestCase, self).setup()
-
-    def collect(self):
-        from unittest import TestLoader
-        cls = self.obj
-        if not getattr(cls, "__test__", True):
-            return
-        self.session._fixturemanager.parsefactories(self, unittest=True)
-        loader = TestLoader()
-        module = self.getparent(pytest.Module).obj
-        foundsomething = False
-        for name in loader.getTestCaseNames(self.obj):
-            x = getattr(self.obj, name)
-            funcobj = getattr(x, 'im_func', x)
-            transfer_markers(funcobj, cls, module)
-            yield TestCaseFunction(name, parent=self)
-            foundsomething = True
-
-        if not foundsomething:
-            runtest = getattr(self.obj, 'runTest', None)
-            if runtest is not None:
-                ut = sys.modules.get("twisted.trial.unittest", None)
-                if ut is None or runtest != ut.TestCase.runTest:
-                    yield TestCaseFunction('runTest', parent=self)
-
-
-
-class TestCaseFunction(pytest.Function):
-    _excinfo = None
-
-    def setup(self):
-        self._testcase = self.parent.obj(self.name)
-        self._fix_unittest_skip_decorator()
-        self._obj = getattr(self._testcase, self.name)
-        if hasattr(self._testcase, 'setup_method'):
-            self._testcase.setup_method(self._obj)
-        if hasattr(self, "_request"):
-            self._request._fillfixtures()
-
-    def _fix_unittest_skip_decorator(self):
-        """
-        The @unittest.skip decorator calls functools.wraps(self._testcase)
-        The call to functools.wraps() fails unless self._testcase
-        has a __name__ attribute. This is usually automatically supplied
-        if the test is a function or method, but we need to add manually
-        here.
-
-        See issue #1169
-        """
-        if sys.version_info[0] == 2:
-            setattr(self._testcase, "__name__", self.name)
-
-    def teardown(self):
-        if hasattr(self._testcase, 'teardown_method'):
-            self._testcase.teardown_method(self._obj)
-
-    def startTest(self, testcase):
-        pass
-
-    def _addexcinfo(self, rawexcinfo):
-        # unwrap potential exception info (see twisted trial support below)
-        rawexcinfo = getattr(rawexcinfo, '_rawexcinfo', rawexcinfo)
-        try:
-            excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
-        except TypeError:
-            try:
-                try:
-                    l = traceback.format_exception(*rawexcinfo)
-                    l.insert(0, "NOTE: Incompatible Exception Representation, "
-                        "displaying natively:\n\n")
-                    pytest.fail("".join(l), pytrace=False)
-                except (pytest.fail.Exception, KeyboardInterrupt):
-                    raise
-                except:
-                    pytest.fail("ERROR: Unknown Incompatible Exception "
-                        "representation:\n%r" %(rawexcinfo,), pytrace=False)
-            except KeyboardInterrupt:
-                raise
-            except pytest.fail.Exception:
-                excinfo = _pytest._code.ExceptionInfo()
-        self.__dict__.setdefault('_excinfo', []).append(excinfo)
-
-    def addError(self, testcase, rawexcinfo):
-        self._addexcinfo(rawexcinfo)
-    def addFailure(self, testcase, rawexcinfo):
-        self._addexcinfo(rawexcinfo)
-
-    def addSkip(self, testcase, reason):
-        try:
-            pytest.skip(reason)
-        except pytest.skip.Exception:
-            self._evalskip = MarkEvaluator(self, 'SkipTest')
-            self._evalskip.result = True
-            self._addexcinfo(sys.exc_info())
-
-    def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
-        try:
-            pytest.xfail(str(reason))
-        except pytest.xfail.Exception:
-            self._addexcinfo(sys.exc_info())
-
-    def addUnexpectedSuccess(self, testcase, reason=""):
-        self._unexpectedsuccess = reason
-
-    def addSuccess(self, testcase):
-        pass
-
-    def stopTest(self, testcase):
-        pass
-
-    def runtest(self):
-        self._testcase(result=self)
-
-    def _prunetraceback(self, excinfo):
-        pytest.Function._prunetraceback(self, excinfo)
-        traceback = excinfo.traceback.filter(
-            lambda x:not x.frame.f_globals.get('__unittest'))
-        if traceback:
-            excinfo.traceback = traceback
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_runtest_makereport(item, call):
-    if isinstance(item, TestCaseFunction):
-        if item._excinfo:
-            call.excinfo = item._excinfo.pop(0)
-            try:
-                del call.result
-            except AttributeError:
-                pass
-
-# twisted trial support
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_runtest_protocol(item):
-    if isinstance(item, TestCaseFunction) and \
-       'twisted.trial.unittest' in sys.modules:
-        ut = sys.modules['twisted.python.failure']
-        Failure__init__ = ut.Failure.__init__
-        check_testcase_implements_trial_reporter()
-        def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
-            captureVars=None):
-            if exc_value is None:
-                self._rawexcinfo = sys.exc_info()
-            else:
-                if exc_type is None:
-                    exc_type = type(exc_value)
-                self._rawexcinfo = (exc_type, exc_value, exc_tb)
-            try:
-                Failure__init__(self, exc_value, exc_type, exc_tb,
-                    captureVars=captureVars)
-            except TypeError:
-                Failure__init__(self, exc_value, exc_type, exc_tb)
-        ut.Failure.__init__ = excstore
-        yield
-        ut.Failure.__init__ = Failure__init__
-    else:
-        yield
-
-
-def check_testcase_implements_trial_reporter(done=[]):
-    if done:
-        return
-    from zope.interface import classImplements
-    from twisted.trial.itrial import IReporter
-    classImplements(TestCaseFunction, IReporter)
-    done.append(1)
diff --git a/tools/pytest/_pytest/vendored_packages/README.md b/tools/pytest/_pytest/vendored_packages/README.md
deleted file mode 100644
index eab7c71..0000000
--- a/tools/pytest/_pytest/vendored_packages/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-This directory vendors the `pluggy` module.
-
-For a more detailed discussion for the reasons to vendoring this 
-package, please see [this issue](https://github.com/pytest-dev/pytest/issues/944).
-
-To update the current version, execute:
-
-```
-$ pip install -U pluggy==<version> --no-compile --target=_pytest/vendored_packages
-```
-
-And commit the modified files. The `pluggy-<version>.dist-info` directory 
-created by `pip` should be ignored.
diff --git a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst b/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst
deleted file mode 100644
index aa3bbf8..0000000
--- a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-Plugin registration and hook calling for Python
-===============================================
-
-This is the plugin manager as used by pytest but stripped
-of pytest specific details.
-
-During the 0.x series this plugin does not have much documentation
-except extensive docstrings in the pluggy.py module.
-
-
diff --git a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA b/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA
deleted file mode 100644
index ec81f0a..0000000
--- a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA
+++ /dev/null
@@ -1,39 +0,0 @@
-Metadata-Version: 2.0
-Name: pluggy
-Version: 0.3.1
-Summary: plugin and hook calling mechanisms for python
-Home-page: UNKNOWN
-Author: Holger Krekel
-Author-email: holger at merlinux.eu
-License: MIT license
-Platform: unix
-Platform: linux
-Platform: osx
-Platform: win32
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: POSIX
-Classifier: Operating System :: Microsoft :: Windows
-Classifier: Operating System :: MacOS :: MacOS X
-Classifier: Topic :: Software Development :: Testing
-Classifier: Topic :: Software Development :: Libraries
-Classifier: Topic :: Utilities
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
-
-Plugin registration and hook calling for Python
-===============================================
-
-This is the plugin manager as used by pytest but stripped
-of pytest specific details.
-
-During the 0.x series this plugin does not have much documentation
-except extensive docstrings in the pluggy.py module.
-
-
diff --git a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD b/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD
deleted file mode 100644
index 9626673..0000000
--- a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD
+++ /dev/null
@@ -1,8 +0,0 @@
-pluggy.py,sha256=v_RfWzyW6DPU1cJu_EFoL_OHq3t13qloVdR6UaMCXQA,29862
-pluggy-0.3.1.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
-pluggy-0.3.1.dist-info/pbr.json,sha256=xX3s6__wOcAyF-AZJX1sdZyW6PUXT-FkfBlM69EEUCg,47
-pluggy-0.3.1.dist-info/RECORD,,
-pluggy-0.3.1.dist-info/metadata.json,sha256=nLKltOT78dMV-00uXD6Aeemp4xNsz2q59j6ORSDeLjw,1027
-pluggy-0.3.1.dist-info/METADATA,sha256=1b85Ho2u4iK30M099k7axMzcDDhLcIMb-A82JUJZnSo,1334
-pluggy-0.3.1.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
-pluggy-0.3.1.dist-info/DESCRIPTION.rst,sha256=P5Akh1EdIBR6CeqtV2P8ZwpGSpZiTKPw0NyS7jEiD-g,306
diff --git a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json b/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json
deleted file mode 100644
index 426a3a7..0000000
--- a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json
+++ /dev/null
@@ -1 +0,0 @@
-{"license": "MIT license", "name": "pluggy", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "plugin and hook calling mechanisms for python", "platform": "unix", "version": "0.3.1", "extensions": {"python.details": {"document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "holger at merlinux.eu", "name": "Holger Krekel"}]}}, "classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"]}
\ No newline at end of file
diff --git a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json b/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json
deleted file mode 100644
index d6b7986..0000000
--- a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json
+++ /dev/null
@@ -1 +0,0 @@
-{"is_release": false, "git_version": "7d4c9cd"}
\ No newline at end of file
diff --git a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt b/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt
deleted file mode 100644
index 11bdb5c..0000000
--- a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-pluggy
diff --git a/tools/pytest/_pytest/vendored_packages/pluggy.py b/tools/pytest/_pytest/vendored_packages/pluggy.py
deleted file mode 100644
index 2f848b2..0000000
--- a/tools/pytest/_pytest/vendored_packages/pluggy.py
+++ /dev/null
@@ -1,777 +0,0 @@
-"""
-PluginManager, basic initialization and tracing.
-
-pluggy is the cristallized core of plugin management as used
-by some 150 plugins for pytest.
-
-Pluggy uses semantic versioning. Breaking changes are only foreseen for
-Major releases (incremented X in "X.Y.Z").  If you want to use pluggy in
-your project you should thus use a dependency restriction like
-"pluggy>=0.1.0,<1.0" to avoid surprises.
-
-pluggy is concerned with hook specification, hook implementations and hook
-calling.  For any given hook specification a hook call invokes up to N implementations.
-A hook implementation can influence its position and type of execution:
-if attributed "tryfirst" or "trylast" it will be tried to execute
-first or last.  However, if attributed "hookwrapper" an implementation
-can wrap all calls to non-hookwrapper implementations.  A hookwrapper
-can thus execute some code ahead and after the execution of other hooks.
-
-Hook specification is done by way of a regular python function where
-both the function name and the names of all its arguments are significant.
-Each hook implementation function is verified against the original specification
-function, including the names of all its arguments.  To allow for hook specifications
-to evolve over the livetime of a project, hook implementations can
-accept less arguments.  One can thus add new arguments and semantics to
-a hook specification by adding another argument typically without breaking
-existing hook implementations.
-
-The chosen approach is meant to let a hook designer think carefuly about
-which objects are needed by an extension writer.  By contrast, subclass-based
-extension mechanisms often expose a lot more state and behaviour than needed,
-thus restricting future developments.
-
-Pluggy currently consists of functionality for:
-
-- a way to register new hook specifications.  Without a hook
-  specification no hook calling can be performed.
-
-- a registry of plugins which contain hook implementation functions.  It
-  is possible to register plugins for which a hook specification is not yet
-  known and validate all hooks when the system is in a more referentially
-  consistent state.  Setting an "optionalhook" attribution to a hook
-  implementation will avoid PluginValidationError's if a specification
-  is missing.  This allows to have optional integration between plugins.
-
-- a "hook" relay object from which you can launch 1:N calls to
-  registered hook implementation functions
-
-- a mechanism for ordering hook implementation functions
-
-- mechanisms for two different type of 1:N calls: "firstresult" for when
-  the call should stop when the first implementation returns a non-None result.
-  And the other (default) way of guaranteeing that all hook implementations
-  will be called and their non-None result collected.
-
-- mechanisms for "historic" extension points such that all newly
-  registered functions will receive all hook calls that happened
-  before their registration.
-
-- a mechanism for discovering plugin objects which are based on
-  setuptools based entry points.
-
-- a simple tracing mechanism, including tracing of plugin calls and
-  their arguments.
-
-"""
-import sys
-import inspect
-
-__version__ = '0.3.1'
-__all__ = ["PluginManager", "PluginValidationError",
-           "HookspecMarker", "HookimplMarker"]
-
-_py3 = sys.version_info > (3, 0)
-
-
-class HookspecMarker:
-    """ Decorator helper class for marking functions as hook specifications.
-
-    You can instantiate it with a project_name to get a decorator.
-    Calling PluginManager.add_hookspecs later will discover all marked functions
-    if the PluginManager uses the same project_name.
-    """
-
-    def __init__(self, project_name):
-        self.project_name = project_name
-
-    def __call__(self, function=None, firstresult=False, historic=False):
-        """ if passed a function, directly sets attributes on the function
-        which will make it discoverable to add_hookspecs().  If passed no
-        function, returns a decorator which can be applied to a function
-        later using the attributes supplied.
-
-        If firstresult is True the 1:N hook call (N being the number of registered
-        hook implementation functions) will stop at I<=N when the I'th function
-        returns a non-None result.
-
-        If historic is True calls to a hook will be memorized and replayed
-        on later registered plugins.
-
-        """
-        def setattr_hookspec_opts(func):
-            if historic and firstresult:
-                raise ValueError("cannot have a historic firstresult hook")
-            setattr(func, self.project_name + "_spec",
-                   dict(firstresult=firstresult, historic=historic))
-            return func
-
-        if function is not None:
-            return setattr_hookspec_opts(function)
-        else:
-            return setattr_hookspec_opts
-
-
-class HookimplMarker:
-    """ Decorator helper class for marking functions as hook implementations.
-
-    You can instantiate with a project_name to get a decorator.
-    Calling PluginManager.register later will discover all marked functions
-    if the PluginManager uses the same project_name.
-    """
-    def __init__(self, project_name):
-        self.project_name = project_name
-
-    def __call__(self, function=None, hookwrapper=False, optionalhook=False,
-                 tryfirst=False, trylast=False):
-
-        """ if passed a function, directly sets attributes on the function
-        which will make it discoverable to register().  If passed no function,
-        returns a decorator which can be applied to a function later using
-        the attributes supplied.
-
-        If optionalhook is True a missing matching hook specification will not result
-        in an error (by default it is an error if no matching spec is found).
-
-        If tryfirst is True this hook implementation will run as early as possible
-        in the chain of N hook implementations for a specfication.
-
-        If trylast is True this hook implementation will run as late as possible
-        in the chain of N hook implementations.
-
-        If hookwrapper is True the hook implementations needs to execute exactly
-        one "yield".  The code before the yield is run early before any non-hookwrapper
-        function is run.  The code after the yield is run after all non-hookwrapper
-        function have run.  The yield receives an ``_CallOutcome`` object representing
-        the exception or result outcome of the inner calls (including other hookwrapper
-        calls).
-
-        """
-        def setattr_hookimpl_opts(func):
-            setattr(func, self.project_name + "_impl",
-                   dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
-                        tryfirst=tryfirst, trylast=trylast))
-            return func
-
-        if function is None:
-            return setattr_hookimpl_opts
-        else:
-            return setattr_hookimpl_opts(function)
-
-
-def normalize_hookimpl_opts(opts):
-    opts.setdefault("tryfirst", False)
-    opts.setdefault("trylast", False)
-    opts.setdefault("hookwrapper", False)
-    opts.setdefault("optionalhook", False)
-
-
-class _TagTracer:
-    def __init__(self):
-        self._tag2proc = {}
-        self.writer = None
-        self.indent = 0
-
-    def get(self, name):
-        return _TagTracerSub(self, (name,))
-
-    def format_message(self, tags, args):
-        if isinstance(args[-1], dict):
-            extra = args[-1]
-            args = args[:-1]
-        else:
-            extra = {}
-
-        content = " ".join(map(str, args))
-        indent = "  " * self.indent
-
-        lines = [
-            "%s%s [%s]\n" % (indent, content, ":".join(tags))
-        ]
-
-        for name, value in extra.items():
-            lines.append("%s    %s: %s\n" % (indent, name, value))
-        return lines
-
-    def processmessage(self, tags, args):
-        if self.writer is not None and args:
-            lines = self.format_message(tags, args)
-            self.writer(''.join(lines))
-        try:
-            self._tag2proc[tags](tags, args)
-        except KeyError:
-            pass
-
-    def setwriter(self, writer):
-        self.writer = writer
-
-    def setprocessor(self, tags, processor):
-        if isinstance(tags, str):
-            tags = tuple(tags.split(":"))
-        else:
-            assert isinstance(tags, tuple)
-        self._tag2proc[tags] = processor
-
-
-class _TagTracerSub:
-    def __init__(self, root, tags):
-        self.root = root
-        self.tags = tags
-
-    def __call__(self, *args):
-        self.root.processmessage(self.tags, args)
-
-    def setmyprocessor(self, processor):
-        self.root.setprocessor(self.tags, processor)
-
-    def get(self, name):
-        return self.__class__(self.root, self.tags + (name,))
-
-
-def _raise_wrapfail(wrap_controller, msg):
-    co = wrap_controller.gi_code
-    raise RuntimeError("wrap_controller at %r %s:%d %s" %
-                   (co.co_name, co.co_filename, co.co_firstlineno, msg))
-
-
-def _wrapped_call(wrap_controller, func):
-    """ Wrap calling to a function with a generator which needs to yield
-    exactly once.  The yield point will trigger calling the wrapped function
-    and return its _CallOutcome to the yield point.  The generator then needs
-    to finish (raise StopIteration) in order for the wrapped call to complete.
-    """
-    try:
-        next(wrap_controller)   # first yield
-    except StopIteration:
-        _raise_wrapfail(wrap_controller, "did not yield")
-    call_outcome = _CallOutcome(func)
-    try:
-        wrap_controller.send(call_outcome)
-        _raise_wrapfail(wrap_controller, "has second yield")
-    except StopIteration:
-        pass
-    return call_outcome.get_result()
-
-
-class _CallOutcome:
-    """ Outcome of a function call, either an exception or a proper result.
-    Calling the ``get_result`` method will return the result or reraise
-    the exception raised when the function was called. """
-    excinfo = None
-
-    def __init__(self, func):
-        try:
-            self.result = func()
-        except BaseException:
-            self.excinfo = sys.exc_info()
-
-    def force_result(self, result):
-        self.result = result
-        self.excinfo = None
-
-    def get_result(self):
-        if self.excinfo is None:
-            return self.result
-        else:
-            ex = self.excinfo
-            if _py3:
-                raise ex[1].with_traceback(ex[2])
-            _reraise(*ex)  # noqa
-
-if not _py3:
-    exec("""
-def _reraise(cls, val, tb):
-    raise cls, val, tb
-""")
-
-
-class _TracedHookExecution:
-    def __init__(self, pluginmanager, before, after):
-        self.pluginmanager = pluginmanager
-        self.before = before
-        self.after = after
-        self.oldcall = pluginmanager._inner_hookexec
-        assert not isinstance(self.oldcall, _TracedHookExecution)
-        self.pluginmanager._inner_hookexec = self
-
-    def __call__(self, hook, hook_impls, kwargs):
-        self.before(hook.name, hook_impls, kwargs)
-        outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
-        self.after(outcome, hook.name, hook_impls, kwargs)
-        return outcome.get_result()
-
-    def undo(self):
-        self.pluginmanager._inner_hookexec = self.oldcall
-
-
-class PluginManager(object):
-    """ Core Pluginmanager class which manages registration
-    of plugin objects and 1:N hook calling.
-
-    You can register new hooks by calling ``addhooks(module_or_class)``.
-    You can register plugin objects (which contain hooks) by calling
-    ``register(plugin)``.  The Pluginmanager is initialized with a
-    prefix that is searched for in the names of the dict of registered
-    plugin objects.  An optional excludefunc allows to blacklist names which
-    are not considered as hooks despite a matching prefix.
-
-    For debugging purposes you can call ``enable_tracing()``
-    which will subsequently send debug information to the trace helper.
-    """
-
-    def __init__(self, project_name, implprefix=None):
-        """ if implprefix is given implementation functions
-        will be recognized if their name matches the implprefix. """
-        self.project_name = project_name
-        self._name2plugin = {}
-        self._plugin2hookcallers = {}
-        self._plugin_distinfo = []
-        self.trace = _TagTracer().get("pluginmanage")
-        self.hook = _HookRelay(self.trace.root.get("hook"))
-        self._implprefix = implprefix
-        self._inner_hookexec = lambda hook, methods, kwargs: \
-            _MultiCall(methods, kwargs, hook.spec_opts).execute()
-
-    def _hookexec(self, hook, methods, kwargs):
-        # called from all hookcaller instances.
-        # enable_tracing will set its own wrapping function at self._inner_hookexec
-        return self._inner_hookexec(hook, methods, kwargs)
-
-    def register(self, plugin, name=None):
-        """ Register a plugin and return its canonical name or None if the name
-        is blocked from registering.  Raise a ValueError if the plugin is already
-        registered. """
-        plugin_name = name or self.get_canonical_name(plugin)
-
-        if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers:
-            if self._name2plugin.get(plugin_name, -1) is None:
-                return  # blocked plugin, return None to indicate no registration
-            raise ValueError("Plugin already registered: %s=%s\n%s" %
-                            (plugin_name, plugin, self._name2plugin))
-
-        # XXX if an error happens we should make sure no state has been
-        # changed at point of return
-        self._name2plugin[plugin_name] = plugin
-
-        # register matching hook implementations of the plugin
-        self._plugin2hookcallers[plugin] = hookcallers = []
-        for name in dir(plugin):
-            hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
-            if hookimpl_opts is not None:
-                normalize_hookimpl_opts(hookimpl_opts)
-                method = getattr(plugin, name)
-                hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
-                hook = getattr(self.hook, name, None)
-                if hook is None:
-                    hook = _HookCaller(name, self._hookexec)
-                    setattr(self.hook, name, hook)
-                elif hook.has_spec():
-                    self._verify_hook(hook, hookimpl)
-                    hook._maybe_apply_history(hookimpl)
-                hook._add_hookimpl(hookimpl)
-                hookcallers.append(hook)
-        return plugin_name
-
-    def parse_hookimpl_opts(self, plugin, name):
-        method = getattr(plugin, name)
-        res = getattr(method, self.project_name + "_impl", None)
-        if res is not None and not isinstance(res, dict):
-            # false positive
-            res = None
-        elif res is None and self._implprefix and name.startswith(self._implprefix):
-            res = {}
-        return res
-
-    def unregister(self, plugin=None, name=None):
-        """ unregister a plugin object and all its contained hook implementations
-        from internal data structures. """
-        if name is None:
-            assert plugin is not None, "one of name or plugin needs to be specified"
-            name = self.get_name(plugin)
-
-        if plugin is None:
-            plugin = self.get_plugin(name)
-
-        # if self._name2plugin[name] == None registration was blocked: ignore
-        if self._name2plugin.get(name):
-            del self._name2plugin[name]
-
-        for hookcaller in self._plugin2hookcallers.pop(plugin, []):
-            hookcaller._remove_plugin(plugin)
-
-        return plugin
-
-    def set_blocked(self, name):
-        """ block registrations of the given name, unregister if already registered. """
-        self.unregister(name=name)
-        self._name2plugin[name] = None
-
-    def is_blocked(self, name):
-        """ return True if the name blogs registering plugins of that name. """
-        return name in self._name2plugin and self._name2plugin[name] is None
-
-    def add_hookspecs(self, module_or_class):
-        """ add new hook specifications defined in the given module_or_class.
-        Functions are recognized if they have been decorated accordingly. """
-        names = []
-        for name in dir(module_or_class):
-            spec_opts = self.parse_hookspec_opts(module_or_class, name)
-            if spec_opts is not None:
-                hc = getattr(self.hook, name, None)
-                if hc is None:
-                    hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts)
-                    setattr(self.hook, name, hc)
-                else:
-                    # plugins registered this hook without knowing the spec
-                    hc.set_specification(module_or_class, spec_opts)
-                    for hookfunction in (hc._wrappers + hc._nonwrappers):
-                        self._verify_hook(hc, hookfunction)
-                names.append(name)
-
-        if not names:
-            raise ValueError("did not find any %r hooks in %r" %
-                             (self.project_name, module_or_class))
-
-    def parse_hookspec_opts(self, module_or_class, name):
-        method = getattr(module_or_class, name)
-        return getattr(method, self.project_name + "_spec", None)
-
-    def get_plugins(self):
-        """ return the set of registered plugins. """
-        return set(self._plugin2hookcallers)
-
-    def is_registered(self, plugin):
-        """ Return True if the plugin is already registered. """
-        return plugin in self._plugin2hookcallers
-
-    def get_canonical_name(self, plugin):
-        """ Return canonical name for a plugin object. Note that a plugin
-        may be registered under a different name which was specified
-        by the caller of register(plugin, name). To obtain the name
-        of an registered plugin use ``get_name(plugin)`` instead."""
-        return getattr(plugin, "__name__", None) or str(id(plugin))
-
-    def get_plugin(self, name):
-        """ Return a plugin or None for the given name. """
-        return self._name2plugin.get(name)
-
-    def get_name(self, plugin):
-        """ Return name for registered plugin or None if not registered. """
-        for name, val in self._name2plugin.items():
-            if plugin == val:
-                return name
-
-    def _verify_hook(self, hook, hookimpl):
-        if hook.is_historic() and hookimpl.hookwrapper:
-            raise PluginValidationError(
-                "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %
-                (hookimpl.plugin_name, hook.name))
-
-        for arg in hookimpl.argnames:
-            if arg not in hook.argnames:
-                raise PluginValidationError(
-                    "Plugin %r\nhook %r\nargument %r not available\n"
-                    "plugin definition: %s\n"
-                    "available hookargs: %s" %
-                    (hookimpl.plugin_name, hook.name, arg,
-                    _formatdef(hookimpl.function), ", ".join(hook.argnames)))
-
-    def check_pending(self):
-        """ Verify that all hooks which have not been verified against
-        a hook specification are optional, otherwise raise PluginValidationError"""
-        for name in self.hook.__dict__:
-            if name[0] != "_":
-                hook = getattr(self.hook, name)
-                if not hook.has_spec():
-                    for hookimpl in (hook._wrappers + hook._nonwrappers):
-                        if not hookimpl.optionalhook:
-                            raise PluginValidationError(
-                                "unknown hook %r in plugin %r" %
-                                (name, hookimpl.plugin))
-
-    def load_setuptools_entrypoints(self, entrypoint_name):
-        """ Load modules from querying the specified setuptools entrypoint name.
-        Return the number of loaded plugins. """
-        from pkg_resources import iter_entry_points, DistributionNotFound
-        for ep in iter_entry_points(entrypoint_name):
-            # is the plugin registered or blocked?
-            if self.get_plugin(ep.name) or self.is_blocked(ep.name):
-                continue
-            try:
-                plugin = ep.load()
-            except DistributionNotFound:
-                continue
-            self.register(plugin, name=ep.name)
-            self._plugin_distinfo.append((plugin, ep.dist))
-        return len(self._plugin_distinfo)
-
-    def list_plugin_distinfo(self):
-        """ return list of distinfo/plugin tuples for all setuptools registered
-        plugins. """
-        return list(self._plugin_distinfo)
-
-    def list_name_plugin(self):
-        """ return list of name/plugin pairs. """
-        return list(self._name2plugin.items())
-
-    def get_hookcallers(self, plugin):
-        """ get all hook callers for the specified plugin. """
-        return self._plugin2hookcallers.get(plugin)
-
-    def add_hookcall_monitoring(self, before, after):
-        """ add before/after tracing functions for all hooks
-        and return an undo function which, when called,
-        will remove the added tracers.
-
-        ``before(hook_name, hook_impls, kwargs)`` will be called ahead
-        of all hook calls and receive a hookcaller instance, a list
-        of HookImpl instances and the keyword arguments for the hook call.
-
-        ``after(outcome, hook_name, hook_impls, kwargs)`` receives the
-        same arguments as ``before`` but also a :py:class:`_CallOutcome`` object
-        which represents the result of the overall hook call.
-        """
-        return _TracedHookExecution(self, before, after).undo
-
-    def enable_tracing(self):
-        """ enable tracing of hook calls and return an undo function. """
-        hooktrace = self.hook._trace
-
-        def before(hook_name, methods, kwargs):
-            hooktrace.root.indent += 1
-            hooktrace(hook_name, kwargs)
-
-        def after(outcome, hook_name, methods, kwargs):
-            if outcome.excinfo is None:
-                hooktrace("finish", hook_name, "-->", outcome.result)
-            hooktrace.root.indent -= 1
-
-        return self.add_hookcall_monitoring(before, after)
-
-    def subset_hook_caller(self, name, remove_plugins):
-        """ Return a new _HookCaller instance for the named method
-        which manages calls to all registered plugins except the
-        ones from remove_plugins. """
-        orig = getattr(self.hook, name)
-        plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
-        if plugins_to_remove:
-            hc = _HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class,
-                             orig.spec_opts)
-            for hookimpl in (orig._wrappers + orig._nonwrappers):
-                plugin = hookimpl.plugin
-                if plugin not in plugins_to_remove:
-                    hc._add_hookimpl(hookimpl)
-                    # we also keep track of this hook caller so it
-                    # gets properly removed on plugin unregistration
-                    self._plugin2hookcallers.setdefault(plugin, []).append(hc)
-            return hc
-        return orig
-
-
-class _MultiCall:
-    """ execute a call into multiple python functions/methods. """
-
-    # XXX note that the __multicall__ argument is supported only
-    # for pytest compatibility reasons.  It was never officially
-    # supported there and is explicitly deprecated since 2.8
-    # so we can remove it soon, allowing to avoid the below recursion
-    # in execute() and simplify/speed up the execute loop.
-
-    def __init__(self, hook_impls, kwargs, specopts={}):
-        self.hook_impls = hook_impls
-        self.kwargs = kwargs
-        self.kwargs["__multicall__"] = self
-        self.specopts = specopts
-
-    def execute(self):
-        all_kwargs = self.kwargs
-        self.results = results = []
-        firstresult = self.specopts.get("firstresult")
-
-        while self.hook_impls:
-            hook_impl = self.hook_impls.pop()
-            args = [all_kwargs[argname] for argname in hook_impl.argnames]
-            if hook_impl.hookwrapper:
-                return _wrapped_call(hook_impl.function(*args), self.execute)
-            res = hook_impl.function(*args)
-            if res is not None:
-                if firstresult:
-                    return res
-                results.append(res)
-
-        if not firstresult:
-            return results
-
-    def __repr__(self):
-        status = "%d meths" % (len(self.hook_impls),)
-        if hasattr(self, "results"):
-            status = ("%d results, " % len(self.results)) + status
-        return "<_MultiCall %s, kwargs=%r>" % (status, self.kwargs)
-
-
-def varnames(func, startindex=None):
-    """ return argument name tuple for a function, method, class or callable.
-
-    In case of a class, its "__init__" method is considered.
-    For methods the "self" parameter is not included unless you are passing
-    an unbound method with Python3 (which has no supports for unbound methods)
-    """
-    cache = getattr(func, "__dict__", {})
-    try:
-        return cache["_varnames"]
-    except KeyError:
-        pass
-    if inspect.isclass(func):
-        try:
-            func = func.__init__
-        except AttributeError:
-            return ()
-        startindex = 1
-    else:
-        if not inspect.isfunction(func) and not inspect.ismethod(func):
-            func = getattr(func, '__call__', func)
-        if startindex is None:
-            startindex = int(inspect.ismethod(func))
-
-    try:
-        rawcode = func.__code__
-    except AttributeError:
-        return ()
-    try:
-        x = rawcode.co_varnames[startindex:rawcode.co_argcount]
-    except AttributeError:
-        x = ()
-    else:
-        defaults = func.__defaults__
-        if defaults:
-            x = x[:-len(defaults)]
-    try:
-        cache["_varnames"] = x
-    except TypeError:
-        pass
-    return x
-
-
-class _HookRelay:
-    """ hook holder object for performing 1:N hook calls where N is the number
-    of registered plugins.
-
-    """
-
-    def __init__(self, trace):
-        self._trace = trace
-
-
-class _HookCaller(object):
-    def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
-        self.name = name
-        self._wrappers = []
-        self._nonwrappers = []
-        self._hookexec = hook_execute
-        if specmodule_or_class is not None:
-            assert spec_opts is not None
-            self.set_specification(specmodule_or_class, spec_opts)
-
-    def has_spec(self):
-        return hasattr(self, "_specmodule_or_class")
-
-    def set_specification(self, specmodule_or_class, spec_opts):
-        assert not self.has_spec()
-        self._specmodule_or_class = specmodule_or_class
-        specfunc = getattr(specmodule_or_class, self.name)
-        argnames = varnames(specfunc, startindex=inspect.isclass(specmodule_or_class))
-        assert "self" not in argnames  # sanity check
-        self.argnames = ["__multicall__"] + list(argnames)
-        self.spec_opts = spec_opts
-        if spec_opts.get("historic"):
-            self._call_history = []
-
-    def is_historic(self):
-        return hasattr(self, "_call_history")
-
-    def _remove_plugin(self, plugin):
-        def remove(wrappers):
-            for i, method in enumerate(wrappers):
-                if method.plugin == plugin:
-                    del wrappers[i]
-                    return True
-        if remove(self._wrappers) is None:
-            if remove(self._nonwrappers) is None:
-                raise ValueError("plugin %r not found" % (plugin,))
-
-    def _add_hookimpl(self, hookimpl):
-        if hookimpl.hookwrapper:
-            methods = self._wrappers
-        else:
-            methods = self._nonwrappers
-
-        if hookimpl.trylast:
-            methods.insert(0, hookimpl)
-        elif hookimpl.tryfirst:
-            methods.append(hookimpl)
-        else:
-            # find last non-tryfirst method
-            i = len(methods) - 1
-            while i >= 0 and methods[i].tryfirst:
-                i -= 1
-            methods.insert(i + 1, hookimpl)
-
-    def __repr__(self):
-        return "<_HookCaller %r>" % (self.name,)
-
-    def __call__(self, **kwargs):
-        assert not self.is_historic()
-        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
-
-    def call_historic(self, proc=None, kwargs=None):
-        self._call_history.append((kwargs or {}, proc))
-        # historizing hooks don't return results
-        self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
-
-    def call_extra(self, methods, kwargs):
-        """ Call the hook with some additional temporarily participating
-        methods using the specified kwargs as call parameters. """
-        old = list(self._nonwrappers), list(self._wrappers)
-        for method in methods:
-            opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
-            hookimpl = HookImpl(None, "<temp>", method, opts)
-            self._add_hookimpl(hookimpl)
-        try:
-            return self(**kwargs)
-        finally:
-            self._nonwrappers, self._wrappers = old
-
-    def _maybe_apply_history(self, method):
-        if self.is_historic():
-            for kwargs, proc in self._call_history:
-                res = self._hookexec(self, [method], kwargs)
-                if res and proc is not None:
-                    proc(res[0])
-
-
-class HookImpl:
-    def __init__(self, plugin, plugin_name, function, hook_impl_opts):
-        self.function = function
-        self.argnames = varnames(self.function)
-        self.plugin = plugin
-        self.opts = hook_impl_opts
-        self.plugin_name = plugin_name
-        self.__dict__.update(hook_impl_opts)
-
-
-class PluginValidationError(Exception):
-    """ plugin failed validation. """
-
-
-if hasattr(inspect, 'signature'):
-    def _formatdef(func):
-        return "%s%s" % (
-            func.__name__,
-            str(inspect.signature(func))
-        )
-else:
-    def _formatdef(func):
-        return "%s%s" % (
-            func.__name__,
-            inspect.formatargspec(*inspect.getargspec(func))
-        )
diff --git a/tools/pytest/appveyor.yml b/tools/pytest/appveyor.yml
deleted file mode 100644
index 4b73645..0000000
--- a/tools/pytest/appveyor.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-environment:
-  COVERALLS_REPO_TOKEN:
-    secure: 2NJ5Ct55cHJ9WEg3xbSqCuv0rdgzzb6pnzOIG5OkMbTndw3wOBrXntWFoQrXiMFi
-    # this is pytest's token in coveralls.io, encrypted
-    # using pytestbot account as detailed here:
-    # https://www.appveyor.com/docs/build-configuration#secure-variables
-
-install:
-  - echo Installed Pythons
-  - dir c:\Python*
-
-  # install pypy using choco (redirect to a file and write to console in case
-  # choco install returns non-zero, because choco install python.pypy is too
-  # noisy)
-  - choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1)
-  - set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy
-  - echo PyPy installed
-  - pypy --version
-
-  - C:\Python35\python -m pip install tox
-
-build: false  # Not a C# project, build stuff at the test step instead.
-
-test_script:
-  - C:\Python35\python -m tox
-  # coveralls is not in tox's envlist, plus for PRs the secure variable
-  # is not defined so we have to check for it
-  - if defined COVERALLS_REPO_TOKEN C:\Python35\python -m tox -e coveralls
diff --git a/tools/pytest/doc/en/Makefile b/tools/pytest/doc/en/Makefile
deleted file mode 100644
index 8621f77..0000000
--- a/tools/pytest/doc/en/Makefile
+++ /dev/null
@@ -1,164 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-PAPER         =
-BUILDDIR      = _build
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-REGENDOC_ARGS := \
-	--normalize "/={8,} (.*) ={8,}/======= \1 ========/" \
-	--normalize "/_{8,} (.*) _{8,}/_______ \1 ________/" \
-	--normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \
-	--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
-
-
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
-
-    
-help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html       to make standalone HTML files"
-	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
-	@echo "  showtarget to show the pytest.org target directory"
-	@echo "  install    to install docs to pytest.org/SITETARGET"
-	@echo "  install-ldf to install the doc pdf to pytest.org/SITETARGET"
-	@echo "  regen      to regenerate pytest examples using the installed pytest"
-	@echo "  linkcheck  to check all external links for integrity"
-
-clean:
-	-rm -rf $(BUILDDIR)/*
-
-SITETARGET=$(shell ./_getdoctarget.py)
-
-showtarget:
-	@echo $(SITETARGET)
-
-install: html
-	 # for access talk to someone with login rights to
-	 # pytest-dev@pytest.org to add your ssh key
-	 rsync -avz _build/html/ pytest-dev@pytest.org:pytest.org/$(SITETARGET)
-
-installpdf: latexpdf
-	@scp $(BUILDDIR)/latex/pytest.pdf pytest-dev@pytest.org:pytest.org/$(SITETARGET)
-
-installall: clean install installpdf
-	@echo "done"
-
-regen:
-	PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
-
-html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
-	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
-	@echo
-	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
-	@echo
-	@echo "Build finished; now you can process the pickle files."
-
-json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
-	@echo
-	@echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
-	@echo
-	@echo "Build finished; now you can run HTML Help Workshop with the" \
-	      ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
-	@echo
-	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
-	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
-	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytest.qhcp"
-	@echo "To view the help file:"
-	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest.qhc"
-
-devhelp:
-	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
-	@echo
-	@echo "Build finished."
-	@echo "To view the help file:"
-	@echo "# mkdir -p $$HOME/.local/share/devhelp/pytest"
-	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest"
-	@echo "# devhelp"
-
-epub:
-	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
-	@echo
-	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo
-	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
-	@echo "Run \`make' in that directory to run these through (pdf)latex" \
-	      "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through pdflatex..."
-	make -C $(BUILDDIR)/latex all-pdf
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
-	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
-	@echo
-	@echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
-	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
-	@echo
-	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-changes:
-	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
-	@echo
-	@echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
-	@echo
-	@echo "Link check complete; look for any errors in the above output " \
-	      "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
-	@echo "Testing of doctests in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/doctest/output.txt."
-
-texinfo:
-	mkdir -p $(BUILDDIR)/texinfo
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo
-	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
-	@echo "Run \`make' in that directory to run these through makeinfo" \
-	      "(use \`make info' here to do that automatically)."
-
-info:
-	mkdir -p $(BUILDDIR)/texinfo
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo "Running Texinfo files through makeinfo..."
-	make -C $(BUILDDIR)/texinfo info
-	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
diff --git a/tools/pytest/doc/en/_getdoctarget.py b/tools/pytest/doc/en/_getdoctarget.py
deleted file mode 100755
index 20e487b..0000000
--- a/tools/pytest/doc/en/_getdoctarget.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python
-
-import py
-
-def get_version_string():
-    fn = py.path.local(__file__).join("..", "..", "..",
-                                      "_pytest", "__init__.py")
-    for line in fn.readlines():
-        if "version" in line and not line.strip().startswith('#'):
-            return eval(line.split("=")[-1])
-
-def get_minor_version_string():
-    return ".".join(get_version_string().split(".")[:2])
-
-if __name__ == "__main__":
-    print (get_minor_version_string())
diff --git a/tools/pytest/doc/en/_templates/globaltoc.html b/tools/pytest/doc/en/_templates/globaltoc.html
deleted file mode 100644
index af42719..0000000
--- a/tools/pytest/doc/en/_templates/globaltoc.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
-
-<ul>
-  <li><a href="{{ pathto('index') }}">Home</a></li>
-  <li><a href="{{ pathto('contents') }}">Contents</a></li>
-  <li><a href="{{ pathto('getting-started') }}">Install</a></li>
-  <li><a href="{{ pathto('example/index') }}">Examples</a></li>
-  <li><a href="{{ pathto('customize') }}">Customize</a></li>
-  <li><a href="{{ pathto('contact') }}">Contact</a></li>
-  <li><a href="{{ pathto('talks') }}">Talks/Posts</a></li>
-  <li><a href="{{ pathto('changelog') }}">Changelog</a></li>
-  <li><a href="{{ pathto('license') }}">License</a></li>
-</ul>
-
-{%- if display_toc %}
-  <hr>
-  {{ toc }}
-{%- endif %}
diff --git a/tools/pytest/doc/en/_templates/layout.html b/tools/pytest/doc/en/_templates/layout.html
deleted file mode 100644
index 0ce480b..0000000
--- a/tools/pytest/doc/en/_templates/layout.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends "!layout.html" %}
-{% block header %}
-<div align="center" xmlns="http://www.w3.org/1999/html" style="background-color: lightgreen;  padding: .5em">
-  <h4>
-      Want to help improve pytest?  Please 
-    <a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
-        contribute to
-    </a>
-    or 
-    <a href="announce/sprint2016.html">
-        join
-    </a>
-    our upcoming sprint in June 2016!
-
-  </h4>
-</div>
-  {{super()}}
-{% endblock %}
-{% block footer %}
-{{ super() }}
-<script type="text/javascript">
-
-  var _gaq = _gaq || [];
-  _gaq.push(['_setAccount', 'UA-7597274-13']);
-  _gaq.push(['_trackPageview']);
-
-  (function() {
-    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
-    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
-    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-  })();
-
-</script>
-{% endblock %}
diff --git a/tools/pytest/doc/en/_templates/links.html b/tools/pytest/doc/en/_templates/links.html
deleted file mode 100644
index 200258e..0000000
--- a/tools/pytest/doc/en/_templates/links.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<h3>Useful Links</h3>
-<ul>
-  <li>
-    <a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
-      <b>Sprint funding campaign</b>
-    </a>
-  </li>
-  <li><a href="{{ pathto('index') }}">The pytest Website</a></li>
-  <li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li>
-  <li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li>
-  <li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
-  <li><a href="http://plugincompat.herokuapp.com/">3rd party plugins</a></li>
-  <li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
-  <li><a href="http://pytest.org/latest/pytest.pdf">PDF Documentation</a>
-</ul>
-
diff --git a/tools/pytest/doc/en/adopt.rst b/tools/pytest/doc/en/adopt.rst
deleted file mode 100644
index aead96e..0000000
--- a/tools/pytest/doc/en/adopt.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-
-April 2015 is "adopt pytest month"
-=============================================
-
-Are you an enthusiastic pytest user, the local testing guru in your workplace? Or are you considering using pytest for your open source project, but not sure how to get started? Then you may be interested in "adopt pytest month"!
-
-We will pair experienced pytest users with open source projects, for a month's effort of getting new development teams started with pytest.
-
-In 2015 we are trying this for the first time. In February and March 2015 we will gather volunteers on both sides, in April we will do the work, and in May we will evaluate how it went. This effort is being coordinated by Brianna Laugher. If you have any questions or comments, you can raise them on the `@pytestdotorg twitter account <https://twitter.com/pytestdotorg>`_ the `issue tracker`_ or the `pytest-dev mailing list`_.
-
-
-.. _`issue tracker`: https://github.com/pytest-dev/pytest/issues/676
-.. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev
-
-
-The ideal pytest helper
------------------------------------------
-
- - will be able to commit 2-4 hours a week to working with their particular project (this might involve joining their mailing list, installing the software and exploring any existing tests, offering advice, writing some example tests)
- - feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents)
- - does not need to be an expert in every aspect!
-
-`Pytest helpers, sign up here`_! (preferably in February, hard deadline 22 March)
-
-
-.. _`Pytest helpers, sign up here`: http://goo.gl/forms/nxqAhqWt1P
-
-
-The ideal partner project
------------------------------------------
-
- - is open source, and predominantly written in Python
- - has an automated/documented install process for developers
- - has more than one core developer
- - has at least one official release (e.g. is available on pypi)
- - has the support of the core development team, in trying out pytest adoption
- - has no tests... or 100% test coverage... or somewhere in between!
-
-`Partner projects, sign up here`_! (by 22 March)
-
-
-.. _`Partner projects, sign up here`:  http://goo.gl/forms/ZGyqlHiwk3
-
-
-What does it mean to "adopt pytest"?
------------------------------------------
-
-There can be many different definitions of "success". Pytest can run many `nose and unittest`_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
-
-Progressive success might look like:
-
- - tests can be run (by pytest) without errors (there may be failures)
- - tests can be run (by pytest) without failures
- - test runner is integrated into CI server
- - existing tests are rewritten to take advantage of pytest features - this can happen in several iterations, for example:
-    - changing to native assert_ statements (pycmd_ has a script to help with that, ``pyconvert_unittest.py``)
-    - changing `setUp/tearDown methods`_ to fixtures_
-    - adding markers_
-    - other changes to reduce boilerplate
- - assess needs for future tests to be written, e.g. new fixtures, distributed_ testing tweaks
-
-"Success" should also include that the development team feels comfortable with their knowledge of how to use pytest. In fact this is probably more important than anything else. So spending a lot of time on communication, giving examples, etc will probably be important - both in running the tests, and in writing them.
-
-It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
-
-.. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest
-.. _assert: asserts.html
-.. _pycmd: https://bitbucket.org/hpk42/pycmd/overview
-.. _`setUp/tearDown methods`: xunit_setup.html
-.. _fixtures: fixture.html
-.. _markers: markers.html
-.. _distributed: xdist.html
-
-
-Other ways to help
------------------------------------------
-
-Promote! Do your favourite open source Python projects use pytest? If not, why not tell them about this page?
diff --git a/tools/pytest/doc/en/announce/index.rst b/tools/pytest/doc/en/announce/index.rst
deleted file mode 100644
index 877afff..0000000
--- a/tools/pytest/doc/en/announce/index.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-
-Release announcements
-===========================================
-
-.. toctree::
-   :maxdepth: 2
-
-
-   sprint2016
-   release-2.9.0
-   release-2.8.7
-   release-2.8.6
-   release-2.8.5
-   release-2.8.4
-   release-2.8.3
-   release-2.8.2
-   release-2.7.2
-   release-2.7.1
-   release-2.7.0
-   release-2.6.3
-   release-2.6.2
-   release-2.6.1
-   release-2.6.0
-   release-2.5.2
-   release-2.5.1
-   release-2.5.0
-   release-2.4.2
-   release-2.4.1
-   release-2.4.0
-   release-2.3.5
-   release-2.3.4
-   release-2.3.3
-   release-2.3.2
-   release-2.3.1
-   release-2.3.0
-   release-2.2.4
-   release-2.2.2
-   release-2.2.1
-   release-2.2.0
-   release-2.1.3
-   release-2.1.2
-   release-2.1.1
-   release-2.1.0
-   release-2.0.3
-   release-2.0.2
-   release-2.0.1
-   release-2.0.0
-
diff --git a/tools/pytest/doc/en/announce/release-2.0.2.rst b/tools/pytest/doc/en/announce/release-2.0.2.rst
deleted file mode 100644
index 733a9f7..0000000
--- a/tools/pytest/doc/en/announce/release-2.0.2.rst
+++ /dev/null
@@ -1,73 +0,0 @@
-py.test 2.0.2: bug fixes, improved xfail/skip expressions, speed ups
-===========================================================================
-
-Welcome to pytest-2.0.2, a maintenance and bug fix release of pytest,
-a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
-and latest PyPy interpreters.  See the extensive docs with tested examples here:
-
-    http://pytest.org/
-
-If you want to install or upgrade pytest, just type one of::
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-Many thanks to all issue reporters and people asking questions
-or complaining, particularly Jurko for his insistence,
-Laura, Victor and Brianna for helping with improving
-and Ronny for his general advise.
-
-best,
-holger krekel
-
-Changes between 2.0.1 and 2.0.2
-----------------------------------------------
-
-- tackle issue32 - speed up test runs of very quick test functions
-  by reducing the relative overhead
-
-- fix issue30 - extended xfail/skipif handling and improved reporting.
-  If you have a syntax error in your skip/xfail
-  expressions you now get nice error reports.
-
-  Also you can now access module globals from xfail/skipif
-  expressions so that this for example works now::
-
-    import pytest
-    import mymodule
-    @pytest.mark.skipif("mymodule.__version__[0] == "1")
-    def test_function():
-        pass
-
-  This will not run the test function if the module's version string
-  does not start with a "1".  Note that specifying a string instead
-  of a boolean expressions allows py.test to report meaningful information
-  when summarizing a test run as to what conditions lead to skipping
-  (or xfail-ing) tests.
-
-- fix issue28 - setup_method and pytest_generate_tests work together
-  The setup_method fixture method now gets called also for
-  test function invocations generated from the pytest_generate_tests
-  hook.
-
-- fix issue27 - collectonly and keyword-selection (-k) now work together
-  Also, if you do "py.test --collectonly -q" you now get a flat list
-  of test ids that you can use to paste to the py.test commandline
-  in order to execute a particular test.
-
-- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
-
-- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
-  Starting with Python3.2 os.symlink may be supported. By requiring
-  a newer py lib version the py.path.local() implementation acknowledges
-  this.
-
-- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
-  thanks to Laura Creighton who also revieved parts of the documentation.
-
-- fix slighly wrong output of verbose progress reporting for classes
-  (thanks Amaury)
-
-- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
-
-- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
diff --git a/tools/pytest/doc/en/announce/release-2.0.3.rst b/tools/pytest/doc/en/announce/release-2.0.3.rst
deleted file mode 100644
index ed746e8..0000000
--- a/tools/pytest/doc/en/announce/release-2.0.3.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-py.test 2.0.3: bug fixes and speed ups 
-===========================================================================
-
-Welcome to pytest-2.0.3, a maintenance and bug fix release of pytest,
-a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
-and latest PyPy interpreters.  See the extensive docs with tested examples here:
-
-    http://pytest.org/
-
-If you want to install or upgrade pytest, just type one of::
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-There also is a bugfix release 1.6 of pytest-xdist, the plugin
-that enables seemless distributed and "looponfail" testing for Python.
-
-best,
-holger krekel
-
-Changes between 2.0.2 and 2.0.3
-----------------------------------------------
-
-- fix issue38: nicer tracebacks on calls to hooks, particularly early
-  configure/sessionstart ones
-
-- fix missing skip reason/meta information in junitxml files, reported
-  via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html
-
-- fix issue34: avoid collection failure with "test" prefixed classes
-  deriving from object.
-
-- don't require zlib (and other libs) for genscript plugin without
-  --genscript actually being used.
-
-- speed up skips (by not doing a full traceback represenation
-  internally)
-
-- fix issue37: avoid invalid characters in junitxml's output
-
diff --git a/tools/pytest/doc/en/announce/release-2.2.1.rst b/tools/pytest/doc/en/announce/release-2.2.1.rst
deleted file mode 100644
index f976463..0000000
--- a/tools/pytest/doc/en/announce/release-2.2.1.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-pytest-2.2.1: bug fixes, perfect teardowns
-===========================================================================
-
-
-pytest-2.2.1 is a minor backward-compatible release of the the py.test
-testing tool.   It contains bug fixes and little improvements, including
-documentation fixes.  If you are using the distributed testing
-pluginmake sure to upgrade it to pytest-xdist-1.8.
-
-For general information see here:
-
-     http://pytest.org/
-
-To install or upgrade pytest:
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-Special thanks for helping on this release to Ronny Pfannschmidt, Jurko
-Gospodnetic and Ralf Schmitt.
-
-best,
-holger krekel
-
-
-Changes between 2.2.0 and 2.2.1
-----------------------------------------
-
-- fix issue99 (in pytest and py) internallerrors with resultlog now
-  produce better output - fixed by normalizing pytest_internalerror 
-  input arguments.
-- fix issue97 / traceback issues (in pytest and py) improve traceback output
-  in conjunction with jinja2 and cython which hack tracebacks
-- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
-  the final test in a test node will now run its teardown directly
-  instead of waiting for the end of the session. Thanks Dave Hunt for
-  the good reporting and feedback.  The pytest_runtest_protocol as well
-  as the pytest_runtest_teardown hooks now have "nextitem" available 
-  which will be None indicating the end of the test run.
-- fix collection crash due to unknown-source collected items, thanks
-  to Ralf Schmitt (fixed by depending on a more recent pylib)
diff --git a/tools/pytest/doc/en/announce/release-2.2.4.rst b/tools/pytest/doc/en/announce/release-2.2.4.rst
deleted file mode 100644
index 8720bdb..0000000
--- a/tools/pytest/doc/en/announce/release-2.2.4.rst
+++ /dev/null
@@ -1,39 +0,0 @@
-pytest-2.2.4: bug fixes, better junitxml/unittest/python3 compat
-===========================================================================
-
-pytest-2.2.4 is a minor backward-compatible release of the versatile
-py.test testing tool.   It contains bug fixes and a few refinements
-to junitxml reporting, better unittest- and python3 compatibility.
-
-For general information see here:
-
-     http://pytest.org/
-
-To install or upgrade pytest:
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-Special thanks for helping on this release to Ronny Pfannschmidt
-and Benjamin Peterson and the contributors of issues.
-
-best,
-holger krekel
-
-Changes between 2.2.3 and 2.2.4
------------------------------------
-
-- fix error message for rewritten assertions involving the % operator
-- fix issue 126: correctly match all invalid xml characters for junitxml
-  binary escape
-- fix issue with unittest: now @unittest.expectedFailure markers should
-  be processed correctly (you can also use @pytest.mark markers)
-- document integration with the extended distribute/setuptools test commands
-- fix issue 140: propperly get the real functions
-  of bound classmethods for setup/teardown_class
-- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
-- fix issue #143: call unconfigure/sessionfinish always when
-  configure/sessionstart where called
-- fix issue #144: better mangle test ids to junitxml classnames
-- upgrade distribute_setup.py to 0.6.27
-
diff --git a/tools/pytest/doc/en/announce/release-2.3.0.rst b/tools/pytest/doc/en/announce/release-2.3.0.rst
deleted file mode 100644
index 54fe396..0000000
--- a/tools/pytest/doc/en/announce/release-2.3.0.rst
+++ /dev/null
@@ -1,134 +0,0 @@
-pytest-2.3: improved fixtures / better unittest integration
-=============================================================================
-
-pytest-2.3 comes with many major improvements for fixture/funcarg management 
-and parametrized testing in Python.  It is now easier, more efficient and
-more predicatable to re-run the same tests with different fixture
-instances.  Also, you can directly declare the caching "scope" of
-fixtures so that dependent tests throughout your whole test suite can
-re-use database or other expensive fixture objects with ease.  Lastly,
-it's possible for fixture functions (formerly known as funcarg
-factories) to use other fixtures, allowing for a completely modular and
-re-useable fixture design. 
-
-For detailed info and tutorial-style examples, see:
-
-    http://pytest.org/latest/fixture.html
-
-Moreover, there is now support for using pytest fixtures/funcargs with
-unittest-style suites, see here for examples:
-
-    http://pytest.org/latest/unittest.html
-
-Besides, more unittest-test suites are now expected to "simply work"
-with pytest.
-
-All changes are backward compatible and you should be able to continue
-to run your test suites and 3rd party plugins that worked with
-pytest-2.2.4.
-
-If you are interested in the precise reasoning (including examples) of the 
-pytest-2.3 fixture evolution, please consult
-http://pytest.org/latest/funcarg_compare.html
-
-For general info on installation and getting started:
-
-    http://pytest.org/latest/getting-started.html
-
-Docs and PDF access as usual at:
-
-    http://pytest.org
-
-and more details for those already in the knowing of pytest can be found
-in the CHANGELOG below.
-
-Particular thanks for this release go to Floris Bruynooghe, Alex Okrushko
-Carl Meyer, Ronny Pfannschmidt, Benjamin Peterson and Alex Gaynor for helping 
-to get the new features right and well integrated.  Ronny and Floris
-also helped to fix a number of bugs and yet more people helped by
-providing bug reports.
-
-have fun,
-holger krekel
-
-
-Changes between 2.2.4 and 2.3.0
------------------------------------
-
-- fix issue202 - better automatic names for parametrized test functions
-- fix issue139 - introduce @pytest.fixture which allows direct scoping
-  and parametrization of funcarg factories.  Introduce new @pytest.setup
-  marker to allow the writing of setup functions which accept funcargs.
-- fix issue198 - conftest fixtures were not found on windows32 in some
-  circumstances with nested directory structures due to path manipulation issues
-- fix issue193 skip test functions with were parametrized with empty
-  parameter sets
-- fix python3.3 compat, mostly reporting bits that previously depended
-  on dict ordering
-- introduce re-ordering of tests by resource and parametrization setup
-  which takes precedence to the usual file-ordering
-- fix issue185 monkeypatching time.time does not cause pytest to fail
-- fix issue172 duplicate call of pytest.setup-decoratored setup_module
-  functions
-- fix junitxml=path construction so that if tests change the
-  current working directory and the path is a relative path
-  it is constructed correctly from the original current working dir.
-- fix "python setup.py test" example to cause a proper "errno" return
-- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
-- catch unicode-issues when writing failure representations
-  to terminal to prevent the whole session from crashing
-- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
-  will now take precedence before xfail-markers because we
-  can't determine xfail/xpass status in case of a skip. see also:
-  http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
-
-- always report installed 3rd party plugins in the header of a test run
-
-- fix issue160: a failing setup of an xfail-marked tests should
-  be reported as xfail (not xpass)
-
-- fix issue128: show captured output when capsys/capfd are used
-
-- fix issue179: propperly show the dependency chain of factories
-
-- pluginmanager.register(...) now raises ValueError if the
-  plugin has been already registered or the name is taken
-
-- fix issue159: improve http://pytest.org/latest/faq.html 
-  especially with respect to the "magic" history, also mention
-  pytest-django, trial and unittest integration.
-
-- make request.keywords and node.keywords writable.  All descendant
-  collection nodes will see keyword values.  Keywords are dictionaries
-  containing markers and other info.
-
-- fix issue 178: xml binary escapes are now wrapped in py.xml.raw
-
-- fix issue 176: correctly catch the builtin AssertionError
-  even when we replaced AssertionError with a subclass on the
-  python level
-
-- factory discovery no longer fails with magic global callables
-  that provide no sane __code__ object (mock.call for example)
-
-- fix issue 182: testdir.inprocess_run now considers passed plugins
-
-- fix issue 188: ensure sys.exc_info is clear on python2
-                 before calling into a test
-
-- fix issue 191: add unittest TestCase runTest method support
-- fix issue 156: monkeypatch correctly handles class level descriptors
-
-- reporting refinements:
-
-  - pytest_report_header now receives a "startdir" so that
-    you can use startdir.bestrelpath(yourpath) to show
-    nice relative path
-
-  - allow plugins to implement both pytest_report_header and 
-    pytest_sessionstart (sessionstart is invoked first).
-
-  - don't show deselected reason line if there is none
-
-  - py.test -vv will show all of assert comparisations instead of truncating
-
diff --git a/tools/pytest/doc/en/announce/release-2.3.2.rst b/tools/pytest/doc/en/announce/release-2.3.2.rst
deleted file mode 100644
index 948b374..0000000
--- a/tools/pytest/doc/en/announce/release-2.3.2.rst
+++ /dev/null
@@ -1,57 +0,0 @@
-pytest-2.3.2: some fixes and more traceback-printing speed
-===========================================================================
-
-pytest-2.3.2 is a another stabilization release:
-
-- issue 205: fixes a regression with conftest detection
-- issue 208/29: fixes traceback-printing speed in some bad cases
-- fix teardown-ordering for parametrized setups
-- fix unittest and trial compat behaviour with respect  to runTest() methods
-- issue 206 and others: some improvements to packaging
-- fix issue127 and others: improve some docs 
-
-See 
-
-     http://pytest.org/
-
-for general information.  To install or upgrade pytest:
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-best,
-holger krekel
-
-
-Changes between 2.3.1 and 2.3.2
------------------------------------
-
-- fix issue208 and fix issue29 use new py version to avoid long pauses 
-  when printing tracebacks in long modules
-
-- fix issue205 - conftests in subdirs customizing
-  pytest_pycollect_makemodule and pytest_pycollect_makeitem
-  now work properly
-
-- fix teardown-ordering for parametrized setups
-
-- fix issue127 - better documentation for pytest_addoption
-  and related objects.
-
-- fix unittest behaviour: TestCase.runtest only called if there are
-  test methods defined
-
-- improve trial support: don't collect its empty
-  unittest.TestCase.runTest() method
-
-- "python setup.py test" now works with pytest itself
-
-- fix/improve internal/packaging related bits:
-
-  - exception message check of test_nose.py now passes on python33 as well
-
-  - issue206 - fix test_assertrewrite.py to work when a global
-    PYTHONDONTWRITEBYTECODE=1 is present
-
-  - add tox.ini to pytest distribution so that ignore-dirs and others config
-    bits are properly distributed for maintainers who run pytest-own tests
diff --git a/tools/pytest/doc/en/announce/release-2.3.3.rst b/tools/pytest/doc/en/announce/release-2.3.3.rst
deleted file mode 100644
index 1d7c702..0000000
--- a/tools/pytest/doc/en/announce/release-2.3.3.rst
+++ /dev/null
@@ -1,62 +0,0 @@
-pytest-2.3.3: integration fixes, py24 suport, ``*/**`` shown in traceback
-===========================================================================
-
-pytest-2.3.3 is a another stabilization release of the py.test tool
-which offers uebersimple assertions, scalable fixture mechanisms
-and deep customization for testing with Python.  Particularly,
-this release provides:
-
-- integration fixes and improvements related to flask, numpy, nose, 
-  unittest, mock
-
-- makes pytest work on py24 again (yes, people sometimes still need to use it)
-
-- show ``*,**`` args in pytest tracebacks
-
-Thanks to Manuel Jacob, Thomas Waldmann, Ronny Pfannschmidt, Pavel Repin
-and Andreas Taumoefolau for providing patches and all for the issues.
-
-See 
-
-     http://pytest.org/
-
-for general information.  To install or upgrade pytest:
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-best,
-holger krekel
-
-Changes between 2.3.2 and 2.3.3
------------------------------------
-
-- fix issue214 - parse modules that contain special objects like e. g.
-  flask's request object which blows up on getattr access if no request
-  is active. thanks Thomas Waldmann.
-
-- fix issue213 - allow to parametrize with values like numpy arrays that
-  do not support an __eq__ operator
-
-- fix issue215 - split test_python.org into multiple files
-
-- fix issue148 - @unittest.skip on classes is now recognized and avoids
-  calling setUpClass/tearDownClass, thanks Pavel Repin
-
-- fix issue209 - reintroduce python2.4 support by depending on newer
-  pylib which re-introduced statement-finding for pre-AST interpreters
-
-- nose support: only call setup if its a callable, thanks Andrew
-  Taumoefolau
-
-- fix issue219 - add py2.4-3.3 classifiers to TROVE list
-
-- in tracebacks *,** arg values are now shown next to normal arguments
-  (thanks Manuel Jacob)
-
-- fix issue217 - support mock.patch with pytest's fixtures - note that
-  you need either mock-1.0.1 or the python3.3 builtin unittest.mock.
-
-- fix issue127 - improve documentation for pytest_addoption() and
-  add a ``config.getoption(name)`` helper function for consistency.
-
diff --git a/tools/pytest/doc/en/announce/release-2.3.5.rst b/tools/pytest/doc/en/announce/release-2.3.5.rst
deleted file mode 100644
index c4e91e0..0000000
--- a/tools/pytest/doc/en/announce/release-2.3.5.rst
+++ /dev/null
@@ -1,97 +0,0 @@
-pytest-2.3.5: bug fixes and little improvements
-===========================================================================
-
-pytest-2.3.5 is a maintenance release with many bug fixes and little
-improvements.  See the changelog below for details.  No backward
-compatibility issues are foreseen and all plugins which worked with the
-prior version are expected to work unmodified.   Speaking of which, a
-few interesting new plugins saw the light last month:
-
-- pytest-instafail: show failure information while tests are running
-- pytest-qt: testing of GUI applications written with QT/Pyside
-- pytest-xprocess: managing external processes across test runs
-- pytest-random: randomize test ordering
-
-And several others like pytest-django saw maintenance releases.
-For a more complete list, check out 
-https://pypi.python.org/pypi?%3Aaction=search&term=pytest&submit=search.
-
-For general information see:
-
-     http://pytest.org/
-
-To install or upgrade pytest:
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-Particular thanks to Floris, Ronny, Benjamin and the many bug reporters
-and fix providers.
-
-may the fixtures be with you,
-holger krekel
-
-
-Changes between 2.3.4 and 2.3.5
------------------------------------
-
-- never consider a fixture function for test function collection
-
-- allow re-running of test items / helps to fix pytest-reruntests plugin
-  and also help to keep less fixture/resource references alive
-
-- put captured stdout/stderr into junitxml output even for passing tests
-  (thanks Adam Goucher)
-
-- Issue 265 - integrate nose setup/teardown with setupstate
-  so it doesnt try to teardown if it did not setup
-
-- issue 271 - dont write junitxml on slave nodes
-
-- Issue 274 - dont try to show full doctest example
-  when doctest does not know the example location
-
-- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
-
-- inject "getfixture()" helper to retrieve fixtures from doctests,
-  thanks Andreas Zeidler
-
-- issue 259 - when assertion rewriting, be consistent with the default
-  source encoding of ASCII on Python 2
-
-- issue 251 - report a skip instead of ignoring classes with init
-
-- issue250 unicode/str mixes in parametrization names and values now works
-
-- issue257, assertion-triggered compilation of source ending in a
-  comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6)
-
-- fix --genscript option to generate standalone scripts that also
-  work with python3.3 (importer ordering)
-
-- issue171 - in assertion rewriting, show the repr of some
-  global variables
-
-- fix option help for "-k"
-
-- move long description of distribution into README.rst
-
-- improve docstring for metafunc.parametrize()
-
-- fix bug where using capsys with pytest.set_trace() in a test
-  function would break when looking at capsys.readouterr()
-
-- allow to specify prefixes starting with "_" when 
-  customizing python_functions test discovery. (thanks Graham Horler)
-
-- improve PYTEST_DEBUG tracing output by puting
-  extra data on a new lines with additional indent
-
-- ensure OutcomeExceptions like skip/fail have initialized exception attributes
-
-- issue 260 - don't use nose special setup on plain unittest cases
-
-- fix issue134 - print the collect errors that prevent running specified test items
-
-- fix issue266 - accept unicode in MarkEvaluator expressions
-
diff --git a/tools/pytest/doc/en/announce/release-2.4.0.rst b/tools/pytest/doc/en/announce/release-2.4.0.rst
deleted file mode 100644
index 88130c4..0000000
--- a/tools/pytest/doc/en/announce/release-2.4.0.rst
+++ /dev/null
@@ -1,225 +0,0 @@
-pytest-2.4.0: new fixture features/hooks and bug fixes
-===========================================================================
-
-The just released pytest-2.4.0 brings many improvements and numerous 
-bug fixes while remaining plugin- and test-suite compatible apart
-from a few supposedly very minor incompatibilities.  See below for 
-a full list of details.  A few feature highlights:
-
-- new yield-style fixtures `pytest.yield_fixture
-  <http://pytest.org/latest/yieldfixture.html>`_, allowing to use
-  existing with-style context managers in fixture functions.
-
-- improved pdb support: ``import pdb ; pdb.set_trace()`` now works
-  without requiring prior disabling of stdout/stderr capturing.
-  Also the ``--pdb`` options works now on collection and internal errors
-  and we introduced a new experimental hook for IDEs/plugins to 
-  intercept debugging: ``pytest_exception_interact(node, call, report)``.
-
-- shorter monkeypatch variant to allow specifying an import path as
-  a target, for example: ``monkeypatch.setattr("requests.get", myfunc)``
-
-- better unittest/nose compatibility: all teardown methods are now only
-  called if the corresponding setup method succeeded.
-
-- integrate tab-completion on command line options if you
-  have `argcomplete <http://pypi.python.org/pypi/argcomplete>`_ 
-  configured.
-
-- allow boolean expression directly with skipif/xfail
-  if a "reason" is also specified.
-
-- a new hook ``pytest_load_initial_conftests`` allows plugins like
-  `pytest-django <http://pypi.python.org/pypi/pytest-django>`_ to
-  influence the environment before conftest files import ``django``.
-
-- reporting: color the last line red or green depending if
-  failures/errors occurred or everything passed.
-
-The documentation has been updated to accomodate the changes, 
-see `http://pytest.org <http://pytest.org>`_ 
-
-To install or upgrade pytest::
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-
-**Many thanks to all who helped, including Floris Bruynooghe, 
-Brianna Laugher, Andreas Pelme, Anthon van der Neut, Anatoly Bubenkoff, 
-Vladimir Keleshev, Mathieu Agopian, Ronny Pfannschmidt, Christian
-Theunert and many others.**
-
-may passing tests be with you,
-
-holger krekel
-
-Changes between 2.3.5 and 2.4
------------------------------------
-
-known incompatibilities:
-
-- if calling --genscript from python2.7 or above, you only get a
-  standalone script which works on python2.7 or above.  Use Python2.6
-  to also get a python2.5 compatible version.
-
-- all xunit-style teardown methods (nose-style, pytest-style,
-  unittest-style) will not be called if the corresponding setup method failed,
-  see issue322 below.
-
-- the pytest_plugin_unregister hook wasn't ever properly called
-  and there is no known implementation of the hook - so it got removed.
-
-- pytest.fixture-decorated functions cannot be generators (i.e. use
-  yield) anymore.  This change might be reversed in 2.4.1 if it causes
-  unforeseen real-life issues.  However, you can always write and return
-  an inner function/generator and change the fixture consumer to iterate
-  over the returned generator.  This change was done in lieu of the new
-  ``pytest.yield_fixture`` decorator, see below.
-
-new features:
-
-- experimentally introduce a new ``pytest.yield_fixture`` decorator
-  which accepts exactly the same parameters as pytest.fixture but
-  mandates a ``yield`` statement instead of a ``return statement`` from
-  fixture functions.  This allows direct integration with "with-style"
-  context managers in fixture functions and generally avoids registering
-  of finalization callbacks in favour of treating the "after-yield" as
-  teardown code.  Thanks Andreas Pelme, Vladimir Keleshev, Floris
-  Bruynooghe, Ronny Pfannschmidt and many others for discussions.
-
-- allow boolean expression directly with skipif/xfail
-  if a "reason" is also specified.  Rework skipping documentation
-  to recommend "condition as booleans" because it prevents surprises
-  when importing markers between modules.  Specifying conditions
-  as strings will remain fully supported.
-
-- reporting: color the last line red or green depending if
-  failures/errors occurred or everything passed.  thanks Christian
-  Theunert.
-
-- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no
-  "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut.
-
-- fix issue181: --pdb now also works on collect errors (and 
-  on internal errors) .  This was implemented by a slight internal 
-  refactoring and the introduction of a new hook 
-  ``pytest_exception_interact`` hook (see next item).
-
-- fix issue341: introduce new experimental hook for IDEs/terminals to 
-  intercept debugging: ``pytest_exception_interact(node, call, report)``.
-
-- new monkeypatch.setattr() variant to provide a shorter
-  invocation for patching out classes/functions from modules:
-
-     monkeypatch.setattr("requests.get", myfunc)
-
-  will replace the "get" function of the "requests" module with ``myfunc``.
-
-- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
-  Mathieu Agopian for the initial fix.  Also make all of pytest/nose
-  finalizer mimick the same generic behaviour: if a setupX exists and
-  fails, don't run teardownX.  This internally introduces a new method
-  "node.addfinalizer()" helper which can only be called during the setup
-  phase of a node.
-
-- simplify pytest.mark.parametrize() signature: allow to pass a
-  CSV-separated string to specify argnames.  For example: 
-  ``pytest.mark.parametrize("input,expected",  [(1,2), (2,3)])``
-  works as well as the previous:
-  ``pytest.mark.parametrize(("input", "expected"), ...)``.
-
-- add support for setUpModule/tearDownModule detection, thanks Brian Okken.
-
-- integrate tab-completion on options through use of "argcomplete".
-  Thanks Anthon van der Neut for the PR.
-
-- change option names to be hyphen-separated long options but keep the
-  old spelling backward compatible.  py.test -h will only show the
-  hyphenated version, for example "--collect-only" but "--collectonly"
-  will remain valid as well (for backward-compat reasons).  Many thanks to
-  Anthon van der Neut for the implementation and to Hynek Schlawack for
-  pushing us.
-
-- fix issue 308 - allow to mark/xfail/skip individual parameter sets
-  when parametrizing.  Thanks Brianna Laugher.
-
-- call new experimental pytest_load_initial_conftests hook to allow
-  3rd party plugins to do something before a conftest is loaded.
-
-Bug fixes:
-
-- fix issue358 - capturing options are now parsed more properly 
-  by using a new parser.parse_known_args method.
-
-- pytest now uses argparse instead of optparse (thanks Anthon) which 
-  means that "argparse" is added as a dependency if installing into python2.6
-  environments or below.
-
-- fix issue333: fix a case of bad unittest/pytest hook interaction.
-
-- PR27: correctly handle nose.SkipTest during collection.  Thanks
-  Antonio Cuni, Ronny Pfannschmidt.
-
-- fix issue355: junitxml puts name="pytest" attribute to testsuite tag.
-
-- fix issue336: autouse fixture in plugins should work again.
-
-- fix issue279: improve object comparisons on assertion failure
-  for standard datatypes and recognise collections.abc.  Thanks to
-  Brianna Laugher and Mathieu Agopian.
-
-- fix issue317: assertion rewriter support for the is_package method
-
-- fix issue335: document py.code.ExceptionInfo() object returned
-  from pytest.raises(), thanks Mathieu Agopian.
-
-- remove implicit distribute_setup support from setup.py.
-
-- fix issue305: ignore any problems when writing pyc files.
-
-- SO-17664702: call fixture finalizers even if the fixture function
-  partially failed (finalizers would not always be called before)
-
-- fix issue320 - fix class scope for fixtures when mixed with
-  module-level functions.  Thanks Anatloy Bubenkoff.
-
-- you can specify "-q" or "-qq" to get different levels of "quieter"
-  reporting (thanks Katarzyna Jachim)
-
-- fix issue300 - Fix order of conftest loading when starting py.test
-  in a subdirectory.
-
-- fix issue323 - sorting of many module-scoped arg parametrizations
-
-- make sessionfinish hooks execute with the same cwd-context as at
-  session start (helps fix plugin behaviour which write output files 
-  with relative path such as pytest-cov)
-
-- fix issue316 - properly reference collection hooks in docs
-
-- fix issue 306 - cleanup of -k/-m options to only match markers/test
-  names/keywords respectively.  Thanks Wouter van Ackooy.
-
-- improved doctest counting for doctests in python modules -- 
-  files without any doctest items will not show up anymore
-  and doctest examples are counted as separate test items.
-  thanks Danilo Bellini.
-
-- fix issue245 by depending on the released py-1.4.14
-  which fixes py.io.dupfile to work with files with no
-  mode. Thanks Jason R. Coombs.
-
-- fix junitxml generation when test output contains control characters,
-  addressing issue267, thanks Jaap Broekhuizen 
-
-- fix issue338: honor --tb style for setup/teardown errors as well.  Thanks Maho.
-
-- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin.
-
-- better parametrize error messages, thanks Brianna Laugher
-
-- pytest_terminal_summary(terminalreporter) hooks can now use
-  ".section(title)" and ".line(msg)" methods to print extra 
-  information at the end of a test run.
-
diff --git a/tools/pytest/doc/en/announce/release-2.5.0.rst b/tools/pytest/doc/en/announce/release-2.5.0.rst
deleted file mode 100644
index b8f28d6..0000000
--- a/tools/pytest/doc/en/announce/release-2.5.0.rst
+++ /dev/null
@@ -1,175 +0,0 @@
-pytest-2.5.0: now down to ZERO reported bugs!
-===========================================================================
-
-pytest-2.5.0 is a big fixing release, the result of two community bug
-fixing days plus numerous additional works from many people and
-reporters.  The release should be fully compatible to 2.4.2, existing
-plugins and test suites.  We aim at maintaining this level of ZERO reported 
-bugs because it's no fun if your testing tool has bugs, is it?  Under a
-condition, though: when submitting a bug report please provide
-clear information about the circumstances and a simple example which
-reproduces the problem.
-
-The issue tracker is of course not empty now.  We have many remaining
-"enhacement" issues which we'll hopefully can tackle in 2014 with your
-help.
-
-For those who use older Python versions, please note that pytest is not
-automatically tested on python2.5 due to virtualenv, setuptools and tox
-not supporting it anymore.  Manual verification shows that it mostly
-works fine but it's not going to be part of the automated release 
-process and thus likely to break in the future.
-
-As usual, current docs are at 
-
-    http://pytest.org 
-
-and you can upgrade from pypi via::
-
-    pip install -U pytest
-
-Particular thanks for helping with this release go to Anatoly Bubenkoff,
-Floris Bruynooghe, Marc Abramowitz, Ralph Schmitt, Ronny Pfannschmidt,
-Donald Stufft, James Lan, Rob Dennis, Jason R. Coombs, Mathieu Agopian,
-Virgil Dupras, Bruno Oliveira, Alex Gaynor and others.
-
-have fun,
-holger krekel
-
-
-2.5.0
------------------------------------
-
-- dropped python2.5 from automated release testing of pytest itself 
-  which means it's probably going to break soon (but still works 
-  with this release we believe).
-
-- simplified and fixed implementation for calling finalizers when
-  parametrized fixtures or function arguments are involved.  finalization 
-  is now performed lazily at setup time instead of in the "teardown phase".
-  While this might sound odd at first, it helps to ensure that we are 
-  correctly handling setup/teardown even in complex code.  User-level code
-  should not be affected unless it's implementing the pytest_runtest_teardown
-  hook and expecting certain fixture instances are torn down within (very
-  unlikely and would have been unreliable anyway).
-
-- PR90: add --color=yes|no|auto option to force terminal coloring 
-  mode ("auto" is default).  Thanks Marc Abramowitz.
-
-- fix issue319 - correctly show unicode in assertion errors.  Many
-  thanks to Floris Bruynooghe for the complete PR.  Also means
-  we depend on py>=1.4.19 now.
-
-- fix issue396 - correctly sort and finalize class-scoped parametrized 
-  tests independently from number of methods on the class.  
-
-- refix issue323 in a better way -- parametrization should now never
-  cause Runtime Recursion errors because the underlying algorithm
-  for re-ordering tests per-scope/per-fixture is not recursive
-  anymore (it was tail-call recursive before which could lead
-  to problems for more than >966 non-function scoped parameters).
-
-- fix issue290 - there is preliminary support now for parametrizing
-  with repeated same values (sometimes useful to to test if calling 
-  a second time works as with the first time).
-
-- close issue240 - document precisely how pytest module importing
-  works, discuss the two common test directory layouts, and how it 
-  interacts with PEP420-namespace packages.
-
-- fix issue246 fix finalizer order to be LIFO on independent fixtures
-  depending on a parametrized higher-than-function scoped fixture. 
-  (was quite some effort so please bear with the complexity of this sentence :)
-  Thanks Ralph Schmitt for the precise failure example.
- 
-- fix issue244 by implementing special index for parameters to only use
-  indices for paramentrized test ids
-
-- fix issue287 by running all finalizers but saving the exception
-  from the first failing finalizer and re-raising it so teardown will
-  still have failed.  We reraise the first failing exception because
-  it might be the cause for other finalizers to fail.
-
-- fix ordering when mock.patch or other standard decorator-wrappings
-  are used with test methods.  This fixues issue346 and should
-  help with random "xdist" collection failures.  Thanks to
-  Ronny Pfannschmidt and Donald Stufft for helping to isolate it.
-
-- fix issue357 - special case "-k" expressions to allow for
-  filtering with simple strings that are not valid python expressions.
-  Examples: "-k 1.3" matches all tests parametrized with 1.3.
-  "-k None" filters all tests that have "None" in their name
-  and conversely "-k 'not None'". 
-  Previously these examples would raise syntax errors.
-  
-- fix issue384 by removing the trial support code
-  since the unittest compat enhancements allow
-  trial to handle it on its own
-
-- don't hide an ImportError when importing a plugin produces one.
-  fixes issue375.
-
-- fix issue275 - allow usefixtures and autouse fixtures 
-  for running doctest text files.
-
-- fix issue380 by making --resultlog only rely on longrepr instead
-  of the "reprcrash" attribute which only exists sometimes.
-
-- address issue122: allow @pytest.fixture(params=iterator) by exploding
-  into a list early on.
-
-- fix pexpect-3.0 compatibility for pytest's own tests.
-  (fixes issue386)
-
-- allow nested parametrize-value markers, thanks James Lan for the PR.
-
-- fix unicode handling with new monkeypatch.setattr(import_path, value)
-  API.  Thanks Rob Dennis.  Fixes issue371.
-
-- fix unicode handling with junitxml, fixes issue368.
-
-- In assertion rewriting mode on Python 2, fix the detection of coding
-  cookies. See issue #330.
-
-- make "--runxfail" turn imperative pytest.xfail calls into no ops
-  (it already did neutralize pytest.mark.xfail markers)
-
-- refine pytest / pkg_resources interactions: The AssertionRewritingHook
-  PEP302 compliant loader now registers itself with setuptools/pkg_resources 
-  properly so that the pkg_resources.resource_stream method works properly.
-  Fixes issue366.  Thanks for the investigations and full PR to Jason R. Coombs.
-
-- pytestconfig fixture is now session-scoped as it is the same object during the
-  whole test run.  Fixes issue370. 
-
-- avoid one surprising case of marker malfunction/confusion::
-  
-      @pytest.mark.some(lambda arg: ...)
-      def test_function():
-
-  would not work correctly because pytest assumes @pytest.mark.some 
-  gets a function to be decorated already.  We now at least detect if this 
-  arg is an lambda and thus the example will work.  Thanks Alex Gaynor
-  for bringing it up.
-
-- xfail a test on pypy that checks wrong encoding/ascii (pypy does
-  not error out). fixes issue385.
-
-- internally make varnames() deal with classes's __init__,
-  although it's not needed by pytest itself atm.  Also
-  fix caching.  Fixes issue376.
-
-- fix issue221 - handle importing of namespace-package with no 
-  __init__.py properly.
-
-- refactor internal FixtureRequest handling to avoid monkeypatching.
-  One of the positive user-facing effects is that the "request" object 
-  can now be used in closures.
-
-- fixed version comparison in pytest.importskip(modname, minverstring)
-
-- fix issue377 by clarifying in the nose-compat docs that pytest
-  does not duplicate the unittest-API into the "plain" namespace.
-
-- fix verbose reporting for @mock'd test functions
-
diff --git a/tools/pytest/doc/en/announce/release-2.5.2.rst b/tools/pytest/doc/en/announce/release-2.5.2.rst
deleted file mode 100644
index 9308ffd..0000000
--- a/tools/pytest/doc/en/announce/release-2.5.2.rst
+++ /dev/null
@@ -1,64 +0,0 @@
-pytest-2.5.2: fixes 
-===========================================================================
-
-pytest is a mature Python testing tool with more than a 1000 tests 
-against itself, passing on many different interpreters and platforms.  
-
-The 2.5.2 release fixes a few bugs with two maybe-bugs remaining and
-actively being worked on (and waiting for the bug reporter's input).
-We also have a new contribution guide thanks to Piotr Banaszkiewicz
-and others.
-
-See docs at:
-
-    http://pytest.org
-
-As usual, you can upgrade from pypi via::
-
-    pip install -U pytest
-
-Thanks to the following people who contributed to this release:
-
-    Anatoly Bubenkov 
-    Ronny Pfannschmidt
-    Floris Bruynooghe
-    Bruno Oliveira 
-    Andreas Pelme 
-    Jurko Gospodnetić
-    Piotr Banaszkiewicz 
-    Simon Liedtke 
-    lakka 
-    Lukasz Balcerzak 
-    Philippe Muller 
-    Daniel Hahler 
-
-have fun,
-holger krekel
-
-2.5.2
------------------------------------
-
-- fix issue409 -- better interoperate with cx_freeze by not
-  trying to import from collections.abc which causes problems 
-  for py27/cx_freeze.  Thanks Wolfgang L. for reporting and tracking it down.
-
-- fixed docs and code to use "pytest" instead of "py.test" almost everywhere.
-  Thanks Jurko Gospodnetic for the complete PR.  
-
-- fix issue425: mention at end of "py.test -h" that --markers
-  and --fixtures work according to specified test path (or current dir)
-
-- fix issue413: exceptions with unicode attributes are now printed
-  correctly also on python2 and with pytest-xdist runs. (the fix
-  requires py-1.4.20)
-
-- copy, cleanup and integrate py.io capture
-  from pylib 1.4.20.dev2 (rev 13d9af95547e)
-  
-- address issue416: clarify docs as to conftest.py loading semantics
-
-- fix issue429: comparing byte strings with non-ascii chars in assert
-  expressions now work better.  Thanks Floris Bruynooghe.
-
-- make capfd/capsys.capture private, its unused and shouldnt be exposed
-
diff --git a/tools/pytest/doc/en/announce/release-2.6.3.rst b/tools/pytest/doc/en/announce/release-2.6.3.rst
deleted file mode 100644
index 13fae31..0000000
--- a/tools/pytest/doc/en/announce/release-2.6.3.rst
+++ /dev/null
@@ -1,52 +0,0 @@
-pytest-2.6.3: fixes and little improvements
-===========================================================================
-
-pytest is a mature Python testing tool with more than a 1100 tests
-against itself, passing on many different interpreters and platforms.
-This release is drop-in compatible to 2.5.2 and 2.6.X.
-See below for the changes and see docs at:
-
-    http://pytest.org
-
-As usual, you can upgrade from pypi via::
-
-    pip install -U pytest
-
-Thanks to all who contributed, among them:
-
-    Floris Bruynooghe
-    Oleg Sinyavskiy
-    Uwe Schmitt
-    Charles Cloud
-    Wolfgang Schnerring
-
-have fun,
-holger krekel
-
-Changes 2.6.3
-======================
-
-- fix issue575: xunit-xml was reporting collection errors as failures
-  instead of errors, thanks Oleg Sinyavskiy.
-
-- fix issue582: fix setuptools example, thanks Laszlo Papp and Ronny
-  Pfannschmidt.
-
-- Fix infinite recursion bug when pickling capture.EncodedFile, thanks
-  Uwe Schmitt.
-
-- fix issue589: fix bad interaction with numpy and others when showing
-  exceptions.  Check for precise "maximum recursion depth exceed" exception
-  instead of presuming any RuntimeError is that one (implemented in py
-  dep).  Thanks Charles Cloud for analysing the issue.
-
-- fix conftest related fixture visibility issue: when running with a
-  CWD outside a test package pytest would get fixture discovery wrong.
-  Thanks to Wolfgang Schnerring for figuring out a reproducable example.
-
-- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the
-  timeout when interactively entering pdb).  Thanks Wolfgang Schnerring.
-
-- check xfail/skip also with non-python function test items. Thanks
-  Floris Bruynooghe.
-
diff --git a/tools/pytest/doc/en/announce/release-2.7.0.rst b/tools/pytest/doc/en/announce/release-2.7.0.rst
deleted file mode 100644
index 07ae44c..0000000
--- a/tools/pytest/doc/en/announce/release-2.7.0.rst
+++ /dev/null
@@ -1,101 +0,0 @@
-pytest-2.7.0: fixes, features, speed improvements
-===========================================================================
-
-pytest is a mature Python testing tool with more than a 1100 tests
-against itself, passing on many different interpreters and platforms.
-This release is supposed to be drop-in compatible to 2.6.X.
-
-See below for the changes and see docs at:
-
-    http://pytest.org
-
-As usual, you can upgrade from pypi via::
-
-    pip install -U pytest
-
-Thanks to all who contributed, among them:
-
-    Anatoly Bubenkoff
-    Floris Bruynooghe
-    Brianna Laugher
-    Eric Siegerman
-    Daniel Hahler
-    Charles Cloud
-    Tom Viner
-    Holger Peters
-    Ldiary Translations
-    almarklein
-
-have fun,
-holger krekel
-
-2.7.0 (compared to 2.6.4)
------------------------------
-
-- fix issue435: make reload() work when assert rewriting is active.
-  Thanks Daniel Hahler.
-
-- fix issue616: conftest.py files and their contained fixutres are now
-  properly considered for visibility, independently from the exact
-  current working directory and test arguments that are used.
-  Many thanks to Eric Siegerman and his PR235 which contains
-  systematic tests for conftest visibility and now passes.
-  This change also introduces the concept of a ``rootdir`` which
-  is printed as a new pytest header and documented in the pytest
-  customize web page.
-
-- change reporting of "diverted" tests, i.e. tests that are collected
-  in one file but actually come from another (e.g. when tests in a test class
-  come from a base class in a different file).  We now show the nodeid
-  and indicate via a postfix the other file.
-
-- add ability to set command line options by environment variable PYTEST_ADDOPTS.
-
-- added documentation on the new pytest-dev teams on bitbucket and
-  github.  See https://pytest.org/latest/contributing.html .
-  Thanks to Anatoly for pushing and initial work on this.
-
-- fix issue650: new option ``--docttest-ignore-import-errors`` which
-  will turn import errors in doctests into skips.  Thanks Charles Cloud
-  for the complete PR.
-
-- fix issue655: work around different ways that cause python2/3
-  to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
-
-- fix issue615: assertion re-writing did not correctly escape % signs
-  when formatting boolean operations, which tripped over mixing
-  booleans with modulo operators.  Thanks to Tom Viner for the report,
-  triaging and fix.
-
-- implement issue351: add ability to specify parametrize ids as a callable
-  to generate custom test ids.  Thanks Brianna Laugher for the idea and
-  implementation.
-
-- introduce and document new hookwrapper mechanism useful for plugins
-  which want to wrap the execution of certain hooks for their purposes.
-  This supersedes the undocumented ``__multicall__`` protocol which
-  pytest itself and some external plugins use.  Note that pytest-2.8
-  is scheduled to drop supporting the old ``__multicall__``
-  and only support the hookwrapper protocol.
-
-- majorly speed up invocation of plugin hooks
-
-- use hookwrapper mechanism in builtin pytest plugins.
-
-- add a doctest ini option for doctest flags, thanks Holger Peters.
-
-- add note to docs that if you want to mark a parameter and the
-  parameter is a callable, you also need to pass in a reason to disambiguate
-  it from the "decorator" case.  Thanks Tom Viner.
-
-- "python_classes" and "python_functions" options now support glob-patterns
-  for test discovery, as discussed in issue600. Thanks Ldiary Translations.
-
-- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
-
-- fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise).
-
-- On failure, the ``sys.last_value``, ``sys.last_type`` and
-  ``sys.last_traceback`` are set, so that a user can inspect the error
-  via postmortem debugging (almarklein).
-
diff --git a/tools/pytest/doc/en/announce/release-2.7.1.rst b/tools/pytest/doc/en/announce/release-2.7.1.rst
deleted file mode 100644
index cd37cad..0000000
--- a/tools/pytest/doc/en/announce/release-2.7.1.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-pytest-2.7.1: bug fixes
-=======================
-
-pytest is a mature Python testing tool with more than a 1100 tests
-against itself, passing on many different interpreters and platforms.
-This release is supposed to be drop-in compatible to 2.7.0.
-
-See below for the changes and see docs at:
-
-    http://pytest.org
-
-As usual, you can upgrade from pypi via::
-
-    pip install -U pytest
-
-Thanks to all who contributed to this release, among them:
-
-    Bruno Oliveira
-    Holger Krekel
-    Ionel Maries Cristian
-    Floris Bruynooghe
-
-Happy testing,
-The py.test Development Team
-
-
-2.7.1 (compared to 2.7.0)
--------------------------
-
-- fix issue731: do not get confused by the braces which may be present
-  and unbalanced in an object's repr while collapsing False
-  explanations.  Thanks Carl Meyer for the report and test case.
-
-- fix issue553: properly handling inspect.getsourcelines failures in
-  FixtureLookupError which would lead to to an internal error,
-  obfuscating the original problem. Thanks talljosh for initial
-  diagnose/patch and Bruno Oliveira for final patch.
-
-- fix issue660: properly report scope-mismatch-access errors
-  independently from ordering of fixture arguments.  Also
-  avoid the pytest internal traceback which does not provide
-  information to the user. Thanks Holger Krekel.
-
-- streamlined and documented release process.  Also all versions
-  (in setup.py and documentation generation) are now read
-  from _pytest/__init__.py. Thanks Holger Krekel.
-
-- fixed docs to remove the notion that yield-fixtures are experimental.
-  They are here to stay :)  Thanks Bruno Oliveira.
-
-- Support building wheels by using environment markers for the
-  requirements.  Thanks Ionel Maries Cristian.
-
-- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
-  when tests raised SystemExit. Thanks Holger Krekel.
-
-- reintroduced _pytest fixture of the pytester plugin which is used
-  at least by pytest-xdist.
diff --git a/tools/pytest/doc/en/announce/release-2.9.0.rst b/tools/pytest/doc/en/announce/release-2.9.0.rst
deleted file mode 100644
index 99c1c63..0000000
--- a/tools/pytest/doc/en/announce/release-2.9.0.rst
+++ /dev/null
@@ -1,159 +0,0 @@
-pytest-2.9.0
-============
-
-pytest is a mature Python testing tool with more than a 1100 tests
-against itself, passing on many different interpreters and platforms.
-
-See below for the changes and see docs at:
-
-    http://pytest.org
-
-As usual, you can upgrade from pypi via::
-
-    pip install -U pytest
-
-Thanks to all who contributed to this release, among them:
-
-    Anatoly Bubenkov                                                                                                                                                                                                         
-    Bruno Oliveira                                                                                                                                                                                                           
-    Buck Golemon                                                                                                                                                                                                             
-    David Vierra                                                                                                                                                                                                             
-    Florian Bruhin                                                                                                                                                                                                           
-    Galaczi Endre                                                                                                                                                                                                            
-    Georgy Dyuldin                                                                                                                                                                                                           
-    Lukas Bednar                                                                                                                                                                                                             
-    Luke Murphy                                                                                                                                                                                                              
-    Marcin Biernat                                                                                                                                                                                                           
-    Matt Williams                                                                                                                                                                                                            
-    Michael Aquilina                                                                                                                                                                                                         
-    Raphael Pierzina                                                                                                                                                                                                         
-    Ronny Pfannschmidt                                                                                                                                                                                                       
-    Ryan Wooden                                                                                                                                                                                                              
-    Tiemo Kieft                                                                                                                                                                                                              
-    TomV                                                                                                                                                                                                                     
-    holger krekel                                                                                                                                                                                                            
-    jab   
-
-
-Happy testing,
-The py.test Development Team
-
-
-2.9.0 (compared to 2.8.7)
--------------------------
-
-**New Features**
-
-* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
-  Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_).
-
-* ``--doctest-glob`` may now be passed multiple times in the command-line.
-  Thanks `@jab`_ and `@nicoddemus`_ for the PR.
-
-* New ``-rp`` and ``-rP`` reporting options give the summary and full output
-  of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR.
-
-* ``pytest.mark.xfail`` now has a ``strict`` option which makes ``XPASS``
-  tests to fail the test suite, defaulting to ``False``. There's also a
-  ``xfail_strict`` ini option that can be used to configure it project-wise.
-  Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_).
-
-* ``Parser.addini`` now supports options of type ``bool``. Thanks
-  `@nicoddemus`_ for the PR.
-
-* New ``ALLOW_BYTES`` doctest option strips ``b`` prefixes from byte strings
-  in doctest output (similar to ``ALLOW_UNICODE``).
-  Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_).
-
-* give a hint on KeyboardInterrupt to use the --fulltrace option to show the errors,
-  this fixes `#1366`_.
-  Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR.
-
-* catch IndexError exceptions when getting exception source location. This fixes
-  pytest internal error for dynamically generated code (fixtures and tests)
-  where source lines are fake by intention
-
-**Changes**
-
-* **Important**: `py.code <http://pylib.readthedocs.org/en/latest/code.html>`_ has been
-  merged into the ``pytest`` repository as ``pytest._code``. This decision 
-  was made because ``py.code`` had very few uses outside ``pytest`` and the 
-  fact that it was in a different repository made it difficult to fix bugs on 
-  its code in a timely manner. The team hopes with this to be able to better
-  refactor out and improve that code.
-  This change shouldn't affect users, but it is useful to let users aware
-  if they encounter any strange behavior.
-  
-  Keep in mind that the code for ``pytest._code`` is **private** and 
-  **experimental**, so you definitely should not import it explicitly!
-
-  Please note that the original ``py.code`` is still available in 
-  `pylib <http://pylib.readthedocs.org>`_.
-
-* ``pytest_enter_pdb`` now optionally receives the pytest config object.
-  Thanks `@nicoddemus`_ for the PR.
-
-* Removed code and documentation for Python 2.5 or lower versions,
-  including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
-  Thanks `@nicoddemus`_ for the PR (`#1226`_).
-
-* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
-  found in the environment, even when -vv isn't used.
-  Thanks `@The-Compiler`_ for the PR.
-
-* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and
-  ``--failed-first`` respectively.
-  Thanks `@MichaelAquilina`_ for the PR.
-
-* Added expected exceptions to pytest.raises fail message
-
-* Collection only displays progress ("collecting X items") when in a terminal.
-  This avoids cluttering the output when using ``--color=yes`` to obtain
-  colors in CI integrations systems (`#1397`_).
-
-**Bug Fixes**
-
-* The ``-s`` and ``-c`` options should now work under ``xdist``;
-  ``Config.fromdictargs`` now represents its input much more faithfully.
-  Thanks to `@bukzor`_ for the complete PR (`#680`_).
-
-* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting.
-  Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR.
-
-* Fix formatting utf-8 explanation messages (`#1379`_).
-  Thanks `@biern`_ for the PR.
-
-* Fix `traceback style docs`_ to describe all of the available options
-  (auto/long/short/line/native/no), with `auto` being the default since v2.6.
-  Thanks `@hackebrot`_ for the PR.
-
-* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
-  with same name.
-  
-  
-.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
-
-.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
-.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
-.. _#1366: https://github.com/pytest-dev/pytest/issues/1366
-.. _#1040: https://github.com/pytest-dev/pytest/pull/1040
-.. _#680: https://github.com/pytest-dev/pytest/issues/680
-.. _#1287: https://github.com/pytest-dev/pytest/pull/1287
-.. _#1226: https://github.com/pytest-dev/pytest/pull/1226
-.. _#1290: https://github.com/pytest-dev/pytest/pull/1290
-.. _#1355: https://github.com/pytest-dev/pytest/pull/1355
-.. _#1397: https://github.com/pytest-dev/pytest/issues/1397
-.. _@biern: https://github.com/biern
-.. _@MichaelAquilina: https://github.com/MichaelAquilina
-.. _@bukzor: https://github.com/bukzor
-.. _@hpk42: https://github.com/hpk42
-.. _@nicoddemus: https://github.com/nicoddemus
-.. _@jab: https://github.com/jab
-.. _@codewarrior0: https://github.com/codewarrior0
-.. _@jaraco: https://github.com/jaraco
-.. _@The-Compiler: https://github.com/The-Compiler
-.. _@Shinkenjoe: https://github.com/Shinkenjoe
-.. _@tomviner: https://github.com/tomviner
-.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
-.. _@rabbbit: https://github.com/rabbbit
-.. _@hackebrot: https://github.com/hackebrot
\ No newline at end of file
diff --git a/tools/pytest/doc/en/announce/release-2.9.1.rst b/tools/pytest/doc/en/announce/release-2.9.1.rst
deleted file mode 100644
index 05a4484..0000000
--- a/tools/pytest/doc/en/announce/release-2.9.1.rst
+++ /dev/null
@@ -1,65 +0,0 @@
-pytest-2.9.1
-============
-
-pytest is a mature Python testing tool with more than a 1100 tests
-against itself, passing on many different interpreters and platforms.
-
-See below for the changes and see docs at:
-
-    http://pytest.org
-
-As usual, you can upgrade from pypi via::
-
-    pip install -U pytest
-
-Thanks to all who contributed to this release, among them:
-
-    Bruno Oliveira                                                                                                                                                                                                                            
-    Daniel Hahler                                                                                                                                                                                                                             
-    Dmitry Malinovsky                                                                                                                                                                                                                         
-    Florian Bruhin                                                                                                                                                                                                                            
-    Floris Bruynooghe                                                                                                                                                                                                                         
-    Matt Bachmann                                                                                                                                                                                                                             
-    Ronny Pfannschmidt                                                                                                                                                                                                                        
-    TomV                                                                                                                                                                                                                                      
-    Vladimir Bolshakov                                                                                                                                                                                                                        
-    Zearin                                                                                                                                                                                                                                     
-    palaviv   
-
-
-Happy testing,
-The py.test Development Team
-
-
-2.9.1 (compared to 2.9.0)
--------------------------
-
-**Bug Fixes**
-
-* Improve error message when a plugin fails to load.
-  Thanks `@nicoddemus`_ for the PR.
-
-* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_):
-  ``pytest.fail`` with non-ascii characters raises an internal pytest error.
-  Thanks `@nicoddemus`_ for the PR.
-
-* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs
-  contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_).
-
-* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors
-  containing non-ascii lines at the point of failure generated an internal
-  py.test error.
-  Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR.
-
-* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize
-  attempt to decode it as utf-8 ignoring errors.
-
-* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line.
-
-
-.. _#1437: https://github.com/pytest-dev/pytest/issues/1437
-.. _#469: https://github.com/pytest-dev/pytest/issues/469
-.. _#1431: https://github.com/pytest-dev/pytest/pull/1431
-.. _#649: https://github.com/pytest-dev/pytest/issues/649
-
-.. _@asottile: https://github.com/asottile
diff --git a/tools/pytest/doc/en/announce/sprint2016.rst b/tools/pytest/doc/en/announce/sprint2016.rst
deleted file mode 100644
index e59ccdd..0000000
--- a/tools/pytest/doc/en/announce/sprint2016.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-python testing sprint June 20th-26th 2016
-======================================================
-
-.. image:: ../img/freiburg2.jpg
-   :width: 400
-
-The pytest core group is heading towards the biggest sprint
-in its history, to take place in the black forest town Freiburg
-in Germany.  As of February 2016 we have started a `funding
-campaign on Indiegogo to cover expenses
-<http://igg.me/at/pytest-sprint/x/4034848>`_ The page also mentions
-some preliminary topics:
-
-- improving pytest-xdist test scheduling to take into account
-  fixture setups and explicit user hints.
-
-- provide info on fixture dependencies during --collect-only
-
-- tying pytest-xdist to tox so that you can do "py.test -e py34"
-  to run tests in a particular tox-managed virtualenv.  Also
-  look into making pytest-xdist use tox environments on
-  remote ssh-sides so that remote dependency management becomes
-  easier.
-
-- refactoring the fixture system so more people understand it :)
-
-- integrating PyUnit setup methods as autouse fixtures.
-  possibly adding ways to influence ordering of same-scoped
-  fixtures (so you can make a choice of which fixtures come
-  before others)
-
-- fixing bugs and issues from the tracker, really an endless source :)
-
-
-Participants
---------------
-
-Here are preliminary participants who said they are likely to come,
-given some expenses funding::
-
-    Anatoly Bubenkoff, Netherlands
-    Andreas Pelme, Personalkollen, Sweden
-    Anthony Wang, Splunk, US
-    Brianna Laugher, Australia
-    Bruno Oliveira, Brazil
-    Danielle Jenkins, Splunk, US
-    Dave Hunt, UK
-    Florian Bruhin, Switzerland
-    Floris Bruynooghe, Cobe.io, UK
-    Holger Krekel, merlinux, Germany
-    Oliver Bestwalter, Avira, Germany
-    Omar Kohl, Germany
-    Raphael Pierzina, FanDuel, UK
-    Tom Viner, UK
-
-    <your name here?>
-
-Other contributors and experienced newcomers are invited to join as well
-but please send a mail to the pytest-dev mailing list if you intend to
-do so somewhat soon, also how much funding you need if so.  And if you
-are working for a company and using pytest heavily you are welcome to
-join and we encourage your company to provide some funding for the
-sprint.  They may see it, and rightfully so, as a very cheap and deep
-training which brings you together with the experts in the field :)
-
-
-Sprint organisation, schedule
--------------------------------
-
-tentative schedule:
-
-- 19/20th arrival in Freiburg
-- 20th social get together, initial hacking
-- 21/22th full sprint days
-- 23rd break day, hiking
-- 24/25th full sprint days
-- 26th departure
-
-We might adjust according to weather to make sure that if
-we do some hiking or excursion we'll have good weather.
-Freiburg is one of the sunniest places in Germany so
-it shouldn't be too much of a constraint.
-
-
-Accomodation
-----------------
-
-We'll see to arrange for renting a flat with multiple
-beds/rooms.  Hotels are usually below 100 per night.
-The earlier we book the better.
-
-Money / funding
----------------
-
-The Indiegogo campaign asks for 11000 USD which should cover
-the costs for flights and accomodation, renting a sprint place
-and maybe a bit of food as well.
-
-If your organisation wants to support the sprint but prefers
-to give money according to an invoice, get in contact with
-holger at http://merlinux.eu who can invoice your organisation
-properly.
-
-If we have excess money we'll use for further sprint/travel
-funding for pytest/tox contributors.
diff --git a/tools/pytest/doc/en/assert.rst b/tools/pytest/doc/en/assert.rst
deleted file mode 100644
index e7f14e8..0000000
--- a/tools/pytest/doc/en/assert.rst
+++ /dev/null
@@ -1,289 +0,0 @@
-
-The writing and reporting of assertions in tests
-==================================================
-
-.. _`assertfeedback`:
-.. _`assert with the assert statement`:
-.. _`assert`:
-
-
-Asserting with the ``assert`` statement
----------------------------------------------------------
-
-``pytest`` allows you to use the standard python ``assert`` for verifying
-expectations and values in Python tests.  For example, you can write the
-following::
-
-    # content of test_assert1.py
-    def f():
-        return 3
-
-    def test_function():
-        assert f() == 4
-
-to assert that your function returns a certain value. If this assertion fails
-you will see the return value of the function call::
-
-    $ py.test test_assert1.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_assert1.py F
-    
-    ======= FAILURES ========
-    _______ test_function ________
-    
-        def test_function():
-    >       assert f() == 4
-    E       assert 3 == 4
-    E        +  where 3 = f()
-    
-    test_assert1.py:5: AssertionError
-    ======= 1 failed in 0.12 seconds ========
-
-``pytest`` has support for showing the values of the most common subexpressions
-including calls, attributes, comparisons, and binary and unary
-operators. (See :ref:`tbreportdemo`).  This allows you to use the
-idiomatic python constructs without boilerplate code while not losing
-introspection information.
-
-However, if you specify a message with the assertion like this::
-
-    assert a % 2 == 0, "value was odd, should be even"
-
-then no assertion introspection takes places at all and the message
-will be simply shown in the traceback.
-
-See :ref:`assert-details` for more information on assertion introspection.
-
-.. _`assertraises`:
-
-Assertions about expected exceptions
-------------------------------------------
-
-In order to write assertions about raised exceptions, you can use
-``pytest.raises`` as a context manager like this::
-
-    import pytest
-
-    def test_zero_division():
-        with pytest.raises(ZeroDivisionError):
-            1 / 0
-
-and if you need to have access to the actual exception info you may use::
-
-    def test_recursion_depth():
-        with pytest.raises(RuntimeError) as excinfo:
-            def f():
-                f()
-            f()
-        assert 'maximum recursion' in str(excinfo.value)
-
-``excinfo`` is a ``ExceptionInfo`` instance, which is a wrapper around
-the actual exception raised.  The main attributes of interest are
-``.type``, ``.value`` and ``.traceback``.
-
-If you want to write test code that works on Python 2.4 as well,
-you may also use two other ways to test for an expected exception::
-
-    pytest.raises(ExpectedException, func, *args, **kwargs)
-    pytest.raises(ExpectedException, "func(*args, **kwargs)")
-
-both of which execute the specified function with args and kwargs and
-asserts that the given ``ExpectedException`` is raised.  The reporter will
-provide you with helpful output in case of failures such as *no
-exception* or *wrong exception*.
-
-Note that it is also possible to specify a "raises" argument to
-``pytest.mark.xfail``, which checks that the test is failing in a more
-specific way than just having any exception raised::
-
-    @pytest.mark.xfail(raises=IndexError)
-    def test_f():
-        f()
-
-Using ``pytest.raises`` is likely to be better for cases where you are testing
-exceptions your own code is deliberately raising, whereas using
-``@pytest.mark.xfail`` with a check function is probably better for something
-like documenting unfixed bugs (where the test describes what "should" happen)
-or bugs in dependencies.
-
-
-.. _`assertwarns`:
-
-Assertions about expected warnings
------------------------------------------
-
-.. versionadded:: 2.8
-
-You can check that code raises a particular warning using
-:ref:`pytest.warns <warns>`.
-
-
-.. _newreport:
-
-Making use of context-sensitive comparisons
--------------------------------------------------
-
-.. versionadded:: 2.0
-
-``pytest`` has rich support for providing context-sensitive information
-when it encounters comparisons.  For example::
-
-    # content of test_assert2.py
-
-    def test_set_comparison():
-        set1 = set("1308")
-        set2 = set("8035")
-        assert set1 == set2
-
-if you run this module::
-
-    $ py.test test_assert2.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_assert2.py F
-    
-    ======= FAILURES ========
-    _______ test_set_comparison ________
-    
-        def test_set_comparison():
-            set1 = set("1308")
-            set2 = set("8035")
-    >       assert set1 == set2
-    E       assert set(['0', '1', '3', '8']) == set(['0', '3', '5', '8'])
-    E         Extra items in the left set:
-    E         '1'
-    E         Extra items in the right set:
-    E         '5'
-    E         Use -v to get the full diff
-    
-    test_assert2.py:5: AssertionError
-    ======= 1 failed in 0.12 seconds ========
-
-Special comparisons are done for a number of cases:
-
-* comparing long strings: a context diff is shown
-* comparing long sequences: first failing indices
-* comparing dicts: different entries
-
-See the :ref:`reporting demo <tbreportdemo>` for many more examples.
-
-Defining your own assertion comparison
-----------------------------------------------
-
-It is possible to add your own detailed explanations by implementing
-the ``pytest_assertrepr_compare`` hook.
-
-.. autofunction:: _pytest.hookspec.pytest_assertrepr_compare
-
-As an example consider adding the following hook in a conftest.py which
-provides an alternative explanation for ``Foo`` objects::
-
-   # content of conftest.py
-   from test_foocompare import Foo
-   def pytest_assertrepr_compare(op, left, right):
-       if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
-           return ['Comparing Foo instances:',
-                   '   vals: %s != %s' % (left.val, right.val)]
-
-now, given this test module::
-
-   # content of test_foocompare.py
-   class Foo:
-       def __init__(self, val):
-           self.val = val
-
-       def __eq__(self, other):
-           return self.val == other.val
-
-   def test_compare():
-       f1 = Foo(1)
-       f2 = Foo(2)
-       assert f1 == f2
-
-you can run the test module and get the custom output defined in
-the conftest file::
-
-   $ py.test -q test_foocompare.py
-   F
-   ======= FAILURES ========
-   _______ test_compare ________
-   
-       def test_compare():
-           f1 = Foo(1)
-           f2 = Foo(2)
-   >       assert f1 == f2
-   E       assert Comparing Foo instances:
-   E            vals: 1 != 2
-   
-   test_foocompare.py:11: AssertionError
-   1 failed in 0.12 seconds
-
-.. _assert-details:
-.. _`assert introspection`:
-
-Advanced assertion introspection
-----------------------------------
-
-.. versionadded:: 2.1
-
-
-Reporting details about a failing assertion is achieved either by rewriting
-assert statements before they are run or re-evaluating the assert expression and
-recording the intermediate values. Which technique is used depends on the
-location of the assert, ``pytest`` configuration, and Python version being used
-to run ``pytest``.
-
-By default, ``pytest`` rewrites assert statements in test modules.
-Rewritten assert statements put introspection information into the assertion failure message.
-``pytest`` only rewrites test modules directly discovered by its test collection process, so
-asserts in supporting modules which are not themselves test modules will not be
-rewritten.
-
-.. note::
-
-   ``pytest`` rewrites test modules on import. It does this by using an import
-   hook to write a new pyc files. Most of the time this works transparently.
-   However, if you are messing with import yourself, the import hook may
-   interfere. If this is the case, simply use ``--assert=reinterp`` or
-   ``--assert=plain``. Additionally, rewriting will fail silently if it cannot
-   write new pycs, i.e. in a read-only filesystem or a zipfile.
-
-If an assert statement has not been rewritten or the Python version is less than
-2.6, ``pytest`` falls back on assert reinterpretation. In assert
-reinterpretation, ``pytest`` walks the frame of the function containing the
-assert statement to discover sub-expression results of the failing assert
-statement. You can force ``pytest`` to always use assertion reinterpretation by
-passing the ``--assert=reinterp`` option.
-
-Assert reinterpretation has a caveat not present with assert rewriting: If
-evaluating the assert expression has side effects you may get a warning that the
-intermediate values could not be determined safely.  A common example of this
-issue is an assertion which reads from a file::
-
-        assert f.read() != '...'
-
-If this assertion fails then the re-evaluation will probably succeed!
-This is because ``f.read()`` will return an empty string when it is
-called the second time during the re-evaluation.  However, it is
-easy to rewrite the assertion and avoid any trouble::
-
-        content = f.read()
-        assert content != '...'
-
-All assert introspection can be turned off by passing ``--assert=plain``.
-
-For further information, Benjamin Peterson wrote up `Behind the scenes of pytest's new assertion rewriting <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_.
-
-.. versionadded:: 2.1
-   Add assert rewriting as an alternate introspection technique.
-
-.. versionchanged:: 2.1
-   Introduce the ``--assert`` option. Deprecate ``--no-assert`` and
-   ``--nomagic``.
diff --git a/tools/pytest/doc/en/bash-completion.rst b/tools/pytest/doc/en/bash-completion.rst
deleted file mode 100644
index b2a52fa..0000000
--- a/tools/pytest/doc/en/bash-completion.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-
-.. _bash_completion:
-
-Setting up bash completion
-==========================
-
-When using bash as your shell, ``pytest`` can use argcomplete
-(https://argcomplete.readthedocs.org/) for auto-completion.
-For this ``argcomplete`` needs to be installed **and** enabled.
-
-Install argcomplete using::
-
-        sudo pip install 'argcomplete>=0.5.7'
-
-For global activation of all argcomplete enabled python applications run::
-
-	sudo activate-global-python-argcomplete
-
-For permanent (but not global) ``pytest`` activation, use::
-
-        register-python-argcomplete py.test >> ~/.bashrc
-
-For one-time activation of argcomplete for ``pytest`` only, use::
-
-        eval "$(register-python-argcomplete py.test)"
-
-
-
diff --git a/tools/pytest/doc/en/builtin.rst b/tools/pytest/doc/en/builtin.rst
deleted file mode 100644
index b18c3f8..0000000
--- a/tools/pytest/doc/en/builtin.rst
+++ /dev/null
@@ -1,134 +0,0 @@
-
-.. _`pytest helpers`:
-
-Pytest API and builtin fixtures
-================================================
-
-This is a list of ``pytest.*`` API functions and fixtures.
-
-For information on plugin hooks and objects, see :ref:`plugins`.
-
-For information on the ``pytest.mark`` mechanism, see :ref:`mark`.
-
-For the below objects, you can also interactively ask for help, e.g. by
-typing on the Python interactive prompt something like::
-
-    import pytest
-    help(pytest)
-
-.. currentmodule:: pytest
-
-Invoking pytest interactively
----------------------------------------------------
-
-.. autofunction:: main
-
-More examples at :ref:`pytest.main-usage`
-
-
-Helpers for assertions about Exceptions/Warnings
---------------------------------------------------------
-
-.. autofunction:: raises
-
-Examples at :ref:`assertraises`.
-
-.. autofunction:: deprecated_call
-
-Raising a specific test outcome
---------------------------------------
-
-You can use the following functions in your test, fixture or setup
-functions to force a certain test outcome.  Note that most often
-you can rather use declarative marks, see :ref:`skipping`.
-
-.. autofunction:: _pytest.runner.fail
-.. autofunction:: _pytest.runner.skip
-.. autofunction:: _pytest.runner.importorskip
-.. autofunction:: _pytest.skipping.xfail
-.. autofunction:: _pytest.runner.exit
-
-fixtures and requests
------------------------------------------------------
-
-To mark a fixture function:
-
-.. autofunction:: _pytest.python.fixture
-
-Tutorial at :ref:`fixtures`.
-
-The ``request`` object that can be used from fixture functions.
-
-.. autoclass:: _pytest.python.FixtureRequest()
-    :members:
-
-
-.. _builtinfixtures:
-.. _builtinfuncargs:
-
-Builtin fixtures/function arguments
------------------------------------------
-
-You can ask for available builtin or project-custom
-:ref:`fixtures <fixtures>` by typing::
-
-    $ py.test -q --fixtures
-    cache
-        Return a cache object that can persist state between testing sessions.
-        
-        cache.get(key, default)
-        cache.set(key, value)
-        
-        Keys must be a ``/`` separated value, where the first part is usually the
-        name of your plugin or application to avoid clashes with other cache users.
-        
-        Values can be any object handled by the json stdlib module.
-    capsys
-        enables capturing of writes to sys.stdout/sys.stderr and makes
-        captured output available via ``capsys.readouterr()`` method calls
-        which return a ``(out, err)`` tuple.
-    capfd
-        enables capturing of writes to file descriptors 1 and 2 and makes
-        captured output available via ``capfd.readouterr()`` method calls
-        which return a ``(out, err)`` tuple.
-    record_xml_property
-        Fixture that adds extra xml properties to the tag for the calling test.
-        The fixture is callable with (name, value), with value being automatically
-        xml-encoded.
-    monkeypatch
-        The returned ``monkeypatch`` funcarg provides these
-        helper methods to modify objects, dictionaries or os.environ::
-        
-        monkeypatch.setattr(obj, name, value, raising=True)
-        monkeypatch.delattr(obj, name, raising=True)
-        monkeypatch.setitem(mapping, name, value)
-        monkeypatch.delitem(obj, name, raising=True)
-        monkeypatch.setenv(name, value, prepend=False)
-        monkeypatch.delenv(name, value, raising=True)
-        monkeypatch.syspath_prepend(path)
-        monkeypatch.chdir(path)
-        
-        All modifications will be undone after the requesting
-        test function has finished. The ``raising``
-        parameter determines if a KeyError or AttributeError
-        will be raised if the set/deletion operation has no target.
-    pytestconfig
-        the pytest config object with access to command line opts.
-    recwarn
-        Return a WarningsRecorder instance that provides these methods:
-        
-        * ``pop(category=None)``: return last warning matching the category.
-        * ``clear()``: clear list of warnings
-        
-        See http://docs.python.org/library/warnings.html for information
-        on warning categories.
-    tmpdir_factory
-        Return a TempdirFactory instance for the test session.
-    tmpdir
-        return a temporary directory path object
-        which is unique to each test function invocation,
-        created as a sub directory of the base temporary
-        directory.  The returned object is a `py.path.local`_
-        path object.
-    
-    no tests ran in 0.12 seconds
diff --git a/tools/pytest/doc/en/cache.rst b/tools/pytest/doc/en/cache.rst
deleted file mode 100644
index 52abb52..0000000
--- a/tools/pytest/doc/en/cache.rst
+++ /dev/null
@@ -1,278 +0,0 @@
-Cache: working with cross-testrun state
-=======================================
-
-.. versionadded:: 2.8
-
-.. warning::
-
-  The functionality of this core plugin was previously distributed
-  as a third party plugin named ``pytest-cache``.  The core plugin
-  is compatible regarding command line options and API usage except that you
-  can only store/receive data between test runs that is json-serializable.
-
-
-Usage
----------
-
-The plugin provides two command line options to rerun failures from the
-last ``py.test`` invocation:
-
-* ``--lf``, ``--last-failed`` - to only re-run the failures.
-* ``--ff``, ``--failed-first`` - to run the failures first and then the rest of
-  the tests.
-
-For cleanup (usually not needed), a ``--cache-clear`` option allows to remove
-all cross-session cache contents ahead of a test run.
-
-Other plugins may access the `config.cache`_ object to set/get 
-**json encodable** values between ``py.test`` invocations.
-
-.. note::
-
-    This plugin is enabled by default, but can be disabled if needed: see
-    :ref:`cmdunregister` (the internal name for this plugin is
-    ``cacheprovider``).
-
-
-Rerunning only failures or failures first
------------------------------------------------
-
-First, let's create 50 test invocation of which only 2 fail::
-
-    # content of test_50.py
-    import pytest
-
-    @pytest.mark.parametrize("i", range(50))
-    def test_num(i):
-        if i in (17, 25):
-           pytest.fail("bad luck")
-
-If you run this for the first time you will see two failures::
-
-    $ py.test -q
-    .................F.......F........................
-    ======= FAILURES ========
-    _______ test_num[17] ________
-    
-    i = 17
-    
-        @pytest.mark.parametrize("i", range(50))
-        def test_num(i):
-            if i in (17, 25):
-    >          pytest.fail("bad luck")
-    E          Failed: bad luck
-    
-    test_50.py:6: Failed
-    _______ test_num[25] ________
-    
-    i = 25
-    
-        @pytest.mark.parametrize("i", range(50))
-        def test_num(i):
-            if i in (17, 25):
-    >          pytest.fail("bad luck")
-    E          Failed: bad luck
-    
-    test_50.py:6: Failed
-    2 failed, 48 passed in 0.12 seconds
-
-If you then run it with ``--lf``::
-
-    $ py.test --lf
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    run-last-failure: rerun last 2 failures
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 50 items
-    
-    test_50.py FF
-    
-    ======= FAILURES ========
-    _______ test_num[17] ________
-    
-    i = 17
-    
-        @pytest.mark.parametrize("i", range(50))
-        def test_num(i):
-            if i in (17, 25):
-    >          pytest.fail("bad luck")
-    E          Failed: bad luck
-    
-    test_50.py:6: Failed
-    _______ test_num[25] ________
-    
-    i = 25
-    
-        @pytest.mark.parametrize("i", range(50))
-        def test_num(i):
-            if i in (17, 25):
-    >          pytest.fail("bad luck")
-    E          Failed: bad luck
-    
-    test_50.py:6: Failed
-    ======= 2 failed, 48 deselected in 0.12 seconds ========
-
-You have run only the two failing test from the last run, while 48 tests have
-not been run ("deselected").
-
-Now, if you run with the ``--ff`` option, all tests will be run but the first
-previous failures will be executed first (as can be seen from the series
-of ``FF`` and dots)::
-
-    $ py.test --ff
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    run-last-failure: rerun last 2 failures first
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 50 items
-    
-    test_50.py FF................................................
-    
-    ======= FAILURES ========
-    _______ test_num[17] ________
-    
-    i = 17
-    
-        @pytest.mark.parametrize("i", range(50))
-        def test_num(i):
-            if i in (17, 25):
-    >          pytest.fail("bad luck")
-    E          Failed: bad luck
-    
-    test_50.py:6: Failed
-    _______ test_num[25] ________
-    
-    i = 25
-    
-        @pytest.mark.parametrize("i", range(50))
-        def test_num(i):
-            if i in (17, 25):
-    >          pytest.fail("bad luck")
-    E          Failed: bad luck
-    
-    test_50.py:6: Failed
-    ======= 2 failed, 48 passed in 0.12 seconds ========
-
-.. _`config.cache`:
-
-The new config.cache object
---------------------------------
-
-.. regendoc:wipe
-
-Plugins or conftest.py support code can get a cached value using the
-pytest ``config`` object.  Here is a basic example plugin which
-implements a :ref:`fixture` which re-uses previously created state
-across py.test invocations::
-
-    # content of test_caching.py
-    import pytest
-    import time
-
-    @pytest.fixture
-    def mydata(request):
-        val = request.config.cache.get("example/value", None)
-        if val is None:
-            time.sleep(9*0.6) # expensive computation :)
-            val = 42
-            request.config.cache.set("example/value", val)
-        return val
-
-    def test_function(mydata):
-        assert mydata == 23
-
-If you run this command once, it will take a while because
-of the sleep::
-
-    $ py.test -q
-    F
-    ======= FAILURES ========
-    _______ test_function ________
-    
-    mydata = 42
-    
-        def test_function(mydata):
-    >       assert mydata == 23
-    E       assert 42 == 23
-    
-    test_caching.py:14: AssertionError
-    1 failed in 0.12 seconds
-
-If you run it a second time the value will be retrieved from
-the cache and this will be quick::
-
-    $ py.test -q
-    F
-    ======= FAILURES ========
-    _______ test_function ________
-    
-    mydata = 42
-    
-        def test_function(mydata):
-    >       assert mydata == 23
-    E       assert 42 == 23
-    
-    test_caching.py:14: AssertionError
-    1 failed in 0.12 seconds
-
-See the `cache-api`_ for more details.
-
-
-Inspecting Cache content
--------------------------------
-
-You can always peek at the content of the cache using the
-``--cache-clear`` command line option::
-
-    $ py.test --cache-clear
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_caching.py F
-    
-    ======= FAILURES ========
-    _______ test_function ________
-    
-    mydata = 42
-    
-        def test_function(mydata):
-    >       assert mydata == 23
-    E       assert 42 == 23
-    
-    test_caching.py:14: AssertionError
-    ======= 1 failed in 0.12 seconds ========
-
-Clearing Cache content
--------------------------------
-
-You can instruct pytest to clear all cache files and values
-by adding the ``--cache-clear`` option like this::
-
-    py.test --cache-clear
-
-This is recommended for invocations from Continous Integration
-servers where isolation and correctness is more important
-than speed.
-
-
-.. _`cache-api`:
-
-config.cache API
-------------------
-
-The ``config.cache`` object allows other plugins,
-including ``conftest.py`` files,
-to safely and flexibly store and retrieve values across
-test runs because the ``config`` object is available
-in many places.
-
-Under the hood, the cache plugin uses the simple
-dumps/loads API of the json stdlib module
-
-.. currentmodule:: _pytest.cacheprovider
-
-.. automethod:: Cache.get
-.. automethod:: Cache.set
-.. automethod:: Cache.makedir
diff --git a/tools/pytest/doc/en/capture.rst b/tools/pytest/doc/en/capture.rst
deleted file mode 100644
index 8892f5b..0000000
--- a/tools/pytest/doc/en/capture.rst
+++ /dev/null
@@ -1,118 +0,0 @@
-
-.. _`captures`:
-
-Capturing of the stdout/stderr output
-=========================================================
-
-Default stdout/stderr/stdin capturing behaviour
----------------------------------------------------------
-
-During test execution any output sent to ``stdout`` and ``stderr`` is
-captured.  If a test or a setup method fails its according captured
-output will usually be shown along with the failure traceback.
-
-In addition, ``stdin`` is set to a "null" object which will
-fail on attempts to read from it because it is rarely desired
-to wait for interactive input when running automated tests.
-
-By default capturing is done by intercepting writes to low level
-file descriptors.  This allows to capture output from simple
-print statements as well as output from a subprocess started by
-a test.
-
-Setting capturing methods or disabling capturing
--------------------------------------------------
-
-There are two ways in which ``pytest`` can perform capturing:
-
-* file descriptor (FD) level capturing (default): All writes going to the
-  operating system file descriptors 1 and 2 will be captured.
-
-* ``sys`` level capturing: Only writes to Python files ``sys.stdout``
-  and ``sys.stderr`` will be captured.  No capturing of writes to
-  filedescriptors is performed.
-
-.. _`disable capturing`:
-
-You can influence output capturing mechanisms from the command line::
-
-    py.test -s            # disable all capturing
-    py.test --capture=sys # replace sys.stdout/stderr with in-mem files
-    py.test --capture=fd  # also point filedescriptors 1 and 2 to temp file
-
-.. _printdebugging:
-
-Using print statements for debugging
----------------------------------------------------
-
-One primary benefit of the default capturing of stdout/stderr output
-is that you can use print statements for debugging::
-
-    # content of test_module.py
-
-    def setup_function(function):
-        print ("setting up %s" % function)
-
-    def test_func1():
-        assert True
-
-    def test_func2():
-        assert False
-
-and running this module will show you precisely the output
-of the failing function and hide the other one::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    
-    test_module.py .F
-    
-    ======= FAILURES ========
-    _______ test_func2 ________
-    
-        def test_func2():
-    >       assert False
-    E       assert False
-    
-    test_module.py:9: AssertionError
-    -------------------------- Captured stdout setup ---------------------------
-    setting up <function test_func2 at 0xdeadbeef>
-    ======= 1 failed, 1 passed in 0.12 seconds ========
-
-Accessing captured output from a test function
----------------------------------------------------
-
-The ``capsys`` and ``capfd`` fixtures allow to access stdout/stderr
-output created during test execution.  Here is an example test function
-that performs some output related checks:
-
-.. code-block:: python
-
-    def test_myoutput(capsys): # or use "capfd" for fd-level
-        print ("hello")
-        sys.stderr.write("world\n")
-        out, err = capsys.readouterr()
-        assert out == "hello\n"
-        assert err == "world\n"
-        print "next"
-        out, err = capsys.readouterr()
-        assert out == "next\n"
-
-The ``readouterr()`` call snapshots the output so far -
-and capturing will be continued.  After the test
-function finishes the original streams will
-be restored.  Using ``capsys`` this way frees your
-test from having to care about setting/resetting
-output streams and also interacts well with pytest's
-own per-test capturing.
-
-If you want to capture on filedescriptor level you can use
-the ``capfd`` function argument which offers the exact
-same interface but allows to also capture output from
-libraries or subprocesses that directly write to operating
-system level output streams (FD1 and FD2).
-
-.. include:: links.inc
diff --git a/tools/pytest/doc/en/conf.py b/tools/pytest/doc/en/conf.py
deleted file mode 100644
index aca0442..0000000
--- a/tools/pytest/doc/en/conf.py
+++ /dev/null
@@ -1,326 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# pytest documentation build configuration file, created by
-# sphinx-quickstart on Fri Oct  8 17:54:28 2010.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The full version, including alpha/beta/rc tags.
-# The short X.Y version.
-
-import os, sys
-sys.path.insert(0, os.path.dirname(__file__))
-import _getdoctarget
-
-version = _getdoctarget.get_minor_version_string()
-release = _getdoctarget.get_version_string()
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-autodoc_member_order = "bysource"
-todo_include_todos = 1
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
-    'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'contents'
-
-# General information about the project.
-project = u'pytest'
-copyright = u'2015, holger krekel and pytest-dev team'
-
-
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['links.inc', '_build', 'naming20.rst', 'test/*',
-    "old_*",
-    '*attic*',
-    '*/attic*',
-    'funcargs.rst',
-    'setup.rst',
-    'example/remoteinterp.rst',
-    ]
-
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-add_module_names = False
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-sys.path.append(os.path.abspath('_themes'))
-html_theme_path = ['_themes']
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-html_theme = 'flask'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-html_theme_options = {
-  'index_logo': None
-}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-html_title = None
-
-# A shorter title for the navigation bar.  Default is the same as html_title.
-html_short_title = "pytest-%s" % release
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-html_logo = "img/pytest1.png"
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-html_favicon = "img/pytest1favi.ico"
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-#html_sidebars = {'index': 'indexsidebar.html'}
-
-html_sidebars = {
-    'index': [
-        'sidebarintro.html',
-        'globaltoc.html',
-        'links.html',
-        'sourcelink.html',
-        'searchbox.html'
-    ],
-    '**': [
-        'globaltoc.html',
-        'relations.html',
-        'links.html',
-        'sourcelink.html',
-        'searchbox.html'
-    ]
-}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-#html_additional_pages = {'index': 'index.html'}
-
-
-# If false, no module index is generated.
-html_domain_indices = True
-
-# If false, no index is generated.
-html_use_index = False
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-html_show_sourcelink = False
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'pytestdoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
-  ('contents', 'pytest.tex', u'pytest Documentation',
-   u'holger krekel, trainer and consultant, http://merlinux.eu', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-latex_logo = 'img/pytest1.png'
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-latex_domain_indices = False
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
-    ('usage', 'pytest', u'pytest usage',
-     [u'holger krekel at merlinux eu'], 1)
-]
-
-
-# -- Options for Epub output ---------------------------------------------------
-
-# Bibliographic Dublin Core info.
-epub_title = u'pytest'
-epub_author = u'holger krekel at merlinux eu'
-epub_publisher = u'holger krekel at merlinux eu'
-epub_copyright = u'2013, holger krekel et alii'
-
-# The language of the text. It defaults to the language option
-# or en if the language is not set.
-#epub_language = ''
-
-# The scheme of the identifier. Typical schemes are ISBN or URL.
-#epub_scheme = ''
-
-# The unique identifier of the text. This can be a ISBN number
-# or the project homepage.
-#epub_identifier = ''
-
-# A unique identification for the text.
-#epub_uid = ''
-
-# HTML files that should be inserted before the pages created by sphinx.
-# The format is a list of tuples containing the path and title.
-#epub_pre_files = []
-
-# HTML files shat should be inserted after the pages created by sphinx.
-# The format is a list of tuples containing the path and title.
-#epub_post_files = []
-
-# A list of files that should not be packed into the epub file.
-#epub_exclude_files = []
-
-# The depth of the table of contents in toc.ncx.
-#epub_tocdepth = 3
-
-# Allow duplicate toc entries.
-#epub_tocdup = True
-
-
-# -- Options for texinfo output ------------------------------------------------
-
-texinfo_documents = [
-  (master_doc, 'pytest', 'pytest Documentation',
-   ('Holger Krekel@*Benjamin Peterson@*Ronny Pfannschmidt@*'
-    'Floris Bruynooghe@*others'),
-   'pytest',
-   'simple powerful testing with Pytho',
-   'Programming',
-   1),
-]
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'python': ('http://docs.python.org/', None),
-#                       'lib': ("http://docs.python.org/2.7library/", None),
-                    }
-
-
-def setup(app):
-    #from sphinx.ext.autodoc import cut_lines
-    #app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
-    app.add_description_unit('confval', 'confval',
-                             objname='configuration value',
-                             indextemplate='pair: %s; configuration value')
diff --git a/tools/pytest/doc/en/contact.rst b/tools/pytest/doc/en/contact.rst
deleted file mode 100644
index d4a1a03..0000000
--- a/tools/pytest/doc/en/contact.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-
-.. _`contact channels`:
-.. _`contact`:
-
-Contact channels
-===================================
-
-- `pytest issue tracker`_ to report bugs or suggest features (for version
-  2.0 and above).
-
-- `pytest on stackoverflow.com <http://stackoverflow.com/search?q=pytest>`_ 
-  to post questions with the tag ``pytest``.  New Questions will usually 
-  be seen by pytest users or developers and answered quickly. 
-
-- `Testing In Python`_: a mailing list for Python testing tools and discussion.
-
-- `pytest-dev at python.org (mailing list)`_ pytest specific announcements and discussions.
-
-- `pytest-commit at python.org (mailing list)`_: for commits and new issues
-
-- :doc:`contribution guide <contributing>` for help on submitting pull
-  requests to bitbucket (including using git via gitifyhg).
-
-- #pylib on irc.freenode.net IRC channel for random questions.
-
-- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
-
-
-- `merlinux.eu`_ offers pytest and tox-related professional teaching and
-  consulting.
-
-.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
-.. _`old issue tracker`: http://bitbucket.org/hpk42/py-trunk/issues/
-
-.. _`merlinux.eu`: http://merlinux.eu
-
-.. _`get an account`:
-
-.. _tetamap: http://tetamap.wordpress.com
-
-.. _`@pylibcommit`: http://twitter.com/pylibcommit
-
-
-.. _`Testing in Python`: http://lists.idyll.org/listinfo/testing-in-python
-.. _FOAF: http://en.wikipedia.org/wiki/FOAF
-.. _`py-dev`:
-.. _`development mailing list`:
-.. _`pytest-dev at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-dev
-.. _`py-svn`:
-.. _`pytest-commit at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-commit
-
diff --git a/tools/pytest/doc/en/contents.rst b/tools/pytest/doc/en/contents.rst
deleted file mode 100644
index 48c3471..0000000
--- a/tools/pytest/doc/en/contents.rst
+++ /dev/null
@@ -1,39 +0,0 @@
-.. _toc:
-
-Full pytest documentation
-===========================
-
-`Download latest version as PDF <pytest.pdf>`_
-
-.. `Download latest version as EPUB <http://media.readthedocs.org/epub/pytest/latest/pytest.epub>`_
-
-.. toctree::
-   :maxdepth: 2
-
-   overview
-   apiref
-   example/index
-   monkeypatch
-   tmpdir
-   capture
-   recwarn
-   cache
-   plugins
-
-   contributing
-   talks
-
-.. only:: html
-
-   .. toctree::
-
-      funcarg_compare
-      announce/index
-
-.. only:: html
-
-   .. toctree::
-      :hidden:
-
-      changelog
-
diff --git a/tools/pytest/doc/en/customize.rst b/tools/pytest/doc/en/customize.rst
deleted file mode 100644
index 34e319c..0000000
--- a/tools/pytest/doc/en/customize.rst
+++ /dev/null
@@ -1,228 +0,0 @@
-Basic test configuration
-===================================
-
-Command line options and configuration file settings
------------------------------------------------------------------
-
-You can get help on command line options and values in INI-style
-configurations files by using the general help option::
-
-    py.test -h   # prints options _and_ config file settings
-
-This will display command line and configuration file settings
-which were registered by installed plugins.
-
-.. _rootdir:
-.. _inifiles:
-
-initialization: determining rootdir and inifile
------------------------------------------------
-
-.. versionadded:: 2.7
-
-pytest determines a "rootdir" for each test run which depends on
-the command line arguments (specified test files, paths) and on
-the existence of inifiles.  The determined rootdir and ini-file are
-printed as part of the pytest header.  The rootdir is used for constructing
-"nodeids" during collection and may also be used by plugins to store
-project/testrun-specific information.
-
-Here is the algorithm which finds the rootdir from ``args``:
-
-- determine the common ancestor directory for the specified ``args``.
-
-- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the
-  ancestor directory and upwards.  If one is matched, it becomes the
-  ini-file and its directory becomes the rootdir.  An existing
-  ``pytest.ini`` file will always be considered a match whereas
-  ``tox.ini`` and ``setup.cfg`` will only match if they contain
-  a ``[pytest]`` section.
-
-- if no ini-file was found, look for ``setup.py`` upwards from
-  the common ancestor directory to determine the ``rootdir``.
-
-- if no ini-file and no ``setup.py`` was found, use the already
-  determined common ancestor as root directory.  This allows to
-  work with pytest in structures that are not part of a package
-  and don't have any particular ini-file configuration.
-
-Note that options from multiple ini-files candidates are never merged,
-the first one wins (``pytest.ini`` always wins even if it does not
-contain a ``[pytest]`` section).
-
-The ``config`` object will subsequently carry these attributes:
-
-- ``config.rootdir``: the determined root directory, guaranteed to exist.
-
-- ``config.inifile``: the determined ini-file, may be ``None``.
-
-The rootdir is used a reference directory for constructing test
-addresses ("nodeids") and can be used also by plugins for storing
-per-testrun information.
-
-Example::
-
-    py.test path/to/testdir path/other/
-
-will determine the common ancestor as ``path`` and then
-check for ini-files as follows::
-
-    # first look for pytest.ini files
-    path/pytest.ini
-    path/setup.cfg  # must also contain [pytest] section to match
-    path/tox.ini    # must also contain [pytest] section to match
-    pytest.ini
-    ... # all the way down to the root
-
-    # now look for setup.py
-    path/setup.py
-    setup.py
-    ... # all the way down to the root
-
-
-.. _`how to change command line options defaults`:
-.. _`adding default options`:
-
-How to change command line options defaults
-------------------------------------------------
-
-It can be tedious to type the same series of command line options
-every time you use ``pytest``.  For example, if you always want to see
-detailed info on skipped and xfailed tests, as well as have terser "dot"
-progress output, you can write it into a configuration file:
-
-.. code-block:: ini
-
-    # content of pytest.ini
-    # (or tox.ini or setup.cfg)
-    [pytest]
-    addopts = -rsxX -q
-
-Alternatively, you can set a PYTEST_ADDOPTS environment variable to add command
-line options while the environment is in use::
-
-    export PYTEST_ADDOPTS="-rsxX -q"
-
-From now on, running ``pytest`` will add the specified options.
-
-
-
-Builtin configuration file options
-----------------------------------------------
-
-.. confval:: minversion
-
-   Specifies a minimal pytest version required for running tests.
-
-        minversion = 2.1  # will fail if we run with pytest-2.0
-
-.. confval:: addopts
-
-   Add the specified ``OPTS`` to the set of command line arguments as if they
-   had been specified by the user. Example: if you have this ini file content:
-
-   .. code-block:: ini
-
-        [pytest]
-        addopts = --maxfail=2 -rf  # exit after 2 failures, report fail info
-
-   issuing ``py.test test_hello.py`` actually means::
-
-        py.test --maxfail=2 -rf test_hello.py
-
-   Default is to add no options.
-
-.. confval:: norecursedirs
-
-   Set the directory basename patterns to avoid when recursing
-   for test discovery.  The individual (fnmatch-style) patterns are
-   applied to the basename of a directory to decide if to recurse into it.
-   Pattern matching characters::
-
-        *       matches everything
-        ?       matches any single character
-        [seq]   matches any character in seq
-        [!seq]  matches any char not in seq
-
-   Default patterns are ``'.*', 'CVS', '_darcs', '{arch}', '*.egg'``.
-   Setting a ``norecursedirs`` replaces the default.  Here is an example of
-   how to avoid certain directories:
-
-   .. code-block:: ini
-
-        # content of setup.cfg
-        [pytest]
-        norecursedirs = .svn _build tmp*
-
-   This would tell ``pytest`` to not look into typical subversion or
-   sphinx-build directories or into any ``tmp`` prefixed directory.
-
-.. confval:: testpaths
-
-   .. versionadded:: 2.8
-
-   Sets list of directories that should be searched for tests when
-   no specific directories, files or test ids are given in the command line when
-   executing pytest from the :ref:`rootdir <rootdir>` directory.
-   Useful when all project tests are in a known location to speed up
-   test collection and to avoid picking up undesired tests by accident.
-
-   .. code-block:: ini
-
-        # content of pytest.ini
-        [pytest]
-        testpaths = testing doc
-
-   This tells pytest to only look for tests in ``testing`` and ``doc``
-   directories when executing from the root directory.
-
-.. confval:: python_files
-
-   One or more Glob-style file patterns determining which python files
-   are considered as test modules.
-
-.. confval:: python_classes
-
-   One or more name prefixes or glob-style patterns determining which classes
-   are considered for test collection. Here is an example of how to collect
-   tests from classes that end in ``Suite``:
-
-   .. code-block:: ini
-
-        # content of pytest.ini
-        [pytest]
-        python_classes = *Suite
-
-   Note that ``unittest.TestCase`` derived classes are always collected
-   regardless of this option, as ``unittest``'s own collection framework is used
-   to collect those tests.
-
-.. confval:: python_functions
-
-   One or more name prefixes or glob-patterns determining which test functions
-   and methods are considered tests. Here is an example of how
-   to collect test functions and methods that end in ``_test``:
-
-   .. code-block:: ini
-
-        # content of pytest.ini
-        [pytest]
-        python_functions = *_test
-
-   Note that this has no effect on methods that live on a ``unittest
-   .TestCase`` derived class, as ``unittest``'s own collection framework is used
-   to collect those tests.
-
-   See :ref:`change naming conventions` for more detailed examples.
-
-.. confval:: doctest_optionflags
-
-   One or more doctest flag names from the standard ``doctest`` module.
-   :doc:`See how py.test handles doctests <doctest>`.
-
-.. confval:: confcutdir
-
-   Sets a directory where search upwards for ``conftest.py`` files stops.
-   By default, pytest will stop searching for ``conftest.py`` files upwards
-   from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
-   or up to the file-system root.
diff --git a/tools/pytest/doc/en/doctest.rst b/tools/pytest/doc/en/doctest.rst
deleted file mode 100644
index db76414..0000000
--- a/tools/pytest/doc/en/doctest.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-
-Doctest integration for modules and test files
-=========================================================
-
-By default all files matching the ``test*.txt`` pattern will
-be run through the python standard ``doctest`` module.  You
-can change the pattern by issuing::
-
-    py.test --doctest-glob='*.rst'
-
-on the command line. Since version ``2.9``, ``--doctest-glob``
-can be given multiple times in the command-line.
-
-You can also trigger running of doctests
-from docstrings in all python modules (including regular
-python test modules)::
-
-    py.test --doctest-modules
-
-You can make these changes permanent in your project by
-putting them into a pytest.ini file like this:
-
-.. code-block:: ini
-
-    # content of pytest.ini
-    [pytest]
-    addopts = --doctest-modules
-
-If you then have a text file like this::
-
-    # content of example.rst
-
-    hello this is a doctest
-    >>> x = 3
-    >>> x
-    3
-
-and another like this::
-
-    # content of mymodule.py
-    def something():
-        """ a doctest in a docstring
-        >>> something()
-        42
-        """
-        return 42
-
-then you can just invoke ``py.test`` without command line options::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
-    collected 1 items
-    
-    mymodule.py .
-    
-    ======= 1 passed in 0.12 seconds ========
-
-It is possible to use fixtures using the ``getfixture`` helper::
-
-    # content of example.rst
-    >>> tmp = getfixture('tmpdir')
-    >>> ...
-    >>>
-
-Also, :ref:`usefixtures` and :ref:`autouse` fixtures are supported
-when executing text doctest files.
-
-The standard ``doctest`` module provides some setting flags to configure the
-strictness of doctest tests. In py.test You can enable those flags those flags
-using the configuration file. To make pytest ignore trailing whitespaces and
-ignore lengthy exception stack traces you can just write:
-
-.. code-block:: ini
-
-    [pytest]
-    doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
-
-py.test also introduces new options to allow doctests to run in Python 2 and
-Python 3 unchanged:
-
-* ``ALLOW_UNICODE``: when enabled, the ``u`` prefix is stripped from unicode
-  strings in expected doctest output.
-
-* ``ALLOW_BYTES``: when enabled, the ``b`` prefix is stripped from byte strings
-  in expected doctest output.
-
-As with any other option flag, these flags can be enabled in ``pytest.ini`` using
-the ``doctest_optionflags`` ini option:
-
-.. code-block:: ini
-
-    [pytest]
-    doctest_optionflags = ALLOW_UNICODE ALLOW_BYTES
-
-
-Alternatively, it can be enabled by an inline comment in the doc test
-itself::
-
-    # content of example.rst
-    >>> get_unicode_greeting()  # doctest: +ALLOW_UNICODE
-    'Hello'
-
-
diff --git a/tools/pytest/doc/en/example/assertion/failure_demo.py b/tools/pytest/doc/en/example/assertion/failure_demo.py
deleted file mode 100644
index a4ff758..0000000
--- a/tools/pytest/doc/en/example/assertion/failure_demo.py
+++ /dev/null
@@ -1,238 +0,0 @@
-from pytest import raises
-import _pytest._code
-import py
-
-def otherfunc(a,b):
-    assert a==b
-
-def somefunc(x,y):
-    otherfunc(x,y)
-
-def otherfunc_multi(a,b):
-    assert (a ==
-            b)
-
-def test_generative(param1, param2):
-    assert param1 * 2 < param2
-
-def pytest_generate_tests(metafunc):
-    if 'param1' in metafunc.fixturenames:
-        metafunc.addcall(funcargs=dict(param1=3, param2=6))
-
-class TestFailing(object):
-    def test_simple(self):
-        def f():
-            return 42
-        def g():
-            return 43
-
-        assert f() == g()
-
-    def test_simple_multiline(self):
-        otherfunc_multi(
-                  42,
-                  6*9)
-
-    def test_not(self):
-        def f():
-            return 42
-        assert not f()
-
-class TestSpecialisedExplanations(object):
-    def test_eq_text(self):
-        assert 'spam' == 'eggs'
-
-    def test_eq_similar_text(self):
-        assert 'foo 1 bar' == 'foo 2 bar'
-
-    def test_eq_multiline_text(self):
-        assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
-
-    def test_eq_long_text(self):
-        a = '1'*100 + 'a' + '2'*100
-        b = '1'*100 + 'b' + '2'*100
-        assert a == b
-
-    def test_eq_long_text_multiline(self):
-        a = '1\n'*100 + 'a' + '2\n'*100
-        b = '1\n'*100 + 'b' + '2\n'*100
-        assert a == b
-
-    def test_eq_list(self):
-        assert [0, 1, 2] == [0, 1, 3]
-
-    def test_eq_list_long(self):
-        a = [0]*100 + [1] + [3]*100
-        b = [0]*100 + [2] + [3]*100
-        assert a == b
-
-    def test_eq_dict(self):
-        assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
-
-    def test_eq_set(self):
-        assert set([0, 10, 11, 12]) == set([0, 20, 21])
-
-    def test_eq_longer_list(self):
-        assert [1,2] == [1,2,3]
-
-    def test_in_list(self):
-        assert 1 in [0, 2, 3, 4, 5]
-
-    def test_not_in_text_multiline(self):
-        text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
-        assert 'foo' not in text
-
-    def test_not_in_text_single(self):
-        text = 'single foo line'
-        assert 'foo' not in text
-
-    def test_not_in_text_single_long(self):
-        text = 'head ' * 50 + 'foo ' + 'tail ' * 20
-        assert 'foo' not in text
-
-    def test_not_in_text_single_long_term(self):
-        text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
-        assert 'f'*70 not in text
-
-
-def test_attribute():
-    class Foo(object):
-        b = 1
-    i = Foo()
-    assert i.b == 2
-
-
-def test_attribute_instance():
-    class Foo(object):
-        b = 1
-    assert Foo().b == 2
-
-
-def test_attribute_failure():
-    class Foo(object):
-        def _get_b(self):
-            raise Exception('Failed to get attrib')
-        b = property(_get_b)
-    i = Foo()
-    assert i.b == 2
-
-
-def test_attribute_multiple():
-    class Foo(object):
-        b = 1
-    class Bar(object):
-        b = 2
-    assert Foo().b == Bar().b
-
-
-def globf(x):
-    return x+1
-
-class TestRaises:
-    def test_raises(self):
-        s = 'qwe'
-        raises(TypeError, "int(s)")
-
-    def test_raises_doesnt(self):
-        raises(IOError, "int('3')")
-
-    def test_raise(self):
-        raise ValueError("demo error")
-
-    def test_tupleerror(self):
-        a,b = [1]
-
-    def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
-        l = [1,2,3]
-        print ("l is %r" % l)
-        a,b = l.pop()
-
-    def test_some_error(self):
-        if namenotexi:
-            pass
-
-    def func1(self):
-        assert 41 == 42
-
-
-# thanks to Matthew Scott for this test
-def test_dynamic_compile_shows_nicely():
-    src = 'def foo():\n assert 1 == 0\n'
-    name = 'abc-123'
-    module = py.std.imp.new_module(name)
-    code = _pytest._code.compile(src, name, 'exec')
-    py.builtin.exec_(code, module.__dict__)
-    py.std.sys.modules[name] = module
-    module.foo()
-
-
-
-class TestMoreErrors:
-    def test_complex_error(self):
-        def f():
-            return 44
-        def g():
-            return 43
-        somefunc(f(), g())
-
-    def test_z1_unpack_error(self):
-        l = []
-        a,b  = l
-
-    def test_z2_type_error(self):
-        l = 3
-        a,b  = l
-
-    def test_startswith(self):
-        s = "123"
-        g = "456"
-        assert s.startswith(g)
-
-    def test_startswith_nested(self):
-        def f():
-            return "123"
-        def g():
-            return "456"
-        assert f().startswith(g())
-
-    def test_global_func(self):
-        assert isinstance(globf(42), float)
-
-    def test_instance(self):
-        self.x = 6*7
-        assert self.x != 42
-
-    def test_compare(self):
-        assert globf(10) < 5
-
-    def test_try_finally(self):
-        x = 1
-        try:
-            assert x == 0
-        finally:
-            x = 0
-
-
-class TestCustomAssertMsg:
-
-    def test_single_line(self):
-        class A:
-            a = 1
-        b = 2
-        assert A.a == b, "A.a appears not to be b"
-
-    def test_multiline(self):
-        class A:
-            a = 1
-        b = 2
-        assert A.a == b, "A.a appears not to be b\n" \
-            "or does not appear to be b\none of those"
-
-    def test_custom_repr(self):
-        class JSON:
-            a = 1
-            def __repr__(self):
-                return "This is JSON\n{\n  'foo': 'bar'\n}"
-        a = JSON()
-        b = 2
-        assert a.a == b, a
diff --git a/tools/pytest/doc/en/example/assertion/test_setup_flow_example.py b/tools/pytest/doc/en/example/assertion/test_setup_flow_example.py
deleted file mode 100644
index 512330c..0000000
--- a/tools/pytest/doc/en/example/assertion/test_setup_flow_example.py
+++ /dev/null
@@ -1,42 +0,0 @@
-def setup_module(module):
-    module.TestStateFullThing.classcount = 0
-
-class TestStateFullThing:
-    def setup_class(cls):
-        cls.classcount += 1
-
-    def teardown_class(cls):
-        cls.classcount -= 1
-
-    def setup_method(self, method):
-        self.id = eval(method.__name__[5:])
-
-    def test_42(self):
-        assert self.classcount == 1
-        assert self.id == 42
-
-    def test_23(self):
-        assert self.classcount == 1
-        assert self.id == 23
-
-def teardown_module(module):
-    assert module.TestStateFullThing.classcount == 0
-
-""" For this example the control flow happens as follows::
-    import test_setup_flow_example
-    setup_module(test_setup_flow_example)
-       setup_class(TestStateFullThing)
-           instance = TestStateFullThing()
-           setup_method(instance, instance.test_42)
-              instance.test_42()
-           setup_method(instance, instance.test_23)
-              instance.test_23()
-       teardown_class(TestStateFullThing)
-    teardown_module(test_setup_flow_example)
-
-Note that ``setup_class(TestStateFullThing)`` is called and not
-``TestStateFullThing.setup_class()`` which would require you
-to insert ``setup_class = classmethod(setup_class)`` to make
-your setup function callable.
-"""
-
diff --git a/tools/pytest/doc/en/example/attic.rst b/tools/pytest/doc/en/example/attic.rst
deleted file mode 100644
index 1bc32b2..0000000
--- a/tools/pytest/doc/en/example/attic.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-
-.. _`accept example`:
-
-example: specifying and selecting acceptance tests
---------------------------------------------------------------
-
-.. sourcecode:: python
-
-    # ./conftest.py
-    def pytest_option(parser):
-        group = parser.getgroup("myproject")
-        group.addoption("-A", dest="acceptance", action="store_true",
-            help="run (slow) acceptance tests")
-
-    def pytest_funcarg__accept(request):
-        return AcceptFixture(request)
-
-    class AcceptFixture:
-        def __init__(self, request):
-            if not request.config.option.acceptance:
-                pytest.skip("specify -A to run acceptance tests")
-            self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
-
-        def run(self, cmd):
-            """ called by test code to execute an acceptance test. """
-            self.tmpdir.chdir()
-            return py.process.cmdexec(cmd)
-
-
-and the actual test function example:
-
-.. sourcecode:: python
-
-    def test_some_acceptance_aspect(accept):
-        accept.tmpdir.mkdir("somesub")
-        result = accept.run("ls -la")
-        assert "somesub" in result
-
-If you run this test without specifying a command line option
-the test will get skipped with an appropriate message. Otherwise
-you can start to add convenience and test support methods
-to your AcceptFixture and drive running of tools or
-applications and provide ways to do assertions about
-the output.
-
-.. _`decorate a funcarg`:
-
-example: decorating a funcarg in a test module
---------------------------------------------------------------
-
-For larger scale setups it's sometimes useful to decorate
-a funcarg just for a particular test module.  We can
-extend the `accept example`_ by putting this in our test module:
-
-.. sourcecode:: python
-
-    def pytest_funcarg__accept(request):
-        # call the next factory (living in our conftest.py)
-        arg = request.getfuncargvalue("accept")
-        # create a special layout in our tempdir
-        arg.tmpdir.mkdir("special")
-        return arg
-
-    class TestSpecialAcceptance:
-        def test_sometest(self, accept):
-            assert accept.tmpdir.join("special").check()
-
-Our module level factory will be invoked first and it can
-ask its request object to call the next factory and then
-decorate its result.  This mechanism allows us to stay
-ignorant of how/where the function argument is provided -
-in our example from a `conftest plugin`_.
-
-sidenote: the temporary directory used here are instances of
-the `py.path.local`_ class which provides many of the os.path
-methods in a convenient way.
-
-.. _`py.path.local`: ../path.html#local
-.. _`conftest plugin`: customize.html#conftestplugin
diff --git a/tools/pytest/doc/en/example/costlysetup/conftest.py b/tools/pytest/doc/en/example/costlysetup/conftest.py
deleted file mode 100644
index d689c11..0000000
--- a/tools/pytest/doc/en/example/costlysetup/conftest.py
+++ /dev/null
@@ -1,18 +0,0 @@
-
-import pytest
-
-@pytest.fixture("session")
-def setup(request):
-    setup = CostlySetup()
-    request.addfinalizer(setup.finalize)
-    return setup
-
-class CostlySetup:
-    def __init__(self):
-        import time
-        print ("performing costly setup")
-        time.sleep(5)
-        self.timecostly = 1
-
-    def finalize(self):
-        del self.timecostly
diff --git a/tools/pytest/doc/en/example/index.rst b/tools/pytest/doc/en/example/index.rst
deleted file mode 100644
index 363de5a..0000000
--- a/tools/pytest/doc/en/example/index.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-
-.. _examples:
-
-Usages and Examples
-===========================================
-
-Here is a (growing) list of examples. :ref:`Contact <contact>` us if you
-need more examples or have questions. Also take a look at the
-:ref:`comprehensive documentation <toc>` which contains many example
-snippets as well.  Also, `pytest on stackoverflow.com
-<http://stackoverflow.com/search?q=pytest>`_ often comes with example
-answers.
-
-For basic examples, see
-
-- :doc:`../getting-started` for basic introductory examples
-- :ref:`assert` for basic assertion examples
-- :ref:`fixtures` for basic fixture/setup examples
-- :ref:`parametrize` for basic test function parametrization
-- :doc:`../unittest` for basic unittest integration
-- :doc:`../nose` for basic nosetests integration
-
-The following examples aim at various use cases you might encounter.
-
-.. toctree::
-   :maxdepth: 2
-
-   reportingdemo
-   simple
-   parametrize
-   markers
-   special
-   pythoncollection
-   nonpython
diff --git a/tools/pytest/doc/en/example/layout1/setup.cfg b/tools/pytest/doc/en/example/layout1/setup.cfg
deleted file mode 100644
index 02d3750..0000000
--- a/tools/pytest/doc/en/example/layout1/setup.cfg
+++ /dev/null
@@ -1,4 +0,0 @@
-[pytest]
-testfilepatterns =
-    ${topdir}/tests/unit/test_${basename}
-    ${topdir}/tests/functional/*.py
diff --git a/tools/pytest/doc/en/example/markers.rst b/tools/pytest/doc/en/example/markers.rst
deleted file mode 100644
index 6bdc603..0000000
--- a/tools/pytest/doc/en/example/markers.rst
+++ /dev/null
@@ -1,592 +0,0 @@
-
-.. _`mark examples`:
-
-Working with custom markers
-=================================================
-
-Here are some example using the :ref:`mark` mechanism.
-
-Marking test functions and selecting them for a run
-----------------------------------------------------
-
-You can "mark" a test function with custom metadata like this::
-
-    # content of test_server.py
-
-    import pytest
-    @pytest.mark.webtest
-    def test_send_http():
-        pass # perform some webtest test for your app
-    def test_something_quick():
-        pass
-    def test_another():
-        pass
-    class TestClass:
-        def test_method(self):
-            pass
-
-.. versionadded:: 2.2
-
-You can then restrict a test run to only run tests marked with ``webtest``::
-
-    $ py.test -v -m webtest
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 4 items
-    
-    test_server.py::test_send_http PASSED
-    
-    ======= 3 tests deselected by "-m 'webtest'" ========
-    ======= 1 passed, 3 deselected in 0.12 seconds ========
-
-Or the inverse, running all tests except the webtest ones::
-
-    $ py.test -v -m "not webtest"
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 4 items
-    
-    test_server.py::test_something_quick PASSED
-    test_server.py::test_another PASSED
-    test_server.py::TestClass::test_method PASSED
-    
-    ======= 1 tests deselected by "-m 'not webtest'" ========
-    ======= 3 passed, 1 deselected in 0.12 seconds ========
-
-Selecting tests based on their node ID
---------------------------------------
-
-You can provide one or more :ref:`node IDs <node-id>` as positional
-arguments to select only specified tests. This makes it easy to select
-tests based on their module, class, method, or function name::
-
-    $ py.test -v test_server.py::TestClass::test_method
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 5 items
-    
-    test_server.py::TestClass::test_method PASSED
-    
-    ======= 1 passed in 0.12 seconds ========
-
-You can also select on the class::
-
-    $ py.test -v test_server.py::TestClass
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 4 items
-    
-    test_server.py::TestClass::test_method PASSED
-    
-    ======= 1 passed in 0.12 seconds ========
-
-Or select multiple nodes::
-
-  $ py.test -v test_server.py::TestClass test_server.py::test_send_http
-  ======= test session starts ========
-  platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-  cachedir: .cache
-  rootdir: $REGENDOC_TMPDIR, inifile: 
-  collecting ... collected 8 items
-  
-  test_server.py::TestClass::test_method PASSED
-  test_server.py::test_send_http PASSED
-  
-  ======= 2 passed in 0.12 seconds ========
-
-.. _node-id:
-
-.. note::
-
-    Node IDs are of the form ``module.py::class::method`` or
-    ``module.py::function``.  Node IDs control which tests are
-    collected, so ``module.py::class`` will select all test methods
-    on the class.  Nodes are also created for each parameter of a
-    parametrized fixture or test, so selecting a parametrized test
-    must include the parameter value, e.g.
-    ``module.py::function[param]``.
-
-    Node IDs for failing tests are displayed in the test summary info
-    when running py.test with the ``-rf`` option.  You can also
-    construct Node IDs from the output of ``py.test --collectonly``.
-
-Using ``-k expr`` to select tests based on their name
--------------------------------------------------------
-
-.. versionadded: 2.0/2.3.4
-
-You can use the ``-k`` command line option to specify an expression
-which implements a substring match on the test names instead of the
-exact match on markers that ``-m`` provides.  This makes it easy to
-select tests based on their names::
-
-    $ py.test -v -k http  # running with the above defined example module
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 4 items
-    
-    test_server.py::test_send_http PASSED
-    
-    ======= 3 tests deselected by '-khttp' ========
-    ======= 1 passed, 3 deselected in 0.12 seconds ========
-
-And you can also run all tests except the ones that match the keyword::
-
-    $ py.test -k "not send_http" -v
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 4 items
-    
-    test_server.py::test_something_quick PASSED
-    test_server.py::test_another PASSED
-    test_server.py::TestClass::test_method PASSED
-    
-    ======= 1 tests deselected by '-knot send_http' ========
-    ======= 3 passed, 1 deselected in 0.12 seconds ========
-
-Or to select "http" and "quick" tests::
-
-    $ py.test -k "http or quick" -v
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 4 items
-    
-    test_server.py::test_send_http PASSED
-    test_server.py::test_something_quick PASSED
-    
-    ======= 2 tests deselected by '-khttp or quick' ========
-    ======= 2 passed, 2 deselected in 0.12 seconds ========
-
-.. note::
-
-    If you are using expressions such as "X and Y" then both X and Y
-    need to be simple non-keyword names.  For example, "pass" or "from"
-    will result in SyntaxErrors because "-k" evaluates the expression.
-
-    However, if the "-k" argument is a simple string, no such restrictions
-    apply.  Also "-k 'not STRING'" has no restrictions.  You can also
-    specify numbers like "-k 1.3" to match tests which are parametrized
-    with the float "1.3".
-
-Registering markers
--------------------------------------
-
-.. versionadded:: 2.2
-
-.. ini-syntax for custom markers:
-
-Registering markers for your test suite is simple::
-
-    # content of pytest.ini
-    [pytest]
-    markers =
-        webtest: mark a test as a webtest.
-
-You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers::
-
-    $ py.test --markers
-    @pytest.mark.webtest: mark a test as a webtest.
-    
-    @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
-    
-    @pytest.mark.xfail(condition, reason=None, run=True, raises=None): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
-    
-    @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
-    
-    @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures 
-    
-    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
-    
-    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
-    
-
-For an example on how to add and work with markers from a plugin, see
-:ref:`adding a custom marker from a plugin`.
-
-.. note::
-
-    It is recommended to explicitly register markers so that:
-
-    * there is one place in your test suite defining your markers
-
-    * asking for existing markers via ``py.test --markers`` gives good output
-
-    * typos in function markers are treated as an error if you use
-      the ``--strict`` option. Future versions of ``pytest`` are probably
-      going to start treating non-registered markers as errors at some point.
-
-.. _`scoped-marking`:
-
-Marking whole classes or modules
-----------------------------------------------------
-
-You may use ``pytest.mark`` decorators with classes to apply markers to all of
-its test methods::
-
-    # content of test_mark_classlevel.py
-    import pytest
-    @pytest.mark.webtest
-    class TestClass:
-        def test_startup(self):
-            pass
-        def test_startup_and_more(self):
-            pass
-
-This is equivalent to directly applying the decorator to the
-two test functions.
-
-To remain backward-compatible with Python 2.4 you can also set a
-``pytestmark`` attribute on a TestClass like this::
-
-    import pytest
-
-    class TestClass:
-        pytestmark = pytest.mark.webtest
-
-or if you need to use multiple markers you can use a list::
-
-    import pytest
-
-    class TestClass:
-        pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
-
-You can also set a module level marker::
-
-    import pytest
-    pytestmark = pytest.mark.webtest
-
-in which case it will be applied to all functions and
-methods defined in the module.
-
-.. _`marking individual tests when using parametrize`:
-
-Marking individual tests when using parametrize
------------------------------------------------
-
-When using parametrize, applying a mark will make it apply
-to each individual test. However it is also possible to
-apply a marker to an individual test instance::
-
-    import pytest
-
-    @pytest.mark.foo
-    @pytest.mark.parametrize(("n", "expected"), [
-        (1, 2),
-        pytest.mark.bar((1, 3)),
-        (2, 3),
-    ])
-    def test_increment(n, expected):
-         assert n + 1 == expected
-
-In this example the mark "foo" will apply to each of the three
-tests, whereas the "bar" mark is only applied to the second test.
-Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with parametrize`.
-
-.. note::
-
-    If the data you are parametrizing happen to be single callables, you need to be careful
-    when marking these items. `pytest.mark.xfail(my_func)` won't work because it's also the
-    signature of a function being decorated. To resolve this ambiguity, you need to pass a
-    reason argument:
-    `pytest.mark.xfail(func_bar, reason="Issue#7")`.
-
-
-.. _`adding a custom marker from a plugin`:
-
-Custom marker and command line option to control test runs
-----------------------------------------------------------
-
-.. regendoc:wipe
-
-Plugins can provide custom markers and implement specific behaviour
-based on it. This is a self-contained example which adds a command
-line option and a parametrized test function marker to run tests
-specifies via named environments::
-
-    # content of conftest.py
-
-    import pytest
-    def pytest_addoption(parser):
-        parser.addoption("-E", action="store", metavar="NAME",
-            help="only run tests matching the environment NAME.")
-
-    def pytest_configure(config):
-        # register an additional marker
-        config.addinivalue_line("markers",
-            "env(name): mark test to run only on named environment")
-
-    def pytest_runtest_setup(item):
-        envmarker = item.get_marker("env")
-        if envmarker is not None:
-            envname = envmarker.args[0]
-            if envname != item.config.getoption("-E"):
-                pytest.skip("test requires env %r" % envname)
-
-A test file using this local plugin::
-
-    # content of test_someenv.py
-
-    import pytest
-    @pytest.mark.env("stage1")
-    def test_basic_db_operation():
-        pass
-
-and an example invocations specifying a different environment than what
-the test needs::
-
-    $ py.test -E stage2
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_someenv.py s
-    
-    ======= 1 skipped in 0.12 seconds ========
-
-and here is one that specifies exactly the environment needed::
-
-    $ py.test -E stage1
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_someenv.py .
-    
-    ======= 1 passed in 0.12 seconds ========
-
-The ``--markers`` option always gives you a list of available markers::
-
-    $ py.test --markers
-    @pytest.mark.env(name): mark test to run only on named environment
-    
-    @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
-    
-    @pytest.mark.xfail(condition, reason=None, run=True, raises=None): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
-    
-    @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
-    
-    @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures 
-    
-    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
-    
-    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
-    
-
-Reading markers which were set from multiple places
-----------------------------------------------------
-
-.. versionadded: 2.2.2
-
-.. regendoc:wipe
-
-If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function.  From plugin
-code you can read over all such settings.  Example::
-
-    # content of test_mark_three_times.py
-    import pytest
-    pytestmark = pytest.mark.glob("module", x=1)
-
-    @pytest.mark.glob("class", x=2)
-    class TestClass:
-        @pytest.mark.glob("function", x=3)
-        def test_something(self):
-            pass
-
-Here we have the marker "glob" applied three times to the same
-test function.  From a conftest file we can read it like this::
-
-    # content of conftest.py
-    import sys
-
-    def pytest_runtest_setup(item):
-        g = item.get_marker("glob")
-        if g is not None:
-            for info in g:
-                print ("glob args=%s kwargs=%s" %(info.args, info.kwargs))
-                sys.stdout.flush()
-
-Let's run this without capturing output and see what we get::
-
-    $ py.test -q -s
-    glob args=('function',) kwargs={'x': 3}
-    glob args=('class',) kwargs={'x': 2}
-    glob args=('module',) kwargs={'x': 1}
-    .
-    1 passed in 0.12 seconds
-
-marking platform specific tests with pytest
---------------------------------------------------------------
-
-.. regendoc:wipe
-
-Consider you have a test suite which marks tests for particular platforms,
-namely ``pytest.mark.darwin``, ``pytest.mark.win32`` etc. and you
-also have tests that run on all platforms and have no specific
-marker.  If you now want to have a way to only run the tests
-for your particular platform, you could use the following plugin::
-
-    # content of conftest.py
-    #
-    import sys
-    import pytest
-
-    ALL = set("darwin linux2 win32".split())
-
-    def pytest_runtest_setup(item):
-        if isinstance(item, item.Function):
-            plat = sys.platform
-            if not item.get_marker(plat):
-                if ALL.intersection(item.keywords):
-                    pytest.skip("cannot run on platform %s" %(plat))
-
-then tests will be skipped if they were specified for a different platform.
-Let's do a little test file to show how this looks like::
-
-    # content of test_plat.py
-
-    import pytest
-
-    @pytest.mark.darwin
-    def test_if_apple_is_evil():
-        pass
-
-    @pytest.mark.linux2
-    def test_if_linux_works():
-        pass
-
-    @pytest.mark.win32
-    def test_if_win32_crashes():
-        pass
-
-    def test_runs_everywhere():
-        pass
-
-then you will see two test skipped and two executed tests as expected::
-
-    $ py.test -rs # this option reports skip reasons
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 4 items
-    
-    test_plat.py sss.
-    ======= short test summary info ========
-    SKIP [3] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
-    
-    ======= 1 passed, 3 skipped in 0.12 seconds ========
-
-Note that if you specify a platform via the marker-command line option like this::
-
-    $ py.test -m linux2
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 4 items
-    
-    test_plat.py s
-    
-    ======= 3 tests deselected by "-m 'linux2'" ========
-    ======= 1 skipped, 3 deselected in 0.12 seconds ========
-
-then the unmarked-tests will not be run.  It is thus a way to restrict the run to the specific tests.
-
-Automatically adding markers based on test names
---------------------------------------------------------
-
-.. regendoc:wipe
-
-If you a test suite where test function names indicate a certain
-type of test, you can implement a hook that automatically defines
-markers so that you can use the ``-m`` option with it. Let's look
-at this test module::
-
-    # content of test_module.py
-
-    def test_interface_simple():
-        assert 0
-
-    def test_interface_complex():
-        assert 0
-
-    def test_event_simple():
-        assert 0
-
-    def test_something_else():
-        assert 0
-
-We want to dynamically define two markers and can do it in a
-``conftest.py`` plugin::
-
-    # content of conftest.py
-
-    import pytest
-    def pytest_collection_modifyitems(items):
-        for item in items:
-            if "interface" in item.nodeid:
-                item.add_marker(pytest.mark.interface)
-            elif "event" in item.nodeid:
-                item.add_marker(pytest.mark.event)
-
-We can now use the ``-m option`` to select one set::
-
-  $ py.test -m interface --tb=short
-  ======= test session starts ========
-  platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-  rootdir: $REGENDOC_TMPDIR, inifile: 
-  collected 4 items
-  
-  test_module.py FF
-  
-  ======= FAILURES ========
-  _______ test_interface_simple ________
-  test_module.py:3: in test_interface_simple
-      assert 0
-  E   assert 0
-  _______ test_interface_complex ________
-  test_module.py:6: in test_interface_complex
-      assert 0
-  E   assert 0
-  ======= 2 tests deselected by "-m 'interface'" ========
-  ======= 2 failed, 2 deselected in 0.12 seconds ========
-
-or to select both "event" and "interface" tests::
-
-  $ py.test -m "interface or event" --tb=short
-  ======= test session starts ========
-  platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-  rootdir: $REGENDOC_TMPDIR, inifile: 
-  collected 4 items
-  
-  test_module.py FFF
-  
-  ======= FAILURES ========
-  _______ test_interface_simple ________
-  test_module.py:3: in test_interface_simple
-      assert 0
-  E   assert 0
-  _______ test_interface_complex ________
-  test_module.py:6: in test_interface_complex
-      assert 0
-  E   assert 0
-  _______ test_event_simple ________
-  test_module.py:9: in test_event_simple
-      assert 0
-  E   assert 0
-  ======= 1 tests deselected by "-m 'interface or event'" ========
-  ======= 3 failed, 1 deselected in 0.12 seconds ========
diff --git a/tools/pytest/doc/en/example/multipython.py b/tools/pytest/doc/en/example/multipython.py
deleted file mode 100644
index 66a368a..0000000
--- a/tools/pytest/doc/en/example/multipython.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-module containing a parametrized tests testing cross-python
-serialization via the pickle module.
-"""
-import py
-import pytest
-import _pytest._code
-
-pythonlist = ['python2.6', 'python2.7', 'python3.3']
-@pytest.fixture(params=pythonlist)
-def python1(request, tmpdir):
-    picklefile = tmpdir.join("data.pickle")
-    return Python(request.param, picklefile)
-
-@pytest.fixture(params=pythonlist)
-def python2(request, python1):
-    return Python(request.param, python1.picklefile)
-
-class Python:
-    def __init__(self, version, picklefile):
-        self.pythonpath = py.path.local.sysfind(version)
-        if not self.pythonpath:
-            pytest.skip("%r not found" %(version,))
-        self.picklefile = picklefile
-    def dumps(self, obj):
-        dumpfile = self.picklefile.dirpath("dump.py")
-        dumpfile.write(_pytest._code.Source("""
-            import pickle
-            f = open(%r, 'wb')
-            s = pickle.dump(%r, f, protocol=2)
-            f.close()
-        """ % (str(self.picklefile), obj)))
-        py.process.cmdexec("%s %s" %(self.pythonpath, dumpfile))
-
-    def load_and_is_true(self, expression):
-        loadfile = self.picklefile.dirpath("load.py")
-        loadfile.write(_pytest._code.Source("""
-            import pickle
-            f = open(%r, 'rb')
-            obj = pickle.load(f)
-            f.close()
-            res = eval(%r)
-            if not res:
-                raise SystemExit(1)
-        """ % (str(self.picklefile), expression)))
-        print (loadfile)
-        py.process.cmdexec("%s %s" %(self.pythonpath, loadfile))
-
-@pytest.mark.parametrize("obj", [42, {}, {1:3},])
-def test_basic_objects(python1, python2, obj):
-    python1.dumps(obj)
-    python2.load_and_is_true("obj == %s" % obj)
diff --git a/tools/pytest/doc/en/example/nonpython.rst b/tools/pytest/doc/en/example/nonpython.rst
deleted file mode 100644
index 6437e39..0000000
--- a/tools/pytest/doc/en/example/nonpython.rst
+++ /dev/null
@@ -1,91 +0,0 @@
-
-.. _`non-python tests`:
-
-Working with non-python tests
-====================================================
-
-.. _`yaml plugin`:
-
-A basic example for specifying tests in Yaml files
---------------------------------------------------------------
-
-.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
-.. _`PyYAML`: http://pypi.python.org/pypi/PyYAML/
-
-Here is an example ``conftest.py`` (extracted from Ali Afshnars special purpose `pytest-yamlwsgi`_ plugin).   This ``conftest.py`` will  collect ``test*.yml`` files and will execute the yaml-formatted content as custom tests:
-
-.. include:: nonpython/conftest.py
-    :literal:
-
-You can create a simple example file:
-
-.. include:: nonpython/test_simple.yml
-    :literal:
-
-and if you installed `PyYAML`_ or a compatible YAML-parser you can
-now execute the test specification::
-
-    nonpython $ py.test test_simple.yml
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR/nonpython, inifile: 
-    collected 2 items
-    
-    test_simple.yml F.
-    
-    ======= FAILURES ========
-    _______ usecase: hello ________
-    usecase execution failed
-       spec failed: 'some': 'other'
-       no further details known at this point.
-    ======= 1 failed, 1 passed in 0.12 seconds ========
-
-.. regendoc:wipe
-
-You get one dot for the passing ``sub1: sub1`` check and one failure.
-Obviously in the above ``conftest.py`` you'll want to implement a more
-interesting interpretation of the yaml-values.  You can easily write
-your own domain specific testing language this way.
-
-.. note::
-
-    ``repr_failure(excinfo)`` is called for representing test failures.
-    If you create custom collection nodes you can return an error
-    representation string of your choice.  It
-    will be reported as a (red) string.
-
-``reportinfo()`` is used for representing the test location and is also
-consulted when reporting in ``verbose`` mode::
-
-    nonpython $ py.test -v
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR/nonpython, inifile: 
-    collecting ... collected 2 items
-    
-    test_simple.yml::hello FAILED
-    test_simple.yml::ok PASSED
-    
-    ======= FAILURES ========
-    _______ usecase: hello ________
-    usecase execution failed
-       spec failed: 'some': 'other'
-       no further details known at this point.
-    ======= 1 failed, 1 passed in 0.12 seconds ========
-
-.. regendoc:wipe
-
-While developing your custom test collection and execution it's also
-interesting to just look at the collection tree::
-
-    nonpython $ py.test --collect-only
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR/nonpython, inifile: 
-    collected 2 items
-    <YamlFile 'test_simple.yml'>
-      <YamlItem 'hello'>
-      <YamlItem 'ok'>
-    
-    ======= no tests ran in 0.12 seconds ========
diff --git a/tools/pytest/doc/en/example/nonpython/conftest.py b/tools/pytest/doc/en/example/nonpython/conftest.py
deleted file mode 100644
index 2406e8f..0000000
--- a/tools/pytest/doc/en/example/nonpython/conftest.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# content of conftest.py
-
-import pytest
-
-def pytest_collect_file(parent, path):
-    if path.ext == ".yml" and path.basename.startswith("test"):
-        return YamlFile(path, parent)
-
-class YamlFile(pytest.File):
-    def collect(self):
-        import yaml # we need a yaml parser, e.g. PyYAML
-        raw = yaml.safe_load(self.fspath.open())
-        for name, spec in raw.items():
-            yield YamlItem(name, self, spec)
-
-class YamlItem(pytest.Item):
-    def __init__(self, name, parent, spec):
-        super(YamlItem, self).__init__(name, parent)
-        self.spec = spec
-
-    def runtest(self):
-        for name, value in self.spec.items():
-            # some custom test execution (dumb example follows)
-            if name != value:
-                raise YamlException(self, name, value)
-
-    def repr_failure(self, excinfo):
-        """ called when self.runtest() raises an exception. """
-        if isinstance(excinfo.value, YamlException):
-            return "\n".join([
-                "usecase execution failed",
-                "   spec failed: %r: %r" % excinfo.value.args[1:3],
-                "   no further details known at this point."
-            ])
-
-    def reportinfo(self):
-        return self.fspath, 0, "usecase: %s" % self.name
-
-class YamlException(Exception):
-    """ custom exception for error reporting. """
diff --git a/tools/pytest/doc/en/example/parametrize.rst b/tools/pytest/doc/en/example/parametrize.rst
deleted file mode 100644
index 5d637ff..0000000
--- a/tools/pytest/doc/en/example/parametrize.rst
+++ /dev/null
@@ -1,475 +0,0 @@
-
-.. _paramexamples:
-
-Parametrizing tests
-=================================================
-
-.. currentmodule:: _pytest.python
-
-``pytest`` allows to easily parametrize test functions.
-For basic docs, see :ref:`parametrize-basics`.
-
-In the following we provide some examples using
-the builtin mechanisms.
-
-Generating parameters combinations, depending on command line
-----------------------------------------------------------------------------
-
-.. regendoc:wipe
-
-Let's say we want to execute a test with different computation
-parameters and the parameter range shall be determined by a command
-line argument.  Let's first write a simple (do-nothing) computation test::
-
-    # content of test_compute.py
-
-    def test_compute(param1):
-        assert param1 < 4
-
-Now we add a test configuration like this::
-
-    # content of conftest.py
-
-    def pytest_addoption(parser):
-        parser.addoption("--all", action="store_true",
-            help="run all combinations")
-
-    def pytest_generate_tests(metafunc):
-        if 'param1' in metafunc.fixturenames:
-            if metafunc.config.option.all:
-                end = 5
-            else:
-                end = 2
-            metafunc.parametrize("param1", range(end))
-
-This means that we only run 2 tests if we do not pass ``--all``::
-
-    $ py.test -q test_compute.py
-    ..
-    2 passed in 0.12 seconds
-
-We run only two computations, so we see two dots.
-let's run the full monty::
-
-    $ py.test -q --all
-    ....F
-    ======= FAILURES ========
-    _______ test_compute[4] ________
-    
-    param1 = 4
-    
-        def test_compute(param1):
-    >       assert param1 < 4
-    E       assert 4 < 4
-    
-    test_compute.py:3: AssertionError
-    1 failed, 4 passed in 0.12 seconds
-
-As expected when running the full range of ``param1`` values
-we'll get an error on the last one.
-
-
-Different options for test IDs
-------------------------------------
-
-pytest will build a string that is the test ID for each set of values in a
-parametrized test. These IDs can be used with ``-k`` to select specific cases
-to run, and they will also identify the specific case when one is failing.
-Running pytest with ``--collect-only`` will show the generated IDs.
-
-Numbers, strings, booleans and None will have their usual string representation
-used in the test ID. For other objects, pytest will make a string based on
-the argument name::
-
-    # content of test_time.py
-
-    import pytest
-
-    from datetime import datetime, timedelta
-
-    testdata = [
-        (datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
-        (datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
-    ]
-
-
-    @pytest.mark.parametrize("a,b,expected", testdata)
-    def test_timedistance_v0(a, b, expected):
-        diff = a - b
-        assert diff == expected
-
-
-    @pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
-    def test_timedistance_v1(a, b, expected):
-        diff = a - b
-        assert diff == expected
-
-
-    def idfn(val):
-        if isinstance(val, (datetime,)):
-            # note this wouldn't show any hours/minutes/seconds
-            return val.strftime('%Y%m%d')
-
-
-    @pytest.mark.parametrize("a,b,expected", testdata, ids=idfn)
-    def test_timedistance_v2(a, b, expected):
-        diff = a - b
-        assert diff == expected
-
-
-In ``test_timedistance_v0``, we let pytest generate the test IDs.
-
-In ``test_timedistance_v1``, we specified ``ids`` as a list of strings which were
-used as the test IDs. These are succinct, but can be a pain to maintain.
-
-In ``test_timedistance_v2``, we specified ``ids`` as a function that can generate a
-string representation to make part of the test ID. So our ``datetime`` values use the
-label generated by ``idfn``, but because we didn't generate a label for ``timedelta``
-objects, they are still using the default pytest representation::
-
-
-    $ py.test test_time.py --collect-only
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 6 items
-    <Module 'test_time.py'>
-      <Function 'test_timedistance_v0[a0-b0-expected0]'>
-      <Function 'test_timedistance_v0[a1-b1-expected1]'>
-      <Function 'test_timedistance_v1[forward]'>
-      <Function 'test_timedistance_v1[backward]'>
-      <Function 'test_timedistance_v2[20011212-20011211-expected0]'>
-      <Function 'test_timedistance_v2[20011211-20011212-expected1]'>
-    
-    ======= no tests ran in 0.12 seconds ========
-
-A quick port of "testscenarios"
-------------------------------------
-
-.. _`test scenarios`: http://pypi.python.org/pypi/testscenarios/
-
-Here is a quick port to run tests configured with `test scenarios`_,
-an add-on from Robert Collins for the standard unittest framework. We
-only have to work a bit to construct the correct arguments for pytest's
-:py:func:`Metafunc.parametrize`::
-
-    # content of test_scenarios.py
-
-    def pytest_generate_tests(metafunc):
-        idlist = []
-        argvalues = []
-        for scenario in metafunc.cls.scenarios:
-            idlist.append(scenario[0])
-            items = scenario[1].items()
-            argnames = [x[0] for x in items]
-            argvalues.append(([x[1] for x in items]))
-        metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
-
-    scenario1 = ('basic', {'attribute': 'value'})
-    scenario2 = ('advanced', {'attribute': 'value2'})
-
-    class TestSampleWithScenarios:
-        scenarios = [scenario1, scenario2]
-
-        def test_demo1(self, attribute):
-            assert isinstance(attribute, str)
-
-        def test_demo2(self, attribute):
-            assert isinstance(attribute, str)
-
-this is a fully self-contained example which you can run with::
-
-    $ py.test test_scenarios.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 4 items
-    
-    test_scenarios.py ....
-    
-    ======= 4 passed in 0.12 seconds ========
-
-If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function::
-
-
-    $ py.test --collect-only test_scenarios.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 4 items
-    <Module 'test_scenarios.py'>
-      <Class 'TestSampleWithScenarios'>
-        <Instance '()'>
-          <Function 'test_demo1[basic]'>
-          <Function 'test_demo2[basic]'>
-          <Function 'test_demo1[advanced]'>
-          <Function 'test_demo2[advanced]'>
-    
-    ======= no tests ran in 0.12 seconds ========
-
-Note that we told ``metafunc.parametrize()`` that your scenario values
-should be considered class-scoped.  With pytest-2.3 this leads to a
-resource-based ordering.
-
-Deferring the setup of parametrized resources
----------------------------------------------------
-
-.. regendoc:wipe
-
-The parametrization of test functions happens at collection
-time.  It is a good idea to setup expensive resources like DB
-connections or subprocess only when the actual test is run.
-Here is a simple example how you can achieve that, first
-the actual test requiring a ``db`` object::
-
-    # content of test_backends.py
-
-    import pytest
-    def test_db_initialized(db):
-        # a dummy test
-        if db.__class__.__name__ == "DB2":
-            pytest.fail("deliberately failing for demo purposes")
-
-We can now add a test configuration that generates two invocations of
-the ``test_db_initialized`` function and also implements a factory that
-creates a database object for the actual test invocations::
-
-    # content of conftest.py
-    import pytest
-
-    def pytest_generate_tests(metafunc):
-        if 'db' in metafunc.fixturenames:
-            metafunc.parametrize("db", ['d1', 'd2'], indirect=True)
-
-    class DB1:
-        "one database object"
-    class DB2:
-        "alternative database object"
-
-    @pytest.fixture
-    def db(request):
-        if request.param == "d1":
-            return DB1()
-        elif request.param == "d2":
-            return DB2()
-        else:
-            raise ValueError("invalid internal test config")
-
-Let's first see how it looks like at collection time::
-
-    $ py.test test_backends.py --collect-only
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    <Module 'test_backends.py'>
-      <Function 'test_db_initialized[d1]'>
-      <Function 'test_db_initialized[d2]'>
-    
-    ======= no tests ran in 0.12 seconds ========
-
-And then when we run the test::
-
-    $ py.test -q test_backends.py
-    .F
-    ======= FAILURES ========
-    _______ test_db_initialized[d2] ________
-    
-    db = <conftest.DB2 object at 0xdeadbeef>
-    
-        def test_db_initialized(db):
-            # a dummy test
-            if db.__class__.__name__ == "DB2":
-    >           pytest.fail("deliberately failing for demo purposes")
-    E           Failed: deliberately failing for demo purposes
-    
-    test_backends.py:6: Failed
-    1 failed, 1 passed in 0.12 seconds
-
-The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed.  Our ``db`` fixture function has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase.
-
-.. regendoc:wipe
-
-Apply indirect on particular arguments
----------------------------------------------------
-
-Very often parametrization uses more than one argument name. There is opportunity to apply ``indirect``
-parameter on particular arguments. It can be done by passing list or tuple of
-arguments' names to ``indirect``. In the example below there is a function ``test_indirect`` which uses
-two fixtures: ``x`` and ``y``. Here we give to indirect the list, which contains the name of the
-fixture ``x``. The indirect parameter will be applied to this argument only, and the value ``a``
-will be passed to respective fixture function::
-
-    # content of test_indirect_list.py
-
-    import pytest
-    @pytest.fixture(scope='function')
-    def x(request):
-        return request.param * 3
-
-    @pytest.fixture(scope='function')
-    def y(request):
-        return request.param * 2
-
-    @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
-    def test_indirect(x,y):
-        assert x == 'aaa'
-        assert y == 'b'
-
-The result of this test will be successful::
-
-    $ py.test test_indirect_list.py --collect-only
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    <Module 'test_indirect_list.py'>
-      <Function 'test_indirect[a-b]'>
-    
-    ======= no tests ran in 0.12 seconds ========
-
-.. regendoc:wipe
-
-Parametrizing test methods through per-class configuration
---------------------------------------------------------------
-
-.. _`unittest parametrizer`: https://github.com/testing-cabal/unittest-ext/blob/master/params.py
-
-
-Here is an example ``pytest_generate_function`` function implementing a
-parametrization scheme similar to Michael Foord's `unittest
-parametrizer`_ but in a lot less code::
-
-    # content of ./test_parametrize.py
-    import pytest
-
-    def pytest_generate_tests(metafunc):
-        # called once per each test function
-        funcarglist = metafunc.cls.params[metafunc.function.__name__]
-        argnames = list(funcarglist[0])
-        metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
-                for funcargs in funcarglist])
-
-    class TestClass:
-        # a map specifying multiple argument sets for a test method
-        params = {
-            'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ],
-            'test_zerodivision': [dict(a=1, b=0), ],
-        }
-
-        def test_equals(self, a, b):
-            assert a == b
-
-        def test_zerodivision(self, a, b):
-            pytest.raises(ZeroDivisionError, "a/b")
-
-Our test generator looks up a class-level definition which specifies which
-argument sets to use for each test function.  Let's run it::
-
-    $ py.test -q
-    F..
-    ======= FAILURES ========
-    _______ TestClass.test_equals[1-2] ________
-    
-    self = <test_parametrize.TestClass object at 0xdeadbeef>, a = 1, b = 2
-    
-        def test_equals(self, a, b):
-    >       assert a == b
-    E       assert 1 == 2
-    
-    test_parametrize.py:18: AssertionError
-    1 failed, 2 passed in 0.12 seconds
-
-Indirect parametrization with multiple fixtures
---------------------------------------------------------------
-
-Here is a stripped down real-life example of using parametrized
-testing for testing serialization of objects between different python
-interpreters.  We define a ``test_basic_objects`` function which
-is to be run with different sets of arguments for its three arguments:
-
-* ``python1``: first python interpreter, run to pickle-dump an object to a file
-* ``python2``: second interpreter, run to pickle-load an object from a file
-* ``obj``: object to be dumped/loaded
-
-.. literalinclude:: multipython.py
-
-Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
-
-   . $ py.test -rs -q multipython.py
-   ssssssssssss...ssssssssssss
-   ======= short test summary info ========
-   SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python3.3' not found
-   SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python2.6' not found
-   3 passed, 24 skipped in 0.12 seconds
-
-Indirect parametrization of optional implementations/imports
---------------------------------------------------------------------
-
-If you want to compare the outcomes of several implementations of a given
-API, you can write test functions that receive the already imported implementations
-and get skipped in case the implementation is not importable/available.  Let's
-say we have a "base" implementation and the other (possibly optimized ones)
-need to provide similar results::
-
-    # content of conftest.py
-
-    import pytest
-
-    @pytest.fixture(scope="session")
-    def basemod(request):
-        return pytest.importorskip("base")
-
-    @pytest.fixture(scope="session", params=["opt1", "opt2"])
-    def optmod(request):
-        return pytest.importorskip(request.param)
-
-And then a base implementation of a simple function::
-
-    # content of base.py
-    def func1():
-        return 1
-
-And an optimized version::
-
-    # content of opt1.py
-    def func1():
-        return 1.0001
-
-And finally a little test module::
-
-    # content of test_module.py
-
-    def test_func1(basemod, optmod):
-        assert round(basemod.func1(), 3) == round(optmod.func1(), 3)
-
-
-If you run this with reporting for skips enabled::
-
-    $ py.test -rs test_module.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    
-    test_module.py .s
-    ======= short test summary info ========
-    SKIP [1] $REGENDOC_TMPDIR/conftest.py:10: could not import 'opt2'
-    
-    ======= 1 passed, 1 skipped in 0.12 seconds ========
-
-You'll see that we don't have a ``opt2`` module and thus the second test run
-of our ``test_func1`` was skipped.  A few notes:
-
-- the fixture functions in the ``conftest.py`` file are "session-scoped" because we
-  don't need to import more than once
-
-- if you have multiple test functions and a skipped import, you will see
-  the ``[1]`` count increasing in the report
-
-- you can put :ref:`@pytest.mark.parametrize <@pytest.mark.parametrize>` style
-  parametrization on the test functions to parametrize input/output
-  values as well.
-
-
-
diff --git a/tools/pytest/doc/en/example/pythoncollection.py b/tools/pytest/doc/en/example/pythoncollection.py
deleted file mode 100644
index 05858eb..0000000
--- a/tools/pytest/doc/en/example/pythoncollection.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-# run this with $ py.test --collect-only test_collectonly.py
-#
-def test_function():
-    pass
-
-class TestClass:
-    def test_method(self):
-        pass
-    def test_anothermethod(self):
-        pass
diff --git a/tools/pytest/doc/en/example/pythoncollection.rst b/tools/pytest/doc/en/example/pythoncollection.rst
deleted file mode 100644
index 5faf4c6..0000000
--- a/tools/pytest/doc/en/example/pythoncollection.rst
+++ /dev/null
@@ -1,192 +0,0 @@
-Changing standard (Python) test discovery
-===============================================
-
-Ignore paths during test collection
------------------------------------
-
-You can easily ignore certain test directories and modules during collection
-by passing the ``--ignore=path`` option on the cli. ``pytest`` allows multiple
-``--ignore`` options. Example::
-
-    tests/
-    ├── example
-    │   ├── test_example_01.py
-    │   ├── test_example_02.py
-    │   └── test_example_03.py
-    ├── foobar
-    │   ├── test_foobar_01.py
-    │   ├── test_foobar_02.py
-    │   └── test_foobar_03.py
-    └── hello
-        └── world
-            ├── test_world_01.py
-            ├── test_world_02.py
-            └── test_world_03.py
-
-Now if you invoke ``pytest`` with ``--ignore=tests/foobar/test_foobar_03.py --ignore=tests/hello/``,
-you will see that ``pytest`` only collects test-modules, which do not match the patterns specified::
-
-    ========= test session starts ==========
-    platform darwin -- Python 2.7.10, pytest-2.8.2, py-1.4.30, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile:
-    collected 5 items
-
-    tests/example/test_example_01.py .
-    tests/example/test_example_02.py .
-    tests/example/test_example_03.py .
-    tests/foobar/test_foobar_01.py .
-    tests/foobar/test_foobar_02.py .
-
-    ======= 5 passed in 0.02 seconds =======
-
-
-Changing directory recursion
------------------------------------------------------
-
-You can set the :confval:`norecursedirs` option in an ini-file, for example your ``setup.cfg`` in the project root directory::
-
-    # content of setup.cfg
-    [pytest]
-    norecursedirs = .svn _build tmp*
-
-This would tell ``pytest`` to not recurse into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory.
-
-.. _`change naming conventions`:
-
-Changing naming conventions
------------------------------------------------------
-
-You can configure different naming conventions by setting
-the :confval:`python_files`, :confval:`python_classes` and
-:confval:`python_functions` configuration options.  Example::
-
-    # content of setup.cfg
-    # can also be defined in in tox.ini or pytest.ini file
-    [pytest]
-    python_files=check_*.py
-    python_classes=Check
-    python_functions=*_check
-
-This would make ``pytest`` look for tests in files that match the ``check_*
-.py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods
-that match ``*_check``.  For example, if we have::
-
-    # content of check_myapp.py
-    class CheckMyApp:
-        def simple_check(self):
-            pass
-        def complex_check(self):
-            pass
-
-then the test collection looks like this::
-
-    $ py.test --collect-only
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: setup.cfg
-    collected 2 items
-    <Module 'check_myapp.py'>
-      <Class 'CheckMyApp'>
-        <Instance '()'>
-          <Function 'simple_check'>
-          <Function 'complex_check'>
-    
-    ======= no tests ran in 0.12 seconds ========
-
-.. note::
-
-   the ``python_functions`` and ``python_classes`` options has no effect
-   for ``unittest.TestCase`` test discovery because pytest delegates
-   detection of test case methods to unittest code.
-
-Interpreting cmdline arguments as Python packages
------------------------------------------------------
-
-You can use the ``--pyargs`` option to make ``pytest`` try
-interpreting arguments as python package names, deriving
-their file system path and then running the test. For
-example if you have unittest2 installed you can type::
-
-    py.test --pyargs unittest2.test.test_skipping -q
-
-which would run the respective test module.  Like with
-other options, through an ini-file and the :confval:`addopts` option you
-can make this change more permanently::
-
-    # content of pytest.ini
-    [pytest]
-    addopts = --pyargs
-
-Now a simple invocation of ``py.test NAME`` will check
-if NAME exists as an importable package/module and otherwise
-treat it as a filesystem path.
-
-Finding out what is collected
------------------------------------------------
-
-You can always peek at the collection tree without running tests like this::
-
-    . $ py.test --collect-only pythoncollection.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
-    collected 3 items
-    <Module 'CWD/pythoncollection.py'>
-      <Function 'test_function'>
-      <Class 'TestClass'>
-        <Instance '()'>
-          <Function 'test_method'>
-          <Function 'test_anothermethod'>
-    
-    ======= no tests ran in 0.12 seconds ========
-
-customizing test collection to find all .py files
----------------------------------------------------------
-
-.. regendoc:wipe
-
-You can easily instruct ``pytest`` to discover tests from every python file::
-
-
-    # content of pytest.ini
-    [pytest]
-    python_files = *.py
-
-However, many projects will have a ``setup.py`` which they don't want to be imported. Moreover, there may files only importable by a specific python version.
-For such cases you can dynamically define files to be ignored by listing
-them in a ``conftest.py`` file::
-
-    # content of conftest.py
-    import sys
-
-    collect_ignore = ["setup.py"]
-    if sys.version_info[0] > 2:
-        collect_ignore.append("pkg/module_py2.py")
-
-And then if you have a module file like this::
-
-    # content of pkg/module_py2.py
-    def test_only_on_python2():
-        try:
-            assert 0
-        except Exception, e:
-            pass
-
-and a setup.py dummy file like this::
-
-    # content of setup.py
-    0/0  # will raise exception if imported
-
-then a pytest run on python2 will find the one test when run with a python2
-interpreters and will leave out the setup.py file::
-
-    $ py.test --collect-only
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
-    collected 0 items
-    
-    ======= no tests ran in 0.12 seconds ========
-
-If you run with a Python3 interpreter the moduled added through the conftest.py file will not be considered for test collection.
-
diff --git a/tools/pytest/doc/en/example/reportingdemo.rst b/tools/pytest/doc/en/example/reportingdemo.rst
deleted file mode 100644
index 28624aa..0000000
--- a/tools/pytest/doc/en/example/reportingdemo.rst
+++ /dev/null
@@ -1,598 +0,0 @@
-
-.. _`tbreportdemo`:
-
-Demo of Python failure reports with pytest
-==================================================
-
-Here is a nice run of several tens of failures
-and how ``pytest`` presents things (unfortunately
-not showing the nice colors here in the HTML that you
-get on the terminal - we are working on that):
-
-.. code-block:: python
-
-    assertion $ py.test failure_demo.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR/assertion, inifile: 
-    collected 42 items
-    
-    failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
-    
-    ======= FAILURES ========
-    _______ test_generative[0] ________
-    
-    param1 = 3, param2 = 6
-    
-        def test_generative(param1, param2):
-    >       assert param1 * 2 < param2
-    E       assert (3 * 2) < 6
-    
-    failure_demo.py:16: AssertionError
-    _______ TestFailing.test_simple ________
-    
-    self = <failure_demo.TestFailing object at 0xdeadbeef>
-    
-        def test_simple(self):
-            def f():
-                return 42
-            def g():
-                return 43
-        
-    >       assert f() == g()
-    E       assert 42 == 43
-    E        +  where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
-    E        +  and   43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
-    
-    failure_demo.py:29: AssertionError
-    _______ TestFailing.test_simple_multiline ________
-    
-    self = <failure_demo.TestFailing object at 0xdeadbeef>
-    
-        def test_simple_multiline(self):
-            otherfunc_multi(
-                      42,
-    >                 6*9)
-    
-    failure_demo.py:34: 
-    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
-    
-    a = 42, b = 54
-    
-        def otherfunc_multi(a,b):
-    >       assert (a ==
-                    b)
-    E       assert 42 == 54
-    
-    failure_demo.py:12: AssertionError
-    _______ TestFailing.test_not ________
-    
-    self = <failure_demo.TestFailing object at 0xdeadbeef>
-    
-        def test_not(self):
-            def f():
-                return 42
-    >       assert not f()
-    E       assert not 42
-    E        +  where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
-    
-    failure_demo.py:39: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_text ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_text(self):
-    >       assert 'spam' == 'eggs'
-    E       assert 'spam' == 'eggs'
-    E         - spam
-    E         + eggs
-    
-    failure_demo.py:43: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_similar_text ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_similar_text(self):
-    >       assert 'foo 1 bar' == 'foo 2 bar'
-    E       assert 'foo 1 bar' == 'foo 2 bar'
-    E         - foo 1 bar
-    E         ?     ^
-    E         + foo 2 bar
-    E         ?     ^
-    
-    failure_demo.py:46: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_multiline_text ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_multiline_text(self):
-    >       assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
-    E       assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
-    E           foo
-    E         - spam
-    E         + eggs
-    E           bar
-    
-    failure_demo.py:49: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_long_text ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_long_text(self):
-            a = '1'*100 + 'a' + '2'*100
-            b = '1'*100 + 'b' + '2'*100
-    >       assert a == b
-    E       assert '111111111111...2222222222222' == '1111111111111...2222222222222'
-    E         Skipping 90 identical leading characters in diff, use -v to show
-    E         Skipping 91 identical trailing characters in diff, use -v to show
-    E         - 1111111111a222222222
-    E         ?           ^
-    E         + 1111111111b222222222
-    E         ?           ^
-    
-    failure_demo.py:54: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_long_text_multiline ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_long_text_multiline(self):
-            a = '1\n'*100 + 'a' + '2\n'*100
-            b = '1\n'*100 + 'b' + '2\n'*100
-    >       assert a == b
-    E       assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n'
-    E         Skipping 190 identical leading characters in diff, use -v to show
-    E         Skipping 191 identical trailing characters in diff, use -v to show
-    E           1
-    E           1
-    E           1
-    E           1
-    E           1
-    E         - a2
-    E         + b2
-    E           2
-    E           2
-    E           2
-    E           2
-    
-    failure_demo.py:59: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_list ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_list(self):
-    >       assert [0, 1, 2] == [0, 1, 3]
-    E       assert [0, 1, 2] == [0, 1, 3]
-    E         At index 2 diff: 2 != 3
-    E         Use -v to get the full diff
-    
-    failure_demo.py:62: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_list_long ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_list_long(self):
-            a = [0]*100 + [1] + [3]*100
-            b = [0]*100 + [2] + [3]*100
-    >       assert a == b
-    E       assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
-    E         At index 100 diff: 1 != 2
-    E         Use -v to get the full diff
-    
-    failure_demo.py:67: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_dict ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_dict(self):
-    >       assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
-    E       assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
-    E         Omitting 1 identical items, use -v to show
-    E         Differing items:
-    E         {'b': 1} != {'b': 2}
-    E         Left contains more items:
-    E         {'c': 0}
-    E         Right contains more items:
-    E         {'d': 0}
-    E         Use -v to get the full diff
-    
-    failure_demo.py:70: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_set ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_set(self):
-    >       assert set([0, 10, 11, 12]) == set([0, 20, 21])
-    E       assert set([0, 10, 11, 12]) == set([0, 20, 21])
-    E         Extra items in the left set:
-    E         10
-    E         11
-    E         12
-    E         Extra items in the right set:
-    E         20
-    E         21
-    E         Use -v to get the full diff
-    
-    failure_demo.py:73: AssertionError
-    _______ TestSpecialisedExplanations.test_eq_longer_list ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_eq_longer_list(self):
-    >       assert [1,2] == [1,2,3]
-    E       assert [1, 2] == [1, 2, 3]
-    E         Right contains more items, first extra item: 3
-    E         Use -v to get the full diff
-    
-    failure_demo.py:76: AssertionError
-    _______ TestSpecialisedExplanations.test_in_list ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_in_list(self):
-    >       assert 1 in [0, 2, 3, 4, 5]
-    E       assert 1 in [0, 2, 3, 4, 5]
-    
-    failure_demo.py:79: AssertionError
-    _______ TestSpecialisedExplanations.test_not_in_text_multiline ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_not_in_text_multiline(self):
-            text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
-    >       assert 'foo' not in text
-    E       assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail'
-    E         'foo' is contained here:
-    E           some multiline
-    E           text
-    E           which
-    E           includes foo
-    E         ?          +++
-    E           and a
-    E           tail
-    
-    failure_demo.py:83: AssertionError
-    _______ TestSpecialisedExplanations.test_not_in_text_single ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_not_in_text_single(self):
-            text = 'single foo line'
-    >       assert 'foo' not in text
-    E       assert 'foo' not in 'single foo line'
-    E         'foo' is contained here:
-    E           single foo line
-    E         ?        +++
-    
-    failure_demo.py:87: AssertionError
-    _______ TestSpecialisedExplanations.test_not_in_text_single_long ________
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_not_in_text_single_long(self):
-            text = 'head ' * 50 + 'foo ' + 'tail ' * 20
-    >       assert 'foo' not in text
-    E       assert 'foo' not in 'head head head head hea...ail tail tail tail tail '
-    E         'foo' is contained here:
-    E           head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail 
-    E         ?           +++
-    
-    failure_demo.py:91: AssertionError
-    ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
-    
-    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
-    
-        def test_not_in_text_single_long_term(self):
-            text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
-    >       assert 'f'*70 not in text
-    E       assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail '
-    E         'ffffffffffffffffff...fffffffffffffffffff' is contained here:
-    E           head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail 
-    E         ?           ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-    
-    failure_demo.py:95: AssertionError
-    _______ test_attribute ________
-    
-        def test_attribute():
-            class Foo(object):
-                b = 1
-            i = Foo()
-    >       assert i.b == 2
-    E       assert 1 == 2
-    E        +  where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
-    
-    failure_demo.py:102: AssertionError
-    _______ test_attribute_instance ________
-    
-        def test_attribute_instance():
-            class Foo(object):
-                b = 1
-    >       assert Foo().b == 2
-    E       assert 1 == 2
-    E        +  where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
-    E        +    where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
-    
-    failure_demo.py:108: AssertionError
-    _______ test_attribute_failure ________
-    
-        def test_attribute_failure():
-            class Foo(object):
-                def _get_b(self):
-                    raise Exception('Failed to get attrib')
-                b = property(_get_b)
-            i = Foo()
-    >       assert i.b == 2
-    
-    failure_demo.py:117: 
-    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
-    
-    self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
-    
-        def _get_b(self):
-    >       raise Exception('Failed to get attrib')
-    E       Exception: Failed to get attrib
-    
-    failure_demo.py:114: Exception
-    _______ test_attribute_multiple ________
-    
-        def test_attribute_multiple():
-            class Foo(object):
-                b = 1
-            class Bar(object):
-                b = 2
-    >       assert Foo().b == Bar().b
-    E       assert 1 == 2
-    E        +  where 1 = <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef>.b
-    E        +    where <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Foo'>()
-    E        +  and   2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
-    E        +    where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
-    
-    failure_demo.py:125: AssertionError
-    _______ TestRaises.test_raises ________
-    
-    self = <failure_demo.TestRaises object at 0xdeadbeef>
-    
-        def test_raises(self):
-            s = 'qwe'
-    >       raises(TypeError, "int(s)")
-    
-    failure_demo.py:134: 
-    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
-    
-    >   int(s)
-    E   ValueError: invalid literal for int() with base 10: 'qwe'
-    
-    <0-codegen $PYTHON_PREFIX/lib/python3.4/site-packages/_pytest/python.py:1302>:1: ValueError
-    _______ TestRaises.test_raises_doesnt ________
-    
-    self = <failure_demo.TestRaises object at 0xdeadbeef>
-    
-        def test_raises_doesnt(self):
-    >       raises(IOError, "int('3')")
-    E       Failed: DID NOT RAISE <class 'OSError'>
-    
-    failure_demo.py:137: Failed
-    _______ TestRaises.test_raise ________
-    
-    self = <failure_demo.TestRaises object at 0xdeadbeef>
-    
-        def test_raise(self):
-    >       raise ValueError("demo error")
-    E       ValueError: demo error
-    
-    failure_demo.py:140: ValueError
-    _______ TestRaises.test_tupleerror ________
-    
-    self = <failure_demo.TestRaises object at 0xdeadbeef>
-    
-        def test_tupleerror(self):
-    >       a,b = [1]
-    E       ValueError: need more than 1 value to unpack
-    
-    failure_demo.py:143: ValueError
-    ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
-    
-    self = <failure_demo.TestRaises object at 0xdeadbeef>
-    
-        def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
-            l = [1,2,3]
-            print ("l is %r" % l)
-    >       a,b = l.pop()
-    E       TypeError: 'int' object is not iterable
-    
-    failure_demo.py:148: TypeError
-    --------------------------- Captured stdout call ---------------------------
-    l is [1, 2, 3]
-    _______ TestRaises.test_some_error ________
-    
-    self = <failure_demo.TestRaises object at 0xdeadbeef>
-    
-        def test_some_error(self):
-    >       if namenotexi:
-    E       NameError: name 'namenotexi' is not defined
-    
-    failure_demo.py:151: NameError
-    _______ test_dynamic_compile_shows_nicely ________
-    
-        def test_dynamic_compile_shows_nicely():
-            src = 'def foo():\n assert 1 == 0\n'
-            name = 'abc-123'
-            module = py.std.imp.new_module(name)
-            code = _pytest._code.compile(src, name, 'exec')
-            py.builtin.exec_(code, module.__dict__)
-            py.std.sys.modules[name] = module
-    >       module.foo()
-    
-    failure_demo.py:166: 
-    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
-    
-        def foo():
-    >    assert 1 == 0
-    E    assert 1 == 0
-    
-    <2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:163>:2: AssertionError
-    _______ TestMoreErrors.test_complex_error ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_complex_error(self):
-            def f():
-                return 44
-            def g():
-                return 43
-    >       somefunc(f(), g())
-    
-    failure_demo.py:176: 
-    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
-    failure_demo.py:9: in somefunc
-        otherfunc(x,y)
-    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
-    
-    a = 44, b = 43
-    
-        def otherfunc(a,b):
-    >       assert a==b
-    E       assert 44 == 43
-    
-    failure_demo.py:6: AssertionError
-    _______ TestMoreErrors.test_z1_unpack_error ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_z1_unpack_error(self):
-            l = []
-    >       a,b  = l
-    E       ValueError: need more than 0 values to unpack
-    
-    failure_demo.py:180: ValueError
-    _______ TestMoreErrors.test_z2_type_error ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_z2_type_error(self):
-            l = 3
-    >       a,b  = l
-    E       TypeError: 'int' object is not iterable
-    
-    failure_demo.py:184: TypeError
-    _______ TestMoreErrors.test_startswith ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_startswith(self):
-            s = "123"
-            g = "456"
-    >       assert s.startswith(g)
-    E       assert <built-in method startswith of str object at 0xdeadbeef>('456')
-    E        +  where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
-    
-    failure_demo.py:189: AssertionError
-    _______ TestMoreErrors.test_startswith_nested ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_startswith_nested(self):
-            def f():
-                return "123"
-            def g():
-                return "456"
-    >       assert f().startswith(g())
-    E       assert <built-in method startswith of str object at 0xdeadbeef>('456')
-    E        +  where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
-    E        +    where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
-    E        +  and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
-    
-    failure_demo.py:196: AssertionError
-    _______ TestMoreErrors.test_global_func ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_global_func(self):
-    >       assert isinstance(globf(42), float)
-    E       assert isinstance(43, float)
-    E        +  where 43 = globf(42)
-    
-    failure_demo.py:199: AssertionError
-    _______ TestMoreErrors.test_instance ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_instance(self):
-            self.x = 6*7
-    >       assert self.x != 42
-    E       assert 42 != 42
-    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
-    
-    failure_demo.py:203: AssertionError
-    _______ TestMoreErrors.test_compare ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_compare(self):
-    >       assert globf(10) < 5
-    E       assert 11 < 5
-    E        +  where 11 = globf(10)
-    
-    failure_demo.py:206: AssertionError
-    _______ TestMoreErrors.test_try_finally ________
-    
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
-    
-        def test_try_finally(self):
-            x = 1
-            try:
-    >           assert x == 0
-    E           assert 1 == 0
-    
-    failure_demo.py:211: AssertionError
-    _______ TestCustomAssertMsg.test_single_line ________
-    
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
-    
-        def test_single_line(self):
-            class A:
-                a = 1
-            b = 2
-    >       assert A.a == b, "A.a appears not to be b"
-    E       AssertionError: A.a appears not to be b
-    E       assert 1 == 2
-    E        +  where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
-    
-    failure_demo.py:222: AssertionError
-    _______ TestCustomAssertMsg.test_multiline ________
-    
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
-    
-        def test_multiline(self):
-            class A:
-                a = 1
-            b = 2
-    >       assert A.a == b, "A.a appears not to be b\n" \
-                "or does not appear to be b\none of those"
-    E       AssertionError: A.a appears not to be b
-    E         or does not appear to be b
-    E         one of those
-    E       assert 1 == 2
-    E        +  where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
-    
-    failure_demo.py:228: AssertionError
-    _______ TestCustomAssertMsg.test_custom_repr ________
-    
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
-    
-        def test_custom_repr(self):
-            class JSON:
-                a = 1
-                def __repr__(self):
-                    return "This is JSON\n{\n  'foo': 'bar'\n}"
-            a = JSON()
-            b = 2
-    >       assert a.a == b, a
-    E       AssertionError: This is JSON
-    E         {
-    E           'foo': 'bar'
-    E         }
-    E       assert 1 == 2
-    E        +  where 1 = This is JSON\n{\n  'foo': 'bar'\n}.a
-    
-    failure_demo.py:238: AssertionError
-    ======= 42 failed in 0.12 seconds ========
diff --git a/tools/pytest/doc/en/example/simple.rst b/tools/pytest/doc/en/example/simple.rst
deleted file mode 100644
index be12d2a..0000000
--- a/tools/pytest/doc/en/example/simple.rst
+++ /dev/null
@@ -1,751 +0,0 @@
-
-.. highlightlang:: python
-
-Basic patterns and examples
-==========================================================
-
-Pass different values to a test function, depending on command line options
-----------------------------------------------------------------------------
-
-.. regendoc:wipe
-
-Suppose we want to write a test that depends on a command line option.
-Here is a basic pattern to achieve this::
-
-    # content of test_sample.py
-    def test_answer(cmdopt):
-        if cmdopt == "type1":
-            print ("first")
-        elif cmdopt == "type2":
-            print ("second")
-        assert 0 # to see what was printed
-
-
-For this to work we need to add a command line option and
-provide the ``cmdopt`` through a :ref:`fixture function <fixture function>`::
-
-    # content of conftest.py
-    import pytest
-
-    def pytest_addoption(parser):
-        parser.addoption("--cmdopt", action="store", default="type1",
-            help="my option: type1 or type2")
-
-    @pytest.fixture
-    def cmdopt(request):
-        return request.config.getoption("--cmdopt")
-
-Let's run this without supplying our new option::
-
-    $ py.test -q test_sample.py
-    F
-    ======= FAILURES ========
-    _______ test_answer ________
-    
-    cmdopt = 'type1'
-    
-        def test_answer(cmdopt):
-            if cmdopt == "type1":
-                print ("first")
-            elif cmdopt == "type2":
-                print ("second")
-    >       assert 0 # to see what was printed
-    E       assert 0
-    
-    test_sample.py:6: AssertionError
-    --------------------------- Captured stdout call ---------------------------
-    first
-    1 failed in 0.12 seconds
-
-And now with supplying a command line option::
-
-    $ py.test -q --cmdopt=type2
-    F
-    ======= FAILURES ========
-    _______ test_answer ________
-    
-    cmdopt = 'type2'
-    
-        def test_answer(cmdopt):
-            if cmdopt == "type1":
-                print ("first")
-            elif cmdopt == "type2":
-                print ("second")
-    >       assert 0 # to see what was printed
-    E       assert 0
-    
-    test_sample.py:6: AssertionError
-    --------------------------- Captured stdout call ---------------------------
-    second
-    1 failed in 0.12 seconds
-
-You can see that the command line option arrived in our test.  This
-completes the basic pattern.  However, one often rather wants to process
-command line options outside of the test and rather pass in different or
-more complex objects.
-
-Dynamically adding command line options
---------------------------------------------------------------
-
-.. regendoc:wipe
-
-Through :confval:`addopts` you can statically add command line
-options for your project.  You can also dynamically modify
-the command line arguments before they get processed::
-
-    # content of conftest.py
-    import sys
-    def pytest_cmdline_preparse(args):
-        if 'xdist' in sys.modules: # pytest-xdist plugin
-            import multiprocessing
-            num = max(multiprocessing.cpu_count() / 2, 1)
-            args[:] = ["-n", str(num)] + args
-
-If you have the :ref:`xdist plugin <xdist>` installed
-you will now always perform test runs using a number
-of subprocesses close to your CPU. Running in an empty
-directory with the above conftest.py::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 0 items
-    
-    ======= no tests ran in 0.12 seconds ========
-
-.. _`excontrolskip`:
-
-Control skipping of tests according to command line option
---------------------------------------------------------------
-
-.. regendoc:wipe
-
-Here is a ``conftest.py`` file adding a ``--runslow`` command
-line option to control skipping of ``slow`` marked tests::
-
-    # content of conftest.py
-
-    import pytest
-    def pytest_addoption(parser):
-        parser.addoption("--runslow", action="store_true",
-            help="run slow tests")
-
-We can now write a test module like this::
-
-    # content of test_module.py
-
-    import pytest
-
-
-    slow = pytest.mark.skipif(
-        not pytest.config.getoption("--runslow"),
-        reason="need --runslow option to run"
-    )
-
-
-    def test_func_fast():
-        pass
-
-
-    @slow
-    def test_func_slow():
-        pass
-
-and when running it will see a skipped "slow" test::
-
-    $ py.test -rs    # "-rs" means report details on the little 's'
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    
-    test_module.py .s
-    ======= short test summary info ========
-    SKIP [1] test_module.py:14: need --runslow option to run
-    
-    ======= 1 passed, 1 skipped in 0.12 seconds ========
-
-Or run it including the ``slow`` marked test::
-
-    $ py.test --runslow
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    
-    test_module.py ..
-    
-    ======= 2 passed in 0.12 seconds ========
-
-Writing well integrated assertion helpers
---------------------------------------------------
-
-.. regendoc:wipe
-
-If you have a test helper function called from a test you can
-use the ``pytest.fail`` marker to fail a test with a certain message.
-The test support function will not show up in the traceback if you
-set the ``__tracebackhide__`` option somewhere in the helper function.
-Example::
-
-    # content of test_checkconfig.py
-    import pytest
-    def checkconfig(x):
-        __tracebackhide__ = True
-        if not hasattr(x, "config"):
-            pytest.fail("not configured: %s" %(x,))
-
-    def test_something():
-        checkconfig(42)
-
-The ``__tracebackhide__`` setting influences ``pytest`` showing
-of tracebacks: the ``checkconfig`` function will not be shown
-unless the ``--fulltrace`` command line option is specified.
-Let's run our little function::
-
-    $ py.test -q test_checkconfig.py
-    F
-    ======= FAILURES ========
-    _______ test_something ________
-    
-        def test_something():
-    >       checkconfig(42)
-    E       Failed: not configured: 42
-    
-    test_checkconfig.py:8: Failed
-    1 failed in 0.12 seconds
-
-Detect if running from within a pytest run
---------------------------------------------------------------
-
-.. regendoc:wipe
-
-Usually it is a bad idea to make application code
-behave differently if called from a test.  But if you
-absolutely must find out if your application code is
-running from a test you can do something like this::
-
-    # content of conftest.py
-
-    def pytest_configure(config):
-        import sys
-        sys._called_from_test = True
-
-    def pytest_unconfigure(config):
-        del sys._called_from_test
-
-and then check for the ``sys._called_from_test`` flag::
-
-    if hasattr(sys, '_called_from_test'):
-        # called from within a test run
-    else:
-        # called "normally"
-
-accordingly in your application.  It's also a good idea
-to use your own application module rather than ``sys``
-for handling flag.
-
-Adding info to test report header
---------------------------------------------------------------
-
-.. regendoc:wipe
-
-It's easy to present extra information in a ``pytest`` run::
-
-    # content of conftest.py
-
-    def pytest_report_header(config):
-        return "project deps: mylib-1.1"
-
-which will add the string to the test header accordingly::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    project deps: mylib-1.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 0 items
-    
-    ======= no tests ran in 0.12 seconds ========
-
-.. regendoc:wipe
-
-You can also return a list of strings which will be considered as several
-lines of information.  You can of course also make the amount of reporting
-information on e.g. the value of ``config.option.verbose`` so that
-you present more information appropriately::
-
-    # content of conftest.py
-
-    def pytest_report_header(config):
-        if config.option.verbose > 0:
-            return ["info1: did you know that ...", "did you?"]
-
-which will add info only when run with "--v"::
-
-    $ py.test -v
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    info1: did you know that ...
-    did you?
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 0 items
-    
-    ======= no tests ran in 0.12 seconds ========
-
-and nothing when run plainly::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 0 items
-    
-    ======= no tests ran in 0.12 seconds ========
-
-profiling test duration
---------------------------
-
-.. regendoc:wipe
-
-.. versionadded: 2.2
-
-If you have a slow running large test suite you might want to find
-out which tests are the slowest. Let's make an artificial test suite::
-
-    # content of test_some_are_slow.py
-
-    import time
-
-    def test_funcfast():
-        pass
-
-    def test_funcslow1():
-        time.sleep(0.1)
-
-    def test_funcslow2():
-        time.sleep(0.2)
-
-Now we can profile which test functions execute the slowest::
-
-    $ py.test --durations=3
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 3 items
-    
-    test_some_are_slow.py ...
-    
-    ======= slowest 3 test durations ========
-    0.20s call     test_some_are_slow.py::test_funcslow2
-    0.10s call     test_some_are_slow.py::test_funcslow1
-    0.00s setup    test_some_are_slow.py::test_funcfast
-    ======= 3 passed in 0.12 seconds ========
-
-incremental testing - test steps
----------------------------------------------------
-
-.. regendoc:wipe
-
-Sometimes you may have a testing situation which consists of a series
-of test steps.  If one step fails it makes no sense to execute further
-steps as they are all expected to fail anyway and their tracebacks
-add no insight.  Here is a simple ``conftest.py`` file which introduces
-an ``incremental`` marker which is to be used on classes::
-
-    # content of conftest.py
-
-    import pytest
-
-    def pytest_runtest_makereport(item, call):
-        if "incremental" in item.keywords:
-            if call.excinfo is not None:
-                parent = item.parent
-                parent._previousfailed = item
-
-    def pytest_runtest_setup(item):
-        if "incremental" in item.keywords:
-            previousfailed = getattr(item.parent, "_previousfailed", None)
-            if previousfailed is not None:
-                pytest.xfail("previous test failed (%s)" %previousfailed.name)
-
-These two hook implementations work together to abort incremental-marked
-tests in a class.  Here is a test module example::
-
-    # content of test_step.py
-
-    import pytest
-
-    @pytest.mark.incremental
-    class TestUserHandling:
-        def test_login(self):
-            pass
-        def test_modification(self):
-            assert 0
-        def test_deletion(self):
-            pass
-
-    def test_normal():
-        pass
-
-If we run this::
-
-    $ py.test -rx
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 4 items
-    
-    test_step.py .Fx.
-    ======= short test summary info ========
-    XFAIL test_step.py::TestUserHandling::()::test_deletion
-      reason: previous test failed (test_modification)
-    
-    ======= FAILURES ========
-    _______ TestUserHandling.test_modification ________
-    
-    self = <test_step.TestUserHandling object at 0xdeadbeef>
-    
-        def test_modification(self):
-    >       assert 0
-    E       assert 0
-    
-    test_step.py:9: AssertionError
-    ======= 1 failed, 2 passed, 1 xfailed in 0.12 seconds ========
-
-We'll see that ``test_deletion`` was not executed because ``test_modification``
-failed.  It is reported as an "expected failure".
-
-
-Package/Directory-level fixtures (setups)
--------------------------------------------------------
-
-If you have nested test directories, you can have per-directory fixture scopes
-by placing fixture functions in a ``conftest.py`` file in that directory
-You can use all types of fixtures including :ref:`autouse fixtures
-<autouse fixtures>` which are the equivalent of xUnit's setup/teardown
-concept.  It's however recommended to have explicit fixture references in your
-tests or test classes rather than relying on implicitly executing
-setup/teardown functions, especially if they are far away from the actual tests.
-
-Here is a an example for making a ``db`` fixture available in a directory::
-
-    # content of a/conftest.py
-    import pytest
-
-    class DB:
-        pass
-
-    @pytest.fixture(scope="session")
-    def db():
-        return DB()
-
-and then a test module in that directory::
-
-    # content of a/test_db.py
-    def test_a1(db):
-        assert 0, db  # to show value
-
-another test module::
-
-    # content of a/test_db2.py
-    def test_a2(db):
-        assert 0, db  # to show value
-
-and then a module in a sister directory which will not see
-the ``db`` fixture::
-
-    # content of b/test_error.py
-    def test_root(db):  # no db here, will error out
-        pass
-
-We can run this::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 7 items
-    
-    test_step.py .Fx.
-    a/test_db.py F
-    a/test_db2.py F
-    b/test_error.py E
-    
-    ======= ERRORS ========
-    _______ ERROR at setup of test_root ________
-    file $REGENDOC_TMPDIR/b/test_error.py, line 1
-      def test_root(db):  # no db here, will error out
-            fixture 'db' not found
-            available fixtures: record_xml_property, recwarn, cache, capsys, pytestconfig, tmpdir_factory, capfd, monkeypatch, tmpdir
-            use 'py.test --fixtures [testpath]' for help on them.
-    
-    $REGENDOC_TMPDIR/b/test_error.py:1
-    ======= FAILURES ========
-    _______ TestUserHandling.test_modification ________
-    
-    self = <test_step.TestUserHandling object at 0xdeadbeef>
-    
-        def test_modification(self):
-    >       assert 0
-    E       assert 0
-    
-    test_step.py:9: AssertionError
-    _______ test_a1 ________
-    
-    db = <conftest.DB object at 0xdeadbeef>
-    
-        def test_a1(db):
-    >       assert 0, db  # to show value
-    E       AssertionError: <conftest.DB object at 0xdeadbeef>
-    E       assert 0
-    
-    a/test_db.py:2: AssertionError
-    _______ test_a2 ________
-    
-    db = <conftest.DB object at 0xdeadbeef>
-    
-        def test_a2(db):
-    >       assert 0, db  # to show value
-    E       AssertionError: <conftest.DB object at 0xdeadbeef>
-    E       assert 0
-    
-    a/test_db2.py:2: AssertionError
-    ======= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12 seconds ========
-
-The two test modules in the ``a`` directory see the same ``db`` fixture instance
-while the one test in the sister-directory ``b`` doesn't see it.  We could of course
-also define a ``db`` fixture in that sister directory's ``conftest.py`` file.
-Note that each fixture is only instantiated if there is a test actually needing
-it (unless you use "autouse" fixture which are always executed ahead of the first test
-executing).
-
-
-post-process test reports / failures
----------------------------------------
-
-If you want to postprocess test reports and need access to the executing
-environment you can implement a hook that gets called when the test
-"report" object is about to be created.  Here we write out all failing
-test calls and also access a fixture (if it was used by the test) in
-case you want to query/look at it during your post processing.  In our
-case we just write some informations out to a ``failures`` file::
-
-    # content of conftest.py
-
-    import pytest
-    import os.path
-
-    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
-    def pytest_runtest_makereport(item, call):
-        # execute all other hooks to obtain the report object
-        outcome = yield
-        rep = outcome.get_result()
-
-        # we only look at actual failing test calls, not setup/teardown
-        if rep.when == "call" and rep.failed:
-            mode = "a" if os.path.exists("failures") else "w"
-            with open("failures", mode) as f:
-                # let's also access a fixture for the fun of it
-                if "tmpdir" in item.fixturenames:
-                    extra = " (%s)" % item.funcargs["tmpdir"]
-                else:
-                    extra = ""
-
-                f.write(rep.nodeid + extra + "\n")
-
-
-if you then have failing tests::
-
-    # content of test_module.py
-    def test_fail1(tmpdir):
-        assert 0
-    def test_fail2():
-        assert 0
-
-and run them::
-
-    $ py.test test_module.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    
-    test_module.py FF
-    
-    ======= FAILURES ========
-    _______ test_fail1 ________
-    
-    tmpdir = local('PYTEST_TMPDIR/test_fail10')
-    
-        def test_fail1(tmpdir):
-    >       assert 0
-    E       assert 0
-    
-    test_module.py:2: AssertionError
-    _______ test_fail2 ________
-    
-        def test_fail2():
-    >       assert 0
-    E       assert 0
-    
-    test_module.py:4: AssertionError
-    ======= 2 failed in 0.12 seconds ========
-
-you will have a "failures" file which contains the failing test ids::
-
-    $ cat failures
-    test_module.py::test_fail1 (PYTEST_TMPDIR/test_fail10)
-    test_module.py::test_fail2
-
-Making test result information available in fixtures
------------------------------------------------------------
-
-.. regendoc:wipe
-
-If you want to make test result reports available in fixture finalizers
-here is a little example implemented via a local plugin::
-
-    # content of conftest.py
-
-    import pytest
-
-    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
-    def pytest_runtest_makereport(item, call):
-        # execute all other hooks to obtain the report object
-        outcome = yield
-        rep = outcome.get_result()
-
-        # set an report attribute for each phase of a call, which can
-        # be "setup", "call", "teardown"
-
-        setattr(item, "rep_" + rep.when, rep)
-
-
-    @pytest.fixture
-    def something(request):
-        def fin():
-            # request.node is an "item" because we use the default
-            # "function" scope
-            if request.node.rep_setup.failed:
-                print ("setting up a test failed!", request.node.nodeid)
-            elif request.node.rep_setup.passed:
-                if request.node.rep_call.failed:
-                    print ("executing test failed", request.node.nodeid)
-        request.addfinalizer(fin)
-
-
-if you then have failing tests::
-
-    # content of test_module.py
-
-    import pytest
-
-    @pytest.fixture
-    def other():
-        assert 0
-
-    def test_setup_fails(something, other):
-        pass
-
-    def test_call_fails(something):
-        assert 0
-
-    def test_fail2():
-        assert 0
-
-and run it::
-
-    $ py.test -s test_module.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 3 items
-    
-    test_module.py Esetting up a test failed! test_module.py::test_setup_fails
-    Fexecuting test failed test_module.py::test_call_fails
-    F
-    
-    ======= ERRORS ========
-    _______ ERROR at setup of test_setup_fails ________
-    
-        @pytest.fixture
-        def other():
-    >       assert 0
-    E       assert 0
-    
-    test_module.py:6: AssertionError
-    ======= FAILURES ========
-    _______ test_call_fails ________
-    
-    something = None
-    
-        def test_call_fails(something):
-    >       assert 0
-    E       assert 0
-    
-    test_module.py:12: AssertionError
-    _______ test_fail2 ________
-    
-        def test_fail2():
-    >       assert 0
-    E       assert 0
-    
-    test_module.py:15: AssertionError
-    ======= 2 failed, 1 error in 0.12 seconds ========
-
-You'll see that the fixture finalizers could use the precise reporting
-information.
-
-Integrating pytest runner and cx_freeze
------------------------------------------------------------
-
-If you freeze your application using a tool like
-`cx_freeze <http://cx-freeze.readthedocs.org>`_ in order to distribute it
-to your end-users, it is a good idea to also package your test runner and run
-your tests using the frozen application.
-
-This way packaging errors such as dependencies not being
-included into the executable can be detected early while also allowing you to
-send test files to users so they can run them in their machines, which can be
-invaluable to obtain more information about a hard to reproduce bug.
-
-Unfortunately ``cx_freeze`` can't discover them
-automatically because of ``pytest``'s use of dynamic module loading, so you
-must declare them explicitly by using ``pytest.freeze_includes()``::
-
-    # contents of setup.py
-    from cx_Freeze import setup, Executable
-    import pytest
-
-    setup(
-        name="app_main",
-        executables=[Executable("app_main.py")],
-        options={"build_exe":
-            {
-            'includes': pytest.freeze_includes()}
-            },
-        # ... other options
-    )
-
-If you don't want to ship a different executable just in order to run your tests,
-you can make your program check for a certain flag and pass control
-over to ``pytest`` instead. For example::
-
-    # contents of app_main.py
-    import sys
-
-    if len(sys.argv) > 1 and sys.argv[1] == '--pytest':
-        import pytest
-        sys.exit(pytest.main(sys.argv[2:]))
-    else:
-        # normal application execution: at this point argv can be parsed
-        # by your argument-parsing library of choice as usual
-        ...
-
-This makes it convenient to execute your tests from within your frozen
-application, using standard ``py.test`` command-line options::
-
-    ./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/
diff --git a/tools/pytest/doc/en/example/special.rst b/tools/pytest/doc/en/example/special.rst
deleted file mode 100644
index 58e66d4..0000000
--- a/tools/pytest/doc/en/example/special.rst
+++ /dev/null
@@ -1,72 +0,0 @@
-A session-fixture which can look at all collected tests
-----------------------------------------------------------------
-
-A session-scoped fixture effectively has access to all
-collected test items.  Here is an example of a fixture
-function which walks all collected tests and looks
-if their test class defines a ``callme`` method and
-calls it::
-
-    # content of conftest.py
-
-    import pytest
-
-    @pytest.fixture(scope="session", autouse=True)
-    def callattr_ahead_of_alltests(request):
-        print ("callattr_ahead_of_alltests called")
-        seen = set([None])
-        session = request.node
-        for item in session.items:
-            cls = item.getparent(pytest.Class)
-            if cls not in seen:
-                if hasattr(cls.obj, "callme"):
-                   cls.obj.callme()
-                seen.add(cls)
-
-test classes may now define a ``callme`` method which
-will be called ahead of running any tests::
-
-    # content of test_module.py
-
-    class TestHello:
-        @classmethod
-        def callme(cls):
-            print ("callme called!")
-
-        def test_method1(self):
-            print ("test_method1 called")
-
-        def test_method2(self):
-            print ("test_method1 called")
-
-    class TestOther:
-        @classmethod
-        def callme(cls):
-            print ("callme other called")
-        def test_other(self):
-            print ("test other")
-
-    # works with unittest as well ...
-    import unittest
-
-    class SomeTest(unittest.TestCase):
-        @classmethod
-        def callme(self):
-            print ("SomeTest callme called")
-
-        def test_unit1(self):
-            print ("test_unit1 method called")
-
-If you run this without output capturing::
-
-    $ py.test -q -s test_module.py
-    callattr_ahead_of_alltests called
-    callme called!
-    callme other called
-    SomeTest callme called
-    test_method1 called
-    .test_method1 called
-    .test other
-    .test_unit1 method called
-    .
-    4 passed in 0.12 seconds
diff --git a/tools/pytest/doc/en/faq.rst b/tools/pytest/doc/en/faq.rst
deleted file mode 100644
index fd7ca35..0000000
--- a/tools/pytest/doc/en/faq.rst
+++ /dev/null
@@ -1,165 +0,0 @@
-Some Issues and Questions
-==================================
-
-.. note::
-
-    This FAQ is here only mostly for historic reasons.  Checkout
-    `pytest Q&A at Stackoverflow <http://stackoverflow.com/search?q=pytest>`_
-    for many questions and answers related to pytest and/or use
-    :ref:`contact channels` to get help.
-
-On naming, nosetests, licensing and magic
-------------------------------------------------
-
-How does pytest relate to nose and unittest?
-+++++++++++++++++++++++++++++++++++++++++++++++++
-
-``pytest`` and nose_ share basic philosophy when it comes
-to running and writing Python tests.  In fact, you can run many tests
-written for nose with ``pytest``.  nose_ was originally created
-as a clone of ``pytest`` when ``pytest`` was in the ``0.8`` release
-cycle.  Note that starting with pytest-2.0 support for running unittest
-test suites is majorly improved.
-
-how does pytest relate to twisted's trial?
-++++++++++++++++++++++++++++++++++++++++++++++
-
-Since some time ``pytest`` has builtin support for supporting tests
-written using trial. It does not itself start a reactor, however,
-and does not handle Deferreds returned from a test in pytest style.
-If you are using trial's unittest.TestCase chances are that you can
-just run your tests even if you return Deferreds.  In addition,
-there also is a dedicated `pytest-twisted
-<http://pypi.python.org/pypi/pytest-twisted>`_ plugin which allows you to
-return deferreds from pytest-style tests, allowing the use of
-:ref:`fixtures` and other features.
-
-how does pytest work with Django?
-++++++++++++++++++++++++++++++++++++++++++++++
-
-In 2012, some work is going into the `pytest-django plugin <http://pypi.python.org/pypi/pytest-django>`_.  It substitutes the usage of Django's
-``manage.py test`` and allows the use of all pytest features_ most of which
-are not available from Django directly.
-
-.. _features: features.html
-
-
-What's this "magic" with pytest? (historic notes)
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-Around 2007 (version ``0.8``) some people thought that ``pytest``
-was using too much "magic".  It had been part of the `pylib`_ which
-contains a lot of unrelated python library code.  Around 2010 there
-was a major cleanup refactoring, which removed unused or deprecated code
-and resulted in the new ``pytest`` PyPI package which strictly contains
-only test-related code.  This release also brought a complete pluginification
-such that the core is around 300 lines of code and everything else is
-implemented in plugins.  Thus ``pytest`` today is a small, universally runnable
-and customizable testing framework for Python.   Note, however, that
-``pytest`` uses metaprogramming techniques and reading its source is
-thus likely not something for Python beginners.
-
-A second "magic" issue was the assert statement debugging feature.
-Nowadays, ``pytest`` explicitly rewrites assert statements in test modules
-in order to provide more useful :ref:`assert feedback <assertfeedback>`.
-This completely avoids previous issues of confusing assertion-reporting.
-It also means, that you can use Python's ``-O`` optimization without losing
-assertions in test modules.
-
-``pytest`` contains a second, mostly obsolete, assert debugging technique
-invoked via ``--assert=reinterpret``: When an ``assert`` statement fails, ``pytest`` re-interprets
-the expression part to show intermediate values.  This technique suffers
-from a caveat that the rewriting does not: If your expression has side
-effects (better to avoid them anyway!) the intermediate values may not
-be the same, confusing the reinterpreter and obfuscating the initial
-error (this is also explained at the command line if it happens).
-
-You can also turn off all assertion interaction using the
-``--assert=plain`` option.
-
-.. _`py namespaces`: index.html
-.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
-
-
-Why a ``py.test`` instead of a ``pytest`` command?
-++++++++++++++++++++++++++++++++++++++++++++++++++
-
-Some of the reasons are historic, others are practical.  ``pytest``
-used to be part of the ``py`` package which provided several developer
-utilities, all starting with ``py.<TAB>``, thus providing nice
-TAB-completion. If
-you install ``pip install pycmd`` you get these tools from a separate
-package.  These days the command line tool could be called ``pytest``
-but since many people have gotten used to the old name and there
-is another tool named "pytest" we just decided to stick with
-``py.test`` for now.
-
-pytest fixtures, parametrized tests
--------------------------------------------------------
-
-.. _funcargs: funcargs.html
-
-Is using pytest fixtures versus xUnit setup a style question?
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-For simple applications and for people experienced with nose_ or
-unittest-style test setup using `xUnit style setup`_ probably
-feels natural.  For larger test suites, parametrized testing
-or setup of complex test resources using fixtures_ may feel more natural.
-Moreover, fixtures are ideal for writing advanced test support
-code (like e.g. the monkeypatch_, the tmpdir_ or capture_ fixtures)
-because the support code can register setup/teardown functions
-in a managed class/module/function scope.
-
-.. _monkeypatch: monkeypatch.html
-.. _tmpdir: tmpdir.html
-.. _capture: capture.html
-.. _fixtures: fixture.html
-
-.. _`why pytest_pyfuncarg__ methods?`:
-
-.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
-
-Can I yield multiple values from a fixture function?
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-There are two conceptual reasons why yielding from a factory function
-is not possible:
-
-* If multiple factories yielded values there would
-  be no natural place to determine the combination
-  policy - in real-world examples some combinations
-  often should not run.
-
-* Calling factories for obtaining test function arguments
-  is part of setting up and running a test.  At that
-  point it is not possible to add new test calls to
-  the test collection anymore.
-
-However, with pytest-2.3 you can use the :ref:`@pytest.fixture` decorator
-and specify ``params`` so that all tests depending on the factory-created
-resource will run multiple times with different parameters.
-
-You can also use the ``pytest_generate_tests`` hook to
-implement the `parametrization scheme of your choice`_. See also
-:ref:`paramexamples` for more examples.
-
-.. _`parametrization scheme of your choice`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
-
-pytest interaction with other packages
----------------------------------------------------
-
-Issues with pytest, multiprocess and setuptools?
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-On Windows the multiprocess package will instantiate sub processes
-by pickling and thus implicitly re-import a lot of local modules.
-Unfortunately, setuptools-0.6.11 does not ``if __name__=='__main__'``
-protect its generated command line script.  This leads to infinite
-recursion when running a test that instantiates Processes.
-
-As of mid-2013, there shouldn't be a problem anymore when you
-use the standard setuptools (note that distribute has been merged
-back into setuptools which is now shipped directly with virtualenv).
-
-.. include:: links.inc
diff --git a/tools/pytest/doc/en/feedback.rst b/tools/pytest/doc/en/feedback.rst
deleted file mode 100644
index 9c63b76..0000000
--- a/tools/pytest/doc/en/feedback.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-
-What users say:
-
-    `py.test is pretty much the best thing ever`_ (Alex Gaynor)
-
-
-.. _`py.test is pretty much the best thing ever`_ (Alex Gaynor)
-    http://twitter.com/#!/alex_gaynor/status/22389410366
diff --git a/tools/pytest/doc/en/fixture.rst b/tools/pytest/doc/en/fixture.rst
deleted file mode 100644
index f48607a..0000000
--- a/tools/pytest/doc/en/fixture.rst
+++ /dev/null
@@ -1,987 +0,0 @@
-.. _fixture:
-.. _fixtures:
-.. _`fixture functions`:
-
-pytest fixtures: explicit, modular, scalable
-========================================================
-
-.. currentmodule:: _pytest.python
-
-.. versionadded:: 2.0/2.3/2.4
-
-.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
-.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
-.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection#Definition
-
-The `purpose of test fixtures`_ is to provide a fixed baseline
-upon which tests can reliably and repeatedly execute.   pytest fixtures
-offer dramatic improvements over the classic xUnit style of setup/teardown
-functions:
-
-* fixtures have explicit names and are activated by declaring their use
-  from test functions, modules, classes or whole projects.
-
-* fixtures are implemented in a modular manner, as each fixture name
-  triggers a *fixture function* which can itself use other fixtures.
-
-* fixture management scales from simple unit to complex
-  functional testing, allowing to parametrize fixtures and tests according
-  to configuration and component options, or to re-use fixtures
-  across class, module or whole test session scopes.
-
-In addition, pytest continues to support :ref:`xunitsetup`.  You can mix
-both styles, moving incrementally from classic to new style, as you
-prefer.  You can also start out from existing :ref:`unittest.TestCase
-style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
-
-.. note::
-
-    pytest-2.4 introduced an additional experimental
-    :ref:`yield fixture mechanism <yieldfixture>` for easier context manager
-    integration and more linear writing of teardown code.
-
-.. _`funcargs`:
-.. _`funcarg mechanism`:
-.. _`fixture function`:
-.. _`@pytest.fixture`:
-.. _`pytest.fixture`:
-
-Fixtures as Function arguments
------------------------------------------
-
-Test functions can receive fixture objects by naming them as an input
-argument. For each argument name, a fixture function with that name provides
-the fixture object.  Fixture functions are registered by marking them with
-:py:func:`@pytest.fixture <_pytest.python.fixture>`.  Let's look at a simple
-self-contained test module containing a fixture and a test function
-using it::
-
-    # content of ./test_smtpsimple.py
-    import pytest
-
-    @pytest.fixture
-    def smtp():
-        import smtplib
-        return smtplib.SMTP("smtp.gmail.com")
-
-    def test_ehlo(smtp):
-        response, msg = smtp.ehlo()
-        assert response == 250
-        assert 0 # for demo purposes
-
-Here, the ``test_ehlo`` needs the ``smtp`` fixture value.  pytest
-will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
-marked ``smtp`` fixture function.  Running the test looks like this::
-
-    $ py.test test_smtpsimple.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_smtpsimple.py F
-    
-    ======= FAILURES ========
-    _______ test_ehlo ________
-    
-    smtp = <smtplib.SMTP object at 0xdeadbeef>
-    
-        def test_ehlo(smtp):
-            response, msg = smtp.ehlo()
-            assert response == 250
-    >       assert 0 # for demo purposes
-    E       assert 0
-    
-    test_smtpsimple.py:11: AssertionError
-    ======= 1 failed in 0.12 seconds ========
-
-In the failure traceback we see that the test function was called with a
-``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture
-function.  The test function fails on our deliberate ``assert 0``.  Here is
-the exact protocol used by ``pytest`` to call the test function this way:
-
-1. pytest :ref:`finds <test discovery>` the ``test_ehlo`` because
-   of the ``test_`` prefix.  The test function needs a function argument
-   named ``smtp``.  A matching fixture function is discovered by
-   looking for a fixture-marked function named ``smtp``.
-
-2. ``smtp()`` is called to create an instance.
-
-3. ``test_ehlo(<SMTP instance>)`` is called and fails in the last
-   line of the test function.
-
-Note that if you misspell a function argument or want
-to use one that isn't available, you'll see an error
-with a list of available function arguments.
-
-.. Note::
-
-    You can always issue::
-
-        py.test --fixtures test_simplefactory.py
-
-    to see available fixtures.
-
-    In versions prior to 2.3 there was no ``@pytest.fixture`` marker
-    and you had to use a magic ``pytest_funcarg__NAME`` prefix
-    for the fixture factory.  This remains and will remain supported
-    but is not anymore advertised as the primary means of declaring fixture
-    functions.
-
-"Funcargs" a prime example of dependency injection
----------------------------------------------------
-
-When injecting fixtures to test functions, pytest-2.0 introduced the
-term "funcargs" or "funcarg mechanism" which continues to be present
-also in docs today.  It now refers to the specific case of injecting
-fixture values as arguments to test functions.  With pytest-2.3 there are
-more possibilities to use fixtures but "funcargs" remain as the main way
-as they allow to directly state the dependencies of a test function.
-
-As the following examples show in more detail, funcargs allow test
-functions to easily receive and work against specific pre-initialized
-application objects without having to care about import/setup/cleanup
-details.  It's a prime example of `dependency injection`_ where fixture
-functions take the role of the *injector* and test functions are the
-*consumers* of fixture objects.
-
-.. _smtpshared:
-
-Sharing a fixture across tests in a module (or class/session)
------------------------------------------------------------------
-
-.. regendoc:wipe
-
-Fixtures requiring network access depend on connectivity and are
-usually time-expensive to create.  Extending the previous example, we
-can add a ``scope='module'`` parameter to the
-:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation
-to cause the decorated ``smtp`` fixture function to only be invoked once
-per test module.  Multiple test functions in a test module will thus
-each receive the same ``smtp`` fixture instance.  The next example puts
-the fixture function into a separate ``conftest.py`` file so
-that tests from multiple test modules in the directory can
-access the fixture function::
-
-    # content of conftest.py
-    import pytest
-    import smtplib
-
-    @pytest.fixture(scope="module")
-    def smtp():
-        return smtplib.SMTP("smtp.gmail.com")
-
-The name of the fixture again is ``smtp`` and you can access its result by
-listing the name ``smtp`` as an input parameter in any test or fixture
-function (in or below the directory where ``conftest.py`` is located)::
-
-    # content of test_module.py
-
-    def test_ehlo(smtp):
-        response, msg = smtp.ehlo()
-        assert response == 250
-        assert b"smtp.gmail.com" in msg
-        assert 0  # for demo purposes   
-
-    def test_noop(smtp):
-        response, msg = smtp.noop()
-        assert response == 250
-        assert 0  # for demo purposes
-
-We deliberately insert failing ``assert 0`` statements in order to
-inspect what is going on and can now run the tests::
-
-    $ py.test test_module.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    
-    test_module.py FF
-    
-    ======= FAILURES ========
-    _______ test_ehlo ________
-    
-    smtp = <smtplib.SMTP object at 0xdeadbeef>
-    
-        def test_ehlo(smtp):
-            response, msg = smtp.ehlo()
-            assert response == 250
-            assert b"smtp.gmail.com" in msg
-    >       assert 0  # for demo purposes
-    E       assert 0
-    
-    test_module.py:6: AssertionError
-    _______ test_noop ________
-    
-    smtp = <smtplib.SMTP object at 0xdeadbeef>
-    
-        def test_noop(smtp):
-            response, msg = smtp.noop()
-            assert response == 250
-    >       assert 0  # for demo purposes
-    E       assert 0
-    
-    test_module.py:11: AssertionError
-    ======= 2 failed in 0.12 seconds ========
-
-You see the two ``assert 0`` failing and more importantly you can also see
-that the same (module-scoped) ``smtp`` object was passed into the two
-test functions because pytest shows the incoming argument values in the
-traceback.  As a result, the two test functions using ``smtp`` run as
-quick as a single one because they reuse the same instance.
-
-If you decide that you rather want to have a session-scoped ``smtp``
-instance, you can simply declare it:
-
-.. code-block:: python
-
-    @pytest.fixture(scope="session")
-    def smtp(...):
-        # the returned fixture value will be shared for
-        # all tests needing it
-
-.. _`finalization`:
-
-Fixture finalization / executing teardown code
--------------------------------------------------------------
-
-pytest supports execution of fixture specific finalization code
-when the fixture goes out of scope.  By accepting a ``request`` object
-into your fixture function you can call its ``request.addfinalizer`` one
-or multiple times::
-
-    # content of conftest.py
-
-    import smtplib
-    import pytest
-
-    @pytest.fixture(scope="module")
-    def smtp(request):
-        smtp = smtplib.SMTP("smtp.gmail.com")
-        def fin():
-            print ("teardown smtp")
-            smtp.close()
-        request.addfinalizer(fin)
-        return smtp  # provide the fixture value
-
-The ``fin`` function will execute when the last test using
-the fixture in the module has finished execution.
-
-Let's execute it::
-
-    $ py.test -s -q --tb=no
-    FFteardown smtp
-    
-    2 failed in 0.12 seconds
-
-We see that the ``smtp`` instance is finalized after the two
-tests finished execution.  Note that if we decorated our fixture
-function with ``scope='function'`` then fixture setup and cleanup would
-occur around each single test.  In either case the test
-module itself does not need to change or know about these details
-of fixture setup.
-
-
-.. _`request-context`:
-
-Fixtures can introspect the requesting test context
--------------------------------------------------------------
-
-Fixture function can accept the :py:class:`request <FixtureRequest>` object
-to introspect the "requesting" test function, class or module context.
-Further extending the previous ``smtp`` fixture example, let's
-read an optional server URL from the test module which uses our fixture::
-
-    # content of conftest.py
-    import pytest
-    import smtplib
-
-    @pytest.fixture(scope="module")
-    def smtp(request):
-        server = getattr(request.module, "smtpserver", "smtp.gmail.com")
-        smtp = smtplib.SMTP(server)
-
-        def fin():
-            print ("finalizing %s (%s)" % (smtp, server))
-            smtp.close()
-        request.addfinalizer(fin)
-        return smtp
-
-We use the ``request.module`` attribute to optionally obtain an
-``smtpserver`` attribute from the test module.  If we just execute
-again, nothing much has changed::
-
-    $ py.test -s -q --tb=no
-    FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
-    
-    2 failed in 0.12 seconds
-
-Let's quickly create another test module that actually sets the
-server URL in its module namespace::
-
-    # content of test_anothersmtp.py
-
-    smtpserver = "mail.python.org"  # will be read by smtp fixture
-
-    def test_showhelo(smtp):
-        assert 0, smtp.helo()
-
-Running it::
-
-    $ py.test -qq --tb=short test_anothersmtp.py
-    F
-    ======= FAILURES ========
-    _______ test_showhelo ________
-    test_anothersmtp.py:5: in test_showhelo
-        assert 0, smtp.helo()
-    E   AssertionError: (250, b'mail.python.org')
-    E   assert 0
-
-voila! The ``smtp`` fixture function picked up our mail server name
-from the module namespace.
-
-.. _`fixture-parametrize`:
-
-Parametrizing a fixture
------------------------------------------------------------------
-
-Fixture functions can be parametrized in which case they will be called
-multiple times, each time executing the set of dependent tests, i. e. the
-tests that depend on this fixture.  Test functions do usually not need
-to be aware of their re-running.  Fixture parametrization helps to
-write exhaustive functional tests for components which themselves can be
-configured in multiple ways.
-
-Extending the previous example, we can flag the fixture to create two
-``smtp`` fixture instances which will cause all tests using the fixture
-to run twice.  The fixture function gets access to each parameter
-through the special :py:class:`request <FixtureRequest>` object::
-
-    # content of conftest.py
-    import pytest
-    import smtplib
-
-    @pytest.fixture(scope="module",
-                    params=["smtp.gmail.com", "mail.python.org"])
-    def smtp(request):
-        smtp = smtplib.SMTP(request.param)
-        def fin():
-            print ("finalizing %s" % smtp)
-            smtp.close()
-        request.addfinalizer(fin)
-        return smtp
-
-The main change is the declaration of ``params`` with
-:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
-for each of which the fixture function will execute and can access
-a value via ``request.param``.  No test function code needs to change.
-So let's just do another run::
-
-    $ py.test -q test_module.py
-    FFFF
-    ======= FAILURES ========
-    _______ test_ehlo[smtp.gmail.com] ________
-    
-    smtp = <smtplib.SMTP object at 0xdeadbeef>
-    
-        def test_ehlo(smtp):
-            response, msg = smtp.ehlo()
-            assert response == 250
-            assert b"smtp.gmail.com" in msg
-    >       assert 0  # for demo purposes
-    E       assert 0
-    
-    test_module.py:6: AssertionError
-    _______ test_noop[smtp.gmail.com] ________
-    
-    smtp = <smtplib.SMTP object at 0xdeadbeef>
-    
-        def test_noop(smtp):
-            response, msg = smtp.noop()
-            assert response == 250
-    >       assert 0  # for demo purposes
-    E       assert 0
-    
-    test_module.py:11: AssertionError
-    _______ test_ehlo[mail.python.org] ________
-    
-    smtp = <smtplib.SMTP object at 0xdeadbeef>
-    
-        def test_ehlo(smtp):
-            response, msg = smtp.ehlo()
-            assert response == 250
-    >       assert b"smtp.gmail.com" in msg
-    E       assert b'smtp.gmail.com' in b'mail.python.org\nSIZE 51200000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
-    
-    test_module.py:5: AssertionError
-    -------------------------- Captured stdout setup ---------------------------
-    finalizing <smtplib.SMTP object at 0xdeadbeef>
-    _______ test_noop[mail.python.org] ________
-    
-    smtp = <smtplib.SMTP object at 0xdeadbeef>
-    
-        def test_noop(smtp):
-            response, msg = smtp.noop()
-            assert response == 250
-    >       assert 0  # for demo purposes
-    E       assert 0
-    
-    test_module.py:11: AssertionError
-    4 failed in 0.12 seconds
-
-We see that our two test functions each ran twice, against the different
-``smtp`` instances.  Note also, that with the ``mail.python.org``
-connection the second test fails in ``test_ehlo`` because a
-different server string is expected than what arrived.
-
-pytest will build a string that is the test ID for each fixture value
-in a parametrized fixture, e.g. ``test_ehlo[smtp.gmail.com]`` and
-``test_ehlo[mail.python.org]`` in the above examples.  These IDs can
-be used with ``-k`` to select specific cases to run, and they will
-also identify the specific case when one is failing.  Running pytest
-with ``--collect-only`` will show the generated IDs.
-
-Numbers, strings, booleans and None will have their usual string
-representation used in the test ID. For other objects, pytest will
-make a string based on the argument name.  It is possible to customise
-the string used in a test ID for a certain fixture value by using the
-``ids`` keyword argument::
-
-   # content of test_ids.py
-   import pytest
-
-   @pytest.fixture(params=[0, 1], ids=["spam", "ham"])
-   def a(request):
-       return request.param
-
-   def test_a(a):
-       pass
-
-   def idfn(fixture_value):
-       if fixture_value == 0:
-           return "eggs"
-       else:
-           return None
-
-   @pytest.fixture(params=[0, 1], ids=idfn)
-   def b(request):
-       return request.param
-
-   def test_b(b):
-       pass
-
-The above shows how ``ids`` can be either a list of strings to use or
-a function which will be called with the fixture value and then
-has to return a string to use.  In the latter case if the function
-return ``None`` then pytest's auto-generated ID will be used.
-
-Running the above tests results in the following test IDs being used::
-
-   $ py.test --collect-only
-   ======= test session starts ========
-   platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-   rootdir: $REGENDOC_TMPDIR, inifile: 
-   collected 10 items
-   <Module 'test_anothersmtp.py'>
-     <Function 'test_showhelo[smtp.gmail.com]'>
-     <Function 'test_showhelo[mail.python.org]'>
-   <Module 'test_ids.py'>
-     <Function 'test_a[spam]'>
-     <Function 'test_a[ham]'>
-     <Function 'test_b[eggs]'>
-     <Function 'test_b[1]'>
-   <Module 'test_module.py'>
-     <Function 'test_ehlo[smtp.gmail.com]'>
-     <Function 'test_noop[smtp.gmail.com]'>
-     <Function 'test_ehlo[mail.python.org]'>
-     <Function 'test_noop[mail.python.org]'>
-   
-   ======= no tests ran in 0.12 seconds ========
-
-.. _`interdependent fixtures`:
-
-Modularity: using fixtures from a fixture function
-----------------------------------------------------------
-
-You can not only use fixtures in test functions but fixture functions
-can use other fixtures themselves.  This contributes to a modular design
-of your fixtures and allows re-use of framework-specific fixtures across
-many projects.  As a simple example, we can extend the previous example
-and instantiate an object ``app`` where we stick the already defined
-``smtp`` resource into it::
-
-    # content of test_appsetup.py
-
-    import pytest
-
-    class App:
-        def __init__(self, smtp):
-            self.smtp = smtp
-
-    @pytest.fixture(scope="module")
-    def app(smtp):
-        return App(smtp)
-
-    def test_smtp_exists(app):
-        assert app.smtp
-
-Here we declare an ``app`` fixture which receives the previously defined
-``smtp`` fixture and instantiates an ``App`` object with it.  Let's run it::
-
-    $ py.test -v test_appsetup.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 2 items
-    
-    test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED
-    test_appsetup.py::test_smtp_exists[mail.python.org] PASSED
-    
-    ======= 2 passed in 0.12 seconds ========
-
-Due to the parametrization of ``smtp`` the test will run twice with two
-different ``App`` instances and respective smtp servers.  There is no
-need for the ``app`` fixture to be aware of the ``smtp`` parametrization
-as pytest will fully analyse the fixture dependency graph.
-
-Note, that the ``app`` fixture has a scope of ``module`` and uses a
-module-scoped ``smtp`` fixture.  The example would still work if ``smtp``
-was cached on a ``session`` scope: it is fine for fixtures to use
-"broader" scoped fixtures but not the other way round:
-A session-scoped fixture could not use a module-scoped one in a
-meaningful way.
-
-
-.. _`automatic per-resource grouping`:
-
-Automatic grouping of tests by fixture instances
-----------------------------------------------------------
-
-.. regendoc: wipe
-
-pytest minimizes the number of active fixtures during test runs.
-If you have a parametrized fixture, then all the tests using it will
-first execute with one instance and then finalizers are called
-before the next fixture instance is created.  Among other things,
-this eases testing of applications which create and use global state.
-
-The following example uses two parametrized funcargs, one of which is
-scoped on a per-module basis, and all the functions perform ``print`` calls
-to show the setup/teardown flow::
-
-    # content of test_module.py
-    import pytest
-
-    @pytest.fixture(scope="module", params=["mod1", "mod2"])
-    def modarg(request):
-        param = request.param
-        print ("create", param)
-        def fin():
-            print ("fin %s" % param)
-        return param
-
-    @pytest.fixture(scope="function", params=[1,2])
-    def otherarg(request):
-        return request.param
-
-    def test_0(otherarg):
-        print ("  test0", otherarg)
-    def test_1(modarg):
-        print ("  test1", modarg)
-    def test_2(otherarg, modarg):
-        print ("  test2", otherarg, modarg)
-
-Let's run the tests in verbose mode and with looking at the print-output::
-
-    $ py.test -v -s test_module.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4
-    cachedir: .cache
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collecting ... collected 8 items
-    
-    test_module.py::test_0[1]   test0 1
-    PASSED
-    test_module.py::test_0[2]   test0 2
-    PASSED
-    test_module.py::test_1[mod1] create mod1
-      test1 mod1
-    PASSED
-    test_module.py::test_2[1-mod1]   test2 1 mod1
-    PASSED
-    test_module.py::test_2[2-mod1]   test2 2 mod1
-    PASSED
-    test_module.py::test_1[mod2] create mod2
-      test1 mod2
-    PASSED
-    test_module.py::test_2[1-mod2]   test2 1 mod2
-    PASSED
-    test_module.py::test_2[2-mod2]   test2 2 mod2
-    PASSED
-    
-    ======= 8 passed in 0.12 seconds ========
-
-You can see that the parametrized module-scoped ``modarg`` resource caused
-an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed
-before the ``mod2`` resource was setup.
-
-
-.. _`usefixtures`:
-
-Using fixtures from classes, modules or projects
-----------------------------------------------------------------------
-
-.. regendoc:wipe
-
-Sometimes test functions do not directly need access to a fixture object.
-For example, tests may require to operate with an empty directory as the
-current working directory but otherwise do not care for the concrete
-directory.  Here is how you can can use the standard `tempfile
-<http://docs.python.org/library/tempfile.html>`_ and pytest fixtures to
-achieve it.  We separate the creation of the fixture into a conftest.py
-file::
-
-    # content of conftest.py
-
-    import pytest
-    import tempfile
-    import os
-
-    @pytest.fixture()
-    def cleandir():
-        newpath = tempfile.mkdtemp()
-        os.chdir(newpath)
-
-and declare its use in a test module via a ``usefixtures`` marker::
-
-    # content of test_setenv.py
-    import os
-    import pytest
-
-    @pytest.mark.usefixtures("cleandir")
-    class TestDirectoryInit:
-        def test_cwd_starts_empty(self):
-            assert os.listdir(os.getcwd()) == []
-            with open("myfile", "w") as f:
-                f.write("hello")
-
-        def test_cwd_again_starts_empty(self):
-            assert os.listdir(os.getcwd()) == []
-
-Due to the ``usefixtures`` marker, the ``cleandir`` fixture
-will be required for the execution of each test method, just as if
-you specified a "cleandir" function argument to each of them.  Let's run it
-to verify our fixture is activated and the tests pass::
-
-    $ py.test -q
-    ..
-    2 passed in 0.12 seconds
-
-You can specify multiple fixtures like this:
-
-.. code-block:: python
-
-    @pytest.mark.usefixtures("cleandir", "anotherfixture")
-
-and you may specify fixture usage at the test module level, using
-a generic feature of the mark mechanism:
-
-.. code-block:: python
-
-    pytestmark = pytest.mark.usefixtures("cleandir")
-
-Note that the assigned variable *must* be called ``pytestmark``, assigning e.g.
-``foomark`` will not activate the fixtures.
-
-Lastly you can put fixtures required by all tests in your project
-into an ini-file:
-
-.. code-block:: ini
-
-    # content of pytest.ini
-    [pytest]
-    usefixtures = cleandir
-
-
-.. _`autouse`:
-.. _`autouse fixtures`:
-
-Autouse fixtures (xUnit setup on steroids)
-----------------------------------------------------------------------
-
-.. regendoc:wipe
-
-Occasionally, you may want to have fixtures get invoked automatically
-without a `usefixtures`_ or `funcargs`_ reference.   As a practical
-example, suppose we have a database fixture which has a
-begin/rollback/commit architecture and we want to automatically surround
-each test method by a transaction and a rollback.  Here is a dummy
-self-contained implementation of this idea::
-
-    # content of test_db_transact.py
-
-    import pytest
-
-    class DB:
-        def __init__(self):
-            self.intransaction = []
-        def begin(self, name):
-            self.intransaction.append(name)
-        def rollback(self):
-            self.intransaction.pop()
-
-    @pytest.fixture(scope="module")
-    def db():
-        return DB()
-
-    class TestClass:
-        @pytest.fixture(autouse=True)
-        def transact(self, request, db):
-            db.begin(request.function.__name__)
-            request.addfinalizer(db.rollback)
-
-        def test_method1(self, db):
-            assert db.intransaction == ["test_method1"]
-
-        def test_method2(self, db):
-            assert db.intransaction == ["test_method2"]
-
-The class-level ``transact`` fixture is marked with *autouse=true*
-which implies that all test methods in the class will use this fixture
-without a need to state it in the test function signature or with a
-class-level ``usefixtures`` decorator.
-
-If we run it, we get two passing tests::
-
-    $ py.test -q
-    ..
-    2 passed in 0.12 seconds
-
-Here is how autouse fixtures work in other scopes:
-
-- if an autouse fixture is defined in a test module, all its test
-  functions automatically use it.
-
-- if an autouse fixture is defined in a conftest.py file then all tests in
-  all test modules below its directory will invoke the fixture.
-
-- lastly, and **please use that with care**: if you define an autouse
-  fixture in a plugin, it will be invoked for all tests in all projects
-  where the plugin is installed.  This can be useful if a fixture only
-  anyway works in the presence of certain settings e. g. in the ini-file.  Such
-  a global fixture should always quickly determine if it should do
-  any work and avoid otherwise expensive imports or computation.
-
-Note that the above ``transact`` fixture may very well be a fixture that
-you want to make available in your project without having it generally
-active.  The canonical way to do that is to put the transact definition
-into a conftest.py file **without** using ``autouse``::
-
-    # content of conftest.py
-    @pytest.fixture()
-    def transact(self, request, db):
-        db.begin()
-        request.addfinalizer(db.rollback)
-
-and then e.g. have a TestClass using it by declaring the need::
-
-    @pytest.mark.usefixtures("transact")
-    class TestClass:
-        def test_method1(self):
-            ...
-
-All test methods in this TestClass will use the transaction fixture while
-other test classes or functions in the module will not use it unless
-they also add a ``transact`` reference.
-
-Shifting (visibility of) fixture functions
-----------------------------------------------------
-
-If during implementing your tests you realize that you
-want to use a fixture function from multiple test files you can move it
-to a :ref:`conftest.py <conftest.py>` file or even separately installable
-:ref:`plugins <plugins>` without changing test code.  The discovery of
-fixtures functions starts at test classes, then test modules, then
-``conftest.py`` files and finally builtin and third party plugins.
-
-Overriding fixtures on various levels
--------------------------------------
-
-In relatively large test suite, you most likely need to ``override`` a ``global`` or ``root`` fixture with a ``locally``
-defined one, keeping the test code readable and maintainable.
-
-Override a fixture on a folder (conftest) level
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Given the tests file structure is:
-
-::
-
-    tests/
-        __init__.py
-
-        conftest.py
-            # content of tests/conftest.py
-            import pytest
-
-            @pytest.fixture
-            def username():
-                return 'username'
-
-        test_something.py
-            # content of tests/test_something.py
-            def test_username(username):
-                assert username == 'username'
-
-        subfolder/
-            __init__.py
-
-            conftest.py
-                # content of tests/subfolder/conftest.py
-                import pytest
-
-                @pytest.fixture
-                def username(username):
-                    return 'overridden-' + username
-
-            test_something.py
-                # content of tests/subfolder/test_something.py
-                def test_username(username):
-                    assert username == 'overridden-username'
-
-As you can see, a fixture with the same name can be overridden for certain test folder level.
-Note that the ``base`` or ``super`` fixture can be accessed from the ``overriding``
-fixture easily - used in the example above.
-
-Override a fixture on a test module level
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Given the tests file structure is:
-
-::
-
-    tests/
-        __init__.py
-
-        conftest.py
-            # content of tests/conftest.py
-            @pytest.fixture
-            def username():
-                return 'username'
-
-        test_something.py
-            # content of tests/test_something.py
-            import pytest
-
-            @pytest.fixture
-            def username(username):
-                return 'overridden-' + username
-
-            def test_username(username):
-                assert username == 'overridden-username'
-
-        test_something_else.py
-            # content of tests/test_something_else.py
-            import pytest
-
-            @pytest.fixture
-            def username(username):
-                return 'overridden-else-' + username
-
-            def test_username(username):
-                assert username == 'overridden-else-username'
-
-In the example above, a fixture with the same name can be overridden for certain test module.
-
-
-Override a fixture with direct test parametrization
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Given the tests file structure is:
-
-::
-
-    tests/
-        __init__.py
-
-        conftest.py
-            # content of tests/conftest.py
-            import pytest
-
-            @pytest.fixture
-            def username():
-                return 'username'
-
-            @pytest.fixture
-            def other_username(username):
-                return 'other-' + username
-
-        test_something.py
-            # content of tests/test_something.py
-            import pytest
-
-            @pytest.mark.parametrize('username', ['directly-overridden-username'])
-            def test_username(username):
-                assert username == 'directly-overridden-username'
-
-            @pytest.mark.parametrize('username', ['directly-overridden-username-other'])
-            def test_username_other(other_username):
-                assert username == 'other-directly-overridden-username-other'
-
-In the example above, a fixture value is overridden by the test parameter value. Note that the value of the fixture
-can be overridden this way even if the test doesn't use it directly (doesn't mention it in the function prototype).
-
-
-Override a parametrized fixture with non-parametrized one and vice versa
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Given the tests file structure is:
-
-::
-
-    tests/
-        __init__.py
-
-        conftest.py
-            # content of tests/conftest.py
-            import pytest
-
-            @pytest.fixture(params=['one', 'two', 'three'])
-            def parametrized_username(request):
-                return request.param
-
-            @pytest.fixture
-            def non_parametrized_username(request):
-                return 'username'
-
-        test_something.py
-            # content of tests/test_something.py
-            import pytest
-
-            @pytest.fixture
-            def parametrized_username():
-                return 'overridden-username'
-
-            @pytest.fixture(params=['one', 'two', 'three'])
-            def non_parametrized_username(request):
-                return request.param
-
-            def test_username(parametrized_username):
-                assert parametrized_username == 'overridden-username'
-
-            def test_parametrized_username(non_parametrized_username):
-                assert non_parametrized_username in ['one', 'two', 'three']
-
-        test_something_else.py
-            # content of tests/test_something_else.py
-            def test_username(parametrized_username):
-                assert parametrized_username in ['one', 'two', 'three']
-
-            def test_username(non_parametrized_username):
-                assert non_parametrized_username == 'username'
-
-In the example above, a parametrized fixture is overridden with a non-parametrized version, and
-a non-parametrized fixture is overridden with a parametrized version for certain test module.
-The same applies for the test folder level obviously.
diff --git a/tools/pytest/doc/en/funcarg_compare.rst b/tools/pytest/doc/en/funcarg_compare.rst
deleted file mode 100644
index 832922e..0000000
--- a/tools/pytest/doc/en/funcarg_compare.rst
+++ /dev/null
@@ -1,217 +0,0 @@
-
-.. _`funcargcompare`:
-
-pytest-2.3: reasoning for fixture/funcarg evolution
-=============================================================
-
-**Target audience**: Reading this document requires basic knowledge of 
-python testing, xUnit setup methods and the (previous) basic pytest 
-funcarg mechanism, see http://pytest.org/2.2.4/funcargs.html 
-If you are new to pytest, then you can simply ignore this
-section and read the other sections.
-
-.. currentmodule:: _pytest
-
-Shortcomings of the previous ``pytest_funcarg__`` mechanism
---------------------------------------------------------------
-
-The pre pytest-2.3 funcarg mechanism calls a factory each time a
-funcarg for a test function is required.  If a factory wants to
-re-use a resource across different scopes, it often used 
-the ``request.cached_setup()`` helper to manage caching of 
-resources.  Here is a basic example how we could implement 
-a per-session Database object::
-
-    # content of conftest.py 
-    class Database:
-        def __init__(self):
-            print ("database instance created")
-        def destroy(self):
-            print ("database instance destroyed")
-
-    def pytest_funcarg__db(request):
-        return request.cached_setup(setup=DataBase, 
-                                    teardown=lambda db: db.destroy,
-                                    scope="session")
-
-There are several limitations and difficulties with this approach:
-
-1. Scoping funcarg resource creation is not straight forward, instead one must
-   understand the intricate cached_setup() method mechanics.
-
-2. parametrizing the "db" resource is not straight forward: 
-   you need to apply a "parametrize" decorator or implement a
-   :py:func:`~hookspec.pytest_generate_tests` hook 
-   calling :py:func:`~python.Metafunc.parametrize` which
-   performs parametrization at the places where the resource 
-   is used.  Moreover, you need to modify the factory to use an 
-   ``extrakey`` parameter containing ``request.param`` to the 
-   :py:func:`~python.Request.cached_setup` call.
-
-3. Multiple parametrized session-scoped resources will be active
-   at the same time, making it hard for them to affect global state
-   of the application under test.
-
-4. there is no way how you can make use of funcarg factories
-   in xUnit setup methods.
-
-5. A non-parametrized fixture function cannot use a parametrized 
-   funcarg resource if it isn't stated in the test function signature.
-
-All of these limitations are addressed with pytest-2.3 and its
-improved :ref:`fixture mechanism <fixture>`.
-
-
-Direct scoping of fixture/funcarg factories
---------------------------------------------------------
-
-Instead of calling cached_setup() with a cache scope, you can use the
-:ref:`@pytest.fixture <pytest.fixture>` decorator and directly state
-the scope::
-
-    @pytest.fixture(scope="session")
-    def db(request):
-        # factory will only be invoked once per session - 
-        db = DataBase()
-        request.addfinalizer(db.destroy)  # destroy when session is finished
-        return db
-
-This factory implementation does not need to call ``cached_setup()`` anymore
-because it will only be invoked once per session.  Moreover, the 
-``request.addfinalizer()`` registers a finalizer according to the specified
-resource scope on which the factory function is operating.
-
-
-Direct parametrization of funcarg resource factories 
-----------------------------------------------------------
-
-Previously, funcarg factories could not directly cause parametrization.
-You needed to specify a ``@parametrize`` decorator on your test function
-or implement a ``pytest_generate_tests`` hook to perform
-parametrization, i.e. calling a test multiple times with different value
-sets.  pytest-2.3 introduces a decorator for use on the factory itself::
-
-    @pytest.fixture(params=["mysql", "pg"])
-    def db(request):
-        ... # use request.param
-
-Here the factory will be invoked twice (with the respective "mysql" 
-and "pg" values set as ``request.param`` attributes) and and all of 
-the tests requiring "db" will run twice as well.  The "mysql" and 
-"pg" values will also be used for reporting the test-invocation variants.
-
-This new way of parametrizing funcarg factories should in many cases
-allow to re-use already written factories because effectively
-``request.param`` was already used when test functions/classes were
-parametrized via
-:py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls.
-
-Of course it's perfectly fine to combine parametrization and scoping::
-
-    @pytest.fixture(scope="session", params=["mysql", "pg"])
-    def db(request):
-        if request.param == "mysql":
-            db = MySQL()
-        elif request.param == "pg":
-            db = PG()
-        request.addfinalizer(db.destroy)  # destroy when session is finished
-        return db
-
-This would execute all tests requiring the per-session "db" resource twice,
-receiving the values created by the two respective invocations to the
-factory function.
-
-
-No ``pytest_funcarg__`` prefix when using @fixture decorator
--------------------------------------------------------------------
-
-When using the ``@fixture`` decorator the name of the function
-denotes the name under which the resource can be accessed as a function
-argument::
-
-    @pytest.fixture()
-    def db(request):
-        ...
-
-The name under which the funcarg resource can be requested is ``db``.
-
-You can still use the "old" non-decorator way of specifying funcarg factories 
-aka::
-
-    def pytest_funcarg__db(request):
-        ...
-
-
-But it is then not possible to define scoping and parametrization.
-It is thus recommended to use the factory decorator.
-
-
-solving per-session setup / autouse fixtures
---------------------------------------------------------------
-
-pytest for a long time offered a pytest_configure and a pytest_sessionstart
-hook which are often used to setup global resources.  This suffers from
-several problems:
-
-1. in distributed testing the master process would setup test resources
-   that are never needed because it only co-ordinates the test run
-   activities of the slave processes.  
-
-2. if you only perform a collection (with "--collect-only") 
-   resource-setup will still be executed.  
-
-3. If a pytest_sessionstart is contained in some subdirectories
-   conftest.py file, it will not be called.  This stems from the
-   fact that this hook is actually used for reporting, in particular
-   the test-header with platform/custom information.
-
-Moreover, it was not easy to define a scoped setup from plugins or
-conftest files other than to implement a ``pytest_runtest_setup()`` hook
-and caring for scoping/caching yourself.  And it's virtually impossible
-to do this with parametrization as ``pytest_runtest_setup()`` is called
-during test execution and parametrization happens at collection time.
-
-It follows that pytest_configure/session/runtest_setup are often not
-appropriate for implementing common fixture needs.  Therefore, 
-pytest-2.3 introduces :ref:`autouse fixtures` which fully
-integrate with the generic :ref:`fixture mechanism <fixture>` 
-and obsolete many prior uses of pytest hooks.
-
-funcargs/fixture discovery now happens at collection time
----------------------------------------------------------------------
-
-pytest-2.3 takes care to discover fixture/funcarg factories
-at collection time.  This is more efficient especially for large test suites. 
-Moreover, a call to "py.test --collect-only" should be able to in the future
-show a lot of setup-information and thus presents a nice method to get an
-overview of fixture management in your project.
-
-.. _`compatibility notes`:
-
-.. _`funcargscompat`:
-
-Conclusion and compatibility notes
----------------------------------------------------------
-
-**funcargs** were originally introduced to pytest-2.0.  In pytest-2.3 
-the mechanism was extended and refined and is now described as
-fixtures:
-
-* previously funcarg factories were specified with a special 
-  ``pytest_funcarg__NAME`` prefix instead of using the 
-  ``@pytest.fixture`` decorator.
-
-* Factories received a ``request`` object which managed caching through
-  ``request.cached_setup()`` calls and allowed using other funcargs via 
-  ``request.getfuncargvalue()`` calls.  These intricate APIs made it hard 
-  to do proper parametrization and implement resource caching. The
-  new :py:func:`pytest.fixture` decorator allows to declare the scope
-  and let pytest figure things out for you.
-
-* if you used parametrization and funcarg factories which made use of
-  ``request.cached_setup()`` it is recommended to invest a few minutes
-  and simplify your fixture function code to use the :ref:`@pytest.fixture`
-  decorator instead.  This will also allow to take advantage of 
-  the automatic per-resource grouping of tests.
-
-
diff --git a/tools/pytest/doc/en/genapi.py b/tools/pytest/doc/en/genapi.py
deleted file mode 100644
index f8cdda6..0000000
--- a/tools/pytest/doc/en/genapi.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import textwrap
-import inspect
-
-class Writer:
-    def __init__(self, clsname):
-        self.clsname = clsname
-
-    def __enter__(self):
-        self.file = open("%s.api" % self.clsname, "w")
-        return self
-
-    def __exit__(self, *args):
-        self.file.close()
-        print "wrote", self.file.name
-
-    def line(self, line):
-        self.file.write(line+"\n")
-
-    def docmethod(self, method):
-        doc = " ".join(method.__doc__.split())
-        indent = "         "
-        w = textwrap.TextWrapper(initial_indent=indent,
-                                 subsequent_indent=indent)
-
-        spec = inspect.getargspec(method)
-        del spec.args[0]
-        self.line(".. py:method:: " + method.__name__ +
-                  inspect.formatargspec(*spec))
-        self.line("")
-        self.line(w.fill(doc))
-        self.line("")
-
-def pytest_funcarg__a(request):
-    with Writer("request") as writer:
-        writer.docmethod(request.getfuncargvalue)
-        writer.docmethod(request.cached_setup)
-        writer.docmethod(request.addfinalizer)
-        writer.docmethod(request.applymarker)
-
-def test_hello(a):
-    pass
diff --git a/tools/pytest/doc/en/getting-started.rst b/tools/pytest/doc/en/getting-started.rst
deleted file mode 100644
index 4a5b75a..0000000
--- a/tools/pytest/doc/en/getting-started.rst
+++ /dev/null
@@ -1,237 +0,0 @@
-Installation and Getting Started
-===================================
-
-**Pythons**: Python 2.6,2.7,3.3,3.4,3.5, Jython, PyPy-2.3
-
-**Platforms**: Unix/Posix and Windows
-
-**PyPI package name**: `pytest <http://pypi.python.org/pypi/pytest>`_
-
-**dependencies**: `py <http://pypi.python.org/pypi/py>`_,
-`colorama (Windows) <http://pypi.python.org/pypi/colorama>`_,
-`argparse (py26) <http://pypi.python.org/pypi/argparse>`_.
-
-**documentation as PDF**: `download latest <http://pytest.org/latest/pytest.pdf>`_
-
-.. _`getstarted`:
-.. _installation:
-
-Installation
-----------------------------------------
-
-Installation options::
-
-    pip install -U pytest # or
-    easy_install -U pytest
-
-To check your installation has installed the correct version::
-
-    $ py.test --version
-    This is pytest version 2.9.1, imported from $PYTHON_PREFIX/lib/python3.4/site-packages/pytest.py
-
-If you get an error checkout :ref:`installation issues`.
-
-.. _`simpletest`:
-
-Our first test run
-----------------------------------------------------------
-
-Let's create a first test file with a simple test function::
-
-    # content of test_sample.py
-    def func(x):
-        return x + 1
-
-    def test_answer():
-        assert func(3) == 5
-
-That's it. You can execute the test function now::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_sample.py F
-    
-    ======= FAILURES ========
-    _______ test_answer ________
-    
-        def test_answer():
-    >       assert func(3) == 5
-    E       assert 4 == 5
-    E        +  where 4 = func(3)
-    
-    test_sample.py:5: AssertionError
-    ======= 1 failed in 0.12 seconds ========
-
-We got a failure report because our little ``func(3)`` call did not return ``5``.
-
-.. note::
-
-    You can simply use the ``assert`` statement for asserting test
-    expectations.  pytest's :ref:`assert introspection` will intelligently
-    report intermediate values of the assert expression freeing
-    you from the need to learn the many names of `JUnit legacy methods`_.
-
-.. _`JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
-
-.. _`assert statement`: http://docs.python.org/reference/simple_stmts.html#the-assert-statement
-
-Running multiple tests
-----------------------------------------------------------
-
-``pytest`` will run all files in the current directory and its subdirectories of the form test_*.py or \*_test.py. More generally, it follows :ref:`standard test discovery rules <test discovery>`.
-
-
-Asserting that a certain exception is raised
---------------------------------------------------------------
-
-If you want to assert that some code raises an exception you can
-use the ``raises`` helper::
-
-    # content of test_sysexit.py
-    import pytest
-    def f():
-        raise SystemExit(1)
-
-    def test_mytest():
-        with pytest.raises(SystemExit):
-            f()
-
-Running it with, this time in "quiet" reporting mode::
-
-    $ py.test -q test_sysexit.py
-    .
-    1 passed in 0.12 seconds
-
-Grouping multiple tests in a class
---------------------------------------------------------------
-
-Once you start to have more than a few tests it often makes sense
-to group tests logically, in classes and modules.  Let's write a class
-containing two tests::
-
-    # content of test_class.py
-    class TestClass:
-        def test_one(self):
-            x = "this"
-            assert 'h' in x
-
-        def test_two(self):
-            x = "hello"
-            assert hasattr(x, 'check')
-
-The two tests are found because of the standard :ref:`test discovery`.
-There is no need to subclass anything.  We can simply
-run the module by passing its filename::
-
-    $ py.test -q test_class.py
-    .F
-    ======= FAILURES ========
-    _______ TestClass.test_two ________
-    
-    self = <test_class.TestClass object at 0xdeadbeef>
-    
-        def test_two(self):
-            x = "hello"
-    >       assert hasattr(x, 'check')
-    E       assert hasattr('hello', 'check')
-    
-    test_class.py:8: AssertionError
-    1 failed, 1 passed in 0.12 seconds
-
-The first test passed, the second failed. Again we can easily see
-the intermediate values used in the assertion, helping us to
-understand the reason for the failure.
-
-Going functional: requesting a unique temporary directory
---------------------------------------------------------------
-
-For functional tests one often needs to create some files
-and pass them to application objects.  pytest provides
-:ref:`builtinfixtures` which allow to request arbitrary
-resources, for example a unique temporary directory::
-
-    # content of test_tmpdir.py
-    def test_needsfiles(tmpdir):
-        print (tmpdir)
-        assert 0
-
-We list the name ``tmpdir`` in the test function signature and
-``pytest`` will lookup and call a fixture factory to create the resource
-before performing the test function call.  Let's just run it::
-
-    $ py.test -q test_tmpdir.py
-    F
-    ======= FAILURES ========
-    _______ test_needsfiles ________
-    
-    tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')
-    
-        def test_needsfiles(tmpdir):
-            print (tmpdir)
-    >       assert 0
-    E       assert 0
-    
-    test_tmpdir.py:3: AssertionError
-    --------------------------- Captured stdout call ---------------------------
-    PYTEST_TMPDIR/test_needsfiles0
-    1 failed in 0.12 seconds
-
-Before the test runs, a unique-per-test-invocation temporary directory
-was created.  More info at :ref:`tmpdir handling`.
-
-You can find out what kind of builtin :ref:`fixtures` exist by typing::
-
-    py.test --fixtures   # shows builtin and custom fixtures
-
-Where to go next
--------------------------------------
-
-Here are a few suggestions where to go next:
-
-* :ref:`cmdline` for command line invocation examples
-* :ref:`good practices <goodpractices>` for virtualenv, test layout, genscript support
-* :ref:`fixtures` for providing a functional baseline to your tests
-* :ref:`apiref` for documentation and examples on using ``pytest``
-* :ref:`plugins` managing and writing plugins
-
-.. _`installation issues`:
-
-Known Installation issues
-------------------------------
-
-easy_install or pip not found?
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-.. _`install pip`: http://www.pip-installer.org/en/latest/index.html
-
-`Install pip`_ for a state of the art python package installer.
-
-Install `setuptools`_ to get ``easy_install`` which allows to install
-``.egg`` binary format packages in addition to source-based ones.
-
-py.test not found on Windows despite installation?
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-.. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html
-
-- **Windows**: If "easy_install" or "py.test" are not found
-  you need to add the Python script path to your ``PATH``, see here:
-  `Python for Windows`_.  You may alternatively use an `ActivePython install`_
-  which does this for you automatically.
-
-.. _`ActivePython install`: http://www.activestate.com/activepython/downloads
-
-.. _`Jython does not create command line launchers`: http://bugs.jython.org/issue1491
-
-- **Jython2.5.1 on Windows XP**: `Jython does not create command line launchers`_
-  so ``py.test`` will not work correctly.  You may install py.test on
-  CPython and type ``py.test --genscript=mytest`` and then use
-  ``jython mytest`` to run your tests with Jython using ``pytest``.
-
- :ref:`examples` for more complex examples
-
-.. include:: links.inc
diff --git a/tools/pytest/doc/en/goodpractices.rst b/tools/pytest/doc/en/goodpractices.rst
deleted file mode 100644
index 2d8050b..0000000
--- a/tools/pytest/doc/en/goodpractices.rst
+++ /dev/null
@@ -1,278 +0,0 @@
-.. highlightlang:: python
-.. _`goodpractices`:
-
-Good Integration Practices
-=================================================
-
-
-.. _`test discovery`:
-.. _`Python test discovery`:
-
-Conventions for Python test discovery
--------------------------------------------------
-
-``pytest`` implements the following standard test discovery:
-
-* If no arguments are specified then collection starts from :confval:`testpaths`
-  (if configured) or the current directory. Alternatively, command line arguments
-  can be used in any combination of directories, file names or node ids.
-* recurse into directories, unless they match :confval:`norecursedirs`
-* ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
-* ``Test`` prefixed test classes (without an ``__init__`` method)
-* ``test_`` prefixed test functions or methods are test items
-
-For examples of how to customize your test discovery :doc:`example/pythoncollection`.
-
-Within Python modules, ``pytest`` also discovers tests using the standard
-:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
-
-
-Choosing a test layout / import rules
-------------------------------------------
-
-``pytest`` supports two common test layouts:
-
-* putting tests into an extra directory outside your actual application
-  code, useful if you have many functional tests or for other reasons
-  want to keep tests separate from actual application code (often a good
-  idea)::
-
-    setup.py   # your setuptools Python package metadata
-    mypkg/
-        __init__.py
-        appmodule.py
-    tests/
-        test_app.py
-        ...
-
-
-* inlining test directories into your application package, useful if you
-  have direct relation between (unit-)test and application modules and
-  want to distribute your tests along with your application::
-
-    setup.py   # your setuptools Python package metadata
-    mypkg/
-        __init__.py
-        appmodule.py
-        ...
-        test/
-            test_app.py
-            ...
-
-Important notes relating to both schemes:
-
-- **make sure that "mypkg" is importable**, for example by typing once::
-
-     pip install -e .   # install package using setup.py in editable mode
-
-- **avoid "__init__.py" files in your test directories**.
-  This way your tests can run easily against an installed version
-  of ``mypkg``, independently from the installed package if it contains
-  the tests or not.
-
-- With inlined tests you might put ``__init__.py`` into test
-  directories and make them installable as part of your application.
-  Using the ``py.test --pyargs mypkg`` invocation pytest will
-  discover where mypkg is installed and collect tests from there.
-  With the "external" test you can still distribute tests but they
-  will not be installed or become importable.
-
-Typically you can run tests by pointing to test directories or modules::
-
-    py.test tests/test_app.py       # for external test dirs
-    py.test mypkg/test/test_app.py  # for inlined test dirs
-    py.test mypkg                   # run tests in all below test directories
-    py.test                         # run all tests below current dir
-    ...
-
-Because of the above ``editable install`` mode you can change your
-source code (both tests and the app) and rerun tests at will.
-Once you are done with your work, you can `use tox`_ to make sure
-that the package is really correct and tests pass in all
-required configurations.
-
-.. note::
-
-    You can use Python3 namespace packages (PEP420) for your application
-    but pytest will still perform `test package name`_ discovery based on the
-    presence of ``__init__.py`` files.  If you use one of the
-    two recommended file system layouts above but leave away the ``__init__.py``
-    files from your directories it should just work on Python3.3 and above.  From
-    "inlined tests", however, you will need to use absolute imports for
-    getting at your application code.
-
-.. _`test package name`:
-
-.. note::
-
-    If ``pytest`` finds a "a/b/test_module.py" test file while
-    recursing into the filesystem it determines the import name
-    as follows:
-
-    * determine ``basedir``: this is the first "upward" (towards the root)
-      directory not containing an ``__init__.py``.  If e.g. both ``a``
-      and ``b`` contain an ``__init__.py`` file then the parent directory
-      of ``a`` will become the ``basedir``.
-
-    * perform ``sys.path.insert(0, basedir)`` to make the test module
-      importable under the fully qualified import name.
-
-    * ``import a.b.test_module`` where the path is determined
-      by converting path separators ``/`` into "." characters.  This means
-      you must follow the convention of having directory and file
-      names map directly to the import names.
-
-    The reason for this somewhat evolved importing technique is
-    that in larger projects multiple test modules might import
-    from each other and thus deriving a canonical import name helps
-    to avoid surprises such as a test modules getting imported twice.
-
-
-.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
-.. _`buildout`: http://www.buildout.org/
-.. _pip: http://pypi.python.org/pypi/pip
-
-.. _`use tox`:
-
-Tox
-------
-
-For development, we recommend to use virtualenv_ environments and pip_
-for installing your application and any dependencies
-as well as the ``pytest`` package itself. This ensures your code and
-dependencies are isolated from the system Python installation.
-
-If you frequently release code and want to make sure that your actual
-package passes all tests you may want to look into `tox`_, the
-virtualenv test automation tool and its `pytest support
-<http://testrun.org/tox/latest/example/pytest.html>`_.
-Tox helps you to setup virtualenv environments with pre-defined
-dependencies and then executing a pre-configured test command with
-options.  It will run tests against the installed package and not
-against your source code checkout, helping to detect packaging
-glitches.
-
-Continuous integration services such as Jenkins_ can make use of the
-``--junitxml=PATH`` option to create a JUnitXML file and generate reports.
-
-
-Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
---------------------------------------------------------------------------
-
-You can integrate test runs into your setuptools based project
-with the `pytest-runner <https://pypi.python.org/pypi/pytest-runner>`_ plugin.
-
-Add this to ``setup.py`` file:
-
-.. code-block:: python
-
-    from setuptools import setup
-
-    setup(
-        #...,
-        setup_requires=['pytest-runner', ...],
-        tests_require=['pytest', ...],
-        #...,
-    )
-
-
-And create an alias into ``setup.cfg`` file:
-
-
-.. code-block:: ini
-
-    [aliases]
-    test=pytest
-
-If you now type::
-
-    python setup.py test
-
-this will execute your tests using ``pytest-runner``. As this is a
-standalone version of ``pytest`` no prior installation whatsoever is
-required for calling the test command. You can also pass additional
-arguments to py.test such as your test directory or other
-options using ``--addopts``.
-
-
-Manual Integration
-^^^^^^^^^^^^^^^^^^
-
-If for some reason you don't want/can't use ``pytest-runner``, you can write
-your own setuptools Test command for invoking pytest.
-
-.. code-block:: python
-
-    import sys
-
-    from setuptools.command.test import test as TestCommand
-
-
-    class PyTest(TestCommand):
-        user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
-
-        def initialize_options(self):
-            TestCommand.initialize_options(self)
-            self.pytest_args = []
-
-        def run_tests(self):
-            #import here, cause outside the eggs aren't loaded
-            import pytest
-            errno = pytest.main(self.pytest_args)
-            sys.exit(errno)
-
-
-    setup(
-        #...,
-        tests_require=['pytest'],
-        cmdclass = {'test': PyTest},
-        )
-
-Now if you run::
-
-    python setup.py test
-
-this will download ``pytest`` if needed and then run your tests
-as you would expect it to. You can pass a single string of arguments
-using the ``--pytest-args`` or ``-a`` command-line option. For example::
-
-    python setup.py test -a "--durations=5"
-
-is equivalent to running ``py.test --durations=5``.
-
-
-.. _standalone:
-.. _`genscript method`:
-
-(deprecated) Create a pytest standalone script
------------------------------------------------
-
-.. deprecated:: 2.8
-
-.. note::
-
-    ``genscript`` has been deprecated because:
-
-    * It cannot support plugins, rendering its usefulness extremely limited;
-    * Tooling has become much better since ``genscript`` was introduced;
-    * It is possible to build a zipped ``pytest`` application without the
-      shortcomings above.
-
-    There's no planned version in which this command will be removed
-    at the moment of this writing, but its use is discouraged for new
-    applications.
-
-If you are a maintainer or application developer and want people
-who don't deal with python much to easily run tests you may generate
-a standalone ``pytest`` script::
-
-    py.test --genscript=runtests.py
-
-This generates a ``runtests.py`` script which is a fully functional basic
-``pytest`` script, running unchanged under Python2 and Python3.
-You can tell people to download the script and then e.g.  run it like this::
-
-    python runtests.py
-
-
-.. include:: links.inc
diff --git a/tools/pytest/doc/en/index.rst b/tools/pytest/doc/en/index.rst
deleted file mode 100644
index 04b4512..0000000
--- a/tools/pytest/doc/en/index.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-
-.. _features:
-
-pytest: helps you write better programs
-=============================================
-
-**a mature full-featured Python testing tool**
-
- - runs on Posix/Windows, Python 2.6-3.5, PyPy and (possibly still) Jython-2.5.1
- - free and open source software, distributed under the terms of the :ref:`MIT license <license>`
- - **well tested** with more than a thousand tests against itself
- - **strict backward compatibility policy** for safe pytest upgrades
- - :ref:`comprehensive online <toc>` and `PDF documentation <pytest.pdf>`_
- - many :ref:`third party plugins <extplugins>` and :ref:`builtin helpers <pytest helpers>`,
- - used in :ref:`many small and large projects and organisations <projects>`
- - comes with many :ref:`tested examples <examples>`
-
-**provides easy no-boilerplate testing**
-
- - makes it :ref:`easy to get started <getstarted>`,
-   has many :ref:`usage options <usage>`
- - :ref:`assert with the assert statement`
- - helpful :ref:`traceback and failing assertion reporting <tbreportdemo>`
- - :ref:`print debugging <printdebugging>` and :ref:`the
-   capturing of standard output during test execution <captures>`
-
-**scales from simple unit to complex functional testing**
-
- - :ref:`modular parametrizeable fixtures <fixture>` (new in 2.3,
-   continuously improved)
- - :ref:`parametrized test functions <parametrized test functions>`
- - :ref:`mark`
- - :ref:`skipping` (improved in 2.4)
- - :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>`
- - :ref:`continuously re-run failing tests <looponfailing>`
- - :doc:`cache`
- - flexible :ref:`Python test discovery`
-
-**integrates with other testing methods and tools**:
-
- - multi-paradigm: pytest can run ``nose``, ``unittest`` and
-   ``doctest`` style test suites, including running testcases made for
-   Django and trial
- - supports :ref:`good integration practices <goodpractices>`
- - supports extended :ref:`xUnit style setup <xunitsetup>`
- - supports domain-specific :ref:`non-python tests`
- - supports generating `test coverage reports
-   <https://pypi.python.org/pypi/pytest-cov>`_
- - supports :pep:`8` compliant coding styles in tests
-
-**extensive plugin and customization system**:
-
- - all collection, reporting, running aspects are delegated to hook functions
- - customizations can be per-directory, per-project or per PyPI released plugin
- - it is easy to add command line options or customize existing behaviour
- - :ref:`easy to write your own plugins <writing-plugins>`
-
-
-.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
-
-
diff --git a/tools/pytest/doc/en/license.rst b/tools/pytest/doc/en/license.rst
deleted file mode 100644
index 3fc1dad..0000000
--- a/tools/pytest/doc/en/license.rst
+++ /dev/null
@@ -1,32 +0,0 @@
-.. _license:
-
-License
--------
-
-Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
-
-::
-
-    The MIT License (MIT)
-
-    Copyright (c) 2004-2016 Holger Krekel and others
-
-    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.
-
-.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
diff --git a/tools/pytest/doc/en/links.inc b/tools/pytest/doc/en/links.inc
deleted file mode 100644
index 3d78637..0000000
--- a/tools/pytest/doc/en/links.inc
+++ /dev/null
@@ -1,21 +0,0 @@
-
-.. _`skipping plugin`: plugin/skipping.html
-.. _`funcargs mechanism`: funcargs.html
-.. _`doctest.py`: http://docs.python.org/library/doctest.html
-.. _`xUnit style setup`: xunit_setup.html
-.. _`pytest_nose`: plugin/nose.html
-.. _`reStructured Text`: http://docutils.sourceforge.net
-.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
-.. _nose: https://nose.readthedocs.org/en/latest/
-.. _pytest: http://pypi.python.org/pypi/pytest
-.. _mercurial: http://mercurial.selenic.com/wiki/
-.. _`setuptools`: http://pypi.python.org/pypi/setuptools
-.. _`easy_install`:
-.. _`distribute docs`:
-.. _`distribute`: http://pypi.python.org/pypi/distribute
-.. _`pip`: http://pypi.python.org/pypi/pip
-.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
-.. _hudson: http://hudson-ci.org/
-.. _jenkins: http://jenkins-ci.org/
-.. _tox: http://testrun.org/tox
-.. _pylib: http://py.readthedocs.org/en/latest/
diff --git a/tools/pytest/doc/en/mark.rst b/tools/pytest/doc/en/mark.rst
deleted file mode 100644
index ab9546d..0000000
--- a/tools/pytest/doc/en/mark.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-
-.. _mark:
-
-Marking test functions with attributes
-=================================================================
-
-.. currentmodule:: _pytest.mark
-
-By using the ``pytest.mark`` helper you can easily set
-metadata on your test functions. There are
-some builtin markers, for example:
-
-* :ref:`skipif <skipif>` - skip a test function if a certain condition is met
-* :ref:`xfail <xfail>` - produce an "expected failure" outcome if a certain
-  condition is met
-* :ref:`parametrize <parametrizemark>` to perform multiple calls
-  to the same test function.
-
-It's easy to create custom markers or to apply markers
-to whole test classes or modules. See :ref:`mark examples` for examples
-which also serve as documentation.
-
-.. note::
-
-    Marks can only be applied to tests, having no effect on
-    :ref:`fixtures <fixtures>`.
-
-
-API reference for mark related objects
-------------------------------------------------
-
-.. autoclass:: MarkGenerator
-    :members:
-
-.. autoclass:: MarkDecorator
-    :members:
-
-.. autoclass:: MarkInfo
-    :members:
-
diff --git a/tools/pytest/doc/en/monkeypatch.rst b/tools/pytest/doc/en/monkeypatch.rst
deleted file mode 100644
index 4155a3a..0000000
--- a/tools/pytest/doc/en/monkeypatch.rst
+++ /dev/null
@@ -1,82 +0,0 @@
-
-Monkeypatching/mocking modules and environments
-================================================================
-
-.. currentmodule:: _pytest.monkeypatch
-
-Sometimes tests need to invoke functionality which depends
-on global settings or which invokes code which cannot be easily
-tested such as network access.  The ``monkeypatch`` function argument
-helps you to safely set/delete an attribute, dictionary item or
-environment variable or to modify ``sys.path`` for importing.
-See the `monkeypatch blog post`_ for some introduction material
-and a discussion of its motivation.
-
-.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
-
-Simple example: monkeypatching functions
----------------------------------------------------
-
-If you want to pretend that ``os.expanduser`` returns a certain
-directory, you can use the :py:meth:`monkeypatch.setattr` method to
-patch this function before calling into a function which uses it::
-
-    # content of test_module.py
-    import os.path
-    def getssh(): # pseudo application code
-        return os.path.join(os.path.expanduser("~admin"), '.ssh')
-
-    def test_mytest(monkeypatch):
-        def mockreturn(path):
-            return '/abc'
-        monkeypatch.setattr(os.path, 'expanduser', mockreturn)
-        x = getssh()
-        assert x == '/abc/.ssh'
-
-Here our test function monkeypatches ``os.path.expanduser`` and
-then calls into an function that calls it.  After the test function 
-finishes the ``os.path.expanduser`` modification will be undone.
-
-example: preventing "requests" from remote operations
-------------------------------------------------------
-
-If you want to prevent the "requests" library from performing http
-requests in all your tests, you can do::
-
-    # content of conftest.py
-    import pytest
-    @pytest.fixture(autouse=True)
-    def no_requests(monkeypatch):
-        monkeypatch.delattr("requests.sessions.Session.request")
-
-This autouse fixture will be executed for each test function and it
-will delete the method ``request.session.Session.request`` 
-so that any attempts within tests to create http requests will fail.
-
-example: setting an attribute on some class
-------------------------------------------------------
-
-If you need to patch out ``os.getcwd()`` to return an artificial
-value::
-
-    def test_some_interaction(monkeypatch):
-        monkeypatch.setattr("os.getcwd", lambda: "/")
-
-which is equivalent to the long form::
-
-    def test_some_interaction(monkeypatch):
-        import os
-        monkeypatch.setattr(os, "getcwd", lambda: "/")
-    
-
-
-Method reference of the monkeypatch function argument
------------------------------------------------------
-
-.. autoclass:: monkeypatch
-    :members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
-
-``monkeypatch.setattr/delattr/delitem/delenv()`` all
-by default raise an Exception if the target does not exist.
-Pass ``raising=False`` if you want to skip this check.
-
diff --git a/tools/pytest/doc/en/nose.rst b/tools/pytest/doc/en/nose.rst
deleted file mode 100644
index 3b92e04..0000000
--- a/tools/pytest/doc/en/nose.rst
+++ /dev/null
@@ -1,55 +0,0 @@
-Running tests written for nose
-=======================================
-
-.. include:: links.inc
-
-``pytest`` has basic support for running tests written for nose_.
-
-.. _nosestyle:
-
-Usage
--------------
-
-After :ref:`installation` type::
-
-    python setup.py develop  # make sure tests can import our package
-    py.test  # instead of 'nosetests'
-
-and you should be able to run your nose style tests and
-make use of pytest's capabilities.
-
-Supported nose Idioms
-----------------------
-
-* setup and teardown at module/class/method level
-* SkipTest exceptions and markers
-* setup/teardown decorators
-* yield-based tests and their setup
-* ``__test__`` attribute on modules/classes/functions
-* general usage of nose utilities
-
-Unsupported idioms / known issues
-----------------------------------
-
-- unittest-style ``setUp, tearDown, setUpClass, tearDownClass``
-  are recognized only on ``unittest.TestCase`` classes but not
-  on plain classes.  ``nose`` supports these methods also on plain
-  classes but pytest deliberately does not.  As nose and pytest already
-  both support ``setup_class, teardown_class, setup_method, teardown_method``
-  it doesn't seem useful to duplicate the unittest-API like nose does.
-  If you however rather think pytest should support the unittest-spelling on
-  plain classes please post `to this issue
-  <https://github.com/pytest-dev/pytest/issues/377/>`_.
-
-- nose imports test modules with the same import path (e.g.
-  ``tests.test_mod``) but different file system paths
-  (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
-  by extending sys.path/import semantics.   pytest does not do that
-  but there is discussion in `issue268 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support.  Note that
-  `nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.org/en/latest/differences.html#test-discovery-and-loading>`_.
-
-- nose-style doctests are not collected and executed correctly,
-  also doctest fixtures don't work.
-
-- no nose-configuration is recognized
-
diff --git a/tools/pytest/doc/en/overview.rst b/tools/pytest/doc/en/overview.rst
deleted file mode 100644
index eb26197..0000000
--- a/tools/pytest/doc/en/overview.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-==================================================
-Getting started basics
-==================================================
-
-.. toctree::
-   :maxdepth: 2
-
-   getting-started
-   usage
-   goodpractices
-   projects
-   faq
-
diff --git a/tools/pytest/doc/en/parametrize.rst b/tools/pytest/doc/en/parametrize.rst
deleted file mode 100644
index 919ac93..0000000
--- a/tools/pytest/doc/en/parametrize.rst
+++ /dev/null
@@ -1,219 +0,0 @@
-
-.. _`test generators`:
-.. _`parametrizing-tests`:
-.. _`parametrized test functions`:
-.. _`parametrize`:
-
-.. _`parametrize-basics`:
-
-Parametrizing fixtures and test functions
-==========================================================================
-
-pytest supports test parametrization in several well-integrated ways:
-
-- :py:func:`pytest.fixture` allows to define :ref:`parametrization
-  at the level of fixture functions <fixture-parametrize>`.
-
-* `@pytest.mark.parametrize`_ allows to define parametrization at the
-  function or class level, provides multiple argument/fixture sets
-  for a particular test function or class.
-
-* `pytest_generate_tests`_ enables implementing your own custom
-  dynamic parametrization scheme or extensions.
-
-.. _parametrizemark:
-.. _`@pytest.mark.parametrize`:
-
-
-``@pytest.mark.parametrize``: parametrizing test functions
----------------------------------------------------------------------
-
-.. regendoc: wipe
-
-.. versionadded:: 2.2
-.. versionchanged:: 2.4
-    Several improvements.
-
-The builtin ``pytest.mark.parametrize`` decorator enables
-parametrization of arguments for a test function.  Here is a typical example
-of a test function that implements checking that a certain input leads
-to an expected output::
-
-    # content of test_expectation.py
-    import pytest
-    @pytest.mark.parametrize("test_input,expected", [
-        ("3+5", 8),
-        ("2+4", 6),
-        ("6*9", 42),
-    ])
-    def test_eval(test_input, expected):
-        assert eval(test_input) == expected
-
-Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)``
-tuples so that the ``test_eval`` function will run three times using
-them in turn::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 3 items
-    
-    test_expectation.py ..F
-    
-    ======= FAILURES ========
-    _______ test_eval[6*9-42] ________
-    
-    test_input = '6*9', expected = 42
-    
-        @pytest.mark.parametrize("test_input,expected", [
-            ("3+5", 8),
-            ("2+4", 6),
-            ("6*9", 42),
-        ])
-        def test_eval(test_input, expected):
-    >       assert eval(test_input) == expected
-    E       assert 54 == 42
-    E        +  where 54 = eval('6*9')
-    
-    test_expectation.py:8: AssertionError
-    ======= 1 failed, 2 passed in 0.12 seconds ========
-
-As designed in this example, only one pair of input/output values fails
-the simple test function.  And as usual with test function arguments,
-you can see the ``input`` and ``output`` values in the traceback.
-
-Note that you could also use the parametrize marker on a class or a module
-(see :ref:`mark`) which would invoke several functions with the argument sets.
-
-It is also possible to mark individual test instances within parametrize,
-for example with the builtin ``mark.xfail``::
-
-    # content of test_expectation.py
-    import pytest
-    @pytest.mark.parametrize("test_input,expected", [
-        ("3+5", 8),
-        ("2+4", 6),
-        pytest.mark.xfail(("6*9", 42)),
-    ])
-    def test_eval(test_input, expected):
-        assert eval(test_input) == expected
-
-Let's run this::
-
-    $ py.test
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 3 items
-    
-    test_expectation.py ..x
-    
-    ======= 2 passed, 1 xfailed in 0.12 seconds ========
-
-The one parameter set which caused a failure previously now
-shows up as an "xfailed (expected to fail)" test.
-
-To get all combinations of multiple parametrized arguments you can stack
-``parametrize`` decorators::
-
-    import pytest
-    @pytest.mark.parametrize("x", [0, 1])
-    @pytest.mark.parametrize("y", [2, 3])
-    def test_foo(x, y):
-        pass
-
-This will run the test with the arguments set to x=0/y=2, x=0/y=3, x=1/y=2 and
-x=1/y=3.
-
-.. note::
-
-    In versions prior to 2.4 one needed to specify the argument
-    names as a tuple.  This remains valid but the simpler ``"name1,name2,..."``
-    comma-separated-string syntax is now advertised first because
-    it's easier to write and produces less line noise.
-
-.. _`pytest_generate_tests`:
-
-Basic ``pytest_generate_tests`` example
----------------------------------------------
-
-Sometimes you may want to implement your own parametrization scheme
-or implement some dynamism for determining the parameters or scope
-of a fixture.   For this, you can use the ``pytest_generate_tests`` hook
-which is called when collecting a test function.  Through the passed in
-``metafunc`` object you can inspect the requesting test context and, most
-importantly, you can call ``metafunc.parametrize()`` to cause
-parametrization.
-
-For example, let's say we want to run a test taking string inputs which
-we want to set via a new ``pytest`` command line option.  Let's first write
-a simple test accepting a ``stringinput`` fixture function argument::
-
-    # content of test_strings.py
-
-    def test_valid_string(stringinput):
-        assert stringinput.isalpha()
-
-Now we add a ``conftest.py`` file containing the addition of a
-command line option and the parametrization of our test function::
-
-    # content of conftest.py
-
-    def pytest_addoption(parser):
-        parser.addoption("--stringinput", action="append", default=[],
-            help="list of stringinputs to pass to test functions")
-
-    def pytest_generate_tests(metafunc):
-        if 'stringinput' in metafunc.fixturenames:
-            metafunc.parametrize("stringinput",
-                                 metafunc.config.option.stringinput)
-
-If we now pass two stringinput values, our test will run twice::
-
-    $ py.test -q --stringinput="hello" --stringinput="world" test_strings.py
-    ..
-    2 passed in 0.12 seconds
-
-Let's also run with a stringinput that will lead to a failing test::
-
-    $ py.test -q --stringinput="!" test_strings.py
-    F
-    ======= FAILURES ========
-    _______ test_valid_string[!] ________
-    
-    stringinput = '!'
-    
-        def test_valid_string(stringinput):
-    >       assert stringinput.isalpha()
-    E       assert <built-in method isalpha of str object at 0xdeadbeef>()
-    E        +  where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
-    
-    test_strings.py:3: AssertionError
-    1 failed in 0.12 seconds
-
-As expected our test function fails.
-
-If you don't specify a stringinput it will be skipped because
-``metafunc.parametrize()`` will be called with an empty parameter
-list::
-
-    $ py.test -q -rs test_strings.py
-    s
-    ======= short test summary info ========
-    SKIP [1] $PYTHON_PREFIX/lib/python3.4/site-packages/_pytest/python.py:1419: got empty parameter set, function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
-    1 skipped in 0.12 seconds
-
-For further examples, you might want to look at :ref:`more
-parametrization examples <paramexamples>`.
-
-.. _`metafunc object`:
-
-The **metafunc** object
--------------------------------------------
-
-.. currentmodule:: _pytest.python
-.. autoclass:: Metafunc
-
-    .. automethod:: Metafunc.parametrize
-    .. automethod:: Metafunc.addcall(funcargs=None,id=_notexists,param=_notexists)
diff --git a/tools/pytest/doc/en/plugins.rst b/tools/pytest/doc/en/plugins.rst
deleted file mode 100644
index e9b3f46..0000000
--- a/tools/pytest/doc/en/plugins.rst
+++ /dev/null
@@ -1,159 +0,0 @@
-.. _`external plugins`:
-.. _`extplugins`:
-.. _`using plugins`:
-
-Installing and Using plugins
-============================
-
-This section talks about installing and using third party plugins.
-For writing your own plugins, please refer to :ref:`writing-plugins`.
-
-Installing a third party plugin can be easily done with ``pip``::
-
-    pip install pytest-NAME
-    pip uninstall pytest-NAME
-
-If a plugin is installed, ``pytest`` automatically finds and integrates it,
-there is no need to activate it.
-
-Here is a little annotated list for some popular plugins:
-
-.. _`django`: https://www.djangoproject.com/
-
-* `pytest-django <http://pypi.python.org/pypi/pytest-django>`_: write tests
-  for `django`_ apps, using pytest integration.
-
-* `pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_: write tests
-  for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
-  processing deferreds from test functions.
-
-* `pytest-catchlog <http://pypi.python.org/pypi/pytest-catchlog>`_:
-  to capture and assert about messages from the logging module
-
-* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
-  coverage reporting, compatible with distributed testing
-
-* `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_:
-  to distribute tests to CPUs and remote hosts, to run in boxed
-  mode which allows to survive segmentation faults, to run in
-  looponfailing mode, automatically re-running failing tests
-  on file changes, see also :ref:`xdist`
-
-* `pytest-instafail <http://pypi.python.org/pypi/pytest-instafail>`_:
-  to report failures while the test run is happening.
-
-* `pytest-bdd <http://pypi.python.org/pypi/pytest-bdd>`_ and
-  `pytest-konira <http://pypi.python.org/pypi/pytest-konira>`_
-  to write tests using behaviour-driven testing.
-
-* `pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_:
-  to timeout tests based on function marks or global definitions.
-
-* `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_:
-  a ``--pep8`` option to enable PEP8 compliance checking.
-
-* `pytest-flakes <https://pypi.python.org/pypi/pytest-flakes>`_:
-  check source code with pyflakes.
-
-* `oejskit <http://pypi.python.org/pypi/oejskit>`_:
-  a plugin to run javascript unittests in live browsers.
-
-To see a complete list of all plugins with their latest testing
-status against different py.test and Python versions, please visit
-`plugincompat <http://plugincompat.herokuapp.com/>`_.
-
-You may also discover more plugins through a `pytest- pypi.python.org search`_.
-
-.. _`available installable plugins`:
-.. _`pytest- pypi.python.org search`: http://pypi.python.org/pypi?%3Aaction=search&term=pytest-&submit=search
-
-
-Requiring/Loading plugins in a test module or conftest file
------------------------------------------------------------
-
-You can require plugins in a test module or a conftest file like this::
-
-    pytest_plugins = "myapp.testsupport.myplugin",
-
-When the test module or conftest plugin is loaded the specified plugins
-will be loaded as well.
-
-    pytest_plugins = "myapp.testsupport.myplugin"
-
-which will import the specified module as a ``pytest`` plugin.
-
-.. _`findpluginname`:
-
-Finding out which plugins are active
-------------------------------------
-
-If you want to find out which plugins are active in your
-environment you can type::
-
-    py.test --traceconfig
-
-and will get an extended test header which shows activated plugins
-and their names. It will also print local plugins aka
-:ref:`conftest.py <conftest>` files when they are loaded.
-
-.. _`cmdunregister`:
-
-Deactivating / unregistering a plugin by name
----------------------------------------------
-
-You can prevent plugins from loading or unregister them::
-
-    py.test -p no:NAME
-
-This means that any subsequent try to activate/load the named
-plugin will not work.
-
-If you want to unconditionally disable a plugin for a project, you can add
-this option to your ``pytest.ini`` file:
-
-.. code-block:: ini
-
-      [pytest]
-      addopts = -p no:NAME
-
-Alternatively to disable it only in certain environments (for example in a
-CI server), you can set ``PYTEST_ADDOPTS`` environment variable to
-``-p no:name``.
-
-See :ref:`findpluginname` for how to obtain the name of a plugin.
-
-.. _`builtin plugins`:
-
-Pytest default plugin reference
--------------------------------
-
-
-You can find the source code for the following plugins
-in the `pytest repository <https://github.com/pytest-dev/pytest>`_.
-
-.. autosummary::
-
-    _pytest.assertion
-    _pytest.cacheprovider
-    _pytest.capture
-    _pytest.config
-    _pytest.doctest
-    _pytest.genscript
-    _pytest.helpconfig
-    _pytest.junitxml
-    _pytest.mark
-    _pytest.monkeypatch
-    _pytest.nose
-    _pytest.pastebin
-    _pytest.pdb
-    _pytest.pytester
-    _pytest.python
-    _pytest.recwarn
-    _pytest.resultlog
-    _pytest.runner
-    _pytest.main
-    _pytest.skipping
-    _pytest.terminal
-    _pytest.tmpdir
-    _pytest.unittest
-
diff --git a/tools/pytest/doc/en/projects.rst b/tools/pytest/doc/en/projects.rst
deleted file mode 100644
index 76d0049..0000000
--- a/tools/pytest/doc/en/projects.rst
+++ /dev/null
@@ -1,85 +0,0 @@
-.. _projects:
-
-.. image:: img/gaynor3.png
-   :width: 400px
-   :align: right
-
-.. image:: img/theuni.png
-   :width: 400px
-   :align: right
-
-.. image:: img/cramer2.png
-   :width: 400px
-   :align: right
-
-.. image:: img/keleshev.png
-   :width: 400px
-   :align: right
-
-
-Project examples
-==========================
-
-Here are some examples of projects using ``pytest`` (please send notes via :ref:`contact`):
-
-* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over
-  `21000 tests <http://buildbot.pypy.org/summary?branch=%3Ctrunk%3E>`_
-* the `MoinMoin <http://moinmo.in>`_ Wiki Engine
-* `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking
-* `Astropy <http://www.astropy.org/>`_ and `affiliated packages <http://www.astropy.org/affiliated/index.html>`_
-* `tox <http://testrun.org/tox>`_, virtualenv/Hudson integration tool
-* `PIDA <http://pida.co.uk>`_ framework for integrated development
-* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
-* `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB
-* `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities
-* `six <http://pypi.python.org/pypi/six/>`_ Python 2 and 3 compatibility utilities
-* `pediapress <http://code.pediapress.com/wiki/wiki>`_ MediaWiki articles
-* `mwlib <http://pypi.python.org/pypi/mwlib>`_ mediawiki parser and utility library
-* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion
-* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
-* `pylib <http://py.rtfd.org>`_ cross-platform path, IO, dynamic code library
-* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes
-* `bbfreeze <http://pypi.python.org/pypi/bbfreeze>`_ create standalone executables from Python scripts
-* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
-* `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem
-* `waskr <http://code.google.com/p/waskr/>`_ WSGI Stats Middleware
-* `guachi <http://code.google.com/p/guachi/>`_ global persistent configs for Python modules
-* `Circuits <http://pypi.python.org/pypi/circuits>`_ lightweight Event Driven Framework
-* `pygtk-helpers <http://bitbucket.org/aafshar/pygtkhelpers-main/>`_ easy interaction with PyGTK
-* `QuantumCore <http://quantumcore.org/>`_ statusmessage and repoze openid plugin
-* `pydataportability <http://pydataportability.net/>`_ libraries for managing the open web
-* `XIST <http://www.livinglogic.de/Python/xist/>`_ extensible HTML/XML generator
-* `tiddlyweb <http://pypi.python.org/pypi/tiddlyweb>`_ optionally headless, extensible RESTful datastore
-* `fancycompleter <http://bitbucket.org/antocuni/fancycompleter/src>`_ for colorful tab-completion
-* `Paludis <http://paludis.exherbo.org/>`_ tools for Gentoo Paludis package manager
-* `Gerald <http://halfcooked.com/code/gerald/>`_ schema comparison tool
-* `abjad <http://code.google.com/p/abjad/>`_ Python API for Formalized Score control
-* `bu <http://packages.python.org/bu/>`_ a microscopic build system
-* `katcp <https://bitbucket.org/hodgestar/katcp>`_ Telescope communication protocol over Twisted
-* `kss plugin timer <http://pypi.python.org/pypi/kss.plugin.timer>`_
-* `pyudev <http://pyudev.readthedocs.org/en/latest/tests/plugins.html>`_ a pure Python binding to the Linux library libudev
-* `pytest-localserver <https://bitbucket.org/basti/pytest-localserver/>`_ a plugin for pytest that provides a httpserver and smtpserver
-* `pytest-monkeyplus <http://pypi.python.org/pypi/pytest-monkeyplus/>`_ a plugin that extends monkeypatch
-
-These projects help integrate ``pytest`` into other Python frameworks:
-
-* `pytest-django <http://pypi.python.org/pypi/pytest-django/>`_ for Django
-* `zope.pytest <http://packages.python.org/zope.pytest/>`_ for Zope and Grok
-* `pytest_gae <http://pypi.python.org/pypi/pytest_gae/0.2.1>`_ for Google App Engine
-* There is `some work <https://github.com/Kotti/Kotti/blob/master/kotti/testing.py>`_ underway for Kotti, a CMS built in Pyramid/Pylons
-
-
-Some organisations using pytest
------------------------------------
-
-* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
-* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests
-* `Tandberg <http://www.tandberg.com/>`_
-* `Shootq <http://web.shootq.com/>`_
-* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
-* `cellzome <http://www.cellzome.com/>`_
-* `Open End, Gothenborg <http://www.openend.se>`_
-* `Laboratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_
-* `merlinux, Germany <http://merlinux.eu>`_
-* `ESSS, Brazil <http://www.esss.com.br>`_
-* many more ... (please be so kind to send a note via :ref:`contact`)
diff --git a/tools/pytest/doc/en/recwarn.rst b/tools/pytest/doc/en/recwarn.rst
deleted file mode 100644
index 3c42bfa..0000000
--- a/tools/pytest/doc/en/recwarn.rst
+++ /dev/null
@@ -1,130 +0,0 @@
-
-Asserting Warnings
-=====================================================
-
-.. _warns:
-
-Asserting warnings with the warns function
------------------------------------------------
-
-.. versionadded:: 2.8
-
-You can check that code raises a particular warning using ``pytest.warns``,
-which works in a similar manner to :ref:`raises <assertraises>`::
-
-    import warnings
-    import pytest
-
-    def test_warning():
-        with pytest.warns(UserWarning):
-            warnings.warn("my warning", UserWarning)
-
-The test will fail if the warning in question is not raised.
-
-You can also call ``pytest.warns`` on a function or code string::
-
-    pytest.warns(expected_warning, func, *args, **kwargs)
-    pytest.warns(expected_warning, "func(*args, **kwargs)")
-
-The function also returns a list of all raised warnings (as
-``warnings.WarningMessage`` objects), which you can query for
-additional information::
-
-    with pytest.warns(RuntimeWarning) as record:
-        warnings.warn("another warning", RuntimeWarning)
-
-    # check that only one warning was raised
-    assert len(record) == 1
-    # check that the message matches
-    assert record[0].message.args[0] == "another warning"
-
-Alternatively, you can examine raised warnings in detail using the
-:ref:`recwarn <recwarn>` fixture (see below).
-
-.. note::
-    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
-    differently; see :ref:`ensuring_function_triggers`.
-
-.. _recwarn:
-
-Recording warnings
-------------------------
-
-You can record raised warnings either using ``pytest.warns`` or with
-the ``recwarn`` fixture.
-
-To record with ``pytest.warns`` without asserting anything about the warnings,
-pass ``None`` as the expected warning type::
-
-    with pytest.warns(None) as record:
-        warnings.warn("user", UserWarning)
-        warnings.warn("runtime", RuntimeWarning)
-
-    assert len(record) == 2
-    assert str(record[0].message) == "user"
-    assert str(record[1].message) == "runtime"
-
-The ``recwarn`` fixture will record warnings for the whole function::
-
-    import warnings
-
-    def test_hello(recwarn):
-        warnings.warn("hello", UserWarning)
-        assert len(recwarn) == 1
-        w = recwarn.pop(UserWarning)
-        assert issubclass(w.category, UserWarning)
-        assert str(w.message) == "hello"
-        assert w.filename
-        assert w.lineno
-
-Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
-warnings: a WarningsRecorder instance. To view the recorded warnings, you can
-iterate over this instance, call ``len`` on it to get the number of recorded
-warnings, or index into it to get a particular recorded warning. It also
-provides these methods:
-
-.. autoclass:: _pytest.recwarn.WarningsRecorder()
-    :members:
-
-Each recorded warning has the attributes ``message``, ``category``,
-``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
-class of the warning. The ``message`` is the warning itself; calling
-``str(message)`` will return the actual message of the warning.
-
-.. note::
-    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
-    differently; see :ref:`ensuring_function_triggers`.
-
-.. _ensuring_function_triggers:
-
-Ensuring a function triggers a deprecation warning
--------------------------------------------------------
-
-You can also call a global helper for checking
-that a certain function call triggers a ``DeprecationWarning`` or
-``PendingDeprecationWarning``::
-
-    import pytest
-
-    def test_global():
-        pytest.deprecated_call(myfunction, 17)
-
-By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
-caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
-them. If you wish to record them in your own code, use the
-command ``warnings.simplefilter('always')``::
-
-    import warnings
-    import pytest
-
-    def test_deprecation(recwarn):
-        warnings.simplefilter('always')
-        warnings.warn("deprecated", DeprecationWarning)
-        assert len(recwarn) == 1
-        assert recwarn.pop(DeprecationWarning)
-
-You can also use it as a contextmanager::
-
-    def test_global():
-        with pytest.deprecated_call():
-            myobject.deprecated_method()
diff --git a/tools/pytest/doc/en/setup.rst b/tools/pytest/doc/en/setup.rst
deleted file mode 100644
index fe23534..0000000
--- a/tools/pytest/doc/en/setup.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-
-setup: is now an "autouse fixture"
-========================================================
-
-During development prior to the pytest-2.3 release the name
-``pytest.setup`` was used but before the release it was renamed
-and moved to become part of the general fixture mechanism,
-namely :ref:`autouse fixtures`
-
-
diff --git a/tools/pytest/doc/en/skipping.rst b/tools/pytest/doc/en/skipping.rst
deleted file mode 100644
index 4282afb..0000000
--- a/tools/pytest/doc/en/skipping.rst
+++ /dev/null
@@ -1,373 +0,0 @@
-.. _`skip and xfail`:
-
-.. _skipping:
-
-Skip and xfail: dealing with tests that can not succeed
-=====================================================================
-
-If you have test functions that cannot be run on certain platforms
-or that you expect to fail you can mark them accordingly or you
-may call helper functions during execution of setup or test functions.
-
-A *skip* means that you expect your test to pass unless the environment
-(e.g. wrong Python interpreter, missing dependency) prevents it to run.
-And *xfail* means that your test can run but you expect it to fail
-because there is an implementation problem.
-
-``pytest`` counts and lists *skip* and *xfail* tests separately. Detailed
-information about skipped/xfailed tests is not shown by default to avoid
-cluttering the output.  You can use the ``-r`` option to see details
-corresponding to the "short" letters shown in the test progress::
-
-    py.test -rxs  # show extra info on skips and xfails
-
-(See :ref:`how to change command line options defaults`)
-
-.. _skipif:
-.. _`condition booleans`:
-
-Marking a test function to be skipped
--------------------------------------------
-
-.. versionadded:: 2.9
-
-The simplest way to skip a test function is to mark it with the ``skip`` decorator
-which may be passed an optional ``reason``:
-
-.. code-block:: python
-
-    @pytest.mark.skip(reason="no way of currently testing this")
-    def test_the_unknown():
-        ...
-
-``skipif``
-~~~~~~~~~~
-
-.. versionadded:: 2.0, 2.4
-
-If you wish to skip something conditionally then you can use ``skipif`` instead.
-Here is an example of marking a test function to be skipped
-when run on a Python3.3 interpreter::
-
-    import sys
-    @pytest.mark.skipif(sys.version_info < (3,3),
-                        reason="requires python3.3")
-    def test_function():
-        ...
-
-During test function setup the condition ("sys.version_info >= (3,3)") is
-checked.  If it evaluates to True, the test function will be skipped
-with the specified reason.  Note that pytest enforces specifying a reason
-in order to report meaningful "skip reasons" (e.g. when using ``-rs``).
-If the condition is a string, it will be evaluated as python expression.
-
-You can share skipif markers between modules.  Consider this test module::
-
-    # content of test_mymodule.py
-
-    import mymodule
-    minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1),
-                                    reason="at least mymodule-1.1 required")
-    @minversion
-    def test_function():
-        ...
-
-You can import it from another test module::
-
-    # test_myothermodule.py
-    from test_mymodule import minversion
-
-    @minversion
-    def test_anotherfunction():
-        ...
-
-For larger test suites it's usually a good idea to have one file
-where you define the markers which you then consistently apply
-throughout your test suite.
-
-Alternatively, the pre pytest-2.4 way to specify :ref:`condition strings
-<string conditions>` instead of booleans will remain fully supported in future
-versions of pytest.  It couldn't be easily used for importing markers
-between test modules so it's no longer advertised as the primary method.
-
-
-Skip all test functions of a class or module
----------------------------------------------
-
-You can use the ``skipif`` decorator (and any other marker) on classes::
-
-    @pytest.mark.skipif(sys.platform == 'win32',
-                        reason="does not run on windows")
-    class TestPosixCalls:
-
-        def test_function(self):
-            "will not be setup or run under 'win32' platform"
-
-If the condition is true, this marker will produce a skip result for
-each of the test methods.
-
-If you want to skip all test functions of a module, you must use
-the ``pytestmark`` name on the global level:
-
-.. code-block:: python
-
-    # test_module.py
-    pytestmark = pytest.mark.skipif(...)
-
-If multiple "skipif" decorators are applied to a test function, it
-will be skipped if any of the skip conditions is true.
-
-.. _`whole class- or module level`: mark.html#scoped-marking
-
-.. _xfail:
-
-Mark a test function as expected to fail
--------------------------------------------------------
-
-You can use the ``xfail`` marker to indicate that you
-expect a test to fail::
-
-    @pytest.mark.xfail
-    def test_function():
-        ...
-
-This test will be run but no traceback will be reported
-when it fails. Instead terminal reporting will list it in the
-"expected to fail" (``XFAIL``) or "unexpectedly passing" (``XPASS``) sections.
-
-``strict`` parameter
-~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.9
-
-Both ``XFAIL`` and ``XPASS`` don't fail the test suite, unless the ``strict`` keyword-only
-parameter is passed as ``True``:
-
-.. code-block:: python
-
-    @pytest.mark.xfail(strict=True)
-    def test_function():
-        ...
-
-
-This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.
-
-You can change the default value of the ``strict`` parameter using the
-``xfail_strict`` ini option:
-
-.. code-block:: ini
-
-    [pytest]
-    xfail_strict=true
-
-
-``reason`` parameter
-~~~~~~~~~~~~~~~~~~~~
-
-As with skipif_ you can also mark your expectation of a failure
-on a particular platform::
-
-    @pytest.mark.xfail(sys.version_info >= (3,3),
-                       reason="python3.3 api changes")
-    def test_function():
-        ...
-
-
-``raises`` parameter
-~~~~~~~~~~~~~~~~~~~~
-
-If you want to be more specific as to why the test is failing, you can specify
-a single exception, or a list of exceptions, in the ``raises`` argument.
-
-.. code-block:: python
-
-    @pytest.mark.xfail(raises=RuntimeError)
-    def test_function():
-        ...
-
-Then the test will be reported as a regular failure if it fails with an
-exception not mentioned in ``raises``.
-
-``run`` parameter
-~~~~~~~~~~~~~~~~~
-
-If a test should be marked as xfail and reported as such but should not be
-even executed, use the ``run`` parameter as ``False``:
-
-.. code-block:: python
-
-    @pytest.mark.xfail(run=False)
-    def test_function():
-        ...
-
-This is specially useful for marking crashing tests for later inspection.
-
-
-Ignoring xfail marks
-~~~~~~~~~~~~~~~~~~~~
-
-By specifying on the commandline::
-
-    pytest --runxfail
-
-you can force the running and reporting of an ``xfail`` marked test
-as if it weren't marked at all.
-
-Examples
-~~~~~~~~
-
-Here is a simple test file with the several usages:
-
-.. literalinclude:: example/xfail_demo.py
-
-Running it with the report-on-xfail option gives this output::
-
-    example $ py.test -rx xfail_demo.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR/example, inifile: 
-    collected 7 items
-    
-    xfail_demo.py xxxxxxx
-    ======= short test summary info ========
-    XFAIL xfail_demo.py::test_hello
-    XFAIL xfail_demo.py::test_hello2
-      reason: [NOTRUN] 
-    XFAIL xfail_demo.py::test_hello3
-      condition: hasattr(os, 'sep')
-    XFAIL xfail_demo.py::test_hello4
-      bug 110
-    XFAIL xfail_demo.py::test_hello5
-      condition: pytest.__version__[0] != "17"
-    XFAIL xfail_demo.py::test_hello6
-      reason: reason
-    XFAIL xfail_demo.py::test_hello7
-    
-    ======= 7 xfailed in 0.12 seconds ========
-
-xfail signature summary
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Here's the signature of the ``xfail`` marker, using Python 3 keyword-only
-arguments syntax:
-
-.. code-block:: python
-
-    def xfail(condition=None, *, reason=None, raises=None, run=True, strict=False):
-
-
-
-.. _`skip/xfail with parametrize`:
-
-Skip/xfail with parametrize
----------------------------
-
-It is possible to apply markers like skip and xfail to individual
-test instances when using parametrize::
-
-    import pytest
-
-    @pytest.mark.parametrize(("n", "expected"), [
-        (1, 2),
-    pytest.mark.xfail((1, 0)),
-	pytest.mark.xfail(reason="some bug")((1, 3)),
-        (2, 3),
-        (3, 4),
-        (4, 5),
-        pytest.mark.skipif("sys.version_info >= (3,0)")((10, 11)),
-    ])
-    def test_increment(n, expected):
-        assert n + 1 == expected
-
-
-Imperative xfail from within a test or setup function
-------------------------------------------------------
-
-If you cannot declare xfail- of skipif conditions at import
-time you can also imperatively produce an according outcome
-imperatively, in test or setup code::
-
-    def test_function():
-        if not valid_config():
-            pytest.xfail("failing configuration (but should work)")
-            # or
-            pytest.skip("unsupported configuration")
-
-
-Skipping on a missing import dependency
---------------------------------------------------
-
-You can use the following import helper at module level
-or within a test or test setup function::
-
-    docutils = pytest.importorskip("docutils")
-
-If ``docutils`` cannot be imported here, this will lead to a
-skip outcome of the test.  You can also skip based on the
-version number of a library::
-
-    docutils = pytest.importorskip("docutils", minversion="0.3")
-
-The version will be read from the specified
-module's ``__version__`` attribute.
-
-
-.. _string conditions:
-
-specifying conditions as strings versus booleans
-----------------------------------------------------------
-
-Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
-to use strings::
-
-    import sys
-    @pytest.mark.skipif("sys.version_info >= (3,3)")
-    def test_function():
-        ...
-
-During test function setup the skipif condition is evaluated by calling
-``eval('sys.version_info >= (3,0)', namespace)``.  The namespace contains
-all the module globals, and ``os`` and ``sys`` as a minimum.
-
-Since pytest-2.4 `condition booleans`_ are considered preferable
-because markers can then be freely imported between test modules.
-With strings you need to import not only the marker but all variables
-everything used by the marker, which violates encapsulation.
-
-The reason for specifying the condition as a string was that ``pytest`` can
-report a summary of skip conditions based purely on the condition string.
-With conditions as booleans you are required to specify a ``reason`` string.
-
-Note that string conditions will remain fully supported and you are free
-to use them if you have no need for cross-importing markers.
-
-The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
-or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
-dictionary which is constructed as follows:
-
-* the namespace is initialized by putting the ``sys`` and ``os`` modules
-  and the pytest ``config`` object into it.
-
-* updated with the module globals of the test function for which the
-  expression is applied.
-
-The pytest ``config`` object allows you to skip based on a test
-configuration value which you might have added::
-
-    @pytest.mark.skipif("not config.getvalue('db')")
-    def test_function(...):
-        ...
-
-The equivalent with "boolean conditions" is::
-
-    @pytest.mark.skipif(not pytest.config.getvalue("db"),
-                        reason="--db was not specified")
-    def test_function(...):
-        pass
-
-.. note::
-
-    You cannot use ``pytest.config.getvalue()`` in code
-    imported before py.test's argument parsing takes place.  For example,
-    ``conftest.py`` files are imported before command line parsing and thus
-    ``config.getvalue()`` will not execute correctly.
diff --git a/tools/pytest/doc/en/status.rst b/tools/pytest/doc/en/status.rst
deleted file mode 100644
index 3c7bf70..0000000
--- a/tools/pytest/doc/en/status.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-pytest development status
-================================
-
-https://travis-ci.org/pytest-dev/pytest
-
diff --git a/tools/pytest/doc/en/talks.rst b/tools/pytest/doc/en/talks.rst
deleted file mode 100644
index 7a52218..0000000
--- a/tools/pytest/doc/en/talks.rst
+++ /dev/null
@@ -1,116 +0,0 @@
-
-Talks and Tutorials
-==========================
-
-.. sidebar:: Next Open Trainings
-
-   `professional testing with pytest and tox <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, 27-29th June 2016, Freiburg, Germany
-
-.. _`funcargs`: funcargs.html
-
-Talks and blog postings
----------------------------------------------
-
-.. _`tutorial1 repository`: http://bitbucket.org/pytest-dev/pytest-tutorial1/
-.. _`pycon 2010 tutorial PDF`: http://bitbucket.org/pytest-dev/pytest-tutorial1/raw/tip/pytest-basic.pdf
-
-- `pytest - Rapid Simple Testing, Florian Bruhin, Swiss Python Summit 2016
-  <https://www.youtube.com/watch?v=rCBHkQ_LVIs>`_.
-
-- `Improve your testing with Pytest and Mock, Gabe Hollombe, PyCon SG 2015
-  <https://www.youtube.com/watch?v=RcN26hznmk4>`_.
-
-- `Introduction to pytest, Andreas Pelme, EuroPython 2014
-  <https://www.youtube.com/watch?v=LdVJj65ikRY>`_.
-
-- `Advanced Uses of py.test Fixtures, Floris Bruynooghe, EuroPython
-  2014 <https://www.youtube.com/watch?v=IBC_dxr-4ps>`_.
-
-- `Why i use py.test and maybe you should too, Andy Todd, Pycon AU 2013
-  <https://www.youtube.com/watch?v=P-AhpukDIik>`_
-
-- `3-part blog series about pytest from @pydanny alias Daniel Greenfeld (January
-  2014) <http://pydanny.com/pytest-no-boilerplate-testing.html>`_
-
-- `pytest: helps you write better Django apps, Andreas Pelme, DjangoCon
-  Europe 2014 <https://www.youtube.com/watch?v=aaArYVh6XSM>`_.
-
-- :ref:`fixtures`
-
-- `Testing Django Applications with pytest, Andreas Pelme, EuroPython
-  2013 <https://www.youtube.com/watch?v=aUf8Fkb7TaY>`_.
-
-- `Testes pythonics com py.test, Vinicius Belchior Assef Neto, Plone
-  Conf 2013, Brazil <https://www.youtube.com/watch?v=QUKoq2K7bis>`_.
-
-- `Introduction to py.test fixtures, FOSDEM 2013, Floris Bruynooghe
-  <https://www.youtube.com/watch?v=bJhRW4eZMco>`_.
-
-- `pytest feature and release highlights, Holger Krekel (GERMAN, October 2013)
-  <http://pyvideo.org/video/2429/pytest-feature-and-new-release-highlights>`_
-
-- `pytest introduction from Brian Okken (January 2013)
-  <http://pythontesting.net/framework/pytest-introduction/>`_
-
-- `monkey patching done right`_ (blog post, consult `monkeypatch
-  plugin`_ for up-to-date API)
-
-Test parametrization:
-
-- `generating parametrized tests with funcargs`_ (uses deprecated ``addcall()`` API.
-- `test generators and cached setup`_
-- `parametrizing tests, generalized`_ (blog post)
-- `putting test-hooks into local or global plugins`_ (blog post)
-
-Assertion introspection:
-
-- `(07/2011) Behind the scenes of pytest's new assertion rewriting
-  <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_
-
-Distributed testing:
-
-- `simultaneously test your code on all platforms`_ (blog entry)
-
-Plugin specific examples:
-
-- `skipping slow tests by default in pytest`_ (blog entry)
-
-- `many examples in the docs for plugins`_
-
-.. _`skipping slow tests by default in pytest`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
-.. _`many examples in the docs for plugins`: plugin/index.html
-.. _`monkeypatch plugin`: plugin/monkeypatch.html
-.. _`application setup in test functions with funcargs`: funcargs.html#appsetup
-.. _`simultaneously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
-.. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
-.. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/
-.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
-.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators
-.. _`test generators and cached setup`: http://bruynooghe.blogspot.com/2010/06/pytest-test-generators-and-cached-setup.html
-
-Older conference talks and tutorials
-----------------------------------------
-
-- `pycon australia 2012 pytest talk from Brianna Laugher
-  <http://2012.pycon-au.org/schedule/52/view_talk?day=sunday>`_ (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
-- `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_
-- `pycon 2010 tutorial PDF`_ and `tutorial1 repository`_
-
-- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009):
-
-  - testing terminology
-  - basic pytest usage, file system layout
-  - test function arguments (funcargs_) and test fixtures
-  - existing plugins
-  - distributed testing
-
-- `ep2009-pytest.pdf`_ 60 minute pytest talk, highlighting unique features and a roadmap (July 2009)
-
-- `pycon2009-pytest-introduction.zip`_ slides and files, extended version of pytest basic introduction, discusses more options, also introduces old-style xUnit setup, looponfailing and other features.
-
-- `pycon2009-pytest-advanced.pdf`_ contain a slightly older version of funcargs and distributed testing, compared to the EuroPython 2009 slides.
-
-.. _`ep2009-rapidtesting.pdf`: http://codespeak.net/download/py/ep2009-rapidtesting.pdf
-.. _`ep2009-pytest.pdf`: http://codespeak.net/download/py/ep2009-pytest.pdf
-.. _`pycon2009-pytest-introduction.zip`: http://codespeak.net/download/py/pycon2009-pytest-introduction.zip
-.. _`pycon2009-pytest-advanced.pdf`: http://codespeak.net/download/py/pycon2009-pytest-advanced.pdf
diff --git a/tools/pytest/doc/en/test/attic.rst b/tools/pytest/doc/en/test/attic.rst
deleted file mode 100644
index 6408c72..0000000
--- a/tools/pytest/doc/en/test/attic.rst
+++ /dev/null
@@ -1,117 +0,0 @@
-===============================================
-ATTIC documentation
-===============================================
-
-XXX REVIEW and remove the below  XXX
-
-Customizing the testing process
-===============================
-
-writing conftest.py files
------------------------------------
-
-You may put conftest.py files containing project-specific
-configuration in your project's root directory, it's usually
-best to put it just into the same directory level as your
-topmost ``__init__.py``.  In fact, ``pytest`` performs
-an "upwards" search starting from the directory that you specify
-to be tested and will lookup configuration values right-to-left.
-You may have options that reside e.g. in your home directory
-but note that project specific settings will be considered
-first.  There is a flag that helps you debugging your
-conftest.py configurations::
-
-    py.test --traceconfig
-
-
-customizing the collecting and running process
------------------------------------------------
-
-To introduce different test items you can create
-one or more ``conftest.py`` files in your project.
-When the collection process traverses directories
-and modules the default collectors will produce
-custom Collectors and Items if they are found
-in a local ``conftest.py`` file.
-
-
-Customizing the collection process in a module
-----------------------------------------------
-
-If you have a module where you want to take responsibility for
-collecting your own test Items and possibly even for executing
-a test then you can provide `generative tests`_ that yield
-callables and possibly arguments as a tuple.   This is especially
-useful for calling application test machinery with different
-parameter sets but counting each of the calls as a separate
-tests.
-
-.. _`generative tests`: features.html#generative-tests
-
-The other extension possibility is about
-specifying a custom test ``Item`` class which
-is responsible for setting up and executing an underlying
-test.  Or you can extend the collection process for a whole
-directory tree by putting Items in a ``conftest.py`` configuration file.
-The collection process dynamically consults the *chain of conftest.py*
-modules to determine collectors and items at ``Directory``, ``Module``,
-``Class``, ``Function`` or ``Generator`` level respectively.
-
-Customizing execution of Items and Functions
-----------------------------------------------------
-
-- ``pytest.Function`` test items control execution
-  of a test function through its ``function.runtest()`` method.
-  This method is responsible for performing setup and teardown
-  ("Test Fixtures") for a test Function.
-
-- ``Function.execute(target, *args)`` methods are invoked by
-  the default ``Function.run()`` to actually execute a python
-  function with the given (usually empty set of) arguments.
-
-.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev
-
-
-.. _`test generators`: funcargs.html#test-generators
-
-.. _`generative tests`:
-
-generative tests: yielding parametrized tests
-====================================================
-
-Deprecated since 1.0 in favour of `test generators`_.
-
-*Generative tests* are test methods that are *generator functions* which
-``yield`` callables and their arguments.  This is useful for running a
-test function multiple times against different parameters.  Example::
-
-    def test_generative():
-        for x in (42,17,49):
-            yield check, x
-
-    def check(arg):
-        assert arg % 7 == 0   # second generated tests fails!
-
-Note that ``test_generative()`` will cause three tests
-to get run, notably ``check(42)``, ``check(17)`` and ``check(49)``
-of which the middle one will obviously fail.
-
-To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example::
-
-    def test_generative():
-        for x in (42,17,49):
-            yield "case %d" % x, check, x
-
-
-disabling a test class
-----------------------
-
-If you want to disable a complete test class you
-can set the class-level attribute ``disabled``.
-For example, in order to avoid running some tests on Win32::
-
-    class TestPosixOnly:
-        disabled = sys.platform == 'win32'
-
-        def test_xxx(self):
-            ...
diff --git a/tools/pytest/doc/en/test/mission.rst b/tools/pytest/doc/en/test/mission.rst
deleted file mode 100644
index cda8d9a..0000000
--- a/tools/pytest/doc/en/test/mission.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-
-Mission
-====================================
-
-``pytest`` strives to make testing a fun and no-boilerplate effort.
-
-The tool is distributed as a `pytest` package.  Its project independent
-``py.test`` command line tool helps you to:
-
-* rapidly collect and run tests
-* run unit- or doctests, functional or integration tests
-* distribute tests to multiple environments
-* use local or global plugins for custom test types and setup
diff --git a/tools/pytest/doc/en/test/plugin/cov.rst b/tools/pytest/doc/en/test/plugin/cov.rst
deleted file mode 100644
index 355093f..0000000
--- a/tools/pytest/doc/en/test/plugin/cov.rst
+++ /dev/null
@@ -1,230 +0,0 @@
-
-produce code coverage reports using the 'coverage' package, including support for distributed testing.
-======================================================================================================
-
-
-.. contents::
-  :local:
-
-This plugin produces coverage reports.  It supports centralised testing and distributed testing in
-both load and each modes.  It also supports coverage of subprocesses.
-
-All features offered by the coverage package should be available, either through pytest-cov or
-through coverage's config file.
-
-
-Installation
-------------
-
-The `pytest-cov`_ package may be installed with pip or easy_install::
-
-    pip install pytest-cov
-    easy_install pytest-cov
-
-.. _`pytest-cov`: http://pypi.python.org/pypi/pytest-cov/
-
-
-Uninstallation
---------------
-
-Uninstalling packages is supported by pip::
-
-    pip uninstall pytest-cov
-
-However easy_install does not provide an uninstall facility.
-
-.. IMPORTANT::
-
-    Ensure that you manually delete the init_covmain.pth file in your
-    site-packages directory.
-
-    This file starts coverage collection of subprocesses if appropriate during
-    site initialization at python startup.
-
-
-Usage
------
-
-Centralised Testing
-~~~~~~~~~~~~~~~~~~~
-
-Centralised testing will report on the combined coverage of the main process and all of it's
-subprocesses.
-
-Running centralised testing::
-
-    py.test --cov myproj tests/
-
-Shows a terminal report::
-
-    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
-    Name                 Stmts   Miss  Cover
-    ----------------------------------------
-    myproj/__init__          2      0   100%
-    myproj/myproj          257     13    94%
-    myproj/feature4286      94      7    92%
-    ----------------------------------------
-    TOTAL                  353     20    94%
-
-
-Distributed Testing: Load
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Distributed testing with dist mode set to load will report on the combined coverage of all slaves.
-The slaves may be spread out over any number of hosts and each slave may be located anywhere on the
-file system.  Each slave will have it's subprocesses measured.
-
-Running distributed testing with dist mode set to load::
-
-    py.test --cov myproj -n 2 tests/
-
-Shows a terminal report::
-
-    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
-    Name                 Stmts   Miss  Cover
-    ----------------------------------------
-    myproj/__init__          2      0   100%
-    myproj/myproj          257     13    94%
-    myproj/feature4286      94      7    92%
-    ----------------------------------------
-    TOTAL                  353     20    94%
-
-
-Again but spread over different hosts and different directories::
-
-    py.test --cov myproj --dist load
-            --tx ssh=memedough@host1//chdir=testenv1
-            --tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python
-            --rsyncdir myproj --rsyncdir tests --rsync examples
-            tests/
-
-Shows a terminal report::
-
-    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
-    Name                 Stmts   Miss  Cover
-    ----------------------------------------
-    myproj/__init__          2      0   100%
-    myproj/myproj          257     13    94%
-    myproj/feature4286      94      7    92%
-    ----------------------------------------
-    TOTAL                  353     20    94%
-
-
-Distributed Testing: Each
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Distributed testing with dist mode set to each will report on the combined coverage of all slaves.
-Since each slave is running all tests this allows generating a combined coverage report for multiple
-environments.
-
-Running distributed testing with dist mode set to each::
-
-    py.test --cov myproj --dist each
-            --tx popen//chdir=/tmp/testenv3//python=/usr/local/python27/bin/python
-            --tx ssh=memedough@host2//chdir=/tmp/testenv4//python=/tmp/env2/bin/python
-            --rsyncdir myproj --rsyncdir tests --rsync examples
-            tests/
-
-Shows a terminal report::
-
-    ---------------------------------------- coverage ----------------------------------------
-                              platform linux2, python 2.6.5-final-0
-                              platform linux2, python 2.7.0-final-0
-    Name                 Stmts   Miss  Cover
-    ----------------------------------------
-    myproj/__init__          2      0   100%
-    myproj/myproj          257     13    94%
-    myproj/feature4286      94      7    92%
-    ----------------------------------------
-    TOTAL                  353     20    94%
-
-
-Reporting
----------
-
-It is possible to generate any combination of the reports for a single test run.
-
-The available reports are terminal (with or without missing line numbers shown), HTML, XML and
-annotated source code.
-
-The terminal report without line numbers (default)::
-
-    py.test --cov-report term --cov myproj tests/
-
-    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
-    Name                 Stmts   Miss  Cover
-    ----------------------------------------
-    myproj/__init__          2      0   100%
-    myproj/myproj          257     13    94%
-    myproj/feature4286      94      7    92%
-    ----------------------------------------
-    TOTAL                  353     20    94%
-
-
-The terminal report with line numbers::
-
-    py.test --cov-report term-missing --cov myproj tests/
-
-    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
-    Name                 Stmts   Miss  Cover   Missing
-    --------------------------------------------------
-    myproj/__init__          2      0   100%
-    myproj/myproj          257     13    94%   24-26, 99, 149, 233-236, 297-298, 369-370
-    myproj/feature4286      94      7    92%   183-188, 197
-    --------------------------------------------------
-    TOTAL                  353     20    94%
-
-
-The remaining three reports output to files without showing anything on the terminal (useful for
-when the output is going to a continuous integration server)::
-
-    py.test --cov-report html --cov-report xml --cov-report annotate --cov myproj tests/
-
-
-Coverage Data File
-------------------
-
-The data file is erased at the beginning of testing to ensure clean data for each test run.
-
-The data file is left at the end of testing so that it is possible to use normal coverage tools to
-examine it.
-
-
-Limitations
------------
-
-For distributed testing the slaves must have the pytest-cov package installed.  This is needed since
-the plugin must be registered through setuptools / distribute for pytest to start the plugin on the
-slave.
-
-For subprocess measurement environment variables must make it from the main process to the
-subprocess.  The python used by the subprocess must have pytest-cov installed.  The subprocess must
-do normal site initialization so that the environment variables can be detected and coverage
-started.
-
-
-Acknowledgments
-----------------
-
-Holger Krekel for pytest with its distributed testing support.
-
-Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs.
-
-Whilst this plugin has been built fresh from the ground up to support distributed testing it has
-been influenced by the work done on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and
-nose-cover (Jason Pellerin) which are other coverage plugins for pytest and nose respectively.
-
-No doubt others have contributed to these tools as well.
-
-command line options
---------------------
-
-
-``--cov=path``
-    measure coverage for filesystem path (multi-allowed)
-``--cov-report=type``
-    type of report to generate: term, term-missing, annotate, html, xml (multi-allowed)
-``--cov-config=path``
-    config file for coverage, default: .coveragerc
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/coverage.rst b/tools/pytest/doc/en/test/plugin/coverage.rst
deleted file mode 100644
index 965b4a4e..0000000
--- a/tools/pytest/doc/en/test/plugin/coverage.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-
-Write and report coverage data with the 'coverage' package.
-===========================================================
-
-
-.. contents::
-  :local:
-
-Note: Original code by Ross Lawley. 
-
-Install
---------------
-
-Use pip to (un)install::
-
-    pip install pytest-coverage 
-    pip uninstall pytest-coverage 
-
-or alternatively use easy_install to install::
-
-    easy_install pytest-coverage 
-
-
-Usage 
--------------
-
-To get full test coverage reports for a particular package type::
-
-    py.test --cover-report=report
-
-command line options
---------------------
-
-
-``--cover=COVERPACKAGES``
-    (multi allowed) only include info from specified package.
-``--cover-report=REPORT_TYPE``
-    html: Directory for html output.
-                    report: Output a text report.
-                    annotate: Annotate your source code for which lines were executed and which were not.
-                    xml: Output an xml report compatible with the cobertura plugin for hudson.
-``--cover-directory=DIRECTORY``
-    Directory for the reports (html / annotate results) defaults to ./coverage
-``--cover-xml-file=XML_FILE``
-    File for the xml report defaults to ./coverage.xml
-``--cover-show-missing``
-    Show missing files
-``--cover-ignore-errors=IGNORE_ERRORS``
-    Ignore errors of finding source files for code.
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/figleaf.rst b/tools/pytest/doc/en/test/plugin/figleaf.rst
deleted file mode 100644
index 86e0da6..0000000
--- a/tools/pytest/doc/en/test/plugin/figleaf.rst
+++ /dev/null
@@ -1,44 +0,0 @@
-
-report test coverage using the 'figleaf' package.
-=================================================
-
-
-.. contents::
-  :local:
-
-Install
----------------
-
-To install the plugin issue::
-
-    easy_install pytest-figleaf  # or
-    pip install pytest-figleaf   
-
-and if you are using pip you can also uninstall::
-
-    pip uninstall pytest-figleaf
-
-
-Usage
----------------
-
-After installation you can simply type::
-
-    py.test --figleaf [...]
-
-to enable figleaf coverage in your test run.  A default ".figleaf" data file
-and "html" directory will be created.  You can use command line options
-to control where data and html files are created.
-
-command line options
---------------------
-
-
-``--figleaf``
-    trace python coverage with figleaf and write HTML for files below the current working dir
-``--fig-data=dir``
-    set tracing file, default: ".figleaf".
-``--fig-html=dir``
-    set html reporting dir, default "html".
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/genscript.rst b/tools/pytest/doc/en/test/plugin/genscript.rst
deleted file mode 100644
index ee80f23..0000000
--- a/tools/pytest/doc/en/test/plugin/genscript.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-
-(deprecated) generate standalone test script to be distributed along with an application.
-============================================================================
-
-
-.. contents::
-  :local:
-
-
-
-command line options
---------------------
-
-
-``--genscript=path``
-    create standalone ``pytest`` script at given target path.
-
-Start improving this plugin in 30 seconds
-=========================================
-
-
-1. Download `pytest_genscript.py`_ plugin source code
-2. put it somewhere as ``pytest_genscript.py`` into your import path
-3. a subsequent ``pytest`` run will use your local version
-
-Checkout customize_, other plugins_ or `get in contact`_.
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/helpconfig.rst b/tools/pytest/doc/en/test/plugin/helpconfig.rst
deleted file mode 100644
index 9b5b8cd..0000000
--- a/tools/pytest/doc/en/test/plugin/helpconfig.rst
+++ /dev/null
@@ -1,38 +0,0 @@
-
-provide version info, conftest/environment config names.
-========================================================
-
-
-.. contents::
-  :local:
-
-
-
-command line options
---------------------
-
-
-``--version``
-    display py lib version and import information.
-``-p name``
-    early-load given plugin (multi-allowed).
-``--traceconfig``
-    trace considerations of conftest.py files.
-``--nomagic``
-    don't reinterpret asserts, no traceback cutting.
-``--debug``
-    generate and show internal debugging information.
-``--help-config``
-    show available conftest.py and ENV-variable names.
-
-Start improving this plugin in 30 seconds
-=========================================
-
-
-1. Download `pytest_helpconfig.py`_ plugin source code
-2. put it somewhere as ``pytest_helpconfig.py`` into your import path
-3. a subsequent ``pytest`` run will use your local version
-
-Checkout customize_, other plugins_ or `get in contact`_.
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/links.rst b/tools/pytest/doc/en/test/plugin/links.rst
deleted file mode 100644
index aa965e7..0000000
--- a/tools/pytest/doc/en/test/plugin/links.rst
+++ /dev/null
@@ -1,47 +0,0 @@
-.. _`helpconfig`: helpconfig.html
-.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_recwarn.py
-.. _`unittest`: unittest.html
-.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_monkeypatch.py
-.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_genscript.py
-.. _`pastebin`: pastebin.html
-.. _`skipping`: skipping.html
-.. _`genscript`: genscript.html
-.. _`plugins`: index.html
-.. _`mark`: mark.html
-.. _`tmpdir`: tmpdir.html
-.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_doctest.py
-.. _`capture`: capture.html
-.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_nose.py
-.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_restdoc.py
-.. _`restdoc`: restdoc.html
-.. _`xdist`: xdist.html
-.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_pastebin.py
-.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_tmpdir.py
-.. _`terminal`: terminal.html
-.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_hooklog.py
-.. _`capturelog`: capturelog.html
-.. _`junitxml`: junitxml.html
-.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_skipping.py
-.. _`checkout the pytest development version`: ../../install.html#checkout
-.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_helpconfig.py
-.. _`oejskit`: oejskit.html
-.. _`doctest`: doctest.html
-.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_mark.py
-.. _`get in contact`: ../../contact.html
-.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_capture.py
-.. _`figleaf`: figleaf.html
-.. _`customize`: ../customize.html
-.. _`hooklog`: hooklog.html
-.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_terminal.py
-.. _`recwarn`: recwarn.html
-.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_pdb.py
-.. _`monkeypatch`: monkeypatch.html
-.. _`coverage`: coverage.html
-.. _`resultlog`: resultlog.html
-.. _`cov`: cov.html
-.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_junitxml.py
-.. _`django`: django.html
-.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_unittest.py
-.. _`nose`: nose.html
-.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_resultlog.py
-.. _`pdb`: pdb.html
diff --git a/tools/pytest/doc/en/test/plugin/nose.rst b/tools/pytest/doc/en/test/plugin/nose.rst
deleted file mode 100644
index f3aa7d7..0000000
--- a/tools/pytest/doc/en/test/plugin/nose.rst
+++ /dev/null
@@ -1,56 +0,0 @@
-
-nose-compatibility plugin: allow to run nose test suites natively.
-==================================================================
-
-
-.. contents::
-  :local:
-
-This is an experimental plugin for allowing to run tests written
-in 'nosetests' style with ``pytest``.
-
-Usage
--------------
-
-type::
-
-    py.test  # instead of 'nosetests'
-
-and you should be able to run nose style tests and at the same
-time can make full use of pytest's capabilities.
-
-Supported nose Idioms
-----------------------
-
-* setup and teardown at module/class/method level
-* SkipTest exceptions and markers
-* setup/teardown decorators
-* yield-based tests and their setup
-* general usage of nose utilities
-
-Unsupported idioms / issues
-----------------------------------
-
-- nose-style doctests are not collected and executed correctly,
-  also fixtures don't work.
-
-- no nose-configuration is recognized
-
-If you find other issues or have suggestions please run::
-
-    py.test --pastebin=all
-
-and send the resulting URL to a ``pytest`` contact channel,
-at best to the mailing list.
-
-Start improving this plugin in 30 seconds
-=========================================
-
-
-1. Download `pytest_nose.py`_ plugin source code
-2. put it somewhere as ``pytest_nose.py`` into your import path
-3. a subsequent ``pytest`` run will use your local version
-
-Checkout customize_, other plugins_ or `get in contact`_.
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/terminal.rst b/tools/pytest/doc/en/test/plugin/terminal.rst
deleted file mode 100644
index 214c24d..0000000
--- a/tools/pytest/doc/en/test/plugin/terminal.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-
-Implements terminal reporting of the full testing process.
-==========================================================
-
-
-.. contents::
-  :local:
-
-This is a good source for looking at the various reporting hooks.
-
-command line options
---------------------
-
-
-``-v, --verbose``
-    increase verbosity.
-``-r chars``
-    show extra test summary info as specified by chars (f)ailed, (s)skipped, (x)failed, (X)passed.
-``-l, --showlocals``
-    show locals in tracebacks (disabled by default).
-``--report=opts``
-    (deprecated, use -r)
-``--tb=style``
-    traceback print mode (long/short/line/no).
-``--fulltrace``
-    don't cut any tracebacks (default is to cut).
-``--fixtures``
-    show available function arguments, sorted by plugin
-
-Start improving this plugin in 30 seconds
-=========================================
-
-
-1. Download `pytest_terminal.py`_ plugin source code
-2. put it somewhere as ``pytest_terminal.py`` into your import path
-3. a subsequent ``pytest`` run will use your local version
-
-Checkout customize_, other plugins_ or `get in contact`_.
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/xdist.rst b/tools/pytest/doc/en/test/plugin/xdist.rst
deleted file mode 100644
index 7ab6cdc..0000000
--- a/tools/pytest/doc/en/test/plugin/xdist.rst
+++ /dev/null
@@ -1,172 +0,0 @@
-
-loop on failing tests, distribute test runs to CPUs and hosts.
-==============================================================
-
-
-.. contents::
-  :local:
-
-The `pytest-xdist`_ plugin extends ``pytest`` with some unique
-test execution modes:
-
-* Looponfail: run your tests repeatedly in a subprocess.  After each run
-  ``pytest`` waits until a file in your project changes and then re-runs the
-  previously failing tests.  This is repeated until all tests pass after which
-  again a full run is performed.
-
-* Load-balancing: if you have multiple CPUs or hosts you can use
-  those for a combined test run.  This allows to speed up
-  development or to use special resources of remote machines.
-
-* Multi-Platform coverage: you can specify different Python interpreters
-  or different platforms and run tests in parallel on all of them.
-
-Before running tests remotely, ``pytest`` efficiently synchronizes your
-program source code to the remote place.  All test results
-are reported back and displayed to your local test session.
-You may specify different Python versions and interpreters.
-
-.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist
-
-Usage examples
----------------------
-
-Speed up test runs by sending tests to multiple CPUs
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-To send tests to multiple CPUs, type::
-
-    py.test -n NUM
-
-Especially for longer running tests or tests requiring
-a lot of IO this can lead to considerable speed ups.
-
-
-Running tests in a Python subprocess
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-To instantiate a python2.4 sub process and send tests to it, you may type::
-
-    py.test -d --tx popen//python=python2.4
-
-This will start a subprocess which is run with the "python2.4"
-Python interpreter, found in your system binary lookup path.
-
-If you prefix the --tx option value like this::
-
-    --tx 3*popen//python=python2.4
-
-then three subprocesses would be created and tests
-will be load-balanced across these three processes.
-
-
-Sending tests to remote SSH accounts
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-Suppose you have a package ``mypkg`` which contains some
-tests that you can successfully run locally. And you
-have a ssh-reachable machine ``myhost``.  Then
-you can ad-hoc distribute your tests by typing::
-
-    py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg
-
-This will synchronize your ``mypkg`` package directory
-to an remote ssh account and then locally collect tests
-and send them to remote places for execution.
-
-You can specify multiple ``--rsyncdir`` directories
-to be sent to the remote side.
-
-**NOTE:** For ``pytest`` to collect and send tests correctly
-you not only need to make sure all code and tests
-directories are rsynced, but that any test (sub) directory
-also has an ``__init__.py`` file because internally
-``pytest`` references tests using their fully qualified python
-module path.  **You will otherwise get strange errors**
-during setup of the remote side.
-
-Sending tests to remote Socket Servers
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-Download the single-module `socketserver.py`_ Python program
-and run it like this::
-
-    python socketserver.py
-
-It will tell you that it starts listening on the default
-port.  You can now on your home machine specify this
-new socket host with something like this::
-
-    py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
-
-
-.. _`atonce`:
-
-Running tests on many platforms at once
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-The basic command to run tests on multiple platforms is::
-
-    py.test --dist=each --tx=spec1 --tx=spec2
-
-If you specify a windows host, an OSX host and a Linux
-environment this command will send each tests to all
-platforms - and report back failures from all platforms
-at once.   The specifications strings use the `xspec syntax`_.
-
-.. _`xspec syntax`: http://codespeak.net/execnet/trunk/basics.html#xspec
-
-.. _`socketserver.py`: http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py
-
-.. _`execnet`: http://codespeak.net/execnet
-
-Specifying test exec environments in a conftest.py
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-Instead of specifying command line options, you can
-put options values in a ``conftest.py`` file like this::
-
-    option_tx = ['ssh=myhost//python=python2.7', 'popen//python=python2.7']
-    option_dist = True
-
-Any commandline ``--tx`` specifications  will add to the list of
-available execution environments.
-
-Specifying "rsync" dirs in a conftest.py
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-In your ``mypkg/conftest.py`` you may specify directories to synchronise
-or to exclude::
-
-    rsyncdirs = ['.', '../plugins']
-    rsyncignore = ['_cache']
-
-These directory specifications are relative to the directory
-where the ``conftest.py`` is found.
-
-command line options
---------------------
-
-
-``-f, --looponfail``
-    run tests in subprocess, wait for modified files and re-run failing test set until all pass.
-``-n numprocesses``
-    shortcut for '--dist=load --tx=NUM*popen'
-``--boxed``
-    box each test run in a separate process (unix)
-``--dist=distmode``
-    set mode for distributing tests to exec environments.
-
-    each: send each test to each available environment.
-
-    load: send each test to one available environment so it is run only once.
-
-    (default) no: run tests inprocess, don't distribute.
-``--tx=xspec``
-    add a test execution environment. some examples: --tx popen//python=python2.7 --tx socket=192.168.1.102:8888 --tx ssh=user@codespeak.net//chdir=testcache
-``-d``
-    load-balance tests.  shortcut for '--dist=load'
-``--rsyncdir=dir1``
-    add directory for rsyncing to remote tx nodes.
-
-.. include:: links.txt
diff --git a/tools/pytest/doc/en/tmpdir.rst b/tools/pytest/doc/en/tmpdir.rst
deleted file mode 100644
index f8935b8..0000000
--- a/tools/pytest/doc/en/tmpdir.rst
+++ /dev/null
@@ -1,111 +0,0 @@
-
-.. _`tmpdir handling`:
-.. _tmpdir:
-
-Temporary directories and files
-================================================
-
-The 'tmpdir' fixture
---------------------
-
-You can use the ``tmpdir`` fixture which will
-provide a temporary directory unique to the test invocation,
-created in the `base temporary directory`_.
-
-``tmpdir`` is a `py.path.local`_ object which offers ``os.path`` methods
-and more.  Here is an example test usage::
-
-    # content of test_tmpdir.py
-    import os
-    def test_create_file(tmpdir):
-        p = tmpdir.mkdir("sub").join("hello.txt")
-        p.write("content")
-        assert p.read() == "content"
-        assert len(tmpdir.listdir()) == 1
-        assert 0
-
-Running this would result in a passed test except for the last
-``assert 0`` line which we use to look at values::
-
-    $ py.test test_tmpdir.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 1 items
-    
-    test_tmpdir.py F
-    
-    ======= FAILURES ========
-    _______ test_create_file ________
-    
-    tmpdir = local('PYTEST_TMPDIR/test_create_file0')
-    
-        def test_create_file(tmpdir):
-            p = tmpdir.mkdir("sub").join("hello.txt")
-            p.write("content")
-            assert p.read() == "content"
-            assert len(tmpdir.listdir()) == 1
-    >       assert 0
-    E       assert 0
-    
-    test_tmpdir.py:7: AssertionError
-    ======= 1 failed in 0.12 seconds ========
-
-The 'tmpdir_factory' fixture
-----------------------------
-
-.. versionadded:: 2.8
-
-The ``tmpdir_factory`` is a session-scoped fixture which can be used
-to create arbitrary temporary directories from any other fixture or test.
-
-For example, suppose your test suite needs a large image on disk, which is
-generated procedurally. Instead of computing the same image for each test
-that uses it into its own ``tmpdir``, you can generate it once per-session
-to save time:
-
-.. code-block:: python
-
-    # contents of conftest.py
-    import pytest
-
-    @pytest.fixture(scope='session')
-    def image_file(tmpdir_factory):
-        img = compute_expensive_image()
-        fn = tmpdir_factory.mktemp('data').join('img.png')
-        img.save(str(fn))
-        return fn
-
-    # contents of test_image.py
-    def test_histogram(image_file):
-        img = load_image(image_file)
-        # compute and test histogram
-
-``tmpdir_factory`` instances have the following methods:
-
-.. currentmodule:: _pytest.tmpdir
-
-.. automethod:: TempdirFactory.mktemp
-.. automethod:: TempdirFactory.getbasetemp
-
-.. _`base temporary directory`:
-
-The default base temporary directory
------------------------------------------------
-
-Temporary directories are by default created as sub-directories of
-the system temporary directory.  The base name will be ``pytest-NUM`` where
-``NUM`` will be incremented with each test run.  Moreover, entries older
-than 3 temporary directories will be removed.
-
-You can override the default temporary directory setting like this::
-
-    py.test --basetemp=mydir
-
-When distributing tests on the local machine, ``pytest`` takes care to
-configure a basetemp directory for the sub processes such that all temporary
-data lands below a single per-test run basetemp directory.
-
-.. _`py.path.local`: http://py.rtfd.org/en/latest/path.html
-
-
diff --git a/tools/pytest/doc/en/unittest.rst b/tools/pytest/doc/en/unittest.rst
deleted file mode 100644
index ce99bd1..0000000
--- a/tools/pytest/doc/en/unittest.rst
+++ /dev/null
@@ -1,190 +0,0 @@
-
-.. _`unittest.TestCase`:
-
-Support for unittest.TestCase / Integration of fixtures
-=====================================================================
-
-.. _`unittest.py style`: http://docs.python.org/library/unittest.html
-
-``pytest`` has support for running Python `unittest.py style`_ tests.
-It's meant for leveraging existing unittest-style projects
-to use pytest features.  Concretely, pytest will automatically 
-collect ``unittest.TestCase`` subclasses and their ``test`` methods in
-test files.  It will invoke typical setup/teardown methods and 
-generally try to make test suites written to run on unittest, to also 
-run using ``pytest``.  We assume here that you are familiar with writing
-``unittest.TestCase`` style tests and rather focus on 
-integration aspects.
-
-Usage
--------------------------------------------------------------------
-
-After :ref:`installation` type::
-
-    py.test 
-
-and you should be able to run your unittest-style tests if they
-are contained in ``test_*`` modules.  If that works for you then
-you can make use of most :ref:`pytest features <features>`, for example
-``--pdb`` debugging in failures, using :ref:`plain assert-statements <assert>`,
-:ref:`more informative tracebacks <tbreportdemo>`, stdout-capturing or 
-distributing tests to multiple CPUs via the ``-nNUM`` option if you 
-installed the ``pytest-xdist`` plugin.  Please refer to
-the general ``pytest`` documentation for many more examples.
-
-Mixing pytest fixtures into unittest.TestCase style tests
------------------------------------------------------------
-
-Running your unittest with ``pytest`` allows you to use its
-:ref:`fixture mechanism <fixture>` with ``unittest.TestCase`` style
-tests.  Assuming you have at least skimmed the pytest fixture features,
-let's jump-start into an example that integrates a pytest ``db_class``
-fixture, setting up a class-cached database object, and then reference
-it from a unittest-style test::
-
-    # content of conftest.py
-
-    # we define a fixture function below and it will be "used" by
-    # referencing its name from tests
-
-    import pytest
-
-    @pytest.fixture(scope="class")
-    def db_class(request):
-        class DummyDB:
-            pass
-        # set a class attribute on the invoking test context 
-        request.cls.db = DummyDB()
-
-This defines a fixture function ``db_class`` which - if used - is 
-called once for each test class and which sets the class-level 
-``db`` attribute to a ``DummyDB`` instance.  The fixture function
-achieves this by receiving a special ``request`` object which gives
-access to :ref:`the requesting test context <request-context>` such
-as the ``cls`` attribute, denoting the class from which the fixture 
-is used.  This architecture de-couples fixture writing from actual test
-code and allows re-use of the fixture by a minimal reference, the fixture
-name.  So let's write an actual ``unittest.TestCase`` class using our 
-fixture definition::
-
-    # content of test_unittest_db.py
-
-    import unittest
-    import pytest
-
-    @pytest.mark.usefixtures("db_class")
-    class MyTest(unittest.TestCase):
-        def test_method1(self):
-            assert hasattr(self, "db")
-            assert 0, self.db   # fail for demo purposes
-
-        def test_method2(self):
-            assert 0, self.db   # fail for demo purposes
-
-The ``@pytest.mark.usefixtures("db_class")`` class-decorator makes sure that 
-the pytest fixture function ``db_class`` is called once per class.
-Due to the deliberately failing assert statements, we can take a look at
-the ``self.db`` values in the traceback::
-
-    $ py.test test_unittest_db.py
-    ======= test session starts ========
-    platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
-    rootdir: $REGENDOC_TMPDIR, inifile: 
-    collected 2 items
-    
-    test_unittest_db.py FF
-    
-    ======= FAILURES ========
-    _______ MyTest.test_method1 ________
-    
-    self = <test_unittest_db.MyTest testMethod=test_method1>
-    
-        def test_method1(self):
-            assert hasattr(self, "db")
-    >       assert 0, self.db   # fail for demo purposes
-    E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef>
-    E       assert 0
-    
-    test_unittest_db.py:9: AssertionError
-    _______ MyTest.test_method2 ________
-    
-    self = <test_unittest_db.MyTest testMethod=test_method2>
-    
-        def test_method2(self):
-    >       assert 0, self.db   # fail for demo purposes
-    E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef>
-    E       assert 0
-    
-    test_unittest_db.py:12: AssertionError
-    ======= 2 failed in 0.12 seconds ========
-
-This default pytest traceback shows that the two test methods
-share the same ``self.db`` instance which was our intention
-when writing the class-scoped fixture function above.
-
-
-autouse fixtures and accessing other fixtures
--------------------------------------------------------------------
-
-Although it's usually better to explicitly declare use of fixtures you need
-for a given test, you may sometimes want to have fixtures that are 
-automatically used in a given context.  After all, the traditional 
-style of unittest-setup mandates the use of this implicit fixture writing
-and chances are, you are used to it or like it.  
-
-You can flag fixture functions with ``@pytest.fixture(autouse=True)``
-and define the fixture function in the context where you want it used.
-Let's look at an ``initdir`` fixture which makes all test methods of a
-``TestCase`` class execute in a temporary directory with a
-pre-initialized ``samplefile.ini``.  Our ``initdir`` fixture itself uses
-the pytest builtin :ref:`tmpdir <tmpdir>` fixture to delegate the
-creation of a per-test temporary directory::
-
-    # content of test_unittest_cleandir.py
-    import pytest
-    import unittest
-
-    class MyTest(unittest.TestCase):
-        @pytest.fixture(autouse=True)
-        def initdir(self, tmpdir):
-            tmpdir.chdir() # change to pytest-provided temporary directory
-            tmpdir.join("samplefile.ini").write("# testdata")
-
-        def test_method(self):
-            s = open("samplefile.ini").read() 
-            assert "testdata" in s
-
-Due to the ``autouse`` flag the ``initdir`` fixture function will be
-used for all methods of the class where it is defined.  This is a
-shortcut for using a ``@pytest.mark.usefixtures("initdir")`` marker
-on the class like in the previous example.
-
-Running this test module ...::
-
-    $ py.test -q test_unittest_cleandir.py
-    .
-    1 passed in 0.12 seconds
-
-... gives us one passed test because the ``initdir`` fixture function
-was executed ahead of the ``test_method``.
-
-.. note::
-
-   While pytest supports receiving fixtures via :ref:`test function arguments <funcargs>` for non-unittest test methods, ``unittest.TestCase`` methods cannot directly receive fixture 
-   function arguments as implementing that is likely to inflict
-   on the ability to run general unittest.TestCase test suites.
-   Maybe optional support would be possible, though.  If unittest finally 
-   grows a plugin system that should help as well.  In the meanwhile, the 
-   above ``usefixtures`` and ``autouse`` examples should help to mix in 
-   pytest fixtures into unittest suites.  And of course you can also start
-   to selectively leave away the ``unittest.TestCase`` subclassing, use
-   plain asserts and get the unlimited pytest feature set.
-
-
-Converting from unittest to pytest
----------------------------------------
-
-If you want to convert your unittest testcases to pytest, there are
-some helpers like `unittest2pytest
-<https://pypi.python.org/pypi/unittest2pytest/>`__, which uses lib2to3
-and introspection for the transformation.
diff --git a/tools/pytest/doc/en/usage.rst b/tools/pytest/doc/en/usage.rst
deleted file mode 100644
index 4b92fd1..0000000
--- a/tools/pytest/doc/en/usage.rst
+++ /dev/null
@@ -1,275 +0,0 @@
-
-.. _usage:
-
-Usage and Invocations
-==========================================
-
-
-.. _cmdline:
-
-Calling pytest through ``python -m pytest``
------------------------------------------------------
-
-.. versionadded:: 2.0
-
-You can invoke testing through the Python interpreter from the command line::
-
-    python -m pytest [...]
-
-This is equivalent to invoking the command line script ``py.test [...]``
-directly.
-
-Getting help on version, option names, environment variables
---------------------------------------------------------------
-
-::
-
-    py.test --version   # shows where pytest was imported from
-    py.test --fixtures  # show available builtin function arguments
-    py.test -h | --help # show help on command line and config file options
-
-
-Stopping after the first (or N) failures
----------------------------------------------------
-
-To stop the testing process after the first (N) failures::
-
-    py.test -x            # stop after first failure
-    py.test --maxfail=2    # stop after two failures
-
-Specifying tests / selecting tests
----------------------------------------------------
-
-Several test run options::
-
-    py.test test_mod.py   # run tests in module
-    py.test somepath      # run all tests below somepath
-    py.test -k stringexpr # only run tests with names that match the
-                          # "string expression", e.g. "MyClass and not method"
-                          # will select TestMyClass.test_something
-                          # but not TestMyClass.test_method_simple
-    py.test test_mod.py::test_func  # only run tests that match the "node ID",
-                                    # e.g "test_mod.py::test_func" will select
-                                    # only test_func in test_mod.py
-    py.test test_mod.py::TestClass::test_method  # run a single method in
-                                                 # a single class
-
-Import 'pkg' and use its filesystem location to find and run tests::
-
-    py.test --pyargs pkg # run all tests found below directory of pypkg
-
-Modifying Python traceback printing
-----------------------------------------------
-
-Examples for modifying traceback printing::
-
-    py.test --showlocals # show local variables in tracebacks
-    py.test -l           # show local variables (shortcut)
-
-    py.test --tb=auto    # (default) 'long' tracebacks for the first and last
-                         # entry, but 'short' style for the other entries
-    py.test --tb=long    # exhaustive, informative traceback formatting
-    py.test --tb=short   # shorter traceback format
-    py.test --tb=line    # only one line per failure
-    py.test --tb=native  # Python standard library formatting
-    py.test --tb=no      # no traceback at all
-
-Dropping to PDB_ (Python Debugger) on failures
------------------------------------------------
-
-.. _PDB: http://docs.python.org/library/pdb.html
-
-Python comes with a builtin Python debugger called PDB_.  ``pytest``
-allows one to drop into the PDB_ prompt via a command line option::
-
-    py.test --pdb
-
-This will invoke the Python debugger on every failure.  Often you might
-only want to do this for the first failing test to understand a certain
-failure situation::
-
-    py.test -x --pdb   # drop to PDB on first failure, then end test session
-    py.test --pdb --maxfail=3  # drop to PDB for first three failures
-
-Note that on any failure the exception information is stored on
-``sys.last_value``, ``sys.last_type`` and ``sys.last_traceback``. In
-interactive use, this allows one to drop into postmortem debugging with
-any debug tool. One can also manually access the exception information,
-for example::
-
-    >>> import sys
-    >>> sys.last_traceback.tb_lineno
-    42
-    >>> sys.last_value
-    AssertionError('assert result == "ok"',)
-
-Setting a breakpoint / aka ``set_trace()``
-----------------------------------------------------
-
-If you want to set a breakpoint and enter the ``pdb.set_trace()`` you
-can use a helper::
-
-    import pytest
-    def test_function():
-        ...
-        pytest.set_trace()    # invoke PDB debugger and tracing
-
-.. versionadded: 2.0.0
-
-Prior to pytest version 2.0.0 you could only enter PDB_ tracing if you disabled
-capturing on the command line via ``py.test -s``. In later versions, pytest
-automatically disables its output capture when you enter PDB_ tracing:
-
-* Output capture in other tests is not affected.
-* Any prior test output that has already been captured and will be processed as
-  such.
-* Any later output produced within the same test will not be captured and will
-  instead get sent directly to ``sys.stdout``. Note that this holds true even
-  for test output occurring after you exit the interactive PDB_ tracing session
-  and continue with the regular test run.
-
-.. versionadded: 2.4.0
-
-Since pytest version 2.4.0 you can also use the native Python
-``import pdb;pdb.set_trace()`` call to enter PDB_ tracing without having to use
-the ``pytest.set_trace()`` wrapper or explicitly disable pytest's output
-capturing via ``py.test -s``.
-
-.. _durations:
-
-Profiling test execution duration
--------------------------------------
-
-.. versionadded: 2.2
-
-To get a list of the slowest 10 test durations::
-
-    py.test --durations=10
-
-
-Creating JUnitXML format files
-----------------------------------------------------
-
-To create result files which can be read by Hudson_ or other Continuous
-integration servers, use this invocation::
-
-    py.test --junitxml=path
-
-to create an XML file at ``path``.
-
-record_xml_property
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. versionadded:: 2.8
-
-If you want to log additional information for a test, you can use the
-``record_xml_property`` fixture:
-
-.. code-block:: python
-
-    def test_function(record_xml_property):
-        record_xml_property("example_key", 1)
-        assert 0
-
-This will add an extra property ``example_key="1"`` to the generated
-``testcase`` tag:
-
-.. code-block:: xml
-
-    <testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009">
-      <properties>
-        <property name="example_key" value="1" />
-      </properties>
-    </testcase>
-
-.. warning::
-
-    This is an experimental feature, and its interface might be replaced
-    by something more powerful and general in future versions. The
-    functionality per-se will be kept, however.
-
-    Currently it does not work when used with the ``pytest-xdist`` plugin.
-
-    Also please note that using this feature will break any schema verification.
-    This might be a problem when used with some CI servers.
-
-Creating resultlog format files
-----------------------------------------------------
-
-To create plain-text machine-readable result files you can issue::
-
-    py.test --resultlog=path
-
-and look at the content at the ``path`` location.  Such files are used e.g.
-by the `PyPy-test`_ web page to show test results over several revisions.
-
-.. _`PyPy-test`: http://buildbot.pypy.org/summary
-
-
-Sending test report to online pastebin service
------------------------------------------------------
-
-**Creating a URL for each test failure**::
-
-    py.test --pastebin=failed
-
-This will submit test run information to a remote Paste service and
-provide a URL for each failure.  You may select tests as usual or add
-for example ``-x`` if you only want to send one particular failure.
-
-**Creating a URL for a whole test session log**::
-
-    py.test --pastebin=all
-
-Currently only pasting to the http://bpaste.net service is implemented.
-
-Disabling plugins
------------------
-
-To disable loading specific plugins at invocation time, use the ``-p`` option
-together with the prefix ``no:``.
-
-Example: to disable loading the plugin ``doctest``, which is responsible for
-executing doctest tests from text files, invoke py.test like this::
-
-    py.test -p no:doctest
-
-.. _`pytest.main-usage`:
-
-Calling pytest from Python code
-----------------------------------------------------
-
-.. versionadded:: 2.0
-
-You can invoke ``pytest`` from Python code directly::
-
-    pytest.main()
-
-this acts as if you would call "py.test" from the command line.
-It will not raise ``SystemExit`` but return the exitcode instead.
-You can pass in options and arguments::
-
-    pytest.main(['-x', 'mytestdir'])
-
-or pass in a string::
-
-    pytest.main("-x mytestdir")
-
-You can specify additional plugins to ``pytest.main``::
-
-    # content of myinvoke.py
-    import pytest
-    class MyPlugin:
-        def pytest_sessionfinish(self):
-            print("*** test run reporting finishing")
-
-    pytest.main("-qq", plugins=[MyPlugin()])
-
-Running it will show that ``MyPlugin`` was added and its
-hook was invoked::
-
-    $ python myinvoke.py
-    *** test run reporting finishing
-    
-
-.. include:: links.inc
diff --git a/tools/pytest/doc/en/writing_plugins.rst b/tools/pytest/doc/en/writing_plugins.rst
deleted file mode 100644
index cc346aa..0000000
--- a/tools/pytest/doc/en/writing_plugins.rst
+++ /dev/null
@@ -1,575 +0,0 @@
-.. _plugins:
-.. _`writing-plugins`:
-
-Writing plugins
-===============
-
-It is easy to implement `local conftest plugins`_ for your own project
-or `pip-installable plugins`_ that can be used throughout many projects,
-including third party projects.  Please refer to :ref:`using plugins` if you
-only want to use but not write plugins.
-
-A plugin contains one or multiple hook functions. :ref:`Writing hooks <writinghooks>`
-explains the basics and details of how you can write a hook function yourself.
-``pytest`` implements all aspects of configuration, collection, running and
-reporting by calling `well specified hooks`_ of the following plugins:
-
-* :ref:`builtin plugins`: loaded from pytest's internal ``_pytest`` directory.
-
-* :ref:`external plugins <extplugins>`: modules discovered through
-  `setuptools entry points`_
-
-* `conftest.py plugins`_: modules auto-discovered in test directories
-
-In principle, each hook call is a ``1:N`` Python function call where ``N`` is the
-number of registered implementation functions for a given specification.
-All specifications and implementations following the ``pytest_`` prefix
-naming convention, making them easy to distinguish and find.
-
-.. _`pluginorder`:
-
-Plugin discovery order at tool startup
---------------------------------------
-
-``pytest`` loads plugin modules at tool startup in the following way:
-
-* by loading all builtin plugins
-
-* by loading all plugins registered through `setuptools entry points`_.
-
-* by pre-scanning the command line for the ``-p name`` option
-  and loading the specified plugin before actual command line parsing.
-
-* by loading all :file:`conftest.py` files as inferred by the command line
-  invocation:
-
-  - if no test paths are specified use current dir as a test path
-  - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
-    to the directory part of the first test path.
-
-  Note that pytest does not find ``conftest.py`` files in deeper nested
-  sub directories at tool startup.  It is usually a good idea to keep
-  your conftest.py file in the top level test or project root directory.
-
-* by recursively loading all plugins specified by the
-  ``pytest_plugins`` variable in ``conftest.py`` files
-
-
-.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
-.. _`conftest.py plugins`:
-.. _`conftest.py`:
-.. _`localplugin`:
-.. _`conftest`:
-.. _`local conftest plugins`:
-
-conftest.py: local per-directory plugins
-----------------------------------------
-
-Local ``conftest.py`` plugins contain directory-specific hook
-implementations.  Hook Session and test running activities will
-invoke all hooks defined in ``conftest.py`` files closer to the
-root of the filesystem.  Example of implementing the
-``pytest_runtest_setup`` hook so that is called for tests in the ``a``
-sub directory but not for other directories::
-
-    a/conftest.py:
-        def pytest_runtest_setup(item):
-            # called for running each test in 'a' directory
-            print ("setting up", item)
-
-    a/test_sub.py:
-        def test_sub():
-            pass
-
-    test_flat.py:
-        def test_flat():
-            pass
-
-Here is how you might run it::
-
-     py.test test_flat.py   # will not show "setting up"
-     py.test a/test_sub.py  # will show "setting up"
-
-.. Note::
-    If you have ``conftest.py`` files which do not reside in a
-    python package directory (i.e. one containing an ``__init__.py``) then
-    "import conftest" can be ambiguous because there might be other
-    ``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
-    It is thus good practice for projects to either put ``conftest.py``
-    under a package scope or to never import anything from a
-    conftest.py file.
-
-
-Writing your own plugin
------------------------
-
-.. _`setuptools`: http://pypi.python.org/pypi/setuptools
-
-If you want to write a plugin, there are many real-life examples
-you can copy from:
-
-* a custom collection example plugin: :ref:`yaml plugin`
-* around 20 :ref:`builtin plugins` which provide pytest's own functionality
-* many `external plugins <http://plugincompat.herokuapp.com>`_ providing additional features
-
-All of these plugins implement the documented `well specified hooks`_
-to extend and add functionality.
-
-.. note::
-    Make sure to check out the excellent
-    `cookiecutter-pytest-plugin <https://github.com/pytest-dev/cookiecutter-pytest-plugin>`_
-    project, which is a `cookiecutter template <https://github.com/audreyr/cookiecutter>`_
-    for authoring plugins.
-
-    The template provides an excellent starting point with a working plugin,
-    tests running with tox, comprehensive README and
-    entry-pointy already pre-configured.
-
-Also consider :ref:`contributing your plugin to pytest-dev<submitplugin>`
-once it has some happy users other than yourself.
-
-
-.. _`setuptools entry points`:
-.. _`pip-installable plugins`:
-
-Making your plugin installable by others
-----------------------------------------
-
-If you want to make your plugin externally available, you
-may define a so-called entry point for your distribution so
-that ``pytest`` finds your plugin module.  Entry points are
-a feature that is provided by `setuptools`_. pytest looks up
-the ``pytest11`` entrypoint to discover its
-plugins and you can thus make your plugin available by defining
-it in your setuptools-invocation:
-
-.. sourcecode:: python
-
-    # sample ./setup.py file
-    from setuptools import setup
-
-    setup(
-        name="myproject",
-        packages = ['myproject']
-
-        # the following makes a plugin available to pytest
-        entry_points = {
-            'pytest11': [
-                'name_of_plugin = myproject.pluginmodule',
-            ]
-        },
-    )
-
-If a package is installed this way, ``pytest`` will load
-``myproject.pluginmodule`` as a plugin which can define
-`well specified hooks`_.
-
-
-
-
-Requiring/Loading plugins in a test module or conftest file
------------------------------------------------------------
-
-You can require plugins in a test module or a conftest file like this::
-
-    pytest_plugins = "name1", "name2",
-
-When the test module or conftest plugin is loaded the specified plugins
-will be loaded as well.  You can also use dotted path like this::
-
-    pytest_plugins = "myapp.testsupport.myplugin"
-
-which will import the specified module as a ``pytest`` plugin.
-
-
-Accessing another plugin by name
---------------------------------
-
-If a plugin wants to collaborate with code from
-another plugin it can obtain a reference through
-the plugin manager like this:
-
-.. sourcecode:: python
-
-    plugin = config.pluginmanager.getplugin("name_of_plugin")
-
-If you want to look at the names of existing plugins, use
-the ``--traceconfig`` option.
-
-Testing plugins
----------------
-
-pytest comes with some facilities that you can enable for testing your
-plugin.  Given that you have an installed plugin you can enable the
-:py:class:`testdir <_pytest.pytester.Testdir>` fixture via specifying a
-command line option to include the pytester plugin (``-p pytester``) or
-by putting ``pytest_plugins = "pytester"`` into your test or
-``conftest.py`` file.  You then will have a ``testdir`` fixture which you
-can use like this::
-
-    # content of test_myplugin.py
-
-    pytest_plugins = "pytester"  # to get testdir fixture
-
-    def test_myplugin(testdir):
-        testdir.makepyfile("""
-            def test_example():
-                pass
-        """)
-        result = testdir.runpytest("--verbose")
-        result.fnmatch_lines("""
-            test_example*
-        """)
-
-Note that by default ``testdir.runpytest()`` will perform a pytest
-in-process.  You can pass the command line option ``--runpytest=subprocess``
-to have it happen in a subprocess.
-
-Also see the :py:class:`RunResult <_pytest.pytester.RunResult>` for more
-methods of the result object that you get from a call to ``runpytest``.
-
-.. _`writinghooks`:
-
-Writing hook functions
-======================
-
-
-.. _validation:
-
-hook function validation and execution
---------------------------------------
-
-pytest calls hook functions from registered plugins for any
-given hook specification.  Let's look at a typical hook function
-for the ``pytest_collection_modifyitems(session, config,
-items)`` hook which pytest calls after collection of all test items is
-completed.
-
-When we implement a ``pytest_collection_modifyitems`` function in our plugin
-pytest will during registration verify that you use argument
-names which match the specification and bail out if not.
-
-Let's look at a possible implementation:
-
-.. code-block:: python
-
-    def pytest_collection_modifyitems(config, items):
-        # called after collection is completed
-        # you can modify the ``items`` list
-
-Here, ``pytest`` will pass in ``config`` (the pytest config object)
-and ``items`` (the list of collected test items) but will not pass
-in the ``session`` argument because we didn't list it in the function
-signature.  This dynamic "pruning" of arguments allows ``pytest`` to
-be "future-compatible": we can introduce new hook named parameters without
-breaking the signatures of existing hook implementations.  It is one of
-the reasons for the general long-lived compatibility of pytest plugins.
-
-Note that hook functions other than ``pytest_runtest_*`` are not
-allowed to raise exceptions.  Doing so will break the pytest run.
-
-
-
-firstresult: stop at first non-None result
--------------------------------------------
-
-Most calls to ``pytest`` hooks result in a **list of results** which contains
-all non-None results of the called hook functions.
-
-Some hook specifications use the ``firstresult=True`` option so that the hook
-call only executes until the first of N registered functions returns a
-non-None result which is then taken as result of the overall hook call.
-The remaining hook functions will not be called in this case.
-
-
-hookwrapper: executing around other hooks
--------------------------------------------------
-
-.. currentmodule:: _pytest.core
-
-.. versionadded:: 2.7 (experimental)
-
-pytest plugins can implement hook wrappers which wrap the execution
-of other hook implementations.  A hook wrapper is a generator function
-which yields exactly once. When pytest invokes hooks it first executes
-hook wrappers and passes the same arguments as to the regular hooks.
-
-At the yield point of the hook wrapper pytest will execute the next hook
-implementations and return their result to the yield point in the form of
-a :py:class:`CallOutcome` instance which encapsulates a result or
-exception info.  The yield point itself will thus typically not raise
-exceptions (unless there are bugs).
-
-Here is an example definition of a hook wrapper::
-
-    import pytest
-
-    @pytest.hookimpl(hookwrapper=True)
-    def pytest_pyfunc_call(pyfuncitem):
-        # do whatever you want before the next hook executes
-
-        outcome = yield
-        # outcome.excinfo may be None or a (cls, val, tb) tuple
-
-        res = outcome.get_result()  # will raise if outcome was exception
-        # postprocess result
-
-Note that hook wrappers don't return results themselves, they merely
-perform tracing or other side effects around the actual hook implementations.
-If the result of the underlying hook is a mutable object, they may modify
-that result but it's probably better to avoid it.
-
-
-Hook function ordering / call example
--------------------------------------
-
-For any given hook specification there may be more than one
-implementation and we thus generally view ``hook`` execution as a
-``1:N`` function call where ``N`` is the number of registered functions.
-There are ways to influence if a hook implementation comes before or
-after others, i.e.  the position in the ``N``-sized list of functions:
-
-.. code-block:: python
-
-    # Plugin 1
-    @pytest.hookimpl(tryfirst=True)
-    def pytest_collection_modifyitems(items):
-        # will execute as early as possible
-
-    # Plugin 2
-    @pytest.hookimpl(trylast=True)
-    def pytest_collection_modifyitems(items):
-        # will execute as late as possible
-
-    # Plugin 3
-    @pytest.hookimpl(hookwrapper=True)
-    def pytest_collection_modifyitems(items):
-        # will execute even before the tryfirst one above!
-        outcome = yield
-        # will execute after all non-hookwrappers executed
-
-Here is the order of execution:
-
-1. Plugin3's pytest_collection_modifyitems called until the yield point
-   because it is a hook wrapper.
-
-2. Plugin1's pytest_collection_modifyitems is called because it is marked
-   with ``tryfirst=True``.
-
-3. Plugin2's pytest_collection_modifyitems is called because it is marked
-   with ``trylast=True`` (but even without this mark it would come after
-   Plugin1).
-
-4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
-   point.  The yield receives a :py:class:`CallOutcome` instance which encapsulates
-   the result from calling the non-wrappers.  Wrappers shall not modify the result.
-
-It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
-``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
-among each other.
-
-
-Declaring new hooks
-------------------------
-
-.. currentmodule:: _pytest.hookspec
-
-Plugins and ``conftest.py`` files may declare new hooks that can then be
-implemented by other plugins in order to alter behaviour or interact with
-the new plugin:
-
-.. autofunction:: pytest_addhooks
-
-Hooks are usually declared as do-nothing functions that contain only
-documentation describing when the hook will be called and what return values
-are expected.
-
-For an example, see `newhooks.py`_ from :ref:`xdist`.
-
-.. _`newhooks.py`: https://github.com/pytest-dev/pytest-xdist/blob/974bd566c599dc6a9ea291838c6f226197208b46/xdist/newhooks.py
-
-
-Optionally using hooks from 3rd party plugins
----------------------------------------------
-
-Using new hooks from plugins as explained above might be a little tricky
-because of the standard :ref:`validation mechanism <validation>`:
-if you depend on a plugin that is not installed, validation will fail and
-the error message will not make much sense to your users.
-
-One approach is to defer the hook implementation to a new plugin instead of
-declaring the hook functions directly in your plugin module, for example::
-
-    # contents of myplugin.py
-
-    class DeferPlugin(object):
-        """Simple plugin to defer pytest-xdist hook functions."""
-
-        def pytest_testnodedown(self, node, error):
-            """standard xdist hook function.
-            """
-
-    def pytest_configure(config):
-        if config.pluginmanager.hasplugin('xdist'):
-            config.pluginmanager.register(DeferPlugin())
-
-This has the added benefit of allowing you to conditionally install hooks
-depending on which plugins are installed.
-
-.. _`well specified hooks`:
-
-.. currentmodule:: _pytest.hookspec
-
-pytest hook reference
-=====================
-
-
-Initialization, command line and configuration hooks
-----------------------------------------------------
-
-.. autofunction:: pytest_load_initial_conftests
-.. autofunction:: pytest_cmdline_preparse
-.. autofunction:: pytest_cmdline_parse
-.. autofunction:: pytest_namespace
-.. autofunction:: pytest_addoption
-.. autofunction:: pytest_cmdline_main
-.. autofunction:: pytest_configure
-.. autofunction:: pytest_unconfigure
-
-Generic "runtest" hooks
------------------------
-
-All runtest related hooks receive a :py:class:`pytest.Item` object.
-
-.. autofunction:: pytest_runtest_protocol
-.. autofunction:: pytest_runtest_setup
-.. autofunction:: pytest_runtest_call
-.. autofunction:: pytest_runtest_teardown
-.. autofunction:: pytest_runtest_makereport
-
-For deeper understanding you may look at the default implementation of
-these hooks in :py:mod:`_pytest.runner` and maybe also
-in :py:mod:`_pytest.pdb` which interacts with :py:mod:`_pytest.capture`
-and its input/output capturing in order to immediately drop
-into interactive debugging when a test failure occurs.
-
-The :py:mod:`_pytest.terminal` reported specifically uses
-the reporting hook to print information about a test run.
-
-Collection hooks
-----------------
-
-``pytest`` calls the following hooks for collecting files and directories:
-
-.. autofunction:: pytest_ignore_collect
-.. autofunction:: pytest_collect_directory
-.. autofunction:: pytest_collect_file
-
-For influencing the collection of objects in Python modules
-you can use the following hook:
-
-.. autofunction:: pytest_pycollect_makeitem
-.. autofunction:: pytest_generate_tests
-
-After collection is complete, you can modify the order of
-items, delete or otherwise amend the test items:
-
-.. autofunction:: pytest_collection_modifyitems
-
-Reporting hooks
----------------
-
-Session related reporting hooks:
-
-.. autofunction:: pytest_collectstart
-.. autofunction:: pytest_itemcollected
-.. autofunction:: pytest_collectreport
-.. autofunction:: pytest_deselected
-.. autofunction:: pytest_report_header
-.. autofunction:: pytest_report_teststatus
-.. autofunction:: pytest_terminal_summary
-
-And here is the central hook for reporting about
-test execution:
-
-.. autofunction:: pytest_runtest_logreport
-
-You can also use this hook to customize assertion representation for some
-types:
-
-.. autofunction:: pytest_assertrepr_compare
-
-
-Debugging/Interaction hooks
----------------------------
-
-There are few hooks which can be used for special
-reporting or interaction with exceptions:
-
-.. autofunction:: pytest_internalerror
-.. autofunction:: pytest_keyboard_interrupt
-.. autofunction:: pytest_exception_interact
-.. autofunction:: pytest_enter_pdb
-
-
-Reference of objects involved in hooks
-======================================
-
-.. autoclass:: _pytest.config.Config()
-    :members:
-
-.. autoclass:: _pytest.config.Parser()
-    :members:
-
-.. autoclass:: _pytest.main.Node()
-    :members:
-
-.. autoclass:: _pytest.main.Collector()
-    :members:
-    :show-inheritance:
-
-.. autoclass:: _pytest.main.Item()
-    :members:
-    :show-inheritance:
-
-.. autoclass:: _pytest.python.Module()
-    :members:
-    :show-inheritance:
-
-.. autoclass:: _pytest.python.Class()
-    :members:
-    :show-inheritance:
-
-.. autoclass:: _pytest.python.Function()
-    :members:
-    :show-inheritance:
-
-.. autoclass:: _pytest.runner.CallInfo()
-    :members:
-
-.. autoclass:: _pytest.runner.TestReport()
-    :members:
-
-.. autoclass:: _pytest.vendored_packages.pluggy._CallOutcome()
-    :members:
-
-.. autofunction:: _pytest.config.get_plugin_manager()
-
-.. autoclass:: _pytest.config.PytestPluginManager()
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-.. autoclass:: pluggy.PluginManager()
-    :members:
-
-.. currentmodule:: _pytest.pytester
-
-.. autoclass:: Testdir()
-    :members: runpytest,runpytest_subprocess,runpytest_inprocess,makeconftest,makepyfile
-
-.. autoclass:: RunResult()
-    :members:
-
-.. autoclass:: LineMatcher()
-    :members:
diff --git a/tools/pytest/doc/en/xdist.rst b/tools/pytest/doc/en/xdist.rst
deleted file mode 100644
index ee1bd6032..0000000
--- a/tools/pytest/doc/en/xdist.rst
+++ /dev/null
@@ -1,197 +0,0 @@
-
-.. _`xdist`:
-
-xdist: pytest distributed testing plugin
-===============================================================
-
-The `pytest-xdist`_ plugin extends ``pytest`` with some unique
-test execution modes:
-
-* Looponfail: run your tests repeatedly in a subprocess.  After each
-  run, ``pytest`` waits until a file in your project changes and then
-  re-runs the previously failing tests.  This is repeated until all
-  tests pass.  At this point a full run is again performed.
-
-* multiprocess Load-balancing: if you have multiple CPUs or hosts you can use
-  them for a combined test run.  This allows to speed up
-  development or to use special resources of remote machines.
-
-* Multi-Platform coverage: you can specify different Python interpreters
-  or different platforms and run tests in parallel on all of them.
-
-Before running tests remotely, ``pytest`` efficiently "rsyncs" your
-program source code to the remote place.  All test results
-are reported back and displayed to your local terminal.
-You may specify different Python versions and interpreters.
-
-
-Installation of xdist plugin
-------------------------------
-
-Install the plugin with::
-
-    easy_install pytest-xdist
-
-    # or
-    
-    pip install pytest-xdist
-
-or use the package in develop/in-place mode with
-a checkout of the `pytest-xdist repository`_ ::
-
-    python setup.py develop
-
-
-Usage examples
----------------------
-
-.. _`xdistcpu`:
-
-Speed up test runs by sending tests to multiple CPUs
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-To send tests to multiple CPUs, type::
-
-    py.test -n NUM
-
-Especially for longer running tests or tests requiring
-a lot of I/O this can lead to considerable speed ups.
-
-
-Running tests in a Python subprocess
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-To instantiate a Python-2.7 subprocess and send tests to it, you may type::
-
-    py.test -d --tx popen//python=python2.7
-
-This will start a subprocess which is run with the "python2.7"
-Python interpreter, found in your system binary lookup path.
-
-If you prefix the --tx option value like this::
-
-    py.test -d --tx 3*popen//python=python2.7
-
-then three subprocesses would be created and the tests
-will be distributed to three subprocesses and run simultanously.
-
-.. _looponfailing:
-
-
-Running tests in looponfailing mode
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-For refactoring a project with a medium or large test suite
-you can use the looponfailing mode. Simply add the ``--f`` option::
-
-    py.test -f
-   
-and ``pytest`` will run your tests. Assuming you have failures it will then
-wait for file changes and re-run the failing test set.  File changes are detected by looking at ``looponfailingroots`` root directories and all of their contents (recursively).  If the default for this value does not work for you you
-can change it in your project by setting a configuration option::
-
-    # content of a pytest.ini, setup.cfg or tox.ini file
-    [pytest]
-    looponfailroots = mypkg testdir
-
-This would lead to only looking for file changes in the respective directories, specified relatively to the ini-file's directory.
-
-Sending tests to remote SSH accounts
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-Suppose you have a package ``mypkg`` which contains some
-tests that you can successfully run locally. And you also
-have a ssh-reachable machine ``myhost``.  Then
-you can ad-hoc distribute your tests by typing::
-
-    py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg
-
-This will synchronize your ``mypkg`` package directory
-with a remote ssh account and then collect and run your
-tests at the remote side.
-
-You can specify multiple ``--rsyncdir`` directories
-to be sent to the remote side.
-
-.. XXX CHECK
-
-    **NOTE:** For ``pytest`` to collect and send tests correctly
-    you not only need to make sure all code and tests
-    directories are rsynced, but that any test (sub) directory
-    also has an ``__init__.py`` file because internally
-    ``pytest`` references tests as a fully qualified python
-    module path.  **You will otherwise get strange errors**
-    during setup of the remote side.
-
-Sending tests to remote Socket Servers
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-Download the single-module `socketserver.py`_ Python program
-and run it like this::
-
-    python socketserver.py
-
-It will tell you that it starts listening on the default
-port.  You can now on your home machine specify this
-new socket host with something like this::
-
-    py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
-
-
-.. _`atonce`:
-
-Running tests on many platforms at once
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-The basic command to run tests on multiple platforms is::
-
-    py.test --dist=each --tx=spec1 --tx=spec2
-
-If you specify a windows host, an OSX host and a Linux
-environment this command will send each tests to all
-platforms - and report back failures from all platforms
-at once.   The specifications strings use the `xspec syntax`_.
-
-.. _`xspec syntax`: http://codespeak.net/execnet/basics.html#xspec
-
-.. _`socketserver.py`: http://bitbucket.org/hpk42/execnet/raw/2af991418160/execnet/script/socketserver.py
-
-.. _`execnet`: http://codespeak.net/execnet
-
-Specifying test exec environments in an ini file
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-pytest (since version 2.0) supports ini-style configuration.
-For example, you could make running with three subprocesses your default::
-
-    [pytest]
-    addopts = -n3
-
-You can also add default environments like this::
-
-    [pytest]
-    addopts = --tx ssh=myhost//python=python2.7 --tx ssh=myhost//python=python2.6
-
-and then just type::
-
-    py.test --dist=each
-
-to run tests in each of the environments.
-
-Specifying "rsync" dirs in an ini-file
-+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-In a ``tox.ini`` or ``setup.cfg`` file in your root project directory
-you may specify directories to include or to exclude in synchronisation::
-
-    [pytest]
-    rsyncdirs = . mypkg helperpkg
-    rsyncignore = .hg
-
-These directory specifications are relative to the directory
-where the configuration file was found.
-
-.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist
-.. _`pytest-xdist repository`: http://bitbucket.org/pytest-dev/pytest-xdist
-.. _`pytest`: http://pytest.org
-
diff --git a/tools/pytest/doc/en/xunit_setup.rst b/tools/pytest/doc/en/xunit_setup.rst
deleted file mode 100644
index 7a80f12..0000000
--- a/tools/pytest/doc/en/xunit_setup.rst
+++ /dev/null
@@ -1,90 +0,0 @@
-
-.. _`classic xunit`:
-.. _xunitsetup:
-
-classic xunit-style setup
-========================================
-
-This section describes a classic and popular way how you can implement
-fixtures (setup and teardown test state) on a per-module/class/function basis.  
-pytest started supporting these methods around 2005 and subsequently
-nose and the standard library introduced them (under slightly different
-names).  While these setup/teardown methods are and will remain fully
-supported you may also use pytest's more powerful :ref:`fixture mechanism
-<fixture>` which leverages the concept of dependency injection, allowing
-for a more modular and more scalable approach for managing test state, 
-especially for larger projects and for functional testing.  You can
-mix both fixture mechanisms in the same file but unittest-based
-test methods cannot receive fixture arguments.
-
-.. note::
-
-    As of pytest-2.4, teardownX functions are not called if 
-    setupX existed and failed/was skipped.  This harmonizes
-    behaviour across all major python testing tools.
-
-Module level setup/teardown
---------------------------------------
-
-If you have multiple test functions and test classes in a single
-module you can optionally implement the following fixture methods
-which will usually be called once for all the functions::
-
-    def setup_module(module):
-        """ setup any state specific to the execution of the given module."""
-
-    def teardown_module(module):
-        """ teardown any state that was previously setup with a setup_module
-        method.
-        """
-
-Class level setup/teardown
-----------------------------------
-
-Similarly, the following methods are called at class level before
-and after all test methods of the class are called::
-
-    @classmethod
-    def setup_class(cls):
-        """ setup any state specific to the execution of the given class (which
-        usually contains tests).
-        """
-
-    @classmethod
-    def teardown_class(cls):
-        """ teardown any state that was previously setup with a call to
-        setup_class.
-        """
-
-Method and function level setup/teardown
------------------------------------------------
-
-Similarly, the following methods are called around each method invocation::
-
-    def setup_method(self, method):
-        """ setup any state tied to the execution of the given method in a
-        class.  setup_method is invoked for every test method of a class.
-        """
-
-    def teardown_method(self, method):
-        """ teardown any state that was previously setup with a setup_method
-        call.
-        """
-
-If you would rather define test functions directly at module level
-you can also use the following functions to implement fixtures::
-
-    def setup_function(function):
-        """ setup any state tied to the execution of the given function.
-        Invoked for every test function in the module.
-        """
-
-    def teardown_function(function):
-        """ teardown any state that was previously setup with a setup_function
-        call.
-        """
-
-Note that it is possible for setup/teardown pairs to be invoked multiple times
-per testing process.
-
-.. _`unittest.py module`: http://docs.python.org/library/unittest.html
diff --git a/tools/pytest/doc/en/yieldfixture.rst b/tools/pytest/doc/en/yieldfixture.rst
deleted file mode 100644
index ee88a27..0000000
--- a/tools/pytest/doc/en/yieldfixture.rst
+++ /dev/null
@@ -1,100 +0,0 @@
-.. _yieldfixture:
-
-Fixture functions using "yield" / context manager integration
----------------------------------------------------------------
-
-.. versionadded:: 2.4
-
-.. regendoc:wipe
-
-pytest-2.4 allows fixture functions to seamlessly use a ``yield`` instead 
-of a ``return`` statement to provide a fixture value while otherwise
-fully supporting all other fixture features.
-
-Let's look at a simple standalone-example using the ``yield`` syntax::
-
-    # content of test_yield.py
-    
-    import pytest
-
-    @pytest.yield_fixture
-    def passwd():
-        print ("\nsetup before yield")
-        f = open("/etc/passwd")
-        yield f.readlines()
-        print ("teardown after yield")
-        f.close()
-
-    def test_has_lines(passwd):
-        print ("test called")
-        assert passwd
-
-In contrast to :ref:`finalization through registering callbacks
-<finalization>`, our fixture function used a ``yield``
-statement to provide the lines of the ``/etc/passwd`` file.  
-The code after the ``yield`` statement serves as the teardown code, 
-avoiding the indirection of registering a teardown callback function.   
-
-Let's run it with output capturing disabled::
-
-    $ py.test -q -s test_yield.py
-    
-    setup before yield
-    test called
-    .teardown after yield
-    
-    1 passed in 0.12 seconds
-
-We can also seamlessly use the new syntax with ``with`` statements.
-Let's simplify the above ``passwd`` fixture::
-
-    # content of test_yield2.py
-    
-    import pytest
-
-    @pytest.yield_fixture
-    def passwd():
-        with open("/etc/passwd") as f:
-            yield f.readlines()
-
-    def test_has_lines(passwd):
-        assert len(passwd) >= 1
-
-The file ``f`` will be closed after the test finished execution
-because the Python ``file`` object supports finalization when
-the ``with`` statement ends. 
-
-Note that the yield fixture form supports all other fixture
-features such as ``scope``, ``params``, etc., thus changing existing
-fixture functions to use ``yield`` is straightforward.
-
-.. note::
-
-    While the ``yield`` syntax is similar to what
-    :py:func:`contextlib.contextmanager` decorated functions
-    provide, with pytest fixture functions the part after the
-    "yield" will always be invoked, independently from the
-    exception status of the test function which uses the fixture.
-    This behaviour makes sense if you consider that many different
-    test functions might use a module or session scoped fixture.
-
-
-Discussion and future considerations / feedback
-++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-There are some topics that are worth mentioning:
-
-- usually ``yield`` is used for producing multiple values.
-  But fixture functions can only yield exactly one value.
-  Yielding a second fixture value will get you an error.
-  It's possible we can evolve pytest to allow for producing
-  multiple values as an alternative to current parametrization.
-  For now, you can just use the normal
-  :ref:`fixture parametrization <fixture-parametrize>`
-  mechanisms together with ``yield``-style fixtures.
-
-- lastly ``yield`` introduces more than one way to write
-  fixture functions, so what's the obvious way to a newcomer?
-
-If you want to feedback or participate in discussion of the above
-topics, please join our :ref:`contact channels`, you are most welcome.
diff --git a/tools/pytest/extra/get_issues.py b/tools/pytest/extra/get_issues.py
deleted file mode 100644
index 6437ba4..0000000
--- a/tools/pytest/extra/get_issues.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import json
-import py
-import textwrap
-
-issues_url = "http://bitbucket.org/api/1.0/repositories/pytest-dev/pytest/issues"
-
-import requests
-
-def get_issues():
-    chunksize = 50
-    start = 0
-    issues = []
-    while 1:
-        post_data = {"accountname": "pytest-dev",
-                     "repo_slug": "pytest",
-                     "start": start,
-                     "limit": chunksize}
-        print ("getting from", start)
-        r = requests.get(issues_url, params=post_data)
-        data = r.json()
-        issues.extend(data["issues"])
-        if start + chunksize >= data["count"]:
-            return issues
-        start += chunksize
-
-kind2num = "bug enhancement task proposal".split()
-
-status2num = "new open resolved duplicate invalid wontfix".split()
-
-def main(args):
-    cachefile = py.path.local(args.cache)
-    if not cachefile.exists() or args.refresh:
-        issues = get_issues()
-        cachefile.write(json.dumps(issues))
-    else:
-        issues = json.loads(cachefile.read())
-
-    open_issues = [x for x in issues
-                    if x["status"] in ("new", "open")]
-
-    def kind_and_id(x):
-        kind = x["metadata"]["kind"]
-        return kind2num.index(kind), len(issues)-int(x["local_id"])
-    open_issues.sort(key=kind_and_id)
-    report(open_issues)
-
-def report(issues):
-    for issue in issues:
-        metadata = issue["metadata"]
-        priority = issue["priority"]
-        title = issue["title"]
-        content = issue["content"]
-        kind = metadata["kind"]
-        status = issue["status"]
-        id = issue["local_id"]
-        link = "https://bitbucket.org/pytest-dev/pytest/issue/%s/" % id
-        print("----")
-        print(status, kind, link)
-        print(title)
-        #print()
-        #lines = content.split("\n")
-        #print ("\n".join(lines[:3]))
-        #if len(lines) > 3 or len(content) > 240:
-        #    print ("...")
-
-if __name__ == "__main__":
-    import argparse
-    parser = argparse.ArgumentParser("process bitbucket issues")
-    parser.add_argument("--refresh", action="store_true",
-                        help="invalidate cache, refresh issues")
-    parser.add_argument("--cache", action="store", default="issues.json",
-                        help="cache file")
-    args = parser.parse_args()
-    main(args)
diff --git a/tools/pytest/plugin-test.sh b/tools/pytest/plugin-test.sh
deleted file mode 100644
index 9c61b50..0000000
--- a/tools/pytest/plugin-test.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# this assumes plugins are installed as sister directories
-
-set -e
-cd ../pytest-pep8
-py.test
-cd ../pytest-instafail
-py.test 
-cd ../pytest-cache
-py.test
-cd ../pytest-xprocess
-py.test
-#cd ../pytest-cov
-#py.test
-cd ../pytest-capturelog
-py.test
-cd ../pytest-xdist
-py.test
-
diff --git a/tools/pytest/pytest.py b/tools/pytest/pytest.py
deleted file mode 100644
index e376e41..0000000
--- a/tools/pytest/pytest.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# PYTHON_ARGCOMPLETE_OK
-"""
-pytest: unit and functional testing with Python.
-"""
-__all__ = [
-    'main',
-    'UsageError',
-    'cmdline',
-    'hookspec',
-    'hookimpl',
-    '__version__',
-]
-
-if __name__ == '__main__': # if run as a script or by 'python -m pytest'
-    # we trigger the below "else" condition by the following import
-    import pytest
-    raise SystemExit(pytest.main())
-
-# else we are imported
-
-from _pytest.config import (
-    main, UsageError, _preloadplugins, cmdline,
-    hookspec, hookimpl
-)
-from _pytest import __version__
-
-_preloadplugins() # to populate pytest.* namespace so help(pytest) works
-
diff --git a/tools/pytest/requirements-docs.txt b/tools/pytest/requirements-docs.txt
deleted file mode 100644
index be3a232..0000000
--- a/tools/pytest/requirements-docs.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-sphinx==1.2.3
-regendoc
-pyyaml
diff --git a/tools/pytest/runtox.py b/tools/pytest/runtox.py
deleted file mode 100644
index 8c13c53..0000000
--- a/tools/pytest/runtox.py
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env python
-
-if __name__ == "__main__":
-    import subprocess
-    import sys
-    subprocess.call([sys.executable, "-m", "tox",
-                     "-i", "ALL=https://devpi.net/hpk/dev/",
-                     "--develop"] + sys.argv[1:])
diff --git a/tools/pytest/setup.cfg b/tools/pytest/setup.cfg
deleted file mode 100644
index 1ab4fd0..0000000
--- a/tools/pytest/setup.cfg
+++ /dev/null
@@ -1,13 +0,0 @@
-[build_sphinx]
-source-dir = doc/en/
-build-dir = doc/build
-all_files = 1
-
-[upload_sphinx]
-upload-dir = doc/en/build/html
-
-[bdist_wheel]
-universal = 1
-
-[devpi:upload]
-formats = sdist.tgz,bdist_wheel
diff --git a/tools/pytest/setup.py b/tools/pytest/setup.py
deleted file mode 100644
index 6660f21..0000000
--- a/tools/pytest/setup.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import os, sys
-import setuptools
-import pkg_resources
-from setuptools import setup, Command
-
-classifiers = ['Development Status :: 6 - Mature',
-               'Intended Audience :: Developers',
-               'License :: OSI Approved :: MIT License',
-               'Operating System :: POSIX',
-               'Operating System :: Microsoft :: Windows',
-               'Operating System :: MacOS :: MacOS X',
-               'Topic :: Software Development :: Testing',
-               'Topic :: Software Development :: Libraries',
-               'Topic :: Utilities'] + [
-              ('Programming Language :: Python :: %s' % x) for x in
-                  '2 2.6 2.7 3 3.2 3.3 3.4 3.5'.split()]
-
-with open('README.rst') as fd:
-    long_description = fd.read()
-
-def get_version():
-    p = os.path.join(os.path.dirname(
-                     os.path.abspath(__file__)), "_pytest", "__init__.py")
-    with open(p) as f:
-        for line in f.readlines():
-            if "__version__" in line:
-                return line.strip().split("=")[-1].strip(" '")
-    raise ValueError("could not read version")
-
-
-def has_environment_marker_support():
-    """
-    Tests that setuptools has support for PEP-426 environment marker support.
-
-    The first known release to support it is 0.7 (and the earliest on PyPI seems to be 0.7.2
-    so we're using that), see: http://pythonhosted.org/setuptools/history.html#id142
-
-    References:
-
-    * https://wheel.readthedocs.org/en/latest/index.html#defining-conditional-dependencies
-    * https://www.python.org/dev/peps/pep-0426/#environment-markers
-    """
-    try:
-        return pkg_resources.parse_version(setuptools.__version__) >= pkg_resources.parse_version('0.7.2')
-    except Exception as exc:
-        sys.stderr.write("Could not test setuptool's version: %s\n" % exc)
-        return False
-
-
-def main():
-    install_requires = ['py>=1.4.29']  # pluggy is vendored in _pytest.vendored_packages
-    extras_require = {}
-    if has_environment_marker_support():
-        extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse']
-        extras_require[':sys_platform=="win32"'] = ['colorama']
-    else:
-        if sys.version_info < (2, 7) or (3,) <= sys.version_info < (3, 2):
-            install_requires.append('argparse')
-        if sys.platform == 'win32':
-            install_requires.append('colorama')
-
-    setup(
-        name='pytest',
-        description='pytest: simple powerful testing with Python',
-        long_description=long_description,
-        version=get_version(),
-        url='http://pytest.org',
-        license='MIT license',
-        platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
-        author='Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others',
-        author_email='holger at merlinux.eu',
-        entry_points=make_entry_points(),
-        classifiers=classifiers,
-        cmdclass={'test': PyTest},
-        # the following should be enabled for release
-        install_requires=install_requires,
-        extras_require=extras_require,
-        packages=['_pytest', '_pytest.assertion', '_pytest._code', '_pytest.vendored_packages'],
-        py_modules=['pytest'],
-        zip_safe=False,
-    )
-
-
-def cmdline_entrypoints(versioninfo, platform, basename):
-    target = 'pytest:main'
-    if platform.startswith('java'):
-        points = {'py.test-jython': target}
-    else:
-        if basename.startswith('pypy'):
-            points = {'py.test-%s' % basename: target}
-        else: # cpython
-            points = {'py.test-%s.%s' % versioninfo[:2] : target}
-        points['py.test'] = target
-    return points
-
-
-def make_entry_points():
-    basename = os.path.basename(sys.executable)
-    points = cmdline_entrypoints(sys.version_info, sys.platform, basename)
-    keys = list(points.keys())
-    keys.sort()
-    l = ['%s = %s' % (x, points[x]) for x in keys]
-    return {'console_scripts': l}
-
-
-class PyTest(Command):
-    user_options = []
-    def initialize_options(self):
-        pass
-    def finalize_options(self):
-        pass
-    def run(self):
-        import subprocess
-        PPATH = [x for x in os.environ.get('PYTHONPATH', '').split(':') if x]
-        PPATH.insert(0, os.getcwd())
-        os.environ['PYTHONPATH'] = ':'.join(PPATH)
-        errno = subprocess.call([sys.executable, 'pytest.py', '--ignore=doc'])
-        raise SystemExit(errno)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/tools/pytest/testing/acceptance_test.py b/tools/pytest/testing/acceptance_test.py
deleted file mode 100644
index 9bc3a19..0000000
--- a/tools/pytest/testing/acceptance_test.py
+++ /dev/null
@@ -1,695 +0,0 @@
-import sys
-
-import _pytest._code
-import py
-import pytest
-from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
-
-
-class TestGeneralUsage:
-    def test_config_error(self, testdir):
-        testdir.makeconftest("""
-            def pytest_configure(config):
-                import pytest
-                raise pytest.UsageError("hello")
-        """)
-        result = testdir.runpytest(testdir.tmpdir)
-        assert result.ret != 0
-        result.stderr.fnmatch_lines([
-            '*ERROR: hello'
-        ])
-
-    def test_root_conftest_syntax_error(self, testdir):
-        testdir.makepyfile(conftest="raise SyntaxError\n")
-        result = testdir.runpytest()
-        result.stderr.fnmatch_lines(["*raise SyntaxError*"])
-        assert result.ret != 0
-
-    def test_early_hook_error_issue38_1(self, testdir):
-        testdir.makeconftest("""
-            def pytest_sessionstart():
-                0 / 0
-        """)
-        result = testdir.runpytest(testdir.tmpdir)
-        assert result.ret != 0
-        # tracestyle is native by default for hook failures
-        result.stdout.fnmatch_lines([
-            '*INTERNALERROR*File*conftest.py*line 2*',
-            '*0 / 0*',
-        ])
-        result = testdir.runpytest(testdir.tmpdir, "--fulltrace")
-        assert result.ret != 0
-        # tracestyle is native by default for hook failures
-        result.stdout.fnmatch_lines([
-            '*INTERNALERROR*def pytest_sessionstart():*',
-            '*INTERNALERROR*0 / 0*',
-        ])
-
-    def test_early_hook_configure_error_issue38(self, testdir):
-        testdir.makeconftest("""
-            def pytest_configure():
-                0 / 0
-        """)
-        result = testdir.runpytest(testdir.tmpdir)
-        assert result.ret != 0
-        # here we get it on stderr
-        result.stderr.fnmatch_lines([
-            '*INTERNALERROR*File*conftest.py*line 2*',
-            '*0 / 0*',
-        ])
-
-    def test_file_not_found(self, testdir):
-        result = testdir.runpytest("asd")
-        assert result.ret != 0
-        result.stderr.fnmatch_lines(["ERROR: file not found*asd"])
-
-    def test_file_not_found_unconfigure_issue143(self, testdir):
-        testdir.makeconftest("""
-            def pytest_configure():
-                print("---configure")
-            def pytest_unconfigure():
-                print("---unconfigure")
-        """)
-        result = testdir.runpytest("-s", "asd")
-        assert result.ret == 4 # EXIT_USAGEERROR
-        result.stderr.fnmatch_lines(["ERROR: file not found*asd"])
-        result.stdout.fnmatch_lines([
-            "*---configure",
-            "*---unconfigure",
-        ])
-
-
-    def test_config_preparse_plugin_option(self, testdir):
-        testdir.makepyfile(pytest_xyz="""
-            def pytest_addoption(parser):
-                parser.addoption("--xyz", dest="xyz", action="store")
-        """)
-        testdir.makepyfile(test_one="""
-            def test_option(pytestconfig):
-                assert pytestconfig.option.xyz == "123"
-        """)
-        result = testdir.runpytest("-p", "pytest_xyz", "--xyz=123", syspathinsert=True)
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            '*1 passed*',
-        ])
-
-    def test_assertion_magic(self, testdir):
-        p = testdir.makepyfile("""
-            def test_this():
-                x = 0
-                assert x
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            ">       assert x",
-            "E       assert 0",
-        ])
-        assert result.ret == 1
-
-    def test_nested_import_error(self, testdir):
-        p = testdir.makepyfile("""
-                import import_fails
-                def test_this():
-                    assert import_fails.a == 1
-        """)
-        testdir.makepyfile(import_fails="import does_not_work")
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            #XXX on jython this fails:  ">   import import_fails",
-            "E   ImportError: No module named *does_not_work*",
-        ])
-        assert result.ret == 1
-
-    def test_not_collectable_arguments(self, testdir):
-        p1 = testdir.makepyfile("")
-        p2 = testdir.makefile(".pyc", "123")
-        result = testdir.runpytest(p1, p2)
-        assert result.ret
-        result.stderr.fnmatch_lines([
-            "*ERROR: not found:*%s" %(p2.basename,)
-        ])
-
-    def test_issue486_better_reporting_on_conftest_load_failure(self, testdir):
-        testdir.makepyfile("")
-        testdir.makeconftest("import qwerty")
-        result = testdir.runpytest("--help")
-        result.stdout.fnmatch_lines("""
-            *--version*
-            *warning*conftest.py*
-        """)
-        result = testdir.runpytest()
-        result.stderr.fnmatch_lines("""
-            *ERROR*could not load*conftest.py*
-        """)
-
-
-    def test_early_skip(self, testdir):
-        testdir.mkdir("xyz")
-        testdir.makeconftest("""
-            import pytest
-            def pytest_collect_directory():
-                pytest.skip("early")
-        """)
-        result = testdir.runpytest()
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        result.stdout.fnmatch_lines([
-            "*1 skip*"
-        ])
-
-    def test_issue88_initial_file_multinodes(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            class MyFile(pytest.File):
-                def collect(self):
-                    return [MyItem("hello", parent=self)]
-            def pytest_collect_file(path, parent):
-                return MyFile(path, parent)
-            class MyItem(pytest.Item):
-                pass
-        """)
-        p = testdir.makepyfile("def test_hello(): pass")
-        result = testdir.runpytest(p, "--collect-only")
-        result.stdout.fnmatch_lines([
-            "*MyFile*test_issue88*",
-            "*Module*test_issue88*",
-        ])
-
-    def test_issue93_initialnode_importing_capturing(self, testdir):
-        testdir.makeconftest("""
-            import sys
-            print ("should not be seen")
-            sys.stderr.write("stder42\\n")
-        """)
-        result = testdir.runpytest()
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        assert "should not be seen" not in result.stdout.str()
-        assert "stderr42" not in result.stderr.str()
-
-    def test_conftest_printing_shows_if_error(self, testdir):
-        testdir.makeconftest("""
-            print ("should be seen")
-            assert 0
-        """)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        assert "should be seen" in result.stdout.str()
-
-    @pytest.mark.skipif(not hasattr(py.path.local, 'mksymlinkto'),
-                        reason="symlink not available on this platform")
-    def test_chdir(self, testdir):
-        testdir.tmpdir.join("py").mksymlinkto(py._pydir)
-        p = testdir.tmpdir.join("main.py")
-        p.write(_pytest._code.Source("""
-            import sys, os
-            sys.path.insert(0, '')
-            import py
-            print (py.__file__)
-            print (py.__path__)
-            os.chdir(os.path.dirname(os.getcwd()))
-            print (py.log)
-        """))
-        result = testdir.runpython(p)
-        assert not result.ret
-
-    def test_issue109_sibling_conftests_not_loaded(self, testdir):
-        sub1 = testdir.tmpdir.mkdir("sub1")
-        sub2 = testdir.tmpdir.mkdir("sub2")
-        sub1.join("conftest.py").write("assert 0")
-        result = testdir.runpytest(sub2)
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        sub2.ensure("__init__.py")
-        p = sub2.ensure("test_hello.py")
-        result = testdir.runpytest(p)
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        result = testdir.runpytest(sub1)
-        assert result.ret == EXIT_USAGEERROR
-
-    def test_directory_skipped(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            def pytest_ignore_collect():
-                pytest.skip("intentional")
-        """)
-        testdir.makepyfile("def test_hello(): pass")
-        result = testdir.runpytest()
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        result.stdout.fnmatch_lines([
-            "*1 skipped*"
-        ])
-
-    def test_multiple_items_per_collector_byid(self, testdir):
-        c = testdir.makeconftest("""
-            import pytest
-            class MyItem(pytest.Item):
-                def runtest(self):
-                    pass
-            class MyCollector(pytest.File):
-                def collect(self):
-                    return [MyItem(name="xyz", parent=self)]
-            def pytest_collect_file(path, parent):
-                if path.basename.startswith("conftest"):
-                    return MyCollector(path, parent)
-        """)
-        result = testdir.runpytest(c.basename+"::"+"xyz")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*1 pass*",
-        ])
-
-    def test_skip_on_generated_funcarg_id(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall({'x': 3}, id='hello-123')
-            def pytest_runtest_setup(item):
-                print (item.keywords)
-                if 'hello-123' in item.keywords:
-                    pytest.skip("hello")
-                assert 0
-        """)
-        p = testdir.makepyfile("""def test_func(x): pass""")
-        res = testdir.runpytest(p)
-        assert res.ret == 0
-        res.stdout.fnmatch_lines(["*1 skipped*"])
-
-    def test_direct_addressing_selects(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall({'i': 1}, id="1")
-                metafunc.addcall({'i': 2}, id="2")
-            def test_func(i):
-                pass
-        """)
-        res = testdir.runpytest(p.basename + "::" + "test_func[1]")
-        assert res.ret == 0
-        res.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_direct_addressing_notfound(self, testdir):
-        p = testdir.makepyfile("""
-            def test_func():
-                pass
-        """)
-        res = testdir.runpytest(p.basename + "::" + "test_notfound")
-        assert res.ret
-        res.stderr.fnmatch_lines(["*ERROR*not found*"])
-
-    def test_docstring_on_hookspec(self):
-        from _pytest import hookspec
-        for name, value in vars(hookspec).items():
-            if name.startswith("pytest_"):
-                assert value.__doc__, "no docstring for %s" % name
-
-    def test_initialization_error_issue49(self, testdir):
-        testdir.makeconftest("""
-            def pytest_configure():
-                x
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 3 # internal error
-        result.stderr.fnmatch_lines([
-            "INTERNAL*pytest_configure*",
-            "INTERNAL*x*",
-        ])
-        assert 'sessionstarttime' not in result.stderr.str()
-
-    @pytest.mark.parametrize('lookfor', ['test_fun.py', 'test_fun.py::test_a'])
-    def test_issue134_report_syntaxerror_when_collecting_member(self, testdir, lookfor):
-        testdir.makepyfile(test_fun="""
-            def test_a():
-                pass
-            def""")
-        result = testdir.runpytest(lookfor)
-        result.stdout.fnmatch_lines(['*SyntaxError*'])
-        if '::' in lookfor:
-            result.stderr.fnmatch_lines([
-                '*ERROR*',
-            ])
-            assert result.ret == 4  # usage error only if item not found
-
-    def test_report_all_failed_collections_initargs(self, testdir):
-        testdir.makepyfile(test_a="def", test_b="def")
-        result = testdir.runpytest("test_a.py::a", "test_b.py::b")
-        result.stderr.fnmatch_lines([
-            "*ERROR*test_a.py::a*",
-            "*ERROR*test_b.py::b*",
-        ])
-
-    def test_namespace_import_doesnt_confuse_import_hook(self, testdir):
-        # Ref #383. Python 3.3's namespace package messed with our import hooks
-        # Importing a module that didn't exist, even if the ImportError was
-        # gracefully handled, would make our test crash.
-        testdir.mkdir('not_a_package')
-        p = testdir.makepyfile("""
-            try:
-                from not_a_package import doesnt_exist
-            except ImportError:
-                # We handle the import error gracefully here
-                pass
-
-            def test_whatever():
-                pass
-        """)
-        res = testdir.runpytest(p.basename)
-        assert res.ret == 0
-
-    def test_unknown_option(self, testdir):
-        result = testdir.runpytest("--qwlkej")
-        result.stderr.fnmatch_lines("""
-            *unrecognized*
-        """)
-
-    def test_getsourcelines_error_issue553(self, testdir, monkeypatch):
-        monkeypatch.setattr("inspect.getsourcelines", None)
-        p = testdir.makepyfile("""
-            def raise_error(obj):
-                raise IOError('source code not available')
-
-            import inspect
-            inspect.getsourcelines = raise_error
-
-            def test_foo(invalid_fixture):
-                pass
-        """)
-        res = testdir.runpytest(p)
-        res.stdout.fnmatch_lines([
-            "*source code not available*",
-            "*fixture 'invalid_fixture' not found",
-        ])
-
-    def test_plugins_given_as_strings(self, tmpdir, monkeypatch):
-        """test that str values passed to main() as `plugins` arg
-        are interpreted as module names to be imported and registered.
-        #855.
-        """
-        with pytest.raises(ImportError) as excinfo:
-            pytest.main([str(tmpdir)], plugins=['invalid.module'])
-        assert 'invalid' in str(excinfo.value)
-
-        p = tmpdir.join('test_test_plugins_given_as_strings.py')
-        p.write('def test_foo(): pass')
-        mod = py.std.types.ModuleType("myplugin")
-        monkeypatch.setitem(sys.modules, 'myplugin', mod)
-        assert pytest.main(args=[str(tmpdir)], plugins=['myplugin']) == 0
-
-    def test_parameterized_with_bytes_regex(self, testdir):
-        p = testdir.makepyfile("""
-            import re
-            import pytest
-            @pytest.mark.parametrize('r', [re.compile(b'foo')])
-            def test_stuff(r):
-                pass
-        """
-        )
-        res = testdir.runpytest(p)
-        res.stdout.fnmatch_lines([
-            '*1 passed*'
-        ])
-
-
-class TestInvocationVariants:
-    def test_earlyinit(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            assert hasattr(pytest, 'mark')
-        """)
-        result = testdir.runpython(p)
-        assert result.ret == 0
-
-    @pytest.mark.xfail("sys.platform.startswith('java')")
-    def test_pydoc(self, testdir):
-        for name in ('py.test', 'pytest'):
-            result = testdir.runpython_c("import %s;help(%s)" % (name, name))
-            assert result.ret == 0
-            s = result.stdout.str()
-            assert 'MarkGenerator' in s
-
-    def test_import_star_py_dot_test(self, testdir):
-        p = testdir.makepyfile("""
-            from py.test import *
-            #collect
-            #cmdline
-            #Item
-            #assert collect.Item is Item
-            #assert collect.Collector is Collector
-            main
-            skip
-            xfail
-        """)
-        result = testdir.runpython(p)
-        assert result.ret == 0
-
-    def test_import_star_pytest(self, testdir):
-        p = testdir.makepyfile("""
-            from pytest import *
-            #Item
-            #File
-            main
-            skip
-            xfail
-        """)
-        result = testdir.runpython(p)
-        assert result.ret == 0
-
-    def test_double_pytestcmdline(self, testdir):
-        p = testdir.makepyfile(run="""
-            import pytest
-            pytest.main()
-            pytest.main()
-        """)
-        testdir.makepyfile("""
-            def test_hello():
-                pass
-        """)
-        result = testdir.runpython(p)
-        result.stdout.fnmatch_lines([
-            "*1 passed*",
-            "*1 passed*",
-        ])
-
-    def test_python_minus_m_invocation_ok(self, testdir):
-        p1 = testdir.makepyfile("def test_hello(): pass")
-        res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
-        assert res.ret == 0
-
-    def test_python_minus_m_invocation_fail(self, testdir):
-        p1 = testdir.makepyfile("def test_fail(): 0/0")
-        res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
-        assert res.ret == 1
-
-    def test_python_pytest_package(self, testdir):
-        p1 = testdir.makepyfile("def test_pass(): pass")
-        res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
-        assert res.ret == 0
-        res.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_equivalence_pytest_pytest(self):
-        assert pytest.main == py.test.cmdline.main
-
-    def test_invoke_with_string(self, capsys):
-        retcode = pytest.main("-h")
-        assert not retcode
-        out, err = capsys.readouterr()
-        assert "--help" in out
-        pytest.raises(ValueError, lambda: pytest.main(0))
-
-    def test_invoke_with_path(self, tmpdir, capsys):
-        retcode = pytest.main(tmpdir)
-        assert retcode == EXIT_NOTESTSCOLLECTED
-        out, err = capsys.readouterr()
-
-    def test_invoke_plugin_api(self, testdir, capsys):
-        class MyPlugin:
-            def pytest_addoption(self, parser):
-                parser.addoption("--myopt")
-
-        pytest.main(["-h"], plugins=[MyPlugin()])
-        out, err = capsys.readouterr()
-        assert "--myopt" in out
-
-    def test_pyargs_importerror(self, testdir, monkeypatch):
-        monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
-        path = testdir.mkpydir("tpkg")
-        path.join("test_hello.py").write('raise ImportError')
-
-        result = testdir.runpytest("--pyargs", "tpkg.test_hello")
-        assert result.ret != 0
-        # FIXME: It would be more natural to match NOT
-        # "ERROR*file*or*package*not*found*".
-        result.stdout.fnmatch_lines([
-            "*collected 0 items*"
-        ])
-
-    def test_cmdline_python_package(self, testdir, monkeypatch):
-        monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
-        path = testdir.mkpydir("tpkg")
-        path.join("test_hello.py").write("def test_hello(): pass")
-        path.join("test_world.py").write("def test_world(): pass")
-        result = testdir.runpytest("--pyargs", "tpkg")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*2 passed*"
-        ])
-        result = testdir.runpytest("--pyargs", "tpkg.test_hello")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*1 passed*"
-        ])
-
-        def join_pythonpath(what):
-            cur = py.std.os.environ.get('PYTHONPATH')
-            if cur:
-                return str(what) + ':' + cur
-            return what
-        empty_package = testdir.mkpydir("empty_package")
-        monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package))
-        result = testdir.runpytest("--pyargs", ".")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*2 passed*"
-        ])
-
-        monkeypatch.setenv('PYTHONPATH', join_pythonpath(testdir))
-        path.join('test_hello.py').remove()
-        result = testdir.runpytest("--pyargs", "tpkg.test_hello")
-        assert result.ret != 0
-        result.stderr.fnmatch_lines([
-            "*not*found*test_hello*",
-        ])
-
-    def test_cmdline_python_package_not_exists(self, testdir):
-        result = testdir.runpytest("--pyargs", "tpkgwhatv")
-        assert result.ret
-        result.stderr.fnmatch_lines([
-            "ERROR*file*or*package*not*found*",
-        ])
-
-    @pytest.mark.xfail(reason="decide: feature or bug")
-    def test_noclass_discovery_if_not_testcase(self, testdir):
-        testpath = testdir.makepyfile("""
-            import unittest
-            class TestHello(object):
-                def test_hello(self):
-                    assert self.attr
-
-            class RealTest(unittest.TestCase, TestHello):
-                attr = 42
-        """)
-        reprec = testdir.inline_run(testpath)
-        reprec.assertoutcome(passed=1)
-
-    def test_doctest_id(self, testdir):
-        testdir.makefile('.txt', """
-            >>> x=3
-            >>> x
-            4
-        """)
-        result = testdir.runpytest("-rf")
-        lines = result.stdout.str().splitlines()
-        for line in lines:
-            if line.startswith("FAIL "):
-                testid = line[5:].strip()
-                break
-        result = testdir.runpytest(testid, '-rf')
-        result.stdout.fnmatch_lines([
-            line,
-            "*1 failed*",
-        ])
-
-    def test_core_backward_compatibility(self):
-        """Test backward compatibility for get_plugin_manager function. See #787."""
-        import _pytest.config
-        assert type(_pytest.config.get_plugin_manager()) is _pytest.config.PytestPluginManager
-
-
-    def test_has_plugin(self, request):
-        """Test hasplugin function of the plugin manager (#932)."""
-        assert request.config.pluginmanager.hasplugin('python')
-
-
-class TestDurations:
-    source = """
-        import time
-        frag = 0.002
-        def test_something():
-            pass
-        def test_2():
-            time.sleep(frag*5)
-        def test_1():
-            time.sleep(frag)
-        def test_3():
-            time.sleep(frag*10)
-    """
-
-    def test_calls(self, testdir):
-        testdir.makepyfile(self.source)
-        result = testdir.runpytest("--durations=10")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines_random([
-            "*durations*",
-            "*call*test_3*",
-            "*call*test_2*",
-            "*call*test_1*",
-        ])
-
-    def test_calls_show_2(self, testdir):
-        testdir.makepyfile(self.source)
-        result = testdir.runpytest("--durations=2")
-        assert result.ret == 0
-        lines = result.stdout.get_lines_after("*slowest*durations*")
-        assert "4 passed" in lines[2]
-
-    def test_calls_showall(self, testdir):
-        testdir.makepyfile(self.source)
-        result = testdir.runpytest("--durations=0")
-        assert result.ret == 0
-        for x in "123":
-            for y in 'call',: #'setup', 'call', 'teardown':
-                for line in result.stdout.lines:
-                    if ("test_%s" % x) in line and y in line:
-                        break
-                else:
-                    raise AssertionError("not found %s %s" % (x,y))
-
-    def test_with_deselected(self, testdir):
-        testdir.makepyfile(self.source)
-        result = testdir.runpytest("--durations=2", "-k test_1")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*durations*",
-            "*call*test_1*",
-        ])
-
-    def test_with_failing_collection(self, testdir):
-        testdir.makepyfile(self.source)
-        testdir.makepyfile(test_collecterror="""xyz""")
-        result = testdir.runpytest("--durations=2", "-k test_1")
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "*durations*",
-            "*call*test_1*",
-        ])
-
-
-class TestDurationWithFixture:
-    source = """
-        import time
-        frag = 0.001
-        def setup_function(func):
-            time.sleep(frag * 3)
-        def test_1():
-            time.sleep(frag*2)
-        def test_2():
-            time.sleep(frag)
-    """
-    def test_setup_function(self, testdir):
-        testdir.makepyfile(self.source)
-        result = testdir.runpytest("--durations=10")
-        assert result.ret == 0
-
-        result.stdout.fnmatch_lines_random("""
-            *durations*
-            * setup *test_1*
-            * call *test_1*
-        """)
-
diff --git a/tools/pytest/testing/code/test_code.py b/tools/pytest/testing/code/test_code.py
deleted file mode 100644
index 0db4ad2..0000000
--- a/tools/pytest/testing/code/test_code.py
+++ /dev/null
@@ -1,174 +0,0 @@
-import sys
-
-import _pytest._code
-import py
-import pytest
-
-
-def test_ne():
-    code1 = _pytest._code.Code(compile('foo = "bar"', '', 'exec'))
-    assert code1 == code1
-    code2 = _pytest._code.Code(compile('foo = "baz"', '', 'exec'))
-    assert code2 != code1
-
-def test_code_gives_back_name_for_not_existing_file():
-    name = 'abc-123'
-    co_code = compile("pass\n", name, 'exec')
-    assert co_code.co_filename == name
-    code = _pytest._code.Code(co_code)
-    assert str(code.path) == name
-    assert code.fullsource is None
-
-def test_code_with_class():
-    class A:
-        pass
-    pytest.raises(TypeError, "_pytest._code.Code(A)")
-
-if True:
-    def x():
-        pass
-
-def test_code_fullsource():
-    code = _pytest._code.Code(x)
-    full = code.fullsource
-    assert 'test_code_fullsource()' in str(full)
-
-def test_code_source():
-    code = _pytest._code.Code(x)
-    src = code.source()
-    expected = """def x():
-    pass"""
-    assert str(src) == expected
-
-def test_frame_getsourcelineno_myself():
-    def func():
-        return sys._getframe(0)
-    f = func()
-    f = _pytest._code.Frame(f)
-    source, lineno = f.code.fullsource, f.lineno
-    assert source[lineno].startswith("        return sys._getframe(0)")
-
-def test_getstatement_empty_fullsource():
-    def func():
-        return sys._getframe(0)
-    f = func()
-    f = _pytest._code.Frame(f)
-    prop = f.code.__class__.fullsource
-    try:
-        f.code.__class__.fullsource = None
-        assert f.statement == _pytest._code.Source("")
-    finally:
-        f.code.__class__.fullsource = prop
-
-def test_code_from_func():
-    co = _pytest._code.Code(test_frame_getsourcelineno_myself)
-    assert co.firstlineno
-    assert co.path
-
-
-
-def test_builtin_patch_unpatch(monkeypatch):
-    cpy_builtin = py.builtin.builtins
-    comp = cpy_builtin.compile
-    def mycompile(*args, **kwargs):
-        return comp(*args, **kwargs)
-    class Sub(AssertionError):
-        pass
-    monkeypatch.setattr(cpy_builtin, 'AssertionError', Sub)
-    monkeypatch.setattr(cpy_builtin, 'compile', mycompile)
-    _pytest._code.patch_builtins()
-    assert cpy_builtin.AssertionError != Sub
-    assert cpy_builtin.compile != mycompile
-    _pytest._code.unpatch_builtins()
-    assert cpy_builtin.AssertionError is Sub
-    assert cpy_builtin.compile == mycompile
-
-
-def test_unicode_handling():
-    value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
-    def f():
-        raise Exception(value)
-    excinfo = pytest.raises(Exception, f)
-    str(excinfo)
-    if sys.version_info[0] < 3:
-        unicode(excinfo)
-
-
-@pytest.mark.skipif(sys.version_info[0] >= 3, reason='python 2 only issue')
-def test_unicode_handling_syntax_error():
-    value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
-    def f():
-        raise SyntaxError('invalid syntax', (None, 1, 3, value))
-    excinfo = pytest.raises(Exception, f)
-    str(excinfo)
-    if sys.version_info[0] < 3:
-        unicode(excinfo)
-
-def test_code_getargs():
-    def f1(x):
-        pass
-    c1 = _pytest._code.Code(f1)
-    assert c1.getargs(var=True) == ('x',)
-
-    def f2(x, *y):
-        pass
-    c2 = _pytest._code.Code(f2)
-    assert c2.getargs(var=True) == ('x', 'y')
-
-    def f3(x, **z):
-        pass
-    c3 = _pytest._code.Code(f3)
-    assert c3.getargs(var=True) == ('x', 'z')
-
-    def f4(x, *y, **z):
-        pass
-    c4 = _pytest._code.Code(f4)
-    assert c4.getargs(var=True) == ('x', 'y', 'z')
-
-
-def test_frame_getargs():
-    def f1(x):
-        return sys._getframe(0)
-    fr1 = _pytest._code.Frame(f1('a'))
-    assert fr1.getargs(var=True) == [('x', 'a')]
-
-    def f2(x, *y):
-        return sys._getframe(0)
-    fr2 = _pytest._code.Frame(f2('a', 'b', 'c'))
-    assert fr2.getargs(var=True) == [('x', 'a'), ('y', ('b', 'c'))]
-
-    def f3(x, **z):
-        return sys._getframe(0)
-    fr3 = _pytest._code.Frame(f3('a', b='c'))
-    assert fr3.getargs(var=True) == [('x', 'a'), ('z', {'b': 'c'})]
-
-    def f4(x, *y, **z):
-        return sys._getframe(0)
-    fr4 = _pytest._code.Frame(f4('a', 'b', c='d'))
-    assert fr4.getargs(var=True) == [('x', 'a'), ('y', ('b',)),
-                                     ('z', {'c': 'd'})]
-
-
-class TestExceptionInfo:
-
-    def test_bad_getsource(self):
-        try:
-            if False: pass
-            else: assert False
-        except AssertionError:
-            exci = _pytest._code.ExceptionInfo()
-        assert exci.getrepr()
-
-
-class TestTracebackEntry:
-
-    def test_getsource(self):
-        try:
-            if False: pass
-            else: assert False
-        except AssertionError:
-            exci = _pytest._code.ExceptionInfo()
-        entry = exci.traceback[0]
-        source = entry.getsource()
-        assert len(source) == 4
-        assert 'else: assert False' in source[3]
diff --git a/tools/pytest/testing/code/test_excinfo.py b/tools/pytest/testing/code/test_excinfo.py
deleted file mode 100644
index 2defa31..0000000
--- a/tools/pytest/testing/code/test_excinfo.py
+++ /dev/null
@@ -1,911 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import _pytest
-import py
-import pytest
-from _pytest._code.code import FormattedExcinfo, ReprExceptionInfo
-
-queue = py.builtin._tryimport('queue', 'Queue')
-
-failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
-from test_source import astonly
-
-try:
-    import importlib
-except ImportError:
-    invalidate_import_caches = None
-else:
-    invalidate_import_caches = getattr(importlib, "invalidate_caches", None)
-
-import pytest
-pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
-
-class TWMock:
-    def __init__(self):
-        self.lines = []
-    def sep(self, sep, line=None):
-        self.lines.append((sep, line))
-    def line(self, line, **kw):
-        self.lines.append(line)
-    def markup(self, text, **kw):
-        return text
-
-    fullwidth = 80
-
-def test_excinfo_simple():
-    try:
-        raise ValueError
-    except ValueError:
-        info = _pytest._code.ExceptionInfo()
-    assert info.type == ValueError
-
-def test_excinfo_getstatement():
-    def g():
-        raise ValueError
-    def f():
-        g()
-    try:
-        f()
-    except ValueError:
-        excinfo = _pytest._code.ExceptionInfo()
-    linenumbers = [_pytest._code.getrawcode(f).co_firstlineno - 1 + 3,
-                   _pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
-                   _pytest._code.getrawcode(g).co_firstlineno - 1 + 1, ]
-    l = list(excinfo.traceback)
-    foundlinenumbers = [x.lineno for x in l]
-    assert foundlinenumbers == linenumbers
-    #for x in info:
-    #    print "%s:%d  %s" %(x.path.relto(root), x.lineno, x.statement)
-    #xxx
-
-# testchain for getentries test below
-def f():
-    #
-    raise ValueError
-    #
-def g():
-    #
-    __tracebackhide__ = True
-    f()
-    #
-def h():
-    #
-    g()
-    #
-
-class TestTraceback_f_g_h:
-    def setup_method(self, method):
-        try:
-            h()
-        except ValueError:
-            self.excinfo = _pytest._code.ExceptionInfo()
-
-    def test_traceback_entries(self):
-        tb = self.excinfo.traceback
-        entries = list(tb)
-        assert len(tb) == 4 # maybe fragile test
-        assert len(entries) == 4 # maybe fragile test
-        names = ['f', 'g', 'h']
-        for entry in entries:
-            try:
-                names.remove(entry.frame.code.name)
-            except ValueError:
-                pass
-        assert not names
-
-    def test_traceback_entry_getsource(self):
-        tb = self.excinfo.traceback
-        s = str(tb[-1].getsource() )
-        assert s.startswith("def f():")
-        assert s.endswith("raise ValueError")
-
-    @astonly
-    @failsonjython
-    def test_traceback_entry_getsource_in_construct(self):
-        source = _pytest._code.Source("""\
-            def xyz():
-                try:
-                    raise ValueError
-                except somenoname:
-                    pass
-            xyz()
-        """)
-        try:
-            exec (source.compile())
-        except NameError:
-            tb = _pytest._code.ExceptionInfo().traceback
-            print (tb[-1].getsource())
-            s = str(tb[-1].getsource())
-            assert s.startswith("def xyz():\n    try:")
-            assert s.strip().endswith("except somenoname:")
-
-    def test_traceback_cut(self):
-        co = _pytest._code.Code(f)
-        path, firstlineno = co.path, co.firstlineno
-        traceback = self.excinfo.traceback
-        newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
-        assert len(newtraceback) == 1
-        newtraceback = traceback.cut(path=path, lineno=firstlineno+2)
-        assert len(newtraceback) == 1
-
-    def test_traceback_cut_excludepath(self, testdir):
-        p = testdir.makepyfile("def f(): raise ValueError")
-        excinfo = pytest.raises(ValueError, "p.pyimport().f()")
-        basedir = py.path.local(pytest.__file__).dirpath()
-        newtraceback = excinfo.traceback.cut(excludepath=basedir)
-        for x in newtraceback:
-            if hasattr(x, 'path'):
-                assert not py.path.local(x.path).relto(basedir)
-        assert newtraceback[-1].frame.code.path == p
-
-    def test_traceback_filter(self):
-        traceback = self.excinfo.traceback
-        ntraceback = traceback.filter()
-        assert len(ntraceback) == len(traceback) - 1
-
-    def test_traceback_recursion_index(self):
-        def f(n):
-            if n < 10:
-                n += 1
-            f(n)
-        excinfo = pytest.raises(RuntimeError, f, 8)
-        traceback = excinfo.traceback
-        recindex = traceback.recursionindex()
-        assert recindex == 3
-
-    def test_traceback_only_specific_recursion_errors(self, monkeypatch):
-        def f(n):
-            if n == 0:
-                raise RuntimeError("hello")
-            f(n-1)
-
-        excinfo = pytest.raises(RuntimeError, f, 100)
-        monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
-        repr = excinfo.getrepr()
-        assert "RuntimeError: hello" in str(repr.reprcrash)
-
-    def test_traceback_no_recursion_index(self):
-        def do_stuff():
-            raise RuntimeError
-        def reraise_me():
-            import sys
-            exc, val, tb = sys.exc_info()
-            py.builtin._reraise(exc, val, tb)
-        def f(n):
-            try:
-                do_stuff()
-            except:
-                reraise_me()
-        excinfo = pytest.raises(RuntimeError, f, 8)
-        traceback = excinfo.traceback
-        recindex = traceback.recursionindex()
-        assert recindex is None
-
-    def test_traceback_messy_recursion(self):
-        #XXX: simplified locally testable version
-        decorator = pytest.importorskip('decorator').decorator
-
-        def log(f, *k, **kw):
-            print('%s %s' % (k, kw))
-            f(*k, **kw)
-        log = decorator(log)
-
-        def fail():
-            raise ValueError('')
-
-        fail = log(log(fail))
-
-        excinfo = pytest.raises(ValueError, fail)
-        assert excinfo.traceback.recursionindex() is None
-
-
-
-    def test_traceback_getcrashentry(self):
-        def i():
-            __tracebackhide__ = True
-            raise ValueError
-        def h():
-            i()
-        def g():
-            __tracebackhide__ = True
-            h()
-        def f():
-            g()
-
-        excinfo = pytest.raises(ValueError, f)
-        tb = excinfo.traceback
-        entry = tb.getcrashentry()
-        co = _pytest._code.Code(h)
-        assert entry.frame.code.path == co.path
-        assert entry.lineno == co.firstlineno + 1
-        assert entry.frame.code.name == 'h'
-
-    def test_traceback_getcrashentry_empty(self):
-        def g():
-            __tracebackhide__ = True
-            raise ValueError
-        def f():
-            __tracebackhide__ = True
-            g()
-
-        excinfo = pytest.raises(ValueError, f)
-        tb = excinfo.traceback
-        entry = tb.getcrashentry()
-        co = _pytest._code.Code(g)
-        assert entry.frame.code.path == co.path
-        assert entry.lineno == co.firstlineno + 2
-        assert entry.frame.code.name == 'g'
-
-def hello(x):
-    x + 5
-
-def test_tbentry_reinterpret():
-    try:
-        hello("hello")
-    except TypeError:
-        excinfo = _pytest._code.ExceptionInfo()
-    tbentry = excinfo.traceback[-1]
-    msg = tbentry.reinterpret()
-    assert msg.startswith("TypeError: ('hello' + 5)")
-
-def test_excinfo_exconly():
-    excinfo = pytest.raises(ValueError, h)
-    assert excinfo.exconly().startswith('ValueError')
-    excinfo = pytest.raises(ValueError,
-        "raise ValueError('hello\\nworld')")
-    msg = excinfo.exconly(tryshort=True)
-    assert msg.startswith('ValueError')
-    assert msg.endswith("world")
-
-def test_excinfo_repr():
-    excinfo = pytest.raises(ValueError, h)
-    s = repr(excinfo)
-    assert s == "<ExceptionInfo ValueError tblen=4>"
-
-def test_excinfo_str():
-    excinfo = pytest.raises(ValueError, h)
-    s = str(excinfo)
-    assert s.startswith(__file__[:-9]) # pyc file and $py.class
-    assert s.endswith("ValueError")
-    assert len(s.split(":")) >= 3 # on windows it's 4
-
-def test_excinfo_errisinstance():
-    excinfo = pytest.raises(ValueError, h)
-    assert excinfo.errisinstance(ValueError)
-
-def test_excinfo_no_sourcecode():
-    try:
-        exec ("raise ValueError()")
-    except ValueError:
-        excinfo = _pytest._code.ExceptionInfo()
-    s = str(excinfo.traceback[-1])
-    if py.std.sys.version_info < (2,5):
-        assert s == "  File '<string>':1 in ?\n  ???\n"
-    else:
-        assert s == "  File '<string>':1 in <module>\n  ???\n"
-
-def test_excinfo_no_python_sourcecode(tmpdir):
-    #XXX: simplified locally testable version
-    tmpdir.join('test.txt').write("{{ h()}}:")
-
-    jinja2 = pytest.importorskip('jinja2')
-    loader = jinja2.FileSystemLoader(str(tmpdir))
-    env = jinja2.Environment(loader=loader)
-    template = env.get_template('test.txt')
-    excinfo = pytest.raises(ValueError,
-                             template.render, h=h)
-    for item in excinfo.traceback:
-        print(item) #XXX: for some reason jinja.Template.render is printed in full
-        item.source # shouldnt fail
-        if item.path.basename == 'test.txt':
-            assert str(item.source) == '{{ h()}}:'
-
-
-def test_entrysource_Queue_example():
-    try:
-        queue.Queue().get(timeout=0.001)
-    except queue.Empty:
-        excinfo = _pytest._code.ExceptionInfo()
-    entry = excinfo.traceback[-1]
-    source = entry.getsource()
-    assert source is not None
-    s = str(source).strip()
-    assert s.startswith("def get")
-
-def test_codepath_Queue_example():
-    try:
-        queue.Queue().get(timeout=0.001)
-    except queue.Empty:
-        excinfo = _pytest._code.ExceptionInfo()
-    entry = excinfo.traceback[-1]
-    path = entry.path
-    assert isinstance(path, py.path.local)
-    assert path.basename.lower() == "queue.py"
-    assert path.check()
-
-class TestFormattedExcinfo:
-    def pytest_funcarg__importasmod(self, request):
-        def importasmod(source):
-            source = _pytest._code.Source(source)
-            tmpdir = request.getfuncargvalue("tmpdir")
-            modpath = tmpdir.join("mod.py")
-            tmpdir.ensure("__init__.py")
-            modpath.write(source)
-            if invalidate_import_caches is not None:
-                invalidate_import_caches()
-            return modpath.pyimport()
-        return importasmod
-
-    def excinfo_from_exec(self, source):
-        source = _pytest._code.Source(source).strip()
-        try:
-            exec (source.compile())
-        except KeyboardInterrupt:
-            raise
-        except:
-            return _pytest._code.ExceptionInfo()
-        assert 0, "did not raise"
-
-    def test_repr_source(self):
-        pr = FormattedExcinfo()
-        source = _pytest._code.Source("""
-            def f(x):
-                pass
-        """).strip()
-        pr.flow_marker = "|"
-        lines = pr.get_source(source, 0)
-        assert len(lines) == 2
-        assert lines[0] == "|   def f(x):"
-        assert lines[1] == "        pass"
-
-    def test_repr_source_excinfo(self):
-        """ check if indentation is right """
-        pr = FormattedExcinfo()
-        excinfo = self.excinfo_from_exec("""
-                def f():
-                    assert 0
-                f()
-        """)
-        pr = FormattedExcinfo()
-        source = pr._getentrysource(excinfo.traceback[-1])
-        lines = pr.get_source(source, 1, excinfo)
-        assert lines == [
-            '    def f():',
-            '>       assert 0',
-            'E       assert 0'
-        ]
-
-
-    def test_repr_source_not_existing(self):
-        pr = FormattedExcinfo()
-        co = compile("raise ValueError()", "", "exec")
-        try:
-            exec (co)
-        except ValueError:
-            excinfo = _pytest._code.ExceptionInfo()
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
-
-    def test_repr_many_line_source_not_existing(self):
-        pr = FormattedExcinfo()
-        co = compile("""
-a = 1
-raise ValueError()
-""", "", "exec")
-        try:
-            exec (co)
-        except ValueError:
-            excinfo = _pytest._code.ExceptionInfo()
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
-
-    def test_repr_source_failing_fullsource(self):
-        pr = FormattedExcinfo()
-
-        class FakeCode(object):
-            class raw:
-                co_filename = '?'
-            path = '?'
-            firstlineno = 5
-
-            def fullsource(self):
-                return None
-            fullsource = property(fullsource)
-
-        class FakeFrame(object):
-            code = FakeCode()
-            f_locals = {}
-            f_globals = {}
-
-        class FakeTracebackEntry(_pytest._code.Traceback.Entry):
-            def __init__(self, tb):
-                self.lineno = 5+3
-
-            @property
-            def frame(self):
-                return FakeFrame()
-
-        class Traceback(_pytest._code.Traceback):
-            Entry = FakeTracebackEntry
-
-        class FakeExcinfo(_pytest._code.ExceptionInfo):
-            typename = "Foo"
-            def __init__(self):
-                pass
-
-            def exconly(self, tryshort):
-                return "EXC"
-            def errisinstance(self, cls):
-                return False
-
-        excinfo = FakeExcinfo()
-        class FakeRawTB(object):
-            tb_next = None
-        tb = FakeRawTB()
-        excinfo.traceback = Traceback(tb)
-
-        fail = IOError()  # noqa
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
-
-        fail = py.error.ENOENT  # noqa
-        repr = pr.repr_excinfo(excinfo)
-        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
-
-
-    def test_repr_local(self):
-        p = FormattedExcinfo(showlocals=True)
-        loc = {'y': 5, 'z': 7, 'x': 3, '@x': 2, '__builtins__': {}}
-        reprlocals = p.repr_locals(loc)
-        assert reprlocals.lines
-        assert reprlocals.lines[0] == '__builtins__ = <builtins>'
-        assert reprlocals.lines[1] == 'x          = 3'
-        assert reprlocals.lines[2] == 'y          = 5'
-        assert reprlocals.lines[3] == 'z          = 7'
-
-    def test_repr_tracebackentry_lines(self, importasmod):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello\\nworld")
-        """)
-        excinfo = pytest.raises(ValueError, mod.func1)
-        excinfo.traceback = excinfo.traceback.filter()
-        p = FormattedExcinfo()
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
-
-        # test as intermittent entry
-        lines = reprtb.lines
-        assert lines[0] == '    def func1():'
-        assert lines[1] == '>       raise ValueError("hello\\nworld")'
-
-        # test as last entry
-        p = FormattedExcinfo(showlocals=True)
-        repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = repr_entry.lines
-        assert lines[0] == '    def func1():'
-        assert lines[1] == '>       raise ValueError("hello\\nworld")'
-        assert lines[2] == 'E       ValueError: hello'
-        assert lines[3] == 'E       world'
-        assert not lines[4:]
-
-        loc = repr_entry.reprlocals is not None
-        loc = repr_entry.reprfileloc
-        assert loc.path == mod.__file__
-        assert loc.lineno == 3
-        #assert loc.message == "ValueError: hello"
-
-    def test_repr_tracebackentry_lines2(self, importasmod):
-        mod = importasmod("""
-            def func1(m, x, y, z):
-                raise ValueError("hello\\nworld")
-        """)
-        excinfo = pytest.raises(ValueError, mod.func1, "m"*90, 5, 13, "z"*120)
-        excinfo.traceback = excinfo.traceback.filter()
-        entry = excinfo.traceback[-1]
-        p = FormattedExcinfo(funcargs=True)
-        reprfuncargs = p.repr_args(entry)
-        assert reprfuncargs.args[0] == ('m', repr("m"*90))
-        assert reprfuncargs.args[1] == ('x', '5')
-        assert reprfuncargs.args[2] == ('y', '13')
-        assert reprfuncargs.args[3] == ('z', repr("z" * 120))
-
-        p = FormattedExcinfo(funcargs=True)
-        repr_entry = p.repr_traceback_entry(entry)
-        assert repr_entry.reprfuncargs.args == reprfuncargs.args
-        tw = TWMock()
-        repr_entry.toterminal(tw)
-        assert tw.lines[0] == "m = " + repr('m' * 90)
-        assert tw.lines[1] == "x = 5, y = 13"
-        assert tw.lines[2] == "z = " + repr('z' * 120)
-
-    def test_repr_tracebackentry_lines_var_kw_args(self, importasmod):
-        mod = importasmod("""
-            def func1(x, *y, **z):
-                raise ValueError("hello\\nworld")
-        """)
-        excinfo = pytest.raises(ValueError, mod.func1, 'a', 'b', c='d')
-        excinfo.traceback = excinfo.traceback.filter()
-        entry = excinfo.traceback[-1]
-        p = FormattedExcinfo(funcargs=True)
-        reprfuncargs = p.repr_args(entry)
-        assert reprfuncargs.args[0] == ('x', repr('a'))
-        assert reprfuncargs.args[1] == ('y', repr(('b',)))
-        assert reprfuncargs.args[2] == ('z', repr({'c': 'd'}))
-
-        p = FormattedExcinfo(funcargs=True)
-        repr_entry = p.repr_traceback_entry(entry)
-        assert repr_entry.reprfuncargs.args == reprfuncargs.args
-        tw = TWMock()
-        repr_entry.toterminal(tw)
-        assert tw.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"
-
-    def test_repr_tracebackentry_short(self, importasmod):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello")
-            def entry():
-                func1()
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-        p = FormattedExcinfo(style="short")
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
-        lines = reprtb.lines
-        basename = py.path.local(mod.__file__).basename
-        assert lines[0] == '    func1()'
-        assert basename in str(reprtb.reprfileloc.path)
-        assert reprtb.reprfileloc.lineno == 5
-
-        # test last entry
-        p = FormattedExcinfo(style="short")
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = reprtb.lines
-        assert lines[0] == '    raise ValueError("hello")'
-        assert lines[1] == 'E   ValueError: hello'
-        assert basename in str(reprtb.reprfileloc.path)
-        assert reprtb.reprfileloc.lineno == 3
-
-    def test_repr_tracebackentry_no(self, importasmod):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello")
-            def entry():
-                func1()
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-        p = FormattedExcinfo(style="no")
-        p.repr_traceback_entry(excinfo.traceback[-2])
-
-        p = FormattedExcinfo(style="no")
-        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = reprentry.lines
-        assert lines[0] == 'E   ValueError: hello'
-        assert not lines[1:]
-
-    def test_repr_traceback_tbfilter(self, importasmod):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-        p = FormattedExcinfo(tbfilter=True)
-        reprtb = p.repr_traceback(excinfo)
-        assert len(reprtb.reprentries) == 2
-        p = FormattedExcinfo(tbfilter=False)
-        reprtb = p.repr_traceback(excinfo)
-        assert len(reprtb.reprentries) == 3
-
-    def test_traceback_short_no_source(self, importasmod, monkeypatch):
-        mod = importasmod("""
-            def func1():
-                raise ValueError("hello")
-            def entry():
-                func1()
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-        from _pytest._code.code import Code
-        monkeypatch.setattr(Code, 'path', 'bogus')
-        excinfo.traceback[0].frame.code.path = "bogus"
-        p = FormattedExcinfo(style="short")
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
-        lines = reprtb.lines
-        last_p = FormattedExcinfo(style="short")
-        last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        last_lines = last_reprtb.lines
-        monkeypatch.undo()
-        assert lines[0] == '    func1()'
-
-        assert last_lines[0] == '    raise ValueError("hello")'
-        assert last_lines[1] == 'E   ValueError: hello'
-
-    def test_repr_traceback_and_excinfo(self, importasmod):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-
-        for style in ("long", "short"):
-            p = FormattedExcinfo(style=style)
-            reprtb = p.repr_traceback(excinfo)
-            assert len(reprtb.reprentries) == 2
-            assert reprtb.style == style
-            assert not reprtb.extraline
-            repr = p.repr_excinfo(excinfo)
-            assert repr.reprtraceback
-            assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries)
-            assert repr.reprcrash.path.endswith("mod.py")
-            assert repr.reprcrash.message == "ValueError: 0"
-
-    def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-
-        p = FormattedExcinfo()
-        def raiseos():
-            raise OSError(2)
-        monkeypatch.setattr(py.std.os, 'getcwd', raiseos)
-        assert p._makepath(__file__) == __file__
-        p.repr_traceback(excinfo)
-
-    def test_repr_excinfo_addouterr(self, importasmod):
-        mod = importasmod("""
-            def entry():
-                raise ValueError()
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-        repr = excinfo.getrepr()
-        repr.addsection("title", "content")
-        twmock = TWMock()
-        repr.toterminal(twmock)
-        assert twmock.lines[-1] == "content"
-        assert twmock.lines[-2] == ("-", "title")
-
-    def test_repr_excinfo_reprcrash(self, importasmod):
-        mod = importasmod("""
-            def entry():
-                raise ValueError()
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-        repr = excinfo.getrepr()
-        assert repr.reprcrash.path.endswith("mod.py")
-        assert repr.reprcrash.lineno == 3
-        assert repr.reprcrash.message == "ValueError"
-        assert str(repr.reprcrash).endswith("mod.py:3: ValueError")
-
-    def test_repr_traceback_recursion(self, importasmod):
-        mod = importasmod("""
-            def rec2(x):
-                return rec1(x+1)
-            def rec1(x):
-                return rec2(x-1)
-            def entry():
-                rec1(42)
-        """)
-        excinfo = pytest.raises(RuntimeError, mod.entry)
-
-        for style in ("short", "long", "no"):
-            p = FormattedExcinfo(style="short")
-            reprtb = p.repr_traceback(excinfo)
-            assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
-            assert str(reprtb)
-
-    def test_tb_entry_AssertionError(self, importasmod):
-        # probably this test is a bit redundant
-        # as py/magic/testing/test_assertion.py
-        # already tests correctness of
-        # assertion-reinterpretation  logic
-        mod = importasmod("""
-            def somefunc():
-                x = 1
-                assert x == 2
-        """)
-        excinfo = pytest.raises(AssertionError, mod.somefunc)
-
-        p = FormattedExcinfo()
-        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        lines = reprentry.lines
-        assert lines[-1] == "E       assert 1 == 2"
-
-    def test_reprexcinfo_getrepr(self, importasmod):
-        mod = importasmod("""
-            def f(x):
-                raise ValueError(x)
-            def entry():
-                f(0)
-        """)
-        excinfo = pytest.raises(ValueError, mod.entry)
-
-        for style in ("short", "long", "no"):
-            for showlocals in (True, False):
-                repr = excinfo.getrepr(style=style, showlocals=showlocals)
-                assert isinstance(repr, ReprExceptionInfo)
-                assert repr.reprtraceback.style == style
-
-    def test_reprexcinfo_unicode(self):
-        from _pytest._code.code import TerminalRepr
-        class MyRepr(TerminalRepr):
-            def toterminal(self, tw):
-                tw.line(py.builtin._totext("я", "utf-8"))
-        x = py.builtin._totext(MyRepr())
-        assert x == py.builtin._totext("я", "utf-8")
-
-    def test_toterminal_long(self, importasmod):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = pytest.raises(ValueError, mod.f)
-        excinfo.traceback = excinfo.traceback.filter()
-        repr = excinfo.getrepr()
-        tw = TWMock()
-        repr.toterminal(tw)
-        assert tw.lines[0] == ""
-        tw.lines.pop(0)
-        assert tw.lines[0] == "    def f():"
-        assert tw.lines[1] == ">       g(3)"
-        assert tw.lines[2] == ""
-        assert tw.lines[3].endswith("mod.py:5: ")
-        assert tw.lines[4] == ("_ ", None)
-        assert tw.lines[5] == ""
-        assert tw.lines[6] == "    def g(x):"
-        assert tw.lines[7] == ">       raise ValueError(x)"
-        assert tw.lines[8] == "E       ValueError: 3"
-        assert tw.lines[9] == ""
-        assert tw.lines[10].endswith("mod.py:3: ValueError")
-
-    def test_toterminal_long_missing_source(self, importasmod, tmpdir):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = pytest.raises(ValueError, mod.f)
-        tmpdir.join('mod.py').remove()
-        excinfo.traceback = excinfo.traceback.filter()
-        repr = excinfo.getrepr()
-        tw = TWMock()
-        repr.toterminal(tw)
-        assert tw.lines[0] == ""
-        tw.lines.pop(0)
-        assert tw.lines[0] == ">   ???"
-        assert tw.lines[1] == ""
-        assert tw.lines[2].endswith("mod.py:5: ")
-        assert tw.lines[3] == ("_ ", None)
-        assert tw.lines[4] == ""
-        assert tw.lines[5] == ">   ???"
-        assert tw.lines[6] == "E   ValueError: 3"
-        assert tw.lines[7] == ""
-        assert tw.lines[8].endswith("mod.py:3: ValueError")
-
-    def test_toterminal_long_incomplete_source(self, importasmod, tmpdir):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = pytest.raises(ValueError, mod.f)
-        tmpdir.join('mod.py').write('asdf')
-        excinfo.traceback = excinfo.traceback.filter()
-        repr = excinfo.getrepr()
-        tw = TWMock()
-        repr.toterminal(tw)
-        assert tw.lines[0] == ""
-        tw.lines.pop(0)
-        assert tw.lines[0] == ">   ???"
-        assert tw.lines[1] == ""
-        assert tw.lines[2].endswith("mod.py:5: ")
-        assert tw.lines[3] == ("_ ", None)
-        assert tw.lines[4] == ""
-        assert tw.lines[5] == ">   ???"
-        assert tw.lines[6] == "E   ValueError: 3"
-        assert tw.lines[7] == ""
-        assert tw.lines[8].endswith("mod.py:3: ValueError")
-
-    def test_toterminal_long_filenames(self, importasmod):
-        mod = importasmod("""
-            def f():
-                raise ValueError()
-        """)
-        excinfo = pytest.raises(ValueError, mod.f)
-        tw = TWMock()
-        path = py.path.local(mod.__file__)
-        old = path.dirpath().chdir()
-        try:
-            repr = excinfo.getrepr(abspath=False)
-            repr.toterminal(tw)
-            line = tw.lines[-1]
-            x = py.path.local().bestrelpath(path)
-            if len(x) < len(str(path)):
-                assert line == "mod.py:3: ValueError"
-
-            repr = excinfo.getrepr(abspath=True)
-            repr.toterminal(tw)
-            line = tw.lines[-1]
-            assert line == "%s:3: ValueError" %(path,)
-        finally:
-            old.chdir()
-
-    @pytest.mark.parametrize('reproptions', [
-        {'style': style, 'showlocals': showlocals,
-         'funcargs': funcargs, 'tbfilter': tbfilter
-        } for style in ("long", "short", "no")
-            for showlocals in (True, False)
-                for tbfilter in (True, False)
-                    for funcargs in (True, False)])
-    def test_format_excinfo(self, importasmod, reproptions):
-        mod = importasmod("""
-            def g(x):
-                raise ValueError(x)
-            def f():
-                g(3)
-        """)
-        excinfo = pytest.raises(ValueError, mod.f)
-        tw = py.io.TerminalWriter(stringio=True)
-        repr = excinfo.getrepr(**reproptions)
-        repr.toterminal(tw)
-        assert tw.stringio.getvalue()
-
-
-    def test_native_style(self):
-        excinfo = self.excinfo_from_exec("""
-            assert 0
-        """)
-        repr = excinfo.getrepr(style='native')
-        assert "assert 0" in str(repr.reprcrash)
-        s = str(repr)
-        assert s.startswith('Traceback (most recent call last):\n  File')
-        assert s.endswith('\nAssertionError: assert 0')
-        assert 'exec (source.compile())' in s
-        # python 2.4 fails to get the source line for the assert
-        if py.std.sys.version_info >= (2, 5):
-            assert s.count('assert 0') == 2
-
-    def test_traceback_repr_style(self, importasmod):
-        mod = importasmod("""
-            def f():
-                g()
-            def g():
-                h()
-            def h():
-                i()
-            def i():
-                raise ValueError()
-        """)
-        excinfo = pytest.raises(ValueError, mod.f)
-        excinfo.traceback = excinfo.traceback.filter()
-        excinfo.traceback[1].set_repr_style("short")
-        excinfo.traceback[2].set_repr_style("short")
-        r = excinfo.getrepr(style="long")
-        tw = TWMock()
-        r.toterminal(tw)
-        for line in tw.lines: print (line)
-        assert tw.lines[0] == ""
-        assert tw.lines[1] == "    def f():"
-        assert tw.lines[2] == ">       g()"
-        assert tw.lines[3] == ""
-        assert tw.lines[4].endswith("mod.py:3: ")
-        assert tw.lines[5] == ("_ ", None)
-        assert tw.lines[6].endswith("in g")
-        assert tw.lines[7] == "    h()"
-        assert tw.lines[8].endswith("in h")
-        assert tw.lines[9] == "    i()"
-        assert tw.lines[10] == ("_ ", None)
-        assert tw.lines[11] == ""
-        assert tw.lines[12] == "    def i():"
-        assert tw.lines[13] == ">       raise ValueError()"
-        assert tw.lines[14] == "E       ValueError"
-        assert tw.lines[15] == ""
-        assert tw.lines[16].endswith("mod.py:9: ValueError")
diff --git a/tools/pytest/testing/code/test_source.py b/tools/pytest/testing/code/test_source.py
deleted file mode 100644
index 007ad14..0000000
--- a/tools/pytest/testing/code/test_source.py
+++ /dev/null
@@ -1,659 +0,0 @@
-# flake8: noqa
-# disable flake check on this file because some constructs are strange
-# or redundant on purpose and can't be disable on a line-by-line basis
-import sys
-
-import _pytest._code
-import py
-import pytest
-from _pytest._code import Source
-from _pytest._code.source import _ast
-
-if _ast is not None:
-    astonly = pytest.mark.nothing
-else:
-    astonly = pytest.mark.xfail("True", reason="only works with AST-compile")
-
-failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
-
-def test_source_str_function():
-    x = Source("3")
-    assert str(x) == "3"
-
-    x = Source("   3")
-    assert str(x) == "3"
-
-    x = Source("""
-        3
-    """, rstrip=False)
-    assert str(x) == "\n3\n    "
-
-    x = Source("""
-        3
-    """, rstrip=True)
-    assert str(x) == "\n3"
-
-def test_unicode():
-    try:
-        unicode
-    except NameError:
-        return
-    x = Source(unicode("4"))
-    assert str(x) == "4"
-    co = _pytest._code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval')
-    val = eval(co)
-    assert isinstance(val, unicode)
-
-def test_source_from_function():
-    source = _pytest._code.Source(test_source_str_function)
-    assert str(source).startswith('def test_source_str_function():')
-
-def test_source_from_method():
-    class TestClass:
-        def test_method(self):
-            pass
-    source = _pytest._code.Source(TestClass().test_method)
-    assert source.lines == ["def test_method(self):",
-                            "    pass"]
-
-def test_source_from_lines():
-    lines = ["a \n", "b\n", "c"]
-    source = _pytest._code.Source(lines)
-    assert source.lines == ['a ', 'b', 'c']
-
-def test_source_from_inner_function():
-    def f():
-        pass
-    source = _pytest._code.Source(f, deindent=False)
-    assert str(source).startswith('    def f():')
-    source = _pytest._code.Source(f)
-    assert str(source).startswith('def f():')
-
-def test_source_putaround_simple():
-    source = Source("raise ValueError")
-    source = source.putaround(
-        "try:", """\
-        except ValueError:
-            x = 42
-        else:
-            x = 23""")
-    assert str(source)=="""\
-try:
-    raise ValueError
-except ValueError:
-    x = 42
-else:
-    x = 23"""
-
-def test_source_putaround():
-    source = Source()
-    source = source.putaround("""
-        if 1:
-            x=1
-    """)
-    assert str(source).strip() == "if 1:\n    x=1"
-
-def test_source_strips():
-    source = Source("")
-    assert source == Source()
-    assert str(source) == ''
-    assert source.strip() == source
-
-def test_source_strip_multiline():
-    source = Source()
-    source.lines = ["", " hello", "  "]
-    source2 = source.strip()
-    assert source2.lines == [" hello"]
-
-def test_syntaxerror_rerepresentation():
-    ex = pytest.raises(SyntaxError, _pytest._code.compile, 'xyz xyz')
-    assert ex.value.lineno == 1
-    assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython?
-    assert ex.value.text.strip(), 'x x'
-
-def test_isparseable():
-    assert Source("hello").isparseable()
-    assert Source("if 1:\n  pass").isparseable()
-    assert Source(" \nif 1:\n  pass").isparseable()
-    assert not Source("if 1:\n").isparseable()
-    assert not Source(" \nif 1:\npass").isparseable()
-    assert not Source(chr(0)).isparseable()
-
-class TestAccesses:
-    source = Source("""\
-        def f(x):
-            pass
-        def g(x):
-            pass
-    """)
-    def test_getrange(self):
-        x = self.source[0:2]
-        assert x.isparseable()
-        assert len(x.lines) == 2
-        assert str(x) == "def f(x):\n    pass"
-
-    def test_getline(self):
-        x = self.source[0]
-        assert x == "def f(x):"
-
-    def test_len(self):
-        assert len(self.source) == 4
-
-    def test_iter(self):
-        l = [x for x in self.source]
-        assert len(l) == 4
-
-class TestSourceParsingAndCompiling:
-    source = Source("""\
-        def f(x):
-            assert (x ==
-                    3 +
-                    4)
-    """).strip()
-
-    def test_compile(self):
-        co = _pytest._code.compile("x=3")
-        d = {}
-        exec (co, d)
-        assert d['x'] == 3
-
-    def test_compile_and_getsource_simple(self):
-        co = _pytest._code.compile("x=3")
-        exec (co)
-        source = _pytest._code.Source(co)
-        assert str(source) == "x=3"
-
-    def test_compile_and_getsource_through_same_function(self):
-        def gensource(source):
-            return _pytest._code.compile(source)
-        co1 = gensource("""
-            def f():
-                raise KeyError()
-        """)
-        co2 = gensource("""
-            def f():
-                raise ValueError()
-        """)
-        source1 = py.std.inspect.getsource(co1)
-        assert 'KeyError' in source1
-        source2 = py.std.inspect.getsource(co2)
-        assert 'ValueError' in source2
-
-    def test_getstatement(self):
-        #print str(self.source)
-        ass = str(self.source[1:])
-        for i in range(1, 4):
-            #print "trying start in line %r" % self.source[i]
-            s = self.source.getstatement(i)
-            #x = s.deindent()
-            assert str(s) == ass
-
-    def test_getstatementrange_triple_quoted(self):
-        #print str(self.source)
-        source = Source("""hello('''
-        ''')""")
-        s = source.getstatement(0)
-        assert s == str(source)
-        s = source.getstatement(1)
-        assert s == str(source)
-
-    @astonly
-    def test_getstatementrange_within_constructs(self):
-        source = Source("""\
-            try:
-                try:
-                    raise ValueError
-                except SomeThing:
-                    pass
-            finally:
-                42
-        """)
-        assert len(source) == 7
-        # check all lineno's that could occur in a traceback
-        #assert source.getstatementrange(0) == (0, 7)
-        #assert source.getstatementrange(1) == (1, 5)
-        assert source.getstatementrange(2) == (2, 3)
-        assert source.getstatementrange(3) == (3, 4)
-        assert source.getstatementrange(4) == (4, 5)
-        #assert source.getstatementrange(5) == (0, 7)
-        assert source.getstatementrange(6) == (6, 7)
-
-    def test_getstatementrange_bug(self):
-        source = Source("""\
-            try:
-                x = (
-                   y +
-                   z)
-            except:
-                pass
-        """)
-        assert len(source) == 6
-        assert source.getstatementrange(2) == (1, 4)
-
-    def test_getstatementrange_bug2(self):
-        source = Source("""\
-            assert (
-                33
-                ==
-                [
-                  X(3,
-                      b=1, c=2
-                   ),
-                ]
-              )
-        """)
-        assert len(source) == 9
-        assert source.getstatementrange(5) == (0, 9)
-
-    def test_getstatementrange_ast_issue58(self):
-        source = Source("""\
-
-            def test_some():
-                for a in [a for a in
-                    CAUSE_ERROR]: pass
-
-            x = 3
-        """)
-        assert getstatement(2, source).lines == source.lines[2:3]
-        assert getstatement(3, source).lines == source.lines[3:4]
-
-    @pytest.mark.skipif("sys.version_info < (2,6)")
-    def test_getstatementrange_out_of_bounds_py3(self):
-        source = Source("if xxx:\n   from .collections import something")
-        r = source.getstatementrange(1)
-        assert r == (1,2)
-
-    def test_getstatementrange_with_syntaxerror_issue7(self):
-        source = Source(":")
-        pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
-
-    @pytest.mark.skipif("sys.version_info < (2,6)")
-    def test_compile_to_ast(self):
-        import ast
-        source = Source("x = 4")
-        mod = source.compile(flag=ast.PyCF_ONLY_AST)
-        assert isinstance(mod, ast.Module)
-        compile(mod, "<filename>", "exec")
-
-    def test_compile_and_getsource(self):
-        co = self.source.compile()
-        py.builtin.exec_(co, globals())
-        f(7)
-        excinfo = pytest.raises(AssertionError, "f(6)")
-        frame = excinfo.traceback[-1].frame
-        stmt = frame.code.fullsource.getstatement(frame.lineno)
-        #print "block", str(block)
-        assert str(stmt).strip().startswith('assert')
-
-    def test_compilefuncs_and_path_sanity(self):
-        def check(comp, name):
-            co = comp(self.source, name)
-            if not name:
-                expected = "codegen %s:%d>" %(mypath, mylineno+2+1)
-            else:
-                expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+1)
-            fn = co.co_filename
-            assert fn.endswith(expected)
-
-        mycode = _pytest._code.Code(self.test_compilefuncs_and_path_sanity)
-        mylineno = mycode.firstlineno
-        mypath = mycode.path
-
-        for comp in _pytest._code.compile, _pytest._code.Source.compile:
-            for name in '', None, 'my':
-                yield check, comp, name
-
-    def test_offsetless_synerr(self):
-        pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval')
-
-def test_getstartingblock_singleline():
-    class A:
-        def __init__(self, *args):
-            frame = sys._getframe(1)
-            self.source = _pytest._code.Frame(frame).statement
-
-    x = A('x', 'y')
-
-    l = [i for i in x.source.lines if i.strip()]
-    assert len(l) == 1
-
-def test_getstartingblock_multiline():
-    class A:
-        def __init__(self, *args):
-            frame = sys._getframe(1)
-            self.source = _pytest._code.Frame(frame).statement
-
-    x = A('x',
-          'y' \
-          ,
-          'z')
-
-    l = [i for i in x.source.lines if i.strip()]
-    assert len(l) == 4
-
-def test_getline_finally():
-    def c(): pass
-    excinfo = pytest.raises(TypeError, """
-           teardown = None
-           try:
-                c(1)
-           finally:
-                if teardown:
-                    teardown()
-    """)
-    source = excinfo.traceback[-1].statement
-    assert str(source).strip() == 'c(1)'
-
-def test_getfuncsource_dynamic():
-    source = """
-        def f():
-            raise ValueError
-
-        def g(): pass
-    """
-    co = _pytest._code.compile(source)
-    py.builtin.exec_(co, globals())
-    assert str(_pytest._code.Source(f)).strip() == 'def f():\n    raise ValueError'
-    assert str(_pytest._code.Source(g)).strip() == 'def g(): pass'
-
-
-def test_getfuncsource_with_multine_string():
-    def f():
-        c = '''while True:
-    pass
-'''
-    assert str(_pytest._code.Source(f)).strip() == "def f():\n    c = '''while True:\n    pass\n'''"
-
-
-def test_deindent():
-    from _pytest._code.source import deindent as deindent
-    assert deindent(['\tfoo', '\tbar', ]) == ['foo', 'bar']
-
-    def f():
-        c = '''while True:
-    pass
-'''
-    import inspect
-    lines = deindent(inspect.getsource(f).splitlines())
-    assert lines == ["def f():", "    c = '''while True:", "    pass", "'''"]
-
-    source = """
-        def f():
-            def g():
-                pass
-    """
-    lines = deindent(source.splitlines())
-    assert lines == ['', 'def f():', '    def g():', '        pass', '    ']
-
-@pytest.mark.xfail("sys.version_info[:3] < (2,7,0) or "
-    "((3,0) <= sys.version_info[:2] < (3,2))")
-def test_source_of_class_at_eof_without_newline(tmpdir):
-    # this test fails because the implicit inspect.getsource(A) below
-    # does not return the "x = 1" last line.
-    source = _pytest._code.Source('''
-        class A(object):
-            def method(self):
-                x = 1
-    ''')
-    path = tmpdir.join("a.py")
-    path.write(source)
-    s2 = _pytest._code.Source(tmpdir.join("a.py").pyimport().A)
-    assert str(source).strip() == str(s2).strip()
-
-if True:
-    def x():
-        pass
-
-def test_getsource_fallback():
-    from _pytest._code.source import getsource
-    expected = """def x():
-    pass"""
-    src = getsource(x)
-    assert src == expected
-
-def test_idem_compile_and_getsource():
-    from _pytest._code.source import getsource
-    expected = "def x(): pass"
-    co = _pytest._code.compile(expected)
-    src = getsource(co)
-    assert src == expected
-
-def test_findsource_fallback():
-    from _pytest._code.source import findsource
-    src, lineno = findsource(x)
-    assert 'test_findsource_simple' in str(src)
-    assert src[lineno] == '    def x():'
-
-def test_findsource():
-    from _pytest._code.source import findsource
-    co = _pytest._code.compile("""if 1:
-    def x():
-        pass
-""")
-
-    src, lineno = findsource(co)
-    assert 'if 1:' in str(src)
-
-    d = {}
-    eval(co, d)
-    src, lineno = findsource(d['x'])
-    assert 'if 1:' in str(src)
-    assert src[lineno] == "    def x():"
-
-
-def test_getfslineno():
-    from _pytest._code import getfslineno
-
-    def f(x):
-        pass
-
-    fspath, lineno = getfslineno(f)
-
-    assert fspath.basename == "test_source.py"
-    assert lineno == _pytest._code.getrawcode(f).co_firstlineno - 1 # see findsource
-
-    class A(object):
-        pass
-
-    fspath, lineno = getfslineno(A)
-
-    _, A_lineno = py.std.inspect.findsource(A)
-    assert fspath.basename == "test_source.py"
-    assert lineno == A_lineno
-
-    assert getfslineno(3) == ("", -1)
-    class B:
-        pass
-    B.__name__ = "B2"
-    assert getfslineno(B)[1] == -1
-
-def test_code_of_object_instance_with_call():
-    class A:
-        pass
-    pytest.raises(TypeError, lambda: _pytest._code.Source(A()))
-    class WithCall:
-        def __call__(self):
-            pass
-
-    code = _pytest._code.Code(WithCall())
-    assert 'pass' in str(code.source())
-
-    class Hello(object):
-        def __call__(self):
-            pass
-    pytest.raises(TypeError, lambda: _pytest._code.Code(Hello))
-
-
-def getstatement(lineno, source):
-    from _pytest._code.source import getstatementrange_ast
-    source = _pytest._code.Source(source, deindent=False)
-    ast, start, end = getstatementrange_ast(lineno, source)
-    return source[start:end]
-
-def test_oneline():
-    source = getstatement(0, "raise ValueError")
-    assert str(source) == "raise ValueError"
-
-def test_comment_and_no_newline_at_end():
-    from _pytest._code.source import getstatementrange_ast
-    source = Source(['def test_basic_complex():',
-                     '    assert 1 == 2',
-                     '# vim: filetype=pyopencl:fdm=marker'])
-    ast, start, end = getstatementrange_ast(1, source)
-    assert end == 2
-
-def test_oneline_and_comment():
-    source = getstatement(0, "raise ValueError\n#hello")
-    assert str(source) == "raise ValueError"
-
-@pytest.mark.xfail(hasattr(sys, "pypy_version_info"),
-                   reason='does not work on pypy')
-def test_comments():
-    source = '''def test():
-    "comment 1"
-    x = 1
-      # comment 2
-    # comment 3
-
-    assert False
-
-"""
-comment 4
-"""
-'''
-    for line in range(2,6):
-        assert str(getstatement(line, source)) == '    x = 1'
-    for line in range(6,10):
-        assert str(getstatement(line, source)) == '    assert False'
-    assert str(getstatement(10, source)) == '"""'
-
-def test_comment_in_statement():
-    source = '''test(foo=1,
-    # comment 1
-    bar=2)
-'''
-    for line in range(1,3):
-        assert str(getstatement(line, source)) == \
-               'test(foo=1,\n    # comment 1\n    bar=2)'
-
-def test_single_line_else():
-    source = getstatement(1, "if False: 2\nelse: 3")
-    assert str(source) == "else: 3"
-
-def test_single_line_finally():
-    source = getstatement(1, "try: 1\nfinally: 3")
-    assert str(source) == "finally: 3"
-
-def test_issue55():
-    source = ('def round_trip(dinp):\n  assert 1 == dinp\n'
-              'def test_rt():\n  round_trip("""\n""")\n')
-    s = getstatement(3, source)
-    assert str(s) == '  round_trip("""\n""")'
-
-
-def XXXtest_multiline():
-    source = getstatement(0, """\
-raise ValueError(
-    23
-)
-x = 3
-""")
-    assert str(source) == "raise ValueError(\n    23\n)"
-
-class TestTry:
-    pytestmark = astonly
-    source = """\
-try:
-    raise ValueError
-except Something:
-    raise IndexError(1)
-else:
-    raise KeyError()
-"""
-
-    def test_body(self):
-        source = getstatement(1, self.source)
-        assert str(source) == "    raise ValueError"
-
-    def test_except_line(self):
-        source = getstatement(2, self.source)
-        assert str(source) == "except Something:"
-
-    def test_except_body(self):
-        source = getstatement(3, self.source)
-        assert str(source) == "    raise IndexError(1)"
-
-    def test_else(self):
-        source = getstatement(5, self.source)
-        assert str(source) == "    raise KeyError()"
-
-class TestTryFinally:
-    source = """\
-try:
-    raise ValueError
-finally:
-    raise IndexError(1)
-"""
-
-    def test_body(self):
-        source = getstatement(1, self.source)
-        assert str(source) == "    raise ValueError"
-
-    def test_finally(self):
-        source = getstatement(3, self.source)
-        assert str(source) == "    raise IndexError(1)"
-
-
-
-class TestIf:
-    pytestmark = astonly
-    source = """\
-if 1:
-    y = 3
-elif False:
-    y = 5
-else:
-    y = 7
-"""
-
-    def test_body(self):
-        source = getstatement(1, self.source)
-        assert str(source) == "    y = 3"
-
-    def test_elif_clause(self):
-        source = getstatement(2, self.source)
-        assert str(source) == "elif False:"
-
-    def test_elif(self):
-        source = getstatement(3, self.source)
-        assert str(source) == "    y = 5"
-
-    def test_else(self):
-        source = getstatement(5, self.source)
-        assert str(source) == "    y = 7"
-
-def test_semicolon():
-    s = """\
-hello ; pytest.skip()
-"""
-    source = getstatement(0, s)
-    assert str(source) == s.strip()
-
-def test_def_online():
-    s = """\
-def func(): raise ValueError(42)
-
-def something():
-    pass
-"""
-    source = getstatement(0, s)
-    assert str(source) == "def func(): raise ValueError(42)"
-
-def XXX_test_expression_multiline():
-    source = """\
-something
-'''
-'''"""
-    result = getstatement(1, source)
-    assert str(result) == "'''\n'''"
-
diff --git a/tools/pytest/testing/cx_freeze/install_cx_freeze.py b/tools/pytest/testing/cx_freeze/install_cx_freeze.py
deleted file mode 100644
index 83dce87..0000000
--- a/tools/pytest/testing/cx_freeze/install_cx_freeze.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""

-Installs cx_freeze from source, but first patching

-setup.py as described here:

-

-http://stackoverflow.com/questions/25107697/compiling-cx-freeze-under-ubuntu

-"""

-import glob

-import tarfile

-import os

-import sys

-import platform

-import py

-

-if __name__ == '__main__':

-    if 'ubuntu' not in platform.version().lower():

-

-        print('Not Ubuntu, installing using pip. (platform.version() is %r)' %

-              platform.version())

-        res = os.system('pip install cx_freeze')

-        if res != 0:

-            sys.exit(res)

-        sys.exit(0)

-

-    rootdir = py.path.local.make_numbered_dir(prefix='cx_freeze')

-

-    res = os.system('pip install --download %s --no-use-wheel '

-                    'cx_freeze' % rootdir)

-    if res != 0:

-        sys.exit(res)

-

-    packages = glob.glob('%s/*.tar.gz' % rootdir)

-    assert len(packages) == 1

-    tar_filename = packages[0]

-

-    tar_file = tarfile.open(tar_filename)

-    try:

-        tar_file.extractall(path=str(rootdir))

-    finally:

-        tar_file.close()

-

-    basename = os.path.basename(tar_filename).replace('.tar.gz', '')

-    setup_py_filename = '%s/%s/setup.py' % (rootdir, basename)

-    with open(setup_py_filename) as f:

-        lines = f.readlines()

-

-    line_to_patch = 'if not vars.get("Py_ENABLE_SHARED", 0):'

-    for index, line in enumerate(lines):

-        if line_to_patch in line:

-            indent = line[:line.index(line_to_patch)]

-            lines[index] = indent + 'if True:\n'

-            print('Patched line %d' % (index + 1))

-            break

-    else:

-        sys.exit('Could not find line in setup.py to patch!')

-

-    with open(setup_py_filename, 'w') as f:

-        f.writelines(lines)

-

-    os.chdir('%s/%s' % (rootdir, basename))

-    res = os.system('python setup.py install')

-    if res != 0:

-        sys.exit(res)

-

-    sys.exit(0)

diff --git a/tools/pytest/testing/cx_freeze/runtests_script.py b/tools/pytest/testing/cx_freeze/runtests_script.py
deleted file mode 100644
index f2b032d..0000000
--- a/tools/pytest/testing/cx_freeze/runtests_script.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""

-This is the script that is actually frozen into an executable: simply executes

-py.test main().

-"""

-

-if __name__ == '__main__':

-    import sys

-    import pytest

-    sys.exit(pytest.main())
\ No newline at end of file
diff --git a/tools/pytest/testing/cx_freeze/runtests_setup.py b/tools/pytest/testing/cx_freeze/runtests_setup.py
deleted file mode 100644
index a2874a6..0000000
--- a/tools/pytest/testing/cx_freeze/runtests_setup.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""

-Sample setup.py script that generates an executable with pytest runner embedded.

-"""

-if __name__ == '__main__':

-    from cx_Freeze import setup, Executable

-    import pytest

-

-    setup(

-        name="runtests",

-        version="0.1",

-        description="exemple of how embedding py.test into an executable using cx_freeze",

-        executables=[Executable("runtests_script.py")],

-        options={"build_exe": {'includes': pytest.freeze_includes()}},

-    )

-

diff --git a/tools/pytest/testing/cx_freeze/tests/test_trivial.py b/tools/pytest/testing/cx_freeze/tests/test_trivial.py
deleted file mode 100644
index d8a572b..0000000
--- a/tools/pytest/testing/cx_freeze/tests/test_trivial.py
+++ /dev/null
@@ -1,6 +0,0 @@
-

-def test_upper():

-    assert 'foo'.upper() == 'FOO'

-

-def test_lower():

-    assert 'FOO'.lower() == 'foo'
\ No newline at end of file
diff --git a/tools/pytest/testing/cx_freeze/tox_run.py b/tools/pytest/testing/cx_freeze/tox_run.py
deleted file mode 100644
index e8df268..0000000
--- a/tools/pytest/testing/cx_freeze/tox_run.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""

-Called by tox.ini: uses the generated executable to run the tests in ./tests/

-directory.

-

-.. note:: somehow calling "build/runtests_script" directly from tox doesn't

-          seem to work (at least on Windows).

-"""

-if __name__ == '__main__':

-    import os

-    import sys

-

-    executable = os.path.join(os.getcwd(), 'build', 'runtests_script')

-    if sys.platform.startswith('win'):

-        executable += '.exe'

-    sys.exit(os.system('%s tests' % executable))
\ No newline at end of file
diff --git a/tools/pytest/testing/python/collect.py b/tools/pytest/testing/python/collect.py
deleted file mode 100644
index 22433da..0000000
--- a/tools/pytest/testing/python/collect.py
+++ /dev/null
@@ -1,1200 +0,0 @@
-# -*- coding: utf-8 -*-
-import sys
-from textwrap import dedent
-
-import _pytest._code
-import py
-import pytest
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-
-
-class TestModule:
-    def test_failing_import(self, testdir):
-        modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
-        pytest.raises(ImportError, modcol.collect)
-        pytest.raises(ImportError, modcol.collect)
-
-    def test_import_duplicate(self, testdir):
-        a = testdir.mkdir("a")
-        b = testdir.mkdir("b")
-        p = a.ensure("test_whatever.py")
-        p.pyimport()
-        del py.std.sys.modules['test_whatever']
-        b.ensure("test_whatever.py")
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*import*mismatch*",
-            "*imported*test_whatever*",
-            "*%s*" % a.join("test_whatever.py"),
-            "*not the same*",
-            "*%s*" % b.join("test_whatever.py"),
-            "*HINT*",
-        ])
-
-    def test_import_prepend_append(self, testdir, monkeypatch):
-        syspath = list(sys.path)
-        monkeypatch.setattr(sys, "path", syspath)
-        root1 = testdir.mkdir("root1")
-        root2 = testdir.mkdir("root2")
-        root1.ensure("x456.py")
-        root2.ensure("x456.py")
-        p = root2.join("test_x456.py")
-        monkeypatch.syspath_prepend(str(root1))
-        p.write(dedent("""\
-            import x456
-            def test():
-                assert x456.__file__.startswith(%r)
-        """ % str(root2)))
-        with root2.as_cwd():
-            reprec = testdir.inline_run("--import-mode=append")
-            reprec.assertoutcome(passed=0, failed=1)
-            reprec = testdir.inline_run()
-            reprec.assertoutcome(passed=1)
-
-    def test_syntax_error_in_module(self, testdir):
-        modcol = testdir.getmodulecol("this is a syntax error")
-        pytest.raises(modcol.CollectError, modcol.collect)
-        pytest.raises(modcol.CollectError, modcol.collect)
-
-    def test_module_considers_pluginmanager_at_import(self, testdir):
-        modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
-        pytest.raises(ImportError, lambda: modcol.obj)
-
-class TestClass:
-    def test_class_with_init_warning(self, testdir):
-        testdir.makepyfile("""
-            class TestClass1:
-                def __init__(self):
-                    pass
-        """)
-        result = testdir.runpytest("-rw")
-        result.stdout.fnmatch_lines_random("""
-            WC1*test_class_with_init_warning.py*__init__*
-        """)
-
-    def test_class_subclassobject(self, testdir):
-        testdir.getmodulecol("""
-            class test(object):
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*collected 0*",
-        ])
-
-    def test_setup_teardown_class_as_classmethod(self, testdir):
-        testdir.makepyfile(test_mod1="""
-            class TestClassMethod:
-                @classmethod
-                def setup_class(cls):
-                    pass
-                def test_1(self):
-                    pass
-                @classmethod
-                def teardown_class(cls):
-                    pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*1 passed*",
-        ])
-
-    def test_issue1035_obj_has_getattr(self, testdir):
-        modcol = testdir.getmodulecol("""
-            class Chameleon(object):
-                def __getattr__(self, name):
-                    return True
-            chameleon = Chameleon()
-        """)
-        colitems = modcol.collect()
-        assert len(colitems) == 0
-
-
-class TestGenerator:
-    def test_generative_functions(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def func1(arg, arg2):
-                assert arg == arg2
-
-            def test_gen():
-                yield func1, 17, 3*5
-                yield func1, 42, 6*7
-        """)
-        colitems = modcol.collect()
-        assert len(colitems) == 1
-        gencol = colitems[0]
-        assert isinstance(gencol, pytest.Generator)
-        gencolitems = gencol.collect()
-        assert len(gencolitems) == 2
-        assert isinstance(gencolitems[0], pytest.Function)
-        assert isinstance(gencolitems[1], pytest.Function)
-        assert gencolitems[0].name == '[0]'
-        assert gencolitems[0].obj.__name__ == 'func1'
-
-    def test_generative_methods(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def func1(arg, arg2):
-                assert arg == arg2
-            class TestGenMethods:
-                def test_gen(self):
-                    yield func1, 17, 3*5
-                    yield func1, 42, 6*7
-        """)
-        gencol = modcol.collect()[0].collect()[0].collect()[0]
-        assert isinstance(gencol, pytest.Generator)
-        gencolitems = gencol.collect()
-        assert len(gencolitems) == 2
-        assert isinstance(gencolitems[0], pytest.Function)
-        assert isinstance(gencolitems[1], pytest.Function)
-        assert gencolitems[0].name == '[0]'
-        assert gencolitems[0].obj.__name__ == 'func1'
-
-    def test_generative_functions_with_explicit_names(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def func1(arg, arg2):
-                assert arg == arg2
-
-            def test_gen():
-                yield "seventeen", func1, 17, 3*5
-                yield "fortytwo", func1, 42, 6*7
-        """)
-        colitems = modcol.collect()
-        assert len(colitems) == 1
-        gencol = colitems[0]
-        assert isinstance(gencol, pytest.Generator)
-        gencolitems = gencol.collect()
-        assert len(gencolitems) == 2
-        assert isinstance(gencolitems[0], pytest.Function)
-        assert isinstance(gencolitems[1], pytest.Function)
-        assert gencolitems[0].name == "['seventeen']"
-        assert gencolitems[0].obj.__name__ == 'func1'
-        assert gencolitems[1].name == "['fortytwo']"
-        assert gencolitems[1].obj.__name__ == 'func1'
-
-    def test_generative_functions_unique_explicit_names(self, testdir):
-        # generative
-        modcol = testdir.getmodulecol("""
-            def func(): pass
-            def test_gen():
-                yield "name", func
-                yield "name", func
-        """)
-        colitems = modcol.collect()
-        assert len(colitems) == 1
-        gencol = colitems[0]
-        assert isinstance(gencol, pytest.Generator)
-        pytest.raises(ValueError, "gencol.collect()")
-
-    def test_generative_methods_with_explicit_names(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def func1(arg, arg2):
-                assert arg == arg2
-            class TestGenMethods:
-                def test_gen(self):
-                    yield "m1", func1, 17, 3*5
-                    yield "m2", func1, 42, 6*7
-        """)
-        gencol = modcol.collect()[0].collect()[0].collect()[0]
-        assert isinstance(gencol, pytest.Generator)
-        gencolitems = gencol.collect()
-        assert len(gencolitems) == 2
-        assert isinstance(gencolitems[0], pytest.Function)
-        assert isinstance(gencolitems[1], pytest.Function)
-        assert gencolitems[0].name == "['m1']"
-        assert gencolitems[0].obj.__name__ == 'func1'
-        assert gencolitems[1].name == "['m2']"
-        assert gencolitems[1].obj.__name__ == 'func1'
-
-    def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
-        o = testdir.makepyfile("""
-            def test_generative_order_of_execution():
-                import py, pytest
-                test_list = []
-                expected_list = list(range(6))
-
-                def list_append(item):
-                    test_list.append(item)
-
-                def assert_order_of_execution():
-                    py.builtin.print_('expected order', expected_list)
-                    py.builtin.print_('but got       ', test_list)
-                    assert test_list == expected_list
-
-                for i in expected_list:
-                    yield list_append, i
-                yield assert_order_of_execution
-        """)
-        reprec = testdir.inline_run(o)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert passed == 7
-        assert not skipped and not failed
-
-    def test_order_of_execution_generator_different_codeline(self, testdir):
-        o = testdir.makepyfile("""
-            def test_generative_tests_different_codeline():
-                import py, pytest
-                test_list = []
-                expected_list = list(range(3))
-
-                def list_append_2():
-                    test_list.append(2)
-
-                def list_append_1():
-                    test_list.append(1)
-
-                def list_append_0():
-                    test_list.append(0)
-
-                def assert_order_of_execution():
-                    py.builtin.print_('expected order', expected_list)
-                    py.builtin.print_('but got       ', test_list)
-                    assert test_list == expected_list
-
-                yield list_append_0
-                yield list_append_1
-                yield list_append_2
-                yield assert_order_of_execution
-        """)
-        reprec = testdir.inline_run(o)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert passed == 4
-        assert not skipped and not failed
-
-    def test_setupstate_is_preserved_134(self, testdir):
-        # yield-based tests are messy wrt to setupstate because
-        # during collection they already invoke setup functions
-        # and then again when they are run.  For now, we want to make sure
-        # that the old 1.3.4 behaviour is preserved such that all
-        # yielded functions all share the same "self" instance that
-        # has been used during collection.
-        o = testdir.makepyfile("""
-            setuplist = []
-            class TestClass:
-                def setup_method(self, func):
-                    #print "setup_method", self, func
-                    setuplist.append(self)
-                    self.init = 42
-
-                def teardown_method(self, func):
-                    self.init = None
-
-                def test_func1(self):
-                    pass
-
-                def test_func2(self):
-                    yield self.func2
-                    yield self.func2
-
-                def func2(self):
-                    assert self.init
-
-            def test_setuplist():
-                # once for test_func2 during collection
-                # once for test_func1 during test run
-                # once for test_func2 during test run
-                #print setuplist
-                assert len(setuplist) == 3, len(setuplist)
-                assert setuplist[0] == setuplist[2], setuplist
-                assert setuplist[1] != setuplist[2], setuplist
-        """)
-        reprec = testdir.inline_run(o, '-v')
-        passed, skipped, failed = reprec.countoutcomes()
-        assert passed == 4
-        assert not skipped and not failed
-
-
-class TestFunction:
-    def test_getmodulecollector(self, testdir):
-        item = testdir.getitem("def test_func(): pass")
-        modcol = item.getparent(pytest.Module)
-        assert isinstance(modcol, pytest.Module)
-        assert hasattr(modcol.obj, 'test_func')
-
-    def test_function_as_object_instance_ignored(self, testdir):
-        testdir.makepyfile("""
-            class A:
-                def __call__(self, tmpdir):
-                    0/0
-
-            test_a = A()
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome()
-
-    def test_function_equality(self, testdir, tmpdir):
-        from _pytest.python import FixtureManager
-        config = testdir.parseconfigure()
-        session = testdir.Session(config)
-        session._fixturemanager = FixtureManager(session)
-        def func1():
-            pass
-        def func2():
-            pass
-        f1 = pytest.Function(name="name", parent=session, config=config,
-                args=(1,), callobj=func1)
-        assert f1 == f1
-        f2 = pytest.Function(name="name",config=config,
-                callobj=func2, parent=session)
-        assert f1 != f2
-
-    def test_issue197_parametrize_emptyset(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.parametrize('arg', [])
-            def test_function(arg):
-                pass
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(skipped=1)
-
-    def test_single_tuple_unwraps_values(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.parametrize(('arg',), [(1,)])
-            def test_function(arg):
-                assert arg == 1
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_issue213_parametrize_value_no_equal(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            class A:
-                def __eq__(self, other):
-                    raise ValueError("not possible")
-            @pytest.mark.parametrize('arg', [A()])
-            def test_function(arg):
-                assert arg.__class__.__name__ == "A"
-        """)
-        reprec = testdir.inline_run("--fulltrace")
-        reprec.assertoutcome(passed=1)
-
-    def test_parametrize_with_non_hashable_values(self, testdir):
-        """Test parametrization with non-hashable values."""
-        testdir.makepyfile("""
-            archival_mapping = {
-                '1.0': {'tag': '1.0'},
-                '1.2.2a1': {'tag': 'release-1.2.2a1'},
-            }
-
-            import pytest
-            @pytest.mark.parametrize('key value'.split(),
-                                     archival_mapping.items())
-            def test_archival_to_version(key, value):
-                assert key in archival_mapping
-                assert value == archival_mapping[key]
-        """)
-        rec = testdir.inline_run()
-        rec.assertoutcome(passed=2)
-
-
-    def test_parametrize_with_non_hashable_values_indirect(self, testdir):
-        """Test parametrization with non-hashable values with indirect parametrization."""
-        testdir.makepyfile("""
-            archival_mapping = {
-                '1.0': {'tag': '1.0'},
-                '1.2.2a1': {'tag': 'release-1.2.2a1'},
-            }
-
-            import pytest
-
-            @pytest.fixture
-            def key(request):
-                return request.param
-
-            @pytest.fixture
-            def value(request):
-                return request.param
-
-            @pytest.mark.parametrize('key value'.split(),
-                                     archival_mapping.items(), indirect=True)
-            def test_archival_to_version(key, value):
-                assert key in archival_mapping
-                assert value == archival_mapping[key]
-        """)
-        rec = testdir.inline_run()
-        rec.assertoutcome(passed=2)
-
-
-    def test_parametrize_overrides_fixture(self, testdir):
-        """Test parametrization when parameter overrides existing fixture with same name."""
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture
-            def value():
-                return 'value'
-
-            @pytest.mark.parametrize('value',
-                                     ['overridden'])
-            def test_overridden_via_param(value):
-                assert value == 'overridden'
-
-            @pytest.mark.parametrize('somevalue', ['overridden'])
-            def test_not_overridden(value, somevalue):
-                assert value == 'value'
-                assert somevalue == 'overridden'
-
-            @pytest.mark.parametrize('other,value', [('foo', 'overridden')])
-            def test_overridden_via_multiparam(other, value):
-                assert other == 'foo'
-                assert value == 'overridden'
-        """)
-        rec = testdir.inline_run()
-        rec.assertoutcome(passed=3)
-
-
-    def test_parametrize_overrides_parametrized_fixture(self, testdir):
-        """Test parametrization when parameter overrides existing parametrized fixture with same name."""
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(params=[1, 2])
-            def value(request):
-                return request.param
-
-            @pytest.mark.parametrize('value',
-                                     ['overridden'])
-            def test_overridden_via_param(value):
-                assert value == 'overridden'
-        """)
-        rec = testdir.inline_run()
-        rec.assertoutcome(passed=1)
-
-    def test_parametrize_with_mark(selfself, testdir):
-        items = testdir.getitems("""
-            import pytest
-            @pytest.mark.foo
-            @pytest.mark.parametrize('arg', [
-                1,
-                pytest.mark.bar(pytest.mark.baz(2))
-            ])
-            def test_function(arg):
-                pass
-        """)
-        keywords = [item.keywords for item in items]
-        assert 'foo' in keywords[0] and 'bar' not in keywords[0] and 'baz' not in keywords[0]
-        assert 'foo' in keywords[1] and 'bar' in keywords[1] and 'baz' in keywords[1]
-
-    def test_function_equality_with_callspec(self, testdir, tmpdir):
-        items = testdir.getitems("""
-            import pytest
-            @pytest.mark.parametrize('arg', [1,2])
-            def test_function(arg):
-                pass
-        """)
-        assert items[0] != items[1]
-        assert not (items[0] == items[1])
-
-    def test_pyfunc_call(self, testdir):
-        item = testdir.getitem("def test_func(): raise ValueError")
-        config = item.config
-        class MyPlugin1:
-            def pytest_pyfunc_call(self, pyfuncitem):
-                raise ValueError
-        class MyPlugin2:
-            def pytest_pyfunc_call(self, pyfuncitem):
-                return True
-        config.pluginmanager.register(MyPlugin1())
-        config.pluginmanager.register(MyPlugin2())
-        config.hook.pytest_runtest_setup(item=item)
-        config.hook.pytest_pyfunc_call(pyfuncitem=item)
-
-    def test_multiple_parametrize(self, testdir):
-        modcol = testdir.getmodulecol("""
-            import pytest
-            @pytest.mark.parametrize('x', [0, 1])
-            @pytest.mark.parametrize('y', [2, 3])
-            def test1(x, y):
-                pass
-        """)
-        colitems = modcol.collect()
-        assert colitems[0].name == 'test1[2-0]'
-        assert colitems[1].name == 'test1[2-1]'
-        assert colitems[2].name == 'test1[3-0]'
-        assert colitems[3].name == 'test1[3-1]'
-
-    def test_issue751_multiple_parametrize_with_ids(self, testdir):
-        modcol = testdir.getmodulecol("""
-            import pytest
-            @pytest.mark.parametrize('x', [0], ids=['c'])
-            @pytest.mark.parametrize('y', [0, 1], ids=['a', 'b'])
-            class Test(object):
-                def test1(self, x, y):
-                    pass
-                def test2(self, x, y):
-                    pass
-        """)
-        colitems = modcol.collect()[0].collect()[0].collect()
-        assert colitems[0].name == 'test1[a-c]'
-        assert colitems[1].name == 'test1[b-c]'
-        assert colitems[2].name == 'test2[a-c]'
-        assert colitems[3].name == 'test2[b-c]'
-
-    def test_parametrize_skipif(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            m = pytest.mark.skipif('True')
-
-            @pytest.mark.parametrize('x', [0, 1, m(2)])
-            def test_skip_if(x):
-                assert x < 2
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
-
-    def test_parametrize_skip(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            m = pytest.mark.skip('')
-
-            @pytest.mark.parametrize('x', [0, 1, m(2)])
-            def test_skip(x):
-                assert x < 2
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
-
-    def test_parametrize_skipif_no_skip(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            m = pytest.mark.skipif('False')
-
-            @pytest.mark.parametrize('x', [0, 1, m(2)])
-            def test_skipif_no_skip(x):
-                assert x < 2
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('* 1 failed, 2 passed in *')
-
-    def test_parametrize_xfail(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            m = pytest.mark.xfail('True')
-
-            @pytest.mark.parametrize('x', [0, 1, m(2)])
-            def test_xfail(x):
-                assert x < 2
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('* 2 passed, 1 xfailed in *')
-
-    def test_parametrize_passed(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            m = pytest.mark.xfail('True')
-
-            @pytest.mark.parametrize('x', [0, 1, m(2)])
-            def test_xfail(x):
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('* 2 passed, 1 xpassed in *')
-
-    def test_parametrize_xfail_passed(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            m = pytest.mark.xfail('False')
-
-            @pytest.mark.parametrize('x', [0, 1, m(2)])
-            def test_passed(x):
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('* 3 passed in *')
-
-
-class TestSorting:
-    def test_check_equality(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def test_pass(): pass
-            def test_fail(): assert 0
-        """)
-        fn1 = testdir.collect_by_name(modcol, "test_pass")
-        assert isinstance(fn1, pytest.Function)
-        fn2 = testdir.collect_by_name(modcol, "test_pass")
-        assert isinstance(fn2, pytest.Function)
-
-        assert fn1 == fn2
-        assert fn1 != modcol
-        if py.std.sys.version_info < (3, 0):
-            assert cmp(fn1, fn2) == 0
-        assert hash(fn1) == hash(fn2)
-
-        fn3 = testdir.collect_by_name(modcol, "test_fail")
-        assert isinstance(fn3, pytest.Function)
-        assert not (fn1 == fn3)
-        assert fn1 != fn3
-
-        for fn in fn1,fn2,fn3:
-            assert fn != 3
-            assert fn != modcol
-            assert fn != [1,2,3]
-            assert [1,2,3] != fn
-            assert modcol != fn
-
-    def test_allow_sane_sorting_for_decorators(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def dec(f):
-                g = lambda: f(2)
-                g.place_as = f
-                return g
-
-
-            def test_b(y):
-                pass
-            test_b = dec(test_b)
-
-            def test_a(y):
-                pass
-            test_a = dec(test_a)
-        """)
-        colitems = modcol.collect()
-        assert len(colitems) == 2
-        assert [item.name for item in colitems] == ['test_b', 'test_a']
-
-
-class TestConftestCustomization:
-    def test_pytest_pycollect_module(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            class MyModule(pytest.Module):
-                pass
-            def pytest_pycollect_makemodule(path, parent):
-                if path.basename == "test_xyz.py":
-                    return MyModule(path, parent)
-        """)
-        testdir.makepyfile("def test_some(): pass")
-        testdir.makepyfile(test_xyz="def test_func(): pass")
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*<Module*test_pytest*",
-            "*<MyModule*xyz*",
-        ])
-
-    def test_customized_pymakemodule_issue205_subdir(self, testdir):
-        b = testdir.mkdir("a").mkdir("b")
-        b.join("conftest.py").write(_pytest._code.Source("""
-            def pytest_pycollect_makemodule(__multicall__):
-                mod = __multicall__.execute()
-                mod.obj.hello = "world"
-                return mod
-        """))
-        b.join("test_module.py").write(_pytest._code.Source("""
-            def test_hello():
-                assert hello == "world"
-        """))
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_customized_pymakeitem(self, testdir):
-        b = testdir.mkdir("a").mkdir("b")
-        b.join("conftest.py").write(_pytest._code.Source("""
-            import pytest
-            @pytest.hookimpl(hookwrapper=True)
-            def pytest_pycollect_makeitem():
-                outcome = yield
-                if outcome.excinfo is None:
-                    result = outcome.result
-                    if result:
-                        for func in result:
-                            func._some123 = "world"
-        """))
-        b.join("test_module.py").write(_pytest._code.Source("""
-            import pytest
-
-            @pytest.fixture()
-            def obj(request):
-                return request.node._some123
-            def test_hello(obj):
-                assert obj == "world"
-        """))
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_pytest_pycollect_makeitem(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            class MyFunction(pytest.Function):
-                pass
-            def pytest_pycollect_makeitem(collector, name, obj):
-                if name == "some":
-                    return MyFunction(name, collector)
-        """)
-        testdir.makepyfile("def some(): pass")
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*MyFunction*some*",
-        ])
-
-    def test_makeitem_non_underscore(self, testdir, monkeypatch):
-        modcol = testdir.getmodulecol("def _hello(): pass")
-        l = []
-        monkeypatch.setattr(pytest.Module, 'makeitem',
-            lambda self, name, obj: l.append(name))
-        l = modcol.collect()
-        assert '_hello' not in l
-
-def test_setup_only_available_in_subdir(testdir):
-    sub1 = testdir.mkpydir("sub1")
-    sub2 = testdir.mkpydir("sub2")
-    sub1.join("conftest.py").write(_pytest._code.Source("""
-        import pytest
-        def pytest_runtest_setup(item):
-            assert item.fspath.purebasename == "test_in_sub1"
-        def pytest_runtest_call(item):
-            assert item.fspath.purebasename == "test_in_sub1"
-        def pytest_runtest_teardown(item):
-            assert item.fspath.purebasename == "test_in_sub1"
-    """))
-    sub2.join("conftest.py").write(_pytest._code.Source("""
-        import pytest
-        def pytest_runtest_setup(item):
-            assert item.fspath.purebasename == "test_in_sub2"
-        def pytest_runtest_call(item):
-            assert item.fspath.purebasename == "test_in_sub2"
-        def pytest_runtest_teardown(item):
-            assert item.fspath.purebasename == "test_in_sub2"
-    """))
-    sub1.join("test_in_sub1.py").write("def test_1(): pass")
-    sub2.join("test_in_sub2.py").write("def test_2(): pass")
-    result = testdir.runpytest("-v", "-s")
-    result.assert_outcomes(passed=2)
-
-def test_modulecol_roundtrip(testdir):
-    modcol = testdir.getmodulecol("pass", withinit=True)
-    trail = modcol.nodeid
-    newcol = modcol.session.perform_collect([trail], genitems=0)[0]
-    assert modcol.name == newcol.name
-
-
-class TestTracebackCutting:
-    def test_skip_simple(self):
-        excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")')
-        assert excinfo.traceback[-1].frame.code.name == "skip"
-        assert excinfo.traceback[-1].ishidden()
-
-    def test_traceback_argsetup(self, testdir):
-        testdir.makeconftest("""
-            def pytest_funcarg__hello(request):
-                raise ValueError("xyz")
-        """)
-        p = testdir.makepyfile("def test(hello): pass")
-        result = testdir.runpytest(p)
-        assert result.ret != 0
-        out = result.stdout.str()
-        assert out.find("xyz") != -1
-        assert out.find("conftest.py:2: ValueError") != -1
-        numentries = out.count("_ _ _") # separator for traceback entries
-        assert numentries == 0
-
-        result = testdir.runpytest("--fulltrace", p)
-        out = result.stdout.str()
-        assert out.find("conftest.py:2: ValueError") != -1
-        numentries = out.count("_ _ _ _") # separator for traceback entries
-        assert numentries > 3
-
-    def test_traceback_error_during_import(self, testdir):
-        testdir.makepyfile("""
-            x = 1
-            x = 2
-            x = 17
-            asd
-        """)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        out = result.stdout.str()
-        assert "x = 1" not in out
-        assert "x = 2" not in out
-        result.stdout.fnmatch_lines([
-            " *asd*",
-            "E*NameError*",
-        ])
-        result = testdir.runpytest("--fulltrace")
-        out = result.stdout.str()
-        assert "x = 1" in out
-        assert "x = 2" in out
-        result.stdout.fnmatch_lines([
-            ">*asd*",
-            "E*NameError*",
-        ])
-
-    def test_traceback_filter_error_during_fixture_collection(self, testdir):
-        """integration test for issue #995.
-        """
-        testdir.makepyfile("""
-            import pytest
-
-            def fail_me(func):
-                ns = {}
-                exec('def w(): raise ValueError("fail me")', ns)
-                return ns['w']
-
-            @pytest.fixture(scope='class')
-            @fail_me
-            def fail_fixture():
-                pass
-
-            def test_failing_fixture(fail_fixture):
-               pass
-        """)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        out = result.stdout.str()
-        assert "INTERNALERROR>" not in out
-        result.stdout.fnmatch_lines([
-            "*ValueError: fail me*",
-            "* 1 error in *",
-        ])
-
-    def test_filter_traceback_generated_code(self):
-        """test that filter_traceback() works with the fact that
-        py.code.Code.path attribute might return an str object.
-        In this case, one of the entries on the traceback was produced by
-        dynamically generated code.
-        See: https://bitbucket.org/pytest-dev/py/issues/71
-        This fixes #995.
-        """
-        from _pytest.python import filter_traceback
-        try:
-            ns = {}
-            exec('def foo(): raise ValueError', ns)
-            ns['foo']()
-        except ValueError:
-            _, _, tb = sys.exc_info()
-
-        tb = _pytest._code.Traceback(tb)
-        assert isinstance(tb[-1].path, str)
-        assert not filter_traceback(tb[-1])
-
-    def test_filter_traceback_path_no_longer_valid(self, testdir):
-        """test that filter_traceback() works with the fact that
-        py.code.Code.path attribute might return an str object.
-        In this case, one of the files in the traceback no longer exists.
-        This fixes #1133.
-        """
-        from _pytest.python import filter_traceback
-        testdir.syspathinsert()
-        testdir.makepyfile(filter_traceback_entry_as_str='''
-            def foo():
-                raise ValueError
-        ''')
-        try:
-            import filter_traceback_entry_as_str
-            filter_traceback_entry_as_str.foo()
-        except ValueError:
-            _, _, tb = sys.exc_info()
-
-        testdir.tmpdir.join('filter_traceback_entry_as_str.py').remove()
-        tb = _pytest._code.Traceback(tb)
-        assert isinstance(tb[-1].path, str)
-        assert filter_traceback(tb[-1])
-
-
-class TestReportInfo:
-    def test_itemreport_reportinfo(self, testdir, linecomp):
-        testdir.makeconftest("""
-            import pytest
-            class MyFunction(pytest.Function):
-                def reportinfo(self):
-                    return "ABCDE", 42, "custom"
-            def pytest_pycollect_makeitem(collector, name, obj):
-                if name == "test_func":
-                    return MyFunction(name, parent=collector)
-        """)
-        item = testdir.getitem("def test_func(): pass")
-        item.config.pluginmanager.getplugin("runner")
-        assert item.location == ("ABCDE", 42, "custom")
-
-    def test_func_reportinfo(self, testdir):
-        item = testdir.getitem("def test_func(): pass")
-        fspath, lineno, modpath = item.reportinfo()
-        assert fspath == item.fspath
-        assert lineno == 0
-        assert modpath == "test_func"
-
-    def test_class_reportinfo(self, testdir):
-        modcol = testdir.getmodulecol("""
-            # lineno 0
-            class TestClass:
-                def test_hello(self): pass
-        """)
-        classcol = testdir.collect_by_name(modcol, "TestClass")
-        fspath, lineno, msg = classcol.reportinfo()
-        assert fspath == modcol.fspath
-        assert lineno == 1
-        assert msg == "TestClass"
-
-    def test_generator_reportinfo(self, testdir):
-        modcol = testdir.getmodulecol("""
-            # lineno 0
-            def test_gen():
-                def check(x):
-                    assert x
-                yield check, 3
-        """)
-        gencol = testdir.collect_by_name(modcol, "test_gen")
-        fspath, lineno, modpath = gencol.reportinfo()
-        assert fspath == modcol.fspath
-        assert lineno == 1
-        assert modpath == "test_gen"
-
-        genitem = gencol.collect()[0]
-        fspath, lineno, modpath = genitem.reportinfo()
-        assert fspath == modcol.fspath
-        assert lineno == 2
-        assert modpath == "test_gen[0]"
-        """
-            def test_func():
-                pass
-            def test_genfunc():
-                def check(x):
-                    pass
-                yield check, 3
-            class TestClass:
-                def test_method(self):
-                    pass
-       """
-
-    def test_reportinfo_with_nasty_getattr(self, testdir):
-        # https://github.com/pytest-dev/pytest/issues/1204
-        modcol = testdir.getmodulecol("""
-            # lineno 0
-            class TestClass:
-                def __getattr__(self, name):
-                    return "this is not an int"
-
-                def test_foo(self):
-                    pass
-        """)
-        classcol = testdir.collect_by_name(modcol, "TestClass")
-        instance = classcol.collect()[0]
-        fspath, lineno, msg = instance.reportinfo()
-
-
-def test_customized_python_discovery(testdir):
-    testdir.makeini("""
-        [pytest]
-        python_files=check_*.py
-        python_classes=Check
-        python_functions=check
-    """)
-    p = testdir.makepyfile("""
-        def check_simple():
-            pass
-        class CheckMyApp:
-            def check_meth(self):
-                pass
-    """)
-    p2 = p.new(basename=p.basename.replace("test", "check"))
-    p.move(p2)
-    result = testdir.runpytest("--collect-only", "-s")
-    result.stdout.fnmatch_lines([
-        "*check_customized*",
-        "*check_simple*",
-        "*CheckMyApp*",
-        "*check_meth*",
-    ])
-
-    result = testdir.runpytest()
-    assert result.ret == 0
-    result.stdout.fnmatch_lines([
-        "*2 passed*",
-    ])
-
-
-def test_customized_python_discovery_functions(testdir):
-    testdir.makeini("""
-        [pytest]
-        python_functions=_test
-    """)
-    testdir.makepyfile("""
-        def _test_underscore():
-            pass
-    """)
-    result = testdir.runpytest("--collect-only", "-s")
-    result.stdout.fnmatch_lines([
-        "*_test_underscore*",
-    ])
-
-    result = testdir.runpytest()
-    assert result.ret == 0
-    result.stdout.fnmatch_lines([
-        "*1 passed*",
-    ])
-
-
-def test_collector_attributes(testdir):
-    testdir.makeconftest("""
-        import pytest
-        def pytest_pycollect_makeitem(collector):
-            assert collector.Function == pytest.Function
-            assert collector.Class == pytest.Class
-            assert collector.Instance == pytest.Instance
-            assert collector.Module == pytest.Module
-    """)
-    testdir.makepyfile("""
-         def test_hello():
-            pass
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*1 passed*",
-    ])
-
-def test_customize_through_attributes(testdir):
-    testdir.makeconftest("""
-        import pytest
-        class MyFunction(pytest.Function):
-            pass
-        class MyInstance(pytest.Instance):
-            Function = MyFunction
-        class MyClass(pytest.Class):
-            Instance = MyInstance
-
-        def pytest_pycollect_makeitem(collector, name, obj):
-            if name.startswith("MyTestClass"):
-                return MyClass(name, parent=collector)
-    """)
-    testdir.makepyfile("""
-         class MyTestClass:
-            def test_hello(self):
-                pass
-    """)
-    result = testdir.runpytest("--collect-only")
-    result.stdout.fnmatch_lines([
-        "*MyClass*",
-        "*MyInstance*",
-        "*MyFunction*test_hello*",
-    ])
-
-
-def test_unorderable_types(testdir):
-    testdir.makepyfile("""
-        class TestJoinEmpty:
-            pass
-
-        def make_test():
-            class Test:
-                pass
-            Test.__name__ = "TestFoo"
-            return Test
-        TestFoo = make_test()
-    """)
-    result = testdir.runpytest()
-    assert "TypeError" not in result.stdout.str()
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-
-
-def test_collect_functools_partial(testdir):
-    """
-    Test that collection of functools.partial object works, and arguments
-    to the wrapped functions are dealt correctly (see #811).
-    """
-    testdir.makepyfile("""
-        import functools
-        import pytest
-
-        @pytest.fixture
-        def fix1():
-            return 'fix1'
-
-        @pytest.fixture
-        def fix2():
-            return 'fix2'
-
-        def check1(i, fix1):
-            assert i == 2
-            assert fix1 == 'fix1'
-
-        def check2(fix1, i):
-            assert i == 2
-            assert fix1 == 'fix1'
-
-        def check3(fix1, i, fix2):
-            assert i == 2
-            assert fix1 == 'fix1'
-            assert fix2 == 'fix2'
-
-        test_ok_1 = functools.partial(check1, i=2)
-        test_ok_2 = functools.partial(check1, i=2, fix1='fix1')
-        test_ok_3 = functools.partial(check1, 2)
-        test_ok_4 = functools.partial(check2, i=2)
-        test_ok_5 = functools.partial(check3, i=2)
-        test_ok_6 = functools.partial(check3, i=2, fix1='fix1')
-
-        test_fail_1 = functools.partial(check2, 2)
-        test_fail_2 = functools.partial(check3, 2)
-    """)
-    result = testdir.inline_run()
-    result.assertoutcome(passed=6, failed=2)
-
-
-def test_dont_collect_non_function_callable(testdir):
-    """Test for issue https://github.com/pytest-dev/pytest/issues/331
-
-    In this case an INTERNALERROR occurred trying to report the failure of
-    a test like this one because py test failed to get the source lines.
-    """
-    testdir.makepyfile("""
-        class Oh(object):
-            def __call__(self):
-                pass
-
-        test_a = Oh()
-
-        def test_real():
-            pass
-    """)
-    result = testdir.runpytest('-rw')
-    result.stdout.fnmatch_lines([
-        '*collected 1 item*',
-        'WC2 *',
-        '*1 passed, 1 pytest-warnings in *',
-    ])
-
-
-def test_class_injection_does_not_break_collection(testdir):
-    """Tests whether injection during collection time will terminate testing.
-
-    In this case the error should not occur if the TestClass itself
-    is modified during collection time, and the original method list
-    is still used for collection.
-    """
-    testdir.makeconftest("""
-        from test_inject import TestClass
-        def pytest_generate_tests(metafunc):
-            TestClass.changed_var = {}
-    """)
-    testdir.makepyfile(test_inject='''
-         class TestClass(object):
-            def test_injection(self):
-                """Test being parametrized."""
-                pass
-    ''')
-    result = testdir.runpytest()
-    assert "RuntimeError: dictionary changed size during iteration" not in result.stdout.str()
-    result.stdout.fnmatch_lines(['*1 passed*'])
-
-
-def test_syntax_error_with_non_ascii_chars(testdir):
-    """Fix decoding issue while formatting SyntaxErrors during collection (#578)
-    """
-    testdir.makepyfile(u"""
-    # -*- coding: UTF-8 -*-
-
-    ☃
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        '*ERROR collecting*',
-        '*SyntaxError*',
-        '*1 error in*',
-    ])
diff --git a/tools/pytest/testing/python/fixture.py b/tools/pytest/testing/python/fixture.py
deleted file mode 100644
index 506d842..0000000
--- a/tools/pytest/testing/python/fixture.py
+++ /dev/null
@@ -1,2693 +0,0 @@
-from textwrap import dedent
-
-import _pytest._code
-import pytest
-import sys
-from _pytest import python as funcargs
-from _pytest.pytester import get_public_names
-from _pytest.python import FixtureLookupError
-
-
-def test_getfuncargnames():
-    def f(): pass
-    assert not funcargs.getfuncargnames(f)
-    def g(arg): pass
-    assert funcargs.getfuncargnames(g) == ('arg',)
-    def h(arg1, arg2="hello"): pass
-    assert funcargs.getfuncargnames(h) == ('arg1',)
-    def h(arg1, arg2, arg3="hello"): pass
-    assert funcargs.getfuncargnames(h) == ('arg1', 'arg2')
-    class A:
-        def f(self, arg1, arg2="hello"):
-            pass
-    assert funcargs.getfuncargnames(A().f) == ('arg1',)
-    if sys.version_info < (3,0):
-        assert funcargs.getfuncargnames(A.f) == ('arg1',)
-
-class TestFillFixtures:
-    def test_fillfuncargs_exposed(self):
-        # used by oejskit, kept for compatibility
-        assert pytest._fillfuncargs == funcargs.fillfixtures
-
-    def test_funcarg_lookupfails(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__xyzsomething(request):
-                return 42
-
-            def test_func(some):
-                pass
-        """)
-        result = testdir.runpytest() # "--collect-only")
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "*def test_func(some)*",
-            "*fixture*some*not found*",
-            "*xyzsomething*",
-        ])
-
-    def test_funcarg_basic(self, testdir):
-        item = testdir.getitem("""
-            def pytest_funcarg__some(request):
-                return request.function.__name__
-            def pytest_funcarg__other(request):
-                return 42
-            def test_func(some, other):
-                pass
-        """)
-        funcargs.fillfixtures(item)
-        del item.funcargs["request"]
-        assert len(get_public_names(item.funcargs)) == 2
-        assert item.funcargs['some'] == "test_func"
-        assert item.funcargs['other'] == 42
-
-    def test_funcarg_lookup_modulelevel(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__something(request):
-                return request.function.__name__
-
-            class TestClass:
-                def test_method(self, something):
-                    assert something == "test_method"
-            def test_func(something):
-                assert something == "test_func"
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_funcarg_lookup_classlevel(self, testdir):
-        p = testdir.makepyfile("""
-            class TestClass:
-                def pytest_funcarg__something(self, request):
-                    return request.instance
-                def test_method(self, something):
-                    assert something is self
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*1 passed*"
-        ])
-
-    def test_conftest_funcargs_only_available_in_subdir(self, testdir):
-        sub1 = testdir.mkpydir("sub1")
-        sub2 = testdir.mkpydir("sub2")
-        sub1.join("conftest.py").write(_pytest._code.Source("""
-            import pytest
-            def pytest_funcarg__arg1(request):
-                pytest.raises(Exception, "request.getfuncargvalue('arg2')")
-        """))
-        sub2.join("conftest.py").write(_pytest._code.Source("""
-            import pytest
-            def pytest_funcarg__arg2(request):
-                pytest.raises(Exception, "request.getfuncargvalue('arg1')")
-        """))
-
-        sub1.join("test_in_sub1.py").write("def test_1(arg1): pass")
-        sub2.join("test_in_sub2.py").write("def test_2(arg2): pass")
-        result = testdir.runpytest("-v")
-        result.assert_outcomes(passed=2)
-
-    def test_extend_fixture_module_class(self, testdir):
-        testfile = testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture
-            def spam():
-                return 'spam'
-
-            class TestSpam:
-
-                 @pytest.fixture
-                 def spam(self, spam):
-                     return spam * 2
-
-                 def test_spam(self, spam):
-                     assert spam == 'spamspam'
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(["*1 passed*"])
-        result = testdir.runpytest(testfile)
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_extend_fixture_conftest_module(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture
-            def spam():
-                return 'spam'
-        """)
-        testfile = testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture
-            def spam(spam):
-                return spam * 2
-
-            def test_spam(spam):
-                assert spam == 'spamspam'
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(["*1 passed*"])
-        result = testdir.runpytest(testfile)
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_extend_fixture_conftest_conftest(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture
-            def spam():
-                return 'spam'
-        """)
-        pkg = testdir.mkpydir("pkg")
-        pkg.join("conftest.py").write(_pytest._code.Source("""
-            import pytest
-
-            @pytest.fixture
-            def spam(spam):
-                return spam * 2
-        """))
-        testfile = pkg.join("test_spam.py")
-        testfile.write(_pytest._code.Source("""
-            def test_spam(spam):
-                assert spam == "spamspam"
-        """))
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(["*1 passed*"])
-        result = testdir.runpytest(testfile)
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_extend_fixture_conftest_plugin(self, testdir):
-        testdir.makepyfile(testplugin="""
-            import pytest
-
-            @pytest.fixture
-            def foo():
-                return 7
-        """)
-        testdir.syspathinsert()
-        testdir.makeconftest("""
-            import pytest
-
-            pytest_plugins = 'testplugin'
-
-            @pytest.fixture
-            def foo(foo):
-                return foo + 7
-        """)
-        testdir.makepyfile("""
-            def test_foo(foo):
-                assert foo == 14
-        """)
-        result = testdir.runpytest('-s')
-        assert result.ret == 0
-
-    def test_extend_fixture_plugin_plugin(self, testdir):
-        # Two plugins should extend each order in loading order
-        testdir.makepyfile(testplugin0="""
-            import pytest
-
-            @pytest.fixture
-            def foo():
-                return 7
-        """)
-        testdir.makepyfile(testplugin1="""
-            import pytest
-
-            @pytest.fixture
-            def foo(foo):
-                return foo + 7
-        """)
-        testdir.syspathinsert()
-        testdir.makepyfile("""
-            pytest_plugins = ['testplugin0', 'testplugin1']
-
-            def test_foo(foo):
-                assert foo == 14
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 0
-
-    def test_override_parametrized_fixture_conftest_module(self, testdir):
-        """Test override of the parametrized fixture with non-parametrized one on the test module level."""
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture(params=[1, 2, 3])
-            def spam(request):
-                return request.param
-        """)
-        testfile = testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture
-            def spam():
-                return 'spam'
-
-            def test_spam(spam):
-                assert spam == 'spam'
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(["*1 passed*"])
-        result = testdir.runpytest(testfile)
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_override_parametrized_fixture_conftest_conftest(self, testdir):
-        """Test override of the parametrized fixture with non-parametrized one on the conftest level."""
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture(params=[1, 2, 3])
-            def spam(request):
-                return request.param
-        """)
-        subdir = testdir.mkpydir('subdir')
-        subdir.join("conftest.py").write(_pytest._code.Source("""
-            import pytest
-
-            @pytest.fixture
-            def spam():
-                return 'spam'
-        """))
-        testfile = subdir.join("test_spam.py")
-        testfile.write(_pytest._code.Source("""
-            def test_spam(spam):
-                assert spam == "spam"
-        """))
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(["*1 passed*"])
-        result = testdir.runpytest(testfile)
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_override_non_parametrized_fixture_conftest_module(self, testdir):
-        """Test override of the non-parametrized fixture with parametrized one on the test module level."""
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture
-            def spam():
-                return 'spam'
-        """)
-        testfile = testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(params=[1, 2, 3])
-            def spam(request):
-                return request.param
-
-            params = {'spam': 1}
-
-            def test_spam(spam):
-                assert spam == params['spam']
-                params['spam'] += 1
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(["*3 passed*"])
-        result = testdir.runpytest(testfile)
-        result.stdout.fnmatch_lines(["*3 passed*"])
-
-    def test_override_non_parametrized_fixture_conftest_conftest(self, testdir):
-        """Test override of the non-parametrized fixture with parametrized one on the conftest level."""
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture
-            def spam():
-                return 'spam'
-        """)
-        subdir = testdir.mkpydir('subdir')
-        subdir.join("conftest.py").write(_pytest._code.Source("""
-            import pytest
-
-            @pytest.fixture(params=[1, 2, 3])
-            def spam(request):
-                return request.param
-        """))
-        testfile = subdir.join("test_spam.py")
-        testfile.write(_pytest._code.Source("""
-            params = {'spam': 1}
-
-            def test_spam(spam):
-                assert spam == params['spam']
-                params['spam'] += 1
-        """))
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(["*3 passed*"])
-        result = testdir.runpytest(testfile)
-        result.stdout.fnmatch_lines(["*3 passed*"])
-
-    def test_autouse_fixture_plugin(self, testdir):
-        # A fixture from a plugin has no baseid set, which screwed up
-        # the autouse fixture handling.
-        testdir.makepyfile(testplugin="""
-            import pytest
-
-            @pytest.fixture(autouse=True)
-            def foo(request):
-                request.function.foo = 7
-        """)
-        testdir.syspathinsert()
-        testdir.makepyfile("""
-            pytest_plugins = 'testplugin'
-
-            def test_foo(request):
-                assert request.function.foo == 7
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 0
-
-    def test_funcarg_lookup_error(self, testdir):
-        testdir.makepyfile("""
-            def test_lookup_error(unknown):
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ERROR*test_lookup_error*",
-            "*def test_lookup_error(unknown):*",
-            "*fixture*unknown*not found*",
-            "*available fixtures*",
-            "*1 error*",
-        ])
-        assert "INTERNAL" not in result.stdout.str()
-
-    def test_fixture_excinfo_leak(self, testdir):
-        # on python2 sys.excinfo would leak into fixture executions
-        testdir.makepyfile("""
-            import sys
-            import traceback
-            import pytest
-
-            @pytest.fixture
-            def leak():
-                if sys.exc_info()[0]:  # python3 bug :)
-                    traceback.print_exc()
-                #fails
-                assert sys.exc_info() == (None, None, None)
-
-            def test_leak(leak):
-                if sys.exc_info()[0]:  # python3 bug :)
-                    traceback.print_exc()
-                assert sys.exc_info() == (None, None, None)
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 0
-
-
-class TestRequestBasic:
-    def test_request_attributes(self, testdir):
-        item = testdir.getitem("""
-            def pytest_funcarg__something(request): pass
-            def test_func(something): pass
-        """)
-        req = funcargs.FixtureRequest(item)
-        assert req.function == item.obj
-        assert req.keywords == item.keywords
-        assert hasattr(req.module, 'test_func')
-        assert req.cls is None
-        assert req.function.__name__ == "test_func"
-        assert req.config == item.config
-        assert repr(req).find(req.function.__name__) != -1
-
-    def test_request_attributes_method(self, testdir):
-        item, = testdir.getitems("""
-            class TestB:
-                def pytest_funcarg__something(self, request):
-                    return 1
-                def test_func(self, something):
-                    pass
-        """)
-        req = item._request
-        assert req.cls.__name__ == "TestB"
-        assert req.instance.__class__ == req.cls
-
-    def XXXtest_request_contains_funcarg_arg2fixturedefs(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def pytest_funcarg__something(request):
-                pass
-            class TestClass:
-                def test_method(self, something):
-                    pass
-        """)
-        item1, = testdir.genitems([modcol])
-        assert item1.name == "test_method"
-        arg2fixturedefs = funcargs.FixtureRequest(item1)._arg2fixturedefs
-        assert len(arg2fixturedefs) == 1
-        assert arg2fixturedefs[0].__name__ == "pytest_funcarg__something"
-
-    def test_getfuncargvalue_recursive(self, testdir):
-        testdir.makeconftest("""
-            def pytest_funcarg__something(request):
-                return 1
-        """)
-        testdir.makepyfile("""
-            def pytest_funcarg__something(request):
-                return request.getfuncargvalue("something") + 1
-            def test_func(something):
-                assert something == 2
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_getfuncargvalue(self, testdir):
-        item = testdir.getitem("""
-            l = [2]
-            def pytest_funcarg__something(request): return 1
-            def pytest_funcarg__other(request):
-                return l.pop()
-            def test_func(something): pass
-        """)
-        req = item._request
-        pytest.raises(FixtureLookupError, req.getfuncargvalue, "notexists")
-        val = req.getfuncargvalue("something")
-        assert val == 1
-        val = req.getfuncargvalue("something")
-        assert val == 1
-        val2 = req.getfuncargvalue("other")
-        assert val2 == 2
-        val2 = req.getfuncargvalue("other")  # see about caching
-        assert val2 == 2
-        pytest._fillfuncargs(item)
-        assert item.funcargs["something"] == 1
-        assert len(get_public_names(item.funcargs)) == 2
-        assert "request" in item.funcargs
-        #assert item.funcargs == {'something': 1, "other": 2}
-
-    def test_request_addfinalizer(self, testdir):
-        item = testdir.getitem("""
-            teardownlist = []
-            def pytest_funcarg__something(request):
-                request.addfinalizer(lambda: teardownlist.append(1))
-            def test_func(something): pass
-        """)
-        item.session._setupstate.prepare(item)
-        pytest._fillfuncargs(item)
-        # successively check finalization calls
-        teardownlist = item.getparent(pytest.Module).obj.teardownlist
-        ss = item.session._setupstate
-        assert not teardownlist
-        ss.teardown_exact(item, None)
-        print(ss.stack)
-        assert teardownlist == [1]
-
-    def test_request_addfinalizer_failing_setup(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = [1]
-            @pytest.fixture
-            def myfix(request):
-                request.addfinalizer(l.pop)
-                assert 0
-            def test_fix(myfix):
-                pass
-            def test_finalizer_ran():
-                assert not l
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(failed=1, passed=1)
-
-    def test_request_addfinalizer_failing_setup_module(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = [1, 2]
-            @pytest.fixture(scope="module")
-            def myfix(request):
-                request.addfinalizer(l.pop)
-                request.addfinalizer(l.pop)
-                assert 0
-            def test_fix(myfix):
-                pass
-        """)
-        reprec = testdir.inline_run("-s")
-        mod = reprec.getcalls("pytest_runtest_setup")[0].item.module
-        assert not mod.l
-
-
-    def test_request_addfinalizer_partial_setup_failure(self, testdir):
-        p = testdir.makepyfile("""
-            l = []
-            def pytest_funcarg__something(request):
-                request.addfinalizer(lambda: l.append(None))
-            def test_func(something, missingarg):
-                pass
-            def test_second():
-                assert len(l) == 1
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*1 error*"  # XXX the whole module collection fails
-            ])
-
-    def test_request_getmodulepath(self, testdir):
-        modcol = testdir.getmodulecol("def test_somefunc(): pass")
-        item, = testdir.genitems([modcol])
-        req = funcargs.FixtureRequest(item)
-        assert req.fspath == modcol.fspath
-
-    def test_request_fixturenames(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            from _pytest.pytester import get_public_names
-            @pytest.fixture()
-            def arg1():
-                pass
-            @pytest.fixture()
-            def farg(arg1):
-                pass
-            @pytest.fixture(autouse=True)
-            def sarg(tmpdir):
-                pass
-            def test_function(request, farg):
-                assert set(get_public_names(request.fixturenames)) == \
-                       set(["tmpdir", "sarg", "arg1", "request", "farg",
-                            "tmpdir_factory"])
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_funcargnames_compatattr(self, testdir):
-        testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                assert metafunc.funcargnames == metafunc.fixturenames
-            def pytest_funcarg__fn(request):
-                assert request._pyfuncitem.funcargnames == \
-                       request._pyfuncitem.fixturenames
-                return request.funcargnames, request.fixturenames
-
-            def test_hello(fn):
-                assert fn[0] == fn[1]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_setupdecorator_and_xunit(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(scope='module', autouse=True)
-            def setup_module():
-                l.append("module")
-            @pytest.fixture(autouse=True)
-            def setup_function():
-                l.append("function")
-
-            def test_func():
-                pass
-
-            class TestClass:
-                @pytest.fixture(scope="class", autouse=True)
-                def setup_class(self):
-                    l.append("class")
-                @pytest.fixture(autouse=True)
-                def setup_method(self):
-                    l.append("method")
-                def test_method(self):
-                    pass
-            def test_all():
-                assert l == ["module", "function", "class",
-                             "function", "method", "function"]
-        """)
-        reprec = testdir.inline_run("-v")
-        reprec.assertoutcome(passed=3)
-
-    def test_fixtures_sub_subdir_normalize_sep(self, testdir):
-        # this tests that normalization of nodeids takes place
-        b = testdir.mkdir("tests").mkdir("unit")
-        b.join("conftest.py").write(_pytest._code.Source("""
-            def pytest_funcarg__arg1():
-                pass
-        """))
-        p = b.join("test_module.py")
-        p.write("def test_func(arg1): pass")
-        result = testdir.runpytest(p, "--fixtures")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines("""
-            *fixtures defined*conftest*
-            *arg1*
-        """)
-
-    def test_show_fixtures_color_yes(self, testdir):
-        testdir.makepyfile("def test_this(): assert 1")
-        result = testdir.runpytest('--color=yes', '--fixtures')
-        assert '\x1b[32mtmpdir' in result.stdout.str()
-
-    def test_newstyle_with_request(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture()
-            def arg(request):
-                pass
-            def test_1(arg):
-                pass
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_setupcontext_no_param(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(params=[1,2])
-            def arg(request):
-                return request.param
-
-            @pytest.fixture(autouse=True)
-            def mysetup(request, arg):
-                assert not hasattr(request, "param")
-            def test_1(arg):
-                assert arg in (1,2)
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-class TestRequestMarking:
-    def test_applymarker(self, testdir):
-        item1,item2 = testdir.getitems("""
-            def pytest_funcarg__something(request):
-                pass
-            class TestClass:
-                def test_func1(self, something):
-                    pass
-                def test_func2(self, something):
-                    pass
-        """)
-        req1 = funcargs.FixtureRequest(item1)
-        assert 'xfail' not in item1.keywords
-        req1.applymarker(pytest.mark.xfail)
-        assert 'xfail' in item1.keywords
-        assert 'skipif' not in item1.keywords
-        req1.applymarker(pytest.mark.skipif)
-        assert 'skipif' in item1.keywords
-        pytest.raises(ValueError, "req1.applymarker(42)")
-
-    def test_accesskeywords(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture()
-            def keywords(request):
-                return request.keywords
-            @pytest.mark.XYZ
-            def test_function(keywords):
-                assert keywords["XYZ"]
-                assert "abc" not in keywords
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_accessmarker_dynamic(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            @pytest.fixture()
-            def keywords(request):
-                return request.keywords
-
-            @pytest.fixture(scope="class", autouse=True)
-            def marking(request):
-                request.applymarker(pytest.mark.XYZ("hello"))
-        """)
-        testdir.makepyfile("""
-            import pytest
-            def test_fun1(keywords):
-                assert keywords["XYZ"] is not None
-                assert "abc" not in keywords
-            def test_fun2(keywords):
-                assert keywords["XYZ"] is not None
-                assert "abc" not in keywords
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-class TestRequestCachedSetup:
-    def test_request_cachedsetup_defaultmodule(self, testdir):
-        reprec = testdir.inline_runsource("""
-            mysetup = ["hello",].pop
-
-            def pytest_funcarg__something(request):
-                return request.cached_setup(mysetup, scope="module")
-
-            def test_func1(something):
-                assert something == "hello"
-            class TestClass:
-                def test_func1a(self, something):
-                    assert something == "hello"
-        """)
-        reprec.assertoutcome(passed=2)
-
-    def test_request_cachedsetup_class(self, testdir):
-        reprec = testdir.inline_runsource("""
-            mysetup = ["hello", "hello2", "hello3"].pop
-
-            def pytest_funcarg__something(request):
-                return request.cached_setup(mysetup, scope="class")
-            def test_func1(something):
-                assert something == "hello3"
-            def test_func2(something):
-                assert something == "hello2"
-            class TestClass:
-                def test_func1a(self, something):
-                    assert something == "hello"
-                def test_func2b(self, something):
-                    assert something == "hello"
-        """)
-        reprec.assertoutcome(passed=4)
-
-    def test_request_cachedsetup_extrakey(self, testdir):
-        item1 = testdir.getitem("def test_func(): pass")
-        req1 = funcargs.FixtureRequest(item1)
-        l = ["hello", "world"]
-        def setup():
-            return l.pop()
-        ret1 = req1.cached_setup(setup, extrakey=1)
-        ret2 = req1.cached_setup(setup, extrakey=2)
-        assert ret2 == "hello"
-        assert ret1 == "world"
-        ret1b = req1.cached_setup(setup, extrakey=1)
-        ret2b = req1.cached_setup(setup, extrakey=2)
-        assert ret1 == ret1b
-        assert ret2 == ret2b
-
-    def test_request_cachedsetup_cache_deletion(self, testdir):
-        item1 = testdir.getitem("def test_func(): pass")
-        req1 = funcargs.FixtureRequest(item1)
-        l = []
-        def setup():
-            l.append("setup")
-        def teardown(val):
-            l.append("teardown")
-        req1.cached_setup(setup, teardown, scope="function")
-        assert l == ['setup']
-        # artificial call of finalizer
-        setupstate = req1._pyfuncitem.session._setupstate
-        setupstate._callfinalizers(item1)
-        assert l == ["setup", "teardown"]
-        req1.cached_setup(setup, teardown, scope="function")
-        assert l == ["setup", "teardown", "setup"]
-        setupstate._callfinalizers(item1)
-        assert l == ["setup", "teardown", "setup", "teardown"]
-
-    def test_request_cached_setup_two_args(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__arg1(request):
-                return request.cached_setup(lambda: 42)
-            def pytest_funcarg__arg2(request):
-                return request.cached_setup(lambda: 17)
-            def test_two_different_setups(arg1, arg2):
-                assert arg1 != arg2
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines([
-            "*1 passed*"
-        ])
-
-    def test_request_cached_setup_getfuncargvalue(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__arg1(request):
-                arg1 = request.getfuncargvalue("arg2")
-                return request.cached_setup(lambda: arg1 + 1)
-            def pytest_funcarg__arg2(request):
-                return request.cached_setup(lambda: 10)
-            def test_two_funcarg(arg1):
-                assert arg1 == 11
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines([
-            "*1 passed*"
-        ])
-
-    def test_request_cached_setup_functional(self, testdir):
-        testdir.makepyfile(test_0="""
-            l = []
-            def pytest_funcarg__something(request):
-                val = request.cached_setup(fsetup, fteardown)
-                return val
-            def fsetup(mycache=[1]):
-                l.append(mycache.pop())
-                return l
-            def fteardown(something):
-                l.remove(something[0])
-                l.append(2)
-            def test_list_once(something):
-                assert something == [1]
-            def test_list_twice(something):
-                assert something == [1]
-        """)
-        testdir.makepyfile(test_1="""
-            import test_0 # should have run already
-            def test_check_test0_has_teardown_correct():
-                assert test_0.l == [2]
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines([
-            "*3 passed*"
-        ])
-
-    def test_issue117_sessionscopeteardown(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__app(request):
-                app = request.cached_setup(
-                    scope='session',
-                    setup=lambda: 0,
-                    teardown=lambda x: 3/x)
-                return app
-            def test_func(app):
-                pass
-        """)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "*3/x*",
-            "*ZeroDivisionError*",
-        ])
-
-class TestFixtureUsages:
-    def test_noargfixturedec(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture
-            def arg1():
-                return 1
-
-            def test_func(arg1):
-                assert arg1 == 1
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_receives_funcargs(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture()
-            def arg1():
-                return 1
-
-            @pytest.fixture()
-            def arg2(arg1):
-                return arg1 + 1
-
-            def test_add(arg2):
-                assert arg2 == 2
-            def test_all(arg1, arg2):
-                assert arg1 == 1
-                assert arg2 == 2
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_receives_funcargs_scope_mismatch(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope="function")
-            def arg1():
-                return 1
-
-            @pytest.fixture(scope="module")
-            def arg2(arg1):
-                return arg1 + 1
-
-            def test_add(arg2):
-                assert arg2 == 2
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ScopeMismatch*involved factories*",
-            "* def arg2*",
-            "* def arg1*",
-            "*1 error*"
-        ])
-
-    def test_receives_funcargs_scope_mismatch_issue660(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope="function")
-            def arg1():
-                return 1
-
-            @pytest.fixture(scope="module")
-            def arg2(arg1):
-                return arg1 + 1
-
-            def test_add(arg1, arg2):
-                assert arg2 == 2
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ScopeMismatch*involved factories*",
-            "* def arg2*",
-            "*1 error*"
-        ])
-
-    def test_funcarg_parametrized_and_used_twice(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(params=[1,2])
-            def arg1(request):
-                l.append(1)
-                return request.param
-
-            @pytest.fixture()
-            def arg2(arg1):
-                return arg1 + 1
-
-            def test_add(arg1, arg2):
-                assert arg2 == arg1 + 1
-                assert len(l) == arg1
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*2 passed*"
-        ])
-
-    def test_factory_uses_unknown_funcarg_as_dependency_error(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture()
-            def fail(missing):
-                return
-
-            @pytest.fixture()
-            def call_fail(fail):
-                return
-
-            def test_missing(call_fail):
-                pass
-            """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines("""
-            *pytest.fixture()*
-            *def call_fail(fail)*
-            *pytest.fixture()*
-            *def fail*
-            *fixture*'missing'*not found*
-        """)
-
-    def test_factory_setup_as_classes_fails(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            class arg1:
-                def __init__(self, request):
-                    self.x = 1
-            arg1 = pytest.fixture()(arg1)
-
-        """)
-        reprec = testdir.inline_run()
-        l = reprec.getfailedcollections()
-        assert len(l) == 1
-
-    def test_request_can_be_overridden(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture()
-            def request(request):
-                request.a = 1
-                return request
-            def test_request(request):
-                assert request.a == 1
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_usefixtures_marker(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            l = []
-
-            @pytest.fixture(scope="class")
-            def myfix(request):
-                request.cls.hello = "world"
-                l.append(1)
-
-            class TestClass:
-                def test_one(self):
-                    assert self.hello == "world"
-                    assert len(l) == 1
-                def test_two(self):
-                    assert self.hello == "world"
-                    assert len(l) == 1
-            pytest.mark.usefixtures("myfix")(TestClass)
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_usefixtures_ini(self, testdir):
-        testdir.makeini("""
-            [pytest]
-            usefixtures = myfix
-        """)
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture(scope="class")
-            def myfix(request):
-                request.cls.hello = "world"
-
-        """)
-        testdir.makepyfile("""
-            class TestClass:
-                def test_one(self):
-                    assert self.hello == "world"
-                def test_two(self):
-                    assert self.hello == "world"
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_usefixtures_seen_in_showmarkers(self, testdir):
-        result = testdir.runpytest("--markers")
-        result.stdout.fnmatch_lines("""
-            *usefixtures(fixturename1*mark tests*fixtures*
-        """)
-
-    def test_request_instance_issue203(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            class TestClass:
-                @pytest.fixture
-                def setup1(self, request):
-                    assert self == request.instance
-                    self.arg1 = 1
-                def test_hello(self, setup1):
-                    assert self.arg1 == 1
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_fixture_parametrized_with_iterator(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            l = []
-            def f():
-                yield 1
-                yield 2
-            dec = pytest.fixture(scope="module", params=f())
-
-            @dec
-            def arg(request):
-                return request.param
-            @dec
-            def arg2(request):
-                return request.param
-
-            def test_1(arg):
-                l.append(arg)
-            def test_2(arg2):
-                l.append(arg2*10)
-        """)
-        reprec = testdir.inline_run("-v")
-        reprec.assertoutcome(passed=4)
-        l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
-        assert l == [1,2, 10,20]
-
-
-class TestFixtureManagerParseFactories:
-    def pytest_funcarg__testdir(self, request):
-        testdir = request.getfuncargvalue("testdir")
-        testdir.makeconftest("""
-            def pytest_funcarg__hello(request):
-                return "conftest"
-
-            def pytest_funcarg__fm(request):
-                return request._fixturemanager
-
-            def pytest_funcarg__item(request):
-                return request._pyfuncitem
-        """)
-        return testdir
-
-    def test_parsefactories_evil_objects_issue214(self, testdir):
-        testdir.makepyfile("""
-            class A:
-                def __call__(self):
-                    pass
-                def __getattr__(self, name):
-                    raise RuntimeError()
-            a = A()
-            def test_hello():
-                pass
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1, failed=0)
-
-    def test_parsefactories_conftest(self, testdir):
-        testdir.makepyfile("""
-            def test_hello(item, fm):
-                for name in ("fm", "hello", "item"):
-                    faclist = fm.getfixturedefs(name, item.nodeid)
-                    assert len(faclist) == 1
-                    fac = faclist[0]
-                    assert fac.func.__name__ == "pytest_funcarg__" + name
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=1)
-
-    def test_parsefactories_conftest_and_module_and_class(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__hello(request):
-                return "module"
-            class TestClass:
-                def pytest_funcarg__hello(self, request):
-                    return "class"
-                def test_hello(self, item, fm):
-                    faclist = fm.getfixturedefs("hello", item.nodeid)
-                    print (faclist)
-                    assert len(faclist) == 3
-                    assert faclist[0].func(item._request) == "conftest"
-                    assert faclist[1].func(item._request) == "module"
-                    assert faclist[2].func(item._request) == "class"
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=1)
-
-    def test_parsefactories_relative_node_ids(self, testdir):
-        # example mostly taken from:
-        # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html
-        runner = testdir.mkdir("runner")
-        package = testdir.mkdir("package")
-        package.join("conftest.py").write(dedent("""\
-            import pytest
-            @pytest.fixture
-            def one():
-                return 1
-        """))
-        package.join("test_x.py").write(dedent("""\
-            def test_x(one):
-                assert one == 1
-        """))
-        sub = package.mkdir("sub")
-        sub.join("__init__.py").ensure()
-        sub.join("conftest.py").write(dedent("""\
-            import pytest
-            @pytest.fixture
-            def one():
-                return 2
-        """))
-        sub.join("test_y.py").write(dedent("""\
-            def test_x(one):
-                assert one == 2
-        """))
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-        with runner.as_cwd():
-            reprec = testdir.inline_run("..")
-            reprec.assertoutcome(passed=2)
-
-
-class TestAutouseDiscovery:
-    def pytest_funcarg__testdir(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            @pytest.fixture(autouse=True)
-            def perfunction(request, tmpdir):
-                pass
-
-            @pytest.fixture()
-            def arg1(tmpdir):
-                pass
-            @pytest.fixture(autouse=True)
-            def perfunction2(arg1):
-                pass
-
-            def pytest_funcarg__fm(request):
-                return request._fixturemanager
-
-            def pytest_funcarg__item(request):
-                return request._pyfuncitem
-        """)
-        return testdir
-
-    def test_parsefactories_conftest(self, testdir):
-        testdir.makepyfile("""
-            from _pytest.pytester import get_public_names
-            def test_check_setup(item, fm):
-                autousenames = fm._getautousenames(item.nodeid)
-                assert len(get_public_names(autousenames)) == 2
-                assert "perfunction2" in autousenames
-                assert "perfunction" in autousenames
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=1)
-
-    def test_two_classes_separated_autouse(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            class TestA:
-                l = []
-                @pytest.fixture(autouse=True)
-                def setup1(self):
-                    self.l.append(1)
-                def test_setup1(self):
-                    assert self.l == [1]
-            class TestB:
-                l = []
-                @pytest.fixture(autouse=True)
-                def setup2(self):
-                    self.l.append(1)
-                def test_setup2(self):
-                    assert self.l == [1]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_setup_at_classlevel(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            class TestClass:
-                @pytest.fixture(autouse=True)
-                def permethod(self, request):
-                    request.instance.funcname = request.function.__name__
-                def test_method1(self):
-                    assert self.funcname == "test_method1"
-                def test_method2(self):
-                    assert self.funcname == "test_method2"
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=2)
-
-    @pytest.mark.xfail(reason="'enabled' feature not implemented")
-    def test_setup_enabled_functionnode(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            def enabled(parentnode, markers):
-                return "needsdb" in markers
-
-            @pytest.fixture(params=[1,2])
-            def db(request):
-                return request.param
-
-            @pytest.fixture(enabled=enabled, autouse=True)
-            def createdb(db):
-                pass
-
-            def test_func1(request):
-                assert "db" not in request.fixturenames
-
-            @pytest.mark.needsdb
-            def test_func2(request):
-                assert "db" in request.fixturenames
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=2)
-
-    def test_callables_nocode(self, testdir):
-        """
-        a imported mock.call would break setup/factory discovery
-        due to it being callable and __code__ not being a code object
-        """
-        testdir.makepyfile("""
-           class _call(tuple):
-               def __call__(self, *k, **kw):
-                   pass
-               def __getattr__(self, k):
-                   return self
-
-           call = _call()
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(failed=0, passed=0)
-
-    def test_autouse_in_conftests(self, testdir):
-        a = testdir.mkdir("a")
-        b = testdir.mkdir("a1")
-        conftest = testdir.makeconftest("""
-            import pytest
-            @pytest.fixture(autouse=True)
-            def hello():
-                xxx
-        """)
-        conftest.move(a.join(conftest.basename))
-        a.join("test_something.py").write("def test_func(): pass")
-        b.join("test_otherthing.py").write("def test_func(): pass")
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines("""
-            *1 passed*1 error*
-        """)
-
-    def test_autouse_in_module_and_two_classes(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(autouse=True)
-            def append1():
-                l.append("module")
-            def test_x():
-                assert l == ["module"]
-
-            class TestA:
-                @pytest.fixture(autouse=True)
-                def append2(self):
-                    l.append("A")
-                def test_hello(self):
-                    assert l == ["module", "module", "A"], l
-            class TestA2:
-                def test_world(self):
-                    assert l == ["module", "module", "A", "module"], l
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=3)
-
-
-class TestAutouseManagement:
-    def test_autouse_conftest_mid_directory(self, testdir):
-        pkgdir = testdir.mkpydir("xyz123")
-        pkgdir.join("conftest.py").write(_pytest._code.Source("""
-            import pytest
-            @pytest.fixture(autouse=True)
-            def app():
-                import sys
-                sys._myapp = "hello"
-        """))
-        t = pkgdir.ensure("tests", "test_app.py")
-        t.write(_pytest._code.Source("""
-            import sys
-            def test_app():
-                assert sys._myapp == "hello"
-        """))
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=1)
-
-    def test_autouse_honored_for_yield(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(autouse=True)
-            def tst():
-                global x
-                x = 3
-            def test_gen():
-                def f(hello):
-                    assert x == abs(hello)
-                yield f, 3
-                yield f, -3
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-
-
-    def test_funcarg_and_setup(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(scope="module")
-            def arg():
-                l.append(1)
-                return 0
-            @pytest.fixture(scope="module", autouse=True)
-            def something(arg):
-                l.append(2)
-
-            def test_hello(arg):
-                assert len(l) == 2
-                assert l == [1,2]
-                assert arg == 0
-
-            def test_hello2(arg):
-                assert len(l) == 2
-                assert l == [1,2]
-                assert arg == 0
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_uses_parametrized_resource(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(params=[1,2])
-            def arg(request):
-                return request.param
-
-            @pytest.fixture(autouse=True)
-            def something(arg):
-                l.append(arg)
-
-            def test_hello():
-                if len(l) == 1:
-                    assert l == [1]
-                elif len(l) == 2:
-                    assert l == [1, 2]
-                else:
-                    0/0
-
-        """)
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=2)
-
-    def test_session_parametrized_function(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            l = []
-
-            @pytest.fixture(scope="session", params=[1,2])
-            def arg(request):
-               return request.param
-
-            @pytest.fixture(scope="function", autouse=True)
-            def append(request, arg):
-                if request.function.__name__ == "test_some":
-                    l.append(arg)
-
-            def test_some():
-                pass
-
-            def test_result(arg):
-                assert len(l) == arg
-                assert l[:arg] == [1,2][:arg]
-        """)
-        reprec = testdir.inline_run("-v", "-s")
-        reprec.assertoutcome(passed=4)
-
-    def test_class_function_parametrization_finalization(self, testdir):
-        p = testdir.makeconftest("""
-            import pytest
-            import pprint
-
-            l = []
-
-            @pytest.fixture(scope="function", params=[1,2])
-            def farg(request):
-                return request.param
-
-            @pytest.fixture(scope="class", params=list("ab"))
-            def carg(request):
-                return request.param
-
-            @pytest.fixture(scope="function", autouse=True)
-            def append(request, farg, carg):
-                def fin():
-                    l.append("fin_%s%s" % (carg, farg))
-                request.addfinalizer(fin)
-        """)
-        testdir.makepyfile("""
-            import pytest
-
-            class TestClass:
-                def test_1(self):
-                    pass
-            class TestClass2:
-                def test_2(self):
-                    pass
-        """)
-        reprec = testdir.inline_run("-v","-s")
-        reprec.assertoutcome(passed=8)
-        config = reprec.getcalls("pytest_unconfigure")[0].config
-        l = config.pluginmanager._getconftestmodules(p)[0].l
-        assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
-
-    def test_scope_ordering(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(scope="function", autouse=True)
-            def fappend2():
-                l.append(2)
-            @pytest.fixture(scope="class", autouse=True)
-            def classappend3():
-                l.append(3)
-            @pytest.fixture(scope="module", autouse=True)
-            def mappend():
-                l.append(1)
-
-            class TestHallo:
-                def test_method(self):
-                    assert l == [1,3,2]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_parametrization_setup_teardown_ordering(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            def pytest_generate_tests(metafunc):
-                if metafunc.cls is not None:
-                    metafunc.parametrize("item", [1,2], scope="class")
-            class TestClass:
-                @pytest.fixture(scope="class", autouse=True)
-                def addteardown(self, item, request):
-                    l.append("setup-%d" % item)
-                    request.addfinalizer(lambda: l.append("teardown-%d" % item))
-                def test_step1(self, item):
-                    l.append("step1-%d" % item)
-                def test_step2(self, item):
-                    l.append("step2-%d" % item)
-
-            def test_finish():
-                print (l)
-                assert l == ["setup-1", "step1-1", "step2-1", "teardown-1",
-                             "setup-2", "step1-2", "step2-2", "teardown-2",]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=5)
-
-    def test_ordering_autouse_before_explicit(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            l = []
-            @pytest.fixture(autouse=True)
-            def fix1():
-                l.append(1)
-            @pytest.fixture()
-            def arg1():
-                l.append(2)
-            def test_hello(arg1):
-                assert l == [1,2]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    @pytest.mark.issue226
-    @pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00","p01"])
-    @pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10","p11"])
-    def test_ordering_dependencies_torndown_first(self, testdir, param1, param2):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(%(param1)s)
-            def arg1(request):
-                request.addfinalizer(lambda: l.append("fin1"))
-                l.append("new1")
-            @pytest.fixture(%(param2)s)
-            def arg2(request, arg1):
-                request.addfinalizer(lambda: l.append("fin2"))
-                l.append("new2")
-
-            def test_arg(arg2):
-                pass
-            def test_check():
-                assert l == ["new1", "new2", "fin2", "fin1"]
-        """ % locals())
-        reprec = testdir.inline_run("-s")
-        reprec.assertoutcome(passed=2)
-
-
-class TestFixtureMarker:
-    def test_parametrize(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(params=["a", "b", "c"])
-            def arg(request):
-                return request.param
-            l = []
-            def test_param(arg):
-                l.append(arg)
-            def test_result():
-                assert l == list("abc")
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=4)
-
-    def test_multiple_parametrization_issue_736(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(params=[1,2,3])
-            def foo(request):
-                return request.param
-
-            @pytest.mark.parametrize('foobar', [4,5,6])
-            def test_issue(foo, foobar):
-                assert foo in [1,2,3]
-                assert foobar in [4,5,6]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=9)
-
-    @pytest.mark.parametrize('param_args', ["'fixt, val'", "'fixt,val'", "['fixt', 'val']", "('fixt', 'val')"])
-    def test_override_parametrized_fixture_issue_979(self, testdir, param_args):
-        """Make sure a parametrized argument can override a parametrized fixture.
-
-        This was a regression introduced in the fix for #736.
-        """
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(params=[1, 2])
-            def fixt(request):
-                return request.param
-
-            @pytest.mark.parametrize(%s, [(3, 'x'), (4, 'x')])
-            def test_foo(fixt, val):
-                pass
-        """ % param_args)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_scope_session(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(scope="module")
-            def arg():
-                l.append(1)
-                return 1
-
-            def test_1(arg):
-                assert arg == 1
-            def test_2(arg):
-                assert arg == 1
-                assert len(l) == 1
-            class TestClass:
-                def test3(self, arg):
-                    assert arg == 1
-                    assert len(l) == 1
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=3)
-
-    def test_scope_session_exc(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(scope="session")
-            def fix():
-                l.append(1)
-                pytest.skip('skipping')
-
-            def test_1(fix):
-                pass
-            def test_2(fix):
-                pass
-            def test_last():
-                assert l == [1]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(skipped=2, passed=1)
-
-    def test_scope_session_exc_two_fix(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            m = []
-            @pytest.fixture(scope="session")
-            def a():
-                l.append(1)
-                pytest.skip('skipping')
-            @pytest.fixture(scope="session")
-            def b(a):
-                m.append(1)
-
-            def test_1(b):
-                pass
-            def test_2(b):
-                pass
-            def test_last():
-                assert l == [1]
-                assert m == []
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(skipped=2, passed=1)
-
-    def test_scope_exc(self, testdir):
-        testdir.makepyfile(
-            test_foo="""
-                def test_foo(fix):
-                    pass
-            """,
-            test_bar="""
-                def test_bar(fix):
-                    pass
-            """,
-            conftest="""
-                import pytest
-                reqs = []
-                @pytest.fixture(scope="session")
-                def fix(request):
-                    reqs.append(1)
-                    pytest.skip()
-                @pytest.fixture
-                def req_list():
-                    return reqs
-            """,
-            test_real="""
-                def test_last(req_list):
-                    assert req_list == [1]
-            """
-        )
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(skipped=2, passed=1)
-
-    def test_scope_module_uses_session(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(scope="module")
-            def arg():
-                l.append(1)
-                return 1
-
-            def test_1(arg):
-                assert arg == 1
-            def test_2(arg):
-                assert arg == 1
-                assert len(l) == 1
-            class TestClass:
-                def test3(self, arg):
-                    assert arg == 1
-                    assert len(l) == 1
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=3)
-
-    def test_scope_module_and_finalizer(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            finalized = []
-            created = []
-            @pytest.fixture(scope="module")
-            def arg(request):
-                created.append(1)
-                assert request.scope == "module"
-                request.addfinalizer(lambda: finalized.append(1))
-            def pytest_funcarg__created(request):
-                return len(created)
-            def pytest_funcarg__finalized(request):
-                return len(finalized)
-        """)
-        testdir.makepyfile(
-            test_mod1="""
-                def test_1(arg, created, finalized):
-                    assert created == 1
-                    assert finalized == 0
-                def test_2(arg, created, finalized):
-                    assert created == 1
-                    assert finalized == 0""",
-            test_mod2="""
-                def test_3(arg, created, finalized):
-                    assert created == 2
-                    assert finalized == 1""",
-            test_mode3="""
-                def test_4(arg, created, finalized):
-                    assert created == 3
-                    assert finalized == 2
-            """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=4)
-
-    @pytest.mark.parametrize("method", [
-        'request.getfuncargvalue("arg")',
-        'request.cached_setup(lambda: None, scope="function")',
-    ], ids=["getfuncargvalue", "cached_setup"])
-    def test_scope_mismatch_various(self, testdir, method):
-        testdir.makeconftest("""
-            import pytest
-            finalized = []
-            created = []
-            @pytest.fixture(scope="function")
-            def arg(request):
-                pass
-        """)
-        testdir.makepyfile(
-            test_mod1="""
-                import pytest
-                @pytest.fixture(scope="session")
-                def arg(request):
-                    %s
-                def test_1(arg):
-                    pass
-            """ % method)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "*ScopeMismatch*You tried*function*session*request*",
-        ])
-
-    def test_register_only_with_mark(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            @pytest.fixture()
-            def arg():
-                return 1
-        """)
-        testdir.makepyfile(
-            test_mod1="""
-                import pytest
-                @pytest.fixture()
-                def arg(arg):
-                    return arg + 1
-                def test_1(arg):
-                    assert arg == 2
-            """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_parametrize_and_scope(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope="module", params=["a", "b", "c"])
-            def arg(request):
-                return request.param
-            l = []
-            def test_param(arg):
-                l.append(arg)
-        """)
-        reprec = testdir.inline_run("-v")
-        reprec.assertoutcome(passed=3)
-        l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
-        assert len(l) == 3
-        assert "a" in l
-        assert "b" in l
-        assert "c" in l
-
-    def test_scope_mismatch(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            @pytest.fixture(scope="function")
-            def arg(request):
-                pass
-        """)
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope="session")
-            def arg(arg):
-                pass
-            def test_mismatch(arg):
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ScopeMismatch*",
-            "*1 error*",
-        ])
-
-    def test_parametrize_separated_order(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(scope="module", params=[1, 2])
-            def arg(request):
-                return request.param
-
-            l = []
-            def test_1(arg):
-                l.append(arg)
-            def test_2(arg):
-                l.append(arg)
-        """)
-        reprec = testdir.inline_run("-v")
-        reprec.assertoutcome(passed=4)
-        l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
-        assert l == [1,1,2,2]
-
-    def test_module_parametrized_ordering(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-
-            @pytest.fixture(scope="session", params="s1 s2".split())
-            def sarg():
-                pass
-            @pytest.fixture(scope="module", params="m1 m2".split())
-            def marg():
-                pass
-        """)
-        testdir.makepyfile(test_mod1="""
-            def test_func(sarg):
-                pass
-            def test_func1(marg):
-                pass
-        """, test_mod2="""
-            def test_func2(sarg):
-                pass
-            def test_func3(sarg, marg):
-                pass
-            def test_func3b(sarg, marg):
-                pass
-            def test_func4(marg):
-                pass
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines("""
-            test_mod1.py::test_func[s1] PASSED
-            test_mod2.py::test_func2[s1] PASSED
-            test_mod2.py::test_func3[s1-m1] PASSED
-            test_mod2.py::test_func3b[s1-m1] PASSED
-            test_mod2.py::test_func3[s1-m2] PASSED
-            test_mod2.py::test_func3b[s1-m2] PASSED
-            test_mod1.py::test_func[s2] PASSED
-            test_mod2.py::test_func2[s2] PASSED
-            test_mod2.py::test_func3[s2-m1] PASSED
-            test_mod2.py::test_func3b[s2-m1] PASSED
-            test_mod2.py::test_func4[m1] PASSED
-            test_mod2.py::test_func3[s2-m2] PASSED
-            test_mod2.py::test_func3b[s2-m2] PASSED
-            test_mod2.py::test_func4[m2] PASSED
-            test_mod1.py::test_func1[m1] PASSED
-            test_mod1.py::test_func1[m2] PASSED
-        """)
-
-    def test_class_ordering(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-
-            l = []
-
-            @pytest.fixture(scope="function", params=[1,2])
-            def farg(request):
-                return request.param
-
-            @pytest.fixture(scope="class", params=list("ab"))
-            def carg(request):
-                return request.param
-
-            @pytest.fixture(scope="function", autouse=True)
-            def append(request, farg, carg):
-                def fin():
-                    l.append("fin_%s%s" % (carg, farg))
-                request.addfinalizer(fin)
-        """)
-        testdir.makepyfile("""
-            import pytest
-
-            class TestClass2:
-                def test_1(self):
-                    pass
-                def test_2(self):
-                    pass
-            class TestClass:
-                def test_3(self):
-                    pass
-        """)
-        result = testdir.runpytest("-vs")
-        result.stdout.fnmatch_lines("""
-            test_class_ordering.py::TestClass2::test_1[1-a] PASSED
-            test_class_ordering.py::TestClass2::test_1[2-a] PASSED
-            test_class_ordering.py::TestClass2::test_2[1-a] PASSED
-            test_class_ordering.py::TestClass2::test_2[2-a] PASSED
-            test_class_ordering.py::TestClass2::test_1[1-b] PASSED
-            test_class_ordering.py::TestClass2::test_1[2-b] PASSED
-            test_class_ordering.py::TestClass2::test_2[1-b] PASSED
-            test_class_ordering.py::TestClass2::test_2[2-b] PASSED
-            test_class_ordering.py::TestClass::test_3[1-a] PASSED
-            test_class_ordering.py::TestClass::test_3[2-a] PASSED
-            test_class_ordering.py::TestClass::test_3[1-b] PASSED
-            test_class_ordering.py::TestClass::test_3[2-b] PASSED
-        """)
-
-    def test_parametrize_separated_order_higher_scope_first(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(scope="function", params=[1, 2])
-            def arg(request):
-                param = request.param
-                request.addfinalizer(lambda: l.append("fin:%s" % param))
-                l.append("create:%s" % param)
-                return request.param
-
-            @pytest.fixture(scope="module", params=["mod1", "mod2"])
-            def modarg(request):
-                param = request.param
-                request.addfinalizer(lambda: l.append("fin:%s" % param))
-                l.append("create:%s" % param)
-                return request.param
-
-            l = []
-            def test_1(arg):
-                l.append("test1")
-            def test_2(modarg):
-                l.append("test2")
-            def test_3(arg, modarg):
-                l.append("test3")
-            def test_4(modarg, arg):
-                l.append("test4")
-        """)
-        reprec = testdir.inline_run("-v")
-        reprec.assertoutcome(passed=12)
-        l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
-        expected = [
-            'create:1', 'test1', 'fin:1', 'create:2', 'test1',
-            'fin:2', 'create:mod1', 'test2', 'create:1', 'test3',
-            'fin:1', 'create:2', 'test3', 'fin:2', 'create:1',
-            'test4', 'fin:1', 'create:2', 'test4', 'fin:2',
-            'fin:mod1', 'create:mod2', 'test2', 'create:1', 'test3',
-            'fin:1', 'create:2', 'test3', 'fin:2', 'create:1',
-            'test4', 'fin:1', 'create:2', 'test4', 'fin:2',
-        'fin:mod2']
-        import pprint
-        pprint.pprint(list(zip(l, expected)))
-        assert l == expected
-
-    def test_parametrized_fixture_teardown_order(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(params=[1,2], scope="class")
-            def param1(request):
-                return request.param
-
-            l = []
-
-            class TestClass:
-                @classmethod
-                @pytest.fixture(scope="class", autouse=True)
-                def setup1(self, request, param1):
-                    l.append(1)
-                    request.addfinalizer(self.teardown1)
-                @classmethod
-                def teardown1(self):
-                    assert l.pop() == 1
-                @pytest.fixture(scope="class", autouse=True)
-                def setup2(self, request, param1):
-                    l.append(2)
-                    request.addfinalizer(self.teardown2)
-                @classmethod
-                def teardown2(self):
-                    assert l.pop() == 2
-                def test(self):
-                    pass
-
-            def test_finish():
-                assert not l
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines("""
-            *3 passed*
-        """)
-        assert "error" not in result.stdout.str()
-
-    def test_fixture_finalizer(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            import sys
-
-            @pytest.fixture
-            def browser(request):
-
-                def finalize():
-                    sys.stdout.write('Finalized')
-                request.addfinalizer(finalize)
-                return {}
-        """)
-        b = testdir.mkdir("subdir")
-        b.join("test_overriden_fixture_finalizer.py").write(dedent("""
-            import pytest
-            @pytest.fixture
-            def browser(browser):
-                browser['visited'] = True
-                return browser
-
-            def test_browser(browser):
-                assert browser['visited'] is True
-        """))
-        reprec = testdir.runpytest("-s")
-        for test in ['test_browser']:
-            reprec.stdout.fnmatch_lines('*Finalized*')
-
-    def test_class_scope_with_normal_tests(self, testdir):
-        testpath = testdir.makepyfile("""
-            import pytest
-
-            class Box:
-                value = 0
-
-            @pytest.fixture(scope='class')
-            def a(request):
-                Box.value += 1
-                return Box.value
-
-            def test_a(a):
-                assert a == 1
-
-            class Test1:
-                def test_b(self, a):
-                    assert a == 2
-
-            class Test2:
-                def test_c(self, a):
-                    assert a == 3""")
-        reprec = testdir.inline_run(testpath)
-        for test in ['test_a', 'test_b', 'test_c']:
-            assert reprec.matchreport(test).passed
-
-    def test_request_is_clean(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(params=[1, 2])
-            def fix(request):
-                request.addfinalizer(lambda: l.append(request.param))
-            def test_fix(fix):
-                pass
-        """)
-        reprec = testdir.inline_run("-s")
-        l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
-        assert l == [1,2]
-
-    def test_parametrize_separated_lifecycle(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            l = []
-            @pytest.fixture(scope="module", params=[1, 2])
-            def arg(request):
-                x = request.param
-                request.addfinalizer(lambda: l.append("fin%s" % x))
-                return request.param
-            def test_1(arg):
-                l.append(arg)
-            def test_2(arg):
-                l.append(arg)
-        """)
-        reprec = testdir.inline_run("-vs")
-        reprec.assertoutcome(passed=4)
-        l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
-        import pprint
-        pprint.pprint(l)
-        #assert len(l) == 6
-        assert l[0] == l[1] == 1
-        assert l[2] == "fin1"
-        assert l[3] == l[4] == 2
-        assert l[5] == "fin2"
-
-    def test_parametrize_function_scoped_finalizers_called(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(scope="function", params=[1, 2])
-            def arg(request):
-                x = request.param
-                request.addfinalizer(lambda: l.append("fin%s" % x))
-                return request.param
-
-            l = []
-            def test_1(arg):
-                l.append(arg)
-            def test_2(arg):
-                l.append(arg)
-            def test_3():
-                assert len(l) == 8
-                assert l == [1, "fin1", 2, "fin2", 1, "fin1", 2, "fin2"]
-        """)
-        reprec = testdir.inline_run("-v")
-        reprec.assertoutcome(passed=5)
-
-
-    @pytest.mark.issue246
-    @pytest.mark.parametrize("scope", ["session", "function", "module"])
-    def test_finalizer_order_on_parametrization(self, scope, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-
-            @pytest.fixture(scope=%(scope)r, params=["1"])
-            def fix1(request):
-                return request.param
-
-            @pytest.fixture(scope=%(scope)r)
-            def fix2(request, base):
-                def cleanup_fix2():
-                    assert not l, "base should not have been finalized"
-                request.addfinalizer(cleanup_fix2)
-
-            @pytest.fixture(scope=%(scope)r)
-            def base(request, fix1):
-                def cleanup_base():
-                    l.append("fin_base")
-                    print ("finalizing base")
-                request.addfinalizer(cleanup_base)
-
-            def test_begin():
-                pass
-            def test_baz(base, fix2):
-                pass
-            def test_other():
-                pass
-        """ % {"scope": scope})
-        reprec = testdir.inline_run("-lvs")
-        reprec.assertoutcome(passed=3)
-
-    @pytest.mark.issue396
-    def test_class_scope_parametrization_ordering(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            @pytest.fixture(params=["John", "Doe"], scope="class")
-            def human(request):
-                request.addfinalizer(lambda: l.append("fin %s" % request.param))
-                return request.param
-
-            class TestGreetings:
-                def test_hello(self, human):
-                    l.append("test_hello")
-
-            class TestMetrics:
-                def test_name(self, human):
-                    l.append("test_name")
-
-                def test_population(self, human):
-                    l.append("test_population")
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=6)
-        l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
-        assert l == ["test_hello", "fin John", "test_hello", "fin Doe",
-                     "test_name", "test_population", "fin John",
-                     "test_name", "test_population", "fin Doe"]
-
-    def test_parametrize_setup_function(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(scope="module", params=[1, 2])
-            def arg(request):
-                return request.param
-
-            @pytest.fixture(scope="module", autouse=True)
-            def mysetup(request, arg):
-                request.addfinalizer(lambda: l.append("fin%s" % arg))
-                l.append("setup%s" % arg)
-
-            l = []
-            def test_1(arg):
-                l.append(arg)
-            def test_2(arg):
-                l.append(arg)
-            def test_3():
-                import pprint
-                pprint.pprint(l)
-                if arg == 1:
-                    assert l == ["setup1", 1, 1, ]
-                elif arg == 2:
-                    assert l == ["setup1", 1, 1, "fin1",
-                                 "setup2", 2, 2, ]
-
-        """)
-        reprec = testdir.inline_run("-v")
-        reprec.assertoutcome(passed=6)
-
-    def test_fixture_marked_function_not_collected_as_test(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture
-            def test_app():
-                return 1
-
-            def test_something(test_app):
-                assert test_app == 1
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_params_and_ids(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(params=[object(), object()],
-                            ids=['alpha', 'beta'])
-            def fix(request):
-                return request.param
-
-            def test_foo(fix):
-                assert 1
-        """)
-        res = testdir.runpytest('-v')
-        res.stdout.fnmatch_lines([
-            '*test_foo*alpha*',
-            '*test_foo*beta*'])
-
-    def test_params_and_ids_yieldfixture(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.yield_fixture(params=[object(), object()],
-                                  ids=['alpha', 'beta'])
-            def fix(request):
-                 yield request.param
-
-            def test_foo(fix):
-                assert 1
-        """)
-        res = testdir.runpytest('-v')
-        res.stdout.fnmatch_lines([
-            '*test_foo*alpha*',
-            '*test_foo*beta*'])
-
-
-class TestRequestScopeAccess:
-    pytestmark = pytest.mark.parametrize(("scope", "ok", "error"),[
-        ["session", "", "fspath class function module"],
-        ["module", "module fspath", "cls function"],
-        ["class", "module fspath cls", "function"],
-        ["function", "module fspath cls function", ""]
-    ])
-
-    def test_setup(self, testdir, scope, ok, error):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope=%r, autouse=True)
-            def myscoped(request):
-                for x in %r:
-                    assert hasattr(request, x)
-                for x in %r:
-                    pytest.raises(AttributeError, lambda:
-                        getattr(request, x))
-                assert request.session
-                assert request.config
-            def test_func():
-                pass
-        """ %(scope, ok.split(), error.split()))
-        reprec = testdir.inline_run("-l")
-        reprec.assertoutcome(passed=1)
-
-    def test_funcarg(self, testdir, scope, ok, error):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope=%r)
-            def arg(request):
-                for x in %r:
-                    assert hasattr(request, x)
-                for x in %r:
-                    pytest.raises(AttributeError, lambda:
-                        getattr(request, x))
-                assert request.session
-                assert request.config
-            def test_func(arg):
-                pass
-        """ %(scope, ok.split(), error.split()))
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-class TestErrors:
-    def test_subfactory_missing_funcarg(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture()
-            def gen(qwe123):
-                return 1
-            def test_something(gen):
-                pass
-        """)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "*def gen(qwe123):*",
-            "*fixture*qwe123*not found*",
-            "*1 error*",
-        ])
-
-    def test_issue498_fixture_finalizer_failing(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture
-            def fix1(request):
-                def f():
-                    raise KeyError
-                request.addfinalizer(f)
-                return object()
-
-            l = []
-            def test_1(fix1):
-                l.append(fix1)
-            def test_2(fix1):
-                l.append(fix1)
-            def test_3():
-                assert l[0] != l[1]
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines("""
-            *ERROR*teardown*test_1*
-            *KeyError*
-            *ERROR*teardown*test_2*
-            *KeyError*
-            *3 pass*2 error*
-        """)
-
-
-
-    def test_setupfunc_missing_funcarg(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(autouse=True)
-            def gen(qwe123):
-                return 1
-            def test_something():
-                pass
-        """)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "*def gen(qwe123):*",
-            "*fixture*qwe123*not found*",
-            "*1 error*",
-        ])
-
-class TestShowFixtures:
-    def test_funcarg_compat(self, testdir):
-        config = testdir.parseconfigure("--funcargs")
-        assert config.option.showfixtures
-
-    def test_show_fixtures(self, testdir):
-        result = testdir.runpytest("--fixtures")
-        result.stdout.fnmatch_lines([
-                "*tmpdir*",
-                "*temporary directory*",
-            ]
-        )
-
-    def test_show_fixtures_verbose(self, testdir):
-        result = testdir.runpytest("--fixtures", "-v")
-        result.stdout.fnmatch_lines([
-                "*tmpdir*--*tmpdir.py*",
-                "*temporary directory*",
-            ]
-        )
-
-    def test_show_fixtures_testmodule(self, testdir):
-        p = testdir.makepyfile('''
-            import pytest
-            @pytest.fixture
-            def _arg0():
-                """ hidden """
-            @pytest.fixture
-            def arg1():
-                """  hello world """
-        ''')
-        result = testdir.runpytest("--fixtures", p)
-        result.stdout.fnmatch_lines("""
-            *tmpdir
-            *fixtures defined from*
-            *arg1*
-            *hello world*
-        """)
-        assert "arg0" not in result.stdout.str()
-
-    @pytest.mark.parametrize("testmod", [True, False])
-    def test_show_fixtures_conftest(self, testdir, testmod):
-        testdir.makeconftest('''
-            import pytest
-            @pytest.fixture
-            def arg1():
-                """  hello world """
-        ''')
-        if testmod:
-            testdir.makepyfile("""
-                def test_hello():
-                    pass
-            """)
-        result = testdir.runpytest("--fixtures")
-        result.stdout.fnmatch_lines("""
-            *tmpdir*
-            *fixtures defined from*conftest*
-            *arg1*
-            *hello world*
-        """)
-
-    def test_show_fixtures_trimmed_doc(self, testdir):
-        p = testdir.makepyfile('''
-            import pytest
-            @pytest.fixture
-            def arg1():
-                """
-                line1
-                line2
-
-                """
-            @pytest.fixture
-            def arg2():
-                """
-                line1
-                line2
-
-                """
-        ''')
-        result = testdir.runpytest("--fixtures", p)
-        result.stdout.fnmatch_lines("""
-            * fixtures defined from test_show_fixtures_trimmed_doc *
-            arg2
-                line1
-                line2
-            arg1
-                line1
-                line2
-
-        """)
-
-
-    def test_show_fixtures_different_files(self, testdir):
-        """
-        #833: --fixtures only shows fixtures from first file
-        """
-        testdir.makepyfile(test_a='''
-            import pytest
-
-            @pytest.fixture
-            def fix_a():
-                """Fixture A"""
-                pass
-
-            def test_a(fix_a):
-                pass
-        ''')
-        testdir.makepyfile(test_b='''
-            import pytest
-
-            @pytest.fixture
-            def fix_b():
-                """Fixture B"""
-                pass
-
-            def test_b(fix_b):
-                pass
-        ''')
-        result = testdir.runpytest("--fixtures")
-        result.stdout.fnmatch_lines("""
-            * fixtures defined from test_a *
-            fix_a
-                Fixture A
-
-            * fixtures defined from test_b *
-            fix_b
-                Fixture B
-        """)
-
-
-class TestContextManagerFixtureFuncs:
-    def test_simple(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.yield_fixture
-            def arg1():
-                print ("setup")
-                yield 1
-                print ("teardown")
-            def test_1(arg1):
-                print ("test1 %s" % arg1)
-            def test_2(arg1):
-                print ("test2 %s" % arg1)
-                assert 0
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *setup*
-            *test1 1*
-            *teardown*
-            *setup*
-            *test2 1*
-            *teardown*
-        """)
-
-    def test_scoped(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.yield_fixture(scope="module")
-            def arg1():
-                print ("setup")
-                yield 1
-                print ("teardown")
-            def test_1(arg1):
-                print ("test1 %s" % arg1)
-            def test_2(arg1):
-                print ("test2 %s" % arg1)
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *setup*
-            *test1 1*
-            *test2 1*
-            *teardown*
-        """)
-
-    def test_setup_exception(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.yield_fixture(scope="module")
-            def arg1():
-                pytest.fail("setup")
-                yield 1
-            def test_1(arg1):
-                pass
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *pytest.fail*setup*
-            *1 error*
-        """)
-
-    def test_teardown_exception(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.yield_fixture(scope="module")
-            def arg1():
-                yield 1
-                pytest.fail("teardown")
-            def test_1(arg1):
-                pass
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *pytest.fail*teardown*
-            *1 passed*1 error*
-        """)
-
-    def test_yields_more_than_one(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.yield_fixture(scope="module")
-            def arg1():
-                yield 1
-                yield 2
-            def test_1(arg1):
-                pass
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *fixture function*
-            *test_yields*:2*
-        """)
-
-
-    def test_no_yield(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.yield_fixture(scope="module")
-            def arg1():
-                return 1
-            def test_1(arg1):
-                pass
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *yield_fixture*requires*yield*
-            *yield_fixture*
-            *def arg1*
-        """)
-
-    def test_yield_not_allowed_in_non_yield(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope="module")
-            def arg1():
-                yield 1
-            def test_1(arg1):
-                pass
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *fixture*cannot use*yield*
-            *def arg1*
-        """)
-
diff --git a/tools/pytest/testing/python/integration.py b/tools/pytest/testing/python/integration.py
deleted file mode 100644
index dea86f9..0000000
--- a/tools/pytest/testing/python/integration.py
+++ /dev/null
@@ -1,369 +0,0 @@
-import pytest
-from _pytest import python
-from _pytest import runner
-
-
-class TestOEJSKITSpecials:
-    def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage
-        testdir.makeconftest("""
-            import pytest
-            def pytest_pycollect_makeitem(collector, name, obj):
-                if name == "MyClass":
-                    return MyCollector(name, parent=collector)
-            class MyCollector(pytest.Collector):
-                def reportinfo(self):
-                    return self.fspath, 3, "xyz"
-        """)
-        modcol = testdir.getmodulecol("""
-            def pytest_funcarg__arg1(request):
-                return 42
-            class MyClass:
-                pass
-        """)
-        # this hook finds funcarg factories
-        rep = runner.collect_one_node(collector=modcol)
-        clscol = rep.result[0]
-        clscol.obj = lambda arg1: None
-        clscol.funcargs = {}
-        pytest._fillfuncargs(clscol)
-        assert clscol.funcargs['arg1'] == 42
-
-    def test_autouse_fixture(self, testdir): # rough jstests usage
-        testdir.makeconftest("""
-            import pytest
-            def pytest_pycollect_makeitem(collector, name, obj):
-                if name == "MyClass":
-                    return MyCollector(name, parent=collector)
-            class MyCollector(pytest.Collector):
-                def reportinfo(self):
-                    return self.fspath, 3, "xyz"
-        """)
-        modcol = testdir.getmodulecol("""
-            import pytest
-            @pytest.fixture(autouse=True)
-            def hello():
-                pass
-            def pytest_funcarg__arg1(request):
-                return 42
-            class MyClass:
-                pass
-        """)
-        # this hook finds funcarg factories
-        rep = runner.collect_one_node(modcol)
-        clscol = rep.result[0]
-        clscol.obj = lambda: None
-        clscol.funcargs = {}
-        pytest._fillfuncargs(clscol)
-        assert not clscol.funcargs
-
-
-def test_wrapped_getfslineno():
-    def func():
-        pass
-    def wrap(f):
-        func.__wrapped__ = f
-        func.patchings = ["qwe"]
-        return func
-    @wrap
-    def wrapped_func(x, y, z):
-        pass
-    fs, lineno = python.getfslineno(wrapped_func)
-    fs2, lineno2 = python.getfslineno(wrap)
-    assert lineno > lineno2, "getfslineno does not unwrap correctly"
-
-class TestMockDecoration:
-    def test_wrapped_getfuncargnames(self):
-        from _pytest.python import getfuncargnames
-        def wrap(f):
-            def func():
-                pass
-            func.__wrapped__ = f
-            return func
-        @wrap
-        def f(x):
-            pass
-        l = getfuncargnames(f)
-        assert l == ("x",)
-
-    def test_wrapped_getfuncargnames_patching(self):
-        from _pytest.python import getfuncargnames
-        def wrap(f):
-            def func():
-                pass
-            func.__wrapped__ = f
-            func.patchings = ["qwe"]
-            return func
-        @wrap
-        def f(x, y, z):
-            pass
-        l = getfuncargnames(f)
-        assert l == ("y", "z")
-
-    def test_unittest_mock(self, testdir):
-        pytest.importorskip("unittest.mock")
-        testdir.makepyfile("""
-            import unittest.mock
-            class T(unittest.TestCase):
-                @unittest.mock.patch("os.path.abspath")
-                def test_hello(self, abspath):
-                    import os
-                    os.path.abspath("hello")
-                    abspath.assert_any_call("hello")
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_unittest_mock_and_fixture(self, testdir):
-        pytest.importorskip("unittest.mock")
-        testdir.makepyfile("""
-            import os.path
-            import unittest.mock
-            import pytest
-
-            @pytest.fixture
-            def inject_me():
-                pass
-
-            @unittest.mock.patch.object(os.path, "abspath",
-                                        new=unittest.mock.MagicMock)
-            def test_hello(inject_me):
-                import os
-                os.path.abspath("hello")
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_mock(self, testdir):
-        pytest.importorskip("mock", "1.0.1")
-        testdir.makepyfile("""
-            import os
-            import unittest
-            import mock
-
-            class T(unittest.TestCase):
-                @mock.patch("os.path.abspath")
-                def test_hello(self, abspath):
-                    os.path.abspath("hello")
-                    abspath.assert_any_call("hello")
-            def mock_basename(path):
-                return "mock_basename"
-            @mock.patch("os.path.abspath")
-            @mock.patch("os.path.normpath")
-            @mock.patch("os.path.basename", new=mock_basename)
-            def test_someting(normpath, abspath, tmpdir):
-                abspath.return_value = "this"
-                os.path.normpath(os.path.abspath("hello"))
-                normpath.assert_any_call("this")
-                assert os.path.basename("123") == "mock_basename"
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-        calls = reprec.getcalls("pytest_runtest_logreport")
-        funcnames = [call.report.location[2] for call in calls
-                        if call.report.when == "call"]
-        assert funcnames == ["T.test_hello", "test_someting"]
-
-    def test_mock_sorting(self, testdir):
-        pytest.importorskip("mock", "1.0.1")
-        testdir.makepyfile("""
-            import os
-            import mock
-
-            @mock.patch("os.path.abspath")
-            def test_one(abspath):
-                pass
-            @mock.patch("os.path.abspath")
-            def test_two(abspath):
-                pass
-            @mock.patch("os.path.abspath")
-            def test_three(abspath):
-                pass
-        """)
-        reprec = testdir.inline_run()
-        calls = reprec.getreports("pytest_runtest_logreport")
-        calls = [x for x in calls if x.when == "call"]
-        names = [x.nodeid.split("::")[-1] for x in calls]
-        assert names == ["test_one", "test_two", "test_three"]
-
-    def test_mock_double_patch_issue473(self, testdir):
-        pytest.importorskip("mock", "1.0.1")
-        testdir.makepyfile("""
-            from mock import patch
-            from pytest import mark
-
-            @patch('os.getcwd')
-            @patch('os.path')
-            @mark.slow
-            class TestSimple:
-                def test_simple_thing(self, mock_path, mock_getcwd):
-                    pass
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-
-class TestReRunTests:
-    def test_rerun(self, testdir):
-        testdir.makeconftest("""
-            from _pytest.runner import runtestprotocol
-            def pytest_runtest_protocol(item, nextitem):
-                runtestprotocol(item, log=False, nextitem=nextitem)
-                runtestprotocol(item, log=True, nextitem=nextitem)
-        """)
-        testdir.makepyfile("""
-            import pytest
-            count = 0
-            req = None
-            @pytest.fixture
-            def fix(request):
-                global count, req
-                assert request != req
-                req = request
-                print ("fix count %s" % count)
-                count += 1
-            def test_fix(fix):
-                pass
-        """)
-        result = testdir.runpytest("-s")
-        result.stdout.fnmatch_lines("""
-            *fix count 0*
-            *fix count 1*
-        """)
-        result.stdout.fnmatch_lines("""
-            *2 passed*
-        """)
-
-def test_pytestconfig_is_session_scoped():
-    from _pytest.python import pytestconfig
-    assert pytestconfig._pytestfixturefunction.scope == "session"
-
-
-class TestNoselikeTestAttribute:
-    def test_module_with_global_test(self, testdir):
-        testdir.makepyfile("""
-            __test__ = False
-            def test_hello():
-                pass
-        """)
-        reprec = testdir.inline_run()
-        assert not reprec.getfailedcollections()
-        calls = reprec.getreports("pytest_runtest_logreport")
-        assert not calls
-
-    def test_class_and_method(self, testdir):
-        testdir.makepyfile("""
-            __test__ = True
-            def test_func():
-                pass
-            test_func.__test__ = False
-
-            class TestSome:
-                __test__ = False
-                def test_method(self):
-                    pass
-        """)
-        reprec = testdir.inline_run()
-        assert not reprec.getfailedcollections()
-        calls = reprec.getreports("pytest_runtest_logreport")
-        assert not calls
-
-    def test_unittest_class(self, testdir):
-        testdir.makepyfile("""
-            import unittest
-            class TC(unittest.TestCase):
-                def test_1(self):
-                    pass
-            class TC2(unittest.TestCase):
-                __test__ = False
-                def test_2(self):
-                    pass
-        """)
-        reprec = testdir.inline_run()
-        assert not reprec.getfailedcollections()
-        call = reprec.getcalls("pytest_collection_modifyitems")[0]
-        assert len(call.items) == 1
-        assert call.items[0].cls.__name__ == "TC"
-
-    def test_class_with_nasty_getattr(self, testdir):
-        """Make sure we handle classes with a custom nasty __getattr__ right.
-
-        With a custom __getattr__ which e.g. returns a function (like with a
-        RPC wrapper), we shouldn't assume this meant "__test__ = True".
-        """
-        # https://github.com/pytest-dev/pytest/issues/1204
-        testdir.makepyfile("""
-            class MetaModel(type):
-
-                def __getattr__(cls, key):
-                    return lambda: None
-
-
-            BaseModel = MetaModel('Model', (), {})
-
-
-            class Model(BaseModel):
-
-                __metaclass__ = MetaModel
-
-                def test_blah(self):
-                    pass
-        """)
-        reprec = testdir.inline_run()
-        assert not reprec.getfailedcollections()
-        call = reprec.getcalls("pytest_collection_modifyitems")[0]
-        assert not call.items
-
-
-@pytest.mark.issue351
-class TestParameterize:
-
-    def test_idfn_marker(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            def idfn(param):
-                if param == 0:
-                    return 'spam'
-                elif param == 1:
-                    return 'ham'
-                else:
-                    return None
-
-            @pytest.mark.parametrize('a,b', [(0, 2), (1, 2)], ids=idfn)
-            def test_params(a, b):
-                pass
-        """)
-        res = testdir.runpytest('--collect-only')
-        res.stdout.fnmatch_lines([
-            "*spam-2*",
-            "*ham-2*",
-        ])
-
-    def test_idfn_fixture(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            def idfn(param):
-                if param == 0:
-                    return 'spam'
-                elif param == 1:
-                    return 'ham'
-                else:
-                    return None
-
-            @pytest.fixture(params=[0, 1], ids=idfn)
-            def a(request):
-                return request.param
-
-            @pytest.fixture(params=[1, 2], ids=idfn)
-            def b(request):
-                return request.param
-
-            def test_params(a, b):
-                pass
-        """)
-        res = testdir.runpytest('--collect-only')
-        res.stdout.fnmatch_lines([
-            "*spam-2*",
-            "*ham-2*",
-        ])
diff --git a/tools/pytest/testing/python/metafunc.py b/tools/pytest/testing/python/metafunc.py
deleted file mode 100644
index faa687f..0000000
--- a/tools/pytest/testing/python/metafunc.py
+++ /dev/null
@@ -1,1094 +0,0 @@
-# -*- coding: utf-8 -*-
-import re
-
-import _pytest._code
-import py
-import pytest
-from _pytest import python as funcargs
-
-class TestMetafunc:
-    def Metafunc(self, func):
-        # the unit tests of this class check if things work correctly
-        # on the funcarg level, so we don't need a full blown
-        # initiliazation
-        class FixtureInfo:
-            name2fixturedefs = None
-            def __init__(self, names):
-                self.names_closure = names
-        names = funcargs.getfuncargnames(func)
-        fixtureinfo = FixtureInfo(names)
-        return funcargs.Metafunc(func, fixtureinfo, None)
-
-    def test_no_funcargs(self, testdir):
-        def function(): pass
-        metafunc = self.Metafunc(function)
-        assert not metafunc.fixturenames
-        repr(metafunc._calls)
-
-    def test_function_basic(self):
-        def func(arg1, arg2="qwe"): pass
-        metafunc = self.Metafunc(func)
-        assert len(metafunc.fixturenames) == 1
-        assert 'arg1' in metafunc.fixturenames
-        assert metafunc.function is func
-        assert metafunc.cls is None
-
-    def test_addcall_no_args(self):
-        def func(arg1): pass
-        metafunc = self.Metafunc(func)
-        metafunc.addcall()
-        assert len(metafunc._calls) == 1
-        call = metafunc._calls[0]
-        assert call.id == "0"
-        assert not hasattr(call, 'param')
-
-    def test_addcall_id(self):
-        def func(arg1): pass
-        metafunc = self.Metafunc(func)
-        pytest.raises(ValueError, "metafunc.addcall(id=None)")
-
-        metafunc.addcall(id=1)
-        pytest.raises(ValueError, "metafunc.addcall(id=1)")
-        pytest.raises(ValueError, "metafunc.addcall(id='1')")
-        metafunc.addcall(id=2)
-        assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].id == "1"
-        assert metafunc._calls[1].id == "2"
-
-    def test_addcall_param(self):
-        def func(arg1): pass
-        metafunc = self.Metafunc(func)
-        class obj: pass
-        metafunc.addcall(param=obj)
-        metafunc.addcall(param=obj)
-        metafunc.addcall(param=1)
-        assert len(metafunc._calls) == 3
-        assert metafunc._calls[0].getparam("arg1") == obj
-        assert metafunc._calls[1].getparam("arg1") == obj
-        assert metafunc._calls[2].getparam("arg1") == 1
-
-    def test_addcall_funcargs(self):
-        def func(x): pass
-        metafunc = self.Metafunc(func)
-        class obj: pass
-        metafunc.addcall(funcargs={"x": 2})
-        metafunc.addcall(funcargs={"x": 3})
-        pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})")
-        assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == {'x': 2}
-        assert metafunc._calls[1].funcargs == {'x': 3}
-        assert not hasattr(metafunc._calls[1], 'param')
-
-    def test_parametrize_error(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        metafunc.parametrize("x", [1,2])
-        pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
-        pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
-        metafunc.parametrize("y", [1,2])
-        pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
-        pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
-
-    def test_parametrize_and_id(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-
-        metafunc.parametrize("x", [1,2], ids=['basic', 'advanced'])
-        metafunc.parametrize("y", ["abc", "def"])
-        ids = [x.id for x in metafunc._calls]
-        assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"]
-
-    def test_parametrize_with_wrong_number_of_ids(self, testdir):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-
-        pytest.raises(ValueError, lambda:
-            metafunc.parametrize("x", [1,2], ids=['basic']))
-
-        pytest.raises(ValueError, lambda:
-            metafunc.parametrize(("x","y"), [("abc", "def"),
-                                             ("ghi", "jkl")], ids=["one"]))
-
-    def test_parametrize_with_userobjects(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        class A:
-            pass
-        metafunc.parametrize("x", [A(), A()])
-        metafunc.parametrize("y", list("ab"))
-        assert metafunc._calls[0].id == "x0-a"
-        assert metafunc._calls[1].id == "x0-b"
-        assert metafunc._calls[2].id == "x1-a"
-        assert metafunc._calls[3].id == "x1-b"
-
-    @pytest.mark.skipif('sys.version_info[0] >= 3')
-    def test_unicode_idval_python2(self):
-        """unittest for the expected behavior to obtain ids for parametrized
-        unicode values in Python 2: if convertible to ascii, they should appear
-        as ascii values, otherwise fallback to hide the value behind the name
-        of the parametrized variable name. #1086
-        """
-        from _pytest.python import _idval
-        values = [
-            (u'', ''),
-            (u'ascii', 'ascii'),
-            (u'ação', 'a6'),
-            (u'josé@blah.com', 'a6'),
-            (u'δοκ.ιμή@παράδειγμα.δοκιμή', 'a6'),
-        ]
-        for val, expected in values:
-            assert _idval(val, 'a', 6, None) == expected
-
-    def test_bytes_idval(self):
-        """unittest for the expected behavior to obtain ids for parametrized
-        bytes values:
-        - python2: non-ascii strings are considered bytes and formatted using
-        "binary escape", where any byte < 127 is escaped into its hex form.
-        - python3: bytes objects are always escaped using "binary escape".
-        """
-        from _pytest.python import _idval
-        values = [
-            (b'', ''),
-            (b'\xc3\xb4\xff\xe4', '\\xc3\\xb4\\xff\\xe4'),
-            (b'ascii', 'ascii'),
-            (u'αρά'.encode('utf-8'), '\\xce\\xb1\\xcf\\x81\\xce\\xac'),
-        ]
-        for val, expected in values:
-            assert _idval(val, 'a', 6, None) == expected
-
-    @pytest.mark.issue250
-    def test_idmaker_autoname(self):
-        from _pytest.python import idmaker
-        result = idmaker(("a", "b"), [("string", 1.0),
-                                      ("st-ring", 2.0)])
-        assert result == ["string-1.0", "st-ring-2.0"]
-
-        result = idmaker(("a", "b"), [(object(), 1.0),
-                                      (object(), object())])
-        assert result == ["a0-1.0", "a1-b1"]
-        # unicode mixing, issue250
-        result = idmaker((py.builtin._totext("a"), "b"), [({}, b'\xc3\xb4')])
-        assert result == ['a0-\\xc3\\xb4']
-
-    def test_idmaker_with_bytes_regex(self):
-        from _pytest.python import idmaker
-        result = idmaker(("a"), [(re.compile(b'foo'), 1.0)])
-        assert result == ["foo"]
-
-    def test_idmaker_native_strings(self):
-        from _pytest.python import idmaker
-        totext = py.builtin._totext
-        result = idmaker(("a", "b"), [(1.0, -1.1),
-                                      (2, -202),
-                                      ("three", "three hundred"),
-                                      (True, False),
-                                      (None, None),
-                                      (re.compile('foo'), re.compile('bar')),
-                                      (str, int),
-                                      (list("six"), [66, 66]),
-                                      (set([7]), set("seven")),
-                                      (tuple("eight"), (8, -8, 8)),
-                                      (b'\xc3\xb4', b"name"),
-                                      (b'\xc3\xb4', totext("other")),
-        ])
-        assert result == ["1.0--1.1",
-                          "2--202",
-                          "three-three hundred",
-                          "True-False",
-                          "None-None",
-                          "foo-bar",
-                          "str-int",
-                          "a7-b7",
-                          "a8-b8",
-                          "a9-b9",
-                          "\\xc3\\xb4-name",
-                          "\\xc3\\xb4-other",
-                          ]
-
-    def test_idmaker_enum(self):
-        from _pytest.python import idmaker
-        enum = pytest.importorskip("enum")
-        e = enum.Enum("Foo", "one, two")
-        result = idmaker(("a", "b"), [(e.one, e.two)])
-        assert result == ["Foo.one-Foo.two"]
-
-    @pytest.mark.issue351
-    def test_idmaker_idfn(self):
-        from _pytest.python import idmaker
-        def ids(val):
-            if isinstance(val, Exception):
-                return repr(val)
-
-        result = idmaker(("a", "b"), [(10.0, IndexError()),
-                                      (20, KeyError()),
-                                      ("three", [1, 2, 3]),
-        ], idfn=ids)
-        assert result == ["10.0-IndexError()",
-                          "20-KeyError()",
-                          "three-b2",
-                         ]
-
-    @pytest.mark.issue351
-    def test_idmaker_idfn_unique_names(self):
-        from _pytest.python import idmaker
-        def ids(val):
-            return 'a'
-
-        result = idmaker(("a", "b"), [(10.0, IndexError()),
-                                      (20, KeyError()),
-                                      ("three", [1, 2, 3]),
-        ], idfn=ids)
-        assert result == ["0a-a",
-                          "1a-a",
-                          "2a-a",
-                         ]
-
-    @pytest.mark.issue351
-    def test_idmaker_idfn_exception(self):
-        from _pytest.python import idmaker
-        def ids(val):
-            raise Exception("bad code")
-
-        result = idmaker(("a", "b"), [(10.0, IndexError()),
-                                      (20, KeyError()),
-                                      ("three", [1, 2, 3]),
-        ], idfn=ids)
-        assert result == ["10.0-b0",
-                          "20-b1",
-                          "three-b2",
-                         ]
-
-    def test_addcall_and_parametrize(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        metafunc.addcall({'x': 1})
-        metafunc.parametrize('y', [2,3])
-        assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == {'x': 1, 'y': 2}
-        assert metafunc._calls[1].funcargs == {'x': 1, 'y': 3}
-        assert metafunc._calls[0].id == "0-2"
-        assert metafunc._calls[1].id == "0-3"
-
-    @pytest.mark.issue714
-    def test_parametrize_indirect(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        metafunc.parametrize('x', [1], indirect=True)
-        metafunc.parametrize('y', [2,3], indirect=True)
-        assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == {}
-        assert metafunc._calls[1].funcargs == {}
-        assert metafunc._calls[0].params == dict(x=1,y=2)
-        assert metafunc._calls[1].params == dict(x=1,y=3)
-
-    @pytest.mark.issue714
-    def test_parametrize_indirect_list(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        metafunc.parametrize('x, y', [('a', 'b')], indirect=['x'])
-        assert metafunc._calls[0].funcargs == dict(y='b')
-        assert metafunc._calls[0].params == dict(x='a')
-
-    @pytest.mark.issue714
-    def test_parametrize_indirect_list_all(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'y'])
-        assert metafunc._calls[0].funcargs == {}
-        assert metafunc._calls[0].params == dict(x='a', y='b')
-
-    @pytest.mark.issue714
-    def test_parametrize_indirect_list_empty(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        metafunc.parametrize('x, y', [('a', 'b')], indirect=[])
-        assert metafunc._calls[0].funcargs == dict(x='a', y='b')
-        assert metafunc._calls[0].params == {}
-
-    @pytest.mark.issue714
-    def test_parametrize_indirect_list_functional(self, testdir):
-        """
-        Test parametrization with 'indirect' parameter applied on
-        particular arguments. As y is is direct, its value should
-        be used directly rather than being passed to the fixture
-        y.
-
-        :param testdir: the instance of Testdir class, a temporary
-        test directory.
-        """
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope='function')
-            def x(request):
-                return request.param * 3
-            @pytest.fixture(scope='function')
-            def y(request):
-                return request.param * 2
-            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
-            def test_simple(x,y):
-                assert len(x) == 3
-                assert len(y) == 1
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines([
-            "*test_simple*a-b*",
-            "*1 passed*",
-        ])
-
-    @pytest.mark.issue714
-    def test_parametrize_indirect_list_error(self, testdir):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        with pytest.raises(ValueError):
-            metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'z'])
-
-    @pytest.mark.issue714
-    def test_parametrize_uses_no_fixture_error_indirect_false(self, testdir):
-        """The 'uses no fixture' error tells the user at collection time
-        that the parametrize data they've set up doesn't correspond to the
-        fixtures in their test function, rather than silently ignoring this
-        and letting the test potentially pass.
-        """
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=False)
-            def test_simple(x):
-                assert len(x) == 3
-        """)
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*uses no fixture 'y'*",
-        ])
-
-    @pytest.mark.issue714
-    def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope='function')
-            def x(request):
-                return request.param * 3
-            @pytest.fixture(scope='function')
-            def y(request):
-                return request.param * 2
-
-            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=True)
-            def test_simple(x):
-                assert len(x) == 3
-        """)
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*uses no fixture 'y'*",
-        ])
-
-    @pytest.mark.issue714
-    def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope='function')
-            def x(request):
-                return request.param * 3
-
-            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
-            def test_simple(x):
-                assert len(x) == 3
-        """)
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*uses no fixture 'y'*",
-        ])
-
-    def test_addcalls_and_parametrize_indirect(self):
-        def func(x, y): pass
-        metafunc = self.Metafunc(func)
-        metafunc.addcall(param="123")
-        metafunc.parametrize('x', [1], indirect=True)
-        metafunc.parametrize('y', [2,3], indirect=True)
-        assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == {}
-        assert metafunc._calls[1].funcargs == {}
-        assert metafunc._calls[0].params == dict(x=1,y=2)
-        assert metafunc._calls[1].params == dict(x=1,y=3)
-
-    def test_parametrize_functional(self, testdir):
-        testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.parametrize('x', [1,2], indirect=True)
-                metafunc.parametrize('y', [2])
-            def pytest_funcarg__x(request):
-                return request.param * 10
-            #def pytest_funcarg__y(request):
-            #    return request.param
-
-            def test_simple(x,y):
-                assert x in (10,20)
-                assert y == 2
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines([
-            "*test_simple*1-2*",
-            "*test_simple*2-2*",
-            "*2 passed*",
-        ])
-
-    def test_parametrize_onearg(self):
-        metafunc = self.Metafunc(lambda x: None)
-        metafunc.parametrize("x", [1,2])
-        assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == dict(x=1)
-        assert metafunc._calls[0].id == "1"
-        assert metafunc._calls[1].funcargs == dict(x=2)
-        assert metafunc._calls[1].id == "2"
-
-    def test_parametrize_onearg_indirect(self):
-        metafunc = self.Metafunc(lambda x: None)
-        metafunc.parametrize("x", [1,2], indirect=True)
-        assert metafunc._calls[0].params == dict(x=1)
-        assert metafunc._calls[0].id == "1"
-        assert metafunc._calls[1].params == dict(x=2)
-        assert metafunc._calls[1].id == "2"
-
-    def test_parametrize_twoargs(self):
-        metafunc = self.Metafunc(lambda x,y: None)
-        metafunc.parametrize(("x", "y"), [(1,2), (3,4)])
-        assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == dict(x=1, y=2)
-        assert metafunc._calls[0].id == "1-2"
-        assert metafunc._calls[1].funcargs == dict(x=3, y=4)
-        assert metafunc._calls[1].id == "3-4"
-
-    def test_parametrize_multiple_times(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            pytestmark = pytest.mark.parametrize("x", [1,2])
-            def test_func(x):
-                assert 0, x
-            class TestClass:
-                pytestmark = pytest.mark.parametrize("y", [3,4])
-                def test_meth(self, x, y):
-                    assert 0, x
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 1
-        result.assert_outcomes(failed=6)
-
-    def test_parametrize_CSV(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.parametrize("x, y,", [(1,2), (2,3)])
-            def test_func(x, y):
-                assert x+1 == y
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
-
-    def test_parametrize_class_scenarios(self, testdir):
-        testdir.makepyfile("""
-        # same as doc/en/example/parametrize scenario example
-        def pytest_generate_tests(metafunc):
-            idlist = []
-            argvalues = []
-            for scenario in metafunc.cls.scenarios:
-                idlist.append(scenario[0])
-                items = scenario[1].items()
-                argnames = [x[0] for x in items]
-                argvalues.append(([x[1] for x in items]))
-            metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
-
-        class Test(object):
-               scenarios = [['1', {'arg': {1: 2}, "arg2": "value2"}],
-                            ['2', {'arg':'value2', "arg2": "value2"}]]
-
-               def test_1(self, arg, arg2):
-                  pass
-
-               def test_2(self, arg2, arg):
-                  pass
-
-               def test_3(self, arg, arg2):
-                  pass
-        """)
-        result = testdir.runpytest("-v")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines("""
-            *test_1*1*
-            *test_2*1*
-            *test_3*1*
-            *test_1*2*
-            *test_2*2*
-            *test_3*2*
-            *6 passed*
-        """)
-
-    def test_format_args(self):
-        def function1(): pass
-        assert funcargs._format_args(function1) == '()'
-
-        def function2(arg1): pass
-        assert funcargs._format_args(function2) == "(arg1)"
-
-        def function3(arg1, arg2="qwe"): pass
-        assert funcargs._format_args(function3) == "(arg1, arg2='qwe')"
-
-        def function4(arg1, *args, **kwargs): pass
-        assert funcargs._format_args(function4) == "(arg1, *args, **kwargs)"
-
-
-class TestMetafuncFunctional:
-    def test_attributes(self, testdir):
-        p = testdir.makepyfile("""
-            # assumes that generate/provide runs in the same process
-            import py, pytest
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall(param=metafunc)
-
-            def pytest_funcarg__metafunc(request):
-                assert request._pyfuncitem._genid == "0"
-                return request.param
-
-            def test_function(metafunc, pytestconfig):
-                assert metafunc.config == pytestconfig
-                assert metafunc.module.__name__ == __name__
-                assert metafunc.function == test_function
-                assert metafunc.cls is None
-
-            class TestClass:
-                def test_method(self, metafunc, pytestconfig):
-                    assert metafunc.config == pytestconfig
-                    assert metafunc.module.__name__ == __name__
-                    if py.std.sys.version_info > (3, 0):
-                        unbound = TestClass.test_method
-                    else:
-                        unbound = TestClass.test_method.im_func
-                    # XXX actually have an unbound test function here?
-                    assert metafunc.function == unbound
-                    assert metafunc.cls == TestClass
-        """)
-        result = testdir.runpytest(p, "-v")
-        result.assert_outcomes(passed=2)
-
-    def test_addcall_with_two_funcargs_generators(self, testdir):
-        testdir.makeconftest("""
-            def pytest_generate_tests(metafunc):
-                assert "arg1" in metafunc.fixturenames
-                metafunc.addcall(funcargs=dict(arg1=1, arg2=2))
-        """)
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall(funcargs=dict(arg1=1, arg2=1))
-
-            class TestClass:
-                def test_myfunc(self, arg1, arg2):
-                    assert arg1 == arg2
-        """)
-        result = testdir.runpytest("-v", p)
-        result.stdout.fnmatch_lines([
-            "*test_myfunc*0*PASS*",
-            "*test_myfunc*1*FAIL*",
-            "*1 failed, 1 passed*"
-        ])
-
-    def test_two_functions(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall(param=10)
-                metafunc.addcall(param=20)
-
-            def pytest_funcarg__arg1(request):
-                return request.param
-
-            def test_func1(arg1):
-                assert arg1 == 10
-            def test_func2(arg1):
-                assert arg1 in (10, 20)
-        """)
-        result = testdir.runpytest("-v", p)
-        result.stdout.fnmatch_lines([
-            "*test_func1*0*PASS*",
-            "*test_func1*1*FAIL*",
-            "*test_func2*PASS*",
-            "*1 failed, 3 passed*"
-        ])
-
-    def test_noself_in_method(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                assert 'xyz' not in metafunc.fixturenames
-
-            class TestHello:
-                def test_hello(xyz):
-                    pass
-        """)
-        result = testdir.runpytest(p)
-        result.assert_outcomes(passed=1)
-
-
-    def test_generate_plugin_and_module(self, testdir):
-        testdir.makeconftest("""
-            def pytest_generate_tests(metafunc):
-                assert "arg1" in metafunc.fixturenames
-                metafunc.addcall(id="world", param=(2,100))
-        """)
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall(param=(1,1), id="hello")
-
-            def pytest_funcarg__arg1(request):
-                return request.param[0]
-            def pytest_funcarg__arg2(request):
-                return request.param[1]
-
-            class TestClass:
-                def test_myfunc(self, arg1, arg2):
-                    assert arg1 == arg2
-        """)
-        result = testdir.runpytest("-v", p)
-        result.stdout.fnmatch_lines([
-            "*test_myfunc*hello*PASS*",
-            "*test_myfunc*world*FAIL*",
-            "*1 failed, 1 passed*"
-        ])
-
-    def test_generate_tests_in_class(self, testdir):
-        p = testdir.makepyfile("""
-            class TestClass:
-                def pytest_generate_tests(self, metafunc):
-                    metafunc.addcall(funcargs={'hello': 'world'}, id="hello")
-
-                def test_myfunc(self, hello):
-                    assert hello == "world"
-        """)
-        result = testdir.runpytest("-v", p)
-        result.stdout.fnmatch_lines([
-            "*test_myfunc*hello*PASS*",
-            "*1 passed*"
-        ])
-
-    def test_two_functions_not_same_instance(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall({'arg1': 10})
-                metafunc.addcall({'arg1': 20})
-
-            class TestClass:
-                def test_func(self, arg1):
-                    assert not hasattr(self, 'x')
-                    self.x = 1
-        """)
-        result = testdir.runpytest("-v", p)
-        result.stdout.fnmatch_lines([
-            "*test_func*0*PASS*",
-            "*test_func*1*PASS*",
-            "*2 pass*",
-        ])
-
-    def test_issue28_setup_method_in_generate_tests(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall({'arg1': 1})
-
-            class TestClass:
-                def test_method(self, arg1):
-                    assert arg1 == self.val
-                def setup_method(self, func):
-                    self.val = 1
-            """)
-        result = testdir.runpytest(p)
-        result.assert_outcomes(passed=1)
-
-    def test_parametrize_functional2(self, testdir):
-        testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.parametrize("arg1", [1,2])
-                metafunc.parametrize("arg2", [4,5])
-            def test_hello(arg1, arg2):
-                assert 0, (arg1, arg2)
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*(1, 4)*",
-            "*(1, 5)*",
-            "*(2, 4)*",
-            "*(2, 5)*",
-            "*4 failed*",
-        ])
-
-    def test_parametrize_and_inner_getfuncargvalue(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.parametrize("arg1", [1], indirect=True)
-                metafunc.parametrize("arg2", [10], indirect=True)
-
-            def pytest_funcarg__arg1(request):
-                x = request.getfuncargvalue("arg2")
-                return x + request.param
-
-            def pytest_funcarg__arg2(request):
-                return request.param
-
-            def test_func1(arg1, arg2):
-                assert arg1 == 11
-        """)
-        result = testdir.runpytest("-v", p)
-        result.stdout.fnmatch_lines([
-            "*test_func1*1*PASS*",
-            "*1 passed*"
-        ])
-
-    def test_parametrize_on_setup_arg(self, testdir):
-        p = testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                assert "arg1" in metafunc.fixturenames
-                metafunc.parametrize("arg1", [1], indirect=True)
-
-            def pytest_funcarg__arg1(request):
-                return request.param
-
-            def pytest_funcarg__arg2(request, arg1):
-                return 10 * arg1
-
-            def test_func(arg2):
-                assert arg2 == 10
-        """)
-        result = testdir.runpytest("-v", p)
-        result.stdout.fnmatch_lines([
-            "*test_func*1*PASS*",
-            "*1 passed*"
-        ])
-
-    def test_parametrize_with_ids(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def pytest_generate_tests(metafunc):
-                metafunc.parametrize(("a", "b"), [(1,1), (1,2)],
-                                     ids=["basic", "advanced"])
-
-            def test_function(a, b):
-                assert a == b
-        """)
-        result = testdir.runpytest("-v")
-        assert result.ret == 1
-        result.stdout.fnmatch_lines_random([
-            "*test_function*basic*PASSED",
-            "*test_function*advanced*FAILED",
-        ])
-
-    def test_parametrize_without_ids(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def pytest_generate_tests(metafunc):
-                metafunc.parametrize(("a", "b"),
-                                     [(1,object()), (1.3,object())])
-
-            def test_function(a, b):
-                assert 1
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines("""
-            *test_function*1-b0*
-            *test_function*1.3-b1*
-        """)
-
-    @pytest.mark.parametrize(("scope", "length"),
-                             [("module", 2), ("function", 4)])
-    def test_parametrize_scope_overrides(self, testdir, scope, length):
-        testdir.makepyfile("""
-            import pytest
-            l = []
-            def pytest_generate_tests(metafunc):
-                if "arg" in metafunc.funcargnames:
-                    metafunc.parametrize("arg", [1,2], indirect=True,
-                                         scope=%r)
-            def pytest_funcarg__arg(request):
-                l.append(request.param)
-                return request.param
-            def test_hello(arg):
-                assert arg in (1,2)
-            def test_world(arg):
-                assert arg in (1,2)
-            def test_checklength():
-                assert len(l) == %d
-        """ % (scope, length))
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=5)
-
-    def test_parametrize_issue323(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture(scope='module', params=range(966))
-            def foo(request):
-                return request.param
-
-            def test_it(foo):
-                pass
-            def test_it2(foo):
-                pass
-        """)
-        reprec = testdir.inline_run("--collect-only")
-        assert not reprec.getcalls("pytest_internalerror")
-
-    def test_usefixtures_seen_in_generate_tests(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def pytest_generate_tests(metafunc):
-                assert "abc" in metafunc.fixturenames
-                metafunc.parametrize("abc", [1])
-
-            @pytest.mark.usefixtures("abc")
-            def test_function():
-                pass
-        """)
-        reprec = testdir.runpytest()
-        reprec.assert_outcomes(passed=1)
-
-    def test_generate_tests_only_done_in_subdir(self, testdir):
-        sub1 = testdir.mkpydir("sub1")
-        sub2 = testdir.mkpydir("sub2")
-        sub1.join("conftest.py").write(_pytest._code.Source("""
-            def pytest_generate_tests(metafunc):
-                assert metafunc.function.__name__ == "test_1"
-        """))
-        sub2.join("conftest.py").write(_pytest._code.Source("""
-            def pytest_generate_tests(metafunc):
-                assert metafunc.function.__name__ == "test_2"
-        """))
-        sub1.join("test_in_sub1.py").write("def test_1(): pass")
-        sub2.join("test_in_sub2.py").write("def test_2(): pass")
-        result = testdir.runpytest("-v", "-s", sub1, sub2, sub1)
-        result.assert_outcomes(passed=3)
-
-    def test_generate_same_function_names_issue403(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            def make_tests():
-                @pytest.mark.parametrize("x", range(2))
-                def test_foo(x):
-                    pass
-                return test_foo
-
-            test_x = make_tests()
-            test_y = make_tests()
-        """)
-        reprec = testdir.runpytest()
-        reprec.assert_outcomes(passed=4)
-
-    @pytest.mark.issue463
-    @pytest.mark.parametrize('attr', ['parametrise', 'parameterize',
-                                     'parameterise'])
-    def test_parametrize_misspelling(self, testdir, attr):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.mark.{0}("x", range(2))
-            def test_foo(x):
-                pass
-        """.format(attr))
-        reprec = testdir.inline_run('--collectonly')
-        failures = reprec.getfailures()
-        assert len(failures) == 1
-        expectederror = "MarkerError: test_foo has '{0}', spelling should be 'parametrize'".format(attr)
-        assert expectederror in failures[0].longrepr.reprcrash.message
-
-
-class TestMarkersWithParametrization:
-    pytestmark = pytest.mark.issue308
-    def test_simple_mark(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.foo
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                pytest.mark.bar((1, 3)),
-                (2, 3),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        items = testdir.getitems(s)
-        assert len(items) == 3
-        for item in items:
-            assert 'foo' in item.keywords
-        assert 'bar' not in items[0].keywords
-        assert 'bar' in items[1].keywords
-        assert 'bar' not in items[2].keywords
-
-    def test_select_based_on_mark(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                pytest.mark.foo((2, 3)),
-                (3, 4),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        testdir.makepyfile(s)
-        rec = testdir.inline_run("-m", 'foo')
-        passed, skipped, fail = rec.listoutcomes()
-        assert len(passed) == 1
-        assert len(skipped) == 0
-        assert len(fail) == 0
-
-    @pytest.mark.xfail(reason="is this important to support??")
-    def test_nested_marks(self, testdir):
-        s = """
-            import pytest
-            mastermark = pytest.mark.foo(pytest.mark.bar)
-
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                mastermark((1, 3)),
-                (2, 3),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        items = testdir.getitems(s)
-        assert len(items) == 3
-        for mark in ['foo', 'bar']:
-            assert mark not in items[0].keywords
-            assert mark in items[1].keywords
-            assert mark not in items[2].keywords
-
-    def test_simple_xfail(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                pytest.mark.xfail((1, 3)),
-                (2, 3),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        testdir.makepyfile(s)
-        reprec = testdir.inline_run()
-        # xfail is skip??
-        reprec.assertoutcome(passed=2, skipped=1)
-
-    def test_simple_xfail_single_argname(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.parametrize("n", [
-                2,
-                pytest.mark.xfail(3),
-                4,
-            ])
-            def test_isEven(n):
-                assert n % 2 == 0
-        """
-        testdir.makepyfile(s)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2, skipped=1)
-
-    def test_xfail_with_arg(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                pytest.mark.xfail("True")((1, 3)),
-                (2, 3),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        testdir.makepyfile(s)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2, skipped=1)
-
-    def test_xfail_with_kwarg(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                pytest.mark.xfail(reason="some bug")((1, 3)),
-                (2, 3),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        testdir.makepyfile(s)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2, skipped=1)
-
-    def test_xfail_with_arg_and_kwarg(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                pytest.mark.xfail("True", reason="some bug")((1, 3)),
-                (2, 3),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        testdir.makepyfile(s)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2, skipped=1)
-
-    def test_xfail_passing_is_xpass(self, testdir):
-        s = """
-            import pytest
-
-            @pytest.mark.parametrize(("n", "expected"), [
-                (1, 2),
-                pytest.mark.xfail("sys.version > 0", reason="some bug")((2, 3)),
-                (3, 4),
-            ])
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        testdir.makepyfile(s)
-        reprec = testdir.inline_run()
-        # xpass is fail, obviously :)
-        reprec.assertoutcome(passed=2, failed=1)
-
-    def test_parametrize_called_in_generate_tests(self, testdir):
-        s = """
-            import pytest
-
-
-            def pytest_generate_tests(metafunc):
-                passingTestData = [(1, 2),
-                                   (2, 3)]
-                failingTestData = [(1, 3),
-                                   (2, 2)]
-
-                testData = passingTestData + [pytest.mark.xfail(d)
-                                  for d in failingTestData]
-                metafunc.parametrize(("n", "expected"), testData)
-
-
-            def test_increment(n, expected):
-                assert n + 1 == expected
-        """
-        testdir.makepyfile(s)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2, skipped=2)
-
-
-    @pytest.mark.issue290
-    def test_parametrize_ID_generation_string_int_works(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture
-            def myfixture():
-                return 'example'
-            @pytest.mark.parametrize(
-                'limit', (0, '0'))
-            def test_limit(limit, myfixture):
-                return
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=2)
diff --git a/tools/pytest/testing/python/raises.py b/tools/pytest/testing/python/raises.py
deleted file mode 100644
index 0ea7f9b..0000000
--- a/tools/pytest/testing/python/raises.py
+++ /dev/null
@@ -1,78 +0,0 @@
-import pytest
-
-class TestRaises:
-    def test_raises(self):
-        source = "int('qwe')"
-        excinfo = pytest.raises(ValueError, source)
-        code = excinfo.traceback[-1].frame.code
-        s = str(code.fullsource)
-        assert s == source
-
-    def test_raises_exec(self):
-        pytest.raises(ValueError, "a,x = []")
-
-    def test_raises_syntax_error(self):
-        pytest.raises(SyntaxError, "qwe qwe qwe")
-
-    def test_raises_function(self):
-        pytest.raises(ValueError, int, 'hello')
-
-    def test_raises_callable_no_exception(self):
-        class A:
-            def __call__(self):
-                pass
-        try:
-            pytest.raises(ValueError, A())
-        except pytest.raises.Exception:
-            pass
-
-    def test_raises_flip_builtin_AssertionError(self):
-        # we replace AssertionError on python level
-        # however c code might still raise the builtin one
-        from _pytest.assertion.util import BuiltinAssertionError # noqa
-        pytest.raises(AssertionError,"""
-            raise BuiltinAssertionError
-        """)
-
-    def test_raises_as_contextmanager(self, testdir):
-        testdir.makepyfile("""
-            from __future__ import with_statement
-            import py, pytest
-            import _pytest._code
-
-            def test_simple():
-                with pytest.raises(ZeroDivisionError) as excinfo:
-                    assert isinstance(excinfo, _pytest._code.ExceptionInfo)
-                    1/0
-                print (excinfo)
-                assert excinfo.type == ZeroDivisionError
-                assert isinstance(excinfo.value, ZeroDivisionError)
-
-            def test_noraise():
-                with pytest.raises(pytest.raises.Exception):
-                    with pytest.raises(ValueError):
-                           int()
-
-            def test_raise_wrong_exception_passes_by():
-                with pytest.raises(ZeroDivisionError):
-                    with pytest.raises(ValueError):
-                           1/0
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            '*3 passed*',
-        ])
-
-    def test_noclass(self):
-        with pytest.raises(TypeError):
-            pytest.raises('wrong', lambda: None)
-
-    def test_tuple(self):
-        with pytest.raises((KeyError, ValueError)):
-            raise KeyError('oops')
-
-    def test_no_raise_message(self):
-        try:
-            pytest.raises(ValueError, int, '0')
-        except pytest.raises.Exception as e:
-            assert e.msg == "DID NOT RAISE {0}".format(repr(ValueError))
diff --git a/tools/pytest/testing/test_argcomplete.py b/tools/pytest/testing/test_argcomplete.py
deleted file mode 100644
index ace7d8c..0000000
--- a/tools/pytest/testing/test_argcomplete.py
+++ /dev/null
@@ -1,90 +0,0 @@
-from __future__ import with_statement
-import py, pytest
-
-# test for _argcomplete but not specific for any application
-
-def equal_with_bash(prefix, ffc, fc, out=None):
-    res = ffc(prefix)
-    res_bash = set(fc(prefix))
-    retval = set(res) == res_bash
-    if out:
-        out.write('equal_with_bash %s %s\n' % (retval, res))
-        if not retval:
-            out.write(' python - bash: %s\n' % (set(res) - res_bash))
-            out.write(' bash - python: %s\n' % (res_bash - set(res)))
-    return retval
-
-# copied from argcomplete.completers as import from there
-# also pulls in argcomplete.__init__ which opens filedescriptor 9
-# this gives an IOError at the end of testrun
-def _wrapcall(*args, **kargs):
-    try:
-        if py.std.sys.version_info > (2,7):
-            return py.std.subprocess.check_output(*args,**kargs).decode().splitlines()
-        if 'stdout' in kargs:
-            raise ValueError('stdout argument not allowed, it will be overridden.')
-        process = py.std.subprocess.Popen(
-            stdout=py.std.subprocess.PIPE, *args, **kargs)
-        output, unused_err = process.communicate()
-        retcode = process.poll()
-        if retcode:
-            cmd = kargs.get("args")
-            if cmd is None:
-                cmd = args[0]
-            raise py.std.subprocess.CalledProcessError(retcode, cmd)
-        return output.decode().splitlines()
-    except py.std.subprocess.CalledProcessError:
-        return []
-
-class FilesCompleter(object):
-    'File completer class, optionally takes a list of allowed extensions'
-    def __init__(self,allowednames=(),directories=True):
-        # Fix if someone passes in a string instead of a list
-        if type(allowednames) is str:
-            allowednames = [allowednames]
-
-        self.allowednames = [x.lstrip('*').lstrip('.') for x in allowednames]
-        self.directories = directories
-
-    def __call__(self, prefix, **kwargs):
-        completion = []
-        if self.allowednames:
-            if self.directories:
-                files = _wrapcall(['bash','-c',
-                    "compgen -A directory -- '{p}'".format(p=prefix)])
-                completion += [ f + '/' for f in files]
-            for x in self.allowednames:
-                completion += _wrapcall(['bash', '-c',
-                    "compgen -A file -X '!*.{0}' -- '{p}'".format(x,p=prefix)])
-        else:
-            completion += _wrapcall(['bash', '-c',
-                "compgen -A file -- '{p}'".format(p=prefix)])
-
-            anticomp = _wrapcall(['bash', '-c',
-                "compgen -A directory -- '{p}'".format(p=prefix)])
-
-            completion = list( set(completion) - set(anticomp))
-
-            if self.directories:
-                completion += [f + '/' for f in anticomp]
-        return completion
-
-class TestArgComplete:
-    @pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
-    def test_compare_with_compgen(self):
-        from _pytest._argcomplete import FastFilesCompleter
-        ffc = FastFilesCompleter()
-        fc = FilesCompleter()
-        for x in '/ /d /data qqq'.split():
-            assert equal_with_bash(x, ffc, fc, out=py.std.sys.stdout)
-
-    @pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
-    def test_remove_dir_prefix(self):
-        """this is not compatible with compgen but it is with bash itself:
-        ls /usr/<TAB>
-        """
-        from _pytest._argcomplete import FastFilesCompleter
-        ffc = FastFilesCompleter()
-        fc = FilesCompleter()
-        for x in '/usr/'.split():
-            assert not equal_with_bash(x, ffc, fc, out=py.std.sys.stdout)
diff --git a/tools/pytest/testing/test_assertinterpret.py b/tools/pytest/testing/test_assertinterpret.py
deleted file mode 100644
index 67a352c..0000000
--- a/tools/pytest/testing/test_assertinterpret.py
+++ /dev/null
@@ -1,274 +0,0 @@
-"PYTEST_DONT_REWRITE"
-import py
-import pytest
-from _pytest.assertion import util
-
-
-def exvalue():
-    return py.std.sys.exc_info()[1]
-
-def f():
-    return 2
-
-def test_not_being_rewritten():
-    assert "@py_builtins" not in globals()
-
-def test_assert():
-    try:
-        assert f() == 3
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith('assert 2 == 3\n')
-
-def test_assert_with_explicit_message():
-    try:
-        assert f() == 3, "hello"
-    except AssertionError:
-        e = exvalue()
-        assert e.msg == 'hello'
-
-def test_assert_within_finally():
-    excinfo = pytest.raises(ZeroDivisionError, """
-        try:
-            1/0
-        finally:
-            i = 42
-    """)
-    s = excinfo.exconly()
-    assert py.std.re.search("division.+by zero", s) is not None
-
-    #def g():
-    #    A.f()
-    #excinfo = getexcinfo(TypeError, g)
-    #msg = getmsg(excinfo)
-    #assert msg.find("must be called with A") != -1
-
-
-def test_assert_multiline_1():
-    try:
-        assert (f() ==
-                3)
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith('assert 2 == 3\n')
-
-def test_assert_multiline_2():
-    try:
-        assert (f() == (4,
-                   3)[-1])
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith('assert 2 ==')
-
-def test_in():
-    try:
-        assert "hi" in [1, 2]
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 'hi' in")
-
-def test_is():
-    try:
-        assert 1 is 2
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 1 is 2")
-
-
-def test_attrib():
-    class Foo(object):
-        b = 1
-    i = Foo()
-    try:
-        assert i.b == 2
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 1 == 2")
-
-def test_attrib_inst():
-    class Foo(object):
-        b = 1
-    try:
-        assert Foo().b == 2
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 1 == 2")
-
-def test_len():
-    l = list(range(42))
-    try:
-        assert len(l) == 100
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert s.startswith("assert 42 == 100")
-        assert "where 42 = len([" in s
-
-def test_assert_non_string_message():
-    class A:
-        def __str__(self):
-            return "hello"
-    try:
-        assert 0 == 1, A()
-    except AssertionError:
-        e = exvalue()
-        assert e.msg == "hello"
-
-def test_assert_keyword_arg():
-    def f(x=3):
-        return False
-    try:
-        assert f(x=5)
-    except AssertionError:
-        e = exvalue()
-        assert "x=5" in e.msg
-
-def test_private_class_variable():
-    class X:
-        def __init__(self):
-            self.__v = 41
-        def m(self):
-            assert self.__v == 42
-    try:
-        X().m()
-    except AssertionError:
-        e = exvalue()
-        assert "== 42" in e.msg
-
-# These tests should both fail, but should fail nicely...
-class WeirdRepr:
-    def __repr__(self):
-        return '<WeirdRepr\nsecond line>'
-
-def bug_test_assert_repr():
-    v = WeirdRepr()
-    try:
-        assert v == 1
-    except AssertionError:
-        e = exvalue()
-        assert e.msg.find('WeirdRepr') != -1
-        assert e.msg.find('second line') != -1
-        assert 0
-
-def test_assert_non_string():
-    try:
-        assert 0, ['list']
-    except AssertionError:
-        e = exvalue()
-        assert e.msg.find("list") != -1
-
-def test_assert_implicit_multiline():
-    try:
-        x = [1,2,3]
-        assert x != [1,
-           2, 3]
-    except AssertionError:
-        e = exvalue()
-        assert e.msg.find('assert [1, 2, 3] !=') != -1
-
-
-def test_assert_with_brokenrepr_arg():
-    class BrokenRepr:
-        def __repr__(self): 0 / 0
-    e = AssertionError(BrokenRepr())
-    if e.msg.find("broken __repr__") == -1:
-        pytest.fail("broken __repr__ not handle correctly")
-
-def test_multiple_statements_per_line():
-    try:
-        a = 1; assert a == 2
-    except AssertionError:
-        e = exvalue()
-        assert "assert 1 == 2" in e.msg
-
-def test_power():
-    try:
-        assert 2**3 == 7
-    except AssertionError:
-        e = exvalue()
-        assert "assert (2 ** 3) == 7" in e.msg
-
-
-def test_assert_customizable_reprcompare(monkeypatch):
-    monkeypatch.setattr(util, '_reprcompare', lambda *args: 'hello')
-    try:
-        assert 3 == 4
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert "hello" in s
-
-def test_assert_long_source_1():
-    try:
-        assert len == [
-            (None, ['somet text', 'more text']),
-        ]
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert 're-run' not in s
-        assert 'somet text' in s
-
-def test_assert_long_source_2():
-    try:
-        assert(len == [
-            (None, ['somet text', 'more text']),
-        ])
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert 're-run' not in s
-        assert 'somet text' in s
-
-def test_assert_raise_alias(testdir):
-    testdir.makepyfile("""
-    "PYTEST_DONT_REWRITE"
-    import sys
-    EX = AssertionError
-    def test_hello():
-        raise EX("hello"
-            "multi"
-            "line")
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*def test_hello*",
-        "*raise EX*",
-        "*1 failed*",
-    ])
-
-
-def test_assert_raise_subclass():
-    class SomeEx(AssertionError):
-        def __init__(self, *args):
-            super(SomeEx, self).__init__()
-    try:
-        raise SomeEx("hello")
-    except AssertionError:
-        s = str(exvalue())
-        assert 're-run' not in s
-        assert 'could not determine' in s
-
-def test_assert_raises_in_nonzero_of_object_pytest_issue10():
-    class A(object):
-        def __nonzero__(self):
-            raise ValueError(42)
-        def __lt__(self, other):
-            return A()
-        def __repr__(self):
-            return "<MY42 object>"
-    def myany(x):
-        return True
-    try:
-        assert not(myany(A() < 0))
-    except AssertionError:
-        e = exvalue()
-        s = str(e)
-        assert "<MY42 object> < 0" in s
diff --git a/tools/pytest/testing/test_assertion.py b/tools/pytest/testing/test_assertion.py
deleted file mode 100644
index 347278e..0000000
--- a/tools/pytest/testing/test_assertion.py
+++ /dev/null
@@ -1,628 +0,0 @@
-# -*- coding: utf-8 -*-
-import sys
-import textwrap
-
-import _pytest.assertion as plugin
-import _pytest._code
-import py
-import pytest
-from _pytest.assertion import reinterpret
-from _pytest.assertion import util
-
-PY3 = sys.version_info >= (3, 0)
-
-
-@pytest.fixture
-def mock_config():
-    class Config(object):
-        verbose = False
-        def getoption(self, name):
-            if name == 'verbose':
-                return self.verbose
-            raise KeyError('Not mocked out: %s' % name)
-    return Config()
-
-
-def interpret(expr):
-    return reinterpret.reinterpret(expr, _pytest._code.Frame(sys._getframe(1)))
-
-class TestBinReprIntegration:
-
-    def test_pytest_assertrepr_compare_called(self, testdir):
-        testdir.makeconftest("""
-            l = []
-            def pytest_assertrepr_compare(op, left, right):
-                l.append((op, left, right))
-            def pytest_funcarg__l(request):
-                return l
-        """)
-        testdir.makepyfile("""
-            def test_hello():
-                assert 0 == 1
-            def test_check(l):
-                assert l == [("==", 0, 1)]
-        """)
-        result = testdir.runpytest("-v")
-        result.stdout.fnmatch_lines([
-            "*test_hello*FAIL*",
-            "*test_check*PASS*",
-        ])
-
-def callequal(left, right, verbose=False):
-    config = mock_config()
-    config.verbose = verbose
-    return plugin.pytest_assertrepr_compare(config, '==', left, right)
-
-
-class TestAssert_reprcompare:
-    def test_different_types(self):
-        assert callequal([0, 1], 'foo') is None
-
-    def test_summary(self):
-        summary = callequal([0, 1], [0, 2])[0]
-        assert len(summary) < 65
-
-    def test_text_diff(self):
-        diff = callequal('spam', 'eggs')[1:]
-        assert '- spam' in diff
-        assert '+ eggs' in diff
-
-    def test_text_skipping(self):
-        lines = callequal('a'*50 + 'spam', 'a'*50 + 'eggs')
-        assert 'Skipping' in lines[1]
-        for line in lines:
-            assert 'a'*50 not in line
-
-    def test_text_skipping_verbose(self):
-        lines = callequal('a'*50 + 'spam', 'a'*50 + 'eggs', verbose=True)
-        assert '- ' + 'a'*50 + 'spam' in lines
-        assert '+ ' + 'a'*50 + 'eggs' in lines
-
-    def test_multiline_text_diff(self):
-        left = 'foo\nspam\nbar'
-        right = 'foo\neggs\nbar'
-        diff = callequal(left, right)
-        assert '- spam' in diff
-        assert '+ eggs' in diff
-
-    def test_list(self):
-        expl = callequal([0, 1], [0, 2])
-        assert len(expl) > 1
-
-    @pytest.mark.parametrize(
-        ['left', 'right', 'expected'], [
-            ([0, 1], [0, 2], """
-                Full diff:
-                - [0, 1]
-                ?     ^
-                + [0, 2]
-                ?     ^
-            """),
-            ({0: 1}, {0: 2}, """
-                Full diff:
-                - {0: 1}
-                ?     ^
-                + {0: 2}
-                ?     ^
-            """),
-            (set([0, 1]), set([0, 2]), """
-                Full diff:
-                - set([0, 1])
-                ?         ^
-                + set([0, 2])
-                ?         ^
-            """ if not PY3 else """
-                Full diff:
-                - {0, 1}
-                ?     ^
-                + {0, 2}
-                ?     ^
-            """)
-        ]
-    )
-    def test_iterable_full_diff(self, left, right, expected):
-        """Test the full diff assertion failure explanation.
-
-        When verbose is False, then just a -v notice to get the diff is rendered,
-        when verbose is True, then ndiff of the pprint is returned.
-        """
-        expl = callequal(left, right, verbose=False)
-        assert expl[-1] == 'Use -v to get the full diff'
-        expl = '\n'.join(callequal(left, right, verbose=True))
-        assert expl.endswith(textwrap.dedent(expected).strip())
-
-    def test_list_different_lenghts(self):
-        expl = callequal([0, 1], [0, 1, 2])
-        assert len(expl) > 1
-        expl = callequal([0, 1, 2], [0, 1])
-        assert len(expl) > 1
-
-    def test_dict(self):
-        expl = callequal({'a': 0}, {'a': 1})
-        assert len(expl) > 1
-
-    def test_dict_omitting(self):
-        lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1})
-        assert lines[1].startswith('Omitting 1 identical item')
-        assert 'Common items' not in lines
-        for line in lines[1:]:
-            assert 'b' not in line
-
-    def test_dict_omitting_verbose(self):
-        lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1}, verbose=True)
-        assert lines[1].startswith('Common items:')
-        assert 'Omitting' not in lines[1]
-        assert lines[2] == "{'b': 1}"
-
-    def test_set(self):
-        expl = callequal(set([0, 1]), set([0, 2]))
-        assert len(expl) > 1
-
-    def test_frozenzet(self):
-        expl = callequal(frozenset([0, 1]), set([0, 2]))
-        assert len(expl) > 1
-
-    def test_Sequence(self):
-        col = py.builtin._tryimport(
-            "collections.abc",
-            "collections",
-            "sys")
-        if not hasattr(col, "MutableSequence"):
-            pytest.skip("cannot import MutableSequence")
-        MutableSequence = col.MutableSequence
-
-        class TestSequence(MutableSequence):  # works with a Sequence subclass
-            def __init__(self, iterable):
-                self.elements = list(iterable)
-
-            def __getitem__(self, item):
-                return self.elements[item]
-
-            def __len__(self):
-                return len(self.elements)
-
-            def __setitem__(self, item, value):
-                pass
-
-            def __delitem__(self, item):
-                pass
-
-            def insert(self, item, index):
-                pass
-
-        expl = callequal(TestSequence([0, 1]), list([0, 2]))
-        assert len(expl) > 1
-
-    def test_list_tuples(self):
-        expl = callequal([], [(1,2)])
-        assert len(expl) > 1
-        expl = callequal([(1,2)], [])
-        assert len(expl) > 1
-
-    def test_list_bad_repr(self):
-        class A:
-            def __repr__(self):
-                raise ValueError(42)
-        expl = callequal([], [A()])
-        assert 'ValueError' in "".join(expl)
-        expl = callequal({}, {'1': A()})
-        assert 'faulty' in "".join(expl)
-
-    def test_one_repr_empty(self):
-        """
-        the faulty empty string repr did trigger
-        a unbound local error in _diff_text
-        """
-        class A(str):
-            def __repr__(self):
-                return ''
-        expl = callequal(A(), '')
-        assert not expl
-
-    def test_repr_no_exc(self):
-        expl = ' '.join(callequal('foo', 'bar'))
-        assert 'raised in repr()' not in expl
-
-    def test_unicode(self):
-        left = py.builtin._totext('£€', 'utf-8')
-        right = py.builtin._totext('£', 'utf-8')
-        expl = callequal(left, right)
-        assert expl[0] == py.builtin._totext("'£€' == '£'", 'utf-8')
-        assert expl[1] == py.builtin._totext('- £€', 'utf-8')
-        assert expl[2] == py.builtin._totext('+ £', 'utf-8')
-
-    def test_nonascii_text(self):
-        """
-        :issue: 877
-        non ascii python2 str caused a UnicodeDecodeError
-        """
-        class A(str):
-            def __repr__(self):
-                return '\xff'
-        expl = callequal(A(), '1')
-        assert expl
-
-    def test_format_nonascii_explanation(self):
-        assert util.format_explanation('λ')
-
-    def test_mojibake(self):
-        # issue 429
-        left = 'e'
-        right = '\xc3\xa9'
-        if not isinstance(left, py.builtin.bytes):
-            left = py.builtin.bytes(left, 'utf-8')
-            right = py.builtin.bytes(right, 'utf-8')
-        expl = callequal(left, right)
-        for line in expl:
-            assert isinstance(line, py.builtin.text)
-        msg = py.builtin._totext('\n').join(expl)
-        assert msg
-
-
-class TestFormatExplanation:
-
-    def test_special_chars_full(self, testdir):
-        # Issue 453, for the bug this would raise IndexError
-        testdir.makepyfile("""
-            def test_foo():
-                assert '\\n}' == ''
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 1
-        result.stdout.fnmatch_lines([
-            "*AssertionError*",
-        ])
-
-    def test_fmt_simple(self):
-        expl = 'assert foo'
-        assert util.format_explanation(expl) == 'assert foo'
-
-    def test_fmt_where(self):
-        expl = '\n'.join(['assert 1',
-                          '{1 = foo',
-                          '} == 2'])
-        res = '\n'.join(['assert 1 == 2',
-                         ' +  where 1 = foo'])
-        assert util.format_explanation(expl) == res
-
-    def test_fmt_and(self):
-        expl = '\n'.join(['assert 1',
-                          '{1 = foo',
-                          '} == 2',
-                          '{2 = bar',
-                          '}'])
-        res = '\n'.join(['assert 1 == 2',
-                         ' +  where 1 = foo',
-                         ' +  and   2 = bar'])
-        assert util.format_explanation(expl) == res
-
-    def test_fmt_where_nested(self):
-        expl = '\n'.join(['assert 1',
-                          '{1 = foo',
-                          '{foo = bar',
-                          '}',
-                          '} == 2'])
-        res = '\n'.join(['assert 1 == 2',
-                         ' +  where 1 = foo',
-                         ' +    where foo = bar'])
-        assert util.format_explanation(expl) == res
-
-    def test_fmt_newline(self):
-        expl = '\n'.join(['assert "foo" == "bar"',
-                          '~- foo',
-                          '~+ bar'])
-        res = '\n'.join(['assert "foo" == "bar"',
-                         '  - foo',
-                         '  + bar'])
-        assert util.format_explanation(expl) == res
-
-    def test_fmt_newline_escaped(self):
-        expl = '\n'.join(['assert foo == bar',
-                          'baz'])
-        res = 'assert foo == bar\\nbaz'
-        assert util.format_explanation(expl) == res
-
-    def test_fmt_newline_before_where(self):
-        expl = '\n'.join(['the assertion message here',
-                          '>assert 1',
-                          '{1 = foo',
-                          '} == 2',
-                          '{2 = bar',
-                          '}'])
-        res = '\n'.join(['the assertion message here',
-                         'assert 1 == 2',
-                         ' +  where 1 = foo',
-                         ' +  and   2 = bar'])
-        assert util.format_explanation(expl) == res
-
-    def test_fmt_multi_newline_before_where(self):
-        expl = '\n'.join(['the assertion',
-                          '~message here',
-                          '>assert 1',
-                          '{1 = foo',
-                          '} == 2',
-                          '{2 = bar',
-                          '}'])
-        res = '\n'.join(['the assertion',
-                         '  message here',
-                         'assert 1 == 2',
-                         ' +  where 1 = foo',
-                         ' +  and   2 = bar'])
-        assert util.format_explanation(expl) == res
-
-
-def test_python25_compile_issue257(testdir):
-    testdir.makepyfile("""
-        def test_rewritten():
-            assert 1 == 2
-        # some comment
-    """)
-    result = testdir.runpytest()
-    assert result.ret == 1
-    result.stdout.fnmatch_lines("""
-            *E*assert 1 == 2*
-            *1 failed*
-    """)
-
-def test_rewritten(testdir):
-    testdir.makepyfile("""
-        def test_rewritten():
-            assert "@py_builtins" in globals()
-    """)
-    assert testdir.runpytest().ret == 0
-
-def test_reprcompare_notin(mock_config):
-    detail = plugin.pytest_assertrepr_compare(
-        mock_config, 'not in', 'foo', 'aaafoobbb')[1:]
-    assert detail == ["'foo' is contained here:", '  aaafoobbb', '?    +++']
-
-def test_pytest_assertrepr_compare_integration(testdir):
-    testdir.makepyfile("""
-        def test_hello():
-            x = set(range(100))
-            y = x.copy()
-            y.remove(50)
-            assert x == y
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*def test_hello():*",
-        "*assert x == y*",
-        "*E*Extra items*left*",
-        "*E*50*",
-    ])
-
-def test_sequence_comparison_uses_repr(testdir):
-    testdir.makepyfile("""
-        def test_hello():
-            x = set("hello x")
-            y = set("hello y")
-            assert x == y
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*def test_hello():*",
-        "*assert x == y*",
-        "*E*Extra items*left*",
-        "*E*'x'*",
-        "*E*Extra items*right*",
-        "*E*'y'*",
-    ])
-
-
-def test_assert_compare_truncate_longmessage(monkeypatch, testdir):
-    testdir.makepyfile(r"""
-        def test_long():
-            a = list(range(200))
-            b = a[::2]
-            a = '\n'.join(map(str, a))
-            b = '\n'.join(map(str, b))
-            assert a == b
-    """)
-    monkeypatch.delenv('CI', raising=False)
-
-    result = testdir.runpytest()
-    # without -vv, truncate the message showing a few diff lines only
-    result.stdout.fnmatch_lines([
-        "*- 1",
-        "*- 3",
-        "*- 5",
-        "*- 7",
-        "*truncated (191 more lines)*use*-vv*",
-    ])
-
-
-    result = testdir.runpytest('-vv')
-    result.stdout.fnmatch_lines([
-        "*- 197",
-    ])
-
-    monkeypatch.setenv('CI', '1')
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*- 197",
-    ])
-
-
-def test_assertrepr_loaded_per_dir(testdir):
-    testdir.makepyfile(test_base=['def test_base(): assert 1 == 2'])
-    a = testdir.mkdir('a')
-    a_test = a.join('test_a.py')
-    a_test.write('def test_a(): assert 1 == 2')
-    a_conftest = a.join('conftest.py')
-    a_conftest.write('def pytest_assertrepr_compare(): return ["summary a"]')
-    b = testdir.mkdir('b')
-    b_test = b.join('test_b.py')
-    b_test.write('def test_b(): assert 1 == 2')
-    b_conftest = b.join('conftest.py')
-    b_conftest.write('def pytest_assertrepr_compare(): return ["summary b"]')
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-            '*def test_base():*',
-            '*E*assert 1 == 2*',
-            '*def test_a():*',
-            '*E*assert summary a*',
-            '*def test_b():*',
-            '*E*assert summary b*'])
-
-
-def test_assertion_options(testdir):
-    testdir.makepyfile("""
-        def test_hello():
-            x = 3
-            assert x == 4
-    """)
-    result = testdir.runpytest()
-    assert "3 == 4" in result.stdout.str()
-    off_options = (("--no-assert",),
-                   ("--nomagic",),
-                   ("--no-assert", "--nomagic"),
-                   ("--assert=plain",),
-                   ("--assert=plain", "--no-assert"),
-                   ("--assert=plain", "--nomagic"),
-                   ("--assert=plain", "--no-assert", "--nomagic"))
-    for opt in off_options:
-        result = testdir.runpytest_subprocess(*opt)
-        assert "3 == 4" not in result.stdout.str()
-
-def test_old_assert_mode(testdir):
-    testdir.makepyfile("""
-        def test_in_old_mode():
-            assert "@py_builtins" not in globals()
-    """)
-    result = testdir.runpytest_subprocess("--assert=reinterp")
-    assert result.ret == 0
-
-def test_triple_quoted_string_issue113(testdir):
-    testdir.makepyfile("""
-        def test_hello():
-            assert "" == '''
-    '''""")
-    result = testdir.runpytest("--fulltrace")
-    result.stdout.fnmatch_lines([
-        "*1 failed*",
-    ])
-    assert 'SyntaxError' not in result.stdout.str()
-
-def test_traceback_failure(testdir):
-    p1 = testdir.makepyfile("""
-        def g():
-            return 2
-        def f(x):
-            assert x == g()
-        def test_onefails():
-            f(3)
-    """)
-    result = testdir.runpytest(p1, "--tb=long")
-    result.stdout.fnmatch_lines([
-        "*test_traceback_failure.py F",
-        "====* FAILURES *====",
-        "____*____",
-        "",
-        "    def test_onefails():",
-        ">       f(3)",
-        "",
-        "*test_*.py:6: ",
-        "_ _ _ *",
-        #"",
-        "    def f(x):",
-        ">       assert x == g()",
-        "E       assert 3 == 2",
-        "E        +  where 2 = g()",
-        "",
-        "*test_traceback_failure.py:4: AssertionError"
-    ])
-
-    result = testdir.runpytest(p1) # "auto"
-    result.stdout.fnmatch_lines([
-        "*test_traceback_failure.py F",
-        "====* FAILURES *====",
-        "____*____",
-        "",
-        "    def test_onefails():",
-        ">       f(3)",
-        "",
-        "*test_*.py:6: ",
-        "",
-        "    def f(x):",
-        ">       assert x == g()",
-        "E       assert 3 == 2",
-        "E        +  where 2 = g()",
-        "",
-        "*test_traceback_failure.py:4: AssertionError"
-    ])
-
-@pytest.mark.skipif("'__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" )
-def test_warn_missing(testdir):
-    testdir.makepyfile("")
-    result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h")
-    result.stderr.fnmatch_lines([
-        "*WARNING*assert statements are not executed*",
-    ])
-    result = testdir.run(sys.executable, "-OO", "-m", "pytest", "--no-assert")
-    result.stderr.fnmatch_lines([
-        "*WARNING*assert statements are not executed*",
-    ])
-
-def test_recursion_source_decode(testdir):
-    testdir.makepyfile("""
-        def test_something():
-            pass
-    """)
-    testdir.makeini("""
-        [pytest]
-        python_files = *.py
-    """)
-    result = testdir.runpytest("--collect-only")
-    result.stdout.fnmatch_lines("""
-        <Module*>
-    """)
-
-def test_AssertionError_message(testdir):
-    testdir.makepyfile("""
-        def test_hello():
-            x,y = 1,2
-            assert 0, (x,y)
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines("""
-        *def test_hello*
-        *assert 0, (x,y)*
-        *AssertionError: (1, 2)*
-    """)
-
-@pytest.mark.skipif(PY3, reason='This bug does not exist on PY3')
-def test_set_with_unsortable_elements():
-    # issue #718
-    class UnsortableKey(object):
-        def __init__(self, name):
-            self.name = name
-
-        def __lt__(self, other):
-            raise RuntimeError()
-
-        def __repr__(self):
-            return 'repr({0})'.format(self.name)
-
-        def __eq__(self, other):
-            return self.name == other.name
-
-        def __hash__(self):
-            return hash(self.name)
-
-    left_set = set(UnsortableKey(str(i)) for i in range(1, 3))
-    right_set = set(UnsortableKey(str(i)) for i in range(2, 4))
-    expl = callequal(left_set, right_set, verbose=True)
-    # skip first line because it contains the "construction" of the set, which does not have a guaranteed order
-    expl = expl[1:]
-    dedent = textwrap.dedent("""
-        Extra items in the left set:
-        repr(1)
-        Extra items in the right set:
-        repr(3)
-        Full diff (fallback to calling repr on each item):
-        - repr(1)
-        repr(2)
-        + repr(3)
-    """).strip()
-    assert '\n'.join(expl) == dedent
diff --git a/tools/pytest/testing/test_assertrewrite.py b/tools/pytest/testing/test_assertrewrite.py
deleted file mode 100644
index f43c424..0000000
--- a/tools/pytest/testing/test_assertrewrite.py
+++ /dev/null
@@ -1,716 +0,0 @@
-import os
-import stat
-import sys
-import zipfile
-import py
-import pytest
-
-ast = pytest.importorskip("ast")
-if sys.platform.startswith("java"):
-    # XXX should be xfail
-    pytest.skip("assert rewrite does currently not work on jython")
-
-import _pytest._code
-from _pytest.assertion import util
-from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-
-
-def setup_module(mod):
-    mod._old_reprcompare = util._reprcompare
-    _pytest._code._reprcompare = None
-
-def teardown_module(mod):
-    util._reprcompare = mod._old_reprcompare
-    del mod._old_reprcompare
-
-
-def rewrite(src):
-    tree = ast.parse(src)
-    rewrite_asserts(tree)
-    return tree
-
-def getmsg(f, extra_ns=None, must_pass=False):
-    """Rewrite the assertions in f, run it, and get the failure message."""
-    src = '\n'.join(_pytest._code.Code(f).source().lines)
-    mod = rewrite(src)
-    code = compile(mod, "<test>", "exec")
-    ns = {}
-    if extra_ns is not None:
-        ns.update(extra_ns)
-    py.builtin.exec_(code, ns)
-    func = ns[f.__name__]
-    try:
-        func()
-    except AssertionError:
-        if must_pass:
-            pytest.fail("shouldn't have raised")
-        s = str(sys.exc_info()[1])
-        if not s.startswith("assert"):
-            return "AssertionError: " + s
-        return s
-    else:
-        if not must_pass:
-            pytest.fail("function didn't raise at all")
-
-
-class TestAssertionRewrite:
-
-    def test_place_initial_imports(self):
-        s = """'Doc string'\nother = stuff"""
-        m = rewrite(s)
-        assert isinstance(m.body[0], ast.Expr)
-        assert isinstance(m.body[0].value, ast.Str)
-        for imp in m.body[1:3]:
-            assert isinstance(imp, ast.Import)
-            assert imp.lineno == 2
-            assert imp.col_offset == 0
-        assert isinstance(m.body[3], ast.Assign)
-        s = """from __future__ import with_statement\nother_stuff"""
-        m = rewrite(s)
-        assert isinstance(m.body[0], ast.ImportFrom)
-        for imp in m.body[1:3]:
-            assert isinstance(imp, ast.Import)
-            assert imp.lineno == 2
-            assert imp.col_offset == 0
-        assert isinstance(m.body[3], ast.Expr)
-        s = """'doc string'\nfrom __future__ import with_statement\nother"""
-        m = rewrite(s)
-        assert isinstance(m.body[0], ast.Expr)
-        assert isinstance(m.body[0].value, ast.Str)
-        assert isinstance(m.body[1], ast.ImportFrom)
-        for imp in m.body[2:4]:
-            assert isinstance(imp, ast.Import)
-            assert imp.lineno == 3
-            assert imp.col_offset == 0
-        assert isinstance(m.body[4], ast.Expr)
-        s = """from . import relative\nother_stuff"""
-        m = rewrite(s)
-        for imp in m.body[0:2]:
-            assert isinstance(imp, ast.Import)
-            assert imp.lineno == 1
-            assert imp.col_offset == 0
-        assert isinstance(m.body[3], ast.Expr)
-
-    def test_dont_rewrite(self):
-        s = """'PYTEST_DONT_REWRITE'\nassert 14"""
-        m = rewrite(s)
-        assert len(m.body) == 2
-        assert isinstance(m.body[0].value, ast.Str)
-        assert isinstance(m.body[1], ast.Assert)
-        assert m.body[1].msg is None
-
-    def test_name(self):
-        def f():
-            assert False
-        assert getmsg(f) == "assert False"
-        def f():
-            f = False
-            assert f
-        assert getmsg(f) == "assert False"
-        def f():
-            assert a_global  # noqa
-        assert getmsg(f, {"a_global" : False}) == "assert False"
-        def f():
-            assert sys == 42
-        assert getmsg(f, {"sys" : sys}) == "assert sys == 42"
-        def f():
-            assert cls == 42  # noqa
-        class X(object):
-            pass
-        assert getmsg(f, {"cls" : X}) == "assert cls == 42"
-
-    def test_assert_already_has_message(self):
-        def f():
-            assert False, "something bad!"
-        assert getmsg(f) == "AssertionError: something bad!\nassert False"
-
-    def test_assertion_message(self, testdir):
-        testdir.makepyfile("""
-            def test_foo():
-                assert 1 == 2, "The failure message"
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 1
-        result.stdout.fnmatch_lines([
-            "*AssertionError*The failure message*",
-            "*assert 1 == 2*",
-        ])
-
-    def test_assertion_message_multiline(self, testdir):
-        testdir.makepyfile("""
-            def test_foo():
-                assert 1 == 2, "A multiline\\nfailure message"
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 1
-        result.stdout.fnmatch_lines([
-            "*AssertionError*A multiline*",
-            "*failure message*",
-            "*assert 1 == 2*",
-        ])
-
-    def test_assertion_message_tuple(self, testdir):
-        testdir.makepyfile("""
-            def test_foo():
-                assert 1 == 2, (1, 2)
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 1
-        result.stdout.fnmatch_lines([
-            "*AssertionError*%s*" % repr((1, 2)),
-            "*assert 1 == 2*",
-        ])
-
-    def test_assertion_message_expr(self, testdir):
-        testdir.makepyfile("""
-            def test_foo():
-                assert 1 == 2, 1 + 2
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 1
-        result.stdout.fnmatch_lines([
-            "*AssertionError*3*",
-            "*assert 1 == 2*",
-        ])
-
-    def test_assertion_message_escape(self, testdir):
-        testdir.makepyfile("""
-            def test_foo():
-                assert 1 == 2, 'To be escaped: %'
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 1
-        result.stdout.fnmatch_lines([
-            "*AssertionError: To be escaped: %",
-            "*assert 1 == 2",
-        ])
-
-    def test_boolop(self):
-        def f():
-            f = g = False
-            assert f and g
-        assert getmsg(f) == "assert (False)"
-        def f():
-            f = True
-            g = False
-            assert f and g
-        assert getmsg(f) == "assert (True and False)"
-        def f():
-            f = False
-            g = True
-            assert f and g
-        assert getmsg(f) == "assert (False)"
-        def f():
-            f = g = False
-            assert f or g
-        assert getmsg(f) == "assert (False or False)"
-        def f():
-            f = g = False
-            assert not f and not g
-        getmsg(f, must_pass=True)
-        def x():
-            return False
-        def f():
-            assert x() and x()
-        assert getmsg(f, {"x" : x}) == "assert (x())"
-        def f():
-            assert False or x()
-        assert getmsg(f, {"x" : x}) == "assert (False or x())"
-        def f():
-            assert 1 in {} and 2 in {}
-        assert getmsg(f) == "assert (1 in {})"
-        def f():
-            x = 1
-            y = 2
-            assert x in {1 : None} and y in {}
-        assert getmsg(f) == "assert (1 in {1: None} and 2 in {})"
-        def f():
-            f = True
-            g = False
-            assert f or g
-        getmsg(f, must_pass=True)
-        def f():
-            f = g = h = lambda: True
-            assert f() and g() and h()
-        getmsg(f, must_pass=True)
-
-    def test_short_circut_evaluation(self):
-        def f():
-            assert True or explode  # noqa
-        getmsg(f, must_pass=True)
-        def f():
-            x = 1
-            assert x == 1 or x == 2
-        getmsg(f, must_pass=True)
-
-    def test_unary_op(self):
-        def f():
-            x = True
-            assert not x
-        assert getmsg(f) == "assert not True"
-        def f():
-            x = 0
-            assert ~x + 1
-        assert getmsg(f) == "assert (~0 + 1)"
-        def f():
-            x = 3
-            assert -x + x
-        assert getmsg(f) == "assert (-3 + 3)"
-        def f():
-            x = 0
-            assert +x + x
-        assert getmsg(f) == "assert (+0 + 0)"
-
-    def test_binary_op(self):
-        def f():
-            x = 1
-            y = -1
-            assert x + y
-        assert getmsg(f) == "assert (1 + -1)"
-        def f():
-            assert not 5 % 4
-        assert getmsg(f) == "assert not (5 % 4)"
-
-    def test_boolop_percent(self):
-        def f():
-            assert 3 % 2 and False
-        assert getmsg(f) == "assert ((3 % 2) and False)"
-        def f():
-            assert False or 4 % 2
-        assert getmsg(f) == "assert (False or (4 % 2))"
-
-    @pytest.mark.skipif("sys.version_info < (3,5)")
-    def test_at_operator_issue1290(self, testdir):
-        testdir.makepyfile("""
-            class Matrix:
-                def __init__(self, num):
-                    self.num = num
-                def __matmul__(self, other):
-                    return self.num * other.num
-
-            def test_multmat_operator():
-                assert Matrix(2) @ Matrix(3) == 6""")
-        testdir.runpytest().assert_outcomes(passed=1)
-
-    def test_call(self):
-        def g(a=42, *args, **kwargs):
-            return False
-        ns = {"g" : g}
-        def f():
-            assert g()
-        assert getmsg(f, ns) == """assert g()"""
-        def f():
-            assert g(1)
-        assert getmsg(f, ns) == """assert g(1)"""
-        def f():
-            assert g(1, 2)
-        assert getmsg(f, ns) == """assert g(1, 2)"""
-        def f():
-            assert g(1, g=42)
-        assert getmsg(f, ns) == """assert g(1, g=42)"""
-        def f():
-            assert g(1, 3, g=23)
-        assert getmsg(f, ns) == """assert g(1, 3, g=23)"""
-        def f():
-            seq = [1, 2, 3]
-            assert g(*seq)
-        assert getmsg(f, ns) == """assert g(*[1, 2, 3])"""
-        def f():
-            x = "a"
-            assert g(**{x : 2})
-        assert getmsg(f, ns) == """assert g(**{'a': 2})"""
-
-    def test_attribute(self):
-        class X(object):
-            g = 3
-        ns = {"x" : X}
-        def f():
-            assert not x.g # noqa
-        assert getmsg(f, ns) == """assert not 3
- +  where 3 = x.g"""
-        def f():
-            x.a = False  # noqa
-            assert x.a   # noqa
-        assert getmsg(f, ns) == """assert x.a"""
-
-    def test_comparisons(self):
-        def f():
-            a, b = range(2)
-            assert b < a
-        assert getmsg(f) == """assert 1 < 0"""
-        def f():
-            a, b, c = range(3)
-            assert a > b > c
-        assert getmsg(f) == """assert 0 > 1"""
-        def f():
-            a, b, c = range(3)
-            assert a < b > c
-        assert getmsg(f) == """assert 1 > 2"""
-        def f():
-            a, b, c = range(3)
-            assert a < b <= c
-        getmsg(f, must_pass=True)
-        def f():
-            a, b, c = range(3)
-            assert a < b
-            assert b < c
-        getmsg(f, must_pass=True)
-
-    def test_len(self):
-        def f():
-            l = list(range(10))
-            assert len(l) == 11
-        assert getmsg(f).startswith("""assert 10 == 11
- +  where 10 = len([""")
-
-    def test_custom_reprcompare(self, monkeypatch):
-        def my_reprcompare(op, left, right):
-            return "42"
-        monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
-        def f():
-            assert 42 < 3
-        assert getmsg(f) == "assert 42"
-        def my_reprcompare(op, left, right):
-            return "%s %s %s" % (left, op, right)
-        monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
-        def f():
-            assert 1 < 3 < 5 <= 4 < 7
-        assert getmsg(f) == "assert 5 <= 4"
-
-    def test_assert_raising_nonzero_in_comparison(self):
-        def f():
-            class A(object):
-                def __nonzero__(self):
-                    raise ValueError(42)
-                def __lt__(self, other):
-                    return A()
-                def __repr__(self):
-                    return "<MY42 object>"
-            def myany(x):
-                return False
-            assert myany(A() < 0)
-        assert "<MY42 object> < 0" in getmsg(f)
-
-    def test_formatchar(self):
-        def f():
-            assert "%test" == "test"
-        assert getmsg(f).startswith("assert '%test' == 'test'")
-
-    def test_custom_repr(self):
-        def f():
-            class Foo(object):
-                a = 1
-
-                def __repr__(self):
-                    return "\n{ \n~ \n}"
-            f = Foo()
-            assert 0 == f.a
-        assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0]
-
-
-class TestRewriteOnImport:
-
-    def test_pycache_is_a_file(self, testdir):
-        testdir.tmpdir.join("__pycache__").write("Hello")
-        testdir.makepyfile("""
-            def test_rewritten():
-                assert "@py_builtins" in globals()""")
-        assert testdir.runpytest().ret == 0
-
-    def test_pycache_is_readonly(self, testdir):
-        cache = testdir.tmpdir.mkdir("__pycache__")
-        old_mode = cache.stat().mode
-        cache.chmod(old_mode ^ stat.S_IWRITE)
-        testdir.makepyfile("""
-            def test_rewritten():
-                assert "@py_builtins" in globals()""")
-        try:
-            assert testdir.runpytest().ret == 0
-        finally:
-            cache.chmod(old_mode)
-
-    def test_zipfile(self, testdir):
-        z = testdir.tmpdir.join("myzip.zip")
-        z_fn = str(z)
-        f = zipfile.ZipFile(z_fn, "w")
-        try:
-            f.writestr("test_gum/__init__.py", "")
-            f.writestr("test_gum/test_lizard.py", "")
-        finally:
-            f.close()
-        z.chmod(256)
-        testdir.makepyfile("""
-            import sys
-            sys.path.append(%r)
-            import test_gum.test_lizard""" % (z_fn,))
-        assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED
-
-    def test_readonly(self, testdir):
-        sub = testdir.mkdir("testing")
-        sub.join("test_readonly.py").write(
-        py.builtin._totext("""
-def test_rewritten():
-    assert "@py_builtins" in globals()
-            """).encode("utf-8"), "wb")
-        old_mode = sub.stat().mode
-        sub.chmod(320)
-        try:
-            assert testdir.runpytest().ret == 0
-        finally:
-            sub.chmod(old_mode)
-
-    def test_dont_write_bytecode(self, testdir, monkeypatch):
-        testdir.makepyfile("""
-            import os
-            def test_no_bytecode():
-                assert "__pycache__" in __cached__
-                assert not os.path.exists(__cached__)
-                assert not os.path.exists(os.path.dirname(__cached__))""")
-        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
-        assert testdir.runpytest_subprocess().ret == 0
-
-    @pytest.mark.skipif('"__pypy__" in sys.modules')
-    def test_pyc_vs_pyo(self, testdir, monkeypatch):
-        testdir.makepyfile("""
-            import pytest
-            def test_optimized():
-                "hello"
-                assert test_optimized.__doc__ is None"""
-        )
-        p = py.path.local.make_numbered_dir(prefix="runpytest-", keep=None,
-                                            rootdir=testdir.tmpdir)
-        tmp = "--basetemp=%s" % p
-        monkeypatch.setenv("PYTHONOPTIMIZE", "2")
-        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
-        assert testdir.runpytest_subprocess(tmp).ret == 0
-        tagged = "test_pyc_vs_pyo." + PYTEST_TAG
-        assert tagged + ".pyo" in os.listdir("__pycache__")
-        monkeypatch.undo()
-        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
-        assert testdir.runpytest_subprocess(tmp).ret == 1
-        assert tagged + ".pyc" in os.listdir("__pycache__")
-
-    def test_package(self, testdir):
-        pkg = testdir.tmpdir.join("pkg")
-        pkg.mkdir()
-        pkg.join("__init__.py").ensure()
-        pkg.join("test_blah.py").write("""
-def test_rewritten():
-    assert "@py_builtins" in globals()""")
-        assert testdir.runpytest().ret == 0
-
-    def test_translate_newlines(self, testdir):
-        content = "def test_rewritten():\r\n assert '@py_builtins' in globals()"
-        b = content.encode("utf-8")
-        testdir.tmpdir.join("test_newlines.py").write(b, "wb")
-        assert testdir.runpytest().ret == 0
-
-    @pytest.mark.skipif(sys.version_info < (3,3),
-            reason='packages without __init__.py not supported on python 2')
-    def test_package_without__init__py(self, testdir):
-        pkg = testdir.mkdir('a_package_without_init_py')
-        pkg.join('module.py').ensure()
-        testdir.makepyfile("import a_package_without_init_py.module")
-        assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED
-
-class TestAssertionRewriteHookDetails(object):
-    def test_loader_is_package_false_for_module(self, testdir):
-        testdir.makepyfile(test_fun="""
-            def test_loader():
-                assert not __loader__.is_package(__name__)
-            """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "* 1 passed*",
-        ])
-
-    def test_loader_is_package_true_for_package(self, testdir):
-        testdir.makepyfile(test_fun="""
-            def test_loader():
-                assert not __loader__.is_package(__name__)
-
-            def test_fun():
-                assert __loader__.is_package('fun')
-
-            def test_missing():
-                assert not __loader__.is_package('pytest_not_there')
-            """)
-        testdir.mkpydir('fun')
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            '* 3 passed*',
-        ])
-
-    @pytest.mark.skipif("sys.version_info[0] >= 3")
-    @pytest.mark.xfail("hasattr(sys, 'pypy_translation_info')")
-    def test_assume_ascii(self, testdir):
-        content = "u'\xe2\x99\xa5\x01\xfe'"
-        testdir.tmpdir.join("test_encoding.py").write(content, "wb")
-        res = testdir.runpytest()
-        assert res.ret != 0
-        assert "SyntaxError: Non-ASCII character" in res.stdout.str()
-
-    @pytest.mark.skipif("sys.version_info[0] >= 3")
-    def test_detect_coding_cookie(self, testdir):
-        testdir.makepyfile(test_cookie="""
-            # -*- coding: utf-8 -*-
-            u"St\xc3\xa4d"
-            def test_rewritten():
-                assert "@py_builtins" in globals()""")
-        assert testdir.runpytest().ret == 0
-
-    @pytest.mark.skipif("sys.version_info[0] >= 3")
-    def test_detect_coding_cookie_second_line(self, testdir):
-        testdir.makepyfile(test_cookie="""
-            # -*- coding: utf-8 -*-
-            u"St\xc3\xa4d"
-            def test_rewritten():
-                assert "@py_builtins" in globals()""")
-        assert testdir.runpytest().ret == 0
-
-    @pytest.mark.skipif("sys.version_info[0] >= 3")
-    def test_detect_coding_cookie_crlf(self, testdir):
-        testdir.makepyfile(test_cookie="""
-            # -*- coding: utf-8 -*-
-            u"St\xc3\xa4d"
-            def test_rewritten():
-                assert "@py_builtins" in globals()""")
-        assert testdir.runpytest().ret == 0
-
-    def test_sys_meta_path_munged(self, testdir):
-        testdir.makepyfile("""
-            def test_meta_path():
-                import sys; sys.meta_path = []""")
-        assert testdir.runpytest().ret == 0
-
-    def test_write_pyc(self, testdir, tmpdir, monkeypatch):
-        from _pytest.assertion.rewrite import _write_pyc
-        from _pytest.assertion import AssertionState
-        try:
-            import __builtin__ as b
-        except ImportError:
-            import builtins as b
-        config = testdir.parseconfig([])
-        state = AssertionState(config, "rewrite")
-        source_path = tmpdir.ensure("source.py")
-        pycpath = tmpdir.join("pyc").strpath
-        assert _write_pyc(state, [1], source_path.stat(), pycpath)
-        def open(*args):
-            e = IOError()
-            e.errno = 10
-            raise e
-        monkeypatch.setattr(b, "open", open)
-        assert not _write_pyc(state, [1], source_path.stat(), pycpath)
-
-    def test_resources_provider_for_loader(self, testdir):
-        """
-        Attempts to load resources from a package should succeed normally,
-        even when the AssertionRewriteHook is used to load the modules.
-
-        See #366 for details.
-        """
-        pytest.importorskip("pkg_resources")
-
-        testdir.mkpydir('testpkg')
-        contents = {
-            'testpkg/test_pkg': """
-                import pkg_resources
-
-                import pytest
-                from _pytest.assertion.rewrite import AssertionRewritingHook
-
-                def test_load_resource():
-                    assert isinstance(__loader__, AssertionRewritingHook)
-                    res = pkg_resources.resource_string(__name__, 'resource.txt')
-                    res = res.decode('ascii')
-                    assert res == 'Load me please.'
-                """,
-        }
-        testdir.makepyfile(**contents)
-        testdir.maketxtfile(**{'testpkg/resource': "Load me please."})
-
-        result = testdir.runpytest_subprocess()
-        result.assert_outcomes(passed=1)
-
-    def test_read_pyc(self, tmpdir):
-        """
-        Ensure that the `_read_pyc` can properly deal with corrupted pyc files.
-        In those circumstances it should just give up instead of generating
-        an exception that is propagated to the caller.
-        """
-        import py_compile
-        from _pytest.assertion.rewrite import _read_pyc
-
-        source = tmpdir.join('source.py')
-        pyc = source + 'c'
-
-        source.write('def test(): pass')
-        py_compile.compile(str(source), str(pyc))
-
-        contents = pyc.read(mode='rb')
-        strip_bytes = 20  # header is around 8 bytes, strip a little more
-        assert len(contents) > strip_bytes
-        pyc.write(contents[:strip_bytes], mode='wb')
-
-        assert _read_pyc(source, str(pyc)) is None  # no error
-
-    def test_reload_is_same(self, testdir):
-        # A file that will be picked up during collecting.
-        testdir.tmpdir.join("file.py").ensure()
-        testdir.tmpdir.join("pytest.ini").write(py.std.textwrap.dedent("""
-            [pytest]
-            python_files = *.py
-        """))
-
-        testdir.makepyfile(test_fun="""
-            import sys
-            try:
-                from imp import reload
-            except ImportError:
-                pass
-
-            def test_loader():
-                import file
-                assert sys.modules["file"] is reload(file)
-            """)
-        result = testdir.runpytest('-s')
-        result.stdout.fnmatch_lines([
-            "* 1 passed*",
-        ])
-
-    def test_get_data_support(self, testdir):
-        """Implement optional PEP302 api (#808).
-        """
-        path = testdir.mkpydir("foo")
-        path.join("test_foo.py").write(_pytest._code.Source("""
-            class Test:
-                def test_foo(self):
-                    import pkgutil
-                    data = pkgutil.get_data('foo.test_foo', 'data.txt')
-                    assert data == b'Hey'
-        """))
-        path.join('data.txt').write('Hey')
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('*1 passed*')
-
-
-def test_issue731(testdir):
-    testdir.makepyfile("""
-    class LongReprWithBraces(object):
-        def __repr__(self):
-           return 'LongReprWithBraces({' + ('a' * 80) + '}' + ('a' * 120) + ')'
-
-        def some_method(self):
-            return False
-
-    def test_long_repr():
-        obj = LongReprWithBraces()
-        assert obj.some_method()
-    """)
-    result = testdir.runpytest()
-    assert 'unbalanced braces' not in result.stdout.str()
-
-
-def test_collapse_false_unbalanced_braces():
-    util._collapse_false('some text{ False\n{False = some more text\n}')
diff --git a/tools/pytest/testing/test_cache.py b/tools/pytest/testing/test_cache.py
deleted file mode 100755
index 98053f8..0000000
--- a/tools/pytest/testing/test_cache.py
+++ /dev/null
@@ -1,386 +0,0 @@
-import sys
-
-import _pytest
-import pytest
-import os
-import shutil
-
-pytest_plugins = "pytester",
-
-class TestNewAPI:
-    def test_config_cache_makedir(self, testdir):
-        testdir.makeini("[pytest]")
-        config = testdir.parseconfigure()
-        with pytest.raises(ValueError):
-            config.cache.makedir("key/name")
-
-        p = config.cache.makedir("name")
-        assert p.check()
-
-    def test_config_cache_dataerror(self, testdir):
-        testdir.makeini("[pytest]")
-        config = testdir.parseconfigure()
-        cache = config.cache
-        pytest.raises(TypeError, lambda: cache.set("key/name", cache))
-        config.cache.set("key/name", 0)
-        config.cache._getvaluepath("key/name").write("123invalid")
-        val = config.cache.get("key/name", -2)
-        assert val == -2
-
-    def test_cache_writefail_cachfile_silent(self, testdir):
-        testdir.makeini("[pytest]")
-        testdir.tmpdir.join('.cache').write('gone wrong')
-        config = testdir.parseconfigure()
-        cache = config.cache
-        cache.set('test/broken', [])
-
-    @pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows')
-    def test_cache_writefail_permissions(self, testdir):
-        testdir.makeini("[pytest]")
-        testdir.tmpdir.ensure_dir('.cache').chmod(0)
-        config = testdir.parseconfigure()
-        cache = config.cache
-        cache.set('test/broken', [])
-
-    @pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows')
-    def test_cache_failure_warns(self, testdir):
-        testdir.tmpdir.ensure_dir('.cache').chmod(0)
-        testdir.makepyfile("""
-            def test_error():
-                raise Exception
-
-        """)
-        result = testdir.runpytest('-rw')
-        assert result.ret == 1
-        result.stdout.fnmatch_lines([
-            "*could not create cache path*",
-            "*1 pytest-warnings*",
-        ])
-
-    def test_config_cache(self, testdir):
-        testdir.makeconftest("""
-            def pytest_configure(config):
-                # see that we get cache information early on
-                assert hasattr(config, "cache")
-        """)
-        testdir.makepyfile("""
-            def test_session(pytestconfig):
-                assert hasattr(pytestconfig, "cache")
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 0
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_cachefuncarg(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def test_cachefuncarg(cache):
-                val = cache.get("some/thing", None)
-                assert val is None
-                cache.set("some/thing", [1])
-                pytest.raises(TypeError, lambda: cache.get("some/thing"))
-                val = cache.get("some/thing", [])
-                assert val == [1]
-        """)
-        result = testdir.runpytest()
-        assert result.ret == 0
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-
-
-def test_cache_reportheader(testdir):
-    testdir.makepyfile("""
-        def test_hello():
-            pass
-    """)
-    result = testdir.runpytest("-v")
-    result.stdout.fnmatch_lines([
-        "cachedir: .cache"
-    ])
-
-
-def test_cache_show(testdir):
-    result = testdir.runpytest("--cache-show")
-    assert result.ret == 0
-    result.stdout.fnmatch_lines([
-        "*cache is empty*"
-    ])
-    testdir.makeconftest("""
-        def pytest_configure(config):
-            config.cache.set("my/name", [1,2,3])
-            config.cache.set("other/some", {1:2})
-            dp = config.cache.makedir("mydb")
-            dp.ensure("hello")
-            dp.ensure("world")
-    """)
-    result = testdir.runpytest()
-    assert result.ret == 5  # no tests executed
-    result = testdir.runpytest("--cache-show")
-    result.stdout.fnmatch_lines_random([
-        "*cachedir:*",
-        "-*cache values*-",
-        "*my/name contains:",
-        "  [1, 2, 3]",
-        "*other/some contains*",
-        "  {*1*: 2}",
-        "-*cache directories*-",
-        "*mydb/hello*length 0*",
-        "*mydb/world*length 0*",
-    ])
-
-
-class TestLastFailed:
-
-    def test_lastfailed_usecase(self, testdir, monkeypatch):
-        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
-        p = testdir.makepyfile("""
-            def test_1():
-                assert 0
-            def test_2():
-                assert 0
-            def test_3():
-                assert 1
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*2 failed*",
-        ])
-        p.write(_pytest._code.Source("""
-            def test_1():
-                assert 1
-
-            def test_2():
-                assert 1
-
-            def test_3():
-                assert 0
-        """))
-        result = testdir.runpytest("--lf")
-        result.stdout.fnmatch_lines([
-            "*2 passed*1 desel*",
-        ])
-        result = testdir.runpytest("--lf")
-        result.stdout.fnmatch_lines([
-            "*1 failed*2 passed*",
-        ])
-        result = testdir.runpytest("--lf", "--cache-clear")
-        result.stdout.fnmatch_lines([
-            "*1 failed*2 passed*",
-        ])
-
-        # Run this again to make sure clear-cache is robust
-        if os.path.isdir('.cache'):
-            shutil.rmtree('.cache')
-        result = testdir.runpytest("--lf", "--cache-clear")
-        result.stdout.fnmatch_lines([
-            "*1 failed*2 passed*",
-        ])
-
-    def test_failedfirst_order(self, testdir):
-        testdir.tmpdir.join('test_a.py').write(_pytest._code.Source("""
-            def test_always_passes():
-                assert 1
-        """))
-        testdir.tmpdir.join('test_b.py').write(_pytest._code.Source("""
-            def test_always_fails():
-                assert 0
-        """))
-        result = testdir.runpytest()
-        # Test order will be collection order; alphabetical
-        result.stdout.fnmatch_lines([
-            "test_a.py*",
-            "test_b.py*",
-        ])
-        result = testdir.runpytest("--lf", "--ff")
-        # Test order will be failing tests firs
-        result.stdout.fnmatch_lines([
-            "test_b.py*",
-            "test_a.py*",
-        ])
-
-    def test_lastfailed_difference_invocations(self, testdir, monkeypatch):
-        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
-        testdir.makepyfile(test_a="""
-            def test_a1():
-                assert 0
-            def test_a2():
-                assert 1
-        """, test_b="""
-            def test_b1():
-                assert 0
-        """)
-        p = testdir.tmpdir.join("test_a.py")
-        p2 = testdir.tmpdir.join("test_b.py")
-
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*2 failed*",
-        ])
-        result = testdir.runpytest("--lf", p2)
-        result.stdout.fnmatch_lines([
-            "*1 failed*",
-        ])
-        p2.write(_pytest._code.Source("""
-            def test_b1():
-                assert 1
-        """))
-        result = testdir.runpytest("--lf", p2)
-        result.stdout.fnmatch_lines([
-            "*1 passed*",
-        ])
-        result = testdir.runpytest("--lf", p)
-        result.stdout.fnmatch_lines([
-            "*1 failed*1 desel*",
-        ])
-
-    def test_lastfailed_usecase_splice(self, testdir, monkeypatch):
-        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
-        testdir.makepyfile("""
-            def test_1():
-                assert 0
-        """)
-        p2 = testdir.tmpdir.join("test_something.py")
-        p2.write(_pytest._code.Source("""
-            def test_2():
-                assert 0
-        """))
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*2 failed*",
-        ])
-        result = testdir.runpytest("--lf", p2)
-        result.stdout.fnmatch_lines([
-            "*1 failed*",
-        ])
-        result = testdir.runpytest("--lf")
-        result.stdout.fnmatch_lines([
-            "*2 failed*",
-        ])
-
-    def test_lastfailed_xpass(self, testdir):
-        testdir.inline_runsource("""
-            import pytest
-            @pytest.mark.xfail
-            def test_hello():
-                assert 1
-        """)
-        config = testdir.parseconfigure()
-        lastfailed = config.cache.get("cache/lastfailed", -1)
-        assert lastfailed == -1
-
-    def test_non_serializable_parametrize(self, testdir):
-        """Test that failed parametrized tests with unmarshable parameters
-        don't break pytest-cache.
-        """
-        testdir.makepyfile(r"""
-            import pytest
-
-            @pytest.mark.parametrize('val', [
-                b'\xac\x10\x02G',
-            ])
-            def test_fail(val):
-                assert False
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('*1 failed in*')
-
-    def test_lastfailed_collectfailure(self, testdir, monkeypatch):
-
-        testdir.makepyfile(test_maybe="""
-            import py
-            env = py.std.os.environ
-            if '1' == env['FAILIMPORT']:
-                raise ImportError('fail')
-            def test_hello():
-                assert '0' == env['FAILTEST']
-        """)
-
-        def rlf(fail_import, fail_run):
-            monkeypatch.setenv('FAILIMPORT', fail_import)
-            monkeypatch.setenv('FAILTEST', fail_run)
-
-            testdir.runpytest('-q')
-            config = testdir.parseconfigure()
-            lastfailed = config.cache.get("cache/lastfailed", -1)
-            return lastfailed
-
-        lastfailed = rlf(fail_import=0, fail_run=0)
-        assert lastfailed == -1
-
-        lastfailed = rlf(fail_import=1, fail_run=0)
-        assert list(lastfailed) == ['test_maybe.py']
-
-        lastfailed = rlf(fail_import=0, fail_run=1)
-        assert list(lastfailed) == ['test_maybe.py::test_hello']
-
-
-    def test_lastfailed_failure_subset(self, testdir, monkeypatch):
-
-        testdir.makepyfile(test_maybe="""
-            import py
-            env = py.std.os.environ
-            if '1' == env['FAILIMPORT']:
-                raise ImportError('fail')
-            def test_hello():
-                assert '0' == env['FAILTEST']
-        """)
-
-        testdir.makepyfile(test_maybe2="""
-            import py
-            env = py.std.os.environ
-            if '1' == env['FAILIMPORT']:
-                raise ImportError('fail')
-            def test_hello():
-                assert '0' == env['FAILTEST']
-
-            def test_pass():
-                pass
-        """)
-
-        def rlf(fail_import, fail_run, args=()):
-            monkeypatch.setenv('FAILIMPORT', fail_import)
-            monkeypatch.setenv('FAILTEST', fail_run)
-
-            result = testdir.runpytest('-q', '--lf', *args)
-            config = testdir.parseconfigure()
-            lastfailed = config.cache.get("cache/lastfailed", -1)
-            return result, lastfailed
-
-        result, lastfailed = rlf(fail_import=0, fail_run=0)
-        assert lastfailed == -1
-        result.stdout.fnmatch_lines([
-            '*3 passed*',
-        ])
-
-        result, lastfailed = rlf(fail_import=1, fail_run=0)
-        assert sorted(list(lastfailed)) == ['test_maybe.py', 'test_maybe2.py']
-
-
-        result, lastfailed = rlf(fail_import=0, fail_run=0,
-                                 args=('test_maybe2.py',))
-        assert list(lastfailed) == ['test_maybe.py']
-
-
-        # edge case of test selection - even if we remember failures
-        # from other tests we still need to run all tests if no test
-        # matches the failures
-        result, lastfailed = rlf(fail_import=0, fail_run=0,
-                                 args=('test_maybe2.py',))
-        assert list(lastfailed) == ['test_maybe.py']
-        result.stdout.fnmatch_lines([
-            '*2 passed*',
-        ])
-
-    def test_lastfailed_creates_cache_when_needed(self, testdir):
-        # Issue #1342
-        testdir.makepyfile(test_empty='')
-        testdir.runpytest('-q', '--lf')
-        assert not os.path.exists('.cache')
-
-        testdir.makepyfile(test_successful='def test_success():\n    assert True')
-        testdir.runpytest('-q', '--lf')
-        assert not os.path.exists('.cache')
-
-        testdir.makepyfile(test_errored='def test_error():\n    assert False')
-        testdir.runpytest('-q', '--lf')
-        assert os.path.exists('.cache')
diff --git a/tools/pytest/testing/test_capture.py b/tools/pytest/testing/test_capture.py
deleted file mode 100644
index 7366069..0000000
--- a/tools/pytest/testing/test_capture.py
+++ /dev/null
@@ -1,1068 +0,0 @@
-# note: py.io capture tests where copied from
-# pylib 1.4.20.dev2 (rev 13d9af95547e)
-from __future__ import with_statement
-import pickle
-import os
-import sys
-
-import _pytest._code
-import py
-import pytest
-import contextlib
-
-from _pytest import capture
-from _pytest.capture import CaptureManager
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-from py.builtin import print_
-
-needsosdup = pytest.mark.xfail("not hasattr(os, 'dup')")
-
-if sys.version_info >= (3, 0):
-    def tobytes(obj):
-        if isinstance(obj, str):
-            obj = obj.encode('UTF-8')
-        assert isinstance(obj, bytes)
-        return obj
-
-    def totext(obj):
-        if isinstance(obj, bytes):
-            obj = str(obj, 'UTF-8')
-        assert isinstance(obj, str)
-        return obj
-else:
-    def tobytes(obj):
-        if isinstance(obj, unicode):
-            obj = obj.encode('UTF-8')
-        assert isinstance(obj, str)
-        return obj
-
-    def totext(obj):
-        if isinstance(obj, str):
-            obj = unicode(obj, 'UTF-8')
-        assert isinstance(obj, unicode)
-        return obj
-
-
-def oswritebytes(fd, obj):
-    os.write(fd, tobytes(obj))
-
-
-
-def StdCaptureFD(out=True, err=True, in_=True):
-    return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture)
-
-def StdCapture(out=True, err=True, in_=True):
-    return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture)
-
-
-class TestCaptureManager:
-    def test_getmethod_default_no_fd(self, monkeypatch):
-        from _pytest.capture import pytest_addoption
-        from _pytest.config import Parser
-        parser = Parser()
-        pytest_addoption(parser)
-        default = parser._groups[0].options[0].default
-        assert default == "fd" if hasattr(os, "dup") else "sys"
-        parser = Parser()
-        monkeypatch.delattr(os, 'dup', raising=False)
-        pytest_addoption(parser)
-        assert parser._groups[0].options[0].default == "sys"
-
-    @needsosdup
-    @pytest.mark.parametrize("method",
-        ['no', 'sys', pytest.mark.skipif('not hasattr(os, "dup")', 'fd')])
-    def test_capturing_basic_api(self, method):
-        capouter = StdCaptureFD()
-        old = sys.stdout, sys.stderr, sys.stdin
-        try:
-            capman = CaptureManager(method)
-            capman.init_capturings()
-            outerr = capman.suspendcapture()
-            assert outerr == ("", "")
-            outerr = capman.suspendcapture()
-            assert outerr == ("", "")
-            print ("hello")
-            out, err = capman.suspendcapture()
-            if method == "no":
-                assert old == (sys.stdout, sys.stderr, sys.stdin)
-            else:
-                assert not out
-            capman.resumecapture()
-            print ("hello")
-            out, err = capman.suspendcapture()
-            if method != "no":
-                assert out == "hello\n"
-            capman.reset_capturings()
-        finally:
-            capouter.stop_capturing()
-
-    @needsosdup
-    def test_init_capturing(self):
-        capouter = StdCaptureFD()
-        try:
-            capman = CaptureManager("fd")
-            capman.init_capturings()
-            pytest.raises(AssertionError, "capman.init_capturings()")
-            capman.reset_capturings()
-        finally:
-            capouter.stop_capturing()
-
-
-@pytest.mark.parametrize("method", ['fd', 'sys'])
-def test_capturing_unicode(testdir, method):
-    if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (2,2):
-        pytest.xfail("does not work on pypy < 2.2")
-    if sys.version_info >= (3, 0):
-        obj = "'b\u00f6y'"
-    else:
-        obj = "u'\u00f6y'"
-    testdir.makepyfile("""
-        # coding=utf8
-        # taken from issue 227 from nosetests
-        def test_unicode():
-            import sys
-            print (sys.stdout)
-            print (%s)
-    """ % obj)
-    result = testdir.runpytest("--capture=%s" % method)
-    result.stdout.fnmatch_lines([
-        "*1 passed*"
-    ])
-
-
-@pytest.mark.parametrize("method", ['fd', 'sys'])
-def test_capturing_bytes_in_utf8_encoding(testdir, method):
-    testdir.makepyfile("""
-        def test_unicode():
-            print ('b\\u00f6y')
-    """)
-    result = testdir.runpytest("--capture=%s" % method)
-    result.stdout.fnmatch_lines([
-        "*1 passed*"
-    ])
-
-
-def test_collect_capturing(testdir):
-    p = testdir.makepyfile("""
-        print ("collect %s failure" % 13)
-        import xyz42123
-    """)
-    result = testdir.runpytest(p)
-    result.stdout.fnmatch_lines([
-        "*Captured stdout*",
-        "*collect 13 failure*",
-    ])
-
-
-class TestPerTestCapturing:
-    def test_capture_and_fixtures(self, testdir):
-        p = testdir.makepyfile("""
-            def setup_module(mod):
-                print ("setup module")
-            def setup_function(function):
-                print ("setup " + function.__name__)
-            def test_func1():
-                print ("in func1")
-                assert 0
-            def test_func2():
-                print ("in func2")
-                assert 0
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "setup module*",
-            "setup test_func1*",
-            "in func1*",
-            "setup test_func2*",
-            "in func2*",
-        ])
-
-    @pytest.mark.xfail(reason="unimplemented feature")
-    def test_capture_scope_cache(self, testdir):
-        p = testdir.makepyfile("""
-            import sys
-            def setup_module(func):
-                print ("module-setup")
-            def setup_function(func):
-                print ("function-setup")
-            def test_func():
-                print ("in function")
-                assert 0
-            def teardown_function(func):
-                print ("in teardown")
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*test_func():*",
-            "*Captured stdout during setup*",
-            "module-setup*",
-            "function-setup*",
-            "*Captured stdout*",
-            "in teardown*",
-        ])
-
-    def test_no_carry_over(self, testdir):
-        p = testdir.makepyfile("""
-            def test_func1():
-                print ("in func1")
-            def test_func2():
-                print ("in func2")
-                assert 0
-        """)
-        result = testdir.runpytest(p)
-        s = result.stdout.str()
-        assert "in func1" not in s
-        assert "in func2" in s
-
-    def test_teardown_capturing(self, testdir):
-        p = testdir.makepyfile("""
-            def setup_function(function):
-                print ("setup func1")
-            def teardown_function(function):
-                print ("teardown func1")
-                assert 0
-            def test_func1():
-                print ("in func1")
-                pass
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            '*teardown_function*',
-            '*Captured stdout*',
-            "setup func1*",
-            "in func1*",
-            "teardown func1*",
-            #"*1 fixture failure*"
-        ])
-
-    def test_teardown_capturing_final(self, testdir):
-        p = testdir.makepyfile("""
-            def teardown_module(mod):
-                print ("teardown module")
-                assert 0
-            def test_func():
-                pass
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*def teardown_module(mod):*",
-            "*Captured stdout*",
-            "*teardown module*",
-            "*1 error*",
-        ])
-
-    def test_capturing_outerr(self, testdir):
-        p1 = testdir.makepyfile("""
-            import sys
-            def test_capturing():
-                print (42)
-                sys.stderr.write(str(23))
-            def test_capturing_error():
-                print (1)
-                sys.stderr.write(str(2))
-                raise ValueError
-        """)
-        result = testdir.runpytest(p1)
-        result.stdout.fnmatch_lines([
-            "*test_capturing_outerr.py .F",
-            "====* FAILURES *====",
-            "____*____",
-            "*test_capturing_outerr.py:8: ValueError",
-            "*--- Captured stdout *call*",
-            "1",
-            "*--- Captured stderr *call*",
-            "2",
-        ])
-
-
-class TestLoggingInteraction:
-    def test_logging_stream_ownership(self, testdir):
-        p = testdir.makepyfile("""
-            def test_logging():
-                import logging
-                import pytest
-                stream = capture.TextIO()
-                logging.basicConfig(stream=stream)
-                stream.close() # to free memory/release resources
-        """)
-        result = testdir.runpytest_subprocess(p)
-        result.stderr.str().find("atexit") == -1
-
-    def test_logging_and_immediate_setupteardown(self, testdir):
-        p = testdir.makepyfile("""
-            import logging
-            def setup_function(function):
-                logging.warn("hello1")
-
-            def test_logging():
-                logging.warn("hello2")
-                assert 0
-
-            def teardown_function(function):
-                logging.warn("hello3")
-                assert 0
-        """)
-        for optargs in (('--capture=sys',), ('--capture=fd',)):
-            print (optargs)
-            result = testdir.runpytest_subprocess(p, *optargs)
-            s = result.stdout.str()
-            result.stdout.fnmatch_lines([
-                "*WARN*hello3",  # errors show first!
-                "*WARN*hello1",
-                "*WARN*hello2",
-            ])
-            # verify proper termination
-            assert "closed" not in s
-
-    def test_logging_and_crossscope_fixtures(self, testdir):
-        p = testdir.makepyfile("""
-            import logging
-            def setup_module(function):
-                logging.warn("hello1")
-
-            def test_logging():
-                logging.warn("hello2")
-                assert 0
-
-            def teardown_module(function):
-                logging.warn("hello3")
-                assert 0
-        """)
-        for optargs in (('--capture=sys',), ('--capture=fd',)):
-            print (optargs)
-            result = testdir.runpytest_subprocess(p, *optargs)
-            s = result.stdout.str()
-            result.stdout.fnmatch_lines([
-                "*WARN*hello3",  # errors come first
-                "*WARN*hello1",
-                "*WARN*hello2",
-            ])
-            # verify proper termination
-            assert "closed" not in s
-
-    def test_logging_initialized_in_test(self, testdir):
-        p = testdir.makepyfile("""
-            import sys
-            def test_something():
-                # pytest does not import logging
-                assert 'logging' not in sys.modules
-                import logging
-                logging.basicConfig()
-                logging.warn("hello432")
-                assert 0
-        """)
-        result = testdir.runpytest_subprocess(
-            p, "--traceconfig",
-            "-p", "no:capturelog")
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "*hello432*",
-        ])
-        assert 'operation on closed file' not in result.stderr.str()
-
-    def test_conftestlogging_is_shown(self, testdir):
-        testdir.makeconftest("""
-                import logging
-                logging.basicConfig()
-                logging.warn("hello435")
-        """)
-        # make sure that logging is still captured in tests
-        result = testdir.runpytest_subprocess("-s", "-p", "no:capturelog")
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        result.stderr.fnmatch_lines([
-            "WARNING*hello435*",
-        ])
-        assert 'operation on closed file' not in result.stderr.str()
-
-    def test_conftestlogging_and_test_logging(self, testdir):
-        testdir.makeconftest("""
-                import logging
-                logging.basicConfig()
-        """)
-        # make sure that logging is still captured in tests
-        p = testdir.makepyfile("""
-            def test_hello():
-                import logging
-                logging.warn("hello433")
-                assert 0
-        """)
-        result = testdir.runpytest_subprocess(p, "-p", "no:capturelog")
-        assert result.ret != 0
-        result.stdout.fnmatch_lines([
-            "WARNING*hello433*",
-        ])
-        assert 'something' not in result.stderr.str()
-        assert 'operation on closed file' not in result.stderr.str()
-
-
-class TestCaptureFixture:
-    @pytest.mark.parametrize("opt", [[], ["-s"]])
-    def test_std_functional(self, testdir, opt):
-        reprec = testdir.inline_runsource("""
-            def test_hello(capsys):
-                print (42)
-                out, err = capsys.readouterr()
-                assert out.startswith("42")
-        """, *opt)
-        reprec.assertoutcome(passed=1)
-
-    def test_capsyscapfd(self, testdir):
-        p = testdir.makepyfile("""
-            def test_one(capsys, capfd):
-                pass
-            def test_two(capfd, capsys):
-                pass
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*ERROR*setup*test_one*",
-            "*capsys*capfd*same*time*",
-            "*ERROR*setup*test_two*",
-            "*capsys*capfd*same*time*",
-            "*2 error*"])
-
-    @pytest.mark.parametrize("method", ["sys", "fd"])
-    def test_capture_is_represented_on_failure_issue128(self, testdir, method):
-        p = testdir.makepyfile("""
-            def test_hello(cap%s):
-                print ("xxx42xxx")
-                assert 0
-        """ % method)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "xxx42xxx",
-        ])
-
-    @needsosdup
-    def test_stdfd_functional(self, testdir):
-        reprec = testdir.inline_runsource("""
-            def test_hello(capfd):
-                import os
-                os.write(1, "42".encode('ascii'))
-                out, err = capfd.readouterr()
-                assert out.startswith("42")
-                capfd.close()
-        """)
-        reprec.assertoutcome(passed=1)
-
-    def test_partial_setup_failure(self, testdir):
-        p = testdir.makepyfile("""
-            def test_hello(capsys, missingarg):
-                pass
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*test_partial_setup_failure*",
-            "*1 error*",
-        ])
-
-    @needsosdup
-    def test_keyboardinterrupt_disables_capturing(self, testdir):
-        p = testdir.makepyfile("""
-            def test_hello(capfd):
-                import os
-                os.write(1, str(42).encode('ascii'))
-                raise KeyboardInterrupt()
-        """)
-        result = testdir.runpytest_subprocess(p)
-        result.stdout.fnmatch_lines([
-            "*KeyboardInterrupt*"
-        ])
-        assert result.ret == 2
-
-    @pytest.mark.issue14
-    def test_capture_and_logging(self, testdir):
-        p = testdir.makepyfile("""
-            import logging
-            def test_log(capsys):
-                logging.error('x')
-            """)
-        result = testdir.runpytest_subprocess(p)
-        assert 'closed' not in result.stderr.str()
-
-
-def test_setup_failure_does_not_kill_capturing(testdir):
-    sub1 = testdir.mkpydir("sub1")
-    sub1.join("conftest.py").write(_pytest._code.Source("""
-        def pytest_runtest_setup(item):
-            raise ValueError(42)
-    """))
-    sub1.join("test_mod.py").write("def test_func1(): pass")
-    result = testdir.runpytest(testdir.tmpdir, '--traceconfig')
-    result.stdout.fnmatch_lines([
-        "*ValueError(42)*",
-        "*1 error*"
-    ])
-
-
-def test_fdfuncarg_skips_on_no_osdup(testdir):
-    testdir.makepyfile("""
-        import os
-        if hasattr(os, 'dup'):
-            del os.dup
-        def test_hello(capfd):
-            pass
-    """)
-    result = testdir.runpytest_subprocess("--capture=no")
-    result.stdout.fnmatch_lines([
-        "*1 skipped*"
-    ])
-
-
-def test_capture_conftest_runtest_setup(testdir):
-    testdir.makeconftest("""
-        def pytest_runtest_setup():
-            print ("hello19")
-    """)
-    testdir.makepyfile("def test_func(): pass")
-    result = testdir.runpytest()
-    assert result.ret == 0
-    assert 'hello19' not in result.stdout.str()
-
-
-def test_capture_badoutput_issue412(testdir):
-    testdir.makepyfile("""
-        import os
-
-        def test_func():
-            omg = bytearray([1,129,1])
-            os.write(1, omg)
-            assert 0
-        """)
-    result = testdir.runpytest('--cap=fd')
-    result.stdout.fnmatch_lines('''
-        *def test_func*
-        *assert 0*
-        *Captured*
-        *1 failed*
-    ''')
-
-
-def test_capture_early_option_parsing(testdir):
-    testdir.makeconftest("""
-        def pytest_runtest_setup():
-            print ("hello19")
-    """)
-    testdir.makepyfile("def test_func(): pass")
-    result = testdir.runpytest("-vs")
-    assert result.ret == 0
-    assert 'hello19' in result.stdout.str()
-
-
-def test_capture_binary_output(testdir):
-    testdir.makepyfile(r"""
-        import pytest
-
-        def test_a():
-            import sys
-            import subprocess
-            subprocess.call([sys.executable, __file__])
-
-        def test_foo():
-            import os;os.write(1, b'\xc3')
-
-        if __name__ == '__main__':
-            test_foo()
-        """)
-    result = testdir.runpytest('--assert=plain')
-    result.assert_outcomes(passed=2)
-
-
-def test_error_during_readouterr(testdir):
-    """Make sure we suspend capturing if errors occurr during readouterr"""
-    testdir.makepyfile(pytest_xyz="""
-        from _pytest.capture import FDCapture
-        def bad_snap(self):
-            raise Exception('boom')
-        assert FDCapture.snap
-        FDCapture.snap = bad_snap
-    """)
-    result = testdir.runpytest_subprocess(
-        "-p", "pytest_xyz", "--version", syspathinsert=True
-    )
-    result.stderr.fnmatch_lines([
-        "*in bad_snap",
-        "    raise Exception('boom')",
-        "Exception: boom",
-    ])
-
-
-class TestTextIO:
-    def test_text(self):
-        f = capture.TextIO()
-        f.write("hello")
-        s = f.getvalue()
-        assert s == "hello"
-        f.close()
-
-    def test_unicode_and_str_mixture(self):
-        f = capture.TextIO()
-        if sys.version_info >= (3, 0):
-            f.write("\u00f6")
-            pytest.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))")
-        else:
-            f.write(unicode("\u00f6", 'UTF-8'))
-            f.write("hello")  # bytes
-            s = f.getvalue()
-            f.close()
-            assert isinstance(s, unicode)
-
-
-def test_bytes_io():
-    f = py.io.BytesIO()
-    f.write(tobytes("hello"))
-    pytest.raises(TypeError, "f.write(totext('hello'))")
-    s = f.getvalue()
-    assert s == tobytes("hello")
-
-
-def test_dontreadfrominput():
-    from _pytest.capture import DontReadFromInput
-    f = DontReadFromInput()
-    assert not f.isatty()
-    pytest.raises(IOError, f.read)
-    pytest.raises(IOError, f.readlines)
-    pytest.raises(IOError, iter, f)
-    pytest.raises(ValueError, f.fileno)
-    f.close()  # just for completeness
-
-
-@pytest.yield_fixture
-def tmpfile(testdir):
-    f = testdir.makepyfile("").open('wb+')
-    yield f
-    if not f.closed:
-        f.close()
-
-@needsosdup
-def test_dupfile(tmpfile):
-    flist = []
-    for i in range(5):
-        nf = capture.safe_text_dupfile(tmpfile, "wb")
-        assert nf != tmpfile
-        assert nf.fileno() != tmpfile.fileno()
-        assert nf not in flist
-        print_(i, end="", file=nf)
-        flist.append(nf)
-    for i in range(5):
-        f = flist[i]
-        f.close()
-    tmpfile.seek(0)
-    s = tmpfile.read()
-    assert "01234" in repr(s)
-    tmpfile.close()
-
-def test_dupfile_on_bytesio():
-    io = py.io.BytesIO()
-    f = capture.safe_text_dupfile(io, "wb")
-    f.write("hello")
-    assert io.getvalue() == b"hello"
-
-def test_dupfile_on_textio():
-    io = py.io.TextIO()
-    f = capture.safe_text_dupfile(io, "wb")
-    f.write("hello")
-    assert io.getvalue() == "hello"
-
-
-@contextlib.contextmanager
-def lsof_check():
-    pid = os.getpid()
-    try:
-        out = py.process.cmdexec("lsof -p %d" % pid)
-    except (py.process.cmdexec.Error, UnicodeDecodeError):
-        # about UnicodeDecodeError, see note on pytester
-        pytest.skip("could not run 'lsof'")
-    yield
-    out2 = py.process.cmdexec("lsof -p %d" % pid)
-    len1 = len([x for x in out.split("\n") if "REG" in x])
-    len2 = len([x for x in out2.split("\n") if "REG" in x])
-    assert len2 < len1 + 3, out2
-
-
-class TestFDCapture:
-    pytestmark = needsosdup
-
-    def test_simple(self, tmpfile):
-        fd = tmpfile.fileno()
-        cap = capture.FDCapture(fd)
-        data = tobytes("hello")
-        os.write(fd, data)
-        s = cap.snap()
-        cap.done()
-        assert not s
-        cap = capture.FDCapture(fd)
-        cap.start()
-        os.write(fd, data)
-        s = cap.snap()
-        cap.done()
-        assert s == "hello"
-
-    def test_simple_many(self, tmpfile):
-        for i in range(10):
-            self.test_simple(tmpfile)
-
-    def test_simple_many_check_open_files(self, testdir):
-        with lsof_check():
-            with testdir.makepyfile("").open('wb+') as tmpfile:
-                self.test_simple_many(tmpfile)
-
-    def test_simple_fail_second_start(self, tmpfile):
-        fd = tmpfile.fileno()
-        cap = capture.FDCapture(fd)
-        cap.done()
-        pytest.raises(ValueError, cap.start)
-
-    def test_stderr(self):
-        cap = capture.FDCapture(2)
-        cap.start()
-        print_("hello", file=sys.stderr)
-        s = cap.snap()
-        cap.done()
-        assert s == "hello\n"
-
-    def test_stdin(self, tmpfile):
-        cap = capture.FDCapture(0)
-        cap.start()
-        x = os.read(0, 100).strip()
-        cap.done()
-        assert x == tobytes('')
-
-    def test_writeorg(self, tmpfile):
-        data1, data2 = tobytes("foo"), tobytes("bar")
-        cap = capture.FDCapture(tmpfile.fileno())
-        cap.start()
-        tmpfile.write(data1)
-        tmpfile.flush()
-        cap.writeorg(data2)
-        scap = cap.snap()
-        cap.done()
-        assert scap == totext(data1)
-        with open(tmpfile.name, 'rb') as stmp_file:
-            stmp = stmp_file.read()
-            assert stmp == data2
-
-    def test_simple_resume_suspend(self, tmpfile):
-        with saved_fd(1):
-            cap = capture.FDCapture(1)
-            cap.start()
-            data = tobytes("hello")
-            os.write(1, data)
-            sys.stdout.write("whatever")
-            s = cap.snap()
-            assert s == "hellowhatever"
-            cap.suspend()
-            os.write(1, tobytes("world"))
-            sys.stdout.write("qlwkej")
-            assert not cap.snap()
-            cap.resume()
-            os.write(1, tobytes("but now"))
-            sys.stdout.write(" yes\n")
-            s = cap.snap()
-            assert s == "but now yes\n"
-            cap.suspend()
-            cap.done()
-            pytest.raises(AttributeError, cap.suspend)
-
-
-@contextlib.contextmanager
-def saved_fd(fd):
-    new_fd = os.dup(fd)
-    try:
-        yield
-    finally:
-        os.dup2(new_fd, fd)
-        os.close(new_fd)
-
-
-class TestStdCapture:
-    captureclass = staticmethod(StdCapture)
-
-    @contextlib.contextmanager
-    def getcapture(self, **kw):
-        cap = self.__class__.captureclass(**kw)
-        cap.start_capturing()
-        try:
-            yield cap
-        finally:
-            cap.stop_capturing()
-
-    def test_capturing_done_simple(self):
-        with self.getcapture() as cap:
-            sys.stdout.write("hello")
-            sys.stderr.write("world")
-            out, err = cap.readouterr()
-        assert out == "hello"
-        assert err == "world"
-
-    def test_capturing_reset_simple(self):
-        with self.getcapture() as cap:
-            print("hello world")
-            sys.stderr.write("hello error\n")
-            out, err = cap.readouterr()
-        assert out == "hello world\n"
-        assert err == "hello error\n"
-
-    def test_capturing_readouterr(self):
-        with self.getcapture() as cap:
-            print ("hello world")
-            sys.stderr.write("hello error\n")
-            out, err = cap.readouterr()
-            assert out == "hello world\n"
-            assert err == "hello error\n"
-            sys.stderr.write("error2")
-            out, err = cap.readouterr()
-        assert err == "error2"
-
-    def test_capturing_readouterr_unicode(self):
-        with self.getcapture() as cap:
-            print ("hx\xc4\x85\xc4\x87")
-            out, err = cap.readouterr()
-        assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
-
-    @pytest.mark.skipif('sys.version_info >= (3,)',
-                        reason='text output different for bytes on python3')
-    def test_capturing_readouterr_decode_error_handling(self):
-        with self.getcapture() as cap:
-            # triggered a internal error in pytest
-            print('\xa6')
-            out, err = cap.readouterr()
-        assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
-
-    def test_reset_twice_error(self):
-        with self.getcapture() as cap:
-            print ("hello")
-            out, err = cap.readouterr()
-        pytest.raises(ValueError, cap.stop_capturing)
-        assert out == "hello\n"
-        assert not err
-
-    def test_capturing_modify_sysouterr_in_between(self):
-        oldout = sys.stdout
-        olderr = sys.stderr
-        with self.getcapture() as cap:
-            sys.stdout.write("hello")
-            sys.stderr.write("world")
-            sys.stdout = capture.TextIO()
-            sys.stderr = capture.TextIO()
-            print ("not seen")
-            sys.stderr.write("not seen\n")
-            out, err = cap.readouterr()
-        assert out == "hello"
-        assert err == "world"
-        assert sys.stdout == oldout
-        assert sys.stderr == olderr
-
-    def test_capturing_error_recursive(self):
-        with self.getcapture() as cap1:
-            print ("cap1")
-            with self.getcapture() as cap2:
-                print ("cap2")
-                out2, err2 = cap2.readouterr()
-                out1, err1 = cap1.readouterr()
-        assert out1 == "cap1\n"
-        assert out2 == "cap2\n"
-
-    def test_just_out_capture(self):
-        with self.getcapture(out=True, err=False) as cap:
-            sys.stdout.write("hello")
-            sys.stderr.write("world")
-            out, err = cap.readouterr()
-        assert out == "hello"
-        assert not err
-
-    def test_just_err_capture(self):
-        with self.getcapture(out=False, err=True) as cap:
-            sys.stdout.write("hello")
-            sys.stderr.write("world")
-            out, err = cap.readouterr()
-        assert err == "world"
-        assert not out
-
-    def test_stdin_restored(self):
-        old = sys.stdin
-        with self.getcapture(in_=True):
-            newstdin = sys.stdin
-        assert newstdin != sys.stdin
-        assert sys.stdin is old
-
-    def test_stdin_nulled_by_default(self):
-        print ("XXX this test may well hang instead of crashing")
-        print ("XXX which indicates an error in the underlying capturing")
-        print ("XXX mechanisms")
-        with self.getcapture():
-            pytest.raises(IOError, "sys.stdin.read()")
-
-
-class TestStdCaptureFD(TestStdCapture):
-    pytestmark = needsosdup
-    captureclass = staticmethod(StdCaptureFD)
-
-    def test_simple_only_fd(self, testdir):
-        testdir.makepyfile("""
-            import os
-            def test_x():
-                os.write(1, "hello\\n".encode("ascii"))
-                assert 0
-        """)
-        result = testdir.runpytest_subprocess()
-        result.stdout.fnmatch_lines("""
-            *test_x*
-            *assert 0*
-            *Captured stdout*
-        """)
-
-    def test_intermingling(self):
-        with self.getcapture() as cap:
-            oswritebytes(1, "1")
-            sys.stdout.write(str(2))
-            sys.stdout.flush()
-            oswritebytes(1, "3")
-            oswritebytes(2, "a")
-            sys.stderr.write("b")
-            sys.stderr.flush()
-            oswritebytes(2, "c")
-            out, err = cap.readouterr()
-        assert out == "123"
-        assert err == "abc"
-
-    def test_many(self, capfd):
-        with lsof_check():
-            for i in range(10):
-                cap = StdCaptureFD()
-                cap.stop_capturing()
-
-
-class TestStdCaptureFDinvalidFD:
-    pytestmark = needsosdup
-
-    def test_stdcapture_fd_invalid_fd(self, testdir):
-        testdir.makepyfile("""
-            import os
-            from _pytest import capture
-            def StdCaptureFD(out=True, err=True, in_=True):
-                return capture.MultiCapture(out, err, in_,
-                                              Capture=capture.FDCapture)
-            def test_stdout():
-                os.close(1)
-                cap = StdCaptureFD(out=True, err=False, in_=False)
-                cap.stop_capturing()
-            def test_stderr():
-                os.close(2)
-                cap = StdCaptureFD(out=False, err=True, in_=False)
-                cap.stop_capturing()
-            def test_stdin():
-                os.close(0)
-                cap = StdCaptureFD(out=False, err=False, in_=True)
-                cap.stop_capturing()
-        """)
-        result = testdir.runpytest_subprocess("--capture=fd")
-        assert result.ret == 0
-        assert result.parseoutcomes()['passed'] == 3
-
-
-def test_capture_not_started_but_reset():
-    capsys = StdCapture()
-    capsys.stop_capturing()
-
-
-@needsosdup
-@pytest.mark.parametrize('use', [True, False])
-def test_fdcapture_tmpfile_remains_the_same(tmpfile, use):
-    if not use:
-        tmpfile = True
-    cap = StdCaptureFD(out=False, err=tmpfile)
-    try:
-        cap.start_capturing()
-        capfile = cap.err.tmpfile
-        cap.readouterr()
-    finally:
-        cap.stop_capturing()
-    capfile2 = cap.err.tmpfile
-    assert capfile2 == capfile
-
-@needsosdup
-def test_close_and_capture_again(testdir):
-    testdir.makepyfile("""
-        import os
-        def test_close():
-            os.close(1)
-        def test_capture_again():
-            os.write(1, b"hello\\n")
-            assert 0
-    """)
-    result = testdir.runpytest_subprocess()
-    result.stdout.fnmatch_lines("""
-        *test_capture_again*
-        *assert 0*
-        *stdout*
-        *hello*
-    """)
-
-
-
-@pytest.mark.parametrize('method', ['SysCapture', 'FDCapture'])
-def test_capturing_and_logging_fundamentals(testdir, method):
-    if method == "StdCaptureFD" and not hasattr(os, 'dup'):
-        pytest.skip("need os.dup")
-    # here we check a fundamental feature
-    p = testdir.makepyfile("""
-        import sys, os
-        import py, logging
-        from _pytest import capture
-        cap = capture.MultiCapture(out=False, in_=False,
-                                     Capture=capture.%s)
-        cap.start_capturing()
-
-        logging.warn("hello1")
-        outerr = cap.readouterr()
-        print ("suspend, captured %%s" %%(outerr,))
-        logging.warn("hello2")
-
-        cap.pop_outerr_to_orig()
-        logging.warn("hello3")
-
-        outerr = cap.readouterr()
-        print ("suspend2, captured %%s" %% (outerr,))
-    """ % (method,))
-    result = testdir.runpython(p)
-    result.stdout.fnmatch_lines("""
-        suspend, captured*hello1*
-        suspend2, captured*WARNING:root:hello3*
-    """)
-    result.stderr.fnmatch_lines("""
-        WARNING:root:hello2
-    """)
-    assert "atexit" not in result.stderr.str()
-
-
-def test_error_attribute_issue555(testdir):
-    testdir.makepyfile("""
-        import sys
-        def test_capattr():
-            assert sys.stdout.errors == "strict"
-            assert sys.stderr.errors == "strict"
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-
-def test_dontreadfrominput_has_encoding(testdir):
-    testdir.makepyfile("""
-        import sys
-        def test_capattr():
-            # should not raise AttributeError
-            assert sys.stdout.encoding
-            assert sys.stderr.encoding
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-
-def test_pickling_and_unpickling_enocded_file():
-    # See https://bitbucket.org/pytest-dev/pytest/pull-request/194
-    # pickle.loads() raises infinite recursion if
-    # EncodedFile.__getattr__ is not implemented properly
-    ef = capture.EncodedFile(None, None)
-    ef_as_str = pickle.dumps(ef)
-    pickle.loads(ef_as_str)
diff --git a/tools/pytest/testing/test_collection.py b/tools/pytest/testing/test_collection.py
deleted file mode 100644
index 749c5b7..0000000
--- a/tools/pytest/testing/test_collection.py
+++ /dev/null
@@ -1,641 +0,0 @@
-import pytest, py
-
-from _pytest.main import Session, EXIT_NOTESTSCOLLECTED
-
-class TestCollector:
-    def test_collect_versus_item(self):
-        from pytest import Collector, Item
-        assert not issubclass(Collector, Item)
-        assert not issubclass(Item, Collector)
-
-    def test_compat_attributes(self, testdir, recwarn):
-        modcol = testdir.getmodulecol("""
-            def test_pass(): pass
-            def test_fail(): assert 0
-        """)
-        recwarn.clear()
-        assert modcol.Module == pytest.Module
-        assert modcol.Class == pytest.Class
-        assert modcol.Item == pytest.Item
-        assert modcol.File == pytest.File
-        assert modcol.Function == pytest.Function
-
-    def test_check_equality(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def test_pass(): pass
-            def test_fail(): assert 0
-        """)
-        fn1 = testdir.collect_by_name(modcol, "test_pass")
-        assert isinstance(fn1, pytest.Function)
-        fn2 = testdir.collect_by_name(modcol, "test_pass")
-        assert isinstance(fn2, pytest.Function)
-
-        assert fn1 == fn2
-        assert fn1 != modcol
-        if py.std.sys.version_info < (3, 0):
-            assert cmp(fn1, fn2) == 0
-        assert hash(fn1) == hash(fn2)
-
-        fn3 = testdir.collect_by_name(modcol, "test_fail")
-        assert isinstance(fn3, pytest.Function)
-        assert not (fn1 == fn3)
-        assert fn1 != fn3
-
-        for fn in fn1,fn2,fn3:
-            assert fn != 3
-            assert fn != modcol
-            assert fn != [1,2,3]
-            assert [1,2,3] != fn
-            assert modcol != fn
-
-    def test_getparent(self, testdir):
-        modcol = testdir.getmodulecol("""
-            class TestClass:
-                 def test_foo():
-                     pass
-        """)
-        cls = testdir.collect_by_name(modcol, "TestClass")
-        fn = testdir.collect_by_name(
-            testdir.collect_by_name(cls, "()"), "test_foo")
-
-        parent = fn.getparent(pytest.Module)
-        assert parent is modcol
-
-        parent = fn.getparent(pytest.Function)
-        assert parent is fn
-
-        parent = fn.getparent(pytest.Class)
-        assert parent is cls
-
-
-    def test_getcustomfile_roundtrip(self, testdir):
-        hello = testdir.makefile(".xxx", hello="world")
-        testdir.makepyfile(conftest="""
-            import pytest
-            class CustomFile(pytest.File):
-                pass
-            def pytest_collect_file(path, parent):
-                if path.ext == ".xxx":
-                    return CustomFile(path, parent=parent)
-        """)
-        node = testdir.getpathnode(hello)
-        assert isinstance(node, pytest.File)
-        assert node.name == "hello.xxx"
-        nodes = node.session.perform_collect([node.nodeid], genitems=False)
-        assert len(nodes) == 1
-        assert isinstance(nodes[0], pytest.File)
-
-class TestCollectFS:
-    def test_ignored_certain_directories(self, testdir):
-        tmpdir = testdir.tmpdir
-        tmpdir.ensure("_darcs", 'test_notfound.py')
-        tmpdir.ensure("CVS", 'test_notfound.py')
-        tmpdir.ensure("{arch}", 'test_notfound.py')
-        tmpdir.ensure(".whatever", 'test_notfound.py')
-        tmpdir.ensure(".bzr", 'test_notfound.py')
-        tmpdir.ensure("normal", 'test_found.py')
-        for x in tmpdir.visit("test_*.py"):
-            x.write("def test_hello(): pass")
-
-        result = testdir.runpytest("--collect-only")
-        s = result.stdout.str()
-        assert "test_notfound" not in s
-        assert "test_found" in s
-
-    def test_custom_norecursedirs(self, testdir):
-        testdir.makeini("""
-            [pytest]
-            norecursedirs = mydir xyz*
-        """)
-        tmpdir = testdir.tmpdir
-        tmpdir.ensure("mydir", "test_hello.py").write("def test_1(): pass")
-        tmpdir.ensure("xyz123", "test_2.py").write("def test_2(): 0/0")
-        tmpdir.ensure("xy", "test_ok.py").write("def test_3(): pass")
-        rec = testdir.inline_run()
-        rec.assertoutcome(passed=1)
-        rec = testdir.inline_run("xyz123/test_2.py")
-        rec.assertoutcome(failed=1)
-
-    def test_testpaths_ini(self, testdir, monkeypatch):
-        testdir.makeini("""
-            [pytest]
-            testpaths = gui uts
-        """)
-        tmpdir = testdir.tmpdir
-        tmpdir.ensure("env", "test_1.py").write("def test_env(): pass")
-        tmpdir.ensure("gui", "test_2.py").write("def test_gui(): pass")
-        tmpdir.ensure("uts", "test_3.py").write("def test_uts(): pass")
-
-        # executing from rootdir only tests from `testpaths` directories
-        # are collected
-        items, reprec = testdir.inline_genitems('-v')
-        assert [x.name for x in items] == ['test_gui', 'test_uts']
-
-        # check that explicitly passing directories in the command-line
-        # collects the tests
-        for dirname in ('env', 'gui', 'uts'):
-            items, reprec = testdir.inline_genitems(tmpdir.join(dirname))
-            assert [x.name for x in items] == ['test_%s' % dirname]
-
-        # changing cwd to each subdirectory and running pytest without
-        # arguments collects the tests in that directory normally
-        for dirname in ('env', 'gui', 'uts'):
-            monkeypatch.chdir(testdir.tmpdir.join(dirname))
-            items, reprec = testdir.inline_genitems()
-            assert [x.name for x in items] == ['test_%s' % dirname]
-
-
-class TestCollectPluginHookRelay:
-    def test_pytest_collect_file(self, testdir):
-        wascalled = []
-        class Plugin:
-            def pytest_collect_file(self, path, parent):
-                wascalled.append(path)
-        testdir.makefile(".abc", "xyz")
-        pytest.main([testdir.tmpdir], plugins=[Plugin()])
-        assert len(wascalled) == 1
-        assert wascalled[0].ext == '.abc'
-
-    def test_pytest_collect_directory(self, testdir):
-        wascalled = []
-        class Plugin:
-            def pytest_collect_directory(self, path, parent):
-                wascalled.append(path.basename)
-        testdir.mkdir("hello")
-        testdir.mkdir("world")
-        pytest.main(testdir.tmpdir, plugins=[Plugin()])
-        assert "hello" in wascalled
-        assert "world" in wascalled
-
-class TestPrunetraceback:
-    def test_collection_error(self, testdir):
-        p = testdir.makepyfile("""
-            import not_exists
-        """)
-        result = testdir.runpytest(p)
-        assert "__import__" not in result.stdout.str(), "too long traceback"
-        result.stdout.fnmatch_lines([
-            "*ERROR collecting*",
-            "*mport*not_exists*"
-        ])
-
-    def test_custom_repr_failure(self, testdir):
-        p = testdir.makepyfile("""
-            import not_exists
-        """)
-        testdir.makeconftest("""
-            import pytest
-            def pytest_collect_file(path, parent):
-                return MyFile(path, parent)
-            class MyError(Exception):
-                pass
-            class MyFile(pytest.File):
-                def collect(self):
-                    raise MyError()
-                def repr_failure(self, excinfo):
-                    if excinfo.errisinstance(MyError):
-                        return "hello world"
-                    return pytest.File.repr_failure(self, excinfo)
-        """)
-
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*ERROR collecting*",
-            "*hello world*",
-        ])
-
-    @pytest.mark.xfail(reason="other mechanism for adding to reporting needed")
-    def test_collect_report_postprocessing(self, testdir):
-        p = testdir.makepyfile("""
-            import not_exists
-        """)
-        testdir.makeconftest("""
-            import pytest
-            def pytest_make_collect_report(__multicall__):
-                rep = __multicall__.execute()
-                rep.headerlines += ["header1"]
-                return rep
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*ERROR collecting*",
-            "*header1*",
-        ])
-
-
-class TestCustomConftests:
-    def test_ignore_collect_path(self, testdir):
-        testdir.makeconftest("""
-            def pytest_ignore_collect(path, config):
-                return path.basename.startswith("x") or \
-                       path.basename == "test_one.py"
-        """)
-        sub = testdir.mkdir("xy123")
-        sub.ensure("test_hello.py").write("syntax error")
-        sub.join("conftest.py").write("syntax error")
-        testdir.makepyfile("def test_hello(): pass")
-        testdir.makepyfile(test_one="syntax error")
-        result = testdir.runpytest("--fulltrace")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_ignore_collect_not_called_on_argument(self, testdir):
-        testdir.makeconftest("""
-            def pytest_ignore_collect(path, config):
-                return True
-        """)
-        p = testdir.makepyfile("def test_hello(): pass")
-        result = testdir.runpytest(p)
-        assert result.ret == 0
-        result.stdout.fnmatch_lines("*1 passed*")
-        result = testdir.runpytest()
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        result.stdout.fnmatch_lines("*collected 0 items*")
-
-    def test_collectignore_exclude_on_option(self, testdir):
-        testdir.makeconftest("""
-            collect_ignore = ['hello', 'test_world.py']
-            def pytest_addoption(parser):
-                parser.addoption("--XX", action="store_true", default=False)
-            def pytest_configure(config):
-                if config.getvalue("XX"):
-                    collect_ignore[:] = []
-        """)
-        testdir.mkdir("hello")
-        testdir.makepyfile(test_world="def test_hello(): pass")
-        result = testdir.runpytest()
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        assert "passed" not in result.stdout.str()
-        result = testdir.runpytest("--XX")
-        assert result.ret == 0
-        assert "passed" in result.stdout.str()
-
-    def test_pytest_fs_collect_hooks_are_seen(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            class MyModule(pytest.Module):
-                pass
-            def pytest_collect_file(path, parent):
-                if path.ext == ".py":
-                    return MyModule(path, parent)
-        """)
-        testdir.mkdir("sub")
-        testdir.makepyfile("def test_x(): pass")
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*MyModule*",
-            "*test_x*"
-        ])
-
-    def test_pytest_collect_file_from_sister_dir(self, testdir):
-        sub1 = testdir.mkpydir("sub1")
-        sub2 = testdir.mkpydir("sub2")
-        conf1 = testdir.makeconftest("""
-            import pytest
-            class MyModule1(pytest.Module):
-                pass
-            def pytest_collect_file(path, parent):
-                if path.ext == ".py":
-                    return MyModule1(path, parent)
-        """)
-        conf1.move(sub1.join(conf1.basename))
-        conf2 = testdir.makeconftest("""
-            import pytest
-            class MyModule2(pytest.Module):
-                pass
-            def pytest_collect_file(path, parent):
-                if path.ext == ".py":
-                    return MyModule2(path, parent)
-        """)
-        conf2.move(sub2.join(conf2.basename))
-        p = testdir.makepyfile("def test_x(): pass")
-        p.copy(sub1.join(p.basename))
-        p.copy(sub2.join(p.basename))
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*MyModule1*",
-            "*MyModule2*",
-            "*test_x*"
-        ])
-
-class TestSession:
-    def test_parsearg(self, testdir):
-        p = testdir.makepyfile("def test_func(): pass")
-        subdir = testdir.mkdir("sub")
-        subdir.ensure("__init__.py")
-        target = subdir.join(p.basename)
-        p.move(target)
-        subdir.chdir()
-        config = testdir.parseconfig(p.basename)
-        rcol = Session(config=config)
-        assert rcol.fspath == subdir
-        parts = rcol._parsearg(p.basename)
-
-        assert parts[0] ==  target
-        assert len(parts) == 1
-        parts = rcol._parsearg(p.basename + "::test_func")
-        assert parts[0] ==  target
-        assert parts[1] ==  "test_func"
-        assert len(parts) == 2
-
-    def test_collect_topdir(self, testdir):
-        p = testdir.makepyfile("def test_func(): pass")
-        id = "::".join([p.basename, "test_func"])
-        # XXX migrate to collectonly? (see below)
-        config = testdir.parseconfig(id)
-        topdir = testdir.tmpdir
-        rcol = Session(config)
-        assert topdir == rcol.fspath
-        #rootid = rcol.nodeid
-        #root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0]
-        #assert root2 == rcol, rootid
-        colitems = rcol.perform_collect([rcol.nodeid], genitems=False)
-        assert len(colitems) == 1
-        assert colitems[0].fspath == p
-
-
-    def test_collect_protocol_single_function(self, testdir):
-        p = testdir.makepyfile("def test_func(): pass")
-        id = "::".join([p.basename, "test_func"])
-        items, hookrec = testdir.inline_genitems(id)
-        item, = items
-        assert item.name == "test_func"
-        newid = item.nodeid
-        assert newid == id
-        py.std.pprint.pprint(hookrec.calls)
-        topdir = testdir.tmpdir  # noqa
-        hookrec.assert_contains([
-            ("pytest_collectstart", "collector.fspath == topdir"),
-            ("pytest_make_collect_report", "collector.fspath == topdir"),
-            ("pytest_collectstart", "collector.fspath == p"),
-            ("pytest_make_collect_report", "collector.fspath == p"),
-            ("pytest_pycollect_makeitem", "name == 'test_func'"),
-            ("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
-            ("pytest_collectreport", "report.nodeid == ''")
-        ])
-
-    def test_collect_protocol_method(self, testdir):
-        p = testdir.makepyfile("""
-            class TestClass:
-                def test_method(self):
-                    pass
-        """)
-        normid = p.basename + "::TestClass::()::test_method"
-        for id in [p.basename,
-                   p.basename + "::TestClass",
-                   p.basename + "::TestClass::()",
-                   normid,
-                   ]:
-            items, hookrec = testdir.inline_genitems(id)
-            assert len(items) == 1
-            assert items[0].name == "test_method"
-            newid = items[0].nodeid
-            assert newid == normid
-
-    def test_collect_custom_nodes_multi_id(self, testdir):
-        p = testdir.makepyfile("def test_func(): pass")
-        testdir.makeconftest("""
-            import pytest
-            class SpecialItem(pytest.Item):
-                def runtest(self):
-                    return # ok
-            class SpecialFile(pytest.File):
-                def collect(self):
-                    return [SpecialItem(name="check", parent=self)]
-            def pytest_collect_file(path, parent):
-                if path.basename == %r:
-                    return SpecialFile(fspath=path, parent=parent)
-        """ % p.basename)
-        id = p.basename
-
-        items, hookrec = testdir.inline_genitems(id)
-        py.std.pprint.pprint(hookrec.calls)
-        assert len(items) == 2
-        hookrec.assert_contains([
-            ("pytest_collectstart",
-                "collector.fspath == collector.session.fspath"),
-            ("pytest_collectstart",
-                "collector.__class__.__name__ == 'SpecialFile'"),
-            ("pytest_collectstart",
-                "collector.__class__.__name__ == 'Module'"),
-            ("pytest_pycollect_makeitem", "name == 'test_func'"),
-            ("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
-            #("pytest_collectreport",
-            #    "report.fspath == %r" % str(rcol.fspath)),
-        ])
-
-    def test_collect_subdir_event_ordering(self, testdir):
-        p = testdir.makepyfile("def test_func(): pass")
-        aaa = testdir.mkpydir("aaa")
-        test_aaa = aaa.join("test_aaa.py")
-        p.move(test_aaa)
-
-        items, hookrec = testdir.inline_genitems()
-        assert len(items) == 1
-        py.std.pprint.pprint(hookrec.calls)
-        hookrec.assert_contains([
-            ("pytest_collectstart", "collector.fspath == test_aaa"),
-            ("pytest_pycollect_makeitem", "name == 'test_func'"),
-            ("pytest_collectreport",
-                    "report.nodeid.startswith('aaa/test_aaa.py')"),
-        ])
-
-    def test_collect_two_commandline_args(self, testdir):
-        p = testdir.makepyfile("def test_func(): pass")
-        aaa = testdir.mkpydir("aaa")
-        bbb = testdir.mkpydir("bbb")
-        test_aaa = aaa.join("test_aaa.py")
-        p.copy(test_aaa)
-        test_bbb = bbb.join("test_bbb.py")
-        p.move(test_bbb)
-
-        id = "."
-
-        items, hookrec = testdir.inline_genitems(id)
-        assert len(items) == 2
-        py.std.pprint.pprint(hookrec.calls)
-        hookrec.assert_contains([
-            ("pytest_collectstart", "collector.fspath == test_aaa"),
-            ("pytest_pycollect_makeitem", "name == 'test_func'"),
-            ("pytest_collectreport", "report.nodeid == 'aaa/test_aaa.py'"),
-            ("pytest_collectstart", "collector.fspath == test_bbb"),
-            ("pytest_pycollect_makeitem", "name == 'test_func'"),
-            ("pytest_collectreport", "report.nodeid == 'bbb/test_bbb.py'"),
-        ])
-
-    def test_serialization_byid(self, testdir):
-        testdir.makepyfile("def test_func(): pass")
-        items, hookrec = testdir.inline_genitems()
-        assert len(items) == 1
-        item, = items
-        items2, hookrec = testdir.inline_genitems(item.nodeid)
-        item2, = items2
-        assert item2.name == item.name
-        assert item2.fspath == item.fspath
-
-    def test_find_byid_without_instance_parents(self, testdir):
-        p = testdir.makepyfile("""
-            class TestClass:
-                def test_method(self):
-                    pass
-        """)
-        arg = p.basename + ("::TestClass::test_method")
-        items, hookrec = testdir.inline_genitems(arg)
-        assert len(items) == 1
-        item, = items
-        assert item.nodeid.endswith("TestClass::()::test_method")
-
-class Test_getinitialnodes:
-    def test_global_file(self, testdir, tmpdir):
-        x = tmpdir.ensure("x.py")
-        config = testdir.parseconfigure(x)
-        col = testdir.getnode(config, x)
-        assert isinstance(col, pytest.Module)
-        assert col.name == 'x.py'
-        assert col.parent.name == testdir.tmpdir.basename
-        assert col.parent.parent is None
-        for col in col.listchain():
-            assert col.config is config
-
-    def test_pkgfile(self, testdir):
-        tmpdir = testdir.tmpdir
-        subdir = tmpdir.join("subdir")
-        x = subdir.ensure("x.py")
-        subdir.ensure("__init__.py")
-        config = testdir.parseconfigure(x)
-        col = testdir.getnode(config, x)
-        assert isinstance(col, pytest.Module)
-        assert col.name == 'x.py'
-        assert col.parent.parent is None
-        for col in col.listchain():
-            assert col.config is config
-
-class Test_genitems:
-    def test_check_collect_hashes(self, testdir):
-        p = testdir.makepyfile("""
-            def test_1():
-                pass
-
-            def test_2():
-                pass
-        """)
-        p.copy(p.dirpath(p.purebasename + "2" + ".py"))
-        items, reprec = testdir.inline_genitems(p.dirpath())
-        assert len(items) == 4
-        for numi, i in enumerate(items):
-            for numj, j in enumerate(items):
-                if numj != numi:
-                    assert hash(i) != hash(j)
-                    assert i != j
-
-    def test_example_items1(self, testdir):
-        p = testdir.makepyfile('''
-            def testone():
-                pass
-
-            class TestX:
-                def testmethod_one(self):
-                    pass
-
-            class TestY(TestX):
-                pass
-        ''')
-        items, reprec = testdir.inline_genitems(p)
-        assert len(items) == 3
-        assert items[0].name == 'testone'
-        assert items[1].name == 'testmethod_one'
-        assert items[2].name == 'testmethod_one'
-
-        # let's also test getmodpath here
-        assert items[0].getmodpath() == "testone"
-        assert items[1].getmodpath() == "TestX.testmethod_one"
-        assert items[2].getmodpath() == "TestY.testmethod_one"
-
-        s = items[0].getmodpath(stopatmodule=False)
-        assert s.endswith("test_example_items1.testone")
-        print(s)
-
-    def test_class_and_functions_discovery_using_glob(self, testdir):
-        """
-        tests that python_classes and python_functions config options work
-        as prefixes and glob-like patterns (issue #600).
-        """
-        testdir.makeini("""
-            [pytest]
-            python_classes = *Suite Test
-            python_functions = *_test test
-        """)
-        p = testdir.makepyfile('''
-            class MyTestSuite:
-                def x_test(self):
-                    pass
-
-            class TestCase:
-                def test_y(self):
-                    pass
-        ''')
-        items, reprec = testdir.inline_genitems(p)
-        ids = [x.getmodpath() for x in items]
-        assert ids == ['MyTestSuite.x_test', 'TestCase.test_y']
-
-
-def test_matchnodes_two_collections_same_file(testdir):
-    testdir.makeconftest("""
-        import pytest
-        def pytest_configure(config):
-            config.pluginmanager.register(Plugin2())
-
-        class Plugin2:
-            def pytest_collect_file(self, path, parent):
-                if path.ext == ".abc":
-                    return MyFile2(path, parent)
-
-        def pytest_collect_file(path, parent):
-            if path.ext == ".abc":
-                return MyFile1(path, parent)
-
-        class MyFile1(pytest.Item, pytest.File):
-            def runtest(self):
-                pass
-        class MyFile2(pytest.File):
-            def collect(self):
-                return [Item2("hello", parent=self)]
-
-        class Item2(pytest.Item):
-            def runtest(self):
-                pass
-    """)
-    p = testdir.makefile(".abc", "")
-    result = testdir.runpytest()
-    assert result.ret == 0
-    result.stdout.fnmatch_lines([
-        "*2 passed*",
-    ])
-    res = testdir.runpytest("%s::hello" % p.basename)
-    res.stdout.fnmatch_lines([
-        "*1 passed*",
-    ])
-
-
-class TestNodekeywords:
-    def test_no_under(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def test_pass(): pass
-            def test_fail(): assert 0
-        """)
-        l = list(modcol.keywords)
-        assert modcol.name in l
-        for x in l:
-            assert not x.startswith("_")
-        assert modcol.name in repr(modcol.keywords)
-
-    def test_issue345(self, testdir):
-        testdir.makepyfile("""
-            def test_should_not_be_selected():
-                assert False, 'I should not have been selected to run'
-
-            def test___repr__():
-                pass
-        """)
-        reprec = testdir.inline_run("-k repr")
-        reprec.assertoutcome(passed=1, failed=0)
diff --git a/tools/pytest/testing/test_config.py b/tools/pytest/testing/test_config.py
deleted file mode 100644
index 92c9bdb..0000000
--- a/tools/pytest/testing/test_config.py
+++ /dev/null
@@ -1,570 +0,0 @@
-import py, pytest
-
-import _pytest._code
-from _pytest.config import getcfg, get_common_ancestor, determine_setup
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-
-class TestParseIni:
-    def test_getcfg_and_config(self, testdir, tmpdir):
-        sub = tmpdir.mkdir("sub")
-        sub.chdir()
-        tmpdir.join("setup.cfg").write(_pytest._code.Source("""
-            [pytest]
-            name = value
-        """))
-        rootdir, inifile, cfg = getcfg([sub], ["setup.cfg"])
-        assert cfg['name'] == "value"
-        config = testdir.parseconfigure(sub)
-        assert config.inicfg['name'] == 'value'
-
-    def test_getcfg_empty_path(self, tmpdir):
-        getcfg([''], ['setup.cfg']) #happens on py.test ""
-
-    def test_append_parse_args(self, testdir, tmpdir, monkeypatch):
-        monkeypatch.setenv('PYTEST_ADDOPTS', '--color no -rs --tb="short"')
-        tmpdir.join("setup.cfg").write(_pytest._code.Source("""
-            [pytest]
-            addopts = --verbose
-        """))
-        config = testdir.parseconfig(tmpdir)
-        assert config.option.color == 'no'
-        assert config.option.reportchars == 's'
-        assert config.option.tbstyle == 'short'
-        assert config.option.verbose
-        #config = testdir.Config()
-        #args = [tmpdir,]
-        #config._preparse(args, addopts=False)
-        #assert len(args) == 1
-
-    def test_tox_ini_wrong_version(self, testdir):
-        testdir.makefile('.ini', tox="""
-            [pytest]
-            minversion=9.0
-        """)
-        result = testdir.runpytest()
-        assert result.ret != 0
-        result.stderr.fnmatch_lines([
-            "*tox.ini:2*requires*9.0*actual*"
-        ])
-
-    @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
-    def test_ini_names(self, testdir, name):
-        testdir.tmpdir.join(name).write(py.std.textwrap.dedent("""
-            [pytest]
-            minversion = 1.0
-        """))
-        config = testdir.parseconfig()
-        assert config.getini("minversion") == "1.0"
-
-    def test_toxini_before_lower_pytestini(self, testdir):
-        sub = testdir.tmpdir.mkdir("sub")
-        sub.join("tox.ini").write(py.std.textwrap.dedent("""
-            [pytest]
-            minversion = 2.0
-        """))
-        testdir.tmpdir.join("pytest.ini").write(py.std.textwrap.dedent("""
-            [pytest]
-            minversion = 1.5
-        """))
-        config = testdir.parseconfigure(sub)
-        assert config.getini("minversion") == "2.0"
-
-    @pytest.mark.xfail(reason="probably not needed")
-    def test_confcutdir(self, testdir):
-        sub = testdir.mkdir("sub")
-        sub.chdir()
-        testdir.makeini("""
-            [pytest]
-            addopts = --qwe
-        """)
-        result = testdir.inline_run("--confcutdir=.")
-        assert result.ret == 0
-
-class TestConfigCmdlineParsing:
-    def test_parsing_again_fails(self, testdir):
-        config = testdir.parseconfig()
-        pytest.raises(AssertionError, lambda: config.parse([]))
-
-    def test_explicitly_specified_config_file_is_loaded(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("custom", "")
-        """)
-        testdir.makeini("""
-            [pytest]
-            custom = 0
-        """)
-        testdir.makefile(".cfg", custom = """
-            [pytest]
-            custom = 1
-        """)
-        config = testdir.parseconfig("-c", "custom.cfg")
-        assert config.getini("custom") == "1"
-
-class TestConfigAPI:
-    def test_config_trace(self, testdir):
-        config = testdir.parseconfig()
-        l = []
-        config.trace.root.setwriter(l.append)
-        config.trace("hello")
-        assert len(l) == 1
-        assert l[0] == "hello [config]\n"
-
-    def test_config_getoption(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addoption("--hello", "-X", dest="hello")
-        """)
-        config = testdir.parseconfig("--hello=this")
-        for x in ("hello", "--hello", "-X"):
-            assert config.getoption(x) == "this"
-        pytest.raises(ValueError, "config.getoption('qweqwe')")
-
-    @pytest.mark.skipif('sys.version_info[:2] not in [(2, 6), (2, 7)]')
-    def test_config_getoption_unicode(self, testdir):
-        testdir.makeconftest("""
-            from __future__ import unicode_literals
-
-            def pytest_addoption(parser):
-                parser.addoption('--hello', type='string')
-        """)
-        config = testdir.parseconfig('--hello=this')
-        assert config.getoption('hello') == 'this'
-
-    def test_config_getvalueorskip(self, testdir):
-        config = testdir.parseconfig()
-        pytest.raises(pytest.skip.Exception,
-            "config.getvalueorskip('hello')")
-        verbose = config.getvalueorskip("verbose")
-        assert verbose == config.option.verbose
-
-    def test_config_getvalueorskip_None(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addoption("--hello")
-        """)
-        config = testdir.parseconfig()
-        with pytest.raises(pytest.skip.Exception):
-            config.getvalueorskip('hello')
-
-    def test_getoption(self, testdir):
-        config = testdir.parseconfig()
-        with pytest.raises(ValueError):
-            config.getvalue('x')
-        assert config.getoption("x", 1) == 1
-
-    def test_getconftest_pathlist(self, testdir, tmpdir):
-        somepath = tmpdir.join("x", "y", "z")
-        p = tmpdir.join("conftest.py")
-        p.write("pathlist = ['.', %r]" % str(somepath))
-        config = testdir.parseconfigure(p)
-        assert config._getconftest_pathlist('notexist', path=tmpdir) is None
-        pl = config._getconftest_pathlist('pathlist', path=tmpdir)
-        print(pl)
-        assert len(pl) == 2
-        assert pl[0] == tmpdir
-        assert pl[1] == somepath
-
-    def test_addini(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("myname", "my new ini value")
-        """)
-        testdir.makeini("""
-            [pytest]
-            myname=hello
-        """)
-        config = testdir.parseconfig()
-        val = config.getini("myname")
-        assert val == "hello"
-        pytest.raises(ValueError, config.getini, 'other')
-
-    def test_addini_pathlist(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("paths", "my new ini value", type="pathlist")
-                parser.addini("abc", "abc value")
-        """)
-        p = testdir.makeini("""
-            [pytest]
-            paths=hello world/sub.py
-        """)
-        config = testdir.parseconfig()
-        l = config.getini("paths")
-        assert len(l) == 2
-        assert l[0] == p.dirpath('hello')
-        assert l[1] == p.dirpath('world/sub.py')
-        pytest.raises(ValueError, config.getini, 'other')
-
-    def test_addini_args(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("args", "new args", type="args")
-                parser.addini("a2", "", "args", default="1 2 3".split())
-        """)
-        testdir.makeini("""
-            [pytest]
-            args=123 "123 hello" "this"
-        """)
-        config = testdir.parseconfig()
-        l = config.getini("args")
-        assert len(l) == 3
-        assert l == ["123", "123 hello", "this"]
-        l = config.getini("a2")
-        assert l == list("123")
-
-    def test_addini_linelist(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("xy", "", type="linelist")
-                parser.addini("a2", "", "linelist")
-        """)
-        testdir.makeini("""
-            [pytest]
-            xy= 123 345
-                second line
-        """)
-        config = testdir.parseconfig()
-        l = config.getini("xy")
-        assert len(l) == 2
-        assert l == ["123 345", "second line"]
-        l = config.getini("a2")
-        assert l == []
-
-    @pytest.mark.parametrize('str_val, bool_val',
-                             [('True', True), ('no', False), ('no-ini', True)])
-    def test_addini_bool(self, testdir, str_val, bool_val):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("strip", "", type="bool", default=True)
-        """)
-        if str_val != 'no-ini':
-            testdir.makeini("""
-                [pytest]
-                strip=%s
-            """ % str_val)
-        config = testdir.parseconfig()
-        assert config.getini("strip") is bool_val
-
-    def test_addinivalue_line_existing(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("xy", "", type="linelist")
-        """)
-        testdir.makeini("""
-            [pytest]
-            xy= 123
-        """)
-        config = testdir.parseconfig()
-        l = config.getini("xy")
-        assert len(l) == 1
-        assert l == ["123"]
-        config.addinivalue_line("xy", "456")
-        l = config.getini("xy")
-        assert len(l) == 2
-        assert l == ["123", "456"]
-
-    def test_addinivalue_line_new(self, testdir):
-        testdir.makeconftest("""
-            def pytest_addoption(parser):
-                parser.addini("xy", "", type="linelist")
-        """)
-        config = testdir.parseconfig()
-        assert not config.getini("xy")
-        config.addinivalue_line("xy", "456")
-        l = config.getini("xy")
-        assert len(l) == 1
-        assert l == ["456"]
-        config.addinivalue_line("xy", "123")
-        l = config.getini("xy")
-        assert len(l) == 2
-        assert l == ["456", "123"]
-
-
-class TestConfigFromdictargs:
-    def test_basic_behavior(self):
-        from _pytest.config import Config
-        option_dict = {
-            'verbose': 444,
-            'foo': 'bar',
-            'capture': 'no',
-        }
-        args = ['a', 'b']
-
-        config = Config.fromdictargs(option_dict, args)
-        with pytest.raises(AssertionError):
-            config.parse(['should refuse to parse again'])
-        assert config.option.verbose == 444
-        assert config.option.foo == 'bar'
-        assert config.option.capture == 'no'
-        assert config.args == args
-
-    def test_origargs(self):
-        """Show that fromdictargs can handle args in their "orig" format"""
-        from _pytest.config import Config
-        option_dict = {}
-        args = ['-vvvv', '-s', 'a', 'b']
-
-        config = Config.fromdictargs(option_dict, args)
-        assert config.args == ['a', 'b']
-        assert config._origargs == args
-        assert config.option.verbose == 4
-        assert config.option.capture == 'no'
-
-    def test_inifilename(self, tmpdir):
-        tmpdir.join("foo/bar.ini").ensure().write(_pytest._code.Source("""
-            [pytest]
-            name = value
-        """))
-
-        from _pytest.config import Config
-        inifile = '../../foo/bar.ini'
-        option_dict = {
-            'inifilename': inifile,
-            'capture': 'no',
-        }
-
-        cwd = tmpdir.join('a/b')
-        cwd.join('pytest.ini').ensure().write(_pytest._code.Source("""
-            [pytest]
-            name = wrong-value
-            should_not_be_set = true
-        """))
-        with cwd.ensure(dir=True).as_cwd():
-            config = Config.fromdictargs(option_dict, ())
-
-        assert config.args == [str(cwd)]
-        assert config.option.inifilename == inifile
-        assert config.option.capture == 'no'
-
-        # this indicates this is the file used for getting configuration values
-        assert config.inifile == inifile
-        assert config.inicfg.get('name') == 'value'
-        assert config.inicfg.get('should_not_be_set') is None
-
-
-def test_options_on_small_file_do_not_blow_up(testdir):
-    def runfiletest(opts):
-        reprec = testdir.inline_run(*opts)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert failed == 2
-        assert skipped == passed == 0
-    path = testdir.makepyfile("""
-        def test_f1(): assert 0
-        def test_f2(): assert 0
-    """)
-
-    for opts in ([], ['-l'], ['-s'], ['--tb=no'], ['--tb=short'],
-                 ['--tb=long'], ['--fulltrace'], ['--nomagic'],
-                 ['--traceconfig'], ['-v'], ['-v', '-v']):
-        runfiletest(opts + [path])
-
-def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
-    pkg_resources = pytest.importorskip("pkg_resources")
-    def my_iter(name):
-        assert name == "pytest11"
-        class EntryPoint:
-            name = "mytestplugin"
-            class dist:
-                pass
-            def load(self):
-                class PseudoPlugin:
-                    x = 42
-                return PseudoPlugin()
-        return iter([EntryPoint()])
-    monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
-    testdir.makeconftest("""
-        pytest_plugins = "mytestplugin",
-    """)
-    monkeypatch.setenv("PYTEST_PLUGINS", "mytestplugin")
-    config = testdir.parseconfig()
-    plugin = config.pluginmanager.getplugin("mytestplugin")
-    assert plugin.x == 42
-
-def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch):
-    pkg_resources = pytest.importorskip("pkg_resources")
-    def my_iter(name):
-        assert name == "pytest11"
-        class EntryPoint:
-            name = "mytestplugin"
-            def load(self):
-                assert 0, "should not arrive here"
-        return iter([EntryPoint()])
-    monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
-    config = testdir.parseconfig("-p", "no:mytestplugin")
-    plugin = config.pluginmanager.getplugin("mytestplugin")
-    assert plugin is None
-
-def test_cmdline_processargs_simple(testdir):
-    testdir.makeconftest("""
-        def pytest_cmdline_preparse(args):
-            args.append("-h")
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*pytest*",
-        "*-h*",
-    ])
-
-def test_invalid_options_show_extra_information(testdir):
-    """display extra information when pytest exits due to unrecognized
-    options in the command-line"""
-    testdir.makeini("""
-        [pytest]
-        addopts = --invalid-option
-    """)
-    result = testdir.runpytest()
-    result.stderr.fnmatch_lines([
-        "*error: unrecognized arguments: --invalid-option*",
-        "*  inifile: %s*" % testdir.tmpdir.join('tox.ini'),
-        "*  rootdir: %s*" % testdir.tmpdir,
-    ])
-
-
-@pytest.mark.parametrize('args', [
-    ['dir1', 'dir2', '-v'],
-    ['dir1', '-v', 'dir2'],
-    ['dir2', '-v', 'dir1'],
-    ['-v', 'dir2', 'dir1'],
-])
-def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args):
-    """
-    Consider all arguments in the command-line for rootdir and inifile
-    discovery, even if they happen to occur after an option. #949
-    """
-    # replace "dir1" and "dir2" from "args" into their real directory
-    root = testdir.tmpdir.mkdir('myroot')
-    d1 = root.mkdir('dir1')
-    d2 = root.mkdir('dir2')
-    for i, arg in enumerate(args):
-        if arg == 'dir1':
-            args[i] = d1
-        elif arg == 'dir2':
-            args[i] = d2
-    result = testdir.runpytest(*args)
-    result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile: '])
-
-
-@pytest.mark.skipif("sys.platform == 'win32'")
-def test_toolongargs_issue224(testdir):
-    result = testdir.runpytest("-m", "hello" * 500)
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-
-def test_notify_exception(testdir, capfd):
-    config = testdir.parseconfig()
-    excinfo = pytest.raises(ValueError, "raise ValueError(1)")
-    config.notify_exception(excinfo)
-    out, err = capfd.readouterr()
-    assert "ValueError" in err
-    class A:
-        def pytest_internalerror(self, excrepr):
-            return True
-    config.pluginmanager.register(A())
-    config.notify_exception(excinfo)
-    out, err = capfd.readouterr()
-    assert not err
-
-
-def test_load_initial_conftest_last_ordering(testdir):
-    from _pytest.config  import get_config
-    pm = get_config().pluginmanager
-    class My:
-        def pytest_load_initial_conftests(self):
-            pass
-    m = My()
-    pm.register(m)
-    hc = pm.hook.pytest_load_initial_conftests
-    l = hc._nonwrappers + hc._wrappers
-    assert l[-1].function.__module__ == "_pytest.capture"
-    assert l[-2].function == m.pytest_load_initial_conftests
-    assert l[-3].function.__module__ == "_pytest.config"
-
-class TestWarning:
-    def test_warn_config(self, testdir):
-        testdir.makeconftest("""
-            l = []
-            def pytest_configure(config):
-                config.warn("C1", "hello")
-            def pytest_logwarning(code, message):
-                if message == "hello" and code == "C1":
-                    l.append(1)
-        """)
-        testdir.makepyfile("""
-            def test_proper(pytestconfig):
-                import conftest
-                assert conftest.l == [1]
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_warn_on_test_item_from_request(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-
-            @pytest.fixture
-            def fix(request):
-                request.node.warn("T1", "hello")
-            def test_hello(fix):
-                pass
-        """)
-        result = testdir.runpytest()
-        assert result.parseoutcomes()["pytest-warnings"] > 0
-        assert "hello" not in result.stdout.str()
-
-        result = testdir.runpytest("-rw")
-        result.stdout.fnmatch_lines("""
-            ===*pytest-warning summary*===
-            *WT1*test_warn_on_test_item*:5*hello*
-        """)
-
-class TestRootdir:
-    def test_simple_noini(self, tmpdir):
-        assert get_common_ancestor([tmpdir]) == tmpdir
-        assert get_common_ancestor([tmpdir.mkdir("a"), tmpdir]) == tmpdir
-        assert get_common_ancestor([tmpdir, tmpdir.join("a")]) == tmpdir
-        with tmpdir.as_cwd():
-            assert get_common_ancestor([]) == tmpdir
-
-    @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
-    def test_with_ini(self, tmpdir, name):
-        inifile = tmpdir.join(name)
-        inifile.write("[pytest]\n")
-
-        a = tmpdir.mkdir("a")
-        b = a.mkdir("b")
-        for args in ([tmpdir], [a], [b]):
-            rootdir, inifile, inicfg = determine_setup(None, args)
-            assert rootdir == tmpdir
-            assert inifile == inifile
-        rootdir, inifile, inicfg = determine_setup(None, [b,a])
-        assert rootdir == tmpdir
-        assert inifile == inifile
-
-    @pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
-    def test_pytestini_overides_empty_other(self, tmpdir, name):
-        inifile = tmpdir.ensure("pytest.ini")
-        a = tmpdir.mkdir("a")
-        a.ensure(name)
-        rootdir, inifile, inicfg = determine_setup(None, [a])
-        assert rootdir == tmpdir
-        assert inifile == inifile
-
-    def test_setuppy_fallback(self, tmpdir):
-        a = tmpdir.mkdir("a")
-        a.ensure("setup.cfg")
-        tmpdir.ensure("setup.py")
-        rootdir, inifile, inicfg = determine_setup(None, [a])
-        assert rootdir == tmpdir
-        assert inifile is None
-        assert inicfg == {}
-
-    def test_nothing(self, tmpdir):
-        rootdir, inifile, inicfg = determine_setup(None, [tmpdir])
-        assert rootdir == tmpdir
-        assert inifile is None
-        assert inicfg == {}
-
-    def test_with_specific_inifile(self, tmpdir):
-        inifile = tmpdir.ensure("pytest.ini")
-        rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir])
-        assert rootdir == tmpdir
diff --git a/tools/pytest/testing/test_conftest.py b/tools/pytest/testing/test_conftest.py
deleted file mode 100644
index 6f5e77f..0000000
--- a/tools/pytest/testing/test_conftest.py
+++ /dev/null
@@ -1,409 +0,0 @@
-from textwrap import dedent
-
-import _pytest._code
-import py
-import pytest
-from _pytest.config import PytestPluginManager
-from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
-
-
-@pytest.fixture(scope="module", params=["global", "inpackage"])
-def basedir(request, tmpdir_factory):
-    from _pytest.tmpdir import tmpdir
-    tmpdir = tmpdir(request, tmpdir_factory)
-    tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
-    tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
-    if request.param == "inpackage":
-        tmpdir.ensure("adir/__init__.py")
-        tmpdir.ensure("adir/b/__init__.py")
-    return tmpdir
-
-def ConftestWithSetinitial(path):
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, [path])
-    return conftest
-
-def conftest_setinitial(conftest, args, confcutdir=None):
-    class Namespace:
-        def __init__(self):
-            self.file_or_dir = args
-            self.confcutdir = str(confcutdir)
-            self.noconftest = False
-    conftest._set_initial_conftests(Namespace())
-
-class TestConftestValueAccessGlobal:
-    def test_basic_init(self, basedir):
-        conftest = PytestPluginManager()
-        p = basedir.join("adir")
-        assert conftest._rget_with_confmod("a", p)[1] == 1
-
-    def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
-        conftest = PytestPluginManager()
-        len(conftest._path2confmods)
-        conftest._getconftestmodules(basedir)
-        snap1 = len(conftest._path2confmods)
-        #assert len(conftest._path2confmods) == snap1 + 1
-        conftest._getconftestmodules(basedir.join('adir'))
-        assert len(conftest._path2confmods) == snap1 + 1
-        conftest._getconftestmodules(basedir.join('b'))
-        assert len(conftest._path2confmods) == snap1 + 2
-
-    def test_value_access_not_existing(self, basedir):
-        conftest = ConftestWithSetinitial(basedir)
-        with pytest.raises(KeyError):
-            conftest._rget_with_confmod('a', basedir)
-
-    def test_value_access_by_path(self, basedir):
-        conftest = ConftestWithSetinitial(basedir)
-        adir = basedir.join("adir")
-        assert conftest._rget_with_confmod("a", adir)[1] == 1
-        assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
-
-    def test_value_access_with_confmod(self, basedir):
-        startdir = basedir.join("adir", "b")
-        startdir.ensure("xx", dir=True)
-        conftest = ConftestWithSetinitial(startdir)
-        mod, value = conftest._rget_with_confmod("a", startdir)
-        assert  value == 1.5
-        path = py.path.local(mod.__file__)
-        assert path.dirpath() == basedir.join("adir", "b")
-        assert path.purebasename.startswith("conftest")
-
-def test_conftest_in_nonpkg_with_init(tmpdir):
-    tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3")
-    tmpdir.ensure("adir-1.0/b/conftest.py").write("b=2 ; a = 1.5")
-    tmpdir.ensure("adir-1.0/b/__init__.py")
-    tmpdir.ensure("adir-1.0/__init__.py")
-    ConftestWithSetinitial(tmpdir.join("adir-1.0", "b"))
-
-def test_doubledash_considered(testdir):
-    conf = testdir.mkdir("--option")
-    conf.join("conftest.py").ensure()
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, [conf.basename, conf.basename])
-    l = conftest._getconftestmodules(conf)
-    assert len(l) == 1
-
-def test_issue151_load_all_conftests(testdir):
-    names = "code proj src".split()
-    for name in names:
-        p = testdir.mkdir(name)
-        p.ensure("conftest.py")
-
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, names)
-    d = list(conftest._conftestpath2mod.values())
-    assert len(d) == len(names)
-
-def test_conftest_global_import(testdir):
-    testdir.makeconftest("x=3")
-    p = testdir.makepyfile("""
-        import py, pytest
-        from _pytest.config import PytestPluginManager
-        conf = PytestPluginManager()
-        mod = conf._importconftest(py.path.local("conftest.py"))
-        assert mod.x == 3
-        import conftest
-        assert conftest is mod, (conftest, mod)
-        subconf = py.path.local().ensure("sub", "conftest.py")
-        subconf.write("y=4")
-        mod2 = conf._importconftest(subconf)
-        assert mod != mod2
-        assert mod2.y == 4
-        import conftest
-        assert conftest is mod2, (conftest, mod)
-    """)
-    res = testdir.runpython(p)
-    assert res.ret == 0
-
-def test_conftestcutdir(testdir):
-    conf = testdir.makeconftest("")
-    p = testdir.mkdir("x")
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
-    l = conftest._getconftestmodules(p)
-    assert len(l) == 0
-    l = conftest._getconftestmodules(conf.dirpath())
-    assert len(l) == 0
-    assert conf not in conftest._conftestpath2mod
-    # but we can still import a conftest directly
-    conftest._importconftest(conf)
-    l = conftest._getconftestmodules(conf.dirpath())
-    assert l[0].__file__.startswith(str(conf))
-    # and all sub paths get updated properly
-    l = conftest._getconftestmodules(p)
-    assert len(l) == 1
-    assert l[0].__file__.startswith(str(conf))
-
-def test_conftestcutdir_inplace_considered(testdir):
-    conf = testdir.makeconftest("")
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
-    l = conftest._getconftestmodules(conf.dirpath())
-    assert len(l) == 1
-    assert l[0].__file__.startswith(str(conf))
-
-@pytest.mark.parametrize("name", 'test tests whatever .dotdir'.split())
-def test_setinitial_conftest_subdirs(testdir, name):
-    sub = testdir.mkdir(name)
-    subconftest = sub.ensure("conftest.py")
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
-    if name not in ('whatever', '.dotdir'):
-        assert  subconftest in conftest._conftestpath2mod
-        assert len(conftest._conftestpath2mod) == 1
-    else:
-        assert  subconftest not in conftest._conftestpath2mod
-        assert len(conftest._conftestpath2mod) == 0
-
-def test_conftest_confcutdir(testdir):
-    testdir.makeconftest("assert 0")
-    x = testdir.mkdir("x")
-    x.join("conftest.py").write(_pytest._code.Source("""
-        def pytest_addoption(parser):
-            parser.addoption("--xyz", action="store_true")
-    """))
-    result = testdir.runpytest("-h", "--confcutdir=%s" % x, x)
-    result.stdout.fnmatch_lines(["*--xyz*"])
-    assert 'warning: could not load initial' not in result.stdout.str()
-
-def test_no_conftest(testdir):
-    testdir.makeconftest("assert 0")
-    result = testdir.runpytest("--noconftest")
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-
-    result = testdir.runpytest()
-    assert result.ret == EXIT_USAGEERROR
-
-def test_conftest_existing_resultlog(testdir):
-    x = testdir.mkdir("tests")
-    x.join("conftest.py").write(_pytest._code.Source("""
-        def pytest_addoption(parser):
-            parser.addoption("--xyz", action="store_true")
-    """))
-    testdir.makefile(ext=".log", result="")  # Writes result.log
-    result = testdir.runpytest("-h", "--resultlog", "result.log")
-    result.stdout.fnmatch_lines(["*--xyz*"])
-
-def test_conftest_existing_junitxml(testdir):
-    x = testdir.mkdir("tests")
-    x.join("conftest.py").write(_pytest._code.Source("""
-        def pytest_addoption(parser):
-            parser.addoption("--xyz", action="store_true")
-    """))
-    testdir.makefile(ext=".xml", junit="")  # Writes junit.xml
-    result = testdir.runpytest("-h", "--junitxml", "junit.xml")
-    result.stdout.fnmatch_lines(["*--xyz*"])
-
-def test_conftest_import_order(testdir, monkeypatch):
-    ct1 = testdir.makeconftest("")
-    sub = testdir.mkdir("sub")
-    ct2 = sub.join("conftest.py")
-    ct2.write("")
-    def impct(p):
-        return p
-    conftest = PytestPluginManager()
-    monkeypatch.setattr(conftest, '_importconftest', impct)
-    assert conftest._getconftestmodules(sub) == [ct1, ct2]
-
-
-def test_fixture_dependency(testdir, monkeypatch):
-    ct1 = testdir.makeconftest("")
-    ct1 = testdir.makepyfile("__init__.py")
-    ct1.write("")
-    sub = testdir.mkdir("sub")
-    sub.join("__init__.py").write("")
-    sub.join("conftest.py").write(py.std.textwrap.dedent("""
-        import pytest
-
-        @pytest.fixture
-        def not_needed():
-            assert False, "Should not be called!"
-
-        @pytest.fixture
-        def foo():
-            assert False, "Should not be called!"
-
-        @pytest.fixture
-        def bar(foo):
-            return 'bar'
-    """))
-    subsub = sub.mkdir("subsub")
-    subsub.join("__init__.py").write("")
-    subsub.join("test_bar.py").write(py.std.textwrap.dedent("""
-        import pytest
-
-        @pytest.fixture
-        def bar():
-            return 'sub bar'
-
-        def test_event_fixture(bar):
-            assert bar == 'sub bar'
-    """))
-    result = testdir.runpytest("sub")
-    result.stdout.fnmatch_lines(["*1 passed*"])
-
-
-def test_conftest_found_with_double_dash(testdir):
-    sub = testdir.mkdir("sub")
-    sub.join("conftest.py").write(py.std.textwrap.dedent("""
-        def pytest_addoption(parser):
-            parser.addoption("--hello-world", action="store_true")
-    """))
-    p = sub.join("test_hello.py")
-    p.write(py.std.textwrap.dedent("""
-        import pytest
-        def test_hello(found):
-            assert found == 1
-    """))
-    result = testdir.runpytest(str(p) + "::test_hello", "-h")
-    result.stdout.fnmatch_lines("""
-        *--hello-world*
-    """)
-
-
-class TestConftestVisibility:
-    def _setup_tree(self, testdir):  # for issue616
-        # example mostly taken from:
-        # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html
-        runner = testdir.mkdir("empty")
-        package = testdir.mkdir("package")
-
-        package.join("conftest.py").write(dedent("""\
-            import pytest
-            @pytest.fixture
-            def fxtr():
-                return "from-package"
-        """))
-        package.join("test_pkgroot.py").write(dedent("""\
-            def test_pkgroot(fxtr):
-                assert fxtr == "from-package"
-        """))
-
-        swc = package.mkdir("swc")
-        swc.join("__init__.py").ensure()
-        swc.join("conftest.py").write(dedent("""\
-            import pytest
-            @pytest.fixture
-            def fxtr():
-                return "from-swc"
-        """))
-        swc.join("test_with_conftest.py").write(dedent("""\
-            def test_with_conftest(fxtr):
-                assert fxtr == "from-swc"
-
-        """))
-
-        snc = package.mkdir("snc")
-        snc.join("__init__.py").ensure()
-        snc.join("test_no_conftest.py").write(dedent("""\
-            def test_no_conftest(fxtr):
-                assert fxtr == "from-package"   # No local conftest.py, so should
-                                                # use value from parent dir's
-
-        """))
-        print ("created directory structure:")
-        for x in testdir.tmpdir.visit():
-            print ("   " + x.relto(testdir.tmpdir))
-
-        return {
-            "runner": runner,
-            "package": package,
-            "swc": swc,
-            "snc": snc}
-
-    # N.B.: "swc" stands for "subdir with conftest.py"
-    #       "snc" stands for "subdir no [i.e. without] conftest.py"
-    @pytest.mark.parametrize("chdir,testarg,expect_ntests_passed", [
-	# Effective target: package/..
-        ("runner",  "..",               3),
-        ("package", "..",               3),
-        ("swc",     "../..",            3),
-        ("snc",     "../..",            3),
-
-	# Effective target: package
-        ("runner",  "../package",       3),
-        ("package", ".",                3),
-        ("swc",     "..",               3),
-        ("snc",     "..",               3),
-
-	# Effective target: package/swc
-        ("runner",  "../package/swc",   1),
-        ("package", "./swc",            1),
-        ("swc",     ".",                1),
-        ("snc",     "../swc",           1),
-
-	# Effective target: package/snc
-        ("runner",  "../package/snc",   1),
-        ("package", "./snc",            1),
-        ("swc",     "../snc",           1),
-        ("snc",     ".",                1),
-    ])
-    @pytest.mark.issue616
-    def test_parsefactories_relative_node_ids(
-            self, testdir, chdir,testarg, expect_ntests_passed):
-        dirs = self._setup_tree(testdir)
-        print("pytest run in cwd: %s" %(
-              dirs[chdir].relto(testdir.tmpdir)))
-        print("pytestarg        : %s" %(testarg))
-        print("expected pass    : %s" %(expect_ntests_passed))
-        with dirs[chdir].as_cwd():
-            reprec = testdir.inline_run(testarg, "-q", "--traceconfig")
-            reprec.assertoutcome(passed=expect_ntests_passed)
-
-
-@pytest.mark.parametrize('confcutdir,passed,error', [
-    ('.', 2, 0),
-    ('src', 1, 1),
-    (None, 1, 1),
-])
-def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error):
-    """Test that conftest files are detected only up to a ini file, unless
-    an explicit --confcutdir option is given.
-    """
-    root = testdir.tmpdir
-    src = root.join('src').ensure(dir=1)
-    src.join('pytest.ini').write('[pytest]')
-    src.join('conftest.py').write(_pytest._code.Source("""
-        import pytest
-        @pytest.fixture
-        def fix1(): pass
-    """))
-    src.join('test_foo.py').write(_pytest._code.Source("""
-        def test_1(fix1):
-            pass
-        def test_2(out_of_reach):
-            pass
-    """))
-    root.join('conftest.py').write(_pytest._code.Source("""
-        import pytest
-        @pytest.fixture
-        def out_of_reach(): pass
-    """))
-
-    args = [str(src)]
-    if confcutdir:
-        args = ['--confcutdir=%s' % root.join(confcutdir)]
-    result = testdir.runpytest(*args)
-    match = ''
-    if passed:
-        match += '*%d passed*' % passed
-    if error:
-        match += '*%d error*' % error
-    result.stdout.fnmatch_lines(match)
-
-
-def test_issue1073_conftest_special_objects(testdir):
-    testdir.makeconftest("""
-        class DontTouchMe:
-            def __getattr__(self, x):
-                raise Exception('cant touch me')
-
-        x = DontTouchMe()
-    """)
-    testdir.makepyfile("""
-        def test_some():
-            pass
-    """)
-    res = testdir.runpytest()
-    assert res.ret == 0
diff --git a/tools/pytest/testing/test_doctest.py b/tools/pytest/testing/test_doctest.py
deleted file mode 100644
index a4821ee..0000000
--- a/tools/pytest/testing/test_doctest.py
+++ /dev/null
@@ -1,715 +0,0 @@
-# encoding: utf-8
-import sys
-import _pytest._code
-from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
-import pytest
-
-class TestDoctests:
-
-    def test_collect_testtextfile(self, testdir):
-        w = testdir.maketxtfile(whatever="")
-        checkfile = testdir.maketxtfile(test_something="""
-            alskdjalsdk
-            >>> i = 5
-            >>> i-1
-            4
-        """)
-        for x in (testdir.tmpdir, checkfile):
-            #print "checking that %s returns custom items" % (x,)
-            items, reprec = testdir.inline_genitems(x)
-            assert len(items) == 1
-            assert isinstance(items[0], DoctestTextfile)
-        items, reprec = testdir.inline_genitems(w)
-        assert len(items) == 1
-
-    def test_collect_module_empty(self, testdir):
-        path = testdir.makepyfile(whatever="#")
-        for p in (path, testdir.tmpdir):
-            items, reprec = testdir.inline_genitems(p,
-                '--doctest-modules')
-            assert len(items) == 0
-
-    def test_collect_module_single_modulelevel_doctest(self, testdir):
-        path = testdir.makepyfile(whatever='""">>> pass"""')
-        for p in (path, testdir.tmpdir):
-            items, reprec = testdir.inline_genitems(p,
-                '--doctest-modules')
-            assert len(items) == 1
-            assert isinstance(items[0], DoctestItem)
-            assert isinstance(items[0].parent, DoctestModule)
-
-    def test_collect_module_two_doctest_one_modulelevel(self, testdir):
-        path = testdir.makepyfile(whatever="""
-            '>>> x = None'
-            def my_func():
-                ">>> magic = 42 "
-        """)
-        for p in (path, testdir.tmpdir):
-            items, reprec = testdir.inline_genitems(p,
-                '--doctest-modules')
-            assert len(items) == 2
-            assert isinstance(items[0], DoctestItem)
-            assert isinstance(items[1], DoctestItem)
-            assert isinstance(items[0].parent, DoctestModule)
-            assert items[0].parent is items[1].parent
-
-    def test_collect_module_two_doctest_no_modulelevel(self, testdir):
-        path = testdir.makepyfile(whatever="""
-            '# Empty'
-            def my_func():
-                ">>> magic = 42 "
-            def unuseful():
-                '''
-                # This is a function
-                # >>> # it doesn't have any doctest
-                '''
-            def another():
-                '''
-                # This is another function
-                >>> import os # this one does have a doctest
-                '''
-        """)
-        for p in (path, testdir.tmpdir):
-            items, reprec = testdir.inline_genitems(p,
-                '--doctest-modules')
-            assert len(items) == 2
-            assert isinstance(items[0], DoctestItem)
-            assert isinstance(items[1], DoctestItem)
-            assert isinstance(items[0].parent, DoctestModule)
-            assert items[0].parent is items[1].parent
-
-    def test_simple_doctestfile(self, testdir):
-        p = testdir.maketxtfile(test_doc="""
-            >>> x = 1
-            >>> x == 1
-            False
-        """)
-        reprec = testdir.inline_run(p, )
-        reprec.assertoutcome(failed=1)
-
-    def test_new_pattern(self, testdir):
-        p = testdir.maketxtfile(xdoc="""
-            >>> x = 1
-            >>> x == 1
-            False
-        """)
-        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
-        reprec.assertoutcome(failed=1)
-
-    def test_multiple_patterns(self, testdir):
-        """Test support for multiple --doctest-glob arguments (#1255).
-        """
-        testdir.maketxtfile(xdoc="""
-            >>> 1
-            1
-        """)
-        testdir.makefile('.foo', test="""
-            >>> 1
-            1
-        """)
-        testdir.maketxtfile(test_normal="""
-            >>> 1
-            1
-        """)
-        expected = set(['xdoc.txt', 'test.foo', 'test_normal.txt'])
-        assert set(x.basename for x in testdir.tmpdir.listdir()) == expected
-        args = ["--doctest-glob=xdoc*.txt", "--doctest-glob=*.foo"]
-        result = testdir.runpytest(*args)
-        result.stdout.fnmatch_lines([
-            '*test.foo *',
-            '*xdoc.txt *',
-            '*2 passed*',
-        ])
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            '*test_normal.txt *',
-            '*1 passed*',
-        ])
-
-    def test_doctest_unexpected_exception(self, testdir):
-        testdir.maketxtfile("""
-            >>> i = 0
-            >>> 0 / i
-            2
-        """)
-        result = testdir.runpytest("--doctest-modules")
-        result.stdout.fnmatch_lines([
-            "*unexpected_exception*",
-            "*>>> i = 0*",
-            "*>>> 0 / i*",
-            "*UNEXPECTED*ZeroDivision*",
-        ])
-
-    def test_docstring_context_around_error(self, testdir):
-        """Test that we show some context before the actual line of a failing
-        doctest.
-        """
-        testdir.makepyfile('''
-            def foo():
-                """
-                text-line-1
-                text-line-2
-                text-line-3
-                text-line-4
-                text-line-5
-                text-line-6
-                text-line-7
-                text-line-8
-                text-line-9
-                text-line-10
-                text-line-11
-                >>> 1 + 1
-                3
-
-                text-line-after
-                """
-        ''')
-        result = testdir.runpytest('--doctest-modules')
-        result.stdout.fnmatch_lines([
-            '*docstring_context_around_error*',
-            '005*text-line-3',
-            '006*text-line-4',
-            '013*text-line-11',
-            '014*>>> 1 + 1',
-            'Expected:',
-            '    3',
-            'Got:',
-            '    2',
-        ])
-        # lines below should be trimmed out
-        assert 'text-line-2' not in result.stdout.str()
-        assert 'text-line-after' not in result.stdout.str()
-
-    def test_doctest_linedata_missing(self, testdir):
-        testdir.tmpdir.join('hello.py').write(_pytest._code.Source("""
-            class Fun(object):
-                @property
-                def test(self):
-                    '''
-                    >>> a = 1
-                    >>> 1/0
-                    '''
-            """))
-        result = testdir.runpytest("--doctest-modules")
-        result.stdout.fnmatch_lines([
-            "*hello*",
-            "*EXAMPLE LOCATION UNKNOWN, not showing all tests of that example*",
-            "*1/0*",
-            "*UNEXPECTED*ZeroDivision*",
-            "*1 failed*",
-        ])
-
-
-    def test_doctest_unex_importerror(self, testdir):
-        testdir.tmpdir.join("hello.py").write(_pytest._code.Source("""
-            import asdalsdkjaslkdjasd
-        """))
-        testdir.maketxtfile("""
-            >>> import hello
-            >>>
-        """)
-        result = testdir.runpytest("--doctest-modules")
-        result.stdout.fnmatch_lines([
-            "*>>> import hello",
-            "*UNEXPECTED*ImportError*",
-            "*import asdals*",
-        ])
-
-    def test_doctestmodule(self, testdir):
-        p = testdir.makepyfile("""
-            '''
-                >>> x = 1
-                >>> x == 1
-                False
-
-            '''
-        """)
-        reprec = testdir.inline_run(p, "--doctest-modules")
-        reprec.assertoutcome(failed=1)
-
-    def test_doctestmodule_external_and_issue116(self, testdir):
-        p = testdir.mkpydir("hello")
-        p.join("__init__.py").write(_pytest._code.Source("""
-            def somefunc():
-                '''
-                    >>> i = 0
-                    >>> i + 1
-                    2
-                '''
-        """))
-        result = testdir.runpytest(p, "--doctest-modules")
-        result.stdout.fnmatch_lines([
-            '004 *>>> i = 0',
-            '005 *>>> i + 1',
-            '*Expected:',
-            "*    2",
-            "*Got:",
-            "*    1",
-            "*:5: DocTestFailure"
-        ])
-
-
-    def test_txtfile_failing(self, testdir):
-        p = testdir.maketxtfile("""
-            >>> i = 0
-            >>> i + 1
-            2
-        """)
-        result = testdir.runpytest(p, "-s")
-        result.stdout.fnmatch_lines([
-            '001 >>> i = 0',
-            '002 >>> i + 1',
-            'Expected:',
-            "    2",
-            "Got:",
-            "    1",
-            "*test_txtfile_failing.txt:2: DocTestFailure"
-        ])
-
-    def test_txtfile_with_fixtures(self, testdir):
-        p = testdir.maketxtfile("""
-            >>> dir = getfixture('tmpdir')
-            >>> type(dir).__name__
-            'LocalPath'
-        """)
-        reprec = testdir.inline_run(p, )
-        reprec.assertoutcome(passed=1)
-
-    def test_txtfile_with_usefixtures_in_ini(self, testdir):
-        testdir.makeini("""
-            [pytest]
-            usefixtures = myfixture
-        """)
-        testdir.makeconftest("""
-            import pytest
-            @pytest.fixture
-            def myfixture(monkeypatch):
-                monkeypatch.setenv("HELLO", "WORLD")
-        """)
-
-        p = testdir.maketxtfile("""
-            >>> import os
-            >>> os.environ["HELLO"]
-            'WORLD'
-        """)
-        reprec = testdir.inline_run(p, )
-        reprec.assertoutcome(passed=1)
-
-    def test_doctestmodule_with_fixtures(self, testdir):
-        p = testdir.makepyfile("""
-            '''
-                >>> dir = getfixture('tmpdir')
-                >>> type(dir).__name__
-                'LocalPath'
-            '''
-        """)
-        reprec = testdir.inline_run(p, "--doctest-modules")
-        reprec.assertoutcome(passed=1)
-
-    def test_doctestmodule_three_tests(self, testdir):
-        p = testdir.makepyfile("""
-            '''
-            >>> dir = getfixture('tmpdir')
-            >>> type(dir).__name__
-            'LocalPath'
-            '''
-            def my_func():
-                '''
-                >>> magic = 42
-                >>> magic - 42
-                0
-                '''
-            def unuseful():
-                pass
-            def another():
-                '''
-                >>> import os
-                >>> os is os
-                True
-                '''
-        """)
-        reprec = testdir.inline_run(p, "--doctest-modules")
-        reprec.assertoutcome(passed=3)
-
-    def test_doctestmodule_two_tests_one_fail(self, testdir):
-        p = testdir.makepyfile("""
-            class MyClass:
-                def bad_meth(self):
-                    '''
-                    >>> magic = 42
-                    >>> magic
-                    0
-                    '''
-                def nice_meth(self):
-                    '''
-                    >>> magic = 42
-                    >>> magic - 42
-                    0
-                    '''
-        """)
-        reprec = testdir.inline_run(p, "--doctest-modules")
-        reprec.assertoutcome(failed=1, passed=1)
-
-    def test_ignored_whitespace(self, testdir):
-        testdir.makeini("""
-            [pytest]
-            doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE
-        """)
-        p = testdir.makepyfile("""
-            class MyClass:
-                '''
-                >>> a = "foo    "
-                >>> print(a)
-                foo
-                '''
-                pass
-        """)
-        reprec = testdir.inline_run(p, "--doctest-modules")
-        reprec.assertoutcome(passed=1)
-
-    def test_non_ignored_whitespace(self, testdir):
-        testdir.makeini("""
-            [pytest]
-            doctest_optionflags = ELLIPSIS
-        """)
-        p = testdir.makepyfile("""
-            class MyClass:
-                '''
-                >>> a = "foo    "
-                >>> print(a)
-                foo
-                '''
-                pass
-        """)
-        reprec = testdir.inline_run(p, "--doctest-modules")
-        reprec.assertoutcome(failed=1, passed=0)
-
-    def test_ignored_whitespace_glob(self, testdir):
-        testdir.makeini("""
-            [pytest]
-            doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE
-        """)
-        p = testdir.maketxtfile(xdoc="""
-            >>> a = "foo    "
-            >>> print(a)
-            foo
-        """)
-        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
-        reprec.assertoutcome(passed=1)
-
-    def test_non_ignored_whitespace_glob(self, testdir):
-        testdir.makeini("""
-            [pytest]
-            doctest_optionflags = ELLIPSIS
-        """)
-        p = testdir.maketxtfile(xdoc="""
-            >>> a = "foo    "
-            >>> print(a)
-            foo
-        """)
-        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
-        reprec.assertoutcome(failed=1, passed=0)
-
-    def test_contains_unicode(self, testdir):
-        """Fix internal error with docstrings containing non-ascii characters.
-        """
-        testdir.makepyfile(u'''
-            # encoding: utf-8
-            def foo():
-                """
-                >>> name = 'с' # not letter 'c' but instead Cyrillic 's'.
-                'anything'
-                """
-        ''')
-        result = testdir.runpytest('--doctest-modules')
-        result.stdout.fnmatch_lines([
-            'Got nothing',
-            '* 1 failed in*',
-        ])
-
-    def test_ignore_import_errors_on_doctest(self, testdir):
-        p = testdir.makepyfile("""
-            import asdf
-
-            def add_one(x):
-                '''
-                >>> add_one(1)
-                2
-                '''
-                return x + 1
-        """)
-
-        reprec = testdir.inline_run(p, "--doctest-modules",
-                                    "--doctest-ignore-import-errors")
-        reprec.assertoutcome(skipped=1, failed=1, passed=0)
-
-    def test_junit_report_for_doctest(self, testdir):
-        """
-        #713: Fix --junit-xml option when used with --doctest-modules.
-        """
-        p = testdir.makepyfile("""
-            def foo():
-                '''
-                >>> 1 + 1
-                3
-                '''
-                pass
-        """)
-        reprec = testdir.inline_run(p, "--doctest-modules",
-                                    "--junit-xml=junit.xml")
-        reprec.assertoutcome(failed=1)
-
-
-class TestLiterals:
-
-    @pytest.mark.parametrize('config_mode', ['ini', 'comment'])
-    def test_allow_unicode(self, testdir, config_mode):
-        """Test that doctests which output unicode work in all python versions
-        tested by pytest when the ALLOW_UNICODE option is used (either in
-        the ini file or by an inline comment).
-        """
-        if config_mode == 'ini':
-            testdir.makeini('''
-            [pytest]
-            doctest_optionflags = ALLOW_UNICODE
-            ''')
-            comment = ''
-        else:
-            comment = '#doctest: +ALLOW_UNICODE'
-
-        testdir.maketxtfile(test_doc="""
-            >>> b'12'.decode('ascii') {comment}
-            '12'
-        """.format(comment=comment))
-        testdir.makepyfile(foo="""
-            def foo():
-              '''
-              >>> b'12'.decode('ascii') {comment}
-              '12'
-              '''
-        """.format(comment=comment))
-        reprec = testdir.inline_run("--doctest-modules")
-        reprec.assertoutcome(passed=2)
-
-    @pytest.mark.parametrize('config_mode', ['ini', 'comment'])
-    def test_allow_bytes(self, testdir, config_mode):
-        """Test that doctests which output bytes work in all python versions
-        tested by pytest when the ALLOW_BYTES option is used (either in
-        the ini file or by an inline comment)(#1287).
-        """
-        if config_mode == 'ini':
-            testdir.makeini('''
-            [pytest]
-            doctest_optionflags = ALLOW_BYTES
-            ''')
-            comment = ''
-        else:
-            comment = '#doctest: +ALLOW_BYTES'
-
-        testdir.maketxtfile(test_doc="""
-            >>> b'foo'  {comment}
-            'foo'
-        """.format(comment=comment))
-        testdir.makepyfile(foo="""
-            def foo():
-              '''
-              >>> b'foo'  {comment}
-              'foo'
-              '''
-        """.format(comment=comment))
-        reprec = testdir.inline_run("--doctest-modules")
-        reprec.assertoutcome(passed=2)
-
-    def test_unicode_string(self, testdir):
-        """Test that doctests which output unicode fail in Python 2 when
-        the ALLOW_UNICODE option is not used. The same test should pass
-        in Python 3.
-        """
-        testdir.maketxtfile(test_doc="""
-            >>> b'12'.decode('ascii')
-            '12'
-        """)
-        reprec = testdir.inline_run()
-        passed = int(sys.version_info[0] >= 3)
-        reprec.assertoutcome(passed=passed, failed=int(not passed))
-
-    def test_bytes_literal(self, testdir):
-        """Test that doctests which output bytes fail in Python 3 when
-        the ALLOW_BYTES option is not used. The same test should pass
-        in Python 2 (#1287).
-        """
-        testdir.maketxtfile(test_doc="""
-            >>> b'foo'
-            'foo'
-        """)
-        reprec = testdir.inline_run()
-        passed = int(sys.version_info[0] == 2)
-        reprec.assertoutcome(passed=passed, failed=int(not passed))
-
-
-class TestDoctestSkips:
-    """
-    If all examples in a doctest are skipped due to the SKIP option, then
-    the tests should be SKIPPED rather than PASSED. (#957)
-    """
-
-    @pytest.fixture(params=['text', 'module'])
-    def makedoctest(self, testdir, request):
-        def makeit(doctest):
-            mode = request.param
-            if mode == 'text':
-                testdir.maketxtfile(doctest)
-            else:
-                assert mode == 'module'
-                testdir.makepyfile('"""\n%s"""' % doctest)
-
-        return makeit
-
-    def test_one_skipped(self, testdir, makedoctest):
-        makedoctest("""
-            >>> 1 + 1  # doctest: +SKIP
-            2
-            >>> 2 + 2
-            4
-        """)
-        reprec = testdir.inline_run("--doctest-modules")
-        reprec.assertoutcome(passed=1)
-
-    def test_one_skipped_failed(self, testdir, makedoctest):
-        makedoctest("""
-            >>> 1 + 1  # doctest: +SKIP
-            2
-            >>> 2 + 2
-            200
-        """)
-        reprec = testdir.inline_run("--doctest-modules")
-        reprec.assertoutcome(failed=1)
-
-    def test_all_skipped(self, testdir, makedoctest):
-        makedoctest("""
-            >>> 1 + 1  # doctest: +SKIP
-            2
-            >>> 2 + 2  # doctest: +SKIP
-            200
-        """)
-        reprec = testdir.inline_run("--doctest-modules")
-        reprec.assertoutcome(skipped=1)
-
-
-class TestDoctestAutoUseFixtures:
-
-    SCOPES = ['module', 'session', 'class', 'function']
-
-    def test_doctest_module_session_fixture(self, testdir):
-        """Test that session fixtures are initialized for doctest modules (#768)
-        """
-        # session fixture which changes some global data, which will
-        # be accessed by doctests in a module
-        testdir.makeconftest("""
-            import pytest
-            import sys
-
-            @pytest.yield_fixture(autouse=True, scope='session')
-            def myfixture():
-                assert not hasattr(sys, 'pytest_session_data')
-                sys.pytest_session_data = 1
-                yield
-                del sys.pytest_session_data
-        """)
-        testdir.makepyfile(foo="""
-            import sys
-
-            def foo():
-              '''
-              >>> assert sys.pytest_session_data == 1
-              '''
-
-            def bar():
-              '''
-              >>> assert sys.pytest_session_data == 1
-              '''
-        """)
-        result = testdir.runpytest("--doctest-modules")
-        result.stdout.fnmatch_lines('*2 passed*')
-
-    @pytest.mark.parametrize('scope', SCOPES)
-    @pytest.mark.parametrize('enable_doctest', [True, False])
-    def test_fixture_scopes(self, testdir, scope, enable_doctest):
-        """Test that auto-use fixtures work properly with doctest modules.
-        See #1057 and #1100.
-        """
-        testdir.makeconftest('''
-            import pytest
-
-            @pytest.fixture(autouse=True, scope="{scope}")
-            def auto(request):
-                return 99
-        '''.format(scope=scope))
-        testdir.makepyfile(test_1='''
-            def test_foo():
-                """
-                >>> getfixture('auto') + 1
-                100
-                """
-            def test_bar():
-                assert 1
-        ''')
-        params = ('--doctest-modules',) if enable_doctest else ()
-        passes = 3 if enable_doctest else 2
-        result = testdir.runpytest(*params)
-        result.stdout.fnmatch_lines(['*=== %d passed in *' % passes])
-
-    @pytest.mark.parametrize('scope', SCOPES)
-    @pytest.mark.parametrize('autouse', [True, False])
-    @pytest.mark.parametrize('use_fixture_in_doctest', [True, False])
-    def test_fixture_module_doctest_scopes(self, testdir, scope, autouse,
-                                           use_fixture_in_doctest):
-        """Test that auto-use fixtures work properly with doctest files.
-        See #1057 and #1100.
-        """
-        testdir.makeconftest('''
-            import pytest
-
-            @pytest.fixture(autouse={autouse}, scope="{scope}")
-            def auto(request):
-                return 99
-        '''.format(scope=scope, autouse=autouse))
-        if use_fixture_in_doctest:
-            testdir.maketxtfile(test_doc="""
-                >>> getfixture('auto')
-                99
-            """)
-        else:
-            testdir.maketxtfile(test_doc="""
-                >>> 1 + 1
-                2
-            """)
-        result = testdir.runpytest('--doctest-modules')
-        assert 'FAILURES' not in str(result.stdout.str())
-        result.stdout.fnmatch_lines(['*=== 1 passed in *'])
-
-    @pytest.mark.parametrize('scope', SCOPES)
-    def test_auto_use_request_attributes(self, testdir, scope):
-        """Check that all attributes of a request in an autouse fixture
-        behave as expected when requested for a doctest item.
-        """
-        testdir.makeconftest('''
-            import pytest
-
-            @pytest.fixture(autouse=True, scope="{scope}")
-            def auto(request):
-                if "{scope}" == 'module':
-                    assert request.module is None
-                if "{scope}" == 'class':
-                    assert request.cls is None
-                if "{scope}" == 'function':
-                    assert request.function is None
-                return 99
-        '''.format(scope=scope))
-        testdir.maketxtfile(test_doc="""
-            >>> 1 + 1
-            2
-        """)
-        result = testdir.runpytest('--doctest-modules')
-        assert 'FAILURES' not in str(result.stdout.str())
-        result.stdout.fnmatch_lines(['*=== 1 passed in *'])
diff --git a/tools/pytest/testing/test_genscript.py b/tools/pytest/testing/test_genscript.py
deleted file mode 100644
index 1260a5a..0000000
--- a/tools/pytest/testing/test_genscript.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import pytest
-import sys
-
-
-@pytest.fixture(scope="module")
-def standalone(request):
-    return Standalone(request)
-
-class Standalone:
-    def __init__(self, request):
-        self.testdir = request.getfuncargvalue("testdir")
-        script = "mypytest"
-        result = self.testdir.runpytest("--genscript=%s" % script)
-        assert result.ret == 0
-        self.script = self.testdir.tmpdir.join(script)
-        assert self.script.check()
-
-    def run(self, anypython, testdir, *args):
-        return testdir._run(anypython, self.script, *args)
-
-def test_gen(testdir, anypython, standalone):
-    if sys.version_info >= (2,7):
-        result = testdir._run(anypython, "-c",
-                                "import sys;print (sys.version_info >=(2,7))")
-        assert result.ret == 0
-        if result.stdout.str() == "False":
-            pytest.skip("genscript called from python2.7 cannot work "
-                        "earlier python versions")
-    result = standalone.run(anypython, testdir, '--version')
-    if result.ret == 2:
-        result.stderr.fnmatch_lines(["*ERROR: setuptools not installed*"])
-    elif result.ret == 0:
-        result.stderr.fnmatch_lines([
-            "*imported from*mypytest*"
-        ])
-        p = testdir.makepyfile("def test_func(): assert 0")
-        result = standalone.run(anypython, testdir, p)
-        assert result.ret != 0
-    else:
-        pytest.fail("Unexpected return code")
-
-
-def test_freeze_includes():
-    """
-    Smoke test for freeze_includes(), to ensure that it works across all
-    supported python versions.
-    """
-    includes = pytest.freeze_includes()
-    assert len(includes) > 1
-    assert '_pytest.genscript' in includes
-
diff --git a/tools/pytest/testing/test_helpconfig.py b/tools/pytest/testing/test_helpconfig.py
deleted file mode 100644
index 9f8d87b..0000000
--- a/tools/pytest/testing/test_helpconfig.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-import pytest
-
-def test_version(testdir, pytestconfig):
-    result = testdir.runpytest("--version")
-    assert result.ret == 0
-    #p = py.path.local(py.__file__).dirpath()
-    result.stderr.fnmatch_lines([
-        '*pytest*%s*imported from*' % (pytest.__version__, )
-    ])
-    if pytestconfig.pluginmanager.list_plugin_distinfo():
-        result.stderr.fnmatch_lines([
-            "*setuptools registered plugins:",
-            "*at*",
-        ])
-
-def test_help(testdir):
-    result = testdir.runpytest("--help")
-    assert result.ret == 0
-    result.stdout.fnmatch_lines("""
-        *-v*verbose*
-        *setup.cfg*
-        *minversion*
-        *to see*markers*py.test --markers*
-        *to see*fixtures*py.test --fixtures*
-    """)
-
-def test_hookvalidation_unknown(testdir):
-    testdir.makeconftest("""
-        def pytest_hello(xyz):
-            pass
-    """)
-    result = testdir.runpytest()
-    assert result.ret != 0
-    result.stderr.fnmatch_lines([
-        '*unknown hook*pytest_hello*'
-    ])
-
-def test_hookvalidation_optional(testdir):
-    testdir.makeconftest("""
-        import pytest
-        @pytest.hookimpl(optionalhook=True)
-        def pytest_hello(xyz):
-            pass
-    """)
-    result = testdir.runpytest()
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-
-def test_traceconfig(testdir):
-    result = testdir.runpytest("--traceconfig")
-    result.stdout.fnmatch_lines([
-        "*using*pytest*py*",
-        "*active plugins*",
-    ])
-
-def test_debug(testdir, monkeypatch):
-    result = testdir.runpytest_subprocess("--debug")
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-    p = testdir.tmpdir.join("pytestdebug.log")
-    assert "pytest_sessionstart" in p.read()
-
-def test_PYTEST_DEBUG(testdir, monkeypatch):
-    monkeypatch.setenv("PYTEST_DEBUG", "1")
-    result = testdir.runpytest_subprocess()
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-    result.stderr.fnmatch_lines([
-        "*pytest_plugin_registered*",
-        "*manager*PluginManager*"
-    ])
diff --git a/tools/pytest/testing/test_junitxml.py b/tools/pytest/testing/test_junitxml.py
deleted file mode 100644
index 5960f88..0000000
--- a/tools/pytest/testing/test_junitxml.py
+++ /dev/null
@@ -1,816 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from xml.dom import minidom
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-import py
-import sys
-import os
-from _pytest.junitxml import LogXML
-import pytest
-
-
-def runandparse(testdir, *args):
-    resultpath = testdir.tmpdir.join("junit.xml")
-    result = testdir.runpytest("--junitxml=%s" % resultpath, *args)
-    xmldoc = minidom.parse(str(resultpath))
-    return result, DomNode(xmldoc)
-
-
-def assert_attr(node, **kwargs):
-    __tracebackhide__ = True
-
-    def nodeval(node, name):
-        anode = node.getAttributeNode(name)
-        if anode is not None:
-            return anode.value
-
-    expected = dict((name, str(value)) for name, value in kwargs.items())
-    on_node = dict((name, nodeval(node, name)) for name in expected)
-    assert on_node == expected
-
-
-class DomNode(object):
-    def __init__(self, dom):
-        self.__node = dom
-
-    def __repr__(self):
-        return self.__node.toxml()
-
-    def find_first_by_tag(self, tag):
-        return self.find_nth_by_tag(tag, 0)
-
-    def _by_tag(self, tag):
-        return self.__node.getElementsByTagName(tag)
-
-    def find_nth_by_tag(self, tag, n):
-        items = self._by_tag(tag)
-        try:
-            nth = items[n]
-        except IndexError:
-            pass
-        else:
-            return type(self)(nth)
-
-    def find_by_tag(self, tag):
-        t = type(self)
-        return [t(x) for x in self.__node.getElementsByTagName(tag)]
-
-    def __getitem__(self, key):
-        node = self.__node.getAttributeNode(key)
-        if node is not None:
-            return node.value
-
-    def assert_attr(self, **kwargs):
-        __tracebackhide__ = True
-        return assert_attr(self.__node, **kwargs)
-
-    def toxml(self):
-        return self.__node.toxml()
-
-    @property
-    def text(self):
-        return self.__node.childNodes[0].wholeText
-
-    @property
-    def tag(self):
-        return self.__node.tagName
-
-    @property
-    def next_siebling(self):
-        return type(self)(self.__node.nextSibling)
-
-
-class TestPython:
-    def test_summing_simple(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def test_pass():
-                pass
-            def test_fail():
-                assert 0
-            def test_skip():
-                pytest.skip("")
-            @pytest.mark.xfail
-            def test_xfail():
-                assert 0
-            @pytest.mark.xfail
-            def test_xpass():
-                assert 1
-        """)
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(name="pytest", errors=0, failures=1, skips=3, tests=2)
-
-    def test_timing_function(self, testdir):
-        testdir.makepyfile("""
-            import time, pytest
-            def setup_module():
-                time.sleep(0.01)
-            def teardown_module():
-                time.sleep(0.01)
-            def test_sleep():
-                time.sleep(0.01)
-        """)
-        result, dom = runandparse(testdir)
-        node = dom.find_first_by_tag("testsuite")
-        tnode = node.find_first_by_tag("testcase")
-        val = tnode["time"]
-        assert round(float(val), 2) >= 0.03
-
-    def test_setup_error(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__arg(request):
-                raise ValueError()
-            def test_function(arg):
-                pass
-        """)
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(errors=1, tests=0)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_setup_error.py",
-            line="2",
-            classname="test_setup_error",
-            name="test_function")
-        fnode = tnode.find_first_by_tag("error")
-        fnode.assert_attr(message="test setup failure")
-        assert "ValueError" in fnode.toxml()
-
-    def test_skip_contains_name_reason(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def test_skip():
-                pytest.skip("hello23")
-        """)
-        result, dom = runandparse(testdir)
-        assert result.ret == 0
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(skips=1)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_skip_contains_name_reason.py",
-            line="1",
-            classname="test_skip_contains_name_reason",
-            name="test_skip")
-        snode = tnode.find_first_by_tag("skipped")
-        snode.assert_attr(type="pytest.skip", message="hello23", )
-
-    def test_classname_instance(self, testdir):
-        testdir.makepyfile("""
-            class TestClass:
-                def test_method(self):
-                    assert 0
-        """)
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(failures=1)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_classname_instance.py",
-            line="1",
-            classname="test_classname_instance.TestClass",
-            name="test_method")
-
-    def test_classname_nested_dir(self, testdir):
-        p = testdir.tmpdir.ensure("sub", "test_hello.py")
-        p.write("def test_func(): 0/0")
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(failures=1)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file=os.path.join("sub", "test_hello.py"),
-            line="0",
-            classname="sub.test_hello",
-            name="test_func")
-
-    def test_internal_error(self, testdir):
-        testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0")
-        testdir.makepyfile("def test_function(): pass")
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(errors=1, tests=0)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(classname="pytest", name="internal")
-        fnode = tnode.find_first_by_tag("error")
-        fnode.assert_attr(message="internal error")
-        assert "Division" in fnode.toxml()
-
-    def test_failure_function(self, testdir):
-        testdir.makepyfile("""
-            import sys
-            def test_fail():
-                print ("hello-stdout")
-                sys.stderr.write("hello-stderr\\n")
-                raise ValueError(42)
-        """)
-
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(failures=1, tests=1)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_failure_function.py",
-            line="1",
-            classname="test_failure_function",
-            name="test_fail")
-        fnode = tnode.find_first_by_tag("failure")
-        fnode.assert_attr(message="ValueError: 42")
-        assert "ValueError" in fnode.toxml()
-        systemout = fnode.next_siebling
-        assert systemout.tag == "system-out"
-        assert "hello-stdout" in systemout.toxml()
-        systemerr = systemout.next_siebling
-        assert systemerr.tag == "system-err"
-        assert "hello-stderr" in systemerr.toxml()
-
-    def test_failure_verbose_message(self, testdir):
-        testdir.makepyfile("""
-            import sys
-            def test_fail():
-                assert 0, "An error"
-        """)
-
-        result, dom = runandparse(testdir)
-        node = dom.find_first_by_tag("testsuite")
-        tnode = node.find_first_by_tag("testcase")
-        fnode = tnode.find_first_by_tag("failure")
-        fnode.assert_attr(message="AssertionError: An error assert 0")
-
-    def test_failure_escape(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.parametrize('arg1', "<&'", ids="<&'")
-            def test_func(arg1):
-                print(arg1)
-                assert 0
-        """)
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(failures=3, tests=3)
-
-        for index, char in enumerate("<&'"):
-
-            tnode = node.find_nth_by_tag("testcase", index)
-            tnode.assert_attr(
-                file="test_failure_escape.py",
-                line="1",
-                classname="test_failure_escape",
-                name="test_func[%s]" % char)
-            sysout = tnode.find_first_by_tag('system-out')
-            text = sysout.text
-            assert text == '%s\n' % char
-
-    def test_junit_prefixing(self, testdir):
-        testdir.makepyfile("""
-            def test_func():
-                assert 0
-            class TestHello:
-                def test_hello(self):
-                    pass
-        """)
-        result, dom = runandparse(testdir, "--junitprefix=xyz")
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(failures=1, tests=2)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_junit_prefixing.py",
-            line="0",
-            classname="xyz.test_junit_prefixing",
-            name="test_func")
-        tnode = node.find_nth_by_tag("testcase", 1)
-        tnode.assert_attr(
-            file="test_junit_prefixing.py",
-            line="3",
-            classname="xyz.test_junit_prefixing."
-            "TestHello",
-            name="test_hello")
-
-    def test_xfailure_function(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def test_xfail():
-                pytest.xfail("42")
-        """)
-        result, dom = runandparse(testdir)
-        assert not result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(skips=1, tests=0)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_xfailure_function.py",
-            line="1",
-            classname="test_xfailure_function",
-            name="test_xfail")
-        fnode = tnode.find_first_by_tag("skipped")
-        fnode.assert_attr(message="expected test failure")
-        # assert "ValueError" in fnode.toxml()
-
-    def test_xfailure_xpass(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.xfail
-            def test_xpass():
-                pass
-        """)
-        result, dom = runandparse(testdir)
-        # assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(skips=1, tests=0)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_xfailure_xpass.py",
-            line="1",
-            classname="test_xfailure_xpass",
-            name="test_xpass")
-        fnode = tnode.find_first_by_tag("skipped")
-        fnode.assert_attr(message="xfail-marked test passes unexpectedly")
-        # assert "ValueError" in fnode.toxml()
-
-    def test_collect_error(self, testdir):
-        testdir.makepyfile("syntax error")
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(errors=1, tests=0)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_collect_error.py",
-            name="test_collect_error")
-        assert tnode["line"] is None
-        fnode = tnode.find_first_by_tag("error")
-        fnode.assert_attr(message="collection failure")
-        assert "SyntaxError" in fnode.toxml()
-
-    def test_collect_skipped(self, testdir):
-        testdir.makepyfile("import pytest; pytest.skip('xyz')")
-        result, dom = runandparse(testdir)
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(skips=1, tests=0)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(
-            file="test_collect_skipped.py",
-            name="test_collect_skipped")
-
-        # py.test doesn't give us a line here.
-        assert tnode["line"] is None
-
-        fnode = tnode.find_first_by_tag("skipped")
-        fnode.assert_attr(message="collection skipped")
-
-    def test_unicode(self, testdir):
-        value = 'hx\xc4\x85\xc4\x87\n'
-        testdir.makepyfile("""
-            # coding: latin1
-            def test_hello():
-                print (%r)
-                assert 0
-        """ % value)
-        result, dom = runandparse(testdir)
-        assert result.ret == 1
-        tnode = dom.find_first_by_tag("testcase")
-        fnode = tnode.find_first_by_tag("failure")
-        if not sys.platform.startswith("java"):
-            assert "hx" in fnode.toxml()
-
-    def test_assertion_binchars(self, testdir):
-        """this test did fail when the escaping wasnt strict"""
-        testdir.makepyfile("""
-
-            M1 = '\x01\x02\x03\x04'
-            M2 = '\x01\x02\x03\x05'
-
-            def test_str_compare():
-                assert M1 == M2
-            """)
-        result, dom = runandparse(testdir)
-        print(dom.toxml())
-
-    def test_pass_captures_stdout(self, testdir):
-        testdir.makepyfile("""
-            def test_pass():
-                print('hello-stdout')
-        """)
-        result, dom = runandparse(testdir)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
-        systemout = pnode.find_first_by_tag("system-out")
-        assert "hello-stdout" in systemout.toxml()
-
-    def test_pass_captures_stderr(self, testdir):
-        testdir.makepyfile("""
-            import sys
-            def test_pass():
-                sys.stderr.write('hello-stderr')
-        """)
-        result, dom = runandparse(testdir)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
-        systemout = pnode.find_first_by_tag("system-err")
-        assert "hello-stderr" in systemout.toxml()
-
-    def test_setup_error_captures_stdout(self, testdir):
-        testdir.makepyfile("""
-            def pytest_funcarg__arg(request):
-                print('hello-stdout')
-                raise ValueError()
-            def test_function(arg):
-                pass
-        """)
-        result, dom = runandparse(testdir)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
-        systemout = pnode.find_first_by_tag("system-out")
-        assert "hello-stdout" in systemout.toxml()
-
-    def test_setup_error_captures_stderr(self, testdir):
-        testdir.makepyfile("""
-            import sys
-            def pytest_funcarg__arg(request):
-                sys.stderr.write('hello-stderr')
-                raise ValueError()
-            def test_function(arg):
-                pass
-        """)
-        result, dom = runandparse(testdir)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
-        systemout = pnode.find_first_by_tag("system-err")
-        assert "hello-stderr" in systemout.toxml()
-
-
-def test_mangle_test_address():
-    from _pytest.junitxml import mangle_test_address
-    address = '::'.join(
-        ["a/my.py.thing.py", "Class", "()", "method", "[a-1-::]"])
-    newnames = mangle_test_address(address)
-    assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"]
-
-
-def test_dont_configure_on_slaves(tmpdir):
-    gotten = []
-
-    class FakeConfig:
-        def __init__(self):
-            self.pluginmanager = self
-            self.option = self
-
-        junitprefix = None
-        # XXX: shouldnt need tmpdir ?
-        xmlpath = str(tmpdir.join('junix.xml'))
-        register = gotten.append
-
-    fake_config = FakeConfig()
-    from _pytest import junitxml
-    junitxml.pytest_configure(fake_config)
-    assert len(gotten) == 1
-    FakeConfig.slaveinput = None
-    junitxml.pytest_configure(fake_config)
-    assert len(gotten) == 1
-
-
-class TestNonPython:
-    def test_summing_simple(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            def pytest_collect_file(path, parent):
-                if path.ext == ".xyz":
-                    return MyItem(path, parent)
-            class MyItem(pytest.Item):
-                def __init__(self, path, parent):
-                    super(MyItem, self).__init__(path.basename, parent)
-                    self.fspath = path
-                def runtest(self):
-                    raise ValueError(42)
-                def repr_failure(self, excinfo):
-                    return "custom item runtest failed"
-        """)
-        testdir.tmpdir.join("myfile.xyz").write("hello")
-        result, dom = runandparse(testdir)
-        assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        node.assert_attr(errors=0, failures=1, skips=0, tests=1)
-        tnode = node.find_first_by_tag("testcase")
-        tnode.assert_attr(name="myfile.xyz")
-        fnode = tnode.find_first_by_tag("failure")
-        fnode.assert_attr(message="custom item runtest failed")
-        assert "custom item runtest failed" in fnode.toxml()
-
-
-def test_nullbyte(testdir):
-    # A null byte can not occur in XML (see section 2.2 of the spec)
-    testdir.makepyfile("""
-        import sys
-        def test_print_nullbyte():
-            sys.stdout.write('Here the null -->' + chr(0) + '<--')
-            sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')
-            assert False
-    """)
-    xmlf = testdir.tmpdir.join('junit.xml')
-    testdir.runpytest('--junitxml=%s' % xmlf)
-    text = xmlf.read()
-    assert '\x00' not in text
-    assert '#x00' in text
-
-
-def test_nullbyte_replace(testdir):
-    # Check if the null byte gets replaced
-    testdir.makepyfile("""
-        import sys
-        def test_print_nullbyte():
-            sys.stdout.write('Here the null -->' + chr(0) + '<--')
-            sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')
-            assert False
-    """)
-    xmlf = testdir.tmpdir.join('junit.xml')
-    testdir.runpytest('--junitxml=%s' % xmlf)
-    text = xmlf.read()
-    assert '#x0' in text
-
-
-def test_invalid_xml_escape():
-    # Test some more invalid xml chars, the full range should be
-    # tested really but let's just thest the edges of the ranges
-    # intead.
-    # XXX This only tests low unicode character points for now as
-    #     there are some issues with the testing infrastructure for
-    #     the higher ones.
-    # XXX Testing 0xD (\r) is tricky as it overwrites the just written
-    #     line in the output, so we skip it too.
-    global unichr
-    try:
-        unichr(65)
-    except NameError:
-        unichr = chr
-    invalid = (0x00, 0x1, 0xB, 0xC, 0xE, 0x19, 27,  # issue #126
-               0xD800, 0xDFFF, 0xFFFE, 0x0FFFF)  # , 0x110000)
-    valid = (0x9, 0xA, 0x20, )
-    # 0xD, 0xD7FF, 0xE000, 0xFFFD, 0x10000, 0x10FFFF)
-
-    from _pytest.junitxml import bin_xml_escape
-
-    for i in invalid:
-        got = bin_xml_escape(unichr(i)).uniobj
-        if i <= 0xFF:
-            expected = '#x%02X' % i
-        else:
-            expected = '#x%04X' % i
-        assert got == expected
-    for i in valid:
-        assert chr(i) == bin_xml_escape(unichr(i)).uniobj
-
-
-def test_logxml_path_expansion(tmpdir, monkeypatch):
-    home_tilde = py.path.local(os.path.expanduser('~')).join('test.xml')
-
-    xml_tilde = LogXML('~%stest.xml' % tmpdir.sep, None)
-    assert xml_tilde.logfile == home_tilde
-
-    # this is here for when $HOME is not set correct
-    monkeypatch.setenv("HOME", tmpdir)
-    home_var = os.path.normpath(os.path.expandvars('$HOME/test.xml'))
-
-    xml_var = LogXML('$HOME%stest.xml' % tmpdir.sep, None)
-    assert xml_var.logfile == home_var
-
-
-def test_logxml_changingdir(testdir):
-    testdir.makepyfile("""
-        def test_func():
-            import os
-            os.chdir("a")
-    """)
-    testdir.tmpdir.mkdir("a")
-    result = testdir.runpytest("--junitxml=a/x.xml")
-    assert result.ret == 0
-    assert testdir.tmpdir.join("a/x.xml").check()
-
-
-def test_logxml_makedir(testdir):
-    """--junitxml should automatically create directories for the xml file"""
-    testdir.makepyfile("""
-        def test_pass():
-            pass
-    """)
-    result = testdir.runpytest("--junitxml=path/to/results.xml")
-    assert result.ret == 0
-    assert testdir.tmpdir.join("path/to/results.xml").check()
-
-
-def test_escaped_parametrized_names_xml(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.parametrize('char', ["\\x00"])
-        def test_func(char):
-            assert char
-    """)
-    result, dom = runandparse(testdir)
-    assert result.ret == 0
-    node = dom.find_first_by_tag("testcase")
-    node.assert_attr(name="test_func[#x00]")
-
-
-def test_double_colon_split_function_issue469(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.parametrize('param', ["double::colon"])
-        def test_func(param):
-            pass
-    """)
-    result, dom = runandparse(testdir)
-    assert result.ret == 0
-    node = dom.find_first_by_tag("testcase")
-    node.assert_attr(classname="test_double_colon_split_function_issue469")
-    node.assert_attr(name='test_func[double::colon]')
-
-
-def test_double_colon_split_method_issue469(testdir):
-    testdir.makepyfile("""
-        import pytest
-        class TestClass:
-            @pytest.mark.parametrize('param', ["double::colon"])
-            def test_func(self, param):
-                pass
-    """)
-    result, dom = runandparse(testdir)
-    assert result.ret == 0
-    node = dom.find_first_by_tag("testcase")
-    node.assert_attr(
-        classname="test_double_colon_split_method_issue469.TestClass")
-    node.assert_attr(name='test_func[double::colon]')
-
-
-def test_unicode_issue368(testdir):
-    path = testdir.tmpdir.join("test.xml")
-    log = LogXML(str(path), None)
-    ustr = py.builtin._totext("ВНИ!", "utf-8")
-    from _pytest.runner import BaseReport
-
-    class Report(BaseReport):
-        longrepr = ustr
-        sections = []
-        nodeid = "something"
-        location = 'tests/filename.py', 42, 'TestClass.method'
-
-    test_report = Report()
-
-    # hopefully this is not too brittle ...
-    log.pytest_sessionstart()
-    node_reporter = log._opentestcase(test_report)
-    node_reporter.append_failure(test_report)
-    node_reporter.append_collect_error(test_report)
-    node_reporter.append_collect_skipped(test_report)
-    node_reporter.append_error(test_report)
-    test_report.longrepr = "filename", 1, ustr
-    node_reporter.append_skipped(test_report)
-    test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣"
-    node_reporter.append_skipped(test_report)
-    test_report.wasxfail = ustr
-    node_reporter.append_skipped(test_report)
-    log.pytest_sessionfinish()
-
-
-def test_record_property(testdir):
-    testdir.makepyfile("""
-        import pytest
-
-        @pytest.fixture
-        def other(record_xml_property):
-            record_xml_property("bar", 1)
-        def test_record(record_xml_property, other):
-            record_xml_property("foo", "<1");
-    """)
-    result, dom = runandparse(testdir, '-rw')
-    node = dom.find_first_by_tag("testsuite")
-    tnode = node.find_first_by_tag("testcase")
-    psnode = tnode.find_first_by_tag('properties')
-    pnodes = psnode.find_by_tag('property')
-    pnodes[0].assert_attr(name="bar", value="1")
-    pnodes[1].assert_attr(name="foo", value="<1")
-    result.stdout.fnmatch_lines('*C3*test_record_property.py*experimental*')
-
-
-def test_record_property_same_name(testdir):
-    testdir.makepyfile("""
-        def test_record_with_same_name(record_xml_property):
-            record_xml_property("foo", "bar")
-            record_xml_property("foo", "baz")
-    """)
-    result, dom = runandparse(testdir, '-rw')
-    node = dom.find_first_by_tag("testsuite")
-    tnode = node.find_first_by_tag("testcase")
-    psnode = tnode.find_first_by_tag('properties')
-    pnodes = psnode.find_by_tag('property')
-    pnodes[0].assert_attr(name="foo", value="bar")
-    pnodes[1].assert_attr(name="foo", value="baz")
-
-
-def test_random_report_log_xdist(testdir):
-    """xdist calls pytest_runtest_logreport as they are executed by the slaves,
-    with nodes from several nodes overlapping, so junitxml must cope with that
-    to produce correct reports. #1064
-    """
-    pytest.importorskip('xdist')
-    testdir.makepyfile("""
-        import pytest, time
-        @pytest.mark.parametrize('i', list(range(30)))
-        def test_x(i):
-            assert i != 22
-    """)
-    _, dom = runandparse(testdir, '-n2')
-    suite_node = dom.find_first_by_tag("testsuite")
-    failed = []
-    for case_node in suite_node.find_by_tag("testcase"):
-        if case_node.find_first_by_tag('failure'):
-            failed.append(case_node['name'])
-
-    assert failed == ['test_x[22]']
-
-
-def test_runs_twice(testdir):
-    f = testdir.makepyfile('''
-        def test_pass():
-            pass
-    ''')
-
-    result, dom = runandparse(testdir, f, f)
-    assert 'INTERNALERROR' not in result.stdout.str()
-    first, second = [x['classname'] for x in dom.find_by_tag("testcase")]
-    assert first == second
-
-
-@pytest.mark.xfail(reason='hangs', run=False)
-def test_runs_twice_xdist(testdir):
-    pytest.importorskip('xdist')
-    f = testdir.makepyfile('''
-        def test_pass():
-            pass
-    ''')
-
-    result, dom = runandparse(
-        testdir, f,
-        '--dist', 'each', '--tx', '2*popen',)
-    assert 'INTERNALERROR' not in result.stdout.str()
-    first, second = [x['classname'] for x in dom.find_by_tag("testcase")]
-    assert first == second
-
-
-def test_fancy_items_regression(testdir):
-    # issue 1259
-    testdir.makeconftest("""
-        import pytest
-        class FunItem(pytest.Item):
-            def runtest(self):
-                pass
-        class NoFunItem(pytest.Item):
-            def runtest(self):
-                pass
-
-        class FunCollector(pytest.File):
-            def collect(self):
-                return [
-                    FunItem('a', self),
-                    NoFunItem('a', self),
-                    NoFunItem('b', self),
-                ]
-
-        def pytest_collect_file(path, parent):
-            if path.check(ext='.py'):
-                return FunCollector(path, parent)
-    """)
-
-    testdir.makepyfile('''
-        def test_pass():
-            pass
-    ''')
-
-    result, dom = runandparse(testdir)
-
-    assert 'INTERNALERROR' not in result.stdout.str()
-
-    items = sorted(
-        '%(classname)s %(name)s %(file)s' % x
-
-        for x in dom.find_by_tag("testcase"))
-    import pprint
-    pprint.pprint(items)
-    assert items == [
-        u'conftest a conftest.py',
-        u'conftest a conftest.py',
-        u'conftest b conftest.py',
-        u'test_fancy_items_regression a test_fancy_items_regression.py',
-        u'test_fancy_items_regression a test_fancy_items_regression.py',
-        u'test_fancy_items_regression b test_fancy_items_regression.py',
-        u'test_fancy_items_regression test_pass'
-        u' test_fancy_items_regression.py',
-    ]
diff --git a/tools/pytest/testing/test_mark.py b/tools/pytest/testing/test_mark.py
deleted file mode 100644
index aa1be6f..0000000
--- a/tools/pytest/testing/test_mark.py
+++ /dev/null
@@ -1,672 +0,0 @@
-import os
-
-import py, pytest
-from _pytest.mark import MarkGenerator as Mark
-
-class TestMark:
-    def test_markinfo_repr(self):
-        from _pytest.mark import MarkInfo
-        m = MarkInfo("hello", (1,2), {})
-        repr(m)
-
-    def test_pytest_exists_in_namespace_all(self):
-        assert 'mark' in py.test.__all__
-        assert 'mark' in pytest.__all__
-
-    def test_pytest_mark_notcallable(self):
-        mark = Mark()
-        pytest.raises((AttributeError, TypeError), mark)
-
-    def test_pytest_mark_name_starts_with_underscore(self):
-        mark = Mark()
-        pytest.raises(AttributeError, getattr, mark, '_some_name')
-
-    def test_pytest_mark_bare(self):
-        mark = Mark()
-        def f():
-            pass
-        mark.hello(f)
-        assert f.hello
-
-    def test_pytest_mark_keywords(self):
-        mark = Mark()
-        def f():
-            pass
-        mark.world(x=3, y=4)(f)
-        assert f.world
-        assert f.world.kwargs['x'] == 3
-        assert f.world.kwargs['y'] == 4
-
-    def test_apply_multiple_and_merge(self):
-        mark = Mark()
-        def f():
-            pass
-        mark.world
-        mark.world(x=3)(f)
-        assert f.world.kwargs['x'] == 3
-        mark.world(y=4)(f)
-        assert f.world.kwargs['x'] == 3
-        assert f.world.kwargs['y'] == 4
-        mark.world(y=1)(f)
-        assert f.world.kwargs['y'] == 1
-        assert len(f.world.args) == 0
-
-    def test_pytest_mark_positional(self):
-        mark = Mark()
-        def f():
-            pass
-        mark.world("hello")(f)
-        assert f.world.args[0] == "hello"
-        mark.world("world")(f)
-
-    def test_pytest_mark_positional_func_and_keyword(self):
-        mark = Mark()
-        def f():
-            raise Exception
-        m = mark.world(f, omega="hello")
-        def g():
-            pass
-        assert m(g) == g
-        assert g.world.args[0] is f
-        assert g.world.kwargs["omega"] == "hello"
-
-    def test_pytest_mark_reuse(self):
-        mark = Mark()
-        def f():
-            pass
-        w = mark.some
-        w("hello", reason="123")(f)
-        assert f.some.args[0] == "hello"
-        assert f.some.kwargs['reason'] == "123"
-        def g():
-            pass
-        w("world", reason2="456")(g)
-        assert g.some.args[0] == "world"
-        assert 'reason' not in g.some.kwargs
-        assert g.some.kwargs['reason2'] == "456"
-
-
-def test_marked_class_run_twice(testdir, request):
-    """Test fails file is run twice that contains marked class.
-    See issue#683.
-    """
-    py_file = testdir.makepyfile("""
-    import pytest
-    @pytest.mark.parametrize('abc', [1, 2, 3])
-    class Test1(object):
-        def test_1(self, abc):
-            assert abc in [1, 2, 3]
-    """)
-    file_name = os.path.basename(py_file.strpath)
-    rec = testdir.inline_run(file_name, file_name)
-    rec.assertoutcome(passed=6)
-
-
-def test_ini_markers(testdir):
-    testdir.makeini("""
-        [pytest]
-        markers =
-            a1: this is a webtest marker
-            a2: this is a smoke marker
-    """)
-    testdir.makepyfile("""
-        def test_markers(pytestconfig):
-            markers = pytestconfig.getini("markers")
-            print (markers)
-            assert len(markers) >= 2
-            assert markers[0].startswith("a1:")
-            assert markers[1].startswith("a2:")
-    """)
-    rec = testdir.inline_run()
-    rec.assertoutcome(passed=1)
-
-def test_markers_option(testdir):
-    testdir.makeini("""
-        [pytest]
-        markers =
-            a1: this is a webtest marker
-            a1some: another marker
-    """)
-    result = testdir.runpytest("--markers", )
-    result.stdout.fnmatch_lines([
-        "*a1*this is a webtest*",
-        "*a1some*another marker",
-    ])
-
-def test_markers_option_with_plugin_in_current_dir(testdir):
-    testdir.makeconftest('pytest_plugins = "flip_flop"')
-    testdir.makepyfile(flip_flop="""\
-        def pytest_configure(config):
-            config.addinivalue_line("markers", "flip:flop")
-
-        def pytest_generate_tests(metafunc):
-            try:
-                mark = metafunc.function.flipper
-            except AttributeError:
-                return
-            metafunc.parametrize("x", (10, 20))""")
-    testdir.makepyfile("""\
-        import pytest
-        @pytest.mark.flipper
-        def test_example(x):
-            assert x""")
-
-    result = testdir.runpytest("--markers")
-    result.stdout.fnmatch_lines(["*flip*flop*"])
-
-
-def test_mark_on_pseudo_function(testdir):
-    testdir.makepyfile("""
-        import pytest
-
-        @pytest.mark.r(lambda x: 0/0)
-        def test_hello():
-            pass
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-def test_strict_prohibits_unregistered_markers(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.unregisteredmark
-        def test_hello():
-            pass
-    """)
-    result = testdir.runpytest("--strict")
-    assert result.ret != 0
-    result.stdout.fnmatch_lines([
-        "*unregisteredmark*not*registered*",
-    ])
-
-@pytest.mark.parametrize("spec", [
-        ("xyz", ("test_one",)),
-        ("xyz and xyz2", ()),
-        ("xyz2", ("test_two",)),
-        ("xyz or xyz2", ("test_one", "test_two"),)
-])
-def test_mark_option(spec, testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.xyz
-        def test_one():
-            pass
-        @pytest.mark.xyz2
-        def test_two():
-            pass
-    """)
-    opt, passed_result = spec
-    rec = testdir.inline_run("-m", opt)
-    passed, skipped, fail = rec.listoutcomes()
-    passed = [x.nodeid.split("::")[-1] for x in passed]
-    assert len(passed) == len(passed_result)
-    assert list(passed) == list(passed_result)
-
-@pytest.mark.parametrize("spec", [
-        ("interface", ("test_interface",)),
-        ("not interface", ("test_nointer",)),
-])
-def test_mark_option_custom(spec, testdir):
-    testdir.makeconftest("""
-        import pytest
-        def pytest_collection_modifyitems(items):
-            for item in items:
-                if "interface" in item.nodeid:
-                    item.keywords["interface"] = pytest.mark.interface
-    """)
-    testdir.makepyfile("""
-        def test_interface():
-            pass
-        def test_nointer():
-            pass
-    """)
-    opt, passed_result = spec
-    rec = testdir.inline_run("-m", opt)
-    passed, skipped, fail = rec.listoutcomes()
-    passed = [x.nodeid.split("::")[-1] for x in passed]
-    assert len(passed) == len(passed_result)
-    assert list(passed) == list(passed_result)
-
-@pytest.mark.parametrize("spec", [
-        ("interface", ("test_interface",)),
-        ("not interface", ("test_nointer", "test_pass")),
-        ("pass", ("test_pass",)),
-        ("not pass", ("test_interface", "test_nointer")),
-])
-def test_keyword_option_custom(spec, testdir):
-    testdir.makepyfile("""
-        def test_interface():
-            pass
-        def test_nointer():
-            pass
-        def test_pass():
-            pass
-    """)
-    opt, passed_result = spec
-    rec = testdir.inline_run("-k", opt)
-    passed, skipped, fail = rec.listoutcomes()
-    passed = [x.nodeid.split("::")[-1] for x in passed]
-    assert len(passed) == len(passed_result)
-    assert list(passed) == list(passed_result)
-
-
-@pytest.mark.parametrize("spec", [
-        ("None", ("test_func[None]",)),
-        ("1.3", ("test_func[1.3]",)),
-        ("2-3", ("test_func[2-3]",))
-])
-def test_keyword_option_parametrize(spec, testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.parametrize("arg", [None, 1.3, "2-3"])
-        def test_func(arg):
-            pass
-    """)
-    opt, passed_result = spec
-    rec = testdir.inline_run("-k", opt)
-    passed, skipped, fail = rec.listoutcomes()
-    passed = [x.nodeid.split("::")[-1] for x in passed]
-    assert len(passed) == len(passed_result)
-    assert list(passed) == list(passed_result)
-
-
-def test_parametrized_collected_from_command_line(testdir):
-    """Parametrized test not collected if test named specified
-       in command line issue#649.
-    """
-    py_file = testdir.makepyfile("""
-        import pytest
-        @pytest.mark.parametrize("arg", [None, 1.3, "2-3"])
-        def test_func(arg):
-            pass
-    """)
-    file_name = os.path.basename(py_file.strpath)
-    rec = testdir.inline_run(file_name + "::" + "test_func")
-    rec.assertoutcome(passed=3)
-
-
-class TestFunctional:
-
-    def test_mark_per_function(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            @pytest.mark.hello
-            def test_hello():
-                assert hasattr(test_hello, 'hello')
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_mark_per_module(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            pytestmark = pytest.mark.hello
-            def test_func():
-                pass
-        """)
-        keywords = item.keywords
-        assert 'hello' in keywords
-
-    def test_marklist_per_class(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            class TestClass:
-                pytestmark = [pytest.mark.hello, pytest.mark.world]
-                def test_func(self):
-                    assert TestClass.test_func.hello
-                    assert TestClass.test_func.world
-        """)
-        keywords = item.keywords
-        assert 'hello' in keywords
-
-    def test_marklist_per_module(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            pytestmark = [pytest.mark.hello, pytest.mark.world]
-            class TestClass:
-                def test_func(self):
-                    assert TestClass.test_func.hello
-                    assert TestClass.test_func.world
-        """)
-        keywords = item.keywords
-        assert 'hello' in keywords
-        assert 'world' in keywords
-
-    def test_mark_per_class_decorator(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.hello
-            class TestClass:
-                def test_func(self):
-                    assert TestClass.test_func.hello
-        """)
-        keywords = item.keywords
-        assert 'hello' in keywords
-
-    def test_mark_per_class_decorator_plus_existing_dec(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.hello
-            class TestClass:
-                pytestmark = pytest.mark.world
-                def test_func(self):
-                    assert TestClass.test_func.hello
-                    assert TestClass.test_func.world
-        """)
-        keywords = item.keywords
-        assert 'hello' in keywords
-        assert 'world' in keywords
-
-    def test_merging_markers(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            pytestmark = pytest.mark.hello("pos1", x=1, y=2)
-            class TestClass:
-                # classlevel overrides module level
-                pytestmark = pytest.mark.hello(x=3)
-                @pytest.mark.hello("pos0", z=4)
-                def test_func(self):
-                    pass
-        """)
-        items, rec = testdir.inline_genitems(p)
-        item, = items
-        keywords = item.keywords
-        marker = keywords['hello']
-        assert marker.args == ("pos0", "pos1")
-        assert marker.kwargs == {'x': 1, 'y': 2, 'z': 4}
-
-        # test the new __iter__ interface
-        l = list(marker)
-        assert len(l) == 3
-        assert l[0].args == ("pos0",)
-        assert l[1].args == ()
-        assert l[2].args == ("pos1", )
-
-    @pytest.mark.xfail(reason='unfixed')
-    def test_merging_markers_deep(self, testdir):
-        # issue 199 - propagate markers into nested classes
-        p = testdir.makepyfile("""
-            import pytest
-            class TestA:
-                pytestmark = pytest.mark.a
-                def test_b(self):
-                    assert True
-                class TestC:
-                    # this one didnt get marked
-                    def test_d(self):
-                        assert True
-        """)
-        items, rec = testdir.inline_genitems(p)
-        for item in items:
-            print (item, item.keywords)
-            assert 'a' in item.keywords
-
-    def test_mark_decorator_subclass_does_not_propagate_to_base(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-
-            @pytest.mark.a
-            class Base: pass
-
-            @pytest.mark.b
-            class Test1(Base):
-                def test_foo(self): pass
-
-            class Test2(Base):
-                def test_bar(self): pass
-        """)
-        items, rec = testdir.inline_genitems(p)
-        self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',))
-
-    def test_mark_decorator_baseclasses_merged(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-
-            @pytest.mark.a
-            class Base: pass
-
-            @pytest.mark.b
-            class Base2(Base): pass
-
-            @pytest.mark.c
-            class Test1(Base2):
-                def test_foo(self): pass
-
-            class Test2(Base2):
-                @pytest.mark.d
-                def test_bar(self): pass
-        """)
-        items, rec = testdir.inline_genitems(p)
-        self.assert_markers(items, test_foo=('a', 'b', 'c'),
-                            test_bar=('a', 'b', 'd'))
-
-    def test_mark_with_wrong_marker(self, testdir):
-        reprec = testdir.inline_runsource("""
-                import pytest
-                class pytestmark:
-                    pass
-                def test_func():
-                    pass
-        """)
-        l = reprec.getfailedcollections()
-        assert len(l) == 1
-        assert "TypeError" in str(l[0].longrepr)
-
-    def test_mark_dynamically_in_funcarg(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            def pytest_funcarg__arg(request):
-                request.applymarker(pytest.mark.hello)
-            def pytest_terminal_summary(terminalreporter):
-                l = terminalreporter.stats['passed']
-                terminalreporter.writer.line("keyword: %s" % l[0].keywords)
-        """)
-        testdir.makepyfile("""
-            def test_func(arg):
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "keyword: *hello*"
-        ])
-
-    def test_merging_markers_two_functions(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            @pytest.mark.hello("pos1", z=4)
-            @pytest.mark.hello("pos0", z=3)
-            def test_func():
-                pass
-        """)
-        items, rec = testdir.inline_genitems(p)
-        item, = items
-        keywords = item.keywords
-        marker = keywords['hello']
-        l = list(marker)
-        assert len(l) == 2
-        assert l[0].args == ("pos0",)
-        assert l[1].args == ("pos1",)
-
-    def test_no_marker_match_on_unmarked_names(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            @pytest.mark.shouldmatch
-            def test_marked():
-                assert 1
-
-            def test_unmarked():
-                assert 1
-        """)
-        reprec = testdir.inline_run("-m", "test_unmarked", p)
-        passed, skipped, failed = reprec.listoutcomes()
-        assert len(passed) + len(skipped) + len(failed) == 0
-        dlist = reprec.getcalls("pytest_deselected")
-        deselected_tests = dlist[0].items
-        assert len(deselected_tests) == 2
-
-    def test_keywords_at_node_level(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.fixture(scope="session", autouse=True)
-            def some(request):
-                request.keywords["hello"] = 42
-                assert "world" not in request.keywords
-
-            @pytest.fixture(scope="function", autouse=True)
-            def funcsetup(request):
-                assert "world" in request.keywords
-                assert "hello" in  request.keywords
-
-            @pytest.mark.world
-            def test_function():
-                pass
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_keyword_added_for_session(self, testdir):
-        testdir.makeconftest("""
-            import pytest
-            def pytest_collection_modifyitems(session):
-                session.add_marker("mark1")
-                session.add_marker(pytest.mark.mark2)
-                session.add_marker(pytest.mark.mark3)
-                pytest.raises(ValueError, lambda:
-                        session.add_marker(10))
-        """)
-        testdir.makepyfile("""
-            def test_some(request):
-                assert "mark1" in request.keywords
-                assert "mark2" in request.keywords
-                assert "mark3" in request.keywords
-                assert 10 not in request.keywords
-                marker = request.node.get_marker("mark1")
-                assert marker.name == "mark1"
-                assert marker.args == ()
-                assert marker.kwargs == {}
-        """)
-        reprec = testdir.inline_run("-m", "mark1")
-        reprec.assertoutcome(passed=1)
-
-    def assert_markers(self, items, **expected):
-        """assert that given items have expected marker names applied to them.
-        expected should be a dict of (item name -> seq of expected marker names)
-
-        .. note:: this could be moved to ``testdir`` if proven to be useful
-        to other modules.
-        """
-        from _pytest.mark import MarkInfo
-        items = dict((x.name, x) for x in items)
-        for name, expected_markers in expected.items():
-            markers = items[name].keywords._markers
-            marker_names = set([name for (name, v) in markers.items()
-                                if isinstance(v, MarkInfo)])
-            assert marker_names == set(expected_markers)
-
-
-class TestKeywordSelection:
-    def test_select_simple(self, testdir):
-        file_test = testdir.makepyfile("""
-            def test_one():
-                assert 0
-            class TestClass(object):
-                def test_method_one(self):
-                    assert 42 == 43
-        """)
-        def check(keyword, name):
-            reprec = testdir.inline_run("-s", "-k", keyword, file_test)
-            passed, skipped, failed = reprec.listoutcomes()
-            assert len(failed) == 1
-            assert failed[0].nodeid.split("::")[-1] == name
-            assert len(reprec.getcalls('pytest_deselected')) == 1
-
-        for keyword in ['test_one', 'est_on']:
-            check(keyword, 'test_one')
-        check('TestClass and test', 'test_method_one')
-
-    @pytest.mark.parametrize("keyword", [
-        'xxx', 'xxx and test_2', 'TestClass', 'xxx and not test_1',
-        'TestClass and test_2', 'xxx and TestClass and test_2'])
-    def test_select_extra_keywords(self, testdir, keyword):
-        p = testdir.makepyfile(test_select="""
-            def test_1():
-                pass
-            class TestClass:
-                def test_2(self):
-                    pass
-        """)
-        testdir.makepyfile(conftest="""
-            import pytest
-            @pytest.hookimpl(hookwrapper=True)
-            def pytest_pycollect_makeitem(name):
-                outcome = yield
-                if name == "TestClass":
-                    item = outcome.get_result()
-                    item.extra_keyword_matches.add("xxx")
-        """)
-        reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword)
-        py.builtin.print_("keyword", repr(keyword))
-        passed, skipped, failed = reprec.listoutcomes()
-        assert len(passed) == 1
-        assert passed[0].nodeid.endswith("test_2")
-        dlist = reprec.getcalls("pytest_deselected")
-        assert len(dlist) == 1
-        assert dlist[0].items[0].name == 'test_1'
-
-    def test_select_starton(self, testdir):
-        threepass = testdir.makepyfile(test_threepass="""
-            def test_one(): assert 1
-            def test_two(): assert 1
-            def test_three(): assert 1
-        """)
-        reprec = testdir.inline_run("-k", "test_two:", threepass)
-        passed, skipped, failed = reprec.listoutcomes()
-        assert len(passed) == 2
-        assert not failed
-        dlist = reprec.getcalls("pytest_deselected")
-        assert len(dlist) == 1
-        item = dlist[0].items[0]
-        assert item.name == "test_one"
-
-    def test_keyword_extra(self, testdir):
-        p = testdir.makepyfile("""
-           def test_one():
-               assert 0
-           test_one.mykeyword = True
-        """)
-        reprec = testdir.inline_run("-k", "mykeyword", p)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert failed == 1
-
-    @pytest.mark.xfail
-    def test_keyword_extra_dash(self, testdir):
-        p = testdir.makepyfile("""
-           def test_one():
-               assert 0
-           test_one.mykeyword = True
-        """)
-        # with argparse the argument to an option cannot
-        # start with '-'
-        reprec = testdir.inline_run("-k", "-mykeyword", p)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert passed + skipped + failed == 0
-
-    def test_no_magic_values(self, testdir):
-        """Make sure the tests do not match on magic values,
-        no double underscored values, like '__dict__',
-        and no instance values, like '()'.
-        """
-        p = testdir.makepyfile("""
-            def test_one(): assert 1
-        """)
-        def assert_test_is_not_selected(keyword):
-            reprec = testdir.inline_run("-k", keyword, p)
-            passed, skipped, failed = reprec.countoutcomes()
-            dlist = reprec.getcalls("pytest_deselected")
-            assert passed + skipped + failed == 0
-            deselected_tests = dlist[0].items
-            assert len(deselected_tests) == 1
-
-        assert_test_is_not_selected("__")
-        assert_test_is_not_selected("()")
-
diff --git a/tools/pytest/testing/test_monkeypatch.py b/tools/pytest/testing/test_monkeypatch.py
deleted file mode 100644
index 048c942..0000000
--- a/tools/pytest/testing/test_monkeypatch.py
+++ /dev/null
@@ -1,330 +0,0 @@
-import os
-import sys
-import textwrap
-
-import pytest
-from _pytest.monkeypatch import monkeypatch as MonkeyPatch
-
-
-def pytest_funcarg__mp(request):
-    cwd = os.getcwd()
-    sys_path = list(sys.path)
-
-    def cleanup():
-        sys.path[:] = sys_path
-        os.chdir(cwd)
-
-    request.addfinalizer(cleanup)
-    return MonkeyPatch()
-
-
-def test_setattr():
-    class A:
-        x = 1
-
-    monkeypatch = MonkeyPatch()
-    pytest.raises(AttributeError, "monkeypatch.setattr(A, 'notexists', 2)")
-    monkeypatch.setattr(A, 'y', 2, raising=False)
-    assert A.y == 2
-    monkeypatch.undo()
-    assert not hasattr(A, 'y')
-
-    monkeypatch = MonkeyPatch()
-    monkeypatch.setattr(A, 'x', 2)
-    assert A.x == 2
-    monkeypatch.setattr(A, 'x', 3)
-    assert A.x == 3
-    monkeypatch.undo()
-    assert A.x == 1
-
-    A.x = 5
-    monkeypatch.undo()  # double-undo makes no modification
-    assert A.x == 5
-
-
-class TestSetattrWithImportPath:
-    def test_string_expression(self, monkeypatch):
-        monkeypatch.setattr("os.path.abspath", lambda x: "hello2")
-        assert os.path.abspath("123") == "hello2"
-
-    def test_string_expression_class(self, monkeypatch):
-        monkeypatch.setattr("_pytest.config.Config", 42)
-        import _pytest
-        assert _pytest.config.Config == 42
-
-    def test_unicode_string(self, monkeypatch):
-        monkeypatch.setattr("_pytest.config.Config", 42)
-        import _pytest
-        assert _pytest.config.Config == 42
-        monkeypatch.delattr("_pytest.config.Config")
-
-    def test_wrong_target(self, monkeypatch):
-        pytest.raises(TypeError, lambda: monkeypatch.setattr(None, None))
-
-    def test_unknown_import(self, monkeypatch):
-        pytest.raises(ImportError,
-                      lambda: monkeypatch.setattr("unkn123.classx", None))
-
-    def test_unknown_attr(self, monkeypatch):
-        pytest.raises(AttributeError,
-                      lambda: monkeypatch.setattr("os.path.qweqwe", None))
-
-    def test_unknown_attr_non_raising(self, monkeypatch):
-        # https://github.com/pytest-dev/pytest/issues/746
-        monkeypatch.setattr('os.path.qweqwe', 42, raising=False)
-        assert os.path.qweqwe == 42
-
-    def test_delattr(self, monkeypatch):
-        monkeypatch.delattr("os.path.abspath")
-        assert not hasattr(os.path, "abspath")
-        monkeypatch.undo()
-        assert os.path.abspath
-
-
-def test_delattr():
-    class A:
-        x = 1
-
-    monkeypatch = MonkeyPatch()
-    monkeypatch.delattr(A, 'x')
-    assert not hasattr(A, 'x')
-    monkeypatch.undo()
-    assert A.x == 1
-
-    monkeypatch = MonkeyPatch()
-    monkeypatch.delattr(A, 'x')
-    pytest.raises(AttributeError, "monkeypatch.delattr(A, 'y')")
-    monkeypatch.delattr(A, 'y', raising=False)
-    monkeypatch.setattr(A, 'x', 5, raising=False)
-    assert A.x == 5
-    monkeypatch.undo()
-    assert A.x == 1
-
-
-def test_setitem():
-    d = {'x': 1}
-    monkeypatch = MonkeyPatch()
-    monkeypatch.setitem(d, 'x', 2)
-    monkeypatch.setitem(d, 'y', 1700)
-    monkeypatch.setitem(d, 'y', 1700)
-    assert d['x'] == 2
-    assert d['y'] == 1700
-    monkeypatch.setitem(d, 'x', 3)
-    assert d['x'] == 3
-    monkeypatch.undo()
-    assert d['x'] == 1
-    assert 'y' not in d
-    d['x'] = 5
-    monkeypatch.undo()
-    assert d['x'] == 5
-
-
-def test_setitem_deleted_meanwhile():
-    d = {}
-    monkeypatch = MonkeyPatch()
-    monkeypatch.setitem(d, 'x', 2)
-    del d['x']
-    monkeypatch.undo()
-    assert not d
-
-
-@pytest.mark.parametrize("before", [True, False])
-def test_setenv_deleted_meanwhile(before):
-    key = "qwpeoip123"
-    if before:
-        os.environ[key] = "world"
-    monkeypatch = MonkeyPatch()
-    monkeypatch.setenv(key, 'hello')
-    del os.environ[key]
-    monkeypatch.undo()
-    if before:
-        assert os.environ[key] == "world"
-        del os.environ[key]
-    else:
-        assert key not in os.environ
-
-
-def test_delitem():
-    d = {'x': 1}
-    monkeypatch = MonkeyPatch()
-    monkeypatch.delitem(d, 'x')
-    assert 'x' not in d
-    monkeypatch.delitem(d, 'y', raising=False)
-    pytest.raises(KeyError, "monkeypatch.delitem(d, 'y')")
-    assert not d
-    monkeypatch.setitem(d, 'y', 1700)
-    assert d['y'] == 1700
-    d['hello'] = 'world'
-    monkeypatch.setitem(d, 'x', 1500)
-    assert d['x'] == 1500
-    monkeypatch.undo()
-    assert d == {'hello': 'world', 'x': 1}
-
-
-def test_setenv():
-    monkeypatch = MonkeyPatch()
-    monkeypatch.setenv('XYZ123', 2)
-    import os
-    assert os.environ['XYZ123'] == "2"
-    monkeypatch.undo()
-    assert 'XYZ123' not in os.environ
-
-
-def test_delenv():
-    name = 'xyz1234'
-    assert name not in os.environ
-    monkeypatch = MonkeyPatch()
-    pytest.raises(KeyError, "monkeypatch.delenv(%r, raising=True)" % name)
-    monkeypatch.delenv(name, raising=False)
-    monkeypatch.undo()
-    os.environ[name] = "1"
-    try:
-        monkeypatch = MonkeyPatch()
-        monkeypatch.delenv(name)
-        assert name not in os.environ
-        monkeypatch.setenv(name, "3")
-        assert os.environ[name] == "3"
-        monkeypatch.undo()
-        assert os.environ[name] == "1"
-    finally:
-        if name in os.environ:
-            del os.environ[name]
-
-
-def test_setenv_prepend():
-    import os
-    monkeypatch = MonkeyPatch()
-    monkeypatch.setenv('XYZ123', 2, prepend="-")
-    assert os.environ['XYZ123'] == "2"
-    monkeypatch.setenv('XYZ123', 3, prepend="-")
-    assert os.environ['XYZ123'] == "3-2"
-    monkeypatch.undo()
-    assert 'XYZ123' not in os.environ
-
-
-def test_monkeypatch_plugin(testdir):
-    reprec = testdir.inline_runsource("""
-        def test_method(monkeypatch):
-            assert monkeypatch.__class__.__name__ == "monkeypatch"
-    """)
-    res = reprec.countoutcomes()
-    assert tuple(res) == (1, 0, 0), res
-
-
-def test_syspath_prepend(mp):
-    old = list(sys.path)
-    mp.syspath_prepend('world')
-    mp.syspath_prepend('hello')
-    assert sys.path[0] == "hello"
-    assert sys.path[1] == "world"
-    mp.undo()
-    assert sys.path == old
-    mp.undo()
-    assert sys.path == old
-
-
-def test_syspath_prepend_double_undo(mp):
-    mp.syspath_prepend('hello world')
-    mp.undo()
-    sys.path.append('more hello world')
-    mp.undo()
-    assert sys.path[-1] == 'more hello world'
-
-
-def test_chdir_with_path_local(mp, tmpdir):
-    mp.chdir(tmpdir)
-    assert os.getcwd() == tmpdir.strpath
-
-
-def test_chdir_with_str(mp, tmpdir):
-    mp.chdir(tmpdir.strpath)
-    assert os.getcwd() == tmpdir.strpath
-
-
-def test_chdir_undo(mp, tmpdir):
-    cwd = os.getcwd()
-    mp.chdir(tmpdir)
-    mp.undo()
-    assert os.getcwd() == cwd
-
-
-def test_chdir_double_undo(mp, tmpdir):
-    mp.chdir(tmpdir.strpath)
-    mp.undo()
-    tmpdir.chdir()
-    mp.undo()
-    assert os.getcwd() == tmpdir.strpath
-
-
-def test_issue185_time_breaks(testdir):
-    testdir.makepyfile("""
-        import time
-        def test_m(monkeypatch):
-            def f():
-                raise Exception
-            monkeypatch.setattr(time, "time", f)
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines("""
-        *1 passed*
-    """)
-
-
-def test_importerror(testdir):
-    p = testdir.mkpydir("package")
-    p.join("a.py").write(textwrap.dedent("""\
-        import doesnotexist
-
-        x = 1
-    """))
-    testdir.tmpdir.join("test_importerror.py").write(textwrap.dedent("""\
-        def test_importerror(monkeypatch):
-            monkeypatch.setattr('package.a.x', 2)
-    """))
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines("""
-        *import error in package.a: No module named {0}doesnotexist{0}*
-    """.format("'" if sys.version_info > (3, 0) else ""))
-
-
-class SampleNew(object):
-    @staticmethod
-    def hello():
-        return True
-
-
-class SampleNewInherit(SampleNew):
-    pass
-
-
-class SampleOld:
-    # oldstyle on python2
-    @staticmethod
-    def hello():
-        return True
-
-
-class SampleOldInherit(SampleOld):
-    pass
-
-
-@pytest.mark.parametrize('Sample', [
-    SampleNew, SampleNewInherit,
-    SampleOld, SampleOldInherit,
-], ids=['new', 'new-inherit', 'old', 'old-inherit'])
-def test_issue156_undo_staticmethod(Sample):
-    monkeypatch = MonkeyPatch()
-
-    monkeypatch.setattr(Sample, 'hello', None)
-    assert Sample.hello is None
-
-    monkeypatch.undo()
-    assert Sample.hello()
-
-def test_issue1338_name_resolving():
-    pytest.importorskip('requests')
-    monkeypatch = MonkeyPatch()
-    try:
-         monkeypatch.delattr('requests.sessions.Session.request')
-    finally:
-        monkeypatch.undo()
\ No newline at end of file
diff --git a/tools/pytest/testing/test_nose.py b/tools/pytest/testing/test_nose.py
deleted file mode 100644
index a516238..0000000
--- a/tools/pytest/testing/test_nose.py
+++ /dev/null
@@ -1,394 +0,0 @@
-import pytest
-
-def setup_module(mod):
-    mod.nose = pytest.importorskip("nose")
-
-def test_nose_setup(testdir):
-    p = testdir.makepyfile("""
-        l = []
-        from nose.tools import with_setup
-
-        @with_setup(lambda: l.append(1), lambda: l.append(2))
-        def test_hello():
-            assert l == [1]
-
-        def test_world():
-            assert l == [1,2]
-
-        test_hello.setup = lambda: l.append(1)
-        test_hello.teardown = lambda: l.append(2)
-    """)
-    result = testdir.runpytest(p, '-p', 'nose')
-    result.assert_outcomes(passed=2)
-
-
-def test_setup_func_with_setup_decorator():
-    from _pytest.nose import call_optional
-    l = []
-    class A:
-        @pytest.fixture(autouse=True)
-        def f(self):
-            l.append(1)
-    call_optional(A(), "f")
-    assert not l
-
-
-def test_setup_func_not_callable():
-    from _pytest.nose import call_optional
-    class A:
-        f = 1
-    call_optional(A(), "f")
-
-def test_nose_setup_func(testdir):
-    p = testdir.makepyfile("""
-        from nose.tools import with_setup
-
-        l = []
-
-        def my_setup():
-            a = 1
-            l.append(a)
-
-        def my_teardown():
-            b = 2
-            l.append(b)
-
-        @with_setup(my_setup, my_teardown)
-        def test_hello():
-            print (l)
-            assert l == [1]
-
-        def test_world():
-            print (l)
-            assert l == [1,2]
-
-    """)
-    result = testdir.runpytest(p, '-p', 'nose')
-    result.assert_outcomes(passed=2)
-
-
-def test_nose_setup_func_failure(testdir):
-    p = testdir.makepyfile("""
-        from nose.tools import with_setup
-
-        l = []
-        my_setup = lambda x: 1
-        my_teardown = lambda x: 2
-
-        @with_setup(my_setup, my_teardown)
-        def test_hello():
-            print (l)
-            assert l == [1]
-
-        def test_world():
-            print (l)
-            assert l == [1,2]
-
-    """)
-    result = testdir.runpytest(p, '-p', 'nose')
-    result.stdout.fnmatch_lines([
-        "*TypeError: <lambda>()*"
-    ])
-
-
-def test_nose_setup_func_failure_2(testdir):
-    testdir.makepyfile("""
-        l = []
-
-        my_setup = 1
-        my_teardown = 2
-
-        def test_hello():
-            assert l == []
-
-        test_hello.setup = my_setup
-        test_hello.teardown = my_teardown
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-def test_nose_setup_partial(testdir):
-    pytest.importorskip("functools")
-    p = testdir.makepyfile("""
-        from functools import partial
-
-        l = []
-
-        def my_setup(x):
-            a = x
-            l.append(a)
-
-        def my_teardown(x):
-            b = x
-            l.append(b)
-
-        my_setup_partial = partial(my_setup, 1)
-        my_teardown_partial = partial(my_teardown, 2)
-
-        def test_hello():
-            print (l)
-            assert l == [1]
-
-        def test_world():
-            print (l)
-            assert l == [1,2]
-
-        test_hello.setup = my_setup_partial
-        test_hello.teardown = my_teardown_partial
-    """)
-    result = testdir.runpytest(p, '-p', 'nose')
-    result.stdout.fnmatch_lines([
-        "*2 passed*"
-    ])
-
-
-def test_nose_test_generator_fixtures(testdir):
-    p = testdir.makepyfile("""
-        # taken from nose-0.11.1 unit_tests/test_generator_fixtures.py
-        from nose.tools import eq_
-        called = []
-
-        def outer_setup():
-            called.append('outer_setup')
-
-        def outer_teardown():
-            called.append('outer_teardown')
-
-        def inner_setup():
-            called.append('inner_setup')
-
-        def inner_teardown():
-            called.append('inner_teardown')
-
-        def test_gen():
-            called[:] = []
-            for i in range(0, 5):
-                yield check, i
-
-        def check(i):
-            expect = ['outer_setup']
-            for x in range(0, i):
-                expect.append('inner_setup')
-                expect.append('inner_teardown')
-            expect.append('inner_setup')
-            eq_(called, expect)
-
-
-        test_gen.setup = outer_setup
-        test_gen.teardown = outer_teardown
-        check.setup = inner_setup
-        check.teardown = inner_teardown
-
-        class TestClass(object):
-            def setup(self):
-                print ("setup called in %s" % self)
-                self.called = ['setup']
-
-            def teardown(self):
-                print ("teardown called in %s" % self)
-                eq_(self.called, ['setup'])
-                self.called.append('teardown')
-
-            def test(self):
-                print ("test called in %s" % self)
-                for i in range(0, 5):
-                    yield self.check, i
-
-            def check(self, i):
-                print ("check called in %s" % self)
-                expect = ['setup']
-                #for x in range(0, i):
-                #    expect.append('setup')
-                #    expect.append('teardown')
-                #expect.append('setup')
-                eq_(self.called, expect)
-    """)
-    result = testdir.runpytest(p, '-p', 'nose')
-    result.stdout.fnmatch_lines([
-        "*10 passed*"
-    ])
-
-
-def test_module_level_setup(testdir):
-    testdir.makepyfile("""
-        from nose.tools import with_setup
-        items = {}
-
-        def setup():
-            items[1]=1
-
-        def teardown():
-            del items[1]
-
-        def setup2():
-            items[2] = 2
-
-        def teardown2():
-            del items[2]
-
-        def test_setup_module_setup():
-            assert items[1] == 1
-
-        @with_setup(setup2, teardown2)
-        def test_local_setup():
-            assert items[2] == 2
-            assert 1 not in items
-    """)
-    result = testdir.runpytest('-p', 'nose')
-    result.stdout.fnmatch_lines([
-        "*2 passed*",
-    ])
-
-
-def test_nose_style_setup_teardown(testdir):
-    testdir.makepyfile("""
-        l = []
-
-        def setup_module():
-            l.append(1)
-
-        def teardown_module():
-            del l[0]
-
-        def test_hello():
-            assert l == [1]
-
-        def test_world():
-            assert l == [1]
-        """)
-    result = testdir.runpytest('-p', 'nose')
-    result.stdout.fnmatch_lines([
-        "*2 passed*",
-    ])
-
-def test_nose_setup_ordering(testdir):
-    testdir.makepyfile("""
-        def setup_module(mod):
-            mod.visited = True
-
-        class TestClass:
-            def setup(self):
-                assert visited
-            def test_first(self):
-                pass
-        """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*1 passed*",
-    ])
-
-
-def test_apiwrapper_problem_issue260(testdir):
-    # this would end up trying a call a optional teardown on the class
-    # for plain unittests we dont want nose behaviour
-    testdir.makepyfile("""
-        import unittest
-        class TestCase(unittest.TestCase):
-            def setup(self):
-                #should not be called in unittest testcases
-                assert 0, 'setup'
-            def teardown(self):
-                #should not be called in unittest testcases
-                assert 0, 'teardown'
-            def setUp(self):
-                print('setup')
-            def tearDown(self):
-                print('teardown')
-            def test_fun(self):
-                pass
-        """)
-    result = testdir.runpytest()
-    result.assert_outcomes(passed=1)
-
-def test_setup_teardown_linking_issue265(testdir):
-    # we accidentally didnt integrate nose setupstate with normal setupstate
-    # this test ensures that won't happen again
-    testdir.makepyfile('''
-        import pytest
-
-        class TestGeneric(object):
-            def test_nothing(self):
-                """Tests the API of the implementation (for generic and specialized)."""
-
-        @pytest.mark.skipif("True", reason=
-                    "Skip tests to check if teardown is skipped as well.")
-        class TestSkipTeardown(TestGeneric):
-
-            def setup(self):
-                """Sets up my specialized implementation for $COOL_PLATFORM."""
-                raise Exception("should not call setup for skipped tests")
-
-            def teardown(self):
-                """Undoes the setup."""
-                raise Exception("should not call teardown for skipped tests")
-        ''')
-    reprec = testdir.runpytest()
-    reprec.assert_outcomes(passed=1, skipped=1)
-
-
-def test_SkipTest_during_collection(testdir):
-    p = testdir.makepyfile("""
-        import nose
-        raise nose.SkipTest("during collection")
-        def test_failing():
-            assert False
-        """)
-    result = testdir.runpytest(p)
-    result.assert_outcomes(skipped=1)
-
-
-def test_SkipTest_in_test(testdir):
-    testdir.makepyfile("""
-        import nose
-
-        def test_skipping():
-            raise nose.SkipTest("in test")
-        """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(skipped=1)
-
-def test_istest_function_decorator(testdir):
-    p = testdir.makepyfile("""
-        import nose.tools
-        @nose.tools.istest
-        def not_test_prefix():
-            pass
-        """)
-    result = testdir.runpytest(p)
-    result.assert_outcomes(passed=1)
-
-def test_nottest_function_decorator(testdir):
-    testdir.makepyfile("""
-        import nose.tools
-        @nose.tools.nottest
-        def test_prefix():
-            pass
-        """)
-    reprec = testdir.inline_run()
-    assert not reprec.getfailedcollections()
-    calls = reprec.getreports("pytest_runtest_logreport")
-    assert not calls
-
-def test_istest_class_decorator(testdir):
-    p = testdir.makepyfile("""
-        import nose.tools
-        @nose.tools.istest
-        class NotTestPrefix:
-            def test_method(self):
-                pass
-        """)
-    result = testdir.runpytest(p)
-    result.assert_outcomes(passed=1)
-
-def test_nottest_class_decorator(testdir):
-    testdir.makepyfile("""
-        import nose.tools
-        @nose.tools.nottest
-        class TestPrefix:
-            def test_method(self):
-                pass
-        """)
-    reprec = testdir.inline_run()
-    assert not reprec.getfailedcollections()
-    calls = reprec.getreports("pytest_runtest_logreport")
-    assert not calls
diff --git a/tools/pytest/testing/test_parseopt.py b/tools/pytest/testing/test_parseopt.py
deleted file mode 100644
index e45ee28..0000000
--- a/tools/pytest/testing/test_parseopt.py
+++ /dev/null
@@ -1,287 +0,0 @@
-from __future__ import with_statement
-import sys
-import os
-import py, pytest
-from _pytest import config as parseopt
-
-@pytest.fixture
-def parser():
-    return parseopt.Parser()
-
-class TestParser:
-    def test_no_help_by_default(self, capsys):
-        parser = parseopt.Parser(usage="xyz")
-        pytest.raises(SystemExit, lambda: parser.parse(["-h"]))
-        out, err = capsys.readouterr()
-        assert err.find("error: unrecognized arguments") != -1
-
-    def test_argument(self):
-        with pytest.raises(parseopt.ArgumentError):
-            # need a short or long option
-            argument = parseopt.Argument()
-        argument = parseopt.Argument('-t')
-        assert argument._short_opts == ['-t']
-        assert argument._long_opts == []
-        assert argument.dest == 't'
-        argument = parseopt.Argument('-t', '--test')
-        assert argument._short_opts == ['-t']
-        assert argument._long_opts == ['--test']
-        assert argument.dest == 'test'
-        argument = parseopt.Argument('-t', '--test', dest='abc')
-        assert argument.dest == 'abc'
-
-    def test_argument_type(self):
-        argument = parseopt.Argument('-t', dest='abc', type='int')
-        assert argument.type is int
-        argument = parseopt.Argument('-t', dest='abc', type='string')
-        assert argument.type is str
-        argument = parseopt.Argument('-t', dest='abc', type=float)
-        assert argument.type is float
-        with pytest.raises(KeyError):
-            argument = parseopt.Argument('-t', dest='abc', type='choice')
-        argument = parseopt.Argument('-t', dest='abc', type='choice',
-                                     choices=['red', 'blue'])
-        assert argument.type is str
-
-    def test_argument_processopt(self):
-        argument = parseopt.Argument('-t', type=int)
-        argument.default = 42
-        argument.dest = 'abc'
-        res = argument.attrs()
-        assert res['default'] == 42
-        assert res['dest'] == 'abc'
-
-    def test_group_add_and_get(self, parser):
-        group = parser.getgroup("hello", description="desc")
-        assert group.name == "hello"
-        assert group.description == "desc"
-
-    def test_getgroup_simple(self, parser):
-        group = parser.getgroup("hello", description="desc")
-        assert group.name == "hello"
-        assert group.description == "desc"
-        group2 = parser.getgroup("hello")
-        assert group2 is group
-
-    def test_group_ordering(self, parser):
-        parser.getgroup("1")
-        parser.getgroup("2")
-        parser.getgroup("3", after="1")
-        groups = parser._groups
-        groups_names = [x.name for x in groups]
-        assert groups_names == list("132")
-
-    def test_group_addoption(self):
-        group = parseopt.OptionGroup("hello")
-        group.addoption("--option1", action="store_true")
-        assert len(group.options) == 1
-        assert isinstance(group.options[0], parseopt.Argument)
-
-    def test_group_shortopt_lowercase(self, parser):
-        group = parser.getgroup("hello")
-        pytest.raises(ValueError, """
-            group.addoption("-x", action="store_true")
-        """)
-        assert len(group.options) == 0
-        group._addoption("-x", action="store_true")
-        assert len(group.options) == 1
-
-    def test_parser_addoption(self, parser):
-        group = parser.getgroup("custom options")
-        assert len(group.options) == 0
-        group.addoption("--option1", action="store_true")
-        assert len(group.options) == 1
-
-    def test_parse(self, parser):
-        parser.addoption("--hello", dest="hello", action="store")
-        args = parser.parse(['--hello', 'world'])
-        assert args.hello == "world"
-        assert not getattr(args, parseopt.FILE_OR_DIR)
-
-    def test_parse2(self, parser):
-        args = parser.parse([py.path.local()])
-        assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local()
-
-    def test_parse_known_args(self, parser):
-        parser.parse_known_args([py.path.local()])
-        parser.addoption("--hello", action="store_true")
-        ns = parser.parse_known_args(["x", "--y", "--hello", "this"])
-        assert ns.hello
-        assert ns.file_or_dir == ['x']
-
-    def test_parse_known_and_unknown_args(self, parser):
-        parser.addoption("--hello", action="store_true")
-        ns, unknown = parser.parse_known_and_unknown_args(["x", "--y",
-                                                           "--hello", "this"])
-        assert ns.hello
-        assert ns.file_or_dir == ['x']
-        assert unknown == ['--y', 'this']
-
-    def test_parse_will_set_default(self, parser):
-        parser.addoption("--hello", dest="hello", default="x", action="store")
-        option = parser.parse([])
-        assert option.hello == "x"
-        del option.hello
-        parser.parse_setoption([], option)
-        assert option.hello == "x"
-
-    def test_parse_setoption(self, parser):
-        parser.addoption("--hello", dest="hello", action="store")
-        parser.addoption("--world", dest="world", default=42)
-        class A: pass
-        option = A()
-        args = parser.parse_setoption(['--hello', 'world'], option)
-        assert option.hello == "world"
-        assert option.world == 42
-        assert not args
-
-    def test_parse_special_destination(self, parser):
-        parser.addoption("--ultimate-answer", type=int)
-        args = parser.parse(['--ultimate-answer', '42'])
-        assert args.ultimate_answer == 42
-
-    def test_parse_split_positional_arguments(self, parser):
-        parser.addoption("-R", action='store_true')
-        parser.addoption("-S", action='store_false')
-        args = parser.parse(['-R', '4', '2', '-S'])
-        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
-        args = parser.parse(['-R', '-S', '4', '2', '-R'])
-        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
-        assert args.R == True
-        assert args.S == False
-        args = parser.parse(['-R', '4', '-S', '2'])
-        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
-        assert args.R == True
-        assert args.S == False
-
-    def test_parse_defaultgetter(self):
-        def defaultget(option):
-            if not hasattr(option, 'type'):
-                return
-            if option.type is int:
-                option.default = 42
-            elif option.type is str:
-                option.default = "world"
-        parser = parseopt.Parser(processopt=defaultget)
-        parser.addoption("--this", dest="this", type="int", action="store")
-        parser.addoption("--hello", dest="hello", type="string", action="store")
-        parser.addoption("--no", dest="no", action="store_true")
-        option = parser.parse([])
-        assert option.hello == "world"
-        assert option.this == 42
-        assert option.no is False
-
-    def test_drop_short_helper(self):
-        parser = py.std.argparse.ArgumentParser(formatter_class=parseopt.DropShorterLongHelpFormatter)
-        parser.add_argument('-t', '--twoword', '--duo', '--two-word', '--two',
-                             help='foo').map_long_option = {'two': 'two-word'}
-        # throws error on --deux only!
-        parser.add_argument('-d', '--deuxmots', '--deux-mots',
-                            action='store_true', help='foo').map_long_option = {'deux': 'deux-mots'}
-        parser.add_argument('-s', action='store_true', help='single short')
-        parser.add_argument('--abc', '-a',
-                            action='store_true', help='bar')
-        parser.add_argument('--klm', '-k', '--kl-m',
-                            action='store_true', help='bar')
-        parser.add_argument('-P', '--pq-r', '-p', '--pqr',
-                            action='store_true', help='bar')
-        parser.add_argument('--zwei-wort', '--zweiwort', '--zweiwort',
-                            action='store_true', help='bar')
-        parser.add_argument('-x', '--exit-on-first', '--exitfirst',
-                            action='store_true', help='spam').map_long_option = {'exitfirst': 'exit-on-first'}
-        parser.add_argument('files_and_dirs', nargs='*')
-        args = parser.parse_args(['-k', '--duo', 'hallo', '--exitfirst'])
-        assert args.twoword == 'hallo'
-        assert args.klm is True
-        assert args.zwei_wort is False
-        assert args.exit_on_first is True
-        assert args.s is False
-        args = parser.parse_args(['--deux-mots'])
-        with pytest.raises(AttributeError):
-            assert args.deux_mots is True
-        assert args.deuxmots is True
-        args = parser.parse_args(['file', 'dir'])
-        assert '|'.join(args.files_and_dirs) == 'file|dir'
-
-    def test_drop_short_0(self, parser):
-        parser.addoption('--funcarg', '--func-arg', action='store_true')
-        parser.addoption('--abc-def', '--abc-def', action='store_true')
-        parser.addoption('--klm-hij', action='store_true')
-        args = parser.parse(['--funcarg', '--k'])
-        assert args.funcarg is True
-        assert args.abc_def is False
-        assert args.klm_hij is True
-
-    def test_drop_short_2(self, parser):
-        parser.addoption('--func-arg', '--doit', action='store_true')
-        args = parser.parse(['--doit'])
-        assert args.func_arg is True
-
-    def test_drop_short_3(self, parser):
-        parser.addoption('--func-arg', '--funcarg', '--doit', action='store_true')
-        args = parser.parse(['abcd'])
-        assert args.func_arg is False
-        assert args.file_or_dir == ['abcd']
-
-    def test_drop_short_help0(self, parser, capsys):
-        parser.addoption('--func-args', '--doit', help = 'foo',
-                         action='store_true')
-        parser.parse([])
-        help = parser.optparser.format_help()
-        assert '--func-args, --doit  foo' in  help
-
-    # testing would be more helpful with all help generated
-    def test_drop_short_help1(self, parser, capsys):
-        group = parser.getgroup("general")
-        group.addoption('--doit', '--func-args', action='store_true', help='foo')
-        group._addoption("-h", "--help", action="store_true", dest="help",
-                help="show help message and configuration info")
-        parser.parse(['-h'])
-        help = parser.optparser.format_help()
-        assert '-doit, --func-args  foo' in  help
-
-
-def test_argcomplete(testdir, monkeypatch):
-    if not py.path.local.sysfind('bash'):
-        pytest.skip("bash not available")
-    script = str(testdir.tmpdir.join("test_argcomplete"))
-    pytest_bin = sys.argv[0]
-    if "py.test" not in os.path.basename(pytest_bin):
-        pytest.skip("need to be run with py.test executable, not %s" %(pytest_bin,))
-
-    with open(str(script), 'w') as fp:
-        # redirect output from argcomplete to stdin and stderr is not trivial
-        # http://stackoverflow.com/q/12589419/1307905
-        # so we use bash
-        fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" %s 8>&1 9>&2' % pytest_bin)
-    # alternative would be exteneded Testdir.{run(),_run(),popen()} to be able
-    # to handle a keyword argument env that replaces os.environ in popen or
-    # extends the copy, advantage: could not forget to restore
-    monkeypatch.setenv('_ARGCOMPLETE', "1")
-    monkeypatch.setenv('_ARGCOMPLETE_IFS',"\x0b")
-    monkeypatch.setenv('COMP_WORDBREAKS', ' \\t\\n"\\\'><=;|&(:')
-
-    arg = '--fu'
-    monkeypatch.setenv('COMP_LINE', "py.test " + arg)
-    monkeypatch.setenv('COMP_POINT', str(len("py.test " + arg)))
-    result = testdir.run('bash', str(script), arg)
-    if result.ret == 255:
-        # argcomplete not found
-        pytest.skip("argcomplete not available")
-    elif not result.stdout.str():
-        pytest.skip("bash provided no output, argcomplete not available?")
-    else:
-        if py.std.sys.version_info < (2,7):
-            result.stdout.lines = result.stdout.lines[0].split('\x0b')
-            result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
-        else:
-            result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
-    if py.std.sys.version_info < (2,7):
-        return
-    os.mkdir('test_argcomplete.d')
-    arg = 'test_argc'
-    monkeypatch.setenv('COMP_LINE', "py.test " + arg)
-    monkeypatch.setenv('COMP_POINT', str(len('py.test ' + arg)))
-    result = testdir.run('bash', str(script), arg)
-    result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"])
-
diff --git a/tools/pytest/testing/test_pastebin.py b/tools/pytest/testing/test_pastebin.py
deleted file mode 100644
index 03570a5..0000000
--- a/tools/pytest/testing/test_pastebin.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# encoding: utf-8
-import sys
-import pytest
-
-class TestPasteCapture:
-
-    @pytest.fixture
-    def pastebinlist(self, monkeypatch, request):
-        pastebinlist = []
-        plugin = request.config.pluginmanager.getplugin('pastebin')
-        monkeypatch.setattr(plugin, 'create_new_paste', pastebinlist.append)
-        return pastebinlist
-
-    def test_failed(self, testdir, pastebinlist):
-        testpath = testdir.makepyfile("""
-            import pytest
-            def test_pass():
-                pass
-            def test_fail():
-                assert 0
-            def test_skip():
-                pytest.skip("")
-        """)
-        reprec = testdir.inline_run(testpath, "--paste=failed")
-        assert len(pastebinlist) == 1
-        s = pastebinlist[0]
-        assert s.find("def test_fail") != -1
-        assert reprec.countoutcomes() == [1,1,1]
-
-    def test_all(self, testdir, pastebinlist):
-        from _pytest.pytester import LineMatcher
-        testpath = testdir.makepyfile("""
-            import pytest
-            def test_pass():
-                pass
-            def test_fail():
-                assert 0
-            def test_skip():
-                pytest.skip("")
-        """)
-        reprec = testdir.inline_run(testpath, "--pastebin=all", '-v')
-        assert reprec.countoutcomes() == [1,1,1]
-        assert len(pastebinlist) == 1
-        contents = pastebinlist[0].decode('utf-8')
-        matcher = LineMatcher(contents.splitlines())
-        matcher.fnmatch_lines([
-            '*test_pass PASSED*',
-            '*test_fail FAILED*',
-            '*test_skip SKIPPED*',
-            '*== 1 failed, 1 passed, 1 skipped in *'
-        ])
-
-    def test_non_ascii_paste_text(self, testdir):
-        """Make sure that text which contains non-ascii characters is pasted
-        correctly. See #1219.
-        """
-        testdir.makepyfile(test_unicode="""
-            # encoding: utf-8
-            def test():
-                assert '☺' == 1
-        """)
-        result = testdir.runpytest('--pastebin=all')
-        if sys.version_info[0] == 3:
-            expected_msg = "*assert '☺' == 1*"
-        else:
-            expected_msg = "*assert '\\xe2\\x98\\xba' == 1*"
-        result.stdout.fnmatch_lines([
-            expected_msg,
-            "*== 1 failed in *",
-            '*Sending information to Paste Service*',
-        ])
-
-
-class TestPaste:
-
-    @pytest.fixture
-    def pastebin(self, request):
-        return request.config.pluginmanager.getplugin('pastebin')
-
-    @pytest.fixture
-    def mocked_urlopen(self, monkeypatch):
-        """
-        monkeypatch the actual urlopen calls done by the internal plugin
-        function that connects to bpaste service.
-        """
-        calls = []
-        def mocked(url, data):
-            calls.append((url, data))
-            class DummyFile:
-                def read(self):
-                    # part of html of a normal response
-                    return b'View <a href="/raw/3c0c6750bd">raw</a>.'
-            return DummyFile()
-
-        if sys.version_info < (3, 0):
-            import urllib
-            monkeypatch.setattr(urllib, 'urlopen', mocked)
-        else:
-            import urllib.request
-            monkeypatch.setattr(urllib.request, 'urlopen', mocked)
-        return calls
-
-    def test_create_new_paste(self, pastebin, mocked_urlopen):
-        result = pastebin.create_new_paste(b'full-paste-contents')
-        assert result == 'https://bpaste.net/show/3c0c6750bd'
-        assert len(mocked_urlopen) == 1
-        url, data = mocked_urlopen[0]
-        assert type(data) is bytes
-        lexer = 'python3' if sys.version_info[0] == 3 else 'python'
-        assert url == 'https://bpaste.net'
-        assert 'lexer=%s' % lexer in data.decode()
-        assert 'code=full-paste-contents' in data.decode()
-        assert 'expiry=1week' in data.decode()
-
-
diff --git a/tools/pytest/testing/test_pdb.py b/tools/pytest/testing/test_pdb.py
deleted file mode 100644
index eeddcf0..0000000
--- a/tools/pytest/testing/test_pdb.py
+++ /dev/null
@@ -1,313 +0,0 @@
-import sys
-
-import _pytest._code
-
-
-def runpdb_and_get_report(testdir, source):
-    p = testdir.makepyfile(source)
-    result = testdir.runpytest_inprocess("--pdb", p)
-    reports = result.reprec.getreports("pytest_runtest_logreport")
-    assert len(reports) == 3, reports # setup/call/teardown
-    return reports[1]
-
-
-class TestPDB:
-    def pytest_funcarg__pdblist(self, request):
-        monkeypatch = request.getfuncargvalue("monkeypatch")
-        pdblist = []
-        def mypdb(*args):
-            pdblist.append(args)
-        plugin = request.config.pluginmanager.getplugin('pdb')
-        monkeypatch.setattr(plugin, 'post_mortem', mypdb)
-        return pdblist
-
-    def test_pdb_on_fail(self, testdir, pdblist):
-        rep = runpdb_and_get_report(testdir, """
-            def test_func():
-                assert 0
-        """)
-        assert rep.failed
-        assert len(pdblist) == 1
-        tb = _pytest._code.Traceback(pdblist[0][0])
-        assert tb[-1].name == "test_func"
-
-    def test_pdb_on_xfail(self, testdir, pdblist):
-        rep = runpdb_and_get_report(testdir, """
-            import pytest
-            @pytest.mark.xfail
-            def test_func():
-                assert 0
-        """)
-        assert "xfail" in rep.keywords
-        assert not pdblist
-
-    def test_pdb_on_skip(self, testdir, pdblist):
-        rep = runpdb_and_get_report(testdir, """
-            import pytest
-            def test_func():
-                pytest.skip("hello")
-        """)
-        assert rep.skipped
-        assert len(pdblist) == 0
-
-    def test_pdb_on_BdbQuit(self, testdir, pdblist):
-        rep = runpdb_and_get_report(testdir, """
-            import bdb
-            def test_func():
-                raise bdb.BdbQuit
-        """)
-        assert rep.failed
-        assert len(pdblist) == 0
-
-    def test_pdb_interaction(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_1():
-                i = 0
-                assert i == 1
-        """)
-        child = testdir.spawn_pytest("--pdb %s" % p1)
-        child.expect(".*def test_1")
-        child.expect(".*i = 0")
-        child.expect("(Pdb)")
-        child.sendeof()
-        rest = child.read().decode("utf8")
-        assert "1 failed" in rest
-        assert "def test_1" not in rest
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_interaction_capture(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_1():
-                print("getrekt")
-                assert False
-        """)
-        child = testdir.spawn_pytest("--pdb %s" % p1)
-        child.expect("getrekt")
-        child.expect("(Pdb)")
-        child.sendeof()
-        rest = child.read().decode("utf8")
-        assert "1 failed" in rest
-        assert "getrekt" not in rest
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_interaction_exception(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            def globalfunc():
-                pass
-            def test_1():
-                pytest.raises(ValueError, globalfunc)
-        """)
-        child = testdir.spawn_pytest("--pdb %s" % p1)
-        child.expect(".*def test_1")
-        child.expect(".*pytest.raises.*globalfunc")
-        child.expect("(Pdb)")
-        child.sendline("globalfunc")
-        child.expect(".*function")
-        child.sendeof()
-        child.expect("1 failed")
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_interaction_on_collection_issue181(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            xxx
-        """)
-        child = testdir.spawn_pytest("--pdb %s" % p1)
-        #child.expect(".*import pytest.*")
-        child.expect("(Pdb)")
-        child.sendeof()
-        child.expect("1 error")
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_interaction_on_internal_error(self, testdir):
-        testdir.makeconftest("""
-            def pytest_runtest_protocol():
-                0/0
-        """)
-        p1 = testdir.makepyfile("def test_func(): pass")
-        child = testdir.spawn_pytest("--pdb %s" % p1)
-        #child.expect(".*import pytest.*")
-        child.expect("(Pdb)")
-        child.sendeof()
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_interaction_capturing_simple(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            def test_1():
-                i = 0
-                print ("hello17")
-                pytest.set_trace()
-                x = 3
-        """)
-        child = testdir.spawn_pytest(str(p1))
-        child.expect("test_1")
-        child.expect("x = 3")
-        child.expect("(Pdb)")
-        child.sendeof()
-        rest = child.read().decode("utf-8")
-        assert "1 failed" in rest
-        assert "def test_1" in rest
-        assert "hello17" in rest # out is captured
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_set_trace_interception(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pdb
-            def test_1():
-                pdb.set_trace()
-        """)
-        child = testdir.spawn_pytest(str(p1))
-        child.expect("test_1")
-        child.expect("(Pdb)")
-        child.sendeof()
-        rest = child.read().decode("utf8")
-        assert "1 failed" in rest
-        assert "reading from stdin while output" not in rest
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_and_capsys(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            def test_1(capsys):
-                print ("hello1")
-                pytest.set_trace()
-        """)
-        child = testdir.spawn_pytest(str(p1))
-        child.expect("test_1")
-        child.send("capsys.readouterr()\n")
-        child.expect("hello1")
-        child.sendeof()
-        child.read()
-        if child.isalive():
-            child.wait()
-
-    def test_set_trace_capturing_afterwards(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pdb
-            def test_1():
-                pdb.set_trace()
-            def test_2():
-                print ("hello")
-                assert 0
-        """)
-        child = testdir.spawn_pytest(str(p1))
-        child.expect("test_1")
-        child.send("c\n")
-        child.expect("test_2")
-        child.expect("Captured")
-        child.expect("hello")
-        child.sendeof()
-        child.read()
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_interaction_doctest(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            def function_1():
-                '''
-                >>> i = 0
-                >>> assert i == 1
-                '''
-        """)
-        child = testdir.spawn_pytest("--doctest-modules --pdb %s" % p1)
-        child.expect("(Pdb)")
-        child.sendline('i')
-        child.expect("0")
-        child.expect("(Pdb)")
-        child.sendeof()
-        rest = child.read().decode("utf8")
-        assert "1 failed" in rest
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_interaction_capturing_twice(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            def test_1():
-                i = 0
-                print ("hello17")
-                pytest.set_trace()
-                x = 3
-                print ("hello18")
-                pytest.set_trace()
-                x = 4
-        """)
-        child = testdir.spawn_pytest(str(p1))
-        child.expect("test_1")
-        child.expect("x = 3")
-        child.expect("(Pdb)")
-        child.sendline('c')
-        child.expect("x = 4")
-        child.sendeof()
-        rest = child.read().decode("utf8")
-        assert "1 failed" in rest
-        assert "def test_1" in rest
-        assert "hello17" in rest # out is captured
-        assert "hello18" in rest # out is captured
-        if child.isalive():
-            child.wait()
-
-    def test_pdb_used_outside_test(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            pytest.set_trace()
-            x = 5
-        """)
-        child = testdir.spawn("%s %s" %(sys.executable, p1))
-        child.expect("x = 5")
-        child.sendeof()
-        child.wait()
-
-    def test_pdb_used_in_generate_tests(self, testdir):
-        p1 = testdir.makepyfile("""
-            import pytest
-            def pytest_generate_tests(metafunc):
-                pytest.set_trace()
-                x = 5
-            def test_foo(a):
-                pass
-        """)
-        child = testdir.spawn_pytest(str(p1))
-        child.expect("x = 5")
-        child.sendeof()
-        child.wait()
-
-    def test_pdb_collection_failure_is_shown(self, testdir):
-        p1 = testdir.makepyfile("""xxx """)
-        result = testdir.runpytest_subprocess("--pdb", p1)
-        result.stdout.fnmatch_lines([
-            "*NameError*xxx*",
-            "*1 error*",
-        ])
-
-    def test_enter_pdb_hook_is_called(self, testdir):
-        testdir.makeconftest("""
-            def pytest_enter_pdb(config):
-                assert config.testing_verification == 'configured'
-                print 'enter_pdb_hook'
-
-            def pytest_configure(config):
-                config.testing_verification = 'configured'
-        """)
-        p1 = testdir.makepyfile("""
-            import pytest
-
-            def test_foo():
-                pytest.set_trace()
-        """)
-        child = testdir.spawn_pytest(str(p1))
-        child.expect("enter_pdb_hook")
-        child.send('c\n')
-        child.sendeof()
-        if child.isalive():
-            child.wait()
diff --git a/tools/pytest/testing/test_pluginmanager.py b/tools/pytest/testing/test_pluginmanager.py
deleted file mode 100644
index 3684763..0000000
--- a/tools/pytest/testing/test_pluginmanager.py
+++ /dev/null
@@ -1,340 +0,0 @@
-import pytest
-import py
-import os
-
-from _pytest.config import get_config, PytestPluginManager
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-
-@pytest.fixture
-def pytestpm():
-    return PytestPluginManager()
-
-class TestPytestPluginInteractions:
-    def test_addhooks_conftestplugin(self, testdir):
-        testdir.makepyfile(newhooks="""
-            def pytest_myhook(xyz):
-                "new hook"
-        """)
-        conf = testdir.makeconftest("""
-            import sys ; sys.path.insert(0, '.')
-            import newhooks
-            def pytest_addhooks(pluginmanager):
-                pluginmanager.addhooks(newhooks)
-            def pytest_myhook(xyz):
-                return xyz + 1
-        """)
-        config = get_config()
-        pm = config.pluginmanager
-        pm.hook.pytest_addhooks.call_historic(
-                                kwargs=dict(pluginmanager=config.pluginmanager))
-        config.pluginmanager._importconftest(conf)
-        #print(config.pluginmanager.get_plugins())
-        res = config.hook.pytest_myhook(xyz=10)
-        assert res == [11]
-
-    def test_addhooks_nohooks(self, testdir):
-        testdir.makeconftest("""
-            import sys
-            def pytest_addhooks(pluginmanager):
-                pluginmanager.addhooks(sys)
-        """)
-        res = testdir.runpytest()
-        assert res.ret != 0
-        res.stderr.fnmatch_lines([
-            "*did not find*sys*"
-        ])
-
-    def test_namespace_early_from_import(self, testdir):
-        p = testdir.makepyfile("""
-            from pytest import Item
-            from pytest import Item as Item2
-            assert Item is Item2
-        """)
-        result = testdir.runpython(p)
-        assert result.ret == 0
-
-    def test_do_ext_namespace(self, testdir):
-        testdir.makeconftest("""
-            def pytest_namespace():
-                return {'hello': 'world'}
-        """)
-        p = testdir.makepyfile("""
-            from pytest import hello
-            import pytest
-            def test_hello():
-                assert hello == "world"
-                assert 'hello' in pytest.__all__
-        """)
-        reprec = testdir.inline_run(p)
-        reprec.assertoutcome(passed=1)
-
-    def test_do_option_postinitialize(self, testdir):
-        config = testdir.parseconfigure()
-        assert not hasattr(config.option, 'test123')
-        p = testdir.makepyfile("""
-            def pytest_addoption(parser):
-                parser.addoption('--test123', action="store_true",
-                    default=True)
-        """)
-        config.pluginmanager._importconftest(p)
-        assert config.option.test123
-
-    def test_configure(self, testdir):
-        config = testdir.parseconfig()
-        l = []
-        class A:
-            def pytest_configure(self, config):
-                l.append(self)
-
-        config.pluginmanager.register(A())
-        assert len(l) == 0
-        config._do_configure()
-        assert len(l) == 1
-        config.pluginmanager.register(A())  # leads to a configured() plugin
-        assert len(l) == 2
-        assert l[0] != l[1]
-
-        config._ensure_unconfigure()
-        config.pluginmanager.register(A())
-        assert len(l) == 2
-
-    def test_hook_tracing(self):
-        pytestpm = get_config().pluginmanager  # fully initialized with plugins
-        saveindent = []
-        class api1:
-            def pytest_plugin_registered(self):
-                saveindent.append(pytestpm.trace.root.indent)
-        class api2:
-            def pytest_plugin_registered(self):
-                saveindent.append(pytestpm.trace.root.indent)
-                raise ValueError()
-        l = []
-        pytestpm.trace.root.setwriter(l.append)
-        undo = pytestpm.enable_tracing()
-        try:
-            indent = pytestpm.trace.root.indent
-            p = api1()
-            pytestpm.register(p)
-            assert pytestpm.trace.root.indent == indent
-            assert len(l) >= 2
-            assert 'pytest_plugin_registered' in l[0]
-            assert 'finish' in l[1]
-
-            l[:] = []
-            with pytest.raises(ValueError):
-                pytestpm.register(api2())
-            assert pytestpm.trace.root.indent == indent
-            assert saveindent[0] > indent
-        finally:
-            undo()
-
-    def test_warn_on_deprecated_multicall(self, pytestpm):
-        warnings = []
-
-        class get_warnings:
-            def pytest_logwarning(self, message):
-                warnings.append(message)
-
-        class Plugin:
-            def pytest_configure(self, __multicall__):
-                pass
-
-        pytestpm.register(get_warnings())
-        before = list(warnings)
-        pytestpm.register(Plugin())
-        assert len(warnings) == len(before) + 1
-        assert "deprecated" in warnings[-1]
-
-    def test_warn_on_deprecated_addhooks(self, pytestpm):
-        warnings = []
-
-        class get_warnings:
-            def pytest_logwarning(self, code, fslocation, message, nodeid):
-                warnings.append(message)
-
-        class Plugin:
-            def pytest_testhook():
-                pass
-
-        pytestpm.register(get_warnings())
-        before = list(warnings)
-        pytestpm.addhooks(Plugin())
-        assert len(warnings) == len(before) + 1
-        assert "deprecated" in warnings[-1]
-
-
-def test_namespace_has_default_and_env_plugins(testdir):
-    p = testdir.makepyfile("""
-        import pytest
-        pytest.mark
-    """)
-    result = testdir.runpython(p)
-    assert result.ret == 0
-
-def test_default_markers(testdir):
-    result = testdir.runpytest("--markers")
-    result.stdout.fnmatch_lines([
-        "*tryfirst*first*",
-        "*trylast*last*",
-    ])
-
-
-def test_importplugin_issue375(testdir, pytestpm):
-    """Don't hide import errors when importing plugins and provide
-    an easy to debug message.
-    """
-    testdir.syspathinsert(testdir.tmpdir)
-    testdir.makepyfile(qwe="import aaaa")
-    with pytest.raises(ImportError) as excinfo:
-        pytestpm.import_plugin("qwe")
-    expected = '.*Error importing plugin "qwe": No module named \'?aaaa\'?'
-    assert py.std.re.match(expected, str(excinfo.value))
-
-
-class TestPytestPluginManager:
-    def test_register_imported_modules(self):
-        pm = PytestPluginManager()
-        mod = py.std.types.ModuleType("x.y.pytest_hello")
-        pm.register(mod)
-        assert pm.is_registered(mod)
-        l = pm.get_plugins()
-        assert mod in l
-        pytest.raises(ValueError, "pm.register(mod)")
-        pytest.raises(ValueError, lambda: pm.register(mod))
-        #assert not pm.is_registered(mod2)
-        assert pm.get_plugins() == l
-
-    def test_canonical_import(self, monkeypatch):
-        mod = py.std.types.ModuleType("pytest_xyz")
-        monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
-        pm = PytestPluginManager()
-        pm.import_plugin('pytest_xyz')
-        assert pm.get_plugin('pytest_xyz') == mod
-        assert pm.is_registered(mod)
-
-    def test_consider_module(self, testdir, pytestpm):
-        testdir.syspathinsert()
-        testdir.makepyfile(pytest_p1="#")
-        testdir.makepyfile(pytest_p2="#")
-        mod = py.std.types.ModuleType("temp")
-        mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
-        pytestpm.consider_module(mod)
-        assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
-        assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
-
-    def test_consider_module_import_module(self, testdir):
-        pytestpm = get_config().pluginmanager
-        mod = py.std.types.ModuleType("x")
-        mod.pytest_plugins = "pytest_a"
-        aplugin = testdir.makepyfile(pytest_a="#")
-        reprec = testdir.make_hook_recorder(pytestpm)
-        #syspath.prepend(aplugin.dirpath())
-        py.std.sys.path.insert(0, str(aplugin.dirpath()))
-        pytestpm.consider_module(mod)
-        call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
-        assert call.plugin.__name__ == "pytest_a"
-
-        # check that it is not registered twice
-        pytestpm.consider_module(mod)
-        l = reprec.getcalls("pytest_plugin_registered")
-        assert len(l) == 1
-
-    def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
-        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
-        with pytest.raises(ImportError):
-            pytestpm.consider_env()
-
-    def test_plugin_skip(self, testdir, monkeypatch):
-        p = testdir.makepyfile(skipping1="""
-            import pytest
-            pytest.skip("hello")
-        """)
-        p.copy(p.dirpath("skipping2.py"))
-        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
-        result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
-        assert result.ret == EXIT_NOTESTSCOLLECTED
-        result.stdout.fnmatch_lines([
-            "WI1*skipped plugin*skipping1*hello*",
-            "WI1*skipped plugin*skipping2*hello*",
-        ])
-
-    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
-        testdir.syspathinsert()
-        testdir.makepyfile(xy123="#")
-        monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
-        l1 = len(pytestpm.get_plugins())
-        pytestpm.consider_env()
-        l2 = len(pytestpm.get_plugins())
-        assert l2 == l1 + 1
-        assert pytestpm.get_plugin('xy123')
-        pytestpm.consider_env()
-        l3 = len(pytestpm.get_plugins())
-        assert l2 == l3
-
-    def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
-        testdir.makepyfile(pytest_x500="#")
-        p = testdir.makepyfile("""
-            import pytest
-            def test_hello(pytestconfig):
-                plugin = pytestconfig.pluginmanager.get_plugin('pytest_x500')
-                assert plugin is not None
-        """)
-        monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
-        result = testdir.runpytest(p, syspathinsert=True)
-        assert result.ret == 0
-        result.stdout.fnmatch_lines(["*1 passed*"])
-
-    def test_import_plugin_importname(self, testdir, pytestpm):
-        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
-        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
-
-        testdir.syspathinsert()
-        pluginname = "pytest_hello"
-        testdir.makepyfile(**{pluginname: ""})
-        pytestpm.import_plugin("pytest_hello")
-        len1 = len(pytestpm.get_plugins())
-        pytestpm.import_plugin("pytest_hello")
-        len2 = len(pytestpm.get_plugins())
-        assert len1 == len2
-        plugin1 = pytestpm.get_plugin("pytest_hello")
-        assert plugin1.__name__.endswith('pytest_hello')
-        plugin2 = pytestpm.get_plugin("pytest_hello")
-        assert plugin2 is plugin1
-
-    def test_import_plugin_dotted_name(self, testdir, pytestpm):
-        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
-        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
-
-        testdir.syspathinsert()
-        testdir.mkpydir("pkg").join("plug.py").write("x=3")
-        pluginname = "pkg.plug"
-        pytestpm.import_plugin(pluginname)
-        mod = pytestpm.get_plugin("pkg.plug")
-        assert mod.x == 3
-
-    def test_consider_conftest_deps(self, testdir, pytestpm):
-        mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
-        with pytest.raises(ImportError):
-            pytestpm.consider_conftest(mod)
-
-
-class TestPytestPluginManagerBootstrapming:
-    def test_preparse_args(self, pytestpm):
-        pytest.raises(ImportError, lambda:
-            pytestpm.consider_preparse(["xyz", "-p", "hello123"]))
-
-    def test_plugin_prevent_register(self, pytestpm):
-        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
-        l1 = pytestpm.get_plugins()
-        pytestpm.register(42, name="abc")
-        l2 = pytestpm.get_plugins()
-        assert len(l2) == len(l1)
-        assert 42 not in l2
-
-    def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
-        pytestpm.register(42, name="abc")
-        l1 = pytestpm.get_plugins()
-        assert 42 in l1
-        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
-        l2 = pytestpm.get_plugins()
-        assert 42 not in l2
diff --git a/tools/pytest/testing/test_pytester.py b/tools/pytest/testing/test_pytester.py
deleted file mode 100644
index 65660af..0000000
--- a/tools/pytest/testing/test_pytester.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import pytest
-import os
-from _pytest.pytester import HookRecorder
-from _pytest.config import PytestPluginManager
-from _pytest.main import EXIT_OK, EXIT_TESTSFAILED
-
-
-def test_make_hook_recorder(testdir):
-    item = testdir.getitem("def test_func(): pass")
-    recorder = testdir.make_hook_recorder(item.config.pluginmanager)
-    assert not recorder.getfailures()
-
-    pytest.xfail("internal reportrecorder tests need refactoring")
-    class rep:
-        excinfo = None
-        passed = False
-        failed = True
-        skipped = False
-        when = "call"
-
-    recorder.hook.pytest_runtest_logreport(report=rep)
-    failures = recorder.getfailures()
-    assert failures == [rep]
-    failures = recorder.getfailures()
-    assert failures == [rep]
-
-    class rep:
-        excinfo = None
-        passed = False
-        failed = False
-        skipped = True
-        when = "call"
-    rep.passed = False
-    rep.skipped = True
-    recorder.hook.pytest_runtest_logreport(report=rep)
-
-    modcol = testdir.getmodulecol("")
-    rep = modcol.config.hook.pytest_make_collect_report(collector=modcol)
-    rep.passed = False
-    rep.failed = True
-    rep.skipped = False
-    recorder.hook.pytest_collectreport(report=rep)
-
-    passed, skipped, failed = recorder.listoutcomes()
-    assert not passed and skipped and failed
-
-    numpassed, numskipped, numfailed = recorder.countoutcomes()
-    assert numpassed == 0
-    assert numskipped == 1
-    assert numfailed == 1
-    assert len(recorder.getfailedcollections()) == 1
-
-    recorder.unregister()
-    recorder.clear()
-    recorder.hook.pytest_runtest_logreport(report=rep)
-    pytest.raises(ValueError, "recorder.getfailures()")
-
-
-def test_parseconfig(testdir):
-    config1 = testdir.parseconfig()
-    config2 = testdir.parseconfig()
-    assert config2 != config1
-    assert config1 != pytest.config
-
-def test_testdir_runs_with_plugin(testdir):
-    testdir.makepyfile("""
-        pytest_plugins = "pytester"
-        def test_hello(testdir):
-            assert 1
-    """)
-    result = testdir.runpytest()
-    result.assert_outcomes(passed=1)
-
-
-def make_holder():
-    class apiclass:
-        def pytest_xyz(self, arg):
-            "x"
-        def pytest_xyz_noarg(self):
-            "x"
-
-    apimod = type(os)('api')
-    def pytest_xyz(arg):
-        "x"
-    def pytest_xyz_noarg():
-        "x"
-    apimod.pytest_xyz = pytest_xyz
-    apimod.pytest_xyz_noarg = pytest_xyz_noarg
-    return apiclass, apimod
-
-
-@pytest.mark.parametrize("holder", make_holder())
-def test_hookrecorder_basic(holder):
-    pm = PytestPluginManager()
-    pm.addhooks(holder)
-    rec = HookRecorder(pm)
-    pm.hook.pytest_xyz(arg=123)
-    call = rec.popcall("pytest_xyz")
-    assert call.arg == 123
-    assert call._name == "pytest_xyz"
-    pytest.raises(pytest.fail.Exception, "rec.popcall('abc')")
-    pm.hook.pytest_xyz_noarg()
-    call = rec.popcall("pytest_xyz_noarg")
-    assert call._name == "pytest_xyz_noarg"
-
-
-def test_makepyfile_unicode(testdir):
-    global unichr
-    try:
-        unichr(65)
-    except NameError:
-        unichr = chr
-    testdir.makepyfile(unichr(0xfffd))
-
-def test_inline_run_clean_modules(testdir):
-    test_mod = testdir.makepyfile("def test_foo(): assert True")
-    result = testdir.inline_run(str(test_mod))
-    assert result.ret == EXIT_OK
-    # rewrite module, now test should fail if module was re-imported
-    test_mod.write("def test_foo(): assert False")
-    result2 = testdir.inline_run(str(test_mod))
-    assert result2.ret == EXIT_TESTSFAILED
diff --git a/tools/pytest/testing/test_recwarn.py b/tools/pytest/testing/test_recwarn.py
deleted file mode 100644
index 87e5846..0000000
--- a/tools/pytest/testing/test_recwarn.py
+++ /dev/null
@@ -1,227 +0,0 @@
-import warnings
-import py
-import pytest
-from _pytest.recwarn import WarningsRecorder
-
-
-def test_recwarn_functional(testdir):
-    reprec = testdir.inline_runsource("""
-        import warnings
-        oldwarn = warnings.showwarning
-        def test_method(recwarn):
-            assert warnings.showwarning != oldwarn
-            warnings.warn("hello")
-            warn = recwarn.pop()
-            assert isinstance(warn.message, UserWarning)
-        def test_finalized():
-            assert warnings.showwarning == oldwarn
-    """)
-    res = reprec.countoutcomes()
-    assert tuple(res) == (2, 0, 0), res
-
-
-class TestWarningsRecorderChecker(object):
-    def test_recording(self, recwarn):
-        showwarning = py.std.warnings.showwarning
-        rec = WarningsRecorder()
-        with rec:
-            assert py.std.warnings.showwarning != showwarning
-            assert not rec.list
-            py.std.warnings.warn_explicit("hello", UserWarning, "xyz", 13)
-            assert len(rec.list) == 1
-            py.std.warnings.warn(DeprecationWarning("hello"))
-            assert len(rec.list) == 2
-            warn = rec.pop()
-            assert str(warn.message) == "hello"
-            l = rec.list
-            rec.clear()
-            assert len(rec.list) == 0
-            assert l is rec.list
-            pytest.raises(AssertionError, "rec.pop()")
-
-        assert showwarning == py.std.warnings.showwarning
-
-    def test_typechecking(self):
-        from _pytest.recwarn import WarningsChecker
-        with pytest.raises(TypeError):
-            WarningsChecker(5)
-        with pytest.raises(TypeError):
-            WarningsChecker(('hi', RuntimeWarning))
-        with pytest.raises(TypeError):
-            WarningsChecker([DeprecationWarning, RuntimeWarning])
-
-    def test_invalid_enter_exit(self):
-        # wrap this test in WarningsRecorder to ensure warning state gets reset
-        with WarningsRecorder():
-            with pytest.raises(RuntimeError):
-                rec = WarningsRecorder()
-                rec.__exit__(None, None, None)  # can't exit before entering
-
-            with pytest.raises(RuntimeError):
-                rec = WarningsRecorder()
-                with rec:
-                    with rec:
-                        pass  # can't enter twice
-
-
-class TestDeprecatedCall(object):
-    """test pytest.deprecated_call()"""
-
-    def dep(self, i, j=None):
-        if i == 0:
-            py.std.warnings.warn("is deprecated", DeprecationWarning,
-                                 stacklevel=1)
-        return 42
-
-    def dep_explicit(self, i):
-        if i == 0:
-            py.std.warnings.warn_explicit("dep_explicit", category=DeprecationWarning,
-                                          filename="hello", lineno=3)
-
-    def test_deprecated_call_raises(self):
-        with pytest.raises(AssertionError) as excinfo:
-            pytest.deprecated_call(self.dep, 3, 5)
-        assert str(excinfo).find("did not produce") != -1
-
-    def test_deprecated_call(self):
-        pytest.deprecated_call(self.dep, 0, 5)
-
-    def test_deprecated_call_ret(self):
-        ret = pytest.deprecated_call(self.dep, 0)
-        assert ret == 42
-
-    def test_deprecated_call_preserves(self):
-        onceregistry = py.std.warnings.onceregistry.copy()
-        filters = py.std.warnings.filters[:]
-        warn = py.std.warnings.warn
-        warn_explicit = py.std.warnings.warn_explicit
-        self.test_deprecated_call_raises()
-        self.test_deprecated_call()
-        assert onceregistry == py.std.warnings.onceregistry
-        assert filters == py.std.warnings.filters
-        assert warn is py.std.warnings.warn
-        assert warn_explicit is py.std.warnings.warn_explicit
-
-    def test_deprecated_explicit_call_raises(self):
-        with pytest.raises(AssertionError):
-            pytest.deprecated_call(self.dep_explicit, 3)
-
-    def test_deprecated_explicit_call(self):
-        pytest.deprecated_call(self.dep_explicit, 0)
-        pytest.deprecated_call(self.dep_explicit, 0)
-
-    def test_deprecated_call_as_context_manager_no_warning(self):
-        with pytest.raises(pytest.fail.Exception) as ex:
-            with pytest.deprecated_call():
-                self.dep(1)
-        assert str(ex.value) == "DID NOT WARN"
-
-    def test_deprecated_call_as_context_manager(self):
-        with pytest.deprecated_call():
-            self.dep(0)
-
-    def test_deprecated_call_pending(self):
-        def f():
-            py.std.warnings.warn(PendingDeprecationWarning("hi"))
-        pytest.deprecated_call(f)
-
-    def test_deprecated_call_specificity(self):
-        other_warnings = [Warning, UserWarning, SyntaxWarning, RuntimeWarning,
-                          FutureWarning, ImportWarning, UnicodeWarning]
-        for warning in other_warnings:
-            def f():
-                py.std.warnings.warn(warning("hi"))
-            with pytest.raises(AssertionError):
-                pytest.deprecated_call(f)
-
-    def test_deprecated_function_already_called(self, testdir):
-        """deprecated_call should be able to catch a call to a deprecated
-        function even if that function has already been called in the same
-        module. See #1190.
-        """
-        testdir.makepyfile("""
-            import warnings
-            import pytest
-
-            def deprecated_function():
-                warnings.warn("deprecated", DeprecationWarning)
-
-            def test_one():
-                deprecated_function()
-
-            def test_two():
-                pytest.deprecated_call(deprecated_function)
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines('*=== 2 passed in *===')
-
-
-class TestWarns(object):
-    def test_strings(self):
-        # different messages, b/c Python suppresses multiple identical warnings
-        source1 = "warnings.warn('w1', RuntimeWarning)"
-        source2 = "warnings.warn('w2', RuntimeWarning)"
-        source3 = "warnings.warn('w3', RuntimeWarning)"
-        pytest.warns(RuntimeWarning, source1)
-        pytest.raises(pytest.fail.Exception,
-                      lambda: pytest.warns(UserWarning, source2))
-        pytest.warns(RuntimeWarning, source3)
-
-    def test_function(self):
-        pytest.warns(SyntaxWarning,
-                     lambda msg: warnings.warn(msg, SyntaxWarning), "syntax")
-
-    def test_warning_tuple(self):
-        pytest.warns((RuntimeWarning, SyntaxWarning),
-                     lambda: warnings.warn('w1', RuntimeWarning))
-        pytest.warns((RuntimeWarning, SyntaxWarning),
-                     lambda: warnings.warn('w2', SyntaxWarning))
-        pytest.raises(pytest.fail.Exception,
-                      lambda: pytest.warns(
-                          (RuntimeWarning, SyntaxWarning),
-                          lambda: warnings.warn('w3', UserWarning)))
-
-    def test_as_contextmanager(self):
-        with pytest.warns(RuntimeWarning):
-            warnings.warn("runtime", RuntimeWarning)
-
-        with pytest.raises(pytest.fail.Exception):
-            with pytest.warns(RuntimeWarning):
-                warnings.warn("user", UserWarning)
-
-        with pytest.raises(pytest.fail.Exception):
-            with pytest.warns(UserWarning):
-                warnings.warn("runtime", RuntimeWarning)
-
-        with pytest.warns(UserWarning):
-            warnings.warn("user", UserWarning)
-
-    def test_record(self):
-        with pytest.warns(UserWarning) as record:
-            warnings.warn("user", UserWarning)
-
-        assert len(record) == 1
-        assert str(record[0].message) == "user"
-
-    def test_record_only(self):
-        with pytest.warns(None) as record:
-            warnings.warn("user", UserWarning)
-            warnings.warn("runtime", RuntimeWarning)
-
-        assert len(record) == 2
-        assert str(record[0].message) == "user"
-        assert str(record[1].message) == "runtime"
-
-    def test_double_test(self, testdir):
-        """If a test is run again, the warning should still be raised"""
-        testdir.makepyfile('''
-            import pytest
-            import warnings
-
-            @pytest.mark.parametrize('run', [1, 2])
-            def test(run):
-                with pytest.warns(RuntimeWarning):
-                    warnings.warn("runtime", RuntimeWarning)
-        ''')
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines(['*2 passed in*'])
diff --git a/tools/pytest/testing/test_resultlog.py b/tools/pytest/testing/test_resultlog.py
deleted file mode 100644
index 74d13f6..0000000
--- a/tools/pytest/testing/test_resultlog.py
+++ /dev/null
@@ -1,236 +0,0 @@
-import os
-
-import _pytest._code
-import py
-import pytest
-from _pytest.main import Node, Item, FSCollector
-from _pytest.resultlog import generic_path, ResultLog, \
-        pytest_configure, pytest_unconfigure
-
-
-def test_generic_path(testdir):
-    from _pytest.main import Session
-    config = testdir.parseconfig()
-    session = Session(config)
-    p1 = Node('a', config=config, session=session)
-    #assert p1.fspath is None
-    p2 = Node('B', parent=p1)
-    p3 = Node('()', parent = p2)
-    item = Item('c', parent = p3)
-
-    res = generic_path(item)
-    assert res == 'a.B().c'
-
-    p0 = FSCollector('proj/test', config=config, session=session)
-    p1 = FSCollector('proj/test/a', parent=p0)
-    p2 = Node('B', parent=p1)
-    p3 = Node('()', parent = p2)
-    p4 = Node('c', parent=p3)
-    item = Item('[1]', parent = p4)
-
-    res = generic_path(item)
-    assert res == 'test/a:B().c[1]'
-
-def test_write_log_entry():
-    reslog = ResultLog(None, None)
-    reslog.logfile = py.io.TextIO()
-    reslog.write_log_entry('name', '.', '')
-    entry = reslog.logfile.getvalue()
-    assert entry[-1] == '\n'
-    entry_lines = entry.splitlines()
-    assert len(entry_lines) == 1
-    assert entry_lines[0] == '. name'
-
-    reslog.logfile = py.io.TextIO()
-    reslog.write_log_entry('name', 's', 'Skipped')
-    entry = reslog.logfile.getvalue()
-    assert entry[-1] == '\n'
-    entry_lines = entry.splitlines()
-    assert len(entry_lines) == 2
-    assert entry_lines[0] == 's name'
-    assert entry_lines[1] == ' Skipped'
-
-    reslog.logfile = py.io.TextIO()
-    reslog.write_log_entry('name', 's', 'Skipped\n')
-    entry = reslog.logfile.getvalue()
-    assert entry[-1] == '\n'
-    entry_lines = entry.splitlines()
-    assert len(entry_lines) == 2
-    assert entry_lines[0] == 's name'
-    assert entry_lines[1] == ' Skipped'
-
-    reslog.logfile = py.io.TextIO()
-    longrepr = ' tb1\n tb 2\nE tb3\nSome Error'
-    reslog.write_log_entry('name', 'F', longrepr)
-    entry = reslog.logfile.getvalue()
-    assert entry[-1] == '\n'
-    entry_lines = entry.splitlines()
-    assert len(entry_lines) == 5
-    assert entry_lines[0] == 'F name'
-    assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()]
-
-
-class TestWithFunctionIntegration:
-    # XXX (hpk) i think that the resultlog plugin should
-    # provide a Parser object so that one can remain
-    # ignorant regarding formatting details.
-    def getresultlog(self, testdir, arg):
-        resultlog = testdir.tmpdir.join("resultlog")
-        testdir.plugins.append("resultlog")
-        args = ["--resultlog=%s" % resultlog] + [arg]
-        testdir.runpytest(*args)
-        return [x for x in resultlog.readlines(cr=0) if x]
-
-    def test_collection_report(self, testdir):
-        ok = testdir.makepyfile(test_collection_ok="")
-        skip = testdir.makepyfile(test_collection_skip=
-            "import pytest ; pytest.skip('hello')")
-        fail = testdir.makepyfile(test_collection_fail="XXX")
-        lines = self.getresultlog(testdir, ok)
-        assert not lines
-
-        lines = self.getresultlog(testdir, skip)
-        assert len(lines) == 2
-        assert lines[0].startswith("S ")
-        assert lines[0].endswith("test_collection_skip.py")
-        assert lines[1].startswith(" ")
-        assert lines[1].endswith("test_collection_skip.py:1: Skipped: hello")
-
-        lines = self.getresultlog(testdir, fail)
-        assert lines
-        assert lines[0].startswith("F ")
-        assert lines[0].endswith("test_collection_fail.py"), lines[0]
-        for x in lines[1:]:
-            assert x.startswith(" ")
-        assert "XXX" in "".join(lines[1:])
-
-    def test_log_test_outcomes(self, testdir):
-        mod = testdir.makepyfile(test_mod="""
-            import pytest
-            def test_pass(): pass
-            def test_skip(): pytest.skip("hello")
-            def test_fail(): raise ValueError("FAIL")
-
-            @pytest.mark.xfail
-            def test_xfail(): raise ValueError("XFAIL")
-            @pytest.mark.xfail
-            def test_xpass(): pass
-
-        """)
-        lines = self.getresultlog(testdir, mod)
-        assert len(lines) >= 3
-        assert lines[0].startswith(". ")
-        assert lines[0].endswith("test_pass")
-        assert lines[1].startswith("s "), lines[1]
-        assert lines[1].endswith("test_skip")
-        assert lines[2].find("hello") != -1
-
-        assert lines[3].startswith("F ")
-        assert lines[3].endswith("test_fail")
-        tb = "".join(lines[4:8])
-        assert tb.find('raise ValueError("FAIL")') != -1
-
-        assert lines[8].startswith('x ')
-        tb = "".join(lines[8:14])
-        assert tb.find('raise ValueError("XFAIL")') != -1
-
-        assert lines[14].startswith('X ')
-        assert len(lines) == 15
-
-    @pytest.mark.parametrize("style", ("native", "long", "short"))
-    def test_internal_exception(self, style):
-        # they are produced for example by a teardown failing
-        # at the end of the run or a failing hook invocation
-        try:
-            raise ValueError
-        except ValueError:
-            excinfo = _pytest._code.ExceptionInfo()
-        reslog = ResultLog(None, py.io.TextIO())
-        reslog.pytest_internalerror(excinfo.getrepr(style=style))
-        entry = reslog.logfile.getvalue()
-        entry_lines = entry.splitlines()
-
-        assert entry_lines[0].startswith('! ')
-        if style != "native":
-            assert os.path.basename(__file__)[:-9] in entry_lines[0] #.pyc/class
-        assert entry_lines[-1][0] == ' '
-        assert 'ValueError' in entry
-
-
-def test_generic(testdir, LineMatcher):
-    testdir.plugins.append("resultlog")
-    testdir.makepyfile("""
-        import pytest
-        def test_pass():
-            pass
-        def test_fail():
-            assert 0
-        def test_skip():
-            pytest.skip("")
-        @pytest.mark.xfail
-        def test_xfail():
-            assert 0
-        @pytest.mark.xfail(run=False)
-        def test_xfail_norun():
-            assert 0
-    """)
-    testdir.runpytest("--resultlog=result.log")
-    lines = testdir.tmpdir.join("result.log").readlines(cr=0)
-    LineMatcher(lines).fnmatch_lines([
-        ". *:test_pass",
-        "F *:test_fail",
-        "s *:test_skip",
-        "x *:test_xfail",
-        "x *:test_xfail_norun",
-    ])
-
-def test_makedir_for_resultlog(testdir, LineMatcher):
-    """--resultlog should automatically create directories for the log file"""
-    testdir.plugins.append("resultlog")
-    testdir.makepyfile("""
-        import pytest
-        def test_pass():
-            pass
-    """)
-    testdir.runpytest("--resultlog=path/to/result.log")
-    lines = testdir.tmpdir.join("path/to/result.log").readlines(cr=0)
-    LineMatcher(lines).fnmatch_lines([
-        ". *:test_pass",
-    ])
-
-
-def test_no_resultlog_on_slaves(testdir):
-    config = testdir.parseconfig("-p", "resultlog", "--resultlog=resultlog")
-
-    assert not hasattr(config, '_resultlog')
-    pytest_configure(config)
-    assert hasattr(config, '_resultlog')
-    pytest_unconfigure(config)
-    assert not hasattr(config, '_resultlog')
-
-    config.slaveinput = {}
-    pytest_configure(config)
-    assert not hasattr(config, '_resultlog')
-    pytest_unconfigure(config)
-    assert not hasattr(config, '_resultlog')
-
-
-def test_failure_issue380(testdir):
-    testdir.makeconftest("""
-        import pytest
-        class MyCollector(pytest.File):
-            def collect(self):
-                raise ValueError()
-            def repr_failure(self, excinfo):
-                return "somestring"
-        def pytest_collect_file(path, parent):
-            return MyCollector(parent=parent, fspath=path)
-    """)
-    testdir.makepyfile("""
-        def test_func():
-            pass
-    """)
-    result = testdir.runpytest("--resultlog=log")
-    assert result.ret == 1
-
-
diff --git a/tools/pytest/testing/test_runner.py b/tools/pytest/testing/test_runner.py
deleted file mode 100644
index 4421c5d..0000000
--- a/tools/pytest/testing/test_runner.py
+++ /dev/null
@@ -1,634 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import with_statement
-
-import _pytest._code
-import os
-import py
-import pytest
-import sys
-from _pytest import runner, main
-
-class TestSetupState:
-    def test_setup(self, testdir):
-        ss = runner.SetupState()
-        item = testdir.getitem("def test_func(): pass")
-        l = [1]
-        ss.prepare(item)
-        ss.addfinalizer(l.pop, colitem=item)
-        assert l
-        ss._pop_and_teardown()
-        assert not l
-
-    def test_teardown_exact_stack_empty(self, testdir):
-        item = testdir.getitem("def test_func(): pass")
-        ss = runner.SetupState()
-        ss.teardown_exact(item, None)
-        ss.teardown_exact(item, None)
-        ss.teardown_exact(item, None)
-
-    def test_setup_fails_and_failure_is_cached(self, testdir):
-        item = testdir.getitem("""
-            def setup_module(mod):
-                raise ValueError(42)
-            def test_func(): pass
-        """) # noqa
-        ss = runner.SetupState()
-        pytest.raises(ValueError, lambda: ss.prepare(item))
-        pytest.raises(ValueError, lambda: ss.prepare(item))
-
-    def test_teardown_multiple_one_fails(self, testdir):
-        r = []
-        def fin1(): r.append('fin1')
-        def fin2(): raise Exception('oops')
-        def fin3(): r.append('fin3')
-        item = testdir.getitem("def test_func(): pass")
-        ss = runner.SetupState()
-        ss.addfinalizer(fin1, item)
-        ss.addfinalizer(fin2, item)
-        ss.addfinalizer(fin3, item)
-        with pytest.raises(Exception) as err:
-            ss._callfinalizers(item)
-        assert err.value.args == ('oops',)
-        assert r == ['fin3', 'fin1']
-
-    def test_teardown_multiple_fail(self, testdir):
-        # Ensure the first exception is the one which is re-raised.
-        # Ideally both would be reported however.
-        def fin1(): raise Exception('oops1')
-        def fin2(): raise Exception('oops2')
-        item = testdir.getitem("def test_func(): pass")
-        ss = runner.SetupState()
-        ss.addfinalizer(fin1, item)
-        ss.addfinalizer(fin2, item)
-        with pytest.raises(Exception) as err:
-            ss._callfinalizers(item)
-        assert err.value.args == ('oops2',)
-
-
-class BaseFunctionalTests:
-    def test_passfunction(self, testdir):
-        reports = testdir.runitem("""
-            def test_func():
-                pass
-        """)
-        rep = reports[1]
-        assert rep.passed
-        assert not rep.failed
-        assert rep.outcome == "passed"
-        assert not rep.longrepr
-
-    def test_failfunction(self, testdir):
-        reports = testdir.runitem("""
-            def test_func():
-                assert 0
-        """)
-        rep = reports[1]
-        assert not rep.passed
-        assert not rep.skipped
-        assert rep.failed
-        assert rep.when == "call"
-        assert rep.outcome == "failed"
-        #assert isinstance(rep.longrepr, ReprExceptionInfo)
-
-    def test_skipfunction(self, testdir):
-        reports = testdir.runitem("""
-            import pytest
-            def test_func():
-                pytest.skip("hello")
-        """)
-        rep = reports[1]
-        assert not rep.failed
-        assert not rep.passed
-        assert rep.skipped
-        assert rep.outcome == "skipped"
-        #assert rep.skipped.when == "call"
-        #assert rep.skipped.when == "call"
-        #assert rep.skipped == "%sreason == "hello"
-        #assert rep.skipped.location.lineno == 3
-        #assert rep.skipped.location.path
-        #assert not rep.skipped.failurerepr
-
-    def test_skip_in_setup_function(self, testdir):
-        reports = testdir.runitem("""
-            import pytest
-            def setup_function(func):
-                pytest.skip("hello")
-            def test_func():
-                pass
-        """)
-        print(reports)
-        rep = reports[0]
-        assert not rep.failed
-        assert not rep.passed
-        assert rep.skipped
-        #assert rep.skipped.reason == "hello"
-        #assert rep.skipped.location.lineno == 3
-        #assert rep.skipped.location.lineno == 3
-        assert len(reports) == 2
-        assert reports[1].passed # teardown
-
-    def test_failure_in_setup_function(self, testdir):
-        reports = testdir.runitem("""
-            import pytest
-            def setup_function(func):
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        rep = reports[0]
-        assert not rep.skipped
-        assert not rep.passed
-        assert rep.failed
-        assert rep.when == "setup"
-        assert len(reports) == 2
-
-    def test_failure_in_teardown_function(self, testdir):
-        reports = testdir.runitem("""
-            import pytest
-            def teardown_function(func):
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        print(reports)
-        assert len(reports) == 3
-        rep = reports[2]
-        assert not rep.skipped
-        assert not rep.passed
-        assert rep.failed
-        assert rep.when == "teardown"
-        #assert rep.longrepr.reprcrash.lineno == 3
-        #assert rep.longrepr.reprtraceback.reprentries
-
-    def test_custom_failure_repr(self, testdir):
-        testdir.makepyfile(conftest="""
-            import pytest
-            class Function(pytest.Function):
-                def repr_failure(self, excinfo):
-                    return "hello"
-        """)
-        reports = testdir.runitem("""
-            import pytest
-            def test_func():
-                assert 0
-        """)
-        rep = reports[1]
-        assert not rep.skipped
-        assert not rep.passed
-        assert rep.failed
-        #assert rep.outcome.when == "call"
-        #assert rep.failed.where.lineno == 3
-        #assert rep.failed.where.path.basename == "test_func.py"
-        #assert rep.failed.failurerepr == "hello"
-
-    def test_teardown_final_returncode(self, testdir):
-        rec = testdir.inline_runsource("""
-            def test_func():
-                pass
-            def teardown_function(func):
-                raise ValueError(42)
-        """)
-        assert rec.ret == 1
-
-    def test_exact_teardown_issue90(self, testdir):
-        rec = testdir.inline_runsource("""
-            import pytest
-
-            class TestClass:
-                def test_method(self):
-                    pass
-                def teardown_class(cls):
-                    raise Exception()
-
-            def test_func():
-                import sys
-                # on python2 exc_info is keept till a function exits
-                # so we would end up calling test functions while
-                # sys.exc_info would return the indexerror
-                # from guessing the lastitem
-                excinfo = sys.exc_info()
-                import traceback
-                assert excinfo[0] is None, \
-                       traceback.format_exception(*excinfo)
-            def teardown_function(func):
-                raise ValueError(42)
-        """)
-        reps = rec.getreports("pytest_runtest_logreport")
-        print (reps)
-        for i in range(2):
-            assert reps[i].nodeid.endswith("test_method")
-            assert reps[i].passed
-        assert reps[2].when == "teardown"
-        assert reps[2].failed
-        assert len(reps) == 6
-        for i in range(3,5):
-            assert reps[i].nodeid.endswith("test_func")
-            assert reps[i].passed
-        assert reps[5].when == "teardown"
-        assert reps[5].nodeid.endswith("test_func")
-        assert reps[5].failed
-
-    def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
-        testdir.makepyfile(conftest="""
-            import pytest
-            class Function(pytest.Function):
-                def repr_failure(self, excinfo):
-                    assert 0
-        """)
-        reports = testdir.runitem("""
-            def setup_function(func):
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        assert len(reports) == 2
-        rep = reports[0]
-        print(rep)
-        assert not rep.skipped
-        assert not rep.passed
-        assert rep.failed
-        #assert rep.outcome.when == "setup"
-        #assert rep.outcome.where.lineno == 3
-        #assert rep.outcome.where.path.basename == "test_func.py"
-        #assert instanace(rep.failed.failurerepr, PythonFailureRepr)
-
-    def test_systemexit_does_not_bail_out(self, testdir):
-        try:
-            reports = testdir.runitem("""
-                def test_func():
-                    raise SystemExit(42)
-            """)
-        except SystemExit:
-            pytest.fail("runner did not catch SystemExit")
-        rep = reports[1]
-        assert rep.failed
-        assert rep.when == "call"
-
-    def test_exit_propagates(self, testdir):
-        try:
-            testdir.runitem("""
-                import pytest
-                def test_func():
-                    raise pytest.exit.Exception()
-            """)
-        except pytest.exit.Exception:
-            pass
-        else:
-            pytest.fail("did not raise")
-
-class TestExecutionNonForked(BaseFunctionalTests):
-    def getrunner(self):
-        def f(item):
-            return runner.runtestprotocol(item, log=False)
-        return f
-
-    def test_keyboardinterrupt_propagates(self, testdir):
-        try:
-            testdir.runitem("""
-                def test_func():
-                    raise KeyboardInterrupt("fake")
-            """)
-        except KeyboardInterrupt:
-            pass
-        else:
-            pytest.fail("did not raise")
-
-class TestExecutionForked(BaseFunctionalTests):
-    pytestmark = pytest.mark.skipif("not hasattr(os, 'fork')")
-
-    def getrunner(self):
-        # XXX re-arrange this test to live in pytest-xdist
-        boxed = pytest.importorskip("xdist.boxed")
-        return boxed.forked_run_report
-
-    def test_suicide(self, testdir):
-        reports = testdir.runitem("""
-            def test_func():
-                import os
-                os.kill(os.getpid(), 15)
-        """)
-        rep = reports[0]
-        assert rep.failed
-        assert rep.when == "???"
-
-class TestSessionReports:
-    def test_collect_result(self, testdir):
-        col = testdir.getmodulecol("""
-            def test_func1():
-                pass
-            class TestClass:
-                pass
-        """)
-        rep = runner.collect_one_node(col)
-        assert not rep.failed
-        assert not rep.skipped
-        assert rep.passed
-        locinfo = rep.location
-        assert locinfo[0] == col.fspath.basename
-        assert not locinfo[1]
-        assert locinfo[2] == col.fspath.basename
-        res = rep.result
-        assert len(res) == 2
-        assert res[0].name == "test_func1"
-        assert res[1].name == "TestClass"
-
-    def test_skip_at_module_scope(self, testdir):
-        col = testdir.getmodulecol("""
-            import pytest
-            pytest.skip("hello")
-            def test_func():
-                pass
-        """)
-        rep = main.collect_one_node(col)
-        assert not rep.failed
-        assert not rep.passed
-        assert rep.skipped
-
-
-reporttypes = [
-    runner.BaseReport,
-    runner.TestReport,
-    runner.TeardownErrorReport,
-    runner.CollectReport,
-]
-
-@pytest.mark.parametrize('reporttype', reporttypes, ids=[x.__name__ for x in reporttypes])
-def test_report_extra_parameters(reporttype):
-    if hasattr(py.std.inspect, 'signature'):
-        args = list(py.std.inspect.signature(reporttype.__init__).parameters.keys())[1:]
-    else:
-        args = py.std.inspect.getargspec(reporttype.__init__)[0][1:]
-    basekw = dict.fromkeys(args, [])
-    report = reporttype(newthing=1, **basekw)
-    assert report.newthing == 1
-
-def test_callinfo():
-    ci = runner.CallInfo(lambda: 0, '123')
-    assert ci.when == "123"
-    assert ci.result == 0
-    assert "result" in repr(ci)
-    ci = runner.CallInfo(lambda: 0/0, '123')
-    assert ci.when == "123"
-    assert not hasattr(ci, 'result')
-    assert ci.excinfo
-    assert "exc" in repr(ci)
-
-# design question: do we want general hooks in python files?
-# then something like the following functional tests makes sense
-@pytest.mark.xfail
-def test_runtest_in_module_ordering(testdir):
-    p1 = testdir.makepyfile("""
-        def pytest_runtest_setup(item): # runs after class-level!
-            item.function.mylist.append("module")
-        class TestClass:
-            def pytest_runtest_setup(self, item):
-                assert not hasattr(item.function, 'mylist')
-                item.function.mylist = ['class']
-            def pytest_funcarg__mylist(self, request):
-                return request.function.mylist
-            def pytest_runtest_call(self, item, __multicall__):
-                try:
-                    __multicall__.execute()
-                except ValueError:
-                    pass
-            def test_hello1(self, mylist):
-                assert mylist == ['class', 'module'], mylist
-                raise ValueError()
-            def test_hello2(self, mylist):
-                assert mylist == ['class', 'module'], mylist
-        def pytest_runtest_teardown(item):
-            del item.function.mylist
-    """)
-    result = testdir.runpytest(p1)
-    result.stdout.fnmatch_lines([
-        "*2 passed*"
-    ])
-
-
-def test_outcomeexception_exceptionattributes():
-    outcome = runner.OutcomeException('test')
-    assert outcome.args[0] == outcome.msg
-
-def test_pytest_exit():
-    try:
-        pytest.exit("hello")
-    except pytest.exit.Exception:
-        excinfo = _pytest._code.ExceptionInfo()
-        assert excinfo.errisinstance(KeyboardInterrupt)
-
-def test_pytest_fail():
-    try:
-        pytest.fail("hello")
-    except pytest.fail.Exception:
-        excinfo = _pytest._code.ExceptionInfo()
-        s = excinfo.exconly(tryshort=True)
-        assert s.startswith("Failed")
-
-def test_pytest_fail_notrace(testdir):
-    testdir.makepyfile("""
-        import pytest
-        def test_hello():
-            pytest.fail("hello", pytrace=False)
-        def teardown_function(function):
-            pytest.fail("world", pytrace=False)
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "world",
-        "hello",
-    ])
-    assert 'def teardown_function' not in result.stdout.str()
-
-
-@pytest.mark.parametrize('str_prefix', ['u', ''])
-def test_pytest_fail_notrace_non_ascii(testdir, str_prefix):
-    """Fix pytest.fail with pytrace=False with non-ascii characters (#1178).
-
-    This tests with native and unicode strings containing non-ascii chars.
-    """
-    testdir.makepyfile(u"""
-        # coding: utf-8
-        import pytest
-
-        def test_hello():
-            pytest.fail(%s'oh oh: ☺', pytrace=False)
-    """ % str_prefix)
-    result = testdir.runpytest()
-    if sys.version_info[0] >= 3:
-        result.stdout.fnmatch_lines(['*test_hello*', "oh oh: ☺"])
-    else:
-        result.stdout.fnmatch_lines(['*test_hello*', "oh oh: *"])
-    assert 'def test_hello' not in result.stdout.str()
-
-
-def test_pytest_no_tests_collected_exit_status(testdir):
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines('*collected 0 items*')
-    assert result.ret == main.EXIT_NOTESTSCOLLECTED
-
-    testdir.makepyfile(test_foo="""
-        def test_foo():
-            assert 1
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines('*collected 1 items*')
-    result.stdout.fnmatch_lines('*1 passed*')
-    assert result.ret == main.EXIT_OK
-
-    result = testdir.runpytest('-k nonmatch')
-    result.stdout.fnmatch_lines('*collected 1 items*')
-    result.stdout.fnmatch_lines('*1 deselected*')
-    assert result.ret == main.EXIT_NOTESTSCOLLECTED
-
-
-def test_exception_printing_skip():
-    try:
-        pytest.skip("hello")
-    except pytest.skip.Exception:
-        excinfo = _pytest._code.ExceptionInfo()
-        s = excinfo.exconly(tryshort=True)
-        assert s.startswith("Skipped")
-
-def test_importorskip(monkeypatch):
-    importorskip = pytest.importorskip
-    def f():
-        importorskip("asdlkj")
-    try:
-        sys = importorskip("sys")  # noqa
-        assert sys == py.std.sys
-        #path = pytest.importorskip("os.path")
-        #assert path == py.std.os.path
-        excinfo = pytest.raises(pytest.skip.Exception, f)
-        path = py.path.local(excinfo.getrepr().reprcrash.path)
-        # check that importorskip reports the actual call
-        # in this test the test_runner.py file
-        assert path.purebasename == "test_runner"
-        pytest.raises(SyntaxError, "pytest.importorskip('x y z')")
-        pytest.raises(SyntaxError, "pytest.importorskip('x=y')")
-        mod = py.std.types.ModuleType("hello123")
-        mod.__version__ = "1.3"
-        monkeypatch.setitem(sys.modules, "hello123", mod)
-        pytest.raises(pytest.skip.Exception, """
-            pytest.importorskip("hello123", minversion="1.3.1")
-        """)
-        mod2 = pytest.importorskip("hello123", minversion="1.3")
-        assert mod2 == mod
-    except pytest.skip.Exception:
-        print(_pytest._code.ExceptionInfo())
-        pytest.fail("spurious skip")
-
-def test_importorskip_imports_last_module_part():
-    ospath = pytest.importorskip("os.path")
-    assert os.path == ospath
-
-def test_importorskip_dev_module(monkeypatch):
-    try:
-        mod = py.std.types.ModuleType("mockmodule")
-        mod.__version__ = '0.13.0.dev-43290'
-        monkeypatch.setitem(sys.modules, 'mockmodule', mod)
-        mod2 = pytest.importorskip('mockmodule', minversion='0.12.0')
-        assert mod2 == mod
-        pytest.raises(pytest.skip.Exception, """
-            pytest.importorskip('mockmodule1', minversion='0.14.0')""")
-    except pytest.skip.Exception:
-        print(_pytest._code.ExceptionInfo())
-        pytest.fail("spurious skip")
-
-
-def test_pytest_cmdline_main(testdir):
-    p = testdir.makepyfile("""
-        import pytest
-        def test_hello():
-            assert 1
-        if __name__ == '__main__':
-           pytest.cmdline.main([__file__])
-    """)
-    import subprocess
-    popen = subprocess.Popen([sys.executable, str(p)], stdout=subprocess.PIPE)
-    popen.communicate()
-    ret = popen.wait()
-    assert ret == 0
-
-
-def test_unicode_in_longrepr(testdir):
-    testdir.makeconftest("""
-        import py
-        def pytest_runtest_makereport(__multicall__):
-            rep = __multicall__.execute()
-            if rep.when == "call":
-                rep.longrepr = py.builtin._totext("\\xc3\\xa4", "utf8")
-            return rep
-    """)
-    testdir.makepyfile("""
-        def test_out():
-            assert 0
-    """)
-    result = testdir.runpytest()
-    assert result.ret == 1
-    assert "UnicodeEncodeError" not in result.stderr.str()
-
-
-def test_failure_in_setup(testdir):
-    testdir.makepyfile("""
-        def setup_module():
-            0/0
-        def test_func():
-            pass
-    """)
-    result = testdir.runpytest("--tb=line")
-    assert "def setup_module" not in result.stdout.str()
-
-
-def test_makereport_getsource(testdir):
-    testdir.makepyfile("""
-        def test_foo():
-            if False: pass
-            else: assert False
-    """)
-    result = testdir.runpytest()
-    assert 'INTERNALERROR' not in result.stdout.str()
-    result.stdout.fnmatch_lines(['*else: assert False*'])
-
-
-def test_makereport_getsource_dynamic_code(testdir, monkeypatch):
-    """Test that exception in dynamically generated code doesn't break getting the source line."""
-    import inspect
-    original_findsource = inspect.findsource
-    def findsource(obj, *args, **kwargs):
-        # Can be triggered by dynamically created functions
-        if obj.__name__ == 'foo':
-            raise IndexError()
-        return original_findsource(obj, *args, **kwargs)
-    monkeypatch.setattr(inspect, 'findsource', findsource)
-
-    testdir.makepyfile("""
-        import pytest
-
-        @pytest.fixture
-        def foo(missing):
-            pass
-
-        def test_fix(foo):
-            assert False
-    """)
-    result = testdir.runpytest('-vv')
-    assert 'INTERNALERROR' not in result.stdout.str()
-    result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"])
-
-
-def test_store_except_info_on_eror():
-    """ Test that upon test failure, the exception info is stored on
-    sys.last_traceback and friends.
-    """
-    # Simulate item that raises a specific exception
-    class ItemThatRaises:
-        def runtest(self):
-            raise IndexError('TEST')
-    try:
-        runner.pytest_runtest_call(ItemThatRaises())
-    except IndexError:
-        pass
-    # Check that exception info is stored on sys
-    assert sys.last_type is IndexError
-    assert sys.last_value.args[0] == 'TEST'
-    assert sys.last_traceback
diff --git a/tools/pytest/testing/test_runner_xunit.py b/tools/pytest/testing/test_runner_xunit.py
deleted file mode 100644
index f32a131..0000000
--- a/tools/pytest/testing/test_runner_xunit.py
+++ /dev/null
@@ -1,252 +0,0 @@
-#
-# test correct setup/teardowns at
-# module, class, and instance level
-
-def test_module_and_function_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        modlevel = []
-        def setup_module(module):
-            assert not modlevel
-            module.modlevel.append(42)
-
-        def teardown_module(module):
-            modlevel.pop()
-
-        def setup_function(function):
-            function.answer = 17
-
-        def teardown_function(function):
-            del function.answer
-
-        def test_modlevel():
-            assert modlevel[0] == 42
-            assert test_modlevel.answer == 17
-
-        class TestFromClass:
-            def test_module(self):
-                assert modlevel[0] == 42
-                assert not hasattr(test_modlevel, 'answer')
-    """)
-    rep = reprec.matchreport("test_modlevel")
-    assert rep.passed
-    rep = reprec.matchreport("test_module")
-    assert rep.passed
-
-def test_module_setup_failure_no_teardown(testdir):
-    reprec = testdir.inline_runsource("""
-        l = []
-        def setup_module(module):
-            l.append(1)
-            0/0
-
-        def test_nothing():
-            pass
-
-        def teardown_module(module):
-            l.append(2)
-    """)
-    reprec.assertoutcome(failed=1)
-    calls = reprec.getcalls("pytest_runtest_setup")
-    assert calls[0].item.module.l == [1]
-
-def test_setup_function_failure_no_teardown(testdir):
-    reprec = testdir.inline_runsource("""
-        modlevel = []
-        def setup_function(function):
-            modlevel.append(1)
-            0/0
-
-        def teardown_function(module):
-            modlevel.append(2)
-
-        def test_func():
-            pass
-    """)
-    calls = reprec.getcalls("pytest_runtest_setup")
-    assert calls[0].item.module.modlevel == [1]
-
-def test_class_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSimpleClassSetup:
-            clslevel = []
-            def setup_class(cls):
-                cls.clslevel.append(23)
-
-            def teardown_class(cls):
-                cls.clslevel.pop()
-
-            def test_classlevel(self):
-                assert self.clslevel[0] == 23
-
-        class TestInheritedClassSetupStillWorks(TestSimpleClassSetup):
-            def test_classlevel_anothertime(self):
-                assert self.clslevel == [23]
-
-        def test_cleanup():
-            assert not TestSimpleClassSetup.clslevel
-            assert not TestInheritedClassSetupStillWorks.clslevel
-    """)
-    reprec.assertoutcome(passed=1+2+1)
-
-def test_class_setup_failure_no_teardown(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSimpleClassSetup:
-            clslevel = []
-            def setup_class(cls):
-                0/0
-
-            def teardown_class(cls):
-                cls.clslevel.append(1)
-
-            def test_classlevel(self):
-                pass
-
-        def test_cleanup():
-            assert not TestSimpleClassSetup.clslevel
-    """)
-    reprec.assertoutcome(failed=1, passed=1)
-
-def test_method_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSetupMethod:
-            def setup_method(self, meth):
-                self.methsetup = meth
-            def teardown_method(self, meth):
-                del self.methsetup
-
-            def test_some(self):
-                assert self.methsetup == self.test_some
-
-            def test_other(self):
-                assert self.methsetup == self.test_other
-    """)
-    reprec.assertoutcome(passed=2)
-
-def test_method_setup_failure_no_teardown(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestMethodSetup:
-            clslevel = []
-            def setup_method(self, method):
-                self.clslevel.append(1)
-                0/0
-
-            def teardown_method(self, method):
-                self.clslevel.append(2)
-
-            def test_method(self):
-                pass
-
-        def test_cleanup():
-            assert TestMethodSetup.clslevel == [1]
-    """)
-    reprec.assertoutcome(failed=1, passed=1)
-
-def test_method_generator_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSetupTeardownOnInstance:
-            def setup_class(cls):
-                cls.classsetup = True
-
-            def setup_method(self, method):
-                self.methsetup = method
-
-            def test_generate(self):
-                assert self.classsetup
-                assert self.methsetup == self.test_generate
-                yield self.generated, 5
-                yield self.generated, 2
-
-            def generated(self, value):
-                assert self.classsetup
-                assert self.methsetup == self.test_generate
-                assert value == 5
-    """)
-    reprec.assertoutcome(passed=1, failed=1)
-
-def test_func_generator_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        import sys
-
-        def setup_module(mod):
-            print ("setup_module")
-            mod.x = []
-
-        def setup_function(fun):
-            print ("setup_function")
-            x.append(1)
-
-        def teardown_function(fun):
-            print ("teardown_function")
-            x.pop()
-
-        def test_one():
-            assert x == [1]
-            def check():
-                print ("check")
-                sys.stderr.write("e\\n")
-                assert x == [1]
-            yield check
-            assert x == [1]
-    """)
-    rep = reprec.matchreport("test_one", names="pytest_runtest_logreport")
-    assert rep.passed
-
-def test_method_setup_uses_fresh_instances(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSelfState1:
-            memory = []
-            def test_hello(self):
-                self.memory.append(self)
-
-            def test_afterhello(self):
-                assert self != self.memory[0]
-    """)
-    reprec.assertoutcome(passed=2, failed=0)
-
-def test_setup_that_skips_calledagain(testdir):
-    p = testdir.makepyfile("""
-        import pytest
-        def setup_module(mod):
-            pytest.skip("x")
-        def test_function1():
-            pass
-        def test_function2():
-            pass
-    """)
-    reprec = testdir.inline_run(p)
-    reprec.assertoutcome(skipped=2)
-
-def test_setup_fails_again_on_all_tests(testdir):
-    p = testdir.makepyfile("""
-        import pytest
-        def setup_module(mod):
-            raise ValueError(42)
-        def test_function1():
-            pass
-        def test_function2():
-            pass
-    """)
-    reprec = testdir.inline_run(p)
-    reprec.assertoutcome(failed=2)
-
-def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
-    p = testdir.makepyfile("""
-        import pytest
-        def setup_module(mod):
-            raise ValueError(42)
-        def pytest_funcarg__hello(request):
-            raise ValueError("xyz43")
-        def test_function1(hello):
-            pass
-        def test_function2(hello):
-            pass
-    """)
-    result = testdir.runpytest(p)
-    result.stdout.fnmatch_lines([
-        "*function1*",
-        "*ValueError*42*",
-        "*function2*",
-        "*ValueError*42*",
-        "*2 error*"
-    ])
-    assert "xyz43" not in result.stdout.str()
diff --git a/tools/pytest/testing/test_session.py b/tools/pytest/testing/test_session.py
deleted file mode 100644
index 76f804b..0000000
--- a/tools/pytest/testing/test_session.py
+++ /dev/null
@@ -1,244 +0,0 @@
-import pytest
-
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-
-class SessionTests:
-    def test_basic_testitem_events(self, testdir):
-        tfile = testdir.makepyfile("""
-            def test_one():
-                pass
-            def test_one_one():
-                assert 0
-            def test_other():
-                raise ValueError(23)
-            class TestClass:
-                def test_two(self, someargs):
-                    pass
-        """)
-        reprec = testdir.inline_run(tfile)
-        passed, skipped, failed = reprec.listoutcomes()
-        assert len(skipped) == 0
-        assert len(passed) == 1
-        assert len(failed) == 3
-        end = lambda x: x.nodeid.split("::")[-1]
-        assert end(failed[0]) == "test_one_one"
-        assert end(failed[1]) == "test_other"
-        itemstarted = reprec.getcalls("pytest_itemcollected")
-        assert len(itemstarted) == 4
-        # XXX check for failing funcarg setup
-        #colreports = reprec.getcalls("pytest_collectreport")
-        #assert len(colreports) == 4
-        #assert colreports[1].report.failed
-
-    def test_nested_import_error(self, testdir):
-        tfile = testdir.makepyfile("""
-            import import_fails
-            def test_this():
-                assert import_fails.a == 1
-        """, import_fails="""
-            import does_not_work
-            a = 1
-        """)
-        reprec = testdir.inline_run(tfile)
-        l = reprec.getfailedcollections()
-        assert len(l) == 1
-        out = l[0].longrepr.reprcrash.message
-        assert out.find('does_not_work') != -1
-
-    def test_raises_output(self, testdir):
-        reprec = testdir.inline_runsource("""
-            import pytest
-            def test_raises_doesnt():
-                pytest.raises(ValueError, int, "3")
-        """)
-        passed, skipped, failed = reprec.listoutcomes()
-        assert len(failed) == 1
-        out = failed[0].longrepr.reprcrash.message
-        if not out.find("DID NOT RAISE") != -1:
-            print(out)
-            pytest.fail("incorrect raises() output")
-
-    def test_generator_yields_None(self, testdir):
-        reprec = testdir.inline_runsource("""
-            def test_1():
-                yield None
-        """)
-        failures = reprec.getfailedcollections()
-        out = failures[0].longrepr.reprcrash.message
-        i = out.find('TypeError')
-        assert i != -1
-
-    def test_syntax_error_module(self, testdir):
-        reprec = testdir.inline_runsource("this is really not python")
-        l = reprec.getfailedcollections()
-        assert len(l) == 1
-        out = str(l[0].longrepr)
-        assert out.find(str('not python')) != -1
-
-    def test_exit_first_problem(self, testdir):
-        reprec = testdir.inline_runsource("""
-            def test_one(): assert 0
-            def test_two(): assert 0
-        """, '--exitfirst')
-        passed, skipped, failed = reprec.countoutcomes()
-        assert failed == 1
-        assert passed == skipped == 0
-
-    def test_maxfail(self, testdir):
-        reprec = testdir.inline_runsource("""
-            def test_one(): assert 0
-            def test_two(): assert 0
-            def test_three(): assert 0
-        """, '--maxfail=2')
-        passed, skipped, failed = reprec.countoutcomes()
-        assert failed == 2
-        assert passed == skipped == 0
-
-    def test_broken_repr(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            class BrokenRepr1:
-                foo=0
-                def __repr__(self):
-                    raise Exception("Ha Ha fooled you, I'm a broken repr().")
-
-            class TestBrokenClass:
-                def test_explicit_bad_repr(self):
-                    t = BrokenRepr1()
-                    pytest.raises(Exception, 'repr(t)')
-
-                def test_implicit_bad_repr1(self):
-                    t = BrokenRepr1()
-                    assert t.foo == 1
-
-        """)
-        reprec = testdir.inline_run(p)
-        passed, skipped, failed = reprec.listoutcomes()
-        assert len(failed) == 1
-        out = failed[0].longrepr.reprcrash.message
-        assert out.find("""[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]""") != -1 #'
-
-    def test_skip_file_by_conftest(self, testdir):
-        testdir.makepyfile(conftest="""
-            import pytest
-            def pytest_collect_file():
-                pytest.skip("intentional")
-        """, test_file="""
-            def test_one(): pass
-        """)
-        try:
-            reprec = testdir.inline_run(testdir.tmpdir)
-        except pytest.skip.Exception:
-            pytest.fail("wrong skipped caught")
-        reports = reprec.getreports("pytest_collectreport")
-        assert len(reports) == 1
-        assert reports[0].skipped
-
-class TestNewSession(SessionTests):
-
-    def test_order_of_execution(self, testdir):
-        reprec = testdir.inline_runsource("""
-            l = []
-            def test_1():
-                l.append(1)
-            def test_2():
-                l.append(2)
-            def test_3():
-                assert l == [1,2]
-            class Testmygroup:
-                reslist = l
-                def test_1(self):
-                    self.reslist.append(1)
-                def test_2(self):
-                    self.reslist.append(2)
-                def test_3(self):
-                    self.reslist.append(3)
-                def test_4(self):
-                    assert self.reslist == [1,2,1,2,3]
-        """)
-        passed, skipped, failed = reprec.countoutcomes()
-        assert failed == skipped == 0
-        assert passed == 7
-        # also test listnames() here ...
-
-    def test_collect_only_with_various_situations(self, testdir):
-        p = testdir.makepyfile(
-            test_one="""
-                def test_one():
-                    raise ValueError()
-
-                class TestX:
-                    def test_method_one(self):
-                        pass
-
-                class TestY(TestX):
-                    pass
-            """,
-            test_two="""
-                import pytest
-                pytest.skip('xxx')
-            """,
-            test_three="xxxdsadsadsadsa",
-            __init__=""
-        )
-        reprec = testdir.inline_run('--collect-only', p.dirpath())
-
-        itemstarted = reprec.getcalls("pytest_itemcollected")
-        assert len(itemstarted) == 3
-        assert not reprec.getreports("pytest_runtest_logreport")
-        started = reprec.getcalls("pytest_collectstart")
-        finished = reprec.getreports("pytest_collectreport")
-        assert len(started) == len(finished)
-        assert len(started) == 8 # XXX extra TopCollector
-        colfail = [x for x in finished if x.failed]
-        colskipped = [x for x in finished if x.skipped]
-        assert len(colfail) == 1
-        assert len(colskipped) == 1
-
-    def test_minus_x_import_error(self, testdir):
-        testdir.makepyfile(__init__="")
-        testdir.makepyfile(test_one="xxxx", test_two="yyyy")
-        reprec = testdir.inline_run("-x", testdir.tmpdir)
-        finished = reprec.getreports("pytest_collectreport")
-        colfail = [x for x in finished if x.failed]
-        assert len(colfail) == 1
-
-
-def test_plugin_specify(testdir):
-    pytest.raises(ImportError, """
-            testdir.parseconfig("-p", "nqweotexistent")
-    """)
-    #pytest.raises(ImportError,
-    #    "config.do_configure(config)"
-    #)
-
-def test_plugin_already_exists(testdir):
-    config = testdir.parseconfig("-p", "terminal")
-    assert config.option.plugins == ['terminal']
-    config._do_configure()
-    config._ensure_unconfigure()
-
-def test_exclude(testdir):
-    hellodir = testdir.mkdir("hello")
-    hellodir.join("test_hello.py").write("x y syntaxerror")
-    hello2dir = testdir.mkdir("hello2")
-    hello2dir.join("test_hello2.py").write("x y syntaxerror")
-    testdir.makepyfile(test_ok="def test_pass(): pass")
-    result = testdir.runpytest("--ignore=hello", "--ignore=hello2")
-    assert result.ret == 0
-    result.stdout.fnmatch_lines(["*1 passed*"])
-
-def test_sessionfinish_with_start(testdir):
-    testdir.makeconftest("""
-        import os
-        l = []
-        def pytest_sessionstart():
-            l.append(os.getcwd())
-            os.chdir("..")
-
-        def pytest_sessionfinish():
-            assert l[0] == os.getcwd()
-
-    """)
-    res = testdir.runpytest("--collect-only")
-    assert res.ret == EXIT_NOTESTSCOLLECTED
diff --git a/tools/pytest/testing/test_skipping.py b/tools/pytest/testing/test_skipping.py
deleted file mode 100644
index 3464974..0000000
--- a/tools/pytest/testing/test_skipping.py
+++ /dev/null
@@ -1,917 +0,0 @@
-import pytest
-import sys
-
-from _pytest.skipping import MarkEvaluator, folded_skips, pytest_runtest_setup
-from _pytest.runner import runtestprotocol
-
-
-class TestEvaluator:
-    def test_no_marker(self, testdir):
-        item = testdir.getitem("def test_func(): pass")
-        evalskipif = MarkEvaluator(item, 'skipif')
-        assert not evalskipif
-        assert not evalskipif.istrue()
-
-    def test_marked_no_args(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.xyz
-            def test_func():
-                pass
-        """)
-        ev = MarkEvaluator(item, 'xyz')
-        assert ev
-        assert ev.istrue()
-        expl = ev.getexplanation()
-        assert expl == ""
-        assert not ev.get("run", False)
-
-    def test_marked_one_arg(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.xyz("hasattr(os, 'sep')")
-            def test_func():
-                pass
-        """)
-        ev = MarkEvaluator(item, 'xyz')
-        assert ev
-        assert ev.istrue()
-        expl = ev.getexplanation()
-        assert expl == "condition: hasattr(os, 'sep')"
-
-    @pytest.mark.skipif('sys.version_info[0] >= 3')
-    def test_marked_one_arg_unicode(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.xyz(u"hasattr(os, 'sep')")
-            def test_func():
-                pass
-        """)
-        ev = MarkEvaluator(item, 'xyz')
-        assert ev
-        assert ev.istrue()
-        expl = ev.getexplanation()
-        assert expl == "condition: hasattr(os, 'sep')"
-
-    def test_marked_one_arg_with_reason(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.xyz("hasattr(os, 'sep')", attr=2, reason="hello world")
-            def test_func():
-                pass
-        """)
-        ev = MarkEvaluator(item, 'xyz')
-        assert ev
-        assert ev.istrue()
-        expl = ev.getexplanation()
-        assert expl == "hello world"
-        assert ev.get("attr") == 2
-
-    def test_marked_one_arg_twice(self, testdir):
-        lines = [
-            '''@pytest.mark.skipif("not hasattr(os, 'murks')")''',
-            '''@pytest.mark.skipif("hasattr(os, 'murks')")'''
-        ]
-        for i in range(0, 2):
-            item = testdir.getitem("""
-                import pytest
-                %s
-                %s
-                def test_func():
-                    pass
-            """ % (lines[i], lines[(i+1) %2]))
-            ev = MarkEvaluator(item, 'skipif')
-            assert ev
-            assert ev.istrue()
-            expl = ev.getexplanation()
-            assert expl == "condition: not hasattr(os, 'murks')"
-
-    def test_marked_one_arg_twice2(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.skipif("hasattr(os, 'murks')")
-            @pytest.mark.skipif("not hasattr(os, 'murks')")
-            def test_func():
-                pass
-        """)
-        ev = MarkEvaluator(item, 'skipif')
-        assert ev
-        assert ev.istrue()
-        expl = ev.getexplanation()
-        assert expl == "condition: not hasattr(os, 'murks')"
-
-    def test_marked_skip_with_not_string(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.skipif(False)
-            def test_func():
-                pass
-        """)
-        ev = MarkEvaluator(item, 'skipif')
-        exc = pytest.raises(pytest.fail.Exception, ev.istrue)
-        assert """Failed: you need to specify reason=STRING when using booleans as conditions.""" in exc.value.msg
-
-    def test_skipif_class(self, testdir):
-        item, = testdir.getitems("""
-            import pytest
-            class TestClass:
-                pytestmark = pytest.mark.skipif("config._hackxyz")
-                def test_func(self):
-                    pass
-        """)
-        item.config._hackxyz = 3
-        ev = MarkEvaluator(item, 'skipif')
-        assert ev.istrue()
-        expl = ev.getexplanation()
-        assert expl == "condition: config._hackxyz"
-
-
-class TestXFail:
-
-    @pytest.mark.parametrize('strict', [True, False])
-    def test_xfail_simple(self, testdir, strict):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.xfail(strict=%s)
-            def test_func():
-                assert 0
-        """ % strict)
-        reports = runtestprotocol(item, log=False)
-        assert len(reports) == 3
-        callreport = reports[1]
-        assert callreport.skipped
-        assert callreport.wasxfail == ""
-
-    def test_xfail_xpassed(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.xfail
-            def test_func():
-                assert 1
-        """)
-        reports = runtestprotocol(item, log=False)
-        assert len(reports) == 3
-        callreport = reports[1]
-        assert callreport.failed
-        assert callreport.wasxfail == ""
-
-    def test_xfail_run_anyway(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.xfail
-            def test_func():
-                assert 0
-            def test_func2():
-                pytest.xfail("hello")
-        """)
-        result = testdir.runpytest("--runxfail")
-        result.stdout.fnmatch_lines([
-            "*def test_func():*",
-            "*assert 0*",
-            "*1 failed*1 pass*",
-        ])
-
-    def test_xfail_evalfalse_but_fails(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.xfail('False')
-            def test_func():
-                assert 0
-        """)
-        reports = runtestprotocol(item, log=False)
-        callreport = reports[1]
-        assert callreport.failed
-        assert not hasattr(callreport, "wasxfail")
-        assert 'xfail' in callreport.keywords
-
-    def test_xfail_not_report_default(self, testdir):
-        p = testdir.makepyfile(test_one="""
-            import pytest
-            @pytest.mark.xfail
-            def test_this():
-                assert 0
-        """)
-        testdir.runpytest(p, '-v')
-        #result.stdout.fnmatch_lines([
-        #    "*HINT*use*-r*"
-        #])
-
-    def test_xfail_not_run_xfail_reporting(self, testdir):
-        p = testdir.makepyfile(test_one="""
-            import pytest
-            @pytest.mark.xfail(run=False, reason="noway")
-            def test_this():
-                assert 0
-            @pytest.mark.xfail("True", run=False)
-            def test_this_true():
-                assert 0
-            @pytest.mark.xfail("False", run=False, reason="huh")
-            def test_this_false():
-                assert 1
-        """)
-        result = testdir.runpytest(p, '--report=xfailed', )
-        result.stdout.fnmatch_lines([
-            "*test_one*test_this*",
-            "*NOTRUN*noway",
-            "*test_one*test_this_true*",
-            "*NOTRUN*condition:*True*",
-            "*1 passed*",
-        ])
-
-    def test_xfail_not_run_no_setup_run(self, testdir):
-        p = testdir.makepyfile(test_one="""
-            import pytest
-            @pytest.mark.xfail(run=False, reason="hello")
-            def test_this():
-                assert 0
-            def setup_module(mod):
-                raise ValueError(42)
-        """)
-        result = testdir.runpytest(p, '--report=xfailed', )
-        result.stdout.fnmatch_lines([
-            "*test_one*test_this*",
-            "*NOTRUN*hello",
-            "*1 xfailed*",
-        ])
-
-    def test_xfail_xpass(self, testdir):
-        p = testdir.makepyfile(test_one="""
-            import pytest
-            @pytest.mark.xfail
-            def test_that():
-                assert 1
-        """)
-        result = testdir.runpytest(p, '-rX')
-        result.stdout.fnmatch_lines([
-            "*XPASS*test_that*",
-            "*1 xpassed*"
-        ])
-        assert result.ret == 0
-
-    def test_xfail_imperative(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            def test_this():
-                pytest.xfail("hello")
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*1 xfailed*",
-        ])
-        result = testdir.runpytest(p, "-rx")
-        result.stdout.fnmatch_lines([
-            "*XFAIL*test_this*",
-            "*reason:*hello*",
-        ])
-        result = testdir.runpytest(p, "--runxfail")
-        result.stdout.fnmatch_lines("*1 pass*")
-
-    def test_xfail_imperative_in_setup_function(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            def setup_function(function):
-                pytest.xfail("hello")
-
-            def test_this():
-                assert 0
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*1 xfailed*",
-        ])
-        result = testdir.runpytest(p, "-rx")
-        result.stdout.fnmatch_lines([
-            "*XFAIL*test_this*",
-            "*reason:*hello*",
-        ])
-        result = testdir.runpytest(p, "--runxfail")
-        result.stdout.fnmatch_lines("""
-            *def test_this*
-            *1 fail*
-        """)
-
-    def xtest_dynamic_xfail_set_during_setup(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            def setup_function(function):
-                pytest.mark.xfail(function)
-            def test_this():
-                assert 0
-            def test_that():
-                assert 1
-        """)
-        result = testdir.runpytest(p, '-rxX')
-        result.stdout.fnmatch_lines([
-            "*XFAIL*test_this*",
-            "*XPASS*test_that*",
-        ])
-
-    def test_dynamic_xfail_no_run(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            def pytest_funcarg__arg(request):
-                request.applymarker(pytest.mark.xfail(run=False))
-            def test_this(arg):
-                assert 0
-        """)
-        result = testdir.runpytest(p, '-rxX')
-        result.stdout.fnmatch_lines([
-            "*XFAIL*test_this*",
-            "*NOTRUN*",
-        ])
-
-    def test_dynamic_xfail_set_during_funcarg_setup(self, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            def pytest_funcarg__arg(request):
-                request.applymarker(pytest.mark.xfail)
-            def test_this2(arg):
-                assert 0
-        """)
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            "*1 xfailed*",
-        ])
-
-
-    @pytest.mark.parametrize('expected, actual, matchline',
-                             [('TypeError', 'TypeError', "*1 xfailed*"),
-                              ('(AttributeError, TypeError)', 'TypeError', "*1 xfailed*"),
-                              ('TypeError', 'IndexError', "*1 failed*"),
-                              ('(AttributeError, TypeError)', 'IndexError', "*1 failed*"),
-                              ])
-    def test_xfail_raises(self, expected, actual, matchline, testdir):
-        p = testdir.makepyfile("""
-            import pytest
-            @pytest.mark.xfail(raises=%s)
-            def test_raises():
-                raise %s()
-        """ % (expected, actual))
-        result = testdir.runpytest(p)
-        result.stdout.fnmatch_lines([
-            matchline,
-        ])
-
-    def test_strict_sanity(self, testdir):
-        """sanity check for xfail(strict=True): a failing test should behave
-        exactly like a normal xfail.
-        """
-        p = testdir.makepyfile("""
-            import pytest
-            @pytest.mark.xfail(reason='unsupported feature', strict=True)
-            def test_foo():
-                assert 0
-        """)
-        result = testdir.runpytest(p, '-rxX')
-        result.stdout.fnmatch_lines([
-            '*XFAIL*',
-            '*unsupported feature*',
-        ])
-        assert result.ret == 0
-
-    @pytest.mark.parametrize('strict', [True, False])
-    def test_strict_xfail(self, testdir, strict):
-        p = testdir.makepyfile("""
-            import pytest
-
-            @pytest.mark.xfail(reason='unsupported feature', strict=%s)
-            def test_foo():
-                with open('foo_executed', 'w'): pass  # make sure test executes
-        """ % strict)
-        result = testdir.runpytest(p, '-rxX')
-        if strict:
-            result.stdout.fnmatch_lines([
-                '*test_foo*',
-                '*XPASS(strict)*unsupported feature*',
-            ])
-        else:
-            result.stdout.fnmatch_lines([
-                '*test_strict_xfail*',
-                'XPASS test_strict_xfail.py::test_foo unsupported feature',
-            ])
-        assert result.ret == (1 if strict else 0)
-        assert testdir.tmpdir.join('foo_executed').isfile()
-
-    @pytest.mark.parametrize('strict', [True, False])
-    def test_strict_xfail_condition(self, testdir, strict):
-        p = testdir.makepyfile("""
-            import pytest
-
-            @pytest.mark.xfail(False, reason='unsupported feature', strict=%s)
-            def test_foo():
-                pass
-        """ % strict)
-        result = testdir.runpytest(p, '-rxX')
-        result.stdout.fnmatch_lines('*1 passed*')
-        assert result.ret == 0
-
-    @pytest.mark.parametrize('strict_val', ['true', 'false'])
-    def test_strict_xfail_default_from_file(self, testdir, strict_val):
-        testdir.makeini('''
-            [pytest]
-            xfail_strict = %s
-        ''' % strict_val)
-        p = testdir.makepyfile("""
-            import pytest
-            @pytest.mark.xfail(reason='unsupported feature')
-            def test_foo():
-                pass
-        """)
-        result = testdir.runpytest(p, '-rxX')
-        strict = strict_val == 'true'
-        result.stdout.fnmatch_lines('*1 failed*' if strict else '*1 xpassed*')
-        assert result.ret == (1 if strict else 0)
-
-
-class TestXFailwithSetupTeardown:
-    def test_failing_setup_issue9(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def setup_function(func):
-                assert 0
-
-            @pytest.mark.xfail
-            def test_func():
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*1 xfail*",
-        ])
-
-    def test_failing_teardown_issue9(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def teardown_function(func):
-                assert 0
-
-            @pytest.mark.xfail
-            def test_func():
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*1 xfail*",
-        ])
-
-
-class TestSkip:
-    def test_skip_class(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skip
-            class TestSomething(object):
-                def test_foo(self):
-                    pass
-                def test_bar(self):
-                    pass
-
-            def test_baz():
-                pass
-        """)
-        rec = testdir.inline_run()
-        rec.assertoutcome(skipped=2, passed=1)
-
-    def test_skips_on_false_string(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skip('False')
-            def test_foo():
-                pass
-        """)
-        rec = testdir.inline_run()
-        rec.assertoutcome(skipped=1)
-
-    def test_arg_as_reason(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skip('testing stuff')
-            def test_bar():
-                pass
-        """)
-        result = testdir.runpytest('-rs')
-        result.stdout.fnmatch_lines([
-            "*testing stuff*",
-            "*1 skipped*",
-        ])
-
-    def test_skip_no_reason(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skip
-            def test_foo():
-                pass
-        """)
-        result = testdir.runpytest('-rs')
-        result.stdout.fnmatch_lines([
-            "*unconditional skip*",
-            "*1 skipped*",
-        ])
-
-    def test_skip_with_reason(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skip(reason="for lolz")
-            def test_bar():
-                pass
-        """)
-        result = testdir.runpytest('-rs')
-        result.stdout.fnmatch_lines([
-            "*for lolz*",
-            "*1 skipped*",
-        ])
-
-    def test_only_skips_marked_test(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skip
-            def test_foo():
-                pass
-            @pytest.mark.skip(reason="nothing in particular")
-            def test_bar():
-                pass
-            def test_baz():
-                assert True
-        """)
-        result = testdir.runpytest('-rs')
-        result.stdout.fnmatch_lines([
-            "*nothing in particular*",
-            "*1 passed*2 skipped*",
-        ])
-
-class TestSkipif:
-    def test_skipif_conditional(self, testdir):
-        item = testdir.getitem("""
-            import pytest
-            @pytest.mark.skipif("hasattr(os, 'sep')")
-            def test_func():
-                pass
-        """) # noqa
-        x = pytest.raises(pytest.skip.Exception, lambda:
-                          pytest_runtest_setup(item))
-        assert x.value.msg == "condition: hasattr(os, 'sep')"
-
-    @pytest.mark.parametrize('params', [
-        '"hasattr(sys, \'platform\')"',
-        'True, reason="invalid platform"',
-    ])
-    def test_skipif_reporting(self, testdir, params):
-        p = testdir.makepyfile(test_foo="""
-            import pytest
-            @pytest.mark.skipif(%(params)s)
-            def test_that():
-                assert 0
-        """ % dict(params=params))
-        result = testdir.runpytest(p, '-s', '-rs')
-        result.stdout.fnmatch_lines([
-            "*SKIP*1*test_foo.py*platform*",
-            "*1 skipped*"
-        ])
-        assert result.ret == 0
-
-    @pytest.mark.parametrize('marker, msg1, msg2', [
-        ('skipif', 'SKIP', 'skipped'),
-        ('xfail', 'XPASS', 'xpassed'),
-    ])
-    def test_skipif_reporting_multiple(self, testdir, marker, msg1, msg2):
-        testdir.makepyfile(test_foo="""
-            import pytest
-            @pytest.mark.{marker}(False, reason='first_condition')
-            @pytest.mark.{marker}(True, reason='second_condition')
-            def test_foobar():
-                assert 1
-        """.format(marker=marker))
-        result = testdir.runpytest('-s', '-rsxX')
-        result.stdout.fnmatch_lines([
-            "*{msg1}*test_foo.py*second_condition*".format(msg1=msg1),
-            "*1 {msg2}*".format(msg2=msg2),
-        ])
-        assert result.ret == 0
-
-
-def test_skip_not_report_default(testdir):
-    p = testdir.makepyfile(test_one="""
-        import pytest
-        def test_this():
-            pytest.skip("hello")
-    """)
-    result = testdir.runpytest(p, '-v')
-    result.stdout.fnmatch_lines([
-        #"*HINT*use*-r*",
-        "*1 skipped*",
-    ])
-
-
-def test_skipif_class(testdir):
-    p = testdir.makepyfile("""
-        import pytest
-
-        class TestClass:
-            pytestmark = pytest.mark.skipif("True")
-            def test_that(self):
-                assert 0
-            def test_though(self):
-                assert 0
-    """)
-    result = testdir.runpytest(p)
-    result.stdout.fnmatch_lines([
-        "*2 skipped*"
-    ])
-
-
-def test_skip_reasons_folding():
-    path = 'xyz'
-    lineno = 3
-    message = "justso"
-    longrepr = (path, lineno, message)
-
-    class X:
-        pass
-    ev1 = X()
-    ev1.when = "execute"
-    ev1.skipped = True
-    ev1.longrepr = longrepr
-
-    ev2 = X()
-    ev2.longrepr = longrepr
-    ev2.skipped = True
-
-    l = folded_skips([ev1, ev2])
-    assert len(l) == 1
-    num, fspath, lineno, reason = l[0]
-    assert num == 2
-    assert fspath == path
-    assert lineno == lineno
-    assert reason == message
-
-def test_skipped_reasons_functional(testdir):
-    testdir.makepyfile(
-        test_one="""
-            from conftest import doskip
-            def setup_function(func):
-                doskip()
-            def test_func():
-                pass
-            class TestClass:
-                def test_method(self):
-                    doskip()
-       """,
-       test_two = """
-            from conftest import doskip
-            doskip()
-       """,
-       conftest = """
-            import pytest
-            def doskip():
-                pytest.skip('test')
-        """
-    )
-    result = testdir.runpytest('--report=skipped')
-    result.stdout.fnmatch_lines([
-        "*SKIP*3*conftest.py:3: test",
-    ])
-    assert result.ret == 0
-
-def test_reportchars(testdir):
-    testdir.makepyfile("""
-        import pytest
-        def test_1():
-            assert 0
-        @pytest.mark.xfail
-        def test_2():
-            assert 0
-        @pytest.mark.xfail
-        def test_3():
-            pass
-        def test_4():
-            pytest.skip("four")
-    """)
-    result = testdir.runpytest("-rfxXs")
-    result.stdout.fnmatch_lines([
-        "FAIL*test_1*",
-        "XFAIL*test_2*",
-        "XPASS*test_3*",
-        "SKIP*four*",
-    ])
-
-def test_reportchars_error(testdir):
-    testdir.makepyfile(
-        conftest="""
-        def pytest_runtest_teardown():
-            assert 0
-        """,
-        test_simple="""
-        def test_foo():
-            pass
-        """)
-    result = testdir.runpytest('-rE')
-    result.stdout.fnmatch_lines([
-        'ERROR*test_foo*',
-    ])
-
-def test_reportchars_all(testdir):
-    testdir.makepyfile("""
-        import pytest
-        def test_1():
-            assert 0
-        @pytest.mark.xfail
-        def test_2():
-            assert 0
-        @pytest.mark.xfail
-        def test_3():
-            pass
-        def test_4():
-            pytest.skip("four")
-    """)
-    result = testdir.runpytest("-ra")
-    result.stdout.fnmatch_lines([
-        "FAIL*test_1*",
-        "SKIP*four*",
-        "XFAIL*test_2*",
-        "XPASS*test_3*",
-    ])
-
-def test_reportchars_all_error(testdir):
-    testdir.makepyfile(
-        conftest="""
-        def pytest_runtest_teardown():
-            assert 0
-        """,
-        test_simple="""
-        def test_foo():
-            pass
-        """)
-    result = testdir.runpytest('-ra')
-    result.stdout.fnmatch_lines([
-        'ERROR*test_foo*',
-    ])
-
-@pytest.mark.xfail("hasattr(sys, 'pypy_version_info')")
-def test_errors_in_xfail_skip_expressions(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.skipif("asd")
-        def test_nameerror():
-            pass
-        @pytest.mark.xfail("syntax error")
-        def test_syntax():
-            pass
-
-        def test_func():
-            pass
-    """)
-    result = testdir.runpytest()
-    markline = "                ^"
-    if sys.platform.startswith("java"):
-        # XXX report this to java
-        markline = "*" + markline[8:]
-    result.stdout.fnmatch_lines([
-        "*ERROR*test_nameerror*",
-        "*evaluating*skipif*expression*",
-        "*asd*",
-        "*ERROR*test_syntax*",
-        "*evaluating*xfail*expression*",
-        "    syntax error",
-        markline,
-        "SyntaxError: invalid syntax",
-        "*1 pass*2 error*",
-    ])
-
-def test_xfail_skipif_with_globals(testdir):
-    testdir.makepyfile("""
-        import pytest
-        x = 3
-        @pytest.mark.skipif("x == 3")
-        def test_skip1():
-            pass
-        @pytest.mark.xfail("x == 3")
-        def test_boolean():
-            assert 0
-    """)
-    result = testdir.runpytest("-rsx")
-    result.stdout.fnmatch_lines([
-        "*SKIP*x == 3*",
-        "*XFAIL*test_boolean*",
-        "*x == 3*",
-    ])
-
-def test_direct_gives_error(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.skipif(True)
-        def test_skip1():
-            pass
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*1 error*",
-    ])
-
-
-def test_default_markers(testdir):
-    result = testdir.runpytest("--markers")
-    result.stdout.fnmatch_lines([
-        "*skipif(*condition)*skip*",
-        "*xfail(*condition, reason=None, run=True, raises=None)*expected failure*",
-    ])
-
-def test_xfail_test_setup_exception(testdir):
-    testdir.makeconftest("""
-            def pytest_runtest_setup():
-                0 / 0
-        """)
-    p = testdir.makepyfile("""
-            import pytest
-            @pytest.mark.xfail
-            def test_func():
-                assert 0
-        """)
-    result = testdir.runpytest(p)
-    assert result.ret == 0
-    assert 'xfailed' in result.stdout.str()
-    assert 'xpassed' not in result.stdout.str()
-
-def test_imperativeskip_on_xfail_test(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.xfail
-        def test_that_fails():
-            assert 0
-
-        @pytest.mark.skipif("True")
-        def test_hello():
-            pass
-    """)
-    testdir.makeconftest("""
-        import pytest
-        def pytest_runtest_setup(item):
-            pytest.skip("abc")
-    """)
-    result = testdir.runpytest("-rsxX")
-    result.stdout.fnmatch_lines_random("""
-        *SKIP*abc*
-        *SKIP*condition: True*
-        *2 skipped*
-    """)
-
-class TestBooleanCondition:
-    def test_skipif(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skipif(True, reason="True123")
-            def test_func1():
-                pass
-            @pytest.mark.skipif(False, reason="True123")
-            def test_func2():
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines("""
-            *1 passed*1 skipped*
-        """)
-
-    def test_skipif_noreason(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.skipif(True)
-            def test_func():
-                pass
-        """)
-        result = testdir.runpytest("-rs")
-        result.stdout.fnmatch_lines("""
-            *1 error*
-        """)
-
-    def test_xfail(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            @pytest.mark.xfail(True, reason="True123")
-            def test_func():
-                assert 0
-        """)
-        result = testdir.runpytest("-rxs")
-        result.stdout.fnmatch_lines("""
-            *XFAIL*
-            *True123*
-            *1 xfail*
-        """)
-
-
-def test_xfail_item(testdir):
-    # Ensure pytest.xfail works with non-Python Item
-    testdir.makeconftest("""
-        import pytest
-
-        class MyItem(pytest.Item):
-            nodeid = 'foo'
-            def runtest(self):
-                pytest.xfail("Expected Failure")
-
-        def pytest_collect_file(path, parent):
-            return MyItem("foo", parent)
-    """)
-    result = testdir.inline_run()
-    passed, skipped, failed = result.listoutcomes()
-    assert not failed
-    xfailed = [r for r in skipped if hasattr(r, 'wasxfail')]
-    assert xfailed
diff --git a/tools/pytest/testing/test_terminal.py b/tools/pytest/testing/test_terminal.py
deleted file mode 100644
index b43d6b3..0000000
--- a/tools/pytest/testing/test_terminal.py
+++ /dev/null
@@ -1,880 +0,0 @@
-"""
-terminal reporting of the full testing process.
-"""
-import collections
-import sys
-
-import _pytest._pluggy as pluggy
-import _pytest._code
-import py
-import pytest
-from _pytest import runner
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt
-from _pytest.terminal import build_summary_stats_line, _plugin_nameversions
-
-
-def basic_run_report(item):
-    runner.call_and_report(item, "setup", log=False)
-    return runner.call_and_report(item, "call", log=False)
-
-DistInfo = collections.namedtuple('DistInfo', ['project_name', 'version'])
-
-
-class Option:
-    def __init__(self, verbose=False, fulltrace=False):
-        self.verbose = verbose
-        self.fulltrace = fulltrace
-
-    @property
-    def args(self):
-        l = []
-        if self.verbose:
-            l.append('-v')
-        if self.fulltrace:
-            l.append('--fulltrace')
-        return l
-
-def pytest_generate_tests(metafunc):
-    if "option" in metafunc.fixturenames:
-        metafunc.addcall(id="default",
-                         funcargs={'option': Option(verbose=False)})
-        metafunc.addcall(id="verbose",
-                         funcargs={'option': Option(verbose=True)})
-        metafunc.addcall(id="quiet",
-                         funcargs={'option': Option(verbose= -1)})
-        metafunc.addcall(id="fulltrace",
-                         funcargs={'option': Option(fulltrace=True)})
-
-
-@pytest.mark.parametrize('input,expected', [
-    ([DistInfo(project_name='test', version=1)], ['test-1']),
-    ([DistInfo(project_name='pytest-test', version=1)], ['test-1']),
-    ([
-        DistInfo(project_name='test', version=1),
-        DistInfo(project_name='test', version=1)
-    ], ['test-1']),
-], ids=['normal', 'prefix-strip', 'deduplicate'])
-def test_plugin_nameversion(input, expected):
-    pluginlist = [(None, x) for x in input]
-    result = _plugin_nameversions(pluginlist)
-    assert result == expected
-
-
-class TestTerminal:
-    def test_pass_skip_fail(self, testdir, option):
-        testdir.makepyfile("""
-            import pytest
-            def test_ok():
-                pass
-            def test_skip():
-                pytest.skip("xx")
-            def test_func():
-                assert 0
-        """)
-        result = testdir.runpytest(*option.args)
-        if option.verbose:
-            result.stdout.fnmatch_lines([
-                "*test_pass_skip_fail.py::test_ok PASS*",
-                "*test_pass_skip_fail.py::test_skip SKIP*",
-                "*test_pass_skip_fail.py::test_func FAIL*",
-            ])
-        else:
-            result.stdout.fnmatch_lines([
-            "*test_pass_skip_fail.py .sF"
-        ])
-        result.stdout.fnmatch_lines([
-            "    def test_func():",
-            ">       assert 0",
-            "E       assert 0",
-        ])
-
-    def test_internalerror(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("def test_one(): pass")
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        excinfo = pytest.raises(ValueError, "raise ValueError('hello')")
-        rep.pytest_internalerror(excinfo.getrepr())
-        linecomp.assert_contains_lines([
-            "INTERNALERROR> *ValueError*hello*"
-        ])
-
-    def test_writeline(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("def test_one(): pass")
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        rep.write_fspath_result(modcol.nodeid, ".")
-        rep.write_line("hello world")
-        lines = linecomp.stringio.getvalue().split('\n')
-        assert not lines[0]
-        assert lines[1].endswith(modcol.name + " .")
-        assert lines[2] == "hello world"
-
-    def test_show_runtest_logstart(self, testdir, linecomp):
-        item = testdir.getitem("def test_func(): pass")
-        tr = TerminalReporter(item.config, file=linecomp.stringio)
-        item.config.pluginmanager.register(tr)
-        location = item.reportinfo()
-        tr.config.hook.pytest_runtest_logstart(nodeid=item.nodeid,
-            location=location, fspath=str(item.fspath))
-        linecomp.assert_contains_lines([
-            "*test_show_runtest_logstart.py*"
-        ])
-
-    def test_runtest_location_shown_before_test_starts(self, testdir):
-        testdir.makepyfile("""
-            def test_1():
-                import time
-                time.sleep(20)
-        """)
-        child = testdir.spawn_pytest("")
-        child.expect(".*test_runtest_location.*py")
-        child.sendeof()
-        child.kill(15)
-
-    def test_itemreport_subclasses_show_subclassed_file(self, testdir):
-        testdir.makepyfile(test_p1="""
-            class BaseTests:
-                def test_p1(self):
-                    pass
-            class TestClass(BaseTests):
-                pass
-        """)
-        p2 = testdir.makepyfile(test_p2="""
-            from test_p1 import BaseTests
-            class TestMore(BaseTests):
-                pass
-        """)
-        result = testdir.runpytest(p2)
-        result.stdout.fnmatch_lines([
-            "*test_p2.py .",
-            "*1 passed*",
-        ])
-        result = testdir.runpytest("-v", p2)
-        result.stdout.fnmatch_lines([
-            "*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED",
-        ])
-
-    def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir):
-        a = testdir.mkpydir("a123")
-        a.join("test_hello123.py").write(_pytest._code.Source("""
-            class TestClass:
-                def test_method(self):
-                    pass
-        """))
-        result = testdir.runpytest("-v")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*a123/test_hello123.py*PASS*",
-        ])
-        assert " <- " not in result.stdout.str()
-
-    def test_keyboard_interrupt(self, testdir, option):
-        testdir.makepyfile("""
-            def test_foobar():
-                assert 0
-            def test_spamegg():
-                import py; pytest.skip('skip me please!')
-            def test_interrupt_me():
-                raise KeyboardInterrupt   # simulating the user
-        """)
-
-        result = testdir.runpytest(*option.args, no_reraise_ctrlc=True)
-        result.stdout.fnmatch_lines([
-            "    def test_foobar():",
-            ">       assert 0",
-            "E       assert 0",
-            "*_keyboard_interrupt.py:6: KeyboardInterrupt*",
-        ])
-        if option.fulltrace:
-            result.stdout.fnmatch_lines([
-                "*raise KeyboardInterrupt   # simulating the user*",
-            ])
-        else:
-            result.stdout.fnmatch_lines([
-                "to show a full traceback on KeyboardInterrupt use --fulltrace"
-            ])
-        result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
-
-    def test_keyboard_in_sessionstart(self, testdir):
-        testdir.makeconftest("""
-            def pytest_sessionstart():
-                raise KeyboardInterrupt
-        """)
-        testdir.makepyfile("""
-            def test_foobar():
-                pass
-        """)
-
-        result = testdir.runpytest(no_reraise_ctrlc=True)
-        assert result.ret == 2
-        result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
-
-
-class TestCollectonly:
-    def test_collectonly_basic(self, testdir):
-        testdir.makepyfile("""
-            def test_func():
-                pass
-        """)
-        result = testdir.runpytest("--collect-only",)
-        result.stdout.fnmatch_lines([
-           "<Module 'test_collectonly_basic.py'>",
-           "  <Function 'test_func'>",
-        ])
-
-    def test_collectonly_skipped_module(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            pytest.skip("hello")
-        """)
-        result = testdir.runpytest("--collect-only", "-rs")
-        result.stdout.fnmatch_lines([
-            "SKIP*hello*",
-            "*1 skip*",
-        ])
-
-    def test_collectonly_failed_module(self, testdir):
-        testdir.makepyfile("""raise ValueError(0)""")
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*raise ValueError*",
-            "*1 error*",
-        ])
-
-    def test_collectonly_fatal(self, testdir):
-        testdir.makeconftest("""
-            def pytest_collectstart(collector):
-                assert 0, "urgs"
-        """)
-        result = testdir.runpytest("--collect-only")
-        result.stdout.fnmatch_lines([
-            "*INTERNAL*args*"
-        ])
-        assert result.ret == 3
-
-    def test_collectonly_simple(self, testdir):
-        p = testdir.makepyfile("""
-            def test_func1():
-                pass
-            class TestClass:
-                def test_method(self):
-                    pass
-        """)
-        result = testdir.runpytest("--collect-only", p)
-        #assert stderr.startswith("inserting into sys.path")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*<Module '*.py'>",
-            "* <Function 'test_func1'*>",
-            "* <Class 'TestClass'>",
-            #"*  <Instance '()'>",
-            "*   <Function 'test_method'*>",
-        ])
-
-    def test_collectonly_error(self, testdir):
-        p = testdir.makepyfile("import Errlkjqweqwe")
-        result = testdir.runpytest("--collect-only", p)
-        assert result.ret == 1
-        result.stdout.fnmatch_lines(_pytest._code.Source("""
-            *ERROR*
-            *import Errlk*
-            *ImportError*
-            *1 error*
-        """).strip())
-
-    def test_collectonly_missing_path(self, testdir):
-        """this checks issue 115,
-            failure in parseargs will cause session
-            not to have the items attribute
-        """
-        result = testdir.runpytest("--collect-only", "uhm_missing_path")
-        assert result.ret == 4
-        result.stderr.fnmatch_lines([
-            '*ERROR: file not found*',
-        ])
-
-    def test_collectonly_quiet(self, testdir):
-        testdir.makepyfile("def test_foo(): pass")
-        result = testdir.runpytest("--collect-only", "-q")
-        result.stdout.fnmatch_lines([
-            '*test_foo*',
-        ])
-
-    def test_collectonly_more_quiet(self, testdir):
-        testdir.makepyfile(test_fun="def test_foo(): pass")
-        result = testdir.runpytest("--collect-only", "-qq")
-        result.stdout.fnmatch_lines([
-            '*test_fun.py: 1*',
-        ])
-
-
-def test_repr_python_version(monkeypatch):
-    try:
-        monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
-        assert repr_pythonversion() == "2.5.1-final-0"
-        py.std.sys.version_info = x = (2, 3)
-        assert repr_pythonversion() == str(x)
-    finally:
-        monkeypatch.undo() # do this early as pytest can get confused
-
-class TestFixtureReporting:
-    def test_setup_fixture_error(self, testdir):
-        testdir.makepyfile("""
-            def setup_function(function):
-                print ("setup func")
-                assert 0
-            def test_nada():
-                pass
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ERROR at setup of test_nada*",
-            "*setup_function(function):*",
-            "*setup func*",
-            "*assert 0*",
-            "*1 error*",
-        ])
-        assert result.ret != 0
-
-    def test_teardown_fixture_error(self, testdir):
-        testdir.makepyfile("""
-            def test_nada():
-                pass
-            def teardown_function(function):
-                print ("teardown func")
-                assert 0
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ERROR at teardown*",
-            "*teardown_function(function):*",
-            "*assert 0*",
-            "*Captured stdout*",
-            "*teardown func*",
-            "*1 passed*1 error*",
-        ])
-
-    def test_teardown_fixture_error_and_test_failure(self, testdir):
-        testdir.makepyfile("""
-            def test_fail():
-                assert 0, "failingfunc"
-
-            def teardown_function(function):
-                print ("teardown func")
-                assert False
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ERROR at teardown of test_fail*",
-            "*teardown_function(function):*",
-            "*assert False*",
-            "*Captured stdout*",
-            "*teardown func*",
-
-            "*test_fail*",
-            "*def test_fail():",
-            "*failingfunc*",
-            "*1 failed*1 error*",
-         ])
-
-class TestTerminalFunctional:
-    def test_deselected(self, testdir):
-        testpath = testdir.makepyfile("""
-                def test_one():
-                    pass
-                def test_two():
-                    pass
-                def test_three():
-                    pass
-           """
-        )
-        result = testdir.runpytest("-k", "test_two:", testpath)
-        result.stdout.fnmatch_lines([
-            "*test_deselected.py ..",
-            "=* 1 test*deselected by*test_two:*=",
-        ])
-        assert result.ret == 0
-
-    def test_no_skip_summary_if_failure(self, testdir):
-        testdir.makepyfile("""
-            import pytest
-            def test_ok():
-                pass
-            def test_fail():
-                assert 0
-            def test_skip():
-                pytest.skip("dontshow")
-        """)
-        result = testdir.runpytest()
-        assert result.stdout.str().find("skip test summary") == -1
-        assert result.ret == 1
-
-    def test_passes(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_passes():
-                pass
-            class TestClass:
-                def test_method(self):
-                    pass
-        """)
-        old = p1.dirpath().chdir()
-        try:
-            result = testdir.runpytest()
-        finally:
-            old.chdir()
-        result.stdout.fnmatch_lines([
-            "test_passes.py ..",
-            "* 2 pass*",
-        ])
-        assert result.ret == 0
-
-    def test_header_trailer_info(self, testdir):
-        testdir.makepyfile("""
-            def test_passes():
-                pass
-        """)
-        result = testdir.runpytest()
-        verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
-        result.stdout.fnmatch_lines([
-            "*===== test session starts ====*",
-            "platform %s -- Python %s*pytest-%s*py-%s*pluggy-%s" % (
-                py.std.sys.platform, verinfo,
-                pytest.__version__, py.__version__, pluggy.__version__),
-            "*test_header_trailer_info.py .",
-            "=* 1 passed*in *.[0-9][0-9] seconds *=",
-        ])
-        if pytest.config.pluginmanager.list_plugin_distinfo():
-            result.stdout.fnmatch_lines([
-                "plugins: *",
-            ])
-
-    def test_showlocals(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_showlocals():
-                x = 3
-                y = "x" * 5000
-                assert 0
-        """)
-        result = testdir.runpytest(p1, '-l')
-        result.stdout.fnmatch_lines([
-            #"_ _ * Locals *",
-            "x* = 3",
-            "y* = 'xxxxxx*"
-        ])
-
-    def test_verbose_reporting(self, testdir, pytestconfig):
-        p1 = testdir.makepyfile("""
-            import pytest
-            def test_fail():
-                raise ValueError()
-            def test_pass():
-                pass
-            class TestClass:
-                def test_skip(self):
-                    pytest.skip("hello")
-            def test_gen():
-                def check(x):
-                    assert x == 1
-                yield check, 0
-        """)
-        result = testdir.runpytest(p1, '-v')
-        result.stdout.fnmatch_lines([
-            "*test_verbose_reporting.py::test_fail *FAIL*",
-            "*test_verbose_reporting.py::test_pass *PASS*",
-            "*test_verbose_reporting.py::TestClass::test_skip *SKIP*",
-            "*test_verbose_reporting.py::test_gen*0* *FAIL*",
-        ])
-        assert result.ret == 1
-
-        if not pytestconfig.pluginmanager.get_plugin("xdist"):
-            pytest.skip("xdist plugin not installed")
-
-        result = testdir.runpytest(p1, '-v', '-n 1')
-        result.stdout.fnmatch_lines([
-            "*FAIL*test_verbose_reporting.py::test_fail*",
-        ])
-        assert result.ret == 1
-
-    def test_quiet_reporting(self, testdir):
-        p1 = testdir.makepyfile("def test_pass(): pass")
-        result = testdir.runpytest(p1, '-q')
-        s = result.stdout.str()
-        assert 'test session starts' not in s
-        assert p1.basename not in s
-        assert "===" not in s
-        assert "passed" in s
-
-    def test_more_quiet_reporting(self, testdir):
-        p1 = testdir.makepyfile("def test_pass(): pass")
-        result = testdir.runpytest(p1, '-qq')
-        s = result.stdout.str()
-        assert 'test session starts' not in s
-        assert p1.basename not in s
-        assert "===" not in s
-        assert "passed" not in s
-
-
-def test_fail_extra_reporting(testdir):
-    testdir.makepyfile("def test_this(): assert 0")
-    result = testdir.runpytest()
-    assert 'short test summary' not in result.stdout.str()
-    result = testdir.runpytest('-rf')
-    result.stdout.fnmatch_lines([
-        "*test summary*",
-        "FAIL*test_fail_extra_reporting*",
-    ])
-
-def test_fail_reporting_on_pass(testdir):
-    testdir.makepyfile("def test_this(): assert 1")
-    result = testdir.runpytest('-rf')
-    assert 'short test summary' not in result.stdout.str()
-
-def test_pass_extra_reporting(testdir):
-    testdir.makepyfile("def test_this(): assert 1")
-    result = testdir.runpytest()
-    assert 'short test summary' not in result.stdout.str()
-    result = testdir.runpytest('-rp')
-    result.stdout.fnmatch_lines([
-        "*test summary*",
-        "PASS*test_pass_extra_reporting*",
-    ])
-
-def test_pass_reporting_on_fail(testdir):
-    testdir.makepyfile("def test_this(): assert 0")
-    result = testdir.runpytest('-rp')
-    assert 'short test summary' not in result.stdout.str()
-
-def test_pass_output_reporting(testdir):
-    testdir.makepyfile("""
-        def test_pass_output():
-            print("Four score and seven years ago...")
-    """)
-    result = testdir.runpytest()
-    assert 'Four score and seven years ago...' not in result.stdout.str()
-    result = testdir.runpytest('-rP')
-    result.stdout.fnmatch_lines([
-        "Four score and seven years ago...",
-    ])
-
-def test_color_yes(testdir):
-    testdir.makepyfile("def test_this(): assert 1")
-    result = testdir.runpytest('--color=yes')
-    assert 'test session starts' in result.stdout.str()
-    assert '\x1b[1m' in result.stdout.str()
-
-
-def test_color_no(testdir):
-    testdir.makepyfile("def test_this(): assert 1")
-    result = testdir.runpytest('--color=no')
-    assert 'test session starts' in result.stdout.str()
-    assert '\x1b[1m' not in result.stdout.str()
-
-
-@pytest.mark.parametrize('verbose', [True, False])
-def test_color_yes_collection_on_non_atty(testdir, verbose):
-    """skip collect progress report when working on non-terminals.
-    #1397
-    """
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.parametrize('i', range(10))
-        def test_this(i):
-            assert 1
-    """)
-    args = ['--color=yes']
-    if verbose:
-        args.append('-vv')
-    result = testdir.runpytest(*args)
-    assert 'test session starts' in result.stdout.str()
-    assert '\x1b[1m' in result.stdout.str()
-    assert 'collecting 10 items' not in result.stdout.str()
-    if verbose:
-        assert 'collecting ...' in result.stdout.str()
-    assert 'collected 10 items' in result.stdout.str()
-
-
-def test_getreportopt():
-    class config:
-        class option:
-            reportchars = ""
-    config.option.report = "xfailed"
-    assert getreportopt(config) == "x"
-
-    config.option.report = "xfailed,skipped"
-    assert getreportopt(config) == "xs"
-
-    config.option.report = "skipped,xfailed"
-    assert getreportopt(config) == "sx"
-
-    config.option.report = "skipped"
-    config.option.reportchars = "sf"
-    assert getreportopt(config) == "sf"
-
-    config.option.reportchars = "sfx"
-    assert getreportopt(config) == "sfx"
-
-def test_terminalreporter_reportopt_addopts(testdir):
-    testdir.makeini("[pytest]\naddopts=-rs")
-    testdir.makepyfile("""
-        def pytest_funcarg__tr(request):
-            tr = request.config.pluginmanager.getplugin("terminalreporter")
-            return tr
-        def test_opt(tr):
-            assert tr.hasopt('skipped')
-            assert not tr.hasopt('qwe')
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*1 passed*"
-    ])
-
-def test_tbstyle_short(testdir):
-    p = testdir.makepyfile("""
-        def pytest_funcarg__arg(request):
-            return 42
-        def test_opt(arg):
-            x = 0
-            assert x
-    """)
-    result = testdir.runpytest("--tb=short")
-    s = result.stdout.str()
-    assert 'arg = 42' not in s
-    assert 'x = 0' not in s
-    result.stdout.fnmatch_lines([
-        "*%s:5*" % p.basename,
-        "    assert x",
-        "E   assert*",
-    ])
-    result = testdir.runpytest()
-    s = result.stdout.str()
-    assert 'x = 0' in s
-    assert 'assert x' in s
-
-def test_traceconfig(testdir, monkeypatch):
-    result = testdir.runpytest("--traceconfig")
-    result.stdout.fnmatch_lines([
-        "*active plugins*"
-    ])
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-
-
-class TestGenericReporting:
-    """ this test class can be subclassed with a different option
-        provider to run e.g. distributed tests.
-    """
-    def test_collect_fail(self, testdir, option):
-        testdir.makepyfile("import xyz\n")
-        result = testdir.runpytest(*option.args)
-        result.stdout.fnmatch_lines([
-            "?   import xyz",
-            "E   ImportError: No module named *xyz*",
-            "*1 error*",
-        ])
-
-    def test_maxfailures(self, testdir, option):
-        testdir.makepyfile("""
-            def test_1():
-                assert 0
-            def test_2():
-                assert 0
-            def test_3():
-                assert 0
-        """)
-        result = testdir.runpytest("--maxfail=2", *option.args)
-        result.stdout.fnmatch_lines([
-            "*def test_1():*",
-            "*def test_2():*",
-            "*!! Interrupted: stopping after 2 failures*!!*",
-            "*2 failed*",
-        ])
-
-
-    def test_tb_option(self, testdir, option):
-        testdir.makepyfile("""
-            import pytest
-            def g():
-                raise IndexError
-            def test_func():
-                print (6*7)
-                g()  # --calling--
-        """)
-        for tbopt in ["long", "short", "no"]:
-            print('testing --tb=%s...' % tbopt)
-            result = testdir.runpytest('--tb=%s' % tbopt)
-            s = result.stdout.str()
-            if tbopt == "long":
-                assert 'print (6*7)' in s
-            else:
-                assert 'print (6*7)' not in s
-            if tbopt != "no":
-                assert '--calling--' in s
-                assert 'IndexError' in s
-            else:
-                assert 'FAILURES' not in s
-                assert '--calling--' not in s
-                assert 'IndexError' not in s
-
-    def test_tb_crashline(self, testdir, option):
-        p = testdir.makepyfile("""
-            import pytest
-            def g():
-                raise IndexError
-            def test_func1():
-                print (6*7)
-                g()  # --calling--
-            def test_func2():
-                assert 0, "hello"
-        """)
-        result = testdir.runpytest("--tb=line")
-        bn = p.basename
-        result.stdout.fnmatch_lines([
-            "*%s:3: IndexError*" % bn,
-            "*%s:8: AssertionError: hello*" % bn,
-        ])
-        s = result.stdout.str()
-        assert "def test_func2" not in s
-
-    def test_pytest_report_header(self, testdir, option):
-        testdir.makeconftest("""
-            def pytest_sessionstart(session):
-                session.config._somevalue = 42
-            def pytest_report_header(config):
-                return "hello: %s" % config._somevalue
-        """)
-        testdir.mkdir("a").join("conftest.py").write("""
-def pytest_report_header(config, startdir):
-    return ["line1", str(startdir)]
-""")
-        result = testdir.runpytest("a")
-        result.stdout.fnmatch_lines([
-            "*hello: 42*",
-            "line1",
-            str(testdir.tmpdir),
-        ])
-
-@pytest.mark.xfail("not hasattr(os, 'dup')")
-def test_fdopen_kept_alive_issue124(testdir):
-    testdir.makepyfile("""
-        import os, sys
-        k = []
-        def test_open_file_and_keep_alive(capfd):
-            stdout = os.fdopen(1, 'w', 1)
-            k.append(stdout)
-
-        def test_close_kept_alive_file():
-            stdout = k.pop()
-            stdout.close()
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "*2 passed*"
-    ])
-
-def test_tbstyle_native_setup_error(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.fixture
-        def setup_error_fixture():
-            raise Exception("error in exception")
-
-        def test_error_fixture(setup_error_fixture):
-            pass
-    """)
-    result = testdir.runpytest("--tb=native")
-    result.stdout.fnmatch_lines([
-            '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*'
-            ])
-
-def test_terminal_summary(testdir):
-    testdir.makeconftest("""
-        def pytest_terminal_summary(terminalreporter):
-            w = terminalreporter
-            w.section("hello")
-            w.line("world")
-    """)
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines("""
-        *==== hello ====*
-        world
-    """)
-
-
-def test_terminal_summary_warnings_are_displayed(testdir):
-    """Test that warnings emitted during pytest_terminal_summary are displayed.
-    (#1305).
-    """
-    testdir.makeconftest("""
-        def pytest_terminal_summary(terminalreporter):
-            config = terminalreporter.config
-            config.warn('C1', 'internal warning')
-    """)
-    result = testdir.runpytest('-rw')
-    result.stdout.fnmatch_lines([
-        '*C1*internal warning',
-        '*== 1 pytest-warnings in *',
-    ])
-
-
-@pytest.mark.parametrize("exp_color, exp_line, stats_arg", [
-    # The method under test only cares about the length of each
-    # dict value, not the actual contents, so tuples of anything
-    # suffice
-
-    # Important statuses -- the highest priority of these always wins
-    ("red", "1 failed", {"failed": (1,)}),
-    ("red", "1 failed, 1 passed", {"failed": (1,), "passed": (1,)}),
-
-    ("red", "1 error", {"error": (1,)}),
-    ("red", "1 passed, 1 error", {"error": (1,), "passed": (1,)}),
-
-    # (a status that's not known to the code)
-    ("yellow", "1 weird", {"weird": (1,)}),
-    ("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}),
-
-    ("yellow", "1 pytest-warnings", {"warnings": (1,)}),
-    ("yellow", "1 passed, 1 pytest-warnings", {"warnings": (1,),
-                                               "passed": (1,)}),
-
-    ("green", "5 passed", {"passed": (1,2,3,4,5)}),
-
-
-    # "Boring" statuses.  These have no effect on the color of the summary
-    # line.  Thus, if *every* test has a boring status, the summary line stays
-    # at its default color, i.e. yellow, to warn the user that the test run
-    # produced no useful information
-    ("yellow", "1 skipped", {"skipped": (1,)}),
-    ("green", "1 passed, 1 skipped", {"skipped": (1,), "passed": (1,)}),
-
-    ("yellow", "1 deselected", {"deselected": (1,)}),
-    ("green", "1 passed, 1 deselected", {"deselected": (1,), "passed": (1,)}),
-
-    ("yellow", "1 xfailed", {"xfailed": (1,)}),
-    ("green", "1 passed, 1 xfailed", {"xfailed": (1,), "passed": (1,)}),
-
-    ("yellow", "1 xpassed", {"xpassed": (1,)}),
-    ("green", "1 passed, 1 xpassed", {"xpassed": (1,), "passed": (1,)}),
-
-    # Likewise if no tests were found at all
-    ("yellow", "no tests ran", {}),
-
-    # Test the empty-key special case
-    ("yellow", "no tests ran", {"": (1,)}),
-    ("green", "1 passed", {"": (1,), "passed": (1,)}),
-
-
-    # A couple more complex combinations
-    ("red", "1 failed, 2 passed, 3 xfailed",
-        {"passed": (1,2), "failed": (1,), "xfailed": (1,2,3)}),
-
-    ("green", "1 passed, 2 skipped, 3 deselected, 2 xfailed",
-        {"passed": (1,),
-        "skipped": (1,2),
-        "deselected": (1,2,3),
-        "xfailed": (1,2)}),
-])
-def test_summary_stats(exp_line, exp_color, stats_arg):
-    print("Based on stats: %s" % stats_arg)
-    print("Expect summary: \"%s\"; with color \"%s\"" % (exp_line, exp_color))
-    (line, color) = build_summary_stats_line(stats_arg)
-    print("Actually got:   \"%s\"; with color \"%s\"" % (line, color))
-    assert line == exp_line
-    assert color == exp_color
diff --git a/tools/pytest/testing/test_tmpdir.py b/tools/pytest/testing/test_tmpdir.py
deleted file mode 100644
index d514e72..0000000
--- a/tools/pytest/testing/test_tmpdir.py
+++ /dev/null
@@ -1,183 +0,0 @@
-import sys
-import py
-import pytest
-
-from _pytest.tmpdir import tmpdir
-
-def test_funcarg(testdir):
-    testdir.makepyfile("""
-            def pytest_generate_tests(metafunc):
-                metafunc.addcall(id='a')
-                metafunc.addcall(id='b')
-            def test_func(tmpdir): pass
-    """)
-    from _pytest.tmpdir import TempdirFactory
-    reprec = testdir.inline_run()
-    calls = reprec.getcalls("pytest_runtest_setup")
-    item = calls[0].item
-    config = item.config
-    tmpdirhandler = TempdirFactory(config)
-    item._initrequest()
-    p = tmpdir(item._request, tmpdirhandler)
-    assert p.check()
-    bn = p.basename.strip("0123456789")
-    assert bn.endswith("test_func_a_")
-    item.name = "qwe/\\abc"
-    p = tmpdir(item._request, tmpdirhandler)
-    assert p.check()
-    bn = p.basename.strip("0123456789")
-    assert bn == "qwe__abc"
-
-def test_ensuretemp(recwarn):
-    #pytest.deprecated_call(pytest.ensuretemp, 'hello')
-    d1 = pytest.ensuretemp('hello')
-    d2 = pytest.ensuretemp('hello')
-    assert d1 == d2
-    assert d1.check(dir=1)
-
-class TestTempdirHandler:
-    def test_mktemp(self, testdir):
-        from _pytest.tmpdir import TempdirFactory
-        config = testdir.parseconfig()
-        config.option.basetemp = testdir.mkdir("hello")
-        t = TempdirFactory(config)
-        tmp = t.mktemp("world")
-        assert tmp.relto(t.getbasetemp()) == "world0"
-        tmp = t.mktemp("this")
-        assert tmp.relto(t.getbasetemp()).startswith("this")
-        tmp2 = t.mktemp("this")
-        assert tmp2.relto(t.getbasetemp()).startswith("this")
-        assert tmp2 != tmp
-
-class TestConfigTmpdir:
-    def test_getbasetemp_custom_removes_old(self, testdir):
-        mytemp = testdir.tmpdir.join("xyz")
-        p = testdir.makepyfile("""
-            def test_1(tmpdir):
-                pass
-        """)
-        testdir.runpytest(p, '--basetemp=%s' % mytemp)
-        mytemp.check()
-        mytemp.ensure("hello")
-
-        testdir.runpytest(p, '--basetemp=%s' % mytemp)
-        mytemp.check()
-        assert not mytemp.join("hello").check()
-
-
-def test_basetemp(testdir):
-    mytemp = testdir.tmpdir.mkdir("mytemp")
-    p = testdir.makepyfile("""
-        import pytest
-        def test_1():
-            pytest.ensuretemp("hello")
-    """)
-    result = testdir.runpytest(p, '--basetemp=%s' % mytemp)
-    assert result.ret == 0
-    assert mytemp.join('hello').check()
-
-@pytest.mark.skipif(not hasattr(py.path.local, 'mksymlinkto'),
-                    reason="symlink not available on this platform")
-def test_tmpdir_always_is_realpath(testdir):
-    # the reason why tmpdir should be a realpath is that
-    # when you cd to it and do "os.getcwd()" you will anyway
-    # get the realpath.  Using the symlinked path can thus
-    # easily result in path-inequality
-    # XXX if that proves to be a problem, consider using
-    # os.environ["PWD"]
-    realtemp = testdir.tmpdir.mkdir("myrealtemp")
-    linktemp = testdir.tmpdir.join("symlinktemp")
-    linktemp.mksymlinkto(realtemp)
-    p = testdir.makepyfile("""
-        def test_1(tmpdir):
-            import os
-            assert os.path.realpath(str(tmpdir)) == str(tmpdir)
-    """)
-    result = testdir.runpytest("-s", p, '--basetemp=%s/bt' % linktemp)
-    assert not result.ret
-
-
-def test_tmpdir_too_long_on_parametrization(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.mark.parametrize("arg", ["1"*1000])
-        def test_some(arg, tmpdir):
-            tmpdir.ensure("hello")
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-
-def test_tmpdir_factory(testdir):
-    testdir.makepyfile("""
-        import pytest
-        @pytest.fixture(scope='session')
-        def session_dir(tmpdir_factory):
-            return tmpdir_factory.mktemp('data', numbered=False)
-        def test_some(session_dir):
-            session_dir.isdir()
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-
-def test_tmpdir_fallback_tox_env(testdir, monkeypatch):
-    """Test that tmpdir works even if environment variables required by getpass
-    module are missing (#1010).
-    """
-    monkeypatch.delenv('USER', raising=False)
-    monkeypatch.delenv('USERNAME', raising=False)
-    testdir.makepyfile("""
-        import pytest
-        def test_some(tmpdir):
-            assert tmpdir.isdir()
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-
-@pytest.fixture
-def break_getuser(monkeypatch):
-    monkeypatch.setattr('os.getuid', lambda: -1)
-    # taken from python 2.7/3.4
-    for envvar in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):
-        monkeypatch.delenv(envvar, raising=False)
-
-
-@pytest.mark.usefixtures("break_getuser")
-@pytest.mark.skipif(sys.platform.startswith('win'), reason='no os.getuid on windows')
-def test_tmpdir_fallback_uid_not_found(testdir):
-    """Test that tmpdir works even if the current process's user id does not
-    correspond to a valid user.
-    """
-
-    testdir.makepyfile("""
-        import pytest
-        def test_some(tmpdir):
-            assert tmpdir.isdir()
-    """)
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(passed=1)
-
-
-@pytest.mark.usefixtures("break_getuser")
-@pytest.mark.skipif(sys.platform.startswith('win'), reason='no os.getuid on windows')
-def test_get_user_uid_not_found():
-    """Test that get_user() function works even if the current process's
-    user id does not correspond to a valid user (e.g. running pytest in a
-    Docker container with 'docker run -u'.
-    """
-    from _pytest.tmpdir import get_user
-    assert get_user() is None
-
-
-@pytest.mark.skipif(not sys.platform.startswith('win'), reason='win only')
-def test_get_user(monkeypatch):
-    """Test that get_user() function works even if environment variables
-    required by getpass module are missing from the environment on Windows
-    (#1010).
-    """
-    from _pytest.tmpdir import get_user
-    monkeypatch.delenv('USER', raising=False)
-    monkeypatch.delenv('USERNAME', raising=False)
-    assert get_user() is None
diff --git a/tools/pytest/testing/test_unittest.py b/tools/pytest/testing/test_unittest.py
deleted file mode 100644
index 144aad7..0000000
--- a/tools/pytest/testing/test_unittest.py
+++ /dev/null
@@ -1,737 +0,0 @@
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-import pytest
-
-def test_simple_unittest(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        class MyTestCase(unittest.TestCase):
-            def testpassing(self):
-                self.assertEquals('foo', 'foo')
-            def test_failing(self):
-                self.assertEquals('foo', 'bar')
-    """)
-    reprec = testdir.inline_run(testpath)
-    assert reprec.matchreport("testpassing").passed
-    assert reprec.matchreport("test_failing").failed
-
-def test_runTest_method(testdir):
-    testdir.makepyfile("""
-        import unittest
-        class MyTestCaseWithRunTest(unittest.TestCase):
-            def runTest(self):
-                self.assertEquals('foo', 'foo')
-        class MyTestCaseWithoutRunTest(unittest.TestCase):
-            def runTest(self):
-                self.assertEquals('foo', 'foo')
-            def test_something(self):
-                pass
-        """)
-    result = testdir.runpytest("-v")
-    result.stdout.fnmatch_lines("""
-        *MyTestCaseWithRunTest::runTest*
-        *MyTestCaseWithoutRunTest::test_something*
-        *2 passed*
-    """)
-
-def test_isclasscheck_issue53(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        class _E(object):
-            def __getattr__(self, tag):
-                pass
-        E = _E()
-    """)
-    result = testdir.runpytest(testpath)
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-
-def test_setup(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        class MyTestCase(unittest.TestCase):
-            def setUp(self):
-                self.foo = 1
-            def setup_method(self, method):
-                self.foo2 = 1
-            def test_both(self):
-                self.assertEquals(1, self.foo)
-                assert self.foo2 == 1
-            def teardown_method(self, method):
-                assert 0, "42"
-
-    """)
-    reprec = testdir.inline_run("-s", testpath)
-    assert reprec.matchreport("test_both", when="call").passed
-    rep = reprec.matchreport("test_both", when="teardown")
-    assert rep.failed and '42' in str(rep.longrepr)
-
-def test_setUpModule(testdir):
-    testpath = testdir.makepyfile("""
-        l = []
-
-        def setUpModule():
-            l.append(1)
-
-        def tearDownModule():
-            del l[0]
-
-        def test_hello():
-            assert l == [1]
-
-        def test_world():
-            assert l == [1]
-        """)
-    result = testdir.runpytest(testpath)
-    result.stdout.fnmatch_lines([
-        "*2 passed*",
-    ])
-
-def test_setUpModule_failing_no_teardown(testdir):
-    testpath = testdir.makepyfile("""
-        l = []
-
-        def setUpModule():
-            0/0
-
-        def tearDownModule():
-            l.append(1)
-
-        def test_hello():
-            pass
-    """)
-    reprec = testdir.inline_run(testpath)
-    reprec.assertoutcome(passed=0, failed=1)
-    call = reprec.getcalls("pytest_runtest_setup")[0]
-    assert not call.item.module.l
-
-def test_new_instances(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        class MyTestCase(unittest.TestCase):
-            def test_func1(self):
-                self.x = 2
-            def test_func2(self):
-                assert not hasattr(self, 'x')
-    """)
-    reprec = testdir.inline_run(testpath)
-    reprec.assertoutcome(passed=2)
-
-def test_teardown(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        class MyTestCase(unittest.TestCase):
-            l = []
-            def test_one(self):
-                pass
-            def tearDown(self):
-                self.l.append(None)
-        class Second(unittest.TestCase):
-            def test_check(self):
-                self.assertEquals(MyTestCase.l, [None])
-    """)
-    reprec = testdir.inline_run(testpath)
-    passed, skipped, failed = reprec.countoutcomes()
-    assert failed == 0, failed
-    assert passed == 2
-    assert passed + skipped + failed == 2
-
-@pytest.mark.skipif("sys.version_info < (2,7)")
-def test_unittest_skip_issue148(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-
-        @unittest.skip("hello")
-        class MyTestCase(unittest.TestCase):
-            @classmethod
-            def setUpClass(self):
-                xxx
-            def test_one(self):
-                pass
-            @classmethod
-            def tearDownClass(self):
-                xxx
-    """)
-    reprec = testdir.inline_run(testpath)
-    reprec.assertoutcome(skipped=1)
-
-def test_method_and_teardown_failing_reporting(testdir):
-    testdir.makepyfile("""
-        import unittest, pytest
-        class TC(unittest.TestCase):
-            def tearDown(self):
-                assert 0, "down1"
-            def test_method(self):
-                assert False, "down2"
-    """)
-    result = testdir.runpytest("-s")
-    assert result.ret == 1
-    result.stdout.fnmatch_lines([
-        "*tearDown*",
-        "*assert 0*",
-        "*test_method*",
-        "*assert False*",
-        "*1 failed*1 error*",
-    ])
-
-def test_setup_failure_is_shown(testdir):
-    testdir.makepyfile("""
-        import unittest
-        import pytest
-        class TC(unittest.TestCase):
-            def setUp(self):
-                assert 0, "down1"
-            def test_method(self):
-                print ("never42")
-                xyz
-    """)
-    result = testdir.runpytest("-s")
-    assert result.ret == 1
-    result.stdout.fnmatch_lines([
-        "*setUp*",
-        "*assert 0*down1*",
-        "*1 failed*",
-    ])
-    assert 'never42' not in result.stdout.str()
-
-def test_setup_setUpClass(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        import pytest
-        class MyTestCase(unittest.TestCase):
-            x = 0
-            @classmethod
-            def setUpClass(cls):
-                cls.x += 1
-            def test_func1(self):
-                assert self.x == 1
-            def test_func2(self):
-                assert self.x == 1
-            @classmethod
-            def tearDownClass(cls):
-                cls.x -= 1
-        def test_teareddown():
-            assert MyTestCase.x == 0
-    """)
-    reprec = testdir.inline_run(testpath)
-    reprec.assertoutcome(passed=3)
-
-def test_setup_class(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        import pytest
-        class MyTestCase(unittest.TestCase):
-            x = 0
-            def setup_class(cls):
-                cls.x += 1
-            def test_func1(self):
-                assert self.x == 1
-            def test_func2(self):
-                assert self.x == 1
-            def teardown_class(cls):
-                cls.x -= 1
-        def test_teareddown():
-            assert MyTestCase.x == 0
-    """)
-    reprec = testdir.inline_run(testpath)
-    reprec.assertoutcome(passed=3)
-
-
-@pytest.mark.parametrize("type", ['Error', 'Failure'])
-def test_testcase_adderrorandfailure_defers(testdir, type):
-    testdir.makepyfile("""
-        from unittest import TestCase
-        import pytest
-        class MyTestCase(TestCase):
-            def run(self, result):
-                excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0)
-                try:
-                    result.add%s(self, excinfo._excinfo)
-                except KeyboardInterrupt:
-                    raise
-                except:
-                    pytest.fail("add%s should not raise")
-            def test_hello(self):
-                pass
-    """ % (type, type))
-    result = testdir.runpytest()
-    assert 'should not raise' not in result.stdout.str()
-
-@pytest.mark.parametrize("type", ['Error', 'Failure'])
-def test_testcase_custom_exception_info(testdir, type):
-    testdir.makepyfile("""
-        from unittest import TestCase
-        import py, pytest
-        import _pytest._code
-        class MyTestCase(TestCase):
-            def run(self, result):
-                excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0)
-                # we fake an incompatible exception info
-                from _pytest.monkeypatch import monkeypatch
-                mp = monkeypatch()
-                def t(*args):
-                    mp.undo()
-                    raise TypeError()
-                mp.setattr(_pytest._code, 'ExceptionInfo', t)
-                try:
-                    excinfo = excinfo._excinfo
-                    result.add%(type)s(self, excinfo)
-                finally:
-                    mp.undo()
-            def test_hello(self):
-                pass
-    """ % locals())
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines([
-        "NOTE: Incompatible Exception Representation*",
-        "*ZeroDivisionError*",
-        "*1 failed*",
-    ])
-
-def test_testcase_totally_incompatible_exception_info(testdir):
-    item, = testdir.getitems("""
-        from unittest import TestCase
-        class MyTestCase(TestCase):
-            def test_hello(self):
-                pass
-    """)
-    item.addError(None, 42)
-    excinfo = item._excinfo.pop(0)
-    assert 'ERROR: Unknown Incompatible' in str(excinfo.getrepr())
-
-def test_module_level_pytestmark(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-        import pytest
-        pytestmark = pytest.mark.xfail
-        class MyTestCase(unittest.TestCase):
-            def test_func1(self):
-                assert 0
-    """)
-    reprec = testdir.inline_run(testpath, "-s")
-    reprec.assertoutcome(skipped=1)
-
-
-def test_trial_testcase_skip_property(testdir):
-    pytest.importorskip('twisted.trial.unittest')
-    testpath = testdir.makepyfile("""
-        from twisted.trial import unittest
-        class MyTestCase(unittest.TestCase):
-            skip = 'dont run'
-            def test_func(self):
-                pass
-        """)
-    reprec = testdir.inline_run(testpath, "-s")
-    reprec.assertoutcome(skipped=1)
-
-
-def test_trial_testfunction_skip_property(testdir):
-    pytest.importorskip('twisted.trial.unittest')
-    testpath = testdir.makepyfile("""
-        from twisted.trial import unittest
-        class MyTestCase(unittest.TestCase):
-            def test_func(self):
-                pass
-            test_func.skip = 'dont run'
-        """)
-    reprec = testdir.inline_run(testpath, "-s")
-    reprec.assertoutcome(skipped=1)
-
-
-def test_trial_testcase_todo_property(testdir):
-    pytest.importorskip('twisted.trial.unittest')
-    testpath = testdir.makepyfile("""
-        from twisted.trial import unittest
-        class MyTestCase(unittest.TestCase):
-            todo = 'dont run'
-            def test_func(self):
-                assert 0
-        """)
-    reprec = testdir.inline_run(testpath, "-s")
-    reprec.assertoutcome(skipped=1)
-
-
-def test_trial_testfunction_todo_property(testdir):
-    pytest.importorskip('twisted.trial.unittest')
-    testpath = testdir.makepyfile("""
-        from twisted.trial import unittest
-        class MyTestCase(unittest.TestCase):
-            def test_func(self):
-                assert 0
-            test_func.todo = 'dont run'
-        """)
-    reprec = testdir.inline_run(testpath, "-s")
-    reprec.assertoutcome(skipped=1)
-
-
-class TestTrialUnittest:
-    def setup_class(cls):
-        cls.ut = pytest.importorskip("twisted.trial.unittest")
-
-    def test_trial_testcase_runtest_not_collected(self, testdir):
-        testdir.makepyfile("""
-            from twisted.trial.unittest import TestCase
-
-            class TC(TestCase):
-                def test_hello(self):
-                    pass
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-        testdir.makepyfile("""
-            from twisted.trial.unittest import TestCase
-
-            class TC(TestCase):
-                def runTest(self):
-                    pass
-        """)
-        reprec = testdir.inline_run()
-        reprec.assertoutcome(passed=1)
-
-    def test_trial_exceptions_with_skips(self, testdir):
-        testdir.makepyfile("""
-            from twisted.trial import unittest
-            import pytest
-            class TC(unittest.TestCase):
-                def test_hello(self):
-                    pytest.skip("skip_in_method")
-                @pytest.mark.skipif("sys.version_info != 1")
-                def test_hello2(self):
-                    pass
-                @pytest.mark.xfail(reason="iwanto")
-                def test_hello3(self):
-                    assert 0
-                def test_hello4(self):
-                    pytest.xfail("i2wanto")
-                def test_trial_skip(self):
-                    pass
-                test_trial_skip.skip = "trialselfskip"
-
-                def test_trial_todo(self):
-                    assert 0
-                test_trial_todo.todo = "mytodo"
-
-                def test_trial_todo_success(self):
-                    pass
-                test_trial_todo_success.todo = "mytodo"
-
-            class TC2(unittest.TestCase):
-                def setup_class(cls):
-                    pytest.skip("skip_in_setup_class")
-                def test_method(self):
-                    pass
-        """)
-        result = testdir.runpytest("-rxs")
-        assert result.ret == 0
-        result.stdout.fnmatch_lines_random([
-            "*XFAIL*test_trial_todo*",
-            "*trialselfskip*",
-            "*skip_in_setup_class*",
-            "*iwanto*",
-            "*i2wanto*",
-            "*sys.version_info*",
-            "*skip_in_method*",
-            "*4 skipped*3 xfail*1 xpass*",
-        ])
-
-    def test_trial_error(self, testdir):
-        testdir.makepyfile("""
-            from twisted.trial.unittest import TestCase
-            from twisted.internet.defer import Deferred
-            from twisted.internet import reactor
-
-            class TC(TestCase):
-                def test_one(self):
-                    crash
-
-                def test_two(self):
-                    def f(_):
-                        crash
-
-                    d = Deferred()
-                    d.addCallback(f)
-                    reactor.callLater(0.3, d.callback, None)
-                    return d
-
-                def test_three(self):
-                    def f():
-                        pass # will never get called
-                    reactor.callLater(0.3, f)
-                # will crash at teardown
-
-                def test_four(self):
-                    def f(_):
-                        reactor.callLater(0.3, f)
-                        crash
-
-                    d = Deferred()
-                    d.addCallback(f)
-                    reactor.callLater(0.3, d.callback, None)
-                    return d
-                # will crash both at test time and at teardown
-        """)
-        result = testdir.runpytest()
-        result.stdout.fnmatch_lines([
-            "*ERRORS*",
-            "*DelayedCalls*",
-            "*test_four*",
-            "*NameError*crash*",
-            "*test_one*",
-            "*NameError*crash*",
-            "*test_three*",
-            "*DelayedCalls*",
-            "*test_two*",
-            "*crash*",
-        ])
-
-    def test_trial_pdb(self, testdir):
-        p = testdir.makepyfile("""
-            from twisted.trial import unittest
-            import pytest
-            class TC(unittest.TestCase):
-                def test_hello(self):
-                    assert 0, "hellopdb"
-        """)
-        child = testdir.spawn_pytest(p)
-        child.expect("hellopdb")
-        child.sendeof()
-
-def test_djangolike_testcase(testdir):
-    # contributed from Morten Breekevold
-    testdir.makepyfile("""
-        from unittest import TestCase, main
-
-        class DjangoLikeTestCase(TestCase):
-
-            def setUp(self):
-                print ("setUp()")
-
-            def test_presetup_has_been_run(self):
-                print ("test_thing()")
-                self.assertTrue(hasattr(self, 'was_presetup'))
-
-            def tearDown(self):
-                print ("tearDown()")
-
-            def __call__(self, result=None):
-                try:
-                    self._pre_setup()
-                except (KeyboardInterrupt, SystemExit):
-                    raise
-                except Exception:
-                    import sys
-                    result.addError(self, sys.exc_info())
-                    return
-                super(DjangoLikeTestCase, self).__call__(result)
-                try:
-                    self._post_teardown()
-                except (KeyboardInterrupt, SystemExit):
-                    raise
-                except Exception:
-                    import sys
-                    result.addError(self, sys.exc_info())
-                    return
-
-            def _pre_setup(self):
-                print ("_pre_setup()")
-                self.was_presetup = True
-
-            def _post_teardown(self):
-                print ("_post_teardown()")
-    """)
-    result = testdir.runpytest("-s")
-    assert result.ret == 0
-    result.stdout.fnmatch_lines([
-        "*_pre_setup()*",
-        "*setUp()*",
-        "*test_thing()*",
-        "*tearDown()*",
-        "*_post_teardown()*",
-    ])
-
-
-def test_unittest_not_shown_in_traceback(testdir):
-    testdir.makepyfile("""
-        import unittest
-        class t(unittest.TestCase):
-            def test_hello(self):
-                x = 3
-                self.assertEquals(x, 4)
-    """)
-    res = testdir.runpytest()
-    assert "failUnlessEqual" not in res.stdout.str()
-
-def test_unorderable_types(testdir):
-    testdir.makepyfile("""
-        import unittest
-        class TestJoinEmpty(unittest.TestCase):
-            pass
-
-        def make_test():
-            class Test(unittest.TestCase):
-                pass
-            Test.__name__ = "TestFoo"
-            return Test
-        TestFoo = make_test()
-    """)
-    result = testdir.runpytest()
-    assert "TypeError" not in result.stdout.str()
-    assert result.ret == EXIT_NOTESTSCOLLECTED
-
-def test_unittest_typerror_traceback(testdir):
-    testdir.makepyfile("""
-        import unittest
-        class TestJoinEmpty(unittest.TestCase):
-            def test_hello(self, arg1):
-                pass
-    """)
-    result = testdir.runpytest()
-    assert "TypeError" in result.stdout.str()
-    assert result.ret == 1
-
-@pytest.mark.skipif("sys.version_info < (2,7)")
-def test_unittest_unexpected_failure(testdir):
-    testdir.makepyfile("""
-        import unittest
-        class MyTestCase(unittest.TestCase):
-            @unittest.expectedFailure
-            def test_func1(self):
-                assert 0
-            @unittest.expectedFailure
-            def test_func2(self):
-                assert 1
-    """)
-    result = testdir.runpytest("-rxX")
-    result.stdout.fnmatch_lines([
-        "*XFAIL*MyTestCase*test_func1*",
-        "*XPASS*MyTestCase*test_func2*",
-        "*1 xfailed*1 xpass*",
-    ])
-
-
-@pytest.mark.parametrize('fix_type, stmt', [
-    ('fixture', 'return'),
-    ('yield_fixture', 'yield'),
-])
-def test_unittest_setup_interaction(testdir, fix_type, stmt):
-    testdir.makepyfile("""
-        import unittest
-        import pytest
-        class MyTestCase(unittest.TestCase):
-            @pytest.{fix_type}(scope="class", autouse=True)
-            def perclass(self, request):
-                request.cls.hello = "world"
-                {stmt}
-            @pytest.{fix_type}(scope="function", autouse=True)
-            def perfunction(self, request):
-                request.instance.funcname = request.function.__name__
-                {stmt}
-
-            def test_method1(self):
-                assert self.funcname == "test_method1"
-                assert self.hello == "world"
-
-            def test_method2(self):
-                assert self.funcname == "test_method2"
-
-            def test_classattr(self):
-                assert self.__class__.hello == "world"
-    """.format(fix_type=fix_type, stmt=stmt))
-    result = testdir.runpytest()
-    result.stdout.fnmatch_lines("*3 passed*")
-
-
-def test_non_unittest_no_setupclass_support(testdir):
-    testpath = testdir.makepyfile("""
-        class TestFoo:
-            x = 0
-
-            @classmethod
-            def setUpClass(cls):
-                cls.x = 1
-
-            def test_method1(self):
-                assert self.x == 0
-
-            @classmethod
-            def tearDownClass(cls):
-                cls.x = 1
-
-        def test_not_teareddown():
-            assert TestFoo.x == 0
-
-    """)
-    reprec = testdir.inline_run(testpath)
-    reprec.assertoutcome(passed=2)
-
-
-def test_no_teardown_if_setupclass_failed(testdir):
-    testpath = testdir.makepyfile("""
-        import unittest
-
-        class MyTestCase(unittest.TestCase):
-            x = 0
-
-            @classmethod
-            def setUpClass(cls):
-                cls.x = 1
-                assert False
-
-            def test_func1(self):
-                cls.x = 10
-
-            @classmethod
-            def tearDownClass(cls):
-                cls.x = 100
-
-        def test_notTornDown():
-            assert MyTestCase.x == 1
-    """)
-    reprec = testdir.inline_run(testpath)
-    reprec.assertoutcome(passed=1, failed=1)
-
-
-def test_issue333_result_clearing(testdir):
-    testdir.makeconftest("""
-        def pytest_runtest_call(__multicall__, item):
-            __multicall__.execute()
-            assert 0
-    """)
-    testdir.makepyfile("""
-        import unittest
-        class TestIt(unittest.TestCase):
-            def test_func(self):
-                0/0
-    """)
-
-    reprec = testdir.inline_run()
-    reprec.assertoutcome(failed=1)
-
-@pytest.mark.skipif("sys.version_info < (2,7)")
-def test_unittest_raise_skip_issue748(testdir):
-    testdir.makepyfile(test_foo="""
-        import unittest
-
-        class MyTestCase(unittest.TestCase):
-            def test_one(self):
-                raise unittest.SkipTest('skipping due to reasons')
-    """)
-    result = testdir.runpytest("-v", '-rs')
-    result.stdout.fnmatch_lines("""
-        *SKIP*[1]*test_foo.py*skipping due to reasons*
-        *1 skipped*
-    """)
-
-@pytest.mark.skipif("sys.version_info < (2,7)")
-def test_unittest_skip_issue1169(testdir):
-    testdir.makepyfile(test_foo="""
-        import unittest
-        
-        class MyTestCase(unittest.TestCase):
-            @unittest.skip("skipping due to reasons")
-            def test_skip(self):
-                 self.fail()
-        """)
-    result = testdir.runpytest("-v", '-rs')
-    result.stdout.fnmatch_lines("""
-        *SKIP*[1]*skipping due to reasons*
-        *1 skipped*
-    """)
diff --git a/tools/pytest/tox.ini b/tools/pytest/tox.ini
deleted file mode 100644
index 5f65446..0000000
--- a/tools/pytest/tox.ini
+++ /dev/null
@@ -1,160 +0,0 @@
-[tox]
-minversion=2.0
-distshare={homedir}/.tox/distshare
-envlist=
-     linting,py26,py27,py33,py34,py35,pypy,
-     {py27,py35}-{pexpect,xdist,trial},
-     py27-nobyte,doctesting,py27-cxfreeze
-
-[testenv]
-commands= py.test --lsof -rfsxX {posargs:testing}
-passenv = USER USERNAME
-deps=
-    nose
-    mock
-    requests
-
-[testenv:py26]
-commands= py.test --lsof -rfsxX {posargs:testing}
-deps=
-    nose
-    mock<1.1  # last supported version for py26
-
-[testenv:py27-subprocess]
-changedir=.
-basepython=python2.7
-deps=pytest-xdist>=1.13
-    mock
-    nose
-commands=
-  py.test -n3 -rfsxX --runpytest=subprocess {posargs:testing}
-
-[testenv:genscript]
-commands= py.test --genscript=pytest1
-
-[testenv:linting]
-basepython = python2.7
-deps = flake8
-    restructuredtext_lint
-commands = flake8 pytest.py _pytest testing
-    rst-lint CHANGELOG.rst
-
-[testenv:py27-xdist]
-deps=pytest-xdist>=1.13
-    mock
-    nose
-commands=
-  py.test -n1 -rfsxX {posargs:testing}
-
-[testenv:py35-xdist]
-deps={[testenv:py27-xdist]deps}
-commands=
-  py.test -n3 -rfsxX {posargs:testing}
-
-[testenv:py27-pexpect]
-changedir=testing
-platform=linux|darwin
-deps=pexpect
-commands=
-  py.test -rfsxX test_pdb.py test_terminal.py test_unittest.py
-
-[testenv:py35-pexpect]
-changedir=testing
-platform=linux|darwin
-deps={[testenv:py27-pexpect]deps}
-commands=
-  py.test -rfsxX test_pdb.py test_terminal.py test_unittest.py
-
-[testenv:py27-nobyte]
-deps=pytest-xdist>=1.13
-distribute=true
-setenv=
-    PYTHONDONTWRITEBYTECODE=1
-commands=
-  py.test -n3 -rfsxX {posargs:testing}
-
-[testenv:py27-trial]
-deps=twisted
-commands=
-  py.test -rsxf {posargs:testing/test_unittest.py}
-
-[testenv:py35-trial]
-platform=linux|darwin
-deps={[testenv:py27-trial]deps}
-commands=
-  py.test -rsxf {posargs:testing/test_unittest.py}
-
-[testenv:doctest]
-commands=py.test --doctest-modules _pytest
-deps=
-
-[testenv:doc]
-basepython=python
-changedir=doc/en
-deps=sphinx
-     PyYAML
-
-commands=
-    make clean
-    make html
-
-[testenv:doctesting]
-basepython = python3.4
-changedir=doc/en
-deps=PyYAML
-commands= py.test -rfsxX {posargs}
-
-[testenv:regen]
-changedir=doc/en
-basepython = python3.4
-deps=sphinx
-     PyYAML
-     regendoc>=0.6.1
-whitelist_externals=
-    rm
-    make
-commands=
-    rm -rf /tmp/doc-exec*
-    make regen
-
-[testenv:jython]
-changedir=testing
-commands=
-    {envpython} {envbindir}/py.test-jython -rfsxX {posargs}
-
-[testenv:py27-cxfreeze]
-changedir=testing/cx_freeze
-platform=linux|darwin
-commands=
-    {envpython} install_cx_freeze.py
-    {envpython} runtests_setup.py build --build-exe build
-    {envpython} tox_run.py
-
-
-[testenv:coveralls]
-passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN
-usedevelop=True
-basepython=python3.4
-changedir=.
-deps =
-    {[testenv]deps}
-    coveralls
-commands=
-    coverage run --source=_pytest -m pytest testing
-    coverage report -m
-    coveralls
-
-[pytest]
-minversion=2.0
-plugins=pytester
-#--pyargs --doctest-modules --ignore=.tox
-addopts= -rxsX -p pytester --ignore=testing/cx_freeze
-rsyncdirs=tox.ini pytest.py _pytest testing
-python_files=test_*.py *_test.py testing/*/*.py
-python_classes=Test Acceptance
-python_functions=test
-norecursedirs = .tox ja .hg cx_freeze_source
-
-
-[flake8]
-ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402
diff --git a/tools/pywebsocket/CONTRIBUTING b/tools/pywebsocket/CONTRIBUTING
new file mode 100644
index 0000000..6415f5d
--- /dev/null
+++ b/tools/pywebsocket/CONTRIBUTING
@@ -0,0 +1,11 @@
+For instructions for contributing code, please read:
+https://github.com/google/pywebsocket/wiki/CodeReviewInstruction
+
+You must complete the Individual Contributor License Agreement.
+https://cla.developers.google.com/about/google-individual
+You can do this online, and it only takes a minute.
+
+If you are contributing on behalf of a corporation, you must fill out the
+Corporate Contributor License Agreement
+https://cla.developers.google.com/about/google-corporate
+and send it to us as described on that page.
diff --git a/tools/pywebsocket/src/COPYING b/tools/pywebsocket/LICENSE
similarity index 100%
rename from tools/pywebsocket/src/COPYING
rename to tools/pywebsocket/LICENSE
diff --git a/tools/pywebsocket/src/MANIFEST.in b/tools/pywebsocket/MANIFEST.in
similarity index 100%
rename from tools/pywebsocket/src/MANIFEST.in
rename to tools/pywebsocket/MANIFEST.in
diff --git a/tools/pywebsocket/src/README b/tools/pywebsocket/README
similarity index 100%
rename from tools/pywebsocket/src/README
rename to tools/pywebsocket/README
diff --git a/tools/pywebsocket/README.md b/tools/pywebsocket/README.md
new file mode 100644
index 0000000..ae8e910
--- /dev/null
+++ b/tools/pywebsocket/README.md
@@ -0,0 +1,8 @@
+
+# pywebsocket #
+
+The pywebsocket project aims to provide a [WebSocket](https://tools.ietf.org/html/rfc6455) standalone server and a WebSocket extension for [Apache HTTP Server](https://httpd.apache.org/), mod\_pywebsocket.
+
+pywebsocket is intended for **testing** or **experimental** purposes.
+
+Please see [Wiki](../../wiki) for more details.
diff --git a/tools/pywebsocket/src/example/abort_handshake_wsh.py b/tools/pywebsocket/example/abort_handshake_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/abort_handshake_wsh.py
rename to tools/pywebsocket/example/abort_handshake_wsh.py
diff --git a/tools/pywebsocket/src/example/abort_wsh.py b/tools/pywebsocket/example/abort_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/abort_wsh.py
rename to tools/pywebsocket/example/abort_wsh.py
diff --git a/tools/pywebsocket/src/example/arraybuffer_benchmark.html b/tools/pywebsocket/example/arraybuffer_benchmark.html
similarity index 100%
rename from tools/pywebsocket/src/example/arraybuffer_benchmark.html
rename to tools/pywebsocket/example/arraybuffer_benchmark.html
diff --git a/tools/pywebsocket/src/example/bench_wsh.py b/tools/pywebsocket/example/bench_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/bench_wsh.py
rename to tools/pywebsocket/example/bench_wsh.py
diff --git a/tools/pywebsocket/example/benchmark.html b/tools/pywebsocket/example/benchmark.html
new file mode 100644
index 0000000..a923863
--- /dev/null
+++ b/tools/pywebsocket/example/benchmark.html
@@ -0,0 +1,175 @@
+<!--
+Copyright 2013, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<html>
+<head>
+<title>WebSocket benchmark</title>
+<script src="util_main.js"></script>
+<script src="util.js"></script>
+<script src="benchmark.js"></script>
+<script>
+var addressBox = null;
+
+function getConfig() {
+  return {
+    prefixUrl: addressBox.value,
+    printSize: getBoolFromCheckBox('printsize'),
+    numSockets: getIntFromInput('numsockets'),
+    // Initial size of messages.
+    numIterations: getIntFromInput('numiterations'),
+    numWarmUpIterations: getIntFromInput('numwarmupiterations'),
+    startSize: getIntFromInput('startsize'),
+    // Stops benchmark when the size of message exceeds this threshold.
+    stopThreshold: getIntFromInput('stopthreshold'),
+    // If the size of each message is small, send/receive multiple messages
+    // until the sum of sizes reaches this threshold.
+    minTotal: getIntFromInput('mintotal'),
+    multipliers: getFloatArrayFromInput('multipliers'),
+    verifyData: getBoolFromCheckBox('verifydata'),
+    addToLog: addToLog,
+    addToSummary: addToSummary,
+    measureValue: measureValue,
+    notifyAbort: notifyAbort
+  };
+}
+
+function onSendBenchmark() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
+}
+
+function onReceiveBenchmark() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
+}
+
+function onBatchBenchmark() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
+}
+
+function onStop() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'stop');
+}
+
+function init() {
+  addressBox = document.getElementById('address');
+  logBox = document.getElementById('log');
+
+  summaryBox = document.getElementById('summary');
+
+  var scheme = window.location.protocol == 'https:' ? 'wss://' : 'ws://';
+  var defaultAddress = scheme + window.location.host + '/benchmark_helper';
+
+  addressBox.value = defaultAddress;
+
+  addToLog(window.navigator.userAgent.toLowerCase());
+  addToSummary(window.navigator.userAgent.toLowerCase());
+
+  if (!('WebSocket' in window)) {
+    addToLog('WebSocket is not available');
+  }
+
+  initWorker('WebSocket', '');
+}
+</script>
+</head>
+<body onload="init()">
+
+<div id="benchmark_div">
+  url <input type="text" id="address" size="40">
+  <input type="button" value="send" onclick="onSendBenchmark()">
+  <input type="button" value="receive" onclick="onReceiveBenchmark()">
+  <input type="button" value="batch" onclick="onBatchBenchmark()">
+  <input type="button" value="stop" onclick="onStop()">
+
+  <br/>
+
+  <input type="checkbox" id="printsize" checked>
+  <label for="printsize">Print size and time per message</label>
+  <input type="checkbox" id="verifydata" checked>
+  <label for="verifydata">Verify data</label>
+  <input type="checkbox" id="worker">
+  <label for="worker">Run on worker</label>
+
+  <br/>
+
+  Parameters:
+
+  <br/>
+
+  <table>
+    <tr>
+      <td>Num sockets</td>
+      <td><input type="text" id="numsockets" value="1"></td>
+    </tr>
+    <tr>
+      <td>Number of iterations</td>
+      <td><input type="text" id="numiterations" value="1"></td>
+    </tr>
+    <tr>
+      <td>Number of warm-up iterations</td>
+      <td><input type="text" id="numwarmupiterations" value="0"></td>
+    </tr>
+    <tr>
+      <td>Start size</td>
+      <td><input type="text" id="startsize" value="10240"></td>
+    </tr>
+    <tr>
+      <td>Stop threshold</td>
+      <td><input type="text" id="stopthreshold" value="102400000"></td>
+    </tr>
+    <tr>
+      <td>Minimum total</td>
+      <td><input type="text" id="mintotal" value="102400000"></td>
+    </tr>
+    <tr>
+      <td>Multipliers</td>
+      <td><input type="text" id="multipliers" value="5, 2"></td>
+    </tr>
+  </table>
+</div>
+
+<div id="log_div">
+  <textarea
+      id="log" rows="20" style="width: 100%" readonly></textarea>
+</div>
+<div id="summary_div">
+  Summary
+  <textarea
+      id="summary" rows="20" style="width: 100%" readonly></textarea>
+</div>
+
+Note: Effect of RTT is not eliminated.
+
+</body>
+</html>
diff --git a/tools/pywebsocket/example/benchmark.js b/tools/pywebsocket/example/benchmark.js
new file mode 100644
index 0000000..856435d
--- /dev/null
+++ b/tools/pywebsocket/example/benchmark.js
@@ -0,0 +1,214 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the COPYING file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+if (typeof importScripts !== "undefined") {
+  // Running on a worker
+  importScripts('util.js', 'util_worker.js');
+}
+
+// Namespace for holding globals.
+var benchmark = {startTimeInMs: 0};
+
+var sockets = [];
+var numEstablishedSockets = 0;
+
+var timerID = null;
+
+function destroySocket(socket) {
+  socket.onopen = null;
+  socket.onmessage = null;
+  socket.onerror = null;
+  socket.onclose = null;
+  socket.close();
+}
+
+function destroyAllSockets() {
+  for (var i = 0; i < sockets.length; ++i) {
+    destroySocket(sockets[i]);
+  }
+  sockets = [];
+}
+
+function sendBenchmarkStep(size, config, isWarmUp) {
+  timerID = null;
+
+  var totalSize = 0;
+  var totalReplied = 0;
+
+  var onMessageHandler = function(event) {
+    if (!verifyAcknowledgement(config, event.data, size)) {
+      destroyAllSockets();
+      config.notifyAbort();
+      return;
+    }
+
+    totalReplied += size;
+
+    if (totalReplied < totalSize) {
+      return;
+    }
+
+    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
+        isWarmUp);
+
+    runNextTask(config);
+  };
+
+  for (var i = 0; i < sockets.length; ++i) {
+    var socket = sockets[i];
+    socket.onmessage = onMessageHandler;
+  }
+
+  var dataArray = [];
+
+  while (totalSize < config.minTotal) {
+    var buffer = new ArrayBuffer(size);
+
+    fillArrayBuffer(buffer, 0x61);
+
+    dataArray.push(buffer);
+    totalSize += size;
+  }
+
+  benchmark.startTimeInMs = getTimeStamp();
+
+  totalSize = 0;
+
+  var socketIndex = 0;
+  var dataIndex = 0;
+  while (totalSize < config.minTotal) {
+    var command = ['send'];
+    command.push(config.verifyData ? '1' : '0');
+    sockets[socketIndex].send(command.join(' '));
+    sockets[socketIndex].send(dataArray[dataIndex]);
+    socketIndex = (socketIndex + 1) % sockets.length;
+
+    totalSize += size;
+    ++dataIndex;
+  }
+}
+
+function receiveBenchmarkStep(size, config, isWarmUp) {
+  timerID = null;
+
+  var totalSize = 0;
+  var totalReplied = 0;
+
+  var onMessageHandler = function(event) {
+    var bytesReceived = event.data.byteLength;
+    if (bytesReceived != size) {
+      config.addToLog('Expected ' + size + 'B but received ' +
+          bytesReceived + 'B');
+      destroyAllSockets();
+      config.notifyAbort();
+      return;
+    }
+
+    if (config.verifyData && !verifyArrayBuffer(event.data, 0x61)) {
+      config.addToLog('Response verification failed');
+      destroyAllSockets();
+      config.notifyAbort();
+      return;
+    }
+
+    totalReplied += bytesReceived;
+
+    if (totalReplied < totalSize) {
+      return;
+    }
+
+    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
+        isWarmUp);
+
+    runNextTask(config);
+  };
+
+  for (var i = 0; i < sockets.length; ++i) {
+    var socket = sockets[i];
+    socket.binaryType = 'arraybuffer';
+    socket.onmessage = onMessageHandler;
+  }
+
+  benchmark.startTimeInMs = getTimeStamp();
+
+  var socketIndex = 0;
+  while (totalSize < config.minTotal) {
+    sockets[socketIndex].send('receive ' + size);
+    socketIndex = (socketIndex + 1) % sockets.length;
+
+    totalSize += size;
+  }
+}
+
+function createSocket(config) {
+  // TODO(tyoshino): Add TCP warm up.
+  var url = config.prefixUrl;
+
+  config.addToLog('Connect ' + url);
+
+  var socket = new WebSocket(url);
+  socket.onmessage = function(event) {
+    config.addToLog('Unexpected message received. Aborting.');
+  };
+  socket.onerror = function() {
+    config.addToLog('Error');
+  };
+  socket.onclose = function(event) {
+    config.addToLog('Closed');
+    config.notifyAbort();
+  };
+  return socket;
+}
+
+function startBenchmark(config) {
+  clearTimeout(timerID);
+  destroyAllSockets();
+
+  numEstablishedSockets = 0;
+
+  for (var i = 0; i < config.numSockets; ++i) {
+    var socket = createSocket(config);
+    socket.onopen = function() {
+      config.addToLog('Opened');
+
+      ++numEstablishedSockets;
+
+      if (numEstablishedSockets == sockets.length) {
+        runNextTask(config);
+      }
+    };
+    sockets.push(socket);
+  }
+}
+
+function getConfigString(config) {
+  return '(WebSocket' +
+    ', ' + (typeof importScripts !== "undefined" ? 'Worker' : 'Main') +
+    ', numSockets=' + config.numSockets +
+    ', numIterations=' + config.numIterations +
+    ', verifyData=' + config.verifyData +
+    ', minTotal=' + config.minTotal +
+    ', numWarmUpIterations=' + config.numWarmUpIterations +
+    ')';
+}
+
+function batchBenchmark(config) {
+  config.addToLog('Batch benchmark');
+  config.addToLog(buildLegendString(config));
+
+  tasks = [];
+  clearAverageData();
+  addTasks(config, sendBenchmarkStep);
+  addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
+  addTasks(config, receiveBenchmarkStep);
+  addResultReportingTask(config, 'Receive Benchmark ' +
+      getConfigString(config));
+  startBenchmark(config);
+}
+
+function cleanup() {
+  destroyAllSockets();
+}
diff --git a/tools/pywebsocket/src/example/benchmark_helper_wsh.py b/tools/pywebsocket/example/benchmark_helper_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/benchmark_helper_wsh.py
rename to tools/pywebsocket/example/benchmark_helper_wsh.py
diff --git a/tools/pywebsocket/src/example/close_wsh.py b/tools/pywebsocket/example/close_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/close_wsh.py
rename to tools/pywebsocket/example/close_wsh.py
diff --git a/tools/pywebsocket/src/example/console.html b/tools/pywebsocket/example/console.html
similarity index 100%
rename from tools/pywebsocket/src/example/console.html
rename to tools/pywebsocket/example/console.html
diff --git a/tools/pywebsocket/src/example/cookie_wsh.py b/tools/pywebsocket/example/cookie_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/cookie_wsh.py
rename to tools/pywebsocket/example/cookie_wsh.py
diff --git a/tools/pywebsocket/src/example/echo_client.py b/tools/pywebsocket/example/echo_client.py
similarity index 100%
rename from tools/pywebsocket/src/example/echo_client.py
rename to tools/pywebsocket/example/echo_client.py
diff --git a/tools/pywebsocket/src/example/echo_noext_wsh.py b/tools/pywebsocket/example/echo_noext_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/echo_noext_wsh.py
rename to tools/pywebsocket/example/echo_noext_wsh.py
diff --git a/tools/pywebsocket/src/example/echo_wsh.py b/tools/pywebsocket/example/echo_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/echo_wsh.py
rename to tools/pywebsocket/example/echo_wsh.py
diff --git a/tools/pywebsocket/src/example/eventsource.cgi b/tools/pywebsocket/example/eventsource.cgi
similarity index 100%
rename from tools/pywebsocket/src/example/eventsource.cgi
rename to tools/pywebsocket/example/eventsource.cgi
diff --git a/tools/pywebsocket/src/example/eventsource.html b/tools/pywebsocket/example/eventsource.html
similarity index 100%
rename from tools/pywebsocket/src/example/eventsource.html
rename to tools/pywebsocket/example/eventsource.html
diff --git a/tools/pywebsocket/example/fetch_benchmark.html b/tools/pywebsocket/example/fetch_benchmark.html
new file mode 100644
index 0000000..2105eb6
--- /dev/null
+++ b/tools/pywebsocket/example/fetch_benchmark.html
@@ -0,0 +1,163 @@
+<!--
+Copyright 2015 Google Inc. All rights reserved.
+
+Use of this source code is governed by a BSD-style
+license that can be found in the COPYING file or at
+https://developers.google.com/open-source/licenses/bsd
+-->
+<html>
+<head>
+<title>Fetch API benchmark</title>
+<script src="util_main.js"></script>
+<script src="util.js"></script>
+<script src="fetch_benchmark.js"></script>
+<script>
+var addressBox = null;
+
+function getConfig() {
+  return {
+    prefixUrl: addressBox.value,
+    printSize: getBoolFromCheckBox('printsize'),
+    numFetches: getIntFromInput('numFetches'),
+    // Initial size of messages.
+    numIterations: getIntFromInput('numiterations'),
+    numWarmUpIterations: getIntFromInput('numwarmupiterations'),
+    startSize: getIntFromInput('startsize'),
+    // Stops benchmark when the size of message exceeds this threshold.
+    stopThreshold: getIntFromInput('stopthreshold'),
+    multipliers: getFloatArrayFromInput('multipliers'),
+    verifyData: getBoolFromCheckBox('verifydata'),
+    addToLog: addToLog,
+    addToSummary: addToSummary,
+    measureValue: measureValue,
+    notifyAbort: notifyAbort
+  };
+}
+
+function onSendBenchmark() {
+  var config = getConfig();
+  config.dataType = getStringFromRadioBox('datatyperadio');
+  doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
+}
+
+function onReceiveBenchmark() {
+  var config = getConfig();
+  config.dataType = getStringFromRadioBox('datatyperadio');
+  doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
+}
+
+function onBatchBenchmark() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
+}
+
+function onStop() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'stop');
+}
+
+function init() {
+  addressBox = document.getElementById('address');
+  logBox = document.getElementById('log');
+
+  summaryBox = document.getElementById('summary');
+
+  // Special address of pywebsocket for XHR/Fetch API benchmark.
+  addressBox.value = '/073be001e10950692ccbf3a2ad21c245';
+
+  addToLog(window.navigator.userAgent.toLowerCase());
+  addToSummary(window.navigator.userAgent.toLowerCase());
+
+  initWorker('fetch', '');
+}
+</script>
+</head>
+<body onload="init()">
+
+<form id="benchmark_form">
+  url prefix <input type="text" id="address" size="40">
+  <input type="button" value="send" onclick="onSendBenchmark()">
+  <input type="button" value="receive" onclick="onReceiveBenchmark()">
+  <input type="button" value="batch" onclick="onBatchBenchmark()">
+  <input type="button" value="stop" onclick="onStop()">
+
+  <br/>
+
+  <input type="checkbox" id="printsize" checked>
+  <label for="printsize">Print size and time per message</label>
+  <input type="checkbox" id="verifydata" checked>
+  <label for="verifydata">Verify data</label>
+  <input type="checkbox" id="worker">
+  <label for="worker">Run on worker</label>
+
+  <br/>
+
+  Parameters:
+
+  <br/>
+
+  <table>
+    <tr>
+      <td>Number of fetch() requests</td>
+      <td><input type="text" id="numFetches" value="1"></td>
+    </tr>
+    <tr>
+      <td>Number of iterations</td>
+      <td><input type="text" id="numiterations" value="1"></td>
+    </tr>
+    <tr>
+      <td>Number of warm-up iterations</td>
+      <td><input type="text" id="numwarmupiterations" value="0"></td>
+    </tr>
+    <tr>
+      <td>Start size</td>
+      <td><input type="text" id="startsize" value="10240"></td>
+    </tr>
+    <tr>
+      <td>Stop threshold</td>
+      <td><input type="text" id="stopthreshold" value="102400000"></td>
+    </tr>
+    <tr>
+      <td>Multipliers</td>
+      <td><input type="text" id="multipliers" value="5, 2"></td>
+    </tr>
+  </table>
+
+  Set data type
+  <input type="radio"
+         name="datatyperadio"
+         id="datatyperadiotext"
+         value="text"
+         checked><label for="datatyperadiotext">text</label>
+  <input type="radio"
+         name="datatyperadio"
+         id="datatyperadioblob"
+         value="blob"
+         ><label for="datatyperadioblob">blob</label>
+  <input type="radio"
+         name="datatyperadio"
+         id="datatyperadioarraybuffer"
+         value="arraybuffer"
+         ><label for="datatyperadioarraybuffer">arraybuffer</label>
+</form>
+
+<div id="log_div">
+  <textarea
+      id="log" rows="20" style="width: 100%" readonly></textarea>
+</div>
+<div id="summary_div">
+  Summary
+  <textarea
+      id="summary" rows="20" style="width: 100%" readonly></textarea>
+</div>
+
+<div id="note_div">
+  Note:
+  <ul>
+    <li>Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.</li>
+    <li>The Stddev column shows NaN when the number of iterations is set to 1.</li>
+  </ul>
+</div>
+
+</body>
+</html>
diff --git a/tools/pywebsocket/example/fetch_benchmark.js b/tools/pywebsocket/example/fetch_benchmark.js
new file mode 100644
index 0000000..1395219
--- /dev/null
+++ b/tools/pywebsocket/example/fetch_benchmark.js
@@ -0,0 +1,225 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the COPYING file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+var isWorker = typeof importScripts !== "undefined";
+
+if (isWorker) {
+  // Running on a worker
+  importScripts('util.js', 'util_worker.js');
+}
+
+// Namespace for holding globals.
+var benchmark = {};
+benchmark.startTimeInMs = 0;
+
+var timerID = null;
+
+function sendBenchmarkStep(size, config, isWarmUp) {
+  timerID = null;
+  benchmark.startTimeInMs = null;
+
+  // Prepare data.
+  var dataArray = [];
+  for (var i = 0; i < config.numFetches; ++i) {
+    var data = null;
+    if (config.dataType == 'arraybuffer' ||
+        config.dataType == 'blob') {
+      data = new ArrayBuffer(size);
+
+      fillArrayBuffer(data, 0x61);
+
+      if (config.dataType == 'blob') {
+        data = new Blob([data]);
+      }
+    } else {
+      data = repeatString('a', size);
+    }
+
+    dataArray.push(data);
+  }
+
+  // Start time measuring.
+  benchmark.startTimeInMs = getTimeStamp();
+
+  // Start fetch.
+  var promises = [];
+  for (var i = 0; i < config.numFetches; ++i) {
+    var data = dataArray[i];
+    var promise = fetch(config.prefixUrl + '_send',
+                        {method: 'POST', body: data})
+        .then(function (response) {
+          if (response.status != 200) {
+            config.addToLog('Failed (status=' + response.status + ')');
+            return Promise.reject();
+          }
+          // Check and warn if proxy is enabled.
+          if (response.headers.get('Via') !== null) {
+            config.addToLog('WARNING: proxy seems enabled.');
+          }
+          if (config.verifyData) {
+            return response.text()
+                .then(function(text) {
+                  if (!verifyAcknowledgement(config, text, size)) {
+                    return Promise.reject();
+                  }
+                });
+          }
+        });
+    promises.push(promise);
+  }
+
+  // Finish and report time measuring.
+  Promise.all(promises)
+      .then(function() {
+        if (benchmark.startTimeInMs == null) {
+          config.addToLog('startTimeInMs not set');
+          return Promise.reject();
+        }
+        calculateAndLogResult(config, size, benchmark.startTimeInMs,
+          size * config.numFetches, isWarmUp);
+        runNextTask(config);
+      })
+      .catch(function(e) {
+        config.addToLog("ERROR: " + e);
+        config.notifyAbort();
+      });
+}
+
+function receiveBenchmarkStep(size, config, isWarmUp) {
+  timerID = null;
+  benchmark.startTimeInMs = null;
+
+  // Start time measuring.
+  benchmark.startTimeInMs = getTimeStamp();
+
+  // Start fetch.
+  var promises = [];
+  for (var i = 0; i < config.numFetches; ++i) {
+    var request;
+    if (config.methodAndCache === 'GET-NOCACHE') {
+      request = new Request(config.prefixUrl + '_receive_getnocache?' + size,
+                            {method: 'GET'});
+    } else if (config.methodAndCache === 'GET-CACHE') {
+      request = new Request(config.prefixUrl + '_receive_getcache?' + size,
+                            {method: 'GET'});
+    } else {
+      request = new Request(config.prefixUrl + '_receive',
+                            {method: 'POST', body: size + ' none'});
+    }
+    var promise = fetch(request)
+        .then(function(response) {
+          if (response.status != 200) {
+            config.addToLog('Failed (status=' + this.status + ')');
+            return Promise.reject();
+          }
+          // Check and warn if proxy is enabled.
+          if (response.headers.get('Via') !== null) {
+            config.addToLog('WARNING: proxy seems enabled.');
+          }
+          if (config.dataType === 'arraybuffer') {
+            return response.arrayBuffer()
+                .then(function(arrayBuffer) {
+                  return [arrayBuffer.byteLength,
+                          (!config.verifyData ||
+                           verifyArrayBuffer(arrayBuffer, 0x61))];
+                });
+          } else if (config.dataType == 'blob') {
+            return response.blob()
+                .then(function(blob) {
+                  return new Promise(function(resolve, reject) {
+                      if (config.verifyData) {
+                        verifyBlob(config, blob, 0x61,
+                            function(receivedSize, verificationResult) {
+                              resolve([receivedSize, verificationResult]);
+                            });
+                      } else {
+                        resolve([blob.size, true]);
+                      }
+                    });
+                });
+          } else {
+            return response.text()
+                .then(function(text) {
+                  return [text.length,
+                          (!config.verifyData ||
+                           text == repeatString('a', text.length))];
+                });
+          }
+        })
+        .then(function(receivedSizeAndVerificationResult) {
+          var receivedSize = receivedSizeAndVerificationResult[0];
+          var verificationResult = receivedSizeAndVerificationResult[1];
+          if (receivedSize !== size) {
+            config.addToLog('Expected ' + size +
+                            'B but received ' + receivedSize + 'B');
+            return Promise.reject();
+          }
+          if (!verificationResult) {
+            config.addToLog('Response verification failed');
+            return Promise.reject();
+          }
+        });
+    promises.push(promise);
+  }
+
+  // Finish and report time measuring.
+  Promise.all(promises)
+      .then(function() {
+        if (benchmark.startTimeInMs == null) {
+          config.addToLog('startTimeInMs not set');
+          return Promise.reject();
+        }
+        calculateAndLogResult(config, size, benchmark.startTimeInMs,
+                              size * config.numFetches, isWarmUp);
+        runNextTask(config);
+      })
+      .catch(function(e) {
+        config.addToLog("ERROR: " + e);
+        config.notifyAbort();
+      });
+}
+
+
+function getConfigString(config) {
+  return '(' + config.dataType +
+    ', verifyData=' + config.verifyData +
+    ', ' + (isWorker ? 'Worker' : 'Main') +
+    ', numFetches=' + config.numFetches +
+    ', numIterations=' + config.numIterations +
+    ', numWarmUpIterations=' + config.numWarmUpIterations +
+    ')';
+}
+
+function startBenchmark(config) {
+  clearTimeout(timerID);
+
+  runNextTask(config);
+}
+
+function batchBenchmark(originalConfig) {
+  originalConfig.addToLog('Batch benchmark');
+
+  tasks = [];
+  clearAverageData();
+
+  var dataTypes = ['text', 'blob', 'arraybuffer'];
+  var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep];
+  var names = ['Send', 'Receive'];
+  for (var i = 0; i < stepFuncs.length; ++i) {
+    for (var j = 0; j < dataTypes.length; ++j) {
+      var config = cloneConfig(originalConfig);
+      config.dataType = dataTypes[j];
+      addTasks(config, stepFuncs[i]);
+      addResultReportingTask(config,
+          names[i] + ' benchmark ' + getConfigString(config));
+    }
+  }
+
+  startBenchmark(config);
+}
+
+function cleanup() {
+}
diff --git a/tools/pywebsocket/example/fetch_performance_test_iframe.html b/tools/pywebsocket/example/fetch_performance_test_iframe.html
new file mode 100644
index 0000000..e12df2e
--- /dev/null
+++ b/tools/pywebsocket/example/fetch_performance_test_iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<head>
+<script src="util.js"></script>
+<script src="performance_test_iframe.js"></script>
+<script src="fetch_benchmark.js"></script>
+</head>
diff --git a/tools/pywebsocket/src/example/handler_map.txt b/tools/pywebsocket/example/handler_map.txt
similarity index 100%
rename from tools/pywebsocket/src/example/handler_map.txt
rename to tools/pywebsocket/example/handler_map.txt
diff --git a/tools/pywebsocket/src/example/hsts_wsh.py b/tools/pywebsocket/example/hsts_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/hsts_wsh.py
rename to tools/pywebsocket/example/hsts_wsh.py
diff --git a/tools/pywebsocket/src/example/internal_error_wsh.py b/tools/pywebsocket/example/internal_error_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/internal_error_wsh.py
rename to tools/pywebsocket/example/internal_error_wsh.py
diff --git a/tools/pywebsocket/src/example/origin_check_wsh.py b/tools/pywebsocket/example/origin_check_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/example/origin_check_wsh.py
rename to tools/pywebsocket/example/origin_check_wsh.py
diff --git a/tools/pywebsocket/example/performance_test_iframe.html b/tools/pywebsocket/example/performance_test_iframe.html
new file mode 100644
index 0000000..78aede9
--- /dev/null
+++ b/tools/pywebsocket/example/performance_test_iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<head>
+<script src="util.js"></script>
+<script src="performance_test_iframe.js"></script>
+<script src="benchmark.js"></script>
+</head>
diff --git a/tools/pywebsocket/example/performance_test_iframe.js b/tools/pywebsocket/example/performance_test_iframe.js
new file mode 100644
index 0000000..065b25b
--- /dev/null
+++ b/tools/pywebsocket/example/performance_test_iframe.js
@@ -0,0 +1,66 @@
+function perfTestAddToLog(text) {
+  parent.postMessage({'command': 'log', 'value': text}, '*');
+}
+
+function perfTestAddToSummary(text) {
+}
+
+function perfTestMeasureValue(value) {
+  parent.postMessage({'command': 'measureValue', 'value': value}, '*');
+}
+
+function perfTestNotifyAbort() {
+  parent.postMessage({'command': 'notifyAbort'}, '*');
+}
+
+function getConfigForPerformanceTest(connectionType, dataType, async,
+                                     verifyData, numIterations,
+                                     numWarmUpIterations) {
+  var prefixUrl;
+  if (connectionType === 'WebSocket') {
+    prefixUrl = 'ws://' + location.host + '/benchmark_helper';
+  } else {
+    // XHR or fetch
+    prefixUrl = 'http://' + location.host + '/073be001e10950692ccbf3a2ad21c245';
+  }
+
+  return {
+    prefixUrl: prefixUrl,
+    printSize: true,
+    numXHRs: 1,
+    numFetches: 1,
+    numSockets: 1,
+    // + 1 is for a warmup iteration by the Telemetry framework.
+    numIterations: numIterations + numWarmUpIterations + 1,
+    numWarmUpIterations: numWarmUpIterations,
+    minTotal: 10240000,
+    startSize: 10240000,
+    stopThreshold: 10240000,
+    multipliers: [2],
+    verifyData: verifyData,
+    dataType: dataType,
+    async: async,
+    addToLog: perfTestAddToLog,
+    addToSummary: perfTestAddToSummary,
+    measureValue: perfTestMeasureValue,
+    notifyAbort: perfTestNotifyAbort
+  };
+}
+
+var data;
+onmessage = function(message) {
+  var action;
+  if (message.data.command === 'start') {
+    data = message.data;
+    initWorker(data.connectionType, 'http://' + location.host);
+    action = data.benchmarkName;
+  } else {
+    action = 'stop';
+  }
+
+  var config = getConfigForPerformanceTest(data.connectionType, data.dataType,
+                                           data.async, data.verifyData,
+                                           data.numIterations,
+                                           data.numWarmUpIterations);
+  doAction(config, data.isWorker, action);
+};
diff --git a/tools/pywebsocket/src/example/pywebsocket.conf b/tools/pywebsocket/example/pywebsocket.conf
similarity index 100%
rename from tools/pywebsocket/src/example/pywebsocket.conf
rename to tools/pywebsocket/example/pywebsocket.conf
diff --git a/tools/pywebsocket/src/example/special_headers.cgi b/tools/pywebsocket/example/special_headers.cgi
similarity index 100%
rename from tools/pywebsocket/src/example/special_headers.cgi
rename to tools/pywebsocket/example/special_headers.cgi
diff --git a/tools/pywebsocket/example/util.js b/tools/pywebsocket/example/util.js
new file mode 100644
index 0000000..9ec0007
--- /dev/null
+++ b/tools/pywebsocket/example/util.js
@@ -0,0 +1,327 @@
+// Copyright 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Utilities for example applications (for both main and worker thread).
+
+var results = {};
+
+function getTimeStamp() {
+  return Date.now();
+}
+
+function formatResultInKiB(size, timePerMessageInMs, stddevTimePerMessageInMs,
+    speed, printSize) {
+  if (printSize) {
+    return (size / 1024) +
+        '\t' + timePerMessageInMs.toFixed(3) +
+        (stddevTimePerMessageInMs == -1 ?
+            '' :
+            '\t' + stddevTimePerMessageInMs.toFixed(3)) +
+        '\t' + speed.toFixed(3);
+  } else {
+    return speed.toString();
+  }
+}
+
+function clearAverageData() {
+  results = {};
+}
+
+function reportAverageData(config) {
+  config.addToSummary(
+      'Size[KiB]\tAverage time[ms]\tStddev time[ms]\tSpeed[KB/s]');
+  for (var size in results) {
+    var averageTimePerMessageInMs = results[size].sum_t / results[size].n;
+    var speed = calculateSpeedInKB(size, averageTimePerMessageInMs);
+    // Calculate sample standard deviation
+    var stddevTimePerMessageInMs = Math.sqrt(
+        (results[size].sum_t2 / results[size].n -
+            averageTimePerMessageInMs * averageTimePerMessageInMs) *
+        results[size].n /
+        (results[size].n - 1));
+    config.addToSummary(formatResultInKiB(
+        size, averageTimePerMessageInMs, stddevTimePerMessageInMs, speed,
+        true));
+  }
+}
+
+function calculateSpeedInKB(size, timeSpentInMs) {
+  return Math.round(size / timeSpentInMs * 1000) / 1000;
+}
+
+function calculateAndLogResult(config, size, startTimeInMs, totalSize,
+    isWarmUp) {
+  var timeSpentInMs = getTimeStamp() - startTimeInMs;
+  var speed = calculateSpeedInKB(totalSize, timeSpentInMs);
+  var timePerMessageInMs = timeSpentInMs / (totalSize / size);
+  if (!isWarmUp) {
+    config.measureValue(timePerMessageInMs);
+    if (!results[size]) {
+      results[size] = {n: 0, sum_t: 0, sum_t2: 0};
+    }
+    results[size].n ++;
+    results[size].sum_t += timePerMessageInMs;
+    results[size].sum_t2 += timePerMessageInMs * timePerMessageInMs;
+  }
+  config.addToLog(formatResultInKiB(size, timePerMessageInMs, -1, speed,
+      config.printSize));
+}
+
+function repeatString(str, count) {
+  var data = '';
+  var expChunk = str;
+  var remain = count;
+  while (true) {
+    if (remain % 2) {
+      data += expChunk;
+      remain = (remain - 1) / 2;
+    } else {
+      remain /= 2;
+    }
+
+    if (remain == 0)
+      break;
+
+    expChunk = expChunk + expChunk;
+  }
+  return data;
+}
+
+function fillArrayBuffer(buffer, c) {
+  var i;
+
+  var u32Content = c * 0x01010101;
+
+  var u32Blocks = Math.floor(buffer.byteLength / 4);
+  var u32View = new Uint32Array(buffer, 0, u32Blocks);
+  // length attribute is slow on Chrome. Don't use it for loop condition.
+  for (i = 0; i < u32Blocks; ++i) {
+    u32View[i] = u32Content;
+  }
+
+  // Fraction
+  var u8Blocks = buffer.byteLength - u32Blocks * 4;
+  var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks);
+  for (i = 0; i < u8Blocks; ++i) {
+    u8View[i] = c;
+  }
+}
+
+function verifyArrayBuffer(buffer, expectedChar) {
+  var i;
+
+  var expectedU32Value = expectedChar * 0x01010101;
+
+  var u32Blocks = Math.floor(buffer.byteLength / 4);
+  var u32View = new Uint32Array(buffer, 0, u32Blocks);
+  for (i = 0; i < u32Blocks; ++i) {
+    if (u32View[i] != expectedU32Value) {
+      return false;
+    }
+  }
+
+  var u8Blocks = buffer.byteLength - u32Blocks * 4;
+  var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks);
+  for (i = 0; i < u8Blocks; ++i) {
+    if (u8View[i] != expectedChar) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+function verifyBlob(config, blob, expectedChar, doneCallback) {
+  var reader = new FileReader(blob);
+  reader.onerror = function() {
+    config.addToLog('FileReader Error: ' + reader.error.message);
+    doneCallback(blob.size, false);
+  }
+  reader.onloadend = function() {
+    var result = verifyArrayBuffer(reader.result, expectedChar);
+    doneCallback(blob.size, result);
+  }
+  reader.readAsArrayBuffer(blob);
+}
+
+function verifyAcknowledgement(config, message, size) {
+  if (typeof message != 'string') {
+    config.addToLog('Invalid ack type: ' + typeof message);
+    return false;
+  }
+  var parsedAck = parseInt(message);
+  if (isNaN(parsedAck)) {
+    config.addToLog('Invalid ack value: ' + message);
+    return false;
+  }
+  if (parsedAck != size) {
+    config.addToLog(
+        'Expected ack for ' + size + 'B but received one for ' + parsedAck +
+        'B');
+    return false;
+  }
+
+  return true;
+}
+
+function cloneConfig(obj) {
+  var newObj = {};
+  for (key in obj) {
+    newObj[key] = obj[key];
+  }
+  return newObj;
+}
+
+var tasks = [];
+
+function runNextTask(config) {
+  var task = tasks.shift();
+  if (task == undefined) {
+    config.addToLog('Finished');
+    cleanup();
+    return;
+  }
+  timerID = setTimeout(task, 0);
+}
+
+function buildLegendString(config) {
+  var legend = ''
+  if (config.printSize)
+    legend = 'Message size in KiB, Time/message in ms, ';
+  legend += 'Speed in kB/s';
+  return legend;
+}
+
+function addTasks(config, stepFunc) {
+  for (var i = 0;
+      i < config.numWarmUpIterations + config.numIterations; ++i) {
+    var multiplierIndex = 0;
+    for (var size = config.startSize;
+         size <= config.stopThreshold;
+         ++multiplierIndex) {
+      var task = stepFunc.bind(
+          null,
+          size,
+          config,
+          i < config.numWarmUpIterations);
+      tasks.push(task);
+      var multiplier = config.multipliers[
+        multiplierIndex % config.multipliers.length];
+      if (multiplier <= 1) {
+        config.addToLog('Invalid multiplier ' + multiplier);
+        config.notifyAbort();
+        throw new Error('Invalid multipler');
+      }
+      size = Math.ceil(size * multiplier);
+    }
+  }
+}
+
+function addResultReportingTask(config, title) {
+  tasks.push(function(){
+      timerID = null;
+      config.addToSummary(title);
+      reportAverageData(config);
+      clearAverageData();
+      runNextTask(config);
+  });
+}
+
+function sendBenchmark(config) {
+  config.addToLog('Send benchmark');
+  config.addToLog(buildLegendString(config));
+
+  tasks = [];
+  clearAverageData();
+  addTasks(config, sendBenchmarkStep);
+  addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
+  startBenchmark(config);
+}
+
+function receiveBenchmark(config) {
+  config.addToLog('Receive benchmark');
+  config.addToLog(buildLegendString(config));
+
+  tasks = [];
+  clearAverageData();
+  addTasks(config, receiveBenchmarkStep);
+  addResultReportingTask(config,
+      'Receive Benchmark ' + getConfigString(config));
+  startBenchmark(config);
+}
+
+function stop(config) {
+  clearTimeout(timerID);
+  timerID = null;
+  tasks = [];
+  config.addToLog('Stopped');
+  cleanup();
+}
+
+var worker;
+
+function initWorker(connectionType, origin) {
+  var scriptPath =
+    connectionType === 'WebSocket' ? '/benchmark.js' :
+    connectionType === 'XHR' ? '/xhr_benchmark.js' :
+    '/fetch_benchmark.js'; // connectionType === 'fetch'
+  worker = new Worker(origin + scriptPath);
+}
+
+function doAction(config, isWindowToWorker, action) {
+  if (isWindowToWorker) {
+    worker.onmessage = function(addToLog, addToSummary,
+                                measureValue, notifyAbort, message) {
+      if (message.data.type === 'addToLog')
+        addToLog(message.data.data);
+      else if (message.data.type === 'addToSummary')
+        addToSummary(message.data.data);
+      else if (message.data.type === 'measureValue')
+        measureValue(message.data.data);
+      else if (message.data.type === 'notifyAbort')
+        notifyAbort();
+    }.bind(undefined, config.addToLog, config.addToSummary,
+           config.measureValue, config.notifyAbort);
+    config.addToLog = undefined;
+    config.addToSummary = undefined;
+    config.measureValue = undefined;
+    config.notifyAbort = undefined;
+    worker.postMessage({type: action, config: config});
+  } else {
+    if (action === 'sendBenchmark')
+      sendBenchmark(config);
+    else if (action === 'receiveBenchmark')
+      receiveBenchmark(config);
+    else if (action === 'batchBenchmark')
+      batchBenchmark(config);
+    else if (action === 'stop')
+      stop(config);
+  }
+}
diff --git a/tools/pywebsocket/example/util_main.js b/tools/pywebsocket/example/util_main.js
new file mode 100644
index 0000000..471ed63
--- /dev/null
+++ b/tools/pywebsocket/example/util_main.js
@@ -0,0 +1,65 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the COPYING file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// Utilities for example applications (for the main thread only).
+
+var logBox = null;
+var queuedLog = '';
+
+var summaryBox = null;
+
+function queueLog(log) {
+  queuedLog += log + '\n';
+}
+
+function addToLog(log) {
+  logBox.value += queuedLog;
+  queuedLog = '';
+  logBox.value += log + '\n';
+  logBox.scrollTop = 1000000;
+}
+
+function addToSummary(log) {
+  summaryBox.value += log + '\n';
+  summaryBox.scrollTop = 1000000;
+}
+
+// value: execution time in milliseconds.
+// config.measureValue is intended to be used in Performance Tests.
+// Do nothing here in non-PerformanceTest.
+function measureValue(value) {
+}
+
+// config.notifyAbort is called when the benchmark failed and aborted, and
+// intended to be used in Performance Tests.
+// Do nothing here in non-PerformanceTest.
+function notifyAbort() {
+}
+
+function getIntFromInput(id) {
+  return parseInt(document.getElementById(id).value);
+}
+
+function getStringFromRadioBox(name) {
+  var list = document.getElementById('benchmark_form')[name];
+  for (var i = 0; i < list.length; ++i)
+    if (list.item(i).checked)
+      return list.item(i).value;
+  return undefined;
+}
+function getBoolFromCheckBox(id) {
+  return document.getElementById(id).checked;
+}
+
+function getIntArrayFromInput(id) {
+  var strArray = document.getElementById(id).value.split(',');
+  return strArray.map(function(str) { return parseInt(str, 10); });
+}
+
+function getFloatArrayFromInput(id) {
+  var strArray = document.getElementById(id).value.split(',');
+  return strArray.map(parseFloat);
+}
diff --git a/tools/pywebsocket/example/util_worker.js b/tools/pywebsocket/example/util_worker.js
new file mode 100644
index 0000000..98dcf44
--- /dev/null
+++ b/tools/pywebsocket/example/util_worker.js
@@ -0,0 +1,20 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the COPYING file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// Utilities for example applications (for the worker threads only).
+
+onmessage = function (message) {
+  var config = message.data.config;
+  config.addToLog = function(text) {
+      postMessage({type: 'addToLog', data: text}); };
+  config.addToSummary = function(text) {
+      postMessage({type: 'addToSummary', data: text}); };
+  config.measureValue = function(value) {
+      postMessage({type: 'measureValue', data: value}); };
+  config.notifyAbort = function() { postMessage({type: 'notifyAbort'}); };
+
+  doAction(config, false, message.data.type);
+};
diff --git a/tools/pywebsocket/example/xhr_benchmark.html b/tools/pywebsocket/example/xhr_benchmark.html
new file mode 100644
index 0000000..800d20c
--- /dev/null
+++ b/tools/pywebsocket/example/xhr_benchmark.html
@@ -0,0 +1,212 @@
+<!--
+Copyright 2013, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+
+<html>
+<head>
+<title>XMLHttpRequest benchmark</title>
+<script src="util_main.js"></script>
+<script src="util.js"></script>
+<script src="xhr_benchmark.js"></script>
+<script>
+var addressBox = null;
+
+function getConfig() {
+  return {
+    prefixUrl: addressBox.value,
+    printSize: getBoolFromCheckBox('printsize'),
+    numXHRs: getIntFromInput('numXHRs'),
+    async: getBoolFromCheckBox('async'),
+    // Initial size of messages.
+    numIterations: getIntFromInput('numiterations'),
+    numWarmUpIterations: getIntFromInput('numwarmupiterations'),
+    startSize: getIntFromInput('startsize'),
+    // Stops benchmark when the size of message exceeds this threshold.
+    stopThreshold: getIntFromInput('stopthreshold'),
+    multipliers: getFloatArrayFromInput('multipliers'),
+    verifyData: getBoolFromCheckBox('verifydata'),
+    methodAndCache: getStringFromRadioBox('methodandcache'),
+    addToLog: addToLog,
+    addToSummary: addToSummary,
+    measureValue: measureValue,
+    notifyAbort: notifyAbort
+  };
+}
+
+function onSendBenchmark() {
+  var config = getConfig();
+  config.dataType = getStringFromRadioBox('datatyperadio');
+  doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
+}
+
+function onReceiveBenchmark() {
+  var config = getConfig();
+  config.dataType = getStringFromRadioBox('datatyperadio');
+  doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
+}
+
+function onBatchBenchmark() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
+}
+
+function onStop() {
+  var config = getConfig();
+  doAction(config, getBoolFromCheckBox('worker'), 'stop');
+}
+
+function init() {
+  addressBox = document.getElementById('address');
+  logBox = document.getElementById('log');
+
+  summaryBox = document.getElementById('summary');
+
+  // Special address of pywebsocket for XHR benchmark.
+  addressBox.value = '/073be001e10950692ccbf3a2ad21c245';
+
+  addToLog(window.navigator.userAgent.toLowerCase());
+  addToSummary(window.navigator.userAgent.toLowerCase());
+
+  initWorker('XHR', '');
+}
+</script>
+</head>
+<body onload="init()">
+
+<form id="benchmark_form">
+  url prefix <input type="text" id="address" size="40">
+  <input type="button" value="send" onclick="onSendBenchmark()">
+  <input type="button" value="receive" onclick="onReceiveBenchmark()">
+  <input type="button" value="batch" onclick="onBatchBenchmark()">
+  <input type="button" value="stop" onclick="onStop()">
+
+  <br/>
+
+  <input type="checkbox" id="printsize" checked>
+  <label for="printsize">Print size and time per message</label>
+  <input type="checkbox" id="verifydata" checked>
+  <label for="verifydata">Verify data</label>
+  <input type="checkbox" id="worker">
+  <label for="worker">Run on worker</label>
+  <input type="checkbox" id="async" checked>
+  <label for="async">Async</label><br>
+  (Receive &amp;&amp; Non-Worker &amp;&amp; Sync is not supported by spec)
+
+  <br/>
+
+  Parameters:
+
+  <br/>
+
+  <table>
+    <tr>
+      <td>Num XHRs</td>
+      <td><input type="text" id="numXHRs" value="1"></td>
+    </tr>
+    <tr>
+      <td>Number of iterations</td>
+      <td><input type="text" id="numiterations" value="1"></td>
+    </tr>
+    <tr>
+      <td>Number of warm-up iterations</td>
+      <td><input type="text" id="numwarmupiterations" value="0"></td>
+    </tr>
+    <tr>
+      <td>Start size</td>
+      <td><input type="text" id="startsize" value="10240"></td>
+    </tr>
+    <tr>
+      <td>Stop threshold</td>
+      <td><input type="text" id="stopthreshold" value="102400000"></td>
+    </tr>
+    <tr>
+      <td>Multipliers</td>
+      <td><input type="text" id="multipliers" value="5, 2"></td>
+    </tr>
+  </table>
+
+  Set data type
+  <input type="radio"
+         name="datatyperadio"
+         id="datatyperadiotext"
+         value="text"
+         checked><label for="datatyperadiotext">text</label>
+  <input type="radio"
+         name="datatyperadio"
+         id="datatyperadioblob"
+         value="blob"
+         ><label for="datatyperadioblob">blob</label>
+  <input type="radio"
+         name="datatyperadio"
+         id="datatyperadioarraybuffer"
+         value="arraybuffer"
+         ><label for="datatyperadioarraybuffer">arraybuffer</label>
+
+  <br>
+  Set HTTP method and cache control
+  <input type="radio"
+         name="methodandcache"
+         id="methodandcachePOST"
+         value="POST"
+         checked><label for="methodandcachePOST">POST (No Cache)</label>
+  <input type="radio"
+         name="methodandcache"
+         id="methodandcacheGETNOCACHE"
+         value="GET-NOCACHE"
+         ><label for="methodandcacheGETNOCACHE">GET (No Cache)</label>
+  <input type="radio"
+         name="methodandcache"
+         id="methodandcacheGETCACHE"
+         value="GET-CACHE"
+         ><label for="methodandcacheGETCACHE">GET (Cache)</label>
+  <br>
+  <span style="font-size: 80%">(Cache control: receive only. Cache is valid for 10 seconds. This config controls Cache-control HTTP response header. Browsers might not cache data e.g. when it is large)</span>
+</form>
+
+<div id="log_div">
+  <textarea
+      id="log" rows="20" style="width: 100%" readonly></textarea>
+</div>
+<div id="summary_div">
+  Summary
+  <textarea
+      id="summary" rows="20" style="width: 100%" readonly></textarea>
+</div>
+
+<div id="note_div">
+  Note:
+  <ul>
+    <li>Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.</li>
+    <li>The Stddev column shows NaN when the number of iterations is set to 1.</li>
+  </ul>
+</div>
+
+</body>
+</html>
diff --git a/tools/pywebsocket/example/xhr_benchmark.js b/tools/pywebsocket/example/xhr_benchmark.js
new file mode 100644
index 0000000..14a97e8
--- /dev/null
+++ b/tools/pywebsocket/example/xhr_benchmark.js
@@ -0,0 +1,292 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the COPYING file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+var isWorker = typeof importScripts !== "undefined";
+
+if (isWorker) {
+  // Running on a worker
+  importScripts('util.js', 'util_worker.js');
+}
+
+// Namespace for holding globals.
+var benchmark = {};
+benchmark.startTimeInMs = 0;
+
+var xhrs = [];
+
+var timerID = null;
+
+function destroyAllXHRs() {
+  for (var i = 0; i < xhrs.length; ++i) {
+    xhrs[i].onreadystatechange = null;
+    // Abort XHRs if they are not yet DONE state.
+    // Calling abort() here (i.e. in onreadystatechange handler) 
+    // causes "NetworkError" messages in DevTools in sync mode,
+    // even if it is after transition to DONE state.
+    if (xhrs[i].readyState != XMLHttpRequest.DONE)
+      xhrs[i].abort();
+  }
+  xhrs = [];
+  // gc() might be needed for Chrome/Blob
+}
+
+function sendBenchmarkStep(size, config, isWarmUp) {
+  timerID = null;
+
+  benchmark.startTimeInMs = null;
+  var totalSize = 0;
+  var totalReplied = 0;
+
+  var onReadyStateChangeHandler = function () {
+    if (this.readyState != this.DONE) {
+      return;
+    }
+
+    if (this.status != 200) {
+      config.addToLog('Failed (status=' + this.status + ')');
+      destroyAllXHRs();
+      config.notifyAbort();
+      return;
+    }
+
+    if (config.verifyData &&
+        !verifyAcknowledgement(config, this.response, size)) {
+      destroyAllXHRs();
+      config.notifyAbort();
+      return;
+    }
+
+    totalReplied += size;
+
+    if (totalReplied < totalSize) {
+      return;
+    }
+
+    if (benchmark.startTimeInMs == null) {
+      config.addToLog('startTimeInMs not set');
+      destroyAllXHRs();
+      config.notifyAbort();
+      return;
+    }
+
+    // Check and warn if proxy is enabled.
+    if (this.getResponseHeader('Via') !== null) {
+      config.addToLog('WARNING: proxy seems enabled.');
+    }
+
+    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
+        isWarmUp);
+
+    destroyAllXHRs();
+
+    runNextTask(config);
+  };
+
+  for (var i = 0; i < config.numXHRs; ++i) {
+    var xhr = new XMLHttpRequest();
+    xhr.onreadystatechange = onReadyStateChangeHandler;
+    xhrs.push(xhr);
+  }
+
+  var dataArray = [];
+
+  for (var i = 0; i < xhrs.length; ++i) {
+    var data = null;
+    if (config.dataType == 'arraybuffer' ||
+        config.dataType == 'blob') {
+      data = new ArrayBuffer(size);
+
+      fillArrayBuffer(data, 0x61);
+
+      if (config.dataType == 'blob') {
+        data = new Blob([data]);
+      }
+    } else {
+      data = repeatString('a', size);
+    }
+
+    dataArray.push(data);
+  }
+
+
+  benchmark.startTimeInMs = getTimeStamp();
+  totalSize = size * xhrs.length;
+
+  for (var i = 0; i < xhrs.length; ++i) {
+    var data = dataArray[i];
+    var xhr = xhrs[i];
+    xhr.open('POST', config.prefixUrl + '_send', config.async);
+    xhr.send(data);
+  }
+}
+
+function receiveBenchmarkStep(size, config, isWarmUp) {
+  timerID = null;
+
+  benchmark.startTimeInMs = null;
+  var totalSize = 0;
+  var totalReplied = 0;
+
+  var checkResultAndContinue = function (bytesReceived, verificationResult) {
+    if (!verificationResult) {
+      config.addToLog('Response verification failed');
+      destroyAllXHRs();
+      config.notifyAbort();
+      return;
+    }
+
+    totalReplied += bytesReceived;
+
+    if (totalReplied < totalSize) {
+      return;
+    }
+
+    if (benchmark.startTimeInMs == null) {
+      config.addToLog('startTimeInMs not set');
+      destroyAllXHRs();
+      config.notifyAbort();
+      return;
+    }
+
+    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
+        isWarmUp);
+
+    destroyAllXHRs();
+
+    runNextTask(config);
+  }
+
+  var onReadyStateChangeHandler = function () {
+    if (this.readyState != this.DONE) {
+      return;
+    }
+
+    if (this.status != 200) {
+      config.addToLog('Failed (status=' + this.status + ')');
+      destroyAllXHRs();
+      config.notifyAbort();
+      return;
+    }
+
+    // Check and warn if proxy is enabled.
+    if (this.getResponseHeader('Via') !== null) {
+      config.addToLog('WARNING: proxy seems enabled.');
+    }
+
+    var bytesReceived = -1;
+    if (this.responseType == 'arraybuffer') {
+      bytesReceived = this.response.byteLength;
+    } else if (this.responseType == 'blob') {
+      bytesReceived = this.response.size;
+    } else {
+      bytesReceived = this.response.length;
+    }
+    if (bytesReceived != size) {
+      config.addToLog('Expected ' + size +
+          'B but received ' + bytesReceived + 'B');
+      destroyAllXHRs();
+      config.notifyAbort();
+      return;
+    }
+
+    if (this.responseType == 'arraybuffer') {
+      checkResultAndContinue(bytesReceived,
+          !config.verifyData || verifyArrayBuffer(this.response, 0x61));
+    } else if (this.responseType == 'blob') {
+      if (config.verifyData)
+        verifyBlob(config, this.response, 0x61, checkResultAndContinue);
+      else
+        checkResultAndContinue(bytesReceived, true);
+    } else {
+      checkResultAndContinue(
+          bytesReceived,
+          !config.verifyData ||
+              this.response == repeatString('a', this.response.length));
+    }
+  };
+
+  for (var i = 0; i < config.numXHRs; ++i) {
+    var xhr = new XMLHttpRequest();
+    xhr.onreadystatechange = onReadyStateChangeHandler;
+    xhrs.push(xhr);
+  }
+
+  benchmark.startTimeInMs = getTimeStamp();
+  totalSize = size * xhrs.length;
+
+  for (var i = 0; i < xhrs.length; ++i) {
+    var xhr = xhrs[i];
+    if (config.methodAndCache === 'GET-NOCACHE') {
+      xhr.open('GET', config.prefixUrl + '_receive_getnocache?' + size,
+          config.async);
+      xhr.responseType = config.dataType;
+      xhr.send();
+    } else if (config.methodAndCache === 'GET-CACHE') {
+      xhr.open('GET', config.prefixUrl + '_receive_getcache?' + size,
+          config.async);
+      xhr.responseType = config.dataType;
+      xhr.send();
+    } else {
+      xhr.open('POST', config.prefixUrl + '_receive', config.async);
+      xhr.responseType = config.dataType;
+      xhr.send(size + ' none');
+    }
+  }
+}
+
+
+function getConfigString(config) {
+  return '(' + config.dataType +
+    ', verifyData=' + config.verifyData +
+    ', ' + (isWorker ? 'Worker' : 'Main') +
+    ', ' + (config.async ? 'Async' : 'Sync') +
+    ', numXHRs=' + config.numXHRs +
+    ', numIterations=' + config.numIterations +
+    ', numWarmUpIterations=' + config.numWarmUpIterations +
+    ')';
+}
+
+function startBenchmark(config) {
+  clearTimeout(timerID);
+  destroyAllXHRs();
+
+  runNextTask(config);
+}
+
+function batchBenchmark(originalConfig) {
+  originalConfig.addToLog('Batch benchmark');
+
+  tasks = [];
+  clearAverageData();
+
+  var dataTypes = ['text', 'blob', 'arraybuffer'];
+  var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep];
+  var names = ['Send', 'Receive'];
+  var async = [true, false];
+  for (var i = 0; i < stepFuncs.length; ++i) {
+    for (var j = 0; j < dataTypes.length; ++j) {
+      for (var k = 0; k < async.length; ++k) {
+        var config = cloneConfig(originalConfig);
+        config.dataType = dataTypes[j];
+        config.async = async[k];
+
+        // Receive && Non-Worker && Sync is not supported by the spec
+        if (stepFuncs[i] === receiveBenchmarkStep && !isWorker &&
+            !config.async)
+          continue;
+
+        addTasks(config, stepFuncs[i]);
+        addResultReportingTask(config,
+            names[i] + ' benchmark ' + getConfigString(config));
+      }
+    }
+  }
+
+  startBenchmark(config);
+}
+
+function cleanup() {
+}
diff --git a/tools/pywebsocket/example/xhr_event_logger.html b/tools/pywebsocket/example/xhr_event_logger.html
new file mode 100644
index 0000000..50db0fb
--- /dev/null
+++ b/tools/pywebsocket/example/xhr_event_logger.html
@@ -0,0 +1,123 @@
+<!--
+Copyright 2014 Google Inc. All rights reserved.
+
+Use of this source code is governed by a BSD-style
+license that can be found in the COPYING file or at
+https://developers.google.com/open-source/licenses/bsd
+-->
+
+<html>
+<head>
+<title>XHR event logger</title>
+<script src="util_main.js"></script>
+<script>
+var events = [];
+var startTime = 0;
+
+function run() {
+  events = [];
+  startTime = Date.now();
+
+  function pushToLog(type) {
+    var time = Date.now();
+    if (events.length != 0 && type === events[events.length - 1].type) {
+      events[events.length - 1].count += 1;
+      events[events.length - 1].last = time;
+    } else {
+      events.push({type: type, count: 1, first: time, last: time});
+    }
+  }
+
+  var xhr = new XMLHttpRequest();
+
+  function getProgressEventDump(e) {
+    return '(' + e.lengthComputable + ', ' + e.loaded + ', ' + e.total + ')';
+  }
+
+  var dumpProgressEvent = getBoolFromCheckBox('dumpprogressevent');
+
+  function log(e) {
+    var type = e.type;
+    if (type === 'readystatechange') {
+      type += e.target.readyState;
+    }
+    if (dumpProgressEvent && (e instanceof ProgressEvent)) {
+      type += getProgressEventDump(e);
+    }
+    pushToLog(type);
+  };
+
+  function logUpload(e) {
+    var type = e.type;
+    if (dumpProgressEvent && (e instanceof ProgressEvent)) {
+      type += getProgressEventDump(e);
+    }
+    pushToLog('upload' + type);
+  }
+
+  if (getBoolFromCheckBox('upload')) {
+    var upload = xhr.upload;
+    upload.onloadstart = logUpload;
+    upload.onprogress = logUpload;
+    upload.onabort = logUpload;
+    upload.onerror = logUpload;
+    upload.onload = logUpload;
+    upload.ontimeout = logUpload;
+    upload.onloadend = logUpload;
+  }
+
+  xhr.onreadystatechange = log;
+  xhr.onloadstart = log;
+  xhr.onprogress = log;
+  xhr.onabort = log;
+  xhr.onerror = log;
+  xhr.onload = log;
+  xhr.ontimeout = log;
+  xhr.onloadend = log;
+
+  xhr.open('POST', '/073be001e10950692ccbf3a2ad21c245_receive',
+           getBoolFromCheckBox('async'));
+  var size = getIntFromInput('size');
+  var chunkedMode = 'none';
+  if (getBoolFromCheckBox('chunkedresponse')) {
+    chunkedMode = 'chunked';
+  }
+  xhr.send(size + ' ' + chunkedMode);
+}
+
+function print() {
+  var result = '';
+  for (var i = 0; i < events.length; ++i) {
+    var event = events[i];
+    var line = '';
+    line += (event.first - startTime) + "ms";
+    if (event.count > 1)
+      line += "-" + (event.last - startTime) + "ms";
+    else
+      line += "        ";
+    while(line.length < 15)
+      line += " ";
+    line += ": " + event.type + ' * ' + event.count + '\n';
+    result += line;
+  }
+  document.getElementById('log').value = result;
+}
+</script>
+
+<body>
+  <textarea id="log" rows="10" cols="70" readonly></textarea>
+  <br/>
+  Size: <input type="text" id="size" value="65536"><br/>
+  <input type="checkbox" id="chunkedresponse">
+  <label for="chunkedresponse">Use Chunked T-E for response</label><br/>
+  <input type="checkbox" id="upload">
+  <label for="upload">Upload progress</label><br/>
+  <input type="checkbox" id="dumpprogressevent">
+  <label for="dumpprogressevent">
+    Dump lengthComputable/loaded/total</label><br/>
+  <input type="checkbox" id="async" checked>
+  <label for="async">Async</label><br/>
+  <input type="button" onclick="run()" value="Run XHR">
+  <input type="button" onclick="print()" value="Print log">
+</body>
+</html>
diff --git a/tools/pywebsocket/example/xhr_performance_test_iframe.html b/tools/pywebsocket/example/xhr_performance_test_iframe.html
new file mode 100644
index 0000000..ab02c9e
--- /dev/null
+++ b/tools/pywebsocket/example/xhr_performance_test_iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<head>
+<script src="util.js"></script>
+<script src="performance_test_iframe.js"></script>
+<script src="xhr_benchmark.js"></script>
+</head>
diff --git a/tools/pywebsocket/src/mod_pywebsocket/__init__.py b/tools/pywebsocket/mod_pywebsocket/__init__.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/__init__.py
rename to tools/pywebsocket/mod_pywebsocket/__init__.py
diff --git a/tools/pywebsocket/src/mod_pywebsocket/_stream_base.py b/tools/pywebsocket/mod_pywebsocket/_stream_base.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/_stream_base.py
rename to tools/pywebsocket/mod_pywebsocket/_stream_base.py
diff --git a/tools/pywebsocket/src/mod_pywebsocket/_stream_hixie75.py b/tools/pywebsocket/mod_pywebsocket/_stream_hixie75.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/_stream_hixie75.py
rename to tools/pywebsocket/mod_pywebsocket/_stream_hixie75.py
diff --git a/tools/pywebsocket/mod_pywebsocket/_stream_hybi.py b/tools/pywebsocket/mod_pywebsocket/_stream_hybi.py
new file mode 100644
index 0000000..104221b
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/_stream_hybi.py
@@ -0,0 +1,894 @@
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file provides classes and helper functions for parsing/building frames
+of the WebSocket protocol (RFC 6455).
+
+Specification:
+http://tools.ietf.org/html/rfc6455
+"""
+
+
+from collections import deque
+import logging
+import os
+import struct
+import time
+
+from mod_pywebsocket import common
+from mod_pywebsocket import util
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import InvalidFrameException
+from mod_pywebsocket._stream_base import InvalidUTF8Exception
+from mod_pywebsocket._stream_base import StreamBase
+from mod_pywebsocket._stream_base import UnsupportedFrameException
+
+
+_NOOP_MASKER = util.NoopMasker()
+
+
+class Frame(object):
+
+    def __init__(self, fin=1, rsv1=0, rsv2=0, rsv3=0,
+                 opcode=None, payload=''):
+        self.fin = fin
+        self.rsv1 = rsv1
+        self.rsv2 = rsv2
+        self.rsv3 = rsv3
+        self.opcode = opcode
+        self.payload = payload
+
+
+# Helper functions made public to be used for writing unittests for WebSocket
+# clients.
+
+
+def create_length_header(length, mask):
+    """Creates a length header.
+
+    Args:
+        length: Frame length. Must be less than 2^63.
+        mask: Mask bit. Must be boolean.
+
+    Raises:
+        ValueError: when bad data is given.
+    """
+
+    if mask:
+        mask_bit = 1 << 7
+    else:
+        mask_bit = 0
+
+    if length < 0:
+        raise ValueError('length must be non negative integer')
+    elif length <= 125:
+        return chr(mask_bit | length)
+    elif length < (1 << 16):
+        return chr(mask_bit | 126) + struct.pack('!H', length)
+    elif length < (1 << 63):
+        return chr(mask_bit | 127) + struct.pack('!Q', length)
+    else:
+        raise ValueError('Payload is too big for one frame')
+
+
+def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask):
+    """Creates a frame header.
+
+    Raises:
+        Exception: when bad data is given.
+    """
+
+    if opcode < 0 or 0xf < opcode:
+        raise ValueError('Opcode out of range')
+
+    if payload_length < 0 or (1 << 63) <= payload_length:
+        raise ValueError('payload_length out of range')
+
+    if (fin | rsv1 | rsv2 | rsv3) & ~1:
+        raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1')
+
+    header = ''
+
+    first_byte = ((fin << 7)
+                  | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4)
+                  | opcode)
+    header += chr(first_byte)
+    header += create_length_header(payload_length, mask)
+
+    return header
+
+
+def _build_frame(header, body, mask):
+    if not mask:
+        return header + body
+
+    masking_nonce = os.urandom(4)
+    masker = util.RepeatedXorMasker(masking_nonce)
+
+    return header + masking_nonce + masker.mask(body)
+
+
+def _filter_and_format_frame_object(frame, mask, frame_filters):
+    for frame_filter in frame_filters:
+        frame_filter.filter(frame)
+
+    header = create_header(
+        frame.opcode, len(frame.payload), frame.fin,
+        frame.rsv1, frame.rsv2, frame.rsv3, mask)
+    return _build_frame(header, frame.payload, mask)
+
+
+def create_binary_frame(
+    message, opcode=common.OPCODE_BINARY, fin=1, mask=False, frame_filters=[]):
+    """Creates a simple binary frame with no extension, reserved bit."""
+
+    frame = Frame(fin=fin, opcode=opcode, payload=message)
+    return _filter_and_format_frame_object(frame, mask, frame_filters)
+
+
+def create_text_frame(
+    message, opcode=common.OPCODE_TEXT, fin=1, mask=False, frame_filters=[]):
+    """Creates a simple text frame with no extension, reserved bit."""
+
+    encoded_message = message.encode('utf-8')
+    return create_binary_frame(encoded_message, opcode, fin, mask,
+                               frame_filters)
+
+
+def parse_frame(receive_bytes, logger=None,
+                ws_version=common.VERSION_HYBI_LATEST,
+                unmask_receive=True):
+    """Parses a frame. Returns a tuple containing each header field and
+    payload.
+
+    Args:
+        receive_bytes: a function that reads frame data from a stream or
+            something similar. The function takes length of the bytes to be
+            read. The function must raise ConnectionTerminatedException if
+            there is not enough data to be read.
+        logger: a logging object.
+        ws_version: the version of WebSocket protocol.
+        unmask_receive: unmask received frames. When received unmasked
+            frame, raises InvalidFrameException.
+
+    Raises:
+        ConnectionTerminatedException: when receive_bytes raises it.
+        InvalidFrameException: when the frame contains invalid data.
+    """
+
+    if not logger:
+        logger = logging.getLogger()
+
+    logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame')
+
+    received = receive_bytes(2)
+
+    first_byte = ord(received[0])
+    fin = (first_byte >> 7) & 1
+    rsv1 = (first_byte >> 6) & 1
+    rsv2 = (first_byte >> 5) & 1
+    rsv3 = (first_byte >> 4) & 1
+    opcode = first_byte & 0xf
+
+    second_byte = ord(received[1])
+    mask = (second_byte >> 7) & 1
+    payload_length = second_byte & 0x7f
+
+    logger.log(common.LOGLEVEL_FINE,
+               'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, '
+               'Mask=%s, Payload_length=%s',
+               fin, rsv1, rsv2, rsv3, opcode, mask, payload_length)
+
+    if (mask == 1) != unmask_receive:
+        raise InvalidFrameException(
+            'Mask bit on the received frame did\'nt match masking '
+            'configuration for received frames')
+
+    # The HyBi and later specs disallow putting a value in 0x0-0xFFFF
+    # into the 8-octet extended payload length field (or 0x0-0xFD in
+    # 2-octet field).
+    valid_length_encoding = True
+    length_encoding_bytes = 1
+    if payload_length == 127:
+        logger.log(common.LOGLEVEL_FINE,
+                   'Receive 8-octet extended payload length')
+
+        extended_payload_length = receive_bytes(8)
+        payload_length = struct.unpack(
+            '!Q', extended_payload_length)[0]
+        if payload_length > 0x7FFFFFFFFFFFFFFF:
+            raise InvalidFrameException(
+                'Extended payload length >= 2^63')
+        if ws_version >= 13 and payload_length < 0x10000:
+            valid_length_encoding = False
+            length_encoding_bytes = 8
+
+        logger.log(common.LOGLEVEL_FINE,
+                   'Decoded_payload_length=%s', payload_length)
+    elif payload_length == 126:
+        logger.log(common.LOGLEVEL_FINE,
+                   'Receive 2-octet extended payload length')
+
+        extended_payload_length = receive_bytes(2)
+        payload_length = struct.unpack(
+            '!H', extended_payload_length)[0]
+        if ws_version >= 13 and payload_length < 126:
+            valid_length_encoding = False
+            length_encoding_bytes = 2
+
+        logger.log(common.LOGLEVEL_FINE,
+                   'Decoded_payload_length=%s', payload_length)
+
+    if not valid_length_encoding:
+        logger.warning(
+            'Payload length is not encoded using the minimal number of '
+            'bytes (%d is encoded using %d bytes)',
+            payload_length,
+            length_encoding_bytes)
+
+    if mask == 1:
+        logger.log(common.LOGLEVEL_FINE, 'Receive mask')
+
+        masking_nonce = receive_bytes(4)
+        masker = util.RepeatedXorMasker(masking_nonce)
+
+        logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce)
+    else:
+        masker = _NOOP_MASKER
+
+    logger.log(common.LOGLEVEL_FINE, 'Receive payload data')
+    if logger.isEnabledFor(common.LOGLEVEL_FINE):
+        receive_start = time.time()
+
+    raw_payload_bytes = receive_bytes(payload_length)
+
+    if logger.isEnabledFor(common.LOGLEVEL_FINE):
+        logger.log(
+            common.LOGLEVEL_FINE,
+            'Done receiving payload data at %s MB/s',
+            payload_length / (time.time() - receive_start) / 1000 / 1000)
+    logger.log(common.LOGLEVEL_FINE, 'Unmask payload data')
+
+    if logger.isEnabledFor(common.LOGLEVEL_FINE):
+        unmask_start = time.time()
+
+    unmasked_bytes = masker.mask(raw_payload_bytes)
+
+    if logger.isEnabledFor(common.LOGLEVEL_FINE):
+        logger.log(
+            common.LOGLEVEL_FINE,
+            'Done unmasking payload data at %s MB/s',
+            payload_length / (time.time() - unmask_start) / 1000 / 1000)
+
+    return opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3
+
+
+class FragmentedFrameBuilder(object):
+    """A stateful class to send a message as fragments."""
+
+    def __init__(self, mask, frame_filters=[], encode_utf8=True):
+        """Constructs an instance."""
+
+        self._mask = mask
+        self._frame_filters = frame_filters
+        # This is for skipping UTF-8 encoding when building text type frames
+        # from compressed data.
+        self._encode_utf8 = encode_utf8
+
+        self._started = False
+
+        # Hold opcode of the first frame in messages to verify types of other
+        # frames in the message are all the same.
+        self._opcode = common.OPCODE_TEXT
+
+    def build(self, payload_data, end, binary):
+        if binary:
+            frame_type = common.OPCODE_BINARY
+        else:
+            frame_type = common.OPCODE_TEXT
+        if self._started:
+            if self._opcode != frame_type:
+                raise ValueError('Message types are different in frames for '
+                                 'the same message')
+            opcode = common.OPCODE_CONTINUATION
+        else:
+            opcode = frame_type
+            self._opcode = frame_type
+
+        if end:
+            self._started = False
+            fin = 1
+        else:
+            self._started = True
+            fin = 0
+
+        if binary or not self._encode_utf8:
+            return create_binary_frame(
+                payload_data, opcode, fin, self._mask, self._frame_filters)
+        else:
+            return create_text_frame(
+                payload_data, opcode, fin, self._mask, self._frame_filters)
+
+
+def _create_control_frame(opcode, body, mask, frame_filters):
+    frame = Frame(opcode=opcode, payload=body)
+
+    for frame_filter in frame_filters:
+        frame_filter.filter(frame)
+
+    if len(frame.payload) > 125:
+        raise BadOperationException(
+            'Payload data size of control frames must be 125 bytes or less')
+
+    header = create_header(
+        frame.opcode, len(frame.payload), frame.fin,
+        frame.rsv1, frame.rsv2, frame.rsv3, mask)
+    return _build_frame(header, frame.payload, mask)
+
+
+def create_ping_frame(body, mask=False, frame_filters=[]):
+    return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters)
+
+
+def create_pong_frame(body, mask=False, frame_filters=[]):
+    return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters)
+
+
+def create_close_frame(body, mask=False, frame_filters=[]):
+    return _create_control_frame(
+        common.OPCODE_CLOSE, body, mask, frame_filters)
+
+
+def create_closing_handshake_body(code, reason):
+    body = ''
+    if code is not None:
+        if (code > common.STATUS_USER_PRIVATE_MAX or
+            code < common.STATUS_NORMAL_CLOSURE):
+            raise BadOperationException('Status code is out of range')
+        if (code == common.STATUS_NO_STATUS_RECEIVED or
+            code == common.STATUS_ABNORMAL_CLOSURE or
+            code == common.STATUS_TLS_HANDSHAKE):
+            raise BadOperationException('Status code is reserved pseudo '
+                'code')
+        encoded_reason = reason.encode('utf-8')
+        body = struct.pack('!H', code) + encoded_reason
+    return body
+
+
+class StreamOptions(object):
+    """Holds option values to configure Stream objects."""
+
+    def __init__(self):
+        """Constructs StreamOptions."""
+
+        # Filters applied to frames.
+        self.outgoing_frame_filters = []
+        self.incoming_frame_filters = []
+
+        # Filters applied to messages. Control frames are not affected by them.
+        self.outgoing_message_filters = []
+        self.incoming_message_filters = []
+
+        self.encode_text_message_to_utf8 = True
+        self.mask_send = False
+        self.unmask_receive = True
+
+
+class Stream(StreamBase):
+    """A class for parsing/building frames of the WebSocket protocol
+    (RFC 6455).
+    """
+
+    def __init__(self, request, options):
+        """Constructs an instance.
+
+        Args:
+            request: mod_python request.
+        """
+
+        StreamBase.__init__(self, request)
+
+        self._logger = util.get_class_logger(self)
+
+        self._options = options
+
+        self._request.client_terminated = False
+        self._request.server_terminated = False
+
+        # Holds body of received fragments.
+        self._received_fragments = []
+        # Holds the opcode of the first fragment.
+        self._original_opcode = None
+
+        self._writer = FragmentedFrameBuilder(
+            self._options.mask_send, self._options.outgoing_frame_filters,
+            self._options.encode_text_message_to_utf8)
+
+        self._ping_queue = deque()
+
+    def _receive_frame(self):
+        """Receives a frame and return data in the frame as a tuple containing
+        each header field and payload separately.
+
+        Raises:
+            ConnectionTerminatedException: when read returns empty
+                string.
+            InvalidFrameException: when the frame contains invalid data.
+        """
+
+        def _receive_bytes(length):
+            return self.receive_bytes(length)
+
+        return parse_frame(receive_bytes=_receive_bytes,
+                           logger=self._logger,
+                           ws_version=self._request.ws_version,
+                           unmask_receive=self._options.unmask_receive)
+
+    def _receive_frame_as_frame_object(self):
+        opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame()
+
+        return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3,
+                     opcode=opcode, payload=unmasked_bytes)
+
+    def receive_filtered_frame(self):
+        """Receives a frame and applies frame filters and message filters.
+        The frame to be received must satisfy following conditions:
+        - The frame is not fragmented.
+        - The opcode of the frame is TEXT or BINARY.
+
+        DO NOT USE this method except for testing purpose.
+        """
+
+        frame = self._receive_frame_as_frame_object()
+        if not frame.fin:
+            raise InvalidFrameException(
+                'Segmented frames must not be received via '
+                'receive_filtered_frame()')
+        if (frame.opcode != common.OPCODE_TEXT and
+            frame.opcode != common.OPCODE_BINARY):
+            raise InvalidFrameException(
+                'Control frames must not be received via '
+                'receive_filtered_frame()')
+
+        for frame_filter in self._options.incoming_frame_filters:
+            frame_filter.filter(frame)
+        for message_filter in self._options.incoming_message_filters:
+            frame.payload = message_filter.filter(frame.payload)
+        return frame
+
+    def send_message(self, message, end=True, binary=False):
+        """Send message.
+
+        Args:
+            message: text in unicode or binary in str to send.
+            binary: send message as binary frame.
+
+        Raises:
+            BadOperationException: when called on a server-terminated
+                connection or called with inconsistent message type or
+                binary parameter.
+        """
+
+        if self._request.server_terminated:
+            raise BadOperationException(
+                'Requested send_message after sending out a closing handshake')
+
+        if binary and isinstance(message, unicode):
+            raise BadOperationException(
+                'Message for binary frame must be instance of str')
+
+        for message_filter in self._options.outgoing_message_filters:
+            message = message_filter.filter(message, end, binary)
+
+        try:
+            # Set this to any positive integer to limit maximum size of data in
+            # payload data of each frame.
+            MAX_PAYLOAD_DATA_SIZE = -1
+
+            if MAX_PAYLOAD_DATA_SIZE <= 0:
+                self._write(self._writer.build(message, end, binary))
+                return
+
+            bytes_written = 0
+            while True:
+                end_for_this_frame = end
+                bytes_to_write = len(message) - bytes_written
+                if (MAX_PAYLOAD_DATA_SIZE > 0 and
+                    bytes_to_write > MAX_PAYLOAD_DATA_SIZE):
+                    end_for_this_frame = False
+                    bytes_to_write = MAX_PAYLOAD_DATA_SIZE
+
+                frame = self._writer.build(
+                    message[bytes_written:bytes_written + bytes_to_write],
+                    end_for_this_frame,
+                    binary)
+                self._write(frame)
+
+                bytes_written += bytes_to_write
+
+                # This if must be placed here (the end of while block) so that
+                # at least one frame is sent.
+                if len(message) <= bytes_written:
+                    break
+        except ValueError, e:
+            raise BadOperationException(e)
+
+    def _get_message_from_frame(self, frame):
+        """Gets a message from frame. If the message is composed of fragmented
+        frames and the frame is not the last fragmented frame, this method
+        returns None. The whole message will be returned when the last
+        fragmented frame is passed to this method.
+
+        Raises:
+            InvalidFrameException: when the frame doesn't match defragmentation
+                context, or the frame contains invalid data.
+        """
+
+        if frame.opcode == common.OPCODE_CONTINUATION:
+            if not self._received_fragments:
+                if frame.fin:
+                    raise InvalidFrameException(
+                        'Received a termination frame but fragmentation '
+                        'not started')
+                else:
+                    raise InvalidFrameException(
+                        'Received an intermediate frame but '
+                        'fragmentation not started')
+
+            if frame.fin:
+                # End of fragmentation frame
+                self._received_fragments.append(frame.payload)
+                message = ''.join(self._received_fragments)
+                self._received_fragments = []
+                return message
+            else:
+                # Intermediate frame
+                self._received_fragments.append(frame.payload)
+                return None
+        else:
+            if self._received_fragments:
+                if frame.fin:
+                    raise InvalidFrameException(
+                        'Received an unfragmented frame without '
+                        'terminating existing fragmentation')
+                else:
+                    raise InvalidFrameException(
+                        'New fragmentation started without terminating '
+                        'existing fragmentation')
+
+            if frame.fin:
+                # Unfragmented frame
+
+                self._original_opcode = frame.opcode
+                return frame.payload
+            else:
+                # Start of fragmentation frame
+
+                if common.is_control_opcode(frame.opcode):
+                    raise InvalidFrameException(
+                        'Control frames must not be fragmented')
+
+                self._original_opcode = frame.opcode
+                self._received_fragments.append(frame.payload)
+                return None
+
+    def _process_close_message(self, message):
+        """Processes close message.
+
+        Args:
+            message: close message.
+
+        Raises:
+            InvalidFrameException: when the message is invalid.
+        """
+
+        self._request.client_terminated = True
+
+        # Status code is optional. We can have status reason only if we
+        # have status code. Status reason can be empty string. So,
+        # allowed cases are
+        # - no application data: no code no reason
+        # - 2 octet of application data: has code but no reason
+        # - 3 or more octet of application data: both code and reason
+        if len(message) == 0:
+            self._logger.debug('Received close frame (empty body)')
+            self._request.ws_close_code = (
+                common.STATUS_NO_STATUS_RECEIVED)
+        elif len(message) == 1:
+            raise InvalidFrameException(
+                'If a close frame has status code, the length of '
+                'status code must be 2 octet')
+        elif len(message) >= 2:
+            self._request.ws_close_code = struct.unpack(
+                '!H', message[0:2])[0]
+            self._request.ws_close_reason = message[2:].decode(
+                'utf-8', 'replace')
+            self._logger.debug(
+                'Received close frame (code=%d, reason=%r)',
+                self._request.ws_close_code,
+                self._request.ws_close_reason)
+
+        # As we've received a close frame, no more data is coming over the
+        # socket. We can now safely close the socket without worrying about
+        # RST sending.
+
+        if self._request.server_terminated:
+            self._logger.debug(
+                'Received ack for server-initiated closing handshake')
+            return
+
+        self._logger.debug(
+            'Received client-initiated closing handshake')
+
+        code = common.STATUS_NORMAL_CLOSURE
+        reason = ''
+        if hasattr(self._request, '_dispatcher'):
+            dispatcher = self._request._dispatcher
+            code, reason = dispatcher.passive_closing_handshake(
+                self._request)
+            if code is None and reason is not None and len(reason) > 0:
+                self._logger.warning(
+                    'Handler specified reason despite code being None')
+                reason = ''
+            if reason is None:
+                reason = ''
+        self._send_closing_handshake(code, reason)
+        self._logger.debug(
+            'Acknowledged closing handshake initiated by the peer '
+            '(code=%r, reason=%r)', code, reason)
+
+    def _process_ping_message(self, message):
+        """Processes ping message.
+
+        Args:
+            message: ping message.
+        """
+
+        try:
+            handler = self._request.on_ping_handler
+            if handler:
+                handler(self._request, message)
+                return
+        except AttributeError, e:
+            pass
+        self._send_pong(message)
+
+    def _process_pong_message(self, message):
+        """Processes pong message.
+
+        Args:
+            message: pong message.
+        """
+
+        # TODO(tyoshino): Add ping timeout handling.
+
+        inflight_pings = deque()
+
+        while True:
+            try:
+                expected_body = self._ping_queue.popleft()
+                if expected_body == message:
+                    # inflight_pings contains pings ignored by the
+                    # other peer. Just forget them.
+                    self._logger.debug(
+                        'Ping %r is acked (%d pings were ignored)',
+                        expected_body, len(inflight_pings))
+                    break
+                else:
+                    inflight_pings.append(expected_body)
+            except IndexError, e:
+                # The received pong was unsolicited pong. Keep the
+                # ping queue as is.
+                self._ping_queue = inflight_pings
+                self._logger.debug('Received a unsolicited pong')
+                break
+
+        try:
+            handler = self._request.on_pong_handler
+            if handler:
+                handler(self._request, message)
+        except AttributeError, e:
+            pass
+
+    def receive_message(self):
+        """Receive a WebSocket frame and return its payload as a text in
+        unicode or a binary in str.
+
+        Returns:
+            payload data of the frame
+            - as unicode instance if received text frame
+            - as str instance if received binary frame
+            or None iff received closing handshake.
+        Raises:
+            BadOperationException: when called on a client-terminated
+                connection.
+            ConnectionTerminatedException: when read returns empty
+                string.
+            InvalidFrameException: when the frame contains invalid
+                data.
+            UnsupportedFrameException: when the received frame has
+                flags, opcode we cannot handle. You can ignore this
+                exception and continue receiving the next frame.
+        """
+
+        if self._request.client_terminated:
+            raise BadOperationException(
+                'Requested receive_message after receiving a closing '
+                'handshake')
+
+        while True:
+            # mp_conn.read will block if no bytes are available.
+            # Timeout is controlled by TimeOut directive of Apache.
+
+            frame = self._receive_frame_as_frame_object()
+
+            # Check the constraint on the payload size for control frames
+            # before extension processes the frame.
+            # See also http://tools.ietf.org/html/rfc6455#section-5.5
+            if (common.is_control_opcode(frame.opcode) and
+                len(frame.payload) > 125):
+                raise InvalidFrameException(
+                    'Payload data size of control frames must be 125 bytes or '
+                    'less')
+
+            for frame_filter in self._options.incoming_frame_filters:
+                frame_filter.filter(frame)
+
+            if frame.rsv1 or frame.rsv2 or frame.rsv3:
+                raise UnsupportedFrameException(
+                    'Unsupported flag is set (rsv = %d%d%d)' %
+                    (frame.rsv1, frame.rsv2, frame.rsv3))
+
+            message = self._get_message_from_frame(frame)
+            if message is None:
+                continue
+
+            for message_filter in self._options.incoming_message_filters:
+                message = message_filter.filter(message)
+
+            if self._original_opcode == common.OPCODE_TEXT:
+                # The WebSocket protocol section 4.4 specifies that invalid
+                # characters must be replaced with U+fffd REPLACEMENT
+                # CHARACTER.
+                try:
+                    return message.decode('utf-8')
+                except UnicodeDecodeError, e:
+                    raise InvalidUTF8Exception(e)
+            elif self._original_opcode == common.OPCODE_BINARY:
+                return message
+            elif self._original_opcode == common.OPCODE_CLOSE:
+                self._process_close_message(message)
+                return None
+            elif self._original_opcode == common.OPCODE_PING:
+                self._process_ping_message(message)
+            elif self._original_opcode == common.OPCODE_PONG:
+                self._process_pong_message(message)
+            else:
+                raise UnsupportedFrameException(
+                    'Opcode %d is not supported' % self._original_opcode)
+
+    def _send_closing_handshake(self, code, reason):
+        body = create_closing_handshake_body(code, reason)
+        frame = create_close_frame(
+            body, mask=self._options.mask_send,
+            frame_filters=self._options.outgoing_frame_filters)
+
+        self._request.server_terminated = True
+
+        self._write(frame)
+
+    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason='',
+                         wait_response=True):
+        """Closes a WebSocket connection. Note that this method blocks until
+        it receives acknowledgement to the closing handshake.
+
+        Args:
+            code: Status code for close frame. If code is None, a close
+                frame with empty body will be sent.
+            reason: string representing close reason.
+            wait_response: True when caller want to wait the response.
+        Raises:
+            BadOperationException: when reason is specified with code None
+            or reason is not an instance of both str and unicode.
+        """
+
+        if self._request.server_terminated:
+            self._logger.debug(
+                'Requested close_connection but server is already terminated')
+            return
+
+        # When we receive a close frame, we call _process_close_message().
+        # _process_close_message() immediately acknowledges to the
+        # server-initiated closing handshake and sets server_terminated to
+        # True. So, here we can assume that we haven't received any close
+        # frame. We're initiating a closing handshake.
+
+        if code is None:
+            if reason is not None and len(reason) > 0:
+                raise BadOperationException(
+                    'close reason must not be specified if code is None')
+            reason = ''
+        else:
+            if not isinstance(reason, str) and not isinstance(reason, unicode):
+                raise BadOperationException(
+                    'close reason must be an instance of str or unicode')
+
+        self._send_closing_handshake(code, reason)
+        self._logger.debug(
+            'Initiated closing handshake (code=%r, reason=%r)',
+            code, reason)
+
+        if (code == common.STATUS_GOING_AWAY or
+            code == common.STATUS_PROTOCOL_ERROR) or not wait_response:
+            # It doesn't make sense to wait for a close frame if the reason is
+            # protocol error or that the server is going away. For some of
+            # other reasons, it might not make sense to wait for a close frame,
+            # but it's not clear, yet.
+            return
+
+        # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
+        # or until a server-defined timeout expires.
+        #
+        # For now, we expect receiving closing handshake right after sending
+        # out closing handshake.
+        message = self.receive_message()
+        if message is not None:
+            raise ConnectionTerminatedException(
+                'Didn\'t receive valid ack for closing handshake')
+        # TODO: 3. close the WebSocket connection.
+        # note: mod_python Connection (mp_conn) doesn't have close method.
+
+    def send_ping(self, body=''):
+        frame = create_ping_frame(
+            body,
+            self._options.mask_send,
+            self._options.outgoing_frame_filters)
+        self._write(frame)
+
+        self._ping_queue.append(body)
+
+    def _send_pong(self, body):
+        frame = create_pong_frame(
+            body,
+            self._options.mask_send,
+            self._options.outgoing_frame_filters)
+        self._write(frame)
+
+    def get_last_received_opcode(self):
+        """Returns the opcode of the WebSocket message which the last received
+        frame belongs to. The return value is valid iff immediately after
+        receive_message call.
+        """
+
+        return self._original_opcode
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/mod_pywebsocket/common.py b/tools/pywebsocket/mod_pywebsocket/common.py
new file mode 100644
index 0000000..6d173db
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/common.py
@@ -0,0 +1,306 @@
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file must not depend on any module specific to the WebSocket protocol.
+"""
+
+
+from mod_pywebsocket import http_header_util
+
+
+# Additional log level definitions.
+LOGLEVEL_FINE = 9
+
+# Constants indicating WebSocket protocol version.
+VERSION_HIXIE75 = -1
+VERSION_HYBI00 = 0
+VERSION_HYBI01 = 1
+VERSION_HYBI02 = 2
+VERSION_HYBI03 = 2
+VERSION_HYBI04 = 4
+VERSION_HYBI05 = 5
+VERSION_HYBI06 = 6
+VERSION_HYBI07 = 7
+VERSION_HYBI08 = 8
+VERSION_HYBI09 = 8
+VERSION_HYBI10 = 8
+VERSION_HYBI11 = 8
+VERSION_HYBI12 = 8
+VERSION_HYBI13 = 13
+VERSION_HYBI14 = 13
+VERSION_HYBI15 = 13
+VERSION_HYBI16 = 13
+VERSION_HYBI17 = 13
+
+# Constants indicating WebSocket protocol latest version.
+VERSION_HYBI_LATEST = VERSION_HYBI13
+
+# Port numbers
+DEFAULT_WEB_SOCKET_PORT = 80
+DEFAULT_WEB_SOCKET_SECURE_PORT = 443
+
+# Schemes
+WEB_SOCKET_SCHEME = 'ws'
+WEB_SOCKET_SECURE_SCHEME = 'wss'
+
+# Frame opcodes defined in the spec.
+OPCODE_CONTINUATION = 0x0
+OPCODE_TEXT = 0x1
+OPCODE_BINARY = 0x2
+OPCODE_CLOSE = 0x8
+OPCODE_PING = 0x9
+OPCODE_PONG = 0xa
+
+# UUIDs used by HyBi 04 and later opening handshake and frame masking.
+WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
+
+# Opening handshake header names and expected values.
+UPGRADE_HEADER = 'Upgrade'
+WEBSOCKET_UPGRADE_TYPE = 'websocket'
+WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket'
+CONNECTION_HEADER = 'Connection'
+UPGRADE_CONNECTION_TYPE = 'Upgrade'
+HOST_HEADER = 'Host'
+ORIGIN_HEADER = 'Origin'
+SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin'
+SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key'
+SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept'
+SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version'
+SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol'
+SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions'
+SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft'
+SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1'
+SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2'
+SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
+
+# Extensions
+DEFLATE_FRAME_EXTENSION = 'deflate-frame'
+PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
+X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
+MUX_EXTENSION = 'mux_DO_NOT_USE'
+
+# Status codes
+# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and
+# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases.
+# Could not be used for codes in actual closing frames.
+# Application level errors must use codes in the range
+# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the
+# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed
+# by IANA. Usually application must define user protocol level errors in the
+# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX.
+STATUS_NORMAL_CLOSURE = 1000
+STATUS_GOING_AWAY = 1001
+STATUS_PROTOCOL_ERROR = 1002
+STATUS_UNSUPPORTED_DATA = 1003
+STATUS_NO_STATUS_RECEIVED = 1005
+STATUS_ABNORMAL_CLOSURE = 1006
+STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
+STATUS_POLICY_VIOLATION = 1008
+STATUS_MESSAGE_TOO_BIG = 1009
+STATUS_MANDATORY_EXTENSION = 1010
+STATUS_INTERNAL_ENDPOINT_ERROR = 1011
+STATUS_TLS_HANDSHAKE = 1015
+STATUS_USER_REGISTERED_BASE = 3000
+STATUS_USER_REGISTERED_MAX = 3999
+STATUS_USER_PRIVATE_BASE = 4000
+STATUS_USER_PRIVATE_MAX = 4999
+# Following definitions are aliases to keep compatibility. Applications must
+# not use these obsoleted definitions anymore.
+STATUS_NORMAL = STATUS_NORMAL_CLOSURE
+STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
+STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
+STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
+STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
+STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
+
+# HTTP status codes
+HTTP_STATUS_BAD_REQUEST = 400
+HTTP_STATUS_FORBIDDEN = 403
+HTTP_STATUS_NOT_FOUND = 404
+
+
+def is_control_opcode(opcode):
+    return (opcode >> 3) == 1
+
+
+class ExtensionParameter(object):
+
+    """This is exchanged on extension negotiation in opening handshake."""
+
+    def __init__(self, name):
+        self._name = name
+        # TODO(tyoshino): Change the data structure to more efficient one such
+        # as dict when the spec changes to say like
+        # - Parameter names must be unique
+        # - The order of parameters is not significant
+        self._parameters = []
+
+    def name(self):
+        """Return the extension name."""
+        return self._name
+
+    def add_parameter(self, name, value):
+        """Add a parameter."""
+        self._parameters.append((name, value))
+
+    def get_parameters(self):
+        """Return the parameters."""
+        return self._parameters
+
+    def get_parameter_names(self):
+        """Return the names of the parameters."""
+        return [name for name, unused_value in self._parameters]
+
+    def has_parameter(self, name):
+        """Test if a parameter exists."""
+        for param_name, param_value in self._parameters:
+            if param_name == name:
+                return True
+        return False
+
+    def get_parameter_value(self, name):
+        """Get the value of a specific parameter."""
+        for param_name, param_value in self._parameters:
+            if param_name == name:
+                return param_value
+
+
+class ExtensionParsingException(Exception):
+
+    """Exception to handle errors in extension parsing."""
+
+    def __init__(self, name):
+        super(ExtensionParsingException, self).__init__(name)
+
+
+def _parse_extension_param(state, definition):
+    param_name = http_header_util.consume_token(state)
+
+    if param_name is None:
+        raise ExtensionParsingException('No valid parameter name found')
+
+    http_header_util.consume_lwses(state)
+
+    if not http_header_util.consume_string(state, '='):
+        definition.add_parameter(param_name, None)
+        return
+
+    http_header_util.consume_lwses(state)
+
+    # TODO(tyoshino): Add code to validate that parsed param_value is token
+    param_value = http_header_util.consume_token_or_quoted_string(state)
+    if param_value is None:
+        raise ExtensionParsingException(
+            'No valid parameter value found on the right-hand side of '
+            'parameter %r' % param_name)
+
+    definition.add_parameter(param_name, param_value)
+
+
+def _parse_extension(state):
+    extension_token = http_header_util.consume_token(state)
+    if extension_token is None:
+        return None
+
+    extension = ExtensionParameter(extension_token)
+
+    while True:
+        http_header_util.consume_lwses(state)
+
+        if not http_header_util.consume_string(state, ';'):
+            break
+
+        http_header_util.consume_lwses(state)
+
+        try:
+            _parse_extension_param(state, extension)
+        except ExtensionParsingException, e:
+            raise ExtensionParsingException(
+                'Failed to parse parameter for %r (%r)' %
+                (extension_token, e))
+
+    return extension
+
+
+def parse_extensions(data):
+    """Parse Sec-WebSocket-Extensions header value.
+
+    Returns a list of ExtensionParameter objects.
+    Leading LWSes must be trimmed.
+    """
+    state = http_header_util.ParsingState(data)
+
+    extension_list = []
+    while True:
+        extension = _parse_extension(state)
+        if extension is not None:
+            extension_list.append(extension)
+
+        http_header_util.consume_lwses(state)
+
+        if http_header_util.peek(state) is None:
+            break
+
+        if not http_header_util.consume_string(state, ','):
+            raise ExtensionParsingException(
+                'Failed to parse Sec-WebSocket-Extensions header: '
+                'Expected a comma but found %r' %
+                http_header_util.peek(state))
+
+        http_header_util.consume_lwses(state)
+
+    if len(extension_list) == 0:
+        raise ExtensionParsingException(
+            'No valid extension entry found')
+
+    return extension_list
+
+
+def format_extension(extension):
+    """Format an ExtensionParameter object."""
+    formatted_params = [extension.name()]
+    for param_name, param_value in extension.get_parameters():
+        if param_value is None:
+            formatted_params.append(param_name)
+        else:
+            quoted_value = http_header_util.quote_if_necessary(param_value)
+            formatted_params.append('%s=%s' % (param_name, quoted_value))
+    return '; '.join(formatted_params)
+
+
+def format_extensions(extension_list):
+    """Format a list of ExtensionParameter objects."""
+    formatted_extension_list = []
+    for extension in extension_list:
+        formatted_extension_list.append(format_extension(extension))
+    return ', '.join(formatted_extension_list)
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/dispatch.py b/tools/pywebsocket/mod_pywebsocket/dispatch.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/dispatch.py
rename to tools/pywebsocket/mod_pywebsocket/dispatch.py
diff --git a/tools/pywebsocket/mod_pywebsocket/extensions.py b/tools/pywebsocket/mod_pywebsocket/extensions.py
new file mode 100644
index 0000000..b4b5eaf
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/extensions.py
@@ -0,0 +1,750 @@
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+from mod_pywebsocket import common
+from mod_pywebsocket import util
+from mod_pywebsocket.http_header_util import quote_if_necessary
+
+
+# The list of available server side extension processor classes.
+_available_processors = {}
+_compression_extension_names = []
+
+
+class ExtensionProcessorInterface(object):
+
+    def __init__(self, request):
+        self._logger = util.get_class_logger(self)
+
+        self._request = request
+        self._active = True
+
+    def request(self):
+        return self._request
+
+    def name(self):
+        return None
+
+    def check_consistency_with_other_processors(self, processors):
+        pass
+
+    def set_active(self, active):
+        self._active = active
+
+    def is_active(self):
+        return self._active
+
+    def _get_extension_response_internal(self):
+        return None
+
+    def get_extension_response(self):
+        if not self._active:
+            self._logger.debug('Extension %s is deactivated', self.name())
+            return None
+
+        response = self._get_extension_response_internal()
+        if response is None:
+            self._active = False
+        return response
+
+    def _setup_stream_options_internal(self, stream_options):
+        pass
+
+    def setup_stream_options(self, stream_options):
+        if self._active:
+            self._setup_stream_options_internal(stream_options)
+
+
+def _log_outgoing_compression_ratio(
+        logger, original_bytes, filtered_bytes, average_ratio):
+    # Print inf when ratio is not available.
+    ratio = float('inf')
+    if original_bytes != 0:
+        ratio = float(filtered_bytes) / original_bytes
+
+    logger.debug('Outgoing compression ratio: %f (average: %f)' %
+            (ratio, average_ratio))
+
+
+def _log_incoming_compression_ratio(
+        logger, received_bytes, filtered_bytes, average_ratio):
+    # Print inf when ratio is not available.
+    ratio = float('inf')
+    if filtered_bytes != 0:
+        ratio = float(received_bytes) / filtered_bytes
+
+    logger.debug('Incoming compression ratio: %f (average: %f)' %
+            (ratio, average_ratio))
+
+
+def _parse_window_bits(bits):
+    """Return parsed integer value iff the given string conforms to the
+    grammar of the window bits extension parameters.
+    """
+
+    if bits is None:
+        raise ValueError('Value is required')
+
+    # For non integer values such as "10.0", ValueError will be raised.
+    int_bits = int(bits)
+
+    # First condition is to drop leading zero case e.g. "08".
+    if bits != str(int_bits) or int_bits < 8 or int_bits > 15:
+        raise ValueError('Invalid value: %r' % bits)
+
+    return int_bits
+
+
+class _AverageRatioCalculator(object):
+    """Stores total bytes of original and result data, and calculates average
+    result / original ratio.
+    """
+
+    def __init__(self):
+        self._total_original_bytes = 0
+        self._total_result_bytes = 0
+
+    def add_original_bytes(self, value):
+        self._total_original_bytes += value
+
+    def add_result_bytes(self, value):
+        self._total_result_bytes += value
+
+    def get_average_ratio(self):
+        if self._total_original_bytes != 0:
+            return (float(self._total_result_bytes) /
+                    self._total_original_bytes)
+        else:
+            return float('inf')
+
+
+class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
+    """deflate-frame extension processor.
+
+    Specification:
+    http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate
+    """
+
+    _WINDOW_BITS_PARAM = 'max_window_bits'
+    _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
+
+    def __init__(self, request):
+        ExtensionProcessorInterface.__init__(self, request)
+        self._logger = util.get_class_logger(self)
+
+        self._response_window_bits = None
+        self._response_no_context_takeover = False
+        self._bfinal = False
+
+        # Calculates
+        #     (Total outgoing bytes supplied to this filter) /
+        #     (Total bytes sent to the network after applying this filter)
+        self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
+
+        # Calculates
+        #     (Total bytes received from the network) /
+        #     (Total incoming bytes obtained after applying this filter)
+        self._incoming_average_ratio_calculator = _AverageRatioCalculator()
+
+    def name(self):
+        return common.DEFLATE_FRAME_EXTENSION
+
+    def _get_extension_response_internal(self):
+        # Any unknown parameter will be just ignored.
+
+        window_bits = None
+        if self._request.has_parameter(self._WINDOW_BITS_PARAM):
+            window_bits = self._request.get_parameter_value(
+                self._WINDOW_BITS_PARAM)
+            try:
+                window_bits = _parse_window_bits(window_bits)
+            except ValueError, e:
+                return None
+
+        no_context_takeover = self._request.has_parameter(
+            self._NO_CONTEXT_TAKEOVER_PARAM)
+        if (no_context_takeover and
+            self._request.get_parameter_value(
+                self._NO_CONTEXT_TAKEOVER_PARAM) is not None):
+            return None
+
+        self._rfc1979_deflater = util._RFC1979Deflater(
+            window_bits, no_context_takeover)
+
+        self._rfc1979_inflater = util._RFC1979Inflater()
+
+        self._compress_outgoing = True
+
+        response = common.ExtensionParameter(self._request.name())
+
+        if self._response_window_bits is not None:
+            response.add_parameter(
+                self._WINDOW_BITS_PARAM, str(self._response_window_bits))
+        if self._response_no_context_takeover:
+            response.add_parameter(
+                self._NO_CONTEXT_TAKEOVER_PARAM, None)
+
+        self._logger.debug(
+            'Enable %s extension ('
+            'request: window_bits=%s; no_context_takeover=%r, '
+            'response: window_wbits=%s; no_context_takeover=%r)' %
+            (self._request.name(),
+             window_bits,
+             no_context_takeover,
+             self._response_window_bits,
+             self._response_no_context_takeover))
+
+        return response
+
+    def _setup_stream_options_internal(self, stream_options):
+
+        class _OutgoingFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+
+            def filter(self, frame):
+                self._parent._outgoing_filter(frame)
+
+        class _IncomingFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+
+            def filter(self, frame):
+                self._parent._incoming_filter(frame)
+
+        stream_options.outgoing_frame_filters.append(
+            _OutgoingFilter(self))
+        stream_options.incoming_frame_filters.insert(
+            0, _IncomingFilter(self))
+
+    def set_response_window_bits(self, value):
+        self._response_window_bits = value
+
+    def set_response_no_context_takeover(self, value):
+        self._response_no_context_takeover = value
+
+    def set_bfinal(self, value):
+        self._bfinal = value
+
+    def enable_outgoing_compression(self):
+        self._compress_outgoing = True
+
+    def disable_outgoing_compression(self):
+        self._compress_outgoing = False
+
+    def _outgoing_filter(self, frame):
+        """Transform outgoing frames. This method is called only by
+        an _OutgoingFilter instance.
+        """
+
+        original_payload_size = len(frame.payload)
+        self._outgoing_average_ratio_calculator.add_original_bytes(
+                original_payload_size)
+
+        if (not self._compress_outgoing or
+            common.is_control_opcode(frame.opcode)):
+            self._outgoing_average_ratio_calculator.add_result_bytes(
+                    original_payload_size)
+            return
+
+        frame.payload = self._rfc1979_deflater.filter(
+            frame.payload, bfinal=self._bfinal)
+        frame.rsv1 = 1
+
+        filtered_payload_size = len(frame.payload)
+        self._outgoing_average_ratio_calculator.add_result_bytes(
+                filtered_payload_size)
+
+        _log_outgoing_compression_ratio(
+                self._logger,
+                original_payload_size,
+                filtered_payload_size,
+                self._outgoing_average_ratio_calculator.get_average_ratio())
+
+    def _incoming_filter(self, frame):
+        """Transform incoming frames. This method is called only by
+        an _IncomingFilter instance.
+        """
+
+        received_payload_size = len(frame.payload)
+        self._incoming_average_ratio_calculator.add_result_bytes(
+                received_payload_size)
+
+        if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
+            self._incoming_average_ratio_calculator.add_original_bytes(
+                    received_payload_size)
+            return
+
+        frame.payload = self._rfc1979_inflater.filter(frame.payload)
+        frame.rsv1 = 0
+
+        filtered_payload_size = len(frame.payload)
+        self._incoming_average_ratio_calculator.add_original_bytes(
+                filtered_payload_size)
+
+        _log_incoming_compression_ratio(
+                self._logger,
+                received_payload_size,
+                filtered_payload_size,
+                self._incoming_average_ratio_calculator.get_average_ratio())
+
+
+_available_processors[common.DEFLATE_FRAME_EXTENSION] = (
+    DeflateFrameExtensionProcessor)
+_compression_extension_names.append(common.DEFLATE_FRAME_EXTENSION)
+
+_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
+    DeflateFrameExtensionProcessor)
+_compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
+
+
+class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
+    """permessage-deflate extension processor.
+
+    Specification:
+    http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08
+    """
+
+    _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits'
+    _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover'
+    _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits'
+    _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
+
+    def __init__(self, request):
+        """Construct PerMessageDeflateExtensionProcessor."""
+
+        ExtensionProcessorInterface.__init__(self, request)
+        self._logger = util.get_class_logger(self)
+
+        self._preferred_client_max_window_bits = None
+        self._client_no_context_takeover = False
+
+    def name(self):
+        # This method returns "deflate" (not "permessage-deflate") for
+        # compatibility.
+        return 'deflate'
+
+    def _get_extension_response_internal(self):
+        for name in self._request.get_parameter_names():
+            if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
+                            self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
+                            self._CLIENT_MAX_WINDOW_BITS_PARAM]:
+                self._logger.debug('Unknown parameter: %r', name)
+                return None
+
+        server_max_window_bits = None
+        if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM):
+            server_max_window_bits = self._request.get_parameter_value(
+                    self._SERVER_MAX_WINDOW_BITS_PARAM)
+            try:
+                server_max_window_bits = _parse_window_bits(
+                    server_max_window_bits)
+            except ValueError, e:
+                self._logger.debug('Bad %s parameter: %r',
+                                   self._SERVER_MAX_WINDOW_BITS_PARAM,
+                                   e)
+                return None
+
+        server_no_context_takeover = self._request.has_parameter(
+            self._SERVER_NO_CONTEXT_TAKEOVER_PARAM)
+        if (server_no_context_takeover and
+            self._request.get_parameter_value(
+                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None):
+            self._logger.debug('%s parameter must not have a value: %r',
+                               self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
+                               server_no_context_takeover)
+            return None
+
+        # client_max_window_bits from a client indicates whether the client can
+        # accept client_max_window_bits from a server or not.
+        client_client_max_window_bits = self._request.has_parameter(
+            self._CLIENT_MAX_WINDOW_BITS_PARAM)
+        if (client_client_max_window_bits and
+            self._request.get_parameter_value(
+                self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
+            self._logger.debug('%s parameter must not have a value in a '
+                               'client\'s opening handshake: %r',
+                               self._CLIENT_MAX_WINDOW_BITS_PARAM,
+                               client_client_max_window_bits)
+            return None
+
+        self._rfc1979_deflater = util._RFC1979Deflater(
+            server_max_window_bits, server_no_context_takeover)
+
+        # Note that we prepare for incoming messages compressed with window
+        # bits upto 15 regardless of the client_max_window_bits value to be
+        # sent to the client.
+        self._rfc1979_inflater = util._RFC1979Inflater()
+
+        self._framer = _PerMessageDeflateFramer(
+            server_max_window_bits, server_no_context_takeover)
+        self._framer.set_bfinal(False)
+        self._framer.set_compress_outgoing_enabled(True)
+
+        response = common.ExtensionParameter(self._request.name())
+
+        if server_max_window_bits is not None:
+            response.add_parameter(
+                self._SERVER_MAX_WINDOW_BITS_PARAM,
+                str(server_max_window_bits))
+
+        if server_no_context_takeover:
+            response.add_parameter(
+                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None)
+
+        if self._preferred_client_max_window_bits is not None:
+            if not client_client_max_window_bits:
+                self._logger.debug('Processor is configured to use %s but '
+                                   'the client cannot accept it',
+                                   self._CLIENT_MAX_WINDOW_BITS_PARAM)
+                return None
+            response.add_parameter(
+                self._CLIENT_MAX_WINDOW_BITS_PARAM,
+                str(self._preferred_client_max_window_bits))
+
+        if self._client_no_context_takeover:
+            response.add_parameter(
+                self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None)
+
+        self._logger.debug(
+            'Enable %s extension ('
+            'request: server_max_window_bits=%s; '
+            'server_no_context_takeover=%r, '
+            'response: client_max_window_bits=%s; '
+            'client_no_context_takeover=%r)' %
+            (self._request.name(),
+             server_max_window_bits,
+             server_no_context_takeover,
+             self._preferred_client_max_window_bits,
+             self._client_no_context_takeover))
+
+        return response
+
+    def _setup_stream_options_internal(self, stream_options):
+        self._framer.setup_stream_options(stream_options)
+
+    def set_client_max_window_bits(self, value):
+        """If this option is specified, this class adds the
+        client_max_window_bits extension parameter to the handshake response,
+        but doesn't reduce the LZ77 sliding window size of its inflater.
+        I.e., you can use this for testing client implementation but cannot
+        reduce memory usage of this class.
+
+        If this method has been called with True and an offer without the
+        client_max_window_bits extension parameter is received,
+        - (When processing the permessage-deflate extension) this processor
+          declines the request.
+        - (When processing the permessage-compress extension) this processor
+          accepts the request.
+        """
+
+        self._preferred_client_max_window_bits = value
+
+    def set_client_no_context_takeover(self, value):
+        """If this option is specified, this class adds the
+        client_no_context_takeover extension parameter to the handshake
+        response, but doesn't reset inflater for each message. I.e., you can
+        use this for testing client implementation but cannot reduce memory
+        usage of this class.
+        """
+
+        self._client_no_context_takeover = value
+
+    def set_bfinal(self, value):
+        self._framer.set_bfinal(value)
+
+    def enable_outgoing_compression(self):
+        self._framer.set_compress_outgoing_enabled(True)
+
+    def disable_outgoing_compression(self):
+        self._framer.set_compress_outgoing_enabled(False)
+
+
+class _PerMessageDeflateFramer(object):
+    """A framer for extensions with per-message DEFLATE feature."""
+
+    def __init__(self, deflate_max_window_bits, deflate_no_context_takeover):
+        self._logger = util.get_class_logger(self)
+
+        self._rfc1979_deflater = util._RFC1979Deflater(
+            deflate_max_window_bits, deflate_no_context_takeover)
+
+        self._rfc1979_inflater = util._RFC1979Inflater()
+
+        self._bfinal = False
+
+        self._compress_outgoing_enabled = False
+
+        # True if a message is fragmented and compression is ongoing.
+        self._compress_ongoing = False
+
+        # Calculates
+        #     (Total outgoing bytes supplied to this filter) /
+        #     (Total bytes sent to the network after applying this filter)
+        self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
+
+        # Calculates
+        #     (Total bytes received from the network) /
+        #     (Total incoming bytes obtained after applying this filter)
+        self._incoming_average_ratio_calculator = _AverageRatioCalculator()
+
+    def set_bfinal(self, value):
+        self._bfinal = value
+
+    def set_compress_outgoing_enabled(self, value):
+        self._compress_outgoing_enabled = value
+
+    def _process_incoming_message(self, message, decompress):
+        if not decompress:
+            return message
+
+        received_payload_size = len(message)
+        self._incoming_average_ratio_calculator.add_result_bytes(
+                received_payload_size)
+
+        message = self._rfc1979_inflater.filter(message)
+
+        filtered_payload_size = len(message)
+        self._incoming_average_ratio_calculator.add_original_bytes(
+                filtered_payload_size)
+
+        _log_incoming_compression_ratio(
+                self._logger,
+                received_payload_size,
+                filtered_payload_size,
+                self._incoming_average_ratio_calculator.get_average_ratio())
+
+        return message
+
+    def _process_outgoing_message(self, message, end, binary):
+        if not binary:
+            message = message.encode('utf-8')
+
+        if not self._compress_outgoing_enabled:
+            return message
+
+        original_payload_size = len(message)
+        self._outgoing_average_ratio_calculator.add_original_bytes(
+            original_payload_size)
+
+        message = self._rfc1979_deflater.filter(
+            message, end=end, bfinal=self._bfinal)
+
+        filtered_payload_size = len(message)
+        self._outgoing_average_ratio_calculator.add_result_bytes(
+            filtered_payload_size)
+
+        _log_outgoing_compression_ratio(
+                self._logger,
+                original_payload_size,
+                filtered_payload_size,
+                self._outgoing_average_ratio_calculator.get_average_ratio())
+
+        if not self._compress_ongoing:
+            self._outgoing_frame_filter.set_compression_bit()
+        self._compress_ongoing = not end
+        return message
+
+    def _process_incoming_frame(self, frame):
+        if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode):
+            self._incoming_message_filter.decompress_next_message()
+            frame.rsv1 = 0
+
+    def _process_outgoing_frame(self, frame, compression_bit):
+        if (not compression_bit or
+            common.is_control_opcode(frame.opcode)):
+            return
+
+        frame.rsv1 = 1
+
+    def setup_stream_options(self, stream_options):
+        """Creates filters and sets them to the StreamOptions."""
+
+        class _OutgoingMessageFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+
+            def filter(self, message, end=True, binary=False):
+                return self._parent._process_outgoing_message(
+                    message, end, binary)
+
+        class _IncomingMessageFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+                self._decompress_next_message = False
+
+            def decompress_next_message(self):
+                self._decompress_next_message = True
+
+            def filter(self, message):
+                message = self._parent._process_incoming_message(
+                    message, self._decompress_next_message)
+                self._decompress_next_message = False
+                return message
+
+        self._outgoing_message_filter = _OutgoingMessageFilter(self)
+        self._incoming_message_filter = _IncomingMessageFilter(self)
+        stream_options.outgoing_message_filters.append(
+            self._outgoing_message_filter)
+        stream_options.incoming_message_filters.append(
+            self._incoming_message_filter)
+
+        class _OutgoingFrameFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+                self._set_compression_bit = False
+
+            def set_compression_bit(self):
+                self._set_compression_bit = True
+
+            def filter(self, frame):
+                self._parent._process_outgoing_frame(
+                    frame, self._set_compression_bit)
+                self._set_compression_bit = False
+
+        class _IncomingFrameFilter(object):
+
+            def __init__(self, parent):
+                self._parent = parent
+
+            def filter(self, frame):
+                self._parent._process_incoming_frame(frame)
+
+        self._outgoing_frame_filter = _OutgoingFrameFilter(self)
+        self._incoming_frame_filter = _IncomingFrameFilter(self)
+        stream_options.outgoing_frame_filters.append(
+            self._outgoing_frame_filter)
+        stream_options.incoming_frame_filters.append(
+            self._incoming_frame_filter)
+
+        stream_options.encode_text_message_to_utf8 = False
+
+
+_available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = (
+        PerMessageDeflateExtensionProcessor)
+# TODO(tyoshino): Reorganize class names.
+_compression_extension_names.append('deflate')
+
+
+class MuxExtensionProcessor(ExtensionProcessorInterface):
+    """WebSocket multiplexing extension processor."""
+
+    _QUOTA_PARAM = 'quota'
+
+    def __init__(self, request):
+        ExtensionProcessorInterface.__init__(self, request)
+        self._quota = 0
+        self._extensions = []
+
+    def name(self):
+        return common.MUX_EXTENSION
+
+    def check_consistency_with_other_processors(self, processors):
+        before_mux = True
+        for processor in processors:
+            name = processor.name()
+            if name == self.name():
+                before_mux = False
+                continue
+            if not processor.is_active():
+                continue
+            if before_mux:
+                # Mux extension cannot be used after extensions
+                # that depend on frame boundary, extension data field, or any
+                # reserved bits which are attributed to each frame.
+                if (name == common.DEFLATE_FRAME_EXTENSION or
+                    name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
+                    self.set_active(False)
+                    return
+            else:
+                # Mux extension should not be applied before any history-based
+                # compression extension.
+                if (name == 'deflate' or
+                    name == common.DEFLATE_FRAME_EXTENSION or
+                    name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
+                    self.set_active(False)
+                    return
+
+    def _get_extension_response_internal(self):
+        self._active = False
+        quota = self._request.get_parameter_value(self._QUOTA_PARAM)
+        if quota is not None:
+            try:
+                quota = int(quota)
+            except ValueError, e:
+                return None
+            if quota < 0 or quota >= 2 ** 32:
+                return None
+            self._quota = quota
+
+        self._active = True
+        return common.ExtensionParameter(common.MUX_EXTENSION)
+
+    def _setup_stream_options_internal(self, stream_options):
+        pass
+
+    def set_quota(self, quota):
+        self._quota = quota
+
+    def quota(self):
+        return self._quota
+
+    def set_extensions(self, extensions):
+        self._extensions = extensions
+
+    def extensions(self):
+        return self._extensions
+
+
+_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor
+
+
+def get_extension_processor(extension_request):
+    """Given an ExtensionParameter representing an extension offer received
+    from a client, configures and returns an instance of the corresponding
+    extension processor class.
+    """
+
+    processor_class = _available_processors.get(extension_request.name())
+    if processor_class is None:
+        return None
+    return processor_class(extension_request)
+
+
+def is_compression_extension(extension_name):
+    return extension_name in _compression_extension_names
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/fast_masking.i b/tools/pywebsocket/mod_pywebsocket/fast_masking.i
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/fast_masking.i
rename to tools/pywebsocket/mod_pywebsocket/fast_masking.i
diff --git a/tools/pywebsocket/src/mod_pywebsocket/handshake/__init__.py b/tools/pywebsocket/mod_pywebsocket/handshake/__init__.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/handshake/__init__.py
rename to tools/pywebsocket/mod_pywebsocket/handshake/__init__.py
diff --git a/tools/pywebsocket/src/mod_pywebsocket/handshake/_base.py b/tools/pywebsocket/mod_pywebsocket/handshake/_base.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/handshake/_base.py
rename to tools/pywebsocket/mod_pywebsocket/handshake/_base.py
diff --git a/tools/pywebsocket/src/mod_pywebsocket/handshake/hybi.py b/tools/pywebsocket/mod_pywebsocket/handshake/hybi.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/handshake/hybi.py
rename to tools/pywebsocket/mod_pywebsocket/handshake/hybi.py
diff --git a/tools/pywebsocket/src/mod_pywebsocket/handshake/hybi00.py b/tools/pywebsocket/mod_pywebsocket/handshake/hybi00.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/handshake/hybi00.py
rename to tools/pywebsocket/mod_pywebsocket/handshake/hybi00.py
diff --git a/tools/pywebsocket/mod_pywebsocket/headerparserhandler.py b/tools/pywebsocket/mod_pywebsocket/headerparserhandler.py
new file mode 100644
index 0000000..dda43c9
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/headerparserhandler.py
@@ -0,0 +1,257 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""PythonHeaderParserHandler for mod_pywebsocket.
+
+Apache HTTP Server and mod_python must be configured such that this
+function is called to handle WebSocket request.
+"""
+
+
+import logging
+
+from mod_python import apache
+
+from mod_pywebsocket import common
+from mod_pywebsocket import dispatch
+from mod_pywebsocket import handshake
+from mod_pywebsocket import util
+
+
+# PythonOption to specify the handler root directory.
+_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root'
+
+# PythonOption to specify the handler scan directory.
+# This must be a directory under the root directory.
+# The default is the root directory.
+_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan'
+
+# PythonOption to allow handlers whose canonical path is
+# not under the root directory. It's disallowed by default.
+# Set this option with value of 'yes' to allow.
+_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = (
+    'mod_pywebsocket.allow_handlers_outside_root_dir')
+# Map from values to their meanings. 'Yes' and 'No' are allowed just for
+# compatibility.
+_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = {
+    'off': False, 'no': False, 'on': True, 'yes': True}
+
+# (Obsolete option. Ignored.)
+# PythonOption to specify to allow handshake defined in Hixie 75 version
+# protocol. The default is None (Off)
+_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75'
+# Map from values to their meanings.
+_PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True}
+
+
+class ApacheLogHandler(logging.Handler):
+
+    """Wrapper logging.Handler to emit log message to apache's error.log."""
+
+    _LEVELS = {
+        logging.DEBUG: apache.APLOG_DEBUG,
+        logging.INFO: apache.APLOG_INFO,
+        logging.WARNING: apache.APLOG_WARNING,
+        logging.ERROR: apache.APLOG_ERR,
+        logging.CRITICAL: apache.APLOG_CRIT,
+        }
+
+    def __init__(self, request=None):
+        logging.Handler.__init__(self)
+        self._log_error = apache.log_error
+        if request is not None:
+            self._log_error = request.log_error
+
+        # Time and level will be printed by Apache.
+        self._formatter = logging.Formatter('%(name)s: %(message)s')
+
+    def emit(self, record):
+        apache_level = apache.APLOG_DEBUG
+        if record.levelno in ApacheLogHandler._LEVELS:
+            apache_level = ApacheLogHandler._LEVELS[record.levelno]
+
+        msg = self._formatter.format(record)
+
+        # "server" parameter must be passed to have "level" parameter work.
+        # If only "level" parameter is passed, nothing shows up on Apache's
+        # log. However, at this point, we cannot get the server object of the
+        # virtual host which will process WebSocket requests. The only server
+        # object we can get here is apache.main_server. But Wherever (server
+        # configuration context or virtual host context) we put
+        # PythonHeaderParserHandler directive, apache.main_server just points
+        # the main server instance (not any of virtual server instance). Then,
+        # Apache follows LogLevel directive in the server configuration context
+        # to filter logs. So, we need to specify LogLevel in the server
+        # configuration context. Even if we specify "LogLevel debug" in the
+        # virtual host context which actually handles WebSocket connections,
+        # DEBUG level logs never show up unless "LogLevel debug" is specified
+        # in the server configuration context.
+        #
+        # TODO(tyoshino): Provide logging methods on request object. When
+        # request is mp_request object (when used together with Apache), the
+        # methods call request.log_error indirectly. When request is
+        # _StandaloneRequest, the methods call Python's logging facility which
+        # we create in standalone.py.
+        self._log_error(msg, apache_level, apache.main_server)
+
+
+def _configure_logging():
+    logger = logging.getLogger()
+    # Logs are filtered by Apache based on LogLevel directive in Apache
+    # configuration file. We must just pass logs for all levels to
+    # ApacheLogHandler.
+    logger.setLevel(logging.DEBUG)
+    logger.addHandler(ApacheLogHandler())
+
+
+_configure_logging()
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def _parse_option(name, value, definition):
+    """Return the meaning of a option value."""
+    if value is None:
+        return False
+
+    meaning = definition.get(value.lower())
+    if meaning is None:
+        raise Exception('Invalid value for PythonOption %s: %r' %
+                        (name, value))
+    return meaning
+
+
+def _create_dispatcher():
+    """Initialize a dispatch.Dispatcher."""
+    _LOGGER.info('Initializing Dispatcher')
+
+    options = apache.main_server.get_options()
+
+    handler_root = options.get(_PYOPT_HANDLER_ROOT, None)
+    if not handler_root:
+        raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT,
+                        apache.APLOG_ERR)
+
+    handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root)
+
+    allow_handlers_outside_root = _parse_option(
+        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT,
+        options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT),
+        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION)
+
+    dispatcher = dispatch.Dispatcher(
+        handler_root, handler_scan, allow_handlers_outside_root)
+
+    for warning in dispatcher.source_warnings():
+        apache.log_error(
+            'mod_pywebsocket: Warning in source loading: %s' % warning,
+            apache.APLOG_WARNING)
+
+    return dispatcher
+
+
+# Initialize
+_dispatcher = _create_dispatcher()
+
+
+def headerparserhandler(request):
+    """Handle request.
+
+    Args:
+        request: mod_python request.
+
+    This function is named headerparserhandler because it is the default
+    name for a PythonHeaderParserHandler.
+    """
+    handshake_is_done = False
+    try:
+        # Fallback to default http handler for request paths for which
+        # we don't have request handlers.
+        if not _dispatcher.get_handler_suite(request.uri):
+            request.log_error(
+                'mod_pywebsocket: No handler for resource: %r' % request.uri,
+                apache.APLOG_INFO)
+            request.log_error(
+                'mod_pywebsocket: Fallback to Apache', apache.APLOG_INFO)
+            return apache.DECLINED
+    except dispatch.DispatchException, e:
+        request.log_error(
+            'mod_pywebsocket: Dispatch failed for error: %s' % e,
+            apache.APLOG_INFO)
+        if not handshake_is_done:
+            return e.status
+
+    try:
+        allow_draft75 = _parse_option(
+            _PYOPT_ALLOW_DRAFT75,
+            apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75),
+            _PYOPT_ALLOW_DRAFT75_DEFINITION)
+
+        try:
+            handshake.do_handshake(
+                request, _dispatcher, allowDraft75=allow_draft75)
+        except handshake.VersionException, e:
+            request.log_error(
+                'mod_pywebsocket: Handshake failed for version error: %s' % e,
+                apache.APLOG_INFO)
+            request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER,
+                                        e.supported_versions)
+            return apache.HTTP_BAD_REQUEST
+        except handshake.HandshakeException, e:
+            # Handshake for ws/wss failed.
+            # Send http response with error status.
+            request.log_error(
+                'mod_pywebsocket: Handshake failed for error: %s' % e,
+                apache.APLOG_INFO)
+            return e.status
+
+        handshake_is_done = True
+        request._dispatcher = _dispatcher
+        _dispatcher.transfer_data(request)
+    except handshake.AbortedByUserException, e:
+        request.log_error('mod_pywebsocket: Aborted: %s' % e,
+                          apache.APLOG_INFO)
+    except Exception, e:
+        # DispatchException can also be thrown if something is wrong in
+        # pywebsocket code. It's caught here, then.
+
+        request.log_error('mod_pywebsocket: Exception occurred: %s\n%s' %
+                          (e, util.get_stack_trace()),
+                          apache.APLOG_ERR)
+        # Unknown exceptions before handshake mean Apache must handle its
+        # request with another handler.
+        if not handshake_is_done:
+            return apache.DECLINED
+    # Set assbackwards to suppress response header generation by Apache.
+    request.assbackwards = 1
+    return apache.DONE  # Return DONE such that no other handlers are invoked.
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/http_header_util.py b/tools/pywebsocket/mod_pywebsocket/http_header_util.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/http_header_util.py
rename to tools/pywebsocket/mod_pywebsocket/http_header_util.py
diff --git a/tools/pywebsocket/mod_pywebsocket/memorizingfile.py b/tools/pywebsocket/mod_pywebsocket/memorizingfile.py
new file mode 100644
index 0000000..a24e7ee
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/memorizingfile.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Memorizing file.
+
+A memorizing file wraps a file and memorizes lines read by readline.
+"""
+
+
+import sys
+
+
+class MemorizingFile(object):
+
+    """MemorizingFile wraps a file and memorizes lines read by readline.
+
+    Note that data read by other methods are not memorized. This behavior
+    is good enough for memorizing lines SimpleHTTPServer reads before
+    the control reaches WebSocketRequestHandler.
+    """
+
+    def __init__(self, file_, max_memorized_lines=sys.maxint):
+        """Construct an instance.
+
+        Args:
+            file_: the file object to wrap.
+            max_memorized_lines: the maximum number of lines to memorize.
+                Only the first max_memorized_lines are memorized.
+                Default: sys.maxint.
+        """
+        self._file = file_
+        self._memorized_lines = []
+        self._max_memorized_lines = max_memorized_lines
+        self._buffered = False
+        self._buffered_line = None
+
+    def __getattribute__(self, name):
+        """Return a file attribute.
+        
+        Returns the value overridden by this class for some attributes,
+        and forwards the call to _file for the other attributes.
+        """
+        if name in ('_file', '_memorized_lines', '_max_memorized_lines',
+                    '_buffered', '_buffered_line', 'readline',
+                    'get_memorized_lines'):
+            return object.__getattribute__(self, name)
+        return self._file.__getattribute__(name)
+
+    def readline(self, size=-1):
+        """Override file.readline and memorize the line read.
+
+        Note that even if size is specified and smaller than actual size,
+        the whole line will be read out from underlying file object by
+        subsequent readline calls.
+        """
+        if self._buffered:
+            line = self._buffered_line
+            self._buffered = False
+        else:
+            line = self._file.readline()
+            if line and len(self._memorized_lines) < self._max_memorized_lines:
+                self._memorized_lines.append(line)
+        if size >= 0 and size < len(line):
+            self._buffered = True
+            self._buffered_line = line[size:]
+            return line[:size]
+        return line
+
+    def get_memorized_lines(self):
+        """Get lines memorized so far."""
+        return self._memorized_lines
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/msgutil.py b/tools/pywebsocket/mod_pywebsocket/msgutil.py
similarity index 100%
rename from tools/pywebsocket/src/mod_pywebsocket/msgutil.py
rename to tools/pywebsocket/mod_pywebsocket/msgutil.py
diff --git a/tools/pywebsocket/mod_pywebsocket/mux.py b/tools/pywebsocket/mod_pywebsocket/mux.py
new file mode 100644
index 0000000..0b65f6e
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/mux.py
@@ -0,0 +1,1885 @@
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file provides classes and helper functions for multiplexing extension.
+
+Specification:
+http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-06
+"""
+
+
+import collections
+import copy
+import email
+import email.parser
+import logging
+import math
+import struct
+import threading
+import traceback
+
+from mod_pywebsocket import common
+from mod_pywebsocket import handshake
+from mod_pywebsocket import util
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import InvalidFrameException
+from mod_pywebsocket._stream_hybi import Frame
+from mod_pywebsocket._stream_hybi import Stream
+from mod_pywebsocket._stream_hybi import StreamOptions
+from mod_pywebsocket._stream_hybi import create_binary_frame
+from mod_pywebsocket._stream_hybi import create_closing_handshake_body
+from mod_pywebsocket._stream_hybi import create_header
+from mod_pywebsocket._stream_hybi import create_length_header
+from mod_pywebsocket._stream_hybi import parse_frame
+from mod_pywebsocket.handshake import hybi
+
+
+_CONTROL_CHANNEL_ID = 0
+_DEFAULT_CHANNEL_ID = 1
+
+_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0
+_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1
+_MUX_OPCODE_FLOW_CONTROL = 2
+_MUX_OPCODE_DROP_CHANNEL = 3
+_MUX_OPCODE_NEW_CHANNEL_SLOT = 4
+
+_MAX_CHANNEL_ID = 2 ** 29 - 1
+
+_INITIAL_NUMBER_OF_CHANNEL_SLOTS = 64
+_INITIAL_QUOTA_FOR_CLIENT = 8 * 1024
+
+_HANDSHAKE_ENCODING_IDENTITY = 0
+_HANDSHAKE_ENCODING_DELTA = 1
+
+# We need only these status code for now.
+_HTTP_BAD_RESPONSE_MESSAGES = {
+    common.HTTP_STATUS_BAD_REQUEST: 'Bad Request',
+}
+
+# DropChannel reason code
+# TODO(bashi): Define all reason code defined in -05 draft.
+_DROP_CODE_NORMAL_CLOSURE = 1000
+
+_DROP_CODE_INVALID_ENCAPSULATING_MESSAGE = 2001
+_DROP_CODE_CHANNEL_ID_TRUNCATED = 2002
+_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED = 2003
+_DROP_CODE_UNKNOWN_MUX_OPCODE = 2004
+_DROP_CODE_INVALID_MUX_CONTROL_BLOCK = 2005
+_DROP_CODE_CHANNEL_ALREADY_EXISTS = 2006
+_DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION = 2007
+_DROP_CODE_UNKNOWN_REQUEST_ENCODING = 2010
+
+_DROP_CODE_SEND_QUOTA_VIOLATION = 3005
+_DROP_CODE_SEND_QUOTA_OVERFLOW = 3006
+_DROP_CODE_ACKNOWLEDGED = 3008
+_DROP_CODE_BAD_FRAGMENTATION = 3009
+
+
+class MuxUnexpectedException(Exception):
+
+    """Exception in handling multiplexing extension."""
+
+    pass
+
+
+# Temporary
+class MuxNotImplementedException(Exception):
+
+    """Raised when a flow enters unimplemented code path."""
+
+    pass
+
+
+class LogicalConnectionClosedException(Exception):
+
+    """Raised when logical connection is gracefully closed."""
+
+    pass
+
+
+class PhysicalConnectionError(Exception):
+
+    """Raised when there is a physical connection error."""
+
+    def __init__(self, drop_code, message=''):
+        super(PhysicalConnectionError, self).__init__(
+            'code=%d, message=%r' % (drop_code, message))
+        self.drop_code = drop_code
+        self.message = message
+
+
+class LogicalChannelError(Exception):
+
+    """Raised when there is a logical channel error."""
+
+    def __init__(self, channel_id, drop_code, message=''):
+        """Initialize the error with a status message."""
+        super(LogicalChannelError, self).__init__(
+            'channel_id=%d, code=%d, message=%r' % (
+                channel_id, drop_code, message))
+        self.channel_id = channel_id
+        self.drop_code = drop_code
+        self.message = message
+
+
+def _encode_channel_id(channel_id):
+    if channel_id < 0:
+        raise ValueError('Channel id %d must not be negative' % channel_id)
+
+    if channel_id < 2 ** 7:
+        return chr(channel_id)
+    if channel_id < 2 ** 14:
+        return struct.pack('!H', 0x8000 + channel_id)
+    if channel_id < 2 ** 21:
+        first = chr(0xc0 + (channel_id >> 16))
+        return first + struct.pack('!H', channel_id & 0xffff)
+    if channel_id < 2 ** 29:
+        return struct.pack('!L', 0xe0000000 + channel_id)
+
+    raise ValueError('Channel id %d is too large' % channel_id)
+
+
+def _encode_number(number):
+    return create_length_header(number, False)
+
+
+def _create_add_channel_response(channel_id, encoded_handshake,
+                                 encoding=0, rejected=False):
+    if encoding != 0 and encoding != 1:
+        raise ValueError('Invalid encoding %d' % encoding)
+
+    first_byte = ((_MUX_OPCODE_ADD_CHANNEL_RESPONSE << 5) |
+                  (rejected << 4) | encoding)
+    block = (chr(first_byte) +
+             _encode_channel_id(channel_id) +
+             _encode_number(len(encoded_handshake)) +
+             encoded_handshake)
+    return block
+
+
+def _create_drop_channel(channel_id, code=None, message=''):
+    if len(message) > 0 and code is None:
+        raise ValueError('Code must be specified if message is specified')
+
+    first_byte = _MUX_OPCODE_DROP_CHANNEL << 5
+    block = chr(first_byte) + _encode_channel_id(channel_id)
+    if code is None:
+        block += _encode_number(0)  # Reason size
+    else:
+        reason = struct.pack('!H', code) + message
+        reason_size = _encode_number(len(reason))
+        block += reason_size + reason
+
+    return block
+
+
+def _create_flow_control(channel_id, replenished_quota):
+    first_byte = _MUX_OPCODE_FLOW_CONTROL << 5
+    block = (chr(first_byte) +
+             _encode_channel_id(channel_id) +
+             _encode_number(replenished_quota))
+    return block
+
+
+def _create_new_channel_slot(slots, send_quota):
+    if slots < 0 or send_quota < 0:
+        raise ValueError('slots and send_quota must be non-negative.')
+    first_byte = _MUX_OPCODE_NEW_CHANNEL_SLOT << 5
+    block = (chr(first_byte) +
+             _encode_number(slots) +
+             _encode_number(send_quota))
+    return block
+
+
+def _create_fallback_new_channel_slot():
+    first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1  # Set the F flag
+    block = (chr(first_byte) + _encode_number(0) + _encode_number(0))
+    return block
+
+
+def _parse_request_text(request_text):
+    request_line, header_lines = request_text.split('\r\n', 1)
+
+    words = request_line.split(' ')
+    if len(words) != 3:
+        raise ValueError('Bad Request-Line syntax %r' % request_line)
+    [command, path, version] = words
+    if version != 'HTTP/1.1':
+        raise ValueError('Bad request version %r' % version)
+
+    # email.parser.Parser() parses RFC 2822 (RFC 822) style headers.
+    # RFC 6455 refers RFC 2616 for handshake parsing, and RFC 2616 refers
+    # RFC 822.
+    headers = email.parser.Parser().parsestr(header_lines)
+    return command, path, version, headers
+
+
+class _ControlBlock(object):
+
+    """A structure that holds parsing result of multiplexing control block.
+
+    Control block specific attributes will be added by _MuxFramePayloadParser.
+    (e.g. encoded_handshake will be added for AddChannelRequest and
+    AddChannelResponse)
+    """
+
+    def __init__(self, opcode):
+        self.opcode = opcode
+
+
+class _MuxFramePayloadParser(object):
+
+    """A class that parses multiplexed frame payload."""
+
+    def __init__(self, payload):
+        self._data = payload
+        self._read_position = 0
+        self._logger = util.get_class_logger(self)
+
+    def read_channel_id(self):
+        """Read channel id.
+
+        Raises:
+            ValueError: when the payload doesn't contain
+                valid channel id.
+        """
+        remaining_length = len(self._data) - self._read_position
+        pos = self._read_position
+        if remaining_length == 0:
+            raise ValueError('Invalid channel id format')
+
+        channel_id = ord(self._data[pos])
+        channel_id_length = 1
+        if channel_id & 0xe0 == 0xe0:
+            if remaining_length < 4:
+                raise ValueError('Invalid channel id format')
+            channel_id = struct.unpack('!L',
+                                       self._data[pos:pos+4])[0] & 0x1fffffff
+            channel_id_length = 4
+        elif channel_id & 0xc0 == 0xc0:
+            if remaining_length < 3:
+                raise ValueError('Invalid channel id format')
+            channel_id = (((channel_id & 0x1f) << 16) +
+                          struct.unpack('!H', self._data[pos+1:pos+3])[0])
+            channel_id_length = 3
+        elif channel_id & 0x80 == 0x80:
+            if remaining_length < 2:
+                raise ValueError('Invalid channel id format')
+            channel_id = struct.unpack('!H',
+                                       self._data[pos:pos+2])[0] & 0x3fff
+            channel_id_length = 2
+        self._read_position += channel_id_length
+
+        return channel_id
+
+    def read_inner_frame(self):
+        """Read an inner frame.
+
+        Raises:
+            PhysicalConnectionError: when the inner frame is invalid.
+        """
+        if len(self._data) == self._read_position:
+            raise PhysicalConnectionError(
+                _DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
+
+        bits = ord(self._data[self._read_position])
+        self._read_position += 1
+        fin = (bits & 0x80) == 0x80
+        rsv1 = (bits & 0x40) == 0x40
+        rsv2 = (bits & 0x20) == 0x20
+        rsv3 = (bits & 0x10) == 0x10
+        opcode = bits & 0xf
+        payload = self.remaining_data()
+        # Consume rest of the message which is payload data of the original
+        # frame.
+        self._read_position = len(self._data)
+        return fin, rsv1, rsv2, rsv3, opcode, payload
+
+    def _read_number(self):
+        if self._read_position + 1 > len(self._data):
+            raise ValueError(
+                'Cannot read the first byte of number field')
+
+        number = ord(self._data[self._read_position])
+        if number & 0x80 == 0x80:
+            raise ValueError(
+                'The most significant bit of the first byte of number should '
+                'be unset')
+        self._read_position += 1
+        pos = self._read_position
+        if number == 127:
+            if pos + 8 > len(self._data):
+                raise ValueError('Invalid number field')
+            self._read_position += 8
+            number = struct.unpack('!Q', self._data[pos:pos+8])[0]
+            if number > 0x7FFFFFFFFFFFFFFF:
+                raise ValueError('Encoded number(%d) >= 2^63' % number)
+            if number <= 0xFFFF:
+                raise ValueError(
+                    '%d should not be encoded by 9 bytes encoding' % number)
+            return number
+        if number == 126:
+            if pos + 2 > len(self._data):
+                raise ValueError('Invalid number field')
+            self._read_position += 2
+            number = struct.unpack('!H', self._data[pos:pos+2])[0]
+            if number <= 125:
+                raise ValueError(
+                    '%d should not be encoded by 3 bytes encoding' % number)
+        return number
+
+    def _read_size_and_contents(self):
+        """Read data that consists of the following:
+            - the size of the contents encoded the same way as payload length
+              of the WebSocket Protocol with 1 bit padding at the head.
+            - the contents.
+        """
+        try:
+            size = self._read_number()
+        except ValueError, e:
+            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                                          str(e))
+        pos = self._read_position
+        if pos + size > len(self._data):
+            raise PhysicalConnectionError(
+                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                'Cannot read %d bytes data' % size)
+
+        self._read_position += size
+        return self._data[pos:pos+size]
+
+    def _read_add_channel_request(self, first_byte, control_block):
+        reserved = (first_byte >> 2) & 0x7
+        if reserved != 0:
+            raise PhysicalConnectionError(
+                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                'Reserved bits must be unset')
+
+        # Invalid encoding will be handled by MuxHandler.
+        encoding = first_byte & 0x3
+        try:
+            control_block.channel_id = self.read_channel_id()
+        except ValueError, e:
+            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
+        control_block.encoding = encoding
+        encoded_handshake = self._read_size_and_contents()
+        control_block.encoded_handshake = encoded_handshake
+        return control_block
+
+    def _read_add_channel_response(self, first_byte, control_block):
+        reserved = (first_byte >> 2) & 0x3
+        if reserved != 0:
+            raise PhysicalConnectionError(
+                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                'Reserved bits must be unset')
+
+        control_block.accepted = (first_byte >> 4) & 1
+        control_block.encoding = first_byte & 0x3
+        try:
+            control_block.channel_id = self.read_channel_id()
+        except ValueError, e:
+            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
+        control_block.encoded_handshake = self._read_size_and_contents()
+        return control_block
+
+    def _read_flow_control(self, first_byte, control_block):
+        reserved = first_byte & 0x1f
+        if reserved != 0:
+            raise PhysicalConnectionError(
+                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                'Reserved bits must be unset')
+
+        try:
+            control_block.channel_id = self.read_channel_id()
+            control_block.send_quota = self._read_number()
+        except ValueError, e:
+            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                                          str(e))
+
+        return control_block
+
+    def _read_drop_channel(self, first_byte, control_block):
+        reserved = first_byte & 0x1f
+        if reserved != 0:
+            raise PhysicalConnectionError(
+                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                'Reserved bits must be unset')
+
+        try:
+            control_block.channel_id = self.read_channel_id()
+        except ValueError, e:
+            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
+        reason = self._read_size_and_contents()
+        if len(reason) == 0:
+            control_block.drop_code = None
+            control_block.drop_message = ''
+        elif len(reason) >= 2:
+            control_block.drop_code = struct.unpack('!H', reason[:2])[0]
+            control_block.drop_message = reason[2:]
+        else:
+            raise PhysicalConnectionError(
+                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                'Received DropChannel that conains only 1-byte reason')
+        return control_block
+
+    def _read_new_channel_slot(self, first_byte, control_block):
+        reserved = first_byte & 0x1e
+        if reserved != 0:
+            raise PhysicalConnectionError(
+                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                'Reserved bits must be unset')
+        control_block.fallback = first_byte & 1
+        try:
+            control_block.slots = self._read_number()
+            control_block.send_quota = self._read_number()
+        except ValueError, e:
+            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                                          str(e))
+        return control_block
+
+    def read_control_blocks(self):
+        """Read control block(s).
+
+        Raises:
+           PhysicalConnectionError: when the payload contains invalid control
+               block(s).
+           StopIteration: when no control blocks left.
+        """
+        while self._read_position < len(self._data):
+            first_byte = ord(self._data[self._read_position])
+            self._read_position += 1
+            opcode = (first_byte >> 5) & 0x7
+            control_block = _ControlBlock(opcode=opcode)
+            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
+                yield self._read_add_channel_request(first_byte, control_block)
+            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
+                yield self._read_add_channel_response(
+                    first_byte, control_block)
+            elif opcode == _MUX_OPCODE_FLOW_CONTROL:
+                yield self._read_flow_control(first_byte, control_block)
+            elif opcode == _MUX_OPCODE_DROP_CHANNEL:
+                yield self._read_drop_channel(first_byte, control_block)
+            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
+                yield self._read_new_channel_slot(first_byte, control_block)
+            else:
+                raise PhysicalConnectionError(
+                    _DROP_CODE_UNKNOWN_MUX_OPCODE,
+                    'Invalid opcode %d' % opcode)
+
+        assert self._read_position == len(self._data)
+        raise StopIteration
+
+    def remaining_data(self):
+        """Return remaining data."""
+        return self._data[self._read_position:]
+
+
+class _LogicalRequest(object):
+
+    """Mimics mod_python request."""
+
+    def __init__(self, channel_id, command, path, protocol, headers,
+                 connection):
+        """Construct an instance.
+
+        Args:
+            channel_id: the channel id of the logical channel.
+            command: HTTP request command.
+            path: HTTP request path.
+            headers: HTTP headers.
+            connection: _LogicalConnection instance.
+        """
+        self.channel_id = channel_id
+        self.method = command
+        self.uri = path
+        self.protocol = protocol
+        self.headers_in = headers
+        self.connection = connection
+        self.server_terminated = False
+        self.client_terminated = False
+
+    def is_https(self):
+        """Mimic request.is_https().
+
+        Returns False because this method is used only by old protocols
+        (hixie and hybi00).
+        """
+        return False
+
+
+class _LogicalConnection(object):
+
+    """Mimics mod_python mp_conn."""
+
+    # For details, see the comment of set_read_state().
+    STATE_ACTIVE = 1
+    STATE_GRACEFULLY_CLOSED = 2
+    STATE_TERMINATED = 3
+
+    def __init__(self, mux_handler, channel_id):
+        """Construct an instance.
+
+        Args:
+            mux_handler: _MuxHandler instance.
+            channel_id: channel id of this connection.
+        """
+        self._mux_handler = mux_handler
+        self._channel_id = channel_id
+        self._incoming_data = ''
+
+        # - Protects _waiting_write_completion
+        # - Signals the thread waiting for completion of write by mux handler
+        self._write_condition = threading.Condition()
+        self._waiting_write_completion = False
+
+        self._read_condition = threading.Condition()
+        self._read_state = self.STATE_ACTIVE
+
+    def get_local_addr(self):
+        """Getter to mimic mp_conn.local_addr."""
+        return self._mux_handler.physical_connection.get_local_addr()
+    local_addr = property(get_local_addr)
+
+    def get_remote_addr(self):
+        """Getter to mimic mp_conn.remote_addr."""
+        return self._mux_handler.physical_connection.get_remote_addr()
+    remote_addr = property(get_remote_addr)
+
+    def get_memorized_lines(self):
+        """Get memorized lines. Not supported."""
+        raise MuxUnexpectedException('_LogicalConnection does not support '
+                                     'get_memorized_lines')
+
+    def write(self, data):
+        """Write data. mux_handler sends data asynchronously.
+
+        The caller will be suspended until write done.
+
+        Args:
+            data: data to be written.
+
+        Raises:
+            MuxUnexpectedException: when called before finishing the previous
+                write.
+        """
+        try:
+            self._write_condition.acquire()
+            if self._waiting_write_completion:
+                raise MuxUnexpectedException(
+                    'Logical connection %d is already waiting the completion '
+                    'of write' % self._channel_id)
+
+            self._waiting_write_completion = True
+            self._mux_handler.send_data(self._channel_id, data)
+            self._write_condition.wait()
+            # TODO(tyoshino): Raise an exception if woke up by on_writer_done.
+        finally:
+            self._write_condition.release()
+
+    def write_control_data(self, data):
+        """Write data via the control channel.
+
+        Don't wait finishing write because this method can be called by
+        mux dispatcher.
+
+        Args:
+            data: data to be written.
+        """
+        self._mux_handler.send_control_data(data)
+
+    def on_write_data_done(self):
+        """Called when sending data is completed."""
+        try:
+            self._write_condition.acquire()
+            if not self._waiting_write_completion:
+                raise MuxUnexpectedException(
+                    'Invalid call of on_write_data_done for logical '
+                    'connection %d' % self._channel_id)
+            self._waiting_write_completion = False
+            self._write_condition.notify()
+        finally:
+            self._write_condition.release()
+
+    def on_writer_done(self):
+        """Called by the mux handler when the writer thread has finished."""
+        try:
+            self._write_condition.acquire()
+            self._waiting_write_completion = False
+            self._write_condition.notify()
+        finally:
+            self._write_condition.release()
+
+    def append_frame_data(self, frame_data):
+        """Append incoming frame data.
+
+        Called when mux_handler dispatches frame data to the corresponding
+        application.
+
+        Args:
+            frame_data: incoming frame data.
+        """
+        self._read_condition.acquire()
+        self._incoming_data += frame_data
+        self._read_condition.notify()
+        self._read_condition.release()
+
+    def read(self, length):
+        """Read data.
+
+        Blocks until enough data has arrived via physical connection.
+
+        Args:
+            length: length of data to be read.
+        Raises:
+            LogicalConnectionClosedException: when closing handshake for this
+                logical channel has been received.
+            ConnectionTerminatedException: when the physical connection has
+                closed, or an error is caused on the reader thread.
+        """
+        self._read_condition.acquire()
+        while (self._read_state == self.STATE_ACTIVE and
+               len(self._incoming_data) < length):
+            self._read_condition.wait()
+
+        try:
+            if self._read_state == self.STATE_GRACEFULLY_CLOSED:
+                raise LogicalConnectionClosedException(
+                    'Logical channel %d has closed.' % self._channel_id)
+            elif self._read_state == self.STATE_TERMINATED:
+                raise ConnectionTerminatedException(
+                    'Receiving %d byte failed. Logical channel (%d) closed' %
+                    (length, self._channel_id))
+
+            value = self._incoming_data[:length]
+            self._incoming_data = self._incoming_data[length:]
+        finally:
+            self._read_condition.release()
+
+        return value
+
+    def set_read_state(self, new_state):
+        """Set the state of this connection.
+
+        Called when an event for this connection has occurred.
+        Args:
+            new_state: state to be set. new_state must be one of followings:
+            - STATE_GRACEFULLY_CLOSED: when closing handshake for this
+                connection has been received.
+            - STATE_TERMINATED: when the physical connection has closed or
+                DropChannel of this connection has received.
+        """
+        self._read_condition.acquire()
+        self._read_state = new_state
+        self._read_condition.notify()
+        self._read_condition.release()
+
+
+class _InnerMessage(object):
+
+    """Hold the result of _InnerMessageBuilder.build()."""
+
+    def __init__(self, opcode, payload):
+        self.opcode = opcode
+        self.payload = payload
+
+
+class _InnerMessageBuilder(object):
+
+    """Class to build an _InnerMessage.
+
+    A class that holds the context of inner message fragmentation and
+    builds a message from fragmented inner frame(s).
+    """
+
+    def __init__(self):
+        self._control_opcode = None
+        self._pending_control_fragments = []
+        self._message_opcode = None
+        self._pending_message_fragments = []
+        self._frame_handler = self._handle_first
+
+    def _handle_first(self, frame):
+        if frame.opcode == common.OPCODE_CONTINUATION:
+            raise InvalidFrameException('Sending invalid continuation opcode')
+
+        if common.is_control_opcode(frame.opcode):
+            return self._process_first_fragmented_control(frame)
+        else:
+            return self._process_first_fragmented_message(frame)
+
+    def _process_first_fragmented_control(self, frame):
+        self._control_opcode = frame.opcode
+        self._pending_control_fragments.append(frame.payload)
+        if not frame.fin:
+            self._frame_handler = self._handle_fragmented_control
+            return None
+        return self._reassemble_fragmented_control()
+
+    def _process_first_fragmented_message(self, frame):
+        self._message_opcode = frame.opcode
+        self._pending_message_fragments.append(frame.payload)
+        if not frame.fin:
+            self._frame_handler = self._handle_fragmented_message
+            return None
+        return self._reassemble_fragmented_message()
+
+    def _handle_fragmented_control(self, frame):
+        if frame.opcode != common.OPCODE_CONTINUATION:
+            raise InvalidFrameException(
+                'Sending invalid opcode %d while sending fragmented control '
+                'message' % frame.opcode)
+        self._pending_control_fragments.append(frame.payload)
+        if not frame.fin:
+            return None
+        return self._reassemble_fragmented_control()
+
+    def _reassemble_fragmented_control(self):
+        opcode = self._control_opcode
+        payload = ''.join(self._pending_control_fragments)
+        self._control_opcode = None
+        self._pending_control_fragments = []
+        if self._message_opcode is not None:
+            self._frame_handler = self._handle_fragmented_message
+        else:
+            self._frame_handler = self._handle_first
+        return _InnerMessage(opcode, payload)
+
+    def _handle_fragmented_message(self, frame):
+        # Sender can interleave a control message while sending fragmented
+        # messages.
+        if common.is_control_opcode(frame.opcode):
+            if self._control_opcode is not None:
+                raise MuxUnexpectedException(
+                    'Should not reach here(Bug in builder)')
+            return self._process_first_fragmented_control(frame)
+
+        if frame.opcode != common.OPCODE_CONTINUATION:
+            raise InvalidFrameException(
+                'Sending invalid opcode %d while sending fragmented message' %
+                frame.opcode)
+        self._pending_message_fragments.append(frame.payload)
+        if not frame.fin:
+            return None
+        return self._reassemble_fragmented_message()
+
+    def _reassemble_fragmented_message(self):
+        opcode = self._message_opcode
+        payload = ''.join(self._pending_message_fragments)
+        self._message_opcode = None
+        self._pending_message_fragments = []
+        self._frame_handler = self._handle_first
+        return _InnerMessage(opcode, payload)
+
+    def build(self, frame):
+        """Build an inner message.
+
+        Returns an _InnerMessage instance when the given frame is the last
+        fragmented frame. Returns None otherwise.
+
+        Args:
+            frame: an inner frame.
+        Raises:
+            InvalidFrameException: when received invalid opcode. (e.g.
+                receiving non continuation data opcode but the fin flag of
+                the previous inner frame was not set.)
+        """
+        return self._frame_handler(frame)
+
+
+class _LogicalStream(Stream):
+
+    """Mimics the Stream class.
+
+    This class interprets multiplexed WebSocket frames.
+    """
+
+    def __init__(self, request, stream_options, send_quota, receive_quota):
+        """Construct an instance.
+
+        Args:
+            request: _LogicalRequest instance.
+            stream_options: StreamOptions instance.
+            send_quota: Initial send quota.
+            receive_quota: Initial receive quota.
+        """
+        # Physical stream is responsible for masking.
+        stream_options.unmask_receive = False
+        Stream.__init__(self, request, stream_options)
+
+        self._send_closed = False
+        self._send_quota = send_quota
+        # - Protects _send_closed and _send_quota
+        # - Signals the thread waiting for send quota replenished
+        self._send_condition = threading.Condition()
+
+        # The opcode of the first frame in messages.
+        self._message_opcode = common.OPCODE_TEXT
+        # True when the last message was fragmented.
+        self._last_message_was_fragmented = False
+
+        self._receive_quota = receive_quota
+        self._write_inner_frame_semaphore = threading.Semaphore()
+
+        self._inner_message_builder = _InnerMessageBuilder()
+
+    def _create_inner_frame(self, opcode, payload, end=True):
+        frame = Frame(fin=end, opcode=opcode, payload=payload)
+        for frame_filter in self._options.outgoing_frame_filters:
+            frame_filter.filter(frame)
+
+        if len(payload) != len(frame.payload):
+            raise MuxUnexpectedException(
+                'Mux extension must not be used after extensions which change '
+                ' frame boundary')
+
+        first_byte = ((frame.fin << 7) | (frame.rsv1 << 6) |
+                      (frame.rsv2 << 5) | (frame.rsv3 << 4) | frame.opcode)
+        return chr(first_byte) + frame.payload
+
+    def _write_inner_frame(self, opcode, payload, end=True):
+        payload_length = len(payload)
+        write_position = 0
+
+        try:
+            # An inner frame will be fragmented if there is no enough send
+            # quota. This semaphore ensures that fragmented inner frames are
+            # sent in order on the logical channel.
+            # Note that frames that come from other logical channels or
+            # multiplexing control blocks can be inserted between fragmented
+            # inner frames on the physical channel.
+            self._write_inner_frame_semaphore.acquire()
+
+            # Consume an octet quota when this is the first fragmented frame.
+            if opcode != common.OPCODE_CONTINUATION:
+                try:
+                    self._send_condition.acquire()
+                    while (not self._send_closed) and self._send_quota == 0:
+                        self._send_condition.wait()
+
+                    if self._send_closed:
+                        raise BadOperationException(
+                            'Logical connection %d is closed' %
+                            self._request.channel_id)
+
+                    self._send_quota -= 1
+                finally:
+                    self._send_condition.release()
+
+            while write_position < payload_length:
+                try:
+                    self._send_condition.acquire()
+                    while (not self._send_closed) and self._send_quota == 0:
+                        self._logger.debug(
+                            'No quota. Waiting FlowControl message for %d.' %
+                            self._request.channel_id)
+                        self._send_condition.wait()
+
+                    if self._send_closed:
+                        raise BadOperationException(
+                            'Logical connection %d is closed' %
+                            self.request._channel_id)
+
+                    remaining = payload_length - write_position
+                    write_length = min(self._send_quota, remaining)
+                    inner_frame_end = (
+                        end and
+                        (write_position + write_length == payload_length))
+
+                    inner_frame = self._create_inner_frame(
+                        opcode,
+                        payload[write_position:write_position+write_length],
+                        inner_frame_end)
+                    self._send_quota -= write_length
+                    self._logger.debug('Consumed quota=%d, remaining=%d' %
+                                       (write_length, self._send_quota))
+                finally:
+                    self._send_condition.release()
+
+                # Writing data will block the worker so we need to release
+                # _send_condition before writing.
+                self._logger.debug('Sending inner frame: %r' % inner_frame)
+                self._request.connection.write(inner_frame)
+                write_position += write_length
+
+                opcode = common.OPCODE_CONTINUATION
+
+        except ValueError, e:
+            raise BadOperationException(e)
+        finally:
+            self._write_inner_frame_semaphore.release()
+
+    def replenish_send_quota(self, send_quota):
+        """Replenish send quota."""
+        try:
+            self._send_condition.acquire()
+            if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF:
+                self._send_quota = 0
+                raise LogicalChannelError(
+                    self._request.channel_id, _DROP_CODE_SEND_QUOTA_OVERFLOW)
+            self._send_quota += send_quota
+            self._logger.debug('Replenished send quota for channel id %d: %d' %
+                               (self._request.channel_id, self._send_quota))
+        finally:
+            self._send_condition.notify()
+            self._send_condition.release()
+
+    def consume_receive_quota(self, amount):
+        """Consume receive quota. Returns False on failure."""
+        if self._receive_quota < amount:
+            self._logger.debug('Violate quota on channel id %d: %d < %d' %
+                               (self._request.channel_id,
+                                self._receive_quota, amount))
+            return False
+        self._receive_quota -= amount
+        return True
+
+    def send_message(self, message, end=True, binary=False):
+        """Override Stream.send_message."""
+        if self._request.server_terminated:
+            raise BadOperationException(
+                'Requested send_message after sending out a closing handshake')
+
+        if binary and isinstance(message, unicode):
+            raise BadOperationException(
+                'Message for binary frame must be instance of str')
+
+        if binary:
+            opcode = common.OPCODE_BINARY
+        else:
+            opcode = common.OPCODE_TEXT
+            message = message.encode('utf-8')
+
+        for message_filter in self._options.outgoing_message_filters:
+            message = message_filter.filter(message, end, binary)
+
+        if self._last_message_was_fragmented:
+            if opcode != self._message_opcode:
+                raise BadOperationException('Message types are different in '
+                                            'frames for the same message')
+            opcode = common.OPCODE_CONTINUATION
+        else:
+            self._message_opcode = opcode
+
+        self._write_inner_frame(opcode, message, end)
+        self._last_message_was_fragmented = not end
+
+    def _receive_frame(self):
+        """Override Stream._receive_frame.
+
+        In addition to call Stream._receive_frame, this method adds the amount
+        of payload to receiving quota and sends FlowControl to the client.
+        We need to do it here because Stream.receive_message() handles
+        control frames internally.
+        """
+        opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
+        amount = len(payload)
+        # Replenish extra one octet when receiving the first fragmented frame.
+        if opcode != common.OPCODE_CONTINUATION:
+            amount += 1
+        self._receive_quota += amount
+        frame_data = _create_flow_control(self._request.channel_id,
+                                          amount)
+        self._logger.debug('Sending flow control for %d, replenished=%d' %
+                           (self._request.channel_id, amount))
+        self._request.connection.write_control_data(frame_data)
+        return opcode, payload, fin, rsv1, rsv2, rsv3
+
+    def _get_message_from_frame(self, frame):
+        """Override Stream._get_message_from_frame."""
+        try:
+            inner_message = self._inner_message_builder.build(frame)
+        except InvalidFrameException:
+            raise LogicalChannelError(
+                self._request.channel_id, _DROP_CODE_BAD_FRAGMENTATION)
+
+        if inner_message is None:
+            return None
+        self._original_opcode = inner_message.opcode
+        return inner_message.payload
+
+    def receive_message(self):
+        """Override Stream.receive_message."""
+        # Just call Stream.receive_message(), but catch
+        # LogicalConnectionClosedException, which is raised when the logical
+        # connection has closed gracefully.
+        try:
+            return Stream.receive_message(self)
+        except LogicalConnectionClosedException, e:
+            self._logger.debug('%s', e)
+            return None
+
+    def _send_closing_handshake(self, code, reason):
+        """Override Stream._send_closing_handshake."""
+        body = create_closing_handshake_body(code, reason)
+        self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
+                           (self._request.channel_id, code, reason))
+        self._write_inner_frame(common.OPCODE_CLOSE, body, end=True)
+
+        self._request.server_terminated = True
+
+    def send_ping(self, body=''):
+        """Override Stream.send_ping."""
+        self._logger.debug('Sending ping on logical channel %d: %r' %
+                           (self._request.channel_id, body))
+        self._write_inner_frame(common.OPCODE_PING, body, end=True)
+
+        self._ping_queue.append(body)
+
+    def _send_pong(self, body):
+        """Override Stream._send_pong."""
+        self._logger.debug('Sending pong on logical channel %d: %r' %
+                           (self._request.channel_id, body))
+        self._write_inner_frame(common.OPCODE_PONG, body, end=True)
+
+    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
+        """Override Stream.close_connection."""
+        # TODO(bashi): Implement
+        self._logger.debug('Closing logical connection %d' %
+                           self._request.channel_id)
+        self._request.server_terminated = True
+
+    def stop_sending(self):
+        """Stop accepting new send operation (_write_inner_frame)."""
+        self._send_condition.acquire()
+        self._send_closed = True
+        self._send_condition.notify()
+        self._send_condition.release()
+
+
+class _OutgoingData(object):
+
+    """Simple data/channel container.
+
+    A structure that holds data to be sent via physical connection and
+    origin of the data.
+    """
+
+    def __init__(self, channel_id, data):
+        self.channel_id = channel_id
+        self.data = data
+
+
+class _PhysicalConnectionWriter(threading.Thread):
+
+    """A thread that is responsible for writing data to physical connection.
+
+    TODO(bashi): Make sure there is no thread-safety problem when the reader
+    thread reads data from the same socket at a time.
+    """
+
+    def __init__(self, mux_handler):
+        """Construct an instance.
+
+        Args:
+            mux_handler: _MuxHandler instance.
+        """
+        threading.Thread.__init__(self)
+        self._logger = util.get_class_logger(self)
+        self._mux_handler = mux_handler
+        self.setDaemon(True)
+
+        # When set, make this thread stop accepting new data, flush pending
+        # data and exit.
+        self._stop_requested = False
+        # The close code of the physical connection.
+        self._close_code = common.STATUS_NORMAL_CLOSURE
+        # Deque for passing write data. It's protected by _deque_condition
+        # until _stop_requested is set.
+        self._deque = collections.deque()
+        # - Protects _deque, _stop_requested and _close_code
+        # - Signals threads waiting for them to be available
+        self._deque_condition = threading.Condition()
+
+    def put_outgoing_data(self, data):
+        """Put outgoing data.
+
+        Args:
+            data: _OutgoingData instance.
+        Raises:
+            BadOperationException: when the thread has been requested to
+                terminate.
+        """
+        try:
+            self._deque_condition.acquire()
+            if self._stop_requested:
+                raise BadOperationException('Cannot write data anymore')
+
+            self._deque.append(data)
+            self._deque_condition.notify()
+        finally:
+            self._deque_condition.release()
+
+    def _write_data(self, outgoing_data):
+        message = (_encode_channel_id(outgoing_data.channel_id) +
+                   outgoing_data.data)
+        try:
+            self._mux_handler.physical_stream.send_message(
+                message=message, end=True, binary=True)
+        except Exception, e:
+            util.prepend_message_to_exception(
+                'Failed to send message to %r: ' %
+                (self._mux_handler.physical_connection.remote_addr,), e)
+            raise
+
+        # TODO(bashi): It would be better to block the thread that sends
+        # control data as well.
+        if outgoing_data.channel_id != _CONTROL_CHANNEL_ID:
+            self._mux_handler.notify_write_data_done(outgoing_data.channel_id)
+
+    def run(self):
+        try:
+            self._deque_condition.acquire()
+            while not self._stop_requested:
+                if len(self._deque) == 0:
+                    self._deque_condition.wait()
+                    continue
+
+                outgoing_data = self._deque.popleft()
+
+                self._deque_condition.release()
+                self._write_data(outgoing_data)
+                self._deque_condition.acquire()
+
+            # Flush deque.
+            #
+            # At this point, self._deque_condition is always acquired.
+            try:
+                while len(self._deque) > 0:
+                    outgoing_data = self._deque.popleft()
+                    self._write_data(outgoing_data)
+            finally:
+                self._deque_condition.release()
+
+            # Close physical connection.
+            try:
+                # Don't wait the response here. The response will be read
+                # by the reader thread.
+                self._mux_handler.physical_stream.close_connection(
+                    self._close_code, wait_response=False)
+            except Exception, e:
+                util.prepend_message_to_exception(
+                    'Failed to close the physical connection: %r' % e)
+                raise
+        finally:
+            self._mux_handler.notify_writer_done()
+
+    def stop(self, close_code=common.STATUS_NORMAL_CLOSURE):
+        """Stop the writer thread."""
+        self._deque_condition.acquire()
+        self._stop_requested = True
+        self._close_code = close_code
+        self._deque_condition.notify()
+        self._deque_condition.release()
+
+
+class _PhysicalConnectionReader(threading.Thread):
+
+    """A thread that is responsible for reading data from physical connection.
+    """
+
+    def __init__(self, mux_handler):
+        """Construct an instance.
+
+        Args:
+            mux_handler: _MuxHandler instance.
+        """
+        threading.Thread.__init__(self)
+        self._logger = util.get_class_logger(self)
+        self._mux_handler = mux_handler
+        self.setDaemon(True)
+
+    def run(self):
+        while True:
+            try:
+                physical_stream = self._mux_handler.physical_stream
+                message = physical_stream.receive_message()
+                if message is None:
+                    break
+                # Below happens only when a data message is received.
+                opcode = physical_stream.get_last_received_opcode()
+                if opcode != common.OPCODE_BINARY:
+                    self._mux_handler.fail_physical_connection(
+                        _DROP_CODE_INVALID_ENCAPSULATING_MESSAGE,
+                        'Received a text message on physical connection')
+                    break
+
+            except ConnectionTerminatedException, e:
+                self._logger.debug('%s', e)
+                break
+
+            try:
+                self._mux_handler.dispatch_message(message)
+            except PhysicalConnectionError, e:
+                self._mux_handler.fail_physical_connection(
+                    e.drop_code, e.message)
+                break
+            except LogicalChannelError, e:
+                self._mux_handler.fail_logical_channel(
+                    e.channel_id, e.drop_code, e.message)
+            except Exception, e:
+                self._logger.debug(traceback.format_exc())
+                break
+
+        self._mux_handler.notify_reader_done()
+
+
+class _Worker(threading.Thread):
+
+    """A thread that is responsible for running the corresponding application
+    handler.
+    """
+
+    def __init__(self, mux_handler, request):
+        """Construct an instance.
+
+        Args:
+            mux_handler: _MuxHandler instance.
+            request: _LogicalRequest instance.
+        """
+        threading.Thread.__init__(self)
+        self._logger = util.get_class_logger(self)
+        self._mux_handler = mux_handler
+        self._request = request
+        self.setDaemon(True)
+
+    def run(self):
+        self._logger.debug('Logical channel worker started. (id=%d)' %
+                           self._request.channel_id)
+        try:
+            # Non-critical exceptions will be handled by dispatcher.
+            self._mux_handler.dispatcher.transfer_data(self._request)
+        except LogicalChannelError, e:
+            self._mux_handler.fail_logical_channel(
+                e.channel_id, e.drop_code, e.message)
+        finally:
+            self._mux_handler.notify_worker_done(self._request.channel_id)
+
+
+class _MuxHandshaker(hybi.Handshaker):
+
+    """Opening handshake processor for multiplexing."""
+
+    _DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
+
+    def __init__(self, request, dispatcher, send_quota, receive_quota):
+        """Construct an instance.
+
+        Args:
+            request: _LogicalRequest instance.
+            dispatcher: Dispatcher instance (dispatch.Dispatcher).
+            send_quota: Initial send quota.
+            receive_quota: Initial receive quota.
+        """
+        hybi.Handshaker.__init__(self, request, dispatcher)
+        self._send_quota = send_quota
+        self._receive_quota = receive_quota
+
+        # Append headers which should not be included in handshake field of
+        # AddChannelRequest.
+        # TODO(bashi): Make sure whether we should raise exception when
+        #     these headers are included already.
+        request.headers_in[common.UPGRADE_HEADER] = (
+            common.WEBSOCKET_UPGRADE_TYPE)
+        request.headers_in[common.SEC_WEBSOCKET_VERSION_HEADER] = (
+            str(common.VERSION_HYBI_LATEST))
+        request.headers_in[common.SEC_WEBSOCKET_KEY_HEADER] = (
+            self._DUMMY_WEBSOCKET_KEY)
+
+    def _create_stream(self, stream_options):
+        """Override hybi.Handshaker._create_stream."""
+        self._logger.debug('Creating logical stream for %d' %
+                           self._request.channel_id)
+        return _LogicalStream(
+            self._request, stream_options, self._send_quota,
+            self._receive_quota)
+
+    def _create_handshake_response(self, accept):
+        """Override hybi._create_handshake_response."""
+        response = []
+
+        response.append('HTTP/1.1 101 Switching Protocols\r\n')
+
+        # Upgrade and Sec-WebSocket-Accept should be excluded.
+        response.append('%s: %s\r\n' % (
+            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
+        if self._request.ws_protocol is not None:
+            response.append('%s: %s\r\n' % (
+                common.SEC_WEBSOCKET_PROTOCOL_HEADER,
+                self._request.ws_protocol))
+        if (self._request.ws_extensions is not None and
+            len(self._request.ws_extensions) != 0):
+            response.append('%s: %s\r\n' % (
+                common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
+                common.format_extensions(self._request.ws_extensions)))
+        response.append('\r\n')
+
+        return ''.join(response)
+
+    def _send_handshake(self, accept):
+        """Override hybi.Handshaker._send_handshake."""
+        # Don't send handshake response for the default channel
+        if self._request.channel_id == _DEFAULT_CHANNEL_ID:
+            return
+
+        handshake_response = self._create_handshake_response(accept)
+        frame_data = _create_add_channel_response(
+                         self._request.channel_id,
+                         handshake_response)
+        self._logger.debug('Sending handshake response for %d: %r' %
+                           (self._request.channel_id, frame_data))
+        self._request.connection.write_control_data(frame_data)
+
+
+class _LogicalChannelData(object):
+
+    """A structure that holds information about logical channel."""
+
+    def __init__(self, request, worker):
+        self.request = request
+        self.worker = worker
+        self.drop_code = _DROP_CODE_NORMAL_CLOSURE
+        self.drop_message = ''
+
+
+class _HandshakeDeltaBase(object):
+    """A class that holds information for delta-encoded handshake."""
+
+    def __init__(self, headers):
+        self._headers = headers
+
+    def create_headers(self, delta=None):
+        """Creates request headers for an AddChannelRequest that has
+        delta-encoded handshake.
+
+        Args:
+            delta: headers should be overridden.
+        """
+
+        headers = copy.copy(self._headers)
+        if delta:
+            for key, value in delta.items():
+                # The spec requires that a header with an empty value is
+                # removed from the delta base.
+                if len(value) == 0 and headers.has_key(key):
+                    del headers[key]
+                else:
+                    headers[key] = value
+        return headers
+
+
+class _MuxHandler(object):
+    """Multiplexing handler. When a handler starts, it launches three
+    threads; the reader thread, the writer thread, and a worker thread.
+
+    The reader thread reads data from the physical stream, i.e., the
+    ws_stream object of the underlying websocket connection. The reader
+    thread interprets multiplexed frames and dispatches them to logical
+    channels. Methods of this class are mostly called by the reader thread.
+
+    The writer thread sends multiplexed frames which are created by
+    logical channels via the physical connection.
+
+    The worker thread launched at the starting point handles the
+    "Implicitly Opened Connection". If multiplexing handler receives
+    an AddChannelRequest and accepts it, the handler will launch a new worker
+    thread and dispatch the request to it.
+    """
+
+    def __init__(self, request, dispatcher):
+        """Constructs an instance.
+
+        Args:
+            request: mod_python request of the physical connection.
+            dispatcher: Dispatcher instance (dispatch.Dispatcher).
+        """
+
+        self.original_request = request
+        self.dispatcher = dispatcher
+        self.physical_connection = request.connection
+        self.physical_stream = request.ws_stream
+        self._logger = util.get_class_logger(self)
+        self._logical_channels = {}
+        self._logical_channels_condition = threading.Condition()
+        # Holds client's initial quota
+        self._channel_slots = collections.deque()
+        self._handshake_base = None
+        self._worker_done_notify_received = False
+        self._reader = None
+        self._writer = None
+
+    def start(self):
+        """Starts the handler.
+
+        Raises:
+            MuxUnexpectedException: when the handler already started, or when
+                opening handshake of the default channel fails.
+        """
+
+        if self._reader or self._writer:
+            raise MuxUnexpectedException('MuxHandler already started')
+
+        self._reader = _PhysicalConnectionReader(self)
+        self._writer = _PhysicalConnectionWriter(self)
+        self._reader.start()
+        self._writer.start()
+
+        # Create "Implicitly Opened Connection".
+        logical_connection = _LogicalConnection(self, _DEFAULT_CHANNEL_ID)
+        headers = copy.copy(self.original_request.headers_in)
+        # Add extensions for logical channel.
+        headers[common.SEC_WEBSOCKET_EXTENSIONS_HEADER] = (
+            common.format_extensions(
+                self.original_request.mux_processor.extensions()))
+        self._handshake_base = _HandshakeDeltaBase(headers)
+        logical_request = _LogicalRequest(
+            _DEFAULT_CHANNEL_ID,
+            self.original_request.method,
+            self.original_request.uri,
+            self.original_request.protocol,
+            self._handshake_base.create_headers(),
+            logical_connection)
+        # Client's send quota for the implicitly opened connection is zero,
+        # but we will send FlowControl later so set the initial quota to
+        # _INITIAL_QUOTA_FOR_CLIENT.
+        self._channel_slots.append(_INITIAL_QUOTA_FOR_CLIENT)
+        send_quota = self.original_request.mux_processor.quota()
+        if not self._do_handshake_for_logical_request(
+            logical_request, send_quota=send_quota):
+            raise MuxUnexpectedException(
+                'Failed handshake on the default channel id')
+        self._add_logical_channel(logical_request)
+
+        # Send FlowControl for the implicitly opened connection.
+        frame_data = _create_flow_control(_DEFAULT_CHANNEL_ID,
+                                          _INITIAL_QUOTA_FOR_CLIENT)
+        logical_request.connection.write_control_data(frame_data)
+
+    def add_channel_slots(self, slots, send_quota):
+        """Adds channel slots.
+
+        Args:
+            slots: number of slots to be added.
+            send_quota: initial send quota for slots.
+        """
+
+        self._channel_slots.extend([send_quota] * slots)
+        # Send NewChannelSlot to client.
+        frame_data = _create_new_channel_slot(slots, send_quota)
+        self.send_control_data(frame_data)
+
+    def wait_until_done(self, timeout=None):
+        """Waits until all workers are done. Returns False when timeout has
+        occurred. Returns True on success.
+
+        Args:
+            timeout: timeout in sec.
+        """
+
+        self._logical_channels_condition.acquire()
+        try:
+            while len(self._logical_channels) > 0:
+                self._logger.debug('Waiting workers(%d)...' %
+                                   len(self._logical_channels))
+                self._worker_done_notify_received = False
+                self._logical_channels_condition.wait(timeout)
+                if not self._worker_done_notify_received:
+                    self._logger.debug('Waiting worker(s) timed out')
+                    return False
+        finally:
+            self._logical_channels_condition.release()
+
+        # Flush pending outgoing data
+        self._writer.stop()
+        self._writer.join()
+
+        return True
+
+    def notify_write_data_done(self, channel_id):
+        """Called by the writer thread when a write operation has done.
+
+        Args:
+            channel_id: objective channel id.
+        """
+
+        try:
+            self._logical_channels_condition.acquire()
+            if channel_id in self._logical_channels:
+                channel_data = self._logical_channels[channel_id]
+                channel_data.request.connection.on_write_data_done()
+            else:
+                self._logger.debug('Seems that logical channel for %d has gone'
+                                   % channel_id)
+        finally:
+            self._logical_channels_condition.release()
+
+    def send_control_data(self, data):
+        """Sends data via the control channel.
+
+        Args:
+            data: data to be sent.
+        """
+
+        self._writer.put_outgoing_data(_OutgoingData(
+                channel_id=_CONTROL_CHANNEL_ID, data=data))
+
+    def send_data(self, channel_id, data):
+        """Sends data via given logical channel. This method is called by
+        worker threads.
+
+        Args:
+            channel_id: objective channel id.
+            data: data to be sent.
+        """
+
+        self._writer.put_outgoing_data(_OutgoingData(
+                channel_id=channel_id, data=data))
+
+    def _send_drop_channel(self, channel_id, code=None, message=''):
+        frame_data = _create_drop_channel(channel_id, code, message)
+        self._logger.debug(
+            'Sending drop channel for channel id %d' % channel_id)
+        self.send_control_data(frame_data)
+
+    def _send_error_add_channel_response(self, channel_id, status=None):
+        if status is None:
+            status = common.HTTP_STATUS_BAD_REQUEST
+
+        if status in _HTTP_BAD_RESPONSE_MESSAGES:
+            message = _HTTP_BAD_RESPONSE_MESSAGES[status]
+        else:
+            self._logger.debug('Response message for %d is not found' % status)
+            message = '???'
+
+        response = 'HTTP/1.1 %d %s\r\n\r\n' % (status, message)
+        frame_data = _create_add_channel_response(channel_id,
+                                                  encoded_handshake=response,
+                                                  encoding=0, rejected=True)
+        self.send_control_data(frame_data)
+
+    def _create_logical_request(self, block):
+        if block.channel_id == _CONTROL_CHANNEL_ID:
+            # TODO(bashi): Raise PhysicalConnectionError with code 2006
+            # instead of MuxUnexpectedException.
+            raise MuxUnexpectedException(
+                'Received the control channel id (0) as objective channel '
+                'id for AddChannel')
+
+        if block.encoding > _HANDSHAKE_ENCODING_DELTA:
+            raise PhysicalConnectionError(
+                _DROP_CODE_UNKNOWN_REQUEST_ENCODING)
+
+        method, path, version, headers = _parse_request_text(
+            block.encoded_handshake)
+        if block.encoding == _HANDSHAKE_ENCODING_DELTA:
+            headers = self._handshake_base.create_headers(headers)
+
+        connection = _LogicalConnection(self, block.channel_id)
+        request = _LogicalRequest(block.channel_id, method, path, version,
+                                  headers, connection)
+        return request
+
+    def _do_handshake_for_logical_request(self, request, send_quota=0):
+        try:
+            receive_quota = self._channel_slots.popleft()
+        except IndexError:
+            raise LogicalChannelError(
+                request.channel_id, _DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION)
+
+        handshaker = _MuxHandshaker(request, self.dispatcher,
+                                    send_quota, receive_quota)
+        try:
+            handshaker.do_handshake()
+        except handshake.VersionException, e:
+            self._logger.info('%s', e)
+            self._send_error_add_channel_response(
+                request.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
+            return False
+        except handshake.HandshakeException, e:
+            # TODO(bashi): Should we _Fail the Logical Channel_ with 3001
+            # instead?
+            self._logger.info('%s', e)
+            self._send_error_add_channel_response(request.channel_id,
+                                                  status=e.status)
+            return False
+        except handshake.AbortedByUserException, e:
+            self._logger.info('%s', e)
+            self._send_error_add_channel_response(request.channel_id)
+            return False
+
+        return True
+
+    def _add_logical_channel(self, logical_request):
+        try:
+            self._logical_channels_condition.acquire()
+            if logical_request.channel_id in self._logical_channels:
+                self._logger.debug('Channel id %d already exists' %
+                                   logical_request.channel_id)
+                raise PhysicalConnectionError(
+                    _DROP_CODE_CHANNEL_ALREADY_EXISTS,
+                    'Channel id %d already exists' %
+                    logical_request.channel_id)
+            worker = _Worker(self, logical_request)
+            channel_data = _LogicalChannelData(logical_request, worker)
+            self._logical_channels[logical_request.channel_id] = channel_data
+            worker.start()
+        finally:
+            self._logical_channels_condition.release()
+
+    def _process_add_channel_request(self, block):
+        try:
+            logical_request = self._create_logical_request(block)
+        except ValueError, e:
+            self._logger.debug('Failed to create logical request: %r' % e)
+            self._send_error_add_channel_response(
+                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
+            return
+        if self._do_handshake_for_logical_request(logical_request):
+            if block.encoding == _HANDSHAKE_ENCODING_IDENTITY:
+                # Update handshake base.
+                # TODO(bashi): Make sure this is the right place to update
+                # handshake base.
+                self._handshake_base = _HandshakeDeltaBase(
+                    logical_request.headers_in)
+            self._add_logical_channel(logical_request)
+        else:
+            self._send_error_add_channel_response(
+                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
+
+    def _process_flow_control(self, block):
+        try:
+            self._logical_channels_condition.acquire()
+            if not block.channel_id in self._logical_channels:
+                return
+            channel_data = self._logical_channels[block.channel_id]
+            channel_data.request.ws_stream.replenish_send_quota(
+                block.send_quota)
+        finally:
+            self._logical_channels_condition.release()
+
+    def _process_drop_channel(self, block):
+        self._logger.debug(
+            'DropChannel received for %d: code=%r, reason=%r' %
+            (block.channel_id, block.drop_code, block.drop_message))
+        try:
+            self._logical_channels_condition.acquire()
+            if not block.channel_id in self._logical_channels:
+                return
+            channel_data = self._logical_channels[block.channel_id]
+            channel_data.drop_code = _DROP_CODE_ACKNOWLEDGED
+
+            # Close the logical channel
+            channel_data.request.connection.set_read_state(
+                _LogicalConnection.STATE_TERMINATED)
+            channel_data.request.ws_stream.stop_sending()
+        finally:
+            self._logical_channels_condition.release()
+
+    def _process_control_blocks(self, parser):
+        for control_block in parser.read_control_blocks():
+            opcode = control_block.opcode
+            self._logger.debug('control block received, opcode: %d' % opcode)
+            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
+                self._process_add_channel_request(control_block)
+            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
+                raise PhysicalConnectionError(
+                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                    'Received AddChannelResponse')
+            elif opcode == _MUX_OPCODE_FLOW_CONTROL:
+                self._process_flow_control(control_block)
+            elif opcode == _MUX_OPCODE_DROP_CHANNEL:
+                self._process_drop_channel(control_block)
+            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
+                raise PhysicalConnectionError(
+                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                    'Received NewChannelSlot')
+            else:
+                raise MuxUnexpectedException(
+                    'Unexpected opcode %r' % opcode)
+
+    def _process_logical_frame(self, channel_id, parser):
+        self._logger.debug('Received a frame. channel id=%d' % channel_id)
+        try:
+            self._logical_channels_condition.acquire()
+            if not channel_id in self._logical_channels:
+                # We must ignore the message for an inactive channel.
+                return
+            channel_data = self._logical_channels[channel_id]
+            fin, rsv1, rsv2, rsv3, opcode, payload = parser.read_inner_frame()
+            consuming_byte = len(payload)
+            if opcode != common.OPCODE_CONTINUATION:
+                consuming_byte += 1
+            if not channel_data.request.ws_stream.consume_receive_quota(
+                consuming_byte):
+                # The client violates quota. Close logical channel.
+                raise LogicalChannelError(
+                    channel_id, _DROP_CODE_SEND_QUOTA_VIOLATION)
+            header = create_header(opcode, len(payload), fin, rsv1, rsv2, rsv3,
+                                   mask=False)
+            frame_data = header + payload
+            channel_data.request.connection.append_frame_data(frame_data)
+        finally:
+            self._logical_channels_condition.release()
+
+    def dispatch_message(self, message):
+        """Dispatches message. The reader thread calls this method.
+
+        Args:
+            message: a message that contains encapsulated frame.
+        Raises:
+            PhysicalConnectionError: if the message contains physical
+                connection level errors.
+            LogicalChannelError: if the message contains logical channel
+                level errors.
+        """
+
+        parser = _MuxFramePayloadParser(message)
+        try:
+            channel_id = parser.read_channel_id()
+        except ValueError, e:
+            raise PhysicalConnectionError(_DROP_CODE_CHANNEL_ID_TRUNCATED)
+        if channel_id == _CONTROL_CHANNEL_ID:
+            self._process_control_blocks(parser)
+        else:
+            self._process_logical_frame(channel_id, parser)
+
+    def notify_worker_done(self, channel_id):
+        """Called when a worker has finished.
+
+        Args:
+            channel_id: channel id corresponded with the worker.
+        """
+
+        self._logger.debug('Worker for channel id %d terminated' % channel_id)
+        try:
+            self._logical_channels_condition.acquire()
+            if not channel_id in self._logical_channels:
+                raise MuxUnexpectedException(
+                    'Channel id %d not found' % channel_id)
+            channel_data = self._logical_channels.pop(channel_id)
+        finally:
+            self._worker_done_notify_received = True
+            self._logical_channels_condition.notify()
+            self._logical_channels_condition.release()
+
+        if not channel_data.request.server_terminated:
+            self._send_drop_channel(
+                channel_id, code=channel_data.drop_code,
+                message=channel_data.drop_message)
+
+    def notify_reader_done(self):
+        """This method is called by the reader thread when the reader has
+        finished.
+        """
+
+        self._logger.debug(
+            'Termiating all logical connections waiting for incoming data '
+            '...')
+        self._logical_channels_condition.acquire()
+        for channel_data in self._logical_channels.values():
+            try:
+                channel_data.request.connection.set_read_state(
+                    _LogicalConnection.STATE_TERMINATED)
+            except Exception:
+                self._logger.debug(traceback.format_exc())
+        self._logical_channels_condition.release()
+
+    def notify_writer_done(self):
+        """This method is called by the writer thread when the writer has
+        finished.
+        """
+
+        self._logger.debug(
+            'Termiating all logical connections waiting for write '
+            'completion ...')
+        self._logical_channels_condition.acquire()
+        for channel_data in self._logical_channels.values():
+            try:
+                channel_data.request.connection.on_writer_done()
+            except Exception:
+                self._logger.debug(traceback.format_exc())
+        self._logical_channels_condition.release()
+
+    def fail_physical_connection(self, code, message):
+        """Fail the physical connection.
+
+        Args:
+            code: drop reason code.
+            message: drop message.
+        """
+
+        self._logger.debug('Failing the physical connection...')
+        self._send_drop_channel(_CONTROL_CHANNEL_ID, code, message)
+        self._writer.stop(common.STATUS_INTERNAL_ENDPOINT_ERROR)
+
+    def fail_logical_channel(self, channel_id, code, message):
+        """Fail a logical channel.
+
+        Args:
+            channel_id: channel id.
+            code: drop reason code.
+            message: drop message.
+        """
+
+        self._logger.debug('Failing logical channel %d...' % channel_id)
+        try:
+            self._logical_channels_condition.acquire()
+            if channel_id in self._logical_channels:
+                channel_data = self._logical_channels[channel_id]
+                # Close the logical channel. notify_worker_done() will be
+                # called later and it will send DropChannel.
+                channel_data.drop_code = code
+                channel_data.drop_message = message
+
+                channel_data.request.connection.set_read_state(
+                    _LogicalConnection.STATE_TERMINATED)
+                channel_data.request.ws_stream.stop_sending()
+            else:
+                self._send_drop_channel(channel_id, code, message)
+        finally:
+            self._logical_channels_condition.release()
+
+
+def use_mux(request):
+    return hasattr(request, 'mux_processor') and (
+        request.mux_processor.is_active())
+
+
+def start(request, dispatcher):
+    mux_handler = _MuxHandler(request, dispatcher)
+    mux_handler.start()
+
+    mux_handler.add_channel_slots(_INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                  _INITIAL_QUOTA_FOR_CLIENT)
+
+    mux_handler.wait_until_done()
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/mod_pywebsocket/standalone.py b/tools/pywebsocket/mod_pywebsocket/standalone.py
new file mode 100755
index 0000000..63200e2
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/standalone.py
@@ -0,0 +1,1208 @@
+#!/usr/bin/env python
+#
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Standalone WebSocket server.
+
+Use this file to launch pywebsocket without Apache HTTP Server.
+
+
+BASIC USAGE
+===========
+
+Go to the src directory and run
+
+  $ python mod_pywebsocket/standalone.py [-p <ws_port>]
+                                         [-w <websock_handlers>]
+                                         [-d <document_root>]
+
+<ws_port> is the port number to use for ws:// connection.
+
+<document_root> is the path to the root directory of HTML files.
+
+<websock_handlers> is the path to the root directory of WebSocket handlers.
+If not specified, <document_root> will be used. See __init__.py (or
+run $ pydoc mod_pywebsocket) for how to write WebSocket handlers.
+
+For more detail and other options, run
+
+  $ python mod_pywebsocket/standalone.py --help
+
+or see _build_option_parser method below.
+
+For trouble shooting, adding "--log_level debug" might help you.
+
+
+TRY DEMO
+========
+
+Go to the src directory and run standalone.py with -d option to set the
+document root to the directory containing example HTMLs and handlers like this:
+
+  $ cd src
+  $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example
+
+to launch pywebsocket with the sample handler and html on port 80. Open
+http://localhost/console.html, click the connect button, type something into
+the text box next to the send button and click the send button. If everything
+is working, you'll see the message you typed echoed by the server.
+
+
+USING TLS
+=========
+
+To run the standalone server with TLS support, run it with -t, -k, and -c
+options. When TLS is enabled, the standalone server accepts only TLS connection.
+
+Note that when ssl module is used and the key/cert location is incorrect,
+TLS connection silently fails while pyOpenSSL fails on startup.
+
+Example:
+
+  $ PYTHONPATH=. python mod_pywebsocket/standalone.py \
+        -d example \
+        -p 10443 \
+        -t \
+        -c ../test/cert/cert.pem \
+        -k ../test/cert/key.pem \
+
+Note that when passing a relative path to -c and -k option, it will be resolved
+using the document root directory as the base.
+
+
+USING CLIENT AUTHENTICATION
+===========================
+
+To run the standalone server with TLS client authentication support, run it with
+--tls-client-auth and --tls-client-ca options in addition to ones required for
+TLS support.
+
+Example:
+
+  $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example -p 10443 -t \
+        -c ../test/cert/cert.pem -k ../test/cert/key.pem \
+        --tls-client-auth \
+        --tls-client-ca=../test/cert/cacert.pem
+
+Note that when passing a relative path to --tls-client-ca option, it will be
+resolved using the document root directory as the base.
+
+
+CONFIGURATION FILE
+==================
+
+You can also write a configuration file and use it by specifying the path to
+the configuration file by --config option. Please write a configuration file
+following the documentation of the Python ConfigParser library. Name of each
+entry must be the long version argument name. E.g. to set log level to debug,
+add the following line:
+
+log_level=debug
+
+For options which doesn't take value, please add some fake value. E.g. for
+--tls option, add the following line:
+
+tls=True
+
+Note that tls will be enabled even if you write tls=False as the value part is
+fake.
+
+When both a command line argument and a configuration file entry are set for
+the same configuration item, the command line value will override one in the
+configuration file.
+
+
+THREADING
+=========
+
+This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
+used for each request.
+
+
+SECURITY WARNING
+================
+
+This uses CGIHTTPServer and CGIHTTPServer is not secure.
+It may execute arbitrary Python code or external programs. It should not be
+used outside a firewall.
+"""
+
+import BaseHTTPServer
+import CGIHTTPServer
+import SimpleHTTPServer
+import SocketServer
+import ConfigParser
+import base64
+import httplib
+import logging
+import logging.handlers
+import optparse
+import os
+import re
+import select
+import socket
+import sys
+import threading
+import time
+import urlparse
+
+from mod_pywebsocket import common
+from mod_pywebsocket import dispatch
+from mod_pywebsocket import handshake
+from mod_pywebsocket import http_header_util
+from mod_pywebsocket import memorizingfile
+from mod_pywebsocket import util
+from mod_pywebsocket.xhr_benchmark_handler import XHRBenchmarkHandler
+
+
+_DEFAULT_LOG_MAX_BYTES = 1024 * 256
+_DEFAULT_LOG_BACKUP_COUNT = 5
+
+_DEFAULT_REQUEST_QUEUE_SIZE = 128
+
+# 1024 is practically large enough to contain WebSocket handshake lines.
+_MAX_MEMORIZED_LINES = 1024
+
+# Constants for the --tls_module flag.
+_TLS_BY_STANDARD_MODULE = 'ssl'
+_TLS_BY_PYOPENSSL = 'pyopenssl'
+
+
+class _StandaloneConnection(object):
+    """Mimic mod_python mp_conn."""
+
+    def __init__(self, request_handler):
+        """Construct an instance.
+
+        Args:
+            request_handler: A WebSocketRequestHandler instance.
+        """
+
+        self._request_handler = request_handler
+
+    def get_local_addr(self):
+        """Getter to mimic mp_conn.local_addr."""
+
+        return (self._request_handler.server.server_name,
+                self._request_handler.server.server_port)
+    local_addr = property(get_local_addr)
+
+    def get_remote_addr(self):
+        """Getter to mimic mp_conn.remote_addr.
+
+        Setting the property in __init__ won't work because the request
+        handler is not initialized yet there."""
+
+        return self._request_handler.client_address
+    remote_addr = property(get_remote_addr)
+
+    def write(self, data):
+        """Mimic mp_conn.write()."""
+
+        return self._request_handler.wfile.write(data)
+
+    def read(self, length):
+        """Mimic mp_conn.read()."""
+
+        return self._request_handler.rfile.read(length)
+
+    def get_memorized_lines(self):
+        """Get memorized lines."""
+
+        return self._request_handler.rfile.get_memorized_lines()
+
+
+class _StandaloneRequest(object):
+    """Mimic mod_python request."""
+
+    def __init__(self, request_handler, use_tls):
+        """Construct an instance.
+
+        Args:
+            request_handler: A WebSocketRequestHandler instance.
+        """
+
+        self._logger = util.get_class_logger(self)
+
+        self._request_handler = request_handler
+        self.connection = _StandaloneConnection(request_handler)
+        self._use_tls = use_tls
+        self.headers_in = request_handler.headers
+
+    def get_uri(self):
+        """Getter to mimic request.uri.
+
+        This method returns the raw data at the Request-URI part of the
+        Request-Line, while the uri method on the request object of mod_python
+        returns the path portion after parsing the raw data. This behavior is
+        kept for compatibility.
+        """
+
+        return self._request_handler.path
+    uri = property(get_uri)
+
+    def get_unparsed_uri(self):
+        """Getter to mimic request.unparsed_uri."""
+
+        return self._request_handler.path
+    unparsed_uri = property(get_unparsed_uri)
+
+    def get_method(self):
+        """Getter to mimic request.method."""
+
+        return self._request_handler.command
+    method = property(get_method)
+
+    def get_protocol(self):
+        """Getter to mimic request.protocol."""
+
+        return self._request_handler.request_version
+    protocol = property(get_protocol)
+
+    def is_https(self):
+        """Mimic request.is_https()."""
+
+        return self._use_tls
+
+
+def _import_ssl():
+    global ssl
+    try:
+        import ssl
+        return True
+    except ImportError:
+        return False
+
+
+def _import_pyopenssl():
+    global OpenSSL
+    try:
+        import OpenSSL.SSL
+        return True
+    except ImportError:
+        return False
+
+
+class _StandaloneSSLConnection(object):
+    """A wrapper class for OpenSSL.SSL.Connection to
+    - provide makefile method which is not supported by the class
+    - tweak shutdown method since OpenSSL.SSL.Connection.shutdown doesn't
+      accept the "how" argument.
+    - convert SysCallError exceptions that its recv method may raise into a
+      return value of '', meaning EOF. We cannot overwrite the recv method on
+      self._connection since it's immutable.
+    """
+
+    _OVERRIDDEN_ATTRIBUTES = ['_connection', 'makefile', 'shutdown', 'recv']
+
+    def __init__(self, connection):
+        self._connection = connection
+
+    def __getattribute__(self, name):
+        if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES:
+            return object.__getattribute__(self, name)
+        return self._connection.__getattribute__(name)
+
+    def __setattr__(self, name, value):
+        if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES:
+            return object.__setattr__(self, name, value)
+        return self._connection.__setattr__(name, value)
+
+    def makefile(self, mode='r', bufsize=-1):
+        return socket._fileobject(self, mode, bufsize)
+
+    def shutdown(self, unused_how):
+        self._connection.shutdown()
+
+    def recv(self, bufsize, flags=0):
+        if flags != 0:
+            raise ValueError('Non-zero flags not allowed')
+
+        try:
+            return self._connection.recv(bufsize)
+        except OpenSSL.SSL.SysCallError, (err, message):
+            if err == -1:
+                # Suppress "unexpected EOF" exception. See the OpenSSL document
+                # for SSL_get_error.
+                return ''
+            raise
+
+
+def _alias_handlers(dispatcher, websock_handlers_map_file):
+    """Set aliases specified in websock_handler_map_file in dispatcher.
+
+    Args:
+        dispatcher: dispatch.Dispatcher instance
+        websock_handler_map_file: alias map file
+    """
+
+    fp = open(websock_handlers_map_file)
+    try:
+        for line in fp:
+            if line[0] == '#' or line.isspace():
+                continue
+            m = re.match('(\S+)\s+(\S+)', line)
+            if not m:
+                logging.warning('Wrong format in map file:' + line)
+                continue
+            try:
+                dispatcher.add_resource_path_alias(
+                    m.group(1), m.group(2))
+            except dispatch.DispatchException, e:
+                logging.error(str(e))
+    finally:
+        fp.close()
+
+
+class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+    """HTTPServer specialized for WebSocket."""
+
+    # Overrides SocketServer.ThreadingMixIn.daemon_threads
+    daemon_threads = True
+    # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
+    allow_reuse_address = True
+
+    def __init__(self, options):
+        """Override SocketServer.TCPServer.__init__ to set SSL enabled
+        socket object to self.socket before server_bind and server_activate,
+        if necessary.
+        """
+
+        # Share a Dispatcher among request handlers to save time for
+        # instantiation.  Dispatcher can be shared because it is thread-safe.
+        options.dispatcher = dispatch.Dispatcher(
+            options.websock_handlers,
+            options.scan_dir,
+            options.allow_handlers_outside_root_dir)
+        if options.websock_handlers_map_file:
+            _alias_handlers(options.dispatcher,
+                            options.websock_handlers_map_file)
+        warnings = options.dispatcher.source_warnings()
+        if warnings:
+            for warning in warnings:
+                logging.warning('Warning in source loading: %s' % warning)
+
+        self._logger = util.get_class_logger(self)
+
+        self.request_queue_size = options.request_queue_size
+        self.__ws_is_shut_down = threading.Event()
+        self.__ws_serving = False
+
+        SocketServer.BaseServer.__init__(
+            self, (options.server_host, options.port), WebSocketRequestHandler)
+
+        # Expose the options object to allow handler objects access it. We name
+        # it with websocket_ prefix to avoid conflict.
+        self.websocket_server_options = options
+
+        self._create_sockets()
+        self.server_bind()
+        self.server_activate()
+
+    def _create_sockets(self):
+        self.server_name, self.server_port = self.server_address
+        self._sockets = []
+        if not self.server_name:
+            # On platforms that doesn't support IPv6, the first bind fails.
+            # On platforms that supports IPv6
+            # - If it binds both IPv4 and IPv6 on call with AF_INET6, the
+            #   first bind succeeds and the second fails (we'll see 'Address
+            #   already in use' error).
+            # - If it binds only IPv6 on call with AF_INET6, both call are
+            #   expected to succeed to listen both protocol.
+            addrinfo_array = [
+                (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''),
+                (socket.AF_INET, socket.SOCK_STREAM, '', '', '')]
+        else:
+            addrinfo_array = socket.getaddrinfo(self.server_name,
+                                                self.server_port,
+                                                socket.AF_UNSPEC,
+                                                socket.SOCK_STREAM,
+                                                socket.IPPROTO_TCP)
+        for addrinfo in addrinfo_array:
+            self._logger.info('Create socket on: %r', addrinfo)
+            family, socktype, proto, canonname, sockaddr = addrinfo
+            try:
+                socket_ = socket.socket(family, socktype)
+            except Exception, e:
+                self._logger.info('Skip by failure: %r', e)
+                continue
+            server_options = self.websocket_server_options
+            if server_options.use_tls:
+                # For the case of _HAS_OPEN_SSL, we do wrapper setup after
+                # accept.
+                if server_options.tls_module == _TLS_BY_STANDARD_MODULE:
+                    if server_options.tls_client_auth:
+                        if server_options.tls_client_cert_optional:
+                            client_cert_ = ssl.CERT_OPTIONAL
+                        else:
+                            client_cert_ = ssl.CERT_REQUIRED
+                    else:
+                        client_cert_ = ssl.CERT_NONE
+                    socket_ = ssl.wrap_socket(socket_,
+                        keyfile=server_options.private_key,
+                        certfile=server_options.certificate,
+                        ssl_version=ssl.PROTOCOL_SSLv23,
+                        ca_certs=server_options.tls_client_ca,
+                        cert_reqs=client_cert_,
+                        do_handshake_on_connect=False)
+            self._sockets.append((socket_, addrinfo))
+
+    def server_bind(self):
+        """Override SocketServer.TCPServer.server_bind to enable multiple
+        sockets bind.
+        """
+
+        failed_sockets = []
+
+        for socketinfo in self._sockets:
+            socket_, addrinfo = socketinfo
+            self._logger.info('Bind on: %r', addrinfo)
+            if self.allow_reuse_address:
+                socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            try:
+                socket_.bind(self.server_address)
+            except Exception, e:
+                self._logger.info('Skip by failure: %r', e)
+                socket_.close()
+                failed_sockets.append(socketinfo)
+            if self.server_address[1] == 0:
+                # The operating system assigns the actual port number for port
+                # number 0. This case, the second and later sockets should use
+                # the same port number. Also self.server_port is rewritten
+                # because it is exported, and will be used by external code.
+                self.server_address = (
+                    self.server_name, socket_.getsockname()[1])
+                self.server_port = self.server_address[1]
+                self._logger.info('Port %r is assigned', self.server_port)
+
+        for socketinfo in failed_sockets:
+            self._sockets.remove(socketinfo)
+
+    def server_activate(self):
+        """Override SocketServer.TCPServer.server_activate to enable multiple
+        sockets listen.
+        """
+
+        failed_sockets = []
+
+        for socketinfo in self._sockets:
+            socket_, addrinfo = socketinfo
+            self._logger.info('Listen on: %r', addrinfo)
+            try:
+                socket_.listen(self.request_queue_size)
+            except Exception, e:
+                self._logger.info('Skip by failure: %r', e)
+                socket_.close()
+                failed_sockets.append(socketinfo)
+
+        for socketinfo in failed_sockets:
+            self._sockets.remove(socketinfo)
+
+        if len(self._sockets) == 0:
+            self._logger.critical(
+                'No sockets activated. Use info log level to see the reason.')
+
+    def server_close(self):
+        """Override SocketServer.TCPServer.server_close to enable multiple
+        sockets close.
+        """
+
+        for socketinfo in self._sockets:
+            socket_, addrinfo = socketinfo
+            self._logger.info('Close on: %r', addrinfo)
+            socket_.close()
+
+    def fileno(self):
+        """Override SocketServer.TCPServer.fileno."""
+
+        self._logger.critical('Not supported: fileno')
+        return self._sockets[0][0].fileno()
+
+    def handle_error(self, request, client_address):
+        """Override SocketServer.handle_error."""
+
+        self._logger.error(
+            'Exception in processing request from: %r\n%s',
+            client_address,
+            util.get_stack_trace())
+        # Note: client_address is a tuple.
+
+    def get_request(self):
+        """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
+        object with _StandaloneSSLConnection to provide makefile method. We
+        cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
+        attribute.
+        """
+
+        accepted_socket, client_address = self.socket.accept()
+
+        server_options = self.websocket_server_options
+        if server_options.use_tls:
+            if server_options.tls_module == _TLS_BY_STANDARD_MODULE:
+                try:
+                    accepted_socket.do_handshake()
+                except ssl.SSLError, e:
+                    self._logger.debug('%r', e)
+                    raise
+
+                # Print cipher in use. Handshake is done on accept.
+                self._logger.debug('Cipher: %s', accepted_socket.cipher())
+                self._logger.debug('Client cert: %r',
+                                   accepted_socket.getpeercert())
+            elif server_options.tls_module == _TLS_BY_PYOPENSSL:
+                # We cannot print the cipher in use. pyOpenSSL doesn't provide
+                # any method to fetch that.
+
+                ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+                ctx.use_privatekey_file(server_options.private_key)
+                ctx.use_certificate_file(server_options.certificate)
+
+                def default_callback(conn, cert, errnum, errdepth, ok):
+                    return ok == 1
+
+                # See the OpenSSL document for SSL_CTX_set_verify.
+                if server_options.tls_client_auth:
+                    verify_mode = OpenSSL.SSL.VERIFY_PEER
+                    if not server_options.tls_client_cert_optional:
+                        verify_mode |= OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT
+                    ctx.set_verify(verify_mode, default_callback)
+                    ctx.load_verify_locations(server_options.tls_client_ca,
+                                              None)
+                else:
+                    ctx.set_verify(OpenSSL.SSL.VERIFY_NONE, default_callback)
+
+                accepted_socket = OpenSSL.SSL.Connection(ctx, accepted_socket)
+                accepted_socket.set_accept_state()
+
+                # Convert SSL related error into socket.error so that
+                # SocketServer ignores them and keeps running.
+                #
+                # TODO(tyoshino): Convert all kinds of errors.
+                try:
+                    accepted_socket.do_handshake()
+                except OpenSSL.SSL.Error, e:
+                    # Set errno part to 1 (SSL_ERROR_SSL) like the ssl module
+                    # does.
+                    self._logger.debug('%r', e)
+                    raise socket.error(1, '%r' % e)
+                cert = accepted_socket.get_peer_certificate()
+                if cert is not None:
+                    self._logger.debug('Client cert subject: %r',
+                                       cert.get_subject().get_components())
+                accepted_socket = _StandaloneSSLConnection(accepted_socket)
+            else:
+                raise ValueError('No TLS support module is available')
+
+        return accepted_socket, client_address
+
+    def serve_forever(self, poll_interval=0.5):
+        """Override SocketServer.BaseServer.serve_forever."""
+
+        self.__ws_serving = True
+        self.__ws_is_shut_down.clear()
+        handle_request = self.handle_request
+        if hasattr(self, '_handle_request_noblock'):
+            handle_request = self._handle_request_noblock
+        else:
+            self._logger.warning('Fallback to blocking request handler')
+        try:
+            while self.__ws_serving:
+                r, w, e = select.select(
+                    [socket_[0] for socket_ in self._sockets],
+                    [], [], poll_interval)
+                for socket_ in r:
+                    self.socket = socket_
+                    handle_request()
+                self.socket = None
+        finally:
+            self.__ws_is_shut_down.set()
+
+    def shutdown(self):
+        """Override SocketServer.BaseServer.shutdown."""
+
+        self.__ws_serving = False
+        self.__ws_is_shut_down.wait()
+
+
+class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
+    """CGIHTTPRequestHandler specialized for WebSocket."""
+
+    # Use httplib.HTTPMessage instead of mimetools.Message.
+    MessageClass = httplib.HTTPMessage
+
+    protocol_version = "HTTP/1.1"
+
+    def setup(self):
+        """Override SocketServer.StreamRequestHandler.setup to wrap rfile
+        with MemorizingFile.
+
+        This method will be called by BaseRequestHandler's constructor
+        before calling BaseHTTPRequestHandler.handle.
+        BaseHTTPRequestHandler.handle will call
+        BaseHTTPRequestHandler.handle_one_request and it will call
+        WebSocketRequestHandler.parse_request.
+        """
+
+        # Call superclass's setup to prepare rfile, wfile, etc. See setup
+        # definition on the root class SocketServer.StreamRequestHandler to
+        # understand what this does.
+        CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
+
+        self.rfile = memorizingfile.MemorizingFile(
+            self.rfile,
+            max_memorized_lines=_MAX_MEMORIZED_LINES)
+
+    def __init__(self, request, client_address, server):
+        self._logger = util.get_class_logger(self)
+
+        self._options = server.websocket_server_options
+
+        # Overrides CGIHTTPServerRequestHandler.cgi_directories.
+        self.cgi_directories = self._options.cgi_directories
+        # Replace CGIHTTPRequestHandler.is_executable method.
+        if self._options.is_executable_method is not None:
+            self.is_executable = self._options.is_executable_method
+
+        # This actually calls BaseRequestHandler.__init__.
+        CGIHTTPServer.CGIHTTPRequestHandler.__init__(
+            self, request, client_address, server)
+
+    def parse_request(self):
+        """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
+
+        Return True to continue processing for HTTP(S), False otherwise.
+
+        See BaseHTTPRequestHandler.handle_one_request method which calls
+        this method to understand how the return value will be handled.
+        """
+
+        # We hook parse_request method, but also call the original
+        # CGIHTTPRequestHandler.parse_request since when we return False,
+        # CGIHTTPRequestHandler.handle_one_request continues processing and
+        # it needs variables set by CGIHTTPRequestHandler.parse_request.
+        #
+        # Variables set by this method will be also used by WebSocket request
+        # handling (self.path, self.command, self.requestline, etc. See also
+        # how _StandaloneRequest's members are implemented using these
+        # attributes).
+        if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
+            return False
+
+        if self.command == "CONNECT":
+            self.send_response(200, "Connected")
+            self.send_header("Connection", "keep-alive")
+            self.end_headers()
+            return False
+
+        if self._options.use_basic_auth:
+            auth = self.headers.getheader('Authorization')
+            if auth != self._options.basic_auth_credential:
+                self.send_response(401)
+                self.send_header('WWW-Authenticate',
+                                 'Basic realm="Pywebsocket"')
+                self.end_headers()
+                self._logger.info('Request basic authentication')
+                return False
+
+        # Special paths for XMLHttpRequest benchmark
+        xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245'
+        parsed_path = urlparse.urlsplit(self.path)
+        if parsed_path.path == (xhr_benchmark_helper_prefix + '_send'):
+            xhr_benchmark_handler = XHRBenchmarkHandler(
+                self.headers, self.rfile, self.wfile)
+            xhr_benchmark_handler.do_send()
+            return False
+        if parsed_path.path == (xhr_benchmark_helper_prefix + '_receive'):
+            xhr_benchmark_handler = XHRBenchmarkHandler(
+                self.headers, self.rfile, self.wfile)
+            xhr_benchmark_handler.do_receive_and_parse()
+            return False
+        if parsed_path.path == (xhr_benchmark_helper_prefix +
+                                '_receive_getnocache'):
+            xhr_benchmark_handler = XHRBenchmarkHandler(
+                self.headers, self.rfile, self.wfile)
+            xhr_benchmark_handler.do_receive(
+                int(parsed_path.query), False, False)
+            return False
+        if parsed_path.path == (xhr_benchmark_helper_prefix +
+                                '_receive_getcache'):
+            xhr_benchmark_handler = XHRBenchmarkHandler(
+                self.headers, self.rfile, self.wfile)
+            xhr_benchmark_handler.do_receive(
+                int(parsed_path.query), False, True)
+            return False
+
+        host, port, resource = http_header_util.parse_uri(self.path)
+        if resource is None:
+            self._logger.info('Invalid URI: %r', self.path)
+            self._logger.info('Fallback to CGIHTTPRequestHandler')
+            return True
+        server_options = self.server.websocket_server_options
+        if host is not None:
+            validation_host = server_options.validation_host
+            if validation_host is not None and host != validation_host:
+                self._logger.info('Invalid host: %r (expected: %r)',
+                                  host,
+                                  validation_host)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
+                return True
+        if port is not None:
+            validation_port = server_options.validation_port
+            if validation_port is not None and port != validation_port:
+                self._logger.info('Invalid port: %r (expected: %r)',
+                                  port,
+                                  validation_port)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
+                return True
+        self.path = resource
+
+        request = _StandaloneRequest(self, self._options.use_tls)
+
+        try:
+            # Fallback to default http handler for request paths for which
+            # we don't have request handlers.
+            if not self._options.dispatcher.get_handler_suite(self.path):
+                self._logger.info('No handler for resource: %r',
+                                  self.path)
+                self._logger.info('Fallback to CGIHTTPRequestHandler')
+                return True
+        except dispatch.DispatchException, e:
+            self._logger.info('Dispatch failed for error: %s', e)
+            self.send_error(e.status)
+            return False
+
+        # If any Exceptions without except clause setup (including
+        # DispatchException) is raised below this point, it will be caught
+        # and logged by WebSocketServer.
+
+        try:
+            try:
+                handshake.do_handshake(
+                    request,
+                    self._options.dispatcher,
+                    allowDraft75=self._options.allow_draft75,
+                    strict=self._options.strict)
+            except handshake.VersionException, e:
+                self._logger.info('Handshake failed for version error: %s', e)
+                self.send_response(common.HTTP_STATUS_BAD_REQUEST)
+                self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER,
+                                 e.supported_versions)
+                self.end_headers()
+                return False
+            except handshake.HandshakeException, e:
+                # Handshake for ws(s) failed.
+                self._logger.info('Handshake failed for error: %s', e)
+                self.send_error(e.status)
+                return False
+
+            request._dispatcher = self._options.dispatcher
+            self._options.dispatcher.transfer_data(request)
+        except handshake.AbortedByUserException, e:
+            self._logger.info('Aborted: %s', e)
+        return False
+
+    def log_request(self, code='-', size='-'):
+        """Override BaseHTTPServer.log_request."""
+
+        self._logger.info('"%s" %s %s',
+                          self.requestline, str(code), str(size))
+
+    def log_error(self, *args):
+        """Override BaseHTTPServer.log_error."""
+
+        # Despite the name, this method is for warnings than for errors.
+        # For example, HTTP status code is logged by this method.
+        self._logger.warning('%s - %s',
+                             self.address_string(),
+                             args[0] % args[1:])
+
+    def is_cgi(self):
+        """Test whether self.path corresponds to a CGI script.
+
+        Add extra check that self.path doesn't contains ..
+        Also check if the file is a executable file or not.
+        If the file is not executable, it is handled as static file or dir
+        rather than a CGI script.
+        """
+
+        if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
+            if '..' in self.path:
+                return False
+            # strip query parameter from request path
+            resource_name = self.path.split('?', 2)[0]
+            # convert resource_name into real path name in filesystem.
+            scriptfile = self.translate_path(resource_name)
+            if not os.path.isfile(scriptfile):
+                return False
+            if not self.is_executable(scriptfile):
+                return False
+            return True
+        return False
+
+
+def _get_logger_from_class(c):
+    return logging.getLogger('%s.%s' % (c.__module__, c.__name__))
+
+
+def _configure_logging(options):
+    logging.addLevelName(common.LOGLEVEL_FINE, 'FINE')
+
+    logger = logging.getLogger()
+    logger.setLevel(logging.getLevelName(options.log_level.upper()))
+    if options.log_file:
+        handler = logging.handlers.RotatingFileHandler(
+                options.log_file, 'a', options.log_max, options.log_count)
+    else:
+        handler = logging.StreamHandler()
+    formatter = logging.Formatter(
+            '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+
+    deflate_log_level_name = logging.getLevelName(
+        options.deflate_log_level.upper())
+    _get_logger_from_class(util._Deflater).setLevel(
+        deflate_log_level_name)
+    _get_logger_from_class(util._Inflater).setLevel(
+        deflate_log_level_name)
+
+
+def _build_option_parser():
+    parser = optparse.OptionParser()
+
+    parser.add_option('--config', dest='config_file', type='string',
+                      default=None,
+                      help=('Path to configuration file. See the file comment '
+                            'at the top of this file for the configuration '
+                            'file format'))
+    parser.add_option('-H', '--server-host', '--server_host',
+                      dest='server_host',
+                      default='',
+                      help='server hostname to listen to')
+    parser.add_option('-V', '--validation-host', '--validation_host',
+                      dest='validation_host',
+                      default=None,
+                      help='server hostname to validate in absolute path.')
+    parser.add_option('-p', '--port', dest='port', type='int',
+                      default=common.DEFAULT_WEB_SOCKET_PORT,
+                      help='port to listen to')
+    parser.add_option('-P', '--validation-port', '--validation_port',
+                      dest='validation_port', type='int',
+                      default=None,
+                      help='server port to validate in absolute path.')
+    parser.add_option('-w', '--websock-handlers', '--websock_handlers',
+                      dest='websock_handlers',
+                      default='.',
+                      help=('The root directory of WebSocket handler files. '
+                            'If the path is relative, --document-root is used '
+                            'as the base.'))
+    parser.add_option('-m', '--websock-handlers-map-file',
+                      '--websock_handlers_map_file',
+                      dest='websock_handlers_map_file',
+                      default=None,
+                      help=('WebSocket handlers map file. '
+                            'Each line consists of alias_resource_path and '
+                            'existing_resource_path, separated by spaces.'))
+    parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
+                      default=None,
+                      help=('Must be a directory under --websock-handlers. '
+                            'Only handlers under this directory are scanned '
+                            'and registered to the server. '
+                            'Useful for saving scan time when the handler '
+                            'root directory contains lots of files that are '
+                            'not handler file or are handler files but you '
+                            'don\'t want them to be registered. '))
+    parser.add_option('--allow-handlers-outside-root-dir',
+                      '--allow_handlers_outside_root_dir',
+                      dest='allow_handlers_outside_root_dir',
+                      action='store_true',
+                      default=False,
+                      help=('Scans WebSocket handlers even if their canonical '
+                            'path is not under --websock-handlers.'))
+    parser.add_option('-d', '--document-root', '--document_root',
+                      dest='document_root', default='.',
+                      help='Document root directory.')
+    parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths',
+                      default=None,
+                      help=('CGI paths relative to document_root.'
+                            'Comma-separated. (e.g -x /cgi,/htbin) '
+                            'Files under document_root/cgi_path are handled '
+                            'as CGI programs. Must be executable.'))
+    parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
+                      default=False, help='use TLS (wss://)')
+    parser.add_option('--tls-module', '--tls_module', dest='tls_module',
+                      type='choice',
+                      choices = [_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL],
+                      help='Use ssl module if "%s" is specified. '
+                      'Use pyOpenSSL module if "%s" is specified' %
+                      (_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL))
+    parser.add_option('-k', '--private-key', '--private_key',
+                      dest='private_key',
+                      default='', help='TLS private key file.')
+    parser.add_option('-c', '--certificate', dest='certificate',
+                      default='', help='TLS certificate file.')
+    parser.add_option('--tls-client-auth', dest='tls_client_auth',
+                      action='store_true', default=False,
+                      help='Requests TLS client auth on every connection.')
+    parser.add_option('--tls-client-cert-optional',
+                      dest='tls_client_cert_optional',
+                      action='store_true', default=False,
+                      help=('Makes client certificate optional even though '
+                            'TLS client auth is enabled.'))
+    parser.add_option('--tls-client-ca', dest='tls_client_ca', default='',
+                      help=('Specifies a pem file which contains a set of '
+                            'concatenated CA certificates which are used to '
+                            'validate certificates passed from clients'))
+    parser.add_option('--basic-auth', dest='use_basic_auth',
+                      action='store_true', default=False,
+                      help='Requires Basic authentication.')
+    parser.add_option('--basic-auth-credential',
+                      dest='basic_auth_credential', default='test:test',
+                      help='Specifies the credential of basic authentication '
+                      'by username:password pair (e.g. test:test).')
+    parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
+                      default='', help='Log file.')
+    # Custom log level:
+    # - FINE: Prints status of each frame processing step
+    parser.add_option('--log-level', '--log_level', type='choice',
+                      dest='log_level', default='warn',
+                      choices=['fine',
+                               'debug', 'info', 'warning', 'warn', 'error',
+                               'critical'],
+                      help='Log level.')
+    parser.add_option('--deflate-log-level', '--deflate_log_level',
+                      type='choice',
+                      dest='deflate_log_level', default='warn',
+                      choices=['debug', 'info', 'warning', 'warn', 'error',
+                               'critical'],
+                      help='Log level for _Deflater and _Inflater.')
+    parser.add_option('--thread-monitor-interval-in-sec',
+                      '--thread_monitor_interval_in_sec',
+                      dest='thread_monitor_interval_in_sec',
+                      type='int', default=-1,
+                      help=('If positive integer is specified, run a thread '
+                            'monitor to show the status of server threads '
+                            'periodically in the specified inteval in '
+                            'second. If non-positive integer is specified, '
+                            'disable the thread monitor.'))
+    parser.add_option('--log-max', '--log_max', dest='log_max', type='int',
+                      default=_DEFAULT_LOG_MAX_BYTES,
+                      help='Log maximum bytes')
+    parser.add_option('--log-count', '--log_count', dest='log_count',
+                      type='int', default=_DEFAULT_LOG_BACKUP_COUNT,
+                      help='Log backup count')
+    parser.add_option('--allow-draft75', dest='allow_draft75',
+                      action='store_true', default=False,
+                      help='Obsolete option. Ignored.')
+    parser.add_option('--strict', dest='strict', action='store_true',
+                      default=False, help='Obsolete option. Ignored.')
+    parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
+                      default=_DEFAULT_REQUEST_QUEUE_SIZE,
+                      help='request queue size')
+
+    return parser
+
+
+class ThreadMonitor(threading.Thread):
+    daemon = True
+
+    def __init__(self, interval_in_sec):
+        threading.Thread.__init__(self, name='ThreadMonitor')
+
+        self._logger = util.get_class_logger(self)
+
+        self._interval_in_sec = interval_in_sec
+
+    def run(self):
+        while True:
+            thread_name_list = []
+            for thread in threading.enumerate():
+                thread_name_list.append(thread.name)
+            self._logger.info(
+                "%d active threads: %s",
+                threading.active_count(),
+                ', '.join(thread_name_list))
+            time.sleep(self._interval_in_sec)
+
+
+def _parse_args_and_config(args):
+    parser = _build_option_parser()
+
+    # First, parse options without configuration file.
+    temporary_options, temporary_args = parser.parse_args(args=args)
+    if temporary_args:
+        logging.critical(
+            'Unrecognized positional arguments: %r', temporary_args)
+        sys.exit(1)
+
+    if temporary_options.config_file:
+        try:
+            config_fp = open(temporary_options.config_file, 'r')
+        except IOError, e:
+            logging.critical(
+                'Failed to open configuration file %r: %r',
+                temporary_options.config_file,
+                e)
+            sys.exit(1)
+
+        config_parser = ConfigParser.SafeConfigParser()
+        config_parser.readfp(config_fp)
+        config_fp.close()
+
+        args_from_config = []
+        for name, value in config_parser.items('pywebsocket'):
+            args_from_config.append('--' + name)
+            args_from_config.append(value)
+        if args is None:
+            args = args_from_config
+        else:
+            args = args_from_config + args
+        return parser.parse_args(args=args)
+    else:
+        return temporary_options, temporary_args
+
+
+def _main(args=None):
+    """You can call this function from your own program, but please note that
+    this function has some side-effects that might affect your program. For
+    example, util.wrap_popen3_for_win use in this method replaces implementation
+    of os.popen3.
+    """
+
+    options, args = _parse_args_and_config(args=args)
+
+    os.chdir(options.document_root)
+
+    _configure_logging(options)
+
+    if options.allow_draft75:
+        logging.warning('--allow_draft75 option is obsolete.')
+
+    if options.strict:
+        logging.warning('--strict option is obsolete.')
+
+    # TODO(tyoshino): Clean up initialization of CGI related values. Move some
+    # of code here to WebSocketRequestHandler class if it's better.
+    options.cgi_directories = []
+    options.is_executable_method = None
+    if options.cgi_paths:
+        options.cgi_directories = options.cgi_paths.split(',')
+        if sys.platform in ('cygwin', 'win32'):
+            cygwin_path = None
+            # For Win32 Python, it is expected that CYGWIN_PATH
+            # is set to a directory of cygwin binaries.
+            # For example, websocket_server.py in Chromium sets CYGWIN_PATH to
+            # full path of third_party/cygwin/bin.
+            if 'CYGWIN_PATH' in os.environ:
+                cygwin_path = os.environ['CYGWIN_PATH']
+            util.wrap_popen3_for_win(cygwin_path)
+
+            def __check_script(scriptpath):
+                return util.get_script_interp(scriptpath, cygwin_path)
+
+            options.is_executable_method = __check_script
+
+    if options.use_tls:
+        if options.tls_module is None:
+            if _import_ssl():
+                options.tls_module = _TLS_BY_STANDARD_MODULE
+                logging.debug('Using ssl module')
+            elif _import_pyopenssl():
+                options.tls_module = _TLS_BY_PYOPENSSL
+                logging.debug('Using pyOpenSSL module')
+            else:
+                logging.critical(
+                        'TLS support requires ssl or pyOpenSSL module.')
+                sys.exit(1)
+        elif options.tls_module == _TLS_BY_STANDARD_MODULE:
+            if not _import_ssl():
+                logging.critical('ssl module is not available')
+                sys.exit(1)
+        elif options.tls_module == _TLS_BY_PYOPENSSL:
+            if not _import_pyopenssl():
+                logging.critical('pyOpenSSL module is not available')
+                sys.exit(1)
+        else:
+            logging.critical('Invalid --tls-module option: %r',
+                             options.tls_module)
+            sys.exit(1)
+
+        if not options.private_key or not options.certificate:
+            logging.critical(
+                    'To use TLS, specify private_key and certificate.')
+            sys.exit(1)
+
+        if (options.tls_client_cert_optional and
+            not options.tls_client_auth):
+            logging.critical('Client authentication must be enabled to '
+                             'specify tls_client_cert_optional')
+            sys.exit(1)
+    else:
+        if options.tls_module is not None:
+            logging.critical('Use --tls-module option only together with '
+                             '--use-tls option.')
+            sys.exit(1)
+
+        if options.tls_client_auth:
+            logging.critical('TLS must be enabled for client authentication.')
+            sys.exit(1)
+
+        if options.tls_client_cert_optional:
+            logging.critical('TLS must be enabled for client authentication.')
+            sys.exit(1)
+
+    if not options.scan_dir:
+        options.scan_dir = options.websock_handlers
+
+    if options.use_basic_auth:
+        options.basic_auth_credential = 'Basic ' + base64.b64encode(
+            options.basic_auth_credential)
+
+    try:
+        if options.thread_monitor_interval_in_sec > 0:
+            # Run a thread monitor to show the status of server threads for
+            # debugging.
+            ThreadMonitor(options.thread_monitor_interval_in_sec).start()
+
+        server = WebSocketServer(options)
+        server.serve_forever()
+    except Exception, e:
+        logging.critical('mod_pywebsocket: %s' % e)
+        logging.critical('mod_pywebsocket: %s' % util.get_stack_trace())
+        sys.exit(1)
+
+
+if __name__ == '__main__':
+    _main(sys.argv[1:])
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/mod_pywebsocket/stream.py b/tools/pywebsocket/mod_pywebsocket/stream.py
new file mode 100644
index 0000000..a7d3136
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/stream.py
@@ -0,0 +1,56 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""This file exports public symbols."""
+
+
+from mod_pywebsocket._stream_base import BadOperationException
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import InvalidFrameException
+from mod_pywebsocket._stream_base import InvalidUTF8Exception
+from mod_pywebsocket._stream_base import UnsupportedFrameException
+from mod_pywebsocket._stream_hixie75 import StreamHixie75
+from mod_pywebsocket._stream_hybi import Frame
+from mod_pywebsocket._stream_hybi import Stream
+from mod_pywebsocket._stream_hybi import StreamOptions
+
+# These methods are intended to be used by WebSocket client developers to have
+# their implementations receive broken data in tests.
+from mod_pywebsocket._stream_hybi import create_close_frame
+from mod_pywebsocket._stream_hybi import create_header
+from mod_pywebsocket._stream_hybi import create_length_header
+from mod_pywebsocket._stream_hybi import create_ping_frame
+from mod_pywebsocket._stream_hybi import create_pong_frame
+from mod_pywebsocket._stream_hybi import create_binary_frame
+from mod_pywebsocket._stream_hybi import create_text_frame
+from mod_pywebsocket._stream_hybi import create_closing_handshake_body
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/mod_pywebsocket/util.py b/tools/pywebsocket/mod_pywebsocket/util.py
new file mode 100644
index 0000000..268d99e
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/util.py
@@ -0,0 +1,424 @@
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""WebSocket utilities."""
+
+
+import array
+import errno
+
+# Import hash classes from a module available and recommended for each Python
+# version and re-export those symbol. Use sha and md5 module in Python 2.4, and
+# hashlib module in Python 2.6.
+try:
+    import hashlib
+    md5_hash = hashlib.md5
+    sha1_hash = hashlib.sha1
+except ImportError:
+    import md5
+    import sha
+    md5_hash = md5.md5
+    sha1_hash = sha.sha
+
+import StringIO
+import logging
+import os
+import re
+import socket
+import traceback
+import zlib
+
+try:
+    from mod_pywebsocket import fast_masking
+except ImportError:
+    pass
+
+
+def get_stack_trace():
+    """Get the current stack trace as string.
+
+    This is needed to support Python 2.3.
+    TODO: Remove this when we only support Python 2.4 and above.
+          Use traceback.format_exc instead.
+    """
+    out = StringIO.StringIO()
+    traceback.print_exc(file=out)
+    return out.getvalue()
+
+
+def prepend_message_to_exception(message, exc):
+    """Prepend message to the exception."""
+    exc.args = (message + str(exc),)
+    return
+
+
+def __translate_interp(interp, cygwin_path):
+    """Translate interp program path for Win32 python to run cygwin program
+    (e.g. perl).  Note that it doesn't support path that contains space,
+    which is typically true for Unix, where #!-script is written.
+    For Win32 python, cygwin_path is a directory of cygwin binaries.
+
+    Args:
+      interp: interp command line
+      cygwin_path: directory name of cygwin binary, or None
+    Returns:
+      translated interp command line.
+    """
+    if not cygwin_path:
+        return interp
+    m = re.match('^[^ ]*/([^ ]+)( .*)?', interp)
+    if m:
+        cmd = os.path.join(cygwin_path, m.group(1))
+        return cmd + m.group(2)
+    return interp
+
+
+def get_script_interp(script_path, cygwin_path=None):
+    r"""Get #!-interpreter command line from the script.
+
+    It also fixes command path.  When Cygwin Python is used, e.g. in WebKit,
+    it could run "/usr/bin/perl -wT hello.pl".
+    When Win32 Python is used, e.g. in Chromium, it couldn't.  So, fix
+    "/usr/bin/perl" to "<cygwin_path>\perl.exe".
+
+    Args:
+      script_path: pathname of the script
+      cygwin_path: directory name of cygwin binary, or None
+    Returns:
+      #!-interpreter command line, or None if it is not #!-script.
+    """
+    fp = open(script_path)
+    line = fp.readline()
+    fp.close()
+    m = re.match('^#!(.*)', line)
+    if m:
+        return __translate_interp(m.group(1), cygwin_path)
+    return None
+
+
+def wrap_popen3_for_win(cygwin_path):
+    """Wrap popen3 to support #!-script on Windows.
+
+    Args:
+      cygwin_path:  path for cygwin binary if command path is needed to be
+                    translated.  None if no translation required.
+    """
+    __orig_popen3 = os.popen3
+
+    def __wrap_popen3(cmd, mode='t', bufsize=-1):
+        cmdline = cmd.split(' ')
+        interp = get_script_interp(cmdline[0], cygwin_path)
+        if interp:
+            cmd = interp + ' ' + cmd
+        return __orig_popen3(cmd, mode, bufsize)
+
+    os.popen3 = __wrap_popen3
+
+
+def hexify(s):
+    return ' '.join(map(lambda x: '%02x' % ord(x), s))
+
+
+def get_class_logger(o):
+    """Return the logging class information."""
+    return logging.getLogger(
+        '%s.%s' % (o.__class__.__module__, o.__class__.__name__))
+
+
+class NoopMasker(object):
+    """A NoOp masking object.
+
+    This has the same interface as RepeatedXorMasker but just returns
+    the string passed in without making any change.
+    """
+
+    def __init__(self):
+        """NoOp."""
+        pass
+
+    def mask(self, s):
+        """NoOp."""
+        return s
+
+
+class RepeatedXorMasker(object):
+
+    """A masking object that applies XOR on the string.
+
+    Applies XOR on the string given to mask method with the masking bytes
+    given to the constructor repeatedly. This object remembers the position
+    in the masking bytes the last mask method call ended and resumes from
+    that point on the next mask method call.
+    """
+
+    def __init__(self, masking_key):
+        self._masking_key = masking_key
+        self._masking_key_index = 0
+
+    def _mask_using_swig(self, s):
+        """Perform the mask via SWIG."""
+        masked_data = fast_masking.mask(
+                s, self._masking_key, self._masking_key_index)
+        self._masking_key_index = (
+                (self._masking_key_index + len(s)) % len(self._masking_key))
+        return masked_data
+
+    def _mask_using_array(self, s):
+        """Perform the mask via python."""
+        result = array.array('B')
+        result.fromstring(s)
+
+        # Use temporary local variables to eliminate the cost to access
+        # attributes
+        masking_key = map(ord, self._masking_key)
+        masking_key_size = len(masking_key)
+        masking_key_index = self._masking_key_index
+
+        for i in xrange(len(result)):
+            result[i] ^= masking_key[masking_key_index]
+            masking_key_index = (masking_key_index + 1) % masking_key_size
+
+        self._masking_key_index = masking_key_index
+
+        return result.tostring()
+
+    if 'fast_masking' in globals():
+        mask = _mask_using_swig
+    else:
+        mask = _mask_using_array
+
+
+# By making wbits option negative, we can suppress CMF/FLG (2 octet) and
+# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as
+# deflate library. DICTID won't be added as far as we don't set dictionary.
+# LZ77 window of 32K will be used for both compression and decompression.
+# For decompression, we can just use 32K to cover any windows size. For
+# compression, we use 32K so receivers must use 32K.
+#
+# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
+# to decode.
+#
+# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
+# Python. See also RFC1950 (ZLIB 3.3).
+
+
+class _Deflater(object):
+
+    def __init__(self, window_bits):
+        self._logger = get_class_logger(self)
+
+        self._compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits)
+
+    def compress(self, bytes):
+        compressed_bytes = self._compress.compress(bytes)
+        self._logger.debug('Compress input %r', bytes)
+        self._logger.debug('Compress result %r', compressed_bytes)
+        return compressed_bytes
+
+    def compress_and_flush(self, bytes):
+        compressed_bytes = self._compress.compress(bytes)
+        compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
+        self._logger.debug('Compress input %r', bytes)
+        self._logger.debug('Compress result %r', compressed_bytes)
+        return compressed_bytes
+
+    def compress_and_finish(self, bytes):
+        compressed_bytes = self._compress.compress(bytes)
+        compressed_bytes += self._compress.flush(zlib.Z_FINISH)
+        self._logger.debug('Compress input %r', bytes)
+        self._logger.debug('Compress result %r', compressed_bytes)
+        return compressed_bytes
+
+
+class _Inflater(object):
+
+    def __init__(self, window_bits):
+        self._logger = get_class_logger(self)
+        self._window_bits = window_bits
+
+        self._unconsumed = ''
+
+        self.reset()
+
+    def decompress(self, size):
+        if not (size == -1 or size > 0):
+            raise Exception('size must be -1 or positive')
+
+        data = ''
+
+        while True:
+            if size == -1:
+                data += self._decompress.decompress(self._unconsumed)
+                # See Python bug http://bugs.python.org/issue12050 to
+                # understand why the same code cannot be used for updating
+                # self._unconsumed for here and else block.
+                self._unconsumed = ''
+            else:
+                data += self._decompress.decompress(
+                    self._unconsumed, size - len(data))
+                self._unconsumed = self._decompress.unconsumed_tail
+            if self._decompress.unused_data:
+                # Encountered a last block (i.e. a block with BFINAL = 1) and
+                # found a new stream (unused_data). We cannot use the same
+                # zlib.Decompress object for the new stream. Create a new
+                # Decompress object to decompress the new one.
+                #
+                # It's fine to ignore unconsumed_tail if unused_data is not
+                # empty.
+                self._unconsumed = self._decompress.unused_data
+                self.reset()
+                if size >= 0 and len(data) == size:
+                    # data is filled. Don't call decompress again.
+                    break
+                else:
+                    # Re-invoke Decompress.decompress to try to decompress all
+                    # available bytes before invoking read which blocks until
+                    # any new byte is available.
+                    continue
+            else:
+                # Here, since unused_data is empty, even if unconsumed_tail is
+                # not empty, bytes of requested length are already in data. We
+                # don't have to "continue" here.
+                break
+
+        if data:
+            self._logger.debug('Decompressed %r', data)
+        return data
+
+    def append(self, data):
+        self._logger.debug('Appended %r', data)
+        self._unconsumed += data
+
+    def reset(self):
+        self._logger.debug('Reset')
+        self._decompress = zlib.decompressobj(-self._window_bits)
+
+
+# Compresses/decompresses given octets using the method introduced in RFC1979.
+
+
+class _RFC1979Deflater(object):
+    """A compressor class that applies DEFLATE to given byte sequence and
+    flushes using the algorithm described in the RFC1979 section 2.1.
+    """
+
+    def __init__(self, window_bits, no_context_takeover):
+        self._deflater = None
+        if window_bits is None:
+            window_bits = zlib.MAX_WBITS
+        self._window_bits = window_bits
+        self._no_context_takeover = no_context_takeover
+
+    def filter(self, bytes, end=True, bfinal=False):
+        if self._deflater is None:
+            self._deflater = _Deflater(self._window_bits)
+
+        if bfinal:
+            result = self._deflater.compress_and_finish(bytes)
+            # Add a padding block with BFINAL = 0 and BTYPE = 0.
+            result = result + chr(0)
+            self._deflater = None
+            return result
+
+        result = self._deflater.compress_and_flush(bytes)
+        if end:
+            # Strip last 4 octets which is LEN and NLEN field of a
+            # non-compressed block added for Z_SYNC_FLUSH.
+            result = result[:-4]
+
+        if self._no_context_takeover and end:
+            self._deflater = None
+
+        return result
+
+
+class _RFC1979Inflater(object):
+    """A decompressor class a la RFC1979.
+
+    A decompressor class for byte sequence compressed and flushed following
+    the algorithm described in the RFC1979 section 2.1.
+    """
+
+    def __init__(self, window_bits=zlib.MAX_WBITS):
+        self._inflater = _Inflater(window_bits)
+
+    def filter(self, bytes):
+        # Restore stripped LEN and NLEN field of a non-compressed block added
+        # for Z_SYNC_FLUSH.
+        self._inflater.append(bytes + '\x00\x00\xff\xff')
+        return self._inflater.decompress(-1)
+
+
+class DeflateSocket(object):
+    """A wrapper class for socket object to intercept send and recv to perform
+    deflate compression and decompression transparently.
+    """
+
+    # Size of the buffer passed to recv to receive compressed data.
+    _RECV_SIZE = 4096
+
+    def __init__(self, socket):
+        self._socket = socket
+
+        self._logger = get_class_logger(self)
+
+        self._deflater = _Deflater(zlib.MAX_WBITS)
+        self._inflater = _Inflater(zlib.MAX_WBITS)
+
+    def recv(self, size):
+        """Receives data from the socket specified on the construction up
+        to the specified size. Once any data is available, returns it even
+        if it's smaller than the specified size.
+        """
+
+        # TODO(tyoshino): Allow call with size=0. It should block until any
+        # decompressed data is available.
+        if size <= 0:
+            raise Exception('Non-positive size passed')
+        while True:
+            data = self._inflater.decompress(size)
+            if len(data) != 0:
+                return data
+
+            read_data = self._socket.recv(DeflateSocket._RECV_SIZE)
+            if not read_data:
+                return ''
+            self._inflater.append(read_data)
+
+    def sendall(self, bytes):
+        self.send(bytes)
+
+    def send(self, bytes):
+        self._socket.sendall(self._deflater.compress_and_flush(bytes))
+        return len(bytes)
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/mod_pywebsocket/xhr_benchmark_handler.py b/tools/pywebsocket/mod_pywebsocket/xhr_benchmark_handler.py
new file mode 100644
index 0000000..c62f154
--- /dev/null
+++ b/tools/pywebsocket/mod_pywebsocket/xhr_benchmark_handler.py
@@ -0,0 +1,121 @@
+# Copyright 2014 Google Inc. All rights reserved.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the COPYING file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+
+from mod_pywebsocket import util
+
+
+class XHRBenchmarkHandler(object):
+    def __init__(self, headers, rfile, wfile):
+        self._logger = util.get_class_logger(self)
+
+        self.headers = headers
+        self.rfile = rfile
+        self.wfile = wfile
+
+    def do_send(self):
+        content_length = int(self.headers.getheader('Content-Length'))
+
+        self._logger.debug('Requested to receive %s bytes', content_length)
+
+        RECEIVE_BLOCK_SIZE = 1024 * 1024
+
+        bytes_to_receive = content_length
+        while bytes_to_receive > 0:
+            bytes_to_receive_in_this_loop = bytes_to_receive
+            if bytes_to_receive_in_this_loop > RECEIVE_BLOCK_SIZE:
+                bytes_to_receive_in_this_loop = RECEIVE_BLOCK_SIZE
+            received_data = self.rfile.read(bytes_to_receive_in_this_loop)
+            if received_data != ('a' * bytes_to_receive_in_this_loop):
+                self._logger.debug('Request body verification failed')
+                return
+            bytes_to_receive -= len(received_data)
+        if bytes_to_receive < 0:
+            self._logger.debug('Received %d more bytes than expected' %
+                               (-bytes_to_receive))
+            return
+
+        # Return the number of received bytes back to the client.
+        response_body = '%d' % content_length
+        self.wfile.write(
+            'HTTP/1.1 200 OK\r\n'
+            'Access-Control-Allow-Origin: *\r\n'
+            'Content-Type: text/html\r\n'
+            'Content-Length: %d\r\n'
+            '\r\n%s' % (len(response_body), response_body))
+        self.wfile.flush()
+
+    def do_receive_and_parse(self):
+        content_length = int(self.headers.getheader('Content-Length'))
+        request_body = self.rfile.read(content_length)
+
+        request_array = request_body.split(' ')
+        if len(request_array) < 2:
+            self._logger.debug('Malformed request body: %r', request_body)
+            return
+
+        # Parse the size parameter.
+        bytes_to_send = request_array[0]
+        try:
+            bytes_to_send = int(bytes_to_send)
+        except ValueError, e:
+            self._logger.debug('Malformed size parameter: %r', bytes_to_send)
+            return
+
+        # Parse the transfer encoding parameter.
+        chunked_mode = False
+        mode_parameter = request_array[1]
+        if mode_parameter == 'chunked':
+            self._logger.debug('Requested chunked transfer encoding')
+            chunked_mode = True
+        elif mode_parameter != 'none':
+            self._logger.debug('Invalid mode parameter: %r', mode_parameter)
+            return
+
+        self.do_receive(bytes_to_send, chunked_mode, False)
+
+    def do_receive(self, bytes_to_send, chunked_mode, enable_cache):
+        self._logger.debug(
+            'Requested to send %s bytes (chunked: %s, cache: %s)',
+            bytes_to_send, chunked_mode, enable_cache)
+        # Write a header
+        response_header = (
+            'HTTP/1.1 200 OK\r\n'
+            'Access-Control-Allow-Origin: *\r\n'
+            'Content-Type: application/octet-stream\r\n')
+        if enable_cache:
+            response_header += 'Cache-Control: private, max-age=10\r\n'
+        else:
+            response_header += \
+                'Cache-Control: no-cache, no-store, must-revalidate\r\n'
+        if chunked_mode:
+            response_header += 'Transfer-Encoding: chunked\r\n\r\n'
+        else:
+            response_header += (
+                'Content-Length: %d\r\n\r\n' % bytes_to_send)
+        self.wfile.write(response_header)
+        self.wfile.flush()
+
+        # Write a body
+        SEND_BLOCK_SIZE = 1024 * 1024
+
+        while bytes_to_send > 0:
+            bytes_to_send_in_this_loop = bytes_to_send
+            if bytes_to_send_in_this_loop > SEND_BLOCK_SIZE:
+                bytes_to_send_in_this_loop = SEND_BLOCK_SIZE
+
+            if chunked_mode:
+                self.wfile.write('%x\r\n' % bytes_to_send_in_this_loop)
+            self.wfile.write('a' * bytes_to_send_in_this_loop)
+            if chunked_mode:
+                self.wfile.write('\r\n')
+            self.wfile.flush()
+
+            bytes_to_send -= bytes_to_send_in_this_loop
+
+        if chunked_mode:
+            self.wfile.write('0\r\n\r\n')
+            self.wfile.flush()
diff --git a/tools/pywebsocket/setup.py b/tools/pywebsocket/setup.py
new file mode 100755
index 0000000..935d6b1
--- /dev/null
+++ b/tools/pywebsocket/setup.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+#
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Set up script for mod_pywebsocket.
+"""
+
+
+from distutils.core import setup, Extension
+import sys
+
+
+_PACKAGE_NAME = 'mod_pywebsocket'
+
+# Build and use a C++ extension for faster masking. SWIG is required.
+_USE_FAST_MASKING = False
+
+if sys.version < '2.3':
+    print >> sys.stderr, '%s requires Python 2.3 or later.' % _PACKAGE_NAME
+    sys.exit(1)
+
+if _USE_FAST_MASKING:
+    setup(ext_modules=[
+                  Extension(
+                          'mod_pywebsocket/_fast_masking',
+                          ['mod_pywebsocket/fast_masking.i'],
+                          swig_opts=['-c++'])])
+
+setup(author='Yuzo Fujishima',
+      author_email='yuzo@chromium.org',
+      description='WebSocket extension for Apache HTTP Server.',
+      long_description=(
+              'mod_pywebsocket is an Apache HTTP Server extension for '
+              'the WebSocket Protocol (RFC 6455). '
+              'See mod_pywebsocket/__init__.py for more detail.'),
+      license='See LICENSE',
+      name=_PACKAGE_NAME,
+      packages=[_PACKAGE_NAME, _PACKAGE_NAME + '.handshake'],
+      url='http://code.google.com/p/pywebsocket/',
+      # See the source of distutils.version, distutils.versionpredicate and
+      # distutils.dist to understand how to name version numbers.
+      version='0.7.9',
+      )
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/example/benchmark.html b/tools/pywebsocket/src/example/benchmark.html
deleted file mode 100644
index 3a21817..0000000
--- a/tools/pywebsocket/src/example/benchmark.html
+++ /dev/null
@@ -1,203 +0,0 @@
-<!--
-Copyright 2013, Google Inc.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-    * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--->
-
-<html>
-<head>
-<title>WebSocket benchmark</title>
-<script src="util_main.js"></script>
-<script src="util.js"></script>
-<script src="benchmark.js"></script>
-<script>
-var addressBox = null;
-
-function getConfig() {
-  return {
-    prefixUrl: addressBox.value,
-    printSize: getBoolFromCheckBox('printsize'),
-    numSockets: getIntFromInput('numsockets'),
-    // Initial size of messages.
-    numIterations: getIntFromInput('numiterations'),
-    numWarmUpIterations: getIntFromInput('numwarmupiterations'),
-    startSize: getIntFromInput('startsize'),
-    // Stops benchmark when the size of message exceeds this threshold.
-    stopThreshold: getIntFromInput('stopthreshold'),
-    // If the size of each message is small, send/receive multiple messages
-    // until the sum of sizes reaches this threshold.
-    minTotal: getIntFromInput('mintotal'),
-    multipliers: getIntArrayFromInput('multipliers'),
-    verifyData: getBoolFromCheckBox('verifydata')
-  };
-}
-
-var worker = new Worker('benchmark.js');
-worker.onmessage = onMessage;
-
-function onSendBenchmark() {
-  var config = getConfig();
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'sendBenchmark', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    sendBenchmark(config);
-  }
-}
-
-function onReceiveBenchmark() {
-  var config = getConfig();
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'receiveBenchmark', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    receiveBenchmark(config);
-  }
-}
-
-function onBatchBenchmark() {
-  var config = getConfig();
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'batchBenchmark', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    batchBenchmark(config);
-  }
-}
-
-function onStop() {
-  var config = getConfig();
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'stop', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    stop(config);
-  }
-}
-function init() {
-  addressBox = document.getElementById('address');
-  logBox = document.getElementById('log');
-
-  summaryBox = document.getElementById('summary');
-
-  var scheme = window.location.protocol == 'https:' ? 'wss://' : 'ws://';
-  var defaultAddress = scheme + window.location.host + '/benchmark_helper';
-
-  addressBox.value = defaultAddress;
-
-  addToLog(window.navigator.userAgent.toLowerCase());
-  addToSummary(window.navigator.userAgent.toLowerCase());
-
-  if (!('WebSocket' in window)) {
-    addToLog('WebSocket is not available');
-  }
-}
-</script>
-</head>
-<body onload="init()">
-
-<div id="benchmark_div">
-  url <input type="text" id="address" size="40">
-  <input type="button" value="send" onclick="onSendBenchmark()">
-  <input type="button" value="receive" onclick="onReceiveBenchmark()">
-  <input type="button" value="batch" onclick="onBatchBenchmark()">
-  <input type="button" value="stop" onclick="onStop()">
-
-  <br/>
-
-  <input type="checkbox" id="printsize" checked>
-  <label for="printsize">Print size and time per message</label>
-  <input type="checkbox" id="verifydata" checked>
-  <label for="verifydata">Verify data</label>
-  <input type="checkbox" id="worker">
-  <label for="worker">Run on worker</label>
-
-  <br/>
-
-  Parameters:
-
-  <br/>
-
-  <table>
-    <tr>
-      <td>Num sockets</td>
-      <td><input type="text" id="numsockets" value="1"></td>
-    </tr>
-    <tr>
-      <td>Number of iterations</td>
-      <td><input type="text" id="numiterations" value="1"></td>
-    </tr>
-    <tr>
-      <td>Number of warm-up iterations</td>
-      <td><input type="text" id="numwarmupiterations" value="0"></td>
-    </tr>
-    <tr>
-      <td>Start size</td>
-      <td><input type="text" id="startsize" value="10240"></td>
-    </tr>
-    <tr>
-      <td>Stop threshold</td>
-      <td><input type="text" id="stopthreshold" value="102400000"></td>
-    </tr>
-    <tr>
-      <td>Minimum total</td>
-      <td><input type="text" id="mintotal" value="102400000"></td>
-    </tr>
-    <tr>
-      <td>Multipliers</td>
-      <td><input type="text" id="multipliers" value="5, 2"></td>
-    </tr>
-  </table>
-</div>
-
-<div id="log_div">
-  <textarea
-      id="log" rows="20" style="width: 100%" readonly></textarea>
-</div>
-<div id="summary_div">
-  Summary
-  <textarea
-      id="summary" rows="20" style="width: 100%" readonly></textarea>
-</div>
-
-Note: Effect of RTT is not eliminated.
-
-</body>
-</html>
diff --git a/tools/pywebsocket/src/example/benchmark.js b/tools/pywebsocket/src/example/benchmark.js
deleted file mode 100644
index d347ae9..0000000
--- a/tools/pywebsocket/src/example/benchmark.js
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright 2014 Google Inc. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the COPYING file or at
-// https://developers.google.com/open-source/licenses/bsd
-
-if (typeof importScripts !== "undefined") {
-  // Running on a worker
-  importScripts('util.js', 'util_worker.js');
-}
-
-// Namespace for holding globals.
-var benchmark = {startTimeInMs: 0};
-
-var sockets = [];
-var numEstablishedSockets = 0;
-
-var timerID = null;
-
-function destroySocket(socket) {
-  socket.onopen = null;
-  socket.onmessage = null;
-  socket.onerror = null;
-  socket.onclose = null;
-  socket.close();
-}
-
-function destroyAllSockets() {
-  for (var i = 0; i < sockets.length; ++i) {
-    destroySocket(sockets[i]);
-  }
-  sockets = [];
-}
-
-function sendBenchmarkStep(size, config) {
-  timerID = null;
-
-  var totalSize = 0;
-  var totalReplied = 0;
-
-  var onMessageHandler = function(event) {
-    if (!verifyAcknowledgement(config, event.data, size)) {
-      destroyAllSockets();
-      return;
-    }
-
-    totalReplied += size;
-
-    if (totalReplied < totalSize) {
-      return;
-    }
-
-    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
-
-    runNextTask(config);
-  };
-
-  for (var i = 0; i < sockets.length; ++i) {
-    var socket = sockets[i];
-    socket.onmessage = onMessageHandler;
-  }
-
-  var dataArray = [];
-
-  while (totalSize < config.minTotal) {
-    var buffer = new ArrayBuffer(size);
-
-    fillArrayBuffer(buffer, 0x61);
-
-    dataArray.push(buffer);
-    totalSize += size;
-  }
-
-  benchmark.startTimeInMs = getTimeStamp();
-
-  totalSize = 0;
-
-  var socketIndex = 0;
-  var dataIndex = 0;
-  while (totalSize < config.minTotal) {
-    var command = ['send'];
-    command.push(config.verifyData ? '1' : '0');
-    sockets[socketIndex].send(command.join(' '));
-    sockets[socketIndex].send(dataArray[dataIndex]);
-    socketIndex = (socketIndex + 1) % sockets.length;
-
-    totalSize += size;
-    ++dataIndex;
-  }
-}
-
-function receiveBenchmarkStep(size, config) {
-  timerID = null;
-
-  var totalSize = 0;
-  var totalReplied = 0;
-
-  var onMessageHandler = function(event) {
-    var bytesReceived = event.data.byteLength;
-    if (bytesReceived != size) {
-      config.addToLog('Expected ' + size + 'B but received ' +
-          bytesReceived + 'B');
-      destroyAllSockets();
-      return;
-    }
-
-    if (config.verifyData && !verifyArrayBuffer(event.data, 0x61)) {
-      config.addToLog('Response verification failed');
-      destroyAllSockets();
-      return;
-    }
-
-    totalReplied += bytesReceived;
-
-    if (totalReplied < totalSize) {
-      return;
-    }
-
-    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
-
-    runNextTask(config);
-  };
-
-  for (var i = 0; i < sockets.length; ++i) {
-    var socket = sockets[i];
-    socket.binaryType = 'arraybuffer';
-    socket.onmessage = onMessageHandler;
-  }
-
-  benchmark.startTimeInMs = getTimeStamp();
-
-  var socketIndex = 0;
-  while (totalSize < config.minTotal) {
-    sockets[socketIndex].send('receive ' + size);
-    socketIndex = (socketIndex + 1) % sockets.length;
-
-    totalSize += size;
-  }
-}
-
-function createSocket(config) {
-  // TODO(tyoshino): Add TCP warm up.
-  var url = config.prefixUrl;
-
-  config.addToLog('Connect ' + url);
-
-  var socket = new WebSocket(url);
-  socket.onmessage = function(event) {
-    config.addToLog('Unexpected message received. Aborting.');
-  };
-  socket.onerror = function() {
-    config.addToLog('Error');
-  };
-  socket.onclose = function(event) {
-    config.addToLog('Closed');
-  };
-  return socket;
-}
-
-var tasks = [];
-
-function startBenchmark(config) {
-  clearTimeout(timerID);
-  destroyAllSockets();
-
-  numEstablishedSockets = 0;
-
-  for (var i = 0; i < config.numSockets; ++i) {
-    var socket = createSocket(config);
-    socket.onopen = function() {
-      config.addToLog('Opened');
-
-      ++numEstablishedSockets;
-
-      if (numEstablishedSockets == sockets.length) {
-        runNextTask(config);
-      }
-    };
-    sockets.push(socket);
-  }
-}
-
-function runNextTask(config) {
-  var task = tasks.shift();
-  if (task == undefined) {
-    config.addToLog('Finished');
-    destroyAllSockets();
-    return;
-  }
-  timerID = setTimeout(task, 0);
-}
-
-function buildLegendString(config) {
-  var legend = ''
-  if (config.printSize)
-    legend = 'Message size in KiB, Time/message in ms, ';
-  legend += 'Speed in kB/s';
-  return legend;
-}
-
-function getConfigString(config) {
-  return '(WebSocket' +
-    ', ' + (typeof importScripts !== "undefined" ? 'Worker' : 'Main') +
-    ', numSockets=' + config.numSockets +
-    ', numIterations=' + config.numIterations +
-    ', verifyData=' + config.verifyData +
-    ', minTotal=' + config.minTotal +
-    ', numWarmUpIterations=' + config.numWarmUpIterations +
-    ')';
-}
-
-function addTasks(config, stepFunc) {
-  for (var i = 0;
-      i < config.numWarmUpIterations + config.numIterations; ++i) {
-    // Ignore the first |config.numWarmUpIterations| iterations.
-    if (i == config.numWarmUpIterations)
-      addResultClearingTask(config);
-
-    var multiplierIndex = 0;
-    for (var size = config.startSize;
-         size <= config.stopThreshold;
-         ++multiplierIndex) {
-      var task = stepFunc.bind(
-          null,
-          size,
-          config);
-      tasks.push(task);
-      size *= config.multipliers[
-          multiplierIndex % config.multipliers.length];
-    }
-  }
-}
-
-function addResultReportingTask(config, title) {
-  tasks.push(function(){
-      timerID = null;
-      config.addToSummary(title);
-      reportAverageData(config);
-      clearAverageData();
-      runNextTask(config);
-  });
-}
-
-function addResultClearingTask(config) {
-  tasks.push(function(){
-      timerID = null;
-      clearAverageData();
-      runNextTask(config);
-  });
-}
-
-function sendBenchmark(config) {
-  config.addToLog('Send benchmark');
-  config.addToLog(buildLegendString(config));
-
-  tasks = [];
-  clearAverageData();
-  addTasks(config, sendBenchmarkStep);
-  addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
-  startBenchmark(config);
-}
-
-function receiveBenchmark(config) {
-  config.addToLog('Receive benchmark');
-  config.addToLog(buildLegendString(config));
-
-  tasks = [];
-  clearAverageData();
-  addTasks(config, receiveBenchmarkStep);
-  addResultReportingTask(config,
-      'Receive Benchmark ' + getConfigString(config));
-  startBenchmark(config);
-}
-
-function batchBenchmark(config) {
-  config.addToLog('Batch benchmark');
-  config.addToLog(buildLegendString(config));
-
-  tasks = [];
-  clearAverageData();
-  addTasks(config, sendBenchmarkStep);
-  addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
-  addTasks(config, receiveBenchmarkStep);
-  addResultReportingTask(config, 'Receive Benchmark ' +
-      getConfigString(config));
-  startBenchmark(config);
-}
-
-function stop(config) {
-  clearTimeout(timerID);
-  timerID = null;
-  config.addToLog('Stopped');
-  destroyAllSockets();
-}
-
-onmessage = function (message) {
-  var config = message.data.config;
-  config.addToLog = workerAddToLog;
-  config.addToSummary = workerAddToSummary;
-  config.measureValue = workerMeasureValue;
-  if (message.data.type === 'sendBenchmark')
-    sendBenchmark(config);
-  else if (message.data.type === 'receiveBenchmark')
-    receiveBenchmark(config);
-  else if (message.data.type === 'batchBenchmark')
-    batchBenchmark(config);
-  else if (message.data.type === 'stop')
-    stop(config);
-};
diff --git a/tools/pywebsocket/src/example/util.js b/tools/pywebsocket/src/example/util.js
deleted file mode 100644
index a1cad49..0000000
--- a/tools/pywebsocket/src/example/util.js
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2013, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-// Utilities for example applications (for both main and worker thread).
-
-var results = {};
-
-function getTimeStamp() {
-  return Date.now();
-}
-
-function formatResultInKiB(size, timePerMessageInMs, stddevTimePerMessageInMs,
-    speed, printSize) {
-  if (printSize) {
-    return (size / 1024) +
-        '\t' + timePerMessageInMs.toFixed(3) +
-        (stddevTimePerMessageInMs == -1 ?
-            '' :
-            '\t' + stddevTimePerMessageInMs.toFixed(3)) +
-        '\t' + speed.toFixed(3);
-  } else {
-    return speed.toString();
-  }
-}
-
-function clearAverageData() {
-  results = {};
-}
-
-function reportAverageData(config) {
-  config.addToSummary(
-      'Size[KiB]\tAverage time[ms]\tStddev time[ms]\tSpeed[KB/s]');
-  for (var size in results) {
-    var averageTimePerMessageInMs = results[size].sum_t / results[size].n;
-    var speed = calculateSpeedInKB(size, averageTimePerMessageInMs);
-    // Calculate sample standard deviation
-    var stddevTimePerMessageInMs = Math.sqrt(
-        (results[size].sum_t2 / results[size].n -
-            averageTimePerMessageInMs * averageTimePerMessageInMs) *
-        results[size].n /
-        (results[size].n - 1));
-    config.addToSummary(formatResultInKiB(
-        size, averageTimePerMessageInMs, stddevTimePerMessageInMs, speed,
-        true));
-  }
-}
-
-function calculateSpeedInKB(size, timeSpentInMs) {
-  return Math.round(size / timeSpentInMs * 1000) / 1000;
-}
-
-function calculateAndLogResult(config, size, startTimeInMs, totalSize) {
-  var timeSpentInMs = getTimeStamp() - startTimeInMs;
-  var speed = calculateSpeedInKB(totalSize, timeSpentInMs);
-  var timePerMessageInMs = timeSpentInMs / (totalSize / size);
-  if (!results[size]) {
-    results[size] = {n: 0, sum_t: 0, sum_t2: 0};
-  }
-  config.measureValue(timePerMessageInMs);
-  results[size].n ++;
-  results[size].sum_t += timePerMessageInMs;
-  results[size].sum_t2 += timePerMessageInMs * timePerMessageInMs;
-  config.addToLog(formatResultInKiB(size, timePerMessageInMs, -1, speed,
-      config.printSize));
-}
-
-function fillArrayBuffer(buffer, c) {
-  var i;
-
-  var u32Content = c * 0x01010101;
-
-  var u32Blocks = Math.floor(buffer.byteLength / 4);
-  var u32View = new Uint32Array(buffer, 0, u32Blocks);
-  // length attribute is slow on Chrome. Don't use it for loop condition.
-  for (i = 0; i < u32Blocks; ++i) {
-    u32View[i] = u32Content;
-  }
-
-  // Fraction
-  var u8Blocks = buffer.byteLength - u32Blocks * 4;
-  var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks);
-  for (i = 0; i < u8Blocks; ++i) {
-    u8View[i] = c;
-  }
-}
-
-function verifyArrayBuffer(buffer, expectedChar) {
-  var i;
-
-  var expectedU32Value = expectedChar * 0x01010101;
-
-  var u32Blocks = Math.floor(buffer.byteLength / 4);
-  var u32View = new Uint32Array(buffer, 0, u32Blocks);
-  for (i = 0; i < u32Blocks; ++i) {
-    if (u32View[i] != expectedU32Value) {
-      return false;
-    }
-  }
-
-  var u8Blocks = buffer.byteLength - u32Blocks * 4;
-  var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks);
-  for (i = 0; i < u8Blocks; ++i) {
-    if (u8View[i] != expectedChar) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-function verifyBlob(config, blob, expectedChar, doneCallback) {
-  var reader = new FileReader(blob);
-  reader.onerror = function() {
-    config.addToLog('FileReader Error: ' + reader.error.message);
-    doneCallback(blob.size, false);
-  }
-  reader.onloadend = function() {
-    var result = verifyArrayBuffer(reader.result, expectedChar);
-    doneCallback(blob.size, result);
-  }
-  reader.readAsArrayBuffer(blob);
-}
-
-function verifyAcknowledgement(config, message, size) {
-  if (typeof message != 'string') {
-    config.addToLog('Invalid ack type: ' + typeof message);
-    return false;
-  }
-  var parsedAck = parseInt(message);
-  if (isNaN(parsedAck)) {
-    config.addToLog('Invalid ack value: ' + message);
-    return false;
-  }
-  if (parsedAck != size) {
-    config.addToLog(
-        'Expected ack for ' + size + 'B but received one for ' + parsedAck +
-        'B');
-    return false;
-  }
-
-  return true;
-}
-
-function cloneConfig(obj) {
-  var newObj = {};
-  for (key in obj) {
-    newObj[key] = obj[key];
-  }
-  return newObj;
-}
diff --git a/tools/pywebsocket/src/example/util_main.js b/tools/pywebsocket/src/example/util_main.js
deleted file mode 100644
index b03d1c2..0000000
--- a/tools/pywebsocket/src/example/util_main.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2014 Google Inc. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the COPYING file or at
-// https://developers.google.com/open-source/licenses/bsd
-
-// Utilities for example applications (for the main thread only).
-
-var logBox = null;
-var queuedLog = '';
-
-var summaryBox = null;
-
-function queueLog(log) {
-  queuedLog += log + '\n';
-}
-
-function addToLog(log) {
-  logBox.value += queuedLog;
-  queuedLog = '';
-  logBox.value += log + '\n';
-  logBox.scrollTop = 1000000;
-}
-
-function addToSummary(log) {
-  summaryBox.value += log + '\n';
-  summaryBox.scrollTop = 1000000;
-}
-
-// value: execution time in milliseconds.
-// config.measureValue is intended to be used in Performance Tests.
-// Do nothing here in non-PerformanceTest.
-function measureValue(value) {
-}
-
-function getIntFromInput(id) {
-  return parseInt(document.getElementById(id).value);
-}
-
-function getStringFromRadioBox(name) {
-  var list = document.getElementById('benchmark_form')[name];
-  for (var i = 0; i < list.length; ++i)
-    if (list.item(i).checked)
-      return list.item(i).value;
-  return undefined;
-}
-function getBoolFromCheckBox(id) {
-  return document.getElementById(id).checked;
-}
-
-function getIntArrayFromInput(id) {
-  var strArray = document.getElementById(id).value.split(',');
-  return strArray.map(function(str) { return parseInt(str, 10); });
-}
-
-function onMessage(message) {
-  if (message.data.type === 'addToLog')
-    addToLog(message.data.data);
-  else if (message.data.type === 'addToSummary')
-    addToSummary(message.data.data);
-  else if (message.data.type === 'measureValue')
-    measureValue(message.data.data);
-}
diff --git a/tools/pywebsocket/src/example/util_worker.js b/tools/pywebsocket/src/example/util_worker.js
deleted file mode 100644
index b64f782..0000000
--- a/tools/pywebsocket/src/example/util_worker.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2014 Google Inc. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the COPYING file or at
-// https://developers.google.com/open-source/licenses/bsd
-
-// Utilities for example applications (for the worker threads only).
-
-function workerAddToLog(text) {
-  postMessage({type: 'addToLog', data: text});
-}
-
-function workerAddToSummary(text) {
-  postMessage({type: 'addToSummary', data: text});
-}
-
-function workerMeasureValue(value) {
-  postMessage({type: 'measureValue', data: value});
-}
diff --git a/tools/pywebsocket/src/example/xhr_benchmark.html b/tools/pywebsocket/src/example/xhr_benchmark.html
deleted file mode 100644
index 1862297..0000000
--- a/tools/pywebsocket/src/example/xhr_benchmark.html
+++ /dev/null
@@ -1,222 +0,0 @@
-<!--
-Copyright 2013, Google Inc.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-    * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--->
-
-<html>
-<head>
-<title>XMLHttpRequest benchmark</title>
-<script src="util_main.js"></script>
-<script src="util.js"></script>
-<script src="xhr_benchmark.js"></script>
-<script>
-var addressBox = null;
-
-function getConfig() {
-  return {
-    prefixUrl: addressBox.value,
-    printSize: getBoolFromCheckBox('printsize'),
-    numXHRs: getIntFromInput('numXHRs'),
-    async: getBoolFromCheckBox('async'),
-    // Initial size of messages.
-    numIterations: getIntFromInput('numiterations'),
-    numWarmUpIterations: getIntFromInput('numwarmupiterations'),
-    startSize: getIntFromInput('startsize'),
-    // Stops benchmark when the size of message exceeds this threshold.
-    stopThreshold: getIntFromInput('stopthreshold'),
-    // If the size of each message is small, send/receive multiple messages
-    // until the sum of sizes reaches this threshold.
-    // minTotal: getIntFromInput('mintotal'),
-    // minTotal is not yet implemented on XHR benchmark
-    multipliers: getIntArrayFromInput('multipliers'),
-    verifyData: getBoolFromCheckBox('verifydata')
-  };
-}
-
-var worker = new Worker('xhr_benchmark.js');
-worker.onmessage = onMessage;
-
-function onSendBenchmark() {
-  var config = getConfig();
-  config.dataType = getStringFromRadioBox('datatyperadio');
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'sendBenchmark', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    sendBenchmark(config);
-  }
-}
-
-function onReceiveBenchmark() {
-  var config = getConfig();
-  config.dataType = getStringFromRadioBox('datatyperadio');
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'receiveBenchmark', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    receiveBenchmark(config);
-  }
-}
-
-function onBatchBenchmark() {
-  var config = getConfig();
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'batchBenchmark', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    batchBenchmark(config);
-  }
-}
-
-function onStop() {
-  var config = getConfig();
-
-  if (getBoolFromCheckBox('worker')) {
-    worker.postMessage({type: 'stop', config: config});
-  } else {
-    config.addToLog = addToLog;
-    config.addToSummary = addToSummary;
-    config.measureValue = measureValue;
-    stop(config);
-  }
-}
-
-function init() {
-  addressBox = document.getElementById('address');
-  logBox = document.getElementById('log');
-
-  summaryBox = document.getElementById('summary');
-
-  // Special address of pywebsocket for XHR benchmark.
-  addressBox.value = '/073be001e10950692ccbf3a2ad21c245';
-
-  addToLog(window.navigator.userAgent.toLowerCase());
-  addToSummary(window.navigator.userAgent.toLowerCase());
-}
-</script>
-</head>
-<body onload="init()">
-
-<form id="benchmark_form">
-  url prefix <input type="text" id="address" size="40">
-  <input type="button" value="send" onclick="onSendBenchmark()">
-  <input type="button" value="receive" onclick="onReceiveBenchmark()">
-  <input type="button" value="batch" onclick="onBatchBenchmark()">
-  <input type="button" value="stop" onclick="onStop()">
-
-  <br/>
-
-  <input type="checkbox" id="printsize" checked>
-  <label for="printsize">Print size and time per message</label>
-  <input type="checkbox" id="verifydata" checked>
-  <label for="verifydata">Verify data</label>
-  <input type="checkbox" id="worker">
-  <label for="worker">Run on worker</label>
-  <input type="checkbox" id="async" checked>
-  <label for="async">Async</label><br>
-  (Receive &amp;&amp; Non-Worker &amp;&amp; Sync is not supported by spec)
-
-  <br/>
-
-  Parameters:
-
-  <br/>
-
-  <table>
-    <tr>
-      <td>Num XHRs</td>
-      <td><input type="text" id="numXHRs" value="1"></td>
-    </tr>
-    <tr>
-      <td>Number of iterations</td>
-      <td><input type="text" id="numiterations" value="1"></td>
-    </tr>
-    <tr>
-      <td>Number of warm-up iterations</td>
-      <td><input type="text" id="numwarmupiterations" value="0"></td>
-    </tr>
-    <tr>
-      <td>Start size</td>
-      <td><input type="text" id="startsize" value="10240"></td>
-    </tr>
-    <tr>
-      <td>Stop threshold</td>
-      <td><input type="text" id="stopthreshold" value="102400000"></td>
-    </tr>
-    <tr>
-      <td>Minimum total</td>
-      <td><input type="text" id="mintotal" value="102400000"></td>
-    </tr>
-    <tr>
-      <td>Multipliers</td>
-      <td><input type="text" id="multipliers" value="5, 2"></td>
-    </tr>
-  </table>
-
-  Set data type
-  <input type="radio"
-         name="datatyperadio"
-         id="datatyperadiotext"
-         value="text"
-         checked><label for="datatyperadiotext">text</label>
-  <input type="radio"
-         name="datatyperadio"
-         id="datatyperadioblob"
-         value="blob"
-         ><label for="datatyperadioblob">blob</label>
-  <input type="radio"
-         name="datatyperadio"
-         id="datatyperadioarraybuffer"
-         value="arraybuffer"
-         ><label for="datatyperadioarraybuffer">arraybuffer</label>
-</form>
-
-<div id="log_div">
-  <textarea
-      id="log" rows="20" style="width: 100%" readonly></textarea>
-</div>
-<div id="summary_div">
-  Summary
-  <textarea
-      id="summary" rows="20" style="width: 100%" readonly></textarea>
-</div>
-
-Note: Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.
-
-</body>
-</html>
diff --git a/tools/pywebsocket/src/example/xhr_benchmark.js b/tools/pywebsocket/src/example/xhr_benchmark.js
deleted file mode 100644
index 233c7cb..0000000
--- a/tools/pywebsocket/src/example/xhr_benchmark.js
+++ /dev/null
@@ -1,389 +0,0 @@
-// Copyright 2014 Google Inc. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the COPYING file or at
-// https://developers.google.com/open-source/licenses/bsd
-
-var isWorker = typeof importScripts !== "undefined";
-
-if (isWorker) {
-  // Running on a worker
-  importScripts('util.js', 'util_worker.js');
-}
-
-// Namespace for holding globals.
-var benchmark = {};
-benchmark.startTimeInMs = 0;
-
-var xhrs = [];
-
-var timerID = null;
-
-function destroyAllXHRs() {
-  for (var i = 0; i < xhrs.length; ++i) {
-    xhrs[i].onreadystatechange = null;
-    // Abort XHRs if they are not yet DONE state.
-    // Calling abort() here (i.e. in onreadystatechange handler) 
-    // causes "NetworkError" messages in DevTools in sync mode,
-    // even if it is after transition to DONE state.
-    if (xhrs[i].readyState != XMLHttpRequest.DONE)
-      xhrs[i].abort();
-  }
-  xhrs = [];
-  // gc() might be needed for Chrome/Blob
-}
-
-function repeatString(str, count) {
-  var data = '';
-  var expChunk = str;
-  var remain = count;
-  while (true) {
-    if (remain % 2) {
-      data += expChunk;
-      remain = (remain - 1) / 2;
-    } else {
-      remain /= 2;
-    }
-
-    if (remain == 0)
-      break;
-
-    expChunk = expChunk + expChunk;
-  }
-  return data;
-}
-
-function sendBenchmarkStep(size, config) {
-  timerID = null;
-
-  benchmark.startTimeInMs = null;
-  var totalSize = 0;
-  var totalReplied = 0;
-
-  var onReadyStateChangeHandler = function () {
-    if (this.readyState != this.DONE) {
-      return;
-    }
-
-    if (this.status != 200) {
-      config.addToLog('Failed (status=' + this.status + ')');
-      destroyAllXHRs();
-      return;
-    }
-
-    if (config.verifyData &&
-        !verifyAcknowledgement(config, this.response, size)) {
-      destroyAllXHRs();
-      return;
-    }
-
-    totalReplied += size;
-
-    if (totalReplied < totalSize) {
-      return;
-    }
-
-    if (benchmark.startTimeInMs == null) {
-      config.addToLog('startTimeInMs not set');
-      destroyAllXHRs();
-      return;
-    }
-
-    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
-
-    destroyAllXHRs();
-
-    runNextTask(config);
-  };
-
-  for (var i = 0; i < config.numXHRs; ++i) {
-    var xhr = new XMLHttpRequest();
-    xhr.onreadystatechange = onReadyStateChangeHandler;
-    xhrs.push(xhr);
-  }
-
-  var dataArray = [];
-
-  for (var i = 0; i < xhrs.length; ++i) {
-    var data = null;
-    if (config.dataType == 'arraybuffer' ||
-        config.dataType == 'blob') {
-      data = new ArrayBuffer(size);
-
-      fillArrayBuffer(data, 0x61);
-
-      if (config.dataType == 'blob') {
-        data = new Blob([data]);
-      }
-    } else {
-      data = repeatString('a', size);
-    }
-
-    dataArray.push(data);
-  }
-
-
-  benchmark.startTimeInMs = getTimeStamp();
-  totalSize = size * xhrs.length;
-
-  for (var i = 0; i < xhrs.length; ++i) {
-    var data = dataArray[i];
-    var xhr = xhrs[i];
-    xhr.open('POST', config.prefixUrl + '_send', config.async);
-    xhr.send(data);
-  }
-}
-
-function receiveBenchmarkStep(size, config) {
-  timerID = null;
-
-  benchmark.startTimeInMs = null;
-  var totalSize = 0;
-  var totalReplied = 0;
-
-  var checkResultAndContinue = function (bytesReceived, verificationResult) {
-    if (!verificationResult) {
-      config.addToLog('Response verification failed');
-      destroyAllXHRs();
-      return;
-    }
-
-    totalReplied += bytesReceived;
-
-    if (totalReplied < totalSize) {
-      return;
-    }
-
-    if (benchmark.startTimeInMs == null) {
-      config.addToLog('startTimeInMs not set');
-      destroyAllXHRs();
-      return;
-    }
-
-    calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
-
-    destroyAllXHRs();
-
-    runNextTask(config);
-  }
-
-  var onReadyStateChangeHandler = function () {
-    if (this.readyState != this.DONE) {
-      return;
-    }
-
-    if (this.status != 200) {
-      config.addToLog('Failed (status=' + this.status + ')');
-      destroyAllXHRs();
-      return;
-    }
-
-    var bytesReceived = -1;
-    if (this.responseType == 'arraybuffer') {
-      bytesReceived = this.response.byteLength;
-    } else if (this.responseType == 'blob') {
-      bytesReceived = this.response.size;
-    } else {
-      bytesReceived = this.response.length;
-    }
-    if (bytesReceived != size) {
-      config.addToLog('Expected ' + size +
-          'B but received ' + bytesReceived + 'B');
-      destroyAllXHRs();
-      return;
-    }
-
-    if (this.responseType == 'arraybuffer') {
-      checkResultAndContinue(bytesReceived,
-          !config.verifyData || verifyArrayBuffer(this.response, 0x61));
-    } else if (this.responseType == 'blob') {
-      if (config.verifyData)
-        verifyBlob(config, this.response, 0x61, checkResultAndContinue);
-      else
-        checkResultAndContinue(bytesReceived, true);
-    } else {
-      checkResultAndContinue(
-          bytesReceived,
-          !config.verifyData ||
-              this.response == repeatString('a', this.response.length));
-    }
-  };
-
-  for (var i = 0; i < config.numXHRs; ++i) {
-    var xhr = new XMLHttpRequest();
-    xhr.onreadystatechange = onReadyStateChangeHandler;
-    xhrs.push(xhr);
-  }
-
-  benchmark.startTimeInMs = getTimeStamp();
-  totalSize = size * xhrs.length;
-
-  for (var i = 0; i < xhrs.length; ++i) {
-    var xhr = xhrs[i];
-    xhr.open('POST', config.prefixUrl + '_receive', config.async);
-    xhr.responseType = config.dataType;
-    xhr.send(size + ' none');
-  }
-}
-
-
-function getConfigString(config) {
-  return '(' + config.dataType +
-    ', verifyData=' + config.verifyData +
-    ', ' + (isWorker ? 'Worker' : 'Main') +
-    ', ' + (config.async ? 'Async' : 'Sync') +
-    ', numXHRs=' + config.numXHRs +
-    ', numIterations=' + config.numIterations +
-    ', numWarmUpIterations=' + config.numWarmUpIterations +
-    ')';
-}
-
-function startBenchmark(config) {
-  clearTimeout(timerID);
-  destroyAllXHRs();
-
-  runNextTask(config);
-}
-
-// TODO(hiroshige): the following code is the same as benchmark.html
-// and some of them should be merged into e.g. util.js
-
-var tasks = [];
-
-function runNextTask(config) {
-  var task = tasks.shift();
-  if (task == undefined) {
-    config.addToLog('Finished');
-    destroyAllXHRs();
-    return;
-  }
-  timerID = setTimeout(task, 0);
-}
-
-function buildLegendString(config) {
-  var legend = ''
-  if (config.printSize)
-    legend = 'Message size in KiB, Time/message in ms, ';
-  legend += 'Speed in kB/s';
-  return legend;
-}
-
-function addTasks(config, stepFunc) {
-  for (var i = 0;
-      i < config.numWarmUpIterations + config.numIterations; ++i) {
-    // Ignore the first |config.numWarmUpIterations| iterations.
-    if (i == config.numWarmUpIterations)
-      addResultClearingTask(config);
-
-    var multiplierIndex = 0;
-    for (var size = config.startSize;
-         size <= config.stopThreshold;
-         ++multiplierIndex) {
-      var task = stepFunc.bind(
-          null,
-          size,
-          config);
-      tasks.push(task);
-      size *= config.multipliers[
-          multiplierIndex % config.multipliers.length];
-    }
-  }
-}
-
-function addResultReportingTask(config, title) {
-  tasks.push(function(){
-      timerID = null;
-      config.addToSummary(title);
-      reportAverageData(config);
-      clearAverageData();
-      runNextTask(config);
-  });
-}
-
-function addResultClearingTask(config) {
-  tasks.push(function(){
-      timerID = null;
-      clearAverageData();
-      runNextTask(config);
-  });
-}
-
-// --------------------------------
-
-function sendBenchmark(config) {
-  config.addToLog('Send benchmark');
-  config.addToLog(buildLegendString(config));
-
-  tasks = [];
-  clearAverageData();
-  addTasks(config, sendBenchmarkStep);
-  addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
-  startBenchmark(config);
-}
-
-function receiveBenchmark(config) {
-  config.addToLog('Receive benchmark');
-  config.addToLog(buildLegendString(config));
-
-  tasks = [];
-  clearAverageData();
-  addTasks(config, receiveBenchmarkStep);
-  addResultReportingTask(config,
-      'Receive Benchmark ' + getConfigString(config));
-  startBenchmark(config);
-}
-
-function batchBenchmark(originalConfig) {
-  originalConfig.addToLog('Batch benchmark');
-
-  tasks = [];
-  clearAverageData();
-
-  var dataTypes = ['text', 'blob', 'arraybuffer'];
-  var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep];
-  var names = ['Send', 'Receive'];
-  var async = [true, false];
-  for (var i = 0; i < stepFuncs.length; ++i) {
-    for (var j = 0; j < dataTypes.length; ++j) {
-      for (var k = 0; k < async.length; ++k) {
-        var config = cloneConfig(originalConfig);
-        config.dataType = dataTypes[j];
-        config.async = async[k];
-
-        // Receive && Non-Worker && Sync is not supported by the spec
-        if (stepFuncs[i] === receiveBenchmarkStep && !isWorker &&
-            !config.async)
-          continue;
-
-        addTasks(config, stepFuncs[i]);
-        addResultReportingTask(config,
-            names[i] + ' benchmark ' + getConfigString(config));
-      }
-    }
-  }
-
-  startBenchmark(config);
-}
-
-
-function stop(config) {
-  destroyAllXHRs();
-  clearTimeout(timerID);
-  timerID = null;
-  config.addToLog('Stopped');
-}
-
-onmessage = function (message) {
-  var config = message.data.config;
-  config.addToLog = workerAddToLog;
-  config.addToSummary = workerAddToSummary;
-  config.measureValue = workerMeasureValue;
-  if (message.data.type === 'sendBenchmark')
-    sendBenchmark(config);
-  else if (message.data.type === 'receiveBenchmark')
-    receiveBenchmark(config);
-  else if (message.data.type === 'batchBenchmark')
-    batchBenchmark(config);
-  else if (message.data.type === 'stop')
-    stop(config);
-};
diff --git a/tools/pywebsocket/src/example/xhr_event_logger.html b/tools/pywebsocket/src/example/xhr_event_logger.html
deleted file mode 100644
index 6983553..0000000
--- a/tools/pywebsocket/src/example/xhr_event_logger.html
+++ /dev/null
@@ -1,110 +0,0 @@
-<!--
-Copyright 2014 Google Inc. All rights reserved.
-
-Use of this source code is governed by a BSD-style
-license that can be found in the COPYING file or at
-https://developers.google.com/open-source/licenses/bsd
--->
-
-<html>
-<head>
-<title>XHR event logger</title>
-<script src="util_main.js"></script>
-<script>
-var events = [];
-
-function run() {
-  events = [];
-
-  function pushToLog(type) {
-    if (events.length != 0 && type === events[events.length - 1].type) {
-      events[events.length - 1].count += 1;
-    } else {
-      events.push({type: type, count: 1});
-    }
-  }
-
-  var xhr = new XMLHttpRequest();
-
-  function getProgressEventDump(e) {
-    return '(' + e.lengthComputable + ', ' + e.loaded + ', ' + e.total + ')';
-  }
-
-  var dumpProgressEvent = getBoolFromCheckBox('dumpprogressevent');
-
-  function log(e) {
-    var type = e.type;
-    if (type === 'readystatechange') {
-      type += e.target.readyState;
-    }
-    if (dumpProgressEvent && (e instanceof ProgressEvent)) {
-      type += getProgressEventDump(e);
-    }
-    pushToLog(type);
-  };
-
-  function logUpload(e) {
-    var type = e.type;
-    if (dumpProgressEvent && (e instanceof ProgressEvent)) {
-      type += getProgressEventDump(e);
-    }
-    pushToLog('upload' + type);
-  }
-
-  if (getBoolFromCheckBox('upload')) {
-    var upload = xhr.upload;
-    upload.onloadstart = logUpload;
-    upload.onprogress = logUpload;
-    upload.onabort = logUpload;
-    upload.onerror = logUpload;
-    upload.onload = logUpload;
-    upload.ontimeout = logUpload;
-    upload.onloadend = logUpload;
-  }
-
-  xhr.onreadystatechange = log;
-  xhr.onloadstart = log;
-  xhr.onprogress = log;
-  xhr.onabort = log;
-  xhr.onerror = log;
-  xhr.onload = log;
-  xhr.ontimeout = log;
-  xhr.onloadend = log;
-
-  xhr.open('POST', '/073be001e10950692ccbf3a2ad21c245_receive',
-           getBoolFromCheckBox('async'));
-  var size = getIntFromInput('size');
-  var chunkedMode = 'none';
-  if (getBoolFromCheckBox('chunkedresponse')) {
-    chunkedMode = 'chunked';
-  }
-  xhr.send(size + ' ' + chunkedMode);
-}
-
-function print() {
-  var result = '';
-  for (var i = 0; i < events.length; ++i) {
-    var event = events[i];
-    result += event.type + ' * ' + event.count + '\n';
-  }
-  document.getElementById('log').value = result;
-}
-</script>
-
-<body>
-  <textarea id="log" rows="10" cols="40" readonly></textarea>
-  <br/>
-  Size: <input type="text" id="size" value="65536"><br/>
-  <input type="checkbox" id="chunkedresponse">
-  <label for="chunkedresponse">Use Chunked T-E for response</label><br/>
-  <input type="checkbox" id="upload">
-  <label for="upload">Upload progress</label><br/>
-  <input type="checkbox" id="dumpprogressevent">
-  <label for="dumpprogressevent">
-    Dump lengthComputable/loaded/total</label><br/>
-  <input type="checkbox" id="async" checked>
-  <label for="async">Async</label><br/>
-  <input type="button" onclick="run()" value="Run XHR">
-  <input type="button" onclick="print()" value="Print log">
-</body>
-</html>
diff --git a/tools/pywebsocket/src/mod_pywebsocket/_stream_hybi.py b/tools/pywebsocket/src/mod_pywebsocket/_stream_hybi.py
deleted file mode 100644
index a8a49e3..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/_stream_hybi.py
+++ /dev/null
@@ -1,887 +0,0 @@
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""This file provides classes and helper functions for parsing/building frames
-of the WebSocket protocol (RFC 6455).
-
-Specification:
-http://tools.ietf.org/html/rfc6455
-"""
-
-
-from collections import deque
-import logging
-import os
-import struct
-import time
-
-from mod_pywebsocket import common
-from mod_pywebsocket import util
-from mod_pywebsocket._stream_base import BadOperationException
-from mod_pywebsocket._stream_base import ConnectionTerminatedException
-from mod_pywebsocket._stream_base import InvalidFrameException
-from mod_pywebsocket._stream_base import InvalidUTF8Exception
-from mod_pywebsocket._stream_base import StreamBase
-from mod_pywebsocket._stream_base import UnsupportedFrameException
-
-
-_NOOP_MASKER = util.NoopMasker()
-
-
-class Frame(object):
-
-    def __init__(self, fin=1, rsv1=0, rsv2=0, rsv3=0,
-                 opcode=None, payload=''):
-        self.fin = fin
-        self.rsv1 = rsv1
-        self.rsv2 = rsv2
-        self.rsv3 = rsv3
-        self.opcode = opcode
-        self.payload = payload
-
-
-# Helper functions made public to be used for writing unittests for WebSocket
-# clients.
-
-
-def create_length_header(length, mask):
-    """Creates a length header.
-
-    Args:
-        length: Frame length. Must be less than 2^63.
-        mask: Mask bit. Must be boolean.
-
-    Raises:
-        ValueError: when bad data is given.
-    """
-
-    if mask:
-        mask_bit = 1 << 7
-    else:
-        mask_bit = 0
-
-    if length < 0:
-        raise ValueError('length must be non negative integer')
-    elif length <= 125:
-        return chr(mask_bit | length)
-    elif length < (1 << 16):
-        return chr(mask_bit | 126) + struct.pack('!H', length)
-    elif length < (1 << 63):
-        return chr(mask_bit | 127) + struct.pack('!Q', length)
-    else:
-        raise ValueError('Payload is too big for one frame')
-
-
-def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask):
-    """Creates a frame header.
-
-    Raises:
-        Exception: when bad data is given.
-    """
-
-    if opcode < 0 or 0xf < opcode:
-        raise ValueError('Opcode out of range')
-
-    if payload_length < 0 or (1 << 63) <= payload_length:
-        raise ValueError('payload_length out of range')
-
-    if (fin | rsv1 | rsv2 | rsv3) & ~1:
-        raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1')
-
-    header = ''
-
-    first_byte = ((fin << 7)
-                  | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4)
-                  | opcode)
-    header += chr(first_byte)
-    header += create_length_header(payload_length, mask)
-
-    return header
-
-
-def _build_frame(header, body, mask):
-    if not mask:
-        return header + body
-
-    masking_nonce = os.urandom(4)
-    masker = util.RepeatedXorMasker(masking_nonce)
-
-    return header + masking_nonce + masker.mask(body)
-
-
-def _filter_and_format_frame_object(frame, mask, frame_filters):
-    for frame_filter in frame_filters:
-        frame_filter.filter(frame)
-
-    header = create_header(
-        frame.opcode, len(frame.payload), frame.fin,
-        frame.rsv1, frame.rsv2, frame.rsv3, mask)
-    return _build_frame(header, frame.payload, mask)
-
-
-def create_binary_frame(
-    message, opcode=common.OPCODE_BINARY, fin=1, mask=False, frame_filters=[]):
-    """Creates a simple binary frame with no extension, reserved bit."""
-
-    frame = Frame(fin=fin, opcode=opcode, payload=message)
-    return _filter_and_format_frame_object(frame, mask, frame_filters)
-
-
-def create_text_frame(
-    message, opcode=common.OPCODE_TEXT, fin=1, mask=False, frame_filters=[]):
-    """Creates a simple text frame with no extension, reserved bit."""
-
-    encoded_message = message.encode('utf-8')
-    return create_binary_frame(encoded_message, opcode, fin, mask,
-                               frame_filters)
-
-
-def parse_frame(receive_bytes, logger=None,
-                ws_version=common.VERSION_HYBI_LATEST,
-                unmask_receive=True):
-    """Parses a frame. Returns a tuple containing each header field and
-    payload.
-
-    Args:
-        receive_bytes: a function that reads frame data from a stream or
-            something similar. The function takes length of the bytes to be
-            read. The function must raise ConnectionTerminatedException if
-            there is not enough data to be read.
-        logger: a logging object.
-        ws_version: the version of WebSocket protocol.
-        unmask_receive: unmask received frames. When received unmasked
-            frame, raises InvalidFrameException.
-
-    Raises:
-        ConnectionTerminatedException: when receive_bytes raises it.
-        InvalidFrameException: when the frame contains invalid data.
-    """
-
-    if not logger:
-        logger = logging.getLogger()
-
-    logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame')
-
-    received = receive_bytes(2)
-
-    first_byte = ord(received[0])
-    fin = (first_byte >> 7) & 1
-    rsv1 = (first_byte >> 6) & 1
-    rsv2 = (first_byte >> 5) & 1
-    rsv3 = (first_byte >> 4) & 1
-    opcode = first_byte & 0xf
-
-    second_byte = ord(received[1])
-    mask = (second_byte >> 7) & 1
-    payload_length = second_byte & 0x7f
-
-    logger.log(common.LOGLEVEL_FINE,
-               'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, '
-               'Mask=%s, Payload_length=%s',
-               fin, rsv1, rsv2, rsv3, opcode, mask, payload_length)
-
-    if (mask == 1) != unmask_receive:
-        raise InvalidFrameException(
-            'Mask bit on the received frame did\'nt match masking '
-            'configuration for received frames')
-
-    # The HyBi and later specs disallow putting a value in 0x0-0xFFFF
-    # into the 8-octet extended payload length field (or 0x0-0xFD in
-    # 2-octet field).
-    valid_length_encoding = True
-    length_encoding_bytes = 1
-    if payload_length == 127:
-        logger.log(common.LOGLEVEL_FINE,
-                   'Receive 8-octet extended payload length')
-
-        extended_payload_length = receive_bytes(8)
-        payload_length = struct.unpack(
-            '!Q', extended_payload_length)[0]
-        if payload_length > 0x7FFFFFFFFFFFFFFF:
-            raise InvalidFrameException(
-                'Extended payload length >= 2^63')
-        if ws_version >= 13 and payload_length < 0x10000:
-            valid_length_encoding = False
-            length_encoding_bytes = 8
-
-        logger.log(common.LOGLEVEL_FINE,
-                   'Decoded_payload_length=%s', payload_length)
-    elif payload_length == 126:
-        logger.log(common.LOGLEVEL_FINE,
-                   'Receive 2-octet extended payload length')
-
-        extended_payload_length = receive_bytes(2)
-        payload_length = struct.unpack(
-            '!H', extended_payload_length)[0]
-        if ws_version >= 13 and payload_length < 126:
-            valid_length_encoding = False
-            length_encoding_bytes = 2
-
-        logger.log(common.LOGLEVEL_FINE,
-                   'Decoded_payload_length=%s', payload_length)
-
-    if not valid_length_encoding:
-        logger.warning(
-            'Payload length is not encoded using the minimal number of '
-            'bytes (%d is encoded using %d bytes)',
-            payload_length,
-            length_encoding_bytes)
-
-    if mask == 1:
-        logger.log(common.LOGLEVEL_FINE, 'Receive mask')
-
-        masking_nonce = receive_bytes(4)
-        masker = util.RepeatedXorMasker(masking_nonce)
-
-        logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce)
-    else:
-        masker = _NOOP_MASKER
-
-    logger.log(common.LOGLEVEL_FINE, 'Receive payload data')
-    if logger.isEnabledFor(common.LOGLEVEL_FINE):
-        receive_start = time.time()
-
-    raw_payload_bytes = receive_bytes(payload_length)
-
-    if logger.isEnabledFor(common.LOGLEVEL_FINE):
-        logger.log(
-            common.LOGLEVEL_FINE,
-            'Done receiving payload data at %s MB/s',
-            payload_length / (time.time() - receive_start) / 1000 / 1000)
-    logger.log(common.LOGLEVEL_FINE, 'Unmask payload data')
-
-    if logger.isEnabledFor(common.LOGLEVEL_FINE):
-        unmask_start = time.time()
-
-    unmasked_bytes = masker.mask(raw_payload_bytes)
-
-    if logger.isEnabledFor(common.LOGLEVEL_FINE):
-        logger.log(
-            common.LOGLEVEL_FINE,
-            'Done unmasking payload data at %s MB/s',
-            payload_length / (time.time() - unmask_start) / 1000 / 1000)
-
-    return opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3
-
-
-class FragmentedFrameBuilder(object):
-    """A stateful class to send a message as fragments."""
-
-    def __init__(self, mask, frame_filters=[], encode_utf8=True):
-        """Constructs an instance."""
-
-        self._mask = mask
-        self._frame_filters = frame_filters
-        # This is for skipping UTF-8 encoding when building text type frames
-        # from compressed data.
-        self._encode_utf8 = encode_utf8
-
-        self._started = False
-
-        # Hold opcode of the first frame in messages to verify types of other
-        # frames in the message are all the same.
-        self._opcode = common.OPCODE_TEXT
-
-    def build(self, payload_data, end, binary):
-        if binary:
-            frame_type = common.OPCODE_BINARY
-        else:
-            frame_type = common.OPCODE_TEXT
-        if self._started:
-            if self._opcode != frame_type:
-                raise ValueError('Message types are different in frames for '
-                                 'the same message')
-            opcode = common.OPCODE_CONTINUATION
-        else:
-            opcode = frame_type
-            self._opcode = frame_type
-
-        if end:
-            self._started = False
-            fin = 1
-        else:
-            self._started = True
-            fin = 0
-
-        if binary or not self._encode_utf8:
-            return create_binary_frame(
-                payload_data, opcode, fin, self._mask, self._frame_filters)
-        else:
-            return create_text_frame(
-                payload_data, opcode, fin, self._mask, self._frame_filters)
-
-
-def _create_control_frame(opcode, body, mask, frame_filters):
-    frame = Frame(opcode=opcode, payload=body)
-
-    for frame_filter in frame_filters:
-        frame_filter.filter(frame)
-
-    if len(frame.payload) > 125:
-        raise BadOperationException(
-            'Payload data size of control frames must be 125 bytes or less')
-
-    header = create_header(
-        frame.opcode, len(frame.payload), frame.fin,
-        frame.rsv1, frame.rsv2, frame.rsv3, mask)
-    return _build_frame(header, frame.payload, mask)
-
-
-def create_ping_frame(body, mask=False, frame_filters=[]):
-    return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters)
-
-
-def create_pong_frame(body, mask=False, frame_filters=[]):
-    return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters)
-
-
-def create_close_frame(body, mask=False, frame_filters=[]):
-    return _create_control_frame(
-        common.OPCODE_CLOSE, body, mask, frame_filters)
-
-
-def create_closing_handshake_body(code, reason):
-    body = ''
-    if code is not None:
-        if (code > common.STATUS_USER_PRIVATE_MAX or
-            code < common.STATUS_NORMAL_CLOSURE):
-            raise BadOperationException('Status code is out of range')
-        if (code == common.STATUS_NO_STATUS_RECEIVED or
-            code == common.STATUS_ABNORMAL_CLOSURE or
-            code == common.STATUS_TLS_HANDSHAKE):
-            raise BadOperationException('Status code is reserved pseudo '
-                'code')
-        encoded_reason = reason.encode('utf-8')
-        body = struct.pack('!H', code) + encoded_reason
-    return body
-
-
-class StreamOptions(object):
-    """Holds option values to configure Stream objects."""
-
-    def __init__(self):
-        """Constructs StreamOptions."""
-
-        # Filters applied to frames.
-        self.outgoing_frame_filters = []
-        self.incoming_frame_filters = []
-
-        # Filters applied to messages. Control frames are not affected by them.
-        self.outgoing_message_filters = []
-        self.incoming_message_filters = []
-
-        self.encode_text_message_to_utf8 = True
-        self.mask_send = False
-        self.unmask_receive = True
-
-
-class Stream(StreamBase):
-    """A class for parsing/building frames of the WebSocket protocol
-    (RFC 6455).
-    """
-
-    def __init__(self, request, options):
-        """Constructs an instance.
-
-        Args:
-            request: mod_python request.
-        """
-
-        StreamBase.__init__(self, request)
-
-        self._logger = util.get_class_logger(self)
-
-        self._options = options
-
-        self._request.client_terminated = False
-        self._request.server_terminated = False
-
-        # Holds body of received fragments.
-        self._received_fragments = []
-        # Holds the opcode of the first fragment.
-        self._original_opcode = None
-
-        self._writer = FragmentedFrameBuilder(
-            self._options.mask_send, self._options.outgoing_frame_filters,
-            self._options.encode_text_message_to_utf8)
-
-        self._ping_queue = deque()
-
-    def _receive_frame(self):
-        """Receives a frame and return data in the frame as a tuple containing
-        each header field and payload separately.
-
-        Raises:
-            ConnectionTerminatedException: when read returns empty
-                string.
-            InvalidFrameException: when the frame contains invalid data.
-        """
-
-        def _receive_bytes(length):
-            return self.receive_bytes(length)
-
-        return parse_frame(receive_bytes=_receive_bytes,
-                           logger=self._logger,
-                           ws_version=self._request.ws_version,
-                           unmask_receive=self._options.unmask_receive)
-
-    def _receive_frame_as_frame_object(self):
-        opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame()
-
-        return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3,
-                     opcode=opcode, payload=unmasked_bytes)
-
-    def receive_filtered_frame(self):
-        """Receives a frame and applies frame filters and message filters.
-        The frame to be received must satisfy following conditions:
-        - The frame is not fragmented.
-        - The opcode of the frame is TEXT or BINARY.
-
-        DO NOT USE this method except for testing purpose.
-        """
-
-        frame = self._receive_frame_as_frame_object()
-        if not frame.fin:
-            raise InvalidFrameException(
-                'Segmented frames must not be received via '
-                'receive_filtered_frame()')
-        if (frame.opcode != common.OPCODE_TEXT and
-            frame.opcode != common.OPCODE_BINARY):
-            raise InvalidFrameException(
-                'Control frames must not be received via '
-                'receive_filtered_frame()')
-
-        for frame_filter in self._options.incoming_frame_filters:
-            frame_filter.filter(frame)
-        for message_filter in self._options.incoming_message_filters:
-            frame.payload = message_filter.filter(frame.payload)
-        return frame
-
-    def send_message(self, message, end=True, binary=False):
-        """Send message.
-
-        Args:
-            message: text in unicode or binary in str to send.
-            binary: send message as binary frame.
-
-        Raises:
-            BadOperationException: when called on a server-terminated
-                connection or called with inconsistent message type or
-                binary parameter.
-        """
-
-        if self._request.server_terminated:
-            raise BadOperationException(
-                'Requested send_message after sending out a closing handshake')
-
-        if binary and isinstance(message, unicode):
-            raise BadOperationException(
-                'Message for binary frame must be instance of str')
-
-        for message_filter in self._options.outgoing_message_filters:
-            message = message_filter.filter(message, end, binary)
-
-        try:
-            # Set this to any positive integer to limit maximum size of data in
-            # payload data of each frame.
-            MAX_PAYLOAD_DATA_SIZE = -1
-
-            if MAX_PAYLOAD_DATA_SIZE <= 0:
-                self._write(self._writer.build(message, end, binary))
-                return
-
-            bytes_written = 0
-            while True:
-                end_for_this_frame = end
-                bytes_to_write = len(message) - bytes_written
-                if (MAX_PAYLOAD_DATA_SIZE > 0 and
-                    bytes_to_write > MAX_PAYLOAD_DATA_SIZE):
-                    end_for_this_frame = False
-                    bytes_to_write = MAX_PAYLOAD_DATA_SIZE
-
-                frame = self._writer.build(
-                    message[bytes_written:bytes_written + bytes_to_write],
-                    end_for_this_frame,
-                    binary)
-                self._write(frame)
-
-                bytes_written += bytes_to_write
-
-                # This if must be placed here (the end of while block) so that
-                # at least one frame is sent.
-                if len(message) <= bytes_written:
-                    break
-        except ValueError, e:
-            raise BadOperationException(e)
-
-    def _get_message_from_frame(self, frame):
-        """Gets a message from frame. If the message is composed of fragmented
-        frames and the frame is not the last fragmented frame, this method
-        returns None. The whole message will be returned when the last
-        fragmented frame is passed to this method.
-
-        Raises:
-            InvalidFrameException: when the frame doesn't match defragmentation
-                context, or the frame contains invalid data.
-        """
-
-        if frame.opcode == common.OPCODE_CONTINUATION:
-            if not self._received_fragments:
-                if frame.fin:
-                    raise InvalidFrameException(
-                        'Received a termination frame but fragmentation '
-                        'not started')
-                else:
-                    raise InvalidFrameException(
-                        'Received an intermediate frame but '
-                        'fragmentation not started')
-
-            if frame.fin:
-                # End of fragmentation frame
-                self._received_fragments.append(frame.payload)
-                message = ''.join(self._received_fragments)
-                self._received_fragments = []
-                return message
-            else:
-                # Intermediate frame
-                self._received_fragments.append(frame.payload)
-                return None
-        else:
-            if self._received_fragments:
-                if frame.fin:
-                    raise InvalidFrameException(
-                        'Received an unfragmented frame without '
-                        'terminating existing fragmentation')
-                else:
-                    raise InvalidFrameException(
-                        'New fragmentation started without terminating '
-                        'existing fragmentation')
-
-            if frame.fin:
-                # Unfragmented frame
-
-                self._original_opcode = frame.opcode
-                return frame.payload
-            else:
-                # Start of fragmentation frame
-
-                if common.is_control_opcode(frame.opcode):
-                    raise InvalidFrameException(
-                        'Control frames must not be fragmented')
-
-                self._original_opcode = frame.opcode
-                self._received_fragments.append(frame.payload)
-                return None
-
-    def _process_close_message(self, message):
-        """Processes close message.
-
-        Args:
-            message: close message.
-
-        Raises:
-            InvalidFrameException: when the message is invalid.
-        """
-
-        self._request.client_terminated = True
-
-        # Status code is optional. We can have status reason only if we
-        # have status code. Status reason can be empty string. So,
-        # allowed cases are
-        # - no application data: no code no reason
-        # - 2 octet of application data: has code but no reason
-        # - 3 or more octet of application data: both code and reason
-        if len(message) == 0:
-            self._logger.debug('Received close frame (empty body)')
-            self._request.ws_close_code = (
-                common.STATUS_NO_STATUS_RECEIVED)
-        elif len(message) == 1:
-            raise InvalidFrameException(
-                'If a close frame has status code, the length of '
-                'status code must be 2 octet')
-        elif len(message) >= 2:
-            self._request.ws_close_code = struct.unpack(
-                '!H', message[0:2])[0]
-            self._request.ws_close_reason = message[2:].decode(
-                'utf-8', 'replace')
-            self._logger.debug(
-                'Received close frame (code=%d, reason=%r)',
-                self._request.ws_close_code,
-                self._request.ws_close_reason)
-
-        # As we've received a close frame, no more data is coming over the
-        # socket. We can now safely close the socket without worrying about
-        # RST sending.
-
-        if self._request.server_terminated:
-            self._logger.debug(
-                'Received ack for server-initiated closing handshake')
-            return
-
-        self._logger.debug(
-            'Received client-initiated closing handshake')
-
-        code = common.STATUS_NORMAL_CLOSURE
-        reason = ''
-        if hasattr(self._request, '_dispatcher'):
-            dispatcher = self._request._dispatcher
-            code, reason = dispatcher.passive_closing_handshake(
-                self._request)
-            if code is None and reason is not None and len(reason) > 0:
-                self._logger.warning(
-                    'Handler specified reason despite code being None')
-                reason = ''
-            if reason is None:
-                reason = ''
-        self._send_closing_handshake(code, reason)
-        self._logger.debug(
-            'Acknowledged closing handshake initiated by the peer '
-            '(code=%r, reason=%r)', code, reason)
-
-    def _process_ping_message(self, message):
-        """Processes ping message.
-
-        Args:
-            message: ping message.
-        """
-
-        try:
-            handler = self._request.on_ping_handler
-            if handler:
-                handler(self._request, message)
-                return
-        except AttributeError, e:
-            pass
-        self._send_pong(message)
-
-    def _process_pong_message(self, message):
-        """Processes pong message.
-
-        Args:
-            message: pong message.
-        """
-
-        # TODO(tyoshino): Add ping timeout handling.
-
-        inflight_pings = deque()
-
-        while True:
-            try:
-                expected_body = self._ping_queue.popleft()
-                if expected_body == message:
-                    # inflight_pings contains pings ignored by the
-                    # other peer. Just forget them.
-                    self._logger.debug(
-                        'Ping %r is acked (%d pings were ignored)',
-                        expected_body, len(inflight_pings))
-                    break
-                else:
-                    inflight_pings.append(expected_body)
-            except IndexError, e:
-                # The received pong was unsolicited pong. Keep the
-                # ping queue as is.
-                self._ping_queue = inflight_pings
-                self._logger.debug('Received a unsolicited pong')
-                break
-
-        try:
-            handler = self._request.on_pong_handler
-            if handler:
-                handler(self._request, message)
-        except AttributeError, e:
-            pass
-
-    def receive_message(self):
-        """Receive a WebSocket frame and return its payload as a text in
-        unicode or a binary in str.
-
-        Returns:
-            payload data of the frame
-            - as unicode instance if received text frame
-            - as str instance if received binary frame
-            or None iff received closing handshake.
-        Raises:
-            BadOperationException: when called on a client-terminated
-                connection.
-            ConnectionTerminatedException: when read returns empty
-                string.
-            InvalidFrameException: when the frame contains invalid
-                data.
-            UnsupportedFrameException: when the received frame has
-                flags, opcode we cannot handle. You can ignore this
-                exception and continue receiving the next frame.
-        """
-
-        if self._request.client_terminated:
-            raise BadOperationException(
-                'Requested receive_message after receiving a closing '
-                'handshake')
-
-        while True:
-            # mp_conn.read will block if no bytes are available.
-            # Timeout is controlled by TimeOut directive of Apache.
-
-            frame = self._receive_frame_as_frame_object()
-
-            # Check the constraint on the payload size for control frames
-            # before extension processes the frame.
-            # See also http://tools.ietf.org/html/rfc6455#section-5.5
-            if (common.is_control_opcode(frame.opcode) and
-                len(frame.payload) > 125):
-                raise InvalidFrameException(
-                    'Payload data size of control frames must be 125 bytes or '
-                    'less')
-
-            for frame_filter in self._options.incoming_frame_filters:
-                frame_filter.filter(frame)
-
-            if frame.rsv1 or frame.rsv2 or frame.rsv3:
-                raise UnsupportedFrameException(
-                    'Unsupported flag is set (rsv = %d%d%d)' %
-                    (frame.rsv1, frame.rsv2, frame.rsv3))
-
-            message = self._get_message_from_frame(frame)
-            if message is None:
-                continue
-
-            for message_filter in self._options.incoming_message_filters:
-                message = message_filter.filter(message)
-
-            if self._original_opcode == common.OPCODE_TEXT:
-                # The WebSocket protocol section 4.4 specifies that invalid
-                # characters must be replaced with U+fffd REPLACEMENT
-                # CHARACTER.
-                try:
-                    return message.decode('utf-8')
-                except UnicodeDecodeError, e:
-                    raise InvalidUTF8Exception(e)
-            elif self._original_opcode == common.OPCODE_BINARY:
-                return message
-            elif self._original_opcode == common.OPCODE_CLOSE:
-                self._process_close_message(message)
-                return None
-            elif self._original_opcode == common.OPCODE_PING:
-                self._process_ping_message(message)
-            elif self._original_opcode == common.OPCODE_PONG:
-                self._process_pong_message(message)
-            else:
-                raise UnsupportedFrameException(
-                    'Opcode %d is not supported' % self._original_opcode)
-
-    def _send_closing_handshake(self, code, reason):
-        body = create_closing_handshake_body(code, reason)
-        frame = create_close_frame(
-            body, mask=self._options.mask_send,
-            frame_filters=self._options.outgoing_frame_filters)
-
-        self._request.server_terminated = True
-
-        self._write(frame)
-
-    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason='',
-                         wait_response=True):
-        """Closes a WebSocket connection.
-
-        Args:
-            code: Status code for close frame. If code is None, a close
-                frame with empty body will be sent.
-            reason: string representing close reason.
-            wait_response: True when caller want to wait the response.
-        Raises:
-            BadOperationException: when reason is specified with code None
-            or reason is not an instance of both str and unicode.
-        """
-
-        if self._request.server_terminated:
-            self._logger.debug(
-                'Requested close_connection but server is already terminated')
-            return
-
-        if code is None:
-            if reason is not None and len(reason) > 0:
-                raise BadOperationException(
-                    'close reason must not be specified if code is None')
-            reason = ''
-        else:
-            if not isinstance(reason, str) and not isinstance(reason, unicode):
-                raise BadOperationException(
-                    'close reason must be an instance of str or unicode')
-
-        self._send_closing_handshake(code, reason)
-        self._logger.debug(
-            'Initiated closing handshake (code=%r, reason=%r)',
-            code, reason)
-
-        if (code == common.STATUS_GOING_AWAY or
-            code == common.STATUS_PROTOCOL_ERROR) or not wait_response:
-            # It doesn't make sense to wait for a close frame if the reason is
-            # protocol error or that the server is going away. For some of
-            # other reasons, it might not make sense to wait for a close frame,
-            # but it's not clear, yet.
-            return
-
-        # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
-        # or until a server-defined timeout expires.
-        #
-        # For now, we expect receiving closing handshake right after sending
-        # out closing handshake.
-        message = self.receive_message()
-        if message is not None:
-            raise ConnectionTerminatedException(
-                'Didn\'t receive valid ack for closing handshake')
-        # TODO: 3. close the WebSocket connection.
-        # note: mod_python Connection (mp_conn) doesn't have close method.
-
-    def send_ping(self, body=''):
-        frame = create_ping_frame(
-            body,
-            self._options.mask_send,
-            self._options.outgoing_frame_filters)
-        self._write(frame)
-
-        self._ping_queue.append(body)
-
-    def _send_pong(self, body):
-        frame = create_pong_frame(
-            body,
-            self._options.mask_send,
-            self._options.outgoing_frame_filters)
-        self._write(frame)
-
-    def get_last_received_opcode(self):
-        """Returns the opcode of the WebSocket message which the last received
-        frame belongs to. The return value is valid iff immediately after
-        receive_message call.
-        """
-
-        return self._original_opcode
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/common.py b/tools/pywebsocket/src/mod_pywebsocket/common.py
deleted file mode 100644
index 2fc2ead..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/common.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""This file must not depend on any module specific to the WebSocket protocol.
-"""
-
-
-from mod_pywebsocket import http_header_util
-
-
-# Additional log level definitions.
-LOGLEVEL_FINE = 9
-
-# Constants indicating WebSocket protocol version.
-VERSION_HIXIE75 = -1
-VERSION_HYBI00 = 0
-VERSION_HYBI01 = 1
-VERSION_HYBI02 = 2
-VERSION_HYBI03 = 2
-VERSION_HYBI04 = 4
-VERSION_HYBI05 = 5
-VERSION_HYBI06 = 6
-VERSION_HYBI07 = 7
-VERSION_HYBI08 = 8
-VERSION_HYBI09 = 8
-VERSION_HYBI10 = 8
-VERSION_HYBI11 = 8
-VERSION_HYBI12 = 8
-VERSION_HYBI13 = 13
-VERSION_HYBI14 = 13
-VERSION_HYBI15 = 13
-VERSION_HYBI16 = 13
-VERSION_HYBI17 = 13
-
-# Constants indicating WebSocket protocol latest version.
-VERSION_HYBI_LATEST = VERSION_HYBI13
-
-# Port numbers
-DEFAULT_WEB_SOCKET_PORT = 80
-DEFAULT_WEB_SOCKET_SECURE_PORT = 443
-
-# Schemes
-WEB_SOCKET_SCHEME = 'ws'
-WEB_SOCKET_SECURE_SCHEME = 'wss'
-
-# Frame opcodes defined in the spec.
-OPCODE_CONTINUATION = 0x0
-OPCODE_TEXT = 0x1
-OPCODE_BINARY = 0x2
-OPCODE_CLOSE = 0x8
-OPCODE_PING = 0x9
-OPCODE_PONG = 0xa
-
-# UUIDs used by HyBi 04 and later opening handshake and frame masking.
-WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
-
-# Opening handshake header names and expected values.
-UPGRADE_HEADER = 'Upgrade'
-WEBSOCKET_UPGRADE_TYPE = 'websocket'
-WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket'
-CONNECTION_HEADER = 'Connection'
-UPGRADE_CONNECTION_TYPE = 'Upgrade'
-HOST_HEADER = 'Host'
-ORIGIN_HEADER = 'Origin'
-SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin'
-SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key'
-SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept'
-SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version'
-SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol'
-SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions'
-SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft'
-SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1'
-SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2'
-SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
-
-# Extensions
-DEFLATE_FRAME_EXTENSION = 'deflate-frame'
-PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress'
-PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
-X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
-X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress'
-MUX_EXTENSION = 'mux_DO_NOT_USE'
-
-# Status codes
-# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and
-# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases.
-# Could not be used for codes in actual closing frames.
-# Application level errors must use codes in the range
-# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the
-# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed
-# by IANA. Usually application must define user protocol level errors in the
-# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX.
-STATUS_NORMAL_CLOSURE = 1000
-STATUS_GOING_AWAY = 1001
-STATUS_PROTOCOL_ERROR = 1002
-STATUS_UNSUPPORTED_DATA = 1003
-STATUS_NO_STATUS_RECEIVED = 1005
-STATUS_ABNORMAL_CLOSURE = 1006
-STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
-STATUS_POLICY_VIOLATION = 1008
-STATUS_MESSAGE_TOO_BIG = 1009
-STATUS_MANDATORY_EXTENSION = 1010
-STATUS_INTERNAL_ENDPOINT_ERROR = 1011
-STATUS_TLS_HANDSHAKE = 1015
-STATUS_USER_REGISTERED_BASE = 3000
-STATUS_USER_REGISTERED_MAX = 3999
-STATUS_USER_PRIVATE_BASE = 4000
-STATUS_USER_PRIVATE_MAX = 4999
-# Following definitions are aliases to keep compatibility. Applications must
-# not use these obsoleted definitions anymore.
-STATUS_NORMAL = STATUS_NORMAL_CLOSURE
-STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
-STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
-STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
-STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
-STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
-
-# HTTP status codes
-HTTP_STATUS_BAD_REQUEST = 400
-HTTP_STATUS_FORBIDDEN = 403
-HTTP_STATUS_NOT_FOUND = 404
-
-
-def is_control_opcode(opcode):
-    return (opcode >> 3) == 1
-
-
-class ExtensionParameter(object):
-    """Holds information about an extension which is exchanged on extension
-    negotiation in opening handshake.
-    """
-
-    def __init__(self, name):
-        self._name = name
-        # TODO(tyoshino): Change the data structure to more efficient one such
-        # as dict when the spec changes to say like
-        # - Parameter names must be unique
-        # - The order of parameters is not significant
-        self._parameters = []
-
-    def name(self):
-        return self._name
-
-    def add_parameter(self, name, value):
-        self._parameters.append((name, value))
-
-    def get_parameters(self):
-        return self._parameters
-
-    def get_parameter_names(self):
-        return [name for name, unused_value in self._parameters]
-
-    def has_parameter(self, name):
-        for param_name, param_value in self._parameters:
-            if param_name == name:
-                return True
-        return False
-
-    def get_parameter_value(self, name):
-        for param_name, param_value in self._parameters:
-            if param_name == name:
-                return param_value
-
-
-class ExtensionParsingException(Exception):
-    def __init__(self, name):
-        super(ExtensionParsingException, self).__init__(name)
-
-
-def _parse_extension_param(state, definition):
-    param_name = http_header_util.consume_token(state)
-
-    if param_name is None:
-        raise ExtensionParsingException('No valid parameter name found')
-
-    http_header_util.consume_lwses(state)
-
-    if not http_header_util.consume_string(state, '='):
-        definition.add_parameter(param_name, None)
-        return
-
-    http_header_util.consume_lwses(state)
-
-    # TODO(tyoshino): Add code to validate that parsed param_value is token
-    param_value = http_header_util.consume_token_or_quoted_string(state)
-    if param_value is None:
-        raise ExtensionParsingException(
-            'No valid parameter value found on the right-hand side of '
-            'parameter %r' % param_name)
-
-    definition.add_parameter(param_name, param_value)
-
-
-def _parse_extension(state):
-    extension_token = http_header_util.consume_token(state)
-    if extension_token is None:
-        return None
-
-    extension = ExtensionParameter(extension_token)
-
-    while True:
-        http_header_util.consume_lwses(state)
-
-        if not http_header_util.consume_string(state, ';'):
-            break
-
-        http_header_util.consume_lwses(state)
-
-        try:
-            _parse_extension_param(state, extension)
-        except ExtensionParsingException, e:
-            raise ExtensionParsingException(
-                'Failed to parse parameter for %r (%r)' %
-                (extension_token, e))
-
-    return extension
-
-
-def parse_extensions(data):
-    """Parses Sec-WebSocket-Extensions header value returns a list of
-    ExtensionParameter objects.
-
-    Leading LWSes must be trimmed.
-    """
-
-    state = http_header_util.ParsingState(data)
-
-    extension_list = []
-    while True:
-        extension = _parse_extension(state)
-        if extension is not None:
-            extension_list.append(extension)
-
-        http_header_util.consume_lwses(state)
-
-        if http_header_util.peek(state) is None:
-            break
-
-        if not http_header_util.consume_string(state, ','):
-            raise ExtensionParsingException(
-                'Failed to parse Sec-WebSocket-Extensions header: '
-                'Expected a comma but found %r' %
-                http_header_util.peek(state))
-
-        http_header_util.consume_lwses(state)
-
-    if len(extension_list) == 0:
-        raise ExtensionParsingException(
-            'No valid extension entry found')
-
-    return extension_list
-
-
-def format_extension(extension):
-    """Formats an ExtensionParameter object."""
-
-    formatted_params = [extension.name()]
-    for param_name, param_value in extension.get_parameters():
-        if param_value is None:
-            formatted_params.append(param_name)
-        else:
-            quoted_value = http_header_util.quote_if_necessary(param_value)
-            formatted_params.append('%s=%s' % (param_name, quoted_value))
-    return '; '.join(formatted_params)
-
-
-def format_extensions(extension_list):
-    """Formats a list of ExtensionParameter objects."""
-
-    formatted_extension_list = []
-    for extension in extension_list:
-        formatted_extension_list.append(format_extension(extension))
-    return ', '.join(formatted_extension_list)
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/extensions.py b/tools/pywebsocket/src/mod_pywebsocket/extensions.py
deleted file mode 100644
index 49a9fdc..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/extensions.py
+++ /dev/null
@@ -1,885 +0,0 @@
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-from mod_pywebsocket import common
-from mod_pywebsocket import util
-from mod_pywebsocket.http_header_util import quote_if_necessary
-
-
-# The list of available server side extension processor classes.
-_available_processors = {}
-_compression_extension_names = []
-
-
-class ExtensionProcessorInterface(object):
-
-    def __init__(self, request):
-        self._logger = util.get_class_logger(self)
-
-        self._request = request
-        self._active = True
-
-    def request(self):
-        return self._request
-
-    def name(self):
-        return None
-
-    def check_consistency_with_other_processors(self, processors):
-        pass
-
-    def set_active(self, active):
-        self._active = active
-
-    def is_active(self):
-        return self._active
-
-    def _get_extension_response_internal(self):
-        return None
-
-    def get_extension_response(self):
-        if not self._active:
-            self._logger.debug('Extension %s is deactivated', self.name())
-            return None
-
-        response = self._get_extension_response_internal()
-        if response is None:
-            self._active = False
-        return response
-
-    def _setup_stream_options_internal(self, stream_options):
-        pass
-
-    def setup_stream_options(self, stream_options):
-        if self._active:
-            self._setup_stream_options_internal(stream_options)
-
-
-def _log_outgoing_compression_ratio(
-        logger, original_bytes, filtered_bytes, average_ratio):
-    # Print inf when ratio is not available.
-    ratio = float('inf')
-    if original_bytes != 0:
-        ratio = float(filtered_bytes) / original_bytes
-
-    logger.debug('Outgoing compression ratio: %f (average: %f)' %
-            (ratio, average_ratio))
-
-
-def _log_incoming_compression_ratio(
-        logger, received_bytes, filtered_bytes, average_ratio):
-    # Print inf when ratio is not available.
-    ratio = float('inf')
-    if filtered_bytes != 0:
-        ratio = float(received_bytes) / filtered_bytes
-
-    logger.debug('Incoming compression ratio: %f (average: %f)' %
-            (ratio, average_ratio))
-
-
-def _parse_window_bits(bits):
-    """Return parsed integer value iff the given string conforms to the
-    grammar of the window bits extension parameters.
-    """
-
-    if bits is None:
-        raise ValueError('Value is required')
-
-    # For non integer values such as "10.0", ValueError will be raised.
-    int_bits = int(bits)
-
-    # First condition is to drop leading zero case e.g. "08".
-    if bits != str(int_bits) or int_bits < 8 or int_bits > 15:
-        raise ValueError('Invalid value: %r' % bits)
-
-    return int_bits
-
-
-class _AverageRatioCalculator(object):
-    """Stores total bytes of original and result data, and calculates average
-    result / original ratio.
-    """
-
-    def __init__(self):
-        self._total_original_bytes = 0
-        self._total_result_bytes = 0
-
-    def add_original_bytes(self, value):
-        self._total_original_bytes += value
-
-    def add_result_bytes(self, value):
-        self._total_result_bytes += value
-
-    def get_average_ratio(self):
-        if self._total_original_bytes != 0:
-            return (float(self._total_result_bytes) /
-                    self._total_original_bytes)
-        else:
-            return float('inf')
-
-
-class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
-    """deflate-frame extension processor.
-
-    Specification:
-    http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate
-    """
-
-    _WINDOW_BITS_PARAM = 'max_window_bits'
-    _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
-
-    def __init__(self, request):
-        ExtensionProcessorInterface.__init__(self, request)
-        self._logger = util.get_class_logger(self)
-
-        self._response_window_bits = None
-        self._response_no_context_takeover = False
-        self._bfinal = False
-
-        # Calculates
-        #     (Total outgoing bytes supplied to this filter) /
-        #     (Total bytes sent to the network after applying this filter)
-        self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
-
-        # Calculates
-        #     (Total bytes received from the network) /
-        #     (Total incoming bytes obtained after applying this filter)
-        self._incoming_average_ratio_calculator = _AverageRatioCalculator()
-
-    def name(self):
-        return common.DEFLATE_FRAME_EXTENSION
-
-    def _get_extension_response_internal(self):
-        # Any unknown parameter will be just ignored.
-
-        window_bits = None
-        if self._request.has_parameter(self._WINDOW_BITS_PARAM):
-            window_bits = self._request.get_parameter_value(
-                self._WINDOW_BITS_PARAM)
-            try:
-                window_bits = _parse_window_bits(window_bits)
-            except ValueError, e:
-                return None
-
-        no_context_takeover = self._request.has_parameter(
-            self._NO_CONTEXT_TAKEOVER_PARAM)
-        if (no_context_takeover and
-            self._request.get_parameter_value(
-                self._NO_CONTEXT_TAKEOVER_PARAM) is not None):
-            return None
-
-        self._rfc1979_deflater = util._RFC1979Deflater(
-            window_bits, no_context_takeover)
-
-        self._rfc1979_inflater = util._RFC1979Inflater()
-
-        self._compress_outgoing = True
-
-        response = common.ExtensionParameter(self._request.name())
-
-        if self._response_window_bits is not None:
-            response.add_parameter(
-                self._WINDOW_BITS_PARAM, str(self._response_window_bits))
-        if self._response_no_context_takeover:
-            response.add_parameter(
-                self._NO_CONTEXT_TAKEOVER_PARAM, None)
-
-        self._logger.debug(
-            'Enable %s extension ('
-            'request: window_bits=%s; no_context_takeover=%r, '
-            'response: window_wbits=%s; no_context_takeover=%r)' %
-            (self._request.name(),
-             window_bits,
-             no_context_takeover,
-             self._response_window_bits,
-             self._response_no_context_takeover))
-
-        return response
-
-    def _setup_stream_options_internal(self, stream_options):
-
-        class _OutgoingFilter(object):
-
-            def __init__(self, parent):
-                self._parent = parent
-
-            def filter(self, frame):
-                self._parent._outgoing_filter(frame)
-
-        class _IncomingFilter(object):
-
-            def __init__(self, parent):
-                self._parent = parent
-
-            def filter(self, frame):
-                self._parent._incoming_filter(frame)
-
-        stream_options.outgoing_frame_filters.append(
-            _OutgoingFilter(self))
-        stream_options.incoming_frame_filters.insert(
-            0, _IncomingFilter(self))
-
-    def set_response_window_bits(self, value):
-        self._response_window_bits = value
-
-    def set_response_no_context_takeover(self, value):
-        self._response_no_context_takeover = value
-
-    def set_bfinal(self, value):
-        self._bfinal = value
-
-    def enable_outgoing_compression(self):
-        self._compress_outgoing = True
-
-    def disable_outgoing_compression(self):
-        self._compress_outgoing = False
-
-    def _outgoing_filter(self, frame):
-        """Transform outgoing frames. This method is called only by
-        an _OutgoingFilter instance.
-        """
-
-        original_payload_size = len(frame.payload)
-        self._outgoing_average_ratio_calculator.add_original_bytes(
-                original_payload_size)
-
-        if (not self._compress_outgoing or
-            common.is_control_opcode(frame.opcode)):
-            self._outgoing_average_ratio_calculator.add_result_bytes(
-                    original_payload_size)
-            return
-
-        frame.payload = self._rfc1979_deflater.filter(
-            frame.payload, bfinal=self._bfinal)
-        frame.rsv1 = 1
-
-        filtered_payload_size = len(frame.payload)
-        self._outgoing_average_ratio_calculator.add_result_bytes(
-                filtered_payload_size)
-
-        _log_outgoing_compression_ratio(
-                self._logger,
-                original_payload_size,
-                filtered_payload_size,
-                self._outgoing_average_ratio_calculator.get_average_ratio())
-
-    def _incoming_filter(self, frame):
-        """Transform incoming frames. This method is called only by
-        an _IncomingFilter instance.
-        """
-
-        received_payload_size = len(frame.payload)
-        self._incoming_average_ratio_calculator.add_result_bytes(
-                received_payload_size)
-
-        if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
-            self._incoming_average_ratio_calculator.add_original_bytes(
-                    received_payload_size)
-            return
-
-        frame.payload = self._rfc1979_inflater.filter(frame.payload)
-        frame.rsv1 = 0
-
-        filtered_payload_size = len(frame.payload)
-        self._incoming_average_ratio_calculator.add_original_bytes(
-                filtered_payload_size)
-
-        _log_incoming_compression_ratio(
-                self._logger,
-                received_payload_size,
-                filtered_payload_size,
-                self._incoming_average_ratio_calculator.get_average_ratio())
-
-
-_available_processors[common.DEFLATE_FRAME_EXTENSION] = (
-    DeflateFrameExtensionProcessor)
-_compression_extension_names.append(common.DEFLATE_FRAME_EXTENSION)
-
-_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
-    DeflateFrameExtensionProcessor)
-_compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
-
-
-def _parse_compression_method(data):
-    """Parses the value of "method" extension parameter."""
-
-    return common.parse_extensions(data)
-
-
-def _create_accepted_method_desc(method_name, method_params):
-    """Creates accepted-method-desc from given method name and parameters"""
-
-    extension = common.ExtensionParameter(method_name)
-    for name, value in method_params:
-        extension.add_parameter(name, value)
-    return common.format_extension(extension)
-
-
-class CompressionExtensionProcessorBase(ExtensionProcessorInterface):
-    """Base class for perframe-compress and permessage-compress extension."""
-
-    _METHOD_PARAM = 'method'
-
-    def __init__(self, request):
-        ExtensionProcessorInterface.__init__(self, request)
-        self._logger = util.get_class_logger(self)
-        self._compression_method_name = None
-        self._compression_processor = None
-        self._compression_processor_hook = None
-
-    def name(self):
-        return ''
-
-    def _lookup_compression_processor(self, method_desc):
-        return None
-
-    def _get_compression_processor_response(self):
-        """Looks up the compression processor based on the self._request and
-           returns the compression processor's response.
-        """
-
-        method_list = self._request.get_parameter_value(self._METHOD_PARAM)
-        if method_list is None:
-            return None
-        methods = _parse_compression_method(method_list)
-        if methods is None:
-            return None
-        comression_processor = None
-        # The current implementation tries only the first method that matches
-        # supported algorithm. Following methods aren't tried even if the
-        # first one is rejected.
-        # TODO(bashi): Need to clarify this behavior.
-        for method_desc in methods:
-            compression_processor = self._lookup_compression_processor(
-                method_desc)
-            if compression_processor is not None:
-                self._compression_method_name = method_desc.name()
-                break
-        if compression_processor is None:
-            return None
-
-        if self._compression_processor_hook:
-            self._compression_processor_hook(compression_processor)
-
-        processor_response = compression_processor.get_extension_response()
-        if processor_response is None:
-            return None
-        self._compression_processor = compression_processor
-        return processor_response
-
-    def _get_extension_response_internal(self):
-        processor_response = self._get_compression_processor_response()
-        if processor_response is None:
-            return None
-
-        response = common.ExtensionParameter(self._request.name())
-        accepted_method_desc = _create_accepted_method_desc(
-                                   self._compression_method_name,
-                                   processor_response.get_parameters())
-        response.add_parameter(self._METHOD_PARAM, accepted_method_desc)
-        self._logger.debug(
-            'Enable %s extension (method: %s)' %
-            (self._request.name(), self._compression_method_name))
-        return response
-
-    def _setup_stream_options_internal(self, stream_options):
-        if self._compression_processor is None:
-            return
-        self._compression_processor.setup_stream_options(stream_options)
-
-    def set_compression_processor_hook(self, hook):
-        self._compression_processor_hook = hook
-
-    def get_compression_processor(self):
-        return self._compression_processor
-
-
-class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
-    """permessage-deflate extension processor. It's also used for
-    permessage-compress extension when the deflate method is chosen.
-
-    Specification:
-    http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08
-    """
-
-    _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits'
-    _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover'
-    _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits'
-    _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
-
-    def __init__(self, request, draft08=True):
-        """Construct PerMessageDeflateExtensionProcessor
-
-        Args:
-            draft08: Follow the constraints on the parameters that were not
-                specified for permessage-compress but are specified for
-                permessage-deflate as on
-                draft-ietf-hybi-permessage-compression-08.
-        """
-
-        ExtensionProcessorInterface.__init__(self, request)
-        self._logger = util.get_class_logger(self)
-
-        self._preferred_client_max_window_bits = None
-        self._client_no_context_takeover = False
-
-        self._draft08 = draft08
-
-    def name(self):
-        return 'deflate'
-
-    def _get_extension_response_internal(self):
-        if self._draft08:
-            for name in self._request.get_parameter_names():
-                if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
-                                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
-                                self._CLIENT_MAX_WINDOW_BITS_PARAM]:
-                    self._logger.debug('Unknown parameter: %r', name)
-                    return None
-        else:
-            # Any unknown parameter will be just ignored.
-            pass
-
-        server_max_window_bits = None
-        if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM):
-            server_max_window_bits = self._request.get_parameter_value(
-                    self._SERVER_MAX_WINDOW_BITS_PARAM)
-            try:
-                server_max_window_bits = _parse_window_bits(
-                    server_max_window_bits)
-            except ValueError, e:
-                self._logger.debug('Bad %s parameter: %r',
-                                   self._SERVER_MAX_WINDOW_BITS_PARAM,
-                                   e)
-                return None
-
-        server_no_context_takeover = self._request.has_parameter(
-            self._SERVER_NO_CONTEXT_TAKEOVER_PARAM)
-        if (server_no_context_takeover and
-            self._request.get_parameter_value(
-                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None):
-            self._logger.debug('%s parameter must not have a value: %r',
-                               self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
-                               server_no_context_takeover)
-            return None
-
-        # client_max_window_bits from a client indicates whether the client can
-        # accept client_max_window_bits from a server or not.
-        client_client_max_window_bits = self._request.has_parameter(
-            self._CLIENT_MAX_WINDOW_BITS_PARAM)
-        if (self._draft08 and
-            client_client_max_window_bits and
-            self._request.get_parameter_value(
-                self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
-            self._logger.debug('%s parameter must not have a value in a '
-                               'client\'s opening handshake: %r',
-                               self._CLIENT_MAX_WINDOW_BITS_PARAM,
-                               client_client_max_window_bits)
-            return None
-
-        self._rfc1979_deflater = util._RFC1979Deflater(
-            server_max_window_bits, server_no_context_takeover)
-
-        # Note that we prepare for incoming messages compressed with window
-        # bits upto 15 regardless of the client_max_window_bits value to be
-        # sent to the client.
-        self._rfc1979_inflater = util._RFC1979Inflater()
-
-        self._framer = _PerMessageDeflateFramer(
-            server_max_window_bits, server_no_context_takeover)
-        self._framer.set_bfinal(False)
-        self._framer.set_compress_outgoing_enabled(True)
-
-        response = common.ExtensionParameter(self._request.name())
-
-        if server_max_window_bits is not None:
-            response.add_parameter(
-                self._SERVER_MAX_WINDOW_BITS_PARAM,
-                str(server_max_window_bits))
-
-        if server_no_context_takeover:
-            response.add_parameter(
-                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None)
-
-        if self._preferred_client_max_window_bits is not None:
-            if self._draft08 and not client_client_max_window_bits:
-                self._logger.debug('Processor is configured to use %s but '
-                                   'the client cannot accept it',
-                                   self._CLIENT_MAX_WINDOW_BITS_PARAM)
-                return None
-            response.add_parameter(
-                self._CLIENT_MAX_WINDOW_BITS_PARAM,
-                str(self._preferred_client_max_window_bits))
-
-        if self._client_no_context_takeover:
-            response.add_parameter(
-                self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None)
-
-        self._logger.debug(
-            'Enable %s extension ('
-            'request: server_max_window_bits=%s; '
-            'server_no_context_takeover=%r, '
-            'response: client_max_window_bits=%s; '
-            'client_no_context_takeover=%r)' %
-            (self._request.name(),
-             server_max_window_bits,
-             server_no_context_takeover,
-             self._preferred_client_max_window_bits,
-             self._client_no_context_takeover))
-
-        return response
-
-    def _setup_stream_options_internal(self, stream_options):
-        self._framer.setup_stream_options(stream_options)
-
-    def set_client_max_window_bits(self, value):
-        """If this option is specified, this class adds the
-        client_max_window_bits extension parameter to the handshake response,
-        but doesn't reduce the LZ77 sliding window size of its inflater.
-        I.e., you can use this for testing client implementation but cannot
-        reduce memory usage of this class.
-
-        If this method has been called with True and an offer without the
-        client_max_window_bits extension parameter is received,
-        - (When processing the permessage-deflate extension) this processor
-          declines the request.
-        - (When processing the permessage-compress extension) this processor
-          accepts the request.
-        """
-
-        self._preferred_client_max_window_bits = value
-
-    def set_client_no_context_takeover(self, value):
-        """If this option is specified, this class adds the
-        client_no_context_takeover extension parameter to the handshake
-        response, but doesn't reset inflater for each message. I.e., you can
-        use this for testing client implementation but cannot reduce memory
-        usage of this class.
-        """
-
-        self._client_no_context_takeover = value
-
-    def set_bfinal(self, value):
-        self._framer.set_bfinal(value)
-
-    def enable_outgoing_compression(self):
-        self._framer.set_compress_outgoing_enabled(True)
-
-    def disable_outgoing_compression(self):
-        self._framer.set_compress_outgoing_enabled(False)
-
-
-class _PerMessageDeflateFramer(object):
-    """A framer for extensions with per-message DEFLATE feature."""
-
-    def __init__(self, deflate_max_window_bits, deflate_no_context_takeover):
-        self._logger = util.get_class_logger(self)
-
-        self._rfc1979_deflater = util._RFC1979Deflater(
-            deflate_max_window_bits, deflate_no_context_takeover)
-
-        self._rfc1979_inflater = util._RFC1979Inflater()
-
-        self._bfinal = False
-
-        self._compress_outgoing_enabled = False
-
-        # True if a message is fragmented and compression is ongoing.
-        self._compress_ongoing = False
-
-        # Calculates
-        #     (Total outgoing bytes supplied to this filter) /
-        #     (Total bytes sent to the network after applying this filter)
-        self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
-
-        # Calculates
-        #     (Total bytes received from the network) /
-        #     (Total incoming bytes obtained after applying this filter)
-        self._incoming_average_ratio_calculator = _AverageRatioCalculator()
-
-    def set_bfinal(self, value):
-        self._bfinal = value
-
-    def set_compress_outgoing_enabled(self, value):
-        self._compress_outgoing_enabled = value
-
-    def _process_incoming_message(self, message, decompress):
-        if not decompress:
-            return message
-
-        received_payload_size = len(message)
-        self._incoming_average_ratio_calculator.add_result_bytes(
-                received_payload_size)
-
-        message = self._rfc1979_inflater.filter(message)
-
-        filtered_payload_size = len(message)
-        self._incoming_average_ratio_calculator.add_original_bytes(
-                filtered_payload_size)
-
-        _log_incoming_compression_ratio(
-                self._logger,
-                received_payload_size,
-                filtered_payload_size,
-                self._incoming_average_ratio_calculator.get_average_ratio())
-
-        return message
-
-    def _process_outgoing_message(self, message, end, binary):
-        if not binary:
-            message = message.encode('utf-8')
-
-        if not self._compress_outgoing_enabled:
-            return message
-
-        original_payload_size = len(message)
-        self._outgoing_average_ratio_calculator.add_original_bytes(
-            original_payload_size)
-
-        message = self._rfc1979_deflater.filter(
-            message, end=end, bfinal=self._bfinal)
-
-        filtered_payload_size = len(message)
-        self._outgoing_average_ratio_calculator.add_result_bytes(
-            filtered_payload_size)
-
-        _log_outgoing_compression_ratio(
-                self._logger,
-                original_payload_size,
-                filtered_payload_size,
-                self._outgoing_average_ratio_calculator.get_average_ratio())
-
-        if not self._compress_ongoing:
-            self._outgoing_frame_filter.set_compression_bit()
-        self._compress_ongoing = not end
-        return message
-
-    def _process_incoming_frame(self, frame):
-        if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode):
-            self._incoming_message_filter.decompress_next_message()
-            frame.rsv1 = 0
-
-    def _process_outgoing_frame(self, frame, compression_bit):
-        if (not compression_bit or
-            common.is_control_opcode(frame.opcode)):
-            return
-
-        frame.rsv1 = 1
-
-    def setup_stream_options(self, stream_options):
-        """Creates filters and sets them to the StreamOptions."""
-
-        class _OutgoingMessageFilter(object):
-
-            def __init__(self, parent):
-                self._parent = parent
-
-            def filter(self, message, end=True, binary=False):
-                return self._parent._process_outgoing_message(
-                    message, end, binary)
-
-        class _IncomingMessageFilter(object):
-
-            def __init__(self, parent):
-                self._parent = parent
-                self._decompress_next_message = False
-
-            def decompress_next_message(self):
-                self._decompress_next_message = True
-
-            def filter(self, message):
-                message = self._parent._process_incoming_message(
-                    message, self._decompress_next_message)
-                self._decompress_next_message = False
-                return message
-
-        self._outgoing_message_filter = _OutgoingMessageFilter(self)
-        self._incoming_message_filter = _IncomingMessageFilter(self)
-        stream_options.outgoing_message_filters.append(
-            self._outgoing_message_filter)
-        stream_options.incoming_message_filters.append(
-            self._incoming_message_filter)
-
-        class _OutgoingFrameFilter(object):
-
-            def __init__(self, parent):
-                self._parent = parent
-                self._set_compression_bit = False
-
-            def set_compression_bit(self):
-                self._set_compression_bit = True
-
-            def filter(self, frame):
-                self._parent._process_outgoing_frame(
-                    frame, self._set_compression_bit)
-                self._set_compression_bit = False
-
-        class _IncomingFrameFilter(object):
-
-            def __init__(self, parent):
-                self._parent = parent
-
-            def filter(self, frame):
-                self._parent._process_incoming_frame(frame)
-
-        self._outgoing_frame_filter = _OutgoingFrameFilter(self)
-        self._incoming_frame_filter = _IncomingFrameFilter(self)
-        stream_options.outgoing_frame_filters.append(
-            self._outgoing_frame_filter)
-        stream_options.incoming_frame_filters.append(
-            self._incoming_frame_filter)
-
-        stream_options.encode_text_message_to_utf8 = False
-
-
-_available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = (
-        PerMessageDeflateExtensionProcessor)
-# TODO(tyoshino): Reorganize class names.
-_compression_extension_names.append('deflate')
-
-
-class PerMessageCompressExtensionProcessor(
-    CompressionExtensionProcessorBase):
-    """permessage-compress extension processor.
-
-    Specification:
-    http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression
-    """
-
-    _DEFLATE_METHOD = 'deflate'
-
-    def __init__(self, request):
-        CompressionExtensionProcessorBase.__init__(self, request)
-
-    def name(self):
-        return common.PERMESSAGE_COMPRESSION_EXTENSION
-
-    def _lookup_compression_processor(self, method_desc):
-        if method_desc.name() == self._DEFLATE_METHOD:
-            return PerMessageDeflateExtensionProcessor(method_desc, False)
-        return None
-
-
-_available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = (
-    PerMessageCompressExtensionProcessor)
-_compression_extension_names.append(common.PERMESSAGE_COMPRESSION_EXTENSION)
-
-
-class MuxExtensionProcessor(ExtensionProcessorInterface):
-    """WebSocket multiplexing extension processor."""
-
-    _QUOTA_PARAM = 'quota'
-
-    def __init__(self, request):
-        ExtensionProcessorInterface.__init__(self, request)
-        self._quota = 0
-        self._extensions = []
-
-    def name(self):
-        return common.MUX_EXTENSION
-
-    def check_consistency_with_other_processors(self, processors):
-        before_mux = True
-        for processor in processors:
-            name = processor.name()
-            if name == self.name():
-                before_mux = False
-                continue
-            if not processor.is_active():
-                continue
-            if before_mux:
-                # Mux extension cannot be used after extensions
-                # that depend on frame boundary, extension data field, or any
-                # reserved bits which are attributed to each frame.
-                if (name == common.DEFLATE_FRAME_EXTENSION or
-                    name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
-                    self.set_active(False)
-                    return
-            else:
-                # Mux extension should not be applied before any history-based
-                # compression extension.
-                if (name == common.DEFLATE_FRAME_EXTENSION or
-                    name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or
-                    name == common.PERMESSAGE_COMPRESSION_EXTENSION or
-                    name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION):
-                    self.set_active(False)
-                    return
-
-    def _get_extension_response_internal(self):
-        self._active = False
-        quota = self._request.get_parameter_value(self._QUOTA_PARAM)
-        if quota is not None:
-            try:
-                quota = int(quota)
-            except ValueError, e:
-                return None
-            if quota < 0 or quota >= 2 ** 32:
-                return None
-            self._quota = quota
-
-        self._active = True
-        return common.ExtensionParameter(common.MUX_EXTENSION)
-
-    def _setup_stream_options_internal(self, stream_options):
-        pass
-
-    def set_quota(self, quota):
-        self._quota = quota
-
-    def quota(self):
-        return self._quota
-
-    def set_extensions(self, extensions):
-        self._extensions = extensions
-
-    def extensions(self):
-        return self._extensions
-
-
-_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor
-
-
-def get_extension_processor(extension_request):
-    """Given an ExtensionParameter representing an extension offer received
-    from a client, configures and returns an instance of the corresponding
-    extension processor class.
-    """
-
-    processor_class = _available_processors.get(extension_request.name())
-    if processor_class is None:
-        return None
-    return processor_class(extension_request)
-
-
-def is_compression_extension(extension_name):
-    return extension_name in _compression_extension_names
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/headerparserhandler.py b/tools/pywebsocket/src/mod_pywebsocket/headerparserhandler.py
deleted file mode 100644
index c244421..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/headerparserhandler.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# Copyright 2011, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""PythonHeaderParserHandler for mod_pywebsocket.
-
-Apache HTTP Server and mod_python must be configured such that this
-function is called to handle WebSocket request.
-"""
-
-
-import logging
-
-from mod_python import apache
-
-from mod_pywebsocket import common
-from mod_pywebsocket import dispatch
-from mod_pywebsocket import handshake
-from mod_pywebsocket import util
-
-
-# PythonOption to specify the handler root directory.
-_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root'
-
-# PythonOption to specify the handler scan directory.
-# This must be a directory under the root directory.
-# The default is the root directory.
-_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan'
-
-# PythonOption to allow handlers whose canonical path is
-# not under the root directory. It's disallowed by default.
-# Set this option with value of 'yes' to allow.
-_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = (
-    'mod_pywebsocket.allow_handlers_outside_root_dir')
-# Map from values to their meanings. 'Yes' and 'No' are allowed just for
-# compatibility.
-_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = {
-    'off': False, 'no': False, 'on': True, 'yes': True}
-
-# (Obsolete option. Ignored.)
-# PythonOption to specify to allow handshake defined in Hixie 75 version
-# protocol. The default is None (Off)
-_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75'
-# Map from values to their meanings.
-_PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True}
-
-
-class ApacheLogHandler(logging.Handler):
-    """Wrapper logging.Handler to emit log message to apache's error.log."""
-
-    _LEVELS = {
-        logging.DEBUG: apache.APLOG_DEBUG,
-        logging.INFO: apache.APLOG_INFO,
-        logging.WARNING: apache.APLOG_WARNING,
-        logging.ERROR: apache.APLOG_ERR,
-        logging.CRITICAL: apache.APLOG_CRIT,
-        }
-
-    def __init__(self, request=None):
-        logging.Handler.__init__(self)
-        self._log_error = apache.log_error
-        if request is not None:
-            self._log_error = request.log_error
-
-        # Time and level will be printed by Apache.
-        self._formatter = logging.Formatter('%(name)s: %(message)s')
-
-    def emit(self, record):
-        apache_level = apache.APLOG_DEBUG
-        if record.levelno in ApacheLogHandler._LEVELS:
-            apache_level = ApacheLogHandler._LEVELS[record.levelno]
-
-        msg = self._formatter.format(record)
-
-        # "server" parameter must be passed to have "level" parameter work.
-        # If only "level" parameter is passed, nothing shows up on Apache's
-        # log. However, at this point, we cannot get the server object of the
-        # virtual host which will process WebSocket requests. The only server
-        # object we can get here is apache.main_server. But Wherever (server
-        # configuration context or virtual host context) we put
-        # PythonHeaderParserHandler directive, apache.main_server just points
-        # the main server instance (not any of virtual server instance). Then,
-        # Apache follows LogLevel directive in the server configuration context
-        # to filter logs. So, we need to specify LogLevel in the server
-        # configuration context. Even if we specify "LogLevel debug" in the
-        # virtual host context which actually handles WebSocket connections,
-        # DEBUG level logs never show up unless "LogLevel debug" is specified
-        # in the server configuration context.
-        #
-        # TODO(tyoshino): Provide logging methods on request object. When
-        # request is mp_request object (when used together with Apache), the
-        # methods call request.log_error indirectly. When request is
-        # _StandaloneRequest, the methods call Python's logging facility which
-        # we create in standalone.py.
-        self._log_error(msg, apache_level, apache.main_server)
-
-
-def _configure_logging():
-    logger = logging.getLogger()
-    # Logs are filtered by Apache based on LogLevel directive in Apache
-    # configuration file. We must just pass logs for all levels to
-    # ApacheLogHandler.
-    logger.setLevel(logging.DEBUG)
-    logger.addHandler(ApacheLogHandler())
-
-
-_configure_logging()
-
-_LOGGER = logging.getLogger(__name__)
-
-
-def _parse_option(name, value, definition):
-    if value is None:
-        return False
-
-    meaning = definition.get(value.lower())
-    if meaning is None:
-        raise Exception('Invalid value for PythonOption %s: %r' %
-                        (name, value))
-    return meaning
-
-
-def _create_dispatcher():
-    _LOGGER.info('Initializing Dispatcher')
-
-    options = apache.main_server.get_options()
-
-    handler_root = options.get(_PYOPT_HANDLER_ROOT, None)
-    if not handler_root:
-        raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT,
-                        apache.APLOG_ERR)
-
-    handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root)
-
-    allow_handlers_outside_root = _parse_option(
-        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT,
-        options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT),
-        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION)
-
-    dispatcher = dispatch.Dispatcher(
-        handler_root, handler_scan, allow_handlers_outside_root)
-
-    for warning in dispatcher.source_warnings():
-        apache.log_error(
-            'mod_pywebsocket: Warning in source loading: %s' % warning,
-            apache.APLOG_WARNING)
-
-    return dispatcher
-
-
-# Initialize
-_dispatcher = _create_dispatcher()
-
-
-def headerparserhandler(request):
-    """Handle request.
-
-    Args:
-        request: mod_python request.
-
-    This function is named headerparserhandler because it is the default
-    name for a PythonHeaderParserHandler.
-    """
-
-    handshake_is_done = False
-    try:
-        # Fallback to default http handler for request paths for which
-        # we don't have request handlers.
-        if not _dispatcher.get_handler_suite(request.uri):
-            request.log_error(
-                'mod_pywebsocket: No handler for resource: %r' % request.uri,
-                apache.APLOG_INFO)
-            request.log_error(
-                'mod_pywebsocket: Fallback to Apache', apache.APLOG_INFO)
-            return apache.DECLINED
-    except dispatch.DispatchException, e:
-        request.log_error(
-            'mod_pywebsocket: Dispatch failed for error: %s' % e,
-            apache.APLOG_INFO)
-        if not handshake_is_done:
-            return e.status
-
-    try:
-        allow_draft75 = _parse_option(
-            _PYOPT_ALLOW_DRAFT75,
-            apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75),
-            _PYOPT_ALLOW_DRAFT75_DEFINITION)
-
-        try:
-            handshake.do_handshake(
-                request, _dispatcher, allowDraft75=allow_draft75)
-        except handshake.VersionException, e:
-            request.log_error(
-                'mod_pywebsocket: Handshake failed for version error: %s' % e,
-                apache.APLOG_INFO)
-            request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER,
-                                        e.supported_versions)
-            return apache.HTTP_BAD_REQUEST
-        except handshake.HandshakeException, e:
-            # Handshake for ws/wss failed.
-            # Send http response with error status.
-            request.log_error(
-                'mod_pywebsocket: Handshake failed for error: %s' % e,
-                apache.APLOG_INFO)
-            return e.status
-
-        handshake_is_done = True
-        request._dispatcher = _dispatcher
-        _dispatcher.transfer_data(request)
-    except handshake.AbortedByUserException, e:
-        request.log_error('mod_pywebsocket: Aborted: %s' % e, apache.APLOG_INFO)
-    except Exception, e:
-        # DispatchException can also be thrown if something is wrong in
-        # pywebsocket code. It's caught here, then.
-
-        request.log_error('mod_pywebsocket: Exception occurred: %s\n%s' %
-                          (e, util.get_stack_trace()),
-                          apache.APLOG_ERR)
-        # Unknown exceptions before handshake mean Apache must handle its
-        # request with another handler.
-        if not handshake_is_done:
-            return apache.DECLINED
-    # Set assbackwards to suppress response header generation by Apache.
-    request.assbackwards = 1
-    return apache.DONE  # Return DONE such that no other handlers are invoked.
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/memorizingfile.py b/tools/pywebsocket/src/mod_pywebsocket/memorizingfile.py
deleted file mode 100644
index 4d4cd95..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/memorizingfile.py
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2011, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""Memorizing file.
-
-A memorizing file wraps a file and memorizes lines read by readline.
-"""
-
-
-import sys
-
-
-class MemorizingFile(object):
-    """MemorizingFile wraps a file and memorizes lines read by readline.
-
-    Note that data read by other methods are not memorized. This behavior
-    is good enough for memorizing lines SimpleHTTPServer reads before
-    the control reaches WebSocketRequestHandler.
-    """
-
-    def __init__(self, file_, max_memorized_lines=sys.maxint):
-        """Construct an instance.
-
-        Args:
-            file_: the file object to wrap.
-            max_memorized_lines: the maximum number of lines to memorize.
-                Only the first max_memorized_lines are memorized.
-                Default: sys.maxint.
-        """
-
-        self._file = file_
-        self._memorized_lines = []
-        self._max_memorized_lines = max_memorized_lines
-        self._buffered = False
-        self._buffered_line = None
-
-    def __getattribute__(self, name):
-        if name in ('_file', '_memorized_lines', '_max_memorized_lines',
-                    '_buffered', '_buffered_line', 'readline',
-                    'get_memorized_lines'):
-            return object.__getattribute__(self, name)
-        return self._file.__getattribute__(name)
-
-    def readline(self, size=-1):
-        """Override file.readline and memorize the line read.
-
-        Note that even if size is specified and smaller than actual size,
-        the whole line will be read out from underlying file object by
-        subsequent readline calls.
-        """
-
-        if self._buffered:
-            line = self._buffered_line
-            self._buffered = False
-        else:
-            line = self._file.readline()
-            if line and len(self._memorized_lines) < self._max_memorized_lines:
-                self._memorized_lines.append(line)
-        if size >= 0 and size < len(line):
-            self._buffered = True
-            self._buffered_line = line[size:]
-            return line[:size]
-        return line
-
-    def get_memorized_lines(self):
-        """Get lines memorized so far."""
-        return self._memorized_lines
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/mux.py b/tools/pywebsocket/src/mod_pywebsocket/mux.py
deleted file mode 100644
index 7633468..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/mux.py
+++ /dev/null
@@ -1,1889 +0,0 @@
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""This file provides classes and helper functions for multiplexing extension.
-
-Specification:
-http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-06
-"""
-
-
-import collections
-import copy
-import email
-import email.parser
-import logging
-import math
-import struct
-import threading
-import traceback
-
-from mod_pywebsocket import common
-from mod_pywebsocket import handshake
-from mod_pywebsocket import util
-from mod_pywebsocket._stream_base import BadOperationException
-from mod_pywebsocket._stream_base import ConnectionTerminatedException
-from mod_pywebsocket._stream_base import InvalidFrameException
-from mod_pywebsocket._stream_hybi import Frame
-from mod_pywebsocket._stream_hybi import Stream
-from mod_pywebsocket._stream_hybi import StreamOptions
-from mod_pywebsocket._stream_hybi import create_binary_frame
-from mod_pywebsocket._stream_hybi import create_closing_handshake_body
-from mod_pywebsocket._stream_hybi import create_header
-from mod_pywebsocket._stream_hybi import create_length_header
-from mod_pywebsocket._stream_hybi import parse_frame
-from mod_pywebsocket.handshake import hybi
-
-
-_CONTROL_CHANNEL_ID = 0
-_DEFAULT_CHANNEL_ID = 1
-
-_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0
-_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1
-_MUX_OPCODE_FLOW_CONTROL = 2
-_MUX_OPCODE_DROP_CHANNEL = 3
-_MUX_OPCODE_NEW_CHANNEL_SLOT = 4
-
-_MAX_CHANNEL_ID = 2 ** 29 - 1
-
-_INITIAL_NUMBER_OF_CHANNEL_SLOTS = 64
-_INITIAL_QUOTA_FOR_CLIENT = 8 * 1024
-
-_HANDSHAKE_ENCODING_IDENTITY = 0
-_HANDSHAKE_ENCODING_DELTA = 1
-
-# We need only these status code for now.
-_HTTP_BAD_RESPONSE_MESSAGES = {
-    common.HTTP_STATUS_BAD_REQUEST: 'Bad Request',
-}
-
-# DropChannel reason code
-# TODO(bashi): Define all reason code defined in -05 draft.
-_DROP_CODE_NORMAL_CLOSURE = 1000
-
-_DROP_CODE_INVALID_ENCAPSULATING_MESSAGE = 2001
-_DROP_CODE_CHANNEL_ID_TRUNCATED = 2002
-_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED = 2003
-_DROP_CODE_UNKNOWN_MUX_OPCODE = 2004
-_DROP_CODE_INVALID_MUX_CONTROL_BLOCK = 2005
-_DROP_CODE_CHANNEL_ALREADY_EXISTS = 2006
-_DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION = 2007
-_DROP_CODE_UNKNOWN_REQUEST_ENCODING = 2010
-
-_DROP_CODE_SEND_QUOTA_VIOLATION = 3005
-_DROP_CODE_SEND_QUOTA_OVERFLOW = 3006
-_DROP_CODE_ACKNOWLEDGED = 3008
-_DROP_CODE_BAD_FRAGMENTATION = 3009
-
-
-class MuxUnexpectedException(Exception):
-    """Exception in handling multiplexing extension."""
-    pass
-
-
-# Temporary
-class MuxNotImplementedException(Exception):
-    """Raised when a flow enters unimplemented code path."""
-    pass
-
-
-class LogicalConnectionClosedException(Exception):
-    """Raised when logical connection is gracefully closed."""
-    pass
-
-
-class PhysicalConnectionError(Exception):
-    """Raised when there is a physical connection error."""
-    def __init__(self, drop_code, message=''):
-        super(PhysicalConnectionError, self).__init__(
-            'code=%d, message=%r' % (drop_code, message))
-        self.drop_code = drop_code
-        self.message = message
-
-
-class LogicalChannelError(Exception):
-    """Raised when there is a logical channel error."""
-    def __init__(self, channel_id, drop_code, message=''):
-        super(LogicalChannelError, self).__init__(
-            'channel_id=%d, code=%d, message=%r' % (
-                channel_id, drop_code, message))
-        self.channel_id = channel_id
-        self.drop_code = drop_code
-        self.message = message
-
-
-def _encode_channel_id(channel_id):
-    if channel_id < 0:
-        raise ValueError('Channel id %d must not be negative' % channel_id)
-
-    if channel_id < 2 ** 7:
-        return chr(channel_id)
-    if channel_id < 2 ** 14:
-        return struct.pack('!H', 0x8000 + channel_id)
-    if channel_id < 2 ** 21:
-        first = chr(0xc0 + (channel_id >> 16))
-        return first + struct.pack('!H', channel_id & 0xffff)
-    if channel_id < 2 ** 29:
-        return struct.pack('!L', 0xe0000000 + channel_id)
-
-    raise ValueError('Channel id %d is too large' % channel_id)
-
-
-def _encode_number(number):
-    return create_length_header(number, False)
-
-
-def _create_add_channel_response(channel_id, encoded_handshake,
-                                 encoding=0, rejected=False):
-    if encoding != 0 and encoding != 1:
-        raise ValueError('Invalid encoding %d' % encoding)
-
-    first_byte = ((_MUX_OPCODE_ADD_CHANNEL_RESPONSE << 5) |
-                  (rejected << 4) | encoding)
-    block = (chr(first_byte) +
-             _encode_channel_id(channel_id) +
-             _encode_number(len(encoded_handshake)) +
-             encoded_handshake)
-    return block
-
-
-def _create_drop_channel(channel_id, code=None, message=''):
-    if len(message) > 0 and code is None:
-        raise ValueError('Code must be specified if message is specified')
-
-    first_byte = _MUX_OPCODE_DROP_CHANNEL << 5
-    block = chr(first_byte) + _encode_channel_id(channel_id)
-    if code is None:
-        block += _encode_number(0) # Reason size
-    else:
-        reason = struct.pack('!H', code) + message
-        reason_size = _encode_number(len(reason))
-        block += reason_size + reason
-
-    return block
-
-
-def _create_flow_control(channel_id, replenished_quota):
-    first_byte = _MUX_OPCODE_FLOW_CONTROL << 5
-    block = (chr(first_byte) +
-             _encode_channel_id(channel_id) +
-             _encode_number(replenished_quota))
-    return block
-
-
-def _create_new_channel_slot(slots, send_quota):
-    if slots < 0 or send_quota < 0:
-        raise ValueError('slots and send_quota must be non-negative.')
-    first_byte = _MUX_OPCODE_NEW_CHANNEL_SLOT << 5
-    block = (chr(first_byte) +
-             _encode_number(slots) +
-             _encode_number(send_quota))
-    return block
-
-
-def _create_fallback_new_channel_slot():
-    first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag
-    block = (chr(first_byte) + _encode_number(0) + _encode_number(0))
-    return block
-
-
-def _parse_request_text(request_text):
-    request_line, header_lines = request_text.split('\r\n', 1)
-
-    words = request_line.split(' ')
-    if len(words) != 3:
-        raise ValueError('Bad Request-Line syntax %r' % request_line)
-    [command, path, version] = words
-    if version != 'HTTP/1.1':
-        raise ValueError('Bad request version %r' % version)
-
-    # email.parser.Parser() parses RFC 2822 (RFC 822) style headers.
-    # RFC 6455 refers RFC 2616 for handshake parsing, and RFC 2616 refers
-    # RFC 822.
-    headers = email.parser.Parser().parsestr(header_lines)
-    return command, path, version, headers
-
-
-class _ControlBlock(object):
-    """A structure that holds parsing result of multiplexing control block.
-    Control block specific attributes will be added by _MuxFramePayloadParser.
-    (e.g. encoded_handshake will be added for AddChannelRequest and
-    AddChannelResponse)
-    """
-
-    def __init__(self, opcode):
-        self.opcode = opcode
-
-
-class _MuxFramePayloadParser(object):
-    """A class that parses multiplexed frame payload."""
-
-    def __init__(self, payload):
-        self._data = payload
-        self._read_position = 0
-        self._logger = util.get_class_logger(self)
-
-    def read_channel_id(self):
-        """Reads channel id.
-
-        Raises:
-            ValueError: when the payload doesn't contain
-                valid channel id.
-        """
-
-        remaining_length = len(self._data) - self._read_position
-        pos = self._read_position
-        if remaining_length == 0:
-            raise ValueError('Invalid channel id format')
-
-        channel_id = ord(self._data[pos])
-        channel_id_length = 1
-        if channel_id & 0xe0 == 0xe0:
-            if remaining_length < 4:
-                raise ValueError('Invalid channel id format')
-            channel_id = struct.unpack('!L',
-                                       self._data[pos:pos+4])[0] & 0x1fffffff
-            channel_id_length = 4
-        elif channel_id & 0xc0 == 0xc0:
-            if remaining_length < 3:
-                raise ValueError('Invalid channel id format')
-            channel_id = (((channel_id & 0x1f) << 16) +
-                          struct.unpack('!H', self._data[pos+1:pos+3])[0])
-            channel_id_length = 3
-        elif channel_id & 0x80 == 0x80:
-            if remaining_length < 2:
-                raise ValueError('Invalid channel id format')
-            channel_id = struct.unpack('!H',
-                                       self._data[pos:pos+2])[0] & 0x3fff
-            channel_id_length = 2
-        self._read_position += channel_id_length
-
-        return channel_id
-
-    def read_inner_frame(self):
-        """Reads an inner frame.
-
-        Raises:
-            PhysicalConnectionError: when the inner frame is invalid.
-        """
-
-        if len(self._data) == self._read_position:
-            raise PhysicalConnectionError(
-                _DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
-
-        bits = ord(self._data[self._read_position])
-        self._read_position += 1
-        fin = (bits & 0x80) == 0x80
-        rsv1 = (bits & 0x40) == 0x40
-        rsv2 = (bits & 0x20) == 0x20
-        rsv3 = (bits & 0x10) == 0x10
-        opcode = bits & 0xf
-        payload = self.remaining_data()
-        # Consume rest of the message which is payload data of the original
-        # frame.
-        self._read_position = len(self._data)
-        return fin, rsv1, rsv2, rsv3, opcode, payload
-
-    def _read_number(self):
-        if self._read_position + 1 > len(self._data):
-            raise ValueError(
-                'Cannot read the first byte of number field')
-
-        number = ord(self._data[self._read_position])
-        if number & 0x80 == 0x80:
-            raise ValueError(
-                'The most significant bit of the first byte of number should '
-                'be unset')
-        self._read_position += 1
-        pos = self._read_position
-        if number == 127:
-            if pos + 8 > len(self._data):
-                raise ValueError('Invalid number field')
-            self._read_position += 8
-            number = struct.unpack('!Q', self._data[pos:pos+8])[0]
-            if number > 0x7FFFFFFFFFFFFFFF:
-                raise ValueError('Encoded number(%d) >= 2^63' % number)
-            if number <= 0xFFFF:
-                raise ValueError(
-                    '%d should not be encoded by 9 bytes encoding' % number)
-            return number
-        if number == 126:
-            if pos + 2 > len(self._data):
-                raise ValueError('Invalid number field')
-            self._read_position += 2
-            number = struct.unpack('!H', self._data[pos:pos+2])[0]
-            if number <= 125:
-                raise ValueError(
-                    '%d should not be encoded by 3 bytes encoding' % number)
-        return number
-
-    def _read_size_and_contents(self):
-        """Reads data that consists of followings:
-            - the size of the contents encoded the same way as payload length
-              of the WebSocket Protocol with 1 bit padding at the head.
-            - the contents.
-        """
-
-        try:
-            size = self._read_number()
-        except ValueError, e:
-            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                                          str(e))
-        pos = self._read_position
-        if pos + size > len(self._data):
-            raise PhysicalConnectionError(
-                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                'Cannot read %d bytes data' % size)
-
-        self._read_position += size
-        return self._data[pos:pos+size]
-
-    def _read_add_channel_request(self, first_byte, control_block):
-        reserved = (first_byte >> 2) & 0x7
-        if reserved != 0:
-            raise PhysicalConnectionError(
-                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                'Reserved bits must be unset')
-
-        # Invalid encoding will be handled by MuxHandler.
-        encoding = first_byte & 0x3
-        try:
-            control_block.channel_id = self.read_channel_id()
-        except ValueError, e:
-            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
-        control_block.encoding = encoding
-        encoded_handshake = self._read_size_and_contents()
-        control_block.encoded_handshake = encoded_handshake
-        return control_block
-
-    def _read_add_channel_response(self, first_byte, control_block):
-        reserved = (first_byte >> 2) & 0x3
-        if reserved != 0:
-            raise PhysicalConnectionError(
-                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                'Reserved bits must be unset')
-
-        control_block.accepted = (first_byte >> 4) & 1
-        control_block.encoding = first_byte & 0x3
-        try:
-            control_block.channel_id = self.read_channel_id()
-        except ValueError, e:
-            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
-        control_block.encoded_handshake = self._read_size_and_contents()
-        return control_block
-
-    def _read_flow_control(self, first_byte, control_block):
-        reserved = first_byte & 0x1f
-        if reserved != 0:
-            raise PhysicalConnectionError(
-                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                'Reserved bits must be unset')
-
-        try:
-            control_block.channel_id = self.read_channel_id()
-            control_block.send_quota = self._read_number()
-        except ValueError, e:
-            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                                          str(e))
-
-        return control_block
-
-    def _read_drop_channel(self, first_byte, control_block):
-        reserved = first_byte & 0x1f
-        if reserved != 0:
-            raise PhysicalConnectionError(
-                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                'Reserved bits must be unset')
-
-        try:
-            control_block.channel_id = self.read_channel_id()
-        except ValueError, e:
-            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
-        reason = self._read_size_and_contents()
-        if len(reason) == 0:
-            control_block.drop_code = None
-            control_block.drop_message = ''
-        elif len(reason) >= 2:
-            control_block.drop_code = struct.unpack('!H', reason[:2])[0]
-            control_block.drop_message = reason[2:]
-        else:
-            raise PhysicalConnectionError(
-                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                'Received DropChannel that conains only 1-byte reason')
-        return control_block
-
-    def _read_new_channel_slot(self, first_byte, control_block):
-        reserved = first_byte & 0x1e
-        if reserved != 0:
-            raise PhysicalConnectionError(
-                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                'Reserved bits must be unset')
-        control_block.fallback = first_byte & 1
-        try:
-            control_block.slots = self._read_number()
-            control_block.send_quota = self._read_number()
-        except ValueError, e:
-            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                                          str(e))
-        return control_block
-
-    def read_control_blocks(self):
-        """Reads control block(s).
-
-        Raises:
-           PhysicalConnectionError: when the payload contains invalid control
-               block(s).
-           StopIteration: when no control blocks left.
-        """
-
-        while self._read_position < len(self._data):
-            first_byte = ord(self._data[self._read_position])
-            self._read_position += 1
-            opcode = (first_byte >> 5) & 0x7
-            control_block = _ControlBlock(opcode=opcode)
-            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
-                yield self._read_add_channel_request(first_byte, control_block)
-            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
-                yield self._read_add_channel_response(
-                    first_byte, control_block)
-            elif opcode == _MUX_OPCODE_FLOW_CONTROL:
-                yield self._read_flow_control(first_byte, control_block)
-            elif opcode == _MUX_OPCODE_DROP_CHANNEL:
-                yield self._read_drop_channel(first_byte, control_block)
-            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
-                yield self._read_new_channel_slot(first_byte, control_block)
-            else:
-                raise PhysicalConnectionError(
-                    _DROP_CODE_UNKNOWN_MUX_OPCODE,
-                    'Invalid opcode %d' % opcode)
-
-        assert self._read_position == len(self._data)
-        raise StopIteration
-
-    def remaining_data(self):
-        """Returns remaining data."""
-
-        return self._data[self._read_position:]
-
-
-class _LogicalRequest(object):
-    """Mimics mod_python request."""
-
-    def __init__(self, channel_id, command, path, protocol, headers,
-                 connection):
-        """Constructs an instance.
-
-        Args:
-            channel_id: the channel id of the logical channel.
-            command: HTTP request command.
-            path: HTTP request path.
-            headers: HTTP headers.
-            connection: _LogicalConnection instance.
-        """
-
-        self.channel_id = channel_id
-        self.method = command
-        self.uri = path
-        self.protocol = protocol
-        self.headers_in = headers
-        self.connection = connection
-        self.server_terminated = False
-        self.client_terminated = False
-
-    def is_https(self):
-        """Mimics request.is_https(). Returns False because this method is
-        used only by old protocols (hixie and hybi00).
-        """
-
-        return False
-
-
-class _LogicalConnection(object):
-    """Mimics mod_python mp_conn."""
-
-    # For details, see the comment of set_read_state().
-    STATE_ACTIVE = 1
-    STATE_GRACEFULLY_CLOSED = 2
-    STATE_TERMINATED = 3
-
-    def __init__(self, mux_handler, channel_id):
-        """Constructs an instance.
-
-        Args:
-            mux_handler: _MuxHandler instance.
-            channel_id: channel id of this connection.
-        """
-
-        self._mux_handler = mux_handler
-        self._channel_id = channel_id
-        self._incoming_data = ''
-
-        # - Protects _waiting_write_completion
-        # - Signals the thread waiting for completion of write by mux handler
-        self._write_condition = threading.Condition()
-        self._waiting_write_completion = False
-
-        self._read_condition = threading.Condition()
-        self._read_state = self.STATE_ACTIVE
-
-    def get_local_addr(self):
-        """Getter to mimic mp_conn.local_addr."""
-
-        return self._mux_handler.physical_connection.get_local_addr()
-    local_addr = property(get_local_addr)
-
-    def get_remote_addr(self):
-        """Getter to mimic mp_conn.remote_addr."""
-
-        return self._mux_handler.physical_connection.get_remote_addr()
-    remote_addr = property(get_remote_addr)
-
-    def get_memorized_lines(self):
-        """Gets memorized lines. Not supported."""
-
-        raise MuxUnexpectedException('_LogicalConnection does not support '
-                                     'get_memorized_lines')
-
-    def write(self, data):
-        """Writes data. mux_handler sends data asynchronously. The caller will
-        be suspended until write done.
-
-        Args:
-            data: data to be written.
-
-        Raises:
-            MuxUnexpectedException: when called before finishing the previous
-                write.
-        """
-
-        try:
-            self._write_condition.acquire()
-            if self._waiting_write_completion:
-                raise MuxUnexpectedException(
-                    'Logical connection %d is already waiting the completion '
-                    'of write' % self._channel_id)
-
-            self._waiting_write_completion = True
-            self._mux_handler.send_data(self._channel_id, data)
-            self._write_condition.wait()
-            # TODO(tyoshino): Raise an exception if woke up by on_writer_done.
-        finally:
-            self._write_condition.release()
-
-    def write_control_data(self, data):
-        """Writes data via the control channel. Don't wait finishing write
-        because this method can be called by mux dispatcher.
-
-        Args:
-            data: data to be written.
-        """
-
-        self._mux_handler.send_control_data(data)
-
-    def on_write_data_done(self):
-        """Called when sending data is completed."""
-
-        try:
-            self._write_condition.acquire()
-            if not self._waiting_write_completion:
-                raise MuxUnexpectedException(
-                    'Invalid call of on_write_data_done for logical '
-                    'connection %d' % self._channel_id)
-            self._waiting_write_completion = False
-            self._write_condition.notify()
-        finally:
-            self._write_condition.release()
-
-    def on_writer_done(self):
-        """Called by the mux handler when the writer thread has finished."""
-
-        try:
-            self._write_condition.acquire()
-            self._waiting_write_completion = False
-            self._write_condition.notify()
-        finally:
-            self._write_condition.release()
-
-
-    def append_frame_data(self, frame_data):
-        """Appends incoming frame data. Called when mux_handler dispatches
-        frame data to the corresponding application.
-
-        Args:
-            frame_data: incoming frame data.
-        """
-
-        self._read_condition.acquire()
-        self._incoming_data += frame_data
-        self._read_condition.notify()
-        self._read_condition.release()
-
-    def read(self, length):
-        """Reads data. Blocks until enough data has arrived via physical
-        connection.
-
-        Args:
-            length: length of data to be read.
-        Raises:
-            LogicalConnectionClosedException: when closing handshake for this
-                logical channel has been received.
-            ConnectionTerminatedException: when the physical connection has
-                closed, or an error is caused on the reader thread.
-        """
-
-        self._read_condition.acquire()
-        while (self._read_state == self.STATE_ACTIVE and
-               len(self._incoming_data) < length):
-            self._read_condition.wait()
-
-        try:
-            if self._read_state == self.STATE_GRACEFULLY_CLOSED:
-                raise LogicalConnectionClosedException(
-                    'Logical channel %d has closed.' % self._channel_id)
-            elif self._read_state == self.STATE_TERMINATED:
-                raise ConnectionTerminatedException(
-                    'Receiving %d byte failed. Logical channel (%d) closed' %
-                    (length, self._channel_id))
-
-            value = self._incoming_data[:length]
-            self._incoming_data = self._incoming_data[length:]
-        finally:
-            self._read_condition.release()
-
-        return value
-
-    def set_read_state(self, new_state):
-        """Sets the state of this connection. Called when an event for this
-        connection has occurred.
-
-        Args:
-            new_state: state to be set. new_state must be one of followings:
-            - STATE_GRACEFULLY_CLOSED: when closing handshake for this
-                connection has been received.
-            - STATE_TERMINATED: when the physical connection has closed or
-                DropChannel of this connection has received.
-        """
-
-        self._read_condition.acquire()
-        self._read_state = new_state
-        self._read_condition.notify()
-        self._read_condition.release()
-
-
-class _InnerMessage(object):
-    """Holds the result of _InnerMessageBuilder.build().
-    """
-
-    def __init__(self, opcode, payload):
-        self.opcode = opcode
-        self.payload = payload
-
-
-class _InnerMessageBuilder(object):
-    """A class that holds the context of inner message fragmentation and
-    builds a message from fragmented inner frame(s).
-    """
-
-    def __init__(self):
-        self._control_opcode = None
-        self._pending_control_fragments = []
-        self._message_opcode = None
-        self._pending_message_fragments = []
-        self._frame_handler = self._handle_first
-
-    def _handle_first(self, frame):
-        if frame.opcode == common.OPCODE_CONTINUATION:
-            raise InvalidFrameException('Sending invalid continuation opcode')
-
-        if common.is_control_opcode(frame.opcode):
-            return self._process_first_fragmented_control(frame)
-        else:
-            return self._process_first_fragmented_message(frame)
-
-    def _process_first_fragmented_control(self, frame):
-        self._control_opcode = frame.opcode
-        self._pending_control_fragments.append(frame.payload)
-        if not frame.fin:
-            self._frame_handler = self._handle_fragmented_control
-            return None
-        return self._reassemble_fragmented_control()
-
-    def _process_first_fragmented_message(self, frame):
-        self._message_opcode = frame.opcode
-        self._pending_message_fragments.append(frame.payload)
-        if not frame.fin:
-            self._frame_handler = self._handle_fragmented_message
-            return None
-        return self._reassemble_fragmented_message()
-
-    def _handle_fragmented_control(self, frame):
-        if frame.opcode != common.OPCODE_CONTINUATION:
-            raise InvalidFrameException(
-                'Sending invalid opcode %d while sending fragmented control '
-                'message' % frame.opcode)
-        self._pending_control_fragments.append(frame.payload)
-        if not frame.fin:
-            return None
-        return self._reassemble_fragmented_control()
-
-    def _reassemble_fragmented_control(self):
-        opcode = self._control_opcode
-        payload = ''.join(self._pending_control_fragments)
-        self._control_opcode = None
-        self._pending_control_fragments = []
-        if self._message_opcode is not None:
-            self._frame_handler = self._handle_fragmented_message
-        else:
-            self._frame_handler = self._handle_first
-        return _InnerMessage(opcode, payload)
-
-    def _handle_fragmented_message(self, frame):
-        # Sender can interleave a control message while sending fragmented
-        # messages.
-        if common.is_control_opcode(frame.opcode):
-            if self._control_opcode is not None:
-                raise MuxUnexpectedException(
-                    'Should not reach here(Bug in builder)')
-            return self._process_first_fragmented_control(frame)
-
-        if frame.opcode != common.OPCODE_CONTINUATION:
-            raise InvalidFrameException(
-                'Sending invalid opcode %d while sending fragmented message' %
-                frame.opcode)
-        self._pending_message_fragments.append(frame.payload)
-        if not frame.fin:
-            return None
-        return self._reassemble_fragmented_message()
-
-    def _reassemble_fragmented_message(self):
-        opcode = self._message_opcode
-        payload = ''.join(self._pending_message_fragments)
-        self._message_opcode = None
-        self._pending_message_fragments = []
-        self._frame_handler = self._handle_first
-        return _InnerMessage(opcode, payload)
-
-    def build(self, frame):
-        """Build an inner message. Returns an _InnerMessage instance when
-        the given frame is the last fragmented frame. Returns None otherwise.
-
-        Args:
-            frame: an inner frame.
-        Raises:
-            InvalidFrameException: when received invalid opcode. (e.g.
-                receiving non continuation data opcode but the fin flag of
-                the previous inner frame was not set.)
-        """
-
-        return self._frame_handler(frame)
-
-
-class _LogicalStream(Stream):
-    """Mimics the Stream class. This class interprets multiplexed WebSocket
-    frames.
-    """
-
-    def __init__(self, request, stream_options, send_quota, receive_quota):
-        """Constructs an instance.
-
-        Args:
-            request: _LogicalRequest instance.
-            stream_options: StreamOptions instance.
-            send_quota: Initial send quota.
-            receive_quota: Initial receive quota.
-        """
-
-        # Physical stream is responsible for masking.
-        stream_options.unmask_receive = False
-        Stream.__init__(self, request, stream_options)
-
-        self._send_closed = False
-        self._send_quota = send_quota
-        # - Protects _send_closed and _send_quota
-        # - Signals the thread waiting for send quota replenished
-        self._send_condition = threading.Condition()
-
-        # The opcode of the first frame in messages.
-        self._message_opcode = common.OPCODE_TEXT
-        # True when the last message was fragmented.
-        self._last_message_was_fragmented = False
-
-        self._receive_quota = receive_quota
-        self._write_inner_frame_semaphore = threading.Semaphore()
-
-        self._inner_message_builder = _InnerMessageBuilder()
-
-    def _create_inner_frame(self, opcode, payload, end=True):
-        frame = Frame(fin=end, opcode=opcode, payload=payload)
-        for frame_filter in self._options.outgoing_frame_filters:
-            frame_filter.filter(frame)
-
-        if len(payload) != len(frame.payload):
-            raise MuxUnexpectedException(
-                'Mux extension must not be used after extensions which change '
-                ' frame boundary')
-
-        first_byte = ((frame.fin << 7) | (frame.rsv1 << 6) |
-                      (frame.rsv2 << 5) | (frame.rsv3 << 4) | frame.opcode)
-        return chr(first_byte) + frame.payload
-
-    def _write_inner_frame(self, opcode, payload, end=True):
-        payload_length = len(payload)
-        write_position = 0
-
-        try:
-            # An inner frame will be fragmented if there is no enough send
-            # quota. This semaphore ensures that fragmented inner frames are
-            # sent in order on the logical channel.
-            # Note that frames that come from other logical channels or
-            # multiplexing control blocks can be inserted between fragmented
-            # inner frames on the physical channel.
-            self._write_inner_frame_semaphore.acquire()
-
-            # Consume an octet quota when this is the first fragmented frame.
-            if opcode != common.OPCODE_CONTINUATION:
-                try:
-                    self._send_condition.acquire()
-                    while (not self._send_closed) and self._send_quota == 0:
-                        self._send_condition.wait()
-
-                    if self._send_closed:
-                        raise BadOperationException(
-                            'Logical connection %d is closed' %
-                            self._request.channel_id)
-
-                    self._send_quota -= 1
-                finally:
-                    self._send_condition.release()
-
-            while write_position < payload_length:
-                try:
-                    self._send_condition.acquire()
-                    while (not self._send_closed) and self._send_quota == 0:
-                        self._logger.debug(
-                            'No quota. Waiting FlowControl message for %d.' %
-                            self._request.channel_id)
-                        self._send_condition.wait()
-
-                    if self._send_closed:
-                        raise BadOperationException(
-                            'Logical connection %d is closed' %
-                            self.request._channel_id)
-
-                    remaining = payload_length - write_position
-                    write_length = min(self._send_quota, remaining)
-                    inner_frame_end = (
-                        end and
-                        (write_position + write_length == payload_length))
-
-                    inner_frame = self._create_inner_frame(
-                        opcode,
-                        payload[write_position:write_position+write_length],
-                        inner_frame_end)
-                    self._send_quota -= write_length
-                    self._logger.debug('Consumed quota=%d, remaining=%d' %
-                                       (write_length, self._send_quota))
-                finally:
-                    self._send_condition.release()
-
-                # Writing data will block the worker so we need to release
-                # _send_condition before writing.
-                self._logger.debug('Sending inner frame: %r' % inner_frame)
-                self._request.connection.write(inner_frame)
-                write_position += write_length
-
-                opcode = common.OPCODE_CONTINUATION
-
-        except ValueError, e:
-            raise BadOperationException(e)
-        finally:
-            self._write_inner_frame_semaphore.release()
-
-    def replenish_send_quota(self, send_quota):
-        """Replenish send quota."""
-
-        try:
-            self._send_condition.acquire()
-            if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF:
-                self._send_quota = 0
-                raise LogicalChannelError(
-                    self._request.channel_id, _DROP_CODE_SEND_QUOTA_OVERFLOW)
-            self._send_quota += send_quota
-            self._logger.debug('Replenished send quota for channel id %d: %d' %
-                               (self._request.channel_id, self._send_quota))
-        finally:
-            self._send_condition.notify()
-            self._send_condition.release()
-
-    def consume_receive_quota(self, amount):
-        """Consumes receive quota. Returns False on failure."""
-
-        if self._receive_quota < amount:
-            self._logger.debug('Violate quota on channel id %d: %d < %d' %
-                               (self._request.channel_id,
-                                self._receive_quota, amount))
-            return False
-        self._receive_quota -= amount
-        return True
-
-    def send_message(self, message, end=True, binary=False):
-        """Override Stream.send_message."""
-
-        if self._request.server_terminated:
-            raise BadOperationException(
-                'Requested send_message after sending out a closing handshake')
-
-        if binary and isinstance(message, unicode):
-            raise BadOperationException(
-                'Message for binary frame must be instance of str')
-
-        if binary:
-            opcode = common.OPCODE_BINARY
-        else:
-            opcode = common.OPCODE_TEXT
-            message = message.encode('utf-8')
-
-        for message_filter in self._options.outgoing_message_filters:
-            message = message_filter.filter(message, end, binary)
-
-        if self._last_message_was_fragmented:
-            if opcode != self._message_opcode:
-                raise BadOperationException('Message types are different in '
-                                            'frames for the same message')
-            opcode = common.OPCODE_CONTINUATION
-        else:
-            self._message_opcode = opcode
-
-        self._write_inner_frame(opcode, message, end)
-        self._last_message_was_fragmented = not end
-
-    def _receive_frame(self):
-        """Overrides Stream._receive_frame.
-
-        In addition to call Stream._receive_frame, this method adds the amount
-        of payload to receiving quota and sends FlowControl to the client.
-        We need to do it here because Stream.receive_message() handles
-        control frames internally.
-        """
-
-        opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
-        amount = len(payload)
-        # Replenish extra one octet when receiving the first fragmented frame.
-        if opcode != common.OPCODE_CONTINUATION:
-            amount += 1
-        self._receive_quota += amount
-        frame_data = _create_flow_control(self._request.channel_id,
-                                          amount)
-        self._logger.debug('Sending flow control for %d, replenished=%d' %
-                           (self._request.channel_id, amount))
-        self._request.connection.write_control_data(frame_data)
-        return opcode, payload, fin, rsv1, rsv2, rsv3
-
-    def _get_message_from_frame(self, frame):
-        """Overrides Stream._get_message_from_frame.
-        """
-
-        try:
-            inner_message = self._inner_message_builder.build(frame)
-        except InvalidFrameException:
-            raise LogicalChannelError(
-                self._request.channel_id, _DROP_CODE_BAD_FRAGMENTATION)
-
-        if inner_message is None:
-            return None
-        self._original_opcode = inner_message.opcode
-        return inner_message.payload
-
-    def receive_message(self):
-        """Overrides Stream.receive_message."""
-
-        # Just call Stream.receive_message(), but catch
-        # LogicalConnectionClosedException, which is raised when the logical
-        # connection has closed gracefully.
-        try:
-            return Stream.receive_message(self)
-        except LogicalConnectionClosedException, e:
-            self._logger.debug('%s', e)
-            return None
-
-    def _send_closing_handshake(self, code, reason):
-        """Overrides Stream._send_closing_handshake."""
-
-        body = create_closing_handshake_body(code, reason)
-        self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
-                           (self._request.channel_id, code, reason))
-        self._write_inner_frame(common.OPCODE_CLOSE, body, end=True)
-
-        self._request.server_terminated = True
-
-    def send_ping(self, body=''):
-        """Overrides Stream.send_ping"""
-
-        self._logger.debug('Sending ping on logical channel %d: %r' %
-                           (self._request.channel_id, body))
-        self._write_inner_frame(common.OPCODE_PING, body, end=True)
-
-        self._ping_queue.append(body)
-
-    def _send_pong(self, body):
-        """Overrides Stream._send_pong"""
-
-        self._logger.debug('Sending pong on logical channel %d: %r' %
-                           (self._request.channel_id, body))
-        self._write_inner_frame(common.OPCODE_PONG, body, end=True)
-
-    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
-        """Overrides Stream.close_connection."""
-
-        # TODO(bashi): Implement
-        self._logger.debug('Closing logical connection %d' %
-                           self._request.channel_id)
-        self._request.server_terminated = True
-
-    def stop_sending(self):
-        """Stops accepting new send operation (_write_inner_frame)."""
-
-        self._send_condition.acquire()
-        self._send_closed = True
-        self._send_condition.notify()
-        self._send_condition.release()
-
-
-class _OutgoingData(object):
-    """A structure that holds data to be sent via physical connection and
-    origin of the data.
-    """
-
-    def __init__(self, channel_id, data):
-        self.channel_id = channel_id
-        self.data = data
-
-
-class _PhysicalConnectionWriter(threading.Thread):
-    """A thread that is responsible for writing data to physical connection.
-
-    TODO(bashi): Make sure there is no thread-safety problem when the reader
-    thread reads data from the same socket at a time.
-    """
-
-    def __init__(self, mux_handler):
-        """Constructs an instance.
-
-        Args:
-            mux_handler: _MuxHandler instance.
-        """
-
-        threading.Thread.__init__(self)
-        self._logger = util.get_class_logger(self)
-        self._mux_handler = mux_handler
-        self.setDaemon(True)
-
-        # When set, make this thread stop accepting new data, flush pending
-        # data and exit.
-        self._stop_requested = False
-        # The close code of the physical connection.
-        self._close_code = common.STATUS_NORMAL_CLOSURE
-        # Deque for passing write data. It's protected by _deque_condition
-        # until _stop_requested is set.
-        self._deque = collections.deque()
-        # - Protects _deque, _stop_requested and _close_code
-        # - Signals threads waiting for them to be available
-        self._deque_condition = threading.Condition()
-
-    def put_outgoing_data(self, data):
-        """Puts outgoing data.
-
-        Args:
-            data: _OutgoingData instance.
-
-        Raises:
-            BadOperationException: when the thread has been requested to
-                terminate.
-        """
-
-        try:
-            self._deque_condition.acquire()
-            if self._stop_requested:
-                raise BadOperationException('Cannot write data anymore')
-
-            self._deque.append(data)
-            self._deque_condition.notify()
-        finally:
-            self._deque_condition.release()
-
-    def _write_data(self, outgoing_data):
-        message = (_encode_channel_id(outgoing_data.channel_id) +
-                   outgoing_data.data)
-        try:
-            self._mux_handler.physical_stream.send_message(
-                message=message, end=True, binary=True)
-        except Exception, e:
-            util.prepend_message_to_exception(
-                'Failed to send message to %r: ' %
-                (self._mux_handler.physical_connection.remote_addr,), e)
-            raise
-
-        # TODO(bashi): It would be better to block the thread that sends
-        # control data as well.
-        if outgoing_data.channel_id != _CONTROL_CHANNEL_ID:
-            self._mux_handler.notify_write_data_done(outgoing_data.channel_id)
-
-    def run(self):
-        try:
-            self._deque_condition.acquire()
-            while not self._stop_requested:
-                if len(self._deque) == 0:
-                    self._deque_condition.wait()
-                    continue
-
-                outgoing_data = self._deque.popleft()
-
-                self._deque_condition.release()
-                self._write_data(outgoing_data)
-                self._deque_condition.acquire()
-
-            # Flush deque.
-            #
-            # At this point, self._deque_condition is always acquired.
-            try:
-                while len(self._deque) > 0:
-                    outgoing_data = self._deque.popleft()
-                    self._write_data(outgoing_data)
-            finally:
-                self._deque_condition.release()
-
-            # Close physical connection.
-            try:
-                # Don't wait the response here. The response will be read
-                # by the reader thread.
-                self._mux_handler.physical_stream.close_connection(
-                    self._close_code, wait_response=False)
-            except Exception, e:
-                util.prepend_message_to_exception(
-                    'Failed to close the physical connection: %r' % e)
-                raise
-        finally:
-            self._mux_handler.notify_writer_done()
-
-    def stop(self, close_code=common.STATUS_NORMAL_CLOSURE):
-        """Stops the writer thread."""
-
-        self._deque_condition.acquire()
-        self._stop_requested = True
-        self._close_code = close_code
-        self._deque_condition.notify()
-        self._deque_condition.release()
-
-
-class _PhysicalConnectionReader(threading.Thread):
-    """A thread that is responsible for reading data from physical connection.
-    """
-
-    def __init__(self, mux_handler):
-        """Constructs an instance.
-
-        Args:
-            mux_handler: _MuxHandler instance.
-        """
-
-        threading.Thread.__init__(self)
-        self._logger = util.get_class_logger(self)
-        self._mux_handler = mux_handler
-        self.setDaemon(True)
-
-    def run(self):
-        while True:
-            try:
-                physical_stream = self._mux_handler.physical_stream
-                message = physical_stream.receive_message()
-                if message is None:
-                    break
-                # Below happens only when a data message is received.
-                opcode = physical_stream.get_last_received_opcode()
-                if opcode != common.OPCODE_BINARY:
-                    self._mux_handler.fail_physical_connection(
-                        _DROP_CODE_INVALID_ENCAPSULATING_MESSAGE,
-                        'Received a text message on physical connection')
-                    break
-
-            except ConnectionTerminatedException, e:
-                self._logger.debug('%s', e)
-                break
-
-            try:
-                self._mux_handler.dispatch_message(message)
-            except PhysicalConnectionError, e:
-                self._mux_handler.fail_physical_connection(
-                    e.drop_code, e.message)
-                break
-            except LogicalChannelError, e:
-                self._mux_handler.fail_logical_channel(
-                    e.channel_id, e.drop_code, e.message)
-            except Exception, e:
-                self._logger.debug(traceback.format_exc())
-                break
-
-        self._mux_handler.notify_reader_done()
-
-
-class _Worker(threading.Thread):
-    """A thread that is responsible for running the corresponding application
-    handler.
-    """
-
-    def __init__(self, mux_handler, request):
-        """Constructs an instance.
-
-        Args:
-            mux_handler: _MuxHandler instance.
-            request: _LogicalRequest instance.
-        """
-
-        threading.Thread.__init__(self)
-        self._logger = util.get_class_logger(self)
-        self._mux_handler = mux_handler
-        self._request = request
-        self.setDaemon(True)
-
-    def run(self):
-        self._logger.debug('Logical channel worker started. (id=%d)' %
-                           self._request.channel_id)
-        try:
-            # Non-critical exceptions will be handled by dispatcher.
-            self._mux_handler.dispatcher.transfer_data(self._request)
-        except LogicalChannelError, e:
-            self._mux_handler.fail_logical_channel(
-                e.channel_id, e.drop_code, e.message)
-        finally:
-            self._mux_handler.notify_worker_done(self._request.channel_id)
-
-
-class _MuxHandshaker(hybi.Handshaker):
-    """Opening handshake processor for multiplexing."""
-
-    _DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
-
-    def __init__(self, request, dispatcher, send_quota, receive_quota):
-        """Constructs an instance.
-        Args:
-            request: _LogicalRequest instance.
-            dispatcher: Dispatcher instance (dispatch.Dispatcher).
-            send_quota: Initial send quota.
-            receive_quota: Initial receive quota.
-        """
-
-        hybi.Handshaker.__init__(self, request, dispatcher)
-        self._send_quota = send_quota
-        self._receive_quota = receive_quota
-
-        # Append headers which should not be included in handshake field of
-        # AddChannelRequest.
-        # TODO(bashi): Make sure whether we should raise exception when
-        #     these headers are included already.
-        request.headers_in[common.UPGRADE_HEADER] = (
-            common.WEBSOCKET_UPGRADE_TYPE)
-        request.headers_in[common.SEC_WEBSOCKET_VERSION_HEADER] = (
-            str(common.VERSION_HYBI_LATEST))
-        request.headers_in[common.SEC_WEBSOCKET_KEY_HEADER] = (
-            self._DUMMY_WEBSOCKET_KEY)
-
-    def _create_stream(self, stream_options):
-        """Override hybi.Handshaker._create_stream."""
-
-        self._logger.debug('Creating logical stream for %d' %
-                           self._request.channel_id)
-        return _LogicalStream(
-            self._request, stream_options, self._send_quota,
-            self._receive_quota)
-
-    def _create_handshake_response(self, accept):
-        """Override hybi._create_handshake_response."""
-
-        response = []
-
-        response.append('HTTP/1.1 101 Switching Protocols\r\n')
-
-        # Upgrade and Sec-WebSocket-Accept should be excluded.
-        response.append('%s: %s\r\n' % (
-            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
-        if self._request.ws_protocol is not None:
-            response.append('%s: %s\r\n' % (
-                common.SEC_WEBSOCKET_PROTOCOL_HEADER,
-                self._request.ws_protocol))
-        if (self._request.ws_extensions is not None and
-            len(self._request.ws_extensions) != 0):
-            response.append('%s: %s\r\n' % (
-                common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
-                common.format_extensions(self._request.ws_extensions)))
-        response.append('\r\n')
-
-        return ''.join(response)
-
-    def _send_handshake(self, accept):
-        """Override hybi.Handshaker._send_handshake."""
-
-        # Don't send handshake response for the default channel
-        if self._request.channel_id == _DEFAULT_CHANNEL_ID:
-            return
-
-        handshake_response = self._create_handshake_response(accept)
-        frame_data = _create_add_channel_response(
-                         self._request.channel_id,
-                         handshake_response)
-        self._logger.debug('Sending handshake response for %d: %r' %
-                           (self._request.channel_id, frame_data))
-        self._request.connection.write_control_data(frame_data)
-
-
-class _LogicalChannelData(object):
-    """A structure that holds information about logical channel.
-    """
-
-    def __init__(self, request, worker):
-        self.request = request
-        self.worker = worker
-        self.drop_code = _DROP_CODE_NORMAL_CLOSURE
-        self.drop_message = ''
-
-
-class _HandshakeDeltaBase(object):
-    """A class that holds information for delta-encoded handshake."""
-
-    def __init__(self, headers):
-        self._headers = headers
-
-    def create_headers(self, delta=None):
-        """Creates request headers for an AddChannelRequest that has
-        delta-encoded handshake.
-
-        Args:
-            delta: headers should be overridden.
-        """
-
-        headers = copy.copy(self._headers)
-        if delta:
-            for key, value in delta.items():
-                # The spec requires that a header with an empty value is
-                # removed from the delta base.
-                if len(value) == 0 and headers.has_key(key):
-                    del headers[key]
-                else:
-                    headers[key] = value
-        return headers
-
-
-class _MuxHandler(object):
-    """Multiplexing handler. When a handler starts, it launches three
-    threads; the reader thread, the writer thread, and a worker thread.
-
-    The reader thread reads data from the physical stream, i.e., the
-    ws_stream object of the underlying websocket connection. The reader
-    thread interprets multiplexed frames and dispatches them to logical
-    channels. Methods of this class are mostly called by the reader thread.
-
-    The writer thread sends multiplexed frames which are created by
-    logical channels via the physical connection.
-
-    The worker thread launched at the starting point handles the
-    "Implicitly Opened Connection". If multiplexing handler receives
-    an AddChannelRequest and accepts it, the handler will launch a new worker
-    thread and dispatch the request to it.
-    """
-
-    def __init__(self, request, dispatcher):
-        """Constructs an instance.
-
-        Args:
-            request: mod_python request of the physical connection.
-            dispatcher: Dispatcher instance (dispatch.Dispatcher).
-        """
-
-        self.original_request = request
-        self.dispatcher = dispatcher
-        self.physical_connection = request.connection
-        self.physical_stream = request.ws_stream
-        self._logger = util.get_class_logger(self)
-        self._logical_channels = {}
-        self._logical_channels_condition = threading.Condition()
-        # Holds client's initial quota
-        self._channel_slots = collections.deque()
-        self._handshake_base = None
-        self._worker_done_notify_received = False
-        self._reader = None
-        self._writer = None
-
-    def start(self):
-        """Starts the handler.
-
-        Raises:
-            MuxUnexpectedException: when the handler already started, or when
-                opening handshake of the default channel fails.
-        """
-
-        if self._reader or self._writer:
-            raise MuxUnexpectedException('MuxHandler already started')
-
-        self._reader = _PhysicalConnectionReader(self)
-        self._writer = _PhysicalConnectionWriter(self)
-        self._reader.start()
-        self._writer.start()
-
-        # Create "Implicitly Opened Connection".
-        logical_connection = _LogicalConnection(self, _DEFAULT_CHANNEL_ID)
-        headers = copy.copy(self.original_request.headers_in)
-        # Add extensions for logical channel.
-        headers[common.SEC_WEBSOCKET_EXTENSIONS_HEADER] = (
-            common.format_extensions(
-                self.original_request.mux_processor.extensions()))
-        self._handshake_base = _HandshakeDeltaBase(headers)
-        logical_request = _LogicalRequest(
-            _DEFAULT_CHANNEL_ID,
-            self.original_request.method,
-            self.original_request.uri,
-            self.original_request.protocol,
-            self._handshake_base.create_headers(),
-            logical_connection)
-        # Client's send quota for the implicitly opened connection is zero,
-        # but we will send FlowControl later so set the initial quota to
-        # _INITIAL_QUOTA_FOR_CLIENT.
-        self._channel_slots.append(_INITIAL_QUOTA_FOR_CLIENT)
-        send_quota = self.original_request.mux_processor.quota()
-        if not self._do_handshake_for_logical_request(
-            logical_request, send_quota=send_quota):
-            raise MuxUnexpectedException(
-                'Failed handshake on the default channel id')
-        self._add_logical_channel(logical_request)
-
-        # Send FlowControl for the implicitly opened connection.
-        frame_data = _create_flow_control(_DEFAULT_CHANNEL_ID,
-                                          _INITIAL_QUOTA_FOR_CLIENT)
-        logical_request.connection.write_control_data(frame_data)
-
-    def add_channel_slots(self, slots, send_quota):
-        """Adds channel slots.
-
-        Args:
-            slots: number of slots to be added.
-            send_quota: initial send quota for slots.
-        """
-
-        self._channel_slots.extend([send_quota] * slots)
-        # Send NewChannelSlot to client.
-        frame_data = _create_new_channel_slot(slots, send_quota)
-        self.send_control_data(frame_data)
-
-    def wait_until_done(self, timeout=None):
-        """Waits until all workers are done. Returns False when timeout has
-        occurred. Returns True on success.
-
-        Args:
-            timeout: timeout in sec.
-        """
-
-        self._logical_channels_condition.acquire()
-        try:
-            while len(self._logical_channels) > 0:
-                self._logger.debug('Waiting workers(%d)...' %
-                                   len(self._logical_channels))
-                self._worker_done_notify_received = False
-                self._logical_channels_condition.wait(timeout)
-                if not self._worker_done_notify_received:
-                    self._logger.debug('Waiting worker(s) timed out')
-                    return False
-        finally:
-            self._logical_channels_condition.release()
-
-        # Flush pending outgoing data
-        self._writer.stop()
-        self._writer.join()
-
-        return True
-
-    def notify_write_data_done(self, channel_id):
-        """Called by the writer thread when a write operation has done.
-
-        Args:
-            channel_id: objective channel id.
-        """
-
-        try:
-            self._logical_channels_condition.acquire()
-            if channel_id in self._logical_channels:
-                channel_data = self._logical_channels[channel_id]
-                channel_data.request.connection.on_write_data_done()
-            else:
-                self._logger.debug('Seems that logical channel for %d has gone'
-                                   % channel_id)
-        finally:
-            self._logical_channels_condition.release()
-
-    def send_control_data(self, data):
-        """Sends data via the control channel.
-
-        Args:
-            data: data to be sent.
-        """
-
-        self._writer.put_outgoing_data(_OutgoingData(
-                channel_id=_CONTROL_CHANNEL_ID, data=data))
-
-    def send_data(self, channel_id, data):
-        """Sends data via given logical channel. This method is called by
-        worker threads.
-
-        Args:
-            channel_id: objective channel id.
-            data: data to be sent.
-        """
-
-        self._writer.put_outgoing_data(_OutgoingData(
-                channel_id=channel_id, data=data))
-
-    def _send_drop_channel(self, channel_id, code=None, message=''):
-        frame_data = _create_drop_channel(channel_id, code, message)
-        self._logger.debug(
-            'Sending drop channel for channel id %d' % channel_id)
-        self.send_control_data(frame_data)
-
-    def _send_error_add_channel_response(self, channel_id, status=None):
-        if status is None:
-            status = common.HTTP_STATUS_BAD_REQUEST
-
-        if status in _HTTP_BAD_RESPONSE_MESSAGES:
-            message = _HTTP_BAD_RESPONSE_MESSAGES[status]
-        else:
-            self._logger.debug('Response message for %d is not found' % status)
-            message = '???'
-
-        response = 'HTTP/1.1 %d %s\r\n\r\n' % (status, message)
-        frame_data = _create_add_channel_response(channel_id,
-                                                  encoded_handshake=response,
-                                                  encoding=0, rejected=True)
-        self.send_control_data(frame_data)
-
-    def _create_logical_request(self, block):
-        if block.channel_id == _CONTROL_CHANNEL_ID:
-            # TODO(bashi): Raise PhysicalConnectionError with code 2006
-            # instead of MuxUnexpectedException.
-            raise MuxUnexpectedException(
-                'Received the control channel id (0) as objective channel '
-                'id for AddChannel')
-
-        if block.encoding > _HANDSHAKE_ENCODING_DELTA:
-            raise PhysicalConnectionError(
-                _DROP_CODE_UNKNOWN_REQUEST_ENCODING)
-
-        method, path, version, headers = _parse_request_text(
-            block.encoded_handshake)
-        if block.encoding == _HANDSHAKE_ENCODING_DELTA:
-            headers = self._handshake_base.create_headers(headers)
-
-        connection = _LogicalConnection(self, block.channel_id)
-        request = _LogicalRequest(block.channel_id, method, path, version,
-                                  headers, connection)
-        return request
-
-    def _do_handshake_for_logical_request(self, request, send_quota=0):
-        try:
-            receive_quota = self._channel_slots.popleft()
-        except IndexError:
-            raise LogicalChannelError(
-                request.channel_id, _DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION)
-
-        handshaker = _MuxHandshaker(request, self.dispatcher,
-                                    send_quota, receive_quota)
-        try:
-            handshaker.do_handshake()
-        except handshake.VersionException, e:
-            self._logger.info('%s', e)
-            self._send_error_add_channel_response(
-                request.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
-            return False
-        except handshake.HandshakeException, e:
-            # TODO(bashi): Should we _Fail the Logical Channel_ with 3001
-            # instead?
-            self._logger.info('%s', e)
-            self._send_error_add_channel_response(request.channel_id,
-                                                  status=e.status)
-            return False
-        except handshake.AbortedByUserException, e:
-            self._logger.info('%s', e)
-            self._send_error_add_channel_response(request.channel_id)
-            return False
-
-        return True
-
-    def _add_logical_channel(self, logical_request):
-        try:
-            self._logical_channels_condition.acquire()
-            if logical_request.channel_id in self._logical_channels:
-                self._logger.debug('Channel id %d already exists' %
-                                   logical_request.channel_id)
-                raise PhysicalConnectionError(
-                    _DROP_CODE_CHANNEL_ALREADY_EXISTS,
-                    'Channel id %d already exists' %
-                    logical_request.channel_id)
-            worker = _Worker(self, logical_request)
-            channel_data = _LogicalChannelData(logical_request, worker)
-            self._logical_channels[logical_request.channel_id] = channel_data
-            worker.start()
-        finally:
-            self._logical_channels_condition.release()
-
-    def _process_add_channel_request(self, block):
-        try:
-            logical_request = self._create_logical_request(block)
-        except ValueError, e:
-            self._logger.debug('Failed to create logical request: %r' % e)
-            self._send_error_add_channel_response(
-                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
-            return
-        if self._do_handshake_for_logical_request(logical_request):
-            if block.encoding == _HANDSHAKE_ENCODING_IDENTITY:
-                # Update handshake base.
-                # TODO(bashi): Make sure this is the right place to update
-                # handshake base.
-                self._handshake_base = _HandshakeDeltaBase(
-                    logical_request.headers_in)
-            self._add_logical_channel(logical_request)
-        else:
-            self._send_error_add_channel_response(
-                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
-
-    def _process_flow_control(self, block):
-        try:
-            self._logical_channels_condition.acquire()
-            if not block.channel_id in self._logical_channels:
-                return
-            channel_data = self._logical_channels[block.channel_id]
-            channel_data.request.ws_stream.replenish_send_quota(
-                block.send_quota)
-        finally:
-            self._logical_channels_condition.release()
-
-    def _process_drop_channel(self, block):
-        self._logger.debug(
-            'DropChannel received for %d: code=%r, reason=%r' %
-            (block.channel_id, block.drop_code, block.drop_message))
-        try:
-            self._logical_channels_condition.acquire()
-            if not block.channel_id in self._logical_channels:
-                return
-            channel_data = self._logical_channels[block.channel_id]
-            channel_data.drop_code = _DROP_CODE_ACKNOWLEDGED
-
-            # Close the logical channel
-            channel_data.request.connection.set_read_state(
-                _LogicalConnection.STATE_TERMINATED)
-            channel_data.request.ws_stream.stop_sending()
-        finally:
-            self._logical_channels_condition.release()
-
-    def _process_control_blocks(self, parser):
-        for control_block in parser.read_control_blocks():
-            opcode = control_block.opcode
-            self._logger.debug('control block received, opcode: %d' % opcode)
-            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
-                self._process_add_channel_request(control_block)
-            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
-                raise PhysicalConnectionError(
-                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                    'Received AddChannelResponse')
-            elif opcode == _MUX_OPCODE_FLOW_CONTROL:
-                self._process_flow_control(control_block)
-            elif opcode == _MUX_OPCODE_DROP_CHANNEL:
-                self._process_drop_channel(control_block)
-            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
-                raise PhysicalConnectionError(
-                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                    'Received NewChannelSlot')
-            else:
-                raise MuxUnexpectedException(
-                    'Unexpected opcode %r' % opcode)
-
-    def _process_logical_frame(self, channel_id, parser):
-        self._logger.debug('Received a frame. channel id=%d' % channel_id)
-        try:
-            self._logical_channels_condition.acquire()
-            if not channel_id in self._logical_channels:
-                # We must ignore the message for an inactive channel.
-                return
-            channel_data = self._logical_channels[channel_id]
-            fin, rsv1, rsv2, rsv3, opcode, payload = parser.read_inner_frame()
-            consuming_byte = len(payload)
-            if opcode != common.OPCODE_CONTINUATION:
-                consuming_byte += 1
-            if not channel_data.request.ws_stream.consume_receive_quota(
-                consuming_byte):
-                # The client violates quota. Close logical channel.
-                raise LogicalChannelError(
-                    channel_id, _DROP_CODE_SEND_QUOTA_VIOLATION)
-            header = create_header(opcode, len(payload), fin, rsv1, rsv2, rsv3,
-                                   mask=False)
-            frame_data = header + payload
-            channel_data.request.connection.append_frame_data(frame_data)
-        finally:
-            self._logical_channels_condition.release()
-
-    def dispatch_message(self, message):
-        """Dispatches message. The reader thread calls this method.
-
-        Args:
-            message: a message that contains encapsulated frame.
-        Raises:
-            PhysicalConnectionError: if the message contains physical
-                connection level errors.
-            LogicalChannelError: if the message contains logical channel
-                level errors.
-        """
-
-        parser = _MuxFramePayloadParser(message)
-        try:
-            channel_id = parser.read_channel_id()
-        except ValueError, e:
-            raise PhysicalConnectionError(_DROP_CODE_CHANNEL_ID_TRUNCATED)
-        if channel_id == _CONTROL_CHANNEL_ID:
-            self._process_control_blocks(parser)
-        else:
-            self._process_logical_frame(channel_id, parser)
-
-    def notify_worker_done(self, channel_id):
-        """Called when a worker has finished.
-
-        Args:
-            channel_id: channel id corresponded with the worker.
-        """
-
-        self._logger.debug('Worker for channel id %d terminated' % channel_id)
-        try:
-            self._logical_channels_condition.acquire()
-            if not channel_id in self._logical_channels:
-                raise MuxUnexpectedException(
-                    'Channel id %d not found' % channel_id)
-            channel_data = self._logical_channels.pop(channel_id)
-        finally:
-            self._worker_done_notify_received = True
-            self._logical_channels_condition.notify()
-            self._logical_channels_condition.release()
-
-        if not channel_data.request.server_terminated:
-            self._send_drop_channel(
-                channel_id, code=channel_data.drop_code,
-                message=channel_data.drop_message)
-
-    def notify_reader_done(self):
-        """This method is called by the reader thread when the reader has
-        finished.
-        """
-
-        self._logger.debug(
-            'Termiating all logical connections waiting for incoming data '
-            '...')
-        self._logical_channels_condition.acquire()
-        for channel_data in self._logical_channels.values():
-            try:
-                channel_data.request.connection.set_read_state(
-                    _LogicalConnection.STATE_TERMINATED)
-            except Exception:
-                self._logger.debug(traceback.format_exc())
-        self._logical_channels_condition.release()
-
-    def notify_writer_done(self):
-        """This method is called by the writer thread when the writer has
-        finished.
-        """
-
-        self._logger.debug(
-            'Termiating all logical connections waiting for write '
-            'completion ...')
-        self._logical_channels_condition.acquire()
-        for channel_data in self._logical_channels.values():
-            try:
-                channel_data.request.connection.on_writer_done()
-            except Exception:
-                self._logger.debug(traceback.format_exc())
-        self._logical_channels_condition.release()
-
-    def fail_physical_connection(self, code, message):
-        """Fail the physical connection.
-
-        Args:
-            code: drop reason code.
-            message: drop message.
-        """
-
-        self._logger.debug('Failing the physical connection...')
-        self._send_drop_channel(_CONTROL_CHANNEL_ID, code, message)
-        self._writer.stop(common.STATUS_INTERNAL_ENDPOINT_ERROR)
-
-    def fail_logical_channel(self, channel_id, code, message):
-        """Fail a logical channel.
-
-        Args:
-            channel_id: channel id.
-            code: drop reason code.
-            message: drop message.
-        """
-
-        self._logger.debug('Failing logical channel %d...' % channel_id)
-        try:
-            self._logical_channels_condition.acquire()
-            if channel_id in self._logical_channels:
-                channel_data = self._logical_channels[channel_id]
-                # Close the logical channel. notify_worker_done() will be
-                # called later and it will send DropChannel.
-                channel_data.drop_code = code
-                channel_data.drop_message = message
-
-                channel_data.request.connection.set_read_state(
-                    _LogicalConnection.STATE_TERMINATED)
-                channel_data.request.ws_stream.stop_sending()
-            else:
-                self._send_drop_channel(channel_id, code, message)
-        finally:
-            self._logical_channels_condition.release()
-
-
-def use_mux(request):
-    return hasattr(request, 'mux_processor') and (
-        request.mux_processor.is_active())
-
-
-def start(request, dispatcher):
-    mux_handler = _MuxHandler(request, dispatcher)
-    mux_handler.start()
-
-    mux_handler.add_channel_slots(_INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                  _INITIAL_QUOTA_FOR_CLIENT)
-
-    mux_handler.wait_until_done()
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/standalone.py b/tools/pywebsocket/src/mod_pywebsocket/standalone.py
deleted file mode 100755
index 24c299e..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/standalone.py
+++ /dev/null
@@ -1,1193 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""Standalone WebSocket server.
-
-Use this file to launch pywebsocket without Apache HTTP Server.
-
-
-BASIC USAGE
-===========
-
-Go to the src directory and run
-
-  $ python mod_pywebsocket/standalone.py [-p <ws_port>]
-                                         [-w <websock_handlers>]
-                                         [-d <document_root>]
-
-<ws_port> is the port number to use for ws:// connection.
-
-<document_root> is the path to the root directory of HTML files.
-
-<websock_handlers> is the path to the root directory of WebSocket handlers.
-If not specified, <document_root> will be used. See __init__.py (or
-run $ pydoc mod_pywebsocket) for how to write WebSocket handlers.
-
-For more detail and other options, run
-
-  $ python mod_pywebsocket/standalone.py --help
-
-or see _build_option_parser method below.
-
-For trouble shooting, adding "--log_level debug" might help you.
-
-
-TRY DEMO
-========
-
-Go to the src directory and run standalone.py with -d option to set the
-document root to the directory containing example HTMLs and handlers like this:
-
-  $ cd src
-  $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example
-
-to launch pywebsocket with the sample handler and html on port 80. Open
-http://localhost/console.html, click the connect button, type something into
-the text box next to the send button and click the send button. If everything
-is working, you'll see the message you typed echoed by the server.
-
-
-USING TLS
-=========
-
-To run the standalone server with TLS support, run it with -t, -k, and -c
-options. When TLS is enabled, the standalone server accepts only TLS connection.
-
-Note that when ssl module is used and the key/cert location is incorrect,
-TLS connection silently fails while pyOpenSSL fails on startup.
-
-Example:
-
-  $ PYTHONPATH=. python mod_pywebsocket/standalone.py \
-        -d example \
-        -p 10443 \
-        -t \
-        -c ../test/cert/cert.pem \
-        -k ../test/cert/key.pem \
-
-Note that when passing a relative path to -c and -k option, it will be resolved
-using the document root directory as the base.
-
-
-USING CLIENT AUTHENTICATION
-===========================
-
-To run the standalone server with TLS client authentication support, run it with
---tls-client-auth and --tls-client-ca options in addition to ones required for
-TLS support.
-
-Example:
-
-  $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example -p 10443 -t \
-        -c ../test/cert/cert.pem -k ../test/cert/key.pem \
-        --tls-client-auth \
-        --tls-client-ca=../test/cert/cacert.pem
-
-Note that when passing a relative path to --tls-client-ca option, it will be
-resolved using the document root directory as the base.
-
-
-CONFIGURATION FILE
-==================
-
-You can also write a configuration file and use it by specifying the path to
-the configuration file by --config option. Please write a configuration file
-following the documentation of the Python ConfigParser library. Name of each
-entry must be the long version argument name. E.g. to set log level to debug,
-add the following line:
-
-log_level=debug
-
-For options which doesn't take value, please add some fake value. E.g. for
---tls option, add the following line:
-
-tls=True
-
-Note that tls will be enabled even if you write tls=False as the value part is
-fake.
-
-When both a command line argument and a configuration file entry are set for
-the same configuration item, the command line value will override one in the
-configuration file.
-
-
-THREADING
-=========
-
-This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
-used for each request.
-
-
-SECURITY WARNING
-================
-
-This uses CGIHTTPServer and CGIHTTPServer is not secure.
-It may execute arbitrary Python code or external programs. It should not be
-used outside a firewall.
-"""
-
-import BaseHTTPServer
-import CGIHTTPServer
-import SimpleHTTPServer
-import SocketServer
-import ConfigParser
-import base64
-import httplib
-import logging
-import logging.handlers
-import optparse
-import os
-import re
-import select
-import socket
-import sys
-import threading
-import time
-
-from mod_pywebsocket import common
-from mod_pywebsocket import dispatch
-from mod_pywebsocket import handshake
-from mod_pywebsocket import http_header_util
-from mod_pywebsocket import memorizingfile
-from mod_pywebsocket import util
-from mod_pywebsocket.xhr_benchmark_handler import XHRBenchmarkHandler
-
-
-_DEFAULT_LOG_MAX_BYTES = 1024 * 256
-_DEFAULT_LOG_BACKUP_COUNT = 5
-
-_DEFAULT_REQUEST_QUEUE_SIZE = 128
-
-# 1024 is practically large enough to contain WebSocket handshake lines.
-_MAX_MEMORIZED_LINES = 1024
-
-# Constants for the --tls_module flag.
-_TLS_BY_STANDARD_MODULE = 'ssl'
-_TLS_BY_PYOPENSSL = 'pyopenssl'
-
-
-class _StandaloneConnection(object):
-    """Mimic mod_python mp_conn."""
-
-    def __init__(self, request_handler):
-        """Construct an instance.
-
-        Args:
-            request_handler: A WebSocketRequestHandler instance.
-        """
-
-        self._request_handler = request_handler
-
-    def get_local_addr(self):
-        """Getter to mimic mp_conn.local_addr."""
-
-        return (self._request_handler.server.server_name,
-                self._request_handler.server.server_port)
-    local_addr = property(get_local_addr)
-
-    def get_remote_addr(self):
-        """Getter to mimic mp_conn.remote_addr.
-
-        Setting the property in __init__ won't work because the request
-        handler is not initialized yet there."""
-
-        return self._request_handler.client_address
-    remote_addr = property(get_remote_addr)
-
-    def write(self, data):
-        """Mimic mp_conn.write()."""
-
-        return self._request_handler.wfile.write(data)
-
-    def read(self, length):
-        """Mimic mp_conn.read()."""
-
-        return self._request_handler.rfile.read(length)
-
-    def get_memorized_lines(self):
-        """Get memorized lines."""
-
-        return self._request_handler.rfile.get_memorized_lines()
-
-
-class _StandaloneRequest(object):
-    """Mimic mod_python request."""
-
-    def __init__(self, request_handler, use_tls):
-        """Construct an instance.
-
-        Args:
-            request_handler: A WebSocketRequestHandler instance.
-        """
-
-        self._logger = util.get_class_logger(self)
-
-        self._request_handler = request_handler
-        self.connection = _StandaloneConnection(request_handler)
-        self._use_tls = use_tls
-        self.headers_in = request_handler.headers
-
-    def get_uri(self):
-        """Getter to mimic request.uri.
-
-        This method returns the raw data at the Request-URI part of the
-        Request-Line, while the uri method on the request object of mod_python
-        returns the path portion after parsing the raw data. This behavior is
-        kept for compatibility.
-        """
-
-        return self._request_handler.path
-    uri = property(get_uri)
-
-    def get_unparsed_uri(self):
-        """Getter to mimic request.unparsed_uri."""
-
-        return self._request_handler.path
-    unparsed_uri = property(get_unparsed_uri)
-
-    def get_method(self):
-        """Getter to mimic request.method."""
-
-        return self._request_handler.command
-    method = property(get_method)
-
-    def get_protocol(self):
-        """Getter to mimic request.protocol."""
-
-        return self._request_handler.request_version
-    protocol = property(get_protocol)
-
-    def is_https(self):
-        """Mimic request.is_https()."""
-
-        return self._use_tls
-
-
-def _import_ssl():
-    global ssl
-    try:
-        import ssl
-        return True
-    except ImportError:
-        return False
-
-
-def _import_pyopenssl():
-    global OpenSSL
-    try:
-        import OpenSSL.SSL
-        return True
-    except ImportError:
-        return False
-
-
-class _StandaloneSSLConnection(object):
-    """A wrapper class for OpenSSL.SSL.Connection to
-    - provide makefile method which is not supported by the class
-    - tweak shutdown method since OpenSSL.SSL.Connection.shutdown doesn't
-      accept the "how" argument.
-    - convert SysCallError exceptions that its recv method may raise into a
-      return value of '', meaning EOF. We cannot overwrite the recv method on
-      self._connection since it's immutable.
-    """
-
-    _OVERRIDDEN_ATTRIBUTES = ['_connection', 'makefile', 'shutdown', 'recv']
-
-    def __init__(self, connection):
-        self._connection = connection
-
-    def __getattribute__(self, name):
-        if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES:
-            return object.__getattribute__(self, name)
-        return self._connection.__getattribute__(name)
-
-    def __setattr__(self, name, value):
-        if name in _StandaloneSSLConnection._OVERRIDDEN_ATTRIBUTES:
-            return object.__setattr__(self, name, value)
-        return self._connection.__setattr__(name, value)
-
-    def makefile(self, mode='r', bufsize=-1):
-        return socket._fileobject(self, mode, bufsize)
-
-    def shutdown(self, unused_how):
-        self._connection.shutdown()
-
-    def recv(self, bufsize, flags=0):
-        if flags != 0:
-            raise ValueError('Non-zero flags not allowed')
-
-        try:
-            return self._connection.recv(bufsize)
-        except OpenSSL.SSL.SysCallError, (err, message):
-            if err == -1:
-                # Suppress "unexpected EOF" exception. See the OpenSSL document
-                # for SSL_get_error.
-                return ''
-            raise
-
-
-def _alias_handlers(dispatcher, websock_handlers_map_file):
-    """Set aliases specified in websock_handler_map_file in dispatcher.
-
-    Args:
-        dispatcher: dispatch.Dispatcher instance
-        websock_handler_map_file: alias map file
-    """
-
-    fp = open(websock_handlers_map_file)
-    try:
-        for line in fp:
-            if line[0] == '#' or line.isspace():
-                continue
-            m = re.match('(\S+)\s+(\S+)', line)
-            if not m:
-                logging.warning('Wrong format in map file:' + line)
-                continue
-            try:
-                dispatcher.add_resource_path_alias(
-                    m.group(1), m.group(2))
-            except dispatch.DispatchException, e:
-                logging.error(str(e))
-    finally:
-        fp.close()
-
-
-class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
-    """HTTPServer specialized for WebSocket."""
-
-    # Overrides SocketServer.ThreadingMixIn.daemon_threads
-    daemon_threads = True
-    # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address
-    allow_reuse_address = True
-
-    def __init__(self, options):
-        """Override SocketServer.TCPServer.__init__ to set SSL enabled
-        socket object to self.socket before server_bind and server_activate,
-        if necessary.
-        """
-
-        # Share a Dispatcher among request handlers to save time for
-        # instantiation.  Dispatcher can be shared because it is thread-safe.
-        options.dispatcher = dispatch.Dispatcher(
-            options.websock_handlers,
-            options.scan_dir,
-            options.allow_handlers_outside_root_dir)
-        if options.websock_handlers_map_file:
-            _alias_handlers(options.dispatcher,
-                            options.websock_handlers_map_file)
-        warnings = options.dispatcher.source_warnings()
-        if warnings:
-            for warning in warnings:
-                logging.warning('Warning in source loading: %s' % warning)
-
-        self._logger = util.get_class_logger(self)
-
-        self.request_queue_size = options.request_queue_size
-        self.__ws_is_shut_down = threading.Event()
-        self.__ws_serving = False
-
-        SocketServer.BaseServer.__init__(
-            self, (options.server_host, options.port), WebSocketRequestHandler)
-
-        # Expose the options object to allow handler objects access it. We name
-        # it with websocket_ prefix to avoid conflict.
-        self.websocket_server_options = options
-
-        self._create_sockets()
-        self.server_bind()
-        self.server_activate()
-
-    def _create_sockets(self):
-        self.server_name, self.server_port = self.server_address
-        self._sockets = []
-        if not self.server_name:
-            # On platforms that doesn't support IPv6, the first bind fails.
-            # On platforms that supports IPv6
-            # - If it binds both IPv4 and IPv6 on call with AF_INET6, the
-            #   first bind succeeds and the second fails (we'll see 'Address
-            #   already in use' error).
-            # - If it binds only IPv6 on call with AF_INET6, both call are
-            #   expected to succeed to listen both protocol.
-            addrinfo_array = [
-                (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''),
-                (socket.AF_INET, socket.SOCK_STREAM, '', '', '')]
-        else:
-            addrinfo_array = socket.getaddrinfo(self.server_name,
-                                                self.server_port,
-                                                socket.AF_UNSPEC,
-                                                socket.SOCK_STREAM,
-                                                socket.IPPROTO_TCP)
-        for addrinfo in addrinfo_array:
-            self._logger.info('Create socket on: %r', addrinfo)
-            family, socktype, proto, canonname, sockaddr = addrinfo
-            try:
-                socket_ = socket.socket(family, socktype)
-            except Exception, e:
-                self._logger.info('Skip by failure: %r', e)
-                continue
-            server_options = self.websocket_server_options
-            if server_options.use_tls:
-                # For the case of _HAS_OPEN_SSL, we do wrapper setup after
-                # accept.
-                if server_options.tls_module == _TLS_BY_STANDARD_MODULE:
-                    if server_options.tls_client_auth:
-                        if server_options.tls_client_cert_optional:
-                            client_cert_ = ssl.CERT_OPTIONAL
-                        else:
-                            client_cert_ = ssl.CERT_REQUIRED
-                    else:
-                        client_cert_ = ssl.CERT_NONE
-                    socket_ = ssl.wrap_socket(socket_,
-                        keyfile=server_options.private_key,
-                        certfile=server_options.certificate,
-                        ssl_version=ssl.PROTOCOL_SSLv23,
-                        ca_certs=server_options.tls_client_ca,
-                        cert_reqs=client_cert_,
-                        do_handshake_on_connect=False)
-            self._sockets.append((socket_, addrinfo))
-
-    def server_bind(self):
-        """Override SocketServer.TCPServer.server_bind to enable multiple
-        sockets bind.
-        """
-
-        failed_sockets = []
-
-        for socketinfo in self._sockets:
-            socket_, addrinfo = socketinfo
-            self._logger.info('Bind on: %r', addrinfo)
-            if self.allow_reuse_address:
-                socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            try:
-                socket_.bind(self.server_address)
-            except Exception, e:
-                self._logger.info('Skip by failure: %r', e)
-                socket_.close()
-                failed_sockets.append(socketinfo)
-            if self.server_address[1] == 0:
-                # The operating system assigns the actual port number for port
-                # number 0. This case, the second and later sockets should use
-                # the same port number. Also self.server_port is rewritten
-                # because it is exported, and will be used by external code.
-                self.server_address = (
-                    self.server_name, socket_.getsockname()[1])
-                self.server_port = self.server_address[1]
-                self._logger.info('Port %r is assigned', self.server_port)
-
-        for socketinfo in failed_sockets:
-            self._sockets.remove(socketinfo)
-
-    def server_activate(self):
-        """Override SocketServer.TCPServer.server_activate to enable multiple
-        sockets listen.
-        """
-
-        failed_sockets = []
-
-        for socketinfo in self._sockets:
-            socket_, addrinfo = socketinfo
-            self._logger.info('Listen on: %r', addrinfo)
-            try:
-                socket_.listen(self.request_queue_size)
-            except Exception, e:
-                self._logger.info('Skip by failure: %r', e)
-                socket_.close()
-                failed_sockets.append(socketinfo)
-
-        for socketinfo in failed_sockets:
-            self._sockets.remove(socketinfo)
-
-        if len(self._sockets) == 0:
-            self._logger.critical(
-                'No sockets activated. Use info log level to see the reason.')
-
-    def server_close(self):
-        """Override SocketServer.TCPServer.server_close to enable multiple
-        sockets close.
-        """
-
-        for socketinfo in self._sockets:
-            socket_, addrinfo = socketinfo
-            self._logger.info('Close on: %r', addrinfo)
-            socket_.close()
-
-    def fileno(self):
-        """Override SocketServer.TCPServer.fileno."""
-
-        self._logger.critical('Not supported: fileno')
-        return self._sockets[0][0].fileno()
-
-    def handle_error(self, request, client_address):
-        """Override SocketServer.handle_error."""
-
-        self._logger.error(
-            'Exception in processing request from: %r\n%s',
-            client_address,
-            util.get_stack_trace())
-        # Note: client_address is a tuple.
-
-    def get_request(self):
-        """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection
-        object with _StandaloneSSLConnection to provide makefile method. We
-        cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly
-        attribute.
-        """
-
-        accepted_socket, client_address = self.socket.accept()
-
-        server_options = self.websocket_server_options
-        if server_options.use_tls:
-            if server_options.tls_module == _TLS_BY_STANDARD_MODULE:
-                try:
-                    accepted_socket.do_handshake()
-                except ssl.SSLError, e:
-                    self._logger.debug('%r', e)
-                    raise
-
-                # Print cipher in use. Handshake is done on accept.
-                self._logger.debug('Cipher: %s', accepted_socket.cipher())
-                self._logger.debug('Client cert: %r',
-                                   accepted_socket.getpeercert())
-            elif server_options.tls_module == _TLS_BY_PYOPENSSL:
-                # We cannot print the cipher in use. pyOpenSSL doesn't provide
-                # any method to fetch that.
-
-                ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
-                ctx.use_privatekey_file(server_options.private_key)
-                ctx.use_certificate_file(server_options.certificate)
-
-                def default_callback(conn, cert, errnum, errdepth, ok):
-                    return ok == 1
-
-                # See the OpenSSL document for SSL_CTX_set_verify.
-                if server_options.tls_client_auth:
-                    verify_mode = OpenSSL.SSL.VERIFY_PEER
-                    if not server_options.tls_client_cert_optional:
-                        verify_mode |= OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT
-                    ctx.set_verify(verify_mode, default_callback)
-                    ctx.load_verify_locations(server_options.tls_client_ca,
-                                              None)
-                else:
-                    ctx.set_verify(OpenSSL.SSL.VERIFY_NONE, default_callback)
-
-                accepted_socket = OpenSSL.SSL.Connection(ctx, accepted_socket)
-                accepted_socket.set_accept_state()
-
-                # Convert SSL related error into socket.error so that
-                # SocketServer ignores them and keeps running.
-                #
-                # TODO(tyoshino): Convert all kinds of errors.
-                try:
-                    accepted_socket.do_handshake()
-                except OpenSSL.SSL.Error, e:
-                    # Set errno part to 1 (SSL_ERROR_SSL) like the ssl module
-                    # does.
-                    self._logger.debug('%r', e)
-                    raise socket.error(1, '%r' % e)
-                cert = accepted_socket.get_peer_certificate()
-                if cert is not None:
-                    self._logger.debug('Client cert subject: %r',
-                                       cert.get_subject().get_components())
-                accepted_socket = _StandaloneSSLConnection(accepted_socket)
-            else:
-                raise ValueError('No TLS support module is available')
-
-        return accepted_socket, client_address
-
-    def serve_forever(self, poll_interval=0.5):
-        """Override SocketServer.BaseServer.serve_forever."""
-
-        self.__ws_serving = True
-        self.__ws_is_shut_down.clear()
-        handle_request = self.handle_request
-        if hasattr(self, '_handle_request_noblock'):
-            handle_request = self._handle_request_noblock
-        else:
-            self._logger.warning('Fallback to blocking request handler')
-        try:
-            while self.__ws_serving:
-                r, w, e = select.select(
-                    [socket_[0] for socket_ in self._sockets],
-                    [], [], poll_interval)
-                for socket_ in r:
-                    self.socket = socket_
-                    handle_request()
-                self.socket = None
-        finally:
-            self.__ws_is_shut_down.set()
-
-    def shutdown(self):
-        """Override SocketServer.BaseServer.shutdown."""
-
-        self.__ws_serving = False
-        self.__ws_is_shut_down.wait()
-
-
-class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
-    """CGIHTTPRequestHandler specialized for WebSocket."""
-
-    # Use httplib.HTTPMessage instead of mimetools.Message.
-    MessageClass = httplib.HTTPMessage
-
-    protocol_version = "HTTP/1.1"
-
-    def setup(self):
-        """Override SocketServer.StreamRequestHandler.setup to wrap rfile
-        with MemorizingFile.
-
-        This method will be called by BaseRequestHandler's constructor
-        before calling BaseHTTPRequestHandler.handle.
-        BaseHTTPRequestHandler.handle will call
-        BaseHTTPRequestHandler.handle_one_request and it will call
-        WebSocketRequestHandler.parse_request.
-        """
-
-        # Call superclass's setup to prepare rfile, wfile, etc. See setup
-        # definition on the root class SocketServer.StreamRequestHandler to
-        # understand what this does.
-        CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
-
-        self.rfile = memorizingfile.MemorizingFile(
-            self.rfile,
-            max_memorized_lines=_MAX_MEMORIZED_LINES)
-
-    def __init__(self, request, client_address, server):
-        self._logger = util.get_class_logger(self)
-
-        self._options = server.websocket_server_options
-
-        # Overrides CGIHTTPServerRequestHandler.cgi_directories.
-        self.cgi_directories = self._options.cgi_directories
-        # Replace CGIHTTPRequestHandler.is_executable method.
-        if self._options.is_executable_method is not None:
-            self.is_executable = self._options.is_executable_method
-
-        # This actually calls BaseRequestHandler.__init__.
-        CGIHTTPServer.CGIHTTPRequestHandler.__init__(
-            self, request, client_address, server)
-
-    def parse_request(self):
-        """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
-
-        Return True to continue processing for HTTP(S), False otherwise.
-
-        See BaseHTTPRequestHandler.handle_one_request method which calls
-        this method to understand how the return value will be handled.
-        """
-
-        # We hook parse_request method, but also call the original
-        # CGIHTTPRequestHandler.parse_request since when we return False,
-        # CGIHTTPRequestHandler.handle_one_request continues processing and
-        # it needs variables set by CGIHTTPRequestHandler.parse_request.
-        #
-        # Variables set by this method will be also used by WebSocket request
-        # handling (self.path, self.command, self.requestline, etc. See also
-        # how _StandaloneRequest's members are implemented using these
-        # attributes).
-        if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
-            return False
-
-        if self.command == "CONNECT":
-            self.send_response(200, "Connected")
-            self.send_header("Connection", "keep-alive")
-            self.end_headers()
-            return False
-
-        if self._options.use_basic_auth:
-            auth = self.headers.getheader('Authorization')
-            if auth != self._options.basic_auth_credential:
-                self.send_response(401)
-                self.send_header('WWW-Authenticate',
-                                 'Basic realm="Pywebsocket"')
-                self.end_headers()
-                self._logger.info('Request basic authentication')
-                return False
-
-        host, port, resource = http_header_util.parse_uri(self.path)
-
-        # Special paths for XMLHttpRequest benchmark
-        xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245'
-        if resource == (xhr_benchmark_helper_prefix + '_send'):
-            xhr_benchmark_handler = XHRBenchmarkHandler(
-                self.headers, self.rfile, self.wfile)
-            xhr_benchmark_handler.do_send()
-            return False
-        if resource == (xhr_benchmark_helper_prefix + '_receive'):
-            xhr_benchmark_handler = XHRBenchmarkHandler(
-                self.headers, self.rfile, self.wfile)
-            xhr_benchmark_handler.do_receive()
-            return False
-
-        if resource is None:
-            self._logger.info('Invalid URI: %r', self.path)
-            self._logger.info('Fallback to CGIHTTPRequestHandler')
-            return True
-        server_options = self.server.websocket_server_options
-        if host is not None:
-            validation_host = server_options.validation_host
-            if validation_host is not None and host != validation_host:
-                self._logger.info('Invalid host: %r (expected: %r)',
-                                  host,
-                                  validation_host)
-                self._logger.info('Fallback to CGIHTTPRequestHandler')
-                return True
-        if port is not None:
-            validation_port = server_options.validation_port
-            if validation_port is not None and port != validation_port:
-                self._logger.info('Invalid port: %r (expected: %r)',
-                                  port,
-                                  validation_port)
-                self._logger.info('Fallback to CGIHTTPRequestHandler')
-                return True
-        self.path = resource
-
-        request = _StandaloneRequest(self, self._options.use_tls)
-
-        try:
-            # Fallback to default http handler for request paths for which
-            # we don't have request handlers.
-            if not self._options.dispatcher.get_handler_suite(self.path):
-                self._logger.info('No handler for resource: %r',
-                                  self.path)
-                self._logger.info('Fallback to CGIHTTPRequestHandler')
-                return True
-        except dispatch.DispatchException, e:
-            self._logger.info('Dispatch failed for error: %s', e)
-            self.send_error(e.status)
-            return False
-
-        # If any Exceptions without except clause setup (including
-        # DispatchException) is raised below this point, it will be caught
-        # and logged by WebSocketServer.
-
-        try:
-            try:
-                handshake.do_handshake(
-                    request,
-                    self._options.dispatcher,
-                    allowDraft75=self._options.allow_draft75,
-                    strict=self._options.strict)
-            except handshake.VersionException, e:
-                self._logger.info('Handshake failed for version error: %s', e)
-                self.send_response(common.HTTP_STATUS_BAD_REQUEST)
-                self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER,
-                                 e.supported_versions)
-                self.end_headers()
-                return False
-            except handshake.HandshakeException, e:
-                # Handshake for ws(s) failed.
-                self._logger.info('Handshake failed for error: %s', e)
-                self.send_error(e.status)
-                return False
-
-            request._dispatcher = self._options.dispatcher
-            self._options.dispatcher.transfer_data(request)
-        except handshake.AbortedByUserException, e:
-            self._logger.info('Aborted: %s', e)
-        return False
-
-    def log_request(self, code='-', size='-'):
-        """Override BaseHTTPServer.log_request."""
-
-        self._logger.info('"%s" %s %s',
-                          self.requestline, str(code), str(size))
-
-    def log_error(self, *args):
-        """Override BaseHTTPServer.log_error."""
-
-        # Despite the name, this method is for warnings than for errors.
-        # For example, HTTP status code is logged by this method.
-        self._logger.warning('%s - %s',
-                             self.address_string(),
-                             args[0] % args[1:])
-
-    def is_cgi(self):
-        """Test whether self.path corresponds to a CGI script.
-
-        Add extra check that self.path doesn't contains ..
-        Also check if the file is a executable file or not.
-        If the file is not executable, it is handled as static file or dir
-        rather than a CGI script.
-        """
-
-        if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
-            if '..' in self.path:
-                return False
-            # strip query parameter from request path
-            resource_name = self.path.split('?', 2)[0]
-            # convert resource_name into real path name in filesystem.
-            scriptfile = self.translate_path(resource_name)
-            if not os.path.isfile(scriptfile):
-                return False
-            if not self.is_executable(scriptfile):
-                return False
-            return True
-        return False
-
-
-def _get_logger_from_class(c):
-    return logging.getLogger('%s.%s' % (c.__module__, c.__name__))
-
-
-def _configure_logging(options):
-    logging.addLevelName(common.LOGLEVEL_FINE, 'FINE')
-
-    logger = logging.getLogger()
-    logger.setLevel(logging.getLevelName(options.log_level.upper()))
-    if options.log_file:
-        handler = logging.handlers.RotatingFileHandler(
-                options.log_file, 'a', options.log_max, options.log_count)
-    else:
-        handler = logging.StreamHandler()
-    formatter = logging.Formatter(
-            '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s')
-    handler.setFormatter(formatter)
-    logger.addHandler(handler)
-
-    deflate_log_level_name = logging.getLevelName(
-        options.deflate_log_level.upper())
-    _get_logger_from_class(util._Deflater).setLevel(
-        deflate_log_level_name)
-    _get_logger_from_class(util._Inflater).setLevel(
-        deflate_log_level_name)
-
-
-def _build_option_parser():
-    parser = optparse.OptionParser()
-
-    parser.add_option('--config', dest='config_file', type='string',
-                      default=None,
-                      help=('Path to configuration file. See the file comment '
-                            'at the top of this file for the configuration '
-                            'file format'))
-    parser.add_option('-H', '--server-host', '--server_host',
-                      dest='server_host',
-                      default='',
-                      help='server hostname to listen to')
-    parser.add_option('-V', '--validation-host', '--validation_host',
-                      dest='validation_host',
-                      default=None,
-                      help='server hostname to validate in absolute path.')
-    parser.add_option('-p', '--port', dest='port', type='int',
-                      default=common.DEFAULT_WEB_SOCKET_PORT,
-                      help='port to listen to')
-    parser.add_option('-P', '--validation-port', '--validation_port',
-                      dest='validation_port', type='int',
-                      default=None,
-                      help='server port to validate in absolute path.')
-    parser.add_option('-w', '--websock-handlers', '--websock_handlers',
-                      dest='websock_handlers',
-                      default='.',
-                      help=('The root directory of WebSocket handler files. '
-                            'If the path is relative, --document-root is used '
-                            'as the base.'))
-    parser.add_option('-m', '--websock-handlers-map-file',
-                      '--websock_handlers_map_file',
-                      dest='websock_handlers_map_file',
-                      default=None,
-                      help=('WebSocket handlers map file. '
-                            'Each line consists of alias_resource_path and '
-                            'existing_resource_path, separated by spaces.'))
-    parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
-                      default=None,
-                      help=('Must be a directory under --websock-handlers. '
-                            'Only handlers under this directory are scanned '
-                            'and registered to the server. '
-                            'Useful for saving scan time when the handler '
-                            'root directory contains lots of files that are '
-                            'not handler file or are handler files but you '
-                            'don\'t want them to be registered. '))
-    parser.add_option('--allow-handlers-outside-root-dir',
-                      '--allow_handlers_outside_root_dir',
-                      dest='allow_handlers_outside_root_dir',
-                      action='store_true',
-                      default=False,
-                      help=('Scans WebSocket handlers even if their canonical '
-                            'path is not under --websock-handlers.'))
-    parser.add_option('-d', '--document-root', '--document_root',
-                      dest='document_root', default='.',
-                      help='Document root directory.')
-    parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths',
-                      default=None,
-                      help=('CGI paths relative to document_root.'
-                            'Comma-separated. (e.g -x /cgi,/htbin) '
-                            'Files under document_root/cgi_path are handled '
-                            'as CGI programs. Must be executable.'))
-    parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
-                      default=False, help='use TLS (wss://)')
-    parser.add_option('--tls-module', '--tls_module', dest='tls_module',
-                      type='choice',
-                      choices = [_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL],
-                      help='Use ssl module if "%s" is specified. '
-                      'Use pyOpenSSL module if "%s" is specified' %
-                      (_TLS_BY_STANDARD_MODULE, _TLS_BY_PYOPENSSL))
-    parser.add_option('-k', '--private-key', '--private_key',
-                      dest='private_key',
-                      default='', help='TLS private key file.')
-    parser.add_option('-c', '--certificate', dest='certificate',
-                      default='', help='TLS certificate file.')
-    parser.add_option('--tls-client-auth', dest='tls_client_auth',
-                      action='store_true', default=False,
-                      help='Requests TLS client auth on every connection.')
-    parser.add_option('--tls-client-cert-optional',
-                      dest='tls_client_cert_optional',
-                      action='store_true', default=False,
-                      help=('Makes client certificate optional even though '
-                            'TLS client auth is enabled.'))
-    parser.add_option('--tls-client-ca', dest='tls_client_ca', default='',
-                      help=('Specifies a pem file which contains a set of '
-                            'concatenated CA certificates which are used to '
-                            'validate certificates passed from clients'))
-    parser.add_option('--basic-auth', dest='use_basic_auth',
-                      action='store_true', default=False,
-                      help='Requires Basic authentication.')
-    parser.add_option('--basic-auth-credential',
-                      dest='basic_auth_credential', default='test:test',
-                      help='Specifies the credential of basic authentication '
-                      'by username:password pair (e.g. test:test).')
-    parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
-                      default='', help='Log file.')
-    # Custom log level:
-    # - FINE: Prints status of each frame processing step
-    parser.add_option('--log-level', '--log_level', type='choice',
-                      dest='log_level', default='warn',
-                      choices=['fine',
-                               'debug', 'info', 'warning', 'warn', 'error',
-                               'critical'],
-                      help='Log level.')
-    parser.add_option('--deflate-log-level', '--deflate_log_level',
-                      type='choice',
-                      dest='deflate_log_level', default='warn',
-                      choices=['debug', 'info', 'warning', 'warn', 'error',
-                               'critical'],
-                      help='Log level for _Deflater and _Inflater.')
-    parser.add_option('--thread-monitor-interval-in-sec',
-                      '--thread_monitor_interval_in_sec',
-                      dest='thread_monitor_interval_in_sec',
-                      type='int', default=-1,
-                      help=('If positive integer is specified, run a thread '
-                            'monitor to show the status of server threads '
-                            'periodically in the specified inteval in '
-                            'second. If non-positive integer is specified, '
-                            'disable the thread monitor.'))
-    parser.add_option('--log-max', '--log_max', dest='log_max', type='int',
-                      default=_DEFAULT_LOG_MAX_BYTES,
-                      help='Log maximum bytes')
-    parser.add_option('--log-count', '--log_count', dest='log_count',
-                      type='int', default=_DEFAULT_LOG_BACKUP_COUNT,
-                      help='Log backup count')
-    parser.add_option('--allow-draft75', dest='allow_draft75',
-                      action='store_true', default=False,
-                      help='Obsolete option. Ignored.')
-    parser.add_option('--strict', dest='strict', action='store_true',
-                      default=False, help='Obsolete option. Ignored.')
-    parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
-                      default=_DEFAULT_REQUEST_QUEUE_SIZE,
-                      help='request queue size')
-
-    return parser
-
-
-class ThreadMonitor(threading.Thread):
-    daemon = True
-
-    def __init__(self, interval_in_sec):
-        threading.Thread.__init__(self, name='ThreadMonitor')
-
-        self._logger = util.get_class_logger(self)
-
-        self._interval_in_sec = interval_in_sec
-
-    def run(self):
-        while True:
-            thread_name_list = []
-            for thread in threading.enumerate():
-                thread_name_list.append(thread.name)
-            self._logger.info(
-                "%d active threads: %s",
-                threading.active_count(),
-                ', '.join(thread_name_list))
-            time.sleep(self._interval_in_sec)
-
-
-def _parse_args_and_config(args):
-    parser = _build_option_parser()
-
-    # First, parse options without configuration file.
-    temporary_options, temporary_args = parser.parse_args(args=args)
-    if temporary_args:
-        logging.critical(
-            'Unrecognized positional arguments: %r', temporary_args)
-        sys.exit(1)
-
-    if temporary_options.config_file:
-        try:
-            config_fp = open(temporary_options.config_file, 'r')
-        except IOError, e:
-            logging.critical(
-                'Failed to open configuration file %r: %r',
-                temporary_options.config_file,
-                e)
-            sys.exit(1)
-
-        config_parser = ConfigParser.SafeConfigParser()
-        config_parser.readfp(config_fp)
-        config_fp.close()
-
-        args_from_config = []
-        for name, value in config_parser.items('pywebsocket'):
-            args_from_config.append('--' + name)
-            args_from_config.append(value)
-        if args is None:
-            args = args_from_config
-        else:
-            args = args_from_config + args
-        return parser.parse_args(args=args)
-    else:
-        return temporary_options, temporary_args
-
-
-def _main(args=None):
-    """You can call this function from your own program, but please note that
-    this function has some side-effects that might affect your program. For
-    example, util.wrap_popen3_for_win use in this method replaces implementation
-    of os.popen3.
-    """
-
-    options, args = _parse_args_and_config(args=args)
-
-    os.chdir(options.document_root)
-
-    _configure_logging(options)
-
-    if options.allow_draft75:
-        logging.warning('--allow_draft75 option is obsolete.')
-
-    if options.strict:
-        logging.warning('--strict option is obsolete.')
-
-    # TODO(tyoshino): Clean up initialization of CGI related values. Move some
-    # of code here to WebSocketRequestHandler class if it's better.
-    options.cgi_directories = []
-    options.is_executable_method = None
-    if options.cgi_paths:
-        options.cgi_directories = options.cgi_paths.split(',')
-        if sys.platform in ('cygwin', 'win32'):
-            cygwin_path = None
-            # For Win32 Python, it is expected that CYGWIN_PATH
-            # is set to a directory of cygwin binaries.
-            # For example, websocket_server.py in Chromium sets CYGWIN_PATH to
-            # full path of third_party/cygwin/bin.
-            if 'CYGWIN_PATH' in os.environ:
-                cygwin_path = os.environ['CYGWIN_PATH']
-            util.wrap_popen3_for_win(cygwin_path)
-
-            def __check_script(scriptpath):
-                return util.get_script_interp(scriptpath, cygwin_path)
-
-            options.is_executable_method = __check_script
-
-    if options.use_tls:
-        if options.tls_module is None:
-            if _import_ssl():
-                options.tls_module = _TLS_BY_STANDARD_MODULE
-                logging.debug('Using ssl module')
-            elif _import_pyopenssl():
-                options.tls_module = _TLS_BY_PYOPENSSL
-                logging.debug('Using pyOpenSSL module')
-            else:
-                logging.critical(
-                        'TLS support requires ssl or pyOpenSSL module.')
-                sys.exit(1)
-        elif options.tls_module == _TLS_BY_STANDARD_MODULE:
-            if not _import_ssl():
-                logging.critical('ssl module is not available')
-                sys.exit(1)
-        elif options.tls_module == _TLS_BY_PYOPENSSL:
-            if not _import_pyopenssl():
-                logging.critical('pyOpenSSL module is not available')
-                sys.exit(1)
-        else:
-            logging.critical('Invalid --tls-module option: %r',
-                             options.tls_module)
-            sys.exit(1)
-
-        if not options.private_key or not options.certificate:
-            logging.critical(
-                    'To use TLS, specify private_key and certificate.')
-            sys.exit(1)
-
-        if (options.tls_client_cert_optional and
-            not options.tls_client_auth):
-            logging.critical('Client authentication must be enabled to '
-                             'specify tls_client_cert_optional')
-            sys.exit(1)
-    else:
-        if options.tls_module is not None:
-            logging.critical('Use --tls-module option only together with '
-                             '--use-tls option.')
-            sys.exit(1)
-
-        if options.tls_client_auth:
-            logging.critical('TLS must be enabled for client authentication.')
-            sys.exit(1)
-
-        if options.tls_client_cert_optional:
-            logging.critical('TLS must be enabled for client authentication.')
-            sys.exit(1)
-
-    if not options.scan_dir:
-        options.scan_dir = options.websock_handlers
-
-    if options.use_basic_auth:
-        options.basic_auth_credential = 'Basic ' + base64.b64encode(
-            options.basic_auth_credential)
-
-    try:
-        if options.thread_monitor_interval_in_sec > 0:
-            # Run a thread monitor to show the status of server threads for
-            # debugging.
-            ThreadMonitor(options.thread_monitor_interval_in_sec).start()
-
-        server = WebSocketServer(options)
-        server.serve_forever()
-    except Exception, e:
-        logging.critical('mod_pywebsocket: %s' % e)
-        logging.critical('mod_pywebsocket: %s' % util.get_stack_trace())
-        sys.exit(1)
-
-
-if __name__ == '__main__':
-    _main(sys.argv[1:])
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/stream.py b/tools/pywebsocket/src/mod_pywebsocket/stream.py
deleted file mode 100644
index edc5332..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/stream.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2011, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""This file exports public symbols.
-"""
-
-
-from mod_pywebsocket._stream_base import BadOperationException
-from mod_pywebsocket._stream_base import ConnectionTerminatedException
-from mod_pywebsocket._stream_base import InvalidFrameException
-from mod_pywebsocket._stream_base import InvalidUTF8Exception
-from mod_pywebsocket._stream_base import UnsupportedFrameException
-from mod_pywebsocket._stream_hixie75 import StreamHixie75
-from mod_pywebsocket._stream_hybi import Frame
-from mod_pywebsocket._stream_hybi import Stream
-from mod_pywebsocket._stream_hybi import StreamOptions
-
-# These methods are intended to be used by WebSocket client developers to have
-# their implementations receive broken data in tests.
-from mod_pywebsocket._stream_hybi import create_close_frame
-from mod_pywebsocket._stream_hybi import create_header
-from mod_pywebsocket._stream_hybi import create_length_header
-from mod_pywebsocket._stream_hybi import create_ping_frame
-from mod_pywebsocket._stream_hybi import create_pong_frame
-from mod_pywebsocket._stream_hybi import create_binary_frame
-from mod_pywebsocket._stream_hybi import create_text_frame
-from mod_pywebsocket._stream_hybi import create_closing_handshake_body
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/util.py b/tools/pywebsocket/src/mod_pywebsocket/util.py
deleted file mode 100644
index d224ae3..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/util.py
+++ /dev/null
@@ -1,416 +0,0 @@
-# Copyright 2011, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""WebSocket utilities.
-"""
-
-
-import array
-import errno
-
-# Import hash classes from a module available and recommended for each Python
-# version and re-export those symbol. Use sha and md5 module in Python 2.4, and
-# hashlib module in Python 2.6.
-try:
-    import hashlib
-    md5_hash = hashlib.md5
-    sha1_hash = hashlib.sha1
-except ImportError:
-    import md5
-    import sha
-    md5_hash = md5.md5
-    sha1_hash = sha.sha
-
-import StringIO
-import logging
-import os
-import re
-import socket
-import traceback
-import zlib
-
-try:
-    from mod_pywebsocket import fast_masking
-except ImportError:
-    pass
-
-
-def get_stack_trace():
-    """Get the current stack trace as string.
-
-    This is needed to support Python 2.3.
-    TODO: Remove this when we only support Python 2.4 and above.
-          Use traceback.format_exc instead.
-    """
-
-    out = StringIO.StringIO()
-    traceback.print_exc(file=out)
-    return out.getvalue()
-
-
-def prepend_message_to_exception(message, exc):
-    """Prepend message to the exception."""
-
-    exc.args = (message + str(exc),)
-    return
-
-
-def __translate_interp(interp, cygwin_path):
-    """Translate interp program path for Win32 python to run cygwin program
-    (e.g. perl).  Note that it doesn't support path that contains space,
-    which is typically true for Unix, where #!-script is written.
-    For Win32 python, cygwin_path is a directory of cygwin binaries.
-
-    Args:
-      interp: interp command line
-      cygwin_path: directory name of cygwin binary, or None
-    Returns:
-      translated interp command line.
-    """
-    if not cygwin_path:
-        return interp
-    m = re.match('^[^ ]*/([^ ]+)( .*)?', interp)
-    if m:
-        cmd = os.path.join(cygwin_path, m.group(1))
-        return cmd + m.group(2)
-    return interp
-
-
-def get_script_interp(script_path, cygwin_path=None):
-    """Gets #!-interpreter command line from the script.
-
-    It also fixes command path.  When Cygwin Python is used, e.g. in WebKit,
-    it could run "/usr/bin/perl -wT hello.pl".
-    When Win32 Python is used, e.g. in Chromium, it couldn't.  So, fix
-    "/usr/bin/perl" to "<cygwin_path>\perl.exe".
-
-    Args:
-      script_path: pathname of the script
-      cygwin_path: directory name of cygwin binary, or None
-    Returns:
-      #!-interpreter command line, or None if it is not #!-script.
-    """
-    fp = open(script_path)
-    line = fp.readline()
-    fp.close()
-    m = re.match('^#!(.*)', line)
-    if m:
-        return __translate_interp(m.group(1), cygwin_path)
-    return None
-
-
-def wrap_popen3_for_win(cygwin_path):
-    """Wrap popen3 to support #!-script on Windows.
-
-    Args:
-      cygwin_path:  path for cygwin binary if command path is needed to be
-                    translated.  None if no translation required.
-    """
-
-    __orig_popen3 = os.popen3
-
-    def __wrap_popen3(cmd, mode='t', bufsize=-1):
-        cmdline = cmd.split(' ')
-        interp = get_script_interp(cmdline[0], cygwin_path)
-        if interp:
-            cmd = interp + ' ' + cmd
-        return __orig_popen3(cmd, mode, bufsize)
-
-    os.popen3 = __wrap_popen3
-
-
-def hexify(s):
-    return ' '.join(map(lambda x: '%02x' % ord(x), s))
-
-
-def get_class_logger(o):
-    return logging.getLogger(
-        '%s.%s' % (o.__class__.__module__, o.__class__.__name__))
-
-
-class NoopMasker(object):
-    """A masking object that has the same interface as RepeatedXorMasker but
-    just returns the string passed in without making any change.
-    """
-
-    def __init__(self):
-        pass
-
-    def mask(self, s):
-        return s
-
-
-class RepeatedXorMasker(object):
-    """A masking object that applies XOR on the string given to mask method
-    with the masking bytes given to the constructor repeatedly. This object
-    remembers the position in the masking bytes the last mask method call
-    ended and resumes from that point on the next mask method call.
-    """
-
-    def __init__(self, masking_key):
-        self._masking_key = masking_key
-        self._masking_key_index = 0
-
-    def _mask_using_swig(self, s):
-        masked_data = fast_masking.mask(
-                s, self._masking_key, self._masking_key_index)
-        self._masking_key_index = (
-                (self._masking_key_index + len(s)) % len(self._masking_key))
-        return masked_data
-
-    def _mask_using_array(self, s):
-        result = array.array('B')
-        result.fromstring(s)
-
-        # Use temporary local variables to eliminate the cost to access
-        # attributes
-        masking_key = map(ord, self._masking_key)
-        masking_key_size = len(masking_key)
-        masking_key_index = self._masking_key_index
-
-        for i in xrange(len(result)):
-            result[i] ^= masking_key[masking_key_index]
-            masking_key_index = (masking_key_index + 1) % masking_key_size
-
-        self._masking_key_index = masking_key_index
-
-        return result.tostring()
-
-    if 'fast_masking' in globals():
-        mask = _mask_using_swig
-    else:
-        mask = _mask_using_array
-
-
-# By making wbits option negative, we can suppress CMF/FLG (2 octet) and
-# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as
-# deflate library. DICTID won't be added as far as we don't set dictionary.
-# LZ77 window of 32K will be used for both compression and decompression.
-# For decompression, we can just use 32K to cover any windows size. For
-# compression, we use 32K so receivers must use 32K.
-#
-# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level
-# to decode.
-#
-# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of
-# Python. See also RFC1950 (ZLIB 3.3).
-
-
-class _Deflater(object):
-
-    def __init__(self, window_bits):
-        self._logger = get_class_logger(self)
-
-        self._compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits)
-
-    def compress(self, bytes):
-        compressed_bytes = self._compress.compress(bytes)
-        self._logger.debug('Compress input %r', bytes)
-        self._logger.debug('Compress result %r', compressed_bytes)
-        return compressed_bytes
-
-    def compress_and_flush(self, bytes):
-        compressed_bytes = self._compress.compress(bytes)
-        compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH)
-        self._logger.debug('Compress input %r', bytes)
-        self._logger.debug('Compress result %r', compressed_bytes)
-        return compressed_bytes
-
-    def compress_and_finish(self, bytes):
-        compressed_bytes = self._compress.compress(bytes)
-        compressed_bytes += self._compress.flush(zlib.Z_FINISH)
-        self._logger.debug('Compress input %r', bytes)
-        self._logger.debug('Compress result %r', compressed_bytes)
-        return compressed_bytes
-
-
-class _Inflater(object):
-
-    def __init__(self, window_bits):
-        self._logger = get_class_logger(self)
-        self._window_bits = window_bits
-
-        self._unconsumed = ''
-
-        self.reset()
-
-    def decompress(self, size):
-        if not (size == -1 or size > 0):
-            raise Exception('size must be -1 or positive')
-
-        data = ''
-
-        while True:
-            if size == -1:
-                data += self._decompress.decompress(self._unconsumed)
-                # See Python bug http://bugs.python.org/issue12050 to
-                # understand why the same code cannot be used for updating
-                # self._unconsumed for here and else block.
-                self._unconsumed = ''
-            else:
-                data += self._decompress.decompress(
-                    self._unconsumed, size - len(data))
-                self._unconsumed = self._decompress.unconsumed_tail
-            if self._decompress.unused_data:
-                # Encountered a last block (i.e. a block with BFINAL = 1) and
-                # found a new stream (unused_data). We cannot use the same
-                # zlib.Decompress object for the new stream. Create a new
-                # Decompress object to decompress the new one.
-                #
-                # It's fine to ignore unconsumed_tail if unused_data is not
-                # empty.
-                self._unconsumed = self._decompress.unused_data
-                self.reset()
-                if size >= 0 and len(data) == size:
-                    # data is filled. Don't call decompress again.
-                    break
-                else:
-                    # Re-invoke Decompress.decompress to try to decompress all
-                    # available bytes before invoking read which blocks until
-                    # any new byte is available.
-                    continue
-            else:
-                # Here, since unused_data is empty, even if unconsumed_tail is
-                # not empty, bytes of requested length are already in data. We
-                # don't have to "continue" here.
-                break
-
-        if data:
-            self._logger.debug('Decompressed %r', data)
-        return data
-
-    def append(self, data):
-        self._logger.debug('Appended %r', data)
-        self._unconsumed += data
-
-    def reset(self):
-        self._logger.debug('Reset')
-        self._decompress = zlib.decompressobj(-self._window_bits)
-
-
-# Compresses/decompresses given octets using the method introduced in RFC1979.
-
-
-class _RFC1979Deflater(object):
-    """A compressor class that applies DEFLATE to given byte sequence and
-    flushes using the algorithm described in the RFC1979 section 2.1.
-    """
-
-    def __init__(self, window_bits, no_context_takeover):
-        self._deflater = None
-        if window_bits is None:
-            window_bits = zlib.MAX_WBITS
-        self._window_bits = window_bits
-        self._no_context_takeover = no_context_takeover
-
-    def filter(self, bytes, end=True, bfinal=False):
-        if self._deflater is None:
-            self._deflater = _Deflater(self._window_bits)
-
-        if bfinal:
-            result = self._deflater.compress_and_finish(bytes)
-            # Add a padding block with BFINAL = 0 and BTYPE = 0.
-            result = result + chr(0)
-            self._deflater = None
-            return result
-
-        result = self._deflater.compress_and_flush(bytes)
-        if end:
-            # Strip last 4 octets which is LEN and NLEN field of a
-            # non-compressed block added for Z_SYNC_FLUSH.
-            result = result[:-4]
-
-        if self._no_context_takeover and end:
-            self._deflater = None
-
-        return result
-
-
-class _RFC1979Inflater(object):
-    """A decompressor class for byte sequence compressed and flushed following
-    the algorithm described in the RFC1979 section 2.1.
-    """
-
-    def __init__(self, window_bits=zlib.MAX_WBITS):
-        self._inflater = _Inflater(window_bits)
-
-    def filter(self, bytes):
-        # Restore stripped LEN and NLEN field of a non-compressed block added
-        # for Z_SYNC_FLUSH.
-        self._inflater.append(bytes + '\x00\x00\xff\xff')
-        return self._inflater.decompress(-1)
-
-
-class DeflateSocket(object):
-    """A wrapper class for socket object to intercept send and recv to perform
-    deflate compression and decompression transparently.
-    """
-
-    # Size of the buffer passed to recv to receive compressed data.
-    _RECV_SIZE = 4096
-
-    def __init__(self, socket):
-        self._socket = socket
-
-        self._logger = get_class_logger(self)
-
-        self._deflater = _Deflater(zlib.MAX_WBITS)
-        self._inflater = _Inflater(zlib.MAX_WBITS)
-
-    def recv(self, size):
-        """Receives data from the socket specified on the construction up
-        to the specified size. Once any data is available, returns it even
-        if it's smaller than the specified size.
-        """
-
-        # TODO(tyoshino): Allow call with size=0. It should block until any
-        # decompressed data is available.
-        if size <= 0:
-            raise Exception('Non-positive size passed')
-        while True:
-            data = self._inflater.decompress(size)
-            if len(data) != 0:
-                return data
-
-            read_data = self._socket.recv(DeflateSocket._RECV_SIZE)
-            if not read_data:
-                return ''
-            self._inflater.append(read_data)
-
-    def sendall(self, bytes):
-        self.send(bytes)
-
-    def send(self, bytes):
-        self._socket.sendall(self._deflater.compress_and_flush(bytes))
-        return len(bytes)
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/mod_pywebsocket/xhr_benchmark_handler.py b/tools/pywebsocket/src/mod_pywebsocket/xhr_benchmark_handler.py
deleted file mode 100644
index 6735c7e..0000000
--- a/tools/pywebsocket/src/mod_pywebsocket/xhr_benchmark_handler.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# Copyright 2014 Google Inc. All rights reserved.
-#
-# Use of this source code is governed by a BSD-style
-# license that can be found in the COPYING file or at
-# https://developers.google.com/open-source/licenses/bsd
-
-
-from mod_pywebsocket import util
-
-
-class XHRBenchmarkHandler(object):
-    def __init__(self, headers, rfile, wfile):
-        self._logger = util.get_class_logger(self)
-
-        self.headers = headers
-        self.rfile = rfile
-        self.wfile = wfile
-
-    def do_send(self):
-        content_length = int(self.headers.getheader('Content-Length'))
-
-        self._logger.debug('Requested to receive %s bytes', content_length)
-
-        RECEIVE_BLOCK_SIZE = 1024 * 1024
-
-        bytes_to_receive = content_length
-        while bytes_to_receive > 0:
-            bytes_to_receive_in_this_loop = bytes_to_receive
-            if bytes_to_receive_in_this_loop > RECEIVE_BLOCK_SIZE:
-                bytes_to_receive_in_this_loop = RECEIVE_BLOCK_SIZE
-            received_data = self.rfile.read(bytes_to_receive_in_this_loop)
-            if received_data != ('a' * bytes_to_receive_in_this_loop):
-                self._logger.debug('Request body verification failed')
-                return
-            bytes_to_receive -= len(received_data)
-        if bytes_to_receive < 0:
-            self._logger.debug('Received %d more bytes than expected' %
-                               (-bytes_to_receive))
-            return
-
-        # Return the number of received bytes back to the client.
-        response_body = '%d' % content_length
-        self.wfile.write(
-            'HTTP/1.1 200 OK\r\n'
-            'Content-Type: text/html\r\n'
-            'Content-Length: %d\r\n'
-            '\r\n%s' % (len(response_body), response_body))
-        self.wfile.flush()
-
-    def do_receive(self):
-        content_length = int(self.headers.getheader('Content-Length'))
-        request_body = self.rfile.read(content_length)
-
-        request_array = request_body.split(' ')
-        if len(request_array) < 2:
-            self._logger.debug('Malformed request body: %r', request_body)
-            return
-
-        # Parse the size parameter.
-        bytes_to_send = request_array[0]
-        try:
-            bytes_to_send = int(bytes_to_send)
-        except ValueError, e:
-            self._logger.debug('Malformed size parameter: %r', bytes_to_send)
-            return
-        self._logger.debug('Requested to send %s bytes', bytes_to_send)
-
-        # Parse the transfer encoding parameter.
-        chunked_mode = False
-        mode_parameter = request_array[1]
-        if mode_parameter == 'chunked':
-            self._logger.debug('Requested chunked transfer encoding')
-            chunked_mode = True
-        elif mode_parameter != 'none':
-            self._logger.debug('Invalid mode parameter: %r', mode_parameter)
-            return
-
-        # Write a header
-        response_header = (
-            'HTTP/1.1 200 OK\r\n'
-            'Content-Type: application/octet-stream\r\n')
-        if chunked_mode:
-            response_header += 'Transfer-Encoding: chunked\r\n\r\n'
-        else:
-            response_header += (
-                'Content-Length: %d\r\n\r\n' % bytes_to_send)
-        self.wfile.write(response_header)
-        self.wfile.flush()
-
-        # Write a body
-        SEND_BLOCK_SIZE = 1024 * 1024
-
-        while bytes_to_send > 0:
-            bytes_to_send_in_this_loop = bytes_to_send
-            if bytes_to_send_in_this_loop > SEND_BLOCK_SIZE:
-                bytes_to_send_in_this_loop = SEND_BLOCK_SIZE
-
-            if chunked_mode:
-                self.wfile.write('%x\r\n' % bytes_to_send_in_this_loop)
-            self.wfile.write('a' * bytes_to_send_in_this_loop)
-            if chunked_mode:
-                self.wfile.write('\r\n')
-            self.wfile.flush()
-
-            bytes_to_send -= bytes_to_send_in_this_loop
-
-        if chunked_mode:
-            self.wfile.write('0\r\n\r\n')
-            self.wfile.flush()
diff --git a/tools/pywebsocket/src/setup.py b/tools/pywebsocket/src/setup.py
deleted file mode 100755
index ada8db3..0000000
--- a/tools/pywebsocket/src/setup.py
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""Set up script for mod_pywebsocket.
-"""
-
-
-from distutils.core import setup, Extension
-import sys
-
-
-_PACKAGE_NAME = 'mod_pywebsocket'
-
-# Build and use a C++ extension for faster masking. SWIG is required.
-_USE_FAST_MASKING = False
-
-if sys.version < '2.3':
-    print >> sys.stderr, '%s requires Python 2.3 or later.' % _PACKAGE_NAME
-    sys.exit(1)
-
-if _USE_FAST_MASKING:
-    setup(ext_modules=[
-                  Extension(
-                          'mod_pywebsocket/_fast_masking',
-                          ['mod_pywebsocket/fast_masking.i'],
-                          swig_opts=['-c++'])])
-
-setup(author='Yuzo Fujishima',
-      author_email='yuzo@chromium.org',
-      description='WebSocket extension for Apache HTTP Server.',
-      long_description=(
-              'mod_pywebsocket is an Apache HTTP Server extension for '
-              'the WebSocket Protocol (RFC 6455). '
-              'See mod_pywebsocket/__init__.py for more detail.'),
-      license='See COPYING',
-      name=_PACKAGE_NAME,
-      packages=[_PACKAGE_NAME, _PACKAGE_NAME + '.handshake'],
-      url='http://code.google.com/p/pywebsocket/',
-      # See the source of distutils.version, distutils.versionpredicate and
-      # distutils.dist to understand how to name version numbers.
-      version='0.7.9',
-      )
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/test_extensions.py b/tools/pywebsocket/src/test/test_extensions.py
deleted file mode 100755
index 6c8b126..0000000
--- a/tools/pywebsocket/src/test/test_extensions.py
+++ /dev/null
@@ -1,360 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""Tests for extensions module."""
-
-
-import unittest
-import zlib
-
-import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
-
-from mod_pywebsocket import common
-from mod_pywebsocket import extensions
-
-
-class ExtensionsTest(unittest.TestCase):
-    """A unittest for non-class methods in extensions.py"""
-
-    def test_parse_window_bits(self):
-        self.assertRaises(ValueError, extensions._parse_window_bits, None)
-        self.assertRaises(ValueError, extensions._parse_window_bits, 'foobar')
-        self.assertRaises(ValueError, extensions._parse_window_bits, ' 8 ')
-        self.assertRaises(ValueError, extensions._parse_window_bits, 'a8a')
-        self.assertRaises(ValueError, extensions._parse_window_bits, '00000')
-        self.assertRaises(ValueError, extensions._parse_window_bits, '00008')
-        self.assertRaises(ValueError, extensions._parse_window_bits, '0x8')
-
-        self.assertRaises(ValueError, extensions._parse_window_bits, '9.5')
-        self.assertRaises(ValueError, extensions._parse_window_bits, '8.0')
-
-        self.assertTrue(extensions._parse_window_bits, '8')
-        self.assertTrue(extensions._parse_window_bits, '15')
-
-        self.assertRaises(ValueError, extensions._parse_window_bits, '-8')
-        self.assertRaises(ValueError, extensions._parse_window_bits, '0')
-        self.assertRaises(ValueError, extensions._parse_window_bits, '7')
-
-        self.assertRaises(ValueError, extensions._parse_window_bits, '16')
-        self.assertRaises(
-                ValueError, extensions._parse_window_bits, '10000000')
-
-
-class CompressionMethodParameterParserTest(unittest.TestCase):
-    """A unittest for _parse_compression_method which parses the compression
-    method description used by perframe-compression and permessage-compression
-    extension in their "method" extension parameter.
-    """
-
-    def test_parse_method_simple(self):
-        method_list = extensions._parse_compression_method('foo')
-        self.assertEqual(1, len(method_list))
-        method = method_list[0]
-        self.assertEqual('foo', method.name())
-        self.assertEqual(0, len(method.get_parameters()))
-
-    def test_parse_method_with_parameter(self):
-        method_list = extensions._parse_compression_method('foo; x; y=10')
-        self.assertEqual(1, len(method_list))
-        method = method_list[0]
-        self.assertEqual('foo', method.name())
-        self.assertEqual(2, len(method.get_parameters()))
-        self.assertTrue(method.has_parameter('x'))
-        self.assertEqual(None, method.get_parameter_value('x'))
-        self.assertTrue(method.has_parameter('y'))
-        self.assertEqual('10', method.get_parameter_value('y'))
-
-    def test_parse_method_with_quoted_parameter(self):
-        method_list = extensions._parse_compression_method(
-            'foo; x="Hello World"; y=10')
-        self.assertEqual(1, len(method_list))
-        method = method_list[0]
-        self.assertEqual('foo', method.name())
-        self.assertEqual(2, len(method.get_parameters()))
-        self.assertTrue(method.has_parameter('x'))
-        self.assertEqual('Hello World', method.get_parameter_value('x'))
-        self.assertTrue(method.has_parameter('y'))
-        self.assertEqual('10', method.get_parameter_value('y'))
-
-    def test_parse_method_multiple(self):
-        method_list = extensions._parse_compression_method('foo, bar')
-        self.assertEqual(2, len(method_list))
-        self.assertEqual('foo', method_list[0].name())
-        self.assertEqual(0, len(method_list[0].get_parameters()))
-        self.assertEqual('bar', method_list[1].name())
-        self.assertEqual(0, len(method_list[1].get_parameters()))
-
-    def test_parse_method_multiple_methods_with_quoted_parameter(self):
-        method_list = extensions._parse_compression_method(
-            'foo; x="Hello World", bar; y=10')
-        self.assertEqual(2, len(method_list))
-        self.assertEqual('foo', method_list[0].name())
-        self.assertEqual(1, len(method_list[0].get_parameters()))
-        self.assertTrue(method_list[0].has_parameter('x'))
-        self.assertEqual('Hello World',
-                         method_list[0].get_parameter_value('x'))
-        self.assertEqual('bar', method_list[1].name())
-        self.assertEqual(1, len(method_list[1].get_parameters()))
-        self.assertTrue(method_list[1].has_parameter('y'))
-        self.assertEqual('10', method_list[1].get_parameter_value('y'))
-
-    def test_create_method_desc_simple(self):
-        params = common.ExtensionParameter('foo')
-        desc = extensions._create_accepted_method_desc('foo',
-                                                       params.get_parameters())
-        self.assertEqual('foo', desc)
-
-    def test_create_method_desc_with_parameters(self):
-        params = common.ExtensionParameter('foo')
-        params.add_parameter('x', 'Hello, World')
-        params.add_parameter('y', '10')
-        desc = extensions._create_accepted_method_desc('foo',
-                                                       params.get_parameters())
-        self.assertEqual('foo; x="Hello, World"; y=10', desc)
-
-
-class DeflateFrameExtensionProcessorParsingTest(unittest.TestCase):
-    """A unittest for checking that DeflateFrameExtensionProcessor parses given
-    extension parameter correctly.
-    """
-
-    def test_registry(self):
-        processor = extensions.get_extension_processor(
-                common.ExtensionParameter('deflate-frame'))
-        self.assertIsInstance(processor,
-                              extensions.DeflateFrameExtensionProcessor)
-
-        processor = extensions.get_extension_processor(
-                common.ExtensionParameter('x-webkit-deflate-frame'))
-        self.assertIsInstance(processor,
-                              extensions.DeflateFrameExtensionProcessor)
-
-    def test_minimal_offer(self):
-        processor = extensions.DeflateFrameExtensionProcessor(
-            common.ExtensionParameter('perframe-deflate'))
-
-        response = processor.get_extension_response()
-        self.assertEqual('perframe-deflate', response.name())
-        self.assertEqual(0, len(response.get_parameters()))
-
-        self.assertEqual(zlib.MAX_WBITS,
-                         processor._rfc1979_deflater._window_bits)
-        self.assertFalse(processor._rfc1979_deflater._no_context_takeover)
-
-    def test_offer_with_max_window_bits(self):
-        parameter = common.ExtensionParameter('perframe-deflate')
-        parameter.add_parameter('max_window_bits', '10')
-        processor = extensions.DeflateFrameExtensionProcessor(parameter)
-
-        response = processor.get_extension_response()
-        self.assertEqual('perframe-deflate', response.name())
-        self.assertEqual(0, len(response.get_parameters()))
-
-        self.assertEqual(10, processor._rfc1979_deflater._window_bits)
-
-    def test_offer_with_out_of_range_max_window_bits(self):
-        parameter = common.ExtensionParameter('perframe-deflate')
-        parameter.add_parameter('max_window_bits', '0')
-        processor = extensions.DeflateFrameExtensionProcessor(parameter)
-
-        self.assertIsNone(processor.get_extension_response())
-
-    def test_offer_with_max_window_bits_without_value(self):
-        parameter = common.ExtensionParameter('perframe-deflate')
-        parameter.add_parameter('max_window_bits', None)
-        processor = extensions.DeflateFrameExtensionProcessor(parameter)
-
-        self.assertIsNone(processor.get_extension_response())
-
-    def test_offer_with_no_context_takeover(self):
-        parameter = common.ExtensionParameter('perframe-deflate')
-        parameter.add_parameter('no_context_takeover', None)
-        processor = extensions.DeflateFrameExtensionProcessor(parameter)
-
-        response = processor.get_extension_response()
-        self.assertEqual('perframe-deflate', response.name())
-        self.assertEqual(0, len(response.get_parameters()))
-
-        self.assertTrue(processor._rfc1979_deflater._no_context_takeover)
-
-    def test_offer_with_no_context_takeover_with_value(self):
-        parameter = common.ExtensionParameter('perframe-deflate')
-        parameter.add_parameter('no_context_takeover', 'foobar')
-        processor = extensions.DeflateFrameExtensionProcessor(parameter)
-
-        self.assertIsNone(processor.get_extension_response())
-
-    def test_offer_with_unknown_parameter(self):
-        parameter = common.ExtensionParameter('perframe-deflate')
-        parameter.add_parameter('foo', 'bar')
-        processor = extensions.DeflateFrameExtensionProcessor(parameter)
-
-        response = processor.get_extension_response()
-        self.assertEqual('perframe-deflate', response.name())
-        self.assertEqual(0, len(response.get_parameters()))
-
-
-class PerMessageDeflateExtensionProcessorParsingTest(unittest.TestCase):
-    """A unittest for checking that PerMessageDeflateExtensionProcessor parses
-    given extension parameter correctly.
-    """
-
-    def test_registry(self):
-        processor = extensions.get_extension_processor(
-                common.ExtensionParameter('permessage-deflate'))
-        self.assertIsInstance(processor,
-                              extensions.PerMessageDeflateExtensionProcessor)
-
-    def test_minimal_offer(self):
-        processor = extensions.PerMessageDeflateExtensionProcessor(
-            common.ExtensionParameter('permessage-deflate'))
-
-        response = processor.get_extension_response()
-        self.assertEqual('permessage-deflate', response.name())
-        self.assertEqual(0, len(response.get_parameters()))
-
-        self.assertEqual(zlib.MAX_WBITS,
-                         processor._rfc1979_deflater._window_bits)
-        self.assertFalse(processor._rfc1979_deflater._no_context_takeover)
-
-    def test_offer_with_max_window_bits(self):
-        parameter = common.ExtensionParameter('permessage-deflate')
-        parameter.add_parameter('server_max_window_bits', '10')
-        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
-
-        response = processor.get_extension_response()
-        self.assertEqual('permessage-deflate', response.name())
-        self.assertEqual([('server_max_window_bits', '10')],
-                         response.get_parameters())
-
-        self.assertEqual(10, processor._rfc1979_deflater._window_bits)
-
-    def test_offer_with_out_of_range_max_window_bits(self):
-        parameter = common.ExtensionParameter('permessage-deflate')
-        parameter.add_parameter('server_max_window_bits', '0')
-        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
-
-        self.assertIsNone(processor.get_extension_response())
-
-    def test_offer_with_max_window_bits_without_value(self):
-        parameter = common.ExtensionParameter('permessage-deflate')
-        parameter.add_parameter('server_max_window_bits', None)
-        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
-
-        self.assertIsNone(processor.get_extension_response())
-
-    def test_offer_with_no_context_takeover(self):
-        parameter = common.ExtensionParameter('permessage-deflate')
-        parameter.add_parameter('server_no_context_takeover', None)
-        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
-
-        response = processor.get_extension_response()
-        self.assertEqual('permessage-deflate', response.name())
-        self.assertEqual([('server_no_context_takeover', None)],
-                         response.get_parameters())
-
-        self.assertTrue(processor._rfc1979_deflater._no_context_takeover)
-
-    def test_offer_with_no_context_takeover_with_value(self):
-        parameter = common.ExtensionParameter('permessage-deflate')
-        parameter.add_parameter('server_no_context_takeover', 'foobar')
-        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
-
-        self.assertIsNone(processor.get_extension_response())
-
-    def test_offer_with_unknown_parameter(self):
-        parameter = common.ExtensionParameter('permessage-deflate')
-        parameter.add_parameter('foo', 'bar')
-        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
-
-        self.assertIsNone(processor.get_extension_response())
-
-
-class PerMessageDeflateExtensionProcessorBuildingTest(unittest.TestCase):
-    """A unittest for checking that PerMessageDeflateExtensionProcessor builds
-    a response based on specified options correctly.
-    """
-
-    def test_response_with_max_window_bits(self):
-        parameter = common.ExtensionParameter('permessage-deflate')
-        parameter.add_parameter('client_max_window_bits', None)
-        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
-        processor.set_client_max_window_bits(10)
-
-        response = processor.get_extension_response()
-        self.assertEqual('permessage-deflate', response.name())
-        self.assertEqual([('client_max_window_bits', '10')],
-                         response.get_parameters())
-
-    def test_response_with_max_window_bits_without_client_permission(self):
-        processor = extensions.PerMessageDeflateExtensionProcessor(
-            common.ExtensionParameter('permessage-deflate'))
-        processor.set_client_max_window_bits(10)
-
-        response = processor.get_extension_response()
-        self.assertIsNone(response)
-
-    def test_response_with_true_for_no_context_takeover(self):
-        processor = extensions.PerMessageDeflateExtensionProcessor(
-            common.ExtensionParameter('permessage-deflate'))
-
-        processor.set_client_no_context_takeover(True)
-
-        response = processor.get_extension_response()
-        self.assertEqual('permessage-deflate', response.name())
-        self.assertEqual([('client_no_context_takeover', None)],
-                         response.get_parameters())
-
-    def test_response_with_false_for_no_context_takeover(self):
-        processor = extensions.PerMessageDeflateExtensionProcessor(
-            common.ExtensionParameter('permessage-deflate'))
-
-        processor.set_client_no_context_takeover(False)
-
-        response = processor.get_extension_response()
-        self.assertEqual('permessage-deflate', response.name())
-        self.assertEqual(0, len(response.get_parameters()))
-
-
-class PerMessageCompressExtensionProcessorTest(unittest.TestCase):
-    def test_registry(self):
-        processor = extensions.get_extension_processor(
-                common.ExtensionParameter('permessage-compress'))
-        self.assertIsInstance(processor,
-                              extensions.PerMessageCompressExtensionProcessor)
-
-
-if __name__ == '__main__':
-    unittest.main()
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/test_handshake_hybi.py b/tools/pywebsocket/src/test/test_handshake_hybi.py
deleted file mode 100755
index 6c87138..0000000
--- a/tools/pywebsocket/src/test/test_handshake_hybi.py
+++ /dev/null
@@ -1,534 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2011, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""Tests for handshake module."""
-
-
-import unittest
-
-import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
-from mod_pywebsocket import common
-from mod_pywebsocket.handshake._base import AbortedByUserException
-from mod_pywebsocket.handshake._base import HandshakeException
-from mod_pywebsocket.handshake._base import VersionException
-from mod_pywebsocket.handshake.hybi import Handshaker
-
-import mock
-
-
-class RequestDefinition(object):
-    """A class for holding data for constructing opening handshake strings for
-    testing the opening handshake processor.
-    """
-
-    def __init__(self, method, uri, headers):
-        self.method = method
-        self.uri = uri
-        self.headers = headers
-
-
-def _create_good_request_def():
-    return RequestDefinition(
-        'GET', '/demo',
-        {'Host': 'server.example.com',
-         'Upgrade': 'websocket',
-         'Connection': 'Upgrade',
-         'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
-         'Sec-WebSocket-Version': '13',
-         'Origin': 'http://example.com'})
-
-
-def _create_request(request_def):
-    conn = mock.MockConn('')
-    return mock.MockRequest(
-        method=request_def.method,
-        uri=request_def.uri,
-        headers_in=request_def.headers,
-        connection=conn)
-
-
-def _create_handshaker(request):
-    handshaker = Handshaker(request, mock.MockDispatcher())
-    return handshaker
-
-
-class SubprotocolChoosingDispatcher(object):
-    """A dispatcher for testing. This dispatcher sets the i-th subprotocol
-    of requested ones to ws_protocol where i is given on construction as index
-    argument. If index is negative, default_value will be set to ws_protocol.
-    """
-
-    def __init__(self, index, default_value=None):
-        self.index = index
-        self.default_value = default_value
-
-    def do_extra_handshake(self, conn_context):
-        if self.index >= 0:
-            conn_context.ws_protocol = conn_context.ws_requested_protocols[
-                self.index]
-        else:
-            conn_context.ws_protocol = self.default_value
-
-    def transfer_data(self, conn_context):
-        pass
-
-
-class HandshakeAbortedException(Exception):
-    pass
-
-
-class AbortingDispatcher(object):
-    """A dispatcher for testing. This dispatcher raises an exception in
-    do_extra_handshake to reject the request.
-    """
-
-    def do_extra_handshake(self, conn_context):
-        raise HandshakeAbortedException('An exception to reject the request')
-
-    def transfer_data(self, conn_context):
-        pass
-
-
-class AbortedByUserDispatcher(object):
-    """A dispatcher for testing. This dispatcher raises an
-    AbortedByUserException in do_extra_handshake to reject the request.
-    """
-
-    def do_extra_handshake(self, conn_context):
-        raise AbortedByUserException('An AbortedByUserException to reject the '
-                                     'request')
-
-    def transfer_data(self, conn_context):
-        pass
-
-
-_EXPECTED_RESPONSE = (
-    'HTTP/1.1 101 Switching Protocols\r\n'
-    'Upgrade: websocket\r\n'
-    'Connection: Upgrade\r\n'
-    'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n')
-
-
-class HandshakerTest(unittest.TestCase):
-    """A unittest for draft-ietf-hybi-thewebsocketprotocol-06 and later
-    handshake processor.
-    """
-
-    def test_do_handshake(self):
-        request = _create_request(_create_good_request_def())
-        dispatcher = mock.MockDispatcher()
-        handshaker = Handshaker(request, dispatcher)
-        handshaker.do_handshake()
-
-        self.assertTrue(dispatcher.do_extra_handshake_called)
-
-        self.assertEqual(
-            _EXPECTED_RESPONSE, request.connection.written_data())
-        self.assertEqual('/demo', request.ws_resource)
-        self.assertEqual('http://example.com', request.ws_origin)
-        self.assertEqual(None, request.ws_protocol)
-        self.assertEqual(None, request.ws_extensions)
-        self.assertEqual(common.VERSION_HYBI_LATEST, request.ws_version)
-
-    def test_do_handshake_with_extra_headers(self):
-        request_def = _create_good_request_def()
-        # Add headers not related to WebSocket opening handshake.
-        request_def.headers['FooKey'] = 'BarValue'
-        request_def.headers['EmptyKey'] = ''
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(
-            _EXPECTED_RESPONSE, request.connection.written_data())
-
-    def test_do_handshake_with_capitalized_value(self):
-        request_def = _create_good_request_def()
-        request_def.headers['upgrade'] = 'WEBSOCKET'
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(
-            _EXPECTED_RESPONSE, request.connection.written_data())
-
-        request_def = _create_good_request_def()
-        request_def.headers['Connection'] = 'UPGRADE'
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(
-            _EXPECTED_RESPONSE, request.connection.written_data())
-
-    def test_do_handshake_with_multiple_connection_values(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Connection'] = 'Upgrade, keep-alive, , '
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(
-            _EXPECTED_RESPONSE, request.connection.written_data())
-
-    def test_aborting_handshake(self):
-        handshaker = Handshaker(
-            _create_request(_create_good_request_def()),
-            AbortingDispatcher())
-        # do_extra_handshake raises an exception. Check that it's not caught by
-        # do_handshake.
-        self.assertRaises(HandshakeAbortedException, handshaker.do_handshake)
-
-    def test_do_handshake_with_protocol(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat'
-
-        request = _create_request(request_def)
-        handshaker = Handshaker(request, SubprotocolChoosingDispatcher(0))
-        handshaker.do_handshake()
-
-        EXPECTED_RESPONSE = (
-            'HTTP/1.1 101 Switching Protocols\r\n'
-            'Upgrade: websocket\r\n'
-            'Connection: Upgrade\r\n'
-            'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n'
-            'Sec-WebSocket-Protocol: chat\r\n\r\n')
-
-        self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data())
-        self.assertEqual('chat', request.ws_protocol)
-
-    def test_do_handshake_protocol_not_in_request_but_in_response(self):
-        request_def = _create_good_request_def()
-        request = _create_request(request_def)
-        handshaker = Handshaker(
-            request, SubprotocolChoosingDispatcher(-1, 'foobar'))
-        # No request has been made but ws_protocol is set. HandshakeException
-        # must be raised.
-        self.assertRaises(HandshakeException, handshaker.do_handshake)
-
-    def test_do_handshake_with_protocol_no_protocol_selection(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat'
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        # ws_protocol is not set. HandshakeException must be raised.
-        self.assertRaises(HandshakeException, handshaker.do_handshake)
-
-    def test_do_handshake_with_extensions(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Extensions'] = (
-            'permessage-compress; method=deflate, unknown')
-
-        EXPECTED_RESPONSE = (
-            'HTTP/1.1 101 Switching Protocols\r\n'
-            'Upgrade: websocket\r\n'
-            'Connection: Upgrade\r\n'
-            'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n'
-            'Sec-WebSocket-Extensions: permessage-compress; method=deflate\r\n'
-            '\r\n')
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data())
-        self.assertEqual(1, len(request.ws_extensions))
-        extension = request.ws_extensions[0]
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         extension.name())
-        self.assertEqual(['method'], extension.get_parameter_names())
-        self.assertEqual('deflate', extension.get_parameter_value('method'))
-        self.assertEqual(1, len(request.ws_extension_processors))
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         request.ws_extension_processors[0].name())
-
-    def test_do_handshake_with_permessage_compress(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Extensions'] = (
-            'permessage-compress; method=deflate')
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(1, len(request.ws_extensions))
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         request.ws_extensions[0].name())
-        self.assertEqual(1, len(request.ws_extension_processors))
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         request.ws_extension_processors[0].name())
-
-    def test_do_handshake_with_quoted_extensions(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Extensions'] = (
-            'permessage-compress; method=deflate, , '
-            'unknown; e   =    "mc^2"; ma="\r\n      \\\rf  "; pv=nrt')
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(2, len(request.ws_requested_extensions))
-        first_extension = request.ws_requested_extensions[0]
-        self.assertEqual('permessage-compress', first_extension.name())
-        self.assertEqual(['method'], first_extension.get_parameter_names())
-        self.assertEqual('deflate',
-                         first_extension.get_parameter_value('method'))
-        second_extension = request.ws_requested_extensions[1]
-        self.assertEqual('unknown', second_extension.name())
-        self.assertEqual(
-            ['e', 'ma', 'pv'], second_extension.get_parameter_names())
-        self.assertEqual('mc^2', second_extension.get_parameter_value('e'))
-        self.assertEqual(' \rf ', second_extension.get_parameter_value('ma'))
-        self.assertEqual('nrt', second_extension.get_parameter_value('pv'))
-
-    def test_do_handshake_with_optional_headers(self):
-        request_def = _create_good_request_def()
-        request_def.headers['EmptyValue'] = ''
-        request_def.headers['AKey'] = 'AValue'
-
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        self.assertEqual(
-            'AValue', request.headers_in['AKey'])
-        self.assertEqual(
-            '', request.headers_in['EmptyValue'])
-
-    def test_abort_extra_handshake(self):
-        handshaker = Handshaker(
-            _create_request(_create_good_request_def()),
-            AbortedByUserDispatcher())
-        # do_extra_handshake raises an AbortedByUserException. Check that it's
-        # not caught by do_handshake.
-        self.assertRaises(AbortedByUserException, handshaker.do_handshake)
-
-    def test_do_handshake_with_mux_and_deflate_frame(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % (
-                common.MUX_EXTENSION,
-                common.DEFLATE_FRAME_EXTENSION))
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        # mux should be rejected.
-        self.assertEqual(1, len(request.ws_extensions))
-        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
-                         request.ws_extensions[0].name())
-        self.assertEqual(2, len(request.ws_extension_processors))
-        self.assertEqual(common.MUX_EXTENSION,
-                         request.ws_extension_processors[0].name())
-        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
-                         request.ws_extension_processors[1].name())
-        self.assertFalse(hasattr(request, 'mux_processor'))
-
-    def test_do_handshake_with_deflate_frame_and_mux(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % (
-                common.DEFLATE_FRAME_EXTENSION,
-                common.MUX_EXTENSION))
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        # mux should be rejected.
-        self.assertEqual(1, len(request.ws_extensions))
-        first_extension = request.ws_extensions[0]
-        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
-                         first_extension.name())
-        self.assertEqual(2, len(request.ws_extension_processors))
-        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
-                         request.ws_extension_processors[0].name())
-        self.assertEqual(common.MUX_EXTENSION,
-                         request.ws_extension_processors[1].name())
-        self.assertFalse(hasattr(request, 'mux'))
-
-    def test_do_handshake_with_permessage_compress_and_mux(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Extensions'] = (
-            '%s; method=deflate, %s' % (
-                common.PERMESSAGE_COMPRESSION_EXTENSION,
-                common.MUX_EXTENSION))
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-
-        self.assertEqual(1, len(request.ws_extensions))
-        self.assertEqual(common.MUX_EXTENSION,
-                         request.ws_extensions[0].name())
-        self.assertEqual(2, len(request.ws_extension_processors))
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         request.ws_extension_processors[0].name())
-        self.assertEqual(common.MUX_EXTENSION,
-                         request.ws_extension_processors[1].name())
-        self.assertTrue(hasattr(request, 'mux_processor'))
-        self.assertTrue(request.mux_processor.is_active())
-        mux_extensions = request.mux_processor.extensions()
-        self.assertEqual(1, len(mux_extensions))
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         mux_extensions[0].name())
-
-    def test_do_handshake_with_mux_and_permessage_compress(self):
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Extensions'] = (
-            '%s, %s; method=deflate' % (
-                common.MUX_EXTENSION,
-                common.PERMESSAGE_COMPRESSION_EXTENSION))
-        request = _create_request(request_def)
-        handshaker = _create_handshaker(request)
-        handshaker.do_handshake()
-        # mux should be rejected.
-        self.assertEqual(1, len(request.ws_extensions))
-        first_extension = request.ws_extensions[0]
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         first_extension.name())
-        self.assertEqual(2, len(request.ws_extension_processors))
-        self.assertEqual(common.MUX_EXTENSION,
-                         request.ws_extension_processors[0].name())
-        self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION,
-                         request.ws_extension_processors[1].name())
-        self.assertFalse(hasattr(request, 'mux_processor'))
-
-    def test_bad_requests(self):
-        bad_cases = [
-            ('HTTP request',
-             RequestDefinition(
-                 'GET', '/demo',
-                 {'Host': 'www.google.com',
-                  'User-Agent':
-                      'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;'
-                      ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3'
-                      ' GTB6 GTBA',
-                  'Accept':
-                      'text/html,application/xhtml+xml,application/xml;q=0.9,'
-                      '*/*;q=0.8',
-                  'Accept-Language': 'en-us,en;q=0.5',
-                  'Accept-Encoding': 'gzip,deflate',
-                  'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
-                  'Keep-Alive': '300',
-                  'Connection': 'keep-alive'}), None, True)]
-
-        request_def = _create_good_request_def()
-        request_def.method = 'POST'
-        bad_cases.append(('Wrong method', request_def, None, True))
-
-        request_def = _create_good_request_def()
-        del request_def.headers['Host']
-        bad_cases.append(('Missing Host', request_def, None, True))
-
-        request_def = _create_good_request_def()
-        del request_def.headers['Upgrade']
-        bad_cases.append(('Missing Upgrade', request_def, None, True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Upgrade'] = 'nonwebsocket'
-        bad_cases.append(('Wrong Upgrade', request_def, None, True))
-
-        request_def = _create_good_request_def()
-        del request_def.headers['Connection']
-        bad_cases.append(('Missing Connection', request_def, None, True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Connection'] = 'Downgrade'
-        bad_cases.append(('Wrong Connection', request_def, None, True))
-
-        request_def = _create_good_request_def()
-        del request_def.headers['Sec-WebSocket-Key']
-        bad_cases.append(('Missing Sec-WebSocket-Key', request_def, 400, True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Key'] = (
-            'dGhlIHNhbXBsZSBub25jZQ==garbage')
-        bad_cases.append(('Wrong Sec-WebSocket-Key (with garbage on the tail)',
-                          request_def, 400, True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Key'] = 'YQ=='  # BASE64 of 'a'
-        bad_cases.append(
-            ('Wrong Sec-WebSocket-Key (decoded value is not 16 octets long)',
-             request_def, 400, True))
-
-        request_def = _create_good_request_def()
-        # The last character right before == must be any of A, Q, w and g.
-        request_def.headers['Sec-WebSocket-Key'] = (
-            'AQIDBAUGBwgJCgsMDQ4PEC==')
-        bad_cases.append(
-            ('Wrong Sec-WebSocket-Key (padding bits are not zero)',
-             request_def, 400, True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Key'] = (
-            'dGhlIHNhbXBsZSBub25jZQ==,dGhlIHNhbXBsZSBub25jZQ==')
-        bad_cases.append(
-            ('Wrong Sec-WebSocket-Key (multiple values)',
-             request_def, 400, True))
-
-        request_def = _create_good_request_def()
-        del request_def.headers['Sec-WebSocket-Version']
-        bad_cases.append(('Missing Sec-WebSocket-Version', request_def, None,
-                          True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Version'] = '3'
-        bad_cases.append(('Wrong Sec-WebSocket-Version', request_def, None,
-                          False))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Version'] = '13, 13'
-        bad_cases.append(('Wrong Sec-WebSocket-Version (multiple values)',
-                          request_def, 400, True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Protocol'] = 'illegal\x09protocol'
-        bad_cases.append(('Illegal Sec-WebSocket-Protocol',
-                          request_def, 400, True))
-
-        request_def = _create_good_request_def()
-        request_def.headers['Sec-WebSocket-Protocol'] = ''
-        bad_cases.append(('Empty Sec-WebSocket-Protocol',
-                          request_def, 400, True))
-
-        for (case_name, request_def, expected_status,
-             expect_handshake_exception) in bad_cases:
-            request = _create_request(request_def)
-            handshaker = Handshaker(request, mock.MockDispatcher())
-            try:
-                handshaker.do_handshake()
-                self.fail('No exception thrown for \'%s\' case' % case_name)
-            except HandshakeException, e:
-                self.assertTrue(expect_handshake_exception)
-                self.assertEqual(expected_status, e.status)
-            except VersionException, e:
-                self.assertFalse(expect_handshake_exception)
-
-
-if __name__ == '__main__':
-    unittest.main()
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/test_msgutil.py b/tools/pywebsocket/src/test/test_msgutil.py
deleted file mode 100755
index 5fedcf9..0000000
--- a/tools/pywebsocket/src/test/test_msgutil.py
+++ /dev/null
@@ -1,1356 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""Tests for msgutil module."""
-
-
-import array
-import Queue
-import random
-import struct
-import unittest
-import zlib
-
-import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
-
-from mod_pywebsocket import common
-from mod_pywebsocket.extensions import DeflateFrameExtensionProcessor
-from mod_pywebsocket.extensions import PerMessageCompressExtensionProcessor
-from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor
-from mod_pywebsocket import msgutil
-from mod_pywebsocket.stream import InvalidUTF8Exception
-from mod_pywebsocket.stream import Stream
-from mod_pywebsocket.stream import StreamHixie75
-from mod_pywebsocket.stream import StreamOptions
-from mod_pywebsocket import util
-from test import mock
-
-
-# We use one fixed nonce for testing instead of cryptographically secure PRNG.
-_MASKING_NONCE = 'ABCD'
-
-
-def _mask_hybi(frame):
-    frame_key = map(ord, _MASKING_NONCE)
-    frame_key_len = len(frame_key)
-    result = array.array('B')
-    result.fromstring(frame)
-    count = 0
-    for i in xrange(len(result)):
-        result[i] ^= frame_key[count]
-        count = (count + 1) % frame_key_len
-    return _MASKING_NONCE + result.tostring()
-
-
-def _install_extension_processor(processor, request, stream_options):
-    response = processor.get_extension_response()
-    if response is not None:
-        processor.setup_stream_options(stream_options)
-        request.ws_extension_processors.append(processor)
-
-
-def _create_request_from_rawdata(
-        read_data,
-        deflate_frame_request=None,
-        permessage_compression_request=None,
-        permessage_deflate_request=None):
-    req = mock.MockRequest(connection=mock.MockConn(''.join(read_data)))
-    req.ws_version = common.VERSION_HYBI_LATEST
-    req.ws_extension_processors = []
-
-    processor = None
-    if deflate_frame_request is not None:
-        processor = DeflateFrameExtensionProcessor(deflate_frame_request)
-    elif permessage_compression_request is not None:
-        processor = PerMessageCompressExtensionProcessor(
-                permessage_compression_request)
-    elif permessage_deflate_request is not None:
-        processor = PerMessageDeflateExtensionProcessor(
-                permessage_deflate_request)
-
-    stream_options = StreamOptions()
-    if processor is not None:
-        _install_extension_processor(processor, req, stream_options)
-    req.ws_stream = Stream(req, stream_options)
-
-    return req
-
-
-def _create_request(*frames):
-    """Creates MockRequest using data given as frames.
-
-    frames will be returned on calling request.connection.read() where request
-    is MockRequest returned by this function.
-    """
-
-    read_data = []
-    for (header, body) in frames:
-        read_data.append(header + _mask_hybi(body))
-
-    return _create_request_from_rawdata(read_data)
-
-
-def _create_blocking_request():
-    """Creates MockRequest.
-
-    Data written to a MockRequest can be read out by calling
-    request.connection.written_data().
-    """
-
-    req = mock.MockRequest(connection=mock.MockBlockingConn())
-    req.ws_version = common.VERSION_HYBI_LATEST
-    stream_options = StreamOptions()
-    req.ws_stream = Stream(req, stream_options)
-    return req
-
-
-def _create_request_hixie75(read_data=''):
-    req = mock.MockRequest(connection=mock.MockConn(read_data))
-    req.ws_stream = StreamHixie75(req)
-    return req
-
-
-def _create_blocking_request_hixie75():
-    req = mock.MockRequest(connection=mock.MockBlockingConn())
-    req.ws_stream = StreamHixie75(req)
-    return req
-
-
-class BasicMessageTest(unittest.TestCase):
-    """Basic tests for Stream."""
-
-    def test_send_message(self):
-        request = _create_request()
-        msgutil.send_message(request, 'Hello')
-        self.assertEqual('\x81\x05Hello', request.connection.written_data())
-
-        payload = 'a' * 125
-        request = _create_request()
-        msgutil.send_message(request, payload)
-        self.assertEqual('\x81\x7d' + payload,
-                         request.connection.written_data())
-
-    def test_send_medium_message(self):
-        payload = 'a' * 126
-        request = _create_request()
-        msgutil.send_message(request, payload)
-        self.assertEqual('\x81\x7e\x00\x7e' + payload,
-                         request.connection.written_data())
-
-        payload = 'a' * ((1 << 16) - 1)
-        request = _create_request()
-        msgutil.send_message(request, payload)
-        self.assertEqual('\x81\x7e\xff\xff' + payload,
-                         request.connection.written_data())
-
-    def test_send_large_message(self):
-        payload = 'a' * (1 << 16)
-        request = _create_request()
-        msgutil.send_message(request, payload)
-        self.assertEqual('\x81\x7f\x00\x00\x00\x00\x00\x01\x00\x00' + payload,
-                         request.connection.written_data())
-
-    def test_send_message_unicode(self):
-        request = _create_request()
-        msgutil.send_message(request, u'\u65e5')
-        # U+65e5 is encoded as e6,97,a5 in UTF-8
-        self.assertEqual('\x81\x03\xe6\x97\xa5',
-                         request.connection.written_data())
-
-    def test_send_message_fragments(self):
-        request = _create_request()
-        msgutil.send_message(request, 'Hello', False)
-        msgutil.send_message(request, ' ', False)
-        msgutil.send_message(request, 'World', False)
-        msgutil.send_message(request, '!', True)
-        self.assertEqual('\x01\x05Hello\x00\x01 \x00\x05World\x80\x01!',
-                         request.connection.written_data())
-
-    def test_send_fragments_immediate_zero_termination(self):
-        request = _create_request()
-        msgutil.send_message(request, 'Hello World!', False)
-        msgutil.send_message(request, '', True)
-        self.assertEqual('\x01\x0cHello World!\x80\x00',
-                         request.connection.written_data())
-
-    def test_receive_message(self):
-        request = _create_request(
-            ('\x81\x85', 'Hello'), ('\x81\x86', 'World!'))
-        self.assertEqual('Hello', msgutil.receive_message(request))
-        self.assertEqual('World!', msgutil.receive_message(request))
-
-        payload = 'a' * 125
-        request = _create_request(('\x81\xfd', payload))
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-    def test_receive_medium_message(self):
-        payload = 'a' * 126
-        request = _create_request(('\x81\xfe\x00\x7e', payload))
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-        payload = 'a' * ((1 << 16) - 1)
-        request = _create_request(('\x81\xfe\xff\xff', payload))
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-    def test_receive_large_message(self):
-        payload = 'a' * (1 << 16)
-        request = _create_request(
-            ('\x81\xff\x00\x00\x00\x00\x00\x01\x00\x00', payload))
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-    def test_receive_length_not_encoded_using_minimal_number_of_bytes(self):
-        # Log warning on receiving bad payload length field that doesn't use
-        # minimal number of bytes but continue processing.
-
-        payload = 'a'
-        # 1 byte can be represented without extended payload length field.
-        request = _create_request(
-            ('\x81\xff\x00\x00\x00\x00\x00\x00\x00\x01', payload))
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-    def test_receive_message_unicode(self):
-        request = _create_request(('\x81\x83', '\xe6\x9c\xac'))
-        # U+672c is encoded as e6,9c,ac in UTF-8
-        self.assertEqual(u'\u672c', msgutil.receive_message(request))
-
-    def test_receive_message_erroneous_unicode(self):
-        # \x80 and \x81 are invalid as UTF-8.
-        request = _create_request(('\x81\x82', '\x80\x81'))
-        # Invalid characters should raise InvalidUTF8Exception
-        self.assertRaises(InvalidUTF8Exception,
-                          msgutil.receive_message,
-                          request)
-
-    def test_receive_fragments(self):
-        request = _create_request(
-            ('\x01\x85', 'Hello'),
-            ('\x00\x81', ' '),
-            ('\x00\x85', 'World'),
-            ('\x80\x81', '!'))
-        self.assertEqual('Hello World!', msgutil.receive_message(request))
-
-    def test_receive_fragments_unicode(self):
-        # UTF-8 encodes U+6f22 into e6bca2 and U+5b57 into e5ad97.
-        request = _create_request(
-            ('\x01\x82', '\xe6\xbc'),
-            ('\x00\x82', '\xa2\xe5'),
-            ('\x80\x82', '\xad\x97'))
-        self.assertEqual(u'\u6f22\u5b57', msgutil.receive_message(request))
-
-    def test_receive_fragments_immediate_zero_termination(self):
-        request = _create_request(
-            ('\x01\x8c', 'Hello World!'), ('\x80\x80', ''))
-        self.assertEqual('Hello World!', msgutil.receive_message(request))
-
-    def test_receive_fragments_duplicate_start(self):
-        request = _create_request(
-            ('\x01\x85', 'Hello'), ('\x01\x85', 'World'))
-        self.assertRaises(msgutil.InvalidFrameException,
-                          msgutil.receive_message,
-                          request)
-
-    def test_receive_fragments_intermediate_but_not_started(self):
-        request = _create_request(('\x00\x85', 'Hello'))
-        self.assertRaises(msgutil.InvalidFrameException,
-                          msgutil.receive_message,
-                          request)
-
-    def test_receive_fragments_end_but_not_started(self):
-        request = _create_request(('\x80\x85', 'Hello'))
-        self.assertRaises(msgutil.InvalidFrameException,
-                          msgutil.receive_message,
-                          request)
-
-    def test_receive_message_discard(self):
-        request = _create_request(
-            ('\x8f\x86', 'IGNORE'), ('\x81\x85', 'Hello'),
-            ('\x8f\x89', 'DISREGARD'), ('\x81\x86', 'World!'))
-        self.assertRaises(msgutil.UnsupportedFrameException,
-                          msgutil.receive_message, request)
-        self.assertEqual('Hello', msgutil.receive_message(request))
-        self.assertRaises(msgutil.UnsupportedFrameException,
-                          msgutil.receive_message, request)
-        self.assertEqual('World!', msgutil.receive_message(request))
-
-    def test_receive_close(self):
-        request = _create_request(
-            ('\x88\x8a', struct.pack('!H', 1000) + 'Good bye'))
-        self.assertEqual(None, msgutil.receive_message(request))
-        self.assertEqual(1000, request.ws_close_code)
-        self.assertEqual('Good bye', request.ws_close_reason)
-
-    def test_send_longest_close(self):
-        reason = 'a' * 123
-        request = _create_request(
-            ('\x88\xfd',
-             struct.pack('!H', common.STATUS_NORMAL_CLOSURE) + reason))
-        request.ws_stream.close_connection(common.STATUS_NORMAL_CLOSURE,
-                                           reason)
-        self.assertEqual(request.ws_close_code, common.STATUS_NORMAL_CLOSURE)
-        self.assertEqual(request.ws_close_reason, reason)
-
-    def test_send_close_too_long(self):
-        request = _create_request()
-        self.assertRaises(msgutil.BadOperationException,
-                          Stream.close_connection,
-                          request.ws_stream,
-                          common.STATUS_NORMAL_CLOSURE,
-                          'a' * 124)
-
-    def test_send_close_inconsistent_code_and_reason(self):
-        request = _create_request()
-        # reason parameter must not be specified when code is None.
-        self.assertRaises(msgutil.BadOperationException,
-                          Stream.close_connection,
-                          request.ws_stream,
-                          None,
-                          'a')
-
-    def test_send_ping(self):
-        request = _create_request()
-        msgutil.send_ping(request, 'Hello World!')
-        self.assertEqual('\x89\x0cHello World!',
-                         request.connection.written_data())
-
-    def test_send_longest_ping(self):
-        request = _create_request()
-        msgutil.send_ping(request, 'a' * 125)
-        self.assertEqual('\x89\x7d' + 'a' * 125,
-                         request.connection.written_data())
-
-    def test_send_ping_too_long(self):
-        request = _create_request()
-        self.assertRaises(msgutil.BadOperationException,
-                          msgutil.send_ping,
-                          request,
-                          'a' * 126)
-
-    def test_receive_ping(self):
-        """Tests receiving a ping control frame."""
-
-        def handler(request, message):
-            request.called = True
-
-        # Stream automatically respond to ping with pong without any action
-        # by application layer.
-        request = _create_request(
-            ('\x89\x85', 'Hello'), ('\x81\x85', 'World'))
-        self.assertEqual('World', msgutil.receive_message(request))
-        self.assertEqual('\x8a\x05Hello',
-                         request.connection.written_data())
-
-        request = _create_request(
-            ('\x89\x85', 'Hello'), ('\x81\x85', 'World'))
-        request.on_ping_handler = handler
-        self.assertEqual('World', msgutil.receive_message(request))
-        self.assertTrue(request.called)
-
-    def test_receive_longest_ping(self):
-        request = _create_request(
-            ('\x89\xfd', 'a' * 125), ('\x81\x85', 'World'))
-        self.assertEqual('World', msgutil.receive_message(request))
-        self.assertEqual('\x8a\x7d' + 'a' * 125,
-                         request.connection.written_data())
-
-    def test_receive_ping_too_long(self):
-        request = _create_request(('\x89\xfe\x00\x7e', 'a' * 126))
-        self.assertRaises(msgutil.InvalidFrameException,
-                          msgutil.receive_message,
-                          request)
-
-    def test_receive_pong(self):
-        """Tests receiving a pong control frame."""
-
-        def handler(request, message):
-            request.called = True
-
-        request = _create_request(
-            ('\x8a\x85', 'Hello'), ('\x81\x85', 'World'))
-        request.on_pong_handler = handler
-        msgutil.send_ping(request, 'Hello')
-        self.assertEqual('\x89\x05Hello',
-                         request.connection.written_data())
-        # Valid pong is received, but receive_message won't return for it.
-        self.assertEqual('World', msgutil.receive_message(request))
-        # Check that nothing was written after receive_message call.
-        self.assertEqual('\x89\x05Hello',
-                         request.connection.written_data())
-
-        self.assertTrue(request.called)
-
-    def test_receive_unsolicited_pong(self):
-        # Unsolicited pong is allowed from HyBi 07.
-        request = _create_request(
-            ('\x8a\x85', 'Hello'), ('\x81\x85', 'World'))
-        msgutil.receive_message(request)
-
-        request = _create_request(
-            ('\x8a\x85', 'Hello'), ('\x81\x85', 'World'))
-        msgutil.send_ping(request, 'Jumbo')
-        # Body mismatch.
-        msgutil.receive_message(request)
-
-    def test_ping_cannot_be_fragmented(self):
-        request = _create_request(('\x09\x85', 'Hello'))
-        self.assertRaises(msgutil.InvalidFrameException,
-                          msgutil.receive_message,
-                          request)
-
-    def test_ping_with_too_long_payload(self):
-        request = _create_request(('\x89\xfe\x01\x00', 'a' * 256))
-        self.assertRaises(msgutil.InvalidFrameException,
-                          msgutil.receive_message,
-                          request)
-
-
-class DeflateFrameTest(unittest.TestCase):
-    """Tests for checking deflate-frame extension."""
-
-    def test_send_message(self):
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        request = _create_request_from_rawdata(
-            '', deflate_frame_request=extension)
-        msgutil.send_message(request, 'Hello')
-        msgutil.send_message(request, 'World')
-
-        expected = ''
-
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        expected += '\xc1%c' % len(compressed_hello)
-        expected += compressed_hello
-
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_world = compressed_world[:-4]
-        expected += '\xc1%c' % len(compressed_world)
-        expected += compressed_world
-
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_message_bfinal(self):
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        request = _create_request_from_rawdata(
-            '', deflate_frame_request=extension)
-        self.assertEquals(1, len(request.ws_extension_processors))
-        deflate_frame_processor = request.ws_extension_processors[0]
-        deflate_frame_processor.set_bfinal(True)
-        msgutil.send_message(request, 'Hello')
-        msgutil.send_message(request, 'World')
-
-        expected = ''
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_FINISH)
-        compressed_hello = compressed_hello + chr(0)
-        expected += '\xc1%c' % len(compressed_hello)
-        expected += compressed_hello
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_FINISH)
-        compressed_world = compressed_world + chr(0)
-        expected += '\xc1%c' % len(compressed_world)
-        expected += compressed_world
-
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_message_comp_bit(self):
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        request = _create_request_from_rawdata(
-            '', deflate_frame_request=extension)
-        self.assertEquals(1, len(request.ws_extension_processors))
-        deflate_frame_processor = request.ws_extension_processors[0]
-        msgutil.send_message(request, 'Hello')
-        deflate_frame_processor.disable_outgoing_compression()
-        msgutil.send_message(request, 'Hello')
-        deflate_frame_processor.enable_outgoing_compression()
-        msgutil.send_message(request, 'Hello')
-
-        expected = ''
-
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        expected += '\xc1%c' % len(compressed_hello)
-        expected += compressed_hello
-
-        expected += '\x81\x05Hello'
-
-        compressed_2nd_hello = compress.compress('Hello')
-        compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_2nd_hello = compressed_2nd_hello[:-4]
-        expected += '\xc1%c' % len(compressed_2nd_hello)
-        expected += compressed_2nd_hello
-
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_message_no_context_takeover_parameter(self):
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        extension.add_parameter('no_context_takeover', None)
-        request = _create_request_from_rawdata(
-            '', deflate_frame_request=extension)
-        for i in xrange(3):
-            msgutil.send_message(request, 'Hello')
-
-        compressed_message = compress.compress('Hello')
-        compressed_message += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_message = compressed_message[:-4]
-        expected = '\xc1%c' % len(compressed_message)
-        expected += compressed_message
-
-        self.assertEqual(
-            expected + expected + expected, request.connection.written_data())
-
-    def test_bad_request_parameters(self):
-        """Tests that if there's anything wrong with deflate-frame extension
-        request, deflate-frame is rejected.
-        """
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        # max_window_bits less than 8 is illegal.
-        extension.add_parameter('max_window_bits', '7')
-        processor = DeflateFrameExtensionProcessor(extension)
-        self.assertEqual(None, processor.get_extension_response())
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        # max_window_bits greater than 15 is illegal.
-        extension.add_parameter('max_window_bits', '16')
-        processor = DeflateFrameExtensionProcessor(extension)
-        self.assertEqual(None, processor.get_extension_response())
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        # Non integer max_window_bits is illegal.
-        extension.add_parameter('max_window_bits', 'foobar')
-        processor = DeflateFrameExtensionProcessor(extension)
-        self.assertEqual(None, processor.get_extension_response())
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        # no_context_takeover must not have any value.
-        extension.add_parameter('no_context_takeover', 'foobar')
-        processor = DeflateFrameExtensionProcessor(extension)
-        self.assertEqual(None, processor.get_extension_response())
-
-    def test_response_parameters(self):
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        processor = DeflateFrameExtensionProcessor(extension)
-        processor.set_response_window_bits(8)
-        response = processor.get_extension_response()
-        self.assertTrue(response.has_parameter('max_window_bits'))
-        self.assertEqual('8', response.get_parameter_value('max_window_bits'))
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        processor = DeflateFrameExtensionProcessor(extension)
-        processor.set_response_no_context_takeover(True)
-        response = processor.get_extension_response()
-        self.assertTrue(response.has_parameter('no_context_takeover'))
-        self.assertTrue(
-            response.get_parameter_value('no_context_takeover') is None)
-
-    def test_receive_message(self):
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        data = ''
-
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        data += '\xc1%c' % (len(compressed_hello) | 0x80)
-        data += _mask_hybi(compressed_hello)
-
-        compressed_websocket = compress.compress('WebSocket')
-        compressed_websocket += compress.flush(zlib.Z_FINISH)
-        compressed_websocket += '\x00'
-        data += '\xc1%c' % (len(compressed_websocket) | 0x80)
-        data += _mask_hybi(compressed_websocket)
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_world = compressed_world[:-4]
-        data += '\xc1%c' % (len(compressed_world) | 0x80)
-        data += _mask_hybi(compressed_world)
-
-        # Close frame
-        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        request = _create_request_from_rawdata(
-            data, deflate_frame_request=extension)
-        self.assertEqual('Hello', msgutil.receive_message(request))
-        self.assertEqual('WebSocket', msgutil.receive_message(request))
-        self.assertEqual('World', msgutil.receive_message(request))
-
-        self.assertEqual(None, msgutil.receive_message(request))
-
-    def test_receive_message_client_using_smaller_window(self):
-        """Test that frames coming from a client which is using smaller window
-        size that the server are correctly received.
-        """
-
-        # Using the smallest window bits of 8 for generating input frames.
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -8)
-
-        data = ''
-
-        # Use a frame whose content is bigger than the clients' DEFLATE window
-        # size before compression. The content mainly consists of 'a' but
-        # repetition of 'b' is put at the head and tail so that if the window
-        # size is big, the head is back-referenced but if small, not.
-        payload = 'b' * 64 + 'a' * 1024 + 'b' * 64
-        compressed_hello = compress.compress(payload)
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        data += '\xc1%c' % (len(compressed_hello) | 0x80)
-        data += _mask_hybi(compressed_hello)
-
-        # Close frame
-        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        request = _create_request_from_rawdata(
-            data, deflate_frame_request=extension)
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-        self.assertEqual(None, msgutil.receive_message(request))
-
-    def test_receive_message_comp_bit(self):
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        data = ''
-
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        data += '\xc1%c' % (len(compressed_hello) | 0x80)
-        data += _mask_hybi(compressed_hello)
-
-        data += '\x81\x85' + _mask_hybi('Hello')
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        compressed_2nd_hello = compress.compress('Hello')
-        compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_2nd_hello = compressed_2nd_hello[:-4]
-        data += '\xc1%c' % (len(compressed_2nd_hello) | 0x80)
-        data += _mask_hybi(compressed_2nd_hello)
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        request = _create_request_from_rawdata(
-            data, deflate_frame_request=extension)
-        for i in xrange(3):
-            self.assertEqual('Hello', msgutil.receive_message(request))
-
-    def test_receive_message_various_btype(self):
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        data = ''
-
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        data += '\xc1%c' % (len(compressed_hello) | 0x80)
-        data += _mask_hybi(compressed_hello)
-
-        compressed_websocket = compress.compress('WebSocket')
-        compressed_websocket += compress.flush(zlib.Z_FINISH)
-        compressed_websocket += '\x00'
-        data += '\xc1%c' % (len(compressed_websocket) | 0x80)
-        data += _mask_hybi(compressed_websocket)
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_world = compressed_world[:-4]
-        data += '\xc1%c' % (len(compressed_world) | 0x80)
-        data += _mask_hybi(compressed_world)
-
-        # Close frame
-        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
-
-        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
-        request = _create_request_from_rawdata(
-            data, deflate_frame_request=extension)
-        self.assertEqual('Hello', msgutil.receive_message(request))
-        self.assertEqual('WebSocket', msgutil.receive_message(request))
-        self.assertEqual('World', msgutil.receive_message(request))
-
-        self.assertEqual(None, msgutil.receive_message(request))
-
-
-class PerMessageDeflateTest(unittest.TestCase):
-    """Tests for permessage-deflate extension."""
-
-    def test_send_message(self):
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        msgutil.send_message(request, 'Hello')
-
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        expected = '\xc1%c' % len(compressed_hello)
-        expected += compressed_hello
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_empty_message(self):
-        """Test that an empty message is compressed correctly."""
-
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-
-        msgutil.send_message(request, '')
-
-        # Payload in binary: 0b00000010 0b00000000
-        # From LSB,
-        # - 1 bit of BFINAL (0)
-        # - 2 bits of BTYPE (01 that means fixed Huffman)
-        # - 7 bits of the first code (0000000 that is the code for the
-        #   end-of-block)
-        # - 1 bit of BFINAL (0)
-        # - 2 bits of BTYPE (no compression)
-        # - 3 bits of padding
-        self.assertEqual('\xc1\x02\x02\x00',
-                         request.connection.written_data())
-
-    def test_send_message_with_null_character(self):
-        """Test that a simple payload (one null) is framed correctly."""
-
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-
-        msgutil.send_message(request, '\x00')
-
-        # Payload in binary: 0b01100010 0b00000000 0b00000000
-        # From LSB,
-        # - 1 bit of BFINAL (0)
-        # - 2 bits of BTYPE (01 that means fixed Huffman)
-        # - 8 bits of the first code (00110000 that is the code for the literal
-        #   alphabet 0x00)
-        # - 7 bits of the second code (0000000 that is the code for the
-        #   end-of-block)
-        # - 1 bit of BFINAL (0)
-        # - 2 bits of BTYPE (no compression)
-        # - 2 bits of padding
-        self.assertEqual('\xc1\x03\x62\x00\x00',
-                         request.connection.written_data())
-
-    def test_send_two_messages(self):
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        msgutil.send_message(request, 'Hello')
-        msgutil.send_message(request, 'World')
-
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        expected = ''
-
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        expected += '\xc1%c' % len(compressed_hello)
-        expected += compressed_hello
-
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_world = compressed_world[:-4]
-        expected += '\xc1%c' % len(compressed_world)
-        expected += compressed_world
-
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_message_fragmented(self):
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        msgutil.send_message(request, 'Hello', end=False)
-        msgutil.send_message(request, 'Goodbye', end=False)
-        msgutil.send_message(request, 'World')
-
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        expected = '\x41%c' % len(compressed_hello)
-        expected += compressed_hello
-        compressed_goodbye = compress.compress('Goodbye')
-        compressed_goodbye += compress.flush(zlib.Z_SYNC_FLUSH)
-        expected += '\x00%c' % len(compressed_goodbye)
-        expected += compressed_goodbye
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_world = compressed_world[:-4]
-        expected += '\x80%c' % len(compressed_world)
-        expected += compressed_world
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_message_fragmented_empty_first_frame(self):
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        msgutil.send_message(request, '', end=False)
-        msgutil.send_message(request, 'Hello')
-
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        expected = '\x41%c' % len(compressed_hello)
-        expected += compressed_hello
-        compressed_empty = compress.compress('Hello')
-        compressed_empty += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_empty = compressed_empty[:-4]
-        expected += '\x80%c' % len(compressed_empty)
-        expected += compressed_empty
-        print '%r' % expected
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_message_fragmented_empty_last_frame(self):
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        msgutil.send_message(request, 'Hello', end=False)
-        msgutil.send_message(request, '')
-
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        expected = '\x41%c' % len(compressed_hello)
-        expected += compressed_hello
-        compressed_empty = compress.compress('')
-        compressed_empty += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_empty = compressed_empty[:-4]
-        expected += '\x80%c' % len(compressed_empty)
-        expected += compressed_empty
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_send_message_using_small_window(self):
-        common_part = 'abcdefghijklmnopqrstuvwxyz'
-        test_message = common_part + '-' * 30000 + common_part
-
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        extension.add_parameter('server_max_window_bits', '8')
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        msgutil.send_message(request, test_message)
-
-        expected_websocket_header_size = 2
-        expected_websocket_payload_size = 91
-
-        actual_frame = request.connection.written_data()
-        self.assertEqual(expected_websocket_header_size +
-                         expected_websocket_payload_size,
-                         len(actual_frame))
-        actual_header = actual_frame[0:expected_websocket_header_size]
-        actual_payload = actual_frame[expected_websocket_header_size:]
-
-        self.assertEqual(
-                '\xc1%c' % expected_websocket_payload_size, actual_header)
-        decompress = zlib.decompressobj(-8)
-        decompressed_message = decompress.decompress(
-                actual_payload + '\x00\x00\xff\xff')
-        decompressed_message += decompress.flush()
-        self.assertEqual(test_message, decompressed_message)
-        self.assertEqual(0, len(decompress.unused_data))
-        self.assertEqual(0, len(decompress.unconsumed_tail))
-
-    def test_send_message_no_context_takeover_parameter(self):
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        extension.add_parameter('server_no_context_takeover', None)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        for i in xrange(3):
-            msgutil.send_message(request, 'Hello', end=False)
-            msgutil.send_message(request, 'Hello', end=True)
-
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        first_hello = compress.compress('Hello')
-        first_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        expected = '\x41%c' % len(first_hello)
-        expected += first_hello
-        second_hello = compress.compress('Hello')
-        second_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        second_hello = second_hello[:-4]
-        expected += '\x80%c' % len(second_hello)
-        expected += second_hello
-
-        self.assertEqual(
-                expected + expected + expected,
-                request.connection.written_data())
-
-    def test_send_message_fragmented_bfinal(self):
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                '', permessage_deflate_request=extension)
-        self.assertEquals(1, len(request.ws_extension_processors))
-        request.ws_extension_processors[0].set_bfinal(True)
-        msgutil.send_message(request, 'Hello', end=False)
-        msgutil.send_message(request, 'World', end=True)
-
-        expected = ''
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_FINISH)
-        compressed_hello = compressed_hello + chr(0)
-        expected += '\x41%c' % len(compressed_hello)
-        expected += compressed_hello
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_FINISH)
-        compressed_world = compressed_world + chr(0)
-        expected += '\x80%c' % len(compressed_world)
-        expected += compressed_world
-
-        self.assertEqual(expected, request.connection.written_data())
-
-    def test_receive_message_deflate(self):
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        data = '\xc1%c' % (len(compressed_hello) | 0x80)
-        data += _mask_hybi(compressed_hello)
-
-        # Close frame
-        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
-
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                data, permessage_deflate_request=extension)
-        self.assertEqual('Hello', msgutil.receive_message(request))
-
-        self.assertEqual(None, msgutil.receive_message(request))
-
-    def test_receive_message_random_section(self):
-        """Test that a compressed message fragmented into lots of chunks is
-        correctly received.
-        """
-
-        random.seed(a=0)
-        payload = ''.join(
-            [chr(random.randint(0, 255)) for i in xrange(1000)])
-
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_payload = compress.compress(payload)
-        compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_payload = compressed_payload[:-4]
-
-        # Fragment the compressed payload into lots of frames.
-        bytes_chunked = 0
-        data = ''
-        frame_count = 0
-
-        chunk_sizes = []
-
-        while bytes_chunked < len(compressed_payload):
-            # Make sure that
-            # - the length of chunks are equal or less than 125 so that we can
-            #   use 1 octet length header format for all frames.
-            # - at least 10 chunks are created.
-            chunk_size = random.randint(
-                1, min(125,
-                       len(compressed_payload) / 10,
-                       len(compressed_payload) - bytes_chunked))
-            chunk_sizes.append(chunk_size)
-            chunk = compressed_payload[
-                bytes_chunked:bytes_chunked + chunk_size]
-            bytes_chunked += chunk_size
-
-            first_octet = 0x00
-            if len(data) == 0:
-                first_octet = first_octet | 0x42
-            if bytes_chunked == len(compressed_payload):
-                first_octet = first_octet | 0x80
-
-            data += '%c%c' % (first_octet, chunk_size | 0x80)
-            data += _mask_hybi(chunk)
-
-            frame_count += 1
-
-        print "Chunk sizes: %r" % chunk_sizes
-        self.assertTrue(len(chunk_sizes) > 10)
-
-        # Close frame
-        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
-
-        extension = common.ExtensionParameter(
-            common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-            data, permessage_deflate_request=extension)
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-        self.assertEqual(None, msgutil.receive_message(request))
-
-    def test_receive_two_messages(self):
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        data = ''
-
-        compressed_hello = compress.compress('HelloWebSocket')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        split_position = len(compressed_hello) / 2
-        data += '\x41%c' % (split_position | 0x80)
-        data += _mask_hybi(compressed_hello[:split_position])
-
-        data += '\x80%c' % ((len(compressed_hello) - split_position) | 0x80)
-        data += _mask_hybi(compressed_hello[split_position:])
-
-        compress = zlib.compressobj(
-                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-
-        compressed_world = compress.compress('World')
-        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_world = compressed_world[:-4]
-        data += '\xc1%c' % (len(compressed_world) | 0x80)
-        data += _mask_hybi(compressed_world)
-
-        # Close frame
-        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
-
-        extension = common.ExtensionParameter(
-                common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-                data, permessage_deflate_request=extension)
-        self.assertEqual('HelloWebSocket', msgutil.receive_message(request))
-        self.assertEqual('World', msgutil.receive_message(request))
-
-        self.assertEqual(None, msgutil.receive_message(request))
-
-    def test_receive_message_mixed_btype(self):
-        """Test that a message compressed using lots of DEFLATE blocks with
-        various flush mode is correctly received.
-        """
-
-        random.seed(a=0)
-        payload = ''.join(
-            [chr(random.randint(0, 255)) for i in xrange(1000)])
-
-        compress = None
-
-        # Fragment the compressed payload into lots of frames.
-        bytes_chunked = 0
-        compressed_payload = ''
-
-        chunk_sizes = []
-        methods = []
-        sync_used = False
-        finish_used = False
-
-        while bytes_chunked < len(payload):
-            # Make sure at least 10 chunks are created.
-            chunk_size = random.randint(
-                1, min(100, len(payload) - bytes_chunked))
-            chunk_sizes.append(chunk_size)
-            chunk = payload[bytes_chunked:bytes_chunked + chunk_size]
-
-            bytes_chunked += chunk_size
-
-            if compress is None:
-                compress = zlib.compressobj(
-                    zlib.Z_DEFAULT_COMPRESSION,
-                    zlib.DEFLATED,
-                    -zlib.MAX_WBITS)
-
-            if bytes_chunked == len(payload):
-                compressed_payload += compress.compress(chunk)
-                compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH)
-                compressed_payload = compressed_payload[:-4]
-            else:
-                method = random.randint(0, 1)
-                methods.append(method)
-                if method == 0:
-                    compressed_payload += compress.compress(chunk)
-                    compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH)
-                    sync_used = True
-                else:
-                    compressed_payload += compress.compress(chunk)
-                    compressed_payload += compress.flush(zlib.Z_FINISH)
-                    compress = None
-                    finish_used = True
-
-        print "Chunk sizes: %r" % chunk_sizes
-        self.assertTrue(len(chunk_sizes) > 10)
-        print "Methods: %r" % methods
-        self.assertTrue(sync_used)
-        self.assertTrue(finish_used)
-
-        self.assertTrue(125 < len(compressed_payload))
-        self.assertTrue(len(compressed_payload) < 65536)
-        data = '\xc2\xfe' + struct.pack('!H', len(compressed_payload))
-        data += _mask_hybi(compressed_payload)
-
-        # Close frame
-        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
-
-        extension = common.ExtensionParameter(
-            common.PERMESSAGE_DEFLATE_EXTENSION)
-        request = _create_request_from_rawdata(
-            data, permessage_deflate_request=extension)
-        self.assertEqual(payload, msgutil.receive_message(request))
-
-        self.assertEqual(None, msgutil.receive_message(request))
-
-
-class PerMessageCompressTest(unittest.TestCase):
-    """Tests for checking permessage-compression extension."""
-
-    def test_deflate_response_parameters(self):
-        extension = common.ExtensionParameter(
-            common.PERMESSAGE_COMPRESSION_EXTENSION)
-        extension.add_parameter('method', 'deflate')
-        processor = PerMessageCompressExtensionProcessor(extension)
-        response = processor.get_extension_response()
-        self.assertEqual('deflate',
-                         response.get_parameter_value('method'))
-
-        extension = common.ExtensionParameter(
-            common.PERMESSAGE_COMPRESSION_EXTENSION)
-        extension.add_parameter('method', 'deflate')
-        processor = PerMessageCompressExtensionProcessor(extension)
-
-        def _compression_processor_hook(compression_processor):
-            compression_processor.set_client_max_window_bits(8)
-            compression_processor.set_client_no_context_takeover(True)
-        processor.set_compression_processor_hook(
-            _compression_processor_hook)
-        response = processor.get_extension_response()
-        self.assertEqual(
-            'deflate; client_max_window_bits=8; client_no_context_takeover',
-            response.get_parameter_value('method'))
-
-
-class MessageTestHixie75(unittest.TestCase):
-    """Tests for draft-hixie-thewebsocketprotocol-76 stream class."""
-
-    def test_send_message(self):
-        request = _create_request_hixie75()
-        msgutil.send_message(request, 'Hello')
-        self.assertEqual('\x00Hello\xff', request.connection.written_data())
-
-    def test_send_message_unicode(self):
-        request = _create_request_hixie75()
-        msgutil.send_message(request, u'\u65e5')
-        # U+65e5 is encoded as e6,97,a5 in UTF-8
-        self.assertEqual('\x00\xe6\x97\xa5\xff',
-                         request.connection.written_data())
-
-    def test_receive_message(self):
-        request = _create_request_hixie75('\x00Hello\xff\x00World!\xff')
-        self.assertEqual('Hello', msgutil.receive_message(request))
-        self.assertEqual('World!', msgutil.receive_message(request))
-
-    def test_receive_message_unicode(self):
-        request = _create_request_hixie75('\x00\xe6\x9c\xac\xff')
-        # U+672c is encoded as e6,9c,ac in UTF-8
-        self.assertEqual(u'\u672c', msgutil.receive_message(request))
-
-    def test_receive_message_erroneous_unicode(self):
-        # \x80 and \x81 are invalid as UTF-8.
-        request = _create_request_hixie75('\x00\x80\x81\xff')
-        # Invalid characters should be replaced with
-        # U+fffd REPLACEMENT CHARACTER
-        self.assertEqual(u'\ufffd\ufffd', msgutil.receive_message(request))
-
-    def test_receive_message_discard(self):
-        request = _create_request_hixie75('\x80\x06IGNORE\x00Hello\xff'
-                                          '\x01DISREGARD\xff\x00World!\xff')
-        self.assertEqual('Hello', msgutil.receive_message(request))
-        self.assertEqual('World!', msgutil.receive_message(request))
-
-
-class MessageReceiverTest(unittest.TestCase):
-    """Tests the Stream class using MessageReceiver."""
-
-    def test_queue(self):
-        request = _create_blocking_request()
-        receiver = msgutil.MessageReceiver(request)
-
-        self.assertEqual(None, receiver.receive_nowait())
-
-        request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!'))
-        self.assertEqual('Hello!', receiver.receive())
-
-    def test_onmessage(self):
-        onmessage_queue = Queue.Queue()
-
-        def onmessage_handler(message):
-            onmessage_queue.put(message)
-
-        request = _create_blocking_request()
-        receiver = msgutil.MessageReceiver(request, onmessage_handler)
-
-        request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!'))
-        self.assertEqual('Hello!', onmessage_queue.get())
-
-
-class MessageReceiverHixie75Test(unittest.TestCase):
-    """Tests the StreamHixie75 class using MessageReceiver."""
-
-    def test_queue(self):
-        request = _create_blocking_request_hixie75()
-        receiver = msgutil.MessageReceiver(request)
-
-        self.assertEqual(None, receiver.receive_nowait())
-
-        request.connection.put_bytes('\x00Hello!\xff')
-        self.assertEqual('Hello!', receiver.receive())
-
-    def test_onmessage(self):
-        onmessage_queue = Queue.Queue()
-
-        def onmessage_handler(message):
-            onmessage_queue.put(message)
-
-        request = _create_blocking_request_hixie75()
-        receiver = msgutil.MessageReceiver(request, onmessage_handler)
-
-        request.connection.put_bytes('\x00Hello!\xff')
-        self.assertEqual('Hello!', onmessage_queue.get())
-
-
-class MessageSenderTest(unittest.TestCase):
-    """Tests the Stream class using MessageSender."""
-
-    def test_send(self):
-        request = _create_blocking_request()
-        sender = msgutil.MessageSender(request)
-
-        sender.send('World')
-        self.assertEqual('\x81\x05World', request.connection.written_data())
-
-    def test_send_nowait(self):
-        # Use a queue to check the bytes written by MessageSender.
-        # request.connection.written_data() cannot be used here because
-        # MessageSender runs in a separate thread.
-        send_queue = Queue.Queue()
-
-        def write(bytes):
-            send_queue.put(bytes)
-
-        request = _create_blocking_request()
-        request.connection.write = write
-
-        sender = msgutil.MessageSender(request)
-
-        sender.send_nowait('Hello')
-        sender.send_nowait('World')
-        self.assertEqual('\x81\x05Hello', send_queue.get())
-        self.assertEqual('\x81\x05World', send_queue.get())
-
-
-class MessageSenderHixie75Test(unittest.TestCase):
-    """Tests the StreamHixie75 class using MessageSender."""
-
-    def test_send(self):
-        request = _create_blocking_request_hixie75()
-        sender = msgutil.MessageSender(request)
-
-        sender.send('World')
-        self.assertEqual('\x00World\xff', request.connection.written_data())
-
-    def test_send_nowait(self):
-        # Use a queue to check the bytes written by MessageSender.
-        # request.connection.written_data() cannot be used here because
-        # MessageSender runs in a separate thread.
-        send_queue = Queue.Queue()
-
-        def write(bytes):
-            send_queue.put(bytes)
-
-        request = _create_blocking_request_hixie75()
-        request.connection.write = write
-
-        sender = msgutil.MessageSender(request)
-
-        sender.send_nowait('Hello')
-        sender.send_nowait('World')
-        self.assertEqual('\x00Hello\xff', send_queue.get())
-        self.assertEqual('\x00World\xff', send_queue.get())
-
-
-if __name__ == '__main__':
-    unittest.main()
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/test_mux.py b/tools/pywebsocket/src/test/test_mux.py
deleted file mode 100644
index d459894..0000000
--- a/tools/pywebsocket/src/test/test_mux.py
+++ /dev/null
@@ -1,2089 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2012, Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-#     * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-#     * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-"""Tests for mux module."""
-
-import Queue
-import copy
-import logging
-import optparse
-import struct
-import sys
-import unittest
-import time
-import zlib
-
-import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
-
-from mod_pywebsocket import common
-from mod_pywebsocket import mux
-from mod_pywebsocket._stream_base import ConnectionTerminatedException
-from mod_pywebsocket._stream_base import UnsupportedFrameException
-from mod_pywebsocket._stream_hybi import Frame
-from mod_pywebsocket._stream_hybi import Stream
-from mod_pywebsocket._stream_hybi import StreamOptions
-from mod_pywebsocket._stream_hybi import create_binary_frame
-from mod_pywebsocket._stream_hybi import create_close_frame
-from mod_pywebsocket._stream_hybi import create_closing_handshake_body
-from mod_pywebsocket._stream_hybi import parse_frame
-from mod_pywebsocket.extensions import MuxExtensionProcessor
-
-
-import mock
-
-
-_TEST_HEADERS = {'Host': 'server.example.com',
-                 'Upgrade': 'websocket',
-                 'Connection': 'Upgrade',
-                 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
-                 'Sec-WebSocket-Version': '13',
-                 'Origin': 'http://example.com'}
-
-
-class _OutgoingChannelData(object):
-    def __init__(self):
-        self.messages = []
-        self.control_messages = []
-
-        self.builder = mux._InnerMessageBuilder()
-
-class _MockMuxConnection(mock.MockBlockingConn):
-    """Mock class of mod_python connection for mux."""
-
-    def __init__(self):
-        mock.MockBlockingConn.__init__(self)
-        self._control_blocks = []
-        self._channel_data = {}
-
-        self._current_opcode = None
-        self._pending_fragments = []
-
-        self.server_close_code = None
-
-    def write(self, data):
-        """Override MockBlockingConn.write."""
-
-        self._current_data = data
-        self._position = 0
-
-        def _receive_bytes(length):
-            if self._position + length > len(self._current_data):
-                raise ConnectionTerminatedException(
-                    'Failed to receive %d bytes from encapsulated '
-                    'frame' % length)
-            data = self._current_data[self._position:self._position+length]
-            self._position += length
-            return data
-
-        # Parse physical frames and assemble a message if the message is
-        # fragmented.
-        opcode, payload, fin, rsv1, rsv2, rsv3 = (
-            parse_frame(_receive_bytes, unmask_receive=False))
-
-        self._pending_fragments.append(payload)
-
-        if self._current_opcode is None:
-            if opcode == common.OPCODE_CONTINUATION:
-                raise Exception('Sending invalid continuation opcode')
-            self._current_opcode = opcode
-        else:
-            if opcode != common.OPCODE_CONTINUATION:
-                raise Exception('Sending invalid opcode %d' % opcode)
-        if not fin:
-            return
-
-        inner_frame_data = ''.join(self._pending_fragments)
-        self._pending_fragments = []
-        self._current_opcode = None
-
-        # Handle a control message on the physical channel.
-        # TODO(bashi): Support other opcodes if needed.
-        if opcode == common.OPCODE_CLOSE:
-            if len(payload) >= 2:
-                self.server_close_code = struct.unpack('!H', payload[:2])[0]
-            close_body = create_closing_handshake_body(
-                common.STATUS_NORMAL_CLOSURE, '')
-            close_frame = create_close_frame(close_body, mask=True)
-            self.put_bytes(close_frame)
-            return
-
-        # Parse the payload of the message on physical channel.
-        parser = mux._MuxFramePayloadParser(inner_frame_data)
-        channel_id = parser.read_channel_id()
-        if channel_id == mux._CONTROL_CHANNEL_ID:
-            self._control_blocks.extend(list(parser.read_control_blocks()))
-            return
-
-        if not channel_id in self._channel_data:
-            self._channel_data[channel_id] = _OutgoingChannelData()
-        channel_data = self._channel_data[channel_id]
-
-        # Parse logical frames and assemble an inner (logical) message.
-        (inner_fin, inner_rsv1, inner_rsv2, inner_rsv3, inner_opcode,
-         inner_payload) = parser.read_inner_frame()
-        inner_frame = Frame(inner_fin, inner_rsv1, inner_rsv2, inner_rsv3,
-                            inner_opcode, inner_payload)
-        message = channel_data.builder.build(inner_frame)
-        if message is None:
-            return
-
-        if (message.opcode == common.OPCODE_TEXT or
-            message.opcode == common.OPCODE_BINARY):
-            channel_data.messages.append(message.payload)
-
-            self.on_data_message(message.payload)
-        else:
-            channel_data.control_messages.append(
-                {'opcode': message.opcode,
-                 'message': message.payload})
-
-    def on_data_message(self, message):
-        pass
-
-    def get_written_control_blocks(self):
-        return self._control_blocks
-
-    def get_written_messages(self, channel_id):
-        return self._channel_data[channel_id].messages
-
-    def get_written_control_messages(self, channel_id):
-        return self._channel_data[channel_id].control_messages
-
-
-class _FailOnWriteConnection(_MockMuxConnection):
-    """Specicialized version of _MockMuxConnection. Its write() method raises
-    an exception for testing when a data message is written.
-    """
-
-    def on_data_message(self, message):
-        """Override to raise an exception."""
-
-        raise Exception('Intentional failure')
-
-
-class _ChannelEvent(object):
-    """A structure that records channel events."""
-
-    def __init__(self):
-        self.request = None
-        self.messages = []
-        self.exception = None
-        self.client_initiated_closing = False
-
-
-class _MuxMockDispatcher(object):
-    """Mock class of dispatch.Dispatcher for mux."""
-
-    def __init__(self):
-        self.channel_events = {}
-
-    def do_extra_handshake(self, request):
-        if request.ws_requested_protocols is not None:
-            request.ws_protocol = request.ws_requested_protocols[0]
-
-    def _do_echo(self, request, channel_events):
-        while True:
-            message = request.ws_stream.receive_message()
-            if message == None:
-                channel_events.client_initiated_closing = True
-                return
-            if message == 'Goodbye':
-                return
-            channel_events.messages.append(message)
-            # echo back
-            request.ws_stream.send_message(message)
-
-    def _do_ping(self, request, channel_events):
-        request.ws_stream.send_ping('Ping!')
-
-    def _do_ping_while_hello_world(self, request, channel_events):
-        request.ws_stream.send_message('Hello ', end=False)
-        request.ws_stream.send_ping('Ping!')
-        request.ws_stream.send_message('World!', end=True)
-
-    def _do_two_ping_while_hello_world(self, request, channel_events):
-        request.ws_stream.send_message('Hello ', end=False)
-        request.ws_stream.send_ping('Ping!')
-        request.ws_stream.send_ping('Pong!')
-        request.ws_stream.send_message('World!', end=True)
-
-    def transfer_data(self, request):
-        self.channel_events[request.channel_id] = _ChannelEvent()
-        self.channel_events[request.channel_id].request = request
-
-        try:
-            # Note: more handler will be added.
-            if request.uri.endswith('echo'):
-                self._do_echo(request,
-                              self.channel_events[request.channel_id])
-            elif request.uri.endswith('ping'):
-                self._do_ping(request,
-                              self.channel_events[request.channel_id])
-            elif request.uri.endswith('two_ping_while_hello_world'):
-                self._do_two_ping_while_hello_world(
-                    request, self.channel_events[request.channel_id])
-            elif request.uri.endswith('ping_while_hello_world'):
-                self._do_ping_while_hello_world(
-                    request, self.channel_events[request.channel_id])
-            else:
-                raise ValueError('Cannot handle path %r' % request.path)
-            if not request.server_terminated:
-                request.ws_stream.close_connection()
-        except ConnectionTerminatedException, e:
-            self.channel_events[request.channel_id].exception = e
-        except Exception, e:
-            self.channel_events[request.channel_id].exception = e
-            raise
-
-
-def _create_mock_request(connection=None, logical_channel_extensions=None):
-    if connection is None:
-        connection = _MockMuxConnection()
-
-    request = mock.MockRequest(uri='/echo',
-                               headers_in=_TEST_HEADERS,
-                               connection=connection)
-    request.ws_stream = Stream(request, options=StreamOptions())
-    request.mux_processor = MuxExtensionProcessor(
-        common.ExtensionParameter(common.MUX_EXTENSION))
-    if logical_channel_extensions is not None:
-        request.mux_processor.set_extensions(logical_channel_extensions)
-    request.mux_processor.set_quota(8 * 1024)
-    return request
-
-
-def _create_add_channel_request_frame(channel_id, encoding, encoded_handshake):
-    # Allow invalid encoding for testing.
-    first_byte = ((mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) | encoding)
-    payload = (chr(first_byte) +
-               mux._encode_channel_id(channel_id) +
-               mux._encode_number(len(encoded_handshake)) +
-               encoded_handshake)
-    return create_binary_frame(
-        (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True)
-
-
-def _create_drop_channel_frame(channel_id, code=None, message=''):
-    payload = mux._create_drop_channel(channel_id, code, message)
-    return create_binary_frame(
-        (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True)
-
-
-def _create_flow_control_frame(channel_id, replenished_quota):
-    payload = mux._create_flow_control(channel_id, replenished_quota)
-    return create_binary_frame(
-        (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True)
-
-
-def _create_logical_frame(channel_id, message, opcode=common.OPCODE_BINARY,
-                          fin=True, rsv1=False, rsv2=False, rsv3=False,
-                          mask=True):
-    bits = chr((fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | opcode)
-    payload = mux._encode_channel_id(channel_id) + bits + message
-    return create_binary_frame(payload, mask=True)
-
-
-def _create_request_header(path='/echo', extensions=None):
-    headers = (
-        'GET %s HTTP/1.1\r\n'
-        'Host: server.example.com\r\n'
-        'Connection: Upgrade\r\n'
-        'Origin: http://example.com\r\n') % path
-    if extensions:
-        headers += '%s: %s' % (
-            common.SEC_WEBSOCKET_EXTENSIONS_HEADER, extensions)
-    return headers
-
-
-class MuxTest(unittest.TestCase):
-    """A unittest for mux module."""
-
-    def test_channel_id_decode(self):
-        data = '\x00\x01\xbf\xff\xdf\xff\xff\xff\xff\xff\xff'
-        parser = mux._MuxFramePayloadParser(data)
-        channel_id = parser.read_channel_id()
-        self.assertEqual(0, channel_id)
-        channel_id = parser.read_channel_id()
-        self.assertEqual(1, channel_id)
-        channel_id = parser.read_channel_id()
-        self.assertEqual(2 ** 14 - 1, channel_id)
-        channel_id = parser.read_channel_id()
-        self.assertEqual(2 ** 21 - 1, channel_id)
-        channel_id = parser.read_channel_id()
-        self.assertEqual(2 ** 29 - 1, channel_id)
-        self.assertEqual(len(data), parser._read_position)
-
-    def test_channel_id_encode(self):
-        encoded = mux._encode_channel_id(0)
-        self.assertEqual('\x00', encoded)
-        encoded = mux._encode_channel_id(2 ** 14 - 1)
-        self.assertEqual('\xbf\xff', encoded)
-        encoded = mux._encode_channel_id(2 ** 14)
-        self.assertEqual('\xc0@\x00', encoded)
-        encoded = mux._encode_channel_id(2 ** 21 - 1)
-        self.assertEqual('\xdf\xff\xff', encoded)
-        encoded = mux._encode_channel_id(2 ** 21)
-        self.assertEqual('\xe0 \x00\x00', encoded)
-        encoded = mux._encode_channel_id(2 ** 29 - 1)
-        self.assertEqual('\xff\xff\xff\xff', encoded)
-        # channel_id is too large
-        self.assertRaises(ValueError,
-                          mux._encode_channel_id,
-                          2 ** 29)
-
-    def test_read_multiple_control_blocks(self):
-        # Use AddChannelRequest because it can contain arbitrary length of data
-        data = ('\x00\x01\x01a'
-                '\x00\x02\x7d%s'
-                '\x00\x03\x7e\xff\xff%s'
-                '\x00\x04\x7f\x00\x00\x00\x00\x00\x01\x00\x00%s') % (
-            'a' * 0x7d, 'b' * 0xffff, 'c' * 0x10000)
-        parser = mux._MuxFramePayloadParser(data)
-        blocks = list(parser.read_control_blocks())
-        self.assertEqual(4, len(blocks))
-
-        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode)
-        self.assertEqual(1, blocks[0].channel_id)
-        self.assertEqual(1, len(blocks[0].encoded_handshake))
-
-        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[1].opcode)
-        self.assertEqual(2, blocks[1].channel_id)
-        self.assertEqual(0x7d, len(blocks[1].encoded_handshake))
-
-        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[2].opcode)
-        self.assertEqual(3, blocks[2].channel_id)
-        self.assertEqual(0xffff, len(blocks[2].encoded_handshake))
-
-        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[3].opcode)
-        self.assertEqual(4, blocks[3].channel_id)
-        self.assertEqual(0x10000, len(blocks[3].encoded_handshake))
-
-        self.assertEqual(len(data), parser._read_position)
-
-    def test_read_add_channel_request(self):
-        data = '\x00\x01\x01a'
-        parser = mux._MuxFramePayloadParser(data)
-        blocks = list(parser.read_control_blocks())
-        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode)
-        self.assertEqual(1, blocks[0].channel_id)
-        self.assertEqual(1, len(blocks[0].encoded_handshake))
-
-    def test_read_drop_channel(self):
-        data = '\x60\x01\x00'
-        parser = mux._MuxFramePayloadParser(data)
-        blocks = list(parser.read_control_blocks())
-        self.assertEqual(1, len(blocks))
-        self.assertEqual(1, blocks[0].channel_id)
-        self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode)
-        self.assertEqual(None, blocks[0].drop_code)
-        self.assertEqual(0, len(blocks[0].drop_message))
-
-        data = '\x60\x02\x09\x03\xe8Success'
-        parser = mux._MuxFramePayloadParser(data)
-        blocks = list(parser.read_control_blocks())
-        self.assertEqual(1, len(blocks))
-        self.assertEqual(2, blocks[0].channel_id)
-        self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode)
-        self.assertEqual(1000, blocks[0].drop_code)
-        self.assertEqual('Success', blocks[0].drop_message)
-
-        # Reason is too short.
-        data = '\x60\x01\x01\x00'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(mux.PhysicalConnectionError,
-                          lambda: list(parser.read_control_blocks()))
-
-    def test_read_flow_control(self):
-        data = '\x40\x01\x02'
-        parser = mux._MuxFramePayloadParser(data)
-        blocks = list(parser.read_control_blocks())
-        self.assertEqual(1, len(blocks))
-        self.assertEqual(1, blocks[0].channel_id)
-        self.assertEqual(mux._MUX_OPCODE_FLOW_CONTROL, blocks[0].opcode)
-        self.assertEqual(2, blocks[0].send_quota)
-
-    def test_read_new_channel_slot(self):
-        data = '\x80\x01\x02\x02\x03'
-        parser = mux._MuxFramePayloadParser(data)
-        # TODO(bashi): Implement
-        self.assertRaises(mux.PhysicalConnectionError,
-                          lambda: list(parser.read_control_blocks()))
-
-    def test_read_invalid_number_field_in_control_block(self):
-        # No number field.
-        data = ''
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(ValueError, parser._read_number)
-
-        # The last two bytes are missing.
-        data = '\x7e'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(ValueError, parser._read_number)
-
-        # Missing the last one byte.
-        data = '\x7f\x00\x00\x00\x00\x00\x01\x00'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(ValueError, parser._read_number)
-
-        # The length of number field is too large.
-        data = '\x7f\xff\xff\xff\xff\xff\xff\xff\xff'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(ValueError, parser._read_number)
-
-        # The msb of the first byte is set.
-        data = '\x80'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(ValueError, parser._read_number)
-
-        # Using 3 bytes encoding for 125.
-        data = '\x7e\x00\x7d'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(ValueError, parser._read_number)
-
-        # Using 9 bytes encoding for 0xffff
-        data = '\x7f\x00\x00\x00\x00\x00\x00\xff\xff'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(ValueError, parser._read_number)
-
-    def test_read_invalid_size_and_contents(self):
-        # Only contain number field.
-        data = '\x01'
-        parser = mux._MuxFramePayloadParser(data)
-        self.assertRaises(mux.PhysicalConnectionError,
-                          parser._read_size_and_contents)
-
-    def test_create_add_channel_response(self):
-        data = mux._create_add_channel_response(channel_id=1,
-                                                encoded_handshake='FooBar',
-                                                encoding=0,
-                                                rejected=False)
-        self.assertEqual('\x20\x01\x06FooBar', data)
-
-        data = mux._create_add_channel_response(channel_id=2,
-                                                encoded_handshake='Hello',
-                                                encoding=1,
-                                                rejected=True)
-        self.assertEqual('\x31\x02\x05Hello', data)
-
-    def test_create_drop_channel(self):
-        data = mux._create_drop_channel(channel_id=1)
-        self.assertEqual('\x60\x01\x00', data)
-
-        data = mux._create_drop_channel(channel_id=1,
-                                        code=2000,
-                                        message='error')
-        self.assertEqual('\x60\x01\x07\x07\xd0error', data)
-
-        # reason must be empty if code is None
-        self.assertRaises(ValueError,
-                          mux._create_drop_channel,
-                          1, None, 'FooBar')
-
-    def test_parse_request_text(self):
-        request_text = _create_request_header()
-        command, path, version, headers = mux._parse_request_text(request_text)
-        self.assertEqual('GET', command)
-        self.assertEqual('/echo', path)
-        self.assertEqual('HTTP/1.1', version)
-        self.assertEqual(3, len(headers))
-        self.assertEqual('server.example.com', headers['Host'])
-        self.assertEqual('http://example.com', headers['Origin'])
-
-
-class MuxHandlerTest(unittest.TestCase):
-
-    def test_add_channel(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=3, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=3,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=3, message='World'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=3, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual([], dispatcher.channel_events[1].messages)
-        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
-        self.assertEqual(['World'], dispatcher.channel_events[3].messages)
-        # Channel 2
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('Hello', messages[0])
-        # Channel 3
-        messages = request.connection.get_written_messages(3)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('World', messages[0])
-        control_blocks = request.connection.get_written_control_blocks()
-        # There should be 8 control blocks:
-        #   - 1 NewChannelSlot
-        #   - 2 AddChannelResponses for channel id 2 and 3
-        #   - 6 FlowControls for channel id 1 (initialize), 'Hello', 'World',
-        #     and 3 'Goodbye's
-        self.assertEqual(9, len(control_blocks))
-
-    def test_physical_connection_write_failure(self):
-        # Use _FailOnWriteConnection.
-        request = _create_mock_request(connection=_FailOnWriteConnection())
-
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        # Let the worker echo back 'Hello'. It causes _FailOnWriteConnection
-        # raising an exception.
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Hello'))
-
-        # Let the worker exit. This will be unnecessary when
-        # _LogicalConnection.write() is changed to throw an exception if
-        # woke up by on_writer_done.
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        # All threads should be done.
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-    def test_send_blocked(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # On receiving this 'Hello', the server tries to echo back 'Hello',
-        # but it will be blocked since there's no send quota available for the
-        # channel 2.
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-
-        # Wait until the worker is blocked due to send quota shortage.
-        time.sleep(1)
-
-        # Close the channel 2. The worker should be notified of the end of
-        # writer thread and stop waiting for send quota to be replenished.
-        drop_channel = _create_drop_channel_frame(channel_id=2)
-
-        request.connection.put_bytes(drop_channel)
-
-        # Make sure the channel 1 is also closed.
-        drop_channel = _create_drop_channel_frame(channel_id=1)
-        request.connection.put_bytes(drop_channel)
-
-        # All threads should be done.
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-    def test_add_channel_delta_encoding(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        delta = 'GET /echo HTTP/1.1\r\n\r\n'
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=1, encoded_handshake=delta)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('Hello', messages[0])
-
-    def test_add_channel_delta_encoding_override(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        # Override Sec-WebSocket-Protocol.
-        delta = ('GET /echo HTTP/1.1\r\n'
-                 'Sec-WebSocket-Protocol: x-foo\r\n'
-                 '\r\n')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=1, encoded_handshake=delta)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('Hello', messages[0])
-        self.assertEqual('x-foo',
-                         dispatcher.channel_events[2].request.ws_protocol)
-
-    def test_add_channel_delta_after_identity(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-        # Sec-WebSocket-Protocol is different from client's opening handshake
-        # of the physical connection.
-        # TODO(bashi): Remove Upgrade, Connection, Sec-WebSocket-Key and
-        # Sec-WebSocket-Version.
-        encoded_handshake = (
-            'GET /echo HTTP/1.1\r\n'
-            'Host: server.example.com\r\n'
-            'Sec-WebSocket-Protocol: x-foo\r\n'
-            'Connection: Upgrade\r\n'
-            'Origin: http://example.com\r\n'
-            '\r\n')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        delta = 'GET /echo HTTP/1.1\r\n\r\n'
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=3, encoding=1, encoded_handshake=delta)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=3,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=3, message='World'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=3, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual([], dispatcher.channel_events[1].messages)
-        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
-        self.assertEqual(['World'], dispatcher.channel_events[3].messages)
-        # Channel 2
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('Hello', messages[0])
-        # Channel 3
-        messages = request.connection.get_written_messages(3)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('World', messages[0])
-        # Handshake base should be updated.
-        self.assertEqual(
-            'x-foo',
-            mux_handler._handshake_base._headers['Sec-WebSocket-Protocol'])
-
-    def test_add_channel_delta_remove_header(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-        # Override handshake delta base.
-        encoded_handshake = (
-            'GET /echo HTTP/1.1\r\n'
-            'Host: server.example.com\r\n'
-            'Sec-WebSocket-Protocol: x-foo\r\n'
-            'Connection: Upgrade\r\n'
-            'Origin: http://example.com\r\n'
-            '\r\n')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        # Remove Sec-WebSocket-Protocol header.
-        delta = ('GET /echo HTTP/1.1\r\n'
-                 'Sec-WebSocket-Protocol:'
-                 '\r\n')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=3, encoding=1, encoded_handshake=delta)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=3,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=3, message='World'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=3, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual([], dispatcher.channel_events[1].messages)
-        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
-        self.assertEqual(['World'], dispatcher.channel_events[3].messages)
-        # Channel 2
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('Hello', messages[0])
-        # Channel 3
-        messages = request.connection.get_written_messages(3)
-        self.assertEqual(1, len(messages))
-        self.assertEqual('World', messages[0])
-        self.assertEqual(
-            'x-foo',
-            dispatcher.channel_events[2].request.ws_protocol)
-        self.assertEqual(
-            None,
-            dispatcher.channel_events[3].request.ws_protocol)
-
-    def test_add_channel_delta_encoding_permessage_compress(self):
-        # Enable permessage compress extension on the implicitly opened channel.
-        extensions = common.parse_extensions(
-            '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION)
-        request = _create_mock_request(
-            logical_channel_extensions=extensions)
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        delta = 'GET /echo HTTP/1.1\r\n\r\n'
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=1, encoded_handshake=delta)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=20)
-        request.connection.put_bytes(flow_control)
-
-        # Send compressed 'Hello' on logical channel 1 and 2.
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message=compressed_hello,
-                                  rsv1=True))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message=compressed_hello,
-                                  rsv1=True))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual(['Hello'], dispatcher.channel_events[1].messages)
-        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
-        # Written 'Hello's should be compressed.
-        messages = request.connection.get_written_messages(1)
-        self.assertEqual(1, len(messages))
-        self.assertEqual(compressed_hello, messages[0])
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(1, len(messages))
-        self.assertEqual(compressed_hello, messages[0])
-
-    def test_add_channel_delta_encoding_remove_extensions(self):
-        # Enable permessage compress extension on the implicitly opened channel.
-        extensions = common.parse_extensions(
-            '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION)
-        request = _create_mock_request(
-            logical_channel_extensions=extensions)
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        # Remove permessage compress extension.
-        delta = ('GET /echo HTTP/1.1\r\n'
-                 'Sec-WebSocket-Extensions:\r\n'
-                 '\r\n')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=1, encoded_handshake=delta)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=20)
-        request.connection.put_bytes(flow_control)
-
-        # Send compressed message on logical channel 2. The message should
-        # be rejected (since rsv1 is set).
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('Hello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message=compressed_hello,
-                                  rsv1=True))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_NORMAL_CLOSURE, drop_channel.drop_code)
-        self.assertEqual(2, drop_channel.channel_id)
-        # UnsupportedFrameException should be raised on logical channel 2.
-        self.assertTrue(isinstance(dispatcher.channel_events[2].exception,
-                                   UnsupportedFrameException))
-
-    def test_add_channel_invalid_encoding(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=3,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_UNKNOWN_REQUEST_ENCODING,
-                         drop_channel.drop_code)
-        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
-                         request.connection.server_close_code)
-
-    def test_add_channel_incomplete_handshake(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        incomplete_encoded_handshake = 'GET /echo HTTP/1.1'
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=incomplete_encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertTrue(1 in dispatcher.channel_events)
-        self.assertTrue(not 2 in dispatcher.channel_events)
-
-    def test_add_channel_duplicate_channel_id(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_CHANNEL_ALREADY_EXISTS,
-                         drop_channel.drop_code)
-        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
-                         request.connection.server_close_code)
-
-    def test_receive_drop_channel(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        drop_channel = _create_drop_channel_frame(channel_id=2)
-        request.connection.put_bytes(drop_channel)
-
-        # Terminate implicitly opened channel.
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        exception = dispatcher.channel_events[2].exception
-        self.assertTrue(exception.__class__ == ConnectionTerminatedException)
-
-    def test_receive_ping_frame(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=13)
-        request.connection.put_bytes(flow_control)
-
-        ping_frame = _create_logical_frame(channel_id=2,
-                                           message='Hello World!',
-                                           opcode=common.OPCODE_PING)
-        request.connection.put_bytes(ping_frame)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PONG, messages[0]['opcode'])
-        self.assertEqual('Hello World!', messages[0]['message'])
-
-    def test_receive_fragmented_ping(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=13)
-        request.connection.put_bytes(flow_control)
-
-        # Send a ping with message 'Hello world!' in two fragmented frames.
-        ping_frame1 = _create_logical_frame(channel_id=2,
-                                            message='Hello ',
-                                            fin=False,
-                                            opcode=common.OPCODE_PING)
-        request.connection.put_bytes(ping_frame1)
-        ping_frame2 = _create_logical_frame(channel_id=2,
-                                            message='World!',
-                                            fin=True,
-                                            opcode=common.OPCODE_CONTINUATION)
-        request.connection.put_bytes(ping_frame2)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PONG, messages[0]['opcode'])
-        self.assertEqual('Hello World!', messages[0]['message'])
-
-    def test_receive_fragmented_ping_while_receiving_fragmented_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=19)
-        request.connection.put_bytes(flow_control)
-
-        # Send a fragmented frame of message 'Hello '.
-        hello = _create_logical_frame(channel_id=2,
-                                      message='Hello ',
-                                      fin=False)
-        request.connection.put_bytes(hello)
-
-        # Before sending the last fragmented frame of the message, send a
-        # fragmented ping.
-        ping1 = _create_logical_frame(channel_id=2,
-                                      message='Pi',
-                                      fin=False,
-                                      opcode=common.OPCODE_PING)
-        request.connection.put_bytes(ping1)
-        ping2 = _create_logical_frame(channel_id=2,
-                                      message='ng!',
-                                      fin=True,
-                                      opcode=common.OPCODE_CONTINUATION)
-        request.connection.put_bytes(ping2)
-
-        # Send the last fragmented frame of the message.
-        world = _create_logical_frame(channel_id=2,
-                                      message='World!',
-                                      fin=True,
-                                      opcode=common.OPCODE_CONTINUATION)
-        request.connection.put_bytes(world)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(['Hello World!'], messages)
-        control_messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode'])
-        self.assertEqual('Ping!', control_messages[0]['message'])
-
-    def test_receive_two_ping_while_receiving_fragmented_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=25)
-        request.connection.put_bytes(flow_control)
-
-        # Send a fragmented frame of message 'Hello '.
-        hello = _create_logical_frame(channel_id=2,
-                                      message='Hello ',
-                                      fin=False)
-        request.connection.put_bytes(hello)
-
-        # Before sending the last fragmented frame of the message, send a
-        # fragmented ping and a non-fragmented ping.
-        ping1 = _create_logical_frame(channel_id=2,
-                                      message='Pi',
-                                      fin=False,
-                                      opcode=common.OPCODE_PING)
-        request.connection.put_bytes(ping1)
-        ping2 = _create_logical_frame(channel_id=2,
-                                      message='ng!',
-                                      fin=True,
-                                      opcode=common.OPCODE_CONTINUATION)
-        request.connection.put_bytes(ping2)
-        ping3 = _create_logical_frame(channel_id=2,
-                                      message='Pong!',
-                                      fin=True,
-                                      opcode=common.OPCODE_PING)
-        request.connection.put_bytes(ping3)
-
-        # Send the last fragmented frame of the message.
-        world = _create_logical_frame(channel_id=2,
-                                      message='World!',
-                                      fin=True,
-                                      opcode=common.OPCODE_CONTINUATION)
-        request.connection.put_bytes(world)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(['Hello World!'], messages)
-        control_messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode'])
-        self.assertEqual('Ping!', control_messages[0]['message'])
-        self.assertEqual(common.OPCODE_PONG, control_messages[1]['opcode'])
-        self.assertEqual('Pong!', control_messages[1]['message'])
-
-    def test_receive_message_while_receiving_fragmented_ping(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                replenished_quota=19)
-        request.connection.put_bytes(flow_control)
-
-        # Send a fragmented ping.
-        ping1 = _create_logical_frame(channel_id=2,
-                                      message='Pi',
-                                      fin=False,
-                                      opcode=common.OPCODE_PING)
-        request.connection.put_bytes(ping1)
-
-        # Before sending the last fragmented ping, send a message.
-        # The logical channel (2) should be dropped.
-        message = _create_logical_frame(channel_id=2,
-                                        message='Hello world!',
-                                        fin=True)
-        request.connection.put_bytes(message)
-
-        # Send the last fragmented frame of the message.
-        ping2 = _create_logical_frame(channel_id=2,
-                                      message='ng!',
-                                      fin=True,
-                                      opcode=common.OPCODE_CONTINUATION)
-        request.connection.put_bytes(ping2)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(2, drop_channel.channel_id)
-        # No message should be sent on channel 2.
-        self.assertRaises(KeyError,
-                          request.connection.get_written_messages,
-                          2)
-        self.assertRaises(KeyError,
-                          request.connection.get_written_control_messages,
-                          2)
-
-    def test_send_ping(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/ping')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PING, messages[0]['opcode'])
-        self.assertEqual('Ping!', messages[0]['message'])
-
-    def test_send_fragmented_ping(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/ping')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Replenish 3 bytes. This isn't enough to send the whole ping frame
-        # because the frame will have 5 bytes message('Ping!'). The frame
-        # should be fragmented.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=3)
-        request.connection.put_bytes(flow_control)
-
-        # Wait until the worker is blocked due to send quota shortage.
-        time.sleep(1)
-
-        # Replenish remaining 2 + 1 bytes (including extra cost).
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=3)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PING, messages[0]['opcode'])
-        self.assertEqual('Ping!', messages[0]['message'])
-
-    def test_send_fragmented_ping_while_sending_fragmented_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(
-            path='/ping_while_hello_world')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Application will send:
-        # - text message 'Hello ' with fin=0
-        # - ping with 'Ping!' message
-        # - text message 'World!' with fin=1
-        # Replenish (6 + 1) + (2 + 1) bytes so that the ping will be
-        # fragmented on the logical channel.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=10)
-        request.connection.put_bytes(flow_control)
-
-        time.sleep(1)
-
-        # Replenish remaining 3 + 6 bytes.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=9)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(['Hello World!'], messages)
-        control_messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode'])
-        self.assertEqual('Ping!', control_messages[0]['message'])
-
-    def test_send_fragmented_two_ping_while_sending_fragmented_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(
-            path='/two_ping_while_hello_world')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Application will send:
-        # - text message 'Hello ' with fin=0
-        # - ping with 'Ping!' message
-        # - ping with 'Pong!' message
-        # - text message 'World!' with fin=1
-        # Replenish (6 + 1) + (2 + 1) bytes so that the first ping will be
-        # fragmented on the logical channel.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=10)
-        request.connection.put_bytes(flow_control)
-
-        time.sleep(1)
-
-        # Replenish remaining 3 + (5 + 1) + 6 bytes. The second ping won't
-        # be fragmented on the logical channel.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=15)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(['Hello World!'], messages)
-        control_messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode'])
-        self.assertEqual('Ping!', control_messages[0]['message'])
-        self.assertEqual(common.OPCODE_PING, control_messages[1]['opcode'])
-        self.assertEqual('Pong!', control_messages[1]['message'])
-
-    def test_send_drop_channel(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        # DropChannel for channel id 1 which doesn't have reason.
-        frame = create_binary_frame('\x00\x60\x01\x00', mask=True)
-        request.connection.put_bytes(frame)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_ACKNOWLEDGED,
-                         drop_channel.drop_code)
-        self.assertEqual(1, drop_channel.channel_id)
-
-    def test_two_flow_control(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Replenish 5 bytes.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=5)
-        request.connection.put_bytes(flow_control)
-
-        # Send 10 bytes. The server will try echo back 10 bytes.
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='HelloWorld'))
-
-        # Replenish 5 + 1 (per-message extra cost) bytes.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(['HelloWorld'], messages)
-        received_flow_controls = [
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2]
-        # Replenishment for 'HelloWorld' + 1
-        self.assertEqual(11, received_flow_controls[0].send_quota)
-        # Replenishment for 'Goodbye' + 1
-        self.assertEqual(8, received_flow_controls[1].send_quota)
-
-    def test_no_send_quota_on_server(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='HelloWorld'))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        # Just wait for 1 sec so that the server attempts to echo back
-        # 'HelloWorld'.
-        self.assertFalse(mux_handler.wait_until_done(timeout=1))
-
-        # No message should be sent on channel 2.
-        self.assertRaises(KeyError,
-                          request.connection.get_written_messages,
-                          2)
-
-    def test_no_send_quota_on_server_for_permessage_extra_cost(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-        # Replenish only len('World') bytes.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=5)
-        request.connection.put_bytes(flow_control)
-        # Server should not callback for this message.
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='World'))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        # Just wait for 1 sec so that the server attempts to echo back
-        # 'World'.
-        self.assertFalse(mux_handler.wait_until_done(timeout=1))
-
-        # Only one message should be sent on channel 2.
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(['Hello'], messages)
-
-    def test_quota_violation_by_client(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 0)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='HelloWorld'))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        control_blocks = request.connection.get_written_control_blocks()
-        self.assertEqual(5, len(control_blocks))
-        drop_channel = next(
-            b for b in control_blocks
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION,
-                         drop_channel.drop_code)
-
-    def test_consume_quota_empty_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        # Client has 1 byte quota.
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 1)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=2)
-        request.connection.put_bytes(flow_control)
-        # Send an empty message. Pywebsocket always replenishes 1 byte quota
-        # for empty message
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message=''))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        # This message violates quota on channel id 2.
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual(1, len(dispatcher.channel_events[2].messages))
-        self.assertEqual('', dispatcher.channel_events[2].messages[0])
-
-        received_flow_controls = [
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2]
-        self.assertEqual(1, len(received_flow_controls))
-        self.assertEqual(1, received_flow_controls[0].send_quota)
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(2, drop_channel.channel_id)
-        self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION,
-                         drop_channel.drop_code)
-
-    def test_consume_quota_fragmented_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        # Client has len('Hello') + len('Goodbye') + 2 bytes quota.
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 14)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='He', fin=False,
-                                  opcode=common.OPCODE_TEXT))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='llo', fin=True,
-                                  opcode=common.OPCODE_CONTINUATION))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(['Hello'], messages)
-
-    def test_fragmented_control_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/ping')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Replenish total 6 bytes in 3 FlowControls.
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=1)
-        request.connection.put_bytes(flow_control)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=2)
-        request.connection.put_bytes(flow_control)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=3)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        messages = request.connection.get_written_control_messages(2)
-        self.assertEqual(common.OPCODE_PING, messages[0]['opcode'])
-        self.assertEqual('Ping!', messages[0]['message'])
-
-    def test_channel_slot_violation_by_client(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(slots=1,
-                                      send_quota=mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Hello'))
-
-        # This request should be rejected.
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=3, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-        flow_control = _create_flow_control_frame(channel_id=3,
-                                                  replenished_quota=6)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=3, message='Hello'))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual([], dispatcher.channel_events[1].messages)
-        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
-        self.assertFalse(dispatcher.channel_events.has_key(3))
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(3, drop_channel.channel_id)
-        self.assertEqual(mux._DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION,
-                         drop_channel.drop_code)
-
-    def test_quota_overflow_by_client(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(slots=1,
-                                      send_quota=mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-        # Replenish 0x7FFFFFFFFFFFFFFF bytes twice.
-        flow_control = _create_flow_control_frame(
-            channel_id=2,
-            replenished_quota=0x7FFFFFFFFFFFFFFF)
-        request.connection.put_bytes(flow_control)
-        request.connection.put_bytes(flow_control)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(2, drop_channel.channel_id)
-        self.assertEqual(mux._DROP_CODE_SEND_QUOTA_OVERFLOW,
-                         drop_channel.drop_code)
-
-    def test_invalid_encapsulated_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        first_byte = (mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5)
-        block = (chr(first_byte) +
-                 mux._encode_channel_id(1) +
-                 mux._encode_number(0))
-        payload = mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + block
-        text_frame = create_binary_frame(payload, opcode=common.OPCODE_TEXT,
-                                         mask=True)
-        request.connection.put_bytes(text_frame)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_INVALID_ENCAPSULATING_MESSAGE,
-                         drop_channel.drop_code)
-        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
-                         request.connection.server_close_code)
-
-    def test_channel_id_truncated(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        # The last byte of the channel id is missing.
-        frame = create_binary_frame('\x80', mask=True)
-        request.connection.put_bytes(frame)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_CHANNEL_ID_TRUNCATED,
-                         drop_channel.drop_code)
-        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
-                         request.connection.server_close_code)
-
-    def test_inner_frame_truncated(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        # Just contain channel id 1.
-        frame = create_binary_frame('\x01', mask=True)
-        request.connection.put_bytes(frame)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED,
-                         drop_channel.drop_code)
-        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
-                         request.connection.server_close_code)
-
-    def test_unknown_mux_opcode(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        # Undefined opcode 5
-        frame = create_binary_frame('\x00\xa0', mask=True)
-        request.connection.put_bytes(frame)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_UNKNOWN_MUX_OPCODE,
-                         drop_channel.drop_code)
-        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
-                         request.connection.server_close_code)
-
-    def test_invalid_mux_control_block(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-
-        # DropChannel contains 1 byte reason
-        frame = create_binary_frame('\x00\x60\x00\x01\x00', mask=True)
-        request.connection.put_bytes(frame)
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channel = next(
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
-        self.assertEqual(mux._DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
-                         drop_channel.drop_code)
-        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
-                         request.connection.server_close_code)
-
-    def test_permessage_compress(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        # Enable permessage compress extension on logical channel 2.
-        extensions = '%s; method=deflate' % (
-            common.PERMESSAGE_COMPRESSION_EXTENSION)
-        encoded_handshake = _create_request_header(path='/echo',
-                                                   extensions=extensions)
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        flow_control = _create_flow_control_frame(channel_id=2,
-                                                  replenished_quota=20)
-        request.connection.put_bytes(flow_control)
-
-        # Send compressed 'Hello' twice.
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello1 = compress.compress('Hello')
-        compressed_hello1 += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello1 = compressed_hello1[:-4]
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message=compressed_hello1,
-                                  rsv1=True))
-        compressed_hello2 = compress.compress('Hello')
-        compressed_hello2 += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello2 = compressed_hello2[:-4]
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message=compressed_hello2,
-                                  rsv1=True))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=2, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual(['Hello', 'Hello'],
-                         dispatcher.channel_events[2].messages)
-        # Written 'Hello's should be compressed.
-        messages = request.connection.get_written_messages(2)
-        self.assertEqual(2, len(messages))
-        self.assertEqual(compressed_hello1, messages[0])
-        self.assertEqual(compressed_hello2, messages[1])
-
-
-    def test_permessage_compress_fragmented_message(self):
-        extensions = common.parse_extensions(
-            '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION)
-        request = _create_mock_request(
-            logical_channel_extensions=extensions)
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        # Send compressed 'HelloHelloHello' as fragmented message.
-        compress = zlib.compressobj(
-            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
-        compressed_hello = compress.compress('HelloHelloHello')
-        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
-        compressed_hello = compressed_hello[:-4]
-
-        m = len(compressed_hello) / 2
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1,
-                                  message=compressed_hello[:m],
-                                  fin=False, rsv1=True,
-                                  opcode=common.OPCODE_TEXT))
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1,
-                                  message=compressed_hello[m:],
-                                  fin=True, rsv1=False,
-                                  opcode=common.OPCODE_CONTINUATION))
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        self.assertEqual(['HelloHelloHello'],
-                         dispatcher.channel_events[1].messages)
-        messages = request.connection.get_written_messages(1)
-        self.assertEqual(1, len(messages))
-        self.assertEqual(compressed_hello, messages[0])
-
-    def test_receive_bad_fragmented_message(self):
-        request = _create_mock_request()
-        dispatcher = _MuxMockDispatcher()
-        mux_handler = mux._MuxHandler(request, dispatcher)
-        mux_handler.start()
-        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
-                                      mux._INITIAL_QUOTA_FOR_CLIENT)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=2, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Send a frame with fin=False, and then send a frame with
-        # opcode=TEXT (not CONTINUATION). Logical channel 2 should be dropped.
-        frame1 = _create_logical_frame(channel_id=2,
-                                       message='Hello ',
-                                       fin=False,
-                                       opcode=common.OPCODE_TEXT)
-        request.connection.put_bytes(frame1)
-        frame2 = _create_logical_frame(channel_id=2,
-                                       message='World!',
-                                       fin=True,
-                                       opcode=common.OPCODE_TEXT)
-        request.connection.put_bytes(frame2)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=3, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Send a frame with opcode=CONTINUATION without a preceding frame
-        # the fin of which is not set. Logical channel 3 should be dropped.
-        frame3 = _create_logical_frame(channel_id=3,
-                                       message='Hello',
-                                       fin=True,
-                                       opcode=common.OPCODE_CONTINUATION)
-        request.connection.put_bytes(frame3)
-
-        encoded_handshake = _create_request_header(path='/echo')
-        add_channel_request = _create_add_channel_request_frame(
-            channel_id=4, encoding=0,
-            encoded_handshake=encoded_handshake)
-        request.connection.put_bytes(add_channel_request)
-
-        # Send a frame with opcode=PING and fin=False, and then send a frame
-        # with opcode=TEXT (not CONTINUATION). Logical channel 4 should be
-        # dropped.
-        frame4 = _create_logical_frame(channel_id=4,
-                                       message='Ping',
-                                       fin=False,
-                                       opcode=common.OPCODE_PING)
-        request.connection.put_bytes(frame4)
-        frame5 = _create_logical_frame(channel_id=4,
-                                       message='Hello',
-                                       fin=True,
-                                       opcode=common.OPCODE_TEXT)
-        request.connection.put_bytes(frame5)
-
-        request.connection.put_bytes(
-            _create_logical_frame(channel_id=1, message='Goodbye'))
-
-        self.assertTrue(mux_handler.wait_until_done(timeout=2))
-
-        drop_channels = [
-            b for b in request.connection.get_written_control_blocks()
-            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL]
-        self.assertEqual(3, len(drop_channels))
-        for d in drop_channels:
-            self.assertEqual(mux._DROP_CODE_BAD_FRAGMENTATION,
-                             d.drop_code)
-
-
-if __name__ == '__main__':
-    unittest.main()
-
-
-# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/__init__.py b/tools/pywebsocket/test/__init__.py
similarity index 100%
rename from tools/pywebsocket/src/test/__init__.py
rename to tools/pywebsocket/test/__init__.py
diff --git a/tools/pywebsocket/src/test/cert/cacert.pem b/tools/pywebsocket/test/cert/cacert.pem
similarity index 100%
rename from tools/pywebsocket/src/test/cert/cacert.pem
rename to tools/pywebsocket/test/cert/cacert.pem
diff --git a/tools/pywebsocket/src/test/cert/cert.pem b/tools/pywebsocket/test/cert/cert.pem
similarity index 100%
rename from tools/pywebsocket/src/test/cert/cert.pem
rename to tools/pywebsocket/test/cert/cert.pem
diff --git a/tools/pywebsocket/src/test/cert/client_cert.p12 b/tools/pywebsocket/test/cert/client_cert.p12
similarity index 100%
rename from tools/pywebsocket/src/test/cert/client_cert.p12
rename to tools/pywebsocket/test/cert/client_cert.p12
Binary files differ
diff --git a/tools/pywebsocket/src/test/cert/key.pem b/tools/pywebsocket/test/cert/key.pem
similarity index 100%
rename from tools/pywebsocket/src/test/cert/key.pem
rename to tools/pywebsocket/test/cert/key.pem
diff --git a/tools/pywebsocket/src/test/client_for_testing.py b/tools/pywebsocket/test/client_for_testing.py
similarity index 100%
rename from tools/pywebsocket/src/test/client_for_testing.py
rename to tools/pywebsocket/test/client_for_testing.py
diff --git a/tools/pywebsocket/src/test/endtoend_with_external_server.py b/tools/pywebsocket/test/endtoend_with_external_server.py
similarity index 100%
rename from tools/pywebsocket/src/test/endtoend_with_external_server.py
rename to tools/pywebsocket/test/endtoend_with_external_server.py
diff --git a/tools/pywebsocket/src/test/mock.py b/tools/pywebsocket/test/mock.py
similarity index 100%
rename from tools/pywebsocket/src/test/mock.py
rename to tools/pywebsocket/test/mock.py
diff --git a/tools/pywebsocket/src/test/mux_client_for_testing.py b/tools/pywebsocket/test/mux_client_for_testing.py
similarity index 100%
rename from tools/pywebsocket/src/test/mux_client_for_testing.py
rename to tools/pywebsocket/test/mux_client_for_testing.py
diff --git a/tools/pywebsocket/src/test/run_all.py b/tools/pywebsocket/test/run_all.py
similarity index 100%
rename from tools/pywebsocket/src/test/run_all.py
rename to tools/pywebsocket/test/run_all.py
diff --git a/tools/pywebsocket/src/test/set_sys_path.py b/tools/pywebsocket/test/set_sys_path.py
similarity index 100%
rename from tools/pywebsocket/src/test/set_sys_path.py
rename to tools/pywebsocket/test/set_sys_path.py
diff --git a/tools/pywebsocket/src/test/test_dispatch.py b/tools/pywebsocket/test/test_dispatch.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_dispatch.py
rename to tools/pywebsocket/test/test_dispatch.py
diff --git a/tools/pywebsocket/src/test/test_endtoend.py b/tools/pywebsocket/test/test_endtoend.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_endtoend.py
rename to tools/pywebsocket/test/test_endtoend.py
diff --git a/tools/pywebsocket/test/test_extensions.py b/tools/pywebsocket/test/test_extensions.py
new file mode 100755
index 0000000..94a4384
--- /dev/null
+++ b/tools/pywebsocket/test/test_extensions.py
@@ -0,0 +1,279 @@
+#!/usr/bin/env python
+#
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for extensions module."""
+
+
+import unittest
+import zlib
+
+import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
+
+from mod_pywebsocket import common
+from mod_pywebsocket import extensions
+
+
+class ExtensionsTest(unittest.TestCase):
+    """A unittest for non-class methods in extensions.py"""
+
+    def test_parse_window_bits(self):
+        self.assertRaises(ValueError, extensions._parse_window_bits, None)
+        self.assertRaises(ValueError, extensions._parse_window_bits, 'foobar')
+        self.assertRaises(ValueError, extensions._parse_window_bits, ' 8 ')
+        self.assertRaises(ValueError, extensions._parse_window_bits, 'a8a')
+        self.assertRaises(ValueError, extensions._parse_window_bits, '00000')
+        self.assertRaises(ValueError, extensions._parse_window_bits, '00008')
+        self.assertRaises(ValueError, extensions._parse_window_bits, '0x8')
+
+        self.assertRaises(ValueError, extensions._parse_window_bits, '9.5')
+        self.assertRaises(ValueError, extensions._parse_window_bits, '8.0')
+
+        self.assertTrue(extensions._parse_window_bits, '8')
+        self.assertTrue(extensions._parse_window_bits, '15')
+
+        self.assertRaises(ValueError, extensions._parse_window_bits, '-8')
+        self.assertRaises(ValueError, extensions._parse_window_bits, '0')
+        self.assertRaises(ValueError, extensions._parse_window_bits, '7')
+
+        self.assertRaises(ValueError, extensions._parse_window_bits, '16')
+        self.assertRaises(
+                ValueError, extensions._parse_window_bits, '10000000')
+
+
+class DeflateFrameExtensionProcessorParsingTest(unittest.TestCase):
+    """A unittest for checking that DeflateFrameExtensionProcessor parses given
+    extension parameter correctly.
+    """
+
+    def test_registry(self):
+        processor = extensions.get_extension_processor(
+                common.ExtensionParameter('deflate-frame'))
+        self.assertIsInstance(processor,
+                              extensions.DeflateFrameExtensionProcessor)
+
+        processor = extensions.get_extension_processor(
+                common.ExtensionParameter('x-webkit-deflate-frame'))
+        self.assertIsInstance(processor,
+                              extensions.DeflateFrameExtensionProcessor)
+
+    def test_minimal_offer(self):
+        processor = extensions.DeflateFrameExtensionProcessor(
+            common.ExtensionParameter('perframe-deflate'))
+
+        response = processor.get_extension_response()
+        self.assertEqual('perframe-deflate', response.name())
+        self.assertEqual(0, len(response.get_parameters()))
+
+        self.assertEqual(zlib.MAX_WBITS,
+                         processor._rfc1979_deflater._window_bits)
+        self.assertFalse(processor._rfc1979_deflater._no_context_takeover)
+
+    def test_offer_with_max_window_bits(self):
+        parameter = common.ExtensionParameter('perframe-deflate')
+        parameter.add_parameter('max_window_bits', '10')
+        processor = extensions.DeflateFrameExtensionProcessor(parameter)
+
+        response = processor.get_extension_response()
+        self.assertEqual('perframe-deflate', response.name())
+        self.assertEqual(0, len(response.get_parameters()))
+
+        self.assertEqual(10, processor._rfc1979_deflater._window_bits)
+
+    def test_offer_with_out_of_range_max_window_bits(self):
+        parameter = common.ExtensionParameter('perframe-deflate')
+        parameter.add_parameter('max_window_bits', '0')
+        processor = extensions.DeflateFrameExtensionProcessor(parameter)
+
+        self.assertIsNone(processor.get_extension_response())
+
+    def test_offer_with_max_window_bits_without_value(self):
+        parameter = common.ExtensionParameter('perframe-deflate')
+        parameter.add_parameter('max_window_bits', None)
+        processor = extensions.DeflateFrameExtensionProcessor(parameter)
+
+        self.assertIsNone(processor.get_extension_response())
+
+    def test_offer_with_no_context_takeover(self):
+        parameter = common.ExtensionParameter('perframe-deflate')
+        parameter.add_parameter('no_context_takeover', None)
+        processor = extensions.DeflateFrameExtensionProcessor(parameter)
+
+        response = processor.get_extension_response()
+        self.assertEqual('perframe-deflate', response.name())
+        self.assertEqual(0, len(response.get_parameters()))
+
+        self.assertTrue(processor._rfc1979_deflater._no_context_takeover)
+
+    def test_offer_with_no_context_takeover_with_value(self):
+        parameter = common.ExtensionParameter('perframe-deflate')
+        parameter.add_parameter('no_context_takeover', 'foobar')
+        processor = extensions.DeflateFrameExtensionProcessor(parameter)
+
+        self.assertIsNone(processor.get_extension_response())
+
+    def test_offer_with_unknown_parameter(self):
+        parameter = common.ExtensionParameter('perframe-deflate')
+        parameter.add_parameter('foo', 'bar')
+        processor = extensions.DeflateFrameExtensionProcessor(parameter)
+
+        response = processor.get_extension_response()
+        self.assertEqual('perframe-deflate', response.name())
+        self.assertEqual(0, len(response.get_parameters()))
+
+
+class PerMessageDeflateExtensionProcessorParsingTest(unittest.TestCase):
+    """A unittest for checking that PerMessageDeflateExtensionProcessor parses
+    given extension parameter correctly.
+    """
+
+    def test_registry(self):
+        processor = extensions.get_extension_processor(
+                common.ExtensionParameter('permessage-deflate'))
+        self.assertIsInstance(processor,
+                              extensions.PerMessageDeflateExtensionProcessor)
+
+    def test_minimal_offer(self):
+        processor = extensions.PerMessageDeflateExtensionProcessor(
+            common.ExtensionParameter('permessage-deflate'))
+
+        response = processor.get_extension_response()
+        self.assertEqual('permessage-deflate', response.name())
+        self.assertEqual(0, len(response.get_parameters()))
+
+        self.assertEqual(zlib.MAX_WBITS,
+                         processor._rfc1979_deflater._window_bits)
+        self.assertFalse(processor._rfc1979_deflater._no_context_takeover)
+
+    def test_offer_with_max_window_bits(self):
+        parameter = common.ExtensionParameter('permessage-deflate')
+        parameter.add_parameter('server_max_window_bits', '10')
+        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
+
+        response = processor.get_extension_response()
+        self.assertEqual('permessage-deflate', response.name())
+        self.assertEqual([('server_max_window_bits', '10')],
+                         response.get_parameters())
+
+        self.assertEqual(10, processor._rfc1979_deflater._window_bits)
+
+    def test_offer_with_out_of_range_max_window_bits(self):
+        parameter = common.ExtensionParameter('permessage-deflate')
+        parameter.add_parameter('server_max_window_bits', '0')
+        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
+
+        self.assertIsNone(processor.get_extension_response())
+
+    def test_offer_with_max_window_bits_without_value(self):
+        parameter = common.ExtensionParameter('permessage-deflate')
+        parameter.add_parameter('server_max_window_bits', None)
+        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
+
+        self.assertIsNone(processor.get_extension_response())
+
+    def test_offer_with_no_context_takeover(self):
+        parameter = common.ExtensionParameter('permessage-deflate')
+        parameter.add_parameter('server_no_context_takeover', None)
+        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
+
+        response = processor.get_extension_response()
+        self.assertEqual('permessage-deflate', response.name())
+        self.assertEqual([('server_no_context_takeover', None)],
+                         response.get_parameters())
+
+        self.assertTrue(processor._rfc1979_deflater._no_context_takeover)
+
+    def test_offer_with_no_context_takeover_with_value(self):
+        parameter = common.ExtensionParameter('permessage-deflate')
+        parameter.add_parameter('server_no_context_takeover', 'foobar')
+        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
+
+        self.assertIsNone(processor.get_extension_response())
+
+    def test_offer_with_unknown_parameter(self):
+        parameter = common.ExtensionParameter('permessage-deflate')
+        parameter.add_parameter('foo', 'bar')
+        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
+
+        self.assertIsNone(processor.get_extension_response())
+
+
+class PerMessageDeflateExtensionProcessorBuildingTest(unittest.TestCase):
+    """A unittest for checking that PerMessageDeflateExtensionProcessor builds
+    a response based on specified options correctly.
+    """
+
+    def test_response_with_max_window_bits(self):
+        parameter = common.ExtensionParameter('permessage-deflate')
+        parameter.add_parameter('client_max_window_bits', None)
+        processor = extensions.PerMessageDeflateExtensionProcessor(parameter)
+        processor.set_client_max_window_bits(10)
+
+        response = processor.get_extension_response()
+        self.assertEqual('permessage-deflate', response.name())
+        self.assertEqual([('client_max_window_bits', '10')],
+                         response.get_parameters())
+
+    def test_response_with_max_window_bits_without_client_permission(self):
+        processor = extensions.PerMessageDeflateExtensionProcessor(
+            common.ExtensionParameter('permessage-deflate'))
+        processor.set_client_max_window_bits(10)
+
+        response = processor.get_extension_response()
+        self.assertIsNone(response)
+
+    def test_response_with_true_for_no_context_takeover(self):
+        processor = extensions.PerMessageDeflateExtensionProcessor(
+            common.ExtensionParameter('permessage-deflate'))
+
+        processor.set_client_no_context_takeover(True)
+
+        response = processor.get_extension_response()
+        self.assertEqual('permessage-deflate', response.name())
+        self.assertEqual([('client_no_context_takeover', None)],
+                         response.get_parameters())
+
+    def test_response_with_false_for_no_context_takeover(self):
+        processor = extensions.PerMessageDeflateExtensionProcessor(
+            common.ExtensionParameter('permessage-deflate'))
+
+        processor.set_client_no_context_takeover(False)
+
+        response = processor.get_extension_response()
+        self.assertEqual('permessage-deflate', response.name())
+        self.assertEqual(0, len(response.get_parameters()))
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/test_handshake.py b/tools/pywebsocket/test/test_handshake.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_handshake.py
rename to tools/pywebsocket/test/test_handshake.py
diff --git a/tools/pywebsocket/test/test_handshake_hybi.py b/tools/pywebsocket/test/test_handshake_hybi.py
new file mode 100755
index 0000000..d106677
--- /dev/null
+++ b/tools/pywebsocket/test/test_handshake_hybi.py
@@ -0,0 +1,521 @@
+#!/usr/bin/env python
+#
+# Copyright 2011, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for handshake module."""
+
+
+import unittest
+
+import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
+from mod_pywebsocket import common
+from mod_pywebsocket.handshake._base import AbortedByUserException
+from mod_pywebsocket.handshake._base import HandshakeException
+from mod_pywebsocket.handshake._base import VersionException
+from mod_pywebsocket.handshake.hybi import Handshaker
+
+import mock
+
+
+class RequestDefinition(object):
+    """A class for holding data for constructing opening handshake strings for
+    testing the opening handshake processor.
+    """
+
+    def __init__(self, method, uri, headers):
+        self.method = method
+        self.uri = uri
+        self.headers = headers
+
+
+def _create_good_request_def():
+    return RequestDefinition(
+        'GET', '/demo',
+        {'Host': 'server.example.com',
+         'Upgrade': 'websocket',
+         'Connection': 'Upgrade',
+         'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
+         'Sec-WebSocket-Version': '13',
+         'Origin': 'http://example.com'})
+
+
+def _create_request(request_def):
+    conn = mock.MockConn('')
+    return mock.MockRequest(
+        method=request_def.method,
+        uri=request_def.uri,
+        headers_in=request_def.headers,
+        connection=conn)
+
+
+def _create_handshaker(request):
+    handshaker = Handshaker(request, mock.MockDispatcher())
+    return handshaker
+
+
+class SubprotocolChoosingDispatcher(object):
+    """A dispatcher for testing. This dispatcher sets the i-th subprotocol
+    of requested ones to ws_protocol where i is given on construction as index
+    argument. If index is negative, default_value will be set to ws_protocol.
+    """
+
+    def __init__(self, index, default_value=None):
+        self.index = index
+        self.default_value = default_value
+
+    def do_extra_handshake(self, conn_context):
+        if self.index >= 0:
+            conn_context.ws_protocol = conn_context.ws_requested_protocols[
+                self.index]
+        else:
+            conn_context.ws_protocol = self.default_value
+
+    def transfer_data(self, conn_context):
+        pass
+
+
+class HandshakeAbortedException(Exception):
+    pass
+
+
+class AbortingDispatcher(object):
+    """A dispatcher for testing. This dispatcher raises an exception in
+    do_extra_handshake to reject the request.
+    """
+
+    def do_extra_handshake(self, conn_context):
+        raise HandshakeAbortedException('An exception to reject the request')
+
+    def transfer_data(self, conn_context):
+        pass
+
+
+class AbortedByUserDispatcher(object):
+    """A dispatcher for testing. This dispatcher raises an
+    AbortedByUserException in do_extra_handshake to reject the request.
+    """
+
+    def do_extra_handshake(self, conn_context):
+        raise AbortedByUserException('An AbortedByUserException to reject the '
+                                     'request')
+
+    def transfer_data(self, conn_context):
+        pass
+
+
+_EXPECTED_RESPONSE = (
+    'HTTP/1.1 101 Switching Protocols\r\n'
+    'Upgrade: websocket\r\n'
+    'Connection: Upgrade\r\n'
+    'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n')
+
+
+class HandshakerTest(unittest.TestCase):
+    """A unittest for draft-ietf-hybi-thewebsocketprotocol-06 and later
+    handshake processor.
+    """
+
+    def test_do_handshake(self):
+        request = _create_request(_create_good_request_def())
+        dispatcher = mock.MockDispatcher()
+        handshaker = Handshaker(request, dispatcher)
+        handshaker.do_handshake()
+
+        self.assertTrue(dispatcher.do_extra_handshake_called)
+
+        self.assertEqual(
+            _EXPECTED_RESPONSE, request.connection.written_data())
+        self.assertEqual('/demo', request.ws_resource)
+        self.assertEqual('http://example.com', request.ws_origin)
+        self.assertEqual(None, request.ws_protocol)
+        self.assertEqual(None, request.ws_extensions)
+        self.assertEqual(common.VERSION_HYBI_LATEST, request.ws_version)
+
+    def test_do_handshake_with_extra_headers(self):
+        request_def = _create_good_request_def()
+        # Add headers not related to WebSocket opening handshake.
+        request_def.headers['FooKey'] = 'BarValue'
+        request_def.headers['EmptyKey'] = ''
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        self.assertEqual(
+            _EXPECTED_RESPONSE, request.connection.written_data())
+
+    def test_do_handshake_with_capitalized_value(self):
+        request_def = _create_good_request_def()
+        request_def.headers['upgrade'] = 'WEBSOCKET'
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        self.assertEqual(
+            _EXPECTED_RESPONSE, request.connection.written_data())
+
+        request_def = _create_good_request_def()
+        request_def.headers['Connection'] = 'UPGRADE'
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        self.assertEqual(
+            _EXPECTED_RESPONSE, request.connection.written_data())
+
+    def test_do_handshake_with_multiple_connection_values(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Connection'] = 'Upgrade, keep-alive, , '
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        self.assertEqual(
+            _EXPECTED_RESPONSE, request.connection.written_data())
+
+    def test_aborting_handshake(self):
+        handshaker = Handshaker(
+            _create_request(_create_good_request_def()),
+            AbortingDispatcher())
+        # do_extra_handshake raises an exception. Check that it's not caught by
+        # do_handshake.
+        self.assertRaises(HandshakeAbortedException, handshaker.do_handshake)
+
+    def test_do_handshake_with_protocol(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat'
+
+        request = _create_request(request_def)
+        handshaker = Handshaker(request, SubprotocolChoosingDispatcher(0))
+        handshaker.do_handshake()
+
+        EXPECTED_RESPONSE = (
+            'HTTP/1.1 101 Switching Protocols\r\n'
+            'Upgrade: websocket\r\n'
+            'Connection: Upgrade\r\n'
+            'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n'
+            'Sec-WebSocket-Protocol: chat\r\n\r\n')
+
+        self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data())
+        self.assertEqual('chat', request.ws_protocol)
+
+    def test_do_handshake_protocol_not_in_request_but_in_response(self):
+        request_def = _create_good_request_def()
+        request = _create_request(request_def)
+        handshaker = Handshaker(
+            request, SubprotocolChoosingDispatcher(-1, 'foobar'))
+        # No request has been made but ws_protocol is set. HandshakeException
+        # must be raised.
+        self.assertRaises(HandshakeException, handshaker.do_handshake)
+
+    def test_do_handshake_with_protocol_no_protocol_selection(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat'
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        # ws_protocol is not set. HandshakeException must be raised.
+        self.assertRaises(HandshakeException, handshaker.do_handshake)
+
+    def test_do_handshake_with_extensions(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Extensions'] = (
+            'permessage-deflate; server_no_context_takeover')
+
+        EXPECTED_RESPONSE = (
+            'HTTP/1.1 101 Switching Protocols\r\n'
+            'Upgrade: websocket\r\n'
+            'Connection: Upgrade\r\n'
+            'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n'
+            'Sec-WebSocket-Extensions: '
+                'permessage-deflate; server_no_context_takeover\r\n'
+            '\r\n')
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data())
+        self.assertEqual(1, len(request.ws_extensions))
+        extension = request.ws_extensions[0]
+        self.assertEqual(common.PERMESSAGE_DEFLATE_EXTENSION,
+                         extension.name())
+        self.assertEqual(['server_no_context_takeover'],
+                         extension.get_parameter_names())
+        self.assertEqual(None,
+                         extension.get_parameter_value(
+                             'server_no_context_takeover'))
+        self.assertEqual(1, len(request.ws_extension_processors))
+        self.assertEqual('deflate',
+                         request.ws_extension_processors[0].name())
+
+    def test_do_handshake_with_quoted_extensions(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Extensions'] = (
+            'permessage-deflate, , '
+            'unknown; e   =    "mc^2"; ma="\r\n      \\\rf  "; pv=nrt')
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        self.assertEqual(2, len(request.ws_requested_extensions))
+        first_extension = request.ws_requested_extensions[0]
+        self.assertEqual('permessage-deflate', first_extension.name())
+        second_extension = request.ws_requested_extensions[1]
+        self.assertEqual('unknown', second_extension.name())
+        self.assertEqual(
+            ['e', 'ma', 'pv'], second_extension.get_parameter_names())
+        self.assertEqual('mc^2', second_extension.get_parameter_value('e'))
+        self.assertEqual(' \rf ', second_extension.get_parameter_value('ma'))
+        self.assertEqual('nrt', second_extension.get_parameter_value('pv'))
+
+    def test_do_handshake_with_optional_headers(self):
+        request_def = _create_good_request_def()
+        request_def.headers['EmptyValue'] = ''
+        request_def.headers['AKey'] = 'AValue'
+
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        self.assertEqual(
+            'AValue', request.headers_in['AKey'])
+        self.assertEqual(
+            '', request.headers_in['EmptyValue'])
+
+    def test_abort_extra_handshake(self):
+        handshaker = Handshaker(
+            _create_request(_create_good_request_def()),
+            AbortedByUserDispatcher())
+        # do_extra_handshake raises an AbortedByUserException. Check that it's
+        # not caught by do_handshake.
+        self.assertRaises(AbortedByUserException, handshaker.do_handshake)
+
+    def test_do_handshake_with_mux_and_deflate_frame(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % (
+                common.MUX_EXTENSION,
+                common.DEFLATE_FRAME_EXTENSION))
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        # mux should be rejected.
+        self.assertEqual(1, len(request.ws_extensions))
+        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
+                         request.ws_extensions[0].name())
+        self.assertEqual(2, len(request.ws_extension_processors))
+        self.assertEqual(common.MUX_EXTENSION,
+                         request.ws_extension_processors[0].name())
+        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
+                         request.ws_extension_processors[1].name())
+        self.assertFalse(hasattr(request, 'mux_processor'))
+
+    def test_do_handshake_with_deflate_frame_and_mux(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % (
+                common.DEFLATE_FRAME_EXTENSION,
+                common.MUX_EXTENSION))
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        # mux should be rejected.
+        self.assertEqual(1, len(request.ws_extensions))
+        first_extension = request.ws_extensions[0]
+        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
+                         first_extension.name())
+        self.assertEqual(2, len(request.ws_extension_processors))
+        self.assertEqual(common.DEFLATE_FRAME_EXTENSION,
+                         request.ws_extension_processors[0].name())
+        self.assertEqual(common.MUX_EXTENSION,
+                         request.ws_extension_processors[1].name())
+        self.assertFalse(hasattr(request, 'mux'))
+
+    def test_do_handshake_with_permessage_deflate_and_mux(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Extensions'] = (
+            '%s, %s' % (
+                common.PERMESSAGE_DEFLATE_EXTENSION,
+                common.MUX_EXTENSION))
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+
+        self.assertEqual(1, len(request.ws_extensions))
+        self.assertEqual(common.MUX_EXTENSION,
+                         request.ws_extensions[0].name())
+        self.assertEqual(2, len(request.ws_extension_processors))
+        self.assertEqual('deflate',
+                         request.ws_extension_processors[0].name())
+        self.assertEqual(common.MUX_EXTENSION,
+                         request.ws_extension_processors[1].name())
+        self.assertTrue(hasattr(request, 'mux_processor'))
+        self.assertTrue(request.mux_processor.is_active())
+        mux_extensions = request.mux_processor.extensions()
+        self.assertEqual(1, len(mux_extensions))
+        self.assertEqual(common.PERMESSAGE_DEFLATE_EXTENSION,
+                         mux_extensions[0].name())
+
+    def test_do_handshake_with_mux_and_permessage_deflate(self):
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Extensions'] = (
+            '%s, %s' % (
+                common.MUX_EXTENSION,
+                common.PERMESSAGE_DEFLATE_EXTENSION))
+        request = _create_request(request_def)
+        handshaker = _create_handshaker(request)
+        handshaker.do_handshake()
+        # mux should be rejected.
+        self.assertEqual(1, len(request.ws_extensions))
+        first_extension = request.ws_extensions[0]
+        self.assertEqual(common.PERMESSAGE_DEFLATE_EXTENSION,
+                         first_extension.name())
+        self.assertEqual(2, len(request.ws_extension_processors))
+        self.assertEqual(common.MUX_EXTENSION,
+                         request.ws_extension_processors[0].name())
+        self.assertEqual('deflate',
+                         request.ws_extension_processors[1].name())
+        self.assertFalse(hasattr(request, 'mux_processor'))
+
+    def test_bad_requests(self):
+        bad_cases = [
+            ('HTTP request',
+             RequestDefinition(
+                 'GET', '/demo',
+                 {'Host': 'www.google.com',
+                  'User-Agent':
+                      'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;'
+                      ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3'
+                      ' GTB6 GTBA',
+                  'Accept':
+                      'text/html,application/xhtml+xml,application/xml;q=0.9,'
+                      '*/*;q=0.8',
+                  'Accept-Language': 'en-us,en;q=0.5',
+                  'Accept-Encoding': 'gzip,deflate',
+                  'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+                  'Keep-Alive': '300',
+                  'Connection': 'keep-alive'}), None, True)]
+
+        request_def = _create_good_request_def()
+        request_def.method = 'POST'
+        bad_cases.append(('Wrong method', request_def, None, True))
+
+        request_def = _create_good_request_def()
+        del request_def.headers['Host']
+        bad_cases.append(('Missing Host', request_def, None, True))
+
+        request_def = _create_good_request_def()
+        del request_def.headers['Upgrade']
+        bad_cases.append(('Missing Upgrade', request_def, None, True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Upgrade'] = 'nonwebsocket'
+        bad_cases.append(('Wrong Upgrade', request_def, None, True))
+
+        request_def = _create_good_request_def()
+        del request_def.headers['Connection']
+        bad_cases.append(('Missing Connection', request_def, None, True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Connection'] = 'Downgrade'
+        bad_cases.append(('Wrong Connection', request_def, None, True))
+
+        request_def = _create_good_request_def()
+        del request_def.headers['Sec-WebSocket-Key']
+        bad_cases.append(('Missing Sec-WebSocket-Key', request_def, 400, True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Key'] = (
+            'dGhlIHNhbXBsZSBub25jZQ==garbage')
+        bad_cases.append(('Wrong Sec-WebSocket-Key (with garbage on the tail)',
+                          request_def, 400, True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Key'] = 'YQ=='  # BASE64 of 'a'
+        bad_cases.append(
+            ('Wrong Sec-WebSocket-Key (decoded value is not 16 octets long)',
+             request_def, 400, True))
+
+        request_def = _create_good_request_def()
+        # The last character right before == must be any of A, Q, w and g.
+        request_def.headers['Sec-WebSocket-Key'] = (
+            'AQIDBAUGBwgJCgsMDQ4PEC==')
+        bad_cases.append(
+            ('Wrong Sec-WebSocket-Key (padding bits are not zero)',
+             request_def, 400, True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Key'] = (
+            'dGhlIHNhbXBsZSBub25jZQ==,dGhlIHNhbXBsZSBub25jZQ==')
+        bad_cases.append(
+            ('Wrong Sec-WebSocket-Key (multiple values)',
+             request_def, 400, True))
+
+        request_def = _create_good_request_def()
+        del request_def.headers['Sec-WebSocket-Version']
+        bad_cases.append(('Missing Sec-WebSocket-Version', request_def, None,
+                          True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Version'] = '3'
+        bad_cases.append(('Wrong Sec-WebSocket-Version', request_def, None,
+                          False))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Version'] = '13, 13'
+        bad_cases.append(('Wrong Sec-WebSocket-Version (multiple values)',
+                          request_def, 400, True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Protocol'] = 'illegal\x09protocol'
+        bad_cases.append(('Illegal Sec-WebSocket-Protocol',
+                          request_def, 400, True))
+
+        request_def = _create_good_request_def()
+        request_def.headers['Sec-WebSocket-Protocol'] = ''
+        bad_cases.append(('Empty Sec-WebSocket-Protocol',
+                          request_def, 400, True))
+
+        for (case_name, request_def, expected_status,
+             expect_handshake_exception) in bad_cases:
+            request = _create_request(request_def)
+            handshaker = Handshaker(request, mock.MockDispatcher())
+            try:
+                handshaker.do_handshake()
+                self.fail('No exception thrown for \'%s\' case' % case_name)
+            except HandshakeException, e:
+                self.assertTrue(expect_handshake_exception)
+                self.assertEqual(expected_status, e.status)
+            except VersionException, e:
+                self.assertFalse(expect_handshake_exception)
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/test_handshake_hybi00.py b/tools/pywebsocket/test/test_handshake_hybi00.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_handshake_hybi00.py
rename to tools/pywebsocket/test/test_handshake_hybi00.py
diff --git a/tools/pywebsocket/src/test/test_http_header_util.py b/tools/pywebsocket/test/test_http_header_util.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_http_header_util.py
rename to tools/pywebsocket/test/test_http_header_util.py
diff --git a/tools/pywebsocket/src/test/test_memorizingfile.py b/tools/pywebsocket/test/test_memorizingfile.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_memorizingfile.py
rename to tools/pywebsocket/test/test_memorizingfile.py
diff --git a/tools/pywebsocket/src/test/test_mock.py b/tools/pywebsocket/test/test_mock.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_mock.py
rename to tools/pywebsocket/test/test_mock.py
diff --git a/tools/pywebsocket/test/test_msgutil.py b/tools/pywebsocket/test/test_msgutil.py
new file mode 100755
index 0000000..9dbbb5d
--- /dev/null
+++ b/tools/pywebsocket/test/test_msgutil.py
@@ -0,0 +1,1346 @@
+#!/usr/bin/env python
+#
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for msgutil module."""
+
+
+import array
+import Queue
+import random
+import struct
+import unittest
+import zlib
+
+import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
+
+from mod_pywebsocket import common
+from mod_pywebsocket.extensions import DeflateFrameExtensionProcessor
+from mod_pywebsocket.extensions import PerMessageDeflateExtensionProcessor
+from mod_pywebsocket import msgutil
+from mod_pywebsocket.stream import InvalidUTF8Exception
+from mod_pywebsocket.stream import Stream
+from mod_pywebsocket.stream import StreamHixie75
+from mod_pywebsocket.stream import StreamOptions
+from mod_pywebsocket import util
+from test import mock
+
+
+# We use one fixed nonce for testing instead of cryptographically secure PRNG.
+_MASKING_NONCE = 'ABCD'
+
+
+def _mask_hybi(frame):
+    frame_key = map(ord, _MASKING_NONCE)
+    frame_key_len = len(frame_key)
+    result = array.array('B')
+    result.fromstring(frame)
+    count = 0
+    for i in xrange(len(result)):
+        result[i] ^= frame_key[count]
+        count = (count + 1) % frame_key_len
+    return _MASKING_NONCE + result.tostring()
+
+
+def _install_extension_processor(processor, request, stream_options):
+    response = processor.get_extension_response()
+    if response is not None:
+        processor.setup_stream_options(stream_options)
+        request.ws_extension_processors.append(processor)
+
+
+def _create_request_from_rawdata(
+        read_data,
+        deflate_frame_request=None,
+        permessage_deflate_request=None):
+    req = mock.MockRequest(connection=mock.MockConn(''.join(read_data)))
+    req.ws_version = common.VERSION_HYBI_LATEST
+    req.ws_extension_processors = []
+
+    processor = None
+    if deflate_frame_request is not None:
+        processor = DeflateFrameExtensionProcessor(deflate_frame_request)
+    elif permessage_deflate_request is not None:
+        processor = PerMessageDeflateExtensionProcessor(
+                permessage_deflate_request)
+
+    stream_options = StreamOptions()
+    if processor is not None:
+        _install_extension_processor(processor, req, stream_options)
+    req.ws_stream = Stream(req, stream_options)
+
+    return req
+
+
+def _create_request(*frames):
+    """Creates MockRequest using data given as frames.
+
+    frames will be returned on calling request.connection.read() where request
+    is MockRequest returned by this function.
+    """
+
+    read_data = []
+    for (header, body) in frames:
+        read_data.append(header + _mask_hybi(body))
+
+    return _create_request_from_rawdata(read_data)
+
+
+def _create_blocking_request():
+    """Creates MockRequest.
+
+    Data written to a MockRequest can be read out by calling
+    request.connection.written_data().
+    """
+
+    req = mock.MockRequest(connection=mock.MockBlockingConn())
+    req.ws_version = common.VERSION_HYBI_LATEST
+    stream_options = StreamOptions()
+    req.ws_stream = Stream(req, stream_options)
+    return req
+
+
+def _create_request_hixie75(read_data=''):
+    req = mock.MockRequest(connection=mock.MockConn(read_data))
+    req.ws_stream = StreamHixie75(req)
+    return req
+
+
+def _create_blocking_request_hixie75():
+    req = mock.MockRequest(connection=mock.MockBlockingConn())
+    req.ws_stream = StreamHixie75(req)
+    return req
+
+
+class BasicMessageTest(unittest.TestCase):
+    """Basic tests for Stream."""
+
+    def test_send_message(self):
+        request = _create_request()
+        msgutil.send_message(request, 'Hello')
+        self.assertEqual('\x81\x05Hello', request.connection.written_data())
+
+        payload = 'a' * 125
+        request = _create_request()
+        msgutil.send_message(request, payload)
+        self.assertEqual('\x81\x7d' + payload,
+                         request.connection.written_data())
+
+    def test_send_medium_message(self):
+        payload = 'a' * 126
+        request = _create_request()
+        msgutil.send_message(request, payload)
+        self.assertEqual('\x81\x7e\x00\x7e' + payload,
+                         request.connection.written_data())
+
+        payload = 'a' * ((1 << 16) - 1)
+        request = _create_request()
+        msgutil.send_message(request, payload)
+        self.assertEqual('\x81\x7e\xff\xff' + payload,
+                         request.connection.written_data())
+
+    def test_send_large_message(self):
+        payload = 'a' * (1 << 16)
+        request = _create_request()
+        msgutil.send_message(request, payload)
+        self.assertEqual('\x81\x7f\x00\x00\x00\x00\x00\x01\x00\x00' + payload,
+                         request.connection.written_data())
+
+    def test_send_message_unicode(self):
+        request = _create_request()
+        msgutil.send_message(request, u'\u65e5')
+        # U+65e5 is encoded as e6,97,a5 in UTF-8
+        self.assertEqual('\x81\x03\xe6\x97\xa5',
+                         request.connection.written_data())
+
+    def test_send_message_fragments(self):
+        request = _create_request()
+        msgutil.send_message(request, 'Hello', False)
+        msgutil.send_message(request, ' ', False)
+        msgutil.send_message(request, 'World', False)
+        msgutil.send_message(request, '!', True)
+        self.assertEqual('\x01\x05Hello\x00\x01 \x00\x05World\x80\x01!',
+                         request.connection.written_data())
+
+    def test_send_fragments_immediate_zero_termination(self):
+        request = _create_request()
+        msgutil.send_message(request, 'Hello World!', False)
+        msgutil.send_message(request, '', True)
+        self.assertEqual('\x01\x0cHello World!\x80\x00',
+                         request.connection.written_data())
+
+    def test_receive_message(self):
+        request = _create_request(
+            ('\x81\x85', 'Hello'), ('\x81\x86', 'World!'))
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertEqual('World!', msgutil.receive_message(request))
+
+        payload = 'a' * 125
+        request = _create_request(('\x81\xfd', payload))
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+    def test_receive_medium_message(self):
+        payload = 'a' * 126
+        request = _create_request(('\x81\xfe\x00\x7e', payload))
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+        payload = 'a' * ((1 << 16) - 1)
+        request = _create_request(('\x81\xfe\xff\xff', payload))
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+    def test_receive_large_message(self):
+        payload = 'a' * (1 << 16)
+        request = _create_request(
+            ('\x81\xff\x00\x00\x00\x00\x00\x01\x00\x00', payload))
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+    def test_receive_length_not_encoded_using_minimal_number_of_bytes(self):
+        # Log warning on receiving bad payload length field that doesn't use
+        # minimal number of bytes but continue processing.
+
+        payload = 'a'
+        # 1 byte can be represented without extended payload length field.
+        request = _create_request(
+            ('\x81\xff\x00\x00\x00\x00\x00\x00\x00\x01', payload))
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+    def test_receive_message_unicode(self):
+        request = _create_request(('\x81\x83', '\xe6\x9c\xac'))
+        # U+672c is encoded as e6,9c,ac in UTF-8
+        self.assertEqual(u'\u672c', msgutil.receive_message(request))
+
+    def test_receive_message_erroneous_unicode(self):
+        # \x80 and \x81 are invalid as UTF-8.
+        request = _create_request(('\x81\x82', '\x80\x81'))
+        # Invalid characters should raise InvalidUTF8Exception
+        self.assertRaises(InvalidUTF8Exception,
+                          msgutil.receive_message,
+                          request)
+
+    def test_receive_fragments(self):
+        request = _create_request(
+            ('\x01\x85', 'Hello'),
+            ('\x00\x81', ' '),
+            ('\x00\x85', 'World'),
+            ('\x80\x81', '!'))
+        self.assertEqual('Hello World!', msgutil.receive_message(request))
+
+    def test_receive_fragments_unicode(self):
+        # UTF-8 encodes U+6f22 into e6bca2 and U+5b57 into e5ad97.
+        request = _create_request(
+            ('\x01\x82', '\xe6\xbc'),
+            ('\x00\x82', '\xa2\xe5'),
+            ('\x80\x82', '\xad\x97'))
+        self.assertEqual(u'\u6f22\u5b57', msgutil.receive_message(request))
+
+    def test_receive_fragments_immediate_zero_termination(self):
+        request = _create_request(
+            ('\x01\x8c', 'Hello World!'), ('\x80\x80', ''))
+        self.assertEqual('Hello World!', msgutil.receive_message(request))
+
+    def test_receive_fragments_duplicate_start(self):
+        request = _create_request(
+            ('\x01\x85', 'Hello'), ('\x01\x85', 'World'))
+        self.assertRaises(msgutil.InvalidFrameException,
+                          msgutil.receive_message,
+                          request)
+
+    def test_receive_fragments_intermediate_but_not_started(self):
+        request = _create_request(('\x00\x85', 'Hello'))
+        self.assertRaises(msgutil.InvalidFrameException,
+                          msgutil.receive_message,
+                          request)
+
+    def test_receive_fragments_end_but_not_started(self):
+        request = _create_request(('\x80\x85', 'Hello'))
+        self.assertRaises(msgutil.InvalidFrameException,
+                          msgutil.receive_message,
+                          request)
+
+    def test_receive_message_discard(self):
+        request = _create_request(
+            ('\x8f\x86', 'IGNORE'), ('\x81\x85', 'Hello'),
+            ('\x8f\x89', 'DISREGARD'), ('\x81\x86', 'World!'))
+        self.assertRaises(msgutil.UnsupportedFrameException,
+                          msgutil.receive_message, request)
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertRaises(msgutil.UnsupportedFrameException,
+                          msgutil.receive_message, request)
+        self.assertEqual('World!', msgutil.receive_message(request))
+
+    def test_receive_close(self):
+        request = _create_request(
+            ('\x88\x8a', struct.pack('!H', 1000) + 'Good bye'))
+        self.assertEqual(None, msgutil.receive_message(request))
+        self.assertEqual(1000, request.ws_close_code)
+        self.assertEqual('Good bye', request.ws_close_reason)
+
+    def test_send_longest_close(self):
+        reason = 'a' * 123
+        request = _create_request(
+            ('\x88\xfd',
+             struct.pack('!H', common.STATUS_NORMAL_CLOSURE) + reason))
+        request.ws_stream.close_connection(common.STATUS_NORMAL_CLOSURE,
+                                           reason)
+        self.assertEqual(request.ws_close_code, common.STATUS_NORMAL_CLOSURE)
+        self.assertEqual(request.ws_close_reason, reason)
+
+    def test_send_close_too_long(self):
+        request = _create_request()
+        self.assertRaises(msgutil.BadOperationException,
+                          Stream.close_connection,
+                          request.ws_stream,
+                          common.STATUS_NORMAL_CLOSURE,
+                          'a' * 124)
+
+    def test_send_close_inconsistent_code_and_reason(self):
+        request = _create_request()
+        # reason parameter must not be specified when code is None.
+        self.assertRaises(msgutil.BadOperationException,
+                          Stream.close_connection,
+                          request.ws_stream,
+                          None,
+                          'a')
+
+    def test_send_ping(self):
+        request = _create_request()
+        msgutil.send_ping(request, 'Hello World!')
+        self.assertEqual('\x89\x0cHello World!',
+                         request.connection.written_data())
+
+    def test_send_longest_ping(self):
+        request = _create_request()
+        msgutil.send_ping(request, 'a' * 125)
+        self.assertEqual('\x89\x7d' + 'a' * 125,
+                         request.connection.written_data())
+
+    def test_send_ping_too_long(self):
+        request = _create_request()
+        self.assertRaises(msgutil.BadOperationException,
+                          msgutil.send_ping,
+                          request,
+                          'a' * 126)
+
+    def test_receive_ping(self):
+        """Tests receiving a ping control frame."""
+
+        def handler(request, message):
+            request.called = True
+
+        # Stream automatically respond to ping with pong without any action
+        # by application layer.
+        request = _create_request(
+            ('\x89\x85', 'Hello'), ('\x81\x85', 'World'))
+        self.assertEqual('World', msgutil.receive_message(request))
+        self.assertEqual('\x8a\x05Hello',
+                         request.connection.written_data())
+
+        request = _create_request(
+            ('\x89\x85', 'Hello'), ('\x81\x85', 'World'))
+        request.on_ping_handler = handler
+        self.assertEqual('World', msgutil.receive_message(request))
+        self.assertTrue(request.called)
+
+    def test_receive_longest_ping(self):
+        request = _create_request(
+            ('\x89\xfd', 'a' * 125), ('\x81\x85', 'World'))
+        self.assertEqual('World', msgutil.receive_message(request))
+        self.assertEqual('\x8a\x7d' + 'a' * 125,
+                         request.connection.written_data())
+
+    def test_receive_ping_too_long(self):
+        request = _create_request(('\x89\xfe\x00\x7e', 'a' * 126))
+        self.assertRaises(msgutil.InvalidFrameException,
+                          msgutil.receive_message,
+                          request)
+
+    def test_receive_pong(self):
+        """Tests receiving a pong control frame."""
+
+        def handler(request, message):
+            request.called = True
+
+        request = _create_request(
+            ('\x8a\x85', 'Hello'), ('\x81\x85', 'World'))
+        request.on_pong_handler = handler
+        msgutil.send_ping(request, 'Hello')
+        self.assertEqual('\x89\x05Hello',
+                         request.connection.written_data())
+        # Valid pong is received, but receive_message won't return for it.
+        self.assertEqual('World', msgutil.receive_message(request))
+        # Check that nothing was written after receive_message call.
+        self.assertEqual('\x89\x05Hello',
+                         request.connection.written_data())
+
+        self.assertTrue(request.called)
+
+    def test_receive_unsolicited_pong(self):
+        # Unsolicited pong is allowed from HyBi 07.
+        request = _create_request(
+            ('\x8a\x85', 'Hello'), ('\x81\x85', 'World'))
+        msgutil.receive_message(request)
+
+        request = _create_request(
+            ('\x8a\x85', 'Hello'), ('\x81\x85', 'World'))
+        msgutil.send_ping(request, 'Jumbo')
+        # Body mismatch.
+        msgutil.receive_message(request)
+
+    def test_ping_cannot_be_fragmented(self):
+        request = _create_request(('\x09\x85', 'Hello'))
+        self.assertRaises(msgutil.InvalidFrameException,
+                          msgutil.receive_message,
+                          request)
+
+    def test_ping_with_too_long_payload(self):
+        request = _create_request(('\x89\xfe\x01\x00', 'a' * 256))
+        self.assertRaises(msgutil.InvalidFrameException,
+                          msgutil.receive_message,
+                          request)
+
+
+class DeflateFrameTest(unittest.TestCase):
+    """Tests for checking deflate-frame extension."""
+
+    def test_send_message(self):
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        request = _create_request_from_rawdata(
+            '', deflate_frame_request=extension)
+        msgutil.send_message(request, 'Hello')
+        msgutil.send_message(request, 'World')
+
+        expected = ''
+
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        expected += '\xc1%c' % len(compressed_hello)
+        expected += compressed_hello
+
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_world = compressed_world[:-4]
+        expected += '\xc1%c' % len(compressed_world)
+        expected += compressed_world
+
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_message_bfinal(self):
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        request = _create_request_from_rawdata(
+            '', deflate_frame_request=extension)
+        self.assertEquals(1, len(request.ws_extension_processors))
+        deflate_frame_processor = request.ws_extension_processors[0]
+        deflate_frame_processor.set_bfinal(True)
+        msgutil.send_message(request, 'Hello')
+        msgutil.send_message(request, 'World')
+
+        expected = ''
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_FINISH)
+        compressed_hello = compressed_hello + chr(0)
+        expected += '\xc1%c' % len(compressed_hello)
+        expected += compressed_hello
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_FINISH)
+        compressed_world = compressed_world + chr(0)
+        expected += '\xc1%c' % len(compressed_world)
+        expected += compressed_world
+
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_message_comp_bit(self):
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        request = _create_request_from_rawdata(
+            '', deflate_frame_request=extension)
+        self.assertEquals(1, len(request.ws_extension_processors))
+        deflate_frame_processor = request.ws_extension_processors[0]
+        msgutil.send_message(request, 'Hello')
+        deflate_frame_processor.disable_outgoing_compression()
+        msgutil.send_message(request, 'Hello')
+        deflate_frame_processor.enable_outgoing_compression()
+        msgutil.send_message(request, 'Hello')
+
+        expected = ''
+
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        expected += '\xc1%c' % len(compressed_hello)
+        expected += compressed_hello
+
+        expected += '\x81\x05Hello'
+
+        compressed_2nd_hello = compress.compress('Hello')
+        compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_2nd_hello = compressed_2nd_hello[:-4]
+        expected += '\xc1%c' % len(compressed_2nd_hello)
+        expected += compressed_2nd_hello
+
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_message_no_context_takeover_parameter(self):
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        extension.add_parameter('no_context_takeover', None)
+        request = _create_request_from_rawdata(
+            '', deflate_frame_request=extension)
+        for i in xrange(3):
+            msgutil.send_message(request, 'Hello')
+
+        compressed_message = compress.compress('Hello')
+        compressed_message += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_message = compressed_message[:-4]
+        expected = '\xc1%c' % len(compressed_message)
+        expected += compressed_message
+
+        self.assertEqual(
+            expected + expected + expected, request.connection.written_data())
+
+    def test_bad_request_parameters(self):
+        """Tests that if there's anything wrong with deflate-frame extension
+        request, deflate-frame is rejected.
+        """
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        # max_window_bits less than 8 is illegal.
+        extension.add_parameter('max_window_bits', '7')
+        processor = DeflateFrameExtensionProcessor(extension)
+        self.assertEqual(None, processor.get_extension_response())
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        # max_window_bits greater than 15 is illegal.
+        extension.add_parameter('max_window_bits', '16')
+        processor = DeflateFrameExtensionProcessor(extension)
+        self.assertEqual(None, processor.get_extension_response())
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        # Non integer max_window_bits is illegal.
+        extension.add_parameter('max_window_bits', 'foobar')
+        processor = DeflateFrameExtensionProcessor(extension)
+        self.assertEqual(None, processor.get_extension_response())
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        # no_context_takeover must not have any value.
+        extension.add_parameter('no_context_takeover', 'foobar')
+        processor = DeflateFrameExtensionProcessor(extension)
+        self.assertEqual(None, processor.get_extension_response())
+
+    def test_response_parameters(self):
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        processor = DeflateFrameExtensionProcessor(extension)
+        processor.set_response_window_bits(8)
+        response = processor.get_extension_response()
+        self.assertTrue(response.has_parameter('max_window_bits'))
+        self.assertEqual('8', response.get_parameter_value('max_window_bits'))
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        processor = DeflateFrameExtensionProcessor(extension)
+        processor.set_response_no_context_takeover(True)
+        response = processor.get_extension_response()
+        self.assertTrue(response.has_parameter('no_context_takeover'))
+        self.assertTrue(
+            response.get_parameter_value('no_context_takeover') is None)
+
+    def test_receive_message(self):
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        data = ''
+
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        data += '\xc1%c' % (len(compressed_hello) | 0x80)
+        data += _mask_hybi(compressed_hello)
+
+        compressed_websocket = compress.compress('WebSocket')
+        compressed_websocket += compress.flush(zlib.Z_FINISH)
+        compressed_websocket += '\x00'
+        data += '\xc1%c' % (len(compressed_websocket) | 0x80)
+        data += _mask_hybi(compressed_websocket)
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_world = compressed_world[:-4]
+        data += '\xc1%c' % (len(compressed_world) | 0x80)
+        data += _mask_hybi(compressed_world)
+
+        # Close frame
+        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        request = _create_request_from_rawdata(
+            data, deflate_frame_request=extension)
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertEqual('WebSocket', msgutil.receive_message(request))
+        self.assertEqual('World', msgutil.receive_message(request))
+
+        self.assertEqual(None, msgutil.receive_message(request))
+
+    def test_receive_message_client_using_smaller_window(self):
+        """Test that frames coming from a client which is using smaller window
+        size that the server are correctly received.
+        """
+
+        # Using the smallest window bits of 8 for generating input frames.
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -8)
+
+        data = ''
+
+        # Use a frame whose content is bigger than the clients' DEFLATE window
+        # size before compression. The content mainly consists of 'a' but
+        # repetition of 'b' is put at the head and tail so that if the window
+        # size is big, the head is back-referenced but if small, not.
+        payload = 'b' * 64 + 'a' * 1024 + 'b' * 64
+        compressed_hello = compress.compress(payload)
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        data += '\xc1%c' % (len(compressed_hello) | 0x80)
+        data += _mask_hybi(compressed_hello)
+
+        # Close frame
+        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        request = _create_request_from_rawdata(
+            data, deflate_frame_request=extension)
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+        self.assertEqual(None, msgutil.receive_message(request))
+
+    def test_receive_message_comp_bit(self):
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        data = ''
+
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        data += '\xc1%c' % (len(compressed_hello) | 0x80)
+        data += _mask_hybi(compressed_hello)
+
+        data += '\x81\x85' + _mask_hybi('Hello')
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        compressed_2nd_hello = compress.compress('Hello')
+        compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_2nd_hello = compressed_2nd_hello[:-4]
+        data += '\xc1%c' % (len(compressed_2nd_hello) | 0x80)
+        data += _mask_hybi(compressed_2nd_hello)
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        request = _create_request_from_rawdata(
+            data, deflate_frame_request=extension)
+        for i in xrange(3):
+            self.assertEqual('Hello', msgutil.receive_message(request))
+
+    def test_receive_message_various_btype(self):
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        data = ''
+
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        data += '\xc1%c' % (len(compressed_hello) | 0x80)
+        data += _mask_hybi(compressed_hello)
+
+        compressed_websocket = compress.compress('WebSocket')
+        compressed_websocket += compress.flush(zlib.Z_FINISH)
+        compressed_websocket += '\x00'
+        data += '\xc1%c' % (len(compressed_websocket) | 0x80)
+        data += _mask_hybi(compressed_websocket)
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_world = compressed_world[:-4]
+        data += '\xc1%c' % (len(compressed_world) | 0x80)
+        data += _mask_hybi(compressed_world)
+
+        # Close frame
+        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
+
+        extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION)
+        request = _create_request_from_rawdata(
+            data, deflate_frame_request=extension)
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertEqual('WebSocket', msgutil.receive_message(request))
+        self.assertEqual('World', msgutil.receive_message(request))
+
+        self.assertEqual(None, msgutil.receive_message(request))
+
+
+class PerMessageDeflateTest(unittest.TestCase):
+    """Tests for permessage-deflate extension."""
+
+    def test_response_parameters(self):
+        extension = common.ExtensionParameter(
+            common.PERMESSAGE_DEFLATE_EXTENSION)
+        extension.add_parameter('server_no_context_takeover', None)
+        processor = PerMessageDeflateExtensionProcessor(extension)
+        response = processor.get_extension_response()
+        self.assertTrue(
+            response.has_parameter('server_no_context_takeover'))
+        self.assertEqual(
+            None, response.get_parameter_value('server_no_context_takeover'))
+
+        extension = common.ExtensionParameter(
+            common.PERMESSAGE_DEFLATE_EXTENSION)
+        extension.add_parameter('client_max_window_bits', None)
+        processor = PerMessageDeflateExtensionProcessor(extension)
+
+        processor.set_client_max_window_bits(8)
+        processor.set_client_no_context_takeover(True)
+        response = processor.get_extension_response()
+        self.assertEqual(
+            '8', response.get_parameter_value('client_max_window_bits'))
+        self.assertTrue(
+            response.has_parameter('client_no_context_takeover'))
+        self.assertEqual(
+            None,
+            response.get_parameter_value('client_no_context_takeover'))
+
+    def test_send_message(self):
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        msgutil.send_message(request, 'Hello')
+
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        expected = '\xc1%c' % len(compressed_hello)
+        expected += compressed_hello
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_empty_message(self):
+        """Test that an empty message is compressed correctly."""
+
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+
+        msgutil.send_message(request, '')
+
+        # Payload in binary: 0b00000000
+        # From LSB,
+        # - 1 bit of BFINAL (0)
+        # - 2 bits of BTYPE (no compression)
+        # - 5 bits of padding
+        self.assertEqual('\xc1\x01\x00',
+                         request.connection.written_data())
+
+    def test_send_message_with_null_character(self):
+        """Test that a simple payload (one null) is framed correctly."""
+
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+
+        msgutil.send_message(request, '\x00')
+
+        # Payload in binary: 0b01100010 0b00000000 0b00000000
+        # From LSB,
+        # - 1 bit of BFINAL (0)
+        # - 2 bits of BTYPE (01 that means fixed Huffman)
+        # - 8 bits of the first code (00110000 that is the code for the literal
+        #   alphabet 0x00)
+        # - 7 bits of the second code (0000000 that is the code for the
+        #   end-of-block)
+        # - 1 bit of BFINAL (0)
+        # - 2 bits of BTYPE (no compression)
+        # - 2 bits of padding
+        self.assertEqual('\xc1\x03\x62\x00\x00',
+                         request.connection.written_data())
+
+    def test_send_two_messages(self):
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        msgutil.send_message(request, 'Hello')
+        msgutil.send_message(request, 'World')
+
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        expected = ''
+
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        expected += '\xc1%c' % len(compressed_hello)
+        expected += compressed_hello
+
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_world = compressed_world[:-4]
+        expected += '\xc1%c' % len(compressed_world)
+        expected += compressed_world
+
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_message_fragmented(self):
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        msgutil.send_message(request, 'Hello', end=False)
+        msgutil.send_message(request, 'Goodbye', end=False)
+        msgutil.send_message(request, 'World')
+
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        expected = '\x41%c' % len(compressed_hello)
+        expected += compressed_hello
+        compressed_goodbye = compress.compress('Goodbye')
+        compressed_goodbye += compress.flush(zlib.Z_SYNC_FLUSH)
+        expected += '\x00%c' % len(compressed_goodbye)
+        expected += compressed_goodbye
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_world = compressed_world[:-4]
+        expected += '\x80%c' % len(compressed_world)
+        expected += compressed_world
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_message_fragmented_empty_first_frame(self):
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        msgutil.send_message(request, '', end=False)
+        msgutil.send_message(request, 'Hello')
+
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        expected = '\x41%c' % len(compressed_hello)
+        expected += compressed_hello
+        compressed_empty = compress.compress('Hello')
+        compressed_empty += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_empty = compressed_empty[:-4]
+        expected += '\x80%c' % len(compressed_empty)
+        expected += compressed_empty
+        print '%r' % expected
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_message_fragmented_empty_last_frame(self):
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        msgutil.send_message(request, 'Hello', end=False)
+        msgutil.send_message(request, '')
+
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        expected = '\x41%c' % len(compressed_hello)
+        expected += compressed_hello
+        compressed_empty = compress.compress('')
+        compressed_empty += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_empty = compressed_empty[:-4]
+        expected += '\x80%c' % len(compressed_empty)
+        expected += compressed_empty
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_send_message_using_small_window(self):
+        common_part = 'abcdefghijklmnopqrstuvwxyz'
+        test_message = common_part + '-' * 30000 + common_part
+
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        extension.add_parameter('server_max_window_bits', '8')
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        msgutil.send_message(request, test_message)
+
+        expected_websocket_header_size = 2
+        expected_websocket_payload_size = 91
+
+        actual_frame = request.connection.written_data()
+        self.assertEqual(expected_websocket_header_size +
+                         expected_websocket_payload_size,
+                         len(actual_frame))
+        actual_header = actual_frame[0:expected_websocket_header_size]
+        actual_payload = actual_frame[expected_websocket_header_size:]
+
+        self.assertEqual(
+                '\xc1%c' % expected_websocket_payload_size, actual_header)
+        decompress = zlib.decompressobj(-8)
+        decompressed_message = decompress.decompress(
+                actual_payload + '\x00\x00\xff\xff')
+        decompressed_message += decompress.flush()
+        self.assertEqual(test_message, decompressed_message)
+        self.assertEqual(0, len(decompress.unused_data))
+        self.assertEqual(0, len(decompress.unconsumed_tail))
+
+    def test_send_message_no_context_takeover_parameter(self):
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        extension.add_parameter('server_no_context_takeover', None)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        for i in xrange(3):
+            msgutil.send_message(request, 'Hello', end=False)
+            msgutil.send_message(request, 'Hello', end=True)
+
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        first_hello = compress.compress('Hello')
+        first_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        expected = '\x41%c' % len(first_hello)
+        expected += first_hello
+        second_hello = compress.compress('Hello')
+        second_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        second_hello = second_hello[:-4]
+        expected += '\x80%c' % len(second_hello)
+        expected += second_hello
+
+        self.assertEqual(
+                expected + expected + expected,
+                request.connection.written_data())
+
+    def test_send_message_fragmented_bfinal(self):
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                '', permessage_deflate_request=extension)
+        self.assertEquals(1, len(request.ws_extension_processors))
+        request.ws_extension_processors[0].set_bfinal(True)
+        msgutil.send_message(request, 'Hello', end=False)
+        msgutil.send_message(request, 'World', end=True)
+
+        expected = ''
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_FINISH)
+        compressed_hello = compressed_hello + chr(0)
+        expected += '\x41%c' % len(compressed_hello)
+        expected += compressed_hello
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_FINISH)
+        compressed_world = compressed_world + chr(0)
+        expected += '\x80%c' % len(compressed_world)
+        expected += compressed_world
+
+        self.assertEqual(expected, request.connection.written_data())
+
+    def test_receive_message_deflate(self):
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        data = '\xc1%c' % (len(compressed_hello) | 0x80)
+        data += _mask_hybi(compressed_hello)
+
+        # Close frame
+        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
+
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                data, permessage_deflate_request=extension)
+        self.assertEqual('Hello', msgutil.receive_message(request))
+
+        self.assertEqual(None, msgutil.receive_message(request))
+
+    def test_receive_message_random_section(self):
+        """Test that a compressed message fragmented into lots of chunks is
+        correctly received.
+        """
+
+        random.seed(a=0)
+        payload = ''.join(
+            [chr(random.randint(0, 255)) for i in xrange(1000)])
+
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_payload = compress.compress(payload)
+        compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_payload = compressed_payload[:-4]
+
+        # Fragment the compressed payload into lots of frames.
+        bytes_chunked = 0
+        data = ''
+        frame_count = 0
+
+        chunk_sizes = []
+
+        while bytes_chunked < len(compressed_payload):
+            # Make sure that
+            # - the length of chunks are equal or less than 125 so that we can
+            #   use 1 octet length header format for all frames.
+            # - at least 10 chunks are created.
+            chunk_size = random.randint(
+                1, min(125,
+                       len(compressed_payload) / 10,
+                       len(compressed_payload) - bytes_chunked))
+            chunk_sizes.append(chunk_size)
+            chunk = compressed_payload[
+                bytes_chunked:bytes_chunked + chunk_size]
+            bytes_chunked += chunk_size
+
+            first_octet = 0x00
+            if len(data) == 0:
+                first_octet = first_octet | 0x42
+            if bytes_chunked == len(compressed_payload):
+                first_octet = first_octet | 0x80
+
+            data += '%c%c' % (first_octet, chunk_size | 0x80)
+            data += _mask_hybi(chunk)
+
+            frame_count += 1
+
+        print "Chunk sizes: %r" % chunk_sizes
+        self.assertTrue(len(chunk_sizes) > 10)
+
+        # Close frame
+        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
+
+        extension = common.ExtensionParameter(
+            common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+            data, permessage_deflate_request=extension)
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+        self.assertEqual(None, msgutil.receive_message(request))
+
+    def test_receive_two_messages(self):
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        data = ''
+
+        compressed_hello = compress.compress('HelloWebSocket')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        split_position = len(compressed_hello) / 2
+        data += '\x41%c' % (split_position | 0x80)
+        data += _mask_hybi(compressed_hello[:split_position])
+
+        data += '\x80%c' % ((len(compressed_hello) - split_position) | 0x80)
+        data += _mask_hybi(compressed_hello[split_position:])
+
+        compress = zlib.compressobj(
+                zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+
+        compressed_world = compress.compress('World')
+        compressed_world += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_world = compressed_world[:-4]
+        data += '\xc1%c' % (len(compressed_world) | 0x80)
+        data += _mask_hybi(compressed_world)
+
+        # Close frame
+        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
+
+        extension = common.ExtensionParameter(
+                common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+                data, permessage_deflate_request=extension)
+        self.assertEqual('HelloWebSocket', msgutil.receive_message(request))
+        self.assertEqual('World', msgutil.receive_message(request))
+
+        self.assertEqual(None, msgutil.receive_message(request))
+
+    def test_receive_message_mixed_btype(self):
+        """Test that a message compressed using lots of DEFLATE blocks with
+        various flush mode is correctly received.
+        """
+
+        random.seed(a=0)
+        payload = ''.join(
+            [chr(random.randint(0, 255)) for i in xrange(1000)])
+
+        compress = None
+
+        # Fragment the compressed payload into lots of frames.
+        bytes_chunked = 0
+        compressed_payload = ''
+
+        chunk_sizes = []
+        methods = []
+        sync_used = False
+        finish_used = False
+
+        while bytes_chunked < len(payload):
+            # Make sure at least 10 chunks are created.
+            chunk_size = random.randint(
+                1, min(100, len(payload) - bytes_chunked))
+            chunk_sizes.append(chunk_size)
+            chunk = payload[bytes_chunked:bytes_chunked + chunk_size]
+
+            bytes_chunked += chunk_size
+
+            if compress is None:
+                compress = zlib.compressobj(
+                    zlib.Z_DEFAULT_COMPRESSION,
+                    zlib.DEFLATED,
+                    -zlib.MAX_WBITS)
+
+            if bytes_chunked == len(payload):
+                compressed_payload += compress.compress(chunk)
+                compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH)
+                compressed_payload = compressed_payload[:-4]
+            else:
+                method = random.randint(0, 1)
+                methods.append(method)
+                if method == 0:
+                    compressed_payload += compress.compress(chunk)
+                    compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH)
+                    sync_used = True
+                else:
+                    compressed_payload += compress.compress(chunk)
+                    compressed_payload += compress.flush(zlib.Z_FINISH)
+                    compress = None
+                    finish_used = True
+
+        print "Chunk sizes: %r" % chunk_sizes
+        self.assertTrue(len(chunk_sizes) > 10)
+        print "Methods: %r" % methods
+        self.assertTrue(sync_used)
+        self.assertTrue(finish_used)
+
+        self.assertTrue(125 < len(compressed_payload))
+        self.assertTrue(len(compressed_payload) < 65536)
+        data = '\xc2\xfe' + struct.pack('!H', len(compressed_payload))
+        data += _mask_hybi(compressed_payload)
+
+        # Close frame
+        data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')
+
+        extension = common.ExtensionParameter(
+            common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_request_from_rawdata(
+            data, permessage_deflate_request=extension)
+        self.assertEqual(payload, msgutil.receive_message(request))
+
+        self.assertEqual(None, msgutil.receive_message(request))
+
+
+class MessageTestHixie75(unittest.TestCase):
+    """Tests for draft-hixie-thewebsocketprotocol-76 stream class."""
+
+    def test_send_message(self):
+        request = _create_request_hixie75()
+        msgutil.send_message(request, 'Hello')
+        self.assertEqual('\x00Hello\xff', request.connection.written_data())
+
+    def test_send_message_unicode(self):
+        request = _create_request_hixie75()
+        msgutil.send_message(request, u'\u65e5')
+        # U+65e5 is encoded as e6,97,a5 in UTF-8
+        self.assertEqual('\x00\xe6\x97\xa5\xff',
+                         request.connection.written_data())
+
+    def test_receive_message(self):
+        request = _create_request_hixie75('\x00Hello\xff\x00World!\xff')
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertEqual('World!', msgutil.receive_message(request))
+
+    def test_receive_message_unicode(self):
+        request = _create_request_hixie75('\x00\xe6\x9c\xac\xff')
+        # U+672c is encoded as e6,9c,ac in UTF-8
+        self.assertEqual(u'\u672c', msgutil.receive_message(request))
+
+    def test_receive_message_erroneous_unicode(self):
+        # \x80 and \x81 are invalid as UTF-8.
+        request = _create_request_hixie75('\x00\x80\x81\xff')
+        # Invalid characters should be replaced with
+        # U+fffd REPLACEMENT CHARACTER
+        self.assertEqual(u'\ufffd\ufffd', msgutil.receive_message(request))
+
+    def test_receive_message_discard(self):
+        request = _create_request_hixie75('\x80\x06IGNORE\x00Hello\xff'
+                                          '\x01DISREGARD\xff\x00World!\xff')
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertEqual('World!', msgutil.receive_message(request))
+
+
+class MessageReceiverTest(unittest.TestCase):
+    """Tests the Stream class using MessageReceiver."""
+
+    def test_queue(self):
+        request = _create_blocking_request()
+        receiver = msgutil.MessageReceiver(request)
+
+        self.assertEqual(None, receiver.receive_nowait())
+
+        request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!'))
+        self.assertEqual('Hello!', receiver.receive())
+
+    def test_onmessage(self):
+        onmessage_queue = Queue.Queue()
+
+        def onmessage_handler(message):
+            onmessage_queue.put(message)
+
+        request = _create_blocking_request()
+        receiver = msgutil.MessageReceiver(request, onmessage_handler)
+
+        request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!'))
+        self.assertEqual('Hello!', onmessage_queue.get())
+
+
+class MessageReceiverHixie75Test(unittest.TestCase):
+    """Tests the StreamHixie75 class using MessageReceiver."""
+
+    def test_queue(self):
+        request = _create_blocking_request_hixie75()
+        receiver = msgutil.MessageReceiver(request)
+
+        self.assertEqual(None, receiver.receive_nowait())
+
+        request.connection.put_bytes('\x00Hello!\xff')
+        self.assertEqual('Hello!', receiver.receive())
+
+    def test_onmessage(self):
+        onmessage_queue = Queue.Queue()
+
+        def onmessage_handler(message):
+            onmessage_queue.put(message)
+
+        request = _create_blocking_request_hixie75()
+        receiver = msgutil.MessageReceiver(request, onmessage_handler)
+
+        request.connection.put_bytes('\x00Hello!\xff')
+        self.assertEqual('Hello!', onmessage_queue.get())
+
+
+class MessageSenderTest(unittest.TestCase):
+    """Tests the Stream class using MessageSender."""
+
+    def test_send(self):
+        request = _create_blocking_request()
+        sender = msgutil.MessageSender(request)
+
+        sender.send('World')
+        self.assertEqual('\x81\x05World', request.connection.written_data())
+
+    def test_send_nowait(self):
+        # Use a queue to check the bytes written by MessageSender.
+        # request.connection.written_data() cannot be used here because
+        # MessageSender runs in a separate thread.
+        send_queue = Queue.Queue()
+
+        def write(bytes):
+            send_queue.put(bytes)
+
+        request = _create_blocking_request()
+        request.connection.write = write
+
+        sender = msgutil.MessageSender(request)
+
+        sender.send_nowait('Hello')
+        sender.send_nowait('World')
+        self.assertEqual('\x81\x05Hello', send_queue.get())
+        self.assertEqual('\x81\x05World', send_queue.get())
+
+
+class MessageSenderHixie75Test(unittest.TestCase):
+    """Tests the StreamHixie75 class using MessageSender."""
+
+    def test_send(self):
+        request = _create_blocking_request_hixie75()
+        sender = msgutil.MessageSender(request)
+
+        sender.send('World')
+        self.assertEqual('\x00World\xff', request.connection.written_data())
+
+    def test_send_nowait(self):
+        # Use a queue to check the bytes written by MessageSender.
+        # request.connection.written_data() cannot be used here because
+        # MessageSender runs in a separate thread.
+        send_queue = Queue.Queue()
+
+        def write(bytes):
+            send_queue.put(bytes)
+
+        request = _create_blocking_request_hixie75()
+        request.connection.write = write
+
+        sender = msgutil.MessageSender(request)
+
+        sender.send_nowait('Hello')
+        sender.send_nowait('World')
+        self.assertEqual('\x00Hello\xff', send_queue.get())
+        self.assertEqual('\x00World\xff', send_queue.get())
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/test/test_mux.py b/tools/pywebsocket/test/test_mux.py
new file mode 100644
index 0000000..3ce45cf
--- /dev/null
+++ b/tools/pywebsocket/test/test_mux.py
@@ -0,0 +1,2088 @@
+#!/usr/bin/env python
+#
+# Copyright 2012, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Tests for mux module."""
+
+import Queue
+import copy
+import logging
+import optparse
+import struct
+import sys
+import unittest
+import time
+import zlib
+
+import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
+
+from mod_pywebsocket import common
+from mod_pywebsocket import mux
+from mod_pywebsocket._stream_base import ConnectionTerminatedException
+from mod_pywebsocket._stream_base import UnsupportedFrameException
+from mod_pywebsocket._stream_hybi import Frame
+from mod_pywebsocket._stream_hybi import Stream
+from mod_pywebsocket._stream_hybi import StreamOptions
+from mod_pywebsocket._stream_hybi import create_binary_frame
+from mod_pywebsocket._stream_hybi import create_close_frame
+from mod_pywebsocket._stream_hybi import create_closing_handshake_body
+from mod_pywebsocket._stream_hybi import parse_frame
+from mod_pywebsocket.extensions import MuxExtensionProcessor
+
+
+import mock
+
+
+_TEST_HEADERS = {'Host': 'server.example.com',
+                 'Upgrade': 'websocket',
+                 'Connection': 'Upgrade',
+                 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
+                 'Sec-WebSocket-Version': '13',
+                 'Origin': 'http://example.com'}
+
+
+class _OutgoingChannelData(object):
+    def __init__(self):
+        self.messages = []
+        self.control_messages = []
+
+        self.builder = mux._InnerMessageBuilder()
+
+class _MockMuxConnection(mock.MockBlockingConn):
+    """Mock class of mod_python connection for mux."""
+
+    def __init__(self):
+        mock.MockBlockingConn.__init__(self)
+        self._control_blocks = []
+        self._channel_data = {}
+
+        self._current_opcode = None
+        self._pending_fragments = []
+
+        self.server_close_code = None
+
+    def write(self, data):
+        """Override MockBlockingConn.write."""
+
+        self._current_data = data
+        self._position = 0
+
+        def _receive_bytes(length):
+            if self._position + length > len(self._current_data):
+                raise ConnectionTerminatedException(
+                    'Failed to receive %d bytes from encapsulated '
+                    'frame' % length)
+            data = self._current_data[self._position:self._position+length]
+            self._position += length
+            return data
+
+        # Parse physical frames and assemble a message if the message is
+        # fragmented.
+        opcode, payload, fin, rsv1, rsv2, rsv3 = (
+            parse_frame(_receive_bytes, unmask_receive=False))
+
+        self._pending_fragments.append(payload)
+
+        if self._current_opcode is None:
+            if opcode == common.OPCODE_CONTINUATION:
+                raise Exception('Sending invalid continuation opcode')
+            self._current_opcode = opcode
+        else:
+            if opcode != common.OPCODE_CONTINUATION:
+                raise Exception('Sending invalid opcode %d' % opcode)
+        if not fin:
+            return
+
+        inner_frame_data = ''.join(self._pending_fragments)
+        self._pending_fragments = []
+        self._current_opcode = None
+
+        # Handle a control message on the physical channel.
+        # TODO(bashi): Support other opcodes if needed.
+        if opcode == common.OPCODE_CLOSE:
+            if len(payload) >= 2:
+                self.server_close_code = struct.unpack('!H', payload[:2])[0]
+            close_body = create_closing_handshake_body(
+                common.STATUS_NORMAL_CLOSURE, '')
+            close_frame = create_close_frame(close_body, mask=True)
+            self.put_bytes(close_frame)
+            return
+
+        # Parse the payload of the message on physical channel.
+        parser = mux._MuxFramePayloadParser(inner_frame_data)
+        channel_id = parser.read_channel_id()
+        if channel_id == mux._CONTROL_CHANNEL_ID:
+            self._control_blocks.extend(list(parser.read_control_blocks()))
+            return
+
+        if not channel_id in self._channel_data:
+            self._channel_data[channel_id] = _OutgoingChannelData()
+        channel_data = self._channel_data[channel_id]
+
+        # Parse logical frames and assemble an inner (logical) message.
+        (inner_fin, inner_rsv1, inner_rsv2, inner_rsv3, inner_opcode,
+         inner_payload) = parser.read_inner_frame()
+        inner_frame = Frame(inner_fin, inner_rsv1, inner_rsv2, inner_rsv3,
+                            inner_opcode, inner_payload)
+        message = channel_data.builder.build(inner_frame)
+        if message is None:
+            return
+
+        if (message.opcode == common.OPCODE_TEXT or
+            message.opcode == common.OPCODE_BINARY):
+            channel_data.messages.append(message.payload)
+
+            self.on_data_message(message.payload)
+        else:
+            channel_data.control_messages.append(
+                {'opcode': message.opcode,
+                 'message': message.payload})
+
+    def on_data_message(self, message):
+        pass
+
+    def get_written_control_blocks(self):
+        return self._control_blocks
+
+    def get_written_messages(self, channel_id):
+        return self._channel_data[channel_id].messages
+
+    def get_written_control_messages(self, channel_id):
+        return self._channel_data[channel_id].control_messages
+
+
+class _FailOnWriteConnection(_MockMuxConnection):
+    """Specicialized version of _MockMuxConnection. Its write() method raises
+    an exception for testing when a data message is written.
+    """
+
+    def on_data_message(self, message):
+        """Override to raise an exception."""
+
+        raise Exception('Intentional failure')
+
+
+class _ChannelEvent(object):
+    """A structure that records channel events."""
+
+    def __init__(self):
+        self.request = None
+        self.messages = []
+        self.exception = None
+        self.client_initiated_closing = False
+
+
+class _MuxMockDispatcher(object):
+    """Mock class of dispatch.Dispatcher for mux."""
+
+    def __init__(self):
+        self.channel_events = {}
+
+    def do_extra_handshake(self, request):
+        if request.ws_requested_protocols is not None:
+            request.ws_protocol = request.ws_requested_protocols[0]
+
+    def _do_echo(self, request, channel_events):
+        while True:
+            message = request.ws_stream.receive_message()
+            if message == None:
+                channel_events.client_initiated_closing = True
+                return
+            if message == 'Goodbye':
+                return
+            channel_events.messages.append(message)
+            # echo back
+            request.ws_stream.send_message(message)
+
+    def _do_ping(self, request, channel_events):
+        request.ws_stream.send_ping('Ping!')
+
+    def _do_ping_while_hello_world(self, request, channel_events):
+        request.ws_stream.send_message('Hello ', end=False)
+        request.ws_stream.send_ping('Ping!')
+        request.ws_stream.send_message('World!', end=True)
+
+    def _do_two_ping_while_hello_world(self, request, channel_events):
+        request.ws_stream.send_message('Hello ', end=False)
+        request.ws_stream.send_ping('Ping!')
+        request.ws_stream.send_ping('Pong!')
+        request.ws_stream.send_message('World!', end=True)
+
+    def transfer_data(self, request):
+        self.channel_events[request.channel_id] = _ChannelEvent()
+        self.channel_events[request.channel_id].request = request
+
+        try:
+            # Note: more handler will be added.
+            if request.uri.endswith('echo'):
+                self._do_echo(request,
+                              self.channel_events[request.channel_id])
+            elif request.uri.endswith('ping'):
+                self._do_ping(request,
+                              self.channel_events[request.channel_id])
+            elif request.uri.endswith('two_ping_while_hello_world'):
+                self._do_two_ping_while_hello_world(
+                    request, self.channel_events[request.channel_id])
+            elif request.uri.endswith('ping_while_hello_world'):
+                self._do_ping_while_hello_world(
+                    request, self.channel_events[request.channel_id])
+            else:
+                raise ValueError('Cannot handle path %r' % request.path)
+            if not request.server_terminated:
+                request.ws_stream.close_connection()
+        except ConnectionTerminatedException, e:
+            self.channel_events[request.channel_id].exception = e
+        except Exception, e:
+            self.channel_events[request.channel_id].exception = e
+            raise
+
+
+def _create_mock_request(connection=None, logical_channel_extensions=None):
+    if connection is None:
+        connection = _MockMuxConnection()
+
+    request = mock.MockRequest(uri='/echo',
+                               headers_in=_TEST_HEADERS,
+                               connection=connection)
+    request.ws_stream = Stream(request, options=StreamOptions())
+    request.mux_processor = MuxExtensionProcessor(
+        common.ExtensionParameter(common.MUX_EXTENSION))
+    if logical_channel_extensions is not None:
+        request.mux_processor.set_extensions(logical_channel_extensions)
+    request.mux_processor.set_quota(8 * 1024)
+    return request
+
+
+def _create_add_channel_request_frame(channel_id, encoding, encoded_handshake):
+    # Allow invalid encoding for testing.
+    first_byte = ((mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) | encoding)
+    payload = (chr(first_byte) +
+               mux._encode_channel_id(channel_id) +
+               mux._encode_number(len(encoded_handshake)) +
+               encoded_handshake)
+    return create_binary_frame(
+        (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True)
+
+
+def _create_drop_channel_frame(channel_id, code=None, message=''):
+    payload = mux._create_drop_channel(channel_id, code, message)
+    return create_binary_frame(
+        (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True)
+
+
+def _create_flow_control_frame(channel_id, replenished_quota):
+    payload = mux._create_flow_control(channel_id, replenished_quota)
+    return create_binary_frame(
+        (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True)
+
+
+def _create_logical_frame(channel_id, message, opcode=common.OPCODE_BINARY,
+                          fin=True, rsv1=False, rsv2=False, rsv3=False,
+                          mask=True):
+    bits = chr((fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | opcode)
+    payload = mux._encode_channel_id(channel_id) + bits + message
+    return create_binary_frame(payload, mask=True)
+
+
+def _create_request_header(path='/echo', extensions=None):
+    headers = (
+        'GET %s HTTP/1.1\r\n'
+        'Host: server.example.com\r\n'
+        'Connection: Upgrade\r\n'
+        'Origin: http://example.com\r\n') % path
+    if extensions:
+        headers += '%s: %s' % (
+            common.SEC_WEBSOCKET_EXTENSIONS_HEADER, extensions)
+    return headers
+
+
+class MuxTest(unittest.TestCase):
+    """A unittest for mux module."""
+
+    def test_channel_id_decode(self):
+        data = '\x00\x01\xbf\xff\xdf\xff\xff\xff\xff\xff\xff'
+        parser = mux._MuxFramePayloadParser(data)
+        channel_id = parser.read_channel_id()
+        self.assertEqual(0, channel_id)
+        channel_id = parser.read_channel_id()
+        self.assertEqual(1, channel_id)
+        channel_id = parser.read_channel_id()
+        self.assertEqual(2 ** 14 - 1, channel_id)
+        channel_id = parser.read_channel_id()
+        self.assertEqual(2 ** 21 - 1, channel_id)
+        channel_id = parser.read_channel_id()
+        self.assertEqual(2 ** 29 - 1, channel_id)
+        self.assertEqual(len(data), parser._read_position)
+
+    def test_channel_id_encode(self):
+        encoded = mux._encode_channel_id(0)
+        self.assertEqual('\x00', encoded)
+        encoded = mux._encode_channel_id(2 ** 14 - 1)
+        self.assertEqual('\xbf\xff', encoded)
+        encoded = mux._encode_channel_id(2 ** 14)
+        self.assertEqual('\xc0@\x00', encoded)
+        encoded = mux._encode_channel_id(2 ** 21 - 1)
+        self.assertEqual('\xdf\xff\xff', encoded)
+        encoded = mux._encode_channel_id(2 ** 21)
+        self.assertEqual('\xe0 \x00\x00', encoded)
+        encoded = mux._encode_channel_id(2 ** 29 - 1)
+        self.assertEqual('\xff\xff\xff\xff', encoded)
+        # channel_id is too large
+        self.assertRaises(ValueError,
+                          mux._encode_channel_id,
+                          2 ** 29)
+
+    def test_read_multiple_control_blocks(self):
+        # Use AddChannelRequest because it can contain arbitrary length of data
+        data = ('\x00\x01\x01a'
+                '\x00\x02\x7d%s'
+                '\x00\x03\x7e\xff\xff%s'
+                '\x00\x04\x7f\x00\x00\x00\x00\x00\x01\x00\x00%s') % (
+            'a' * 0x7d, 'b' * 0xffff, 'c' * 0x10000)
+        parser = mux._MuxFramePayloadParser(data)
+        blocks = list(parser.read_control_blocks())
+        self.assertEqual(4, len(blocks))
+
+        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode)
+        self.assertEqual(1, blocks[0].channel_id)
+        self.assertEqual(1, len(blocks[0].encoded_handshake))
+
+        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[1].opcode)
+        self.assertEqual(2, blocks[1].channel_id)
+        self.assertEqual(0x7d, len(blocks[1].encoded_handshake))
+
+        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[2].opcode)
+        self.assertEqual(3, blocks[2].channel_id)
+        self.assertEqual(0xffff, len(blocks[2].encoded_handshake))
+
+        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[3].opcode)
+        self.assertEqual(4, blocks[3].channel_id)
+        self.assertEqual(0x10000, len(blocks[3].encoded_handshake))
+
+        self.assertEqual(len(data), parser._read_position)
+
+    def test_read_add_channel_request(self):
+        data = '\x00\x01\x01a'
+        parser = mux._MuxFramePayloadParser(data)
+        blocks = list(parser.read_control_blocks())
+        self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode)
+        self.assertEqual(1, blocks[0].channel_id)
+        self.assertEqual(1, len(blocks[0].encoded_handshake))
+
+    def test_read_drop_channel(self):
+        data = '\x60\x01\x00'
+        parser = mux._MuxFramePayloadParser(data)
+        blocks = list(parser.read_control_blocks())
+        self.assertEqual(1, len(blocks))
+        self.assertEqual(1, blocks[0].channel_id)
+        self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode)
+        self.assertEqual(None, blocks[0].drop_code)
+        self.assertEqual(0, len(blocks[0].drop_message))
+
+        data = '\x60\x02\x09\x03\xe8Success'
+        parser = mux._MuxFramePayloadParser(data)
+        blocks = list(parser.read_control_blocks())
+        self.assertEqual(1, len(blocks))
+        self.assertEqual(2, blocks[0].channel_id)
+        self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode)
+        self.assertEqual(1000, blocks[0].drop_code)
+        self.assertEqual('Success', blocks[0].drop_message)
+
+        # Reason is too short.
+        data = '\x60\x01\x01\x00'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(mux.PhysicalConnectionError,
+                          lambda: list(parser.read_control_blocks()))
+
+    def test_read_flow_control(self):
+        data = '\x40\x01\x02'
+        parser = mux._MuxFramePayloadParser(data)
+        blocks = list(parser.read_control_blocks())
+        self.assertEqual(1, len(blocks))
+        self.assertEqual(1, blocks[0].channel_id)
+        self.assertEqual(mux._MUX_OPCODE_FLOW_CONTROL, blocks[0].opcode)
+        self.assertEqual(2, blocks[0].send_quota)
+
+    def test_read_new_channel_slot(self):
+        data = '\x80\x01\x02\x02\x03'
+        parser = mux._MuxFramePayloadParser(data)
+        # TODO(bashi): Implement
+        self.assertRaises(mux.PhysicalConnectionError,
+                          lambda: list(parser.read_control_blocks()))
+
+    def test_read_invalid_number_field_in_control_block(self):
+        # No number field.
+        data = ''
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(ValueError, parser._read_number)
+
+        # The last two bytes are missing.
+        data = '\x7e'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(ValueError, parser._read_number)
+
+        # Missing the last one byte.
+        data = '\x7f\x00\x00\x00\x00\x00\x01\x00'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(ValueError, parser._read_number)
+
+        # The length of number field is too large.
+        data = '\x7f\xff\xff\xff\xff\xff\xff\xff\xff'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(ValueError, parser._read_number)
+
+        # The msb of the first byte is set.
+        data = '\x80'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(ValueError, parser._read_number)
+
+        # Using 3 bytes encoding for 125.
+        data = '\x7e\x00\x7d'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(ValueError, parser._read_number)
+
+        # Using 9 bytes encoding for 0xffff
+        data = '\x7f\x00\x00\x00\x00\x00\x00\xff\xff'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(ValueError, parser._read_number)
+
+    def test_read_invalid_size_and_contents(self):
+        # Only contain number field.
+        data = '\x01'
+        parser = mux._MuxFramePayloadParser(data)
+        self.assertRaises(mux.PhysicalConnectionError,
+                          parser._read_size_and_contents)
+
+    def test_create_add_channel_response(self):
+        data = mux._create_add_channel_response(channel_id=1,
+                                                encoded_handshake='FooBar',
+                                                encoding=0,
+                                                rejected=False)
+        self.assertEqual('\x20\x01\x06FooBar', data)
+
+        data = mux._create_add_channel_response(channel_id=2,
+                                                encoded_handshake='Hello',
+                                                encoding=1,
+                                                rejected=True)
+        self.assertEqual('\x31\x02\x05Hello', data)
+
+    def test_create_drop_channel(self):
+        data = mux._create_drop_channel(channel_id=1)
+        self.assertEqual('\x60\x01\x00', data)
+
+        data = mux._create_drop_channel(channel_id=1,
+                                        code=2000,
+                                        message='error')
+        self.assertEqual('\x60\x01\x07\x07\xd0error', data)
+
+        # reason must be empty if code is None
+        self.assertRaises(ValueError,
+                          mux._create_drop_channel,
+                          1, None, 'FooBar')
+
+    def test_parse_request_text(self):
+        request_text = _create_request_header()
+        command, path, version, headers = mux._parse_request_text(request_text)
+        self.assertEqual('GET', command)
+        self.assertEqual('/echo', path)
+        self.assertEqual('HTTP/1.1', version)
+        self.assertEqual(3, len(headers))
+        self.assertEqual('server.example.com', headers['Host'])
+        self.assertEqual('http://example.com', headers['Origin'])
+
+
+class MuxHandlerTest(unittest.TestCase):
+
+    def test_add_channel(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=3, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=3,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=3, message='World'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=3, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual([], dispatcher.channel_events[1].messages)
+        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
+        self.assertEqual(['World'], dispatcher.channel_events[3].messages)
+        # Channel 2
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('Hello', messages[0])
+        # Channel 3
+        messages = request.connection.get_written_messages(3)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('World', messages[0])
+        control_blocks = request.connection.get_written_control_blocks()
+        # There should be 8 control blocks:
+        #   - 1 NewChannelSlot
+        #   - 2 AddChannelResponses for channel id 2 and 3
+        #   - 6 FlowControls for channel id 1 (initialize), 'Hello', 'World',
+        #     and 3 'Goodbye's
+        self.assertEqual(9, len(control_blocks))
+
+    def test_physical_connection_write_failure(self):
+        # Use _FailOnWriteConnection.
+        request = _create_mock_request(connection=_FailOnWriteConnection())
+
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        # Let the worker echo back 'Hello'. It causes _FailOnWriteConnection
+        # raising an exception.
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Hello'))
+
+        # Let the worker exit. This will be unnecessary when
+        # _LogicalConnection.write() is changed to throw an exception if
+        # woke up by on_writer_done.
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        # All threads should be done.
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+    def test_send_blocked(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # On receiving this 'Hello', the server tries to echo back 'Hello',
+        # but it will be blocked since there's no send quota available for the
+        # channel 2.
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+
+        # Wait until the worker is blocked due to send quota shortage.
+        time.sleep(1)
+
+        # Close the channel 2. The worker should be notified of the end of
+        # writer thread and stop waiting for send quota to be replenished.
+        drop_channel = _create_drop_channel_frame(channel_id=2)
+
+        request.connection.put_bytes(drop_channel)
+
+        # Make sure the channel 1 is also closed.
+        drop_channel = _create_drop_channel_frame(channel_id=1)
+        request.connection.put_bytes(drop_channel)
+
+        # All threads should be done.
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+    def test_add_channel_delta_encoding(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        delta = 'GET /echo HTTP/1.1\r\n\r\n'
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=1, encoded_handshake=delta)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('Hello', messages[0])
+
+    def test_add_channel_delta_encoding_override(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        # Override Sec-WebSocket-Protocol.
+        delta = ('GET /echo HTTP/1.1\r\n'
+                 'Sec-WebSocket-Protocol: x-foo\r\n'
+                 '\r\n')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=1, encoded_handshake=delta)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('Hello', messages[0])
+        self.assertEqual('x-foo',
+                         dispatcher.channel_events[2].request.ws_protocol)
+
+    def test_add_channel_delta_after_identity(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+        # Sec-WebSocket-Protocol is different from client's opening handshake
+        # of the physical connection.
+        # TODO(bashi): Remove Upgrade, Connection, Sec-WebSocket-Key and
+        # Sec-WebSocket-Version.
+        encoded_handshake = (
+            'GET /echo HTTP/1.1\r\n'
+            'Host: server.example.com\r\n'
+            'Sec-WebSocket-Protocol: x-foo\r\n'
+            'Connection: Upgrade\r\n'
+            'Origin: http://example.com\r\n'
+            '\r\n')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        delta = 'GET /echo HTTP/1.1\r\n\r\n'
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=3, encoding=1, encoded_handshake=delta)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=3,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=3, message='World'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=3, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual([], dispatcher.channel_events[1].messages)
+        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
+        self.assertEqual(['World'], dispatcher.channel_events[3].messages)
+        # Channel 2
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('Hello', messages[0])
+        # Channel 3
+        messages = request.connection.get_written_messages(3)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('World', messages[0])
+        # Handshake base should be updated.
+        self.assertEqual(
+            'x-foo',
+            mux_handler._handshake_base._headers['Sec-WebSocket-Protocol'])
+
+    def test_add_channel_delta_remove_header(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+        # Override handshake delta base.
+        encoded_handshake = (
+            'GET /echo HTTP/1.1\r\n'
+            'Host: server.example.com\r\n'
+            'Sec-WebSocket-Protocol: x-foo\r\n'
+            'Connection: Upgrade\r\n'
+            'Origin: http://example.com\r\n'
+            '\r\n')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        # Remove Sec-WebSocket-Protocol header.
+        delta = ('GET /echo HTTP/1.1\r\n'
+                 'Sec-WebSocket-Protocol:'
+                 '\r\n')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=3, encoding=1, encoded_handshake=delta)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=3,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=3, message='World'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=3, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual([], dispatcher.channel_events[1].messages)
+        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
+        self.assertEqual(['World'], dispatcher.channel_events[3].messages)
+        # Channel 2
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('Hello', messages[0])
+        # Channel 3
+        messages = request.connection.get_written_messages(3)
+        self.assertEqual(1, len(messages))
+        self.assertEqual('World', messages[0])
+        self.assertEqual(
+            'x-foo',
+            dispatcher.channel_events[2].request.ws_protocol)
+        self.assertEqual(
+            None,
+            dispatcher.channel_events[3].request.ws_protocol)
+
+    def test_add_channel_delta_encoding_permessage_deflate(self):
+        # Enable permessage deflate extension on the implicitly opened channel.
+        extensions = common.parse_extensions(
+            common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_mock_request(
+            logical_channel_extensions=extensions)
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        delta = 'GET /echo HTTP/1.1\r\n\r\n'
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=1, encoded_handshake=delta)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=20)
+        request.connection.put_bytes(flow_control)
+
+        # Send compressed 'Hello' on logical channel 1 and 2.
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message=compressed_hello,
+                                  rsv1=True))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message=compressed_hello,
+                                  rsv1=True))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual(['Hello'], dispatcher.channel_events[1].messages)
+        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
+        # Written 'Hello's should be compressed.
+        messages = request.connection.get_written_messages(1)
+        self.assertEqual(1, len(messages))
+        self.assertEqual(compressed_hello, messages[0])
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(1, len(messages))
+        self.assertEqual(compressed_hello, messages[0])
+
+    def test_add_channel_delta_encoding_remove_extensions(self):
+        # Enable permessage deflate extension on the implicitly opened channel.
+        extensions = common.parse_extensions(
+            common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_mock_request(
+            logical_channel_extensions=extensions)
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        # Remove permessage deflate extension.
+        delta = ('GET /echo HTTP/1.1\r\n'
+                 'Sec-WebSocket-Extensions:\r\n'
+                 '\r\n')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=1, encoded_handshake=delta)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=20)
+        request.connection.put_bytes(flow_control)
+
+        # Send compressed message on logical channel 2. The message should
+        # be rejected (since rsv1 is set).
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('Hello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message=compressed_hello,
+                                  rsv1=True))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_NORMAL_CLOSURE, drop_channel.drop_code)
+        self.assertEqual(2, drop_channel.channel_id)
+        # UnsupportedFrameException should be raised on logical channel 2.
+        self.assertTrue(isinstance(dispatcher.channel_events[2].exception,
+                                   UnsupportedFrameException))
+
+    def test_add_channel_invalid_encoding(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=3,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_UNKNOWN_REQUEST_ENCODING,
+                         drop_channel.drop_code)
+        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
+                         request.connection.server_close_code)
+
+    def test_add_channel_incomplete_handshake(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        incomplete_encoded_handshake = 'GET /echo HTTP/1.1'
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=incomplete_encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertTrue(1 in dispatcher.channel_events)
+        self.assertTrue(not 2 in dispatcher.channel_events)
+
+    def test_add_channel_duplicate_channel_id(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_CHANNEL_ALREADY_EXISTS,
+                         drop_channel.drop_code)
+        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
+                         request.connection.server_close_code)
+
+    def test_receive_drop_channel(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        drop_channel = _create_drop_channel_frame(channel_id=2)
+        request.connection.put_bytes(drop_channel)
+
+        # Terminate implicitly opened channel.
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        exception = dispatcher.channel_events[2].exception
+        self.assertTrue(exception.__class__ == ConnectionTerminatedException)
+
+    def test_receive_ping_frame(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=13)
+        request.connection.put_bytes(flow_control)
+
+        ping_frame = _create_logical_frame(channel_id=2,
+                                           message='Hello World!',
+                                           opcode=common.OPCODE_PING)
+        request.connection.put_bytes(ping_frame)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PONG, messages[0]['opcode'])
+        self.assertEqual('Hello World!', messages[0]['message'])
+
+    def test_receive_fragmented_ping(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=13)
+        request.connection.put_bytes(flow_control)
+
+        # Send a ping with message 'Hello world!' in two fragmented frames.
+        ping_frame1 = _create_logical_frame(channel_id=2,
+                                            message='Hello ',
+                                            fin=False,
+                                            opcode=common.OPCODE_PING)
+        request.connection.put_bytes(ping_frame1)
+        ping_frame2 = _create_logical_frame(channel_id=2,
+                                            message='World!',
+                                            fin=True,
+                                            opcode=common.OPCODE_CONTINUATION)
+        request.connection.put_bytes(ping_frame2)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PONG, messages[0]['opcode'])
+        self.assertEqual('Hello World!', messages[0]['message'])
+
+    def test_receive_fragmented_ping_while_receiving_fragmented_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=19)
+        request.connection.put_bytes(flow_control)
+
+        # Send a fragmented frame of message 'Hello '.
+        hello = _create_logical_frame(channel_id=2,
+                                      message='Hello ',
+                                      fin=False)
+        request.connection.put_bytes(hello)
+
+        # Before sending the last fragmented frame of the message, send a
+        # fragmented ping.
+        ping1 = _create_logical_frame(channel_id=2,
+                                      message='Pi',
+                                      fin=False,
+                                      opcode=common.OPCODE_PING)
+        request.connection.put_bytes(ping1)
+        ping2 = _create_logical_frame(channel_id=2,
+                                      message='ng!',
+                                      fin=True,
+                                      opcode=common.OPCODE_CONTINUATION)
+        request.connection.put_bytes(ping2)
+
+        # Send the last fragmented frame of the message.
+        world = _create_logical_frame(channel_id=2,
+                                      message='World!',
+                                      fin=True,
+                                      opcode=common.OPCODE_CONTINUATION)
+        request.connection.put_bytes(world)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(['Hello World!'], messages)
+        control_messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode'])
+        self.assertEqual('Ping!', control_messages[0]['message'])
+
+    def test_receive_two_ping_while_receiving_fragmented_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=25)
+        request.connection.put_bytes(flow_control)
+
+        # Send a fragmented frame of message 'Hello '.
+        hello = _create_logical_frame(channel_id=2,
+                                      message='Hello ',
+                                      fin=False)
+        request.connection.put_bytes(hello)
+
+        # Before sending the last fragmented frame of the message, send a
+        # fragmented ping and a non-fragmented ping.
+        ping1 = _create_logical_frame(channel_id=2,
+                                      message='Pi',
+                                      fin=False,
+                                      opcode=common.OPCODE_PING)
+        request.connection.put_bytes(ping1)
+        ping2 = _create_logical_frame(channel_id=2,
+                                      message='ng!',
+                                      fin=True,
+                                      opcode=common.OPCODE_CONTINUATION)
+        request.connection.put_bytes(ping2)
+        ping3 = _create_logical_frame(channel_id=2,
+                                      message='Pong!',
+                                      fin=True,
+                                      opcode=common.OPCODE_PING)
+        request.connection.put_bytes(ping3)
+
+        # Send the last fragmented frame of the message.
+        world = _create_logical_frame(channel_id=2,
+                                      message='World!',
+                                      fin=True,
+                                      opcode=common.OPCODE_CONTINUATION)
+        request.connection.put_bytes(world)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(['Hello World!'], messages)
+        control_messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode'])
+        self.assertEqual('Ping!', control_messages[0]['message'])
+        self.assertEqual(common.OPCODE_PONG, control_messages[1]['opcode'])
+        self.assertEqual('Pong!', control_messages[1]['message'])
+
+    def test_receive_message_while_receiving_fragmented_ping(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                replenished_quota=19)
+        request.connection.put_bytes(flow_control)
+
+        # Send a fragmented ping.
+        ping1 = _create_logical_frame(channel_id=2,
+                                      message='Pi',
+                                      fin=False,
+                                      opcode=common.OPCODE_PING)
+        request.connection.put_bytes(ping1)
+
+        # Before sending the last fragmented ping, send a message.
+        # The logical channel (2) should be dropped.
+        message = _create_logical_frame(channel_id=2,
+                                        message='Hello world!',
+                                        fin=True)
+        request.connection.put_bytes(message)
+
+        # Send the last fragmented frame of the message.
+        ping2 = _create_logical_frame(channel_id=2,
+                                      message='ng!',
+                                      fin=True,
+                                      opcode=common.OPCODE_CONTINUATION)
+        request.connection.put_bytes(ping2)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(2, drop_channel.channel_id)
+        # No message should be sent on channel 2.
+        self.assertRaises(KeyError,
+                          request.connection.get_written_messages,
+                          2)
+        self.assertRaises(KeyError,
+                          request.connection.get_written_control_messages,
+                          2)
+
+    def test_send_ping(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/ping')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PING, messages[0]['opcode'])
+        self.assertEqual('Ping!', messages[0]['message'])
+
+    def test_send_fragmented_ping(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/ping')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Replenish 3 bytes. This isn't enough to send the whole ping frame
+        # because the frame will have 5 bytes message('Ping!'). The frame
+        # should be fragmented.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=3)
+        request.connection.put_bytes(flow_control)
+
+        # Wait until the worker is blocked due to send quota shortage.
+        time.sleep(1)
+
+        # Replenish remaining 2 + 1 bytes (including extra cost).
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=3)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PING, messages[0]['opcode'])
+        self.assertEqual('Ping!', messages[0]['message'])
+
+    def test_send_fragmented_ping_while_sending_fragmented_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(
+            path='/ping_while_hello_world')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Application will send:
+        # - text message 'Hello ' with fin=0
+        # - ping with 'Ping!' message
+        # - text message 'World!' with fin=1
+        # Replenish (6 + 1) + (2 + 1) bytes so that the ping will be
+        # fragmented on the logical channel.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=10)
+        request.connection.put_bytes(flow_control)
+
+        time.sleep(1)
+
+        # Replenish remaining 3 + 6 bytes.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=9)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(['Hello World!'], messages)
+        control_messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode'])
+        self.assertEqual('Ping!', control_messages[0]['message'])
+
+    def test_send_fragmented_two_ping_while_sending_fragmented_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(
+            path='/two_ping_while_hello_world')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Application will send:
+        # - text message 'Hello ' with fin=0
+        # - ping with 'Ping!' message
+        # - ping with 'Pong!' message
+        # - text message 'World!' with fin=1
+        # Replenish (6 + 1) + (2 + 1) bytes so that the first ping will be
+        # fragmented on the logical channel.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=10)
+        request.connection.put_bytes(flow_control)
+
+        time.sleep(1)
+
+        # Replenish remaining 3 + (5 + 1) + 6 bytes. The second ping won't
+        # be fragmented on the logical channel.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=15)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(['Hello World!'], messages)
+        control_messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode'])
+        self.assertEqual('Ping!', control_messages[0]['message'])
+        self.assertEqual(common.OPCODE_PING, control_messages[1]['opcode'])
+        self.assertEqual('Pong!', control_messages[1]['message'])
+
+    def test_send_drop_channel(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        # DropChannel for channel id 1 which doesn't have reason.
+        frame = create_binary_frame('\x00\x60\x01\x00', mask=True)
+        request.connection.put_bytes(frame)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_ACKNOWLEDGED,
+                         drop_channel.drop_code)
+        self.assertEqual(1, drop_channel.channel_id)
+
+    def test_two_flow_control(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Replenish 5 bytes.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=5)
+        request.connection.put_bytes(flow_control)
+
+        # Send 10 bytes. The server will try echo back 10 bytes.
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='HelloWorld'))
+
+        # Replenish 5 + 1 (per-message extra cost) bytes.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(['HelloWorld'], messages)
+        received_flow_controls = [
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2]
+        # Replenishment for 'HelloWorld' + 1
+        self.assertEqual(11, received_flow_controls[0].send_quota)
+        # Replenishment for 'Goodbye' + 1
+        self.assertEqual(8, received_flow_controls[1].send_quota)
+
+    def test_no_send_quota_on_server(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='HelloWorld'))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        # Just wait for 1 sec so that the server attempts to echo back
+        # 'HelloWorld'.
+        self.assertFalse(mux_handler.wait_until_done(timeout=1))
+
+        # No message should be sent on channel 2.
+        self.assertRaises(KeyError,
+                          request.connection.get_written_messages,
+                          2)
+
+    def test_no_send_quota_on_server_for_permessage_extra_cost(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+        # Replenish only len('World') bytes.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=5)
+        request.connection.put_bytes(flow_control)
+        # Server should not callback for this message.
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='World'))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        # Just wait for 1 sec so that the server attempts to echo back
+        # 'World'.
+        self.assertFalse(mux_handler.wait_until_done(timeout=1))
+
+        # Only one message should be sent on channel 2.
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(['Hello'], messages)
+
+    def test_quota_violation_by_client(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 0)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='HelloWorld'))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        control_blocks = request.connection.get_written_control_blocks()
+        self.assertEqual(5, len(control_blocks))
+        drop_channel = next(
+            b for b in control_blocks
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION,
+                         drop_channel.drop_code)
+
+    def test_consume_quota_empty_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        # Client has 1 byte quota.
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 1)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=2)
+        request.connection.put_bytes(flow_control)
+        # Send an empty message. Pywebsocket always replenishes 1 byte quota
+        # for empty message
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message=''))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        # This message violates quota on channel id 2.
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual(1, len(dispatcher.channel_events[2].messages))
+        self.assertEqual('', dispatcher.channel_events[2].messages[0])
+
+        received_flow_controls = [
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2]
+        self.assertEqual(1, len(received_flow_controls))
+        self.assertEqual(1, received_flow_controls[0].send_quota)
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(2, drop_channel.channel_id)
+        self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION,
+                         drop_channel.drop_code)
+
+    def test_consume_quota_fragmented_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        # Client has len('Hello') + len('Goodbye') + 2 bytes quota.
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 14)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='He', fin=False,
+                                  opcode=common.OPCODE_TEXT))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='llo', fin=True,
+                                  opcode=common.OPCODE_CONTINUATION))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(['Hello'], messages)
+
+    def test_fragmented_control_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/ping')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Replenish total 6 bytes in 3 FlowControls.
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=1)
+        request.connection.put_bytes(flow_control)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=2)
+        request.connection.put_bytes(flow_control)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=3)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        messages = request.connection.get_written_control_messages(2)
+        self.assertEqual(common.OPCODE_PING, messages[0]['opcode'])
+        self.assertEqual('Ping!', messages[0]['message'])
+
+    def test_channel_slot_violation_by_client(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(slots=1,
+                                      send_quota=mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Hello'))
+
+        # This request should be rejected.
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=3, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+        flow_control = _create_flow_control_frame(channel_id=3,
+                                                  replenished_quota=6)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=3, message='Hello'))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual([], dispatcher.channel_events[1].messages)
+        self.assertEqual(['Hello'], dispatcher.channel_events[2].messages)
+        self.assertFalse(dispatcher.channel_events.has_key(3))
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(3, drop_channel.channel_id)
+        self.assertEqual(mux._DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION,
+                         drop_channel.drop_code)
+
+    def test_quota_overflow_by_client(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(slots=1,
+                                      send_quota=mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+        # Replenish 0x7FFFFFFFFFFFFFFF bytes twice.
+        flow_control = _create_flow_control_frame(
+            channel_id=2,
+            replenished_quota=0x7FFFFFFFFFFFFFFF)
+        request.connection.put_bytes(flow_control)
+        request.connection.put_bytes(flow_control)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(2, drop_channel.channel_id)
+        self.assertEqual(mux._DROP_CODE_SEND_QUOTA_OVERFLOW,
+                         drop_channel.drop_code)
+
+    def test_invalid_encapsulated_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        first_byte = (mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5)
+        block = (chr(first_byte) +
+                 mux._encode_channel_id(1) +
+                 mux._encode_number(0))
+        payload = mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + block
+        text_frame = create_binary_frame(payload, opcode=common.OPCODE_TEXT,
+                                         mask=True)
+        request.connection.put_bytes(text_frame)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_INVALID_ENCAPSULATING_MESSAGE,
+                         drop_channel.drop_code)
+        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
+                         request.connection.server_close_code)
+
+    def test_channel_id_truncated(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        # The last byte of the channel id is missing.
+        frame = create_binary_frame('\x80', mask=True)
+        request.connection.put_bytes(frame)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_CHANNEL_ID_TRUNCATED,
+                         drop_channel.drop_code)
+        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
+                         request.connection.server_close_code)
+
+    def test_inner_frame_truncated(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        # Just contain channel id 1.
+        frame = create_binary_frame('\x01', mask=True)
+        request.connection.put_bytes(frame)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED,
+                         drop_channel.drop_code)
+        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
+                         request.connection.server_close_code)
+
+    def test_unknown_mux_opcode(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        # Undefined opcode 5
+        frame = create_binary_frame('\x00\xa0', mask=True)
+        request.connection.put_bytes(frame)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_UNKNOWN_MUX_OPCODE,
+                         drop_channel.drop_code)
+        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
+                         request.connection.server_close_code)
+
+    def test_invalid_mux_control_block(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+
+        # DropChannel contains 1 byte reason
+        frame = create_binary_frame('\x00\x60\x00\x01\x00', mask=True)
+        request.connection.put_bytes(frame)
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channel = next(
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL)
+        self.assertEqual(mux._DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
+                         drop_channel.drop_code)
+        self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR,
+                         request.connection.server_close_code)
+
+    def test_permessage_deflate(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        # Enable permessage deflate extension on logical channel 2.
+        extensions = common.PERMESSAGE_DEFLATE_EXTENSION
+        encoded_handshake = _create_request_header(path='/echo',
+                                                   extensions=extensions)
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        flow_control = _create_flow_control_frame(channel_id=2,
+                                                  replenished_quota=20)
+        request.connection.put_bytes(flow_control)
+
+        # Send compressed 'Hello' twice.
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello1 = compress.compress('Hello')
+        compressed_hello1 += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello1 = compressed_hello1[:-4]
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message=compressed_hello1,
+                                  rsv1=True))
+        compressed_hello2 = compress.compress('Hello')
+        compressed_hello2 += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello2 = compressed_hello2[:-4]
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message=compressed_hello2,
+                                  rsv1=True))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=2, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual(['Hello', 'Hello'],
+                         dispatcher.channel_events[2].messages)
+        # Written 'Hello's should be compressed.
+        messages = request.connection.get_written_messages(2)
+        self.assertEqual(2, len(messages))
+        self.assertEqual(compressed_hello1, messages[0])
+        self.assertEqual(compressed_hello2, messages[1])
+
+
+    def test_permessage_deflate_fragmented_message(self):
+        extensions = common.parse_extensions(
+            common.PERMESSAGE_DEFLATE_EXTENSION)
+        request = _create_mock_request(
+            logical_channel_extensions=extensions)
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        # Send compressed 'HelloHelloHello' as fragmented message.
+        compress = zlib.compressobj(
+            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS)
+        compressed_hello = compress.compress('HelloHelloHello')
+        compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH)
+        compressed_hello = compressed_hello[:-4]
+
+        m = len(compressed_hello) / 2
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1,
+                                  message=compressed_hello[:m],
+                                  fin=False, rsv1=True,
+                                  opcode=common.OPCODE_TEXT))
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1,
+                                  message=compressed_hello[m:],
+                                  fin=True, rsv1=False,
+                                  opcode=common.OPCODE_CONTINUATION))
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        self.assertEqual(['HelloHelloHello'],
+                         dispatcher.channel_events[1].messages)
+        messages = request.connection.get_written_messages(1)
+        self.assertEqual(1, len(messages))
+        self.assertEqual(compressed_hello, messages[0])
+
+    def test_receive_bad_fragmented_message(self):
+        request = _create_mock_request()
+        dispatcher = _MuxMockDispatcher()
+        mux_handler = mux._MuxHandler(request, dispatcher)
+        mux_handler.start()
+        mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS,
+                                      mux._INITIAL_QUOTA_FOR_CLIENT)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=2, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Send a frame with fin=False, and then send a frame with
+        # opcode=TEXT (not CONTINUATION). Logical channel 2 should be dropped.
+        frame1 = _create_logical_frame(channel_id=2,
+                                       message='Hello ',
+                                       fin=False,
+                                       opcode=common.OPCODE_TEXT)
+        request.connection.put_bytes(frame1)
+        frame2 = _create_logical_frame(channel_id=2,
+                                       message='World!',
+                                       fin=True,
+                                       opcode=common.OPCODE_TEXT)
+        request.connection.put_bytes(frame2)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=3, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Send a frame with opcode=CONTINUATION without a preceding frame
+        # the fin of which is not set. Logical channel 3 should be dropped.
+        frame3 = _create_logical_frame(channel_id=3,
+                                       message='Hello',
+                                       fin=True,
+                                       opcode=common.OPCODE_CONTINUATION)
+        request.connection.put_bytes(frame3)
+
+        encoded_handshake = _create_request_header(path='/echo')
+        add_channel_request = _create_add_channel_request_frame(
+            channel_id=4, encoding=0,
+            encoded_handshake=encoded_handshake)
+        request.connection.put_bytes(add_channel_request)
+
+        # Send a frame with opcode=PING and fin=False, and then send a frame
+        # with opcode=TEXT (not CONTINUATION). Logical channel 4 should be
+        # dropped.
+        frame4 = _create_logical_frame(channel_id=4,
+                                       message='Ping',
+                                       fin=False,
+                                       opcode=common.OPCODE_PING)
+        request.connection.put_bytes(frame4)
+        frame5 = _create_logical_frame(channel_id=4,
+                                       message='Hello',
+                                       fin=True,
+                                       opcode=common.OPCODE_TEXT)
+        request.connection.put_bytes(frame5)
+
+        request.connection.put_bytes(
+            _create_logical_frame(channel_id=1, message='Goodbye'))
+
+        self.assertTrue(mux_handler.wait_until_done(timeout=2))
+
+        drop_channels = [
+            b for b in request.connection.get_written_control_blocks()
+            if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL]
+        self.assertEqual(3, len(drop_channels))
+        for d in drop_channels:
+            self.assertEqual(mux._DROP_CODE_BAD_FRAGMENTATION,
+                             d.drop_code)
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/tools/pywebsocket/src/test/test_stream.py b/tools/pywebsocket/test/test_stream.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_stream.py
rename to tools/pywebsocket/test/test_stream.py
diff --git a/tools/pywebsocket/src/test/test_stream_hixie75.py b/tools/pywebsocket/test/test_stream_hixie75.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_stream_hixie75.py
rename to tools/pywebsocket/test/test_stream_hixie75.py
diff --git a/tools/pywebsocket/src/test/test_util.py b/tools/pywebsocket/test/test_util.py
similarity index 100%
rename from tools/pywebsocket/src/test/test_util.py
rename to tools/pywebsocket/test/test_util.py
diff --git a/tools/pywebsocket/src/test/testdata/README b/tools/pywebsocket/test/testdata/README
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/README
rename to tools/pywebsocket/test/testdata/README
diff --git a/tools/pywebsocket/src/test/testdata/handlers/abort_by_user_wsh.py b/tools/pywebsocket/test/testdata/handlers/abort_by_user_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/abort_by_user_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/abort_by_user_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/blank_wsh.py b/tools/pywebsocket/test/testdata/handlers/blank_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/blank_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/blank_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/origin_check_wsh.py b/tools/pywebsocket/test/testdata/handlers/origin_check_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/origin_check_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/origin_check_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/sub/exception_in_transfer_wsh.py b/tools/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/sub/exception_in_transfer_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/sub/no_wsh_at_the_end.py b/tools/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/sub/no_wsh_at_the_end.py
rename to tools/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/sub/non_callable_wsh.py b/tools/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/sub/non_callable_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/sub/plain_wsh.py b/tools/pywebsocket/test/testdata/handlers/sub/plain_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/sub/plain_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/sub/plain_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py b/tools/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py b/tools/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py
rename to tools/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py
diff --git a/tools/pywebsocket/src/test/testdata/hello.pl b/tools/pywebsocket/test/testdata/hello.pl
similarity index 100%
rename from tools/pywebsocket/src/test/testdata/hello.pl
rename to tools/pywebsocket/test/testdata/hello.pl
diff --git a/tools/runner/report.py b/tools/runner/report.py
index f4b3fa7..952df08 100644
--- a/tools/runner/report.py
+++ b/tools/runner/report.py
@@ -1,3 +1,5 @@
+# flake8: noqa
+
 from __future__ import print_function
 
 import argparse
diff --git a/tools/serve/serve.py b/tools/serve/serve.py
index 1faea9c..0fd5f90 100644
--- a/tools/serve/serve.py
+++ b/tools/serve/serve.py
@@ -5,8 +5,9 @@
 import abc
 import argparse
 import json
+import logging
 import os
-import re
+import platform
 import socket
 import sys
 import threading
@@ -17,16 +18,19 @@
 from collections import defaultdict, OrderedDict
 from multiprocessing import Process, Event
 
-from ..localpaths import repo_root
+from localpaths import repo_root
+from six.moves import reload_module
 
-import sslutils
-from manifest.sourcefile import read_script_metadata, js_meta_re
+from manifest.sourcefile import read_script_metadata, js_meta_re, parse_variants
 from wptserve import server as wptserve, handlers
 from wptserve import stash
+from wptserve import config
 from wptserve.logger import set_logger
 from wptserve.handlers import filesystem_path, wrap_pipeline
+from wptserve.utils import get_port, HTTPException
 from mod_pywebsocket import standalone as pywebsocket
 
+
 def replace_end(s, old, new):
     """
     Given a string `s` that ends with `old`, replace that occurrence of `old`
@@ -54,9 +58,14 @@
         for header_name, header_value in self.headers:
             response.headers.set(header_name, header_value)
 
+        self.check_exposure(request)
+
         path = self._get_path(request.url_parts.path, True)
+        query = request.url_parts.query
+        if query:
+            query = "?" + query
         meta = "\n".join(self._get_meta(request))
-        response.content = self.wrapper % {"meta": meta, "path": path}
+        response.content = self.wrapper % {"meta": meta, "path": path, "query": query}
         wrap_pipeline(path, request, response)
 
     def _get_path(self, path, resource_path):
@@ -83,18 +92,27 @@
                 path = replace_end(path, src, dest)
         return path
 
-    def _get_meta(self, request):
-        """Get an iterator over strings to inject into the wrapper document
-        based on //META comments in the associated js file.
+    def _get_metadata(self, request):
+        """Get an iterator over script metadata based on //META comments in the
+        associated js file.
 
         :param request: The Request being processed.
         """
         path = self._get_path(filesystem_path(self.base_path, request, self.url_base), False)
         with open(path, "rb") as f:
             for key, value in read_script_metadata(f, js_meta_re):
-                replacement = self._meta_replacement(key, value)
-                if replacement:
-                    yield replacement
+                yield key, value
+
+    def _get_meta(self, request):
+        """Get an iterator over strings to inject into the wrapper document
+        based on //META comments in the associated js file.
+
+        :param request: The Request being processed.
+        """
+        for key, value in self._get_metadata(request):
+            replacement = self._meta_replacement(key, value)
+            if replacement:
+                yield replacement
 
     @abc.abstractproperty
     def path_replace(self):
@@ -116,8 +134,27 @@
         # a specific metadata key: value pair.
         pass
 
+    @abc.abstractmethod
+    def check_exposure(self, request):
+        # Raise an exception if this handler shouldn't be exposed after all.
+        pass
+
 
 class HtmlWrapperHandler(WrapperHandler):
+    global_type = None
+
+    def check_exposure(self, request):
+        if self.global_type:
+            globals = b""
+            for (key, value) in self._get_metadata(request):
+                if key == b"global":
+                    globals = value
+                    break
+
+            if self.global_type not in parse_variants(globals):
+                raise HTTPException(404, "This test cannot be loaded in %s mode" %
+                                    self.global_type)
+
     def _meta_replacement(self, key, value):
         if key == b"timeout":
             if value == b"long":
@@ -129,6 +166,7 @@
 
 
 class WorkersHandler(HtmlWrapperHandler):
+    global_type = b"dedicatedworker"
     path_replace = [(".any.worker.html", ".any.js", ".any.worker.js"),
                     (".worker.html", ".worker.js")]
     wrapper = """<!doctype html>
@@ -138,7 +176,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <div id=log></div>
 <script>
-fetch_tests_from_worker(new Worker("%(path)s"));
+fetch_tests_from_worker(new Worker("%(path)s%(query)s"));
 </script>
 """
 
@@ -156,6 +194,7 @@
 
 
 class AnyHtmlHandler(HtmlWrapperHandler):
+    global_type = b"window"
     path_replace = [(".any.html", ".any.js")]
     wrapper = """<!doctype html>
 <meta charset=utf-8>
@@ -173,6 +212,42 @@
 """
 
 
+class SharedWorkersHandler(HtmlWrapperHandler):
+    global_type = b"sharedworker"
+    path_replace = [(".any.sharedworker.html", ".any.js", ".any.worker.js")]
+    wrapper = """<!doctype html>
+<meta charset=utf-8>
+%(meta)s
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+fetch_tests_from_worker(new SharedWorker("%(path)s%(query)s"));
+</script>
+"""
+
+
+class ServiceWorkersHandler(HtmlWrapperHandler):
+    global_type = b"serviceworker"
+    path_replace = [(".https.any.serviceworker.html", ".any.js", ".any.worker.js")]
+    wrapper = """<!doctype html>
+<meta charset=utf-8>
+%(meta)s
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+(async function() {
+  const scope = 'does/not/exist';
+  let reg = await navigator.serviceWorker.getRegistration(scope);
+  if (reg) await reg.unregister();
+  reg = await navigator.serviceWorker.register("%(path)s%(query)s", {scope});
+  fetch_tests_from_worker(reg.installing);
+})();
+</script>
+"""
+
+
 class AnyWorkerHandler(WrapperHandler):
     headers = [('Content-Type', 'text/javascript')]
     path_replace = [(".any.worker.js", ".any.js")]
@@ -187,8 +262,6 @@
 """
 
     def _meta_replacement(self, key, value):
-        if key == b"timeout":
-            return None
         if key == b"script":
             attribute = value.decode('utf-8').replace("\\", "\\\\").replace('"', '\\"')
             return 'importScripts("%s")' % attribute
@@ -197,12 +270,6 @@
 
 rewrites = [("GET", "/resources/WebIDLParser.js", "/resources/webidl2/lib/webidl2.js")]
 
-subdomains = [u"www",
-              u"www1",
-              u"www2",
-              u"天気の良い日",
-              u"élève"]
-
 class RoutesBuilder(object):
     def __init__(self):
         self.forbidden_override = [("GET", "/tools/runner/*", handlers.file_handler),
@@ -232,8 +299,10 @@
     def add_handler(self, method, route, handler):
         self.extra.append((str(method), str(route), handler))
 
-    def add_static(self, path, format_args, content_type, route):
-        handler = handlers.StaticHandler(path, format_args, content_type)
+    def add_static(self, path, format_args, content_type, route, headers=None):
+        if headers is None:
+            headers = {}
+        handler = handlers.StaticHandler(path, format_args, content_type, **headers)
         self.add_handler(b"GET", str(route), handler)
 
     def add_mount_point(self, url_base, path):
@@ -245,6 +314,8 @@
             ("GET", "*.worker.html", WorkersHandler),
             ("GET", "*.window.html", WindowHandler),
             ("GET", "*.any.html", AnyHtmlHandler),
+            ("GET", "*.any.sharedworker.html", SharedWorkersHandler),
+            ("GET", "*.https.any.serviceworker.html", ServiceWorkersHandler),
             ("GET", "*.any.worker.js", AnyWorkerHandler),
             ("GET", "*.asis", handlers.AsIsHandler),
             ("*", "*.py", handlers.PythonScriptHandler),
@@ -278,124 +349,25 @@
     return builder.get_routes()
 
 
-def setup_logger(level):
-    import logging
-    global logger
-    logger = logging.getLogger("web-platform-tests")
-    logger.setLevel(getattr(logging, level.upper()))
-    set_logger(logger)
-
-
-def open_socket(port):
-    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    if port != 0:
-        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-    sock.bind(('127.0.0.1', port))
-    sock.listen(5)
-    return sock
-
-def bad_port(port):
-    """
-    Bad port as per https://fetch.spec.whatwg.org/#port-blocking
-    """
-    return port in [
-        1,     # tcpmux
-        7,     # echo
-        9,     # discard
-        11,    # systat
-        13,    # daytime
-        15,    # netstat
-        17,    # qotd
-        19,    # chargen
-        20,    # ftp-data
-        21,    # ftp
-        22,    # ssh
-        23,    # telnet
-        25,    # smtp
-        37,    # time
-        42,    # name
-        43,    # nicname
-        53,    # domain
-        77,    # priv-rjs
-        79,    # finger
-        87,    # ttylink
-        95,    # supdup
-        101,   # hostriame
-        102,   # iso-tsap
-        103,   # gppitnp
-        104,   # acr-nema
-        109,   # pop2
-        110,   # pop3
-        111,   # sunrpc
-        113,   # auth
-        115,   # sftp
-        117,   # uucp-path
-        119,   # nntp
-        123,   # ntp
-        135,   # loc-srv / epmap
-        139,   # netbios
-        143,   # imap2
-        179,   # bgp
-        389,   # ldap
-        465,   # smtp+ssl
-        512,   # print / exec
-        513,   # login
-        514,   # shell
-        515,   # printer
-        526,   # tempo
-        530,   # courier
-        531,   # chat
-        532,   # netnews
-        540,   # uucp
-        556,   # remotefs
-        563,   # nntp+ssl
-        587,   # smtp
-        601,   # syslog-conn
-        636,   # ldap+ssl
-        993,   # imap+ssl
-        995,   # pop3+ssl
-        2049,  # nfs
-        3659,  # apple-sasl
-        4045,  # lockd
-        6000,  # x11
-        6665,  # irc (alternate)
-        6666,  # irc (alternate)
-        6667,  # irc (default)
-        6668,  # irc (alternate)
-        6669,  # irc (alternate)
-    ]
-
-def get_port():
-    port = 0
-    while True:
-        free_socket = open_socket(0)
-        port = free_socket.getsockname()[1]
-        free_socket.close()
-        if not bad_port(port):
-            break
-    logger.debug("Going to use port %s" % port)
-    return port
-
-
 class ServerProc(object):
     def __init__(self):
         self.proc = None
         self.daemon = None
         self.stop = Event()
 
-    def start(self, init_func, host, port, paths, routes, bind_hostname, external_config,
+    def start(self, init_func, host, port, paths, routes, bind_address, config,
               ssl_config, **kwargs):
         self.proc = Process(target=self.create_daemon,
-                            args=(init_func, host, port, paths, routes, bind_hostname,
-                                  external_config, ssl_config),
+                            args=(init_func, host, port, paths, routes, bind_address,
+                                  config, ssl_config),
                             kwargs=kwargs)
         self.proc.daemon = True
         self.proc.start()
 
-    def create_daemon(self, init_func, host, port, paths, routes, bind_hostname,
-                      external_config, ssl_config, **kwargs):
+    def create_daemon(self, init_func, host, port, paths, routes, bind_address,
+                      config, ssl_config, **kwargs):
         try:
-            self.daemon = init_func(host, port, paths, routes, bind_hostname, external_config,
+            self.daemon = init_func(host, port, paths, routes, bind_address, config,
                                     ssl_config, **kwargs)
         except socket.error:
             print("Socket error on port %s" % port, file=sys.stderr)
@@ -428,12 +400,18 @@
         return self.proc.is_alive()
 
 
-def check_subdomains(host, paths, bind_hostname, ssl_config, aliases):
-    port = get_port()
-    subdomains = get_subdomains(host)
+def check_subdomains(config):
+    paths = config.paths
+    bind_address = config.bind_address
+    ssl_config = config.ssl_config
+    aliases = config.aliases
+
+    host = config.server_host
+    port = get_port(host)
+    logger.debug("Going to use port %d to check subdomains" % port)
 
     wrapper = ServerProc()
-    wrapper.start(start_http_server, host, port, paths, build_routes(aliases), bind_hostname,
+    wrapper.start(start_http_server, host, port, paths, build_routes(aliases), bind_address,
                   None, ssl_config)
 
     connected = False
@@ -450,8 +428,10 @@
                         "You may need to edit /etc/hosts or similar, see README.md." % (host, port))
         sys.exit(1)
 
-    for subdomain, (punycode, host) in subdomains.iteritems():
-        domain = "%s.%s" % (punycode, host)
+    for domain in config.domains_set:
+        if domain == host:
+            continue
+
         try:
             urllib2.urlopen("http://%s:%d/" % (domain, port))
         except Exception as e:
@@ -462,13 +442,27 @@
     wrapper.wait()
 
 
-def get_subdomains(host):
-    #This assumes that the tld is ascii-only or already in punycode
-    return {subdomain: (subdomain.encode("idna"), host)
-            for subdomain in subdomains}
+def make_hosts_file(config, host):
+    rv = []
+
+    for domain in config.domains_set:
+        rv.append("%s\t%s\n" % (host, domain))
+
+    # Windows interpets the IP address 0.0.0.0 as non-existent, making it an
+    # appropriate alias for non-existent hosts. However, UNIX-like systems
+    # interpret the same address to mean any IP address, which is inappropraite
+    # for this context. These systems do not reserve any value for this
+    # purpose, so the inavailability of the domains must be taken for granted.
+    #
+    # https://github.com/w3c/web-platform-tests/issues/10560
+    if platform.uname()[0] == "Windows":
+        for not_domain in config.not_domains_set:
+            rv.append("0.0.0.0\t%s\n" % not_domain)
+
+    return "".join(rv)
 
 
-def start_servers(host, ports, paths, routes, bind_hostname, external_config, ssl_config,
+def start_servers(host, ports, paths, routes, bind_address, config, ssl_config,
                   **kwargs):
     servers = defaultdict(list)
     for scheme, ports in ports.iteritems():
@@ -483,37 +477,37 @@
                          "wss":start_wss_server}[scheme]
 
             server_proc = ServerProc()
-            server_proc.start(init_func, host, port, paths, routes, bind_hostname,
-                              external_config, ssl_config, **kwargs)
+            server_proc.start(init_func, host, port, paths, routes, bind_address,
+                              config, ssl_config, **kwargs)
             servers[scheme].append((port, server_proc))
 
     return servers
 
 
-def start_http_server(host, port, paths, routes, bind_hostname, external_config, ssl_config,
+def start_http_server(host, port, paths, routes, bind_address, config, ssl_config,
                       **kwargs):
     return wptserve.WebTestHttpd(host=host,
                                  port=port,
                                  doc_root=paths["doc_root"],
                                  routes=routes,
                                  rewrites=rewrites,
-                                 bind_hostname=bind_hostname,
-                                 config=external_config,
+                                 bind_address=bind_address,
+                                 config=config,
                                  use_ssl=False,
                                  key_file=None,
                                  certificate=None,
                                  latency=kwargs.get("latency"))
 
 
-def start_https_server(host, port, paths, routes, bind_hostname, external_config, ssl_config,
+def start_https_server(host, port, paths, routes, bind_address, config, ssl_config,
                        **kwargs):
     return wptserve.WebTestHttpd(host=host,
                                  port=port,
                                  doc_root=paths["doc_root"],
                                  routes=routes,
                                  rewrites=rewrites,
-                                 bind_hostname=bind_hostname,
-                                 config=external_config,
+                                 bind_address=bind_address,
+                                 config=config,
                                  use_ssl=True,
                                  key_file=ssl_config["key_path"],
                                  certificate=ssl_config["cert_path"],
@@ -522,7 +516,7 @@
 
 
 class WebSocketDaemon(object):
-    def __init__(self, host, port, doc_root, handlers_root, log_level, bind_hostname,
+    def __init__(self, host, port, doc_root, handlers_root, log_level, bind_address,
                  ssl_config):
         self.host = host
         cmd_args = ["-p", port,
@@ -547,7 +541,7 @@
                          "--certificate", ssl_config["cert_path"],
                          "--tls-module", tls_module]
 
-        if (bind_hostname):
+        if (bind_address):
             cmd_args = ["-H", host] + cmd_args
         opts, args = pywebsocket._parse_args_and_config(cmd_args)
         opts.cgi_directories = []
@@ -586,90 +580,47 @@
         self.server = None
 
 
-def start_ws_server(host, port, paths, routes, bind_hostname, external_config, ssl_config,
+def start_ws_server(host, port, paths, routes, bind_address, config, ssl_config,
                     **kwargs):
+    # Ensure that when we start this in a new process we don't inherit the
+    # global lock in the logging module
+    reload_module(logging)
     return WebSocketDaemon(host,
                            str(port),
                            repo_root,
                            paths["ws_doc_root"],
                            "debug",
-                           bind_hostname,
+                           bind_address,
                            ssl_config = None)
 
 
-def start_wss_server(host, port, paths, routes, bind_hostname, external_config, ssl_config,
+def start_wss_server(host, port, paths, routes, bind_address, config, ssl_config,
                      **kwargs):
+    # Ensure that when we start this in a new process we don't inherit the
+    # global lock in the logging module
+    reload_module(logging)
     return WebSocketDaemon(host,
                            str(port),
                            repo_root,
                            paths["ws_doc_root"],
                            "debug",
-                           bind_hostname,
+                           bind_address,
                            ssl_config)
 
 
-def get_ports(config, ssl_environment):
-    rv = defaultdict(list)
-    for scheme, ports in config["ports"].iteritems():
-        for i, port in enumerate(ports):
-            if scheme in ["wss", "https"] and not ssl_environment.ssl_enabled:
-                port = None
-            if port == "auto":
-                port = get_port()
-            else:
-                port = port
-            rv[scheme].append(port)
-    return rv
-
-
-
-def normalise_config(config, ports):
-    host = config["external_host"] if config["external_host"] else config["host"]
-    domains = get_subdomains(host)
-    ports_ = {}
-    for scheme, ports_used in ports.iteritems():
-        ports_[scheme] = ports_used
-
-    for key, value in domains.iteritems():
-        domains[key] = ".".join(value)
-
-    domains[""] = host
-
-    ports_ = {}
-    for scheme, ports_used in ports.iteritems():
-        ports_[scheme] = ports_used
-
-    return {"host": host,
-            "domains": domains,
-            "ports": ports_}
-
-
-def get_ssl_config(config, external_domains, ssl_environment):
-    key_path, cert_path = ssl_environment.host_cert_path(external_domains)
-    return {"key_path": key_path,
-            "cert_path": cert_path,
-            "encrypt_after_connect": config["ssl"]["encrypt_after_connect"]}
-
 def start(config, ssl_environment, routes, **kwargs):
-    host = config["host"]
-    domains = get_subdomains(host)
-    ports = get_ports(config, ssl_environment)
-    bind_hostname = config["bind_hostname"]
+    host = config["server_host"]
+    ports = config.ports
+    paths = config.paths
+    bind_address = config["bind_address"]
+    ssl_config = config.ssl_config
 
-    paths = {"doc_root": config["doc_root"],
-             "ws_doc_root": config["ws_doc_root"]}
+    logger.debug("Using ports: %r" % ports)
 
-    external_config = normalise_config(config, ports)
-
-    ssl_config = get_ssl_config(config, external_config["domains"].values(), ssl_environment)
-
-    if config["check_subdomains"]:
-        check_subdomains(host, paths, bind_hostname, ssl_config, config["aliases"])
-
-    servers = start_servers(host, ports, paths, routes, bind_hostname, external_config,
+    servers = start_servers(host, ports, paths, routes, bind_address, config,
                             ssl_config, **kwargs)
 
-    return external_config, servers
+    return servers
 
 
 def iter_procs(servers):
@@ -678,70 +629,20 @@
             yield server.proc
 
 
-def value_set(config, key):
-    return key in config and config[key] is not None
+def load_config(override_path=None, **kwargs):
+    rv = Config()
 
-
-def get_value_or_default(config, key, default=None):
-    return config[key] if value_set(config, key) else default
-
-
-def set_computed_defaults(config):
-    if not value_set(config, "doc_root"):
-        config["doc_root"] = repo_root
-
-    if not value_set(config, "ws_doc_root"):
-        root = get_value_or_default(config, "doc_root", default=repo_root)
-        config["ws_doc_root"] = os.path.join(root, "websockets", "handlers")
-
-    if not value_set(config, "aliases"):
-        config["aliases"] = []
-
-
-def merge_json(base_obj, override_obj):
-    rv = {}
-    for key, value in base_obj.iteritems():
-        if key not in override_obj:
-            rv[key] = value
-        else:
-            if isinstance(value, dict):
-                rv[key] = merge_json(value, override_obj[key])
-            else:
-                rv[key] = override_obj[key]
-    return rv
-
-
-def get_ssl_environment(config):
-    implementation_type = config["ssl"]["type"]
-    cls = sslutils.environments[implementation_type]
-    try:
-        kwargs = config["ssl"][implementation_type].copy()
-    except KeyError:
-        raise ValueError("%s is not a vaid ssl type." % implementation_type)
-    return cls(logger, **kwargs)
-
-
-def load_config(default_path, override_path=None, **kwargs):
-    if os.path.exists(default_path):
-        with open(default_path) as f:
-            base_obj = json.load(f)
-    else:
-        raise ValueError("Config path %s does not exist" % default_path)
-
-    if os.path.exists(override_path):
+    if override_path and os.path.exists(override_path):
         with open(override_path) as f:
             override_obj = json.load(f)
-    else:
-        override_obj = {}
-    rv = merge_json(base_obj, override_obj)
+        rv.update(override_obj)
 
     if kwargs.get("config_path"):
         other_path = os.path.abspath(os.path.expanduser(kwargs.get("config_path")))
         if os.path.exists(other_path):
-            base_obj = rv
             with open(other_path) as f:
                 override_obj = json.load(f)
-            rv = merge_json(base_obj, override_obj)
+            rv.update(override_obj)
         else:
             raise ValueError("Config path %s does not exist" % other_path)
 
@@ -754,11 +655,84 @@
         value = os.path.abspath(os.path.expanduser(value))
         if not os.path.exists(value):
             raise ValueError("%s path %s does not exist" % (title, value))
-        rv[key] = value
+        setattr(rv, key, value)
 
-    set_computed_defaults(rv)
     return rv
 
+_subdomains = {u"www",
+               u"www1",
+               u"www2",
+               u"天気の良い日",
+               u"élève"}
+
+_not_subdomains = {u"nonexistent"}
+
+
+class Config(config.Config):
+    """serve config
+
+    this subclasses wptserve.config.Config to add serve config options"""
+
+    _default = {
+        "browser_host": "web-platform.test",
+        "alternate_hosts": {
+            "alt": "not-web-platform.test"
+        },
+        "doc_root": repo_root,
+        "ws_doc_root": os.path.join(repo_root, "websockets", "handlers"),
+        "server_host": None,
+        "ports": {
+            "http": [8000, "auto"],
+            "https": [8443],
+            "ws": ["auto"],
+            "wss": ["auto"]
+        },
+        "check_subdomains": True,
+        "log_level": "debug",
+        "bind_address": True,
+        "ssl": {
+            "type": "pregenerated",
+            "encrypt_after_connect": False,
+            "openssl": {
+                "openssl_binary": "openssl",
+                "base_path": "_certs",
+                "force_regenerate": False,
+                "base_conf_path": None
+            },
+            "pregenerated": {
+                "host_key_path": os.path.join(repo_root, "tools", "certs", "web-platform.test.key"),
+                "host_cert_path": os.path.join(repo_root, "tools", "certs", "web-platform.test.pem")
+            },
+            "none": {}
+        },
+        "aliases": []
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(Config, self).__init__(
+            subdomains=_subdomains,
+            not_subdomains=_not_subdomains,
+            *args,
+            **kwargs
+        )
+
+    @property
+    def ws_doc_root(self):
+        if self._ws_doc_root is not None:
+            return self._ws_doc_root
+        else:
+            return os.path.join(self.doc_root, "websockets", "handlers")
+
+    @ws_doc_root.setter
+    def ws_doc_root(self, v):
+        self._ws_doc_root = v
+
+    @property
+    def paths(self):
+        rv = super(Config, self).paths
+        rv["ws_doc_root"] = self.ws_doc_root
+        return rv
+
 
 def get_parser():
     parser = argparse.ArgumentParser()
@@ -774,26 +748,32 @@
 
 
 def run(**kwargs):
-    config = load_config(os.path.join(repo_root, "config.default.json"),
-                         os.path.join(repo_root, "config.json"),
+    config = load_config(os.path.join(repo_root, "config.json"),
                          **kwargs)
 
-    setup_logger(config["log_level"])
+    global logger
+    logger = config.logger
+    set_logger(logger)
+
+    bind_address = config["bind_address"]
+
+    if config["check_subdomains"]:
+        check_subdomains(config)
 
     stash_address = None
-    if config["bind_hostname"]:
-        stash_address = (config["host"], get_port())
+    if bind_address:
+        stash_address = (config.server_host, get_port(config.server_host))
+        logger.debug("Going to use port %d for stash" % stash_address[1])
 
     with stash.StashServer(stash_address, authkey=str(uuid.uuid4())):
-        with get_ssl_environment(config) as ssl_env:
-            config_, servers = start(config, ssl_env, build_routes(config["aliases"]), **kwargs)
+        servers = start(config, config.ssl_env, build_routes(config["aliases"]), **kwargs)
 
-            try:
-                while any(item.is_alive() for item in iter_procs(servers)):
-                    for item in iter_procs(servers):
-                        item.join(1)
-            except KeyboardInterrupt:
-                logger.info("Shutting down")
+        try:
+            while any(item.is_alive() for item in iter_procs(servers)):
+                for item in iter_procs(servers):
+                    item.join(1)
+        except KeyboardInterrupt:
+            logger.info("Shutting down")
 
 
 def main():
diff --git a/tools/serve/test_serve.py b/tools/serve/test_serve.py
new file mode 100644
index 0000000..b517e64
--- /dev/null
+++ b/tools/serve/test_serve.py
@@ -0,0 +1,79 @@
+import pickle
+import platform
+import os
+
+import pytest
+
+import localpaths
+from . import serve
+from .serve import Config
+
+
+@pytest.mark.skipif(platform.uname()[0] == "Windows",
+                    reason="Expected contents are platform-dependent")
+def test_make_hosts_file_nix():
+    c = Config(browser_host="foo.bar", alternate_hosts={"alt": "foo2.bar"})
+    hosts = serve.make_hosts_file(c, "192.168.42.42")
+    lines = hosts.split("\n")
+    assert set(lines) == {"",
+                          "192.168.42.42\tfoo.bar",
+                          "192.168.42.42\tfoo2.bar",
+                          "192.168.42.42\twww.foo.bar",
+                          "192.168.42.42\twww.foo2.bar",
+                          "192.168.42.42\twww1.foo.bar",
+                          "192.168.42.42\twww1.foo2.bar",
+                          "192.168.42.42\twww2.foo.bar",
+                          "192.168.42.42\twww2.foo2.bar",
+                          "192.168.42.42\txn--lve-6lad.foo.bar",
+                          "192.168.42.42\txn--lve-6lad.foo2.bar",
+                          "192.168.42.42\txn--n8j6ds53lwwkrqhv28a.foo.bar",
+                          "192.168.42.42\txn--n8j6ds53lwwkrqhv28a.foo2.bar"}
+    assert lines[-1] == ""
+
+@pytest.mark.skipif(platform.uname()[0] != "Windows",
+                    reason="Expected contents are platform-dependent")
+def test_make_hosts_file_windows():
+    c = Config(browser_host="foo.bar", alternate_hosts={"alt": "foo2.bar"})
+    hosts = serve.make_hosts_file(c, "192.168.42.42")
+    lines = hosts.split("\n")
+    assert set(lines) == {"",
+                          "0.0.0.0\tnonexistent.foo.bar",
+                          "0.0.0.0\tnonexistent.foo2.bar",
+                          "192.168.42.42\tfoo.bar",
+                          "192.168.42.42\tfoo2.bar",
+                          "192.168.42.42\twww.foo.bar",
+                          "192.168.42.42\twww.foo2.bar",
+                          "192.168.42.42\twww1.foo.bar",
+                          "192.168.42.42\twww1.foo2.bar",
+                          "192.168.42.42\twww2.foo.bar",
+                          "192.168.42.42\twww2.foo2.bar",
+                          "192.168.42.42\txn--lve-6lad.foo.bar",
+                          "192.168.42.42\txn--lve-6lad.foo2.bar",
+                          "192.168.42.42\txn--n8j6ds53lwwkrqhv28a.foo.bar",
+                          "192.168.42.42\txn--n8j6ds53lwwkrqhv28a.foo2.bar"}
+    assert lines[-1] == ""
+
+
+def test_ws_doc_root_default():
+    c = Config()
+    assert c.ws_doc_root == os.path.join(localpaths.repo_root, "websockets", "handlers")
+
+
+def test_init_ws_doc_root():
+    c = Config(ws_doc_root="/")
+    assert c.doc_root == localpaths.repo_root  # check this hasn't changed
+    assert c._ws_doc_root == "/"
+    assert c.ws_doc_root == "/"
+
+
+def test_set_ws_doc_root():
+    c = Config()
+    c.ws_doc_root = "/"
+    assert c.doc_root == localpaths.repo_root  # check this hasn't changed
+    assert c._ws_doc_root == "/"
+    assert c.ws_doc_root == "/"
+
+
+def test_pickle():
+    # Ensure that the config object can be pickled
+    pickle.dumps(Config())
diff --git a/tools/six/documentation/index.rst b/tools/six/documentation/index.rst
index 7851421..9827e66 100644
--- a/tools/six/documentation/index.rst
+++ b/tools/six/documentation/index.rst
@@ -104,7 +104,7 @@
 Object model compatibility
 >>>>>>>>>>>>>>>>>>>>>>>>>>
 
-Python 3 renamed the attributes of several intepreter data structures.  The
+Python 3 renamed the attributes of several interpreter data structures.  The
 following accessors are available.  Note that the recommended way to inspect
 functions and methods is the stdlib :mod:`py3:inspect` module.
 
@@ -459,7 +459,7 @@
 Python 3.  When the new Python 3 name is a package, the components of the name
 are separated by underscores.  For example, ``html.parser`` becomes
 ``html_parser``.  In some cases where several modules have been combined, the
-Python 2 name is retained.  This is so the appropiate modules can be found when
+Python 2 name is retained.  This is so the appropriate modules can be found when
 running on Python 2.  For example, ``BaseHTTPServer`` which is in
 ``http.server`` in Python 3 is aliased as ``BaseHTTPServer``.
 
diff --git a/tools/sslutils/base.py b/tools/sslutils/base.py
deleted file mode 100644
index e78e138..0000000
--- a/tools/sslutils/base.py
+++ /dev/null
@@ -1,23 +0,0 @@
-def get_logger(name="ssl"):
-    logger = structured.get_default_logger(name)
-    if logger is None:
-        logger = structured.structuredlog.StructuredLogger(name)
-    return logger
-
-class NoSSLEnvironment(object):
-    ssl_enabled = False
-
-    def __init__(self, *args, **kwargs):
-        pass
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, *args, **kwargs):
-        pass
-
-    def host_cert_path(self, host):
-        return None, None
-
-    def ca_cert_path(self):
-        return None
diff --git a/tools/sslutils/openssl.py b/tools/sslutils/openssl.py
deleted file mode 100644
index 09c2471..0000000
--- a/tools/sslutils/openssl.py
+++ /dev/null
@@ -1,422 +0,0 @@
-import functools
-import os
-import random
-import shutil
-import subprocess
-import tempfile
-from datetime import datetime, timedelta
-
-# Amount of time beyond the present to consider certificates "expired." This
-# allows certificates to be proactively re-generated in the "buffer" period
-# prior to their exact expiration time.
-CERT_EXPIRY_BUFFER = dict(hours=6)
-
-class OpenSSL(object):
-    def __init__(self, logger, binary, base_path, conf_path, hosts, duration,
-                 base_conf_path=None):
-        """Context manager for interacting with OpenSSL.
-        Creates a config file for the duration of the context.
-
-        :param logger: stdlib logger or python structured logger
-        :param binary: path to openssl binary
-        :param base_path: path to directory for storing certificates
-        :param conf_path: path for configuration file storing configuration data
-        :param hosts: list of hosts to include in configuration (or None if not
-                      generating host certificates)
-        :param duration: Certificate duration in days"""
-
-        self.base_path = base_path
-        self.binary = binary
-        self.conf_path = conf_path
-        self.base_conf_path = base_conf_path
-        self.logger = logger
-        self.proc = None
-        self.cmd = []
-        self.hosts = hosts
-        self.duration = duration
-
-    def __enter__(self):
-        with open(self.conf_path, "w") as f:
-            f.write(get_config(self.base_path, self.hosts, self.duration))
-        return self
-
-    def __exit__(self, *args, **kwargs):
-        os.unlink(self.conf_path)
-
-    def log(self, line):
-        if hasattr(self.logger, "process_output"):
-            self.logger.process_output(self.proc.pid if self.proc is not None else None,
-                                       line.decode("utf8", "replace"),
-                                       command=" ".join(self.cmd))
-        else:
-            self.logger.debug(line)
-
-    def __call__(self, cmd, *args, **kwargs):
-        """Run a command using OpenSSL in the current context.
-
-        :param cmd: The openssl subcommand to run
-        :param *args: Additional arguments to pass to the command
-        """
-        self.cmd = [self.binary, cmd]
-        if cmd != "x509":
-            self.cmd += ["-config", self.conf_path]
-        self.cmd += list(args)
-
-        # Copy the environment, converting to plain strings. Windows
-        # StartProcess is picky about all the keys/values being plain strings,
-        # but at least in MSYS shells, the os.environ dictionary can be mixed.
-        env = {}
-        for k, v in os.environ.iteritems():
-            try:
-                env[k.encode("utf8")] = v.encode("utf8")
-            except UnicodeDecodeError:
-                pass
-
-        if self.base_conf_path is not None:
-            env["OPENSSL_CONF"] = self.base_conf_path.encode("utf8")
-
-        self.proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-                                     env=env)
-        stdout, stderr = self.proc.communicate()
-        self.log(stdout)
-        if self.proc.returncode != 0:
-            raise subprocess.CalledProcessError(self.proc.returncode, self.cmd,
-                                                output=stdout)
-
-        self.cmd = []
-        self.proc = None
-        return stdout
-
-
-def make_subject(common_name,
-                 country=None,
-                 state=None,
-                 locality=None,
-                 organization=None,
-                 organization_unit=None):
-    args = [("country", "C"),
-            ("state", "ST"),
-            ("locality", "L"),
-            ("organization", "O"),
-            ("organization_unit", "OU"),
-            ("common_name", "CN")]
-
-    rv = []
-
-    for var, key in args:
-        value = locals()[var]
-        if value is not None:
-            rv.append("/%s=%s" % (key, value.replace("/", "\\/")))
-
-    return "".join(rv)
-
-def make_alt_names(hosts):
-    rv = []
-    for name in hosts:
-        rv.append("DNS:%s" % name)
-    return ",".join(rv)
-
-def get_config(root_dir, hosts, duration=30):
-    if hosts is None:
-        san_line = ""
-    else:
-        san_line = "subjectAltName = %s" % make_alt_names(hosts)
-
-    if os.path.sep == "\\":
-        # This seems to be needed for the Shining Light OpenSSL on
-        # Windows, at least.
-        root_dir = root_dir.replace("\\", "\\\\")
-
-    rv = """[ ca ]
-default_ca = CA_default
-
-[ CA_default ]
-dir = %(root_dir)s
-certs = $dir
-new_certs_dir = $certs
-crl_dir = $dir%(sep)scrl
-database = $dir%(sep)sindex.txt
-private_key = $dir%(sep)scakey.pem
-certificate = $dir%(sep)scacert.pem
-serial = $dir%(sep)sserial
-crldir = $dir%(sep)scrl
-crlnumber = $dir%(sep)scrlnumber
-crl = $crldir%(sep)scrl.pem
-RANDFILE = $dir%(sep)sprivate%(sep)s.rand
-x509_extensions = usr_cert
-name_opt        = ca_default
-cert_opt        = ca_default
-default_days = %(duration)d
-default_crl_days = %(duration)d
-default_md = sha256
-preserve = no
-policy = policy_anything
-copy_extensions = copy
-
-[ policy_anything ]
-countryName = optional
-stateOrProvinceName = optional
-localityName = optional
-organizationName = optional
-organizationalUnitName = optional
-commonName = supplied
-emailAddress = optional
-
-[ req ]
-default_bits = 2048
-default_keyfile  = privkey.pem
-distinguished_name = req_distinguished_name
-attributes = req_attributes
-x509_extensions = v3_ca
-
-# Passwords for private keys if not present they will be prompted for
-# input_password = secret
-# output_password = secret
-string_mask = utf8only
-req_extensions = v3_req
-
-[ req_distinguished_name ]
-countryName = Country Name (2 letter code)
-countryName_default = AU
-countryName_min = 2
-countryName_max = 2
-stateOrProvinceName = State or Province Name (full name)
-stateOrProvinceName_default =
-localityName = Locality Name (eg, city)
-0.organizationName = Organization Name
-0.organizationName_default = Web Platform Tests
-organizationalUnitName = Organizational Unit Name (eg, section)
-#organizationalUnitName_default =
-commonName = Common Name (e.g. server FQDN or YOUR name)
-commonName_max = 64
-emailAddress = Email Address
-emailAddress_max = 64
-
-[ req_attributes ]
-
-[ usr_cert ]
-basicConstraints=CA:false
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
-
-[ v3_req ]
-basicConstraints = CA:FALSE
-keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-extendedKeyUsage = serverAuth
-%(san_line)s
-
-[ v3_ca ]
-basicConstraints = CA:true
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid:always,issuer:always
-keyUsage = keyCertSign
-""" % {"root_dir": root_dir,
-       "san_line": san_line,
-       "duration": duration,
-       "sep": os.path.sep.replace("\\", "\\\\")}
-
-    return rv
-
-class OpenSSLEnvironment(object):
-    ssl_enabled = True
-
-    def __init__(self, logger, openssl_binary="openssl", base_path=None,
-                 password="web-platform-tests", force_regenerate=False,
-                 duration=30, base_conf_path=None):
-        """SSL environment that creates a local CA and host certificate using OpenSSL.
-
-        By default this will look in base_path for existing certificates that are still
-        valid and only create new certificates if there aren't any. This behaviour can
-        be adjusted using the force_regenerate option.
-
-        :param logger: a stdlib logging compatible logger or mozlog structured logger
-        :param openssl_binary: Path to the OpenSSL binary
-        :param base_path: Path in which certificates will be stored. If None, a temporary
-                          directory will be used and removed when the server shuts down
-        :param password: Password to use
-        :param force_regenerate: Always create a new certificate even if one already exists.
-        """
-        self.logger = logger
-
-        self.temporary = False
-        if base_path is None:
-            base_path = tempfile.mkdtemp()
-            self.temporary = True
-
-        self.base_path = os.path.abspath(base_path)
-        self.password = password
-        self.force_regenerate = force_regenerate
-        self.duration = duration
-        self.base_conf_path = base_conf_path
-
-        self.path = None
-        self.binary = openssl_binary
-        self.openssl = None
-
-        self._ca_cert_path = None
-        self._ca_key_path = None
-        self.host_certificates = {}
-
-    def __enter__(self):
-        if not os.path.exists(self.base_path):
-            os.makedirs(self.base_path)
-
-        path = functools.partial(os.path.join, self.base_path)
-
-        with open(path("index.txt"), "w"):
-            pass
-        with open(path("serial"), "w") as f:
-            serial = "%x" % random.randint(0, 1000000)
-            if len(serial) % 2:
-                serial = "0" + serial
-            f.write(serial)
-
-        self.path = path
-
-        return self
-
-    def __exit__(self, *args, **kwargs):
-        if self.temporary:
-            shutil.rmtree(self.base_path)
-
-    def _config_openssl(self, hosts):
-        conf_path = self.path("openssl.cfg")
-        return OpenSSL(self.logger, self.binary, self.base_path, conf_path, hosts,
-                       self.duration, self.base_conf_path)
-
-    def ca_cert_path(self):
-        """Get the path to the CA certificate file, generating a
-        new one if needed"""
-        if self._ca_cert_path is None and not self.force_regenerate:
-            self._load_ca_cert()
-        if self._ca_cert_path is None:
-            self._generate_ca()
-        return self._ca_cert_path
-
-    def _load_ca_cert(self):
-        key_path = self.path("cakey.pem")
-        cert_path = self.path("cacert.pem")
-
-        if self.check_key_cert(key_path, cert_path, None):
-            self.logger.info("Using existing CA cert")
-            self._ca_key_path, self._ca_cert_path = key_path, cert_path
-
-    def check_key_cert(self, key_path, cert_path, hosts):
-        """Check that a key and cert file exist and are valid"""
-        if not os.path.exists(key_path) or not os.path.exists(cert_path):
-            return False
-
-        with self._config_openssl(hosts) as openssl:
-            end_date_str = openssl("x509",
-                                   "-noout",
-                                   "-enddate",
-                                   "-in", cert_path).split("=", 1)[1].strip()
-            # Not sure if this works in other locales
-            end_date = datetime.strptime(end_date_str, "%b %d %H:%M:%S %Y %Z")
-            time_buffer = timedelta(**CERT_EXPIRY_BUFFER)
-            # Because `strptime` does not account for time zone offsets, it is
-            # always in terms of UTC, so the current time should be calculated
-            # accordingly.
-            if end_date < datetime.utcnow() + time_buffer:
-                return False
-
-        #TODO: check the key actually signed the cert.
-        return True
-
-    def _generate_ca(self):
-        path = self.path
-        self.logger.info("Generating new CA in %s" % self.base_path)
-
-        key_path = path("cakey.pem")
-        req_path = path("careq.pem")
-        cert_path = path("cacert.pem")
-
-        with self._config_openssl(None) as openssl:
-            openssl("req",
-                    "-batch",
-                    "-new",
-                    "-newkey", "rsa:2048",
-                    "-keyout", key_path,
-                    "-out", req_path,
-                    "-subj", make_subject("web-platform-tests"),
-                    "-passout", "pass:%s" % self.password)
-
-            openssl("ca",
-                    "-batch",
-                    "-create_serial",
-                    "-keyfile", key_path,
-                    "-passin", "pass:%s" % self.password,
-                    "-selfsign",
-                    "-extensions", "v3_ca",
-                    "-in", req_path,
-                    "-out", cert_path)
-
-        os.unlink(req_path)
-
-        self._ca_key_path, self._ca_cert_path = key_path, cert_path
-
-    def host_cert_path(self, hosts):
-        """Get a tuple of (private key path, certificate path) for a host,
-        generating new ones if necessary.
-
-        hosts must be a list of all hosts to appear on the certificate, with
-        the primary hostname first."""
-        hosts = tuple(hosts)
-        if hosts not in self.host_certificates:
-            if not self.force_regenerate:
-                key_cert = self._load_host_cert(hosts)
-            else:
-                key_cert = None
-            if key_cert is None:
-                key, cert = self._generate_host_cert(hosts)
-            else:
-                key, cert = key_cert
-            self.host_certificates[hosts] = key, cert
-
-        return self.host_certificates[hosts]
-
-    def _load_host_cert(self, hosts):
-        host = hosts[0]
-        key_path = self.path("%s.key" % host)
-        cert_path = self.path("%s.pem" % host)
-
-        # TODO: check that this cert was signed by the CA cert
-        if self.check_key_cert(key_path, cert_path, hosts):
-            self.logger.info("Using existing host cert")
-            return key_path, cert_path
-
-    def _generate_host_cert(self, hosts):
-        host = hosts[0]
-        if self._ca_key_path is None:
-            self._generate_ca()
-        ca_key_path = self._ca_key_path
-
-        assert os.path.exists(ca_key_path)
-
-        path = self.path
-
-        req_path = path("wpt.req")
-        cert_path = path("%s.pem" % host)
-        key_path = path("%s.key" % host)
-
-        self.logger.info("Generating new host cert")
-
-        with self._config_openssl(hosts) as openssl:
-            openssl("req",
-                    "-batch",
-                    "-newkey", "rsa:2048",
-                    "-keyout", key_path,
-                    "-in", ca_key_path,
-                    "-nodes",
-                    "-out", req_path)
-
-            openssl("ca",
-                    "-batch",
-                    "-in", req_path,
-                    "-passin", "pass:%s" % self.password,
-                    "-subj", make_subject(host),
-                    "-out", cert_path)
-
-        os.unlink(req_path)
-
-        return key_path, cert_path
diff --git a/tools/third_party/attrs/.coveragerc b/tools/third_party/attrs/.coveragerc
new file mode 100644
index 0000000..093c119
--- /dev/null
+++ b/tools/third_party/attrs/.coveragerc
@@ -0,0 +1,13 @@
+[run]
+branch = True
+source =
+    attr
+
+[paths]
+source =
+   src/attr
+   .tox/*/lib/python*/site-packages/attr
+   .tox/pypy/site-packages/attr
+
+[report]
+show_missing = True
diff --git a/tools/third_party/attrs/.gitignore b/tools/third_party/attrs/.gitignore
new file mode 100644
index 0000000..a4ca3f8
--- /dev/null
+++ b/tools/third_party/attrs/.gitignore
@@ -0,0 +1,9 @@
+.tox
+.coverage*
+*.pyc
+*.egg-info
+docs/_build/
+htmlcov
+dist
+.cache
+.hypothesis
\ No newline at end of file
diff --git a/tools/third_party/attrs/.travis.yml b/tools/third_party/attrs/.travis.yml
new file mode 100644
index 0000000..4f1cf2f
--- /dev/null
+++ b/tools/third_party/attrs/.travis.yml
@@ -0,0 +1,56 @@
+dist: trusty
+sudo: false
+cache:
+  directories:
+    - $HOME/.cache/pip
+
+language: python
+
+
+matrix:
+  include:
+    - python: "2.7"
+      env: TOXENV=py27
+    - python: "3.4"
+      env: TOXENV=py34
+    - python: "3.5"
+      env: TOXENV=py35
+    - python: "3.6"
+      env: TOXENV=py36
+    - python: "pypy2.7-5.8.0"
+      env: TOXENV=pypy
+    - python: "pypy3.5-5.8.0"
+      env: TOXENV=pypy3
+
+    # Meta
+    - python: "3.6"
+      env: TOXENV=flake8
+    - python: "3.6"
+      env: TOXENV=manifest
+    - python: "3.6"
+      env: TOXENV=docs
+    - python: "3.6"
+      env: TOXENV=readme
+    - python: "3.6"
+      env: TOXENV=changelog
+
+
+install:
+  - pip install tox
+
+
+script:
+  - tox
+
+
+before_install:
+  - pip install codecov
+
+
+after_success:
+  - tox -e coverage-report
+  - codecov
+
+
+notifications:
+  email: false
diff --git a/tools/third_party/attrs/AUTHORS.rst b/tools/third_party/attrs/AUTHORS.rst
new file mode 100644
index 0000000..73eae21
--- /dev/null
+++ b/tools/third_party/attrs/AUTHORS.rst
@@ -0,0 +1,11 @@
+Credits
+=======
+
+``attrs`` is written and maintained by `Hynek Schlawack <https://hynek.me/>`_.
+
+The development is kindly supported by `Variomedia AG <https://www.variomedia.de/>`_.
+
+A full list of contributors can be found in `GitHub's overview <https://github.com/python-attrs/attrs/graphs/contributors>`_.
+
+It’s the spiritual successor of `characteristic <https://characteristic.readthedocs.io/>`_ and aspires to fix some of it clunkiness and unfortunate decisions.
+Both were inspired by Twisted’s `FancyEqMixin <https://twistedmatrix.com/documents/current/api/twisted.python.util.FancyEqMixin.html>`_ but both are implemented using class decorators because `sub-classing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, m’kay?
diff --git a/tools/third_party/attrs/CHANGELOG.rst b/tools/third_party/attrs/CHANGELOG.rst
new file mode 100644
index 0000000..55cd459
--- /dev/null
+++ b/tools/third_party/attrs/CHANGELOG.rst
@@ -0,0 +1,326 @@
+Changelog
+=========
+
+Versions follow `CalVer <http://calver.org>`_ with a strict backwards compatibility policy.
+The third digit is only for regressions.
+
+Changes for the upcoming release can be found in the `"changelog.d" directory <https://github.com/python-attrs/attrs/tree/master/changelog.d>`_ in our repository.
+
+..
+   Do *NOT* add changelog entries here!
+
+   This changelog is managed by towncrier and is compiled at release time.
+
+   See http://www.attrs.org/en/latest/contributing.html#changelog for details."""  # noqa
+
+.. towncrier release notes start
+
+
+17.3.0 (2017-11-08)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Attributes are not defined on the class body anymore.
+
+  This means that if you define a class ``C`` with an attribute ``x``, the class will *not* have an attribute ``x`` for introspection anymore.
+  Instead of ``C.x``, use ``attr.fields(C).x`` or look at ``C.__attrs_attrs__``.
+  The old behavior has been deprecated since version 16.1.
+  (`#253 <https://github.com/python-attrs/attrs/issues/253>`_)
+
+
+Changes
+^^^^^^^
+
+- ``super()`` and ``__class__`` now work on Python 3 when ``slots=True``.
+  (`#102 <https://github.com/python-attrs/attrs/issues/102>`_, `#226 <https://github.com/python-attrs/attrs/issues/226>`_, `#269 <https://github.com/python-attrs/attrs/issues/269>`_, `#270 <https://github.com/python-attrs/attrs/issues/270>`_, `#272 <https://github.com/python-attrs/attrs/issues/272>`_)
+- Added ``type`` argument to ``attr.ib()`` and corresponding ``type`` attribute to ``attr.Attribute``.
+
+  This change paves the way for automatic type checking and serialization (though as of this release ``attrs`` does not make use of it).
+  In Python 3.6 or higher, the value of ``attr.Attribute.type`` can alternately be set using variable type annotations
+  (see `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_). (`#151 <https://github.com/python-attrs/attrs/issues/151>`_, `#214 <https://github.com/python-attrs/attrs/issues/214>`_, `#215 <https://github.com/python-attrs/attrs/issues/215>`_, `#239 <https://github.com/python-attrs/attrs/issues/239>`_)
+- The combination of ``str=True`` and ``slots=True`` now works on Python 2.
+  (`#198 <https://github.com/python-attrs/attrs/issues/198>`_)
+- ``attr.Factory`` is hashable again. (`#204
+  <https://github.com/python-attrs/attrs/issues/204>`_)
+- Subclasses now can overwrite attribute definitions of their superclass.
+
+  That means that you can -- for example -- change the default value for an attribute by redefining it.
+  (`#221 <https://github.com/python-attrs/attrs/issues/221>`_, `#229 <https://github.com/python-attrs/attrs/issues/229>`_)
+- Added new option ``auto_attribs`` to ``@attr.s`` that allows to collect annotated fields without setting them to ``attr.ib()``.
+
+  Setting a field to an ``attr.ib()`` is still possible to supply options like validators.
+  Setting it to any other value is treated like it was passed as ``attr.ib(default=value)`` -- passing an instance of ``attr.Factory`` also works as expected.
+  (`#262 <https://github.com/python-attrs/attrs/issues/262>`_, `#277 <https://github.com/python-attrs/attrs/issues/277>`_)
+- Instances of classes created using ``attr.make_class()`` can now be pickled.
+  (`#282 <https://github.com/python-attrs/attrs/issues/282>`_)
+
+
+----
+
+
+17.2.0 (2017-05-24)
+-------------------
+
+
+Changes:
+^^^^^^^^
+
+- Validators are hashable again.
+  Note that validators may become frozen in the future, pending availability of no-overhead frozen classes.
+  `#192 <https://github.com/python-attrs/attrs/issues/192>`_
+
+
+----
+
+
+17.1.0 (2017-05-16)
+-------------------
+
+To encourage more participation, the project has also been moved into a `dedicated GitHub organization <https://github.com/python-attrs/>`_ and everyone is most welcome to join!
+
+``attrs`` also has a logo now!
+
+.. image:: http://www.attrs.org/en/latest/_static/attrs_logo.png
+   :alt: attrs logo
+
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ``attrs`` will set the ``__hash__()`` method to ``None`` by default now.
+  The way hashes were handled before was in conflict with `Python's specification <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_.
+  This *may* break some software although this breakage is most likely just surfacing of latent bugs.
+  You can always make ``attrs`` create the ``__hash__()`` method using ``@attr.s(hash=True)``.
+  See `#136`_ for the rationale of this change.
+
+  .. warning::
+
+    Please *do not* upgrade blindly and *do* test your software!
+    *Especially* if you use instances as dict keys or put them into sets!
+
+- Correspondingly, ``attr.ib``'s ``hash`` argument is ``None`` by default too and mirrors the ``cmp`` argument as it should.
+
+
+Deprecations:
+^^^^^^^^^^^^^
+
+- ``attr.assoc()`` is now deprecated in favor of ``attr.evolve()`` and will stop working in 2018.
+
+
+Changes:
+^^^^^^^^
+
+- Fix default hashing behavior.
+  Now *hash* mirrors the value of *cmp* and classes are unhashable by default.
+  `#136`_
+  `#142 <https://github.com/python-attrs/attrs/issues/142>`_
+- Added ``attr.evolve()`` that, given an instance of an ``attrs`` class and field changes as keyword arguments, will instantiate a copy of the given instance with the changes applied.
+  ``evolve()`` replaces ``assoc()``, which is now deprecated.
+  ``evolve()`` is significantly faster than ``assoc()``, and requires the class have an initializer that can take the field values as keyword arguments (like ``attrs`` itself can generate).
+  `#116 <https://github.com/python-attrs/attrs/issues/116>`_
+  `#124 <https://github.com/python-attrs/attrs/pull/124>`_
+  `#135 <https://github.com/python-attrs/attrs/pull/135>`_
+- ``FrozenInstanceError`` is now raised when trying to delete an attribute from a frozen class.
+  `#118 <https://github.com/python-attrs/attrs/pull/118>`_
+- Frozen-ness of classes is now inherited.
+  `#128 <https://github.com/python-attrs/attrs/pull/128>`_
+- ``__attrs_post_init__()`` is now run if validation is disabled.
+  `#130 <https://github.com/python-attrs/attrs/pull/130>`_
+- Added ``attr.validators.in_(options)`` that, given the allowed `options`, checks whether the attribute value is in it.
+  This can be used to check constants, enums, mappings, etc.
+  `#181 <https://github.com/python-attrs/attrs/pull/181>`_
+- Added ``attr.validators.and_()`` that composes multiple validators into one.
+  `#161 <https://github.com/python-attrs/attrs/issues/161>`_
+- For convenience, the ``validator`` argument of ``@attr.s`` now can take a ``list`` of validators that are wrapped using ``and_()``.
+  `#138 <https://github.com/python-attrs/attrs/issues/138>`_
+- Accordingly, ``attr.validators.optional()`` now can take a ``list`` of validators too.
+  `#161 <https://github.com/python-attrs/attrs/issues/161>`_
+- Validators can now be defined conveniently inline by using the attribute as a decorator.
+  Check out the `examples <http://www.attrs.org/en/stable/examples.html#validators>`_ to see it in action!
+  `#143 <https://github.com/python-attrs/attrs/issues/143>`_
+- ``attr.Factory()`` now has a ``takes_self`` argument that makes the initializer to pass the partially initialized instance into the factory.
+  In other words you can define attribute defaults based on other attributes.
+  `#165`_
+  `#189 <https://github.com/python-attrs/attrs/issues/189>`_
+- Default factories can now also be defined inline using decorators.
+  They are *always* passed the partially initialized instance.
+  `#165`_
+- Conversion can now be made optional using ``attr.converters.optional()``.
+  `#105 <https://github.com/python-attrs/attrs/issues/105>`_
+  `#173 <https://github.com/python-attrs/attrs/pull/173>`_
+- ``attr.make_class()`` now accepts the keyword argument ``bases`` which allows for subclassing.
+  `#152 <https://github.com/python-attrs/attrs/pull/152>`_
+- Metaclasses are now preserved with ``slots=True``.
+  `#155 <https://github.com/python-attrs/attrs/pull/155>`_
+
+.. _`#136`: https://github.com/python-attrs/attrs/issues/136
+.. _`#165`: https://github.com/python-attrs/attrs/issues/165
+
+
+----
+
+
+16.3.0 (2016-11-24)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Attributes now can have user-defined metadata which greatly improves ``attrs``'s extensibility.
+  `#96 <https://github.com/python-attrs/attrs/pull/96>`_
+- Allow for a ``__attrs_post_init__()`` method that -- if defined -- will get called at the end of the ``attrs``-generated ``__init__()`` method.
+  `#111 <https://github.com/python-attrs/attrs/pull/111>`_
+- Added ``@attr.s(str=True)`` that will optionally create a ``__str__()`` method that is identical to ``__repr__()``.
+  This is mainly useful with ``Exception``\ s and other classes that rely on a useful ``__str__()`` implementation but overwrite the default one through a poor own one.
+  Default Python class behavior is to use ``__repr__()`` as ``__str__()`` anyways.
+
+  If you tried using ``attrs`` with ``Exception``\ s and were puzzled by the tracebacks: this option is for you.
+- ``__name__`` is not overwritten with ``__qualname__`` for ``attr.s(slots=True)`` classes anymore.
+  `#99 <https://github.com/python-attrs/attrs/issues/99>`_
+
+
+----
+
+
+16.2.0 (2016-09-17)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added ``attr.astuple()`` that -- similarly to ``attr.asdict()`` -- returns the instance as a tuple.
+  `#77 <https://github.com/python-attrs/attrs/issues/77>`_
+- Converts now work with frozen classes.
+  `#76 <https://github.com/python-attrs/attrs/issues/76>`_
+- Instantiation of ``attrs`` classes with converters is now significantly faster.
+  `#80 <https://github.com/python-attrs/attrs/pull/80>`_
+- Pickling now works with ``__slots__`` classes.
+  `#81 <https://github.com/python-attrs/attrs/issues/81>`_
+- ``attr.assoc()`` now works with ``__slots__`` classes.
+  `#84 <https://github.com/python-attrs/attrs/issues/84>`_
+- The tuple returned by ``attr.fields()`` now also allows to access the ``Attribute`` instances by name.
+  Yes, we've subclassed ``tuple`` so you don't have to!
+  Therefore ``attr.fields(C).x`` is equivalent to the deprecated ``C.x`` and works with ``__slots__`` classes.
+  `#88 <https://github.com/python-attrs/attrs/issues/88>`_
+
+
+----
+
+
+16.1.0 (2016-08-30)
+-------------------
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- All instances where function arguments were called ``cl`` have been changed to the more Pythonic ``cls``.
+  Since it was always the first argument, it's doubtful anyone ever called those function with in the keyword form.
+  If so, sorry for any breakage but there's no practical deprecation path to solve this ugly wart.
+
+
+Deprecations:
+^^^^^^^^^^^^^
+
+- Accessing ``Attribute`` instances on class objects is now deprecated and will stop working in 2017.
+  If you need introspection please use the ``__attrs_attrs__`` attribute or the ``attr.fields()`` function that carry them too.
+  In the future, the attributes that are defined on the class body and are usually overwritten in your ``__init__`` method are simply removed after ``@attr.s`` has been applied.
+
+  This will remove the confusing error message if you write your own ``__init__`` and forget to initialize some attribute.
+  Instead you will get a straightforward ``AttributeError``.
+  In other words: decorated classes will work more like plain Python classes which was always ``attrs``'s goal.
+- The serious business aliases ``attr.attributes`` and ``attr.attr`` have been deprecated in favor of ``attr.attrs`` and ``attr.attrib`` which are much more consistent and frankly obvious in hindsight.
+  They will be purged from documentation immediately but there are no plans to actually remove them.
+
+
+Changes:
+^^^^^^^^
+
+- ``attr.asdict()``\ 's ``dict_factory`` arguments is now propagated on recursion.
+  `#45 <https://github.com/python-attrs/attrs/issues/45>`_
+- ``attr.asdict()``, ``attr.has()`` and ``attr.fields()`` are significantly faster.
+  `#48 <https://github.com/python-attrs/attrs/issues/48>`_
+  `#51 <https://github.com/python-attrs/attrs/issues/51>`_
+- Add ``attr.attrs`` and ``attr.attrib`` as a more consistent aliases for ``attr.s`` and ``attr.ib``.
+- Add ``frozen`` option to ``attr.s`` that will make instances best-effort immutable.
+  `#60 <https://github.com/python-attrs/attrs/issues/60>`_
+- ``attr.asdict()`` now takes ``retain_collection_types`` as an argument.
+  If ``True``, it does not convert attributes of type ``tuple`` or ``set`` to ``list``.
+  `#69 <https://github.com/python-attrs/attrs/issues/69>`_
+
+
+----
+
+
+16.0.0 (2016-05-23)
+-------------------
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Python 3.3 and 2.6 aren't supported anymore.
+  They may work by chance but any effort to keep them working has ceased.
+
+  The last Python 2.6 release was on October 29, 2013 and isn't supported by the CPython core team anymore.
+  Major Python packages like Django and Twisted dropped Python 2.6 a while ago already.
+
+  Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release.
+
+Changes:
+^^^^^^^^
+
+- ``__slots__`` have arrived!
+  Classes now can automatically be `slots <https://docs.python.org/3/reference/datamodel.html#slots>`_-style (and save your precious memory) just by passing ``slots=True``.
+  `#35 <https://github.com/python-attrs/attrs/issues/35>`_
+- Allow the case of initializing attributes that are set to ``init=False``.
+  This allows for clean initializer parameter lists while being able to initialize attributes to default values.
+  `#32 <https://github.com/python-attrs/attrs/issues/32>`_
+- ``attr.asdict()`` can now produce arbitrary mappings instead of Python ``dict``\ s when provided with a ``dict_factory`` argument.
+  `#40 <https://github.com/python-attrs/attrs/issues/40>`_
+- Multiple performance improvements.
+
+
+----
+
+
+15.2.0 (2015-12-08)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added a ``convert`` argument to ``attr.ib``, which allows specifying a function to run on arguments.
+  This allows for simple type conversions, e.g. with ``attr.ib(convert=int)``.
+  `#26 <https://github.com/python-attrs/attrs/issues/26>`_
+- Speed up object creation when attribute validators are used.
+  `#28 <https://github.com/python-attrs/attrs/issues/28>`_
+
+
+----
+
+
+15.1.0 (2015-08-20)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added ``attr.validators.optional()`` that wraps other validators allowing attributes to be ``None``.
+  `#16 <https://github.com/python-attrs/attrs/issues/16>`_
+- Multi-level inheritance now works.
+  `#24 <https://github.com/python-attrs/attrs/issues/24>`_
+- ``__repr__()`` now works with non-redecorated subclasses.
+  `#20 <https://github.com/python-attrs/attrs/issues/20>`_
+
+
+----
+
+
+15.0.0 (2015-04-15)
+-------------------
+
+Changes:
+^^^^^^^^
+
+Initial release.
diff --git a/tools/third_party/attrs/CODE_OF_CONDUCT.rst b/tools/third_party/attrs/CODE_OF_CONDUCT.rst
new file mode 100644
index 0000000..4f4b8ee
--- /dev/null
+++ b/tools/third_party/attrs/CODE_OF_CONDUCT.rst
@@ -0,0 +1,55 @@
+Contributor Covenant Code of Conduct
+====================================
+
+Our Pledge
+----------
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+Our Standards
+-------------
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+Our Responsibilities
+--------------------
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+Scope
+-----
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
+Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
+Representation of a project may be further defined and clarified by project maintainers.
+
+Enforcement
+-----------
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hs@ox.cx.
+All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
+The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+Attribution
+-----------
+
+This Code of Conduct is adapted from the `Contributor Covenant <http://contributor-covenant.org>`_, version 1.4, available at http://contributor-covenant.org/version/1/4.
diff --git a/tools/third_party/attrs/CONTRIBUTING.rst b/tools/third_party/attrs/CONTRIBUTING.rst
new file mode 100644
index 0000000..9822f89
--- /dev/null
+++ b/tools/third_party/attrs/CONTRIBUTING.rst
@@ -0,0 +1,220 @@
+How To Contribute
+=================
+
+First off, thank you for considering contributing to ``attrs``!
+It's people like *you* who make it is such a great tool for everyone.
+
+This document is mainly to help you to get started by codifying tribal knowledge and expectations and make it more accessible to everyone.
+But don't be afraid to open half-finished PRs and ask questions if something is unclear!
+
+
+Support
+-------
+
+In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity:
+help your fellow developers on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_!
+
+The offical tag is ``python-attrs`` and helping out in support frees us up for improving ``attrs`` instead!
+
+
+Workflow
+--------
+
+- No contribution is too small!
+  Please submit as many fixes for typos and grammar bloopers as you can!
+- Try to limit each pull request to *one* change only.
+- *Always* add tests and docs for your code.
+  This is a hard rule; patches with missing tests or documentation can't be merged.
+- Make sure your changes pass our CI_.
+  You won't get any feedback until it's green unless you ask for it.
+- Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done.
+- Don’t break `backward compatibility`_.
+
+
+Code
+----
+
+- Obey `PEP 8`_ and `PEP 257`_.
+  We use the ``"""``\ -on-separate-lines style for docstrings:
+
+  .. code-block:: python
+
+     def func(x):
+         """
+         Do something.
+
+         :param str x: A very important parameter.
+
+         :rtype: str
+         """
+- If you add or change public APIs, tag the docstring using ``..  versionadded:: 16.0.0 WHAT`` or ``..  versionchanged:: 16.2.0 WHAT``.
+- Prefer double quotes (``"``) over single quotes (``'``) unless the string contains double quotes itself.
+
+
+Tests
+-----
+
+- Write your asserts as ``expected == actual`` to line them up nicely:
+
+  .. code-block:: python
+
+     x = f()
+
+     assert 42 == x.some_attribute
+     assert "foo" == x._a_private_attribute
+
+- To run the test suite, all you need is a recent tox_.
+  It will ensure the test suite runs with all dependencies against all Python versions just as it will on Travis CI.
+  If you lack some Python versions, you can can always limit the environments like ``tox -e py27,py35`` (in that case you may want to look into pyenv_, which makes it very easy to install many different Python versions in parallel).
+- Write `good test docstrings`_.
+- To ensure new features work well with the rest of the system, they should be also added to our `Hypothesis`_ testing strategy which you find in ``tests/util.py``.
+
+
+Documentation
+-------------
+
+- Use `semantic newlines`_ in reStructuredText_ files (files ending in ``.rst``):
+
+  .. code-block:: rst
+
+     This is a sentence.
+     This is another sentence.
+
+- If you start a new section, add two blank lines before and one blank line after the header except if two headers follow immediately after each other:
+
+  .. code-block:: rst
+
+     Last line of previous section.
+
+
+     Header of New Top Section
+     -------------------------
+
+     Header of New Section
+     ^^^^^^^^^^^^^^^^^^^^^
+
+     First line of new section.
+- If you add a new feature, demonstrate its awesomeness in the `examples page`_!
+
+
+Changelog
+^^^^^^^^^
+
+If your change is noteworthy, there needs to be a changelog entry, so our users can learn about it!
+
+To avoid merge conflicts, we use the towncrier_ package to manage our changelog.
+``towncrier`` uses independent files for each pull request -- so called *news fragments* -- instead of one monolithic changelog file.
+On release those news fragments are compiled into our ``CHANGELOG.rst``.
+
+You don't need to install ``towncrier`` yourself, you just have to abide to a few simple rules:
+
+- For each pull request, add a new file into ``changelog.d`` with a filename adhering to the ``pr#.(change|deprecation|breaking).rst`` schema:
+  For example ``changelog.d/42.change.rst`` for a non-breaking change, that is proposed in pull request number 42.
+- As with other docs, please use `semantic newlines`_ within news fragments.
+- Wrap symbols like modules, functions, or classes into double backticks so they are rendered in a monospaced font.
+- If you mention functions or other callables, add parantheses at the end of their names: ``attr.func()`` or ``attr.Class.method()``.
+  This makes the changelog a lot more readable.
+- Prefer simple past or constructions with "now".
+  For example:
+
+  + Added ``attr.validators.func()``.
+  + ``attr.func()`` now doesn't crash the Large Hadron Collider anymore.
+- If you want to reference multiple issues, copy the news fragment to another filename.
+  ``towncrier`` will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests.
+
+Example entries:
+
+  .. code-block:: rst
+
+     Added ``attr.validators.func()``.
+     The feature really *is* awesome.
+
+or:
+
+  .. code-block:: rst
+
+     ``attr.func()`` now doesn't crash the Large Hadron Collider anymore.
+     The bug really *was* nasty.
+
+----
+
+``tox -e changelog`` will render the current changelog to the terminal if you have any doubts.
+
+
+Local Development Environment
+-----------------------------
+
+You can (and should) run our test suite using tox_.
+However you’ll probably want a more traditional environment too.
+We highly recommend to develop using the latest Python 3 release because ``attrs`` tries to take advantage of modern features whenever possible.
+
+First create a `virtual environment <https://virtualenv.pypa.io/>`_.
+It’s out of scope for this document to list all the ways to manage virtual environments in Python but if you don’t have already a pet way, take some time to look at tools like `pew <https://github.com/berdario/pew>`_, `virtualfish <http://virtualfish.readthedocs.io/>`_, and `virtualenvwrapper <http://virtualenvwrapper.readthedocs.io/>`_.
+
+Next get an up to date checkout of the ``attrs`` repository:
+
+.. code-block:: bash
+
+    git checkout git@github.com:python-attrs/attrs.git
+
+Change into the newly created directory and **after activating your virtual environment** install an editable version of ``attrs``:
+
+.. code-block:: bash
+
+    cd attrs
+    pip install -e .
+
+If you run the virtual environment’s Python and try to ``import attr`` it should work!
+
+To run the test suite, you'll need our development dependencies which can be installed using
+
+.. code-block:: bash
+
+    pip install -r dev-requirements.txt
+
+At this point
+
+.. code-block:: bash
+
+   python -m pytest
+
+should work and pass!
+
+
+Governance
+----------
+
+``attrs`` is maintained by `team of volunteers`_ that is always open for new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort.
+If you'd like to join, just get a pull request merged and ask to be added in the very same pull request!
+
+**The simple rule is that everyone is welcome to review/merge pull requests of others but nobody is allowed to merge their own code.**
+
+`Hynek Schlawack`_ acts reluctantly as the BDFL_ and has the final say over design decisions.
+
+
+****
+
+Please note that this project is released with a Contributor `Code of Conduct`_.
+By participating in this project you agree to abide by its terms.
+Please report any harm to `Hynek Schlawack`_ in any way you find appropriate.
+
+Thank you for considering contributing to ``attrs``!
+
+
+.. _`Hynek Schlawack`: https://hynek.me/about/
+.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
+.. _`PEP 257`: https://www.python.org/dev/peps/pep-0257/
+.. _`good test docstrings`: https://jml.io/pages/test-docstrings.html
+.. _`Code of Conduct`: https://github.com/python-attrs/attrs/blob/master/CODE_OF_CONDUCT.rst
+.. _changelog: https://github.com/python-attrs/attrs/blob/master/CHANGELOG.rst
+.. _`backward compatibility`: http://www.attrs.org/en/latest/backward-compatibility.html
+.. _tox: https://tox.readthedocs.io/
+.. _pyenv: https://github.com/pyenv/pyenv
+.. _reStructuredText: http://www.sphinx-doc.org/en/stable/rest.html
+.. _semantic newlines: http://rhodesmill.org/brandon/2012/one-sentence-per-line/
+.. _examples page: https://github.com/python-attrs/attrs/blob/master/docs/examples.rst
+.. _Hypothesis: https://hypothesis.readthedocs.io/
+.. _CI: https://travis-ci.org/python-attrs/attrs/
+.. _`team of volunteers`: https://github.com/python-attrs
+.. _BDFL: https://en.wikipedia.org/wiki/Benevolent_dictator_for_life
+.. _towncrier: https://pypi.org/project/towncrier
diff --git a/tools/third_party/attrs/LICENSE b/tools/third_party/attrs/LICENSE
new file mode 100644
index 0000000..7ae3df9
--- /dev/null
+++ b/tools/third_party/attrs/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Hynek Schlawack
+
+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.
diff --git a/tools/third_party/attrs/MANIFEST.in b/tools/third_party/attrs/MANIFEST.in
new file mode 100644
index 0000000..03a948e
--- /dev/null
+++ b/tools/third_party/attrs/MANIFEST.in
@@ -0,0 +1,19 @@
+include LICENSE *.rst *.toml
+
+# Don't package GitHub-specific files.
+exclude *.md .travis.yml
+
+# Tests
+include tox.ini .coveragerc conftest.py dev-requirements.txt docs-requirements.txt
+recursive-include tests *.py
+
+# Documentation
+include docs/Makefile docs/docutils.conf
+recursive-include docs *.png
+recursive-include docs *.svg
+recursive-include docs *.py
+recursive-include docs *.rst
+prune docs/_build
+
+# Changelog news fragments -- is empty on releases.
+prune changelog.d
diff --git a/tools/third_party/attrs/PULL_REQUEST_TEMPLATE.md b/tools/third_party/attrs/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..34c468e
--- /dev/null
+++ b/tools/third_party/attrs/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,12 @@
+# Pull Request Check List
+
+This is just a reminder about the most common mistakes.  Please make sure that you tick all *appropriate* boxes.  But please read our [contribution guide](http://www.attrs.org/en/latest/contributing.html) at least once, it will save you unnecessary review cycles!
+
+- [ ] Added **tests** for changed code.
+- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/master/tests/utils.py).
+- [ ] Updated **documentation** for changed code.
+- [ ] Documentation in `.rst` files is written using [semantic newlines](http://rhodesmill.org/brandon/2012/one-sentence-per-line/).
+- [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).
+- [ ] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/master/changelog.d).
+
+If you have *any* questions to *any* of the points above, just **submit and ask**!  This checklist is here to *help* you, not to deter you from contributing!
diff --git a/tools/third_party/attrs/README.rst b/tools/third_party/attrs/README.rst
new file mode 100644
index 0000000..84d6aa7
--- /dev/null
+++ b/tools/third_party/attrs/README.rst
@@ -0,0 +1,129 @@
+.. image:: http://www.attrs.org/en/latest/_static/attrs_logo.png
+   :alt: attrs Logo
+
+======================================
+``attrs``: Classes Without Boilerplate
+======================================
+
+.. image:: https://readthedocs.org/projects/attrs/badge/?version=stable
+   :target: http://www.attrs.org/en/stable/?badge=stable
+   :alt: Documentation Status
+
+.. image:: https://travis-ci.org/python-attrs/attrs.svg?branch=master
+   :target: https://travis-ci.org/python-attrs/attrs
+   :alt: CI Status
+
+.. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg
+   :target: https://codecov.io/github/python-attrs/attrs
+   :alt: Test Coverage
+
+.. teaser-begin
+
+``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder <https://nedbatchelder.com/blog/200605/dunder.html>`_ methods).
+
+Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
+
+.. -spiel-end-
+
+For that, it gives you a class decorator and a way to declaratively define the attributes on that class:
+
+.. -code-begin-
+
+.. code-block:: pycon
+
+   >>> import attr
+   
+   >>> @attr.s
+   ... class SomeClass(object):
+   ...     a_number = attr.ib(default=42)
+   ...     list_of_numbers = attr.ib(default=attr.Factory(list))
+   ...
+   ...     def hard_math(self, another_number):
+   ...         return self.a_number + sum(self.list_of_numbers) * another_number
+   
+   
+   >>> sc = SomeClass(1, [1, 2, 3])
+   >>> sc
+   SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
+   
+   >>> sc.hard_math(3)
+   19
+   >>> sc == SomeClass(1, [1, 2, 3])
+   True
+   >>> sc != SomeClass(2, [3, 2, 1])
+   True
+   
+   >>> attr.asdict(sc)
+   {'a_number': 1, 'list_of_numbers': [1, 2, 3]}
+   
+   >>> SomeClass()
+   SomeClass(a_number=42, list_of_numbers=[])
+   
+   >>> C = attr.make_class("C", ["a", "b"])
+   >>> C("foo", "bar")
+   C(a='foo', b='bar')
+
+
+After *declaring* your attributes ``attrs`` gives you:
+
+- a concise and explicit overview of the class's attributes,
+- a nice human-readable ``__repr__``,
+- a complete set of comparison methods,
+- an initializer,
+- and much more,
+
+*without* writing dull boilerplate code again and again and *without* runtime performance penalties.
+
+This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving <http://www.attrs.org/en/stable/why.html#namedtuples>`_ ``namedtuple``\ s.
+Which in turn encourages you to write *small classes* that do `one thing well <https://www.destroyallsoftware.com/talks/boundaries>`_.
+Never again violate the `single responsibility principle <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_ just because implementing ``__init__`` et al is a painful drag.
+
+
+.. -testimonials-
+
+Testimonials
+============
+
+**Amber Hawkie Brown**, Twisted Release Manager and Computer Owl:
+
+  Writing a fully-functional class using attrs takes me less time than writing this testimonial.
+
+
+**Glyph Lefkowitz**, creator of `Twisted <https://twistedmatrix.com/>`_, `Automat <https://pypi.python.org/pypi/Automat>`_, and other open source software, in `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_:
+
+  I’m looking forward to is being able to program in Python-with-attrs everywhere.
+  It exerts a subtle, but positive, design influence in all the codebases I’ve see it used in.
+
+
+**Kenneth Reitz**, author of `requests <http://www.python-requests.org/>`_, Python Overlord at Heroku, `on paper no less <https://twitter.com/hynek/status/866817877650751488>`_:
+
+  attrs—classes for humans.  I like it.
+
+
+**Łukasz Langa**, prolific CPython core developer and Production Engineer at Facebook:
+
+  I'm increasingly digging your attr.ocity. Good job!
+
+
+.. -end-
+
+.. -project-information-
+
+Getting Help
+============
+
+Please use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ to get help.
+
+Answering questions of your fellow developers is also great way to help the project!
+
+
+Project Information
+===================
+
+``attrs`` is released under the `MIT <https://choosealicense.com/licenses/mit/>`_ license,
+its documentation lives at `Read the Docs <http://www.attrs.org/>`_,
+the code on `GitHub <https://github.com/python-attrs/attrs>`_,
+and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
+It’s rigorously tested on Python 2.7, 3.4+, and PyPy.
+
+If you'd like to contribute you're most welcome and we've written `a little guide <http://www.attrs.org/en/latest/contributing.html>`_ to get you started!
diff --git a/tools/py/testing/log/__init__.py b/tools/third_party/attrs/changelog.d/.gitignore
similarity index 100%
copy from tools/py/testing/log/__init__.py
copy to tools/third_party/attrs/changelog.d/.gitignore
diff --git a/tools/third_party/attrs/changelog.d/261.change.rst b/tools/third_party/attrs/changelog.d/261.change.rst
new file mode 100644
index 0000000..6867f66
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/261.change.rst
@@ -0,0 +1,4 @@
+Generated ``__hash__`` methods now hash the class type along with the attribute values.
+Until now the hashes of two classes with the same values were identical which was a bug.
+
+The generated method is also *much* faster now.
diff --git a/tools/third_party/attrs/changelog.d/284.change.rst b/tools/third_party/attrs/changelog.d/284.change.rst
new file mode 100644
index 0000000..266599d
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/284.change.rst
@@ -0,0 +1,2 @@
+``ctypes`` is optional now however if it's missing, a bare ``super()`` will not work in slots classes.
+This should only happen in special environments like Google App Engine.
diff --git a/tools/third_party/attrs/changelog.d/285.change.rst b/tools/third_party/attrs/changelog.d/285.change.rst
new file mode 100644
index 0000000..c3fbb79
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/285.change.rst
@@ -0,0 +1,2 @@
+The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance.
+In that case, the definition that is closer to the base of the class hierarchy wins.
diff --git a/tools/third_party/attrs/changelog.d/286.change.rst b/tools/third_party/attrs/changelog.d/286.change.rst
new file mode 100644
index 0000000..266599d
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/286.change.rst
@@ -0,0 +1,2 @@
+``ctypes`` is optional now however if it's missing, a bare ``super()`` will not work in slots classes.
+This should only happen in special environments like Google App Engine.
diff --git a/tools/third_party/attrs/changelog.d/287.change.rst b/tools/third_party/attrs/changelog.d/287.change.rst
new file mode 100644
index 0000000..c3fbb79
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/287.change.rst
@@ -0,0 +1,2 @@
+The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance.
+In that case, the definition that is closer to the base of the class hierarchy wins.
diff --git a/tools/third_party/attrs/changelog.d/291.change.rst b/tools/third_party/attrs/changelog.d/291.change.rst
new file mode 100644
index 0000000..0d5438f
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/291.change.rst
@@ -0,0 +1 @@
+Subclasses of ``auto_attribs=True`` can be empty now.
diff --git a/tools/third_party/attrs/changelog.d/292.change.rst b/tools/third_party/attrs/changelog.d/292.change.rst
new file mode 100644
index 0000000..0d5438f
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/292.change.rst
@@ -0,0 +1 @@
+Subclasses of ``auto_attribs=True`` can be empty now.
diff --git a/tools/third_party/attrs/changelog.d/295.change.rst b/tools/third_party/attrs/changelog.d/295.change.rst
new file mode 100644
index 0000000..6867f66
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/295.change.rst
@@ -0,0 +1,4 @@
+Generated ``__hash__`` methods now hash the class type along with the attribute values.
+Until now the hashes of two classes with the same values were identical which was a bug.
+
+The generated method is also *much* faster now.
diff --git a/tools/third_party/attrs/changelog.d/296.change.rst b/tools/third_party/attrs/changelog.d/296.change.rst
new file mode 100644
index 0000000..6867f66
--- /dev/null
+++ b/tools/third_party/attrs/changelog.d/296.change.rst
@@ -0,0 +1,4 @@
+Generated ``__hash__`` methods now hash the class type along with the attribute values.
+Until now the hashes of two classes with the same values were identical which was a bug.
+
+The generated method is also *much* faster now.
diff --git a/tools/third_party/attrs/conftest.py b/tools/third_party/attrs/conftest.py
new file mode 100644
index 0000000..ed4d652
--- /dev/null
+++ b/tools/third_party/attrs/conftest.py
@@ -0,0 +1,28 @@
+from __future__ import absolute_import, division, print_function
+
+import sys
+
+import pytest
+
+
+@pytest.fixture(scope="session")
+def C():
+    """
+    Return a simple but fully featured attrs class with an x and a y attribute.
+    """
+    import attr
+
+    @attr.s
+    class C(object):
+        x = attr.ib()
+        y = attr.ib()
+
+    return C
+
+
+collect_ignore = []
+if sys.version_info[:2] < (3, 6):
+    collect_ignore.extend([
+        "tests/test_annotations.py",
+        "tests/test_init_subclass.py",
+    ])
diff --git a/tools/third_party/attrs/dev-requirements.txt b/tools/third_party/attrs/dev-requirements.txt
new file mode 100644
index 0000000..bbd6b92
--- /dev/null
+++ b/tools/third_party/attrs/dev-requirements.txt
@@ -0,0 +1,6 @@
+coverage
+hypothesis
+pympler
+pytest
+six
+zope.interface
diff --git a/tools/third_party/attrs/docs-requirements.txt b/tools/third_party/attrs/docs-requirements.txt
new file mode 100644
index 0000000..c473e1e
--- /dev/null
+++ b/tools/third_party/attrs/docs-requirements.txt
@@ -0,0 +1,3 @@
+-e .
+sphinx
+zope.interface
diff --git a/tools/third_party/attrs/docs/Makefile b/tools/third_party/attrs/docs/Makefile
new file mode 100644
index 0000000..3143891
--- /dev/null
+++ b/tools/third_party/attrs/docs/Makefile
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/attrs.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/attrs.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/attrs"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/attrs"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/tools/third_party/attrs/docs/_static/attrs_logo.png b/tools/third_party/attrs/docs/_static/attrs_logo.png
new file mode 100644
index 0000000..11b6e6f
--- /dev/null
+++ b/tools/third_party/attrs/docs/_static/attrs_logo.png
Binary files differ
diff --git a/tools/third_party/attrs/docs/_static/attrs_logo.svg b/tools/third_party/attrs/docs/_static/attrs_logo.svg
new file mode 100644
index 0000000..1bb3e4b
--- /dev/null
+++ b/tools/third_party/attrs/docs/_static/attrs_logo.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 142 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><rect id="ArtBoard1" x="0" y="0" width="141.578" height="117.638" style="fill:none;"/><path d="M32.55,82.516c-0.191,4.027 -0.832,8.503 -1.793,13.238c-1.023,4.793 -2.301,9.269 -3.836,13.234c-3.324,1.215 -7.035,1.922 -10.808,1.922c-3.77,0 -7.161,-0.707 -9.973,-1.922c0,0 -0.387,-3.133 0.637,-7.863c1.023,-4.797 2.687,-7.93 2.687,-7.93c3.324,-1.215 7.098,-1.918 10.871,-1.918c1.856,0 3.645,0.192 5.309,0.512c0.445,-2.688 0.832,-5.309 0.832,-5.309c0,0 -2.496,-0.703 -4.988,-0.703c-4.864,0 -9.465,1.086 -11.45,1.664c0.579,-1.726 1.09,-3.328 1.727,-4.925c3.328,-1.153 7.035,-1.856 10.809,-1.856c3.773,0 7.16,0.703 9.976,1.856Zm-15.348,23.277c2.75,0 5.309,-0.703 5.309,-0.703c0,0 1.34,-4.668 2.109,-7.996c-1.152,-0.317 -3.132,-0.704 -5.371,-0.704c-2.496,0 -5.437,0.704 -5.437,0.704c0,0 -0.703,1.664 -1.215,4.031c-0.512,2.301 -0.445,3.965 -0.445,3.965c0,0 2.554,0.703 5.05,0.703Zm32.676,0c2.813,0 5.629,-0.512 7.867,-1.024c-0.769,1.981 -1.664,3.774 -2.621,5.5c-2.047,0.383 -4.16,0.641 -6.332,0.641c-3.773,0 -7.097,-0.707 -9.914,-1.922c0.129,-3.965 0.77,-8.441 1.793,-13.234l1.918,-9.082l-6.461,0c0.707,-1.789 1.473,-3.453 2.496,-5.051l5.051,0l1.344,-6.332c1.918,-0.703 3.965,-1.215 6.074,-1.535l-1.66,7.867l11.125,0c-0.703,1.789 -1.535,3.516 -2.492,5.051l-9.723,0l-1.918,9.082c-0.703,3.262 -1.469,9.336 -1.469,9.336c0,0 2.493,0.703 4.922,0.703Zm27.496,0c2.817,0 5.629,-0.512 7.867,-1.024c-0.765,1.981 -1.664,3.774 -2.621,5.5c-2.046,0.383 -4.156,0.641 -6.332,0.641c-3.773,0 -7.097,-0.707 -9.91,-1.922c0.125,-3.965 0.766,-8.441 1.789,-13.234l1.918,-9.082l-6.457,0c0.703,-1.789 1.469,-3.453 2.492,-5.051l5.055,0l1.34,-6.332c1.918,-0.703 3.965,-1.215 6.074,-1.535l-1.66,7.867l11.125,0c-0.703,1.789 -1.535,3.516 -2.492,5.051l-9.719,0l-1.922,9.082c-0.703,3.262 -1.469,9.336 -1.469,9.336c0,0 2.493,0.703 4.922,0.703Zm28.137,-25.133c1.984,0 3.84,0.191 5.629,0.578c-0.703,1.727 -1.469,3.324 -2.367,4.86c-1.406,-0.192 -2.813,-0.321 -4.348,-0.321c-2.492,0 -5.242,0.703 -5.242,0.703c0,0 -1.856,6.075 -2.496,9.274l-3.067,14.515l-5.82,0l3.07,-14.515c0.957,-4.735 2.301,-9.211 3.836,-13.238c3.325,-1.153 7.035,-1.856 10.805,-1.856Zm28.715,13.621c0,0 0.445,2.621 -0.574,7.356c-1.024,4.796 -2.559,7.351 -2.559,7.351c-3.328,1.215 -7.035,1.922 -10.809,1.922c-3.773,0 -7.16,-0.707 -9.976,-1.922c0,0 -0.383,-1.726 0.129,-4.988c1.406,0.387 6.457,1.793 10.933,1.793c2.497,0 5.375,-0.703 5.375,-0.703c0,0 0.704,-1.153 1.149,-3.453c0.512,-2.305 0.32,-3.454 0.32,-3.454c0,0 -2.558,-0.703 -5.051,-0.703c-3.902,0 -7.226,-0.64 -10.039,-1.855c0,0 -0.386,-2.879 0.383,-6.524c0.766,-3.707 2.43,-6.585 2.43,-6.585c3.324,-1.153 7.035,-1.856 10.808,-1.856c3.77,0 7.161,0.703 9.973,1.856c0,0.128 0.387,2.046 -0.062,5.179c-1.536,-0.449 -7.165,-1.918 -11,-1.918c-2.493,0 -5.372,0.703 -5.372,0.703c0,0 -0.64,1.024 -0.957,2.621c-0.32,1.598 -0.195,2.622 -0.195,2.622c0,0 2.496,0.64 5.117,0.703c3.774,0 7.164,0.64 9.977,1.855Z" style="fill:#222;fill-rule:nonzero;"/><path d="M79.12,44.539c0,-0.914 -0.066,-1.805 -0.195,-2.68l2.832,-1.687l-0.058,-0.266c-0.434,-2.004 -1.145,-3.902 -2.086,-5.66l-0.137,-0.254l-3.156,0.723c-0.766,-1.235 -1.676,-2.375 -2.703,-3.395l1.285,-3.012l-0.2,-0.175c-1.5,-1.293 -3.168,-2.391 -4.976,-3.246l-0.254,-0.118l-2.137,2.442c-1.386,-0.551 -2.855,-0.934 -4.386,-1.137l-0.727,-3.176l-0.277,-0.015c-0.477,-0.035 -0.965,-0.059 -1.457,-0.059c-1.52,0 -3,0.16 -4.43,0.457l-0.277,0.051l-0.293,3.25c-1.465,0.41 -2.864,0.996 -4.164,1.73l-2.438,-2.132l-0.238,0.156c-1.672,1.09 -3.188,2.402 -4.496,3.894l-0.18,0.207l1.684,2.821c-0.86,1.125 -1.602,2.363 -2.188,3.675l-3.219,-0.289l-0.093,0.266c-0.707,1.863 -1.157,3.852 -1.313,5.918l-0.023,0.266l3.035,1.296c-0.004,0.047 -0.004,0.098 -0.004,0.149c0,1.289 0.137,2.555 0.387,3.766l-2.782,1.656l0.071,0.273c0.55,2.028 1.379,3.926 2.453,5.672l0.14,0.227l3.223,-0.731c0.703,0.973 1.496,1.883 2.371,2.703l-1.277,2.993l0.211,0.179c1.609,1.328 3.41,2.434 5.359,3.258l0.242,0.105l2.176,-2.48c1.059,0.367 2.176,0.641 3.313,0.809l0.726,3.187l0.281,0.02c0.563,0.047 1.149,0.078 1.743,0.078c1.554,0 3.07,-0.168 4.535,-0.481l0.273,-0.058l0.293,-3.254c1.113,-0.317 2.18,-0.738 3.203,-1.242l2.477,2.164l0.234,-0.137c1.813,-1.07 3.449,-2.391 4.875,-3.918l0.196,-0.203l-1.665,-2.789c0.774,-0.938 1.45,-1.965 2.028,-3.047l3.293,0.297l0.105,-0.25c0.821,-1.84 1.391,-3.836 1.672,-5.922l0.035,-0.285l-2.976,-1.27c0.031,-0.429 0.054,-0.871 0.054,-1.32Zm-12.921,0c0,3.156 -2.555,5.711 -5.711,5.715c-3.161,-0.004 -5.711,-2.559 -5.715,-5.715c0.004,-3.152 2.554,-5.703 5.715,-5.715c3.156,0.012 5.711,2.563 5.711,5.715Z" style="fill:#222;fill-rule:nonzero;"/><path d="M103.519,15.5l-0.067,-0.242l-2.253,-0.012c-0.317,-0.934 -0.747,-1.832 -1.258,-2.664l1.351,-1.844l-0.136,-0.191c-0.809,-1.121 -1.778,-2.133 -2.868,-3.004l-0.195,-0.156l-1.84,1.316c-0.832,-0.578 -1.75,-1.059 -2.722,-1.437l0.011,-2.266l-0.23,-0.074c-0.328,-0.098 -0.649,-0.192 -0.992,-0.274c-1.028,-0.242 -2.063,-0.367 -3.086,-0.402l-0.25,-0.004l-0.715,2.16c-1.028,0.047 -2.047,0.215 -3.024,0.496l-1.308,-1.84l-0.238,0.09c-1.317,0.469 -2.559,1.121 -3.692,1.934l-0.195,0.14l0.695,2.18c-0.742,0.617 -1.422,1.313 -2.016,2.09l-2.14,-0.711l-0.137,0.211c-0.773,1.156 -1.402,2.434 -1.844,3.816l-0.066,0.223l1.852,1.363c-0.012,0.02 -0.016,0.043 -0.02,0.059c-0.207,0.867 -0.312,1.726 -0.34,2.578l-2.156,0.684l0.012,0.246c0.054,1.468 0.308,2.902 0.761,4.261l0.075,0.227l2.308,0.012c0.313,0.75 0.692,1.465 1.137,2.148l-1.348,1.828l0.149,0.196c0.886,1.16 1.929,2.207 3.129,3.074l0.191,0.14l1.867,-1.335c0.641,0.402 1.328,0.75 2.047,1.039l-0.016,2.281l0.231,0.074c0.387,0.121 0.777,0.234 1.18,0.332c1.062,0.246 2.121,0.379 3.168,0.402l0.242,0.004l0.718,-2.164c0.782,-0.035 1.547,-0.144 2.297,-0.316l1.336,1.863l0.231,-0.074c1.398,-0.43 2.73,-1.074 3.945,-1.887l0.203,-0.137l-0.679,-2.16c0.648,-0.504 1.257,-1.066 1.804,-1.687l2.184,0.726l0.144,-0.191c0.86,-1.117 1.567,-2.387 2.09,-3.762l0.09,-0.238l-1.816,-1.336c0.086,-0.277 0.16,-0.559 0.23,-0.84c0.141,-0.613 0.238,-1.219 0.293,-1.82l2.191,-0.695l0,-0.239c0,-0.086 0.008,-0.172 0.008,-0.261c-0.004,-1.34 -0.183,-2.661 -0.523,-3.93Zm-10.766,3.941c0,0.301 -0.031,0.602 -0.101,0.903c-0.43,1.8 -2.032,3.015 -3.805,3.015c-0.301,0 -0.598,-0.035 -0.898,-0.109c-1.805,-0.414 -3.012,-2.024 -3.012,-3.797c0,-0.297 0.031,-0.594 0.097,-0.898c0.422,-1.801 2.032,-3.016 3.809,-3.016c0.289,0 0.594,0.031 0.898,0.105c1.793,0.418 3.012,2.032 3.012,3.797Z" style="fill:#222;fill-rule:nonzero;"/></svg>
\ No newline at end of file
diff --git a/tools/third_party/attrs/docs/api.rst b/tools/third_party/attrs/docs/api.rst
new file mode 100644
index 0000000..e2acb74
--- /dev/null
+++ b/tools/third_party/attrs/docs/api.rst
@@ -0,0 +1,428 @@
+.. _api:
+
+API Reference
+=============
+
+.. currentmodule:: attr
+
+``attrs`` works by decorating a class using :func:`attr.s` and then optionally defining attributes on the class using :func:`attr.ib`.
+
+.. note::
+
+   When this documentation speaks about "``attrs`` attributes" it means those attributes that are defined using :func:`attr.ib` in the class body.
+
+What follows is the API explanation, if you'd like a more hands-on introduction, have a look at :doc:`examples`.
+
+
+
+Core
+----
+
+.. autofunction:: attr.s(these=None, repr_ns=None, repr=True, cmp=True, hash=None, init=True, slots=False, frozen=False, str=False)
+
+   .. note::
+
+      ``attrs`` also comes with a serious business alias ``attr.attrs``.
+
+   For example:
+
+   .. doctest::
+
+      >>> import attr
+      >>> @attr.s
+      ... class C(object):
+      ...     _private = attr.ib()
+      >>> C(private=42)
+      C(_private=42)
+      >>> class D(object):
+      ...     def __init__(self, x):
+      ...         self.x = x
+      >>> D(1)
+      <D object at ...>
+      >>> D = attr.s(these={"x": attr.ib()}, init=False)(D)
+      >>> D(1)
+      D(x=1)
+
+
+.. autofunction:: attr.ib
+
+   .. note::
+
+      ``attrs`` also comes with a serious business alias ``attr.attrib``.
+
+   The object returned by :func:`attr.ib` also allows for setting the default and the validator using decorators:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      ...     @x.validator
+      ...     def name_can_be_anything(self, attribute, value):
+      ...         if value < 0:
+      ...             raise ValueError("x must be positive")
+      ...     @y.default
+      ...     def name_does_not_matter(self):
+      ...         return self.x + 1
+      >>> C(1)
+      C(x=1, y=2)
+      >>> C(-1)
+      Traceback (most recent call last):
+          ...
+      ValueError: x must be positive
+
+.. autoclass:: attr.Attribute
+
+   Instances of this class are frequently used for introspection purposes like:
+
+   - :func:`fields` returns a tuple of them.
+   - Validators get them passed as the first argument.
+
+   .. warning::
+
+       You should never instantiate this class yourself!
+
+   .. doctest::
+
+      >>> import attr
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      >>> attr.fields(C).x
+      Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None)
+
+
+.. autofunction:: attr.make_class
+
+   This is handy if you want to programmatically create classes.
+
+   For example:
+
+   .. doctest::
+
+      >>> C1 = attr.make_class("C1", ["x", "y"])
+      >>> C1(1, 2)
+      C1(x=1, y=2)
+      >>> C2 = attr.make_class("C2", {"x": attr.ib(default=42),
+      ...                             "y": attr.ib(default=attr.Factory(list))})
+      >>> C2()
+      C2(x=42, y=[])
+
+
+.. autoclass:: attr.Factory
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(default=attr.Factory(list))
+      ...     y = attr.ib(default=attr.Factory(
+      ...         lambda self: set(self.x),
+      ...         takes_self=True)
+      ...     )
+      >>> C()
+      C(x=[], y=set())
+      >>> C([1, 2, 3])
+      C(x=[1, 2, 3], y={1, 2, 3})
+
+
+.. autoexception:: attr.exceptions.FrozenInstanceError
+.. autoexception:: attr.exceptions.AttrsAttributeNotFoundError
+.. autoexception:: attr.exceptions.NotAnAttrsClassError
+.. autoexception:: attr.exceptions.DefaultAlreadySetError
+.. autoexception:: attr.exceptions.UnannotatedAttributeError
+
+   For example::
+
+       @attr.s(auto_attribs=True)
+       class C:
+           x: int
+           y = attr.ib()
+
+
+Influencing Initialization
+++++++++++++++++++++++++++
+
+Generally speaking, it's best to keep logic out of your ``__init__``.
+The moment you need a finer control over how your class is instantiated, it's usually best to use a classmethod factory or to apply the `builder pattern <https://en.wikipedia.org/wiki/Builder_pattern>`_.
+
+However, sometimes you need to do that one quick thing after your class is initialized.
+And for that ``attrs`` offers the ``__attrs_post_init__`` hook that is automatically detected and run after ``attrs`` is done initializing your instance:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib(init=False)
+   ...     def __attrs_post_init__(self):
+   ...         self.y = self.x + 1
+   >>> C(1)
+   C(x=1, y=2)
+
+Please note that you can't directly set attributes on frozen classes:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class FrozenBroken(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib(init=False)
+   ...     def __attrs_post_init__(self):
+   ...         self.y = self.x + 1
+   >>> FrozenBroken(1)
+   Traceback (most recent call last):
+      ...
+   attr.exceptions.FrozenInstanceError: can't set attribute
+
+If you need to set attributes on a frozen class, you'll have to resort to the :ref:`same trick <how-frozen>` as ``attrs`` and use :meth:`object.__setattr__`:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class Frozen(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib(init=False)
+   ...     def __attrs_post_init__(self):
+   ...         object.__setattr__(self, "y", self.x + 1)
+   >>> Frozen(1)
+   Frozen(x=1, y=2)
+
+
+.. _helpers:
+
+Helpers
+-------
+
+``attrs`` comes with a bunch of helper methods that make working with it easier:
+
+.. autofunction:: attr.fields
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.fields(C)
+      (Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None))
+      >>> attr.fields(C)[1]
+      Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None)
+      >>> attr.fields(C).y is attr.fields(C)[1]
+      True
+
+
+.. autofunction:: attr.has
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     pass
+      >>> attr.has(C)
+      True
+      >>> attr.has(object)
+      False
+
+
+.. autofunction:: attr.asdict
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.asdict(C(1, C(2, 3)))
+      {'x': 1, 'y': {'x': 2, 'y': 3}}
+
+
+.. autofunction:: attr.astuple
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.astuple(C(1,2))
+      (1, 2)
+
+``attrs`` includes some handy helpers for filtering:
+
+.. autofunction:: attr.filters.include
+
+.. autofunction:: attr.filters.exclude
+
+See :ref:`asdict` for examples.
+
+.. autofunction:: attr.evolve
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> i1 = C(1, 2)
+      >>> i1
+      C(x=1, y=2)
+      >>> i2 = attr.evolve(i1, y=3)
+      >>> i2
+      C(x=1, y=3)
+      >>> i1 == i2
+      False
+
+   ``evolve`` creates a new instance using ``__init__``.
+   This fact has several implications:
+
+   * private attributes should be specified without the leading underscore, just like in ``__init__``.
+   * attributes with ``init=False`` can't be set with ``evolve``.
+   * the usual ``__init__`` validators will validate the new values.
+
+.. autofunction:: validate
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.instance_of(int))
+      >>> i = C(1)
+      >>> i.x = "1"
+      >>> attr.validate(i)
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got '1' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None), <type 'int'>, '1')
+
+
+Validators can be globally disabled if you want to run them only in development and tests but not in production because you fear their performance impact:
+
+.. autofunction:: set_run_validators
+
+.. autofunction:: get_run_validators
+
+
+.. _api_validators:
+
+Validators
+----------
+
+``attrs`` comes with some common validators in the ``attrs.validators`` module:
+
+
+.. autofunction:: attr.validators.instance_of
+
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.instance_of(int))
+      >>> C(42)
+      C(x=42)
+      >>> C("42")
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
+      >>> C(None)
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got None that is a <type 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None), <type 'int'>, None)
+
+.. autofunction:: attr.validators.in_
+
+   For example:
+
+   .. doctest::
+
+       >>> import enum
+       >>> class State(enum.Enum):
+       ...     ON = "on"
+       ...     OFF = "off"
+       >>> @attr.s
+       ... class C(object):
+       ...     state = attr.ib(validator=attr.validators.in_(State))
+       ...     val = attr.ib(validator=attr.validators.in_([1, 2, 3]))
+       >>> C(State.ON, 1)
+       C(state=<State.ON: 'on'>, val=1)
+       >>> C("on", 1)
+       Traceback (most recent call last):
+          ...
+       ValueError: 'state' must be in <enum 'State'> (got 'on')
+       >>> C(State.ON, 4)
+       Traceback (most recent call last):
+          ...
+       ValueError: 'val' must be in [1, 2, 3] (got 4)
+
+.. autofunction:: attr.validators.provides
+
+.. autofunction:: attr.validators.and_
+
+   For convenience, it's also possible to pass a list to :func:`attr.ib`'s validator argument.
+
+   Thus the following two statements are equivalent::
+
+      x = attr.ib(validator=attr.validators.and_(v1, v2, v3))
+      x = attr.ib(validator=[v1, v2, v3])
+
+.. autofunction:: attr.validators.optional
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(int)))
+      >>> C(42)
+      C(x=42)
+      >>> C("42")
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
+      >>> C(None)
+      C(x=None)
+
+
+Converters
+----------
+
+.. autofunction:: attr.converters.optional
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(convert=attr.converters.optional(int))
+      >>> C(None)
+      C(x=None)
+      >>> C(42)
+      C(x=42)
+
+
+Deprecated APIs
+---------------
+
+The serious business aliases used to be called ``attr.attributes`` and ``attr.attr``.
+There are no plans to remove them but they shouldn't be used in new code.
+
+.. autofunction:: assoc
diff --git a/tools/third_party/attrs/docs/backward-compatibility.rst b/tools/third_party/attrs/docs/backward-compatibility.rst
new file mode 100644
index 0000000..52559f8
--- /dev/null
+++ b/tools/third_party/attrs/docs/backward-compatibility.rst
@@ -0,0 +1,19 @@
+Backward Compatibility
+======================
+
+.. currentmodule:: attr
+
+``attrs`` has a very strong backward compatibility policy that is inspired by the policy of the `Twisted framework <https://twistedmatrix.com/trac/wiki/CompatibilityPolicy>`_.
+
+Put simply, you shouldn't ever be afraid to upgrade ``attrs`` if you're only using its public APIs.
+If there will ever be a need to break compatibility, it will be announced in the :doc:`changelog` and raise a ``DeprecationWarning`` for a year (if possible) before it's finally really broken.
+
+
+.. _exemption:
+
+.. warning::
+
+   The structure of the :class:`attr.Attribute` class is exempt from this rule.
+   It *will* change in the future, but since it should be considered read-only, that shouldn't matter.
+
+   However if you intend to build extensions on top of ``attrs`` you have to anticipate that.
diff --git a/tools/third_party/attrs/docs/changelog.rst b/tools/third_party/attrs/docs/changelog.rst
new file mode 100644
index 0000000..565b052
--- /dev/null
+++ b/tools/third_party/attrs/docs/changelog.rst
@@ -0,0 +1 @@
+.. include:: ../CHANGELOG.rst
diff --git a/tools/third_party/attrs/docs/conf.py b/tools/third_party/attrs/docs/conf.py
new file mode 100644
index 0000000..1cdb07c
--- /dev/null
+++ b/tools/third_party/attrs/docs/conf.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+import os
+import re
+
+
+def read(*parts):
+    """
+    Build an absolute path from *parts* and and return the contents of the
+    resulting file.  Assume UTF-8 encoding.
+    """
+    here = os.path.abspath(os.path.dirname(__file__))
+    with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f:
+        return f.read()
+
+
+def find_version(*file_paths):
+    """
+    Build a path from *file_paths* and search for a ``__version__``
+    string inside.
+    """
+    version_file = read(*file_paths)
+    version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
+                              version_file, re.M)
+    if version_match:
+        return version_match.group(1)
+    raise RuntimeError("Unable to find version string.")
+
+
+# -- General configuration ------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.todo',
+]
+
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'attrs'
+copyright = u'2015, Hynek Schlawack'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+release = find_version("../src/attr/__init__.py")
+version = release.rsplit(u".", 1)[0]
+# The full version, including alpha/beta/rc tags.
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+
+html_theme = "alabaster"
+html_theme_options = {
+    "font_family": '"Avenir Next", Calibri, "PT Sans", sans-serif',
+    "head_font_family": '"Avenir Next", Calibri, "PT Sans", sans-serif',
+    "font_size": "18px",
+    "page_width": "980px",
+}
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = "_static/attrs_logo.svg"
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If false, no module index is generated.
+html_domain_indices = True
+
+# If false, no index is generated.
+html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = False
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'attrsdoc'
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'attrs', u'attrs Documentation',
+     [u'Hynek Schlawack'], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    ('index', 'attrs', u'attrs Documentation',
+     u'Hynek Schlawack', 'attrs', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+intersphinx_mapping = {
+    "https://docs.python.org/3": None,
+}
+
+# Allow non-local URIs so we can have images in CHANGELOG etc.
+suppress_warnings = ['image.nonlocal_uri']
diff --git a/tools/third_party/attrs/docs/contributing.rst b/tools/third_party/attrs/docs/contributing.rst
new file mode 100644
index 0000000..1d519c3
--- /dev/null
+++ b/tools/third_party/attrs/docs/contributing.rst
@@ -0,0 +1,5 @@
+.. _contributing:
+
+.. include:: ../CONTRIBUTING.rst
+
+.. include:: ../CODE_OF_CONDUCT.rst
diff --git a/tools/third_party/attrs/docs/docutils.conf b/tools/third_party/attrs/docs/docutils.conf
new file mode 100644
index 0000000..db8ca82
--- /dev/null
+++ b/tools/third_party/attrs/docs/docutils.conf
@@ -0,0 +1,3 @@
+[parsers]
+[restructuredtext parser]
+smart_quotes=yes
diff --git a/tools/third_party/attrs/docs/examples.rst b/tools/third_party/attrs/docs/examples.rst
new file mode 100644
index 0000000..4432e8f
--- /dev/null
+++ b/tools/third_party/attrs/docs/examples.rst
@@ -0,0 +1,705 @@
+.. _examples:
+
+``attrs`` by Example
+====================
+
+
+Basics
+------
+
+The simplest possible usage is:
+
+.. doctest::
+
+   >>> import attr
+   >>> @attr.s
+   ... class Empty(object):
+   ...     pass
+   >>> Empty()
+   Empty()
+   >>> Empty() == Empty()
+   True
+   >>> Empty() is Empty()
+   False
+
+So in other words: ``attrs`` is useful even without actual attributes!
+
+But you'll usually want some data on your classes, so let's add some:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class Coordinates(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+
+By default, all features are added, so you immediately have a fully functional data class with a nice ``repr`` string and comparison methods.
+
+.. doctest::
+
+   >>> c1 = Coordinates(1, 2)
+   >>> c1
+   Coordinates(x=1, y=2)
+   >>> c2 = Coordinates(x=2, y=1)
+   >>> c2
+   Coordinates(x=2, y=1)
+   >>> c1 == c2
+   False
+
+As shown, the generated ``__init__`` method allows for both positional and keyword arguments.
+
+If playful naming turns you off, ``attrs`` comes with serious business aliases:
+
+.. doctest::
+
+   >>> from attr import attrs, attrib
+   >>> @attrs
+   ... class SeriousCoordinates(object):
+   ...     x = attrib()
+   ...     y = attrib()
+   >>> SeriousCoordinates(1, 2)
+   SeriousCoordinates(x=1, y=2)
+   >>> attr.fields(Coordinates) == attr.fields(SeriousCoordinates)
+   True
+
+For private attributes, ``attrs`` will strip the leading underscores for keyword arguments:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     _x = attr.ib()
+   >>> C(x=1)
+   C(_x=1)
+
+If you want to initialize your private attributes yourself, you can do that too:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     _x = attr.ib(init=False, default=42)
+   >>> C()
+   C(_x=42)
+   >>> C(23)
+   Traceback (most recent call last):
+      ...
+   TypeError: __init__() takes exactly 1 argument (2 given)
+
+An additional way of defining attributes is supported too.
+This is useful in times when you want to enhance classes that are not yours (nice ``__repr__`` for Django models anyone?):
+
+.. doctest::
+
+   >>> class SomethingFromSomeoneElse(object):
+   ...     def __init__(self, x):
+   ...         self.x = x
+   >>> SomethingFromSomeoneElse = attr.s(
+   ...     these={
+   ...         "x": attr.ib()
+   ...     }, init=False)(SomethingFromSomeoneElse)
+   >>> SomethingFromSomeoneElse(1)
+   SomethingFromSomeoneElse(x=1)
+
+
+`Subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, but ``attrs`` will still do what you'd hope for:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class A(object):
+   ...     a = attr.ib()
+   ...     def get_a(self):
+   ...         return self.a
+   >>> @attr.s
+   ... class B(object):
+   ...     b = attr.ib()
+   >>> @attr.s
+   ... class C(B, A):
+   ...     c = attr.ib()
+   >>> i = C(1, 2, 3)
+   >>> i
+   C(a=1, b=2, c=3)
+   >>> i == C(1, 2, 3)
+   True
+   >>> i.get_a()
+   1
+
+The order of the attributes is defined by the `MRO <https://www.python.org/download/releases/2.3/mro/>`_.
+
+In Python 3, classes defined within other classes are `detected <https://www.python.org/dev/peps/pep-3155/>`_ and reflected in the ``__repr__``.
+In Python 2 though, it's impossible.
+Therefore ``@attr.s`` comes with the ``repr_ns`` option to set it manually:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     @attr.s(repr_ns="C")
+   ...     class D(object):
+   ...         pass
+   >>> C.D()
+   C.D()
+
+``repr_ns`` works on both Python 2 and 3.
+On Python 3 it overrides the implicit detection.
+
+
+.. _asdict:
+
+Converting to Collections Types
+-------------------------------
+
+When you have a class with data, it often is very convenient to transform that class into a :class:`dict` (for example if you want to serialize it to JSON):
+
+.. doctest::
+
+   >>> attr.asdict(Coordinates(x=1, y=2))
+   {'x': 1, 'y': 2}
+
+Some fields cannot or should not be transformed.
+For that, :func:`attr.asdict` offers a callback that decides whether an attribute should be included:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class UserList(object):
+   ...     users = attr.ib()
+   >>> @attr.s
+   ... class User(object):
+   ...     email = attr.ib()
+   ...     password = attr.ib()
+   >>> attr.asdict(UserList([User("jane@doe.invalid", "s33kred"),
+   ...                       User("joe@doe.invalid", "p4ssw0rd")]),
+   ...             filter=lambda attr, value: attr.name != "password")
+   {'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]}
+
+For the common case where you want to :func:`include <attr.filters.include>` or :func:`exclude <attr.filters.exclude>` certain types or attributes, ``attrs`` ships with a few helpers:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class User(object):
+   ...     login = attr.ib()
+   ...     password = attr.ib()
+   ...     id = attr.ib()
+   >>> attr.asdict(
+   ...     User("jane", "s33kred", 42),
+   ...     filter=attr.filters.exclude(attr.fields(User).password, int))
+   {'login': 'jane'}
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   ...     z = attr.ib()
+   >>> attr.asdict(C("foo", "2", 3),
+   ...             filter=attr.filters.include(int, attr.fields(C).x))
+   {'x': 'foo', 'z': 3}
+
+Other times, all you want is a tuple and ``attrs`` won't let you down:
+
+.. doctest::
+
+   >>> import sqlite3
+   >>> import attr
+   >>> @attr.s
+   ... class Foo:
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   >>> foo = Foo(2, 3)
+   >>> with sqlite3.connect(":memory:") as conn:
+   ...    c = conn.cursor()
+   ...    c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS
+   ...    c.execute("INSERT INTO foo VALUES (?, ?)", attr.astuple(foo)) #doctest: +ELLIPSIS
+   ...    foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone())
+   <sqlite3.Cursor object at ...>
+   <sqlite3.Cursor object at ...>
+   >>> foo == foo2
+   True
+
+
+
+
+Defaults
+--------
+
+Sometimes you want to have default values for your initializer.
+And sometimes you even want mutable objects as default values (ever used accidentally ``def f(arg=[])``?).
+``attrs`` has you covered in both cases:
+
+.. doctest::
+
+   >>> import collections
+   >>> @attr.s
+   ... class Connection(object):
+   ...     socket = attr.ib()
+   ...     @classmethod
+   ...     def connect(cls, db_string):
+   ...        # ... connect somehow to db_string ...
+   ...        return cls(socket=42)
+   >>> @attr.s
+   ... class ConnectionPool(object):
+   ...     db_string = attr.ib()
+   ...     pool = attr.ib(default=attr.Factory(collections.deque))
+   ...     debug = attr.ib(default=False)
+   ...     def get_connection(self):
+   ...         try:
+   ...             return self.pool.pop()
+   ...         except IndexError:
+   ...             if self.debug:
+   ...                 print("New connection!")
+   ...             return Connection.connect(self.db_string)
+   ...     def free_connection(self, conn):
+   ...         if self.debug:
+   ...             print("Connection returned!")
+   ...         self.pool.appendleft(conn)
+   ...
+   >>> cp = ConnectionPool("postgres://localhost")
+   >>> cp
+   ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False)
+   >>> conn = cp.get_connection()
+   >>> conn
+   Connection(socket=42)
+   >>> cp.free_connection(conn)
+   >>> cp
+   ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False)
+
+More information on why class methods for constructing objects are awesome can be found in this insightful `blog post <http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html>`_.
+
+Default factories can also be set using a decorator.
+The method receives the partially initialized instance which enables you to base a default value on other attributes:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(default=1)
+   ...     y = attr.ib()
+   ...     @y.default
+   ...     def name_does_not_matter(self):
+   ...         return self.x + 1
+   >>> C()
+   C(x=1, y=2)
+
+
+.. _examples_validators:
+
+Validators
+----------
+
+Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments.
+
+``attrs`` offers two ways to define validators for each attribute and it's up to you to choose which one suites better your style and project.
+
+
+Decorator
+~~~~~~~~~
+
+The more straightforward way is by using the attribute's ``validator`` method as a decorator.
+The method has to accept three arguments:
+
+#. the *instance* that's being validated (aka ``self``),
+#. the *attribute* that it's validating, and finally
+#. the *value* that is passed for it.
+
+If the value does not pass the validator's standards, it just raises an appropriate exception.
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     @x.validator
+   ...     def check(self, attribute, value):
+   ...         if value > 42:
+   ...             raise ValueError("x must be smaller or equal to 42")
+   >>> C(42)
+   C(x=42)
+   >>> C(43)
+   Traceback (most recent call last):
+      ...
+   ValueError: x must be smaller or equal to 42
+
+
+Callables
+~~~~~~~~~
+
+If you want to re-use your validators, you should have a look at the ``validator`` argument to :func:`attr.ib()`.
+
+It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach.
+
+Since the validators runs *after* the instance is initialized, you can refer to other attributes while validating:
+
+.. doctest::
+
+   >>> def x_smaller_than_y(instance, attribute, value):
+   ...     if value >= instance.y:
+   ...         raise ValueError("'x' has to be smaller than 'y'!")
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=[attr.validators.instance_of(int),
+   ...                            x_smaller_than_y])
+   ...     y = attr.ib()
+   >>> C(x=3, y=4)
+   C(x=3, y=4)
+   >>> C(x=4, y=3)
+   Traceback (most recent call last):
+      ...
+   ValueError: 'x' has to be smaller than 'y'!
+
+This example also shows of some syntactic sugar for using the :func:`attr.validators.and_` validator: if you pass a list, all validators have to pass.
+
+``attrs`` won't intercept your changes to those attributes but you can always call :func:`attr.validate` on any instance to verify that it's still valid:
+
+.. doctest::
+
+   >>> i = C(4, 5)
+   >>> i.x = 5  # works, no magic here
+   >>> attr.validate(i)
+   Traceback (most recent call last):
+      ...
+   ValueError: 'x' has to be smaller than 'y'!
+
+``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=attr.validators.instance_of(int))
+   >>> C(42)
+   C(x=42)
+   >>> C("42")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
+
+Of course you can mix and match the two approaches at your convenience:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=attr.validators.instance_of(int))
+   ...     @x.validator
+   ...     def fits_byte(self, attribute, value):
+   ...         if not 0 < value < 256:
+   ...             raise ValueError("value out of bounds")
+   >>> C(128)
+   C(x=128)
+   >>> C("128")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, convert=None, metadata=mappingproxy({}), type=None), <class 'int'>, '128')
+   >>> C(256)
+   Traceback (most recent call last):
+      ...
+   ValueError: value out of bounds
+
+And finally you can disable validators globally:
+
+   >>> attr.set_run_validators(False)
+   >>> C("128")
+   C(x='128')
+   >>> attr.set_run_validators(True)
+   >>> C("128")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, convert=None, metadata=mappingproxy({}), type=None), <class 'int'>, '128')
+
+
+Conversion
+----------
+
+Attributes can have a ``convert`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
+This can be useful for doing type-conversions on values that you don't want to force your callers to do.
+
+.. doctest::
+
+    >>> @attr.s
+    ... class C(object):
+    ...     x = attr.ib(convert=int)
+    >>> o = C("1")
+    >>> o.x
+    1
+
+Converters are run *before* validators, so you can use validators to check the final form of the value.
+
+.. doctest::
+
+    >>> def validate_x(instance, attribute, value):
+    ...     if value < 0:
+    ...         raise ValueError("x must be be at least 0.")
+    >>> @attr.s
+    ... class C(object):
+    ...     x = attr.ib(convert=int, validator=validate_x)
+    >>> o = C("0")
+    >>> o.x
+    0
+    >>> C("-1")
+    Traceback (most recent call last):
+        ...
+    ValueError: x must be be at least 0.
+
+
+.. _metadata:
+
+Metadata
+--------
+
+All ``attrs`` attributes may include arbitrary metadata in the form of a read-only dictionary.
+
+.. doctest::
+
+    >>> @attr.s
+    ... class C(object):
+    ...    x = attr.ib(metadata={'my_metadata': 1})
+    >>> attr.fields(C).x.metadata
+    mappingproxy({'my_metadata': 1})
+    >>> attr.fields(C).x.metadata['my_metadata']
+    1
+
+Metadata is not used by ``attrs``, and is meant to enable rich functionality in third-party libraries.
+The metadata dictionary follows the normal dictionary rules: keys need to be hashable, and both keys and values are recommended to be immutable.
+
+If you're the author of a third-party library with ``attrs`` integration, please see :ref:`Extending Metadata <extending_metadata>`.
+
+
+Types
+-----
+
+``attrs`` also allows you to associate a type with an attribute using either the *type* argument to :func:`attr.ib` or -- as of Python 3.6 -- using `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_-annotations:
+
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C:
+   ...     x = attr.ib(type=int)
+   ...     y: int = attr.ib()
+   >>> attr.fields(C).x.type
+   <class 'int'>
+   >>> attr.fields(C).y.type
+   <class 'int'>
+
+If you don't mind annotating *all* attributes, you can even drop the :func:`attr.ib` and assign default values instead:
+
+.. doctest::
+
+   >>> import typing
+   >>> @attr.s(auto_attribs=True)
+   ... class AutoC:
+   ...     cls_var: typing.ClassVar[int] = 5  # this one is ignored
+   ...     l: typing.List[int] = attr.Factory(list)
+   ...     x: int = 1
+   ...     foo: str = attr.ib(
+   ...          default="every attrib needs a type if auto_attribs=True"
+   ...     )
+   ...     bar: typing.Any = None
+   >>> attr.fields(AutoC).l.type
+   typing.List[int]
+   >>> attr.fields(AutoC).x.type
+   <class 'int'>
+   >>> attr.fields(AutoC).foo.type
+   <class 'str'>
+   >>> attr.fields(AutoC).bar.type
+   typing.Any
+   >>> AutoC()
+   AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None)
+   >>> AutoC.cls_var
+   5
+
+
+.. warning::
+
+   ``attrs`` itself doesn't have any features that work on top of type metadata *yet*.
+   However it's useful for writing your own validators or serialization frameworks.
+
+
+.. _slots:
+
+Slots
+-----
+
+By default, instances of classes have a dictionary for attribute storage.
+This wastes space for objects having very few data attributes.
+The space consumption can become significant when creating large numbers of instances.
+
+Normal Python classes can avoid using a separate dictionary for each instance of a class by `defining <https://docs.python.org/3/reference/datamodel.html#slots>`_ ``__slots__``.
+For ``attrs`` classes it's enough to set ``slots=True``:
+
+.. doctest::
+
+   >>> @attr.s(slots=True)
+   ... class Coordinates(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+
+
+.. note::
+
+    ``attrs`` slot classes can inherit from other classes just like non-slot classes, but some of the benefits of slot classes are lost if you do that.
+    If you must inherit from other classes, try to inherit only from other slot classes.
+
+Slot classes are a little different than ordinary, dictionary-backed classes:
+
+- Assigning to a non-existent attribute of an instance will result in an ``AttributeError`` being raised.
+  Depending on your needs, this might be a good thing since it will let you catch typos early.
+  This is not the case if your class inherits from any non-slot classes.
+
+  .. doctest::
+
+     >>> @attr.s(slots=True)
+     ... class Coordinates(object):
+     ...     x = attr.ib()
+     ...     y = attr.ib()
+     ...
+     >>> c = Coordinates(x=1, y=2)
+     >>> c.z = 3
+     Traceback (most recent call last):
+         ...
+     AttributeError: 'Coordinates' object has no attribute 'z'
+
+- Since non-slot classes cannot be turned into slot classes after they have been created, ``attr.s(slots=True)`` will *replace* the class it is applied to with a copy.
+  In almost all cases this isn't a problem, but we mention it for the sake of completeness.
+
+  * One notable problem is that certain metaclass features like ``__init_subclass__`` do not work with slot classes.
+
+- Using :mod:`pickle` with slot classes requires pickle protocol 2 or greater.
+  Python 2 uses protocol 0 by default so the protocol needs to be specified.
+  Python 3 uses protocol 3 by default.
+  You can support protocol 0 and 1 by implementing :meth:`__getstate__ <object.__getstate__>` and :meth:`__setstate__ <object.__setstate__>` methods yourself.
+  Those methods are created for frozen slot classes because they won't pickle otherwise.
+  `Think twice <https://www.youtube.com/watch?v=7KnfGDajDQw>`_ before using :mod:`pickle` though.
+
+- As always with slot classes, you must specify a ``__weakref__`` slot if you wish for the class to be weak-referenceable.
+  Here's how it looks using ``attrs``:
+
+  .. doctest::
+
+    >>> import weakref
+    >>> @attr.s(slots=True)
+    ... class C(object):
+    ...     __weakref__ = attr.ib(init=False, hash=False, repr=False, cmp=False)
+    ...     x = attr.ib()
+    >>> c = C(1)
+    >>> weakref.ref(c)
+    <weakref at 0x...; to 'C' at 0x...>
+
+All in all, setting ``slots=True`` is usually a very good idea.
+
+
+Immutability
+------------
+
+Sometimes you have instances that shouldn't be changed after instantiation.
+Immutability is especially popular in functional programming and is generally a very good thing.
+If you'd like to enforce it, ``attrs`` will try to help:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class C(object):
+   ...     x = attr.ib()
+   >>> i = C(1)
+   >>> i.x = 2
+   Traceback (most recent call last):
+      ...
+   attr.exceptions.FrozenInstanceError: can't set attribute
+   >>> i.x
+   1
+
+Please note that true immutability is impossible in Python but it will :ref:`get <how-frozen>` you 99% there.
+By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example.
+
+In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes.
+In Clojure that function is called `assoc <https://clojuredocs.org/clojure.core/assoc>`_ and ``attrs`` shamelessly imitates it: :func:`attr.evolve`:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   >>> i1 = C(1, 2)
+   >>> i1
+   C(x=1, y=2)
+   >>> i2 = attr.evolve(i1, y=3)
+   >>> i2
+   C(x=1, y=3)
+   >>> i1 == i2
+   False
+
+
+Other Goodies
+-------------
+
+Sometimes you may want to create a class programmatically.
+``attrs`` won't let you down and gives you :func:`attr.make_class` :
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C1(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   >>> C2 = attr.make_class("C2", ["x", "y"])
+   >>> attr.fields(C1) == attr.fields(C2)
+   True
+
+You can still have power over the attributes if you pass a dictionary of name: ``attr.ib`` mappings and can pass arguments to ``@attr.s``:
+
+.. doctest::
+
+   >>> C = attr.make_class("C", {"x": attr.ib(default=42),
+   ...                           "y": attr.ib(default=attr.Factory(list))},
+   ...                     repr=False)
+   >>> i = C()
+   >>> i  # no repr added!
+   <__main__.C object at ...>
+   >>> i.x
+   42
+   >>> i.y
+   []
+
+If you need to dynamically make a class with :func:`attr.make_class` and it needs to be a subclass of something else than ``object``, use the ``bases`` argument:
+
+.. doctest::
+
+  >>> class D(object):
+  ...    def __eq__(self, other):
+  ...        return True  # arbitrary example
+  >>> C = attr.make_class("C", {}, bases=(D,), cmp=False)
+  >>> isinstance(C(), D)
+  True
+
+Sometimes, you want to have your class's ``__init__`` method do more than just
+the initialization, validation, etc. that gets done for you automatically when
+using ``@attr.s``.
+To do this, just define a ``__attrs_post_init__`` method in your class.
+It will get called at the end of the generated ``__init__`` method.
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   ...     z = attr.ib(init=False)
+   ...
+   ...     def __attrs_post_init__(self):
+   ...         self.z = self.x + self.y
+   >>> obj = C(x=1, y=2)
+   >>> obj
+   C(x=1, y=2, z=3)
+
+Finally, you can exclude single attributes from certain methods:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     user = attr.ib()
+   ...     password = attr.ib(repr=False)
+   >>> C("me", "s3kr3t")
+   C(user='me')
diff --git a/tools/third_party/attrs/docs/extending.rst b/tools/third_party/attrs/docs/extending.rst
new file mode 100644
index 0000000..d460ee9
--- /dev/null
+++ b/tools/third_party/attrs/docs/extending.rst
@@ -0,0 +1,112 @@
+.. _extending:
+
+Extending
+=========
+
+Each ``attrs``-decorated class has a ``__attrs_attrs__`` class attribute.
+It is a tuple of :class:`attr.Attribute` carrying meta-data about each attribute.
+
+So it is fairly simple to build your own decorators on top of ``attrs``:
+
+.. doctest::
+
+   >>> import attr
+   >>> def print_attrs(cls):
+   ...     print(cls.__attrs_attrs__)
+   >>> @print_attrs
+   ... @attr.s
+   ... class C(object):
+   ...     a = attr.ib()
+   (Attribute(name='a', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None),)
+
+
+.. warning::
+
+   The :func:`attr.s` decorator **must** be applied first because it puts ``__attrs_attrs__`` in place!
+   That means that is has to come *after* your decorator because::
+
+      @a
+      @b
+      def f():
+         pass
+
+   is just `syntactic sugar <https://en.wikipedia.org/wiki/Syntactic_sugar>`_ for::
+
+      def original_f():
+         pass
+
+      f = a(b(original_f))
+
+
+Wrapping the Decorator
+----------------------
+
+A more elegant way can be to wrap ``attrs`` altogether and build a class `DSL <https://en.wikipedia.org/wiki/Domain-specific_language>`_ on top of it.
+
+An example for that is the package `environ_config <https://github.com/hynek/environ_config>`_ that uses ``attrs`` under the hood to define environment-based configurations declaratively without exposing ``attrs`` APIs at all.
+
+
+Types
+-----
+
+``attrs`` offers two ways of attaching type information to attributes:
+
+- `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ annotations on Python 3.6 and later,
+- and the *type* argument to :func:`attr.ib`.
+
+This information is available to you:
+
+.. doctest::
+
+   >>> import attr
+   >>> @attr.s
+   ... class C(object):
+   ...     x: int = attr.ib()
+   ...     y = attr.ib(type=str)
+   >>> attr.fields(C).x.type
+   <class 'int'>
+   >>> attr.fields(C).y.type
+   <class 'str'>
+
+Currently, ``attrs`` doesn't do anything with this information but it's very useful if you'd like to write your own validators or serializers!
+
+
+.. _extending_metadata:
+
+Metadata
+--------
+
+If you're the author of a third-party library with ``attrs`` integration, you may want to take advantage of attribute metadata.
+
+Here are some tips for effective use of metadata:
+
+- Try making your metadata keys and values immutable.
+  This keeps the entire ``Attribute`` instances immutable too.
+
+- To avoid metadata key collisions, consider exposing your metadata keys from your modules.::
+
+    from mylib import MY_METADATA_KEY
+
+    @attr.s
+    class C(object):
+      x = attr.ib(metadata={MY_METADATA_KEY: 1})
+
+  Metadata should be composable, so consider supporting this approach even if you decide implementing your metadata in one of the following ways.
+
+- Expose ``attr.ib`` wrappers for your specific metadata.
+  This is a more graceful approach if your users don't require metadata from other libraries.
+
+  .. doctest::
+
+    >>> MY_TYPE_METADATA = '__my_type_metadata'
+    >>>
+    >>> def typed(cls, default=attr.NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata={}):
+    ...     metadata = dict() if not metadata else metadata
+    ...     metadata[MY_TYPE_METADATA] = cls
+    ...     return attr.ib(default, validator, repr, cmp, hash, init, convert, metadata)
+    >>>
+    >>> @attr.s
+    ... class C(object):
+    ...     x = typed(int, default=1, init=False)
+    >>> attr.fields(C).x.metadata[MY_TYPE_METADATA]
+    <class 'int'>
diff --git a/tools/third_party/attrs/docs/how-does-it-work.rst b/tools/third_party/attrs/docs/how-does-it-work.rst
new file mode 100644
index 0000000..c20becd
--- /dev/null
+++ b/tools/third_party/attrs/docs/how-does-it-work.rst
@@ -0,0 +1,79 @@
+.. _how:
+
+How Does It Work?
+=================
+
+
+Boilerplate
+-----------
+
+``attrs`` certainly isn't the first library that aims to simplify class definition in Python.
+But its **declarative** approach combined with **no runtime overhead** lets it stand out.
+
+Once you apply the ``@attr.s`` decorator to a class, ``attrs`` searches the class object for instances of ``attr.ib``\ s.
+Internally they're a representation of the data passed into ``attr.ib`` along with a counter to preserve the order of the attributes.
+
+In order to ensure that sub-classing works as you'd expect it to work, ``attrs`` also walks the class hierarchy and collects the attributes of all super-classes.
+Please note that ``attrs`` does *not* call ``super()`` *ever*.
+It will write dunder methods to work on *all* of those attributes which also has performance benefits due to fewer function calls.
+
+Once ``attrs`` knows what attributes it has to work on, it writes the requested dunder methods and -- depending on whether you wish to have ``__slots__`` -- creates a new class for you (``slots=True``) or attaches them to the original class (``slots=False``).
+While creating new classes is more elegant, we've run into several edge cases surrounding metaclasses that make it impossible to go this route unconditionally.
+
+To be very clear: if you define a class with a single attribute  without a default value, the generated ``__init__`` will look *exactly* how you'd expect:
+
+.. doctest::
+
+   >>> import attr, inspect
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   >>> print(inspect.getsource(C.__init__))
+   def __init__(self, x):
+       self.x = x
+   <BLANKLINE>
+
+No magic, no meta programming, no expensive introspection at runtime.
+
+****
+
+Everything until this point happens exactly *once* when the class is defined.
+As soon as a class is done, it's done.
+And it's just a regular Python class like any other, except for a single ``__attrs_attrs__`` attribute that can be used for introspection or for writing your own tools and decorators on top of ``attrs`` (like :func:`attr.asdict`).
+
+And once you start instantiating your classes, ``attrs`` is out of your way completely.
+
+This **static** approach was very much a design goal of ``attrs`` and what I strongly believe makes it distinct.
+
+
+.. _how-frozen:
+
+Immutability
+------------
+
+In order to give you immutability, ``attrs`` will attach a ``__setattr__`` method to your class that raises a :exc:`attr.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute.
+
+To circumvent that ourselves in ``__init__``, ``attrs`` uses (an aggressively cached) :meth:`object.__setattr__` to set your attributes.
+This is (still) slower than a plain assignment:
+
+.. code-block:: none
+
+  $ pyperf timeit --rigorous \
+        -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True)" \
+        "C(1, 2, 3)"
+  ........................................
+  Median +- std dev: 378 ns +- 12 ns
+
+  $ pyperf timeit --rigorous \
+        -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True, frozen=True)" \
+        "C(1, 2, 3)"
+  ........................................
+  Median +- std dev: 676 ns +- 16 ns
+
+So on a standard notebook the difference is about 300 nanoseconds (1 second is 1,000,000,000 nanoseconds).
+It's certainly something you'll feel in a hot loop but shouldn't matter in normal code.
+Pick what's more important to you.
+
+****
+
+Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes.
diff --git a/tools/third_party/attrs/docs/index.rst b/tools/third_party/attrs/docs/index.rst
new file mode 100644
index 0000000..bb24cd7
--- /dev/null
+++ b/tools/third_party/attrs/docs/index.rst
@@ -0,0 +1,80 @@
+======================================
+``attrs``: Classes Without Boilerplate
+======================================
+
+Release v\ |release| (:doc:`What's new? <changelog>`).
+
+.. include:: ../README.rst
+   :start-after: teaser-begin
+   :end-before: -spiel-end-
+
+
+Getting Started
+===============
+
+``attrs`` is a Python-only package `hosted on PyPI <https://pypi.org/project/attrs/>`_.
+The recommended installation method is `pip <https://pip.pypa.io/en/stable/>`_-installing into a `virtualenv <https://hynek.me/articles/virtualenv-lives/>`_:
+
+.. code-block:: console
+
+   $ pip install attrs
+
+The next three steps should bring you up and running in no time:
+
+- :doc:`overview` will show you a simple example of ``attrs`` in action and introduce you to its philosophy.
+  Afterwards, you can start writing your own classes, understand what drives ``attrs``'s design, and know what ``@attr.s`` and ``attr.ib()`` stand for.
+- :doc:`examples` will give you a comprehensive tour of ``attrs``'s features.
+  After reading, you will know about our advanced features and how to use them.
+- Finally :doc:`why` gives you a rundown of potential alternatives and why we think ``attrs`` is superior.
+  Yes, we've heard about ``namedtuple``\ s!
+
+
+If you need any help while getting started, feel free to use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ and someone will surely help you out!
+
+
+Day-to-Day Usage
+================
+
+- Once you're comfortable with the concepts, our :doc:`api` contains all information you need to use ``attrs`` to its fullest.
+- ``attrs`` is built for extension from the ground up.
+  :doc:`extending` will show you the affordances it offers and how to make it a building block of your own projects.
+
+
+.. include:: ../README.rst
+   :start-after: -testimonials-
+   :end-before: -end-
+
+.. include:: ../README.rst
+   :start-after: -project-information-
+
+.. toctree::
+   :maxdepth: 1
+
+   license
+   backward-compatibility
+   contributing
+   changelog
+
+
+----
+
+
+Full Table of Contents
+======================
+
+.. toctree::
+   :maxdepth: 2
+
+   overview
+   why
+   examples
+   api
+   extending
+   how-does-it-work
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/tools/third_party/attrs/docs/license.rst b/tools/third_party/attrs/docs/license.rst
new file mode 100644
index 0000000..cef5f39
--- /dev/null
+++ b/tools/third_party/attrs/docs/license.rst
@@ -0,0 +1,8 @@
+===================
+License and Credits
+===================
+
+``attrs`` is licensed under the `MIT <https://choosealicense.com/licenses/mit/>`_ license.
+The full license text can be also found in the `source code repository <https://github.com/python-attrs/attrs/blob/master/LICENSE>`_.
+
+.. include:: ../AUTHORS.rst
diff --git a/tools/third_party/attrs/docs/overview.rst b/tools/third_party/attrs/docs/overview.rst
new file mode 100644
index 0000000..47b022f
--- /dev/null
+++ b/tools/third_party/attrs/docs/overview.rst
@@ -0,0 +1,87 @@
+========
+Overview
+========
+
+In order to fulfill its ambitious goal of bringing back the joy to writing classes, it gives you a class decorator and a way to declaratively define the attributes on that class:
+
+.. include:: ../README.rst
+   :start-after: -code-begin-
+   :end-before: -testimonials-
+
+
+.. _philosophy:
+
+Philosophy
+==========
+
+**It's about regular classes.**
+   ``attrs`` is for creating well-behaved classes with a type, attributes, methods, and everything that comes with a class.
+   It can be used for data-only containers like ``namedtuple``\ s or ``types.SimpleNamespace`` but they're just a sub-genre of what ``attrs`` is good for.
+
+**The class belongs to the users.**
+   You define a class and ``attrs`` adds static methods to that class based on the attributes you declare.
+   The end.
+   It doesn't add metaclasses.
+   It doesn't add classes you've never heard of to your inheritance tree.
+   An ``attrs`` class in runtime is indistiguishable from a regular class: because it *is* a regular class with a few boilerplate-y methods attached.
+
+**Be light on API impact.**
+   As convenient as it seems at first, ``attrs`` will *not* tack on any methods to your classes save the dunder ones.
+   Hence all the useful :ref:`tools <helpers>` that come with ``attrs`` live in functions that operate on top of instances.
+   Since they take an ``attrs`` instance as their first argument, you can attach them to your classes with one line of code.
+
+**Performance matters.**
+   ``attrs`` runtime impact is very close to zero because all the work is done when the class is defined.
+   Once you're instantiating it, ``attrs`` is out of the picture completely.
+
+**No surprises.**
+   ``attrs`` creates classes that arguably work the way a Python beginner would reasonably expect them to work.
+   It doesn't try to guess what you mean because explicit is better than implicit.
+   It doesn't try to be clever because software shouldn't be clever.
+
+Check out :doc:`how-does-it-work` if you'd like to know how it achieves all of the above.
+
+
+What ``attrs`` Is Not
+=====================
+
+``attrs`` does *not* invent some kind of magic system that pulls classes out of its hat using meta classes, runtime introspection, and shaky interdependencies.
+
+All ``attrs`` does is:
+
+1. take your declaration,
+2. write dunder methods based on that information,
+3. and attach them to your class.
+
+It does *nothing* dynamic at runtime, hence zero runtime overhead.
+It's still *your* class.
+Do with it as you please.
+
+
+On the ``attr.s`` and ``attr.ib`` Names
+=======================================
+
+The ``attr.s`` decorator and the ``attr.ib`` function aren't any obscure abbreviations.
+They are a *concise* and highly *readable* way to write ``attrs`` and ``attrib`` with an *explicit namespace*.
+
+At first, some people have a negative gut reaction to that; resembling the reactions to Python's significant whitespace.
+And as with that, once one gets used to it, the readability and explicitness of that API prevails and delights.
+
+For those who can't swallow that API at all, ``attrs`` comes with serious business aliases: ``attr.attrs`` and ``attr.attrib``.
+
+Therefore, the following class definition is identical to the previous one:
+
+.. doctest::
+
+   >>> from attr import attrs, attrib, Factory
+   >>> @attrs
+   ... class SomeClass(object):
+   ...     a_number = attrib(default=42)
+   ...     list_of_numbers = attrib(default=Factory(list))
+   ...
+   ...     def hard_math(self, another_number):
+   ...         return self.a_number + sum(self.list_of_numbers) * another_number
+   >>> SomeClass(1, [1, 2, 3])
+   SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
+
+Use whichever variant fits your taste better.
diff --git a/tools/third_party/attrs/docs/why.rst b/tools/third_party/attrs/docs/why.rst
new file mode 100644
index 0000000..9c64cb9
--- /dev/null
+++ b/tools/third_party/attrs/docs/why.rst
@@ -0,0 +1,251 @@
+.. _why:
+
+Why not…
+========
+
+
+If you'd like third party's account why ``attrs`` is great, have a look at Glyph's `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_!
+
+
+…tuples?
+--------
+
+
+Readability
+^^^^^^^^^^^
+
+What makes more sense while debugging::
+
+   Point(x=1, y=2)
+
+or::
+
+   (1, 2)
+
+?
+
+Let's add even more ambiguity::
+
+   Customer(id=42, reseller=23, first_name="Jane", last_name="John")
+
+or::
+
+   (42, 23, "Jane", "John")
+
+?
+
+Why would you want to write ``customer[2]`` instead of ``customer.first_name``?
+
+Don't get me started when you add nesting.
+If you've never run into mysterious tuples you had no idea what the hell they meant while debugging, you're much smarter than yours truly.
+
+Using proper classes with names and types makes program code much more readable and comprehensible_.
+Especially when trying to grok a new piece of software or returning to old code after several months.
+
+.. _comprehensible: https://arxiv.org/pdf/1304.5257.pdf
+
+
+Extendability
+^^^^^^^^^^^^^
+
+Imagine you have a function that takes or returns a tuple.
+Especially if you use tuple unpacking (eg. ``x, y = get_point()``), adding additional data means that you have to change the invocation of that function *everywhere*.
+
+Adding an attribute to a class concerns only those who actually care about that attribute.
+
+
+…namedtuples?
+-------------
+
+:func:`collections.namedtuple`\ s are tuples with names, not classes. [#history]_
+Since writing classes is tiresome in Python, every now and then someone discovers all the typing they could save and gets really excited.
+However that convenience comes at a price.
+
+The most obvious difference between ``namedtuple``\ s and ``attrs``-based classes is that the latter are type-sensitive:
+
+.. doctest::
+
+   >>> import attr
+   >>> C1 = attr.make_class("C1", ["a"])
+   >>> C2 = attr.make_class("C2", ["a"])
+   >>> i1 = C1(1)
+   >>> i2 = C2(1)
+   >>> i1.a == i2.a
+   True
+   >>> i1 == i2
+   False
+
+…while a ``namedtuple`` is *intentionally* `behaving like a tuple`_ which means the type of a tuple is *ignored*:
+
+.. doctest::
+
+   >>> from collections import namedtuple
+   >>> NT1 = namedtuple("NT1", "a")
+   >>> NT2 = namedtuple("NT2", "b")
+   >>> t1 = NT1(1)
+   >>> t2 = NT2(1)
+   >>> t1 == t2 == (1,)
+   True
+
+Other often surprising behaviors include:
+
+- Since they are a subclass of tuples, ``namedtuple``\ s have a length and are both iterable and indexable.
+  That's not what you'd expect from a class and is likely to shadow subtle typo bugs.
+- Iterability also implies that it's easy to accidentally unpack a ``namedtuple`` which leads to hard-to-find bugs. [#iter]_
+- ``namedtuple``\ s have their methods *on your instances* whether you like it or not. [#pollution]_
+- ``namedtuple``\ s are *always* immutable.
+  Not only does that mean that you can't decide for yourself whether your instances should be immutable or not, it also means that if you want to influence your class' initialization (validation?  default values?), you have to implement :meth:`__new__() <object.__new__>` which is a particularly hacky and error-prone requirement for a very common problem. [#immutable]_
+- To attach methods to a ``namedtuple`` you have to subclass it.
+  And if you follow the standard library documentation's recommendation of::
+
+    class Point(namedtuple('Point', ['x', 'y'])):
+        # ...
+
+  you end up with a class that has *two* ``Point``\ s in its :attr:`__mro__ <class.__mro__>`: ``[<class 'point.Point'>, <class 'point.Point'>, <type 'tuple'>, <type 'object'>]``.
+
+  That's not only confusing, it also has very practical consequences:
+  for example if you create documentation that includes class hierarchies like `Sphinx's autodoc <http://www.sphinx-doc.org/en/stable/ext/autodoc.html>`_ with ``show-inheritance``.
+  Again: common problem, hacky solution with confusing fallout.
+
+All these things make ``namedtuple``\ s a particularly poor choice for public APIs because all your objects are irrevocably tainted.
+With ``attrs`` your users won't notice a difference because it creates regular, well-behaved classes.
+
+.. admonition:: Summary
+
+  If you want a *tuple with names*, by all means: go for a ``namedtuple``. [#perf]_
+  But if you want a class with methods, you're doing yourself a disservice by relying on a pile of hacks that requires you to employ even more hacks as your requirements expand.
+
+  Other than that, ``attrs`` also adds nifty features like validators, converters, and (mutable!) default values.
+
+
+.. rubric:: Footnotes
+
+.. [#history] The word is that ``namedtuple``\ s were added to the Python standard library as a way to make tuples in return values more readable.
+              And indeed that is something you see throughout the standard library.
+
+              Looking at what the makers of ``namedtuple``\ s use it for themselves is a good guideline for deciding on your own use cases.
+.. [#pollution] ``attrs`` only adds a single attribute: ``__attrs_attrs__`` for introspection.
+                All helpers are functions in the ``attr`` package.
+                Since they take the instance as first argument, you can easily attach them to your classes under a name of your own choice.
+.. [#iter] :func:`attr.astuple` can be used to get that behavior in ``attrs`` on *explicit demand*.
+.. [#immutable] ``attrs`` offers *optional* immutability through the ``frozen`` keyword.
+.. [#perf] Although ``attrs`` would serve you just as well!
+           Since both employ the same method of writing and compiling Python code for you, the performance penalty is negligible at worst and in some cases ``attrs`` is even faster if you use ``slots=True`` (which is generally a good idea anyway).
+
+.. _behaving like a tuple: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
+
+
+…dicts?
+-------
+
+Dictionaries are not for fixed fields.
+
+If you have a dict, it maps something to something else.
+You should be able to add and remove values.
+
+
+
+``attrs`` lets you be specific about those expectations; a dictionary does not.
+It gives you a named entity (the class) in your code, which lets you explain in other places whether you take a parameter of that class or return a value of that class.
+
+In other words: if your dict has a fixed and known set of keys, it is an object, not a hash.
+So if you never iterate over the keys of a dict, you should use a proper class.
+
+
+…hand-written classes?
+----------------------
+
+While we're fans of all things artisanal, writing the same nine methods all over again doesn't qualify for me.
+I usually manage to get some typos inside and there's simply more code that can break and thus has to be tested.
+
+To bring it into perspective, the equivalent of
+
+.. doctest::
+
+   >>> @attr.s
+   ... class SmartClass(object):
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   >>> SmartClass(1, 2)
+   SmartClass(a=1, b=2)
+
+is
+
+.. doctest::
+
+   >>> class ArtisanalClass(object):
+   ...     def __init__(self, a, b):
+   ...         self.a = a
+   ...         self.b = b
+   ...
+   ...     def __repr__(self):
+   ...         return "ArtisanalClass(a={}, b={})".format(self.a, self.b)
+   ...
+   ...     def __eq__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) == (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __ne__(self, other):
+   ...         result = self.__eq__(other)
+   ...         if result is NotImplemented:
+   ...             return NotImplemented
+   ...         else:
+   ...             return not result
+   ...
+   ...     def __lt__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) < (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __le__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) <= (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __gt__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) > (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __ge__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) >= (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __hash__(self):
+   ...         return hash((self.a, self.b))
+   >>> ArtisanalClass(a=1, b=2)
+   ArtisanalClass(a=1, b=2)
+
+which is quite a mouthful and it doesn't even use any of ``attrs``'s more advanced features like validators or defaults values.
+Also: no tests whatsoever.
+And who will guarantee you, that you don't accidentally flip the ``<`` in your tenth implementation of ``__gt__``?
+
+It also should be noted that ``attrs`` is not an all-or-nothing solution.
+You can freely choose which features you want and disable those that you want more control over:
+
+.. doctest::
+
+   >>> @attr.s(repr=False)
+   ... class SmartClass(object):
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   ...
+   ...    def __repr__(self):
+   ...        return "<SmartClass(a=%d)>" % (self.a,)
+   >>> SmartClass(1, 2)
+   <SmartClass(a=1)>
+
+.. admonition:: Summary
+
+   If you don't care and like typing, we're not gonna stop you.
+
+   However it takes a lot of bias and determined rationalization to claim that ``attrs`` raises the mental burden on a project given how difficult it is to find the important bits in a hand-written class and how annoying it is to ensure you've copy-pasted your code correctly over all your classes.
+
+   In any case, if you ever get sick of the repetitiveness and drowning important code in a sea of boilerplate, ``attrs`` will be waiting for you.
diff --git a/tools/third_party/attrs/pyproject.toml b/tools/third_party/attrs/pyproject.toml
new file mode 100644
index 0000000..0f68a7cb
--- /dev/null
+++ b/tools/third_party/attrs/pyproject.toml
@@ -0,0 +1,26 @@
+[tool.towncrier]
+    package = "attr"
+    package_dir = "src"
+    filename = "CHANGELOG.rst"
+    issue_format = "`#{issue} <https://github.com/python-attrs/attrs/issues/{issue}>`_"
+    directory = "changelog.d"
+    title_format = "{version} ({project_date})"
+    underlines = ["-", "^"]
+
+    [[tool.towncrier.section]]
+        path = ""
+
+    [[tool.towncrier.type]]
+        directory = "breaking"
+        name = "Backward-incompatible Changes"
+        showcontent = true
+
+    [[tool.towncrier.type]]
+        directory = "deprecation"
+        name = "Deprecations"
+        showcontent = true
+
+    [[tool.towncrier.type]]
+        directory = "change"
+        name = "Changes"
+        showcontent = true
diff --git a/tools/third_party/attrs/setup.cfg b/tools/third_party/attrs/setup.cfg
new file mode 100644
index 0000000..8ddbbab
--- /dev/null
+++ b/tools/third_party/attrs/setup.cfg
@@ -0,0 +1,26 @@
+[bdist_wheel]
+universal = 1
+
+
+[metadata]
+# ensure LICENSE is included in wheel metadata
+license_file = LICENSE
+
+
+[tool:pytest]
+minversion = 3.0
+strict = true
+addopts = -ra
+testpaths = tests
+filterwarnings =
+    once::Warning
+
+
+[isort]
+atomic=true
+lines_after_imports=2
+lines_between_types=1
+multi_line_output=5
+not_skip=__init__.py
+
+known_first_party=attr
diff --git a/tools/third_party/attrs/setup.py b/tools/third_party/attrs/setup.py
new file mode 100644
index 0000000..232d3f5
--- /dev/null
+++ b/tools/third_party/attrs/setup.py
@@ -0,0 +1,95 @@
+import codecs
+import os
+import re
+
+from setuptools import find_packages, setup
+
+
+###############################################################################
+
+NAME = "attrs"
+PACKAGES = find_packages(where="src")
+META_PATH = os.path.join("src", "attr", "__init__.py")
+KEYWORDS = ["class", "attribute", "boilerplate"]
+CLASSIFIERS = [
+    "Development Status :: 5 - Production/Stable",
+    "Intended Audience :: Developers",
+    "Natural Language :: English",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 2",
+    "Programming Language :: Python :: 2.7",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.4",
+    "Programming Language :: Python :: 3.5",
+    "Programming Language :: Python :: 3.6",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Programming Language :: Python :: Implementation :: PyPy",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+]
+INSTALL_REQUIRES = []
+
+###############################################################################
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+
+def read(*parts):
+    """
+    Build an absolute path from *parts* and and return the contents of the
+    resulting file.  Assume UTF-8 encoding.
+    """
+    with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f:
+        return f.read()
+
+
+META_FILE = read(META_PATH)
+
+
+def find_meta(meta):
+    """
+    Extract __*meta*__ from META_FILE.
+    """
+    meta_match = re.search(
+        r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta),
+        META_FILE, re.M
+    )
+    if meta_match:
+        return meta_match.group(1)
+    raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta))
+
+
+VERSION = find_meta("version")
+URI = find_meta("uri")
+LONG = (
+    read("README.rst") + "\n\n" +
+    "Release Information\n" +
+    "===================\n\n" +
+    re.search("(\d+.\d.\d \(.*?\)\n.*?)\n\n\n----\n\n\n",
+              read("CHANGELOG.rst"), re.S).group(1) +
+    "\n\n`Full changelog " +
+    "<{uri}en/stable/changelog.html>`_.\n\n".format(uri=URI) +
+    read("AUTHORS.rst")
+)
+
+
+if __name__ == "__main__":
+    setup(
+        name=NAME,
+        description=find_meta("description"),
+        license=find_meta("license"),
+        url=URI,
+        version=VERSION,
+        author=find_meta("author"),
+        author_email=find_meta("email"),
+        maintainer=find_meta("author"),
+        maintainer_email=find_meta("email"),
+        keywords=KEYWORDS,
+        long_description=LONG,
+        packages=PACKAGES,
+        package_dir={"": "src"},
+        zip_safe=False,
+        classifiers=CLASSIFIERS,
+        install_requires=INSTALL_REQUIRES,
+    )
diff --git a/tools/third_party/attrs/src/attr/__init__.py b/tools/third_party/attrs/src/attr/__init__.py
new file mode 100644
index 0000000..929b172
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/__init__.py
@@ -0,0 +1,55 @@
+from __future__ import absolute_import, division, print_function
+
+from functools import partial
+
+from . import converters, exceptions, filters, validators
+from ._config import get_run_validators, set_run_validators
+from ._funcs import asdict, assoc, astuple, evolve, has
+from ._make import (
+    NOTHING, Attribute, Factory, attrib, attrs, fields, make_class, validate
+)
+
+
+__version__ = "17.4.0.dev0"
+
+__title__ = "attrs"
+__description__ = "Classes Without Boilerplate"
+__uri__ = "http://www.attrs.org/"
+__doc__ = __description__ + " <" + __uri__ + ">"
+
+__author__ = "Hynek Schlawack"
+__email__ = "hs@ox.cx"
+
+__license__ = "MIT"
+__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
+
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = partial(attrs, auto_attribs=True)  # happy Easter ;)
+
+__all__ = [
+    "Attribute",
+    "Factory",
+    "NOTHING",
+    "asdict",
+    "assoc",
+    "astuple",
+    "attr",
+    "attrib",
+    "attributes",
+    "attrs",
+    "converters",
+    "evolve",
+    "exceptions",
+    "fields",
+    "filters",
+    "get_run_validators",
+    "has",
+    "ib",
+    "make_class",
+    "s",
+    "set_run_validators",
+    "validate",
+    "validators",
+]
diff --git a/tools/third_party/attrs/src/attr/_compat.py b/tools/third_party/attrs/src/attr/_compat.py
new file mode 100644
index 0000000..8a49341
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/_compat.py
@@ -0,0 +1,139 @@
+from __future__ import absolute_import, division, print_function
+
+import platform
+import sys
+import types
+import warnings
+
+
+PY2 = sys.version_info[0] == 2
+PYPY = platform.python_implementation() == "PyPy"
+
+
+if PY2:
+    from UserDict import IterableUserDict
+
+    # We 'bundle' isclass instead of using inspect as importing inspect is
+    # fairly expensive (order of 10-15 ms for a modern machine in 2016)
+    def isclass(klass):
+        return isinstance(klass, (type, types.ClassType))
+
+    # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
+    TYPE = "type"
+
+    def iteritems(d):
+        return d.iteritems()
+
+    # Python 2 is bereft of a read-only dict proxy, so we make one!
+    class ReadOnlyDict(IterableUserDict):
+        """
+        Best-effort read-only dict wrapper.
+        """
+
+        def __setitem__(self, key, val):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "assignment")
+
+        def update(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'update'")
+
+        def __delitem__(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "deletion")
+
+        def clear(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'clear'")
+
+        def pop(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'pop'")
+
+        def popitem(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'popitem'")
+
+        def setdefault(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'setdefault'")
+
+        def __repr__(self):
+            # Override to be identical to the Python 3 version.
+            return "mappingproxy(" + repr(self.data) + ")"
+
+    def metadata_proxy(d):
+        res = ReadOnlyDict()
+        res.data.update(d)  # We blocked update, so we have to do it like this.
+        return res
+
+else:
+    def isclass(klass):
+        return isinstance(klass, type)
+
+    TYPE = "class"
+
+    def iteritems(d):
+        return d.items()
+
+    def metadata_proxy(d):
+        return types.MappingProxyType(dict(d))
+
+
+def import_ctypes():  # pragma: nocover
+    """
+    Moved into a function for testability.
+    """
+    try:
+        import ctypes
+        return ctypes
+    except ImportError:
+        return None
+
+
+if not PY2:
+    def just_warn(*args, **kw):
+        """
+        We only warn on Python 3 because we are not aware of any concrete
+        consequences of not setting the cell on Python 2.
+        """
+        warnings.warn(
+            "Missing ctypes.  Some features like bare super() or accessing "
+            "__class__ will not work with slots classes.",
+            RuntimeWarning,
+            stacklevel=2,
+        )
+else:
+    def just_warn(*args, **kw):  # pragma: nocover
+        """
+        We only warn on Python 3 because we are not aware of any concrete
+        consequences of not setting the cell on Python 2.
+        """
+
+
+def make_set_closure_cell():
+    """
+    Moved into a function for testability.
+    """
+    if PYPY:  # pragma: no cover
+        def set_closure_cell(cell, value):
+            cell.__setstate__((value,))
+    else:
+        ctypes = import_ctypes()
+        if ctypes is not None:
+            set_closure_cell = ctypes.pythonapi.PyCell_Set
+            set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
+            set_closure_cell.restype = ctypes.c_int
+        else:
+            set_closure_cell = just_warn
+    return set_closure_cell
+
+
+set_closure_cell = make_set_closure_cell()
diff --git a/tools/third_party/attrs/src/attr/_config.py b/tools/third_party/attrs/src/attr/_config.py
new file mode 100644
index 0000000..8ec9209
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/_config.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function
+
+
+__all__ = ["set_run_validators", "get_run_validators"]
+
+_run_validators = True
+
+
+def set_run_validators(run):
+    """
+    Set whether or not validators are run.  By default, they are run.
+    """
+    if not isinstance(run, bool):
+        raise TypeError("'run' must be bool.")
+    global _run_validators
+    _run_validators = run
+
+
+def get_run_validators():
+    """
+    Return whether or not validators are run.
+    """
+    return _run_validators
diff --git a/tools/third_party/attrs/src/attr/_funcs.py b/tools/third_party/attrs/src/attr/_funcs.py
new file mode 100644
index 0000000..798043a
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/_funcs.py
@@ -0,0 +1,212 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+from ._compat import iteritems
+from ._make import NOTHING, _obj_setattr, fields
+from .exceptions import AttrsAttributeNotFoundError
+
+
+def asdict(inst, recurse=True, filter=None, dict_factory=dict,
+           retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a dict.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable dict_factory: A callable to produce dictionaries from.  For
+        example, to produce ordered dictionaries instead of normal Python
+        dictionaries, pass in ``collections.OrderedDict``.
+    :param bool retain_collection_types: Do not convert to ``list`` when
+        encountering an attribute whose type is ``tuple`` or ``set``.  Only
+        meaningful if ``recurse`` is ``True``.
+
+    :rtype: return type of *dict_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.0.0 *dict_factory*
+    ..  versionadded:: 16.1.0 *retain_collection_types*
+    """
+    attrs = fields(inst.__class__)
+    rv = dict_factory()
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv[a.name] = asdict(v, recurse=True, filter=filter,
+                                    dict_factory=dict_factory)
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain_collection_types is True else list
+                rv[a.name] = cf([
+                    asdict(i, recurse=True, filter=filter,
+                           dict_factory=dict_factory)
+                    if has(i.__class__) else i
+                    for i in v
+                ])
+            elif isinstance(v, dict):
+                df = dict_factory
+                rv[a.name] = df((
+                    asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
+                    asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
+                    for kk, vv in iteritems(v))
+            else:
+                rv[a.name] = v
+        else:
+            rv[a.name] = v
+    return rv
+
+
+def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
+            retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a tuple.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable tuple_factory: A callable to produce tuples from.  For
+        example, to produce lists instead of tuples.
+    :param bool retain_collection_types: Do not convert to ``list``
+        or ``dict`` when encountering an attribute which type is
+        ``tuple``, ``dict`` or ``set``.  Only meaningful if ``recurse`` is
+        ``True``.
+
+    :rtype: return type of *tuple_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.2.0
+    """
+    attrs = fields(inst.__class__)
+    rv = []
+    retain = retain_collection_types  # Very long. :/
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv.append(astuple(v, recurse=True, filter=filter,
+                                  tuple_factory=tuple_factory,
+                                  retain_collection_types=retain))
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain is True else list
+                rv.append(cf([
+                    astuple(j, recurse=True, filter=filter,
+                            tuple_factory=tuple_factory,
+                            retain_collection_types=retain)
+                    if has(j.__class__) else j
+                    for j in v
+                ]))
+            elif isinstance(v, dict):
+                df = v.__class__ if retain is True else dict
+                rv.append(df(
+                        (
+                            astuple(
+                                kk,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(kk.__class__) else kk,
+                            astuple(
+                                vv,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(vv.__class__) else vv
+                        )
+                        for kk, vv in iteritems(v)))
+            else:
+                rv.append(v)
+        else:
+            rv.append(v)
+    return rv if tuple_factory is list else tuple_factory(rv)
+
+
+def has(cls):
+    """
+    Check whether *cls* is a class with ``attrs`` attributes.
+
+    :param type cls: Class to introspect.
+    :raise TypeError: If *cls* is not a class.
+
+    :rtype: :class:`bool`
+    """
+    return getattr(cls, "__attrs_attrs__", None) is not None
+
+
+def assoc(inst, **changes):
+    """
+    Copy *inst* and apply *changes*.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
+        be found on *cls*.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  deprecated:: 17.1.0
+        Use :func:`evolve` instead.
+    """
+    import warnings
+    warnings.warn("assoc is deprecated and will be removed after 2018/01.",
+                  DeprecationWarning, stacklevel=2)
+    new = copy.copy(inst)
+    attrs = fields(inst.__class__)
+    for k, v in iteritems(changes):
+        a = getattr(attrs, k, NOTHING)
+        if a is NOTHING:
+            raise AttrsAttributeNotFoundError(
+                "{k} is not an attrs attribute on {cl}."
+                .format(k=k, cl=new.__class__)
+            )
+        _obj_setattr(new, k, v)
+    return new
+
+
+def evolve(inst, **changes):
+    """
+    Create a new instance, based on *inst* with *changes* applied.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise TypeError: If *attr_name* couldn't be found in the class
+        ``__init__``.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 17.1.0
+    """
+    cls = inst.__class__
+    attrs = fields(cls)
+    for a in attrs:
+        if not a.init:
+            continue
+        attr_name = a.name  # To deal with private attributes.
+        init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
+        if init_name not in changes:
+            changes[init_name] = getattr(inst, attr_name)
+    return cls(**changes)
diff --git a/tools/third_party/attrs/src/attr/_make.py b/tools/third_party/attrs/src/attr/_make.py
new file mode 100644
index 0000000..31c5f94
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/_make.py
@@ -0,0 +1,1395 @@
+from __future__ import absolute_import, division, print_function
+
+import hashlib
+import linecache
+import sys
+
+from operator import itemgetter
+
+from . import _config
+from ._compat import PY2, isclass, iteritems, metadata_proxy, set_closure_cell
+from .exceptions import (
+    DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError,
+    UnannotatedAttributeError
+)
+
+
+# This is used at least twice, so cache it here.
+_obj_setattr = object.__setattr__
+_init_convert_pat = "__attr_convert_{}"
+_init_factory_pat = "__attr_factory_{}"
+_tuple_property_pat = "    {attr_name} = property(itemgetter({index}))"
+_empty_metadata_singleton = metadata_proxy({})
+
+
+class _Nothing(object):
+    """
+    Sentinel class to indicate the lack of a value when ``None`` is ambiguous.
+
+    All instances of `_Nothing` are equal.
+    """
+    def __copy__(self):
+        return self
+
+    def __deepcopy__(self, _):
+        return self
+
+    def __eq__(self, other):
+        return other.__class__ == _Nothing
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __repr__(self):
+        return "NOTHING"
+
+    def __hash__(self):
+        return 0xdeadbeef
+
+
+NOTHING = _Nothing()
+"""
+Sentinel to indicate the lack of a value when ``None`` is ambiguous.
+"""
+
+
+def attrib(default=NOTHING, validator=None,
+           repr=True, cmp=True, hash=None, init=True,
+           convert=None, metadata={}, type=None):
+    """
+    Create a new attribute on a class.
+
+    ..  warning::
+
+        Does *not* do anything unless the class is also decorated with
+        :func:`attr.s`!
+
+    :param default: A value that is used if an ``attrs``-generated ``__init__``
+        is used and no value is passed while instantiating or the attribute is
+        excluded using ``init=False``.
+
+        If the value is an instance of :class:`Factory`, its callable will be
+        used to construct a new value (useful for mutable data types like lists
+        or dicts).
+
+        If a default is not set (or set manually to ``attr.NOTHING``), a value
+        *must* be supplied when instantiating; otherwise a :exc:`TypeError`
+        will be raised.
+
+        The default can also be set using decorator notation as shown below.
+
+    :type default: Any value.
+
+    :param validator: :func:`callable` that is called by ``attrs``-generated
+        ``__init__`` methods after the instance has been initialized.  They
+        receive the initialized instance, the :class:`Attribute`, and the
+        passed value.
+
+        The return value is *not* inspected so the validator has to throw an
+        exception itself.
+
+        If a ``list`` is passed, its items are treated as validators and must
+        all pass.
+
+        Validators can be globally disabled and re-enabled using
+        :func:`get_run_validators`.
+
+        The validator can also be set using decorator notation as shown below.
+
+    :type validator: ``callable`` or a ``list`` of ``callable``\ s.
+
+    :param bool repr: Include this attribute in the generated ``__repr__``
+        method.
+    :param bool cmp: Include this attribute in the generated comparison methods
+        (``__eq__`` et al).
+    :param hash: Include this attribute in the generated ``__hash__``
+        method.  If ``None`` (default), mirror *cmp*'s value.  This is the
+        correct behavior according the Python spec.  Setting this value to
+        anything else than ``None`` is *discouraged*.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Include this attribute in the generated ``__init__``
+        method.  It is possible to set this to ``False`` and set a default
+        value.  In that case this attributed is unconditionally initialized
+        with the specified default value or factory.
+    :param callable convert: :func:`callable` that is called by
+        ``attrs``-generated ``__init__`` methods to convert attribute's value
+        to the desired format.  It is given the passed-in value, and the
+        returned value will be used as the new value of the attribute.  The
+        value is converted before being passed to the validator, if any.
+    :param metadata: An arbitrary mapping, to be used by third-party
+        components.  See :ref:`extending_metadata`.
+    :param type: The type of the attribute.  In Python 3.6 or greater, the
+        preferred method to specify the type is using a variable annotation
+        (see `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_).
+        This argument is provided for backward compatibility.
+        Regardless of the approach used, the type will be stored on
+        ``Attribute.type``.
+
+    ..  versionchanged:: 17.1.0 *validator* can be a ``list`` now.
+    ..  versionchanged:: 17.1.0
+        *hash* is ``None`` and therefore mirrors *cmp* by default.
+    ..  versionadded:: 17.3.0 *type*
+    """
+    if hash is not None and hash is not True and hash is not False:
+        raise TypeError(
+            "Invalid value for hash.  Must be True, False, or None."
+        )
+    return _CountingAttr(
+        default=default,
+        validator=validator,
+        repr=repr,
+        cmp=cmp,
+        hash=hash,
+        init=init,
+        convert=convert,
+        metadata=metadata,
+        type=type,
+    )
+
+
+def _make_attr_tuple_class(cls_name, attr_names):
+    """
+    Create a tuple subclass to hold `Attribute`s for an `attrs` class.
+
+    The subclass is a bare tuple with properties for names.
+
+    class MyClassAttributes(tuple):
+        __slots__ = ()
+        x = property(itemgetter(0))
+    """
+    attr_class_name = "{}Attributes".format(cls_name)
+    attr_class_template = [
+        "class {}(tuple):".format(attr_class_name),
+        "    __slots__ = ()",
+    ]
+    if attr_names:
+        for i, attr_name in enumerate(attr_names):
+            attr_class_template.append(_tuple_property_pat.format(
+                index=i,
+                attr_name=attr_name,
+            ))
+    else:
+        attr_class_template.append("    pass")
+    globs = {"itemgetter": itemgetter}
+    eval(compile("\n".join(attr_class_template), "", "exec"), globs)
+    return globs[attr_class_name]
+
+
+# Tuple class for extracted attributes from a class definition.
+# `super_attrs` is a subset of `attrs`.
+_Attributes = _make_attr_tuple_class("_Attributes", [
+    "attrs",        # all attributes to build dunder methods for
+    "super_attrs",  # attributes that have been inherited from super classes
+])
+
+
+def _is_class_var(annot):
+    """
+    Check whether *annot* is a typing.ClassVar.
+
+    The implementation is gross but importing `typing` is slow and there are
+    discussions to remove it from the stdlib alltogether.
+    """
+    return str(annot).startswith("typing.ClassVar")
+
+
+def _get_annotations(cls):
+    """
+    Get annotations for *cls*.
+    """
+    anns = getattr(cls, "__annotations__", None)
+    if anns is None:
+        return {}
+
+    # Verify that the annotations aren't merely inherited.
+    for super_cls in cls.__mro__[1:]:
+        if anns is getattr(super_cls, "__annotations__", None):
+            return {}
+
+    return anns
+
+
+def _transform_attrs(cls, these, auto_attribs):
+    """
+    Transform all `_CountingAttr`s on a class into `Attribute`s.
+
+    If *these* is passed, use that and don't look for them on the class.
+
+    Return an `_Attributes`.
+    """
+    cd = cls.__dict__
+    anns = _get_annotations(cls)
+
+    if these is not None:
+        ca_list = sorted((
+            (name, ca)
+            for name, ca
+            in iteritems(these)
+        ), key=lambda e: e[1].counter)
+    elif auto_attribs is True:
+        ca_names = {
+            name
+            for name, attr
+            in cd.items()
+            if isinstance(attr, _CountingAttr)
+        }
+        ca_list = []
+        annot_names = set()
+        for attr_name, type in anns.items():
+            if _is_class_var(type):
+                continue
+            annot_names.add(attr_name)
+            a = cd.get(attr_name, NOTHING)
+            if not isinstance(a, _CountingAttr):
+                if a is NOTHING:
+                    a = attrib()
+                else:
+                    a = attrib(default=a)
+            ca_list.append((attr_name, a))
+
+        unannotated = ca_names - annot_names
+        if len(unannotated) > 0:
+            raise UnannotatedAttributeError(
+                "The following `attr.ib`s lack a type annotation: " +
+                ", ".join(sorted(
+                    unannotated,
+                    key=lambda n: cd.get(n).counter
+                )) + "."
+            )
+    else:
+        ca_list = sorted((
+            (name, attr)
+            for name, attr
+            in cd.items()
+            if isinstance(attr, _CountingAttr)
+        ), key=lambda e: e[1].counter)
+
+    non_super_attrs = [
+        Attribute.from_counting_attr(
+            name=attr_name,
+            ca=ca,
+            type=anns.get(attr_name),
+        )
+        for attr_name, ca
+        in ca_list
+    ]
+
+    # Walk *down* the MRO for attributes.  While doing so, we collect the names
+    # of attributes we've seen in `take_attr_names` and ignore their
+    # redefinitions deeper in the hierarchy.
+    super_attrs = []
+    taken_attr_names = {a.name: a for a in non_super_attrs}
+    for super_cls in cls.__mro__[1:-1]:
+        sub_attrs = getattr(super_cls, "__attrs_attrs__", None)
+        if sub_attrs is not None:
+            # We iterate over sub_attrs backwards so we can reverse the whole
+            # list in the end and get all attributes in the order they have
+            # been defined.
+            for a in reversed(sub_attrs):
+                prev_a = taken_attr_names.get(a.name)
+                if prev_a is None:
+                    super_attrs.append(a)
+                    taken_attr_names[a.name] = a
+                elif prev_a == a:
+                    # This happens thru multiple inheritance.  We don't want
+                    # to favor attributes that are further down in the tree
+                    # so we move them to the back.
+                    super_attrs.remove(a)
+                    super_attrs.append(a)
+
+    # Now reverse the list, such that the attributes are sorted by *descending*
+    # age.  IOW: the oldest attribute definition is at the head of the list.
+    super_attrs.reverse()
+
+    attr_names = [a.name for a in super_attrs + non_super_attrs]
+
+    AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+    attrs = AttrsClass(
+        super_attrs + [
+            Attribute.from_counting_attr(
+                name=attr_name,
+                ca=ca,
+                type=anns.get(attr_name)
+            )
+            for attr_name, ca
+            in ca_list
+        ]
+    )
+
+    had_default = False
+    for a in attrs:
+        if had_default is True and a.default is NOTHING and a.init is True:
+            raise ValueError(
+                "No mandatory attributes allowed after an attribute with a "
+                "default value or factory.  Attribute in question: {a!r}"
+                .format(a=a)
+            )
+        elif had_default is False and \
+                a.default is not NOTHING and \
+                a.init is not False:
+            had_default = True
+
+    return _Attributes((attrs, super_attrs))
+
+
+def _frozen_setattrs(self, name, value):
+    """
+    Attached to frozen classes as __setattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def _frozen_delattrs(self, name):
+    """
+    Attached to frozen classes as __delattr__.
+    """
+    raise FrozenInstanceError()
+
+
+class _ClassBuilder(object):
+    """
+    Iteratively build *one* class.
+    """
+    __slots__ = (
+        "_cls", "_cls_dict", "_attrs", "_super_names", "_attr_names", "_slots",
+        "_frozen", "_has_post_init",
+    )
+
+    def __init__(self, cls, these, slots, frozen, auto_attribs):
+        attrs, super_attrs = _transform_attrs(cls, these, auto_attribs)
+
+        self._cls = cls
+        self._cls_dict = dict(cls.__dict__) if slots else {}
+        self._attrs = attrs
+        self._super_names = set(a.name for a in super_attrs)
+        self._attr_names = tuple(a.name for a in attrs)
+        self._slots = slots
+        self._frozen = frozen or _has_frozen_superclass(cls)
+        self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
+
+        self._cls_dict["__attrs_attrs__"] = self._attrs
+
+        if frozen:
+            self._cls_dict["__setattr__"] = _frozen_setattrs
+            self._cls_dict["__delattr__"] = _frozen_delattrs
+
+    def __repr__(self):
+        return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__)
+
+    def build_class(self):
+        """
+        Finalize class based on the accumulated configuration.
+
+        Builder cannot be used anymore after calling this method.
+        """
+        if self._slots is True:
+            return self._create_slots_class()
+        else:
+            return self._patch_original_class()
+
+    def _patch_original_class(self):
+        """
+        Apply accumulated methods and return the class.
+        """
+        cls = self._cls
+        super_names = self._super_names
+
+        # Clean class of attribute definitions (`attr.ib()`s).
+        for name in self._attr_names:
+            if name not in super_names and \
+                    getattr(cls, name, None) is not None:
+                delattr(cls, name)
+
+        # Attach our dunder methods.
+        for name, value in self._cls_dict.items():
+            setattr(cls, name, value)
+
+        return cls
+
+    def _create_slots_class(self):
+        """
+        Build and return a new class with a `__slots__` attribute.
+        """
+        super_names = self._super_names
+        cd = {
+            k: v
+            for k, v in iteritems(self._cls_dict)
+            if k not in tuple(self._attr_names) + ("__dict__",)
+        }
+
+        # We only add the names of attributes that aren't inherited.
+        # Settings __slots__ to inherited attributes wastes memory.
+        cd["__slots__"] = tuple(
+            name
+            for name in self._attr_names
+            if name not in super_names
+        )
+
+        qualname = getattr(self._cls, "__qualname__", None)
+        if qualname is not None:
+            cd["__qualname__"] = qualname
+
+        attr_names = tuple(self._attr_names)
+
+        def slots_getstate(self):
+            """
+            Automatically created by attrs.
+            """
+            return tuple(getattr(self, name) for name in attr_names)
+
+        def slots_setstate(self, state):
+            """
+            Automatically created by attrs.
+            """
+            __bound_setattr = _obj_setattr.__get__(self, Attribute)
+            for name, value in zip(attr_names, state):
+                __bound_setattr(name, value)
+
+        # slots and frozen require __getstate__/__setstate__ to work
+        cd["__getstate__"] = slots_getstate
+        cd["__setstate__"] = slots_setstate
+
+        # Create new class based on old class and our methods.
+        cls = type(self._cls)(
+            self._cls.__name__,
+            self._cls.__bases__,
+            cd,
+        )
+
+        # The following is a fix for
+        # https://github.com/python-attrs/attrs/issues/102.  On Python 3,
+        # if a method mentions `__class__` or uses the no-arg super(), the
+        # compiler will bake a reference to the class in the method itself
+        # as `method.__closure__`.  Since we replace the class with a
+        # clone, we rewrite these references so it keeps working.
+        for item in cls.__dict__.values():
+            if isinstance(item, (classmethod, staticmethod)):
+                # Class- and staticmethods hide their functions inside.
+                # These might need to be rewritten as well.
+                closure_cells = getattr(item.__func__, "__closure__", None)
+            else:
+                closure_cells = getattr(item, "__closure__", None)
+
+            if not closure_cells:  # Catch None or the empty list.
+                continue
+            for cell in closure_cells:
+                if cell.cell_contents is self._cls:
+                    set_closure_cell(cell, cls)
+
+        return cls
+
+    def add_repr(self, ns):
+        self._cls_dict["__repr__"] = _make_repr(self._attrs, ns=ns)
+        return self
+
+    def add_str(self):
+        repr_ = self._cls_dict.get("__repr__")
+        if repr_ is None:
+            raise ValueError(
+                "__str__ can only be generated if a __repr__ exists."
+            )
+
+        self._cls_dict["__str__"] = repr_
+        return self
+
+    def make_unhashable(self):
+        self._cls_dict["__hash__"] = None
+        return self
+
+    def add_hash(self):
+        self._cls_dict["__hash__"] = _make_hash(self._attrs)
+        return self
+
+    def add_init(self):
+        self._cls_dict["__init__"] = _make_init(
+            self._attrs,
+            self._has_post_init,
+            self._frozen,
+        )
+        return self
+
+    def add_cmp(self):
+        cd = self._cls_dict
+
+        cd["__eq__"], cd["__ne__"], cd["__lt__"], cd["__le__"], cd["__gt__"], \
+            cd["__ge__"] = _make_cmp(self._attrs)
+
+        return self
+
+
+def attrs(maybe_cls=None, these=None, repr_ns=None,
+          repr=True, cmp=True, hash=None, init=True,
+          slots=False, frozen=False, str=False, auto_attribs=False):
+    r"""
+    A class decorator that adds `dunder
+    <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the
+    specified attributes using :func:`attr.ib` or the *these* argument.
+
+    :param these: A dictionary of name to :func:`attr.ib` mappings.  This is
+        useful to avoid the definition of your attributes within the class body
+        because you can't (e.g. if you want to add ``__repr__`` methods to
+        Django models) or don't want to.
+
+        If *these* is not ``None``, ``attrs`` will *not* search the class body
+        for attributes.
+
+    :type these: :class:`dict` of :class:`str` to :func:`attr.ib`
+
+    :param str repr_ns: When using nested classes, there's no way in Python 2
+        to automatically detect that.  Therefore it's possible to set the
+        namespace explicitly for a more meaningful ``repr`` output.
+    :param bool repr: Create a ``__repr__`` method with a human readable
+        representation of ``attrs`` attributes..
+    :param bool str: Create a ``__str__`` method that is identical to
+        ``__repr__``.  This is usually not necessary except for
+        :class:`Exception`\ s.
+    :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``,
+        ``__gt__``, and ``__ge__`` methods that compare the class as if it were
+        a tuple of its ``attrs`` attributes.  But the attributes are *only*
+        compared, if the type of both classes is *identical*!
+    :param hash: If ``None`` (default), the ``__hash__`` method is generated
+        according how *cmp* and *frozen* are set.
+
+        1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you.
+        2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to
+           None, marking it unhashable (which it is).
+        3. If *cmp* is False, ``__hash__`` will be left untouched meaning the
+           ``__hash__`` method of the superclass will be used (if superclass is
+           ``object``, this means it will fall back to id-based hashing.).
+
+        Although not recommended, you can decide for yourself and force
+        ``attrs`` to create one (e.g. if the class is immutable even though you
+        didn't freeze it programmatically) by passing ``True`` or not.  Both of
+        these cases are rather special and should be used carefully.
+
+        See the `Python documentation \
+        <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_
+        and the `GitHub issue that led to the default behavior \
+        <https://github.com/python-attrs/attrs/issues/136>`_ for more details.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Create a ``__init__`` method that initializes the
+        ``attrs`` attributes.  Leading underscores are stripped for the
+        argument name.  If a ``__attrs_post_init__`` method exists on the
+        class, it will be called after the class is fully initialized.
+    :param bool slots: Create a slots_-style class that's more
+        memory-efficient.  See :ref:`slots` for further ramifications.
+    :param bool frozen: Make instances immutable after initialization.  If
+        someone attempts to modify a frozen instance,
+        :exc:`attr.exceptions.FrozenInstanceError` is raised.
+
+        Please note:
+
+            1. This is achieved by installing a custom ``__setattr__`` method
+               on your class so you can't implement an own one.
+
+            2. True immutability is impossible in Python.
+
+            3. This *does* have a minor a runtime performance :ref:`impact
+               <how-frozen>` when initializing new instances.  In other words:
+               ``__init__`` is slightly slower with ``frozen=True``.
+
+            4. If a class is frozen, you cannot modify ``self`` in
+               ``__attrs_post_init__`` or a self-written ``__init__``. You can
+               circumvent that limitation by using
+               ``object.__setattr__(self, "attribute_name", value)``.
+
+        ..  _slots: https://docs.python.org/3/reference/datamodel.html#slots
+    :param bool auto_attribs: If True, collect `PEP 526`_-annotated attributes
+        (Python 3.6 and later only) from the class body.
+
+        In this case, you **must** annotate every field.  If ``attrs``
+        encounters a field that is set to an :func:`attr.ib` but lacks a type
+        annotation, an :exc:`attr.exceptions.UnannotatedAttributeError` is
+        raised.  Use ``field_name: typing.Any = attr.ib(...)`` if you don't
+        want to set a type.
+
+        If you assign a value to those attributes (e.g. ``x: int = 42``), that
+        value becomes the default value like if it were passed using
+        ``attr.ib(default=42)``.  Passing an instance of :class:`Factory` also
+        works as expected.
+
+        Attributes annotated as :data:`typing.ClassVar` are **ignored**.
+
+        .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/
+
+    ..  versionadded:: 16.0.0 *slots*
+    ..  versionadded:: 16.1.0 *frozen*
+    ..  versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``.
+    ..  versionchanged::
+            17.1.0 *hash* supports ``None`` as value which is also the default
+            now.
+    .. versionadded:: 17.3.0 *auto_attribs*
+    """
+    def wrap(cls):
+        if getattr(cls, "__class__", None) is None:
+            raise TypeError("attrs only works with new-style classes.")
+
+        builder = _ClassBuilder(cls, these, slots, frozen, auto_attribs)
+
+        if repr is True:
+            builder.add_repr(repr_ns)
+        if str is True:
+            builder.add_str()
+        if cmp is True:
+            builder.add_cmp()
+
+        if hash is not True and hash is not False and hash is not None:
+            # Can't use `hash in` because 1 == True for example.
+            raise TypeError(
+                "Invalid value for hash.  Must be True, False, or None."
+            )
+        elif hash is False or (hash is None and cmp is False):
+            pass
+        elif hash is True or (hash is None and cmp is True and frozen is True):
+            builder.add_hash()
+        else:
+            builder.make_unhashable()
+
+        if init is True:
+            builder.add_init()
+
+        return builder.build_class()
+
+    # maybe_cls's type depends on the usage of the decorator.  It's a class
+    # if it's used as `@attrs` but ``None`` if used as `@attrs()`.
+    if maybe_cls is None:
+        return wrap
+    else:
+        return wrap(maybe_cls)
+
+
+_attrs = attrs
+"""
+Internal alias so we can use it in functions that take an argument called
+*attrs*.
+"""
+
+
+if PY2:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return (
+            getattr(
+                cls.__setattr__, "__module__", None
+            ) == _frozen_setattrs.__module__ and
+            cls.__setattr__.__name__ == _frozen_setattrs.__name__
+        )
+else:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return cls.__setattr__ == _frozen_setattrs
+
+
+def _attrs_to_tuple(obj, attrs):
+    """
+    Create a tuple of all values of *obj*'s *attrs*.
+    """
+    return tuple(getattr(obj, a.name) for a in attrs)
+
+
+def _make_hash(attrs):
+    attrs = tuple(
+        a
+        for a in attrs
+        if a.hash is True or (a.hash is None and a.cmp is True)
+    )
+
+    # We cache the generated init methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated hash %s>" % (sha1.hexdigest(),)
+    type_hash = hash(unique_filename)
+    lines = [
+        "def __hash__(self):",
+        "    return hash((",
+        "        %d," % (type_hash,),
+    ]
+    for a in attrs:
+        lines.append("        self.%s," % (a.name))
+
+    lines.append("    ))")
+
+    script = "\n".join(lines)
+    globs = {}
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+
+    return locs["__hash__"]
+
+
+def _add_hash(cls, attrs):
+    """
+    Add a hash method to *cls*.
+    """
+    cls.__hash__ = _make_hash(attrs)
+    return cls
+
+
+def _make_cmp(attrs):
+    attrs = [a for a in attrs if a.cmp]
+
+    def attrs_to_tuple(obj):
+        """
+        Save us some typing.
+        """
+        return _attrs_to_tuple(obj, attrs)
+
+    def eq(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) == attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ne(self, other):
+        """
+        Automatically created by attrs.
+        """
+        result = eq(self, other)
+        if result is NotImplemented:
+            return NotImplemented
+        else:
+            return not result
+
+    def lt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) < attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def le(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) <= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def gt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) > attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ge(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) >= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    return eq, ne, lt, le, gt, ge
+
+
+def _add_cmp(cls, attrs=None):
+    """
+    Add comparison methods to *cls*.
+    """
+    if attrs is None:
+        attrs = cls.__attrs_attrs__
+
+    cls.__eq__, cls.__ne__, cls.__lt__, cls.__le__, cls.__gt__, cls.__ge__ = \
+        _make_cmp(attrs)
+
+    return cls
+
+
+def _make_repr(attrs, ns):
+    """
+    Make a repr method for *attr_names* adding *ns* to the full name.
+    """
+    attr_names = tuple(
+        a.name
+        for a in attrs
+        if a.repr
+    )
+
+    def repr_(self):
+        """
+        Automatically created by attrs.
+        """
+        real_cls = self.__class__
+        if ns is None:
+            qualname = getattr(real_cls, "__qualname__", None)
+            if qualname is not None:
+                class_name = qualname.rsplit(">.", 1)[-1]
+            else:
+                class_name = real_cls.__name__
+        else:
+            class_name = ns + "." + real_cls.__name__
+
+        return "{0}({1})".format(
+            class_name,
+            ", ".join(
+                name + "=" + repr(getattr(self, name))
+                for name in attr_names
+            )
+        )
+    return repr_
+
+
+def _add_repr(cls, ns=None, attrs=None):
+    """
+    Add a repr method to *cls*.
+    """
+    if attrs is None:
+        attrs = cls.__attrs_attrs__
+
+    repr_ = _make_repr(attrs, ns)
+    cls.__repr__ = repr_
+    return cls
+
+
+def _make_init(attrs, post_init, frozen):
+    attrs = [
+        a
+        for a in attrs
+        if a.init or a.default is not NOTHING
+    ]
+
+    # We cache the generated init methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated init {0}>".format(
+        sha1.hexdigest()
+    )
+
+    script, globs = _attrs_to_init_script(
+        attrs,
+        frozen,
+        post_init,
+    )
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    attr_dict = dict((a.name, a) for a in attrs)
+    globs.update({
+        "NOTHING": NOTHING,
+        "attr_dict": attr_dict,
+    })
+    if frozen is True:
+        # Save the lookup overhead in __init__ if we need to circumvent
+        # immutability.
+        globs["_cached_setattr"] = _obj_setattr
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+
+    return locs["__init__"]
+
+
+def _add_init(cls, frozen):
+    """
+    Add a __init__ method to *cls*.  If *frozen* is True, make it immutable.
+    """
+    cls.__init__ = _make_init(
+        cls.__attrs_attrs__,
+        getattr(cls, "__attrs_post_init__", False),
+        frozen,
+    )
+    return cls
+
+
+def fields(cls):
+    """
+    Returns the tuple of ``attrs`` attributes for a class.
+
+    The tuple also allows accessing the fields by their names (see below for
+    examples).
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: tuple (with name accessors) of :class:`attr.Attribute`
+
+    ..  versionchanged:: 16.2.0 Returned tuple allows accessing the fields
+        by name.
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return attrs
+
+
+def validate(inst):
+    """
+    Validate all attributes on *inst* that have a validator.
+
+    Leaves all exceptions through.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    """
+    if _config._run_validators is False:
+        return
+
+    for a in fields(inst.__class__):
+        v = a.validator
+        if v is not None:
+            v(inst, a, getattr(inst, a.name))
+
+
+def _attrs_to_init_script(attrs, frozen, post_init):
+    """
+    Return a script of an initializer for *attrs* and a dict of globals.
+
+    The globals are expected by the generated script.
+
+     If *frozen* is True, we cannot set the attributes directly so we use
+    a cached ``object.__setattr__``.
+    """
+    lines = []
+    if frozen is True:
+        lines.append(
+            # Circumvent the __setattr__ descriptor to save one lookup per
+            # assignment.
+            "_setattr = _cached_setattr.__get__(self, self.__class__)"
+        )
+
+        def fmt_setter(attr_name, value_var):
+            return "_setattr('%(attr_name)s', %(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+    else:
+        def fmt_setter(attr_name, value):
+            return "self.%(attr_name)s = %(value)s" % {
+                "attr_name": attr_name,
+                "value": value,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+
+    args = []
+    attrs_to_validate = []
+
+    # This is a dictionary of names to validator and converter callables.
+    # Injecting this into __init__ globals lets us avoid lookups.
+    names_for_globals = {}
+
+    for a in attrs:
+        if a.validator:
+            attrs_to_validate.append(a)
+        attr_name = a.name
+        arg_name = a.name.lstrip("_")
+        has_factory = isinstance(a.default, Factory)
+        if has_factory and a.default.takes_self:
+            maybe_self = "self"
+        else:
+            maybe_self = ""
+        if a.init is False:
+            if has_factory:
+                init_factory_name = _init_factory_pat.format(a.name)
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)
+                    ))
+                names_for_globals[init_factory_name] = a.default.factory
+            else:
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+        elif a.default is not NOTHING and not has_factory:
+            args.append(
+                "{arg_name}=attr_dict['{attr_name}'].default".format(
+                    arg_name=arg_name,
+                    attr_name=attr_name,
+                )
+            )
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+        elif has_factory:
+            args.append("{arg_name}=NOTHING".format(arg_name=arg_name))
+            lines.append("if {arg_name} is not NOTHING:"
+                         .format(arg_name=arg_name))
+            init_factory_name = _init_factory_pat.format(a.name)
+            if a.convert is not None:
+                lines.append("    " + fmt_setter_with_converter(attr_name,
+                                                                arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter_with_converter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append("    " + fmt_setter(attr_name, arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+            names_for_globals[init_factory_name] = a.default.factory
+        else:
+            args.append(arg_name)
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+
+    if attrs_to_validate:  # we can skip this if there are no validators.
+        names_for_globals["_config"] = _config
+        lines.append("if _config._run_validators is True:")
+        for a in attrs_to_validate:
+            val_name = "__attr_validator_{}".format(a.name)
+            attr_name = "__attr_{}".format(a.name)
+            lines.append("    {}(self, {}, self.{})".format(
+                val_name, attr_name, a.name))
+            names_for_globals[val_name] = a.validator
+            names_for_globals[attr_name] = a
+    if post_init:
+        lines.append("self.__attrs_post_init__()")
+
+    return """\
+def __init__(self, {args}):
+    {lines}
+""".format(
+        args=", ".join(args),
+        lines="\n    ".join(lines) if lines else "pass",
+    ), names_for_globals
+
+
+class Attribute(object):
+    """
+    *Read-only* representation of an attribute.
+
+    :attribute name: The name of the attribute.
+
+    Plus *all* arguments of :func:`attr.ib`.
+    """
+    __slots__ = (
+        "name", "default", "validator", "repr", "cmp", "hash", "init",
+        "convert", "metadata", "type"
+    )
+
+    def __init__(self, name, default, validator, repr, cmp, hash, init,
+                 convert=None, metadata=None, type=None):
+        # Cache this descriptor here to speed things up later.
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+
+        bound_setattr("name", name)
+        bound_setattr("default", default)
+        bound_setattr("validator", validator)
+        bound_setattr("repr", repr)
+        bound_setattr("cmp", cmp)
+        bound_setattr("hash", hash)
+        bound_setattr("init", init)
+        bound_setattr("convert", convert)
+        bound_setattr("metadata", (metadata_proxy(metadata) if metadata
+                                   else _empty_metadata_singleton))
+        bound_setattr("type", type)
+
+    def __setattr__(self, name, value):
+        raise FrozenInstanceError()
+
+    @classmethod
+    def from_counting_attr(cls, name, ca, type=None):
+        # type holds the annotated value. deal with conflicts:
+        if type is None:
+            type = ca.type
+        elif ca.type is not None:
+            raise ValueError(
+                "Type annotation and type argument cannot both be present"
+            )
+        inst_dict = {
+            k: getattr(ca, k)
+            for k
+            in Attribute.__slots__
+            if k not in (
+                "name", "validator", "default", "type"
+            )  # exclude methods
+        }
+        return cls(name=name, validator=ca._validator, default=ca._default,
+                   type=type, **inst_dict)
+
+    # Don't use _add_pickle since fields(Attribute) doesn't work
+    def __getstate__(self):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(self, name) if name != "metadata"
+                     else dict(self.metadata)
+                     for name in self.__slots__)
+
+    def __setstate__(self, state):
+        """
+        Play nice with pickle.
+        """
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+        for name, value in zip(self.__slots__, state):
+            if name != "metadata":
+                bound_setattr(name, value)
+            else:
+                bound_setattr(name, metadata_proxy(value) if value else
+                              _empty_metadata_singleton)
+
+
+_a = [Attribute(name=name, default=NOTHING, validator=None,
+                repr=True, cmp=True, hash=(name != "metadata"), init=True)
+      for name in Attribute.__slots__]
+
+Attribute = _add_hash(
+    _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a),
+    attrs=[a for a in _a if a.hash]
+)
+
+
+class _CountingAttr(object):
+    """
+    Intermediate representation of attributes that uses a counter to preserve
+    the order in which the attributes have been defined.
+
+    *Internal* data structure of the attrs library.  Running into is most
+    likely the result of a bug like a forgotten `@attr.s` decorator.
+    """
+    __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init",
+                 "metadata", "_validator", "convert", "type")
+    __attrs_attrs__ = tuple(
+        Attribute(name=name, default=NOTHING, validator=None,
+                  repr=True, cmp=True, hash=True, init=True)
+        for name
+        in ("counter", "_default", "repr", "cmp", "hash", "init",)
+    ) + (
+        Attribute(name="metadata", default=None, validator=None,
+                  repr=True, cmp=True, hash=False, init=True),
+    )
+    cls_counter = 0
+
+    def __init__(self, default, validator, repr, cmp, hash, init, convert,
+                 metadata, type):
+        _CountingAttr.cls_counter += 1
+        self.counter = _CountingAttr.cls_counter
+        self._default = default
+        # If validator is a list/tuple, wrap it using helper validator.
+        if validator and isinstance(validator, (list, tuple)):
+            self._validator = and_(*validator)
+        else:
+            self._validator = validator
+        self.repr = repr
+        self.cmp = cmp
+        self.hash = hash
+        self.init = init
+        self.convert = convert
+        self.metadata = metadata
+        self.type = type
+
+    def validator(self, meth):
+        """
+        Decorator that adds *meth* to the list of validators.
+
+        Returns *meth* unchanged.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._validator is None:
+            self._validator = meth
+        else:
+            self._validator = and_(self._validator, meth)
+        return meth
+
+    def default(self, meth):
+        """
+        Decorator that allows to set the default for an attribute.
+
+        Returns *meth* unchanged.
+
+        :raises DefaultAlreadySetError: If default has been set before.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._default is not NOTHING:
+            raise DefaultAlreadySetError()
+
+        self._default = Factory(meth, takes_self=True)
+
+        return meth
+
+
+_CountingAttr = _add_cmp(_add_repr(_CountingAttr))
+
+
+@attrs(slots=True, init=False, hash=True)
+class Factory(object):
+    """
+    Stores a factory callable.
+
+    If passed as the default value to :func:`attr.ib`, the factory is used to
+    generate a new value.
+
+    :param callable factory: A callable that takes either none or exactly one
+        mandatory positional argument depending on *takes_self*.
+    :param bool takes_self: Pass the partially initialized instance that is
+        being initialized as a positional argument.
+
+    .. versionadded:: 17.1.0  *takes_self*
+    """
+    factory = attrib()
+    takes_self = attrib()
+
+    def __init__(self, factory, takes_self=False):
+        """
+        `Factory` is part of the default machinery so if we want a default
+        value here, we have to implement it ourselves.
+        """
+        self.factory = factory
+        self.takes_self = takes_self
+
+
+def make_class(name, attrs, bases=(object,), **attributes_arguments):
+    """
+    A quick way to create a new class called *name* with *attrs*.
+
+    :param name: The name for the new class.
+    :type name: str
+
+    :param attrs: A list of names or a dictionary of mappings of names to
+        attributes.
+    :type attrs: :class:`list` or :class:`dict`
+
+    :param tuple bases: Classes that the new class will subclass.
+
+    :param attributes_arguments: Passed unmodified to :func:`attr.s`.
+
+    :return: A new class with *attrs*.
+    :rtype: type
+
+    ..  versionadded:: 17.1.0 *bases*
+    """
+    if isinstance(attrs, dict):
+        cls_dict = attrs
+    elif isinstance(attrs, (list, tuple)):
+        cls_dict = dict((a, attrib()) for a in attrs)
+    else:
+        raise TypeError("attrs argument must be a dict or a list.")
+
+    post_init = cls_dict.pop("__attrs_post_init__", None)
+    type_ = type(
+        name,
+        bases,
+        {} if post_init is None else {"__attrs_post_init__": post_init}
+    )
+    # For pickling to work, the __module__ variable needs to be set to the
+    # frame where the class is created.  Bypass this step in environments where
+    # sys._getframe is not defined (Jython for example) or sys._getframe is not
+    # defined for arguments greater than 0 (IronPython).
+    try:
+        type_.__module__ = sys._getframe(1).f_globals.get(
+            "__name__", "__main__",
+        )
+    except (AttributeError, ValueError):
+        pass
+
+    return _attrs(these=cls_dict, **attributes_arguments)(type_)
+
+
+# These are required by within this module so we define them here and merely
+# import into .validators.
+
+
+@attrs(slots=True, hash=True)
+class _AndValidator(object):
+    """
+    Compose many validators to a single one.
+    """
+    _validators = attrib()
+
+    def __call__(self, inst, attr, value):
+        for v in self._validators:
+            v(inst, attr, value)
+
+
+def and_(*validators):
+    """
+    A validator that composes multiple validators into one.
+
+    When called on a value, it runs all wrapped validators.
+
+    :param validators: Arbitrary number of validators.
+    :type validators: callables
+
+    .. versionadded:: 17.1.0
+    """
+    vals = []
+    for validator in validators:
+        vals.extend(
+            validator._validators if isinstance(validator, _AndValidator)
+            else [validator]
+        )
+
+    return _AndValidator(tuple(vals))
diff --git a/tools/third_party/attrs/src/attr/converters.py b/tools/third_party/attrs/src/attr/converters.py
new file mode 100644
index 0000000..3b3bac9
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/converters.py
@@ -0,0 +1,24 @@
+"""
+Commonly useful converters.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+def optional(converter):
+    """
+    A converter that allows an attribute to be optional. An optional attribute
+    is one which can be set to ``None``.
+
+    :param callable converter: the converter that is used for non-``None``
+        values.
+
+    ..  versionadded:: 17.1.0
+    """
+
+    def optional_converter(val):
+        if val is None:
+            return None
+        return converter(val)
+
+    return optional_converter
diff --git a/tools/third_party/attrs/src/attr/exceptions.py b/tools/third_party/attrs/src/attr/exceptions.py
new file mode 100644
index 0000000..f949f3c
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/exceptions.py
@@ -0,0 +1,48 @@
+from __future__ import absolute_import, division, print_function
+
+
+class FrozenInstanceError(AttributeError):
+    """
+    A frozen/immutable instance has been attempted to be modified.
+
+    It mirrors the behavior of ``namedtuples`` by using the same error message
+    and subclassing :exc:`AttributeError`.
+
+    .. versionadded:: 16.1.0
+    """
+    msg = "can't set attribute"
+    args = [msg]
+
+
+class AttrsAttributeNotFoundError(ValueError):
+    """
+    An ``attrs`` function couldn't find an attribute that the user asked for.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class NotAnAttrsClassError(ValueError):
+    """
+    A non-``attrs`` class has been passed into an ``attrs`` function.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class DefaultAlreadySetError(RuntimeError):
+    """
+    A default has been set using ``attr.ib()`` and is attempted to be reset
+    using the decorator.
+
+    .. versionadded:: 17.1.0
+    """
+
+
+class UnannotatedAttributeError(RuntimeError):
+    """
+    A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
+    annotation.
+
+    .. versionadded:: 17.3.0
+    """
diff --git a/tools/third_party/attrs/src/attr/filters.py b/tools/third_party/attrs/src/attr/filters.py
new file mode 100644
index 0000000..d1bad35
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/filters.py
@@ -0,0 +1,52 @@
+"""
+Commonly useful filters for :func:`attr.asdict`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._compat import isclass
+from ._make import Attribute
+
+
+def _split_what(what):
+    """
+    Returns a tuple of `frozenset`s of classes and attributes.
+    """
+    return (
+        frozenset(cls for cls in what if isclass(cls)),
+        frozenset(cls for cls in what if isinstance(cls, Attribute)),
+    )
+
+
+def include(*what):
+    """
+    Whitelist *what*.
+
+    :param what: What to whitelist.
+    :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def include_(attribute, value):
+        return value.__class__ in cls or attribute in attrs
+
+    return include_
+
+
+def exclude(*what):
+    """
+    Blacklist *what*.
+
+    :param what: What to blacklist.
+    :type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def exclude_(attribute, value):
+        return value.__class__ not in cls and attribute not in attrs
+
+    return exclude_
diff --git a/tools/third_party/attrs/src/attr/validators.py b/tools/third_party/attrs/src/attr/validators.py
new file mode 100644
index 0000000..f8892fc
--- /dev/null
+++ b/tools/third_party/attrs/src/attr/validators.py
@@ -0,0 +1,166 @@
+"""
+Commonly useful validators.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import _AndValidator, and_, attrib, attrs
+
+
+__all__ = [
+    "and_",
+    "in_",
+    "instance_of",
+    "optional",
+    "provides",
+]
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InstanceOfValidator(object):
+    type = attrib()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not isinstance(value, self.type):
+            raise TypeError(
+                "'{name}' must be {type!r} (got {value!r} that is a "
+                "{actual!r})."
+                .format(name=attr.name, type=self.type,
+                        actual=value.__class__, value=value),
+                attr, self.type, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<instance_of validator for type {type!r}>"
+            .format(type=self.type)
+        )
+
+
+def instance_of(type):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with a wrong type for this particular attribute (checks are performed using
+    :func:`isinstance` therefore it's also valid to pass a tuple of types).
+
+    :param type: The type to check for.
+    :type type: type or tuple of types
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected type, and the value it
+        got.
+    """
+    return _InstanceOfValidator(type)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _ProvidesValidator(object):
+    interface = attrib()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not self.interface.providedBy(value):
+            raise TypeError(
+                "'{name}' must provide {interface!r} which {value!r} "
+                "doesn't."
+                .format(name=attr.name, interface=self.interface, value=value),
+                attr, self.interface, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<provides validator for interface {interface!r}>"
+            .format(interface=self.interface)
+        )
+
+
+def provides(interface):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with an object that does not provide the requested *interface* (checks are
+    performed using ``interface.providedBy(value)`` (see `zope.interface
+    <https://zopeinterface.readthedocs.io/en/latest/>`_).
+
+    :param zope.interface.Interface interface: The interface to check for.
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected interface, and the
+        value it got.
+    """
+    return _ProvidesValidator(interface)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _OptionalValidator(object):
+    validator = attrib()
+
+    def __call__(self, inst, attr, value):
+        if value is None:
+            return
+
+        self.validator(inst, attr, value)
+
+    def __repr__(self):
+        return (
+            "<optional validator for {what} or None>"
+            .format(what=repr(self.validator))
+        )
+
+
+def optional(validator):
+    """
+    A validator that makes an attribute optional.  An optional attribute is one
+    which can be set to ``None`` in addition to satisfying the requirements of
+    the sub-validator.
+
+    :param validator: A validator (or a list of validators) that is used for
+        non-``None`` values.
+    :type validator: callable or :class:`list` of callables.
+
+    .. versionadded:: 15.1.0
+    .. versionchanged:: 17.1.0 *validator* can be a list of validators.
+    """
+    if isinstance(validator, list):
+        return _OptionalValidator(_AndValidator(validator))
+    return _OptionalValidator(validator)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InValidator(object):
+    options = attrib()
+
+    def __call__(self, inst, attr, value):
+        if value not in self.options:
+            raise ValueError(
+                "'{name}' must be in {options!r} (got {value!r})"
+                .format(name=attr.name, options=self.options, value=value)
+            )
+
+    def __repr__(self):
+        return (
+            "<in_ validator with options {options!r}>"
+            .format(options=self.options)
+        )
+
+
+def in_(options):
+    """
+    A validator that raises a :exc:`ValueError` if the initializer is called
+    with a value that does not belong in the options provided.  The check is
+    performed using ``value in options``.
+
+    :param options: Allowed options.
+    :type options: list, tuple, :class:`enum.Enum`, ...
+
+    :raises ValueError: With a human readable error message, the attribute (of
+       type :class:`attr.Attribute`), the expected options, and the value it
+       got.
+
+    .. versionadded:: 17.1.0
+    """
+    return _InValidator(options)
diff --git a/tools/py/testing/log/__init__.py b/tools/third_party/attrs/tests/__init__.py
similarity index 100%
copy from tools/py/testing/log/__init__.py
copy to tools/third_party/attrs/tests/__init__.py
diff --git a/tools/third_party/attrs/tests/test_annotations.py b/tools/third_party/attrs/tests/test_annotations.py
new file mode 100644
index 0000000..602f21b
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_annotations.py
@@ -0,0 +1,156 @@
+"""
+Tests for PEP-526 type annotations.
+
+Python 3.6+ only.
+"""
+
+import types
+import typing
+
+import pytest
+
+import attr
+
+from attr.exceptions import UnannotatedAttributeError
+
+
+class TestAnnotations:
+    """
+    Tests for types derived from variable annotations (PEP-526).
+    """
+
+    def test_basic_annotations(self):
+        """
+        Sets the `Attribute.type` attr from basic type annotations.
+        """
+        @attr.s
+        class C:
+            x: int = attr.ib()
+            y = attr.ib(type=str)
+            z = attr.ib()
+
+        assert int is attr.fields(C).x.type
+        assert str is attr.fields(C).y.type
+        assert None is attr.fields(C).z.type
+
+    def test_catches_basic_type_conflict(self):
+        """
+        Raises ValueError if type is specified both ways.
+        """
+        with pytest.raises(ValueError) as e:
+            @attr.s
+            class C:
+                x: int = attr.ib(type=int)
+
+        assert (
+            "Type annotation and type argument cannot both be present",
+        ) == e.value.args
+
+    def test_typing_annotations(self):
+        """
+        Sets the `Attribute.type` attr from typing annotations.
+        """
+        @attr.s
+        class C:
+            x: typing.List[int] = attr.ib()
+            y = attr.ib(type=typing.Optional[str])
+
+        assert typing.List[int] is attr.fields(C).x.type
+        assert typing.Optional[str] is attr.fields(C).y.type
+
+    def test_only_attrs_annotations_collected(self):
+        """
+        Annotations that aren't set to an attr.ib are ignored.
+        """
+        @attr.s
+        class C:
+            x: typing.List[int] = attr.ib()
+            y: int
+
+        assert 1 == len(attr.fields(C))
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs(self, slots):
+        """
+        If *auto_attribs* is True, bare annotations are collected too.
+        Defaults work and class variables are ignored.
+        """
+        @attr.s(auto_attribs=True, slots=slots)
+        class C:
+            cls_var: typing.ClassVar[int] = 23
+            a: int
+            x: typing.List[int] = attr.Factory(list)
+            y: int = 2
+            z: int = attr.ib(default=3)
+            foo: typing.Any = None
+
+        i = C(42)
+        assert "C(a=42, x=[], y=2, z=3, foo=None)" == repr(i)
+
+        attr_names = set(a.name for a in C.__attrs_attrs__)
+        assert "a" in attr_names  # just double check that the set works
+        assert "cls_var" not in attr_names
+
+        assert int == attr.fields(C).a.type
+
+        assert attr.Factory(list) == attr.fields(C).x.default
+        assert typing.List[int] == attr.fields(C).x.type
+
+        assert int == attr.fields(C).y.type
+        assert 2 == attr.fields(C).y.default
+
+        assert int == attr.fields(C).z.type
+
+        assert typing.Any == attr.fields(C).foo.type
+
+        # Class body is clean.
+        if slots is False:
+            with pytest.raises(AttributeError):
+                C.y
+
+            assert 2 == i.y
+        else:
+            assert isinstance(C.y, types.MemberDescriptorType)
+
+            i.y = 23
+            assert 23 == i.y
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs_unannotated(self, slots):
+        """
+        Unannotated `attr.ib`s raise an error.
+        """
+        with pytest.raises(UnannotatedAttributeError) as e:
+            @attr.s(slots=slots, auto_attribs=True)
+            class C:
+                v = attr.ib()
+                x: int
+                y = attr.ib()
+                z: str
+
+        assert (
+            "The following `attr.ib`s lack a type annotation: v, y.",
+        ) == e.value.args
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs_subclassing(self, slots):
+        """
+        Attributes from super classes are inherited, it doesn't matter if the
+        subclass has annotations or not.
+
+        Ref #291
+        """
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: int = 1
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class B(A):
+            b: int = 2
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class C(A):
+            pass
+
+        assert "B(a=1, b=2)" == repr(B())
+        assert "C(a=1)" == repr(C())
diff --git a/tools/third_party/attrs/tests/test_config.py b/tools/third_party/attrs/tests/test_config.py
new file mode 100644
index 0000000..287be03
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_config.py
@@ -0,0 +1,43 @@
+"""
+Tests for `attr._config`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+from attr import _config
+
+
+class TestConfig(object):
+    def test_default(self):
+        """
+        Run validators by default.
+        """
+        assert True is _config._run_validators
+
+    def test_set_run_validators(self):
+        """
+        Sets `_run_validators`.
+        """
+        _config.set_run_validators(False)
+        assert False is _config._run_validators
+        _config.set_run_validators(True)
+        assert True is _config._run_validators
+
+    def test_get_run_validators(self):
+        """
+        Returns `_run_validators`.
+        """
+        _config._run_validators = False
+        assert _config._run_validators is _config.get_run_validators()
+        _config._run_validators = True
+        assert _config._run_validators is _config.get_run_validators()
+
+    def test_wrong_type(self):
+        """
+        Passing anything else than a boolean raises TypeError.
+        """
+        with pytest.raises(TypeError) as e:
+            _config.set_run_validators("False")
+        assert "'run' must be bool." == e.value.args[0]
diff --git a/tools/third_party/attrs/tests/test_converters.py b/tools/third_party/attrs/tests/test_converters.py
new file mode 100644
index 0000000..daf39d8
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_converters.py
@@ -0,0 +1,36 @@
+"""
+Tests for `attr.converters`.
+"""
+
+from __future__ import absolute_import
+
+import pytest
+
+from attr.converters import optional
+
+
+class TestOptional(object):
+    """
+    Tests for `optional`.
+    """
+    def test_success_with_type(self):
+        """
+        Wrapped converter is used as usual if value is not None.
+        """
+        c = optional(int)
+        assert c("42") == 42
+
+    def test_success_with_none(self):
+        """
+        Nothing happens if None.
+        """
+        c = optional(int)
+        assert c(None) is None
+
+    def test_fail(self):
+        """
+        Propagates the underlying conversion error when conversion fails.
+        """
+        c = optional(int)
+        with pytest.raises(ValueError):
+            c("not_an_int")
diff --git a/tools/third_party/attrs/tests/test_dark_magic.py b/tools/third_party/attrs/tests/test_dark_magic.py
new file mode 100644
index 0000000..bc6665c
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_dark_magic.py
@@ -0,0 +1,382 @@
+"""
+End-to-end tests.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pickle
+
+import pytest
+import six
+
+from hypothesis import given
+from hypothesis.strategies import booleans
+
+import attr
+
+from attr._compat import TYPE
+from attr._make import NOTHING, Attribute
+from attr.exceptions import FrozenInstanceError
+
+
+@attr.s
+class C1(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+
+@attr.s(slots=True)
+class C1Slots(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+
+foo = None
+
+
+@attr.s()
+class C2(object):
+    x = attr.ib(default=foo)
+    y = attr.ib(default=attr.Factory(list))
+
+
+@attr.s(slots=True)
+class C2Slots(object):
+    x = attr.ib(default=foo)
+    y = attr.ib(default=attr.Factory(list))
+
+
+@attr.s
+class Super(object):
+    x = attr.ib()
+
+    def meth(self):
+        return self.x
+
+
+@attr.s(slots=True)
+class SuperSlots(object):
+    x = attr.ib()
+
+    def meth(self):
+        return self.x
+
+
+@attr.s
+class Sub(Super):
+    y = attr.ib()
+
+
+@attr.s(slots=True)
+class SubSlots(SuperSlots):
+    y = attr.ib()
+
+
+@attr.s(frozen=True, slots=True)
+class Frozen(object):
+    x = attr.ib()
+
+
+@attr.s
+class SubFrozen(Frozen):
+    y = attr.ib()
+
+
+@attr.s(frozen=True, slots=False)
+class FrozenNoSlots(object):
+    x = attr.ib()
+
+
+class Meta(type):
+    pass
+
+
+@attr.s
+@six.add_metaclass(Meta)
+class WithMeta(object):
+    pass
+
+
+@attr.s(slots=True)
+@six.add_metaclass(Meta)
+class WithMetaSlots(object):
+    pass
+
+
+FromMakeClass = attr.make_class("FromMakeClass", ["x"])
+
+
+class TestDarkMagic(object):
+    """
+    Integration tests.
+    """
+    @pytest.mark.parametrize("cls", [C2, C2Slots])
+    def test_fields(self, cls):
+        """
+        `attr.fields` works.
+        """
+        assert (
+            Attribute(name="x", default=foo, validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+            Attribute(name="y", default=attr.Factory(list), validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+        ) == attr.fields(cls)
+
+    @pytest.mark.parametrize("cls", [C1, C1Slots])
+    def test_asdict(self, cls):
+        """
+        `attr.asdict` works.
+        """
+        assert {
+            "x": 1,
+            "y": 2,
+        } == attr.asdict(cls(x=1, y=2))
+
+    @pytest.mark.parametrize("cls", [C1, C1Slots])
+    def test_validator(self, cls):
+        """
+        `instance_of` raises `TypeError` on type mismatch.
+        """
+        with pytest.raises(TypeError) as e:
+            cls("1", 2)
+
+        # Using C1 explicitly, since slot classes don't support this.
+        assert (
+            "'x' must be <{type} 'int'> (got '1' that is a <{type} "
+            "'str'>).".format(type=TYPE),
+            attr.fields(C1).x, int, "1",
+        ) == e.value.args
+
+    @given(booleans())
+    def test_renaming(self, slots):
+        """
+        Private members are renamed but only in `__init__`.
+        """
+        @attr.s(slots=slots)
+        class C3(object):
+            _x = attr.ib()
+
+        assert "C3(_x=1)" == repr(C3(x=1))
+
+    @given(booleans(), booleans())
+    def test_programmatic(self, slots, frozen):
+        """
+        `attr.make_class` works.
+        """
+        PC = attr.make_class("PC", ["a", "b"], slots=slots, frozen=frozen)
+        assert (
+            Attribute(name="a", default=NOTHING, validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+            Attribute(name="b", default=NOTHING, validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+        ) == attr.fields(PC)
+
+    @pytest.mark.parametrize("cls", [Sub, SubSlots])
+    def test_subclassing_with_extra_attrs(self, cls):
+        """
+        Sub-classing (where the subclass has extra attrs) does what you'd hope
+        for.
+        """
+        obj = object()
+        i = cls(x=obj, y=2)
+        assert i.x is i.meth() is obj
+        assert i.y == 2
+        if cls is Sub:
+            assert "Sub(x={obj}, y=2)".format(obj=obj) == repr(i)
+        else:
+            assert "SubSlots(x={obj}, y=2)".format(obj=obj) == repr(i)
+
+    @pytest.mark.parametrize("base", [Super, SuperSlots])
+    def test_subclass_without_extra_attrs(self, base):
+        """
+        Sub-classing (where the subclass does not have extra attrs) still
+        behaves the same as a subclass with extra attrs.
+        """
+        class Sub2(base):
+            pass
+
+        obj = object()
+        i = Sub2(x=obj)
+        assert i.x is i.meth() is obj
+        assert "Sub2(x={obj})".format(obj=obj) == repr(i)
+
+    @pytest.mark.parametrize("frozen_class", [
+        Frozen,  # has slots=True
+        attr.make_class("FrozenToo", ["x"], slots=False, frozen=True),
+    ])
+    def test_frozen_instance(self, frozen_class):
+        """
+        Frozen instances can't be modified (easily).
+        """
+        frozen = frozen_class(1)
+
+        with pytest.raises(FrozenInstanceError) as e:
+            frozen.x = 2
+
+        with pytest.raises(FrozenInstanceError) as e:
+            del frozen.x
+
+        assert e.value.args[0] == "can't set attribute"
+        assert 1 == frozen.x
+
+    @pytest.mark.parametrize("cls",
+                             [C1, C1Slots, C2, C2Slots, Super, SuperSlots,
+                              Sub, SubSlots, Frozen, FrozenNoSlots,
+                              FromMakeClass])
+    @pytest.mark.parametrize("protocol",
+                             range(2, pickle.HIGHEST_PROTOCOL + 1))
+    def test_pickle_attributes(self, cls, protocol):
+        """
+        Pickling/un-pickling of Attribute instances works.
+        """
+        for attribute in attr.fields(cls):
+            assert attribute == pickle.loads(pickle.dumps(attribute, protocol))
+
+    @pytest.mark.parametrize("cls",
+                             [C1, C1Slots, C2, C2Slots, Super, SuperSlots,
+                              Sub, SubSlots, Frozen, FrozenNoSlots,
+                              FromMakeClass])
+    @pytest.mark.parametrize("protocol",
+                             range(2, pickle.HIGHEST_PROTOCOL + 1))
+    def test_pickle_object(self, cls, protocol):
+        """
+        Pickle object serialization works on all kinds of attrs classes.
+        """
+        if len(attr.fields(cls)) == 2:
+            obj = cls(123, 456)
+        else:
+            obj = cls(123)
+        assert repr(obj) == repr(pickle.loads(pickle.dumps(obj, protocol)))
+
+    def test_subclassing_frozen_gives_frozen(self):
+        """
+        The frozen-ness of classes is inherited.  Subclasses of frozen classes
+        are also frozen and can be instantiated.
+        """
+        i = SubFrozen("foo", "bar")
+
+        assert i.x == "foo"
+        assert i.y == "bar"
+
+    @pytest.mark.parametrize("cls", [WithMeta, WithMetaSlots])
+    def test_metaclass_preserved(self, cls):
+        """
+        Metaclass data is preserved.
+        """
+        assert Meta == type(cls)
+
+    def test_default_decorator(self):
+        """
+        Default decorator sets the default and the respective method gets
+        called.
+        """
+        @attr.s
+        class C(object):
+            x = attr.ib(default=1)
+            y = attr.ib()
+
+            @y.default
+            def compute(self):
+                return self.x + 1
+
+        assert C(1, 2) == C()
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_attrib_overwrite(self, slots, frozen):
+        """
+        Subclasses can overwrite attributes of their superclass.
+        """
+        @attr.s(slots=slots, frozen=frozen)
+        class SubOverwrite(Super):
+            x = attr.ib(default=attr.Factory(list))
+
+        assert SubOverwrite([]) == SubOverwrite()
+
+    def test_dict_patch_class(self):
+        """
+        dict-classes are never replaced.
+        """
+        class C(object):
+            x = attr.ib()
+
+        C_new = attr.s(C)
+
+        assert C_new is C
+
+    def test_hash_by_id(self):
+        """
+        With dict classes, hashing by ID is active for hash=False even on
+        Python 3.  This is incorrect behavior but we have to retain it for
+        backward compatibility.
+        """
+        @attr.s(hash=False)
+        class HashByIDBackwardCompat(object):
+            x = attr.ib()
+
+        assert (
+            hash(HashByIDBackwardCompat(1)) != hash(HashByIDBackwardCompat(1))
+        )
+
+        @attr.s(hash=False, cmp=False)
+        class HashByID(object):
+            x = attr.ib()
+
+        assert hash(HashByID(1)) != hash(HashByID(1))
+
+        @attr.s(hash=True)
+        class HashByValues(object):
+            x = attr.ib()
+
+        assert hash(HashByValues(1)) == hash(HashByValues(1))
+
+    def test_handles_different_defaults(self):
+        """
+        Unhashable defaults + subclassing values work.
+        """
+        @attr.s
+        class Unhashable(object):
+            pass
+
+        @attr.s
+        class C(object):
+            x = attr.ib(default=Unhashable())
+
+        @attr.s
+        class D(C):
+            pass
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_hash_false_cmp_false(self, slots):
+        """
+        hash=False and cmp=False make a class hashable by ID.
+        """
+        @attr.s(hash=False, cmp=False, slots=slots)
+        class C(object):
+            pass
+
+        assert hash(C()) != hash(C())
+
+    def test_overwrite_super(self):
+        """
+        Super classes can overwrite each other and the attributes are added
+        in the order they are defined.
+        """
+        @attr.s
+        class C(object):
+            c = attr.ib(default=100)
+            x = attr.ib(default=1)
+            b = attr.ib(default=23)
+
+        @attr.s
+        class D(C):
+            a = attr.ib(default=42)
+            x = attr.ib(default=2)
+            d = attr.ib(default=3.14)
+
+        @attr.s
+        class E(D):
+            y = attr.ib(default=3)
+            z = attr.ib(default=4)
+
+        assert "E(c=100, b=23, a=42, x=2, d=3.14, y=3, z=4)" == repr(E())
diff --git a/tools/third_party/attrs/tests/test_dunders.py b/tools/third_party/attrs/tests/test_dunders.py
new file mode 100644
index 0000000..951faba
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_dunders.py
@@ -0,0 +1,502 @@
+"""
+Tests for dunder methods from `attrib._make`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+import pytest
+
+from hypothesis import given
+from hypothesis.strategies import booleans
+
+import attr
+
+from attr._make import (
+    NOTHING, Factory, _add_init, _add_repr, _Nothing, fields, make_class
+)
+from attr.validators import instance_of
+
+from .utils import simple_attr, simple_class
+
+
+CmpC = simple_class(cmp=True)
+CmpCSlots = simple_class(cmp=True, slots=True)
+ReprC = simple_class(repr=True)
+ReprCSlots = simple_class(repr=True, slots=True)
+
+# HashC is hashable by explicit definition while HashCSlots is hashable
+# implicitly.
+HashC = simple_class(hash=True)
+HashCSlots = simple_class(hash=None, cmp=True, frozen=True, slots=True)
+
+
+class InitC(object):
+    __attrs_attrs__ = [simple_attr("a"), simple_attr("b")]
+
+
+InitC = _add_init(InitC, False)
+
+
+class TestAddCmp(object):
+    """
+    Tests for `_add_cmp`.
+    """
+    @given(booleans())
+    def test_cmp(self, slots):
+        """
+        If `cmp` is False, ignore that attribute.
+        """
+        C = make_class("C", {
+            "a": attr.ib(cmp=False),
+            "b": attr.ib()
+        }, slots=slots)
+
+        assert C(1, 2) == C(2, 2)
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_equal(self, cls):
+        """
+        Equal objects are detected as equal.
+        """
+        assert cls(1, 2) == cls(1, 2)
+        assert not (cls(1, 2) != cls(1, 2))
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_unequal_same_class(self, cls):
+        """
+        Unequal objects of correct type are detected as unequal.
+        """
+        assert cls(1, 2) != cls(2, 1)
+        assert not (cls(1, 2) == cls(2, 1))
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_unequal_different_class(self, cls):
+        """
+        Unequal objects of different type are detected even if their attributes
+        match.
+        """
+        class NotCmpC(object):
+            a = 1
+            b = 2
+        assert cls(1, 2) != NotCmpC()
+        assert not (cls(1, 2) == NotCmpC())
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_lt(self, cls):
+        """
+        __lt__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((1, 2),  (2, 1)),
+            ((1, 2),  (1, 3)),
+            (("a", "b"), ("b", "a")),
+        ]:
+            assert cls(*a) < cls(*b)
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_lt_unordable(self, cls):
+        """
+        __lt__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__lt__(42))
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_le(self, cls):
+        """
+        __le__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((1, 2),  (2, 1)),
+            ((1, 2),  (1, 3)),
+            ((1, 1),  (1, 1)),
+            (("a", "b"), ("b", "a")),
+            (("a", "b"), ("a", "b")),
+        ]:
+            assert cls(*a) <= cls(*b)
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_le_unordable(self, cls):
+        """
+        __le__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__le__(42))
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_gt(self, cls):
+        """
+        __gt__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((2, 1), (1, 2)),
+            ((1, 3), (1, 2)),
+            (("b", "a"), ("a", "b")),
+        ]:
+            assert cls(*a) > cls(*b)
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_gt_unordable(self, cls):
+        """
+        __gt__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__gt__(42))
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_ge(self, cls):
+        """
+        __ge__ compares objects as tuples of attribute values.
+        """
+        for a, b in [
+            ((2, 1), (1, 2)),
+            ((1, 3), (1, 2)),
+            ((1, 1), (1, 1)),
+            (("b", "a"), ("a", "b")),
+            (("a", "b"), ("a", "b")),
+        ]:
+            assert cls(*a) >= cls(*b)
+
+    @pytest.mark.parametrize("cls", [CmpC, CmpCSlots])
+    def test_ge_unordable(self, cls):
+        """
+        __ge__ returns NotImplemented if classes differ.
+        """
+        assert NotImplemented == (cls(1, 2).__ge__(42))
+
+
+class TestAddRepr(object):
+    """
+    Tests for `_add_repr`.
+    """
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_repr(self, slots):
+        """
+        If `repr` is False, ignore that attribute.
+        """
+        C = make_class("C", {
+            "a": attr.ib(repr=False),
+            "b": attr.ib()
+        }, slots=slots)
+
+        assert "C(b=2)" == repr(C(1, 2))
+
+    @pytest.mark.parametrize("cls", [ReprC, ReprCSlots])
+    def test_repr_works(self, cls):
+        """
+        repr returns a sensible value.
+        """
+        assert "C(a=1, b=2)" == repr(cls(1, 2))
+
+    def test_underscores(self):
+        """
+        repr does not strip underscores.
+        """
+        class C(object):
+            __attrs_attrs__ = [simple_attr("_x")]
+
+        C = _add_repr(C)
+        i = C()
+        i._x = 42
+
+        assert "C(_x=42)" == repr(i)
+
+    @given(add_str=booleans(), slots=booleans())
+    def test_str(self, add_str, slots):
+        """
+        If str is True, it returns the same as repr.
+
+        This only makes sense when subclassing a class with an poor __str__
+        (like Exceptions).
+        """
+        @attr.s(str=add_str, slots=slots)
+        class Error(Exception):
+            x = attr.ib()
+
+        e = Error(42)
+
+        assert (str(e) == repr(e)) is add_str
+
+    def test_str_no_repr(self):
+        """
+        Raises a ValueError if repr=False and str=True.
+        """
+        with pytest.raises(ValueError) as e:
+            simple_class(repr=False, str=True)
+
+        assert (
+            "__str__ can only be generated if a __repr__ exists."
+        ) == e.value.args[0]
+
+
+class TestAddHash(object):
+    """
+    Tests for `_add_hash`.
+    """
+    def test_enforces_type(self):
+        """
+        The `hash` argument to both attrs and attrib must be None, True, or
+        False.
+        """
+        exc_args = ("Invalid value for hash.  Must be True, False, or None.",)
+
+        with pytest.raises(TypeError) as e:
+            make_class("C", {}, hash=1),
+
+        assert exc_args == e.value.args
+
+        with pytest.raises(TypeError) as e:
+            make_class("C", {"a": attr.ib(hash=1)}),
+
+        assert exc_args == e.value.args
+
+    @given(booleans())
+    def test_hash_attribute(self, slots):
+        """
+        If `hash` is False on an attribute, ignore that attribute.
+        """
+        C = make_class("C", {"a": attr.ib(hash=False), "b": attr.ib()},
+                       slots=slots, hash=True)
+
+        assert hash(C(1, 2)) == hash(C(2, 2))
+
+    @given(booleans())
+    def test_hash_attribute_mirrors_cmp(self, cmp):
+        """
+        If `hash` is None, the hash generation mirrors `cmp`.
+        """
+        C = make_class("C", {"a": attr.ib(cmp=cmp)}, cmp=True, frozen=True)
+
+        if cmp:
+            assert C(1) != C(2)
+            assert hash(C(1)) != hash(C(2))
+            assert hash(C(1)) == hash(C(1))
+        else:
+            assert C(1) == C(2)
+            assert hash(C(1)) == hash(C(2))
+
+    @given(booleans())
+    def test_hash_mirrors_cmp(self, cmp):
+        """
+        If `hash` is None, the hash generation mirrors `cmp`.
+        """
+        C = make_class("C", {"a": attr.ib()}, cmp=cmp, frozen=True)
+
+        i = C(1)
+
+        assert i == i
+        assert hash(i) == hash(i)
+
+        if cmp:
+            assert C(1) == C(1)
+            assert hash(C(1)) == hash(C(1))
+        else:
+            assert C(1) != C(1)
+            assert hash(C(1)) != hash(C(1))
+
+    @pytest.mark.parametrize("cls", [HashC, HashCSlots])
+    def test_hash_works(self, cls):
+        """
+        __hash__ returns different hashes for different values.
+        """
+        assert hash(cls(1, 2)) != hash(cls(1, 1))
+
+    def test_hash_default(self):
+        """
+        Classes are not hashable by default.
+        """
+        C = make_class("C", {})
+
+        with pytest.raises(TypeError) as e:
+            hash(C())
+
+        assert e.value.args[0] in (
+            "'C' objects are unhashable",  # PyPy
+            "unhashable type: 'C'",  # CPython
+        )
+
+
+class TestAddInit(object):
+    """
+    Tests for `_add_init`.
+    """
+    @given(booleans(), booleans())
+    def test_init(self, slots, frozen):
+        """
+        If `init` is False, ignore that attribute.
+        """
+        C = make_class("C", {"a": attr.ib(init=False), "b": attr.ib()},
+                       slots=slots, frozen=frozen)
+        with pytest.raises(TypeError) as e:
+            C(a=1, b=2)
+
+        assert (
+            "__init__() got an unexpected keyword argument 'a'" ==
+            e.value.args[0]
+        )
+
+    @given(booleans(), booleans())
+    def test_no_init_default(self, slots, frozen):
+        """
+        If `init` is False but a Factory is specified, don't allow passing that
+        argument but initialize it anyway.
+        """
+        C = make_class("C", {
+            "_a": attr.ib(init=False, default=42),
+            "_b": attr.ib(init=False, default=Factory(list)),
+            "c": attr.ib()
+        }, slots=slots, frozen=frozen)
+        with pytest.raises(TypeError):
+            C(a=1, c=2)
+        with pytest.raises(TypeError):
+            C(b=1, c=2)
+
+        i = C(23)
+        assert (42, [], 23) == (i._a, i._b, i.c)
+
+    @given(booleans(), booleans())
+    def test_no_init_order(self, slots, frozen):
+        """
+        If an attribute is `init=False`, it's legal to come after a mandatory
+        attribute.
+        """
+        make_class("C", {
+            "a": attr.ib(default=Factory(list)),
+            "b": attr.ib(init=False),
+        }, slots=slots, frozen=frozen)
+
+    def test_sets_attributes(self):
+        """
+        The attributes are initialized using the passed keywords.
+        """
+        obj = InitC(a=1, b=2)
+        assert 1 == obj.a
+        assert 2 == obj.b
+
+    def test_default(self):
+        """
+        If a default value is present, it's used as fallback.
+        """
+        class C(object):
+            __attrs_attrs__ = [
+                simple_attr(name="a", default=2),
+                simple_attr(name="b", default="hallo"),
+                simple_attr(name="c", default=None),
+            ]
+
+        C = _add_init(C, False)
+        i = C()
+        assert 2 == i.a
+        assert "hallo" == i.b
+        assert None is i.c
+
+    def test_factory(self):
+        """
+        If a default factory is present, it's used as fallback.
+        """
+        class D(object):
+            pass
+
+        class C(object):
+            __attrs_attrs__ = [
+                simple_attr(name="a", default=Factory(list)),
+                simple_attr(name="b", default=Factory(D)),
+            ]
+        C = _add_init(C, False)
+        i = C()
+
+        assert [] == i.a
+        assert isinstance(i.b, D)
+
+    def test_validator(self):
+        """
+        If a validator is passed, call it with the preliminary instance, the
+        Attribute, and the argument.
+        """
+        class VException(Exception):
+            pass
+
+        def raiser(*args):
+            raise VException(*args)
+
+        C = make_class("C", {"a": attr.ib("a", validator=raiser)})
+        with pytest.raises(VException) as e:
+            C(42)
+
+        assert (fields(C).a, 42,) == e.value.args[1:]
+        assert isinstance(e.value.args[0], C)
+
+    def test_validator_slots(self):
+        """
+        If a validator is passed, call it with the preliminary instance, the
+        Attribute, and the argument.
+        """
+        class VException(Exception):
+            pass
+
+        def raiser(*args):
+            raise VException(*args)
+
+        C = make_class("C", {"a": attr.ib("a", validator=raiser)}, slots=True)
+        with pytest.raises(VException) as e:
+            C(42)
+
+        assert (fields(C)[0], 42,) == e.value.args[1:]
+        assert isinstance(e.value.args[0], C)
+
+    @given(booleans())
+    def test_validator_others(self, slots):
+        """
+        Does not interfere when setting non-attrs attributes.
+        """
+        C = make_class("C", {
+            "a": attr.ib("a", validator=instance_of(int))
+        }, slots=slots)
+        i = C(1)
+
+        assert 1 == i.a
+
+        if not slots:
+            i.b = "foo"
+            assert "foo" == i.b
+        else:
+            with pytest.raises(AttributeError):
+                i.b = "foo"
+
+    def test_underscores(self):
+        """
+        The argument names in `__init__` are without leading and trailing
+        underscores.
+        """
+        class C(object):
+            __attrs_attrs__ = [simple_attr("_private")]
+
+        C = _add_init(C, False)
+        i = C(private=42)
+        assert 42 == i._private
+
+
+class TestNothing(object):
+    """
+    Tests for `_Nothing`.
+    """
+    def test_copy(self):
+        """
+        __copy__ returns the same object.
+        """
+        n = _Nothing()
+        assert n is copy.copy(n)
+
+    def test_deepcopy(self):
+        """
+        __deepcopy__ returns the same object.
+        """
+        n = _Nothing()
+        assert n is copy.deepcopy(n)
+
+    def test_eq(self):
+        """
+        All instances are equal.
+        """
+        assert _Nothing() == _Nothing() == NOTHING
+        assert not (_Nothing() != _Nothing())
+        assert 1 != _Nothing()
diff --git a/tools/third_party/attrs/tests/test_filters.py b/tools/third_party/attrs/tests/test_filters.py
new file mode 100644
index 0000000..8f7dd69
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_filters.py
@@ -0,0 +1,94 @@
+"""
+Tests for `attr.filters`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+import attr
+
+from attr import fields
+from attr.filters import _split_what, exclude, include
+
+
+@attr.s
+class C(object):
+    a = attr.ib()
+    b = attr.ib()
+
+
+class TestSplitWhat(object):
+    """
+    Tests for `_split_what`.
+    """
+    def test_splits(self):
+        """
+        Splits correctly.
+        """
+        assert (
+            frozenset((int, str)),
+            frozenset((fields(C).a,)),
+        ) == _split_what((str, fields(C).a, int,))
+
+
+class TestInclude(object):
+    """
+    Tests for `include`.
+    """
+    @pytest.mark.parametrize("incl,value", [
+        ((int,), 42),
+        ((str,), "hello"),
+        ((str, fields(C).a), 42),
+        ((str, fields(C).b), "hello"),
+    ])
+    def test_allow(self, incl, value):
+        """
+        Return True if a class or attribute is whitelisted.
+        """
+        i = include(*incl)
+        assert i(fields(C).a, value) is True
+
+    @pytest.mark.parametrize("incl,value", [
+        ((str,), 42),
+        ((int,), "hello"),
+        ((str, fields(C).b), 42),
+        ((int, fields(C).b), "hello"),
+    ])
+    def test_drop_class(self, incl, value):
+        """
+        Return False on non-whitelisted classes and attributes.
+        """
+        i = include(*incl)
+        assert i(fields(C).a, value) is False
+
+
+class TestExclude(object):
+    """
+    Tests for `exclude`.
+    """
+    @pytest.mark.parametrize("excl,value", [
+        ((str,), 42),
+        ((int,), "hello"),
+        ((str, fields(C).b), 42),
+        ((int, fields(C).b), "hello"),
+    ])
+    def test_allow(self, excl, value):
+        """
+        Return True if class or attribute is not blacklisted.
+        """
+        e = exclude(*excl)
+        assert e(fields(C).a, value) is True
+
+    @pytest.mark.parametrize("excl,value", [
+        ((int,), 42),
+        ((str,), "hello"),
+        ((str, fields(C).a), 42),
+        ((str, fields(C).b), "hello"),
+    ])
+    def test_drop_class(self, excl, value):
+        """
+        Return True on non-blacklisted classes and attributes.
+        """
+        e = exclude(*excl)
+        assert e(fields(C).a, value) is False
diff --git a/tools/third_party/attrs/tests/test_funcs.py b/tools/third_party/attrs/tests/test_funcs.py
new file mode 100644
index 0000000..0167823
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_funcs.py
@@ -0,0 +1,527 @@
+"""
+Tests for `attr._funcs`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from collections import Mapping, OrderedDict, Sequence
+
+import pytest
+
+from hypothesis import strategies as st
+from hypothesis import HealthCheck, assume, given, settings
+
+import attr
+
+from attr import asdict, assoc, astuple, evolve, fields, has
+from attr._compat import TYPE
+from attr.exceptions import AttrsAttributeNotFoundError
+from attr.validators import instance_of
+
+from .utils import nested_classes, simple_classes
+
+
+MAPPING_TYPES = (dict, OrderedDict)
+SEQUENCE_TYPES = (list, tuple)
+
+
+class TestAsDict(object):
+    """
+    Tests for `asdict`.
+    """
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_shallow(self, C, dict_factory):
+        """
+        Shallow asdict returns correct dict.
+        """
+        assert {
+            "x": 1,
+            "y": 2,
+        } == asdict(C(x=1, y=2), False, dict_factory=dict_factory)
+
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_recurse(self, C, dict_class):
+        """
+        Deep asdict returns correct dict.
+        """
+        assert {
+            "x": {"x": 1, "y": 2},
+            "y": {"x": 3, "y": 4},
+        } == asdict(C(
+            C(1, 2),
+            C(3, 4),
+        ), dict_factory=dict_class)
+
+    @given(nested_classes, st.sampled_from(MAPPING_TYPES))
+    @settings(suppress_health_check=[HealthCheck.too_slow])
+    def test_recurse_property(self, cls, dict_class):
+        """
+        Property tests for recursive asdict.
+        """
+        obj = cls()
+        obj_dict = asdict(obj, dict_factory=dict_class)
+
+        def assert_proper_dict_class(obj, obj_dict):
+            assert isinstance(obj_dict, dict_class)
+
+            for field in fields(obj.__class__):
+                field_val = getattr(obj, field.name)
+                if has(field_val.__class__):
+                    # This field holds a class, recurse the assertions.
+                    assert_proper_dict_class(field_val, obj_dict[field.name])
+                elif isinstance(field_val, Sequence):
+                    dict_val = obj_dict[field.name]
+                    for item, item_dict in zip(field_val, dict_val):
+                        if has(item.__class__):
+                            assert_proper_dict_class(item, item_dict)
+                elif isinstance(field_val, Mapping):
+                    # This field holds a dictionary.
+                    assert isinstance(obj_dict[field.name], dict_class)
+
+                    for key, val in field_val.items():
+                        if has(val.__class__):
+                            assert_proper_dict_class(val,
+                                                     obj_dict[field.name][key])
+
+        assert_proper_dict_class(obj, obj_dict)
+
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_filter(self, C, dict_factory):
+        """
+        Attributes that are supposed to be skipped are skipped.
+        """
+        assert {
+            "x": {"x": 1},
+        } == asdict(C(
+            C(1, 2),
+            C(3, 4),
+        ), filter=lambda a, v: a.name != "y", dict_factory=dict_factory)
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples(self, container, C):
+        """
+        If recurse is True, also recurse into lists.
+        """
+        assert {
+            "x": 1,
+            "y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"],
+        } == asdict(C(1, container([C(2, 3), C(4, 5), "a"])))
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples_retain_type(self, container, C):
+        """
+        If recurse and retain_collection_types are True, also recurse
+        into lists and do not convert them into list.
+        """
+        assert {
+            "x": 1,
+            "y": container([{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"]),
+        } == asdict(C(1, container([C(2, 3), C(4, 5), "a"])),
+                    retain_collection_types=True)
+
+    @given(st.sampled_from(MAPPING_TYPES))
+    def test_dicts(self, C, dict_factory):
+        """
+        If recurse is True, also recurse into dicts.
+        """
+        res = asdict(C(1, {"a": C(4, 5)}), dict_factory=dict_factory)
+        assert {
+            "x": 1,
+            "y": {"a": {"x": 4, "y": 5}},
+        } == res
+        assert isinstance(res, dict_factory)
+
+    @given(simple_classes(private_attrs=False), st.sampled_from(MAPPING_TYPES))
+    def test_roundtrip(self, cls, dict_class):
+        """
+        Test dumping to dicts and back for Hypothesis-generated classes.
+
+        Private attributes don't round-trip (the attribute name is different
+        than the initializer argument).
+        """
+        instance = cls()
+        dict_instance = asdict(instance, dict_factory=dict_class)
+
+        assert isinstance(dict_instance, dict_class)
+
+        roundtrip_instance = cls(**dict_instance)
+
+        assert instance == roundtrip_instance
+
+    @given(simple_classes())
+    def test_asdict_preserve_order(self, cls):
+        """
+        Field order should be preserved when dumping to OrderedDicts.
+        """
+        instance = cls()
+        dict_instance = asdict(instance, dict_factory=OrderedDict)
+
+        assert [a.name for a in fields(cls)] == list(dict_instance.keys())
+
+
+class TestAsTuple(object):
+    """
+    Tests for `astuple`.
+    """
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_shallow(self, C, tuple_factory):
+        """
+        Shallow astuple returns correct dict.
+        """
+        assert (tuple_factory([1, 2]) ==
+                astuple(C(x=1, y=2), False, tuple_factory=tuple_factory))
+
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_recurse(self, C, tuple_factory):
+        """
+        Deep astuple returns correct tuple.
+        """
+        assert (tuple_factory([tuple_factory([1, 2]),
+                              tuple_factory([3, 4])])
+                == astuple(C(
+                            C(1, 2),
+                            C(3, 4),
+                            ),
+                           tuple_factory=tuple_factory))
+
+    @given(nested_classes, st.sampled_from(SEQUENCE_TYPES))
+    @settings(suppress_health_check=[HealthCheck.too_slow])
+    def test_recurse_property(self, cls, tuple_class):
+        """
+        Property tests for recursive astuple.
+        """
+        obj = cls()
+        obj_tuple = astuple(obj, tuple_factory=tuple_class)
+
+        def assert_proper_tuple_class(obj, obj_tuple):
+            assert isinstance(obj_tuple, tuple_class)
+            for index, field in enumerate(fields(obj.__class__)):
+                field_val = getattr(obj, field.name)
+                if has(field_val.__class__):
+                    # This field holds a class, recurse the assertions.
+                    assert_proper_tuple_class(field_val, obj_tuple[index])
+
+        assert_proper_tuple_class(obj, obj_tuple)
+
+    @given(nested_classes, st.sampled_from(SEQUENCE_TYPES))
+    @settings(suppress_health_check=[HealthCheck.too_slow])
+    def test_recurse_retain(self, cls, tuple_class):
+        """
+        Property tests for asserting collection types are retained.
+        """
+        obj = cls()
+        obj_tuple = astuple(obj, tuple_factory=tuple_class,
+                            retain_collection_types=True)
+
+        def assert_proper_col_class(obj, obj_tuple):
+            # Iterate over all attributes, and if they are lists or mappings
+            # in the original, assert they are the same class in the dumped.
+            for index, field in enumerate(fields(obj.__class__)):
+                field_val = getattr(obj, field.name)
+                if has(field_val.__class__):
+                    # This field holds a class, recurse the assertions.
+                    assert_proper_col_class(field_val, obj_tuple[index])
+                elif isinstance(field_val, (list, tuple)):
+                    # This field holds a sequence of something.
+                    expected_type = type(obj_tuple[index])
+                    assert type(field_val) is expected_type  # noqa: E721
+                    for obj_e, obj_tuple_e in zip(field_val, obj_tuple[index]):
+                        if has(obj_e.__class__):
+                            assert_proper_col_class(obj_e, obj_tuple_e)
+                elif isinstance(field_val, dict):
+                    orig = field_val
+                    tupled = obj_tuple[index]
+                    assert type(orig) is type(tupled)  # noqa: E721
+                    for obj_e, obj_tuple_e in zip(orig.items(),
+                                                  tupled.items()):
+                        if has(obj_e[0].__class__):  # Dict key
+                            assert_proper_col_class(obj_e[0], obj_tuple_e[0])
+                        if has(obj_e[1].__class__):  # Dict value
+                            assert_proper_col_class(obj_e[1], obj_tuple_e[1])
+
+        assert_proper_col_class(obj, obj_tuple)
+
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_filter(self, C, tuple_factory):
+        """
+        Attributes that are supposed to be skipped are skipped.
+        """
+        assert tuple_factory([tuple_factory([1, ]), ]) == astuple(C(
+            C(1, 2),
+            C(3, 4),
+        ), filter=lambda a, v: a.name != "y", tuple_factory=tuple_factory)
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples(self, container, C):
+        """
+        If recurse is True, also recurse into lists.
+        """
+        assert ((1, [(2, 3), (4, 5), "a"])
+                == astuple(C(1, container([C(2, 3), C(4, 5), "a"])))
+                )
+
+    @given(st.sampled_from(SEQUENCE_TYPES))
+    def test_dicts(self, C, tuple_factory):
+        """
+        If recurse is True, also recurse into dicts.
+        """
+        res = astuple(C(1, {"a": C(4, 5)}), tuple_factory=tuple_factory)
+        assert tuple_factory([1, {"a": tuple_factory([4, 5])}]) == res
+        assert isinstance(res, tuple_factory)
+
+    @given(container=st.sampled_from(SEQUENCE_TYPES))
+    def test_lists_tuples_retain_type(self, container, C):
+        """
+        If recurse and retain_collection_types are True, also recurse
+        into lists and do not convert them into list.
+        """
+        assert (
+            (1, container([(2, 3), (4, 5), "a"]))
+            == astuple(C(1, container([C(2, 3), C(4, 5), "a"])),
+                       retain_collection_types=True))
+
+    @given(container=st.sampled_from(MAPPING_TYPES))
+    def test_dicts_retain_type(self, container, C):
+        """
+        If recurse and retain_collection_types are True, also recurse
+        into lists and do not convert them into list.
+        """
+        assert (
+            (1, container({"a": (4, 5)}))
+            == astuple(C(1, container({"a": C(4, 5)})),
+                       retain_collection_types=True))
+
+    @given(simple_classes(), st.sampled_from(SEQUENCE_TYPES))
+    def test_roundtrip(self, cls, tuple_class):
+        """
+        Test dumping to tuple and back for Hypothesis-generated classes.
+        """
+        instance = cls()
+        tuple_instance = astuple(instance, tuple_factory=tuple_class)
+
+        assert isinstance(tuple_instance, tuple_class)
+
+        roundtrip_instance = cls(*tuple_instance)
+
+        assert instance == roundtrip_instance
+
+
+class TestHas(object):
+    """
+    Tests for `has`.
+    """
+    def test_positive(self, C):
+        """
+        Returns `True` on decorated classes.
+        """
+        assert has(C)
+
+    def test_positive_empty(self):
+        """
+        Returns `True` on decorated classes even if there are no attributes.
+        """
+        @attr.s
+        class D(object):
+            pass
+
+        assert has(D)
+
+    def test_negative(self):
+        """
+        Returns `False` on non-decorated classes.
+        """
+        assert not has(object)
+
+
+class TestAssoc(object):
+    """
+    Tests for `assoc`.
+    """
+    @given(slots=st.booleans(), frozen=st.booleans())
+    def test_empty(self, slots, frozen):
+        """
+        Empty classes without changes get copied.
+        """
+        @attr.s(slots=slots, frozen=frozen)
+        class C(object):
+            pass
+
+        i1 = C()
+        with pytest.deprecated_call():
+            i2 = assoc(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes())
+    def test_no_changes(self, C):
+        """
+        No changes means a verbatim copy.
+        """
+        i1 = C()
+        with pytest.deprecated_call():
+            i2 = assoc(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes(), st.data())
+    def test_change(self, C, data):
+        """
+        Changes work.
+        """
+        # Take the first attribute, and change it.
+        assume(fields(C))  # Skip classes with no attributes.
+        field_names = [a.name for a in fields(C)]
+        original = C()
+        chosen_names = data.draw(st.sets(st.sampled_from(field_names)))
+        change_dict = {name: data.draw(st.integers())
+                       for name in chosen_names}
+
+        with pytest.deprecated_call():
+            changed = assoc(original, **change_dict)
+
+        for k, v in change_dict.items():
+            assert getattr(changed, k) == v
+
+    @given(simple_classes())
+    def test_unknown(self, C):
+        """
+        Wanting to change an unknown attribute raises an
+        AttrsAttributeNotFoundError.
+        """
+        # No generated class will have a four letter attribute.
+        with pytest.raises(AttrsAttributeNotFoundError) as e, \
+                pytest.deprecated_call():
+            assoc(C(), aaaa=2)
+
+        assert (
+            "aaaa is not an attrs attribute on {cls!r}.".format(cls=C),
+        ) == e.value.args
+
+    def test_frozen(self):
+        """
+        Works on frozen classes.
+        """
+        @attr.s(frozen=True)
+        class C(object):
+            x = attr.ib()
+            y = attr.ib()
+
+        with pytest.deprecated_call():
+            assert C(3, 2) == assoc(C(1, 2), x=3)
+
+    def test_warning(self):
+        """
+        DeprecationWarning points to the correct file.
+        """
+        @attr.s
+        class C(object):
+            x = attr.ib()
+
+        with pytest.warns(DeprecationWarning) as wi:
+            assert C(2) == assoc(C(1), x=2)
+
+        assert __file__ == wi.list[0].filename
+
+
+class TestEvolve(object):
+    """
+    Tests for `evolve`.
+    """
+    @given(slots=st.booleans(), frozen=st.booleans())
+    def test_empty(self, slots, frozen):
+        """
+        Empty classes without changes get copied.
+        """
+        @attr.s(slots=slots, frozen=frozen)
+        class C(object):
+            pass
+
+        i1 = C()
+        i2 = evolve(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes())
+    def test_no_changes(self, C):
+        """
+        No changes means a verbatim copy.
+        """
+        i1 = C()
+        i2 = evolve(i1)
+
+        assert i1 is not i2
+        assert i1 == i2
+
+    @given(simple_classes(), st.data())
+    def test_change(self, C, data):
+        """
+        Changes work.
+        """
+        # Take the first attribute, and change it.
+        assume(fields(C))  # Skip classes with no attributes.
+        field_names = [a.name for a in fields(C)]
+        original = C()
+        chosen_names = data.draw(st.sets(st.sampled_from(field_names)))
+        # We pay special attention to private attributes, they should behave
+        # like in `__init__`.
+        change_dict = {name.replace('_', ''): data.draw(st.integers())
+                       for name in chosen_names}
+        changed = evolve(original, **change_dict)
+        for name in chosen_names:
+            assert getattr(changed, name) == change_dict[name.replace('_', '')]
+
+    @given(simple_classes())
+    def test_unknown(self, C):
+        """
+        Wanting to change an unknown attribute raises an
+        AttrsAttributeNotFoundError.
+        """
+        # No generated class will have a four letter attribute.
+        with pytest.raises(TypeError) as e:
+            evolve(C(), aaaa=2)
+        expected = "__init__() got an unexpected keyword argument 'aaaa'"
+        assert (expected,) == e.value.args
+
+    def test_validator_failure(self):
+        """
+        TypeError isn't swallowed when validation fails within evolve.
+        """
+        @attr.s
+        class C(object):
+            a = attr.ib(validator=instance_of(int))
+
+        with pytest.raises(TypeError) as e:
+            evolve(C(a=1), a="some string")
+        m = e.value.args[0]
+
+        assert m.startswith("'a' must be <{type} 'int'>".format(type=TYPE))
+
+    def test_private(self):
+        """
+        evolve() acts as `__init__` with regards to private attributes.
+        """
+        @attr.s
+        class C(object):
+            _a = attr.ib()
+
+        assert evolve(C(1), a=2)._a == 2
+
+        with pytest.raises(TypeError):
+            evolve(C(1), _a=2)
+
+        with pytest.raises(TypeError):
+            evolve(C(1), a=3, _a=2)
+
+    def test_non_init_attrs(self):
+        """
+        evolve() handles `init=False` attributes.
+        """
+        @attr.s
+        class C(object):
+            a = attr.ib()
+            b = attr.ib(init=False, default=0)
+
+        assert evolve(C(1), a=2).a == 2
diff --git a/tools/third_party/attrs/tests/test_init_subclass.py b/tools/third_party/attrs/tests/test_init_subclass.py
new file mode 100644
index 0000000..b66130e
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_init_subclass.py
@@ -0,0 +1,44 @@
+"""
+Tests for `__init_subclass__` related tests.
+
+Python 3.6+ only.
+"""
+
+import pytest
+
+import attr
+
+
+@pytest.mark.parametrize("slots", [True, False])
+def test_init_subclass_vanilla(slots):
+    """
+    `super().__init_subclass__` can be used if the subclass is not an attrs
+    class both with dict and slots classes.
+    """
+    @attr.s(slots=slots)
+    class Base:
+        def __init_subclass__(cls, param, **kw):
+            super().__init_subclass__(**kw)
+            cls.param = param
+
+    class Vanilla(Base, param="foo"):
+        pass
+
+    assert "foo" == Vanilla().param
+
+
+def test_init_subclass_attrs():
+    """
+    `__init_subclass__` works with attrs classes as long as slots=False.
+    """
+    @attr.s(slots=False)
+    class Base:
+        def __init_subclass__(cls, param, **kw):
+            super().__init_subclass__(**kw)
+            cls.param = param
+
+    @attr.s
+    class Attrs(Base, param="foo"):
+        pass
+
+    assert "foo" == Attrs().param
diff --git a/tools/third_party/attrs/tests/test_make.py b/tools/third_party/attrs/tests/test_make.py
new file mode 100644
index 0000000..f8c8022
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_make.py
@@ -0,0 +1,866 @@
+"""
+Tests for `attr._make`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import inspect
+import sys
+
+from operator import attrgetter
+
+import pytest
+
+from hypothesis import given
+from hypothesis.strategies import booleans, integers, lists, sampled_from, text
+
+import attr
+
+from attr import _config
+from attr._compat import PY2
+from attr._make import (
+    Attribute, Factory, _AndValidator, _Attributes, _ClassBuilder,
+    _CountingAttr, _transform_attrs, and_, fields, make_class, validate
+)
+from attr.exceptions import DefaultAlreadySetError, NotAnAttrsClassError
+
+from .utils import (
+    gen_attr_names, list_of_attrs, simple_attr, simple_attrs,
+    simple_attrs_without_metadata, simple_classes
+)
+
+
+attrs_st = simple_attrs.map(lambda c: Attribute.from_counting_attr("name", c))
+
+
+class TestCountingAttr(object):
+    """
+    Tests for `attr`.
+    """
+    def test_returns_Attr(self):
+        """
+        Returns an instance of _CountingAttr.
+        """
+        a = attr.ib()
+
+        assert isinstance(a, _CountingAttr)
+
+    def test_validators_lists_to_wrapped_tuples(self):
+        """
+        If a list is passed as validator, it's just converted to a tuple.
+        """
+        def v1(_, __):
+            pass
+
+        def v2(_, __):
+            pass
+
+        a = attr.ib(validator=[v1, v2])
+
+        assert _AndValidator((v1, v2,)) == a._validator
+
+    def test_validator_decorator_single(self):
+        """
+        If _CountingAttr.validator is used as a decorator and there is no
+        decorator set, the decorated method is used as the validator.
+        """
+        a = attr.ib()
+
+        @a.validator
+        def v():
+            pass
+
+        assert v == a._validator
+
+    @pytest.mark.parametrize("wrap", [
+        lambda v: v,
+        lambda v: [v],
+        lambda v: and_(v)
+
+    ])
+    def test_validator_decorator(self, wrap):
+        """
+        If _CountingAttr.validator is used as a decorator and there is already
+        a decorator set, the decorators are composed using `and_`.
+        """
+        def v(_, __):
+            pass
+
+        a = attr.ib(validator=wrap(v))
+
+        @a.validator
+        def v2(self, _, __):
+            pass
+
+        assert _AndValidator((v, v2,)) == a._validator
+
+    def test_default_decorator_already_set(self):
+        """
+        Raise DefaultAlreadySetError if the decorator is used after a default
+        has been set.
+        """
+        a = attr.ib(default=42)
+
+        with pytest.raises(DefaultAlreadySetError):
+            @a.default
+            def f(self):
+                pass
+
+    def test_default_decorator_sets(self):
+        """
+        Decorator wraps the method in a Factory with pass_self=True and sets
+        the default.
+        """
+        a = attr.ib()
+
+        @a.default
+        def f(self):
+            pass
+
+        assert Factory(f, True) == a._default
+
+
+def make_tc():
+    class TransformC(object):
+        z = attr.ib()
+        y = attr.ib()
+        x = attr.ib()
+        a = 42
+    return TransformC
+
+
+class TestTransformAttrs(object):
+    """
+    Tests for `_transform_attrs`.
+    """
+    def test_no_modifications(self):
+        """
+        Doesn't attach __attrs_attrs__ to the class anymore.
+        """
+        C = make_tc()
+        _transform_attrs(C, None, False)
+
+        assert None is getattr(C, "__attrs_attrs__", None)
+
+    def test_normal(self):
+        """
+        Transforms every `_CountingAttr` and leaves others (a) be.
+        """
+        C = make_tc()
+        attrs, _, = _transform_attrs(C, None, False)
+
+        assert ["z", "y", "x"] == [a.name for a in attrs]
+
+    def test_empty(self):
+        """
+        No attributes works as expected.
+        """
+        @attr.s
+        class C(object):
+            pass
+
+        assert _Attributes(((), [])) == _transform_attrs(C, None, False)
+
+    def test_transforms_to_attribute(self):
+        """
+        All `_CountingAttr`s are transformed into `Attribute`s.
+        """
+        C = make_tc()
+        attrs, super_attrs = _transform_attrs(C, None, False)
+
+        assert [] == super_attrs
+        assert 3 == len(attrs)
+        assert all(isinstance(a, Attribute) for a in attrs)
+
+    def test_conflicting_defaults(self):
+        """
+        Raises `ValueError` if attributes with defaults are followed by
+        mandatory attributes.
+        """
+        class C(object):
+            x = attr.ib(default=None)
+            y = attr.ib()
+
+        with pytest.raises(ValueError) as e:
+            _transform_attrs(C, None, False)
+        assert (
+            "No mandatory attributes allowed after an attribute with a "
+            "default value or factory.  Attribute in question: Attribute"
+            "(name='y', default=NOTHING, validator=None, repr=True, "
+            "cmp=True, hash=None, init=True, convert=None, "
+            "metadata=mappingproxy({}), type=None)",
+        ) == e.value.args
+
+    def test_these(self):
+        """
+        If these is passed, use it and ignore body and super classes.
+        """
+        class Base(object):
+            z = attr.ib()
+
+        class C(Base):
+            y = attr.ib()
+
+        attrs, super_attrs = _transform_attrs(C, {"x": attr.ib()}, False)
+
+        assert [] == super_attrs
+        assert (
+            simple_attr("x"),
+        ) == attrs
+
+    def test_multiple_inheritance(self):
+        """
+        Order of attributes doesn't get mixed up by multiple inheritance.
+
+        See #285
+        """
+        @attr.s
+        class A(object):
+            a1 = attr.ib(default="a1")
+            a2 = attr.ib(default="a2")
+
+        @attr.s
+        class B(A):
+            b1 = attr.ib(default="b1")
+            b2 = attr.ib(default="b2")
+
+        @attr.s
+        class C(B, A):
+            c1 = attr.ib(default="c1")
+            c2 = attr.ib(default="c2")
+
+        @attr.s
+        class D(A):
+            d1 = attr.ib(default="d1")
+            d2 = attr.ib(default="d2")
+
+        @attr.s
+        class E(D, C):
+            e1 = attr.ib(default="e1")
+            e2 = attr.ib(default="e2")
+
+        assert (
+            "E(a1='a1', a2='a2', b1='b1', b2='b2', c1='c1', c2='c2', d1='d1', "
+            "d2='d2', e1='e1', e2='e2')"
+        ) == repr(E())
+
+
+class TestAttributes(object):
+    """
+    Tests for the `attrs`/`attr.s` class decorator.
+    """
+    @pytest.mark.skipif(not PY2, reason="No old-style classes in Py3")
+    def test_catches_old_style(self):
+        """
+        Raises TypeError on old-style classes.
+        """
+        with pytest.raises(TypeError) as e:
+            @attr.s
+            class C:
+                pass
+
+        assert ("attrs only works with new-style classes.",) == e.value.args
+
+    def test_sets_attrs(self):
+        """
+        Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s.
+        """
+        @attr.s
+        class C(object):
+            x = attr.ib()
+
+        assert "x" == C.__attrs_attrs__[0].name
+        assert all(isinstance(a, Attribute) for a in C.__attrs_attrs__)
+
+    def test_empty(self):
+        """
+        No attributes, no problems.
+        """
+        @attr.s
+        class C3(object):
+            pass
+
+        assert "C3()" == repr(C3())
+        assert C3() == C3()
+
+    @given(attr=attrs_st, attr_name=sampled_from(Attribute.__slots__))
+    def test_immutable(self, attr, attr_name):
+        """
+        Attribute instances are immutable.
+        """
+        with pytest.raises(AttributeError):
+            setattr(attr, attr_name, 1)
+
+    @pytest.mark.parametrize("method_name", [
+        "__repr__",
+        "__eq__",
+        "__hash__",
+        "__init__",
+    ])
+    def test_adds_all_by_default(self, method_name):
+        """
+        If no further arguments are supplied, all add_XXX functions except
+        add_hash are applied.  __hash__ is set to None.
+        """
+        # Set the method name to a sentinel and check whether it has been
+        # overwritten afterwards.
+        sentinel = object()
+
+        class C(object):
+            x = attr.ib()
+
+        setattr(C, method_name, sentinel)
+
+        C = attr.s(C)
+        meth = getattr(C, method_name)
+
+        assert sentinel != meth
+        if method_name == "__hash__":
+            assert meth is None
+
+    @pytest.mark.parametrize("arg_name, method_name", [
+        ("repr", "__repr__"),
+        ("cmp", "__eq__"),
+        ("hash", "__hash__"),
+        ("init", "__init__"),
+    ])
+    def test_respects_add_arguments(self, arg_name, method_name):
+        """
+        If a certain `add_XXX` is `False`, `__XXX__` is not added to the class.
+        """
+        # Set the method name to a sentinel and check whether it has been
+        # overwritten afterwards.
+        sentinel = object()
+
+        am_args = {
+            "repr": True,
+            "cmp": True,
+            "hash": True,
+            "init": True
+        }
+        am_args[arg_name] = False
+
+        class C(object):
+            x = attr.ib()
+
+        setattr(C, method_name, sentinel)
+
+        C = attr.s(**am_args)(C)
+
+        assert sentinel == getattr(C, method_name)
+
+    @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.")
+    @given(slots_outer=booleans(), slots_inner=booleans())
+    def test_repr_qualname(self, slots_outer, slots_inner):
+        """
+        On Python 3, the name in repr is the __qualname__.
+        """
+        @attr.s(slots=slots_outer)
+        class C(object):
+            @attr.s(slots=slots_inner)
+            class D(object):
+                pass
+
+        assert "C.D()" == repr(C.D())
+        assert "GC.D()" == repr(GC.D())
+
+    @given(slots_outer=booleans(), slots_inner=booleans())
+    def test_repr_fake_qualname(self, slots_outer, slots_inner):
+        """
+        Setting repr_ns overrides a potentially guessed namespace.
+        """
+        @attr.s(slots=slots_outer)
+        class C(object):
+            @attr.s(repr_ns="C", slots=slots_inner)
+            class D(object):
+                pass
+        assert "C.D()" == repr(C.D())
+
+    @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.")
+    @given(slots_outer=booleans(), slots_inner=booleans())
+    def test_name_not_overridden(self, slots_outer, slots_inner):
+        """
+        On Python 3, __name__ is different from __qualname__.
+        """
+        @attr.s(slots=slots_outer)
+        class C(object):
+            @attr.s(slots=slots_inner)
+            class D(object):
+                pass
+
+        assert C.D.__name__ == "D"
+        assert C.D.__qualname__ == C.__qualname__ + ".D"
+
+    @given(with_validation=booleans())
+    def test_post_init(self, with_validation, monkeypatch):
+        """
+        Verify that __attrs_post_init__ gets called if defined.
+        """
+        monkeypatch.setattr(_config, "_run_validators", with_validation)
+
+        @attr.s
+        class C(object):
+            x = attr.ib()
+            y = attr.ib()
+
+            def __attrs_post_init__(self2):
+                self2.z = self2.x + self2.y
+
+        c = C(x=10, y=20)
+
+        assert 30 == getattr(c, 'z', None)
+
+    def test_types(self):
+        """
+        Sets the `Attribute.type` attr from type argument.
+        """
+        @attr.s
+        class C(object):
+            x = attr.ib(type=int)
+            y = attr.ib(type=str)
+            z = attr.ib()
+
+        assert int is fields(C).x.type
+        assert str is fields(C).y.type
+        assert None is fields(C).z.type
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_clean_class(self, slots):
+        """
+        Attribute definitions do not appear on the class body after @attr.s.
+        """
+        @attr.s(slots=slots)
+        class C(object):
+            x = attr.ib()
+
+        x = getattr(C, "x", None)
+
+        assert not isinstance(x, _CountingAttr)
+
+
+@attr.s
+class GC(object):
+    @attr.s
+    class D(object):
+        pass
+
+
+class TestMakeClass(object):
+    """
+    Tests for `make_class`.
+    """
+    @pytest.mark.parametrize("ls", [
+        list,
+        tuple
+    ])
+    def test_simple(self, ls):
+        """
+        Passing a list of strings creates attributes with default args.
+        """
+        C1 = make_class("C1", ls(["a", "b"]))
+
+        @attr.s
+        class C2(object):
+            a = attr.ib()
+            b = attr.ib()
+
+        assert C1.__attrs_attrs__ == C2.__attrs_attrs__
+
+    def test_dict(self):
+        """
+        Passing a dict of name: _CountingAttr creates an equivalent class.
+        """
+        C1 = make_class("C1", {
+            "a": attr.ib(default=42),
+            "b": attr.ib(default=None),
+        })
+
+        @attr.s
+        class C2(object):
+            a = attr.ib(default=42)
+            b = attr.ib(default=None)
+
+        assert C1.__attrs_attrs__ == C2.__attrs_attrs__
+
+    def test_attr_args(self):
+        """
+        attributes_arguments are passed to attributes
+        """
+        C = make_class("C", ["x"], repr=False)
+
+        assert repr(C(1)).startswith("<tests.test_make.C object at 0x")
+
+    def test_catches_wrong_attrs_type(self):
+        """
+        Raise `TypeError` if an invalid type for attrs is passed.
+        """
+        with pytest.raises(TypeError) as e:
+            make_class("C", object())
+
+        assert (
+            "attrs argument must be a dict or a list.",
+        ) == e.value.args
+
+    def test_bases(self):
+        """
+        Parameter bases default to (object,) and subclasses correctly
+        """
+        class D(object):
+            pass
+
+        cls = make_class("C", {})
+
+        assert cls.__mro__[-1] == object
+
+        cls = make_class("C", {}, bases=(D,))
+
+        assert D in cls.__mro__
+        assert isinstance(cls(), D)
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_clean_class(self, slots):
+        """
+        Attribute definitions do not appear on the class body.
+        """
+        C = make_class("C", ["x"], slots=slots)
+
+        x = getattr(C, "x", None)
+
+        assert not isinstance(x, _CountingAttr)
+
+    def test_missing_sys_getframe(self, monkeypatch):
+        """
+        `make_class()` does not fail when `sys._getframe()` is not available.
+        """
+        monkeypatch.delattr(sys, '_getframe')
+        C = make_class("C", ["x"])
+
+        assert 1 == len(C.__attrs_attrs__)
+
+
+class TestFields(object):
+    """
+    Tests for `fields`.
+    """
+    def test_instance(self, C):
+        """
+        Raises `TypeError` on non-classes.
+        """
+        with pytest.raises(TypeError) as e:
+            fields(C(1, 2))
+
+        assert "Passed object must be a class." == e.value.args[0]
+
+    def test_handler_non_attrs_class(self, C):
+        """
+        Raises `ValueError` if passed a non-``attrs`` instance.
+        """
+        with pytest.raises(NotAnAttrsClassError) as e:
+            fields(object)
+        assert (
+            "{o!r} is not an attrs-decorated class.".format(o=object)
+        ) == e.value.args[0]
+
+    @given(simple_classes())
+    def test_fields(self, C):
+        """
+        Returns a list of `Attribute`a.
+        """
+        assert all(isinstance(a, Attribute) for a in fields(C))
+
+    @given(simple_classes())
+    def test_fields_properties(self, C):
+        """
+        Fields returns a tuple with properties.
+        """
+        for attribute in fields(C):
+            assert getattr(fields(C), attribute.name) is attribute
+
+
+class TestConvert(object):
+    """
+    Tests for attribute conversion.
+    """
+    def test_convert(self):
+        """
+        Return value of convert is used as the attribute's value.
+        """
+        C = make_class("C", {
+            "x": attr.ib(convert=lambda v: v + 1),
+            "y": attr.ib(),
+        })
+        c = C(1, 2)
+
+        assert c.x == 2
+        assert c.y == 2
+
+    @given(integers(), booleans())
+    def test_convert_property(self, val, init):
+        """
+        Property tests for attributes with convert.
+        """
+        C = make_class("C", {
+            "y": attr.ib(),
+            "x": attr.ib(init=init, default=val, convert=lambda v: v + 1),
+        })
+        c = C(2)
+
+        assert c.x == val + 1
+        assert c.y == 2
+
+    @given(integers(), booleans())
+    def test_convert_factory_property(self, val, init):
+        """
+        Property tests for attributes with convert, and a factory default.
+        """
+        C = make_class("C", {
+            "y": attr.ib(),
+            "x": attr.ib(
+                init=init,
+                default=Factory(lambda: val),
+                convert=lambda v: v + 1),
+        })
+        c = C(2)
+
+        assert c.x == val + 1
+        assert c.y == 2
+
+    def test_factory_takes_self(self):
+        """
+        If takes_self on factories is True, self is passed.
+        """
+        C = make_class("C", {
+            "x": attr.ib(
+                default=Factory((lambda self: self), takes_self=True)
+            ),
+        })
+
+        i = C()
+
+        assert i is i.x
+
+    def test_factory_hashable(self):
+        """
+        Factory is hashable.
+        """
+        assert hash(Factory(None, False)) == hash(Factory(None, False))
+
+    def test_convert_before_validate(self):
+        """
+        Validation happens after conversion.
+        """
+        def validator(inst, attr, val):
+            raise RuntimeError("foo")
+        C = make_class(
+            "C", {
+                "x": attr.ib(validator=validator, convert=lambda v: 1 / 0),
+                "y": attr.ib(),
+            })
+        with pytest.raises(ZeroDivisionError):
+            C(1, 2)
+
+    def test_frozen(self):
+        """
+        Converters circumvent immutability.
+        """
+        C = make_class("C", {
+            "x": attr.ib(convert=lambda v: int(v)),
+        }, frozen=True)
+        C("1")
+
+
+class TestValidate(object):
+    """
+    Tests for `validate`.
+    """
+    def test_success(self):
+        """
+        If the validator succeeds, nothing gets raised.
+        """
+        C = make_class("C", {
+            "x": attr.ib(validator=lambda *a: None),
+            "y": attr.ib()
+        })
+        validate(C(1, 2))
+
+    def test_propagates(self):
+        """
+        The exception of the validator is handed through.
+        """
+        def raiser(_, __, value):
+            if value == 42:
+                raise FloatingPointError
+
+        C = make_class("C", {"x": attr.ib(validator=raiser)})
+        i = C(1)
+        i.x = 42
+
+        with pytest.raises(FloatingPointError):
+            validate(i)
+
+    def test_run_validators(self):
+        """
+        Setting `_run_validators` to False prevents validators from running.
+        """
+        _config._run_validators = False
+        obj = object()
+
+        def raiser(_, __, ___):
+            raise Exception(obj)
+
+        C = make_class("C", {"x": attr.ib(validator=raiser)})
+        c = C(1)
+        validate(c)
+        assert 1 == c.x
+        _config._run_validators = True
+
+        with pytest.raises(Exception):
+            validate(c)
+
+        with pytest.raises(Exception) as e:
+            C(1)
+        assert (obj,) == e.value.args
+
+    def test_multiple_validators(self):
+        """
+        If a list is passed as a validator, all of its items are treated as one
+        and must pass.
+        """
+        def v1(_, __, value):
+            if value == 23:
+                raise TypeError("omg")
+
+        def v2(_, __, value):
+            if value == 42:
+                raise ValueError("omg")
+
+        C = make_class("C", {"x": attr.ib(validator=[v1, v2])})
+
+        validate(C(1))
+
+        with pytest.raises(TypeError) as e:
+            C(23)
+
+        assert "omg" == e.value.args[0]
+
+        with pytest.raises(ValueError) as e:
+            C(42)
+
+        assert "omg" == e.value.args[0]
+
+    def test_multiple_empty(self):
+        """
+        Empty list/tuple for validator is the same as None.
+        """
+        C1 = make_class("C", {"x": attr.ib(validator=[])})
+        C2 = make_class("C", {"x": attr.ib(validator=None)})
+
+        assert inspect.getsource(C1.__init__) == inspect.getsource(C2.__init__)
+
+
+# Hypothesis seems to cache values, so the lists of attributes come out
+# unsorted.
+sorted_lists_of_attrs = list_of_attrs.map(
+    lambda l: sorted(l, key=attrgetter("counter")))
+
+
+class TestMetadata(object):
+    """
+    Tests for metadata handling.
+    """
+    @given(sorted_lists_of_attrs)
+    def test_metadata_present(self, list_of_attrs):
+        """
+        Assert dictionaries are copied and present.
+        """
+        C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs)))
+
+        for hyp_attr, class_attr in zip(list_of_attrs, fields(C)):
+            if hyp_attr.metadata is None:
+                # The default is a singleton empty dict.
+                assert class_attr.metadata is not None
+                assert len(class_attr.metadata) == 0
+            else:
+                assert hyp_attr.metadata == class_attr.metadata
+
+                # Once more, just to assert getting items and iteration.
+                for k in class_attr.metadata:
+                    assert hyp_attr.metadata[k] == class_attr.metadata[k]
+                    assert (hyp_attr.metadata.get(k) ==
+                            class_attr.metadata.get(k))
+
+    @given(simple_classes(), text())
+    def test_metadata_immutability(self, C, string):
+        """
+        The metadata dict should be best-effort immutable.
+        """
+        for a in fields(C):
+            with pytest.raises(TypeError):
+                a.metadata[string] = string
+            with pytest.raises(AttributeError):
+                a.metadata.update({string: string})
+            with pytest.raises(AttributeError):
+                a.metadata.clear()
+            with pytest.raises(AttributeError):
+                a.metadata.setdefault(string, string)
+
+            for k in a.metadata:
+                # For some reason, Python 3's MappingProxyType throws an
+                # IndexError for deletes on a large integer key.
+                with pytest.raises((TypeError, IndexError)):
+                    del a.metadata[k]
+                with pytest.raises(AttributeError):
+                    a.metadata.pop(k)
+            with pytest.raises(AttributeError):
+                    a.metadata.popitem()
+
+    @given(lists(simple_attrs_without_metadata, min_size=2, max_size=5))
+    def test_empty_metadata_singleton(self, list_of_attrs):
+        """
+        All empty metadata attributes share the same empty metadata dict.
+        """
+        C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs)))
+        for a in fields(C)[1:]:
+            assert a.metadata is fields(C)[0].metadata
+
+
+class TestClassBuilder(object):
+    """
+    Tests for `_ClassBuilder`.
+    """
+    def test_repr_str(self):
+        """
+        Trying to add a `__str__` without having a `__repr__` raises a
+        ValueError.
+        """
+        with pytest.raises(ValueError) as ei:
+            make_class("C", {}, repr=False, str=True)
+
+        assert (
+            "__str__ can only be generated if a __repr__ exists.",
+        ) == ei.value.args
+
+    def test_repr(self):
+        """
+        repr of builder itself makes sense.
+        """
+        class C(object):
+            pass
+
+        b = _ClassBuilder(C, None, True, True, False)
+
+        assert "<_ClassBuilder(cls=C)>" == repr(b)
+
+    def test_returns_self(self):
+        """
+        All methods return the builder for chaining.
+        """
+        class C(object):
+            x = attr.ib()
+
+        b = _ClassBuilder(C, None, True, True, False)
+
+        cls = b.add_cmp().add_hash().add_init().add_repr("ns").add_str() \
+            .build_class()
+
+        assert "ns.C(x=1)" == repr(cls(1))
diff --git a/tools/third_party/attrs/tests/test_slots.py b/tools/third_party/attrs/tests/test_slots.py
new file mode 100644
index 0000000..516a797
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_slots.py
@@ -0,0 +1,423 @@
+"""
+Unit tests for slot-related functionality.
+"""
+
+import pytest
+
+import attr
+
+from attr._compat import PY2, PYPY, just_warn, make_set_closure_cell
+
+
+# Pympler doesn't work on PyPy.
+try:
+    from pympler.asizeof import asizeof
+    has_pympler = True
+except BaseException:  # Won't be an import error.
+    has_pympler = False
+
+
+@attr.s
+class C1(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+    def method(self):
+        return self.x
+
+    @classmethod
+    def classmethod(cls):
+        return "clsmethod"
+
+    @staticmethod
+    def staticmethod():
+        return "staticmethod"
+
+    if not PY2:
+        def my_class(self):
+            return __class__  # NOQA: F821
+
+        def my_super(self):
+            """Just to test out the no-arg super."""
+            return super().__repr__()
+
+
+@attr.s(slots=True, hash=True)
+class C1Slots(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+    def method(self):
+        return self.x
+
+    @classmethod
+    def classmethod(cls):
+        return "clsmethod"
+
+    @staticmethod
+    def staticmethod():
+        return "staticmethod"
+
+    if not PY2:
+        def my_class(self):
+            return __class__  # NOQA: F821
+
+        def my_super(self):
+            """Just to test out the no-arg super."""
+            return super().__repr__()
+
+
+def test_slots_being_used():
+    """
+    The class is really using __slots__.
+    """
+    non_slot_instance = C1(x=1, y="test")
+    slot_instance = C1Slots(x=1, y="test")
+
+    assert "__dict__" not in dir(slot_instance)
+    assert "__slots__" in dir(slot_instance)
+
+    assert "__dict__" in dir(non_slot_instance)
+    assert "__slots__" not in dir(non_slot_instance)
+
+    assert set(["x", "y"]) == set(slot_instance.__slots__)
+
+    if has_pympler:
+        assert asizeof(slot_instance) < asizeof(non_slot_instance)
+
+    non_slot_instance.t = "test"
+    with pytest.raises(AttributeError):
+        slot_instance.t = "test"
+
+    assert 1 == non_slot_instance.method()
+    assert 1 == slot_instance.method()
+
+    assert attr.fields(C1Slots) == attr.fields(C1)
+    assert attr.asdict(slot_instance) == attr.asdict(non_slot_instance)
+
+
+def test_basic_attr_funcs():
+    """
+    Comparison, `__eq__`, `__hash__`, `__repr__`, `attrs.asdict` work.
+    """
+    a = C1Slots(x=1, y=2)
+    b = C1Slots(x=1, y=3)
+    a_ = C1Slots(x=1, y=2)
+
+    # Comparison.
+    assert b > a
+
+    assert a_ == a
+
+    # Hashing.
+    hash(b)  # Just to assert it doesn't raise.
+
+    # Repr.
+    assert "C1Slots(x=1, y=2)" == repr(a)
+
+    assert {"x": 1, "y": 2} == attr.asdict(a)
+
+
+def test_inheritance_from_nonslots():
+    """
+    Inheritance from a non-slot class works.
+
+    Note that a slots class inheriting from an ordinary class loses most of the
+    benefits of slots classes, but it should still work.
+    """
+    @attr.s(slots=True, hash=True)
+    class C2Slots(C1):
+        z = attr.ib()
+
+    c2 = C2Slots(x=1, y=2, z="test")
+
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+
+    c2.t = "test"  # This will work, using the base class.
+
+    assert "test" == c2.t
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    assert set(["z"]) == set(C2Slots.__slots__)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+
+    assert c3 > c2
+
+    c2_ = C2Slots(x=1, y=2, z="test")
+
+    assert c2 == c2_
+
+    assert "C2Slots(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+def test_nonslots_these():
+    """
+    Enhancing a non-slots class using 'these' works.
+
+    This will actually *replace* the class with another one, using slots.
+    """
+    class SimpleOrdinaryClass(object):
+        def __init__(self, x, y, z):
+            self.x = x
+            self.y = y
+            self.z = z
+
+        def method(self):
+            return self.x
+
+        @classmethod
+        def classmethod(cls):
+            return "clsmethod"
+
+        @staticmethod
+        def staticmethod():
+            return "staticmethod"
+
+    C2Slots = attr.s(these={"x": attr.ib(), "y": attr.ib(), "z": attr.ib()},
+                     init=False, slots=True, hash=True)(SimpleOrdinaryClass)
+
+    c2 = C2Slots(x=1, y=2, z="test")
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+    with pytest.raises(AttributeError):
+        c2.t = "test"  # We have slots now.
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    assert set(["x", "y", "z"]) == set(C2Slots.__slots__)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+    assert c3 > c2
+    c2_ = C2Slots(x=1, y=2, z="test")
+    assert c2 == c2_
+
+    assert "SimpleOrdinaryClass(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+def test_inheritance_from_slots():
+    """
+    Inheriting from an attr slot class works.
+    """
+    @attr.s(slots=True, hash=True)
+    class C2Slots(C1Slots):
+        z = attr.ib()
+
+    @attr.s(slots=True, hash=True)
+    class C2(C1):
+        z = attr.ib()
+
+    c2 = C2Slots(x=1, y=2, z="test")
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+
+    assert set(["z"]) == set(C2Slots.__slots__)
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    with pytest.raises(AttributeError):
+        c2.t = "test"
+
+    non_slot_instance = C2(x=1, y=2, z="test")
+    if has_pympler:
+        assert asizeof(c2) < asizeof(non_slot_instance)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+    assert c3 > c2
+    c2_ = C2Slots(x=1, y=2, z="test")
+    assert c2 == c2_
+
+    assert "C2Slots(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+def test_bare_inheritance_from_slots():
+    """
+    Inheriting from a bare attr slot class works.
+    """
+    @attr.s(init=False, cmp=False, hash=False, repr=False, slots=True)
+    class C1BareSlots(object):
+        x = attr.ib(validator=attr.validators.instance_of(int))
+        y = attr.ib()
+
+        def method(self):
+            return self.x
+
+        @classmethod
+        def classmethod(cls):
+            return "clsmethod"
+
+        @staticmethod
+        def staticmethod():
+            return "staticmethod"
+
+    @attr.s(init=False, cmp=False, hash=False, repr=False)
+    class C1Bare(object):
+        x = attr.ib(validator=attr.validators.instance_of(int))
+        y = attr.ib()
+
+        def method(self):
+            return self.x
+
+        @classmethod
+        def classmethod(cls):
+            return "clsmethod"
+
+        @staticmethod
+        def staticmethod():
+            return "staticmethod"
+
+    @attr.s(slots=True, hash=True)
+    class C2Slots(C1BareSlots):
+        z = attr.ib()
+
+    @attr.s(slots=True, hash=True)
+    class C2(C1Bare):
+        z = attr.ib()
+
+    c2 = C2Slots(x=1, y=2, z="test")
+    assert 1 == c2.x
+    assert 2 == c2.y
+    assert "test" == c2.z
+
+    assert 1 == c2.method()
+    assert "clsmethod" == c2.classmethod()
+    assert "staticmethod" == c2.staticmethod()
+
+    with pytest.raises(AttributeError):
+        c2.t = "test"
+
+    non_slot_instance = C2(x=1, y=2, z="test")
+    if has_pympler:
+        assert asizeof(c2) < asizeof(non_slot_instance)
+
+    c3 = C2Slots(x=1, y=3, z="test")
+    assert c3 > c2
+    c2_ = C2Slots(x=1, y=2, z="test")
+    assert c2 == c2_
+
+    assert "C2Slots(x=1, y=2, z='test')" == repr(c2)
+
+    hash(c2)  # Just to assert it doesn't raise.
+
+    assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
+
+
+@pytest.mark.skipif(PY2, reason="closure cell rewriting is PY3-only.")
+class TestClosureCellRewriting(object):
+    def test_closure_cell_rewriting(self):
+        """
+        Slot classes support proper closure cell rewriting.
+
+        This affects features like `__class__` and the no-arg super().
+        """
+        non_slot_instance = C1(x=1, y="test")
+        slot_instance = C1Slots(x=1, y="test")
+
+        assert non_slot_instance.my_class() is C1
+        assert slot_instance.my_class() is C1Slots
+
+        # Just assert they return something, and not an exception.
+        assert non_slot_instance.my_super()
+        assert slot_instance.my_super()
+
+    def test_inheritance(self):
+        """
+        Slot classes support proper closure cell rewriting when inheriting.
+
+        This affects features like `__class__` and the no-arg super().
+        """
+        @attr.s
+        class C2(C1):
+            def my_subclass(self):
+                return __class__  # NOQA: F821
+
+        @attr.s
+        class C2Slots(C1Slots):
+            def my_subclass(self):
+                return __class__  # NOQA: F821
+
+        non_slot_instance = C2(x=1, y="test")
+        slot_instance = C2Slots(x=1, y="test")
+
+        assert non_slot_instance.my_class() is C1
+        assert slot_instance.my_class() is C1Slots
+
+        # Just assert they return something, and not an exception.
+        assert non_slot_instance.my_super()
+        assert slot_instance.my_super()
+
+        assert non_slot_instance.my_subclass() is C2
+        assert slot_instance.my_subclass() is C2Slots
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_cls_static(self, slots):
+        """
+        Slot classes support proper closure cell rewriting for class- and
+        static methods.
+        """
+        # Python can reuse closure cells, so we create new classes just for
+        # this test.
+
+        @attr.s(slots=slots)
+        class C:
+            @classmethod
+            def clsmethod(cls):
+                return __class__  # noqa: F821
+
+        assert C.clsmethod() is C
+
+        @attr.s(slots=slots)
+        class D:
+            @staticmethod
+            def statmethod():
+                return __class__  # noqa: F821
+
+        assert D.statmethod() is D
+
+    @pytest.mark.skipif(
+        PYPY,
+        reason="ctypes are used only on CPython"
+    )
+    def test_missing_ctypes(self, monkeypatch):
+        """
+        Keeps working if ctypes is missing.
+
+        A warning is emitted that points to the actual code.
+        """
+        monkeypatch.setattr(attr._compat, "import_ctypes", lambda: None)
+        func = make_set_closure_cell()
+
+        with pytest.warns(RuntimeWarning) as wr:
+            func()
+
+        w = wr.pop()
+        assert __file__ == w.filename
+        assert (
+            "Missing ctypes.  Some features like bare super() or accessing "
+            "__class__ will not work with slots classes.",
+        ) == w.message.args
+
+        assert just_warn is func
diff --git a/tools/third_party/attrs/tests/test_validators.py b/tools/third_party/attrs/tests/test_validators.py
new file mode 100644
index 0000000..9722da2
--- /dev/null
+++ b/tools/third_party/attrs/tests/test_validators.py
@@ -0,0 +1,266 @@
+"""
+Tests for `attr.validators`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+import zope.interface
+
+import attr
+
+from attr import validators as validator_module
+from attr import has
+from attr._compat import TYPE
+from attr.validators import and_, in_, instance_of, optional, provides
+
+from .utils import simple_attr
+
+
+class TestInstanceOf(object):
+    """
+    Tests for `instance_of`.
+    """
+    def test_success(self):
+        """
+        Nothing happens if types match.
+        """
+        v = instance_of(int)
+        v(None, simple_attr("test"), 42)
+
+    def test_subclass(self):
+        """
+        Subclasses are accepted too.
+        """
+        v = instance_of(int)
+        # yep, bools are a subclass of int :(
+        v(None, simple_attr("test"), True)
+
+    def test_fail(self):
+        """
+        Raises `TypeError` on wrong types.
+        """
+        v = instance_of(int)
+        a = simple_attr("test")
+        with pytest.raises(TypeError) as e:
+            v(None, a, "42")
+        assert (
+            "'test' must be <{type} 'int'> (got '42' that is a <{type} "
+            "'str'>).".format(type=TYPE),
+            a, int, "42",
+
+        ) == e.value.args
+
+    def test_repr(self):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = instance_of(int)
+        assert (
+            "<instance_of validator for type <{type} 'int'>>"
+            .format(type=TYPE)
+        ) == repr(v)
+
+
+def always_pass(_, __, ___):
+    """
+    Toy validator that always passses.
+    """
+
+
+def always_fail(_, __, ___):
+    """
+    Toy validator that always fails.
+    """
+    0/0
+
+
+class TestAnd(object):
+    def test_success(self):
+        """
+        Succeeds if all wrapped validators succeed.
+        """
+        v = and_(instance_of(int), always_pass)
+
+        v(None, simple_attr("test"), 42)
+
+    def test_fail(self):
+        """
+        Fails if any wrapped validator fails.
+        """
+        v = and_(instance_of(int), always_fail)
+
+        with pytest.raises(ZeroDivisionError):
+            v(None, simple_attr("test"), 42)
+
+    def test_sugar(self):
+        """
+        `and_(v1, v2, v3)` and `[v1, v2, v3]` are equivalent.
+        """
+        @attr.s
+        class C(object):
+            a1 = attr.ib("a1", validator=and_(
+                instance_of(int),
+            ))
+            a2 = attr.ib("a2", validator=[
+                instance_of(int),
+            ])
+
+        assert C.__attrs_attrs__[0].validator == C.__attrs_attrs__[1].validator
+
+
+class IFoo(zope.interface.Interface):
+    """
+    An interface.
+    """
+    def f():
+        """
+        A function called f.
+        """
+
+
+class TestProvides(object):
+    """
+    Tests for `provides`.
+    """
+    def test_success(self):
+        """
+        Nothing happens if value provides requested interface.
+        """
+        @zope.interface.implementer(IFoo)
+        class C(object):
+            def f(self):
+                pass
+
+        v = provides(IFoo)
+        v(None, simple_attr("x"), C())
+
+    def test_fail(self):
+        """
+        Raises `TypeError` if interfaces isn't provided by value.
+        """
+        value = object()
+        a = simple_attr("x")
+
+        v = provides(IFoo)
+        with pytest.raises(TypeError) as e:
+            v(None, a, value)
+        assert (
+            "'x' must provide {interface!r} which {value!r} doesn't."
+            .format(interface=IFoo, value=value),
+            a, IFoo, value,
+        ) == e.value.args
+
+    def test_repr(self):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = provides(IFoo)
+        assert (
+            "<provides validator for interface {interface!r}>"
+            .format(interface=IFoo)
+        ) == repr(v)
+
+
+@pytest.mark.parametrize("validator", [
+    instance_of(int),
+    [always_pass, instance_of(int)],
+])
+class TestOptional(object):
+    """
+    Tests for `optional`.
+    """
+    def test_success(self, validator):
+        """
+        Nothing happens if validator succeeds.
+        """
+        v = optional(validator)
+        v(None, simple_attr("test"), 42)
+
+    def test_success_with_none(self, validator):
+        """
+        Nothing happens if None.
+        """
+        v = optional(validator)
+        v(None, simple_attr("test"), None)
+
+    def test_fail(self, validator):
+        """
+        Raises `TypeError` on wrong types.
+        """
+        v = optional(validator)
+        a = simple_attr("test")
+        with pytest.raises(TypeError) as e:
+            v(None, a, "42")
+        assert (
+            "'test' must be <{type} 'int'> (got '42' that is a <{type} "
+            "'str'>).".format(type=TYPE),
+            a, int, "42",
+
+        ) == e.value.args
+
+    def test_repr(self, validator):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = optional(validator)
+
+        if isinstance(validator, list):
+            assert (
+                ("<optional validator for _AndValidator(_validators=[{func}, "
+                 "<instance_of validator for type <{type} 'int'>>]) or None>")
+                .format(func=repr(always_pass), type=TYPE)
+            ) == repr(v)
+        else:
+            assert (
+                ("<optional validator for <instance_of validator for type "
+                 "<{type} 'int'>> or None>")
+                .format(type=TYPE)
+            ) == repr(v)
+
+
+class TestIn_(object):
+    """
+    Tests for `in_`.
+    """
+    def test_success_with_value(self):
+        """
+        If the value is in our options, nothing happens.
+        """
+        v = in_([1, 2, 3])
+        a = simple_attr("test")
+        v(1, a, 3)
+
+    def test_fail(self):
+        """
+        Raise ValueError if the value is outside our options.
+        """
+        v = in_([1, 2, 3])
+        a = simple_attr("test")
+        with pytest.raises(ValueError) as e:
+            v(None, a, None)
+        assert (
+            "'test' must be in [1, 2, 3] (got None)",
+        ) == e.value.args
+
+    def test_repr(self):
+        """
+        Returned validator has a useful `__repr__`.
+        """
+        v = in_([3, 4, 5])
+        assert(
+            ("<in_ validator with options [3, 4, 5]>")
+        ) == repr(v)
+
+
+def test_hashability():
+    """
+    Validator classes are hashable.
+    """
+    for obj_name in dir(validator_module):
+        obj = getattr(validator_module, obj_name)
+        if not has(obj):
+            continue
+        hash_func = getattr(obj, '__hash__', None)
+        assert hash_func is not None
+        assert hash_func is not object.__hash__
diff --git a/tools/third_party/attrs/tests/utils.py b/tools/third_party/attrs/tests/utils.py
new file mode 100644
index 0000000..36b6249
--- /dev/null
+++ b/tools/third_party/attrs/tests/utils.py
@@ -0,0 +1,237 @@
+"""
+Common helper functions for tests.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import keyword
+import string
+
+from collections import OrderedDict
+
+from hypothesis import strategies as st
+
+import attr
+
+from attr import Attribute
+from attr._make import NOTHING, make_class
+
+
+def simple_class(cmp=False, repr=False, hash=False, str=False, slots=False,
+                 frozen=False):
+    """
+    Return a new simple class.
+    """
+    return make_class(
+        "C", ["a", "b"],
+        cmp=cmp, repr=repr, hash=hash, init=True, slots=slots, str=str,
+        frozen=frozen,
+    )
+
+
+def simple_attr(name, default=NOTHING, validator=None, repr=True,
+                cmp=True, hash=None, init=True):
+    """
+    Return an attribute with a name and no other bells and whistles.
+    """
+    return Attribute(
+        name=name, default=default, validator=validator, repr=repr,
+        cmp=cmp, hash=hash, init=init
+    )
+
+
+class TestSimpleClass(object):
+    """
+    Tests for the testing helper function `make_class`.
+    """
+    def test_returns_class(self):
+        """
+        Returns a class object.
+        """
+        assert type is simple_class().__class__
+
+    def returns_distinct_classes(self):
+        """
+        Each call returns a completely new class.
+        """
+        assert simple_class() is not simple_class()
+
+
+def gen_attr_names():
+    """
+    Generate names for attributes, 'a'...'z', then 'aa'...'zz'.
+
+    ~702 different attribute names should be enough in practice.
+
+    Some short strings (such as 'as') are keywords, so we skip them.
+    """
+    lc = string.ascii_lowercase
+    for c in lc:
+        yield c
+    for outer in lc:
+        for inner in lc:
+            res = outer + inner
+            if keyword.iskeyword(res):
+                continue
+            yield outer + inner
+
+
+def maybe_underscore_prefix(source):
+    """
+    A generator to sometimes prepend an underscore.
+    """
+    to_underscore = False
+    for val in source:
+        yield val if not to_underscore else '_' + val
+        to_underscore = not to_underscore
+
+
+def _create_hyp_class(attrs):
+    """
+    A helper function for Hypothesis to generate attrs classes.
+    """
+    return make_class(
+        "HypClass", dict(zip(gen_attr_names(), attrs))
+    )
+
+
+def _create_hyp_nested_strategy(simple_class_strategy):
+    """
+    Create a recursive attrs class.
+
+    Given a strategy for building (simpler) classes, create and return
+    a strategy for building classes that have as an attribute: either just
+    the simpler class, a list of simpler classes, a tuple of simpler classes,
+    an ordered dict or a dict mapping the string "cls" to a simpler class.
+    """
+    # Use a tuple strategy to combine simple attributes and an attr class.
+    def just_class(tup):
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=attr.Factory(tup[1])))
+        return _create_hyp_class(combined_attrs)
+
+    def list_of_class(tup):
+        default = attr.Factory(lambda: [tup[1]()])
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    def tuple_of_class(tup):
+        default = attr.Factory(lambda: (tup[1](),))
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    def dict_of_class(tup):
+        default = attr.Factory(lambda: {"cls": tup[1]()})
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    def ordereddict_of_class(tup):
+        default = attr.Factory(lambda: OrderedDict([("cls", tup[1]())]))
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    # A strategy producing tuples of the form ([list of attributes], <given
+    # class strategy>).
+    attrs_and_classes = st.tuples(list_of_attrs, simple_class_strategy)
+
+    return st.one_of(attrs_and_classes.map(just_class),
+                     attrs_and_classes.map(list_of_class),
+                     attrs_and_classes.map(tuple_of_class),
+                     attrs_and_classes.map(dict_of_class),
+                     attrs_and_classes.map(ordereddict_of_class))
+
+
+bare_attrs = st.just(attr.ib(default=None))
+int_attrs = st.integers().map(lambda i: attr.ib(default=i))
+str_attrs = st.text().map(lambda s: attr.ib(default=s))
+float_attrs = st.floats().map(lambda f: attr.ib(default=f))
+dict_attrs = (st.dictionaries(keys=st.text(), values=st.integers())
+              .map(lambda d: attr.ib(default=d)))
+
+simple_attrs_without_metadata = (bare_attrs | int_attrs | str_attrs |
+                                 float_attrs | dict_attrs)
+
+
+@st.composite
+def simple_attrs_with_metadata(draw):
+    """
+    Create a simple attribute with arbitrary metadata.
+    """
+    c_attr = draw(simple_attrs)
+    keys = st.booleans() | st.binary() | st.integers() | st.text()
+    vals = st.booleans() | st.binary() | st.integers() | st.text()
+    metadata = draw(st.dictionaries(keys=keys, values=vals))
+
+    return attr.ib(c_attr._default, c_attr._validator, c_attr.repr,
+                   c_attr.cmp, c_attr.hash, c_attr.init, c_attr.convert,
+                   metadata)
+
+
+simple_attrs = simple_attrs_without_metadata | simple_attrs_with_metadata()
+
+# Python functions support up to 255 arguments.
+list_of_attrs = st.lists(simple_attrs, average_size=3, max_size=9)
+
+
+@st.composite
+def simple_classes(draw, slots=None, frozen=None, private_attrs=None):
+    """
+    A strategy that generates classes with default non-attr attributes.
+
+    For example, this strategy might generate a class such as:
+
+    @attr.s(slots=True, frozen=True)
+    class HypClass:
+        a = attr.ib(default=1)
+        _b = attr.ib(default=None)
+        c = attr.ib(default='text')
+        _d = attr.ib(default=1.0)
+        c = attr.ib(default={'t': 1})
+
+    By default, all combinations of slots and frozen classes will be generated.
+    If `slots=True` is passed in, only slots classes will be generated, and
+    if `slots=False` is passed in, no slot classes will be generated. The same
+    applies to `frozen`.
+
+    By default, some attributes will be private (i.e. prefixed with an
+    underscore). If `private_attrs=True` is passed in, all attributes will be
+    private, and if `private_attrs=False`, no attributes will be private.
+    """
+    attrs = draw(list_of_attrs)
+    frozen_flag = draw(st.booleans()) if frozen is None else frozen
+    slots_flag = draw(st.booleans()) if slots is None else slots
+
+    if private_attrs is None:
+        attr_names = maybe_underscore_prefix(gen_attr_names())
+    elif private_attrs is True:
+        attr_names = ('_' + n for n in gen_attr_names())
+    elif private_attrs is False:
+        attr_names = gen_attr_names()
+
+    cls_dict = dict(zip(attr_names, attrs))
+    post_init_flag = draw(st.booleans())
+    if post_init_flag:
+        def post_init(self):
+            pass
+        cls_dict["__attrs_post_init__"] = post_init
+
+    return make_class(
+        "HypClass",
+        cls_dict,
+        slots=slots_flag,
+        frozen=frozen_flag,
+    )
+
+
+# st.recursive works by taking a base strategy (in this case, simple_classes)
+# and a special function.  This function receives a strategy, and returns
+# another strategy (building on top of the base strategy).
+nested_classes = st.recursive(
+    simple_classes(),
+    _create_hyp_nested_strategy,
+    max_leaves=10
+)
diff --git a/tools/third_party/attrs/tox.ini b/tools/third_party/attrs/tox.ini
new file mode 100644
index 0000000..e02c330
--- /dev/null
+++ b/tools/third_party/attrs/tox.ini
@@ -0,0 +1,83 @@
+[tox]
+envlist = isort,py27,py34,py35,py36,pypy,pypy3,flake8,manifest,docs,readme,changelog,coverage-report
+
+
+[testenv]
+# Prevent random setuptools/pip breakages like
+# https://github.com/pypa/setuptools/issues/1042 from breaking our builds.
+setenv =
+    VIRTUALENV_NO_DOWNLOAD=1
+deps = -rdev-requirements.txt
+commands = python -m pytest {posargs}
+
+
+[testenv:py27]
+deps = -rdev-requirements.txt
+commands = coverage run --parallel -m pytest {posargs}
+
+
+[testenv:py36]
+deps = -rdev-requirements.txt
+commands = coverage run --parallel -m pytest {posargs}
+
+
+# Uses default basepython otherwise reporting doesn't work on Travis where
+# Python 3.6 is only available in 3.6 jobs.
+[testenv:coverage-report]
+deps = coverage
+skip_install = true
+commands =
+    coverage combine
+    coverage report
+
+
+[testenv:flake8]
+basepython = python3.6
+# Needs a full install so isort can determine own/foreign imports.
+deps =
+    -rdev-requirements.txt
+    flake8
+    flake8-isort
+commands = flake8 src tests setup.py conftest.py docs/conf.py
+
+
+[testenv:isort]
+basepython = python3.6
+# Needs a full install so isort can determine own/foreign imports.
+deps =
+    -rdev-requirements.txt
+    isort
+commands =
+    isort --recursive setup.py conftest.py src tests
+
+
+[testenv:docs]
+basepython = python3.6
+setenv =
+    PYTHONHASHSEED = 0
+deps = -rdocs-requirements.txt
+commands =
+    sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
+    sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
+    python -m doctest README.rst
+
+
+[testenv:manifest]
+basepython = python3.6
+deps = check-manifest
+skip_install = true
+commands = check-manifest
+
+
+[testenv:readme]
+basepython = python3.6
+deps = readme_renderer
+skip_install = true
+commands = python setup.py check -r -s
+
+
+[testenv:changelog]
+basepython = python3.6
+deps = towncrier
+skip_install = true
+commands = towncrier --draft
diff --git a/tools/third_party/funcsigs/.coveragerc b/tools/third_party/funcsigs/.coveragerc
new file mode 100644
index 0000000..d83bfc2
--- /dev/null
+++ b/tools/third_party/funcsigs/.coveragerc
@@ -0,0 +1,6 @@
+[run]
+source=funcsigs
+omit=funcsigs/odict*
+
+[report]
+include=funcsigs*
diff --git a/tools/third_party/funcsigs/.travis.yml b/tools/third_party/funcsigs/.travis.yml
new file mode 100644
index 0000000..d2a7ab3
--- /dev/null
+++ b/tools/third_party/funcsigs/.travis.yml
@@ -0,0 +1,17 @@
+language: python
+python:
+  - 2.6
+  - 2.7
+  - 3.2
+  - 3.3
+  - pypy
+install:
+  - pip install -r requirements/development.txt -r requirements/production.txt
+  - python setup.py install
+script:
+  - coverage run setup.py test
+  - coverage report --show-missing
+after_success:
+  - coveralls
+notifications:
+    email: aaron.iles+travis-ci@gmail.com
diff --git a/tools/third_party/funcsigs/CHANGELOG b/tools/third_party/funcsigs/CHANGELOG
new file mode 100644
index 0000000..602eec5
--- /dev/null
+++ b/tools/third_party/funcsigs/CHANGELOG
@@ -0,0 +1,19 @@
+Changelog
+---------
+
+0.4 (2013-12-20)
+````````````````
+* Fix unbound methods getting their first parameter curried
+* Publish Python wheel packages
+
+0.3 (2013-05-29)
+````````````````
+* Fix annotation formatting of builtin types on Python 2.x
+
+0.2 (2012-01-07)
+````````````````
+* PyPy compatability
+
+0.1 (2012-01-06)
+````````````````
+* Initial release
diff --git a/tools/third_party/funcsigs/LICENSE b/tools/third_party/funcsigs/LICENSE
new file mode 100644
index 0000000..3e563d6
--- /dev/null
+++ b/tools/third_party/funcsigs/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2013 Aaron Iles
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/tools/third_party/funcsigs/MANIFEST.in b/tools/third_party/funcsigs/MANIFEST.in
new file mode 100644
index 0000000..f0abb42
--- /dev/null
+++ b/tools/third_party/funcsigs/MANIFEST.in
@@ -0,0 +1,7 @@
+recursive-include docs *
+recursive-include tests *.py
+include *.py
+include CHANGELOG
+include LICENSE
+include MANIFEST.in
+include README.rst
diff --git a/tools/third_party/funcsigs/Makefile b/tools/third_party/funcsigs/Makefile
new file mode 100644
index 0000000..e232923
--- /dev/null
+++ b/tools/third_party/funcsigs/Makefile
@@ -0,0 +1,39 @@
+SHELL := /bin/bash
+
+deps:
+	pip install --upgrade \
+	            -r requirements/development.txt \
+	            -r requirements/production.txt
+
+sdist:
+	python setup.py sdist
+	python setup.py bdist_wheel
+
+register:
+	python setup.py register
+	python setup.py sdist upload
+	python setup.py bdist_wheel upload
+
+site:
+	cd docs; make html
+
+test:
+	coverage run setup.py test
+
+unittest:
+	coverage run -m unittest discover
+
+lint:
+	flake8 --exit-zero funcsigs tests
+
+coverage:
+	coverage report --show-missing
+
+clean:
+	python setup.py clean --all
+	find . -type f -name "*.pyc" -exec rm '{}' +
+	find . -type d -name "__pycache__" -exec rmdir '{}' +
+	rm -rf *.egg-info .coverage
+	cd docs; make clean
+
+docs: site
diff --git a/tools/third_party/funcsigs/README.rst b/tools/third_party/funcsigs/README.rst
new file mode 100644
index 0000000..f04b7b4
--- /dev/null
+++ b/tools/third_party/funcsigs/README.rst
@@ -0,0 +1,83 @@
+funcsigs
+========
+
+``funcsigs`` is a backport of the `PEP 362`_ function signature features from
+Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7
+as well as 3.2 and up.
+
+|pypi_version|
+
+Documentation
+-------------
+
+The reference documentation is standard library documentation for the
+`inspect`_ module in Python3. This documentation has been included in the
+``funcsigs`` package documentation hosted on `Read The Docs`_.
+
+Example
+-------
+
+To obtain a signature object, pass the target function to the
+``funcsigs.signature`` function. ::
+
+    >>> from funcsigs import signature
+    >>> def foo(a, b=None, *args, **kwargs):
+    ...     pass
+
+    >>> sig = signature(foo)
+
+For the details of the signature object, refer to the either the package of
+standard library documentation.
+
+Compatability
+-------------
+
+The ``funcsigs`` backport has been tested against:
+
+* CPython 2.6
+* CPython 2.7
+* CPython 3.2
+* PyPy 1.9
+
+Continuous integration testing is provided by `Travis CI`_.
+
+Under Python 2.x there is a compatability issue when a function is assigned to
+the ``__wrapped__`` property of a class after it has been constructed.
+Similiarily there under PyPy directly passing the ``__call__`` method of a
+builtin is also a compatability issues.  Otherwise the functionality is
+believed to be uniform between both Python2 and Python3.
+
+Issues
+------
+
+Source code for ``funcsigs`` is hosted on `GitHub`_. Any bug reports or feature
+requests can be made using GitHub's `issues system`_. |build_status| |coverage|
+
+Copyright
+---------
+
+This is a derived work of CPython under the terms of the `PSF License
+Agreement`_. The original CPython inspect module, its unit tests and
+documentation are the copyright of the Python Software Foundation. The derived
+work is distributed under the `Apache License Version 2.0`_.
+
+.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0
+.. _GitHub: https://github.com/aliles/funcsigs
+.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
+.. _Travis CI: http://travis-ci.org/
+.. _Read The Docs: http://funcsigs.readthedocs.org/
+.. _PEP 362: http://www.python.org/dev/peps/pep-0362/
+.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object
+.. _issues system: https://github.com/alies/funcsigs/issues
+
+.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master
+   :target: http://travis-ci.org/#!/aliles/funcsigs
+   :alt: Current build status
+
+.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master
+   :target: https://coveralls.io/r/aliles/funcsigs?branch=master
+   :alt: Coverage status
+
+.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png
+   :target: https://crate.io/packages/funcsigs/
+   :alt: Latest PyPI version
diff --git a/tools/third_party/funcsigs/docs/Makefile b/tools/third_party/funcsigs/docs/Makefile
new file mode 100644
index 0000000..f7ab3d1
--- /dev/null
+++ b/tools/third_party/funcsigs/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/funcsigs.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/funcsigs.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/funcsigs"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/funcsigs"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/tools/third_party/funcsigs/docs/_templates/page.html b/tools/third_party/funcsigs/docs/_templates/page.html
new file mode 100644
index 0000000..5e1e00b
--- /dev/null
+++ b/tools/third_party/funcsigs/docs/_templates/page.html
@@ -0,0 +1,9 @@
+{% extends "!page.html" %}
+{% block extrahead %}
+    <a href="https://github.com/aliles/funcsigs">
+        <img style="position: absolute; top: 0; right: 0; border: 0;"
+             src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"
+             alt="Fork me on GitHub">
+    </a>
+    {{ super() }}
+{% endblock %}
diff --git a/tools/third_party/funcsigs/docs/conf.py b/tools/third_party/funcsigs/docs/conf.py
new file mode 100644
index 0000000..c6e4194
--- /dev/null
+++ b/tools/third_party/funcsigs/docs/conf.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# funcsigs documentation build configuration file, created by
+# sphinx-quickstart on Fri Apr 20 20:27:52 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('..'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'funcsigs'
+copyright = '2013, Aaron Iles'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+from funcsigs import __version__
+version = '.'.join(__version__.split('.')[:2])
+# The full version, including alpha/beta/rc tags.
+release = __version__
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'agogo'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'funcsigsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'funcsigs.tex', 'funcsigs Documentation',
+   'Aaron Iles', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'funcsigs', 'funcsigs Documentation',
+     ['Aaron Iles'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'funcsigs', 'funcsigs Documentation',
+   'Aaron Iles', 'funcsigs', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {
+        'python3': ('http://docs.python.org/py3k', None),
+        'python': ('http://docs.python.org/', None)
+}
diff --git a/tools/third_party/funcsigs/docs/index.rst b/tools/third_party/funcsigs/docs/index.rst
new file mode 100644
index 0000000..5d0f42f
--- /dev/null
+++ b/tools/third_party/funcsigs/docs/index.rst
@@ -0,0 +1,315 @@
+.. funcsigs documentation master file, created by
+   sphinx-quickstart on Fri Apr 20 20:27:52 2012.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Introducing funcsigs
+====================
+
+The Funcsigs Package
+--------------------
+
+*funcsigs* is a backport of the `PEP 362`_ function signature features from
+Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7
+as well as 3.2 and up.
+
+.. _PEP 362: http://www.python.org/dev/peps/pep-0362/
+.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object
+
+Compatability
+`````````````
+
+The *funcsigs* backport has been tested against:
+
+* CPython 2.6
+* CPython 2.7
+* CPython 3.2
+* PyPy 1.9
+
+Continuous integration testing is provided by `Travis CI`_.
+
+Under Python 2.x there is a compatability issue when a function is assigned to
+the ``__wrapped__`` property of a class after it has been constructed.
+Similiarily there under PyPy directly passing the ``__call__`` method of a
+builtin is also a compatability issues.  Otherwise the functionality is
+believed to be uniform between both Python2 and Python3.
+
+.. _Travis CI: http://travis-ci.org/
+
+Issues
+``````
+
+Source code for *funcsigs* is hosted on `GitHub`_. Any bug reports or feature
+requests can be made using GitHub's `issues system`_.
+
+.. _GitHub: https://github.com/aliles/funcsigs
+.. _issues system: https://github.com/alies/funcsigs/issues
+
+Introspecting callables with the Signature object
+-------------------------------------------------
+
+.. note::
+
+   This section of documentation is a direct repoduction of the Python
+   standard library documentation for the inspect module.
+
+The Signature object represents the call signature of a callable object and its
+return annotation.  To retrieve a Signature object, use the :func:`signature`
+function.
+
+.. function:: signature(callable)
+
+   Return a :class:`Signature` object for the given ``callable``::
+
+      >>> from inspect import signature
+      >>> def foo(a, *, b:int, **kwargs):
+      ...     pass
+
+      >>> sig = signature(foo)
+
+      >>> str(sig)
+      '(a, *, b:int, **kwargs)'
+
+      >>> str(sig.parameters['b'])
+      'b:int'
+
+      >>> sig.parameters['b'].annotation
+      <class 'int'>
+
+   Accepts a wide range of python callables, from plain functions and classes to
+   :func:`functools.partial` objects.
+
+   .. note::
+
+      Some callables may not be introspectable in certain implementations of
+      Python.  For example, in CPython, built-in functions defined in C provide
+      no metadata about their arguments.
+
+
+.. class:: Signature
+
+   A Signature object represents the call signature of a function and its return
+   annotation.  For each parameter accepted by the function it stores a
+   :class:`Parameter` object in its :attr:`parameters` collection.
+
+   Signature objects are *immutable*.  Use :meth:`Signature.replace` to make a
+   modified copy.
+
+   .. attribute:: Signature.empty
+
+      A special class-level marker to specify absence of a return annotation.
+
+   .. attribute:: Signature.parameters
+
+      An ordered mapping of parameters' names to the corresponding
+      :class:`Parameter` objects.
+
+   .. attribute:: Signature.return_annotation
+
+      The "return" annotation for the callable.  If the callable has no "return"
+      annotation, this attribute is set to :attr:`Signature.empty`.
+
+   .. method:: Signature.bind(*args, **kwargs)
+
+      Create a mapping from positional and keyword arguments to parameters.
+      Returns :class:`BoundArguments` if ``*args`` and ``**kwargs`` match the
+      signature, or raises a :exc:`TypeError`.
+
+   .. method:: Signature.bind_partial(*args, **kwargs)
+
+      Works the same way as :meth:`Signature.bind`, but allows the omission of
+      some required arguments (mimics :func:`functools.partial` behavior.)
+      Returns :class:`BoundArguments`, or raises a :exc:`TypeError` if the
+      passed arguments do not match the signature.
+
+   .. method:: Signature.replace(*[, parameters][, return_annotation])
+
+      Create a new Signature instance based on the instance replace was invoked
+      on.  It is possible to pass different ``parameters`` and/or
+      ``return_annotation`` to override the corresponding properties of the base
+      signature.  To remove return_annotation from the copied Signature, pass in
+      :attr:`Signature.empty`.
+
+      ::
+
+         >>> def test(a, b):
+         ...     pass
+         >>> sig = signature(test)
+         >>> new_sig = sig.replace(return_annotation="new return anno")
+         >>> str(new_sig)
+         "(a, b) -> 'new return anno'"
+
+
+.. class:: Parameter
+
+   Parameter objects are *immutable*.  Instead of modifying a Parameter object,
+   you can use :meth:`Parameter.replace` to create a modified copy.
+
+   .. attribute:: Parameter.empty
+
+      A special class-level marker to specify absence of default values and
+      annotations.
+
+   .. attribute:: Parameter.name
+
+      The name of the parameter as a string.  Must be a valid python identifier
+      name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have
+      it set to ``None``).
+
+   .. attribute:: Parameter.default
+
+      The default value for the parameter.  If the parameter has no default
+      value, this attribute is set to :attr:`Parameter.empty`.
+
+   .. attribute:: Parameter.annotation
+
+      The annotation for the parameter.  If the parameter has no annotation,
+      this attribute is set to :attr:`Parameter.empty`.
+
+   .. attribute:: Parameter.kind
+
+      Describes how argument values are bound to the parameter.  Possible values
+      (accessible via :class:`Parameter`, like ``Parameter.KEYWORD_ONLY``):
+
+      +------------------------+----------------------------------------------+
+      |    Name                | Meaning                                      |
+      +========================+==============================================+
+      | *POSITIONAL_ONLY*      | Value must be supplied as a positional       |
+      |                        | argument.                                    |
+      |                        |                                              |
+      |                        | Python has no explicit syntax for defining   |
+      |                        | positional-only parameters, but many built-in|
+      |                        | and extension module functions (especially   |
+      |                        | those that accept only one or two parameters)|
+      |                        | accept them.                                 |
+      +------------------------+----------------------------------------------+
+      | *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or |
+      |                        | positional argument (this is the standard    |
+      |                        | binding behaviour for functions implemented  |
+      |                        | in Python.)                                  |
+      +------------------------+----------------------------------------------+
+      | *VAR_POSITIONAL*       | A tuple of positional arguments that aren't  |
+      |                        | bound to any other parameter. This           |
+      |                        | corresponds to a ``*args`` parameter in a    |
+      |                        | Python function definition.                  |
+      +------------------------+----------------------------------------------+
+      | *KEYWORD_ONLY*         | Value must be supplied as a keyword argument.|
+      |                        | Keyword only parameters are those which      |
+      |                        | appear after a ``*`` or ``*args`` entry in a |
+      |                        | Python function definition.                  |
+      +------------------------+----------------------------------------------+
+      | *VAR_KEYWORD*          | A dict of keyword arguments that aren't bound|
+      |                        | to any other parameter. This corresponds to a|
+      |                        | ``**kwargs`` parameter in a Python function  |
+      |                        | definition.                                  |
+      +------------------------+----------------------------------------------+
+
+      Example: print all keyword-only arguments without default values::
+
+         >>> def foo(a, b, *, c, d=10):
+         ...     pass
+
+         >>> sig = signature(foo)
+         >>> for param in sig.parameters.values():
+         ...     if (param.kind == param.KEYWORD_ONLY and
+         ...                        param.default is param.empty):
+         ...         print('Parameter:', param)
+         Parameter: c
+
+   .. method:: Parameter.replace(*[, name][, kind][, default][, annotation])
+
+      Create a new Parameter instance based on the instance replaced was invoked
+      on.  To override a :class:`Parameter` attribute, pass the corresponding
+      argument.  To remove a default value or/and an annotation from a
+      Parameter, pass :attr:`Parameter.empty`.
+
+      ::
+
+         >>> from inspect import Parameter
+         >>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42)
+         >>> str(param)
+         'foo=42'
+
+         >>> str(param.replace()) # Will create a shallow copy of 'param'
+         'foo=42'
+
+         >>> str(param.replace(default=Parameter.empty, annotation='spam'))
+         "foo:'spam'"
+
+
+.. class:: BoundArguments
+
+   Result of a :meth:`Signature.bind` or :meth:`Signature.bind_partial` call.
+   Holds the mapping of arguments to the function's parameters.
+
+   .. attribute:: BoundArguments.arguments
+
+      An ordered, mutable mapping (:class:`collections.OrderedDict`) of
+      parameters' names to arguments' values.  Contains only explicitly bound
+      arguments.  Changes in :attr:`arguments` will reflect in :attr:`args` and
+      :attr:`kwargs`.
+
+      Should be used in conjunction with :attr:`Signature.parameters` for any
+      argument processing purposes.
+
+      .. note::
+
+         Arguments for which :meth:`Signature.bind` or
+         :meth:`Signature.bind_partial` relied on a default value are skipped.
+         However, if needed, it is easy to include them.
+
+      ::
+
+        >>> def foo(a, b=10):
+        ...     pass
+
+        >>> sig = signature(foo)
+        >>> ba = sig.bind(5)
+
+        >>> ba.args, ba.kwargs
+        ((5,), {})
+
+        >>> for param in sig.parameters.values():
+        ...     if param.name not in ba.arguments:
+        ...         ba.arguments[param.name] = param.default
+
+        >>> ba.args, ba.kwargs
+        ((5, 10), {})
+
+
+   .. attribute:: BoundArguments.args
+
+      A tuple of positional arguments values.  Dynamically computed from the
+      :attr:`arguments` attribute.
+
+   .. attribute:: BoundArguments.kwargs
+
+      A dict of keyword arguments values.  Dynamically computed from the
+      :attr:`arguments` attribute.
+
+   The :attr:`args` and :attr:`kwargs` properties can be used to invoke
+   functions::
+
+      def test(a, *, b):
+         ...
+
+      sig = signature(test)
+      ba = sig.bind(10, b=20)
+      test(*ba.args, **ba.kwargs)
+
+
+.. seealso::
+
+   :pep:`362` - Function Signature Object.
+      The detailed specification, implementation details and examples.
+
+Copyright
+---------
+
+*funcsigs* is a derived work of CPython under the terms of the `PSF License
+Agreement`_. The original CPython inspect module, its unit tests and
+documentation are the copyright of the Python Software Foundation. The derived
+work is distributed under the `Apache License Version 2.0`_.
+
+.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
+.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0
diff --git a/tools/third_party/funcsigs/funcsigs/__init__.py b/tools/third_party/funcsigs/funcsigs/__init__.py
new file mode 100644
index 0000000..fd2f47b
--- /dev/null
+++ b/tools/third_party/funcsigs/funcsigs/__init__.py
@@ -0,0 +1,818 @@
+# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
+"""Function signature objects for callables
+
+Back port of Python 3.3's function signature tools from the inspect module,
+modified to be compatible with Python 2.6, 2.7 and 3.2+.
+"""
+from __future__ import absolute_import, division, print_function
+import itertools
+import functools
+import re
+import types
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    from funcsigs.odict import OrderedDict
+
+from funcsigs.version import __version__
+
+__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
+
+
+_WrapperDescriptor = type(type.__call__)
+_MethodWrapper = type(all.__call__)
+
+_NonUserDefinedCallables = (_WrapperDescriptor,
+                            _MethodWrapper,
+                            types.BuiltinFunctionType)
+
+
+def formatannotation(annotation, base_module=None):
+    if isinstance(annotation, type):
+        if annotation.__module__ in ('builtins', '__builtin__', base_module):
+            return annotation.__name__
+        return annotation.__module__+'.'+annotation.__name__
+    return repr(annotation)
+
+
+def _get_user_defined_method(cls, method_name, *nested):
+    try:
+        if cls is type:
+            return
+        meth = getattr(cls, method_name)
+        for name in nested:
+            meth = getattr(meth, name, meth)
+    except AttributeError:
+        return
+    else:
+        if not isinstance(meth, _NonUserDefinedCallables):
+            # Once '__signature__' will be added to 'C'-level
+            # callables, this check won't be necessary
+            return meth
+
+
+def signature(obj):
+    '''Get a signature object for the passed callable.'''
+
+    if not callable(obj):
+        raise TypeError('{0!r} is not a callable object'.format(obj))
+
+    if isinstance(obj, types.MethodType):
+        sig = signature(obj.__func__)
+        if obj.__self__ is None:
+            # Unbound method: the first parameter becomes positional-only
+            if sig.parameters:
+                first = sig.parameters.values()[0].replace(
+                    kind=_POSITIONAL_ONLY)
+                return sig.replace(
+                    parameters=(first,) + tuple(sig.parameters.values())[1:])
+            else:
+                return sig
+        else:
+            # In this case we skip the first parameter of the underlying
+            # function (usually `self` or `cls`).
+            return sig.replace(parameters=tuple(sig.parameters.values())[1:])
+
+    try:
+        sig = obj.__signature__
+    except AttributeError:
+        pass
+    else:
+        if sig is not None:
+            return sig
+
+    try:
+        # Was this function wrapped by a decorator?
+        wrapped = obj.__wrapped__
+    except AttributeError:
+        pass
+    else:
+        return signature(wrapped)
+
+    if isinstance(obj, types.FunctionType):
+        return Signature.from_function(obj)
+
+    if isinstance(obj, functools.partial):
+        sig = signature(obj.func)
+
+        new_params = OrderedDict(sig.parameters.items())
+
+        partial_args = obj.args or ()
+        partial_keywords = obj.keywords or {}
+        try:
+            ba = sig.bind_partial(*partial_args, **partial_keywords)
+        except TypeError as ex:
+            msg = 'partial object {0!r} has incorrect arguments'.format(obj)
+            raise ValueError(msg)
+
+        for arg_name, arg_value in ba.arguments.items():
+            param = new_params[arg_name]
+            if arg_name in partial_keywords:
+                # We set a new default value, because the following code
+                # is correct:
+                #
+                #   >>> def foo(a): print(a)
+                #   >>> print(partial(partial(foo, a=10), a=20)())
+                #   20
+                #   >>> print(partial(partial(foo, a=10), a=20)(a=30))
+                #   30
+                #
+                # So, with 'partial' objects, passing a keyword argument is
+                # like setting a new default value for the corresponding
+                # parameter
+                #
+                # We also mark this parameter with '_partial_kwarg'
+                # flag.  Later, in '_bind', the 'default' value of this
+                # parameter will be added to 'kwargs', to simulate
+                # the 'functools.partial' real call.
+                new_params[arg_name] = param.replace(default=arg_value,
+                                                     _partial_kwarg=True)
+
+            elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
+                            not param._partial_kwarg):
+                new_params.pop(arg_name)
+
+        return sig.replace(parameters=new_params.values())
+
+    sig = None
+    if isinstance(obj, type):
+        # obj is a class or a metaclass
+
+        # First, let's see if it has an overloaded __call__ defined
+        # in its metaclass
+        call = _get_user_defined_method(type(obj), '__call__')
+        if call is not None:
+            sig = signature(call)
+        else:
+            # Now we check if the 'obj' class has a '__new__' method
+            new = _get_user_defined_method(obj, '__new__')
+            if new is not None:
+                sig = signature(new)
+            else:
+                # Finally, we should have at least __init__ implemented
+                init = _get_user_defined_method(obj, '__init__')
+                if init is not None:
+                    sig = signature(init)
+    elif not isinstance(obj, _NonUserDefinedCallables):
+        # An object with __call__
+        # We also check that the 'obj' is not an instance of
+        # _WrapperDescriptor or _MethodWrapper to avoid
+        # infinite recursion (and even potential segfault)
+        call = _get_user_defined_method(type(obj), '__call__', 'im_func')
+        if call is not None:
+            sig = signature(call)
+
+    if sig is not None:
+        # For classes and objects we skip the first parameter of their
+        # __call__, __new__, or __init__ methods
+        return sig.replace(parameters=tuple(sig.parameters.values())[1:])
+
+    if isinstance(obj, types.BuiltinFunctionType):
+        # Raise a nicer error message for builtins
+        msg = 'no signature found for builtin function {0!r}'.format(obj)
+        raise ValueError(msg)
+
+    raise ValueError('callable {0!r} is not supported by signature'.format(obj))
+
+
+class _void(object):
+    '''A private marker - used in Parameter & Signature'''
+
+
+class _empty(object):
+    pass
+
+
+class _ParameterKind(int):
+    def __new__(self, *args, **kwargs):
+        obj = int.__new__(self, *args)
+        obj._name = kwargs['name']
+        return obj
+
+    def __str__(self):
+        return self._name
+
+    def __repr__(self):
+        return '<_ParameterKind: {0!r}>'.format(self._name)
+
+
+_POSITIONAL_ONLY        = _ParameterKind(0, name='POSITIONAL_ONLY')
+_POSITIONAL_OR_KEYWORD  = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
+_VAR_POSITIONAL         = _ParameterKind(2, name='VAR_POSITIONAL')
+_KEYWORD_ONLY           = _ParameterKind(3, name='KEYWORD_ONLY')
+_VAR_KEYWORD            = _ParameterKind(4, name='VAR_KEYWORD')
+
+
+class Parameter(object):
+    '''Represents a parameter in a function signature.
+
+    Has the following public attributes:
+
+    * name : str
+        The name of the parameter as a string.
+    * default : object
+        The default value for the parameter if specified.  If the
+        parameter has no default value, this attribute is not set.
+    * annotation
+        The annotation for the parameter if specified.  If the
+        parameter has no annotation, this attribute is not set.
+    * kind : str
+        Describes how argument values are bound to the parameter.
+        Possible values: `Parameter.POSITIONAL_ONLY`,
+        `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
+        `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
+    '''
+
+    __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
+
+    POSITIONAL_ONLY         = _POSITIONAL_ONLY
+    POSITIONAL_OR_KEYWORD   = _POSITIONAL_OR_KEYWORD
+    VAR_POSITIONAL          = _VAR_POSITIONAL
+    KEYWORD_ONLY            = _KEYWORD_ONLY
+    VAR_KEYWORD             = _VAR_KEYWORD
+
+    empty = _empty
+
+    def __init__(self, name, kind, default=_empty, annotation=_empty,
+                 _partial_kwarg=False):
+
+        if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
+                        _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
+            raise ValueError("invalid value for 'Parameter.kind' attribute")
+        self._kind = kind
+
+        if default is not _empty:
+            if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
+                msg = '{0} parameters cannot have default values'.format(kind)
+                raise ValueError(msg)
+        self._default = default
+        self._annotation = annotation
+
+        if name is None:
+            if kind != _POSITIONAL_ONLY:
+                raise ValueError("None is not a valid name for a "
+                                 "non-positional-only parameter")
+            self._name = name
+        else:
+            name = str(name)
+            if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
+                msg = '{0!r} is not a valid parameter name'.format(name)
+                raise ValueError(msg)
+            self._name = name
+
+        self._partial_kwarg = _partial_kwarg
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def default(self):
+        return self._default
+
+    @property
+    def annotation(self):
+        return self._annotation
+
+    @property
+    def kind(self):
+        return self._kind
+
+    def replace(self, name=_void, kind=_void, annotation=_void,
+                default=_void, _partial_kwarg=_void):
+        '''Creates a customized copy of the Parameter.'''
+
+        if name is _void:
+            name = self._name
+
+        if kind is _void:
+            kind = self._kind
+
+        if annotation is _void:
+            annotation = self._annotation
+
+        if default is _void:
+            default = self._default
+
+        if _partial_kwarg is _void:
+            _partial_kwarg = self._partial_kwarg
+
+        return type(self)(name, kind, default=default, annotation=annotation,
+                          _partial_kwarg=_partial_kwarg)
+
+    def __str__(self):
+        kind = self.kind
+
+        formatted = self._name
+        if kind == _POSITIONAL_ONLY:
+            if formatted is None:
+                formatted = ''
+            formatted = '<{0}>'.format(formatted)
+
+        # Add annotation and default value
+        if self._annotation is not _empty:
+            formatted = '{0}:{1}'.format(formatted,
+                                       formatannotation(self._annotation))
+
+        if self._default is not _empty:
+            formatted = '{0}={1}'.format(formatted, repr(self._default))
+
+        if kind == _VAR_POSITIONAL:
+            formatted = '*' + formatted
+        elif kind == _VAR_KEYWORD:
+            formatted = '**' + formatted
+
+        return formatted
+
+    def __repr__(self):
+        return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
+                                           id(self), self.name)
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        return (issubclass(other.__class__, Parameter) and
+                self._name == other._name and
+                self._kind == other._kind and
+                self._default == other._default and
+                self._annotation == other._annotation)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class BoundArguments(object):
+    '''Result of `Signature.bind` call.  Holds the mapping of arguments
+    to the function's parameters.
+
+    Has the following public attributes:
+
+    * arguments : OrderedDict
+        An ordered mutable mapping of parameters' names to arguments' values.
+        Does not contain arguments' default values.
+    * signature : Signature
+        The Signature object that created this instance.
+    * args : tuple
+        Tuple of positional arguments values.
+    * kwargs : dict
+        Dict of keyword arguments values.
+    '''
+
+    def __init__(self, signature, arguments):
+        self.arguments = arguments
+        self._signature = signature
+
+    @property
+    def signature(self):
+        return self._signature
+
+    @property
+    def args(self):
+        args = []
+        for param_name, param in self._signature.parameters.items():
+            if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
+                                                    param._partial_kwarg):
+                # Keyword arguments mapped by 'functools.partial'
+                # (Parameter._partial_kwarg is True) are mapped
+                # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
+                # KEYWORD_ONLY
+                break
+
+            try:
+                arg = self.arguments[param_name]
+            except KeyError:
+                # We're done here. Other arguments
+                # will be mapped in 'BoundArguments.kwargs'
+                break
+            else:
+                if param.kind == _VAR_POSITIONAL:
+                    # *args
+                    args.extend(arg)
+                else:
+                    # plain argument
+                    args.append(arg)
+
+        return tuple(args)
+
+    @property
+    def kwargs(self):
+        kwargs = {}
+        kwargs_started = False
+        for param_name, param in self._signature.parameters.items():
+            if not kwargs_started:
+                if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
+                                                param._partial_kwarg):
+                    kwargs_started = True
+                else:
+                    if param_name not in self.arguments:
+                        kwargs_started = True
+                        continue
+
+            if not kwargs_started:
+                continue
+
+            try:
+                arg = self.arguments[param_name]
+            except KeyError:
+                pass
+            else:
+                if param.kind == _VAR_KEYWORD:
+                    # **kwargs
+                    kwargs.update(arg)
+                else:
+                    # plain keyword argument
+                    kwargs[param_name] = arg
+
+        return kwargs
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        return (issubclass(other.__class__, BoundArguments) and
+                self.signature == other.signature and
+                self.arguments == other.arguments)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class Signature(object):
+    '''A Signature object represents the overall signature of a function.
+    It stores a Parameter object for each parameter accepted by the
+    function, as well as information specific to the function itself.
+
+    A Signature object has the following public attributes and methods:
+
+    * parameters : OrderedDict
+        An ordered mapping of parameters' names to the corresponding
+        Parameter objects (keyword-only arguments are in the same order
+        as listed in `code.co_varnames`).
+    * return_annotation : object
+        The annotation for the return type of the function if specified.
+        If the function has no annotation for its return type, this
+        attribute is not set.
+    * bind(*args, **kwargs) -> BoundArguments
+        Creates a mapping from positional and keyword arguments to
+        parameters.
+    * bind_partial(*args, **kwargs) -> BoundArguments
+        Creates a partial mapping from positional and keyword arguments
+        to parameters (simulating 'functools.partial' behavior.)
+    '''
+
+    __slots__ = ('_return_annotation', '_parameters')
+
+    _parameter_cls = Parameter
+    _bound_arguments_cls = BoundArguments
+
+    empty = _empty
+
+    def __init__(self, parameters=None, return_annotation=_empty,
+                 __validate_parameters__=True):
+        '''Constructs Signature from the given list of Parameter
+        objects and 'return_annotation'.  All arguments are optional.
+        '''
+
+        if parameters is None:
+            params = OrderedDict()
+        else:
+            if __validate_parameters__:
+                params = OrderedDict()
+                top_kind = _POSITIONAL_ONLY
+
+                for idx, param in enumerate(parameters):
+                    kind = param.kind
+                    if kind < top_kind:
+                        msg = 'wrong parameter order: {0} before {1}'
+                        msg = msg.format(top_kind, param.kind)
+                        raise ValueError(msg)
+                    else:
+                        top_kind = kind
+
+                    name = param.name
+                    if name is None:
+                        name = str(idx)
+                        param = param.replace(name=name)
+
+                    if name in params:
+                        msg = 'duplicate parameter name: {0!r}'.format(name)
+                        raise ValueError(msg)
+                    params[name] = param
+            else:
+                params = OrderedDict(((param.name, param)
+                                                for param in parameters))
+
+        self._parameters = params
+        self._return_annotation = return_annotation
+
+    @classmethod
+    def from_function(cls, func):
+        '''Constructs Signature for the given python function'''
+
+        if not isinstance(func, types.FunctionType):
+            raise TypeError('{0!r} is not a Python function'.format(func))
+
+        Parameter = cls._parameter_cls
+
+        # Parameter information.
+        func_code = func.__code__
+        pos_count = func_code.co_argcount
+        arg_names = func_code.co_varnames
+        positional = tuple(arg_names[:pos_count])
+        keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
+        keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
+        annotations = getattr(func, '__annotations__', {})
+        defaults = func.__defaults__
+        kwdefaults = getattr(func, '__kwdefaults__', None)
+
+        if defaults:
+            pos_default_count = len(defaults)
+        else:
+            pos_default_count = 0
+
+        parameters = []
+
+        # Non-keyword-only parameters w/o defaults.
+        non_default_count = pos_count - pos_default_count
+        for name in positional[:non_default_count]:
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_POSITIONAL_OR_KEYWORD))
+
+        # ... w/ defaults.
+        for offset, name in enumerate(positional[non_default_count:]):
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_POSITIONAL_OR_KEYWORD,
+                                        default=defaults[offset]))
+
+        # *args
+        if func_code.co_flags & 0x04:
+            name = arg_names[pos_count + keyword_only_count]
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_VAR_POSITIONAL))
+
+        # Keyword-only parameters.
+        for name in keyword_only:
+            default = _empty
+            if kwdefaults is not None:
+                default = kwdefaults.get(name, _empty)
+
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_KEYWORD_ONLY,
+                                        default=default))
+        # **kwargs
+        if func_code.co_flags & 0x08:
+            index = pos_count + keyword_only_count
+            if func_code.co_flags & 0x04:
+                index += 1
+
+            name = arg_names[index]
+            annotation = annotations.get(name, _empty)
+            parameters.append(Parameter(name, annotation=annotation,
+                                        kind=_VAR_KEYWORD))
+
+        return cls(parameters,
+                   return_annotation=annotations.get('return', _empty),
+                   __validate_parameters__=False)
+
+    @property
+    def parameters(self):
+        try:
+            return types.MappingProxyType(self._parameters)
+        except AttributeError:
+            return OrderedDict(self._parameters.items())
+
+    @property
+    def return_annotation(self):
+        return self._return_annotation
+
+    def replace(self, parameters=_void, return_annotation=_void):
+        '''Creates a customized copy of the Signature.
+        Pass 'parameters' and/or 'return_annotation' arguments
+        to override them in the new copy.
+        '''
+
+        if parameters is _void:
+            parameters = self.parameters.values()
+
+        if return_annotation is _void:
+            return_annotation = self._return_annotation
+
+        return type(self)(parameters,
+                          return_annotation=return_annotation)
+
+    def __hash__(self):
+        msg = "unhashable type: '{0}'".format(self.__class__.__name__)
+        raise TypeError(msg)
+
+    def __eq__(self, other):
+        if (not issubclass(type(other), Signature) or
+                    self.return_annotation != other.return_annotation or
+                    len(self.parameters) != len(other.parameters)):
+            return False
+
+        other_positions = dict((param, idx)
+                           for idx, param in enumerate(other.parameters.keys()))
+
+        for idx, (param_name, param) in enumerate(self.parameters.items()):
+            if param.kind == _KEYWORD_ONLY:
+                try:
+                    other_param = other.parameters[param_name]
+                except KeyError:
+                    return False
+                else:
+                    if param != other_param:
+                        return False
+            else:
+                try:
+                    other_idx = other_positions[param_name]
+                except KeyError:
+                    return False
+                else:
+                    if (idx != other_idx or
+                                    param != other.parameters[param_name]):
+                        return False
+
+        return True
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def _bind(self, args, kwargs, partial=False):
+        '''Private method.  Don't use directly.'''
+
+        arguments = OrderedDict()
+
+        parameters = iter(self.parameters.values())
+        parameters_ex = ()
+        arg_vals = iter(args)
+
+        if partial:
+            # Support for binding arguments to 'functools.partial' objects.
+            # See 'functools.partial' case in 'signature()' implementation
+            # for details.
+            for param_name, param in self.parameters.items():
+                if (param._partial_kwarg and param_name not in kwargs):
+                    # Simulating 'functools.partial' behavior
+                    kwargs[param_name] = param.default
+
+        while True:
+            # Let's iterate through the positional arguments and corresponding
+            # parameters
+            try:
+                arg_val = next(arg_vals)
+            except StopIteration:
+                # No more positional arguments
+                try:
+                    param = next(parameters)
+                except StopIteration:
+                    # No more parameters. That's it. Just need to check that
+                    # we have no `kwargs` after this while loop
+                    break
+                else:
+                    if param.kind == _VAR_POSITIONAL:
+                        # That's OK, just empty *args.  Let's start parsing
+                        # kwargs
+                        break
+                    elif param.name in kwargs:
+                        if param.kind == _POSITIONAL_ONLY:
+                            msg = '{arg!r} parameter is positional only, ' \
+                                  'but was passed as a keyword'
+                            msg = msg.format(arg=param.name)
+                            raise TypeError(msg)
+                        parameters_ex = (param,)
+                        break
+                    elif (param.kind == _VAR_KEYWORD or
+                                                param.default is not _empty):
+                        # That's fine too - we have a default value for this
+                        # parameter.  So, lets start parsing `kwargs`, starting
+                        # with the current parameter
+                        parameters_ex = (param,)
+                        break
+                    else:
+                        if partial:
+                            parameters_ex = (param,)
+                            break
+                        else:
+                            msg = '{arg!r} parameter lacking default value'
+                            msg = msg.format(arg=param.name)
+                            raise TypeError(msg)
+            else:
+                # We have a positional argument to process
+                try:
+                    param = next(parameters)
+                except StopIteration:
+                    raise TypeError('too many positional arguments')
+                else:
+                    if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
+                        # Looks like we have no parameter for this positional
+                        # argument
+                        raise TypeError('too many positional arguments')
+
+                    if param.kind == _VAR_POSITIONAL:
+                        # We have an '*args'-like argument, let's fill it with
+                        # all positional arguments we have left and move on to
+                        # the next phase
+                        values = [arg_val]
+                        values.extend(arg_vals)
+                        arguments[param.name] = tuple(values)
+                        break
+
+                    if param.name in kwargs:
+                        raise TypeError('multiple values for argument '
+                                        '{arg!r}'.format(arg=param.name))
+
+                    arguments[param.name] = arg_val
+
+        # Now, we iterate through the remaining parameters to process
+        # keyword arguments
+        kwargs_param = None
+        for param in itertools.chain(parameters_ex, parameters):
+            if param.kind == _POSITIONAL_ONLY:
+                # This should never happen in case of a properly built
+                # Signature object (but let's have this check here
+                # to ensure correct behaviour just in case)
+                raise TypeError('{arg!r} parameter is positional only, '
+                                'but was passed as a keyword'. \
+                                format(arg=param.name))
+
+            if param.kind == _VAR_KEYWORD:
+                # Memorize that we have a '**kwargs'-like parameter
+                kwargs_param = param
+                continue
+
+            param_name = param.name
+            try:
+                arg_val = kwargs.pop(param_name)
+            except KeyError:
+                # We have no value for this parameter.  It's fine though,
+                # if it has a default value, or it is an '*args'-like
+                # parameter, left alone by the processing of positional
+                # arguments.
+                if (not partial and param.kind != _VAR_POSITIONAL and
+                                                    param.default is _empty):
+                    raise TypeError('{arg!r} parameter lacking default value'. \
+                                    format(arg=param_name))
+
+            else:
+                arguments[param_name] = arg_val
+
+        if kwargs:
+            if kwargs_param is not None:
+                # Process our '**kwargs'-like parameter
+                arguments[kwargs_param.name] = kwargs
+            else:
+                raise TypeError('too many keyword arguments')
+
+        return self._bound_arguments_cls(self, arguments)
+
+    def bind(self, *args, **kwargs):
+        '''Get a BoundArguments object, that maps the passed `args`
+        and `kwargs` to the function's signature.  Raises `TypeError`
+        if the passed arguments can not be bound.
+        '''
+        return self._bind(args, kwargs)
+
+    def bind_partial(self, *args, **kwargs):
+        '''Get a BoundArguments object, that partially maps the
+        passed `args` and `kwargs` to the function's signature.
+        Raises `TypeError` if the passed arguments can not be bound.
+        '''
+        return self._bind(args, kwargs, partial=True)
+
+    def __str__(self):
+        result = []
+        render_kw_only_separator = True
+        for idx, param in enumerate(self.parameters.values()):
+            formatted = str(param)
+
+            kind = param.kind
+            if kind == _VAR_POSITIONAL:
+                # OK, we have an '*args'-like parameter, so we won't need
+                # a '*' to separate keyword-only arguments
+                render_kw_only_separator = False
+            elif kind == _KEYWORD_ONLY and render_kw_only_separator:
+                # We have a keyword-only parameter to render and we haven't
+                # rendered an '*args'-like parameter before, so add a '*'
+                # separator to the parameters list ("foo(arg1, *, arg2)" case)
+                result.append('*')
+                # This condition should be only triggered once, so
+                # reset the flag
+                render_kw_only_separator = False
+
+            result.append(formatted)
+
+        rendered = '({0})'.format(', '.join(result))
+
+        if self.return_annotation is not _empty:
+            anno = formatannotation(self.return_annotation)
+            rendered += ' -> {0}'.format(anno)
+
+        return rendered
diff --git a/tools/third_party/funcsigs/funcsigs/odict.py b/tools/third_party/funcsigs/funcsigs/odict.py
new file mode 100644
index 0000000..6221e97
--- /dev/null
+++ b/tools/third_party/funcsigs/funcsigs/odict.py
@@ -0,0 +1,261 @@
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+# Copyright 2009 Raymond Hettinger
+# http://code.activestate.com/recipes/576693/
+"Ordered dictionary"
+
+try:
+    from thread import get_ident as _get_ident
+except ImportError:
+    from dummy_thread import get_ident as _get_ident
+
+try:
+    from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+    pass
+
+
+class OrderedDict(dict):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
+
+    def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the linked
+        # list, and the inherited dictionary is updated with the new key/value pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, key = self.__map.pop(key)
+        link_prev[1] = link_next
+        link_next[0] = link_prev
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        root = self.__root
+        curr = root[1]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[1]
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        root = self.__root
+        curr = root[0]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[0]
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        try:
+            for node in self.__map.itervalues():
+                del node[:]
+            root = self.__root
+            root[:] = [root, root, None]
+            self.__map.clear()
+        except AttributeError:
+            pass
+        dict.clear(self)
+
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
+        if not self:
+            raise KeyError('dictionary is empty')
+        root = self.__root
+        if last:
+            link = root[0]
+            link_prev = link[0]
+            link_prev[1] = root
+            root[0] = link_prev
+        else:
+            link = root[1]
+            link_next = link[1]
+            root[1] = link_next
+            link_next[0] = root
+        key = link[2]
+        del self.__map[key]
+        value = dict.pop(self, key)
+        return key, value
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        'od.keys() -> list of keys in od'
+        return list(self)
+
+    def values(self):
+        'od.values() -> list of values in od'
+        return [self[key] for key in self]
+
+    def items(self):
+        'od.items() -> list of (key, value) pairs in od'
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        'od.iterkeys() -> an iterator over the keys in od'
+        return iter(self)
+
+    def itervalues(self):
+        'od.itervalues -> an iterator over the values in od'
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        'od.iteritems -> an iterator over the (key, value) items in od'
+        for k in self:
+            yield (k, self[k])
+
+    def update(*args, **kwds):
+        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
+
+        If E is a dict instance, does:           for k in E: od[k] = E[k]
+        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
+        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
+        In either case, this is followed by:     for k, v in F.items(): od[k] = v
+
+        '''
+        if len(args) > 2:
+            raise TypeError('update() takes at most 2 positional '
+                            'arguments (%d given)' % (len(args),))
+        elif not args:
+            raise TypeError('update() takes at least 1 argument (0 given)')
+        self = args[0]
+        # Make progressively weaker assumptions about "other"
+        other = ()
+        if len(args) == 2:
+            other = args[1]
+        if isinstance(other, dict):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, 'keys'):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    __update = update  # let subclasses override update without breaking __init__
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised.
+
+        '''
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return len(self)==len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # -- the following methods are only used in Python 2.7 --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
diff --git a/tools/third_party/funcsigs/funcsigs/version.py b/tools/third_party/funcsigs/funcsigs/version.py
new file mode 100644
index 0000000..896a370
--- /dev/null
+++ b/tools/third_party/funcsigs/funcsigs/version.py
@@ -0,0 +1 @@
+__version__ = "0.4"
diff --git a/tools/third_party/funcsigs/requirements/development.txt b/tools/third_party/funcsigs/requirements/development.txt
new file mode 100644
index 0000000..ecafb0a
--- /dev/null
+++ b/tools/third_party/funcsigs/requirements/development.txt
@@ -0,0 +1,6 @@
+coverage
+coveralls
+pip
+flake8
+sphinx
+wheel
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/tools/third_party/funcsigs/requirements/production.txt
similarity index 100%
copy from tools/pytest/_pytest/vendored_packages/__init__.py
copy to tools/third_party/funcsigs/requirements/production.txt
diff --git a/tools/third_party/funcsigs/setup.cfg b/tools/third_party/funcsigs/setup.cfg
new file mode 100644
index 0000000..5e40900
--- /dev/null
+++ b/tools/third_party/funcsigs/setup.cfg
@@ -0,0 +1,2 @@
+[wheel]
+universal = 1
diff --git a/tools/third_party/funcsigs/setup.py b/tools/third_party/funcsigs/setup.py
new file mode 100644
index 0000000..98b0912
--- /dev/null
+++ b/tools/third_party/funcsigs/setup.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+from setuptools import setup
+import re
+import sys
+
+def load_version(filename='funcsigs/version.py'):
+    "Parse a __version__ number from a source file"
+    with open(filename) as source:
+        text = source.read()
+        match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", text)
+        if not match:
+            msg = "Unable to find version number in {}".format(filename)
+            raise RuntimeError(msg)
+        version = match.group(1)
+        return version
+
+def load_rst(filename='docs/source/guide_content.rst'):
+    "Purge refs directives from restructured text"
+    with open(filename) as source:
+        text = source.read()
+        doc = re.sub(r':\w+:`~?([a-zA-Z._()]+)`', r'*\1*', text)
+        return doc
+
+setup(
+    name="funcsigs",
+    version=load_version(),
+    packages=['funcsigs'],
+    zip_safe=False,
+    author="Aaron Iles",
+    author_email="aaron.iles@gmail.com",
+    url="http://funcsigs.readthedocs.org",
+    description="Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+",
+    long_description=open('README.rst').read(),
+    # long_description=load_rst(),
+    license="ASL",
+    install_requires = [],
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Apache Software License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: Implementation :: CPython',
+        'Programming Language :: Python :: Implementation :: PyPy',
+        'Topic :: Software Development :: Libraries :: Python Modules'
+    ],
+    tests_require = [] if sys.version_info[0] > 2 else ['unittest2'],
+    test_suite = "tests" if sys.version_info[0] > 2 else 'unittest2.collector'
+)
diff --git a/tools/py/testing/log/__init__.py b/tools/third_party/funcsigs/tests/__init__.py
similarity index 100%
copy from tools/py/testing/log/__init__.py
copy to tools/third_party/funcsigs/tests/__init__.py
diff --git a/tools/third_party/funcsigs/tests/test_formatannotation.py b/tools/third_party/funcsigs/tests/test_formatannotation.py
new file mode 100644
index 0000000..fd7a887
--- /dev/null
+++ b/tools/third_party/funcsigs/tests/test_formatannotation.py
@@ -0,0 +1,27 @@
+try:
+    # python 2.x
+    import unittest2 as unittest
+except ImportError:
+    # python 3.x
+    import unittest
+
+import funcsigs
+
+
+class TestFormatAnnotation(unittest.TestCase):
+    def test_string (self):
+        self.assertEqual(funcsigs.formatannotation("annotation"),
+                         "'annotation'")
+
+    def test_builtin_type (self):
+        self.assertEqual(funcsigs.formatannotation(int),
+                         "int")
+
+    def test_user_type (self):
+        class dummy (object): pass
+        self.assertEqual(funcsigs.formatannotation(dummy),
+                         "tests.test_formatannotation.dummy")
+
+
+if __name__ == "__main__":
+    unittest.begin()
diff --git a/tools/third_party/funcsigs/tests/test_funcsigs.py b/tools/third_party/funcsigs/tests/test_funcsigs.py
new file mode 100644
index 0000000..eecc0a8
--- /dev/null
+++ b/tools/third_party/funcsigs/tests/test_funcsigs.py
@@ -0,0 +1,93 @@
+try:
+    # python 2.x
+    import unittest2 as unittest
+except ImportError:
+    # python 3.x
+    import unittest
+
+import doctest
+import sys
+
+import funcsigs as inspect
+
+
+class TestFunctionSignatures(unittest.TestCase):
+
+    @staticmethod
+    def signature(func):
+        sig = inspect.signature(func)
+        return (tuple((param.name,
+                       (Ellipsis if param.default is param.empty else param.default),
+                       (Ellipsis if param.annotation is param.empty
+                                                        else param.annotation),
+                       str(param.kind).lower())
+                                    for param in sig.parameters.values()),
+                (Ellipsis if sig.return_annotation is sig.empty
+                                            else sig.return_annotation))
+
+    def test_zero_arguments(self):
+        def test():
+            pass
+        self.assertEqual(self.signature(test),
+                ((), Ellipsis))
+
+    def test_single_positional_argument(self):
+        def test(a):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('a', Ellipsis, Ellipsis, "positional_or_keyword")),), Ellipsis))
+
+    def test_single_keyword_argument(self):
+        def test(a=None):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('a', None, Ellipsis, "positional_or_keyword")),), Ellipsis))
+
+    def test_var_args(self):
+        def test(*args):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('args', Ellipsis, Ellipsis, "var_positional")),), Ellipsis))
+
+    def test_keywords_args(self):
+        def test(**kwargs):
+            pass
+        self.assertEqual(self.signature(test),
+                (((('kwargs', Ellipsis, Ellipsis, "var_keyword")),), Ellipsis))
+
+    def test_multiple_arguments(self):
+        def test(a, b=None, *args, **kwargs):
+            pass
+        self.assertEqual(self.signature(test), ((
+            ('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+            ('b', None, Ellipsis, "positional_or_keyword"),
+            ('args', Ellipsis, Ellipsis, "var_positional"),
+            ('kwargs', Ellipsis, Ellipsis, "var_keyword"),
+            ), Ellipsis))
+
+    def test_has_version(self):
+        self.assertTrue(inspect.__version__)
+
+    def test_readme(self):
+        doctest.testfile('../README.rst')
+
+    def test_unbound_method(self):
+        if sys.version_info < (3,):
+            self_kind = "positional_only"
+        else:
+            self_kind = "positional_or_keyword"
+        class Test(object):
+            def method(self):
+                pass
+            def method_with_args(self, a):
+                pass
+        self.assertEqual(self.signature(Test.method),
+                (((('self', Ellipsis, Ellipsis, self_kind)),), Ellipsis))
+        self.assertEqual(self.signature(Test.method_with_args), ((
+                ('self', Ellipsis, Ellipsis, self_kind),
+                ('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                ), Ellipsis))
+
+
+if __name__ == "__main__":
+    unittest.begin()
diff --git a/tools/third_party/funcsigs/tests/test_inspect.py b/tools/third_party/funcsigs/tests/test_inspect.py
new file mode 100644
index 0000000..323c323
--- /dev/null
+++ b/tools/third_party/funcsigs/tests/test_inspect.py
@@ -0,0 +1,1019 @@
+# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
+from __future__ import absolute_import, division, print_function
+import collections
+import sys
+
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+
+import funcsigs as inspect
+
+
+class TestSignatureObject(unittest.TestCase):
+    @staticmethod
+    def signature(func):
+        sig = inspect.signature(func)
+        return (tuple((param.name,
+                       (Ellipsis if param.default is param.empty else param.default),
+                       (Ellipsis if param.annotation is param.empty
+                                                        else param.annotation),
+                       str(param.kind).lower())
+                                    for param in sig.parameters.values()),
+                (Ellipsis if sig.return_annotation is sig.empty
+                                            else sig.return_annotation))
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_object(self):
+    S = inspect.Signature
+    P = inspect.Parameter
+
+    self.assertEqual(str(S()), '()')
+
+    def test(po, pk, *args, ko, **kwargs):
+        pass
+    sig = inspect.signature(test)
+    po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY)
+    pk = sig.parameters['pk']
+    args = sig.parameters['args']
+    ko = sig.parameters['ko']
+    kwargs = sig.parameters['kwargs']
+
+    S((po, pk, args, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((pk, po, args, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((po, args, pk, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((args, po, pk, ko, kwargs))
+
+    with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
+        S((po, pk, args, kwargs, ko))
+
+    kwargs2 = kwargs.replace(name='args')
+    with self.assertRaisesRegex(ValueError, 'duplicate parameter name'):
+        S((po, pk, args, kwargs2, ko))
+""")
+
+    def test_signature_immutability(self):
+        def test(a):
+            pass
+        sig = inspect.signature(test)
+
+        with self.assertRaises(AttributeError):
+            sig.foo = 'bar'
+
+        # Python2 does not have MappingProxyType class
+        if sys.version_info[:2] < (3, 3):
+            return
+
+        with self.assertRaises(TypeError):
+            sig.parameters['a'] = None
+
+    def test_signature_on_noarg(self):
+        def test():
+            pass
+        self.assertEqual(self.signature(test), ((), Ellipsis))
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_wargs(self):
+    def test(a, b:'foo') -> 123:
+        pass
+    self.assertEqual(self.signature(test),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, 'foo', "positional_or_keyword")),
+                      123))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_wkwonly(self):
+    def test(*, a:float, b:str) -> int:
+        pass
+    self.assertEqual(self.signature(test),
+                     ((('a', Ellipsis, float, "keyword_only"),
+                       ('b', Ellipsis, str, "keyword_only")),
+                       int))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_complex_args(self):
+    def test(a, b:'foo'=10, *args:'bar', spam:'baz', ham=123, **kwargs:int):
+        pass
+    self.assertEqual(self.signature(test),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', 10, 'foo', "positional_or_keyword"),
+                       ('args', Ellipsis, 'bar', "var_positional"),
+                       ('spam', Ellipsis, 'baz', "keyword_only"),
+                       ('ham', 123, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, int, "var_keyword")),
+                      Ellipsis))
+""")
+
+    def test_signature_on_builtin_function(self):
+        with self.assertRaisesRegex(ValueError, 'not supported by signature'):
+            inspect.signature(type)
+        with self.assertRaisesRegex(ValueError, 'not supported by signature'):
+            # support for 'wrapper_descriptor'
+            inspect.signature(type.__call__)
+            if hasattr(sys, 'pypy_version_info'):
+                raise ValueError('not supported by signature')
+        with self.assertRaisesRegex(ValueError, 'not supported by signature'):
+            # support for 'method-wrapper'
+            inspect.signature(min.__call__)
+            if hasattr(sys, 'pypy_version_info'):
+                raise ValueError('not supported by signature')
+        with self.assertRaisesRegex(ValueError,
+                                     'no signature found for builtin function'):
+            # support for 'method-wrapper'
+            inspect.signature(min)
+
+    def test_signature_on_non_function(self):
+        with self.assertRaisesRegex(TypeError, 'is not a callable object'):
+            inspect.signature(42)
+
+        with self.assertRaisesRegex(TypeError, 'is not a Python function'):
+            inspect.Signature.from_function(42)
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_method(self):
+    class Test:
+        def foo(self, arg1, arg2=1) -> int:
+            pass
+
+    meth = Test().foo
+
+    self.assertEqual(self.signature(meth),
+                     ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg2', 1, Ellipsis, "positional_or_keyword")),
+                      int))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_classmethod(self):
+    class Test:
+        @classmethod
+        def foo(cls, arg1, *, arg2=1):
+            pass
+
+    meth = Test().foo
+    self.assertEqual(self.signature(meth),
+                     ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg2', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    meth = Test.foo
+    self.assertEqual(self.signature(meth),
+                     ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg2', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_staticmethod(self):
+    class Test:
+        @staticmethod
+        def foo(cls, *, arg):
+            pass
+
+    meth = Test().foo
+    self.assertEqual(self.signature(meth),
+                     ((('cls', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    meth = Test.foo
+    self.assertEqual(self.signature(meth),
+                     ((('cls', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('arg', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_partial(self):
+    from functools import partial
+
+    def test():
+        pass
+
+    self.assertEqual(self.signature(partial(test)), ((), Ellipsis))
+
+    with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
+        inspect.signature(partial(test, 1))
+
+    with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
+        inspect.signature(partial(test, a=1))
+
+    def test(a, b, *, c, d):
+        pass
+
+    self.assertEqual(self.signature(partial(test)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1)),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1, c=2)),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', 2, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, b=1, c=2)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', 1, Ellipsis, "positional_or_keyword"),
+                       ('c', 2, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 0, b=1, c=2)),
+                     ((('b', 1, Ellipsis, "positional_or_keyword"),
+                       ('c', 2, Ellipsis, "keyword_only"),
+                       ('d', Ellipsis, Ellipsis, "keyword_only"),),
+                      Ellipsis))
+
+    def test(a, *args, b, **kwargs):
+        pass
+
+    self.assertEqual(self.signature(partial(test, 1)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', Ellipsis, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1, 2, 3)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', Ellipsis, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+
+    self.assertEqual(self.signature(partial(test, 1, 2, 3, test=True)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', Ellipsis, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, 1, 2, 3, test=1, b=0)),
+                     ((('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', 0, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, b=0)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', 0, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(partial(test, b=0, test=1)),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('args', Ellipsis, Ellipsis, "var_positional"),
+                       ('b', 0, Ellipsis, "keyword_only"),
+                       ('kwargs', Ellipsis, Ellipsis, "var_keyword")),
+                      Ellipsis))
+
+    def test(a, b, c:int) -> 42:
+        pass
+
+    sig = test.__signature__ = inspect.signature(test)
+
+    self.assertEqual(self.signature(partial(partial(test, 1))),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, int, "positional_or_keyword")),
+                      42))
+
+    self.assertEqual(self.signature(partial(partial(test, 1), 2)),
+                     ((('c', Ellipsis, int, "positional_or_keyword"),),
+                      42))
+
+    psig = inspect.signature(partial(partial(test, 1), 2))
+
+    def foo(a):
+        return a
+    _foo = partial(partial(foo, a=10), a=20)
+    self.assertEqual(self.signature(_foo),
+                     ((('a', 20, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+    # check that we don't have any side-effects in signature(),
+    # and the partial object is still functioning
+    self.assertEqual(_foo(), 20)
+
+    def foo(a, b, c):
+        return a, b, c
+    _foo = partial(partial(foo, 1, b=20), b=30)
+    self.assertEqual(self.signature(_foo),
+                     ((('b', 30, Ellipsis, "positional_or_keyword"),
+                       ('c', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+    self.assertEqual(_foo(c=10), (1, 30, 10))
+    _foo = partial(_foo, 2) # now 'b' has two values -
+                            # positional and keyword
+    with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
+        inspect.signature(_foo)
+
+    def foo(a, b, c, *, d):
+        return a, b, c, d
+    _foo = partial(partial(foo, d=20, c=20), b=10, d=30)
+    self.assertEqual(self.signature(_foo),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', 10, Ellipsis, "positional_or_keyword"),
+                       ('c', 20, Ellipsis, "positional_or_keyword"),
+                       ('d', 30, Ellipsis, "keyword_only")),
+                      Ellipsis))
+    ba = inspect.signature(_foo).bind(a=200, b=11)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (200, 11, 20, 30))
+
+    def foo(a=1, b=2, c=3):
+        return a, b, c
+    _foo = partial(foo, a=10, c=13)
+    ba = inspect.signature(_foo).bind(11)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 2, 13))
+    ba = inspect.signature(_foo).bind(11, 12)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13))
+    ba = inspect.signature(_foo).bind(11, b=12)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13))
+    ba = inspect.signature(_foo).bind(b=12)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (10, 12, 13))
+    _foo = partial(_foo, b=10)
+    ba = inspect.signature(_foo).bind(12, 14)
+    self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 14, 13))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_decorated(self):
+    import functools
+
+    def decorator(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs) -> int:
+            return func(*args, **kwargs)
+        return wrapper
+
+    class Foo:
+        @decorator
+        def bar(self, a, b):
+            pass
+
+    self.assertEqual(self.signature(Foo.bar),
+                     ((('self', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(Foo().bar),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    # Test that we handle method wrappers correctly
+    def decorator(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs) -> int:
+            return func(42, *args, **kwargs)
+        sig = inspect.signature(func)
+        new_params = tuple(sig.parameters.values())[1:]
+        wrapper.__signature__ = sig.replace(parameters=new_params)
+        return wrapper
+
+    class Foo:
+        @decorator
+        def __call__(self, a, b):
+            pass
+
+    self.assertEqual(self.signature(Foo.__call__),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('b', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(Foo().__call__),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_on_class(self):
+    class C:
+        def __init__(self, a):
+            pass
+
+    self.assertEqual(self.signature(C),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    class CM(type):
+        def __call__(cls, a):
+            pass
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(C),
+                     ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    class CM(type):
+        def __new__(mcls, name, bases, dct, *, foo=1):
+            return super().__new__(mcls, name, bases, dct)
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(C),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(CM),
+                     ((('name', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bases', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dct', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('foo', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    class CMM(type):
+        def __new__(mcls, name, bases, dct, *, foo=1):
+            return super().__new__(mcls, name, bases, dct)
+        def __call__(cls, nm, bs, dt):
+            return type(nm, bs, dt)
+    class CM(type, metaclass=CMM):
+        def __new__(mcls, name, bases, dct, *, bar=2):
+            return super().__new__(mcls, name, bases, dct)
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(CMM),
+                     ((('name', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bases', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dct', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('foo', 1, Ellipsis, "keyword_only")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(CM),
+                     ((('nm', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bs', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dt', Ellipsis, Ellipsis, "positional_or_keyword")),
+                      Ellipsis))
+
+    self.assertEqual(self.signature(C),
+                     ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                      Ellipsis))
+
+    class CM(type):
+        def __init__(cls, name, bases, dct, *, bar=2):
+            return super().__init__(name, bases, dct)
+    class C(metaclass=CM):
+        def __init__(self, b):
+            pass
+
+    self.assertEqual(self.signature(CM),
+                     ((('name', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bases', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('dct', Ellipsis, Ellipsis, "positional_or_keyword"),
+                       ('bar', 2, Ellipsis, "keyword_only")),
+                      Ellipsis))
+""")
+
+    def test_signature_on_callable_objects(self):
+        class Foo(object):
+            def __call__(self, a):
+                pass
+
+        self.assertEqual(self.signature(Foo()),
+                         ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+        class Spam(object):
+            pass
+        with self.assertRaisesRegex(TypeError, "is not a callable object"):
+            inspect.signature(Spam())
+
+        class Bar(Spam, Foo):
+            pass
+
+        self.assertEqual(self.signature(Bar()),
+                         ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+        class ToFail(object):
+            __call__ = type
+        with self.assertRaisesRegex(ValueError, "not supported by signature"):
+            inspect.signature(ToFail())
+
+        if sys.version_info[0] < 3:
+            return
+
+        class Wrapped(object):
+            pass
+        Wrapped.__wrapped__ = lambda a: None
+        self.assertEqual(self.signature(Wrapped),
+                         ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+    def test_signature_on_lambdas(self):
+        self.assertEqual(self.signature((lambda a=10: a)),
+                         ((('a', 10, Ellipsis, "positional_or_keyword"),),
+                          Ellipsis))
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_equality(self):
+    def foo(a, *, b:int) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), 42)
+
+    def bar(a, *, b:int) -> float: pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, b:int) -> int: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, b:int): pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, b:int=42) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, *, c) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def bar(a, b:int) -> float: pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+    def spam(b:int, a) -> float: pass
+    self.assertNotEqual(inspect.signature(spam), inspect.signature(bar))
+
+    def foo(*, a, b, c): pass
+    def bar(*, c, b, a): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(*, a=1, b, c): pass
+    def bar(*, c, b, a=1): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(pos, *, a=1, b, c): pass
+    def bar(pos, *, c, b, a=1): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(pos, *, a, b, c): pass
+    def bar(pos, *, c, b, a=1): pass
+    self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+
+    def foo(pos, *args, a=42, b, c, **kwargs:int): pass
+    def bar(pos, *args, c, b, a=42, **kwargs:int): pass
+    self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+""")
+
+    def test_signature_unhashable(self):
+        def foo(a): pass
+        sig = inspect.signature(foo)
+        with self.assertRaisesRegex(TypeError, 'unhashable type'):
+            hash(sig)
+
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_str(self):
+    def foo(a:int=1, *, b, c=None, **kwargs) -> 42:
+        pass
+    self.assertEqual(str(inspect.signature(foo)),
+                     '(a:int=1, *, b, c=None, **kwargs) -> 42')
+
+    def foo(a:int=1, *args, b, c=None, **kwargs) -> 42:
+        pass
+    self.assertEqual(str(inspect.signature(foo)),
+                     '(a:int=1, *args, b, c=None, **kwargs) -> 42')
+
+    def foo():
+        pass
+    self.assertEqual(str(inspect.signature(foo)), '()')
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_str_positional_only(self):
+    P = inspect.Parameter
+
+    def test(a_po, *, b, **kwargs):
+        return a_po, kwargs
+
+    sig = inspect.signature(test)
+    new_params = list(sig.parameters.values())
+    new_params[0] = new_params[0].replace(kind=P.POSITIONAL_ONLY)
+    test.__signature__ = sig.replace(parameters=new_params)
+
+    self.assertEqual(str(inspect.signature(test)),
+                     '(<a_po>, *, b, **kwargs)')
+
+    sig = inspect.signature(test)
+    new_params = list(sig.parameters.values())
+    new_params[0] = new_params[0].replace(name=None)
+    test.__signature__ = sig.replace(parameters=new_params)
+    self.assertEqual(str(inspect.signature(test)),
+                     '(<0>, *, b, **kwargs)')
+""")
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_replace_anno(self):
+    def test() -> 42:
+        pass
+
+    sig = inspect.signature(test)
+    sig = sig.replace(return_annotation=None)
+    self.assertIs(sig.return_annotation, None)
+    sig = sig.replace(return_annotation=sig.empty)
+    self.assertIs(sig.return_annotation, sig.empty)
+    sig = sig.replace(return_annotation=42)
+    self.assertEqual(sig.return_annotation, 42)
+    self.assertEqual(sig, inspect.signature(test))
+""")
+
+
+class TestParameterObject(unittest.TestCase):
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    def test_signature_parameter_kinds(self):
+        P = inspect.Parameter
+        self.assertTrue(P.POSITIONAL_ONLY < P.POSITIONAL_OR_KEYWORD < \
+                        P.VAR_POSITIONAL < P.KEYWORD_ONLY < P.VAR_KEYWORD)
+
+        self.assertEqual(str(P.POSITIONAL_ONLY), 'POSITIONAL_ONLY')
+        self.assertTrue('POSITIONAL_ONLY' in repr(P.POSITIONAL_ONLY))
+
+    def test_signature_parameter_object(self):
+        p = inspect.Parameter('foo', default=10,
+                              kind=inspect.Parameter.POSITIONAL_ONLY)
+        self.assertEqual(p.name, 'foo')
+        self.assertEqual(p.default, 10)
+        self.assertIs(p.annotation, p.empty)
+        self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
+
+        with self.assertRaisesRegex(ValueError, 'invalid value'):
+            inspect.Parameter('foo', default=10, kind='123')
+
+        with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
+            inspect.Parameter('1', kind=inspect.Parameter.VAR_KEYWORD)
+
+        with self.assertRaisesRegex(ValueError,
+                                     'non-positional-only parameter'):
+            inspect.Parameter(None, kind=inspect.Parameter.VAR_KEYWORD)
+
+        with self.assertRaisesRegex(ValueError, 'cannot have default values'):
+            inspect.Parameter('a', default=42,
+                              kind=inspect.Parameter.VAR_KEYWORD)
+
+        with self.assertRaisesRegex(ValueError, 'cannot have default values'):
+            inspect.Parameter('a', default=42,
+                              kind=inspect.Parameter.VAR_POSITIONAL)
+
+        p = inspect.Parameter('a', default=42,
+                              kind=inspect.Parameter.POSITIONAL_OR_KEYWORD)
+        with self.assertRaisesRegex(ValueError, 'cannot have default values'):
+            p.replace(kind=inspect.Parameter.VAR_POSITIONAL)
+
+        self.assertTrue(repr(p).startswith('<Parameter'))
+
+    def test_signature_parameter_equality(self):
+        P = inspect.Parameter
+        p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY)
+
+        self.assertEqual(p, p)
+        self.assertNotEqual(p, 42)
+
+        self.assertEqual(p, P('foo', default=42,
+                              kind=inspect.Parameter.KEYWORD_ONLY))
+
+    def test_signature_parameter_unhashable(self):
+        p = inspect.Parameter('foo', default=42,
+                              kind=inspect.Parameter.KEYWORD_ONLY)
+
+        with self.assertRaisesRegex(TypeError, 'unhashable type'):
+            hash(p)
+
+    def test_signature_parameter_replace(self):
+        p = inspect.Parameter('foo', default=42,
+                              kind=inspect.Parameter.KEYWORD_ONLY)
+
+        self.assertIsNot(p, p.replace())
+        self.assertEqual(p, p.replace())
+
+        p2 = p.replace(annotation=1)
+        self.assertEqual(p2.annotation, 1)
+        p2 = p2.replace(annotation=p2.empty)
+        self.assertEqual(p, p2)
+
+        p2 = p2.replace(name='bar')
+        self.assertEqual(p2.name, 'bar')
+        self.assertNotEqual(p2, p)
+
+        with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
+            p2 = p2.replace(name=p2.empty)
+
+        p2 = p2.replace(name='foo', default=None)
+        self.assertIs(p2.default, None)
+        self.assertNotEqual(p2, p)
+
+        p2 = p2.replace(name='foo', default=p2.empty)
+        self.assertIs(p2.default, p2.empty)
+
+
+        p2 = p2.replace(default=42, kind=p2.POSITIONAL_OR_KEYWORD)
+        self.assertEqual(p2.kind, p2.POSITIONAL_OR_KEYWORD)
+        self.assertNotEqual(p2, p)
+
+        with self.assertRaisesRegex(ValueError, 'invalid value for'):
+            p2 = p2.replace(kind=p2.empty)
+
+        p2 = p2.replace(kind=p2.KEYWORD_ONLY)
+        self.assertEqual(p2, p)
+
+    def test_signature_parameter_positional_only(self):
+        p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
+        self.assertEqual(str(p), '<>')
+
+        p = p.replace(name='1')
+        self.assertEqual(str(p), '<1>')
+
+    def test_signature_parameter_immutability(self):
+        p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
+
+        with self.assertRaises(AttributeError):
+            p.foo = 'bar'
+
+        with self.assertRaises(AttributeError):
+            p.kind = 123
+
+
+class TestSignatureBind(unittest.TestCase):
+    @staticmethod
+    def call(func, *args, **kwargs):
+        sig = inspect.signature(func)
+        ba = sig.bind(*args, **kwargs)
+        return func(*ba.args, **ba.kwargs)
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    def test_signature_bind_empty(self):
+        def test():
+            return 42
+
+        self.assertEqual(self.call(test), 42)
+        with self.assertRaisesRegex(TypeError, 'too many positional arguments'):
+            self.call(test, 1)
+        with self.assertRaisesRegex(TypeError, 'too many positional arguments'):
+            self.call(test, 1, spam=10)
+        with self.assertRaisesRegex(TypeError, 'too many keyword arguments'):
+            self.call(test, spam=1)
+
+    def test_signature_bind_var(self):
+        def test(*args, **kwargs):
+            return args, kwargs
+
+        self.assertEqual(self.call(test), ((), {}))
+        self.assertEqual(self.call(test, 1), ((1,), {}))
+        self.assertEqual(self.call(test, 1, 2), ((1, 2), {}))
+        self.assertEqual(self.call(test, foo='bar'), ((), {'foo': 'bar'}))
+        self.assertEqual(self.call(test, 1, foo='bar'), ((1,), {'foo': 'bar'}))
+        self.assertEqual(self.call(test, args=10), ((), {'args': 10}))
+        self.assertEqual(self.call(test, 1, 2, foo='bar'),
+                         ((1, 2), {'foo': 'bar'}))
+
+    def test_signature_bind_just_args(self):
+        def test(a, b, c):
+            return a, b, c
+
+        self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3))
+
+        with self.assertRaisesRegex(TypeError, 'too many positional arguments'):
+            self.call(test, 1, 2, 3, 4)
+
+        with self.assertRaisesRegex(TypeError, "'b' parameter lacking default"):
+            self.call(test, 1)
+
+        with self.assertRaisesRegex(TypeError, "'a' parameter lacking default"):
+            self.call(test)
+
+        def test(a, b, c=10):
+            return a, b, c
+        self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3))
+        self.assertEqual(self.call(test, 1, 2), (1, 2, 10))
+
+        def test(a=1, b=2, c=3):
+            return a, b, c
+        self.assertEqual(self.call(test, a=10, c=13), (10, 2, 13))
+        self.assertEqual(self.call(test, a=10), (10, 2, 3))
+        self.assertEqual(self.call(test, b=10), (1, 10, 3))
+
+    def test_signature_bind_varargs_order(self):
+        def test(*args):
+            return args
+
+        self.assertEqual(self.call(test), ())
+        self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3))
+
+    def test_signature_bind_args_and_varargs(self):
+        def test(a, b, c=3, *args):
+            return a, b, c, args
+
+        self.assertEqual(self.call(test, 1, 2, 3, 4, 5), (1, 2, 3, (4, 5)))
+        self.assertEqual(self.call(test, 1, 2), (1, 2, 3, ()))
+        self.assertEqual(self.call(test, b=1, a=2), (2, 1, 3, ()))
+        self.assertEqual(self.call(test, 1, b=2), (1, 2, 3, ()))
+
+        with self.assertRaisesRegex(TypeError,
+                                     "multiple values for argument 'c'"):
+            self.call(test, 1, 2, 3, c=4)
+
+    def test_signature_bind_just_kwargs(self):
+        def test(**kwargs):
+            return kwargs
+
+        self.assertEqual(self.call(test), {})
+        self.assertEqual(self.call(test, foo='bar', spam='ham'),
+                         {'foo': 'bar', 'spam': 'ham'})
+
+    def test_signature_bind_args_and_kwargs(self):
+        def test(a, b, c=3, **kwargs):
+            return a, b, c, kwargs
+
+        self.assertEqual(self.call(test, 1, 2), (1, 2, 3, {}))
+        self.assertEqual(self.call(test, 1, 2, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, b=2, a=1, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, a=1, b=2, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, 1, b=2, foo='bar', spam='ham'),
+                         (1, 2, 3, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, 1, b=2, c=4, foo='bar', spam='ham'),
+                         (1, 2, 4, {'foo': 'bar', 'spam': 'ham'}))
+        self.assertEqual(self.call(test, 1, 2, 4, foo='bar'),
+                         (1, 2, 4, {'foo': 'bar'}))
+        self.assertEqual(self.call(test, c=5, a=4, b=3),
+                         (4, 3, 5, {}))
+
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_bind_kwonly(self):
+    def test(*, foo):
+        return foo
+    with self.assertRaisesRegex(TypeError,
+                                 'too many positional arguments'):
+        self.call(test, 1)
+    self.assertEqual(self.call(test, foo=1), 1)
+
+    def test(a, *, foo=1, bar):
+        return foo
+    with self.assertRaisesRegex(TypeError,
+                                 "'bar' parameter lacking default value"):
+        self.call(test, 1)
+
+    def test(foo, *, bar):
+        return foo, bar
+    self.assertEqual(self.call(test, 1, bar=2), (1, 2))
+    self.assertEqual(self.call(test, bar=2, foo=1), (1, 2))
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many keyword arguments'):
+        self.call(test, bar=2, foo=1, spam=10)
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many positional arguments'):
+        self.call(test, 1, 2)
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many positional arguments'):
+        self.call(test, 1, 2, bar=2)
+
+    with self.assertRaisesRegex(TypeError,
+                                 'too many keyword arguments'):
+        self.call(test, 1, bar=2, spam='ham')
+
+    with self.assertRaisesRegex(TypeError,
+                                 "'bar' parameter lacking default value"):
+        self.call(test, 1)
+
+    def test(foo, *, bar, **bin):
+        return foo, bar, bin
+    self.assertEqual(self.call(test, 1, bar=2), (1, 2, {}))
+    self.assertEqual(self.call(test, foo=1, bar=2), (1, 2, {}))
+    self.assertEqual(self.call(test, 1, bar=2, spam='ham'),
+                     (1, 2, {'spam': 'ham'}))
+    self.assertEqual(self.call(test, spam='ham', foo=1, bar=2),
+                     (1, 2, {'spam': 'ham'}))
+    with self.assertRaisesRegex(TypeError,
+                                 "'foo' parameter lacking default value"):
+        self.call(test, spam='ham', bar=2)
+    self.assertEqual(self.call(test, 1, bar=2, bin=1, spam=10),
+                     (1, 2, {'bin': 1, 'spam': 10}))
+""")
+#
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_bind_arguments(self):
+    def test(a, *args, b, z=100, **kwargs):
+        pass
+    sig = inspect.signature(test)
+    ba = sig.bind(10, 20, b=30, c=40, args=50, kwargs=60)
+    # we won't have 'z' argument in the bound arguments object, as we didn't
+    # pass it to the 'bind'
+    self.assertEqual(tuple(ba.arguments.items()),
+                     (('a', 10), ('args', (20,)), ('b', 30),
+                      ('kwargs', {'c': 40, 'args': 50, 'kwargs': 60})))
+    self.assertEqual(ba.kwargs,
+                     {'b': 30, 'c': 40, 'args': 50, 'kwargs': 60})
+    self.assertEqual(ba.args, (10, 20))
+""")
+#
+    if sys.version_info[0] > 2:
+        exec("""
+def test_signature_bind_positional_only(self):
+    P = inspect.Parameter
+
+    def test(a_po, b_po, c_po=3, foo=42, *, bar=50, **kwargs):
+        return a_po, b_po, c_po, foo, bar, kwargs
+
+    sig = inspect.signature(test)
+    new_params = collections.OrderedDict(tuple(sig.parameters.items()))
+    for name in ('a_po', 'b_po', 'c_po'):
+        new_params[name] = new_params[name].replace(kind=P.POSITIONAL_ONLY)
+    new_sig = sig.replace(parameters=new_params.values())
+    test.__signature__ = new_sig
+
+    self.assertEqual(self.call(test, 1, 2, 4, 5, bar=6),
+                     (1, 2, 4, 5, 6, {}))
+
+    with self.assertRaisesRegex(TypeError, "parameter is positional only"):
+        self.call(test, 1, 2, c_po=4)
+
+    with self.assertRaisesRegex(TypeError, "parameter is positional only"):
+        self.call(test, a_po=1, b_po=2)
+""")
+
+
+class TestBoundArguments(unittest.TestCase):
+
+    def __init__(self, *args, **kwargs):
+        unittest.TestCase.__init__(self, *args, **kwargs)
+        if not hasattr(self, 'assertRaisesRegex'):
+            self.assertRaisesRegex = self.assertRaisesRegexp
+
+    def test_signature_bound_arguments_unhashable(self):
+        def foo(a): pass
+        ba = inspect.signature(foo).bind(1)
+
+        with self.assertRaisesRegex(TypeError, 'unhashable type'):
+            hash(ba)
+
+    def test_signature_bound_arguments_equality(self):
+        def foo(a): pass
+        ba = inspect.signature(foo).bind(1)
+        self.assertEqual(ba, ba)
+
+        ba2 = inspect.signature(foo).bind(1)
+        self.assertEqual(ba, ba2)
+
+        ba3 = inspect.signature(foo).bind(2)
+        self.assertNotEqual(ba, ba3)
+        ba3.arguments['a'] = 1
+        self.assertEqual(ba, ba3)
+
+        def bar(b): pass
+        ba4 = inspect.signature(bar).bind(1)
+        self.assertNotEqual(ba, ba4)
+
+
+if __name__ == "__main__":
+    unittest.begin()
diff --git a/tools/third_party/pluggy/.gitignore b/tools/third_party/pluggy/.gitignore
new file mode 100644
index 0000000..e5c7e98
--- /dev/null
+++ b/tools/third_party/pluggy/.gitignore
@@ -0,0 +1,58 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+*.swp
diff --git a/tools/third_party/pluggy/.travis.yml b/tools/third_party/pluggy/.travis.yml
new file mode 100644
index 0000000..6fd136b
--- /dev/null
+++ b/tools/third_party/pluggy/.travis.yml
@@ -0,0 +1,48 @@
+sudo: false
+language: python
+
+matrix:
+  include:
+    - python: '3.6'
+      env: TOXENV=check
+    - python: '3.6'
+      env: TOXENV=docs
+    - python: '2.7'
+      env: TOXENV=py27-pytestrelease
+    - python: '3.4'
+      env: TOXENV=py34-pytestrelease
+    - python: '3.5'
+      env: TOXENV=py35-pytestrelease
+    - python: '3.6'
+      env: TOXENV=py36-pytestrelease
+    - python: 'pypy'
+      env: TOXENV=pypy-pytestrelease
+    - python: 'nightly'
+      env: TOXENV=py37-pytestrelease
+    - python: '2.7'
+      env: TOXENV=py27-pytestmaster
+    - python: '2.7'
+      env: TOXENV=py27-pytestfeatures
+    - python: '3.6'
+      env: TOXENV=py36-pytestmaster
+    - python: '3.6'
+      env: TOXENV=py36-pytestfeatures
+    - python: '3.6'
+      env: TOXENV=benchmark
+
+install:
+  - pip install -U setuptools pip
+  - pip install -U tox
+
+script:
+  - tox
+
+notifications:
+  irc:
+    channels:
+      - "chat.freenode.net#pytest"
+    on_success: change
+    on_failure: change
+    skip_join: true
+#  email:
+#    - pytest-commit@python.org
diff --git a/tools/third_party/pluggy/CHANGELOG.rst b/tools/third_party/pluggy/CHANGELOG.rst
new file mode 100644
index 0000000..91bf53f
--- /dev/null
+++ b/tools/third_party/pluggy/CHANGELOG.rst
@@ -0,0 +1,152 @@
+0.6.0
+-----
+- Add CI testing for the features, release, and master
+  branches of ``pytest`` (PR `#79`_).
+- Document public API for ``_Result`` objects passed to wrappers
+  (PR `#85`_).
+- Document and test hook LIFO ordering (PR `#85`_).
+- Turn warnings into errors in test suite (PR `#89`_).
+- Deprecate ``_Result.result`` (PR `#88`_).
+- Convert ``_Multicall`` to a simple function distinguishing it from
+  the legacy version (PR `#90`_).
+- Resolve E741 errors (PR `#96`_).
+- Test and bug fix for unmarked hook collection (PRs `#97`_ and
+  `#102`_).
+- Drop support for EOL Python 2.6 and 3.3 (PR `#103`_).
+- Fix ``inspect`` based arg introspection on py3.6 (PR `#94`_).
+
+.. _#79: https://github.com/pytest-dev/pluggy/pull/79
+.. _#85: https://github.com/pytest-dev/pluggy/pull/85
+.. _#88: https://github.com/pytest-dev/pluggy/pull/88
+.. _#89: https://github.com/pytest-dev/pluggy/pull/89
+.. _#90: https://github.com/pytest-dev/pluggy/pull/90
+.. _#94: https://github.com/pytest-dev/pluggy/pull/94
+.. _#96: https://github.com/pytest-dev/pluggy/pull/96
+.. _#97: https://github.com/pytest-dev/pluggy/pull/97
+.. _#102: https://github.com/pytest-dev/pluggy/pull/102
+.. _#103: https://github.com/pytest-dev/pluggy/pull/103
+
+
+0.5.2
+-----
+- fix bug where ``firstresult`` wrappers were being sent an incorrectly configured
+  ``_Result`` (a list was set instead of a single value). Add tests to check for
+  this as well as ``_Result.force_result()`` behaviour. Thanks to `@tgoodlet`_
+  for the PR `#72`_.
+
+- fix incorrect ``getattr``  of ``DeprecationWarning`` from the ``warnings``
+  module. Thanks to `@nicoddemus`_ for the PR `#77`_.
+
+- hide ``pytest`` tracebacks in certain core routines. Thanks to
+  `@nicoddemus`_ for the PR `#80`_.
+
+.. _#72: https://github.com/pytest-dev/pluggy/pull/72
+.. _#77: https://github.com/pytest-dev/pluggy/pull/77
+.. _#80: https://github.com/pytest-dev/pluggy/pull/80
+
+
+0.5.1
+-----
+- fix a bug and add tests for case where ``firstresult`` hooks return
+  ``None`` results. Thanks to `@RonnyPfannschmidt`_ and `@tgoodlet`_
+  for the issue (`#68`_) and PR (`#69`_) respectively.
+
+.. _#69: https://github.com/pytest-dev/pluggy/pull/69
+.. _#68: https://github.com/pytest-dev/pluggy/issuses/68
+
+
+0.5.0
+-----
+- fix bug where callbacks for historic hooks would not be called for
+  already registered plugins.  Thanks `@vodik`_ for the PR
+  and `@hpk42`_ for further fixes.
+
+- fix `#17`_ by considering only actual functions for hooks
+  this removes the ability to register arbitrary callable objects
+  which at first glance is a reasonable simplification,
+  thanks `@RonnyPfannschmidt`_ for report and pr.
+
+- fix `#19`_: allow registering hookspecs from instances.  The PR from
+  `@tgoodlet`_ also modernized the varnames implementation.
+
+- resolve `#32`_: split up the test set into multiple modules.
+  Thanks to `@RonnyPfannschmidt`_ for the PR and `@tgoodlet`_ for
+  the initial request.
+
+- resolve `#14`_: add full sphinx docs. Thanks to `@tgoodlet`_ for
+  PR `#39`_.
+
+- add hook call mismatch warnings. Thanks to `@tgoodlet`_ for the
+  PR `#42`_.
+
+- resolve `#44`_: move to new-style classes. Thanks to `@MichalTHEDUDE`_
+  for PR `#46`_.
+
+- add baseline benchmarking/speed tests using ``pytest-benchmark``
+  in PR `#54`_.  Thanks to `@tgoodlet`_.
+
+- update the README to showcase the API. Thanks to `@tgoodlet`_ for the
+  issue and PR `#55`_.
+
+- deprecate ``__multicall__`` and add a faster call loop implementation.
+  Thanks to `@tgoodlet`_ for PR `#58`_.
+
+- raise a comprehensible error when a ``hookimpl`` is called with positional
+  args. Thanks to `@RonnyPfannschmidt`_ for the issue and `@tgoodlet`_ for
+  PR `#60`_.
+
+- fix the ``firstresult`` test making it more complete
+  and remove a duplicate of that test. Thanks to `@tgoodlet`_
+  for PR `#62`_.
+
+.. _#62: https://github.com/pytest-dev/pluggy/pull/62
+.. _#60: https://github.com/pytest-dev/pluggy/pull/60
+.. _#58: https://github.com/pytest-dev/pluggy/pull/58
+.. _#55: https://github.com/pytest-dev/pluggy/pull/55
+.. _#54: https://github.com/pytest-dev/pluggy/pull/54
+.. _#46: https://github.com/pytest-dev/pluggy/pull/46
+.. _#44: https://github.com/pytest-dev/pluggy/issues/44
+.. _#42: https://github.com/pytest-dev/pluggy/pull/42
+.. _#39: https://github.com/pytest-dev/pluggy/pull/39
+.. _#32: https://github.com/pytest-dev/pluggy/pull/32
+.. _#19: https://github.com/pytest-dev/pluggy/issues/19
+.. _#17: https://github.com/pytest-dev/pluggy/issues/17
+.. _#14: https://github.com/pytest-dev/pluggy/issues/14
+
+
+0.4.0
+-----
+- add ``has_plugin(name)`` method to pluginmanager.  thanks `@nicoddemus`_.
+
+- fix `#11`_: make plugin parsing more resilient against exceptions
+  from ``__getattr__`` functions. Thanks `@nicoddemus`_.
+
+- fix issue `#4`_: specific ``HookCallError`` exception for when a hook call
+  provides not enough arguments.
+
+- better error message when loading setuptools entrypoints fails
+  due to a ``VersionConflict``.  Thanks `@blueyed`_.
+
+.. _#11: https://github.com/pytest-dev/pluggy/issues/11
+.. _#4: https://github.com/pytest-dev/pluggy/issues/4
+
+
+0.3.1
+-----
+- avoid using deprecated-in-python3.5 getargspec method. Thanks
+  `@mdboom`_.
+
+
+0.3.0
+-----
+initial release
+
+.. contributors
+.. _@hpk42: https://github.com/hpk42
+.. _@tgoodlet: https://github.com/tgoodlet
+.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE
+.. _@vodik: https://github.com/vodik
+.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
+.. _@blueyed: https://github.com/blueyed
+.. _@nicoddemus: https://github.com/nicoddemus
+.. _@mdboom: https://github.com/mdboom
diff --git a/tools/third_party/pluggy/LICENSE b/tools/third_party/pluggy/LICENSE
new file mode 100644
index 0000000..121017d
--- /dev/null
+++ b/tools/third_party/pluggy/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42) 
+
+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.
+
diff --git a/tools/third_party/pluggy/MANIFEST.in b/tools/third_party/pluggy/MANIFEST.in
new file mode 100644
index 0000000..0cf8f3e
--- /dev/null
+++ b/tools/third_party/pluggy/MANIFEST.in
@@ -0,0 +1,7 @@
+include CHANGELOG
+include README.rst
+include setup.py
+include tox.ini
+include LICENSE
+graft testing
+recursive-exclude * *.pyc *.pyo
diff --git a/tools/third_party/pluggy/README.rst b/tools/third_party/pluggy/README.rst
new file mode 100644
index 0000000..3636b6e
--- /dev/null
+++ b/tools/third_party/pluggy/README.rst
@@ -0,0 +1,80 @@
+pluggy - A minimalist production ready plugin system
+====================================================
+|pypi| |anaconda| |versions| |travis| |appveyor|
+
+
+This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects.
+
+Please `read the docs`_ to learn more!
+
+A definitive example
+********************
+.. code-block:: python
+
+    import pluggy
+
+    hookspec = pluggy.HookspecMarker("myproject")
+    hookimpl = pluggy.HookimplMarker("myproject")
+
+
+    class MySpec(object):
+        """A hook specification namespace.
+        """
+        @hookspec
+        def myhook(self, arg1, arg2):
+            """My special little hook that you can customize.
+            """
+
+
+    class Plugin_1(object):
+        """A hook implementation namespace.
+        """
+        @hookimpl
+        def myhook(self, arg1, arg2):
+            print("inside Plugin_1.myhook()")
+            return arg1 + arg2
+
+
+    class Plugin_2(object):
+        """A 2nd hook implementation namespace.
+        """
+        @hookimpl
+        def myhook(self, arg1, arg2):
+            print("inside Plugin_2.myhook()")
+            return arg1 - arg2
+
+
+    # create a manager and add the spec
+    pm = pluggy.PluginManager("myproject")
+    pm.add_hookspecs(MySpec)
+
+    # register plugins
+    pm.register(Plugin_1())
+    pm.register(Plugin_2())
+
+    # call our `myhook` hook
+    results = pm.hook.myhook(arg1=1, arg2=2)
+    print(results)
+
+
+.. badges
+.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg
+    :target: https://pypi.python.org/pypi/pluggy
+.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg
+    :target: https://pypi.python.org/pypi/pluggy
+.. |travis| image:: https://img.shields.io/travis/pytest-dev/pluggy/master.svg
+    :target: https://travis-ci.org/pytest-dev/pluggy
+.. |appveyor| image:: https://img.shields.io/appveyor/ci/pytestbot/pluggy/master.svg
+    :target: https://ci.appveyor.com/project/pytestbot/pluggy
+.. |anaconda| image:: https://anaconda.org/conda-forge/pluggy/badges/version.svg
+    :target: https://anaconda.org/conda-forge/pluggy
+
+.. links
+.. _pytest:
+    http://pytest.org
+.. _tox:
+    https://tox.readthedocs.org
+.. _devpi:
+    http://doc.devpi.net
+.. _read the docs:
+   https://pluggy.readthedocs.io/en/latest/
diff --git a/tools/third_party/pluggy/appveyor.yml b/tools/third_party/pluggy/appveyor.yml
new file mode 100644
index 0000000..c3903f8
--- /dev/null
+++ b/tools/third_party/pluggy/appveyor.yml
@@ -0,0 +1,34 @@
+environment:
+  matrix:
+  # note: please use "tox --listenvs" to populate the build matrix below
+  - TOXENV: "check"
+  - TOXENV: "docs"
+  - TOXENV: "py27-pytestrelease"
+  - TOXENV: "py34-pytestrelease"
+  - TOXENV: "py35-pytestrelease"
+  - TOXENV: "py36-pytestrelease"
+  - TOXENV: "pypy-pytestrelease"
+  - TOXENV: "py27-pytestmaster"
+  - TOXENV: "py27-pytestfeatures"
+  - TOXENV: "py36-pytestmaster"
+  - TOXENV: "py36-pytestfeatures"
+
+install:
+  - echo Installed Pythons
+  - dir c:\Python*
+
+  # install pypy using choco (redirect to a file and write to console in case
+  # choco install returns non-zero, because choco install python.pypy is too
+  # noisy)
+  # pypy is disabled until #1963 gets fixed
+  - choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1)
+  - set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy
+  - echo PyPy installed
+  - pypy --version
+
+  - C:\Python35\python -m pip install tox
+
+build: false  # Not a C# project, build stuff at the test step instead.
+
+test_script:
+  - C:\Python35\python -m tox
diff --git a/tools/third_party/pluggy/docs/_static/img/plug.png b/tools/third_party/pluggy/docs/_static/img/plug.png
new file mode 100644
index 0000000..3339f8a
--- /dev/null
+++ b/tools/third_party/pluggy/docs/_static/img/plug.png
Binary files differ
diff --git a/tools/third_party/pluggy/docs/api_reference.rst b/tools/third_party/pluggy/docs/api_reference.rst
new file mode 100644
index 0000000..aa15135
--- /dev/null
+++ b/tools/third_party/pluggy/docs/api_reference.rst
@@ -0,0 +1,14 @@
+Api Reference
+=============
+
+.. automodule:: pluggy
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+.. automethod:: pluggy._Result.get_result
+
+.. automethod:: pluggy._Result.force_result
+
+.. automethod:: pluggy._HookCaller.call_extra
diff --git a/tools/third_party/pluggy/docs/conf.py b/tools/third_party/pluggy/docs/conf.py
new file mode 100644
index 0000000..0406521
--- /dev/null
+++ b/tools/third_party/pluggy/docs/conf.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+import pkg_resources
+
+
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.coverage',
+    'sphinx.ext.viewcode',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+
+dist = pkg_resources.get_distribution('pluggy')
+project = dist.project_name
+copyright = u'2016, Holger Krekel'
+author = 'Holger Krekel'
+
+release = dist.version
+# The short X.Y version.
+version = u'.'.join(dist.version.split('.')[:2])
+
+
+language = None
+
+pygments_style = 'sphinx'
+html_logo = '_static/img/plug.png'
+html_theme = 'alabaster'
+html_theme_options = {
+    # 'logo': 'img/plug.png',
+    # 'logo_name': 'true',
+    'description': 'The `pytest` plugin system',
+    'github_user': 'pytest-dev',
+    'github_repo': 'pluggy',
+    'github_button': 'true',
+    'github_banner': 'true',
+    'page_width': '1080px',
+    'fixed_sidebar': 'false',
+}
+html_static_path = ['_static']
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'pluggy', u'pluggy Documentation',
+     [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'pluggy', u'pluggy Documentation',
+     author, 'pluggy', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}
diff --git a/tools/third_party/pluggy/docs/examples/firstexample.py b/tools/third_party/pluggy/docs/examples/firstexample.py
new file mode 100644
index 0000000..3cec7cd
--- /dev/null
+++ b/tools/third_party/pluggy/docs/examples/firstexample.py
@@ -0,0 +1,44 @@
+import pluggy
+
+hookspec = pluggy.HookspecMarker("myproject")
+hookimpl = pluggy.HookimplMarker("myproject")
+
+
+class MySpec(object):
+    """A hook specification namespace.
+    """
+    @hookspec
+    def myhook(self, arg1, arg2):
+        """My special little hook that you can customize.
+        """
+
+
+class Plugin_1(object):
+    """A hook implementation namespace.
+    """
+    @hookimpl
+    def myhook(self, arg1, arg2):
+        print("inside Plugin_1.myhook()")
+        return arg1 + arg2
+
+
+class Plugin_2(object):
+    """A 2nd hook implementation namespace.
+    """
+    @hookimpl
+    def myhook(self, arg1, arg2):
+        print("inside Plugin_2.myhook()")
+        return arg1 - arg2
+
+
+# create a manager and add the spec
+pm = pluggy.PluginManager("myproject")
+pm.add_hookspecs(MySpec)
+
+# register plugins
+pm.register(Plugin_1())
+pm.register(Plugin_2())
+
+# call our `myhook` hook
+results = pm.hook.myhook(arg1=1, arg2=2)
+print(results)
diff --git a/tools/third_party/pluggy/docs/index.rst b/tools/third_party/pluggy/docs/index.rst
new file mode 100644
index 0000000..db65a10
--- /dev/null
+++ b/tools/third_party/pluggy/docs/index.rst
@@ -0,0 +1,705 @@
+``pluggy``
+==========
+
+The ``pytest`` plugin system
+****************************
+``pluggy`` is the crystallized core of `plugin management and hook
+calling`_ for `pytest`_.
+
+In fact, ``pytest`` is itself composed as a set of ``pluggy`` plugins
+which are invoked in sequence according to a well defined set of protocols.
+Some `200+ plugins`_ use ``pluggy`` to extend and customize ``pytest``'s default behaviour.
+
+In essence, ``pluggy`` enables function `hooking`_ so you can build "pluggable" systems.
+
+How's it work?
+--------------
+A `plugin` is a `namespace`_ which defines hook functions.
+
+``pluggy`` manages *plugins* by relying on:
+
+- a hook *specification* - defines a call signature
+- a set of hook *implementations* - aka `callbacks`_
+- the hook *caller* - a call loop which collects results
+
+where for each registered hook *specification*, a hook *call* will invoke up to ``N``
+registered hook *implementations*.
+
+``pluggy`` accomplishes all this by implementing a `request-response pattern`_ using *function*
+subscriptions and can be thought of and used as a rudimentary busless `publish-subscribe`_
+event system.
+
+``pluggy``'s approach is meant to let a designer think carefuly about which objects are
+explicitly needed by an extension writer. This is in contrast to subclass-based extension
+systems which may expose unecessary state and behaviour or encourage `tight coupling`_
+in overlying frameworks.
+
+
+A first example
+---------------
+
+.. literalinclude:: examples/firstexample.py
+
+Running this directly gets us::
+
+    $ python docs/examples/firstexample.py
+
+    inside Plugin_2.myhook()
+    inside Plugin_1.myhook()
+    [-1, 3]
+
+For more details and advanced usage please read on.
+
+.. _define:
+
+Defining and Collecting Hooks
+*****************************
+A *plugin* is a namespace type (currently one of a ``class`` or module)
+which defines a set of *hook* functions.
+
+As mentioned in :ref:`manage`, all *plugins* which define *hooks*
+are managed by an instance of a :py:class:`pluggy.PluginManager` which
+defines the primary ``pluggy`` API.
+
+In order for a ``PluginManager`` to detect functions in a namespace
+intended to be *hooks*, they must be decorated using special ``pluggy`` *marks*.
+
+.. _marking_hooks:
+
+Marking hooks
+-------------
+The :py:class:`~pluggy.HookspecMarker` and :py:class:`~pluggy.HookimplMarker`
+decorators are used to *mark* functions for detection by a ``PluginManager``:
+
+.. code-block:: python
+
+    from pluggy import HookspecMarker, HookimplMarker
+
+    hookspec = HookspecMarker('project_name')
+    hookimpl = HookimplMarker('project_name')
+
+
+Each decorator type takes a single ``project_name`` string as its
+lone argument the value of which is used to mark hooks for detection by
+by a similarly configured ``PluginManager`` instance.
+
+That is, a *mark* type called with ``project_name`` returns an object which
+can be used to decorate functions which will then be detected by a
+``PluginManager`` which was instantiated with the the same ``project_name``
+value.
+
+Furthermore, each *hookimpl* or *hookspec* decorator can configure the
+underlying call-time behavior of each *hook* object by providing special
+*options* passed as keyword arguments.
+
+
+.. note::
+    The following sections correspond to similar documentation in
+    ``pytest`` for `Writing hook functions`_ and can be used
+    as a supplementary resource.
+
+.. _impls:
+
+Implementations
+---------------
+A hook *implementation* (*hookimpl*) is just a (callback) function
+which has been appropriately marked.
+
+*hookimpls* are loaded from a plugin using the
+:py:meth:`~pluggy.PluginManager.register()` method:
+
+.. code-block:: python
+
+    import sys
+    from pluggy import PluginManager, HookimplMarker
+
+    hookimpl = HookimplMarker('myproject')
+
+    @hookimpl
+    def setup_project(config, args):
+        """This hook is used to process the initial config
+        and possibly input arguments.
+        """
+        if args:
+            config.process_args(args)
+
+        return config
+
+    pm = PluginManager('myproject')
+
+    # load all hookimpls from the local module's namespace
+    plugin_name = pm.register(sys.modules[__name__])
+
+.. _optionalhook:
+
+Optional validation
+^^^^^^^^^^^^^^^^^^^
+Normally each *hookimpl* should be validated a against a corresponding
+hook :ref:`specification <specs>`. If you want to make an exception
+then the *hookimpl* should be marked with the ``"optionalhook"`` option:
+
+.. code-block:: python
+
+    @hookimpl(optionalhook=True)
+    def setup_project(config, args):
+        """This hook is used to process the initial config
+        and possibly input arguments.
+        """
+        if args:
+            config.process_args(args)
+
+        return config
+
+Call time order
+^^^^^^^^^^^^^^^
+By default hooks are :ref:`called <calling>` in LIFO registered order, however,
+a *hookimpl* can influence its call-time invocation position using special
+attributes. If marked with a ``"tryfirst"`` or ``"trylast"`` option it
+will be executed *first* or *last* respectively in the hook call loop:
+
+.. code-block:: python
+
+    import sys
+    from pluggy import PluginManager, HookimplMarker
+
+    hookimpl = HookimplMarker('myproject')
+
+    @hookimpl(trylast=True)
+    def setup_project(config, args):
+        """Default implementation.
+        """
+        if args:
+            config.process_args(args)
+
+        return config
+
+
+    class SomeOtherPlugin(object):
+        """Some other plugin defining the same hook.
+        """
+        @hookimpl(tryfirst=True)
+        def setup_project(config, args):
+            """Report what args were passed before calling
+            downstream hooks.
+            """
+            if args:
+                print("Got args: {}".format(args))
+
+            return config
+
+    pm = PluginManager('myproject')
+
+    # load from the local module's namespace
+    pm.register(sys.modules[__name__])
+    # load a plugin defined on a class
+    pm.register(SomePlugin())
+
+For another example see the `hook function ordering`_ section of the
+``pytest`` docs.
+
+.. note::
+    ``tryfirst`` and ``trylast`` hooks are still invoked in LIFO order within
+    each category.
+
+Wrappers
+^^^^^^^^
+A *hookimpl* can be marked with a ``"hookwrapper"`` option which indicates that
+the function will be called to *wrap* (or surround) all other normal *hookimpl*
+calls. A *hookwrapper* can thus execute some code ahead and after the execution
+of all corresponding non-wrappper *hookimpls*.
+
+Much in the same way as a `@contextlib.contextmanager`_, *hookwrappers* must
+be implemented as generator function with a single ``yield`` in its body:
+
+
+.. code-block:: python
+
+    @hookimpl(hookwrapper=True)
+    def setup_project(config, args):
+        """Wrap calls to ``setup_project()`` implementations which
+        should return json encoded config options.
+        """
+        if config.debug:
+            print("Pre-hook config is {}".format(
+                config.tojson()))
+
+        # get initial default config
+        defaults = config.tojson()
+
+        # all corresponding hookimpls are invoked here
+        outcome = yield
+
+        for item in outcome.get_result():
+            print("JSON config override is {}".format(item))
+
+        if config.debug:
+            print("Post-hook config is {}".format(
+                config.tojson()))
+
+        if config.use_defaults:
+            outcome.force_result(defaults)
+
+The generator is `sent`_ a :py:class:`pluggy._Result` object which can
+be assigned in the ``yield`` expression and used to override or inspect
+the final result(s) returned back to the caller using the
+:py:meth:`~pluggy._Result.force_result` or
+:py:meth:`~pluggy._Result.get_result` methods.
+
+.. note::
+    Hook wrappers can **not** return results (as per generator function
+    semantics); they can only modify them using the ``_Result`` API.
+
+Also see the `hookwrapper`_ section in the ``pytest`` docs.
+
+.. _specs:
+
+Specifications
+--------------
+A hook *specification* (*hookspec*) is a definition used to validate each
+*hookimpl* ensuring that an extension writer has correctly defined their
+callback function *implementation* .
+
+*hookspecs* are defined using similarly marked functions however only the
+function *signature* (its name and names of all its arguments) is analyzed
+and stored. As such, often you will see a *hookspec* defined with only
+a docstring in its body.
+
+*hookspecs* are loaded using the
+:py:meth:`~pluggy.PluginManager.add_hookspecs()` method and normally
+should be added before registering corresponding *hookimpls*:
+
+.. code-block:: python
+
+    import sys
+    from pluggy import PluginManager, HookspecMarker
+
+    hookspec = HookspecMarker('myproject')
+
+    @hookspec
+    def setup_project(config, args):
+        """This hook is used to process the inital config and input
+        arguments.
+        """
+
+    pm = PluginManager('myproject')
+
+    # load from the local module's namespace
+    pm.add_hookspecs(sys.modules[__name__])
+
+
+Registering a *hookimpl* which does not meet the constraints of its
+corresponding *hookspec* will result in an error.
+
+A *hookspec* can also be added **after** some *hookimpls* have been
+registered however this is not normally recommended as it results in
+delayed hook validation.
+
+.. note::
+    The term *hookspec* can sometimes refer to the plugin-namespace
+    which defines ``hookspec`` decorated functions as in the case of
+    ``pytest``'s `hookspec module`_
+
+Enforcing spec validation
+^^^^^^^^^^^^^^^^^^^^^^^^^
+By default there is no strict requirement that each *hookimpl* has
+a corresponding *hookspec*. However, if you'd like you enforce this
+behavior you can run a check with the
+:py:meth:`~pluggy.PluginManager.check_pending()` method. If you'd like
+to enforce requisite *hookspecs* but with certain exceptions for some hooks
+then make sure to mark those hooks as :ref:`optional <optionalhook>`.
+
+Opt-in arguments
+^^^^^^^^^^^^^^^^
+To allow for *hookspecs* to evolve over the lifetime of a project,
+*hookimpls* can accept **less** arguments then defined in the spec.
+This allows for extending hook arguments (and thus semantics) without
+breaking existing *hookimpls*.
+
+In other words this is ok:
+
+.. code-block:: python
+
+    @hookspec
+    def myhook(config, args):
+        pass
+
+    @hookimpl
+    def myhook(args):
+        print(args)
+
+
+whereas this is not:
+
+.. code-block:: python
+
+    @hookspec
+    def myhook(config, args):
+        pass
+
+    @hookimpl
+    def myhook(config, args, extra_arg):
+        print(args)
+
+.. _firstresult:
+
+First result only
+^^^^^^^^^^^^^^^^^
+A *hookspec* can be marked such that when the *hook* is called the call loop
+will only invoke up to the first *hookimpl* which returns a result other
+then ``None``.
+
+.. code-block:: python
+
+    @hookspec(firstresult=True)
+    def myhook(config, args):
+        pass
+
+This can be useful for optimizing a call loop for which you are only
+interested in a single core *hookimpl*. An example is the
+`pytest_cmdline_main`_ central routine of ``pytest``.
+
+Also see the `first result`_ section in the ``pytest`` docs.
+
+.. _historic:
+
+Historic hooks
+^^^^^^^^^^^^^^
+You can mark a *hookspec* as being *historic* meaning that the hook
+can be called with :py:meth:`~pluggy.PluginManager.call_historic()` **before**
+having been registered:
+
+.. code-block:: python
+
+    @hookspec(historic=True)
+    def myhook(config, args):
+        pass
+
+The implication is that late registered *hookimpls* will be called back
+immediately at register time and **can not** return a result to the caller.**
+
+This turns out to be particularly useful when dealing with lazy or
+dynamically loaded plugins.
+
+For more info see :ref:`call_historic`.
+
+
+.. links
+.. _@contextlib.contextmanager:
+    https://docs.python.org/3.6/library/contextlib.html#contextlib.contextmanager
+.. _pytest_cmdline_main:
+    https://github.com/pytest-dev/pytest/blob/master/_pytest/hookspec.py#L80
+.. _hookspec module:
+    https://github.com/pytest-dev/pytest/blob/master/_pytest/hookspec.py
+.. _Writing hook functions:
+    http://doc.pytest.org/en/latest/writing_plugins.html#writing-hook-functions
+.. _hookwrapper:
+    http://doc.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks
+.. _hook function ordering:
+    http://doc.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example
+.. _first result:
+    http://doc.pytest.org/en/latest/writing_plugins.html#firstresult-stop-at-first-non-none-result
+.. _sent:
+    https://docs.python.org/3/reference/expressions.html#generator.send
+
+.. _manage:
+
+The Plugin Registry
+*******************
+``pluggy`` manages plugins using instances of the
+:py:class:`pluggy.PluginManager`.
+
+A ``PluginManager`` is instantiated with a single
+``str`` argument, the ``project_name``:
+
+.. code-block:: python
+
+    import pluggy
+    pm = pluggy.PluginManager('my_project_name')
+
+
+The ``project_name`` value is used when a ``PluginManager`` scans for *hook*
+functions :ref:`defined on a plugin <define>`.
+This allows for multiple
+plugin managers from multiple projects to define hooks alongside each other.
+
+
+Registration
+------------
+Each ``PluginManager`` maintains a *plugin* registry where each *plugin*
+contains a set of *hookimpl* definitions. Loading *hookimpl* and *hookspec*
+definitions to populate the registry is described in detail in the section on
+:ref:`define`.
+
+In summary, you pass a plugin namespace object to the
+:py:meth:`~pluggy.PluginManager.register()` and
+:py:meth:`~pluggy.PluginManager.add_hookspec()` methods to collect
+hook *implementations* and *specfications* from *plugin* namespaces respectively.
+
+You can unregister any *plugin*'s hooks using
+:py:meth:`~pluggy.PluginManager.unregister()` and check if a plugin is
+registered by passing its name to the
+:py:meth:`~pluggy.PluginManager.is_registered()` method.
+
+Loading ``setuptools`` entry points
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+You can automatically load plugins registered through `setuptools entry points`_
+with the :py:meth:`~pluggy.PluginManager.load_setuptools_entrypoints()`
+method.
+
+An example use of this is the `pytest entry point`_.
+
+
+Blocking
+--------
+You can block any plugin from being registered using
+:py:meth:`~pluggy.PluginManager.set_blocked()` and check if a given
+*plugin* is blocked by name using :py:meth:`~pluggy.PluginManager.is_blocked()`.
+
+
+Inspection
+----------
+You can use a variety of methods to inspect the both the registry
+and particular plugins in it:
+
+- :py:meth:`~pluggy.PluginManager.list_name_plugin()` -
+  return a list of name-plugin pairs
+- :py:meth:`~pluggy.PluginManager.get_plugins()` - retrieve all plugins
+- :py:meth:`~pluggy.PluginManager.get_canonical_name()`- get a *plugin*'s
+  canonical name (the name it was registered with)
+- :py:meth:`~pluggy.PluginManager.get_plugin()` - retrieve a plugin by its
+  canonical name
+
+
+Parsing mark options
+^^^^^^^^^^^^^^^^^^^^
+You can retrieve the *options* applied to a particular
+*hookspec* or *hookimpl* as per :ref:`marking_hooks` using the
+:py:meth:`~pluggy.PluginManager.parse_hookspec_opts()` and
+:py:meth:`~pluggy.PluginManager.parse_hookimpl_opts()` respectively.
+
+.. links
+.. _setuptools entry points:
+    http://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins
+.. _pytest entry point:
+    http://doc.pytest.org/en/latest/writing_plugins.html#setuptools-entry-points
+
+
+.. _calling:
+
+Calling Hooks
+*************
+The core functionality of ``pluggy`` enables an extension provider
+to override function calls made at certain points throughout a program.
+
+A particular *hook* is invoked by calling an instance of
+a :py:class:`pluggy._HookCaller` which in turn *loops* through the
+``1:N`` registered *hookimpls* and calls them in sequence.
+
+Every :py:class:`pluggy.PluginManager` has a ``hook`` attribute
+which is an instance of this :py:class:`pluggy._HookRelay`.
+The ``_HookRelay`` itself contains references (by hook name) to each
+registered *hookimpl*'s ``_HookCaller`` instance.
+
+More practically you call a *hook* like so:
+
+.. code-block:: python
+
+    import sys
+    import pluggy
+    import mypluginspec
+    import myplugin
+    from configuration import config
+
+    pm = pluggy.PluginManager("myproject")
+    pm.add_hookspecs(mypluginspec)
+    pm.register(myplugin)
+
+    # we invoke the _HookCaller and thus all underlying hookimpls
+    result_list = pm.hook.myhook(config=config, args=sys.argv)
+
+Note that you **must** call hooks using keyword `arguments`_ syntax!
+
+Hook implementations are called in LIFO registered order: *the last
+registered plugin's hooks are called first*. As an example, the below
+assertion should not error:
+
+.. code-block:: python
+
+    from pluggy import PluginManager, HookimplMarker
+
+    hookimpl = HookimplMarker('myproject')
+
+    class Plugin1(object):
+        def myhook(self, args):
+            """Default implementation.
+            """
+            return 1
+
+    class Plugin2(object):
+        def myhook(self, args):
+            """Default implementation.
+            """
+            return 2
+
+    class Plugin3(object):
+        def myhook(self, args):
+            """Default implementation.
+            """
+            return 3
+
+    pm = PluginManager('myproject')
+    pm.register(Plugin1())
+    pm.register(Plugin2())
+    pm.register(Plugin3())
+
+    assert pm.hook.myhook(args=()) == [3, 2, 1]
+
+Collecting results
+------------------
+By default calling a hook results in all underlying :ref:`hookimpls
+<impls>` functions to be invoked in sequence via a loop. Any function
+which returns a value other then a ``None`` result will have that result
+appended to a :py:class:`list` which is returned by the call.
+
+The only exception to this behaviour is if the hook has been marked to return
+its :ref:`firstresult` in which case only the first single value (which is not
+``None``) will be returned.
+
+.. _call_historic:
+
+Historic calls
+--------------
+A *historic call* allows for all newly registered functions to receive all hook
+calls that happened before their registration. The implication is that this is
+only useful if you expect that some *hookimpls* may be registered **after** the
+hook is initially invoked.
+
+Historic hooks must be :ref:`specially marked <historic>` and called
+using the :py:meth:`pluggy._HookCaller.call_historic()` method:
+
+.. code-block:: python
+
+    # call with history; no results returned
+    pm.hook.myhook.call_historic(config=config, args=sys.argv)
+
+    # ... more of our program ...
+
+    # late loading of some plugin
+    import mylateplugin
+
+    # historic call back is done here
+    pm.register(mylateplugin)
+
+Note that if you ``call_historic()`` the ``_HookCaller`` (and thus your
+calling code) can not receive results back from the underlying *hookimpl*
+functions.
+
+Calling with extras
+-------------------
+You can call a hook with temporarily participating *implementation* functions
+(that aren't in the registry) using the
+:py:meth:`pluggy._HookCaller.call_extra()` method.
+
+
+Calling with a subset of registered plugins
+-------------------------------------------
+You can make a call using a subset of plugins by asking the
+``PluginManager`` first for a ``_HookCaller`` with those plugins removed
+using the :py:meth:`pluggy.PluginManager.subset_hook_caller()` method.
+
+You then can use that ``_HookCaller`` to make normal, ``call_historic()``,
+or ``call_extra()`` calls as necessary.
+
+
+.. links
+.. _arguments:
+    https://docs.python.org/3/glossary.html#term-argument
+
+
+Built-in tracing
+****************
+``pluggy`` comes with some batteries included hook tracing for your
+debugging needs.
+
+
+Call tracing
+------------
+To enable tracing use the
+:py:meth:`pluggy.PluginManager.enable_tracing()` method which returns an
+undo function to disable the behaviour.
+
+.. code-block:: python
+
+    pm = PluginManager('myproject')
+    # magic line to set a writer function
+    pm.trace.root.setwriter(print)
+    undo = pm.enable_tracing()
+
+
+Call monitoring
+---------------
+Instead of using the built-in tracing mechanism you can also add your
+own ``before`` and ``after`` monitoring functions using
+:py:class:`pluggy.PluginManager.add_hookcall_monitoring()`.
+
+The expected signature and default implementations for these functions is:
+
+.. code-block:: python
+
+    def before(hook_name, methods, kwargs):
+        pass
+
+    def after(outcome, hook_name, methods, kwargs):
+        pass
+
+Public API
+**********
+Please see the :doc:`api_reference`.
+
+Development
+***********
+Great care must taken when hacking on ``pluggy`` since multiple mature
+projects rely on it. Our Github integrated CI process runs the full
+`tox test suite`_ on each commit so be sure your changes can run on
+all required `Python interpreters`_ and ``pytest`` versions.
+
+Release Policy
+**************
+Pluggy uses `Semantic Versioning`_. Breaking changes are only foreseen for
+Major releases (incremented X in "X.Y.Z").  If you want to use ``pluggy``
+in your project you should thus use a dependency restriction like
+``"pluggy>=0.1.0,<1.0"`` to avoid surprises.
+
+
+.. hyperlinks
+.. _pytest:
+    http://pytest.org
+.. _request-response pattern:
+    https://en.wikipedia.org/wiki/Request%E2%80%93response
+.. _publish-subscribe:
+    https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern
+.. _hooking:
+    https://en.wikipedia.org/wiki/Hooking
+.. _plugin management and hook calling:
+    http://doc.pytest.org/en/latest/writing_plugins.html
+.. _namespace:
+    https://docs.python.org/3.6/tutorial/classes.html#python-scopes-and-namespaces
+.. _callbacks:
+    https://en.wikipedia.org/wiki/Callback_(computer_programming)
+.. _tox test suite:
+    https://github.com/pytest-dev/pluggy/blob/master/tox.ini
+.. _Semantic Versioning:
+    http://semver.org/
+.. _tight coupling:
+    https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29#Types_of_coupling
+.. _Python interpreters:
+    https://github.com/pytest-dev/pluggy/blob/master/tox.ini#L2
+.. _200+ plugins:
+    http://plugincompat.herokuapp.com/
+
+
+.. Indices and tables
+.. ==================
+.. * :ref:`genindex`
+.. * :ref:`modindex`
+.. * :ref:`search`
diff --git a/tools/third_party/pluggy/pluggy/__init__.py b/tools/third_party/pluggy/pluggy/__init__.py
new file mode 100644
index 0000000..ab5175d
--- /dev/null
+++ b/tools/third_party/pluggy/pluggy/__init__.py
@@ -0,0 +1,684 @@
+import inspect
+import warnings
+from .callers import _multicall, HookCallError, _Result, _legacymulticall
+
+__version__ = '0.5.3.dev'
+
+__all__ = ["PluginManager", "PluginValidationError", "HookCallError",
+           "HookspecMarker", "HookimplMarker"]
+
+
+class PluginValidationError(Exception):
+    """ plugin failed validation. """
+
+
+class HookspecMarker(object):
+    """ Decorator helper class for marking functions as hook specifications.
+
+    You can instantiate it with a project_name to get a decorator.
+    Calling PluginManager.add_hookspecs later will discover all marked functions
+    if the PluginManager uses the same project_name.
+    """
+
+    def __init__(self, project_name):
+        self.project_name = project_name
+
+    def __call__(self, function=None, firstresult=False, historic=False):
+        """ if passed a function, directly sets attributes on the function
+        which will make it discoverable to add_hookspecs().  If passed no
+        function, returns a decorator which can be applied to a function
+        later using the attributes supplied.
+
+        If firstresult is True the 1:N hook call (N being the number of registered
+        hook implementation functions) will stop at I<=N when the I'th function
+        returns a non-None result.
+
+        If historic is True calls to a hook will be memorized and replayed
+        on later registered plugins.
+
+        """
+        def setattr_hookspec_opts(func):
+            if historic and firstresult:
+                raise ValueError("cannot have a historic firstresult hook")
+            setattr(func, self.project_name + "_spec",
+                    dict(firstresult=firstresult, historic=historic))
+            return func
+
+        if function is not None:
+            return setattr_hookspec_opts(function)
+        else:
+            return setattr_hookspec_opts
+
+
+class HookimplMarker(object):
+    """ Decorator helper class for marking functions as hook implementations.
+
+    You can instantiate with a project_name to get a decorator.
+    Calling PluginManager.register later will discover all marked functions
+    if the PluginManager uses the same project_name.
+    """
+    def __init__(self, project_name):
+        self.project_name = project_name
+
+    def __call__(self, function=None, hookwrapper=False, optionalhook=False,
+                 tryfirst=False, trylast=False):
+
+        """ if passed a function, directly sets attributes on the function
+        which will make it discoverable to register().  If passed no function,
+        returns a decorator which can be applied to a function later using
+        the attributes supplied.
+
+        If optionalhook is True a missing matching hook specification will not result
+        in an error (by default it is an error if no matching spec is found).
+
+        If tryfirst is True this hook implementation will run as early as possible
+        in the chain of N hook implementations for a specfication.
+
+        If trylast is True this hook implementation will run as late as possible
+        in the chain of N hook implementations.
+
+        If hookwrapper is True the hook implementations needs to execute exactly
+        one "yield".  The code before the yield is run early before any non-hookwrapper
+        function is run.  The code after the yield is run after all non-hookwrapper
+        function have run.  The yield receives a ``_Result`` object representing
+        the exception or result outcome of the inner calls (including other hookwrapper
+        calls).
+
+        """
+        def setattr_hookimpl_opts(func):
+            setattr(func, self.project_name + "_impl",
+                    dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
+                         tryfirst=tryfirst, trylast=trylast))
+            return func
+
+        if function is None:
+            return setattr_hookimpl_opts
+        else:
+            return setattr_hookimpl_opts(function)
+
+
+def normalize_hookimpl_opts(opts):
+    opts.setdefault("tryfirst", False)
+    opts.setdefault("trylast", False)
+    opts.setdefault("hookwrapper", False)
+    opts.setdefault("optionalhook", False)
+
+
+class _TagTracer(object):
+    def __init__(self):
+        self._tag2proc = {}
+        self.writer = None
+        self.indent = 0
+
+    def get(self, name):
+        return _TagTracerSub(self, (name,))
+
+    def format_message(self, tags, args):
+        if isinstance(args[-1], dict):
+            extra = args[-1]
+            args = args[:-1]
+        else:
+            extra = {}
+
+        content = " ".join(map(str, args))
+        indent = "  " * self.indent
+
+        lines = [
+            "%s%s [%s]\n" % (indent, content, ":".join(tags))
+        ]
+
+        for name, value in extra.items():
+            lines.append("%s    %s: %s\n" % (indent, name, value))
+        return lines
+
+    def processmessage(self, tags, args):
+        if self.writer is not None and args:
+            lines = self.format_message(tags, args)
+            self.writer(''.join(lines))
+        try:
+            self._tag2proc[tags](tags, args)
+        except KeyError:
+            pass
+
+    def setwriter(self, writer):
+        self.writer = writer
+
+    def setprocessor(self, tags, processor):
+        if isinstance(tags, str):
+            tags = tuple(tags.split(":"))
+        else:
+            assert isinstance(tags, tuple)
+        self._tag2proc[tags] = processor
+
+
+class _TagTracerSub(object):
+    def __init__(self, root, tags):
+        self.root = root
+        self.tags = tags
+
+    def __call__(self, *args):
+        self.root.processmessage(self.tags, args)
+
+    def setmyprocessor(self, processor):
+        self.root.setprocessor(self.tags, processor)
+
+    def get(self, name):
+        return self.__class__(self.root, self.tags + (name,))
+
+
+class _TracedHookExecution(object):
+    def __init__(self, pluginmanager, before, after):
+        self.pluginmanager = pluginmanager
+        self.before = before
+        self.after = after
+        self.oldcall = pluginmanager._inner_hookexec
+        assert not isinstance(self.oldcall, _TracedHookExecution)
+        self.pluginmanager._inner_hookexec = self
+
+    def __call__(self, hook, hook_impls, kwargs):
+        self.before(hook.name, hook_impls, kwargs)
+        outcome = _Result.from_call(lambda: self.oldcall(hook, hook_impls, kwargs))
+        self.after(outcome, hook.name, hook_impls, kwargs)
+        return outcome.get_result()
+
+    def undo(self):
+        self.pluginmanager._inner_hookexec = self.oldcall
+
+
+class PluginManager(object):
+    """ Core Pluginmanager class which manages registration
+    of plugin objects and 1:N hook calling.
+
+    You can register new hooks by calling ``add_hookspec(module_or_class)``.
+    You can register plugin objects (which contain hooks) by calling
+    ``register(plugin)``.  The Pluginmanager is initialized with a
+    prefix that is searched for in the names of the dict of registered
+    plugin objects.  An optional excludefunc allows to blacklist names which
+    are not considered as hooks despite a matching prefix.
+
+    For debugging purposes you can call ``enable_tracing()``
+    which will subsequently send debug information to the trace helper.
+    """
+
+    def __init__(self, project_name, implprefix=None):
+        """ if implprefix is given implementation functions
+        will be recognized if their name matches the implprefix. """
+        self.project_name = project_name
+        self._name2plugin = {}
+        self._plugin2hookcallers = {}
+        self._plugin_distinfo = []
+        self.trace = _TagTracer().get("pluginmanage")
+        self.hook = _HookRelay(self.trace.root.get("hook"))
+        self._implprefix = implprefix
+        self._inner_hookexec = lambda hook, methods, kwargs: \
+            hook.multicall(
+                methods, kwargs,
+                firstresult=hook.spec_opts.get('firstresult'),
+            )
+
+    def _hookexec(self, hook, methods, kwargs):
+        # called from all hookcaller instances.
+        # enable_tracing will set its own wrapping function at self._inner_hookexec
+        return self._inner_hookexec(hook, methods, kwargs)
+
+    def register(self, plugin, name=None):
+        """ Register a plugin and return its canonical name or None if the name
+        is blocked from registering.  Raise a ValueError if the plugin is already
+        registered. """
+        plugin_name = name or self.get_canonical_name(plugin)
+
+        if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers:
+            if self._name2plugin.get(plugin_name, -1) is None:
+                return  # blocked plugin, return None to indicate no registration
+            raise ValueError("Plugin already registered: %s=%s\n%s" %
+                             (plugin_name, plugin, self._name2plugin))
+
+        # XXX if an error happens we should make sure no state has been
+        # changed at point of return
+        self._name2plugin[plugin_name] = plugin
+
+        # register matching hook implementations of the plugin
+        self._plugin2hookcallers[plugin] = hookcallers = []
+        for name in dir(plugin):
+            hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
+            if hookimpl_opts is not None:
+                normalize_hookimpl_opts(hookimpl_opts)
+                method = getattr(plugin, name)
+                hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
+                hook = getattr(self.hook, name, None)
+                if hook is None:
+                    hook = _HookCaller(name, self._hookexec)
+                    setattr(self.hook, name, hook)
+                elif hook.has_spec():
+                    self._verify_hook(hook, hookimpl)
+                    hook._maybe_apply_history(hookimpl)
+                hook._add_hookimpl(hookimpl)
+                hookcallers.append(hook)
+        return plugin_name
+
+    def parse_hookimpl_opts(self, plugin, name):
+        method = getattr(plugin, name)
+        if not inspect.isroutine(method):
+            return
+        try:
+            res = getattr(method, self.project_name + "_impl", None)
+        except Exception:
+            res = {}
+        if res is not None and not isinstance(res, dict):
+            # false positive
+            res = None
+        elif res is None and self._implprefix and name.startswith(self._implprefix):
+            res = {}
+        return res
+
+    def unregister(self, plugin=None, name=None):
+        """ unregister a plugin object and all its contained hook implementations
+        from internal data structures. """
+        if name is None:
+            assert plugin is not None, "one of name or plugin needs to be specified"
+            name = self.get_name(plugin)
+
+        if plugin is None:
+            plugin = self.get_plugin(name)
+
+        # if self._name2plugin[name] == None registration was blocked: ignore
+        if self._name2plugin.get(name):
+            del self._name2plugin[name]
+
+        for hookcaller in self._plugin2hookcallers.pop(plugin, []):
+            hookcaller._remove_plugin(plugin)
+
+        return plugin
+
+    def set_blocked(self, name):
+        """ block registrations of the given name, unregister if already registered. """
+        self.unregister(name=name)
+        self._name2plugin[name] = None
+
+    def is_blocked(self, name):
+        """ return True if the name blogs registering plugins of that name. """
+        return name in self._name2plugin and self._name2plugin[name] is None
+
+    def add_hookspecs(self, module_or_class):
+        """ add new hook specifications defined in the given module_or_class.
+        Functions are recognized if they have been decorated accordingly. """
+        names = []
+        for name in dir(module_or_class):
+            spec_opts = self.parse_hookspec_opts(module_or_class, name)
+            if spec_opts is not None:
+                hc = getattr(self.hook, name, None)
+                if hc is None:
+                    hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts)
+                    setattr(self.hook, name, hc)
+                else:
+                    # plugins registered this hook without knowing the spec
+                    hc.set_specification(module_or_class, spec_opts)
+                    for hookfunction in (hc._wrappers + hc._nonwrappers):
+                        self._verify_hook(hc, hookfunction)
+                names.append(name)
+
+        if not names:
+            raise ValueError("did not find any %r hooks in %r" %
+                             (self.project_name, module_or_class))
+
+    def parse_hookspec_opts(self, module_or_class, name):
+        method = getattr(module_or_class, name)
+        return getattr(method, self.project_name + "_spec", None)
+
+    def get_plugins(self):
+        """ return the set of registered plugins. """
+        return set(self._plugin2hookcallers)
+
+    def is_registered(self, plugin):
+        """ Return True if the plugin is already registered. """
+        return plugin in self._plugin2hookcallers
+
+    def get_canonical_name(self, plugin):
+        """ Return canonical name for a plugin object. Note that a plugin
+        may be registered under a different name which was specified
+        by the caller of register(plugin, name). To obtain the name
+        of an registered plugin use ``get_name(plugin)`` instead."""
+        return getattr(plugin, "__name__", None) or str(id(plugin))
+
+    def get_plugin(self, name):
+        """ Return a plugin or None for the given name. """
+        return self._name2plugin.get(name)
+
+    def has_plugin(self, name):
+        """ Return True if a plugin with the given name is registered. """
+        return self.get_plugin(name) is not None
+
+    def get_name(self, plugin):
+        """ Return name for registered plugin or None if not registered. """
+        for name, val in self._name2plugin.items():
+            if plugin == val:
+                return name
+
+    def _verify_hook(self, hook, hookimpl):
+        if hook.is_historic() and hookimpl.hookwrapper:
+            raise PluginValidationError(
+                "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %
+                (hookimpl.plugin_name, hook.name))
+
+        # positional arg checking
+        notinspec = set(hookimpl.argnames) - set(hook.argnames)
+        if notinspec:
+            raise PluginValidationError(
+                "Plugin %r for hook %r\nhookimpl definition: %s\n"
+                "Argument(s) %s are declared in the hookimpl but "
+                "can not be found in the hookspec" %
+                (hookimpl.plugin_name, hook.name,
+                 _formatdef(hookimpl.function), notinspec)
+            )
+
+    def check_pending(self):
+        """ Verify that all hooks which have not been verified against
+        a hook specification are optional, otherwise raise PluginValidationError"""
+        for name in self.hook.__dict__:
+            if name[0] != "_":
+                hook = getattr(self.hook, name)
+                if not hook.has_spec():
+                    for hookimpl in (hook._wrappers + hook._nonwrappers):
+                        if not hookimpl.optionalhook:
+                            raise PluginValidationError(
+                                "unknown hook %r in plugin %r" %
+                                (name, hookimpl.plugin))
+
+    def load_setuptools_entrypoints(self, entrypoint_name):
+        """ Load modules from querying the specified setuptools entrypoint name.
+        Return the number of loaded plugins. """
+        from pkg_resources import (iter_entry_points, DistributionNotFound,
+                                   VersionConflict)
+        for ep in iter_entry_points(entrypoint_name):
+            # is the plugin registered or blocked?
+            if self.get_plugin(ep.name) or self.is_blocked(ep.name):
+                continue
+            try:
+                plugin = ep.load()
+            except DistributionNotFound:
+                continue
+            except VersionConflict as e:
+                raise PluginValidationError(
+                    "Plugin %r could not be loaded: %s!" % (ep.name, e))
+            self.register(plugin, name=ep.name)
+            self._plugin_distinfo.append((plugin, ep.dist))
+        return len(self._plugin_distinfo)
+
+    def list_plugin_distinfo(self):
+        """ return list of distinfo/plugin tuples for all setuptools registered
+        plugins. """
+        return list(self._plugin_distinfo)
+
+    def list_name_plugin(self):
+        """ return list of name/plugin pairs. """
+        return list(self._name2plugin.items())
+
+    def get_hookcallers(self, plugin):
+        """ get all hook callers for the specified plugin. """
+        return self._plugin2hookcallers.get(plugin)
+
+    def add_hookcall_monitoring(self, before, after):
+        """ add before/after tracing functions for all hooks
+        and return an undo function which, when called,
+        will remove the added tracers.
+
+        ``before(hook_name, hook_impls, kwargs)`` will be called ahead
+        of all hook calls and receive a hookcaller instance, a list
+        of HookImpl instances and the keyword arguments for the hook call.
+
+        ``after(outcome, hook_name, hook_impls, kwargs)`` receives the
+        same arguments as ``before`` but also a :py:class:`_Result`` object
+        which represents the result of the overall hook call.
+        """
+        return _TracedHookExecution(self, before, after).undo
+
+    def enable_tracing(self):
+        """ enable tracing of hook calls and return an undo function. """
+        hooktrace = self.hook._trace
+
+        def before(hook_name, methods, kwargs):
+            hooktrace.root.indent += 1
+            hooktrace(hook_name, kwargs)
+
+        def after(outcome, hook_name, methods, kwargs):
+            if outcome.excinfo is None:
+                hooktrace("finish", hook_name, "-->", outcome.get_result())
+            hooktrace.root.indent -= 1
+
+        return self.add_hookcall_monitoring(before, after)
+
+    def subset_hook_caller(self, name, remove_plugins):
+        """ Return a new _HookCaller instance for the named method
+        which manages calls to all registered plugins except the
+        ones from remove_plugins. """
+        orig = getattr(self.hook, name)
+        plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
+        if plugins_to_remove:
+            hc = _HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class,
+                             orig.spec_opts)
+            for hookimpl in (orig._wrappers + orig._nonwrappers):
+                plugin = hookimpl.plugin
+                if plugin not in plugins_to_remove:
+                    hc._add_hookimpl(hookimpl)
+                    # we also keep track of this hook caller so it
+                    # gets properly removed on plugin unregistration
+                    self._plugin2hookcallers.setdefault(plugin, []).append(hc)
+            return hc
+        return orig
+
+
+def varnames(func):
+    """Return tuple of positional and keywrord argument names for a function,
+    method, class or callable.
+
+    In case of a class, its ``__init__`` method is considered.
+    For methods the ``self`` parameter is not included.
+    """
+    cache = getattr(func, "__dict__", {})
+    try:
+        return cache["_varnames"]
+    except KeyError:
+        pass
+
+    if inspect.isclass(func):
+        try:
+            func = func.__init__
+        except AttributeError:
+            return (), ()
+    elif not inspect.isroutine(func):  # callable object?
+        try:
+            func = getattr(func, '__call__', func)
+        except Exception:
+            return ()
+
+    try:  # func MUST be a function or method here or we won't parse any args
+        spec = _getargspec(func)
+    except TypeError:
+        return (), ()
+
+    args, defaults = tuple(spec.args), spec.defaults
+    if defaults:
+        index = -len(defaults)
+        args, defaults = args[:index], tuple(args[index:])
+    else:
+        defaults = ()
+
+    # strip any implicit instance arg
+    if args:
+        if inspect.ismethod(func) or (
+            '.' in getattr(func, '__qualname__', ()) and args[0] == 'self'
+        ):
+            args = args[1:]
+
+    assert "self" not in args  # best naming practises check?
+    try:
+        cache["_varnames"] = args, defaults
+    except TypeError:
+        pass
+    return args, defaults
+
+
+class _HookRelay(object):
+    """ hook holder object for performing 1:N hook calls where N is the number
+    of registered plugins.
+
+    """
+
+    def __init__(self, trace):
+        self._trace = trace
+
+
+class _HookCaller(object):
+    def __init__(self, name, hook_execute, specmodule_or_class=None,
+                 spec_opts=None):
+        self.name = name
+        self._wrappers = []
+        self._nonwrappers = []
+        self._hookexec = hook_execute
+        self._specmodule_or_class = None
+        self.argnames = None
+        self.kwargnames = None
+        self.multicall = _multicall
+        self.spec_opts = spec_opts or {}
+        if specmodule_or_class is not None:
+            self.set_specification(specmodule_or_class, spec_opts)
+
+    def has_spec(self):
+        return self._specmodule_or_class is not None
+
+    def set_specification(self, specmodule_or_class, spec_opts):
+        assert not self.has_spec()
+        self._specmodule_or_class = specmodule_or_class
+        specfunc = getattr(specmodule_or_class, self.name)
+        # get spec arg signature
+        argnames, self.kwargnames = varnames(specfunc)
+        self.argnames = ["__multicall__"] + list(argnames)
+        self.spec_opts.update(spec_opts)
+        if spec_opts.get("historic"):
+            self._call_history = []
+
+    def is_historic(self):
+        return hasattr(self, "_call_history")
+
+    def _remove_plugin(self, plugin):
+        def remove(wrappers):
+            for i, method in enumerate(wrappers):
+                if method.plugin == plugin:
+                    del wrappers[i]
+                    return True
+        if remove(self._wrappers) is None:
+            if remove(self._nonwrappers) is None:
+                raise ValueError("plugin %r not found" % (plugin,))
+
+    def _add_hookimpl(self, hookimpl):
+        """A an implementation to the callback chain.
+        """
+        if hookimpl.hookwrapper:
+            methods = self._wrappers
+        else:
+            methods = self._nonwrappers
+
+        if hookimpl.trylast:
+            methods.insert(0, hookimpl)
+        elif hookimpl.tryfirst:
+            methods.append(hookimpl)
+        else:
+            # find last non-tryfirst method
+            i = len(methods) - 1
+            while i >= 0 and methods[i].tryfirst:
+                i -= 1
+            methods.insert(i + 1, hookimpl)
+
+        if '__multicall__' in hookimpl.argnames:
+            warnings.warn(
+                "Support for __multicall__ is now deprecated and will be"
+                "removed in an upcoming release.",
+                DeprecationWarning
+            )
+            self.multicall = _legacymulticall
+
+    def __repr__(self):
+        return "<_HookCaller %r>" % (self.name,)
+
+    def __call__(self, *args, **kwargs):
+        if args:
+            raise TypeError("hook calling supports only keyword arguments")
+        assert not self.is_historic()
+        if self.argnames:
+            notincall = set(self.argnames) - set(['__multicall__']) - set(
+                kwargs.keys())
+            if notincall:
+                warnings.warn(
+                    "Argument(s) {} which are declared in the hookspec "
+                    "can not be found in this hook call"
+                    .format(tuple(notincall)),
+                    stacklevel=2,
+                )
+        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
+
+    def call_historic(self, proc=None, kwargs=None):
+        """ call the hook with given ``kwargs`` for all registered plugins and
+        for all plugins which will be registered afterwards.
+
+        If ``proc`` is not None it will be called for for each non-None result
+        obtained from a hook implementation.
+        """
+        self._call_history.append((kwargs or {}, proc))
+        # historizing hooks don't return results
+        res = self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
+        for x in res or []:
+            proc(x)
+
+    def call_extra(self, methods, kwargs):
+        """ Call the hook with some additional temporarily participating
+        methods using the specified kwargs as call parameters. """
+        old = list(self._nonwrappers), list(self._wrappers)
+        for method in methods:
+            opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
+            hookimpl = HookImpl(None, "<temp>", method, opts)
+            self._add_hookimpl(hookimpl)
+        try:
+            return self(**kwargs)
+        finally:
+            self._nonwrappers, self._wrappers = old
+
+    def _maybe_apply_history(self, method):
+        """Apply call history to a new hookimpl if it is marked as historic.
+        """
+        if self.is_historic():
+            for kwargs, proc in self._call_history:
+                res = self._hookexec(self, [method], kwargs)
+                if res and proc is not None:
+                    proc(res[0])
+
+
+class HookImpl(object):
+    def __init__(self, plugin, plugin_name, function, hook_impl_opts):
+        self.function = function
+        self.argnames, self.kwargnames = varnames(self.function)
+        self.plugin = plugin
+        self.opts = hook_impl_opts
+        self.plugin_name = plugin_name
+        self.__dict__.update(hook_impl_opts)
+
+
+if hasattr(inspect, 'getfullargspec'):
+    def _getargspec(func):
+        return inspect.getfullargspec(func)
+else:
+    def _getargspec(func):
+        return inspect.getargspec(func)
+
+
+if hasattr(inspect, 'signature'):
+    def _formatdef(func):
+        return "%s%s" % (
+            func.__name__,
+            str(inspect.signature(func))
+        )
+else:
+    def _formatdef(func):
+        return "%s%s" % (
+            func.__name__,
+            inspect.formatargspec(*inspect.getargspec(func))
+        )
diff --git a/tools/third_party/pluggy/pluggy/callers.py b/tools/third_party/pluggy/pluggy/callers.py
new file mode 100644
index 0000000..3ff67be
--- /dev/null
+++ b/tools/third_party/pluggy/pluggy/callers.py
@@ -0,0 +1,201 @@
+'''
+Call loop machinery
+'''
+import sys
+import warnings
+
+_py3 = sys.version_info > (3, 0)
+
+
+if not _py3:
+    exec("""
+def _reraise(cls, val, tb):
+    raise cls, val, tb
+""")
+
+
+def _raise_wrapfail(wrap_controller, msg):
+    co = wrap_controller.gi_code
+    raise RuntimeError("wrap_controller at %r %s:%d %s" %
+                       (co.co_name, co.co_filename, co.co_firstlineno, msg))
+
+
+class HookCallError(Exception):
+    """ Hook was called wrongly. """
+
+
+class _Result(object):
+    def __init__(self, result, excinfo):
+        self._result = result
+        self._excinfo = excinfo
+
+    @property
+    def excinfo(self):
+        return self._excinfo
+
+    @property
+    def result(self):
+        """Get the result(s) for this hook call (DEPRECATED in favor of ``get_result()``)."""
+        msg = 'Use get_result() which forces correct exception handling'
+        warnings.warn(DeprecationWarning(msg), stacklevel=2)
+        return self._result
+
+    @classmethod
+    def from_call(cls, func):
+        __tracebackhide__ = True
+        result = excinfo = None
+        try:
+            result = func()
+        except BaseException:
+            excinfo = sys.exc_info()
+
+        return cls(result, excinfo)
+
+    def force_result(self, result):
+        """Force the result(s) to ``result``.
+
+        If the hook was marked as a ``firstresult`` a single value should
+        be set otherwise set a (modified) list of results. Any exceptions
+        found during invocation will be deleted.
+        """
+        self._result = result
+        self._excinfo = None
+
+    def get_result(self):
+        """Get the result(s) for this hook call.
+
+        If the hook was marked as a ``firstresult`` only a single value
+        will be returned otherwise a list of results.
+        """
+        __tracebackhide__ = True
+        if self._excinfo is None:
+            return self._result
+        else:
+            ex = self._excinfo
+            if _py3:
+                raise ex[1].with_traceback(ex[2])
+            _reraise(*ex)  # noqa
+
+
+def _wrapped_call(wrap_controller, func):
+    """ Wrap calling to a function with a generator which needs to yield
+    exactly once.  The yield point will trigger calling the wrapped function
+    and return its ``_Result`` to the yield point.  The generator then needs
+    to finish (raise StopIteration) in order for the wrapped call to complete.
+    """
+    try:
+        next(wrap_controller)   # first yield
+    except StopIteration:
+        _raise_wrapfail(wrap_controller, "did not yield")
+    call_outcome = _Result.from_call(func)
+    try:
+        wrap_controller.send(call_outcome)
+        _raise_wrapfail(wrap_controller, "has second yield")
+    except StopIteration:
+        pass
+    return call_outcome.get_result()
+
+
+class _LegacyMultiCall(object):
+    """ execute a call into multiple python functions/methods. """
+
+    # XXX note that the __multicall__ argument is supported only
+    # for pytest compatibility reasons.  It was never officially
+    # supported there and is explicitely deprecated since 2.8
+    # so we can remove it soon, allowing to avoid the below recursion
+    # in execute() and simplify/speed up the execute loop.
+
+    def __init__(self, hook_impls, kwargs, firstresult=False):
+        self.hook_impls = hook_impls
+        self.caller_kwargs = kwargs  # come from _HookCaller.__call__()
+        self.caller_kwargs["__multicall__"] = self
+        self.firstresult = firstresult
+
+    def execute(self):
+        caller_kwargs = self.caller_kwargs
+        self.results = results = []
+        firstresult = self.firstresult
+
+        while self.hook_impls:
+            hook_impl = self.hook_impls.pop()
+            try:
+                args = [caller_kwargs[argname] for argname in hook_impl.argnames]
+            except KeyError:
+                for argname in hook_impl.argnames:
+                    if argname not in caller_kwargs:
+                        raise HookCallError(
+                            "hook call must provide argument %r" % (argname,))
+            if hook_impl.hookwrapper:
+                return _wrapped_call(hook_impl.function(*args), self.execute)
+            res = hook_impl.function(*args)
+            if res is not None:
+                if firstresult:
+                    return res
+                results.append(res)
+
+        if not firstresult:
+            return results
+
+    def __repr__(self):
+        status = "%d meths" % (len(self.hook_impls),)
+        if hasattr(self, "results"):
+            status = ("%d results, " % len(self.results)) + status
+        return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)
+
+
+def _legacymulticall(hook_impls, caller_kwargs, firstresult=False):
+    return _LegacyMultiCall(
+        hook_impls, caller_kwargs, firstresult=firstresult).execute()
+
+
+def _multicall(hook_impls, caller_kwargs, firstresult=False):
+    """Execute a call into multiple python functions/methods and return the
+    result(s).
+
+    ``caller_kwargs`` comes from _HookCaller.__call__().
+    """
+    __tracebackhide__ = True
+    results = []
+    excinfo = None
+    try:  # run impl and wrapper setup functions in a loop
+        teardowns = []
+        try:
+            for hook_impl in reversed(hook_impls):
+                try:
+                    args = [caller_kwargs[argname] for argname in hook_impl.argnames]
+                except KeyError:
+                    for argname in hook_impl.argnames:
+                        if argname not in caller_kwargs:
+                            raise HookCallError(
+                                "hook call must provide argument %r" % (argname,))
+
+                if hook_impl.hookwrapper:
+                    try:
+                        gen = hook_impl.function(*args)
+                        next(gen)   # first yield
+                        teardowns.append(gen)
+                    except StopIteration:
+                        _raise_wrapfail(gen, "did not yield")
+                else:
+                    res = hook_impl.function(*args)
+                    if res is not None:
+                        results.append(res)
+                        if firstresult:  # halt further impl calls
+                            break
+        except BaseException:
+            excinfo = sys.exc_info()
+    finally:
+        if firstresult:  # first result hooks return a single value
+            outcome = _Result(results[0] if results else None, excinfo)
+        else:
+            outcome = _Result(results, excinfo)
+
+        # run all wrapper post-yield blocks
+        for gen in reversed(teardowns):
+            try:
+                gen.send(outcome)
+                _raise_wrapfail(gen, "has second yield")
+            except StopIteration:
+                pass
+
+        return outcome.get_result()
diff --git a/tools/third_party/pluggy/setup.cfg b/tools/third_party/pluggy/setup.cfg
new file mode 100644
index 0000000..7e729cb
--- /dev/null
+++ b/tools/third_party/pluggy/setup.cfg
@@ -0,0 +1,8 @@
+[bdist_wheel]
+universal=1
+
+[metadata]
+license_file=LICENSE
+
+[devpi:upload]
+formats=sdist.tgz,bdist_wheel
diff --git a/tools/third_party/pluggy/setup.py b/tools/third_party/pluggy/setup.py
new file mode 100644
index 0000000..b7c0f69
--- /dev/null
+++ b/tools/third_party/pluggy/setup.py
@@ -0,0 +1,51 @@
+import os
+from setuptools import setup
+
+classifiers = [
+    'Development Status :: 4 - Beta',
+    'Intended Audience :: Developers',
+    'License :: OSI Approved :: MIT License',
+    'Operating System :: POSIX',
+    'Operating System :: Microsoft :: Windows',
+    'Operating System :: MacOS :: MacOS X',
+    'Topic :: Software Development :: Testing',
+    'Topic :: Software Development :: Libraries',
+    'Topic :: Utilities',
+    'Programming Language :: Python :: Implementation :: CPython',
+    'Programming Language :: Python :: Implementation :: PyPy'] + [
+    ('Programming Language :: Python :: %s' % x) for x in
+    '2 2.7 3 3.4 3.5 3.6'.split()]
+
+with open('README.rst') as fd:
+    long_description = fd.read()
+
+
+def get_version():
+    p = os.path.join(os.path.dirname(
+                     os.path.abspath(__file__)), "pluggy/__init__.py")
+    with open(p) as f:
+        for line in f.readlines():
+            if "__version__" in line:
+                return line.strip().split("=")[-1].strip(" '")
+    raise ValueError("could not read version")
+
+
+def main():
+    setup(
+        name='pluggy',
+        description='plugin and hook calling mechanisms for python',
+        long_description=long_description,
+        version=get_version(),
+        license='MIT license',
+        platforms=['unix', 'linux', 'osx', 'win32'],
+        author='Holger Krekel',
+        author_email='holger@merlinux.eu',
+        url='https://github.com/pytest-dev/pluggy',
+        python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
+        classifiers=classifiers,
+        packages=['pluggy'],
+    )
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/third_party/pluggy/testing/benchmark.py b/tools/third_party/pluggy/testing/benchmark.py
new file mode 100644
index 0000000..5a913e9
--- /dev/null
+++ b/tools/third_party/pluggy/testing/benchmark.py
@@ -0,0 +1,59 @@
+"""
+Benchmarking and performance tests.
+"""
+import pytest
+from pluggy import (_multicall, _legacymulticall, HookImpl, HookspecMarker,
+                    HookimplMarker)
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def MC(methods, kwargs, callertype, firstresult=False):
+    hookfuncs = []
+    for method in methods:
+        f = HookImpl(None, "<temp>", method, method.example_impl)
+        hookfuncs.append(f)
+    return callertype(hookfuncs, kwargs, {"firstresult": firstresult})
+
+
+@hookimpl
+def hook(arg1, arg2, arg3):
+    return arg1, arg2, arg3
+
+
+@hookimpl(hookwrapper=True)
+def wrapper(arg1, arg2, arg3):
+    yield
+
+
+@pytest.fixture(
+    params=[10, 100],
+    ids="hooks={}".format,
+)
+def hooks(request):
+    return [hook for i in range(request.param)]
+
+
+@pytest.fixture(
+    params=[10, 100],
+    ids="wrappers={}".format,
+)
+def wrappers(request):
+    return [wrapper for i in range(request.param)]
+
+
+@pytest.fixture(
+    params=[_multicall, _legacymulticall],
+    ids=lambda item: item.__name__
+)
+def callertype(request):
+    return request.param
+
+
+def inner_exec(methods, callertype):
+    return MC(methods, {'arg1': 1, 'arg2': 2, 'arg3': 3}, callertype)
+
+
+def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype):
+    benchmark(inner_exec, hooks + wrappers, callertype)
diff --git a/tools/third_party/pluggy/testing/conftest.py b/tools/third_party/pluggy/testing/conftest.py
new file mode 100644
index 0000000..3d61a34
--- /dev/null
+++ b/tools/third_party/pluggy/testing/conftest.py
@@ -0,0 +1,30 @@
+import pytest
+
+
+@pytest.fixture(
+    params=[
+        lambda spec: spec,
+        lambda spec: spec()
+    ],
+    ids=[
+        "spec-is-class",
+        "spec-is-instance"
+    ],
+)
+def he_pm(request, pm):
+    from pluggy import HookspecMarker
+    hookspec = HookspecMarker("example")
+
+    class Hooks(object):
+        @hookspec
+        def he_method1(self, arg):
+            return arg + 1
+
+    pm.add_hookspecs(request.param(Hooks))
+    return pm
+
+
+@pytest.fixture
+def pm():
+    from pluggy import PluginManager
+    return PluginManager("example")
diff --git a/tools/third_party/pluggy/testing/test_details.py b/tools/third_party/pluggy/testing/test_details.py
new file mode 100644
index 0000000..2fad198
--- /dev/null
+++ b/tools/third_party/pluggy/testing/test_details.py
@@ -0,0 +1,103 @@
+import warnings
+
+import pytest
+
+from pluggy import PluginManager, HookimplMarker, HookspecMarker, _Result
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_parse_hookimpl_override():
+    class MyPluginManager(PluginManager):
+        def parse_hookimpl_opts(self, module_or_class, name):
+            opts = PluginManager.parse_hookimpl_opts(
+                self, module_or_class, name)
+            if opts is None:
+                if name.startswith("x1"):
+                    opts = {}
+            return opts
+
+    class Plugin(object):
+        def x1meth(self):
+            pass
+
+        @hookimpl(hookwrapper=True, tryfirst=True)
+        def x1meth2(self):
+            pass
+
+    class Spec(object):
+        @hookspec
+        def x1meth(self):
+            pass
+
+        @hookspec
+        def x1meth2(self):
+            pass
+
+    pm = MyPluginManager(hookspec.project_name)
+    pm.register(Plugin())
+    pm.add_hookspecs(Spec)
+    assert not pm.hook.x1meth._nonwrappers[0].hookwrapper
+    assert not pm.hook.x1meth._nonwrappers[0].tryfirst
+    assert not pm.hook.x1meth._nonwrappers[0].trylast
+    assert not pm.hook.x1meth._nonwrappers[0].optionalhook
+
+    assert pm.hook.x1meth2._wrappers[0].tryfirst
+    assert pm.hook.x1meth2._wrappers[0].hookwrapper
+
+
+def test_plugin_getattr_raises_errors():
+    """Pluggy must be able to handle plugins which raise weird exceptions
+    when getattr() gets called (#11).
+    """
+    class DontTouchMe(object):
+        def __getattr__(self, x):
+            raise Exception('cant touch me')
+
+    class Module(object):
+        pass
+
+    module = Module()
+    module.x = DontTouchMe()
+
+    pm = PluginManager(hookspec.project_name)
+    # register() would raise an error
+    pm.register(module, 'donttouch')
+    assert pm.get_plugin('donttouch') is module
+
+
+def test_warning_on_call_vs_hookspec_arg_mismatch():
+    """Verify that is a hook is called with less arguments then defined in the
+    spec that a warning is emitted.
+    """
+    class Spec:
+        @hookspec
+        def myhook(self, arg1, arg2):
+            pass
+
+    class Plugin:
+        @hookimpl
+        def myhook(self, arg1):
+            pass
+
+    pm = PluginManager(hookspec.project_name)
+    pm.register(Plugin())
+    pm.add_hookspecs(Spec())
+
+    with warnings.catch_warnings(record=True) as warns:
+        warnings.simplefilter('always')
+
+        # calling should trigger a warning
+        pm.hook.myhook(arg1=1)
+
+        assert len(warns) == 1
+        warning = warns[-1]
+        assert issubclass(warning.category, Warning)
+        assert "Argument(s) ('arg2',)" in str(warning.message)
+
+
+def test_result_deprecated():
+    r = _Result(10, None)
+    with pytest.deprecated_call():
+        assert r.result == 10
diff --git a/tools/third_party/pluggy/testing/test_helpers.py b/tools/third_party/pluggy/testing/test_helpers.py
new file mode 100644
index 0000000..b178096
--- /dev/null
+++ b/tools/third_party/pluggy/testing/test_helpers.py
@@ -0,0 +1,68 @@
+from pluggy import _formatdef, varnames
+
+
+def test_varnames():
+    def f(x):
+        i = 3  # noqa
+
+    class A(object):
+        def f(self, y):
+            pass
+
+    class B(object):
+        def __call__(self, z):
+            pass
+
+    assert varnames(f) == (("x",), ())
+    assert varnames(A().f) == (('y',), ())
+    assert varnames(B()) == (('z',), ())
+
+
+def test_varnames_default():
+    def f(x, y=3):
+        pass
+
+    assert varnames(f) == (("x",), ("y",))
+
+
+def test_varnames_class():
+    class C(object):
+        def __init__(self, x):
+            pass
+
+    class D(object):
+        pass
+
+    class E(object):
+        def __init__(self, x):
+            pass
+
+    class F(object):
+        pass
+
+    assert varnames(C) == (("x",), ())
+    assert varnames(D) == ((), ())
+    assert varnames(E) == (("x",), ())
+    assert varnames(F) == ((), ())
+
+
+def test_formatdef():
+    def function1():
+        pass
+
+    assert _formatdef(function1) == 'function1()'
+
+    def function2(arg1):
+        pass
+
+    assert _formatdef(function2) == "function2(arg1)"
+
+    def function3(arg1, arg2="qwe"):
+        pass
+
+    assert _formatdef(function3) == "function3(arg1, arg2='qwe')"
+
+    def function4(arg1, *args, **kwargs):
+        pass
+
+    assert _formatdef(function4) == "function4(arg1, *args, **kwargs)"
diff --git a/tools/third_party/pluggy/testing/test_hookrelay.py b/tools/third_party/pluggy/testing/test_hookrelay.py
new file mode 100644
index 0000000..5e7821b
--- /dev/null
+++ b/tools/third_party/pluggy/testing/test_hookrelay.py
@@ -0,0 +1,210 @@
+import pytest
+from pluggy import PluginValidationError, HookimplMarker, HookspecMarker
+
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_happypath(pm):
+    class Api(object):
+        @hookspec
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+    hook = pm.hook
+    assert hasattr(hook, 'hello')
+    assert repr(hook.hello).find("hello") != -1
+
+    class Plugin(object):
+        @hookimpl
+        def hello(self, arg):
+            return arg + 1
+
+    plugin = Plugin()
+    pm.register(plugin)
+    out = hook.hello(arg=3)
+    assert out == [4]
+    assert not hasattr(hook, 'world')
+    pm.unregister(plugin)
+    assert hook.hello(arg=3) == []
+
+
+def test_argmismatch(pm):
+    class Api(object):
+        @hookspec
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+
+    class Plugin(object):
+        @hookimpl
+        def hello(self, argwrong):
+            pass
+
+    with pytest.raises(PluginValidationError) as exc:
+        pm.register(Plugin())
+
+    assert "argwrong" in str(exc.value)
+
+
+def test_only_kwargs(pm):
+    class Api(object):
+        @hookspec
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+    with pytest.raises(TypeError) as exc:
+        pm.hook.hello(3)
+
+    comprehensible = "hook calling supports only keyword arguments"
+    assert comprehensible in str(exc.value)
+
+
+def test_call_order(pm):
+    class Api(object):
+        @hookspec
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+
+    class Plugin1(object):
+        @hookimpl
+        def hello(self, arg):
+            return 1
+
+    class Plugin2(object):
+        @hookimpl
+        def hello(self, arg):
+            return 2
+
+    class Plugin3(object):
+        @hookimpl
+        def hello(self, arg):
+            return 3
+
+    class Plugin4(object):
+        @hookimpl(hookwrapper=True)
+        def hello(self, arg):
+            assert arg == 0
+            outcome = yield
+            assert outcome.get_result() == [3, 2, 1]
+
+    pm.register(Plugin1())
+    pm.register(Plugin2())
+    pm.register(Plugin3())
+    pm.register(Plugin4())  # hookwrapper should get same list result
+    res = pm.hook.hello(arg=0)
+    assert res == [3, 2, 1]
+
+
+def test_firstresult_definition(pm):
+    class Api(object):
+        @hookspec(firstresult=True)
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+
+    class Plugin1(object):
+        @hookimpl
+        def hello(self, arg):
+            return arg + 1
+
+    class Plugin2(object):
+        @hookimpl
+        def hello(self, arg):
+            return arg - 1
+
+    class Plugin3(object):
+        @hookimpl
+        def hello(self, arg):
+            return None
+
+    class Plugin4(object):
+        @hookimpl(hookwrapper=True)
+        def hello(self, arg):
+            assert arg == 3
+            outcome = yield
+            assert outcome.get_result() == 2
+
+    pm.register(Plugin1())  # discarded - not the last registered plugin
+    pm.register(Plugin2())  # used as result
+    pm.register(Plugin3())  # None result is ignored
+    pm.register(Plugin4())  # hookwrapper should get same non-list result
+    res = pm.hook.hello(arg=3)
+    assert res == 2
+
+
+def test_firstresult_force_result(pm):
+    """Verify forcing a result in a wrapper.
+    """
+    class Api(object):
+        @hookspec(firstresult=True)
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+
+    class Plugin1(object):
+        @hookimpl
+        def hello(self, arg):
+            return arg + 1
+
+    class Plugin2(object):
+        @hookimpl(hookwrapper=True)
+        def hello(self, arg):
+            assert arg == 3
+            outcome = yield
+            assert outcome.get_result() == 4
+            outcome.force_result(0)
+
+    class Plugin3(object):
+        @hookimpl
+        def hello(self, arg):
+            return None
+
+    pm.register(Plugin1())
+    pm.register(Plugin2())  # wrapper
+    pm.register(Plugin3())  # ignored since returns None
+    res = pm.hook.hello(arg=3)
+    assert res == 0  # this result is forced and not a list
+
+
+def test_firstresult_returns_none(pm):
+    """If None results are returned by underlying implementations ensure
+    the multi-call loop returns a None value.
+    """
+    class Api(object):
+        @hookspec(firstresult=True)
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+
+    class Plugin1(object):
+        @hookimpl
+        def hello(self, arg):
+            return None
+
+    pm.register(Plugin1())
+    res = pm.hook.hello(arg=3)
+    assert res is None
+
+
+def test_firstresult_no_plugin(pm):
+    """If no implementations/plugins have been registered for a firstresult
+    hook the multi-call loop should return a None value.
+    """
+    class Api(object):
+        @hookspec(firstresult=True)
+        def hello(self, arg):
+            "api hook 1"
+
+    pm.add_hookspecs(Api)
+    res = pm.hook.hello(arg=3)
+    assert res is None
diff --git a/tools/third_party/pluggy/testing/test_method_ordering.py b/tools/third_party/pluggy/testing/test_method_ordering.py
new file mode 100644
index 0000000..9584a0a
--- /dev/null
+++ b/tools/third_party/pluggy/testing/test_method_ordering.py
@@ -0,0 +1,322 @@
+import pytest
+
+
+import sys
+import types
+
+from pluggy import PluginManager, HookImpl, HookimplMarker, HookspecMarker
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+@pytest.fixture
+def hc(pm):
+    class Hooks(object):
+        @hookspec
+        def he_method1(self, arg):
+            pass
+    pm.add_hookspecs(Hooks)
+    return pm.hook.he_method1
+
+
+@pytest.fixture
+def addmeth(hc):
+    def addmeth(tryfirst=False, trylast=False, hookwrapper=False):
+        def wrap(func):
+            hookimpl(tryfirst=tryfirst, trylast=trylast,
+                     hookwrapper=hookwrapper)(func)
+            hc._add_hookimpl(HookImpl(None, "<temp>", func, func.example_impl))
+            return func
+        return wrap
+    return addmeth
+
+
+def funcs(hookmethods):
+    return [hookmethod.function for hookmethod in hookmethods]
+
+
+def test_adding_nonwrappers(hc, addmeth):
+    @addmeth()
+    def he_method1():
+        pass
+
+    @addmeth()
+    def he_method2():
+        pass
+
+    @addmeth()
+    def he_method3():
+        pass
+    assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3]
+
+
+def test_adding_nonwrappers_trylast(hc, addmeth):
+    @addmeth()
+    def he_method1_middle():
+        pass
+
+    @addmeth(trylast=True)
+    def he_method1():
+        pass
+
+    @addmeth()
+    def he_method1_b():
+        pass
+    assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b]
+
+
+def test_adding_nonwrappers_trylast3(hc, addmeth):
+    @addmeth()
+    def he_method1_a():
+        pass
+
+    @addmeth(trylast=True)
+    def he_method1_b():
+        pass
+
+    @addmeth()
+    def he_method1_c():
+        pass
+
+    @addmeth(trylast=True)
+    def he_method1_d():
+        pass
+    assert funcs(hc._nonwrappers) == \
+        [he_method1_d, he_method1_b, he_method1_a, he_method1_c]
+
+
+def test_adding_nonwrappers_trylast2(hc, addmeth):
+    @addmeth()
+    def he_method1_middle():
+        pass
+
+    @addmeth()
+    def he_method1_b():
+        pass
+
+    @addmeth(trylast=True)
+    def he_method1():
+        pass
+    assert funcs(hc._nonwrappers) == \
+        [he_method1, he_method1_middle, he_method1_b]
+
+
+def test_adding_nonwrappers_tryfirst(hc, addmeth):
+    @addmeth(tryfirst=True)
+    def he_method1():
+        pass
+
+    @addmeth()
+    def he_method1_middle():
+        pass
+
+    @addmeth()
+    def he_method1_b():
+        pass
+    assert funcs(hc._nonwrappers) == [
+        he_method1_middle, he_method1_b, he_method1]
+
+
+def test_adding_wrappers_ordering(hc, addmeth):
+    @addmeth(hookwrapper=True)
+    def he_method1():
+        pass
+
+    @addmeth()
+    def he_method1_middle():
+        pass
+
+    @addmeth(hookwrapper=True)
+    def he_method3():
+        pass
+
+    assert funcs(hc._nonwrappers) == [he_method1_middle]
+    assert funcs(hc._wrappers) == [he_method1, he_method3]
+
+
+def test_adding_wrappers_ordering_tryfirst(hc, addmeth):
+    @addmeth(hookwrapper=True, tryfirst=True)
+    def he_method1():
+        pass
+
+    @addmeth(hookwrapper=True)
+    def he_method2():
+        pass
+
+    assert hc._nonwrappers == []
+    assert funcs(hc._wrappers) == [he_method2, he_method1]
+
+
+def test_hookspec(pm):
+    class HookSpec(object):
+        @hookspec()
+        def he_myhook1(arg1):
+            pass
+
+        @hookspec(firstresult=True)
+        def he_myhook2(arg1):
+            pass
+
+        @hookspec(firstresult=False)
+        def he_myhook3(arg1):
+            pass
+
+    pm.add_hookspecs(HookSpec)
+    assert not pm.hook.he_myhook1.spec_opts["firstresult"]
+    assert pm.hook.he_myhook2.spec_opts["firstresult"]
+    assert not pm.hook.he_myhook3.spec_opts["firstresult"]
+
+
+@pytest.mark.parametrize('name', ["hookwrapper", "optionalhook", "tryfirst", "trylast"])
+@pytest.mark.parametrize('val', [True, False])
+def test_hookimpl(name, val):
+    @hookimpl(**{name: val})
+    def he_myhook1(arg1):
+        pass
+    if val:
+        assert he_myhook1.example_impl.get(name)
+    else:
+        assert not hasattr(he_myhook1, name)
+
+
+def test_load_setuptools_instantiation(monkeypatch, pm):
+    pkg_resources = pytest.importorskip("pkg_resources")
+
+    def my_iter(name):
+        assert name == "hello"
+
+        class EntryPoint(object):
+            name = "myname"
+            dist = None
+
+            def load(self):
+                class PseudoPlugin(object):
+                    x = 42
+                return PseudoPlugin()
+
+        return iter([EntryPoint()])
+
+    monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+    num = pm.load_setuptools_entrypoints("hello")
+    assert num == 1
+    plugin = pm.get_plugin("myname")
+    assert plugin.x == 42
+    assert pm.list_plugin_distinfo() == [(plugin, None)]
+
+
+def test_load_setuptools_not_installed(monkeypatch, pm):
+    monkeypatch.setitem(
+        sys.modules, 'pkg_resources',
+        types.ModuleType("pkg_resources"))
+
+    with pytest.raises(ImportError):
+        pm.load_setuptools_entrypoints("qwe")
+
+
+def test_add_tracefuncs(he_pm):
+    out = []
+
+    class api1(object):
+        @hookimpl
+        def he_method1(self):
+            out.append("he_method1-api1")
+
+    class api2(object):
+        @hookimpl
+        def he_method1(self):
+            out.append("he_method1-api2")
+
+    he_pm.register(api1())
+    he_pm.register(api2())
+
+    def before(hook_name, hook_impls, kwargs):
+        out.append((hook_name, list(hook_impls), kwargs))
+
+    def after(outcome, hook_name, hook_impls, kwargs):
+        out.append((outcome, hook_name, list(hook_impls), kwargs))
+
+    undo = he_pm.add_hookcall_monitoring(before, after)
+
+    he_pm.hook.he_method1(arg=1)
+    assert len(out) == 4
+    assert out[0][0] == "he_method1"
+    assert len(out[0][1]) == 2
+    assert isinstance(out[0][2], dict)
+    assert out[1] == "he_method1-api2"
+    assert out[2] == "he_method1-api1"
+    assert len(out[3]) == 4
+    assert out[3][1] == out[0][0]
+
+    undo()
+    he_pm.hook.he_method1(arg=1)
+    assert len(out) == 4 + 2
+
+
+def test_hook_tracing(he_pm):
+    saveindent = []
+
+    class api1(object):
+        @hookimpl
+        def he_method1(self):
+            saveindent.append(he_pm.trace.root.indent)
+
+    class api2(object):
+        @hookimpl
+        def he_method1(self):
+            saveindent.append(he_pm.trace.root.indent)
+            raise ValueError()
+
+    he_pm.register(api1())
+    out = []
+    he_pm.trace.root.setwriter(out.append)
+    undo = he_pm.enable_tracing()
+    try:
+        indent = he_pm.trace.root.indent
+        he_pm.hook.he_method1(arg=1)
+        assert indent == he_pm.trace.root.indent
+        assert len(out) == 2
+        assert 'he_method1' in out[0]
+        assert 'finish' in out[1]
+
+        out[:] = []
+        he_pm.register(api2())
+
+        with pytest.raises(ValueError):
+            he_pm.hook.he_method1(arg=1)
+        assert he_pm.trace.root.indent == indent
+        assert saveindent[0] > indent
+    finally:
+        undo()
+
+
+@pytest.mark.parametrize('include_hookspec', [True, False])
+def test_prefix_hookimpl(include_hookspec):
+    pm = PluginManager(hookspec.project_name, "hello_")
+
+    if include_hookspec:
+        class HookSpec(object):
+            @hookspec
+            def hello_myhook(self, arg1):
+                """ add to arg1 """
+
+        pm.add_hookspecs(HookSpec)
+
+    class Plugin(object):
+        def hello_myhook(self, arg1):
+            return arg1 + 1
+
+    pm.register(Plugin())
+    pm.register(Plugin())
+    results = pm.hook.hello_myhook(arg1=17)
+    assert results == [18, 18]
+
+
+def test_prefix_hookimpl_dontmatch_module():
+    pm = PluginManager(hookspec.project_name, "hello_")
+
+    class BadPlugin(object):
+        hello_module = __import__('email')
+
+    pm.register(BadPlugin())
+    pm.check_pending()
diff --git a/tools/third_party/pluggy/testing/test_multicall.py b/tools/third_party/pluggy/testing/test_multicall.py
new file mode 100644
index 0000000..860a209
--- /dev/null
+++ b/tools/third_party/pluggy/testing/test_multicall.py
@@ -0,0 +1,194 @@
+import pytest
+
+from pluggy import _multicall, _legacymulticall, HookImpl, HookCallError
+from pluggy.callers import _LegacyMultiCall
+from pluggy import HookspecMarker, HookimplMarker
+
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_uses_copy_of_methods():
+    out = [lambda: 42]
+    mc = _LegacyMultiCall(out, {})
+    repr(mc)
+    out[:] = []
+    res = mc.execute()
+    return res == 42
+
+
+def MC(methods, kwargs, firstresult=False):
+    caller = _multicall
+    hookfuncs = []
+    for method in methods:
+        f = HookImpl(None, "<temp>", method, method.example_impl)
+        hookfuncs.append(f)
+        if '__multicall__' in f.argnames:
+            caller = _legacymulticall
+    return caller(hookfuncs, kwargs, firstresult=firstresult)
+
+
+def test_call_passing():
+    class P1(object):
+        @hookimpl
+        def m(self, __multicall__, x):
+            assert len(__multicall__.results) == 1
+            assert not __multicall__.hook_impls
+            return 17
+
+    class P2(object):
+        @hookimpl
+        def m(self, __multicall__, x):
+            assert __multicall__.results == []
+            assert __multicall__.hook_impls
+            return 23
+
+    p1 = P1()
+    p2 = P2()
+    reslist = MC([p1.m, p2.m], {"x": 23})
+    assert len(reslist) == 2
+    # ensure reversed order
+    assert reslist == [23, 17]
+
+
+def test_keyword_args():
+    @hookimpl
+    def f(x):
+        return x + 1
+
+    class A(object):
+        @hookimpl
+        def f(self, x, y):
+            return x + y
+
+    reslist = MC([f, A().f], dict(x=23, y=24))
+    assert reslist == [24 + 23, 24]
+
+
+def test_keyword_args_with_defaultargs():
+    @hookimpl
+    def f(x, z=1):
+        return x + z
+    reslist = MC([f], dict(x=23, y=24))
+    assert reslist == [24]
+
+
+def test_tags_call_error():
+    @hookimpl
+    def f(x):
+        return x
+    with pytest.raises(HookCallError):
+        MC([f], {})
+
+
+def test_call_subexecute():
+    @hookimpl
+    def m(__multicall__):
+        subresult = __multicall__.execute()
+        return subresult + 1
+
+    @hookimpl
+    def n():
+        return 1
+
+    res = MC([n, m], {}, firstresult=True)
+    assert res == 2
+
+
+def test_call_none_is_no_result():
+    @hookimpl
+    def m1():
+        return 1
+
+    @hookimpl
+    def m2():
+        return None
+
+    res = MC([m1, m2], {}, firstresult=True)
+    assert res == 1
+    res = MC([m1, m2], {}, {})
+    assert res == [1]
+
+
+def test_hookwrapper():
+    out = []
+
+    @hookimpl(hookwrapper=True)
+    def m1():
+        out.append("m1 init")
+        yield None
+        out.append("m1 finish")
+
+    @hookimpl
+    def m2():
+        out.append("m2")
+        return 2
+
+    res = MC([m2, m1], {})
+    assert res == [2]
+    assert out == ["m1 init", "m2", "m1 finish"]
+    out[:] = []
+    res = MC([m2, m1], {}, firstresult=True)
+    assert res == 2
+    assert out == ["m1 init", "m2", "m1 finish"]
+
+
+def test_hookwrapper_order():
+    out = []
+
+    @hookimpl(hookwrapper=True)
+    def m1():
+        out.append("m1 init")
+        yield 1
+        out.append("m1 finish")
+
+    @hookimpl(hookwrapper=True)
+    def m2():
+        out.append("m2 init")
+        yield 2
+        out.append("m2 finish")
+
+    res = MC([m2, m1], {})
+    assert res == []
+    assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
+
+
+def test_hookwrapper_not_yield():
+    @hookimpl(hookwrapper=True)
+    def m1():
+        pass
+
+    with pytest.raises(TypeError):
+        MC([m1], {})
+
+
+def test_hookwrapper_too_many_yield():
+    @hookimpl(hookwrapper=True)
+    def m1():
+        yield 1
+        yield 2
+
+    with pytest.raises(RuntimeError) as ex:
+        MC([m1], {})
+    assert "m1" in str(ex.value)
+    assert (__file__ + ':') in str(ex.value)
+
+
+@pytest.mark.parametrize("exc", [ValueError, SystemExit])
+def test_hookwrapper_exception(exc):
+    out = []
+
+    @hookimpl(hookwrapper=True)
+    def m1():
+        out.append("m1 init")
+        yield None
+        out.append("m1 finish")
+
+    @hookimpl
+    def m2():
+        raise exc
+
+    with pytest.raises(exc):
+        MC([m2, m1], {})
+    assert out == ["m1 init", "m1 finish"]
diff --git a/tools/third_party/pluggy/testing/test_pluginmanager.py b/tools/third_party/pluggy/testing/test_pluginmanager.py
new file mode 100644
index 0000000..e2c86cc
--- /dev/null
+++ b/tools/third_party/pluggy/testing/test_pluginmanager.py
@@ -0,0 +1,374 @@
+import pytest
+import types
+
+from pluggy import (PluginValidationError,
+                    HookCallError, HookimplMarker, HookspecMarker)
+
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_plugin_double_register(pm):
+    pm.register(42, name="abc")
+    with pytest.raises(ValueError):
+        pm.register(42, name="abc")
+    with pytest.raises(ValueError):
+        pm.register(42, name="def")
+
+
+def test_pm(pm):
+    class A(object):
+        pass
+
+    a1, a2 = A(), A()
+    pm.register(a1)
+    assert pm.is_registered(a1)
+    pm.register(a2, "hello")
+    assert pm.is_registered(a2)
+    out = pm.get_plugins()
+    assert a1 in out
+    assert a2 in out
+    assert pm.get_plugin('hello') == a2
+    assert pm.unregister(a1) == a1
+    assert not pm.is_registered(a1)
+
+    out = pm.list_name_plugin()
+    assert len(out) == 1
+    assert out == [("hello", a2)]
+
+
+def test_has_plugin(pm):
+    class A(object):
+        pass
+
+    a1 = A()
+    pm.register(a1, 'hello')
+    assert pm.is_registered(a1)
+    assert pm.has_plugin('hello')
+
+
+def test_register_dynamic_attr(he_pm):
+    class A(object):
+        def __getattr__(self, name):
+            if name[0] != "_":
+                return 42
+            raise AttributeError()
+
+    a = A()
+    he_pm.register(a)
+    assert not he_pm.get_hookcallers(a)
+
+
+def test_pm_name(pm):
+    class A(object):
+        pass
+
+    a1 = A()
+    name = pm.register(a1, name="hello")
+    assert name == "hello"
+    pm.unregister(a1)
+    assert pm.get_plugin(a1) is None
+    assert not pm.is_registered(a1)
+    assert not pm.get_plugins()
+    name2 = pm.register(a1, name="hello")
+    assert name2 == name
+    pm.unregister(name="hello")
+    assert pm.get_plugin(a1) is None
+    assert not pm.is_registered(a1)
+    assert not pm.get_plugins()
+
+
+def test_set_blocked(pm):
+    class A(object):
+        pass
+
+    a1 = A()
+    name = pm.register(a1)
+    assert pm.is_registered(a1)
+    assert not pm.is_blocked(name)
+    pm.set_blocked(name)
+    assert pm.is_blocked(name)
+    assert not pm.is_registered(a1)
+
+    pm.set_blocked("somename")
+    assert pm.is_blocked("somename")
+    assert not pm.register(A(), "somename")
+    pm.unregister(name="somename")
+    assert pm.is_blocked("somename")
+
+
+def test_register_mismatch_method(he_pm):
+    class hello(object):
+        @hookimpl
+        def he_method_notexists(self):
+            pass
+
+    he_pm.register(hello())
+    with pytest.raises(PluginValidationError):
+        he_pm.check_pending()
+
+
+def test_register_mismatch_arg(he_pm):
+    class hello(object):
+        @hookimpl
+        def he_method1(self, qlwkje):
+            pass
+
+    with pytest.raises(PluginValidationError):
+        he_pm.register(hello())
+
+
+def test_register(pm):
+    class MyPlugin(object):
+        pass
+    my = MyPlugin()
+    pm.register(my)
+    assert my in pm.get_plugins()
+    my2 = MyPlugin()
+    pm.register(my2)
+    assert set([my, my2]).issubset(pm.get_plugins())
+
+    assert pm.is_registered(my)
+    assert pm.is_registered(my2)
+    pm.unregister(my)
+    assert not pm.is_registered(my)
+    assert my not in pm.get_plugins()
+
+
+def test_register_unknown_hooks(pm):
+    class Plugin1(object):
+        @hookimpl
+        def he_method1(self, arg):
+            return arg + 1
+
+    pname = pm.register(Plugin1())
+
+    class Hooks(object):
+        @hookspec
+        def he_method1(self, arg):
+            pass
+
+    pm.add_hookspecs(Hooks)
+    # assert not pm._unverified_hooks
+    assert pm.hook.he_method1(arg=1) == [2]
+    assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1
+
+
+def test_register_historic(pm):
+    class Hooks(object):
+        @hookspec(historic=True)
+        def he_method1(self, arg):
+            pass
+    pm.add_hookspecs(Hooks)
+
+    pm.hook.he_method1.call_historic(kwargs=dict(arg=1))
+    out = []
+
+    class Plugin(object):
+        @hookimpl
+        def he_method1(self, arg):
+            out.append(arg)
+
+    pm.register(Plugin())
+    assert out == [1]
+
+    class Plugin2(object):
+        @hookimpl
+        def he_method1(self, arg):
+            out.append(arg * 10)
+
+    pm.register(Plugin2())
+    assert out == [1, 10]
+    pm.hook.he_method1.call_historic(kwargs=dict(arg=12))
+    assert out == [1, 10, 120, 12]
+
+
+def test_with_result_memorized(pm):
+    class Hooks(object):
+        @hookspec(historic=True)
+        def he_method1(self, arg):
+            pass
+    pm.add_hookspecs(Hooks)
+
+    he_method1 = pm.hook.he_method1
+    he_method1.call_historic(lambda res: out.append(res), dict(arg=1))
+    out = []
+
+    class Plugin(object):
+        @hookimpl
+        def he_method1(self, arg):
+            return arg * 10
+
+    pm.register(Plugin())
+    assert out == [10]
+
+
+def test_with_callbacks_immediately_executed(pm):
+    class Hooks(object):
+        @hookspec(historic=True)
+        def he_method1(self, arg):
+            pass
+    pm.add_hookspecs(Hooks)
+
+    class Plugin1(object):
+        @hookimpl
+        def he_method1(self, arg):
+            return arg * 10
+
+    class Plugin2(object):
+        @hookimpl
+        def he_method1(self, arg):
+            return arg * 20
+
+    class Plugin3(object):
+        @hookimpl
+        def he_method1(self, arg):
+            return arg * 30
+
+    out = []
+    pm.register(Plugin1())
+    pm.register(Plugin2())
+
+    he_method1 = pm.hook.he_method1
+    he_method1.call_historic(lambda res: out.append(res), dict(arg=1))
+    assert out == [20, 10]
+    pm.register(Plugin3())
+    assert out == [20, 10, 30]
+
+
+def test_register_historic_incompat_hookwrapper(pm):
+    class Hooks(object):
+        @hookspec(historic=True)
+        def he_method1(self, arg):
+            pass
+
+    pm.add_hookspecs(Hooks)
+
+    out = []
+
+    class Plugin(object):
+        @hookimpl(hookwrapper=True)
+        def he_method1(self, arg):
+            out.append(arg)
+
+    with pytest.raises(PluginValidationError):
+        pm.register(Plugin())
+
+
+def test_call_extra(pm):
+    class Hooks(object):
+        @hookspec
+        def he_method1(self, arg):
+            pass
+
+    pm.add_hookspecs(Hooks)
+
+    def he_method1(arg):
+        return arg * 10
+
+    out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1))
+    assert out == [10]
+
+
+def test_call_with_too_few_args(pm):
+    class Hooks(object):
+        @hookspec
+        def he_method1(self, arg):
+            pass
+
+    pm.add_hookspecs(Hooks)
+
+    class Plugin1(object):
+        @hookimpl
+        def he_method1(self, arg):
+            0 / 0
+    pm.register(Plugin1())
+    with pytest.raises(HookCallError):
+        with pytest.warns(UserWarning):
+            pm.hook.he_method1()
+
+
+def test_subset_hook_caller(pm):
+    class Hooks(object):
+        @hookspec
+        def he_method1(self, arg):
+            pass
+
+    pm.add_hookspecs(Hooks)
+
+    out = []
+
+    class Plugin1(object):
+        @hookimpl
+        def he_method1(self, arg):
+            out.append(arg)
+
+    class Plugin2(object):
+        @hookimpl
+        def he_method1(self, arg):
+            out.append(arg * 10)
+
+    class PluginNo(object):
+        pass
+
+    plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo()
+    pm.register(plugin1)
+    pm.register(plugin2)
+    pm.register(plugin3)
+    pm.hook.he_method1(arg=1)
+    assert out == [10, 1]
+    out[:] = []
+
+    hc = pm.subset_hook_caller("he_method1", [plugin1])
+    hc(arg=2)
+    assert out == [20]
+    out[:] = []
+
+    hc = pm.subset_hook_caller("he_method1", [plugin2])
+    hc(arg=2)
+    assert out == [2]
+    out[:] = []
+
+    pm.unregister(plugin1)
+    hc(arg=2)
+    assert out == []
+    out[:] = []
+
+    pm.hook.he_method1(arg=1)
+    assert out == [10]
+
+
+def test_multicall_deprecated(pm):
+    class P1(object):
+        @hookimpl
+        def m(self, __multicall__, x):
+            pass
+
+    pytest.deprecated_call(pm.register, P1())
+
+
+def test_add_hookspecs_nohooks(pm):
+    with pytest.raises(ValueError):
+        pm.add_hookspecs(10)
+
+
+def test_reject_prefixed_module(pm):
+    """Verify that a module type attribute that contains the project
+    prefix in its name (in this case `'example_*'` isn't collected
+    when registering a module which imports it.
+    """
+    pm._implprefix = 'example'
+    conftest = types.ModuleType("conftest")
+    src = ("""
+def example_hook():
+    pass
+""")
+    exec(src, conftest.__dict__)
+    conftest.example_blah = types.ModuleType("example_blah")
+    name = pm.register(conftest)
+    assert name == 'conftest'
+    assert getattr(pm.hook, 'example_blah', None) is None
+    assert getattr(pm.hook, 'example_hook', None)  # conftest.example_hook should be collected
+    assert pm.parse_hookimpl_opts(conftest, 'example_blah') is None
+    assert pm.parse_hookimpl_opts(conftest, 'example_hook') == {}
diff --git a/tools/third_party/pluggy/testing/test_tracer.py b/tools/third_party/pluggy/testing/test_tracer.py
new file mode 100644
index 0000000..4a3e16c
--- /dev/null
+++ b/tools/third_party/pluggy/testing/test_tracer.py
@@ -0,0 +1,89 @@
+
+from pluggy import _TagTracer
+
+
+def test_simple():
+    rootlogger = _TagTracer()
+    log = rootlogger.get("pytest")
+    log("hello")
+    out = []
+    rootlogger.setwriter(out.append)
+    log("world")
+    assert len(out) == 1
+    assert out[0] == "world [pytest]\n"
+    sublog = log.get("collection")
+    sublog("hello")
+    assert out[1] == "hello [pytest:collection]\n"
+
+
+def test_indent():
+    rootlogger = _TagTracer()
+    log = rootlogger.get("1")
+    out = []
+    log.root.setwriter(lambda arg: out.append(arg))
+    log("hello")
+    log.root.indent += 1
+    log("line1")
+    log("line2")
+    log.root.indent += 1
+    log("line3")
+    log("line4")
+    log.root.indent -= 1
+    log("line5")
+    log.root.indent -= 1
+    log("last")
+    assert len(out) == 7
+    names = [x[:x.rfind(' [')] for x in out]
+    assert names == [
+        'hello', '  line1', '  line2',
+        '    line3', '    line4', '  line5', 'last']
+
+
+def test_readable_output_dictargs():
+    rootlogger = _TagTracer()
+
+    out = rootlogger.format_message(['test'], [1])
+    assert out == ['1 [test]\n']
+
+    out2 = rootlogger.format_message(['test'], ['test', {'a': 1}])
+    assert out2 == [
+        'test [test]\n',
+        '    a: 1\n'
+    ]
+
+
+def test_setprocessor():
+    rootlogger = _TagTracer()
+    log = rootlogger.get("1")
+    log2 = log.get("2")
+    assert log2.tags == tuple("12")
+    out = []
+    rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args))
+    log("not seen")
+    log2("seen")
+    assert len(out) == 1
+    tags, args = out[0]
+    assert "1" in tags
+    assert "2" in tags
+    assert args == ("seen",)
+    l2 = []
+    rootlogger.setprocessor("1:2", lambda *args: l2.append(args))
+    log2("seen")
+    tags, args = l2[0]
+    assert args == ("seen",)
+
+
+def test_setmyprocessor():
+    rootlogger = _TagTracer()
+    log = rootlogger.get("1")
+    log2 = log.get("2")
+    out = []
+    log2.setmyprocessor(lambda *args: out.append(args))
+    log("not seen")
+    assert not out
+    log2(42)
+    assert len(out) == 1
+    tags, args = out[0]
+    assert "1" in tags
+    assert "2" in tags
+    assert args == (42,)
diff --git a/tools/third_party/pluggy/tox.ini b/tools/third_party/pluggy/tox.ini
new file mode 100644
index 0000000..89d44e3
--- /dev/null
+++ b/tools/third_party/pluggy/tox.ini
@@ -0,0 +1,44 @@
+[tox]
+envlist=check,docs,py{27,34,35,36,py}-pytestrelease,py{27,36}-pytest{master,features}
+
+[testenv]
+commands=py.test {posargs:testing/}
+setenv=
+  _PYTEST_SETUP_SKIP_PLUGGY_DEP=1
+deps=
+  pytestrelease: pytest
+  pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
+  pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
+
+[testenv:benchmark]
+commands=py.test {posargs:testing/benchmark.py}
+deps=
+  pytest
+  pytest-benchmark
+
+[testenv:check]
+deps =
+  flake8
+  restructuredtext_lint
+  pygments
+commands =
+  flake8 pluggy.py setup.py testing
+  rst-lint CHANGELOG.rst README.rst
+
+[testenv:docs]
+deps =
+  sphinx
+  pygments
+commands =
+  sphinx-build -b html {toxinidir}/docs {toxinidir}/build/html-docs
+
+[pytest]
+minversion=2.0
+#--pyargs --doctest-modules --ignore=.tox
+addopts=-rxsX
+norecursedirs=.tox ja .hg .env*
+filterwarnings =
+  error
+
+[flake8]
+max-line-length=99
diff --git a/tools/third_party/py/.gitattributes b/tools/third_party/py/.gitattributes
new file mode 100644
index 0000000..1246879
--- /dev/null
+++ b/tools/third_party/py/.gitattributes
@@ -0,0 +1 @@
+*.dump   eol=lf
diff --git a/tools/third_party/py/.gitignore b/tools/third_party/py/.gitignore
new file mode 100644
index 0000000..5bb0d45
--- /dev/null
+++ b/tools/third_party/py/.gitignore
@@ -0,0 +1,12 @@
+
+.cache/
+.tox/
+__pycache__/
+
+*.pyc
+*.pyo
+
+*.egg-info
+.eggs/
+
+dist/*
diff --git a/tools/py/.hgignore b/tools/third_party/py/.hgignore
similarity index 100%
rename from tools/py/.hgignore
rename to tools/third_party/py/.hgignore
diff --git a/tools/third_party/py/.hgtags b/tools/third_party/py/.hgtags
new file mode 100644
index 0000000..9d48095
--- /dev/null
+++ b/tools/third_party/py/.hgtags
@@ -0,0 +1,68 @@
+52c6d9e78777a5a34e813123997dfc614a1a4767 1.0.0b3
+1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
+1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
+0000000000000000000000000000000000000000 1.0.0b4
+0000000000000000000000000000000000000000 1.0.0b4
+8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
+8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
+0000000000000000000000000000000000000000 1.0.0b4
+2cc0507f117ffe721dff7ee026648cfce00ec92f 1.0.0b6
+86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
+86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
+c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
+c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
+0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8
+e2a60653cb490aeed81bbbd83c070b99401c211c 1.0.0b9
+5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
+5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
+7acde360d94b6a2690ce3d03ff39301da84c0a2b 1.0.0
+6bd221981ac99103002c1cb94fede400d23a96a1 1.0.1
+4816e8b80602a3fd3a0a120333ad85fbe7d8bab4 1.0.2
+60c44bdbf093285dc69d5462d4dbb4acad325ca6 1.1.0
+319187fcda66714c5eb1353492babeec3d3c826f 1.1.1
+4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0
+c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1
+eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0
+d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
+d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
+8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
+3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
+c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
+79ef6377705184c55633d456832eea318fedcf61 1.3.4
+79ef6377705184c55633d456832eea318fedcf61 1.3.4
+90fffd35373e9f125af233f78b19416f0938d841 1.3.4
+5346ab41b059c95a48cbe1e8a7bae96ce6e0da27 1.4.0
+1f3125cba7976538952be268f107c1d0c36c5ce8 1.4.1
+04ab22db4ff737cf31e91d75a0f5d7077f324167 1.4.2
+9950bf9d684a984d511795013421c89c5cf88bef 1.4.3
+d9951e3bdbc765e73835ae13012f6a074d13d8bf 1.4.4
+b827dd156a36753e32c7f3f15ce82d6fe9e356c8 1.4.6
+f15726f9e5a67cc6221c499affa4840e9d591763 1.4.7
+abfabd07a1d328f13c730e8a50d80d2e470afd3b 1.4.9
+7f37ee0aff9be4b839d6759cfee336f60e8393a4 1.4.10
+fe4593263efa10ea7ba014db6e3379e0b82368a2 1.4.11
+f07af25a26786e4825b5170e17ad693245cb3426 1.4.12
+d3730d84ba7eda92fd3469a3f63fd6d8cb22c975 1.4.13
+12c1ae8e7c5345721e9ec9f8e27b1e36c07f74dc 1.4.14
+12c1ae8e7c5345721e9ec9f8e27b1e36c07f74dc 1.4.14
+0000000000000000000000000000000000000000 1.4.14
+0000000000000000000000000000000000000000 1.4.14
+1497e2efd0f8c73a0e3d529debf0c489e4cd6cab 1.4.14
+e065014c1ce8ad110a381e9baaaa5d647ba7ac6b 1.4.15
+e9e5b38f53dc35b35aa1f9ee9a9be9bbd2d2c3b1 1.4.16
+c603503945f52b78522d96a423605cbc953236d3 1.4.17
+c59201105a29801cc858eb9160b7a19791b91a35 1.4.18
+284cc172e294d48edc840012e1451c32c3963d92 1.4.19
+a3e0626aa0c5aecf271367dc77e476ab216ea3c8 1.4.20
+5e48016c4a3af8e7358a1267d33d021e71765bed 1.4.21
+01ae2cfcc61c4fcb3aa5031349adb5b467c31018 1.4.23
+5ffd982f4dff60b588f309cd9bdc61036547282a 1.4.24
+dc9ffbcaf1f7d72e96be3f68c11deebb7e7193c5 1.4.25
+6de1a44bf75de7af4fcae947c235e9072bbdbb9a 1.4.26
+7d650ba2657890a2253c8c4a83f170febebd90fa 1.4.27
+7d650ba2657890a2253c8c4a83f170febebd90fa 1.4.27
+1810003dec63dd1b506a23849861fffa5bc3ba13 1.4.27
+ba08706f08ddea1b77a426f00dfe2bdc244345e8 1.4.28
+4e8054ada63f3327bcf759ae7cd36c7c8652bc9b 1.4.29
+366ab346610c6de8aaa7617e24011794b40236c6 1.4.30
+657380e439f9b7e04918cb162cb2e46388244b42 1.4.31
diff --git a/tools/third_party/py/.travis.yml b/tools/third_party/py/.travis.yml
new file mode 100644
index 0000000..917c59d
--- /dev/null
+++ b/tools/third_party/py/.travis.yml
@@ -0,0 +1,27 @@
+sudo: false
+language: python
+python:
+- '2.7'
+- '3.4'
+- '3.5'
+- '3.6'
+- 'pypy-5.4'
+env:
+- DEPS="pytest~=2.9.0"
+- DEPS="pytest~=3.0.0"
+#- DEPS="pytest~=3.1.0"
+
+matrix:
+
+  include:
+  - python: '2.7'
+    # using a different option due to pytest-addopts pytester issues
+    env: PYTEST_XADDOPTS="-n 3 --runslowtests" DEPS="pytest~=3.0.0 pytest-xdist"
+  allow_failures:
+  - python: 'pypy-5.4'
+install:
+- pip install -U setuptools setuptools_scm
+- pip install $DEPS
+- pip install -U . --force-reinstall
+script:
+- py.test --lsof $PYTEST_XADDOPTS
diff --git a/tools/py/AUTHORS b/tools/third_party/py/AUTHORS
similarity index 100%
rename from tools/py/AUTHORS
rename to tools/third_party/py/AUTHORS
diff --git a/tools/third_party/py/CHANGELOG b/tools/third_party/py/CHANGELOG
new file mode 100644
index 0000000..7e7b01b
--- /dev/null
+++ b/tools/third_party/py/CHANGELOG
@@ -0,0 +1,1149 @@
+1.5.2
+=====
+
+- fix #169, #170: error importing py.log on Windows: no module named ``syslog``.
+
+1.5.1
+=====
+
+- fix #167 - prevent pip from installing py in unsupported Python versions.
+
+1.5.0
+=====
+
+NOTE: **this release has been removed from PyPI** due to missing package
+metadata which caused a number of problems to py26 and py33 users.
+This issue was fixed in the 1.5.1 release.
+
+- python 2.6 and 3.3 are no longer supported
+- deprecate py.std and remove all internal uses
+- fix #73 turn py.error into an actual module
+- path join to / no longer produces leading double slashes
+- fix #82 - remove unsupportable aliases
+- fix python37 compatibility of path.sysfind on windows by correctly replacing vars
+- turn iniconfig and apipkg into vendored packages and ease de-vendoring for distributions
+- fix #68 remove invalid py.test.ensuretemp references
+- fix #25 - deprecate path.listdir(sort=callable)
+- add ``TerminalWriter.chars_on_current_line`` read-only property that tracks how many characters
+  have been written to the current line.
+
+1.4.34
+====================================================================
+
+- fix issue119 / pytest issue708 where tmpdir may fail to make numbered directories
+  when the filesystem is case-insensitive.
+
+1.4.33
+====================================================================
+
+- avoid imports in calls to py.path.local().fnmatch(). Thanks Andreas Pelme for
+  the PR.
+
+- fix issue106: Naive unicode encoding when calling fspath() in python2. Thanks Tiago Nobrega for the PR.
+
+- fix issue110: unittest.TestCase.assertWarns fails with py imported.
+
+1.4.32
+====================================================================
+
+- fix issue70: added ability to copy all stat info in py.path.local.copy.
+
+- make TerminalWriter.fullwidth a property.  This results in the correct
+  value when the terminal gets resized.
+
+- update supported html tags to include recent additions.
+  Thanks Denis Afonso for the PR.
+
+- Remove internal code in ``Source.compile`` meant to support earlier Python 3 versions that produced the side effect
+  of leaving ``None`` in ``sys.modules`` when called (see pytest-dev/pytest#2103).
+  Thanks Bruno Oliveira for the PR.
+
+1.4.31
+==================================================
+
+- fix local().copy(dest, mode=True) to also work
+  with unicode.
+
+- pass better error message with svn EEXIST paths
+
+1.4.30
+==================================================
+
+- fix issue68 an assert with a  multiline list comprehension 
+  was not reported correctly. Thanks Henrik Heibuerger.
+
+
+1.4.29
+==================================================
+
+- fix issue55: revert a change to the statement finding algorithm
+  which is used by pytest for generating tracebacks.
+  Thanks Daniel Hahler for initial analysis.
+
+- fix pytest issue254 for when traceback rendering can't
+  find valid source code.  Thanks Ionel Cristian Maries.
+
+
+1.4.28
+==================================================
+
+- fix issue64 -- dirpath regression when "abs=True" is passed.
+  Thanks Gilles Dartiguelongue.
+
+1.4.27
+==================================================
+
+- fix issue59: point to new repo site
+
+- allow a new ensuresyspath="append" mode for py.path.local.pyimport()
+  so that a neccessary import path is appended instead of prepended to
+  sys.path 
+
+- strike undocumented, untested argument to py.path.local.pypkgpath
+
+- speed up py.path.local.dirpath by a factor of 10
+
+1.4.26
+==================================================
+
+- avoid calling normpath twice in py.path.local
+
+- py.builtin._reraise properly reraises under Python3 now.
+
+- fix issue53 - remove module index, thanks jenisys.
+
+- allow posix path separators when "fnmatch" is called.
+  Thanks Christian Long for the complete PR.
+
+1.4.25
+==================================================
+
+- fix issue52: vaguely fix py25 compat of py.path.local (it's not
+  officially supported), also fix docs
+
+- fix pytest issue 589: when checking if we have a recursion error
+  check for the specific "maximum recursion depth" text of the exception.
+
+1.4.24
+==================================================
+
+- Fix retrieving source when an else: line has an other statement on
+  the same line.
+
+- add localpath read_text/write_text/read_bytes/write_bytes methods
+  as shortcuts and clearer bytes/text interfaces for read/write.
+  Adapted from a PR from Paul Moore.
+
+
+1.4.23
+==================================================
+
+- use newer apipkg version which makes attribute access on
+  alias modules resolve to None rather than an ImportError.
+  This helps with code that uses inspect.getframeinfo()
+  on py34 which causes a complete walk on sys.modules
+  thus triggering the alias module to resolve and blowing
+  up with ImportError.  The negative side is that something
+  like "py.test.X" will now result in None instead of "importerror: pytest"
+  if pytest is not installed.  But you shouldn't import "py.test" 
+  anyway anymore.
+
+- adapt one svn test to only check for any exception instead 
+  of specific ones because different svn versions cause different
+  errors and we don't care.
+
+
+1.4.22
+==================================================
+
+- refactor class-level registry on ForkedFunc child start/finish
+  event to become instance based (i.e. passed into the constructor)
+
+1.4.21
+==================================================
+
+- ForkedFunc now has class-level register_on_start/on_exit()
+  methods to allow adding information in the boxed process.
+  Thanks Marc Schlaich.
+
+- ForkedFunc in the child opens in "auto-flush" mode for
+  stdout/stderr so that when a subprocess dies you can see
+  its output even if it didn't flush itself.
+
+- refactor traceback generation in light of pytest issue 364
+  (shortening tracebacks).   you can now set a new traceback style 
+  on a per-entry basis such that a caller can force entries to be 
+  isplayed as short or long entries.
+
+- win32: py.path.local.sysfind(name) will preferrably return files with
+  extensions so that if "X" and "X.bat" or "X.exe" is on the PATH,
+  one of the latter two will be returned.
+
+1.4.20
+==================================================
+
+- ignore unicode decode errors in xmlescape.  Thanks Anatoly Bubenkoff.
+
+- on python2 modify traceback.format_exception_only to match python3 
+  behaviour, namely trying to print unicode for Exception instances
+
+- use a safer way for serializing exception reports (helps to fix
+  pytest issue413)
+
+Changes between 1.4.18 and 1.4.19
+==================================================
+
+- merge in apipkg fixes
+
+- some micro-optimizations in py/_code/code.py for speeding
+  up pytest runs.  Thanks Alex Gaynor for initiative.
+
+- check PY_COLORS=1 or PY_COLORS=0 to force coloring/not-coloring
+  for py.io.TerminalWriter() independently from capabilities
+  of the output file.  Thanks Marc Abramowitz for the PR.
+
+- some fixes to unicode handling in assertion handling.
+  Thanks for the PR to Floris Bruynooghe.  (This helps
+  to fix pytest issue 319).
+
+- depend on setuptools presence, remove distribute_setup
+
+Changes between 1.4.17 and 1.4.18
+==================================================
+
+- introduce path.ensure_dir() as a synonym for ensure(..., dir=1)
+
+- some unicode/python3 related fixes wrt to path manipulations
+  (if you start passing unicode particular in py2 you might 
+  still get problems, though)
+
+Changes between 1.4.16 and 1.4.17
+==================================================
+
+- make py.io.TerminalWriter() prefer colorama if it is available
+  and avoid empty lines when separator-lines are printed by
+  being defensive and reducing the working terminalwidth by 1
+
+- introduce optional "expanduser" argument to py.path.local
+  to that local("~", expanduser=True) gives the home
+  directory of "user".
+
+Changes between 1.4.15 and 1.4.16
+==================================================
+
+- fix issue35 - define __gt__ ordering between a local path
+  and strings
+
+- fix issue36 - make chdir() work even if os.getcwd() fails.
+
+- add path.exists/isdir/isfile/islink shortcuts
+
+- introduce local path.as_cwd() context manager.
+
+- introduce p.write(ensure=1) and p.open(ensure=1)
+  where ensure triggers creation of neccessary parent
+  dirs.
+
+
+Changes between 1.4.14 and 1.4.15
+==================================================
+
+- majorly speed up some common calling patterns with
+  LocalPath.listdir()/join/check/stat functions considerably.
+
+- fix an edge case with fnmatch where a glob style pattern appeared
+  in an absolute path.
+
+Changes between 1.4.13 and 1.4.14
+==================================================
+
+- fix dupfile to work with files that don't
+  carry a mode. Thanks Jason R. Coombs.
+
+Changes between 1.4.12 and 1.4.13
+==================================================
+
+- fix getting statementrange/compiling a file ending
+  in a comment line without newline (on python2.5)
+- for local paths you can pass "mode=True" to a copy()
+  in order to copy permission bits (underlying mechanism
+  is using shutil.copymode)
+- add paths arguments to py.path.local.sysfind to restrict
+  search to the diretories in the path.
+- add isdir/isfile/islink to path.stat() objects allowing to perform
+  multiple checks without calling out multiple times
+- drop py.path.local.__new__ in favour of a simpler __init__
+- iniconfig: allow "name:value" settings in config files, no space after
+  "name" required
+- fix issue 27 - NameError in unlikely untested case of saferepr
+
+
+Changes between 1.4.11 and 1.4.12
+==================================================
+
+- fix python2.4 support - for pre-AST interpreters re-introduce 
+  old way to find statements in exceptions (closes pytest issue 209)
+- add tox.ini to distribution
+- fix issue23 - print *,** args information in tracebacks,
+  thanks Manuel Jacob
+
+
+Changes between 1.4.10 and 1.4.11
+==================================================
+
+- use _ast to determine statement ranges when printing tracebacks -
+  avoiding multi-second delays on some large test modules
+- fix an internal test to not use class-denoted pytest_funcarg__
+- fix a doc link to bug tracker
+- try to make terminal.write() printing more robust against
+  unicodeencode/decode problems, amend according test
+- introduce py.builtin.text and py.builtin.bytes
+  to point to respective str/unicode (py2) and bytes/str (py3) types
+- fix error handling on win32/py33 for ENODIR 
+
+Changes between 1.4.9 and 1.4.10
+==================================================
+
+- terminalwriter: default to encode to UTF8 if no encoding is defined
+  on the output stream
+- issue22: improve heuristic for finding the statementrange in exceptions
+
+Changes between 1.4.8 and 1.4.9
+==================================================
+
+- fix bug of path.visit() which would not recognize glob-style patterns
+  for the "rec" recursion argument
+- changed iniconfig parsing to better conform, now the chars ";"
+  and "#" only mark a comment at the stripped start of a line
+- include recent apipkg-1.2
+- change internal terminalwriter.line/reline logic to more nicely
+  support file spinners
+
+Changes between 1.4.7 and 1.4.8
+==================================================
+
+- fix issue 13 - correct handling of the tag name object in xmlgen
+- fix issue 14 - support raw attribute values in xmlgen
+- fix windows terminalwriter printing/re-line problem
+- update distribute_setup.py to 0.6.27
+
+Changes between 1.4.6 and 1.4.7
+==================================================
+
+- fix issue11 - own test failure with python3.3 / Thanks Benjamin Peterson
+- help fix pytest issue 102
+
+Changes between 1.4.5 and 1.4.6
+==================================================
+
+- help to fix pytest issue99: unify output of 
+  ExceptionInfo.getrepr(style="native") with ...(style="long")
+- fix issue7: source.getstatementrange() now raises proper error
+  if no valid statement can be found
+- fix issue8: fix code and tests of svnurl/svnwc to work on subversion 1.7 - 
+  note that path.status(updates=1) will not properly work svn-17's status 
+  --xml output is broken.
+- make source.getstatementrange() more resilent about non-python code frames
+  (as seen from jnja2)
+- make trackeback recursion detection more resilent
+  about the eval magic of a decorator library
+- iniconfig: add support for ; as comment starter
+- properly handle lists in xmlgen on python3
+- normalize py.code.getfslineno(obj) to always return a (string, int) tuple
+  defaulting to ("", -1) respectively if no source code can be found for obj.
+
+Changes between 1.4.4 and 1.4.5
+==================================================
+
+- improve some unicode handling in terminalwriter and capturing
+  (used by pytest)
+
+Changes between 1.4.3 and 1.4.4
+==================================================
+
+- a few fixes and assertion related refinements for pytest-2.1
+- guard py.code.Code and getfslineno against bogus input
+  and make py.code.Code objects for object instance
+  by looking up their __call__ function.
+- make exception presentation robust against invalid current cwd
+
+Changes between 1.4.2 and 1.4.3
+==================================================
+
+- fix terminal coloring issue for skipped tests (thanks Amaury)
+- fix issue4 - large calls to ansi_print (thanks Amaury)
+
+Changes between 1.4.1 and 1.4.2
+==================================================
+
+- fix (pytest) issue23 - tmpdir argument now works on Python3.2 and WindowsXP
+  (which apparently starts to offer os.symlink now)
+
+- better error message for syntax errors from compiled code
+
+- small fix to better deal with (un-)colored terminal output on windows
+
+Changes between 1.4.0 and 1.4.1
+==================================================
+
+- fix issue1 - py.error.* classes to be pickleable
+
+- fix issue2 - on windows32 use PATHEXT as the list of potential
+  extensions to find find binaries with py.path.local.sysfind(commandname)
+
+- fix (pytest-) issue10 and refine assertion reinterpretation
+  to avoid breaking if the __nonzero__ of an object fails
+
+- fix (pytest-) issue17 where python3 does not like "import *"
+  leading to misrepresentation of import-errors in test modules
+
+- fix py.error.* attribute pypy access issue
+
+- allow path.samefile(arg) to succeed when arg is a relative filename
+
+- fix (pytest-) issue20 path.samefile(relpath) works as expected now
+
+- fix (pytest-) issue8 len(long_list) now shows the length of the list
+
+Changes between 1.3.4 and 1.4.0
+==================================================
+
+- py.test was moved to a separate "pytest" package. What remains is
+  a stub hook which will proxy ``import py.test`` to ``pytest``.
+- all command line tools ("py.cleanup/lookup/countloc/..." moved 
+  to "pycmd" package)
+- removed the old and deprecated "py.magic" namespace
+- use apipkg-1.1 and make py.apipkg.initpkg|ApiModule available
+- add py.iniconfig module for brain-dead easy ini-config file parsing
+- introduce py.builtin.any()
+- path objects have a .dirname attribute now (equivalent to
+  os.path.dirname(path))
+- path.visit() accepts breadthfirst (bf) and sort options
+- remove deprecated py.compat namespace
+
+Changes between 1.3.3 and 1.3.4
+==================================================
+
+- fix issue111: improve install documentation for windows
+- fix issue119: fix custom collectability of __init__.py as a module
+- fix issue116: --doctestmodules work with __init__.py files as well
+- fix issue115: unify internal exception passthrough/catching/GeneratorExit
+- fix issue118: new --tb=native for presenting cpython-standard exceptions
+
+Changes between 1.3.2 and 1.3.3
+==================================================
+
+- fix issue113: assertion representation problem with triple-quoted strings
+  (and possibly other cases)
+- make conftest loading detect that a conftest file with the same
+  content was already loaded, avoids surprises in nested directory structures
+  which can be produced e.g. by Hudson. It probably removes the need to use
+  --confcutdir in most cases.
+- fix terminal coloring for win32
+  (thanks Michael Foord for reporting)
+- fix weirdness: make terminal width detection work on stdout instead of stdin
+  (thanks Armin Ronacher for reporting)
+- remove trailing whitespace in all py/text distribution files
+
+Changes between 1.3.1 and 1.3.2
+==================================================
+
+New features
+++++++++++++++++++
+
+- fix issue103:  introduce py.test.raises as context manager, examples::
+
+    with py.test.raises(ZeroDivisionError):
+        x = 0
+        1 / x
+
+    with py.test.raises(RuntimeError) as excinfo:
+        call_something()
+
+    # you may do extra checks on excinfo.value|type|traceback here
+
+  (thanks Ronny Pfannschmidt)
+
+- Funcarg factories can now dynamically apply a marker to a
+  test invocation.  This is for example useful if a factory
+  provides parameters to a test which are expected-to-fail::
+
+    def pytest_funcarg__arg(request):
+        request.applymarker(py.test.mark.xfail(reason="flaky config"))
+        ...
+
+    def test_function(arg):
+        ...
+
+- improved error reporting on collection and import errors. This makes
+  use of a more general mechanism, namely that for custom test item/collect
+  nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
+  override it to return a string error representation of your choice
+  which is going to be reported as a (red) string.
+
+- introduce '--junitprefix=STR' option to prepend a prefix
+  to all reports in the junitxml file.
+
+Bug fixes / Maintenance
+++++++++++++++++++++++++++
+
+- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
+  to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
+  you can properly check for their existence in a cross-python manner).
+- refine --pdb: ignore xfailed tests, unify its TB-reporting and
+  don't display failures again at the end.
+- fix assertion interpretation with the ** operator (thanks Benjamin Peterson)
+- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson)
+- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous)
+- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
+- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
+- fix py.code.compile(source) to generate unique filenames
+- fix assertion re-interp problems on PyPy, by defering code
+  compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
+- fix py.path.local.pyimport() to work with directories
+- streamline py.path.local.mkdtemp implementation and usage
+- don't print empty lines when showing junitxml-filename
+- add optional boolean ignore_errors parameter to py.path.local.remove
+- fix terminal writing on win32/python2.4
+- py.process.cmdexec() now tries harder to return properly encoded unicode objects
+  on all python versions
+- install plain py.test/py.which scripts also for Jython, this helps to
+  get canonical script paths in virtualenv situations
+- make path.bestrelpath(path) return ".", note that when calling
+  X.bestrelpath the assumption is that X is a directory.
+- make initial conftest discovery ignore "--" prefixed arguments
+- fix resultlog plugin when used in an multicpu/multihost xdist situation
+  (thanks Jakub Gustak)
+- perform distributed testing related reporting in the xdist-plugin
+  rather than having dist-related code in the generic py.test
+  distribution
+- fix homedir detection on Windows
+- ship distribute_setup.py version 0.6.13
+
+Changes between 1.3.0 and 1.3.1
+==================================================
+
+New features
+++++++++++++++++++
+
+- issue91: introduce new py.test.xfail(reason) helper
+  to imperatively mark a test as expected to fail. Can
+  be used from within setup and test functions. This is
+  useful especially for parametrized tests when certain
+  configurations are expected-to-fail.  In this case the
+  declarative approach with the @py.test.mark.xfail cannot
+  be used as it would mark all configurations as xfail.
+
+- issue102: introduce new --maxfail=NUM option to stop
+  test runs after NUM failures.  This is a generalization
+  of the '-x' or '--exitfirst' option which is now equivalent
+  to '--maxfail=1'.  Both '-x' and '--maxfail' will
+  now also print a line near the end indicating the Interruption.
+
+- issue89: allow py.test.mark decorators to be used on classes
+  (class decorators were introduced with python2.6) and
+  also allow to have multiple markers applied at class/module level
+  by specifying a list.
+
+- improve and refine letter reporting in the progress bar:
+  .  pass
+  f  failed test
+  s  skipped tests (reminder: use for dependency/platform mismatch only)
+  x  xfailed test (test that was expected to fail)
+  X  xpassed test (test that was expected to fail but passed)
+
+  You can use any combination of 'fsxX' with the '-r' extended
+  reporting option. The xfail/xpass results will show up as
+  skipped tests in the junitxml output - which also fixes
+  issue99.
+
+- make py.test.cmdline.main() return the exitstatus instead of raising
+  SystemExit and also allow it to be called multiple times.  This of
+  course requires that your application and tests are properly teared
+  down and don't have global state.
+
+Fixes / Maintenance
+++++++++++++++++++++++
+
+- improved traceback presentation:
+  - improved and unified reporting for "--tb=short" option
+  - Errors during test module imports are much shorter, (using --tb=short style)
+  - raises shows shorter more relevant tracebacks
+  - --fulltrace now more systematically makes traces longer / inhibits cutting
+
+- improve support for raises and other dynamically compiled code by
+  manipulating python's linecache.cache instead of the previous
+  rather hacky way of creating custom code objects.  This makes
+  it seemlessly work on Jython and PyPy where it previously didn't.
+
+- fix issue96: make capturing more resilient against Control-C
+  interruptions (involved somewhat substantial refactoring
+  to the underlying capturing functionality to avoid race
+  conditions).
+
+- fix chaining of conditional skipif/xfail decorators - so it works now
+  as expected to use multiple @py.test.mark.skipif(condition) decorators,
+  including specific reporting which of the conditions lead to skipping.
+
+- fix issue95: late-import zlib so that it's not required
+  for general py.test startup.
+
+- fix issue94: make reporting more robust against bogus source code
+  (and internally be more careful when presenting unexpected byte sequences)
+
+
+Changes between 1.2.1 and 1.3.0
+==================================================
+
+- deprecate --report option in favour of a new shorter and easier to
+  remember -r option: it takes a string argument consisting of any
+  combination of 'xfsX' characters.  They relate to the single chars
+  you see during the dotted progress printing and will print an extra line
+  per test at the end of the test run.  This extra line indicates the exact
+  position or test ID that you directly paste to the py.test cmdline in order
+  to re-run a particular test.
+
+- allow external plugins to register new hooks via the new
+  pytest_addhooks(pluginmanager) hook.  The new release of
+  the pytest-xdist plugin for distributed and looponfailing
+  testing requires this feature.
+
+- add a new pytest_ignore_collect(path, config) hook to allow projects and
+  plugins to define exclusion behaviour for their directory structure -
+  for example you may define in a conftest.py this method::
+
+        def pytest_ignore_collect(path):
+            return path.check(link=1)
+
+  to prevent even a collection try of any tests in symlinked dirs.
+
+- new pytest_pycollect_makemodule(path, parent) hook for
+  allowing customization of the Module collection object for a
+  matching test module.
+
+- extend and refine xfail mechanism:
+  ``@py.test.mark.xfail(run=False)`` do not run the decorated test
+  ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries
+  specifiying ``--runxfail`` on command line virtually ignores xfail markers
+
+- expose (previously internal) commonly useful methods:
+  py.io.get_terminal_with() -> return terminal width
+  py.io.ansi_print(...) -> print colored/bold text on linux/win32
+  py.io.saferepr(obj) -> return limited representation string
+
+- expose test outcome related exceptions as py.test.skip.Exception,
+  py.test.raises.Exception etc., useful mostly for plugins
+  doing special outcome interpretation/tweaking
+
+- (issue85) fix junitxml plugin to handle tests with non-ascii output
+
+- fix/refine python3 compatibility (thanks Benjamin Peterson)
+
+- fixes for making the jython/win32 combination work, note however:
+  jython2.5.1/win32 does not provide a command line launcher, see
+  http://bugs.jython.org/issue1491 . See pylib install documentation
+  for how to work around.
+
+- fixes for handling of unicode exception values and unprintable objects
+
+- (issue87) fix unboundlocal error in assertionold code
+
+- (issue86) improve documentation for looponfailing
+
+- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
+
+- ship distribute_setup.py version 0.6.10
+
+- added links to the new capturelog and coverage plugins
+
+
+Changes between 1.2.1 and 1.2.0
+=====================================
+
+- refined usage and options for "py.cleanup"::
+
+    py.cleanup     # remove "*.pyc" and "*$py.class" (jython) files
+    py.cleanup -e .swp -e .cache # also remove files with these extensions
+    py.cleanup -s  # remove "build" and "dist" directory next to setup.py files
+    py.cleanup -d  # also remove empty directories
+    py.cleanup -a  # synonym for "-s -d -e 'pip-log.txt'"
+    py.cleanup -n  # dry run, only show what would be removed
+
+- add a new option "py.test --funcargs" which shows available funcargs
+  and their help strings (docstrings on their respective factory function)
+  for a given test path
+
+- display a short and concise traceback if a funcarg lookup fails
+
+- early-load "conftest.py" files in non-dot first-level sub directories.
+  allows to conveniently keep and access test-related options in a ``test``
+  subdir and still add command line options.
+
+- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
+
+- fix issue78: always call python-level teardown functions even if the
+  according setup failed.  This includes refinements for calling setup_module/class functions
+  which will now only be called once instead of the previous behaviour where they'd be called
+  multiple times if they raise an exception (including a Skipped exception).  Any exception
+  will be re-corded and associated with all tests in the according module/class scope.
+
+- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
+
+- fix pdb debugging to be in the correct frame on raises-related errors
+
+- update apipkg.py to fix an issue where recursive imports might
+  unnecessarily break importing
+
+- fix plugin links
+
+Changes between 1.2 and 1.1.1
+=====================================
+
+- moved dist/looponfailing from py.test core into a new
+  separately released pytest-xdist plugin.
+
+- new junitxml plugin: --junitxml=path will generate a junit style xml file
+  which is processable e.g. by the Hudson CI system.
+
+- new option: --genscript=path will generate a standalone py.test script
+  which will not need any libraries installed.  thanks to Ralf Schmitt.
+
+- new option: --ignore will prevent specified path from collection.
+  Can be specified multiple times.
+
+- new option: --confcutdir=dir will make py.test only consider conftest
+  files that are relative to the specified dir.
+
+- new funcarg: "pytestconfig" is the pytest config object for access
+  to command line args and can now be easily used in a test.
+
+- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to
+  disambiguate between Python3, python2.X, Jython and PyPy installed versions.
+
+- new "pytestconfig" funcarg allows access to test config object
+
+- new "pytest_report_header" hook can return additional lines
+  to be displayed at the header of a test run.
+
+- (experimental) allow "py.test path::name1::name2::..." for pointing
+  to a test within a test collection directly.  This might eventually
+  evolve as a full substitute to "-k" specifications.
+
+- streamlined plugin loading: order is now as documented in
+  customize.html: setuptools, ENV, commandline, conftest.
+  also setuptools entry point names are turned to canonical namees ("pytest_*")
+
+- automatically skip tests that need 'capfd' but have no os.dup
+
+- allow pytest_generate_tests to be defined in classes as well
+
+- deprecate usage of 'disabled' attribute in favour of pytestmark
+- deprecate definition of Directory, Module, Class and Function nodes
+  in conftest.py files.  Use pytest collect hooks instead.
+
+- collection/item node specific runtest/collect hooks are only called exactly
+  on matching conftest.py files, i.e. ones which are exactly below
+  the filesystem path of an item
+
+- change: the first pytest_collect_directory hook to return something
+  will now prevent further hooks to be called.
+
+- change: figleaf plugin now requires --figleaf to run.  Also
+  change its long command line options to be a bit shorter (see py.test -h).
+
+- change: pytest doctest plugin is now enabled by default and has a
+  new option --doctest-glob to set a pattern for file matches.
+
+- change: remove internal py._* helper vars, only keep py._pydir
+
+- robustify capturing to survive if custom pytest_runtest_setup
+  code failed and prevented the capturing setup code from running.
+
+- make py.test.* helpers provided by default plugins visible early -
+  works transparently both for pydoc and for interactive sessions
+  which will regularly see e.g. py.test.mark and py.test.importorskip.
+
+- simplify internal plugin manager machinery
+- simplify internal collection tree by introducing a RootCollector node
+
+- fix assert reinterpreation that sees a call containing "keyword=..."
+
+- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
+  hooks on slaves during dist-testing, report module/session teardown
+  hooks correctly.
+
+- fix issue65: properly handle dist-testing if no
+  execnet/py lib installed remotely.
+
+- skip some install-tests if no execnet is available
+
+- fix docs, fix internal bin/ script generation
+
+
+Changes between 1.1.1 and 1.1.0
+=====================================
+
+- introduce automatic plugin registration via 'pytest11'
+  entrypoints via setuptools' pkg_resources.iter_entry_points
+
+- fix py.test dist-testing to work with execnet >= 1.0.0b4
+
+- re-introduce py.test.cmdline.main() for better backward compatibility
+
+- svn paths: fix a bug with path.check(versioned=True) for svn paths,
+  allow '%' in svn paths, make svnwc.update() default to interactive mode
+  like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
+
+- refine distributed tarball to contain test and no pyc files
+
+- try harder to have deprecation warnings for py.compat.* accesses
+  report a correct location
+
+Changes between 1.1.0 and 1.0.2
+=====================================
+
+* adjust and improve docs
+
+* remove py.rest tool and internal namespace - it was
+  never really advertised and can still be used with
+  the old release if needed.  If there is interest
+  it could be revived into its own tool i guess.
+
+* fix issue48 and issue59: raise an Error if the module
+  from an imported test file does not seem to come from
+  the filepath - avoids "same-name" confusion that has
+  been reported repeatedly
+
+* merged Ronny's nose-compatibility hacks: now
+  nose-style setup_module() and setup() functions are
+  supported
+
+* introduce generalized py.test.mark function marking
+
+* reshuffle / refine command line grouping
+
+* deprecate parser.addgroup in favour of getgroup which creates option group
+
+* add --report command line option that allows to control showing of skipped/xfailed sections
+
+* generalized skipping: a new way to mark python functions with skipif or xfail
+  at function, class and modules level based on platform or sys-module attributes.
+
+* extend py.test.mark decorator to allow for positional args
+
+* introduce and test "py.cleanup -d" to remove empty directories
+
+* fix issue #59 - robustify unittest test collection
+
+* make bpython/help interaction work by adding an __all__ attribute
+  to ApiModule, cleanup initpkg
+
+* use MIT license for pylib, add some contributors
+
+* remove py.execnet code and substitute all usages with 'execnet' proper
+
+* fix issue50 - cached_setup now caches more to expectations
+  for test functions with multiple arguments.
+
+* merge Jarko's fixes, issue #45 and #46
+
+* add the ability to specify a path for py.lookup to search in
+
+* fix a funcarg cached_setup bug probably only occuring
+  in distributed testing and "module" scope with teardown.
+
+* many fixes and changes for making the code base python3 compatible,
+  many thanks to Benjamin Peterson for helping with this.
+
+* consolidate builtins implementation to be compatible with >=2.3,
+  add helpers to ease keeping 2 and 3k compatible code
+
+* deprecate py.compat.doctest|subprocess|textwrap|optparse
+
+* deprecate py.magic.autopath, remove py/magic directory
+
+* move pytest assertion handling to py/code and a pytest_assertion
+  plugin, add "--no-assert" option, deprecate py.magic namespaces
+  in favour of (less) py.code ones.
+
+* consolidate and cleanup py/code classes and files
+
+* cleanup py/misc, move tests to bin-for-dist
+
+* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
+
+* consolidate py.log implementation, remove old approach.
+
+* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
+  text/unicode and byte-streams (uses underlying standard lib io.*
+  if available)
+
+* make py.unittest_convert helper script available which converts "unittest.py"
+  style files into the simpler assert/direct-test-classes py.test/nosetests
+  style.  The script was written by Laura Creighton.
+
+* simplified internal localpath implementation
+
+Changes between 1.0.1 and 1.0.2
+=====================================
+
+* fixing packaging issues, triggered by fedora redhat packaging,
+  also added doc, examples and contrib dirs to the tarball.
+
+* added a documentation link to the new django plugin.
+
+Changes between 1.0.0 and 1.0.1
+=====================================
+
+* added a 'pytest_nose' plugin which handles nose.SkipTest,
+  nose-style function/method/generator setup/teardown and
+  tries to report functions correctly.
+
+* capturing of unicode writes or encoded strings to sys.stdout/err
+  work better, also terminalwriting was adapted and somewhat
+  unified between windows and linux.
+
+* improved documentation layout and content a lot
+
+* added a "--help-config" option to show conftest.py / ENV-var names for
+  all longopt cmdline options, and some special conftest.py variables.
+  renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
+
+* fix issue #27: better reporting on non-collectable items given on commandline
+  (e.g. pyc files)
+
+* fix issue #33: added --version flag (thanks Benjamin Peterson)
+
+* fix issue #32: adding support for "incomplete" paths to wcpath.status()
+
+* "Test" prefixed classes are *not* collected by default anymore if they
+  have an __init__ method
+
+* monkeypatch setenv() now accepts a "prepend" parameter
+
+* improved reporting of collection error tracebacks
+
+* simplified multicall mechanism and plugin architecture,
+  renamed some internal methods and argnames
+
+Changes between 1.0.0b9 and 1.0.0
+=====================================
+
+* more terse reporting try to show filesystem path relatively to current dir
+* improve xfail output a bit
+
+Changes between 1.0.0b8 and 1.0.0b9
+=====================================
+
+* cleanly handle and report final teardown of test setup
+
+* fix svn-1.6 compat issue with py.path.svnwc().versioned()
+  (thanks Wouter Vanden Hove)
+
+* setup/teardown or collection problems now show as ERRORs
+  or with big "E"'s in the progress lines.  they are reported
+  and counted separately.
+
+* dist-testing: properly handle test items that get locally
+  collected but cannot be collected on the remote side - often
+  due to platform/dependency reasons
+
+* simplified py.test.mark API - see keyword plugin documentation
+
+* integrate better with logging: capturing now by default captures
+  test functions and their immediate setup/teardown in a single stream
+
+* capsys and capfd funcargs now have a readouterr() and a close() method
+  (underlyingly py.io.StdCapture/FD objects are used which grew a
+  readouterr() method as well to return snapshots of captured out/err)
+
+* make assert-reinterpretation work better with comparisons not
+  returning bools (reported with numpy from thanks maciej fijalkowski)
+
+* reworked per-test output capturing into the pytest_iocapture.py plugin
+  and thus removed capturing code from config object
+
+* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
+
+
+Changes between 1.0.0b7 and 1.0.0b8
+=====================================
+
+* pytest_unittest-plugin is now enabled by default
+
+* introduced pytest_keyboardinterrupt hook and
+  refined pytest_sessionfinish hooked, added tests.
+
+* workaround a buggy logging module interaction ("closing already closed
+  files").  Thanks to Sridhar Ratnakumar for triggering.
+
+* if plugins use "py.test.importorskip" for importing
+  a dependency only a warning will be issued instead
+  of exiting the testing process.
+
+* many improvements to docs:
+  - refined funcargs doc , use the term "factory" instead of "provider"
+  - added a new talk/tutorial doc page
+  - better download page
+  - better plugin docstrings
+  - added new plugins page and automatic doc generation script
+
+* fixed teardown problem related to partially failing funcarg setups
+  (thanks MrTopf for reporting), "pytest_runtest_teardown" is now
+  always invoked even if the "pytest_runtest_setup" failed.
+
+* tweaked doctest output for docstrings in py modules,
+  thanks Radomir.
+
+Changes between 1.0.0b3 and 1.0.0b7
+=============================================
+
+* renamed py.test.xfail back to py.test.mark.xfail to avoid
+  two ways to decorate for xfail
+
+* re-added py.test.mark decorator for setting keywords on functions
+  (it was actually documented so removing it was not nice)
+
+* remove scope-argument from request.addfinalizer() because
+  request.cached_setup has the scope arg. TOOWTDI.
+
+* perform setup finalization before reporting failures
+
+* apply modified patches from Andreas Kloeckner to allow
+  test functions to have no func_code (#22) and to make
+  "-k" and function keywords work  (#20)
+
+* apply patch from Daniel Peolzleithner (issue #23)
+
+* resolve issue #18, multiprocessing.Manager() and
+  redirection clash
+
+* make __name__ == "__channelexec__" for remote_exec code
+
+Changes between 1.0.0b1 and 1.0.0b3
+=============================================
+
+* plugin classes are removed: one now defines
+  hooks directly in conftest.py or global pytest_*.py
+  files.
+
+* added new pytest_namespace(config) hook that allows
+  to inject helpers directly to the py.test.* namespace.
+
+* documented and refined many hooks
+
+* added new style of generative tests via
+  pytest_generate_tests hook that integrates
+  well with function arguments.
+
+
+Changes between 0.9.2 and 1.0.0b1
+=============================================
+
+* introduced new "funcarg" setup method,
+  see doc/test/funcarg.txt
+
+* introduced plugin architecuture and many
+  new py.test plugins, see
+  doc/test/plugins.txt
+
+* teardown_method is now guaranteed to get
+  called after a test method has run.
+
+* new method: py.test.importorskip(mod,minversion)
+  will either import or call py.test.skip()
+
+* completely revised internal py.test architecture
+
+* new py.process.ForkedFunc object allowing to
+  fork execution of a function to a sub process
+  and getting a result back.
+
+XXX lots of things missing here XXX
+
+Changes between 0.9.1 and 0.9.2
+===============================
+
+* refined installation and metadata, created new setup.py,
+  now based on setuptools/ez_setup (thanks to Ralf Schmitt
+  for his support).
+
+* improved the way of making py.* scripts available in
+  windows environments, they are now added to the
+  Scripts directory as ".cmd" files.
+
+* py.path.svnwc.status() now is more complete and
+  uses xml output from the 'svn' command if available
+  (Guido Wesdorp)
+
+* fix for py.path.svn* to work with svn 1.5
+  (Chris Lamb)
+
+* fix path.relto(otherpath) method on windows to
+  use normcase for checking if a path is relative.
+
+* py.test's traceback is better parseable from editors
+  (follows the filenames:LINENO: MSG convention)
+  (thanks to Osmo Salomaa)
+
+* fix to javascript-generation, "py.test --runbrowser"
+  should work more reliably now
+
+* removed previously accidentally added
+  py.test.broken and py.test.notimplemented helpers.
+
+* there now is a py.__version__ attribute
+
+Changes between 0.9.0 and 0.9.1
+===============================
+
+This is a fairly complete list of changes between 0.9 and 0.9.1, which can
+serve as a reference for developers.
+
+* allowing + signs in py.path.svn urls [39106]
+* fixed support for Failed exceptions without excinfo in py.test [39340]
+* added support for killing processes for Windows (as well as platforms that
+  support os.kill) in py.misc.killproc [39655]
+* added setup/teardown for generative tests to py.test [40702]
+* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739]
+* fixed problem with calling .remove() on wcpaths of non-versioned files in
+  py.path [44248]
+* fixed some import and inheritance issues in py.test [41480, 44648, 44655]
+* fail to run greenlet tests when pypy is available, but without stackless
+  [45294]
+* small fixes in rsession tests [45295]
+* fixed issue with 2.5 type representations in py.test [45483, 45484]
+* made that internal reporting issues displaying is done atomically in py.test
+  [45518]
+* made that non-existing files are igored by the py.lookup script [45519]
+* improved exception name creation in py.test [45535]
+* made that less threads are used in execnet [merge in 45539]
+* removed lock required for atomical reporting issue displaying in py.test
+  [45545]
+* removed globals from execnet [45541, 45547]
+* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit
+  get called in 2.5 (py.execnet) [45548]
+* fixed bug in joining threads in py.execnet's servemain [45549]
+* refactored py.test.rsession tests to not rely on exact output format anymore
+  [45646]
+* using repr() on test outcome [45647]
+* added 'Reason' classes for py.test.skip() [45648, 45649]
+* killed some unnecessary sanity check in py.test.collect [45655]
+* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only
+  usable by Administrators [45901]
+* added support for locking and non-recursive commits to py.path.svnwc [45994]
+* locking files in py.execnet to prevent CPython from segfaulting [46010]
+* added export() method to py.path.svnurl
+* fixed -d -x in py.test [47277]
+* fixed argument concatenation problem in py.path.svnwc [49423]
+* restore py.test behaviour that it exits with code 1 when there are failures
+  [49974]
+* don't fail on html files that don't have an accompanying .txt file [50606]
+* fixed 'utestconvert.py < input' [50645]
+* small fix for code indentation in py.code.source [50755]
+* fix _docgen.py documentation building [51285]
+* improved checks for source representation of code blocks in py.test [51292]
+* added support for passing authentication to py.path.svn* objects [52000,
+  52001]
+* removed sorted() call for py.apigen tests in favour of [].sort() to support
+  Python 2.3 [52481]
diff --git a/tools/py/LICENSE b/tools/third_party/py/LICENSE
similarity index 100%
rename from tools/py/LICENSE
rename to tools/third_party/py/LICENSE
diff --git a/tools/third_party/py/MANIFEST.in b/tools/third_party/py/MANIFEST.in
new file mode 100644
index 0000000..239ad22
--- /dev/null
+++ b/tools/third_party/py/MANIFEST.in
@@ -0,0 +1,10 @@
+include CHANGELOG
+include AUTHORS
+include README.rst
+include setup.py
+include LICENSE
+include conftest.py
+include tox.ini
+graft doc
+graft testing
+global-exclude *.pyc
diff --git a/tools/third_party/py/README.rst b/tools/third_party/py/README.rst
new file mode 100644
index 0000000..7092ae4
--- /dev/null
+++ b/tools/third_party/py/README.rst
@@ -0,0 +1,34 @@
+.. image:: https://img.shields.io/pypi/v/py.svg
+    :target: https://pypi.org/project/py
+
+.. image:: https://anaconda.org/conda-forge/py/badges/version.svg
+    :target: https://anaconda.org/conda-forge/py
+
+.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
+  :target: https://pypi.org/project/py
+
+.. image:: https://img.shields.io/travis/pytest-dev/py.svg
+   :target: https://travis-ci.org/pytest-dev/py
+
+.. image:: https://ci.appveyor.com/api/projects/status/10keglan6uqwj5al/branch/master?svg=true
+   :target: https://ci.appveyor.com/project/pytestbot/py
+
+
+**NOTE**: this library is in **maintenance mode** and should not be used in new code.
+
+The py lib is a Python development support library featuring
+the following tools and modules:
+
+* ``py.path``:  uniform local and svn path objects
+* ``py.apipkg``:  explicit API control and lazy-importing
+* ``py.iniconfig``:  easy parsing of .ini files
+* ``py.code``: dynamic code generation and introspection (deprecated, moved to ``pytest``).
+
+**NOTE**: prior to the 1.4 release this distribution used to
+contain py.test which is now its own package, see http://pytest.org
+
+For questions and more information please visit http://py.readthedocs.org
+
+Bugs and issues: https://github.com/pytest-dev/py
+
+Authors: Holger Krekel and others, 2004-2017
diff --git a/tools/third_party/py/appveyor.yml b/tools/third_party/py/appveyor.yml
new file mode 100644
index 0000000..5fbeca9
--- /dev/null
+++ b/tools/third_party/py/appveyor.yml
@@ -0,0 +1,26 @@
+environment:
+  matrix:
+  # note: please use "tox --listenvs" to populate the build matrix below
+  - TOXENV: "py27-pytest29"
+  - TOXENV: "py27-pytest30"
+  - TOXENV: "py27-pytest31"
+  - TOXENV: "py34-pytest29"
+  - TOXENV: "py34-pytest30"
+  - TOXENV: "py34-pytest31"
+  - TOXENV: "py35-pytest29"
+  - TOXENV: "py35-pytest30"
+  - TOXENV: "py35-pytest31"
+  - TOXENV: "py36-pytest29"
+  - TOXENV: "py36-pytest30"
+  - TOXENV: "py36-pytest31"
+
+install:
+  - echo Installed Pythons
+  - dir c:\Python*
+
+  - C:\Python36\python -m pip install --upgrade --pre tox
+
+build: false  # Not a C# project, build stuff at the test step instead.
+
+test_script:
+  - C:\Python36\python -m tox
diff --git a/tools/py/bench/localpath.py b/tools/third_party/py/bench/localpath.py
similarity index 100%
rename from tools/py/bench/localpath.py
rename to tools/third_party/py/bench/localpath.py
diff --git a/tools/third_party/py/conftest.py b/tools/third_party/py/conftest.py
new file mode 100644
index 0000000..5bff3fe
--- /dev/null
+++ b/tools/third_party/py/conftest.py
@@ -0,0 +1,60 @@
+import py
+import pytest
+import sys
+
+pytest_plugins = 'doctest', 'pytester'
+
+collect_ignore = ['build', 'doc/_build']
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("pylib", "py lib testing options")
+    group.addoption('--runslowtests',
+           action="store_true", dest="runslowtests", default=False,
+           help=("run slow tests"))
+
+@pytest.fixture
+def sshhost(request):
+    val = request.config.getvalue("sshhost")
+    if val:
+        return val
+    py.test.skip("need --sshhost option")
+
+
+# XXX copied from execnet's conftest.py - needs to be merged
+winpymap = {
+    'python2.7': r'C:\Python27\python.exe',
+}
+
+
+def getexecutable(name, cache={}):
+    try:
+        return cache[name]
+    except KeyError:
+        executable = py.path.local.sysfind(name)
+        if executable:
+            if name == "jython":
+                import subprocess
+                popen = subprocess.Popen(
+                    [str(executable), "--version"],
+                    universal_newlines=True, stderr=subprocess.PIPE)
+                out, err = popen.communicate()
+                if not err or "2.5" not in err:
+                    executable = None
+        cache[name] = executable
+        return executable
+
+
+@pytest.fixture(params=('python2.7', 'pypy-c', 'jython'))
+def anypython(request):
+    name = request.param
+    executable = getexecutable(name)
+    if executable is None:
+        if sys.platform == "win32":
+            executable = winpymap.get(name, None)
+            if executable:
+                executable = py.path.local(executable)
+                if executable.check():
+                    return executable
+        py.test.skip("no %s found" % (name,))
+    return executable
diff --git a/tools/py/doc/Makefile b/tools/third_party/py/doc/Makefile
similarity index 100%
rename from tools/py/doc/Makefile
rename to tools/third_party/py/doc/Makefile
diff --git a/tools/py/doc/_templates/layout.html b/tools/third_party/py/doc/_templates/layout.html
similarity index 100%
rename from tools/py/doc/_templates/layout.html
rename to tools/third_party/py/doc/_templates/layout.html
diff --git a/tools/py/doc/announce/release-0.9.0.txt b/tools/third_party/py/doc/announce/release-0.9.0.txt
similarity index 100%
rename from tools/py/doc/announce/release-0.9.0.txt
rename to tools/third_party/py/doc/announce/release-0.9.0.txt
diff --git a/tools/py/doc/announce/release-0.9.2.txt b/tools/third_party/py/doc/announce/release-0.9.2.txt
similarity index 100%
rename from tools/py/doc/announce/release-0.9.2.txt
rename to tools/third_party/py/doc/announce/release-0.9.2.txt
diff --git a/tools/py/doc/announce/release-1.0.0.txt b/tools/third_party/py/doc/announce/release-1.0.0.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.0.0.txt
rename to tools/third_party/py/doc/announce/release-1.0.0.txt
diff --git a/tools/py/doc/announce/release-1.0.1.txt b/tools/third_party/py/doc/announce/release-1.0.1.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.0.1.txt
rename to tools/third_party/py/doc/announce/release-1.0.1.txt
diff --git a/tools/py/doc/announce/release-1.0.2.txt b/tools/third_party/py/doc/announce/release-1.0.2.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.0.2.txt
rename to tools/third_party/py/doc/announce/release-1.0.2.txt
diff --git a/tools/py/doc/announce/release-1.1.0.txt b/tools/third_party/py/doc/announce/release-1.1.0.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.1.0.txt
rename to tools/third_party/py/doc/announce/release-1.1.0.txt
diff --git a/tools/py/doc/announce/release-1.1.1.txt b/tools/third_party/py/doc/announce/release-1.1.1.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.1.1.txt
rename to tools/third_party/py/doc/announce/release-1.1.1.txt
diff --git a/tools/py/doc/announce/release-1.2.0.txt b/tools/third_party/py/doc/announce/release-1.2.0.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.2.0.txt
rename to tools/third_party/py/doc/announce/release-1.2.0.txt
diff --git a/tools/py/doc/announce/release-1.2.1.txt b/tools/third_party/py/doc/announce/release-1.2.1.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.2.1.txt
rename to tools/third_party/py/doc/announce/release-1.2.1.txt
diff --git a/tools/py/doc/announce/release-1.3.0.txt b/tools/third_party/py/doc/announce/release-1.3.0.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.3.0.txt
rename to tools/third_party/py/doc/announce/release-1.3.0.txt
diff --git a/tools/py/doc/announce/release-1.3.1.txt b/tools/third_party/py/doc/announce/release-1.3.1.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.3.1.txt
rename to tools/third_party/py/doc/announce/release-1.3.1.txt
diff --git a/tools/py/doc/announce/release-1.3.2.txt b/tools/third_party/py/doc/announce/release-1.3.2.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.3.2.txt
rename to tools/third_party/py/doc/announce/release-1.3.2.txt
diff --git a/tools/py/doc/announce/release-1.3.3.txt b/tools/third_party/py/doc/announce/release-1.3.3.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.3.3.txt
rename to tools/third_party/py/doc/announce/release-1.3.3.txt
diff --git a/tools/py/doc/announce/release-1.3.4.txt b/tools/third_party/py/doc/announce/release-1.3.4.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.3.4.txt
rename to tools/third_party/py/doc/announce/release-1.3.4.txt
diff --git a/tools/py/doc/announce/release-1.4.0.txt b/tools/third_party/py/doc/announce/release-1.4.0.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.4.0.txt
rename to tools/third_party/py/doc/announce/release-1.4.0.txt
diff --git a/tools/py/doc/announce/release-1.4.1.txt b/tools/third_party/py/doc/announce/release-1.4.1.txt
similarity index 100%
rename from tools/py/doc/announce/release-1.4.1.txt
rename to tools/third_party/py/doc/announce/release-1.4.1.txt
diff --git a/tools/py/doc/announce/releases.txt b/tools/third_party/py/doc/announce/releases.txt
similarity index 100%
rename from tools/py/doc/announce/releases.txt
rename to tools/third_party/py/doc/announce/releases.txt
diff --git a/tools/py/doc/changelog.txt b/tools/third_party/py/doc/changelog.txt
similarity index 100%
rename from tools/py/doc/changelog.txt
rename to tools/third_party/py/doc/changelog.txt
diff --git a/tools/py/doc/code.txt b/tools/third_party/py/doc/code.txt
similarity index 100%
rename from tools/py/doc/code.txt
rename to tools/third_party/py/doc/code.txt
diff --git a/tools/py/doc/conf.py b/tools/third_party/py/doc/conf.py
similarity index 100%
rename from tools/py/doc/conf.py
rename to tools/third_party/py/doc/conf.py
diff --git a/tools/py/doc/download.html b/tools/third_party/py/doc/download.html
similarity index 100%
rename from tools/py/doc/download.html
rename to tools/third_party/py/doc/download.html
diff --git a/tools/py/doc/example/genhtml.py b/tools/third_party/py/doc/example/genhtml.py
similarity index 100%
rename from tools/py/doc/example/genhtml.py
rename to tools/third_party/py/doc/example/genhtml.py
diff --git a/tools/py/doc/example/genhtmlcss.py b/tools/third_party/py/doc/example/genhtmlcss.py
similarity index 100%
rename from tools/py/doc/example/genhtmlcss.py
rename to tools/third_party/py/doc/example/genhtmlcss.py
diff --git a/tools/py/doc/example/genxml.py b/tools/third_party/py/doc/example/genxml.py
similarity index 100%
rename from tools/py/doc/example/genxml.py
rename to tools/third_party/py/doc/example/genxml.py
diff --git a/tools/py/doc/faq.txt b/tools/third_party/py/doc/faq.txt
similarity index 100%
rename from tools/py/doc/faq.txt
rename to tools/third_party/py/doc/faq.txt
diff --git a/tools/py/doc/img/pylib.png b/tools/third_party/py/doc/img/pylib.png
similarity index 100%
rename from tools/py/doc/img/pylib.png
rename to tools/third_party/py/doc/img/pylib.png
Binary files differ
diff --git a/tools/third_party/py/doc/index.txt b/tools/third_party/py/doc/index.txt
new file mode 100644
index 0000000..c700b17
--- /dev/null
+++ b/tools/third_party/py/doc/index.txt
@@ -0,0 +1,39 @@
+.. py documentation master file, created by
+   sphinx-quickstart on Thu Oct 21 08:30:10 2010.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to py's documentation!
+=================================
+
+see :ref:`CHANGELOG <changelog>` for latest changes.
+
+.. _`pytest distribution`: http://pytest.org
+
+Contents:
+
+.. toctree::
+
+   install
+   path
+   code
+   io
+   log
+   xml
+   misc
+
+   :maxdepth: 2
+
+.. toctree::
+    :hidden:
+
+    announce/release-2.0.0
+    changelog
+    announce/*
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
+
diff --git a/tools/third_party/py/doc/install.txt b/tools/third_party/py/doc/install.txt
new file mode 100644
index 0000000..fb4056d
--- /dev/null
+++ b/tools/third_party/py/doc/install.txt
@@ -0,0 +1,88 @@
+
+.. _`py`:
+.. _`index page`: http://pypi.python.org/pypi/py/
+
+installation info in a nutshell
+===================================================
+
+**PyPI name**: py_
+
+**Pythons**: CPython 2.7, 3.4, 3.5, 3.6, PyPy-5.4
+
+**Operating systems**: Linux, Windows, OSX, Unix
+
+**Requirements**: setuptools_ or Distribute_
+
+**Installers**: ``easy_install`` and ``pip``
+
+**hg repository**: https://bitbucket.org/hpk42/py
+
+easy install or pip ``py``
+-----------------------------
+
+Both `Distribute`_ and setuptools_ provide the ``easy_install``
+installation tool with which you can type into a command line window::
+
+    easy_install -U py
+
+to install the latest release of the py lib.  The ``-U`` switch
+will trigger an upgrade if you already have an older version installed.
+
+.. note::
+
+   As of version 1.4 py does not contain py.test anymore - you
+   need to install the new `pytest`_ distribution.
+
+.. _pytest: http://pytest.org
+
+Working from version control or a tarball
+-----------------------------------------------
+
+To follow development or start experiments, checkout the
+complete code and documentation source with mercurial_::
+
+    hg clone https://bitbucket.org/hpk42/py
+
+Development takes place on the 'trunk' branch.
+
+You can also go to the python package index and
+download and unpack a TAR file::
+
+    http://pypi.python.org/pypi/py/
+
+activating a checkout with setuptools
+--------------------------------------------
+
+With a working `Distribute`_ or setuptools_ installation you can type::
+
+    python setup.py develop
+
+in order to work inline with the tools and the lib of your checkout.
+
+.. _`no-setuptools`:
+
+.. _`directly use a checkout`:
+
+.. _`setuptools`: http://pypi.python.org/pypi/setuptools
+
+
+Mailing list and issue tracker
+--------------------------------------
+
+- `py-dev developers list`_ and `commit mailing list`_.
+
+- #pylib on irc.freenode.net IRC channel for random questions.
+
+- `bitbucket issue tracker`_ use this bitbucket issue tracker to report
+  bugs or request features.
+
+.. _`bitbucket issue tracker`: http://bitbucket.org/hpk42/py/issues/
+
+.. _codespeak: http://codespeak.net/
+.. _`py-dev`:
+.. _`development mailing list`:
+.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev
+.. _`py-svn`:
+.. _`commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn
+
+.. include:: links.inc
diff --git a/tools/py/doc/io.txt b/tools/third_party/py/doc/io.txt
similarity index 100%
rename from tools/py/doc/io.txt
rename to tools/third_party/py/doc/io.txt
diff --git a/tools/py/doc/links.inc b/tools/third_party/py/doc/links.inc
similarity index 100%
rename from tools/py/doc/links.inc
rename to tools/third_party/py/doc/links.inc
diff --git a/tools/py/doc/log.txt b/tools/third_party/py/doc/log.txt
similarity index 100%
rename from tools/py/doc/log.txt
rename to tools/third_party/py/doc/log.txt
diff --git a/tools/third_party/py/doc/misc.txt b/tools/third_party/py/doc/misc.txt
new file mode 100644
index 0000000..4b45348
--- /dev/null
+++ b/tools/third_party/py/doc/misc.txt
@@ -0,0 +1,93 @@
+====================================
+Miscellaneous features of the py lib
+====================================
+
+Mapping the standard python library into py
+===========================================
+
+The ``py.std`` object allows lazy access to
+standard library modules.  For example, to get to the print-exception
+functionality of the standard library you can write::
+
+    py.std.traceback.print_exc()
+
+without having to do anything else than the usual ``import py``
+at the beginning.  You can access any other top-level standard
+library module this way.  This means that you will only trigger
+imports of modules that are actually needed.  Note that no attempt
+is made to import submodules.
+
+Support for interaction with system utilities/binaries
+======================================================
+
+Currently, the py lib offers two ways to interact with
+system executables. ``py.process.cmdexec()`` invokes
+the shell in order to execute a string.  The other
+one, ``py.path.local``'s 'sysexec()' method lets you
+directly execute a binary.
+
+Both approaches will raise an exception in case of a return-
+code other than 0 and otherwise return the stdout-output
+of the child process.
+
+The shell based approach
+------------------------
+
+You can execute a command via your system shell
+by doing something like::
+
+    out = py.process.cmdexec('ls -v')
+
+However, the ``cmdexec`` approach has a few shortcomings:
+
+- it relies on the underlying system shell
+- it neccessitates shell-escaping for expressing arguments
+- it does not easily allow to "fix" the binary you want to run.
+- it only allows to execute executables from the local
+  filesystem
+
+.. _sysexec:
+
+local paths have ``sysexec``
+----------------------------
+
+In order to synchronously execute an executable file you
+can use ``sysexec``::
+
+    binsvn.sysexec('ls', 'http://codespeak.net/svn')
+
+where ``binsvn`` is a path that points to the ``svn`` commandline
+binary. Note that this function does not offer any shell-escaping
+so you have to pass in already separated arguments.
+
+finding an executable local path
+--------------------------------
+
+Finding an executable is quite different on multiple platforms.
+Currently, the ``PATH`` environment variable based search on
+unix platforms is supported::
+
+    py.path.local.sysfind('svn')
+
+which returns the first path whose ``basename`` matches ``svn``.
+In principle, `sysfind` deploys platform specific algorithms
+to perform the search.  On Windows, for example, it may look
+at the registry (XXX).
+
+To make the story complete, we allow to pass in a second ``checker``
+argument that is called for each found executable.  For example, if
+you have multiple binaries available you may want to select the
+right version::
+
+    def mysvn(p):
+        """ check that the given svn binary has version 1.1. """
+        line = p.execute('--version'').readlines()[0]
+        if line.find('version 1.1'):
+            return p
+    binsvn = py.path.local.sysfind('svn', checker=mysvn)
+
+
+Cross-Python Version compatibility helpers
+=============================================
+
+The ``py.builtin`` namespace provides a number of helpers that help to write python code compatible across Python interpreters, mainly Python2 and Python3.  Type ``help(py.builtin)`` on a Python prompt for the selection of builtins.
diff --git a/tools/third_party/py/doc/path.txt b/tools/third_party/py/doc/path.txt
new file mode 100644
index 0000000..c906179
--- /dev/null
+++ b/tools/third_party/py/doc/path.txt
@@ -0,0 +1,258 @@
+=======
+py.path
+=======
+
+The 'py' lib provides a uniform high-level api to deal with filesystems
+and filesystem-like interfaces: ``py.path``.  It aims to offer a central
+object to fs-like object trees (reading from and writing to files, adding
+files/directories, examining the types and structure, etc.), and out-of-the-box
+provides a number of implementations of this API.
+
+py.path.local - local file system path
+===============================================
+
+.. _`local`:
+
+basic interactive example
+-------------------------------------
+
+The first and most obvious of the implementations is a wrapper around a local
+filesystem. It's just a bit nicer in usage than the regular Python APIs, and
+of course all the functionality is bundled together rather than spread over a
+number of modules.
+
+
+.. sourcecode:: pycon
+
+  >>> import py
+  >>> temppath = py.path.local('py.path_documentation')
+  >>> foopath = temppath.join('foo') # get child 'foo' (lazily)
+  >>> foopath.check() # check if child 'foo' exists
+  False
+  >>> foopath.write('bar') # write some data to it
+  >>> foopath.check()
+  True
+  >>> foopath.read()
+  'bar'
+  >>> foofile = foopath.open() # return a 'real' file object
+  >>> foofile.read(1)
+  'b'
+
+reference documentation
+---------------------------------
+
+.. autoclass:: py._path.local.LocalPath
+    :members:
+    :inherited-members:
+
+``py.path.svnurl`` and ``py.path.svnwc``
+==================================================
+
+Two other ``py.path`` implementations that the py lib provides wrap the
+popular `Subversion`_ revision control system: the first (called 'svnurl')
+by interfacing with a remote server, the second by wrapping a local checkout.
+Both allow you to access relatively advanced features such as metadata and
+versioning, and both in a way more user-friendly manner than existing other
+solutions.
+
+Some example usage of ``py.path.svnurl``:
+
+.. sourcecode:: pycon
+
+  .. >>> import py
+  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
+  >>> url = py.path.svnurl('http://codespeak.net/svn/py')
+  >>> info = url.info()
+  >>> info.kind
+  'dir'
+  >>> firstentry = url.log()[-1]
+  >>> import time
+  >>> time.strftime('%Y-%m-%d', time.gmtime(firstentry.date))
+  '2004-10-02'
+
+Example usage of ``py.path.svnwc``:
+
+.. sourcecode:: pycon
+
+  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
+  >>> temp = py.path.local('py.path_documentation')
+  >>> wc = py.path.svnwc(temp.join('svnwc'))
+  >>> wc.checkout('http://codespeak.net/svn/py/dist/py/path/local')
+  >>> wc.join('local.py').check()
+  True
+
+.. _`Subversion`: http://subversion.tigris.org/
+
+svn path related API reference
+-----------------------------------------
+
+.. autoclass:: py._path.svnwc.SvnWCCommandPath
+    :members:
+    :inherited-members:
+
+.. autoclass:: py._path.svnurl.SvnCommandPath
+    :members:
+    :inherited-members:
+
+.. autoclass:: py._path.svnwc.SvnAuth
+    :members:
+    :inherited-members:
+
+Common vs. specific API, Examples
+========================================
+
+All Path objects support a common set of operations, suitable
+for many use cases and allowing to transparently switch the
+path object within an application (e.g. from "local" to "svnwc").
+The common set includes functions such as `path.read()` to read all data
+from a file, `path.write()` to write data, `path.listdir()` to get a list
+of directory entries, `path.check()` to check if a node exists
+and is of a particular type, `path.join()` to get
+to a (grand)child, `path.visit()` to recursively walk through a node's
+children, etc.  Only things that are not common on 'normal' filesystems (yet),
+such as handling metadata (e.g. the Subversion "properties") require
+using specific APIs.
+
+A quick 'cookbook' of small examples that will be useful 'in real life',
+which also presents parts of the 'common' API, and shows some non-common
+methods:
+
+Searching `.txt` files
+--------------------------------
+
+Search for a particular string inside all files with a .txt extension in a
+specific directory.
+
+.. sourcecode:: pycon
+
+  >>> dirpath = temppath.ensure('testdir', dir=True)
+  >>> dirpath.join('textfile1.txt').write('foo bar baz')
+  >>> dirpath.join('textfile2.txt').write('frob bar spam eggs')
+  >>> subdir = dirpath.ensure('subdir', dir=True)
+  >>> subdir.join('textfile1.txt').write('foo baz')
+  >>> subdir.join('textfile2.txt').write('spam eggs spam foo bar spam')
+  >>> results = []
+  >>> for fpath in dirpath.visit('*.txt'):
+  ...     if 'bar' in fpath.read():
+  ...         results.append(fpath.basename)
+  >>> results.sort()
+  >>> results
+  ['textfile1.txt', 'textfile2.txt', 'textfile2.txt']
+
+Working with Paths
+----------------------------
+
+This example shows the ``py.path`` features to deal with
+filesystem paths Note that the filesystem is never touched,
+all operations are performed on a string level (so the paths
+don't have to exist, either):
+
+.. sourcecode:: pycon
+
+  >>> p1 = py.path.local('/foo/bar')
+  >>> p2 = p1.join('baz/qux')
+  >>> p2 == py.path.local('/foo/bar/baz/qux')
+  True
+  >>> sep = py.path.local.sep
+  >>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string
+  'baz/qux'
+  >>> p2.bestrelpath(p1).replace(sep, '/')
+  '../..'
+  >>> p2.join(p2.bestrelpath(p1)) == p1
+  True
+  >>> p3 = p1 / 'baz/qux' # the / operator allows joining, too
+  >>> p2 == p3
+  True
+  >>> p4 = p1 + ".py"
+  >>> p4.basename == "bar.py"
+  True
+  >>> p4.ext == ".py"
+  True
+  >>> p4.purebasename == "bar"
+  True
+
+This should be possible on every implementation of ``py.path``, so
+regardless of whether the implementation wraps a UNIX filesystem, a Windows
+one, or a database or object tree, these functions should be available (each
+with their own notion of path seperators and dealing with conversions, etc.).
+
+Checking path types
+-------------------------------
+
+Now we will show a bit about the powerful 'check()' method on paths, which
+allows you to check whether a file exists, what type it is, etc.:
+
+.. sourcecode:: pycon
+
+  >>> file1 = temppath.join('file1')
+  >>> file1.check() # does it exist?
+  False
+  >>> file1 = file1.ensure(file=True) # 'touch' the file
+  >>> file1.check()
+  True
+  >>> file1.check(dir=True) # is it a dir?
+  False
+  >>> file1.check(file=True) # or a file?
+  True
+  >>> file1.check(ext='.txt') # check the extension
+  False
+  >>> textfile = temppath.ensure('text.txt', file=True)
+  >>> textfile.check(ext='.txt')
+  True
+  >>> file1.check(basename='file1') # we can use all the path's properties here
+  True
+
+Setting svn-properties
+--------------------------------
+
+As an example of 'uncommon' methods, we'll show how to read and write
+properties in an ``py.path.svnwc`` instance:
+
+.. sourcecode:: pycon
+
+  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
+  >>> wc.propget('foo')
+  ''
+  >>> wc.propset('foo', 'bar')
+  >>> wc.propget('foo')
+  'bar'
+  >>> len(wc.status().prop_modified) # our own props
+  1
+  >>> msg = wc.revert() # roll back our changes
+  >>> len(wc.status().prop_modified)
+  0
+
+SVN authentication
+----------------------------
+
+Some uncommon functionality can also be provided as extensions, such as SVN
+authentication:
+
+.. sourcecode:: pycon
+
+  .. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
+  >>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False,
+  ...             interactive=False)
+  >>> wc.auth = auth
+  >>> wc.update() # this should work
+  >>> path = wc.ensure('thisshouldnotexist.txt')
+  >>> try:
+  ...     path.commit('testing')
+  ... except py.process.cmdexec.Error, e:
+  ...     pass
+  >>> 'authorization failed' in str(e)
+  True
+
+Known problems / limitations
+===================================
+
+* The SVN path objects require the "svn" command line,
+  there is currently no support for python bindings.
+  Parsing the svn output can lead to problems, particularly
+  regarding if you have a non-english "locales" setting.
+
+* While the path objects basically work on windows,
+  there is no attention yet on making unicode paths
+  work or deal with the famous "8.3" filename issues.
+
+
diff --git a/tools/py/doc/style.css b/tools/third_party/py/doc/style.css
similarity index 100%
rename from tools/py/doc/style.css
rename to tools/third_party/py/doc/style.css
diff --git a/tools/py/doc/xml.txt b/tools/third_party/py/doc/xml.txt
similarity index 100%
rename from tools/py/doc/xml.txt
rename to tools/third_party/py/doc/xml.txt
diff --git a/tools/third_party/py/py/__init__.py b/tools/third_party/py/py/__init__.py
new file mode 100644
index 0000000..b5e0c16
--- /dev/null
+++ b/tools/third_party/py/py/__init__.py
@@ -0,0 +1,154 @@
+"""
+pylib: rapid testing and development utils
+
+this module uses apipkg.py for lazy-loading sub modules
+and classes.  The initpkg-dictionary  below specifies
+name->value mappings where value can be another namespace
+dictionary or an import path.
+
+(c) Holger Krekel and others, 2004-2014
+"""
+__version__ = '1.5.2'
+
+try:
+    from py._vendored_packages import apipkg
+    lib_not_mangled_by_packagers = True
+    vendor_prefix = '._vendored_packages.'
+except ImportError:
+    import apipkg
+    lib_not_mangled_by_packagers = False
+    vendor_prefix = ''
+
+# so that py.error.* instances are picklable
+import sys
+
+apipkg.initpkg(__name__, attr={'_apipkg': apipkg}, exportdefs={
+    # access to all standard lib modules
+    'std': '._std:std',
+    # access to all posix errno's as classes
+    'error': '._error:error',
+
+    '_pydir' : '.__metainfo:pydir',
+    'version': 'py:__version__', # backward compatibility
+
+    # pytest-2.0 has a flat namespace, we use alias modules
+    # to keep old references compatible
+    'test' : 'pytest',
+
+    # hook into the top-level standard library
+    'process' : {
+        '__doc__'        : '._process:__doc__',
+        'cmdexec'        : '._process.cmdexec:cmdexec',
+        'kill'           : '._process.killproc:kill',
+        'ForkedFunc'     : '._process.forkedfunc:ForkedFunc',
+    },
+
+    'apipkg' : {
+        'initpkg'   : vendor_prefix + 'apipkg:initpkg',
+        'ApiModule' : vendor_prefix + 'apipkg:ApiModule',
+    },
+
+    'iniconfig' : {
+        'IniConfig'      : vendor_prefix + 'iniconfig:IniConfig',
+        'ParseError'     : vendor_prefix + 'iniconfig:ParseError',
+    },
+
+    'path' : {
+        '__doc__'        : '._path:__doc__',
+        'svnwc'          : '._path.svnwc:SvnWCCommandPath',
+        'svnurl'         : '._path.svnurl:SvnCommandPath',
+        'local'          : '._path.local:LocalPath',
+        'SvnAuth'        : '._path.svnwc:SvnAuth',
+    },
+
+    # python inspection/code-generation API
+    'code' : {
+        '__doc__'           : '._code:__doc__',
+        'compile'           : '._code.source:compile_',
+        'Source'            : '._code.source:Source',
+        'Code'              : '._code.code:Code',
+        'Frame'             : '._code.code:Frame',
+        'ExceptionInfo'     : '._code.code:ExceptionInfo',
+        'Traceback'         : '._code.code:Traceback',
+        'getfslineno'       : '._code.source:getfslineno',
+        'getrawcode'        : '._code.code:getrawcode',
+        'patch_builtins'    : '._code.code:patch_builtins',
+        'unpatch_builtins'  : '._code.code:unpatch_builtins',
+        '_AssertionError'   : '._code.assertion:AssertionError',
+        '_reinterpret_old'  : '._code.assertion:reinterpret_old',
+        '_reinterpret'      : '._code.assertion:reinterpret',
+        '_reprcompare'      : '._code.assertion:_reprcompare',
+        '_format_explanation' : '._code.assertion:_format_explanation',
+    },
+
+    # backports and additions of builtins
+    'builtin' : {
+        '__doc__'        : '._builtin:__doc__',
+        'enumerate'      : '._builtin:enumerate',
+        'reversed'       : '._builtin:reversed',
+        'sorted'         : '._builtin:sorted',
+        'any'            : '._builtin:any',
+        'all'            : '._builtin:all',
+        'set'            : '._builtin:set',
+        'frozenset'      : '._builtin:frozenset',
+        'BaseException'  : '._builtin:BaseException',
+        'GeneratorExit'  : '._builtin:GeneratorExit',
+        '_sysex'         : '._builtin:_sysex',
+        'print_'         : '._builtin:print_',
+        '_reraise'       : '._builtin:_reraise',
+        '_tryimport'     : '._builtin:_tryimport',
+        'exec_'          : '._builtin:exec_',
+        '_basestring'    : '._builtin:_basestring',
+        '_totext'        : '._builtin:_totext',
+        '_isbytes'       : '._builtin:_isbytes',
+        '_istext'        : '._builtin:_istext',
+        '_getimself'     : '._builtin:_getimself',
+        '_getfuncdict'   : '._builtin:_getfuncdict',
+        '_getcode'       : '._builtin:_getcode',
+        'builtins'       : '._builtin:builtins',
+        'execfile'       : '._builtin:execfile',
+        'callable'       : '._builtin:callable',
+        'bytes'       : '._builtin:bytes',
+        'text'       : '._builtin:text',
+    },
+
+    # input-output helping
+    'io' : {
+        '__doc__'             : '._io:__doc__',
+        'dupfile'             : '._io.capture:dupfile',
+        'TextIO'              : '._io.capture:TextIO',
+        'BytesIO'             : '._io.capture:BytesIO',
+        'FDCapture'           : '._io.capture:FDCapture',
+        'StdCapture'          : '._io.capture:StdCapture',
+        'StdCaptureFD'        : '._io.capture:StdCaptureFD',
+        'TerminalWriter'      : '._io.terminalwriter:TerminalWriter',
+        'ansi_print'          : '._io.terminalwriter:ansi_print',
+        'get_terminal_width'  : '._io.terminalwriter:get_terminal_width',
+        'saferepr'            : '._io.saferepr:saferepr',
+    },
+
+    # small and mean xml/html generation
+    'xml' : {
+        '__doc__'            : '._xmlgen:__doc__',
+        'html'               : '._xmlgen:html',
+        'Tag'                : '._xmlgen:Tag',
+        'raw'                : '._xmlgen:raw',
+        'Namespace'          : '._xmlgen:Namespace',
+        'escape'             : '._xmlgen:escape',
+    },
+
+    'log' : {
+        # logging API ('producers' and 'consumers' connected via keywords)
+        '__doc__'            : '._log:__doc__',
+        '_apiwarn'           : '._log.warning:_apiwarn',
+        'Producer'           : '._log.log:Producer',
+        'setconsumer'        : '._log.log:setconsumer',
+        '_setstate'          : '._log.log:setstate',
+        '_getstate'          : '._log.log:getstate',
+        'Path'               : '._log.log:Path',
+        'STDOUT'             : '._log.log:STDOUT',
+        'STDERR'             : '._log.log:STDERR',
+        'Syslog'             : '._log.log:Syslog',
+    },
+
+})
diff --git a/tools/py/py/__metainfo.py b/tools/third_party/py/py/__metainfo.py
similarity index 100%
rename from tools/py/py/__metainfo.py
rename to tools/third_party/py/py/__metainfo.py
diff --git a/tools/py/py/_builtin.py b/tools/third_party/py/py/_builtin.py
similarity index 100%
rename from tools/py/py/_builtin.py
rename to tools/third_party/py/py/_builtin.py
diff --git a/tools/py/py/_code/__init__.py b/tools/third_party/py/py/_code/__init__.py
similarity index 100%
rename from tools/py/py/_code/__init__.py
rename to tools/third_party/py/py/_code/__init__.py
diff --git a/tools/third_party/py/py/_code/_assertionnew.py b/tools/third_party/py/py/_code/_assertionnew.py
new file mode 100644
index 0000000..d03f29d
--- /dev/null
+++ b/tools/third_party/py/py/_code/_assertionnew.py
@@ -0,0 +1,322 @@
+"""
+Find intermediate evalutation results in assert statements through builtin AST.
+This should replace _assertionold.py eventually.
+"""
+
+import sys
+import ast
+
+import py
+from py._code.assertion import _format_explanation, BuiltinAssertionError
+
+
+def _is_ast_expr(node):
+    return isinstance(node, ast.expr)
+def _is_ast_stmt(node):
+    return isinstance(node, ast.stmt)
+
+
+class Failure(Exception):
+    """Error found while interpreting AST."""
+
+    def __init__(self, explanation=""):
+        self.cause = sys.exc_info()
+        self.explanation = explanation
+
+
+def interpret(source, frame, should_fail=False):
+    mod = ast.parse(source)
+    visitor = DebugInterpreter(frame)
+    try:
+        visitor.visit(mod)
+    except Failure:
+        failure = sys.exc_info()[1]
+        return getfailure(failure)
+    if should_fail:
+        return ("(assertion failed, but when it was re-run for "
+                "printing intermediate values, it did not fail.  Suggestions: "
+                "compute assert expression before the assert or use --no-assert)")
+
+def run(offending_line, frame=None):
+    if frame is None:
+        frame = py.code.Frame(sys._getframe(1))
+    return interpret(offending_line, frame)
+
+def getfailure(failure):
+    explanation = _format_explanation(failure.explanation)
+    value = failure.cause[1]
+    if str(value):
+        lines = explanation.splitlines()
+        if not lines:
+            lines.append("")
+        lines[0] += " << %s" % (value,)
+        explanation = "\n".join(lines)
+    text = "%s: %s" % (failure.cause[0].__name__, explanation)
+    if text.startswith("AssertionError: assert "):
+        text = text[16:]
+    return text
+
+
+operator_map = {
+    ast.BitOr : "|",
+    ast.BitXor : "^",
+    ast.BitAnd : "&",
+    ast.LShift : "<<",
+    ast.RShift : ">>",
+    ast.Add : "+",
+    ast.Sub : "-",
+    ast.Mult : "*",
+    ast.Div : "/",
+    ast.FloorDiv : "//",
+    ast.Mod : "%",
+    ast.Eq : "==",
+    ast.NotEq : "!=",
+    ast.Lt : "<",
+    ast.LtE : "<=",
+    ast.Gt : ">",
+    ast.GtE : ">=",
+    ast.Pow : "**",
+    ast.Is : "is",
+    ast.IsNot : "is not",
+    ast.In : "in",
+    ast.NotIn : "not in"
+}
+
+unary_map = {
+    ast.Not : "not %s",
+    ast.Invert : "~%s",
+    ast.USub : "-%s",
+    ast.UAdd : "+%s"
+}
+
+
+class DebugInterpreter(ast.NodeVisitor):
+    """Interpret AST nodes to gleam useful debugging information. """
+
+    def __init__(self, frame):
+        self.frame = frame
+
+    def generic_visit(self, node):
+        # Fallback when we don't have a special implementation.
+        if _is_ast_expr(node):
+            mod = ast.Expression(node)
+            co = self._compile(mod)
+            try:
+                result = self.frame.eval(co)
+            except Exception:
+                raise Failure()
+            explanation = self.frame.repr(result)
+            return explanation, result
+        elif _is_ast_stmt(node):
+            mod = ast.Module([node])
+            co = self._compile(mod, "exec")
+            try:
+                self.frame.exec_(co)
+            except Exception:
+                raise Failure()
+            return None, None
+        else:
+            raise AssertionError("can't handle %s" %(node,))
+
+    def _compile(self, source, mode="eval"):
+        return compile(source, "<assertion interpretation>", mode)
+
+    def visit_Expr(self, expr):
+        return self.visit(expr.value)
+
+    def visit_Module(self, mod):
+        for stmt in mod.body:
+            self.visit(stmt)
+
+    def visit_Name(self, name):
+        explanation, result = self.generic_visit(name)
+        # See if the name is local.
+        source = "%r in locals() is not globals()" % (name.id,)
+        co = self._compile(source)
+        try:
+            local = self.frame.eval(co)
+        except Exception:
+            # have to assume it isn't
+            local = False
+        if not local:
+            return name.id, result
+        return explanation, result
+
+    def visit_Compare(self, comp):
+        left = comp.left
+        left_explanation, left_result = self.visit(left)
+        for op, next_op in zip(comp.ops, comp.comparators):
+            next_explanation, next_result = self.visit(next_op)
+            op_symbol = operator_map[op.__class__]
+            explanation = "%s %s %s" % (left_explanation, op_symbol,
+                                        next_explanation)
+            source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
+            co = self._compile(source)
+            try:
+                result = self.frame.eval(co, __exprinfo_left=left_result,
+                                         __exprinfo_right=next_result)
+            except Exception:
+                raise Failure(explanation)
+            try:
+                if not result:
+                    break
+            except KeyboardInterrupt:
+                raise
+            except:
+                break
+            left_explanation, left_result = next_explanation, next_result
+
+        rcomp = py.code._reprcompare
+        if rcomp:
+            res = rcomp(op_symbol, left_result, next_result)
+            if res:
+                explanation = res
+        return explanation, result
+
+    def visit_BoolOp(self, boolop):
+        is_or = isinstance(boolop.op, ast.Or)
+        explanations = []
+        for operand in boolop.values:
+            explanation, result = self.visit(operand)
+            explanations.append(explanation)
+            if result == is_or:
+                break
+        name = is_or and " or " or " and "
+        explanation = "(" + name.join(explanations) + ")"
+        return explanation, result
+
+    def visit_UnaryOp(self, unary):
+        pattern = unary_map[unary.op.__class__]
+        operand_explanation, operand_result = self.visit(unary.operand)
+        explanation = pattern % (operand_explanation,)
+        co = self._compile(pattern % ("__exprinfo_expr",))
+        try:
+            result = self.frame.eval(co, __exprinfo_expr=operand_result)
+        except Exception:
+            raise Failure(explanation)
+        return explanation, result
+
+    def visit_BinOp(self, binop):
+        left_explanation, left_result = self.visit(binop.left)
+        right_explanation, right_result = self.visit(binop.right)
+        symbol = operator_map[binop.op.__class__]
+        explanation = "(%s %s %s)" % (left_explanation, symbol,
+                                      right_explanation)
+        source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
+        co = self._compile(source)
+        try:
+            result = self.frame.eval(co, __exprinfo_left=left_result,
+                                     __exprinfo_right=right_result)
+        except Exception:
+            raise Failure(explanation)
+        return explanation, result
+
+    def visit_Call(self, call):
+        func_explanation, func = self.visit(call.func)
+        arg_explanations = []
+        ns = {"__exprinfo_func" : func}
+        arguments = []
+        for arg in call.args:
+            arg_explanation, arg_result = self.visit(arg)
+            arg_name = "__exprinfo_%s" % (len(ns),)
+            ns[arg_name] = arg_result
+            arguments.append(arg_name)
+            arg_explanations.append(arg_explanation)
+        for keyword in call.keywords:
+            arg_explanation, arg_result = self.visit(keyword.value)
+            arg_name = "__exprinfo_%s" % (len(ns),)
+            ns[arg_name] = arg_result
+            keyword_source = "%s=%%s" % (keyword.arg)
+            arguments.append(keyword_source % (arg_name,))
+            arg_explanations.append(keyword_source % (arg_explanation,))
+        if call.starargs:
+            arg_explanation, arg_result = self.visit(call.starargs)
+            arg_name = "__exprinfo_star"
+            ns[arg_name] = arg_result
+            arguments.append("*%s" % (arg_name,))
+            arg_explanations.append("*%s" % (arg_explanation,))
+        if call.kwargs:
+            arg_explanation, arg_result = self.visit(call.kwargs)
+            arg_name = "__exprinfo_kwds"
+            ns[arg_name] = arg_result
+            arguments.append("**%s" % (arg_name,))
+            arg_explanations.append("**%s" % (arg_explanation,))
+        args_explained = ", ".join(arg_explanations)
+        explanation = "%s(%s)" % (func_explanation, args_explained)
+        args = ", ".join(arguments)
+        source = "__exprinfo_func(%s)" % (args,)
+        co = self._compile(source)
+        try:
+            result = self.frame.eval(co, **ns)
+        except Exception:
+            raise Failure(explanation)
+        pattern = "%s\n{%s = %s\n}"
+        rep = self.frame.repr(result)
+        explanation = pattern % (rep, rep, explanation)
+        return explanation, result
+
+    def _is_builtin_name(self, name):
+        pattern = "%r not in globals() and %r not in locals()"
+        source = pattern % (name.id, name.id)
+        co = self._compile(source)
+        try:
+            return self.frame.eval(co)
+        except Exception:
+            return False
+
+    def visit_Attribute(self, attr):
+        if not isinstance(attr.ctx, ast.Load):
+            return self.generic_visit(attr)
+        source_explanation, source_result = self.visit(attr.value)
+        explanation = "%s.%s" % (source_explanation, attr.attr)
+        source = "__exprinfo_expr.%s" % (attr.attr,)
+        co = self._compile(source)
+        try:
+            result = self.frame.eval(co, __exprinfo_expr=source_result)
+        except Exception:
+            raise Failure(explanation)
+        explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
+                                              self.frame.repr(result),
+                                              source_explanation, attr.attr)
+        # Check if the attr is from an instance.
+        source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
+        source = source % (attr.attr,)
+        co = self._compile(source)
+        try:
+            from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
+        except Exception:
+            from_instance = True
+        if from_instance:
+            rep = self.frame.repr(result)
+            pattern = "%s\n{%s = %s\n}"
+            explanation = pattern % (rep, rep, explanation)
+        return explanation, result
+
+    def visit_Assert(self, assrt):
+        test_explanation, test_result = self.visit(assrt.test)
+        if test_explanation.startswith("False\n{False =") and \
+                test_explanation.endswith("\n"):
+            test_explanation = test_explanation[15:-2]
+        explanation = "assert %s" % (test_explanation,)
+        if not test_result:
+            try:
+                raise BuiltinAssertionError
+            except Exception:
+                raise Failure(explanation)
+        return explanation, test_result
+
+    def visit_Assign(self, assign):
+        value_explanation, value_result = self.visit(assign.value)
+        explanation = "... = %s" % (value_explanation,)
+        name = ast.Name("__exprinfo_expr", ast.Load(),
+                        lineno=assign.value.lineno,
+                        col_offset=assign.value.col_offset)
+        new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
+                                col_offset=assign.col_offset)
+        mod = ast.Module([new_assign])
+        co = self._compile(mod, "exec")
+        try:
+            self.frame.exec_(co, __exprinfo_expr=value_result)
+        except Exception:
+            raise Failure(explanation)
+        return explanation, value_result
diff --git a/tools/third_party/py/py/_code/_assertionold.py b/tools/third_party/py/py/_code/_assertionold.py
new file mode 100644
index 0000000..1bb70a8
--- /dev/null
+++ b/tools/third_party/py/py/_code/_assertionold.py
@@ -0,0 +1,556 @@
+import py
+import sys, inspect
+from compiler import parse, ast, pycodegen
+from py._code.assertion import BuiltinAssertionError, _format_explanation
+import types
+
+passthroughex = py.builtin._sysex
+
+class Failure:
+    def __init__(self, node):
+        self.exc, self.value, self.tb = sys.exc_info()
+        self.node = node
+
+class View(object):
+    """View base class.
+
+    If C is a subclass of View, then C(x) creates a proxy object around
+    the object x.  The actual class of the proxy is not C in general,
+    but a *subclass* of C determined by the rules below.  To avoid confusion
+    we call view class the class of the proxy (a subclass of C, so of View)
+    and object class the class of x.
+
+    Attributes and methods not found in the proxy are automatically read on x.
+    Other operations like setting attributes are performed on the proxy, as
+    determined by its view class.  The object x is available from the proxy
+    as its __obj__ attribute.
+
+    The view class selection is determined by the __view__ tuples and the
+    optional __viewkey__ method.  By default, the selected view class is the
+    most specific subclass of C whose __view__ mentions the class of x.
+    If no such subclass is found, the search proceeds with the parent
+    object classes.  For example, C(True) will first look for a subclass
+    of C with __view__ = (..., bool, ...) and only if it doesn't find any
+    look for one with __view__ = (..., int, ...), and then ..., object,...
+    If everything fails the class C itself is considered to be the default.
+
+    Alternatively, the view class selection can be driven by another aspect
+    of the object x, instead of the class of x, by overriding __viewkey__.
+    See last example at the end of this module.
+    """
+
+    _viewcache = {}
+    __view__ = ()
+
+    def __new__(rootclass, obj, *args, **kwds):
+        self = object.__new__(rootclass)
+        self.__obj__ = obj
+        self.__rootclass__ = rootclass
+        key = self.__viewkey__()
+        try:
+            self.__class__ = self._viewcache[key]
+        except KeyError:
+            self.__class__ = self._selectsubclass(key)
+        return self
+
+    def __getattr__(self, attr):
+        # attributes not found in the normal hierarchy rooted on View
+        # are looked up in the object's real class
+        return getattr(self.__obj__, attr)
+
+    def __viewkey__(self):
+        return self.__obj__.__class__
+
+    def __matchkey__(self, key, subclasses):
+        if inspect.isclass(key):
+            keys = inspect.getmro(key)
+        else:
+            keys = [key]
+        for key in keys:
+            result = [C for C in subclasses if key in C.__view__]
+            if result:
+                return result
+        return []
+
+    def _selectsubclass(self, key):
+        subclasses = list(enumsubclasses(self.__rootclass__))
+        for C in subclasses:
+            if not isinstance(C.__view__, tuple):
+                C.__view__ = (C.__view__,)
+        choices = self.__matchkey__(key, subclasses)
+        if not choices:
+            return self.__rootclass__
+        elif len(choices) == 1:
+            return choices[0]
+        else:
+            # combine the multiple choices
+            return type('?', tuple(choices), {})
+
+    def __repr__(self):
+        return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
+
+
+def enumsubclasses(cls):
+    for subcls in cls.__subclasses__():
+        for subsubclass in enumsubclasses(subcls):
+            yield subsubclass
+    yield cls
+
+
+class Interpretable(View):
+    """A parse tree node with a few extra methods."""
+    explanation = None
+
+    def is_builtin(self, frame):
+        return False
+
+    def eval(self, frame):
+        # fall-back for unknown expression nodes
+        try:
+            expr = ast.Expression(self.__obj__)
+            expr.filename = '<eval>'
+            self.__obj__.filename = '<eval>'
+            co = pycodegen.ExpressionCodeGenerator(expr).getCode()
+            result = frame.eval(co)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+        self.result = result
+        self.explanation = self.explanation or frame.repr(self.result)
+
+    def run(self, frame):
+        # fall-back for unknown statement nodes
+        try:
+            expr = ast.Module(None, ast.Stmt([self.__obj__]))
+            expr.filename = '<run>'
+            co = pycodegen.ModuleCodeGenerator(expr).getCode()
+            frame.exec_(co)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+
+    def nice_explanation(self):
+        return _format_explanation(self.explanation)
+
+
+class Name(Interpretable):
+    __view__ = ast.Name
+
+    def is_local(self, frame):
+        source = '%r in locals() is not globals()' % self.name
+        try:
+            return frame.is_true(frame.eval(source))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def is_global(self, frame):
+        source = '%r in globals()' % self.name
+        try:
+            return frame.is_true(frame.eval(source))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def is_builtin(self, frame):
+        source = '%r not in locals() and %r not in globals()' % (
+            self.name, self.name)
+        try:
+            return frame.is_true(frame.eval(source))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def eval(self, frame):
+        super(Name, self).eval(frame)
+        if not self.is_local(frame):
+            self.explanation = self.name
+
+class Compare(Interpretable):
+    __view__ = ast.Compare
+
+    def eval(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        for operation, expr2 in self.ops:
+            if hasattr(self, 'result'):
+                # shortcutting in chained expressions
+                if not frame.is_true(self.result):
+                    break
+            expr2 = Interpretable(expr2)
+            expr2.eval(frame)
+            self.explanation = "%s %s %s" % (
+                expr.explanation, operation, expr2.explanation)
+            source = "__exprinfo_left %s __exprinfo_right" % operation
+            try:
+                self.result = frame.eval(source,
+                                         __exprinfo_left=expr.result,
+                                         __exprinfo_right=expr2.result)
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+            expr = expr2
+
+class And(Interpretable):
+    __view__ = ast.And
+
+    def eval(self, frame):
+        explanations = []
+        for expr in self.nodes:
+            expr = Interpretable(expr)
+            expr.eval(frame)
+            explanations.append(expr.explanation)
+            self.result = expr.result
+            if not frame.is_true(expr.result):
+                break
+        self.explanation = '(' + ' and '.join(explanations) + ')'
+
+class Or(Interpretable):
+    __view__ = ast.Or
+
+    def eval(self, frame):
+        explanations = []
+        for expr in self.nodes:
+            expr = Interpretable(expr)
+            expr.eval(frame)
+            explanations.append(expr.explanation)
+            self.result = expr.result
+            if frame.is_true(expr.result):
+                break
+        self.explanation = '(' + ' or '.join(explanations) + ')'
+
+
+# == Unary operations ==
+keepalive = []
+for astclass, astpattern in {
+    ast.Not    : 'not __exprinfo_expr',
+    ast.Invert : '(~__exprinfo_expr)',
+    }.items():
+
+    class UnaryArith(Interpretable):
+        __view__ = astclass
+
+        def eval(self, frame, astpattern=astpattern):
+            expr = Interpretable(self.expr)
+            expr.eval(frame)
+            self.explanation = astpattern.replace('__exprinfo_expr',
+                                                  expr.explanation)
+            try:
+                self.result = frame.eval(astpattern,
+                                         __exprinfo_expr=expr.result)
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+
+    keepalive.append(UnaryArith)
+
+# == Binary operations ==
+for astclass, astpattern in {
+    ast.Add    : '(__exprinfo_left + __exprinfo_right)',
+    ast.Sub    : '(__exprinfo_left - __exprinfo_right)',
+    ast.Mul    : '(__exprinfo_left * __exprinfo_right)',
+    ast.Div    : '(__exprinfo_left / __exprinfo_right)',
+    ast.Mod    : '(__exprinfo_left % __exprinfo_right)',
+    ast.Power  : '(__exprinfo_left ** __exprinfo_right)',
+    }.items():
+
+    class BinaryArith(Interpretable):
+        __view__ = astclass
+
+        def eval(self, frame, astpattern=astpattern):
+            left = Interpretable(self.left)
+            left.eval(frame)
+            right = Interpretable(self.right)
+            right.eval(frame)
+            self.explanation = (astpattern
+                                .replace('__exprinfo_left',  left .explanation)
+                                .replace('__exprinfo_right', right.explanation))
+            try:
+                self.result = frame.eval(astpattern,
+                                         __exprinfo_left=left.result,
+                                         __exprinfo_right=right.result)
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+
+    keepalive.append(BinaryArith)
+
+
+class CallFunc(Interpretable):
+    __view__ = ast.CallFunc
+
+    def is_bool(self, frame):
+        source = 'isinstance(__exprinfo_value, bool)'
+        try:
+            return frame.is_true(frame.eval(source,
+                                            __exprinfo_value=self.result))
+        except passthroughex:
+            raise
+        except:
+            return False
+
+    def eval(self, frame):
+        node = Interpretable(self.node)
+        node.eval(frame)
+        explanations = []
+        vars = {'__exprinfo_fn': node.result}
+        source = '__exprinfo_fn('
+        for a in self.args:
+            if isinstance(a, ast.Keyword):
+                keyword = a.name
+                a = a.expr
+            else:
+                keyword = None
+            a = Interpretable(a)
+            a.eval(frame)
+            argname = '__exprinfo_%d' % len(vars)
+            vars[argname] = a.result
+            if keyword is None:
+                source += argname + ','
+                explanations.append(a.explanation)
+            else:
+                source += '%s=%s,' % (keyword, argname)
+                explanations.append('%s=%s' % (keyword, a.explanation))
+        if self.star_args:
+            star_args = Interpretable(self.star_args)
+            star_args.eval(frame)
+            argname = '__exprinfo_star'
+            vars[argname] = star_args.result
+            source += '*' + argname + ','
+            explanations.append('*' + star_args.explanation)
+        if self.dstar_args:
+            dstar_args = Interpretable(self.dstar_args)
+            dstar_args.eval(frame)
+            argname = '__exprinfo_kwds'
+            vars[argname] = dstar_args.result
+            source += '**' + argname + ','
+            explanations.append('**' + dstar_args.explanation)
+        self.explanation = "%s(%s)" % (
+            node.explanation, ', '.join(explanations))
+        if source.endswith(','):
+            source = source[:-1]
+        source += ')'
+        try:
+            self.result = frame.eval(source, **vars)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+        if not node.is_builtin(frame) or not self.is_bool(frame):
+            r = frame.repr(self.result)
+            self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
+
+class Getattr(Interpretable):
+    __view__ = ast.Getattr
+
+    def eval(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        source = '__exprinfo_expr.%s' % self.attrname
+        try:
+            self.result = frame.eval(source, __exprinfo_expr=expr.result)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+        self.explanation = '%s.%s' % (expr.explanation, self.attrname)
+        # if the attribute comes from the instance, its value is interesting
+        source = ('hasattr(__exprinfo_expr, "__dict__") and '
+                  '%r in __exprinfo_expr.__dict__' % self.attrname)
+        try:
+            from_instance = frame.is_true(
+                frame.eval(source, __exprinfo_expr=expr.result))
+        except passthroughex:
+            raise
+        except:
+            from_instance = True
+        if from_instance:
+            r = frame.repr(self.result)
+            self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
+
+# == Re-interpretation of full statements ==
+
+class Assert(Interpretable):
+    __view__ = ast.Assert
+
+    def run(self, frame):
+        test = Interpretable(self.test)
+        test.eval(frame)
+        # simplify 'assert False where False = ...'
+        if (test.explanation.startswith('False\n{False = ') and
+            test.explanation.endswith('\n}')):
+            test.explanation = test.explanation[15:-2]
+        # print the result as  'assert <explanation>'
+        self.result = test.result
+        self.explanation = 'assert ' + test.explanation
+        if not frame.is_true(test.result):
+            try:
+                raise BuiltinAssertionError
+            except passthroughex:
+                raise
+            except:
+                raise Failure(self)
+
+class Assign(Interpretable):
+    __view__ = ast.Assign
+
+    def run(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        self.result = expr.result
+        self.explanation = '... = ' + expr.explanation
+        # fall-back-run the rest of the assignment
+        ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
+        mod = ast.Module(None, ast.Stmt([ass]))
+        mod.filename = '<run>'
+        co = pycodegen.ModuleCodeGenerator(mod).getCode()
+        try:
+            frame.exec_(co, __exprinfo_expr=expr.result)
+        except passthroughex:
+            raise
+        except:
+            raise Failure(self)
+
+class Discard(Interpretable):
+    __view__ = ast.Discard
+
+    def run(self, frame):
+        expr = Interpretable(self.expr)
+        expr.eval(frame)
+        self.result = expr.result
+        self.explanation = expr.explanation
+
+class Stmt(Interpretable):
+    __view__ = ast.Stmt
+
+    def run(self, frame):
+        for stmt in self.nodes:
+            stmt = Interpretable(stmt)
+            stmt.run(frame)
+
+
+def report_failure(e):
+    explanation = e.node.nice_explanation()
+    if explanation:
+        explanation = ", in: " + explanation
+    else:
+        explanation = ""
+    sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
+
+def check(s, frame=None):
+    if frame is None:
+        frame = sys._getframe(1)
+        frame = py.code.Frame(frame)
+    expr = parse(s, 'eval')
+    assert isinstance(expr, ast.Expression)
+    node = Interpretable(expr.node)
+    try:
+        node.eval(frame)
+    except passthroughex:
+        raise
+    except Failure:
+        e = sys.exc_info()[1]
+        report_failure(e)
+    else:
+        if not frame.is_true(node.result):
+            sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
+
+
+###########################################################
+# API / Entry points
+# #########################################################
+
+def interpret(source, frame, should_fail=False):
+    module = Interpretable(parse(source, 'exec').node)
+    #print "got module", module
+    if isinstance(frame, types.FrameType):
+        frame = py.code.Frame(frame)
+    try:
+        module.run(frame)
+    except Failure:
+        e = sys.exc_info()[1]
+        return getfailure(e)
+    except passthroughex:
+        raise
+    except:
+        import traceback
+        traceback.print_exc()
+    if should_fail:
+        return ("(assertion failed, but when it was re-run for "
+                "printing intermediate values, it did not fail.  Suggestions: "
+                "compute assert expression before the assert or use --nomagic)")
+    else:
+        return None
+
+def getmsg(excinfo):
+    if isinstance(excinfo, tuple):
+        excinfo = py.code.ExceptionInfo(excinfo)
+    #frame, line = gettbline(tb)
+    #frame = py.code.Frame(frame)
+    #return interpret(line, frame)
+
+    tb = excinfo.traceback[-1]
+    source = str(tb.statement).strip()
+    x = interpret(source, tb.frame, should_fail=True)
+    if not isinstance(x, str):
+        raise TypeError("interpret returned non-string %r" % (x,))
+    return x
+
+def getfailure(e):
+    explanation = e.node.nice_explanation()
+    if str(e.value):
+        lines = explanation.split('\n')
+        lines[0] += "  << %s" % (e.value,)
+        explanation = '\n'.join(lines)
+    text = "%s: %s" % (e.exc.__name__, explanation)
+    if text.startswith('AssertionError: assert '):
+        text = text[16:]
+    return text
+
+def run(s, frame=None):
+    if frame is None:
+        frame = sys._getframe(1)
+        frame = py.code.Frame(frame)
+    module = Interpretable(parse(s, 'exec').node)
+    try:
+        module.run(frame)
+    except Failure:
+        e = sys.exc_info()[1]
+        report_failure(e)
+
+
+if __name__ == '__main__':
+    # example:
+    def f():
+        return 5
+    def g():
+        return 3
+    def h(x):
+        return 'never'
+    check("f() * g() == 5")
+    check("not f()")
+    check("not (f() and g() or 0)")
+    check("f() == g()")
+    i = 4
+    check("i == f()")
+    check("len(f()) == 0")
+    check("isinstance(2+3+4, float)")
+
+    run("x = i")
+    check("x == 5")
+
+    run("assert not f(), 'oops'")
+    run("a, b, c = 1, 2")
+    run("a, b, c = f()")
+
+    check("max([f(),g()]) == 4")
+    check("'hello'[g()] == 'h'")
+    run("'guk%d' % h(f())")
diff --git a/tools/py/py/_code/_py2traceback.py b/tools/third_party/py/py/_code/_py2traceback.py
similarity index 100%
rename from tools/py/py/_code/_py2traceback.py
rename to tools/third_party/py/py/_code/_py2traceback.py
diff --git a/tools/third_party/py/py/_code/assertion.py b/tools/third_party/py/py/_code/assertion.py
new file mode 100644
index 0000000..ff16437
--- /dev/null
+++ b/tools/third_party/py/py/_code/assertion.py
@@ -0,0 +1,90 @@
+import sys
+import py
+
+BuiltinAssertionError = py.builtin.builtins.AssertionError
+
+_reprcompare = None # if set, will be called by assert reinterp for comparison ops
+
+def _format_explanation(explanation):
+    """This formats an explanation
+
+    Normally all embedded newlines are escaped, however there are
+    three exceptions: \n{, \n} and \n~.  The first two are intended
+    cover nested explanations, see function and attribute explanations
+    for examples (.visit_Call(), visit_Attribute()).  The last one is
+    for when one explanation needs to span multiple lines, e.g. when
+    displaying diffs.
+    """
+    raw_lines = (explanation or '').split('\n')
+    # escape newlines not followed by {, } and ~
+    lines = [raw_lines[0]]
+    for l in raw_lines[1:]:
+        if l.startswith('{') or l.startswith('}') or l.startswith('~'):
+            lines.append(l)
+        else:
+            lines[-1] += '\\n' + l
+
+    result = lines[:1]
+    stack = [0]
+    stackcnt = [0]
+    for line in lines[1:]:
+        if line.startswith('{'):
+            if stackcnt[-1]:
+                s = 'and   '
+            else:
+                s = 'where '
+            stack.append(len(result))
+            stackcnt[-1] += 1
+            stackcnt.append(0)
+            result.append(' +' + '  '*(len(stack)-1) + s + line[1:])
+        elif line.startswith('}'):
+            assert line.startswith('}')
+            stack.pop()
+            stackcnt.pop()
+            result[stack[-1]] += line[1:]
+        else:
+            assert line.startswith('~')
+            result.append('  '*len(stack) + line[1:])
+    assert len(stack) == 1
+    return '\n'.join(result)
+
+
+class AssertionError(BuiltinAssertionError):
+    def __init__(self, *args):
+        BuiltinAssertionError.__init__(self, *args)
+        if args:
+            try:
+                self.msg = str(args[0])
+            except py.builtin._sysex:
+                raise
+            except:
+                self.msg = "<[broken __repr__] %s at %0xd>" %(
+                    args[0].__class__, id(args[0]))
+        else:
+            f = py.code.Frame(sys._getframe(1))
+            try:
+                source = f.code.fullsource
+                if source is not None:
+                    try:
+                        source = source.getstatement(f.lineno, assertion=True)
+                    except IndexError:
+                        source = None
+                    else:
+                        source = str(source.deindent()).strip()
+            except py.error.ENOENT:
+                source = None
+                # this can also occur during reinterpretation, when the
+                # co_filename is set to "<run>".
+            if source:
+                self.msg = reinterpret(source, f, should_fail=True)
+            else:
+                self.msg = "<could not determine information>"
+            if not self.args:
+                self.args = (self.msg,)
+
+if sys.version_info > (3, 0):
+    AssertionError.__module__ = "builtins"
+    reinterpret_old = "old reinterpretation not available for py3"
+else:
+    from py._code._assertionold import interpret as reinterpret_old
+from py._code._assertionnew import interpret as reinterpret
diff --git a/tools/third_party/py/py/_code/code.py b/tools/third_party/py/py/_code/code.py
new file mode 100644
index 0000000..dad7962
--- /dev/null
+++ b/tools/third_party/py/py/_code/code.py
@@ -0,0 +1,796 @@
+import py
+import sys
+from inspect import CO_VARARGS, CO_VARKEYWORDS, isclass
+
+builtin_repr = repr
+
+reprlib = py.builtin._tryimport('repr', 'reprlib')
+
+if sys.version_info[0] >= 3:
+    from traceback import format_exception_only
+else:
+    from py._code._py2traceback import format_exception_only
+
+import traceback
+
+
+class Code(object):
+    """ wrapper around Python code objects """
+    def __init__(self, rawcode):
+        if not hasattr(rawcode, "co_filename"):
+            rawcode = py.code.getrawcode(rawcode)
+        try:
+            self.filename = rawcode.co_filename
+            self.firstlineno = rawcode.co_firstlineno - 1
+            self.name = rawcode.co_name
+        except AttributeError:
+            raise TypeError("not a code object: %r" % (rawcode,))
+        self.raw = rawcode
+
+    def __eq__(self, other):
+        return self.raw == other.raw
+
+    def __ne__(self, other):
+        return not self == other
+
+    @property
+    def path(self):
+        """ return a path object pointing to source code (note that it
+        might not point to an actually existing file). """
+        p = py.path.local(self.raw.co_filename)
+        # maybe don't try this checking
+        if not p.check():
+            # XXX maybe try harder like the weird logic
+            # in the standard lib [linecache.updatecache] does?
+            p = self.raw.co_filename
+        return p
+
+    @property
+    def fullsource(self):
+        """ return a py.code.Source object for the full source file of the code
+        """
+        from py._code import source
+        full, _ = source.findsource(self.raw)
+        return full
+
+    def source(self):
+        """ return a py.code.Source object for the code object's source only
+        """
+        # return source only for that part of code
+        return py.code.Source(self.raw)
+
+    def getargs(self, var=False):
+        """ return a tuple with the argument names for the code object
+
+            if 'var' is set True also return the names of the variable and
+            keyword arguments when present
+        """
+        # handfull shortcut for getting args
+        raw = self.raw
+        argcount = raw.co_argcount
+        if var:
+            argcount += raw.co_flags & CO_VARARGS
+            argcount += raw.co_flags & CO_VARKEYWORDS
+        return raw.co_varnames[:argcount]
+
+class Frame(object):
+    """Wrapper around a Python frame holding f_locals and f_globals
+    in which expressions can be evaluated."""
+
+    def __init__(self, frame):
+        self.lineno = frame.f_lineno - 1
+        self.f_globals = frame.f_globals
+        self.f_locals = frame.f_locals
+        self.raw = frame
+        self.code = py.code.Code(frame.f_code)
+
+    @property
+    def statement(self):
+        """ statement this frame is at """
+        if self.code.fullsource is None:
+            return py.code.Source("")
+        return self.code.fullsource.getstatement(self.lineno)
+
+    def eval(self, code, **vars):
+        """ evaluate 'code' in the frame
+
+            'vars' are optional additional local variables
+
+            returns the result of the evaluation
+        """
+        f_locals = self.f_locals.copy()
+        f_locals.update(vars)
+        return eval(code, self.f_globals, f_locals)
+
+    def exec_(self, code, **vars):
+        """ exec 'code' in the frame
+
+            'vars' are optiona; additional local variables
+        """
+        f_locals = self.f_locals.copy()
+        f_locals.update(vars)
+        py.builtin.exec_(code, self.f_globals, f_locals)
+
+    def repr(self, object):
+        """ return a 'safe' (non-recursive, one-line) string repr for 'object'
+        """
+        return py.io.saferepr(object)
+
+    def is_true(self, object):
+        return object
+
+    def getargs(self, var=False):
+        """ return a list of tuples (name, value) for all arguments
+
+            if 'var' is set True also include the variable and keyword
+            arguments when present
+        """
+        retval = []
+        for arg in self.code.getargs(var):
+            try:
+                retval.append((arg, self.f_locals[arg]))
+            except KeyError:
+                pass     # this can occur when using Psyco
+        return retval
+
+
+class TracebackEntry(object):
+    """ a single entry in a traceback """
+
+    _repr_style = None
+    exprinfo = None
+
+    def __init__(self, rawentry):
+        self._rawentry = rawentry
+        self.lineno = rawentry.tb_lineno - 1
+
+    def set_repr_style(self, mode):
+        assert mode in ("short", "long")
+        self._repr_style = mode
+
+    @property
+    def frame(self):
+        return py.code.Frame(self._rawentry.tb_frame)
+
+    @property
+    def relline(self):
+        return self.lineno - self.frame.code.firstlineno
+
+    def __repr__(self):
+        return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno+1)
+
+    @property
+    def statement(self):
+        """ py.code.Source object for the current statement """
+        source = self.frame.code.fullsource
+        return source.getstatement(self.lineno)
+
+    @property
+    def path(self):
+        """ path to the source code """
+        return self.frame.code.path
+
+    def getlocals(self):
+        return self.frame.f_locals
+    locals = property(getlocals, None, None, "locals of underlaying frame")
+
+    def reinterpret(self):
+        """Reinterpret the failing statement and returns a detailed information
+           about what operations are performed."""
+        if self.exprinfo is None:
+            source = str(self.statement).strip()
+            x = py.code._reinterpret(source, self.frame, should_fail=True)
+            if not isinstance(x, str):
+                raise TypeError("interpret returned non-string %r" % (x,))
+            self.exprinfo = x
+        return self.exprinfo
+
+    def getfirstlinesource(self):
+        # on Jython this firstlineno can be -1 apparently
+        return max(self.frame.code.firstlineno, 0)
+
+    def getsource(self, astcache=None):
+        """ return failing source code. """
+        # we use the passed in astcache to not reparse asttrees
+        # within exception info printing
+        from py._code.source import getstatementrange_ast
+        source = self.frame.code.fullsource
+        if source is None:
+            return None
+        key = astnode = None
+        if astcache is not None:
+            key = self.frame.code.path
+            if key is not None:
+                astnode = astcache.get(key, None)
+        start = self.getfirstlinesource()
+        try:
+            astnode, _, end = getstatementrange_ast(self.lineno, source,
+                                                    astnode=astnode)
+        except SyntaxError:
+            end = self.lineno + 1
+        else:
+            if key is not None:
+                astcache[key] = astnode
+        return source[start:end]
+
+    source = property(getsource)
+
+    def ishidden(self):
+        """ return True if the current frame has a var __tracebackhide__
+            resolving to True
+
+            mostly for internal use
+        """
+        try:
+            return self.frame.f_locals['__tracebackhide__']
+        except KeyError:
+            try:
+                return self.frame.f_globals['__tracebackhide__']
+            except KeyError:
+                return False
+
+    def __str__(self):
+        try:
+            fn = str(self.path)
+        except py.error.Error:
+            fn = '???'
+        name = self.frame.code.name
+        try:
+            line = str(self.statement).lstrip()
+        except KeyboardInterrupt:
+            raise
+        except:
+            line = "???"
+        return "  File %r:%d in %s\n  %s\n" % (fn, self.lineno+1, name, line)
+
+    def name(self):
+        return self.frame.code.raw.co_name
+    name = property(name, None, None, "co_name of underlaying code")
+
+
+class Traceback(list):
+    """ Traceback objects encapsulate and offer higher level
+        access to Traceback entries.
+    """
+    Entry = TracebackEntry
+
+    def __init__(self, tb):
+        """ initialize from given python traceback object. """
+        if hasattr(tb, 'tb_next'):
+            def f(cur):
+                while cur is not None:
+                    yield self.Entry(cur)
+                    cur = cur.tb_next
+            list.__init__(self, f(tb))
+        else:
+            list.__init__(self, tb)
+
+    def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
+        """ return a Traceback instance wrapping part of this Traceback
+
+            by provding any combination of path, lineno and firstlineno, the
+            first frame to start the to-be-returned traceback is determined
+
+            this allows cutting the first part of a Traceback instance e.g.
+            for formatting reasons (removing some uninteresting bits that deal
+            with handling of the exception/traceback)
+        """
+        for x in self:
+            code = x.frame.code
+            codepath = code.path
+            if ((path is None or codepath == path) and
+                (excludepath is None or not hasattr(codepath, 'relto') or
+                 not codepath.relto(excludepath)) and
+                (lineno is None or x.lineno == lineno) and
+                (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
+                return Traceback(x._rawentry)
+        return self
+
+    def __getitem__(self, key):
+        val = super(Traceback, self).__getitem__(key)
+        if isinstance(key, type(slice(0))):
+            val = self.__class__(val)
+        return val
+
+    def filter(self, fn=lambda x: not x.ishidden()):
+        """ return a Traceback instance with certain items removed
+
+            fn is a function that gets a single argument, a TracebackItem
+            instance, and should return True when the item should be added
+            to the Traceback, False when not
+
+            by default this removes all the TracebackItems which are hidden
+            (see ishidden() above)
+        """
+        return Traceback(filter(fn, self))
+
+    def getcrashentry(self):
+        """ return last non-hidden traceback entry that lead
+        to the exception of a traceback.
+        """
+        for i in range(-1, -len(self)-1, -1):
+            entry = self[i]
+            if not entry.ishidden():
+                return entry
+        return self[-1]
+
+    def recursionindex(self):
+        """ return the index of the frame/TracebackItem where recursion
+            originates if appropriate, None if no recursion occurred
+        """
+        cache = {}
+        for i, entry in enumerate(self):
+            # id for the code.raw is needed to work around
+            # the strange metaprogramming in the decorator lib from pypi
+            # which generates code objects that have hash/value equality
+            #XXX needs a test
+            key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
+            #print "checking for recursion at", key
+            l = cache.setdefault(key, [])
+            if l:
+                f = entry.frame
+                loc = f.f_locals
+                for otherloc in l:
+                    if f.is_true(f.eval(co_equal,
+                        __recursioncache_locals_1=loc,
+                        __recursioncache_locals_2=otherloc)):
+                        return i
+            l.append(entry.frame.f_locals)
+        return None
+
+co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
+                   '?', 'eval')
+
+class ExceptionInfo(object):
+    """ wraps sys.exc_info() objects and offers
+        help for navigating the traceback.
+    """
+    _striptext = ''
+    def __init__(self, tup=None, exprinfo=None):
+        if tup is None:
+            tup = sys.exc_info()
+            if exprinfo is None and isinstance(tup[1], AssertionError):
+                exprinfo = getattr(tup[1], 'msg', None)
+                if exprinfo is None:
+                    exprinfo = str(tup[1])
+                if exprinfo and exprinfo.startswith('assert '):
+                    self._striptext = 'AssertionError: '
+        self._excinfo = tup
+        #: the exception class
+        self.type = tup[0]
+        #: the exception instance
+        self.value = tup[1]
+        #: the exception raw traceback
+        self.tb = tup[2]
+        #: the exception type name
+        self.typename = self.type.__name__
+        #: the exception traceback (py.code.Traceback instance)
+        self.traceback = py.code.Traceback(self.tb)
+
+    def __repr__(self):
+        return "<ExceptionInfo %s tblen=%d>" % (
+            self.typename, len(self.traceback))
+
+    def exconly(self, tryshort=False):
+        """ return the exception as a string
+
+            when 'tryshort' resolves to True, and the exception is a
+            py.code._AssertionError, only the actual exception part of
+            the exception representation is returned (so 'AssertionError: ' is
+            removed from the beginning)
+        """
+        lines = format_exception_only(self.type, self.value)
+        text = ''.join(lines)
+        text = text.rstrip()
+        if tryshort:
+            if text.startswith(self._striptext):
+                text = text[len(self._striptext):]
+        return text
+
+    def errisinstance(self, exc):
+        """ return True if the exception is an instance of exc """
+        return isinstance(self.value, exc)
+
+    def _getreprcrash(self):
+        exconly = self.exconly(tryshort=True)
+        entry = self.traceback.getcrashentry()
+        path, lineno = entry.frame.code.raw.co_filename, entry.lineno
+        return ReprFileLocation(path, lineno+1, exconly)
+
+    def getrepr(self, showlocals=False, style="long",
+                abspath=False, tbfilter=True, funcargs=False):
+        """ return str()able representation of this exception info.
+            showlocals: show locals per traceback entry
+            style: long|short|no|native traceback style
+            tbfilter: hide entries (where __tracebackhide__ is true)
+
+            in case of style==native, tbfilter and showlocals is ignored.
+        """
+        if style == 'native':
+            return ReprExceptionInfo(ReprTracebackNative(
+                traceback.format_exception(
+                    self.type,
+                    self.value,
+                    self.traceback[0]._rawentry,
+                )), self._getreprcrash())
+
+        fmt = FormattedExcinfo(
+            showlocals=showlocals, style=style,
+            abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
+        return fmt.repr_excinfo(self)
+
+    def __str__(self):
+        entry = self.traceback[-1]
+        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+        return str(loc)
+
+    def __unicode__(self):
+        entry = self.traceback[-1]
+        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+        return loc.__unicode__()
+
+
+class FormattedExcinfo(object):
+    """ presenting information about failing Functions and Generators. """
+    # for traceback entries
+    flow_marker = ">"
+    fail_marker = "E"
+
+    def __init__(self, showlocals=False, style="long",
+                 abspath=True, tbfilter=True, funcargs=False):
+        self.showlocals = showlocals
+        self.style = style
+        self.tbfilter = tbfilter
+        self.funcargs = funcargs
+        self.abspath = abspath
+        self.astcache = {}
+
+    def _getindent(self, source):
+        # figure out indent for given source
+        try:
+            s = str(source.getstatement(len(source)-1))
+        except KeyboardInterrupt:
+            raise
+        except:
+            try:
+                s = str(source[-1])
+            except KeyboardInterrupt:
+                raise
+            except:
+                return 0
+        return 4 + (len(s) - len(s.lstrip()))
+
+    def _getentrysource(self, entry):
+        source = entry.getsource(self.astcache)
+        if source is not None:
+            source = source.deindent()
+        return source
+
+    def _saferepr(self, obj):
+        return py.io.saferepr(obj)
+
+    def repr_args(self, entry):
+        if self.funcargs:
+            args = []
+            for argname, argvalue in entry.frame.getargs(var=True):
+                args.append((argname, self._saferepr(argvalue)))
+            return ReprFuncArgs(args)
+
+    def get_source(self, source, line_index=-1, excinfo=None, short=False):
+        """ return formatted and marked up source lines. """
+        lines = []
+        if source is None or line_index >= len(source.lines):
+            source = py.code.Source("???")
+            line_index = 0
+        if line_index < 0:
+            line_index += len(source)
+        space_prefix = "    "
+        if short:
+            lines.append(space_prefix + source.lines[line_index].strip())
+        else:
+            for line in source.lines[:line_index]:
+                lines.append(space_prefix + line)
+            lines.append(self.flow_marker + "   " + source.lines[line_index])
+            for line in source.lines[line_index+1:]:
+                lines.append(space_prefix + line)
+        if excinfo is not None:
+            indent = 4 if short else self._getindent(source)
+            lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
+        return lines
+
+    def get_exconly(self, excinfo, indent=4, markall=False):
+        lines = []
+        indent = " " * indent
+        # get the real exception information out
+        exlines = excinfo.exconly(tryshort=True).split('\n')
+        failindent = self.fail_marker + indent[1:]
+        for line in exlines:
+            lines.append(failindent + line)
+            if not markall:
+                failindent = indent
+        return lines
+
+    def repr_locals(self, locals):
+        if self.showlocals:
+            lines = []
+            keys = [loc for loc in locals if loc[0] != "@"]
+            keys.sort()
+            for name in keys:
+                value = locals[name]
+                if name == '__builtins__':
+                    lines.append("__builtins__ = <builtins>")
+                else:
+                    # This formatting could all be handled by the
+                    # _repr() function, which is only reprlib.Repr in
+                    # disguise, so is very configurable.
+                    str_repr = self._saferepr(value)
+                    #if len(str_repr) < 70 or not isinstance(value,
+                    #                            (list, tuple, dict)):
+                    lines.append("%-10s = %s" %(name, str_repr))
+                    #else:
+                    #    self._line("%-10s =\\" % (name,))
+                    #    # XXX
+                    #    pprint.pprint(value, stream=self.excinfowriter)
+            return ReprLocals(lines)
+
+    def repr_traceback_entry(self, entry, excinfo=None):
+        source = self._getentrysource(entry)
+        if source is None:
+            source = py.code.Source("???")
+            line_index = 0
+        else:
+            # entry.getfirstlinesource() can be -1, should be 0 on jython
+            line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
+
+        lines = []
+        style = entry._repr_style
+        if style is None:
+            style = self.style
+        if style in ("short", "long"):
+            short = style == "short"
+            reprargs = self.repr_args(entry) if not short else None
+            s = self.get_source(source, line_index, excinfo, short=short)
+            lines.extend(s)
+            if short:
+                message = "in %s" %(entry.name)
+            else:
+                message = excinfo and excinfo.typename or ""
+            path = self._makepath(entry.path)
+            filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
+            localsrepr = None
+            if not short:
+                localsrepr =  self.repr_locals(entry.locals)
+            return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
+        if excinfo:
+            lines.extend(self.get_exconly(excinfo, indent=4))
+        return ReprEntry(lines, None, None, None, style)
+
+    def _makepath(self, path):
+        if not self.abspath:
+            try:
+                np = py.path.local().bestrelpath(path)
+            except OSError:
+                return path
+            if len(np) < len(str(path)):
+                path = np
+        return path
+
+    def repr_traceback(self, excinfo):
+        traceback = excinfo.traceback
+        if self.tbfilter:
+            traceback = traceback.filter()
+        recursionindex = None
+        if excinfo.errisinstance(RuntimeError):
+            if "maximum recursion depth exceeded" in str(excinfo.value):
+                recursionindex = traceback.recursionindex()
+        last = traceback[-1]
+        entries = []
+        extraline = None
+        for index, entry in enumerate(traceback):
+            einfo = (last == entry) and excinfo or None
+            reprentry = self.repr_traceback_entry(entry, einfo)
+            entries.append(reprentry)
+            if index == recursionindex:
+                extraline = "!!! Recursion detected (same locals & position)"
+                break
+        return ReprTraceback(entries, extraline, style=self.style)
+
+    def repr_excinfo(self, excinfo):
+        reprtraceback = self.repr_traceback(excinfo)
+        reprcrash = excinfo._getreprcrash()
+        return ReprExceptionInfo(reprtraceback, reprcrash)
+
+class TerminalRepr:
+    def __str__(self):
+        s = self.__unicode__()
+        if sys.version_info[0] < 3:
+            s = s.encode('utf-8')
+        return s
+
+    def __unicode__(self):
+        # FYI this is called from pytest-xdist's serialization of exception
+        # information.
+        io = py.io.TextIO()
+        tw = py.io.TerminalWriter(file=io)
+        self.toterminal(tw)
+        return io.getvalue().strip()
+
+    def __repr__(self):
+        return "<%s instance at %0x>" %(self.__class__, id(self))
+
+
+class ReprExceptionInfo(TerminalRepr):
+    def __init__(self, reprtraceback, reprcrash):
+        self.reprtraceback = reprtraceback
+        self.reprcrash = reprcrash
+        self.sections = []
+
+    def addsection(self, name, content, sep="-"):
+        self.sections.append((name, content, sep))
+
+    def toterminal(self, tw):
+        self.reprtraceback.toterminal(tw)
+        for name, content, sep in self.sections:
+            tw.sep(sep, name)
+            tw.line(content)
+
+class ReprTraceback(TerminalRepr):
+    entrysep = "_ "
+
+    def __init__(self, reprentries, extraline, style):
+        self.reprentries = reprentries
+        self.extraline = extraline
+        self.style = style
+
+    def toterminal(self, tw):
+        # the entries might have different styles
+        last_style = None
+        for i, entry in enumerate(self.reprentries):
+            if entry.style == "long":
+                tw.line("")
+            entry.toterminal(tw)
+            if i < len(self.reprentries) - 1:
+                next_entry = self.reprentries[i+1]
+                if entry.style == "long" or \
+                   entry.style == "short" and next_entry.style == "long":
+                    tw.sep(self.entrysep)
+
+        if self.extraline:
+            tw.line(self.extraline)
+
+class ReprTracebackNative(ReprTraceback):
+    def __init__(self, tblines):
+        self.style = "native"
+        self.reprentries = [ReprEntryNative(tblines)]
+        self.extraline = None
+
+class ReprEntryNative(TerminalRepr):
+    style = "native"
+
+    def __init__(self, tblines):
+        self.lines = tblines
+
+    def toterminal(self, tw):
+        tw.write("".join(self.lines))
+
+class ReprEntry(TerminalRepr):
+    localssep = "_ "
+
+    def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
+        self.lines = lines
+        self.reprfuncargs = reprfuncargs
+        self.reprlocals = reprlocals
+        self.reprfileloc = filelocrepr
+        self.style = style
+
+    def toterminal(self, tw):
+        if self.style == "short":
+            self.reprfileloc.toterminal(tw)
+            for line in self.lines:
+                red = line.startswith("E   ")
+                tw.line(line, bold=True, red=red)
+            #tw.line("")
+            return
+        if self.reprfuncargs:
+            self.reprfuncargs.toterminal(tw)
+        for line in self.lines:
+            red = line.startswith("E   ")
+            tw.line(line, bold=True, red=red)
+        if self.reprlocals:
+            #tw.sep(self.localssep, "Locals")
+            tw.line("")
+            self.reprlocals.toterminal(tw)
+        if self.reprfileloc:
+            if self.lines:
+                tw.line("")
+            self.reprfileloc.toterminal(tw)
+
+    def __str__(self):
+        return "%s\n%s\n%s" % ("\n".join(self.lines),
+                               self.reprlocals,
+                               self.reprfileloc)
+
+class ReprFileLocation(TerminalRepr):
+    def __init__(self, path, lineno, message):
+        self.path = str(path)
+        self.lineno = lineno
+        self.message = message
+
+    def toterminal(self, tw):
+        # filename and lineno output for each entry,
+        # using an output format that most editors unterstand
+        msg = self.message
+        i = msg.find("\n")
+        if i != -1:
+            msg = msg[:i]
+        tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
+
+class ReprLocals(TerminalRepr):
+    def __init__(self, lines):
+        self.lines = lines
+
+    def toterminal(self, tw):
+        for line in self.lines:
+            tw.line(line)
+
+class ReprFuncArgs(TerminalRepr):
+    def __init__(self, args):
+        self.args = args
+
+    def toterminal(self, tw):
+        if self.args:
+            linesofar = ""
+            for name, value in self.args:
+                ns = "%s = %s" %(name, value)
+                if len(ns) + len(linesofar) + 2 > tw.fullwidth:
+                    if linesofar:
+                        tw.line(linesofar)
+                    linesofar =  ns
+                else:
+                    if linesofar:
+                        linesofar += ", " + ns
+                    else:
+                        linesofar = ns
+            if linesofar:
+                tw.line(linesofar)
+            tw.line("")
+
+
+
+oldbuiltins = {}
+
+def patch_builtins(assertion=True, compile=True):
+    """ put compile and AssertionError builtins to Python's builtins. """
+    if assertion:
+        from py._code import assertion
+        l = oldbuiltins.setdefault('AssertionError', [])
+        l.append(py.builtin.builtins.AssertionError)
+        py.builtin.builtins.AssertionError = assertion.AssertionError
+    if compile:
+        l = oldbuiltins.setdefault('compile', [])
+        l.append(py.builtin.builtins.compile)
+        py.builtin.builtins.compile = py.code.compile
+
+def unpatch_builtins(assertion=True, compile=True):
+    """ remove compile and AssertionError builtins from Python builtins. """
+    if assertion:
+        py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
+    if compile:
+        py.builtin.builtins.compile = oldbuiltins['compile'].pop()
+
+def getrawcode(obj, trycall=True):
+    """ return code object for given function. """
+    try:
+        return obj.__code__
+    except AttributeError:
+        obj = getattr(obj, 'im_func', obj)
+        obj = getattr(obj, 'func_code', obj)
+        obj = getattr(obj, 'f_code', obj)
+        obj = getattr(obj, '__code__', obj)
+        if trycall and not hasattr(obj, 'co_firstlineno'):
+            if hasattr(obj, '__call__') and not isclass(obj):
+                x = getrawcode(obj.__call__, trycall=False)
+                if hasattr(x, 'co_firstlineno'):
+                    return x
+        return obj
+
diff --git a/tools/third_party/py/py/_code/source.py b/tools/third_party/py/py/_code/source.py
new file mode 100644
index 0000000..7fc7b23
--- /dev/null
+++ b/tools/third_party/py/py/_code/source.py
@@ -0,0 +1,410 @@
+from __future__ import generators
+
+from bisect import bisect_right
+import sys
+import inspect, tokenize
+import py
+from types import ModuleType
+cpy_compile = compile
+
+try:
+    import _ast
+    from _ast import PyCF_ONLY_AST as _AST_FLAG
+except ImportError:
+    _AST_FLAG = 0
+    _ast = None
+
+
+class Source(object):
+    """ a immutable object holding a source code fragment,
+        possibly deindenting it.
+    """
+    _compilecounter = 0
+    def __init__(self, *parts, **kwargs):
+        self.lines = lines = []
+        de = kwargs.get('deindent', True)
+        rstrip = kwargs.get('rstrip', True)
+        for part in parts:
+            if not part:
+                partlines = []
+            if isinstance(part, Source):
+                partlines = part.lines
+            elif isinstance(part, (tuple, list)):
+                partlines = [x.rstrip("\n") for x in part]
+            elif isinstance(part, py.builtin._basestring):
+                partlines = part.split('\n')
+                if rstrip:
+                    while partlines:
+                        if partlines[-1].strip():
+                            break
+                        partlines.pop()
+            else:
+                partlines = getsource(part, deindent=de).lines
+            if de:
+                partlines = deindent(partlines)
+            lines.extend(partlines)
+
+    def __eq__(self, other):
+        try:
+            return self.lines == other.lines
+        except AttributeError:
+            if isinstance(other, str):
+                return str(self) == other
+            return False
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            return self.lines[key]
+        else:
+            if key.step not in (None, 1):
+                raise IndexError("cannot slice a Source with a step")
+            return self.__getslice__(key.start, key.stop)
+
+    def __len__(self):
+        return len(self.lines)
+
+    def __getslice__(self, start, end):
+        newsource = Source()
+        newsource.lines = self.lines[start:end]
+        return newsource
+
+    def strip(self):
+        """ return new source object with trailing
+            and leading blank lines removed.
+        """
+        start, end = 0, len(self)
+        while start < end and not self.lines[start].strip():
+            start += 1
+        while end > start and not self.lines[end-1].strip():
+            end -= 1
+        source = Source()
+        source.lines[:] = self.lines[start:end]
+        return source
+
+    def putaround(self, before='', after='', indent=' ' * 4):
+        """ return a copy of the source object with
+            'before' and 'after' wrapped around it.
+        """
+        before = Source(before)
+        after = Source(after)
+        newsource = Source()
+        lines = [ (indent + line) for line in self.lines]
+        newsource.lines = before.lines + lines +  after.lines
+        return newsource
+
+    def indent(self, indent=' ' * 4):
+        """ return a copy of the source object with
+            all lines indented by the given indent-string.
+        """
+        newsource = Source()
+        newsource.lines = [(indent+line) for line in self.lines]
+        return newsource
+
+    def getstatement(self, lineno, assertion=False):
+        """ return Source statement which contains the
+            given linenumber (counted from 0).
+        """
+        start, end = self.getstatementrange(lineno, assertion)
+        return self[start:end]
+
+    def getstatementrange(self, lineno, assertion=False):
+        """ return (start, end) tuple which spans the minimal
+            statement region which containing the given lineno.
+        """
+        if not (0 <= lineno < len(self)):
+            raise IndexError("lineno out of range")
+        ast, start, end = getstatementrange_ast(lineno, self)
+        return start, end
+
+    def deindent(self, offset=None):
+        """ return a new source object deindented by offset.
+            If offset is None then guess an indentation offset from
+            the first non-blank line.  Subsequent lines which have a
+            lower indentation offset will be copied verbatim as
+            they are assumed to be part of multilines.
+        """
+        # XXX maybe use the tokenizer to properly handle multiline
+        #     strings etc.pp?
+        newsource = Source()
+        newsource.lines[:] = deindent(self.lines, offset)
+        return newsource
+
+    def isparseable(self, deindent=True):
+        """ return True if source is parseable, heuristically
+            deindenting it by default.
+        """
+        try:
+            import parser
+        except ImportError:
+            syntax_checker = lambda x: compile(x, 'asd', 'exec')
+        else:
+            syntax_checker = parser.suite
+
+        if deindent:
+            source = str(self.deindent())
+        else:
+            source = str(self)
+        try:
+            #compile(source+'\n', "x", "exec")
+            syntax_checker(source+'\n')
+        except KeyboardInterrupt:
+            raise
+        except Exception:
+            return False
+        else:
+            return True
+
+    def __str__(self):
+        return "\n".join(self.lines)
+
+    def compile(self, filename=None, mode='exec',
+                flag=generators.compiler_flag,
+                dont_inherit=0, _genframe=None):
+        """ return compiled code object. if filename is None
+            invent an artificial filename which displays
+            the source/line position of the caller frame.
+        """
+        if not filename or py.path.local(filename).check(file=0):
+            if _genframe is None:
+                _genframe = sys._getframe(1) # the caller
+            fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
+            base = "<%d-codegen " % self._compilecounter
+            self.__class__._compilecounter += 1
+            if not filename:
+                filename = base + '%s:%d>' % (fn, lineno)
+            else:
+                filename = base + '%r %s:%d>' % (filename, fn, lineno)
+        source = "\n".join(self.lines) + '\n'
+        try:
+            co = cpy_compile(source, filename, mode, flag)
+        except SyntaxError:
+            ex = sys.exc_info()[1]
+            # re-represent syntax errors from parsing python strings
+            msglines = self.lines[:ex.lineno]
+            if ex.offset:
+                msglines.append(" "*ex.offset + '^')
+            msglines.append("(code was compiled probably from here: %s)" % filename)
+            newex = SyntaxError('\n'.join(msglines))
+            newex.offset = ex.offset
+            newex.lineno = ex.lineno
+            newex.text = ex.text
+            raise newex
+        else:
+            if flag & _AST_FLAG:
+                return co
+            lines = [(x + "\n") for x in self.lines]
+            import linecache
+            linecache.cache[filename] = (1, None, lines, filename)
+            return co
+
+#
+# public API shortcut functions
+#
+
+def compile_(source, filename=None, mode='exec', flags=
+            generators.compiler_flag, dont_inherit=0):
+    """ compile the given source to a raw code object,
+        and maintain an internal cache which allows later
+        retrieval of the source code for the code object
+        and any recursively created code objects.
+    """
+    if _ast is not None and isinstance(source, _ast.AST):
+        # XXX should Source support having AST?
+        return cpy_compile(source, filename, mode, flags, dont_inherit)
+    _genframe = sys._getframe(1) # the caller
+    s = Source(source)
+    co = s.compile(filename, mode, flags, _genframe=_genframe)
+    return co
+
+
+def getfslineno(obj):
+    """ Return source location (path, lineno) for the given object.
+    If the source cannot be determined return ("", -1)
+    """
+    try:
+        code = py.code.Code(obj)
+    except TypeError:
+        try:
+            fn = (inspect.getsourcefile(obj) or
+                  inspect.getfile(obj))
+        except TypeError:
+            return "", -1
+
+        fspath = fn and py.path.local(fn) or None
+        lineno = -1
+        if fspath:
+            try:
+                _, lineno = findsource(obj)
+            except IOError:
+                pass
+    else:
+        fspath = code.path
+        lineno = code.firstlineno
+    assert isinstance(lineno, int)
+    return fspath, lineno
+
+#
+# helper functions
+#
+
+def findsource(obj):
+    try:
+        sourcelines, lineno = inspect.findsource(obj)
+    except py.builtin._sysex:
+        raise
+    except:
+        return None, -1
+    source = Source()
+    source.lines = [line.rstrip() for line in sourcelines]
+    return source, lineno
+
+def getsource(obj, **kwargs):
+    obj = py.code.getrawcode(obj)
+    try:
+        strsrc = inspect.getsource(obj)
+    except IndentationError:
+        strsrc = "\"Buggy python version consider upgrading, cannot get source\""
+    assert isinstance(strsrc, str)
+    return Source(strsrc, **kwargs)
+
+def deindent(lines, offset=None):
+    if offset is None:
+        for line in lines:
+            line = line.expandtabs()
+            s = line.lstrip()
+            if s:
+                offset = len(line)-len(s)
+                break
+        else:
+            offset = 0
+    if offset == 0:
+        return list(lines)
+    newlines = []
+    def readline_generator(lines):
+        for line in lines:
+            yield line + '\n'
+        while True:
+            yield ''
+
+    it = readline_generator(lines)
+
+    try:
+        for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
+            if sline > len(lines):
+                break # End of input reached
+            if sline > len(newlines):
+                line = lines[sline - 1].expandtabs()
+                if line.lstrip() and line[:offset].isspace():
+                    line = line[offset:] # Deindent
+                newlines.append(line)
+
+            for i in range(sline, eline):
+                # Don't deindent continuing lines of
+                # multiline tokens (i.e. multiline strings)
+                newlines.append(lines[i])
+    except (IndentationError, tokenize.TokenError):
+        pass
+    # Add any lines we didn't see. E.g. if an exception was raised.
+    newlines.extend(lines[len(newlines):])
+    return newlines
+
+
+def get_statement_startend2(lineno, node):
+    import ast
+    # flatten all statements and except handlers into one lineno-list
+    # AST's line numbers start indexing at 1
+    l = []
+    for x in ast.walk(node):
+        if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
+            l.append(x.lineno - 1)
+            for name in "finalbody", "orelse":
+                val = getattr(x, name, None)
+                if val:
+                    # treat the finally/orelse part as its own statement
+                    l.append(val[0].lineno - 1 - 1)
+    l.sort()
+    insert_index = bisect_right(l, lineno)
+    start = l[insert_index - 1]
+    if insert_index >= len(l):
+        end = None
+    else:
+        end = l[insert_index]
+    return start, end
+
+
+def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
+    if astnode is None:
+        content = str(source)
+        try:
+            astnode = compile(content, "source", "exec", 1024)  # 1024 for AST
+        except ValueError:
+            start, end = getstatementrange_old(lineno, source, assertion)
+            return None, start, end
+    start, end = get_statement_startend2(lineno, astnode)
+    # we need to correct the end:
+    # - ast-parsing strips comments
+    # - there might be empty lines
+    # - we might have lesser indented code blocks at the end
+    if end is None:
+        end = len(source.lines)
+
+    if end > start + 1:
+        # make sure we don't span differently indented code blocks
+        # by using the BlockFinder helper used which inspect.getsource() uses itself
+        block_finder = inspect.BlockFinder()
+        # if we start with an indented line, put blockfinder to "started" mode
+        block_finder.started = source.lines[start][0].isspace()
+        it = ((x + "\n") for x in source.lines[start:end])
+        try:
+            for tok in tokenize.generate_tokens(lambda: next(it)):
+                block_finder.tokeneater(*tok)
+        except (inspect.EndOfBlock, IndentationError):
+            end = block_finder.last + start
+        except Exception:
+            pass
+
+    # the end might still point to a comment or empty line, correct it
+    while end:
+        line = source.lines[end - 1].lstrip()
+        if line.startswith("#") or not line:
+            end -= 1
+        else:
+            break
+    return astnode, start, end
+
+
+def getstatementrange_old(lineno, source, assertion=False):
+    """ return (start, end) tuple which spans the minimal
+        statement region which containing the given lineno.
+        raise an IndexError if no such statementrange can be found.
+    """
+    # XXX this logic is only used on python2.4 and below
+    # 1. find the start of the statement
+    from codeop import compile_command
+    for start in range(lineno, -1, -1):
+        if assertion:
+            line = source.lines[start]
+            # the following lines are not fully tested, change with care
+            if 'super' in line and 'self' in line and '__init__' in line:
+                raise IndexError("likely a subclass")
+            if "assert" not in line and "raise" not in line:
+                continue
+        trylines = source.lines[start:lineno+1]
+        # quick hack to prepare parsing an indented line with
+        # compile_command() (which errors on "return" outside defs)
+        trylines.insert(0, 'def xxx():')
+        trysource = '\n '.join(trylines)
+        #              ^ space here
+        try:
+            compile_command(trysource)
+        except (SyntaxError, OverflowError, ValueError):
+            continue
+
+        # 2. find the end of the statement
+        for end in range(lineno+1, len(source)+1):
+            trysource = source[start:end]
+            if trysource.isparseable():
+                return start, end
+    raise SyntaxError("no valid source range around line %d " % (lineno,))
+
+
diff --git a/tools/third_party/py/py/_error.py b/tools/third_party/py/py/_error.py
new file mode 100644
index 0000000..a6375de
--- /dev/null
+++ b/tools/third_party/py/py/_error.py
@@ -0,0 +1,91 @@
+"""
+create errno-specific classes for IO or os calls.
+
+"""
+from types import ModuleType
+import sys, os, errno
+
+class Error(EnvironmentError):
+    def __repr__(self):
+        return "%s.%s %r: %s " %(self.__class__.__module__,
+                               self.__class__.__name__,
+                               self.__class__.__doc__,
+                               " ".join(map(str, self.args)),
+                               #repr(self.args)
+                                )
+
+    def __str__(self):
+        s = "[%s]: %s" %(self.__class__.__doc__,
+                          " ".join(map(str, self.args)),
+                          )
+        return s
+
+_winerrnomap = {
+    2: errno.ENOENT,
+    3: errno.ENOENT,
+    17: errno.EEXIST,
+    18: errno.EXDEV,
+    13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable
+    22: errno.ENOTDIR,
+    20: errno.ENOTDIR,
+    267: errno.ENOTDIR,
+    5: errno.EACCES,  # anything better?
+}
+
+class ErrorMaker(ModuleType):
+    """ lazily provides Exception classes for each possible POSIX errno
+        (as defined per the 'errno' module).  All such instances
+        subclass EnvironmentError.
+    """
+    Error = Error
+    _errno2class = {}
+
+    def __getattr__(self, name):
+        if name[0] == "_":
+            raise AttributeError(name)
+        eno = getattr(errno, name)
+        cls = self._geterrnoclass(eno)
+        setattr(self, name, cls)
+        return cls
+
+    def _geterrnoclass(self, eno):
+        try:
+            return self._errno2class[eno]
+        except KeyError:
+            clsname = errno.errorcode.get(eno, "UnknownErrno%d" %(eno,))
+            errorcls = type(Error)(clsname, (Error,),
+                    {'__module__':'py.error',
+                     '__doc__': os.strerror(eno)})
+            self._errno2class[eno] = errorcls
+            return errorcls
+
+    def checked_call(self, func, *args, **kwargs):
+        """ call a function and raise an errno-exception if applicable. """
+        __tracebackhide__ = True
+        try:
+            return func(*args, **kwargs)
+        except self.Error:
+            raise
+        except (OSError, EnvironmentError):
+            cls, value, tb = sys.exc_info()
+            if not hasattr(value, 'errno'):
+                raise
+            __tracebackhide__ = False
+            errno = value.errno
+            try:
+                if not isinstance(value, WindowsError):
+                    raise NameError
+            except NameError:
+                # we are not on Windows, or we got a proper OSError
+                cls = self._geterrnoclass(errno)
+            else:
+                try:
+                    cls = self._geterrnoclass(_winerrnomap[errno])
+                except KeyError:
+                    raise value
+            raise cls("%s%r" % (func.__name__, args))
+            __tracebackhide__ = True
+            
+
+error = ErrorMaker('py.error')
+sys.modules[error.__name__] = error
\ No newline at end of file
diff --git a/tools/py/py/_io/__init__.py b/tools/third_party/py/py/_io/__init__.py
similarity index 100%
rename from tools/py/py/_io/__init__.py
rename to tools/third_party/py/py/_io/__init__.py
diff --git a/tools/py/py/_io/capture.py b/tools/third_party/py/py/_io/capture.py
similarity index 100%
rename from tools/py/py/_io/capture.py
rename to tools/third_party/py/py/_io/capture.py
diff --git a/tools/py/py/_io/saferepr.py b/tools/third_party/py/py/_io/saferepr.py
similarity index 100%
rename from tools/py/py/_io/saferepr.py
rename to tools/third_party/py/py/_io/saferepr.py
diff --git a/tools/third_party/py/py/_io/terminalwriter.py b/tools/third_party/py/py/_io/terminalwriter.py
new file mode 100644
index 0000000..74d3125
--- /dev/null
+++ b/tools/third_party/py/py/_io/terminalwriter.py
@@ -0,0 +1,384 @@
+"""
+
+Helper functions for writing to terminals and files.
+
+"""
+
+
+import sys, os
+import py
+py3k = sys.version_info[0] >= 3
+from py.builtin import text, bytes
+
+win32_and_ctypes = False
+colorama = None
+if sys.platform == "win32":
+    try:
+        import colorama
+    except ImportError:
+        try:
+            import ctypes
+            win32_and_ctypes = True
+        except ImportError:
+            pass
+
+
+def _getdimensions():
+    import termios,fcntl,struct
+    call = fcntl.ioctl(1,termios.TIOCGWINSZ,"\000"*8)
+    height,width = struct.unpack( "hhhh", call ) [:2]
+    return height, width
+
+
+def get_terminal_width():
+    width = 0
+    try:
+        _, width = _getdimensions()
+    except py.builtin._sysex:
+        raise
+    except:
+        # pass to fallback below
+        pass
+
+    if width == 0:
+        # FALLBACK:
+        # * some exception happened
+        # * or this is emacs terminal which reports (0,0)
+        width = int(os.environ.get('COLUMNS', 80))
+
+    # XXX the windows getdimensions may be bogus, let's sanify a bit
+    if width < 40:
+        width = 80
+    return width
+
+terminal_width = get_terminal_width()
+
+# XXX unify with _escaped func below
+def ansi_print(text, esc, file=None, newline=True, flush=False):
+    if file is None:
+        file = sys.stderr
+    text = text.rstrip()
+    if esc and not isinstance(esc, tuple):
+        esc = (esc,)
+    if esc and sys.platform != "win32" and file.isatty():
+        text = (''.join(['\x1b[%sm' % cod for cod in esc])  +
+                text +
+                '\x1b[0m')     # ANSI color code "reset"
+    if newline:
+        text += '\n'
+
+    if esc and win32_and_ctypes and file.isatty():
+        if 1 in esc:
+            bold = True
+            esc = tuple([x for x in esc if x != 1])
+        else:
+            bold = False
+        esctable = {()   : FOREGROUND_WHITE,                 # normal
+                    (31,): FOREGROUND_RED,                   # red
+                    (32,): FOREGROUND_GREEN,                 # green
+                    (33,): FOREGROUND_GREEN|FOREGROUND_RED,  # yellow
+                    (34,): FOREGROUND_BLUE,                  # blue
+                    (35,): FOREGROUND_BLUE|FOREGROUND_RED,   # purple
+                    (36,): FOREGROUND_BLUE|FOREGROUND_GREEN, # cyan
+                    (37,): FOREGROUND_WHITE,                 # white
+                    (39,): FOREGROUND_WHITE,                 # reset
+                    }
+        attr = esctable.get(esc, FOREGROUND_WHITE)
+        if bold:
+            attr |= FOREGROUND_INTENSITY
+        STD_OUTPUT_HANDLE = -11
+        STD_ERROR_HANDLE = -12
+        if file is sys.stderr:
+            handle = GetStdHandle(STD_ERROR_HANDLE)
+        else:
+            handle = GetStdHandle(STD_OUTPUT_HANDLE)
+        oldcolors = GetConsoleInfo(handle).wAttributes
+        attr |= (oldcolors & 0x0f0)
+        SetConsoleTextAttribute(handle, attr)
+        while len(text) > 32768:
+            file.write(text[:32768])
+            text = text[32768:]
+        if text:
+            file.write(text)
+        SetConsoleTextAttribute(handle, oldcolors)
+    else:
+        file.write(text)
+
+    if flush:
+        file.flush()
+
+def should_do_markup(file):
+    if os.environ.get('PY_COLORS') == '1':
+        return True
+    if os.environ.get('PY_COLORS') == '0':
+        return False
+    return hasattr(file, 'isatty') and file.isatty() \
+           and os.environ.get('TERM') != 'dumb' \
+           and not (sys.platform.startswith('java') and os._name == 'nt')
+
+class TerminalWriter(object):
+    _esctable = dict(black=30, red=31, green=32, yellow=33,
+                     blue=34, purple=35, cyan=36, white=37,
+                     Black=40, Red=41, Green=42, Yellow=43,
+                     Blue=44, Purple=45, Cyan=46, White=47,
+                     bold=1, light=2, blink=5, invert=7)
+
+    # XXX deprecate stringio argument
+    def __init__(self, file=None, stringio=False, encoding=None):
+        if file is None:
+            if stringio:
+                self.stringio = file = py.io.TextIO()
+            else:
+                from sys import stdout as file
+        elif py.builtin.callable(file) and not (
+             hasattr(file, "write") and hasattr(file, "flush")):
+            file = WriteFile(file, encoding=encoding)
+        if hasattr(file, "isatty") and file.isatty() and colorama:
+            file = colorama.AnsiToWin32(file).stream
+        self.encoding = encoding or getattr(file, 'encoding', "utf-8")
+        self._file = file
+        self.hasmarkup = should_do_markup(file)
+        self._lastlen = 0
+        self._chars_on_current_line = 0
+
+    @property
+    def fullwidth(self):
+        if hasattr(self, '_terminal_width'):
+            return self._terminal_width
+        return get_terminal_width()
+
+    @fullwidth.setter
+    def fullwidth(self, value):
+        self._terminal_width = value
+
+    @property
+    def chars_on_current_line(self):
+        """Return the number of characters written so far in the current line.
+
+        Please note that this count does not produce correct results after a reline() call,
+        see #164.
+
+        .. versionadded:: 1.5.0
+
+        :rtype: int
+        """
+        return self._chars_on_current_line
+
+    def _escaped(self, text, esc):
+        if esc and self.hasmarkup:
+            text = (''.join(['\x1b[%sm' % cod for cod in esc])  +
+                text +'\x1b[0m')
+        return text
+
+    def markup(self, text, **kw):
+        esc = []
+        for name in kw:
+            if name not in self._esctable:
+                raise ValueError("unknown markup: %r" %(name,))
+            if kw[name]:
+                esc.append(self._esctable[name])
+        return self._escaped(text, tuple(esc))
+
+    def sep(self, sepchar, title=None, fullwidth=None, **kw):
+        if fullwidth is None:
+            fullwidth = self.fullwidth
+        # the goal is to have the line be as long as possible
+        # under the condition that len(line) <= fullwidth
+        if sys.platform == "win32":
+            # if we print in the last column on windows we are on a
+            # new line but there is no way to verify/neutralize this
+            # (we may not know the exact line width)
+            # so let's be defensive to avoid empty lines in the output
+            fullwidth -= 1
+        if title is not None:
+            # we want 2 + 2*len(fill) + len(title) <= fullwidth
+            # i.e.    2 + 2*len(sepchar)*N + len(title) <= fullwidth
+            #         2*len(sepchar)*N <= fullwidth - len(title) - 2
+            #         N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
+            N = (fullwidth - len(title) - 2) // (2*len(sepchar))
+            fill = sepchar * N
+            line = "%s %s %s" % (fill, title, fill)
+        else:
+            # we want len(sepchar)*N <= fullwidth
+            # i.e.    N <= fullwidth // len(sepchar)
+            line = sepchar * (fullwidth // len(sepchar))
+        # in some situations there is room for an extra sepchar at the right,
+        # in particular if we consider that with a sepchar like "_ " the
+        # trailing space is not important at the end of the line
+        if len(line) + len(sepchar.rstrip()) <= fullwidth:
+            line += sepchar.rstrip()
+
+        self.line(line, **kw)
+
+    def write(self, msg, **kw):
+        if msg:
+            if not isinstance(msg, (bytes, text)):
+                msg = text(msg)
+
+            self._update_chars_on_current_line(msg)
+
+            if self.hasmarkup and kw:
+                markupmsg = self.markup(msg, **kw)
+            else:
+                markupmsg = msg
+            write_out(self._file, markupmsg)
+
+    def _update_chars_on_current_line(self, text):
+        fields = text.rsplit('\n', 1)
+        if '\n' in text:
+            self._chars_on_current_line = len(fields[-1])
+        else:
+            self._chars_on_current_line += len(fields[-1])
+
+    def line(self, s='', **kw):
+        self.write(s, **kw)
+        self._checkfill(s)
+        self.write('\n')
+
+    def reline(self, line, **kw):
+        if not self.hasmarkup:
+            raise ValueError("cannot use rewrite-line without terminal")
+        self.write(line, **kw)
+        self._checkfill(line)
+        self.write('\r')
+        self._lastlen = len(line)
+
+    def _checkfill(self, line):
+        diff2last = self._lastlen - len(line)
+        if diff2last > 0:
+            self.write(" " * diff2last)
+
+class Win32ConsoleWriter(TerminalWriter):
+    def write(self, msg, **kw):
+        if msg:
+            if not isinstance(msg, (bytes, text)):
+                msg = text(msg)
+
+            self._update_chars_on_current_line(msg)
+
+            oldcolors = None
+            if self.hasmarkup and kw:
+                handle = GetStdHandle(STD_OUTPUT_HANDLE)
+                oldcolors = GetConsoleInfo(handle).wAttributes
+                default_bg = oldcolors & 0x00F0
+                attr = default_bg
+                if kw.pop('bold', False):
+                    attr |= FOREGROUND_INTENSITY
+
+                if kw.pop('red', False):
+                    attr |= FOREGROUND_RED
+                elif kw.pop('blue', False):
+                    attr |= FOREGROUND_BLUE
+                elif kw.pop('green', False):
+                    attr |= FOREGROUND_GREEN
+                elif kw.pop('yellow', False):
+                    attr |= FOREGROUND_GREEN|FOREGROUND_RED
+                else:
+                    attr |= oldcolors & 0x0007
+
+                SetConsoleTextAttribute(handle, attr)
+            write_out(self._file, msg)
+            if oldcolors:
+                SetConsoleTextAttribute(handle, oldcolors)
+
+class WriteFile(object):
+    def __init__(self, writemethod, encoding=None):
+        self.encoding = encoding
+        self._writemethod = writemethod
+
+    def write(self, data):
+        if self.encoding:
+            data = data.encode(self.encoding, "replace")
+        self._writemethod(data)
+
+    def flush(self):
+        return
+
+
+if win32_and_ctypes:
+    TerminalWriter = Win32ConsoleWriter
+    import ctypes
+    from ctypes import wintypes
+
+    # ctypes access to the Windows console
+    STD_OUTPUT_HANDLE = -11
+    STD_ERROR_HANDLE  = -12
+    FOREGROUND_BLACK     = 0x0000 # black text
+    FOREGROUND_BLUE      = 0x0001 # text color contains blue.
+    FOREGROUND_GREEN     = 0x0002 # text color contains green.
+    FOREGROUND_RED       = 0x0004 # text color contains red.
+    FOREGROUND_WHITE     = 0x0007
+    FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
+    BACKGROUND_BLACK     = 0x0000 # background color black
+    BACKGROUND_BLUE      = 0x0010 # background color contains blue.
+    BACKGROUND_GREEN     = 0x0020 # background color contains green.
+    BACKGROUND_RED       = 0x0040 # background color contains red.
+    BACKGROUND_WHITE     = 0x0070
+    BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
+
+    SHORT = ctypes.c_short
+    class COORD(ctypes.Structure):
+        _fields_ = [('X', SHORT),
+                    ('Y', SHORT)]
+    class SMALL_RECT(ctypes.Structure):
+        _fields_ = [('Left', SHORT),
+                    ('Top', SHORT),
+                    ('Right', SHORT),
+                    ('Bottom', SHORT)]
+    class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
+        _fields_ = [('dwSize', COORD),
+                    ('dwCursorPosition', COORD),
+                    ('wAttributes', wintypes.WORD),
+                    ('srWindow', SMALL_RECT),
+                    ('dwMaximumWindowSize', COORD)]
+
+    _GetStdHandle = ctypes.windll.kernel32.GetStdHandle
+    _GetStdHandle.argtypes = [wintypes.DWORD]
+    _GetStdHandle.restype = wintypes.HANDLE
+    def GetStdHandle(kind):
+        return _GetStdHandle(kind)
+
+    SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute
+    SetConsoleTextAttribute.argtypes = [wintypes.HANDLE, wintypes.WORD]
+    SetConsoleTextAttribute.restype = wintypes.BOOL
+
+    _GetConsoleScreenBufferInfo = \
+        ctypes.windll.kernel32.GetConsoleScreenBufferInfo
+    _GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE,
+                                ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO)]
+    _GetConsoleScreenBufferInfo.restype = wintypes.BOOL
+    def GetConsoleInfo(handle):
+        info = CONSOLE_SCREEN_BUFFER_INFO()
+        _GetConsoleScreenBufferInfo(handle, ctypes.byref(info))
+        return info
+
+    def _getdimensions():
+        handle = GetStdHandle(STD_OUTPUT_HANDLE)
+        info = GetConsoleInfo(handle)
+        # Substract one from the width, otherwise the cursor wraps
+        # and the ending \n causes an empty line to display.
+        return info.dwSize.Y, info.dwSize.X - 1
+
+def write_out(fil, msg):
+    # XXX sometimes "msg" is of type bytes, sometimes text which
+    # complicates the situation.  Should we try to enforce unicode?
+    try:
+        # on py27 and above writing out to sys.stdout with an encoding
+        # should usually work for unicode messages (if the encoding is
+        # capable of it)
+        fil.write(msg)
+    except UnicodeEncodeError:
+        # on py26 it might not work because stdout expects bytes
+        if fil.encoding:
+            try:
+                fil.write(msg.encode(fil.encoding))
+            except UnicodeEncodeError:
+                # it might still fail if the encoding is not capable
+                pass
+            else:
+                fil.flush()
+                return
+        # fallback: escape all unicode characters
+        msg = msg.encode("unicode-escape").decode("ascii")
+        fil.write(msg)
+    fil.flush()
diff --git a/tools/py/py/_log/__init__.py b/tools/third_party/py/py/_log/__init__.py
similarity index 100%
rename from tools/py/py/_log/__init__.py
rename to tools/third_party/py/py/_log/__init__.py
diff --git a/tools/third_party/py/py/_log/log.py b/tools/third_party/py/py/_log/log.py
new file mode 100644
index 0000000..56969bc
--- /dev/null
+++ b/tools/third_party/py/py/_log/log.py
@@ -0,0 +1,206 @@
+"""
+basic logging functionality based on a producer/consumer scheme.
+
+XXX implement this API: (maybe put it into slogger.py?)
+
+        log = Logger(
+                    info=py.log.STDOUT,
+                    debug=py.log.STDOUT,
+                    command=None)
+        log.info("hello", "world")
+        log.command("hello", "world")
+
+        log = Logger(info=Logger(something=...),
+                     debug=py.log.STDOUT,
+                     command=None)
+"""
+import py
+import sys
+
+
+class Message(object):
+    def __init__(self, keywords, args):
+        self.keywords = keywords
+        self.args = args
+
+    def content(self):
+        return " ".join(map(str, self.args))
+
+    def prefix(self):
+        return "[%s] " % (":".join(self.keywords))
+
+    def __str__(self):
+        return self.prefix() + self.content()
+
+
+class Producer(object):
+    """ (deprecated) Log producer API which sends messages to be logged
+        to a 'consumer' object, which then prints them to stdout,
+        stderr, files, etc. Used extensively by PyPy-1.1.
+    """
+
+    Message = Message  # to allow later customization
+    keywords2consumer = {}
+
+    def __init__(self, keywords, keywordmapper=None, **kw):
+        if hasattr(keywords, 'split'):
+            keywords = tuple(keywords.split())
+        self._keywords = keywords
+        if keywordmapper is None:
+            keywordmapper = default_keywordmapper
+        self._keywordmapper = keywordmapper
+
+    def __repr__(self):
+        return "<py.log.Producer %s>" % ":".join(self._keywords)
+
+    def __getattr__(self, name):
+        if '_' in name:
+            raise AttributeError(name)
+        producer = self.__class__(self._keywords + (name,))
+        setattr(self, name, producer)
+        return producer
+
+    def __call__(self, *args):
+        """ write a message to the appropriate consumer(s) """
+        func = self._keywordmapper.getconsumer(self._keywords)
+        if func is not None:
+            func(self.Message(self._keywords, args))
+
+class KeywordMapper:
+    def __init__(self):
+        self.keywords2consumer = {}
+
+    def getstate(self):
+        return self.keywords2consumer.copy()
+
+    def setstate(self, state):
+        self.keywords2consumer.clear()
+        self.keywords2consumer.update(state)
+
+    def getconsumer(self, keywords):
+        """ return a consumer matching the given keywords.
+
+            tries to find the most suitable consumer by walking, starting from
+            the back, the list of keywords, the first consumer matching a
+            keyword is returned (falling back to py.log.default)
+        """
+        for i in range(len(keywords), 0, -1):
+            try:
+                return self.keywords2consumer[keywords[:i]]
+            except KeyError:
+                continue
+        return self.keywords2consumer.get('default', default_consumer)
+
+    def setconsumer(self, keywords, consumer):
+        """ set a consumer for a set of keywords. """
+        # normalize to tuples
+        if isinstance(keywords, str):
+            keywords = tuple(filter(None, keywords.split()))
+        elif hasattr(keywords, '_keywords'):
+            keywords = keywords._keywords
+        elif not isinstance(keywords, tuple):
+            raise TypeError("key %r is not a string or tuple" % (keywords,))
+        if consumer is not None and not py.builtin.callable(consumer):
+            if not hasattr(consumer, 'write'):
+                raise TypeError(
+                    "%r should be None, callable or file-like" % (consumer,))
+            consumer = File(consumer)
+        self.keywords2consumer[keywords] = consumer
+
+
+def default_consumer(msg):
+    """ the default consumer, prints the message to stdout (using 'print') """
+    sys.stderr.write(str(msg)+"\n")
+
+default_keywordmapper = KeywordMapper()
+
+
+def setconsumer(keywords, consumer):
+    default_keywordmapper.setconsumer(keywords, consumer)
+
+
+def setstate(state):
+    default_keywordmapper.setstate(state)
+
+
+def getstate():
+    return default_keywordmapper.getstate()
+
+#
+# Consumers
+#
+
+
+class File(object):
+    """ log consumer wrapping a file(-like) object """
+    def __init__(self, f):
+        assert hasattr(f, 'write')
+        # assert isinstance(f, file) or not hasattr(f, 'open')
+        self._file = f
+
+    def __call__(self, msg):
+        """ write a message to the log """
+        self._file.write(str(msg) + "\n")
+        if hasattr(self._file, 'flush'):
+            self._file.flush()
+
+
+class Path(object):
+    """ log consumer that opens and writes to a Path """
+    def __init__(self, filename, append=False,
+                 delayed_create=False, buffering=False):
+        self._append = append
+        self._filename = str(filename)
+        self._buffering = buffering
+        if not delayed_create:
+            self._openfile()
+
+    def _openfile(self):
+        mode = self._append and 'a' or 'w'
+        f = open(self._filename, mode)
+        self._file = f
+
+    def __call__(self, msg):
+        """ write a message to the log """
+        if not hasattr(self, "_file"):
+            self._openfile()
+        self._file.write(str(msg) + "\n")
+        if not self._buffering:
+            self._file.flush()
+
+
+def STDOUT(msg):
+    """ consumer that writes to sys.stdout """
+    sys.stdout.write(str(msg)+"\n")
+
+
+def STDERR(msg):
+    """ consumer that writes to sys.stderr """
+    sys.stderr.write(str(msg)+"\n")
+
+
+class Syslog:
+    """ consumer that writes to the syslog daemon """
+
+    def __init__(self, priority=None):
+        if priority is None:
+            priority = self.LOG_INFO
+        self.priority = priority
+
+    def __call__(self, msg):
+        """ write a message to the log """
+        import syslog
+        syslog.syslog(self.priority, str(msg))
+
+
+try:
+    import syslog
+except ImportError:
+    pass
+else:
+    for _prio in "EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG".split():
+        _prio = "LOG_" + _prio
+        try:
+            setattr(Syslog, _prio, getattr(syslog, _prio))
+        except AttributeError:
+            pass
diff --git a/tools/third_party/py/py/_log/warning.py b/tools/third_party/py/py/_log/warning.py
new file mode 100644
index 0000000..6ef20d9
--- /dev/null
+++ b/tools/third_party/py/py/_log/warning.py
@@ -0,0 +1,79 @@
+import py, sys
+
+class DeprecationWarning(DeprecationWarning):
+    def __init__(self, msg, path, lineno):
+        self.msg = msg
+        self.path = path
+        self.lineno = lineno
+    def __repr__(self):
+        return "%s:%d: %s" %(self.path, self.lineno+1, self.msg)
+    def __str__(self):
+        return self.msg
+
+def _apiwarn(startversion, msg, stacklevel=2, function=None):
+    # below is mostly COPIED from python2.4/warnings.py's def warn()
+    # Get context information
+    if isinstance(stacklevel, str):
+        frame = sys._getframe(1)
+        level = 1
+        found = frame.f_code.co_filename.find(stacklevel) != -1
+        while frame:
+            co = frame.f_code
+            if co.co_filename.find(stacklevel) == -1:
+                if found:
+                    stacklevel = level
+                    break
+            else:
+                found = True
+            level += 1
+            frame = frame.f_back
+        else:
+            stacklevel = 1
+    msg = "%s (since version %s)" %(msg, startversion)
+    warn(msg, stacklevel=stacklevel+1, function=function)
+
+
+def warn(msg, stacklevel=1, function=None):
+    if function is not None:
+        import inspect
+        filename = inspect.getfile(function)
+        lineno = py.code.getrawcode(function).co_firstlineno
+    else:
+        try:
+            caller = sys._getframe(stacklevel)
+        except ValueError:
+            globals = sys.__dict__
+            lineno = 1
+        else:
+            globals = caller.f_globals
+            lineno = caller.f_lineno
+        if '__name__' in globals:
+            module = globals['__name__']
+        else:
+            module = "<string>"
+        filename = globals.get('__file__')
+    if filename:
+        fnl = filename.lower()
+        if fnl.endswith(".pyc") or fnl.endswith(".pyo"):
+            filename = filename[:-1]
+        elif fnl.endswith("$py.class"):
+            filename = filename.replace('$py.class', '.py')
+    else:
+        if module == "__main__":
+            try:
+                filename = sys.argv[0]
+            except AttributeError:
+                # embedded interpreters don't have sys.argv, see bug #839151
+                filename = '__main__'
+        if not filename:
+            filename = module
+    path = py.path.local(filename)
+    warning = DeprecationWarning(msg, path, lineno)
+    import warnings
+    warnings.warn_explicit(warning, category=Warning,
+        filename=str(warning.path),
+        lineno=warning.lineno,
+        registry=warnings.__dict__.setdefault(
+            "__warningsregistry__", {})
+    )
+
diff --git a/tools/py/py/_path/__init__.py b/tools/third_party/py/py/_path/__init__.py
similarity index 100%
rename from tools/py/py/_path/__init__.py
rename to tools/third_party/py/py/_path/__init__.py
diff --git a/tools/py/py/_path/cacheutil.py b/tools/third_party/py/py/_path/cacheutil.py
similarity index 100%
rename from tools/py/py/_path/cacheutil.py
rename to tools/third_party/py/py/_path/cacheutil.py
diff --git a/tools/third_party/py/py/_path/common.py b/tools/third_party/py/py/_path/common.py
new file mode 100644
index 0000000..2d490b5
--- /dev/null
+++ b/tools/third_party/py/py/_path/common.py
@@ -0,0 +1,453 @@
+"""
+"""
+import warnings
+import os
+import sys
+import posixpath
+import fnmatch
+import py
+
+# Moved from local.py.
+iswin32 = sys.platform == "win32" or (getattr(os, '_name', False) == 'nt')
+
+try:
+    from os import fspath
+except ImportError:
+    def fspath(path):
+        """
+        Return the string representation of the path.
+        If str or bytes is passed in, it is returned unchanged.
+        This code comes from PEP 519, modified to support earlier versions of
+        python.
+
+        This is required for python < 3.6.
+        """
+        if isinstance(path, (py.builtin.text, py.builtin.bytes)):
+            return path
+
+        # Work from the object's type to match method resolution of other magic
+        # methods.
+        path_type = type(path)
+        try:
+            return path_type.__fspath__(path)
+        except AttributeError:
+            if hasattr(path_type, '__fspath__'):
+                raise
+            try:
+                import pathlib
+            except ImportError:
+                pass
+            else:
+                if isinstance(path, pathlib.PurePath):
+                    return py.builtin.text(path)
+
+            raise TypeError("expected str, bytes or os.PathLike object, not "
+                            + path_type.__name__)
+
+class Checkers:
+    _depend_on_existence = 'exists', 'link', 'dir', 'file'
+
+    def __init__(self, path):
+        self.path = path
+
+    def dir(self):
+        raise NotImplementedError
+
+    def file(self):
+        raise NotImplementedError
+
+    def dotfile(self):
+        return self.path.basename.startswith('.')
+
+    def ext(self, arg):
+        if not arg.startswith('.'):
+            arg = '.' + arg
+        return self.path.ext == arg
+
+    def exists(self):
+        raise NotImplementedError
+
+    def basename(self, arg):
+        return self.path.basename == arg
+
+    def basestarts(self, arg):
+        return self.path.basename.startswith(arg)
+
+    def relto(self, arg):
+        return self.path.relto(arg)
+
+    def fnmatch(self, arg):
+        return self.path.fnmatch(arg)
+
+    def endswith(self, arg):
+        return str(self.path).endswith(arg)
+
+    def _evaluate(self, kw):
+        for name, value in kw.items():
+            invert = False
+            meth = None
+            try:
+                meth = getattr(self, name)
+            except AttributeError:
+                if name[:3] == 'not':
+                    invert = True
+                    try:
+                        meth = getattr(self, name[3:])
+                    except AttributeError:
+                        pass
+            if meth is None:
+                raise TypeError(
+                    "no %r checker available for %r" % (name, self.path))
+            try:
+                if py.code.getrawcode(meth).co_argcount > 1:
+                    if (not meth(value)) ^ invert:
+                        return False
+                else:
+                    if bool(value) ^ bool(meth()) ^ invert:
+                        return False
+            except (py.error.ENOENT, py.error.ENOTDIR, py.error.EBUSY):
+                # EBUSY feels not entirely correct,
+                # but its kind of necessary since ENOMEDIUM
+                # is not accessible in python
+                for name in self._depend_on_existence:
+                    if name in kw:
+                        if kw.get(name):
+                            return False
+                    name = 'not' + name
+                    if name in kw:
+                        if not kw.get(name):
+                            return False
+        return True
+
+class NeverRaised(Exception):
+    pass
+
+class PathBase(object):
+    """ shared implementation for filesystem path objects."""
+    Checkers = Checkers
+
+    def __div__(self, other):
+        return self.join(fspath(other))
+    __truediv__ = __div__ # py3k
+
+    def basename(self):
+        """ basename part of path. """
+        return self._getbyspec('basename')[0]
+    basename = property(basename, None, None, basename.__doc__)
+
+    def dirname(self):
+        """ dirname part of path. """
+        return self._getbyspec('dirname')[0]
+    dirname = property(dirname, None, None, dirname.__doc__)
+
+    def purebasename(self):
+        """ pure base name of the path."""
+        return self._getbyspec('purebasename')[0]
+    purebasename = property(purebasename, None, None, purebasename.__doc__)
+
+    def ext(self):
+        """ extension of the path (including the '.')."""
+        return self._getbyspec('ext')[0]
+    ext = property(ext, None, None, ext.__doc__)
+
+    def dirpath(self, *args, **kwargs):
+        """ return the directory path joined with any given path arguments.  """
+        return self.new(basename='').join(*args, **kwargs)
+
+    def read_binary(self):
+        """ read and return a bytestring from reading the path. """
+        with self.open('rb') as f:
+            return f.read()
+
+    def read_text(self, encoding):
+        """ read and return a Unicode string from reading the path. """
+        with self.open("r", encoding=encoding) as f:
+            return f.read()
+
+
+    def read(self, mode='r'):
+        """ read and return a bytestring from reading the path. """
+        with self.open(mode) as f:
+            return f.read()
+
+    def readlines(self, cr=1):
+        """ read and return a list of lines from the path. if cr is False, the
+newline will be removed from the end of each line. """
+        if sys.version_info < (3, ):
+            mode = 'rU'
+        else:  # python 3 deprecates mode "U" in favor of "newline" option
+            mode = 'r'
+
+        if not cr:
+            content = self.read(mode)
+            return content.split('\n')
+        else:
+            f = self.open(mode)
+            try:
+                return f.readlines()
+            finally:
+                f.close()
+
+    def load(self):
+        """ (deprecated) return object unpickled from self.read() """
+        f = self.open('rb')
+        try:
+            import pickle
+            return py.error.checked_call(pickle.load, f)
+        finally:
+            f.close()
+
+    def move(self, target):
+        """ move this path to target. """
+        if target.relto(self):
+            raise py.error.EINVAL(
+                target,
+                "cannot move path into a subdirectory of itself")
+        try:
+            self.rename(target)
+        except py.error.EXDEV:  # invalid cross-device link
+            self.copy(target)
+            self.remove()
+
+    def __repr__(self):
+        """ return a string representation of this path. """
+        return repr(str(self))
+
+    def check(self, **kw):
+        """ check a path for existence and properties.
+
+            Without arguments, return True if the path exists, otherwise False.
+
+            valid checkers::
+
+                file=1    # is a file
+                file=0    # is not a file (may not even exist)
+                dir=1     # is a dir
+                link=1    # is a link
+                exists=1  # exists
+
+            You can specify multiple checker definitions, for example::
+
+                path.check(file=1, link=1)  # a link pointing to a file
+        """
+        if not kw:
+            kw = {'exists': 1}
+        return self.Checkers(self)._evaluate(kw)
+
+    def fnmatch(self, pattern):
+        """return true if the basename/fullname matches the glob-'pattern'.
+
+        valid pattern characters::
+
+            *       matches everything
+            ?       matches any single character
+            [seq]   matches any character in seq
+            [!seq]  matches any char not in seq
+
+        If the pattern contains a path-separator then the full path
+        is used for pattern matching and a '*' is prepended to the
+        pattern.
+
+        if the pattern doesn't contain a path-separator the pattern
+        is only matched against the basename.
+        """
+        return FNMatcher(pattern)(self)
+
+    def relto(self, relpath):
+        """ return a string which is the relative part of the path
+        to the given 'relpath'.
+        """
+        if not isinstance(relpath, (str, PathBase)):
+            raise TypeError("%r: not a string or path object" %(relpath,))
+        strrelpath = str(relpath)
+        if strrelpath and strrelpath[-1] != self.sep:
+            strrelpath += self.sep
+        #assert strrelpath[-1] == self.sep
+        #assert strrelpath[-2] != self.sep
+        strself = self.strpath
+        if sys.platform == "win32" or getattr(os, '_name', None) == 'nt':
+            if os.path.normcase(strself).startswith(
+               os.path.normcase(strrelpath)):
+                return strself[len(strrelpath):]
+        elif strself.startswith(strrelpath):
+            return strself[len(strrelpath):]
+        return ""
+
+    def ensure_dir(self, *args):
+        """ ensure the path joined with args is a directory. """
+        return self.ensure(*args, **{"dir": True})
+
+    def bestrelpath(self, dest):
+        """ return a string which is a relative path from self
+            (assumed to be a directory) to dest such that
+            self.join(bestrelpath) == dest and if not such
+            path can be determined return dest.
+        """
+        try:
+            if self == dest:
+                return os.curdir
+            base = self.common(dest)
+            if not base:  # can be the case on windows
+                return str(dest)
+            self2base = self.relto(base)
+            reldest = dest.relto(base)
+            if self2base:
+                n = self2base.count(self.sep) + 1
+            else:
+                n = 0
+            l = [os.pardir] * n
+            if reldest:
+                l.append(reldest)
+            target = dest.sep.join(l)
+            return target
+        except AttributeError:
+            return str(dest)
+
+    def exists(self):
+        return self.check()
+
+    def isdir(self):
+        return self.check(dir=1)
+
+    def isfile(self):
+        return self.check(file=1)
+
+    def parts(self, reverse=False):
+        """ return a root-first list of all ancestor directories
+            plus the path itself.
+        """
+        current = self
+        l = [self]
+        while 1:
+            last = current
+            current = current.dirpath()
+            if last == current:
+                break
+            l.append(current)
+        if not reverse:
+            l.reverse()
+        return l
+
+    def common(self, other):
+        """ return the common part shared with the other path
+            or None if there is no common part.
+        """
+        last = None
+        for x, y in zip(self.parts(), other.parts()):
+            if x != y:
+                return last
+            last = x
+        return last
+
+    def __add__(self, other):
+        """ return new path object with 'other' added to the basename"""
+        return self.new(basename=self.basename+str(other))
+
+    def __cmp__(self, other):
+        """ return sort value (-1, 0, +1). """
+        try:
+            return cmp(self.strpath, other.strpath)
+        except AttributeError:
+            return cmp(str(self), str(other)) # self.path, other.path)
+
+    def __lt__(self, other):
+        try:
+            return self.strpath < other.strpath
+        except AttributeError:
+            return str(self) < str(other)
+
+    def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False):
+        """ yields all paths below the current one
+
+            fil is a filter (glob pattern or callable), if not matching the
+            path will not be yielded, defaulting to None (everything is
+            returned)
+
+            rec is a filter (glob pattern or callable) that controls whether
+            a node is descended, defaulting to None
+
+            ignore is an Exception class that is ignoredwhen calling dirlist()
+            on any of the paths (by default, all exceptions are reported)
+
+            bf if True will cause a breadthfirst search instead of the
+            default depthfirst. Default: False
+
+            sort if True will sort entries within each directory level.
+        """
+        for x in Visitor(fil, rec, ignore, bf, sort).gen(self):
+            yield x
+
+    def _sortlist(self, res, sort):
+        if sort:
+            if hasattr(sort, '__call__'):
+                warnings.warn(DeprecationWarning(
+                    "listdir(sort=callable) is deprecated and breaks on python3"
+                ), stacklevel=3)
+                res.sort(sort)
+            else:
+                res.sort()
+
+    def samefile(self, other):
+        """ return True if other refers to the same stat object as self. """
+        return self.strpath == str(other)
+
+    def __fspath__(self):
+        return self.strpath
+
+class Visitor:
+    def __init__(self, fil, rec, ignore, bf, sort):
+        if isinstance(fil, py.builtin._basestring):
+            fil = FNMatcher(fil)
+        if isinstance(rec, py.builtin._basestring):
+            self.rec = FNMatcher(rec)
+        elif not hasattr(rec, '__call__') and rec:
+            self.rec = lambda path: True
+        else:
+            self.rec = rec
+        self.fil = fil
+        self.ignore = ignore
+        self.breadthfirst = bf
+        self.optsort = sort and sorted or (lambda x: x)
+
+    def gen(self, path):
+        try:
+            entries = path.listdir()
+        except self.ignore:
+            return
+        rec = self.rec
+        dirs = self.optsort([p for p in entries
+                    if p.check(dir=1) and (rec is None or rec(p))])
+        if not self.breadthfirst:
+            for subdir in dirs:
+                for p in self.gen(subdir):
+                    yield p
+        for p in self.optsort(entries):
+            if self.fil is None or self.fil(p):
+                yield p
+        if self.breadthfirst:
+            for subdir in dirs:
+                for p in self.gen(subdir):
+                    yield p
+
+class FNMatcher:
+    def __init__(self, pattern):
+        self.pattern = pattern
+
+    def __call__(self, path):
+        pattern = self.pattern
+
+        if (pattern.find(path.sep) == -1 and
+        iswin32 and
+        pattern.find(posixpath.sep) != -1):
+            # Running on Windows, the pattern has no Windows path separators,
+            # and the pattern has one or more Posix path separators. Replace
+            # the Posix path separators with the Windows path separator.
+            pattern = pattern.replace(posixpath.sep, path.sep)
+
+        if pattern.find(path.sep) == -1:
+            name = path.basename
+        else:
+            name = str(path) # path.strpath # XXX svn?
+            if not os.path.isabs(pattern):
+                pattern = '*' + path.sep + pattern
+        return fnmatch.fnmatch(name, pattern)
diff --git a/tools/third_party/py/py/_path/local.py b/tools/third_party/py/py/_path/local.py
new file mode 100644
index 0000000..c550fa2
--- /dev/null
+++ b/tools/third_party/py/py/_path/local.py
@@ -0,0 +1,992 @@
+"""
+local path implementation.
+"""
+from __future__ import with_statement
+
+from contextlib import contextmanager
+import sys, os, re, atexit, io, uuid
+import py
+from py._path import common
+from py._path.common import iswin32, fspath
+from stat import S_ISLNK, S_ISDIR, S_ISREG
+
+from os.path import abspath, normcase, normpath, isabs, exists, isdir, isfile, islink, dirname
+
+if sys.version_info > (3,0):
+    def map_as_list(func, iter):
+        return list(map(func, iter))
+else:
+    map_as_list = map
+
+class Stat(object):
+    def __getattr__(self, name):
+        return getattr(self._osstatresult, "st_" + name)
+
+    def __init__(self, path, osstatresult):
+        self.path = path
+        self._osstatresult = osstatresult
+
+    @property
+    def owner(self):
+        if iswin32:
+            raise NotImplementedError("XXX win32")
+        import pwd
+        entry = py.error.checked_call(pwd.getpwuid, self.uid)
+        return entry[0]
+
+    @property
+    def group(self):
+        """ return group name of file. """
+        if iswin32:
+            raise NotImplementedError("XXX win32")
+        import grp
+        entry = py.error.checked_call(grp.getgrgid, self.gid)
+        return entry[0]
+
+    def isdir(self):
+        return S_ISDIR(self._osstatresult.st_mode)
+
+    def isfile(self):
+        return S_ISREG(self._osstatresult.st_mode)
+
+    def islink(self):
+        st = self.path.lstat()
+        return S_ISLNK(self._osstatresult.st_mode)
+
+class PosixPath(common.PathBase):
+    def chown(self, user, group, rec=0):
+        """ change ownership to the given user and group.
+            user and group may be specified by a number or
+            by a name.  if rec is True change ownership
+            recursively.
+        """
+        uid = getuserid(user)
+        gid = getgroupid(group)
+        if rec:
+            for x in self.visit(rec=lambda x: x.check(link=0)):
+                if x.check(link=0):
+                    py.error.checked_call(os.chown, str(x), uid, gid)
+        py.error.checked_call(os.chown, str(self), uid, gid)
+
+    def readlink(self):
+        """ return value of a symbolic link. """
+        return py.error.checked_call(os.readlink, self.strpath)
+
+    def mklinkto(self, oldname):
+        """ posix style hard link to another name. """
+        py.error.checked_call(os.link, str(oldname), str(self))
+
+    def mksymlinkto(self, value, absolute=1):
+        """ create a symbolic link with the given value (pointing to another name). """
+        if absolute:
+            py.error.checked_call(os.symlink, str(value), self.strpath)
+        else:
+            base = self.common(value)
+            # with posix local paths '/' is always a common base
+            relsource = self.__class__(value).relto(base)
+            reldest = self.relto(base)
+            n = reldest.count(self.sep)
+            target = self.sep.join(('..', )*n + (relsource, ))
+            py.error.checked_call(os.symlink, target, self.strpath)
+
+def getuserid(user):
+    import pwd
+    if not isinstance(user, int):
+        user = pwd.getpwnam(user)[2]
+    return user
+
+def getgroupid(group):
+    import grp
+    if not isinstance(group, int):
+        group = grp.getgrnam(group)[2]
+    return group
+
+FSBase = not iswin32 and PosixPath or common.PathBase
+
+class LocalPath(FSBase):
+    """ object oriented interface to os.path and other local filesystem
+        related information.
+    """
+    class ImportMismatchError(ImportError):
+        """ raised on pyimport() if there is a mismatch of __file__'s"""
+
+    sep = os.sep
+    class Checkers(common.Checkers):
+        def _stat(self):
+            try:
+                return self._statcache
+            except AttributeError:
+                try:
+                    self._statcache = self.path.stat()
+                except py.error.ELOOP:
+                    self._statcache = self.path.lstat()
+                return self._statcache
+
+        def dir(self):
+            return S_ISDIR(self._stat().mode)
+
+        def file(self):
+            return S_ISREG(self._stat().mode)
+
+        def exists(self):
+            return self._stat()
+
+        def link(self):
+            st = self.path.lstat()
+            return S_ISLNK(st.mode)
+
+    def __init__(self, path=None, expanduser=False):
+        """ Initialize and return a local Path instance.
+
+        Path can be relative to the current directory.
+        If path is None it defaults to the current working directory.
+        If expanduser is True, tilde-expansion is performed.
+        Note that Path instances always carry an absolute path.
+        Note also that passing in a local path object will simply return
+        the exact same path object. Use new() to get a new copy.
+        """
+        if path is None:
+            self.strpath = py.error.checked_call(os.getcwd)
+        else:
+            try:
+                path = fspath(path)
+            except TypeError:
+                raise ValueError("can only pass None, Path instances "
+                                 "or non-empty strings to LocalPath")
+            if expanduser:
+                path = os.path.expanduser(path)
+            self.strpath = abspath(path)
+
+    def __hash__(self):
+        return hash(self.strpath)
+
+    def __eq__(self, other):
+        s1 = fspath(self)
+        try:
+            s2 = fspath(other)
+        except TypeError:
+            return False
+        if iswin32:
+            s1 = s1.lower()
+            try:
+                s2 = s2.lower()
+            except AttributeError:
+                return False
+        return s1 == s2
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __lt__(self, other):
+        return fspath(self) < fspath(other)
+
+    def __gt__(self, other):
+        return fspath(self) > fspath(other)
+
+    def samefile(self, other):
+        """ return True if 'other' references the same file as 'self'.
+        """
+        other = fspath(other)
+        if not isabs(other):
+            other = abspath(other)
+        if self == other:
+            return True
+        if iswin32:
+            return False # there is no samefile
+        return py.error.checked_call(
+                os.path.samefile, self.strpath, other)
+
+    def remove(self, rec=1, ignore_errors=False):
+        """ remove a file or directory (or a directory tree if rec=1).
+        if ignore_errors is True, errors while removing directories will
+        be ignored.
+        """
+        if self.check(dir=1, link=0):
+            if rec:
+                # force remove of readonly files on windows
+                if iswin32:
+                    self.chmod(0o700, rec=1)
+                import shutil
+                py.error.checked_call(
+                    shutil.rmtree, self.strpath,
+                    ignore_errors=ignore_errors)
+            else:
+                py.error.checked_call(os.rmdir, self.strpath)
+        else:
+            if iswin32:
+                self.chmod(0o700)
+            py.error.checked_call(os.remove, self.strpath)
+
+    def computehash(self, hashtype="md5", chunksize=524288):
+        """ return hexdigest of hashvalue for this file. """
+        try:
+            try:
+                import hashlib as mod
+            except ImportError:
+                if hashtype == "sha1":
+                    hashtype = "sha"
+                mod = __import__(hashtype)
+            hash = getattr(mod, hashtype)()
+        except (AttributeError, ImportError):
+            raise ValueError("Don't know how to compute %r hash" %(hashtype,))
+        f = self.open('rb')
+        try:
+            while 1:
+                buf = f.read(chunksize)
+                if not buf:
+                    return hash.hexdigest()
+                hash.update(buf)
+        finally:
+            f.close()
+
+    def new(self, **kw):
+        """ create a modified version of this path.
+            the following keyword arguments modify various path parts::
+
+              a:/some/path/to/a/file.ext
+              xx                           drive
+              xxxxxxxxxxxxxxxxx            dirname
+                                xxxxxxxx   basename
+                                xxxx       purebasename
+                                     xxx   ext
+        """
+        obj = object.__new__(self.__class__)
+        if not kw:
+            obj.strpath = self.strpath
+            return obj
+        drive, dirname, basename, purebasename,ext = self._getbyspec(
+             "drive,dirname,basename,purebasename,ext")
+        if 'basename' in kw:
+            if 'purebasename' in kw or 'ext' in kw:
+                raise ValueError("invalid specification %r" % kw)
+        else:
+            pb = kw.setdefault('purebasename', purebasename)
+            try:
+                ext = kw['ext']
+            except KeyError:
+                pass
+            else:
+                if ext and not ext.startswith('.'):
+                    ext = '.' + ext
+            kw['basename'] = pb + ext
+
+        if ('dirname' in kw and not kw['dirname']):
+            kw['dirname'] = drive
+        else:
+            kw.setdefault('dirname', dirname)
+        kw.setdefault('sep', self.sep)
+        obj.strpath = normpath(
+            "%(dirname)s%(sep)s%(basename)s" % kw)
+        return obj
+
+    def _getbyspec(self, spec):
+        """ see new for what 'spec' can be. """
+        res = []
+        parts = self.strpath.split(self.sep)
+
+        args = filter(None, spec.split(',') )
+        append = res.append
+        for name in args:
+            if name == 'drive':
+                append(parts[0])
+            elif name == 'dirname':
+                append(self.sep.join(parts[:-1]))
+            else:
+                basename = parts[-1]
+                if name == 'basename':
+                    append(basename)
+                else:
+                    i = basename.rfind('.')
+                    if i == -1:
+                        purebasename, ext = basename, ''
+                    else:
+                        purebasename, ext = basename[:i], basename[i:]
+                    if name == 'purebasename':
+                        append(purebasename)
+                    elif name == 'ext':
+                        append(ext)
+                    else:
+                        raise ValueError("invalid part specification %r" % name)
+        return res
+
+    def dirpath(self, *args, **kwargs):
+        """ return the directory path joined with any given path arguments.  """
+        if not kwargs:
+            path = object.__new__(self.__class__)
+            path.strpath = dirname(self.strpath)
+            if args:
+                path = path.join(*args)
+            return path
+        return super(LocalPath, self).dirpath(*args, **kwargs)
+
+    def join(self, *args, **kwargs):
+        """ return a new path by appending all 'args' as path
+        components.  if abs=1 is used restart from root if any
+        of the args is an absolute path.
+        """
+        sep = self.sep
+        strargs = [fspath(arg) for arg in args]
+        strpath = self.strpath
+        if kwargs.get('abs'):
+            newargs = []
+            for arg in reversed(strargs):
+                if isabs(arg):
+                    strpath = arg
+                    strargs = newargs
+                    break
+                newargs.insert(0, arg)
+        # special case for when we have e.g. strpath == "/"
+        actual_sep = "" if strpath.endswith(sep) else sep
+        for arg in strargs:
+            arg = arg.strip(sep)
+            if iswin32:
+                # allow unix style paths even on windows.
+                arg = arg.strip('/')
+                arg = arg.replace('/', sep)
+            strpath = strpath + actual_sep + arg
+            actual_sep = sep
+        obj = object.__new__(self.__class__)
+        obj.strpath = normpath(strpath)
+        return obj
+
+    def open(self, mode='r', ensure=False, encoding=None):
+        """ return an opened file with the given mode.
+
+        If ensure is True, create parent directories if needed.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        if encoding:
+            return py.error.checked_call(io.open, self.strpath, mode, encoding=encoding)
+        return py.error.checked_call(open, self.strpath, mode)
+
+    def _fastjoin(self, name):
+        child = object.__new__(self.__class__)
+        child.strpath = self.strpath + self.sep + name
+        return child
+
+    def islink(self):
+        return islink(self.strpath)
+
+    def check(self, **kw):
+        if not kw:
+            return exists(self.strpath)
+        if len(kw) == 1:
+            if "dir" in kw:
+                return not kw["dir"] ^ isdir(self.strpath)
+            if "file" in kw:
+                return not kw["file"] ^ isfile(self.strpath)
+        return super(LocalPath, self).check(**kw)
+
+    _patternchars = set("*?[" + os.path.sep)
+    def listdir(self, fil=None, sort=None):
+        """ list directory contents, possibly filter by the given fil func
+            and possibly sorted.
+        """
+        if fil is None and sort is None:
+            names = py.error.checked_call(os.listdir, self.strpath)
+            return map_as_list(self._fastjoin, names)
+        if isinstance(fil, py.builtin._basestring):
+            if not self._patternchars.intersection(fil):
+                child = self._fastjoin(fil)
+                if exists(child.strpath):
+                    return [child]
+                return []
+            fil = common.FNMatcher(fil)
+        names = py.error.checked_call(os.listdir, self.strpath)
+        res = []
+        for name in names:
+            child = self._fastjoin(name)
+            if fil is None or fil(child):
+                res.append(child)
+        self._sortlist(res, sort)
+        return res
+
+    def size(self):
+        """ return size of the underlying file object """
+        return self.stat().size
+
+    def mtime(self):
+        """ return last modification time of the path. """
+        return self.stat().mtime
+
+    def copy(self, target, mode=False, stat=False):
+        """ copy path to target.
+
+            If mode is True, will copy copy permission from path to target.
+            If stat is True, copy permission, last modification
+            time, last access time, and flags from path to target.
+        """
+        if self.check(file=1):
+            if target.check(dir=1):
+                target = target.join(self.basename)
+            assert self!=target
+            copychunked(self, target)
+            if mode:
+                copymode(self.strpath, target.strpath)
+            if stat:
+                copystat(self, target)
+        else:
+            def rec(p):
+                return p.check(link=0)
+            for x in self.visit(rec=rec):
+                relpath = x.relto(self)
+                newx = target.join(relpath)
+                newx.dirpath().ensure(dir=1)
+                if x.check(link=1):
+                    newx.mksymlinkto(x.readlink())
+                    continue
+                elif x.check(file=1):
+                    copychunked(x, newx)
+                elif x.check(dir=1):
+                    newx.ensure(dir=1)
+                if mode:
+                    copymode(x.strpath, newx.strpath)
+                if stat:
+                    copystat(x, newx)
+
+    def rename(self, target):
+        """ rename this path to target. """
+        target = fspath(target)
+        return py.error.checked_call(os.rename, self.strpath, target)
+
+    def dump(self, obj, bin=1):
+        """ pickle object into path location"""
+        f = self.open('wb')
+        import pickle
+        try:
+            py.error.checked_call(pickle.dump, obj, f, bin)
+        finally:
+            f.close()
+
+    def mkdir(self, *args):
+        """ create & return the directory joined with args. """
+        p = self.join(*args)
+        py.error.checked_call(os.mkdir, fspath(p))
+        return p
+
+    def write_binary(self, data, ensure=False):
+        """ write binary data into path.   If ensure is True create
+        missing parent directories.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        with self.open('wb') as f:
+            f.write(data)
+
+    def write_text(self, data, encoding, ensure=False):
+        """ write text data into path using the specified encoding.
+        If ensure is True create missing parent directories.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        with self.open('w', encoding=encoding) as f:
+            f.write(data)
+
+    def write(self, data, mode='w', ensure=False):
+        """ write data into path.   If ensure is True create
+        missing parent directories.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        if 'b' in mode:
+            if not py.builtin._isbytes(data):
+                raise ValueError("can only process bytes")
+        else:
+            if not py.builtin._istext(data):
+                if not py.builtin._isbytes(data):
+                    data = str(data)
+                else:
+                    data = py.builtin._totext(data, sys.getdefaultencoding())
+        f = self.open(mode)
+        try:
+            f.write(data)
+        finally:
+            f.close()
+
+    def _ensuredirs(self):
+        parent = self.dirpath()
+        if parent == self:
+            return self
+        if parent.check(dir=0):
+            parent._ensuredirs()
+        if self.check(dir=0):
+            try:
+                self.mkdir()
+            except py.error.EEXIST:
+                # race condition: file/dir created by another thread/process.
+                # complain if it is not a dir
+                if self.check(dir=0):
+                    raise
+        return self
+
+    def ensure(self, *args, **kwargs):
+        """ ensure that an args-joined path exists (by default as
+            a file). if you specify a keyword argument 'dir=True'
+            then the path is forced to be a directory path.
+        """
+        p = self.join(*args)
+        if kwargs.get('dir', 0):
+            return p._ensuredirs()
+        else:
+            p.dirpath()._ensuredirs()
+            if not p.check(file=1):
+                p.open('w').close()
+            return p
+
+    def stat(self, raising=True):
+        """ Return an os.stat() tuple. """
+        if raising == True:
+            return Stat(self, py.error.checked_call(os.stat, self.strpath))
+        try:
+            return Stat(self, os.stat(self.strpath))
+        except KeyboardInterrupt:
+            raise
+        except Exception:
+            return None
+
+    def lstat(self):
+        """ Return an os.lstat() tuple. """
+        return Stat(self, py.error.checked_call(os.lstat, self.strpath))
+
+    def setmtime(self, mtime=None):
+        """ set modification time for the given path.  if 'mtime' is None
+        (the default) then the file's mtime is set to current time.
+
+        Note that the resolution for 'mtime' is platform dependent.
+        """
+        if mtime is None:
+            return py.error.checked_call(os.utime, self.strpath, mtime)
+        try:
+            return py.error.checked_call(os.utime, self.strpath, (-1, mtime))
+        except py.error.EINVAL:
+            return py.error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
+
+    def chdir(self):
+        """ change directory to self and return old current directory """
+        try:
+            old = self.__class__()
+        except py.error.ENOENT:
+            old = None
+        py.error.checked_call(os.chdir, self.strpath)
+        return old
+
+
+    @contextmanager
+    def as_cwd(self):
+        """ return context manager which changes to current dir during the
+        managed "with" context. On __enter__ it returns the old dir.
+        """
+        old = self.chdir()
+        try:
+            yield old
+        finally:
+            old.chdir()
+
+    def realpath(self):
+        """ return a new path which contains no symbolic links."""
+        return self.__class__(os.path.realpath(self.strpath))
+
+    def atime(self):
+        """ return last access time of the path. """
+        return self.stat().atime
+
+    def __repr__(self):
+        return 'local(%r)' % self.strpath
+
+    def __str__(self):
+        """ return string representation of the Path. """
+        return self.strpath
+
+    def chmod(self, mode, rec=0):
+        """ change permissions to the given mode. If mode is an
+            integer it directly encodes the os-specific modes.
+            if rec is True perform recursively.
+        """
+        if not isinstance(mode, int):
+            raise TypeError("mode %r must be an integer" % (mode,))
+        if rec:
+            for x in self.visit(rec=rec):
+                py.error.checked_call(os.chmod, str(x), mode)
+        py.error.checked_call(os.chmod, self.strpath, mode)
+
+    def pypkgpath(self):
+        """ return the Python package path by looking for the last
+        directory upwards which still contains an __init__.py.
+        Return None if a pkgpath can not be determined.
+        """
+        pkgpath = None
+        for parent in self.parts(reverse=True):
+            if parent.isdir():
+                if not parent.join('__init__.py').exists():
+                    break
+                if not isimportable(parent.basename):
+                    break
+                pkgpath = parent
+        return pkgpath
+
+    def _ensuresyspath(self, ensuremode, path):
+        if ensuremode:
+            s = str(path)
+            if ensuremode == "append":
+                if s not in sys.path:
+                    sys.path.append(s)
+            else:
+                if s != sys.path[0]:
+                    sys.path.insert(0, s)
+
+    def pyimport(self, modname=None, ensuresyspath=True):
+        """ return path as an imported python module.
+
+        If modname is None, look for the containing package
+        and construct an according module name.
+        The module will be put/looked up in sys.modules.
+        if ensuresyspath is True then the root dir for importing
+        the file (taking __init__.py files into account) will
+        be prepended to sys.path if it isn't there already.
+        If ensuresyspath=="append" the root dir will be appended
+        if it isn't already contained in sys.path.
+        if ensuresyspath is False no modification of syspath happens.
+        """
+        if not self.check():
+            raise py.error.ENOENT(self)
+
+        pkgpath = None
+        if modname is None:
+            pkgpath = self.pypkgpath()
+            if pkgpath is not None:
+                pkgroot = pkgpath.dirpath()
+                names = self.new(ext="").relto(pkgroot).split(self.sep)
+                if names[-1] == "__init__":
+                    names.pop()
+                modname = ".".join(names)
+            else:
+                pkgroot = self.dirpath()
+                modname = self.purebasename
+
+            self._ensuresyspath(ensuresyspath, pkgroot)
+            __import__(modname)
+            mod = sys.modules[modname]
+            if self.basename == "__init__.py":
+                return mod # we don't check anything as we might
+                       # we in a namespace package ... too icky to check
+            modfile = mod.__file__
+            if modfile[-4:] in ('.pyc', '.pyo'):
+                modfile = modfile[:-1]
+            elif modfile.endswith('$py.class'):
+                modfile = modfile[:-9] + '.py'
+            if modfile.endswith(os.path.sep + "__init__.py"):
+                if self.basename != "__init__.py":
+                    modfile = modfile[:-12]
+            try:
+                issame = self.samefile(modfile)
+            except py.error.ENOENT:
+                issame = False
+            if not issame:
+                raise self.ImportMismatchError(modname, modfile, self)
+            return mod
+        else:
+            try:
+                return sys.modules[modname]
+            except KeyError:
+                # we have a custom modname, do a pseudo-import
+                import types
+                mod = types.ModuleType(modname)
+                mod.__file__ = str(self)
+                sys.modules[modname] = mod
+                try:
+                    py.builtin.execfile(str(self), mod.__dict__)
+                except:
+                    del sys.modules[modname]
+                    raise
+                return mod
+
+    def sysexec(self, *argv, **popen_opts):
+        """ return stdout text from executing a system child process,
+            where the 'self' path points to executable.
+            The process is directly invoked and not through a system shell.
+        """
+        from subprocess import Popen, PIPE
+        argv = map_as_list(str, argv)
+        popen_opts['stdout'] = popen_opts['stderr'] = PIPE
+        proc = Popen([str(self)] + argv, **popen_opts)
+        stdout, stderr = proc.communicate()
+        ret = proc.wait()
+        if py.builtin._isbytes(stdout):
+            stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
+        if ret != 0:
+            if py.builtin._isbytes(stderr):
+                stderr = py.builtin._totext(stderr, sys.getdefaultencoding())
+            raise py.process.cmdexec.Error(ret, ret, str(self),
+                                           stdout, stderr,)
+        return stdout
+
+    def sysfind(cls, name, checker=None, paths=None):
+        """ return a path object found by looking at the systems
+            underlying PATH specification. If the checker is not None
+            it will be invoked to filter matching paths.  If a binary
+            cannot be found, None is returned
+            Note: This is probably not working on plain win32 systems
+            but may work on cygwin.
+        """
+        if isabs(name):
+            p = py.path.local(name)
+            if p.check(file=1):
+                return p
+        else:
+            if paths is None:
+                if iswin32:
+                    paths = os.environ['Path'].split(';')
+                    if '' not in paths and '.' not in paths:
+                        paths.append('.')
+                    try:
+                        systemroot = os.environ['SYSTEMROOT']
+                    except KeyError:
+                        pass
+                    else:
+                        paths = [path.replace('%SystemRoot%', systemroot)
+                                 for path in paths]
+                else:
+                    paths = os.environ['PATH'].split(':')
+            tryadd = []
+            if iswin32:
+                tryadd += os.environ['PATHEXT'].split(os.pathsep)
+            tryadd.append("")
+
+            for x in paths:
+                for addext in tryadd:
+                    p = py.path.local(x).join(name, abs=True) + addext
+                    try:
+                        if p.check(file=1):
+                            if checker:
+                                if not checker(p):
+                                    continue
+                            return p
+                    except py.error.EACCES:
+                        pass
+        return None
+    sysfind = classmethod(sysfind)
+
+    def _gethomedir(cls):
+        try:
+            x = os.environ['HOME']
+        except KeyError:
+            try:
+                x = os.environ["HOMEDRIVE"] + os.environ['HOMEPATH']
+            except KeyError:
+                return None
+        return cls(x)
+    _gethomedir = classmethod(_gethomedir)
+
+    # """
+    # special class constructors for local filesystem paths
+    # """
+    @classmethod
+    def get_temproot(cls):
+        """ return the system's temporary directory
+            (where tempfiles are usually created in)
+        """
+        import tempfile
+        return py.path.local(tempfile.gettempdir())
+
+    @classmethod
+    def mkdtemp(cls, rootdir=None):
+        """ return a Path object pointing to a fresh new temporary directory
+            (which we created ourself).
+        """
+        import tempfile
+        if rootdir is None:
+            rootdir = cls.get_temproot()
+        return cls(py.error.checked_call(tempfile.mkdtemp, dir=str(rootdir)))
+
+    def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3,
+                          lock_timeout = 172800):   # two days
+        """ return unique directory with a number greater than the current
+            maximum one.  The number is assumed to start directly after prefix.
+            if keep is true directories with a number less than (maxnum-keep)
+            will be removed. If .lock files are used (lock_timeout non-zero),
+            algorithm is multi-process safe.
+        """
+        if rootdir is None:
+            rootdir = cls.get_temproot()
+
+        nprefix = normcase(prefix)
+        def parse_num(path):
+            """ parse the number out of a path (if it matches the prefix) """
+            nbasename = normcase(path.basename)
+            if nbasename.startswith(nprefix):
+                try:
+                    return int(nbasename[len(nprefix):])
+                except ValueError:
+                    pass
+
+        def create_lockfile(path):
+            """ exclusively create lockfile. Throws when failed """
+            mypid = os.getpid()
+            lockfile = path.join('.lock')
+            if hasattr(lockfile, 'mksymlinkto'):
+                lockfile.mksymlinkto(str(mypid))
+            else:
+                fd = py.error.checked_call(os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
+                with os.fdopen(fd, 'w') as f:
+                    f.write(str(mypid))
+            return lockfile
+
+        def atexit_remove_lockfile(lockfile):
+            """ ensure lockfile is removed at process exit """
+            mypid = os.getpid()
+            def try_remove_lockfile():
+                # in a fork() situation, only the last process should
+                # remove the .lock, otherwise the other processes run the
+                # risk of seeing their temporary dir disappear.  For now
+                # we remove the .lock in the parent only (i.e. we assume
+                # that the children finish before the parent).
+                if os.getpid() != mypid:
+                    return
+                try:
+                    lockfile.remove()
+                except py.error.Error:
+                    pass
+            atexit.register(try_remove_lockfile)
+
+        # compute the maximum number currently in use with the prefix
+        lastmax = None
+        while True:
+            maxnum = -1
+            for path in rootdir.listdir():
+                num = parse_num(path)
+                if num is not None:
+                    maxnum = max(maxnum, num)
+
+            # make the new directory
+            try:
+                udir = rootdir.mkdir(prefix + str(maxnum+1))
+                if lock_timeout:
+                    lockfile = create_lockfile(udir)
+                    atexit_remove_lockfile(lockfile)
+            except (py.error.EEXIST, py.error.ENOENT, py.error.EBUSY):
+                # race condition (1): another thread/process created the dir
+                #                     in the meantime - try again
+                # race condition (2): another thread/process spuriously acquired
+                #                     lock treating empty directory as candidate
+                #                     for removal - try again
+                # race condition (3): another thread/process tried to create the lock at
+                #                     the same time (happened in Python 3.3 on Windows)
+                # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa
+                if lastmax == maxnum:
+                    raise
+                lastmax = maxnum
+                continue
+            break
+
+        def get_mtime(path):
+            """ read file modification time """
+            try:
+                return path.lstat().mtime
+            except py.error.Error:
+                pass
+
+        garbage_prefix = prefix + 'garbage-'
+
+        def is_garbage(path):
+            """ check if path denotes directory scheduled for removal """
+            bn = path.basename
+            return bn.startswith(garbage_prefix)
+
+        # prune old directories
+        udir_time = get_mtime(udir)
+        if keep and udir_time:
+            for path in rootdir.listdir():
+                num = parse_num(path)
+                if num is not None and num <= (maxnum - keep):
+                    try:
+                        # try acquiring lock to remove directory as exclusive user
+                        if lock_timeout:
+                            create_lockfile(path)
+                    except (py.error.EEXIST, py.error.ENOENT, py.error.EBUSY):
+                        path_time = get_mtime(path)
+                        if not path_time:
+                            # assume directory doesn't exist now
+                            continue
+                        if abs(udir_time - path_time) < lock_timeout:
+                            # assume directory with lockfile exists
+                            # and lock timeout hasn't expired yet
+                            continue
+
+                    # path dir locked for exclusive use
+                    # and scheduled for removal to avoid another thread/process
+                    # treating it as a new directory or removal candidate
+                    garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4()))
+                    try:
+                        path.rename(garbage_path)
+                        garbage_path.remove(rec=1)
+                    except KeyboardInterrupt:
+                        raise
+                    except: # this might be py.error.Error, WindowsError ...
+                        pass
+                if is_garbage(path):
+                    try:
+                        path.remove(rec=1)
+                    except KeyboardInterrupt:
+                        raise
+                    except: # this might be py.error.Error, WindowsError ...
+                        pass
+
+        # make link...
+        try:
+            username = os.environ['USER']           #linux, et al
+        except KeyError:
+            try:
+                username = os.environ['USERNAME']   #windows
+            except KeyError:
+                username = 'current'
+
+        src  = str(udir)
+        dest = src[:src.rfind('-')] + '-' + username
+        try:
+            os.unlink(dest)
+        except OSError:
+            pass
+        try:
+            os.symlink(src, dest)
+        except (OSError, AttributeError, NotImplementedError):
+            pass
+
+        return udir
+    make_numbered_dir = classmethod(make_numbered_dir)
+
+
+def copymode(src, dest):
+    """ copy permission from src to dst. """
+    import shutil
+    shutil.copymode(src, dest)
+
+
+def copystat(src, dest):
+    """ copy permission,  last modification time,
+    last access time, and flags from src to dst."""
+    import shutil
+    shutil.copystat(str(src), str(dest))
+
+
+def copychunked(src, dest):
+    chunksize = 524288  # half a meg of bytes
+    fsrc = src.open('rb')
+    try:
+        fdest = dest.open('wb')
+        try:
+            while 1:
+                buf = fsrc.read(chunksize)
+                if not buf:
+                    break
+                fdest.write(buf)
+        finally:
+            fdest.close()
+    finally:
+        fsrc.close()
+
+
+def isimportable(name):
+    if name and (name[0].isalpha() or name[0] == '_'):
+        name = name.replace("_", '')
+        return not name or name.isalnum()
diff --git a/tools/third_party/py/py/_path/svnurl.py b/tools/third_party/py/py/_path/svnurl.py
new file mode 100644
index 0000000..6589a71
--- /dev/null
+++ b/tools/third_party/py/py/_path/svnurl.py
@@ -0,0 +1,380 @@
+"""
+module defining a subversion path object based on the external
+command 'svn'. This modules aims to work with svn 1.3 and higher
+but might also interact well with earlier versions.
+"""
+
+import os, sys, time, re
+import py
+from py import path, process
+from py._path import common
+from py._path import svnwc as svncommon
+from py._path.cacheutil import BuildcostAccessCache, AgingCache
+
+DEBUG=False
+
+class SvnCommandPath(svncommon.SvnPathBase):
+    """ path implementation that offers access to (possibly remote) subversion
+    repositories. """
+
+    _lsrevcache = BuildcostAccessCache(maxentries=128)
+    _lsnorevcache = AgingCache(maxentries=1000, maxseconds=60.0)
+
+    def __new__(cls, path, rev=None, auth=None):
+        self = object.__new__(cls)
+        if isinstance(path, cls):
+            rev = path.rev
+            auth = path.auth
+            path = path.strpath
+        svncommon.checkbadchars(path)
+        path = path.rstrip('/')
+        self.strpath = path
+        self.rev = rev
+        self.auth = auth
+        return self
+
+    def __repr__(self):
+        if self.rev == -1:
+            return 'svnurl(%r)' % self.strpath
+        else:
+            return 'svnurl(%r, %r)' % (self.strpath, self.rev)
+
+    def _svnwithrev(self, cmd, *args):
+        """ execute an svn command, append our own url and revision """
+        if self.rev is None:
+            return self._svnwrite(cmd, *args)
+        else:
+            args = ['-r', self.rev] + list(args)
+            return self._svnwrite(cmd, *args)
+
+    def _svnwrite(self, cmd, *args):
+        """ execute an svn command, append our own url """
+        l = ['svn %s' % cmd]
+        args = ['"%s"' % self._escape(item) for item in args]
+        l.extend(args)
+        l.append('"%s"' % self._encodedurl())
+        # fixing the locale because we can't otherwise parse
+        string = " ".join(l)
+        if DEBUG:
+            print("execing %s" % string)
+        out = self._svncmdexecauth(string)
+        return out
+
+    def _svncmdexecauth(self, cmd):
+        """ execute an svn command 'as is' """
+        cmd = svncommon.fixlocale() + cmd
+        if self.auth is not None:
+            cmd += ' ' + self.auth.makecmdoptions()
+        return self._cmdexec(cmd)
+
+    def _cmdexec(self, cmd):
+        try:
+            out = process.cmdexec(cmd)
+        except py.process.cmdexec.Error:
+            e = sys.exc_info()[1]
+            if (e.err.find('File Exists') != -1 or
+                            e.err.find('File already exists') != -1):
+                raise py.error.EEXIST(self)
+            raise
+        return out
+
+    def _svnpopenauth(self, cmd):
+        """ execute an svn command, return a pipe for reading stdin """
+        cmd = svncommon.fixlocale() + cmd
+        if self.auth is not None:
+            cmd += ' ' + self.auth.makecmdoptions()
+        return self._popen(cmd)
+
+    def _popen(self, cmd):
+        return os.popen(cmd)
+
+    def _encodedurl(self):
+        return self._escape(self.strpath)
+
+    def _norev_delentry(self, path):
+        auth = self.auth and self.auth.makecmdoptions() or None
+        self._lsnorevcache.delentry((str(path), auth))
+
+    def open(self, mode='r'):
+        """ return an opened file with the given mode. """
+        if mode not in ("r", "rU",):
+            raise ValueError("mode %r not supported" % (mode,))
+        assert self.check(file=1) # svn cat returns an empty file otherwise
+        if self.rev is None:
+            return self._svnpopenauth('svn cat "%s"' % (
+                                      self._escape(self.strpath), ))
+        else:
+            return self._svnpopenauth('svn cat -r %s "%s"' % (
+                                      self.rev, self._escape(self.strpath)))
+
+    def dirpath(self, *args, **kwargs):
+        """ return the directory path of the current path joined
+            with any given path arguments.
+        """
+        l = self.strpath.split(self.sep)
+        if len(l) < 4:
+            raise py.error.EINVAL(self, "base is not valid")
+        elif len(l) == 4:
+            return self.join(*args, **kwargs)
+        else:
+            return self.new(basename='').join(*args, **kwargs)
+
+    # modifying methods (cache must be invalidated)
+    def mkdir(self, *args, **kwargs):
+        """ create & return the directory joined with args.
+        pass a 'msg' keyword argument to set the commit message.
+        """
+        commit_msg = kwargs.get('msg', "mkdir by py lib invocation")
+        createpath = self.join(*args)
+        createpath._svnwrite('mkdir', '-m', commit_msg)
+        self._norev_delentry(createpath.dirpath())
+        return createpath
+
+    def copy(self, target, msg='copied by py lib invocation'):
+        """ copy path to target with checkin message msg."""
+        if getattr(target, 'rev', None) is not None:
+            raise py.error.EINVAL(target, "revisions are immutable")
+        self._svncmdexecauth('svn copy -m "%s" "%s" "%s"' %(msg,
+                             self._escape(self), self._escape(target)))
+        self._norev_delentry(target.dirpath())
+
+    def rename(self, target, msg="renamed by py lib invocation"):
+        """ rename this path to target with checkin message msg. """
+        if getattr(self, 'rev', None) is not None:
+            raise py.error.EINVAL(self, "revisions are immutable")
+        self._svncmdexecauth('svn move -m "%s" --force "%s" "%s"' %(
+                             msg, self._escape(self), self._escape(target)))
+        self._norev_delentry(self.dirpath())
+        self._norev_delentry(self)
+
+    def remove(self, rec=1, msg='removed by py lib invocation'):
+        """ remove a file or directory (or a directory tree if rec=1) with
+checkin message msg."""
+        if self.rev is not None:
+            raise py.error.EINVAL(self, "revisions are immutable")
+        self._svncmdexecauth('svn rm -m "%s" "%s"' %(msg, self._escape(self)))
+        self._norev_delentry(self.dirpath())
+
+    def export(self, topath):
+        """ export to a local path
+
+            topath should not exist prior to calling this, returns a
+            py.path.local instance
+        """
+        topath = py.path.local(topath)
+        args = ['"%s"' % (self._escape(self),),
+                '"%s"' % (self._escape(topath),)]
+        if self.rev is not None:
+            args = ['-r', str(self.rev)] + args
+        self._svncmdexecauth('svn export %s' % (' '.join(args),))
+        return topath
+
+    def ensure(self, *args, **kwargs):
+        """ ensure that an args-joined path exists (by default as
+            a file). If you specify a keyword argument 'dir=True'
+            then the path is forced to be a directory path.
+        """
+        if getattr(self, 'rev', None) is not None:
+            raise py.error.EINVAL(self, "revisions are immutable")
+        target = self.join(*args)
+        dir = kwargs.get('dir', 0)
+        for x in target.parts(reverse=True):
+            if x.check():
+                break
+        else:
+            raise py.error.ENOENT(target, "has not any valid base!")
+        if x == target:
+            if not x.check(dir=dir):
+                raise dir and py.error.ENOTDIR(x) or py.error.EISDIR(x)
+            return x
+        tocreate = target.relto(x)
+        basename = tocreate.split(self.sep, 1)[0]
+        tempdir = py.path.local.mkdtemp()
+        try:
+            tempdir.ensure(tocreate, dir=dir)
+            cmd = 'svn import -m "%s" "%s" "%s"' % (
+                    "ensure %s" % self._escape(tocreate),
+                    self._escape(tempdir.join(basename)),
+                    x.join(basename)._encodedurl())
+            self._svncmdexecauth(cmd)
+            self._norev_delentry(x)
+        finally:
+            tempdir.remove()
+        return target
+
+    # end of modifying methods
+    def _propget(self, name):
+        res = self._svnwithrev('propget', name)
+        return res[:-1] # strip trailing newline
+
+    def _proplist(self):
+        res = self._svnwithrev('proplist')
+        lines = res.split('\n')
+        lines = [x.strip() for x in lines[1:]]
+        return svncommon.PropListDict(self, lines)
+
+    def info(self):
+        """ return an Info structure with svn-provided information. """
+        parent = self.dirpath()
+        nameinfo_seq = parent._listdir_nameinfo()
+        bn = self.basename
+        for name, info in nameinfo_seq:
+            if name == bn:
+                return info
+        raise py.error.ENOENT(self)
+
+
+    def _listdir_nameinfo(self):
+        """ return sequence of name-info directory entries of self """
+        def builder():
+            try:
+                res = self._svnwithrev('ls', '-v')
+            except process.cmdexec.Error:
+                e = sys.exc_info()[1]
+                if e.err.find('non-existent in that revision') != -1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find("E200009:") != -1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find('File not found') != -1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find('not part of a repository')!=-1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find('Unable to open')!=-1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.lower().find('method not allowed')!=-1:
+                    raise py.error.EACCES(self, e.err)
+                raise py.error.Error(e.err)
+            lines = res.split('\n')
+            nameinfo_seq = []
+            for lsline in lines:
+                if lsline:
+                    info = InfoSvnCommand(lsline)
+                    if info._name != '.':  # svn 1.5 produces '.' dirs,
+                        nameinfo_seq.append((info._name, info))
+            nameinfo_seq.sort()
+            return nameinfo_seq
+        auth = self.auth and self.auth.makecmdoptions() or None
+        if self.rev is not None:
+            return self._lsrevcache.getorbuild((self.strpath, self.rev, auth),
+                                               builder)
+        else:
+            return self._lsnorevcache.getorbuild((self.strpath, auth),
+                                                 builder)
+
+    def listdir(self, fil=None, sort=None):
+        """ list directory contents, possibly filter by the given fil func
+            and possibly sorted.
+        """
+        if isinstance(fil, str):
+            fil = common.FNMatcher(fil)
+        nameinfo_seq = self._listdir_nameinfo()
+        if len(nameinfo_seq) == 1:
+            name, info = nameinfo_seq[0]
+            if name == self.basename and info.kind == 'file':
+                #if not self.check(dir=1):
+                raise py.error.ENOTDIR(self)
+        paths = [self.join(name) for (name, info) in nameinfo_seq]
+        if fil:
+            paths = [x for x in paths if fil(x)]
+        self._sortlist(paths, sort)
+        return paths
+
+
+    def log(self, rev_start=None, rev_end=1, verbose=False):
+        """ return a list of LogEntry instances for this path.
+rev_start is the starting revision (defaulting to the first one).
+rev_end is the last revision (defaulting to HEAD).
+if verbose is True, then the LogEntry instances also know which files changed.
+"""
+        assert self.check() #make it simpler for the pipe
+        rev_start = rev_start is None and "HEAD" or rev_start
+        rev_end = rev_end is None and "HEAD" or rev_end
+
+        if rev_start == "HEAD" and rev_end == 1:
+            rev_opt = ""
+        else:
+            rev_opt = "-r %s:%s" % (rev_start, rev_end)
+        verbose_opt = verbose and "-v" or ""
+        xmlpipe =  self._svnpopenauth('svn log --xml %s %s "%s"' %
+                                      (rev_opt, verbose_opt, self.strpath))
+        from xml.dom import minidom
+        tree = minidom.parse(xmlpipe)
+        result = []
+        for logentry in filter(None, tree.firstChild.childNodes):
+            if logentry.nodeType == logentry.ELEMENT_NODE:
+                result.append(svncommon.LogEntry(logentry))
+        return result
+
+#01234567890123456789012345678901234567890123467
+#   2256      hpk        165 Nov 24 17:55 __init__.py
+# XXX spotted by Guido, SVN 1.3.0 has different aligning, breaks the code!!!
+#   1312 johnny           1627 May 05 14:32 test_decorators.py
+#
+class InfoSvnCommand:
+    # the '0?' part in the middle is an indication of whether the resource is
+    # locked, see 'svn help ls'
+    lspattern = re.compile(
+        r'^ *(?P<rev>\d+) +(?P<author>.+?) +(0? *(?P<size>\d+))? '
+            r'*(?P<date>\w+ +\d{2} +[\d:]+) +(?P<file>.*)$')
+    def __init__(self, line):
+        # this is a typical line from 'svn ls http://...'
+        #_    1127      jum        0 Jul 13 15:28 branch/
+        match = self.lspattern.match(line)
+        data = match.groupdict()
+        self._name = data['file']
+        if self._name[-1] == '/':
+            self._name = self._name[:-1]
+            self.kind = 'dir'
+        else:
+            self.kind = 'file'
+        #self.has_props = l.pop(0) == 'P'
+        self.created_rev = int(data['rev'])
+        self.last_author = data['author']
+        self.size = data['size'] and int(data['size']) or 0
+        self.mtime = parse_time_with_missing_year(data['date'])
+        self.time = self.mtime * 1000000
+
+    def __eq__(self, other):
+        return self.__dict__ == other.__dict__
+
+
+#____________________________________________________
+#
+# helper functions
+#____________________________________________________
+def parse_time_with_missing_year(timestr):
+    """ analyze the time part from a single line of "svn ls -v"
+    the svn output doesn't show the year makes the 'timestr'
+    ambigous.
+    """
+    import calendar
+    t_now = time.gmtime()
+
+    tparts = timestr.split()
+    month = time.strptime(tparts.pop(0), '%b')[1]
+    day = time.strptime(tparts.pop(0), '%d')[2]
+    last = tparts.pop(0) # year or hour:minute
+    try:
+        if ":" in last:
+            raise ValueError()
+        year = time.strptime(last, '%Y')[0]
+        hour = minute = 0
+    except ValueError:
+        hour, minute = time.strptime(last, '%H:%M')[3:5]
+        year = t_now[0]
+
+        t_result = (year, month, day, hour, minute, 0,0,0,0)
+        if t_result > t_now:
+            year -= 1
+    t_result = (year, month, day, hour, minute, 0,0,0,0)
+    return calendar.timegm(t_result)
+
+class PathEntry:
+    def __init__(self, ppart):
+        self.strpath = ppart.firstChild.nodeValue.encode('UTF-8')
+        self.action = ppart.getAttribute('action').encode('UTF-8')
+        if self.action == 'A':
+            self.copyfrom_path = ppart.getAttribute('copyfrom-path').encode('UTF-8')
+            if self.copyfrom_path:
+                self.copyfrom_rev = int(ppart.getAttribute('copyfrom-rev'))
+
diff --git a/tools/third_party/py/py/_path/svnwc.py b/tools/third_party/py/py/_path/svnwc.py
new file mode 100644
index 0000000..3138dd8
--- /dev/null
+++ b/tools/third_party/py/py/_path/svnwc.py
@@ -0,0 +1,1240 @@
+"""
+svn-Command based Implementation of a Subversion WorkingCopy Path.
+
+  SvnWCCommandPath  is the main class.
+
+"""
+
+import os, sys, time, re, calendar
+import py
+import subprocess
+from py._path import common
+
+#-----------------------------------------------------------
+# Caching latest repository revision and repo-paths
+# (getting them is slow with the current implementations)
+#
+# XXX make mt-safe
+#-----------------------------------------------------------
+
+class cache:
+    proplist = {}
+    info = {}
+    entries = {}
+    prop = {}
+
+class RepoEntry:
+    def __init__(self, url, rev, timestamp):
+        self.url = url
+        self.rev = rev
+        self.timestamp = timestamp
+
+    def __str__(self):
+        return "repo: %s;%s  %s" %(self.url, self.rev, self.timestamp)
+
+class RepoCache:
+    """ The Repocache manages discovered repository paths
+    and their revisions.  If inside a timeout the cache
+    will even return the revision of the root.
+    """
+    timeout = 20 # seconds after which we forget that we know the last revision
+
+    def __init__(self):
+        self.repos = []
+
+    def clear(self):
+        self.repos = []
+
+    def put(self, url, rev, timestamp=None):
+        if rev is None:
+            return
+        if timestamp is None:
+            timestamp = time.time()
+
+        for entry in self.repos:
+            if url == entry.url:
+                entry.timestamp = timestamp
+                entry.rev = rev
+                #print "set repo", entry
+                break
+        else:
+            entry = RepoEntry(url, rev, timestamp)
+            self.repos.append(entry)
+            #print "appended repo", entry
+
+    def get(self, url):
+        now = time.time()
+        for entry in self.repos:
+            if url.startswith(entry.url):
+                if now < entry.timestamp + self.timeout:
+                    #print "returning immediate Etrny", entry
+                    return entry.url, entry.rev
+                return entry.url, -1
+        return url, -1
+
+repositories = RepoCache()
+
+
+# svn support code
+
+ALLOWED_CHARS = "_ -/\\=$.~+%" #add characters as necessary when tested
+if sys.platform == "win32":
+    ALLOWED_CHARS += ":"
+ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:'
+
+def _getsvnversion(ver=[]):
+    try:
+        return ver[0]
+    except IndexError:
+        v = py.process.cmdexec("svn -q --version")
+        v.strip()
+        v = '.'.join(v.split('.')[:2])
+        ver.append(v)
+        return v
+
+def _escape_helper(text):
+    text = str(text)
+    if sys.platform != 'win32':
+        text = str(text).replace('$', '\\$')
+    return text
+
+def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS):
+    for c in str(text):
+        if c.isalnum():
+            continue
+        if c in allowed_chars:
+            continue
+        return True
+    return False
+
+def checkbadchars(url):
+    # (hpk) not quite sure about the exact purpose, guido w.?
+    proto, uri = url.split("://", 1)
+    if proto != "file":
+        host, uripath = uri.split('/', 1)
+        # only check for bad chars in the non-protocol parts
+        if (_check_for_bad_chars(host, ALLOWED_CHARS_HOST) \
+            or _check_for_bad_chars(uripath, ALLOWED_CHARS)):
+            raise ValueError("bad char in %r" % (url, ))
+
+
+#_______________________________________________________________
+
+class SvnPathBase(common.PathBase):
+    """ Base implementation for SvnPath implementations. """
+    sep = '/'
+
+    def _geturl(self):
+        return self.strpath
+    url = property(_geturl, None, None, "url of this svn-path.")
+
+    def __str__(self):
+        """ return a string representation (including rev-number) """
+        return self.strpath
+
+    def __hash__(self):
+        return hash(self.strpath)
+
+    def new(self, **kw):
+        """ create a modified version of this path. A 'rev' argument
+            indicates a new revision.
+            the following keyword arguments modify various path parts::
+
+              http://host.com/repo/path/file.ext
+              |-----------------------|          dirname
+                                        |------| basename
+                                        |--|     purebasename
+                                            |--| ext
+        """
+        obj = object.__new__(self.__class__)
+        obj.rev = kw.get('rev', self.rev)
+        obj.auth = kw.get('auth', self.auth)
+        dirname, basename, purebasename, ext = self._getbyspec(
+             "dirname,basename,purebasename,ext")
+        if 'basename' in kw:
+            if 'purebasename' in kw or 'ext' in kw:
+                raise ValueError("invalid specification %r" % kw)
+        else:
+            pb = kw.setdefault('purebasename', purebasename)
+            ext = kw.setdefault('ext', ext)
+            if ext and not ext.startswith('.'):
+                ext = '.' + ext
+            kw['basename'] = pb + ext
+
+        kw.setdefault('dirname', dirname)
+        kw.setdefault('sep', self.sep)
+        if kw['basename']:
+            obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw
+        else:
+            obj.strpath = "%(dirname)s" % kw
+        return obj
+
+    def _getbyspec(self, spec):
+        """ get specified parts of the path.  'arg' is a string
+            with comma separated path parts. The parts are returned
+            in exactly the order of the specification.
+
+            you may specify the following parts:
+
+            http://host.com/repo/path/file.ext
+            |-----------------------|          dirname
+                                      |------| basename
+                                      |--|     purebasename
+                                          |--| ext
+        """
+        res = []
+        parts = self.strpath.split(self.sep)
+        for name in spec.split(','):
+            name = name.strip()
+            if name == 'dirname':
+                res.append(self.sep.join(parts[:-1]))
+            elif name == 'basename':
+                res.append(parts[-1])
+            else:
+                basename = parts[-1]
+                i = basename.rfind('.')
+                if i == -1:
+                    purebasename, ext = basename, ''
+                else:
+                    purebasename, ext = basename[:i], basename[i:]
+                if name == 'purebasename':
+                    res.append(purebasename)
+                elif name == 'ext':
+                    res.append(ext)
+                else:
+                    raise NameError("Don't know part %r" % name)
+        return res
+
+    def __eq__(self, other):
+        """ return true if path and rev attributes each match """
+        return (str(self) == str(other) and
+               (self.rev == other.rev or self.rev == other.rev))
+
+    def __ne__(self, other):
+        return not self == other
+
+    def join(self, *args):
+        """ return a new Path (with the same revision) which is composed
+            of the self Path followed by 'args' path components.
+        """
+        if not args:
+            return self
+
+        args = tuple([arg.strip(self.sep) for arg in args])
+        parts = (self.strpath, ) + args
+        newpath = self.__class__(self.sep.join(parts), self.rev, self.auth)
+        return newpath
+
+    def propget(self, name):
+        """ return the content of the given property. """
+        value = self._propget(name)
+        return value
+
+    def proplist(self):
+        """ list all property names. """
+        content = self._proplist()
+        return content
+
+    def size(self):
+        """ Return the size of the file content of the Path. """
+        return self.info().size
+
+    def mtime(self):
+        """ Return the last modification time of the file. """
+        return self.info().mtime
+
+    # shared help methods
+
+    def _escape(self, cmd):
+        return _escape_helper(cmd)
+
+
+    #def _childmaxrev(self):
+    #    """ return maximum revision number of childs (or self.rev if no childs) """
+    #    rev = self.rev
+    #    for name, info in self._listdir_nameinfo():
+    #        rev = max(rev, info.created_rev)
+    #    return rev
+
+    #def _getlatestrevision(self):
+    #    """ return latest repo-revision for this path. """
+    #    url = self.strpath
+    #    path = self.__class__(url, None)
+    #
+    #    # we need a long walk to find the root-repo and revision
+    #    while 1:
+    #        try:
+    #            rev = max(rev, path._childmaxrev())
+    #            previous = path
+    #            path = path.dirpath()
+    #        except (IOError, process.cmdexec.Error):
+    #            break
+    #    if rev is None:
+    #        raise IOError, "could not determine newest repo revision for %s" % self
+    #    return rev
+
+    class Checkers(common.Checkers):
+        def dir(self):
+            try:
+                return self.path.info().kind == 'dir'
+            except py.error.Error:
+                return self._listdirworks()
+
+        def _listdirworks(self):
+            try:
+                self.path.listdir()
+            except py.error.ENOENT:
+                return False
+            else:
+                return True
+
+        def file(self):
+            try:
+                return self.path.info().kind == 'file'
+            except py.error.ENOENT:
+                return False
+
+        def exists(self):
+            try:
+                return self.path.info()
+            except py.error.ENOENT:
+                return self._listdirworks()
+
+def parse_apr_time(timestr):
+    i = timestr.rfind('.')
+    if i == -1:
+        raise ValueError("could not parse %s" % timestr)
+    timestr = timestr[:i]
+    parsedtime = time.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
+    return time.mktime(parsedtime)
+
+class PropListDict(dict):
+    """ a Dictionary which fetches values (InfoSvnCommand instances) lazily"""
+    def __init__(self, path, keynames):
+        dict.__init__(self, [(x, None) for x in keynames])
+        self.path = path
+
+    def __getitem__(self, key):
+        value = dict.__getitem__(self, key)
+        if value is None:
+            value = self.path.propget(key)
+            dict.__setitem__(self, key, value)
+        return value
+
+def fixlocale():
+    if sys.platform != 'win32':
+        return 'LC_ALL=C '
+    return ''
+
+# some nasty chunk of code to solve path and url conversion and quoting issues
+ILLEGAL_CHARS = '* | \\ / : < > ? \t \n \x0b \x0c \r'.split(' ')
+if os.sep in ILLEGAL_CHARS:
+    ILLEGAL_CHARS.remove(os.sep)
+ISWINDOWS = sys.platform == 'win32'
+_reg_allow_disk = re.compile(r'^([a-z]\:\\)?[^:]+$', re.I)
+def _check_path(path):
+    illegal = ILLEGAL_CHARS[:]
+    sp = path.strpath
+    if ISWINDOWS:
+        illegal.remove(':')
+        if not _reg_allow_disk.match(sp):
+            raise ValueError('path may not contain a colon (:)')
+    for char in sp:
+        if char not in string.printable or char in illegal:
+            raise ValueError('illegal character %r in path' % (char,))
+
+def path_to_fspath(path, addat=True):
+    _check_path(path)
+    sp = path.strpath
+    if addat and path.rev != -1:
+        sp = '%s@%s' % (sp, path.rev)
+    elif addat:
+        sp = '%s@HEAD' % (sp,)
+    return sp
+
+def url_from_path(path):
+    fspath = path_to_fspath(path, False)
+    from urllib import quote
+    if ISWINDOWS:
+        match = _reg_allow_disk.match(fspath)
+        fspath = fspath.replace('\\', '/')
+        if match.group(1):
+            fspath = '/%s%s' % (match.group(1).replace('\\', '/'),
+                                quote(fspath[len(match.group(1)):]))
+        else:
+            fspath = quote(fspath)
+    else:
+        fspath = quote(fspath)
+    if path.rev != -1:
+        fspath = '%s@%s' % (fspath, path.rev)
+    else:
+        fspath = '%s@HEAD' % (fspath,)
+    return 'file://%s' % (fspath,)
+
+class SvnAuth(object):
+    """ container for auth information for Subversion """
+    def __init__(self, username, password, cache_auth=True, interactive=True):
+        self.username = username
+        self.password = password
+        self.cache_auth = cache_auth
+        self.interactive = interactive
+
+    def makecmdoptions(self):
+        uname = self.username.replace('"', '\\"')
+        passwd = self.password.replace('"', '\\"')
+        ret = []
+        if uname:
+            ret.append('--username="%s"' % (uname,))
+        if passwd:
+            ret.append('--password="%s"' % (passwd,))
+        if not self.cache_auth:
+            ret.append('--no-auth-cache')
+        if not self.interactive:
+            ret.append('--non-interactive')
+        return ' '.join(ret)
+
+    def __str__(self):
+        return "<SvnAuth username=%s ...>" %(self.username,)
+
+rex_blame = re.compile(r'\s*(\d+)\s*(\S+) (.*)')
+
+class SvnWCCommandPath(common.PathBase):
+    """ path implementation offering access/modification to svn working copies.
+        It has methods similar to the functions in os.path and similar to the
+        commands of the svn client.
+    """
+    sep = os.sep
+
+    def __new__(cls, wcpath=None, auth=None):
+        self = object.__new__(cls)
+        if isinstance(wcpath, cls):
+            if wcpath.__class__ == cls:
+                return wcpath
+            wcpath = wcpath.localpath
+        if _check_for_bad_chars(str(wcpath),
+                                          ALLOWED_CHARS):
+            raise ValueError("bad char in wcpath %s" % (wcpath, ))
+        self.localpath = py.path.local(wcpath)
+        self.auth = auth
+        return self
+
+    strpath = property(lambda x: str(x.localpath), None, None, "string path")
+    rev = property(lambda x: x.info(usecache=0).rev, None, None, "revision")
+
+    def __eq__(self, other):
+        return self.localpath == getattr(other, 'localpath', None)
+
+    def _geturl(self):
+        if getattr(self, '_url', None) is None:
+            info = self.info()
+            self._url = info.url #SvnPath(info.url, info.rev)
+        assert isinstance(self._url, py.builtin._basestring)
+        return self._url
+
+    url = property(_geturl, None, None, "url of this WC item")
+
+    def _escape(self, cmd):
+        return _escape_helper(cmd)
+
+    def dump(self, obj):
+        """ pickle object into path location"""
+        return self.localpath.dump(obj)
+
+    def svnurl(self):
+        """ return current SvnPath for this WC-item. """
+        info = self.info()
+        return py.path.svnurl(info.url)
+
+    def __repr__(self):
+        return "svnwc(%r)" % (self.strpath) # , self._url)
+
+    def __str__(self):
+        return str(self.localpath)
+
+    def _makeauthoptions(self):
+        if self.auth is None:
+            return ''
+        return self.auth.makecmdoptions()
+
+    def _authsvn(self, cmd, args=None):
+        args = args and list(args) or []
+        args.append(self._makeauthoptions())
+        return self._svn(cmd, *args)
+
+    def _svn(self, cmd, *args):
+        l = ['svn %s' % cmd]
+        args = [self._escape(item) for item in args]
+        l.extend(args)
+        l.append('"%s"' % self._escape(self.strpath))
+        # try fixing the locale because we can't otherwise parse
+        string = fixlocale() + " ".join(l)
+        try:
+            try:
+                key = 'LC_MESSAGES'
+                hold = os.environ.get(key)
+                os.environ[key] = 'C'
+                out = py.process.cmdexec(string)
+            finally:
+                if hold:
+                    os.environ[key] = hold
+                else:
+                    del os.environ[key]
+        except py.process.cmdexec.Error:
+            e = sys.exc_info()[1]
+            strerr = e.err.lower()
+            if strerr.find('not found') != -1:
+                raise py.error.ENOENT(self)
+            elif strerr.find("E200009:") != -1:
+                raise py.error.ENOENT(self)
+            if (strerr.find('file exists') != -1 or
+                strerr.find('file already exists') != -1 or
+                strerr.find('w150002:') != -1 or
+                strerr.find("can't create directory") != -1):
+                raise py.error.EEXIST(strerr) #self)
+            raise
+        return out
+
+    def switch(self, url):
+        """ switch to given URL. """
+        self._authsvn('switch', [url])
+
+    def checkout(self, url=None, rev=None):
+        """ checkout from url to local wcpath. """
+        args = []
+        if url is None:
+            url = self.url
+        if rev is None or rev == -1:
+            if (sys.platform != 'win32' and
+                    _getsvnversion() == '1.3'):
+                url += "@HEAD"
+        else:
+            if _getsvnversion() == '1.3':
+                url += "@%d" % rev
+            else:
+                args.append('-r' + str(rev))
+        args.append(url)
+        self._authsvn('co', args)
+
+    def update(self, rev='HEAD', interactive=True):
+        """ update working copy item to given revision. (None -> HEAD). """
+        opts = ['-r', rev]
+        if not interactive:
+            opts.append("--non-interactive")
+        self._authsvn('up', opts)
+
+    def write(self, content, mode='w'):
+        """ write content into local filesystem wc. """
+        self.localpath.write(content, mode)
+
+    def dirpath(self, *args):
+        """ return the directory Path of the current Path. """
+        return self.__class__(self.localpath.dirpath(*args), auth=self.auth)
+
+    def _ensuredirs(self):
+        parent = self.dirpath()
+        if parent.check(dir=0):
+            parent._ensuredirs()
+        if self.check(dir=0):
+            self.mkdir()
+        return self
+
+    def ensure(self, *args, **kwargs):
+        """ ensure that an args-joined path exists (by default as
+            a file). if you specify a keyword argument 'directory=True'
+            then the path is forced  to be a directory path.
+        """
+        p = self.join(*args)
+        if p.check():
+            if p.check(versioned=False):
+                p.add()
+            return p
+        if kwargs.get('dir', 0):
+            return p._ensuredirs()
+        parent = p.dirpath()
+        parent._ensuredirs()
+        p.write("")
+        p.add()
+        return p
+
+    def mkdir(self, *args):
+        """ create & return the directory joined with args. """
+        if args:
+            return self.join(*args).mkdir()
+        else:
+            self._svn('mkdir')
+            return self
+
+    def add(self):
+        """ add ourself to svn """
+        self._svn('add')
+
+    def remove(self, rec=1, force=1):
+        """ remove a file or a directory tree. 'rec'ursive is
+            ignored and considered always true (because of
+            underlying svn semantics.
+        """
+        assert rec, "svn cannot remove non-recursively"
+        if not self.check(versioned=True):
+            # not added to svn (anymore?), just remove
+            py.path.local(self).remove()
+            return
+        flags = []
+        if force:
+            flags.append('--force')
+        self._svn('remove', *flags)
+
+    def copy(self, target):
+        """ copy path to target."""
+        py.process.cmdexec("svn copy %s %s" %(str(self), str(target)))
+
+    def rename(self, target):
+        """ rename this path to target. """
+        py.process.cmdexec("svn move --force %s %s" %(str(self), str(target)))
+
+    def lock(self):
+        """ set a lock (exclusive) on the resource """
+        out = self._authsvn('lock').strip()
+        if not out:
+            # warning or error, raise exception
+            raise ValueError("unknown error in svn lock command")
+
+    def unlock(self):
+        """ unset a previously set lock """
+        out = self._authsvn('unlock').strip()
+        if out.startswith('svn:'):
+            # warning or error, raise exception
+            raise Exception(out[4:])
+
+    def cleanup(self):
+        """ remove any locks from the resource """
+        # XXX should be fixed properly!!!
+        try:
+            self.unlock()
+        except:
+            pass
+
+    def status(self, updates=0, rec=0, externals=0):
+        """ return (collective) Status object for this file. """
+        # http://svnbook.red-bean.com/book.html#svn-ch-3-sect-4.3.1
+        #             2201     2192        jum   test
+        # XXX
+        if externals:
+            raise ValueError("XXX cannot perform status() "
+                             "on external items yet")
+        else:
+            #1.2 supports: externals = '--ignore-externals'
+            externals = ''
+        if rec:
+            rec= ''
+        else:
+            rec = '--non-recursive'
+
+        # XXX does not work on all subversion versions
+        #if not externals:
+        #    externals = '--ignore-externals'
+
+        if updates:
+            updates = '-u'
+        else:
+            updates = ''
+
+        try:
+            cmd = 'status -v --xml --no-ignore %s %s %s' % (
+                    updates, rec, externals)
+            out = self._authsvn(cmd)
+        except py.process.cmdexec.Error:
+            cmd = 'status -v --no-ignore %s %s %s' % (
+                    updates, rec, externals)
+            out = self._authsvn(cmd)
+            rootstatus = WCStatus(self).fromstring(out, self)
+        else:
+            rootstatus = XMLWCStatus(self).fromstring(out, self)
+        return rootstatus
+
+    def diff(self, rev=None):
+        """ return a diff of the current path against revision rev (defaulting
+            to the last one).
+        """
+        args = []
+        if rev is not None:
+            args.append("-r %d" % rev)
+        out = self._authsvn('diff', args)
+        return out
+
+    def blame(self):
+        """ return a list of tuples of three elements:
+            (revision, commiter, line)
+        """
+        out = self._svn('blame')
+        result = []
+        blamelines = out.splitlines()
+        reallines = py.path.svnurl(self.url).readlines()
+        for i, (blameline, line) in enumerate(
+                zip(blamelines, reallines)):
+            m = rex_blame.match(blameline)
+            if not m:
+                raise ValueError("output line %r of svn blame does not match "
+                                 "expected format" % (line, ))
+            rev, name, _ = m.groups()
+            result.append((int(rev), name, line))
+        return result
+
+    _rex_commit = re.compile(r'.*Committed revision (\d+)\.$', re.DOTALL)
+    def commit(self, msg='', rec=1):
+        """ commit with support for non-recursive commits """
+        # XXX i guess escaping should be done better here?!?
+        cmd = 'commit -m "%s" --force-log' % (msg.replace('"', '\\"'),)
+        if not rec:
+            cmd += ' -N'
+        out = self._authsvn(cmd)
+        try:
+            del cache.info[self]
+        except KeyError:
+            pass
+        if out:
+            m = self._rex_commit.match(out)
+            return int(m.group(1))
+
+    def propset(self, name, value, *args):
+        """ set property name to value on this path. """
+        d = py.path.local.mkdtemp()
+        try:
+            p = d.join('value')
+            p.write(value)
+            self._svn('propset', name, '--file', str(p), *args)
+        finally:
+            d.remove()
+
+    def propget(self, name):
+        """ get property name on this path. """
+        res = self._svn('propget', name)
+        return res[:-1] # strip trailing newline
+
+    def propdel(self, name):
+        """ delete property name on this path. """
+        res = self._svn('propdel', name)
+        return res[:-1] # strip trailing newline
+
+    def proplist(self, rec=0):
+        """ return a mapping of property names to property values.
+If rec is True, then return a dictionary mapping sub-paths to such mappings.
+"""
+        if rec:
+            res = self._svn('proplist -R')
+            return make_recursive_propdict(self, res)
+        else:
+            res = self._svn('proplist')
+            lines = res.split('\n')
+            lines = [x.strip() for x in lines[1:]]
+            return PropListDict(self, lines)
+
+    def revert(self, rec=0):
+        """ revert the local changes of this path. if rec is True, do so
+recursively. """
+        if rec:
+            result = self._svn('revert -R')
+        else:
+            result = self._svn('revert')
+        return result
+
+    def new(self, **kw):
+        """ create a modified version of this path. A 'rev' argument
+            indicates a new revision.
+            the following keyword arguments modify various path parts:
+
+              http://host.com/repo/path/file.ext
+              |-----------------------|          dirname
+                                        |------| basename
+                                        |--|     purebasename
+                                            |--| ext
+        """
+        if kw:
+            localpath = self.localpath.new(**kw)
+        else:
+            localpath = self.localpath
+        return self.__class__(localpath, auth=self.auth)
+
+    def join(self, *args, **kwargs):
+        """ return a new Path (with the same revision) which is composed
+            of the self Path followed by 'args' path components.
+        """
+        if not args:
+            return self
+        localpath = self.localpath.join(*args, **kwargs)
+        return self.__class__(localpath, auth=self.auth)
+
+    def info(self, usecache=1):
+        """ return an Info structure with svn-provided information. """
+        info = usecache and cache.info.get(self)
+        if not info:
+            try:
+                output = self._svn('info')
+            except py.process.cmdexec.Error:
+                e = sys.exc_info()[1]
+                if e.err.find('Path is not a working copy directory') != -1:
+                    raise py.error.ENOENT(self, e.err)
+                elif e.err.find("is not under version control") != -1:
+                    raise py.error.ENOENT(self, e.err)
+                raise
+            # XXX SVN 1.3 has output on stderr instead of stdout (while it does
+            # return 0!), so a bit nasty, but we assume no output is output
+            # to stderr...
+            if (output.strip() == '' or
+                    output.lower().find('not a versioned resource') != -1):
+                raise py.error.ENOENT(self, output)
+            info = InfoSvnWCCommand(output)
+
+            # Can't reliably compare on Windows without access to win32api
+            if sys.platform != 'win32':
+                if info.path != self.localpath:
+                    raise py.error.ENOENT(self, "not a versioned resource:" +
+                            " %s != %s" % (info.path, self.localpath))
+            cache.info[self] = info
+        return info
+
+    def listdir(self, fil=None, sort=None):
+        """ return a sequence of Paths.
+
+        listdir will return either a tuple or a list of paths
+        depending on implementation choices.
+        """
+        if isinstance(fil, str):
+            fil = common.FNMatcher(fil)
+        # XXX unify argument naming with LocalPath.listdir
+        def notsvn(path):
+            return path.basename != '.svn'
+
+        paths = []
+        for localpath in self.localpath.listdir(notsvn):
+            p = self.__class__(localpath, auth=self.auth)
+            if notsvn(p) and (not fil or fil(p)):
+                paths.append(p)
+        self._sortlist(paths, sort)
+        return paths
+
+    def open(self, mode='r'):
+        """ return an opened file with the given mode. """
+        return open(self.strpath, mode)
+
+    def _getbyspec(self, spec):
+        return self.localpath._getbyspec(spec)
+
+    class Checkers(py.path.local.Checkers):
+        def __init__(self, path):
+            self.svnwcpath = path
+            self.path = path.localpath
+        def versioned(self):
+            try:
+                s = self.svnwcpath.info()
+            except (py.error.ENOENT, py.error.EEXIST):
+                return False
+            except py.process.cmdexec.Error:
+                e = sys.exc_info()[1]
+                if e.err.find('is not a working copy')!=-1:
+                    return False
+                if e.err.lower().find('not a versioned resource') != -1:
+                    return False
+                raise
+            else:
+                return True
+
+    def log(self, rev_start=None, rev_end=1, verbose=False):
+        """ return a list of LogEntry instances for this path.
+rev_start is the starting revision (defaulting to the first one).
+rev_end is the last revision (defaulting to HEAD).
+if verbose is True, then the LogEntry instances also know which files changed.
+"""
+        assert self.check()   # make it simpler for the pipe
+        rev_start = rev_start is None and "HEAD" or rev_start
+        rev_end = rev_end is None and "HEAD" or rev_end
+        if rev_start == "HEAD" and rev_end == 1:
+                rev_opt = ""
+        else:
+            rev_opt = "-r %s:%s" % (rev_start, rev_end)
+        verbose_opt = verbose and "-v" or ""
+        locale_env = fixlocale()
+        # some blather on stderr
+        auth_opt = self._makeauthoptions()
+        #stdin, stdout, stderr  = os.popen3(locale_env +
+        #                                   'svn log --xml %s %s %s "%s"' % (
+        #                                    rev_opt, verbose_opt, auth_opt,
+        #                                    self.strpath))
+        cmd = locale_env + 'svn log --xml %s %s %s "%s"' % (
+            rev_opt, verbose_opt, auth_opt, self.strpath)
+
+        popen = subprocess.Popen(cmd,
+                    stdout=subprocess.PIPE,
+                    stderr=subprocess.PIPE,
+                    shell=True,
+        )
+        stdout, stderr = popen.communicate()
+        stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
+        minidom,ExpatError = importxml()
+        try:
+            tree = minidom.parseString(stdout)
+        except ExpatError:
+            raise ValueError('no such revision')
+        result = []
+        for logentry in filter(None, tree.firstChild.childNodes):
+            if logentry.nodeType == logentry.ELEMENT_NODE:
+                result.append(LogEntry(logentry))
+        return result
+
+    def size(self):
+        """ Return the size of the file content of the Path. """
+        return self.info().size
+
+    def mtime(self):
+        """ Return the last modification time of the file. """
+        return self.info().mtime
+
+    def __hash__(self):
+        return hash((self.strpath, self.__class__, self.auth))
+
+
+class WCStatus:
+    attrnames = ('modified','added', 'conflict', 'unchanged', 'external',
+                'deleted', 'prop_modified', 'unknown', 'update_available',
+                'incomplete', 'kindmismatch', 'ignored', 'locked', 'replaced'
+                )
+
+    def __init__(self, wcpath, rev=None, modrev=None, author=None):
+        self.wcpath = wcpath
+        self.rev = rev
+        self.modrev = modrev
+        self.author = author
+
+        for name in self.attrnames:
+            setattr(self, name, [])
+
+    def allpath(self, sort=True, **kw):
+        d = {}
+        for name in self.attrnames:
+            if name not in kw or kw[name]:
+                for path in getattr(self, name):
+                    d[path] = 1
+        l = d.keys()
+        if sort:
+            l.sort()
+        return l
+
+    # XXX a bit scary to assume there's always 2 spaces between username and
+    # path, however with win32 allowing spaces in user names there doesn't
+    # seem to be a more solid approach :(
+    _rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)')
+
+    def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
+        """ return a new WCStatus object from data 's'
+        """
+        rootstatus = WCStatus(rootwcpath, rev, modrev, author)
+        update_rev = None
+        for line in data.split('\n'):
+            if not line.strip():
+                continue
+            #print "processing %r" % line
+            flags, rest = line[:8], line[8:]
+            # first column
+            c0,c1,c2,c3,c4,c5,x6,c7 = flags
+            #if '*' in line:
+            #    print "flags", repr(flags), "rest", repr(rest)
+
+            if c0 in '?XI':
+                fn = line.split(None, 1)[1]
+                if c0 == '?':
+                    wcpath = rootwcpath.join(fn, abs=1)
+                    rootstatus.unknown.append(wcpath)
+                elif c0 == 'X':
+                    wcpath = rootwcpath.__class__(
+                        rootwcpath.localpath.join(fn, abs=1),
+                        auth=rootwcpath.auth)
+                    rootstatus.external.append(wcpath)
+                elif c0 == 'I':
+                    wcpath = rootwcpath.join(fn, abs=1)
+                    rootstatus.ignored.append(wcpath)
+
+                continue
+
+            #elif c0 in '~!' or c4 == 'S':
+            #    raise NotImplementedError("received flag %r" % c0)
+
+            m = WCStatus._rex_status.match(rest)
+            if not m:
+                if c7 == '*':
+                    fn = rest.strip()
+                    wcpath = rootwcpath.join(fn, abs=1)
+                    rootstatus.update_available.append(wcpath)
+                    continue
+                if line.lower().find('against revision:')!=-1:
+                    update_rev = int(rest.split(':')[1].strip())
+                    continue
+                if line.lower().find('status on external') > -1:
+                    # XXX not sure what to do here... perhaps we want to
+                    # store some state instead of just continuing, as right
+                    # now it makes the top-level external get added twice
+                    # (once as external, once as 'normal' unchanged item)
+                    # because of the way SVN presents external items
+                    continue
+                # keep trying
+                raise ValueError("could not parse line %r" % line)
+            else:
+                rev, modrev, author, fn = m.groups()
+            wcpath = rootwcpath.join(fn, abs=1)
+            #assert wcpath.check()
+            if c0 == 'M':
+                assert wcpath.check(file=1), "didn't expect a directory with changed content here"
+                rootstatus.modified.append(wcpath)
+            elif c0 == 'A' or c3 == '+' :
+                rootstatus.added.append(wcpath)
+            elif c0 == 'D':
+                rootstatus.deleted.append(wcpath)
+            elif c0 == 'C':
+                rootstatus.conflict.append(wcpath)
+            elif c0 == '~':
+                rootstatus.kindmismatch.append(wcpath)
+            elif c0 == '!':
+                rootstatus.incomplete.append(wcpath)
+            elif c0 == 'R':
+                rootstatus.replaced.append(wcpath)
+            elif not c0.strip():
+                rootstatus.unchanged.append(wcpath)
+            else:
+                raise NotImplementedError("received flag %r" % c0)
+
+            if c1 == 'M':
+                rootstatus.prop_modified.append(wcpath)
+            # XXX do we cover all client versions here?
+            if c2 == 'L' or c5 == 'K':
+                rootstatus.locked.append(wcpath)
+            if c7 == '*':
+                rootstatus.update_available.append(wcpath)
+
+            if wcpath == rootwcpath:
+                rootstatus.rev = rev
+                rootstatus.modrev = modrev
+                rootstatus.author = author
+                if update_rev:
+                    rootstatus.update_rev = update_rev
+                continue
+        return rootstatus
+    fromstring = staticmethod(fromstring)
+
+class XMLWCStatus(WCStatus):
+    def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
+        """ parse 'data' (XML string as outputted by svn st) into a status obj
+        """
+        # XXX for externals, the path is shown twice: once
+        # with external information, and once with full info as if
+        # the item was a normal non-external... the current way of
+        # dealing with this issue is by ignoring it - this does make
+        # externals appear as external items as well as 'normal',
+        # unchanged ones in the status object so this is far from ideal
+        rootstatus = WCStatus(rootwcpath, rev, modrev, author)
+        update_rev = None
+        minidom, ExpatError = importxml()
+        try:
+            doc = minidom.parseString(data)
+        except ExpatError:
+            e = sys.exc_info()[1]
+            raise ValueError(str(e))
+        urevels = doc.getElementsByTagName('against')
+        if urevels:
+            rootstatus.update_rev = urevels[-1].getAttribute('revision')
+        for entryel in doc.getElementsByTagName('entry'):
+            path = entryel.getAttribute('path')
+            statusel = entryel.getElementsByTagName('wc-status')[0]
+            itemstatus = statusel.getAttribute('item')
+
+            if itemstatus == 'unversioned':
+                wcpath = rootwcpath.join(path, abs=1)
+                rootstatus.unknown.append(wcpath)
+                continue
+            elif itemstatus == 'external':
+                wcpath = rootwcpath.__class__(
+                    rootwcpath.localpath.join(path, abs=1),
+                    auth=rootwcpath.auth)
+                rootstatus.external.append(wcpath)
+                continue
+            elif itemstatus == 'ignored':
+                wcpath = rootwcpath.join(path, abs=1)
+                rootstatus.ignored.append(wcpath)
+                continue
+            elif itemstatus == 'incomplete':
+                wcpath = rootwcpath.join(path, abs=1)
+                rootstatus.incomplete.append(wcpath)
+                continue
+
+            rev = statusel.getAttribute('revision')
+            if itemstatus == 'added' or itemstatus == 'none':
+                rev = '0'
+                modrev = '?'
+                author = '?'
+                date = ''
+            elif itemstatus == "replaced":
+                pass
+            else:
+                #print entryel.toxml()
+                commitel = entryel.getElementsByTagName('commit')[0]
+                if commitel:
+                    modrev = commitel.getAttribute('revision')
+                    author = ''
+                    author_els = commitel.getElementsByTagName('author')
+                    if author_els:
+                        for c in author_els[0].childNodes:
+                            author += c.nodeValue
+                    date = ''
+                    for c in commitel.getElementsByTagName('date')[0]\
+                            .childNodes:
+                        date += c.nodeValue
+
+            wcpath = rootwcpath.join(path, abs=1)
+
+            assert itemstatus != 'modified' or wcpath.check(file=1), (
+                'did\'t expect a directory with changed content here')
+
+            itemattrname = {
+                'normal': 'unchanged',
+                'unversioned': 'unknown',
+                'conflicted': 'conflict',
+                'none': 'added',
+            }.get(itemstatus, itemstatus)
+
+            attr = getattr(rootstatus, itemattrname)
+            attr.append(wcpath)
+
+            propsstatus = statusel.getAttribute('props')
+            if propsstatus not in ('none', 'normal'):
+                rootstatus.prop_modified.append(wcpath)
+
+            if wcpath == rootwcpath:
+                rootstatus.rev = rev
+                rootstatus.modrev = modrev
+                rootstatus.author = author
+                rootstatus.date = date
+
+            # handle repos-status element (remote info)
+            rstatusels = entryel.getElementsByTagName('repos-status')
+            if rstatusels:
+                rstatusel = rstatusels[0]
+                ritemstatus = rstatusel.getAttribute('item')
+                if ritemstatus in ('added', 'modified'):
+                    rootstatus.update_available.append(wcpath)
+
+            lockels = entryel.getElementsByTagName('lock')
+            if len(lockels):
+                rootstatus.locked.append(wcpath)
+
+        return rootstatus
+    fromstring = staticmethod(fromstring)
+
+class InfoSvnWCCommand:
+    def __init__(self, output):
+        # Path: test
+        # URL: http://codespeak.net/svn/std.path/trunk/dist/std.path/test
+        # Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
+        # Revision: 2151
+        # Node Kind: directory
+        # Schedule: normal
+        # Last Changed Author: hpk
+        # Last Changed Rev: 2100
+        # Last Changed Date: 2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)
+        # Properties Last Updated: 2003-11-03 14:47:48 +0100 (Mon, 03 Nov 2003)
+
+        d = {}
+        for line in output.split('\n'):
+            if not line.strip():
+                continue
+            key, value = line.split(':', 1)
+            key = key.lower().replace(' ', '')
+            value = value.strip()
+            d[key] = value
+        try:
+            self.url = d['url']
+        except KeyError:
+            raise  ValueError("Not a versioned resource")
+            #raise ValueError, "Not a versioned resource %r" % path
+        self.kind = d['nodekind'] == 'directory' and 'dir' or d['nodekind']
+        try:
+            self.rev = int(d['revision'])
+        except KeyError:
+            self.rev = None
+
+        self.path = py.path.local(d['path'])
+        self.size = self.path.size()
+        if 'lastchangedrev' in d:
+            self.created_rev = int(d['lastchangedrev'])
+        if 'lastchangedauthor' in d:
+            self.last_author = d['lastchangedauthor']
+        if 'lastchangeddate' in d:
+            self.mtime = parse_wcinfotime(d['lastchangeddate'])
+            self.time = self.mtime * 1000000
+
+    def __eq__(self, other):
+        return self.__dict__ == other.__dict__
+
+def parse_wcinfotime(timestr):
+    """ Returns seconds since epoch, UTC. """
+    # example: 2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)
+    m = re.match(r'(\d+-\d+-\d+ \d+:\d+:\d+) ([+-]\d+) .*', timestr)
+    if not m:
+        raise ValueError("timestring %r does not match" % timestr)
+    timestr, timezone = m.groups()
+    # do not handle timezone specially, return value should be UTC
+    parsedtime = time.strptime(timestr, "%Y-%m-%d %H:%M:%S")
+    return calendar.timegm(parsedtime)
+
+def make_recursive_propdict(wcroot,
+                            output,
+                            rex = re.compile("Properties on '(.*)':")):
+    """ Return a dictionary of path->PropListDict mappings. """
+    lines = [x for x in output.split('\n') if x]
+    pdict = {}
+    while lines:
+        line = lines.pop(0)
+        m = rex.match(line)
+        if not m:
+            raise ValueError("could not parse propget-line: %r" % line)
+        path = m.groups()[0]
+        wcpath = wcroot.join(path, abs=1)
+        propnames = []
+        while lines and lines[0].startswith('  '):
+            propname = lines.pop(0).strip()
+            propnames.append(propname)
+        assert propnames, "must have found properties!"
+        pdict[wcpath] = PropListDict(wcpath, propnames)
+    return pdict
+
+
+def importxml(cache=[]):
+    if cache:
+        return cache
+    from xml.dom import minidom
+    from xml.parsers.expat import ExpatError
+    cache.extend([minidom, ExpatError])
+    return cache
+
+class LogEntry:
+    def __init__(self, logentry):
+        self.rev = int(logentry.getAttribute('revision'))
+        for lpart in filter(None, logentry.childNodes):
+            if lpart.nodeType == lpart.ELEMENT_NODE:
+                if lpart.nodeName == 'author':
+                    self.author = lpart.firstChild.nodeValue
+                elif lpart.nodeName == 'msg':
+                    if lpart.firstChild:
+                        self.msg = lpart.firstChild.nodeValue
+                    else:
+                        self.msg = ''
+                elif lpart.nodeName == 'date':
+                    #2003-07-29T20:05:11.598637Z
+                    timestr = lpart.firstChild.nodeValue
+                    self.date = parse_apr_time(timestr)
+                elif lpart.nodeName == 'paths':
+                    self.strpaths = []
+                    for ppart in filter(None, lpart.childNodes):
+                        if ppart.nodeType == ppart.ELEMENT_NODE:
+                            self.strpaths.append(PathEntry(ppart))
+    def __repr__(self):
+        return '<Logentry rev=%d author=%s date=%s>' % (
+            self.rev, self.author, self.date)
+
+
diff --git a/tools/py/py/_process/__init__.py b/tools/third_party/py/py/_process/__init__.py
similarity index 100%
rename from tools/py/py/_process/__init__.py
rename to tools/third_party/py/py/_process/__init__.py
diff --git a/tools/py/py/_process/cmdexec.py b/tools/third_party/py/py/_process/cmdexec.py
similarity index 100%
rename from tools/py/py/_process/cmdexec.py
rename to tools/third_party/py/py/_process/cmdexec.py
diff --git a/tools/py/py/_process/forkedfunc.py b/tools/third_party/py/py/_process/forkedfunc.py
similarity index 100%
rename from tools/py/py/_process/forkedfunc.py
rename to tools/third_party/py/py/_process/forkedfunc.py
diff --git a/tools/py/py/_process/killproc.py b/tools/third_party/py/py/_process/killproc.py
similarity index 100%
rename from tools/py/py/_process/killproc.py
rename to tools/third_party/py/py/_process/killproc.py
diff --git a/tools/third_party/py/py/_std.py b/tools/third_party/py/py/_std.py
new file mode 100644
index 0000000..74d4367
--- /dev/null
+++ b/tools/third_party/py/py/_std.py
@@ -0,0 +1,26 @@
+import sys
+import warnings
+
+
+class PyStdIsDeprecatedWarning(DeprecationWarning):
+    pass
+
+
+class Std(object):
+    """ makes top-level python modules available as an attribute,
+        importing them on first access.
+    """
+
+    def __init__(self):
+        self.__dict__ = sys.modules
+
+    def __getattr__(self, name):
+        warnings.warn("py.std is deprecated, plase import %s directly" % name,
+                      category=PyStdIsDeprecatedWarning)
+        try:
+            m = __import__(name)
+        except ImportError:
+            raise AttributeError("py.std: could not import %s" % name)
+        return m
+
+std = Std()
diff --git a/tools/pytest/_pytest/vendored_packages/__init__.py b/tools/third_party/py/py/_vendored_packages/__init__.py
similarity index 100%
rename from tools/pytest/_pytest/vendored_packages/__init__.py
rename to tools/third_party/py/py/_vendored_packages/__init__.py
diff --git a/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/DESCRIPTION.rst b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/DESCRIPTION.rst
new file mode 100644
index 0000000..5482220
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/DESCRIPTION.rst
@@ -0,0 +1,87 @@
+Welcome to apipkg!
+------------------------
+
+With apipkg you can control the exported namespace of a
+python package and greatly reduce the number of imports for your users.
+It is a `small pure python module`_ that works on virtually all Python
+versions, including CPython2.3 to Python3.1, Jython and PyPy.  It co-operates
+well with Python's ``help()`` system, custom importers (PEP302) and common
+command line completion tools.
+
+Usage is very simple: you can require 'apipkg' as a dependency or you
+can copy paste the <200 Lines of code into your project.
+
+
+Tutorial example
+-------------------
+
+Here is a simple ``mypkg`` package that specifies one namespace
+and exports two objects imported from different modules::
+
+    # mypkg/__init__.py
+    import apipkg
+    apipkg.initpkg(__name__, {
+        'path': {
+            'Class1': "_mypkg.somemodule:Class1",
+            'clsattr': "_mypkg.othermodule:Class2.attr",
+        }
+    }
+
+The package is initialized with a dictionary as namespace.
+
+You need to create a ``_mypkg`` package with a ``somemodule.py``
+and ``othermodule.py`` containing the respective classes.
+The ``_mypkg`` is not special - it's a completely
+regular python package.
+
+Namespace dictionaries contain ``name: value`` mappings
+where the value may be another namespace dictionary or
+a string specifying an import location.  On accessing
+an namespace attribute an import will be performed::
+
+    >>> import mypkg
+    >>> mypkg.path
+    <ApiModule 'mypkg.path'>
+    >>> mypkg.path.Class1   # '_mypkg.somemodule' gets imported now
+    <class _mypkg.somemodule.Class1 at 0xb7d428fc>
+    >>> mypkg.path.clsattr  # '_mypkg.othermodule' gets imported now
+    4 # the value of _mypkg.othermodule.Class2.attr
+
+The ``mypkg.path`` namespace and its two entries are
+loaded when they are accessed.   This means:
+
+* lazy loading - only what is actually needed is ever loaded
+
+* only the root "mypkg" ever needs to be imported to get
+  access to the complete functionality.
+
+* the underlying modules are also accessible, for example::
+
+    from mypkg.sub import Class1
+
+
+Including apipkg in your package
+--------------------------------------
+
+If you don't want to add an ``apipkg`` dependency to your package you
+can copy the `apipkg.py`_ file somewhere to your own package,
+for example ``_mypkg/apipkg.py`` in the above example.  You
+then import the ``initpkg`` function from that new place and
+are good to go.
+
+.. _`small pure python module`:
+.. _`apipkg.py`: http://bitbucket.org/hpk42/apipkg/src/tip/apipkg.py
+
+Feedback?
+-----------------------
+
+If you have questions you are welcome to
+
+* join the #pylib channel on irc.freenode.net
+* subscribe to the http://codespeak.net/mailman/listinfo/py-dev list.
+* create an issue on http://bitbucket.org/hpk42/apipkg/issues
+
+have fun,
+holger krekel
+
+
diff --git a/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/INSTALLER b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/METADATA b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/METADATA
new file mode 100644
index 0000000..eb7e60a
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/METADATA
@@ -0,0 +1,109 @@
+Metadata-Version: 2.0
+Name: apipkg
+Version: 1.4
+Summary: apipkg: namespace control and lazy-import mechanism
+Home-page: http://bitbucket.org/hpk42/apipkg
+Author: holger krekel
+Author-email: holger at merlinux.eu
+License: MIT License
+Platform: unix
+Platform: linux
+Platform: osx
+Platform: cygwin
+Platform: win32
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Programming Language :: Python
+
+Welcome to apipkg!
+------------------------
+
+With apipkg you can control the exported namespace of a
+python package and greatly reduce the number of imports for your users.
+It is a `small pure python module`_ that works on virtually all Python
+versions, including CPython2.3 to Python3.1, Jython and PyPy.  It co-operates
+well with Python's ``help()`` system, custom importers (PEP302) and common
+command line completion tools.
+
+Usage is very simple: you can require 'apipkg' as a dependency or you
+can copy paste the <200 Lines of code into your project.
+
+
+Tutorial example
+-------------------
+
+Here is a simple ``mypkg`` package that specifies one namespace
+and exports two objects imported from different modules::
+
+    # mypkg/__init__.py
+    import apipkg
+    apipkg.initpkg(__name__, {
+        'path': {
+            'Class1': "_mypkg.somemodule:Class1",
+            'clsattr': "_mypkg.othermodule:Class2.attr",
+        }
+    }
+
+The package is initialized with a dictionary as namespace.
+
+You need to create a ``_mypkg`` package with a ``somemodule.py``
+and ``othermodule.py`` containing the respective classes.
+The ``_mypkg`` is not special - it's a completely
+regular python package.
+
+Namespace dictionaries contain ``name: value`` mappings
+where the value may be another namespace dictionary or
+a string specifying an import location.  On accessing
+an namespace attribute an import will be performed::
+
+    >>> import mypkg
+    >>> mypkg.path
+    <ApiModule 'mypkg.path'>
+    >>> mypkg.path.Class1   # '_mypkg.somemodule' gets imported now
+    <class _mypkg.somemodule.Class1 at 0xb7d428fc>
+    >>> mypkg.path.clsattr  # '_mypkg.othermodule' gets imported now
+    4 # the value of _mypkg.othermodule.Class2.attr
+
+The ``mypkg.path`` namespace and its two entries are
+loaded when they are accessed.   This means:
+
+* lazy loading - only what is actually needed is ever loaded
+
+* only the root "mypkg" ever needs to be imported to get
+  access to the complete functionality.
+
+* the underlying modules are also accessible, for example::
+
+    from mypkg.sub import Class1
+
+
+Including apipkg in your package
+--------------------------------------
+
+If you don't want to add an ``apipkg`` dependency to your package you
+can copy the `apipkg.py`_ file somewhere to your own package,
+for example ``_mypkg/apipkg.py`` in the above example.  You
+then import the ``initpkg`` function from that new place and
+are good to go.
+
+.. _`small pure python module`:
+.. _`apipkg.py`: http://bitbucket.org/hpk42/apipkg/src/tip/apipkg.py
+
+Feedback?
+-----------------------
+
+If you have questions you are welcome to
+
+* join the #pylib channel on irc.freenode.net
+* subscribe to the http://codespeak.net/mailman/listinfo/py-dev list.
+* create an issue on http://bitbucket.org/hpk42/apipkg/issues
+
+have fun,
+holger krekel
+
+
diff --git a/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/RECORD b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/RECORD
new file mode 100644
index 0000000..dc72959
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/RECORD
@@ -0,0 +1,9 @@
+apipkg.py,sha256=BNnv_qvq8zZvku-uudoqgp3XTNFbwsNUmtzOKrVI7X0,6420

+apipkg-1.4.dist-info/top_level.txt,sha256=3TGS6nmN7kjxhUK4LpPCB3QkQI34QYGrT0ZQGWajoZ8,7

+apipkg-1.4.dist-info/METADATA,sha256=Fk_8BrHyXE--kvB3_ZBKgwvPaKusAZUjchH-kpB63Hs,3491

+apipkg-1.4.dist-info/DESCRIPTION.rst,sha256=RkMQqk5ljhGy0DiZkR_nbpjqvwCIhuIEHsyvkn3O96k,2803

+apipkg-1.4.dist-info/metadata.json,sha256=GdshYrA_7gAII3E3EQMH-31BHzU-klTZ6bPQzlDmuy4,779

+apipkg-1.4.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110

+apipkg-1.4.dist-info/RECORD,,

+apipkg-1.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4

+__pycache__/apipkg.cpython-35.pyc,,

diff --git a/tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/WHEEL
similarity index 100%
rename from tools/pytest/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL
rename to tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/WHEEL
diff --git a/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/metadata.json b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/metadata.json
new file mode 100644
index 0000000..05609b9
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/metadata.json
@@ -0,0 +1 @@
+{"license": "MIT License", "name": "apipkg", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "apipkg: namespace control and lazy-import mechanism", "platform": "unix", "version": "1.4", "extensions": {"python.details": {"project_urls": {"Home": "http://bitbucket.org/hpk42/apipkg"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "holger at merlinux.eu", "name": "holger krekel"}]}}, "classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Libraries", "Programming Language :: Python"]}
\ No newline at end of file
diff --git a/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/top_level.txt b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/top_level.txt
new file mode 100644
index 0000000..e2221c8
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/apipkg-1.4.dist-info/top_level.txt
@@ -0,0 +1 @@
+apipkg
diff --git a/tools/third_party/py/py/_vendored_packages/apipkg.py b/tools/third_party/py/py/_vendored_packages/apipkg.py
new file mode 100644
index 0000000..9d56e0b
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/apipkg.py
@@ -0,0 +1,205 @@
+"""
+apipkg: control the exported namespace of a python package.
+
+see http://pypi.python.org/pypi/apipkg
+
+(c) holger krekel, 2009 - MIT license
+"""
+import os
+import sys
+from types import ModuleType
+
+
+__version__ = '1.4'
+
+
+def _py_abspath(path):
+    """
+    special version of abspath
+    that will leave paths from jython jars alone
+    """
+    if path.startswith('__pyclasspath__'):
+
+        return path
+    else:
+        return os.path.abspath(path)
+
+
+def distribution_version(name):
+    """try to get the version of the named distribution,
+    returs None on failure"""
+    from pkg_resources import get_distribution, DistributionNotFound
+    try:
+        dist = get_distribution(name)
+    except DistributionNotFound:
+        pass
+    else:
+        return dist.version
+
+
+def initpkg(pkgname, exportdefs, attr=dict(), eager=False):
+    """ initialize given package from the export definitions. """
+    oldmod = sys.modules.get(pkgname)
+    d = {}
+    f = getattr(oldmod, '__file__', None)
+    if f:
+        f = _py_abspath(f)
+    d['__file__'] = f
+    if hasattr(oldmod, '__version__'):
+        d['__version__'] = oldmod.__version__
+    if hasattr(oldmod, '__loader__'):
+        d['__loader__'] = oldmod.__loader__
+    if hasattr(oldmod, '__path__'):
+        d['__path__'] = [_py_abspath(p) for p in oldmod.__path__]
+    if '__doc__' not in exportdefs and getattr(oldmod, '__doc__', None):
+        d['__doc__'] = oldmod.__doc__
+    d.update(attr)
+    if hasattr(oldmod, "__dict__"):
+        oldmod.__dict__.update(d)
+    mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d)
+    sys.modules[pkgname] = mod
+    # eagerload in bypthon to avoid their monkeypatching breaking packages
+    if 'bpython' in sys.modules or eager:
+        for module in sys.modules.values():
+            if isinstance(module, ApiModule):
+                module.__dict__
+
+
+def importobj(modpath, attrname):
+    module = __import__(modpath, None, None, ['__doc__'])
+    if not attrname:
+        return module
+
+    retval = module
+    names = attrname.split(".")
+    for x in names:
+        retval = getattr(retval, x)
+    return retval
+
+
+class ApiModule(ModuleType):
+    def __docget(self):
+        try:
+            return self.__doc
+        except AttributeError:
+            if '__doc__' in self.__map__:
+                return self.__makeattr('__doc__')
+
+    def __docset(self, value):
+        self.__doc = value
+    __doc__ = property(__docget, __docset)
+
+    def __init__(self, name, importspec, implprefix=None, attr=None):
+        self.__name__ = name
+        self.__all__ = [x for x in importspec if x != '__onfirstaccess__']
+        self.__map__ = {}
+        self.__implprefix__ = implprefix or name
+        if attr:
+            for name, val in attr.items():
+                # print "setting", self.__name__, name, val
+                setattr(self, name, val)
+        for name, importspec in importspec.items():
+            if isinstance(importspec, dict):
+                subname = '%s.%s' % (self.__name__, name)
+                apimod = ApiModule(subname, importspec, implprefix)
+                sys.modules[subname] = apimod
+                setattr(self, name, apimod)
+            else:
+                parts = importspec.split(':')
+                modpath = parts.pop(0)
+                attrname = parts and parts[0] or ""
+                if modpath[0] == '.':
+                    modpath = implprefix + modpath
+
+                if not attrname:
+                    subname = '%s.%s' % (self.__name__, name)
+                    apimod = AliasModule(subname, modpath)
+                    sys.modules[subname] = apimod
+                    if '.' not in name:
+                        setattr(self, name, apimod)
+                else:
+                    self.__map__[name] = (modpath, attrname)
+
+    def __repr__(self):
+        l = []
+        if hasattr(self, '__version__'):
+            l.append("version=" + repr(self.__version__))
+        if hasattr(self, '__file__'):
+            l.append('from ' + repr(self.__file__))
+        if l:
+            return '<ApiModule %r %s>' % (self.__name__, " ".join(l))
+        return '<ApiModule %r>' % (self.__name__,)
+
+    def __makeattr(self, name):
+        """lazily compute value for name or raise AttributeError if unknown."""
+        # print "makeattr", self.__name__, name
+        target = None
+        if '__onfirstaccess__' in self.__map__:
+            target = self.__map__.pop('__onfirstaccess__')
+            importobj(*target)()
+        try:
+            modpath, attrname = self.__map__[name]
+        except KeyError:
+            if target is not None and name != '__onfirstaccess__':
+                # retry, onfirstaccess might have set attrs
+                return getattr(self, name)
+            raise AttributeError(name)
+        else:
+            result = importobj(modpath, attrname)
+            setattr(self, name, result)
+            try:
+                del self.__map__[name]
+            except KeyError:
+                pass  # in a recursive-import situation a double-del can happen
+            return result
+
+    __getattr__ = __makeattr
+
+    @property
+    def __dict__(self):
+        # force all the content of the module
+        # to be loaded when __dict__ is read
+        dictdescr = ModuleType.__dict__['__dict__']
+        dict = dictdescr.__get__(self)
+        if dict is not None:
+            hasattr(self, 'some')
+            for name in self.__all__:
+                try:
+                    self.__makeattr(name)
+                except AttributeError:
+                    pass
+        return dict
+
+
+def AliasModule(modname, modpath, attrname=None):
+    mod = []
+
+    def getmod():
+        if not mod:
+            x = importobj(modpath, None)
+            if attrname is not None:
+                x = getattr(x, attrname)
+            mod.append(x)
+        return mod[0]
+
+    class AliasModule(ModuleType):
+
+        def __repr__(self):
+            x = modpath
+            if attrname:
+                x += "." + attrname
+            return '<AliasModule %r for %r>' % (modname, x)
+
+        def __getattribute__(self, name):
+            try:
+                return getattr(getmod(), name)
+            except ImportError:
+                return None
+
+        def __setattr__(self, name, value):
+            setattr(getmod(), name, value)
+
+        def __delattr__(self, name):
+            delattr(getmod(), name)
+
+    return AliasModule(str(modname))
diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/DESCRIPTION.rst b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/DESCRIPTION.rst
new file mode 100644
index 0000000..6d59bc2
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/DESCRIPTION.rst
@@ -0,0 +1,53 @@
+iniconfig: brain-dead simple parsing of ini files
+=======================================================
+
+iniconfig is a small and simple INI-file parser module
+having a unique set of features:
+
+* tested against Python2.4 across to Python3.2, Jython, PyPy
+* maintains order of sections and entries
+* supports multi-line values with or without line-continuations
+* supports "#" comments everywhere
+* raises errors with proper line-numbers
+* no bells and whistles like automatic substitutions
+* iniconfig raises an Error if two sections have the same name.
+
+If you encounter issues or have feature wishes please report them to:
+
+    http://github.org/RonnyPfannschmidt/iniconfig/issues
+
+Basic Example
+===================================
+
+If you have an ini file like this::
+
+    # content of example.ini
+    [section1] # comment
+    name1=value1  # comment
+    name1b=value1,value2  # comment
+
+    [section2]
+    name2=
+        line1
+        line2
+
+then you can do::
+
+    >>> import iniconfig
+    >>> ini = iniconfig.IniConfig("example.ini")
+    >>> ini['section1']['name1'] # raises KeyError if not exists
+    'value1'
+    >>> ini.get('section1', 'name1b', [], lambda x: x.split(","))
+    ['value1', 'value2']
+    >>> ini.get('section1', 'notexist', [], lambda x: x.split(","))
+    []
+    >>> [x.name for x in list(ini)]
+    ['section1', 'section2']
+    >>> list(list(ini)[0].items())
+    [('name1', 'value1'), ('name1b', 'value1,value2')]
+    >>> 'section1' in ini
+    True
+    >>> 'inexistendsection' in ini
+    False
+
+
diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/INSTALLER b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/METADATA b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/METADATA
new file mode 100644
index 0000000..79ea62d
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/METADATA
@@ -0,0 +1,78 @@
+Metadata-Version: 2.0
+Name: iniconfig
+Version: 1.0.0
+Summary: iniconfig: brain-dead simple config-ini parsing
+Home-page: http://github.com/RonnyPfannschmidt/iniconfig
+Author: Ronny Pfannschmidt, Holger Krekel
+Author-email: opensource@ronnypfannschmidt.de, holger.krekel@gmail.com
+License: MIT License
+Platform: unix
+Platform: linux
+Platform: osx
+Platform: cygwin
+Platform: win32
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
+
+iniconfig: brain-dead simple parsing of ini files
+=======================================================
+
+iniconfig is a small and simple INI-file parser module
+having a unique set of features:
+
+* tested against Python2.4 across to Python3.2, Jython, PyPy
+* maintains order of sections and entries
+* supports multi-line values with or without line-continuations
+* supports "#" comments everywhere
+* raises errors with proper line-numbers
+* no bells and whistles like automatic substitutions
+* iniconfig raises an Error if two sections have the same name.
+
+If you encounter issues or have feature wishes please report them to:
+
+    http://github.org/RonnyPfannschmidt/iniconfig/issues
+
+Basic Example
+===================================
+
+If you have an ini file like this::
+
+    # content of example.ini
+    [section1] # comment
+    name1=value1  # comment
+    name1b=value1,value2  # comment
+
+    [section2]
+    name2=
+        line1
+        line2
+
+then you can do::
+
+    >>> import iniconfig
+    >>> ini = iniconfig.IniConfig("example.ini")
+    >>> ini['section1']['name1'] # raises KeyError if not exists
+    'value1'
+    >>> ini.get('section1', 'name1b', [], lambda x: x.split(","))
+    ['value1', 'value2']
+    >>> ini.get('section1', 'notexist', [], lambda x: x.split(","))
+    []
+    >>> [x.name for x in list(ini)]
+    ['section1', 'section2']
+    >>> list(list(ini)[0].items())
+    [('name1', 'value1'), ('name1b', 'value1,value2')]
+    >>> 'section1' in ini
+    True
+    >>> 'inexistendsection' in ini
+    False
+
+
diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/RECORD b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/RECORD
new file mode 100644
index 0000000..ec2f5e1
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/RECORD
@@ -0,0 +1,9 @@
+iniconfig.py,sha256=-pBe5AF_6aAwo1CxJQ8i_zJq6ejc6IxHta7qk2tNJhY,5208

+iniconfig-1.0.0.dist-info/DESCRIPTION.rst,sha256=BDLMwWqfjpwZ5yqXRvz1x6bf8Dnt_pZhElekAwtL19o,1522

+iniconfig-1.0.0.dist-info/METADATA,sha256=bb2T8WUSDXXiUVxZ4WXhbffq6stikMTlB1jyrPbLfyU,2405

+iniconfig-1.0.0.dist-info/RECORD,,

+iniconfig-1.0.0.dist-info/WHEEL,sha256=3XK1Z4AI42GuJXciCpiHMOkbehxRV8QDBW8IU41k3ZU,96

+iniconfig-1.0.0.dist-info/metadata.json,sha256=UYYwW0p815nU4qz8Iq1gGqIYaAcsCyGju3jXvTOyXSI,950

+iniconfig-1.0.0.dist-info/top_level.txt,sha256=7KfM0fugdlToj9UW7enKXk2HYALQD8qHiyKtjhSzgN8,10

+iniconfig-1.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4

+__pycache__/iniconfig.cpython-35.pyc,,

diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/WHEEL b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/WHEEL
new file mode 100644
index 0000000..15b96c9
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.30.0.a0)
+Root-Is-Purelib: true
+Tag: cp35-none-any
+
diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/metadata.json b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/metadata.json
new file mode 100644
index 0000000..084daa6
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/metadata.json
@@ -0,0 +1 @@
+{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3"], "extensions": {"python.details": {"contacts": [{"email": "opensource@ronnypfannschmidt.de, holger.krekel@gmail.com", "name": "Ronny Pfannschmidt, Holger Krekel", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/RonnyPfannschmidt/iniconfig"}}}, "generator": "bdist_wheel (0.30.0.a0)", "license": "MIT License", "metadata_version": "2.0", "name": "iniconfig", "platform": "unix", "summary": "iniconfig: brain-dead simple config-ini parsing", "version": "1.0.0"}
\ No newline at end of file
diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/top_level.txt b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/top_level.txt
new file mode 100644
index 0000000..9dda536
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig-1.0.0.dist-info/top_level.txt
@@ -0,0 +1 @@
+iniconfig
diff --git a/tools/third_party/py/py/_vendored_packages/iniconfig.py b/tools/third_party/py/py/_vendored_packages/iniconfig.py
new file mode 100644
index 0000000..6ad9eaf
--- /dev/null
+++ b/tools/third_party/py/py/_vendored_packages/iniconfig.py
@@ -0,0 +1,165 @@
+""" brain-dead simple parser for ini-style files.
+(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed
+"""
+__all__ = ['IniConfig', 'ParseError']
+
+COMMENTCHARS = "#;"
+
+
+class ParseError(Exception):
+    def __init__(self, path, lineno, msg):
+        Exception.__init__(self, path, lineno, msg)
+        self.path = path
+        self.lineno = lineno
+        self.msg = msg
+
+    def __str__(self):
+        return "%s:%s: %s" % (self.path, self.lineno+1, self.msg)
+
+
+class SectionWrapper(object):
+    def __init__(self, config, name):
+        self.config = config
+        self.name = name
+
+    def lineof(self, name):
+        return self.config.lineof(self.name, name)
+
+    def get(self, key, default=None, convert=str):
+        return self.config.get(self.name, key,
+                               convert=convert, default=default)
+
+    def __getitem__(self, key):
+        return self.config.sections[self.name][key]
+
+    def __iter__(self):
+        section = self.config.sections.get(self.name, [])
+
+        def lineof(key):
+            return self.config.lineof(self.name, key)
+        for name in sorted(section, key=lineof):
+            yield name
+
+    def items(self):
+        for name in self:
+            yield name, self[name]
+
+
+class IniConfig(object):
+    def __init__(self, path, data=None):
+        self.path = str(path)  # convenience
+        if data is None:
+            f = open(self.path)
+            try:
+                tokens = self._parse(iter(f))
+            finally:
+                f.close()
+        else:
+            tokens = self._parse(data.splitlines(True))
+
+        self._sources = {}
+        self.sections = {}
+
+        for lineno, section, name, value in tokens:
+            if section is None:
+                self._raise(lineno, 'no section header defined')
+            self._sources[section, name] = lineno
+            if name is None:
+                if section in self.sections:
+                    self._raise(lineno, 'duplicate section %r' % (section, ))
+                self.sections[section] = {}
+            else:
+                if name in self.sections[section]:
+                    self._raise(lineno, 'duplicate name %r' % (name, ))
+                self.sections[section][name] = value
+
+    def _raise(self, lineno, msg):
+        raise ParseError(self.path, lineno, msg)
+
+    def _parse(self, line_iter):
+        result = []
+        section = None
+        for lineno, line in enumerate(line_iter):
+            name, data = self._parseline(line, lineno)
+            # new value
+            if name is not None and data is not None:
+                result.append((lineno, section, name, data))
+            # new section
+            elif name is not None and data is None:
+                if not name:
+                    self._raise(lineno, 'empty section name')
+                section = name
+                result.append((lineno, section, None, None))
+            # continuation
+            elif name is None and data is not None:
+                if not result:
+                    self._raise(lineno, 'unexpected value continuation')
+                last = result.pop()
+                last_name, last_data = last[-2:]
+                if last_name is None:
+                    self._raise(lineno, 'unexpected value continuation')
+
+                if last_data:
+                    data = '%s\n%s' % (last_data, data)
+                result.append(last[:-1] + (data,))
+        return result
+
+    def _parseline(self, line, lineno):
+        # blank lines
+        if iscommentline(line):
+            line = ""
+        else:
+            line = line.rstrip()
+        if not line:
+            return None, None
+        # section
+        if line[0] == '[':
+            realline = line
+            for c in COMMENTCHARS:
+                line = line.split(c)[0].rstrip()
+            if line[-1] == "]":
+                return line[1:-1], None
+            return None, realline.strip()
+        # value
+        elif not line[0].isspace():
+            try:
+                name, value = line.split('=', 1)
+                if ":" in name:
+                    raise ValueError()
+            except ValueError:
+                try:
+                    name, value = line.split(":", 1)
+                except ValueError:
+                    self._raise(lineno, 'unexpected line: %r' % line)
+            return name.strip(), value.strip()
+        # continuation
+        else:
+            return None, line.strip()
+
+    def lineof(self, section, name=None):
+        lineno = self._sources.get((section, name))
+        if lineno is not None:
+            return lineno + 1
+
+    def get(self, section, name, default=None, convert=str):
+        try:
+            return convert(self.sections[section][name])
+        except KeyError:
+            return default
+
+    def __getitem__(self, name):
+        if name not in self.sections:
+            raise KeyError(name)
+        return SectionWrapper(self, name)
+
+    def __iter__(self):
+        for name in sorted(self.sections, key=self.lineof):
+            yield SectionWrapper(self, name)
+
+    def __contains__(self, arg):
+        return arg in self.sections
+
+
+def iscommentline(line):
+    c = line.lstrip()[:1]
+    return c in COMMENTCHARS
diff --git a/tools/third_party/py/py/_xmlgen.py b/tools/third_party/py/py/_xmlgen.py
new file mode 100644
index 0000000..1c83545
--- /dev/null
+++ b/tools/third_party/py/py/_xmlgen.py
@@ -0,0 +1,255 @@
+"""
+module for generating and serializing xml and html structures
+by using simple python objects.
+
+(c) holger krekel, holger at merlinux eu. 2009
+"""
+import sys, re
+
+if sys.version_info >= (3,0):
+    def u(s):
+        return s
+    def unicode(x, errors=None):
+        if hasattr(x, '__unicode__'):
+            return x.__unicode__()
+        return str(x)
+else:
+    def u(s):
+        return unicode(s)
+    unicode = unicode
+
+
+class NamespaceMetaclass(type):
+    def __getattr__(self, name):
+        if name[:1] == '_':
+            raise AttributeError(name)
+        if self == Namespace:
+            raise ValueError("Namespace class is abstract")
+        tagspec = self.__tagspec__
+        if tagspec is not None and name not in tagspec:
+            raise AttributeError(name)
+        classattr = {}
+        if self.__stickyname__:
+            classattr['xmlname'] = name
+        cls = type(name, (self.__tagclass__,), classattr)
+        setattr(self, name, cls)
+        return cls
+
+class Tag(list):
+    class Attr(object):
+        def __init__(self, **kwargs):
+            self.__dict__.update(kwargs)
+
+    def __init__(self, *args, **kwargs):
+        super(Tag, self).__init__(args)
+        self.attr = self.Attr(**kwargs)
+
+    def __unicode__(self):
+        return self.unicode(indent=0)
+    __str__ = __unicode__
+
+    def unicode(self, indent=2):
+        l = []
+        SimpleUnicodeVisitor(l.append, indent).visit(self)
+        return u("").join(l)
+
+    def __repr__(self):
+        name = self.__class__.__name__
+        return "<%r tag object %d>" % (name, id(self))
+
+Namespace = NamespaceMetaclass('Namespace', (object, ), {
+    '__tagspec__': None,
+    '__tagclass__': Tag,
+    '__stickyname__': False,
+})
+
+class HtmlTag(Tag):
+    def unicode(self, indent=2):
+        l = []
+        HtmlVisitor(l.append, indent, shortempty=False).visit(self)
+        return u("").join(l)
+
+# exported plain html namespace
+class html(Namespace):
+    __tagclass__ = HtmlTag
+    __stickyname__ = True
+    __tagspec__ = dict([(x,1) for x in (
+        'a,abbr,acronym,address,applet,area,article,aside,audio,b,'
+        'base,basefont,bdi,bdo,big,blink,blockquote,body,br,button,'
+        'canvas,caption,center,cite,code,col,colgroup,command,comment,'
+        'datalist,dd,del,details,dfn,dir,div,dl,dt,em,embed,'
+        'fieldset,figcaption,figure,footer,font,form,frame,frameset,h1,'
+        'h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,img,input,'
+        'ins,isindex,kbd,keygen,label,legend,li,link,listing,map,mark,'
+        'marquee,menu,meta,meter,multicol,nav,nobr,noembed,noframes,'
+        'noscript,object,ol,optgroup,option,output,p,param,pre,progress,'
+        'q,rp,rt,ruby,s,samp,script,section,select,small,source,span,'
+        'strike,strong,style,sub,summary,sup,table,tbody,td,textarea,'
+        'tfoot,th,thead,time,title,tr,track,tt,u,ul,xmp,var,video,wbr'
+    ).split(',') if x])
+
+    class Style(object):
+        def __init__(self, **kw):
+            for x, y in kw.items():
+                x = x.replace('_', '-')
+                setattr(self, x, y)
+
+
+class raw(object):
+    """just a box that can contain a unicode string that will be
+    included directly in the output"""
+    def __init__(self, uniobj):
+        self.uniobj = uniobj
+
+class SimpleUnicodeVisitor(object):
+    """ recursive visitor to write unicode. """
+    def __init__(self, write, indent=0, curindent=0, shortempty=True):
+        self.write = write
+        self.cache = {}
+        self.visited = {} # for detection of recursion
+        self.indent = indent
+        self.curindent = curindent
+        self.parents = []
+        self.shortempty = shortempty  # short empty tags or not
+
+    def visit(self, node):
+        """ dispatcher on node's class/bases name. """
+        cls = node.__class__
+        try:
+            visitmethod = self.cache[cls]
+        except KeyError:
+            for subclass in cls.__mro__:
+                visitmethod = getattr(self, subclass.__name__, None)
+                if visitmethod is not None:
+                    break
+            else:
+                visitmethod = self.__object
+            self.cache[cls] = visitmethod
+        visitmethod(node)
+
+    # the default fallback handler is marked private
+    # to avoid clashes with the tag name object
+    def __object(self, obj):
+        #self.write(obj)
+        self.write(escape(unicode(obj)))
+
+    def raw(self, obj):
+        self.write(obj.uniobj)
+
+    def list(self, obj):
+        assert id(obj) not in self.visited
+        self.visited[id(obj)] = 1
+        for elem in obj:
+            self.visit(elem)
+
+    def Tag(self, tag):
+        assert id(tag) not in self.visited
+        try:
+            tag.parent = self.parents[-1]
+        except IndexError:
+            tag.parent = None
+        self.visited[id(tag)] = 1
+        tagname = getattr(tag, 'xmlname', tag.__class__.__name__)
+        if self.curindent and not self._isinline(tagname):
+            self.write("\n" + u(' ') * self.curindent)
+        if tag:
+            self.curindent += self.indent
+            self.write(u('<%s%s>') % (tagname, self.attributes(tag)))
+            self.parents.append(tag)
+            for x in tag:
+                self.visit(x)
+            self.parents.pop()
+            self.write(u('</%s>') % tagname)
+            self.curindent -= self.indent
+        else:
+            nameattr = tagname+self.attributes(tag)
+            if self._issingleton(tagname):
+                self.write(u('<%s/>') % (nameattr,))
+            else:
+                self.write(u('<%s></%s>') % (nameattr, tagname))
+
+    def attributes(self, tag):
+        # serialize attributes
+        attrlist = dir(tag.attr)
+        attrlist.sort()
+        l = []
+        for name in attrlist:
+            res = self.repr_attribute(tag.attr, name)
+            if res is not None:
+                l.append(res)
+        l.extend(self.getstyle(tag))
+        return u("").join(l)
+
+    def repr_attribute(self, attrs, name):
+        if name[:2] != '__':
+            value = getattr(attrs, name)
+            if name.endswith('_'):
+                name = name[:-1]
+            if isinstance(value, raw):
+                insert = value.uniobj
+            else:
+                insert = escape(unicode(value))
+            return ' %s="%s"' % (name, insert)
+
+    def getstyle(self, tag):
+        """ return attribute list suitable for styling. """
+        try:
+            styledict = tag.style.__dict__
+        except AttributeError:
+            return []
+        else:
+            stylelist = [x+': ' + y for x,y in styledict.items()]
+            return [u(' style="%s"') % u('; ').join(stylelist)]
+
+    def _issingleton(self, tagname):
+        """can (and will) be overridden in subclasses"""
+        return self.shortempty
+
+    def _isinline(self, tagname):
+        """can (and will) be overridden in subclasses"""
+        return False
+
+class HtmlVisitor(SimpleUnicodeVisitor):
+
+    single = dict([(x, 1) for x in
+                ('br,img,area,param,col,hr,meta,link,base,'
+                    'input,frame').split(',')])
+    inline = dict([(x, 1) for x in
+                ('a abbr acronym b basefont bdo big br cite code dfn em font '
+                 'i img input kbd label q s samp select small span strike '
+                 'strong sub sup textarea tt u var'.split(' '))])
+
+    def repr_attribute(self, attrs, name):
+        if name == 'class_':
+            value = getattr(attrs, name)
+            if value is None:
+                return
+        return super(HtmlVisitor, self).repr_attribute(attrs, name)
+
+    def _issingleton(self, tagname):
+        return tagname in self.single
+
+    def _isinline(self, tagname):
+        return tagname in self.inline
+
+
+class _escape:
+    def __init__(self):
+        self.escape = {
+            u('"') : u('&quot;'), u('<') : u('&lt;'), u('>') : u('&gt;'),
+            u('&') : u('&amp;'), u("'") : u('&apos;'),
+            }
+        self.charef_rex = re.compile(u("|").join(self.escape.keys()))
+
+    def _replacer(self, match):
+        return self.escape[match.group(0)]
+
+    def __call__(self, ustring):
+        """ xml-escape the given unicode string. """
+        try:
+            ustring = unicode(ustring)
+        except UnicodeDecodeError:
+            ustring = unicode(ustring, 'utf-8', errors='replace')
+        return self.charef_rex.sub(self._replacer, ustring)
+
+escape = _escape()
diff --git a/tools/py/py/test.py b/tools/third_party/py/py/test.py
similarity index 100%
rename from tools/py/py/test.py
rename to tools/third_party/py/py/test.py
diff --git a/tools/third_party/py/setup.cfg b/tools/third_party/py/setup.cfg
new file mode 100644
index 0000000..5f25c2f
--- /dev/null
+++ b/tools/third_party/py/setup.cfg
@@ -0,0 +1,8 @@
+[wheel]
+universal = 1
+
+[metadata]
+license_file = LICENSE
+
+[devpi:upload]
+formats=sdist.tgz,bdist_wheel
diff --git a/tools/third_party/py/setup.py b/tools/third_party/py/setup.py
new file mode 100644
index 0000000..959323b
--- /dev/null
+++ b/tools/third_party/py/setup.py
@@ -0,0 +1,53 @@
+import os
+import sys
+
+from setuptools import setup, find_packages
+
+
+def get_version():
+    p = os.path.join(os.path.dirname(
+                     os.path.abspath(__file__)), "py", "__init__.py")
+    with open(p) as f:
+        for line in f.readlines():
+            if "__version__" in line:
+                return line.strip().split("=")[-1].strip(" '")
+    raise ValueError("could not read version")
+
+
+def main():
+    setup(
+        name='py',
+        description='library with cross-python path, ini-parsing, io, code, log facilities',
+        long_description=open('README.rst').read(),
+        version=get_version(),
+        url='http://py.readthedocs.io/',
+        license='MIT license',
+        platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
+        python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
+        author='holger krekel, Ronny Pfannschmidt, Benjamin Peterson and others',
+        author_email='pytest-dev@python.org',
+        classifiers=['Development Status :: 6 - Mature',
+                     'Intended Audience :: Developers',
+                     'License :: OSI Approved :: MIT License',
+                     'Operating System :: POSIX',
+                     'Operating System :: Microsoft :: Windows',
+                     'Operating System :: MacOS :: MacOS X',
+                     'Topic :: Software Development :: Testing',
+                     'Topic :: Software Development :: Libraries',
+                     'Topic :: Utilities',
+                     'Programming Language :: Python',
+                     'Programming Language :: Python :: 2',
+                     'Programming Language :: Python :: 2.7',
+                     'Programming Language :: Python :: 3',
+                     'Programming Language :: Python :: 3.4',
+                     'Programming Language :: Python :: 3.5',
+                     'Programming Language :: Python :: 3.6',
+                     'Programming Language :: Python :: Implementation :: CPython',
+                     'Programming Language :: Python :: Implementation :: PyPy',
+                    ],
+        packages=find_packages(exclude=['tasks', 'testing']),
+        zip_safe=False,
+    )
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/third_party/py/tasks/__init__.py b/tools/third_party/py/tasks/__init__.py
new file mode 100644
index 0000000..5d74b64
--- /dev/null
+++ b/tools/third_party/py/tasks/__init__.py
@@ -0,0 +1,12 @@
+"""
+Invoke tasks to help with pytest development and release process.
+"""
+
+import invoke
+
+from . import vendoring
+
+
+ns = invoke.Collection(
+    vendoring
+)
diff --git a/tools/third_party/py/tasks/vendoring.py b/tools/third_party/py/tasks/vendoring.py
new file mode 100644
index 0000000..fbc171b
--- /dev/null
+++ b/tools/third_party/py/tasks/vendoring.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, print_function
+import py
+import invoke
+
+VENDOR_TARGET = py.path.local("py/_vendored_packages")
+GOOD_FILES = 'README.md', '__init__.py'
+
+@invoke.task()
+def remove_libs(ctx):
+    print("removing vendored libs")
+    for path in VENDOR_TARGET.listdir():
+        if path.basename not in GOOD_FILES:
+            print(" ", path)
+            path.remove()
+
+@invoke.task(pre=[remove_libs])
+def update_libs(ctx):
+    print("installing libs")
+    ctx.run("pip install -t {target} apipkg iniconfig".format(target=VENDOR_TARGET))
+    ctx.run("git add {target}".format(target=VENDOR_TARGET))
+    print("Please commit to finish the update after running the tests:")
+    print()
+    print('    git commit -am "Updated vendored libs"')
diff --git a/tools/third_party/py/testing/code/test_assertion.py b/tools/third_party/py/testing/code/test_assertion.py
new file mode 100644
index 0000000..e2a7f90
--- /dev/null
+++ b/tools/third_party/py/testing/code/test_assertion.py
@@ -0,0 +1,305 @@
+import pytest, py
+import re
+
+def exvalue():
+    import sys
+    return sys.exc_info()[1]
+
+def f():
+    return 2
+
+def test_assert():
+    try:
+        assert f() == 3
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith('assert 2 == 3\n')
+
+
+def test_assert_within_finally():
+    excinfo = py.test.raises(ZeroDivisionError, """
+        try:
+            1/0
+        finally:
+            i = 42
+    """)
+    s = excinfo.exconly()
+    assert re.search("ZeroDivisionError:.*division", s) is not None
+
+
+def test_assert_multiline_1():
+    try:
+        assert (f() ==
+                3)
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith('assert 2 == 3\n')
+
+def test_assert_multiline_2():
+    try:
+        assert (f() == (4,
+                   3)[-1])
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith('assert 2 ==')
+
+def test_in():
+    try:
+        assert "hi" in [1, 2]
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith("assert 'hi' in")
+
+def test_is():
+    try:
+        assert 1 is 2
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith("assert 1 is 2")
+
+
+def test_attrib():
+    class Foo(object):
+        b = 1
+    i = Foo()
+    try:
+        assert i.b == 2
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith("assert 1 == 2")
+
+def test_attrib_inst():
+    class Foo(object):
+        b = 1
+    try:
+        assert Foo().b == 2
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith("assert 1 == 2")
+
+def test_len():
+    l = list(range(42))
+    try:
+        assert len(l) == 100
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert s.startswith("assert 42 == 100")
+        assert "where 42 = len([" in s
+
+
+def test_assert_keyword_arg():
+    def f(x=3):
+        return False
+    try:
+        assert f(x=5)
+    except AssertionError:
+        e = exvalue()
+        assert "x=5" in str(e)
+
+# These tests should both fail, but should fail nicely...
+class WeirdRepr:
+    def __repr__(self):
+        return '<WeirdRepr\nsecond line>'
+
+def bug_test_assert_repr():
+    v = WeirdRepr()
+    try:
+        assert v == 1
+    except AssertionError:
+        e = exvalue()
+        assert str(e).find('WeirdRepr') != -1
+        assert str(e).find('second line') != -1
+        assert 0
+
+def test_assert_non_string():
+    try:
+        assert 0, ['list']
+    except AssertionError:
+        e = exvalue()
+        assert str(e).find("list") != -1
+
+def test_assert_implicit_multiline():
+    try:
+        x = [1,2,3]
+        assert x != [1,
+           2, 3]
+    except AssertionError:
+        e = exvalue()
+        assert str(e).find('assert [1, 2, 3] !=') != -1
+
+@py.test.mark.xfail(py.test.__version__[0] != "2",
+                    reason="broken on modern pytest",
+                    run=False
+)
+def test_assert_with_brokenrepr_arg():
+    class BrokenRepr:
+        def __repr__(self): 0 / 0
+    e = AssertionError(BrokenRepr())
+    if e.msg.find("broken __repr__") == -1:
+        py.test.fail("broken __repr__ not handle correctly")
+
+def test_multiple_statements_per_line():
+    try:
+        a = 1; assert a == 2
+    except AssertionError:
+        e = exvalue()
+        assert "assert 1 == 2" in str(e)
+
+def test_power():
+    try:
+        assert 2**3 == 7
+    except AssertionError:
+        e = exvalue()
+        assert "assert (2 ** 3) == 7" in str(e)
+
+
+class TestView:
+
+    def setup_class(cls):
+        cls.View = py.test.importorskip("py._code._assertionold").View
+
+    def test_class_dispatch(self):
+        ### Use a custom class hierarchy with existing instances
+
+        class Picklable(self.View):
+            pass
+
+        class Simple(Picklable):
+            __view__ = object
+            def pickle(self):
+                return repr(self.__obj__)
+
+        class Seq(Picklable):
+            __view__ = list, tuple, dict
+            def pickle(self):
+                return ';'.join(
+                    [Picklable(item).pickle() for item in self.__obj__])
+
+        class Dict(Seq):
+            __view__ = dict
+            def pickle(self):
+                return Seq.pickle(self) + '!' + Seq(self.values()).pickle()
+
+        assert Picklable(123).pickle() == '123'
+        assert Picklable([1,[2,3],4]).pickle() == '1;2;3;4'
+        assert Picklable({1:2}).pickle() == '1!2'
+
+    def test_viewtype_class_hierarchy(self):
+        # Use a custom class hierarchy based on attributes of existing instances
+        class Operation:
+            "Existing class that I don't want to change."
+            def __init__(self, opname, *args):
+                self.opname = opname
+                self.args = args
+
+        existing = [Operation('+', 4, 5),
+                    Operation('getitem', '', 'join'),
+                    Operation('setattr', 'x', 'y', 3),
+                    Operation('-', 12, 1)]
+
+        class PyOp(self.View):
+            def __viewkey__(self):
+                return self.opname
+            def generate(self):
+                return '%s(%s)' % (self.opname, ', '.join(map(repr, self.args)))
+
+        class PyBinaryOp(PyOp):
+            __view__ = ('+', '-', '*', '/')
+            def generate(self):
+                return '%s %s %s' % (self.args[0], self.opname, self.args[1])
+
+        codelines = [PyOp(op).generate() for op in existing]
+        assert codelines == ["4 + 5", "getitem('', 'join')",
+            "setattr('x', 'y', 3)", "12 - 1"]
+
+def test_underscore_api():
+    py.code._AssertionError
+    py.code._reinterpret_old # used by pypy
+    py.code._reinterpret
+
+def test_assert_customizable_reprcompare(monkeypatch):
+    util = pytest.importorskip("_pytest.assertion.util")
+    monkeypatch.setattr(util, '_reprcompare', lambda *args: 'hello')
+    try:
+        assert 3 == 4
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert "hello" in s
+
+def test_assert_long_source_1():
+    try:
+        assert len == [
+            (None, ['somet text', 'more text']),
+        ]
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert 're-run' not in s
+        assert 'somet text' in s
+
+def test_assert_long_source_2():
+    try:
+        assert(len == [
+            (None, ['somet text', 'more text']),
+        ])
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert 're-run' not in s
+        assert 'somet text' in s
+
+def test_assert_raise_alias(testdir):
+    testdir.makepyfile("""
+    import sys
+    EX = AssertionError
+    def test_hello():
+        raise EX("hello"
+            "multi"
+            "line")
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*def test_hello*",
+        "*raise EX*",
+        "*1 failed*",
+    ])
+
+@py.test.mark.xfail(py.test.__version__[0] != "2",
+                    reason="broken on modern pytest",
+                    run=False)
+def test_assert_raise_subclass():
+    class SomeEx(AssertionError):
+        def __init__(self, *args):
+            super(SomeEx, self).__init__()
+    try:
+        raise SomeEx("hello")
+    except AssertionError as e:
+        s = str(e)
+        assert 're-run' not in s
+        assert 'could not determine' in s
+
+def test_assert_raises_in_nonzero_of_object_pytest_issue10():
+    class A(object):
+        def __nonzero__(self):
+            raise ValueError(42)
+        def __lt__(self, other):
+            return A()
+        def __repr__(self):
+            return "<MY42 object>"
+    def myany(x):
+        return True
+    try:
+        assert not(myany(A() < 0))
+    except AssertionError:
+        e = exvalue()
+        s = str(e)
+        assert "<MY42 object> < 0" in s
diff --git a/tools/py/testing/code/test_code.py b/tools/third_party/py/testing/code/test_code.py
similarity index 100%
rename from tools/py/testing/code/test_code.py
rename to tools/third_party/py/testing/code/test_code.py
diff --git a/tools/third_party/py/testing/code/test_excinfo.py b/tools/third_party/py/testing/code/test_excinfo.py
new file mode 100644
index 0000000..c148ab8
--- /dev/null
+++ b/tools/third_party/py/testing/code/test_excinfo.py
@@ -0,0 +1,956 @@
+# -*- coding: utf-8 -*-
+
+import py
+import pytest
+import sys
+from test_source import astonly
+
+from py._code.code import FormattedExcinfo, ReprExceptionInfo
+queue = py.builtin._tryimport('queue', 'Queue')
+
+failsonjython = py.test.mark.xfail("sys.platform.startswith('java')")
+
+try:
+    import importlib
+except ImportError:
+    invalidate_import_caches = None
+else:
+    invalidate_import_caches = getattr(importlib, "invalidate_caches", None)
+
+
+pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
+
+broken_on_modern_pytest = pytest.mark.xfail(
+    pytest_version_info[0] != 2,
+    reason="this test hasn't been fixed after moving py.code into pytest",
+    run=False
+    )
+
+
+class TWMock:
+    def __init__(self):
+        self.lines = []
+
+    def sep(self, sep, line=None):
+        self.lines.append((sep, line))
+
+    def line(self, line, **kw):
+        self.lines.append(line)
+
+    def markup(self, text, **kw):
+        return text
+
+    fullwidth = 80
+
+
+def test_excinfo_simple():
+    try:
+        raise ValueError
+    except ValueError:
+        info = py.code.ExceptionInfo()
+    assert info.type == ValueError
+
+
+def test_excinfo_getstatement():
+    def g():
+        raise ValueError
+
+    def f():
+        g()
+    try:
+        f()
+    except ValueError:
+        excinfo = py.code.ExceptionInfo()
+    linenumbers = [
+        py.code.getrawcode(f).co_firstlineno-1+3,
+        py.code.getrawcode(f).co_firstlineno-1+1,
+        py.code.getrawcode(g).co_firstlineno-1+1,
+    ]
+    l = list(excinfo.traceback)
+    foundlinenumbers = [x.lineno for x in l]
+    assert foundlinenumbers == linenumbers
+    #for x in info:
+    #    print "%s:%d  %s" %(x.path.relto(root), x.lineno, x.statement)
+    #xxx
+
+# testchain for getentries test below
+def f():
+    #
+    raise ValueError
+    #
+def g():
+    #
+    __tracebackhide__ = True
+    f()
+    #
+def h():
+    #
+    g()
+    #
+
+class TestTraceback_f_g_h:
+    def setup_method(self, method):
+        try:
+            h()
+        except ValueError:
+            self.excinfo = py.code.ExceptionInfo()
+
+    def test_traceback_entries(self):
+        tb = self.excinfo.traceback
+        entries = list(tb)
+        assert len(tb) == 4 # maybe fragile test
+        assert len(entries) == 4 # maybe fragile test
+        names = ['f', 'g', 'h']
+        for entry in entries:
+            try:
+                names.remove(entry.frame.code.name)
+            except ValueError:
+                pass
+        assert not names
+
+    def test_traceback_entry_getsource(self):
+        tb = self.excinfo.traceback
+        s = str(tb[-1].getsource())
+        assert s.startswith("def f():")
+        assert s.endswith("raise ValueError")
+
+    @astonly
+    @failsonjython
+    def test_traceback_entry_getsource_in_construct(self):
+        source = py.code.Source("""\
+            def xyz():
+                try:
+                    raise ValueError
+                except somenoname:
+                    pass
+            xyz()
+        """)
+        try:
+            exec (source.compile())
+        except NameError:
+            tb = py.code.ExceptionInfo().traceback
+            print (tb[-1].getsource())
+            s = str(tb[-1].getsource())
+            assert s.startswith("def xyz():\n    try:")
+            assert s.strip().endswith("except somenoname:")
+
+    def test_traceback_cut(self):
+        co = py.code.Code(f)
+        path, firstlineno = co.path, co.firstlineno
+        traceback = self.excinfo.traceback
+        newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
+        assert len(newtraceback) == 1
+        newtraceback = traceback.cut(path=path, lineno=firstlineno+2)
+        assert len(newtraceback) == 1
+
+    def test_traceback_cut_excludepath(self, testdir):
+        p = testdir.makepyfile("def f(): raise ValueError")
+        excinfo = py.test.raises(ValueError, "p.pyimport().f()")
+        basedir = py.path.local(py.test.__file__).dirpath()
+        newtraceback = excinfo.traceback.cut(excludepath=basedir)
+        for x in newtraceback:
+            if hasattr(x, 'path'):
+                assert not py.path.local(x.path).relto(basedir)
+        assert newtraceback[-1].frame.code.path == p
+
+    def test_traceback_filter(self):
+        traceback = self.excinfo.traceback
+        ntraceback = traceback.filter()
+        assert len(ntraceback) == len(traceback) - 1
+
+    def test_traceback_recursion_index(self):
+        def f(n):
+            if n < 10:
+                n += 1
+            f(n)
+        excinfo = py.test.raises(RuntimeError, f, 8)
+        traceback = excinfo.traceback
+        recindex = traceback.recursionindex()
+        assert recindex == 3
+
+    def test_traceback_only_specific_recursion_errors(self, monkeypatch):
+        def f(n):
+            if n == 0:
+                raise RuntimeError("hello")
+            f(n-1)
+
+        excinfo = pytest.raises(RuntimeError, f, 100)
+        monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
+        repr = excinfo.getrepr()
+        assert "RuntimeError: hello" in str(repr.reprcrash)
+
+    def test_traceback_no_recursion_index(self):
+        def do_stuff():
+            raise RuntimeError
+
+        def reraise_me():
+            import sys
+            exc, val, tb = sys.exc_info()
+            py.builtin._reraise(exc, val, tb)
+
+        def f(n):
+            try:
+                do_stuff()
+            except:
+                reraise_me()
+        excinfo = py.test.raises(RuntimeError, f, 8)
+        traceback = excinfo.traceback
+        recindex = traceback.recursionindex()
+        assert recindex is None
+
+    def test_traceback_messy_recursion(self):
+        # XXX: simplified locally testable version
+        decorator = py.test.importorskip('decorator').decorator
+
+        def log(f, *k, **kw):
+            print('%s %s' % (k, kw))
+            f(*k, **kw)
+        log = decorator(log)
+
+        def fail():
+            raise ValueError('')
+
+        fail = log(log(fail))
+
+        excinfo = py.test.raises(ValueError, fail)
+        assert excinfo.traceback.recursionindex() is None
+
+    def test_traceback_getcrashentry(self):
+        def i():
+            __tracebackhide__ = True
+            raise ValueError
+
+        def h():
+            i()
+
+        def g():
+            __tracebackhide__ = True
+            h()
+
+        def f():
+            g()
+
+        excinfo = py.test.raises(ValueError, f)
+        tb = excinfo.traceback
+        entry = tb.getcrashentry()
+        co = py.code.Code(h)
+        assert entry.frame.code.path == co.path
+        assert entry.lineno == co.firstlineno + 1
+        assert entry.frame.code.name == 'h'
+
+    def test_traceback_getcrashentry_empty(self):
+        def g():
+            __tracebackhide__ = True
+            raise ValueError
+
+        def f():
+            __tracebackhide__ = True
+            g()
+
+        excinfo = py.test.raises(ValueError, f)
+        tb = excinfo.traceback
+        entry = tb.getcrashentry()
+        co = py.code.Code(g)
+        assert entry.frame.code.path == co.path
+        assert entry.lineno == co.firstlineno + 2
+        assert entry.frame.code.name == 'g'
+
+
+def hello(x):
+    x + 5
+
+
+def test_tbentry_reinterpret():
+    try:
+        hello("hello")
+    except TypeError:
+        excinfo = py.code.ExceptionInfo()
+    tbentry = excinfo.traceback[-1]
+    msg = tbentry.reinterpret()
+    assert msg.startswith("TypeError: ('hello' + 5)")
+
+
+def test_excinfo_exconly():
+    excinfo = py.test.raises(ValueError, h)
+    assert excinfo.exconly().startswith('ValueError')
+    excinfo = py.test.raises(ValueError,
+        "raise ValueError('hello\\nworld')")
+    msg = excinfo.exconly(tryshort=True)
+    assert msg.startswith('ValueError')
+    assert msg.endswith("world")
+
+
+def test_excinfo_repr():
+    excinfo = py.test.raises(ValueError, h)
+    s = repr(excinfo)
+    assert s == "<ExceptionInfo ValueError tblen=4>"
+
+
+def test_excinfo_str():
+    excinfo = py.test.raises(ValueError, h)
+    s = str(excinfo)
+    assert s.startswith(__file__[:-9]) # pyc file and $py.class
+    assert s.endswith("ValueError")
+    assert len(s.split(":")) >= 3 # on windows it's 4
+
+
+def test_excinfo_errisinstance():
+    excinfo = py.test.raises(ValueError, h)
+    assert excinfo.errisinstance(ValueError)
+
+
+def test_excinfo_no_sourcecode():
+    try:
+        exec ("raise ValueError()")
+    except ValueError:
+        excinfo = py.code.ExceptionInfo()
+    s = str(excinfo.traceback[-1])
+    assert s == "  File '<string>':1 in <module>\n  ???\n"
+
+
+def test_excinfo_no_python_sourcecode(tmpdir):
+    #XXX: simplified locally testable version
+    tmpdir.join('test.txt').write("{{ h()}}:")
+
+    jinja2 = py.test.importorskip('jinja2')
+    loader = jinja2.FileSystemLoader(str(tmpdir))
+    env = jinja2.Environment(loader=loader)
+    template = env.get_template('test.txt')
+    excinfo = py.test.raises(ValueError,
+                             template.render, h=h)
+    for item in excinfo.traceback:
+        print(item) # XXX: for some reason jinja.Template.render is printed in full
+        item.source # shouldnt fail
+        if item.path.basename == 'test.txt':
+            assert str(item.source) == '{{ h()}}:'
+
+
+def test_entrysource_Queue_example():
+    try:
+        queue.Queue().get(timeout=0.001)
+    except queue.Empty:
+        excinfo = py.code.ExceptionInfo()
+    entry = excinfo.traceback[-1]
+    source = entry.getsource()
+    assert source is not None
+    s = str(source).strip()
+    assert s.startswith("def get")
+
+
+def test_codepath_Queue_example():
+    try:
+        queue.Queue().get(timeout=0.001)
+    except queue.Empty:
+        excinfo = py.code.ExceptionInfo()
+    entry = excinfo.traceback[-1]
+    path = entry.path
+    assert isinstance(path, py.path.local)
+    assert path.basename.lower() == "queue.py"
+    assert path.check()
+
+
+class TestFormattedExcinfo:
+    def pytest_funcarg__importasmod(self, request):
+        def importasmod(source):
+            source = py.code.Source(source)
+            tmpdir = request.getfuncargvalue("tmpdir")
+            modpath = tmpdir.join("mod.py")
+            tmpdir.ensure("__init__.py")
+            modpath.write(source)
+            if invalidate_import_caches is not None:
+                invalidate_import_caches()
+            return modpath.pyimport()
+        return importasmod
+
+    def excinfo_from_exec(self, source):
+        source = py.code.Source(source).strip()
+        try:
+            exec (source.compile())
+        except KeyboardInterrupt:
+            raise
+        except:
+            return py.code.ExceptionInfo()
+        assert 0, "did not raise"
+
+    def test_repr_source(self):
+        pr = FormattedExcinfo()
+        source = py.code.Source("""
+            def f(x):
+                pass
+        """).strip()
+        pr.flow_marker = "|"
+        lines = pr.get_source(source, 0)
+        assert len(lines) == 2
+        assert lines[0] == "|   def f(x):"
+        assert lines[1] == "        pass"
+
+    @broken_on_modern_pytest
+    def test_repr_source_excinfo(self):
+        """ check if indentation is right """
+        pr = FormattedExcinfo()
+        excinfo = self.excinfo_from_exec("""
+                def f():
+                    assert 0
+                f()
+        """)
+        pr = FormattedExcinfo()
+        source = pr._getentrysource(excinfo.traceback[-1])
+        lines = pr.get_source(source, 1, excinfo)
+        assert lines == [
+            '    def f():',
+            '>       assert 0',
+            'E       assert 0'
+        ]
+
+    def test_repr_source_not_existing(self):
+        pr = FormattedExcinfo()
+        co = compile("raise ValueError()", "", "exec")
+        try:
+            exec (co)
+        except ValueError:
+            excinfo = py.code.ExceptionInfo()
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
+
+    def test_repr_many_line_source_not_existing(self):
+        pr = FormattedExcinfo()
+        co = compile("""
+a = 1
+raise ValueError()
+""", "", "exec")
+        try:
+            exec (co)
+        except ValueError:
+            excinfo = py.code.ExceptionInfo()
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
+
+    def test_repr_source_failing_fullsource(self):
+        pr = FormattedExcinfo()
+
+        class FakeCode(object):
+            class raw:
+                co_filename = '?'
+            path = '?'
+            firstlineno = 5
+
+            def fullsource(self):
+                return None
+            fullsource = property(fullsource)
+
+        class FakeFrame(object):
+            code = FakeCode()
+            f_locals = {}
+            f_globals = {}
+
+        class FakeTracebackEntry(py.code.Traceback.Entry):
+            def __init__(self, tb):
+                self.lineno = 5+3
+
+            @property
+            def frame(self):
+                return FakeFrame()
+
+        class Traceback(py.code.Traceback):
+            Entry = FakeTracebackEntry
+
+        class FakeExcinfo(py.code.ExceptionInfo):
+            typename = "Foo"
+            def __init__(self):
+                pass
+
+            def exconly(self, tryshort):
+                return "EXC"
+            def errisinstance(self, cls):
+                return False
+
+        excinfo = FakeExcinfo()
+        class FakeRawTB(object):
+            tb_next = None
+        tb = FakeRawTB()
+        excinfo.traceback = Traceback(tb)
+
+        fail = IOError()
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
+
+        fail = py.error.ENOENT
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
+
+
+    def test_repr_local(self):
+        p = FormattedExcinfo(showlocals=True)
+        loc = {'y': 5, 'z': 7, 'x': 3, '@x': 2, '__builtins__': {}}
+        reprlocals = p.repr_locals(loc)
+        assert reprlocals.lines
+        assert reprlocals.lines[0] == '__builtins__ = <builtins>'
+        assert reprlocals.lines[1] == 'x          = 3'
+        assert reprlocals.lines[2] == 'y          = 5'
+        assert reprlocals.lines[3] == 'z          = 7'
+
+    def test_repr_tracebackentry_lines(self, importasmod):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello\\nworld")
+        """)
+        excinfo = py.test.raises(ValueError, mod.func1)
+        excinfo.traceback = excinfo.traceback.filter()
+        p = FormattedExcinfo()
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
+
+        # test as intermittent entry
+        lines = reprtb.lines
+        assert lines[0] == '    def func1():'
+        assert lines[1] == '>       raise ValueError("hello\\nworld")'
+
+        # test as last entry
+        p = FormattedExcinfo(showlocals=True)
+        repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        lines = repr_entry.lines
+        assert lines[0] == '    def func1():'
+        assert lines[1] == '>       raise ValueError("hello\\nworld")'
+        assert lines[2] == 'E       ValueError: hello'
+        assert lines[3] == 'E       world'
+        assert not lines[4:]
+
+        loc = repr_entry.reprlocals is not None
+        loc = repr_entry.reprfileloc
+        assert loc.path == mod.__file__
+        assert loc.lineno == 3
+        #assert loc.message == "ValueError: hello"
+
+    def test_repr_tracebackentry_lines(self, importasmod):
+        mod = importasmod("""
+            def func1(m, x, y, z):
+                raise ValueError("hello\\nworld")
+        """)
+        excinfo = py.test.raises(ValueError, mod.func1, "m"*90, 5, 13, "z"*120)
+        excinfo.traceback = excinfo.traceback.filter()
+        entry = excinfo.traceback[-1]
+        p = FormattedExcinfo(funcargs=True)
+        reprfuncargs = p.repr_args(entry)
+        assert reprfuncargs.args[0] == ('m', repr("m"*90))
+        assert reprfuncargs.args[1] == ('x', '5')
+        assert reprfuncargs.args[2] == ('y', '13')
+        assert reprfuncargs.args[3] == ('z', repr("z" * 120))
+
+        p = FormattedExcinfo(funcargs=True)
+        repr_entry = p.repr_traceback_entry(entry)
+        assert repr_entry.reprfuncargs.args == reprfuncargs.args
+        tw = TWMock()
+        repr_entry.toterminal(tw)
+        assert tw.lines[0] == "m = " + repr('m' * 90)
+        assert tw.lines[1] == "x = 5, y = 13"
+        assert tw.lines[2] == "z = " + repr('z' * 120)
+
+    def test_repr_tracebackentry_lines_var_kw_args(self, importasmod):
+        mod = importasmod("""
+            def func1(x, *y, **z):
+                raise ValueError("hello\\nworld")
+        """)
+        excinfo = py.test.raises(ValueError, mod.func1, 'a', 'b', c='d')
+        excinfo.traceback = excinfo.traceback.filter()
+        entry = excinfo.traceback[-1]
+        p = FormattedExcinfo(funcargs=True)
+        reprfuncargs = p.repr_args(entry)
+        assert reprfuncargs.args[0] == ('x', repr('a'))
+        assert reprfuncargs.args[1] == ('y', repr(('b',)))
+        assert reprfuncargs.args[2] == ('z', repr({'c': 'd'}))
+
+        p = FormattedExcinfo(funcargs=True)
+        repr_entry = p.repr_traceback_entry(entry)
+        assert repr_entry.reprfuncargs.args == reprfuncargs.args
+        tw = TWMock()
+        repr_entry.toterminal(tw)
+        assert tw.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"
+
+    def test_repr_tracebackentry_short(self, importasmod):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello")
+            def entry():
+                func1()
+        """)
+        excinfo = py.test.raises(ValueError, mod.entry)
+        p = FormattedExcinfo(style="short")
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
+        lines = reprtb.lines
+        basename = py.path.local(mod.__file__).basename
+        assert lines[0] == '    func1()'
+        assert basename in str(reprtb.reprfileloc.path)
+        assert reprtb.reprfileloc.lineno == 5
+
+        # test last entry
+        p = FormattedExcinfo(style="short")
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        lines = reprtb.lines
+        assert lines[0] == '    raise ValueError("hello")'
+        assert lines[1] == 'E   ValueError: hello'
+        assert basename in str(reprtb.reprfileloc.path)
+        assert reprtb.reprfileloc.lineno == 3
+
+    def test_repr_tracebackentry_no(self, importasmod):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello")
+            def entry():
+                func1()
+        """)
+        excinfo = py.test.raises(ValueError, mod.entry)
+        p = FormattedExcinfo(style="no")
+        p.repr_traceback_entry(excinfo.traceback[-2])
+
+        p = FormattedExcinfo(style="no")
+        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        lines = reprentry.lines
+        assert lines[0] == 'E   ValueError: hello'
+        assert not lines[1:]
+
+    def test_repr_traceback_tbfilter(self, importasmod):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        excinfo = py.test.raises(ValueError, mod.entry)
+        p = FormattedExcinfo(tbfilter=True)
+        reprtb = p.repr_traceback(excinfo)
+        assert len(reprtb.reprentries) == 2
+        p = FormattedExcinfo(tbfilter=False)
+        reprtb = p.repr_traceback(excinfo)
+        assert len(reprtb.reprentries) == 3
+
+    def test_traceback_short_no_source(self, importasmod, monkeypatch):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello")
+            def entry():
+                func1()
+        """)
+        try:
+            mod.entry()
+        except ValueError:
+            excinfo = py.code.ExceptionInfo()
+        from py._code.code import Code
+        monkeypatch.setattr(Code, 'path', 'bogus')
+        excinfo.traceback[0].frame.code.path = "bogus"
+        p = FormattedExcinfo(style="short")
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
+        lines = reprtb.lines
+        last_p = FormattedExcinfo(style="short")
+        last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        last_lines = last_reprtb.lines
+        monkeypatch.undo()
+        basename = py.path.local(mod.__file__).basename
+        assert lines[0] == '    func1()'
+
+        assert last_lines[0] == '    raise ValueError("hello")'
+        assert last_lines[1] == 'E   ValueError: hello'
+
+    def test_repr_traceback_and_excinfo(self, importasmod):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        excinfo = py.test.raises(ValueError, mod.entry)
+
+        for style in ("long", "short"):
+            p = FormattedExcinfo(style=style)
+            reprtb = p.repr_traceback(excinfo)
+            assert len(reprtb.reprentries) == 2
+            assert reprtb.style == style
+            assert not reprtb.extraline
+            repr = p.repr_excinfo(excinfo)
+            assert repr.reprtraceback
+            assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries)
+            assert repr.reprcrash.path.endswith("mod.py")
+            assert repr.reprcrash.message == "ValueError: 0"
+
+    def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        excinfo = py.test.raises(ValueError, mod.entry)
+
+        p = FormattedExcinfo()
+        def raiseos():
+            raise OSError(2)
+        monkeypatch.setattr('os.getcwd', raiseos)
+        assert p._makepath(__file__) == __file__
+        reprtb = p.repr_traceback(excinfo)
+
+    @broken_on_modern_pytest
+    def test_repr_excinfo_addouterr(self, importasmod):
+        mod = importasmod("""
+            def entry():
+                raise ValueError()
+        """)
+        excinfo = py.test.raises(ValueError, mod.entry)
+        repr = excinfo.getrepr()
+        repr.addsection("title", "content")
+        twmock = TWMock()
+        repr.toterminal(twmock)
+        assert twmock.lines[-1] == "content"
+        assert twmock.lines[-2] == ("-", "title")
+
+    def test_repr_excinfo_reprcrash(self, importasmod):
+        mod = importasmod("""
+            def entry():
+                raise ValueError()
+        """)
+        excinfo = py.test.raises(ValueError, mod.entry)
+        repr = excinfo.getrepr()
+        assert repr.reprcrash.path.endswith("mod.py")
+        assert repr.reprcrash.lineno == 3
+        assert repr.reprcrash.message == "ValueError"
+        assert str(repr.reprcrash).endswith("mod.py:3: ValueError")
+
+    def test_repr_traceback_recursion(self, importasmod):
+        mod = importasmod("""
+            def rec2(x):
+                return rec1(x+1)
+            def rec1(x):
+                return rec2(x-1)
+            def entry():
+                rec1(42)
+        """)
+        excinfo = py.test.raises(RuntimeError, mod.entry)
+
+        for style in ("short", "long", "no"):
+            p = FormattedExcinfo(style="short")
+            reprtb = p.repr_traceback(excinfo)
+            assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
+            assert str(reprtb)
+
+    @broken_on_modern_pytest
+    def test_tb_entry_AssertionError(self, importasmod):
+        # probably this test is a bit redundant
+        # as py/magic/testing/test_assertion.py
+        # already tests correctness of
+        # assertion-reinterpretation  logic
+        mod = importasmod("""
+            def somefunc():
+                x = 1
+                assert x == 2
+        """)
+        excinfo = py.test.raises(AssertionError, mod.somefunc)
+
+        p = FormattedExcinfo()
+        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        lines = reprentry.lines
+        assert lines[-1] == "E       assert 1 == 2"
+
+    def test_reprexcinfo_getrepr(self, importasmod):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        try:
+            mod.entry()
+        except ValueError:
+            excinfo = py.code.ExceptionInfo()
+
+        for style in ("short", "long", "no"):
+            for showlocals in (True, False):
+                repr = excinfo.getrepr(style=style, showlocals=showlocals)
+                assert isinstance(repr, ReprExceptionInfo)
+                assert repr.reprtraceback.style == style
+
+    def test_reprexcinfo_unicode(self):
+        from py._code.code import TerminalRepr
+        class MyRepr(TerminalRepr):
+            def toterminal(self, tw):
+                tw.line(py.builtin._totext("я", "utf-8"))
+        x = py.builtin._totext(MyRepr())
+        assert x == py.builtin._totext("я", "utf-8")
+
+    @broken_on_modern_pytest
+    def test_toterminal_long(self, importasmod):
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = py.test.raises(ValueError, mod.f)
+        excinfo.traceback = excinfo.traceback.filter()
+        repr = excinfo.getrepr()
+        tw = TWMock()
+        repr.toterminal(tw)
+        assert tw.lines[0] == ""
+        tw.lines.pop(0)
+        assert tw.lines[0] == "    def f():"
+        assert tw.lines[1] == ">       g(3)"
+        assert tw.lines[2] == ""
+        assert tw.lines[3].endswith("mod.py:5: ")
+        assert tw.lines[4] == ("_ ", None)
+        assert tw.lines[5] == ""
+        assert tw.lines[6] == "    def g(x):"
+        assert tw.lines[7] == ">       raise ValueError(x)"
+        assert tw.lines[8] == "E       ValueError: 3"
+        assert tw.lines[9] == ""
+        assert tw.lines[10].endswith("mod.py:3: ValueError")
+
+    @broken_on_modern_pytest
+    def test_toterminal_long_missing_source(self, importasmod, tmpdir):
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = py.test.raises(ValueError, mod.f)
+        tmpdir.join('mod.py').remove()
+        excinfo.traceback = excinfo.traceback.filter()
+        repr = excinfo.getrepr()
+        tw = TWMock()
+        repr.toterminal(tw)
+        assert tw.lines[0] == ""
+        tw.lines.pop(0)
+        assert tw.lines[0] == ">   ???"
+        assert tw.lines[1] == ""
+        assert tw.lines[2].endswith("mod.py:5: ")
+        assert tw.lines[3] == ("_ ", None)
+        assert tw.lines[4] == ""
+        assert tw.lines[5] == ">   ???"
+        assert tw.lines[6] == "E   ValueError: 3"
+        assert tw.lines[7] == ""
+        assert tw.lines[8].endswith("mod.py:3: ValueError")
+
+    @broken_on_modern_pytest
+    def test_toterminal_long_incomplete_source(self, importasmod, tmpdir):
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = py.test.raises(ValueError, mod.f)
+        tmpdir.join('mod.py').write('asdf')
+        excinfo.traceback = excinfo.traceback.filter()
+        repr = excinfo.getrepr()
+        tw = TWMock()
+        repr.toterminal(tw)
+        assert tw.lines[0] == ""
+        tw.lines.pop(0)
+        assert tw.lines[0] == ">   ???"
+        assert tw.lines[1] == ""
+        assert tw.lines[2].endswith("mod.py:5: ")
+        assert tw.lines[3] == ("_ ", None)
+        assert tw.lines[4] == ""
+        assert tw.lines[5] == ">   ???"
+        assert tw.lines[6] == "E   ValueError: 3"
+        assert tw.lines[7] == ""
+        assert tw.lines[8].endswith("mod.py:3: ValueError")
+
+    @broken_on_modern_pytest
+    def test_toterminal_long_filenames(self, importasmod):
+        mod = importasmod("""
+            def f():
+                raise ValueError()
+        """)
+        excinfo = py.test.raises(ValueError, mod.f)
+        tw = TWMock()
+        path = py.path.local(mod.__file__)
+        old = path.dirpath().chdir()
+        try:
+            repr = excinfo.getrepr(abspath=False)
+            repr.toterminal(tw)
+            line = tw.lines[-1]
+            x = py.path.local().bestrelpath(path)
+            if len(x) < len(str(path)):
+                assert line == "mod.py:3: ValueError"
+
+            repr = excinfo.getrepr(abspath=True)
+            repr.toterminal(tw)
+            line = tw.lines[-1]
+            assert line == "%s:3: ValueError" %(path,)
+        finally:
+            old.chdir()
+
+    @pytest.mark.parametrize('style', ("long", "short", "no"))
+    @pytest.mark.parametrize('showlocals', (True, False),
+                             ids=['locals', 'nolocals'])
+    @pytest.mark.parametrize('tbfilter', (True, False),
+                             ids=['tbfilter', 'nofilter'])
+    @pytest.mark.parametrize('funcargs', (True, False),
+                             ids=['funcargs', 'nofuncargs'])
+    def test_format_excinfo(self, importasmod,
+                            style, showlocals, tbfilter, funcargs):
+        
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = py.test.raises(ValueError, mod.f)
+        tw = py.io.TerminalWriter(stringio=True)
+        repr = excinfo.getrepr(
+            style=style,
+            showlocals=showlocals,
+            funcargs=funcargs,
+            tbfilter=tbfilter
+        )
+        repr.toterminal(tw)
+        assert tw.stringio.getvalue()
+
+    @broken_on_modern_pytest
+    def test_native_style(self):
+        excinfo = self.excinfo_from_exec("""
+            assert 0
+        """)
+        repr = excinfo.getrepr(style='native')
+        assert "assert 0" in str(repr.reprcrash)
+        s = str(repr)
+        assert s.startswith('Traceback (most recent call last):\n  File')
+        assert s.endswith('\nAssertionError: assert 0')
+        assert 'exec (source.compile())' in s
+        assert s.count('assert 0') == 2
+
+    @broken_on_modern_pytest
+    def test_traceback_repr_style(self, importasmod):
+        mod = importasmod("""
+            def f():
+                g()
+            def g():
+                h()
+            def h():
+                i()
+            def i():
+                raise ValueError()
+        """)
+        excinfo = py.test.raises(ValueError, mod.f)
+        excinfo.traceback = excinfo.traceback.filter()
+        excinfo.traceback[1].set_repr_style("short")
+        excinfo.traceback[2].set_repr_style("short")
+        r = excinfo.getrepr(style="long")
+        tw = TWMock()
+        r.toterminal(tw)
+        for line in tw.lines: print (line)
+        assert tw.lines[0] == ""
+        assert tw.lines[1] == "    def f():"
+        assert tw.lines[2] == ">       g()"
+        assert tw.lines[3] == ""
+        assert tw.lines[4].endswith("mod.py:3: ")
+        assert tw.lines[5] == ("_ ", None)
+        assert tw.lines[6].endswith("in g")
+        assert tw.lines[7] == "    h()"
+        assert tw.lines[8].endswith("in h")
+        assert tw.lines[9] == "    i()"
+        assert tw.lines[10] == ("_ ", None)
+        assert tw.lines[11] == ""
+        assert tw.lines[12] == "    def i():"
+        assert tw.lines[13] == ">       raise ValueError()"
+        assert tw.lines[14] == "E       ValueError"
+        assert tw.lines[15] == ""
+        assert tw.lines[16].endswith("mod.py:9: ValueError")
diff --git a/tools/third_party/py/testing/code/test_source.py b/tools/third_party/py/testing/code/test_source.py
new file mode 100644
index 0000000..3492761
--- /dev/null
+++ b/tools/third_party/py/testing/code/test_source.py
@@ -0,0 +1,648 @@
+from py.code import Source
+import py
+import sys
+import inspect
+
+from py._code.source import _ast
+if _ast is not None:
+    astonly = py.test.mark.nothing
+else:
+    astonly = py.test.mark.xfail("True", reason="only works with AST-compile")
+
+failsonjython = py.test.mark.xfail("sys.platform.startswith('java')")
+
+def test_source_str_function():
+    x = Source("3")
+    assert str(x) == "3"
+
+    x = Source("   3")
+    assert str(x) == "3"
+
+    x = Source("""
+        3
+    """, rstrip=False)
+    assert str(x) == "\n3\n    "
+
+    x = Source("""
+        3
+    """, rstrip=True)
+    assert str(x) == "\n3"
+
+def test_unicode():
+    try:
+        unicode
+    except NameError:
+        return
+    x = Source(unicode("4"))
+    assert str(x) == "4"
+    co = py.code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval')
+    val = eval(co)
+    assert isinstance(val, unicode)
+
+def test_source_from_function():
+    source = py.code.Source(test_source_str_function)
+    assert str(source).startswith('def test_source_str_function():')
+
+def test_source_from_method():
+    class TestClass:
+        def test_method(self):
+            pass
+    source = py.code.Source(TestClass().test_method)
+    assert source.lines == ["def test_method(self):",
+                            "    pass"]
+
+def test_source_from_lines():
+    lines = ["a \n", "b\n", "c"]
+    source = py.code.Source(lines)
+    assert source.lines == ['a ', 'b', 'c']
+
+def test_source_from_inner_function():
+    def f():
+        pass
+    source = py.code.Source(f, deindent=False)
+    assert str(source).startswith('    def f():')
+    source = py.code.Source(f)
+    assert str(source).startswith('def f():')
+
+def test_source_putaround_simple():
+    source = Source("raise ValueError")
+    source = source.putaround(
+        "try:", """\
+        except ValueError:
+            x = 42
+        else:
+            x = 23""")
+    assert str(source)=="""\
+try:
+    raise ValueError
+except ValueError:
+    x = 42
+else:
+    x = 23"""
+
+def test_source_putaround():
+    source = Source()
+    source = source.putaround("""
+        if 1:
+            x=1
+    """)
+    assert str(source).strip() == "if 1:\n    x=1"
+
+def test_source_strips():
+    source = Source("")
+    assert source == Source()
+    assert str(source) == ''
+    assert source.strip() == source
+
+def test_source_strip_multiline():
+    source = Source()
+    source.lines = ["", " hello", "  "]
+    source2 = source.strip()
+    assert source2.lines == [" hello"]
+
+def test_syntaxerror_rerepresentation():
+    ex = py.test.raises(SyntaxError, py.code.compile, 'xyz xyz')
+    assert ex.value.lineno == 1
+    assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython?
+    assert ex.value.text.strip(), 'x x'
+
+def test_isparseable():
+    assert Source("hello").isparseable()
+    assert Source("if 1:\n  pass").isparseable()
+    assert Source(" \nif 1:\n  pass").isparseable()
+    assert not Source("if 1:\n").isparseable()
+    assert not Source(" \nif 1:\npass").isparseable()
+    assert not Source(chr(0)).isparseable()
+
+class TestAccesses:
+    source = Source("""\
+        def f(x):
+            pass
+        def g(x):
+            pass
+    """)
+    def test_getrange(self):
+        x = self.source[0:2]
+        assert x.isparseable()
+        assert len(x.lines) == 2
+        assert str(x) == "def f(x):\n    pass"
+
+    def test_getline(self):
+        x = self.source[0]
+        assert x == "def f(x):"
+
+    def test_len(self):
+        assert len(self.source) == 4
+
+    def test_iter(self):
+        l = [x for x in self.source]
+        assert len(l) == 4
+
+class TestSourceParsingAndCompiling:
+    source = Source("""\
+        def f(x):
+            assert (x ==
+                    3 +
+                    4)
+    """).strip()
+
+    def test_compile(self):
+        co = py.code.compile("x=3")
+        d = {}
+        exec (co, d)
+        assert d['x'] == 3
+
+    def test_compile_and_getsource_simple(self):
+        co = py.code.compile("x=3")
+        exec (co)
+        source = py.code.Source(co)
+        assert str(source) == "x=3"
+
+    def test_compile_and_getsource_through_same_function(self):
+        def gensource(source):
+            return py.code.compile(source)
+        co1 = gensource("""
+            def f():
+                raise KeyError()
+        """)
+        co2 = gensource("""
+            def f():
+                raise ValueError()
+        """)
+        source1 = inspect.getsource(co1)
+        assert 'KeyError' in source1
+        source2 = inspect.getsource(co2)
+        assert 'ValueError' in source2
+
+    def test_getstatement(self):
+        #print str(self.source)
+        ass = str(self.source[1:])
+        for i in range(1, 4):
+            #print "trying start in line %r" % self.source[i]
+            s = self.source.getstatement(i)
+            #x = s.deindent()
+            assert str(s) == ass
+
+    def test_getstatementrange_triple_quoted(self):
+        #print str(self.source)
+        source = Source("""hello('''
+        ''')""")
+        s = source.getstatement(0)
+        assert s == str(source)
+        s = source.getstatement(1)
+        assert s == str(source)
+
+    @astonly
+    def test_getstatementrange_within_constructs(self):
+        source = Source("""\
+            try:
+                try:
+                    raise ValueError
+                except SomeThing:
+                    pass
+            finally:
+                42
+        """)
+        assert len(source) == 7
+        # check all lineno's that could occur in a traceback
+        #assert source.getstatementrange(0) == (0, 7)
+        #assert source.getstatementrange(1) == (1, 5)
+        assert source.getstatementrange(2) == (2, 3)
+        assert source.getstatementrange(3) == (3, 4)
+        assert source.getstatementrange(4) == (4, 5)
+        #assert source.getstatementrange(5) == (0, 7)
+        assert source.getstatementrange(6) == (6, 7)
+
+    def test_getstatementrange_bug(self):
+        source = Source("""\
+            try:
+                x = (
+                   y +
+                   z)
+            except:
+                pass
+        """)
+        assert len(source) == 6
+        assert source.getstatementrange(2) == (1, 4)
+
+    def test_getstatementrange_bug2(self):
+        source = Source("""\
+            assert (
+                33
+                ==
+                [
+                  X(3,
+                      b=1, c=2
+                   ),
+                ]
+              )
+        """)
+        assert len(source) == 9
+        assert source.getstatementrange(5) == (0, 9)
+
+    def test_getstatementrange_ast_issue58(self):
+        source = Source("""\
+
+            def test_some():
+                for a in [a for a in
+                    CAUSE_ERROR]: pass
+
+            x = 3
+        """)
+        assert getstatement(2, source).lines == source.lines[2:3]
+        assert getstatement(3, source).lines == source.lines[3:4]
+
+    def test_getstatementrange_out_of_bounds_py3(self):
+        source = Source("if xxx:\n   from .collections import something")
+        r = source.getstatementrange(1)
+        assert r == (1,2)
+
+    def test_getstatementrange_with_syntaxerror_issue7(self):
+        source = Source(":")
+        py.test.raises(SyntaxError, lambda: source.getstatementrange(0))
+
+    def test_compile_to_ast(self):
+        import ast
+        source = Source("x = 4")
+        mod = source.compile(flag=ast.PyCF_ONLY_AST)
+        assert isinstance(mod, ast.Module)
+        compile(mod, "<filename>", "exec")
+
+    def test_compile_and_getsource(self):
+        co = self.source.compile()
+        py.builtin.exec_(co, globals())
+        f(7)
+        excinfo = py.test.raises(AssertionError, "f(6)")
+        frame = excinfo.traceback[-1].frame
+        stmt = frame.code.fullsource.getstatement(frame.lineno)
+        #print "block", str(block)
+        assert str(stmt).strip().startswith('assert')
+
+    def test_compilefuncs_and_path_sanity(self):
+        def check(comp, name):
+            co = comp(self.source, name)
+            if not name:
+                expected = "codegen %s:%d>" %(mypath, mylineno+2+1)
+            else:
+                expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+1)
+            fn = co.co_filename
+            assert fn.endswith(expected)
+
+        mycode = py.code.Code(self.test_compilefuncs_and_path_sanity)
+        mylineno = mycode.firstlineno
+        mypath = mycode.path
+
+        for comp in py.code.compile, py.code.Source.compile:
+            for name in '', None, 'my':
+                yield check, comp, name
+
+    def test_offsetless_synerr(self):
+        py.test.raises(SyntaxError, py.code.compile, "lambda a,a: 0", mode='eval')
+
+def test_getstartingblock_singleline():
+    class A:
+        def __init__(self, *args):
+            frame = sys._getframe(1)
+            self.source = py.code.Frame(frame).statement
+
+    x = A('x', 'y')
+
+    l = [i for i in x.source.lines if i.strip()]
+    assert len(l) == 1
+
+def test_getstartingblock_multiline():
+    class A:
+        def __init__(self, *args):
+            frame = sys._getframe(1)
+            self.source = py.code.Frame(frame).statement
+
+    x = A('x',
+          'y' \
+          ,
+          'z')
+
+    l = [i for i in x.source.lines if i.strip()]
+    assert len(l) == 4
+
+def test_getline_finally():
+    def c(): pass
+    excinfo = py.test.raises(TypeError, """
+           teardown = None
+           try:
+                c(1)
+           finally:
+                if teardown:
+                    teardown()
+    """)
+    source = excinfo.traceback[-1].statement
+    assert str(source).strip() == 'c(1)'
+
+def test_getfuncsource_dynamic():
+    source = """
+        def f():
+            raise ValueError
+
+        def g(): pass
+    """
+    co = py.code.compile(source)
+    py.builtin.exec_(co, globals())
+    assert str(py.code.Source(f)).strip() == 'def f():\n    raise ValueError'
+    assert str(py.code.Source(g)).strip() == 'def g(): pass'
+
+
+def test_getfuncsource_with_multine_string():
+    def f():
+        c = '''while True:
+    pass
+'''
+    assert str(py.code.Source(f)).strip() == "def f():\n    c = '''while True:\n    pass\n'''"
+
+
+def test_deindent():
+    from py._code.source import deindent as deindent
+    assert deindent(['\tfoo', '\tbar', ]) == ['foo', 'bar']
+
+    def f():
+        c = '''while True:
+    pass
+'''
+    import inspect
+    lines = deindent(inspect.getsource(f).splitlines())
+    assert lines == ["def f():", "    c = '''while True:", "    pass", "'''"]
+
+    source = """
+        def f():
+            def g():
+                pass
+    """
+    lines = deindent(source.splitlines())
+    assert lines == ['', 'def f():', '    def g():', '        pass', '    ']
+
+def test_source_of_class_at_eof_without_newline(tmpdir):
+    # this test fails because the implicit inspect.getsource(A) below
+    # does not return the "x = 1" last line.
+    source = py.code.Source('''
+        class A(object):
+            def method(self):
+                x = 1
+    ''')
+    path = tmpdir.join("a.py")
+    path.write(source)
+    s2 = py.code.Source(tmpdir.join("a.py").pyimport().A)
+    assert str(source).strip() == str(s2).strip()
+
+if True:
+    def x():
+        pass
+
+def test_getsource_fallback():
+    from py._code.source import getsource
+    expected = """def x():
+    pass"""
+    src = getsource(x)
+    assert src == expected
+
+def test_idem_compile_and_getsource():
+    from py._code.source import getsource
+    expected = "def x(): pass"
+    co = py.code.compile(expected)
+    src = getsource(co)
+    assert src == expected
+
+def test_findsource_fallback():
+    from py._code.source import findsource
+    src, lineno = findsource(x)
+    assert 'test_findsource_simple' in str(src)
+    assert src[lineno] == '    def x():'
+
+def test_findsource():
+    from py._code.source import findsource
+    co = py.code.compile("""if 1:
+    def x():
+        pass
+""")
+
+    src, lineno = findsource(co)
+    assert 'if 1:' in str(src)
+
+    d = {}
+    eval(co, d)
+    src, lineno = findsource(d['x'])
+    assert 'if 1:' in str(src)
+    assert src[lineno] == "    def x():"
+
+
+def test_getfslineno():
+    from py.code import getfslineno
+
+    def f(x):
+        pass
+
+    fspath, lineno = getfslineno(f)
+
+    assert fspath.basename == "test_source.py"
+    assert lineno == py.code.getrawcode(f).co_firstlineno-1 # see findsource
+
+    class A(object):
+        pass
+
+    fspath, lineno = getfslineno(A)
+
+    _, A_lineno = inspect.findsource(A)
+    assert fspath.basename == "test_source.py"
+    assert lineno == A_lineno
+
+    assert getfslineno(3) == ("", -1)
+    class B:
+        pass
+    B.__name__ = "B2"
+    assert getfslineno(B)[1] == -1
+
+def test_code_of_object_instance_with_call():
+    class A:
+        pass
+    py.test.raises(TypeError, lambda: py.code.Source(A()))
+    class WithCall:
+        def __call__(self):
+            pass
+
+    code = py.code.Code(WithCall())
+    assert 'pass' in str(code.source())
+
+    class Hello(object):
+        def __call__(self):
+            pass
+    py.test.raises(TypeError, lambda: py.code.Code(Hello))
+
+
+def getstatement(lineno, source):
+    from py._code.source import getstatementrange_ast
+    source = py.code.Source(source, deindent=False)
+    ast, start, end = getstatementrange_ast(lineno, source)
+    return source[start:end]
+
+def test_oneline():
+    source = getstatement(0, "raise ValueError")
+    assert str(source) == "raise ValueError"
+
+def test_comment_and_no_newline_at_end():
+    from py._code.source import getstatementrange_ast
+    source = Source(['def test_basic_complex():',
+                     '    assert 1 == 2',
+                     '# vim: filetype=pyopencl:fdm=marker'])
+    ast, start, end = getstatementrange_ast(1, source)
+    assert end == 2
+
+def test_oneline_and_comment():
+    source = getstatement(0, "raise ValueError\n#hello")
+    assert str(source) == "raise ValueError"
+
+def test_comments():
+    source = '''def test():
+    "comment 1"
+    x = 1
+      # comment 2
+    # comment 3
+
+    assert False
+
+"""
+comment 4
+"""
+'''
+    for line in range(2,6):
+        assert str(getstatement(line, source)) == '    x = 1'
+    for line in range(6,10):
+        assert str(getstatement(line, source)) == '    assert False'
+    assert str(getstatement(10, source)) == '"""'
+
+def test_comment_in_statement():
+    source = '''test(foo=1,
+    # comment 1
+    bar=2)
+'''
+    for line in range(1,3):
+        assert str(getstatement(line, source)) == \
+               'test(foo=1,\n    # comment 1\n    bar=2)'
+
+def test_single_line_else():
+    source = getstatement(1, "if False: 2\nelse: 3")
+    assert str(source) == "else: 3"
+
+def test_single_line_finally():
+    source = getstatement(1, "try: 1\nfinally: 3")
+    assert str(source) == "finally: 3"
+
+def test_issue55():
+    source = ('def round_trip(dinp):\n  assert 1 == dinp\n'
+              'def test_rt():\n  round_trip("""\n""")\n')
+    s = getstatement(3, source)
+    assert str(s) == '  round_trip("""\n""")'
+
+
+def XXXtest_multiline():
+    source = getstatement(0, """\
+raise ValueError(
+    23
+)
+x = 3
+""")
+    assert str(source) == "raise ValueError(\n    23\n)"
+
+class TestTry:
+    pytestmark = astonly
+    source = """\
+try:
+    raise ValueError
+except Something:
+    raise IndexError(1)
+else:
+    raise KeyError()
+"""
+
+    def test_body(self):
+        source = getstatement(1, self.source)
+        assert str(source) == "    raise ValueError"
+
+    def test_except_line(self):
+        source = getstatement(2, self.source)
+        assert str(source) == "except Something:"
+
+    def test_except_body(self):
+        source = getstatement(3, self.source)
+        assert str(source) == "    raise IndexError(1)"
+
+    def test_else(self):
+        source = getstatement(5, self.source)
+        assert str(source) == "    raise KeyError()"
+
+class TestTryFinally:
+    source = """\
+try:
+    raise ValueError
+finally:
+    raise IndexError(1)
+"""
+
+    def test_body(self):
+        source = getstatement(1, self.source)
+        assert str(source) == "    raise ValueError"
+
+    def test_finally(self):
+        source = getstatement(3, self.source)
+        assert str(source) == "    raise IndexError(1)"
+
+
+
+class TestIf:
+    pytestmark = astonly
+    source = """\
+if 1:
+    y = 3
+elif False:
+    y = 5
+else:
+    y = 7
+"""
+
+    def test_body(self):
+        source = getstatement(1, self.source)
+        assert str(source) == "    y = 3"
+
+    def test_elif_clause(self):
+        source = getstatement(2, self.source)
+        assert str(source) == "elif False:"
+
+    def test_elif(self):
+        source = getstatement(3, self.source)
+        assert str(source) == "    y = 5"
+
+    def test_else(self):
+        source = getstatement(5, self.source)
+        assert str(source) == "    y = 7"
+
+def test_semicolon():
+    s = """\
+hello ; pytest.skip()
+"""
+    source = getstatement(0, s)
+    assert str(source) == s.strip()
+
+def test_def_online():
+    s = """\
+def func(): raise ValueError(42)
+
+def something():
+    pass
+"""
+    source = getstatement(0, s)
+    assert str(source) == "def func(): raise ValueError(42)"
+
+def XXX_test_expression_multiline():
+    source = """\
+something
+'''
+'''"""
+    result = getstatement(1, source)
+    assert str(result) == "'''\n'''"
+
diff --git a/tools/py/testing/conftest.py b/tools/third_party/py/testing/conftest.py
similarity index 100%
rename from tools/py/testing/conftest.py
rename to tools/third_party/py/testing/conftest.py
diff --git a/tools/py/testing/io_/__init__.py b/tools/third_party/py/testing/io_/__init__.py
similarity index 100%
rename from tools/py/testing/io_/__init__.py
rename to tools/third_party/py/testing/io_/__init__.py
diff --git a/tools/third_party/py/testing/io_/test_capture.py b/tools/third_party/py/testing/io_/test_capture.py
new file mode 100644
index 0000000..b5fedd0
--- /dev/null
+++ b/tools/third_party/py/testing/io_/test_capture.py
@@ -0,0 +1,501 @@
+from __future__ import with_statement
+
+import os, sys
+import py
+
+needsdup = py.test.mark.skipif("not hasattr(os, 'dup')")
+
+from py.builtin import print_
+
+if sys.version_info >= (3,0):
+    def tobytes(obj):
+        if isinstance(obj, str):
+            obj = obj.encode('UTF-8')
+        assert isinstance(obj, bytes)
+        return obj
+    def totext(obj):
+        if isinstance(obj, bytes):
+            obj = str(obj, 'UTF-8')
+        assert isinstance(obj, str)
+        return obj
+else:
+    def tobytes(obj):
+        if isinstance(obj, unicode):
+            obj = obj.encode('UTF-8')
+        assert isinstance(obj, str)
+        return obj
+    def totext(obj):
+        if isinstance(obj, str):
+            obj = unicode(obj, 'UTF-8')
+        assert isinstance(obj, unicode)
+        return obj
+
+def oswritebytes(fd, obj):
+    os.write(fd, tobytes(obj))
+
+class TestTextIO:
+    def test_text(self):
+        f = py.io.TextIO()
+        f.write("hello")
+        s = f.getvalue()
+        assert s == "hello"
+        f.close()
+
+    def test_unicode_and_str_mixture(self):
+        f = py.io.TextIO()
+        if sys.version_info >= (3,0):
+            f.write("\u00f6")
+            py.test.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))")
+        else:
+            f.write(unicode("\u00f6", 'UTF-8'))
+            f.write("hello") # bytes
+            s = f.getvalue()
+            f.close()
+            assert isinstance(s, unicode)
+
+def test_bytes_io():
+    f = py.io.BytesIO()
+    f.write(tobytes("hello"))
+    py.test.raises(TypeError, "f.write(totext('hello'))")
+    s = f.getvalue()
+    assert s == tobytes("hello")
+
+def test_dontreadfrominput():
+    from py._io.capture import DontReadFromInput
+    f = DontReadFromInput()
+    assert not f.isatty()
+    py.test.raises(IOError, f.read)
+    py.test.raises(IOError, f.readlines)
+    py.test.raises(IOError, iter, f)
+    py.test.raises(ValueError, f.fileno)
+    f.close() # just for completeness
+
+def pytest_funcarg__tmpfile(request):
+    testdir = request.getfuncargvalue("testdir")
+    f = testdir.makepyfile("").open('wb+')
+    request.addfinalizer(f.close)
+    return f
+
+@needsdup
+def test_dupfile(tmpfile):
+    flist = []
+    for i in range(5):
+        nf = py.io.dupfile(tmpfile, encoding="utf-8")
+        assert nf != tmpfile
+        assert nf.fileno() != tmpfile.fileno()
+        assert nf not in flist
+        print_(i, end="", file=nf)
+        flist.append(nf)
+    for i in range(5):
+        f = flist[i]
+        f.close()
+    tmpfile.seek(0)
+    s = tmpfile.read()
+    assert "01234" in repr(s)
+    tmpfile.close()
+
+def test_dupfile_no_mode():
+    """
+    dupfile should trap an AttributeError and return f if no mode is supplied.
+    """
+    class SomeFileWrapper(object):
+        "An object with a fileno method but no mode attribute"
+        def fileno(self):
+            return 1
+    tmpfile = SomeFileWrapper()
+    assert py.io.dupfile(tmpfile) is tmpfile
+    with py.test.raises(AttributeError):
+        py.io.dupfile(tmpfile, raising=True)
+
+def lsof_check(func):
+    pid = os.getpid()
+    try:
+        out = py.process.cmdexec("lsof -p %d" % pid)
+    except py.process.cmdexec.Error:
+        py.test.skip("could not run 'lsof'")
+    func()
+    out2 = py.process.cmdexec("lsof -p %d" % pid)
+    len1 = len([x for x in out.split("\n") if "REG" in x])
+    len2 = len([x for x in out2.split("\n") if "REG" in x])
+    assert len2 < len1 + 3, out2
+
+class TestFDCapture:
+    pytestmark = needsdup
+
+    def test_not_now(self, tmpfile):
+        fd = tmpfile.fileno()
+        cap = py.io.FDCapture(fd, now=False)
+        data = tobytes("hello")
+        os.write(fd, data)
+        f = cap.done()
+        s = f.read()
+        assert not s
+        cap = py.io.FDCapture(fd, now=False)
+        cap.start()
+        os.write(fd, data)
+        f = cap.done()
+        s = f.read()
+        assert s == "hello"
+
+    def test_simple(self, tmpfile):
+        fd = tmpfile.fileno()
+        cap = py.io.FDCapture(fd)
+        data = tobytes("hello")
+        os.write(fd, data)
+        f = cap.done()
+        s = f.read()
+        assert s == "hello"
+        f.close()
+
+    def test_simple_many(self, tmpfile):
+        for i in range(10):
+            self.test_simple(tmpfile)
+
+    def test_simple_many_check_open_files(self, tmpfile):
+        lsof_check(lambda: self.test_simple_many(tmpfile))
+
+    def test_simple_fail_second_start(self, tmpfile):
+        fd = tmpfile.fileno()
+        cap = py.io.FDCapture(fd)
+        f = cap.done()
+        py.test.raises(ValueError, cap.start)
+        f.close()
+
+    def test_stderr(self):
+        cap = py.io.FDCapture(2, patchsys=True)
+        print_("hello", file=sys.stderr)
+        f = cap.done()
+        s = f.read()
+        assert s == "hello\n"
+
+    def test_stdin(self, tmpfile):
+        tmpfile.write(tobytes("3"))
+        tmpfile.seek(0)
+        cap = py.io.FDCapture(0, tmpfile=tmpfile)
+        # check with os.read() directly instead of raw_input(), because
+        # sys.stdin itself may be redirected (as py.test now does by default)
+        x = os.read(0, 100).strip()
+        f = cap.done()
+        assert x == tobytes("3")
+
+    def test_writeorg(self, tmpfile):
+        data1, data2 = tobytes("foo"), tobytes("bar")
+        try:
+            cap = py.io.FDCapture(tmpfile.fileno())
+            tmpfile.write(data1)
+            cap.writeorg(data2)
+        finally:
+            tmpfile.close()
+        f = cap.done()
+        scap = f.read()
+        assert scap == totext(data1)
+        stmp = open(tmpfile.name, 'rb').read()
+        assert stmp == data2
+
+
+class TestStdCapture:
+    def getcapture(self, **kw):
+        return py.io.StdCapture(**kw)
+
+    def test_capturing_done_simple(self):
+        cap = self.getcapture()
+        sys.stdout.write("hello")
+        sys.stderr.write("world")
+        outfile, errfile = cap.done()
+        s = outfile.read()
+        assert s == "hello"
+        s = errfile.read()
+        assert s == "world"
+
+    def test_capturing_reset_simple(self):
+        cap = self.getcapture()
+        print("hello world")
+        sys.stderr.write("hello error\n")
+        out, err = cap.reset()
+        assert out == "hello world\n"
+        assert err == "hello error\n"
+
+    def test_capturing_readouterr(self):
+        cap = self.getcapture()
+        try:
+            print ("hello world")
+            sys.stderr.write("hello error\n")
+            out, err = cap.readouterr()
+            assert out == "hello world\n"
+            assert err == "hello error\n"
+            sys.stderr.write("error2")
+        finally:
+            out, err = cap.reset()
+        assert err == "error2"
+
+    def test_capturing_readouterr_unicode(self):
+        cap = self.getcapture()
+        print ("hx\xc4\x85\xc4\x87")
+        out, err = cap.readouterr()
+        assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
+
+    @py.test.mark.skipif('sys.version_info >= (3,)',
+                      reason='text output different for bytes on python3')
+    def test_capturing_readouterr_decode_error_handling(self):
+        cap = self.getcapture()
+        # triggered a internal error in pytest
+        print('\xa6')
+        out, err = cap.readouterr()
+        assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
+
+    def test_capturing_mixed(self):
+        cap = self.getcapture(mixed=True)
+        sys.stdout.write("hello ")
+        sys.stderr.write("world")
+        sys.stdout.write(".")
+        out, err = cap.reset()
+        assert out.strip() == "hello world."
+        assert not err
+
+    def test_reset_twice_error(self):
+        cap = self.getcapture()
+        print ("hello")
+        out, err = cap.reset()
+        py.test.raises(ValueError, cap.reset)
+        assert out == "hello\n"
+        assert not err
+
+    def test_capturing_modify_sysouterr_in_between(self):
+        oldout = sys.stdout
+        olderr = sys.stderr
+        cap = self.getcapture()
+        sys.stdout.write("hello")
+        sys.stderr.write("world")
+        sys.stdout = py.io.TextIO()
+        sys.stderr = py.io.TextIO()
+        print ("not seen")
+        sys.stderr.write("not seen\n")
+        out, err = cap.reset()
+        assert out == "hello"
+        assert err == "world"
+        assert sys.stdout == oldout
+        assert sys.stderr == olderr
+
+    def test_capturing_error_recursive(self):
+        cap1 = self.getcapture()
+        print ("cap1")
+        cap2 = self.getcapture()
+        print ("cap2")
+        out2, err2 = cap2.reset()
+        out1, err1 = cap1.reset()
+        assert out1 == "cap1\n"
+        assert out2 == "cap2\n"
+
+    def test_just_out_capture(self):
+        cap = self.getcapture(out=True, err=False)
+        sys.stdout.write("hello")
+        sys.stderr.write("world")
+        out, err = cap.reset()
+        assert out == "hello"
+        assert not err
+
+    def test_just_err_capture(self):
+        cap = self.getcapture(out=False, err=True)
+        sys.stdout.write("hello")
+        sys.stderr.write("world")
+        out, err = cap.reset()
+        assert err == "world"
+        assert not out
+
+    def test_stdin_restored(self):
+        old = sys.stdin
+        cap = self.getcapture(in_=True)
+        newstdin = sys.stdin
+        out, err = cap.reset()
+        assert newstdin != sys.stdin
+        assert sys.stdin is old
+
+    def test_stdin_nulled_by_default(self):
+        print ("XXX this test may well hang instead of crashing")
+        print ("XXX which indicates an error in the underlying capturing")
+        print ("XXX mechanisms")
+        cap = self.getcapture()
+        py.test.raises(IOError, "sys.stdin.read()")
+        out, err = cap.reset()
+
+    def test_suspend_resume(self):
+        cap = self.getcapture(out=True, err=False, in_=False)
+        try:
+            print ("hello")
+            sys.stderr.write("error\n")
+            out, err = cap.suspend()
+            assert out == "hello\n"
+            assert not err
+            print ("in between")
+            sys.stderr.write("in between\n")
+            cap.resume()
+            print ("after")
+            sys.stderr.write("error_after\n")
+        finally:
+            out, err = cap.reset()
+        assert out == "after\n"
+        assert not err
+
+class TestStdCaptureNotNow(TestStdCapture):
+    def getcapture(self, **kw):
+        kw['now'] = False
+        cap = py.io.StdCapture(**kw)
+        cap.startall()
+        return cap
+
+class TestStdCaptureFD(TestStdCapture):
+    pytestmark = needsdup
+
+    def getcapture(self, **kw):
+        return py.io.StdCaptureFD(**kw)
+
+    def test_intermingling(self):
+        cap = self.getcapture()
+        oswritebytes(1, "1")
+        sys.stdout.write(str(2))
+        sys.stdout.flush()
+        oswritebytes(1, "3")
+        oswritebytes(2, "a")
+        sys.stderr.write("b")
+        sys.stderr.flush()
+        oswritebytes(2, "c")
+        out, err = cap.reset()
+        assert out == "123"
+        assert err == "abc"
+
+    def test_callcapture(self):
+        def func(x, y):
+            print (x)
+            sys.stderr.write(str(y))
+            return 42
+
+        res, out, err = py.io.StdCaptureFD.call(func, 3, y=4)
+        assert res == 42
+        assert out.startswith("3")
+        assert err.startswith("4")
+
+    def test_many(self, capfd):
+        def f():
+            for i in range(10):
+                cap = py.io.StdCaptureFD()
+                cap.reset()
+        lsof_check(f)
+
+class TestStdCaptureFDNotNow(TestStdCaptureFD):
+    pytestmark = needsdup
+
+    def getcapture(self, **kw):
+        kw['now'] = False
+        cap = py.io.StdCaptureFD(**kw)
+        cap.startall()
+        return cap
+
+@needsdup
+def test_stdcapture_fd_tmpfile(tmpfile):
+    capfd = py.io.StdCaptureFD(out=tmpfile)
+    os.write(1, "hello".encode("ascii"))
+    os.write(2, "world".encode("ascii"))
+    outf, errf = capfd.done()
+    assert outf == tmpfile
+
+class TestStdCaptureFDinvalidFD:
+    pytestmark = needsdup
+    def test_stdcapture_fd_invalid_fd(self, testdir):
+        testdir.makepyfile("""
+            import py, os
+            def test_stdout():
+                os.close(1)
+                cap = py.io.StdCaptureFD(out=True, err=False, in_=False)
+                cap.done()
+            def test_stderr():
+                os.close(2)
+                cap = py.io.StdCaptureFD(out=False, err=True, in_=False)
+                cap.done()
+            def test_stdin():
+                os.close(0)
+                cap = py.io.StdCaptureFD(out=False, err=False, in_=True)
+                cap.done()
+        """)
+        result = testdir.runpytest("--capture=fd")
+        assert result.ret == 0
+        assert result.parseoutcomes()['passed'] == 3
+
+def test_capture_not_started_but_reset():
+    capsys = py.io.StdCapture(now=False)
+    capsys.done()
+    capsys.done()
+    capsys.reset()
+
+@needsdup
+def test_capture_no_sys():
+    capsys = py.io.StdCapture()
+    try:
+        cap = py.io.StdCaptureFD(patchsys=False)
+        sys.stdout.write("hello")
+        sys.stderr.write("world")
+        oswritebytes(1, "1")
+        oswritebytes(2, "2")
+        out, err = cap.reset()
+        assert out == "1"
+        assert err == "2"
+    finally:
+        capsys.reset()
+
+@needsdup
+def test_callcapture_nofd():
+    def func(x, y):
+        oswritebytes(1, "hello")
+        oswritebytes(2, "hello")
+        print (x)
+        sys.stderr.write(str(y))
+        return 42
+
+    capfd = py.io.StdCaptureFD(patchsys=False)
+    try:
+        res, out, err = py.io.StdCapture.call(func, 3, y=4)
+    finally:
+        capfd.reset()
+    assert res == 42
+    assert out.startswith("3")
+    assert err.startswith("4")
+
+@needsdup
+@py.test.mark.parametrize('use', [True, False])
+def test_fdcapture_tmpfile_remains_the_same(tmpfile, use):
+    if not use:
+        tmpfile = True
+    cap = py.io.StdCaptureFD(out=False, err=tmpfile, now=False)
+    cap.startall()
+    capfile = cap.err.tmpfile
+    cap.suspend()
+    cap.resume()
+    capfile2 = cap.err.tmpfile
+    assert capfile2 == capfile
+
+@py.test.mark.parametrize('method', ['StdCapture', 'StdCaptureFD'])
+def test_capturing_and_logging_fundamentals(testdir, method):
+    if method == "StdCaptureFD" and not hasattr(os, 'dup'):
+        py.test.skip("need os.dup")
+    # here we check a fundamental feature
+    p = testdir.makepyfile("""
+        import sys, os
+        import py, logging
+        cap = py.io.%s(out=False, in_=False)
+
+        logging.warn("hello1")
+        outerr = cap.suspend()
+        print ("suspend, captured %%s" %%(outerr,))
+        logging.warn("hello2")
+
+        cap.resume()
+        logging.warn("hello3")
+
+        outerr = cap.suspend()
+        print ("suspend2, captured %%s" %% (outerr,))
+    """ % (method,))
+    result = testdir.runpython(p)
+    result.stdout.fnmatch_lines([
+        "suspend, captured*hello1*",
+        "suspend2, captured*hello2*WARNING:root:hello3*",
+    ])
+    assert "atexit" not in result.stderr.str()
diff --git a/tools/third_party/py/testing/io_/test_saferepr.py b/tools/third_party/py/testing/io_/test_saferepr.py
new file mode 100644
index 0000000..97be141
--- /dev/null
+++ b/tools/third_party/py/testing/io_/test_saferepr.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import generators
+import py
+import sys
+
+saferepr = py.io.saferepr
+
+class TestSafeRepr:
+    def test_simple_repr(self):
+        assert saferepr(1) == '1'
+        assert saferepr(None) == 'None'
+
+    def test_maxsize(self):
+        s = saferepr('x'*50, maxsize=25)
+        assert len(s) == 25
+        expected = repr('x'*10 + '...' + 'x'*10)
+        assert s == expected
+
+    def test_maxsize_error_on_instance(self):
+        class A:
+            def __repr__(self):
+                raise ValueError('...')
+
+        s = saferepr(('*'*50, A()), maxsize=25)
+        assert len(s) == 25
+        assert s[0] == '(' and s[-1] == ')'
+
+    def test_exceptions(self):
+        class BrokenRepr:
+            def __init__(self, ex):
+                self.ex = ex
+                foo = 0
+            def __repr__(self):
+                raise self.ex
+        class BrokenReprException(Exception):
+            __str__ = None
+            __repr__ = None
+        assert 'Exception' in saferepr(BrokenRepr(Exception("broken")))
+        s = saferepr(BrokenReprException("really broken"))
+        assert 'TypeError' in s
+        assert 'TypeError' in saferepr(BrokenRepr("string"))
+
+        s2 = saferepr(BrokenRepr(BrokenReprException('omg even worse')))
+        assert 'NameError' not in s2
+        assert 'unknown' in s2
+
+    def test_big_repr(self):
+        from py._io.saferepr import SafeRepr
+        assert len(saferepr(range(1000))) <= \
+               len('[' + SafeRepr().maxlist * "1000" + ']')
+
+    def test_repr_on_newstyle(self):
+        class Function(object):
+            def __repr__(self):
+                return "<%s>" %(self.name)
+        try:
+            s = saferepr(Function())
+        except Exception:
+            py.test.fail("saferepr failed for newstyle class")
+
+    def test_unicode(self):
+        val = py.builtin._totext('£€', 'utf-8')
+        reprval = py.builtin._totext("'£€'", 'utf-8')
+        assert saferepr(val) == reprval
+
+def test_unicode_handling():
+    value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
+    def f():
+        raise Exception(value)
+    excinfo = py.test.raises(Exception, f)
+    s = str(excinfo)
+    if sys.version_info[0] < 3:
+        u = unicode(excinfo)
+
diff --git a/tools/third_party/py/testing/io_/test_terminalwriter.py b/tools/third_party/py/testing/io_/test_terminalwriter.py
new file mode 100644
index 0000000..7e9ebf4
--- /dev/null
+++ b/tools/third_party/py/testing/io_/test_terminalwriter.py
@@ -0,0 +1,292 @@
+
+import py
+import os, sys
+from py._io import terminalwriter
+import codecs
+import pytest
+
+def test_get_terminal_width():
+    x = py.io.get_terminal_width
+    assert x == terminalwriter.get_terminal_width
+
+def test_getdimensions(monkeypatch):
+    fcntl = py.test.importorskip("fcntl")
+    import struct
+    l = []
+    monkeypatch.setattr(fcntl, 'ioctl', lambda *args: l.append(args))
+    try:
+        terminalwriter._getdimensions()
+    except (TypeError, struct.error):
+        pass
+    assert len(l) == 1
+    assert l[0][0] == 1
+
+def test_terminal_width_COLUMNS(monkeypatch):
+    """ Dummy test for get_terminal_width
+    """
+    fcntl = py.test.importorskip("fcntl")
+    monkeypatch.setattr(fcntl, 'ioctl', lambda *args: int('x'))
+    monkeypatch.setenv('COLUMNS', '42')
+    assert terminalwriter.get_terminal_width() == 42
+    monkeypatch.delenv('COLUMNS', raising=False)
+
+def test_terminalwriter_defaultwidth_80(monkeypatch):
+    monkeypatch.setattr(terminalwriter, '_getdimensions', lambda: 0/0)
+    monkeypatch.delenv('COLUMNS', raising=False)
+    tw = py.io.TerminalWriter()
+    assert tw.fullwidth == 80
+
+def test_terminalwriter_getdimensions_bogus(monkeypatch):
+    monkeypatch.setattr(terminalwriter, '_getdimensions', lambda: (10,10))
+    monkeypatch.delenv('COLUMNS', raising=False)
+    tw = py.io.TerminalWriter()
+    assert tw.fullwidth == 80
+
+def test_terminalwriter_getdimensions_emacs(monkeypatch):
+    # emacs terminal returns (0,0) but set COLUMNS properly
+    monkeypatch.setattr(terminalwriter, '_getdimensions', lambda: (0,0))
+    monkeypatch.setenv('COLUMNS', '42')
+    tw = py.io.TerminalWriter()
+    assert tw.fullwidth == 42
+
+def test_terminalwriter_computes_width(monkeypatch):
+    monkeypatch.setattr(terminalwriter, 'get_terminal_width', lambda: 42)
+    tw = py.io.TerminalWriter()
+    assert tw.fullwidth == 42
+
+def test_terminalwriter_default_instantiation():
+    tw = py.io.TerminalWriter(stringio=True)
+    assert hasattr(tw, 'stringio')
+
+def test_terminalwriter_dumb_term_no_markup(monkeypatch):
+    monkeypatch.setattr(os, 'environ', {'TERM': 'dumb', 'PATH': ''})
+    class MyFile:
+        closed = False
+        def isatty(self):
+            return True
+    monkeypatch.setattr(sys, 'stdout', MyFile())
+    try:
+        assert sys.stdout.isatty()
+        tw = py.io.TerminalWriter()
+        assert not tw.hasmarkup
+    finally:
+        monkeypatch.undo()
+
+def test_terminalwriter_file_unicode(tmpdir):
+    f = codecs.open(str(tmpdir.join("xyz")), "wb", "utf8")
+    tw = py.io.TerminalWriter(file=f)
+    assert tw.encoding == "utf8"
+
+def test_unicode_encoding():
+    msg = py.builtin._totext('b\u00f6y', 'utf8')
+    for encoding in 'utf8', 'latin1':
+        l = []
+        tw = py.io.TerminalWriter(l.append, encoding=encoding)
+        tw.line(msg)
+        assert l[0].strip() == msg.encode(encoding)
+
+@pytest.mark.parametrize("encoding", ["ascii"])
+def test_unicode_on_file_with_ascii_encoding(tmpdir, monkeypatch, encoding):
+    msg = py.builtin._totext('hell\xf6', "latin1")
+    #pytest.raises(UnicodeEncodeError, lambda: bytes(msg))
+    f = codecs.open(str(tmpdir.join("x")), "w", encoding)
+    tw = py.io.TerminalWriter(f)
+    tw.line(msg)
+    f.close()
+    s = tmpdir.join("x").open("rb").read().strip()
+    assert encoding == "ascii"
+    assert s == msg.encode("unicode-escape")
+
+
+win32 = int(sys.platform == "win32")
+class TestTerminalWriter:
+    def pytest_generate_tests(self, metafunc):
+        if "tw" in metafunc.funcargnames:
+            metafunc.addcall(id="path", param="path")
+            metafunc.addcall(id="stringio", param="stringio")
+            metafunc.addcall(id="callable", param="callable")
+    def pytest_funcarg__tw(self, request):
+        if request.param == "path":
+            tmpdir = request.getfuncargvalue("tmpdir")
+            p = tmpdir.join("tmpfile")
+            f = codecs.open(str(p), 'w+', encoding='utf8')
+            tw = py.io.TerminalWriter(f)
+            def getlines():
+                tw._file.flush()
+                return codecs.open(str(p), 'r',
+                    encoding='utf8').readlines()
+        elif request.param == "stringio":
+            tw = py.io.TerminalWriter(stringio=True)
+            def getlines():
+                tw.stringio.seek(0)
+                return tw.stringio.readlines()
+        elif request.param == "callable":
+            writes = []
+            tw = py.io.TerminalWriter(writes.append)
+            def getlines():
+                io = py.io.TextIO()
+                io.write("".join(writes))
+                io.seek(0)
+                return io.readlines()
+        tw.getlines = getlines
+        tw.getvalue = lambda: "".join(getlines())
+        return tw
+
+    def test_line(self, tw):
+        tw.line("hello")
+        l = tw.getlines()
+        assert len(l) == 1
+        assert l[0] == "hello\n"
+
+    def test_line_unicode(self, tw):
+        for encoding in 'utf8', 'latin1':
+            tw._encoding = encoding
+            msg = py.builtin._totext('b\u00f6y', 'utf8')
+            tw.line(msg)
+            l = tw.getlines()
+            assert l[0] == msg + "\n"
+
+    def test_sep_no_title(self, tw):
+        tw.sep("-", fullwidth=60)
+        l = tw.getlines()
+        assert len(l) == 1
+        assert l[0] == "-" * (60-win32) + "\n"
+
+    def test_sep_with_title(self, tw):
+        tw.sep("-", "hello", fullwidth=60)
+        l = tw.getlines()
+        assert len(l) == 1
+        assert l[0] == "-" * 26 + " hello " + "-" * (27-win32) + "\n"
+
+    @py.test.mark.skipif("sys.platform == 'win32'")
+    def test__escaped(self, tw):
+        text2 = tw._escaped("hello", (31))
+        assert text2.find("hello") != -1
+
+    @py.test.mark.skipif("sys.platform == 'win32'")
+    def test_markup(self, tw):
+        for bold in (True, False):
+            for color in ("red", "green"):
+                text2 = tw.markup("hello", **{color: True, 'bold': bold})
+                assert text2.find("hello") != -1
+        py.test.raises(ValueError, "tw.markup('x', wronkw=3)")
+        py.test.raises(ValueError, "tw.markup('x', wronkw=0)")
+
+    def test_line_write_markup(self, tw):
+        tw.hasmarkup = True
+        tw.line("x", bold=True)
+        tw.write("x\n", red=True)
+        l = tw.getlines()
+        if sys.platform != "win32":
+            assert len(l[0]) >= 2, l
+            assert len(l[1]) >= 2, l
+
+    def test_attr_fullwidth(self, tw):
+        tw.sep("-", "hello", fullwidth=70)
+        tw.fullwidth = 70
+        tw.sep("-", "hello")
+        l = tw.getlines()
+        assert len(l[0]) == len(l[1])
+
+    def test_reline(self, tw):
+        tw.line("hello")
+        tw.hasmarkup = False
+        pytest.raises(ValueError, lambda: tw.reline("x"))
+        tw.hasmarkup = True
+        tw.reline("0 1 2")
+        tw.getlines()
+        l = tw.getvalue().split("\n")
+        assert len(l) == 2
+        tw.reline("0 1 3")
+        l = tw.getvalue().split("\n")
+        assert len(l) == 2
+        assert l[1].endswith("0 1 3\r")
+        tw.line("so")
+        l = tw.getvalue().split("\n")
+        assert len(l) == 3
+        assert l[-1] == ""
+        assert l[1] == ("0 1 2\r0 1 3\rso   ")
+        assert l[0] == "hello"
+
+
+def test_terminal_with_callable_write_and_flush():
+    l = set()
+    class fil:
+        flush = lambda self: l.add("1")
+        write = lambda self, x: l.add("1")
+        __call__ = lambda self, x: l.add("2")
+
+    tw = py.io.TerminalWriter(fil())
+    tw.line("hello")
+    assert l == set(["1"])
+    del fil.flush
+    l.clear()
+    tw = py.io.TerminalWriter(fil())
+    tw.line("hello")
+    assert l == set(["2"])
+
+
+def test_chars_on_current_line():
+    tw = py.io.TerminalWriter(stringio=True)
+
+    written = []
+
+    def write_and_check(s, expected):
+        tw.write(s, bold=True)
+        written.append(s)
+        assert tw.chars_on_current_line == expected
+        assert tw.stringio.getvalue() == ''.join(written)
+
+    write_and_check('foo', 3)
+    write_and_check('bar', 6)
+    write_and_check('\n', 0)
+    write_and_check('\n', 0)
+    write_and_check('\n\n\n', 0)
+    write_and_check('\nfoo', 3)
+    write_and_check('\nfbar\nhello', 5)
+    write_and_check('10', 7)
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi")
+def test_attr_hasmarkup():
+    tw = py.io.TerminalWriter(stringio=True)
+    assert not tw.hasmarkup
+    tw.hasmarkup = True
+    tw.line("hello", bold=True)
+    s = tw.stringio.getvalue()
+    assert len(s) > len("hello\n")
+    assert '\x1b[1m' in s
+    assert '\x1b[0m' in s
+
+@pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi")
+def test_ansi_print():
+    # we have no easy way to construct a file that
+    # represents a terminal
+    f = py.io.TextIO()
+    f.isatty = lambda: True
+    py.io.ansi_print("hello", 0x32, file=f)
+    text2 = f.getvalue()
+    assert text2.find("hello") != -1
+    assert len(text2) >= len("hello\n")
+    assert '\x1b[50m' in text2
+    assert '\x1b[0m' in text2
+
+def test_should_do_markup_PY_COLORS_eq_1(monkeypatch):
+    monkeypatch.setitem(os.environ, 'PY_COLORS', '1')
+    tw = py.io.TerminalWriter(stringio=True)
+    assert tw.hasmarkup
+    tw.line("hello", bold=True)
+    s = tw.stringio.getvalue()
+    assert len(s) > len("hello\n")
+    assert '\x1b[1m' in s
+    assert '\x1b[0m' in s
+
+def test_should_do_markup_PY_COLORS_eq_0(monkeypatch):
+    monkeypatch.setitem(os.environ, 'PY_COLORS', '0')
+    f = py.io.TextIO()
+    f.isatty = lambda: True
+    tw = py.io.TerminalWriter(file=f)
+    assert not tw.hasmarkup
+    tw.line("hello", bold=True)
+    s = f.getvalue()
+    assert s == "hello\n"
diff --git a/tools/py/testing/log/__init__.py b/tools/third_party/py/testing/log/__init__.py
similarity index 100%
rename from tools/py/testing/log/__init__.py
rename to tools/third_party/py/testing/log/__init__.py
diff --git a/tools/third_party/py/testing/log/test_log.py b/tools/third_party/py/testing/log/test_log.py
new file mode 100644
index 0000000..5c706d9
--- /dev/null
+++ b/tools/third_party/py/testing/log/test_log.py
@@ -0,0 +1,191 @@
+import py
+
+from py._log.log import default_keywordmapper
+
+callcapture = py.io.StdCapture.call
+
+
+def setup_module(mod):
+    mod._oldstate = default_keywordmapper.getstate()
+
+def teardown_module(mod):
+    default_keywordmapper.setstate(mod._oldstate)
+
+class TestLogProducer:
+    def setup_method(self, meth):
+        from py._log.log import default_keywordmapper
+        default_keywordmapper.setstate(_oldstate)
+
+    def test_getstate_setstate(self):
+        state = py.log._getstate()
+        py.log.setconsumer("hello", [].append)
+        state2 = py.log._getstate()
+        assert state2 != state
+        py.log._setstate(state)
+        state3 = py.log._getstate()
+        assert state3 == state
+
+    def test_producer_repr(self):
+        d = py.log.Producer("default")
+        assert repr(d).find('default') != -1
+
+    def test_produce_one_keyword(self):
+        l = []
+        py.log.setconsumer('s1', l.append)
+        py.log.Producer('s1')("hello world")
+        assert len(l) == 1
+        msg = l[0]
+        assert msg.content().startswith('hello world')
+        assert msg.prefix() == '[s1] '
+        assert str(msg) == "[s1] hello world"
+
+    def test_producer_class(self):
+        p = py.log.Producer('x1')
+        l = []
+        py.log.setconsumer(p._keywords, l.append)
+        p("hello")
+        assert len(l) == 1
+        assert len(l[0].keywords) == 1
+        assert 'x1' == l[0].keywords[0]
+
+    def test_producer_caching(self):
+        p = py.log.Producer('x1')
+        x2 = p.x2
+        assert x2 is p.x2
+
+class TestLogConsumer:
+    def setup_method(self, meth):
+        default_keywordmapper.setstate(_oldstate)
+    def test_log_none(self):
+        log = py.log.Producer("XXX")
+        l = []
+        py.log.setconsumer('XXX', l.append)
+        log("1")
+        assert l
+        l[:] = []
+        py.log.setconsumer('XXX', None)
+        log("2")
+        assert not l
+
+    def test_log_default_stderr(self):
+        res, out, err = callcapture(py.log.Producer("default"), "hello")
+        assert err.strip() == "[default] hello"
+
+    def test_simple_consumer_match(self):
+        l = []
+        py.log.setconsumer("x1", l.append)
+        p = py.log.Producer("x1 x2")
+        p("hello")
+        assert l
+        assert l[0].content() == "hello"
+
+    def test_simple_consumer_match_2(self):
+        l = []
+        p = py.log.Producer("x1 x2")
+        py.log.setconsumer(p._keywords, l.append)
+        p("42")
+        assert l
+        assert l[0].content() == "42"
+
+    def test_no_auto_producer(self):
+        p = py.log.Producer('x')
+        py.test.raises(AttributeError, "p._x")
+        py.test.raises(AttributeError, "p.x_y")
+
+    def test_setconsumer_with_producer(self):
+        l = []
+        p = py.log.Producer("hello")
+        py.log.setconsumer(p, l.append)
+        p("world")
+        assert str(l[0]) == "[hello] world"
+
+    def test_multi_consumer(self):
+        l = []
+        py.log.setconsumer("x1", l.append)
+        py.log.setconsumer("x1 x2", None)
+        p = py.log.Producer("x1 x2")
+        p("hello")
+        assert not l
+        py.log.Producer("x1")("hello")
+        assert l
+        assert l[0].content() == "hello"
+
+    def test_log_stderr(self):
+        py.log.setconsumer("xyz", py.log.STDOUT)
+        res, out, err = callcapture(py.log.Producer("xyz"), "hello")
+        assert not err
+        assert out.strip() == '[xyz] hello'
+
+    def test_log_file(self, tmpdir):
+        customlog = tmpdir.join('log.out')
+        py.log.setconsumer("default", open(str(customlog), 'w', 1))
+        py.log.Producer("default")("hello world #1")
+        assert customlog.readlines() == ['[default] hello world #1\n']
+
+        py.log.setconsumer("default", py.log.Path(customlog, buffering=False))
+        py.log.Producer("default")("hello world #2")
+        res = customlog.readlines()
+        assert res == ['[default] hello world #2\n'] # no append by default!
+
+    def test_log_file_append_mode(self, tmpdir):
+        logfilefn = tmpdir.join('log_append.out')
+
+        # The append mode is on by default, so we don't need to specify it for File
+        py.log.setconsumer("default", py.log.Path(logfilefn, append=True,
+                                                    buffering=0))
+        assert logfilefn.check()
+        py.log.Producer("default")("hello world #1")
+        lines = logfilefn.readlines()
+        assert lines == ['[default] hello world #1\n']
+        py.log.setconsumer("default", py.log.Path(logfilefn, append=True,
+                                                    buffering=0))
+        py.log.Producer("default")("hello world #1")
+        lines = logfilefn.readlines()
+        assert lines == ['[default] hello world #1\n',
+                         '[default] hello world #1\n']
+
+    def test_log_file_delayed_create(self, tmpdir):
+        logfilefn = tmpdir.join('log_create.out')
+
+        py.log.setconsumer("default", py.log.Path(logfilefn,
+                                        delayed_create=True, buffering=0))
+        assert not logfilefn.check()
+        py.log.Producer("default")("hello world #1")
+        lines = logfilefn.readlines()
+        assert lines == ['[default] hello world #1\n']
+
+    def test_keyword_based_log_files(self, tmpdir):
+        logfiles = []
+        keywords = 'k1 k2 k3'.split()
+        for key in keywords:
+            path = tmpdir.join(key)
+            py.log.setconsumer(key, py.log.Path(path, buffering=0))
+
+        py.log.Producer('k1')('1')
+        py.log.Producer('k2')('2')
+        py.log.Producer('k3')('3')
+
+        for key in keywords:
+            path = tmpdir.join(key)
+            assert path.read().strip() == '[%s] %s' % (key, key[-1])
+
+    # disabled for now; the syslog log file can usually be read only by root
+    # I manually inspected /var/log/messages and the entries were there
+    def no_test_log_syslog(self):
+        py.log.setconsumer("default", py.log.Syslog())
+        py.log.default("hello world #1")
+
+    # disabled for now until I figure out how to read entries in the
+    # Event Logs on Windows
+    # I manually inspected the Application Log and the entries were there
+    def no_test_log_winevent(self):
+        py.log.setconsumer("default", py.log.WinEvent())
+        py.log.default("hello world #1")
+
+    # disabled for now until I figure out how to properly pass the parameters
+    def no_test_log_email(self):
+        py.log.setconsumer("default", py.log.Email(mailhost="gheorghiu.net",
+                                                   fromaddr="grig",
+                                                   toaddrs="grig",
+                                                   subject = "py.log email"))
+        py.log.default("hello world #1")
diff --git a/tools/third_party/py/testing/log/test_warning.py b/tools/third_party/py/testing/log/test_warning.py
new file mode 100644
index 0000000..a460c31
--- /dev/null
+++ b/tools/third_party/py/testing/log/test_warning.py
@@ -0,0 +1,86 @@
+import sys
+from distutils.version import LooseVersion
+
+import pytest
+
+import py
+
+mypath = py.path.local(__file__).new(ext=".py")
+
+
+win = sys.platform.startswith('win')
+pytestmark = pytest.mark.skipif(win and LooseVersion(pytest.__version__) >= LooseVersion('3.1'),
+                                reason='apiwarn is not compatible with pytest >= 3.1 (#162)')
+
+
+@pytest.mark.xfail
+def test_forwarding_to_warnings_module():
+    pytest.deprecated_call(py.log._apiwarn, "1.3", "..")
+
+def test_apiwarn_functional(recwarn):
+    capture = py.io.StdCapture()
+    py.log._apiwarn("x.y.z", "something", stacklevel=1)
+    out, err = capture.reset()
+    py.builtin.print_("out", out)
+    py.builtin.print_("err", err)
+    assert err.find("x.y.z") != -1
+    lno = py.code.getrawcode(test_apiwarn_functional).co_firstlineno + 2
+    exp = "%s:%s" % (mypath, lno)
+    assert err.find(exp) != -1
+
+def test_stacklevel(recwarn):
+    def f():
+        py.log._apiwarn("x", "some", stacklevel=2)
+    # 3
+    # 4
+    capture = py.io.StdCapture()
+    f()
+    out, err = capture.reset()
+    lno = py.code.getrawcode(test_stacklevel).co_firstlineno + 6
+    warning = str(err)
+    assert warning.find(":%s" % lno) != -1
+
+def test_stacklevel_initpkg_with_resolve(testdir, recwarn):
+    testdir.makepyfile(modabc="""
+        import py
+        def f():
+            py.log._apiwarn("x", "some", stacklevel="apipkg123")
+    """)
+    testdir.makepyfile(apipkg123="""
+        def __getattr__():
+            import modabc
+            modabc.f()
+    """)
+    p = testdir.makepyfile("""
+        import apipkg123
+        apipkg123.__getattr__()
+    """)
+    capture = py.io.StdCapture()
+    p.pyimport()
+    out, err = capture.reset()
+    warning = str(err)
+    loc = 'test_stacklevel_initpkg_with_resolve.py:2'
+    assert warning.find(loc) != -1
+
+def test_stacklevel_initpkg_no_resolve(recwarn):
+    def f():
+        py.log._apiwarn("x", "some", stacklevel="apipkg")
+    capture = py.io.StdCapture()
+    f()
+    out, err = capture.reset()
+    lno = py.code.getrawcode(test_stacklevel_initpkg_no_resolve).co_firstlineno + 2
+    warning = str(err)
+    assert warning.find(":%s" % lno) != -1
+
+
+def test_function(recwarn):
+    capture = py.io.StdCapture()
+    py.log._apiwarn("x.y.z", "something", function=test_function)
+    out, err = capture.reset()
+    py.builtin.print_("out", out)
+    py.builtin.print_("err", err)
+    assert err.find("x.y.z") != -1
+    lno = py.code.getrawcode(test_function).co_firstlineno
+    exp = "%s:%s" % (mypath, lno)
+    assert err.find(exp) != -1
+
diff --git a/tools/third_party/py/testing/path/common.py b/tools/third_party/py/testing/path/common.py
new file mode 100644
index 0000000..d69a1c3
--- /dev/null
+++ b/tools/third_party/py/testing/path/common.py
@@ -0,0 +1,492 @@
+import py
+import sys
+
+import pytest
+
+class CommonFSTests(object):
+    def test_constructor_equality(self, path1):
+        p = path1.__class__(path1)
+        assert p == path1
+
+    def test_eq_nonstring(self, path1):
+        p1 = path1.join('sampledir')
+        p2 = path1.join('sampledir')
+        assert p1 == p2
+
+    def test_new_identical(self, path1):
+        assert path1 == path1.new()
+
+    def test_join(self, path1):
+        p = path1.join('sampledir')
+        strp = str(p)
+        assert strp.endswith('sampledir')
+        assert strp.startswith(str(path1))
+
+    def test_join_normalized(self, path1):
+        newpath = path1.join(path1.sep+'sampledir')
+        strp = str(newpath)
+        assert strp.endswith('sampledir')
+        assert strp.startswith(str(path1))
+        newpath = path1.join((path1.sep*2) + 'sampledir')
+        strp = str(newpath)
+        assert strp.endswith('sampledir')
+        assert strp.startswith(str(path1))
+
+    def test_join_noargs(self, path1):
+        newpath = path1.join()
+        assert path1 == newpath
+
+    def test_add_something(self, path1):
+        p = path1.join('sample')
+        p = p + 'dir'
+        assert p.check()
+        assert p.exists()
+        assert p.isdir()
+        assert not p.isfile()
+
+    def test_parts(self, path1):
+        newpath = path1.join('sampledir', 'otherfile')
+        par = newpath.parts()[-3:]
+        assert par == [path1, path1.join('sampledir'), newpath]
+
+        revpar = newpath.parts(reverse=True)[:3]
+        assert revpar == [newpath, path1.join('sampledir'), path1]
+
+    def test_common(self, path1):
+        other = path1.join('sampledir')
+        x = other.common(path1)
+        assert x == path1
+
+    #def test_parents_nonexisting_file(self, path1):
+    #    newpath = path1 / 'dirnoexist' / 'nonexisting file'
+    #    par = list(newpath.parents())
+    #    assert par[:2] == [path1 / 'dirnoexist', path1]
+
+    def test_basename_checks(self, path1):
+        newpath = path1.join('sampledir')
+        assert newpath.check(basename='sampledir')
+        assert newpath.check(notbasename='xyz')
+        assert newpath.basename == 'sampledir'
+
+    def test_basename(self, path1):
+        newpath = path1.join('sampledir')
+        assert newpath.check(basename='sampledir')
+        assert newpath.basename, 'sampledir'
+
+    def test_dirname(self, path1):
+        newpath = path1.join('sampledir')
+        assert newpath.dirname == str(path1)
+
+    def test_dirpath(self, path1):
+        newpath = path1.join('sampledir')
+        assert newpath.dirpath() == path1
+
+    def test_dirpath_with_args(self, path1):
+        newpath = path1.join('sampledir')
+        assert newpath.dirpath('x') == path1.join('x')
+
+    def test_newbasename(self, path1):
+        newpath = path1.join('samplefile')
+        newbase = newpath.new(basename="samplefile2")
+        assert newbase.basename == "samplefile2"
+        assert newbase.dirpath() == newpath.dirpath()
+
+    def test_not_exists(self, path1):
+        assert not path1.join('does_not_exist').check()
+        assert path1.join('does_not_exist').check(exists=0)
+
+    def test_exists(self, path1):
+        assert path1.join("samplefile").check()
+        assert path1.join("samplefile").check(exists=1)
+        assert path1.join("samplefile").exists()
+        assert path1.join("samplefile").isfile()
+        assert not path1.join("samplefile").isdir()
+
+    def test_dir(self, path1):
+        #print repr(path1.join("sampledir"))
+        assert path1.join("sampledir").check(dir=1)
+        assert path1.join('samplefile').check(notdir=1)
+        assert not path1.join("samplefile").check(dir=1)
+        assert path1.join("samplefile").exists()
+        assert not path1.join("samplefile").isdir()
+        assert path1.join("samplefile").isfile()
+
+    def test_fnmatch_file(self, path1):
+        assert path1.join("samplefile").check(fnmatch='s*e')
+        assert path1.join("samplefile").fnmatch('s*e')
+        assert not path1.join("samplefile").fnmatch('s*x')
+        assert not path1.join("samplefile").check(fnmatch='s*x')
+
+    #def test_fnmatch_dir(self, path1):
+
+    #    pattern = path1.sep.join(['s*file'])
+    #    sfile = path1.join("samplefile")
+    #    assert sfile.check(fnmatch=pattern)
+
+    def test_relto(self, path1):
+        l=path1.join("sampledir", "otherfile")
+        assert l.relto(path1) == l.sep.join(["sampledir", "otherfile"])
+        assert l.check(relto=path1)
+        assert path1.check(notrelto=l)
+        assert not path1.check(relto=l)
+
+    def test_bestrelpath(self, path1):
+        curdir = path1
+        sep = curdir.sep
+        s = curdir.bestrelpath(curdir)
+        assert s == "."
+        s = curdir.bestrelpath(curdir.join("hello", "world"))
+        assert s == "hello" + sep + "world"
+
+        s = curdir.bestrelpath(curdir.dirpath().join("sister"))
+        assert s == ".." + sep + "sister"
+        assert curdir.bestrelpath(curdir.dirpath()) == ".."
+
+        assert curdir.bestrelpath("hello") == "hello"
+
+    def test_relto_not_relative(self, path1):
+        l1=path1.join("bcde")
+        l2=path1.join("b")
+        assert not l1.relto(l2)
+        assert not l2.relto(l1)
+
+    @py.test.mark.xfail("sys.platform.startswith('java')")
+    def test_listdir(self, path1):
+        l = path1.listdir()
+        assert path1.join('sampledir') in l
+        assert path1.join('samplefile') in l
+        py.test.raises(py.error.ENOTDIR,
+                       "path1.join('samplefile').listdir()")
+
+    def test_listdir_fnmatchstring(self, path1):
+        l = path1.listdir('s*dir')
+        assert len(l)
+        assert l[0], path1.join('sampledir')
+
+    def test_listdir_filter(self, path1):
+        l = path1.listdir(lambda x: x.check(dir=1))
+        assert path1.join('sampledir') in l
+        assert not path1.join('samplefile') in l
+
+    def test_listdir_sorted(self, path1):
+        l = path1.listdir(lambda x: x.check(basestarts="sample"), sort=True)
+        assert path1.join('sampledir') == l[0]
+        assert path1.join('samplefile') == l[1]
+        assert path1.join('samplepickle') == l[2]
+
+    def test_visit_nofilter(self, path1):
+        l = []
+        for i in path1.visit():
+            l.append(i.relto(path1))
+        assert "sampledir" in l
+        assert path1.sep.join(["sampledir", "otherfile"]) in l
+
+    def test_visit_norecurse(self, path1):
+        l = []
+        for i in path1.visit(None, lambda x: x.basename != "sampledir"):
+            l.append(i.relto(path1))
+        assert "sampledir" in l
+        assert not path1.sep.join(["sampledir", "otherfile"]) in l
+
+    @pytest.mark.parametrize('fil', ['*dir', u'*dir',
+                             pytest.mark.skip("sys.version_info <"
+                                              " (3,6)")(b'*dir')])
+    def test_visit_filterfunc_is_string(self, path1, fil):
+        l = []
+        for i in path1.visit(fil):
+            l.append(i.relto(path1))
+        assert len(l), 2
+        assert "sampledir" in l
+        assert "otherdir" in l
+
+    @py.test.mark.xfail("sys.platform.startswith('java')")
+    def test_visit_ignore(self, path1):
+        p = path1.join('nonexisting')
+        assert list(p.visit(ignore=py.error.ENOENT)) == []
+
+    def test_visit_endswith(self, path1):
+        l = []
+        for i in path1.visit(lambda x: x.check(endswith="file")):
+            l.append(i.relto(path1))
+        assert path1.sep.join(["sampledir", "otherfile"]) in l
+        assert "samplefile" in l
+
+    def test_endswith(self, path1):
+        assert path1.check(notendswith='.py')
+        x = path1.join('samplefile')
+        assert x.check(endswith='file')
+
+    def test_cmp(self, path1):
+        path1 = path1.join('samplefile')
+        path2 = path1.join('samplefile2')
+        assert (path1 < path2) == ('samplefile' < 'samplefile2')
+        assert not (path1 < path1)
+
+    def test_simple_read(self, path1):
+        x = path1.join('samplefile').read('r')
+        assert x == 'samplefile\n'
+
+    def test_join_div_operator(self, path1):
+        newpath = path1 / '/sampledir' / '/test//'
+        newpath2 = path1.join('sampledir', 'test')
+        assert newpath == newpath2
+
+    def test_ext(self, path1):
+        newpath = path1.join('sampledir.ext')
+        assert newpath.ext == '.ext'
+        newpath = path1.join('sampledir')
+        assert not newpath.ext
+
+    def test_purebasename(self, path1):
+        newpath = path1.join('samplefile.py')
+        assert newpath.purebasename == 'samplefile'
+
+    def test_multiple_parts(self, path1):
+        newpath = path1.join('samplefile.py')
+        dirname, purebasename, basename, ext = newpath._getbyspec(
+            'dirname,purebasename,basename,ext')
+        assert str(path1).endswith(dirname) # be careful with win32 'drive'
+        assert purebasename == 'samplefile'
+        assert basename == 'samplefile.py'
+        assert ext == '.py'
+
+    def test_dotted_name_ext(self, path1):
+        newpath = path1.join('a.b.c')
+        ext = newpath.ext
+        assert ext == '.c'
+        assert newpath.ext == '.c'
+
+    def test_newext(self, path1):
+        newpath = path1.join('samplefile.py')
+        newext = newpath.new(ext='.txt')
+        assert newext.basename == "samplefile.txt"
+        assert newext.purebasename == "samplefile"
+
+    def test_readlines(self, path1):
+        fn = path1.join('samplefile')
+        contents = fn.readlines()
+        assert contents == ['samplefile\n']
+
+    def test_readlines_nocr(self, path1):
+        fn = path1.join('samplefile')
+        contents = fn.readlines(cr=0)
+        assert contents == ['samplefile', '']
+
+    def test_file(self, path1):
+        assert path1.join('samplefile').check(file=1)
+
+    def test_not_file(self, path1):
+        assert not path1.join("sampledir").check(file=1)
+        assert path1.join("sampledir").check(file=0)
+
+    def test_non_existent(self, path1):
+        assert path1.join("sampledir.nothere").check(dir=0)
+        assert path1.join("sampledir.nothere").check(file=0)
+        assert path1.join("sampledir.nothere").check(notfile=1)
+        assert path1.join("sampledir.nothere").check(notdir=1)
+        assert path1.join("sampledir.nothere").check(notexists=1)
+        assert not path1.join("sampledir.nothere").check(notfile=0)
+
+    #    pattern = path1.sep.join(['s*file'])
+    #    sfile = path1.join("samplefile")
+    #    assert sfile.check(fnmatch=pattern)
+
+    def test_size(self, path1):
+        url = path1.join("samplefile")
+        assert url.size() > len("samplefile")
+
+    def test_mtime(self, path1):
+        url = path1.join("samplefile")
+        assert url.mtime() > 0
+
+    def test_relto_wrong_type(self, path1):
+        py.test.raises(TypeError, "path1.relto(42)")
+
+    def test_load(self, path1):
+        p = path1.join('samplepickle')
+        obj = p.load()
+        assert type(obj) is dict
+        assert obj.get('answer',None) == 42
+
+    def test_visit_filesonly(self, path1):
+        l = []
+        for i in path1.visit(lambda x: x.check(file=1)):
+            l.append(i.relto(path1))
+        assert not "sampledir" in l
+        assert path1.sep.join(["sampledir", "otherfile"]) in l
+
+    def test_visit_nodotfiles(self, path1):
+        l = []
+        for i in path1.visit(lambda x: x.check(dotfile=0)):
+            l.append(i.relto(path1))
+        assert "sampledir" in l
+        assert path1.sep.join(["sampledir", "otherfile"]) in l
+        assert not ".dotfile" in l
+
+    def test_visit_breadthfirst(self, path1):
+        l = []
+        for i in path1.visit(bf=True):
+            l.append(i.relto(path1))
+        for i, p in enumerate(l):
+            if path1.sep in p:
+                for j in range(i, len(l)):
+                    assert path1.sep in l[j]
+                break
+        else:
+            py.test.fail("huh")
+
+    def test_visit_sort(self, path1):
+        l = []
+        for i in path1.visit(bf=True, sort=True):
+            l.append(i.relto(path1))
+        for i, p in enumerate(l):
+            if path1.sep in p:
+                break
+        assert l[:i] == sorted(l[:i])
+        assert l[i:] == sorted(l[i:])
+
+    def test_endswith(self, path1):
+        def chk(p):
+            return p.check(endswith="pickle")
+        assert not chk(path1)
+        assert not chk(path1.join('samplefile'))
+        assert chk(path1.join('somepickle'))
+
+    def test_copy_file(self, path1):
+        otherdir = path1.join('otherdir')
+        initpy = otherdir.join('__init__.py')
+        copied = otherdir.join('copied')
+        initpy.copy(copied)
+        try:
+            assert copied.check()
+            s1 = initpy.read()
+            s2 = copied.read()
+            assert s1 == s2
+        finally:
+            if copied.check():
+                copied.remove()
+
+    def test_copy_dir(self, path1):
+        otherdir = path1.join('otherdir')
+        copied = path1.join('newdir')
+        try:
+            otherdir.copy(copied)
+            assert copied.check(dir=1)
+            assert copied.join('__init__.py').check(file=1)
+            s1 = otherdir.join('__init__.py').read()
+            s2 = copied.join('__init__.py').read()
+            assert s1 == s2
+        finally:
+            if copied.check(dir=1):
+                copied.remove(rec=1)
+
+    def test_remove_file(self, path1):
+        d = path1.ensure('todeleted')
+        assert d.check()
+        d.remove()
+        assert not d.check()
+
+    def test_remove_dir_recursive_by_default(self, path1):
+        d = path1.ensure('to', 'be', 'deleted')
+        assert d.check()
+        p = path1.join('to')
+        p.remove()
+        assert not p.check()
+
+    def test_ensure_dir(self, path1):
+        b = path1.ensure_dir("001", "002")
+        assert b.basename == "002"
+        assert b.isdir()
+
+    def test_mkdir_and_remove(self, path1):
+        tmpdir = path1
+        py.test.raises(py.error.EEXIST, tmpdir.mkdir, 'sampledir')
+        new = tmpdir.join('mktest1')
+        new.mkdir()
+        assert new.check(dir=1)
+        new.remove()
+
+        new = tmpdir.mkdir('mktest')
+        assert new.check(dir=1)
+        new.remove()
+        assert tmpdir.join('mktest') == new
+
+    def test_move_file(self, path1):
+        p = path1.join('samplefile')
+        newp = p.dirpath('moved_samplefile')
+        p.move(newp)
+        try:
+            assert newp.check(file=1)
+            assert not p.check()
+        finally:
+            dp = newp.dirpath()
+            if hasattr(dp, 'revert'):
+                dp.revert()
+            else:
+                newp.move(p)
+                assert p.check()
+
+    def test_move_dir(self, path1):
+        source = path1.join('sampledir')
+        dest = path1.join('moveddir')
+        source.move(dest)
+        assert dest.check(dir=1)
+        assert dest.join('otherfile').check(file=1)
+        assert not source.join('sampledir').check()
+
+    def test_fspath_protocol_match_strpath(self, path1):
+        assert path1.__fspath__() == path1.strpath
+
+    def test_fspath_func_match_strpath(self, path1):
+        try:
+            from os import fspath
+        except ImportError:
+            from py._path.common import fspath
+        assert fspath(path1) == path1.strpath
+
+    @py.test.mark.skip("sys.version_info < (3,6)")
+    def test_fspath_open(self, path1):
+        f = path1.join('opentestfile')
+        open(f)
+
+    @py.test.mark.skip("sys.version_info < (3,6)")
+    def test_fspath_fsencode(self, path1):
+        from os import fsencode
+        assert fsencode(path1) == fsencode(path1.strpath)
+
+def setuptestfs(path):
+    if path.join('samplefile').check():
+        return
+    #print "setting up test fs for", repr(path)
+    samplefile = path.ensure('samplefile')
+    samplefile.write('samplefile\n')
+
+    execfile = path.ensure('execfile')
+    execfile.write('x=42')
+
+    execfilepy = path.ensure('execfile.py')
+    execfilepy.write('x=42')
+
+    d = {1:2, 'hello': 'world', 'answer': 42}
+    path.ensure('samplepickle').dump(d)
+
+    sampledir = path.ensure('sampledir', dir=1)
+    sampledir.ensure('otherfile')
+
+    otherdir = path.ensure('otherdir', dir=1)
+    otherdir.ensure('__init__.py')
+
+    module_a = otherdir.ensure('a.py')
+    module_a.write('from .b import stuff as result\n')
+    module_b = otherdir.ensure('b.py')
+    module_b.write('stuff="got it"\n')
+    module_c = otherdir.ensure('c.py')
+    module_c.write('''import py;
+import otherdir.a
+value = otherdir.a.result
+''')
+    module_d = otherdir.ensure('d.py')
+    module_d.write('''import py;
+from otherdir import a
+value2 = a.result
+''')
diff --git a/tools/third_party/py/testing/path/conftest.py b/tools/third_party/py/testing/path/conftest.py
new file mode 100644
index 0000000..84fb5c8
--- /dev/null
+++ b/tools/third_party/py/testing/path/conftest.py
@@ -0,0 +1,80 @@
+import py
+import sys
+from py._path import svnwc as svncommon
+
+svnbin = py.path.local.sysfind('svn')
+repodump = py.path.local(__file__).dirpath('repotest.dump')
+from py.builtin import print_
+
+def pytest_funcarg__repowc1(request):
+    if svnbin is None:
+        py.test.skip("svn binary not found")
+
+    tmpdir = request.getfuncargvalue("tmpdir")
+    repo, repourl, wc = request.cached_setup(
+        setup=lambda: getrepowc(tmpdir, "path1repo", "path1wc"),
+        scope="module",
+    )
+    for x in ('test_remove', 'test_move', 'test_status_deleted'):
+        if request.function.__name__.startswith(x):
+            #print >>sys.stderr, ("saving repo", repo, "for", request.function)
+            _savedrepowc = save_repowc(repo, wc)
+            request.addfinalizer(lambda: restore_repowc(_savedrepowc))
+    return repo, repourl, wc
+
+def pytest_funcarg__repowc2(request):
+    tmpdir = request.getfuncargvalue("tmpdir")
+    name = request.function.__name__
+    repo, url, wc = getrepowc(tmpdir, "%s-repo-2" % name, "%s-wc-2" % name)
+    return repo, url, wc
+
+def getsvnbin():
+    if svnbin is None:
+        py.test.skip("svn binary not found")
+    return svnbin
+
+# make a wc directory out of a given root url
+# cache previously obtained wcs!
+#
+def getrepowc(tmpdir, reponame='basetestrepo', wcname='wc'):
+    repo = tmpdir.mkdir(reponame)
+    wcdir = tmpdir.mkdir(wcname)
+    repo.ensure(dir=1)
+    py.process.cmdexec('svnadmin create "%s"' %
+            svncommon._escape_helper(repo))
+    py.process.cmdexec('svnadmin load -q "%s" <"%s"' %
+            (svncommon._escape_helper(repo), repodump))
+    print_("created svn repository", repo)
+    wcdir.ensure(dir=1)
+    wc = py.path.svnwc(wcdir)
+    if sys.platform == 'win32':
+        repourl = "file://" + '/' + str(repo).replace('\\', '/')
+    else:
+        repourl = "file://%s" % repo
+    wc.checkout(repourl)
+    print_("checked out new repo into", wc)
+    return (repo, repourl, wc)
+
+
+def save_repowc(repo, wc):
+    assert not str(repo).startswith("file://"), repo
+    assert repo.check()
+    savedrepo = repo.dirpath(repo.basename+".1")
+    savedwc = wc.dirpath(wc.basename+".1")
+    repo.copy(savedrepo)
+    wc.localpath.copy(savedwc.localpath)
+    return savedrepo, savedwc
+
+def restore_repowc(obj):
+    savedrepo, savedwc = obj
+    #print >>sys.stderr, ("restoring", savedrepo)
+    repo = savedrepo.new(basename=savedrepo.basename[:-2])
+    assert repo.check()
+    wc = savedwc.new(basename=savedwc.basename[:-2])
+    assert wc.check()
+    wc.localpath.remove()
+    repo.remove()
+    savedrepo.move(repo)
+    savedwc.localpath.move(wc.localpath)
+    py.path.svnurl._lsnorevcache.clear()
+    py.path.svnurl._lsrevcache.clear()
diff --git a/tools/py/testing/path/repotest.dump b/tools/third_party/py/testing/path/repotest.dump
similarity index 100%
rename from tools/py/testing/path/repotest.dump
rename to tools/third_party/py/testing/path/repotest.dump
diff --git a/tools/py/testing/path/svntestbase.py b/tools/third_party/py/testing/path/svntestbase.py
similarity index 100%
rename from tools/py/testing/path/svntestbase.py
rename to tools/third_party/py/testing/path/svntestbase.py
diff --git a/tools/third_party/py/testing/path/test_cacheutil.py b/tools/third_party/py/testing/path/test_cacheutil.py
new file mode 100644
index 0000000..c9fc074
--- /dev/null
+++ b/tools/third_party/py/testing/path/test_cacheutil.py
@@ -0,0 +1,89 @@
+import pytest
+from py._path import cacheutil
+
+import time
+
+class BasicCacheAPITest:
+    cache = None
+    def test_getorbuild(self):
+        val = self.cache.getorbuild(-42, lambda: 42)
+        assert val == 42
+        val = self.cache.getorbuild(-42, lambda: 23)
+        assert val == 42
+
+    def test_cache_get_key_error(self):
+        pytest.raises(KeyError, "self.cache._getentry(-23)")
+
+    def test_delentry_non_raising(self):
+        self.cache.getorbuild(100, lambda: 100)
+        self.cache.delentry(100)
+        pytest.raises(KeyError, "self.cache._getentry(100)")
+
+    def test_delentry_raising(self):
+        self.cache.getorbuild(100, lambda: 100)
+        self.cache.delentry(100)
+        pytest.raises(KeyError, self.cache.delentry, 100, raising=True)
+
+    def test_clear(self):
+        self.cache.clear()
+
+
+class TestBuildcostAccess(BasicCacheAPITest):
+    cache = cacheutil.BuildcostAccessCache(maxentries=128)
+
+    def test_cache_works_somewhat_simple(self, monkeypatch):
+        cache = cacheutil.BuildcostAccessCache()
+        # the default gettime
+        # BuildcostAccessCache.build can
+        # result into time()-time() == 0 which makes the below
+        # test fail randomly.  Let's rather use incrementing
+        # numbers instead.
+        l = [0]
+
+        def counter():
+            l[0] = l[0] + 1
+            return l[0]
+        monkeypatch.setattr(cacheutil, 'gettime', counter)
+        for x in range(cache.maxentries):
+            y = cache.getorbuild(x, lambda: x)
+            assert x == y
+        for x in range(cache.maxentries):
+            assert cache.getorbuild(x, None) == x
+        halfentries = int(cache.maxentries / 2)
+        for x in range(halfentries):
+            assert cache.getorbuild(x, None) == x
+            assert cache.getorbuild(x, None) == x
+        # evict one entry
+        val = cache.getorbuild(-1, lambda: 42)
+        assert val == 42
+        # check that recently used ones are still there
+        # and are not build again
+        for x in range(halfentries):
+            assert cache.getorbuild(x, None) == x
+        assert cache.getorbuild(-1, None) == 42
+
+
+class TestAging(BasicCacheAPITest):
+    maxsecs = 0.10
+    cache = cacheutil.AgingCache(maxentries=128, maxseconds=maxsecs)
+
+    def test_cache_eviction(self):
+        self.cache.getorbuild(17, lambda: 17)
+        endtime = time.time() + self.maxsecs * 10
+        while time.time() < endtime:
+            try:
+                self.cache._getentry(17)
+            except KeyError:
+                break
+            time.sleep(self.maxsecs*0.3)
+        else:
+            pytest.fail("waiting for cache eviction failed")
+
+
+def test_prune_lowestweight():
+    maxsecs = 0.05
+    cache = cacheutil.AgingCache(maxentries=10, maxseconds=maxsecs)
+    for x in range(cache.maxentries):
+        cache.getorbuild(x, lambda: x)
+    time.sleep(maxsecs*1.1)
+    cache.getorbuild(cache.maxentries+1, lambda: 42)
diff --git a/tools/third_party/py/testing/path/test_local.py b/tools/third_party/py/testing/path/test_local.py
new file mode 100644
index 0000000..c9075d6
--- /dev/null
+++ b/tools/third_party/py/testing/path/test_local.py
@@ -0,0 +1,977 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+import time
+import py
+import pytest
+import os
+import sys
+import multiprocessing
+from py.path import local
+import common
+
+failsonjython = py.test.mark.xfail("sys.platform.startswith('java')")
+failsonjywin32 = py.test.mark.xfail(
+    "sys.platform.startswith('java') "
+    "and getattr(os, '_name', None) == 'nt'")
+win32only = py.test.mark.skipif(
+        "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')")
+skiponwin32 = py.test.mark.skipif(
+        "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'")
+
+ATIME_RESOLUTION = 0.01
+
+
+@pytest.yield_fixture(scope="session")
+def path1(tmpdir_factory):
+    path = tmpdir_factory.mktemp('path')
+    common.setuptestfs(path)
+    yield path
+    assert path.join("samplefile").check()
+
+
+@pytest.fixture
+def fake_fspath_obj(request):
+    class FakeFSPathClass(object):
+        def __init__(self, path):
+            self._path = path
+
+        def __fspath__(self):
+            return self._path
+
+    return FakeFSPathClass(os.path.join("this", "is", "a", "fake", "path"))
+
+
+def batch_make_numbered_dirs(rootdir, repeats):
+    try:
+        for i in range(repeats):
+            dir_ = py.path.local.make_numbered_dir(prefix='repro-', rootdir=rootdir)
+            file_ = dir_.join('foo')
+            file_.write('%s' % i)
+            actual = int(file_.read())
+            assert actual == i, 'int(file_.read()) is %s instead of %s' % (actual, i)
+            dir_.join('.lock').remove(ignore_errors=True)
+        return True
+    except KeyboardInterrupt:
+        # makes sure that interrupting test session won't hang it
+        os.exit(2)
+
+
+class TestLocalPath(common.CommonFSTests):
+    def test_join_normpath(self, tmpdir):
+        assert tmpdir.join(".") == tmpdir
+        p = tmpdir.join("../%s" % tmpdir.basename)
+        assert p == tmpdir
+        p = tmpdir.join("..//%s/" % tmpdir.basename)
+        assert p == tmpdir
+
+    @skiponwin32
+    def test_dirpath_abs_no_abs(self, tmpdir):
+        p = tmpdir.join('foo')
+        assert p.dirpath('/bar') == tmpdir.join('bar')
+        assert tmpdir.dirpath('/bar', abs=True) == local('/bar')
+
+    def test_gethash(self, tmpdir):
+        md5 = py.builtin._tryimport('md5', 'hashlib').md5
+        lib = py.builtin._tryimport('sha', 'hashlib')
+        sha = getattr(lib, 'sha1', getattr(lib, 'sha', None))
+        fn = tmpdir.join("testhashfile")
+        data = 'hello'.encode('ascii')
+        fn.write(data, mode="wb")
+        assert fn.computehash("md5") == md5(data).hexdigest()
+        assert fn.computehash("sha1") == sha(data).hexdigest()
+        py.test.raises(ValueError, fn.computehash, "asdasd")
+
+    def test_remove_removes_readonly_file(self, tmpdir):
+        readonly_file = tmpdir.join('readonly').ensure()
+        readonly_file.chmod(0)
+        readonly_file.remove()
+        assert not readonly_file.check(exists=1)
+
+    def test_remove_removes_readonly_dir(self, tmpdir):
+        readonly_dir = tmpdir.join('readonlydir').ensure(dir=1)
+        readonly_dir.chmod(int("500", 8))
+        readonly_dir.remove()
+        assert not readonly_dir.check(exists=1)
+
+    def test_remove_removes_dir_and_readonly_file(self, tmpdir):
+        readonly_dir = tmpdir.join('readonlydir').ensure(dir=1)
+        readonly_file = readonly_dir.join('readonlyfile').ensure()
+        readonly_file.chmod(0)
+        readonly_dir.remove()
+        assert not readonly_dir.check(exists=1)
+
+    def test_remove_routes_ignore_errors(self, tmpdir, monkeypatch):
+        l = []
+        monkeypatch.setattr(
+            'shutil.rmtree',
+            lambda *args, **kwargs: l.append(kwargs))
+        tmpdir.remove()
+        assert not l[0]['ignore_errors']
+        for val in (True, False):
+            l[:] = []
+            tmpdir.remove(ignore_errors=val)
+            assert l[0]['ignore_errors'] == val
+
+    def test_initialize_curdir(self):
+        assert str(local()) == os.getcwd()
+
+    @skiponwin32
+    def test_chdir_gone(self, path1):
+        p = path1.ensure("dir_to_be_removed", dir=1)
+        p.chdir()
+        p.remove()
+        pytest.raises(py.error.ENOENT, py.path.local)
+        assert path1.chdir() is None
+        assert os.getcwd() == str(path1)
+
+    def test_as_cwd(self, path1):
+        dir = path1.ensure("subdir", dir=1)
+        old = py.path.local()
+        with dir.as_cwd() as x:
+            assert x == old
+            assert py.path.local() == dir
+        assert os.getcwd() == str(old)
+
+    def test_as_cwd_exception(self, path1):
+        old = py.path.local()
+        dir = path1.ensure("subdir", dir=1)
+        with pytest.raises(ValueError):
+            with dir.as_cwd():
+                raise ValueError()
+        assert old == py.path.local()
+
+    def test_initialize_reldir(self, path1):
+        with path1.as_cwd():
+            p = local('samplefile')
+            assert p.check()
+
+    def test_tilde_expansion(self, monkeypatch, tmpdir):
+        monkeypatch.setenv("HOME", str(tmpdir))
+        p = py.path.local("~", expanduser=True)
+        assert p == os.path.expanduser("~")
+
+    def test_eq_with_strings(self, path1):
+        path1 = path1.join('sampledir')
+        path2 = str(path1)
+        assert path1 == path2
+        assert path2 == path1
+        path3 = path1.join('samplefile')
+        assert path3 != path2
+        assert path2 != path3
+
+    def test_eq_with_none(self, path1):
+        assert path1 != None  # noqa
+
+    def test_eq_non_ascii_unicode(self, path1):
+        path2 = path1.join(u'temp')
+        path3 = path1.join(u'ação')
+        path4 = path1.join(u'ディレクトリ')
+
+        assert path2 != path3
+        assert path2 != path4
+        assert path4 != path3
+
+    def test_gt_with_strings(self, path1):
+        path2 = path1.join('sampledir')
+        path3 = str(path1.join("ttt"))
+        assert path3 > path2
+        assert path2 < path3
+        assert path2 < "ttt"
+        assert "ttt" > path2
+        path4 = path1.join("aaa")
+        l = [path2, path4, path3]
+        assert sorted(l) == [path4, path2, path3]
+
+    def test_open_and_ensure(self, path1):
+        p = path1.join("sub1", "sub2", "file")
+        with p.open("w", ensure=1) as f:
+            f.write("hello")
+        assert p.read() == "hello"
+
+    def test_write_and_ensure(self, path1):
+        p = path1.join("sub1", "sub2", "file")
+        p.write("hello", ensure=1)
+        assert p.read() == "hello"
+
+    @py.test.mark.parametrize('bin', (False, True))
+    def test_dump(self, tmpdir, bin):
+        path = tmpdir.join("dumpfile%s" % int(bin))
+        try:
+            d = {'answer': 42}
+            path.dump(d, bin=bin)
+            f = path.open('rb+')
+            import pickle
+            dnew = pickle.load(f)
+            assert d == dnew
+        finally:
+            f.close()
+
+    @failsonjywin32
+    def test_setmtime(self):
+        import tempfile
+        import time
+        try:
+            fd, name = tempfile.mkstemp()
+            os.close(fd)
+        except AttributeError:
+            name = tempfile.mktemp()
+            open(name, 'w').close()
+        try:
+            mtime = int(time.time())-100
+            path = local(name)
+            assert path.mtime() != mtime
+            path.setmtime(mtime)
+            assert path.mtime() == mtime
+            path.setmtime()
+            assert path.mtime() != mtime
+        finally:
+            os.remove(name)
+
+    def test_normpath(self, path1):
+        new1 = path1.join("/otherdir")
+        new2 = path1.join("otherdir")
+        assert str(new1) == str(new2)
+
+    def test_mkdtemp_creation(self):
+        d = local.mkdtemp()
+        try:
+            assert d.check(dir=1)
+        finally:
+            d.remove(rec=1)
+
+    def test_tmproot(self):
+        d = local.mkdtemp()
+        tmproot = local.get_temproot()
+        try:
+            assert d.check(dir=1)
+            assert d.dirpath() == tmproot
+        finally:
+            d.remove(rec=1)
+
+    def test_chdir(self, tmpdir):
+        old = local()
+        try:
+            res = tmpdir.chdir()
+            assert str(res) == str(old)
+            assert os.getcwd() == str(tmpdir)
+        finally:
+            old.chdir()
+
+    def test_ensure_filepath_withdir(self, tmpdir):
+        newfile = tmpdir.join('test1', 'test')
+        newfile.ensure()
+        assert newfile.check(file=1)
+        newfile.write("42")
+        newfile.ensure()
+        s = newfile.read()
+        assert s == "42"
+
+    def test_ensure_filepath_withoutdir(self, tmpdir):
+        newfile = tmpdir.join('test1file')
+        t = newfile.ensure()
+        assert t == newfile
+        assert newfile.check(file=1)
+
+    def test_ensure_dirpath(self, tmpdir):
+        newfile = tmpdir.join('test1', 'testfile')
+        t = newfile.ensure(dir=1)
+        assert t == newfile
+        assert newfile.check(dir=1)
+
+    def test_ensure_non_ascii_unicode(self, tmpdir):
+        newfile = tmpdir.join(u'ação',u'ディレクトリ')
+        t = newfile.ensure(dir=1)
+        assert t == newfile
+        assert newfile.check(dir=1)
+
+    def test_init_from_path(self, tmpdir):
+        l = local()
+        l2 = local(l)
+        assert l2 == l
+
+        wc = py.path.svnwc('.')
+        l3 = local(wc)
+        assert l3 is not wc
+        assert l3.strpath == wc.strpath
+        assert not hasattr(l3, 'commit')
+
+    @py.test.mark.xfail(run=False, reason="unreliable est for long filenames")
+    def test_long_filenames(self, tmpdir):
+        if sys.platform == "win32":
+            py.test.skip("win32: work around needed for path length limit")
+        # see http://codespeak.net/pipermail/py-dev/2008q2/000922.html
+
+        # testing paths > 260 chars (which is Windows' limitation, but
+        # depending on how the paths are used), but > 4096 (which is the
+        # Linux' limitation) - the behaviour of paths with names > 4096 chars
+        # is undetermined
+        newfilename = '/test' * 60
+        l = tmpdir.join(newfilename)
+        l.ensure(file=True)
+        l.write('foo')
+        l2 = tmpdir.join(newfilename)
+        assert l2.read() == 'foo'
+
+    def test_visit_depth_first(self, tmpdir):
+        tmpdir.ensure("a", "1")
+        tmpdir.ensure("b", "2")
+        p3 = tmpdir.ensure("breadth")
+        l = list(tmpdir.visit(lambda x: x.check(file=1)))
+        assert len(l) == 3
+        # check that breadth comes last
+        assert l[2] == p3
+
+    def test_visit_rec_fnmatch(self, tmpdir):
+        p1 = tmpdir.ensure("a", "123")
+        tmpdir.ensure(".b", "345")
+        l = list(tmpdir.visit("???", rec="[!.]*"))
+        assert len(l) == 1
+        # check that breadth comes last
+        assert l[0] == p1
+
+    def test_fnmatch_file_abspath(self, tmpdir):
+        b = tmpdir.join("a", "b")
+        assert b.fnmatch(os.sep.join("ab"))
+        pattern = os.sep.join([str(tmpdir), "*", "b"])
+        assert b.fnmatch(pattern)
+
+    def test_sysfind(self):
+        name = sys.platform == "win32" and "cmd" or "test"
+        x = py.path.local.sysfind(name)
+        assert x.check(file=1)
+        assert py.path.local.sysfind('jaksdkasldqwe') is None
+        assert py.path.local.sysfind(name, paths=[]) is None
+        x2 = py.path.local.sysfind(name, paths=[x.dirpath()])
+        assert x2 == x
+
+    def test_fspath_protocol_other_class(self, fake_fspath_obj):
+        # py.path is always absolute
+        py_path = py.path.local(fake_fspath_obj)
+        str_path = fake_fspath_obj.__fspath__()
+        assert py_path.check(endswith=str_path)
+        assert py_path.join(fake_fspath_obj).strpath == os.path.join(
+                py_path.strpath, str_path)
+
+    def test_make_numbered_dir_multiprocess_safe(self, tmpdir):
+        # https://github.com/pytest-dev/py/issues/30
+        pool = multiprocessing.Pool()
+        results = [pool.apply_async(batch_make_numbered_dirs, [tmpdir, 100]) for _ in range(20)]
+        for r in results:
+            assert r.get()
+
+
+class TestExecutionOnWindows:
+    pytestmark = win32only
+
+    def test_sysfind_bat_exe_before(self, tmpdir, monkeypatch):
+        monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep)
+        tmpdir.ensure("hello")
+        h = tmpdir.ensure("hello.bat")
+        x = py.path.local.sysfind("hello")
+        assert x == h
+
+
+class TestExecution:
+    pytestmark = skiponwin32
+
+    def test_sysfind_no_permisson_ignored(self, monkeypatch, tmpdir):
+        noperm = tmpdir.ensure('noperm', dir=True)
+        monkeypatch.setenv("PATH", noperm, prepend=":")
+        noperm.chmod(0)
+        assert py.path.local.sysfind('jaksdkasldqwe') is None
+
+    def test_sysfind_absolute(self):
+        x = py.path.local.sysfind('test')
+        assert x.check(file=1)
+        y = py.path.local.sysfind(str(x))
+        assert y.check(file=1)
+        assert y == x
+
+    def test_sysfind_multiple(self, tmpdir, monkeypatch):
+        monkeypatch.setenv('PATH', "%s:%s" % (
+                            tmpdir.ensure('a'),
+                            tmpdir.join('b')),
+                           prepend=":")
+        tmpdir.ensure('b', 'a')
+        x = py.path.local.sysfind(
+            'a', checker=lambda x: x.dirpath().basename == 'b')
+        assert x.basename == 'a'
+        assert x.dirpath().basename == 'b'
+        assert py.path.local.sysfind('a', checker=lambda x: None) is None
+
+    def test_sysexec(self):
+        x = py.path.local.sysfind('ls')
+        out = x.sysexec('-a')
+        for x in py.path.local().listdir():
+            assert out.find(x.basename) != -1
+
+    def test_sysexec_failing(self):
+        x = py.path.local.sysfind('false')
+        with pytest.raises(py.process.cmdexec.Error):
+            x.sysexec('aksjdkasjd')
+
+    def test_make_numbered_dir(self, tmpdir):
+        tmpdir.ensure('base.not_an_int', dir=1)
+        for i in range(10):
+            numdir = local.make_numbered_dir(prefix='base.', rootdir=tmpdir,
+                                             keep=2, lock_timeout=0)
+            assert numdir.check()
+            assert numdir.basename == 'base.%d' % i
+            if i >= 1:
+                assert numdir.new(ext=str(i-1)).check()
+            if i >= 2:
+                assert numdir.new(ext=str(i-2)).check()
+            if i >= 3:
+                assert not numdir.new(ext=str(i-3)).check()
+
+    def test_make_numbered_dir_case_insensitive(self, tmpdir, monkeypatch):
+        # https://github.com/pytest-dev/pytest/issues/708
+        monkeypatch.setattr(py._path.local, 'normcase',
+                            lambda path: path.lower())
+        monkeypatch.setattr(tmpdir, 'listdir',
+                            lambda: [tmpdir._fastjoin('case.0')])
+        numdir = local.make_numbered_dir(prefix='CAse.', rootdir=tmpdir,
+                                         keep=2, lock_timeout=0)
+        assert numdir.basename.endswith('.1')
+
+    def test_make_numbered_dir_case_sensitive(self, tmpdir, monkeypatch):
+        # https://github.com/pytest-dev/pytest/issues/708
+        monkeypatch.setattr(py._path.local, 'normcase', lambda path: path)
+        monkeypatch.setattr(tmpdir, 'listdir',
+                            lambda: [tmpdir._fastjoin('case.0')])
+        numdir = local.make_numbered_dir(prefix='CAse.', rootdir=tmpdir,
+                                         keep=2, lock_timeout=0)
+        assert numdir.basename.endswith('.0')
+
+    def test_make_numbered_dir_NotImplemented_Error(self, tmpdir, monkeypatch):
+        def notimpl(x, y):
+            raise NotImplementedError(42)
+        monkeypatch.setattr(os, 'symlink', notimpl)
+        x = tmpdir.make_numbered_dir(rootdir=tmpdir, lock_timeout=0)
+        assert x.relto(tmpdir)
+        assert x.check()
+
+    def test_locked_make_numbered_dir(self, tmpdir):
+        for i in range(10):
+            numdir = local.make_numbered_dir(prefix='base2.', rootdir=tmpdir,
+                                             keep=2)
+            assert numdir.check()
+            assert numdir.basename == 'base2.%d' % i
+            for j in range(i):
+                assert numdir.new(ext=str(j)).check()
+
+    def test_error_preservation(self, path1):
+        py.test.raises(EnvironmentError, path1.join('qwoeqiwe').mtime)
+        py.test.raises(EnvironmentError, path1.join('qwoeqiwe').read)
+
+    # def test_parentdirmatch(self):
+    #    local.parentdirmatch('std', startmodule=__name__)
+    #
+
+
+class TestImport:
+    def test_pyimport(self, path1):
+        obj = path1.join('execfile.py').pyimport()
+        assert obj.x == 42
+        assert obj.__name__ == 'execfile'
+
+    def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir):
+        p = tmpdir.ensure("a", "test_x123.py")
+        p.pyimport()
+        tmpdir.join("a").move(tmpdir.join("b"))
+        with pytest.raises(tmpdir.ImportMismatchError):
+            tmpdir.join("b", "test_x123.py").pyimport()
+
+    def test_pyimport_messy_name(self, tmpdir):
+        # http://bitbucket.org/hpk42/py-trunk/issue/129
+        path = tmpdir.ensure('foo__init__.py')
+        path.pyimport()
+
+    def test_pyimport_dir(self, tmpdir):
+        p = tmpdir.join("hello_123")
+        p_init = p.ensure("__init__.py")
+        m = p.pyimport()
+        assert m.__name__ == "hello_123"
+        m = p_init.pyimport()
+        assert m.__name__ == "hello_123"
+
+    def test_pyimport_execfile_different_name(self, path1):
+        obj = path1.join('execfile.py').pyimport(modname="0x.y.z")
+        assert obj.x == 42
+        assert obj.__name__ == '0x.y.z'
+
+    def test_pyimport_a(self, path1):
+        otherdir = path1.join('otherdir')
+        mod = otherdir.join('a.py').pyimport()
+        assert mod.result == "got it"
+        assert mod.__name__ == 'otherdir.a'
+
+    def test_pyimport_b(self, path1):
+        otherdir = path1.join('otherdir')
+        mod = otherdir.join('b.py').pyimport()
+        assert mod.stuff == "got it"
+        assert mod.__name__ == 'otherdir.b'
+
+    def test_pyimport_c(self, path1):
+        otherdir = path1.join('otherdir')
+        mod = otherdir.join('c.py').pyimport()
+        assert mod.value == "got it"
+
+    def test_pyimport_d(self, path1):
+        otherdir = path1.join('otherdir')
+        mod = otherdir.join('d.py').pyimport()
+        assert mod.value2 == "got it"
+
+    def test_pyimport_and_import(self, tmpdir):
+        tmpdir.ensure('xxxpackage', '__init__.py')
+        mod1path = tmpdir.ensure('xxxpackage', 'module1.py')
+        mod1 = mod1path.pyimport()
+        assert mod1.__name__ == 'xxxpackage.module1'
+        from xxxpackage import module1
+        assert module1 is mod1
+
+    def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir):
+        name = 'pointsback123'
+        ModuleType = type(os)
+        p = tmpdir.ensure(name + '.py')
+        for ending in ('.pyc', '$py.class', '.pyo'):
+            mod = ModuleType(name)
+            pseudopath = tmpdir.ensure(name+ending)
+            mod.__file__ = str(pseudopath)
+            monkeypatch.setitem(sys.modules, name, mod)
+            newmod = p.pyimport()
+            assert mod == newmod
+        monkeypatch.undo()
+        mod = ModuleType(name)
+        pseudopath = tmpdir.ensure(name+"123.py")
+        mod.__file__ = str(pseudopath)
+        monkeypatch.setitem(sys.modules, name, mod)
+        excinfo = py.test.raises(pseudopath.ImportMismatchError, p.pyimport)
+        modname, modfile, orig = excinfo.value.args
+        assert modname == name
+        assert modfile == pseudopath
+        assert orig == p
+        assert issubclass(pseudopath.ImportMismatchError, ImportError)
+
+    def test_issue131_pyimport_on__init__(self, tmpdir):
+        # __init__.py files may be namespace packages, and thus the
+        # __file__ of an imported module may not be ourselves
+        # see issue
+        p1 = tmpdir.ensure("proja", "__init__.py")
+        p2 = tmpdir.ensure("sub", "proja", "__init__.py")
+        m1 = p1.pyimport()
+        m2 = p2.pyimport()
+        assert m1 == m2
+
+    def test_ensuresyspath_append(self, tmpdir):
+        root1 = tmpdir.mkdir("root1")
+        file1 = root1.ensure("x123.py")
+        assert str(root1) not in sys.path
+        file1.pyimport(ensuresyspath="append")
+        assert str(root1) == sys.path[-1]
+        assert str(root1) not in sys.path[:-1]
+
+
+def test_pypkgdir(tmpdir):
+    pkg = tmpdir.ensure('pkg1', dir=1)
+    pkg.ensure("__init__.py")
+    pkg.ensure("subdir/__init__.py")
+    assert pkg.pypkgpath() == pkg
+    assert pkg.join('subdir', '__init__.py').pypkgpath() == pkg
+
+
+def test_pypkgdir_unimportable(tmpdir):
+    pkg = tmpdir.ensure('pkg1-1', dir=1)  # unimportable
+    pkg.ensure("__init__.py")
+    subdir = pkg.ensure("subdir/__init__.py").dirpath()
+    assert subdir.pypkgpath() == subdir
+    assert subdir.ensure("xyz.py").pypkgpath() == subdir
+    assert not pkg.pypkgpath()
+
+
+def test_isimportable():
+    from py._path.local import isimportable
+    assert not isimportable("")
+    assert isimportable("x")
+    assert isimportable("x1")
+    assert isimportable("x_1")
+    assert isimportable("_")
+    assert isimportable("_1")
+    assert not isimportable("x-1")
+    assert not isimportable("x:1")
+
+
+def test_homedir_from_HOME(monkeypatch):
+    path = os.getcwd()
+    monkeypatch.setenv("HOME", path)
+    assert py.path.local._gethomedir() == py.path.local(path)
+
+
+def test_homedir_not_exists(monkeypatch):
+    monkeypatch.delenv("HOME", raising=False)
+    monkeypatch.delenv("HOMEDRIVE", raising=False)
+    homedir = py.path.local._gethomedir()
+    assert homedir is None
+
+
+def test_samefile(tmpdir):
+    assert tmpdir.samefile(tmpdir)
+    p = tmpdir.ensure("hello")
+    assert p.samefile(p)
+    with p.dirpath().as_cwd():
+        assert p.samefile(p.basename)
+    if sys.platform == "win32":
+        p1 = p.__class__(str(p).lower())
+        p2 = p.__class__(str(p).upper())
+        assert p1.samefile(p2)
+
+
+def test_listdir_single_arg(tmpdir):
+    tmpdir.ensure("hello")
+    assert tmpdir.listdir("hello")[0].basename == "hello"
+
+
+def test_mkdtemp_rootdir(tmpdir):
+    dtmp = local.mkdtemp(rootdir=tmpdir)
+    assert tmpdir.listdir() == [dtmp]
+
+
+class TestWINLocalPath:
+    pytestmark = win32only
+
+    def test_owner_group_not_implemented(self, path1):
+        py.test.raises(NotImplementedError, "path1.stat().owner")
+        py.test.raises(NotImplementedError, "path1.stat().group")
+
+    def test_chmod_simple_int(self, path1):
+        py.builtin.print_("path1 is", path1)
+        mode = path1.stat().mode
+        # Ensure that we actually change the mode to something different.
+        path1.chmod(mode == 0 and 1 or 0)
+        try:
+            print(path1.stat().mode)
+            print(mode)
+            assert path1.stat().mode != mode
+        finally:
+            path1.chmod(mode)
+            assert path1.stat().mode == mode
+
+    def test_path_comparison_lowercase_mixed(self, path1):
+        t1 = path1.join("a_path")
+        t2 = path1.join("A_path")
+        assert t1 == t1
+        assert t1 == t2
+
+    def test_relto_with_mixed_case(self, path1):
+        t1 = path1.join("a_path", "fiLe")
+        t2 = path1.join("A_path")
+        assert t1.relto(t2) == "fiLe"
+
+    def test_allow_unix_style_paths(self, path1):
+        t1 = path1.join('a_path')
+        assert t1 == str(path1) + '\\a_path'
+        t1 = path1.join('a_path/')
+        assert t1 == str(path1) + '\\a_path'
+        t1 = path1.join('dir/a_path')
+        assert t1 == str(path1) + '\\dir\\a_path'
+
+    def test_sysfind_in_currentdir(self, path1):
+        cmd = py.path.local.sysfind('cmd')
+        root = cmd.new(dirname='', basename='')  # c:\ in most installations
+        with root.as_cwd():
+            x = py.path.local.sysfind(cmd.relto(root))
+            assert x.check(file=1)
+
+    def test_fnmatch_file_abspath_posix_pattern_on_win32(self, tmpdir):
+        # path-matching patterns might contain a posix path separator '/'
+        # Test that we can match that pattern on windows.
+        import posixpath
+        b = tmpdir.join("a", "b")
+        assert b.fnmatch(posixpath.sep.join("ab"))
+        pattern = posixpath.sep.join([str(tmpdir), "*", "b"])
+        assert b.fnmatch(pattern)
+
+
+class TestPOSIXLocalPath:
+    pytestmark = skiponwin32
+
+    def test_hardlink(self, tmpdir):
+        linkpath = tmpdir.join('test')
+        filepath = tmpdir.join('file')
+        filepath.write("Hello")
+        nlink = filepath.stat().nlink
+        linkpath.mklinkto(filepath)
+        assert filepath.stat().nlink == nlink + 1
+
+    def test_symlink_are_identical(self, tmpdir):
+        filepath = tmpdir.join('file')
+        filepath.write("Hello")
+        linkpath = tmpdir.join('test')
+        linkpath.mksymlinkto(filepath)
+        assert linkpath.readlink() == str(filepath)
+
+    def test_symlink_isfile(self, tmpdir):
+        linkpath = tmpdir.join('test')
+        filepath = tmpdir.join('file')
+        filepath.write("")
+        linkpath.mksymlinkto(filepath)
+        assert linkpath.check(file=1)
+        assert not linkpath.check(link=0, file=1)
+        assert linkpath.islink()
+
+    def test_symlink_relative(self, tmpdir):
+        linkpath = tmpdir.join('test')
+        filepath = tmpdir.join('file')
+        filepath.write("Hello")
+        linkpath.mksymlinkto(filepath, absolute=False)
+        assert linkpath.readlink() == "file"
+        assert filepath.read() == linkpath.read()
+
+    def test_symlink_not_existing(self, tmpdir):
+        linkpath = tmpdir.join('testnotexisting')
+        assert not linkpath.check(link=1)
+        assert linkpath.check(link=0)
+
+    def test_relto_with_root(self, path1, tmpdir):
+        y = path1.join('x').relto(py.path.local('/'))
+        assert y[0] == str(path1)[1]
+
+    def test_visit_recursive_symlink(self, tmpdir):
+        linkpath = tmpdir.join('test')
+        linkpath.mksymlinkto(tmpdir)
+        visitor = tmpdir.visit(None, lambda x: x.check(link=0))
+        assert list(visitor) == [linkpath]
+
+    def test_symlink_isdir(self, tmpdir):
+        linkpath = tmpdir.join('test')
+        linkpath.mksymlinkto(tmpdir)
+        assert linkpath.check(dir=1)
+        assert not linkpath.check(link=0, dir=1)
+
+    def test_symlink_remove(self, tmpdir):
+        linkpath = tmpdir.join('test')
+        linkpath.mksymlinkto(linkpath)  # point to itself
+        assert linkpath.check(link=1)
+        linkpath.remove()
+        assert not linkpath.check()
+
+    def test_realpath_file(self, tmpdir):
+        linkpath = tmpdir.join('test')
+        filepath = tmpdir.join('file')
+        filepath.write("")
+        linkpath.mksymlinkto(filepath)
+        realpath = linkpath.realpath()
+        assert realpath.basename == 'file'
+
+    def test_owner(self, path1, tmpdir):
+        from pwd import getpwuid
+        from grp import getgrgid
+        stat = path1.stat()
+        assert stat.path == path1
+
+        uid = stat.uid
+        gid = stat.gid
+        owner = getpwuid(uid)[0]
+        group = getgrgid(gid)[0]
+
+        assert uid == stat.uid
+        assert owner == stat.owner
+        assert gid == stat.gid
+        assert group == stat.group
+
+    def test_stat_helpers(self, tmpdir, monkeypatch):
+        path1 = tmpdir.ensure("file")
+        stat1 = path1.stat()
+        stat2 = tmpdir.stat()
+        assert stat1.isfile()
+        assert stat2.isdir()
+        assert not stat1.islink()
+        assert not stat2.islink()
+
+    def test_stat_non_raising(self, tmpdir):
+        path1 = tmpdir.join("file")
+        pytest.raises(py.error.ENOENT, lambda: path1.stat())
+        res = path1.stat(raising=False)
+        assert res is None
+
+    def test_atime(self, tmpdir):
+        import time
+        path = tmpdir.ensure('samplefile')
+        now = time.time()
+        atime1 = path.atime()
+        # we could wait here but timer resolution is very
+        # system dependent
+        path.read()
+        time.sleep(ATIME_RESOLUTION)
+        atime2 = path.atime()
+        time.sleep(ATIME_RESOLUTION)
+        duration = time.time() - now
+        assert (atime2-atime1) <= duration
+
+    def test_commondir(self, path1):
+        # XXX This is here in local until we find a way to implement this
+        #     using the subversion command line api.
+        p1 = path1.join('something')
+        p2 = path1.join('otherthing')
+        assert p1.common(p2) == path1
+        assert p2.common(p1) == path1
+
+    def test_commondir_nocommon(self, path1):
+        # XXX This is here in local until we find a way to implement this
+        #     using the subversion command line api.
+        p1 = path1.join('something')
+        p2 = py.path.local(path1.sep+'blabla')
+        assert p1.common(p2) == '/'
+
+    def test_join_to_root(self, path1):
+        root = path1.parts()[0]
+        assert len(str(root)) == 1
+        assert str(root.join('a')) == '/a'
+
+    def test_join_root_to_root_with_no_abs(self, path1):
+        nroot = path1.join('/')
+        assert str(path1) == str(nroot)
+        assert path1 == nroot
+
+    def test_chmod_simple_int(self, path1):
+        mode = path1.stat().mode
+        path1.chmod(int(mode/2))
+        try:
+            assert path1.stat().mode != mode
+        finally:
+            path1.chmod(mode)
+            assert path1.stat().mode == mode
+
+    def test_chmod_rec_int(self, path1):
+        # XXX fragile test
+        def recfilter(x): return x.check(dotfile=0, link=0)
+        oldmodes = {}
+        for x in path1.visit(rec=recfilter):
+            oldmodes[x] = x.stat().mode
+        path1.chmod(int("772", 8), rec=recfilter)
+        try:
+            for x in path1.visit(rec=recfilter):
+                assert x.stat().mode & int("777", 8) == int("772", 8)
+        finally:
+            for x, y in oldmodes.items():
+                x.chmod(y)
+
+    def test_copy_archiving(self, tmpdir):
+        unicode_fn = u"something-\342\200\223.txt"
+        f = tmpdir.ensure("a", unicode_fn)
+        a = f.dirpath()
+        oldmode = f.stat().mode
+        newmode = oldmode ^ 1
+        f.chmod(newmode)
+        b = tmpdir.join("b")
+        a.copy(b, mode=True)
+        assert b.join(f.basename).stat().mode == newmode
+
+    def test_copy_stat_file(self, tmpdir):
+        src = tmpdir.ensure('src')
+        dst = tmpdir.join('dst')
+        # a small delay before the copy
+        time.sleep(ATIME_RESOLUTION)
+        src.copy(dst, stat=True)
+        oldstat = src.stat()
+        newstat = dst.stat()
+        assert oldstat.mode == newstat.mode
+        assert (dst.atime() - src.atime()) < ATIME_RESOLUTION
+        assert (dst.mtime() - src.mtime()) < ATIME_RESOLUTION
+
+    def test_copy_stat_dir(self, tmpdir):
+        test_files = ['a', 'b', 'c']
+        src = tmpdir.join('src')
+        for f in test_files:
+            src.join(f).write(f, ensure=True)
+        dst = tmpdir.join('dst')
+        # a small delay before the copy
+        time.sleep(ATIME_RESOLUTION)
+        src.copy(dst, stat=True)
+        for f in test_files:
+            oldstat = src.join(f).stat()
+            newstat = dst.join(f).stat()
+            assert (newstat.atime - oldstat.atime) < ATIME_RESOLUTION
+            assert (newstat.mtime - oldstat.mtime) < ATIME_RESOLUTION
+            assert oldstat.mode == newstat.mode
+
+    @failsonjython
+    def test_chown_identity(self, path1):
+        owner = path1.stat().owner
+        group = path1.stat().group
+        path1.chown(owner, group)
+
+    @failsonjython
+    def test_chown_dangling_link(self, path1):
+        owner = path1.stat().owner
+        group = path1.stat().group
+        x = path1.join('hello')
+        x.mksymlinkto('qlwkejqwlek')
+        try:
+            path1.chown(owner, group, rec=1)
+        finally:
+            x.remove(rec=0)
+
+    @failsonjython
+    def test_chown_identity_rec_mayfail(self, path1):
+        owner = path1.stat().owner
+        group = path1.stat().group
+        path1.chown(owner, group)
+
+
+class TestUnicodePy2Py3:
+    def test_join_ensure(self, tmpdir, monkeypatch):
+        if sys.version_info >= (3, 0) and "LANG" not in os.environ:
+            pytest.skip("cannot run test without locale")
+        x = py.path.local(tmpdir.strpath)
+        part = "hällo"
+        y = x.ensure(part)
+        assert x.join(part) == y
+
+    def test_listdir(self, tmpdir):
+        if sys.version_info >= (3, 0) and "LANG" not in os.environ:
+            pytest.skip("cannot run test without locale")
+        x = py.path.local(tmpdir.strpath)
+        part = "hällo"
+        y = x.ensure(part)
+        assert x.listdir(part)[0] == y
+
+    @pytest.mark.xfail(
+        reason="changing read/write might break existing usages")
+    def test_read_write(self, tmpdir):
+        x = tmpdir.join("hello")
+        part = py.builtin._totext("hällo", "utf8")
+        x.write(part)
+        assert x.read() == part
+        x.write(part.encode(sys.getdefaultencoding()))
+        assert x.read() == part.encode(sys.getdefaultencoding())
+
+
+class TestBinaryAndTextMethods:
+    def test_read_binwrite(self, tmpdir):
+        x = tmpdir.join("hello")
+        part = py.builtin._totext("hällo", "utf8")
+        part_utf8 = part.encode("utf8")
+        x.write_binary(part_utf8)
+        assert x.read_binary() == part_utf8
+        s = x.read_text(encoding="utf8")
+        assert s == part
+        assert py.builtin._istext(s)
+
+    def test_read_textwrite(self, tmpdir):
+        x = tmpdir.join("hello")
+        part = py.builtin._totext("hällo", "utf8")
+        part_utf8 = part.encode("utf8")
+        x.write_text(part, encoding="utf8")
+        assert x.read_binary() == part_utf8
+        assert x.read_text(encoding="utf8") == part
+
+    def test_default_encoding(self, tmpdir):
+        x = tmpdir.join("hello")
+        # Can't use UTF8 as the default encoding (ASCII) doesn't support it
+        part = py.builtin._totext("hello", "ascii")
+        x.write_text(part, "ascii")
+        s = x.read_text("ascii")
+        assert s == part
+        assert type(s) == type(part)
diff --git a/tools/third_party/py/testing/path/test_svnauth.py b/tools/third_party/py/testing/path/test_svnauth.py
new file mode 100644
index 0000000..654f033
--- /dev/null
+++ b/tools/third_party/py/testing/path/test_svnauth.py
@@ -0,0 +1,460 @@
+import py
+from py.path import SvnAuth
+import time
+import sys
+
+svnbin = py.path.local.sysfind('svn')
+
+
+def make_repo_auth(repo, userdata):
+    """ write config to repo
+
+        user information in userdata is used for auth
+        userdata has user names as keys, and a tuple (password, readwrite) as
+        values, where 'readwrite' is either 'r' or 'rw'
+    """
+    confdir = py.path.local(repo).join('conf')
+    confdir.join('svnserve.conf').write('''\
+[general]
+anon-access = none
+password-db = passwd
+authz-db = authz
+realm = TestRepo
+''')
+    authzdata = '[/]\n'
+    passwddata = '[users]\n'
+    for user in userdata:
+        authzdata += '%s = %s\n' % (user, userdata[user][1])
+        passwddata += '%s = %s\n' % (user, userdata[user][0])
+    confdir.join('authz').write(authzdata)
+    confdir.join('passwd').write(passwddata)
+
+def serve_bg(repopath):
+    pidfile = py.path.local(repopath).join('pid')
+    port = 10000
+    e = None
+    while port < 10010:
+        cmd = 'svnserve -d -T --listen-port=%d --pid-file=%s -r %s' % (
+               port, pidfile, repopath)
+        print(cmd)
+        try:
+            py.process.cmdexec(cmd)
+        except py.process.cmdexec.Error:
+            e = sys.exc_info()[1]
+        else:
+            # XXX we assume here that the pid file gets written somewhere, I
+            # guess this should be relatively safe... (I hope, at least?)
+            counter = pid = 0
+            while counter < 10:
+                counter += 1
+                try:
+                    pid = pidfile.read()
+                except py.error.ENOENT:
+                    pass
+                if pid:
+                    break
+                time.sleep(0.2)
+            return port, int(pid)
+        port += 1
+    raise IOError('could not start svnserve: %s' % (e,))
+
+class TestSvnAuth(object):
+    def test_basic(self):
+        auth = SvnAuth('foo', 'bar')
+        assert auth.username == 'foo'
+        assert auth.password == 'bar'
+        assert str(auth)
+
+    def test_makecmdoptions_uname_pw_makestr(self):
+        auth = SvnAuth('foo', 'bar')
+        assert auth.makecmdoptions() == '--username="foo" --password="bar"'
+
+    def test_makecmdoptions_quote_escape(self):
+        auth = SvnAuth('fo"o', '"ba\'r"')
+        assert auth.makecmdoptions() == '--username="fo\\"o" --password="\\"ba\'r\\""'
+
+    def test_makecmdoptions_no_cache_auth(self):
+        auth = SvnAuth('foo', 'bar', cache_auth=False)
+        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
+                                         '--no-auth-cache')
+
+    def test_makecmdoptions_no_interactive(self):
+        auth = SvnAuth('foo', 'bar', interactive=False)
+        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
+                                         '--non-interactive')
+
+    def test_makecmdoptions_no_interactive_no_cache_auth(self):
+        auth = SvnAuth('foo', 'bar', cache_auth=False,
+                               interactive=False)
+        assert auth.makecmdoptions() == ('--username="foo" --password="bar" '
+                                         '--no-auth-cache --non-interactive')
+
+class svnwc_no_svn(py.path.svnwc):
+    def __new__(cls, *args, **kwargs):
+        self = super(svnwc_no_svn, cls).__new__(cls, *args, **kwargs)
+        self.commands = []
+        return self
+
+    def _svn(self, *args):
+        self.commands.append(args)
+
+class TestSvnWCAuth(object):
+    def setup_method(self, meth):
+        if not svnbin:
+            py.test.skip("svn binary required")
+        self.auth = SvnAuth('user', 'pass', cache_auth=False)
+
+    def test_checkout(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.checkout('url')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+    def test_commit(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.commit('msg')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+    def test_checkout_no_cache_auth(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.checkout('url')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+    def test_checkout_auth_from_constructor(self):
+        wc = svnwc_no_svn('foo', auth=self.auth)
+        wc.checkout('url')
+        assert wc.commands[0][-1] == ('--username="user" --password="pass" '
+                                      '--no-auth-cache')
+
+class svnurl_no_svn(py.path.svnurl):
+    cmdexec_output = 'test'
+    popen_output = 'test'
+    def __new__(cls, *args, **kwargs):
+        self = super(svnurl_no_svn, cls).__new__(cls, *args, **kwargs)
+        self.commands = []
+        return self
+
+    def _cmdexec(self, cmd):
+        self.commands.append(cmd)
+        return self.cmdexec_output
+
+    def _popen(self, cmd):
+        self.commands.append(cmd)
+        return self.popen_output
+
+class TestSvnURLAuth(object):
+    def setup_method(self, meth):
+        self.auth = SvnAuth('foo', 'bar')
+
+    def test_init(self):
+        u = svnurl_no_svn('http://foo.bar/svn')
+        assert u.auth is None
+
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        assert u.auth is self.auth
+
+    def test_new(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        new = u.new(basename='bar')
+        assert new.auth is self.auth
+        assert new.url == 'http://foo.bar/svn/bar'
+
+    def test_join(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        new = u.join('foo')
+        assert new.auth is self.auth
+        assert new.url == 'http://foo.bar/svn/foo'
+
+    def test_listdir(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        u.cmdexec_output = '''\
+   1717 johnny           1529 Nov 04 14:32 LICENSE.txt
+   1716 johnny           5352 Nov 04 14:28 README.txt
+'''
+        paths = u.listdir()
+        assert paths[0].auth is self.auth
+        assert paths[1].auth is self.auth
+        assert paths[0].basename == 'LICENSE.txt'
+
+    def test_info(self):
+        u = svnurl_no_svn('http://foo.bar/svn/LICENSE.txt', auth=self.auth)
+        def dirpath(self):
+            return self
+        u.cmdexec_output = '''\
+   1717 johnny           1529 Nov 04 14:32 LICENSE.txt
+   1716 johnny           5352 Nov 04 14:28 README.txt
+'''
+        org_dp = u.__class__.dirpath
+        u.__class__.dirpath = dirpath
+        try:
+            info = u.info()
+        finally:
+            u.dirpath = org_dp
+        assert info.size == 1529
+
+    def test_open(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        foo = u.join('foo')
+        foo.check = lambda *args, **kwargs: True
+        ret = foo.open()
+        assert ret == 'test'
+        assert '--username="foo" --password="bar"' in foo.commands[0]
+
+    def test_dirpath(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        parent = u.dirpath()
+        assert parent.auth is self.auth
+
+    def test_mkdir(self):
+        u = svnurl_no_svn('http://foo.bar/svn/qweqwe', auth=self.auth)
+        assert not u.commands
+        u.mkdir(msg='created dir foo')
+        assert u.commands
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_copy(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        u2 = svnurl_no_svn('http://foo.bar/svn2')
+        u.copy(u2, 'copied dir')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_rename(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        u.rename('http://foo.bar/svn/bar', 'moved foo to bar')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_remove(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        u.remove(msg='removing foo')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_export(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        target = py.path.local('/foo')
+        u.export(target)
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+    def test_log(self):
+        u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth)
+        u.popen_output = py.io.TextIO(py.builtin._totext('''\
+<?xml version="1.0"?>
+<log>
+<logentry revision="51381">
+<author>guido</author>
+<date>2008-02-11T12:12:18.476481Z</date>
+<msg>Creating branch to work on auth support for py.path.svn*.
+</msg>
+</logentry>
+</log>
+''', 'ascii'))
+        u.check = lambda *args, **kwargs: True
+        ret = u.log(10, 20, verbose=True)
+        assert '--username="foo" --password="bar"' in u.commands[0]
+        assert len(ret) == 1
+        assert int(ret[0].rev) == 51381
+        assert ret[0].author == 'guido'
+
+    def test_propget(self):
+        u = svnurl_no_svn('http://foo.bar/svn', auth=self.auth)
+        u.propget('foo')
+        assert '--username="foo" --password="bar"' in u.commands[0]
+
+def pytest_funcarg__setup(request):
+    return Setup(request)
+
+class Setup:
+    def __init__(self, request):
+        if not svnbin:
+            py.test.skip("svn binary required")
+        if not request.config.option.runslowtests:
+            py.test.skip('use --runslowtests to run these tests')
+
+        tmpdir = request.getfuncargvalue("tmpdir")
+        repodir = tmpdir.join("repo")
+        py.process.cmdexec('svnadmin create %s' % repodir)
+        if sys.platform == 'win32':
+            repodir = '/' + str(repodir).replace('\\', '/')
+        self.repo = py.path.svnurl("file://%s" % repodir)
+        if sys.platform == 'win32':
+            # remove trailing slash...
+            repodir = repodir[1:]
+        self.repopath = py.path.local(repodir)
+        self.temppath = tmpdir.mkdir("temppath")
+        self.auth = SvnAuth('johnny', 'foo', cache_auth=False,
+                                    interactive=False)
+        make_repo_auth(self.repopath, {'johnny': ('foo', 'rw')})
+        self.port, self.pid = serve_bg(self.repopath.dirpath())
+        # XXX caching is too global
+        py.path.svnurl._lsnorevcache._dict.clear()
+        request.addfinalizer(lambda: py.process.kill(self.pid))
+
+class TestSvnWCAuthFunctional:
+    def test_checkout_constructor_arg(self, setup):
+        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
+        wc.checkout(
+            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
+        assert wc.join('.svn').check()
+
+    def test_checkout_function_arg(self, setup):
+        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
+        wc.checkout(
+            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
+        assert wc.join('.svn').check()
+
+    def test_checkout_failing_non_interactive(self, setup):
+        auth = SvnAuth('johnny', 'bar', cache_auth=False,
+                               interactive=False)
+        wc = py.path.svnwc(setup.temppath, auth)
+        py.test.raises(Exception,
+           ("wc.checkout('svn://localhost:%(port)s/%(repopath)s')" %
+             setup.__dict__))
+
+    def test_log(self, setup):
+        wc = py.path.svnwc(setup.temppath, setup.auth)
+        wc.checkout(
+            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
+        foo = wc.ensure('foo.txt')
+        wc.commit('added foo.txt')
+        log = foo.log()
+        assert len(log) == 1
+        assert log[0].msg == 'added foo.txt'
+
+    def test_switch(self, setup):
+        import pytest
+        try:
+            import xdist
+            pytest.skip('#160: fails under xdist')
+        except ImportError:
+            pass
+        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
+        svnurl = 'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename)
+        wc.checkout(svnurl)
+        wc.ensure('foo', dir=True).ensure('foo.txt').write('foo')
+        wc.commit('added foo dir with foo.txt file')
+        wc.ensure('bar', dir=True)
+        wc.commit('added bar dir')
+        bar = wc.join('bar')
+        bar.switch(svnurl + '/foo')
+        assert bar.join('foo.txt')
+
+    def test_update(self, setup):
+        wc1 = py.path.svnwc(setup.temppath.ensure('wc1', dir=True),
+                            auth=setup.auth)
+        wc2 = py.path.svnwc(setup.temppath.ensure('wc2', dir=True),
+                            auth=setup.auth)
+        wc1.checkout(
+            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
+        wc2.checkout(
+            'svn://localhost:%s/%s' % (setup.port, setup.repopath.basename))
+        wc1.ensure('foo', dir=True)
+        wc1.commit('added foo dir')
+        wc2.update()
+        assert wc2.join('foo').check()
+
+        auth = SvnAuth('unknown', 'unknown', interactive=False)
+        wc2.auth = auth
+        py.test.raises(Exception, 'wc2.update()')
+
+    def test_lock_unlock_status(self, setup):
+        port = setup.port
+        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
+        wc.checkout(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename,))
+        wc.ensure('foo', file=True)
+        wc.commit('added foo file')
+        foo = wc.join('foo')
+        foo.lock()
+        status = foo.status()
+        assert status.locked
+        foo.unlock()
+        status = foo.status()
+        assert not status.locked
+
+        auth = SvnAuth('unknown', 'unknown', interactive=False)
+        foo.auth = auth
+        py.test.raises(Exception, 'foo.lock()')
+        py.test.raises(Exception, 'foo.unlock()')
+
+    def test_diff(self, setup):
+        port = setup.port
+        wc = py.path.svnwc(setup.temppath, auth=setup.auth)
+        wc.checkout(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename,))
+        wc.ensure('foo', file=True)
+        wc.commit('added foo file')
+        wc.update()
+        rev = int(wc.status().rev)
+        foo = wc.join('foo')
+        foo.write('bar')
+        diff = foo.diff()
+        assert '\n+bar\n' in diff
+        foo.commit('added some content')
+        diff = foo.diff()
+        assert not diff
+        diff = foo.diff(rev=rev)
+        assert '\n+bar\n' in diff
+
+        auth = SvnAuth('unknown', 'unknown', interactive=False)
+        foo.auth = auth
+        py.test.raises(Exception, 'foo.diff(rev=rev)')
+
+class TestSvnURLAuthFunctional:
+    def test_listdir(self, setup):
+        port = setup.port
+        u = py.path.svnurl(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
+            auth=setup.auth)
+        u.ensure('foo')
+        paths = u.listdir()
+        assert len(paths) == 1
+        assert paths[0].auth is setup.auth
+
+        auth = SvnAuth('foo', 'bar', interactive=False)
+        u = py.path.svnurl(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
+            auth=auth)
+        py.test.raises(Exception, 'u.listdir()')
+
+    def test_copy(self, setup):
+        port = setup.port
+        u = py.path.svnurl(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
+            auth=setup.auth)
+        foo = u.mkdir('foo')
+        assert foo.check()
+        bar = u.join('bar')
+        foo.copy(bar)
+        assert bar.check()
+        assert bar.auth is setup.auth
+
+        auth = SvnAuth('foo', 'bar', interactive=False)
+        u = py.path.svnurl(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
+            auth=auth)
+        foo = u.join('foo')
+        bar = u.join('bar')
+        py.test.raises(Exception, 'foo.copy(bar)')
+
+    def test_write_read(self, setup):
+        port = setup.port
+        u = py.path.svnurl(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
+            auth=setup.auth)
+        foo = u.ensure('foo')
+        fp = foo.open()
+        try:
+            data = fp.read()
+        finally:
+            fp.close()
+        assert data == ''
+
+        auth = SvnAuth('foo', 'bar', interactive=False)
+        u = py.path.svnurl(
+            'svn://localhost:%s/%s' % (port, setup.repopath.basename),
+            auth=auth)
+        foo = u.join('foo')
+        py.test.raises(Exception, 'foo.open()')
+
+    # XXX rinse, repeat... :|
diff --git a/tools/py/testing/path/test_svnurl.py b/tools/third_party/py/testing/path/test_svnurl.py
similarity index 100%
rename from tools/py/testing/path/test_svnurl.py
rename to tools/third_party/py/testing/path/test_svnurl.py
diff --git a/tools/third_party/py/testing/path/test_svnwc.py b/tools/third_party/py/testing/path/test_svnwc.py
new file mode 100644
index 0000000..c643d99
--- /dev/null
+++ b/tools/third_party/py/testing/path/test_svnwc.py
@@ -0,0 +1,557 @@
+import py
+import os, sys
+import pytest
+from py._path.svnwc import InfoSvnWCCommand, XMLWCStatus, parse_wcinfotime
+from py._path import svnwc as svncommon
+from svntestbase import CommonSvnTests
+
+
+pytestmark = pytest.mark.xfail(sys.platform.startswith('win'),
+                               reason='#161 all tests in this file are failing on Windows',
+                               run=False)
+
+
+def test_make_repo(path1, tmpdir):
+    repo = tmpdir.join("repo")
+    py.process.cmdexec('svnadmin create %s' % repo)
+    if sys.platform == 'win32':
+        repo = '/' + str(repo).replace('\\', '/')
+    repo = py.path.svnurl("file://%s" % repo)
+    wc = py.path.svnwc(tmpdir.join("wc"))
+    wc.checkout(repo)
+    assert wc.rev == 0
+    assert len(wc.listdir()) == 0
+    p = wc.join("a_file")
+    p.write("test file")
+    p.add()
+    rev = wc.commit("some test")
+    assert p.info().rev == 1
+    assert rev == 1
+    rev = wc.commit()
+    assert rev is None
+
+def pytest_funcarg__path1(request):
+    repo, repourl, wc = request.getfuncargvalue("repowc1")
+    return wc
+
+class TestWCSvnCommandPath(CommonSvnTests):
+    def test_status_attributes_simple(self, path1):
+        def assert_nochange(p):
+            s = p.status()
+            assert not s.modified
+            assert not s.prop_modified
+            assert not s.added
+            assert not s.deleted
+            assert not s.replaced
+
+        dpath = path1.join('sampledir')
+        assert_nochange(path1.join('sampledir'))
+        assert_nochange(path1.join('samplefile'))
+
+    def test_status_added(self, path1):
+        nf = path1.join('newfile')
+        nf.write('hello')
+        nf.add()
+        try:
+            s = nf.status()
+            assert s.added
+            assert not s.modified
+            assert not s.prop_modified
+            assert not s.replaced
+        finally:
+            nf.revert()
+
+    def test_status_change(self, path1):
+        nf = path1.join('samplefile')
+        try:
+            nf.write(nf.read() + 'change')
+            s = nf.status()
+            assert not s.added
+            assert s.modified
+            assert not s.prop_modified
+            assert not s.replaced
+        finally:
+            nf.revert()
+
+    def test_status_added_ondirectory(self, path1):
+        sampledir = path1.join('sampledir')
+        try:
+            t2 = sampledir.mkdir('t2')
+            t1 = t2.join('t1')
+            t1.write('test')
+            t1.add()
+            s = sampledir.status(rec=1)
+            # Comparing just the file names, because paths are unpredictable
+            # on Windows. (long vs. 8.3 paths)
+            assert t1.basename in [item.basename for item in s.added]
+            assert t2.basename in [item.basename for item in s.added]
+        finally:
+            t2.revert(rec=1)
+            t2.localpath.remove(rec=1)
+
+    def test_status_unknown(self, path1):
+        t1 = path1.join('un1')
+        try:
+            t1.write('test')
+            s = path1.status()
+            # Comparing just the file names, because paths are unpredictable
+            # on Windows. (long vs. 8.3 paths)
+            assert t1.basename in [item.basename for item in s.unknown]
+        finally:
+            t1.localpath.remove()
+
+    def test_status_unchanged(self, path1):
+        r = path1
+        s = path1.status(rec=1)
+        # Comparing just the file names, because paths are unpredictable
+        # on Windows. (long vs. 8.3 paths)
+        assert r.join('samplefile').basename in [item.basename
+                                                    for item in s.unchanged]
+        assert r.join('sampledir').basename in [item.basename
+                                                    for item in s.unchanged]
+        assert r.join('sampledir/otherfile').basename in [item.basename
+                                                    for item in s.unchanged]
+
+    def test_status_update(self, path1):
+        # not a mark because the global "pytestmark" will end up overwriting a mark here
+        pytest.xfail("svn-1.7 has buggy 'status --xml' output")
+        r = path1
+        try:
+            r.update(rev=1)
+            s = r.status(updates=1, rec=1)
+            # Comparing just the file names, because paths are unpredictable
+            # on Windows. (long vs. 8.3 paths)
+            import pprint
+            pprint.pprint(s.allpath())
+            assert r.join('anotherfile').basename in [item.basename for
+                                                    item in s.update_available]
+            #assert len(s.update_available) == 1
+        finally:
+            r.update()
+
+    def test_status_replaced(self, path1):
+        p = path1.join("samplefile")
+        p.remove()
+        p.ensure(dir=0)
+        try:
+            s = path1.status()
+            assert p.basename in [item.basename for item in s.replaced]
+        finally:
+            path1.revert(rec=1)
+
+    def test_status_ignored(self, path1):
+        try:
+            d = path1.join('sampledir')
+            p = py.path.local(d).join('ignoredfile')
+            p.ensure(file=True)
+            s = d.status()
+            assert [x.basename for x in s.unknown] == ['ignoredfile']
+            assert [x.basename for x in s.ignored] == []
+            d.propset('svn:ignore', 'ignoredfile')
+            s = d.status()
+            assert [x.basename for x in s.unknown] == []
+            assert [x.basename for x in s.ignored] == ['ignoredfile']
+        finally:
+            path1.revert(rec=1)
+
+    def test_status_conflict(self, path1, tmpdir):
+        wc = path1
+        wccopy = py.path.svnwc(tmpdir.join("conflict_copy"))
+        wccopy.checkout(wc.url)
+        p = wc.ensure('conflictsamplefile', file=1)
+        p.write('foo')
+        wc.commit('added conflictsamplefile')
+        wccopy.update()
+        assert wccopy.join('conflictsamplefile').check()
+        p.write('bar')
+        wc.commit('wrote some data')
+        wccopy.join('conflictsamplefile').write('baz')
+        wccopy.update(interactive=False)
+        s = wccopy.status()
+        assert [x.basename for x in s.conflict] == ['conflictsamplefile']
+
+    def test_status_external(self, path1, repowc2):
+        otherrepo, otherrepourl, otherwc = repowc2
+        d = path1.ensure('sampledir', dir=1)
+        try:
+            d.update()
+            d.propset('svn:externals', 'otherwc %s' % (otherwc.url,))
+            d.update()
+            s = d.status()
+            assert [x.basename for x in s.external] == ['otherwc']
+            assert 'otherwc' not in [x.basename for x in s.unchanged]
+            s = d.status(rec=1)
+            assert [x.basename for x in s.external] == ['otherwc']
+            assert 'otherwc' in [x.basename for x in s.unchanged]
+        finally:
+            path1.revert(rec=1)
+
+    def test_status_deleted(self, path1):
+        d = path1.ensure('sampledir', dir=1)
+        d.remove()
+        d.ensure(dir=1)
+        path1.commit()
+        d.ensure('deletefile', dir=0)
+        d.commit()
+        s = d.status()
+        assert 'deletefile' in [x.basename for x in s.unchanged]
+        assert not s.deleted
+        p = d.join('deletefile')
+        p.remove()
+        s = d.status()
+        assert 'deletefile' not in s.unchanged
+        assert [x.basename for x in s.deleted] == ['deletefile']
+
+    def test_status_noauthor(self, path1):
+        # testing for XML without author - this used to raise an exception
+        xml = '''\
+        <entry path="/tmp/pytest-23/wc">
+        <wc-status item="normal" props="none" revision="0">
+        <commit revision="0">
+        <date>2008-08-19T16:50:53.400198Z</date>
+        </commit>
+        </wc-status>
+        </entry>
+        '''
+        XMLWCStatus.fromstring(xml, path1)
+
+    def test_status_wrong_xml(self, path1):
+        # testing for XML without author - this used to raise an exception
+        xml = '<entry path="/home/jean/zope/venv/projectdb/parts/development-products/DataGridField">\n<wc-status item="incomplete" props="none" revision="784">\n</wc-status>\n</entry>'
+        st = XMLWCStatus.fromstring(xml, path1)
+        assert len(st.incomplete) == 1
+
+    def test_diff(self, path1):
+        p = path1 / 'anotherfile'
+        out = p.diff(rev=2)
+        assert out.find('hello') != -1
+
+    def test_blame(self, path1):
+        p = path1.join('samplepickle')
+        lines = p.blame()
+        assert sum([l[0] for l in lines]) == len(lines)
+        for l1, l2 in zip(p.readlines(), [l[2] for l in lines]):
+            assert l1 == l2
+        assert [l[1] for l in lines] == ['hpk'] * len(lines)
+        p = path1.join('samplefile')
+        lines = p.blame()
+        assert sum([l[0] for l in lines]) == len(lines)
+        for l1, l2 in zip(p.readlines(), [l[2] for l in lines]):
+            assert l1 == l2
+        assert [l[1] for l in lines] == ['hpk'] * len(lines)
+
+    def test_join_abs(self, path1):
+        s = str(path1.localpath)
+        n = path1.join(s, abs=1)
+        assert path1 == n
+
+    def test_join_abs2(self, path1):
+        assert path1.join('samplefile', abs=1) == path1.join('samplefile')
+
+    def test_str_gives_localpath(self, path1):
+        assert str(path1) == str(path1.localpath)
+
+    def test_versioned(self, path1):
+        assert path1.check(versioned=1)
+        # TODO: Why does my copy of svn think .svn is versioned?
+        #assert path1.join('.svn').check(versioned=0)
+        assert path1.join('samplefile').check(versioned=1)
+        assert not path1.join('notexisting').check(versioned=1)
+        notexisting = path1.join('hello').localpath
+        try:
+            notexisting.write("")
+            assert path1.join('hello').check(versioned=0)
+        finally:
+            notexisting.remove()
+
+    def test_listdir_versioned(self, path1):
+        assert path1.check(versioned=1)
+        p = path1.localpath.ensure("not_a_versioned_file")
+        l = [x.localpath
+                for x in path1.listdir(lambda x: x.check(versioned=True))]
+        assert p not in l
+
+    def test_nonversioned_remove(self, path1):
+        assert path1.check(versioned=1)
+        somefile = path1.join('nonversioned/somefile')
+        nonwc = py.path.local(somefile)
+        nonwc.ensure()
+        assert somefile.check()
+        assert not somefile.check(versioned=True)
+        somefile.remove() # this used to fail because it tried to 'svn rm'
+
+    def test_properties(self, path1):
+        try:
+            path1.propset('gaga', 'this')
+            assert path1.propget('gaga') == 'this'
+            # Comparing just the file names, because paths are unpredictable
+            # on Windows. (long vs. 8.3 paths)
+            assert path1.basename in [item.basename for item in
+                                        path1.status().prop_modified]
+            assert 'gaga' in path1.proplist()
+            assert path1.proplist()['gaga'] == 'this'
+
+        finally:
+            path1.propdel('gaga')
+
+    def test_proplist_recursive(self, path1):
+        s = path1.join('samplefile')
+        s.propset('gugu', 'that')
+        try:
+            p = path1.proplist(rec=1)
+            # Comparing just the file names, because paths are unpredictable
+            # on Windows. (long vs. 8.3 paths)
+            assert (path1 / 'samplefile').basename in [item.basename
+                                                                for item in p]
+        finally:
+            s.propdel('gugu')
+
+    def test_long_properties(self, path1):
+        value = """
+        vadm:posix : root root 0100755
+        Properties on 'chroot/dns/var/bind/db.net.xots':
+                """
+        try:
+            path1.propset('gaga', value)
+            backvalue = path1.propget('gaga')
+            assert backvalue == value
+            #assert len(backvalue.split('\n')) == 1
+        finally:
+            path1.propdel('gaga')
+
+
+    def test_ensure(self, path1):
+        newpath = path1.ensure('a', 'b', 'c')
+        try:
+            assert newpath.check(exists=1, versioned=1)
+            newpath.write("hello")
+            newpath.ensure()
+            assert newpath.read() == "hello"
+        finally:
+            path1.join('a').remove(force=1)
+
+    def test_not_versioned(self, path1):
+        p = path1.localpath.mkdir('whatever')
+        f = path1.localpath.ensure('testcreatedfile')
+        try:
+            assert path1.join('whatever').check(versioned=0)
+            assert path1.join('testcreatedfile').check(versioned=0)
+            assert not path1.join('testcreatedfile').check(versioned=1)
+        finally:
+            p.remove(rec=1)
+            f.remove()
+
+    def test_lock_unlock(self, path1):
+        root = path1
+        somefile = root.join('somefile')
+        somefile.ensure(file=True)
+        # not yet added to repo
+        py.test.raises(Exception, 'somefile.lock()')
+        somefile.write('foo')
+        somefile.commit('test')
+        assert somefile.check(versioned=True)
+        somefile.lock()
+        try:
+            locked = root.status().locked
+            assert len(locked) == 1
+            assert locked[0].basename == somefile.basename
+            assert locked[0].dirpath().basename == somefile.dirpath().basename
+            #assert somefile.locked()
+            py.test.raises(Exception, 'somefile.lock()')
+        finally:
+            somefile.unlock()
+        #assert not somefile.locked()
+        locked = root.status().locked
+        assert locked == []
+        py.test.raises(Exception, 'somefile,unlock()')
+        somefile.remove()
+
+    def test_commit_nonrecursive(self, path1):
+        somedir = path1.join('sampledir')
+        somedir.mkdir("subsubdir")
+        somedir.propset('foo', 'bar')
+        status = somedir.status()
+        assert len(status.prop_modified) == 1
+        assert len(status.added) == 1
+
+        somedir.commit('non-recursive commit', rec=0)
+        status = somedir.status()
+        assert len(status.prop_modified) == 0
+        assert len(status.added) == 1
+
+        somedir.commit('recursive commit')
+        status = somedir.status()
+        assert len(status.prop_modified) == 0
+        assert len(status.added) == 0
+
+    def test_commit_return_value(self, path1):
+        testfile = path1.join('test.txt').ensure(file=True)
+        testfile.write('test')
+        rev = path1.commit('testing')
+        assert type(rev) == int
+
+        anotherfile = path1.join('another.txt').ensure(file=True)
+        anotherfile.write('test')
+        rev2 = path1.commit('testing more')
+        assert type(rev2) == int
+        assert rev2 == rev + 1
+
+    #def test_log(self, path1):
+    #   l = path1.log()
+    #   assert len(l) == 3  # might need to be upped if more tests are added
+
+class XTestWCSvnCommandPathSpecial:
+
+    rooturl = 'http://codespeak.net/svn/py.path/trunk/dist/py.path/test/data'
+    #def test_update_none_rev(self, path1):
+    #    path = tmpdir.join('checkouttest')
+    #    wcpath = newpath(xsvnwc=str(path), url=path1url)
+    #    try:
+    #        wcpath.checkout(rev=2100)
+    #        wcpath.update()
+    #        assert wcpath.info().rev > 2100
+    #    finally:
+    #        wcpath.localpath.remove(rec=1)
+
+def test_parse_wcinfotime():
+    assert (parse_wcinfotime('2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)') ==
+            1149021926)
+    assert (parse_wcinfotime('2003-10-27 20:43:14 +0100 (Mon, 27 Oct 2003)') ==
+            1067287394)
+
+class TestInfoSvnWCCommand:
+
+    def test_svn_1_2(self, path1):
+        output = """
+        Path: test_svnwc.py
+        Name: test_svnwc.py
+        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
+        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
+        Revision: 28137
+        Node Kind: file
+        Schedule: normal
+        Last Changed Author: jan
+        Last Changed Rev: 27939
+        Last Changed Date: 2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)
+        Text Last Updated: 2006-06-01 00:42:53 +0200 (Thu, 01 Jun 2006)
+        Properties Last Updated: 2006-05-23 11:54:59 +0200 (Tue, 23 May 2006)
+        Checksum: 357e44880e5d80157cc5fbc3ce9822e3
+        """
+        path = py.path.local(__file__).dirpath().chdir()
+        try:
+            info = InfoSvnWCCommand(output)
+        finally:
+            path.chdir()
+        assert info.last_author == 'jan'
+        assert info.kind == 'file'
+        assert info.mtime == 1149021926.0
+        assert info.url == 'http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py'
+        assert info.time == 1149021926000000.0
+        assert info.rev == 28137
+
+
+    def test_svn_1_3(self, path1):
+        output = """
+        Path: test_svnwc.py
+        Name: test_svnwc.py
+        URL: http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py
+        Repository Root: http://codespeak.net/svn
+        Repository UUID: fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
+        Revision: 28124
+        Node Kind: file
+        Schedule: normal
+        Last Changed Author: jan
+        Last Changed Rev: 27939
+        Last Changed Date: 2006-05-30 20:45:26 +0200 (Tue, 30 May 2006)
+        Text Last Updated: 2006-06-02 23:46:11 +0200 (Fri, 02 Jun 2006)
+        Properties Last Updated: 2006-06-02 23:45:28 +0200 (Fri, 02 Jun 2006)
+        Checksum: 357e44880e5d80157cc5fbc3ce9822e3
+        """
+        path = py.path.local(__file__).dirpath().chdir()
+        try:
+            info = InfoSvnWCCommand(output)
+        finally:
+            path.chdir()
+        assert info.last_author == 'jan'
+        assert info.kind == 'file'
+        assert info.mtime == 1149021926.0
+        assert info.url == 'http://codespeak.net/svn/py/dist/py/path/svn/wccommand.py'
+        assert info.rev == 28124
+        assert info.time == 1149021926000000.0
+
+
+def test_characters_at():
+    py.test.raises(ValueError, "py.path.svnwc('/tmp/@@@:')")
+
+def test_characters_tilde():
+    py.path.svnwc('/tmp/test~')
+
+
+class TestRepo:
+    def test_trailing_slash_is_stripped(self, path1):
+        # XXX we need to test more normalizing properties
+        url = path1.join("/")
+        assert path1 == url
+
+    #def test_different_revs_compare_unequal(self, path1):
+    #    newpath = path1.new(rev=1199)
+    #    assert newpath != path1
+
+    def test_exists_svn_root(self, path1):
+        assert path1.check()
+
+    #def test_not_exists_rev(self, path1):
+    #    url = path1.__class__(path1url, rev=500)
+    #    assert url.check(exists=0)
+
+    #def test_nonexisting_listdir_rev(self, path1):
+    #    url = path1.__class__(path1url, rev=500)
+    #    raises(py.error.ENOENT, url.listdir)
+
+    #def test_newrev(self, path1):
+    #    url = path1.new(rev=None)
+    #    assert url.rev == None
+    #    assert url.strpath == path1.strpath
+    #    url = path1.new(rev=10)
+    #    assert url.rev == 10
+
+    #def test_info_rev(self, path1):
+    #    url = path1.__class__(path1url, rev=1155)
+    #    url = url.join("samplefile")
+    #    res = url.info()
+    #    assert res.size > len("samplefile") and res.created_rev == 1155
+
+    # the following tests are easier if we have a path class
+    def test_repocache_simple(self, path1):
+        repocache = svncommon.RepoCache()
+        repocache.put(path1.strpath, 42)
+        url, rev = repocache.get(path1.join('test').strpath)
+        assert rev == 42
+        assert url == path1.strpath
+
+    def test_repocache_notimeout(self, path1):
+        repocache = svncommon.RepoCache()
+        repocache.timeout = 0
+        repocache.put(path1.strpath, path1.rev)
+        url, rev = repocache.get(path1.strpath)
+        assert rev == -1
+        assert url == path1.strpath
+
+    def test_repocache_outdated(self, path1):
+        repocache = svncommon.RepoCache()
+        repocache.put(path1.strpath, 42, timestamp=0)
+        url, rev = repocache.get(path1.join('test').strpath)
+        assert rev == -1
+        assert url == path1.strpath
+
+    def _test_getreporev(self):
+        """ this test runs so slow it's usually disabled """
+        old = svncommon.repositories.repos
+        try:
+            _repocache.clear()
+            root = path1.new(rev=-1)
+            url, rev = cache.repocache.get(root.strpath)
+            assert rev>=0
+            assert url == svnrepourl
+        finally:
+            repositories.repos = old
diff --git a/tools/py/testing/process/__init__.py b/tools/third_party/py/testing/process/__init__.py
similarity index 100%
rename from tools/py/testing/process/__init__.py
rename to tools/third_party/py/testing/process/__init__.py
diff --git a/tools/third_party/py/testing/process/test_cmdexec.py b/tools/third_party/py/testing/process/test_cmdexec.py
new file mode 100644
index 0000000..98463d9
--- /dev/null
+++ b/tools/third_party/py/testing/process/test_cmdexec.py
@@ -0,0 +1,41 @@
+import py
+from py.process import cmdexec
+
+def exvalue():
+    import sys
+    return sys.exc_info()[1]
+
+
+class Test_exec_cmd:
+    def test_simple(self):
+        out = cmdexec('echo hallo')
+        assert out.strip() == 'hallo'
+        assert py.builtin._istext(out)
+
+    def test_simple_newline(self):
+        import sys
+        out = cmdexec(r"""%s -c "print ('hello')" """ % sys.executable)
+        assert out == 'hello\n'
+        assert py.builtin._istext(out)
+
+    def test_simple_error(self):
+        py.test.raises(cmdexec.Error, cmdexec, 'exit 1')
+
+    def test_simple_error_exact_status(self):
+        try:
+            cmdexec('exit 1')
+        except cmdexec.Error:
+            e = exvalue()
+            assert e.status == 1
+            assert py.builtin._istext(e.out)
+            assert py.builtin._istext(e.err)
+
+    def test_err(self):
+        try:
+            cmdexec('echoqweqwe123 hallo')
+            raise AssertionError("command succeeded but shouldn't")
+        except cmdexec.Error:
+            e = exvalue()
+            assert hasattr(e, 'err')
+            assert hasattr(e, 'out')
+            assert e.err or e.out
diff --git a/tools/third_party/py/testing/process/test_forkedfunc.py b/tools/third_party/py/testing/process/test_forkedfunc.py
new file mode 100644
index 0000000..ae0d9ab
--- /dev/null
+++ b/tools/third_party/py/testing/process/test_forkedfunc.py
@@ -0,0 +1,173 @@
+import pytest
+import py, sys, os
+
+pytestmark = py.test.mark.skipif("not hasattr(os, 'fork')")
+
+
+def test_waitfinish_removes_tempdir():
+    ff = py.process.ForkedFunc(boxf1)
+    assert ff.tempdir.check()
+    ff.waitfinish()
+    assert not ff.tempdir.check()
+
+def test_tempdir_gets_gc_collected(monkeypatch):
+    monkeypatch.setattr(os, 'fork', lambda: os.getpid())
+    ff = py.process.ForkedFunc(boxf1)
+    assert ff.tempdir.check()
+    ff.__del__()
+    assert not ff.tempdir.check()
+
+def test_basic_forkedfunc():
+    result = py.process.ForkedFunc(boxf1).waitfinish()
+    assert result.out == "some out\n"
+    assert result.err == "some err\n"
+    assert result.exitstatus == 0
+    assert result.signal == 0
+    assert result.retval == 1
+
+def test_exitstatus():
+    def func():
+        os._exit(4)
+    result = py.process.ForkedFunc(func).waitfinish()
+    assert result.exitstatus == 4
+    assert result.signal == 0
+    assert not result.out
+    assert not result.err
+
+def test_execption_in_func():
+    def fun():
+        raise ValueError(42)
+    ff = py.process.ForkedFunc(fun)
+    result = ff.waitfinish()
+    assert result.exitstatus == ff.EXITSTATUS_EXCEPTION
+    assert result.err.find("ValueError: 42") != -1
+    assert result.signal == 0
+    assert not result.retval
+
+def test_forkedfunc_on_fds():
+    result = py.process.ForkedFunc(boxf2).waitfinish()
+    assert result.out == "someout"
+    assert result.err == "someerr"
+    assert result.exitstatus == 0
+    assert result.signal == 0
+    assert result.retval == 2
+
+def test_forkedfunc_on_fds_output():
+    result = py.process.ForkedFunc(boxf3).waitfinish()
+    assert result.signal == 11
+    assert result.out == "s"
+
+
+def test_forkedfunc_on_stdout():
+    def boxf3():
+        import sys
+        sys.stdout.write("hello\n")
+        os.kill(os.getpid(), 11)
+    result = py.process.ForkedFunc(boxf3).waitfinish()
+    assert result.signal == 11
+    assert result.out == "hello\n"
+
+def test_forkedfunc_signal():
+    result = py.process.ForkedFunc(boxseg).waitfinish()
+    assert result.retval is None
+    assert result.signal == 11
+
+def test_forkedfunc_huge_data():
+    result = py.process.ForkedFunc(boxhuge).waitfinish()
+    assert result.out
+    assert result.exitstatus == 0
+    assert result.signal == 0
+    assert result.retval == 3
+
+def test_box_seq():
+    # we run many boxes with huge data, just one after another
+    for i in range(50):
+        result = py.process.ForkedFunc(boxhuge).waitfinish()
+        assert result.out
+        assert result.exitstatus == 0
+        assert result.signal == 0
+        assert result.retval == 3
+
+def test_box_in_a_box():
+    def boxfun():
+        result = py.process.ForkedFunc(boxf2).waitfinish()
+        print (result.out)
+        sys.stderr.write(result.err + "\n")
+        return result.retval
+
+    result = py.process.ForkedFunc(boxfun).waitfinish()
+    assert result.out == "someout\n"
+    assert result.err == "someerr\n"
+    assert result.exitstatus == 0
+    assert result.signal == 0
+    assert result.retval == 2
+
+def test_kill_func_forked():
+    class A:
+        pass
+    info = A()
+    import time
+
+    def box_fun():
+        time.sleep(10) # we don't want to last forever here
+
+    ff = py.process.ForkedFunc(box_fun)
+    os.kill(ff.pid, 15)
+    result = ff.waitfinish()
+    assert result.signal == 15
+
+
+def test_hooks(monkeypatch):
+    def _boxed():
+        return 1
+
+    def _on_start():
+        sys.stdout.write("some out\n")
+        sys.stdout.flush()
+
+    def _on_exit():
+        sys.stderr.write("some err\n")
+        sys.stderr.flush()
+
+    result = py.process.ForkedFunc(_boxed, child_on_start=_on_start,
+                                   child_on_exit=_on_exit).waitfinish()
+    assert result.out == "some out\n"
+    assert result.err == "some err\n"
+    assert result.exitstatus == 0
+    assert result.signal == 0
+    assert result.retval == 1
+
+
+# ======================================================================
+# examples
+# ======================================================================
+#
+
+def boxf1():
+    sys.stdout.write("some out\n")
+    sys.stderr.write("some err\n")
+    return 1
+
+def boxf2():
+    os.write(1, "someout".encode('ascii'))
+    os.write(2, "someerr".encode('ascii'))
+    return 2
+
+def boxf3():
+    os.write(1, "s".encode('ascii'))
+    os.kill(os.getpid(), 11)
+
+def boxseg():
+    os.kill(os.getpid(), 11)
+
+def boxhuge():
+    s = " ".encode('ascii')
+    os.write(1, s * 10000)
+    os.write(2, s * 10000)
+    os.write(1, s * 10000)
+
+    os.write(1, s * 10000)
+    os.write(2, s * 10000)
+    os.write(2, s * 10000)
+    os.write(1, s * 10000)
+    return 3
diff --git a/tools/third_party/py/testing/process/test_killproc.py b/tools/third_party/py/testing/process/test_killproc.py
new file mode 100644
index 0000000..b0d6e2f
--- /dev/null
+++ b/tools/third_party/py/testing/process/test_killproc.py
@@ -0,0 +1,18 @@
+import pytest
+import sys
+import py
+
+
+@pytest.mark.skipif("sys.platform.startswith('java')")
+def test_kill(tmpdir):
+    subprocess = pytest.importorskip("subprocess")
+    t = tmpdir.join("t.py")
+    t.write("import time ; time.sleep(100)")
+    proc = subprocess.Popen([sys.executable, str(t)])
+    assert proc.poll() is None  # no return value yet
+    py.process.kill(proc.pid)
+    ret = proc.wait()
+    if sys.platform == "win32" and ret == 0:
+        pytest.skip("XXX on win32, subprocess.Popen().wait() on a killed "
+                    "process does not yield return value != 0")
+    assert ret != 0
diff --git a/tools/py/testing/root/__init__.py b/tools/third_party/py/testing/root/__init__.py
similarity index 100%
rename from tools/py/testing/root/__init__.py
rename to tools/third_party/py/testing/root/__init__.py
diff --git a/tools/py/testing/root/test_builtin.py b/tools/third_party/py/testing/root/test_builtin.py
similarity index 100%
rename from tools/py/testing/root/test_builtin.py
rename to tools/third_party/py/testing/root/test_builtin.py
diff --git a/tools/third_party/py/testing/root/test_error.py b/tools/third_party/py/testing/root/test_error.py
new file mode 100644
index 0000000..a1185f3
--- /dev/null
+++ b/tools/third_party/py/testing/root/test_error.py
@@ -0,0 +1,68 @@
+
+import py
+
+import errno
+
+
+def test_error_classes():
+    for name in errno.errorcode.values():
+        x = getattr(py.error, name)
+        assert issubclass(x, py.error.Error)
+        assert issubclass(x, EnvironmentError)
+
+
+def test_has_name():
+    assert py.error.__name__ == 'py.error'
+
+
+def test_picklability_issue1():
+    import pickle
+    e1 = py.error.ENOENT()
+    s = pickle.dumps(e1)
+    e2 = pickle.loads(s)
+    assert isinstance(e2, py.error.ENOENT)
+
+
+def test_unknown_error():
+    num = 3999
+    cls = py.error._geterrnoclass(num)
+    assert cls.__name__ == 'UnknownErrno%d' % (num,)
+    assert issubclass(cls, py.error.Error)
+    assert issubclass(cls, EnvironmentError)
+    cls2 = py.error._geterrnoclass(num)
+    assert cls is cls2
+
+
+def test_error_conversion_ENOTDIR(testdir):
+    p = testdir.makepyfile("")
+    excinfo = py.test.raises(py.error.Error, py.error.checked_call, p.listdir)
+    assert isinstance(excinfo.value, EnvironmentError)
+    assert isinstance(excinfo.value, py.error.Error)
+    assert "ENOTDIR" in repr(excinfo.value)
+
+
+def test_checked_call_supports_kwargs(tmpdir):
+    import tempfile
+    py.error.checked_call(tempfile.mkdtemp, dir=str(tmpdir))
+
+
+try:
+    import unittest
+    unittest.TestCase.assertWarns
+except (ImportError, AttributeError):
+    pass  # required interface not available
+else:
+    import sys
+    import warnings
+
+    class Case(unittest.TestCase):
+        def test_assertWarns(self):
+            # Clear everything "py.*" from sys.modules and re-import py
+            # as a fresh start
+            for mod in tuple(sys.modules.keys()):
+                if mod and (mod == 'py' or mod.startswith('py.')):
+                    del sys.modules[mod]
+            import py
+
+            with self.assertWarns(UserWarning):
+                warnings.warn('this should work')
diff --git a/tools/third_party/py/testing/root/test_py_imports.py b/tools/third_party/py/testing/root/test_py_imports.py
new file mode 100644
index 0000000..31fe6ea
--- /dev/null
+++ b/tools/third_party/py/testing/root/test_py_imports.py
@@ -0,0 +1,71 @@
+import py
+import sys
+
+
+@py.test.mark.parametrize('name', [x for x in dir(py) if x[0] != '_'])
+def test_dir(name):
+    obj = getattr(py, name)
+    if hasattr(obj, '__map__'):  # isinstance(obj, Module):
+        keys = dir(obj)
+        assert len(keys) > 0
+        print (obj.__map__)
+        for name in list(obj.__map__):
+            assert hasattr(obj, name), (obj, name)
+
+
+def test_virtual_module_identity():
+    from py import path as path1
+    from py import path as path2
+    assert path1 is path2
+    from py.path import local as local1
+    from py.path import local as local2
+    assert local1 is local2
+
+
+def test_importall():
+    base = py._pydir
+    nodirs = [
+    ]
+    if sys.version_info >= (3, 0):
+        nodirs.append(base.join('_code', '_assertionold.py'))
+    else:
+        nodirs.append(base.join('_code', '_assertionnew.py'))
+
+    def recurse(p):
+        return p.check(dotfile=0) and p.basename != "attic"
+
+    for p in base.visit('*.py', recurse):
+        if p.basename == '__init__.py':
+            continue
+        relpath = p.new(ext='').relto(base)
+        if base.sep in relpath:  # not py/*.py itself
+            for x in nodirs:
+                if p == x or p.relto(x):
+                    break
+            else:
+                relpath = relpath.replace(base.sep, '.')
+                modpath = 'py.%s' % relpath
+                try:
+                    check_import(modpath)
+                except py.test.skip.Exception:
+                    pass
+
+
+def check_import(modpath):
+    py.builtin.print_("checking import", modpath)
+    assert __import__(modpath)
+
+
+def test_star_import():
+    exec("from py import *")
+
+
+def test_all_resolves():
+    seen = py.builtin.set([py])
+    lastlength = None
+    while len(seen) != lastlength:
+        lastlength = len(seen)
+        for item in py.builtin.frozenset(seen):
+            for value in item.__dict__.values():
+                if isinstance(value, type(py.test)):
+                    seen.add(value)
diff --git a/tools/py/testing/root/test_std.py b/tools/third_party/py/testing/root/test_std.py
similarity index 100%
rename from tools/py/testing/root/test_std.py
rename to tools/third_party/py/testing/root/test_std.py
diff --git a/tools/third_party/py/testing/root/test_xmlgen.py b/tools/third_party/py/testing/root/test_xmlgen.py
new file mode 100644
index 0000000..fc0e826
--- /dev/null
+++ b/tools/third_party/py/testing/root/test_xmlgen.py
@@ -0,0 +1,146 @@
+
+import py
+from py._xmlgen import unicode, html, raw
+import sys
+
+class ns(py.xml.Namespace):
+    pass
+
+def test_escape():
+    uvalue = py.builtin._totext('\xc4\x85\xc4\x87\n\xe2\x82\xac\n', 'utf-8')
+    class A:
+        def __unicode__(self):
+            return uvalue
+        def __str__(self):
+            x = self.__unicode__()
+            if sys.version_info[0] < 3:
+                return x.encode('utf-8')
+            return x
+    y = py.xml.escape(uvalue)
+    assert y == uvalue
+    x = py.xml.escape(A())
+    assert x == uvalue
+    if sys.version_info[0] < 3:
+        assert isinstance(x, unicode)
+        assert isinstance(y, unicode)
+        y = py.xml.escape(uvalue.encode('utf-8'))
+        assert y == uvalue
+
+
+def test_tag_with_text():
+    x = ns.hello("world")
+    u = unicode(x)
+    assert u == "<hello>world</hello>"
+
+def test_class_identity():
+    assert ns.hello is ns.hello
+
+def test_tag_with_text_and_attributes():
+    x = ns.some(name="hello", value="world")
+    assert x.attr.name == 'hello'
+    assert x.attr.value == 'world'
+    u = unicode(x)
+    assert u == '<some name="hello" value="world"/>'
+
+def test_tag_with_subclassed_attr_simple():
+    class my(ns.hello):
+        class Attr(ns.hello.Attr):
+            hello="world"
+    x = my()
+    assert x.attr.hello == 'world'
+    assert unicode(x) == '<my hello="world"/>'
+
+def test_tag_with_raw_attr():
+    x = html.object(data=raw('&'))
+    assert unicode(x) == '<object data="&"></object>'
+
+def test_tag_nested():
+    x = ns.hello(ns.world())
+    unicode(x) # triggers parentifying
+    assert x[0].parent is x
+    u = unicode(x)
+    assert u == '<hello><world/></hello>'
+
+def test_list_nested():
+    x = ns.hello([ns.world()]) #pass in a list here
+    u = unicode(x)
+    assert u == '<hello><world/></hello>'
+
+def test_tag_xmlname():
+    class my(ns.hello):
+        xmlname = 'world'
+    u = unicode(my())
+    assert u == '<world/>'
+
+def test_tag_with_text_entity():
+    x = ns.hello('world & rest')
+    u = unicode(x)
+    assert u == "<hello>world &amp; rest</hello>"
+
+def test_tag_with_text_and_attributes_entity():
+    x = ns.some(name="hello & world")
+    assert x.attr.name == "hello & world"
+    u = unicode(x)
+    assert u == '<some name="hello &amp; world"/>'
+
+def test_raw():
+    x = ns.some(py.xml.raw("<p>literal</p>"))
+    u = unicode(x)
+    assert u == "<some><p>literal</p></some>"
+
+
+def test_html_name_stickyness():
+    class my(html.p):
+        pass
+    x = my("hello")
+    assert unicode(x) == '<p>hello</p>'
+
+def test_stylenames():
+    class my:
+        class body(html.body):
+            style = html.Style(font_size = "12pt")
+    u = unicode(my.body())
+    assert u == '<body style="font-size: 12pt"></body>'
+
+def test_class_None():
+    t = html.body(class_=None)
+    u = unicode(t)
+    assert u == '<body></body>'
+
+def test_alternating_style():
+    alternating = (
+        html.Style(background="white"),
+        html.Style(background="grey"),
+    )
+    class my(html):
+        class li(html.li):
+            def style(self):
+                i = self.parent.index(self)
+                return alternating[i%2]
+            style = property(style)
+
+    x = my.ul(
+            my.li("hello"),
+            my.li("world"),
+            my.li("42"))
+    u = unicode(x)
+    assert u == ('<ul><li style="background: white">hello</li>'
+                     '<li style="background: grey">world</li>'
+                     '<li style="background: white">42</li>'
+                 '</ul>')
+
+def test_singleton():
+    h = html.head(html.link(href="foo"))
+    assert unicode(h) == '<head><link href="foo"/></head>'
+
+    h = html.head(html.script(src="foo"))
+    assert unicode(h) == '<head><script src="foo"></script></head>'
+
+def test_inline():
+    h = html.div(html.span('foo'), html.span('bar'))
+    assert (h.unicode(indent=2) ==
+            '<div><span>foo</span><span>bar</span></div>')
+
+def test_object_tags():
+    o = html.object(html.object())
+    assert o.unicode(indent=0) == '<object><object></object></object>'
diff --git a/tools/third_party/py/tox.ini b/tools/third_party/py/tox.ini
new file mode 100644
index 0000000..601661c
--- /dev/null
+++ b/tools/third_party/py/tox.ini
@@ -0,0 +1,33 @@
+[tox]
+envlist=py{27,34,35,36}-pytest{29,30,31}
+
+[testenv]
+changedir=testing
+commands=
+    pip install -U .. # hande the install order fallout since pytest depends on pip
+	
+	py.test --confcutdir=.. -rfsxX --junitxml={envlogdir}/junit-{envname}.xml []
+deps=
+	pytest29: pytest~=2.9.0
+	pytest30: pytest~=3.0.0
+	pytest31: pytest~=3.1.0
+
+[testenv:py27-xdist]
+basepython=python2.7
+deps=
+    pytest~=2.9.0
+    pytest-xdist<=1.16.0
+commands=
+  pip install -U .. # hande the install order fallout since pytest depends on pip
+  py.test -n3 -rfsxX --confcutdir=.. --runslowtests \
+        --junitxml={envlogdir}/junit-{envname}.xml []
+
+[testenv:jython]
+changedir=testing
+commands=
+    {envpython} -m pip install -U .. # hande the install order fallout since pytest depends on pip
+    {envpython} -m pytest --confcutdir=.. -rfsxX --junitxml={envlogdir}/junit-{envname}0.xml {posargs:io_ code}
+
+[pytest]
+rsyncdirs = conftest.py py doc testing
+addopts = -ra
diff --git a/tools/third_party/pytest/.coveragerc b/tools/third_party/pytest/.coveragerc
new file mode 100644
index 0000000..61ff667
--- /dev/null
+++ b/tools/third_party/pytest/.coveragerc
@@ -0,0 +1,4 @@
+[run]
+omit =
+    # standlonetemplate is read dynamically and tested by test_genscript
+    *standalonetemplate.py
diff --git a/tools/pytest/.gitattributes b/tools/third_party/pytest/.gitattributes
similarity index 100%
rename from tools/pytest/.gitattributes
rename to tools/third_party/pytest/.gitattributes
diff --git a/tools/third_party/pytest/.github/ISSUE_TEMPLATE.md b/tools/third_party/pytest/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..fbcbb16
--- /dev/null
+++ b/tools/third_party/pytest/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,8 @@
+Thanks for submitting an issue!
+
+Here's a quick checklist in what to include:
+
+- [ ] Include a detailed description of the bug or suggestion
+- [ ] `pip list` of the virtual environment you are using
+- [ ] pytest and operating system versions
+- [ ] Minimal example if possible
diff --git a/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md b/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..bf9fc19
--- /dev/null
+++ b/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,15 @@
+Thanks for submitting a PR, your contribution is really appreciated!
+
+Here's a quick checklist that should be present in PRs:
+
+- [ ] Add a new news fragment into the changelog folder
+  * name it `$issue_id.$type` for example (588.bug)
+  * if you don't have an issue_id change it to the pr id after creating the pr
+  * ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial`
+  * Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files."
+- [ ] Target: for `bugfix`, `vendor`, `doc` or `trivial` fixes, target `master`; for removals or features target `features`;
+- [ ] Make sure to include reasonable tests for your change if necessary
+
+Unless your change is a trivial or a documentation fix (e.g.,  a typo or reword of a small section) please:
+
+- [ ] Add yourself to `AUTHORS`, in alphabetical order;
diff --git a/tools/third_party/pytest/.gitignore b/tools/third_party/pytest/.gitignore
new file mode 100644
index 0000000..3b7ec9f
--- /dev/null
+++ b/tools/third_party/pytest/.gitignore
@@ -0,0 +1,39 @@
+# Automatically generated by `hgimportsvn`
+.svn
+.hgsvn
+
+# Ignore local virtualenvs
+lib/
+bin/
+include/
+.Python/
+
+# These lines are suggested according to the svn:ignore property
+# Feel free to enable them by uncommenting them
+*.pyc
+*.pyo
+*.swp
+*.class
+*.orig
+*~
+.hypothesis/
+
+# autogenerated
+_pytest/_version.py
+# setuptools
+.eggs/
+
+doc/*/_build
+build/
+dist/
+*.egg-info
+issue/
+env/
+.env/
+3rdparty/
+.tox
+.cache
+.coverage
+.ropeproject
+.idea
+.hypothesis
diff --git a/tools/third_party/pytest/.travis.yml b/tools/third_party/pytest/.travis.yml
new file mode 100644
index 0000000..938391c
--- /dev/null
+++ b/tools/third_party/pytest/.travis.yml
@@ -0,0 +1,56 @@
+sudo: false
+language: python
+python:
+  - '3.6'
+# command to install dependencies
+install:
+  - pip install --upgrade --pre tox
+# # command to run tests
+env:
+  matrix:
+    # coveralls is not listed in tox's envlist, but should run in travis
+    - TOXENV=coveralls
+    # note: please use "tox --listenvs" to populate the build matrix below
+    - TOXENV=linting
+    - TOXENV=py27
+    - TOXENV=py34
+    - TOXENV=py36
+    - TOXENV=py27-pexpect
+    - TOXENV=py27-xdist
+    - TOXENV=py27-trial
+    - TOXENV=py27-numpy
+    - TOXENV=py27-pluggymaster
+    - TOXENV=py36-pexpect
+    - TOXENV=py36-xdist
+    - TOXENV=py36-trial
+    - TOXENV=py36-numpy
+    - TOXENV=py36-pluggymaster
+    - TOXENV=py27-nobyte
+    - TOXENV=doctesting
+    - TOXENV=docs
+
+matrix:
+  include:
+    - env: TOXENV=pypy
+      python: 'pypy-5.4'
+    - env: TOXENV=py35
+      python: '3.5'
+    - env: TOXENV=py35-freeze
+      python: '3.5'
+    - env: TOXENV=py37
+      python: 'nightly'
+  allow_failures:
+    - env: TOXENV=py37
+      python: 'nightly'
+
+script: tox --recreate
+
+notifications:
+  irc:
+    channels:
+      - "chat.freenode.net#pytest"
+    on_success: change
+    on_failure: change
+    skip_join: true
+  email:
+    - pytest-commit@python.org
diff --git a/tools/third_party/pytest/AUTHORS b/tools/third_party/pytest/AUTHORS
new file mode 100644
index 0000000..44ae6aa
--- /dev/null
+++ b/tools/third_party/pytest/AUTHORS
@@ -0,0 +1,189 @@
+Holger Krekel, holger at merlinux eu
+merlinux GmbH, Germany, office at merlinux eu
+
+Contributors include::
+
+Abdeali JK
+Abhijeet Kasurde
+Ahn Ki-Wook
+Alexander Johnson
+Alexei Kozlenok
+Anatoly Bubenkoff
+Andras Tim
+Andreas Zeidler
+Andrzej Ostrowski
+Andy Freeland
+Anthon van der Neut
+Anthony Sottile
+Antony Lee
+Armin Rigo
+Aron Curzon
+Aviv Palivoda
+Barney Gale
+Ben Webb
+Benjamin Peterson
+Bernard Pratz
+Bob Ippolito
+Brian Dorsey
+Brian Okken
+Brianna Laugher
+Bruno Oliveira
+Cal Leeming
+Carl Friedrich Bolz
+Ceridwen
+Charles Cloud
+Charnjit SiNGH (CCSJ)
+Chris Lamb
+Christian Boelsen
+Christian Theunert
+Christian Tismer
+Christopher Gilling
+Daniel Grana
+Daniel Hahler
+Daniel Nuri
+Daniel Wandschneider
+Danielle Jenkins
+Dave Hunt
+David Díaz-Barquero
+David Mohr
+David Vierra
+Daw-Ran Liou
+Denis Kirisov
+Diego Russo
+Dmitry Dygalo
+Dmitry Pribysh
+Duncan Betts
+Edison Gustavo Muenz
+Edoardo Batini
+Eduardo Schettino
+Eli Boyarski
+Elizaveta Shashkova
+Endre Galaczi
+Eric Hunsberger
+Eric Siegerman
+Erik M. Bray
+Feng Ma
+Florian Bruhin
+Floris Bruynooghe
+Gabriel Reis
+George Kussumoto
+Georgy Dyuldin
+Graham Horler
+Greg Price
+Grig Gheorghiu
+Grigorii Eremeev (budulianin)
+Guido Wesdorp
+Harald Armin Massa
+Hugo van Kemenade
+Hui Wang (coldnight)
+Ian Bicking
+Jaap Broekhuizen
+Jan Balster
+Janne Vanhala
+Jason R. Coombs
+Javier Domingo Cansino
+Javier Romero
+Jeff Widman
+John Eddie Ayson
+John Towler
+Jon Sonesen
+Jonas Obrist
+Jordan Guymon
+Jordan Moldow
+Joshua Bronson
+Jurko Gospodnetić
+Justyna Janczyszyn
+Kale Kundert
+Katarzyna Jachim
+Kevin Cox
+Kodi B. Arfer
+Lawrence Mitchell
+Lee Kamentsky
+Lev Maximov
+Llandy Riveron Del Risco
+Loic Esteve
+Lukas Bednar
+Luke Murphy
+Maciek Fijalkowski
+Maho
+Maik Figura
+Mandeep Bhutani
+Manuel Krebber
+Marc Schlaich
+Marcin Bachry
+Mark Abramowitz
+Markus Unterwaditzer
+Martijn Faassen
+Martin Altmayer
+Martin K. Scherer
+Martin Prusse
+Mathieu Clabaut
+Matt Bachmann
+Matt Duck
+Matt Williams
+Matthias Hafner
+Maxim Filipenko
+mbyt
+Michael Aquilina
+Michael Birtwell
+Michael Droettboom
+Michael Seifert
+Michal Wajszczuk
+Mihai Capotă
+Mike Lundy
+Nathaniel Waisbrot
+Ned Batchelder
+Neven Mundar
+Nicolas Delaby
+Oleg Pidsadnyi
+Oliver Bestwalter
+Omar Kohl
+Omer Hadari
+Patrick Hayes
+Paweł Adamczak
+Pieter Mulder
+Piotr Banaszkiewicz
+Punyashloka Biswal
+Quentin Pradet
+Ralf Schmitt
+Ran Benita
+Raphael Pierzina
+Raquel Alegre
+Ravi Chandra
+Roberto Polli
+Romain Dorgueil
+Roman Bolshakov
+Ronny Pfannschmidt
+Ross Lawley
+Russel Winder
+Ryan Wooden
+Samuel Dion-Girardeau
+Samuele Pedroni
+Segev Finer
+Simon Gomizelj
+Skylar Downes
+Srinivas Reddy Thatiparthy
+Stefan Farmbauer
+Stefan Zimmermann
+Stefano Taschini
+Steffen Allner
+Stephan Obermann
+Tarcisio Fischer
+Tareq Alayan
+Ted Xiao
+Thomas Grainger
+Thomas Hisch
+Tom Dalton
+Tom Viner
+Trevor Bekolay
+Tyler Goodlet
+Vasily Kuznetsov
+Victor Uriarte
+Vidar T. Fauske
+Vitaly Lashmanov
+Vlad Dragos
+Wouter van Ackooy
+Xuan Luong
+Xuecong Liao
+Zoltán Máté
+Roland Puntaier
diff --git a/tools/third_party/pytest/CHANGELOG.rst b/tools/third_party/pytest/CHANGELOG.rst
new file mode 100644
index 0000000..7e7bfaf
--- /dev/null
+++ b/tools/third_party/pytest/CHANGELOG.rst
@@ -0,0 +1,4194 @@
+.. 
+    You should *NOT* be adding new change log entries to this file, this
+    file is managed by towncrier. You *may* edit previous change logs to
+    fix problems like typo corrections or such.
+    To add a new change log entry, please see
+    https://pip.pypa.io/en/latest/development/#adding-a-news-entry
+    we named the news folder changelog
+
+.. towncrier release notes start
+
+Pytest 3.3.0 (2017-11-23)
+=========================
+
+Deprecations and Removals
+-------------------------
+
+- Pytest no longer supports Python **2.6** and **3.3**. Those Python versions
+  are EOL for some time now and incur maintenance and compatibility costs on
+  the pytest core team, and following up with the rest of the community we
+  decided that they will no longer be supported starting on this version. Users
+  which still require those versions should pin pytest to ``<3.3``. (`#2812
+  <https://github.com/pytest-dev/pytest/issues/2812>`_)
+
+- Remove internal ``_preloadplugins()`` function. This removal is part of the
+  ``pytest_namespace()`` hook deprecation. (`#2636
+  <https://github.com/pytest-dev/pytest/issues/2636>`_)
+
+- Internally change ``CallSpec2`` to have a list of marks instead of a broken
+  mapping of keywords. This removes the keywords attribute of the internal
+  ``CallSpec2`` class. (`#2672
+  <https://github.com/pytest-dev/pytest/issues/2672>`_)
+
+- Remove ParameterSet.deprecated_arg_dict - its not a public api and the lack
+  of the underscore was a naming error. (`#2675
+  <https://github.com/pytest-dev/pytest/issues/2675>`_)
+
+- Remove the internal multi-typed attribute ``Node._evalskip`` and replace it
+  with the boolean ``Node._skipped_by_mark``. (`#2767
+  <https://github.com/pytest-dev/pytest/issues/2767>`_)
+
+- The ``params`` list passed to ``pytest.fixture`` is now for
+  all effects considered immutable and frozen at the moment of the ``pytest.fixture``
+  call. Previously the list could be changed before the first invocation of the fixture
+  allowing for a form of dynamic parametrization (for example, updated from command-line options),
+  but this was an unwanted implementation detail which complicated the internals and prevented
+  some internal cleanup. See issue `#2959 <https://github.com/pytest-dev/pytest/issues/2959>`_
+  for details and a recommended workaround.
+
+Features
+--------
+
+- ``pytest_fixture_post_finalizer`` hook can now receive a ``request``
+  argument. (`#2124 <https://github.com/pytest-dev/pytest/issues/2124>`_)
+
+- Replace the old introspection code in compat.py that determines the available
+  arguments of fixtures with inspect.signature on Python 3 and
+  funcsigs.signature on Python 2. This should respect ``__signature__``
+  declarations on functions. (`#2267
+  <https://github.com/pytest-dev/pytest/issues/2267>`_)
+
+- Report tests with global ``pytestmark`` variable only once. (`#2549
+  <https://github.com/pytest-dev/pytest/issues/2549>`_)
+
+- Now pytest displays the total progress percentage while running tests. The
+  previous output style can be set by configuring the ``console_output_style``
+  setting to ``classic``. (`#2657 <https://github.com/pytest-dev/pytest/issues/2657>`_)
+
+- Match ``warns`` signature to ``raises`` by adding ``match`` keyword. (`#2708
+  <https://github.com/pytest-dev/pytest/issues/2708>`_)
+
+- Pytest now captures and displays output from the standard `logging` module.
+  The user can control the logging level to be captured by specifying options
+  in ``pytest.ini``, the command line and also during individual tests using
+  markers. Also, a ``caplog`` fixture is available that enables users to test
+  the captured log during specific tests (similar to ``capsys`` for example).
+  For more information, please see the `logging docs
+  <https://docs.pytest.org/en/latest/logging.html>`_. This feature was
+  introduced by merging the popular `pytest-catchlog
+  <https://pypi.org/project/pytest-catchlog/>`_ plugin, thanks to `Thomas Hisch
+  <https://github.com/thisch>`_. Be advised that during the merging the
+  backward compatibility interface with the defunct ``pytest-capturelog`` has
+  been dropped. (`#2794 <https://github.com/pytest-dev/pytest/issues/2794>`_)
+
+- Add ``allow_module_level`` kwarg to ``pytest.skip()``, enabling to skip the
+  whole module. (`#2808 <https://github.com/pytest-dev/pytest/issues/2808>`_)
+
+- Allow setting ``file_or_dir``, ``-c``, and ``-o`` in PYTEST_ADDOPTS. (`#2824
+  <https://github.com/pytest-dev/pytest/issues/2824>`_)
+
+- Return stdout/stderr capture results as a ``namedtuple``, so ``out`` and
+  ``err`` can be accessed by attribute. (`#2879
+  <https://github.com/pytest-dev/pytest/issues/2879>`_)
+
+- Add ``capfdbinary``, a version of ``capfd`` which returns bytes from
+  ``readouterr()``. (`#2923
+  <https://github.com/pytest-dev/pytest/issues/2923>`_)
+
+- Add ``capsysbinary`` a version of ``capsys`` which returns bytes from
+  ``readouterr()``. (`#2934
+  <https://github.com/pytest-dev/pytest/issues/2934>`_)
+
+- Implement feature to skip ``setup.py`` files when run with
+  ``--doctest-modules``. (`#502
+  <https://github.com/pytest-dev/pytest/issues/502>`_)
+
+
+Bug Fixes
+---------
+
+- Resume output capturing after ``capsys/capfd.disabled()`` context manager.
+  (`#1993 <https://github.com/pytest-dev/pytest/issues/1993>`_)
+
+- ``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hooks are now
+  called for all ``conftest.py`` files. (`#2124
+  <https://github.com/pytest-dev/pytest/issues/2124>`_)
+
+- If an exception happens while loading a plugin, pytest no longer hides the
+  original traceback. In python2 it will show the original traceback with a new
+  message that explains in which plugin. In python3 it will show 2 canonized
+  exceptions, the original exception while loading the plugin in addition to an
+  exception that PyTest throws about loading a plugin. (`#2491
+  <https://github.com/pytest-dev/pytest/issues/2491>`_)
+
+- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709
+  <https://github.com/pytest-dev/pytest/issues/2709>`_)
+
+- Internal ``pytester`` plugin properly encodes ``bytes`` arguments to
+  ``utf-8``. (`#2738 <https://github.com/pytest-dev/pytest/issues/2738>`_)
+
+- ``testdir`` now uses use the same method used by ``tmpdir`` to create its
+  temporary directory. This changes the final structure of the ``testdir``
+  directory slightly, but should not affect usage in normal scenarios and
+  avoids a number of potential problems. (`#2751
+  <https://github.com/pytest-dev/pytest/issues/2751>`_)
+
+- Pytest no longer complains about warnings with unicode messages being
+  non-ascii compatible even for ascii-compatible messages. As a result of this,
+  warnings with unicode messages are converted first to an ascii representation
+  for safety. (`#2809 <https://github.com/pytest-dev/pytest/issues/2809>`_)
+
+- Change return value of pytest command when ``--maxfail`` is reached from
+  ``2`` (interrupted) to ``1`` (failed). (`#2845
+  <https://github.com/pytest-dev/pytest/issues/2845>`_)
+
+- Fix issue in assertion rewriting which could lead it to rewrite modules which
+  should not be rewritten. (`#2939
+  <https://github.com/pytest-dev/pytest/issues/2939>`_)
+
+- Handle marks without description in ``pytest.ini``. (`#2942
+  <https://github.com/pytest-dev/pytest/issues/2942>`_)
+
+
+Trivial/Internal Changes
+------------------------
+
+- pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal
+  structures to ease code maintainability. (`#2641
+  <https://github.com/pytest-dev/pytest/issues/2641>`_)
+
+- Refactored internal Python 2/3 compatibility code to use ``six``. (`#2642
+  <https://github.com/pytest-dev/pytest/issues/2642>`_)
+
+- Stop vendoring ``pluggy`` - we're missing out on its latest changes for not
+  much benefit (`#2719 <https://github.com/pytest-dev/pytest/issues/2719>`_)
+
+- Internal refactor: simplify ascii string escaping by using the
+  backslashreplace error handler in newer Python 3 versions. (`#2734
+  <https://github.com/pytest-dev/pytest/issues/2734>`_)
+
+- Remove unnecessary mark evaluator in unittest plugin (`#2767
+  <https://github.com/pytest-dev/pytest/issues/2767>`_)
+
+- Calls to ``Metafunc.addcall`` now emit a deprecation warning. This function
+  is scheduled to be removed in ``pytest-4.0``. (`#2876
+  <https://github.com/pytest-dev/pytest/issues/2876>`_)
+
+- Internal move of the parameterset extraction to a more maintainable place.
+  (`#2877 <https://github.com/pytest-dev/pytest/issues/2877>`_)
+
+- Internal refactoring to simplify scope node lookup. (`#2910
+  <https://github.com/pytest-dev/pytest/issues/2910>`_)
+
+- Configure ``pytest`` to prevent pip from installing pytest in unsupported
+  Python versions. (`#2922
+  <https://github.com/pytest-dev/pytest/issues/2922>`_)
+
+
+Pytest 3.2.5 (2017-11-15)
+=========================
+
+Bug Fixes
+---------
+
+- Remove ``py<1.5`` restriction from ``pytest`` as this can cause version
+  conflicts in some installations. (`#2926
+  <https://github.com/pytest-dev/pytest/issues/2926>`_)
+
+
+Pytest 3.2.4 (2017-11-13)
+=========================
+
+Bug Fixes
+---------
+
+- Fix the bug where running with ``--pyargs`` will result in items with
+  empty ``parent.nodeid`` if run from a different root directory. (`#2775
+  <https://github.com/pytest-dev/pytest/issues/2775>`_)
+
+- Fix issue with ``@pytest.parametrize`` if argnames was specified as keyword arguments.
+  (`#2819 <https://github.com/pytest-dev/pytest/issues/2819>`_)
+
+- Strip whitespace from marker names when reading them from INI config. (`#2856
+  <https://github.com/pytest-dev/pytest/issues/2856>`_)
+
+- Show full context of doctest source in the pytest output, if the line number of
+  failed example in the docstring is < 9. (`#2882
+  <https://github.com/pytest-dev/pytest/issues/2882>`_)
+
+- Match fixture paths against actual path segments in order to avoid matching folders which share a prefix.
+  (`#2836 <https://github.com/pytest-dev/pytest/issues/2836>`_)
+
+Improved Documentation
+----------------------
+
+- Introduce a dedicated section about conftest.py. (`#1505
+  <https://github.com/pytest-dev/pytest/issues/1505>`_)
+
+- Explicitly mention ``xpass`` in the documentation of ``xfail``. (`#1997
+  <https://github.com/pytest-dev/pytest/issues/1997>`_)
+
+- Append example for pytest.param in the example/parametrize document. (`#2658
+  <https://github.com/pytest-dev/pytest/issues/2658>`_)
+
+- Clarify language of proposal for fixtures parameters (`#2893
+  <https://github.com/pytest-dev/pytest/issues/2893>`_)
+
+- List python 3.6 in the documented supported versions in the getting started
+  document. (`#2903 <https://github.com/pytest-dev/pytest/issues/2903>`_)
+
+- Clarify the documentation of available fixture scopes. (`#538
+  <https://github.com/pytest-dev/pytest/issues/538>`_)
+
+- Add documentation about the ``python -m pytest`` invocation adding the
+  current directory to sys.path. (`#911
+  <https://github.com/pytest-dev/pytest/issues/911>`_)
+
+
+Pytest 3.2.3 (2017-10-03)
+=========================
+
+Bug Fixes
+---------
+
+- Fix crash in tab completion when no prefix is given. (`#2748
+  <https://github.com/pytest-dev/pytest/issues/2748>`_)
+
+- The equality checking function (``__eq__``) of ``MarkDecorator`` returns
+  ``False`` if one object is not an instance of ``MarkDecorator``. (`#2758
+  <https://github.com/pytest-dev/pytest/issues/2758>`_)
+
+- When running ``pytest --fixtures-per-test``: don't crash if an item has no
+  _fixtureinfo attribute (e.g. doctests) (`#2788
+  <https://github.com/pytest-dev/pytest/issues/2788>`_)
+
+
+Improved Documentation
+----------------------
+
+- In help text of ``-k`` option, add example of using ``not`` to not select
+  certain tests whose names match the provided expression. (`#1442
+  <https://github.com/pytest-dev/pytest/issues/1442>`_)
+
+- Add note in ``parametrize.rst`` about calling ``metafunc.parametrize``
+  multiple times. (`#1548 <https://github.com/pytest-dev/pytest/issues/1548>`_)
+
+
+Trivial/Internal Changes
+------------------------
+
+- Set ``xfail_strict=True`` in pytest's own test suite to catch expected
+  failures as soon as they start to pass. (`#2722
+  <https://github.com/pytest-dev/pytest/issues/2722>`_)
+
+- Fix typo in example of passing a callable to markers (in example/markers.rst)
+  (`#2765 <https://github.com/pytest-dev/pytest/issues/2765>`_)
+
+
+Pytest 3.2.2 (2017-09-06)
+=========================
+
+Bug Fixes
+---------
+
+- Calling the deprecated `request.getfuncargvalue()` now shows the source of
+  the call. (`#2681 <https://github.com/pytest-dev/pytest/issues/2681>`_)
+
+- Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699
+  <https://github.com/pytest-dev/pytest/issues/2699>`_)
+
+- Fixed edge-case during collection: attributes which raised ``pytest.fail``
+  when accessed would abort the entire collection. (`#2707
+  <https://github.com/pytest-dev/pytest/issues/2707>`_)
+
+- Fix ``ReprFuncArgs`` with mixed unicode and UTF-8 args. (`#2731
+  <https://github.com/pytest-dev/pytest/issues/2731>`_)
+
+
+Improved Documentation
+----------------------
+
+- In examples on working with custom markers, add examples demonstrating the
+  usage of ``pytest.mark.MARKER_NAME.with_args`` in comparison with
+  ``pytest.mark.MARKER_NAME.__call__`` (`#2604
+  <https://github.com/pytest-dev/pytest/issues/2604>`_)
+
+- In one of the simple examples, use `pytest_collection_modifyitems()` to skip
+  tests based on a command-line option, allowing its sharing while preventing a
+  user error when acessing `pytest.config` before the argument parsing. (`#2653
+  <https://github.com/pytest-dev/pytest/issues/2653>`_)
+
+
+Trivial/Internal Changes
+------------------------
+
+- Fixed minor error in 'Good Practices/Manual Integration' code snippet.
+  (`#2691 <https://github.com/pytest-dev/pytest/issues/2691>`_)
+
+- Fixed typo in goodpractices.rst. (`#2721
+  <https://github.com/pytest-dev/pytest/issues/2721>`_)
+
+- Improve user guidance regarding ``--resultlog`` deprecation. (`#2739
+  <https://github.com/pytest-dev/pytest/issues/2739>`_)
+
+
+Pytest 3.2.1 (2017-08-08)
+=========================
+
+Bug Fixes
+---------
+
+- Fixed small terminal glitch when collecting a single test item. (`#2579
+  <https://github.com/pytest-dev/pytest/issues/2579>`_)
+
+- Correctly consider ``/`` as the file separator to automatically mark plugin
+  files for rewrite on Windows. (`#2591 <https://github.com/pytest-
+  dev/pytest/issues/2591>`_)
+
+- Properly escape test names when setting ``PYTEST_CURRENT_TEST`` environment
+  variable. (`#2644 <https://github.com/pytest-dev/pytest/issues/2644>`_)
+
+- Fix error on Windows and Python 3.6+ when ``sys.stdout`` has been replaced
+  with a stream-like object which does not implement the full ``io`` module
+  buffer protocol. In particular this affects ``pytest-xdist`` users on the
+  aforementioned platform. (`#2666 <https://github.com/pytest-
+  dev/pytest/issues/2666>`_)
+
+
+Improved Documentation
+----------------------
+
+- Explicitly document which pytest features work with ``unittest``. (`#2626
+  <https://github.com/pytest-dev/pytest/issues/2626>`_)
+
+
+Pytest 3.2.0 (2017-07-30)
+=========================
+
+Deprecations and Removals
+-------------------------
+
+- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
+  operators to avoid surprising/inconsistent behavior. See `the approx docs
+  <https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more
+  information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
+
+- All old-style specific behavior in current classes in the pytest's API is
+  considered deprecated at this point and will be removed in a future release.
+  This affects Python 2 users only and in rare situations. (`#2147
+  <https://github.com/pytest-dev/pytest/issues/2147>`_)
+
+- A deprecation warning is now raised when using marks for parameters
+  in ``pytest.mark.parametrize``. Use ``pytest.param`` to apply marks to
+  parameters instead. (`#2427 <https://github.com/pytest-dev/pytest/issues/2427>`_)
+
+
+Features
+--------
+
+- Add support for numpy arrays (and dicts) to approx. (`#1994
+  <https://github.com/pytest-dev/pytest/issues/1994>`_)
+
+- Now test function objects have a ``pytestmark`` attribute containing a list
+  of marks applied directly to the test function, as opposed to marks inherited
+  from parent classes or modules. (`#2516 <https://github.com/pytest-
+  dev/pytest/issues/2516>`_)
+
+- Collection ignores local virtualenvs by default; `--collect-in-virtualenv`
+  overrides this behavior. (`#2518 <https://github.com/pytest-
+  dev/pytest/issues/2518>`_)
+
+- Allow class methods decorated as ``@staticmethod`` to be candidates for
+  collection as a test function. (Only for Python 2.7 and above. Python 2.6
+  will still ignore static methods.) (`#2528 <https://github.com/pytest-
+  dev/pytest/issues/2528>`_)
+
+- Introduce ``mark.with_args`` in order to allow passing functions/classes as
+  sole argument to marks. (`#2540 <https://github.com/pytest-
+  dev/pytest/issues/2540>`_)
+
+- New ``cache_dir`` ini option: sets the directory where the contents of the
+  cache plugin are stored. Directory may be relative or absolute path: if relative path, then
+  directory is created relative to ``rootdir``, otherwise it is used as is.
+  Additionally path may contain environment variables which are expanded during
+  runtime. (`#2543 <https://github.com/pytest-dev/pytest/issues/2543>`_)
+
+- Introduce the ``PYTEST_CURRENT_TEST`` environment variable that is set with
+  the ``nodeid`` and stage (``setup``, ``call`` and ``teardown``) of the test
+  being currently executed. See the `documentation
+  <https://docs.pytest.org/en/latest/example/simple.html#pytest-current-test-
+  environment-variable>`_ for more info. (`#2583 <https://github.com/pytest-
+  dev/pytest/issues/2583>`_)
+
+- Introduced ``@pytest.mark.filterwarnings`` mark which allows overwriting the
+  warnings filter on a per test, class or module level. See the `docs
+  <https://docs.pytest.org/en/latest/warnings.html#pytest-mark-
+  filterwarnings>`_ for more information. (`#2598 <https://github.com/pytest-
+  dev/pytest/issues/2598>`_)
+
+- ``--last-failed`` now remembers forever when a test has failed and only
+  forgets it if it passes again. This makes it easy to fix a test suite by
+  selectively running files and fixing tests incrementally. (`#2621
+  <https://github.com/pytest-dev/pytest/issues/2621>`_)
+
+- New ``pytest_report_collectionfinish`` hook which allows plugins to add
+  messages to the terminal reporting after collection has been finished
+  successfully. (`#2622 <https://github.com/pytest-dev/pytest/issues/2622>`_)
+
+- Added support for `PEP-415's <https://www.python.org/dev/peps/pep-0415/>`_
+  ``Exception.__suppress_context__``. Now if a ``raise exception from None`` is
+  caught by pytest, pytest will no longer chain the context in the test report.
+  The behavior now matches Python's traceback behavior. (`#2631
+  <https://github.com/pytest-dev/pytest/issues/2631>`_)
+
+- Exceptions raised by ``pytest.fail``, ``pytest.skip`` and ``pytest.xfail``
+  now subclass BaseException, making them harder to be caught unintentionally
+  by normal code. (`#580 <https://github.com/pytest-dev/pytest/issues/580>`_)
+
+
+Bug Fixes
+---------
+
+- Set ``stdin`` to a closed ``PIPE`` in ``pytester.py.Testdir.popen()`` for
+  avoid unwanted interactive ``pdb`` (`#2023 <https://github.com/pytest-
+  dev/pytest/issues/2023>`_)
+
+- Add missing ``encoding`` attribute to ``sys.std*`` streams when using
+  ``capsys`` capture mode. (`#2375 <https://github.com/pytest-
+  dev/pytest/issues/2375>`_)
+
+- Fix terminal color changing to black on Windows if ``colorama`` is imported
+  in a ``conftest.py`` file. (`#2510 <https://github.com/pytest-
+  dev/pytest/issues/2510>`_)
+
+- Fix line number when reporting summary of skipped tests. (`#2548
+  <https://github.com/pytest-dev/pytest/issues/2548>`_)
+
+- capture: ensure that EncodedFile.name is a string. (`#2555
+  <https://github.com/pytest-dev/pytest/issues/2555>`_)
+
+- The options ``--fixtures`` and ``--fixtures-per-test`` will now keep
+  indentation within docstrings. (`#2574 <https://github.com/pytest-
+  dev/pytest/issues/2574>`_)
+
+- doctests line numbers are now reported correctly, fixing `pytest-sugar#122
+  <https://github.com/Frozenball/pytest-sugar/issues/122>`_. (`#2610
+  <https://github.com/pytest-dev/pytest/issues/2610>`_)
+
+- Fix non-determinism in order of fixture collection. Adds new dependency
+  (ordereddict) for Python 2.6. (`#920 <https://github.com/pytest-
+  dev/pytest/issues/920>`_)
+
+
+Improved Documentation
+----------------------
+
+- Clarify ``pytest_configure`` hook call order. (`#2539
+  <https://github.com/pytest-dev/pytest/issues/2539>`_)
+
+- Extend documentation for testing plugin code with the ``pytester`` plugin.
+  (`#971 <https://github.com/pytest-dev/pytest/issues/971>`_)
+
+
+Trivial/Internal Changes
+------------------------
+
+- Update help message for ``--strict`` to make it clear it only deals with
+  unregistered markers, not warnings. (`#2444 <https://github.com/pytest-
+  dev/pytest/issues/2444>`_)
+
+- Internal code move: move code for pytest.approx/pytest.raises to own files in
+  order to cut down the size of python.py (`#2489 <https://github.com/pytest-
+  dev/pytest/issues/2489>`_)
+
+- Renamed the utility function ``_pytest.compat._escape_strings`` to
+  ``_ascii_escaped`` to better communicate the function's purpose. (`#2533
+  <https://github.com/pytest-dev/pytest/issues/2533>`_)
+
+- Improve error message for CollectError with skip/skipif. (`#2546
+  <https://github.com/pytest-dev/pytest/issues/2546>`_)
+
+- Emit warning about ``yield`` tests being deprecated only once per generator.
+  (`#2562 <https://github.com/pytest-dev/pytest/issues/2562>`_)
+
+- Ensure final collected line doesn't include artifacts of previous write.
+  (`#2571 <https://github.com/pytest-dev/pytest/issues/2571>`_)
+
+- Fixed all flake8 errors and warnings. (`#2581 <https://github.com/pytest-
+  dev/pytest/issues/2581>`_)
+
+- Added ``fix-lint`` tox environment to run automatic pep8 fixes on the code.
+  (`#2582 <https://github.com/pytest-dev/pytest/issues/2582>`_)
+
+- Turn warnings into errors in pytest's own test suite in order to catch
+  regressions due to deprecations more promptly. (`#2588
+  <https://github.com/pytest-dev/pytest/issues/2588>`_)
+
+- Show multiple issue links in CHANGELOG entries. (`#2620
+  <https://github.com/pytest-dev/pytest/issues/2620>`_)
+
+
+Pytest 3.1.3 (2017-07-03)
+=========================
+
+Bug Fixes
+---------
+
+- Fix decode error in Python 2 for doctests in docstrings. (`#2434
+  <https://github.com/pytest-dev/pytest/issues/2434>`_)
+
+- Exceptions raised during teardown by finalizers are now suppressed until all
+  finalizers are called, with the initial exception reraised. (`#2440
+  <https://github.com/pytest-dev/pytest/issues/2440>`_)
+
+- Fix incorrect "collected items" report when specifying tests on the command-
+  line. (`#2464 <https://github.com/pytest-dev/pytest/issues/2464>`_)
+
+- ``deprecated_call`` in context-manager form now captures deprecation warnings
+  even if the same warning has already been raised. Also, ``deprecated_call``
+  will always produce the same error message (previously it would produce
+  different messages in context-manager vs. function-call mode). (`#2469
+  <https://github.com/pytest-dev/pytest/issues/2469>`_)
+
+- Fix issue where paths collected by pytest could have triple leading ``/``
+  characters. (`#2475 <https://github.com/pytest-dev/pytest/issues/2475>`_)
+
+- Fix internal error when trying to detect the start of a recursive traceback.
+  (`#2486 <https://github.com/pytest-dev/pytest/issues/2486>`_)
+
+
+Improved Documentation
+----------------------
+
+- Explicitly state for which hooks the calls stop after the first non-None
+  result. (`#2493 <https://github.com/pytest-dev/pytest/issues/2493>`_)
+
+
+Trivial/Internal Changes
+------------------------
+
+- Create invoke tasks for updating the vendored packages. (`#2474
+  <https://github.com/pytest-dev/pytest/issues/2474>`_)
+
+- Update copyright dates in LICENSE, README.rst and in the documentation.
+  (`#2499 <https://github.com/pytest-dev/pytest/issues/2499>`_)
+
+
+Pytest 3.1.2 (2017-06-08)
+=========================
+
+Bug Fixes
+---------
+
+- Required options added via ``pytest_addoption`` will no longer prevent using
+  --help without passing them. (#1999)
+
+- Respect ``python_files`` in assertion rewriting. (#2121)
+
+- Fix recursion error detection when frames in the traceback contain objects
+  that can't be compared (like ``numpy`` arrays). (#2459)
+
+- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only
+  when the message contains non-ascii unicode (Python 2 only). (#2463)
+
+- Added a workaround for Python 3.6 ``WindowsConsoleIO`` breaking due to Pytests's
+  ``FDCapture``. Other code using console handles might still be affected by the
+  very same issue and might require further workarounds/fixes, i.e. ``colorama``.
+  (#2467)
+
+
+Improved Documentation
+----------------------
+
+- Fix internal API links to ``pluggy`` objects. (#2331)
+
+- Make it clear that ``pytest.xfail`` stops test execution at the calling point
+  and improve overall flow of the ``skipping`` docs. (#810)
+
+
+Pytest 3.1.1 (2017-05-30)
+=========================
+
+Bug Fixes
+---------
+
+- pytest warning capture no longer overrides existing warning filters. The
+  previous behaviour would override all filters and caused regressions in test
+  suites which configure warning filters to match their needs. Note that as a
+  side-effect of this is that ``DeprecationWarning`` and
+  ``PendingDeprecationWarning`` are no longer shown by default. (#2430)
+
+- Fix issue with non-ascii contents in doctest text files. (#2434)
+
+- Fix encoding errors for unicode warnings in Python 2. (#2436)
+
+- ``pytest.deprecated_call`` now captures ``PendingDeprecationWarning`` in
+  context manager form. (#2441)
+
+
+Improved Documentation
+----------------------
+
+- Addition of towncrier for changelog management. (#2390)
+
+
+3.1.0 (2017-05-22)
+==================
+
+
+New Features
+------------
+
+* The ``pytest-warnings`` plugin has been integrated into the core and now ``pytest`` automatically
+  captures and displays warnings at the end of the test session.
+
+  .. warning::
+
+    This feature may disrupt test suites which apply and treat warnings themselves, and can be
+    disabled in your ``pytest.ini``:
+
+    .. code-block:: ini
+
+      [pytest]
+      addopts = -p no:warnings
+
+    See the `warnings documentation page <https://docs.pytest.org/en/latest/warnings.html>`_ for more
+    information.
+
+  Thanks `@nicoddemus`_ for the PR.
+
+* Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (`#533`_).
+
+* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files.
+  Thanks `@wheerd`_ for the PR (`#2101`_).
+
+* ``pytest.warns`` now checks for subclass relationship rather than
+  class equality. Thanks `@lesteve`_ for the PR (`#2166`_)
+
+* ``pytest.raises`` now asserts that the error message matches a text or regex
+  with the ``match`` keyword argument. Thanks `@Kriechi`_ for the PR.
+
+* ``pytest.param`` can be used to declare test parameter sets with marks and test ids.
+  Thanks `@RonnyPfannschmidt`_ for the PR.
+
+
+Changes
+-------
+
+* remove all internal uses of pytest_namespace hooks,
+  this is to prepare the removal of preloadconfig in pytest 4.0
+  Thanks to `@RonnyPfannschmidt`_ for the PR.
+
+* pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR.
+
+* It is now possible to skip test classes from being collected by setting a
+  ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks
+  to `@syre`_ for the report and `@lwm`_ for the PR.
+
+* Change junitxml.py to produce reports that comply with Junitxml schema.
+  If the same test fails with failure in call and then errors in teardown
+  we split testcase element into two, one containing the error and the other
+  the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR.
+
+* Testcase reports with a ``url`` attribute will now properly write this to junitxml.
+  Thanks `@fushi`_ for the PR (`#1874`_).
+
+* Remove common items from dict comparision output when verbosity=1. Also update
+  the truncation message to make it clearer that pytest truncates all
+  assertion messages if verbosity < 2 (`#1512`_).
+  Thanks `@mattduck`_ for the PR
+
+* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
+  ``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for
+  the PR (`#1952`_).
+
+* fix `#2013`_: turn RecordedWarning into ``namedtuple``,
+  to give it a comprehensible repr while preventing unwarranted modification.
+
+* fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func.
+  Thanks `@RonnyPfannschmidt`_ for the report and PR.
+
+* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This
+  makes it easy to write hooks for plugins which will be loaded during collection, for example using the
+  ``pytest_plugins`` special variable (`#1821`_).
+  Thanks `@nicoddemus`_ for the PR.
+
+* Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an
+  additional parameter.
+  Thanks `@unsignedint`_ for the PR.
+
+* Add ``venv`` to the default ``norecursedirs`` setting.
+  Thanks `@The-Compiler`_ for the PR.
+
+* ``PluginManager.import_plugin`` now accepts unicode plugin names in Python 2.
+  Thanks `@reutsharabani`_ for the PR.
+
+* fix `#2308`_: When using both ``--lf`` and ``--ff``, only the last failed tests are run.
+  Thanks `@ojii`_ for the PR.
+
+* Replace minor/patch level version numbers in the documentation with placeholders.
+  This significantly reduces change-noise as different contributors regnerate
+  the documentation on different platforms.
+  Thanks `@RonnyPfannschmidt`_ for the PR.
+
+* fix `#2391`_: consider pytest_plugins on all plugin modules
+  Thanks `@RonnyPfannschmidt`_ for the PR.
+
+
+Bug Fixes
+---------
+
+* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer``
+  while using ``capsys`` fixture in python 3. (`#1407`_).
+  Thanks to `@asottile`_.
+
+* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather
+  than ValueErrors in the ``fileno`` method (`#2276`_).
+  Thanks `@metasyn`_ and `@vlad-dragos`_ for the PR.
+
+* Fix exception formatting while importing modules when the exception message
+  contains non-ascii characters (`#2336`_).
+  Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR.
+
+* Added documentation related to issue (`#1937`_)
+  Thanks `@skylarjhdownes`_ for the PR.
+
+* Allow collecting files with any file extension as Python modules (`#2369`_).
+  Thanks `@Kodiologist`_ for the PR.
+
+* Show the correct error message when collect "parametrize" func with wrong args (`#2383`_).
+  Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR.
+
+
+.. _@davidszotten: https://github.com/davidszotten
+.. _@fabioz: https://github.com/fabioz
+.. _@fogo: https://github.com/fogo
+.. _@fushi: https://github.com/fushi
+.. _@Kodiologist: https://github.com/Kodiologist
+.. _@Kriechi: https://github.com/Kriechi
+.. _@mandeep: https://github.com/mandeep
+.. _@mattduck: https://github.com/mattduck
+.. _@metasyn: https://github.com/metasyn
+.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE
+.. _@ojii: https://github.com/ojii
+.. _@reutsharabani: https://github.com/reutsharabani
+.. _@robin0371: https://github.com/robin0371
+.. _@skylarjhdownes: https://github.com/skylarjhdownes
+.. _@unsignedint: https://github.com/unsignedint
+.. _@wheerd: https://github.com/wheerd
+
+
+.. _#1407: https://github.com/pytest-dev/pytest/issues/1407
+.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
+.. _#1821: https://github.com/pytest-dev/pytest/issues/1821
+.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
+.. _#1937: https://github.com/pytest-dev/pytest/issues/1937
+.. _#1952: https://github.com/pytest-dev/pytest/pull/1952
+.. _#2007: https://github.com/pytest-dev/pytest/issues/2007
+.. _#2013: https://github.com/pytest-dev/pytest/issues/2013
+.. _#2101: https://github.com/pytest-dev/pytest/pull/2101
+.. _#2166: https://github.com/pytest-dev/pytest/pull/2166
+.. _#2208: https://github.com/pytest-dev/pytest/issues/2208
+.. _#2228: https://github.com/pytest-dev/pytest/issues/2228
+.. _#2276: https://github.com/pytest-dev/pytest/issues/2276
+.. _#2308: https://github.com/pytest-dev/pytest/issues/2308
+.. _#2336: https://github.com/pytest-dev/pytest/issues/2336
+.. _#2369: https://github.com/pytest-dev/pytest/issues/2369
+.. _#2383: https://github.com/pytest-dev/pytest/issues/2383
+.. _#2391: https://github.com/pytest-dev/pytest/issues/2391
+.. _#533: https://github.com/pytest-dev/pytest/issues/533
+
+
+
+3.0.7 (2017-03-14)
+==================
+
+
+* Fix issue in assertion rewriting breaking due to modules silently discarding
+  other modules when importing fails
+  Notably, importing the ``anydbm`` module is fixed. (`#2248`_).
+  Thanks `@pfhayes`_ for the PR.
+
+* junitxml: Fix problematic case where system-out tag occured twice per testcase
+  element in the XML report. Thanks `@kkoukiou`_ for the PR.
+
+* Fix regression, pytest now skips unittest correctly if run with ``--pdb``
+  (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR.
+
+* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_).
+  Thanks to `@bluetech`_.
+
+* ``--override-ini`` now correctly overrides some fundamental options like ``python_files`` (`#2238`_).
+  Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR.
+
+* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_).
+  Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR.
+
+* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test.
+  Thanks `@omerhadari`_ for the PR.
+
+* Skipping plugin now also works with test items generated by custom collectors (`#2231`_).
+  Thanks to `@vidartf`_.
+
+* Fix trailing whitespace in console output if no .ini file presented (`#2281`_). Thanks `@fbjorn`_ for the PR.
+
+* Conditionless ``xfail`` markers no longer rely on the underlying test item
+  being an instance of ``PyobjMixin``, and can therefore apply to tests not
+  collected by the built-in python test collector. Thanks `@barneygale`_ for the
+  PR.
+
+
+.. _@pfhayes: https://github.com/pfhayes
+.. _@bluetech: https://github.com/bluetech
+.. _@gst: https://github.com/gst
+.. _@sirex: https://github.com/sirex
+.. _@vidartf: https://github.com/vidartf
+.. _@kkoukiou: https://github.com/KKoukiou
+.. _@omerhadari: https://github.com/omerhadari
+.. _@fbjorn: https://github.com/fbjorn
+
+.. _#2248: https://github.com/pytest-dev/pytest/issues/2248
+.. _#2137: https://github.com/pytest-dev/pytest/issues/2137
+.. _#2160: https://github.com/pytest-dev/pytest/issues/2160
+.. _#2231: https://github.com/pytest-dev/pytest/issues/2231
+.. _#2234: https://github.com/pytest-dev/pytest/issues/2234
+.. _#2238: https://github.com/pytest-dev/pytest/issues/2238
+.. _#2281: https://github.com/pytest-dev/pytest/issues/2281
+
+.. _PEP-479: https://www.python.org/dev/peps/pep-0479/
+
+
+3.0.6 (2017-01-22)
+==================
+
+* pytest no longer generates ``PendingDeprecationWarning`` from its own operations, which was introduced by mistake in version ``3.0.5`` (`#2118`_).
+  Thanks to `@nicoddemus`_ for the report and `@RonnyPfannschmidt`_ for the PR.
+
+
+* pytest no longer recognizes coroutine functions as yield tests (`#2129`_).
+  Thanks to `@malinoff`_ for the PR.
+
+* Plugins loaded by the ``PYTEST_PLUGINS`` environment variable are now automatically
+  considered for assertion rewriting (`#2185`_).
+  Thanks `@nicoddemus`_ for the PR.
+
+* Improve error message when pytest.warns fails (`#2150`_). The type(s) of the
+  expected warnings and the list of caught warnings is added to the
+  error message. Thanks `@lesteve`_ for the PR.
+
+* Fix ``pytester`` internal plugin to work correctly with latest versions of
+  ``zope.interface`` (`#1989`_). Thanks `@nicoddemus`_ for the PR.
+
+* Assert statements of the ``pytester`` plugin again benefit from assertion rewriting (`#1920`_).
+  Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR.
+
+* Specifying tests with colons like ``test_foo.py::test_bar`` for tests in
+  subdirectories with ini configuration files now uses the correct ini file
+  (`#2148`_).  Thanks `@pelme`_.
+
+* Fail ``testdir.runpytest().assert_outcomes()`` explicitly if the pytest
+  terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR.
+
+
+.. _@barneygale: https://github.com/barneygale
+.. _@lesteve: https://github.com/lesteve
+.. _@malinoff: https://github.com/malinoff
+.. _@pelme: https://github.com/pelme
+.. _@eli-b: https://github.com/eli-b
+
+.. _#2118: https://github.com/pytest-dev/pytest/issues/2118
+
+.. _#1989: https://github.com/pytest-dev/pytest/issues/1989
+.. _#1920: https://github.com/pytest-dev/pytest/issues/1920
+.. _#2129: https://github.com/pytest-dev/pytest/issues/2129
+.. _#2148: https://github.com/pytest-dev/pytest/issues/2148
+.. _#2150: https://github.com/pytest-dev/pytest/issues/2150
+.. _#2185: https://github.com/pytest-dev/pytest/issues/2185
+
+
+3.0.5 (2016-12-05)
+==================
+
+* Add warning when not passing ``option=value`` correctly to ``-o/--override-ini`` (`#2105`_).
+  Also improved the help documentation. Thanks to `@mbukatov`_ for the report and
+  `@lwm`_ for the PR.
+
+* Now ``--confcutdir`` and ``--junit-xml`` are properly validated if they are directories
+  and filenames, respectively (`#2089`_ and `#2078`_). Thanks to `@lwm`_ for the PR.
+
+* Add hint to error message hinting possible missing ``__init__.py`` (`#478`_). Thanks `@DuncanBetts`_.
+
+* More accurately describe when fixture finalization occurs in documentation (`#687`_). Thanks `@DuncanBetts`_.
+
+* Provide ``:ref:`` targets for ``recwarn.rst`` so we can use intersphinx referencing.
+  Thanks to `@dupuy`_ for the report and `@lwm`_ for the PR.
+
+* In Python 2, use a simple ``+-`` ASCII string in the string representation of ``pytest.approx`` (for example ``"4 +- 4.0e-06"``)
+  because it is brittle to handle that in different contexts and representations internally in pytest
+  which can result in bugs such as `#2111`_. In Python 3, the representation still uses ``±`` (for example ``4 ± 4.0e-06``).
+  Thanks `@kerrick-lyft`_ for the report and `@nicoddemus`_ for the PR.
+
+* Using ``item.Function``, ``item.Module``, etc., is now issuing deprecation warnings, prefer
+  ``pytest.Function``, ``pytest.Module``, etc., instead (`#2034`_).
+  Thanks `@nmundar`_ for the PR.
+
+* Fix error message using ``approx`` with complex numbers (`#2082`_).
+  Thanks `@adler-j`_ for the report and `@nicoddemus`_ for the PR.
+
+* Fixed false-positives warnings from assertion rewrite hook for modules imported more than
+  once by the ``pytest_plugins`` mechanism.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Remove an internal cache which could cause hooks from ``conftest.py`` files in
+  sub-directories to be called in other directories incorrectly (`#2016`_).
+  Thanks `@d-b-w`_ for the report and `@nicoddemus`_ for the PR.
+
+* Remove internal code meant to support earlier Python 3 versions that produced the side effect
+  of leaving ``None`` in ``sys.modules`` when expressions were evaluated by pytest (for example passing a condition
+  as a string to ``pytest.mark.skipif``)(`#2103`_).
+  Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR.
+
+* Cope gracefully with a .pyc file with no matching .py file (`#2038`_). Thanks
+  `@nedbat`_.
+
+.. _@syre: https://github.com/syre
+.. _@adler-j: https://github.com/adler-j
+.. _@d-b-w: https://bitbucket.org/d-b-w/
+.. _@DuncanBetts: https://github.com/DuncanBetts
+.. _@dupuy: https://bitbucket.org/dupuy/
+.. _@kerrick-lyft: https://github.com/kerrick-lyft
+.. _@lwm: https://github.com/lwm
+.. _@mbukatov: https://github.com/mbukatov
+.. _@nedbat: https://github.com/nedbat
+.. _@nmundar: https://github.com/nmundar
+
+.. _#2016: https://github.com/pytest-dev/pytest/issues/2016
+.. _#2034: https://github.com/pytest-dev/pytest/issues/2034
+.. _#2038: https://github.com/pytest-dev/pytest/issues/2038
+.. _#2078: https://github.com/pytest-dev/pytest/issues/2078
+.. _#2082: https://github.com/pytest-dev/pytest/issues/2082
+.. _#2089: https://github.com/pytest-dev/pytest/issues/2089
+.. _#2103: https://github.com/pytest-dev/pytest/issues/2103
+.. _#2105: https://github.com/pytest-dev/pytest/issues/2105
+.. _#2111: https://github.com/pytest-dev/pytest/issues/2111
+.. _#478: https://github.com/pytest-dev/pytest/issues/478
+.. _#687: https://github.com/pytest-dev/pytest/issues/687
+
+
+3.0.4 (2016-11-09)
+==================
+
+* Import errors when collecting test modules now display the full traceback (`#1976`_).
+  Thanks `@cwitty`_ for the report and `@nicoddemus`_ for the PR.
+
+* Fix confusing command-line help message for custom options with two or more ``metavar`` properties (`#2004`_).
+  Thanks `@okulynyak`_ and `@davehunt`_ for the report and `@nicoddemus`_ for the PR.
+
+* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_).
+  Thanks `@nicoddemus`_ for the PR.
+
+* Fixed cyclic reference when ``pytest.raises`` is used in context-manager form (`#1965`_). Also as a
+  result of this fix, ``sys.exc_info()`` is left empty in both context-manager and function call usages.
+  Previously, ``sys.exc_info`` would contain the exception caught by the context manager,
+  even when the expected exception occurred.
+  Thanks `@MSeifert04`_ for the report and the PR.
+
+* Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but
+  were later marked explicitly by ``pytest.register_assert_rewrite``
+  or implicitly as a plugin (`#2005`_).
+  Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR.
+
+* Report teardown output on test failure (`#442`_).
+  Thanks `@matclab`_ for the PR.
+
+* Fix teardown error message in generated xUnit XML.
+  Thanks `@gdyuldin`_ for the PR.
+
+* Properly handle exceptions in ``multiprocessing`` tasks (`#1984`_).
+  Thanks `@adborden`_ for the report and `@nicoddemus`_ for the PR.
+
+* Clean up unittest TestCase objects after tests are complete (`#1649`_).
+  Thanks `@d_b_w`_ for the report and PR.
+
+
+.. _@adborden: https://github.com/adborden
+.. _@cwitty: https://github.com/cwitty
+.. _@d_b_w: https://github.com/d_b_w
+.. _@gdyuldin: https://github.com/gdyuldin
+.. _@matclab: https://github.com/matclab
+.. _@MSeifert04: https://github.com/MSeifert04
+.. _@okulynyak: https://github.com/okulynyak
+
+.. _#442: https://github.com/pytest-dev/pytest/issues/442
+.. _#1965: https://github.com/pytest-dev/pytest/issues/1965
+.. _#1976: https://github.com/pytest-dev/pytest/issues/1976
+.. _#1984: https://github.com/pytest-dev/pytest/issues/1984
+.. _#1998: https://github.com/pytest-dev/pytest/issues/1998
+.. _#2004: https://github.com/pytest-dev/pytest/issues/2004
+.. _#2005: https://github.com/pytest-dev/pytest/issues/2005
+.. _#1649: https://github.com/pytest-dev/pytest/issues/1649
+
+
+3.0.3 (2016-09-28)
+==================
+
+* The ``ids`` argument to ``parametrize`` again accepts ``unicode`` strings
+  in Python 2 (`#1905`_).
+  Thanks `@philpep`_ for the report and `@nicoddemus`_ for the PR.
+
+* Assertions are now being rewritten for plugins in development mode
+  (``pip install -e``) (`#1934`_).
+  Thanks `@nicoddemus`_ for the PR.
+
+* Fix pkg_resources import error in Jython projects (`#1853`_).
+  Thanks `@raquel-ucl`_ for the PR.
+
+* Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception
+  in Python 3 (`#1944`_).
+  Thanks `@axil`_ for the PR.
+
+* Explain a bad scope value passed to ``@fixture`` declarations or
+  a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR.
+
+* This version includes ``pluggy-0.4.0``, which correctly handles
+  ``VersionConflict`` errors in plugins (`#704`_).
+  Thanks `@nicoddemus`_ for the PR.
+
+
+.. _@philpep: https://github.com/philpep
+.. _@raquel-ucl: https://github.com/raquel-ucl
+.. _@axil: https://github.com/axil
+.. _@tgoodlet: https://github.com/tgoodlet
+.. _@vlad-dragos: https://github.com/vlad-dragos
+
+.. _#1853: https://github.com/pytest-dev/pytest/issues/1853
+.. _#1905: https://github.com/pytest-dev/pytest/issues/1905
+.. _#1934: https://github.com/pytest-dev/pytest/issues/1934
+.. _#1944: https://github.com/pytest-dev/pytest/issues/1944
+.. _#704: https://github.com/pytest-dev/pytest/issues/704
+
+
+
+
+3.0.2 (2016-09-01)
+==================
+
+* Improve error message when passing non-string ids to ``pytest.mark.parametrize`` (`#1857`_).
+  Thanks `@okken`_ for the report and `@nicoddemus`_ for the PR.
+
+* Add ``buffer`` attribute to stdin stub class ``pytest.capture.DontReadFromInput``
+  Thanks `@joguSD`_ for the PR.
+
+* Fix ``UnicodeEncodeError`` when string comparison with unicode has failed. (`#1864`_)
+  Thanks `@AiOO`_ for the PR.
+
+* ``pytest_plugins`` is now handled correctly if defined as a string (as opposed as
+  a sequence of strings) when modules are considered for assertion rewriting.
+  Due to this bug, much more modules were being rewritten than necessary
+  if a test suite uses ``pytest_plugins`` to load internal plugins (`#1888`_).
+  Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR (`#1891`_).
+
+* Do not call tearDown and cleanups when running tests from
+  ``unittest.TestCase`` subclasses with ``--pdb``
+  enabled. This allows proper post mortem debugging for all applications
+  which have significant logic in their tearDown machinery (`#1890`_). Thanks
+  `@mbyt`_ for the PR.
+
+* Fix use of deprecated ``getfuncargvalue`` method in the internal doctest plugin.
+  Thanks `@ViviCoder`_ for the report (`#1898`_).
+
+.. _@joguSD: https://github.com/joguSD
+.. _@AiOO: https://github.com/AiOO
+.. _@mbyt: https://github.com/mbyt
+.. _@ViviCoder: https://github.com/ViviCoder
+
+.. _#1857: https://github.com/pytest-dev/pytest/issues/1857
+.. _#1864: https://github.com/pytest-dev/pytest/issues/1864
+.. _#1888: https://github.com/pytest-dev/pytest/issues/1888
+.. _#1891: https://github.com/pytest-dev/pytest/pull/1891
+.. _#1890: https://github.com/pytest-dev/pytest/issues/1890
+.. _#1898: https://github.com/pytest-dev/pytest/issues/1898
+
+
+3.0.1 (2016-08-23)
+==================
+
+* Fix regression when ``importorskip`` is used at module level (`#1822`_).
+  Thanks `@jaraco`_ and `@The-Compiler`_ for the report and `@nicoddemus`_ for the PR.
+
+* Fix parametrization scope when session fixtures are used in conjunction
+  with normal parameters in the same call (`#1832`_).
+  Thanks `@The-Compiler`_ for the report, `@Kingdread`_ and `@nicoddemus`_ for the PR.
+
+* Fix internal error when parametrizing tests or fixtures using an empty ``ids`` argument (`#1849`_).
+  Thanks `@OPpuolitaival`_ for the report and `@nicoddemus`_ for the PR.
+
+* Fix loader error when running ``pytest`` embedded in a zipfile.
+  Thanks `@mbachry`_ for the PR.
+
+
+.. _@Kingdread: https://github.com/Kingdread
+.. _@mbachry: https://github.com/mbachry
+.. _@OPpuolitaival: https://github.com/OPpuolitaival
+
+.. _#1822: https://github.com/pytest-dev/pytest/issues/1822
+.. _#1832: https://github.com/pytest-dev/pytest/issues/1832
+.. _#1849: https://github.com/pytest-dev/pytest/issues/1849
+
+
+3.0.0 (2016-08-18)
+==================
+
+**Incompatible changes**
+
+
+A number of incompatible changes were made in this release, with the intent of removing features deprecated for a long
+time or change existing behaviors in order to make them less surprising/more useful.
+
+* Reinterpretation mode has now been removed.  Only plain and rewrite
+  mode are available, consequently the ``--assert=reinterp`` option is
+  no longer available.  This also means files imported from plugins or
+  ``conftest.py`` will not benefit from improved assertions by
+  default, you should use ``pytest.register_assert_rewrite()`` to
+  explicitly turn on assertion rewriting for those files.  Thanks
+  `@flub`_ for the PR.
+
+* The following deprecated commandline options were removed:
+
+  * ``--genscript``: no longer supported;
+  * ``--no-assert``: use ``--assert=plain`` instead;
+  * ``--nomagic``: use ``--assert=plain`` instead;
+  * ``--report``: use ``-r`` instead;
+
+  Thanks to `@RedBeardCode`_ for the PR (`#1664`_).
+
+* ImportErrors in plugins now are a fatal error instead of issuing a
+  pytest warning (`#1479`_). Thanks to `@The-Compiler`_ for the PR.
+
+* Removed support code for Python 3 versions < 3.3 (`#1627`_).
+
+* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
+  were never documented and a leftover from a pre-virtualenv era. These entry
+  points also created broken entry points in wheels, so removing them also
+  removes a source of confusion for users (`#1632`_).
+  Thanks `@obestwalter`_ for the PR.
+
+* ``pytest.skip()`` now raises an error when used to decorate a test function,
+  as opposed to its original intent (to imperatively skip a test inside a test function). Previously
+  this usage would cause the entire module to be skipped (`#607`_).
+  Thanks `@omarkohl`_ for the complete PR (`#1519`_).
+
+* Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C
+  anyway as soon as they see collection errors, so pytest might as well make that the default behavior (`#1421`_).
+  A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour.
+  Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_).
+
+* Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module.
+
+* Raise a helpful failure message when requesting a parametrized fixture at runtime,
+  e.g. with ``request.getfixturevalue``. Previously these parameters were simply
+  never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])``
+  only ran once (`#460`_).
+  Thanks to `@nikratio`_ for the bug report, `@RedBeardCode`_ and `@tomviner`_ for the PR.
+
+* ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch``
+  so it doesn't conflict with the ``monkeypatch`` fixture.
+
+* ``--exitfirst / -x`` can now be overridden by a following ``--maxfail=N``
+  and is just a synonym for ``--maxfail=1``.
+
+
+**New Features**
+
+* Support nose-style ``__test__`` attribute on methods of classes,
+  including unittest-style Classes. If set to ``False``, the test will not be
+  collected.
+
+* New ``doctest_namespace`` fixture for injecting names into the
+  namespace in which doctests run.
+  Thanks `@milliams`_ for the complete PR (`#1428`_).
+
+* New ``--doctest-report`` option available to change the output format of diffs
+  when running (failing) doctests (implements `#1749`_).
+  Thanks `@hartym`_ for the PR.
+
+* New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name
+  for a fixture (to solve the funcarg-shadowing-fixture problem).
+  Thanks `@novas0x2a`_ for the complete PR (`#1444`_).
+
+* New ``approx()`` function for easily comparing floating-point numbers in
+  tests.
+  Thanks `@kalekundert`_ for the complete PR (`#1441`_).
+
+* Ability to add global properties in the final xunit output file by accessing
+  the internal ``junitxml`` plugin (experimental).
+  Thanks `@tareqalayan`_ for the complete PR `#1454`_).
+
+* New ``ExceptionInfo.match()`` method to match a regular expression on the
+  string representation of an exception (`#372`_).
+  Thanks `@omarkohl`_ for the complete PR (`#1502`_).
+
+* ``__tracebackhide__`` can now also be set to a callable which then can decide
+  whether to filter the traceback based on the ``ExceptionInfo`` object passed
+  to it. Thanks `@The-Compiler`_ for the complete PR (`#1526`_).
+
+* New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide
+  friendly strings for custom types.
+  Thanks `@palaviv`_ for the PR.
+
+* ``capsys`` and ``capfd`` now have a ``disabled()`` context-manager method, which
+  can be used to temporarily disable capture within a test.
+  Thanks `@nicoddemus`_ for the PR.
+
+* New cli flag ``--fixtures-per-test``: shows which fixtures are being used
+  for each selected test item. Features doc strings of fixtures by default.
+  Can also show where fixtures are defined if combined with ``-v``.
+  Thanks `@hackebrot`_ for the PR.
+
+* Introduce ``pytest`` command as recommended entry point. Note that ``py.test``
+  still works and is not scheduled for removal. Closes proposal
+  `#1629`_. Thanks `@obestwalter`_ and `@davehunt`_ for the complete PR
+  (`#1633`_).
+
+* New cli flags:
+
+  + ``--setup-plan``: performs normal collection and reports
+    the potential setup and teardown and does not execute any fixtures and tests;
+  + ``--setup-only``: performs normal collection, executes setup and teardown of
+    fixtures and reports them;
+  + ``--setup-show``: performs normal test execution and additionally shows
+    setup and teardown of fixtures;
+  + ``--keep-duplicates``: py.test now ignores duplicated paths given in the command
+    line. To retain the previous behavior where the same test could be run multiple
+    times by specifying it in the command-line multiple times, pass the ``--keep-duplicates``
+    argument (`#1609`_);
+
+  Thanks `@d6e`_, `@kvas-it`_, `@sallner`_, `@ioggstream`_ and `@omarkohl`_ for the PRs.
+
+* New CLI flag ``--override-ini``/``-o``: overrides values from the ini file.
+  For example: ``"-o xfail_strict=True"``'.
+  Thanks `@blueyed`_ and `@fengxx`_ for the PR.
+
+* New hooks:
+
+  + ``pytest_fixture_setup(fixturedef, request)``: executes fixture setup;
+  + ``pytest_fixture_post_finalizer(fixturedef)``: called after the fixture's
+    finalizer and has access to the fixture's result cache.
+
+  Thanks `@d6e`_, `@sallner`_.
+
+* Issue warnings for asserts whose test is a tuple literal. Such asserts will
+  never fail because tuples are always truthy and are usually a mistake
+  (see `#1562`_). Thanks `@kvas-it`_, for the PR.
+
+* Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``).
+  Thanks to `@anntzer`_ for the PR.
+
+
+**Changes**
+
+* Plugins now benefit from assertion rewriting.  Thanks
+  `@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR.
+
+* Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict
+  mode and ``"failed"`` in strict mode. Thanks to `@hackebrot`_ for the PR
+  (`#1795`_) and `@gprasad84`_ for report (`#1546`_).
+
+* Tests marked with ``xfail(strict=False)`` (the default) now appear in
+  JUnitXML reports as passing tests instead of skipped.
+  Thanks to `@hackebrot`_ for the PR (`#1795`_).
+
+* Highlight path of the file location in the error report to make it easier to copy/paste.
+  Thanks `@suzaku`_ for the PR (`#1778`_).
+
+* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like
+  those marked with the ``@pytest.yield_fixture`` decorator. This change renders
+  ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements
+  the preferred way to write teardown code (`#1461`_).
+  Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR.
+
+* Explicitly passed parametrize ids do not get escaped to ascii (`#1351`_).
+  Thanks `@ceridwen`_ for the PR.
+
+* Fixtures are now sorted in the error message displayed when an unknown
+  fixture is declared in a test function.
+  Thanks `@nicoddemus`_ for the PR.
+
+* ``pytest_terminal_summary`` hook now receives the ``exitstatus``
+  of the test session as argument. Thanks `@blueyed`_ for the PR (`#1809`_).
+
+* Parametrize ids can accept ``None`` as specific test id, in which case the
+  automatically generated id for that argument will be used.
+  Thanks `@palaviv`_ for the complete PR (`#1468`_).
+
+* The parameter to xunit-style setup/teardown methods (``setup_method``,
+  ``setup_module``, etc.) is now optional and may be omitted.
+  Thanks `@okken`_ for bringing this to attention and `@nicoddemus`_ for the PR.
+
+* Improved automatic id generation selection in case of duplicate ids in
+  parametrize.
+  Thanks `@palaviv`_ for the complete PR (`#1474`_).
+
+* Now pytest warnings summary is shown up by default. Added a new flag
+  ``--disable-pytest-warnings`` to explicitly disable the warnings summary (`#1668`_).
+
+* Make ImportError during collection more explicit by reminding
+  the user to check the name of the test module/package(s) (`#1426`_).
+  Thanks `@omarkohl`_ for the complete PR (`#1520`_).
+
+* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks
+  `@mikofski`_ for the report and `@tomviner`_ for the PR (`#1544`_).
+
+* ``pytest.raises`` in the context manager form accepts a custom
+  ``message`` to raise when no exception occurred.
+  Thanks `@palaviv`_ for the complete PR (`#1616`_).
+
+* ``conftest.py`` files now benefit from assertion rewriting; previously it
+  was only available for test modules. Thanks `@flub`_, `@sober7`_ and
+  `@nicoddemus`_ for the PR (`#1619`_).
+
+* Text documents without any doctests no longer appear as "skipped".
+  Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_).
+
+* Ensure that a module within a namespace package can be found when it
+  is specified on the command line together with the ``--pyargs``
+  option.  Thanks to `@taschini`_ for the PR (`#1597`_).
+
+* Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding
+  sub-expressions that happened to be ``False``, assuming this was redundant information.
+  Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and
+  `@tomviner`_ for the PR.
+
+* ``OptionGroup.addoption()`` now checks if option names were already
+  added before, to make it easier to track down issues like `#1618`_.
+  Before, you only got exceptions later from ``argparse`` library,
+  giving no clue about the actual reason for double-added options.
+
+* ``yield``-based tests are considered deprecated and will be removed in pytest-4.0.
+  Thanks `@nicoddemus`_ for the PR.
+
+* ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
+  to avoid conflicts with other distutils commands (see `#567`_). ``[pytest]`` sections in
+  ``pytest.ini`` or ``tox.ini`` files are supported and unchanged.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be
+  removed in pytest-4.0 (`#1684`_).
+  Thanks `@nicoddemus`_ for the PR.
+
+* Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled
+  for removal in pytest-4.0. It is recommended to pass a list of arguments instead (`#1723`_).
+
+* Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is
+  still present but is now considered deprecated. Thanks to `@RedBeardCode`_ and `@tomviner`_
+  for the PR (`#1626`_).
+
+* ``optparse`` type usage now triggers DeprecationWarnings (`#1740`_).
+
+
+* ``optparse`` backward compatibility supports float/complex types (`#457`_).
+
+* Refined logic for determining the ``rootdir``, considering only valid
+  paths which fixes a number of issues: `#1594`_, `#1435`_ and `#1471`_.
+  Updated the documentation according to current behavior. Thanks to
+  `@blueyed`_, `@davehunt`_ and `@matthiasha`_ for the PR.
+
+* Always include full assertion explanation. The previous behaviour was hiding
+  sub-expressions that happened to be False, assuming this was redundant information.
+  Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and
+  `@tomviner`_ for PR.
+
+* Better message in case of not using parametrized variable (see `#1539`_).
+  Thanks to `@tramwaj29`_ for the PR.
+
+* Updated docstrings with a more uniform style.
+
+* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown.
+  Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@JonathonSonesen`_ and
+  `@tomviner`_ for the PR.
+
+* No longer display the incorrect test deselection reason (`#1372`_).
+  Thanks `@ronnypfannschmidt`_ for the PR.
+
+* The ``--resultlog`` command line option has been deprecated: it is little used
+  and there are more modern and better alternatives (see `#830`_).
+  Thanks `@nicoddemus`_ for the PR.
+
+* Improve error message with fixture lookup errors: add an 'E' to the first
+  line and '>' to the rest. Fixes `#717`_. Thanks `@blueyed`_ for reporting and
+  a PR, `@eolo999`_ for the initial PR and `@tomviner`_ for his guidance during
+  EuroPython2016 sprint.
+
+
+**Bug Fixes**
+
+* Parametrize now correctly handles duplicated test ids.
+
+* Fix internal error issue when the ``method`` argument is missing for
+  ``teardown_method()`` (`#1605`_).
+
+* Fix exception visualization in case the current working directory (CWD) gets
+  deleted during testing (`#1235`_). Thanks `@bukzor`_ for reporting. PR by
+  `@marscher`_.
+
+* Improve test output for logical expression with brackets (`#925`_).
+  Thanks `@DRMacIver`_ for reporting and `@RedBeardCode`_ for the PR.
+
+* Create correct diff for strings ending with newlines (`#1553`_).
+  Thanks `@Vogtinator`_ for reporting and `@RedBeardCode`_ and
+  `@tomviner`_ for the PR.
+
+* ``ConftestImportFailure`` now shows the traceback making it easier to
+  identify bugs in ``conftest.py`` files (`#1516`_). Thanks `@txomon`_ for
+  the PR.
+
+* Text documents without any doctests no longer appear as "skipped".
+  Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_).
+
+* Fixed collection of classes with custom ``__new__`` method.
+  Fixes `#1579`_. Thanks to `@Stranger6667`_ for the PR.
+
+* Fixed scope overriding inside metafunc.parametrize (`#634`_).
+  Thanks to `@Stranger6667`_ for the PR.
+
+* Fixed the total tests tally in junit xml output (`#1798`_).
+  Thanks to `@cryporchild`_ for the PR.
+
+* Fixed off-by-one error with lines from ``request.node.warn``.
+  Thanks to `@blueyed`_ for the PR.
+
+
+.. _#1210: https://github.com/pytest-dev/pytest/issues/1210
+.. _#1235: https://github.com/pytest-dev/pytest/issues/1235
+.. _#1351: https://github.com/pytest-dev/pytest/issues/1351
+.. _#1372: https://github.com/pytest-dev/pytest/issues/1372
+.. _#1421: https://github.com/pytest-dev/pytest/issues/1421
+.. _#1426: https://github.com/pytest-dev/pytest/issues/1426
+.. _#1428: https://github.com/pytest-dev/pytest/pull/1428
+.. _#1435: https://github.com/pytest-dev/pytest/issues/1435
+.. _#1441: https://github.com/pytest-dev/pytest/pull/1441
+.. _#1444: https://github.com/pytest-dev/pytest/pull/1444
+.. _#1454: https://github.com/pytest-dev/pytest/pull/1454
+.. _#1461: https://github.com/pytest-dev/pytest/pull/1461
+.. _#1468: https://github.com/pytest-dev/pytest/pull/1468
+.. _#1471: https://github.com/pytest-dev/pytest/issues/1471
+.. _#1474: https://github.com/pytest-dev/pytest/pull/1474
+.. _#1479: https://github.com/pytest-dev/pytest/issues/1479
+.. _#1502: https://github.com/pytest-dev/pytest/pull/1502
+.. _#1503: https://github.com/pytest-dev/pytest/issues/1503
+.. _#1516: https://github.com/pytest-dev/pytest/pull/1516
+.. _#1519: https://github.com/pytest-dev/pytest/pull/1519
+.. _#1520: https://github.com/pytest-dev/pytest/pull/1520
+.. _#1526: https://github.com/pytest-dev/pytest/pull/1526
+.. _#1539: https://github.com/pytest-dev/pytest/issues/1539
+.. _#1544: https://github.com/pytest-dev/pytest/issues/1544
+.. _#1546: https://github.com/pytest-dev/pytest/issues/1546
+.. _#1553: https://github.com/pytest-dev/pytest/issues/1553
+.. _#1562: https://github.com/pytest-dev/pytest/issues/1562
+.. _#1579: https://github.com/pytest-dev/pytest/issues/1579
+.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
+.. _#1594: https://github.com/pytest-dev/pytest/issues/1594
+.. _#1597: https://github.com/pytest-dev/pytest/pull/1597
+.. _#1605: https://github.com/pytest-dev/pytest/issues/1605
+.. _#1616: https://github.com/pytest-dev/pytest/pull/1616
+.. _#1618: https://github.com/pytest-dev/pytest/issues/1618
+.. _#1619: https://github.com/pytest-dev/pytest/issues/1619
+.. _#1626: https://github.com/pytest-dev/pytest/pull/1626
+.. _#1627: https://github.com/pytest-dev/pytest/pull/1627
+.. _#1628: https://github.com/pytest-dev/pytest/pull/1628
+.. _#1629: https://github.com/pytest-dev/pytest/issues/1629
+.. _#1632: https://github.com/pytest-dev/pytest/issues/1632
+.. _#1633: https://github.com/pytest-dev/pytest/pull/1633
+.. _#1664: https://github.com/pytest-dev/pytest/pull/1664
+.. _#1668: https://github.com/pytest-dev/pytest/issues/1668
+.. _#1684: https://github.com/pytest-dev/pytest/pull/1684
+.. _#1723: https://github.com/pytest-dev/pytest/pull/1723
+.. _#1740: https://github.com/pytest-dev/pytest/issues/1740
+.. _#1749: https://github.com/pytest-dev/pytest/issues/1749
+.. _#1778: https://github.com/pytest-dev/pytest/pull/1778
+.. _#1795: https://github.com/pytest-dev/pytest/pull/1795
+.. _#1798: https://github.com/pytest-dev/pytest/pull/1798
+.. _#1809: https://github.com/pytest-dev/pytest/pull/1809
+.. _#372: https://github.com/pytest-dev/pytest/issues/372
+.. _#457: https://github.com/pytest-dev/pytest/issues/457
+.. _#460: https://github.com/pytest-dev/pytest/pull/460
+.. _#567: https://github.com/pytest-dev/pytest/pull/567
+.. _#607: https://github.com/pytest-dev/pytest/issues/607
+.. _#634: https://github.com/pytest-dev/pytest/issues/634
+.. _#717: https://github.com/pytest-dev/pytest/issues/717
+.. _#830: https://github.com/pytest-dev/pytest/issues/830
+.. _#925: https://github.com/pytest-dev/pytest/issues/925
+
+
+.. _@anntzer: https://github.com/anntzer
+.. _@bagerard: https://github.com/bagerard
+.. _@BeyondEvil: https://github.com/BeyondEvil
+.. _@blueyed: https://github.com/blueyed
+.. _@ceridwen: https://github.com/ceridwen
+.. _@cryporchild: https://github.com/cryporchild
+.. _@csaftoiu: https://github.com/csaftoiu
+.. _@d6e: https://github.com/d6e
+.. _@davehunt: https://github.com/davehunt
+.. _@DRMacIver: https://github.com/DRMacIver
+.. _@eolo999: https://github.com/eolo999
+.. _@fengxx: https://github.com/fengxx
+.. _@flub: https://github.com/flub
+.. _@gprasad84: https://github.com/gprasad84
+.. _@graingert: https://github.com/graingert
+.. _@hartym: https://github.com/hartym
+.. _@JonathonSonesen: https://github.com/JonathonSonesen
+.. _@kalekundert: https://github.com/kalekundert
+.. _@kvas-it: https://github.com/kvas-it
+.. _@marscher: https://github.com/marscher
+.. _@mikofski: https://github.com/mikofski
+.. _@milliams: https://github.com/milliams
+.. _@nikratio: https://github.com/nikratio
+.. _@novas0x2a: https://github.com/novas0x2a
+.. _@obestwalter: https://github.com/obestwalter
+.. _@okken: https://github.com/okken
+.. _@olegpidsadnyi: https://github.com/olegpidsadnyi
+.. _@omarkohl: https://github.com/omarkohl
+.. _@palaviv: https://github.com/palaviv
+.. _@RedBeardCode: https://github.com/RedBeardCode
+.. _@sallner: https://github.com/sallner
+.. _@sober7: https://github.com/sober7
+.. _@Stranger6667: https://github.com/Stranger6667
+.. _@suzaku: https://github.com/suzaku
+.. _@tareqalayan: https://github.com/tareqalayan
+.. _@taschini: https://github.com/taschini
+.. _@tramwaj29: https://github.com/tramwaj29
+.. _@txomon: https://github.com/txomon
+.. _@Vogtinator: https://github.com/Vogtinator
+.. _@matthiasha: https://github.com/matthiasha
+
+
+2.9.2 (2016-05-31)
+==================
+
+**Bug Fixes**
+
+* fix `#510`_: skip tests where one parameterize dimension was empty
+  thanks Alex Stapleton for the Report and `@RonnyPfannschmidt`_ for the PR
+
+* Fix Xfail does not work with condition keyword argument.
+  Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_
+  for PR the (`#1524`_).
+
+* Fix win32 path issue when putting custom config file with absolute path
+  in ``pytest.main("-c your_absolute_path")``.
+
+* Fix maximum recursion depth detection when raised error class is not aware
+  of unicode/encoded bytes.
+  Thanks `@prusse-martin`_ for the PR (`#1506`_).
+
+* Fix ``pytest.mark.skip`` mark when used in strict mode.
+  Thanks `@pquentin`_ for the PR and `@RonnyPfannschmidt`_ for
+  showing how to fix the bug.
+
+* Minor improvements and fixes to the documentation.
+  Thanks `@omarkohl`_ for the PR.
+
+* Fix ``--fixtures`` to show all fixture definitions as opposed to just
+  one per fixture name.
+  Thanks to `@hackebrot`_ for the PR.
+
+.. _#510: https://github.com/pytest-dev/pytest/issues/510
+.. _#1506: https://github.com/pytest-dev/pytest/pull/1506
+.. _#1496: https://github.com/pytest-dev/pytest/issues/1496
+.. _#1524: https://github.com/pytest-dev/pytest/pull/1524
+
+.. _@prusse-martin: https://github.com/prusse-martin
+.. _@astraw38: https://github.com/astraw38
+
+
+2.9.1 (2016-03-17)
+==================
+
+**Bug Fixes**
+
+* Improve error message when a plugin fails to load.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_):
+  ``pytest.fail`` with non-ascii characters raises an internal pytest error.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs
+  contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_).
+
+* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors
+  containing non-ascii lines at the point of failure generated an internal
+  py.test error.
+  Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR.
+
+* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize
+  attempt to decode it as utf-8 ignoring errors.
+
+* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line.
+
+* Fix (`#138`_): better reporting for python 3.3+ chained exceptions
+
+.. _#1437: https://github.com/pytest-dev/pytest/issues/1437
+.. _#469: https://github.com/pytest-dev/pytest/issues/469
+.. _#1431: https://github.com/pytest-dev/pytest/pull/1431
+.. _#649: https://github.com/pytest-dev/pytest/issues/649
+.. _#138: https://github.com/pytest-dev/pytest/issues/138
+
+.. _@asottile: https://github.com/asottile
+
+
+2.9.0 (2016-02-29)
+==================
+
+**New Features**
+
+* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
+  Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_).
+
+* ``--doctest-glob`` may now be passed multiple times in the command-line.
+  Thanks `@jab`_ and `@nicoddemus`_ for the PR.
+
+* New ``-rp`` and ``-rP`` reporting options give the summary and full output
+  of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR.
+
+* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS``
+  tests to fail the test suite (defaulting to ``False``). There's also a
+  ``xfail_strict`` ini option that can be used to configure it project-wise.
+  Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_).
+
+* ``Parser.addini`` now supports options of type ``bool``.
+  Thanks `@nicoddemus`_ for the PR.
+
+* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
+  in doctest output (similar to ``ALLOW_UNICODE``).
+  Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_).
+
+* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors.
+  Fixes `#1366`_.
+  Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR.
+
+* Catch ``IndexError`` exceptions when getting exception source location.
+  Fixes a pytest internal error for dynamically generated code (fixtures and tests)
+  where source lines are fake by intention.
+
+**Changes**
+
+* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been
+  merged into the ``pytest`` repository as ``pytest._code``. This decision
+  was made because ``py.code`` had very few uses outside ``pytest`` and the
+  fact that it was in a different repository made it difficult to fix bugs on
+  its code in a timely manner. The team hopes with this to be able to better
+  refactor out and improve that code.
+  This change shouldn't affect users, but it is useful to let users aware
+  if they encounter any strange behavior.
+
+  Keep in mind that the code for ``pytest._code`` is **private** and
+  **experimental**, so you definitely should not import it explicitly!
+
+  Please note that the original ``py.code`` is still available in
+  `pylib <https://pylib.readthedocs.io>`_.
+
+* ``pytest_enter_pdb`` now optionally receives the pytest config object.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Removed code and documentation for Python 2.5 or lower versions,
+  including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
+  Thanks `@nicoddemus`_ for the PR (`#1226`_).
+
+* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
+  found in the environment, even when ``-vv`` isn't used.
+  Thanks `@The-Compiler`_ for the PR.
+
+* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and
+  ``--failed-first`` respectively.
+  Thanks `@MichaelAquilina`_ for the PR.
+
+* Added expected exceptions to ``pytest.raises`` fail message.
+
+* Collection only displays progress ("collecting X items") when in a terminal.
+  This avoids cluttering the output when using ``--color=yes`` to obtain
+  colors in CI integrations systems (`#1397`_).
+
+**Bug Fixes**
+
+* The ``-s`` and ``-c`` options should now work under ``xdist``;
+  ``Config.fromdictargs`` now represents its input much more faithfully.
+  Thanks to `@bukzor`_ for the complete PR (`#680`_).
+
+* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting.
+  Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR.
+
+* Fix formatting utf-8 explanation messages (`#1379`_).
+  Thanks `@biern`_ for the PR.
+
+* Fix `traceback style docs`_ to describe all of the available options
+  (auto/long/short/line/native/no), with ``auto`` being the default since v2.6.
+  Thanks `@hackebrot`_ for the PR.
+
+* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
+  with same name.
+
+.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
+
+.. _#1609: https://github.com/pytest-dev/pytest/issues/1609
+.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
+.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
+.. _#1366: https://github.com/pytest-dev/pytest/issues/1366
+.. _#1040: https://github.com/pytest-dev/pytest/pull/1040
+.. _#680: https://github.com/pytest-dev/pytest/issues/680
+.. _#1287: https://github.com/pytest-dev/pytest/pull/1287
+.. _#1226: https://github.com/pytest-dev/pytest/pull/1226
+.. _#1290: https://github.com/pytest-dev/pytest/pull/1290
+.. _#1355: https://github.com/pytest-dev/pytest/pull/1355
+.. _#1397: https://github.com/pytest-dev/pytest/issues/1397
+.. _@biern: https://github.com/biern
+.. _@MichaelAquilina: https://github.com/MichaelAquilina
+.. _@bukzor: https://github.com/bukzor
+.. _@hpk42: https://github.com/hpk42
+.. _@nicoddemus: https://github.com/nicoddemus
+.. _@jab: https://github.com/jab
+.. _@codewarrior0: https://github.com/codewarrior0
+.. _@jaraco: https://github.com/jaraco
+.. _@The-Compiler: https://github.com/The-Compiler
+.. _@Shinkenjoe: https://github.com/Shinkenjoe
+.. _@tomviner: https://github.com/tomviner
+.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
+.. _@rabbbit: https://github.com/rabbbit
+.. _@hackebrot: https://github.com/hackebrot
+.. _@pquentin: https://github.com/pquentin
+.. _@ioggstream: https://github.com/ioggstream
+
+2.8.7 (2016-01-24)
+==================
+
+- fix #1338: use predictable object resolution for monkeypatch
+
+2.8.6 (2016-01-21)
+==================
+
+- fix #1259: allow for double nodeids in junitxml,
+  this was a regression failing plugins combinations
+  like pytest-pep8 + pytest-flakes
+
+- Workaround for exception that occurs in pyreadline when using
+  ``--pdb`` with standard I/O capture enabled.
+  Thanks Erik M. Bray for the PR.
+
+- fix #900: Better error message in case the target of a ``monkeypatch`` call
+  raises an ``ImportError``.
+
+- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1).
+  Thanks David R. MacIver for the report and Bruno Oliveira for the PR.
+
+- fix #1223: captured stdout and stderr are now properly displayed before
+  entering pdb when ``--pdb`` is used instead of being thrown away.
+  Thanks Cal Leeming for the PR.
+
+- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now
+  properly displayed.
+  Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
+
+- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
+  Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
+
+- fix #1334: Add captured stdout to jUnit XML report on setup error.
+  Thanks Georgy Dyuldin for the PR.
+
+
+2.8.5 (2015-12-11)
+==================
+
+- fix #1243: fixed issue where class attributes injected during collection could break pytest.
+  PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help.
+
+- fix #1074: precompute junitxml chunks instead of storing the whole tree in objects
+  Thanks Bruno Oliveira for the report and Ronny Pfannschmidt for the PR
+
+- fix #1238: fix ``pytest.deprecated_call()`` receiving multiple arguments
+  (Regression introduced in 2.8.4). Thanks Alex Gaynor for the report and
+  Bruno Oliveira for the PR.
+
+
+2.8.4 (2015-12-06)
+==================
+
+- fix #1190: ``deprecated_call()`` now works when the deprecated
+  function has been already called by another test in the same
+  module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the
+  PR.
+
+- fix #1198: ``--pastebin`` option now works on Python 3. Thanks
+  Mehdy Khoshnoody for the PR.
+
+- fix #1219: ``--pastebin`` now works correctly when captured output contains
+  non-ascii characters. Thanks Bruno Oliveira for the PR.
+
+- fix #1204: another error when collecting with a nasty __getattr__().
+  Thanks Florian Bruhin for the PR.
+
+- fix the summary printed when no tests did run.
+  Thanks Florian Bruhin for the PR.
+- fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist
+
+- a number of documentation modernizations wrt good practices.
+  Thanks Bruno Oliveira for the PR.
+
+2.8.3 (2015-11-18)
+==================
+
+- fix #1169: add __name__ attribute to testcases in TestCaseFunction to
+  support the @unittest.skip decorator on functions and methods.
+  Thanks Lee Kamentsky for the PR.
+
+- fix #1035: collecting tests if test module level obj has __getattr__().
+  Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR.
+
+- fix #331: don't collect tests if their failure cannot be reported correctly
+  e.g. they are a callable instance of a class.
+
+- fix #1133: fixed internal error when filtering tracebacks where one entry
+  belongs to a file which is no longer available.
+  Thanks Bruno Oliveira for the PR.
+
+- enhancement made to highlight in red the name of the failing tests so
+  they stand out in the output.
+  Thanks Gabriel Reis for the PR.
+
+- add more talks to the documentation
+- extend documentation on the --ignore cli option
+- use pytest-runner for setuptools integration
+- minor fixes for interaction with OS X El Capitan
+  system integrity protection (thanks Florian)
+
+
+2.8.2 (2015-10-07)
+==================
+
+- fix #1085: proper handling of encoding errors when passing encoded byte
+  strings to pytest.parametrize in Python 2.
+  Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR.
+
+- fix #1087: handling SystemError when passing empty byte strings to
+  pytest.parametrize in Python 3.
+  Thanks Paul Kehrer for the report and Bruno Oliveira for the PR.
+
+- fix #995: fixed internal error when filtering tracebacks where one entry
+  was generated by an exec() statement.
+  Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
+  for contributing and Bruno Oliveira for the PR.
+
+- fix #1100 and #1057: errors when using autouse fixtures and doctest modules.
+  Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno
+  Oliveira for the PR.
+
+2.8.1 (2015-09-29)
+==================
+
+- fix #1034: Add missing nodeid on pytest_logwarning call in
+  addhook.  Thanks Simon Gomizelj for the PR.
+
+- 'deprecated_call' is now only satisfied with a DeprecationWarning or
+  PendingDeprecationWarning. Before 2.8.0, it accepted any warning, and 2.8.0
+  made it accept only DeprecationWarning (but not PendingDeprecationWarning).
+  Thanks Alex Gaynor for the issue and Eric Hunsberger for the PR.
+
+- fix issue #1073: avoid calling __getattr__ on potential plugin objects.
+  This fixes an incompatibility with pytest-django.  Thanks Andreas Pelme,
+  Bruno Oliveira and Ronny Pfannschmidt for contributing and Holger Krekel
+  for the fix.
+
+- Fix issue #704: handle versionconflict during plugin loading more
+  gracefully.  Thanks Bruno Oliveira for the PR.
+
+- Fix issue #1064: ""--junitxml" regression when used with the
+  "pytest-xdist" plugin, with test reports being assigned to the wrong tests.
+  Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
+
+- (experimental) adapt more SEMVER style versioning and change meaning of
+  master branch in git repo: "master" branch now keeps the bugfixes, changes
+  aimed for micro releases.  "features" branch will only be released
+  with minor or major pytest releases.
+
+- Fix issue #766 by removing documentation references to distutils.
+  Thanks Russel Winder.
+
+- Fix issue #1030: now byte-strings are escaped to produce item node ids
+  to make them always serializable.
+  Thanks Andy Freeland for the report and Bruno Oliveira for the PR.
+
+- Python 2: if unicode parametrized values are convertible to ascii, their
+  ascii representation is used for the node id.
+
+- Fix issue #411: Add __eq__ method to assertion comparison example.
+  Thanks Ben Webb.
+- Fix issue #653: deprecated_call can be used as context manager.
+
+- fix issue 877: properly handle assertion explanations with non-ascii repr
+  Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR.
+
+- fix issue 1029: transform errors when writing cache values into pytest-warnings
+
+2.8.0 (2015-09-18)
+==================
+
+- new ``--lf`` and ``-ff`` options to run only the last failing tests or
+  "failing tests first" from the last run.  This functionality is provided
+  through porting the formerly external pytest-cache plugin into pytest core.
+  BACKWARD INCOMPAT: if you used pytest-cache's functionality to persist
+  data between test runs be aware that we don't serialize sets anymore.
+  Thanks Ronny Pfannschmidt for most of the merging work.
+
+- "-r" option now accepts "a" to include all possible reports, similar
+  to passing "fEsxXw" explicitly (isse960).
+  Thanks Abhijeet Kasurde for the PR.
+
+- avoid python3.5 deprecation warnings by introducing version
+  specific inspection helpers, thanks Michael Droettboom.
+
+- fix issue562: @nose.tools.istest now fully respected.
+
+- fix issue934: when string comparison fails and a diff is too large to display
+  without passing -vv, still show a few lines of the diff.
+  Thanks Florian Bruhin for the report and Bruno Oliveira for the PR.
+
+- fix issue736: Fix a bug where fixture params would be discarded when combined
+  with parametrization markers.
+  Thanks to Markus Unterwaditzer for the PR.
+
+- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the
+  ``u`` prefix is stripped from unicode strings in expected doctest output. This
+  allows doctests which use unicode to run in Python 2 and 3 unchanged.
+  Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
+
+- parametrize now also generates meaningful test IDs for enum, regex and class
+  objects (as opposed to class instances).
+  Thanks to Florian Bruhin for the PR.
+
+- Add 'warns' to assert that warnings are thrown (like 'raises').
+  Thanks to Eric Hunsberger for the PR.
+
+- Fix issue683: Do not apply an already applied mark.  Thanks ojake for the PR.
+
+- Deal with capturing failures better so fewer exceptions get lost to
+  /dev/null.  Thanks David Szotten for the PR.
+
+- fix issue730: deprecate and warn about the --genscript option.
+  Thanks Ronny Pfannschmidt for the report and Christian Pommranz for the PR.
+
+- fix issue751: multiple parametrize with ids bug if it parametrizes class with
+  two or more test methods. Thanks Sergey Chipiga for reporting and Jan
+  Bednarik for PR.
+
+- fix issue82: avoid loading conftest files from setup.cfg/pytest.ini/tox.ini
+  files and upwards by default (--confcutdir can still be set to override this).
+  Thanks Bruno Oliveira for the PR.
+
+- fix issue768: docstrings found in python modules were not setting up session
+  fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
+
+- added ``tmpdir_factory``, a session-scoped fixture that can be used to create
+  directories under the base temporary directory. Previously this object was
+  installed as a ``_tmpdirhandler`` attribute of the ``config`` object, but now it
+  is part of the official API and using ``config._tmpdirhandler`` is
+  deprecated.
+  Thanks Bruno Oliveira for the PR.
+
+- fix issue808: pytest's internal assertion rewrite hook now implements the
+  optional PEP302 get_data API so tests can access data files next to them.
+  Thanks xmo-odoo for request and example and Bruno Oliveira for
+  the PR.
+
+- rootdir and inifile are now displayed during usage errors to help
+  users diagnose problems such as unexpected ini files which add
+  unknown options being picked up by pytest. Thanks to Pavel Savchenko for
+  bringing the problem to attention in #821 and Bruno Oliveira for the PR.
+
+- Summary bar now is colored yellow for warning
+  situations such as: all tests either were skipped or xpass/xfailed,
+  or no tests were run at all (this is a partial fix for issue500).
+
+- fix issue812: pytest now exits with status code 5 in situations where no
+  tests were run at all, such as the directory given in the command line does
+  not contain any tests or as result of a command line option filters
+  all out all tests (-k for example).
+  Thanks Eric Siegerman (issue812) and Bruno Oliveira for the PR.
+
+- Summary bar now is colored yellow for warning
+  situations such as: all tests either were skipped or xpass/xfailed,
+  or no tests were run at all (related to issue500).
+  Thanks Eric Siegerman.
+
+- New ``testpaths`` ini option: list of directories to search for tests
+  when executing pytest from the root directory. This can be used
+  to speed up test collection when a project has well specified directories
+  for tests, being usually more practical than configuring norecursedirs for
+  all directories that do not contain tests.
+  Thanks to Adrian for idea (#694) and Bruno Oliveira for the PR.
+
+- fix issue713: JUnit XML reports for doctest failures.
+  Thanks Punyashloka Biswal.
+
+- fix issue970: internal pytest warnings now appear as "pytest-warnings" in
+  the terminal instead of "warnings", so it is clear for users that those
+  warnings are from pytest and not from the builtin "warnings" module.
+  Thanks Bruno Oliveira.
+
+- Include setup and teardown in junitxml test durations.
+  Thanks Janne Vanhala.
+
+- fix issue735: assertion failures on debug versions of Python 3.4+
+
+- new option ``--import-mode`` to allow to change test module importing
+  behaviour to append to sys.path instead of prepending.  This better allows
+  to run test modules against installed versions of a package even if the
+  package under test has the same import root.  In this example::
+
+        testing/__init__.py
+        testing/test_pkg_under_test.py
+        pkg_under_test/
+
+  the tests will run against the installed version
+  of pkg_under_test when ``--import-mode=append`` is used whereas
+  by default they would always pick up the local version.  Thanks Holger Krekel.
+
+- pytester: add method ``TmpTestdir.delete_loaded_modules()``, and call it
+  from ``inline_run()`` to allow temporary modules to be reloaded.
+  Thanks Eduardo Schettino.
+
+- internally refactor pluginmanager API and code so that there
+  is a clear distinction between a pytest-agnostic rather simple
+  pluginmanager and the PytestPluginManager which adds a lot of
+  behaviour, among it handling of the local conftest files.
+  In terms of documented methods this is a backward compatible
+  change but it might still break 3rd party plugins which relied on
+  details like especially the pluginmanager.add_shutdown() API.
+  Thanks Holger Krekel.
+
+- pluginmanagement: introduce ``pytest.hookimpl`` and
+  ``pytest.hookspec`` decorators for setting impl/spec
+  specific parameters.  This substitutes the previous
+  now deprecated use of ``pytest.mark`` which is meant to
+  contain markers for test functions only.
+
+- write/refine docs for "writing plugins" which now have their
+  own page and are separate from the "using/installing plugins`` page.
+
+- fix issue732: properly unregister plugins from any hook calling
+  sites allowing to have temporary plugins during test execution.
+
+- deprecate and warn about ``__multicall__`` argument in hook
+  implementations.  Use the ``hookwrapper`` mechanism instead already
+  introduced with pytest-2.7.
+
+- speed up pytest's own test suite considerably by using inprocess
+  tests by default (testrun can be modified with --runpytest=subprocess
+  to create subprocesses in many places instead).  The main
+  APIs to run pytest in a test is "runpytest()" or "runpytest_subprocess"
+  and "runpytest_inprocess" if you need a particular way of running
+  the test.  In all cases you get back a RunResult but the inprocess
+  one will also have a "reprec" attribute with the recorded events/reports.
+
+- fix monkeypatch.setattr("x.y", raising=False) to actually not raise
+  if "y" is not a pre-existing attribute. Thanks Florian Bruhin.
+
+- fix issue741: make running output from testdir.run copy/pasteable
+  Thanks Bruno Oliveira.
+
+- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files.
+
+- add ``file`` and ``line`` attributes to JUnit-XML output.
+
+- fix issue890: changed extension of all documentation files from ``txt`` to
+  ``rst``. Thanks to Abhijeet for the PR.
+
+- fix issue714: add ability to apply indirect=True parameter on particular argnames.
+  Thanks Elizaveta239.
+
+- fix issue890: changed extension of all documentation files from ``txt`` to
+  ``rst``. Thanks to Abhijeet for the PR.
+
+- fix issue957: "# doctest: SKIP" option will now register doctests as SKIPPED
+  rather than PASSED.
+  Thanks Thomas Grainger for the report and Bruno Oliveira for the PR.
+
+- issue951: add new record_xml_property fixture, that supports logging
+  additional information on xml output. Thanks David Diaz for the PR.
+
+- issue949: paths after normal options (for example ``-s``, ``-v``, etc) are now
+  properly used to discover ``rootdir`` and ``ini`` files.
+  Thanks Peter Lauri for the report and Bruno Oliveira for the PR.
+
+2.7.3 (2015-09-15)
+==================
+
+- Allow 'dev', 'rc', or other non-integer version strings in ``importorskip``.
+  Thanks to Eric Hunsberger for the PR.
+
+- fix issue856: consider --color parameter in all outputs (for example
+  --fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR.
+
+- fix issue855: passing str objects as ``plugins`` argument to pytest.main
+  is now interpreted as a module name to be imported and registered as a
+  plugin, instead of silently having no effect.
+  Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
+
+- fix issue744: fix for ast.Call changes in Python 3.5+.  Thanks
+  Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and
+  Thomas Kluyver.
+
+- fix issue842: applying markers in classes no longer propagate this markers
+  to superclasses which also have markers.
+  Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
+
+- preserve warning functions after call to pytest.deprecated_call. Thanks
+  Pieter Mulder for PR.
+
+- fix issue854: autouse yield_fixtures defined as class members of
+  unittest.TestCase subclasses now work as expected.
+  Thannks xmo-odoo for the report and Bruno Oliveira for the PR.
+
+- fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the
+  fixtures declared on the first one.
+  Thanks Florian Bruhin for reporting and Bruno Oliveira for the PR.
+
+- fix issue863: skipped tests now report the correct reason when a skip/xfail
+  condition is met when using multiple markers.
+  Thanks Raphael Pierzina for reporting and Bruno Oliveira for the PR.
+
+- optimized tmpdir fixture initialization, which should make test sessions
+  faster (specially when using pytest-xdist). The only visible effect
+  is that now pytest uses a subdirectory in the $TEMP directory for all
+  directories created by this fixture (defaults to $TEMP/pytest-$USER).
+  Thanks Bruno Oliveira for the PR.
+
+2.7.2 (2015-06-23)
+==================
+
+- fix issue767: pytest.raises value attribute does not contain the exception
+  instance on Python 2.6. Thanks Eric Siegerman for providing the test
+  case and Bruno Oliveira for PR.
+
+- Automatically create directory for junitxml and results log.
+  Thanks Aron Curzon.
+
+- fix issue713: JUnit XML reports for doctest failures.
+  Thanks Punyashloka Biswal.
+
+- fix issue735: assertion failures on debug versions of Python 3.4+
+  Thanks Benjamin Peterson.
+
+- fix issue114: skipif marker reports to internal skipping plugin;
+  Thanks Floris Bruynooghe for reporting and Bruno Oliveira for the PR.
+
+- fix issue748: unittest.SkipTest reports to internal pytest unittest plugin.
+  Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR.
+
+- fix issue718: failed to create representation of sets containing unsortable
+  elements in python 2. Thanks Edison Gustavo Muenz.
+
+- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29
+  which has a refined algorithm for traceback generation.
+
+
+2.7.1 (2015-05-19)
+==================
+
+- fix issue731: do not get confused by the braces which may be present
+  and unbalanced in an object's repr while collapsing False
+  explanations.  Thanks Carl Meyer for the report and test case.
+
+- fix issue553: properly handling inspect.getsourcelines failures in
+  FixtureLookupError which would lead to an internal error,
+  obfuscating the original problem. Thanks talljosh for initial
+  diagnose/patch and Bruno Oliveira for final patch.
+
+- fix issue660: properly report scope-mismatch-access errors
+  independently from ordering of fixture arguments.  Also
+  avoid the pytest internal traceback which does not provide
+  information to the user. Thanks Holger Krekel.
+
+- streamlined and documented release process.  Also all versions
+  (in setup.py and documentation generation) are now read
+  from _pytest/__init__.py. Thanks Holger Krekel.
+
+- fixed docs to remove the notion that yield-fixtures are experimental.
+  They are here to stay :)  Thanks Bruno Oliveira.
+
+- Support building wheels by using environment markers for the
+  requirements.  Thanks Ionel Maries Cristian.
+
+- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
+  when tests raised SystemExit. Thanks Holger Krekel.
+
+- reintroduced _pytest fixture of the pytester plugin which is used
+  at least by pytest-xdist.
+
+2.7.0 (2015-03-26)
+==================
+
+- fix issue435: make reload() work when assert rewriting is active.
+  Thanks Daniel Hahler.
+
+- fix issue616: conftest.py files and their contained fixutres are now
+  properly considered for visibility, independently from the exact
+  current working directory and test arguments that are used.
+  Many thanks to Eric Siegerman and his PR235 which contains
+  systematic tests for conftest visibility and now passes.
+  This change also introduces the concept of a ``rootdir`` which
+  is printed as a new pytest header and documented in the pytest
+  customize web page.
+
+- change reporting of "diverted" tests, i.e. tests that are collected
+  in one file but actually come from another (e.g. when tests in a test class
+  come from a base class in a different file).  We now show the nodeid
+  and indicate via a postfix the other file.
+
+- add ability to set command line options by environment variable PYTEST_ADDOPTS.
+
+- added documentation on the new pytest-dev teams on bitbucket and
+  github.  See https://pytest.org/latest/contributing.html .
+  Thanks to Anatoly for pushing and initial work on this.
+
+- fix issue650: new option ``--docttest-ignore-import-errors`` which
+  will turn import errors in doctests into skips.  Thanks Charles Cloud
+  for the complete PR.
+
+- fix issue655: work around different ways that cause python2/3
+  to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
+
+- fix issue615: assertion rewriting did not correctly escape % signs
+  when formatting boolean operations, which tripped over mixing
+  booleans with modulo operators.  Thanks to Tom Viner for the report,
+  triaging and fix.
+
+- implement issue351: add ability to specify parametrize ids as a callable
+  to generate custom test ids.  Thanks Brianna Laugher for the idea and
+  implementation.
+
+- introduce and document new hookwrapper mechanism useful for plugins
+  which want to wrap the execution of certain hooks for their purposes.
+  This supersedes the undocumented ``__multicall__`` protocol which
+  pytest itself and some external plugins use.  Note that pytest-2.8
+  is scheduled to drop supporting the old ``__multicall__``
+  and only support the hookwrapper protocol.
+
+- majorly speed up invocation of plugin hooks
+
+- use hookwrapper mechanism in builtin pytest plugins.
+
+- add a doctest ini option for doctest flags, thanks Holger Peters.
+
+- add note to docs that if you want to mark a parameter and the
+  parameter is a callable, you also need to pass in a reason to disambiguate
+  it from the "decorator" case.  Thanks Tom Viner.
+
+- "python_classes" and "python_functions" options now support glob-patterns
+  for test discovery, as discussed in issue600. Thanks Ldiary Translations.
+
+- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
+
+- fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise).
+
+- On failure, the ``sys.last_value``, ``sys.last_type`` and
+  ``sys.last_traceback`` are set, so that a user can inspect the error
+  via postmortem debugging (almarklein).
+
+2.6.4 (2014-10-24)
+==================
+
+- Improve assertion failure reporting on iterables, by using ndiff and
+  pprint.
+
+- removed outdated japanese docs from source tree.
+
+- docs for "pytest_addhooks" hook.  Thanks Bruno Oliveira.
+
+- updated plugin index docs.  Thanks Bruno Oliveira.
+
+- fix issue557: with "-k" we only allow the old style "-" for negation
+  at the beginning of strings and even that is deprecated.  Use "not" instead.
+  This should allow to pick parametrized tests where "-" appeared in the parameter.
+
+- fix issue604: Escape % character in the assertion message.
+
+- fix issue620: add explanation in the --genscript target about what
+  the binary blob means. Thanks Dinu Gherman.
+
+- fix issue614: fixed pastebin support.
+
+
+- fix issue620: add explanation in the --genscript target about what
+  the binary blob means. Thanks Dinu Gherman.
+
+- fix issue614: fixed pastebin support.
+
+2.6.3 (2014-09-24)
+==================
+
+- fix issue575: xunit-xml was reporting collection errors as failures
+  instead of errors, thanks Oleg Sinyavskiy.
+
+- fix issue582: fix setuptools example, thanks Laszlo Papp and Ronny
+  Pfannschmidt.
+
+- Fix infinite recursion bug when pickling capture.EncodedFile, thanks
+  Uwe Schmitt.
+
+- fix issue589: fix bad interaction with numpy and others when showing
+  exceptions.  Check for precise "maximum recursion depth exceed" exception
+  instead of presuming any RuntimeError is that one (implemented in py
+  dep).  Thanks Charles Cloud for analysing the issue.
+
+- fix conftest related fixture visibility issue: when running with a
+  CWD outside of a test package pytest would get fixture discovery wrong.
+  Thanks to Wolfgang Schnerring for figuring out a reproducible example.
+
+- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the
+  timeout when interactively entering pdb).  Thanks Wolfgang Schnerring.
+
+- check xfail/skip also with non-python function test items. Thanks
+  Floris Bruynooghe.
+
+2.6.2 (2014-09-05)
+==================
+
+- Added function pytest.freeze_includes(), which makes it easy to embed
+  pytest into executables using tools like cx_freeze.
+  See docs for examples and rationale. Thanks Bruno Oliveira.
+
+- Improve assertion rewriting cache invalidation precision.
+
+- fixed issue561: adapt autouse fixture example for python3.
+
+- fixed issue453: assertion rewriting issue with __repr__ containing
+  "\n{", "\n}" and "\n~".
+
+- fix issue560: correctly display code if an "else:" or "finally:" is
+  followed by statements on the same line.
+
+- Fix example in monkeypatch documentation, thanks t-8ch.
+
+- fix issue572: correct tmpdir doc example for python3.
+
+- Do not mark as universal wheel because Python 2.6 is different from
+  other builds due to the extra argparse dependency.  Fixes issue566.
+  Thanks sontek.
+
+- Implement issue549: user-provided assertion messages now no longer
+  replace the py.test introspection message but are shown in addition
+  to them.
+
+2.6.1 (2014-08-07)
+==================
+
+- No longer show line numbers in the --verbose output, the output is now
+  purely the nodeid.  The line number is still shown in failure reports.
+  Thanks Floris Bruynooghe.
+
+- fix issue437 where assertion rewriting could cause pytest-xdist slaves
+  to collect different tests. Thanks Bruno Oliveira.
+
+- fix issue555: add "errors" attribute to capture-streams to satisfy
+  some distutils and possibly other code accessing sys.stdout.errors.
+
+- fix issue547 capsys/capfd also work when output capturing ("-s") is disabled.
+
+- address issue170: allow pytest.mark.xfail(...) to specify expected exceptions via
+  an optional "raises=EXC" argument where EXC can be a single exception
+  or a tuple of exception classes.  Thanks David Mohr for the complete
+  PR.
+
+- fix integration of pytest with unittest.mock.patch decorator when
+  it uses the "new" argument.  Thanks Nicolas Delaby for test and PR.
+
+- fix issue with detecting conftest files if the arguments contain
+  "::" node id specifications (copy pasted from "-v" output)
+
+- fix issue544 by only removing "@NUM" at the end of "::" separated parts
+  and if the part has an ".py" extension
+
+- don't use py.std import helper, rather import things directly.
+  Thanks Bruno Oliveira.
+
+2.6
+===
+
+- Cache exceptions from fixtures according to their scope (issue 467).
+
+- fix issue537: Avoid importing old assertion reinterpretation code by default.
+
+- fix issue364: shorten and enhance tracebacks representation by default.
+  The new "--tb=auto" option (default) will only display long tracebacks
+  for the first and last entry.  You can get the old behaviour of printing
+  all entries as long entries with "--tb=long".  Also short entries by
+  default are now printed very similarly to "--tb=native" ones.
+
+- fix issue514: teach assertion reinterpretation about private class attributes
+
+- change -v output to include full node IDs of tests.  Users can copy
+  a node ID from a test run, including line number, and use it as a
+  positional argument in order to run only a single test.
+
+- fix issue 475: fail early and comprehensible if calling
+  pytest.raises with wrong exception type.
+
+- fix issue516: tell in getting-started about current dependencies.
+
+- cleanup setup.py a bit and specify supported versions. Thanks Jurko
+  Gospodnetic for the PR.
+
+- change XPASS colour to yellow rather then red when tests are run
+  with -v.
+
+- fix issue473: work around mock putting an unbound method into a class
+  dict when double-patching.
+
+- fix issue498: if a fixture finalizer fails, make sure that
+  the fixture is still invalidated.
+
+- fix issue453: the result of the pytest_assertrepr_compare hook now gets
+  it's newlines escaped so that format_exception does not blow up.
+
+- internal new warning system: pytest will now produce warnings when
+  it detects oddities in your test collection or execution.
+  Warnings are ultimately sent to a new pytest_logwarning hook which is
+  currently only implemented by the terminal plugin which displays
+  warnings in the summary line and shows more details when -rw (report on
+  warnings) is specified.
+
+- change skips into warnings for test classes with an __init__ and
+  callables in test modules which look like a test but are not functions.
+
+- fix issue436: improved finding of initial conftest files from command
+  line arguments by using the result of parse_known_args rather than
+  the previous flaky heuristics.  Thanks Marc Abramowitz for tests
+  and initial fixing approaches in this area.
+
+- fix issue #479: properly handle nose/unittest(2) SkipTest exceptions
+  during collection/loading of test modules.  Thanks to Marc Schlaich
+  for the complete PR.
+
+- fix issue490: include pytest_load_initial_conftests in documentation
+  and improve docstring.
+
+- fix issue472: clarify that ``pytest.config.getvalue()`` cannot work
+  if it's triggered ahead of command line parsing.
+
+- merge PR123: improved integration with mock.patch decorator on tests.
+
+- fix issue412: messing with stdout/stderr FD-level streams is now
+  captured without crashes.
+
+- fix issue483: trial/py33 works now properly.  Thanks Daniel Grana for PR.
+
+- improve example for pytest integration with "python setup.py test"
+  which now has a generic "-a" or "--pytest-args" option where you
+  can pass additional options as a quoted string.  Thanks Trevor Bekolay.
+
+- simplified internal capturing mechanism and made it more robust
+  against tests or setups changing FD1/FD2, also better integrated
+  now with pytest.pdb() in single tests.
+
+- improvements to pytest's own test-suite leakage detection, courtesy of PRs
+  from Marc Abramowitz
+
+- fix issue492: avoid leak in test_writeorg.  Thanks Marc Abramowitz.
+
+- fix issue493: don't run tests in doc directory with ``python setup.py test``
+  (use tox -e doctesting for that)
+
+- fix issue486: better reporting and handling of early conftest loading failures
+
+- some cleanup and simplification of internal conftest handling.
+
+- work a bit harder to break reference cycles when catching exceptions.
+  Thanks Jurko Gospodnetic.
+
+- fix issue443: fix skip examples to use proper comparison.  Thanks Alex
+  Groenholm.
+
+- support nose-style ``__test__`` attribute on modules, classes and
+  functions, including unittest-style Classes.  If set to False, the
+  test will not be collected.
+
+- fix issue512: show "<notset>" for arguments which might not be set
+  in monkeypatch plugin.  Improves output in documentation.
+
+
+2.5.2 (2014-01-29)
+==================
+
+- fix issue409 -- better interoperate with cx_freeze by not
+  trying to import from collections.abc which causes problems
+  for py27/cx_freeze.  Thanks Wolfgang L. for reporting and tracking it down.
+
+- fixed docs and code to use "pytest" instead of "py.test" almost everywhere.
+  Thanks Jurko Gospodnetic for the complete PR.
+
+- fix issue425: mention at end of "py.test -h" that --markers
+  and --fixtures work according to specified test path (or current dir)
+
+- fix issue413: exceptions with unicode attributes are now printed
+  correctly also on python2 and with pytest-xdist runs. (the fix
+  requires py-1.4.20)
+
+- copy, cleanup and integrate py.io capture
+  from pylib 1.4.20.dev2 (rev 13d9af95547e)
+
+- address issue416: clarify docs as to conftest.py loading semantics
+
+- fix issue429: comparing byte strings with non-ascii chars in assert
+  expressions now work better.  Thanks Floris Bruynooghe.
+
+- make capfd/capsys.capture private, its unused and shouldn't be exposed
+
+
+2.5.1 (2013-12-17)
+==================
+
+- merge new documentation styling PR from Tobias Bieniek.
+
+- fix issue403: allow parametrize of multiple same-name functions within
+  a collection node.  Thanks Andreas Kloeckner and Alex Gaynor for reporting
+  and analysis.
+
+- Allow parameterized fixtures to specify the ID of the parameters by
+  adding an ids argument to pytest.fixture() and pytest.yield_fixture().
+  Thanks Floris Bruynooghe.
+
+- fix issue404 by always using the binary xml escape in the junitxml
+  plugin.  Thanks Ronny Pfannschmidt.
+
+- fix issue407: fix addoption docstring to point to argparse instead of
+  optparse. Thanks Daniel D. Wright.
+
+
+
+2.5.0 (2013-12-12)
+==================
+
+- dropped python2.5 from automated release testing of pytest itself
+  which means it's probably going to break soon (but still works
+  with this release we believe).
+
+- simplified and fixed implementation for calling finalizers when
+  parametrized fixtures or function arguments are involved.  finalization
+  is now performed lazily at setup time instead of in the "teardown phase".
+  While this might sound odd at first, it helps to ensure that we are
+  correctly handling setup/teardown even in complex code.  User-level code
+  should not be affected unless it's implementing the pytest_runtest_teardown
+  hook and expecting certain fixture instances are torn down within (very
+  unlikely and would have been unreliable anyway).
+
+- PR90: add --color=yes|no|auto option to force terminal coloring
+  mode ("auto" is default).  Thanks Marc Abramowitz.
+
+- fix issue319 - correctly show unicode in assertion errors.  Many
+  thanks to Floris Bruynooghe for the complete PR.  Also means
+  we depend on py>=1.4.19 now.
+
+- fix issue396 - correctly sort and finalize class-scoped parametrized
+  tests independently from number of methods on the class.
+
+- refix issue323 in a better way -- parametrization should now never
+  cause Runtime Recursion errors because the underlying algorithm
+  for re-ordering tests per-scope/per-fixture is not recursive
+  anymore (it was tail-call recursive before which could lead
+  to problems for more than >966 non-function scoped parameters).
+
+- fix issue290 - there is preliminary support now for parametrizing
+  with repeated same values (sometimes useful to test if calling
+  a second time works as with the first time).
+
+- close issue240 - document precisely how pytest module importing
+  works, discuss the two common test directory layouts, and how it
+  interacts with PEP420-namespace packages.
+
+- fix issue246 fix finalizer order to be LIFO on independent fixtures
+  depending on a parametrized higher-than-function scoped fixture.
+  (was quite some effort so please bear with the complexity of this sentence :)
+  Thanks Ralph Schmitt for the precise failure example.
+
+- fix issue244 by implementing special index for parameters to only use
+  indices for paramentrized test ids
+
+- fix issue287 by running all finalizers but saving the exception
+  from the first failing finalizer and re-raising it so teardown will
+  still have failed.  We reraise the first failing exception because
+  it might be the cause for other finalizers to fail.
+
+- fix ordering when mock.patch or other standard decorator-wrappings
+  are used with test methods.  This fixues issue346 and should
+  help with random "xdist" collection failures.  Thanks to
+  Ronny Pfannschmidt and Donald Stufft for helping to isolate it.
+
+- fix issue357 - special case "-k" expressions to allow for
+  filtering with simple strings that are not valid python expressions.
+  Examples: "-k 1.3" matches all tests parametrized with 1.3.
+  "-k None" filters all tests that have "None" in their name
+  and conversely "-k 'not None'".
+  Previously these examples would raise syntax errors.
+
+- fix issue384 by removing the trial support code
+  since the unittest compat enhancements allow
+  trial to handle it on its own
+
+- don't hide an ImportError when importing a plugin produces one.
+  fixes issue375.
+
+- fix issue275 - allow usefixtures and autouse fixtures
+  for running doctest text files.
+
+- fix issue380 by making --resultlog only rely on longrepr instead
+  of the "reprcrash" attribute which only exists sometimes.
+
+- address issue122: allow @pytest.fixture(params=iterator) by exploding
+  into a list early on.
+
+- fix pexpect-3.0 compatibility for pytest's own tests.
+  (fixes issue386)
+
+- allow nested parametrize-value markers, thanks James Lan for the PR.
+
+- fix unicode handling with new monkeypatch.setattr(import_path, value)
+  API.  Thanks Rob Dennis.  Fixes issue371.
+
+- fix unicode handling with junitxml, fixes issue368.
+
+- In assertion rewriting mode on Python 2, fix the detection of coding
+  cookies. See issue #330.
+
+- make "--runxfail" turn imperative pytest.xfail calls into no ops
+  (it already did neutralize pytest.mark.xfail markers)
+
+- refine pytest / pkg_resources interactions: The AssertionRewritingHook
+  PEP302 compliant loader now registers itself with setuptools/pkg_resources
+  properly so that the pkg_resources.resource_stream method works properly.
+  Fixes issue366.  Thanks for the investigations and full PR to Jason R. Coombs.
+
+- pytestconfig fixture is now session-scoped as it is the same object during the
+  whole test run.  Fixes issue370.
+
+- avoid one surprising case of marker malfunction/confusion::
+
+      @pytest.mark.some(lambda arg: ...)
+      def test_function():
+
+  would not work correctly because pytest assumes @pytest.mark.some
+  gets a function to be decorated already.  We now at least detect if this
+  arg is an lambda and thus the example will work.  Thanks Alex Gaynor
+  for bringing it up.
+
+- xfail a test on pypy that checks wrong encoding/ascii (pypy does
+  not error out). fixes issue385.
+
+- internally make varnames() deal with classes's __init__,
+  although it's not needed by pytest itself atm.  Also
+  fix caching.  Fixes issue376.
+
+- fix issue221 - handle importing of namespace-package with no
+  __init__.py properly.
+
+- refactor internal FixtureRequest handling to avoid monkeypatching.
+  One of the positive user-facing effects is that the "request" object
+  can now be used in closures.
+
+- fixed version comparison in pytest.importskip(modname, minverstring)
+
+- fix issue377 by clarifying in the nose-compat docs that pytest
+  does not duplicate the unittest-API into the "plain" namespace.
+
+- fix verbose reporting for @mock'd test functions
+
+2.4.2 (2013-10-04)
+==================
+
+- on Windows require colorama and a newer py lib so that py.io.TerminalWriter()
+  now uses colorama instead of its own ctypes hacks. (fixes issue365)
+  thanks Paul Moore for bringing it up.
+
+- fix "-k" matching of tests where "repr" and "attr" and other names would
+  cause wrong matches because of an internal implementation quirk
+  (don't ask) which is now properly implemented. fixes issue345.
+
+- avoid tmpdir fixture to create too long filenames especially
+  when parametrization is used (issue354)
+
+- fix pytest-pep8 and pytest-flakes / pytest interactions
+  (collection names in mark plugin was assuming an item always
+  has a function which is not true for those plugins etc.)
+  Thanks Andi Zeidler.
+
+- introduce node.get_marker/node.add_marker API for plugins
+  like pytest-pep8 and pytest-flakes to avoid the messy
+  details of the node.keywords  pseudo-dicts.  Adapted
+  docs.
+
+- remove attempt to "dup" stdout at startup as it's icky.
+  the normal capturing should catch enough possibilities
+  of tests messing up standard FDs.
+
+- add pluginmanager.do_configure(config) as a link to
+  config.do_configure() for plugin-compatibility
+
+2.4.1 (2013-10-02)
+==================
+
+- When using parser.addoption() unicode arguments to the
+  "type" keyword should also be converted to the respective types.
+  thanks Floris Bruynooghe, @dnozay. (fixes issue360 and issue362)
+
+- fix dotted filename completion when using argcomplete
+  thanks Anthon van der Neuth. (fixes issue361)
+
+- fix regression when a 1-tuple ("arg",) is used for specifying
+  parametrization (the values of the parametrization were passed
+  nested in a tuple).  Thanks Donald Stufft.
+
+- merge doc typo fixes, thanks Andy Dirnberger
+
+2.4
+===
+
+known incompatibilities:
+
+- if calling --genscript from python2.7 or above, you only get a
+  standalone script which works on python2.7 or above.  Use Python2.6
+  to also get a python2.5 compatible version.
+
+- all xunit-style teardown methods (nose-style, pytest-style,
+  unittest-style) will not be called if the corresponding setup method failed,
+  see issue322 below.
+
+- the pytest_plugin_unregister hook wasn't ever properly called
+  and there is no known implementation of the hook - so it got removed.
+
+- pytest.fixture-decorated functions cannot be generators (i.e. use
+  yield) anymore.  This change might be reversed in 2.4.1 if it causes
+  unforeseen real-life issues.  However, you can always write and return
+  an inner function/generator and change the fixture consumer to iterate
+  over the returned generator.  This change was done in lieu of the new
+  ``pytest.yield_fixture`` decorator, see below.
+
+new features:
+
+- experimentally introduce a new ``pytest.yield_fixture`` decorator
+  which accepts exactly the same parameters as pytest.fixture but
+  mandates a ``yield`` statement instead of a ``return statement`` from
+  fixture functions.  This allows direct integration with "with-style"
+  context managers in fixture functions and generally avoids registering
+  of finalization callbacks in favour of treating the "after-yield" as
+  teardown code.  Thanks Andreas Pelme, Vladimir Keleshev, Floris
+  Bruynooghe, Ronny Pfannschmidt and many others for discussions.
+
+- allow boolean expression directly with skipif/xfail
+  if a "reason" is also specified.  Rework skipping documentation
+  to recommend "condition as booleans" because it prevents surprises
+  when importing markers between modules.  Specifying conditions
+  as strings will remain fully supported.
+
+- reporting: color the last line red or green depending if
+  failures/errors occurred or everything passed.  thanks Christian
+  Theunert.
+
+- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no
+  "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut.
+
+- fix issue181: --pdb now also works on collect errors (and
+  on internal errors) .  This was implemented by a slight internal
+  refactoring and the introduction of a new hook
+  ``pytest_exception_interact`` hook (see next item).
+
+- fix issue341: introduce new experimental hook for IDEs/terminals to
+  intercept debugging: ``pytest_exception_interact(node, call, report)``.
+
+- new monkeypatch.setattr() variant to provide a shorter
+  invocation for patching out classes/functions from modules:
+
+     monkeypatch.setattr("requests.get", myfunc)
+
+  will replace the "get" function of the "requests" module with ``myfunc``.
+
+- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
+  Mathieu Agopian for the initial fix.  Also make all of pytest/nose
+  finalizer mimic the same generic behaviour: if a setupX exists and
+  fails, don't run teardownX.  This internally introduces a new method
+  "node.addfinalizer()" helper which can only be called during the setup
+  phase of a node.
+
+- simplify pytest.mark.parametrize() signature: allow to pass a
+  CSV-separated string to specify argnames.  For example:
+  ``pytest.mark.parametrize("input,expected",  [(1,2), (2,3)])``
+  works as well as the previous:
+  ``pytest.mark.parametrize(("input", "expected"), ...)``.
+
+- add support for setUpModule/tearDownModule detection, thanks Brian Okken.
+
+- integrate tab-completion on options through use of "argcomplete".
+  Thanks Anthon van der Neut for the PR.
+
+- change option names to be hyphen-separated long options but keep the
+  old spelling backward compatible.  py.test -h will only show the
+  hyphenated version, for example "--collect-only" but "--collectonly"
+  will remain valid as well (for backward-compat reasons).  Many thanks to
+  Anthon van der Neut for the implementation and to Hynek Schlawack for
+  pushing us.
+
+- fix issue 308 - allow to mark/xfail/skip individual parameter sets
+  when parametrizing.  Thanks Brianna Laugher.
+
+- call new experimental pytest_load_initial_conftests hook to allow
+  3rd party plugins to do something before a conftest is loaded.
+
+Bug fixes:
+
+- fix issue358 - capturing options are now parsed more properly
+  by using a new parser.parse_known_args method.
+
+- pytest now uses argparse instead of optparse (thanks Anthon) which
+  means that "argparse" is added as a dependency if installing into python2.6
+  environments or below.
+
+- fix issue333: fix a case of bad unittest/pytest hook interaction.
+
+- PR27: correctly handle nose.SkipTest during collection.  Thanks
+  Antonio Cuni, Ronny Pfannschmidt.
+
+- fix issue355: junitxml puts name="pytest" attribute to testsuite tag.
+
+- fix issue336: autouse fixture in plugins should work again.
+
+- fix issue279: improve object comparisons on assertion failure
+  for standard datatypes and recognise collections.abc.  Thanks to
+  Brianna Laugher and Mathieu Agopian.
+
+- fix issue317: assertion rewriter support for the is_package method
+
+- fix issue335: document py.code.ExceptionInfo() object returned
+  from pytest.raises(), thanks Mathieu Agopian.
+
+- remove implicit distribute_setup support from setup.py.
+
+- fix issue305: ignore any problems when writing pyc files.
+
+- SO-17664702: call fixture finalizers even if the fixture function
+  partially failed (finalizers would not always be called before)
+
+- fix issue320 - fix class scope for fixtures when mixed with
+  module-level functions.  Thanks Anatloy Bubenkoff.
+
+- you can specify "-q" or "-qq" to get different levels of "quieter"
+  reporting (thanks Katarzyna Jachim)
+
+- fix issue300 - Fix order of conftest loading when starting py.test
+  in a subdirectory.
+
+- fix issue323 - sorting of many module-scoped arg parametrizations
+
+- make sessionfinish hooks execute with the same cwd-context as at
+  session start (helps fix plugin behaviour which write output files
+  with relative path such as pytest-cov)
+
+- fix issue316 - properly reference collection hooks in docs
+
+- fix issue 306 - cleanup of -k/-m options to only match markers/test
+  names/keywords respectively.  Thanks Wouter van Ackooy.
+
+- improved doctest counting for doctests in python modules --
+  files without any doctest items will not show up anymore
+  and doctest examples are counted as separate test items.
+  thanks Danilo Bellini.
+
+- fix issue245 by depending on the released py-1.4.14
+  which fixes py.io.dupfile to work with files with no
+  mode. Thanks Jason R. Coombs.
+
+- fix junitxml generation when test output contains control characters,
+  addressing issue267, thanks Jaap Broekhuizen
+
+- fix issue338: honor --tb style for setup/teardown errors as well.  Thanks Maho.
+
+- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin.
+
+- better parametrize error messages, thanks Brianna Laugher
+
+- pytest_terminal_summary(terminalreporter) hooks can now use
+  ".section(title)" and ".line(msg)" methods to print extra
+  information at the end of a test run.
+
+2.3.5 (2013-04-30)
+==================
+
+- fix issue169: respect --tb=style with setup/teardown errors as well.
+
+- never consider a fixture function for test function collection
+
+- allow re-running of test items / helps to fix pytest-reruntests plugin
+  and also help to keep less fixture/resource references alive
+
+- put captured stdout/stderr into junitxml output even for passing tests
+  (thanks Adam Goucher)
+
+- Issue 265 - integrate nose setup/teardown with setupstate
+  so it doesn't try to teardown if it did not setup
+
+- issue 271 - don't write junitxml on slave nodes
+
+- Issue 274 - don't try to show full doctest example
+  when doctest does not know the example location
+
+- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
+
+- inject "getfixture()" helper to retrieve fixtures from doctests,
+  thanks Andreas Zeidler
+
+- issue 259 - when assertion rewriting, be consistent with the default
+  source encoding of ASCII on Python 2
+
+- issue 251 - report a skip instead of ignoring classes with init
+
+- issue250 unicode/str mixes in parametrization names and values now works
+
+- issue257, assertion-triggered compilation of source ending in a
+  comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6)
+
+- fix --genscript option to generate standalone scripts that also
+  work with python3.3 (importer ordering)
+
+- issue171 - in assertion rewriting, show the repr of some
+  global variables
+
+- fix option help for "-k"
+
+- move long description of distribution into README.rst
+
+- improve docstring for metafunc.parametrize()
+
+- fix bug where using capsys with pytest.set_trace() in a test
+  function would break when looking at capsys.readouterr()
+
+- allow to specify prefixes starting with "_" when
+  customizing python_functions test discovery. (thanks Graham Horler)
+
+- improve PYTEST_DEBUG tracing output by putting
+  extra data on a new lines with additional indent
+
+- ensure OutcomeExceptions like skip/fail have initialized exception attributes
+
+- issue 260 - don't use nose special setup on plain unittest cases
+
+- fix issue134 - print the collect errors that prevent running specified test items
+
+- fix issue266 - accept unicode in MarkEvaluator expressions
+
+2.3.4 (2012-11-20)
+==================
+
+- yielded test functions will now have autouse-fixtures active but
+  cannot accept fixtures as funcargs - it's anyway recommended to
+  rather use the post-2.0 parametrize features instead of yield, see:
+  http://pytest.org/latest/example/parametrize.html
+- fix autouse-issue where autouse-fixtures would not be discovered
+  if defined in a a/conftest.py file and tests in a/tests/test_some.py
+- fix issue226 - LIFO ordering for fixture teardowns
+- fix issue224 - invocations with >256 char arguments now work
+- fix issue91 - add/discuss package/directory level setups in example
+- allow to dynamically define markers via
+  item.keywords[...]=assignment integrating with "-m" option
+- make "-k" accept an expressions the same as with "-m" so that one
+  can write: -k "name1 or name2" etc.  This is a slight incompatibility
+  if you used special syntax like "TestClass.test_method" which you now
+  need to write as -k "TestClass and test_method" to match a certain
+  method in a certain test class.
+
+2.3.3 (2012-11-06)
+==================
+
+- fix issue214 - parse modules that contain special objects like e. g.
+  flask's request object which blows up on getattr access if no request
+  is active. thanks Thomas Waldmann.
+
+- fix issue213 - allow to parametrize with values like numpy arrays that
+  do not support an __eq__ operator
+
+- fix issue215 - split test_python.org into multiple files
+
+- fix issue148 - @unittest.skip on classes is now recognized and avoids
+  calling setUpClass/tearDownClass, thanks Pavel Repin
+
+- fix issue209 - reintroduce python2.4 support by depending on newer
+  pylib which re-introduced statement-finding for pre-AST interpreters
+
+- nose support: only call setup if it's a callable, thanks Andrew
+  Taumoefolau
+
+- fix issue219 - add py2.4-3.3 classifiers to TROVE list
+
+- in tracebacks *,** arg values are now shown next to normal arguments
+  (thanks Manuel Jacob)
+
+- fix issue217 - support mock.patch with pytest's fixtures - note that
+  you need either mock-1.0.1 or the python3.3 builtin unittest.mock.
+
+- fix issue127 - improve documentation for pytest_addoption() and
+  add a ``config.getoption(name)`` helper function for consistency.
+
+2.3.2 (2012-10-25)
+==================
+
+- fix issue208 and fix issue29 use new py version to avoid long pauses
+  when printing tracebacks in long modules
+
+- fix issue205 - conftests in subdirs customizing
+  pytest_pycollect_makemodule and pytest_pycollect_makeitem
+  now work properly
+
+- fix teardown-ordering for parametrized setups
+
+- fix issue127 - better documentation for pytest_addoption
+  and related objects.
+
+- fix unittest behaviour: TestCase.runtest only called if there are
+  test methods defined
+
+- improve trial support: don't collect its empty
+  unittest.TestCase.runTest() method
+
+- "python setup.py test" now works with pytest itself
+
+- fix/improve internal/packaging related bits:
+
+  - exception message check of test_nose.py now passes on python33 as well
+
+  - issue206 - fix test_assertrewrite.py to work when a global
+    PYTHONDONTWRITEBYTECODE=1 is present
+
+  - add tox.ini to pytest distribution so that ignore-dirs and others config
+    bits are properly distributed for maintainers who run pytest-own tests
+
+2.3.1 (2012-10-20)
+==================
+
+- fix issue202 - fix regression: using "self" from fixture functions now
+  works as expected (it's the same "self" instance that a test method
+  which uses the fixture sees)
+
+- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems
+  due to pexpect not supporting it properly (hanging)
+
+- link to web pages from --markers output which provides help for
+  pytest.mark.* usage.
+
+2.3.0 (2012-10-19)
+==================
+
+- fix issue202 - better automatic names for parametrized test functions
+- fix issue139 - introduce @pytest.fixture which allows direct scoping
+  and parametrization of funcarg factories.
+- fix issue198 - conftest fixtures were not found on windows32 in some
+  circumstances with nested directory structures due to path manipulation issues
+- fix issue193 skip test functions with were parametrized with empty
+  parameter sets
+- fix python3.3 compat, mostly reporting bits that previously depended
+  on dict ordering
+- introduce re-ordering of tests by resource and parametrization setup
+  which takes precedence to the usual file-ordering
+- fix issue185 monkeypatching time.time does not cause pytest to fail
+- fix issue172 duplicate call of pytest.fixture decoratored setup_module
+  functions
+- fix junitxml=path construction so that if tests change the
+  current working directory and the path is a relative path
+  it is constructed correctly from the original current working dir.
+- fix "python setup.py test" example to cause a proper "errno" return
+- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
+- catch unicode-issues when writing failure representations
+  to terminal to prevent the whole session from crashing
+- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
+  will now take precedence before xfail-markers because we
+  can't determine xfail/xpass status in case of a skip. see also:
+  http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
+
+- always report installed 3rd party plugins in the header of a test run
+
+- fix issue160: a failing setup of an xfail-marked tests should
+  be reported as xfail (not xpass)
+
+- fix issue128: show captured output when capsys/capfd are used
+
+- fix issue179: properly show the dependency chain of factories
+
+- pluginmanager.register(...) now raises ValueError if the
+  plugin has been already registered or the name is taken
+
+- fix issue159: improve http://pytest.org/latest/faq.html
+  especially with respect to the "magic" history, also mention
+  pytest-django, trial and unittest integration.
+
+- make request.keywords and node.keywords writable.  All descendant
+  collection nodes will see keyword values.  Keywords are dictionaries
+  containing markers and other info.
+
+- fix issue 178: xml binary escapes are now wrapped in py.xml.raw
+
+- fix issue 176: correctly catch the builtin AssertionError
+  even when we replaced AssertionError with a subclass on the
+  python level
+
+- factory discovery no longer fails with magic global callables
+  that provide no sane __code__ object (mock.call for example)
+
+- fix issue 182: testdir.inprocess_run now considers passed plugins
+
+- fix issue 188: ensure sys.exc_info is clear on python2
+                 before calling into a test
+
+- fix issue 191: add unittest TestCase runTest method support
+- fix issue 156: monkeypatch correctly handles class level descriptors
+
+- reporting refinements:
+
+  - pytest_report_header now receives a "startdir" so that
+    you can use startdir.bestrelpath(yourpath) to show
+    nice relative path
+
+  - allow plugins to implement both pytest_report_header and
+    pytest_sessionstart (sessionstart is invoked first).
+
+  - don't show deselected reason line if there is none
+
+  - py.test -vv will show all of assert comparisons instead of truncating
+
+2.2.4 (2012-05-22)
+==================
+
+- fix error message for rewritten assertions involving the % operator
+- fix issue 126: correctly match all invalid xml characters for junitxml
+  binary escape
+- fix issue with unittest: now @unittest.expectedFailure markers should
+  be processed correctly (you can also use @pytest.mark markers)
+- document integration with the extended distribute/setuptools test commands
+- fix issue 140: properly get the real functions
+  of bound classmethods for setup/teardown_class
+- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
+- fix issue #143: call unconfigure/sessionfinish always when
+  configure/sessionstart where called
+- fix issue #144: better mangle test ids to junitxml classnames
+- upgrade distribute_setup.py to 0.6.27
+
+2.2.3 (2012-02-05)
+==================
+
+- fix uploaded package to only include necessary files
+
+2.2.2 (2012-02-05)
+==================
+
+- fix issue101: wrong args to unittest.TestCase test function now
+  produce better output
+- fix issue102: report more useful errors and hints for when a
+  test directory was renamed and some pyc/__pycache__ remain
+- fix issue106: allow parametrize to be applied multiple times
+  e.g. from module, class and at function level.
+- fix issue107: actually perform session scope finalization
+- don't check in parametrize if indirect parameters are funcarg names
+- add chdir method to monkeypatch funcarg
+- fix crash resulting from calling monkeypatch undo a second time
+- fix issue115: make --collectonly robust against early failure
+  (missing files/directories)
+- "-qq --collectonly" now shows only files and the number of tests in them
+- "-q --collectonly" now shows test ids
+- allow adding of attributes to test reports such that it also works
+  with distributed testing (no upgrade of pytest-xdist needed)
+
+2.2.1 (2011-12-16)
+==================
+
+- fix issue99 (in pytest and py) internallerrors with resultlog now
+  produce better output - fixed by normalizing pytest_internalerror
+  input arguments.
+- fix issue97 / traceback issues (in pytest and py) improve traceback output
+  in conjunction with jinja2 and cython which hack tracebacks
+- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
+  the final test in a test node will now run its teardown directly
+  instead of waiting for the end of the session. Thanks Dave Hunt for
+  the good reporting and feedback.  The pytest_runtest_protocol as well
+  as the pytest_runtest_teardown hooks now have "nextitem" available
+  which will be None indicating the end of the test run.
+- fix collection crash due to unknown-source collected items, thanks
+  to Ralf Schmitt (fixed by depending on a more recent pylib)
+
+2.2.0 (2011-11-18)
+==================
+
+- fix issue90: introduce eager tearing down of test items so that
+  teardown function are called earlier.
+- add an all-powerful metafunc.parametrize function which allows to
+  parametrize test function arguments in multiple steps and therefore
+  from independent plugins and places.
+- add a @pytest.mark.parametrize helper which allows to easily
+  call a test function with different argument values
+- Add examples to the "parametrize" example page, including a quick port
+  of Test scenarios and the new parametrize function and decorator.
+- introduce registration for "pytest.mark.*" helpers via ini-files
+  or through plugin hooks.  Also introduce a "--strict" option which
+  will treat unregistered markers as errors
+  allowing to avoid typos and maintain a well described set of markers
+  for your test suite.  See exaples at http://pytest.org/latest/mark.html
+  and its links.
+- issue50: introduce "-m marker" option to select tests based on markers
+  (this is a stricter and more predictable version of '-k' in that "-m"
+  only matches complete markers and has more obvious rules for and/or
+  semantics.
+- new feature to help optimizing the speed of your tests:
+  --durations=N option for displaying N slowest test calls
+  and setup/teardown methods.
+- fix issue87: --pastebin now works with python3
+- fix issue89: --pdb with unexpected exceptions in doctest work more sensibly
+- fix and cleanup pytest's own test suite to not leak FDs
+- fix issue83: link to generated funcarg list
+- fix issue74: pyarg module names are now checked against imp.find_module false positives
+- fix compatibility with twisted/trial-11.1.0 use cases
+- simplify Node.listchain
+- simplify junitxml output code by relying on py.xml
+- add support for skip properties on unittest classes and functions
+
+2.1.3 (2011-10-18)
+==================
+
+- fix issue79: assertion rewriting failed on some comparisons in boolops
+- correctly handle zero length arguments (a la pytest '')
+- fix issue67 / junitxml now contains correct test durations, thanks ronny
+- fix issue75 / skipping test failure on jython
+- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
+
+2.1.2 (2011-09-24)
+==================
+
+- fix assertion rewriting on files with windows newlines on some Python versions
+- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
+- fix issue69 / assertion rewriting fixed on some boolean operations
+- fix issue68 / packages now work with assertion rewriting
+- fix issue66: use different assertion rewriting caches when the -O option is passed
+- don't try assertion rewriting on Jython, use reinterp
+
+2.1.1
+=====
+
+- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
+- fix issue60 / fix error conditions involving the creation of __pycache__
+- fix issue63 / assertion rewriting on inserts involving strings containing '%'
+- fix assertion rewriting on calls with a ** arg
+- don't cache rewritten modules if bytecode generation is disabled
+- fix assertion rewriting in read-only directories
+- fix issue59: provide system-out/err tags for junitxml output
+- fix issue61: assertion rewriting on boolean operations with 3 or more operands
+- you can now build a man page with "cd doc ; make man"
+
+2.1.0 (2011-07-09)
+==================
+
+- fix issue53 call nosestyle setup functions with correct ordering
+- fix issue58 and issue59: new assertion code fixes
+- merge Benjamin's assertionrewrite branch: now assertions
+  for test modules on python 2.6 and above are done by rewriting
+  the AST and saving the pyc file before the test module is imported.
+  see doc/assert.txt for more info.
+- fix issue43: improve doctests with better traceback reporting on
+  unexpected exceptions
+- fix issue47: timing output in junitxml for test cases is now correct
+- fix issue48: typo in MarkInfo repr leading to exception
+- fix issue49: avoid confusing error when initizaliation partially fails
+- fix issue44: env/username expansion for junitxml file path
+- show releaselevel information in test runs for pypy
+- reworked doc pages for better navigation and PDF generation
+- report KeyboardInterrupt even if interrupted during session startup
+- fix issue 35 - provide PDF doc version and download link from index page
+
+2.0.3 (2011-05-11)
+==================
+
+- fix issue38: nicer tracebacks on calls to hooks, particularly early
+  configure/sessionstart ones
+
+- fix missing skip reason/meta information in junitxml files, reported
+  via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html
+
+- fix issue34: avoid collection failure with "test" prefixed classes
+  deriving from object.
+
+- don't require zlib (and other libs) for genscript plugin without
+  --genscript actually being used.
+
+- speed up skips (by not doing a full traceback representation
+  internally)
+
+- fix issue37: avoid invalid characters in junitxml's output
+
+2.0.2 (2011-03-09)
+==================
+
+- tackle issue32 - speed up test runs of very quick test functions
+  by reducing the relative overhead
+
+- fix issue30 - extended xfail/skipif handling and improved reporting.
+  If you have a syntax error in your skip/xfail
+  expressions you now get nice error reports.
+
+  Also you can now access module globals from xfail/skipif
+  expressions so that this for example works now::
+
+    import pytest
+    import mymodule
+    @pytest.mark.skipif("mymodule.__version__[0] == "1")
+    def test_function():
+        pass
+
+  This will not run the test function if the module's version string
+  does not start with a "1".  Note that specifying a string instead
+  of a boolean expressions allows py.test to report meaningful information
+  when summarizing a test run as to what conditions lead to skipping
+  (or xfail-ing) tests.
+
+- fix issue28 - setup_method and pytest_generate_tests work together
+  The setup_method fixture method now gets called also for
+  test function invocations generated from the pytest_generate_tests
+  hook.
+
+- fix issue27 - collectonly and keyword-selection (-k) now work together
+  Also, if you do "py.test --collectonly -q" you now get a flat list
+  of test ids that you can use to paste to the py.test commandline
+  in order to execute a particular test.
+
+- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
+
+- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
+  Starting with Python3.2 os.symlink may be supported. By requiring
+  a newer py lib version the py.path.local() implementation acknowledges
+  this.
+
+- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
+  thanks to Laura Creighton who also reviewed parts of the documentation.
+
+- fix slightly wrong output of verbose progress reporting for classes
+  (thanks Amaury)
+
+- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
+
+- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
+
+2.0.1 (2011-02-07)
+==================
+
+- refine and unify initial capturing so that it works nicely
+  even if the logging module is used on an early-loaded conftest.py
+  file or plugin.
+- allow to omit "()" in test ids to allow for uniform test ids
+  as produced by Alfredo's nice pytest.vim plugin.
+- fix issue12 - show plugin versions with "--version" and
+  "--traceconfig" and also document how to add extra information
+  to reporting test header
+- fix issue17 (import-* reporting issue on python3) by
+  requiring py>1.4.0 (1.4.1 is going to include it)
+- fix issue10 (numpy arrays truth checking) by refining
+  assertion interpretation in py lib
+- fix issue15: make nose compatibility tests compatible
+  with python3 (now that nose-1.0 supports python3)
+- remove somewhat surprising "same-conftest" detection because
+  it ignores conftest.py when they appear in several subdirs.
+- improve assertions ("not in"), thanks Floris Bruynooghe
+- improve behaviour/warnings when running on top of "python -OO"
+  (assertions and docstrings are turned off, leading to potential
+  false positives)
+- introduce a pytest_cmdline_processargs(args) hook
+  to allow dynamic computation of command line arguments.
+  This fixes a regression because py.test prior to 2.0
+  allowed to set command line options from conftest.py
+  files which so far pytest-2.0 only allowed from ini-files now.
+- fix issue7: assert failures in doctest modules.
+  unexpected failures in doctests will not generally
+  show nicer, i.e. within the doctest failing context.
+- fix issue9: setup/teardown functions for an xfail-marked
+  test will report as xfail if they fail but report as normally
+  passing (not xpassing) if they succeed.  This only is true
+  for "direct" setup/teardown invocations because teardown_class/
+  teardown_module cannot closely relate to a single test.
+- fix issue14: no logging errors at process exit
+- refinements to "collecting" output on non-ttys
+- refine internal plugin registration and --traceconfig output
+- introduce a mechanism to prevent/unregister plugins from the
+  command line, see http://pytest.org/plugins.html#cmdunregister
+- activate resultlog plugin by default
+- fix regression wrt yielded tests which due to the
+  collection-before-running semantics were not
+  setup as with pytest 1.3.4.  Note, however, that
+  the recommended and much cleaner way to do test
+  parametraization remains the "pytest_generate_tests"
+  mechanism, see the docs.
+
+2.0.0 (2010-11-25)
+==================
+
+- pytest-2.0 is now its own package and depends on pylib-2.0
+- new ability: python -m pytest / python -m pytest.main ability
+- new python invocation: pytest.main(args, plugins) to load
+  some custom plugins early.
+- try harder to run unittest test suites in a more compatible manner
+  by deferring setup/teardown semantics to the unittest package.
+  also work harder to run twisted/trial and Django tests which
+  should now basically work by default.
+- introduce a new way to set config options via ini-style files,
+  by default setup.cfg and tox.ini files are searched.  The old
+  ways (certain environment variables, dynamic conftest.py reading
+  is removed).
+- add a new "-q" option which decreases verbosity and prints a more
+  nose/unittest-style "dot" output.
+- fix issue135 - marks now work with unittest test cases as well
+- fix issue126 - introduce py.test.set_trace() to trace execution via
+  PDB during the running of tests even if capturing is ongoing.
+- fix issue123 - new "python -m py.test" invocation for py.test
+  (requires Python 2.5 or above)
+- fix issue124 - make reporting more resilient against tests opening
+  files on filedescriptor 1 (stdout).
+- fix issue109 - sibling conftest.py files will not be loaded.
+  (and Directory collectors cannot be customized anymore from a Directory's
+  conftest.py - this needs to happen at least one level up).
+- introduce (customizable) assertion failure representations and enhance
+  output on assertion failures for comparisons and other cases (Floris Bruynooghe)
+- nose-plugin: pass through type-signature failures in setup/teardown
+  functions instead of not calling them (Ed Singleton)
+- remove py.test.collect.Directory (follows from a major refactoring
+  and simplification of the collection process)
+- majorly reduce py.test core code, shift function/python testing to own plugin
+- fix issue88 (finding custom test nodes from command line arg)
+- refine 'tmpdir' creation, will now create basenames better associated
+  with test names (thanks Ronny)
+- "xpass" (unexpected pass) tests don't cause exitcode!=0
+- fix issue131 / issue60 - importing doctests in __init__ files used as namespace packages
+- fix issue93 stdout/stderr is captured while importing conftest.py
+- fix bug: unittest collected functions now also can have "pytestmark"
+  applied at class/module level
+- add ability to use "class" level for cached_setup helper
+- fix strangeness: mark.* objects are now immutable, create new instances
+
+1.3.4 (2010-09-14)
+==================
+
+- fix issue111: improve install documentation for windows
+- fix issue119: fix custom collectability of __init__.py as a module
+- fix issue116: --doctestmodules work with __init__.py files as well
+- fix issue115: unify internal exception passthrough/catching/GeneratorExit
+- fix issue118: new --tb=native for presenting cpython-standard exceptions
+
+1.3.3 (2010-07-30)
+==================
+
+- fix issue113: assertion representation problem with triple-quoted strings
+  (and possibly other cases)
+- make conftest loading detect that a conftest file with the same
+  content was already loaded, avoids surprises in nested directory structures
+  which can be produced e.g. by Hudson. It probably removes the need to use
+  --confcutdir in most cases.
+- fix terminal coloring for win32
+  (thanks Michael Foord for reporting)
+- fix weirdness: make terminal width detection work on stdout instead of stdin
+  (thanks Armin Ronacher for reporting)
+- remove trailing whitespace in all py/text distribution files
+
+1.3.2 (2010-07-08)
+==================
+
+**New features**
+
+- fix issue103:  introduce py.test.raises as context manager, examples::
+
+    with py.test.raises(ZeroDivisionError):
+        x = 0
+        1 / x
+
+    with py.test.raises(RuntimeError) as excinfo:
+        call_something()
+
+    # you may do extra checks on excinfo.value|type|traceback here
+
+  (thanks Ronny Pfannschmidt)
+
+- Funcarg factories can now dynamically apply a marker to a
+  test invocation.  This is for example useful if a factory
+  provides parameters to a test which are expected-to-fail::
+
+    def pytest_funcarg__arg(request):
+        request.applymarker(py.test.mark.xfail(reason="flaky config"))
+        ...
+
+    def test_function(arg):
+        ...
+
+- improved error reporting on collection and import errors. This makes
+  use of a more general mechanism, namely that for custom test item/collect
+  nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
+  override it to return a string error representation of your choice
+  which is going to be reported as a (red) string.
+
+- introduce '--junitprefix=STR' option to prepend a prefix
+  to all reports in the junitxml file.
+
+**Bug fixes**
+
+- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
+  to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
+  you can properly check for their existence in a cross-python manner).
+- refine --pdb: ignore xfailed tests, unify its TB-reporting and
+  don't display failures again at the end.
+- fix assertion interpretation with the ** operator (thanks Benjamin Peterson)
+- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson)
+- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous)
+- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
+- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
+- fix py.code.compile(source) to generate unique filenames
+- fix assertion re-interp problems on PyPy, by defering code
+  compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
+- fix py.path.local.pyimport() to work with directories
+- streamline py.path.local.mkdtemp implementation and usage
+- don't print empty lines when showing junitxml-filename
+- add optional boolean ignore_errors parameter to py.path.local.remove
+- fix terminal writing on win32/python2.4
+- py.process.cmdexec() now tries harder to return properly encoded unicode objects
+  on all python versions
+- install plain py.test/py.which scripts also for Jython, this helps to
+  get canonical script paths in virtualenv situations
+- make path.bestrelpath(path) return ".", note that when calling
+  X.bestrelpath the assumption is that X is a directory.
+- make initial conftest discovery ignore "--" prefixed arguments
+- fix resultlog plugin when used in an multicpu/multihost xdist situation
+  (thanks Jakub Gustak)
+- perform distributed testing related reporting in the xdist-plugin
+  rather than having dist-related code in the generic py.test
+  distribution
+- fix homedir detection on Windows
+- ship distribute_setup.py version 0.6.13
+
+1.3.1 (2010-05-25)
+==================
+
+**New features**
+
+- issue91: introduce new py.test.xfail(reason) helper
+  to imperatively mark a test as expected to fail. Can
+  be used from within setup and test functions. This is
+  useful especially for parametrized tests when certain
+  configurations are expected-to-fail.  In this case the
+  declarative approach with the @py.test.mark.xfail cannot
+  be used as it would mark all configurations as xfail.
+
+- issue102: introduce new --maxfail=NUM option to stop
+  test runs after NUM failures.  This is a generalization
+  of the '-x' or '--exitfirst' option which is now equivalent
+  to '--maxfail=1'.  Both '-x' and '--maxfail' will
+  now also print a line near the end indicating the Interruption.
+
+- issue89: allow py.test.mark decorators to be used on classes
+  (class decorators were introduced with python2.6) and
+  also allow to have multiple markers applied at class/module level
+  by specifying a list.
+
+- improve and refine letter reporting in the progress bar:
+  .  pass
+  f  failed test
+  s  skipped tests (reminder: use for dependency/platform mismatch only)
+  x  xfailed test (test that was expected to fail)
+  X  xpassed test (test that was expected to fail but passed)
+
+  You can use any combination of 'fsxX' with the '-r' extended
+  reporting option. The xfail/xpass results will show up as
+  skipped tests in the junitxml output - which also fixes
+  issue99.
+
+- make py.test.cmdline.main() return the exitstatus instead of raising
+  SystemExit and also allow it to be called multiple times.  This of
+  course requires that your application and tests are properly teared
+  down and don't have global state.
+
+**Bug Fixes**
+
+- improved traceback presentation:
+  - improved and unified reporting for "--tb=short" option
+  - Errors during test module imports are much shorter, (using --tb=short style)
+  - raises shows shorter more relevant tracebacks
+  - --fulltrace now more systematically makes traces longer / inhibits cutting
+
+- improve support for raises and other dynamically compiled code by
+  manipulating python's linecache.cache instead of the previous
+  rather hacky way of creating custom code objects.  This makes
+  it seemlessly work on Jython and PyPy where it previously didn't.
+
+- fix issue96: make capturing more resilient against Control-C
+  interruptions (involved somewhat substantial refactoring
+  to the underlying capturing functionality to avoid race
+  conditions).
+
+- fix chaining of conditional skipif/xfail decorators - so it works now
+  as expected to use multiple @py.test.mark.skipif(condition) decorators,
+  including specific reporting which of the conditions lead to skipping.
+
+- fix issue95: late-import zlib so that it's not required
+  for general py.test startup.
+
+- fix issue94: make reporting more robust against bogus source code
+  (and internally be more careful when presenting unexpected byte sequences)
+
+
+1.3.0 (2010-05-05)
+==================
+
+- deprecate --report option in favour of a new shorter and easier to
+  remember -r option: it takes a string argument consisting of any
+  combination of 'xfsX' characters.  They relate to the single chars
+  you see during the dotted progress printing and will print an extra line
+  per test at the end of the test run.  This extra line indicates the exact
+  position or test ID that you directly paste to the py.test cmdline in order
+  to re-run a particular test.
+
+- allow external plugins to register new hooks via the new
+  pytest_addhooks(pluginmanager) hook.  The new release of
+  the pytest-xdist plugin for distributed and looponfailing
+  testing requires this feature.
+
+- add a new pytest_ignore_collect(path, config) hook to allow projects and
+  plugins to define exclusion behaviour for their directory structure -
+  for example you may define in a conftest.py this method::
+
+        def pytest_ignore_collect(path):
+            return path.check(link=1)
+
+  to prevent even a collection try of any tests in symlinked dirs.
+
+- new pytest_pycollect_makemodule(path, parent) hook for
+  allowing customization of the Module collection object for a
+  matching test module.
+
+- extend and refine xfail mechanism:
+  ``@py.test.mark.xfail(run=False)`` do not run the decorated test
+  ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries
+  specifying ``--runxfail`` on command line virtually ignores xfail markers
+
+- expose (previously internal) commonly useful methods:
+  py.io.get_terminal_with() -> return terminal width
+  py.io.ansi_print(...) -> print colored/bold text on linux/win32
+  py.io.saferepr(obj) -> return limited representation string
+
+- expose test outcome related exceptions as py.test.skip.Exception,
+  py.test.raises.Exception etc., useful mostly for plugins
+  doing special outcome interpretation/tweaking
+
+- (issue85) fix junitxml plugin to handle tests with non-ascii output
+
+- fix/refine python3 compatibility (thanks Benjamin Peterson)
+
+- fixes for making the jython/win32 combination work, note however:
+  jython2.5.1/win32 does not provide a command line launcher, see
+  http://bugs.jython.org/issue1491 . See pylib install documentation
+  for how to work around.
+
+- fixes for handling of unicode exception values and unprintable objects
+
+- (issue87) fix unboundlocal error in assertionold code
+
+- (issue86) improve documentation for looponfailing
+
+- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
+
+- ship distribute_setup.py version 0.6.10
+
+- added links to the new capturelog and coverage plugins
+
+
+1.2.0 (2010-01-18)
+==================
+
+- refined usage and options for "py.cleanup"::
+
+    py.cleanup     # remove "*.pyc" and "*$py.class" (jython) files
+    py.cleanup -e .swp -e .cache # also remove files with these extensions
+    py.cleanup -s  # remove "build" and "dist" directory next to setup.py files
+    py.cleanup -d  # also remove empty directories
+    py.cleanup -a  # synonym for "-s -d -e 'pip-log.txt'"
+    py.cleanup -n  # dry run, only show what would be removed
+
+- add a new option "py.test --funcargs" which shows available funcargs
+  and their help strings (docstrings on their respective factory function)
+  for a given test path
+
+- display a short and concise traceback if a funcarg lookup fails
+
+- early-load "conftest.py" files in non-dot first-level sub directories.
+  allows to conveniently keep and access test-related options in a ``test``
+  subdir and still add command line options.
+
+- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
+
+- fix issue78: always call python-level teardown functions even if the
+  according setup failed.  This includes refinements for calling setup_module/class functions
+  which will now only be called once instead of the previous behaviour where they'd be called
+  multiple times if they raise an exception (including a Skipped exception).  Any exception
+  will be re-corded and associated with all tests in the according module/class scope.
+
+- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
+
+- fix pdb debugging to be in the correct frame on raises-related errors
+
+- update apipkg.py to fix an issue where recursive imports might
+  unnecessarily break importing
+
+- fix plugin links
+
+1.1.1 (2009-11-24)
+==================
+
+- moved dist/looponfailing from py.test core into a new
+  separately released pytest-xdist plugin.
+
+- new junitxml plugin: --junitxml=path will generate a junit style xml file
+  which is processable e.g. by the Hudson CI system.
+
+- new option: --genscript=path will generate a standalone py.test script
+  which will not need any libraries installed.  thanks to Ralf Schmitt.
+
+- new option: --ignore will prevent specified path from collection.
+  Can be specified multiple times.
+
+- new option: --confcutdir=dir will make py.test only consider conftest
+  files that are relative to the specified dir.
+
+- new funcarg: "pytestconfig" is the pytest config object for access
+  to command line args and can now be easily used in a test.
+
+- install ``py.test`` and ``py.which`` with a ``-$VERSION`` suffix to
+  disambiguate between Python3, python2.X, Jython and PyPy installed versions.
+
+- new "pytestconfig" funcarg allows access to test config object
+
+- new "pytest_report_header" hook can return additional lines
+  to be displayed at the header of a test run.
+
+- (experimental) allow "py.test path::name1::name2::..." for pointing
+  to a test within a test collection directly.  This might eventually
+  evolve as a full substitute to "-k" specifications.
+
+- streamlined plugin loading: order is now as documented in
+  customize.html: setuptools, ENV, commandline, conftest.
+  also setuptools entry point names are turned to canonical namees ("pytest_*")
+
+- automatically skip tests that need 'capfd' but have no os.dup
+
+- allow pytest_generate_tests to be defined in classes as well
+
+- deprecate usage of 'disabled' attribute in favour of pytestmark
+- deprecate definition of Directory, Module, Class and Function nodes
+  in conftest.py files.  Use pytest collect hooks instead.
+
+- collection/item node specific runtest/collect hooks are only called exactly
+  on matching conftest.py files, i.e. ones which are exactly below
+  the filesystem path of an item
+
+- change: the first pytest_collect_directory hook to return something
+  will now prevent further hooks to be called.
+
+- change: figleaf plugin now requires --figleaf to run.  Also
+  change its long command line options to be a bit shorter (see py.test -h).
+
+- change: pytest doctest plugin is now enabled by default and has a
+  new option --doctest-glob to set a pattern for file matches.
+
+- change: remove internal py._* helper vars, only keep py._pydir
+
+- robustify capturing to survive if custom pytest_runtest_setup
+  code failed and prevented the capturing setup code from running.
+
+- make py.test.* helpers provided by default plugins visible early -
+  works transparently both for pydoc and for interactive sessions
+  which will regularly see e.g. py.test.mark and py.test.importorskip.
+
+- simplify internal plugin manager machinery
+- simplify internal collection tree by introducing a RootCollector node
+
+- fix assert reinterpreation that sees a call containing "keyword=..."
+
+- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
+  hooks on slaves during dist-testing, report module/session teardown
+  hooks correctly.
+
+- fix issue65: properly handle dist-testing if no
+  execnet/py lib installed remotely.
+
+- skip some install-tests if no execnet is available
+
+- fix docs, fix internal bin/ script generation
+
+
+1.1.0 (2009-11-05)
+==================
+
+- introduce automatic plugin registration via 'pytest11'
+  entrypoints via setuptools' pkg_resources.iter_entry_points
+
+- fix py.test dist-testing to work with execnet >= 1.0.0b4
+
+- re-introduce py.test.cmdline.main() for better backward compatibility
+
+- svn paths: fix a bug with path.check(versioned=True) for svn paths,
+  allow '%' in svn paths, make svnwc.update() default to interactive mode
+  like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
+
+- refine distributed tarball to contain test and no pyc files
+
+- try harder to have deprecation warnings for py.compat.* accesses
+  report a correct location
+
+1.0.3
+=====
+
+* adjust and improve docs
+
+* remove py.rest tool and internal namespace - it was
+  never really advertised and can still be used with
+  the old release if needed.  If there is interest
+  it could be revived into its own tool i guess.
+
+* fix issue48 and issue59: raise an Error if the module
+  from an imported test file does not seem to come from
+  the filepath - avoids "same-name" confusion that has
+  been reported repeatedly
+
+* merged Ronny's nose-compatibility hacks: now
+  nose-style setup_module() and setup() functions are
+  supported
+
+* introduce generalized py.test.mark function marking
+
+* reshuffle / refine command line grouping
+
+* deprecate parser.addgroup in favour of getgroup which creates option group
+
+* add --report command line option that allows to control showing of skipped/xfailed sections
+
+* generalized skipping: a new way to mark python functions with skipif or xfail
+  at function, class and modules level based on platform or sys-module attributes.
+
+* extend py.test.mark decorator to allow for positional args
+
+* introduce and test "py.cleanup -d" to remove empty directories
+
+* fix issue #59 - robustify unittest test collection
+
+* make bpython/help interaction work by adding an __all__ attribute
+  to ApiModule, cleanup initpkg
+
+* use MIT license for pylib, add some contributors
+
+* remove py.execnet code and substitute all usages with 'execnet' proper
+
+* fix issue50 - cached_setup now caches more to expectations
+  for test functions with multiple arguments.
+
+* merge Jarko's fixes, issue #45 and #46
+
+* add the ability to specify a path for py.lookup to search in
+
+* fix a funcarg cached_setup bug probably only occurring
+  in distributed testing and "module" scope with teardown.
+
+* many fixes and changes for making the code base python3 compatible,
+  many thanks to Benjamin Peterson for helping with this.
+
+* consolidate builtins implementation to be compatible with >=2.3,
+  add helpers to ease keeping 2 and 3k compatible code
+
+* deprecate py.compat.doctest|subprocess|textwrap|optparse
+
+* deprecate py.magic.autopath, remove py/magic directory
+
+* move pytest assertion handling to py/code and a pytest_assertion
+  plugin, add "--no-assert" option, deprecate py.magic namespaces
+  in favour of (less) py.code ones.
+
+* consolidate and cleanup py/code classes and files
+
+* cleanup py/misc, move tests to bin-for-dist
+
+* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
+
+* consolidate py.log implementation, remove old approach.
+
+* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
+  text/unicode and byte-streams (uses underlying standard lib io.*
+  if available)
+
+* make py.unittest_convert helper script available which converts "unittest.py"
+  style files into the simpler assert/direct-test-classes py.test/nosetests
+  style.  The script was written by Laura Creighton.
+
+* simplified internal localpath implementation
+
+1.0.2 (2009-08-27)
+==================
+
+* fixing packaging issues, triggered by fedora redhat packaging,
+  also added doc, examples and contrib dirs to the tarball.
+
+* added a documentation link to the new django plugin.
+
+1.0.1 (2009-08-19)
+==================
+
+* added a 'pytest_nose' plugin which handles nose.SkipTest,
+  nose-style function/method/generator setup/teardown and
+  tries to report functions correctly.
+
+* capturing of unicode writes or encoded strings to sys.stdout/err
+  work better, also terminalwriting was adapted and somewhat
+  unified between windows and linux.
+
+* improved documentation layout and content a lot
+
+* added a "--help-config" option to show conftest.py / ENV-var names for
+  all longopt cmdline options, and some special conftest.py variables.
+  renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
+
+* fix issue #27: better reporting on non-collectable items given on commandline
+  (e.g. pyc files)
+
+* fix issue #33: added --version flag (thanks Benjamin Peterson)
+
+* fix issue #32: adding support for "incomplete" paths to wcpath.status()
+
+* "Test" prefixed classes are *not* collected by default anymore if they
+  have an __init__ method
+
+* monkeypatch setenv() now accepts a "prepend" parameter
+
+* improved reporting of collection error tracebacks
+
+* simplified multicall mechanism and plugin architecture,
+  renamed some internal methods and argnames
+
+1.0.0 (2009-08-04)
+==================
+
+* more terse reporting try to show filesystem path relatively to current dir
+* improve xfail output a bit
+
+1.0.0b9 (2009-07-31)
+====================
+
+* cleanly handle and report final teardown of test setup
+
+* fix svn-1.6 compat issue with py.path.svnwc().versioned()
+  (thanks Wouter Vanden Hove)
+
+* setup/teardown or collection problems now show as ERRORs
+  or with big "E"'s in the progress lines.  they are reported
+  and counted separately.
+
+* dist-testing: properly handle test items that get locally
+  collected but cannot be collected on the remote side - often
+  due to platform/dependency reasons
+
+* simplified py.test.mark API - see keyword plugin documentation
+
+* integrate better with logging: capturing now by default captures
+  test functions and their immediate setup/teardown in a single stream
+
+* capsys and capfd funcargs now have a readouterr() and a close() method
+  (underlyingly py.io.StdCapture/FD objects are used which grew a
+  readouterr() method as well to return snapshots of captured out/err)
+
+* make assert-reinterpretation work better with comparisons not
+  returning bools (reported with numpy from thanks maciej fijalkowski)
+
+* reworked per-test output capturing into the pytest_iocapture.py plugin
+  and thus removed capturing code from config object
+
+* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
+
+
+1.0.0b8 (2009-07-22)
+====================
+
+* pytest_unittest-plugin is now enabled by default
+
+* introduced pytest_keyboardinterrupt hook and
+  refined pytest_sessionfinish hooked, added tests.
+
+* workaround a buggy logging module interaction ("closing already closed
+  files").  Thanks to Sridhar Ratnakumar for triggering.
+
+* if plugins use "py.test.importorskip" for importing
+  a dependency only a warning will be issued instead
+  of exiting the testing process.
+
+* many improvements to docs:
+  - refined funcargs doc , use the term "factory" instead of "provider"
+  - added a new talk/tutorial doc page
+  - better download page
+  - better plugin docstrings
+  - added new plugins page and automatic doc generation script
+
+* fixed teardown problem related to partially failing funcarg setups
+  (thanks MrTopf for reporting), "pytest_runtest_teardown" is now
+  always invoked even if the "pytest_runtest_setup" failed.
+
+* tweaked doctest output for docstrings in py modules,
+  thanks Radomir.
+
+1.0.0b7
+=======
+
+* renamed py.test.xfail back to py.test.mark.xfail to avoid
+  two ways to decorate for xfail
+
+* re-added py.test.mark decorator for setting keywords on functions
+  (it was actually documented so removing it was not nice)
+
+* remove scope-argument from request.addfinalizer() because
+  request.cached_setup has the scope arg. TOOWTDI.
+
+* perform setup finalization before reporting failures
+
+* apply modified patches from Andreas Kloeckner to allow
+  test functions to have no func_code (#22) and to make
+  "-k" and function keywords work  (#20)
+
+* apply patch from Daniel Peolzleithner (issue #23)
+
+* resolve issue #18, multiprocessing.Manager() and
+  redirection clash
+
+* make __name__ == "__channelexec__" for remote_exec code
+
+1.0.0b3 (2009-06-19)
+====================
+
+* plugin classes are removed: one now defines
+  hooks directly in conftest.py or global pytest_*.py
+  files.
+
+* added new pytest_namespace(config) hook that allows
+  to inject helpers directly to the py.test.* namespace.
+
+* documented and refined many hooks
+
+* added new style of generative tests via
+  pytest_generate_tests hook that integrates
+  well with function arguments.
+
+
+1.0.0b1
+=======
+
+* introduced new "funcarg" setup method,
+  see doc/test/funcarg.txt
+
+* introduced plugin architecture and many
+  new py.test plugins, see
+  doc/test/plugins.txt
+
+* teardown_method is now guaranteed to get
+  called after a test method has run.
+
+* new method: py.test.importorskip(mod,minversion)
+  will either import or call py.test.skip()
+
+* completely revised internal py.test architecture
+
+* new py.process.ForkedFunc object allowing to
+  fork execution of a function to a sub process
+  and getting a result back.
+
+XXX lots of things missing here XXX
+
+0.9.2
+=====
+
+* refined installation and metadata, created new setup.py,
+  now based on setuptools/ez_setup (thanks to Ralf Schmitt
+  for his support).
+
+* improved the way of making py.* scripts available in
+  windows environments, they are now added to the
+  Scripts directory as ".cmd" files.
+
+* py.path.svnwc.status() now is more complete and
+  uses xml output from the 'svn' command if available
+  (Guido Wesdorp)
+
+* fix for py.path.svn* to work with svn 1.5
+  (Chris Lamb)
+
+* fix path.relto(otherpath) method on windows to
+  use normcase for checking if a path is relative.
+
+* py.test's traceback is better parseable from editors
+  (follows the filenames:LINENO: MSG convention)
+  (thanks to Osmo Salomaa)
+
+* fix to javascript-generation, "py.test --runbrowser"
+  should work more reliably now
+
+* removed previously accidentally added
+  py.test.broken and py.test.notimplemented helpers.
+
+* there now is a py.__version__ attribute
+
+0.9.1
+=====
+
+This is a fairly complete list of v0.9.1, which can
+serve as a reference for developers.
+
+* allowing + signs in py.path.svn urls [39106]
+* fixed support for Failed exceptions without excinfo in py.test [39340]
+* added support for killing processes for Windows (as well as platforms that
+  support os.kill) in py.misc.killproc [39655]
+* added setup/teardown for generative tests to py.test [40702]
+* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739]
+* fixed problem with calling .remove() on wcpaths of non-versioned files in
+  py.path [44248]
+* fixed some import and inheritance issues in py.test [41480, 44648, 44655]
+* fail to run greenlet tests when pypy is available, but without stackless
+  [45294]
+* small fixes in rsession tests [45295]
+* fixed issue with 2.5 type representations in py.test [45483, 45484]
+* made that internal reporting issues displaying is done atomically in py.test
+  [45518]
+* made that non-existing files are ignored by the py.lookup script [45519]
+* improved exception name creation in py.test [45535]
+* made that less threads are used in execnet [merge in 45539]
+* removed lock required for atomic reporting issue displaying in py.test
+  [45545]
+* removed globals from execnet [45541, 45547]
+* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit
+  get called in 2.5 (py.execnet) [45548]
+* fixed bug in joining threads in py.execnet's servemain [45549]
+* refactored py.test.rsession tests to not rely on exact output format anymore
+  [45646]
+* using repr() on test outcome [45647]
+* added 'Reason' classes for py.test.skip() [45648, 45649]
+* killed some unnecessary sanity check in py.test.collect [45655]
+* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only
+  usable by Administrators [45901]
+* added support for locking and non-recursive commits to py.path.svnwc [45994]
+* locking files in py.execnet to prevent CPython from segfaulting [46010]
+* added export() method to py.path.svnurl
+* fixed -d -x in py.test [47277]
+* fixed argument concatenation problem in py.path.svnwc [49423]
+* restore py.test behaviour that it exits with code 1 when there are failures
+  [49974]
+* don't fail on html files that don't have an accompanying .txt file [50606]
+* fixed 'utestconvert.py < input' [50645]
+* small fix for code indentation in py.code.source [50755]
+* fix _docgen.py documentation building [51285]
+* improved checks for source representation of code blocks in py.test [51292]
+* added support for passing authentication to py.path.svn* objects [52000,
+  52001]
+* removed sorted() call for py.apigen tests in favour of [].sort() to support
+  Python 2.3 [52481]
diff --git a/tools/third_party/pytest/CONTRIBUTING.rst b/tools/third_party/pytest/CONTRIBUTING.rst
new file mode 100644
index 0000000..d85a894
--- /dev/null
+++ b/tools/third_party/pytest/CONTRIBUTING.rst
@@ -0,0 +1,278 @@
+============================
+Contribution getting started
+============================
+
+Contributions are highly welcomed and appreciated.  Every little help counts,
+so do not hesitate!
+
+.. contents:: Contribution links
+   :depth: 2
+
+
+.. _submitfeedback:
+
+Feature requests and feedback
+-----------------------------
+
+Do you like pytest?  Share some love on Twitter or in your blog posts!
+
+We'd also like to hear about your propositions and suggestions.  Feel free to
+`submit them as issues <https://github.com/pytest-dev/pytest/issues>`_ and:
+
+* Explain in detail how they should work.
+* Keep the scope as narrow as possible.  This will make it easier to implement.
+
+
+.. _reportbugs:
+
+Report bugs
+-----------
+
+Report bugs for pytest in the `issue tracker <https://github.com/pytest-dev/pytest/issues>`_.
+
+If you are reporting a bug, please include:
+
+* Your operating system name and version.
+* Any details about your local setup that might be helpful in troubleshooting,
+  specifically the Python interpreter version, installed libraries, and pytest
+  version.
+* Detailed steps to reproduce the bug.
+
+If you can write a demonstration test that currently fails but should pass
+(xfail), that is a very useful commit to make as well, even if you cannot
+fix the bug itself.
+
+
+.. _fixbugs:
+
+Fix bugs
+--------
+
+Look through the GitHub issues for bugs.  Here is a filter you can use:
+https://github.com/pytest-dev/pytest/labels/type%3A%20bug
+
+:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
+
+Don't forget to check the issue trackers of your favourite plugins, too!
+
+.. _writeplugins:
+
+Implement features
+------------------
+
+Look through the GitHub issues for enhancements.  Here is a filter you can use:
+https://github.com/pytest-dev/pytest/labels/enhancement
+
+:ref:`Talk <contact>` to developers to find out how you can implement specific
+features.
+
+Write documentation
+-------------------
+
+Pytest could always use more documentation.  What exactly is needed?
+
+* More complementary documentation.  Have you perhaps found something unclear?
+* Documentation translations.  We currently have only English.
+* Docstrings.  There can never be too many of them.
+* Blog posts, articles and such -- they're all very appreciated.
+
+You can also edit documentation files directly in the GitHub web interface,
+without using a local copy.  This can be convenient for small fixes.
+
+.. note::
+    Build the documentation locally with the following command:
+
+    .. code:: bash
+
+        $ tox -e docs
+
+    The built documentation should be available in the ``doc/en/_build/``.
+
+    Where 'en' refers to the documentation language.
+
+.. _submitplugin:
+
+Submitting Plugins to pytest-dev
+--------------------------------
+
+Pytest development of the core, some plugins and support code happens
+in repositories living under the ``pytest-dev`` organisations:
+
+- `pytest-dev on GitHub <https://github.com/pytest-dev>`_
+
+- `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_
+
+All pytest-dev Contributors team members have write access to all contained
+repositories.  Pytest core and plugins are generally developed
+using `pull requests`_ to respective repositories.
+
+The objectives of the ``pytest-dev`` organisation are:
+
+* Having a central location for popular pytest plugins
+* Sharing some of the maintenance responsibility (in case a maintainer no
+  longer wishes to maintain a plugin)
+
+You can submit your plugin by subscribing to the `pytest-dev mail list
+<https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a
+mail pointing to your existing pytest plugin repository which must have
+the following:
+
+- PyPI presence with a ``setup.py`` that contains a license, ``pytest-``
+  prefixed name, version number, authors, short and long description.
+
+- a ``tox.ini`` for running tests using `tox <https://tox.readthedocs.io>`_.
+
+- a ``README.txt`` describing how to use the plugin and on which
+  platforms it runs.
+
+- a ``LICENSE.txt`` file or equivalent containing the licensing
+  information, with matching info in ``setup.py``.
+
+- an issue tracker for bug reports and enhancement requests.
+
+- a `changelog <http://keepachangelog.com/>`_
+
+If no contributor strongly objects and two agree, the repository can then be
+transferred to the ``pytest-dev`` organisation.
+
+Here's a rundown of how a repository transfer usually proceeds
+(using a repository named ``joedoe/pytest-xyz`` as example):
+
+* ``joedoe`` transfers repository ownership to ``pytest-dev`` administrator ``calvin``.
+* ``calvin`` creates ``pytest-xyz-admin`` and ``pytest-xyz-developers`` teams, inviting ``joedoe`` to both as **maintainer**.
+* ``calvin`` transfers repository to ``pytest-dev`` and configures team access:
+  
+  - ``pytest-xyz-admin`` **admin** access;
+  - ``pytest-xyz-developers`` **write** access;
+
+The ``pytest-dev/Contributors`` team has write access to all projects, and
+every project administrator is in it. We recommend that each plugin has at least three
+people who have the right to release to PyPI.
+
+Repository owners can rest assured that no ``pytest-dev`` administrator will ever make
+releases of your repository or take ownership in any way, except in rare cases
+where someone becomes unresponsive after months of contact attempts.
+As stated, the objective is to share maintenance and avoid "plugin-abandon".
+
+
+.. _`pull requests`:
+.. _pull-requests:
+
+Preparing Pull Requests
+-----------------------
+
+Short version
+~~~~~~~~~~~~~
+
+#. Fork the repository;
+#. Target ``master`` for bugfixes and doc changes;
+#. Target ``features`` for new features or functionality changes.
+#. Follow **PEP-8**. There's a ``tox`` command to help fixing it: ``tox -e fix-lint``.
+#. Tests are run using ``tox``::
+
+    tox -e linting,py27,py36
+
+   The test environments above are usually enough to cover most cases locally.
+
+#. Write a ``changelog`` entry: ``changelog/2574.bugfix``, use issue id number
+   and one of ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or
+   ``trivial`` for the issue type.
+#. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please
+   add yourself to the ``AUTHORS`` file, in alphabetical order;
+
+
+Long version
+~~~~~~~~~~~~
+
+What is a "pull request"?  It informs the project's core developers about the
+changes you want to review and merge.  Pull requests are stored on
+`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
+Once you send a pull request, we can discuss its potential modifications and
+even add more commits to it later on. There's an excellent tutorial on how Pull
+Requests work in the
+`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_.
+
+Here is a simple overview, with pytest-specific bits:
+
+#. Fork the
+   `pytest GitHub repository <https://github.com/pytest-dev/pytest>`__.  It's
+   fine to use ``pytest`` as your fork repository name because it will live
+   under your user.
+
+#. Clone your fork locally using `git <https://git-scm.com/>`_ and create a branch::
+
+    $ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
+    $ cd pytest
+    # now, to fix a bug create your own branch off "master":
+    
+        $ git checkout -b your-bugfix-branch-name master
+
+    # or to instead add a feature create your own branch off "features":
+    
+        $ git checkout -b your-feature-branch-name features
+
+   Given we have "major.minor.micro" version numbers, bugfixes will usually 
+   be released in micro releases whereas features will be released in 
+   minor releases and incompatible changes in major releases.
+
+   If you need some help with Git, follow this quick start
+   guide: https://git.wiki.kernel.org/index.php/QuickStart
+
+#. Install tox
+
+   Tox is used to run all the tests and will automatically setup virtualenvs
+   to run the tests in.
+   (will implicitly use http://www.virtualenv.org/en/latest/)::
+
+    $ pip install tox
+
+#. Run all the tests
+
+   You need to have Python 2.7 and 3.6 available in your system.  Now
+   running tests is as simple as issuing this command::
+
+    $ tox -e linting,py27,py36
+
+   This command will run tests via the "tox" tool against Python 2.7 and 3.6
+   and also perform "lint" coding-style checks.
+
+#. You can now edit your local working copy. Please follow PEP-8.
+
+   You can now make the changes you want and run the tests again as necessary.
+
+   If you have too much linting errors, try running::
+
+    $ tox -e fix-lint
+
+   To fix pep8 related errors.
+
+   You can pass different options to ``tox``. For example, to run tests on Python 2.7 and pass options to pytest
+   (e.g. enter pdb on failure) to pytest you can do::
+
+    $ tox -e py27 -- --pdb
+
+   Or to only run tests in a particular test module on Python 3.6::
+
+    $ tox -e py36 -- testing/test_config.py
+
+#. Commit and push once your tests pass and you are happy with your change(s)::
+
+    $ git commit -a -m "<commit message>"
+    $ git push -u
+
+#. Create a new changelog entry in ``changelog``. The file should be named ``<issueid>.<type>``,
+   where *issueid* is the number of the issue related to the change and *type* is one of
+   ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or ``trivial``.
+
+#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order.
+
+#. Finally, submit a pull request through the GitHub website using this data::
+
+    head-fork: YOUR_GITHUB_USERNAME/pytest
+    compare: your-branch-name
+
+    base-fork: pytest-dev/pytest
+    base: master          # if it's a bugfix
+    base: features        # if it's a feature
+
+
diff --git a/tools/third_party/pytest/HOWTORELEASE.rst b/tools/third_party/pytest/HOWTORELEASE.rst
new file mode 100644
index 0000000..48a3461
--- /dev/null
+++ b/tools/third_party/pytest/HOWTORELEASE.rst
@@ -0,0 +1,65 @@
+Release Procedure
+-----------------
+
+Our current policy for releasing is to aim for a bugfix every few weeks and a minor release every 2-3 months. The idea
+is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence
+taking a lot of time to make a new one.
+
+.. important::
+
+    pytest releases must be prepared on **Linux** because the docs and examples expect
+    to be executed in that platform.
+
+#. Install development dependencies in a virtual environment with::
+
+    pip3 install -r tasks/requirements.txt
+
+#. Create a branch ``release-X.Y.Z`` with the version for the release.
+
+   * **patch releases**: from the latest ``master``;
+
+   * **minor releases**: from the latest ``features``; then merge with the latest ``master``;
+
+   Ensure your are in a clean work tree.
+
+#. Generate docs, changelog, announcements and upload a package to
+   your ``devpi`` staging server::
+
+     invoke generate.pre-release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
+
+   If ``--password`` is not given, it is assumed the user is already logged in ``devpi``.
+   If you don't have an account, please ask for one.
+
+#. Open a PR for this branch targeting ``master``.
+
+#. Test the package
+
+   * **Manual method**
+
+     Run from multiple machines::
+
+       devpi use https://devpi.net/USER/dev
+       devpi test pytest==VERSION
+
+     Check that tests pass for relevant combinations with::
+
+       devpi list pytest
+
+   * **CI servers**
+
+     Configure a repository as per-instructions on
+     devpi-cloud-test_ to test the package on Travis_ and AppVeyor_.
+     All test environments should pass.
+
+#. Publish to PyPI::
+
+      invoke generate.publish-release <VERSION> <DEVPI USER> <PYPI_NAME>
+
+   where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
+   file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
+
+#. After a minor/major release, merge ``release-X.Y.Z`` into ``master`` and push (or open a PR).
+
+.. _devpi-cloud-test: https://github.com/obestwalter/devpi-cloud-test
+.. _AppVeyor: https://www.appveyor.com/
+.. _Travis: https://travis-ci.org
diff --git a/tools/third_party/pytest/LICENSE b/tools/third_party/pytest/LICENSE
new file mode 100644
index 0000000..629df45
--- /dev/null
+++ b/tools/third_party/pytest/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2004-2017 Holger Krekel and others
+
+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.
diff --git a/tools/third_party/pytest/README.rst b/tools/third_party/pytest/README.rst
new file mode 100644
index 0000000..3630dd4
--- /dev/null
+++ b/tools/third_party/pytest/README.rst
@@ -0,0 +1,109 @@
+.. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
+   :target: http://docs.pytest.org
+   :align: center
+   :alt: pytest
+
+------
+
+.. image:: https://img.shields.io/pypi/v/pytest.svg
+    :target: https://pypi.python.org/pypi/pytest
+
+.. image:: https://anaconda.org/conda-forge/pytest/badges/version.svg
+    :target: https://anaconda.org/conda-forge/pytest
+
+.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
+    :target: https://pypi.python.org/pypi/pytest
+
+.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
+    :target: https://coveralls.io/r/pytest-dev/pytest
+
+.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
+    :target: https://travis-ci.org/pytest-dev/pytest
+
+.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
+    :target: https://ci.appveyor.com/project/pytestbot/pytest
+
+The ``pytest`` framework makes it easy to write small tests, yet
+scales to support complex functional testing for applications and libraries.
+
+An example of a simple test:
+
+.. code-block:: python
+
+    # content of test_sample.py
+    def inc(x):
+        return x + 1
+
+    def test_answer():
+        assert inc(3) == 5
+
+
+To execute it::
+
+    $ pytest
+    ============================= test session starts =============================
+    collected 1 items
+
+    test_sample.py F
+
+    ================================== FAILURES ===================================
+    _________________________________ test_answer _________________________________
+
+        def test_answer():
+    >       assert inc(3) == 5
+    E       assert 4 == 5
+    E        +  where 4 = inc(3)
+
+    test_sample.py:5: AssertionError
+    ========================== 1 failed in 0.04 seconds ===========================
+
+
+Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples.
+
+
+Features
+--------
+
+- Detailed info on failing `assert statements <http://docs.pytest.org/en/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
+
+- `Auto-discovery
+  <http://docs.pytest.org/en/latest/goodpractices.html#python-test-discovery>`_
+  of test modules and functions;
+
+- `Modular fixtures <http://docs.pytest.org/en/latest/fixture.html>`_ for
+  managing small or parametrized long-lived test resources;
+
+- Can run `unittest <http://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
+  `nose <http://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
+
+- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
+
+- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
+
+
+Documentation
+-------------
+
+For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org.
+
+
+Bugs/Requests
+-------------
+
+Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
+
+
+Changelog
+---------
+
+Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`__ page for fixes and enhancements of each version.
+
+
+License
+-------
+
+Copyright Holger Krekel and others, 2004-2017.
+
+Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
+
+.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
diff --git a/tools/third_party/pytest/_pytest/__init__.py b/tools/third_party/pytest/_pytest/__init__.py
new file mode 100644
index 0000000..6e41f05
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/__init__.py
@@ -0,0 +1,8 @@
+__all__ = ['__version__']
+
+try:
+    from ._version import version as __version__
+except ImportError:
+    # broken installation, we don't even try
+    # unknown only works because we do poor mans version compare
+    __version__ = 'unknown'
diff --git a/tools/third_party/pytest/_pytest/_argcomplete.py b/tools/third_party/pytest/_pytest/_argcomplete.py
new file mode 100644
index 0000000..0625a75
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/_argcomplete.py
@@ -0,0 +1,103 @@
+
+"""allow bash-completion for argparse with argcomplete if installed
+needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
+to find the magic string, so _ARGCOMPLETE env. var is never set, and
+this does not need special code.
+
+Function try_argcomplete(parser) should be called directly before
+the call to ArgumentParser.parse_args().
+
+The filescompleter is what you normally would use on the positional
+arguments specification, in order to get "dirname/" after "dirn<TAB>"
+instead of the default "dirname ":
+
+   optparser.add_argument(Config._file_or_dir, nargs='*'
+                               ).completer=filescompleter
+
+Other, application specific, completers should go in the file
+doing the add_argument calls as they need to be specified as .completer
+attributes as well. (If argcomplete is not installed, the function the
+attribute points to will not be used).
+
+SPEEDUP
+=======
+The generic argcomplete script for bash-completion
+(/etc/bash_completion.d/python-argcomplete.sh )
+uses a python program to determine startup script generated by pip.
+You can speed up completion somewhat by changing this script to include
+  # PYTHON_ARGCOMPLETE_OK
+so the the python-argcomplete-check-easy-install-script does not
+need to be called to find the entry point of the code and see if that is
+marked  with PYTHON_ARGCOMPLETE_OK
+
+INSTALL/DEBUGGING
+=================
+To include this support in another application that has setup.py generated
+scripts:
+- add the line:
+    # PYTHON_ARGCOMPLETE_OK
+  near the top of the main python entry point
+- include in the file calling parse_args():
+    from _argcomplete import try_argcomplete, filescompleter
+   , call try_argcomplete just before parse_args(), and optionally add
+   filescompleter to the positional arguments' add_argument()
+If things do not work right away:
+- switch on argcomplete debugging with (also helpful when doing custom
+  completers):
+    export _ARC_DEBUG=1
+- run:
+    python-argcomplete-check-easy-install-script $(which appname)
+    echo $?
+  will echo 0 if the magic line has been found, 1 if not
+- sometimes it helps to find early on errors using:
+    _ARGCOMPLETE=1 _ARC_DEBUG=1 appname
+  which should throw a KeyError: 'COMPLINE' (which is properly set by the
+  global argcomplete script).
+"""
+from __future__ import absolute_import, division, print_function
+import sys
+import os
+from glob import glob
+
+
+class FastFilesCompleter:
+    'Fast file completer class'
+
+    def __init__(self, directories=True):
+        self.directories = directories
+
+    def __call__(self, prefix, **kwargs):
+        """only called on non option completions"""
+        if os.path.sep in prefix[1:]:
+            prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
+        else:
+            prefix_dir = 0
+        completion = []
+        globbed = []
+        if '*' not in prefix and '?' not in prefix:
+            # we are on unix, otherwise no bash
+            if not prefix or prefix[-1] == os.path.sep:
+                globbed.extend(glob(prefix + '.*'))
+            prefix += '*'
+        globbed.extend(glob(prefix))
+        for x in sorted(globbed):
+            if os.path.isdir(x):
+                x += '/'
+            # append stripping the prefix (like bash, not like compgen)
+            completion.append(x[prefix_dir:])
+        return completion
+
+
+if os.environ.get('_ARGCOMPLETE'):
+    try:
+        import argcomplete.completers
+    except ImportError:
+        sys.exit(-1)
+    filescompleter = FastFilesCompleter()
+
+    def try_argcomplete(parser):
+        argcomplete.autocomplete(parser, always_complete_options=False)
+else:
+    def try_argcomplete(parser):
+        pass
+    filescompleter = None
diff --git a/tools/third_party/pytest/_pytest/_code/__init__.py b/tools/third_party/pytest/_pytest/_code/__init__.py
new file mode 100644
index 0000000..815c13b
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/_code/__init__.py
@@ -0,0 +1,10 @@
+""" python inspection/code generation API """
+from __future__ import absolute_import, division, print_function
+from .code import Code  # noqa
+from .code import ExceptionInfo  # noqa
+from .code import Frame  # noqa
+from .code import Traceback  # noqa
+from .code import getrawcode  # noqa
+from .source import Source  # noqa
+from .source import compile_ as compile  # noqa
+from .source import getfslineno  # noqa
diff --git a/tools/third_party/pytest/_pytest/_code/_py2traceback.py b/tools/third_party/pytest/_pytest/_code/_py2traceback.py
new file mode 100644
index 0000000..5aacf0a
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/_code/_py2traceback.py
@@ -0,0 +1,85 @@
+# copied from python-2.7.3's traceback.py
+# CHANGES:
+# - some_str is replaced, trying to create unicode strings
+#
+from __future__ import absolute_import, division, print_function
+import types
+
+
+def format_exception_only(etype, value):
+    """Format the exception part of a traceback.
+
+    The arguments are the exception type and value such as given by
+    sys.last_type and sys.last_value. The return value is a list of
+    strings, each ending in a newline.
+
+    Normally, the list contains a single string; however, for
+    SyntaxError exceptions, it contains several lines that (when
+    printed) display detailed information about where the syntax
+    error occurred.
+
+    The message indicating which exception occurred is always the last
+    string in the list.
+
+    """
+
+    # An instance should not have a meaningful value parameter, but
+    # sometimes does, particularly for string exceptions, such as
+    # >>> raise string1, string2  # deprecated
+    #
+    # Clear these out first because issubtype(string1, SyntaxError)
+    # would throw another exception and mask the original problem.
+    if (isinstance(etype, BaseException) or
+        isinstance(etype, types.InstanceType) or
+            etype is None or type(etype) is str):
+        return [_format_final_exc_line(etype, value)]
+
+    stype = etype.__name__
+
+    if not issubclass(etype, SyntaxError):
+        return [_format_final_exc_line(stype, value)]
+
+    # It was a syntax error; show exactly where the problem was found.
+    lines = []
+    try:
+        msg, (filename, lineno, offset, badline) = value.args
+    except Exception:
+        pass
+    else:
+        filename = filename or "<string>"
+        lines.append('  File "%s", line %d\n' % (filename, lineno))
+        if badline is not None:
+            if isinstance(badline, bytes):  # python 2 only
+                badline = badline.decode('utf-8', 'replace')
+            lines.append(u'    %s\n' % badline.strip())
+            if offset is not None:
+                caretspace = badline.rstrip('\n')[:offset].lstrip()
+                # non-space whitespace (likes tabs) must be kept for alignment
+                caretspace = ((c.isspace() and c or ' ') for c in caretspace)
+                # only three spaces to account for offset1 == pos 0
+                lines.append('   %s^\n' % ''.join(caretspace))
+        value = msg
+
+    lines.append(_format_final_exc_line(stype, value))
+    return lines
+
+
+def _format_final_exc_line(etype, value):
+    """Return a list of a single line -- normal case for format_exception_only"""
+    valuestr = _some_str(value)
+    if value is None or not valuestr:
+        line = "%s\n" % etype
+    else:
+        line = "%s: %s\n" % (etype, valuestr)
+    return line
+
+
+def _some_str(value):
+    try:
+        return unicode(value)
+    except Exception:
+        try:
+            return str(value)
+        except Exception:
+            pass
+    return '<unprintable %s object>' % type(value).__name__
diff --git a/tools/third_party/pytest/_pytest/_code/code.py b/tools/third_party/pytest/_pytest/_code/code.py
new file mode 100644
index 0000000..3fb232b
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/_code/code.py
@@ -0,0 +1,906 @@
+from __future__ import absolute_import, division, print_function
+import sys
+from inspect import CO_VARARGS, CO_VARKEYWORDS
+import re
+from weakref import ref
+from _pytest.compat import _PY2, _PY3, PY35, safe_str
+
+import py
+builtin_repr = repr
+
+if _PY3:
+    from traceback import format_exception_only
+else:
+    from ._py2traceback import format_exception_only
+
+
+class Code(object):
+    """ wrapper around Python code objects """
+
+    def __init__(self, rawcode):
+        if not hasattr(rawcode, "co_filename"):
+            rawcode = getrawcode(rawcode)
+        try:
+            self.filename = rawcode.co_filename
+            self.firstlineno = rawcode.co_firstlineno - 1
+            self.name = rawcode.co_name
+        except AttributeError:
+            raise TypeError("not a code object: %r" % (rawcode,))
+        self.raw = rawcode
+
+    def __eq__(self, other):
+        return self.raw == other.raw
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not self == other
+
+    @property
+    def path(self):
+        """ return a path object pointing to source code (note that it
+        might not point to an actually existing file). """
+        try:
+            p = py.path.local(self.raw.co_filename)
+            # maybe don't try this checking
+            if not p.check():
+                raise OSError("py.path check failed.")
+        except OSError:
+            # XXX maybe try harder like the weird logic
+            # in the standard lib [linecache.updatecache] does?
+            p = self.raw.co_filename
+
+        return p
+
+    @property
+    def fullsource(self):
+        """ return a _pytest._code.Source object for the full source file of the code
+        """
+        from _pytest._code import source
+        full, _ = source.findsource(self.raw)
+        return full
+
+    def source(self):
+        """ return a _pytest._code.Source object for the code object's source only
+        """
+        # return source only for that part of code
+        import _pytest._code
+        return _pytest._code.Source(self.raw)
+
+    def getargs(self, var=False):
+        """ return a tuple with the argument names for the code object
+
+            if 'var' is set True also return the names of the variable and
+            keyword arguments when present
+        """
+        # handfull shortcut for getting args
+        raw = self.raw
+        argcount = raw.co_argcount
+        if var:
+            argcount += raw.co_flags & CO_VARARGS
+            argcount += raw.co_flags & CO_VARKEYWORDS
+        return raw.co_varnames[:argcount]
+
+
+class Frame(object):
+    """Wrapper around a Python frame holding f_locals and f_globals
+    in which expressions can be evaluated."""
+
+    def __init__(self, frame):
+        self.lineno = frame.f_lineno - 1
+        self.f_globals = frame.f_globals
+        self.f_locals = frame.f_locals
+        self.raw = frame
+        self.code = Code(frame.f_code)
+
+    @property
+    def statement(self):
+        """ statement this frame is at """
+        import _pytest._code
+        if self.code.fullsource is None:
+            return _pytest._code.Source("")
+        return self.code.fullsource.getstatement(self.lineno)
+
+    def eval(self, code, **vars):
+        """ evaluate 'code' in the frame
+
+            'vars' are optional additional local variables
+
+            returns the result of the evaluation
+        """
+        f_locals = self.f_locals.copy()
+        f_locals.update(vars)
+        return eval(code, self.f_globals, f_locals)
+
+    def exec_(self, code, **vars):
+        """ exec 'code' in the frame
+
+            'vars' are optiona; additional local variables
+        """
+        f_locals = self.f_locals.copy()
+        f_locals.update(vars)
+        py.builtin.exec_(code, self.f_globals, f_locals)
+
+    def repr(self, object):
+        """ return a 'safe' (non-recursive, one-line) string repr for 'object'
+        """
+        return py.io.saferepr(object)
+
+    def is_true(self, object):
+        return object
+
+    def getargs(self, var=False):
+        """ return a list of tuples (name, value) for all arguments
+
+            if 'var' is set True also include the variable and keyword
+            arguments when present
+        """
+        retval = []
+        for arg in self.code.getargs(var):
+            try:
+                retval.append((arg, self.f_locals[arg]))
+            except KeyError:
+                pass     # this can occur when using Psyco
+        return retval
+
+
+class TracebackEntry(object):
+    """ a single entry in a traceback """
+
+    _repr_style = None
+    exprinfo = None
+
+    def __init__(self, rawentry, excinfo=None):
+        self._excinfo = excinfo
+        self._rawentry = rawentry
+        self.lineno = rawentry.tb_lineno - 1
+
+    def set_repr_style(self, mode):
+        assert mode in ("short", "long")
+        self._repr_style = mode
+
+    @property
+    def frame(self):
+        import _pytest._code
+        return _pytest._code.Frame(self._rawentry.tb_frame)
+
+    @property
+    def relline(self):
+        return self.lineno - self.frame.code.firstlineno
+
+    def __repr__(self):
+        return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
+
+    @property
+    def statement(self):
+        """ _pytest._code.Source object for the current statement """
+        source = self.frame.code.fullsource
+        return source.getstatement(self.lineno)
+
+    @property
+    def path(self):
+        """ path to the source code """
+        return self.frame.code.path
+
+    def getlocals(self):
+        return self.frame.f_locals
+    locals = property(getlocals, None, None, "locals of underlaying frame")
+
+    def getfirstlinesource(self):
+        # on Jython this firstlineno can be -1 apparently
+        return max(self.frame.code.firstlineno, 0)
+
+    def getsource(self, astcache=None):
+        """ return failing source code. """
+        # we use the passed in astcache to not reparse asttrees
+        # within exception info printing
+        from _pytest._code.source import getstatementrange_ast
+        source = self.frame.code.fullsource
+        if source is None:
+            return None
+        key = astnode = None
+        if astcache is not None:
+            key = self.frame.code.path
+            if key is not None:
+                astnode = astcache.get(key, None)
+        start = self.getfirstlinesource()
+        try:
+            astnode, _, end = getstatementrange_ast(self.lineno, source,
+                                                    astnode=astnode)
+        except SyntaxError:
+            end = self.lineno + 1
+        else:
+            if key is not None:
+                astcache[key] = astnode
+        return source[start:end]
+
+    source = property(getsource)
+
+    def ishidden(self):
+        """ return True if the current frame has a var __tracebackhide__
+            resolving to True
+
+            If __tracebackhide__ is a callable, it gets called with the
+            ExceptionInfo instance and can decide whether to hide the traceback.
+
+            mostly for internal use
+        """
+        try:
+            tbh = self.frame.f_locals['__tracebackhide__']
+        except KeyError:
+            try:
+                tbh = self.frame.f_globals['__tracebackhide__']
+            except KeyError:
+                return False
+
+        if callable(tbh):
+            return tbh(None if self._excinfo is None else self._excinfo())
+        else:
+            return tbh
+
+    def __str__(self):
+        try:
+            fn = str(self.path)
+        except py.error.Error:
+            fn = '???'
+        name = self.frame.code.name
+        try:
+            line = str(self.statement).lstrip()
+        except KeyboardInterrupt:
+            raise
+        except:  # noqa
+            line = "???"
+        return "  File %r:%d in %s\n  %s\n" % (fn, self.lineno + 1, name, line)
+
+    def name(self):
+        return self.frame.code.raw.co_name
+    name = property(name, None, None, "co_name of underlaying code")
+
+
+class Traceback(list):
+    """ Traceback objects encapsulate and offer higher level
+        access to Traceback entries.
+    """
+    Entry = TracebackEntry
+
+    def __init__(self, tb, excinfo=None):
+        """ initialize from given python traceback object and ExceptionInfo """
+        self._excinfo = excinfo
+        if hasattr(tb, 'tb_next'):
+            def f(cur):
+                while cur is not None:
+                    yield self.Entry(cur, excinfo=excinfo)
+                    cur = cur.tb_next
+            list.__init__(self, f(tb))
+        else:
+            list.__init__(self, tb)
+
+    def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
+        """ return a Traceback instance wrapping part of this Traceback
+
+            by provding any combination of path, lineno and firstlineno, the
+            first frame to start the to-be-returned traceback is determined
+
+            this allows cutting the first part of a Traceback instance e.g.
+            for formatting reasons (removing some uninteresting bits that deal
+            with handling of the exception/traceback)
+        """
+        for x in self:
+            code = x.frame.code
+            codepath = code.path
+            if ((path is None or codepath == path) and
+                (excludepath is None or not hasattr(codepath, 'relto') or
+                 not codepath.relto(excludepath)) and
+                (lineno is None or x.lineno == lineno) and
+                    (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
+                return Traceback(x._rawentry, self._excinfo)
+        return self
+
+    def __getitem__(self, key):
+        val = super(Traceback, self).__getitem__(key)
+        if isinstance(key, type(slice(0))):
+            val = self.__class__(val)
+        return val
+
+    def filter(self, fn=lambda x: not x.ishidden()):
+        """ return a Traceback instance with certain items removed
+
+            fn is a function that gets a single argument, a TracebackEntry
+            instance, and should return True when the item should be added
+            to the Traceback, False when not
+
+            by default this removes all the TracebackEntries which are hidden
+            (see ishidden() above)
+        """
+        return Traceback(filter(fn, self), self._excinfo)
+
+    def getcrashentry(self):
+        """ return last non-hidden traceback entry that lead
+        to the exception of a traceback.
+        """
+        for i in range(-1, -len(self) - 1, -1):
+            entry = self[i]
+            if not entry.ishidden():
+                return entry
+        return self[-1]
+
+    def recursionindex(self):
+        """ return the index of the frame/TracebackEntry where recursion
+            originates if appropriate, None if no recursion occurred
+        """
+        cache = {}
+        for i, entry in enumerate(self):
+            # id for the code.raw is needed to work around
+            # the strange metaprogramming in the decorator lib from pypi
+            # which generates code objects that have hash/value equality
+            # XXX needs a test
+            key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
+            # print "checking for recursion at", key
+            values = cache.setdefault(key, [])
+            if values:
+                f = entry.frame
+                loc = f.f_locals
+                for otherloc in values:
+                    if f.is_true(f.eval(co_equal,
+                                        __recursioncache_locals_1=loc,
+                                        __recursioncache_locals_2=otherloc)):
+                        return i
+            values.append(entry.frame.f_locals)
+        return None
+
+
+co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
+                   '?', 'eval')
+
+
+class ExceptionInfo(object):
+    """ wraps sys.exc_info() objects and offers
+        help for navigating the traceback.
+    """
+    _striptext = ''
+    _assert_start_repr = "AssertionError(u\'assert " if _PY2 else "AssertionError(\'assert "
+
+    def __init__(self, tup=None, exprinfo=None):
+        import _pytest._code
+        if tup is None:
+            tup = sys.exc_info()
+            if exprinfo is None and isinstance(tup[1], AssertionError):
+                exprinfo = getattr(tup[1], 'msg', None)
+                if exprinfo is None:
+                    exprinfo = py.io.saferepr(tup[1])
+                if exprinfo and exprinfo.startswith(self._assert_start_repr):
+                    self._striptext = 'AssertionError: '
+        self._excinfo = tup
+        #: the exception class
+        self.type = tup[0]
+        #: the exception instance
+        self.value = tup[1]
+        #: the exception raw traceback
+        self.tb = tup[2]
+        #: the exception type name
+        self.typename = self.type.__name__
+        #: the exception traceback (_pytest._code.Traceback instance)
+        self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
+
+    def __repr__(self):
+        return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
+
+    def exconly(self, tryshort=False):
+        """ return the exception as a string
+
+            when 'tryshort' resolves to True, and the exception is a
+            _pytest._code._AssertionError, only the actual exception part of
+            the exception representation is returned (so 'AssertionError: ' is
+            removed from the beginning)
+        """
+        lines = format_exception_only(self.type, self.value)
+        text = ''.join(lines)
+        text = text.rstrip()
+        if tryshort:
+            if text.startswith(self._striptext):
+                text = text[len(self._striptext):]
+        return text
+
+    def errisinstance(self, exc):
+        """ return True if the exception is an instance of exc """
+        return isinstance(self.value, exc)
+
+    def _getreprcrash(self):
+        exconly = self.exconly(tryshort=True)
+        entry = self.traceback.getcrashentry()
+        path, lineno = entry.frame.code.raw.co_filename, entry.lineno
+        return ReprFileLocation(path, lineno + 1, exconly)
+
+    def getrepr(self, showlocals=False, style="long",
+                abspath=False, tbfilter=True, funcargs=False):
+        """ return str()able representation of this exception info.
+            showlocals: show locals per traceback entry
+            style: long|short|no|native traceback style
+            tbfilter: hide entries (where __tracebackhide__ is true)
+
+            in case of style==native, tbfilter and showlocals is ignored.
+        """
+        if style == 'native':
+            return ReprExceptionInfo(ReprTracebackNative(
+                py.std.traceback.format_exception(
+                    self.type,
+                    self.value,
+                    self.traceback[0]._rawentry,
+                )), self._getreprcrash())
+
+        fmt = FormattedExcinfo(showlocals=showlocals, style=style,
+                               abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
+        return fmt.repr_excinfo(self)
+
+    def __str__(self):
+        entry = self.traceback[-1]
+        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+        return str(loc)
+
+    def __unicode__(self):
+        entry = self.traceback[-1]
+        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+        return unicode(loc)
+
+    def match(self, regexp):
+        """
+        Match the regular expression 'regexp' on the string representation of
+        the exception. If it matches then True is returned (so that it is
+        possible to write 'assert excinfo.match()'). If it doesn't match an
+        AssertionError is raised.
+        """
+        __tracebackhide__ = True
+        if not re.search(regexp, str(self.value)):
+            assert 0, "Pattern '{0!s}' not found in '{1!s}'".format(
+                regexp, self.value)
+        return True
+
+
+class FormattedExcinfo(object):
+    """ presenting information about failing Functions and Generators. """
+    # for traceback entries
+    flow_marker = ">"
+    fail_marker = "E"
+
+    def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
+        self.showlocals = showlocals
+        self.style = style
+        self.tbfilter = tbfilter
+        self.funcargs = funcargs
+        self.abspath = abspath
+        self.astcache = {}
+
+    def _getindent(self, source):
+        # figure out indent for given source
+        try:
+            s = str(source.getstatement(len(source) - 1))
+        except KeyboardInterrupt:
+            raise
+        except:  # noqa
+            try:
+                s = str(source[-1])
+            except KeyboardInterrupt:
+                raise
+            except:  # noqa
+                return 0
+        return 4 + (len(s) - len(s.lstrip()))
+
+    def _getentrysource(self, entry):
+        source = entry.getsource(self.astcache)
+        if source is not None:
+            source = source.deindent()
+        return source
+
+    def _saferepr(self, obj):
+        return py.io.saferepr(obj)
+
+    def repr_args(self, entry):
+        if self.funcargs:
+            args = []
+            for argname, argvalue in entry.frame.getargs(var=True):
+                args.append((argname, self._saferepr(argvalue)))
+            return ReprFuncArgs(args)
+
+    def get_source(self, source, line_index=-1, excinfo=None, short=False):
+        """ return formatted and marked up source lines. """
+        import _pytest._code
+        lines = []
+        if source is None or line_index >= len(source.lines):
+            source = _pytest._code.Source("???")
+            line_index = 0
+        if line_index < 0:
+            line_index += len(source)
+        space_prefix = "    "
+        if short:
+            lines.append(space_prefix + source.lines[line_index].strip())
+        else:
+            for line in source.lines[:line_index]:
+                lines.append(space_prefix + line)
+            lines.append(self.flow_marker + "   " + source.lines[line_index])
+            for line in source.lines[line_index + 1:]:
+                lines.append(space_prefix + line)
+        if excinfo is not None:
+            indent = 4 if short else self._getindent(source)
+            lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
+        return lines
+
+    def get_exconly(self, excinfo, indent=4, markall=False):
+        lines = []
+        indent = " " * indent
+        # get the real exception information out
+        exlines = excinfo.exconly(tryshort=True).split('\n')
+        failindent = self.fail_marker + indent[1:]
+        for line in exlines:
+            lines.append(failindent + line)
+            if not markall:
+                failindent = indent
+        return lines
+
+    def repr_locals(self, locals):
+        if self.showlocals:
+            lines = []
+            keys = [loc for loc in locals if loc[0] != "@"]
+            keys.sort()
+            for name in keys:
+                value = locals[name]
+                if name == '__builtins__':
+                    lines.append("__builtins__ = <builtins>")
+                else:
+                    # This formatting could all be handled by the
+                    # _repr() function, which is only reprlib.Repr in
+                    # disguise, so is very configurable.
+                    str_repr = self._saferepr(value)
+                    # if len(str_repr) < 70 or not isinstance(value,
+                    #                            (list, tuple, dict)):
+                    lines.append("%-10s = %s" % (name, str_repr))
+                    # else:
+                    #    self._line("%-10s =\\" % (name,))
+                    #    # XXX
+                    #    py.std.pprint.pprint(value, stream=self.excinfowriter)
+            return ReprLocals(lines)
+
+    def repr_traceback_entry(self, entry, excinfo=None):
+        import _pytest._code
+        source = self._getentrysource(entry)
+        if source is None:
+            source = _pytest._code.Source("???")
+            line_index = 0
+        else:
+            # entry.getfirstlinesource() can be -1, should be 0 on jython
+            line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
+
+        lines = []
+        style = entry._repr_style
+        if style is None:
+            style = self.style
+        if style in ("short", "long"):
+            short = style == "short"
+            reprargs = self.repr_args(entry) if not short else None
+            s = self.get_source(source, line_index, excinfo, short=short)
+            lines.extend(s)
+            if short:
+                message = "in %s" % (entry.name)
+            else:
+                message = excinfo and excinfo.typename or ""
+            path = self._makepath(entry.path)
+            filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
+            localsrepr = None
+            if not short:
+                localsrepr = self.repr_locals(entry.locals)
+            return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
+        if excinfo:
+            lines.extend(self.get_exconly(excinfo, indent=4))
+        return ReprEntry(lines, None, None, None, style)
+
+    def _makepath(self, path):
+        if not self.abspath:
+            try:
+                np = py.path.local().bestrelpath(path)
+            except OSError:
+                return path
+            if len(np) < len(str(path)):
+                path = np
+        return path
+
+    def repr_traceback(self, excinfo):
+        traceback = excinfo.traceback
+        if self.tbfilter:
+            traceback = traceback.filter()
+
+        if is_recursion_error(excinfo):
+            traceback, extraline = self._truncate_recursive_traceback(traceback)
+        else:
+            extraline = None
+
+        last = traceback[-1]
+        entries = []
+        for index, entry in enumerate(traceback):
+            einfo = (last == entry) and excinfo or None
+            reprentry = self.repr_traceback_entry(entry, einfo)
+            entries.append(reprentry)
+        return ReprTraceback(entries, extraline, style=self.style)
+
+    def _truncate_recursive_traceback(self, traceback):
+        """
+        Truncate the given recursive traceback trying to find the starting point
+        of the recursion.
+
+        The detection is done by going through each traceback entry and finding the
+        point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
+
+        Handle the situation where the recursion process might raise an exception (for example
+        comparing numpy arrays using equality raises a TypeError), in which case we do our best to
+        warn the user of the error and show a limited traceback.
+        """
+        try:
+            recursionindex = traceback.recursionindex()
+        except Exception as e:
+            max_frames = 10
+            extraline = (
+                '!!! Recursion error detected, but an error occurred locating the origin of recursion.\n'
+                '  The following exception happened when comparing locals in the stack frame:\n'
+                '    {exc_type}: {exc_msg}\n'
+                '  Displaying first and last {max_frames} stack frames out of {total}.'
+            ).format(exc_type=type(e).__name__, exc_msg=safe_str(e), max_frames=max_frames, total=len(traceback))
+            traceback = traceback[:max_frames] + traceback[-max_frames:]
+        else:
+            if recursionindex is not None:
+                extraline = "!!! Recursion detected (same locals & position)"
+                traceback = traceback[:recursionindex + 1]
+            else:
+                extraline = None
+
+        return traceback, extraline
+
+    def repr_excinfo(self, excinfo):
+        if _PY2:
+            reprtraceback = self.repr_traceback(excinfo)
+            reprcrash = excinfo._getreprcrash()
+
+            return ReprExceptionInfo(reprtraceback, reprcrash)
+        else:
+            repr_chain = []
+            e = excinfo.value
+            descr = None
+            while e is not None:
+                if excinfo:
+                    reprtraceback = self.repr_traceback(excinfo)
+                    reprcrash = excinfo._getreprcrash()
+                else:
+                    # fallback to native repr if the exception doesn't have a traceback:
+                    # ExceptionInfo objects require a full traceback to work
+                    reprtraceback = ReprTracebackNative(py.std.traceback.format_exception(type(e), e, None))
+                    reprcrash = None
+
+                repr_chain += [(reprtraceback, reprcrash, descr)]
+                if e.__cause__ is not None:
+                    e = e.__cause__
+                    excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
+                    descr = 'The above exception was the direct cause of the following exception:'
+                elif (e.__context__ is not None and not e.__suppress_context__):
+                    e = e.__context__
+                    excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
+                    descr = 'During handling of the above exception, another exception occurred:'
+                else:
+                    e = None
+            repr_chain.reverse()
+            return ExceptionChainRepr(repr_chain)
+
+
+class TerminalRepr(object):
+    def __str__(self):
+        s = self.__unicode__()
+        if _PY2:
+            s = s.encode('utf-8')
+        return s
+
+    def __unicode__(self):
+        # FYI this is called from pytest-xdist's serialization of exception
+        # information.
+        io = py.io.TextIO()
+        tw = py.io.TerminalWriter(file=io)
+        self.toterminal(tw)
+        return io.getvalue().strip()
+
+    def __repr__(self):
+        return "<%s instance at %0x>" % (self.__class__, id(self))
+
+
+class ExceptionRepr(TerminalRepr):
+    def __init__(self):
+        self.sections = []
+
+    def addsection(self, name, content, sep="-"):
+        self.sections.append((name, content, sep))
+
+    def toterminal(self, tw):
+        for name, content, sep in self.sections:
+            tw.sep(sep, name)
+            tw.line(content)
+
+
+class ExceptionChainRepr(ExceptionRepr):
+    def __init__(self, chain):
+        super(ExceptionChainRepr, self).__init__()
+        self.chain = chain
+        # reprcrash and reprtraceback of the outermost (the newest) exception
+        # in the chain
+        self.reprtraceback = chain[-1][0]
+        self.reprcrash = chain[-1][1]
+
+    def toterminal(self, tw):
+        for element in self.chain:
+            element[0].toterminal(tw)
+            if element[2] is not None:
+                tw.line("")
+                tw.line(element[2], yellow=True)
+        super(ExceptionChainRepr, self).toterminal(tw)
+
+
+class ReprExceptionInfo(ExceptionRepr):
+    def __init__(self, reprtraceback, reprcrash):
+        super(ReprExceptionInfo, self).__init__()
+        self.reprtraceback = reprtraceback
+        self.reprcrash = reprcrash
+
+    def toterminal(self, tw):
+        self.reprtraceback.toterminal(tw)
+        super(ReprExceptionInfo, self).toterminal(tw)
+
+
+class ReprTraceback(TerminalRepr):
+    entrysep = "_ "
+
+    def __init__(self, reprentries, extraline, style):
+        self.reprentries = reprentries
+        self.extraline = extraline
+        self.style = style
+
+    def toterminal(self, tw):
+        # the entries might have different styles
+        for i, entry in enumerate(self.reprentries):
+            if entry.style == "long":
+                tw.line("")
+            entry.toterminal(tw)
+            if i < len(self.reprentries) - 1:
+                next_entry = self.reprentries[i + 1]
+                if entry.style == "long" or \
+                   entry.style == "short" and next_entry.style == "long":
+                    tw.sep(self.entrysep)
+
+        if self.extraline:
+            tw.line(self.extraline)
+
+
+class ReprTracebackNative(ReprTraceback):
+    def __init__(self, tblines):
+        self.style = "native"
+        self.reprentries = [ReprEntryNative(tblines)]
+        self.extraline = None
+
+
+class ReprEntryNative(TerminalRepr):
+    style = "native"
+
+    def __init__(self, tblines):
+        self.lines = tblines
+
+    def toterminal(self, tw):
+        tw.write("".join(self.lines))
+
+
+class ReprEntry(TerminalRepr):
+    localssep = "_ "
+
+    def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
+        self.lines = lines
+        self.reprfuncargs = reprfuncargs
+        self.reprlocals = reprlocals
+        self.reprfileloc = filelocrepr
+        self.style = style
+
+    def toterminal(self, tw):
+        if self.style == "short":
+            self.reprfileloc.toterminal(tw)
+            for line in self.lines:
+                red = line.startswith("E   ")
+                tw.line(line, bold=True, red=red)
+            # tw.line("")
+            return
+        if self.reprfuncargs:
+            self.reprfuncargs.toterminal(tw)
+        for line in self.lines:
+            red = line.startswith("E   ")
+            tw.line(line, bold=True, red=red)
+        if self.reprlocals:
+            # tw.sep(self.localssep, "Locals")
+            tw.line("")
+            self.reprlocals.toterminal(tw)
+        if self.reprfileloc:
+            if self.lines:
+                tw.line("")
+            self.reprfileloc.toterminal(tw)
+
+    def __str__(self):
+        return "%s\n%s\n%s" % ("\n".join(self.lines),
+                               self.reprlocals,
+                               self.reprfileloc)
+
+
+class ReprFileLocation(TerminalRepr):
+    def __init__(self, path, lineno, message):
+        self.path = str(path)
+        self.lineno = lineno
+        self.message = message
+
+    def toterminal(self, tw):
+        # filename and lineno output for each entry,
+        # using an output format that most editors unterstand
+        msg = self.message
+        i = msg.find("\n")
+        if i != -1:
+            msg = msg[:i]
+        tw.write(self.path, bold=True, red=True)
+        tw.line(":%s: %s" % (self.lineno, msg))
+
+
+class ReprLocals(TerminalRepr):
+    def __init__(self, lines):
+        self.lines = lines
+
+    def toterminal(self, tw):
+        for line in self.lines:
+            tw.line(line)
+
+
+class ReprFuncArgs(TerminalRepr):
+    def __init__(self, args):
+        self.args = args
+
+    def toterminal(self, tw):
+        if self.args:
+            linesofar = ""
+            for name, value in self.args:
+                ns = "%s = %s" % (safe_str(name), safe_str(value))
+                if len(ns) + len(linesofar) + 2 > tw.fullwidth:
+                    if linesofar:
+                        tw.line(linesofar)
+                    linesofar = ns
+                else:
+                    if linesofar:
+                        linesofar += ", " + ns
+                    else:
+                        linesofar = ns
+            if linesofar:
+                tw.line(linesofar)
+            tw.line("")
+
+
+def getrawcode(obj, trycall=True):
+    """ return code object for given function. """
+    try:
+        return obj.__code__
+    except AttributeError:
+        obj = getattr(obj, 'im_func', obj)
+        obj = getattr(obj, 'func_code', obj)
+        obj = getattr(obj, 'f_code', obj)
+        obj = getattr(obj, '__code__', obj)
+        if trycall and not hasattr(obj, 'co_firstlineno'):
+            if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
+                x = getrawcode(obj.__call__, trycall=False)
+                if hasattr(x, 'co_firstlineno'):
+                    return x
+        return obj
+
+
+if PY35:  # RecursionError introduced in 3.5
+    def is_recursion_error(excinfo):
+        return excinfo.errisinstance(RecursionError)  # noqa
+else:
+    def is_recursion_error(excinfo):
+        if not excinfo.errisinstance(RuntimeError):
+            return False
+        try:
+            return "maximum recursion depth exceeded" in str(excinfo.value)
+        except UnicodeError:
+            return False
diff --git a/tools/third_party/pytest/_pytest/_code/source.py b/tools/third_party/pytest/_pytest/_code/source.py
new file mode 100644
index 0000000..2638c59
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/_code/source.py
@@ -0,0 +1,415 @@
+from __future__ import absolute_import, division, generators, print_function
+
+from bisect import bisect_right
+import sys
+import six
+import inspect
+import tokenize
+import py
+cpy_compile = compile
+
+try:
+    import _ast
+    from _ast import PyCF_ONLY_AST as _AST_FLAG
+except ImportError:
+    _AST_FLAG = 0
+    _ast = None
+
+
+class Source(object):
+    """ a immutable object holding a source code fragment,
+        possibly deindenting it.
+    """
+    _compilecounter = 0
+
+    def __init__(self, *parts, **kwargs):
+        self.lines = lines = []
+        de = kwargs.get('deindent', True)
+        rstrip = kwargs.get('rstrip', True)
+        for part in parts:
+            if not part:
+                partlines = []
+            if isinstance(part, Source):
+                partlines = part.lines
+            elif isinstance(part, (tuple, list)):
+                partlines = [x.rstrip("\n") for x in part]
+            elif isinstance(part, six.string_types):
+                partlines = part.split('\n')
+                if rstrip:
+                    while partlines:
+                        if partlines[-1].strip():
+                            break
+                        partlines.pop()
+            else:
+                partlines = getsource(part, deindent=de).lines
+            if de:
+                partlines = deindent(partlines)
+            lines.extend(partlines)
+
+    def __eq__(self, other):
+        try:
+            return self.lines == other.lines
+        except AttributeError:
+            if isinstance(other, str):
+                return str(self) == other
+            return False
+
+    __hash__ = None
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            return self.lines[key]
+        else:
+            if key.step not in (None, 1):
+                raise IndexError("cannot slice a Source with a step")
+            newsource = Source()
+            newsource.lines = self.lines[key.start:key.stop]
+            return newsource
+
+    def __len__(self):
+        return len(self.lines)
+
+    def strip(self):
+        """ return new source object with trailing
+            and leading blank lines removed.
+        """
+        start, end = 0, len(self)
+        while start < end and not self.lines[start].strip():
+            start += 1
+        while end > start and not self.lines[end - 1].strip():
+            end -= 1
+        source = Source()
+        source.lines[:] = self.lines[start:end]
+        return source
+
+    def putaround(self, before='', after='', indent=' ' * 4):
+        """ return a copy of the source object with
+            'before' and 'after' wrapped around it.
+        """
+        before = Source(before)
+        after = Source(after)
+        newsource = Source()
+        lines = [(indent + line) for line in self.lines]
+        newsource.lines = before.lines + lines + after.lines
+        return newsource
+
+    def indent(self, indent=' ' * 4):
+        """ return a copy of the source object with
+            all lines indented by the given indent-string.
+        """
+        newsource = Source()
+        newsource.lines = [(indent + line) for line in self.lines]
+        return newsource
+
+    def getstatement(self, lineno, assertion=False):
+        """ return Source statement which contains the
+            given linenumber (counted from 0).
+        """
+        start, end = self.getstatementrange(lineno, assertion)
+        return self[start:end]
+
+    def getstatementrange(self, lineno, assertion=False):
+        """ return (start, end) tuple which spans the minimal
+            statement region which containing the given lineno.
+        """
+        if not (0 <= lineno < len(self)):
+            raise IndexError("lineno out of range")
+        ast, start, end = getstatementrange_ast(lineno, self)
+        return start, end
+
+    def deindent(self, offset=None):
+        """ return a new source object deindented by offset.
+            If offset is None then guess an indentation offset from
+            the first non-blank line.  Subsequent lines which have a
+            lower indentation offset will be copied verbatim as
+            they are assumed to be part of multilines.
+        """
+        # XXX maybe use the tokenizer to properly handle multiline
+        #     strings etc.pp?
+        newsource = Source()
+        newsource.lines[:] = deindent(self.lines, offset)
+        return newsource
+
+    def isparseable(self, deindent=True):
+        """ return True if source is parseable, heuristically
+            deindenting it by default.
+        """
+        try:
+            import parser
+        except ImportError:
+            def syntax_checker(x):
+                return compile(x, 'asd', 'exec')
+        else:
+            syntax_checker = parser.suite
+
+        if deindent:
+            source = str(self.deindent())
+        else:
+            source = str(self)
+        try:
+            # compile(source+'\n', "x", "exec")
+            syntax_checker(source + '\n')
+        except KeyboardInterrupt:
+            raise
+        except Exception:
+            return False
+        else:
+            return True
+
+    def __str__(self):
+        return "\n".join(self.lines)
+
+    def compile(self, filename=None, mode='exec',
+                flag=generators.compiler_flag,
+                dont_inherit=0, _genframe=None):
+        """ return compiled code object. if filename is None
+            invent an artificial filename which displays
+            the source/line position of the caller frame.
+        """
+        if not filename or py.path.local(filename).check(file=0):
+            if _genframe is None:
+                _genframe = sys._getframe(1)  # the caller
+            fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno
+            base = "<%d-codegen " % self._compilecounter
+            self.__class__._compilecounter += 1
+            if not filename:
+                filename = base + '%s:%d>' % (fn, lineno)
+            else:
+                filename = base + '%r %s:%d>' % (filename, fn, lineno)
+        source = "\n".join(self.lines) + '\n'
+        try:
+            co = cpy_compile(source, filename, mode, flag)
+        except SyntaxError:
+            ex = sys.exc_info()[1]
+            # re-represent syntax errors from parsing python strings
+            msglines = self.lines[:ex.lineno]
+            if ex.offset:
+                msglines.append(" " * ex.offset + '^')
+            msglines.append("(code was compiled probably from here: %s)" % filename)
+            newex = SyntaxError('\n'.join(msglines))
+            newex.offset = ex.offset
+            newex.lineno = ex.lineno
+            newex.text = ex.text
+            raise newex
+        else:
+            if flag & _AST_FLAG:
+                return co
+            lines = [(x + "\n") for x in self.lines]
+            py.std.linecache.cache[filename] = (1, None, lines, filename)
+            return co
+
+#
+# public API shortcut functions
+#
+
+
+def compile_(source, filename=None, mode='exec', flags=generators.compiler_flag, dont_inherit=0):
+    """ compile the given source to a raw code object,
+        and maintain an internal cache which allows later
+        retrieval of the source code for the code object
+        and any recursively created code objects.
+    """
+    if _ast is not None and isinstance(source, _ast.AST):
+        # XXX should Source support having AST?
+        return cpy_compile(source, filename, mode, flags, dont_inherit)
+    _genframe = sys._getframe(1)  # the caller
+    s = Source(source)
+    co = s.compile(filename, mode, flags, _genframe=_genframe)
+    return co
+
+
+def getfslineno(obj):
+    """ Return source location (path, lineno) for the given object.
+    If the source cannot be determined return ("", -1)
+    """
+    import _pytest._code
+    try:
+        code = _pytest._code.Code(obj)
+    except TypeError:
+        try:
+            fn = (py.std.inspect.getsourcefile(obj) or
+                  py.std.inspect.getfile(obj))
+        except TypeError:
+            return "", -1
+
+        fspath = fn and py.path.local(fn) or None
+        lineno = -1
+        if fspath:
+            try:
+                _, lineno = findsource(obj)
+            except IOError:
+                pass
+    else:
+        fspath = code.path
+        lineno = code.firstlineno
+    assert isinstance(lineno, int)
+    return fspath, lineno
+
+#
+# helper functions
+#
+
+
+def findsource(obj):
+    try:
+        sourcelines, lineno = py.std.inspect.findsource(obj)
+    except py.builtin._sysex:
+        raise
+    except:  # noqa
+        return None, -1
+    source = Source()
+    source.lines = [line.rstrip() for line in sourcelines]
+    return source, lineno
+
+
+def getsource(obj, **kwargs):
+    import _pytest._code
+    obj = _pytest._code.getrawcode(obj)
+    try:
+        strsrc = inspect.getsource(obj)
+    except IndentationError:
+        strsrc = "\"Buggy python version consider upgrading, cannot get source\""
+    assert isinstance(strsrc, str)
+    return Source(strsrc, **kwargs)
+
+
+def deindent(lines, offset=None):
+    if offset is None:
+        for line in lines:
+            line = line.expandtabs()
+            s = line.lstrip()
+            if s:
+                offset = len(line) - len(s)
+                break
+        else:
+            offset = 0
+    if offset == 0:
+        return list(lines)
+    newlines = []
+
+    def readline_generator(lines):
+        for line in lines:
+            yield line + '\n'
+        while True:
+            yield ''
+
+    it = readline_generator(lines)
+
+    try:
+        for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
+            if sline > len(lines):
+                break  # End of input reached
+            if sline > len(newlines):
+                line = lines[sline - 1].expandtabs()
+                if line.lstrip() and line[:offset].isspace():
+                    line = line[offset:]  # Deindent
+                newlines.append(line)
+
+            for i in range(sline, eline):
+                # Don't deindent continuing lines of
+                # multiline tokens (i.e. multiline strings)
+                newlines.append(lines[i])
+    except (IndentationError, tokenize.TokenError):
+        pass
+    # Add any lines we didn't see. E.g. if an exception was raised.
+    newlines.extend(lines[len(newlines):])
+    return newlines
+
+
+def get_statement_startend2(lineno, node):
+    import ast
+    # flatten all statements and except handlers into one lineno-list
+    # AST's line numbers start indexing at 1
+    values = []
+    for x in ast.walk(node):
+        if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
+            values.append(x.lineno - 1)
+            for name in "finalbody", "orelse":
+                val = getattr(x, name, None)
+                if val:
+                    # treat the finally/orelse part as its own statement
+                    values.append(val[0].lineno - 1 - 1)
+    values.sort()
+    insert_index = bisect_right(values, lineno)
+    start = values[insert_index - 1]
+    if insert_index >= len(values):
+        end = None
+    else:
+        end = values[insert_index]
+    return start, end
+
+
+def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
+    if astnode is None:
+        content = str(source)
+        try:
+            astnode = compile(content, "source", "exec", 1024)  # 1024 for AST
+        except ValueError:
+            start, end = getstatementrange_old(lineno, source, assertion)
+            return None, start, end
+    start, end = get_statement_startend2(lineno, astnode)
+    # we need to correct the end:
+    # - ast-parsing strips comments
+    # - there might be empty lines
+    # - we might have lesser indented code blocks at the end
+    if end is None:
+        end = len(source.lines)
+
+    if end > start + 1:
+        # make sure we don't span differently indented code blocks
+        # by using the BlockFinder helper used which inspect.getsource() uses itself
+        block_finder = inspect.BlockFinder()
+        # if we start with an indented line, put blockfinder to "started" mode
+        block_finder.started = source.lines[start][0].isspace()
+        it = ((x + "\n") for x in source.lines[start:end])
+        try:
+            for tok in tokenize.generate_tokens(lambda: next(it)):
+                block_finder.tokeneater(*tok)
+        except (inspect.EndOfBlock, IndentationError):
+            end = block_finder.last + start
+        except Exception:
+            pass
+
+    # the end might still point to a comment or empty line, correct it
+    while end:
+        line = source.lines[end - 1].lstrip()
+        if line.startswith("#") or not line:
+            end -= 1
+        else:
+            break
+    return astnode, start, end
+
+
+def getstatementrange_old(lineno, source, assertion=False):
+    """ return (start, end) tuple which spans the minimal
+        statement region which containing the given lineno.
+        raise an IndexError if no such statementrange can be found.
+    """
+    # XXX this logic is only used on python2.4 and below
+    # 1. find the start of the statement
+    from codeop import compile_command
+    for start in range(lineno, -1, -1):
+        if assertion:
+            line = source.lines[start]
+            # the following lines are not fully tested, change with care
+            if 'super' in line and 'self' in line and '__init__' in line:
+                raise IndexError("likely a subclass")
+            if "assert" not in line and "raise" not in line:
+                continue
+        trylines = source.lines[start:lineno + 1]
+        # quick hack to prepare parsing an indented line with
+        # compile_command() (which errors on "return" outside defs)
+        trylines.insert(0, 'def xxx():')
+        trysource = '\n '.join(trylines)
+        #              ^ space here
+        try:
+            compile_command(trysource)
+        except (SyntaxError, OverflowError, ValueError):
+            continue
+
+        # 2. find the end of the statement
+        for end in range(lineno + 1, len(source) + 1):
+            trysource = source[start:end]
+            if trysource.isparseable():
+                return start, end
+    raise SyntaxError("no valid source range around line %d " % (lineno,))
diff --git a/tools/third_party/pytest/_pytest/assertion/__init__.py b/tools/third_party/pytest/_pytest/assertion/__init__.py
new file mode 100644
index 0000000..a48e98c
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/assertion/__init__.py
@@ -0,0 +1,146 @@
+"""
+support for presenting detailed information in failing assertions.
+"""
+from __future__ import absolute_import, division, print_function
+import sys
+import six
+
+from _pytest.assertion import util
+from _pytest.assertion import rewrite
+from _pytest.assertion import truncate
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("debugconfig")
+    group.addoption('--assert',
+                    action="store",
+                    dest="assertmode",
+                    choices=("rewrite", "plain",),
+                    default="rewrite",
+                    metavar="MODE",
+                    help="""Control assertion debugging tools.  'plain'
+                            performs no assertion debugging.  'rewrite'
+                            (the default) rewrites assert statements in
+                            test modules on import to provide assert
+                            expression information.""")
+
+
+def register_assert_rewrite(*names):
+    """Register one or more module names to be rewritten on import.
+
+    This function will make sure that this module or all modules inside
+    the package will get their assert statements rewritten.
+    Thus you should make sure to call this before the module is
+    actually imported, usually in your __init__.py if you are a plugin
+    using a package.
+
+    :raise TypeError: if the given module names are not strings.
+    """
+    for name in names:
+        if not isinstance(name, str):
+            msg = 'expected module names as *args, got {0} instead'
+            raise TypeError(msg.format(repr(names)))
+    for hook in sys.meta_path:
+        if isinstance(hook, rewrite.AssertionRewritingHook):
+            importhook = hook
+            break
+    else:
+        importhook = DummyRewriteHook()
+    importhook.mark_rewrite(*names)
+
+
+class DummyRewriteHook(object):
+    """A no-op import hook for when rewriting is disabled."""
+
+    def mark_rewrite(self, *names):
+        pass
+
+
+class AssertionState:
+    """State for the assertion plugin."""
+
+    def __init__(self, config, mode):
+        self.mode = mode
+        self.trace = config.trace.root.get("assertion")
+        self.hook = None
+
+
+def install_importhook(config):
+    """Try to install the rewrite hook, raise SystemError if it fails."""
+    # Jython has an AST bug that make the assertion rewriting hook malfunction.
+    if (sys.platform.startswith('java')):
+        raise SystemError('rewrite not supported')
+
+    config._assertstate = AssertionState(config, 'rewrite')
+    config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
+    sys.meta_path.insert(0, hook)
+    config._assertstate.trace('installed rewrite import hook')
+
+    def undo():
+        hook = config._assertstate.hook
+        if hook is not None and hook in sys.meta_path:
+            sys.meta_path.remove(hook)
+
+    config.add_cleanup(undo)
+    return hook
+
+
+def pytest_collection(session):
+    # this hook is only called when test modules are collected
+    # so for example not in the master process of pytest-xdist
+    # (which does not collect test modules)
+    assertstate = getattr(session.config, '_assertstate', None)
+    if assertstate:
+        if assertstate.hook is not None:
+            assertstate.hook.set_session(session)
+
+
+def pytest_runtest_setup(item):
+    """Setup the pytest_assertrepr_compare hook
+
+    The newinterpret and rewrite modules will use util._reprcompare if
+    it exists to use custom reporting via the
+    pytest_assertrepr_compare hook.  This sets up this custom
+    comparison for the test.
+    """
+    def callbinrepr(op, left, right):
+        """Call the pytest_assertrepr_compare hook and prepare the result
+
+        This uses the first result from the hook and then ensures the
+        following:
+        * Overly verbose explanations are truncated unless configured otherwise
+          (eg. if running in verbose mode).
+        * Embedded newlines are escaped to help util.format_explanation()
+          later.
+        * If the rewrite mode is used embedded %-characters are replaced
+          to protect later % formatting.
+
+        The result can be formatted by util.format_explanation() for
+        pretty printing.
+        """
+        hook_result = item.ihook.pytest_assertrepr_compare(
+            config=item.config, op=op, left=left, right=right)
+        for new_expl in hook_result:
+            if new_expl:
+                new_expl = truncate.truncate_if_required(new_expl, item)
+                new_expl = [line.replace("\n", "\\n") for line in new_expl]
+                res = six.text_type("\n~").join(new_expl)
+                if item.config.getvalue("assertmode") == "rewrite":
+                    res = res.replace("%", "%%")
+                return res
+    util._reprcompare = callbinrepr
+
+
+def pytest_runtest_teardown(item):
+    util._reprcompare = None
+
+
+def pytest_sessionfinish(session):
+    assertstate = getattr(session.config, '_assertstate', None)
+    if assertstate:
+        if assertstate.hook is not None:
+            assertstate.hook.set_session(None)
+
+
+# Expose this plugin's implementation for the pytest_assertrepr_compare hook
+pytest_assertrepr_compare = util.assertrepr_compare
diff --git a/tools/third_party/pytest/_pytest/assertion/rewrite.py b/tools/third_party/pytest/_pytest/assertion/rewrite.py
new file mode 100644
index 0000000..f64358f
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/assertion/rewrite.py
@@ -0,0 +1,948 @@
+"""Rewrite assertion AST to produce nice error messages"""
+from __future__ import absolute_import, division, print_function
+import ast
+import _ast
+import errno
+import itertools
+import imp
+import marshal
+import os
+import re
+import six
+import struct
+import sys
+import types
+
+import py
+from _pytest.assertion import util
+
+
+# pytest caches rewritten pycs in __pycache__.
+if hasattr(imp, "get_tag"):
+    PYTEST_TAG = imp.get_tag() + "-PYTEST"
+else:
+    if hasattr(sys, "pypy_version_info"):
+        impl = "pypy"
+    elif sys.platform == "java":
+        impl = "jython"
+    else:
+        impl = "cpython"
+    ver = sys.version_info
+    PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
+    del ver, impl
+
+PYC_EXT = ".py" + (__debug__ and "c" or "o")
+PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
+
+ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
+
+if sys.version_info >= (3, 5):
+    ast_Call = ast.Call
+else:
+    def ast_Call(a, b, c):
+        return ast.Call(a, b, c, None, None)
+
+
+class AssertionRewritingHook(object):
+    """PEP302 Import hook which rewrites asserts."""
+
+    def __init__(self, config):
+        self.config = config
+        self.fnpats = config.getini("python_files")
+        self.session = None
+        self.modules = {}
+        self._rewritten_names = set()
+        self._register_with_pkg_resources()
+        self._must_rewrite = set()
+
+    def set_session(self, session):
+        self.session = session
+
+    def find_module(self, name, path=None):
+        state = self.config._assertstate
+        state.trace("find_module called for: %s" % name)
+        names = name.rsplit(".", 1)
+        lastname = names[-1]
+        pth = None
+        if path is not None:
+            # Starting with Python 3.3, path is a _NamespacePath(), which
+            # causes problems if not converted to list.
+            path = list(path)
+            if len(path) == 1:
+                pth = path[0]
+        if pth is None:
+            try:
+                fd, fn, desc = imp.find_module(lastname, path)
+            except ImportError:
+                return None
+            if fd is not None:
+                fd.close()
+            tp = desc[2]
+            if tp == imp.PY_COMPILED:
+                if hasattr(imp, "source_from_cache"):
+                    try:
+                        fn = imp.source_from_cache(fn)
+                    except ValueError:
+                        # Python 3 doesn't like orphaned but still-importable
+                        # .pyc files.
+                        fn = fn[:-1]
+                else:
+                    fn = fn[:-1]
+            elif tp != imp.PY_SOURCE:
+                # Don't know what this is.
+                return None
+        else:
+            fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
+
+        fn_pypath = py.path.local(fn)
+        if not self._should_rewrite(name, fn_pypath, state):
+            return None
+
+        self._rewritten_names.add(name)
+
+        # The requested module looks like a test file, so rewrite it. This is
+        # the most magical part of the process: load the source, rewrite the
+        # asserts, and load the rewritten source. We also cache the rewritten
+        # module code in a special pyc. We must be aware of the possibility of
+        # concurrent pytest processes rewriting and loading pycs. To avoid
+        # tricky race conditions, we maintain the following invariant: The
+        # cached pyc is always a complete, valid pyc. Operations on it must be
+        # atomic. POSIX's atomic rename comes in handy.
+        write = not sys.dont_write_bytecode
+        cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
+        if write:
+            try:
+                os.mkdir(cache_dir)
+            except OSError:
+                e = sys.exc_info()[1].errno
+                if e == errno.EEXIST:
+                    # Either the __pycache__ directory already exists (the
+                    # common case) or it's blocked by a non-dir node. In the
+                    # latter case, we'll ignore it in _write_pyc.
+                    pass
+                elif e in [errno.ENOENT, errno.ENOTDIR]:
+                    # One of the path components was not a directory, likely
+                    # because we're in a zip file.
+                    write = False
+                elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
+                    state.trace("read only directory: %r" % fn_pypath.dirname)
+                    write = False
+                else:
+                    raise
+        cache_name = fn_pypath.basename[:-3] + PYC_TAIL
+        pyc = os.path.join(cache_dir, cache_name)
+        # Notice that even if we're in a read-only directory, I'm going
+        # to check for a cached pyc. This may not be optimal...
+        co = _read_pyc(fn_pypath, pyc, state.trace)
+        if co is None:
+            state.trace("rewriting %r" % (fn,))
+            source_stat, co = _rewrite_test(self.config, fn_pypath)
+            if co is None:
+                # Probably a SyntaxError in the test.
+                return None
+            if write:
+                _make_rewritten_pyc(state, source_stat, pyc, co)
+        else:
+            state.trace("found cached rewritten pyc for %r" % (fn,))
+        self.modules[name] = co, pyc
+        return self
+
+    def _should_rewrite(self, name, fn_pypath, state):
+        # always rewrite conftest files
+        fn = str(fn_pypath)
+        if fn_pypath.basename == 'conftest.py':
+            state.trace("rewriting conftest file: %r" % (fn,))
+            return True
+
+        if self.session is not None:
+            if self.session.isinitpath(fn):
+                state.trace("matched test file (was specified on cmdline): %r" %
+                            (fn,))
+                return True
+
+        # modules not passed explicitly on the command line are only
+        # rewritten if they match the naming convention for test files
+        for pat in self.fnpats:
+            if fn_pypath.fnmatch(pat):
+                state.trace("matched test file %r" % (fn,))
+                return True
+
+        for marked in self._must_rewrite:
+            if name == marked or name.startswith(marked + '.'):
+                state.trace("matched marked file %r (from %r)" % (name, marked))
+                return True
+
+        return False
+
+    def mark_rewrite(self, *names):
+        """Mark import names as needing to be rewritten.
+
+        The named module or package as well as any nested modules will
+        be rewritten on import.
+        """
+        already_imported = set(names).intersection(set(sys.modules))
+        if already_imported:
+            for name in already_imported:
+                if name not in self._rewritten_names:
+                    self._warn_already_imported(name)
+        self._must_rewrite.update(names)
+
+    def _warn_already_imported(self, name):
+        self.config.warn(
+            'P1',
+            'Module already imported so cannot be rewritten: %s' % name)
+
+    def load_module(self, name):
+        # If there is an existing module object named 'fullname' in
+        # sys.modules, the loader must use that existing module. (Otherwise,
+        # the reload() builtin will not work correctly.)
+        if name in sys.modules:
+            return sys.modules[name]
+
+        co, pyc = self.modules.pop(name)
+        # I wish I could just call imp.load_compiled here, but __file__ has to
+        # be set properly. In Python 3.2+, this all would be handled correctly
+        # by load_compiled.
+        mod = sys.modules[name] = imp.new_module(name)
+        try:
+            mod.__file__ = co.co_filename
+            # Normally, this attribute is 3.2+.
+            mod.__cached__ = pyc
+            mod.__loader__ = self
+            py.builtin.exec_(co, mod.__dict__)
+        except:  # noqa
+            if name in sys.modules:
+                del sys.modules[name]
+            raise
+        return sys.modules[name]
+
+    def is_package(self, name):
+        try:
+            fd, fn, desc = imp.find_module(name)
+        except ImportError:
+            return False
+        if fd is not None:
+            fd.close()
+        tp = desc[2]
+        return tp == imp.PKG_DIRECTORY
+
+    @classmethod
+    def _register_with_pkg_resources(cls):
+        """
+        Ensure package resources can be loaded from this loader. May be called
+        multiple times, as the operation is idempotent.
+        """
+        try:
+            import pkg_resources
+            # access an attribute in case a deferred importer is present
+            pkg_resources.__name__
+        except ImportError:
+            return
+
+        # Since pytest tests are always located in the file system, the
+        #  DefaultProvider is appropriate.
+        pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider)
+
+    def get_data(self, pathname):
+        """Optional PEP302 get_data API.
+        """
+        with open(pathname, 'rb') as f:
+            return f.read()
+
+
+def _write_pyc(state, co, source_stat, pyc):
+    # Technically, we don't have to have the same pyc format as
+    # (C)Python, since these "pycs" should never be seen by builtin
+    # import. However, there's little reason deviate, and I hope
+    # sometime to be able to use imp.load_compiled to load them. (See
+    # the comment in load_module above.)
+    try:
+        fp = open(pyc, "wb")
+    except IOError:
+        err = sys.exc_info()[1].errno
+        state.trace("error writing pyc file at %s: errno=%s" % (pyc, err))
+        # we ignore any failure to write the cache file
+        # there are many reasons, permission-denied, __pycache__ being a
+        # file etc.
+        return False
+    try:
+        fp.write(imp.get_magic())
+        mtime = int(source_stat.mtime)
+        size = source_stat.size & 0xFFFFFFFF
+        fp.write(struct.pack("<ll", mtime, size))
+        marshal.dump(co, fp)
+    finally:
+        fp.close()
+    return True
+
+
+RN = "\r\n".encode("utf-8")
+N = "\n".encode("utf-8")
+
+cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
+BOM_UTF8 = '\xef\xbb\xbf'
+
+
+def _rewrite_test(config, fn):
+    """Try to read and rewrite *fn* and return the code object."""
+    state = config._assertstate
+    try:
+        stat = fn.stat()
+        source = fn.read("rb")
+    except EnvironmentError:
+        return None, None
+    if ASCII_IS_DEFAULT_ENCODING:
+        # ASCII is the default encoding in Python 2. Without a coding
+        # declaration, Python 2 will complain about any bytes in the file
+        # outside the ASCII range. Sadly, this behavior does not extend to
+        # compile() or ast.parse(), which prefer to interpret the bytes as
+        # latin-1. (At least they properly handle explicit coding cookies.) To
+        # preserve this error behavior, we could force ast.parse() to use ASCII
+        # as the encoding by inserting a coding cookie. Unfortunately, that
+        # messes up line numbers. Thus, we have to check ourselves if anything
+        # is outside the ASCII range in the case no encoding is explicitly
+        # declared. For more context, see issue #269. Yay for Python 3 which
+        # gets this right.
+        end1 = source.find("\n")
+        end2 = source.find("\n", end1 + 1)
+        if (not source.startswith(BOM_UTF8) and
+            cookie_re.match(source[0:end1]) is None and
+                cookie_re.match(source[end1 + 1:end2]) is None):
+            if hasattr(state, "_indecode"):
+                # encodings imported us again, so don't rewrite.
+                return None, None
+            state._indecode = True
+            try:
+                try:
+                    source.decode("ascii")
+                except UnicodeDecodeError:
+                    # Let it fail in real import.
+                    return None, None
+            finally:
+                del state._indecode
+    try:
+        tree = ast.parse(source)
+    except SyntaxError:
+        # Let this pop up again in the real import.
+        state.trace("failed to parse: %r" % (fn,))
+        return None, None
+    rewrite_asserts(tree, fn, config)
+    try:
+        co = compile(tree, fn.strpath, "exec", dont_inherit=True)
+    except SyntaxError:
+        # It's possible that this error is from some bug in the
+        # assertion rewriting, but I don't know of a fast way to tell.
+        state.trace("failed to compile: %r" % (fn,))
+        return None, None
+    return stat, co
+
+
+def _make_rewritten_pyc(state, source_stat, pyc, co):
+    """Try to dump rewritten code to *pyc*."""
+    if sys.platform.startswith("win"):
+        # Windows grants exclusive access to open files and doesn't have atomic
+        # rename, so just write into the final file.
+        _write_pyc(state, co, source_stat, pyc)
+    else:
+        # When not on windows, assume rename is atomic. Dump the code object
+        # into a file specific to this process and atomically replace it.
+        proc_pyc = pyc + "." + str(os.getpid())
+        if _write_pyc(state, co, source_stat, proc_pyc):
+            os.rename(proc_pyc, pyc)
+
+
+def _read_pyc(source, pyc, trace=lambda x: None):
+    """Possibly read a pytest pyc containing rewritten code.
+
+    Return rewritten code if successful or None if not.
+    """
+    try:
+        fp = open(pyc, "rb")
+    except IOError:
+        return None
+    with fp:
+        try:
+            mtime = int(source.mtime())
+            size = source.size()
+            data = fp.read(12)
+        except EnvironmentError as e:
+            trace('_read_pyc(%s): EnvironmentError %s' % (source, e))
+            return None
+        # Check for invalid or out of date pyc file.
+        if (len(data) != 12 or data[:4] != imp.get_magic() or
+                struct.unpack("<ll", data[4:]) != (mtime, size)):
+            trace('_read_pyc(%s): invalid or out of date pyc' % source)
+            return None
+        try:
+            co = marshal.load(fp)
+        except Exception as e:
+            trace('_read_pyc(%s): marshal.load error %s' % (source, e))
+            return None
+        if not isinstance(co, types.CodeType):
+            trace('_read_pyc(%s): not a code object' % source)
+            return None
+        return co
+
+
+def rewrite_asserts(mod, module_path=None, config=None):
+    """Rewrite the assert statements in mod."""
+    AssertionRewriter(module_path, config).run(mod)
+
+
+def _saferepr(obj):
+    """Get a safe repr of an object for assertion error messages.
+
+    The assertion formatting (util.format_explanation()) requires
+    newlines to be escaped since they are a special character for it.
+    Normally assertion.util.format_explanation() does this but for a
+    custom repr it is possible to contain one of the special escape
+    sequences, especially '\n{' and '\n}' are likely to be present in
+    JSON reprs.
+
+    """
+    repr = py.io.saferepr(obj)
+    if isinstance(repr, six.text_type):
+        t = six.text_type
+    else:
+        t = six.binary_type
+    return repr.replace(t("\n"), t("\\n"))
+
+
+from _pytest.assertion.util import format_explanation as _format_explanation  # noqa
+
+
+def _format_assertmsg(obj):
+    """Format the custom assertion message given.
+
+    For strings this simply replaces newlines with '\n~' so that
+    util.format_explanation() will preserve them instead of escaping
+    newlines.  For other objects py.io.saferepr() is used first.
+
+    """
+    # reprlib appears to have a bug which means that if a string
+    # contains a newline it gets escaped, however if an object has a
+    # .__repr__() which contains newlines it does not get escaped.
+    # However in either case we want to preserve the newline.
+    if isinstance(obj, six.text_type) or isinstance(obj, six.binary_type):
+        s = obj
+        is_repr = False
+    else:
+        s = py.io.saferepr(obj)
+        is_repr = True
+    if isinstance(s, six.text_type):
+        t = six.text_type
+    else:
+        t = six.binary_type
+    s = s.replace(t("\n"), t("\n~")).replace(t("%"), t("%%"))
+    if is_repr:
+        s = s.replace(t("\\n"), t("\n~"))
+    return s
+
+
+def _should_repr_global_name(obj):
+    return not hasattr(obj, "__name__") and not callable(obj)
+
+
+def _format_boolop(explanations, is_or):
+    explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
+    if isinstance(explanation, six.text_type):
+        t = six.text_type
+    else:
+        t = six.binary_type
+    return explanation.replace(t('%'), t('%%'))
+
+
+def _call_reprcompare(ops, results, expls, each_obj):
+    for i, res, expl in zip(range(len(ops)), results, expls):
+        try:
+            done = not res
+        except Exception:
+            done = True
+        if done:
+            break
+    if util._reprcompare is not None:
+        custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
+        if custom is not None:
+            return custom
+    return expl
+
+
+unary_map = {
+    ast.Not: "not %s",
+    ast.Invert: "~%s",
+    ast.USub: "-%s",
+    ast.UAdd: "+%s"
+}
+
+binop_map = {
+    ast.BitOr: "|",
+    ast.BitXor: "^",
+    ast.BitAnd: "&",
+    ast.LShift: "<<",
+    ast.RShift: ">>",
+    ast.Add: "+",
+    ast.Sub: "-",
+    ast.Mult: "*",
+    ast.Div: "/",
+    ast.FloorDiv: "//",
+    ast.Mod: "%%",  # escaped for string formatting
+    ast.Eq: "==",
+    ast.NotEq: "!=",
+    ast.Lt: "<",
+    ast.LtE: "<=",
+    ast.Gt: ">",
+    ast.GtE: ">=",
+    ast.Pow: "**",
+    ast.Is: "is",
+    ast.IsNot: "is not",
+    ast.In: "in",
+    ast.NotIn: "not in"
+}
+# Python 3.5+ compatibility
+try:
+    binop_map[ast.MatMult] = "@"
+except AttributeError:
+    pass
+
+# Python 3.4+ compatibility
+if hasattr(ast, "NameConstant"):
+    _NameConstant = ast.NameConstant
+else:
+    def _NameConstant(c):
+        return ast.Name(str(c), ast.Load())
+
+
+def set_location(node, lineno, col_offset):
+    """Set node location information recursively."""
+    def _fix(node, lineno, col_offset):
+        if "lineno" in node._attributes:
+            node.lineno = lineno
+        if "col_offset" in node._attributes:
+            node.col_offset = col_offset
+        for child in ast.iter_child_nodes(node):
+            _fix(child, lineno, col_offset)
+    _fix(node, lineno, col_offset)
+    return node
+
+
+class AssertionRewriter(ast.NodeVisitor):
+    """Assertion rewriting implementation.
+
+    The main entrypoint is to call .run() with an ast.Module instance,
+    this will then find all the assert statements and rewrite them to
+    provide intermediate values and a detailed assertion error.  See
+    http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html
+    for an overview of how this works.
+
+    The entry point here is .run() which will iterate over all the
+    statements in an ast.Module and for each ast.Assert statement it
+    finds call .visit() with it.  Then .visit_Assert() takes over and
+    is responsible for creating new ast statements to replace the
+    original assert statement: it rewrites the test of an assertion
+    to provide intermediate values and replace it with an if statement
+    which raises an assertion error with a detailed explanation in
+    case the expression is false.
+
+    For this .visit_Assert() uses the visitor pattern to visit all the
+    AST nodes of the ast.Assert.test field, each visit call returning
+    an AST node and the corresponding explanation string.  During this
+    state is kept in several instance attributes:
+
+    :statements: All the AST statements which will replace the assert
+       statement.
+
+    :variables: This is populated by .variable() with each variable
+       used by the statements so that they can all be set to None at
+       the end of the statements.
+
+    :variable_counter: Counter to create new unique variables needed
+       by statements.  Variables are created using .variable() and
+       have the form of "@py_assert0".
+
+    :on_failure: The AST statements which will be executed if the
+       assertion test fails.  This is the code which will construct
+       the failure message and raises the AssertionError.
+
+    :explanation_specifiers: A dict filled by .explanation_param()
+       with %-formatting placeholders and their corresponding
+       expressions to use in the building of an assertion message.
+       This is used by .pop_format_context() to build a message.
+
+    :stack: A stack of the explanation_specifiers dicts maintained by
+       .push_format_context() and .pop_format_context() which allows
+       to build another %-formatted string while already building one.
+
+    This state is reset on every new assert statement visited and used
+    by the other visitors.
+
+    """
+
+    def __init__(self, module_path, config):
+        super(AssertionRewriter, self).__init__()
+        self.module_path = module_path
+        self.config = config
+
+    def run(self, mod):
+        """Find all assert statements in *mod* and rewrite them."""
+        if not mod.body:
+            # Nothing to do.
+            return
+        # Insert some special imports at the top of the module but after any
+        # docstrings and __future__ imports.
+        aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
+                   ast.alias("_pytest.assertion.rewrite", "@pytest_ar")]
+        doc = getattr(mod, "docstring", None)
+        expect_docstring = doc is None
+        if doc is not None and self.is_rewrite_disabled(doc):
+            return
+        pos = 0
+        lineno = 1
+        for item in mod.body:
+            if (expect_docstring and isinstance(item, ast.Expr) and
+                    isinstance(item.value, ast.Str)):
+                doc = item.value.s
+                if self.is_rewrite_disabled(doc):
+                    return
+                expect_docstring = False
+            elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or
+                  item.module != "__future__"):
+                lineno = item.lineno
+                break
+            pos += 1
+        else:
+            lineno = item.lineno
+        imports = [ast.Import([alias], lineno=lineno, col_offset=0)
+                   for alias in aliases]
+        mod.body[pos:pos] = imports
+        # Collect asserts.
+        nodes = [mod]
+        while nodes:
+            node = nodes.pop()
+            for name, field in ast.iter_fields(node):
+                if isinstance(field, list):
+                    new = []
+                    for i, child in enumerate(field):
+                        if isinstance(child, ast.Assert):
+                            # Transform assert.
+                            new.extend(self.visit(child))
+                        else:
+                            new.append(child)
+                            if isinstance(child, ast.AST):
+                                nodes.append(child)
+                    setattr(node, name, new)
+                elif (isinstance(field, ast.AST) and
+                      # Don't recurse into expressions as they can't contain
+                      # asserts.
+                      not isinstance(field, ast.expr)):
+                    nodes.append(field)
+
+    def is_rewrite_disabled(self, docstring):
+        return "PYTEST_DONT_REWRITE" in docstring
+
+    def variable(self):
+        """Get a new variable."""
+        # Use a character invalid in python identifiers to avoid clashing.
+        name = "@py_assert" + str(next(self.variable_counter))
+        self.variables.append(name)
+        return name
+
+    def assign(self, expr):
+        """Give *expr* a name."""
+        name = self.variable()
+        self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
+        return ast.Name(name, ast.Load())
+
+    def display(self, expr):
+        """Call py.io.saferepr on the expression."""
+        return self.helper("saferepr", expr)
+
+    def helper(self, name, *args):
+        """Call a helper in this module."""
+        py_name = ast.Name("@pytest_ar", ast.Load())
+        attr = ast.Attribute(py_name, "_" + name, ast.Load())
+        return ast_Call(attr, list(args), [])
+
+    def builtin(self, name):
+        """Return the builtin called *name*."""
+        builtin_name = ast.Name("@py_builtins", ast.Load())
+        return ast.Attribute(builtin_name, name, ast.Load())
+
+    def explanation_param(self, expr):
+        """Return a new named %-formatting placeholder for expr.
+
+        This creates a %-formatting placeholder for expr in the
+        current formatting context, e.g. ``%(py0)s``.  The placeholder
+        and expr are placed in the current format context so that it
+        can be used on the next call to .pop_format_context().
+
+        """
+        specifier = "py" + str(next(self.variable_counter))
+        self.explanation_specifiers[specifier] = expr
+        return "%(" + specifier + ")s"
+
+    def push_format_context(self):
+        """Create a new formatting context.
+
+        The format context is used for when an explanation wants to
+        have a variable value formatted in the assertion message.  In
+        this case the value required can be added using
+        .explanation_param().  Finally .pop_format_context() is used
+        to format a string of %-formatted values as added by
+        .explanation_param().
+
+        """
+        self.explanation_specifiers = {}
+        self.stack.append(self.explanation_specifiers)
+
+    def pop_format_context(self, expl_expr):
+        """Format the %-formatted string with current format context.
+
+        The expl_expr should be an ast.Str instance constructed from
+        the %-placeholders created by .explanation_param().  This will
+        add the required code to format said string to .on_failure and
+        return the ast.Name instance of the formatted string.
+
+        """
+        current = self.stack.pop()
+        if self.stack:
+            self.explanation_specifiers = self.stack[-1]
+        keys = [ast.Str(key) for key in current.keys()]
+        format_dict = ast.Dict(keys, list(current.values()))
+        form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
+        name = "@py_format" + str(next(self.variable_counter))
+        self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
+        return ast.Name(name, ast.Load())
+
+    def generic_visit(self, node):
+        """Handle expressions we don't have custom code for."""
+        assert isinstance(node, ast.expr)
+        res = self.assign(node)
+        return res, self.explanation_param(self.display(res))
+
+    def visit_Assert(self, assert_):
+        """Return the AST statements to replace the ast.Assert instance.
+
+        This rewrites the test of an assertion to provide
+        intermediate values and replace it with an if statement which
+        raises an assertion error with a detailed explanation in case
+        the expression is false.
+
+        """
+        if isinstance(assert_.test, ast.Tuple) and self.config is not None:
+            fslocation = (self.module_path, assert_.lineno)
+            self.config.warn('R1', 'assertion is always true, perhaps '
+                             'remove parentheses?', fslocation=fslocation)
+        self.statements = []
+        self.variables = []
+        self.variable_counter = itertools.count()
+        self.stack = []
+        self.on_failure = []
+        self.push_format_context()
+        # Rewrite assert into a bunch of statements.
+        top_condition, explanation = self.visit(assert_.test)
+        # Create failure message.
+        body = self.on_failure
+        negation = ast.UnaryOp(ast.Not(), top_condition)
+        self.statements.append(ast.If(negation, body, []))
+        if assert_.msg:
+            assertmsg = self.helper('format_assertmsg', assert_.msg)
+            explanation = "\n>assert " + explanation
+        else:
+            assertmsg = ast.Str("")
+            explanation = "assert " + explanation
+        template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
+        msg = self.pop_format_context(template)
+        fmt = self.helper("format_explanation", msg)
+        err_name = ast.Name("AssertionError", ast.Load())
+        exc = ast_Call(err_name, [fmt], [])
+        if sys.version_info[0] >= 3:
+            raise_ = ast.Raise(exc, None)
+        else:
+            raise_ = ast.Raise(exc, None, None)
+        body.append(raise_)
+        # Clear temporary variables by setting them to None.
+        if self.variables:
+            variables = [ast.Name(name, ast.Store())
+                         for name in self.variables]
+            clear = ast.Assign(variables, _NameConstant(None))
+            self.statements.append(clear)
+        # Fix line numbers.
+        for stmt in self.statements:
+            set_location(stmt, assert_.lineno, assert_.col_offset)
+        return self.statements
+
+    def visit_Name(self, name):
+        # Display the repr of the name if it's a local variable or
+        # _should_repr_global_name() thinks it's acceptable.
+        locs = ast_Call(self.builtin("locals"), [], [])
+        inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
+        dorepr = self.helper("should_repr_global_name", name)
+        test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
+        expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
+        return name, self.explanation_param(expr)
+
+    def visit_BoolOp(self, boolop):
+        res_var = self.variable()
+        expl_list = self.assign(ast.List([], ast.Load()))
+        app = ast.Attribute(expl_list, "append", ast.Load())
+        is_or = int(isinstance(boolop.op, ast.Or))
+        body = save = self.statements
+        fail_save = self.on_failure
+        levels = len(boolop.values) - 1
+        self.push_format_context()
+        # Process each operand, short-circuting if needed.
+        for i, v in enumerate(boolop.values):
+            if i:
+                fail_inner = []
+                # cond is set in a prior loop iteration below
+                self.on_failure.append(ast.If(cond, fail_inner, []))  # noqa
+                self.on_failure = fail_inner
+            self.push_format_context()
+            res, expl = self.visit(v)
+            body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
+            expl_format = self.pop_format_context(ast.Str(expl))
+            call = ast_Call(app, [expl_format], [])
+            self.on_failure.append(ast.Expr(call))
+            if i < levels:
+                cond = res
+                if is_or:
+                    cond = ast.UnaryOp(ast.Not(), cond)
+                inner = []
+                self.statements.append(ast.If(cond, inner, []))
+                self.statements = body = inner
+        self.statements = save
+        self.on_failure = fail_save
+        expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or))
+        expl = self.pop_format_context(expl_template)
+        return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
+
+    def visit_UnaryOp(self, unary):
+        pattern = unary_map[unary.op.__class__]
+        operand_res, operand_expl = self.visit(unary.operand)
+        res = self.assign(ast.UnaryOp(unary.op, operand_res))
+        return res, pattern % (operand_expl,)
+
+    def visit_BinOp(self, binop):
+        symbol = binop_map[binop.op.__class__]
+        left_expr, left_expl = self.visit(binop.left)
+        right_expr, right_expl = self.visit(binop.right)
+        explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
+        res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
+        return res, explanation
+
+    def visit_Call_35(self, call):
+        """
+        visit `ast.Call` nodes on Python3.5 and after
+        """
+        new_func, func_expl = self.visit(call.func)
+        arg_expls = []
+        new_args = []
+        new_kwargs = []
+        for arg in call.args:
+            res, expl = self.visit(arg)
+            arg_expls.append(expl)
+            new_args.append(res)
+        for keyword in call.keywords:
+            res, expl = self.visit(keyword.value)
+            new_kwargs.append(ast.keyword(keyword.arg, res))
+            if keyword.arg:
+                arg_expls.append(keyword.arg + "=" + expl)
+            else:  # **args have `arg` keywords with an .arg of None
+                arg_expls.append("**" + expl)
+
+        expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
+        new_call = ast.Call(new_func, new_args, new_kwargs)
+        res = self.assign(new_call)
+        res_expl = self.explanation_param(self.display(res))
+        outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
+        return res, outer_expl
+
+    def visit_Starred(self, starred):
+        # From Python 3.5, a Starred node can appear in a function call
+        res, expl = self.visit(starred.value)
+        return starred, '*' + expl
+
+    def visit_Call_legacy(self, call):
+        """
+        visit `ast.Call nodes on 3.4 and below`
+        """
+        new_func, func_expl = self.visit(call.func)
+        arg_expls = []
+        new_args = []
+        new_kwargs = []
+        new_star = new_kwarg = None
+        for arg in call.args:
+            res, expl = self.visit(arg)
+            new_args.append(res)
+            arg_expls.append(expl)
+        for keyword in call.keywords:
+            res, expl = self.visit(keyword.value)
+            new_kwargs.append(ast.keyword(keyword.arg, res))
+            arg_expls.append(keyword.arg + "=" + expl)
+        if call.starargs:
+            new_star, expl = self.visit(call.starargs)
+            arg_expls.append("*" + expl)
+        if call.kwargs:
+            new_kwarg, expl = self.visit(call.kwargs)
+            arg_expls.append("**" + expl)
+        expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
+        new_call = ast.Call(new_func, new_args, new_kwargs,
+                            new_star, new_kwarg)
+        res = self.assign(new_call)
+        res_expl = self.explanation_param(self.display(res))
+        outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
+        return res, outer_expl
+
+    # ast.Call signature changed on 3.5,
+    # conditionally change  which methods is named
+    # visit_Call depending on Python version
+    if sys.version_info >= (3, 5):
+        visit_Call = visit_Call_35
+    else:
+        visit_Call = visit_Call_legacy
+
+    def visit_Attribute(self, attr):
+        if not isinstance(attr.ctx, ast.Load):
+            return self.generic_visit(attr)
+        value, value_expl = self.visit(attr.value)
+        res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
+        res_expl = self.explanation_param(self.display(res))
+        pat = "%s\n{%s = %s.%s\n}"
+        expl = pat % (res_expl, res_expl, value_expl, attr.attr)
+        return res, expl
+
+    def visit_Compare(self, comp):
+        self.push_format_context()
+        left_res, left_expl = self.visit(comp.left)
+        if isinstance(comp.left, (_ast.Compare, _ast.BoolOp)):
+            left_expl = "({0})".format(left_expl)
+        res_variables = [self.variable() for i in range(len(comp.ops))]
+        load_names = [ast.Name(v, ast.Load()) for v in res_variables]
+        store_names = [ast.Name(v, ast.Store()) for v in res_variables]
+        it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
+        expls = []
+        syms = []
+        results = [left_res]
+        for i, op, next_operand in it:
+            next_res, next_expl = self.visit(next_operand)
+            if isinstance(next_operand, (_ast.Compare, _ast.BoolOp)):
+                next_expl = "({0})".format(next_expl)
+            results.append(next_res)
+            sym = binop_map[op.__class__]
+            syms.append(ast.Str(sym))
+            expl = "%s %s %s" % (left_expl, sym, next_expl)
+            expls.append(ast.Str(expl))
+            res_expr = ast.Compare(left_res, [op], [next_res])
+            self.statements.append(ast.Assign([store_names[i]], res_expr))
+            left_res, left_expl = next_res, next_expl
+        # Use pytest.assertion.util._reprcompare if that's available.
+        expl_call = self.helper("call_reprcompare",
+                                ast.Tuple(syms, ast.Load()),
+                                ast.Tuple(load_names, ast.Load()),
+                                ast.Tuple(expls, ast.Load()),
+                                ast.Tuple(results, ast.Load()))
+        if len(comp.ops) > 1:
+            res = ast.BoolOp(ast.And(), load_names)
+        else:
+            res = load_names[0]
+        return res, self.explanation_param(self.pop_format_context(expl_call))
diff --git a/tools/third_party/pytest/_pytest/assertion/truncate.py b/tools/third_party/pytest/_pytest/assertion/truncate.py
new file mode 100644
index 0000000..2ed12e2
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/assertion/truncate.py
@@ -0,0 +1,102 @@
+"""
+Utilities for truncating assertion output.
+
+Current default behaviour is to truncate assertion explanations at
+~8 terminal lines, unless running in "-vv" mode or running on CI.
+"""
+from __future__ import absolute_import, division, print_function
+import os
+
+import six
+
+
+DEFAULT_MAX_LINES = 8
+DEFAULT_MAX_CHARS = 8 * 80
+USAGE_MSG = "use '-vv' to show"
+
+
+def truncate_if_required(explanation, item, max_length=None):
+    """
+    Truncate this assertion explanation if the given test item is eligible.
+    """
+    if _should_truncate_item(item):
+        return _truncate_explanation(explanation)
+    return explanation
+
+
+def _should_truncate_item(item):
+    """
+    Whether or not this test item is eligible for truncation.
+    """
+    verbose = item.config.option.verbose
+    return verbose < 2 and not _running_on_ci()
+
+
+def _running_on_ci():
+    """Check if we're currently running on a CI system."""
+    env_vars = ['CI', 'BUILD_NUMBER']
+    return any(var in os.environ for var in env_vars)
+
+
+def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
+    """
+    Truncate given list of strings that makes up the assertion explanation.
+
+    Truncates to either 8 lines, or 640 characters - whichever the input reaches
+    first. The remaining lines will be replaced by a usage message.
+    """
+
+    if max_lines is None:
+        max_lines = DEFAULT_MAX_LINES
+    if max_chars is None:
+        max_chars = DEFAULT_MAX_CHARS
+
+    # Check if truncation required
+    input_char_count = len("".join(input_lines))
+    if len(input_lines) <= max_lines and input_char_count <= max_chars:
+        return input_lines
+
+    # Truncate first to max_lines, and then truncate to max_chars if max_chars
+    # is exceeded.
+    truncated_explanation = input_lines[:max_lines]
+    truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars)
+
+    # Add ellipsis to final line
+    truncated_explanation[-1] = truncated_explanation[-1] + "..."
+
+    # Append useful message to explanation
+    truncated_line_count = len(input_lines) - len(truncated_explanation)
+    truncated_line_count += 1  # Account for the part-truncated final line
+    msg = '...Full output truncated'
+    if truncated_line_count == 1:
+        msg += ' ({0} line hidden)'.format(truncated_line_count)
+    else:
+        msg += ' ({0} lines hidden)'.format(truncated_line_count)
+    msg += ", {0}" .format(USAGE_MSG)
+    truncated_explanation.extend([
+        six.text_type(""),
+        six.text_type(msg),
+    ])
+    return truncated_explanation
+
+
+def _truncate_by_char_count(input_lines, max_chars):
+    # Check if truncation required
+    if len("".join(input_lines)) <= max_chars:
+        return input_lines
+
+    # Find point at which input length exceeds total allowed length
+    iterated_char_count = 0
+    for iterated_index, input_line in enumerate(input_lines):
+        if iterated_char_count + len(input_line) > max_chars:
+            break
+        iterated_char_count += len(input_line)
+
+    # Create truncated explanation with modified final line
+    truncated_result = input_lines[:iterated_index]
+    final_line = input_lines[iterated_index]
+    if final_line:
+        final_line_truncate_point = max_chars - iterated_char_count
+        final_line = final_line[:final_line_truncate_point]
+    truncated_result.append(final_line)
+    return truncated_result
diff --git a/tools/third_party/pytest/_pytest/assertion/util.py b/tools/third_party/pytest/_pytest/assertion/util.py
new file mode 100644
index 0000000..511d98e
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/assertion/util.py
@@ -0,0 +1,308 @@
+"""Utilities for assertion debugging"""
+from __future__ import absolute_import, division, print_function
+import pprint
+
+import _pytest._code
+import py
+import six
+try:
+    from collections import Sequence
+except ImportError:
+    Sequence = list
+
+
+u = six.text_type
+
+# The _reprcompare attribute on the util module is used by the new assertion
+# interpretation code and assertion rewriter to detect this plugin was
+# loaded and in turn call the hooks defined here as part of the
+# DebugInterpreter.
+_reprcompare = None
+
+
+# the re-encoding is needed for python2 repr
+# with non-ascii characters (see issue 877 and 1379)
+def ecu(s):
+    try:
+        return u(s, 'utf-8', 'replace')
+    except TypeError:
+        return s
+
+
+def format_explanation(explanation):
+    """This formats an explanation
+
+    Normally all embedded newlines are escaped, however there are
+    three exceptions: \n{, \n} and \n~.  The first two are intended
+    cover nested explanations, see function and attribute explanations
+    for examples (.visit_Call(), visit_Attribute()).  The last one is
+    for when one explanation needs to span multiple lines, e.g. when
+    displaying diffs.
+    """
+    explanation = ecu(explanation)
+    lines = _split_explanation(explanation)
+    result = _format_lines(lines)
+    return u('\n').join(result)
+
+
+def _split_explanation(explanation):
+    """Return a list of individual lines in the explanation
+
+    This will return a list of lines split on '\n{', '\n}' and '\n~'.
+    Any other newlines will be escaped and appear in the line as the
+    literal '\n' characters.
+    """
+    raw_lines = (explanation or u('')).split('\n')
+    lines = [raw_lines[0]]
+    for values in raw_lines[1:]:
+        if values and values[0] in ['{', '}', '~', '>']:
+            lines.append(values)
+        else:
+            lines[-1] += '\\n' + values
+    return lines
+
+
+def _format_lines(lines):
+    """Format the individual lines
+
+    This will replace the '{', '}' and '~' characters of our mini
+    formatting language with the proper 'where ...', 'and ...' and ' +
+    ...' text, taking care of indentation along the way.
+
+    Return a list of formatted lines.
+    """
+    result = lines[:1]
+    stack = [0]
+    stackcnt = [0]
+    for line in lines[1:]:
+        if line.startswith('{'):
+            if stackcnt[-1]:
+                s = u('and   ')
+            else:
+                s = u('where ')
+            stack.append(len(result))
+            stackcnt[-1] += 1
+            stackcnt.append(0)
+            result.append(u(' +') + u('  ') * (len(stack) - 1) + s + line[1:])
+        elif line.startswith('}'):
+            stack.pop()
+            stackcnt.pop()
+            result[stack[-1]] += line[1:]
+        else:
+            assert line[0] in ['~', '>']
+            stack[-1] += 1
+            indent = len(stack) if line.startswith('~') else len(stack) - 1
+            result.append(u('  ') * indent + line[1:])
+    assert len(stack) == 1
+    return result
+
+
+# Provide basestring in python3
+try:
+    basestring = basestring
+except NameError:
+    basestring = str
+
+
+def assertrepr_compare(config, op, left, right):
+    """Return specialised explanations for some operators/operands"""
+    width = 80 - 15 - len(op) - 2  # 15 chars indentation, 1 space around op
+    left_repr = py.io.saferepr(left, maxsize=int(width // 2))
+    right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
+
+    summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
+
+    def issequence(x):
+        return (isinstance(x, (list, tuple, Sequence)) and not isinstance(x, basestring))
+
+    def istext(x):
+        return isinstance(x, basestring)
+
+    def isdict(x):
+        return isinstance(x, dict)
+
+    def isset(x):
+        return isinstance(x, (set, frozenset))
+
+    def isiterable(obj):
+        try:
+            iter(obj)
+            return not istext(obj)
+        except TypeError:
+            return False
+
+    verbose = config.getoption('verbose')
+    explanation = None
+    try:
+        if op == '==':
+            if istext(left) and istext(right):
+                explanation = _diff_text(left, right, verbose)
+            else:
+                if issequence(left) and issequence(right):
+                    explanation = _compare_eq_sequence(left, right, verbose)
+                elif isset(left) and isset(right):
+                    explanation = _compare_eq_set(left, right, verbose)
+                elif isdict(left) and isdict(right):
+                    explanation = _compare_eq_dict(left, right, verbose)
+                if isiterable(left) and isiterable(right):
+                    expl = _compare_eq_iterable(left, right, verbose)
+                    if explanation is not None:
+                        explanation.extend(expl)
+                    else:
+                        explanation = expl
+        elif op == 'not in':
+            if istext(left) and istext(right):
+                explanation = _notin_text(left, right, verbose)
+    except Exception:
+        explanation = [
+            u('(pytest_assertion plugin: representation of details failed.  '
+              'Probably an object has a faulty __repr__.)'),
+            u(_pytest._code.ExceptionInfo())]
+
+    if not explanation:
+        return None
+
+    return [summary] + explanation
+
+
+def _diff_text(left, right, verbose=False):
+    """Return the explanation for the diff between text or bytes
+
+    Unless --verbose is used this will skip leading and trailing
+    characters which are identical to keep the diff minimal.
+
+    If the input are bytes they will be safely converted to text.
+    """
+    from difflib import ndiff
+    explanation = []
+    if isinstance(left, six.binary_type):
+        left = u(repr(left)[1:-1]).replace(r'\n', '\n')
+    if isinstance(right, six.binary_type):
+        right = u(repr(right)[1:-1]).replace(r'\n', '\n')
+    if not verbose:
+        i = 0  # just in case left or right has zero length
+        for i in range(min(len(left), len(right))):
+            if left[i] != right[i]:
+                break
+        if i > 42:
+            i -= 10                 # Provide some context
+            explanation = [u('Skipping %s identical leading '
+                             'characters in diff, use -v to show') % i]
+            left = left[i:]
+            right = right[i:]
+        if len(left) == len(right):
+            for i in range(len(left)):
+                if left[-i] != right[-i]:
+                    break
+            if i > 42:
+                i -= 10     # Provide some context
+                explanation += [u('Skipping %s identical trailing '
+                                  'characters in diff, use -v to show') % i]
+                left = left[:-i]
+                right = right[:-i]
+    keepends = True
+    explanation += [line.strip('\n')
+                    for line in ndiff(left.splitlines(keepends),
+                                      right.splitlines(keepends))]
+    return explanation
+
+
+def _compare_eq_iterable(left, right, verbose=False):
+    if not verbose:
+        return [u('Use -v to get the full diff')]
+    # dynamic import to speedup pytest
+    import difflib
+
+    try:
+        left_formatting = pprint.pformat(left).splitlines()
+        right_formatting = pprint.pformat(right).splitlines()
+        explanation = [u('Full diff:')]
+    except Exception:
+        # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
+        # sorted() on a list would raise. See issue #718.
+        # As a workaround, the full diff is generated by using the repr() string of each item of each container.
+        left_formatting = sorted(repr(x) for x in left)
+        right_formatting = sorted(repr(x) for x in right)
+        explanation = [u('Full diff (fallback to calling repr on each item):')]
+    explanation.extend(line.strip() for line in difflib.ndiff(left_formatting, right_formatting))
+    return explanation
+
+
+def _compare_eq_sequence(left, right, verbose=False):
+    explanation = []
+    for i in range(min(len(left), len(right))):
+        if left[i] != right[i]:
+            explanation += [u('At index %s diff: %r != %r')
+                            % (i, left[i], right[i])]
+            break
+    if len(left) > len(right):
+        explanation += [u('Left contains more items, first extra item: %s')
+                        % py.io.saferepr(left[len(right)],)]
+    elif len(left) < len(right):
+        explanation += [
+            u('Right contains more items, first extra item: %s') %
+            py.io.saferepr(right[len(left)],)]
+    return explanation
+
+
+def _compare_eq_set(left, right, verbose=False):
+    explanation = []
+    diff_left = left - right
+    diff_right = right - left
+    if diff_left:
+        explanation.append(u('Extra items in the left set:'))
+        for item in diff_left:
+            explanation.append(py.io.saferepr(item))
+    if diff_right:
+        explanation.append(u('Extra items in the right set:'))
+        for item in diff_right:
+            explanation.append(py.io.saferepr(item))
+    return explanation
+
+
+def _compare_eq_dict(left, right, verbose=False):
+    explanation = []
+    common = set(left).intersection(set(right))
+    same = dict((k, left[k]) for k in common if left[k] == right[k])
+    if same and verbose < 2:
+        explanation += [u('Omitting %s identical items, use -vv to show') %
+                        len(same)]
+    elif same:
+        explanation += [u('Common items:')]
+        explanation += pprint.pformat(same).splitlines()
+    diff = set(k for k in common if left[k] != right[k])
+    if diff:
+        explanation += [u('Differing items:')]
+        for k in diff:
+            explanation += [py.io.saferepr({k: left[k]}) + ' != ' +
+                            py.io.saferepr({k: right[k]})]
+    extra_left = set(left) - set(right)
+    if extra_left:
+        explanation.append(u('Left contains more items:'))
+        explanation.extend(pprint.pformat(
+            dict((k, left[k]) for k in extra_left)).splitlines())
+    extra_right = set(right) - set(left)
+    if extra_right:
+        explanation.append(u('Right contains more items:'))
+        explanation.extend(pprint.pformat(
+            dict((k, right[k]) for k in extra_right)).splitlines())
+    return explanation
+
+
+def _notin_text(term, text, verbose=False):
+    index = text.find(term)
+    head = text[:index]
+    tail = text[index + len(term):]
+    correct_text = head + tail
+    diff = _diff_text(correct_text, text, verbose)
+    newdiff = [u('%s is contained here:') % py.io.saferepr(term, maxsize=42)]
+    for line in diff:
+        if line.startswith(u('Skipping')):
+            continue
+        if line.startswith(u('- ')):
+            continue
+        if line.startswith(u('+ ')):
+            newdiff.append(u('  ') + line[2:])
+        else:
+            newdiff.append(line)
+    return newdiff
diff --git a/tools/third_party/pytest/_pytest/cacheprovider.py b/tools/third_party/pytest/_pytest/cacheprovider.py
new file mode 100755
index 0000000..c537c14
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/cacheprovider.py
@@ -0,0 +1,260 @@
+"""
+merged implementation of the cache provider
+
+the name cache was not chosen to ensure pluggy automatically
+ignores the external pytest-cache
+"""
+from __future__ import absolute_import, division, print_function
+import py
+import pytest
+import json
+import os
+from os.path import sep as _sep, altsep as _altsep
+
+
+class Cache(object):
+    def __init__(self, config):
+        self.config = config
+        self._cachedir = Cache.cache_dir_from_config(config)
+        self.trace = config.trace.root.get("cache")
+        if config.getvalue("cacheclear"):
+            self.trace("clearing cachedir")
+            if self._cachedir.check():
+                self._cachedir.remove()
+            self._cachedir.mkdir()
+
+    @staticmethod
+    def cache_dir_from_config(config):
+        cache_dir = config.getini("cache_dir")
+        cache_dir = os.path.expanduser(cache_dir)
+        cache_dir = os.path.expandvars(cache_dir)
+        if os.path.isabs(cache_dir):
+            return py.path.local(cache_dir)
+        else:
+            return config.rootdir.join(cache_dir)
+
+    def makedir(self, name):
+        """ return a directory path object with the given name.  If the
+        directory does not yet exist, it will be created.  You can use it
+        to manage files likes e. g. store/retrieve database
+        dumps across test sessions.
+
+        :param name: must be a string not containing a ``/`` separator.
+             Make sure the name contains your plugin or application
+             identifiers to prevent clashes with other cache users.
+        """
+        if _sep in name or _altsep is not None and _altsep in name:
+            raise ValueError("name is not allowed to contain path separators")
+        return self._cachedir.ensure_dir("d", name)
+
+    def _getvaluepath(self, key):
+        return self._cachedir.join('v', *key.split('/'))
+
+    def get(self, key, default):
+        """ return cached value for the given key.  If no value
+        was yet cached or the value cannot be read, the specified
+        default is returned.
+
+        :param key: must be a ``/`` separated value. Usually the first
+             name is the name of your plugin or your application.
+        :param default: must be provided in case of a cache-miss or
+             invalid cache values.
+
+        """
+        path = self._getvaluepath(key)
+        if path.check():
+            try:
+                with path.open("r") as f:
+                    return json.load(f)
+            except ValueError:
+                self.trace("cache-invalid at %s" % (path,))
+        return default
+
+    def set(self, key, value):
+        """ save value for the given key.
+
+        :param key: must be a ``/`` separated value. Usually the first
+             name is the name of your plugin or your application.
+        :param value: must be of any combination of basic
+               python types, including nested types
+               like e. g. lists of dictionaries.
+        """
+        path = self._getvaluepath(key)
+        try:
+            path.dirpath().ensure_dir()
+        except (py.error.EEXIST, py.error.EACCES):
+            self.config.warn(
+                code='I9', message='could not create cache path %s' % (path,)
+            )
+            return
+        try:
+            f = path.open('w')
+        except py.error.ENOTDIR:
+            self.config.warn(
+                code='I9', message='cache could not write path %s' % (path,))
+        else:
+            with f:
+                self.trace("cache-write %s: %r" % (key, value,))
+                json.dump(value, f, indent=2, sort_keys=True)
+
+
+class LFPlugin:
+    """ Plugin which implements the --lf (run last-failing) option """
+
+    def __init__(self, config):
+        self.config = config
+        active_keys = 'lf', 'failedfirst'
+        self.active = any(config.getvalue(key) for key in active_keys)
+        self.lastfailed = config.cache.get("cache/lastfailed", {})
+        self._previously_failed_count = None
+
+    def pytest_report_collectionfinish(self):
+        if self.active:
+            if not self._previously_failed_count:
+                mode = "run all (no recorded failures)"
+            else:
+                noun = 'failure' if self._previously_failed_count == 1 else 'failures'
+                suffix = " first" if self.config.getvalue("failedfirst") else ""
+                mode = "rerun previous {count} {noun}{suffix}".format(
+                    count=self._previously_failed_count, suffix=suffix, noun=noun
+                )
+            return "run-last-failure: %s" % mode
+
+    def pytest_runtest_logreport(self, report):
+        if (report.when == 'call' and report.passed) or report.skipped:
+            self.lastfailed.pop(report.nodeid, None)
+        elif report.failed:
+            self.lastfailed[report.nodeid] = True
+
+    def pytest_collectreport(self, report):
+        passed = report.outcome in ('passed', 'skipped')
+        if passed:
+            if report.nodeid in self.lastfailed:
+                self.lastfailed.pop(report.nodeid)
+                self.lastfailed.update(
+                    (item.nodeid, True)
+                    for item in report.result)
+        else:
+            self.lastfailed[report.nodeid] = True
+
+    def pytest_collection_modifyitems(self, session, config, items):
+        if self.active and self.lastfailed:
+            previously_failed = []
+            previously_passed = []
+            for item in items:
+                if item.nodeid in self.lastfailed:
+                    previously_failed.append(item)
+                else:
+                    previously_passed.append(item)
+            self._previously_failed_count = len(previously_failed)
+            if not previously_failed:
+                # running a subset of all tests with recorded failures outside
+                # of the set of tests currently executing
+                return
+            if self.config.getvalue("lf"):
+                items[:] = previously_failed
+                config.hook.pytest_deselected(items=previously_passed)
+            else:
+                items[:] = previously_failed + previously_passed
+
+    def pytest_sessionfinish(self, session):
+        config = self.config
+        if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
+            return
+
+        saved_lastfailed = config.cache.get("cache/lastfailed", {})
+        if saved_lastfailed != self.lastfailed:
+            config.cache.set("cache/lastfailed", self.lastfailed)
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group.addoption(
+        '--lf', '--last-failed', action='store_true', dest="lf",
+        help="rerun only the tests that failed "
+             "at the last run (or all if none failed)")
+    group.addoption(
+        '--ff', '--failed-first', action='store_true', dest="failedfirst",
+        help="run all tests but run the last failures first.  "
+             "This may re-order tests and thus lead to "
+             "repeated fixture setup/teardown")
+    group.addoption(
+        '--cache-show', action='store_true', dest="cacheshow",
+        help="show cache contents, don't perform collection or tests")
+    group.addoption(
+        '--cache-clear', action='store_true', dest="cacheclear",
+        help="remove all cache contents at start of test run.")
+    parser.addini(
+        "cache_dir", default='.cache',
+        help="cache directory path.")
+
+
+def pytest_cmdline_main(config):
+    if config.option.cacheshow:
+        from _pytest.main import wrap_session
+        return wrap_session(config, cacheshow)
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_configure(config):
+    config.cache = Cache(config)
+    config.pluginmanager.register(LFPlugin(config), "lfplugin")
+
+
+@pytest.fixture
+def cache(request):
+    """
+    Return a cache object that can persist state between testing sessions.
+
+    cache.get(key, default)
+    cache.set(key, value)
+
+    Keys must be a ``/`` separated value, where the first part is usually the
+    name of your plugin or application to avoid clashes with other cache users.
+
+    Values can be any object handled by the json stdlib module.
+    """
+    return request.config.cache
+
+
+def pytest_report_header(config):
+    if config.option.verbose:
+        relpath = py.path.local().bestrelpath(config.cache._cachedir)
+        return "cachedir: %s" % relpath
+
+
+def cacheshow(config, session):
+    from pprint import pprint
+    tw = py.io.TerminalWriter()
+    tw.line("cachedir: " + str(config.cache._cachedir))
+    if not config.cache._cachedir.check():
+        tw.line("cache is empty")
+        return 0
+    dummy = object()
+    basedir = config.cache._cachedir
+    vdir = basedir.join("v")
+    tw.sep("-", "cache values")
+    for valpath in sorted(vdir.visit(lambda x: x.isfile())):
+        key = valpath.relto(vdir).replace(valpath.sep, "/")
+        val = config.cache.get(key, dummy)
+        if val is dummy:
+            tw.line("%s contains unreadable content, "
+                    "will be ignored" % key)
+        else:
+            tw.line("%s contains:" % key)
+            stream = py.io.TextIO()
+            pprint(val, stream=stream)
+            for line in stream.getvalue().splitlines():
+                tw.line("  " + line)
+
+    ddir = basedir.join("d")
+    if ddir.isdir() and ddir.listdir():
+        tw.sep("-", "cache directories")
+        for p in sorted(basedir.join("d").visit()):
+            # if p.check(dir=1):
+            #    print("%s/" % p.relto(basedir))
+            if p.isfile():
+                key = p.relto(basedir)
+                tw.line("%s is a file of length %d" % (
+                        key, p.size()))
+    return 0
diff --git a/tools/third_party/pytest/_pytest/capture.py b/tools/third_party/pytest/_pytest/capture.py
new file mode 100644
index 0000000..f2ebe38
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/capture.py
@@ -0,0 +1,683 @@
+"""
+per-test stdout/stderr capturing mechanism.
+
+"""
+from __future__ import absolute_import, division, print_function
+
+import collections
+import contextlib
+import sys
+import os
+import io
+from io import UnsupportedOperation
+from tempfile import TemporaryFile
+
+import six
+import pytest
+from _pytest.compat import CaptureIO
+
+
+patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group._addoption(
+        '--capture', action="store",
+        default="fd" if hasattr(os, "dup") else "sys",
+        metavar="method", choices=['fd', 'sys', 'no'],
+        help="per-test capturing method: one of fd|sys|no.")
+    group._addoption(
+        '-s', action="store_const", const="no", dest="capture",
+        help="shortcut for --capture=no.")
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_load_initial_conftests(early_config, parser, args):
+    ns = early_config.known_args_namespace
+    if ns.capture == "fd":
+        _py36_windowsconsoleio_workaround(sys.stdout)
+    _colorama_workaround()
+    _readline_workaround()
+    pluginmanager = early_config.pluginmanager
+    capman = CaptureManager(ns.capture)
+    pluginmanager.register(capman, "capturemanager")
+
+    # make sure that capturemanager is properly reset at final shutdown
+    early_config.add_cleanup(capman.stop_global_capturing)
+
+    # make sure logging does not raise exceptions at the end
+    def silence_logging_at_shutdown():
+        if "logging" in sys.modules:
+            sys.modules["logging"].raiseExceptions = False
+    early_config.add_cleanup(silence_logging_at_shutdown)
+
+    # finally trigger conftest loading but while capturing (issue93)
+    capman.start_global_capturing()
+    outcome = yield
+    out, err = capman.suspend_global_capture()
+    if outcome.excinfo is not None:
+        sys.stdout.write(out)
+        sys.stderr.write(err)
+
+
+class CaptureManager:
+    """
+    Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
+    test phase (setup, call, teardown). After each of those points, the captured output is obtained and
+    attached to the collection/runtest report.
+
+    There are two levels of capture:
+    * global: which is enabled by default and can be suppressed by the ``-s`` option. This is always enabled/disabled
+      during collection and each test phase.
+    * fixture: when a test function or one of its fixture depend on the ``capsys`` or ``capfd`` fixtures. In this
+      case special handling is needed to ensure the fixtures take precedence over the global capture.
+    """
+
+    def __init__(self, method):
+        self._method = method
+        self._global_capturing = None
+
+    def _getcapture(self, method):
+        if method == "fd":
+            return MultiCapture(out=True, err=True, Capture=FDCapture)
+        elif method == "sys":
+            return MultiCapture(out=True, err=True, Capture=SysCapture)
+        elif method == "no":
+            return MultiCapture(out=False, err=False, in_=False)
+        else:
+            raise ValueError("unknown capturing method: %r" % method)
+
+    def start_global_capturing(self):
+        assert self._global_capturing is None
+        self._global_capturing = self._getcapture(self._method)
+        self._global_capturing.start_capturing()
+
+    def stop_global_capturing(self):
+        if self._global_capturing is not None:
+            self._global_capturing.pop_outerr_to_orig()
+            self._global_capturing.stop_capturing()
+            self._global_capturing = None
+
+    def resume_global_capture(self):
+        self._global_capturing.resume_capturing()
+
+    def suspend_global_capture(self, item=None, in_=False):
+        if item is not None:
+            self.deactivate_fixture(item)
+        cap = getattr(self, "_global_capturing", None)
+        if cap is not None:
+            try:
+                outerr = cap.readouterr()
+            finally:
+                cap.suspend_capturing(in_=in_)
+            return outerr
+
+    def activate_fixture(self, item):
+        """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
+        the global capture.
+        """
+        fixture = getattr(item, "_capture_fixture", None)
+        if fixture is not None:
+            fixture._start()
+
+    def deactivate_fixture(self, item):
+        """Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
+        fixture = getattr(item, "_capture_fixture", None)
+        if fixture is not None:
+            fixture.close()
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_make_collect_report(self, collector):
+        if isinstance(collector, pytest.File):
+            self.resume_global_capture()
+            outcome = yield
+            out, err = self.suspend_global_capture()
+            rep = outcome.get_result()
+            if out:
+                rep.sections.append(("Captured stdout", out))
+            if err:
+                rep.sections.append(("Captured stderr", err))
+        else:
+            yield
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_runtest_setup(self, item):
+        self.resume_global_capture()
+        # no need to activate a capture fixture because they activate themselves during creation; this
+        # only makes sense when a fixture uses a capture fixture, otherwise the capture fixture will
+        # be activated during pytest_runtest_call
+        yield
+        self.suspend_capture_item(item, "setup")
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_runtest_call(self, item):
+        self.resume_global_capture()
+        # it is important to activate this fixture during the call phase so it overwrites the "global"
+        # capture
+        self.activate_fixture(item)
+        yield
+        self.suspend_capture_item(item, "call")
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_runtest_teardown(self, item):
+        self.resume_global_capture()
+        self.activate_fixture(item)
+        yield
+        self.suspend_capture_item(item, "teardown")
+
+    @pytest.hookimpl(tryfirst=True)
+    def pytest_keyboard_interrupt(self, excinfo):
+        self.stop_global_capturing()
+
+    @pytest.hookimpl(tryfirst=True)
+    def pytest_internalerror(self, excinfo):
+        self.stop_global_capturing()
+
+    def suspend_capture_item(self, item, when, in_=False):
+        out, err = self.suspend_global_capture(item, in_=in_)
+        item.add_report_section(when, "stdout", out)
+        item.add_report_section(when, "stderr", err)
+
+
+capture_fixtures = {'capfd', 'capfdbinary', 'capsys', 'capsysbinary'}
+
+
+def _ensure_only_one_capture_fixture(request, name):
+    fixtures = set(request.fixturenames) & capture_fixtures - set((name,))
+    if fixtures:
+        fixtures = sorted(fixtures)
+        fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
+        raise request.raiseerror(
+            "cannot use {0} and {1} at the same time".format(
+                fixtures, name,
+            ),
+        )
+
+
+@pytest.fixture
+def capsys(request):
+    """Enable capturing of writes to sys.stdout/sys.stderr and make
+    captured output available via ``capsys.readouterr()`` method calls
+    which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be ``text``
+    objects.
+    """
+    _ensure_only_one_capture_fixture(request, 'capsys')
+    with _install_capture_fixture_on_item(request, SysCapture) as fixture:
+        yield fixture
+
+
+@pytest.fixture
+def capsysbinary(request):
+    """Enable capturing of writes to sys.stdout/sys.stderr and make
+    captured output available via ``capsys.readouterr()`` method calls
+    which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be ``bytes``
+    objects.
+    """
+    _ensure_only_one_capture_fixture(request, 'capsysbinary')
+    # Currently, the implementation uses the python3 specific `.buffer`
+    # property of CaptureIO.
+    if sys.version_info < (3,):
+        raise request.raiseerror('capsysbinary is only supported on python 3')
+    with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
+        yield fixture
+
+
+@pytest.fixture
+def capfd(request):
+    """Enable capturing of writes to file descriptors 1 and 2 and make
+    captured output available via ``capfd.readouterr()`` method calls
+    which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be ``text``
+    objects.
+    """
+    _ensure_only_one_capture_fixture(request, 'capfd')
+    if not hasattr(os, 'dup'):
+        pytest.skip("capfd fixture needs os.dup function which is not available in this system")
+    with _install_capture_fixture_on_item(request, FDCapture) as fixture:
+        yield fixture
+
+
+@pytest.fixture
+def capfdbinary(request):
+    """Enable capturing of write to file descriptors 1 and 2 and make
+    captured output available via ``capfdbinary.readouterr`` method calls
+    which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be
+    ``bytes`` objects.
+    """
+    _ensure_only_one_capture_fixture(request, 'capfdbinary')
+    if not hasattr(os, 'dup'):
+        pytest.skip("capfdbinary fixture needs os.dup function which is not available in this system")
+    with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture:
+        yield fixture
+
+
+@contextlib.contextmanager
+def _install_capture_fixture_on_item(request, capture_class):
+    """
+    Context manager which creates a ``CaptureFixture`` instance and "installs" it on
+    the item/node of the given request. Used by ``capsys`` and ``capfd``.
+
+    The CaptureFixture is added as attribute of the item because it needs to accessed
+    by ``CaptureManager`` during its ``pytest_runtest_*`` hooks.
+    """
+    request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
+    capmanager = request.config.pluginmanager.getplugin('capturemanager')
+    # need to active this fixture right away in case it is being used by another fixture (setup phase)
+    # if this fixture is being used only by a test function (call phase), then we wouldn't need this
+    # activation, but it doesn't hurt
+    capmanager.activate_fixture(request.node)
+    yield fixture
+    fixture.close()
+    del request.node._capture_fixture
+
+
+class CaptureFixture:
+    def __init__(self, captureclass, request):
+        self.captureclass = captureclass
+        self.request = request
+
+    def _start(self):
+        self._capture = MultiCapture(out=True, err=True, in_=False,
+                                     Capture=self.captureclass)
+        self._capture.start_capturing()
+
+    def close(self):
+        cap = self.__dict__.pop("_capture", None)
+        if cap is not None:
+            self._outerr = cap.pop_outerr_to_orig()
+            cap.stop_capturing()
+
+    def readouterr(self):
+        try:
+            return self._capture.readouterr()
+        except AttributeError:
+            return self._outerr
+
+    @contextlib.contextmanager
+    def disabled(self):
+        self._capture.suspend_capturing()
+        capmanager = self.request.config.pluginmanager.getplugin('capturemanager')
+        capmanager.suspend_global_capture(item=None, in_=False)
+        try:
+            yield
+        finally:
+            capmanager.resume_global_capture()
+            self._capture.resume_capturing()
+
+
+def safe_text_dupfile(f, mode, default_encoding="UTF8"):
+    """ return a open text file object that's a duplicate of f on the
+        FD-level if possible.
+    """
+    encoding = getattr(f, "encoding", None)
+    try:
+        fd = f.fileno()
+    except Exception:
+        if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
+            # we seem to have a text stream, let's just use it
+            return f
+    else:
+        newfd = os.dup(fd)
+        if "b" not in mode:
+            mode += "b"
+        f = os.fdopen(newfd, mode, 0)  # no buffering
+    return EncodedFile(f, encoding or default_encoding)
+
+
+class EncodedFile(object):
+    errors = "strict"  # possibly needed by py3 code (issue555)
+
+    def __init__(self, buffer, encoding):
+        self.buffer = buffer
+        self.encoding = encoding
+
+    def write(self, obj):
+        if isinstance(obj, six.text_type):
+            obj = obj.encode(self.encoding, "replace")
+        self.buffer.write(obj)
+
+    def writelines(self, linelist):
+        data = ''.join(linelist)
+        self.write(data)
+
+    @property
+    def name(self):
+        """Ensure that file.name is a string."""
+        return repr(self.buffer)
+
+    def __getattr__(self, name):
+        return getattr(object.__getattribute__(self, "buffer"), name)
+
+
+CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
+
+
+class MultiCapture(object):
+    out = err = in_ = None
+
+    def __init__(self, out=True, err=True, in_=True, Capture=None):
+        if in_:
+            self.in_ = Capture(0)
+        if out:
+            self.out = Capture(1)
+        if err:
+            self.err = Capture(2)
+
+    def start_capturing(self):
+        if self.in_:
+            self.in_.start()
+        if self.out:
+            self.out.start()
+        if self.err:
+            self.err.start()
+
+    def pop_outerr_to_orig(self):
+        """ pop current snapshot out/err capture and flush to orig streams. """
+        out, err = self.readouterr()
+        if out:
+            self.out.writeorg(out)
+        if err:
+            self.err.writeorg(err)
+        return out, err
+
+    def suspend_capturing(self, in_=False):
+        if self.out:
+            self.out.suspend()
+        if self.err:
+            self.err.suspend()
+        if in_ and self.in_:
+            self.in_.suspend()
+            self._in_suspended = True
+
+    def resume_capturing(self):
+        if self.out:
+            self.out.resume()
+        if self.err:
+            self.err.resume()
+        if hasattr(self, "_in_suspended"):
+            self.in_.resume()
+            del self._in_suspended
+
+    def stop_capturing(self):
+        """ stop capturing and reset capturing streams """
+        if hasattr(self, '_reset'):
+            raise ValueError("was already stopped")
+        self._reset = True
+        if self.out:
+            self.out.done()
+        if self.err:
+            self.err.done()
+        if self.in_:
+            self.in_.done()
+
+    def readouterr(self):
+        """ return snapshot unicode value of stdout/stderr capturings. """
+        return CaptureResult(self.out.snap() if self.out is not None else "",
+                             self.err.snap() if self.err is not None else "")
+
+
+class NoCapture:
+    __init__ = start = done = suspend = resume = lambda *args: None
+
+
+class FDCaptureBinary:
+    """Capture IO to/from a given os-level filedescriptor.
+
+    snap() produces `bytes`
+    """
+
+    def __init__(self, targetfd, tmpfile=None):
+        self.targetfd = targetfd
+        try:
+            self.targetfd_save = os.dup(self.targetfd)
+        except OSError:
+            self.start = lambda: None
+            self.done = lambda: None
+        else:
+            if targetfd == 0:
+                assert not tmpfile, "cannot set tmpfile with stdin"
+                tmpfile = open(os.devnull, "r")
+                self.syscapture = SysCapture(targetfd)
+            else:
+                if tmpfile is None:
+                    f = TemporaryFile()
+                    with f:
+                        tmpfile = safe_text_dupfile(f, mode="wb+")
+                if targetfd in patchsysdict:
+                    self.syscapture = SysCapture(targetfd, tmpfile)
+                else:
+                    self.syscapture = NoCapture()
+            self.tmpfile = tmpfile
+            self.tmpfile_fd = tmpfile.fileno()
+
+    def __repr__(self):
+        return "<FDCapture %s oldfd=%s>" % (self.targetfd, self.targetfd_save)
+
+    def start(self):
+        """ Start capturing on targetfd using memorized tmpfile. """
+        try:
+            os.fstat(self.targetfd_save)
+        except (AttributeError, OSError):
+            raise ValueError("saved filedescriptor not valid anymore")
+        os.dup2(self.tmpfile_fd, self.targetfd)
+        self.syscapture.start()
+
+    def snap(self):
+        self.tmpfile.seek(0)
+        res = self.tmpfile.read()
+        self.tmpfile.seek(0)
+        self.tmpfile.truncate()
+        return res
+
+    def done(self):
+        """ stop capturing, restore streams, return original capture file,
+        seeked to position zero. """
+        targetfd_save = self.__dict__.pop("targetfd_save")
+        os.dup2(targetfd_save, self.targetfd)
+        os.close(targetfd_save)
+        self.syscapture.done()
+        self.tmpfile.close()
+
+    def suspend(self):
+        self.syscapture.suspend()
+        os.dup2(self.targetfd_save, self.targetfd)
+
+    def resume(self):
+        self.syscapture.resume()
+        os.dup2(self.tmpfile_fd, self.targetfd)
+
+    def writeorg(self, data):
+        """ write to original file descriptor. """
+        if isinstance(data, six.text_type):
+            data = data.encode("utf8")  # XXX use encoding of original stream
+        os.write(self.targetfd_save, data)
+
+
+class FDCapture(FDCaptureBinary):
+    """Capture IO to/from a given os-level filedescriptor.
+
+    snap() produces text
+    """
+    def snap(self):
+        res = FDCaptureBinary.snap(self)
+        enc = getattr(self.tmpfile, "encoding", None)
+        if enc and isinstance(res, bytes):
+            res = six.text_type(res, enc, "replace")
+        return res
+
+
+class SysCapture:
+    def __init__(self, fd, tmpfile=None):
+        name = patchsysdict[fd]
+        self._old = getattr(sys, name)
+        self.name = name
+        if tmpfile is None:
+            if name == "stdin":
+                tmpfile = DontReadFromInput()
+            else:
+                tmpfile = CaptureIO()
+        self.tmpfile = tmpfile
+
+    def start(self):
+        setattr(sys, self.name, self.tmpfile)
+
+    def snap(self):
+        res = self.tmpfile.getvalue()
+        self.tmpfile.seek(0)
+        self.tmpfile.truncate()
+        return res
+
+    def done(self):
+        setattr(sys, self.name, self._old)
+        del self._old
+        self.tmpfile.close()
+
+    def suspend(self):
+        setattr(sys, self.name, self._old)
+
+    def resume(self):
+        setattr(sys, self.name, self.tmpfile)
+
+    def writeorg(self, data):
+        self._old.write(data)
+        self._old.flush()
+
+
+class SysCaptureBinary(SysCapture):
+    def snap(self):
+        res = self.tmpfile.buffer.getvalue()
+        self.tmpfile.seek(0)
+        self.tmpfile.truncate()
+        return res
+
+
+class DontReadFromInput:
+    """Temporary stub class.  Ideally when stdin is accessed, the
+    capturing should be turned off, with possibly all data captured
+    so far sent to the screen.  This should be configurable, though,
+    because in automated test runs it is better to crash than
+    hang indefinitely.
+    """
+
+    encoding = None
+
+    def read(self, *args):
+        raise IOError("reading from stdin while output is captured")
+    readline = read
+    readlines = read
+    __iter__ = read
+
+    def fileno(self):
+        raise UnsupportedOperation("redirected stdin is pseudofile, "
+                                   "has no fileno()")
+
+    def isatty(self):
+        return False
+
+    def close(self):
+        pass
+
+    @property
+    def buffer(self):
+        if sys.version_info >= (3, 0):
+            return self
+        else:
+            raise AttributeError('redirected stdin has no attribute buffer')
+
+
+def _colorama_workaround():
+    """
+    Ensure colorama is imported so that it attaches to the correct stdio
+    handles on Windows.
+
+    colorama uses the terminal on import time. So if something does the
+    first import of colorama while I/O capture is active, colorama will
+    fail in various ways.
+    """
+
+    if not sys.platform.startswith('win32'):
+        return
+    try:
+        import colorama  # noqa
+    except ImportError:
+        pass
+
+
+def _readline_workaround():
+    """
+    Ensure readline is imported so that it attaches to the correct stdio
+    handles on Windows.
+
+    Pdb uses readline support where available--when not running from the Python
+    prompt, the readline module is not imported until running the pdb REPL.  If
+    running pytest with the --pdb option this means the readline module is not
+    imported until after I/O capture has been started.
+
+    This is a problem for pyreadline, which is often used to implement readline
+    support on Windows, as it does not attach to the correct handles for stdout
+    and/or stdin if they have been redirected by the FDCapture mechanism.  This
+    workaround ensures that readline is imported before I/O capture is setup so
+    that it can attach to the actual stdin/out for the console.
+
+    See https://github.com/pytest-dev/pytest/pull/1281
+    """
+
+    if not sys.platform.startswith('win32'):
+        return
+    try:
+        import readline  # noqa
+    except ImportError:
+        pass
+
+
+def _py36_windowsconsoleio_workaround(stream):
+    """
+    Python 3.6 implemented unicode console handling for Windows. This works
+    by reading/writing to the raw console handle using
+    ``{Read,Write}ConsoleW``.
+
+    The problem is that we are going to ``dup2`` over the stdio file
+    descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the
+    handles used by Python to write to the console. Though there is still some
+    weirdness and the console handle seems to only be closed randomly and not
+    on the first call to ``CloseHandle``, or maybe it gets reopened with the
+    same handle value when we suspend capturing.
+
+    The workaround in this case will reopen stdio with a different fd which
+    also means a different handle by replicating the logic in
+    "Py_lifecycle.c:initstdio/create_stdio".
+
+    :param stream: in practice ``sys.stdout`` or ``sys.stderr``, but given
+        here as parameter for unittesting purposes.
+
+    See https://github.com/pytest-dev/py/issues/103
+    """
+    if not sys.platform.startswith('win32') or sys.version_info[:2] < (3, 6):
+        return
+
+    # bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)
+    if not hasattr(stream, 'buffer'):
+        return
+
+    buffered = hasattr(stream.buffer, 'raw')
+    raw_stdout = stream.buffer.raw if buffered else stream.buffer
+
+    if not isinstance(raw_stdout, io._WindowsConsoleIO):
+        return
+
+    def _reopen_stdio(f, mode):
+        if not buffered and mode[0] == 'w':
+            buffering = 0
+        else:
+            buffering = -1
+
+        return io.TextIOWrapper(
+            open(os.dup(f.fileno()), mode, buffering),
+            f.encoding,
+            f.errors,
+            f.newlines,
+            f.line_buffering)
+
+    sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, 'rb')
+    sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, 'wb')
+    sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, 'wb')
diff --git a/tools/third_party/pytest/_pytest/compat.py b/tools/third_party/pytest/_pytest/compat.py
new file mode 100644
index 0000000..7560fbe
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/compat.py
@@ -0,0 +1,322 @@
+"""
+python version compatibility code
+"""
+from __future__ import absolute_import, division, print_function
+
+import codecs
+import functools
+import inspect
+import re
+import sys
+
+import py
+
+import _pytest
+from _pytest.outcomes import TEST_OUTCOME
+
+try:
+    import enum
+except ImportError:  # pragma: no cover
+    # Only available in Python 3.4+ or as a backport
+    enum = None
+
+
+_PY3 = sys.version_info > (3, 0)
+_PY2 = not _PY3
+
+
+if _PY3:
+    from inspect import signature, Parameter as Parameter
+else:
+    from funcsigs import signature, Parameter as Parameter
+
+
+NoneType = type(None)
+NOTSET = object()
+
+PY35 = sys.version_info[:2] >= (3, 5)
+PY36 = sys.version_info[:2] >= (3, 6)
+MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError'
+
+
+def _format_args(func):
+    return str(signature(func))
+
+
+isfunction = inspect.isfunction
+isclass = inspect.isclass
+# used to work around a python2 exception info leak
+exc_clear = getattr(sys, 'exc_clear', lambda: None)
+# The type of re.compile objects is not exposed in Python.
+REGEX_TYPE = type(re.compile(''))
+
+
+def is_generator(func):
+    genfunc = inspect.isgeneratorfunction(func)
+    return genfunc and not iscoroutinefunction(func)
+
+
+def iscoroutinefunction(func):
+    """Return True if func is a decorated coroutine function.
+
+    Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
+    which in turns also initializes the "logging" module as side-effect (see issue #8).
+    """
+    return (getattr(func, '_is_coroutine', False) or
+            (hasattr(inspect, 'iscoroutinefunction') and inspect.iscoroutinefunction(func)))
+
+
+def getlocation(function, curdir):
+    fn = py.path.local(inspect.getfile(function))
+    lineno = py.builtin._getcode(function).co_firstlineno
+    if fn.relto(curdir):
+        fn = fn.relto(curdir)
+    return "%s:%d" % (fn, lineno + 1)
+
+
+def num_mock_patch_args(function):
+    """ return number of arguments used up by mock arguments (if any) """
+    patchings = getattr(function, "patchings", None)
+    if not patchings:
+        return 0
+    mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
+    if mock is not None:
+        return len([p for p in patchings
+                    if not p.attribute_name and p.new is mock.DEFAULT])
+    return len(patchings)
+
+
+def getfuncargnames(function, is_method=False, cls=None):
+    """Returns the names of a function's mandatory arguments.
+
+    This should return the names of all function arguments that:
+        * Aren't bound to an instance or type as in instance or class methods.
+        * Don't have default values.
+        * Aren't bound with functools.partial.
+        * Aren't replaced with mocks.
+
+    The is_method and cls arguments indicate that the function should
+    be treated as a bound method even though it's not unless, only in
+    the case of cls, the function is a static method.
+
+    @RonnyPfannschmidt: This function should be refactored when we
+    revisit fixtures. The fixture mechanism should ask the node for
+    the fixture names, and not try to obtain directly from the
+    function object well after collection has occurred.
+
+    """
+    # The parameters attribute of a Signature object contains an
+    # ordered mapping of parameter names to Parameter instances.  This
+    # creates a tuple of the names of the parameters that don't have
+    # defaults.
+    arg_names = tuple(p.name for p in signature(function).parameters.values()
+                      if (p.kind is Parameter.POSITIONAL_OR_KEYWORD or
+                          p.kind is Parameter.KEYWORD_ONLY) and
+                      p.default is Parameter.empty)
+    # If this function should be treated as a bound method even though
+    # it's passed as an unbound method or function, remove the first
+    # parameter name.
+    if (is_method or
+        (cls and not isinstance(cls.__dict__.get(function.__name__, None),
+                                staticmethod))):
+        arg_names = arg_names[1:]
+    # Remove any names that will be replaced with mocks.
+    if hasattr(function, "__wrapped__"):
+        arg_names = arg_names[num_mock_patch_args(function):]
+    return arg_names
+
+
+if _PY3:
+    STRING_TYPES = bytes, str
+    UNICODE_TYPES = str,
+
+    if PY35:
+        def _bytes_to_ascii(val):
+            return val.decode('ascii', 'backslashreplace')
+    else:
+        def _bytes_to_ascii(val):
+            if val:
+                # source: http://goo.gl/bGsnwC
+                encoded_bytes, _ = codecs.escape_encode(val)
+                return encoded_bytes.decode('ascii')
+            else:
+                # empty bytes crashes codecs.escape_encode (#1087)
+                return ''
+
+    def ascii_escaped(val):
+        """If val is pure ascii, returns it as a str().  Otherwise, escapes
+        bytes objects into a sequence of escaped bytes:
+
+        b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
+
+        and escapes unicode objects into a sequence of escaped unicode
+        ids, e.g.:
+
+        '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
+
+        note:
+           the obvious "v.decode('unicode-escape')" will return
+           valid utf-8 unicode if it finds them in bytes, but we
+           want to return escaped bytes for any byte, even if they match
+           a utf-8 string.
+
+        """
+        if isinstance(val, bytes):
+            return _bytes_to_ascii(val)
+        else:
+            return val.encode('unicode_escape').decode('ascii')
+else:
+    STRING_TYPES = bytes, str, unicode
+    UNICODE_TYPES = unicode,
+
+    def ascii_escaped(val):
+        """In py2 bytes and str are the same type, so return if it's a bytes
+        object, return it unchanged if it is a full ascii string,
+        otherwise escape it into its binary form.
+
+        If it's a unicode string, change the unicode characters into
+        unicode escapes.
+
+        """
+        if isinstance(val, bytes):
+            try:
+                return val.encode('ascii')
+            except UnicodeDecodeError:
+                return val.encode('string-escape')
+        else:
+            return val.encode('unicode-escape')
+
+
+def get_real_func(obj):
+    """ gets the real function object of the (possibly) wrapped object by
+    functools.wraps or functools.partial.
+    """
+    start_obj = obj
+    for i in range(100):
+        new_obj = getattr(obj, '__wrapped__', None)
+        if new_obj is None:
+            break
+        obj = new_obj
+    else:
+        raise ValueError(
+            ("could not find real function of {start}"
+             "\nstopped at {current}").format(
+                start=py.io.saferepr(start_obj),
+                current=py.io.saferepr(obj)))
+    if isinstance(obj, functools.partial):
+        obj = obj.func
+    return obj
+
+
+def getfslineno(obj):
+    # xxx let decorators etc specify a sane ordering
+    obj = get_real_func(obj)
+    if hasattr(obj, 'place_as'):
+        obj = obj.place_as
+    fslineno = _pytest._code.getfslineno(obj)
+    assert isinstance(fslineno[1], int), obj
+    return fslineno
+
+
+def getimfunc(func):
+    try:
+        return func.__func__
+    except AttributeError:
+        return func
+
+
+def safe_getattr(object, name, default):
+    """ Like getattr but return default upon any Exception or any OutcomeException.
+
+    Attribute access can potentially fail for 'evil' Python objects.
+    See issue #214.
+    It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
+    instead of Exception (for more details check #2707)
+    """
+    try:
+        return getattr(object, name, default)
+    except TEST_OUTCOME:
+        return default
+
+
+def _is_unittest_unexpected_success_a_failure():
+    """Return if the test suite should fail if a @expectedFailure unittest test PASSES.
+
+    From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful:
+        Changed in version 3.4: Returns False if there were any
+        unexpectedSuccesses from tests marked with the expectedFailure() decorator.
+    """
+    return sys.version_info >= (3, 4)
+
+
+if _PY3:
+    def safe_str(v):
+        """returns v as string"""
+        return str(v)
+else:
+    def safe_str(v):
+        """returns v as string, converting to ascii if necessary"""
+        try:
+            return str(v)
+        except UnicodeError:
+            if not isinstance(v, unicode):
+                v = unicode(v)
+            errors = 'replace'
+            return v.encode('utf-8', errors)
+
+
+COLLECT_FAKEMODULE_ATTRIBUTES = (
+    'Collector',
+    'Module',
+    'Generator',
+    'Function',
+    'Instance',
+    'Session',
+    'Item',
+    'Class',
+    'File',
+    '_fillfuncargs',
+)
+
+
+def _setup_collect_fakemodule():
+    from types import ModuleType
+    import pytest
+    pytest.collect = ModuleType('pytest.collect')
+    pytest.collect.__all__ = []  # used for setns
+    for attr in COLLECT_FAKEMODULE_ATTRIBUTES:
+        setattr(pytest.collect, attr, getattr(pytest, attr))
+
+
+if _PY2:
+    # Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
+    from py.io import TextIO
+
+    class CaptureIO(TextIO):
+
+        @property
+        def encoding(self):
+            return getattr(self, '_encoding', 'UTF-8')
+
+else:
+    import io
+
+    class CaptureIO(io.TextIOWrapper):
+        def __init__(self):
+            super(CaptureIO, self).__init__(
+                io.BytesIO(),
+                encoding='UTF-8', newline='', write_through=True,
+            )
+
+        def getvalue(self):
+            return self.buffer.getvalue().decode('UTF-8')
+
+
+class FuncargnamesCompatAttr(object):
+    """ helper class so that Metafunc, Function and FixtureRequest
+    don't need to each define the "funcargnames" compatibility attribute.
+    """
+    @property
+    def funcargnames(self):
+        """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
+        return self.fixturenames
diff --git a/tools/third_party/pytest/_pytest/config.py b/tools/third_party/pytest/_pytest/config.py
new file mode 100644
index 0000000..499c807
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/config.py
@@ -0,0 +1,1400 @@
+""" command line options, ini-file and conftest.py processing. """
+from __future__ import absolute_import, division, print_function
+import argparse
+import shlex
+import traceback
+import types
+import warnings
+
+import six
+import py
+# DON't import pytest here because it causes import cycle troubles
+import sys
+import os
+import _pytest._code
+import _pytest.hookspec  # the extension point definitions
+import _pytest.assertion
+from pluggy import PluginManager, HookimplMarker, HookspecMarker
+from _pytest.compat import safe_str
+
+hookimpl = HookimplMarker("pytest")
+hookspec = HookspecMarker("pytest")
+
+# pytest startup
+#
+
+
+class ConftestImportFailure(Exception):
+    def __init__(self, path, excinfo):
+        Exception.__init__(self, path, excinfo)
+        self.path = path
+        self.excinfo = excinfo
+
+    def __str__(self):
+        etype, evalue, etb = self.excinfo
+        formatted = traceback.format_tb(etb)
+        # The level of the tracebacks we want to print is hand crafted :(
+        return repr(evalue) + '\n' + ''.join(formatted[2:])
+
+
+def main(args=None, plugins=None):
+    """ return exit code, after performing an in-process test run.
+
+    :arg args: list of command line arguments.
+
+    :arg plugins: list of plugin objects to be auto-registered during
+                  initialization.
+    """
+    try:
+        try:
+            config = _prepareconfig(args, plugins)
+        except ConftestImportFailure as e:
+            tw = py.io.TerminalWriter(sys.stderr)
+            for line in traceback.format_exception(*e.excinfo):
+                tw.line(line.rstrip(), red=True)
+            tw.line("ERROR: could not load %s\n" % (e.path), red=True)
+            return 4
+        else:
+            try:
+                return config.hook.pytest_cmdline_main(config=config)
+            finally:
+                config._ensure_unconfigure()
+    except UsageError as e:
+        for msg in e.args:
+            sys.stderr.write("ERROR: %s\n" % (msg,))
+        return 4
+
+
+class cmdline:  # compatibility namespace
+    main = staticmethod(main)
+
+
+class UsageError(Exception):
+    """ error in pytest usage or invocation"""
+
+
+class PrintHelp(Exception):
+    """Raised when pytest should print it's help to skip the rest of the
+    argument parsing and validation."""
+    pass
+
+
+def filename_arg(path, optname):
+    """ Argparse type validator for filename arguments.
+
+    :path: path of filename
+    :optname: name of the option
+    """
+    if os.path.isdir(path):
+        raise UsageError("{0} must be a filename, given: {1}".format(optname, path))
+    return path
+
+
+def directory_arg(path, optname):
+    """Argparse type validator for directory arguments.
+
+    :path: path of directory
+    :optname: name of the option
+    """
+    if not os.path.isdir(path):
+        raise UsageError("{0} must be a directory, given: {1}".format(optname, path))
+    return path
+
+
+default_plugins = (
+    "mark main terminal runner python fixtures debugging unittest capture skipping "
+    "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
+    "junitxml resultlog doctest cacheprovider freeze_support "
+    "setuponly setupplan warnings logging").split()
+
+
+builtin_plugins = set(default_plugins)
+builtin_plugins.add("pytester")
+
+
+def get_config():
+    # subsequent calls to main will create a fresh instance
+    pluginmanager = PytestPluginManager()
+    config = Config(pluginmanager)
+    for spec in default_plugins:
+        pluginmanager.import_plugin(spec)
+    return config
+
+
+def get_plugin_manager():
+    """
+    Obtain a new instance of the
+    :py:class:`_pytest.config.PytestPluginManager`, with default plugins
+    already loaded.
+
+    This function can be used by integration with other tools, like hooking
+    into pytest to run tests into an IDE.
+    """
+    return get_config().pluginmanager
+
+
+def _prepareconfig(args=None, plugins=None):
+    warning = None
+    if args is None:
+        args = sys.argv[1:]
+    elif isinstance(args, py.path.local):
+        args = [str(args)]
+    elif not isinstance(args, (tuple, list)):
+        if not isinstance(args, str):
+            raise ValueError("not a string or argument list: %r" % (args,))
+        args = shlex.split(args, posix=sys.platform != "win32")
+        from _pytest import deprecated
+        warning = deprecated.MAIN_STR_ARGS
+    config = get_config()
+    pluginmanager = config.pluginmanager
+    try:
+        if plugins:
+            for plugin in plugins:
+                if isinstance(plugin, six.string_types):
+                    pluginmanager.consider_pluginarg(plugin)
+                else:
+                    pluginmanager.register(plugin)
+        if warning:
+            config.warn('C1', warning)
+        return pluginmanager.hook.pytest_cmdline_parse(
+            pluginmanager=pluginmanager, args=args)
+    except BaseException:
+        config._ensure_unconfigure()
+        raise
+
+
+class PytestPluginManager(PluginManager):
+    """
+    Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
+    functionality:
+
+    * loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
+      ``pytest_plugins`` global variables found in plugins being loaded;
+    * ``conftest.py`` loading during start-up;
+    """
+
+    def __init__(self):
+        super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
+        self._conftest_plugins = set()
+
+        # state related to local conftest plugins
+        self._path2confmods = {}
+        self._conftestpath2mod = {}
+        self._confcutdir = None
+        self._noconftest = False
+        self._duplicatepaths = set()
+
+        self.add_hookspecs(_pytest.hookspec)
+        self.register(self)
+        if os.environ.get('PYTEST_DEBUG'):
+            err = sys.stderr
+            encoding = getattr(err, 'encoding', 'utf8')
+            try:
+                err = py.io.dupfile(err, encoding=encoding)
+            except Exception:
+                pass
+            self.trace.root.setwriter(err.write)
+            self.enable_tracing()
+
+        # Config._consider_importhook will set a real object if required.
+        self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
+
+    def addhooks(self, module_or_class):
+        """
+        .. deprecated:: 2.8
+
+        Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
+        instead.
+        """
+        warning = dict(code="I2",
+                       fslocation=_pytest._code.getfslineno(sys._getframe(1)),
+                       nodeid=None,
+                       message="use pluginmanager.add_hookspecs instead of "
+                               "deprecated addhooks() method.")
+        self._warn(warning)
+        return self.add_hookspecs(module_or_class)
+
+    def parse_hookimpl_opts(self, plugin, name):
+        # pytest hooks are always prefixed with pytest_
+        # so we avoid accessing possibly non-readable attributes
+        # (see issue #1073)
+        if not name.startswith("pytest_"):
+            return
+        # ignore some historic special names which can not be hooks anyway
+        if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
+            return
+
+        method = getattr(plugin, name)
+        opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
+        if opts is not None:
+            for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
+                opts.setdefault(name, hasattr(method, name))
+        return opts
+
+    def parse_hookspec_opts(self, module_or_class, name):
+        opts = super(PytestPluginManager, self).parse_hookspec_opts(
+            module_or_class, name)
+        if opts is None:
+            method = getattr(module_or_class, name)
+            if name.startswith("pytest_"):
+                opts = {"firstresult": hasattr(method, "firstresult"),
+                        "historic": hasattr(method, "historic")}
+        return opts
+
+    def register(self, plugin, name=None):
+        if name == 'pytest_catchlog':
+            self._warn('pytest-catchlog plugin has been merged into the core, '
+                       'please remove it from your requirements.')
+            return
+        ret = super(PytestPluginManager, self).register(plugin, name)
+        if ret:
+            self.hook.pytest_plugin_registered.call_historic(
+                kwargs=dict(plugin=plugin, manager=self))
+
+            if isinstance(plugin, types.ModuleType):
+                self.consider_module(plugin)
+        return ret
+
+    def getplugin(self, name):
+        # support deprecated naming because plugins (xdist e.g.) use it
+        return self.get_plugin(name)
+
+    def hasplugin(self, name):
+        """Return True if the plugin with the given name is registered."""
+        return bool(self.get_plugin(name))
+
+    def pytest_configure(self, config):
+        # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
+        # we should remove tryfirst/trylast as markers
+        config.addinivalue_line("markers",
+                                "tryfirst: mark a hook implementation function such that the "
+                                "plugin machinery will try to call it first/as early as possible.")
+        config.addinivalue_line("markers",
+                                "trylast: mark a hook implementation function such that the "
+                                "plugin machinery will try to call it last/as late as possible.")
+
+    def _warn(self, message):
+        kwargs = message if isinstance(message, dict) else {
+            'code': 'I1',
+            'message': message,
+            'fslocation': None,
+            'nodeid': None,
+        }
+        self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
+
+    #
+    # internal API for local conftest plugin handling
+    #
+    def _set_initial_conftests(self, namespace):
+        """ load initial conftest files given a preparsed "namespace".
+            As conftest files may add their own command line options
+            which have arguments ('--my-opt somepath') we might get some
+            false positives.  All builtin and 3rd party plugins will have
+            been loaded, however, so common options will not confuse our logic
+            here.
+        """
+        current = py.path.local()
+        self._confcutdir = current.join(namespace.confcutdir, abs=True) \
+            if namespace.confcutdir else None
+        self._noconftest = namespace.noconftest
+        testpaths = namespace.file_or_dir
+        foundanchor = False
+        for path in testpaths:
+            path = str(path)
+            # remove node-id syntax
+            i = path.find("::")
+            if i != -1:
+                path = path[:i]
+            anchor = current.join(path, abs=1)
+            if exists(anchor):  # we found some file object
+                self._try_load_conftest(anchor)
+                foundanchor = True
+        if not foundanchor:
+            self._try_load_conftest(current)
+
+    def _try_load_conftest(self, anchor):
+        self._getconftestmodules(anchor)
+        # let's also consider test* subdirs
+        if anchor.check(dir=1):
+            for x in anchor.listdir("test*"):
+                if x.check(dir=1):
+                    self._getconftestmodules(x)
+
+    def _getconftestmodules(self, path):
+        if self._noconftest:
+            return []
+        try:
+            return self._path2confmods[path]
+        except KeyError:
+            if path.isfile():
+                clist = self._getconftestmodules(path.dirpath())
+            else:
+                # XXX these days we may rather want to use config.rootdir
+                # and allow users to opt into looking into the rootdir parent
+                # directories instead of requiring to specify confcutdir
+                clist = []
+                for parent in path.parts():
+                    if self._confcutdir and self._confcutdir.relto(parent):
+                        continue
+                    conftestpath = parent.join("conftest.py")
+                    if conftestpath.isfile():
+                        mod = self._importconftest(conftestpath)
+                        clist.append(mod)
+
+            self._path2confmods[path] = clist
+            return clist
+
+    def _rget_with_confmod(self, name, path):
+        modules = self._getconftestmodules(path)
+        for mod in reversed(modules):
+            try:
+                return mod, getattr(mod, name)
+            except AttributeError:
+                continue
+        raise KeyError(name)
+
+    def _importconftest(self, conftestpath):
+        try:
+            return self._conftestpath2mod[conftestpath]
+        except KeyError:
+            pkgpath = conftestpath.pypkgpath()
+            if pkgpath is None:
+                _ensure_removed_sysmodule(conftestpath.purebasename)
+            try:
+                mod = conftestpath.pyimport()
+            except Exception:
+                raise ConftestImportFailure(conftestpath, sys.exc_info())
+
+            self._conftest_plugins.add(mod)
+            self._conftestpath2mod[conftestpath] = mod
+            dirpath = conftestpath.dirpath()
+            if dirpath in self._path2confmods:
+                for path, mods in self._path2confmods.items():
+                    if path and path.relto(dirpath) or path == dirpath:
+                        assert mod not in mods
+                        mods.append(mod)
+            self.trace("loaded conftestmodule %r" % (mod))
+            self.consider_conftest(mod)
+            return mod
+
+    #
+    # API for bootstrapping plugin loading
+    #
+    #
+
+    def consider_preparse(self, args):
+        for opt1, opt2 in zip(args, args[1:]):
+            if opt1 == "-p":
+                self.consider_pluginarg(opt2)
+
+    def consider_pluginarg(self, arg):
+        if arg.startswith("no:"):
+            name = arg[3:]
+            self.set_blocked(name)
+            if not name.startswith("pytest_"):
+                self.set_blocked("pytest_" + name)
+        else:
+            self.import_plugin(arg)
+
+    def consider_conftest(self, conftestmodule):
+        self.register(conftestmodule, name=conftestmodule.__file__)
+
+    def consider_env(self):
+        self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
+
+    def consider_module(self, mod):
+        self._import_plugin_specs(getattr(mod, 'pytest_plugins', []))
+
+    def _import_plugin_specs(self, spec):
+        plugins = _get_plugin_specs_as_list(spec)
+        for import_spec in plugins:
+            self.import_plugin(import_spec)
+
+    def import_plugin(self, modname):
+        # most often modname refers to builtin modules, e.g. "pytester",
+        # "terminal" or "capture".  Those plugins are registered under their
+        # basename for historic purposes but must be imported with the
+        # _pytest prefix.
+        assert isinstance(modname, (six.text_type, str)), "module name as text required, got %r" % modname
+        modname = str(modname)
+        if self.is_blocked(modname) or self.get_plugin(modname) is not None:
+            return
+        if modname in builtin_plugins:
+            importspec = "_pytest." + modname
+        else:
+            importspec = modname
+        self.rewrite_hook.mark_rewrite(importspec)
+        try:
+            __import__(importspec)
+        except ImportError as e:
+            new_exc_type = ImportError
+            new_exc_message = 'Error importing plugin "%s": %s' % (modname, safe_str(e.args[0]))
+            new_exc = new_exc_type(new_exc_message)
+
+            six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
+
+        except Exception as e:
+            import pytest
+            if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
+                raise
+            self._warn("skipped plugin %r: %s" % ((modname, e.msg)))
+        else:
+            mod = sys.modules[importspec]
+            self.register(mod, modname)
+
+
+def _get_plugin_specs_as_list(specs):
+    """
+    Parses a list of "plugin specs" and returns a list of plugin names.
+
+    Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in
+    which case it is returned as a list. Specs can also be `None` in which case an
+    empty list is returned.
+    """
+    if specs is not None:
+        if isinstance(specs, str):
+            specs = specs.split(',') if specs else []
+        if not isinstance(specs, (list, tuple)):
+            raise UsageError("Plugin specs must be a ','-separated string or a "
+                             "list/tuple of strings for plugin names. Given: %r" % specs)
+        return list(specs)
+    return []
+
+
+class Parser:
+    """ Parser for command line arguments and ini-file values.
+
+    :ivar extra_info: dict of generic param -> value to display in case
+        there's an error processing the command line arguments.
+    """
+
+    def __init__(self, usage=None, processopt=None):
+        self._anonymous = OptionGroup("custom options", parser=self)
+        self._groups = []
+        self._processopt = processopt
+        self._usage = usage
+        self._inidict = {}
+        self._ininames = []
+        self.extra_info = {}
+
+    def processoption(self, option):
+        if self._processopt:
+            if option.dest:
+                self._processopt(option)
+
+    def getgroup(self, name, description="", after=None):
+        """ get (or create) a named option Group.
+
+        :name: name of the option group.
+        :description: long description for --help output.
+        :after: name of other group, used for ordering --help output.
+
+        The returned group object has an ``addoption`` method with the same
+        signature as :py:func:`parser.addoption
+        <_pytest.config.Parser.addoption>` but will be shown in the
+        respective group in the output of ``pytest. --help``.
+        """
+        for group in self._groups:
+            if group.name == name:
+                return group
+        group = OptionGroup(name, description, parser=self)
+        i = 0
+        for i, grp in enumerate(self._groups):
+            if grp.name == after:
+                break
+        self._groups.insert(i + 1, group)
+        return group
+
+    def addoption(self, *opts, **attrs):
+        """ register a command line option.
+
+        :opts: option names, can be short or long options.
+        :attrs: same attributes which the ``add_option()`` function of the
+           `argparse library
+           <http://docs.python.org/2/library/argparse.html>`_
+           accepts.
+
+        After command line parsing options are available on the pytest config
+        object via ``config.option.NAME`` where ``NAME`` is usually set
+        by passing a ``dest`` attribute, for example
+        ``addoption("--long", dest="NAME", ...)``.
+        """
+        self._anonymous.addoption(*opts, **attrs)
+
+    def parse(self, args, namespace=None):
+        from _pytest._argcomplete import try_argcomplete
+        self.optparser = self._getparser()
+        try_argcomplete(self.optparser)
+        return self.optparser.parse_args([str(x) for x in args], namespace=namespace)
+
+    def _getparser(self):
+        from _pytest._argcomplete import filescompleter
+        optparser = MyOptionParser(self, self.extra_info)
+        groups = self._groups + [self._anonymous]
+        for group in groups:
+            if group.options:
+                desc = group.description or group.name
+                arggroup = optparser.add_argument_group(desc)
+                for option in group.options:
+                    n = option.names()
+                    a = option.attrs()
+                    arggroup.add_argument(*n, **a)
+        # bash like autocompletion for dirs (appending '/')
+        optparser.add_argument(FILE_OR_DIR, nargs='*').completer = filescompleter
+        return optparser
+
+    def parse_setoption(self, args, option, namespace=None):
+        parsedoption = self.parse(args, namespace=namespace)
+        for name, value in parsedoption.__dict__.items():
+            setattr(option, name, value)
+        return getattr(parsedoption, FILE_OR_DIR)
+
+    def parse_known_args(self, args, namespace=None):
+        """parses and returns a namespace object with known arguments at this
+        point.
+        """
+        return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
+
+    def parse_known_and_unknown_args(self, args, namespace=None):
+        """parses and returns a namespace object with known arguments, and
+        the remaining arguments unknown at this point.
+        """
+        optparser = self._getparser()
+        args = [str(x) for x in args]
+        return optparser.parse_known_args(args, namespace=namespace)
+
+    def addini(self, name, help, type=None, default=None):
+        """ register an ini-file option.
+
+        :name: name of the ini-variable
+        :type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
+               or ``bool``.
+        :default: default value if no ini-file option exists but is queried.
+
+        The value of ini-variables can be retrieved via a call to
+        :py:func:`config.getini(name) <_pytest.config.Config.getini>`.
+        """
+        assert type in (None, "pathlist", "args", "linelist", "bool")
+        self._inidict[name] = (help, type, default)
+        self._ininames.append(name)
+
+
+class ArgumentError(Exception):
+    """
+    Raised if an Argument instance is created with invalid or
+    inconsistent arguments.
+    """
+
+    def __init__(self, msg, option):
+        self.msg = msg
+        self.option_id = str(option)
+
+    def __str__(self):
+        if self.option_id:
+            return "option %s: %s" % (self.option_id, self.msg)
+        else:
+            return self.msg
+
+
+class Argument:
+    """class that mimics the necessary behaviour of optparse.Option
+
+    its currently a least effort implementation
+    and ignoring choices and integer prefixes
+    https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
+    """
+    _typ_map = {
+        'int': int,
+        'string': str,
+        'float': float,
+        'complex': complex,
+    }
+
+    def __init__(self, *names, **attrs):
+        """store parms in private vars for use in add_argument"""
+        self._attrs = attrs
+        self._short_opts = []
+        self._long_opts = []
+        self.dest = attrs.get('dest')
+        if '%default' in (attrs.get('help') or ''):
+            warnings.warn(
+                'pytest now uses argparse. "%default" should be'
+                ' changed to "%(default)s" ',
+                DeprecationWarning,
+                stacklevel=3)
+        try:
+            typ = attrs['type']
+        except KeyError:
+            pass
+        else:
+            # this might raise a keyerror as well, don't want to catch that
+            if isinstance(typ, six.string_types):
+                if typ == 'choice':
+                    warnings.warn(
+                        'type argument to addoption() is a string %r.'
+                        ' For parsearg this is optional and when supplied'
+                        ' should be a type.'
+                        ' (options: %s)' % (typ, names),
+                        DeprecationWarning,
+                        stacklevel=3)
+                    # argparse expects a type here take it from
+                    # the type of the first element
+                    attrs['type'] = type(attrs['choices'][0])
+                else:
+                    warnings.warn(
+                        'type argument to addoption() is a string %r.'
+                        ' For parsearg this should be a type.'
+                        ' (options: %s)' % (typ, names),
+                        DeprecationWarning,
+                        stacklevel=3)
+                    attrs['type'] = Argument._typ_map[typ]
+                # used in test_parseopt -> test_parse_defaultgetter
+                self.type = attrs['type']
+            else:
+                self.type = typ
+        try:
+            # attribute existence is tested in Config._processopt
+            self.default = attrs['default']
+        except KeyError:
+            pass
+        self._set_opt_strings(names)
+        if not self.dest:
+            if self._long_opts:
+                self.dest = self._long_opts[0][2:].replace('-', '_')
+            else:
+                try:
+                    self.dest = self._short_opts[0][1:]
+                except IndexError:
+                    raise ArgumentError(
+                        'need a long or short option', self)
+
+    def names(self):
+        return self._short_opts + self._long_opts
+
+    def attrs(self):
+        # update any attributes set by processopt
+        attrs = 'default dest help'.split()
+        if self.dest:
+            attrs.append(self.dest)
+        for attr in attrs:
+            try:
+                self._attrs[attr] = getattr(self, attr)
+            except AttributeError:
+                pass
+        if self._attrs.get('help'):
+            a = self._attrs['help']
+            a = a.replace('%default', '%(default)s')
+            # a = a.replace('%prog', '%(prog)s')
+            self._attrs['help'] = a
+        return self._attrs
+
+    def _set_opt_strings(self, opts):
+        """directly from optparse
+
+        might not be necessary as this is passed to argparse later on"""
+        for opt in opts:
+            if len(opt) < 2:
+                raise ArgumentError(
+                    "invalid option string %r: "
+                    "must be at least two characters long" % opt, self)
+            elif len(opt) == 2:
+                if not (opt[0] == "-" and opt[1] != "-"):
+                    raise ArgumentError(
+                        "invalid short option string %r: "
+                        "must be of the form -x, (x any non-dash char)" % opt,
+                        self)
+                self._short_opts.append(opt)
+            else:
+                if not (opt[0:2] == "--" and opt[2] != "-"):
+                    raise ArgumentError(
+                        "invalid long option string %r: "
+                        "must start with --, followed by non-dash" % opt,
+                        self)
+                self._long_opts.append(opt)
+
+    def __repr__(self):
+        args = []
+        if self._short_opts:
+            args += ['_short_opts: ' + repr(self._short_opts)]
+        if self._long_opts:
+            args += ['_long_opts: ' + repr(self._long_opts)]
+        args += ['dest: ' + repr(self.dest)]
+        if hasattr(self, 'type'):
+            args += ['type: ' + repr(self.type)]
+        if hasattr(self, 'default'):
+            args += ['default: ' + repr(self.default)]
+        return 'Argument({0})'.format(', '.join(args))
+
+
+class OptionGroup:
+    def __init__(self, name, description="", parser=None):
+        self.name = name
+        self.description = description
+        self.options = []
+        self.parser = parser
+
+    def addoption(self, *optnames, **attrs):
+        """ add an option to this group.
+
+        if a shortened version of a long option is specified it will
+        be suppressed in the help. addoption('--twowords', '--two-words')
+        results in help showing '--two-words' only, but --twowords gets
+        accepted **and** the automatic destination is in args.twowords
+        """
+        conflict = set(optnames).intersection(
+            name for opt in self.options for name in opt.names())
+        if conflict:
+            raise ValueError("option names %s already added" % conflict)
+        option = Argument(*optnames, **attrs)
+        self._addoption_instance(option, shortupper=False)
+
+    def _addoption(self, *optnames, **attrs):
+        option = Argument(*optnames, **attrs)
+        self._addoption_instance(option, shortupper=True)
+
+    def _addoption_instance(self, option, shortupper=False):
+        if not shortupper:
+            for opt in option._short_opts:
+                if opt[0] == '-' and opt[1].islower():
+                    raise ValueError("lowercase shortoptions reserved")
+        if self.parser:
+            self.parser.processoption(option)
+        self.options.append(option)
+
+
+class MyOptionParser(argparse.ArgumentParser):
+    def __init__(self, parser, extra_info=None):
+        if not extra_info:
+            extra_info = {}
+        self._parser = parser
+        argparse.ArgumentParser.__init__(self, usage=parser._usage,
+                                         add_help=False, formatter_class=DropShorterLongHelpFormatter)
+        # extra_info is a dict of (param -> value) to display if there's
+        # an usage error to provide more contextual information to the user
+        self.extra_info = extra_info
+
+    def parse_args(self, args=None, namespace=None):
+        """allow splitting of positional arguments"""
+        args, argv = self.parse_known_args(args, namespace)
+        if argv:
+            for arg in argv:
+                if arg and arg[0] == '-':
+                    lines = ['unrecognized arguments: %s' % (' '.join(argv))]
+                    for k, v in sorted(self.extra_info.items()):
+                        lines.append('  %s: %s' % (k, v))
+                    self.error('\n'.join(lines))
+            getattr(args, FILE_OR_DIR).extend(argv)
+        return args
+
+
+class DropShorterLongHelpFormatter(argparse.HelpFormatter):
+    """shorten help for long options that differ only in extra hyphens
+
+    - collapse **long** options that are the same except for extra hyphens
+    - special action attribute map_long_option allows surpressing additional
+      long options
+    - shortcut if there are only two options and one of them is a short one
+    - cache result on action object as this is called at least 2 times
+    """
+
+    def _format_action_invocation(self, action):
+        orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
+        if orgstr and orgstr[0] != '-':  # only optional arguments
+            return orgstr
+        res = getattr(action, '_formatted_action_invocation', None)
+        if res:
+            return res
+        options = orgstr.split(', ')
+        if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
+            # a shortcut for '-h, --help' or '--abc', '-a'
+            action._formatted_action_invocation = orgstr
+            return orgstr
+        return_list = []
+        option_map = getattr(action, 'map_long_option', {})
+        if option_map is None:
+            option_map = {}
+        short_long = {}
+        for option in options:
+            if len(option) == 2 or option[2] == ' ':
+                continue
+            if not option.startswith('--'):
+                raise ArgumentError('long optional argument without "--": [%s]'
+                                    % (option), self)
+            xxoption = option[2:]
+            if xxoption.split()[0] not in option_map:
+                shortened = xxoption.replace('-', '')
+                if shortened not in short_long or \
+                   len(short_long[shortened]) < len(xxoption):
+                    short_long[shortened] = xxoption
+        # now short_long has been filled out to the longest with dashes
+        # **and** we keep the right option ordering from add_argument
+        for option in options:
+            if len(option) == 2 or option[2] == ' ':
+                return_list.append(option)
+            if option[2:] == short_long.get(option.replace('-', '')):
+                return_list.append(option.replace(' ', '=', 1))
+        action._formatted_action_invocation = ', '.join(return_list)
+        return action._formatted_action_invocation
+
+
+def _ensure_removed_sysmodule(modname):
+    try:
+        del sys.modules[modname]
+    except KeyError:
+        pass
+
+
+class CmdOptions(object):
+    """ holds cmdline options as attributes."""
+
+    def __init__(self, values=()):
+        self.__dict__.update(values)
+
+    def __repr__(self):
+        return "<CmdOptions %r>" % (self.__dict__,)
+
+    def copy(self):
+        return CmdOptions(self.__dict__)
+
+
+class Notset:
+    def __repr__(self):
+        return "<NOTSET>"
+
+
+notset = Notset()
+FILE_OR_DIR = 'file_or_dir'
+
+
+def _iter_rewritable_modules(package_files):
+    for fn in package_files:
+        is_simple_module = '/' not in fn and fn.endswith('.py')
+        is_package = fn.count('/') == 1 and fn.endswith('__init__.py')
+        if is_simple_module:
+            module_name, _ = os.path.splitext(fn)
+            yield module_name
+        elif is_package:
+            package_name = os.path.dirname(fn)
+            yield package_name
+
+
+class Config(object):
+    """ access to configuration values, pluginmanager and plugin hooks.  """
+
+    def __init__(self, pluginmanager):
+        #: access to command line option as attributes.
+        #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
+        self.option = CmdOptions()
+        _a = FILE_OR_DIR
+        self._parser = Parser(
+            usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
+            processopt=self._processopt,
+        )
+        #: a pluginmanager instance
+        self.pluginmanager = pluginmanager
+        self.trace = self.pluginmanager.trace.root.get("config")
+        self.hook = self.pluginmanager.hook
+        self._inicache = {}
+        self._override_ini = ()
+        self._opt2dest = {}
+        self._cleanup = []
+        self._warn = self.pluginmanager._warn
+        self.pluginmanager.register(self, "pytestconfig")
+        self._configured = False
+
+        def do_setns(dic):
+            import pytest
+            setns(pytest, dic)
+
+        self.hook.pytest_namespace.call_historic(do_setns, {})
+        self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
+
+    def add_cleanup(self, func):
+        """ Add a function to be called when the config object gets out of
+        use (usually coninciding with pytest_unconfigure)."""
+        self._cleanup.append(func)
+
+    def _do_configure(self):
+        assert not self._configured
+        self._configured = True
+        self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
+
+    def _ensure_unconfigure(self):
+        if self._configured:
+            self._configured = False
+            self.hook.pytest_unconfigure(config=self)
+            self.hook.pytest_configure._call_history = []
+        while self._cleanup:
+            fin = self._cleanup.pop()
+            fin()
+
+    def warn(self, code, message, fslocation=None, nodeid=None):
+        """ generate a warning for this test session. """
+        self.hook.pytest_logwarning.call_historic(kwargs=dict(
+            code=code, message=message,
+            fslocation=fslocation, nodeid=nodeid))
+
+    def get_terminal_writer(self):
+        return self.pluginmanager.get_plugin("terminalreporter")._tw
+
+    def pytest_cmdline_parse(self, pluginmanager, args):
+        # REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
+        self.parse(args)
+        return self
+
+    def notify_exception(self, excinfo, option=None):
+        if option and option.fulltrace:
+            style = "long"
+        else:
+            style = "native"
+        excrepr = excinfo.getrepr(funcargs=True,
+                                  showlocals=getattr(option, 'showlocals', False),
+                                  style=style,
+                                  )
+        res = self.hook.pytest_internalerror(excrepr=excrepr,
+                                             excinfo=excinfo)
+        if not any(res):
+            for line in str(excrepr).split("\n"):
+                sys.stderr.write("INTERNALERROR> %s\n" % line)
+                sys.stderr.flush()
+
+    def cwd_relative_nodeid(self, nodeid):
+        # nodeid's are relative to the rootpath, compute relative to cwd
+        if self.invocation_dir != self.rootdir:
+            fullpath = self.rootdir.join(nodeid)
+            nodeid = self.invocation_dir.bestrelpath(fullpath)
+        return nodeid
+
+    @classmethod
+    def fromdictargs(cls, option_dict, args):
+        """ constructor useable for subprocesses. """
+        config = get_config()
+        config.option.__dict__.update(option_dict)
+        config.parse(args, addopts=False)
+        for x in config.option.plugins:
+            config.pluginmanager.consider_pluginarg(x)
+        return config
+
+    def _processopt(self, opt):
+        for name in opt._short_opts + opt._long_opts:
+            self._opt2dest[name] = opt.dest
+
+        if hasattr(opt, 'default') and opt.dest:
+            if not hasattr(self.option, opt.dest):
+                setattr(self.option, opt.dest, opt.default)
+
+    @hookimpl(trylast=True)
+    def pytest_load_initial_conftests(self, early_config):
+        self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
+
+    def _initini(self, args):
+        ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy())
+        r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args, warnfunc=self.warn)
+        self.rootdir, self.inifile, self.inicfg = r
+        self._parser.extra_info['rootdir'] = self.rootdir
+        self._parser.extra_info['inifile'] = self.inifile
+        self.invocation_dir = py.path.local()
+        self._parser.addini('addopts', 'extra command line options', 'args')
+        self._parser.addini('minversion', 'minimally required pytest version')
+        self._override_ini = ns.override_ini or ()
+
+    def _consider_importhook(self, args):
+        """Install the PEP 302 import hook if using assertion rewriting.
+
+        Needs to parse the --assert=<mode> option from the commandline
+        and find all the installed plugins to mark them for rewriting
+        by the importhook.
+        """
+        ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
+        mode = ns.assertmode
+        if mode == 'rewrite':
+            try:
+                hook = _pytest.assertion.install_importhook(self)
+            except SystemError:
+                mode = 'plain'
+            else:
+                self._mark_plugins_for_rewrite(hook)
+        self._warn_about_missing_assertion(mode)
+
+    def _mark_plugins_for_rewrite(self, hook):
+        """
+        Given an importhook, mark for rewrite any top-level
+        modules or packages in the distribution package for
+        all pytest plugins.
+        """
+        import pkg_resources
+        self.pluginmanager.rewrite_hook = hook
+
+        # 'RECORD' available for plugins installed normally (pip install)
+        # 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
+        # for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
+        # so it shouldn't be an issue
+        metadata_files = 'RECORD', 'SOURCES.txt'
+
+        package_files = (
+            entry.split(',')[0]
+            for entrypoint in pkg_resources.iter_entry_points('pytest11')
+            for metadata in metadata_files
+            for entry in entrypoint.dist._get_metadata(metadata)
+        )
+
+        for name in _iter_rewritable_modules(package_files):
+            hook.mark_rewrite(name)
+
+    def _warn_about_missing_assertion(self, mode):
+        try:
+            assert False
+        except AssertionError:
+            pass
+        else:
+            if mode == 'plain':
+                sys.stderr.write("WARNING: ASSERTIONS ARE NOT EXECUTED"
+                                 " and FAILING TESTS WILL PASS.  Are you"
+                                 " using python -O?")
+            else:
+                sys.stderr.write("WARNING: assertions not in test modules or"
+                                 " plugins will be ignored"
+                                 " because assert statements are not executed "
+                                 "by the underlying Python interpreter "
+                                 "(are you using python -O?)\n")
+
+    def _preparse(self, args, addopts=True):
+        if addopts:
+            args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
+        self._initini(args)
+        if addopts:
+            args[:] = self.getini("addopts") + args
+        self._checkversion()
+        self._consider_importhook(args)
+        self.pluginmanager.consider_preparse(args)
+        self.pluginmanager.load_setuptools_entrypoints('pytest11')
+        self.pluginmanager.consider_env()
+        self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
+        if self.known_args_namespace.confcutdir is None and self.inifile:
+            confcutdir = py.path.local(self.inifile).dirname
+            self.known_args_namespace.confcutdir = confcutdir
+        try:
+            self.hook.pytest_load_initial_conftests(early_config=self,
+                                                    args=args, parser=self._parser)
+        except ConftestImportFailure:
+            e = sys.exc_info()[1]
+            if ns.help or ns.version:
+                # we don't want to prevent --help/--version to work
+                # so just let is pass and print a warning at the end
+                self._warn("could not load initial conftests (%s)\n" % e.path)
+            else:
+                raise
+
+    def _checkversion(self):
+        import pytest
+        minver = self.inicfg.get('minversion', None)
+        if minver:
+            ver = minver.split(".")
+            myver = pytest.__version__.split(".")
+            if myver < ver:
+                raise pytest.UsageError(
+                    "%s:%d: requires pytest-%s, actual pytest-%s'" % (
+                        self.inicfg.config.path, self.inicfg.lineof('minversion'),
+                        minver, pytest.__version__))
+
+    def parse(self, args, addopts=True):
+        # parse given cmdline arguments into this config object.
+        assert not hasattr(self, 'args'), (
+            "can only parse cmdline args at most once per Config object")
+        self._origargs = args
+        self.hook.pytest_addhooks.call_historic(
+            kwargs=dict(pluginmanager=self.pluginmanager))
+        self._preparse(args, addopts=addopts)
+        # XXX deprecated hook:
+        self.hook.pytest_cmdline_preparse(config=self, args=args)
+        self._parser.after_preparse = True
+        try:
+            args = self._parser.parse_setoption(args, self.option, namespace=self.option)
+            if not args:
+                cwd = os.getcwd()
+                if cwd == self.rootdir:
+                    args = self.getini('testpaths')
+                if not args:
+                    args = [cwd]
+            self.args = args
+        except PrintHelp:
+            pass
+
+    def addinivalue_line(self, name, line):
+        """ add a line to an ini-file option. The option must have been
+        declared but might not yet be set in which case the line becomes the
+        the first line in its value. """
+        x = self.getini(name)
+        assert isinstance(x, list)
+        x.append(line)  # modifies the cached list inline
+
+    def getini(self, name):
+        """ return configuration value from an :ref:`ini file <inifiles>`. If the
+        specified name hasn't been registered through a prior
+        :py:func:`parser.addini <_pytest.config.Parser.addini>`
+        call (usually from a plugin), a ValueError is raised. """
+        try:
+            return self._inicache[name]
+        except KeyError:
+            self._inicache[name] = val = self._getini(name)
+            return val
+
+    def _getini(self, name):
+        try:
+            description, type, default = self._parser._inidict[name]
+        except KeyError:
+            raise ValueError("unknown configuration value: %r" % (name,))
+        value = self._get_override_ini_value(name)
+        if value is None:
+            try:
+                value = self.inicfg[name]
+            except KeyError:
+                if default is not None:
+                    return default
+                if type is None:
+                    return ''
+                return []
+        if type == "pathlist":
+            dp = py.path.local(self.inicfg.config.path).dirpath()
+            values = []
+            for relpath in shlex.split(value):
+                values.append(dp.join(relpath, abs=True))
+            return values
+        elif type == "args":
+            return shlex.split(value)
+        elif type == "linelist":
+            return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
+        elif type == "bool":
+            return bool(_strtobool(value.strip()))
+        else:
+            assert type is None
+            return value
+
+    def _getconftest_pathlist(self, name, path):
+        try:
+            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
+        except KeyError:
+            return None
+        modpath = py.path.local(mod.__file__).dirpath()
+        values = []
+        for relroot in relroots:
+            if not isinstance(relroot, py.path.local):
+                relroot = relroot.replace("/", py.path.local.sep)
+                relroot = modpath.join(relroot, abs=True)
+            values.append(relroot)
+        return values
+
+    def _get_override_ini_value(self, name):
+        value = None
+        # override_ini is a list of list, to support both -o foo1=bar1 foo2=bar2 and
+        # and -o foo1=bar1 -o foo2=bar2 options
+        # always use the last item if multiple value set for same ini-name,
+        # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
+        for ini_config_list in self._override_ini:
+            for ini_config in ini_config_list:
+                try:
+                    (key, user_ini_value) = ini_config.split("=", 1)
+                except ValueError:
+                    raise UsageError("-o/--override-ini expects option=value style.")
+                if key == name:
+                    value = user_ini_value
+        return value
+
+    def getoption(self, name, default=notset, skip=False):
+        """ return command line option value.
+
+        :arg name: name of the option.  You may also specify
+            the literal ``--OPT`` option instead of the "dest" option name.
+        :arg default: default value if no option of that name exists.
+        :arg skip: if True raise pytest.skip if option does not exists
+            or has a None value.
+        """
+        name = self._opt2dest.get(name, name)
+        try:
+            val = getattr(self.option, name)
+            if val is None and skip:
+                raise AttributeError(name)
+            return val
+        except AttributeError:
+            if default is not notset:
+                return default
+            if skip:
+                import pytest
+                pytest.skip("no %r option found" % (name,))
+            raise ValueError("no option named %r" % (name,))
+
+    def getvalue(self, name, path=None):
+        """ (deprecated, use getoption()) """
+        return self.getoption(name)
+
+    def getvalueorskip(self, name, path=None):
+        """ (deprecated, use getoption(skip=True)) """
+        return self.getoption(name, skip=True)
+
+
+def exists(path, ignore=EnvironmentError):
+    try:
+        return path.check()
+    except ignore:
+        return False
+
+
+def getcfg(args, warnfunc=None):
+    """
+    Search the list of arguments for a valid ini-file for pytest,
+    and return a tuple of (rootdir, inifile, cfg-dict).
+
+    note: warnfunc is an optional function used to warn
+        about ini-files that use deprecated features.
+        This parameter should be removed when pytest
+        adopts standard deprecation warnings (#1804).
+    """
+    from _pytest.deprecated import SETUP_CFG_PYTEST
+    inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"]
+    args = [x for x in args if not str(x).startswith("-")]
+    if not args:
+        args = [py.path.local()]
+    for arg in args:
+        arg = py.path.local(arg)
+        for base in arg.parts(reverse=True):
+            for inibasename in inibasenames:
+                p = base.join(inibasename)
+                if exists(p):
+                    iniconfig = py.iniconfig.IniConfig(p)
+                    if 'pytest' in iniconfig.sections:
+                        if inibasename == 'setup.cfg' and warnfunc:
+                            warnfunc('C1', SETUP_CFG_PYTEST)
+                        return base, p, iniconfig['pytest']
+                    if inibasename == 'setup.cfg' and 'tool:pytest' in iniconfig.sections:
+                        return base, p, iniconfig['tool:pytest']
+                    elif inibasename == "pytest.ini":
+                        # allowed to be empty
+                        return base, p, {}
+    return None, None, None
+
+
+def get_common_ancestor(paths):
+    common_ancestor = None
+    for path in paths:
+        if not path.exists():
+            continue
+        if common_ancestor is None:
+            common_ancestor = path
+        else:
+            if path.relto(common_ancestor) or path == common_ancestor:
+                continue
+            elif common_ancestor.relto(path):
+                common_ancestor = path
+            else:
+                shared = path.common(common_ancestor)
+                if shared is not None:
+                    common_ancestor = shared
+    if common_ancestor is None:
+        common_ancestor = py.path.local()
+    elif common_ancestor.isfile():
+        common_ancestor = common_ancestor.dirpath()
+    return common_ancestor
+
+
+def get_dirs_from_args(args):
+    def is_option(x):
+        return str(x).startswith('-')
+
+    def get_file_part_from_node_id(x):
+        return str(x).split('::')[0]
+
+    def get_dir_from_path(path):
+        if path.isdir():
+            return path
+        return py.path.local(path.dirname)
+
+    # These look like paths but may not exist
+    possible_paths = (
+        py.path.local(get_file_part_from_node_id(arg))
+        for arg in args
+        if not is_option(arg)
+    )
+
+    return [
+        get_dir_from_path(path)
+        for path in possible_paths
+        if path.exists()
+    ]
+
+
+def determine_setup(inifile, args, warnfunc=None):
+    dirs = get_dirs_from_args(args)
+    if inifile:
+        iniconfig = py.iniconfig.IniConfig(inifile)
+        try:
+            inicfg = iniconfig["pytest"]
+        except KeyError:
+            inicfg = None
+        rootdir = get_common_ancestor(dirs)
+    else:
+        ancestor = get_common_ancestor(dirs)
+        rootdir, inifile, inicfg = getcfg([ancestor], warnfunc=warnfunc)
+        if rootdir is None:
+            for rootdir in ancestor.parts(reverse=True):
+                if rootdir.join("setup.py").exists():
+                    break
+            else:
+                rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc)
+                if rootdir is None:
+                    rootdir = get_common_ancestor([py.path.local(), ancestor])
+                    is_fs_root = os.path.splitdrive(str(rootdir))[1] == '/'
+                    if is_fs_root:
+                        rootdir = ancestor
+    return rootdir, inifile, inicfg or {}
+
+
+def setns(obj, dic):
+    import pytest
+    for name, value in dic.items():
+        if isinstance(value, dict):
+            mod = getattr(obj, name, None)
+            if mod is None:
+                modname = "pytest.%s" % name
+                mod = types.ModuleType(modname)
+                sys.modules[modname] = mod
+                mod.__all__ = []
+                setattr(obj, name, mod)
+            obj.__all__.append(name)
+            setns(mod, value)
+        else:
+            setattr(obj, name, value)
+            obj.__all__.append(name)
+            # if obj != pytest:
+            #    pytest.__all__.append(name)
+            setattr(pytest, name, value)
+
+
+def create_terminal_writer(config, *args, **kwargs):
+    """Create a TerminalWriter instance configured according to the options
+    in the config object. Every code which requires a TerminalWriter object
+    and has access to a config object should use this function.
+    """
+    tw = py.io.TerminalWriter(*args, **kwargs)
+    if config.option.color == 'yes':
+        tw.hasmarkup = True
+    if config.option.color == 'no':
+        tw.hasmarkup = False
+    return tw
+
+
+def _strtobool(val):
+    """Convert a string representation of truth to true (1) or false (0).
+
+    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
+    are 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if
+    'val' is anything else.
+
+    .. note:: copied from distutils.util
+    """
+    val = val.lower()
+    if val in ('y', 'yes', 't', 'true', 'on', '1'):
+        return 1
+    elif val in ('n', 'no', 'f', 'false', 'off', '0'):
+        return 0
+    else:
+        raise ValueError("invalid truth value %r" % (val,))
diff --git a/tools/third_party/pytest/_pytest/debugging.py b/tools/third_party/pytest/_pytest/debugging.py
new file mode 100644
index 0000000..d7dca78
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/debugging.py
@@ -0,0 +1,123 @@
+""" interactive debugging with PDB, the Python Debugger. """
+from __future__ import absolute_import, division, print_function
+import pdb
+import sys
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group._addoption(
+        '--pdb', dest="usepdb", action="store_true",
+        help="start the interactive Python debugger on errors.")
+    group._addoption(
+        '--pdbcls', dest="usepdb_cls", metavar="modulename:classname",
+        help="start a custom interactive Python debugger on errors. "
+             "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb")
+
+
+def pytest_configure(config):
+    if config.getvalue("usepdb_cls"):
+        modname, classname = config.getvalue("usepdb_cls").split(":")
+        __import__(modname)
+        pdb_cls = getattr(sys.modules[modname], classname)
+    else:
+        pdb_cls = pdb.Pdb
+
+    if config.getvalue("usepdb"):
+        config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
+
+    old = (pdb.set_trace, pytestPDB._pluginmanager)
+
+    def fin():
+        pdb.set_trace, pytestPDB._pluginmanager = old
+        pytestPDB._config = None
+        pytestPDB._pdb_cls = pdb.Pdb
+
+    pdb.set_trace = pytestPDB.set_trace
+    pytestPDB._pluginmanager = config.pluginmanager
+    pytestPDB._config = config
+    pytestPDB._pdb_cls = pdb_cls
+    config._cleanup.append(fin)
+
+
+class pytestPDB:
+    """ Pseudo PDB that defers to the real pdb. """
+    _pluginmanager = None
+    _config = None
+    _pdb_cls = pdb.Pdb
+
+    @classmethod
+    def set_trace(cls):
+        """ invoke PDB set_trace debugging, dropping any IO capturing. """
+        import _pytest.config
+        frame = sys._getframe().f_back
+        if cls._pluginmanager is not None:
+            capman = cls._pluginmanager.getplugin("capturemanager")
+            if capman:
+                capman.suspend_global_capture(in_=True)
+            tw = _pytest.config.create_terminal_writer(cls._config)
+            tw.line()
+            tw.sep(">", "PDB set_trace (IO-capturing turned off)")
+            cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
+        cls._pdb_cls().set_trace(frame)
+
+
+class PdbInvoke:
+    def pytest_exception_interact(self, node, call, report):
+        capman = node.config.pluginmanager.getplugin("capturemanager")
+        if capman:
+            out, err = capman.suspend_global_capture(in_=True)
+            sys.stdout.write(out)
+            sys.stdout.write(err)
+        _enter_pdb(node, call.excinfo, report)
+
+    def pytest_internalerror(self, excrepr, excinfo):
+        for line in str(excrepr).split("\n"):
+            sys.stderr.write("INTERNALERROR> %s\n" % line)
+            sys.stderr.flush()
+        tb = _postmortem_traceback(excinfo)
+        post_mortem(tb)
+
+
+def _enter_pdb(node, excinfo, rep):
+    # XXX we re-use the TerminalReporter's terminalwriter
+    # because this seems to avoid some encoding related troubles
+    # for not completely clear reasons.
+    tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
+    tw.line()
+    tw.sep(">", "traceback")
+    rep.toterminal(tw)
+    tw.sep(">", "entering PDB")
+    tb = _postmortem_traceback(excinfo)
+    post_mortem(tb)
+    rep._pdbshown = True
+    return rep
+
+
+def _postmortem_traceback(excinfo):
+    # A doctest.UnexpectedException is not useful for post_mortem.
+    # Use the underlying exception instead:
+    from doctest import UnexpectedException
+    if isinstance(excinfo.value, UnexpectedException):
+        return excinfo.value.exc_info[2]
+    else:
+        return excinfo._excinfo[2]
+
+
+def _find_last_non_hidden_frame(stack):
+    i = max(0, len(stack) - 1)
+    while i and stack[i][0].f_locals.get("__tracebackhide__", False):
+        i -= 1
+    return i
+
+
+def post_mortem(t):
+    class Pdb(pytestPDB._pdb_cls):
+        def get_stack(self, f, t):
+            stack, i = pdb.Pdb.get_stack(self, f, t)
+            if f is None:
+                i = _find_last_non_hidden_frame(stack)
+            return stack, i
+    p = Pdb()
+    p.reset()
+    p.interaction(None, t)
diff --git a/tools/third_party/pytest/_pytest/deprecated.py b/tools/third_party/pytest/_pytest/deprecated.py
new file mode 100644
index 0000000..9c0fbec
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/deprecated.py
@@ -0,0 +1,52 @@
+"""
+This module contains deprecation messages and bits of code used elsewhere in the codebase
+that is planned to be removed in the next pytest release.
+
+Keeping it in a central location makes it easy to track what is deprecated and should
+be removed when the time comes.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+class RemovedInPytest4Warning(DeprecationWarning):
+    """warning class for features removed in pytest 4.0"""
+
+
+MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \
+    'pass a list of arguments instead.'
+
+YIELD_TESTS = 'yield tests are deprecated, and scheduled to be removed in pytest 4.0'
+
+FUNCARG_PREFIX = (
+    '{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
+    'and scheduled to be removed in pytest 4.0.  '
+    'Please remove the prefix and use the @pytest.fixture decorator instead.')
+
+SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead.'
+
+GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"
+
+RESULT_LOG = (
+    '--result-log is deprecated and scheduled for removal in pytest 4.0.\n'
+    'See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information.'
+)
+
+MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
+    "MarkInfo objects are deprecated as they contain the merged marks"
+)
+
+MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
+    "Applying marks directly to parameters is deprecated,"
+    " please use pytest.param(..., marks=...) instead.\n"
+    "For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
+)
+
+COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
+    "pycollector makeitem was removed "
+    "as it is an accidentially leaked internal api"
+)
+
+METAFUNC_ADD_CALL = (
+    "Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n"
+    "Please use Metafunc.parametrize instead."
+)
diff --git a/tools/third_party/pytest/_pytest/doctest.py b/tools/third_party/pytest/_pytest/doctest.py
new file mode 100644
index 0000000..bba90e5
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/doctest.py
@@ -0,0 +1,369 @@
+""" discover and run doctests in modules and test files."""
+from __future__ import absolute_import, division, print_function
+
+import traceback
+
+import pytest
+from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr
+from _pytest.fixtures import FixtureRequest
+
+
+DOCTEST_REPORT_CHOICE_NONE = 'none'
+DOCTEST_REPORT_CHOICE_CDIFF = 'cdiff'
+DOCTEST_REPORT_CHOICE_NDIFF = 'ndiff'
+DOCTEST_REPORT_CHOICE_UDIFF = 'udiff'
+DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = 'only_first_failure'
+
+DOCTEST_REPORT_CHOICES = (
+    DOCTEST_REPORT_CHOICE_NONE,
+    DOCTEST_REPORT_CHOICE_CDIFF,
+    DOCTEST_REPORT_CHOICE_NDIFF,
+    DOCTEST_REPORT_CHOICE_UDIFF,
+    DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
+)
+
+
+def pytest_addoption(parser):
+    parser.addini('doctest_optionflags', 'option flags for doctests',
+                  type="args", default=["ELLIPSIS"])
+    parser.addini("doctest_encoding", 'encoding used for doctest files', default="utf-8")
+    group = parser.getgroup("collect")
+    group.addoption("--doctest-modules",
+                    action="store_true", default=False,
+                    help="run doctests in all .py modules",
+                    dest="doctestmodules")
+    group.addoption("--doctest-report",
+                    type=str.lower, default="udiff",
+                    help="choose another output format for diffs on doctest failure",
+                    choices=DOCTEST_REPORT_CHOICES,
+                    dest="doctestreport")
+    group.addoption("--doctest-glob",
+                    action="append", default=[], metavar="pat",
+                    help="doctests file matching pattern, default: test*.txt",
+                    dest="doctestglob")
+    group.addoption("--doctest-ignore-import-errors",
+                    action="store_true", default=False,
+                    help="ignore doctest ImportErrors",
+                    dest="doctest_ignore_import_errors")
+
+
+def pytest_collect_file(path, parent):
+    config = parent.config
+    if path.ext == ".py":
+        if config.option.doctestmodules and not _is_setup_py(config, path, parent):
+            return DoctestModule(path, parent)
+    elif _is_doctest(config, path, parent):
+        return DoctestTextfile(path, parent)
+
+
+def _is_setup_py(config, path, parent):
+    if path.basename != "setup.py":
+        return False
+    contents = path.read()
+    return 'setuptools' in contents or 'distutils' in contents
+
+
+def _is_doctest(config, path, parent):
+    if path.ext in ('.txt', '.rst') and parent.session.isinitpath(path):
+        return True
+    globs = config.getoption("doctestglob") or ['test*.txt']
+    for glob in globs:
+        if path.check(fnmatch=glob):
+            return True
+    return False
+
+
+class ReprFailDoctest(TerminalRepr):
+
+    def __init__(self, reprlocation, lines):
+        self.reprlocation = reprlocation
+        self.lines = lines
+
+    def toterminal(self, tw):
+        for line in self.lines:
+            tw.line(line)
+        self.reprlocation.toterminal(tw)
+
+
+class DoctestItem(pytest.Item):
+    def __init__(self, name, parent, runner=None, dtest=None):
+        super(DoctestItem, self).__init__(name, parent)
+        self.runner = runner
+        self.dtest = dtest
+        self.obj = None
+        self.fixture_request = None
+
+    def setup(self):
+        if self.dtest is not None:
+            self.fixture_request = _setup_fixtures(self)
+            globs = dict(getfixture=self.fixture_request.getfixturevalue)
+            for name, value in self.fixture_request.getfixturevalue('doctest_namespace').items():
+                globs[name] = value
+            self.dtest.globs.update(globs)
+
+    def runtest(self):
+        _check_all_skipped(self.dtest)
+        self.runner.run(self.dtest)
+
+    def repr_failure(self, excinfo):
+        import doctest
+        if excinfo.errisinstance((doctest.DocTestFailure,
+                                  doctest.UnexpectedException)):
+            doctestfailure = excinfo.value
+            example = doctestfailure.example
+            test = doctestfailure.test
+            filename = test.filename
+            if test.lineno is None:
+                lineno = None
+            else:
+                lineno = test.lineno + example.lineno + 1
+            message = excinfo.type.__name__
+            reprlocation = ReprFileLocation(filename, lineno, message)
+            checker = _get_checker()
+            report_choice = _get_report_choice(self.config.getoption("doctestreport"))
+            if lineno is not None:
+                lines = doctestfailure.test.docstring.splitlines(False)
+                # add line numbers to the left of the error message
+                lines = ["%03d %s" % (i + test.lineno + 1, x)
+                         for (i, x) in enumerate(lines)]
+                # trim docstring error lines to 10
+                lines = lines[max(example.lineno - 9, 0):example.lineno + 1]
+            else:
+                lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
+                indent = '>>>'
+                for line in example.source.splitlines():
+                    lines.append('??? %s %s' % (indent, line))
+                    indent = '...'
+            if excinfo.errisinstance(doctest.DocTestFailure):
+                lines += checker.output_difference(example,
+                                                   doctestfailure.got, report_choice).split("\n")
+            else:
+                inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
+                lines += ["UNEXPECTED EXCEPTION: %s" %
+                          repr(inner_excinfo.value)]
+                lines += traceback.format_exception(*excinfo.value.exc_info)
+            return ReprFailDoctest(reprlocation, lines)
+        else:
+            return super(DoctestItem, self).repr_failure(excinfo)
+
+    def reportinfo(self):
+        return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
+
+
+def _get_flag_lookup():
+    import doctest
+    return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
+                DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
+                NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
+                ELLIPSIS=doctest.ELLIPSIS,
+                IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
+                COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
+                ALLOW_UNICODE=_get_allow_unicode_flag(),
+                ALLOW_BYTES=_get_allow_bytes_flag(),
+                )
+
+
+def get_optionflags(parent):
+    optionflags_str = parent.config.getini("doctest_optionflags")
+    flag_lookup_table = _get_flag_lookup()
+    flag_acc = 0
+    for flag in optionflags_str:
+        flag_acc |= flag_lookup_table[flag]
+    return flag_acc
+
+
+class DoctestTextfile(pytest.Module):
+    obj = None
+
+    def collect(self):
+        import doctest
+
+        # inspired by doctest.testfile; ideally we would use it directly,
+        # but it doesn't support passing a custom checker
+        encoding = self.config.getini("doctest_encoding")
+        text = self.fspath.read_text(encoding)
+        filename = str(self.fspath)
+        name = self.fspath.basename
+        globs = {'__name__': '__main__'}
+
+        optionflags = get_optionflags(self)
+        runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
+                                     checker=_get_checker())
+        _fix_spoof_python2(runner, encoding)
+
+        parser = doctest.DocTestParser()
+        test = parser.get_doctest(text, globs, name, filename, 0)
+        if test.examples:
+            yield DoctestItem(test.name, self, runner, test)
+
+
+def _check_all_skipped(test):
+    """raises pytest.skip() if all examples in the given DocTest have the SKIP
+    option set.
+    """
+    import doctest
+    all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
+    if all_skipped:
+        pytest.skip('all tests skipped by +SKIP option')
+
+
+class DoctestModule(pytest.Module):
+    def collect(self):
+        import doctest
+        if self.fspath.basename == "conftest.py":
+            module = self.config.pluginmanager._importconftest(self.fspath)
+        else:
+            try:
+                module = self.fspath.pyimport()
+            except ImportError:
+                if self.config.getvalue('doctest_ignore_import_errors'):
+                    pytest.skip('unable to import module %r' % self.fspath)
+                else:
+                    raise
+        # uses internal doctest module parsing mechanism
+        finder = doctest.DocTestFinder()
+        optionflags = get_optionflags(self)
+        runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
+                                     checker=_get_checker())
+
+        for test in finder.find(module, module.__name__):
+            if test.examples:  # skip empty doctests
+                yield DoctestItem(test.name, self, runner, test)
+
+
+def _setup_fixtures(doctest_item):
+    """
+    Used by DoctestTextfile and DoctestItem to setup fixture information.
+    """
+    def func():
+        pass
+
+    doctest_item.funcargs = {}
+    fm = doctest_item.session._fixturemanager
+    doctest_item._fixtureinfo = fm.getfixtureinfo(node=doctest_item, func=func,
+                                                  cls=None, funcargs=False)
+    fixture_request = FixtureRequest(doctest_item)
+    fixture_request._fillfixtures()
+    return fixture_request
+
+
+def _get_checker():
+    """
+    Returns a doctest.OutputChecker subclass that takes in account the
+    ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
+    to strip b'' prefixes.
+    Useful when the same doctest should run in Python 2 and Python 3.
+
+    An inner class is used to avoid importing "doctest" at the module
+    level.
+    """
+    if hasattr(_get_checker, 'LiteralsOutputChecker'):
+        return _get_checker.LiteralsOutputChecker()
+
+    import doctest
+    import re
+
+    class LiteralsOutputChecker(doctest.OutputChecker):
+        """
+        Copied from doctest_nose_plugin.py from the nltk project:
+            https://github.com/nltk/nltk
+
+        Further extended to also support byte literals.
+        """
+
+        _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
+        _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
+
+        def check_output(self, want, got, optionflags):
+            res = doctest.OutputChecker.check_output(self, want, got,
+                                                     optionflags)
+            if res:
+                return True
+
+            allow_unicode = optionflags & _get_allow_unicode_flag()
+            allow_bytes = optionflags & _get_allow_bytes_flag()
+            if not allow_unicode and not allow_bytes:
+                return False
+
+            else:  # pragma: no cover
+                def remove_prefixes(regex, txt):
+                    return re.sub(regex, r'\1\2', txt)
+
+                if allow_unicode:
+                    want = remove_prefixes(self._unicode_literal_re, want)
+                    got = remove_prefixes(self._unicode_literal_re, got)
+                if allow_bytes:
+                    want = remove_prefixes(self._bytes_literal_re, want)
+                    got = remove_prefixes(self._bytes_literal_re, got)
+                res = doctest.OutputChecker.check_output(self, want, got,
+                                                         optionflags)
+                return res
+
+    _get_checker.LiteralsOutputChecker = LiteralsOutputChecker
+    return _get_checker.LiteralsOutputChecker()
+
+
+def _get_allow_unicode_flag():
+    """
+    Registers and returns the ALLOW_UNICODE flag.
+    """
+    import doctest
+    return doctest.register_optionflag('ALLOW_UNICODE')
+
+
+def _get_allow_bytes_flag():
+    """
+    Registers and returns the ALLOW_BYTES flag.
+    """
+    import doctest
+    return doctest.register_optionflag('ALLOW_BYTES')
+
+
+def _get_report_choice(key):
+    """
+    This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid
+    importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests.
+    """
+    import doctest
+
+    return {
+        DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF,
+        DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF,
+        DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF,
+        DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE,
+        DOCTEST_REPORT_CHOICE_NONE: 0,
+    }[key]
+
+
+def _fix_spoof_python2(runner, encoding):
+    """
+    Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
+    should patch only doctests for text files because they don't have a way to declare their
+    encoding. Doctests in docstrings from Python modules don't have the same problem given that
+    Python already decoded the strings.
+
+    This fixes the problem related in issue #2434.
+    """
+    from _pytest.compat import _PY2
+    if not _PY2:
+        return
+
+    from doctest import _SpoofOut
+
+    class UnicodeSpoof(_SpoofOut):
+
+        def getvalue(self):
+            result = _SpoofOut.getvalue(self)
+            if encoding:
+                result = result.decode(encoding)
+            return result
+
+    runner._fakeout = UnicodeSpoof()
+
+
+@pytest.fixture(scope='session')
+def doctest_namespace():
+    """
+    Inject names into the doctest namespace.
+    """
+    return dict()
diff --git a/tools/third_party/pytest/_pytest/fixtures.py b/tools/third_party/pytest/_pytest/fixtures.py
new file mode 100644
index 0000000..e09ffad
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/fixtures.py
@@ -0,0 +1,1147 @@
+from __future__ import absolute_import, division, print_function
+
+import functools
+import inspect
+import sys
+import warnings
+from collections import OrderedDict
+
+import attr
+import py
+from py._code.code import FormattedExcinfo
+
+import _pytest
+from _pytest import nodes
+from _pytest._code.code import TerminalRepr
+from _pytest.compat import (
+    NOTSET, exc_clear, _format_args,
+    getfslineno, get_real_func,
+    is_generator, isclass, getimfunc,
+    getlocation, getfuncargnames,
+    safe_getattr,
+    FuncargnamesCompatAttr,
+)
+from _pytest.outcomes import fail, TEST_OUTCOME
+
+
+def pytest_sessionstart(session):
+    import _pytest.python
+
+    scopename2class.update({
+        'class': _pytest.python.Class,
+        'module': _pytest.python.Module,
+        'function': _pytest.main.Item,
+        'session': _pytest.main.Session,
+    })
+    session._fixturemanager = FixtureManager(session)
+
+
+scopename2class = {}
+
+
+scope2props = dict(session=())
+scope2props["module"] = ("fspath", "module")
+scope2props["class"] = scope2props["module"] + ("cls",)
+scope2props["instance"] = scope2props["class"] + ("instance", )
+scope2props["function"] = scope2props["instance"] + ("function", "keywords")
+
+
+def scopeproperty(name=None, doc=None):
+    def decoratescope(func):
+        scopename = name or func.__name__
+
+        def provide(self):
+            if func.__name__ in scope2props[self.scope]:
+                return func(self)
+            raise AttributeError("%s not available in %s-scoped context" % (
+                scopename, self.scope))
+
+        return property(provide, None, None, func.__doc__)
+    return decoratescope
+
+
+def get_scope_node(node, scope):
+    cls = scopename2class.get(scope)
+    if cls is None:
+        raise ValueError("unknown scope")
+    return node.getparent(cls)
+
+
+def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
+    # this function will transform all collected calls to a functions
+    # if they use direct funcargs (i.e. direct parametrization)
+    # because we want later test execution to be able to rely on
+    # an existing FixtureDef structure for all arguments.
+    # XXX we can probably avoid this algorithm  if we modify CallSpec2
+    # to directly care for creating the fixturedefs within its methods.
+    if not metafunc._calls[0].funcargs:
+        return  # this function call does not have direct parametrization
+    # collect funcargs of all callspecs into a list of values
+    arg2params = {}
+    arg2scope = {}
+    for callspec in metafunc._calls:
+        for argname, argvalue in callspec.funcargs.items():
+            assert argname not in callspec.params
+            callspec.params[argname] = argvalue
+            arg2params_list = arg2params.setdefault(argname, [])
+            callspec.indices[argname] = len(arg2params_list)
+            arg2params_list.append(argvalue)
+            if argname not in arg2scope:
+                scopenum = callspec._arg2scopenum.get(argname,
+                                                      scopenum_function)
+                arg2scope[argname] = scopes[scopenum]
+        callspec.funcargs.clear()
+
+    # register artificial FixtureDef's so that later at test execution
+    # time we can rely on a proper FixtureDef to exist for fixture setup.
+    arg2fixturedefs = metafunc._arg2fixturedefs
+    for argname, valuelist in arg2params.items():
+        # if we have a scope that is higher than function we need
+        # to make sure we only ever create an according fixturedef on
+        # a per-scope basis. We thus store and cache the fixturedef on the
+        # node related to the scope.
+        scope = arg2scope[argname]
+        node = None
+        if scope != "function":
+            node = get_scope_node(collector, scope)
+            if node is None:
+                assert scope == "class" and isinstance(collector, _pytest.python.Module)
+                # use module-level collector for class-scope (for now)
+                node = collector
+        if node and argname in node._name2pseudofixturedef:
+            arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
+        else:
+            fixturedef = FixtureDef(fixturemanager, '', argname,
+                                    get_direct_param_fixture_func,
+                                    arg2scope[argname],
+                                    valuelist, False, False)
+            arg2fixturedefs[argname] = [fixturedef]
+            if node is not None:
+                node._name2pseudofixturedef[argname] = fixturedef
+
+
+def getfixturemarker(obj):
+    """ return fixturemarker or None if it doesn't exist or raised
+    exceptions."""
+    try:
+        return getattr(obj, "_pytestfixturefunction", None)
+    except TEST_OUTCOME:
+        # some objects raise errors like request (from flask import request)
+        # we don't expect them to be fixture functions
+        return None
+
+
+def get_parametrized_fixture_keys(item, scopenum):
+    """ return list of keys for all parametrized arguments which match
+    the specified scope. """
+    assert scopenum < scopenum_function  # function
+    try:
+        cs = item.callspec
+    except AttributeError:
+        pass
+    else:
+        # cs.indices.items() is random order of argnames.  Need to
+        # sort this so that different calls to
+        # get_parametrized_fixture_keys will be deterministic.
+        for argname, param_index in sorted(cs.indices.items()):
+            if cs._arg2scopenum[argname] != scopenum:
+                continue
+            if scopenum == 0:    # session
+                key = (argname, param_index)
+            elif scopenum == 1:  # module
+                key = (argname, param_index, item.fspath)
+            elif scopenum == 2:  # class
+                key = (argname, param_index, item.fspath, item.cls)
+            yield key
+
+
+# algorithm for sorting on a per-parametrized resource setup basis
+# it is called for scopenum==0 (session) first and performs sorting
+# down to the lower scopes such as to minimize number of "high scope"
+# setups and teardowns
+
+def reorder_items(items):
+    argkeys_cache = {}
+    for scopenum in range(0, scopenum_function):
+        argkeys_cache[scopenum] = d = {}
+        for item in items:
+            keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
+            if keys:
+                d[item] = keys
+    return reorder_items_atscope(items, set(), argkeys_cache, 0)
+
+
+def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
+    if scopenum >= scopenum_function or len(items) < 3:
+        return items
+    items_done = []
+    while 1:
+        items_before, items_same, items_other, newignore = \
+            slice_items(items, ignore, argkeys_cache[scopenum])
+        items_before = reorder_items_atscope(
+            items_before, ignore, argkeys_cache, scopenum + 1)
+        if items_same is None:
+            # nothing to reorder in this scope
+            assert items_other is None
+            return items_done + items_before
+        items_done.extend(items_before)
+        items = items_same + items_other
+        ignore = newignore
+
+
+def slice_items(items, ignore, scoped_argkeys_cache):
+    # we pick the first item which uses a fixture instance in the
+    # requested scope and which we haven't seen yet.  We slice the input
+    # items list into a list of items_nomatch, items_same and
+    # items_other
+    if scoped_argkeys_cache:  # do we need to do work at all?
+        it = iter(items)
+        # first find a slicing key
+        for i, item in enumerate(it):
+            argkeys = scoped_argkeys_cache.get(item)
+            if argkeys is not None:
+                newargkeys = OrderedDict.fromkeys(k for k in argkeys if k not in ignore)
+                if newargkeys:  # found a slicing key
+                    slicing_argkey, _ = newargkeys.popitem()
+                    items_before = items[:i]
+                    items_same = [item]
+                    items_other = []
+                    # now slice the remainder of the list
+                    for item in it:
+                        argkeys = scoped_argkeys_cache.get(item)
+                        if argkeys and slicing_argkey in argkeys and \
+                                slicing_argkey not in ignore:
+                            items_same.append(item)
+                        else:
+                            items_other.append(item)
+                    newignore = ignore.copy()
+                    newignore.add(slicing_argkey)
+                    return (items_before, items_same, items_other, newignore)
+    return items, None, None, None
+
+
+def fillfixtures(function):
+    """ fill missing funcargs for a test function. """
+    try:
+        request = function._request
+    except AttributeError:
+        # XXX this special code path is only expected to execute
+        # with the oejskit plugin.  It uses classes with funcargs
+        # and we thus have to work a bit to allow this.
+        fm = function.session._fixturemanager
+        fi = fm.getfixtureinfo(function.parent, function.obj, None)
+        function._fixtureinfo = fi
+        request = function._request = FixtureRequest(function)
+        request._fillfixtures()
+        # prune out funcargs for jstests
+        newfuncargs = {}
+        for name in fi.argnames:
+            newfuncargs[name] = function.funcargs[name]
+        function.funcargs = newfuncargs
+    else:
+        request._fillfixtures()
+
+
+def get_direct_param_fixture_func(request):
+    return request.param
+
+
+class FuncFixtureInfo:
+    def __init__(self, argnames, names_closure, name2fixturedefs):
+        self.argnames = argnames
+        self.names_closure = names_closure
+        self.name2fixturedefs = name2fixturedefs
+
+
+class FixtureRequest(FuncargnamesCompatAttr):
+    """ A request for a fixture from a test or fixture function.
+
+    A request object gives access to the requesting test context
+    and has an optional ``param`` attribute in case
+    the fixture is parametrized indirectly.
+    """
+
+    def __init__(self, pyfuncitem):
+        self._pyfuncitem = pyfuncitem
+        #: fixture for which this request is being performed
+        self.fixturename = None
+        #: Scope string, one of "function", "class", "module", "session"
+        self.scope = "function"
+        self._fixture_values = {}  # argname -> fixture value
+        self._fixture_defs = {}  # argname -> FixtureDef
+        fixtureinfo = pyfuncitem._fixtureinfo
+        self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
+        self._arg2index = {}
+        self._fixturemanager = pyfuncitem.session._fixturemanager
+
+    @property
+    def fixturenames(self):
+        # backward incompatible note: now a readonly property
+        return list(self._pyfuncitem._fixtureinfo.names_closure)
+
+    @property
+    def node(self):
+        """ underlying collection node (depends on current request scope)"""
+        return self._getscopeitem(self.scope)
+
+    def _getnextfixturedef(self, argname):
+        fixturedefs = self._arg2fixturedefs.get(argname, None)
+        if fixturedefs is None:
+            # we arrive here because of a  a dynamic call to
+            # getfixturevalue(argname) usage which was naturally
+            # not known at parsing/collection time
+            parentid = self._pyfuncitem.parent.nodeid
+            fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
+            self._arg2fixturedefs[argname] = fixturedefs
+        # fixturedefs list is immutable so we maintain a decreasing index
+        index = self._arg2index.get(argname, 0) - 1
+        if fixturedefs is None or (-index > len(fixturedefs)):
+            raise FixtureLookupError(argname, self)
+        self._arg2index[argname] = index
+        return fixturedefs[index]
+
+    @property
+    def config(self):
+        """ the pytest config object associated with this request. """
+        return self._pyfuncitem.config
+
+    @scopeproperty()
+    def function(self):
+        """ test function object if the request has a per-function scope. """
+        return self._pyfuncitem.obj
+
+    @scopeproperty("class")
+    def cls(self):
+        """ class (can be None) where the test function was collected. """
+        clscol = self._pyfuncitem.getparent(_pytest.python.Class)
+        if clscol:
+            return clscol.obj
+
+    @property
+    def instance(self):
+        """ instance (can be None) on which test function was collected. """
+        # unittest support hack, see _pytest.unittest.TestCaseFunction
+        try:
+            return self._pyfuncitem._testcase
+        except AttributeError:
+            function = getattr(self, "function", None)
+            if function is not None:
+                return py.builtin._getimself(function)
+
+    @scopeproperty()
+    def module(self):
+        """ python module object where the test function was collected. """
+        return self._pyfuncitem.getparent(_pytest.python.Module).obj
+
+    @scopeproperty()
+    def fspath(self):
+        """ the file system path of the test module which collected this test. """
+        return self._pyfuncitem.fspath
+
+    @property
+    def keywords(self):
+        """ keywords/markers dictionary for the underlying node. """
+        return self.node.keywords
+
+    @property
+    def session(self):
+        """ pytest session object. """
+        return self._pyfuncitem.session
+
+    def addfinalizer(self, finalizer):
+        """ add finalizer/teardown function to be called after the
+        last test within the requesting test context finished
+        execution. """
+        # XXX usually this method is shadowed by fixturedef specific ones
+        self._addfinalizer(finalizer, scope=self.scope)
+
+    def _addfinalizer(self, finalizer, scope):
+        colitem = self._getscopeitem(scope)
+        self._pyfuncitem.session._setupstate.addfinalizer(
+            finalizer=finalizer, colitem=colitem)
+
+    def applymarker(self, marker):
+        """ Apply a marker to a single test function invocation.
+        This method is useful if you don't want to have a keyword/marker
+        on all function invocations.
+
+        :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
+            created by a call to ``pytest.mark.NAME(...)``.
+        """
+        try:
+            self.node.keywords[marker.markname] = marker
+        except AttributeError:
+            raise ValueError(marker)
+
+    def raiseerror(self, msg):
+        """ raise a FixtureLookupError with the given message. """
+        raise self._fixturemanager.FixtureLookupError(None, self, msg)
+
+    def _fillfixtures(self):
+        item = self._pyfuncitem
+        fixturenames = getattr(item, "fixturenames", self.fixturenames)
+        for argname in fixturenames:
+            if argname not in item.funcargs:
+                item.funcargs[argname] = self.getfixturevalue(argname)
+
+    def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
+        """ (deprecated) Return a testing resource managed by ``setup`` &
+        ``teardown`` calls.  ``scope`` and ``extrakey`` determine when the
+        ``teardown`` function will be called so that subsequent calls to
+        ``setup`` would recreate the resource.  With pytest-2.3 you often
+        do not need ``cached_setup()`` as you can directly declare a scope
+        on a fixture function and register a finalizer through
+        ``request.addfinalizer()``.
+
+        :arg teardown: function receiving a previously setup resource.
+        :arg setup: a no-argument function creating a resource.
+        :arg scope: a string value out of ``function``, ``class``, ``module``
+            or ``session`` indicating the caching lifecycle of the resource.
+        :arg extrakey: added to internal caching key of (funcargname, scope).
+        """
+        if not hasattr(self.config, '_setupcache'):
+            self.config._setupcache = {}  # XXX weakref?
+        cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
+        cache = self.config._setupcache
+        try:
+            val = cache[cachekey]
+        except KeyError:
+            self._check_scope(self.fixturename, self.scope, scope)
+            val = setup()
+            cache[cachekey] = val
+            if teardown is not None:
+                def finalizer():
+                    del cache[cachekey]
+                    teardown(val)
+                self._addfinalizer(finalizer, scope=scope)
+        return val
+
+    def getfixturevalue(self, argname):
+        """ Dynamically run a named fixture function.
+
+        Declaring fixtures via function argument is recommended where possible.
+        But if you can only decide whether to use another fixture at test
+        setup time, you may use this function to retrieve it inside a fixture
+        or test function body.
+        """
+        return self._get_active_fixturedef(argname).cached_result[0]
+
+    def getfuncargvalue(self, argname):
+        """ Deprecated, use getfixturevalue. """
+        from _pytest import deprecated
+        warnings.warn(
+            deprecated.GETFUNCARGVALUE,
+            DeprecationWarning,
+            stacklevel=2)
+        return self.getfixturevalue(argname)
+
+    def _get_active_fixturedef(self, argname):
+        try:
+            return self._fixture_defs[argname]
+        except KeyError:
+            try:
+                fixturedef = self._getnextfixturedef(argname)
+            except FixtureLookupError:
+                if argname == "request":
+                    class PseudoFixtureDef:
+                        cached_result = (self, [0], None)
+                        scope = "function"
+                    return PseudoFixtureDef
+                raise
+        # remove indent to prevent the python3 exception
+        # from leaking into the call
+        result = self._getfixturevalue(fixturedef)
+        self._fixture_values[argname] = result
+        self._fixture_defs[argname] = fixturedef
+        return fixturedef
+
+    def _get_fixturestack(self):
+        current = self
+        values = []
+        while 1:
+            fixturedef = getattr(current, "_fixturedef", None)
+            if fixturedef is None:
+                values.reverse()
+                return values
+            values.append(fixturedef)
+            current = current._parent_request
+
+    def _getfixturevalue(self, fixturedef):
+        # prepare a subrequest object before calling fixture function
+        # (latter managed by fixturedef)
+        argname = fixturedef.argname
+        funcitem = self._pyfuncitem
+        scope = fixturedef.scope
+        try:
+            param = funcitem.callspec.getparam(argname)
+        except (AttributeError, ValueError):
+            param = NOTSET
+            param_index = 0
+            if fixturedef.params is not None:
+                frame = inspect.stack()[3]
+                frameinfo = inspect.getframeinfo(frame[0])
+                source_path = frameinfo.filename
+                source_lineno = frameinfo.lineno
+                source_path = py.path.local(source_path)
+                if source_path.relto(funcitem.config.rootdir):
+                    source_path = source_path.relto(funcitem.config.rootdir)
+                msg = (
+                    "The requested fixture has no parameter defined for the "
+                    "current test.\n\nRequested fixture '{0}' defined in:\n{1}"
+                    "\n\nRequested here:\n{2}:{3}".format(
+                        fixturedef.argname,
+                        getlocation(fixturedef.func, funcitem.config.rootdir),
+                        source_path,
+                        source_lineno,
+                    )
+                )
+                fail(msg)
+        else:
+            # indices might not be set if old-style metafunc.addcall() was used
+            param_index = funcitem.callspec.indices.get(argname, 0)
+            # if a parametrize invocation set a scope it will override
+            # the static scope defined with the fixture function
+            paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
+            if paramscopenum is not None:
+                scope = scopes[paramscopenum]
+
+        subrequest = SubRequest(self, scope, param, param_index, fixturedef)
+
+        # check if a higher-level scoped fixture accesses a lower level one
+        subrequest._check_scope(argname, self.scope, scope)
+
+        # clear sys.exc_info before invoking the fixture (python bug?)
+        # if its not explicitly cleared it will leak into the call
+        exc_clear()
+        try:
+            # call the fixture function
+            val = fixturedef.execute(request=subrequest)
+        finally:
+            # if fixture function failed it might have registered finalizers
+            self.session._setupstate.addfinalizer(functools.partial(fixturedef.finish, request=subrequest),
+                                                  subrequest.node)
+        return val
+
+    def _check_scope(self, argname, invoking_scope, requested_scope):
+        if argname == "request":
+            return
+        if scopemismatch(invoking_scope, requested_scope):
+            # try to report something helpful
+            lines = self._factorytraceback()
+            fail("ScopeMismatch: You tried to access the %r scoped "
+                 "fixture %r with a %r scoped request object, "
+                 "involved factories\n%s" % (
+                     (requested_scope, argname, invoking_scope, "\n".join(lines))),
+                 pytrace=False)
+
+    def _factorytraceback(self):
+        lines = []
+        for fixturedef in self._get_fixturestack():
+            factory = fixturedef.func
+            fs, lineno = getfslineno(factory)
+            p = self._pyfuncitem.session.fspath.bestrelpath(fs)
+            args = _format_args(factory)
+            lines.append("%s:%d:  def %s%s" % (
+                p, lineno, factory.__name__, args))
+        return lines
+
+    def _getscopeitem(self, scope):
+        if scope == "function":
+            # this might also be a non-function Item despite its attribute name
+            return self._pyfuncitem
+        node = get_scope_node(self._pyfuncitem, scope)
+        if node is None and scope == "class":
+            # fallback to function item itself
+            node = self._pyfuncitem
+        assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(scope, self._pyfuncitem)
+        return node
+
+    def __repr__(self):
+        return "<FixtureRequest for %r>" % (self.node)
+
+
+class SubRequest(FixtureRequest):
+    """ a sub request for handling getting a fixture from a
+    test function/fixture. """
+
+    def __init__(self, request, scope, param, param_index, fixturedef):
+        self._parent_request = request
+        self.fixturename = fixturedef.argname
+        if param is not NOTSET:
+            self.param = param
+        self.param_index = param_index
+        self.scope = scope
+        self._fixturedef = fixturedef
+        self._pyfuncitem = request._pyfuncitem
+        self._fixture_values = request._fixture_values
+        self._fixture_defs = request._fixture_defs
+        self._arg2fixturedefs = request._arg2fixturedefs
+        self._arg2index = request._arg2index
+        self._fixturemanager = request._fixturemanager
+
+    def __repr__(self):
+        return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
+
+    def addfinalizer(self, finalizer):
+        self._fixturedef.addfinalizer(finalizer)
+
+
+class ScopeMismatchError(Exception):
+    """ A fixture function tries to use a different fixture function which
+    which has a lower scope (e.g. a Session one calls a function one)
+    """
+
+
+scopes = "session module class function".split()
+scopenum_function = scopes.index("function")
+
+
+def scopemismatch(currentscope, newscope):
+    return scopes.index(newscope) > scopes.index(currentscope)
+
+
+def scope2index(scope, descr, where=None):
+    """Look up the index of ``scope`` and raise a descriptive value error
+    if not defined.
+    """
+    try:
+        return scopes.index(scope)
+    except ValueError:
+        raise ValueError(
+            "{0} {1}has an unsupported scope value '{2}'".format(
+                descr, 'from {0} '.format(where) if where else '',
+                scope)
+        )
+
+
+class FixtureLookupError(LookupError):
+    """ could not return a requested Fixture (missing or invalid). """
+
+    def __init__(self, argname, request, msg=None):
+        self.argname = argname
+        self.request = request
+        self.fixturestack = request._get_fixturestack()
+        self.msg = msg
+
+    def formatrepr(self):
+        tblines = []
+        addline = tblines.append
+        stack = [self.request._pyfuncitem.obj]
+        stack.extend(map(lambda x: x.func, self.fixturestack))
+        msg = self.msg
+        if msg is not None:
+            # the last fixture raise an error, let's present
+            # it at the requesting side
+            stack = stack[:-1]
+        for function in stack:
+            fspath, lineno = getfslineno(function)
+            try:
+                lines, _ = inspect.getsourcelines(get_real_func(function))
+            except (IOError, IndexError, TypeError):
+                error_msg = "file %s, line %s: source code not available"
+                addline(error_msg % (fspath, lineno + 1))
+            else:
+                addline("file %s, line %s" % (fspath, lineno + 1))
+                for i, line in enumerate(lines):
+                    line = line.rstrip()
+                    addline("  " + line)
+                    if line.lstrip().startswith('def'):
+                        break
+
+        if msg is None:
+            fm = self.request._fixturemanager
+            available = []
+            parentid = self.request._pyfuncitem.parent.nodeid
+            for name, fixturedefs in fm._arg2fixturedefs.items():
+                faclist = list(fm._matchfactories(fixturedefs, parentid))
+                if faclist and name not in available:
+                    available.append(name)
+            msg = "fixture %r not found" % (self.argname,)
+            msg += "\n available fixtures: %s" % (", ".join(sorted(available)),)
+            msg += "\n use 'pytest --fixtures [testpath]' for help on them."
+
+        return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
+
+
+class FixtureLookupErrorRepr(TerminalRepr):
+    def __init__(self, filename, firstlineno, tblines, errorstring, argname):
+        self.tblines = tblines
+        self.errorstring = errorstring
+        self.filename = filename
+        self.firstlineno = firstlineno
+        self.argname = argname
+
+    def toterminal(self, tw):
+        # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
+        for tbline in self.tblines:
+            tw.line(tbline.rstrip())
+        lines = self.errorstring.split("\n")
+        if lines:
+            tw.line('{0}       {1}'.format(FormattedExcinfo.fail_marker,
+                                           lines[0].strip()), red=True)
+            for line in lines[1:]:
+                tw.line('{0}       {1}'.format(FormattedExcinfo.flow_marker,
+                                               line.strip()), red=True)
+        tw.line()
+        tw.line("%s:%d" % (self.filename, self.firstlineno + 1))
+
+
+def fail_fixturefunc(fixturefunc, msg):
+    fs, lineno = getfslineno(fixturefunc)
+    location = "%s:%s" % (fs, lineno + 1)
+    source = _pytest._code.Source(fixturefunc)
+    fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
+         pytrace=False)
+
+
+def call_fixture_func(fixturefunc, request, kwargs):
+    yieldctx = is_generator(fixturefunc)
+    if yieldctx:
+        it = fixturefunc(**kwargs)
+        res = next(it)
+
+        def teardown():
+            try:
+                next(it)
+            except StopIteration:
+                pass
+            else:
+                fail_fixturefunc(fixturefunc,
+                                 "yield_fixture function has more than one 'yield'")
+
+        request.addfinalizer(teardown)
+    else:
+        res = fixturefunc(**kwargs)
+    return res
+
+
+class FixtureDef:
+    """ A container for a factory definition. """
+
+    def __init__(self, fixturemanager, baseid, argname, func, scope, params,
+                 unittest=False, ids=None):
+        self._fixturemanager = fixturemanager
+        self.baseid = baseid or ''
+        self.has_location = baseid is not None
+        self.func = func
+        self.argname = argname
+        self.scope = scope
+        self.scopenum = scope2index(
+            scope or "function",
+            descr='fixture {0}'.format(func.__name__),
+            where=baseid
+        )
+        self.params = params
+        self.argnames = getfuncargnames(func, is_method=unittest)
+        self.unittest = unittest
+        self.ids = ids
+        self._finalizers = []
+
+    def addfinalizer(self, finalizer):
+        self._finalizers.append(finalizer)
+
+    def finish(self, request):
+        exceptions = []
+        try:
+            while self._finalizers:
+                try:
+                    func = self._finalizers.pop()
+                    func()
+                except:  # noqa
+                    exceptions.append(sys.exc_info())
+            if exceptions:
+                e = exceptions[0]
+                del exceptions  # ensure we don't keep all frames alive because of the traceback
+                py.builtin._reraise(*e)
+
+        finally:
+            hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
+            hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
+            # even if finalization fails, we invalidate
+            # the cached fixture value and remove
+            # all finalizers because they may be bound methods which will
+            # keep instances alive
+            if hasattr(self, "cached_result"):
+                del self.cached_result
+            self._finalizers = []
+
+    def execute(self, request):
+        # get required arguments and register our own finish()
+        # with their finalization
+        for argname in self.argnames:
+            fixturedef = request._get_active_fixturedef(argname)
+            if argname != "request":
+                fixturedef.addfinalizer(functools.partial(self.finish, request=request))
+
+        my_cache_key = request.param_index
+        cached_result = getattr(self, "cached_result", None)
+        if cached_result is not None:
+            result, cache_key, err = cached_result
+            if my_cache_key == cache_key:
+                if err is not None:
+                    py.builtin._reraise(*err)
+                else:
+                    return result
+            # we have a previous but differently parametrized fixture instance
+            # so we need to tear it down before creating a new one
+            self.finish(request)
+            assert not hasattr(self, "cached_result")
+
+        hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
+        return hook.pytest_fixture_setup(fixturedef=self, request=request)
+
+    def __repr__(self):
+        return ("<FixtureDef name=%r scope=%r baseid=%r >" %
+                (self.argname, self.scope, self.baseid))
+
+
+def pytest_fixture_setup(fixturedef, request):
+    """ Execution of fixture setup. """
+    kwargs = {}
+    for argname in fixturedef.argnames:
+        fixdef = request._get_active_fixturedef(argname)
+        result, arg_cache_key, exc = fixdef.cached_result
+        request._check_scope(argname, request.scope, fixdef.scope)
+        kwargs[argname] = result
+
+    fixturefunc = fixturedef.func
+    if fixturedef.unittest:
+        if request.instance is not None:
+            # bind the unbound method to the TestCase instance
+            fixturefunc = fixturedef.func.__get__(request.instance)
+    else:
+        # the fixture function needs to be bound to the actual
+        # request.instance so that code working with "fixturedef" behaves
+        # as expected.
+        if request.instance is not None:
+            fixturefunc = getimfunc(fixturedef.func)
+            if fixturefunc != fixturedef.func:
+                fixturefunc = fixturefunc.__get__(request.instance)
+    my_cache_key = request.param_index
+    try:
+        result = call_fixture_func(fixturefunc, request, kwargs)
+    except TEST_OUTCOME:
+        fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
+        raise
+    fixturedef.cached_result = (result, my_cache_key, None)
+    return result
+
+
+def _ensure_immutable_ids(ids):
+    if ids is None:
+        return
+    if callable(ids):
+        return ids
+    return tuple(ids)
+
+
+@attr.s(frozen=True)
+class FixtureFunctionMarker(object):
+    scope = attr.ib()
+    params = attr.ib(convert=attr.converters.optional(tuple))
+    autouse = attr.ib(default=False)
+    ids = attr.ib(default=None, convert=_ensure_immutable_ids)
+    name = attr.ib(default=None)
+
+    def __call__(self, function):
+        if isclass(function):
+            raise ValueError(
+                "class fixtures not supported (may be in the future)")
+        function._pytestfixturefunction = self
+        return function
+
+
+def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+    """ (return a) decorator to mark a fixture factory function.
+
+    This decorator can be used (with or without parameters) to define a
+    fixture function.  The name of the fixture function can later be
+    referenced to cause its invocation ahead of running tests: test
+    modules or classes can use the pytest.mark.usefixtures(fixturename)
+    marker.  Test functions can directly use fixture names as input
+    arguments in which case the fixture instance returned from the fixture
+    function will be injected.
+
+    :arg scope: the scope for which this fixture is shared, one of
+                "function" (default), "class", "module" or "session".
+
+    :arg params: an optional list of parameters which will cause multiple
+                invocations of the fixture function and all of the tests
+                using it.
+
+    :arg autouse: if True, the fixture func is activated for all tests that
+                can see it.  If False (the default) then an explicit
+                reference is needed to activate the fixture.
+
+    :arg ids: list of string ids each corresponding to the params
+                so that they are part of the test id. If no ids are provided
+                they will be generated automatically from the params.
+
+    :arg name: the name of the fixture. This defaults to the name of the
+                decorated function. If a fixture is used in the same module in
+                which it is defined, the function name of the fixture will be
+                shadowed by the function arg that requests the fixture; one way
+                to resolve this is to name the decorated function
+                ``fixture_<fixturename>`` and then use
+                ``@pytest.fixture(name='<fixturename>')``.
+
+    Fixtures can optionally provide their values to test functions using a ``yield`` statement,
+    instead of ``return``. In this case, the code block after the ``yield`` statement is executed
+    as teardown code regardless of the test outcome. A fixture function must yield exactly once.
+    """
+    if callable(scope) and params is None and autouse is False:
+        # direct decoration
+        return FixtureFunctionMarker(
+            "function", params, autouse, name=name)(scope)
+    if params is not None and not isinstance(params, (list, tuple)):
+        params = list(params)
+    return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
+
+
+def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+    """ (return a) decorator to mark a yield-fixture factory function.
+
+    .. deprecated:: 3.0
+        Use :py:func:`pytest.fixture` directly instead.
+    """
+    if callable(scope) and params is None and not autouse:
+        # direct decoration
+        return FixtureFunctionMarker(
+            "function", params, autouse, ids=ids, name=name)(scope)
+    else:
+        return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
+
+
+defaultfuncargprefixmarker = fixture()
+
+
+@fixture(scope="session")
+def pytestconfig(request):
+    """ the pytest config object with access to command line opts."""
+    return request.config
+
+
+class FixtureManager:
+    """
+    pytest fixtures definitions and information is stored and managed
+    from this class.
+
+    During collection fm.parsefactories() is called multiple times to parse
+    fixture function definitions into FixtureDef objects and internal
+    data structures.
+
+    During collection of test functions, metafunc-mechanics instantiate
+    a FuncFixtureInfo object which is cached per node/func-name.
+    This FuncFixtureInfo object is later retrieved by Function nodes
+    which themselves offer a fixturenames attribute.
+
+    The FuncFixtureInfo object holds information about fixtures and FixtureDefs
+    relevant for a particular function.  An initial list of fixtures is
+    assembled like this:
+
+    - ini-defined usefixtures
+    - autouse-marked fixtures along the collection chain up from the function
+    - usefixtures markers at module/class/function level
+    - test function funcargs
+
+    Subsequently the funcfixtureinfo.fixturenames attribute is computed
+    as the closure of the fixtures needed to setup the initial fixtures,
+    i. e. fixtures needed by fixture functions themselves are appended
+    to the fixturenames list.
+
+    Upon the test-setup phases all fixturenames are instantiated, retrieved
+    by a lookup of their FuncFixtureInfo.
+    """
+
+    _argprefix = "pytest_funcarg__"
+    FixtureLookupError = FixtureLookupError
+    FixtureLookupErrorRepr = FixtureLookupErrorRepr
+
+    def __init__(self, session):
+        self.session = session
+        self.config = session.config
+        self._arg2fixturedefs = {}
+        self._holderobjseen = set()
+        self._arg2finish = {}
+        self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
+        session.config.pluginmanager.register(self, "funcmanage")
+
+    def getfixtureinfo(self, node, func, cls, funcargs=True):
+        if funcargs and not hasattr(node, "nofuncargs"):
+            argnames = getfuncargnames(func, cls=cls)
+        else:
+            argnames = ()
+        usefixtures = getattr(func, "usefixtures", None)
+        initialnames = argnames
+        if usefixtures is not None:
+            initialnames = usefixtures.args + initialnames
+        fm = node.session._fixturemanager
+        names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
+                                                              node)
+        return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs)
+
+    def pytest_plugin_registered(self, plugin):
+        nodeid = None
+        try:
+            p = py.path.local(plugin.__file__)
+        except AttributeError:
+            pass
+        else:
+            # construct the base nodeid which is later used to check
+            # what fixtures are visible for particular tests (as denoted
+            # by their test id)
+            if p.basename.startswith("conftest.py"):
+                nodeid = p.dirpath().relto(self.config.rootdir)
+                if p.sep != nodes.SEP:
+                    nodeid = nodeid.replace(p.sep, nodes.SEP)
+        self.parsefactories(plugin, nodeid)
+
+    def _getautousenames(self, nodeid):
+        """ return a tuple of fixture names to be used. """
+        autousenames = []
+        for baseid, basenames in self._nodeid_and_autousenames:
+            if nodeid.startswith(baseid):
+                if baseid:
+                    i = len(baseid)
+                    nextchar = nodeid[i:i + 1]
+                    if nextchar and nextchar not in ":/":
+                        continue
+                autousenames.extend(basenames)
+        # make sure autousenames are sorted by scope, scopenum 0 is session
+        autousenames.sort(
+            key=lambda x: self._arg2fixturedefs[x][-1].scopenum)
+        return autousenames
+
+    def getfixtureclosure(self, fixturenames, parentnode):
+        # collect the closure of all fixtures , starting with the given
+        # fixturenames as the initial set.  As we have to visit all
+        # factory definitions anyway, we also return a arg2fixturedefs
+        # mapping so that the caller can reuse it and does not have
+        # to re-discover fixturedefs again for each fixturename
+        # (discovering matching fixtures for a given name/node is expensive)
+
+        parentid = parentnode.nodeid
+        fixturenames_closure = self._getautousenames(parentid)
+
+        def merge(otherlist):
+            for arg in otherlist:
+                if arg not in fixturenames_closure:
+                    fixturenames_closure.append(arg)
+
+        merge(fixturenames)
+        arg2fixturedefs = {}
+        lastlen = -1
+        while lastlen != len(fixturenames_closure):
+            lastlen = len(fixturenames_closure)
+            for argname in fixturenames_closure:
+                if argname in arg2fixturedefs:
+                    continue
+                fixturedefs = self.getfixturedefs(argname, parentid)
+                if fixturedefs:
+                    arg2fixturedefs[argname] = fixturedefs
+                    merge(fixturedefs[-1].argnames)
+        return fixturenames_closure, arg2fixturedefs
+
+    def pytest_generate_tests(self, metafunc):
+        for argname in metafunc.fixturenames:
+            faclist = metafunc._arg2fixturedefs.get(argname)
+            if faclist:
+                fixturedef = faclist[-1]
+                if fixturedef.params is not None:
+                    parametrize_func = getattr(metafunc.function, 'parametrize', None)
+                    func_params = getattr(parametrize_func, 'args', [[None]])
+                    func_kwargs = getattr(parametrize_func, 'kwargs', {})
+                    # skip directly parametrized arguments
+                    if "argnames" in func_kwargs:
+                        argnames = parametrize_func.kwargs["argnames"]
+                    else:
+                        argnames = func_params[0]
+                    if not isinstance(argnames, (tuple, list)):
+                        argnames = [x.strip() for x in argnames.split(",") if x.strip()]
+                    if argname not in func_params and argname not in argnames:
+                        metafunc.parametrize(argname, fixturedef.params,
+                                             indirect=True, scope=fixturedef.scope,
+                                             ids=fixturedef.ids)
+            else:
+                continue  # will raise FixtureLookupError at setup time
+
+    def pytest_collection_modifyitems(self, items):
+        # separate parametrized setups
+        items[:] = reorder_items(items)
+
+    def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
+        if nodeid is not NOTSET:
+            holderobj = node_or_obj
+        else:
+            holderobj = node_or_obj.obj
+            nodeid = node_or_obj.nodeid
+        if holderobj in self._holderobjseen:
+            return
+        self._holderobjseen.add(holderobj)
+        autousenames = []
+        for name in dir(holderobj):
+            # The attribute can be an arbitrary descriptor, so the attribute
+            # access below can raise. safe_getatt() ignores such exceptions.
+            obj = safe_getattr(holderobj, name, None)
+            # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
+            # or are "@pytest.fixture" marked
+            marker = getfixturemarker(obj)
+            if marker is None:
+                if not name.startswith(self._argprefix):
+                    continue
+                if not callable(obj):
+                    continue
+                marker = defaultfuncargprefixmarker
+                from _pytest import deprecated
+                self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid)
+                name = name[len(self._argprefix):]
+            elif not isinstance(marker, FixtureFunctionMarker):
+                # magic globals  with __getattr__ might have got us a wrong
+                # fixture attribute
+                continue
+            else:
+                if marker.name:
+                    name = marker.name
+                msg = 'fixtures cannot have "pytest_funcarg__" prefix ' \
+                      'and be decorated with @pytest.fixture:\n%s' % name
+                assert not name.startswith(self._argprefix), msg
+
+            fixture_def = FixtureDef(self, nodeid, name, obj,
+                                     marker.scope, marker.params,
+                                     unittest=unittest, ids=marker.ids)
+
+            faclist = self._arg2fixturedefs.setdefault(name, [])
+            if fixture_def.has_location:
+                faclist.append(fixture_def)
+            else:
+                # fixturedefs with no location are at the front
+                # so this inserts the current fixturedef after the
+                # existing fixturedefs from external plugins but
+                # before the fixturedefs provided in conftests.
+                i = len([f for f in faclist if not f.has_location])
+                faclist.insert(i, fixture_def)
+            if marker.autouse:
+                autousenames.append(name)
+
+        if autousenames:
+            self._nodeid_and_autousenames.append((nodeid or '', autousenames))
+
+    def getfixturedefs(self, argname, nodeid):
+        """
+        Gets a list of fixtures which are applicable to the given node id.
+
+        :param str argname: name of the fixture to search for
+        :param str nodeid: full node id of the requesting test.
+        :return: list[FixtureDef]
+        """
+        try:
+            fixturedefs = self._arg2fixturedefs[argname]
+        except KeyError:
+            return None
+        else:
+            return tuple(self._matchfactories(fixturedefs, nodeid))
+
+    def _matchfactories(self, fixturedefs, nodeid):
+        for fixturedef in fixturedefs:
+            if nodes.ischildnode(fixturedef.baseid, nodeid):
+                yield fixturedef
diff --git a/tools/third_party/pytest/_pytest/freeze_support.py b/tools/third_party/pytest/_pytest/freeze_support.py
new file mode 100644
index 0000000..97147a8
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/freeze_support.py
@@ -0,0 +1,43 @@
+"""
+Provides a function to report all internal modules for using freezing tools
+pytest
+"""
+from __future__ import absolute_import, division, print_function
+
+
+def freeze_includes():
+    """
+    Returns a list of module names used by py.test that should be
+    included by cx_freeze.
+    """
+    import py
+    import _pytest
+    result = list(_iter_all_modules(py))
+    result += list(_iter_all_modules(_pytest))
+    return result
+
+
+def _iter_all_modules(package, prefix=''):
+    """
+    Iterates over the names of all modules that can be found in the given
+    package, recursively.
+    Example:
+        _iter_all_modules(_pytest) ->
+            ['_pytest.assertion.newinterpret',
+             '_pytest.capture',
+             '_pytest.core',
+             ...
+            ]
+    """
+    import os
+    import pkgutil
+    if type(package) is not str:
+        path, prefix = package.__path__[0], package.__name__ + '.'
+    else:
+        path = package
+    for _, name, is_package in pkgutil.iter_modules([path]):
+        if is_package:
+            for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'):
+                yield prefix + m
+        else:
+            yield prefix + name
diff --git a/tools/third_party/pytest/_pytest/helpconfig.py b/tools/third_party/pytest/_pytest/helpconfig.py
new file mode 100644
index 0000000..e744637
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/helpconfig.py
@@ -0,0 +1,184 @@
+""" version info, help messages, tracing configuration.  """
+from __future__ import absolute_import, division, print_function
+
+import py
+import pytest
+from _pytest.config import PrintHelp
+import os
+import sys
+from argparse import Action
+
+
+class HelpAction(Action):
+    """This is an argparse Action that will raise an exception in
+    order to skip the rest of the argument parsing when --help is passed.
+    This prevents argparse from quitting due to missing required arguments
+    when any are defined, for example by ``pytest_addoption``.
+    This is similar to the way that the builtin argparse --help option is
+    implemented by raising SystemExit.
+    """
+
+    def __init__(self,
+                 option_strings,
+                 dest=None,
+                 default=False,
+                 help=None):
+        super(HelpAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            const=True,
+            default=default,
+            nargs=0,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        setattr(namespace, self.dest, self.const)
+
+        # We should only skip the rest of the parsing after preparse is done
+        if getattr(parser._parser, 'after_preparse', False):
+            raise PrintHelp
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup('debugconfig')
+    group.addoption('--version', action="store_true",
+                    help="display pytest lib version and import information.")
+    group._addoption("-h", "--help", action=HelpAction, dest="help",
+                     help="show help message and configuration info")
+    group._addoption('-p', action="append", dest="plugins", default=[],
+                     metavar="name",
+                     help="early-load given plugin (multi-allowed). "
+                     "To avoid loading of plugins, use the `no:` prefix, e.g. "
+                     "`no:doctest`.")
+    group.addoption('--traceconfig', '--trace-config',
+                    action="store_true", default=False,
+                    help="trace considerations of conftest.py files."),
+    group.addoption('--debug',
+                    action="store_true", dest="debug", default=False,
+                    help="store internal tracing debug information in 'pytestdebug.log'.")
+    group._addoption(
+        '-o', '--override-ini', nargs='*', dest="override_ini",
+        action="append",
+        help="override config option with option=value style, e.g. `-o xfail_strict=True`.")
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_cmdline_parse():
+    outcome = yield
+    config = outcome.get_result()
+    if config.option.debug:
+        path = os.path.abspath("pytestdebug.log")
+        debugfile = open(path, 'w')
+        debugfile.write("versions pytest-%s, py-%s, "
+                        "python-%s\ncwd=%s\nargs=%s\n\n" % (
+                            pytest.__version__, py.__version__,
+                            ".".join(map(str, sys.version_info)),
+                            os.getcwd(), config._origargs))
+        config.trace.root.setwriter(debugfile.write)
+        undo_tracing = config.pluginmanager.enable_tracing()
+        sys.stderr.write("writing pytestdebug information to %s\n" % path)
+
+        def unset_tracing():
+            debugfile.close()
+            sys.stderr.write("wrote pytestdebug information to %s\n" %
+                             debugfile.name)
+            config.trace.root.setwriter(None)
+            undo_tracing()
+
+        config.add_cleanup(unset_tracing)
+
+
+def pytest_cmdline_main(config):
+    if config.option.version:
+        p = py.path.local(pytest.__file__)
+        sys.stderr.write("This is pytest version %s, imported from %s\n" %
+                         (pytest.__version__, p))
+        plugininfo = getpluginversioninfo(config)
+        if plugininfo:
+            for line in plugininfo:
+                sys.stderr.write(line + "\n")
+        return 0
+    elif config.option.help:
+        config._do_configure()
+        showhelp(config)
+        config._ensure_unconfigure()
+        return 0
+
+
+def showhelp(config):
+    reporter = config.pluginmanager.get_plugin('terminalreporter')
+    tw = reporter._tw
+    tw.write(config._parser.optparser.format_help())
+    tw.line()
+    tw.line()
+    tw.line("[pytest] ini-options in the first "
+            "pytest.ini|tox.ini|setup.cfg file found:")
+    tw.line()
+
+    for name in config._parser._ininames:
+        help, type, default = config._parser._inidict[name]
+        if type is None:
+            type = "string"
+        spec = "%s (%s)" % (name, type)
+        line = "  %-24s %s" % (spec, help)
+        tw.line(line[:tw.fullwidth])
+
+    tw.line()
+    tw.line("environment variables:")
+    vars = [
+        ("PYTEST_ADDOPTS", "extra command line options"),
+        ("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
+        ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals")
+    ]
+    for name, help in vars:
+        tw.line("  %-24s %s" % (name, help))
+    tw.line()
+    tw.line()
+
+    tw.line("to see available markers type: pytest --markers")
+    tw.line("to see available fixtures type: pytest --fixtures")
+    tw.line("(shown according to specified file_or_dir or current dir "
+            "if not specified)")
+
+    for warningreport in reporter.stats.get('warnings', []):
+        tw.line("warning : " + warningreport.message, red=True)
+    return
+
+
+conftest_options = [
+    ('pytest_plugins', 'list of plugin names to load'),
+]
+
+
+def getpluginversioninfo(config):
+    lines = []
+    plugininfo = config.pluginmanager.list_plugin_distinfo()
+    if plugininfo:
+        lines.append("setuptools registered plugins:")
+        for plugin, dist in plugininfo:
+            loc = getattr(plugin, '__file__', repr(plugin))
+            content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
+            lines.append("  " + content)
+    return lines
+
+
+def pytest_report_header(config):
+    lines = []
+    if config.option.debug or config.option.traceconfig:
+        lines.append("using: pytest-%s pylib-%s" %
+                     (pytest.__version__, py.__version__))
+
+        verinfo = getpluginversioninfo(config)
+        if verinfo:
+            lines.extend(verinfo)
+
+    if config.option.traceconfig:
+        lines.append("active plugins:")
+        items = config.pluginmanager.list_name_plugin()
+        for name, plugin in items:
+            if hasattr(plugin, '__file__'):
+                r = plugin.__file__
+            else:
+                r = repr(plugin)
+            lines.append("    %-20s: %s" % (name, r))
+    return lines
diff --git a/tools/third_party/pytest/_pytest/hookspec.py b/tools/third_party/pytest/_pytest/hookspec.py
new file mode 100644
index 0000000..440bf99
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/hookspec.py
@@ -0,0 +1,423 @@
+""" hook specifications for pytest plugins, invoked from main.py and builtin plugins.  """
+
+from pluggy import HookspecMarker
+
+hookspec = HookspecMarker("pytest")
+
+# -------------------------------------------------------------------------
+# Initialization hooks called for every plugin
+# -------------------------------------------------------------------------
+
+
+@hookspec(historic=True)
+def pytest_addhooks(pluginmanager):
+    """called at plugin registration time to allow adding new hooks via a call to
+    pluginmanager.add_hookspecs(module_or_class, prefix)."""
+
+
+@hookspec(historic=True)
+def pytest_namespace():
+    """
+    DEPRECATED: this hook causes direct monkeypatching on pytest, its use is strongly discouraged
+    return dict of name->object to be made globally available in
+    the pytest namespace.  This hook is called at plugin registration
+    time.
+    """
+
+
+@hookspec(historic=True)
+def pytest_plugin_registered(plugin, manager):
+    """ a new pytest plugin got registered. """
+
+
+@hookspec(historic=True)
+def pytest_addoption(parser):
+    """register argparse-style options and ini-style config values,
+    called once at the beginning of a test run.
+
+    .. note::
+
+        This function should be implemented only in plugins or ``conftest.py``
+        files situated at the tests root directory due to how pytest
+        :ref:`discovers plugins during startup <pluginorder>`.
+
+    :arg parser: To add command line options, call
+        :py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
+        To add ini-file values call :py:func:`parser.addini(...)
+        <_pytest.config.Parser.addini>`.
+
+    Options can later be accessed through the
+    :py:class:`config <_pytest.config.Config>` object, respectively:
+
+    - :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
+      retrieve the value of a command line option.
+
+    - :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
+      a value read from an ini-style file.
+
+    The config object is passed around on many internal objects via the ``.config``
+    attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
+    via (deprecated) ``pytest.config``.
+    """
+
+
+@hookspec(historic=True)
+def pytest_configure(config):
+    """
+    Allows plugins and conftest files to perform initial configuration.
+
+    This hook is called for every plugin and initial conftest file
+    after command line options have been parsed.
+
+    After that, the hook is called for other conftest files as they are
+    imported.
+
+    :arg config: pytest config object
+    :type config: _pytest.config.Config
+    """
+
+# -------------------------------------------------------------------------
+# Bootstrapping hooks called for plugins registered early enough:
+# internal and 3rd party plugins as well as directly
+# discoverable conftest.py local plugins.
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_cmdline_parse(pluginmanager, args):
+    """return initialized config object, parsing the specified args.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_cmdline_preparse(config, args):
+    """(deprecated) modify command line arguments before option parsing. """
+
+
+@hookspec(firstresult=True)
+def pytest_cmdline_main(config):
+    """ called for performing the main command line action. The default
+    implementation will invoke the configure hooks and runtest_mainloop.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_load_initial_conftests(early_config, parser, args):
+    """ implements the loading of initial conftest files ahead
+    of command line option parsing. """
+
+
+# -------------------------------------------------------------------------
+# collection hooks
+# -------------------------------------------------------------------------
+
+@hookspec(firstresult=True)
+def pytest_collection(session):
+    """ perform the collection protocol for the given session.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_collection_modifyitems(session, config, items):
+    """ called after collection has been performed, may filter or re-order
+    the items in-place."""
+
+
+def pytest_collection_finish(session):
+    """ called after collection has been performed and modified. """
+
+
+@hookspec(firstresult=True)
+def pytest_ignore_collect(path, config):
+    """ return True to prevent considering this path for collection.
+    This hook is consulted for all files and directories prior to calling
+    more specific hooks.
+
+    Stops at first non-None result, see :ref:`firstresult`
+    """
+
+
+@hookspec(firstresult=True)
+def pytest_collect_directory(path, parent):
+    """ called before traversing a directory for collection files.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_collect_file(path, parent):
+    """ return collection Node or None for the given path. Any new node
+    needs to have the specified ``parent`` as a parent."""
+
+# logging hooks for collection
+
+
+def pytest_collectstart(collector):
+    """ collector starts collecting. """
+
+
+def pytest_itemcollected(item):
+    """ we just collected a test item. """
+
+
+def pytest_collectreport(report):
+    """ collector finished collecting. """
+
+
+def pytest_deselected(items):
+    """ called for test items deselected by keyword. """
+
+
+@hookspec(firstresult=True)
+def pytest_make_collect_report(collector):
+    """ perform ``collector.collect()`` and return a CollectReport.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+# -------------------------------------------------------------------------
+# Python test function related hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_pycollect_makemodule(path, parent):
+    """ return a Module collector or None for the given path.
+    This hook will be called for each matching test module path.
+    The pytest_collect_file hook needs to be used if you want to
+    create test modules for files that do not match as a test module.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+@hookspec(firstresult=True)
+def pytest_pycollect_makeitem(collector, name, obj):
+    """ return custom item/collector for a python object in a module, or None.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+@hookspec(firstresult=True)
+def pytest_pyfunc_call(pyfuncitem):
+    """ call underlying test function.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_generate_tests(metafunc):
+    """ generate (multiple) parametrized calls to a test function."""
+
+
+@hookspec(firstresult=True)
+def pytest_make_parametrize_id(config, val, argname):
+    """Return a user-friendly string representation of the given ``val`` that will be used
+    by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
+    The parameter name is available as ``argname``, if required.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+# -------------------------------------------------------------------------
+# generic runtest related hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_runtestloop(session):
+    """ called for performing the main runtest loop
+    (after collection finished).
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_itemstart(item, node):
+    """ (deprecated, use pytest_runtest_logstart). """
+
+
+@hookspec(firstresult=True)
+def pytest_runtest_protocol(item, nextitem):
+    """ implements the runtest_setup/call/teardown protocol for
+    the given test item, including capturing exceptions and calling
+    reporting hooks.
+
+    :arg item: test item for which the runtest protocol is performed.
+
+    :arg nextitem: the scheduled-to-be-next test item (or None if this
+                   is the end my friend).  This argument is passed on to
+                   :py:func:`pytest_runtest_teardown`.
+
+    :return boolean: True if no further hook implementations should be invoked.
+
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_runtest_logstart(nodeid, location):
+    """ signal the start of running a single test item. """
+
+
+def pytest_runtest_setup(item):
+    """ called before ``pytest_runtest_call(item)``. """
+
+
+def pytest_runtest_call(item):
+    """ called to execute the test ``item``. """
+
+
+def pytest_runtest_teardown(item, nextitem):
+    """ called after ``pytest_runtest_call``.
+
+    :arg nextitem: the scheduled-to-be-next test item (None if no further
+                   test item is scheduled).  This argument can be used to
+                   perform exact teardowns, i.e. calling just enough finalizers
+                   so that nextitem only needs to call setup-functions.
+    """
+
+
+@hookspec(firstresult=True)
+def pytest_runtest_makereport(item, call):
+    """ return a :py:class:`_pytest.runner.TestReport` object
+    for the given :py:class:`pytest.Item <_pytest.main.Item>` and
+    :py:class:`_pytest.runner.CallInfo`.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_runtest_logreport(report):
+    """ process a test setup/call/teardown report relating to
+    the respective phase of executing a test. """
+
+# -------------------------------------------------------------------------
+# Fixture related hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_fixture_setup(fixturedef, request):
+    """ performs fixture setup execution.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_fixture_post_finalizer(fixturedef, request):
+    """ called after fixture teardown, but before the cache is cleared so
+    the fixture result cache ``fixturedef.cached_result`` can
+    still be accessed."""
+
+# -------------------------------------------------------------------------
+# test session related hooks
+# -------------------------------------------------------------------------
+
+
+def pytest_sessionstart(session):
+    """ before session.main() is called. """
+
+
+def pytest_sessionfinish(session, exitstatus):
+    """ whole test run finishes. """
+
+
+def pytest_unconfigure(config):
+    """ called before test process is exited.  """
+
+
+# -------------------------------------------------------------------------
+# hooks for customizing the assert methods
+# -------------------------------------------------------------------------
+
+def pytest_assertrepr_compare(config, op, left, right):
+    """return explanation for comparisons in failing assert expressions.
+
+    Return None for no custom explanation, otherwise return a list
+    of strings.  The strings will be joined by newlines but any newlines
+    *in* a string will be escaped.  Note that all but the first line will
+    be indented slightly, the intention is for the first line to be a summary.
+    """
+
+# -------------------------------------------------------------------------
+# hooks for influencing reporting (invoked from _pytest_terminal)
+# -------------------------------------------------------------------------
+
+
+def pytest_report_header(config, startdir):
+    """ return a string or list of strings to be displayed as header info for terminal reporting.
+
+    :param config: the pytest config object.
+    :param startdir: py.path object with the starting dir
+
+    .. note::
+
+        This function should be implemented only in plugins or ``conftest.py``
+        files situated at the tests root directory due to how pytest
+        :ref:`discovers plugins during startup <pluginorder>`.
+    """
+
+
+def pytest_report_collectionfinish(config, startdir, items):
+    """
+    .. versionadded:: 3.2
+
+    return a string or list of strings to be displayed after collection has finished successfully.
+
+    This strings will be displayed after the standard "collected X items" message.
+
+    :param config: the pytest config object.
+    :param startdir: py.path object with the starting dir
+    :param items: list of pytest items that are going to be executed; this list should not be modified.
+    """
+
+
+@hookspec(firstresult=True)
+def pytest_report_teststatus(report):
+    """ return result-category, shortletter and verbose word for reporting.
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_terminal_summary(terminalreporter, exitstatus):
+    """ add additional section in terminal summary reporting.  """
+
+
+@hookspec(historic=True)
+def pytest_logwarning(message, code, nodeid, fslocation):
+    """ process a warning specified by a message, a code string,
+    a nodeid and fslocation (both of which may be None
+    if the warning is not tied to a partilar node/location)."""
+
+# -------------------------------------------------------------------------
+# doctest hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_doctest_prepare_content(content):
+    """ return processed content for a given doctest
+
+    Stops at first non-None result, see :ref:`firstresult` """
+
+# -------------------------------------------------------------------------
+# error handling and internal debugging hooks
+# -------------------------------------------------------------------------
+
+
+def pytest_internalerror(excrepr, excinfo):
+    """ called for internal errors. """
+
+
+def pytest_keyboard_interrupt(excinfo):
+    """ called for keyboard interrupt. """
+
+
+def pytest_exception_interact(node, call, report):
+    """called when an exception was raised which can potentially be
+    interactively handled.
+
+    This hook is only called if an exception was raised
+    that is not an internal exception like ``skip.Exception``.
+    """
+
+
+def pytest_enter_pdb(config):
+    """ called upon pdb.set_trace(), can be used by plugins to take special
+    action just before the python debugger enters in interactive mode.
+
+    :arg config: pytest config object
+    :type config: _pytest.config.Config
+    """
diff --git a/tools/third_party/pytest/_pytest/junitxml.py b/tools/third_party/pytest/_pytest/junitxml.py
new file mode 100644
index 0000000..7fb40dc
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/junitxml.py
@@ -0,0 +1,453 @@
+"""
+    report test results in JUnit-XML format,
+    for use with Jenkins and build integration servers.
+
+
+Based on initial code from Ross Lawley.
+
+Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
+src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
+"""
+from __future__ import absolute_import, division, print_function
+
+import functools
+import py
+import os
+import re
+import sys
+import time
+import pytest
+from _pytest import nodes
+from _pytest.config import filename_arg
+
+# Python 2.X and 3.X compatibility
+if sys.version_info[0] < 3:
+    from codecs import open
+else:
+    unichr = chr
+    unicode = str
+    long = int
+
+
+class Junit(py.xml.Namespace):
+    pass
+
+
+# We need to get the subset of the invalid unicode ranges according to
+# XML 1.0 which are valid in this python build.  Hence we calculate
+# this dynamically instead of hardcoding it.  The spec range of valid
+# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+#                    | [#x10000-#x10FFFF]
+_legal_chars = (0x09, 0x0A, 0x0d)
+_legal_ranges = (
+    (0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF),
+)
+_legal_xml_re = [
+    unicode("%s-%s") % (unichr(low), unichr(high))
+    for (low, high) in _legal_ranges if low < sys.maxunicode
+]
+_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
+illegal_xml_re = re.compile(unicode('[^%s]') % unicode('').join(_legal_xml_re))
+del _legal_chars
+del _legal_ranges
+del _legal_xml_re
+
+_py_ext_re = re.compile(r"\.py$")
+
+
+def bin_xml_escape(arg):
+    def repl(matchobj):
+        i = ord(matchobj.group())
+        if i <= 0xFF:
+            return unicode('#x%02X') % i
+        else:
+            return unicode('#x%04X') % i
+
+    return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
+
+
+class _NodeReporter(object):
+    def __init__(self, nodeid, xml):
+
+        self.id = nodeid
+        self.xml = xml
+        self.add_stats = self.xml.add_stats
+        self.duration = 0
+        self.properties = []
+        self.nodes = []
+        self.testcase = None
+        self.attrs = {}
+
+    def append(self, node):
+        self.xml.add_stats(type(node).__name__)
+        self.nodes.append(node)
+
+    def add_property(self, name, value):
+        self.properties.append((str(name), bin_xml_escape(value)))
+
+    def make_properties_node(self):
+        """Return a Junit node containing custom properties, if any.
+        """
+        if self.properties:
+            return Junit.properties([
+                Junit.property(name=name, value=value)
+                for name, value in self.properties
+            ])
+        return ''
+
+    def record_testreport(self, testreport):
+        assert not self.testcase
+        names = mangle_test_address(testreport.nodeid)
+        classnames = names[:-1]
+        if self.xml.prefix:
+            classnames.insert(0, self.xml.prefix)
+        attrs = {
+            "classname": ".".join(classnames),
+            "name": bin_xml_escape(names[-1]),
+            "file": testreport.location[0],
+        }
+        if testreport.location[1] is not None:
+            attrs["line"] = testreport.location[1]
+        if hasattr(testreport, "url"):
+            attrs["url"] = testreport.url
+        self.attrs = attrs
+
+    def to_xml(self):
+        testcase = Junit.testcase(time=self.duration, **self.attrs)
+        testcase.append(self.make_properties_node())
+        for node in self.nodes:
+            testcase.append(node)
+        return testcase
+
+    def _add_simple(self, kind, message, data=None):
+        data = bin_xml_escape(data)
+        node = kind(data, message=message)
+        self.append(node)
+
+    def write_captured_output(self, report):
+        for capname in ('out', 'err'):
+            content = getattr(report, 'capstd' + capname)
+            if content:
+                tag = getattr(Junit, 'system-' + capname)
+                self.append(tag(bin_xml_escape(content)))
+
+    def append_pass(self, report):
+        self.add_stats('passed')
+
+    def append_failure(self, report):
+        # msg = str(report.longrepr.reprtraceback.extraline)
+        if hasattr(report, "wasxfail"):
+            self._add_simple(
+                Junit.skipped,
+                "xfail-marked test passes unexpectedly")
+        else:
+            if hasattr(report.longrepr, "reprcrash"):
+                message = report.longrepr.reprcrash.message
+            elif isinstance(report.longrepr, (unicode, str)):
+                message = report.longrepr
+            else:
+                message = str(report.longrepr)
+            message = bin_xml_escape(message)
+            fail = Junit.failure(message=message)
+            fail.append(bin_xml_escape(report.longrepr))
+            self.append(fail)
+
+    def append_collect_error(self, report):
+        # msg = str(report.longrepr.reprtraceback.extraline)
+        self.append(Junit.error(bin_xml_escape(report.longrepr),
+                                message="collection failure"))
+
+    def append_collect_skipped(self, report):
+        self._add_simple(
+            Junit.skipped, "collection skipped", report.longrepr)
+
+    def append_error(self, report):
+        if getattr(report, 'when', None) == 'teardown':
+            msg = "test teardown failure"
+        else:
+            msg = "test setup failure"
+        self._add_simple(
+            Junit.error, msg, report.longrepr)
+
+    def append_skipped(self, report):
+        if hasattr(report, "wasxfail"):
+            self._add_simple(
+                Junit.skipped, "expected test failure", report.wasxfail
+            )
+        else:
+            filename, lineno, skipreason = report.longrepr
+            if skipreason.startswith("Skipped: "):
+                skipreason = bin_xml_escape(skipreason[9:])
+            self.append(
+                Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
+                              type="pytest.skip",
+                              message=skipreason))
+        self.write_captured_output(report)
+
+    def finalize(self):
+        data = self.to_xml().unicode(indent=0)
+        self.__dict__.clear()
+        self.to_xml = lambda: py.xml.raw(data)
+
+
+@pytest.fixture
+def record_xml_property(request):
+    """Add extra xml properties to the tag for the calling test.
+    The fixture is callable with ``(name, value)``, with value being automatically
+    xml-encoded.
+    """
+    request.node.warn(
+        code='C3',
+        message='record_xml_property is an experimental feature',
+    )
+    xml = getattr(request.config, "_xml", None)
+    if xml is not None:
+        node_reporter = xml.node_reporter(request.node.nodeid)
+        return node_reporter.add_property
+    else:
+        def add_property_noop(name, value):
+            pass
+
+        return add_property_noop
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("terminal reporting")
+    group.addoption(
+        '--junitxml', '--junit-xml',
+        action="store",
+        dest="xmlpath",
+        metavar="path",
+        type=functools.partial(filename_arg, optname="--junitxml"),
+        default=None,
+        help="create junit-xml style report file at given path.")
+    group.addoption(
+        '--junitprefix', '--junit-prefix',
+        action="store",
+        metavar="str",
+        default=None,
+        help="prepend prefix to classnames in junit-xml output")
+    parser.addini("junit_suite_name", "Test suite name for JUnit report", default="pytest")
+
+
+def pytest_configure(config):
+    xmlpath = config.option.xmlpath
+    # prevent opening xmllog on slave nodes (xdist)
+    if xmlpath and not hasattr(config, 'slaveinput'):
+        config._xml = LogXML(xmlpath, config.option.junitprefix, config.getini("junit_suite_name"))
+        config.pluginmanager.register(config._xml)
+
+
+def pytest_unconfigure(config):
+    xml = getattr(config, '_xml', None)
+    if xml:
+        del config._xml
+        config.pluginmanager.unregister(xml)
+
+
+def mangle_test_address(address):
+    path, possible_open_bracket, params = address.partition('[')
+    names = path.split("::")
+    try:
+        names.remove('()')
+    except ValueError:
+        pass
+    # convert file path to dotted path
+    names[0] = names[0].replace(nodes.SEP, '.')
+    names[0] = _py_ext_re.sub("", names[0])
+    # put any params back
+    names[-1] += possible_open_bracket + params
+    return names
+
+
+class LogXML(object):
+    def __init__(self, logfile, prefix, suite_name="pytest"):
+        logfile = os.path.expanduser(os.path.expandvars(logfile))
+        self.logfile = os.path.normpath(os.path.abspath(logfile))
+        self.prefix = prefix
+        self.suite_name = suite_name
+        self.stats = dict.fromkeys([
+            'error',
+            'passed',
+            'failure',
+            'skipped',
+        ], 0)
+        self.node_reporters = {}  # nodeid -> _NodeReporter
+        self.node_reporters_ordered = []
+        self.global_properties = []
+        # List of reports that failed on call but teardown is pending.
+        self.open_reports = []
+        self.cnt_double_fail_tests = 0
+
+    def finalize(self, report):
+        nodeid = getattr(report, 'nodeid', report)
+        # local hack to handle xdist report order
+        slavenode = getattr(report, 'node', None)
+        reporter = self.node_reporters.pop((nodeid, slavenode))
+        if reporter is not None:
+            reporter.finalize()
+
+    def node_reporter(self, report):
+        nodeid = getattr(report, 'nodeid', report)
+        # local hack to handle xdist report order
+        slavenode = getattr(report, 'node', None)
+
+        key = nodeid, slavenode
+
+        if key in self.node_reporters:
+            # TODO: breasks for --dist=each
+            return self.node_reporters[key]
+
+        reporter = _NodeReporter(nodeid, self)
+
+        self.node_reporters[key] = reporter
+        self.node_reporters_ordered.append(reporter)
+
+        return reporter
+
+    def add_stats(self, key):
+        if key in self.stats:
+            self.stats[key] += 1
+
+    def _opentestcase(self, report):
+        reporter = self.node_reporter(report)
+        reporter.record_testreport(report)
+        return reporter
+
+    def pytest_runtest_logreport(self, report):
+        """handle a setup/call/teardown report, generating the appropriate
+        xml tags as necessary.
+
+        note: due to plugins like xdist, this hook may be called in interlaced
+        order with reports from other nodes. for example:
+
+        usual call order:
+            -> setup node1
+            -> call node1
+            -> teardown node1
+            -> setup node2
+            -> call node2
+            -> teardown node2
+
+        possible call order in xdist:
+            -> setup node1
+            -> call node1
+            -> setup node2
+            -> call node2
+            -> teardown node2
+            -> teardown node1
+        """
+        close_report = None
+        if report.passed:
+            if report.when == "call":  # ignore setup/teardown
+                reporter = self._opentestcase(report)
+                reporter.append_pass(report)
+        elif report.failed:
+            if report.when == "teardown":
+                # The following vars are needed when xdist plugin is used
+                report_wid = getattr(report, "worker_id", None)
+                report_ii = getattr(report, "item_index", None)
+                close_report = next(
+                    (rep for rep in self.open_reports
+                     if (rep.nodeid == report.nodeid and
+                         getattr(rep, "item_index", None) == report_ii and
+                         getattr(rep, "worker_id", None) == report_wid
+                         )
+                     ), None)
+                if close_report:
+                    # We need to open new testcase in case we have failure in
+                    # call and error in teardown in order to follow junit
+                    # schema
+                    self.finalize(close_report)
+                    self.cnt_double_fail_tests += 1
+            reporter = self._opentestcase(report)
+            if report.when == "call":
+                reporter.append_failure(report)
+                self.open_reports.append(report)
+            else:
+                reporter.append_error(report)
+        elif report.skipped:
+            reporter = self._opentestcase(report)
+            reporter.append_skipped(report)
+        self.update_testcase_duration(report)
+        if report.when == "teardown":
+            reporter = self._opentestcase(report)
+            reporter.write_captured_output(report)
+            self.finalize(report)
+            report_wid = getattr(report, "worker_id", None)
+            report_ii = getattr(report, "item_index", None)
+            close_report = next(
+                (rep for rep in self.open_reports
+                 if (rep.nodeid == report.nodeid and
+                     getattr(rep, "item_index", None) == report_ii and
+                     getattr(rep, "worker_id", None) == report_wid
+                     )
+                 ), None)
+            if close_report:
+                self.open_reports.remove(close_report)
+
+    def update_testcase_duration(self, report):
+        """accumulates total duration for nodeid from given report and updates
+        the Junit.testcase with the new total if already created.
+        """
+        reporter = self.node_reporter(report)
+        reporter.duration += getattr(report, 'duration', 0.0)
+
+    def pytest_collectreport(self, report):
+        if not report.passed:
+            reporter = self._opentestcase(report)
+            if report.failed:
+                reporter.append_collect_error(report)
+            else:
+                reporter.append_collect_skipped(report)
+
+    def pytest_internalerror(self, excrepr):
+        reporter = self.node_reporter('internal')
+        reporter.attrs.update(classname="pytest", name='internal')
+        reporter._add_simple(Junit.error, 'internal error', excrepr)
+
+    def pytest_sessionstart(self):
+        self.suite_start_time = time.time()
+
+    def pytest_sessionfinish(self):
+        dirname = os.path.dirname(os.path.abspath(self.logfile))
+        if not os.path.isdir(dirname):
+            os.makedirs(dirname)
+        logfile = open(self.logfile, 'w', encoding='utf-8')
+        suite_stop_time = time.time()
+        suite_time_delta = suite_stop_time - self.suite_start_time
+
+        numtests = (self.stats['passed'] + self.stats['failure'] +
+                    self.stats['skipped'] + self.stats['error'] -
+                    self.cnt_double_fail_tests)
+        logfile.write('<?xml version="1.0" encoding="utf-8"?>')
+
+        logfile.write(Junit.testsuite(
+            self._get_global_properties_node(),
+            [x.to_xml() for x in self.node_reporters_ordered],
+            name=self.suite_name,
+            errors=self.stats['error'],
+            failures=self.stats['failure'],
+            skips=self.stats['skipped'],
+            tests=numtests,
+            time="%.3f" % suite_time_delta, ).unicode(indent=0))
+        logfile.close()
+
+    def pytest_terminal_summary(self, terminalreporter):
+        terminalreporter.write_sep("-",
+                                   "generated xml file: %s" % (self.logfile))
+
+    def add_global_property(self, name, value):
+        self.global_properties.append((str(name), bin_xml_escape(value)))
+
+    def _get_global_properties_node(self):
+        """Return a Junit node containing custom properties, if any.
+        """
+        if self.global_properties:
+            return Junit.properties(
+                [
+                    Junit.property(name=name, value=value)
+                    for name, value in self.global_properties
+                ]
+            )
+        return ''
diff --git a/tools/third_party/pytest/_pytest/logging.py b/tools/third_party/pytest/_pytest/logging.py
new file mode 100644
index 0000000..ed4db25
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/logging.py
@@ -0,0 +1,337 @@
+from __future__ import absolute_import, division, print_function
+
+import logging
+from contextlib import closing, contextmanager
+import sys
+import six
+
+import pytest
+import py
+
+
+DEFAULT_LOG_FORMAT = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'
+DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'
+
+
+def get_option_ini(config, *names):
+    for name in names:
+        ret = config.getoption(name)  # 'default' arg won't work as expected
+        if ret is None:
+            ret = config.getini(name)
+        if ret:
+            return ret
+
+
+def pytest_addoption(parser):
+    """Add options to control log capturing."""
+    group = parser.getgroup('logging')
+
+    def add_option_ini(option, dest, default=None, type=None, **kwargs):
+        parser.addini(dest, default=default, type=type,
+                      help='default value for ' + option)
+        group.addoption(option, dest=dest, **kwargs)
+
+    add_option_ini(
+        '--no-print-logs',
+        dest='log_print', action='store_const', const=False, default=True,
+        type='bool',
+        help='disable printing caught logs on failed tests.')
+    add_option_ini(
+        '--log-level',
+        dest='log_level', default=None,
+        help='logging level used by the logging module')
+    add_option_ini(
+        '--log-format',
+        dest='log_format', default=DEFAULT_LOG_FORMAT,
+        help='log format as used by the logging module.')
+    add_option_ini(
+        '--log-date-format',
+        dest='log_date_format', default=DEFAULT_LOG_DATE_FORMAT,
+        help='log date format as used by the logging module.')
+    add_option_ini(
+        '--log-cli-level',
+        dest='log_cli_level', default=None,
+        help='cli logging level.')
+    add_option_ini(
+        '--log-cli-format',
+        dest='log_cli_format', default=None,
+        help='log format as used by the logging module.')
+    add_option_ini(
+        '--log-cli-date-format',
+        dest='log_cli_date_format', default=None,
+        help='log date format as used by the logging module.')
+    add_option_ini(
+        '--log-file',
+        dest='log_file', default=None,
+        help='path to a file when logging will be written to.')
+    add_option_ini(
+        '--log-file-level',
+        dest='log_file_level', default=None,
+        help='log file logging level.')
+    add_option_ini(
+        '--log-file-format',
+        dest='log_file_format', default=DEFAULT_LOG_FORMAT,
+        help='log format as used by the logging module.')
+    add_option_ini(
+        '--log-file-date-format',
+        dest='log_file_date_format', default=DEFAULT_LOG_DATE_FORMAT,
+        help='log date format as used by the logging module.')
+
+
+@contextmanager
+def logging_using_handler(handler, logger=None):
+    """Context manager that safely registers a given handler."""
+    logger = logger or logging.getLogger(logger)
+
+    if handler in logger.handlers:  # reentrancy
+        # Adding the same handler twice would confuse logging system.
+        # Just don't do that.
+        yield
+    else:
+        logger.addHandler(handler)
+        try:
+            yield
+        finally:
+            logger.removeHandler(handler)
+
+
+@contextmanager
+def catching_logs(handler, formatter=None,
+                  level=logging.NOTSET, logger=None):
+    """Context manager that prepares the whole logging machinery properly."""
+    logger = logger or logging.getLogger(logger)
+
+    if formatter is not None:
+        handler.setFormatter(formatter)
+    handler.setLevel(level)
+
+    with logging_using_handler(handler, logger):
+        orig_level = logger.level
+        logger.setLevel(min(orig_level, level))
+        try:
+            yield handler
+        finally:
+            logger.setLevel(orig_level)
+
+
+class LogCaptureHandler(logging.StreamHandler):
+    """A logging handler that stores log records and the log text."""
+
+    def __init__(self):
+        """Creates a new log handler."""
+        logging.StreamHandler.__init__(self, py.io.TextIO())
+        self.records = []
+
+    def emit(self, record):
+        """Keep the log records in a list in addition to the log text."""
+        self.records.append(record)
+        logging.StreamHandler.emit(self, record)
+
+
+class LogCaptureFixture(object):
+    """Provides access and control of log capturing."""
+
+    def __init__(self, item):
+        """Creates a new funcarg."""
+        self._item = item
+
+    @property
+    def handler(self):
+        return self._item.catch_log_handler
+
+    @property
+    def text(self):
+        """Returns the log text."""
+        return self.handler.stream.getvalue()
+
+    @property
+    def records(self):
+        """Returns the list of log records."""
+        return self.handler.records
+
+    @property
+    def record_tuples(self):
+        """Returns a list of a striped down version of log records intended
+        for use in assertion comparison.
+
+        The format of the tuple is:
+
+            (logger_name, log_level, message)
+        """
+        return [(r.name, r.levelno, r.getMessage()) for r in self.records]
+
+    def clear(self):
+        """Reset the list of log records."""
+        self.handler.records = []
+
+    def set_level(self, level, logger=None):
+        """Sets the level for capturing of logs.
+
+        By default, the level is set on the handler used to capture
+        logs. Specify a logger name to instead set the level of any
+        logger.
+        """
+        if logger is None:
+            logger = self.handler
+        else:
+            logger = logging.getLogger(logger)
+        logger.setLevel(level)
+
+    @contextmanager
+    def at_level(self, level, logger=None):
+        """Context manager that sets the level for capturing of logs.
+
+        By default, the level is set on the handler used to capture
+        logs. Specify a logger name to instead set the level of any
+        logger.
+        """
+        if logger is None:
+            logger = self.handler
+        else:
+            logger = logging.getLogger(logger)
+
+        orig_level = logger.level
+        logger.setLevel(level)
+        try:
+            yield
+        finally:
+            logger.setLevel(orig_level)
+
+
+@pytest.fixture
+def caplog(request):
+    """Access and control log capturing.
+
+    Captured logs are available through the following methods::
+
+    * caplog.text()          -> string containing formatted log output
+    * caplog.records()       -> list of logging.LogRecord instances
+    * caplog.record_tuples() -> list of (logger_name, level, message) tuples
+    """
+    return LogCaptureFixture(request.node)
+
+
+def get_actual_log_level(config, *setting_names):
+    """Return the actual logging level."""
+
+    for setting_name in setting_names:
+        log_level = config.getoption(setting_name)
+        if log_level is None:
+            log_level = config.getini(setting_name)
+        if log_level:
+            break
+    else:
+        return
+
+    if isinstance(log_level, six.string_types):
+        log_level = log_level.upper()
+    try:
+        return int(getattr(logging, log_level, log_level))
+    except ValueError:
+        # Python logging does not recognise this as a logging level
+        raise pytest.UsageError(
+            "'{0}' is not recognized as a logging level name for "
+            "'{1}'. Please consider passing the "
+            "logging level num instead.".format(
+                log_level,
+                setting_name))
+
+
+def pytest_configure(config):
+    config.pluginmanager.register(LoggingPlugin(config),
+                                  'logging-plugin')
+
+
+class LoggingPlugin(object):
+    """Attaches to the logging module and captures log messages for each test.
+    """
+
+    def __init__(self, config):
+        """Creates a new plugin to capture log messages.
+
+        The formatter can be safely shared across all handlers so
+        create a single one for the entire test session here.
+        """
+        self.log_cli_level = get_actual_log_level(
+            config, 'log_cli_level', 'log_level') or logging.WARNING
+
+        self.print_logs = get_option_ini(config, 'log_print')
+        self.formatter = logging.Formatter(
+            get_option_ini(config, 'log_format'),
+            get_option_ini(config, 'log_date_format'))
+
+        log_cli_handler = logging.StreamHandler(sys.stderr)
+        log_cli_format = get_option_ini(
+            config, 'log_cli_format', 'log_format')
+        log_cli_date_format = get_option_ini(
+            config, 'log_cli_date_format', 'log_date_format')
+        log_cli_formatter = logging.Formatter(
+            log_cli_format,
+            datefmt=log_cli_date_format)
+        self.log_cli_handler = log_cli_handler  # needed for a single unittest
+        self.live_logs = catching_logs(log_cli_handler,
+                                       formatter=log_cli_formatter,
+                                       level=self.log_cli_level)
+
+        log_file = get_option_ini(config, 'log_file')
+        if log_file:
+            self.log_file_level = get_actual_log_level(
+                config, 'log_file_level') or logging.WARNING
+
+            log_file_format = get_option_ini(
+                config, 'log_file_format', 'log_format')
+            log_file_date_format = get_option_ini(
+                config, 'log_file_date_format', 'log_date_format')
+            self.log_file_handler = logging.FileHandler(
+                log_file,
+                # Each pytest runtests session will write to a clean logfile
+                mode='w')
+            log_file_formatter = logging.Formatter(
+                log_file_format,
+                datefmt=log_file_date_format)
+            self.log_file_handler.setFormatter(log_file_formatter)
+        else:
+            self.log_file_handler = None
+
+    @contextmanager
+    def _runtest_for(self, item, when):
+        """Implements the internals of pytest_runtest_xxx() hook."""
+        with catching_logs(LogCaptureHandler(),
+                           formatter=self.formatter) as log_handler:
+            item.catch_log_handler = log_handler
+            try:
+                yield  # run test
+            finally:
+                del item.catch_log_handler
+
+            if self.print_logs:
+                # Add a captured log section to the report.
+                log = log_handler.stream.getvalue().strip()
+                item.add_report_section(when, 'log', log)
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_runtest_setup(self, item):
+        with self._runtest_for(item, 'setup'):
+            yield
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_runtest_call(self, item):
+        with self._runtest_for(item, 'call'):
+            yield
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_runtest_teardown(self, item):
+        with self._runtest_for(item, 'teardown'):
+            yield
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_runtestloop(self, session):
+        """Runs all collected test items."""
+        with self.live_logs:
+            if self.log_file_handler is not None:
+                with closing(self.log_file_handler):
+                    with catching_logs(self.log_file_handler,
+                                       level=self.log_file_level):
+                        yield  # run all the tests
+            else:
+                yield  # run all the tests
diff --git a/tools/third_party/pytest/_pytest/main.py b/tools/third_party/pytest/_pytest/main.py
new file mode 100644
index 0000000..2555409
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/main.py
@@ -0,0 +1,821 @@
+""" core implementation of testing process: init, session, runtest loop. """
+from __future__ import absolute_import, division, print_function
+
+import functools
+import os
+import six
+import sys
+
+import _pytest
+from _pytest import nodes
+import _pytest._code
+import py
+try:
+    from collections import MutableMapping as MappingMixin
+except ImportError:
+    from UserDict import DictMixin as MappingMixin
+
+from _pytest.config import directory_arg, UsageError, hookimpl
+from _pytest.outcomes import exit
+from _pytest.runner import collect_one_node
+
+tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
+
+# exitcodes for the command line
+EXIT_OK = 0
+EXIT_TESTSFAILED = 1
+EXIT_INTERRUPTED = 2
+EXIT_INTERNALERROR = 3
+EXIT_USAGEERROR = 4
+EXIT_NOTESTSCOLLECTED = 5
+
+
+def pytest_addoption(parser):
+    parser.addini("norecursedirs", "directory patterns to avoid for recursion",
+                  type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'])
+    parser.addini("testpaths", "directories to search for tests when no files or directories are given in the "
+                               "command line.",
+                  type="args", default=[])
+    # parser.addini("dirpatterns",
+    #    "patterns specifying possible locations of test files",
+    #    type="linelist", default=["**/test_*.txt",
+    #            "**/test_*.py", "**/*_test.py"]
+    # )
+    group = parser.getgroup("general", "running and selection options")
+    group._addoption('-x', '--exitfirst', action="store_const",
+                     dest="maxfail", const=1,
+                     help="exit instantly on first error or failed test."),
+    group._addoption('--maxfail', metavar="num",
+                     action="store", type=int, dest="maxfail", default=0,
+                     help="exit after first num failures or errors.")
+    group._addoption('--strict', action="store_true",
+                     help="marks not registered in configuration file raise errors.")
+    group._addoption("-c", metavar="file", type=str, dest="inifilename",
+                     help="load configuration from `file` instead of trying to locate one of the implicit "
+                          "configuration files.")
+    group._addoption("--continue-on-collection-errors", action="store_true",
+                     default=False, dest="continue_on_collection_errors",
+                     help="Force test execution even if collection errors occur.")
+
+    group = parser.getgroup("collect", "collection")
+    group.addoption('--collectonly', '--collect-only', action="store_true",
+                    help="only collect tests, don't execute them."),
+    group.addoption('--pyargs', action="store_true",
+                    help="try to interpret all arguments as python packages.")
+    group.addoption("--ignore", action="append", metavar="path",
+                    help="ignore path during collection (multi-allowed).")
+    # when changing this to --conf-cut-dir, config.py Conftest.setinitial
+    # needs upgrading as well
+    group.addoption('--confcutdir', dest="confcutdir", default=None,
+                    metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"),
+                    help="only load conftest.py's relative to specified dir.")
+    group.addoption('--noconftest', action="store_true",
+                    dest="noconftest", default=False,
+                    help="Don't load any conftest.py files.")
+    group.addoption('--keepduplicates', '--keep-duplicates', action="store_true",
+                    dest="keepduplicates", default=False,
+                    help="Keep duplicate tests.")
+    group.addoption('--collect-in-virtualenv', action='store_true',
+                    dest='collect_in_virtualenv', default=False,
+                    help="Don't ignore tests in a local virtualenv directory")
+
+    group = parser.getgroup("debugconfig",
+                            "test session debugging and configuration")
+    group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
+                    help="base temporary directory for this test run.")
+
+
+def pytest_configure(config):
+    __import__('pytest').config = config  # compatibiltiy
+
+
+def wrap_session(config, doit):
+    """Skeleton command line program"""
+    session = Session(config)
+    session.exitstatus = EXIT_OK
+    initstate = 0
+    try:
+        try:
+            config._do_configure()
+            initstate = 1
+            config.hook.pytest_sessionstart(session=session)
+            initstate = 2
+            session.exitstatus = doit(config, session) or 0
+        except UsageError:
+            raise
+        except Failed:
+            session.exitstatus = EXIT_TESTSFAILED
+        except KeyboardInterrupt:
+            excinfo = _pytest._code.ExceptionInfo()
+            if initstate < 2 and isinstance(excinfo.value, exit.Exception):
+                sys.stderr.write('{0}: {1}\n'.format(
+                    excinfo.typename, excinfo.value.msg))
+            config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
+            session.exitstatus = EXIT_INTERRUPTED
+        except:  # noqa
+            excinfo = _pytest._code.ExceptionInfo()
+            config.notify_exception(excinfo, config.option)
+            session.exitstatus = EXIT_INTERNALERROR
+            if excinfo.errisinstance(SystemExit):
+                sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
+
+    finally:
+        excinfo = None  # Explicitly break reference cycle.
+        session.startdir.chdir()
+        if initstate >= 2:
+            config.hook.pytest_sessionfinish(
+                session=session,
+                exitstatus=session.exitstatus)
+        config._ensure_unconfigure()
+    return session.exitstatus
+
+
+def pytest_cmdline_main(config):
+    return wrap_session(config, _main)
+
+
+def _main(config, session):
+    """ default command line protocol for initialization, session,
+    running tests and reporting. """
+    config.hook.pytest_collection(session=session)
+    config.hook.pytest_runtestloop(session=session)
+
+    if session.testsfailed:
+        return EXIT_TESTSFAILED
+    elif session.testscollected == 0:
+        return EXIT_NOTESTSCOLLECTED
+
+
+def pytest_collection(session):
+    return session.perform_collect()
+
+
+def pytest_runtestloop(session):
+    if (session.testsfailed and
+            not session.config.option.continue_on_collection_errors):
+        raise session.Interrupted(
+            "%d errors during collection" % session.testsfailed)
+
+    if session.config.option.collectonly:
+        return True
+
+    for i, item in enumerate(session.items):
+        nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
+        item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
+        if session.shouldfail:
+            raise session.Failed(session.shouldfail)
+        if session.shouldstop:
+            raise session.Interrupted(session.shouldstop)
+    return True
+
+
+def _in_venv(path):
+    """Attempts to detect if ``path`` is the root of a Virtual Environment by
+    checking for the existence of the appropriate activate script"""
+    bindir = path.join('Scripts' if sys.platform.startswith('win') else 'bin')
+    if not bindir.exists():
+        return False
+    activates = ('activate', 'activate.csh', 'activate.fish',
+                 'Activate', 'Activate.bat', 'Activate.ps1')
+    return any([fname.basename in activates for fname in bindir.listdir()])
+
+
+def pytest_ignore_collect(path, config):
+    ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
+    ignore_paths = ignore_paths or []
+    excludeopt = config.getoption("ignore")
+    if excludeopt:
+        ignore_paths.extend([py.path.local(x) for x in excludeopt])
+
+    if py.path.local(path) in ignore_paths:
+        return True
+
+    allow_in_venv = config.getoption("collect_in_virtualenv")
+    if _in_venv(path) and not allow_in_venv:
+        return True
+
+    # Skip duplicate paths.
+    keepduplicates = config.getoption("keepduplicates")
+    duplicate_paths = config.pluginmanager._duplicatepaths
+    if not keepduplicates:
+        if path in duplicate_paths:
+            return True
+        else:
+            duplicate_paths.add(path)
+
+    return False
+
+
+class FSHookProxy:
+    def __init__(self, fspath, pm, remove_mods):
+        self.fspath = fspath
+        self.pm = pm
+        self.remove_mods = remove_mods
+
+    def __getattr__(self, name):
+        x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
+        self.__dict__[name] = x
+        return x
+
+
+class _CompatProperty(object):
+    def __init__(self, name):
+        self.name = name
+
+    def __get__(self, obj, owner):
+        if obj is None:
+            return self
+
+        # TODO: reenable in the features branch
+        # warnings.warn(
+        #     "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
+        #         name=self.name, owner=type(owner).__name__),
+        #     PendingDeprecationWarning, stacklevel=2)
+        return getattr(__import__('pytest'), self.name)
+
+
+class NodeKeywords(MappingMixin):
+    def __init__(self, node):
+        self.node = node
+        self.parent = node.parent
+        self._markers = {node.name: True}
+
+    def __getitem__(self, key):
+        try:
+            return self._markers[key]
+        except KeyError:
+            if self.parent is None:
+                raise
+            return self.parent.keywords[key]
+
+    def __setitem__(self, key, value):
+        self._markers[key] = value
+
+    def __delitem__(self, key):
+        raise ValueError("cannot delete key in keywords dict")
+
+    def __iter__(self):
+        seen = set(self._markers)
+        if self.parent is not None:
+            seen.update(self.parent.keywords)
+        return iter(seen)
+
+    def __len__(self):
+        return len(self.__iter__())
+
+    def keys(self):
+        return list(self)
+
+    def __repr__(self):
+        return "<NodeKeywords for node %s>" % (self.node, )
+
+
+class Node(object):
+    """ base class for Collector and Item the test collection tree.
+    Collector subclasses have children, Items are terminal nodes."""
+
+    def __init__(self, name, parent=None, config=None, session=None):
+        #: a unique name within the scope of the parent node
+        self.name = name
+
+        #: the parent collector node.
+        self.parent = parent
+
+        #: the pytest config object
+        self.config = config or parent.config
+
+        #: the session this node is part of
+        self.session = session or parent.session
+
+        #: filesystem path where this node was collected from (can be None)
+        self.fspath = getattr(parent, 'fspath', None)
+
+        #: keywords/markers collected from all scopes
+        self.keywords = NodeKeywords(self)
+
+        #: allow adding of extra keywords to use for matching
+        self.extra_keyword_matches = set()
+
+        # used for storing artificial fixturedefs for direct parametrization
+        self._name2pseudofixturedef = {}
+
+    @property
+    def ihook(self):
+        """ fspath sensitive hook proxy used to call pytest hooks"""
+        return self.session.gethookproxy(self.fspath)
+
+    Module = _CompatProperty("Module")
+    Class = _CompatProperty("Class")
+    Instance = _CompatProperty("Instance")
+    Function = _CompatProperty("Function")
+    File = _CompatProperty("File")
+    Item = _CompatProperty("Item")
+
+    def _getcustomclass(self, name):
+        maybe_compatprop = getattr(type(self), name)
+        if isinstance(maybe_compatprop, _CompatProperty):
+            return getattr(__import__('pytest'), name)
+        else:
+            cls = getattr(self, name)
+            # TODO: reenable in the features branch
+            # warnings.warn("use of node.%s is deprecated, "
+            #    "use pytest_pycollect_makeitem(...) to create custom "
+            #    "collection nodes" % name, category=DeprecationWarning)
+        return cls
+
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__,
+                            getattr(self, 'name', None))
+
+    def warn(self, code, message):
+        """ generate a warning with the given code and message for this
+        item. """
+        assert isinstance(code, str)
+        fslocation = getattr(self, "location", None)
+        if fslocation is None:
+            fslocation = getattr(self, "fspath", None)
+        self.ihook.pytest_logwarning.call_historic(kwargs=dict(
+            code=code, message=message,
+            nodeid=self.nodeid, fslocation=fslocation))
+
+    # methods for ordering nodes
+    @property
+    def nodeid(self):
+        """ a ::-separated string denoting its collection tree address. """
+        try:
+            return self._nodeid
+        except AttributeError:
+            self._nodeid = x = self._makeid()
+            return x
+
+    def _makeid(self):
+        return self.parent.nodeid + "::" + self.name
+
+    def __hash__(self):
+        return hash(self.nodeid)
+
+    def setup(self):
+        pass
+
+    def teardown(self):
+        pass
+
+    def listchain(self):
+        """ return list of all parent collectors up to self,
+            starting from root of collection tree. """
+        chain = []
+        item = self
+        while item is not None:
+            chain.append(item)
+            item = item.parent
+        chain.reverse()
+        return chain
+
+    def add_marker(self, marker):
+        """ dynamically add a marker object to the node.
+
+        ``marker`` can be a string or pytest.mark.* instance.
+        """
+        from _pytest.mark import MarkDecorator, MARK_GEN
+        if isinstance(marker, six.string_types):
+            marker = getattr(MARK_GEN, marker)
+        elif not isinstance(marker, MarkDecorator):
+            raise ValueError("is not a string or pytest.mark.* Marker")
+        self.keywords[marker.name] = marker
+
+    def get_marker(self, name):
+        """ get a marker object from this node or None if
+        the node doesn't have a marker with that name. """
+        val = self.keywords.get(name, None)
+        if val is not None:
+            from _pytest.mark import MarkInfo, MarkDecorator
+            if isinstance(val, (MarkDecorator, MarkInfo)):
+                return val
+
+    def listextrakeywords(self):
+        """ Return a set of all extra keywords in self and any parents."""
+        extra_keywords = set()
+        item = self
+        for item in self.listchain():
+            extra_keywords.update(item.extra_keyword_matches)
+        return extra_keywords
+
+    def listnames(self):
+        return [x.name for x in self.listchain()]
+
+    def addfinalizer(self, fin):
+        """ register a function to be called when this node is finalized.
+
+        This method can only be called when this node is active
+        in a setup chain, for example during self.setup().
+        """
+        self.session._setupstate.addfinalizer(fin, self)
+
+    def getparent(self, cls):
+        """ get the next parent node (including ourself)
+        which is an instance of the given class"""
+        current = self
+        while current and not isinstance(current, cls):
+            current = current.parent
+        return current
+
+    def _prunetraceback(self, excinfo):
+        pass
+
+    def _repr_failure_py(self, excinfo, style=None):
+        fm = self.session._fixturemanager
+        if excinfo.errisinstance(fm.FixtureLookupError):
+            return excinfo.value.formatrepr()
+        tbfilter = True
+        if self.config.option.fulltrace:
+            style = "long"
+        else:
+            tb = _pytest._code.Traceback([excinfo.traceback[-1]])
+            self._prunetraceback(excinfo)
+            if len(excinfo.traceback) == 0:
+                excinfo.traceback = tb
+            tbfilter = False  # prunetraceback already does it
+            if style == "auto":
+                style = "long"
+        # XXX should excinfo.getrepr record all data and toterminal() process it?
+        if style is None:
+            if self.config.option.tbstyle == "short":
+                style = "short"
+            else:
+                style = "long"
+
+        try:
+            os.getcwd()
+            abspath = False
+        except OSError:
+            abspath = True
+
+        return excinfo.getrepr(funcargs=True, abspath=abspath,
+                               showlocals=self.config.option.showlocals,
+                               style=style, tbfilter=tbfilter)
+
+    repr_failure = _repr_failure_py
+
+
+class Collector(Node):
+    """ Collector instances create children through collect()
+        and thus iteratively build a tree.
+    """
+
+    class CollectError(Exception):
+        """ an error during collection, contains a custom message. """
+
+    def collect(self):
+        """ returns a list of children (items and collectors)
+            for this collection node.
+        """
+        raise NotImplementedError("abstract")
+
+    def repr_failure(self, excinfo):
+        """ represent a collection failure. """
+        if excinfo.errisinstance(self.CollectError):
+            exc = excinfo.value
+            return str(exc.args[0])
+        return self._repr_failure_py(excinfo, style="short")
+
+    def _prunetraceback(self, excinfo):
+        if hasattr(self, 'fspath'):
+            traceback = excinfo.traceback
+            ntraceback = traceback.cut(path=self.fspath)
+            if ntraceback == traceback:
+                ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
+            excinfo.traceback = ntraceback.filter()
+
+
+class FSCollector(Collector):
+    def __init__(self, fspath, parent=None, config=None, session=None):
+        fspath = py.path.local(fspath)  # xxx only for test_resultlog.py?
+        name = fspath.basename
+        if parent is not None:
+            rel = fspath.relto(parent.fspath)
+            if rel:
+                name = rel
+            name = name.replace(os.sep, nodes.SEP)
+        super(FSCollector, self).__init__(name, parent, config, session)
+        self.fspath = fspath
+
+    def _check_initialpaths_for_relpath(self):
+        for initialpath in self.session._initialpaths:
+            if self.fspath.common(initialpath) == initialpath:
+                return self.fspath.relto(initialpath.dirname)
+
+    def _makeid(self):
+        relpath = self.fspath.relto(self.config.rootdir)
+
+        if not relpath:
+            relpath = self._check_initialpaths_for_relpath()
+        if os.sep != nodes.SEP:
+            relpath = relpath.replace(os.sep, nodes.SEP)
+        return relpath
+
+
+class File(FSCollector):
+    """ base class for collecting tests from a file. """
+
+
+class Item(Node):
+    """ a basic test invocation item. Note that for a single function
+    there might be multiple test invocation items.
+    """
+    nextitem = None
+
+    def __init__(self, name, parent=None, config=None, session=None):
+        super(Item, self).__init__(name, parent, config, session)
+        self._report_sections = []
+
+    def add_report_section(self, when, key, content):
+        """
+        Adds a new report section, similar to what's done internally to add stdout and
+        stderr captured output::
+
+            item.add_report_section("call", "stdout", "report section contents")
+
+        :param str when:
+            One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
+        :param str key:
+            Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
+            ``"stderr"`` internally.
+
+        :param str content:
+            The full contents as a string.
+        """
+        if content:
+            self._report_sections.append((when, key, content))
+
+    def reportinfo(self):
+        return self.fspath, None, ""
+
+    @property
+    def location(self):
+        try:
+            return self._location
+        except AttributeError:
+            location = self.reportinfo()
+            # bestrelpath is a quite slow function
+            cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
+            try:
+                fspath = cache[location[0]]
+            except KeyError:
+                fspath = self.session.fspath.bestrelpath(location[0])
+                cache[location[0]] = fspath
+            location = (fspath, location[1], str(location[2]))
+            self._location = location
+            return location
+
+
+class NoMatch(Exception):
+    """ raised if matching cannot locate a matching names. """
+
+
+class Interrupted(KeyboardInterrupt):
+    """ signals an interrupted test run. """
+    __module__ = 'builtins'  # for py3
+
+
+class Failed(Exception):
+    """ signals an stop as failed test run. """
+
+
+class Session(FSCollector):
+    Interrupted = Interrupted
+    Failed = Failed
+
+    def __init__(self, config):
+        FSCollector.__init__(self, config.rootdir, parent=None,
+                             config=config, session=self)
+        self.testsfailed = 0
+        self.testscollected = 0
+        self.shouldstop = False
+        self.shouldfail = False
+        self.trace = config.trace.root.get("collection")
+        self._norecursepatterns = config.getini("norecursedirs")
+        self.startdir = py.path.local()
+        self.config.pluginmanager.register(self, name="session")
+
+    def _makeid(self):
+        return ""
+
+    @hookimpl(tryfirst=True)
+    def pytest_collectstart(self):
+        if self.shouldfail:
+            raise self.Failed(self.shouldfail)
+        if self.shouldstop:
+            raise self.Interrupted(self.shouldstop)
+
+    @hookimpl(tryfirst=True)
+    def pytest_runtest_logreport(self, report):
+        if report.failed and not hasattr(report, 'wasxfail'):
+            self.testsfailed += 1
+            maxfail = self.config.getvalue("maxfail")
+            if maxfail and self.testsfailed >= maxfail:
+                self.shouldfail = "stopping after %d failures" % (
+                    self.testsfailed)
+    pytest_collectreport = pytest_runtest_logreport
+
+    def isinitpath(self, path):
+        return path in self._initialpaths
+
+    def gethookproxy(self, fspath):
+        # check if we have the common case of running
+        # hooks with all conftest.py filesall conftest.py
+        pm = self.config.pluginmanager
+        my_conftestmodules = pm._getconftestmodules(fspath)
+        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
+        if remove_mods:
+            # one or more conftests are not in use at this fspath
+            proxy = FSHookProxy(fspath, pm, remove_mods)
+        else:
+            # all plugis are active for this fspath
+            proxy = self.config.hook
+        return proxy
+
+    def perform_collect(self, args=None, genitems=True):
+        hook = self.config.hook
+        try:
+            items = self._perform_collect(args, genitems)
+            self.config.pluginmanager.check_pending()
+            hook.pytest_collection_modifyitems(session=self,
+                                               config=self.config, items=items)
+        finally:
+            hook.pytest_collection_finish(session=self)
+        self.testscollected = len(items)
+        return items
+
+    def _perform_collect(self, args, genitems):
+        if args is None:
+            args = self.config.args
+        self.trace("perform_collect", self, args)
+        self.trace.root.indent += 1
+        self._notfound = []
+        self._initialpaths = set()
+        self._initialparts = []
+        self.items = items = []
+        for arg in args:
+            parts = self._parsearg(arg)
+            self._initialparts.append(parts)
+            self._initialpaths.add(parts[0])
+        rep = collect_one_node(self)
+        self.ihook.pytest_collectreport(report=rep)
+        self.trace.root.indent -= 1
+        if self._notfound:
+            errors = []
+            for arg, exc in self._notfound:
+                line = "(no name %r in any of %r)" % (arg, exc.args[0])
+                errors.append("not found: %s\n%s" % (arg, line))
+                # XXX: test this
+            raise UsageError(*errors)
+        if not genitems:
+            return rep.result
+        else:
+            if rep.passed:
+                for node in rep.result:
+                    self.items.extend(self.genitems(node))
+            return items
+
+    def collect(self):
+        for parts in self._initialparts:
+            arg = "::".join(map(str, parts))
+            self.trace("processing argument", arg)
+            self.trace.root.indent += 1
+            try:
+                for x in self._collect(arg):
+                    yield x
+            except NoMatch:
+                # we are inside a make_report hook so
+                # we cannot directly pass through the exception
+                self._notfound.append((arg, sys.exc_info()[1]))
+
+            self.trace.root.indent -= 1
+
+    def _collect(self, arg):
+        names = self._parsearg(arg)
+        path = names.pop(0)
+        if path.check(dir=1):
+            assert not names, "invalid arg %r" % (arg,)
+            for path in path.visit(fil=lambda x: x.check(file=1),
+                                   rec=self._recurse, bf=True, sort=True):
+                for x in self._collectfile(path):
+                    yield x
+        else:
+            assert path.check(file=1)
+            for x in self.matchnodes(self._collectfile(path), names):
+                yield x
+
+    def _collectfile(self, path):
+        ihook = self.gethookproxy(path)
+        if not self.isinitpath(path):
+            if ihook.pytest_ignore_collect(path=path, config=self.config):
+                return ()
+        return ihook.pytest_collect_file(path=path, parent=self)
+
+    def _recurse(self, path):
+        ihook = self.gethookproxy(path.dirpath())
+        if ihook.pytest_ignore_collect(path=path, config=self.config):
+            return
+        for pat in self._norecursepatterns:
+            if path.check(fnmatch=pat):
+                return False
+        ihook = self.gethookproxy(path)
+        ihook.pytest_collect_directory(path=path, parent=self)
+        return True
+
+    def _tryconvertpyarg(self, x):
+        """Convert a dotted module name to path.
+
+        """
+        import pkgutil
+        try:
+            loader = pkgutil.find_loader(x)
+        except ImportError:
+            return x
+        if loader is None:
+            return x
+        # This method is sometimes invoked when AssertionRewritingHook, which
+        # does not define a get_filename method, is already in place:
+        try:
+            path = loader.get_filename(x)
+        except AttributeError:
+            # Retrieve path from AssertionRewritingHook:
+            path = loader.modules[x][0].co_filename
+        if loader.is_package(x):
+            path = os.path.dirname(path)
+        return path
+
+    def _parsearg(self, arg):
+        """ return (fspath, names) tuple after checking the file exists. """
+        parts = str(arg).split("::")
+        if self.config.option.pyargs:
+            parts[0] = self._tryconvertpyarg(parts[0])
+        relpath = parts[0].replace("/", os.sep)
+        path = self.config.invocation_dir.join(relpath, abs=True)
+        if not path.check():
+            if self.config.option.pyargs:
+                raise UsageError(
+                    "file or package not found: " + arg +
+                    " (missing __init__.py?)")
+            else:
+                raise UsageError("file not found: " + arg)
+        parts[0] = path
+        return parts
+
+    def matchnodes(self, matching, names):
+        self.trace("matchnodes", matching, names)
+        self.trace.root.indent += 1
+        nodes = self._matchnodes(matching, names)
+        num = len(nodes)
+        self.trace("matchnodes finished -> ", num, "nodes")
+        self.trace.root.indent -= 1
+        if num == 0:
+            raise NoMatch(matching, names[:1])
+        return nodes
+
+    def _matchnodes(self, matching, names):
+        if not matching or not names:
+            return matching
+        name = names[0]
+        assert name
+        nextnames = names[1:]
+        resultnodes = []
+        for node in matching:
+            if isinstance(node, Item):
+                if not names:
+                    resultnodes.append(node)
+                continue
+            assert isinstance(node, Collector)
+            rep = collect_one_node(node)
+            if rep.passed:
+                has_matched = False
+                for x in rep.result:
+                    # TODO: remove parametrized workaround once collection structure contains parametrization
+                    if x.name == name or x.name.split("[")[0] == name:
+                        resultnodes.extend(self.matchnodes([x], nextnames))
+                        has_matched = True
+                # XXX accept IDs that don't have "()" for class instances
+                if not has_matched and len(rep.result) == 1 and x.name == "()":
+                    nextnames.insert(0, name)
+                    resultnodes.extend(self.matchnodes([x], nextnames))
+            else:
+                # report collection failures here to avoid failing to run some test
+                # specified in the command line because the module could not be
+                # imported (#134)
+                node.ihook.pytest_collectreport(report=rep)
+        return resultnodes
+
+    def genitems(self, node):
+        self.trace("genitems", node)
+        if isinstance(node, Item):
+            node.ihook.pytest_itemcollected(item=node)
+            yield node
+        else:
+            assert isinstance(node, Collector)
+            rep = collect_one_node(node)
+            if rep.passed:
+                for subnode in rep.result:
+                    for x in self.genitems(subnode):
+                        yield x
+            node.ihook.pytest_collectreport(report=rep)
diff --git a/tools/third_party/pytest/_pytest/mark.py b/tools/third_party/pytest/_pytest/mark.py
new file mode 100644
index 0000000..3f1f01b
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/mark.py
@@ -0,0 +1,496 @@
+""" generic mechanism for marking and selecting python functions. """
+from __future__ import absolute_import, division, print_function
+
+import inspect
+import warnings
+import attr
+from collections import namedtuple
+from operator import attrgetter
+from six.moves import map
+from .deprecated import MARK_PARAMETERSET_UNPACKING
+from .compat import NOTSET, getfslineno
+
+
+def alias(name, warning=None):
+    getter = attrgetter(name)
+
+    def warned(self):
+        warnings.warn(warning, stacklevel=2)
+        return getter(self)
+
+    return property(getter if warning is None else warned, doc='alias for ' + name)
+
+
+class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
+    @classmethod
+    def param(cls, *values, **kw):
+        marks = kw.pop('marks', ())
+        if isinstance(marks, MarkDecorator):
+            marks = marks,
+        else:
+            assert isinstance(marks, (tuple, list, set))
+
+        def param_extract_id(id=None):
+            return id
+
+        id = param_extract_id(**kw)
+        return cls(values, marks, id)
+
+    @classmethod
+    def extract_from(cls, parameterset, legacy_force_tuple=False):
+        """
+        :param parameterset:
+            a legacy style parameterset that may or may not be a tuple,
+            and may or may not be wrapped into a mess of mark objects
+
+        :param legacy_force_tuple:
+            enforce tuple wrapping so single argument tuple values
+            don't get decomposed and break tests
+
+        """
+
+        if isinstance(parameterset, cls):
+            return parameterset
+        if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
+            return cls.param(parameterset)
+
+        newmarks = []
+        argval = parameterset
+        while isinstance(argval, MarkDecorator):
+            newmarks.append(MarkDecorator(Mark(
+                argval.markname, argval.args[:-1], argval.kwargs)))
+            argval = argval.args[-1]
+        assert not isinstance(argval, ParameterSet)
+        if legacy_force_tuple:
+            argval = argval,
+
+        if newmarks:
+            warnings.warn(MARK_PARAMETERSET_UNPACKING)
+
+        return cls(argval, marks=newmarks, id=None)
+
+    @classmethod
+    def _for_parameterize(cls, argnames, argvalues, function):
+        if not isinstance(argnames, (tuple, list)):
+            argnames = [x.strip() for x in argnames.split(",") if x.strip()]
+            force_tuple = len(argnames) == 1
+        else:
+            force_tuple = False
+        parameters = [
+            ParameterSet.extract_from(x, legacy_force_tuple=force_tuple)
+            for x in argvalues]
+        del argvalues
+
+        if not parameters:
+            fs, lineno = getfslineno(function)
+            reason = "got empty parameter set %r, function %s at %s:%d" % (
+                argnames, function.__name__, fs, lineno)
+            mark = MARK_GEN.skip(reason=reason)
+            parameters.append(ParameterSet(
+                values=(NOTSET,) * len(argnames),
+                marks=[mark],
+                id=None,
+            ))
+        return argnames, parameters
+
+
+class MarkerError(Exception):
+
+    """Error in use of a pytest marker/attribute."""
+
+
+def param(*values, **kw):
+    return ParameterSet.param(*values, **kw)
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group._addoption(
+        '-k',
+        action="store", dest="keyword", default='', metavar="EXPRESSION",
+        help="only run tests which match the given substring expression. "
+             "An expression is a python evaluatable expression "
+             "where all names are substring-matched against test names "
+             "and their parent classes. Example: -k 'test_method or test_"
+             "other' matches all test functions and classes whose name "
+             "contains 'test_method' or 'test_other', while -k 'not test_method' "
+             "matches those that don't contain 'test_method' in their names. "
+             "Additionally keywords are matched to classes and functions "
+             "containing extra names in their 'extra_keyword_matches' set, "
+             "as well as functions which have names assigned directly to them."
+    )
+
+    group._addoption(
+        "-m",
+        action="store", dest="markexpr", default="", metavar="MARKEXPR",
+        help="only run tests matching given mark expression.  "
+             "example: -m 'mark1 and not mark2'."
+    )
+
+    group.addoption(
+        "--markers", action="store_true",
+        help="show markers (builtin, plugin and per-project ones)."
+    )
+
+    parser.addini("markers", "markers for test functions", 'linelist')
+
+
+def pytest_cmdline_main(config):
+    import _pytest.config
+    if config.option.markers:
+        config._do_configure()
+        tw = _pytest.config.create_terminal_writer(config)
+        for line in config.getini("markers"):
+            parts = line.split(":", 1)
+            name = parts[0]
+            rest = parts[1] if len(parts) == 2 else ''
+            tw.write("@pytest.mark.%s:" % name, bold=True)
+            tw.line(rest)
+            tw.line()
+        config._ensure_unconfigure()
+        return 0
+
+
+pytest_cmdline_main.tryfirst = True
+
+
+def pytest_collection_modifyitems(items, config):
+    keywordexpr = config.option.keyword.lstrip()
+    matchexpr = config.option.markexpr
+    if not keywordexpr and not matchexpr:
+        return
+    # pytest used to allow "-" for negating
+    # but today we just allow "-" at the beginning, use "not" instead
+    # we probably remove "-" altogether soon
+    if keywordexpr.startswith("-"):
+        keywordexpr = "not " + keywordexpr[1:]
+    selectuntil = False
+    if keywordexpr[-1:] == ":":
+        selectuntil = True
+        keywordexpr = keywordexpr[:-1]
+
+    remaining = []
+    deselected = []
+    for colitem in items:
+        if keywordexpr and not matchkeyword(colitem, keywordexpr):
+            deselected.append(colitem)
+        else:
+            if selectuntil:
+                keywordexpr = None
+            if matchexpr:
+                if not matchmark(colitem, matchexpr):
+                    deselected.append(colitem)
+                    continue
+            remaining.append(colitem)
+
+    if deselected:
+        config.hook.pytest_deselected(items=deselected)
+        items[:] = remaining
+
+
+@attr.s
+class MarkMapping(object):
+    """Provides a local mapping for markers where item access
+    resolves to True if the marker is present. """
+
+    own_mark_names = attr.ib()
+
+    @classmethod
+    def from_keywords(cls, keywords):
+        mark_names = set()
+        for key, value in keywords.items():
+            if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
+                mark_names.add(key)
+        return cls(mark_names)
+
+    def __getitem__(self, name):
+        return name in self.own_mark_names
+
+
+class KeywordMapping(object):
+    """Provides a local mapping for keywords.
+    Given a list of names, map any substring of one of these names to True.
+    """
+
+    def __init__(self, names):
+        self._names = names
+
+    def __getitem__(self, subname):
+        for name in self._names:
+            if subname in name:
+                return True
+        return False
+
+
+def matchmark(colitem, markexpr):
+    """Tries to match on any marker names, attached to the given colitem."""
+    return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords))
+
+
+def matchkeyword(colitem, keywordexpr):
+    """Tries to match given keyword expression to given collector item.
+
+    Will match on the name of colitem, including the names of its parents.
+    Only matches names of items which are either a :class:`Class` or a
+    :class:`Function`.
+    Additionally, matches on names in the 'extra_keyword_matches' set of
+    any item, as well as names directly assigned to test functions.
+    """
+    mapped_names = set()
+
+    # Add the names of the current item and any parent items
+    import pytest
+    for item in colitem.listchain():
+        if not isinstance(item, pytest.Instance):
+            mapped_names.add(item.name)
+
+    # Add the names added as extra keywords to current or parent items
+    for name in colitem.listextrakeywords():
+        mapped_names.add(name)
+
+    # Add the names attached to the current function through direct assignment
+    if hasattr(colitem, 'function'):
+        for name in colitem.function.__dict__:
+            mapped_names.add(name)
+
+    mapping = KeywordMapping(mapped_names)
+    if " " not in keywordexpr:
+        # special case to allow for simple "-k pass" and "-k 1.3"
+        return mapping[keywordexpr]
+    elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
+        return not mapping[keywordexpr[4:]]
+    return eval(keywordexpr, {}, mapping)
+
+
+def pytest_configure(config):
+    config._old_mark_config = MARK_GEN._config
+    if config.option.strict:
+        MARK_GEN._config = config
+
+
+def pytest_unconfigure(config):
+    MARK_GEN._config = getattr(config, '_old_mark_config', None)
+
+
+class MarkGenerator:
+    """ Factory for :class:`MarkDecorator` objects - exposed as
+    a ``pytest.mark`` singleton instance.  Example::
+
+         import pytest
+         @pytest.mark.slowtest
+         def test_function():
+            pass
+
+    will set a 'slowtest' :class:`MarkInfo` object
+    on the ``test_function`` object. """
+    _config = None
+
+    def __getattr__(self, name):
+        if name[0] == "_":
+            raise AttributeError("Marker name must NOT start with underscore")
+        if self._config is not None:
+            self._check(name)
+        return MarkDecorator(Mark(name, (), {}))
+
+    def _check(self, name):
+        try:
+            if name in self._markers:
+                return
+        except AttributeError:
+            pass
+        self._markers = values = set()
+        for line in self._config.getini("markers"):
+            marker = line.split(":", 1)[0]
+            marker = marker.rstrip()
+            x = marker.split("(", 1)[0]
+            values.add(x)
+        if name not in self._markers:
+            raise AttributeError("%r not a registered marker" % (name,))
+
+
+def istestfunc(func):
+    return hasattr(func, "__call__") and \
+        getattr(func, "__name__", "<lambda>") != "<lambda>"
+
+
+@attr.s(frozen=True)
+class Mark(object):
+    name = attr.ib()
+    args = attr.ib()
+    kwargs = attr.ib()
+
+    def combined_with(self, other):
+        assert self.name == other.name
+        return Mark(
+            self.name, self.args + other.args,
+            dict(self.kwargs, **other.kwargs))
+
+
+@attr.s
+class MarkDecorator(object):
+    """ A decorator for test functions and test classes.  When applied
+    it will create :class:`MarkInfo` objects which may be
+    :ref:`retrieved by hooks as item keywords <excontrolskip>`.
+    MarkDecorator instances are often created like this::
+
+        mark1 = pytest.mark.NAME              # simple MarkDecorator
+        mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator
+
+    and can then be applied as decorators to test functions::
+
+        @mark2
+        def test_function():
+            pass
+
+    When a MarkDecorator instance is called it does the following:
+      1. If called with a single class as its only positional argument and no
+         additional keyword arguments, it attaches itself to the class so it
+         gets applied automatically to all test cases found in that class.
+      2. If called with a single function as its only positional argument and
+         no additional keyword arguments, it attaches a MarkInfo object to the
+         function, containing all the arguments already stored internally in
+         the MarkDecorator.
+      3. When called in any other case, it performs a 'fake construction' call,
+         i.e. it returns a new MarkDecorator instance with the original
+         MarkDecorator's content updated with the arguments passed to this
+         call.
+
+    Note: The rules above prevent MarkDecorator objects from storing only a
+    single function or class reference as their positional argument with no
+    additional keyword or positional arguments.
+
+    """
+
+    mark = attr.ib(validator=attr.validators.instance_of(Mark))
+
+    name = alias('mark.name')
+    args = alias('mark.args')
+    kwargs = alias('mark.kwargs')
+
+    @property
+    def markname(self):
+        return self.name  # for backward-compat (2.4.1 had this attr)
+
+    def __eq__(self, other):
+        return self.mark == other.mark if isinstance(other, MarkDecorator) else False
+
+    def __repr__(self):
+        return "<MarkDecorator %r>" % (self.mark,)
+
+    def with_args(self, *args, **kwargs):
+        """ return a MarkDecorator with extra arguments added
+
+        unlike call this can be used even if the sole argument is a callable/class
+
+        :return: MarkDecorator
+        """
+
+        mark = Mark(self.name, args, kwargs)
+        return self.__class__(self.mark.combined_with(mark))
+
+    def __call__(self, *args, **kwargs):
+        """ if passed a single callable argument: decorate it with mark info.
+            otherwise add *args/**kwargs in-place to mark information. """
+        if args and not kwargs:
+            func = args[0]
+            is_class = inspect.isclass(func)
+            if len(args) == 1 and (istestfunc(func) or is_class):
+                if is_class:
+                    store_mark(func, self.mark)
+                else:
+                    store_legacy_markinfo(func, self.mark)
+                    store_mark(func, self.mark)
+                return func
+        return self.with_args(*args, **kwargs)
+
+
+def get_unpacked_marks(obj):
+    """
+    obtain the unpacked marks that are stored on a object
+    """
+    mark_list = getattr(obj, 'pytestmark', [])
+
+    if not isinstance(mark_list, list):
+        mark_list = [mark_list]
+    return [
+        getattr(mark, 'mark', mark)  # unpack MarkDecorator
+        for mark in mark_list
+    ]
+
+
+def store_mark(obj, mark):
+    """store a Mark on a object
+    this is used to implement the Mark declarations/decorators correctly
+    """
+    assert isinstance(mark, Mark), mark
+    # always reassign name to avoid updating pytestmark
+    # in a reference that was only borrowed
+    obj.pytestmark = get_unpacked_marks(obj) + [mark]
+
+
+def store_legacy_markinfo(func, mark):
+    """create the legacy MarkInfo objects and put them onto the function
+    """
+    if not isinstance(mark, Mark):
+        raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
+    holder = getattr(func, mark.name, None)
+    if holder is None:
+        holder = MarkInfo(mark)
+        setattr(func, mark.name, holder)
+    else:
+        holder.add_mark(mark)
+
+
+class MarkInfo(object):
+    """ Marking object created by :class:`MarkDecorator` instances. """
+
+    def __init__(self, mark):
+        assert isinstance(mark, Mark), repr(mark)
+        self.combined = mark
+        self._marks = [mark]
+
+    name = alias('combined.name')
+    args = alias('combined.args')
+    kwargs = alias('combined.kwargs')
+
+    def __repr__(self):
+        return "<MarkInfo {0!r}>".format(self.combined)
+
+    def add_mark(self, mark):
+        """ add a MarkInfo with the given args and kwargs. """
+        self._marks.append(mark)
+        self.combined = self.combined.combined_with(mark)
+
+    def __iter__(self):
+        """ yield MarkInfo objects each relating to a marking-call. """
+        return map(MarkInfo, self._marks)
+
+
+MARK_GEN = MarkGenerator()
+
+
+def _marked(func, mark):
+    """ Returns True if :func: is already marked with :mark:, False otherwise.
+    This can happen if marker is applied to class and the test file is
+    invoked more than once.
+    """
+    try:
+        func_mark = getattr(func, mark.name)
+    except AttributeError:
+        return False
+    return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
+
+
+def transfer_markers(funcobj, cls, mod):
+    """
+    this function transfers class level markers and module level markers
+    into function level markinfo objects
+
+    this is the main reason why marks are so broken
+    the resolution will involve phasing out function level MarkInfo objects
+
+    """
+    for obj in (cls, mod):
+        for mark in get_unpacked_marks(obj):
+            if not _marked(funcobj, mark):
+                store_legacy_markinfo(funcobj, mark)
diff --git a/tools/third_party/pytest/_pytest/monkeypatch.py b/tools/third_party/pytest/_pytest/monkeypatch.py
new file mode 100644
index 0000000..40ae560
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/monkeypatch.py
@@ -0,0 +1,258 @@
+""" monkeypatching and mocking functionality.  """
+from __future__ import absolute_import, division, print_function
+
+import os
+import sys
+import re
+import six
+from _pytest.fixtures import fixture
+
+RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
+
+
+@fixture
+def monkeypatch():
+    """The returned ``monkeypatch`` fixture provides these
+    helper methods to modify objects, dictionaries or os.environ::
+
+        monkeypatch.setattr(obj, name, value, raising=True)
+        monkeypatch.delattr(obj, name, raising=True)
+        monkeypatch.setitem(mapping, name, value)
+        monkeypatch.delitem(obj, name, raising=True)
+        monkeypatch.setenv(name, value, prepend=False)
+        monkeypatch.delenv(name, value, raising=True)
+        monkeypatch.syspath_prepend(path)
+        monkeypatch.chdir(path)
+
+    All modifications will be undone after the requesting
+    test function or fixture has finished. The ``raising``
+    parameter determines if a KeyError or AttributeError
+    will be raised if the set/deletion operation has no target.
+    """
+    mpatch = MonkeyPatch()
+    yield mpatch
+    mpatch.undo()
+
+
+def resolve(name):
+    # simplified from zope.dottedname
+    parts = name.split('.')
+
+    used = parts.pop(0)
+    found = __import__(used)
+    for part in parts:
+        used += '.' + part
+        try:
+            found = getattr(found, part)
+        except AttributeError:
+            pass
+        else:
+            continue
+        # we use explicit un-nesting of the handling block in order
+        # to avoid nested exceptions on python 3
+        try:
+            __import__(used)
+        except ImportError as ex:
+            # str is used for py2 vs py3
+            expected = str(ex).split()[-1]
+            if expected == used:
+                raise
+            else:
+                raise ImportError(
+                    'import error in %s: %s' % (used, ex)
+                )
+        found = annotated_getattr(found, part, used)
+    return found
+
+
+def annotated_getattr(obj, name, ann):
+    try:
+        obj = getattr(obj, name)
+    except AttributeError:
+        raise AttributeError(
+            '%r object at %s has no attribute %r' % (
+                type(obj).__name__, ann, name
+            )
+        )
+    return obj
+
+
+def derive_importpath(import_path, raising):
+    if not isinstance(import_path, six.string_types) or "." not in import_path:
+        raise TypeError("must be absolute import path string, not %r" %
+                        (import_path,))
+    module, attr = import_path.rsplit('.', 1)
+    target = resolve(module)
+    if raising:
+        annotated_getattr(target, attr, ann=module)
+    return attr, target
+
+
+class Notset:
+    def __repr__(self):
+        return "<notset>"
+
+
+notset = Notset()
+
+
+class MonkeyPatch:
+    """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
+    """
+
+    def __init__(self):
+        self._setattr = []
+        self._setitem = []
+        self._cwd = None
+        self._savesyspath = None
+
+    def setattr(self, target, name, value=notset, raising=True):
+        """ Set attribute value on target, memorizing the old value.
+        By default raise AttributeError if the attribute did not exist.
+
+        For convenience you can specify a string as ``target`` which
+        will be interpreted as a dotted import path, with the last part
+        being the attribute name.  Example:
+        ``monkeypatch.setattr("os.getcwd", lambda x: "/")``
+        would set the ``getcwd`` function of the ``os`` module.
+
+        The ``raising`` value determines if the setattr should fail
+        if the attribute is not already present (defaults to True
+        which means it will raise).
+        """
+        __tracebackhide__ = True
+        import inspect
+
+        if value is notset:
+            if not isinstance(target, six.string_types):
+                raise TypeError("use setattr(target, name, value) or "
+                                "setattr(target, value) with target being a dotted "
+                                "import string")
+            value = name
+            name, target = derive_importpath(target, raising)
+
+        oldval = getattr(target, name, notset)
+        if raising and oldval is notset:
+            raise AttributeError("%r has no attribute %r" % (target, name))
+
+        # avoid class descriptors like staticmethod/classmethod
+        if inspect.isclass(target):
+            oldval = target.__dict__.get(name, notset)
+        self._setattr.append((target, name, oldval))
+        setattr(target, name, value)
+
+    def delattr(self, target, name=notset, raising=True):
+        """ Delete attribute ``name`` from ``target``, by default raise
+        AttributeError it the attribute did not previously exist.
+
+        If no ``name`` is specified and ``target`` is a string
+        it will be interpreted as a dotted import path with the
+        last part being the attribute name.
+
+        If ``raising`` is set to False, no exception will be raised if the
+        attribute is missing.
+        """
+        __tracebackhide__ = True
+        if name is notset:
+            if not isinstance(target, six.string_types):
+                raise TypeError("use delattr(target, name) or "
+                                "delattr(target) with target being a dotted "
+                                "import string")
+            name, target = derive_importpath(target, raising)
+
+        if not hasattr(target, name):
+            if raising:
+                raise AttributeError(name)
+        else:
+            self._setattr.append((target, name, getattr(target, name, notset)))
+            delattr(target, name)
+
+    def setitem(self, dic, name, value):
+        """ Set dictionary entry ``name`` to value. """
+        self._setitem.append((dic, name, dic.get(name, notset)))
+        dic[name] = value
+
+    def delitem(self, dic, name, raising=True):
+        """ Delete ``name`` from dict. Raise KeyError if it doesn't exist.
+
+        If ``raising`` is set to False, no exception will be raised if the
+        key is missing.
+        """
+        if name not in dic:
+            if raising:
+                raise KeyError(name)
+        else:
+            self._setitem.append((dic, name, dic.get(name, notset)))
+            del dic[name]
+
+    def setenv(self, name, value, prepend=None):
+        """ Set environment variable ``name`` to ``value``.  If ``prepend``
+        is a character, read the current environment variable value
+        and prepend the ``value`` adjoined with the ``prepend`` character."""
+        value = str(value)
+        if prepend and name in os.environ:
+            value = value + prepend + os.environ[name]
+        self.setitem(os.environ, name, value)
+
+    def delenv(self, name, raising=True):
+        """ Delete ``name`` from the environment. Raise KeyError it does not
+        exist.
+
+        If ``raising`` is set to False, no exception will be raised if the
+        environment variable is missing.
+        """
+        self.delitem(os.environ, name, raising=raising)
+
+    def syspath_prepend(self, path):
+        """ Prepend ``path`` to ``sys.path`` list of import locations. """
+        if self._savesyspath is None:
+            self._savesyspath = sys.path[:]
+        sys.path.insert(0, str(path))
+
+    def chdir(self, path):
+        """ Change the current working directory to the specified path.
+        Path can be a string or a py.path.local object.
+        """
+        if self._cwd is None:
+            self._cwd = os.getcwd()
+        if hasattr(path, "chdir"):
+            path.chdir()
+        else:
+            os.chdir(path)
+
+    def undo(self):
+        """ Undo previous changes.  This call consumes the
+        undo stack. Calling it a second time has no effect unless
+        you do more monkeypatching after the undo call.
+
+        There is generally no need to call `undo()`, since it is
+        called automatically during tear-down.
+
+        Note that the same `monkeypatch` fixture is used across a
+        single test function invocation. If `monkeypatch` is used both by
+        the test function itself and one of the test fixtures,
+        calling `undo()` will undo all of the changes made in
+        both functions.
+        """
+        for obj, name, value in reversed(self._setattr):
+            if value is not notset:
+                setattr(obj, name, value)
+            else:
+                delattr(obj, name)
+        self._setattr[:] = []
+        for dictionary, name, value in reversed(self._setitem):
+            if value is notset:
+                try:
+                    del dictionary[name]
+                except KeyError:
+                    pass  # was already deleted, so we have the desired state
+            else:
+                dictionary[name] = value
+        self._setitem[:] = []
+        if self._savesyspath is not None:
+            sys.path[:] = self._savesyspath
+            self._savesyspath = None
+
+        if self._cwd is not None:
+            os.chdir(self._cwd)
+            self._cwd = None
diff --git a/tools/third_party/pytest/_pytest/nodes.py b/tools/third_party/pytest/_pytest/nodes.py
new file mode 100644
index 0000000..ad3af2c
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/nodes.py
@@ -0,0 +1,37 @@
+SEP = "/"
+
+
+def _splitnode(nodeid):
+    """Split a nodeid into constituent 'parts'.
+
+    Node IDs are strings, and can be things like:
+        ''
+        'testing/code'
+        'testing/code/test_excinfo.py'
+        'testing/code/test_excinfo.py::TestFormattedExcinfo::()'
+
+    Return values are lists e.g.
+        []
+        ['testing', 'code']
+        ['testing', 'code', 'test_excinfo.py']
+        ['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()']
+    """
+    if nodeid == '':
+        # If there is no root node at all, return an empty list so the caller's logic can remain sane
+        return []
+    parts = nodeid.split(SEP)
+    # Replace single last element 'test_foo.py::Bar::()' with multiple elements 'test_foo.py', 'Bar', '()'
+    parts[-1:] = parts[-1].split("::")
+    return parts
+
+
+def ischildnode(baseid, nodeid):
+    """Return True if the nodeid is a child node of the baseid.
+
+    E.g. 'foo/bar::Baz::()' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp'
+    """
+    base_parts = _splitnode(baseid)
+    node_parts = _splitnode(nodeid)
+    if len(node_parts) < len(base_parts):
+        return False
+    return node_parts[:len(base_parts)] == base_parts
diff --git a/tools/third_party/pytest/_pytest/nose.py b/tools/third_party/pytest/_pytest/nose.py
new file mode 100644
index 0000000..c81542e
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/nose.py
@@ -0,0 +1,72 @@
+""" run test suites written for nose. """
+from __future__ import absolute_import, division, print_function
+
+import sys
+
+from _pytest import unittest, runner, python
+from _pytest.config import hookimpl
+
+
+def get_skip_exceptions():
+    skip_classes = set()
+    for module_name in ('unittest', 'unittest2', 'nose'):
+        mod = sys.modules.get(module_name)
+        if hasattr(mod, 'SkipTest'):
+            skip_classes.add(mod.SkipTest)
+    return tuple(skip_classes)
+
+
+def pytest_runtest_makereport(item, call):
+    if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
+        # let's substitute the excinfo with a pytest.skip one
+        call2 = call.__class__(
+            lambda: runner.skip(str(call.excinfo.value)), call.when)
+        call.excinfo = call2.excinfo
+
+
+@hookimpl(trylast=True)
+def pytest_runtest_setup(item):
+    if is_potential_nosetest(item):
+        if isinstance(item.parent, python.Generator):
+            gen = item.parent
+            if not hasattr(gen, '_nosegensetup'):
+                call_optional(gen.obj, 'setup')
+                if isinstance(gen.parent, python.Instance):
+                    call_optional(gen.parent.obj, 'setup')
+                gen._nosegensetup = True
+        if not call_optional(item.obj, 'setup'):
+            # call module level setup if there is no object level one
+            call_optional(item.parent.obj, 'setup')
+        # XXX this implies we only call teardown when setup worked
+        item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
+
+
+def teardown_nose(item):
+    if is_potential_nosetest(item):
+        if not call_optional(item.obj, 'teardown'):
+            call_optional(item.parent.obj, 'teardown')
+        # if hasattr(item.parent, '_nosegensetup'):
+        #    #call_optional(item._nosegensetup, 'teardown')
+        #    del item.parent._nosegensetup
+
+
+def pytest_make_collect_report(collector):
+    if isinstance(collector, python.Generator):
+        call_optional(collector.obj, 'setup')
+
+
+def is_potential_nosetest(item):
+    # extra check needed since we do not do nose style setup/teardown
+    # on direct unittest style classes
+    return isinstance(item, python.Function) and \
+        not isinstance(item, unittest.TestCaseFunction)
+
+
+def call_optional(obj, name):
+    method = getattr(obj, name, None)
+    isfixture = hasattr(method, "_pytestfixturefunction")
+    if method is not None and not isfixture and callable(method):
+        # If there's any problems allow the exception to raise rather than
+        # silently ignoring them
+        method()
+        return True
diff --git a/tools/third_party/pytest/_pytest/outcomes.py b/tools/third_party/pytest/_pytest/outcomes.py
new file mode 100644
index 0000000..7f0c18f
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/outcomes.py
@@ -0,0 +1,147 @@
+"""
+exception classes and constants handling test outcomes
+as well as functions creating them
+"""
+from __future__ import absolute_import, division, print_function
+import py
+import sys
+
+
+class OutcomeException(BaseException):
+    """ OutcomeException and its subclass instances indicate and
+        contain info about test and collection outcomes.
+    """
+    def __init__(self, msg=None, pytrace=True):
+        BaseException.__init__(self, msg)
+        self.msg = msg
+        self.pytrace = pytrace
+
+    def __repr__(self):
+        if self.msg:
+            val = self.msg
+            if isinstance(val, bytes):
+                val = py._builtin._totext(val, errors='replace')
+            return val
+        return "<%s instance>" % (self.__class__.__name__,)
+    __str__ = __repr__
+
+
+TEST_OUTCOME = (OutcomeException, Exception)
+
+
+class Skipped(OutcomeException):
+    # XXX hackish: on 3k we fake to live in the builtins
+    # in order to have Skipped exception printing shorter/nicer
+    __module__ = 'builtins'
+
+    def __init__(self, msg=None, pytrace=True, allow_module_level=False):
+        OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
+        self.allow_module_level = allow_module_level
+
+
+class Failed(OutcomeException):
+    """ raised from an explicit call to pytest.fail() """
+    __module__ = 'builtins'
+
+
+class Exit(KeyboardInterrupt):
+    """ raised for immediate program exits (no tracebacks/summaries)"""
+    def __init__(self, msg="unknown reason"):
+        self.msg = msg
+        KeyboardInterrupt.__init__(self, msg)
+
+# exposed helper methods
+
+
+def exit(msg):
+    """ exit testing process as if KeyboardInterrupt was triggered. """
+    __tracebackhide__ = True
+    raise Exit(msg)
+
+
+exit.Exception = Exit
+
+
+def skip(msg="", **kwargs):
+    """ skip an executing test with the given message.  Note: it's usually
+    better to use the pytest.mark.skipif marker to declare a test to be
+    skipped under certain conditions like mismatching platforms or
+    dependencies.  See the pytest_skipping plugin for details.
+
+    :kwarg bool allow_module_level: allows this function to be called at
+        module level, skipping the rest of the module. Default to False.
+    """
+    __tracebackhide__ = True
+    allow_module_level = kwargs.pop('allow_module_level', False)
+    if kwargs:
+        keys = [k for k in kwargs.keys()]
+        raise TypeError('unexpected keyword arguments: {0}'.format(keys))
+    raise Skipped(msg=msg, allow_module_level=allow_module_level)
+
+
+skip.Exception = Skipped
+
+
+def fail(msg="", pytrace=True):
+    """ explicitly fail an currently-executing test with the given Message.
+
+    :arg pytrace: if false the msg represents the full failure information
+                  and no python traceback will be reported.
+    """
+    __tracebackhide__ = True
+    raise Failed(msg=msg, pytrace=pytrace)
+
+
+fail.Exception = Failed
+
+
+class XFailed(fail.Exception):
+    """ raised from an explicit call to pytest.xfail() """
+
+
+def xfail(reason=""):
+    """ xfail an executing test or setup functions with the given reason."""
+    __tracebackhide__ = True
+    raise XFailed(reason)
+
+
+xfail.Exception = XFailed
+
+
+def importorskip(modname, minversion=None):
+    """ return imported module if it has at least "minversion" as its
+    __version__ attribute.  If no minversion is specified the a skip
+    is only triggered if the module can not be imported.
+    """
+    import warnings
+    __tracebackhide__ = True
+    compile(modname, '', 'eval')  # to catch syntaxerrors
+    should_skip = False
+
+    with warnings.catch_warnings():
+        # make sure to ignore ImportWarnings that might happen because
+        # of existing directories with the same name we're trying to
+        # import but without a __init__.py file
+        warnings.simplefilter('ignore')
+        try:
+            __import__(modname)
+        except ImportError:
+            # Do not raise chained exception here(#1485)
+            should_skip = True
+    if should_skip:
+        raise Skipped("could not import %r" % (modname,), allow_module_level=True)
+    mod = sys.modules[modname]
+    if minversion is None:
+        return mod
+    verattr = getattr(mod, '__version__', None)
+    if minversion is not None:
+        try:
+            from pkg_resources import parse_version as pv
+        except ImportError:
+            raise Skipped("we have a required version for %r but can not import "
+                          "pkg_resources to parse version strings." % (modname,),
+                          allow_module_level=True)
+        if verattr is None or pv(verattr) < pv(minversion):
+            raise Skipped("module %r has __version__ %r, required is: %r" % (
+                          modname, verattr, minversion), allow_module_level=True)
+    return mod
diff --git a/tools/third_party/pytest/_pytest/pastebin.py b/tools/third_party/pytest/_pytest/pastebin.py
new file mode 100644
index 0000000..b588b02
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/pastebin.py
@@ -0,0 +1,100 @@
+""" submit failure or test session information to a pastebin service. """
+from __future__ import absolute_import, division, print_function
+
+import pytest
+import six
+import sys
+import tempfile
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("terminal reporting")
+    group._addoption('--pastebin', metavar="mode",
+                     action='store', dest="pastebin", default=None,
+                     choices=['failed', 'all'],
+                     help="send failed|all info to bpaste.net pastebin service.")
+
+
+@pytest.hookimpl(trylast=True)
+def pytest_configure(config):
+    if config.option.pastebin == "all":
+        tr = config.pluginmanager.getplugin('terminalreporter')
+        # if no terminal reporter plugin is present, nothing we can do here;
+        # this can happen when this function executes in a slave node
+        # when using pytest-xdist, for example
+        if tr is not None:
+            # pastebin file will be utf-8 encoded binary file
+            config._pastebinfile = tempfile.TemporaryFile('w+b')
+            oldwrite = tr._tw.write
+
+            def tee_write(s, **kwargs):
+                oldwrite(s, **kwargs)
+                if isinstance(s, six.text_type):
+                    s = s.encode('utf-8')
+                config._pastebinfile.write(s)
+
+            tr._tw.write = tee_write
+
+
+def pytest_unconfigure(config):
+    if hasattr(config, '_pastebinfile'):
+        # get terminal contents and delete file
+        config._pastebinfile.seek(0)
+        sessionlog = config._pastebinfile.read()
+        config._pastebinfile.close()
+        del config._pastebinfile
+        # undo our patching in the terminal reporter
+        tr = config.pluginmanager.getplugin('terminalreporter')
+        del tr._tw.__dict__['write']
+        # write summary
+        tr.write_sep("=", "Sending information to Paste Service")
+        pastebinurl = create_new_paste(sessionlog)
+        tr.write_line("pastebin session-log: %s\n" % pastebinurl)
+
+
+def create_new_paste(contents):
+    """
+    Creates a new paste using bpaste.net service.
+
+    :contents: paste contents as utf-8 encoded bytes
+    :returns: url to the pasted contents
+    """
+    import re
+    if sys.version_info < (3, 0):
+        from urllib import urlopen, urlencode
+    else:
+        from urllib.request import urlopen
+        from urllib.parse import urlencode
+
+    params = {
+        'code': contents,
+        'lexer': 'python3' if sys.version_info[0] == 3 else 'python',
+        'expiry': '1week',
+    }
+    url = 'https://bpaste.net'
+    response = urlopen(url, data=urlencode(params).encode('ascii')).read()
+    m = re.search(r'href="/raw/(\w+)"', response.decode('utf-8'))
+    if m:
+        return '%s/show/%s' % (url, m.group(1))
+    else:
+        return 'bad response: ' + response
+
+
+def pytest_terminal_summary(terminalreporter):
+    import _pytest.config
+    if terminalreporter.config.option.pastebin != "failed":
+        return
+    tr = terminalreporter
+    if 'failed' in tr.stats:
+        terminalreporter.write_sep("=", "Sending information to Paste Service")
+        for rep in terminalreporter.stats.get('failed'):
+            try:
+                msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
+            except AttributeError:
+                msg = tr._getfailureheadline(rep)
+            tw = _pytest.config.create_terminal_writer(terminalreporter.config, stringio=True)
+            rep.toterminal(tw)
+            s = tw.stringio.getvalue()
+            assert len(s)
+            pastebinurl = create_new_paste(s)
+            tr.write_line("%s --> %s" % (msg, pastebinurl))
diff --git a/tools/third_party/pytest/_pytest/pytester.py b/tools/third_party/pytest/_pytest/pytester.py
new file mode 100644
index 0000000..f2dd599
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/pytester.py
@@ -0,0 +1,1187 @@
+""" (disabled by default) support for testing pytest and pytest plugins. """
+from __future__ import absolute_import, division, print_function
+
+import codecs
+import gc
+import os
+import platform
+import re
+import subprocess
+import six
+import sys
+import time
+import traceback
+from fnmatch import fnmatch
+
+from weakref import WeakKeyDictionary
+
+from _pytest.capture import MultiCapture, SysCapture
+from _pytest._code import Source
+import py
+import pytest
+from _pytest.main import Session, EXIT_OK
+from _pytest.assertion.rewrite import AssertionRewritingHook
+
+
+PYTEST_FULLPATH = os.path.abspath(pytest.__file__.rstrip("oc")).replace("$py.class", ".py")
+
+
+def pytest_addoption(parser):
+    # group = parser.getgroup("pytester", "pytester (self-tests) options")
+    parser.addoption('--lsof',
+                     action="store_true", dest="lsof", default=False,
+                     help=("run FD checks if lsof is available"))
+
+    parser.addoption('--runpytest', default="inprocess", dest="runpytest",
+                     choices=("inprocess", "subprocess", ),
+                     help=("run pytest sub runs in tests using an 'inprocess' "
+                           "or 'subprocess' (python -m main) method"))
+
+
+def pytest_configure(config):
+    if config.getvalue("lsof"):
+        checker = LsofFdLeakChecker()
+        if checker.matching_platform():
+            config.pluginmanager.register(checker)
+
+
+class LsofFdLeakChecker(object):
+    def get_open_files(self):
+        out = self._exec_lsof()
+        open_files = self._parse_lsof_output(out)
+        return open_files
+
+    def _exec_lsof(self):
+        pid = os.getpid()
+        return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
+
+    def _parse_lsof_output(self, out):
+        def isopen(line):
+            return line.startswith('f') and ("deleted" not in line and
+                                             'mem' not in line and "txt" not in line and 'cwd' not in line)
+
+        open_files = []
+
+        for line in out.split("\n"):
+            if isopen(line):
+                fields = line.split('\0')
+                fd = fields[0][1:]
+                filename = fields[1][1:]
+                if filename.startswith('/'):
+                    open_files.append((fd, filename))
+
+        return open_files
+
+    def matching_platform(self):
+        try:
+            py.process.cmdexec("lsof -v")
+        except (py.process.cmdexec.Error, UnicodeDecodeError):
+            # cmdexec may raise UnicodeDecodeError on Windows systems
+            # with locale other than english:
+            # https://bitbucket.org/pytest-dev/py/issues/66
+            return False
+        else:
+            return True
+
+    @pytest.hookimpl(hookwrapper=True, tryfirst=True)
+    def pytest_runtest_protocol(self, item):
+        lines1 = self.get_open_files()
+        yield
+        if hasattr(sys, "pypy_version_info"):
+            gc.collect()
+        lines2 = self.get_open_files()
+
+        new_fds = set([t[0] for t in lines2]) - set([t[0] for t in lines1])
+        leaked_files = [t for t in lines2 if t[0] in new_fds]
+        if leaked_files:
+            error = []
+            error.append("***** %s FD leakage detected" % len(leaked_files))
+            error.extend([str(f) for f in leaked_files])
+            error.append("*** Before:")
+            error.extend([str(f) for f in lines1])
+            error.append("*** After:")
+            error.extend([str(f) for f in lines2])
+            error.append(error[0])
+            error.append("*** function %s:%s: %s " % item.location)
+            error.append("See issue #2366")
+            item.warn('', "\n".join(error))
+
+
+# XXX copied from execnet's conftest.py - needs to be merged
+winpymap = {
+    'python2.7': r'C:\Python27\python.exe',
+    'python3.4': r'C:\Python34\python.exe',
+    'python3.5': r'C:\Python35\python.exe',
+    'python3.6': r'C:\Python36\python.exe',
+}
+
+
+def getexecutable(name, cache={}):
+    try:
+        return cache[name]
+    except KeyError:
+        executable = py.path.local.sysfind(name)
+        if executable:
+            import subprocess
+            popen = subprocess.Popen([str(executable), "--version"],
+                                     universal_newlines=True, stderr=subprocess.PIPE)
+            out, err = popen.communicate()
+            if name == "jython":
+                if not err or "2.5" not in err:
+                    executable = None
+                if "2.5.2" in err:
+                    executable = None  # http://bugs.jython.org/issue1790
+            elif popen.returncode != 0:
+                # Handle pyenv's 127.
+                executable = None
+        cache[name] = executable
+        return executable
+
+
+@pytest.fixture(params=['python2.7', 'python3.4', 'pypy', 'pypy3'])
+def anypython(request):
+    name = request.param
+    executable = getexecutable(name)
+    if executable is None:
+        if sys.platform == "win32":
+            executable = winpymap.get(name, None)
+            if executable:
+                executable = py.path.local(executable)
+                if executable.check():
+                    return executable
+        pytest.skip("no suitable %s found" % (name,))
+    return executable
+
+# used at least by pytest-xdist plugin
+
+
+@pytest.fixture
+def _pytest(request):
+    """ Return a helper which offers a gethookrecorder(hook)
+    method which returns a HookRecorder instance which helps
+    to make assertions about called hooks.
+    """
+    return PytestArg(request)
+
+
+class PytestArg:
+    def __init__(self, request):
+        self.request = request
+
+    def gethookrecorder(self, hook):
+        hookrecorder = HookRecorder(hook._pm)
+        self.request.addfinalizer(hookrecorder.finish_recording)
+        return hookrecorder
+
+
+def get_public_names(values):
+    """Only return names from iterator values without a leading underscore."""
+    return [x for x in values if x[0] != "_"]
+
+
+class ParsedCall:
+    def __init__(self, name, kwargs):
+        self.__dict__.update(kwargs)
+        self._name = name
+
+    def __repr__(self):
+        d = self.__dict__.copy()
+        del d['_name']
+        return "<ParsedCall %r(**%r)>" % (self._name, d)
+
+
+class HookRecorder:
+    """Record all hooks called in a plugin manager.
+
+    This wraps all the hook calls in the plugin manager, recording
+    each call before propagating the normal calls.
+
+    """
+
+    def __init__(self, pluginmanager):
+        self._pluginmanager = pluginmanager
+        self.calls = []
+
+        def before(hook_name, hook_impls, kwargs):
+            self.calls.append(ParsedCall(hook_name, kwargs))
+
+        def after(outcome, hook_name, hook_impls, kwargs):
+            pass
+
+        self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
+
+    def finish_recording(self):
+        self._undo_wrapping()
+
+    def getcalls(self, names):
+        if isinstance(names, str):
+            names = names.split()
+        return [call for call in self.calls if call._name in names]
+
+    def assert_contains(self, entries):
+        __tracebackhide__ = True
+        i = 0
+        entries = list(entries)
+        backlocals = sys._getframe(1).f_locals
+        while entries:
+            name, check = entries.pop(0)
+            for ind, call in enumerate(self.calls[i:]):
+                if call._name == name:
+                    print("NAMEMATCH", name, call)
+                    if eval(check, backlocals, call.__dict__):
+                        print("CHECKERMATCH", repr(check), "->", call)
+                    else:
+                        print("NOCHECKERMATCH", repr(check), "-", call)
+                        continue
+                    i += ind + 1
+                    break
+                print("NONAMEMATCH", name, "with", call)
+            else:
+                pytest.fail("could not find %r check %r" % (name, check))
+
+    def popcall(self, name):
+        __tracebackhide__ = True
+        for i, call in enumerate(self.calls):
+            if call._name == name:
+                del self.calls[i]
+                return call
+        lines = ["could not find call %r, in:" % (name,)]
+        lines.extend(["  %s" % str(x) for x in self.calls])
+        pytest.fail("\n".join(lines))
+
+    def getcall(self, name):
+        values = self.getcalls(name)
+        assert len(values) == 1, (name, values)
+        return values[0]
+
+    # functionality for test reports
+
+    def getreports(self,
+                   names="pytest_runtest_logreport pytest_collectreport"):
+        return [x.report for x in self.getcalls(names)]
+
+    def matchreport(self, inamepart="",
+                    names="pytest_runtest_logreport pytest_collectreport", when=None):
+        """ return a testreport whose dotted import path matches """
+        values = []
+        for rep in self.getreports(names=names):
+            try:
+                if not when and rep.when != "call" and rep.passed:
+                    # setup/teardown passing reports - let's ignore those
+                    continue
+            except AttributeError:
+                pass
+            if when and getattr(rep, 'when', None) != when:
+                continue
+            if not inamepart or inamepart in rep.nodeid.split("::"):
+                values.append(rep)
+        if not values:
+            raise ValueError("could not find test report matching %r: "
+                             "no test reports at all!" % (inamepart,))
+        if len(values) > 1:
+            raise ValueError(
+                "found 2 or more testreports matching %r: %s" % (inamepart, values))
+        return values[0]
+
+    def getfailures(self,
+                    names='pytest_runtest_logreport pytest_collectreport'):
+        return [rep for rep in self.getreports(names) if rep.failed]
+
+    def getfailedcollections(self):
+        return self.getfailures('pytest_collectreport')
+
+    def listoutcomes(self):
+        passed = []
+        skipped = []
+        failed = []
+        for rep in self.getreports(
+                "pytest_collectreport pytest_runtest_logreport"):
+            if rep.passed:
+                if getattr(rep, "when", None) == "call":
+                    passed.append(rep)
+            elif rep.skipped:
+                skipped.append(rep)
+            elif rep.failed:
+                failed.append(rep)
+        return passed, skipped, failed
+
+    def countoutcomes(self):
+        return [len(x) for x in self.listoutcomes()]
+
+    def assertoutcome(self, passed=0, skipped=0, failed=0):
+        realpassed, realskipped, realfailed = self.listoutcomes()
+        assert passed == len(realpassed)
+        assert skipped == len(realskipped)
+        assert failed == len(realfailed)
+
+    def clear(self):
+        self.calls[:] = []
+
+
+@pytest.fixture
+def linecomp(request):
+    return LineComp()
+
+
+@pytest.fixture(name='LineMatcher')
+def LineMatcher_fixture(request):
+    return LineMatcher
+
+
+@pytest.fixture
+def testdir(request, tmpdir_factory):
+    return Testdir(request, tmpdir_factory)
+
+
+rex_outcome = re.compile(r"(\d+) ([\w-]+)")
+
+
+class RunResult:
+    """The result of running a command.
+
+    Attributes:
+
+    :ret: The return value.
+    :outlines: List of lines captured from stdout.
+    :errlines: List of lines captures from stderr.
+    :stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
+       reconstruct stdout or the commonly used
+       ``stdout.fnmatch_lines()`` method.
+    :stderrr: :py:class:`LineMatcher` of stderr.
+    :duration: Duration in seconds.
+
+    """
+
+    def __init__(self, ret, outlines, errlines, duration):
+        self.ret = ret
+        self.outlines = outlines
+        self.errlines = errlines
+        self.stdout = LineMatcher(outlines)
+        self.stderr = LineMatcher(errlines)
+        self.duration = duration
+
+    def parseoutcomes(self):
+        """ Return a dictionary of outcomestring->num from parsing
+        the terminal output that the test process produced."""
+        for line in reversed(self.outlines):
+            if 'seconds' in line:
+                outcomes = rex_outcome.findall(line)
+                if outcomes:
+                    d = {}
+                    for num, cat in outcomes:
+                        d[cat] = int(num)
+                    return d
+        raise ValueError("Pytest terminal report not found")
+
+    def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
+        """ assert that the specified outcomes appear with the respective
+        numbers (0 means it didn't occur) in the text output from a test run."""
+        d = self.parseoutcomes()
+        obtained = {
+            'passed': d.get('passed', 0),
+            'skipped': d.get('skipped', 0),
+            'failed': d.get('failed', 0),
+            'error': d.get('error', 0),
+        }
+        assert obtained == dict(passed=passed, skipped=skipped, failed=failed, error=error)
+
+
+class Testdir:
+    """Temporary test directory with tools to test/run pytest itself.
+
+    This is based on the ``tmpdir`` fixture but provides a number of
+    methods which aid with testing pytest itself.  Unless
+    :py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
+    current working directory.
+
+    Attributes:
+
+    :tmpdir: The :py:class:`py.path.local` instance of the temporary
+       directory.
+
+    :plugins: A list of plugins to use with :py:meth:`parseconfig` and
+       :py:meth:`runpytest`.  Initially this is an empty list but
+       plugins can be added to the list.  The type of items to add to
+       the list depend on the method which uses them so refer to them
+       for details.
+
+    """
+
+    def __init__(self, request, tmpdir_factory):
+        self.request = request
+        self._mod_collections = WeakKeyDictionary()
+        name = request.function.__name__
+        self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
+        self.plugins = []
+        self._savesyspath = (list(sys.path), list(sys.meta_path))
+        self._savemodulekeys = set(sys.modules)
+        self.chdir()  # always chdir
+        self.request.addfinalizer(self.finalize)
+        method = self.request.config.getoption("--runpytest")
+        if method == "inprocess":
+            self._runpytest_method = self.runpytest_inprocess
+        elif method == "subprocess":
+            self._runpytest_method = self.runpytest_subprocess
+
+    def __repr__(self):
+        return "<Testdir %r>" % (self.tmpdir,)
+
+    def finalize(self):
+        """Clean up global state artifacts.
+
+        Some methods modify the global interpreter state and this
+        tries to clean this up.  It does not remove the temporary
+        directory however so it can be looked at after the test run
+        has finished.
+
+        """
+        sys.path[:], sys.meta_path[:] = self._savesyspath
+        if hasattr(self, '_olddir'):
+            self._olddir.chdir()
+        self.delete_loaded_modules()
+
+    def delete_loaded_modules(self):
+        """Delete modules that have been loaded during a test.
+
+        This allows the interpreter to catch module changes in case
+        the module is re-imported.
+        """
+        for name in set(sys.modules).difference(self._savemodulekeys):
+            # some zope modules used by twisted-related tests keeps internal
+            # state and can't be deleted; we had some trouble in the past
+            # with zope.interface for example
+            if not name.startswith("zope"):
+                del sys.modules[name]
+
+    def make_hook_recorder(self, pluginmanager):
+        """Create a new :py:class:`HookRecorder` for a PluginManager."""
+        assert not hasattr(pluginmanager, "reprec")
+        pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
+        self.request.addfinalizer(reprec.finish_recording)
+        return reprec
+
+    def chdir(self):
+        """Cd into the temporary directory.
+
+        This is done automatically upon instantiation.
+
+        """
+        old = self.tmpdir.chdir()
+        if not hasattr(self, '_olddir'):
+            self._olddir = old
+
+    def _makefile(self, ext, args, kwargs, encoding='utf-8'):
+        items = list(kwargs.items())
+
+        def to_text(s):
+            return s.decode(encoding) if isinstance(s, bytes) else six.text_type(s)
+
+        if args:
+            source = u"\n".join(to_text(x) for x in args)
+            basename = self.request.function.__name__
+            items.insert(0, (basename, source))
+
+        ret = None
+        for basename, value in items:
+            p = self.tmpdir.join(basename).new(ext=ext)
+            p.dirpath().ensure_dir()
+            source = Source(value)
+            source = u"\n".join(to_text(line) for line in source.lines)
+            p.write(source.strip().encode(encoding), "wb")
+            if ret is None:
+                ret = p
+        return ret
+
+    def makefile(self, ext, *args, **kwargs):
+        """Create a new file in the testdir.
+
+        ext: The extension the file should use, including the dot.
+           E.g. ".py".
+
+        args: All args will be treated as strings and joined using
+           newlines.  The result will be written as contents to the
+           file.  The name of the file will be based on the test
+           function requesting this fixture.
+           E.g. "testdir.makefile('.txt', 'line1', 'line2')"
+
+        kwargs: Each keyword is the name of a file, while the value of
+           it will be written as contents of the file.
+           E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
+
+        """
+        return self._makefile(ext, args, kwargs)
+
+    def makeconftest(self, source):
+        """Write a contest.py file with 'source' as contents."""
+        return self.makepyfile(conftest=source)
+
+    def makeini(self, source):
+        """Write a tox.ini file with 'source' as contents."""
+        return self.makefile('.ini', tox=source)
+
+    def getinicfg(self, source):
+        """Return the pytest section from the tox.ini config file."""
+        p = self.makeini(source)
+        return py.iniconfig.IniConfig(p)['pytest']
+
+    def makepyfile(self, *args, **kwargs):
+        """Shortcut for .makefile() with a .py extension."""
+        return self._makefile('.py', args, kwargs)
+
+    def maketxtfile(self, *args, **kwargs):
+        """Shortcut for .makefile() with a .txt extension."""
+        return self._makefile('.txt', args, kwargs)
+
+    def syspathinsert(self, path=None):
+        """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
+
+        This is undone automatically after the test.
+        """
+        if path is None:
+            path = self.tmpdir
+        sys.path.insert(0, str(path))
+        # a call to syspathinsert() usually means that the caller
+        # wants to import some dynamically created files.
+        # with python3 we thus invalidate import caches.
+        self._possibly_invalidate_import_caches()
+
+    def _possibly_invalidate_import_caches(self):
+        # invalidate caches if we can (py33 and above)
+        try:
+            import importlib
+        except ImportError:
+            pass
+        else:
+            if hasattr(importlib, "invalidate_caches"):
+                importlib.invalidate_caches()
+
+    def mkdir(self, name):
+        """Create a new (sub)directory."""
+        return self.tmpdir.mkdir(name)
+
+    def mkpydir(self, name):
+        """Create a new python package.
+
+        This creates a (sub)directory with an empty ``__init__.py``
+        file so that is recognised as a python package.
+
+        """
+        p = self.mkdir(name)
+        p.ensure("__init__.py")
+        return p
+
+    Session = Session
+
+    def getnode(self, config, arg):
+        """Return the collection node of a file.
+
+        :param config: :py:class:`_pytest.config.Config` instance, see
+           :py:meth:`parseconfig` and :py:meth:`parseconfigure` to
+           create the configuration.
+
+        :param arg: A :py:class:`py.path.local` instance of the file.
+
+        """
+        session = Session(config)
+        assert '::' not in str(arg)
+        p = py.path.local(arg)
+        config.hook.pytest_sessionstart(session=session)
+        res = session.perform_collect([str(p)], genitems=False)[0]
+        config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
+        return res
+
+    def getpathnode(self, path):
+        """Return the collection node of a file.
+
+        This is like :py:meth:`getnode` but uses
+        :py:meth:`parseconfigure` to create the (configured) pytest
+        Config instance.
+
+        :param path: A :py:class:`py.path.local` instance of the file.
+
+        """
+        config = self.parseconfigure(path)
+        session = Session(config)
+        x = session.fspath.bestrelpath(path)
+        config.hook.pytest_sessionstart(session=session)
+        res = session.perform_collect([x], genitems=False)[0]
+        config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
+        return res
+
+    def genitems(self, colitems):
+        """Generate all test items from a collection node.
+
+        This recurses into the collection node and returns a list of
+        all the test items contained within.
+
+        """
+        session = colitems[0].session
+        result = []
+        for colitem in colitems:
+            result.extend(session.genitems(colitem))
+        return result
+
+    def runitem(self, source):
+        """Run the "test_func" Item.
+
+        The calling test instance (the class which contains the test
+        method) must provide a ``.getrunner()`` method which should
+        return a runner which can run the test protocol for a single
+        item, like e.g. :py:func:`_pytest.runner.runtestprotocol`.
+
+        """
+        # used from runner functional tests
+        item = self.getitem(source)
+        # the test class where we are called from wants to provide the runner
+        testclassinstance = self.request.instance
+        runner = testclassinstance.getrunner()
+        return runner(item)
+
+    def inline_runsource(self, source, *cmdlineargs):
+        """Run a test module in process using ``pytest.main()``.
+
+        This run writes "source" into a temporary file and runs
+        ``pytest.main()`` on it, returning a :py:class:`HookRecorder`
+        instance for the result.
+
+        :param source: The source code of the test module.
+
+        :param cmdlineargs: Any extra command line arguments to use.
+
+        :return: :py:class:`HookRecorder` instance of the result.
+
+        """
+        p = self.makepyfile(source)
+        values = list(cmdlineargs) + [p]
+        return self.inline_run(*values)
+
+    def inline_genitems(self, *args):
+        """Run ``pytest.main(['--collectonly'])`` in-process.
+
+        Returns a tuple of the collected items and a
+        :py:class:`HookRecorder` instance.
+
+        This runs the :py:func:`pytest.main` function to run all of
+        pytest inside the test process itself like
+        :py:meth:`inline_run`.  However the return value is a tuple of
+        the collection items and a :py:class:`HookRecorder` instance.
+
+        """
+        rec = self.inline_run("--collect-only", *args)
+        items = [x.item for x in rec.getcalls("pytest_itemcollected")]
+        return items, rec
+
+    def inline_run(self, *args, **kwargs):
+        """Run ``pytest.main()`` in-process, returning a HookRecorder.
+
+        This runs the :py:func:`pytest.main` function to run all of
+        pytest inside the test process itself.  This means it can
+        return a :py:class:`HookRecorder` instance which gives more
+        detailed results from then run then can be done by matching
+        stdout/stderr from :py:meth:`runpytest`.
+
+        :param args: Any command line arguments to pass to
+           :py:func:`pytest.main`.
+
+        :param plugin: (keyword-only) Extra plugin instances the
+           ``pytest.main()`` instance should use.
+
+        :return: A :py:class:`HookRecorder` instance.
+        """
+        # When running py.test inline any plugins active in the main
+        # test process are already imported.  So this disables the
+        # warning which will trigger to say they can no longer be
+        # rewritten, which is fine as they are already rewritten.
+        orig_warn = AssertionRewritingHook._warn_already_imported
+
+        def revert():
+            AssertionRewritingHook._warn_already_imported = orig_warn
+
+        self.request.addfinalizer(revert)
+        AssertionRewritingHook._warn_already_imported = lambda *a: None
+
+        rec = []
+
+        class Collect:
+            def pytest_configure(x, config):
+                rec.append(self.make_hook_recorder(config.pluginmanager))
+
+        plugins = kwargs.get("plugins") or []
+        plugins.append(Collect())
+        ret = pytest.main(list(args), plugins=plugins)
+        self.delete_loaded_modules()
+        if len(rec) == 1:
+            reprec = rec.pop()
+        else:
+            class reprec:
+                pass
+        reprec.ret = ret
+
+        # typically we reraise keyboard interrupts from the child run
+        # because it's our user requesting interruption of the testing
+        if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
+            calls = reprec.getcalls("pytest_keyboard_interrupt")
+            if calls and calls[-1].excinfo.type == KeyboardInterrupt:
+                raise KeyboardInterrupt()
+        return reprec
+
+    def runpytest_inprocess(self, *args, **kwargs):
+        """ Return result of running pytest in-process, providing a similar
+        interface to what self.runpytest() provides. """
+        if kwargs.get("syspathinsert"):
+            self.syspathinsert()
+        now = time.time()
+        capture = MultiCapture(Capture=SysCapture)
+        capture.start_capturing()
+        try:
+            try:
+                reprec = self.inline_run(*args, **kwargs)
+            except SystemExit as e:
+
+                class reprec:
+                    ret = e.args[0]
+
+            except Exception:
+                traceback.print_exc()
+
+                class reprec:
+                    ret = 3
+        finally:
+            out, err = capture.readouterr()
+            capture.stop_capturing()
+            sys.stdout.write(out)
+            sys.stderr.write(err)
+
+        res = RunResult(reprec.ret,
+                        out.split("\n"), err.split("\n"),
+                        time.time() - now)
+        res.reprec = reprec
+        return res
+
+    def runpytest(self, *args, **kwargs):
+        """ Run pytest inline or in a subprocess, depending on the command line
+        option "--runpytest" and return a :py:class:`RunResult`.
+
+        """
+        args = self._ensure_basetemp(args)
+        return self._runpytest_method(*args, **kwargs)
+
+    def _ensure_basetemp(self, args):
+        args = [str(x) for x in args]
+        for x in args:
+            if str(x).startswith('--basetemp'):
+                # print("basedtemp exists: %s" %(args,))
+                break
+        else:
+            args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
+            # print("added basetemp: %s" %(args,))
+        return args
+
+    def parseconfig(self, *args):
+        """Return a new pytest Config instance from given commandline args.
+
+        This invokes the pytest bootstrapping code in _pytest.config
+        to create a new :py:class:`_pytest.core.PluginManager` and
+        call the pytest_cmdline_parse hook to create new
+        :py:class:`_pytest.config.Config` instance.
+
+        If :py:attr:`plugins` has been populated they should be plugin
+        modules which will be registered with the PluginManager.
+
+        """
+        args = self._ensure_basetemp(args)
+
+        import _pytest.config
+        config = _pytest.config._prepareconfig(args, self.plugins)
+        # we don't know what the test will do with this half-setup config
+        # object and thus we make sure it gets unconfigured properly in any
+        # case (otherwise capturing could still be active, for example)
+        self.request.addfinalizer(config._ensure_unconfigure)
+        return config
+
+    def parseconfigure(self, *args):
+        """Return a new pytest configured Config instance.
+
+        This returns a new :py:class:`_pytest.config.Config` instance
+        like :py:meth:`parseconfig`, but also calls the
+        pytest_configure hook.
+
+        """
+        config = self.parseconfig(*args)
+        config._do_configure()
+        self.request.addfinalizer(config._ensure_unconfigure)
+        return config
+
+    def getitem(self, source, funcname="test_func"):
+        """Return the test item for a test function.
+
+        This writes the source to a python file and runs pytest's
+        collection on the resulting module, returning the test item
+        for the requested function name.
+
+        :param source: The module source.
+
+        :param funcname: The name of the test function for which the
+           Item must be returned.
+
+        """
+        items = self.getitems(source)
+        for item in items:
+            if item.name == funcname:
+                return item
+        assert 0, "%r item not found in module:\n%s\nitems: %s" % (
+                  funcname, source, items)
+
+    def getitems(self, source):
+        """Return all test items collected from the module.
+
+        This writes the source to a python file and runs pytest's
+        collection on the resulting module, returning all test items
+        contained within.
+
+        """
+        modcol = self.getmodulecol(source)
+        return self.genitems([modcol])
+
+    def getmodulecol(self, source, configargs=(), withinit=False):
+        """Return the module collection node for ``source``.
+
+        This writes ``source`` to a file using :py:meth:`makepyfile`
+        and then runs the pytest collection on it, returning the
+        collection node for the test module.
+
+        :param source: The source code of the module to collect.
+
+        :param configargs: Any extra arguments to pass to
+           :py:meth:`parseconfigure`.
+
+        :param withinit: Whether to also write a ``__init__.py`` file
+           to the temporary directory to ensure it is a package.
+
+        """
+        kw = {self.request.function.__name__: Source(source).strip()}
+        path = self.makepyfile(**kw)
+        if withinit:
+            self.makepyfile(__init__="#")
+        self.config = config = self.parseconfigure(path, *configargs)
+        node = self.getnode(config, path)
+
+        return node
+
+    def collect_by_name(self, modcol, name):
+        """Return the collection node for name from the module collection.
+
+        This will search a module collection node for a collection
+        node matching the given name.
+
+        :param modcol: A module collection node, see
+           :py:meth:`getmodulecol`.
+
+        :param name: The name of the node to return.
+
+        """
+        if modcol not in self._mod_collections:
+            self._mod_collections[modcol] = list(modcol.collect())
+        for colitem in self._mod_collections[modcol]:
+            if colitem.name == name:
+                return colitem
+
+    def popen(self, cmdargs, stdout, stderr, **kw):
+        """Invoke subprocess.Popen.
+
+        This calls subprocess.Popen making sure the current working
+        directory is the PYTHONPATH.
+
+        You probably want to use :py:meth:`run` instead.
+
+        """
+        env = os.environ.copy()
+        env['PYTHONPATH'] = os.pathsep.join(filter(None, [
+            str(os.getcwd()), env.get('PYTHONPATH', '')]))
+        kw['env'] = env
+
+        popen = subprocess.Popen(cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, **kw)
+        popen.stdin.close()
+
+        return popen
+
+    def run(self, *cmdargs):
+        """Run a command with arguments.
+
+        Run a process using subprocess.Popen saving the stdout and
+        stderr.
+
+        Returns a :py:class:`RunResult`.
+
+        """
+        return self._run(*cmdargs)
+
+    def _run(self, *cmdargs):
+        cmdargs = [str(x) for x in cmdargs]
+        p1 = self.tmpdir.join("stdout")
+        p2 = self.tmpdir.join("stderr")
+        print("running:", ' '.join(cmdargs))
+        print("     in:", str(py.path.local()))
+        f1 = codecs.open(str(p1), "w", encoding="utf8")
+        f2 = codecs.open(str(p2), "w", encoding="utf8")
+        try:
+            now = time.time()
+            popen = self.popen(cmdargs, stdout=f1, stderr=f2,
+                               close_fds=(sys.platform != "win32"))
+            ret = popen.wait()
+        finally:
+            f1.close()
+            f2.close()
+        f1 = codecs.open(str(p1), "r", encoding="utf8")
+        f2 = codecs.open(str(p2), "r", encoding="utf8")
+        try:
+            out = f1.read().splitlines()
+            err = f2.read().splitlines()
+        finally:
+            f1.close()
+            f2.close()
+        self._dump_lines(out, sys.stdout)
+        self._dump_lines(err, sys.stderr)
+        return RunResult(ret, out, err, time.time() - now)
+
+    def _dump_lines(self, lines, fp):
+        try:
+            for line in lines:
+                print(line, file=fp)
+        except UnicodeEncodeError:
+            print("couldn't print to %s because of encoding" % (fp,))
+
+    def _getpytestargs(self):
+        # we cannot use "(sys.executable,script)"
+        # because on windows the script is e.g. a pytest.exe
+        return (sys.executable, PYTEST_FULLPATH) # noqa
+
+    def runpython(self, script):
+        """Run a python script using sys.executable as interpreter.
+
+        Returns a :py:class:`RunResult`.
+        """
+        return self.run(sys.executable, script)
+
+    def runpython_c(self, command):
+        """Run python -c "command", return a :py:class:`RunResult`."""
+        return self.run(sys.executable, "-c", command)
+
+    def runpytest_subprocess(self, *args, **kwargs):
+        """Run pytest as a subprocess with given arguments.
+
+        Any plugins added to the :py:attr:`plugins` list will added
+        using the ``-p`` command line option.  Addtionally
+        ``--basetemp`` is used put any temporary files and directories
+        in a numbered directory prefixed with "runpytest-" so they do
+        not conflict with the normal numberd pytest location for
+        temporary files and directories.
+
+        Returns a :py:class:`RunResult`.
+
+        """
+        p = py.path.local.make_numbered_dir(prefix="runpytest-",
+                                            keep=None, rootdir=self.tmpdir)
+        args = ('--basetemp=%s' % p, ) + args
+        # for x in args:
+        #    if '--confcutdir' in str(x):
+        #        break
+        # else:
+        #    pass
+        #    args = ('--confcutdir=.',) + args
+        plugins = [x for x in self.plugins if isinstance(x, str)]
+        if plugins:
+            args = ('-p', plugins[0]) + args
+        args = self._getpytestargs() + args
+        return self.run(*args)
+
+    def spawn_pytest(self, string, expect_timeout=10.0):
+        """Run pytest using pexpect.
+
+        This makes sure to use the right pytest and sets up the
+        temporary directory locations.
+
+        The pexpect child is returned.
+
+        """
+        basetemp = self.tmpdir.mkdir("temp-pexpect")
+        invoke = " ".join(map(str, self._getpytestargs()))
+        cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
+        return self.spawn(cmd, expect_timeout=expect_timeout)
+
+    def spawn(self, cmd, expect_timeout=10.0):
+        """Run a command using pexpect.
+
+        The pexpect child is returned.
+        """
+        pexpect = pytest.importorskip("pexpect", "3.0")
+        if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
+            pytest.skip("pypy-64 bit not supported")
+        if sys.platform.startswith("freebsd"):
+            pytest.xfail("pexpect does not work reliably on freebsd")
+        logfile = self.tmpdir.join("spawn.out").open("wb")
+        child = pexpect.spawn(cmd, logfile=logfile)
+        self.request.addfinalizer(logfile.close)
+        child.timeout = expect_timeout
+        return child
+
+
+def getdecoded(out):
+    try:
+        return out.decode("utf-8")
+    except UnicodeDecodeError:
+        return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
+            py.io.saferepr(out),)
+
+
+class LineComp:
+    def __init__(self):
+        self.stringio = py.io.TextIO()
+
+    def assert_contains_lines(self, lines2):
+        """ assert that lines2 are contained (linearly) in lines1.
+            return a list of extralines found.
+        """
+        __tracebackhide__ = True
+        val = self.stringio.getvalue()
+        self.stringio.truncate(0)
+        self.stringio.seek(0)
+        lines1 = val.split("\n")
+        return LineMatcher(lines1).fnmatch_lines(lines2)
+
+
+class LineMatcher:
+    """Flexible matching of text.
+
+    This is a convenience class to test large texts like the output of
+    commands.
+
+    The constructor takes a list of lines without their trailing
+    newlines, i.e. ``text.splitlines()``.
+
+    """
+
+    def __init__(self, lines):
+        self.lines = lines
+        self._log_output = []
+
+    def str(self):
+        """Return the entire original text."""
+        return "\n".join(self.lines)
+
+    def _getlines(self, lines2):
+        if isinstance(lines2, str):
+            lines2 = Source(lines2)
+        if isinstance(lines2, Source):
+            lines2 = lines2.strip().lines
+        return lines2
+
+    def fnmatch_lines_random(self, lines2):
+        """Check lines exist in the output using ``fnmatch.fnmatch``, in any order.
+
+        The argument is a list of lines which have to occur in the
+        output, in any order.
+        """
+        self._match_lines_random(lines2, fnmatch)
+
+    def re_match_lines_random(self, lines2):
+        """Check lines exist in the output using ``re.match``, in any order.
+
+        The argument is a list of lines which have to occur in the
+        output, in any order.
+
+        """
+        self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
+
+    def _match_lines_random(self, lines2, match_func):
+        """Check lines exist in the output.
+
+        The argument is a list of lines which have to occur in the
+        output, in any order.  Each line can contain glob whildcards.
+
+        """
+        lines2 = self._getlines(lines2)
+        for line in lines2:
+            for x in self.lines:
+                if line == x or match_func(x, line):
+                    self._log("matched: ", repr(line))
+                    break
+            else:
+                self._log("line %r not found in output" % line)
+                raise ValueError(self._log_text)
+
+    def get_lines_after(self, fnline):
+        """Return all lines following the given line in the text.
+
+        The given line can contain glob wildcards.
+        """
+        for i, line in enumerate(self.lines):
+            if fnline == line or fnmatch(line, fnline):
+                return self.lines[i + 1:]
+        raise ValueError("line %r not found in output" % fnline)
+
+    def _log(self, *args):
+        self._log_output.append(' '.join((str(x) for x in args)))
+
+    @property
+    def _log_text(self):
+        return '\n'.join(self._log_output)
+
+    def fnmatch_lines(self, lines2):
+        """Search captured text for matching lines using ``fnmatch.fnmatch``.
+
+        The argument is a list of lines which have to match and can
+        use glob wildcards.  If they do not match a pytest.fail() is
+        called.  The matches and non-matches are also printed on
+        stdout.
+
+        """
+        self._match_lines(lines2, fnmatch, 'fnmatch')
+
+    def re_match_lines(self, lines2):
+        """Search captured text for matching lines using ``re.match``.
+
+        The argument is a list of lines which have to match using ``re.match``.
+        If they do not match a pytest.fail() is called.
+
+        The matches and non-matches are also printed on
+        stdout.
+        """
+        self._match_lines(lines2, lambda name, pat: re.match(pat, name), 're.match')
+
+    def _match_lines(self, lines2, match_func, match_nickname):
+        """Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
+
+        :param list[str] lines2: list of string patterns to match. The actual format depends on
+            ``match_func``.
+        :param match_func: a callable ``match_func(line, pattern)`` where line is the captured
+            line from stdout/stderr and pattern is the matching pattern.
+
+        :param str match_nickname: the nickname for the match function that will be logged
+            to stdout when a match occurs.
+        """
+        lines2 = self._getlines(lines2)
+        lines1 = self.lines[:]
+        nextline = None
+        extralines = []
+        __tracebackhide__ = True
+        for line in lines2:
+            nomatchprinted = False
+            while lines1:
+                nextline = lines1.pop(0)
+                if line == nextline:
+                    self._log("exact match:", repr(line))
+                    break
+                elif match_func(nextline, line):
+                    self._log("%s:" % match_nickname, repr(line))
+                    self._log("   with:", repr(nextline))
+                    break
+                else:
+                    if not nomatchprinted:
+                        self._log("nomatch:", repr(line))
+                        nomatchprinted = True
+                    self._log("    and:", repr(nextline))
+                extralines.append(nextline)
+            else:
+                self._log("remains unmatched: %r" % (line,))
+                pytest.fail(self._log_text)
diff --git a/tools/third_party/pytest/_pytest/python.py b/tools/third_party/pytest/_pytest/python.py
new file mode 100644
index 0000000..650171a
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/python.py
@@ -0,0 +1,1175 @@
+""" Python test discovery, setup and run of test functions. """
+from __future__ import absolute_import, division, print_function
+
+import fnmatch
+import inspect
+import sys
+import os
+import collections
+import warnings
+from textwrap import dedent
+from itertools import count
+
+
+import py
+import six
+from _pytest.mark import MarkerError
+from _pytest.config import hookimpl
+
+import _pytest
+import pluggy
+from _pytest import fixtures
+from _pytest import main
+from _pytest import deprecated
+from _pytest.compat import (
+    isclass, isfunction, is_generator, ascii_escaped,
+    REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
+    get_real_func, getfslineno, safe_getattr,
+    safe_str, getlocation, enum,
+)
+from _pytest.outcomes import fail
+from _pytest.mark import transfer_markers
+
+cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
+cutdir2 = py.path.local(_pytest.__file__).dirpath()
+cutdir3 = py.path.local(py.__file__).dirpath()
+
+
+def filter_traceback(entry):
+    """Return True if a TracebackEntry instance should be removed from tracebacks:
+    * dynamically generated code (no code to show up for it);
+    * internal traceback from pytest or its internal libraries, py and pluggy.
+    """
+    # entry.path might sometimes return a str object when the entry
+    # points to dynamically generated code
+    # see https://bitbucket.org/pytest-dev/py/issues/71
+    raw_filename = entry.frame.code.raw.co_filename
+    is_generated = '<' in raw_filename and '>' in raw_filename
+    if is_generated:
+        return False
+    # entry.path might point to an inexisting file, in which case it will
+    # alsso return a str object. see #1133
+    p = py.path.local(entry.path)
+    return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3)
+
+
+def pyobj_property(name):
+    def get(self):
+        node = self.getparent(getattr(__import__('pytest'), name))
+        if node is not None:
+            return node.obj
+    doc = "python %s object this node was collected from (can be None)." % (
+          name.lower(),)
+    return property(get, None, None, doc)
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group.addoption('--fixtures', '--funcargs',
+                    action="store_true", dest="showfixtures", default=False,
+                    help="show available fixtures, sorted by plugin appearance")
+    group.addoption(
+        '--fixtures-per-test',
+        action="store_true",
+        dest="show_fixtures_per_test",
+        default=False,
+        help="show fixtures per test",
+    )
+    parser.addini("usefixtures", type="args", default=[],
+                  help="list of default fixtures to be used with this project")
+    parser.addini("python_files", type="args",
+                  default=['test_*.py', '*_test.py'],
+                  help="glob-style file patterns for Python test module discovery")
+    parser.addini("python_classes", type="args", default=["Test", ],
+                  help="prefixes or glob names for Python test class discovery")
+    parser.addini("python_functions", type="args", default=["test", ],
+                  help="prefixes or glob names for Python test function and "
+                  "method discovery")
+
+    group.addoption("--import-mode", default="prepend",
+                    choices=["prepend", "append"], dest="importmode",
+                    help="prepend/append to sys.path when importing test modules, "
+                    "default is to prepend.")
+
+
+def pytest_cmdline_main(config):
+    if config.option.showfixtures:
+        showfixtures(config)
+        return 0
+    if config.option.show_fixtures_per_test:
+        show_fixtures_per_test(config)
+        return 0
+
+
+def pytest_generate_tests(metafunc):
+    # those alternative spellings are common - raise a specific error to alert
+    # the user
+    alt_spellings = ['parameterize', 'parametrise', 'parameterise']
+    for attr in alt_spellings:
+        if hasattr(metafunc.function, attr):
+            msg = "{0} has '{1}', spelling should be 'parametrize'"
+            raise MarkerError(msg.format(metafunc.function.__name__, attr))
+    try:
+        markers = metafunc.function.parametrize
+    except AttributeError:
+        return
+    for marker in markers:
+        metafunc.parametrize(*marker.args, **marker.kwargs)
+
+
+def pytest_configure(config):
+    config.addinivalue_line("markers",
+                            "parametrize(argnames, argvalues): call a test function multiple "
+                            "times passing in different arguments in turn. argvalues generally "
+                            "needs to be a list of values if argnames specifies only one name "
+                            "or a list of tuples of values if argnames specifies multiple names. "
+                            "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
+                            "decorated test function, one with arg1=1 and another with arg1=2."
+                            "see http://pytest.org/latest/parametrize.html for more info and "
+                            "examples."
+                            )
+    config.addinivalue_line("markers",
+                            "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
+                            "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
+                            )
+
+
+@hookimpl(trylast=True)
+def pytest_pyfunc_call(pyfuncitem):
+    testfunction = pyfuncitem.obj
+    if pyfuncitem._isyieldedfunction():
+        testfunction(*pyfuncitem._args)
+    else:
+        funcargs = pyfuncitem.funcargs
+        testargs = {}
+        for arg in pyfuncitem._fixtureinfo.argnames:
+            testargs[arg] = funcargs[arg]
+        testfunction(**testargs)
+    return True
+
+
+def pytest_collect_file(path, parent):
+    ext = path.ext
+    if ext == ".py":
+        if not parent.session.isinitpath(path):
+            for pat in parent.config.getini('python_files'):
+                if path.fnmatch(pat):
+                    break
+            else:
+                return
+        ihook = parent.session.gethookproxy(path)
+        return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
+
+
+def pytest_pycollect_makemodule(path, parent):
+    return Module(path, parent)
+
+
+@hookimpl(hookwrapper=True)
+def pytest_pycollect_makeitem(collector, name, obj):
+    outcome = yield
+    res = outcome.get_result()
+    if res is not None:
+        return
+    # nothing was collected elsewhere, let's do it here
+    if isclass(obj):
+        if collector.istestclass(obj, name):
+            Class = collector._getcustomclass("Class")
+            outcome.force_result(Class(name, parent=collector))
+    elif collector.istestfunction(obj, name):
+        # mock seems to store unbound methods (issue473), normalize it
+        obj = getattr(obj, "__func__", obj)
+        # We need to try and unwrap the function if it's a functools.partial
+        # or a funtools.wrapped.
+        # We musn't if it's been wrapped with mock.patch (python 2 only)
+        if not (isfunction(obj) or isfunction(get_real_func(obj))):
+            collector.warn(code="C2", message="cannot collect %r because it is not a function."
+                           % name, )
+        elif getattr(obj, "__test__", True):
+            if is_generator(obj):
+                res = Generator(name, parent=collector)
+            else:
+                res = list(collector._genfunctions(name, obj))
+            outcome.force_result(res)
+
+
+def pytest_make_parametrize_id(config, val, argname=None):
+    return None
+
+
+class PyobjContext(object):
+    module = pyobj_property("Module")
+    cls = pyobj_property("Class")
+    instance = pyobj_property("Instance")
+
+
+class PyobjMixin(PyobjContext):
+    def obj():
+        def fget(self):
+            obj = getattr(self, '_obj', None)
+            if obj is None:
+                self._obj = obj = self._getobj()
+            return obj
+
+        def fset(self, value):
+            self._obj = value
+
+        return property(fget, fset, None, "underlying python object")
+
+    obj = obj()
+
+    def _getobj(self):
+        return getattr(self.parent.obj, self.name)
+
+    def getmodpath(self, stopatmodule=True, includemodule=False):
+        """ return python path relative to the containing module. """
+        chain = self.listchain()
+        chain.reverse()
+        parts = []
+        for node in chain:
+            if isinstance(node, Instance):
+                continue
+            name = node.name
+            if isinstance(node, Module):
+                name = os.path.splitext(name)[0]
+                if stopatmodule:
+                    if includemodule:
+                        parts.append(name)
+                    break
+            parts.append(name)
+        parts.reverse()
+        s = ".".join(parts)
+        return s.replace(".[", "[")
+
+    def _getfslineno(self):
+        return getfslineno(self.obj)
+
+    def reportinfo(self):
+        # XXX caching?
+        obj = self.obj
+        compat_co_firstlineno = getattr(obj, 'compat_co_firstlineno', None)
+        if isinstance(compat_co_firstlineno, int):
+            # nose compatibility
+            fspath = sys.modules[obj.__module__].__file__
+            if fspath.endswith(".pyc"):
+                fspath = fspath[:-1]
+            lineno = compat_co_firstlineno
+        else:
+            fspath, lineno = getfslineno(obj)
+        modpath = self.getmodpath()
+        assert isinstance(lineno, int)
+        return fspath, lineno, modpath
+
+
+class PyCollector(PyobjMixin, main.Collector):
+
+    def funcnamefilter(self, name):
+        return self._matches_prefix_or_glob_option('python_functions', name)
+
+    def isnosetest(self, obj):
+        """ Look for the __test__ attribute, which is applied by the
+        @nose.tools.istest decorator
+        """
+        # We explicitly check for "is True" here to not mistakenly treat
+        # classes with a custom __getattr__ returning something truthy (like a
+        # function) as test classes.
+        return safe_getattr(obj, '__test__', False) is True
+
+    def classnamefilter(self, name):
+        return self._matches_prefix_or_glob_option('python_classes', name)
+
+    def istestfunction(self, obj, name):
+        if self.funcnamefilter(name) or self.isnosetest(obj):
+            if isinstance(obj, staticmethod):
+                # static methods need to be unwrapped
+                obj = safe_getattr(obj, '__func__', False)
+                if obj is False:
+                    # Python 2.6 wraps in a different way that we won't try to handle
+                    msg = "cannot collect static method %r because " \
+                          "it is not a function (always the case in Python 2.6)"
+                    self.warn(
+                        code="C2", message=msg % name)
+                    return False
+            return (
+                safe_getattr(obj, "__call__", False) and fixtures.getfixturemarker(obj) is None
+            )
+        else:
+            return False
+
+    def istestclass(self, obj, name):
+        return self.classnamefilter(name) or self.isnosetest(obj)
+
+    def _matches_prefix_or_glob_option(self, option_name, name):
+        """
+        checks if the given name matches the prefix or glob-pattern defined
+        in ini configuration.
+        """
+        for option in self.config.getini(option_name):
+            if name.startswith(option):
+                return True
+            # check that name looks like a glob-string before calling fnmatch
+            # because this is called for every name in each collected module,
+            # and fnmatch is somewhat expensive to call
+            elif ('*' in option or '?' in option or '[' in option) and \
+                    fnmatch.fnmatch(name, option):
+                return True
+        return False
+
+    def collect(self):
+        if not getattr(self.obj, "__test__", True):
+            return []
+
+        # NB. we avoid random getattrs and peek in the __dict__ instead
+        # (XXX originally introduced from a PyPy need, still true?)
+        dicts = [getattr(self.obj, '__dict__', {})]
+        for basecls in inspect.getmro(self.obj.__class__):
+            dicts.append(basecls.__dict__)
+        seen = {}
+        values = []
+        for dic in dicts:
+            for name, obj in list(dic.items()):
+                if name in seen:
+                    continue
+                seen[name] = True
+                res = self._makeitem(name, obj)
+                if res is None:
+                    continue
+                if not isinstance(res, list):
+                    res = [res]
+                values.extend(res)
+        values.sort(key=lambda item: item.reportinfo()[:2])
+        return values
+
+    def makeitem(self, name, obj):
+        warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2)
+        self._makeitem(name, obj)
+
+    def _makeitem(self, name, obj):
+        # assert self.ihook.fspath == self.fspath, self
+        return self.ihook.pytest_pycollect_makeitem(
+            collector=self, name=name, obj=obj)
+
+    def _genfunctions(self, name, funcobj):
+        module = self.getparent(Module).obj
+        clscol = self.getparent(Class)
+        cls = clscol and clscol.obj or None
+        transfer_markers(funcobj, cls, module)
+        fm = self.session._fixturemanager
+        fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
+        metafunc = Metafunc(funcobj, fixtureinfo, self.config,
+                            cls=cls, module=module)
+        methods = []
+        if hasattr(module, "pytest_generate_tests"):
+            methods.append(module.pytest_generate_tests)
+        if hasattr(cls, "pytest_generate_tests"):
+            methods.append(cls().pytest_generate_tests)
+        if methods:
+            self.ihook.pytest_generate_tests.call_extra(methods,
+                                                        dict(metafunc=metafunc))
+        else:
+            self.ihook.pytest_generate_tests(metafunc=metafunc)
+
+        Function = self._getcustomclass("Function")
+        if not metafunc._calls:
+            yield Function(name, parent=self, fixtureinfo=fixtureinfo)
+        else:
+            # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
+            fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
+
+            for callspec in metafunc._calls:
+                subname = "%s[%s]" % (name, callspec.id)
+                yield Function(name=subname, parent=self,
+                               callspec=callspec, callobj=funcobj,
+                               fixtureinfo=fixtureinfo,
+                               keywords={callspec.id: True},
+                               originalname=name,
+                               )
+
+
+class Module(main.File, PyCollector):
+    """ Collector for test classes and functions. """
+
+    def _getobj(self):
+        return self._importtestmodule()
+
+    def collect(self):
+        self.session._fixturemanager.parsefactories(self)
+        return super(Module, self).collect()
+
+    def _importtestmodule(self):
+        # we assume we are only called once per module
+        importmode = self.config.getoption("--import-mode")
+        try:
+            mod = self.fspath.pyimport(ensuresyspath=importmode)
+        except SyntaxError:
+            raise self.CollectError(
+                _pytest._code.ExceptionInfo().getrepr(style="short"))
+        except self.fspath.ImportMismatchError:
+            e = sys.exc_info()[1]
+            raise self.CollectError(
+                "import file mismatch:\n"
+                "imported module %r has this __file__ attribute:\n"
+                "  %s\n"
+                "which is not the same as the test file we want to collect:\n"
+                "  %s\n"
+                "HINT: remove __pycache__ / .pyc files and/or use a "
+                "unique basename for your test file modules"
+                % e.args
+            )
+        except ImportError:
+            from _pytest._code.code import ExceptionInfo
+            exc_info = ExceptionInfo()
+            if self.config.getoption('verbose') < 2:
+                exc_info.traceback = exc_info.traceback.filter(filter_traceback)
+            exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly()
+            formatted_tb = safe_str(exc_repr)
+            raise self.CollectError(
+                "ImportError while importing test module '{fspath}'.\n"
+                "Hint: make sure your test modules/packages have valid Python names.\n"
+                "Traceback:\n"
+                "{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
+            )
+        except _pytest.runner.Skipped as e:
+            if e.allow_module_level:
+                raise
+            raise self.CollectError(
+                "Using pytest.skip outside of a test is not allowed. "
+                "To decorate a test function, use the @pytest.mark.skip "
+                "or @pytest.mark.skipif decorators instead, and to skip a "
+                "module use `pytestmark = pytest.mark.{skip,skipif}."
+            )
+        self.config.pluginmanager.consider_module(mod)
+        return mod
+
+    def setup(self):
+        setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule")
+        if setup_module is None:
+            setup_module = _get_xunit_setup_teardown(self.obj, "setup_module")
+        if setup_module is not None:
+            setup_module()
+
+        teardown_module = _get_xunit_setup_teardown(self.obj, 'tearDownModule')
+        if teardown_module is None:
+            teardown_module = _get_xunit_setup_teardown(self.obj, 'teardown_module')
+        if teardown_module is not None:
+            self.addfinalizer(teardown_module)
+
+
+def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
+    """
+    Return a callable to perform xunit-style setup or teardown if
+    the function exists in the ``holder`` object.
+    The ``param_obj`` parameter is the parameter which will be passed to the function
+    when the callable is called without arguments, defaults to the ``holder`` object.
+    Return ``None`` if a suitable callable is not found.
+    """
+    param_obj = param_obj if param_obj is not None else holder
+    result = _get_xunit_func(holder, attr_name)
+    if result is not None:
+        arg_count = result.__code__.co_argcount
+        if inspect.ismethod(result):
+            arg_count -= 1
+        if arg_count:
+            return lambda: result(param_obj)
+        else:
+            return result
+
+
+def _get_xunit_func(obj, name):
+    """Return the attribute from the given object to be used as a setup/teardown
+    xunit-style function, but only if not marked as a fixture to
+    avoid calling it twice.
+    """
+    meth = getattr(obj, name, None)
+    if fixtures.getfixturemarker(meth) is None:
+        return meth
+
+
+class Class(PyCollector):
+    """ Collector for test methods. """
+
+    def collect(self):
+        if not safe_getattr(self.obj, "__test__", True):
+            return []
+        if hasinit(self.obj):
+            self.warn("C1", "cannot collect test class %r because it has a "
+                      "__init__ constructor" % self.obj.__name__)
+            return []
+        elif hasnew(self.obj):
+            self.warn("C1", "cannot collect test class %r because it has a "
+                            "__new__ constructor" % self.obj.__name__)
+            return []
+        return [self._getcustomclass("Instance")(name="()", parent=self)]
+
+    def setup(self):
+        setup_class = _get_xunit_func(self.obj, 'setup_class')
+        if setup_class is not None:
+            setup_class = getattr(setup_class, 'im_func', setup_class)
+            setup_class = getattr(setup_class, '__func__', setup_class)
+            setup_class(self.obj)
+
+        fin_class = getattr(self.obj, 'teardown_class', None)
+        if fin_class is not None:
+            fin_class = getattr(fin_class, 'im_func', fin_class)
+            fin_class = getattr(fin_class, '__func__', fin_class)
+            self.addfinalizer(lambda: fin_class(self.obj))
+
+
+class Instance(PyCollector):
+    def _getobj(self):
+        return self.parent.obj()
+
+    def collect(self):
+        self.session._fixturemanager.parsefactories(self)
+        return super(Instance, self).collect()
+
+    def newinstance(self):
+        self.obj = self._getobj()
+        return self.obj
+
+
+class FunctionMixin(PyobjMixin):
+    """ mixin for the code common to Function and Generator.
+    """
+
+    def setup(self):
+        """ perform setup for this test function. """
+        if hasattr(self, '_preservedparent'):
+            obj = self._preservedparent
+        elif isinstance(self.parent, Instance):
+            obj = self.parent.newinstance()
+            self.obj = self._getobj()
+        else:
+            obj = self.parent.obj
+        if inspect.ismethod(self.obj):
+            setup_name = 'setup_method'
+            teardown_name = 'teardown_method'
+        else:
+            setup_name = 'setup_function'
+            teardown_name = 'teardown_function'
+        setup_func_or_method = _get_xunit_setup_teardown(obj, setup_name, param_obj=self.obj)
+        if setup_func_or_method is not None:
+            setup_func_or_method()
+        teardown_func_or_method = _get_xunit_setup_teardown(obj, teardown_name, param_obj=self.obj)
+        if teardown_func_or_method is not None:
+            self.addfinalizer(teardown_func_or_method)
+
+    def _prunetraceback(self, excinfo):
+        if hasattr(self, '_obj') and not self.config.option.fulltrace:
+            code = _pytest._code.Code(get_real_func(self.obj))
+            path, firstlineno = code.path, code.firstlineno
+            traceback = excinfo.traceback
+            ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
+            if ntraceback == traceback:
+                ntraceback = ntraceback.cut(path=path)
+                if ntraceback == traceback:
+                    # ntraceback = ntraceback.cut(excludepath=cutdir2)
+                    ntraceback = ntraceback.filter(filter_traceback)
+                    if not ntraceback:
+                        ntraceback = traceback
+
+            excinfo.traceback = ntraceback.filter()
+            # issue364: mark all but first and last frames to
+            # only show a single-line message for each frame
+            if self.config.option.tbstyle == "auto":
+                if len(excinfo.traceback) > 2:
+                    for entry in excinfo.traceback[1:-1]:
+                        entry.set_repr_style('short')
+
+    def _repr_failure_py(self, excinfo, style="long"):
+        if excinfo.errisinstance(fail.Exception):
+            if not excinfo.value.pytrace:
+                return py._builtin._totext(excinfo.value)
+        return super(FunctionMixin, self)._repr_failure_py(excinfo,
+                                                           style=style)
+
+    def repr_failure(self, excinfo, outerr=None):
+        assert outerr is None, "XXX outerr usage is deprecated"
+        style = self.config.option.tbstyle
+        if style == "auto":
+            style = "long"
+        return self._repr_failure_py(excinfo, style=style)
+
+
+class Generator(FunctionMixin, PyCollector):
+    def collect(self):
+        # test generators are seen as collectors but they also
+        # invoke setup/teardown on popular request
+        # (induced by the common "test_*" naming shared with normal tests)
+        from _pytest import deprecated
+        self.session._setupstate.prepare(self)
+        # see FunctionMixin.setup and test_setupstate_is_preserved_134
+        self._preservedparent = self.parent.obj
+        values = []
+        seen = {}
+        for i, x in enumerate(self.obj()):
+            name, call, args = self.getcallargs(x)
+            if not callable(call):
+                raise TypeError("%r yielded non callable test %r" % (self.obj, call,))
+            if name is None:
+                name = "[%d]" % i
+            else:
+                name = "['%s']" % name
+            if name in seen:
+                raise ValueError("%r generated tests with non-unique name %r" % (self, name))
+            seen[name] = True
+            values.append(self.Function(name, self, args=args, callobj=call))
+        self.warn('C1', deprecated.YIELD_TESTS)
+        return values
+
+    def getcallargs(self, obj):
+        if not isinstance(obj, (tuple, list)):
+            obj = (obj,)
+        # explicit naming
+        if isinstance(obj[0], six.string_types):
+            name = obj[0]
+            obj = obj[1:]
+        else:
+            name = None
+        call, args = obj[0], obj[1:]
+        return name, call, args
+
+
+def hasinit(obj):
+    init = getattr(obj, '__init__', None)
+    if init:
+        return init != object.__init__
+
+
+def hasnew(obj):
+    new = getattr(obj, '__new__', None)
+    if new:
+        return new != object.__new__
+
+
+class CallSpec2(object):
+    def __init__(self, metafunc):
+        self.metafunc = metafunc
+        self.funcargs = {}
+        self._idlist = []
+        self.params = {}
+        self._globalid = NOTSET
+        self._globalid_args = set()
+        self._globalparam = NOTSET
+        self._arg2scopenum = {}  # used for sorting parametrized resources
+        self.marks = []
+        self.indices = {}
+
+    def copy(self, metafunc):
+        cs = CallSpec2(self.metafunc)
+        cs.funcargs.update(self.funcargs)
+        cs.params.update(self.params)
+        cs.marks.extend(self.marks)
+        cs.indices.update(self.indices)
+        cs._arg2scopenum.update(self._arg2scopenum)
+        cs._idlist = list(self._idlist)
+        cs._globalid = self._globalid
+        cs._globalid_args = self._globalid_args
+        cs._globalparam = self._globalparam
+        return cs
+
+    def _checkargnotcontained(self, arg):
+        if arg in self.params or arg in self.funcargs:
+            raise ValueError("duplicate %r" % (arg,))
+
+    def getparam(self, name):
+        try:
+            return self.params[name]
+        except KeyError:
+            if self._globalparam is NOTSET:
+                raise ValueError(name)
+            return self._globalparam
+
+    @property
+    def id(self):
+        return "-".join(map(str, filter(None, self._idlist)))
+
+    def setmulti2(self, valtypes, argnames, valset, id, marks, scopenum,
+                  param_index):
+        for arg, val in zip(argnames, valset):
+            self._checkargnotcontained(arg)
+            valtype_for_arg = valtypes[arg]
+            getattr(self, valtype_for_arg)[arg] = val
+            self.indices[arg] = param_index
+            self._arg2scopenum[arg] = scopenum
+        self._idlist.append(id)
+        self.marks.extend(marks)
+
+    def setall(self, funcargs, id, param):
+        for x in funcargs:
+            self._checkargnotcontained(x)
+        self.funcargs.update(funcargs)
+        if id is not NOTSET:
+            self._idlist.append(id)
+        if param is not NOTSET:
+            assert self._globalparam is NOTSET
+            self._globalparam = param
+        for arg in funcargs:
+            self._arg2scopenum[arg] = fixtures.scopenum_function
+
+
+class Metafunc(fixtures.FuncargnamesCompatAttr):
+    """
+    Metafunc objects are passed to the ``pytest_generate_tests`` hook.
+    They help to inspect a test function and to generate tests according to
+    test configuration or values specified in the class or module where a
+    test function is defined.
+    """
+
+    def __init__(self, function, fixtureinfo, config, cls=None, module=None):
+        #: access to the :class:`_pytest.config.Config` object for the test session
+        self.config = config
+
+        #: the module object where the test function is defined in.
+        self.module = module
+
+        #: underlying python test function
+        self.function = function
+
+        #: set of fixture names required by the test function
+        self.fixturenames = fixtureinfo.names_closure
+
+        #: class object where the test function is defined in or ``None``.
+        self.cls = cls
+
+        self._calls = []
+        self._ids = set()
+        self._arg2fixturedefs = fixtureinfo.name2fixturedefs
+
+    def parametrize(self, argnames, argvalues, indirect=False, ids=None,
+                    scope=None):
+        """ Add new invocations to the underlying test function using the list
+        of argvalues for the given argnames.  Parametrization is performed
+        during the collection phase.  If you need to setup expensive resources
+        see about setting indirect to do it rather at test setup time.
+
+        :arg argnames: a comma-separated string denoting one or more argument
+                       names, or a list/tuple of argument strings.
+
+        :arg argvalues: The list of argvalues determines how often a
+            test is invoked with different argument values.  If only one
+            argname was specified argvalues is a list of values.  If N
+            argnames were specified, argvalues must be a list of N-tuples,
+            where each tuple-element specifies a value for its respective
+            argname.
+
+        :arg indirect: The list of argnames or boolean. A list of arguments'
+            names (subset of argnames). If True the list contains all names from
+            the argnames. Each argvalue corresponding to an argname in this list will
+            be passed as request.param to its respective argname fixture
+            function so that it can perform more expensive setups during the
+            setup phase of a test rather than at collection time.
+
+        :arg ids: list of string ids, or a callable.
+            If strings, each is corresponding to the argvalues so that they are
+            part of the test id. If None is given as id of specific test, the
+            automatically generated id for that argument will be used.
+            If callable, it should take one argument (a single argvalue) and return
+            a string or return None. If None, the automatically generated id for that
+            argument will be used.
+            If no ids are provided they will be generated automatically from
+            the argvalues.
+
+        :arg scope: if specified it denotes the scope of the parameters.
+            The scope is used for grouping tests by parameter instances.
+            It will also override any fixture-function defined scope, allowing
+            to set a dynamic scope using test context or configuration.
+        """
+        from _pytest.fixtures import scope2index
+        from _pytest.mark import ParameterSet
+        from py.io import saferepr
+        argnames, parameters = ParameterSet._for_parameterize(
+            argnames, argvalues, self.function)
+        del argvalues
+
+        if scope is None:
+            scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
+
+        scopenum = scope2index(scope, descr='call to {0}'.format(self.parametrize))
+        valtypes = {}
+        for arg in argnames:
+            if arg not in self.fixturenames:
+                if isinstance(indirect, (tuple, list)):
+                    name = 'fixture' if arg in indirect else 'argument'
+                else:
+                    name = 'fixture' if indirect else 'argument'
+                raise ValueError(
+                    "%r uses no %s %r" % (
+                        self.function, name, arg))
+
+        if indirect is True:
+            valtypes = dict.fromkeys(argnames, "params")
+        elif indirect is False:
+            valtypes = dict.fromkeys(argnames, "funcargs")
+        elif isinstance(indirect, (tuple, list)):
+            valtypes = dict.fromkeys(argnames, "funcargs")
+            for arg in indirect:
+                if arg not in argnames:
+                    raise ValueError("indirect given to %r: fixture %r doesn't exist" % (
+                                     self.function, arg))
+                valtypes[arg] = "params"
+        idfn = None
+        if callable(ids):
+            idfn = ids
+            ids = None
+        if ids:
+            if len(ids) != len(parameters):
+                raise ValueError('%d tests specified with %d ids' % (
+                                 len(parameters), len(ids)))
+            for id_value in ids:
+                if id_value is not None and not isinstance(id_value, six.string_types):
+                    msg = 'ids must be list of strings, found: %s (type: %s)'
+                    raise ValueError(msg % (saferepr(id_value), type(id_value).__name__))
+        ids = idmaker(argnames, parameters, idfn, ids, self.config)
+        newcalls = []
+        for callspec in self._calls or [CallSpec2(self)]:
+            elements = zip(ids, parameters, count())
+            for a_id, param, param_index in elements:
+                if len(param.values) != len(argnames):
+                    raise ValueError(
+                        'In "parametrize" the number of values ({0}) must be '
+                        'equal to the number of names ({1})'.format(
+                            param.values, argnames))
+                newcallspec = callspec.copy(self)
+                newcallspec.setmulti2(valtypes, argnames, param.values, a_id,
+                                      param.marks, scopenum, param_index)
+                newcalls.append(newcallspec)
+        self._calls = newcalls
+
+    def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
+        """ Add a new call to the underlying test function during the collection phase of a test run.
+
+        .. deprecated:: 3.3
+
+            Use :meth:`parametrize` instead.
+
+        Note that request.addcall() is called during the test collection phase prior and
+        independently to actual test execution.  You should only use addcall()
+        if you need to specify multiple arguments of a test function.
+
+        :arg funcargs: argument keyword dictionary used when invoking
+            the test function.
+
+        :arg id: used for reporting and identification purposes.  If you
+            don't supply an `id` an automatic unique id will be generated.
+
+        :arg param: a parameter which will be exposed to a later fixture function
+            invocation through the ``request.param`` attribute.
+        """
+        if self.config:
+            self.config.warn('C1', message=deprecated.METAFUNC_ADD_CALL, fslocation=None)
+        assert funcargs is None or isinstance(funcargs, dict)
+        if funcargs is not None:
+            for name in funcargs:
+                if name not in self.fixturenames:
+                    fail("funcarg %r not used in this function." % name)
+        else:
+            funcargs = {}
+        if id is None:
+            raise ValueError("id=None not allowed")
+        if id is NOTSET:
+            id = len(self._calls)
+        id = str(id)
+        if id in self._ids:
+            raise ValueError("duplicate id %r" % id)
+        self._ids.add(id)
+
+        cs = CallSpec2(self)
+        cs.setall(funcargs, id, param)
+        self._calls.append(cs)
+
+
+def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
+    """Find the most appropriate scope for a parametrized call based on its arguments.
+
+    When there's at least one direct argument, always use "function" scope.
+
+    When a test function is parametrized and all its arguments are indirect
+    (e.g. fixtures), return the most narrow scope based on the fixtures used.
+
+    Related to issue #1832, based on code posted by @Kingdread.
+    """
+    from _pytest.fixtures import scopes
+    indirect_as_list = isinstance(indirect, (list, tuple))
+    all_arguments_are_fixtures = indirect is True or \
+        indirect_as_list and len(indirect) == argnames
+    if all_arguments_are_fixtures:
+        fixturedefs = arg2fixturedefs or {}
+        used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
+        if used_scopes:
+            # Takes the most narrow scope from used fixtures
+            for scope in reversed(scopes):
+                if scope in used_scopes:
+                    return scope
+
+    return 'function'
+
+
+def _idval(val, argname, idx, idfn, config=None):
+    if idfn:
+        s = None
+        try:
+            s = idfn(val)
+        except Exception:
+            # See issue https://github.com/pytest-dev/pytest/issues/2169
+            import warnings
+            msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx)
+            msg += '\nUpdate your code as this will raise an error in pytest-4.0.'
+            warnings.warn(msg, DeprecationWarning)
+        if s:
+            return ascii_escaped(s)
+
+    if config:
+        hook_id = config.hook.pytest_make_parametrize_id(
+            config=config, val=val, argname=argname)
+        if hook_id:
+            return hook_id
+
+    if isinstance(val, STRING_TYPES):
+        return ascii_escaped(val)
+    elif isinstance(val, (float, int, bool, NoneType)):
+        return str(val)
+    elif isinstance(val, REGEX_TYPE):
+        return ascii_escaped(val.pattern)
+    elif enum is not None and isinstance(val, enum.Enum):
+        return str(val)
+    elif isclass(val) and hasattr(val, '__name__'):
+        return val.__name__
+    return str(argname) + str(idx)
+
+
+def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
+    if parameterset.id is not None:
+        return parameterset.id
+    if ids is None or (idx >= len(ids) or ids[idx] is None):
+        this_id = [_idval(val, argname, idx, idfn, config)
+                   for val, argname in zip(parameterset.values, argnames)]
+        return "-".join(this_id)
+    else:
+        return ascii_escaped(ids[idx])
+
+
+def idmaker(argnames, parametersets, idfn=None, ids=None, config=None):
+    ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config)
+           for valindex, parameterset in enumerate(parametersets)]
+    if len(set(ids)) != len(ids):
+        # The ids are not unique
+        duplicates = [testid for testid in ids if ids.count(testid) > 1]
+        counters = collections.defaultdict(lambda: 0)
+        for index, testid in enumerate(ids):
+            if testid in duplicates:
+                ids[index] = testid + str(counters[testid])
+                counters[testid] += 1
+    return ids
+
+
+def show_fixtures_per_test(config):
+    from _pytest.main import wrap_session
+    return wrap_session(config, _show_fixtures_per_test)
+
+
+def _show_fixtures_per_test(config, session):
+    import _pytest.config
+    session.perform_collect()
+    curdir = py.path.local()
+    tw = _pytest.config.create_terminal_writer(config)
+    verbose = config.getvalue("verbose")
+
+    def get_best_relpath(func):
+        loc = getlocation(func, curdir)
+        return curdir.bestrelpath(loc)
+
+    def write_fixture(fixture_def):
+        argname = fixture_def.argname
+        if verbose <= 0 and argname.startswith("_"):
+            return
+        if verbose > 0:
+            bestrel = get_best_relpath(fixture_def.func)
+            funcargspec = "{0} -- {1}".format(argname, bestrel)
+        else:
+            funcargspec = argname
+        tw.line(funcargspec, green=True)
+        fixture_doc = fixture_def.func.__doc__
+        if fixture_doc:
+            write_docstring(tw, fixture_doc)
+        else:
+            tw.line('    no docstring available', red=True)
+
+    def write_item(item):
+        try:
+            info = item._fixtureinfo
+        except AttributeError:
+            # doctests items have no _fixtureinfo attribute
+            return
+        if not info.name2fixturedefs:
+            # this test item does not use any fixtures
+            return
+        tw.line()
+        tw.sep('-', 'fixtures used by {0}'.format(item.name))
+        tw.sep('-', '({0})'.format(get_best_relpath(item.function)))
+        # dict key not used in loop but needed for sorting
+        for _, fixturedefs in sorted(info.name2fixturedefs.items()):
+            assert fixturedefs is not None
+            if not fixturedefs:
+                continue
+            # last item is expected to be the one used by the test item
+            write_fixture(fixturedefs[-1])
+
+    for session_item in session.items:
+        write_item(session_item)
+
+
+def showfixtures(config):
+    from _pytest.main import wrap_session
+    return wrap_session(config, _showfixtures_main)
+
+
+def _showfixtures_main(config, session):
+    import _pytest.config
+    session.perform_collect()
+    curdir = py.path.local()
+    tw = _pytest.config.create_terminal_writer(config)
+    verbose = config.getvalue("verbose")
+
+    fm = session._fixturemanager
+
+    available = []
+    seen = set()
+
+    for argname, fixturedefs in fm._arg2fixturedefs.items():
+        assert fixturedefs is not None
+        if not fixturedefs:
+            continue
+        for fixturedef in fixturedefs:
+            loc = getlocation(fixturedef.func, curdir)
+            if (fixturedef.argname, loc) in seen:
+                continue
+            seen.add((fixturedef.argname, loc))
+            available.append((len(fixturedef.baseid),
+                              fixturedef.func.__module__,
+                              curdir.bestrelpath(loc),
+                              fixturedef.argname, fixturedef))
+
+    available.sort()
+    currentmodule = None
+    for baseid, module, bestrel, argname, fixturedef in available:
+        if currentmodule != module:
+            if not module.startswith("_pytest."):
+                tw.line()
+                tw.sep("-", "fixtures defined from %s" % (module,))
+                currentmodule = module
+        if verbose <= 0 and argname[0] == "_":
+            continue
+        if verbose > 0:
+            funcargspec = "%s -- %s" % (argname, bestrel,)
+        else:
+            funcargspec = argname
+        tw.line(funcargspec, green=True)
+        loc = getlocation(fixturedef.func, curdir)
+        doc = fixturedef.func.__doc__ or ""
+        if doc:
+            write_docstring(tw, doc)
+        else:
+            tw.line("    %s: no docstring available" % (loc,),
+                    red=True)
+
+
+def write_docstring(tw, doc):
+    INDENT = "    "
+    doc = doc.rstrip()
+    if "\n" in doc:
+        firstline, rest = doc.split("\n", 1)
+    else:
+        firstline, rest = doc, ""
+
+    if firstline.strip():
+        tw.line(INDENT + firstline.strip())
+
+    if rest:
+        for line in dedent(rest).split("\n"):
+            tw.write(INDENT + line + "\n")
+
+
+class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr):
+    """ a Function Item is responsible for setting up and executing a
+    Python test function.
+    """
+    _genid = None
+
+    def __init__(self, name, parent, args=None, config=None,
+                 callspec=None, callobj=NOTSET, keywords=None, session=None,
+                 fixtureinfo=None, originalname=None):
+        super(Function, self).__init__(name, parent, config=config,
+                                       session=session)
+        self._args = args
+        if callobj is not NOTSET:
+            self.obj = callobj
+
+        self.keywords.update(self.obj.__dict__)
+        if callspec:
+            self.callspec = callspec
+            # this is total hostile and a mess
+            # keywords are broken by design by now
+            # this will be redeemed later
+            for mark in callspec.marks:
+                # feel free to cry, this was broken for years before
+                # and keywords cant fix it per design
+                self.keywords[mark.name] = mark
+        if keywords:
+            self.keywords.update(keywords)
+
+        if fixtureinfo is None:
+            fixtureinfo = self.session._fixturemanager.getfixtureinfo(
+                self.parent, self.obj, self.cls,
+                funcargs=not self._isyieldedfunction())
+        self._fixtureinfo = fixtureinfo
+        self.fixturenames = fixtureinfo.names_closure
+        self._initrequest()
+
+        #: original function name, without any decorations (for example
+        #: parametrization adds a ``"[...]"`` suffix to function names).
+        #:
+        #: .. versionadded:: 3.0
+        self.originalname = originalname
+
+    def _initrequest(self):
+        self.funcargs = {}
+        if self._isyieldedfunction():
+            assert not hasattr(self, "callspec"), (
+                "yielded functions (deprecated) cannot have funcargs")
+        else:
+            if hasattr(self, "callspec"):
+                callspec = self.callspec
+                assert not callspec.funcargs
+                self._genid = callspec.id
+                if hasattr(callspec, "param"):
+                    self.param = callspec.param
+        self._request = fixtures.FixtureRequest(self)
+
+    @property
+    def function(self):
+        "underlying python 'function' object"
+        return getattr(self.obj, 'im_func', self.obj)
+
+    def _getobj(self):
+        name = self.name
+        i = name.find("[")  # parametrization
+        if i != -1:
+            name = name[:i]
+        return getattr(self.parent.obj, name)
+
+    @property
+    def _pyfuncitem(self):
+        "(compatonly) for code expecting pytest-2.2 style request objects"
+        return self
+
+    def _isyieldedfunction(self):
+        return getattr(self, "_args", None) is not None
+
+    def runtest(self):
+        """ execute the underlying test function. """
+        self.ihook.pytest_pyfunc_call(pyfuncitem=self)
+
+    def setup(self):
+        super(Function, self).setup()
+        fixtures.fillfixtures(self)
diff --git a/tools/third_party/pytest/_pytest/python_api.py b/tools/third_party/pytest/_pytest/python_api.py
new file mode 100644
index 0000000..8196029
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/python_api.py
@@ -0,0 +1,619 @@
+import math
+import sys
+
+import py
+from six.moves import zip
+
+from _pytest.compat import isclass
+from _pytest.outcomes import fail
+import _pytest._code
+
+
+def _cmp_raises_type_error(self, other):
+    """__cmp__ implementation which raises TypeError. Used
+    by Approx base classes to implement only == and != and raise a
+    TypeError for other comparisons.
+
+    Needed in Python 2 only, Python 3 all it takes is not implementing the
+    other operators at all.
+    """
+    __tracebackhide__ = True
+    raise TypeError('Comparison operators other than == and != not supported by approx objects')
+
+
+# builtin pytest.approx helper
+
+
+class ApproxBase(object):
+    """
+    Provide shared utilities for making approximate comparisons between numbers
+    or sequences of numbers.
+    """
+
+    def __init__(self, expected, rel=None, abs=None, nan_ok=False):
+        self.expected = expected
+        self.abs = abs
+        self.rel = rel
+        self.nan_ok = nan_ok
+
+    def __repr__(self):
+        raise NotImplementedError
+
+    def __eq__(self, actual):
+        return all(
+            a == self._approx_scalar(x)
+            for a, x in self._yield_comparisons(actual))
+
+    __hash__ = None
+
+    def __ne__(self, actual):
+        return not (actual == self)
+
+    if sys.version_info[0] == 2:
+        __cmp__ = _cmp_raises_type_error
+
+    def _approx_scalar(self, x):
+        return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
+
+    def _yield_comparisons(self, actual):
+        """
+        Yield all the pairs of numbers to be compared.  This is used to
+        implement the `__eq__` method.
+        """
+        raise NotImplementedError
+
+
+class ApproxNumpy(ApproxBase):
+    """
+    Perform approximate comparisons for numpy arrays.
+    """
+
+    # Tell numpy to use our `__eq__` operator instead of its.
+    __array_priority__ = 100
+
+    def __repr__(self):
+        # It might be nice to rewrite this function to account for the
+        # shape of the array...
+        return "approx({0!r})".format(list(
+            self._approx_scalar(x) for x in self.expected))
+
+    if sys.version_info[0] == 2:
+        __cmp__ = _cmp_raises_type_error
+
+    def __eq__(self, actual):
+        import numpy as np
+
+        try:
+            actual = np.asarray(actual)
+        except:  # noqa
+            raise TypeError("cannot compare '{0}' to numpy.ndarray".format(actual))
+
+        if actual.shape != self.expected.shape:
+            return False
+
+        return ApproxBase.__eq__(self, actual)
+
+    def _yield_comparisons(self, actual):
+        import numpy as np
+
+        # We can be sure that `actual` is a numpy array, because it's
+        # casted in `__eq__` before being passed to `ApproxBase.__eq__`,
+        # which is the only method that calls this one.
+        for i in np.ndindex(self.expected.shape):
+            yield actual[i], self.expected[i]
+
+
+class ApproxMapping(ApproxBase):
+    """
+    Perform approximate comparisons for mappings where the values are numbers
+    (the keys can be anything).
+    """
+
+    def __repr__(self):
+        return "approx({0!r})".format(dict(
+            (k, self._approx_scalar(v))
+            for k, v in self.expected.items()))
+
+    def __eq__(self, actual):
+        if set(actual.keys()) != set(self.expected.keys()):
+            return False
+
+        return ApproxBase.__eq__(self, actual)
+
+    def _yield_comparisons(self, actual):
+        for k in self.expected.keys():
+            yield actual[k], self.expected[k]
+
+
+class ApproxSequence(ApproxBase):
+    """
+    Perform approximate comparisons for sequences of numbers.
+    """
+
+    # Tell numpy to use our `__eq__` operator instead of its.
+    __array_priority__ = 100
+
+    def __repr__(self):
+        seq_type = type(self.expected)
+        if seq_type not in (tuple, list, set):
+            seq_type = list
+        return "approx({0!r})".format(seq_type(
+            self._approx_scalar(x) for x in self.expected))
+
+    def __eq__(self, actual):
+        if len(actual) != len(self.expected):
+            return False
+        return ApproxBase.__eq__(self, actual)
+
+    def _yield_comparisons(self, actual):
+        return zip(actual, self.expected)
+
+
+class ApproxScalar(ApproxBase):
+    """
+    Perform approximate comparisons for single numbers only.
+    """
+
+    def __repr__(self):
+        """
+        Return a string communicating both the expected value and the tolerance
+        for the comparison being made, e.g. '1.0 +- 1e-6'.  Use the unicode
+        plus/minus symbol if this is python3 (it's too hard to get right for
+        python2).
+        """
+        if isinstance(self.expected, complex):
+            return str(self.expected)
+
+        # Infinities aren't compared using tolerances, so don't show a
+        # tolerance.
+        if math.isinf(self.expected):
+            return str(self.expected)
+
+        # If a sensible tolerance can't be calculated, self.tolerance will
+        # raise a ValueError.  In this case, display '???'.
+        try:
+            vetted_tolerance = '{:.1e}'.format(self.tolerance)
+        except ValueError:
+            vetted_tolerance = '???'
+
+        if sys.version_info[0] == 2:
+            return '{0} +- {1}'.format(self.expected, vetted_tolerance)
+        else:
+            return u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance)
+
+    def __eq__(self, actual):
+        """
+        Return true if the given value is equal to the expected value within
+        the pre-specified tolerance.
+        """
+
+        # Short-circuit exact equality.
+        if actual == self.expected:
+            return True
+
+        # Allow the user to control whether NaNs are considered equal to each
+        # other or not.  The abs() calls are for compatibility with complex
+        # numbers.
+        if math.isnan(abs(self.expected)):
+            return self.nan_ok and math.isnan(abs(actual))
+
+        # Infinity shouldn't be approximately equal to anything but itself, but
+        # if there's a relative tolerance, it will be infinite and infinity
+        # will seem approximately equal to everything.  The equal-to-itself
+        # case would have been short circuited above, so here we can just
+        # return false if the expected value is infinite.  The abs() call is
+        # for compatibility with complex numbers.
+        if math.isinf(abs(self.expected)):
+            return False
+
+        # Return true if the two numbers are within the tolerance.
+        return abs(self.expected - actual) <= self.tolerance
+
+    __hash__ = None
+
+    @property
+    def tolerance(self):
+        """
+        Return the tolerance for the comparison.  This could be either an
+        absolute tolerance or a relative tolerance, depending on what the user
+        specified or which would be larger.
+        """
+        def set_default(x, default):
+            return x if x is not None else default
+
+        # Figure out what the absolute tolerance should be.  ``self.abs`` is
+        # either None or a value specified by the user.
+        absolute_tolerance = set_default(self.abs, 1e-12)
+
+        if absolute_tolerance < 0:
+            raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance))
+        if math.isnan(absolute_tolerance):
+            raise ValueError("absolute tolerance can't be NaN.")
+
+        # If the user specified an absolute tolerance but not a relative one,
+        # just return the absolute tolerance.
+        if self.rel is None:
+            if self.abs is not None:
+                return absolute_tolerance
+
+        # Figure out what the relative tolerance should be.  ``self.rel`` is
+        # either None or a value specified by the user.  This is done after
+        # we've made sure the user didn't ask for an absolute tolerance only,
+        # because we don't want to raise errors about the relative tolerance if
+        # we aren't even going to use it.
+        relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected)
+
+        if relative_tolerance < 0:
+            raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance))
+        if math.isnan(relative_tolerance):
+            raise ValueError("relative tolerance can't be NaN.")
+
+        # Return the larger of the relative and absolute tolerances.
+        return max(relative_tolerance, absolute_tolerance)
+
+
+def approx(expected, rel=None, abs=None, nan_ok=False):
+    """
+    Assert that two numbers (or two sets of numbers) are equal to each other
+    within some tolerance.
+
+    Due to the `intricacies of floating-point arithmetic`__, numbers that we
+    would intuitively expect to be equal are not always so::
+
+        >>> 0.1 + 0.2 == 0.3
+        False
+
+    __ https://docs.python.org/3/tutorial/floatingpoint.html
+
+    This problem is commonly encountered when writing tests, e.g. when making
+    sure that floating-point values are what you expect them to be.  One way to
+    deal with this problem is to assert that two floating-point numbers are
+    equal to within some appropriate tolerance::
+
+        >>> abs((0.1 + 0.2) - 0.3) < 1e-6
+        True
+
+    However, comparisons like this are tedious to write and difficult to
+    understand.  Furthermore, absolute comparisons like the one above are
+    usually discouraged because there's no tolerance that works well for all
+    situations.  ``1e-6`` is good for numbers around ``1``, but too small for
+    very big numbers and too big for very small ones.  It's better to express
+    the tolerance as a fraction of the expected value, but relative comparisons
+    like that are even more difficult to write correctly and concisely.
+
+    The ``approx`` class performs floating-point comparisons using a syntax
+    that's as intuitive as possible::
+
+        >>> from pytest import approx
+        >>> 0.1 + 0.2 == approx(0.3)
+        True
+
+    The same syntax also works for sequences of numbers::
+
+        >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
+        True
+
+    Dictionary *values*::
+
+        >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
+        True
+
+    And ``numpy`` arrays::
+
+        >>> import numpy as np                                                          # doctest: +SKIP
+        >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
+        True
+
+    By default, ``approx`` considers numbers within a relative tolerance of
+    ``1e-6`` (i.e. one part in a million) of its expected value to be equal.
+    This treatment would lead to surprising results if the expected value was
+    ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
+    To handle this case less surprisingly, ``approx`` also considers numbers
+    within an absolute tolerance of ``1e-12`` of its expected value to be
+    equal.  Infinity and NaN are special cases.  Infinity is only considered
+    equal to itself, regardless of the relative tolerance.  NaN is not
+    considered equal to anything by default, but you can make it be equal to
+    itself by setting the ``nan_ok`` argument to True.  (This is meant to
+    facilitate comparing arrays that use NaN to mean "no data".)
+
+    Both the relative and absolute tolerances can be changed by passing
+    arguments to the ``approx`` constructor::
+
+        >>> 1.0001 == approx(1)
+        False
+        >>> 1.0001 == approx(1, rel=1e-3)
+        True
+        >>> 1.0001 == approx(1, abs=1e-3)
+        True
+
+    If you specify ``abs`` but not ``rel``, the comparison will not consider
+    the relative tolerance at all.  In other words, two numbers that are within
+    the default relative tolerance of ``1e-6`` will still be considered unequal
+    if they exceed the specified absolute tolerance.  If you specify both
+    ``abs`` and ``rel``, the numbers will be considered equal if either
+    tolerance is met::
+
+        >>> 1 + 1e-8 == approx(1)
+        True
+        >>> 1 + 1e-8 == approx(1, abs=1e-12)
+        False
+        >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
+        True
+
+    If you're thinking about using ``approx``, then you might want to know how
+    it compares to other good ways of comparing floating-point numbers.  All of
+    these algorithms are based on relative and absolute tolerances and should
+    agree for the most part, but they do have meaningful differences:
+
+    - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``:  True if the relative
+      tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
+      tolerance is met.  Because the relative tolerance is calculated w.r.t.
+      both ``a`` and ``b``, this test is symmetric (i.e.  neither ``a`` nor
+      ``b`` is a "reference value").  You have to specify an absolute tolerance
+      if you want to compare to ``0.0`` because there is no tolerance by
+      default.  Only available in python>=3.5.  `More information...`__
+
+      __ https://docs.python.org/3/library/math.html#math.isclose
+
+    - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
+      between ``a`` and ``b`` is less that the sum of the relative tolerance
+      w.r.t. ``b`` and the absolute tolerance.  Because the relative tolerance
+      is only calculated w.r.t. ``b``, this test is asymmetric and you can
+      think of ``b`` as the reference value.  Support for comparing sequences
+      is provided by ``numpy.allclose``.  `More information...`__
+
+      __ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
+
+    - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
+      are within an absolute tolerance of ``1e-7``.  No relative tolerance is
+      considered and the absolute tolerance cannot be changed, so this function
+      is not appropriate for very large or very small numbers.  Also, it's only
+      available in subclasses of ``unittest.TestCase`` and it's ugly because it
+      doesn't follow PEP8.  `More information...`__
+
+      __ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
+
+    - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
+      tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
+      Because the relative tolerance is only calculated w.r.t. ``b``, this test
+      is asymmetric and you can think of ``b`` as the reference value.  In the
+      special case that you explicitly specify an absolute tolerance but not a
+      relative tolerance, only the absolute tolerance is considered.
+
+    .. warning::
+
+       .. versionchanged:: 3.2
+
+       In order to avoid inconsistent behavior, ``TypeError`` is
+       raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons.
+       The example below illustrates the problem::
+
+           assert approx(0.1) > 0.1 + 1e-10  # calls approx(0.1).__gt__(0.1 + 1e-10)
+           assert 0.1 + 1e-10 > approx(0.1)  # calls approx(0.1).__lt__(0.1 + 1e-10)
+
+       In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)``
+       to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to
+       comparison. This is because the call hierarchy of rich comparisons
+       follows a fixed behavior. `More information...`__
+
+       __ https://docs.python.org/3/reference/datamodel.html#object.__ge__
+    """
+
+    from collections import Mapping, Sequence
+    from _pytest.compat import STRING_TYPES as String
+
+    # Delegate the comparison to a class that knows how to deal with the type
+    # of the expected value (e.g. int, float, list, dict, numpy.array, etc).
+    #
+    # This architecture is really driven by the need to support numpy arrays.
+    # The only way to override `==` for arrays without requiring that approx be
+    # the left operand is to inherit the approx object from `numpy.ndarray`.
+    # But that can't be a general solution, because it requires (1) numpy to be
+    # installed and (2) the expected value to be a numpy array.  So the general
+    # solution is to delegate each type of expected value to a different class.
+    #
+    # This has the advantage that it made it easy to support mapping types
+    # (i.e. dict).  The old code accepted mapping types, but would only compare
+    # their keys, which is probably not what most people would expect.
+
+    if _is_numpy_array(expected):
+        cls = ApproxNumpy
+    elif isinstance(expected, Mapping):
+        cls = ApproxMapping
+    elif isinstance(expected, Sequence) and not isinstance(expected, String):
+        cls = ApproxSequence
+    else:
+        cls = ApproxScalar
+
+    return cls(expected, rel, abs, nan_ok)
+
+
+def _is_numpy_array(obj):
+    """
+    Return true if the given object is a numpy array.  Make a special effort to
+    avoid importing numpy unless it's really necessary.
+    """
+    import inspect
+
+    for cls in inspect.getmro(type(obj)):
+        if cls.__module__ == 'numpy':
+            try:
+                import numpy as np
+                return isinstance(obj, np.ndarray)
+            except ImportError:
+                pass
+
+    return False
+
+
+# builtin pytest.raises helper
+
+def raises(expected_exception, *args, **kwargs):
+    """
+    Assert that a code block/function call raises ``expected_exception``
+    and raise a failure exception otherwise.
+
+    This helper produces a ``ExceptionInfo()`` object (see below).
+
+    You may use this function as a context manager::
+
+        >>> with raises(ZeroDivisionError):
+        ...    1/0
+
+    .. versionchanged:: 2.10
+
+    In the context manager form you may use the keyword argument
+    ``message`` to specify a custom failure message::
+
+        >>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
+        ...    pass
+        Traceback (most recent call last):
+          ...
+        Failed: Expecting ZeroDivisionError
+
+    .. note::
+
+       When using ``pytest.raises`` as a context manager, it's worthwhile to
+       note that normal context manager rules apply and that the exception
+       raised *must* be the final line in the scope of the context manager.
+       Lines of code after that, within the scope of the context manager will
+       not be executed. For example::
+
+           >>> value = 15
+           >>> with raises(ValueError) as exc_info:
+           ...     if value > 10:
+           ...         raise ValueError("value must be <= 10")
+           ...     assert exc_info.type == ValueError  # this will not execute
+
+       Instead, the following approach must be taken (note the difference in
+       scope)::
+
+           >>> with raises(ValueError) as exc_info:
+           ...     if value > 10:
+           ...         raise ValueError("value must be <= 10")
+           ...
+           >>> assert exc_info.type == ValueError
+
+
+    Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
+    exception matches a text or regex::
+
+        >>> with raises(ValueError, match='must be 0 or None'):
+        ...     raise ValueError("value must be 0 or None")
+
+        >>> with raises(ValueError, match=r'must be \d+$'):
+        ...     raise ValueError("value must be 42")
+
+    **Legacy forms**
+
+    The forms below are fully supported but are discouraged for new code because the
+    context manager form is regarded as more readable and less error-prone.
+
+    It is possible to specify a callable by passing a to-be-called lambda::
+
+        >>> raises(ZeroDivisionError, lambda: 1/0)
+        <ExceptionInfo ...>
+
+    or you can specify an arbitrary callable with arguments::
+
+        >>> def f(x): return 1/x
+        ...
+        >>> raises(ZeroDivisionError, f, 0)
+        <ExceptionInfo ...>
+        >>> raises(ZeroDivisionError, f, x=0)
+        <ExceptionInfo ...>
+
+    It is also possible to pass a string to be evaluated at runtime::
+
+        >>> raises(ZeroDivisionError, "f(0)")
+        <ExceptionInfo ...>
+
+    The string will be evaluated using the same ``locals()`` and ``globals()``
+    at the moment of the ``raises`` call.
+
+    .. autoclass:: _pytest._code.ExceptionInfo
+        :members:
+
+    .. note::
+        Similar to caught exception objects in Python, explicitly clearing
+        local references to returned ``ExceptionInfo`` objects can
+        help the Python interpreter speed up its garbage collection.
+
+        Clearing those references breaks a reference cycle
+        (``ExceptionInfo`` --> caught exception --> frame stack raising
+        the exception --> current frame stack --> local variables -->
+        ``ExceptionInfo``) which makes Python keep all objects referenced
+        from that cycle (including all local variables in the current
+        frame) alive until the next cyclic garbage collection run. See the
+        official Python ``try`` statement documentation for more detailed
+        information.
+
+    """
+    __tracebackhide__ = True
+    msg = ("exceptions must be old-style classes or"
+           " derived from BaseException, not %s")
+    if isinstance(expected_exception, tuple):
+        for exc in expected_exception:
+            if not isclass(exc):
+                raise TypeError(msg % type(exc))
+    elif not isclass(expected_exception):
+        raise TypeError(msg % type(expected_exception))
+
+    message = "DID NOT RAISE {0}".format(expected_exception)
+    match_expr = None
+
+    if not args:
+        if "message" in kwargs:
+            message = kwargs.pop("message")
+        if "match" in kwargs:
+            match_expr = kwargs.pop("match")
+            message += " matching '{0}'".format(match_expr)
+        return RaisesContext(expected_exception, message, match_expr)
+    elif isinstance(args[0], str):
+        code, = args
+        assert isinstance(code, str)
+        frame = sys._getframe(1)
+        loc = frame.f_locals.copy()
+        loc.update(kwargs)
+        # print "raises frame scope: %r" % frame.f_locals
+        try:
+            code = _pytest._code.Source(code).compile()
+            py.builtin.exec_(code, frame.f_globals, loc)
+            # XXX didn'T mean f_globals == f_locals something special?
+            #     this is destroyed here ...
+        except expected_exception:
+            return _pytest._code.ExceptionInfo()
+    else:
+        func = args[0]
+        try:
+            func(*args[1:], **kwargs)
+        except expected_exception:
+            return _pytest._code.ExceptionInfo()
+    fail(message)
+
+
+raises.Exception = fail.Exception
+
+
+class RaisesContext(object):
+    def __init__(self, expected_exception, message, match_expr):
+        self.expected_exception = expected_exception
+        self.message = message
+        self.match_expr = match_expr
+        self.excinfo = None
+
+    def __enter__(self):
+        self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
+        return self.excinfo
+
+    def __exit__(self, *tp):
+        __tracebackhide__ = True
+        if tp[0] is None:
+            fail(self.message)
+        self.excinfo.__init__(tp)
+        suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
+        if sys.version_info[0] == 2 and suppress_exception:
+            sys.exc_clear()
+        if self.match_expr:
+            self.excinfo.match(self.match_expr)
+        return suppress_exception
diff --git a/tools/third_party/pytest/_pytest/recwarn.py b/tools/third_party/pytest/_pytest/recwarn.py
new file mode 100644
index 0000000..4fceb10
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/recwarn.py
@@ -0,0 +1,236 @@
+""" recording warnings during test function execution. """
+from __future__ import absolute_import, division, print_function
+
+import inspect
+
+import _pytest._code
+import py
+import sys
+import warnings
+
+import re
+
+from _pytest.fixtures import yield_fixture
+from _pytest.outcomes import fail
+
+
+@yield_fixture
+def recwarn():
+    """Return a WarningsRecorder instance that provides these methods:
+
+    * ``pop(category=None)``: return last warning matching the category.
+    * ``clear()``: clear list of warnings
+
+    See http://docs.python.org/library/warnings.html for information
+    on warning categories.
+    """
+    wrec = WarningsRecorder()
+    with wrec:
+        warnings.simplefilter('default')
+        yield wrec
+
+
+def deprecated_call(func=None, *args, **kwargs):
+    """context manager that can be used to ensure a block of code triggers a
+    ``DeprecationWarning`` or ``PendingDeprecationWarning``::
+
+        >>> import warnings
+        >>> def api_call_v2():
+        ...     warnings.warn('use v3 of this api', DeprecationWarning)
+        ...     return 200
+
+        >>> with deprecated_call():
+        ...    assert api_call_v2() == 200
+
+    ``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``,
+    in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
+    types above.
+    """
+    if not func:
+        return _DeprecatedCallContext()
+    else:
+        __tracebackhide__ = True
+        with _DeprecatedCallContext():
+            return func(*args, **kwargs)
+
+
+class _DeprecatedCallContext(object):
+    """Implements the logic to capture deprecation warnings as a context manager."""
+
+    def __enter__(self):
+        self._captured_categories = []
+        self._old_warn = warnings.warn
+        self._old_warn_explicit = warnings.warn_explicit
+        warnings.warn_explicit = self._warn_explicit
+        warnings.warn = self._warn
+
+    def _warn_explicit(self, message, category, *args, **kwargs):
+        self._captured_categories.append(category)
+
+    def _warn(self, message, category=None, *args, **kwargs):
+        if isinstance(message, Warning):
+            self._captured_categories.append(message.__class__)
+        else:
+            self._captured_categories.append(category)
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        warnings.warn_explicit = self._old_warn_explicit
+        warnings.warn = self._old_warn
+
+        if exc_type is None:
+            deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
+            if not any(issubclass(c, deprecation_categories) for c in self._captured_categories):
+                __tracebackhide__ = True
+                msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
+                raise AssertionError(msg)
+
+
+def warns(expected_warning, *args, **kwargs):
+    """Assert that code raises a particular class of warning.
+
+    Specifically, the input @expected_warning can be a warning class or
+    tuple of warning classes, and the code must return that warning
+    (if a single class) or one of those warnings (if a tuple).
+
+    This helper produces a list of ``warnings.WarningMessage`` objects,
+    one for each warning raised.
+
+    This function can be used as a context manager, or any of the other ways
+    ``pytest.raises`` can be used::
+
+        >>> with warns(RuntimeWarning):
+        ...    warnings.warn("my warning", RuntimeWarning)
+
+    In the context manager form you may use the keyword argument ``match`` to assert
+    that the exception matches a text or regex::
+
+        >>> with warns(UserWarning, match='must be 0 or None'):
+        ...     warnings.warn("value must be 0 or None", UserWarning)
+
+        >>> with warns(UserWarning, match=r'must be \d+$'):
+        ...     warnings.warn("value must be 42", UserWarning)
+
+        >>> with warns(UserWarning, match=r'must be \d+$'):
+        ...     warnings.warn("this is not here", UserWarning)
+        Traceback (most recent call last):
+          ...
+        Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
+
+    """
+    match_expr = None
+    if not args:
+        if "match" in kwargs:
+            match_expr = kwargs.pop("match")
+        return WarningsChecker(expected_warning, match_expr=match_expr)
+    elif isinstance(args[0], str):
+        code, = args
+        assert isinstance(code, str)
+        frame = sys._getframe(1)
+        loc = frame.f_locals.copy()
+        loc.update(kwargs)
+
+        with WarningsChecker(expected_warning, match_expr=match_expr):
+            code = _pytest._code.Source(code).compile()
+            py.builtin.exec_(code, frame.f_globals, loc)
+    else:
+        func = args[0]
+        with WarningsChecker(expected_warning, match_expr=match_expr):
+            return func(*args[1:], **kwargs)
+
+
+class WarningsRecorder(warnings.catch_warnings):
+    """A context manager to record raised warnings.
+
+    Adapted from `warnings.catch_warnings`.
+    """
+
+    def __init__(self):
+        super(WarningsRecorder, self).__init__(record=True)
+        self._entered = False
+        self._list = []
+
+    @property
+    def list(self):
+        """The list of recorded warnings."""
+        return self._list
+
+    def __getitem__(self, i):
+        """Get a recorded warning by index."""
+        return self._list[i]
+
+    def __iter__(self):
+        """Iterate through the recorded warnings."""
+        return iter(self._list)
+
+    def __len__(self):
+        """The number of recorded warnings."""
+        return len(self._list)
+
+    def pop(self, cls=Warning):
+        """Pop the first recorded warning, raise exception if not exists."""
+        for i, w in enumerate(self._list):
+            if issubclass(w.category, cls):
+                return self._list.pop(i)
+        __tracebackhide__ = True
+        raise AssertionError("%r not found in warning list" % cls)
+
+    def clear(self):
+        """Clear the list of recorded warnings."""
+        self._list[:] = []
+
+    def __enter__(self):
+        if self._entered:
+            __tracebackhide__ = True
+            raise RuntimeError("Cannot enter %r twice" % self)
+        self._list = super(WarningsRecorder, self).__enter__()
+        warnings.simplefilter('always')
+        return self
+
+    def __exit__(self, *exc_info):
+        if not self._entered:
+            __tracebackhide__ = True
+            raise RuntimeError("Cannot exit %r without entering first" % self)
+        super(WarningsRecorder, self).__exit__(*exc_info)
+
+
+class WarningsChecker(WarningsRecorder):
+    def __init__(self, expected_warning=None, match_expr=None):
+        super(WarningsChecker, self).__init__()
+
+        msg = ("exceptions must be old-style classes or "
+               "derived from Warning, not %s")
+        if isinstance(expected_warning, tuple):
+            for exc in expected_warning:
+                if not inspect.isclass(exc):
+                    raise TypeError(msg % type(exc))
+        elif inspect.isclass(expected_warning):
+            expected_warning = (expected_warning,)
+        elif expected_warning is not None:
+            raise TypeError(msg % type(expected_warning))
+
+        self.expected_warning = expected_warning
+        self.match_expr = match_expr
+
+    def __exit__(self, *exc_info):
+        super(WarningsChecker, self).__exit__(*exc_info)
+
+        # only check if we're not currently handling an exception
+        if all(a is None for a in exc_info):
+            if self.expected_warning is not None:
+                if not any(issubclass(r.category, self.expected_warning)
+                           for r in self):
+                    __tracebackhide__ = True
+                    fail("DID NOT WARN. No warnings of type {0} was emitted. "
+                         "The list of emitted warnings is: {1}.".format(
+                             self.expected_warning,
+                             [each.message for each in self]))
+                elif self.match_expr is not None:
+                    for r in self:
+                        if issubclass(r.category, self.expected_warning):
+                            if re.compile(self.match_expr).search(str(r.message)):
+                                break
+                    else:
+                        fail("DID NOT WARN. No warnings of type {0} matching"
+                             " ('{1}') was emitted. The list of emitted warnings"
+                             " is: {2}.".format(self.expected_warning, self.match_expr,
+                                                [each.message for each in self]))
diff --git a/tools/third_party/pytest/_pytest/resultlog.py b/tools/third_party/pytest/_pytest/resultlog.py
new file mode 100644
index 0000000..9f9c2d1
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/resultlog.py
@@ -0,0 +1,113 @@
+""" log machine-parseable test session result information in a plain
+text file.
+"""
+from __future__ import absolute_import, division, print_function
+
+import py
+import os
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("terminal reporting", "resultlog plugin options")
+    group.addoption('--resultlog', '--result-log', action="store",
+                    metavar="path", default=None,
+                    help="DEPRECATED path for machine-readable result log.")
+
+
+def pytest_configure(config):
+    resultlog = config.option.resultlog
+    # prevent opening resultlog on slave nodes (xdist)
+    if resultlog and not hasattr(config, 'slaveinput'):
+        dirname = os.path.dirname(os.path.abspath(resultlog))
+        if not os.path.isdir(dirname):
+            os.makedirs(dirname)
+        logfile = open(resultlog, 'w', 1)  # line buffered
+        config._resultlog = ResultLog(config, logfile)
+        config.pluginmanager.register(config._resultlog)
+
+        from _pytest.deprecated import RESULT_LOG
+        config.warn('C1', RESULT_LOG)
+
+
+def pytest_unconfigure(config):
+    resultlog = getattr(config, '_resultlog', None)
+    if resultlog:
+        resultlog.logfile.close()
+        del config._resultlog
+        config.pluginmanager.unregister(resultlog)
+
+
+def generic_path(item):
+    chain = item.listchain()
+    gpath = [chain[0].name]
+    fspath = chain[0].fspath
+    fspart = False
+    for node in chain[1:]:
+        newfspath = node.fspath
+        if newfspath == fspath:
+            if fspart:
+                gpath.append(':')
+                fspart = False
+            else:
+                gpath.append('.')
+        else:
+            gpath.append('/')
+            fspart = True
+        name = node.name
+        if name[0] in '([':
+            gpath.pop()
+        gpath.append(name)
+        fspath = newfspath
+    return ''.join(gpath)
+
+
+class ResultLog(object):
+    def __init__(self, config, logfile):
+        self.config = config
+        self.logfile = logfile  # preferably line buffered
+
+    def write_log_entry(self, testpath, lettercode, longrepr):
+        print("%s %s" % (lettercode, testpath), file=self.logfile)
+        for line in longrepr.splitlines():
+            print(" %s" % line, file=self.logfile)
+
+    def log_outcome(self, report, lettercode, longrepr):
+        testpath = getattr(report, 'nodeid', None)
+        if testpath is None:
+            testpath = report.fspath
+        self.write_log_entry(testpath, lettercode, longrepr)
+
+    def pytest_runtest_logreport(self, report):
+        if report.when != "call" and report.passed:
+            return
+        res = self.config.hook.pytest_report_teststatus(report=report)
+        code = res[1]
+        if code == 'x':
+            longrepr = str(report.longrepr)
+        elif code == 'X':
+            longrepr = ''
+        elif report.passed:
+            longrepr = ""
+        elif report.failed:
+            longrepr = str(report.longrepr)
+        elif report.skipped:
+            longrepr = str(report.longrepr[2])
+        self.log_outcome(report, code, longrepr)
+
+    def pytest_collectreport(self, report):
+        if not report.passed:
+            if report.failed:
+                code = "F"
+                longrepr = str(report.longrepr)
+            else:
+                assert report.skipped
+                code = "S"
+                longrepr = "%s:%d: %s" % report.longrepr
+            self.log_outcome(report, code, longrepr)
+
+    def pytest_internalerror(self, excrepr):
+        reprcrash = getattr(excrepr, 'reprcrash', None)
+        path = getattr(reprcrash, "path", None)
+        if path is None:
+            path = "cwd:%s" % py.path.local()
+        self.write_log_entry(path, '!', str(excrepr))
diff --git a/tools/third_party/pytest/_pytest/runner.py b/tools/third_party/pytest/_pytest/runner.py
new file mode 100644
index 0000000..e07ed2a
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/runner.py
@@ -0,0 +1,506 @@
+""" basic collect and runtest protocol implementations """
+from __future__ import absolute_import, division, print_function
+
+import bdb
+import os
+import sys
+from time import time
+
+import py
+from _pytest._code.code import TerminalRepr, ExceptionInfo
+from _pytest.outcomes import skip, Skipped, TEST_OUTCOME
+
+#
+# pytest plugin hooks
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("terminal reporting", "reporting", after="general")
+    group.addoption('--durations',
+                    action="store", type=int, default=None, metavar="N",
+                    help="show N slowest setup/test durations (N=0 for all)."),
+
+
+def pytest_terminal_summary(terminalreporter):
+    durations = terminalreporter.config.option.durations
+    if durations is None:
+        return
+    tr = terminalreporter
+    dlist = []
+    for replist in tr.stats.values():
+        for rep in replist:
+            if hasattr(rep, 'duration'):
+                dlist.append(rep)
+    if not dlist:
+        return
+    dlist.sort(key=lambda x: x.duration)
+    dlist.reverse()
+    if not durations:
+        tr.write_sep("=", "slowest test durations")
+    else:
+        tr.write_sep("=", "slowest %s test durations" % durations)
+        dlist = dlist[:durations]
+
+    for rep in dlist:
+        nodeid = rep.nodeid.replace("::()::", "::")
+        tr.write_line("%02.2fs %-8s %s" %
+                      (rep.duration, rep.when, nodeid))
+
+
+def pytest_sessionstart(session):
+    session._setupstate = SetupState()
+
+
+def pytest_sessionfinish(session):
+    session._setupstate.teardown_all()
+
+
+def pytest_runtest_protocol(item, nextitem):
+    item.ihook.pytest_runtest_logstart(
+        nodeid=item.nodeid, location=item.location,
+    )
+    runtestprotocol(item, nextitem=nextitem)
+    return True
+
+
+def runtestprotocol(item, log=True, nextitem=None):
+    hasrequest = hasattr(item, "_request")
+    if hasrequest and not item._request:
+        item._initrequest()
+    rep = call_and_report(item, "setup", log)
+    reports = [rep]
+    if rep.passed:
+        if item.config.option.setupshow:
+            show_test_item(item)
+        if not item.config.option.setuponly:
+            reports.append(call_and_report(item, "call", log))
+    reports.append(call_and_report(item, "teardown", log,
+                                   nextitem=nextitem))
+    # after all teardown hooks have been called
+    # want funcargs and request info to go away
+    if hasrequest:
+        item._request = False
+        item.funcargs = None
+    return reports
+
+
+def show_test_item(item):
+    """Show test function, parameters and the fixtures of the test item."""
+    tw = item.config.get_terminal_writer()
+    tw.line()
+    tw.write(' ' * 8)
+    tw.write(item._nodeid)
+    used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys())
+    if used_fixtures:
+        tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures)))
+
+
+def pytest_runtest_setup(item):
+    _update_current_test_var(item, 'setup')
+    item.session._setupstate.prepare(item)
+
+
+def pytest_runtest_call(item):
+    _update_current_test_var(item, 'call')
+    try:
+        item.runtest()
+    except Exception:
+        # Store trace info to allow postmortem debugging
+        type, value, tb = sys.exc_info()
+        tb = tb.tb_next  # Skip *this* frame
+        sys.last_type = type
+        sys.last_value = value
+        sys.last_traceback = tb
+        del tb  # Get rid of it in this namespace
+        raise
+
+
+def pytest_runtest_teardown(item, nextitem):
+    _update_current_test_var(item, 'teardown')
+    item.session._setupstate.teardown_exact(item, nextitem)
+    _update_current_test_var(item, None)
+
+
+def _update_current_test_var(item, when):
+    """
+    Update PYTEST_CURRENT_TEST to reflect the current item and stage.
+
+    If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
+    """
+    var_name = 'PYTEST_CURRENT_TEST'
+    if when:
+        value = '{0} ({1})'.format(item.nodeid, when)
+        # don't allow null bytes on environment variables (see #2644, #2957)
+        value = value.replace('\x00', '(null)')
+        os.environ[var_name] = value
+    else:
+        os.environ.pop(var_name)
+
+
+def pytest_report_teststatus(report):
+    if report.when in ("setup", "teardown"):
+        if report.failed:
+            #      category, shortletter, verbose-word
+            return "error", "E", "ERROR"
+        elif report.skipped:
+            return "skipped", "s", "SKIPPED"
+        else:
+            return "", "", ""
+
+
+#
+# Implementation
+
+def call_and_report(item, when, log=True, **kwds):
+    call = call_runtest_hook(item, when, **kwds)
+    hook = item.ihook
+    report = hook.pytest_runtest_makereport(item=item, call=call)
+    if log:
+        hook.pytest_runtest_logreport(report=report)
+    if check_interactive_exception(call, report):
+        hook.pytest_exception_interact(node=item, call=call, report=report)
+    return report
+
+
+def check_interactive_exception(call, report):
+    return call.excinfo and not (
+        hasattr(report, "wasxfail") or
+        call.excinfo.errisinstance(skip.Exception) or
+        call.excinfo.errisinstance(bdb.BdbQuit))
+
+
+def call_runtest_hook(item, when, **kwds):
+    hookname = "pytest_runtest_" + when
+    ihook = getattr(item.ihook, hookname)
+    return CallInfo(lambda: ihook(item=item, **kwds), when=when)
+
+
+class CallInfo:
+    """ Result/Exception info a function invocation. """
+    #: None or ExceptionInfo object.
+    excinfo = None
+
+    def __init__(self, func, when):
+        #: context of invocation: one of "setup", "call",
+        #: "teardown", "memocollect"
+        self.when = when
+        self.start = time()
+        try:
+            self.result = func()
+        except KeyboardInterrupt:
+            self.stop = time()
+            raise
+        except:  # noqa
+            self.excinfo = ExceptionInfo()
+        self.stop = time()
+
+    def __repr__(self):
+        if self.excinfo:
+            status = "exception: %s" % str(self.excinfo.value)
+        else:
+            status = "result: %r" % (self.result,)
+        return "<CallInfo when=%r %s>" % (self.when, status)
+
+
+def getslaveinfoline(node):
+    try:
+        return node._slaveinfocache
+    except AttributeError:
+        d = node.slaveinfo
+        ver = "%s.%s.%s" % d['version_info'][:3]
+        node._slaveinfocache = s = "[%s] %s -- Python %s %s" % (
+            d['id'], d['sysplatform'], ver, d['executable'])
+        return s
+
+
+class BaseReport(object):
+
+    def __init__(self, **kw):
+        self.__dict__.update(kw)
+
+    def toterminal(self, out):
+        if hasattr(self, 'node'):
+            out.line(getslaveinfoline(self.node))
+
+        longrepr = self.longrepr
+        if longrepr is None:
+            return
+
+        if hasattr(longrepr, 'toterminal'):
+            longrepr.toterminal(out)
+        else:
+            try:
+                out.line(longrepr)
+            except UnicodeEncodeError:
+                out.line("<unprintable longrepr>")
+
+    def get_sections(self, prefix):
+        for name, content in self.sections:
+            if name.startswith(prefix):
+                yield prefix, content
+
+    @property
+    def longreprtext(self):
+        """
+        Read-only property that returns the full string representation
+        of ``longrepr``.
+
+        .. versionadded:: 3.0
+        """
+        tw = py.io.TerminalWriter(stringio=True)
+        tw.hasmarkup = False
+        self.toterminal(tw)
+        exc = tw.stringio.getvalue()
+        return exc.strip()
+
+    @property
+    def capstdout(self):
+        """Return captured text from stdout, if capturing is enabled
+
+        .. versionadded:: 3.0
+        """
+        return ''.join(content for (prefix, content) in self.get_sections('Captured stdout'))
+
+    @property
+    def capstderr(self):
+        """Return captured text from stderr, if capturing is enabled
+
+        .. versionadded:: 3.0
+        """
+        return ''.join(content for (prefix, content) in self.get_sections('Captured stderr'))
+
+    passed = property(lambda x: x.outcome == "passed")
+    failed = property(lambda x: x.outcome == "failed")
+    skipped = property(lambda x: x.outcome == "skipped")
+
+    @property
+    def fspath(self):
+        return self.nodeid.split("::")[0]
+
+
+def pytest_runtest_makereport(item, call):
+    when = call.when
+    duration = call.stop - call.start
+    keywords = dict([(x, 1) for x in item.keywords])
+    excinfo = call.excinfo
+    sections = []
+    if not call.excinfo:
+        outcome = "passed"
+        longrepr = None
+    else:
+        if not isinstance(excinfo, ExceptionInfo):
+            outcome = "failed"
+            longrepr = excinfo
+        elif excinfo.errisinstance(skip.Exception):
+            outcome = "skipped"
+            r = excinfo._getreprcrash()
+            longrepr = (str(r.path), r.lineno, r.message)
+        else:
+            outcome = "failed"
+            if call.when == "call":
+                longrepr = item.repr_failure(excinfo)
+            else:  # exception in setup or teardown
+                longrepr = item._repr_failure_py(excinfo,
+                                                 style=item.config.option.tbstyle)
+    for rwhen, key, content in item._report_sections:
+        sections.append(("Captured %s %s" % (key, rwhen), content))
+    return TestReport(item.nodeid, item.location,
+                      keywords, outcome, longrepr, when,
+                      sections, duration)
+
+
+class TestReport(BaseReport):
+    """ Basic test report object (also used for setup and teardown calls if
+    they fail).
+    """
+
+    def __init__(self, nodeid, location, keywords, outcome,
+                 longrepr, when, sections=(), duration=0, **extra):
+        #: normalized collection node id
+        self.nodeid = nodeid
+
+        #: a (filesystempath, lineno, domaininfo) tuple indicating the
+        #: actual location of a test item - it might be different from the
+        #: collected one e.g. if a method is inherited from a different module.
+        self.location = location
+
+        #: a name -> value dictionary containing all keywords and
+        #: markers associated with a test invocation.
+        self.keywords = keywords
+
+        #: test outcome, always one of "passed", "failed", "skipped".
+        self.outcome = outcome
+
+        #: None or a failure representation.
+        self.longrepr = longrepr
+
+        #: one of 'setup', 'call', 'teardown' to indicate runtest phase.
+        self.when = when
+
+        #: list of pairs ``(str, str)`` of extra information which needs to
+        #: marshallable. Used by pytest to add captured text
+        #: from ``stdout`` and ``stderr``, but may be used by other plugins
+        #: to add arbitrary information to reports.
+        self.sections = list(sections)
+
+        #: time it took to run just the test
+        self.duration = duration
+
+        self.__dict__.update(extra)
+
+    def __repr__(self):
+        return "<TestReport %r when=%r outcome=%r>" % (
+            self.nodeid, self.when, self.outcome)
+
+
+class TeardownErrorReport(BaseReport):
+    outcome = "failed"
+    when = "teardown"
+
+    def __init__(self, longrepr, **extra):
+        self.longrepr = longrepr
+        self.sections = []
+        self.__dict__.update(extra)
+
+
+def pytest_make_collect_report(collector):
+    call = CallInfo(
+        lambda: list(collector.collect()),
+        'collect')
+    longrepr = None
+    if not call.excinfo:
+        outcome = "passed"
+    else:
+        from _pytest import nose
+        skip_exceptions = (Skipped,) + nose.get_skip_exceptions()
+        if call.excinfo.errisinstance(skip_exceptions):
+            outcome = "skipped"
+            r = collector._repr_failure_py(call.excinfo, "line").reprcrash
+            longrepr = (str(r.path), r.lineno, r.message)
+        else:
+            outcome = "failed"
+            errorinfo = collector.repr_failure(call.excinfo)
+            if not hasattr(errorinfo, "toterminal"):
+                errorinfo = CollectErrorRepr(errorinfo)
+            longrepr = errorinfo
+    rep = CollectReport(collector.nodeid, outcome, longrepr,
+                        getattr(call, 'result', None))
+    rep.call = call  # see collect_one_node
+    return rep
+
+
+class CollectReport(BaseReport):
+    def __init__(self, nodeid, outcome, longrepr, result,
+                 sections=(), **extra):
+        self.nodeid = nodeid
+        self.outcome = outcome
+        self.longrepr = longrepr
+        self.result = result or []
+        self.sections = list(sections)
+        self.__dict__.update(extra)
+
+    @property
+    def location(self):
+        return (self.fspath, None, self.fspath)
+
+    def __repr__(self):
+        return "<CollectReport %r lenresult=%s outcome=%r>" % (
+            self.nodeid, len(self.result), self.outcome)
+
+
+class CollectErrorRepr(TerminalRepr):
+    def __init__(self, msg):
+        self.longrepr = msg
+
+    def toterminal(self, out):
+        out.line(self.longrepr, red=True)
+
+
+class SetupState(object):
+    """ shared state for setting up/tearing down test items or collectors. """
+
+    def __init__(self):
+        self.stack = []
+        self._finalizers = {}
+
+    def addfinalizer(self, finalizer, colitem):
+        """ attach a finalizer to the given colitem.
+        if colitem is None, this will add a finalizer that
+        is called at the end of teardown_all().
+        """
+        assert colitem and not isinstance(colitem, tuple)
+        assert callable(finalizer)
+        # assert colitem in self.stack  # some unit tests don't setup stack :/
+        self._finalizers.setdefault(colitem, []).append(finalizer)
+
+    def _pop_and_teardown(self):
+        colitem = self.stack.pop()
+        self._teardown_with_finalization(colitem)
+
+    def _callfinalizers(self, colitem):
+        finalizers = self._finalizers.pop(colitem, None)
+        exc = None
+        while finalizers:
+            fin = finalizers.pop()
+            try:
+                fin()
+            except TEST_OUTCOME:
+                # XXX Only first exception will be seen by user,
+                #     ideally all should be reported.
+                if exc is None:
+                    exc = sys.exc_info()
+        if exc:
+            py.builtin._reraise(*exc)
+
+    def _teardown_with_finalization(self, colitem):
+        self._callfinalizers(colitem)
+        if hasattr(colitem, "teardown"):
+            colitem.teardown()
+        for colitem in self._finalizers:
+            assert colitem is None or colitem in self.stack \
+                or isinstance(colitem, tuple)
+
+    def teardown_all(self):
+        while self.stack:
+            self._pop_and_teardown()
+        for key in list(self._finalizers):
+            self._teardown_with_finalization(key)
+        assert not self._finalizers
+
+    def teardown_exact(self, item, nextitem):
+        needed_collectors = nextitem and nextitem.listchain() or []
+        self._teardown_towards(needed_collectors)
+
+    def _teardown_towards(self, needed_collectors):
+        while self.stack:
+            if self.stack == needed_collectors[:len(self.stack)]:
+                break
+            self._pop_and_teardown()
+
+    def prepare(self, colitem):
+        """ setup objects along the collector chain to the test-method
+            and teardown previously setup objects."""
+        needed_collectors = colitem.listchain()
+        self._teardown_towards(needed_collectors)
+
+        # check if the last collection node has raised an error
+        for col in self.stack:
+            if hasattr(col, '_prepare_exc'):
+                py.builtin._reraise(*col._prepare_exc)
+        for col in needed_collectors[len(self.stack):]:
+            self.stack.append(col)
+            try:
+                col.setup()
+            except TEST_OUTCOME:
+                col._prepare_exc = sys.exc_info()
+                raise
+
+
+def collect_one_node(collector):
+    ihook = collector.ihook
+    ihook.pytest_collectstart(collector=collector)
+    rep = ihook.pytest_make_collect_report(collector=collector)
+    call = rep.__dict__.pop("call", None)
+    if call and check_interactive_exception(call, rep):
+        ihook.pytest_exception_interact(node=collector, call=call, report=rep)
+    return rep
diff --git a/tools/third_party/pytest/_pytest/setuponly.py b/tools/third_party/pytest/_pytest/setuponly.py
new file mode 100644
index 0000000..a1c7457
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/setuponly.py
@@ -0,0 +1,74 @@
+from __future__ import absolute_import, division, print_function
+
+import pytest
+import sys
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("debugconfig")
+    group.addoption('--setuponly', '--setup-only', action="store_true",
+                    help="only setup fixtures, do not execute tests.")
+    group.addoption('--setupshow', '--setup-show', action="store_true",
+                    help="show setup of fixtures while executing tests.")
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_fixture_setup(fixturedef, request):
+    yield
+    config = request.config
+    if config.option.setupshow:
+        if hasattr(request, 'param'):
+            # Save the fixture parameter so ._show_fixture_action() can
+            # display it now and during the teardown (in .finish()).
+            if fixturedef.ids:
+                if callable(fixturedef.ids):
+                    fixturedef.cached_param = fixturedef.ids(request.param)
+                else:
+                    fixturedef.cached_param = fixturedef.ids[
+                        request.param_index]
+            else:
+                fixturedef.cached_param = request.param
+        _show_fixture_action(fixturedef, 'SETUP')
+
+
+def pytest_fixture_post_finalizer(fixturedef):
+    if hasattr(fixturedef, "cached_result"):
+        config = fixturedef._fixturemanager.config
+        if config.option.setupshow:
+            _show_fixture_action(fixturedef, 'TEARDOWN')
+            if hasattr(fixturedef, "cached_param"):
+                del fixturedef.cached_param
+
+
+def _show_fixture_action(fixturedef, msg):
+    config = fixturedef._fixturemanager.config
+    capman = config.pluginmanager.getplugin('capturemanager')
+    if capman:
+        out, err = capman.suspend_global_capture()
+
+    tw = config.get_terminal_writer()
+    tw.line()
+    tw.write(' ' * 2 * fixturedef.scopenum)
+    tw.write('{step} {scope} {fixture}'.format(
+        step=msg.ljust(8),  # align the output to TEARDOWN
+        scope=fixturedef.scope[0].upper(),
+        fixture=fixturedef.argname))
+
+    if msg == 'SETUP':
+        deps = sorted(arg for arg in fixturedef.argnames if arg != 'request')
+        if deps:
+            tw.write(' (fixtures used: {0})'.format(', '.join(deps)))
+
+    if hasattr(fixturedef, 'cached_param'):
+        tw.write('[{0}]'.format(fixturedef.cached_param))
+
+    if capman:
+        capman.resume_global_capture()
+        sys.stdout.write(out)
+        sys.stderr.write(err)
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_main(config):
+    if config.option.setuponly:
+        config.option.setupshow = True
diff --git a/tools/third_party/pytest/_pytest/setupplan.py b/tools/third_party/pytest/_pytest/setupplan.py
new file mode 100644
index 0000000..e11bd40
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/setupplan.py
@@ -0,0 +1,25 @@
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("debugconfig")
+    group.addoption('--setupplan', '--setup-plan', action="store_true",
+                    help="show what fixtures and tests would be executed but "
+                    "don't execute anything.")
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_fixture_setup(fixturedef, request):
+    # Will return a dummy fixture if the setuponly option is provided.
+    if request.config.option.setupplan:
+        fixturedef.cached_result = (None, None, None)
+        return fixturedef.cached_result
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_main(config):
+    if config.option.setupplan:
+        config.option.setuponly = True
+        config.option.setupshow = True
diff --git a/tools/third_party/pytest/_pytest/skipping.py b/tools/third_party/pytest/_pytest/skipping.py
new file mode 100644
index 0000000..a1e5b43
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/skipping.py
@@ -0,0 +1,397 @@
+""" support for skip/xfail functions and markers. """
+from __future__ import absolute_import, division, print_function
+
+import os
+import six
+import sys
+import traceback
+
+from _pytest.config import hookimpl
+from _pytest.mark import MarkInfo, MarkDecorator
+from _pytest.outcomes import fail, skip, xfail, TEST_OUTCOME
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group.addoption('--runxfail',
+                    action="store_true", dest="runxfail", default=False,
+                    help="run tests even if they are marked xfail")
+
+    parser.addini("xfail_strict", "default for the strict parameter of xfail "
+                                  "markers when not given explicitly (default: "
+                                  "False)",
+                                  default=False,
+                                  type="bool")
+
+
+def pytest_configure(config):
+    if config.option.runxfail:
+        # yay a hack
+        import pytest
+        old = pytest.xfail
+        config._cleanup.append(lambda: setattr(pytest, "xfail", old))
+
+        def nop(*args, **kwargs):
+            pass
+
+        nop.Exception = xfail.Exception
+        setattr(pytest, "xfail", nop)
+
+    config.addinivalue_line("markers",
+                            "skip(reason=None): skip the given test function with an optional reason. "
+                            "Example: skip(reason=\"no way of currently testing this\") skips the "
+                            "test."
+                            )
+    config.addinivalue_line("markers",
+                            "skipif(condition): skip the given test function if eval(condition) "
+                            "results in a True value.  Evaluation happens within the "
+                            "module global context. Example: skipif('sys.platform == \"win32\"') "
+                            "skips the test if we are on the win32 platform. see "
+                            "http://pytest.org/latest/skipping.html"
+                            )
+    config.addinivalue_line("markers",
+                            "xfail(condition, reason=None, run=True, raises=None, strict=False): "
+                            "mark the test function as an expected failure if eval(condition) "
+                            "has a True value. Optionally specify a reason for better reporting "
+                            "and run=False if you don't even want to execute the test function. "
+                            "If only specific exception(s) are expected, you can list them in "
+                            "raises, and if the test fails in other ways, it will be reported as "
+                            "a true failure. See http://pytest.org/latest/skipping.html"
+                            )
+
+
+class MarkEvaluator(object):
+    def __init__(self, item, name):
+        self.item = item
+        self._marks = None
+        self._mark = None
+        self._mark_name = name
+
+    def __bool__(self):
+        self._marks = self._get_marks()
+        return bool(self._marks)
+    __nonzero__ = __bool__
+
+    def wasvalid(self):
+        return not hasattr(self, 'exc')
+
+    def _get_marks(self):
+
+        keyword = self.item.keywords.get(self._mark_name)
+        if isinstance(keyword, MarkDecorator):
+            return [keyword.mark]
+        elif isinstance(keyword, MarkInfo):
+            return [x.combined for x in keyword]
+        else:
+            return []
+
+    def invalidraise(self, exc):
+        raises = self.get('raises')
+        if not raises:
+            return
+        return not isinstance(exc, raises)
+
+    def istrue(self):
+        try:
+            return self._istrue()
+        except TEST_OUTCOME:
+            self.exc = sys.exc_info()
+            if isinstance(self.exc[1], SyntaxError):
+                msg = [" " * (self.exc[1].offset + 4) + "^", ]
+                msg.append("SyntaxError: invalid syntax")
+            else:
+                msg = traceback.format_exception_only(*self.exc[:2])
+            fail("Error evaluating %r expression\n"
+                 "    %s\n"
+                 "%s"
+                 % (self._mark_name, self.expr, "\n".join(msg)),
+                 pytrace=False)
+
+    def _getglobals(self):
+        d = {'os': os, 'sys': sys, 'config': self.item.config}
+        if hasattr(self.item, 'obj'):
+            d.update(self.item.obj.__globals__)
+        return d
+
+    def _istrue(self):
+        if hasattr(self, 'result'):
+            return self.result
+        self._marks = self._get_marks()
+
+        if self._marks:
+            self.result = False
+            for mark in self._marks:
+                self._mark = mark
+                if 'condition' in mark.kwargs:
+                    args = (mark.kwargs['condition'],)
+                else:
+                    args = mark.args
+
+                for expr in args:
+                    self.expr = expr
+                    if isinstance(expr, six.string_types):
+                        d = self._getglobals()
+                        result = cached_eval(self.item.config, expr, d)
+                    else:
+                        if "reason" not in mark.kwargs:
+                            # XXX better be checked at collection time
+                            msg = "you need to specify reason=STRING " \
+                                  "when using booleans as conditions."
+                            fail(msg)
+                        result = bool(expr)
+                    if result:
+                        self.result = True
+                        self.reason = mark.kwargs.get('reason', None)
+                        self.expr = expr
+                        return self.result
+
+                if not args:
+                    self.result = True
+                    self.reason = mark.kwargs.get('reason', None)
+                    return self.result
+        return False
+
+    def get(self, attr, default=None):
+        if self._mark is None:
+            return default
+        return self._mark.kwargs.get(attr, default)
+
+    def getexplanation(self):
+        expl = getattr(self, 'reason', None) or self.get('reason', None)
+        if not expl:
+            if not hasattr(self, 'expr'):
+                return ""
+            else:
+                return "condition: " + str(self.expr)
+        return expl
+
+
+@hookimpl(tryfirst=True)
+def pytest_runtest_setup(item):
+    # Check if skip or skipif are specified as pytest marks
+    item._skipped_by_mark = False
+    skipif_info = item.keywords.get('skipif')
+    if isinstance(skipif_info, (MarkInfo, MarkDecorator)):
+        eval_skipif = MarkEvaluator(item, 'skipif')
+        if eval_skipif.istrue():
+            item._skipped_by_mark = True
+            skip(eval_skipif.getexplanation())
+
+    skip_info = item.keywords.get('skip')
+    if isinstance(skip_info, (MarkInfo, MarkDecorator)):
+        item._skipped_by_mark = True
+        if 'reason' in skip_info.kwargs:
+            skip(skip_info.kwargs['reason'])
+        elif skip_info.args:
+            skip(skip_info.args[0])
+        else:
+            skip("unconditional skip")
+
+    item._evalxfail = MarkEvaluator(item, 'xfail')
+    check_xfail_no_run(item)
+
+
+@hookimpl(hookwrapper=True)
+def pytest_pyfunc_call(pyfuncitem):
+    check_xfail_no_run(pyfuncitem)
+    outcome = yield
+    passed = outcome.excinfo is None
+    if passed:
+        check_strict_xfail(pyfuncitem)
+
+
+def check_xfail_no_run(item):
+    """check xfail(run=False)"""
+    if not item.config.option.runxfail:
+        evalxfail = item._evalxfail
+        if evalxfail.istrue():
+            if not evalxfail.get('run', True):
+                xfail("[NOTRUN] " + evalxfail.getexplanation())
+
+
+def check_strict_xfail(pyfuncitem):
+    """check xfail(strict=True) for the given PASSING test"""
+    evalxfail = pyfuncitem._evalxfail
+    if evalxfail.istrue():
+        strict_default = pyfuncitem.config.getini('xfail_strict')
+        is_strict_xfail = evalxfail.get('strict', strict_default)
+        if is_strict_xfail:
+            del pyfuncitem._evalxfail
+            explanation = evalxfail.getexplanation()
+            fail('[XPASS(strict)] ' + explanation, pytrace=False)
+
+
+@hookimpl(hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+    outcome = yield
+    rep = outcome.get_result()
+    evalxfail = getattr(item, '_evalxfail', None)
+    # unitttest special case, see setting of _unexpectedsuccess
+    if hasattr(item, '_unexpectedsuccess') and rep.when == "call":
+        from _pytest.compat import _is_unittest_unexpected_success_a_failure
+        if item._unexpectedsuccess:
+            rep.longrepr = "Unexpected success: {0}".format(item._unexpectedsuccess)
+        else:
+            rep.longrepr = "Unexpected success"
+        if _is_unittest_unexpected_success_a_failure():
+            rep.outcome = "failed"
+        else:
+            rep.outcome = "passed"
+            rep.wasxfail = rep.longrepr
+    elif item.config.option.runxfail:
+        pass   # don't interefere
+    elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
+        rep.wasxfail = "reason: " + call.excinfo.value.msg
+        rep.outcome = "skipped"
+    elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
+            evalxfail.istrue():
+        if call.excinfo:
+            if evalxfail.invalidraise(call.excinfo.value):
+                rep.outcome = "failed"
+            else:
+                rep.outcome = "skipped"
+                rep.wasxfail = evalxfail.getexplanation()
+        elif call.when == "call":
+            strict_default = item.config.getini('xfail_strict')
+            is_strict_xfail = evalxfail.get('strict', strict_default)
+            explanation = evalxfail.getexplanation()
+            if is_strict_xfail:
+                rep.outcome = "failed"
+                rep.longrepr = "[XPASS(strict)] {0}".format(explanation)
+            else:
+                rep.outcome = "passed"
+                rep.wasxfail = explanation
+    elif item._skipped_by_mark and rep.skipped and type(rep.longrepr) is tuple:
+        # skipped by mark.skipif; change the location of the failure
+        # to point to the item definition, otherwise it will display
+        # the location of where the skip exception was raised within pytest
+        filename, line, reason = rep.longrepr
+        filename, line = item.location[:2]
+        rep.longrepr = filename, line, reason
+
+# called by terminalreporter progress reporting
+
+
+def pytest_report_teststatus(report):
+    if hasattr(report, "wasxfail"):
+        if report.skipped:
+            return "xfailed", "x", "xfail"
+        elif report.passed:
+            return "xpassed", "X", ("XPASS", {'yellow': True})
+
+# called by the terminalreporter instance/plugin
+
+
+def pytest_terminal_summary(terminalreporter):
+    tr = terminalreporter
+    if not tr.reportchars:
+        # for name in "xfailed skipped failed xpassed":
+        #    if not tr.stats.get(name, 0):
+        #        tr.write_line("HINT: use '-r' option to see extra "
+        #              "summary info about tests")
+        #        break
+        return
+
+    lines = []
+    for char in tr.reportchars:
+        if char == "x":
+            show_xfailed(terminalreporter, lines)
+        elif char == "X":
+            show_xpassed(terminalreporter, lines)
+        elif char in "fF":
+            show_simple(terminalreporter, lines, 'failed', "FAIL %s")
+        elif char in "sS":
+            show_skipped(terminalreporter, lines)
+        elif char == "E":
+            show_simple(terminalreporter, lines, 'error', "ERROR %s")
+        elif char == 'p':
+            show_simple(terminalreporter, lines, 'passed', "PASSED %s")
+
+    if lines:
+        tr._tw.sep("=", "short test summary info")
+        for line in lines:
+            tr._tw.line(line)
+
+
+def show_simple(terminalreporter, lines, stat, format):
+    failed = terminalreporter.stats.get(stat)
+    if failed:
+        for rep in failed:
+            pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
+            lines.append(format % (pos,))
+
+
+def show_xfailed(terminalreporter, lines):
+    xfailed = terminalreporter.stats.get("xfailed")
+    if xfailed:
+        for rep in xfailed:
+            pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
+            reason = rep.wasxfail
+            lines.append("XFAIL %s" % (pos,))
+            if reason:
+                lines.append("  " + str(reason))
+
+
+def show_xpassed(terminalreporter, lines):
+    xpassed = terminalreporter.stats.get("xpassed")
+    if xpassed:
+        for rep in xpassed:
+            pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
+            reason = rep.wasxfail
+            lines.append("XPASS %s %s" % (pos, reason))
+
+
+def cached_eval(config, expr, d):
+    if not hasattr(config, '_evalcache'):
+        config._evalcache = {}
+    try:
+        return config._evalcache[expr]
+    except KeyError:
+        import _pytest._code
+        exprcode = _pytest._code.compile(expr, mode="eval")
+        config._evalcache[expr] = x = eval(exprcode, d)
+        return x
+
+
+def folded_skips(skipped):
+    d = {}
+    for event in skipped:
+        key = event.longrepr
+        assert len(key) == 3, (event, key)
+        keywords = getattr(event, 'keywords', {})
+        # folding reports with global pytestmark variable
+        # this is workaround, because for now we cannot identify the scope of a skip marker
+        # TODO: revisit after marks scope would be fixed
+        when = getattr(event, 'when', None)
+        if when == 'setup' and 'skip' in keywords and 'pytestmark' not in keywords:
+            key = (key[0], None, key[2], )
+        d.setdefault(key, []).append(event)
+    values = []
+    for key, events in d.items():
+        values.append((len(events),) + key)
+    return values
+
+
+def show_skipped(terminalreporter, lines):
+    tr = terminalreporter
+    skipped = tr.stats.get('skipped', [])
+    if skipped:
+        # if not tr.hasopt('skipped'):
+        #    tr.write_line(
+        #        "%d skipped tests, specify -rs for more info" %
+        #        len(skipped))
+        #    return
+        fskips = folded_skips(skipped)
+        if fskips:
+            # tr.write_sep("_", "skipped test summary")
+            for num, fspath, lineno, reason in fskips:
+                if reason.startswith("Skipped: "):
+                    reason = reason[9:]
+                if lineno is not None:
+                    lines.append(
+                        "SKIP [%d] %s:%d: %s" %
+                        (num, fspath, lineno + 1, reason))
+                else:
+                    lines.append(
+                        "SKIP [%d] %s: %s" %
+                        (num, fspath, reason))
diff --git a/tools/third_party/pytest/_pytest/terminal.py b/tools/third_party/pytest/_pytest/terminal.py
new file mode 100644
index 0000000..1aba5e8
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/terminal.py
@@ -0,0 +1,703 @@
+""" terminal reporting of the full testing process.
+
+This is a good source for looking at the various reporting hooks.
+"""
+from __future__ import absolute_import, division, print_function
+
+import itertools
+import platform
+import sys
+import time
+
+import pluggy
+import py
+import six
+
+import pytest
+from _pytest import nodes
+from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
+    EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("terminal reporting", "reporting", after="general")
+    group._addoption('-v', '--verbose', action="count",
+                     dest="verbose", default=0, help="increase verbosity."),
+    group._addoption('-q', '--quiet', action="count",
+                     dest="quiet", default=0, help="decrease verbosity."),
+    group._addoption('-r',
+                     action="store", dest="reportchars", default='', metavar="chars",
+                     help="show extra test summary info as specified by chars (f)ailed, "
+                     "(E)error, (s)skipped, (x)failed, (X)passed, "
+                     "(p)passed, (P)passed with output, (a)all except pP. "
+                     "Warnings are displayed at all times except when "
+                     "--disable-warnings is set")
+    group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
+                     dest='disable_warnings', action='store_true',
+                     help='disable warnings summary')
+    group._addoption('-l', '--showlocals',
+                     action="store_true", dest="showlocals", default=False,
+                     help="show locals in tracebacks (disabled by default).")
+    group._addoption('--tb', metavar="style",
+                     action="store", dest="tbstyle", default='auto',
+                     choices=['auto', 'long', 'short', 'no', 'line', 'native'],
+                     help="traceback print mode (auto/long/short/line/native/no).")
+    group._addoption('--fulltrace', '--full-trace',
+                     action="store_true", default=False,
+                     help="don't cut any tracebacks (default is to cut).")
+    group._addoption('--color', metavar="color",
+                     action="store", dest="color", default='auto',
+                     choices=['yes', 'no', 'auto'],
+                     help="color terminal output (yes/no/auto).")
+
+    parser.addini("console_output_style",
+                  help="console output: classic or with additional progress information (classic|progress).",
+                  default='progress')
+
+
+def pytest_configure(config):
+    config.option.verbose -= config.option.quiet
+    reporter = TerminalReporter(config, sys.stdout)
+    config.pluginmanager.register(reporter, 'terminalreporter')
+    if config.option.debug or config.option.traceconfig:
+        def mywriter(tags, args):
+            msg = " ".join(map(str, args))
+            reporter.write_line("[traceconfig] " + msg)
+        config.trace.root.setprocessor("pytest:config", mywriter)
+
+
+def getreportopt(config):
+    reportopts = ""
+    reportchars = config.option.reportchars
+    if not config.option.disable_warnings and 'w' not in reportchars:
+        reportchars += 'w'
+    elif config.option.disable_warnings and 'w' in reportchars:
+        reportchars = reportchars.replace('w', '')
+    if reportchars:
+        for char in reportchars:
+            if char not in reportopts and char != 'a':
+                reportopts += char
+            elif char == 'a':
+                reportopts = 'fEsxXw'
+    return reportopts
+
+
+def pytest_report_teststatus(report):
+    if report.passed:
+        letter = "."
+    elif report.skipped:
+        letter = "s"
+    elif report.failed:
+        letter = "F"
+        if report.when != "call":
+            letter = "f"
+    return report.outcome, letter, report.outcome.upper()
+
+
+class WarningReport:
+    """
+    Simple structure to hold warnings information captured by ``pytest_logwarning``.
+    """
+
+    def __init__(self, code, message, nodeid=None, fslocation=None):
+        """
+        :param code: unused
+        :param str message: user friendly message about the warning
+        :param str|None nodeid: node id that generated the warning (see ``get_location``).
+        :param tuple|py.path.local fslocation:
+            file system location of the source of the warning (see ``get_location``).
+        """
+        self.code = code
+        self.message = message
+        self.nodeid = nodeid
+        self.fslocation = fslocation
+
+    def get_location(self, config):
+        """
+        Returns the more user-friendly information about the location
+        of a warning, or None.
+        """
+        if self.nodeid:
+            return self.nodeid
+        if self.fslocation:
+            if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
+                filename, linenum = self.fslocation[:2]
+                relpath = py.path.local(filename).relto(config.invocation_dir)
+                return '%s:%s' % (relpath, linenum)
+            else:
+                return str(self.fslocation)
+        return None
+
+
+class TerminalReporter:
+    def __init__(self, config, file=None):
+        import _pytest.config
+        self.config = config
+        self.verbosity = self.config.option.verbose
+        self.showheader = self.verbosity >= 0
+        self.showfspath = self.verbosity >= 0
+        self.showlongtestinfo = self.verbosity > 0
+        self._numcollected = 0
+        self._session = None
+
+        self.stats = {}
+        self.startdir = py.path.local()
+        if file is None:
+            file = sys.stdout
+        self._tw = _pytest.config.create_terminal_writer(config, file)
+        # self.writer will be deprecated in pytest-3.4
+        self.writer = self._tw
+        self._screen_width = self._tw.fullwidth
+        self.currentfspath = None
+        self.reportchars = getreportopt(config)
+        self.hasmarkup = self._tw.hasmarkup
+        self.isatty = file.isatty()
+        self._progress_items_reported = 0
+        self._show_progress_info = self.config.getini('console_output_style') == 'progress'
+
+    def hasopt(self, char):
+        char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
+        return char in self.reportchars
+
+    def write_fspath_result(self, nodeid, res):
+        fspath = self.config.rootdir.join(nodeid.split("::")[0])
+        if fspath != self.currentfspath:
+            if self.currentfspath is not None:
+                self._write_progress_information_filling_space()
+            self.currentfspath = fspath
+            fspath = self.startdir.bestrelpath(fspath)
+            self._tw.line()
+            self._tw.write(fspath + " ")
+        self._tw.write(res)
+
+    def write_ensure_prefix(self, prefix, extra="", **kwargs):
+        if self.currentfspath != prefix:
+            self._tw.line()
+            self.currentfspath = prefix
+            self._tw.write(prefix)
+        if extra:
+            self._tw.write(extra, **kwargs)
+            self.currentfspath = -2
+            self._write_progress_information_filling_space()
+
+    def ensure_newline(self):
+        if self.currentfspath:
+            self._tw.line()
+            self.currentfspath = None
+
+    def write(self, content, **markup):
+        self._tw.write(content, **markup)
+
+    def write_line(self, line, **markup):
+        if not isinstance(line, six.text_type):
+            line = six.text_type(line, errors="replace")
+        self.ensure_newline()
+        self._tw.line(line, **markup)
+
+    def rewrite(self, line, **markup):
+        """
+        Rewinds the terminal cursor to the beginning and writes the given line.
+
+        :kwarg erase: if True, will also add spaces until the full terminal width to ensure
+            previous lines are properly erased.
+
+        The rest of the keyword arguments are markup instructions.
+        """
+        erase = markup.pop('erase', False)
+        if erase:
+            fill_count = self._tw.fullwidth - len(line) - 1
+            fill = ' ' * fill_count
+        else:
+            fill = ''
+        line = str(line)
+        self._tw.write("\r" + line + fill, **markup)
+
+    def write_sep(self, sep, title=None, **markup):
+        self.ensure_newline()
+        self._tw.sep(sep, title, **markup)
+
+    def section(self, title, sep="=", **kw):
+        self._tw.sep(sep, title, **kw)
+
+    def line(self, msg, **kw):
+        self._tw.line(msg, **kw)
+
+    def pytest_internalerror(self, excrepr):
+        for line in six.text_type(excrepr).split("\n"):
+            self.write_line("INTERNALERROR> " + line)
+        return 1
+
+    def pytest_logwarning(self, code, fslocation, message, nodeid):
+        warnings = self.stats.setdefault("warnings", [])
+        warning = WarningReport(code=code, fslocation=fslocation,
+                                message=message, nodeid=nodeid)
+        warnings.append(warning)
+
+    def pytest_plugin_registered(self, plugin):
+        if self.config.option.traceconfig:
+            msg = "PLUGIN registered: %s" % (plugin,)
+            # XXX this event may happen during setup/teardown time
+            #     which unfortunately captures our output here
+            #     which garbles our output if we use self.write_line
+            self.write_line(msg)
+
+    def pytest_deselected(self, items):
+        self.stats.setdefault('deselected', []).extend(items)
+
+    def pytest_runtest_logstart(self, nodeid, location):
+        # ensure that the path is printed before the
+        # 1st test of a module starts running
+        if self.showlongtestinfo:
+            line = self._locationline(nodeid, *location)
+            self.write_ensure_prefix(line, "")
+        elif self.showfspath:
+            fsid = nodeid.split("::")[0]
+            self.write_fspath_result(fsid, "")
+
+    def pytest_runtest_logreport(self, report):
+        rep = report
+        res = self.config.hook.pytest_report_teststatus(report=rep)
+        cat, letter, word = res
+        if isinstance(word, tuple):
+            word, markup = word
+        else:
+            markup = None
+        self.stats.setdefault(cat, []).append(rep)
+        self._tests_ran = True
+        if not letter and not word:
+            # probably passed setup/teardown
+            return
+        running_xdist = hasattr(rep, 'node')
+        self._progress_items_reported += 1
+        if self.verbosity <= 0:
+            if not running_xdist and self.showfspath:
+                self.write_fspath_result(rep.nodeid, letter)
+            else:
+                self._tw.write(letter)
+            self._write_progress_if_past_edge()
+        else:
+            if markup is None:
+                if rep.passed:
+                    markup = {'green': True}
+                elif rep.failed:
+                    markup = {'red': True}
+                elif rep.skipped:
+                    markup = {'yellow': True}
+                else:
+                    markup = {}
+            line = self._locationline(rep.nodeid, *rep.location)
+            if not running_xdist:
+                self.write_ensure_prefix(line, word, **markup)
+            else:
+                self.ensure_newline()
+                self._tw.write("[%s]" % rep.node.gateway.id)
+                if self._show_progress_info:
+                    self._tw.write(self._get_progress_information_message() + " ", cyan=True)
+                else:
+                    self._tw.write(' ')
+                self._tw.write(word, **markup)
+                self._tw.write(" " + line)
+                self.currentfspath = -2
+
+    def _write_progress_if_past_edge(self):
+        if not self._show_progress_info:
+            return
+        last_item = self._progress_items_reported == self._session.testscollected
+        if last_item:
+            self._write_progress_information_filling_space()
+            return
+
+        past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width
+        if past_edge:
+            msg = self._get_progress_information_message()
+            self._tw.write(msg + '\n', cyan=True)
+
+    _PROGRESS_LENGTH = len(' [100%]')
+
+    def _get_progress_information_message(self):
+        collected = self._session.testscollected
+        if collected:
+            progress = self._progress_items_reported * 100 // collected
+            return ' [{:3d}%]'.format(progress)
+        return ' [100%]'
+
+    def _write_progress_information_filling_space(self):
+        if not self._show_progress_info:
+            return
+        msg = self._get_progress_information_message()
+        fill = ' ' * (self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1)
+        self.write(fill + msg, cyan=True)
+
+    def pytest_collection(self):
+        if not self.isatty and self.config.option.verbose >= 1:
+            self.write("collecting ... ", bold=True)
+
+    def pytest_collectreport(self, report):
+        if report.failed:
+            self.stats.setdefault("error", []).append(report)
+        elif report.skipped:
+            self.stats.setdefault("skipped", []).append(report)
+        items = [x for x in report.result if isinstance(x, pytest.Item)]
+        self._numcollected += len(items)
+        if self.isatty:
+            # self.write_fspath_result(report.nodeid, 'E')
+            self.report_collect()
+
+    def report_collect(self, final=False):
+        if self.config.option.verbose < 0:
+            return
+
+        errors = len(self.stats.get('error', []))
+        skipped = len(self.stats.get('skipped', []))
+        if final:
+            line = "collected "
+        else:
+            line = "collecting "
+        line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's')
+        if errors:
+            line += " / %d errors" % errors
+        if skipped:
+            line += " / %d skipped" % skipped
+        if self.isatty:
+            self.rewrite(line, bold=True, erase=True)
+            if final:
+                self.write('\n')
+        else:
+            self.write_line(line)
+
+    def pytest_collection_modifyitems(self):
+        self.report_collect(True)
+
+    @pytest.hookimpl(trylast=True)
+    def pytest_sessionstart(self, session):
+        self._session = session
+        self._sessionstarttime = time.time()
+        if not self.showheader:
+            return
+        self.write_sep("=", "test session starts", bold=True)
+        verinfo = platform.python_version()
+        msg = "platform %s -- Python %s" % (sys.platform, verinfo)
+        if hasattr(sys, 'pypy_version_info'):
+            verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
+            msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
+        msg += ", pytest-%s, py-%s, pluggy-%s" % (
+               pytest.__version__, py.__version__, pluggy.__version__)
+        if self.verbosity > 0 or self.config.option.debug or \
+           getattr(self.config.option, 'pastebin', None):
+            msg += " -- " + str(sys.executable)
+        self.write_line(msg)
+        lines = self.config.hook.pytest_report_header(
+            config=self.config, startdir=self.startdir)
+        self._write_report_lines_from_hooks(lines)
+
+    def _write_report_lines_from_hooks(self, lines):
+        lines.reverse()
+        for line in flatten(lines):
+            self.write_line(line)
+
+    def pytest_report_header(self, config):
+        inifile = ""
+        if config.inifile:
+            inifile = " " + config.rootdir.bestrelpath(config.inifile)
+        lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)]
+
+        plugininfo = config.pluginmanager.list_plugin_distinfo()
+        if plugininfo:
+
+            lines.append(
+                "plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
+        return lines
+
+    def pytest_collection_finish(self, session):
+        if self.config.option.collectonly:
+            self._printcollecteditems(session.items)
+            if self.stats.get('failed'):
+                self._tw.sep("!", "collection failures")
+                for rep in self.stats.get('failed'):
+                    rep.toterminal(self._tw)
+                return 1
+            return 0
+        lines = self.config.hook.pytest_report_collectionfinish(
+            config=self.config, startdir=self.startdir, items=session.items)
+        self._write_report_lines_from_hooks(lines)
+
+    def _printcollecteditems(self, items):
+        # to print out items and their parent collectors
+        # we take care to leave out Instances aka ()
+        # because later versions are going to get rid of them anyway
+        if self.config.option.verbose < 0:
+            if self.config.option.verbose < -1:
+                counts = {}
+                for item in items:
+                    name = item.nodeid.split('::', 1)[0]
+                    counts[name] = counts.get(name, 0) + 1
+                for name, count in sorted(counts.items()):
+                    self._tw.line("%s: %d" % (name, count))
+            else:
+                for item in items:
+                    nodeid = item.nodeid
+                    nodeid = nodeid.replace("::()::", "::")
+                    self._tw.line(nodeid)
+            return
+        stack = []
+        indent = ""
+        for item in items:
+            needed_collectors = item.listchain()[1:]  # strip root node
+            while stack:
+                if stack == needed_collectors[:len(stack)]:
+                    break
+                stack.pop()
+            for col in needed_collectors[len(stack):]:
+                stack.append(col)
+                # if col.name == "()":
+                #    continue
+                indent = (len(stack) - 1) * "  "
+                self._tw.line("%s%s" % (indent, col))
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_sessionfinish(self, exitstatus):
+        outcome = yield
+        outcome.get_result()
+        self._tw.line("")
+        summary_exit_codes = (
+            EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
+            EXIT_NOTESTSCOLLECTED)
+        if exitstatus in summary_exit_codes:
+            self.config.hook.pytest_terminal_summary(terminalreporter=self,
+                                                     exitstatus=exitstatus)
+            self.summary_errors()
+            self.summary_failures()
+            self.summary_warnings()
+            self.summary_passes()
+        if exitstatus == EXIT_INTERRUPTED:
+            self._report_keyboardinterrupt()
+            del self._keyboardinterrupt_memo
+        self.summary_deselected()
+        self.summary_stats()
+
+    def pytest_keyboard_interrupt(self, excinfo):
+        self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
+
+    def pytest_unconfigure(self):
+        if hasattr(self, '_keyboardinterrupt_memo'):
+            self._report_keyboardinterrupt()
+
+    def _report_keyboardinterrupt(self):
+        excrepr = self._keyboardinterrupt_memo
+        msg = excrepr.reprcrash.message
+        self.write_sep("!", msg)
+        if "KeyboardInterrupt" in msg:
+            if self.config.option.fulltrace:
+                excrepr.toterminal(self._tw)
+            else:
+                self._tw.line("to show a full traceback on KeyboardInterrupt use --fulltrace", yellow=True)
+                excrepr.reprcrash.toterminal(self._tw)
+
+    def _locationline(self, nodeid, fspath, lineno, domain):
+        def mkrel(nodeid):
+            line = self.config.cwd_relative_nodeid(nodeid)
+            if domain and line.endswith(domain):
+                line = line[:-len(domain)]
+                values = domain.split("[")
+                values[0] = values[0].replace('.', '::')  # don't replace '.' in params
+                line += "[".join(values)
+            return line
+        # collect_fspath comes from testid which has a "/"-normalized path
+
+        if fspath:
+            res = mkrel(nodeid).replace("::()", "")  # parens-normalization
+            if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
+                res += " <- " + self.startdir.bestrelpath(fspath)
+        else:
+            res = "[location]"
+        return res + " "
+
+    def _getfailureheadline(self, rep):
+        if hasattr(rep, 'location'):
+            fspath, lineno, domain = rep.location
+            return domain
+        else:
+            return "test session"  # XXX?
+
+    def _getcrashline(self, rep):
+        try:
+            return str(rep.longrepr.reprcrash)
+        except AttributeError:
+            try:
+                return str(rep.longrepr)[:50]
+            except AttributeError:
+                return ""
+
+    #
+    # summaries for sessionfinish
+    #
+    def getreports(self, name):
+        values = []
+        for x in self.stats.get(name, []):
+            if not hasattr(x, '_pdbshown'):
+                values.append(x)
+        return values
+
+    def summary_warnings(self):
+        if self.hasopt("w"):
+            all_warnings = self.stats.get("warnings")
+            if not all_warnings:
+                return
+
+            grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
+
+            self.write_sep("=", "warnings summary", yellow=True, bold=False)
+            for location, warning_records in grouped:
+                self._tw.line(str(location) or '<undetermined location>')
+                for w in warning_records:
+                    lines = w.message.splitlines()
+                    indented = '\n'.join('  ' + x for x in lines)
+                    self._tw.line(indented)
+                self._tw.line()
+            self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html')
+
+    def summary_passes(self):
+        if self.config.option.tbstyle != "no":
+            if self.hasopt("P"):
+                reports = self.getreports('passed')
+                if not reports:
+                    return
+                self.write_sep("=", "PASSES")
+                for rep in reports:
+                    msg = self._getfailureheadline(rep)
+                    self.write_sep("_", msg)
+                    self._outrep_summary(rep)
+
+    def print_teardown_sections(self, rep):
+        for secname, content in rep.sections:
+            if 'teardown' in secname:
+                self._tw.sep('-', secname)
+                if content[-1:] == "\n":
+                    content = content[:-1]
+                self._tw.line(content)
+
+    def summary_failures(self):
+        if self.config.option.tbstyle != "no":
+            reports = self.getreports('failed')
+            if not reports:
+                return
+            self.write_sep("=", "FAILURES")
+            for rep in reports:
+                if self.config.option.tbstyle == "line":
+                    line = self._getcrashline(rep)
+                    self.write_line(line)
+                else:
+                    msg = self._getfailureheadline(rep)
+                    markup = {'red': True, 'bold': True}
+                    self.write_sep("_", msg, **markup)
+                    self._outrep_summary(rep)
+                    for report in self.getreports(''):
+                        if report.nodeid == rep.nodeid and report.when == 'teardown':
+                            self.print_teardown_sections(report)
+
+    def summary_errors(self):
+        if self.config.option.tbstyle != "no":
+            reports = self.getreports('error')
+            if not reports:
+                return
+            self.write_sep("=", "ERRORS")
+            for rep in self.stats['error']:
+                msg = self._getfailureheadline(rep)
+                if not hasattr(rep, 'when'):
+                    # collect
+                    msg = "ERROR collecting " + msg
+                elif rep.when == "setup":
+                    msg = "ERROR at setup of " + msg
+                elif rep.when == "teardown":
+                    msg = "ERROR at teardown of " + msg
+                self.write_sep("_", msg)
+                self._outrep_summary(rep)
+
+    def _outrep_summary(self, rep):
+        rep.toterminal(self._tw)
+        for secname, content in rep.sections:
+            self._tw.sep("-", secname)
+            if content[-1:] == "\n":
+                content = content[:-1]
+            self._tw.line(content)
+
+    def summary_stats(self):
+        session_duration = time.time() - self._sessionstarttime
+        (line, color) = build_summary_stats_line(self.stats)
+        msg = "%s in %.2f seconds" % (line, session_duration)
+        markup = {color: True, 'bold': True}
+
+        if self.verbosity >= 0:
+            self.write_sep("=", msg, **markup)
+        if self.verbosity == -1:
+            self.write_line(msg, **markup)
+
+    def summary_deselected(self):
+        if 'deselected' in self.stats:
+            self.write_sep("=", "%d tests deselected" % (
+                len(self.stats['deselected'])), bold=True)
+
+
+def repr_pythonversion(v=None):
+    if v is None:
+        v = sys.version_info
+    try:
+        return "%s.%s.%s-%s-%s" % v
+    except (TypeError, ValueError):
+        return str(v)
+
+
+def flatten(values):
+    for x in values:
+        if isinstance(x, (list, tuple)):
+            for y in flatten(x):
+                yield y
+        else:
+            yield x
+
+
+def build_summary_stats_line(stats):
+    keys = ("failed passed skipped deselected "
+            "xfailed xpassed warnings error").split()
+    unknown_key_seen = False
+    for key in stats.keys():
+        if key not in keys:
+            if key:  # setup/teardown reports have an empty key, ignore them
+                keys.append(key)
+                unknown_key_seen = True
+    parts = []
+    for key in keys:
+        val = stats.get(key, None)
+        if val:
+            parts.append("%d %s" % (len(val), key))
+
+    if parts:
+        line = ", ".join(parts)
+    else:
+        line = "no tests ran"
+
+    if 'failed' in stats or 'error' in stats:
+        color = 'red'
+    elif 'warnings' in stats or unknown_key_seen:
+        color = 'yellow'
+    elif 'passed' in stats:
+        color = 'green'
+    else:
+        color = 'yellow'
+
+    return (line, color)
+
+
+def _plugin_nameversions(plugininfo):
+    values = []
+    for plugin, dist in plugininfo:
+        # gets us name and version!
+        name = '{dist.project_name}-{dist.version}'.format(dist=dist)
+        # questionable convenience, but it keeps things short
+        if name.startswith("pytest-"):
+            name = name[7:]
+        # we decided to print python package names
+        # they can have more than one plugin
+        if name not in values:
+            values.append(name)
+    return values
diff --git a/tools/third_party/pytest/_pytest/tmpdir.py b/tools/third_party/pytest/_pytest/tmpdir.py
new file mode 100644
index 0000000..da1b032
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/tmpdir.py
@@ -0,0 +1,126 @@
+""" support for providing temporary directories to test functions.  """
+from __future__ import absolute_import, division, print_function
+
+import re
+
+import pytest
+import py
+from _pytest.monkeypatch import MonkeyPatch
+
+
+class TempdirFactory:
+    """Factory for temporary directories under the common base temp directory.
+
+    The base directory can be configured using the ``--basetemp`` option.
+    """
+
+    def __init__(self, config):
+        self.config = config
+        self.trace = config.trace.get("tmpdir")
+
+    def ensuretemp(self, string, dir=1):
+        """ (deprecated) return temporary directory path with
+            the given string as the trailing part.  It is usually
+            better to use the 'tmpdir' function argument which
+            provides an empty unique-per-test-invocation directory
+            and is guaranteed to be empty.
+        """
+        # py.log._apiwarn(">1.1", "use tmpdir function argument")
+        return self.getbasetemp().ensure(string, dir=dir)
+
+    def mktemp(self, basename, numbered=True):
+        """Create a subdirectory of the base temporary directory and return it.
+        If ``numbered``, ensure the directory is unique by adding a number
+        prefix greater than any existing one.
+        """
+        basetemp = self.getbasetemp()
+        if not numbered:
+            p = basetemp.mkdir(basename)
+        else:
+            p = py.path.local.make_numbered_dir(prefix=basename,
+                                                keep=0, rootdir=basetemp, lock_timeout=None)
+        self.trace("mktemp", p)
+        return p
+
+    def getbasetemp(self):
+        """ return base temporary directory. """
+        try:
+            return self._basetemp
+        except AttributeError:
+            basetemp = self.config.option.basetemp
+            if basetemp:
+                basetemp = py.path.local(basetemp)
+                if basetemp.check():
+                    basetemp.remove()
+                basetemp.mkdir()
+            else:
+                temproot = py.path.local.get_temproot()
+                user = get_user()
+                if user:
+                    # use a sub-directory in the temproot to speed-up
+                    # make_numbered_dir() call
+                    rootdir = temproot.join('pytest-of-%s' % user)
+                else:
+                    rootdir = temproot
+                rootdir.ensure(dir=1)
+                basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
+                                                           rootdir=rootdir)
+            self._basetemp = t = basetemp.realpath()
+            self.trace("new basetemp", t)
+            return t
+
+    def finish(self):
+        self.trace("finish")
+
+
+def get_user():
+    """Return the current user name, or None if getuser() does not work
+    in the current environment (see #1010).
+    """
+    import getpass
+    try:
+        return getpass.getuser()
+    except (ImportError, KeyError):
+        return None
+
+
+# backward compatibility
+TempdirHandler = TempdirFactory
+
+
+def pytest_configure(config):
+    """Create a TempdirFactory and attach it to the config object.
+
+    This is to comply with existing plugins which expect the handler to be
+    available at pytest_configure time, but ideally should be moved entirely
+    to the tmpdir_factory session fixture.
+    """
+    mp = MonkeyPatch()
+    t = TempdirFactory(config)
+    config._cleanup.extend([mp.undo, t.finish])
+    mp.setattr(config, '_tmpdirhandler', t, raising=False)
+    mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
+
+
+@pytest.fixture(scope='session')
+def tmpdir_factory(request):
+    """Return a TempdirFactory instance for the test session.
+    """
+    return request.config._tmpdirhandler
+
+
+@pytest.fixture
+def tmpdir(request, tmpdir_factory):
+    """Return a temporary directory path object
+    which is unique to each test function invocation,
+    created as a sub directory of the base temporary
+    directory.  The returned object is a `py.path.local`_
+    path object.
+    """
+    name = request.node.name
+    name = re.sub(r"[\W]", "_", name)
+    MAXVAL = 30
+    if len(name) > MAXVAL:
+        name = name[:MAXVAL]
+    x = tmpdir_factory.mktemp(name, numbered=True)
+    return x
diff --git a/tools/third_party/pytest/_pytest/unittest.py b/tools/third_party/pytest/_pytest/unittest.py
new file mode 100644
index 0000000..3ddb394
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/unittest.py
@@ -0,0 +1,237 @@
+""" discovery and running of std-library "unittest" style tests. """
+from __future__ import absolute_import, division, print_function
+
+import sys
+import traceback
+
+# for transferring markers
+import _pytest._code
+from _pytest.config import hookimpl
+from _pytest.outcomes import fail, skip, xfail
+from _pytest.python import transfer_markers, Class, Module, Function
+
+
+def pytest_pycollect_makeitem(collector, name, obj):
+    # has unittest been imported and is obj a subclass of its TestCase?
+    try:
+        if not issubclass(obj, sys.modules["unittest"].TestCase):
+            return
+    except Exception:
+        return
+    # yes, so let's collect it
+    return UnitTestCase(name, parent=collector)
+
+
+class UnitTestCase(Class):
+    # marker for fixturemanger.getfixtureinfo()
+    # to declare that our children do not support funcargs
+    nofuncargs = True
+
+    def setup(self):
+        cls = self.obj
+        if getattr(cls, '__unittest_skip__', False):
+            return  # skipped
+        setup = getattr(cls, 'setUpClass', None)
+        if setup is not None:
+            setup()
+        teardown = getattr(cls, 'tearDownClass', None)
+        if teardown is not None:
+            self.addfinalizer(teardown)
+        super(UnitTestCase, self).setup()
+
+    def collect(self):
+        from unittest import TestLoader
+        cls = self.obj
+        if not getattr(cls, "__test__", True):
+            return
+        self.session._fixturemanager.parsefactories(self, unittest=True)
+        loader = TestLoader()
+        module = self.getparent(Module).obj
+        foundsomething = False
+        for name in loader.getTestCaseNames(self.obj):
+            x = getattr(self.obj, name)
+            if not getattr(x, '__test__', True):
+                continue
+            funcobj = getattr(x, 'im_func', x)
+            transfer_markers(funcobj, cls, module)
+            yield TestCaseFunction(name, parent=self)
+            foundsomething = True
+
+        if not foundsomething:
+            runtest = getattr(self.obj, 'runTest', None)
+            if runtest is not None:
+                ut = sys.modules.get("twisted.trial.unittest", None)
+                if ut is None or runtest != ut.TestCase.runTest:
+                    yield TestCaseFunction('runTest', parent=self)
+
+
+class TestCaseFunction(Function):
+    _excinfo = None
+
+    def setup(self):
+        self._testcase = self.parent.obj(self.name)
+        self._fix_unittest_skip_decorator()
+        self._obj = getattr(self._testcase, self.name)
+        if hasattr(self._testcase, 'setup_method'):
+            self._testcase.setup_method(self._obj)
+        if hasattr(self, "_request"):
+            self._request._fillfixtures()
+
+    def _fix_unittest_skip_decorator(self):
+        """
+        The @unittest.skip decorator calls functools.wraps(self._testcase)
+        The call to functools.wraps() fails unless self._testcase
+        has a __name__ attribute. This is usually automatically supplied
+        if the test is a function or method, but we need to add manually
+        here.
+
+        See issue #1169
+        """
+        if sys.version_info[0] == 2:
+            setattr(self._testcase, "__name__", self.name)
+
+    def teardown(self):
+        if hasattr(self._testcase, 'teardown_method'):
+            self._testcase.teardown_method(self._obj)
+        # Allow garbage collection on TestCase instance attributes.
+        self._testcase = None
+        self._obj = None
+
+    def startTest(self, testcase):
+        pass
+
+    def _addexcinfo(self, rawexcinfo):
+        # unwrap potential exception info (see twisted trial support below)
+        rawexcinfo = getattr(rawexcinfo, '_rawexcinfo', rawexcinfo)
+        try:
+            excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
+        except TypeError:
+            try:
+                try:
+                    values = traceback.format_exception(*rawexcinfo)
+                    values.insert(0, "NOTE: Incompatible Exception Representation, "
+                                  "displaying natively:\n\n")
+                    fail("".join(values), pytrace=False)
+                except (fail.Exception, KeyboardInterrupt):
+                    raise
+                except:  # noqa
+                    fail("ERROR: Unknown Incompatible Exception "
+                         "representation:\n%r" % (rawexcinfo,), pytrace=False)
+            except KeyboardInterrupt:
+                raise
+            except fail.Exception:
+                excinfo = _pytest._code.ExceptionInfo()
+        self.__dict__.setdefault('_excinfo', []).append(excinfo)
+
+    def addError(self, testcase, rawexcinfo):
+        self._addexcinfo(rawexcinfo)
+
+    def addFailure(self, testcase, rawexcinfo):
+        self._addexcinfo(rawexcinfo)
+
+    def addSkip(self, testcase, reason):
+        try:
+            skip(reason)
+        except skip.Exception:
+            self._skipped_by_mark = True
+            self._addexcinfo(sys.exc_info())
+
+    def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
+        try:
+            xfail(str(reason))
+        except xfail.Exception:
+            self._addexcinfo(sys.exc_info())
+
+    def addUnexpectedSuccess(self, testcase, reason=""):
+        self._unexpectedsuccess = reason
+
+    def addSuccess(self, testcase):
+        pass
+
+    def stopTest(self, testcase):
+        pass
+
+    def _handle_skip(self):
+        # implements the skipping machinery (see #2137)
+        # analog to pythons Lib/unittest/case.py:run
+        testMethod = getattr(self._testcase, self._testcase._testMethodName)
+        if (getattr(self._testcase.__class__, "__unittest_skip__", False) or
+                getattr(testMethod, "__unittest_skip__", False)):
+            # If the class or method was skipped.
+            skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or
+                        getattr(testMethod, '__unittest_skip_why__', ''))
+            try:  # PY3, unittest2 on PY2
+                self._testcase._addSkip(self, self._testcase, skip_why)
+            except TypeError:  # PY2
+                if sys.version_info[0] != 2:
+                    raise
+                self._testcase._addSkip(self, skip_why)
+            return True
+        return False
+
+    def runtest(self):
+        if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
+            self._testcase(result=self)
+        else:
+            # disables tearDown and cleanups for post mortem debugging (see #1890)
+            if self._handle_skip():
+                return
+            self._testcase.debug()
+
+    def _prunetraceback(self, excinfo):
+        Function._prunetraceback(self, excinfo)
+        traceback = excinfo.traceback.filter(
+            lambda x: not x.frame.f_globals.get('__unittest'))
+        if traceback:
+            excinfo.traceback = traceback
+
+
+@hookimpl(tryfirst=True)
+def pytest_runtest_makereport(item, call):
+    if isinstance(item, TestCaseFunction):
+        if item._excinfo:
+            call.excinfo = item._excinfo.pop(0)
+            try:
+                del call.result
+            except AttributeError:
+                pass
+
+# twisted trial support
+
+
+@hookimpl(hookwrapper=True)
+def pytest_runtest_protocol(item):
+    if isinstance(item, TestCaseFunction) and \
+       'twisted.trial.unittest' in sys.modules:
+        ut = sys.modules['twisted.python.failure']
+        Failure__init__ = ut.Failure.__init__
+        check_testcase_implements_trial_reporter()
+
+        def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
+                     captureVars=None):
+            if exc_value is None:
+                self._rawexcinfo = sys.exc_info()
+            else:
+                if exc_type is None:
+                    exc_type = type(exc_value)
+                self._rawexcinfo = (exc_type, exc_value, exc_tb)
+            try:
+                Failure__init__(self, exc_value, exc_type, exc_tb,
+                                captureVars=captureVars)
+            except TypeError:
+                Failure__init__(self, exc_value, exc_type, exc_tb)
+
+        ut.Failure.__init__ = excstore
+        yield
+        ut.Failure.__init__ = Failure__init__
+    else:
+        yield
+
+
+def check_testcase_implements_trial_reporter(done=[]):
+    if done:
+        return
+    from zope.interface import classImplements
+    from twisted.trial.itrial import IReporter
+    classImplements(TestCaseFunction, IReporter)
+    done.append(1)
diff --git a/tools/third_party/pytest/_pytest/warnings.py b/tools/third_party/pytest/_pytest/warnings.py
new file mode 100644
index 0000000..3c2b191
--- /dev/null
+++ b/tools/third_party/pytest/_pytest/warnings.py
@@ -0,0 +1,96 @@
+from __future__ import absolute_import, division, print_function
+
+import warnings
+from contextlib import contextmanager
+
+import pytest
+
+from _pytest import compat
+
+
+def _setoption(wmod, arg):
+    """
+    Copy of the warning._setoption function but does not escape arguments.
+    """
+    parts = arg.split(':')
+    if len(parts) > 5:
+        raise wmod._OptionError("too many fields (max 5): %r" % (arg,))
+    while len(parts) < 5:
+        parts.append('')
+    action, message, category, module, lineno = [s.strip()
+                                                 for s in parts]
+    action = wmod._getaction(action)
+    category = wmod._getcategory(category)
+    if lineno:
+        try:
+            lineno = int(lineno)
+            if lineno < 0:
+                raise ValueError
+        except (ValueError, OverflowError):
+            raise wmod._OptionError("invalid lineno %r" % (lineno,))
+    else:
+        lineno = 0
+    wmod.filterwarnings(action, message, category, module, lineno)
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("pytest-warnings")
+    group.addoption(
+        '-W', '--pythonwarnings', action='append',
+        help="set which warnings to report, see -W option of python itself.")
+    parser.addini("filterwarnings", type="linelist",
+                  help="Each line specifies a pattern for "
+                  "warnings.filterwarnings. "
+                  "Processed after -W and --pythonwarnings.")
+
+
+@contextmanager
+def catch_warnings_for_item(item):
+    """
+    catches the warnings generated during setup/call/teardown execution
+    of the given item and after it is done posts them as warnings to this
+    item.
+    """
+    args = item.config.getoption('pythonwarnings') or []
+    inifilters = item.config.getini("filterwarnings")
+    with warnings.catch_warnings(record=True) as log:
+        for arg in args:
+            warnings._setoption(arg)
+
+        for arg in inifilters:
+            _setoption(warnings, arg)
+
+        mark = item.get_marker('filterwarnings')
+        if mark:
+            for arg in mark.args:
+                warnings._setoption(arg)
+
+        yield
+
+        for warning in log:
+            warn_msg = warning.message
+            unicode_warning = False
+
+            if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
+                new_args = []
+                for m in warn_msg.args:
+                    new_args.append(compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m)
+                unicode_warning = list(warn_msg.args) != new_args
+                warn_msg.args = new_args
+
+            msg = warnings.formatwarning(
+                warn_msg, warning.category,
+                warning.filename, warning.lineno, warning.line)
+            item.warn("unused", msg)
+
+            if unicode_warning:
+                warnings.warn(
+                    "Warning is using unicode non convertible to ascii, "
+                    "converting to a safe representation:\n  %s" % msg,
+                    UnicodeWarning)
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_runtest_protocol(item):
+    with catch_warnings_for_item(item):
+        yield
diff --git a/tools/third_party/pytest/appveyor.yml b/tools/third_party/pytest/appveyor.yml
new file mode 100644
index 0000000..4f4afe1
--- /dev/null
+++ b/tools/third_party/pytest/appveyor.yml
@@ -0,0 +1,44 @@
+environment:
+  COVERALLS_REPO_TOKEN:
+    secure: 2NJ5Ct55cHJ9WEg3xbSqCuv0rdgzzb6pnzOIG5OkMbTndw3wOBrXntWFoQrXiMFi
+    # this is pytest's token in coveralls.io, encrypted
+    # using pytestbot account as detailed here:
+    # https://www.appveyor.com/docs/build-configuration#secure-variables
+
+  matrix:
+  # coveralls is not in the default env list
+  - TOXENV: "coveralls"
+  # note: please use "tox --listenvs" to populate the build matrix below
+  - TOXENV: "linting"
+  - TOXENV: "py27"
+  - TOXENV: "py34"
+  - TOXENV: "py35"
+  - TOXENV: "py36"
+  - TOXENV: "pypy"
+  - TOXENV: "py27-pexpect"
+  - TOXENV: "py27-xdist"
+  - TOXENV: "py27-trial"
+  - TOXENV: "py27-numpy"
+  - TOXENV: "py27-pluggymaster"
+  - TOXENV: "py36-pexpect"
+  - TOXENV: "py36-xdist"
+  - TOXENV: "py36-trial"
+  - TOXENV: "py36-numpy"
+  - TOXENV: "py36-pluggymaster"
+  - TOXENV: "py27-nobyte"
+  - TOXENV: "doctesting"
+  - TOXENV: "py35-freeze"
+  - TOXENV: "docs"
+
+install:
+  - echo Installed Pythons
+  - dir c:\Python*
+
+  - if "%TOXENV%" == "pypy" call scripts\install-pypy.bat
+
+  - C:\Python36\python -m pip install --upgrade --pre tox
+
+build: false  # Not a C# project, build stuff at the test step instead.
+
+test_script:
+  - call scripts\call-tox.bat
diff --git a/tools/pytest/bench/bench.py b/tools/third_party/pytest/bench/bench.py
similarity index 100%
rename from tools/pytest/bench/bench.py
rename to tools/third_party/pytest/bench/bench.py
diff --git a/tools/pytest/bench/bench_argcomplete.py b/tools/third_party/pytest/bench/bench_argcomplete.py
similarity index 100%
rename from tools/pytest/bench/bench_argcomplete.py
rename to tools/third_party/pytest/bench/bench_argcomplete.py
diff --git a/tools/pytest/bench/empty.py b/tools/third_party/pytest/bench/empty.py
similarity index 100%
rename from tools/pytest/bench/empty.py
rename to tools/third_party/pytest/bench/empty.py
diff --git a/tools/pytest/bench/manyparam.py b/tools/third_party/pytest/bench/manyparam.py
similarity index 100%
rename from tools/pytest/bench/manyparam.py
rename to tools/third_party/pytest/bench/manyparam.py
diff --git a/tools/pytest/bench/skip.py b/tools/third_party/pytest/bench/skip.py
similarity index 100%
rename from tools/pytest/bench/skip.py
rename to tools/third_party/pytest/bench/skip.py
diff --git a/tools/third_party/pytest/changelog/2920.bugfix b/tools/third_party/pytest/changelog/2920.bugfix
new file mode 100644
index 0000000..9c52172
--- /dev/null
+++ b/tools/third_party/pytest/changelog/2920.bugfix
@@ -0,0 +1 @@
+Fix issue about ``-p no:<plugin>`` having no effect.
diff --git a/tools/third_party/pytest/changelog/2949.trivial b/tools/third_party/pytest/changelog/2949.trivial
new file mode 100644
index 0000000..39789e7
--- /dev/null
+++ b/tools/third_party/pytest/changelog/2949.trivial
@@ -0,0 +1 @@
+Update github "bugs" link in CONTRIBUTING.rst
diff --git a/tools/third_party/pytest/changelog/2956.bugfix b/tools/third_party/pytest/changelog/2956.bugfix
new file mode 100644
index 0000000..1371765
--- /dev/null
+++ b/tools/third_party/pytest/changelog/2956.bugfix
@@ -0,0 +1 @@
+Fix regression with warnings that contained non-strings in their arguments in Python 2.
diff --git a/tools/third_party/pytest/changelog/2957.bugfix b/tools/third_party/pytest/changelog/2957.bugfix
new file mode 100644
index 0000000..589665b
--- /dev/null
+++ b/tools/third_party/pytest/changelog/2957.bugfix
@@ -0,0 +1 @@
+Always escape null bytes when setting ``PYTEST_CURRENT_TEST``.
diff --git a/tools/third_party/pytest/changelog/2963.doc b/tools/third_party/pytest/changelog/2963.doc
new file mode 100644
index 0000000..c9a1d66
--- /dev/null
+++ b/tools/third_party/pytest/changelog/2963.doc
@@ -0,0 +1 @@
+Fix broken link to plugin pytest-localserver.
diff --git a/tools/third_party/pytest/changelog/2971.bugfix b/tools/third_party/pytest/changelog/2971.bugfix
new file mode 100644
index 0000000..36684e8
--- /dev/null
+++ b/tools/third_party/pytest/changelog/2971.bugfix
@@ -0,0 +1 @@
+Fix ``ZeroDivisionError`` when using the ``testmon`` plugin when no tests were actually collected.
diff --git a/tools/third_party/pytest/changelog/2984.bugfix b/tools/third_party/pytest/changelog/2984.bugfix
new file mode 100644
index 0000000..21f5748
--- /dev/null
+++ b/tools/third_party/pytest/changelog/2984.bugfix
@@ -0,0 +1 @@
+Bring back ``TerminalReporter.writer`` as an alias to ``TerminalReporter._tw``. This alias was removed by accident in the ``3.3.0`` release.
diff --git a/tools/third_party/pytest/changelog/_template.rst b/tools/third_party/pytest/changelog/_template.rst
new file mode 100644
index 0000000..a898abc
--- /dev/null
+++ b/tools/third_party/pytest/changelog/_template.rst
@@ -0,0 +1,40 @@
+{% for section in sections %}
+{% set underline = "-" %}
+{% if section %}
+{{section}}
+{{ underline * section|length }}{% set underline = "~" %}
+
+{% endif %}
+{% if sections[section] %}
+{% for category, val in definitions.items() if category in sections[section] %}
+
+{{ definitions[category]['name'] }}
+{{ underline * definitions[category]['name']|length }}
+
+{% if definitions[category]['showcontent'] %}
+{% for text, values in sections[section][category]|dictsort(by='value') %}
+{% set issue_joiner = joiner(', ') %}
+- {{ text }}{% if category != 'vendor' %} ({% for value in values|sort %}{{ issue_joiner() }}`{{ value }} <https://github.com/pytest-dev/pytest/issues/{{ value[1:] }}>`_{% endfor %}){% endif %}
+
+
+{% endfor %}
+{% else %}
+- {{ sections[section][category]['']|sort|join(', ') }}
+
+
+{% endif %}
+{% if sections[section][category]|length == 0 %}
+
+No significant changes.
+
+
+{% else %}
+{% endif %}
+{% endfor %}
+{% else %}
+
+No significant changes.
+
+
+{% endif %}
+{% endfor %}
diff --git a/tools/third_party/pytest/doc/en/Makefile b/tools/third_party/pytest/doc/en/Makefile
new file mode 100644
index 0000000..fa8e826
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/Makefile
@@ -0,0 +1,150 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+REGENDOC_ARGS := \
+	--normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \
+	--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
+	--normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \
+	--normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \
+	--normalize "@py-(\d+)\\.[^ ,]+@py-\1.x.y@" \
+	--normalize "@pluggy-(\d+)\\.[.\d,]+@pluggy-\1.x.y@" \
+	--normalize "@hypothesis-(\d+)\\.[.\d,]+@hypothesis-\1.x.y@" \
+	--normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@"
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  showtarget to show the pytest.org target directory"
+	@echo "  install    to install docs to pytest.org/SITETARGET"
+	@echo "  install-ldf to install the doc pdf to pytest.org/SITETARGET"
+	@echo "  regen      to regenerate pytest examples using the installed pytest"
+	@echo "  linkcheck  to check all external links for integrity"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+regen:
+	PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytest.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/pytest"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	make -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+texinfo:
+	mkdir -p $(BUILDDIR)/texinfo
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	mkdir -p $(BUILDDIR)/texinfo
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
diff --git a/tools/third_party/pytest/doc/en/_templates/globaltoc.html b/tools/third_party/pytest/doc/en/_templates/globaltoc.html
new file mode 100644
index 0000000..fdd4dd5
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/_templates/globaltoc.html
@@ -0,0 +1,19 @@
+<h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
+
+<ul>
+  <li><a href="{{ pathto('index') }}">Home</a></li>
+  <li><a href="{{ pathto('contents') }}">Contents</a></li>
+  <li><a href="{{ pathto('getting-started') }}">Install</a></li>
+  <li><a href="{{ pathto('example/index') }}">Examples</a></li>
+  <li><a href="{{ pathto('customize') }}">Customize</a></li>
+  <li><a href="{{ pathto('contact') }}">Contact</a></li>
+  <li><a href="{{ pathto('talks') }}">Talks/Posts</a></li>
+  <li><a href="{{ pathto('changelog') }}">Changelog</a></li>
+  <li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
+  <li><a href="{{ pathto('license') }}">License</a></li>
+</ul>
+
+{%- if display_toc %}
+  <hr>
+  {{ toc }}
+{%- endif %}
diff --git a/tools/third_party/pytest/doc/en/_templates/layout.html b/tools/third_party/pytest/doc/en/_templates/layout.html
new file mode 100644
index 0000000..2fc8e2a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/_templates/layout.html
@@ -0,0 +1,20 @@
+{% extends "!layout.html" %}
+{% block header %}
+  {{super()}}
+{% endblock %}
+{% block footer %}
+{{ super() }}
+<script type="text/javascript">
+
+  var _gaq = _gaq || [];
+  _gaq.push(['_setAccount', 'UA-7597274-13']);
+  _gaq.push(['_trackPageview']);
+
+  (function() {
+    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+  })();
+
+</script>
+{% endblock %}
diff --git a/tools/third_party/pytest/doc/en/_templates/links.html b/tools/third_party/pytest/doc/en/_templates/links.html
new file mode 100644
index 0000000..d855a01
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/_templates/links.html
@@ -0,0 +1,11 @@
+<h3>Useful Links</h3>
+<ul>
+  <li><a href="{{ pathto('index') }}">The pytest Website</a></li>
+  <li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li>
+  <li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li>
+  <li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
+  <li><a href="http://plugincompat.herokuapp.com/">3rd party plugins</a></li>
+  <li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
+  <li><a href="https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf">PDF Documentation</a>
+</ul>
+
diff --git a/tools/pytest/doc/en/_templates/sidebarintro.html b/tools/third_party/pytest/doc/en/_templates/sidebarintro.html
similarity index 100%
rename from tools/pytest/doc/en/_templates/sidebarintro.html
rename to tools/third_party/pytest/doc/en/_templates/sidebarintro.html
diff --git a/tools/pytest/doc/en/_themes/.gitignore b/tools/third_party/pytest/doc/en/_themes/.gitignore
similarity index 100%
rename from tools/pytest/doc/en/_themes/.gitignore
rename to tools/third_party/pytest/doc/en/_themes/.gitignore
diff --git a/tools/pytest/doc/en/_themes/LICENSE b/tools/third_party/pytest/doc/en/_themes/LICENSE
similarity index 100%
rename from tools/pytest/doc/en/_themes/LICENSE
rename to tools/third_party/pytest/doc/en/_themes/LICENSE
diff --git a/tools/pytest/doc/en/_themes/README b/tools/third_party/pytest/doc/en/_themes/README
similarity index 100%
rename from tools/pytest/doc/en/_themes/README
rename to tools/third_party/pytest/doc/en/_themes/README
diff --git a/tools/pytest/doc/en/_themes/flask/layout.html b/tools/third_party/pytest/doc/en/_themes/flask/layout.html
similarity index 100%
rename from tools/pytest/doc/en/_themes/flask/layout.html
rename to tools/third_party/pytest/doc/en/_themes/flask/layout.html
diff --git a/tools/pytest/doc/en/_themes/flask/relations.html b/tools/third_party/pytest/doc/en/_themes/flask/relations.html
similarity index 100%
rename from tools/pytest/doc/en/_themes/flask/relations.html
rename to tools/third_party/pytest/doc/en/_themes/flask/relations.html
diff --git a/tools/pytest/doc/en/_themes/flask/static/flasky.css_t b/tools/third_party/pytest/doc/en/_themes/flask/static/flasky.css_t
similarity index 100%
rename from tools/pytest/doc/en/_themes/flask/static/flasky.css_t
rename to tools/third_party/pytest/doc/en/_themes/flask/static/flasky.css_t
diff --git a/tools/pytest/doc/en/_themes/flask/theme.conf b/tools/third_party/pytest/doc/en/_themes/flask/theme.conf
similarity index 100%
rename from tools/pytest/doc/en/_themes/flask/theme.conf
rename to tools/third_party/pytest/doc/en/_themes/flask/theme.conf
diff --git a/tools/pytest/doc/en/_themes/flask_theme_support.py b/tools/third_party/pytest/doc/en/_themes/flask_theme_support.py
similarity index 100%
rename from tools/pytest/doc/en/_themes/flask_theme_support.py
rename to tools/third_party/pytest/doc/en/_themes/flask_theme_support.py
diff --git a/tools/third_party/pytest/doc/en/adopt.rst b/tools/third_party/pytest/doc/en/adopt.rst
new file mode 100644
index 0000000..710f431
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/adopt.rst
@@ -0,0 +1,82 @@
+:orphan:
+
+.. warnings about this file not being included in any toctree will be suppressed by :orphan:
+
+
+April 2015 is "adopt pytest month"
+=============================================
+
+Are you an enthusiastic pytest user, the local testing guru in your workplace? Or are you considering using pytest for your open source project, but not sure how to get started? Then you may be interested in "adopt pytest month"!
+
+We will pair experienced pytest users with open source projects, for a month's effort of getting new development teams started with pytest.
+
+In 2015 we are trying this for the first time. In February and March 2015 we will gather volunteers on both sides, in April we will do the work, and in May we will evaluate how it went. This effort is being coordinated by Brianna Laugher. If you have any questions or comments, you can raise them on the `@pytestdotorg twitter account <https://twitter.com/pytestdotorg>`_ the `issue tracker`_ or the `pytest-dev mailing list`_.
+
+
+.. _`issue tracker`: https://github.com/pytest-dev/pytest/issues/676
+.. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev
+
+
+The ideal pytest helper
+-----------------------------------------
+
+ - will be able to commit 2-4 hours a week to working with their particular project (this might involve joining their mailing list, installing the software and exploring any existing tests, offering advice, writing some example tests)
+ - feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents)
+ - does not need to be an expert in every aspect!
+
+`Pytest helpers, sign up here`_! (preferably in February, hard deadline 22 March)
+
+
+.. _`Pytest helpers, sign up here`: http://goo.gl/forms/nxqAhqWt1P
+
+
+The ideal partner project
+-----------------------------------------
+
+ - is open source, and predominantly written in Python
+ - has an automated/documented install process for developers
+ - has more than one core developer
+ - has at least one official release (e.g. is available on pypi)
+ - has the support of the core development team, in trying out pytest adoption
+ - has no tests... or 100% test coverage... or somewhere in between!
+
+`Partner projects, sign up here`_! (by 22 March)
+
+
+.. _`Partner projects, sign up here`:  http://goo.gl/forms/ZGyqlHiwk3
+
+
+What does it mean to "adopt pytest"?
+-----------------------------------------
+
+There can be many different definitions of "success". Pytest can run many `nose and unittest`_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
+
+Progressive success might look like:
+
+ - tests can be run (by pytest) without errors (there may be failures)
+ - tests can be run (by pytest) without failures
+ - test runner is integrated into CI server
+ - existing tests are rewritten to take advantage of pytest features - this can happen in several iterations, for example:
+    - changing to native assert_ statements (pycmd_ has a script to help with that, ``pyconvert_unittest.py``)
+    - changing `setUp/tearDown methods`_ to fixtures_
+    - adding markers_
+    - other changes to reduce boilerplate
+ - assess needs for future tests to be written, e.g. new fixtures, distributed_ testing tweaks
+
+"Success" should also include that the development team feels comfortable with their knowledge of how to use pytest. In fact this is probably more important than anything else. So spending a lot of time on communication, giving examples, etc will probably be important - both in running the tests, and in writing them.
+
+It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
+
+.. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest
+.. _assert: asserts.html
+.. _pycmd: https://bitbucket.org/hpk42/pycmd/overview
+.. _`setUp/tearDown methods`: xunit_setup.html
+.. _fixtures: fixture.html
+.. _markers: markers.html
+.. _distributed: xdist.html
+
+
+Other ways to help
+-----------------------------------------
+
+Promote! Do your favourite open source Python projects use pytest? If not, why not tell them about this page?
diff --git a/tools/third_party/pytest/doc/en/announce/index.rst b/tools/third_party/pytest/doc/en/announce/index.rst
new file mode 100644
index 0000000..1a5f376
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/index.rst
@@ -0,0 +1,68 @@
+
+Release announcements
+===========================================
+
+.. toctree::
+   :maxdepth: 2
+
+   
+   release-3.3.0
+   release-3.2.5
+   release-3.2.4
+   release-3.2.3
+   release-3.2.2
+   release-3.2.1
+   release-3.2.0
+   release-3.1.3
+   release-3.1.2
+   release-3.1.1
+   release-3.1.0
+   release-3.0.7
+   release-3.0.6
+   release-3.0.5
+   release-3.0.4
+   release-3.0.3
+   release-3.0.2
+   release-3.0.1
+   release-3.0.0
+   sprint2016
+   release-2.9.2
+   release-2.9.1
+   release-2.9.0
+   release-2.8.7
+   release-2.8.6
+   release-2.8.5
+   release-2.8.4
+   release-2.8.3
+   release-2.8.2
+   release-2.7.2
+   release-2.7.1
+   release-2.7.0
+   release-2.6.3
+   release-2.6.2
+   release-2.6.1
+   release-2.6.0
+   release-2.5.2
+   release-2.5.1
+   release-2.5.0
+   release-2.4.2
+   release-2.4.1
+   release-2.4.0
+   release-2.3.5
+   release-2.3.4
+   release-2.3.3
+   release-2.3.2
+   release-2.3.1
+   release-2.3.0
+   release-2.2.4
+   release-2.2.2
+   release-2.2.1
+   release-2.2.0
+   release-2.1.3
+   release-2.1.2
+   release-2.1.1
+   release-2.1.0
+   release-2.0.3
+   release-2.0.2
+   release-2.0.1
+   release-2.0.0
diff --git a/tools/pytest/doc/en/announce/release-2.0.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.0.0.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.0.0.rst
diff --git a/tools/pytest/doc/en/announce/release-2.0.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.0.1.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.0.1.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.0.1.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.0.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.0.2.rst
new file mode 100644
index 0000000..f1f44f3
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.0.2.rst
@@ -0,0 +1,73 @@
+py.test 2.0.2: bug fixes, improved xfail/skip expressions, speed ups
+===========================================================================
+
+Welcome to pytest-2.0.2, a maintenance and bug fix release of pytest,
+a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
+and latest PyPy interpreters.  See the extensive docs with tested examples here:
+
+    http://pytest.org/
+
+If you want to install or upgrade pytest, just type one of::
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+Many thanks to all issue reporters and people asking questions
+or complaining, particularly Jurko for his insistence,
+Laura, Victor and Brianna for helping with improving
+and Ronny for his general advise.
+
+best,
+holger krekel
+
+Changes between 2.0.1 and 2.0.2
+----------------------------------------------
+
+- tackle issue32 - speed up test runs of very quick test functions
+  by reducing the relative overhead
+
+- fix issue30 - extended xfail/skipif handling and improved reporting.
+  If you have a syntax error in your skip/xfail
+  expressions you now get nice error reports.
+
+  Also you can now access module globals from xfail/skipif
+  expressions so that this for example works now::
+
+    import pytest
+    import mymodule
+    @pytest.mark.skipif("mymodule.__version__[0] == "1")
+    def test_function():
+        pass
+
+  This will not run the test function if the module's version string
+  does not start with a "1".  Note that specifying a string instead
+  of a boolean expressions allows py.test to report meaningful information
+  when summarizing a test run as to what conditions lead to skipping
+  (or xfail-ing) tests.
+
+- fix issue28 - setup_method and pytest_generate_tests work together
+  The setup_method fixture method now gets called also for
+  test function invocations generated from the pytest_generate_tests
+  hook.
+
+- fix issue27 - collectonly and keyword-selection (-k) now work together
+  Also, if you do "py.test --collectonly -q" you now get a flat list
+  of test ids that you can use to paste to the py.test commandline
+  in order to execute a particular test.
+
+- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
+
+- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
+  Starting with Python3.2 os.symlink may be supported. By requiring
+  a newer py lib version the py.path.local() implementation acknowledges
+  this.
+
+- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
+  thanks to Laura Creighton who also reviewed parts of the documentation.
+
+- fix slightly wrong output of verbose progress reporting for classes
+  (thanks Amaury)
+
+- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
+
+- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.0.3.rst b/tools/third_party/pytest/doc/en/announce/release-2.0.3.rst
new file mode 100644
index 0000000..9bbfdaa
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.0.3.rst
@@ -0,0 +1,40 @@
+py.test 2.0.3: bug fixes and speed ups 
+===========================================================================
+
+Welcome to pytest-2.0.3, a maintenance and bug fix release of pytest,
+a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
+and latest PyPy interpreters.  See the extensive docs with tested examples here:
+
+    http://pytest.org/
+
+If you want to install or upgrade pytest, just type one of::
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+There also is a bugfix release 1.6 of pytest-xdist, the plugin
+that enables seamless distributed and "looponfail" testing for Python.
+
+best,
+holger krekel
+
+Changes between 2.0.2 and 2.0.3
+----------------------------------------------
+
+- fix issue38: nicer tracebacks on calls to hooks, particularly early
+  configure/sessionstart ones
+
+- fix missing skip reason/meta information in junitxml files, reported
+  via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html
+
+- fix issue34: avoid collection failure with "test" prefixed classes
+  deriving from object.
+
+- don't require zlib (and other libs) for genscript plugin without
+  --genscript actually being used.
+
+- speed up skips (by not doing a full traceback representation
+  internally)
+
+- fix issue37: avoid invalid characters in junitxml's output
+
diff --git a/tools/pytest/doc/en/announce/release-2.1.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.1.0.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.1.0.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.1.0.rst
diff --git a/tools/pytest/doc/en/announce/release-2.1.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.1.1.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.1.1.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.1.1.rst
diff --git a/tools/pytest/doc/en/announce/release-2.1.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.1.2.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.1.2.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.1.2.rst
diff --git a/tools/pytest/doc/en/announce/release-2.1.3.rst b/tools/third_party/pytest/doc/en/announce/release-2.1.3.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.1.3.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.1.3.rst
diff --git a/tools/pytest/doc/en/announce/release-2.2.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.2.0.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.2.0.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.2.0.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.2.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.2.1.rst
new file mode 100644
index 0000000..5d28bcb
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.2.1.rst
@@ -0,0 +1,41 @@
+pytest-2.2.1: bug fixes, perfect teardowns
+===========================================================================
+
+
+pytest-2.2.1 is a minor backward-compatible release of the py.test
+testing tool.   It contains bug fixes and little improvements, including
+documentation fixes.  If you are using the distributed testing
+pluginmake sure to upgrade it to pytest-xdist-1.8.
+
+For general information see here:
+
+     http://pytest.org/
+
+To install or upgrade pytest:
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+Special thanks for helping on this release to Ronny Pfannschmidt, Jurko
+Gospodnetic and Ralf Schmitt.
+
+best,
+holger krekel
+
+
+Changes between 2.2.0 and 2.2.1
+----------------------------------------
+
+- fix issue99 (in pytest and py) internallerrors with resultlog now
+  produce better output - fixed by normalizing pytest_internalerror 
+  input arguments.
+- fix issue97 / traceback issues (in pytest and py) improve traceback output
+  in conjunction with jinja2 and cython which hack tracebacks
+- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
+  the final test in a test node will now run its teardown directly
+  instead of waiting for the end of the session. Thanks Dave Hunt for
+  the good reporting and feedback.  The pytest_runtest_protocol as well
+  as the pytest_runtest_teardown hooks now have "nextitem" available 
+  which will be None indicating the end of the test run.
+- fix collection crash due to unknown-source collected items, thanks
+  to Ralf Schmitt (fixed by depending on a more recent pylib)
diff --git a/tools/pytest/doc/en/announce/release-2.2.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.2.2.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.2.2.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.2.2.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.2.4.rst b/tools/third_party/pytest/doc/en/announce/release-2.2.4.rst
new file mode 100644
index 0000000..67f0feb
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.2.4.rst
@@ -0,0 +1,39 @@
+pytest-2.2.4: bug fixes, better junitxml/unittest/python3 compat
+===========================================================================
+
+pytest-2.2.4 is a minor backward-compatible release of the versatile
+py.test testing tool.   It contains bug fixes and a few refinements
+to junitxml reporting, better unittest- and python3 compatibility.
+
+For general information see here:
+
+     http://pytest.org/
+
+To install or upgrade pytest:
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+Special thanks for helping on this release to Ronny Pfannschmidt
+and Benjamin Peterson and the contributors of issues.
+
+best,
+holger krekel
+
+Changes between 2.2.3 and 2.2.4
+-----------------------------------
+
+- fix error message for rewritten assertions involving the % operator
+- fix issue 126: correctly match all invalid xml characters for junitxml
+  binary escape
+- fix issue with unittest: now @unittest.expectedFailure markers should
+  be processed correctly (you can also use @pytest.mark markers)
+- document integration with the extended distribute/setuptools test commands
+- fix issue 140: properly get the real functions
+  of bound classmethods for setup/teardown_class
+- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
+- fix issue #143: call unconfigure/sessionfinish always when
+  configure/sessionstart where called
+- fix issue #144: better mangle test ids to junitxml classnames
+- upgrade distribute_setup.py to 0.6.27
+
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst
new file mode 100644
index 0000000..f863aad
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst
@@ -0,0 +1,134 @@
+pytest-2.3: improved fixtures / better unittest integration
+=============================================================================
+
+pytest-2.3 comes with many major improvements for fixture/funcarg management 
+and parametrized testing in Python.  It is now easier, more efficient and
+more predicatable to re-run the same tests with different fixture
+instances.  Also, you can directly declare the caching "scope" of
+fixtures so that dependent tests throughout your whole test suite can
+re-use database or other expensive fixture objects with ease.  Lastly,
+it's possible for fixture functions (formerly known as funcarg
+factories) to use other fixtures, allowing for a completely modular and
+re-useable fixture design. 
+
+For detailed info and tutorial-style examples, see:
+
+    http://pytest.org/latest/fixture.html
+
+Moreover, there is now support for using pytest fixtures/funcargs with
+unittest-style suites, see here for examples:
+
+    http://pytest.org/latest/unittest.html
+
+Besides, more unittest-test suites are now expected to "simply work"
+with pytest.
+
+All changes are backward compatible and you should be able to continue
+to run your test suites and 3rd party plugins that worked with
+pytest-2.2.4.
+
+If you are interested in the precise reasoning (including examples) of the 
+pytest-2.3 fixture evolution, please consult
+http://pytest.org/latest/funcarg_compare.html
+
+For general info on installation and getting started:
+
+    http://pytest.org/latest/getting-started.html
+
+Docs and PDF access as usual at:
+
+    http://pytest.org
+
+and more details for those already in the knowing of pytest can be found
+in the CHANGELOG below.
+
+Particular thanks for this release go to Floris Bruynooghe, Alex Okrushko
+Carl Meyer, Ronny Pfannschmidt, Benjamin Peterson and Alex Gaynor for helping 
+to get the new features right and well integrated.  Ronny and Floris
+also helped to fix a number of bugs and yet more people helped by
+providing bug reports.
+
+have fun,
+holger krekel
+
+
+Changes between 2.2.4 and 2.3.0
+-----------------------------------
+
+- fix issue202 - better automatic names for parametrized test functions
+- fix issue139 - introduce @pytest.fixture which allows direct scoping
+  and parametrization of funcarg factories.  Introduce new @pytest.setup
+  marker to allow the writing of setup functions which accept funcargs.
+- fix issue198 - conftest fixtures were not found on windows32 in some
+  circumstances with nested directory structures due to path manipulation issues
+- fix issue193 skip test functions with were parametrized with empty
+  parameter sets
+- fix python3.3 compat, mostly reporting bits that previously depended
+  on dict ordering
+- introduce re-ordering of tests by resource and parametrization setup
+  which takes precedence to the usual file-ordering
+- fix issue185 monkeypatching time.time does not cause pytest to fail
+- fix issue172 duplicate call of pytest.setup-decoratored setup_module
+  functions
+- fix junitxml=path construction so that if tests change the
+  current working directory and the path is a relative path
+  it is constructed correctly from the original current working dir.
+- fix "python setup.py test" example to cause a proper "errno" return
+- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
+- catch unicode-issues when writing failure representations
+  to terminal to prevent the whole session from crashing
+- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
+  will now take precedence before xfail-markers because we
+  can't determine xfail/xpass status in case of a skip. see also:
+  http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
+
+- always report installed 3rd party plugins in the header of a test run
+
+- fix issue160: a failing setup of an xfail-marked tests should
+  be reported as xfail (not xpass)
+
+- fix issue128: show captured output when capsys/capfd are used
+
+- fix issue179: properly show the dependency chain of factories
+
+- pluginmanager.register(...) now raises ValueError if the
+  plugin has been already registered or the name is taken
+
+- fix issue159: improve http://pytest.org/latest/faq.html 
+  especially with respect to the "magic" history, also mention
+  pytest-django, trial and unittest integration.
+
+- make request.keywords and node.keywords writable.  All descendant
+  collection nodes will see keyword values.  Keywords are dictionaries
+  containing markers and other info.
+
+- fix issue 178: xml binary escapes are now wrapped in py.xml.raw
+
+- fix issue 176: correctly catch the builtin AssertionError
+  even when we replaced AssertionError with a subclass on the
+  python level
+
+- factory discovery no longer fails with magic global callables
+  that provide no sane __code__ object (mock.call for example)
+
+- fix issue 182: testdir.inprocess_run now considers passed plugins
+
+- fix issue 188: ensure sys.exc_info is clear on python2
+                 before calling into a test
+
+- fix issue 191: add unittest TestCase runTest method support
+- fix issue 156: monkeypatch correctly handles class level descriptors
+
+- reporting refinements:
+
+  - pytest_report_header now receives a "startdir" so that
+    you can use startdir.bestrelpath(yourpath) to show
+    nice relative path
+
+  - allow plugins to implement both pytest_report_header and 
+    pytest_sessionstart (sessionstart is invoked first).
+
+  - don't show deselected reason line if there is none
+
+  - py.test -vv will show all of assert comparisons instead of truncating
+
diff --git a/tools/pytest/doc/en/announce/release-2.3.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.3.1.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.3.1.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.3.1.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.3.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.3.2.rst
new file mode 100644
index 0000000..75312b4
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.3.2.rst
@@ -0,0 +1,57 @@
+pytest-2.3.2: some fixes and more traceback-printing speed
+===========================================================================
+
+pytest-2.3.2 is another stabilization release:
+
+- issue 205: fixes a regression with conftest detection
+- issue 208/29: fixes traceback-printing speed in some bad cases
+- fix teardown-ordering for parametrized setups
+- fix unittest and trial compat behaviour with respect  to runTest() methods
+- issue 206 and others: some improvements to packaging
+- fix issue127 and others: improve some docs 
+
+See 
+
+     http://pytest.org/
+
+for general information.  To install or upgrade pytest:
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+best,
+holger krekel
+
+
+Changes between 2.3.1 and 2.3.2
+-----------------------------------
+
+- fix issue208 and fix issue29 use new py version to avoid long pauses 
+  when printing tracebacks in long modules
+
+- fix issue205 - conftests in subdirs customizing
+  pytest_pycollect_makemodule and pytest_pycollect_makeitem
+  now work properly
+
+- fix teardown-ordering for parametrized setups
+
+- fix issue127 - better documentation for pytest_addoption
+  and related objects.
+
+- fix unittest behaviour: TestCase.runtest only called if there are
+  test methods defined
+
+- improve trial support: don't collect its empty
+  unittest.TestCase.runTest() method
+
+- "python setup.py test" now works with pytest itself
+
+- fix/improve internal/packaging related bits:
+
+  - exception message check of test_nose.py now passes on python33 as well
+
+  - issue206 - fix test_assertrewrite.py to work when a global
+    PYTHONDONTWRITEBYTECODE=1 is present
+
+  - add tox.ini to pytest distribution so that ignore-dirs and others config
+    bits are properly distributed for maintainers who run pytest-own tests
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.3.3.rst b/tools/third_party/pytest/doc/en/announce/release-2.3.3.rst
new file mode 100644
index 0000000..3a48b6a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.3.3.rst
@@ -0,0 +1,62 @@
+pytest-2.3.3: integration fixes, py24 support, ``*/**`` shown in traceback
+===========================================================================
+
+pytest-2.3.3 is another stabilization release of the py.test tool
+which offers uebersimple assertions, scalable fixture mechanisms
+and deep customization for testing with Python.  Particularly,
+this release provides:
+
+- integration fixes and improvements related to flask, numpy, nose, 
+  unittest, mock
+
+- makes pytest work on py24 again (yes, people sometimes still need to use it)
+
+- show ``*,**`` args in pytest tracebacks
+
+Thanks to Manuel Jacob, Thomas Waldmann, Ronny Pfannschmidt, Pavel Repin
+and Andreas Taumoefolau for providing patches and all for the issues.
+
+See 
+
+     http://pytest.org/
+
+for general information.  To install or upgrade pytest:
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+best,
+holger krekel
+
+Changes between 2.3.2 and 2.3.3
+-----------------------------------
+
+- fix issue214 - parse modules that contain special objects like e. g.
+  flask's request object which blows up on getattr access if no request
+  is active. thanks Thomas Waldmann.
+
+- fix issue213 - allow to parametrize with values like numpy arrays that
+  do not support an __eq__ operator
+
+- fix issue215 - split test_python.org into multiple files
+
+- fix issue148 - @unittest.skip on classes is now recognized and avoids
+  calling setUpClass/tearDownClass, thanks Pavel Repin
+
+- fix issue209 - reintroduce python2.4 support by depending on newer
+  pylib which re-introduced statement-finding for pre-AST interpreters
+
+- nose support: only call setup if it's a callable, thanks Andrew
+  Taumoefolau
+
+- fix issue219 - add py2.4-3.3 classifiers to TROVE list
+
+- in tracebacks *,** arg values are now shown next to normal arguments
+  (thanks Manuel Jacob)
+
+- fix issue217 - support mock.patch with pytest's fixtures - note that
+  you need either mock-1.0.1 or the python3.3 builtin unittest.mock.
+
+- fix issue127 - improve documentation for pytest_addoption() and
+  add a ``config.getoption(name)`` helper function for consistency.
+
diff --git a/tools/pytest/doc/en/announce/release-2.3.4.rst b/tools/third_party/pytest/doc/en/announce/release-2.3.4.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.3.4.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.3.4.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst b/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst
new file mode 100644
index 0000000..112399e
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst
@@ -0,0 +1,97 @@
+pytest-2.3.5: bug fixes and little improvements
+===========================================================================
+
+pytest-2.3.5 is a maintenance release with many bug fixes and little
+improvements.  See the changelog below for details.  No backward
+compatibility issues are foreseen and all plugins which worked with the
+prior version are expected to work unmodified.   Speaking of which, a
+few interesting new plugins saw the light last month:
+
+- pytest-instafail: show failure information while tests are running
+- pytest-qt: testing of GUI applications written with QT/Pyside
+- pytest-xprocess: managing external processes across test runs
+- pytest-random: randomize test ordering
+
+And several others like pytest-django saw maintenance releases.
+For a more complete list, check out 
+https://pypi.python.org/pypi?%3Aaction=search&term=pytest&submit=search.
+
+For general information see:
+
+     http://pytest.org/
+
+To install or upgrade pytest:
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+Particular thanks to Floris, Ronny, Benjamin and the many bug reporters
+and fix providers.
+
+may the fixtures be with you,
+holger krekel
+
+
+Changes between 2.3.4 and 2.3.5
+-----------------------------------
+
+- never consider a fixture function for test function collection
+
+- allow re-running of test items / helps to fix pytest-reruntests plugin
+  and also help to keep less fixture/resource references alive
+
+- put captured stdout/stderr into junitxml output even for passing tests
+  (thanks Adam Goucher)
+
+- Issue 265 - integrate nose setup/teardown with setupstate
+  so it doesn't try to teardown if it did not setup
+
+- issue 271 - don't write junitxml on slave nodes
+
+- Issue 274 - don't try to show full doctest example
+  when doctest does not know the example location
+
+- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
+
+- inject "getfixture()" helper to retrieve fixtures from doctests,
+  thanks Andreas Zeidler
+
+- issue 259 - when assertion rewriting, be consistent with the default
+  source encoding of ASCII on Python 2
+
+- issue 251 - report a skip instead of ignoring classes with init
+
+- issue250 unicode/str mixes in parametrization names and values now works
+
+- issue257, assertion-triggered compilation of source ending in a
+  comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6)
+
+- fix --genscript option to generate standalone scripts that also
+  work with python3.3 (importer ordering)
+
+- issue171 - in assertion rewriting, show the repr of some
+  global variables
+
+- fix option help for "-k"
+
+- move long description of distribution into README.rst
+
+- improve docstring for metafunc.parametrize()
+
+- fix bug where using capsys with pytest.set_trace() in a test
+  function would break when looking at capsys.readouterr()
+
+- allow to specify prefixes starting with "_" when 
+  customizing python_functions test discovery. (thanks Graham Horler)
+
+- improve PYTEST_DEBUG tracing output by putting
+  extra data on a new lines with additional indent
+
+- ensure OutcomeExceptions like skip/fail have initialized exception attributes
+
+- issue 260 - don't use nose special setup on plain unittest cases
+
+- fix issue134 - print the collect errors that prevent running specified test items
+
+- fix issue266 - accept unicode in MarkEvaluator expressions
+
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst
new file mode 100644
index 0000000..be3aaed
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst
@@ -0,0 +1,225 @@
+pytest-2.4.0: new fixture features/hooks and bug fixes
+===========================================================================
+
+The just released pytest-2.4.0 brings many improvements and numerous 
+bug fixes while remaining plugin- and test-suite compatible apart
+from a few supposedly very minor incompatibilities.  See below for 
+a full list of details.  A few feature highlights:
+
+- new yield-style fixtures `pytest.yield_fixture
+  <http://pytest.org/latest/yieldfixture.html>`_, allowing to use
+  existing with-style context managers in fixture functions.
+
+- improved pdb support: ``import pdb ; pdb.set_trace()`` now works
+  without requiring prior disabling of stdout/stderr capturing.
+  Also the ``--pdb`` options works now on collection and internal errors
+  and we introduced a new experimental hook for IDEs/plugins to 
+  intercept debugging: ``pytest_exception_interact(node, call, report)``.
+
+- shorter monkeypatch variant to allow specifying an import path as
+  a target, for example: ``monkeypatch.setattr("requests.get", myfunc)``
+
+- better unittest/nose compatibility: all teardown methods are now only
+  called if the corresponding setup method succeeded.
+
+- integrate tab-completion on command line options if you
+  have `argcomplete <http://pypi.python.org/pypi/argcomplete>`_ 
+  configured.
+
+- allow boolean expression directly with skipif/xfail
+  if a "reason" is also specified.
+
+- a new hook ``pytest_load_initial_conftests`` allows plugins like
+  `pytest-django <http://pypi.python.org/pypi/pytest-django>`_ to
+  influence the environment before conftest files import ``django``.
+
+- reporting: color the last line red or green depending if
+  failures/errors occurred or everything passed.
+
+The documentation has been updated to accommodate the changes, 
+see `http://pytest.org <http://pytest.org>`_ 
+
+To install or upgrade pytest::
+
+    pip install -U pytest # or
+    easy_install -U pytest
+
+
+**Many thanks to all who helped, including Floris Bruynooghe, 
+Brianna Laugher, Andreas Pelme, Anthon van der Neut, Anatoly Bubenkoff, 
+Vladimir Keleshev, Mathieu Agopian, Ronny Pfannschmidt, Christian
+Theunert and many others.**
+
+may passing tests be with you,
+
+holger krekel
+
+Changes between 2.3.5 and 2.4
+-----------------------------------
+
+known incompatibilities:
+
+- if calling --genscript from python2.7 or above, you only get a
+  standalone script which works on python2.7 or above.  Use Python2.6
+  to also get a python2.5 compatible version.
+
+- all xunit-style teardown methods (nose-style, pytest-style,
+  unittest-style) will not be called if the corresponding setup method failed,
+  see issue322 below.
+
+- the pytest_plugin_unregister hook wasn't ever properly called
+  and there is no known implementation of the hook - so it got removed.
+
+- pytest.fixture-decorated functions cannot be generators (i.e. use
+  yield) anymore.  This change might be reversed in 2.4.1 if it causes
+  unforeseen real-life issues.  However, you can always write and return
+  an inner function/generator and change the fixture consumer to iterate
+  over the returned generator.  This change was done in lieu of the new
+  ``pytest.yield_fixture`` decorator, see below.
+
+new features:
+
+- experimentally introduce a new ``pytest.yield_fixture`` decorator
+  which accepts exactly the same parameters as pytest.fixture but
+  mandates a ``yield`` statement instead of a ``return statement`` from
+  fixture functions.  This allows direct integration with "with-style"
+  context managers in fixture functions and generally avoids registering
+  of finalization callbacks in favour of treating the "after-yield" as
+  teardown code.  Thanks Andreas Pelme, Vladimir Keleshev, Floris
+  Bruynooghe, Ronny Pfannschmidt and many others for discussions.
+
+- allow boolean expression directly with skipif/xfail
+  if a "reason" is also specified.  Rework skipping documentation
+  to recommend "condition as booleans" because it prevents surprises
+  when importing markers between modules.  Specifying conditions
+  as strings will remain fully supported.
+
+- reporting: color the last line red or green depending if
+  failures/errors occurred or everything passed.  thanks Christian
+  Theunert.
+
+- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no
+  "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut.
+
+- fix issue181: --pdb now also works on collect errors (and 
+  on internal errors) .  This was implemented by a slight internal 
+  refactoring and the introduction of a new hook 
+  ``pytest_exception_interact`` hook (see next item).
+
+- fix issue341: introduce new experimental hook for IDEs/terminals to 
+  intercept debugging: ``pytest_exception_interact(node, call, report)``.
+
+- new monkeypatch.setattr() variant to provide a shorter
+  invocation for patching out classes/functions from modules:
+
+     monkeypatch.setattr("requests.get", myfunc)
+
+  will replace the "get" function of the "requests" module with ``myfunc``.
+
+- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
+  Mathieu Agopian for the initial fix.  Also make all of pytest/nose
+  finalizer mimic the same generic behaviour: if a setupX exists and
+  fails, don't run teardownX.  This internally introduces a new method
+  "node.addfinalizer()" helper which can only be called during the setup
+  phase of a node.
+
+- simplify pytest.mark.parametrize() signature: allow to pass a
+  CSV-separated string to specify argnames.  For example: 
+  ``pytest.mark.parametrize("input,expected",  [(1,2), (2,3)])``
+  works as well as the previous:
+  ``pytest.mark.parametrize(("input", "expected"), ...)``.
+
+- add support for setUpModule/tearDownModule detection, thanks Brian Okken.
+
+- integrate tab-completion on options through use of "argcomplete".
+  Thanks Anthon van der Neut for the PR.
+
+- change option names to be hyphen-separated long options but keep the
+  old spelling backward compatible.  py.test -h will only show the
+  hyphenated version, for example "--collect-only" but "--collectonly"
+  will remain valid as well (for backward-compat reasons).  Many thanks to
+  Anthon van der Neut for the implementation and to Hynek Schlawack for
+  pushing us.
+
+- fix issue 308 - allow to mark/xfail/skip individual parameter sets
+  when parametrizing.  Thanks Brianna Laugher.
+
+- call new experimental pytest_load_initial_conftests hook to allow
+  3rd party plugins to do something before a conftest is loaded.
+
+Bug fixes:
+
+- fix issue358 - capturing options are now parsed more properly 
+  by using a new parser.parse_known_args method.
+
+- pytest now uses argparse instead of optparse (thanks Anthon) which 
+  means that "argparse" is added as a dependency if installing into python2.6
+  environments or below.
+
+- fix issue333: fix a case of bad unittest/pytest hook interaction.
+
+- PR27: correctly handle nose.SkipTest during collection.  Thanks
+  Antonio Cuni, Ronny Pfannschmidt.
+
+- fix issue355: junitxml puts name="pytest" attribute to testsuite tag.
+
+- fix issue336: autouse fixture in plugins should work again.
+
+- fix issue279: improve object comparisons on assertion failure
+  for standard datatypes and recognise collections.abc.  Thanks to
+  Brianna Laugher and Mathieu Agopian.
+
+- fix issue317: assertion rewriter support for the is_package method
+
+- fix issue335: document py.code.ExceptionInfo() object returned
+  from pytest.raises(), thanks Mathieu Agopian.
+
+- remove implicit distribute_setup support from setup.py.
+
+- fix issue305: ignore any problems when writing pyc files.
+
+- SO-17664702: call fixture finalizers even if the fixture function
+  partially failed (finalizers would not always be called before)
+
+- fix issue320 - fix class scope for fixtures when mixed with
+  module-level functions.  Thanks Anatloy Bubenkoff.
+
+- you can specify "-q" or "-qq" to get different levels of "quieter"
+  reporting (thanks Katarzyna Jachim)
+
+- fix issue300 - Fix order of conftest loading when starting py.test
+  in a subdirectory.
+
+- fix issue323 - sorting of many module-scoped arg parametrizations
+
+- make sessionfinish hooks execute with the same cwd-context as at
+  session start (helps fix plugin behaviour which write output files 
+  with relative path such as pytest-cov)
+
+- fix issue316 - properly reference collection hooks in docs
+
+- fix issue 306 - cleanup of -k/-m options to only match markers/test
+  names/keywords respectively.  Thanks Wouter van Ackooy.
+
+- improved doctest counting for doctests in python modules -- 
+  files without any doctest items will not show up anymore
+  and doctest examples are counted as separate test items.
+  thanks Danilo Bellini.
+
+- fix issue245 by depending on the released py-1.4.14
+  which fixes py.io.dupfile to work with files with no
+  mode. Thanks Jason R. Coombs.
+
+- fix junitxml generation when test output contains control characters,
+  addressing issue267, thanks Jaap Broekhuizen 
+
+- fix issue338: honor --tb style for setup/teardown errors as well.  Thanks Maho.
+
+- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin.
+
+- better parametrize error messages, thanks Brianna Laugher
+
+- pytest_terminal_summary(terminalreporter) hooks can now use
+  ".section(title)" and ".line(msg)" methods to print extra 
+  information at the end of a test run.
+
diff --git a/tools/pytest/doc/en/announce/release-2.4.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.4.1.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.4.1.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.4.1.rst
diff --git a/tools/pytest/doc/en/announce/release-2.4.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.4.2.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.4.2.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.4.2.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst
new file mode 100644
index 0000000..b04a825
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst
@@ -0,0 +1,175 @@
+pytest-2.5.0: now down to ZERO reported bugs!
+===========================================================================
+
+pytest-2.5.0 is a big fixing release, the result of two community bug
+fixing days plus numerous additional works from many people and
+reporters.  The release should be fully compatible to 2.4.2, existing
+plugins and test suites.  We aim at maintaining this level of ZERO reported 
+bugs because it's no fun if your testing tool has bugs, is it?  Under a
+condition, though: when submitting a bug report please provide
+clear information about the circumstances and a simple example which
+reproduces the problem.
+
+The issue tracker is of course not empty now.  We have many remaining
+"enhacement" issues which we'll hopefully can tackle in 2014 with your
+help.
+
+For those who use older Python versions, please note that pytest is not
+automatically tested on python2.5 due to virtualenv, setuptools and tox
+not supporting it anymore.  Manual verification shows that it mostly
+works fine but it's not going to be part of the automated release 
+process and thus likely to break in the future.
+
+As usual, current docs are at 
+
+    http://pytest.org 
+
+and you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Particular thanks for helping with this release go to Anatoly Bubenkoff,
+Floris Bruynooghe, Marc Abramowitz, Ralph Schmitt, Ronny Pfannschmidt,
+Donald Stufft, James Lan, Rob Dennis, Jason R. Coombs, Mathieu Agopian,
+Virgil Dupras, Bruno Oliveira, Alex Gaynor and others.
+
+have fun,
+holger krekel
+
+
+2.5.0
+-----------------------------------
+
+- dropped python2.5 from automated release testing of pytest itself 
+  which means it's probably going to break soon (but still works 
+  with this release we believe).
+
+- simplified and fixed implementation for calling finalizers when
+  parametrized fixtures or function arguments are involved.  finalization 
+  is now performed lazily at setup time instead of in the "teardown phase".
+  While this might sound odd at first, it helps to ensure that we are 
+  correctly handling setup/teardown even in complex code.  User-level code
+  should not be affected unless it's implementing the pytest_runtest_teardown
+  hook and expecting certain fixture instances are torn down within (very
+  unlikely and would have been unreliable anyway).
+
+- PR90: add --color=yes|no|auto option to force terminal coloring 
+  mode ("auto" is default).  Thanks Marc Abramowitz.
+
+- fix issue319 - correctly show unicode in assertion errors.  Many
+  thanks to Floris Bruynooghe for the complete PR.  Also means
+  we depend on py>=1.4.19 now.
+
+- fix issue396 - correctly sort and finalize class-scoped parametrized 
+  tests independently from number of methods on the class.  
+
+- refix issue323 in a better way -- parametrization should now never
+  cause Runtime Recursion errors because the underlying algorithm
+  for re-ordering tests per-scope/per-fixture is not recursive
+  anymore (it was tail-call recursive before which could lead
+  to problems for more than >966 non-function scoped parameters).
+
+- fix issue290 - there is preliminary support now for parametrizing
+  with repeated same values (sometimes useful to test if calling 
+  a second time works as with the first time).
+
+- close issue240 - document precisely how pytest module importing
+  works, discuss the two common test directory layouts, and how it 
+  interacts with PEP420-namespace packages.
+
+- fix issue246 fix finalizer order to be LIFO on independent fixtures
+  depending on a parametrized higher-than-function scoped fixture. 
+  (was quite some effort so please bear with the complexity of this sentence :)
+  Thanks Ralph Schmitt for the precise failure example.
+ 
+- fix issue244 by implementing special index for parameters to only use
+  indices for paramentrized test ids
+
+- fix issue287 by running all finalizers but saving the exception
+  from the first failing finalizer and re-raising it so teardown will
+  still have failed.  We reraise the first failing exception because
+  it might be the cause for other finalizers to fail.
+
+- fix ordering when mock.patch or other standard decorator-wrappings
+  are used with test methods.  This fixues issue346 and should
+  help with random "xdist" collection failures.  Thanks to
+  Ronny Pfannschmidt and Donald Stufft for helping to isolate it.
+
+- fix issue357 - special case "-k" expressions to allow for
+  filtering with simple strings that are not valid python expressions.
+  Examples: "-k 1.3" matches all tests parametrized with 1.3.
+  "-k None" filters all tests that have "None" in their name
+  and conversely "-k 'not None'". 
+  Previously these examples would raise syntax errors.
+  
+- fix issue384 by removing the trial support code
+  since the unittest compat enhancements allow
+  trial to handle it on its own
+
+- don't hide an ImportError when importing a plugin produces one.
+  fixes issue375.
+
+- fix issue275 - allow usefixtures and autouse fixtures 
+  for running doctest text files.
+
+- fix issue380 by making --resultlog only rely on longrepr instead
+  of the "reprcrash" attribute which only exists sometimes.
+
+- address issue122: allow @pytest.fixture(params=iterator) by exploding
+  into a list early on.
+
+- fix pexpect-3.0 compatibility for pytest's own tests.
+  (fixes issue386)
+
+- allow nested parametrize-value markers, thanks James Lan for the PR.
+
+- fix unicode handling with new monkeypatch.setattr(import_path, value)
+  API.  Thanks Rob Dennis.  Fixes issue371.
+
+- fix unicode handling with junitxml, fixes issue368.
+
+- In assertion rewriting mode on Python 2, fix the detection of coding
+  cookies. See issue #330.
+
+- make "--runxfail" turn imperative pytest.xfail calls into no ops
+  (it already did neutralize pytest.mark.xfail markers)
+
+- refine pytest / pkg_resources interactions: The AssertionRewritingHook
+  PEP302 compliant loader now registers itself with setuptools/pkg_resources 
+  properly so that the pkg_resources.resource_stream method works properly.
+  Fixes issue366.  Thanks for the investigations and full PR to Jason R. Coombs.
+
+- pytestconfig fixture is now session-scoped as it is the same object during the
+  whole test run.  Fixes issue370. 
+
+- avoid one surprising case of marker malfunction/confusion::
+  
+      @pytest.mark.some(lambda arg: ...)
+      def test_function():
+
+  would not work correctly because pytest assumes @pytest.mark.some 
+  gets a function to be decorated already.  We now at least detect if this 
+  arg is a lambda and thus the example will work.  Thanks Alex Gaynor
+  for bringing it up.
+
+- xfail a test on pypy that checks wrong encoding/ascii (pypy does
+  not error out). fixes issue385.
+
+- internally make varnames() deal with classes's __init__,
+  although it's not needed by pytest itself atm.  Also
+  fix caching.  Fixes issue376.
+
+- fix issue221 - handle importing of namespace-package with no 
+  __init__.py properly.
+
+- refactor internal FixtureRequest handling to avoid monkeypatching.
+  One of the positive user-facing effects is that the "request" object 
+  can now be used in closures.
+
+- fixed version comparison in pytest.importskip(modname, minverstring)
+
+- fix issue377 by clarifying in the nose-compat docs that pytest
+  does not duplicate the unittest-API into the "plain" namespace.
+
+- fix verbose reporting for @mock'd test functions
+
diff --git a/tools/pytest/doc/en/announce/release-2.5.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.5.1.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.5.1.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.5.1.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst
new file mode 100644
index 0000000..d5cfca2
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst
@@ -0,0 +1,64 @@
+pytest-2.5.2: fixes 
+===========================================================================
+
+pytest is a mature Python testing tool with more than a 1000 tests 
+against itself, passing on many different interpreters and platforms.  
+
+The 2.5.2 release fixes a few bugs with two maybe-bugs remaining and
+actively being worked on (and waiting for the bug reporter's input).
+We also have a new contribution guide thanks to Piotr Banaszkiewicz
+and others.
+
+See docs at:
+
+    http://pytest.org
+
+As usual, you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Thanks to the following people who contributed to this release:
+
+    Anatoly Bubenkov 
+    Ronny Pfannschmidt
+    Floris Bruynooghe
+    Bruno Oliveira 
+    Andreas Pelme 
+    Jurko Gospodnetić
+    Piotr Banaszkiewicz 
+    Simon Liedtke 
+    lakka 
+    Lukasz Balcerzak 
+    Philippe Muller 
+    Daniel Hahler 
+
+have fun,
+holger krekel
+
+2.5.2
+-----------------------------------
+
+- fix issue409 -- better interoperate with cx_freeze by not
+  trying to import from collections.abc which causes problems 
+  for py27/cx_freeze.  Thanks Wolfgang L. for reporting and tracking it down.
+
+- fixed docs and code to use "pytest" instead of "py.test" almost everywhere.
+  Thanks Jurko Gospodnetic for the complete PR.  
+
+- fix issue425: mention at end of "py.test -h" that --markers
+  and --fixtures work according to specified test path (or current dir)
+
+- fix issue413: exceptions with unicode attributes are now printed
+  correctly also on python2 and with pytest-xdist runs. (the fix
+  requires py-1.4.20)
+
+- copy, cleanup and integrate py.io capture
+  from pylib 1.4.20.dev2 (rev 13d9af95547e)
+  
+- address issue416: clarify docs as to conftest.py loading semantics
+
+- fix issue429: comparing byte strings with non-ascii chars in assert
+  expressions now work better.  Thanks Floris Bruynooghe.
+
+- make capfd/capsys.capture private, its unused and shouldn't be exposed
+
diff --git a/tools/pytest/doc/en/announce/release-2.6.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.6.0.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.6.0.rst
diff --git a/tools/pytest/doc/en/announce/release-2.6.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.6.1.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.6.1.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.6.1.rst
diff --git a/tools/pytest/doc/en/announce/release-2.6.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.6.2.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.6.2.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.6.2.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst b/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst
new file mode 100644
index 0000000..ee0d269
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst
@@ -0,0 +1,52 @@
+pytest-2.6.3: fixes and little improvements
+===========================================================================
+
+pytest is a mature Python testing tool with more than a 1100 tests
+against itself, passing on many different interpreters and platforms.
+This release is drop-in compatible to 2.5.2 and 2.6.X.
+See below for the changes and see docs at:
+
+    http://pytest.org
+
+As usual, you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Thanks to all who contributed, among them:
+
+    Floris Bruynooghe
+    Oleg Sinyavskiy
+    Uwe Schmitt
+    Charles Cloud
+    Wolfgang Schnerring
+
+have fun,
+holger krekel
+
+Changes 2.6.3
+======================
+
+- fix issue575: xunit-xml was reporting collection errors as failures
+  instead of errors, thanks Oleg Sinyavskiy.
+
+- fix issue582: fix setuptools example, thanks Laszlo Papp and Ronny
+  Pfannschmidt.
+
+- Fix infinite recursion bug when pickling capture.EncodedFile, thanks
+  Uwe Schmitt.
+
+- fix issue589: fix bad interaction with numpy and others when showing
+  exceptions.  Check for precise "maximum recursion depth exceed" exception
+  instead of presuming any RuntimeError is that one (implemented in py
+  dep).  Thanks Charles Cloud for analysing the issue.
+
+- fix conftest related fixture visibility issue: when running with a
+  CWD outside of a test package pytest would get fixture discovery wrong.
+  Thanks to Wolfgang Schnerring for figuring out a reproducible example.
+
+- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the
+  timeout when interactively entering pdb).  Thanks Wolfgang Schnerring.
+
+- check xfail/skip also with non-python function test items. Thanks
+  Floris Bruynooghe.
+
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst
new file mode 100644
index 0000000..4e317ff
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst
@@ -0,0 +1,101 @@
+pytest-2.7.0: fixes, features, speed improvements
+===========================================================================
+
+pytest is a mature Python testing tool with more than a 1100 tests
+against itself, passing on many different interpreters and platforms.
+This release is supposed to be drop-in compatible to 2.6.X.
+
+See below for the changes and see docs at:
+
+    http://pytest.org
+
+As usual, you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Thanks to all who contributed, among them:
+
+    Anatoly Bubenkoff
+    Floris Bruynooghe
+    Brianna Laugher
+    Eric Siegerman
+    Daniel Hahler
+    Charles Cloud
+    Tom Viner
+    Holger Peters
+    Ldiary Translations
+    almarklein
+
+have fun,
+holger krekel
+
+2.7.0 (compared to 2.6.4)
+-----------------------------
+
+- fix issue435: make reload() work when assert rewriting is active.
+  Thanks Daniel Hahler.
+
+- fix issue616: conftest.py files and their contained fixutres are now
+  properly considered for visibility, independently from the exact
+  current working directory and test arguments that are used.
+  Many thanks to Eric Siegerman and his PR235 which contains
+  systematic tests for conftest visibility and now passes.
+  This change also introduces the concept of a ``rootdir`` which
+  is printed as a new pytest header and documented in the pytest
+  customize web page.
+
+- change reporting of "diverted" tests, i.e. tests that are collected
+  in one file but actually come from another (e.g. when tests in a test class
+  come from a base class in a different file).  We now show the nodeid
+  and indicate via a postfix the other file.
+
+- add ability to set command line options by environment variable PYTEST_ADDOPTS.
+
+- added documentation on the new pytest-dev teams on bitbucket and
+  github.  See https://pytest.org/latest/contributing.html .
+  Thanks to Anatoly for pushing and initial work on this.
+
+- fix issue650: new option ``--docttest-ignore-import-errors`` which
+  will turn import errors in doctests into skips.  Thanks Charles Cloud
+  for the complete PR.
+
+- fix issue655: work around different ways that cause python2/3
+  to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
+
+- fix issue615: assertion rewriting did not correctly escape % signs
+  when formatting boolean operations, which tripped over mixing
+  booleans with modulo operators.  Thanks to Tom Viner for the report,
+  triaging and fix.
+
+- implement issue351: add ability to specify parametrize ids as a callable
+  to generate custom test ids.  Thanks Brianna Laugher for the idea and
+  implementation.
+
+- introduce and document new hookwrapper mechanism useful for plugins
+  which want to wrap the execution of certain hooks for their purposes.
+  This supersedes the undocumented ``__multicall__`` protocol which
+  pytest itself and some external plugins use.  Note that pytest-2.8
+  is scheduled to drop supporting the old ``__multicall__``
+  and only support the hookwrapper protocol.
+
+- majorly speed up invocation of plugin hooks
+
+- use hookwrapper mechanism in builtin pytest plugins.
+
+- add a doctest ini option for doctest flags, thanks Holger Peters.
+
+- add note to docs that if you want to mark a parameter and the
+  parameter is a callable, you also need to pass in a reason to disambiguate
+  it from the "decorator" case.  Thanks Tom Viner.
+
+- "python_classes" and "python_functions" options now support glob-patterns
+  for test discovery, as discussed in issue600. Thanks Ldiary Translations.
+
+- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
+
+- fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise).
+
+- On failure, the ``sys.last_value``, ``sys.last_type`` and
+  ``sys.last_traceback`` are set, so that a user can inspect the error
+  via postmortem debugging (almarklein).
+
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst
new file mode 100644
index 0000000..fdc71ee
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst
@@ -0,0 +1,58 @@
+pytest-2.7.1: bug fixes
+=======================
+
+pytest is a mature Python testing tool with more than a 1100 tests
+against itself, passing on many different interpreters and platforms.
+This release is supposed to be drop-in compatible to 2.7.0.
+
+See below for the changes and see docs at:
+
+    http://pytest.org
+
+As usual, you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+    Bruno Oliveira
+    Holger Krekel
+    Ionel Maries Cristian
+    Floris Bruynooghe
+
+Happy testing,
+The py.test Development Team
+
+
+2.7.1 (compared to 2.7.0)
+-------------------------
+
+- fix issue731: do not get confused by the braces which may be present
+  and unbalanced in an object's repr while collapsing False
+  explanations.  Thanks Carl Meyer for the report and test case.
+
+- fix issue553: properly handling inspect.getsourcelines failures in
+  FixtureLookupError which would lead to an internal error,
+  obfuscating the original problem. Thanks talljosh for initial
+  diagnose/patch and Bruno Oliveira for final patch.
+
+- fix issue660: properly report scope-mismatch-access errors
+  independently from ordering of fixture arguments.  Also
+  avoid the pytest internal traceback which does not provide
+  information to the user. Thanks Holger Krekel.
+
+- streamlined and documented release process.  Also all versions
+  (in setup.py and documentation generation) are now read
+  from _pytest/__init__.py. Thanks Holger Krekel.
+
+- fixed docs to remove the notion that yield-fixtures are experimental.
+  They are here to stay :)  Thanks Bruno Oliveira.
+
+- Support building wheels by using environment markers for the
+  requirements.  Thanks Ionel Maries Cristian.
+
+- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
+  when tests raised SystemExit. Thanks Holger Krekel.
+
+- reintroduced _pytest fixture of the pytester plugin which is used
+  at least by pytest-xdist.
diff --git a/tools/pytest/doc/en/announce/release-2.7.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.7.2.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.7.2.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.7.2.rst
diff --git a/tools/pytest/doc/en/announce/release-2.8.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.8.2.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.8.2.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.8.2.rst
diff --git a/tools/pytest/doc/en/announce/release-2.8.3.rst b/tools/third_party/pytest/doc/en/announce/release-2.8.3.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.8.3.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.8.3.rst
diff --git a/tools/pytest/doc/en/announce/release-2.8.4.rst b/tools/third_party/pytest/doc/en/announce/release-2.8.4.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.8.4.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.8.4.rst
diff --git a/tools/pytest/doc/en/announce/release-2.8.5.rst b/tools/third_party/pytest/doc/en/announce/release-2.8.5.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.8.5.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.8.5.rst
diff --git a/tools/pytest/doc/en/announce/release-2.8.6.rst b/tools/third_party/pytest/doc/en/announce/release-2.8.6.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.8.6.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.8.6.rst
diff --git a/tools/pytest/doc/en/announce/release-2.8.7.rst b/tools/third_party/pytest/doc/en/announce/release-2.8.7.rst
similarity index 100%
rename from tools/pytest/doc/en/announce/release-2.8.7.rst
rename to tools/third_party/pytest/doc/en/announce/release-2.8.7.rst
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst b/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst
new file mode 100644
index 0000000..011b1ff
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst
@@ -0,0 +1,159 @@
+pytest-2.9.0
+============
+
+pytest is a mature Python testing tool with more than a 1100 tests
+against itself, passing on many different interpreters and platforms.
+
+See below for the changes and see docs at:
+
+    http://pytest.org
+
+As usual, you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+    Anatoly Bubenkov                                                                                                                                                                                                         
+    Bruno Oliveira                                                                                                                                                                                                           
+    Buck Golemon                                                                                                                                                                                                             
+    David Vierra                                                                                                                                                                                                             
+    Florian Bruhin                                                                                                                                                                                                           
+    Galaczi Endre                                                                                                                                                                                                            
+    Georgy Dyuldin                                                                                                                                                                                                           
+    Lukas Bednar                                                                                                                                                                                                             
+    Luke Murphy                                                                                                                                                                                                              
+    Marcin Biernat                                                                                                                                                                                                           
+    Matt Williams                                                                                                                                                                                                            
+    Michael Aquilina                                                                                                                                                                                                         
+    Raphael Pierzina                                                                                                                                                                                                         
+    Ronny Pfannschmidt                                                                                                                                                                                                       
+    Ryan Wooden                                                                                                                                                                                                              
+    Tiemo Kieft                                                                                                                                                                                                              
+    TomV                                                                                                                                                                                                                     
+    holger krekel                                                                                                                                                                                                            
+    jab   
+
+
+Happy testing,
+The py.test Development Team
+
+
+2.9.0 (compared to 2.8.7)
+-------------------------
+
+**New Features**
+
+* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
+  Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_).
+
+* ``--doctest-glob`` may now be passed multiple times in the command-line.
+  Thanks `@jab`_ and `@nicoddemus`_ for the PR.
+
+* New ``-rp`` and ``-rP`` reporting options give the summary and full output
+  of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR.
+
+* ``pytest.mark.xfail`` now has a ``strict`` option which makes ``XPASS``
+  tests to fail the test suite, defaulting to ``False``. There's also a
+  ``xfail_strict`` ini option that can be used to configure it project-wise.
+  Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_).
+
+* ``Parser.addini`` now supports options of type ``bool``. Thanks
+  `@nicoddemus`_ for the PR.
+
+* New ``ALLOW_BYTES`` doctest option strips ``b`` prefixes from byte strings
+  in doctest output (similar to ``ALLOW_UNICODE``).
+  Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_).
+
+* give a hint on KeyboardInterrupt to use the --fulltrace option to show the errors,
+  this fixes `#1366`_.
+  Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR.
+
+* catch IndexError exceptions when getting exception source location. This fixes
+  pytest internal error for dynamically generated code (fixtures and tests)
+  where source lines are fake by intention
+
+**Changes**
+
+* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been
+  merged into the ``pytest`` repository as ``pytest._code``. This decision 
+  was made because ``py.code`` had very few uses outside ``pytest`` and the 
+  fact that it was in a different repository made it difficult to fix bugs on 
+  its code in a timely manner. The team hopes with this to be able to better
+  refactor out and improve that code.
+  This change shouldn't affect users, but it is useful to let users aware
+  if they encounter any strange behavior.
+  
+  Keep in mind that the code for ``pytest._code`` is **private** and 
+  **experimental**, so you definitely should not import it explicitly!
+
+  Please note that the original ``py.code`` is still available in 
+  `pylib <https://pylib.readthedocs.io>`_.
+
+* ``pytest_enter_pdb`` now optionally receives the pytest config object.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Removed code and documentation for Python 2.5 or lower versions,
+  including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
+  Thanks `@nicoddemus`_ for the PR (`#1226`_).
+
+* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
+  found in the environment, even when -vv isn't used.
+  Thanks `@The-Compiler`_ for the PR.
+
+* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and
+  ``--failed-first`` respectively.
+  Thanks `@MichaelAquilina`_ for the PR.
+
+* Added expected exceptions to pytest.raises fail message
+
+* Collection only displays progress ("collecting X items") when in a terminal.
+  This avoids cluttering the output when using ``--color=yes`` to obtain
+  colors in CI integrations systems (`#1397`_).
+
+**Bug Fixes**
+
+* The ``-s`` and ``-c`` options should now work under ``xdist``;
+  ``Config.fromdictargs`` now represents its input much more faithfully.
+  Thanks to `@bukzor`_ for the complete PR (`#680`_).
+
+* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting.
+  Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR.
+
+* Fix formatting utf-8 explanation messages (`#1379`_).
+  Thanks `@biern`_ for the PR.
+
+* Fix `traceback style docs`_ to describe all of the available options
+  (auto/long/short/line/native/no), with `auto` being the default since v2.6.
+  Thanks `@hackebrot`_ for the PR.
+
+* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
+  with same name.
+  
+  
+.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
+
+.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
+.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
+.. _#1366: https://github.com/pytest-dev/pytest/issues/1366
+.. _#1040: https://github.com/pytest-dev/pytest/pull/1040
+.. _#680: https://github.com/pytest-dev/pytest/issues/680
+.. _#1287: https://github.com/pytest-dev/pytest/pull/1287
+.. _#1226: https://github.com/pytest-dev/pytest/pull/1226
+.. _#1290: https://github.com/pytest-dev/pytest/pull/1290
+.. _#1355: https://github.com/pytest-dev/pytest/pull/1355
+.. _#1397: https://github.com/pytest-dev/pytest/issues/1397
+.. _@biern: https://github.com/biern
+.. _@MichaelAquilina: https://github.com/MichaelAquilina
+.. _@bukzor: https://github.com/bukzor
+.. _@hpk42: https://github.com/hpk42
+.. _@nicoddemus: https://github.com/nicoddemus
+.. _@jab: https://github.com/jab
+.. _@codewarrior0: https://github.com/codewarrior0
+.. _@jaraco: https://github.com/jaraco
+.. _@The-Compiler: https://github.com/The-Compiler
+.. _@Shinkenjoe: https://github.com/Shinkenjoe
+.. _@tomviner: https://github.com/tomviner
+.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
+.. _@rabbbit: https://github.com/rabbbit
+.. _@hackebrot: https://github.com/hackebrot
\ No newline at end of file
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst b/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst
new file mode 100644
index 0000000..3277da1
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst
@@ -0,0 +1,67 @@
+pytest-2.9.1
+============
+
+pytest is a mature Python testing tool with more than a 1100 tests
+against itself, passing on many different interpreters and platforms.
+
+See below for the changes and see docs at:
+
+    http://pytest.org
+
+As usual, you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+    Bruno Oliveira                                                                                                                                                                                                                            
+    Daniel Hahler                                                                                                                                                                                                                             
+    Dmitry Malinovsky                                                                                                                                                                                                                         
+    Florian Bruhin                                                                                                                                                                                                                            
+    Floris Bruynooghe                                                                                                                                                                                                                         
+    Matt Bachmann                                                                                                                                                                                                                             
+    Ronny Pfannschmidt                                                                                                                                                                                                                        
+    TomV                                                                                                                                                                                                                                      
+    Vladimir Bolshakov                                                                                                                                                                                                                        
+    Zearin                                                                                                                                                                                                                                     
+    palaviv   
+
+
+Happy testing,
+The py.test Development Team
+
+
+2.9.1 (compared to 2.9.0)
+-------------------------
+
+**Bug Fixes**
+
+* Improve error message when a plugin fails to load.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_):
+  ``pytest.fail`` with non-ascii characters raises an internal pytest error.
+  Thanks `@nicoddemus`_ for the PR.
+
+* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs
+  contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_).
+
+* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors
+  containing non-ascii lines at the point of failure generated an internal
+  py.test error.
+  Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR.
+
+* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize
+  attempt to decode it as utf-8 ignoring errors.
+
+* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line.
+
+
+.. _#1437: https://github.com/pytest-dev/pytest/issues/1437
+.. _#469: https://github.com/pytest-dev/pytest/issues/469
+.. _#1431: https://github.com/pytest-dev/pytest/pull/1431
+.. _#649: https://github.com/pytest-dev/pytest/issues/649
+
+.. _@asottile: https://github.com/asottile
+.. _@nicoddemus: https://github.com/nicoddemus
+.. _@tomviner: https://github.com/tomviner
diff --git a/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst b/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst
new file mode 100644
index 0000000..8f274cd
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst
@@ -0,0 +1,78 @@
+pytest-2.9.2
+============
+
+pytest is a mature Python testing tool with more than a 1100 tests
+against itself, passing on many different interpreters and platforms.
+
+See below for the changes and see docs at:
+
+    http://pytest.org
+
+As usual, you can upgrade from pypi via::
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+      Adam Chainz
+      Benjamin Dopplinger
+      Bruno Oliveira
+      Florian Bruhin
+      John Towler
+      Martin Prusse
+      Meng Jue
+      MengJueM
+      Omar Kohl
+      Quentin Pradet
+      Ronny Pfannschmidt
+      Thomas Güttler
+      TomV
+      Tyler Goodlet
+
+
+Happy testing,
+The py.test Development Team
+
+
+2.9.2 (compared to 2.9.1)
+---------------------------
+
+**Bug Fixes**
+
+* fix `#510`_: skip tests where one parameterize dimension was empty
+  thanks Alex Stapleton for the Report and `@RonnyPfannschmidt`_ for the PR
+
+* Fix Xfail does not work with condition keyword argument.
+  Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_
+  for PR the (`#1524`_).
+
+* Fix win32 path issue when putting custom config file with absolute path
+  in ``pytest.main("-c your_absolute_path")``.
+
+* Fix maximum recursion depth detection when raised error class is not aware
+  of unicode/encoded bytes.
+  Thanks `@prusse-martin`_ for the PR (`#1506`_).
+
+* Fix ``pytest.mark.skip`` mark when used in strict mode.
+  Thanks `@pquentin`_ for the PR and `@RonnyPfannschmidt`_ for
+  showing how to fix the bug.
+
+* Minor improvements and fixes to the documentation.
+  Thanks `@omarkohl`_ for the PR.
+
+* Fix ``--fixtures`` to show all fixture definitions as opposed to just
+  one per fixture name.
+  Thanks to `@hackebrot`_ for the PR.
+
+.. _#510: https://github.com/pytest-dev/pytest/issues/510
+.. _#1506: https://github.com/pytest-dev/pytest/pull/1506
+.. _#1496: https://github.com/pytest-dev/pytest/issue/1496
+.. _#1524: https://github.com/pytest-dev/pytest/issue/1524
+
+.. _@astraw38: https://github.com/astraw38
+.. _@hackebrot: https://github.com/hackebrot
+.. _@omarkohl: https://github.com/omarkohl
+.. _@pquentin: https://github.com/pquentin
+.. _@prusse-martin: https://github.com/prusse-martin
+.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
+.. _@tomviner: https://github.com/tomviner
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst
new file mode 100644
index 0000000..4bf1e85
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst
@@ -0,0 +1,82 @@
+pytest-3.0.0
+============
+
+The pytest team is proud to announce the 3.0.0 release!
+
+pytest is a mature Python testing tool with more than a 1600 tests
+against itself, passing on many different interpreters and platforms.
+
+This release contains a lot of bugs fixes and improvements, and much of
+the work done on it was possible because of the 2016 Sprint[1], which
+was funded by an indiegogo campaign which raised over US$12,000 with 
+nearly 100 backers. 
+
+There's a "What's new in pytest 3.0" [2] blog post highlighting the 
+major features in this release.
+
+To see the complete changelog and documentation, please visit:
+
+    http://docs.pytest.org
+
+As usual, you can upgrade from pypi via:
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+    AbdealiJK
+    Ana Ribeiro
+    Antony Lee
+    Brandon W Maister
+    Brianna Laugher
+    Bruno Oliveira
+    Ceridwen
+    Christian Boelsen
+    Daniel Hahler
+    Danielle Jenkins
+    Dave Hunt
+    Diego Russo
+    Dmitry Dygalo
+    Edoardo Batini
+    Eli Boyarski
+    Florian Bruhin
+    Floris Bruynooghe
+    Greg Price
+    Guyzmo
+    HEAD KANGAROO
+    JJ
+    Javi Romero
+    Javier Domingo Cansino
+    Kale Kundert
+    Kalle Bronsen
+    Marius Gedminas
+    Matt Williams
+    Mike Lundy
+    Oliver Bestwalter
+    Omar Kohl
+    Raphael Pierzina
+    RedBeardCode
+    Roberto Polli
+    Romain Dorgueil
+    Roman Bolshakov
+    Ronny Pfannschmidt
+    Stefan Zimmermann
+    Steffen Allner
+    Tareq Alayan
+    Ted Xiao
+    Thomas Grainger
+    Tom Viner
+    TomV
+    Vasily Kuznetsov
+    aostr
+    marscher
+    palaviv
+    satoru
+    taschini
+
+
+Happy testing,
+The Pytest Development Team
+
+[1] http://blog.pytest.org/2016/pytest-development-sprint/
+[2] http://blog.pytest.org/2016/whats-new-in-pytest-30/
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst
new file mode 100644
index 0000000..9fb3804
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst
@@ -0,0 +1,26 @@
+pytest-3.0.1
+============
+
+pytest 3.0.1 has just been released to PyPI.
+
+This release fixes some regressions reported in version 3.0.0, being a
+drop-in replacement. To upgrade:
+
+  pip install --upgrade pytest
+  
+The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+      Adam Chainz
+      Andrew Svetlov
+      Bruno Oliveira
+      Daniel Hahler
+      Dmitry Dygalo
+      Florian Bruhin
+      Marcin Bachry
+      Ronny Pfannschmidt
+      matthiasha
+
+Happy testing,
+The py.test Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst
new file mode 100644
index 0000000..9d1c05f
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst
@@ -0,0 +1,24 @@
+pytest-3.0.2
+============
+
+pytest 3.0.2 has just been released to PyPI.
+
+This release fixes some regressions and bugs reported in version 3.0.1, being a
+drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Ahn Ki-Wook
+* Bruno Oliveira
+* Florian Bruhin
+* Jordan Guymon
+* Raphael Pierzina
+* Ronny Pfannschmidt
+* mbyt
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst
new file mode 100644
index 0000000..f001721
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst
@@ -0,0 +1,27 @@
+pytest-3.0.3
+============
+
+pytest 3.0.3 has just been released to PyPI.
+
+This release fixes some regressions and bugs reported in the last version, 
+being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Bruno Oliveira
+* Florian Bruhin
+* Floris Bruynooghe
+* Huayi Zhang
+* Lev Maximov
+* Raquel Alegre
+* Ronny Pfannschmidt
+* Roy Williams
+* Tyler Goodlet
+* mbyt
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst
new file mode 100644
index 0000000..8520570
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst
@@ -0,0 +1,29 @@
+pytest-3.0.4
+============
+
+pytest 3.0.4 has just been released to PyPI.
+
+This release fixes some regressions and bugs reported in the last version, 
+being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Bruno Oliveira
+* Dan Wandschneider
+* Florian Bruhin
+* Georgy Dyuldin
+* Grigorii Eremeev
+* Jason R. Coombs
+* Manuel Jacob
+* Mathieu Clabaut
+* Michael Seifert
+* Nikolaus Rath
+* Ronny Pfannschmidt
+* Tom V
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst
new file mode 100644
index 0000000..3e2419d
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst
@@ -0,0 +1,27 @@
+pytest-3.0.5
+============
+
+pytest 3.0.5 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Ana Vojnovic
+* Bruno Oliveira
+* Daniel Hahler
+* Duncan Betts
+* Igor Starikov
+* Ismail
+* Luke Murphy
+* Ned Batchelder
+* Ronny Pfannschmidt
+* Sebastian Ramacher
+* nmundar
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst
new file mode 100644
index 0000000..2988b9c
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst
@@ -0,0 +1,33 @@
+pytest-3.0.6
+============
+
+pytest 3.0.6 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+
+Thanks to all who contributed to this release, among them:
+
+* Andreas Pelme
+* Bruno Oliveira
+* Dmitry Malinovsky
+* Eli Boyarski
+* Jakub Wilk
+* Jeff Widman
+* Loïc Estève
+* Luke Murphy
+* Miro Hrončok
+* Oscar Hellström
+* Peter Heatwole
+* Philippe Ombredanne
+* Ronny Pfannschmidt
+* Rutger Prins
+* Stefan Scherfke
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst b/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst
new file mode 100644
index 0000000..591557a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst
@@ -0,0 +1,33 @@
+pytest-3.0.7
+============
+
+pytest 3.0.7 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Anthony Sottile
+* Barney Gale
+* Bruno Oliveira
+* Florian Bruhin
+* Floris Bruynooghe
+* Ionel Cristian Mărieș
+* Katerina Koukiou
+* NODA, Kai
+* Omer Hadari
+* Patrick Hayes
+* Ran Benita
+* Ronny Pfannschmidt
+* Victor Uriarte
+* Vidar Tonaas Fauske
+* Ville Skyttä
+* fbjorn
+* mbyt
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst b/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst
new file mode 100644
index 0000000..99cc6bd
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst
@@ -0,0 +1,61 @@
+pytest-3.1.0
+=======================================
+
+The pytest team is proud to announce the 3.1.0 release!
+
+pytest is a mature Python testing tool with more than a 1600 tests
+against itself, passing on many different interpreters and platforms.
+
+This release contains a bugs fixes and improvements, so users are encouraged
+to take a look at the CHANGELOG:
+
+http://doc.pytest.org/en/latest/changelog.html
+
+For complete documentation, please visit:
+
+    http://docs.pytest.org
+
+As usual, you can upgrade from pypi via:
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+* Anthony Sottile
+* Ben Lloyd
+* Bruno Oliveira
+* David Giese
+* David Szotten
+* Dmitri Pribysh
+* Florian Bruhin
+* Florian Schulze
+* Floris Bruynooghe
+* John Towler
+* Jonas Obrist
+* Katerina Koukiou
+* Kodi Arfer
+* Krzysztof Szularz
+* Lev Maximov
+* Loïc Estève
+* Luke Murphy
+* Manuel Krebber
+* Matthew Duck
+* Matthias Bussonnier
+* Michael Howitz
+* Michal Wajszczuk
+* Paweł Adamczak
+* Rafael Bertoldi
+* Ravi Chandra
+* Ronny Pfannschmidt
+* Skylar Downes
+* Thomas Kriechbaumer
+* Vitaly Lashmanov
+* Vlad Dragos
+* Wheerd
+* Xander Johnson
+* mandeep
+* reut
+
+
+Happy testing,
+The Pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst b/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst
new file mode 100644
index 0000000..370b8fd
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst
@@ -0,0 +1,23 @@
+pytest-3.1.1
+=======================================
+
+pytest 3.1.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Bruno Oliveira
+* Florian Bruhin
+* Floris Bruynooghe
+* Jason R. Coombs
+* Ronny Pfannschmidt
+* wanghui
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst b/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst
new file mode 100644
index 0000000..60168a8
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst
@@ -0,0 +1,23 @@
+pytest-3.1.2
+=======================================
+
+pytest 3.1.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Andreas Pelme
+* ApaDoctor
+* Bruno Oliveira
+* Florian Bruhin
+* Ronny Pfannschmidt
+* Segev Finer
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst b/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst
new file mode 100644
index 0000000..a552806
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst
@@ -0,0 +1,23 @@
+pytest-3.1.3
+=======================================
+
+pytest 3.1.3 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Antoine Legrand
+* Bruno Oliveira
+* Max Moroz
+* Raphael Pierzina
+* Ronny Pfannschmidt
+* Ryan Fitzpatrick
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst b/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst
new file mode 100644
index 0000000..4d2830e
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst
@@ -0,0 +1,48 @@
+pytest-3.2.0
+=======================================
+
+The pytest team is proud to announce the 3.2.0 release!
+
+pytest is a mature Python testing tool with more than a 1600 tests
+against itself, passing on many different interpreters and platforms.
+
+This release contains a number of bugs fixes and improvements, so users are encouraged
+to take a look at the CHANGELOG:
+
+    http://doc.pytest.org/en/latest/changelog.html
+
+For complete documentation, please visit:
+
+    http://docs.pytest.org
+
+As usual, you can upgrade from pypi via:
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+* Alex Hartoto
+* Andras Tim
+* Bruno Oliveira
+* Daniel Hahler
+* Florian Bruhin
+* Floris Bruynooghe
+* John Still
+* Jordan Moldow
+* Kale Kundert
+* Lawrence Mitchell
+* Llandy Riveron Del Risco
+* Maik Figura
+* Martin Altmayer
+* Mihai Capotă
+* Nathaniel Waisbrot
+* Nguyễn Hồng Quân
+* Pauli Virtanen
+* Raphael Pierzina
+* Ronny Pfannschmidt
+* Segev Finer
+* V.Kuznetsov
+
+
+Happy testing,
+The Pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst b/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst
new file mode 100644
index 0000000..899ffcd
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst
@@ -0,0 +1,22 @@
+pytest-3.2.1
+=======================================
+
+pytest 3.2.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Alex Gaynor
+* Bruno Oliveira
+* Florian Bruhin
+* Ronny Pfannschmidt
+* Srinivas Reddy Thatiparthy
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst b/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst
new file mode 100644
index 0000000..599bf87
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst
@@ -0,0 +1,28 @@
+pytest-3.2.2
+=======================================
+
+pytest 3.2.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Andreas Pelme
+* Antonio Hidalgo
+* Bruno Oliveira
+* Felipe Dau
+* Fernando Macedo
+* Jesús Espino
+* Joan Massich
+* Joe Talbott
+* Kirill Pinchuk
+* Ronny Pfannschmidt
+* Xuan Luong
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst b/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst
new file mode 100644
index 0000000..5893749
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst
@@ -0,0 +1,23 @@
+pytest-3.2.3
+=======================================
+
+pytest 3.2.3 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Bruno Oliveira
+* Evan
+* Joe Hamman
+* Oliver Bestwalter
+* Ronny Pfannschmidt
+* Xuan Luong
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst b/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst
new file mode 100644
index 0000000..44bfcc2
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst
@@ -0,0 +1,36 @@
+pytest-3.2.4
+=======================================
+
+pytest 3.2.4 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Bruno Oliveira
+* Christian Boelsen
+* Christoph Buchner
+* Daw-Ran Liou
+* Florian Bruhin
+* Franck Michea
+* Leonard Lausen
+* Matty G
+* Owen Tuz
+* Pavel Karateev
+* Pierre GIRAUD
+* Ronny Pfannschmidt
+* Stephen Finucane
+* Sviatoslav Abakumov
+* Thomas Hisch
+* Tom Dalton
+* Xuan Luong
+* Yorgos Pagles
+* Семён Марьясин
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst b/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst
new file mode 100644
index 0000000..a520ce2
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst
@@ -0,0 +1,18 @@
+pytest-3.2.5
+=======================================
+
+pytest 3.2.5 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+* Bruno Oliveira
+
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst b/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst
new file mode 100644
index 0000000..e0740e7
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst
@@ -0,0 +1,50 @@
+pytest-3.3.0
+=======================================
+
+The pytest team is proud to announce the 3.3.0 release!
+
+pytest is a mature Python testing tool with more than a 1600 tests
+against itself, passing on many different interpreters and platforms.
+
+This release contains a number of bugs fixes and improvements, so users are encouraged
+to take a look at the CHANGELOG:
+
+    http://doc.pytest.org/en/latest/changelog.html
+
+For complete documentation, please visit:
+
+    http://docs.pytest.org
+
+As usual, you can upgrade from pypi via:
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+* Anthony Sottile
+* Bruno Oliveira
+* Ceridwen
+* Daniel Hahler
+* Dirk Thomas
+* Dmitry Malinovsky
+* Florian Bruhin
+* George Y. Kussumoto
+* Hugo
+* Jesús Espino
+* Joan Massich
+* Ofir
+* OfirOshir
+* Ronny Pfannschmidt
+* Samuel Dion-Girardeau
+* Srinivas Reddy Thatiparthy
+* Sviatoslav Abakumov
+* Tarcisio Fischer
+* Thomas Hisch
+* Tyler Goodlet
+* hugovk
+* je
+* prokaktus
+
+
+Happy testing,
+The Pytest Development Team
diff --git a/tools/third_party/pytest/doc/en/announce/sprint2016.rst b/tools/third_party/pytest/doc/en/announce/sprint2016.rst
new file mode 100644
index 0000000..8e70658
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/announce/sprint2016.rst
@@ -0,0 +1,64 @@
+python testing sprint June 20th-26th 2016
+======================================================
+
+.. image:: ../img/freiburg2.jpg
+   :width: 400
+
+The pytest core group held the biggest sprint
+in its history in June 2016, taking place in the black forest town Freiburg
+in Germany.  In February 2016 we started a `funding
+campaign on Indiegogo to cover expenses
+<http://igg.me/at/pytest-sprint/x/4034848>`_ The page also mentions
+some preliminary topics:
+
+- improving pytest-xdist test scheduling to take into account
+  fixture setups and explicit user hints.
+
+- provide info on fixture dependencies during --collect-only
+
+- tying pytest-xdist to tox so that you can do "py.test -e py34"
+  to run tests in a particular tox-managed virtualenv.  Also
+  look into making pytest-xdist use tox environments on
+  remote ssh-sides so that remote dependency management becomes
+  easier.
+
+- refactoring the fixture system so more people understand it :)
+
+- integrating PyUnit setup methods as autouse fixtures.
+  possibly adding ways to influence ordering of same-scoped
+  fixtures (so you can make a choice of which fixtures come
+  before others)
+
+- fixing bugs and issues from the tracker, really an endless source :)
+
+
+Participants
+--------------
+
+Over 20 participants took part from 4 continents, including employees
+from Splunk, Personalkollen, Cobe.io, FanDuel and Dolby. Some newcomers
+mixed with developers who have worked on pytest since its beginning, and
+of course everyone in between.
+
+
+Sprint organisation, schedule
+-------------------------------
+
+People arrived in Freiburg on the 19th, with sprint development taking
+place on 20th, 21st, 22nd, 24th and 25th. On the 23rd we took a break
+day for some hot hiking in the Black Forest.
+
+Sprint activity was organised heavily around pairing, with plenty of group
+discusssions to take advantage of the high bandwidth, and lightning talks
+as well.
+
+
+Money / funding
+---------------
+
+
+The Indiegogo campaign aimed for 11000 USD and in the end raised over
+12000, to reimburse travel costs, pay for a sprint venue and catering.
+
+Excess money is reserved for further sprint/travel funding for pytest/tox
+contributors.
diff --git a/tools/third_party/pytest/doc/en/assert.rst b/tools/third_party/pytest/doc/en/assert.rst
new file mode 100644
index 0000000..4a85297
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/assert.rst
@@ -0,0 +1,299 @@
+
+The writing and reporting of assertions in tests
+==================================================
+
+.. _`assertfeedback`:
+.. _`assert with the assert statement`:
+.. _`assert`:
+
+
+Asserting with the ``assert`` statement
+---------------------------------------------------------
+
+``pytest`` allows you to use the standard python ``assert`` for verifying
+expectations and values in Python tests.  For example, you can write the
+following::
+
+    # content of test_assert1.py
+    def f():
+        return 3
+
+    def test_function():
+        assert f() == 4
+
+to assert that your function returns a certain value. If this assertion fails
+you will see the return value of the function call::
+
+    $ pytest test_assert1.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_assert1.py F                                                    [100%]
+    
+    ================================= FAILURES =================================
+    ______________________________ test_function _______________________________
+    
+        def test_function():
+    >       assert f() == 4
+    E       assert 3 == 4
+    E        +  where 3 = f()
+    
+    test_assert1.py:5: AssertionError
+    ========================= 1 failed in 0.12 seconds =========================
+
+``pytest`` has support for showing the values of the most common subexpressions
+including calls, attributes, comparisons, and binary and unary
+operators. (See :ref:`tbreportdemo`).  This allows you to use the
+idiomatic python constructs without boilerplate code while not losing
+introspection information.
+
+However, if you specify a message with the assertion like this::
+
+    assert a % 2 == 0, "value was odd, should be even"
+
+then no assertion introspection takes places at all and the message
+will be simply shown in the traceback.
+
+See :ref:`assert-details` for more information on assertion introspection.
+
+.. _`assertraises`:
+
+Assertions about expected exceptions
+------------------------------------------
+
+In order to write assertions about raised exceptions, you can use
+``pytest.raises`` as a context manager like this::
+
+    import pytest
+
+    def test_zero_division():
+        with pytest.raises(ZeroDivisionError):
+            1 / 0
+
+and if you need to have access to the actual exception info you may use::
+
+    def test_recursion_depth():
+        with pytest.raises(RuntimeError) as excinfo:
+            def f():
+                f()
+            f()
+        assert 'maximum recursion' in str(excinfo.value)
+
+``excinfo`` is a ``ExceptionInfo`` instance, which is a wrapper around
+the actual exception raised.  The main attributes of interest are
+``.type``, ``.value`` and ``.traceback``.
+
+.. versionchanged:: 3.0
+
+In the context manager form you may use the keyword argument
+``message`` to specify a custom failure message::
+
+     >>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
+     ...    pass
+     ... Failed: Expecting ZeroDivisionError
+
+If you want to write test code that works on Python 2.4 as well,
+you may also use two other ways to test for an expected exception::
+
+    pytest.raises(ExpectedException, func, *args, **kwargs)
+    pytest.raises(ExpectedException, "func(*args, **kwargs)")
+
+both of which execute the specified function with args and kwargs and
+asserts that the given ``ExpectedException`` is raised.  The reporter will
+provide you with helpful output in case of failures such as *no
+exception* or *wrong exception*.
+
+Note that it is also possible to specify a "raises" argument to
+``pytest.mark.xfail``, which checks that the test is failing in a more
+specific way than just having any exception raised::
+
+    @pytest.mark.xfail(raises=IndexError)
+    def test_f():
+        f()
+
+Using ``pytest.raises`` is likely to be better for cases where you are testing
+exceptions your own code is deliberately raising, whereas using
+``@pytest.mark.xfail`` with a check function is probably better for something
+like documenting unfixed bugs (where the test describes what "should" happen)
+or bugs in dependencies.
+
+Also, the context manager form accepts a ``match`` keyword parameter to test
+that a regular expression matches on the string representation of an exception
+(like the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
+
+    import pytest
+
+    def myfunc():
+        raise ValueError("Exception 123 raised")
+
+    def test_match():
+        with pytest.raises(ValueError, match=r'.* 123 .*'):
+            myfunc()
+
+The regexp parameter of the ``match`` method is matched with the ``re.search``
+function. So in the above example ``match='123'`` would have worked as
+well.
+
+
+.. _`assertwarns`:
+
+Assertions about expected warnings
+-----------------------------------------
+
+.. versionadded:: 2.8
+
+You can check that code raises a particular warning using
+:ref:`pytest.warns <warns>`.
+
+
+.. _newreport:
+
+Making use of context-sensitive comparisons
+-------------------------------------------------
+
+.. versionadded:: 2.0
+
+``pytest`` has rich support for providing context-sensitive information
+when it encounters comparisons.  For example::
+
+    # content of test_assert2.py
+
+    def test_set_comparison():
+        set1 = set("1308")
+        set2 = set("8035")
+        assert set1 == set2
+
+if you run this module::
+
+    $ pytest test_assert2.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_assert2.py F                                                    [100%]
+    
+    ================================= FAILURES =================================
+    ___________________________ test_set_comparison ____________________________
+    
+        def test_set_comparison():
+            set1 = set("1308")
+            set2 = set("8035")
+    >       assert set1 == set2
+    E       AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
+    E         Extra items in the left set:
+    E         '1'
+    E         Extra items in the right set:
+    E         '5'
+    E         Use -v to get the full diff
+    
+    test_assert2.py:5: AssertionError
+    ========================= 1 failed in 0.12 seconds =========================
+
+Special comparisons are done for a number of cases:
+
+* comparing long strings: a context diff is shown
+* comparing long sequences: first failing indices
+* comparing dicts: different entries
+
+See the :ref:`reporting demo <tbreportdemo>` for many more examples.
+
+Defining your own assertion comparison
+----------------------------------------------
+
+It is possible to add your own detailed explanations by implementing
+the ``pytest_assertrepr_compare`` hook.
+
+.. autofunction:: _pytest.hookspec.pytest_assertrepr_compare
+   :noindex:
+
+As an example consider adding the following hook in a :ref:`conftest.py <conftest.py>` 
+file which provides an alternative explanation for ``Foo`` objects::
+
+   # content of conftest.py
+   from test_foocompare import Foo
+   def pytest_assertrepr_compare(op, left, right):
+       if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
+           return ['Comparing Foo instances:',
+                   '   vals: %s != %s' % (left.val, right.val)]
+
+now, given this test module::
+
+   # content of test_foocompare.py
+   class Foo(object):
+       def __init__(self, val):
+           self.val = val
+
+       def __eq__(self, other):
+           return self.val == other.val
+
+   def test_compare():
+       f1 = Foo(1)
+       f2 = Foo(2)
+       assert f1 == f2
+
+you can run the test module and get the custom output defined in
+the conftest file::
+
+   $ pytest -q test_foocompare.py
+   F                                                                    [100%]
+   ================================= FAILURES =================================
+   _______________________________ test_compare _______________________________
+   
+       def test_compare():
+           f1 = Foo(1)
+           f2 = Foo(2)
+   >       assert f1 == f2
+   E       assert Comparing Foo instances:
+   E            vals: 1 != 2
+   
+   test_foocompare.py:11: AssertionError
+   1 failed in 0.12 seconds
+
+.. _assert-details:
+.. _`assert introspection`:
+
+Advanced assertion introspection
+----------------------------------
+
+.. versionadded:: 2.1
+
+
+Reporting details about a failing assertion is achieved by rewriting assert
+statements before they are run.  Rewritten assert statements put introspection
+information into the assertion failure message.  ``pytest`` only rewrites test
+modules directly discovered by its test collection process, so asserts in
+supporting modules which are not themselves test modules will not be rewritten.
+
+.. note::
+
+   ``pytest`` rewrites test modules on import by using an import
+   hook to write new ``pyc`` files. Most of the time this works transparently.
+   However, if you are messing with import yourself, the import hook may
+   interfere.
+
+   If this is the case you have two options:
+
+   * Disable rewriting for a specific module by adding the string
+     ``PYTEST_DONT_REWRITE`` to its docstring.
+
+   * Disable rewriting for all modules by using ``--assert=plain``.
+
+   Additionally, rewriting will fail silently if it cannot write new ``.pyc`` files,
+   i.e. in a read-only filesystem or a zipfile.
+
+
+For further information, Benjamin Peterson wrote up `Behind the scenes of pytest's new assertion rewriting <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_.
+
+.. versionadded:: 2.1
+   Add assert rewriting as an alternate introspection technique.
+
+.. versionchanged:: 2.1
+   Introduce the ``--assert`` option. Deprecate ``--no-assert`` and
+   ``--nomagic``.
+
+.. versionchanged:: 3.0
+   Removes the ``--no-assert`` and ``--nomagic`` options.
+   Removes the ``--assert=reinterp`` option.
diff --git a/tools/third_party/pytest/doc/en/backwards-compatibility.rst b/tools/third_party/pytest/doc/en/backwards-compatibility.rst
new file mode 100644
index 0000000..84f2c43
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/backwards-compatibility.rst
@@ -0,0 +1,105 @@
+.. _backwards-compatibility:
+
+Backwards Compatibility Policy
+==============================
+
+Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary.
+
+With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
+
+To communicate changes we are already issuing deprecation warnings, but they are not displayed by default. In pytest 3.0 we changed the default setting so that pytest deprecation warnings are displayed if not explicitly silenced (with ``--disable-pytest-warnings``).
+
+We will only remove deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we will not remove it in 4.0 but in 5.0).
+
+
+Deprecation Roadmap
+-------------------
+
+This page lists deprecated features and when we plan to remove them. It is important to list the feature, the version where it got deprecated and the version we plan to remove it.
+
+Following our deprecation policy, we should aim to keep features for *at least* two minor versions after it was considered deprecated.
+
+
+Future Releases
+~~~~~~~~~~~~~~~
+
+3.4
+^^^
+
+**Old style classes**
+
+Issue: `#2147 <https://github.com/pytest-dev/pytest/issues/2147>`_.
+
+Deprecated in ``3.2``.
+
+4.0
+^^^
+
+**Yield tests**
+
+Deprecated in ``3.0``.
+
+**pytest-namespace hook**
+
+deprecated in ``3.2``.
+
+**Marks in parameter sets**
+
+Deprecated in ``3.2``.
+
+**--result-log**
+
+Deprecated in ``3.0``.
+
+See `#830 <https://github.com/pytest-dev/pytest/issues/830>`_ for more information. Suggested alternative: `pytest-tap <https://pypi.python.org/pypi/pytest-tap>`_.
+
+**metafunc.addcall**
+
+Issue: `#2876 <https://github.com/pytest-dev/pytest/issues/2876>`_.
+
+Deprecated in ``3.3``.
+
+**pytest_plugins in non-toplevel conftests**
+
+There is a deep conceptual confusion as ``conftest.py`` files themselves are activated/deactivated based on path, but the plugins they depend on aren't.
+
+Issue: `#2639 <https://github.com/pytest-dev/pytest/issues/2639>`_.
+
+Not yet officially deprecated.
+
+**passing a single string to pytest.main()**
+
+Pass a list of strings to ``pytest.main()`` instead.
+
+Deprecated in ``3.1``.
+
+**[pytest] section in setup.cfg**
+
+Use ``[tool:pytest]`` instead for compatibility with other tools.
+
+Deprecated in ``3.0``.
+
+Past Releases
+~~~~~~~~~~~~~
+
+3.0
+^^^
+
+* The following deprecated commandline options were removed:
+
+  * ``--genscript``: no longer supported;
+  * ``--no-assert``: use ``--assert=plain`` instead;
+  * ``--nomagic``: use ``--assert=plain`` instead;
+  * ``--report``: use ``-r`` instead;
+
+* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
+  were never documented and a leftover from a pre-virtualenv era. These entry
+  points also created broken entry points in wheels, so removing them also
+  removes a source of confusion for users.
+
+
+
+3.3
+^^^
+
+* Dropped support for EOL Python 2.6 and 3.3.
\ No newline at end of file
diff --git a/tools/third_party/pytest/doc/en/bash-completion.rst b/tools/third_party/pytest/doc/en/bash-completion.rst
new file mode 100644
index 0000000..81fe621
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/bash-completion.rst
@@ -0,0 +1,28 @@
+
+.. _bash_completion:
+
+Setting up bash completion
+==========================
+
+When using bash as your shell, ``pytest`` can use argcomplete
+(https://argcomplete.readthedocs.io/) for auto-completion.
+For this ``argcomplete`` needs to be installed **and** enabled.
+
+Install argcomplete using::
+
+        sudo pip install 'argcomplete>=0.5.7'
+
+For global activation of all argcomplete enabled python applications run::
+
+	sudo activate-global-python-argcomplete
+
+For permanent (but not global) ``pytest`` activation, use::
+
+        register-python-argcomplete pytest >> ~/.bashrc
+
+For one-time activation of argcomplete for ``pytest`` only, use::
+
+        eval "$(register-python-argcomplete pytest)"
+
+
+
diff --git a/tools/third_party/pytest/doc/en/builtin.rst b/tools/third_party/pytest/doc/en/builtin.rst
new file mode 100644
index 0000000..d11eb56
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/builtin.rst
@@ -0,0 +1,161 @@
+
+.. _`pytest helpers`:
+
+Pytest API and builtin fixtures
+================================================
+
+This is a list of ``pytest.*`` API functions and fixtures.
+
+For information on plugin hooks and objects, see :ref:`plugins`.
+
+For information on the ``pytest.mark`` mechanism, see :ref:`mark`.
+
+For the below objects, you can also interactively ask for help, e.g. by
+typing on the Python interactive prompt something like::
+
+    import pytest
+    help(pytest)
+
+.. currentmodule:: pytest
+
+Invoking pytest interactively
+---------------------------------------------------
+
+.. autofunction:: main
+
+More examples at :ref:`pytest.main-usage`
+
+
+Helpers for assertions about Exceptions/Warnings
+--------------------------------------------------------
+
+.. autofunction:: raises
+
+Examples at :ref:`assertraises`.
+
+.. autofunction:: deprecated_call
+
+Comparing floating point numbers
+--------------------------------
+
+.. autofunction:: approx
+
+Raising a specific test outcome
+--------------------------------------
+
+You can use the following functions in your test, fixture or setup
+functions to force a certain test outcome.  Note that most often
+you can rather use declarative marks, see :ref:`skipping`.
+
+.. autofunction:: _pytest.outcomes.fail
+.. autofunction:: _pytest.outcomes.skip
+.. autofunction:: _pytest.outcomes.importorskip
+.. autofunction:: _pytest.outcomes.xfail
+.. autofunction:: _pytest.outcomes.exit
+
+Fixtures and requests
+-----------------------------------------------------
+
+To mark a fixture function:
+
+.. autofunction:: _pytest.fixtures.fixture
+
+Tutorial at :ref:`fixtures`.
+
+The ``request`` object that can be used from fixture functions.
+
+.. autoclass:: _pytest.fixtures.FixtureRequest()
+    :members:
+
+
+.. _builtinfixtures:
+.. _builtinfuncargs:
+
+Builtin fixtures/function arguments
+-----------------------------------------
+
+You can ask for available builtin or project-custom
+:ref:`fixtures <fixtures>` by typing::
+
+    $ pytest -q --fixtures
+    cache
+        Return a cache object that can persist state between testing sessions.
+        
+        cache.get(key, default)
+        cache.set(key, value)
+        
+        Keys must be a ``/`` separated value, where the first part is usually the
+        name of your plugin or application to avoid clashes with other cache users.
+        
+        Values can be any object handled by the json stdlib module.
+    capsys
+        Enable capturing of writes to sys.stdout/sys.stderr and make
+        captured output available via ``capsys.readouterr()`` method calls
+        which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be ``text``
+        objects.
+    capsysbinary
+        Enable capturing of writes to sys.stdout/sys.stderr and make
+        captured output available via ``capsys.readouterr()`` method calls
+        which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be ``bytes``
+        objects.
+    capfd
+        Enable capturing of writes to file descriptors 1 and 2 and make
+        captured output available via ``capfd.readouterr()`` method calls
+        which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be ``text``
+        objects.
+    capfdbinary
+        Enable capturing of write to file descriptors 1 and 2 and make
+        captured output available via ``capfdbinary.readouterr`` method calls
+        which return a ``(out, err)`` tuple.  ``out`` and ``err`` will be
+        ``bytes`` objects.
+    doctest_namespace
+        Inject names into the doctest namespace.
+    pytestconfig
+        the pytest config object with access to command line opts.
+    record_xml_property
+        Add extra xml properties to the tag for the calling test.
+        The fixture is callable with ``(name, value)``, with value being automatically
+        xml-encoded.
+    caplog
+        Access and control log capturing.
+        
+        Captured logs are available through the following methods::
+        
+        * caplog.text()          -> string containing formatted log output
+        * caplog.records()       -> list of logging.LogRecord instances
+        * caplog.record_tuples() -> list of (logger_name, level, message) tuples
+    monkeypatch
+        The returned ``monkeypatch`` fixture provides these
+        helper methods to modify objects, dictionaries or os.environ::
+        
+            monkeypatch.setattr(obj, name, value, raising=True)
+            monkeypatch.delattr(obj, name, raising=True)
+            monkeypatch.setitem(mapping, name, value)
+            monkeypatch.delitem(obj, name, raising=True)
+            monkeypatch.setenv(name, value, prepend=False)
+            monkeypatch.delenv(name, value, raising=True)
+            monkeypatch.syspath_prepend(path)
+            monkeypatch.chdir(path)
+        
+        All modifications will be undone after the requesting
+        test function or fixture has finished. The ``raising``
+        parameter determines if a KeyError or AttributeError
+        will be raised if the set/deletion operation has no target.
+    recwarn
+        Return a WarningsRecorder instance that provides these methods:
+        
+        * ``pop(category=None)``: return last warning matching the category.
+        * ``clear()``: clear list of warnings
+        
+        See http://docs.python.org/library/warnings.html for information
+        on warning categories.
+    tmpdir_factory
+        Return a TempdirFactory instance for the test session.
+    tmpdir
+        Return a temporary directory path object
+        which is unique to each test function invocation,
+        created as a sub directory of the base temporary
+        directory.  The returned object is a `py.path.local`_
+        path object.
+    
+    no tests ran in 0.12 seconds
diff --git a/tools/third_party/pytest/doc/en/cache.rst b/tools/third_party/pytest/doc/en/cache.rst
new file mode 100644
index 0000000..c88721b
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/cache.rst
@@ -0,0 +1,268 @@
+.. _`cache_provider`:
+.. _cache:
+
+
+Cache: working with cross-testrun state
+=======================================
+
+.. versionadded:: 2.8
+
+Usage
+---------
+
+The plugin provides two command line options to rerun failures from the
+last ``pytest`` invocation:
+
+* ``--lf``, ``--last-failed`` - to only re-run the failures.
+* ``--ff``, ``--failed-first`` - to run the failures first and then the rest of
+  the tests.
+
+For cleanup (usually not needed), a ``--cache-clear`` option allows to remove
+all cross-session cache contents ahead of a test run.
+
+Other plugins may access the `config.cache`_ object to set/get 
+**json encodable** values between ``pytest`` invocations.
+
+.. note::
+
+    This plugin is enabled by default, but can be disabled if needed: see
+    :ref:`cmdunregister` (the internal name for this plugin is
+    ``cacheprovider``).
+
+
+Rerunning only failures or failures first
+-----------------------------------------------
+
+First, let's create 50 test invocation of which only 2 fail::
+
+    # content of test_50.py
+    import pytest
+
+    @pytest.mark.parametrize("i", range(50))
+    def test_num(i):
+        if i in (17, 25):
+           pytest.fail("bad luck")
+
+If you run this for the first time you will see two failures::
+
+    $ pytest -q
+    .................F.......F........................                   [100%]
+    ================================= FAILURES =================================
+    _______________________________ test_num[17] _______________________________
+    
+    i = 17
+    
+        @pytest.mark.parametrize("i", range(50))
+        def test_num(i):
+            if i in (17, 25):
+    >          pytest.fail("bad luck")
+    E          Failed: bad luck
+    
+    test_50.py:6: Failed
+    _______________________________ test_num[25] _______________________________
+    
+    i = 25
+    
+        @pytest.mark.parametrize("i", range(50))
+        def test_num(i):
+            if i in (17, 25):
+    >          pytest.fail("bad luck")
+    E          Failed: bad luck
+    
+    test_50.py:6: Failed
+    2 failed, 48 passed in 0.12 seconds
+
+If you then run it with ``--lf``::
+
+    $ pytest --lf
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 50 items
+    run-last-failure: rerun previous 2 failures
+    
+    test_50.py FF                                                        [100%]
+    
+    ================================= FAILURES =================================
+    _______________________________ test_num[17] _______________________________
+    
+    i = 17
+    
+        @pytest.mark.parametrize("i", range(50))
+        def test_num(i):
+            if i in (17, 25):
+    >          pytest.fail("bad luck")
+    E          Failed: bad luck
+    
+    test_50.py:6: Failed
+    _______________________________ test_num[25] _______________________________
+    
+    i = 25
+    
+        @pytest.mark.parametrize("i", range(50))
+        def test_num(i):
+            if i in (17, 25):
+    >          pytest.fail("bad luck")
+    E          Failed: bad luck
+    
+    test_50.py:6: Failed
+    =========================== 48 tests deselected ============================
+    ================= 2 failed, 48 deselected in 0.12 seconds ==================
+
+You have run only the two failing test from the last run, while 48 tests have
+not been run ("deselected").
+
+Now, if you run with the ``--ff`` option, all tests will be run but the first
+previous failures will be executed first (as can be seen from the series
+of ``FF`` and dots)::
+
+    $ pytest --ff
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 50 items
+    run-last-failure: rerun previous 2 failures first
+    
+    test_50.py FF................................................        [100%]
+    
+    ================================= FAILURES =================================
+    _______________________________ test_num[17] _______________________________
+    
+    i = 17
+    
+        @pytest.mark.parametrize("i", range(50))
+        def test_num(i):
+            if i in (17, 25):
+    >          pytest.fail("bad luck")
+    E          Failed: bad luck
+    
+    test_50.py:6: Failed
+    _______________________________ test_num[25] _______________________________
+    
+    i = 25
+    
+        @pytest.mark.parametrize("i", range(50))
+        def test_num(i):
+            if i in (17, 25):
+    >          pytest.fail("bad luck")
+    E          Failed: bad luck
+    
+    test_50.py:6: Failed
+    =================== 2 failed, 48 passed in 0.12 seconds ====================
+
+.. _`config.cache`:
+
+The new config.cache object
+--------------------------------
+
+.. regendoc:wipe
+
+Plugins or conftest.py support code can get a cached value using the
+pytest ``config`` object.  Here is a basic example plugin which
+implements a :ref:`fixture` which re-uses previously created state
+across pytest invocations::
+
+    # content of test_caching.py
+    import pytest
+    import time
+
+    @pytest.fixture
+    def mydata(request):
+        val = request.config.cache.get("example/value", None)
+        if val is None:
+            time.sleep(9*0.6) # expensive computation :)
+            val = 42
+            request.config.cache.set("example/value", val)
+        return val
+
+    def test_function(mydata):
+        assert mydata == 23
+
+If you run this command once, it will take a while because
+of the sleep::
+
+    $ pytest -q
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    ______________________________ test_function _______________________________
+    
+    mydata = 42
+    
+        def test_function(mydata):
+    >       assert mydata == 23
+    E       assert 42 == 23
+    
+    test_caching.py:14: AssertionError
+    1 failed in 0.12 seconds
+
+If you run it a second time the value will be retrieved from
+the cache and this will be quick::
+
+    $ pytest -q
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    ______________________________ test_function _______________________________
+    
+    mydata = 42
+    
+        def test_function(mydata):
+    >       assert mydata == 23
+    E       assert 42 == 23
+    
+    test_caching.py:14: AssertionError
+    1 failed in 0.12 seconds
+
+See the `cache-api`_ for more details.
+
+
+Inspecting Cache content
+-------------------------------
+
+You can always peek at the content of the cache using the
+``--cache-show`` command line option::
+
+    $ py.test --cache-show
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    cachedir: $REGENDOC_TMPDIR/.cache
+    ------------------------------- cache values -------------------------------
+    cache/lastfailed contains:
+      {'test_caching.py::test_function': True}
+    example/value contains:
+      42
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+Clearing Cache content
+-------------------------------
+
+You can instruct pytest to clear all cache files and values
+by adding the ``--cache-clear`` option like this::
+
+    pytest --cache-clear
+
+This is recommended for invocations from Continuous Integration
+servers where isolation and correctness is more important
+than speed.
+
+
+.. _`cache-api`:
+
+config.cache API
+------------------
+
+The ``config.cache`` object allows other plugins,
+including ``conftest.py`` files,
+to safely and flexibly store and retrieve values across
+test runs because the ``config`` object is available
+in many places.
+
+Under the hood, the cache plugin uses the simple
+dumps/loads API of the json stdlib module
+
+.. currentmodule:: _pytest.cacheprovider
+
+.. automethod:: Cache.get
+.. automethod:: Cache.set
+.. automethod:: Cache.makedir
diff --git a/tools/third_party/pytest/doc/en/capture.rst b/tools/third_party/pytest/doc/en/capture.rst
new file mode 100644
index 0000000..a87b57f
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/capture.rst
@@ -0,0 +1,148 @@
+
+.. _`captures`:
+
+Capturing of the stdout/stderr output
+=========================================================
+
+Default stdout/stderr/stdin capturing behaviour
+---------------------------------------------------------
+
+During test execution any output sent to ``stdout`` and ``stderr`` is
+captured.  If a test or a setup method fails its according captured
+output will usually be shown along with the failure traceback.
+
+In addition, ``stdin`` is set to a "null" object which will
+fail on attempts to read from it because it is rarely desired
+to wait for interactive input when running automated tests.
+
+By default capturing is done by intercepting writes to low level
+file descriptors.  This allows to capture output from simple
+print statements as well as output from a subprocess started by
+a test.
+
+Setting capturing methods or disabling capturing
+-------------------------------------------------
+
+There are two ways in which ``pytest`` can perform capturing:
+
+* file descriptor (FD) level capturing (default): All writes going to the
+  operating system file descriptors 1 and 2 will be captured.
+
+* ``sys`` level capturing: Only writes to Python files ``sys.stdout``
+  and ``sys.stderr`` will be captured.  No capturing of writes to
+  filedescriptors is performed.
+
+.. _`disable capturing`:
+
+You can influence output capturing mechanisms from the command line::
+
+    pytest -s            # disable all capturing
+    pytest --capture=sys # replace sys.stdout/stderr with in-mem files
+    pytest --capture=fd  # also point filedescriptors 1 and 2 to temp file
+
+.. _printdebugging:
+
+Using print statements for debugging
+---------------------------------------------------
+
+One primary benefit of the default capturing of stdout/stderr output
+is that you can use print statements for debugging::
+
+    # content of test_module.py
+
+    def setup_function(function):
+        print ("setting up %s" % function)
+
+    def test_func1():
+        assert True
+
+    def test_func2():
+        assert False
+
+and running this module will show you precisely the output
+of the failing function and hide the other one::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    
+    test_module.py .F                                                    [100%]
+    
+    ================================= FAILURES =================================
+    ________________________________ test_func2 ________________________________
+    
+        def test_func2():
+    >       assert False
+    E       assert False
+    
+    test_module.py:9: AssertionError
+    -------------------------- Captured stdout setup ---------------------------
+    setting up <function test_func2 at 0xdeadbeef>
+    ==================== 1 failed, 1 passed in 0.12 seconds ====================
+
+Accessing captured output from a test function
+---------------------------------------------------
+
+The ``capsys``, ``capsysbinary``, ``capfd``, and ``capfdbinary`` fixtures
+allow access to stdout/stderr output created during test execution.  Here is
+an example test function that performs some output related checks:
+
+.. code-block:: python
+
+    def test_myoutput(capsys): # or use "capfd" for fd-level
+        print ("hello")
+        sys.stderr.write("world\n")
+        out, err = capsys.readouterr()
+        assert out == "hello\n"
+        assert err == "world\n"
+        print ("next")
+        out, err = capsys.readouterr()
+        assert out == "next\n"
+
+The ``readouterr()`` call snapshots the output so far -
+and capturing will be continued.  After the test
+function finishes the original streams will
+be restored.  Using ``capsys`` this way frees your
+test from having to care about setting/resetting
+output streams and also interacts well with pytest's
+own per-test capturing.
+
+If you want to capture on filedescriptor level you can use
+the ``capfd`` fixture which offers the exact
+same interface but allows to also capture output from
+libraries or subprocesses that directly write to operating
+system level output streams (FD1 and FD2).
+
+.. versionadded:: 3.3
+
+If the code under test writes non-textual data, you can capture this using
+the ``capsysbinary`` fixture which instead returns ``bytes`` from
+the ``readouterr`` method.  The ``capfsysbinary`` fixture is currently only
+available in python 3.
+
+
+.. versionadded:: 3.3
+
+If the code under test writes non-textual data, you can capture this using
+the ``capfdbinary`` fixture which instead returns ``bytes`` from
+the ``readouterr`` method.  The ``capfdbinary`` fixture operates on the
+filedescriptor level.
+
+
+.. versionadded:: 3.0
+
+To temporarily disable capture within a test, both ``capsys``
+and ``capfd`` have a ``disabled()`` method that can be used
+as a context manager, disabling capture inside the ``with`` block:
+
+.. code-block:: python
+
+    def test_disabling_capturing(capsys):
+        print('this output is captured')
+        with capsys.disabled():
+            print('output not captured, going directly to sys.stdout')
+        print('this output is also captured')
+
+.. include:: links.inc
diff --git a/tools/pytest/doc/en/changelog.rst b/tools/third_party/pytest/doc/en/changelog.rst
similarity index 100%
rename from tools/pytest/doc/en/changelog.rst
rename to tools/third_party/pytest/doc/en/changelog.rst
diff --git a/tools/pytest/doc/en/check_sphinx.py b/tools/third_party/pytest/doc/en/check_sphinx.py
similarity index 100%
rename from tools/pytest/doc/en/check_sphinx.py
rename to tools/third_party/pytest/doc/en/check_sphinx.py
diff --git a/tools/third_party/pytest/doc/en/conf.py b/tools/third_party/pytest/doc/en/conf.py
new file mode 100644
index 0000000..40f1e41
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/conf.py
@@ -0,0 +1,323 @@
+# -*- coding: utf-8 -*-
+#
+# pytest documentation build configuration file, created by
+# sphinx-quickstart on Fri Oct  8 17:54:28 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The full version, including alpha/beta/rc tags.
+# The short X.Y version.
+
+import os, sys
+from _pytest import __version__ as version
+release = ".".join(version.split(".")[:2])
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+autodoc_member_order = "bysource"
+todo_include_todos = 1
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
+    'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'contents'
+
+# General information about the project.
+project = u'pytest'
+copyright = u'2015, holger krekel and pytest-dev team'
+
+
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['links.inc', '_build', 'naming20.rst', 'test/*',
+    "old_*",
+    '*attic*',
+    '*/attic*',
+    'funcargs.rst',
+    'setup.rst',
+    'example/remoteinterp.rst',
+    ]
+
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = False
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+sys.path.append(os.path.abspath('_themes'))
+html_theme_path = ['_themes']
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'flask'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+html_theme_options = {
+  'index_logo': None
+}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+html_title = 'pytest documentation'
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+html_short_title = "pytest-%s" % release
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = "img/pytest1.png"
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+html_favicon = "img/pytest1favi.ico"
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+#html_sidebars = {'index': 'indexsidebar.html'}
+
+html_sidebars = {
+    'index': [
+        'sidebarintro.html',
+        'globaltoc.html',
+        'links.html',
+        'sourcelink.html',
+        'searchbox.html'
+    ],
+    '**': [
+        'globaltoc.html',
+        'relations.html',
+        'links.html',
+        'sourcelink.html',
+        'searchbox.html'
+    ]
+}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+#html_additional_pages = {'index': 'index.html'}
+
+
+# If false, no module index is generated.
+html_domain_indices = True
+
+# If false, no index is generated.
+html_use_index = False
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = False
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pytestdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('contents', 'pytest.tex', u'pytest Documentation',
+   u'holger krekel, trainer and consultant, http://merlinux.eu', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+latex_logo = 'img/pytest1.png'
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+latex_domain_indices = False
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('usage', 'pytest', u'pytest usage',
+     [u'holger krekel at merlinux eu'], 1)
+]
+
+
+# -- Options for Epub output ---------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'pytest'
+epub_author = u'holger krekel at merlinux eu'
+epub_publisher = u'holger krekel at merlinux eu'
+epub_copyright = u'2013, holger krekel et alii'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+#epub_exclude_files = []
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
+
+
+# -- Options for texinfo output ------------------------------------------------
+
+texinfo_documents = [
+  (master_doc, 'pytest', 'pytest Documentation',
+   ('Holger Krekel@*Benjamin Peterson@*Ronny Pfannschmidt@*'
+    'Floris Bruynooghe@*others'),
+   'pytest',
+   'simple powerful testing with Python',
+   'Programming',
+   1),
+]
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'python': ('http://docs.python.org/', None),
+#                       'lib': ("http://docs.python.org/2.7library/", None),
+                    }
+
+
+def setup(app):
+    #from sphinx.ext.autodoc import cut_lines
+    #app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
+    app.add_description_unit('confval', 'confval',
+                             objname='configuration value',
+                             indextemplate='pair: %s; configuration value')
diff --git a/tools/pytest/doc/en/conftest.py b/tools/third_party/pytest/doc/en/conftest.py
similarity index 100%
rename from tools/pytest/doc/en/conftest.py
rename to tools/third_party/pytest/doc/en/conftest.py
diff --git a/tools/third_party/pytest/doc/en/contact.rst b/tools/third_party/pytest/doc/en/contact.rst
new file mode 100644
index 0000000..83d4966
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/contact.rst
@@ -0,0 +1,50 @@
+
+.. _`contact channels`:
+.. _`contact`:
+
+Contact channels
+===================================
+
+- `pytest issue tracker`_ to report bugs or suggest features (for version
+  2.0 and above).
+
+- `pytest on stackoverflow.com <http://stackoverflow.com/search?q=pytest>`_ 
+  to post questions with the tag ``pytest``.  New Questions will usually 
+  be seen by pytest users or developers and answered quickly. 
+
+- `Testing In Python`_: a mailing list for Python testing tools and discussion.
+
+- `pytest-dev at python.org (mailing list)`_ pytest specific announcements and discussions.
+
+- `pytest-commit at python.org (mailing list)`_: for commits and new issues
+
+- :doc:`contribution guide <contributing>` for help on submitting pull
+  requests to GitHub.
+
+- ``#pylib`` on irc.freenode.net IRC channel for random questions.
+
+- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
+
+
+- `merlinux.eu`_ offers pytest and tox-related professional teaching and
+  consulting.
+
+.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
+.. _`old issue tracker`: http://bitbucket.org/hpk42/py-trunk/issues/
+
+.. _`merlinux.eu`: http://merlinux.eu
+
+.. _`get an account`:
+
+.. _tetamap: http://tetamap.wordpress.com
+
+.. _`@pylibcommit`: http://twitter.com/pylibcommit
+
+
+.. _`Testing in Python`: http://lists.idyll.org/listinfo/testing-in-python
+.. _FOAF: http://en.wikipedia.org/wiki/FOAF
+.. _`py-dev`:
+.. _`development mailing list`:
+.. _`pytest-dev at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-dev
+.. _`pytest-commit at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-commit
+
diff --git a/tools/third_party/pytest/doc/en/contents.rst b/tools/third_party/pytest/doc/en/contents.rst
new file mode 100644
index 0000000..7a6570e
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/contents.rst
@@ -0,0 +1,65 @@
+.. _toc:
+
+Full pytest documentation
+===========================
+
+`Download latest version as PDF <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
+
+.. `Download latest version as EPUB <http://media.readthedocs.org/epub/pytest/latest/pytest.epub>`_
+
+.. toctree::
+   :maxdepth: 2
+
+   getting-started
+   usage
+   existingtestsuite
+   assert
+   builtin
+   fixture
+   monkeypatch
+   tmpdir
+   capture
+   warnings
+   doctest
+   mark
+   skipping
+   parametrize
+   cache
+   unittest
+   nose
+   xunit_setup
+   plugins
+   writing_plugins
+   logging
+
+   goodpractices
+   pythonpath
+   customize
+   example/index
+   bash-completion
+
+   backwards-compatibility
+   historical-notes
+   license
+   contributing
+   development_guide
+   talks
+   projects
+   faq
+   contact
+
+.. only:: html
+
+   .. toctree::
+      :maxdepth: 1
+
+      announce/index
+
+.. only:: html
+
+   .. toctree::
+      :hidden:
+      :maxdepth: 1
+
+      changelog
+
diff --git a/tools/pytest/doc/en/contributing.rst b/tools/third_party/pytest/doc/en/contributing.rst
similarity index 100%
rename from tools/pytest/doc/en/contributing.rst
rename to tools/third_party/pytest/doc/en/contributing.rst
diff --git a/tools/third_party/pytest/doc/en/customize.rst b/tools/third_party/pytest/doc/en/customize.rst
new file mode 100644
index 0000000..8133704
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/customize.rst
@@ -0,0 +1,333 @@
+Configuration
+=============
+
+Command line options and configuration file settings
+-----------------------------------------------------------------
+
+You can get help on command line options and values in INI-style
+configurations files by using the general help option::
+
+    pytest -h   # prints options _and_ config file settings
+
+This will display command line and configuration file settings
+which were registered by installed plugins.
+
+.. _rootdir:
+.. _inifiles:
+
+Initialization: determining rootdir and inifile
+-----------------------------------------------
+
+.. versionadded:: 2.7
+
+pytest determines a ``rootdir`` for each test run which depends on
+the command line arguments (specified test files, paths) and on
+the existence of *ini-files*.  The determined ``rootdir`` and *ini-file* are
+printed as part of the pytest header during startup.
+
+Here's a summary what ``pytest`` uses ``rootdir`` for:
+
+* Construct *nodeids* during collection; each test is assigned
+  a unique *nodeid* which is rooted at the ``rootdir`` and takes in account full path,
+  class name, function name and parametrization (if any).
+
+* Is used by plugins as a stable location to store project/test run specific information;
+  for example, the internal :ref:`cache <cache>` plugin creates a ``.cache`` subdirectory
+  in ``rootdir`` to store its cross-test run state.
+
+Important to emphasize that ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or
+influence how modules are imported. See :ref:`pythonpath` for more details.
+
+Finding the ``rootdir``
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is the algorithm which finds the rootdir from ``args``:
+
+- determine the common ancestor directory for the specified ``args`` that are
+  recognised as paths that exist in the file system. If no such paths are
+  found, the common ancestor directory is set to the current working directory.
+
+- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor
+  directory and upwards.  If one is matched, it becomes the ini-file and its
+  directory becomes the rootdir.
+
+- if no ini-file was found, look for ``setup.py`` upwards from the common
+  ancestor directory to determine the ``rootdir``.
+
+- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and
+  ``setup.cfg`` in each of the specified ``args`` and upwards. If one is
+  matched, it becomes the ini-file and its directory becomes the rootdir.
+
+- if no ini-file was found, use the already determined common ancestor as root
+  directory. This allows the use of pytest in structures that are not part of
+  a package and don't have any particular ini-file configuration.
+
+If no ``args`` are given, pytest collects test below the current working
+directory and also starts determining the rootdir from there.
+
+:warning: custom pytest plugin commandline arguments may include a path, as in
+    ``pytest --log-output ../../test.log args``. Then ``args`` is mandatory,
+    otherwise pytest uses the folder of test.log for rootdir determination
+    (see also `issue 1435 <https://github.com/pytest-dev/pytest/issues/1435>`_).
+    A dot ``.`` for referencing to the current working directory is also
+    possible.
+
+Note that an existing ``pytest.ini`` file will always be considered a match,
+whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a
+``[pytest]`` or ``[tool:pytest]`` section, respectively. Options from multiple ini-files candidates are never
+merged - the first one wins (``pytest.ini`` always wins, even if it does not
+contain a ``[pytest]`` section).
+
+The ``config`` object will subsequently carry these attributes:
+
+- ``config.rootdir``: the determined root directory, guaranteed to exist.
+
+- ``config.inifile``: the determined ini-file, may be ``None``.
+
+The rootdir is used a reference directory for constructing test
+addresses ("nodeids") and can be used also by plugins for storing
+per-testrun information.
+
+Example::
+
+    pytest path/to/testdir path/other/
+
+will determine the common ancestor as ``path`` and then
+check for ini-files as follows::
+
+    # first look for pytest.ini files
+    path/pytest.ini
+    path/setup.cfg  # must also contain [tool:pytest] section to match
+    path/tox.ini    # must also contain [pytest] section to match
+    pytest.ini
+    ... # all the way down to the root
+
+    # now look for setup.py
+    path/setup.py
+    setup.py
+    ... # all the way down to the root
+
+
+.. _`how to change command line options defaults`:
+.. _`adding default options`:
+
+
+
+How to change command line options defaults
+------------------------------------------------
+
+It can be tedious to type the same series of command line options
+every time you use ``pytest``.  For example, if you always want to see
+detailed info on skipped and xfailed tests, as well as have terser "dot"
+progress output, you can write it into a configuration file:
+
+.. code-block:: ini
+
+    # content of pytest.ini
+    # (or tox.ini or setup.cfg)
+    [pytest]
+    addopts = -ra -q
+
+Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
+line options while the environment is in use::
+
+    export PYTEST_ADDOPTS="-v"
+
+Here's how the command-line is built in the presence of ``addopts`` or the environment variable::
+
+    <pytest.ini:addopts> $PYTEST_ADDOTPS <extra command-line arguments>
+
+So if the user executes in the command-line::
+
+    pytest -m slow
+
+The actual command line executed is::
+
+    pytest -ra -q -v -m slow
+
+Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example
+above will show verbose output because ``-v`` overwrites ``-q``.
+
+
+Builtin configuration file options
+----------------------------------------------
+
+.. confval:: minversion
+
+   Specifies a minimal pytest version required for running tests.
+
+        minversion = 2.1  # will fail if we run with pytest-2.0
+
+.. confval:: addopts
+
+   Add the specified ``OPTS`` to the set of command line arguments as if they
+   had been specified by the user. Example: if you have this ini file content:
+
+   .. code-block:: ini
+
+        [pytest]
+        addopts = --maxfail=2 -rf  # exit after 2 failures, report fail info
+
+   issuing ``pytest test_hello.py`` actually means::
+
+        pytest --maxfail=2 -rf test_hello.py
+
+   Default is to add no options.
+
+.. confval:: norecursedirs
+
+   Set the directory basename patterns to avoid when recursing
+   for test discovery.  The individual (fnmatch-style) patterns are
+   applied to the basename of a directory to decide if to recurse into it.
+   Pattern matching characters::
+
+        *       matches everything
+        ?       matches any single character
+        [seq]   matches any character in seq
+        [!seq]  matches any char not in seq
+
+   Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'``.
+   Setting a ``norecursedirs`` replaces the default.  Here is an example of
+   how to avoid certain directories:
+
+   .. code-block:: ini
+
+        # content of pytest.ini
+        [pytest]
+        norecursedirs = .svn _build tmp*
+
+   This would tell ``pytest`` to not look into typical subversion or
+   sphinx-build directories or into any ``tmp`` prefixed directory.  
+   
+   Additionally, ``pytest`` will attempt to intelligently identify and ignore a
+   virtualenv by the presence of an activation script.  Any directory deemed to
+   be the root of a virtual environment will not be considered during test
+   collection unless ``‑‑collect‑in‑virtualenv`` is given.  Note also that
+   ``norecursedirs`` takes precedence over ``‑‑collect‑in‑virtualenv``; e.g. if
+   you intend to run tests in a virtualenv with a base directory that matches
+   ``'.*'`` you *must* override ``norecursedirs`` in addition to using the
+   ``‑‑collect‑in‑virtualenv`` flag.
+
+.. confval:: testpaths
+
+   .. versionadded:: 2.8
+
+   Sets list of directories that should be searched for tests when
+   no specific directories, files or test ids are given in the command line when
+   executing pytest from the :ref:`rootdir <rootdir>` directory.
+   Useful when all project tests are in a known location to speed up
+   test collection and to avoid picking up undesired tests by accident.
+
+   .. code-block:: ini
+
+        # content of pytest.ini
+        [pytest]
+        testpaths = testing doc
+
+   This tells pytest to only look for tests in ``testing`` and ``doc``
+   directories when executing from the root directory.
+
+.. confval:: python_files
+
+   One or more Glob-style file patterns determining which python files
+   are considered as test modules. By default, pytest will consider
+   any file matching with ``test_*.py`` and ``*_test.py`` globs as a test
+   module.
+
+.. confval:: python_classes
+
+   One or more name prefixes or glob-style patterns determining which classes
+   are considered for test collection. By default, pytest will consider any
+   class prefixed with ``Test`` as a test collection.  Here is an example of how
+   to collect tests from classes that end in ``Suite``:
+
+   .. code-block:: ini
+
+        # content of pytest.ini
+        [pytest]
+        python_classes = *Suite
+
+   Note that ``unittest.TestCase`` derived classes are always collected
+   regardless of this option, as ``unittest``'s own collection framework is used
+   to collect those tests.
+
+.. confval:: python_functions
+
+   One or more name prefixes or glob-patterns determining which test functions
+   and methods are considered tests. By default, pytest will consider any
+   function prefixed with ``test`` as a test.  Here is an example of how
+   to collect test functions and methods that end in ``_test``:
+
+   .. code-block:: ini
+
+        # content of pytest.ini
+        [pytest]
+        python_functions = *_test
+
+   Note that this has no effect on methods that live on a ``unittest
+   .TestCase`` derived class, as ``unittest``'s own collection framework is used
+   to collect those tests.
+
+   See :ref:`change naming conventions` for more detailed examples.
+
+.. confval:: doctest_optionflags
+
+   One or more doctest flag names from the standard ``doctest`` module.
+   :doc:`See how pytest handles doctests <doctest>`.
+
+.. confval:: confcutdir
+
+   Sets a directory where search upwards for ``conftest.py`` files stops.
+   By default, pytest will stop searching for ``conftest.py`` files upwards
+   from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
+   or up to the file-system root.
+
+
+.. confval:: filterwarnings
+
+   .. versionadded:: 3.1
+
+   Sets a list of filters and actions that should be taken for matched
+   warnings. By default all warnings emitted during the test session
+   will be displayed in a summary at the end of the test session.
+
+   .. code-block:: ini
+
+        # content of pytest.ini
+        [pytest]
+        filterwarnings =
+            error
+            ignore::DeprecationWarning
+
+   This tells pytest to ignore deprecation warnings and turn all other warnings
+   into errors. For more information please refer to :ref:`warnings`.
+
+.. confval:: cache_dir
+
+   .. versionadded:: 3.2
+
+   Sets a directory where stores content of cache plugin. Default directory is
+   ``.cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
+   relative or absolute path. If setting relative path, then directory is created
+   relative to :ref:`rootdir <rootdir>`. Additionally path may contain environment
+   variables, that will be expanded. For more information about cache plugin
+   please refer to :ref:`cache_provider`.
+
+
+.. confval:: console_output_style
+
+   .. versionadded:: 3.3
+
+   Sets the console output style while running tests:
+
+   * ``classic``: classic pytest output.
+   * ``progress``: like classic pytest output, but with a progress indicator.
+
+   The default is ``progress``, but you can fallback to ``classic`` if you prefer or
+   the new mode is causing unexpected problems:
+
+   .. code-block:: ini
+
+        # content of pytest.ini
+        [pytest]
+        console_output_style = classic
diff --git a/tools/third_party/pytest/doc/en/development_guide.rst b/tools/third_party/pytest/doc/en/development_guide.rst
new file mode 100644
index 0000000..465e97d
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/development_guide.rst
@@ -0,0 +1,108 @@
+=================
+Development Guide
+=================
+
+Some general guidelines regarding development in pytest for core maintainers and general contributors. Nothing here
+is set in stone and can't be changed, feel free to suggest improvements or changes in the workflow.
+
+
+Code Style
+----------
+
+* `PEP-8 <https://www.python.org/dev/peps/pep-0008>`_
+* `flake8 <https://pypi.python.org/pypi/flake8>`_ for quality checks
+* `invoke <http://www.pyinvoke.org/>`_ to automate development tasks
+
+
+Branches
+--------
+
+We have two long term branches:
+
+* ``master``: contains the code for the next bugfix release.
+* ``features``: contains the code with new features for the next minor release.
+
+The official repository usually does not contain topic branches, developers and contributors should create topic
+branches in their own forks.
+
+Exceptions can be made for cases where more than one contributor is working on the same
+topic or where it makes sense to use some automatic capability of the main repository, such as automatic docs from
+`readthedocs <readthedocs.org>`_ for a branch dealing with documentation refactoring.
+
+Issues
+------
+
+Any question, feature, bug or proposal is welcome as an issue. Users are encouraged to use them whenever they need.
+
+GitHub issues should use labels to categorize them. Labels should be created sporadically, to fill a niche; we should
+avoid creating labels just for the sake of creating them.
+
+Here is a list of labels and a brief description mentioning their intent.
+
+
+**Type**
+
+* ``type: backward compatibility``: issue that will cause problems with old pytest versions.
+* ``type: bug``: problem that needs to be addressed.
+* ``type: deprecation``: feature that will be deprecated in the future.
+* ``type: docs``: documentation missing or needing clarification.
+* ``type: enhancement``: new feature or API change, should be merged into ``features``.
+* ``type: feature-branch``: new feature or API change, should be merged into ``features``.
+* ``type: infrastructure``: improvement to development/releases/CI structure.
+* ``type: performance``: performance or memory problem/improvement.
+* ``type: proposal``: proposal for a new feature, often to gather opinions or design the API around the new feature.
+* ``type: question``: question regarding usage, installation, internals or how to test something.
+* ``type: refactoring``: internal improvements to the code.
+* ``type: regression``: indicates a problem that was introduced in a release which was working previously.
+
+**Status**
+
+* ``status: critical``: grave problem or usability issue that affects lots of users.
+* ``status: easy``: easy issue that is friendly to new contributors.
+* ``status: help wanted``: core developers need help from experts on this topic.
+* ``status: needs information``: reporter needs to provide more information; can be closed after 2 or more weeks of inactivity.
+
+**Topic**
+
+* ``topic: collection``
+* ``topic: fixtures``
+* ``topic: parametrize``
+* ``topic: reporting``
+* ``topic: selection``
+* ``topic: tracebacks``
+
+**Plugin (internal or external)**
+
+* ``plugin: cache``
+* ``plugin: capture``
+* ``plugin: doctests``
+* ``plugin: junitxml``
+* ``plugin: monkeypatch``
+* ``plugin: nose``
+* ``plugin: pastebin``
+* ``plugin: pytester``
+* ``plugin: tmpdir``
+* ``plugin: unittest``
+* ``plugin: warnings``
+* ``plugin: xdist``
+
+
+**OS**
+
+Issues specific to a single operating system. Do not use as a means to indicate where an issue originated from, only
+for problems that happen **only** in that system.
+
+* ``os: linux``
+* ``os: mac``
+* ``os: windows``
+
+**Temporary**
+
+Used to classify issues for limited time, to help find issues related in events for example.
+They should be removed after they are no longer relevant.
+
+* ``temporary: EP2017 sprint``:
+* ``temporary: sprint-candidate``:
+
+
+.. include:: ../../HOWTORELEASE.rst
diff --git a/tools/third_party/pytest/doc/en/doctest.rst b/tools/third_party/pytest/doc/en/doctest.rst
new file mode 100644
index 0000000..4c5a878
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/doctest.rst
@@ -0,0 +1,165 @@
+
+Doctest integration for modules and test files
+=========================================================
+
+By default all files matching the ``test*.txt`` pattern will
+be run through the python standard ``doctest`` module.  You
+can change the pattern by issuing::
+
+    pytest --doctest-glob='*.rst'
+
+on the command line. Since version ``2.9``, ``--doctest-glob``
+can be given multiple times in the command-line.
+
+.. versionadded:: 3.1
+
+    You can specify the encoding that will be used for those doctest files
+    using the ``doctest_encoding`` ini option:
+
+    .. code-block:: ini
+
+        # content of pytest.ini
+        [pytest]
+        doctest_encoding = latin1
+
+    The default encoding is UTF-8.
+
+You can also trigger running of doctests
+from docstrings in all python modules (including regular
+python test modules)::
+
+    pytest --doctest-modules
+
+You can make these changes permanent in your project by
+putting them into a pytest.ini file like this:
+
+.. code-block:: ini
+
+    # content of pytest.ini
+    [pytest]
+    addopts = --doctest-modules
+
+If you then have a text file like this::
+
+    # content of example.rst
+
+    hello this is a doctest
+    >>> x = 3
+    >>> x
+    3
+
+and another like this::
+
+    # content of mymodule.py
+    def something():
+        """ a doctest in a docstring
+        >>> something()
+        42
+        """
+        return 42
+
+then you can just invoke ``pytest`` without command line options::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
+    collected 1 item
+    
+    mymodule.py .                                                        [100%]
+    
+    ========================= 1 passed in 0.12 seconds =========================
+
+It is possible to use fixtures using the ``getfixture`` helper::
+
+    # content of example.rst
+    >>> tmp = getfixture('tmpdir')
+    >>> ...
+    >>>
+
+Also, :ref:`usefixtures` and :ref:`autouse` fixtures are supported
+when executing text doctest files.
+
+The standard ``doctest`` module provides some setting flags to configure the
+strictness of doctest tests. In pytest You can enable those flags those flags
+using the configuration file. To make pytest ignore trailing whitespaces and
+ignore lengthy exception stack traces you can just write:
+
+.. code-block:: ini
+
+    [pytest]
+    doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
+
+pytest also introduces new options to allow doctests to run in Python 2 and
+Python 3 unchanged:
+
+* ``ALLOW_UNICODE``: when enabled, the ``u`` prefix is stripped from unicode
+  strings in expected doctest output.
+
+* ``ALLOW_BYTES``: when enabled, the ``b`` prefix is stripped from byte strings
+  in expected doctest output.
+
+As with any other option flag, these flags can be enabled in ``pytest.ini`` using
+the ``doctest_optionflags`` ini option:
+
+.. code-block:: ini
+
+    [pytest]
+    doctest_optionflags = ALLOW_UNICODE ALLOW_BYTES
+
+
+Alternatively, it can be enabled by an inline comment in the doc test
+itself::
+
+    # content of example.rst
+    >>> get_unicode_greeting()  # doctest: +ALLOW_UNICODE
+    'Hello'
+
+
+The 'doctest_namespace' fixture
+-------------------------------
+
+.. versionadded:: 3.0
+
+The ``doctest_namespace`` fixture can be used to inject items into the
+namespace in which your doctests run. It is intended to be used within
+your own fixtures to provide the tests that use them with context.
+
+``doctest_namespace`` is a standard ``dict`` object into which you
+place the objects you want to appear in the doctest namespace::
+
+    # content of conftest.py
+    import numpy
+    @pytest.fixture(autouse=True)
+    def add_np(doctest_namespace):
+        doctest_namespace['np'] = numpy
+
+which can then be used in your doctests directly::
+
+    # content of numpy.py
+    def arange():
+        """
+        >>> a = np.arange(10)
+        >>> len(a)
+        10
+        """
+        pass
+
+
+Output format
+-------------
+
+.. versionadded:: 3.0
+
+You can change the diff output format on failure for your doctests
+by using one of standard doctest modules format in options
+(see :data:`python:doctest.REPORT_UDIFF`, :data:`python:doctest.REPORT_CDIFF`,
+:data:`python:doctest.REPORT_NDIFF`, :data:`python:doctest.REPORT_ONLY_FIRST_FAILURE`)::
+
+    pytest --doctest-modules --doctest-report none
+    pytest --doctest-modules --doctest-report udiff
+    pytest --doctest-modules --doctest-report cdiff
+    pytest --doctest-modules --doctest-report ndiff
+    pytest --doctest-modules --doctest-report only_first_failure
+
+
diff --git a/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py b/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py
new file mode 100644
index 0000000..d31fba2
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py
@@ -0,0 +1,238 @@
+from pytest import raises
+import _pytest._code
+import py
+
+def otherfunc(a,b):
+    assert a==b
+
+def somefunc(x,y):
+    otherfunc(x,y)
+
+def otherfunc_multi(a,b):
+    assert (a ==
+            b)
+
+def test_generative(param1, param2):
+    assert param1 * 2 < param2
+
+def pytest_generate_tests(metafunc):
+    if 'param1' in metafunc.fixturenames:
+        metafunc.addcall(funcargs=dict(param1=3, param2=6))
+
+class TestFailing(object):
+    def test_simple(self):
+        def f():
+            return 42
+        def g():
+            return 43
+
+        assert f() == g()
+
+    def test_simple_multiline(self):
+        otherfunc_multi(
+                  42,
+                  6*9)
+
+    def test_not(self):
+        def f():
+            return 42
+        assert not f()
+
+class TestSpecialisedExplanations(object):
+    def test_eq_text(self):
+        assert 'spam' == 'eggs'
+
+    def test_eq_similar_text(self):
+        assert 'foo 1 bar' == 'foo 2 bar'
+
+    def test_eq_multiline_text(self):
+        assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
+
+    def test_eq_long_text(self):
+        a = '1'*100 + 'a' + '2'*100
+        b = '1'*100 + 'b' + '2'*100
+        assert a == b
+
+    def test_eq_long_text_multiline(self):
+        a = '1\n'*100 + 'a' + '2\n'*100
+        b = '1\n'*100 + 'b' + '2\n'*100
+        assert a == b
+
+    def test_eq_list(self):
+        assert [0, 1, 2] == [0, 1, 3]
+
+    def test_eq_list_long(self):
+        a = [0]*100 + [1] + [3]*100
+        b = [0]*100 + [2] + [3]*100
+        assert a == b
+
+    def test_eq_dict(self):
+        assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
+
+    def test_eq_set(self):
+        assert set([0, 10, 11, 12]) == set([0, 20, 21])
+
+    def test_eq_longer_list(self):
+        assert [1,2] == [1,2,3]
+
+    def test_in_list(self):
+        assert 1 in [0, 2, 3, 4, 5]
+
+    def test_not_in_text_multiline(self):
+        text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
+        assert 'foo' not in text
+
+    def test_not_in_text_single(self):
+        text = 'single foo line'
+        assert 'foo' not in text
+
+    def test_not_in_text_single_long(self):
+        text = 'head ' * 50 + 'foo ' + 'tail ' * 20
+        assert 'foo' not in text
+
+    def test_not_in_text_single_long_term(self):
+        text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
+        assert 'f'*70 not in text
+
+
+def test_attribute():
+    class Foo(object):
+        b = 1
+    i = Foo()
+    assert i.b == 2
+
+
+def test_attribute_instance():
+    class Foo(object):
+        b = 1
+    assert Foo().b == 2
+
+
+def test_attribute_failure():
+    class Foo(object):
+        def _get_b(self):
+            raise Exception('Failed to get attrib')
+        b = property(_get_b)
+    i = Foo()
+    assert i.b == 2
+
+
+def test_attribute_multiple():
+    class Foo(object):
+        b = 1
+    class Bar(object):
+        b = 2
+    assert Foo().b == Bar().b
+
+
+def globf(x):
+    return x+1
+
+class TestRaises(object):
+    def test_raises(self):
+        s = 'qwe'
+        raises(TypeError, "int(s)")
+
+    def test_raises_doesnt(self):
+        raises(IOError, "int('3')")
+
+    def test_raise(self):
+        raise ValueError("demo error")
+
+    def test_tupleerror(self):
+        a,b = [1]
+
+    def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
+        l = [1,2,3]
+        print ("l is %r" % l)
+        a,b = l.pop()
+
+    def test_some_error(self):
+        if namenotexi:
+            pass
+
+    def func1(self):
+        assert 41 == 42
+
+
+# thanks to Matthew Scott for this test
+def test_dynamic_compile_shows_nicely():
+    src = 'def foo():\n assert 1 == 0\n'
+    name = 'abc-123'
+    module = py.std.imp.new_module(name)
+    code = _pytest._code.compile(src, name, 'exec')
+    py.builtin.exec_(code, module.__dict__)
+    py.std.sys.modules[name] = module
+    module.foo()
+
+
+
+class TestMoreErrors(object):
+    def test_complex_error(self):
+        def f():
+            return 44
+        def g():
+            return 43
+        somefunc(f(), g())
+
+    def test_z1_unpack_error(self):
+        l = []
+        a,b  = l
+
+    def test_z2_type_error(self):
+        l = 3
+        a,b  = l
+
+    def test_startswith(self):
+        s = "123"
+        g = "456"
+        assert s.startswith(g)
+
+    def test_startswith_nested(self):
+        def f():
+            return "123"
+        def g():
+            return "456"
+        assert f().startswith(g())
+
+    def test_global_func(self):
+        assert isinstance(globf(42), float)
+
+    def test_instance(self):
+        self.x = 6*7
+        assert self.x != 42
+
+    def test_compare(self):
+        assert globf(10) < 5
+
+    def test_try_finally(self):
+        x = 1
+        try:
+            assert x == 0
+        finally:
+            x = 0
+
+
+class TestCustomAssertMsg(object):
+
+    def test_single_line(self):
+        class A(object):
+            a = 1
+        b = 2
+        assert A.a == b, "A.a appears not to be b"
+
+    def test_multiline(self):
+        class A(object):
+            a = 1
+        b = 2
+        assert A.a == b, "A.a appears not to be b\n" \
+            "or does not appear to be b\none of those"
+
+    def test_custom_repr(self):
+        class JSON(object):
+            a = 1
+            def __repr__(self):
+                return "This is JSON\n{\n  'foo': 'bar'\n}"
+        a = JSON()
+        b = 2
+        assert a.a == b, a
diff --git a/tools/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py b/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py
similarity index 100%
rename from tools/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py
rename to tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py
diff --git a/tools/pytest/doc/en/example/assertion/global_testmodule_config/test_hello.py b/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello.py
similarity index 100%
rename from tools/pytest/doc/en/example/assertion/global_testmodule_config/test_hello.py
rename to tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello.py
diff --git a/tools/pytest/doc/en/example/assertion/test_failures.py b/tools/third_party/pytest/doc/en/example/assertion/test_failures.py
similarity index 100%
rename from tools/pytest/doc/en/example/assertion/test_failures.py
rename to tools/third_party/pytest/doc/en/example/assertion/test_failures.py
diff --git a/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py b/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py
new file mode 100644
index 0000000..100effa
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py
@@ -0,0 +1,42 @@
+def setup_module(module):
+    module.TestStateFullThing.classcount = 0
+
+class TestStateFullThing(object):
+    def setup_class(cls):
+        cls.classcount += 1
+
+    def teardown_class(cls):
+        cls.classcount -= 1
+
+    def setup_method(self, method):
+        self.id = eval(method.__name__[5:])
+
+    def test_42(self):
+        assert self.classcount == 1
+        assert self.id == 42
+
+    def test_23(self):
+        assert self.classcount == 1
+        assert self.id == 23
+
+def teardown_module(module):
+    assert module.TestStateFullThing.classcount == 0
+
+""" For this example the control flow happens as follows::
+    import test_setup_flow_example
+    setup_module(test_setup_flow_example)
+       setup_class(TestStateFullThing)
+           instance = TestStateFullThing()
+           setup_method(instance, instance.test_42)
+              instance.test_42()
+           setup_method(instance, instance.test_23)
+              instance.test_23()
+       teardown_class(TestStateFullThing)
+    teardown_module(test_setup_flow_example)
+
+Note that ``setup_class(TestStateFullThing)`` is called and not
+``TestStateFullThing.setup_class()`` which would require you
+to insert ``setup_class = classmethod(setup_class)`` to make
+your setup function callable.
+"""
+
diff --git a/tools/third_party/pytest/doc/en/example/attic.rst b/tools/third_party/pytest/doc/en/example/attic.rst
new file mode 100644
index 0000000..9e124a5
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/attic.rst
@@ -0,0 +1,79 @@
+
+.. _`accept example`:
+
+example: specifying and selecting acceptance tests
+--------------------------------------------------------------
+
+.. sourcecode:: python
+
+    # ./conftest.py
+    def pytest_option(parser):
+        group = parser.getgroup("myproject")
+        group.addoption("-A", dest="acceptance", action="store_true",
+            help="run (slow) acceptance tests")
+
+    def pytest_funcarg__accept(request):
+        return AcceptFixture(request)
+
+    class AcceptFixture(object):
+        def __init__(self, request):
+            if not request.config.getoption('acceptance'):
+                pytest.skip("specify -A to run acceptance tests")
+            self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
+
+        def run(self, cmd):
+            """ called by test code to execute an acceptance test. """
+            self.tmpdir.chdir()
+            return py.process.cmdexec(cmd)
+
+
+and the actual test function example:
+
+.. sourcecode:: python
+
+    def test_some_acceptance_aspect(accept):
+        accept.tmpdir.mkdir("somesub")
+        result = accept.run("ls -la")
+        assert "somesub" in result
+
+If you run this test without specifying a command line option
+the test will get skipped with an appropriate message. Otherwise
+you can start to add convenience and test support methods
+to your AcceptFixture and drive running of tools or
+applications and provide ways to do assertions about
+the output.
+
+.. _`decorate a funcarg`:
+
+example: decorating a funcarg in a test module
+--------------------------------------------------------------
+
+For larger scale setups it's sometimes useful to decorate
+a funcarg just for a particular test module.  We can
+extend the `accept example`_ by putting this in our test module:
+
+.. sourcecode:: python
+
+    def pytest_funcarg__accept(request):
+        # call the next factory (living in our conftest.py)
+        arg = request.getfuncargvalue("accept")
+        # create a special layout in our tempdir
+        arg.tmpdir.mkdir("special")
+        return arg
+
+    class TestSpecialAcceptance(object):
+        def test_sometest(self, accept):
+            assert accept.tmpdir.join("special").check()
+
+Our module level factory will be invoked first and it can
+ask its request object to call the next factory and then
+decorate its result.  This mechanism allows us to stay
+ignorant of how/where the function argument is provided -
+in our example from a `conftest plugin`_.
+
+sidenote: the temporary directory used here are instances of
+the `py.path.local`_ class which provides many of the os.path
+methods in a convenient way.
+
+.. _`py.path.local`: ../path.html#local
+.. _`conftest plugin`: customize.html#conftestplugin
diff --git a/tools/pytest/doc/en/example/conftest.py b/tools/third_party/pytest/doc/en/example/conftest.py
similarity index 100%
rename from tools/pytest/doc/en/example/conftest.py
rename to tools/third_party/pytest/doc/en/example/conftest.py
diff --git a/tools/third_party/pytest/doc/en/example/costlysetup/conftest.py b/tools/third_party/pytest/doc/en/example/costlysetup/conftest.py
new file mode 100644
index 0000000..ea3c1cf
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/costlysetup/conftest.py
@@ -0,0 +1,18 @@
+
+import pytest
+
+@pytest.fixture("session")
+def setup(request):
+    setup = CostlySetup()
+    yield setup
+    setup.finalize()
+
+class CostlySetup(object):
+    def __init__(self):
+        import time
+        print ("performing costly setup")
+        time.sleep(5)
+        self.timecostly = 1
+
+    def finalize(self):
+        del self.timecostly
diff --git a/tools/pytest/doc/en/example/costlysetup/sub1/__init__.py b/tools/third_party/pytest/doc/en/example/costlysetup/sub1/__init__.py
similarity index 100%
rename from tools/pytest/doc/en/example/costlysetup/sub1/__init__.py
rename to tools/third_party/pytest/doc/en/example/costlysetup/sub1/__init__.py
diff --git a/tools/pytest/doc/en/example/costlysetup/sub1/test_quick.py b/tools/third_party/pytest/doc/en/example/costlysetup/sub1/test_quick.py
similarity index 100%
rename from tools/pytest/doc/en/example/costlysetup/sub1/test_quick.py
rename to tools/third_party/pytest/doc/en/example/costlysetup/sub1/test_quick.py
diff --git a/tools/pytest/doc/en/example/costlysetup/sub2/__init__.py b/tools/third_party/pytest/doc/en/example/costlysetup/sub2/__init__.py
similarity index 100%
rename from tools/pytest/doc/en/example/costlysetup/sub2/__init__.py
rename to tools/third_party/pytest/doc/en/example/costlysetup/sub2/__init__.py
diff --git a/tools/pytest/doc/en/example/costlysetup/sub2/test_two.py b/tools/third_party/pytest/doc/en/example/costlysetup/sub2/test_two.py
similarity index 100%
rename from tools/pytest/doc/en/example/costlysetup/sub2/test_two.py
rename to tools/third_party/pytest/doc/en/example/costlysetup/sub2/test_two.py
diff --git a/tools/third_party/pytest/doc/en/example/index.rst b/tools/third_party/pytest/doc/en/example/index.rst
new file mode 100644
index 0000000..f63cb82
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/index.rst
@@ -0,0 +1,34 @@
+
+.. _examples:
+
+Examples and customization tricks
+=================================
+
+Here is a (growing) list of examples. :ref:`Contact <contact>` us if you
+need more examples or have questions. Also take a look at the
+:ref:`comprehensive documentation <toc>` which contains many example
+snippets as well.  Also, `pytest on stackoverflow.com
+<http://stackoverflow.com/search?q=pytest>`_ often comes with example
+answers.
+
+For basic examples, see
+
+- :doc:`../getting-started` for basic introductory examples
+- :ref:`assert` for basic assertion examples
+- :ref:`fixtures` for basic fixture/setup examples
+- :ref:`parametrize` for basic test function parametrization
+- :doc:`../unittest` for basic unittest integration
+- :doc:`../nose` for basic nosetests integration
+
+The following examples aim at various use cases you might encounter.
+
+.. toctree::
+   :maxdepth: 2
+
+   reportingdemo
+   simple
+   parametrize
+   markers
+   special
+   pythoncollection
+   nonpython
diff --git a/tools/third_party/pytest/doc/en/example/markers.rst b/tools/third_party/pytest/doc/en/example/markers.rst
new file mode 100644
index 0000000..43c20d5
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/markers.rst
@@ -0,0 +1,642 @@
+
+.. _`mark examples`:
+
+Working with custom markers
+=================================================
+
+Here are some example using the :ref:`mark` mechanism.
+
+Marking test functions and selecting them for a run
+----------------------------------------------------
+
+You can "mark" a test function with custom metadata like this::
+
+    # content of test_server.py
+
+    import pytest
+    @pytest.mark.webtest
+    def test_send_http():
+        pass # perform some webtest test for your app
+    def test_something_quick():
+        pass
+    def test_another():
+        pass
+    class TestClass(object):
+        def test_method(self):
+            pass
+
+.. versionadded:: 2.2
+
+You can then restrict a test run to only run tests marked with ``webtest``::
+
+    $ pytest -v -m webtest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 4 items
+    
+    test_server.py::test_send_http PASSED                                [100%]
+    
+    ============================ 3 tests deselected ============================
+    ================== 1 passed, 3 deselected in 0.12 seconds ==================
+
+Or the inverse, running all tests except the webtest ones::
+
+    $ pytest -v -m "not webtest"
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 4 items
+    
+    test_server.py::test_something_quick PASSED                          [ 33%]
+    test_server.py::test_another PASSED                                  [ 66%]
+    test_server.py::TestClass::test_method PASSED                        [100%]
+    
+    ============================ 1 tests deselected ============================
+    ================== 3 passed, 1 deselected in 0.12 seconds ==================
+
+Selecting tests based on their node ID
+--------------------------------------
+
+You can provide one or more :ref:`node IDs <node-id>` as positional
+arguments to select only specified tests. This makes it easy to select
+tests based on their module, class, method, or function name::
+
+    $ pytest -v test_server.py::TestClass::test_method
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 1 item
+    
+    test_server.py::TestClass::test_method PASSED                        [100%]
+    
+    ========================= 1 passed in 0.12 seconds =========================
+
+You can also select on the class::
+
+    $ pytest -v test_server.py::TestClass
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 1 item
+    
+    test_server.py::TestClass::test_method PASSED                        [100%]
+    
+    ========================= 1 passed in 0.12 seconds =========================
+
+Or select multiple nodes::
+
+  $ pytest -v test_server.py::TestClass test_server.py::test_send_http
+  =========================== test session starts ============================
+  platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+  cachedir: .cache
+  rootdir: $REGENDOC_TMPDIR, inifile:
+  collecting ... collected 2 items
+  
+  test_server.py::TestClass::test_method PASSED                        [ 50%]
+  test_server.py::test_send_http PASSED                                [100%]
+  
+  ========================= 2 passed in 0.12 seconds =========================
+
+.. _node-id:
+
+.. note::
+
+    Node IDs are of the form ``module.py::class::method`` or
+    ``module.py::function``.  Node IDs control which tests are
+    collected, so ``module.py::class`` will select all test methods
+    on the class.  Nodes are also created for each parameter of a
+    parametrized fixture or test, so selecting a parametrized test
+    must include the parameter value, e.g.
+    ``module.py::function[param]``.
+
+    Node IDs for failing tests are displayed in the test summary info
+    when running pytest with the ``-rf`` option.  You can also
+    construct Node IDs from the output of ``pytest --collectonly``.
+
+Using ``-k expr`` to select tests based on their name
+-------------------------------------------------------
+
+.. versionadded: 2.0/2.3.4
+
+You can use the ``-k`` command line option to specify an expression
+which implements a substring match on the test names instead of the
+exact match on markers that ``-m`` provides.  This makes it easy to
+select tests based on their names::
+
+    $ pytest -v -k http  # running with the above defined example module
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 4 items
+    
+    test_server.py::test_send_http PASSED                                [100%]
+    
+    ============================ 3 tests deselected ============================
+    ================== 1 passed, 3 deselected in 0.12 seconds ==================
+
+And you can also run all tests except the ones that match the keyword::
+
+    $ pytest -k "not send_http" -v
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 4 items
+    
+    test_server.py::test_something_quick PASSED                          [ 33%]
+    test_server.py::test_another PASSED                                  [ 66%]
+    test_server.py::TestClass::test_method PASSED                        [100%]
+    
+    ============================ 1 tests deselected ============================
+    ================== 3 passed, 1 deselected in 0.12 seconds ==================
+
+Or to select "http" and "quick" tests::
+
+    $ pytest -k "http or quick" -v
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 4 items
+    
+    test_server.py::test_send_http PASSED                                [ 50%]
+    test_server.py::test_something_quick PASSED                          [100%]
+    
+    ============================ 2 tests deselected ============================
+    ================== 2 passed, 2 deselected in 0.12 seconds ==================
+
+.. note::
+
+    If you are using expressions such as ``"X and Y"`` then both ``X`` and ``Y``
+    need to be simple non-keyword names. For example, ``"pass"`` or ``"from"``
+    will result in SyntaxErrors because ``"-k"`` evaluates the expression using
+    Python's `eval`_ function.
+
+.. _`eval`: https://docs.python.org/3.6/library/functions.html#eval
+
+
+    However, if the ``"-k"`` argument is a simple string, no such restrictions
+    apply. Also ``"-k 'not STRING'"`` has no restrictions.  You can also
+    specify numbers like ``"-k 1.3"`` to match tests which are parametrized
+    with the float ``"1.3"``.
+
+Registering markers
+-------------------------------------
+
+.. versionadded:: 2.2
+
+.. ini-syntax for custom markers:
+
+Registering markers for your test suite is simple::
+
+    # content of pytest.ini
+    [pytest]
+    markers =
+        webtest: mark a test as a webtest.
+
+You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers::
+
+    $ pytest --markers
+    @pytest.mark.webtest: mark a test as a webtest.
+    
+    @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
+    
+    @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
+    
+    @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
+    
+    @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
+    
+    @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures 
+    
+    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
+    
+    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
+    
+
+For an example on how to add and work with markers from a plugin, see
+:ref:`adding a custom marker from a plugin`.
+
+.. note::
+
+    It is recommended to explicitly register markers so that:
+
+    * There is one place in your test suite defining your markers
+
+    * Asking for existing markers via ``pytest --markers`` gives good output
+
+    * Typos in function markers are treated as an error if you use
+      the ``--strict`` option. 
+
+.. _`scoped-marking`:
+
+Marking whole classes or modules
+----------------------------------------------------
+
+You may use ``pytest.mark`` decorators with classes to apply markers to all of
+its test methods::
+
+    # content of test_mark_classlevel.py
+    import pytest
+    @pytest.mark.webtest
+    class TestClass(object):
+        def test_startup(self):
+            pass
+        def test_startup_and_more(self):
+            pass
+
+This is equivalent to directly applying the decorator to the
+two test functions.
+
+To remain backward-compatible with Python 2.4 you can also set a
+``pytestmark`` attribute on a TestClass like this::
+
+    import pytest
+
+    class TestClass(object):
+        pytestmark = pytest.mark.webtest
+
+or if you need to use multiple markers you can use a list::
+
+    import pytest
+
+    class TestClass(object):
+        pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
+
+You can also set a module level marker::
+
+    import pytest
+    pytestmark = pytest.mark.webtest
+
+in which case it will be applied to all functions and
+methods defined in the module.
+
+.. _`marking individual tests when using parametrize`:
+
+Marking individual tests when using parametrize
+-----------------------------------------------
+
+When using parametrize, applying a mark will make it apply
+to each individual test. However it is also possible to
+apply a marker to an individual test instance::
+
+    import pytest
+
+    @pytest.mark.foo
+    @pytest.mark.parametrize(("n", "expected"), [
+        (1, 2),
+        pytest.mark.bar((1, 3)),
+        (2, 3),
+    ])
+    def test_increment(n, expected):
+         assert n + 1 == expected
+
+In this example the mark "foo" will apply to each of the three
+tests, whereas the "bar" mark is only applied to the second test.
+Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with parametrize`.
+
+.. note::
+
+    If the data you are parametrizing happen to be single callables, you need to be careful
+    when marking these items. `pytest.mark.xfail(my_func)` won't work because it's also the
+    signature of a function being decorated. To resolve this ambiguity, you need to pass a
+    reason argument:
+    `pytest.mark.xfail(func_bar, reason="Issue#7")`.
+
+
+.. _`adding a custom marker from a plugin`:
+
+Custom marker and command line option to control test runs
+----------------------------------------------------------
+
+.. regendoc:wipe
+
+Plugins can provide custom markers and implement specific behaviour
+based on it. This is a self-contained example which adds a command
+line option and a parametrized test function marker to run tests
+specifies via named environments::
+
+    # content of conftest.py
+
+    import pytest
+    def pytest_addoption(parser):
+        parser.addoption("-E", action="store", metavar="NAME",
+            help="only run tests matching the environment NAME.")
+
+    def pytest_configure(config):
+        # register an additional marker
+        config.addinivalue_line("markers",
+            "env(name): mark test to run only on named environment")
+
+    def pytest_runtest_setup(item):
+        envmarker = item.get_marker("env")
+        if envmarker is not None:
+            envname = envmarker.args[0]
+            if envname != item.config.getoption("-E"):
+                pytest.skip("test requires env %r" % envname)
+
+A test file using this local plugin::
+
+    # content of test_someenv.py
+
+    import pytest
+    @pytest.mark.env("stage1")
+    def test_basic_db_operation():
+        pass
+
+and an example invocations specifying a different environment than what
+the test needs::
+
+    $ pytest -E stage2
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_someenv.py s                                                    [100%]
+    
+    ======================== 1 skipped in 0.12 seconds =========================
+
+and here is one that specifies exactly the environment needed::
+
+    $ pytest -E stage1
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_someenv.py .                                                    [100%]
+    
+    ========================= 1 passed in 0.12 seconds =========================
+
+The ``--markers`` option always gives you a list of available markers::
+
+    $ pytest --markers
+    @pytest.mark.env(name): mark test to run only on named environment
+    
+    @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
+    
+    @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
+    
+    @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
+    
+    @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
+    
+    @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures 
+    
+    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
+    
+    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
+    
+
+.. _`passing callables to custom markers`:
+
+Passing a callable to custom markers
+--------------------------------------------
+
+.. regendoc:wipe
+
+Below is the config file that will be used in the next examples::
+
+    # content of conftest.py
+    import sys
+
+    def pytest_runtest_setup(item):
+        marker = item.get_marker('my_marker')
+        if marker is not None:
+            for info in marker:
+                print('Marker info name={} args={} kwars={}'.format(info.name, info.args, info.kwargs))
+                sys.stdout.flush()
+
+A custom marker can have its argument set, i.e. ``args`` and ``kwargs`` properties, defined by either invoking it as a callable or using ``pytest.mark.MARKER_NAME.with_args``. These two methods achieve the same effect most of the time.
+
+However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue::
+
+    # content of test_custom_marker.py
+    import pytest
+
+    def hello_world(*args, **kwargs):
+        return 'Hello World'
+
+    @pytest.mark.my_marker.with_args(hello_world)
+    def test_with_args():
+        pass
+
+The output is as follows::
+
+    $ pytest -q -s
+    Marker info name=my_marker args=(<function hello_world at 0xdeadbeef>,) kwars={}
+    .                                                                    [100%]
+    1 passed in 0.12 seconds
+
+We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
+
+
+Reading markers which were set from multiple places
+----------------------------------------------------
+
+.. versionadded: 2.2.2
+
+.. regendoc:wipe
+
+If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function.  From plugin
+code you can read over all such settings.  Example::
+
+    # content of test_mark_three_times.py
+    import pytest
+    pytestmark = pytest.mark.glob("module", x=1)
+
+    @pytest.mark.glob("class", x=2)
+    class TestClass(object):
+        @pytest.mark.glob("function", x=3)
+        def test_something(self):
+            pass
+
+Here we have the marker "glob" applied three times to the same
+test function.  From a conftest file we can read it like this::
+
+    # content of conftest.py
+    import sys
+
+    def pytest_runtest_setup(item):
+        g = item.get_marker("glob")
+        if g is not None:
+            for info in g:
+                print ("glob args=%s kwargs=%s" %(info.args, info.kwargs))
+                sys.stdout.flush()
+
+Let's run this without capturing output and see what we get::
+
+    $ pytest -q -s
+    glob args=('function',) kwargs={'x': 3}
+    glob args=('class',) kwargs={'x': 2}
+    glob args=('module',) kwargs={'x': 1}
+    .                                                                    [100%]
+    1 passed in 0.12 seconds
+
+marking platform specific tests with pytest
+--------------------------------------------------------------
+
+.. regendoc:wipe
+
+Consider you have a test suite which marks tests for particular platforms,
+namely ``pytest.mark.darwin``, ``pytest.mark.win32`` etc. and you
+also have tests that run on all platforms and have no specific
+marker.  If you now want to have a way to only run the tests
+for your particular platform, you could use the following plugin::
+
+    # content of conftest.py
+    #
+    import sys
+    import pytest
+
+    ALL = set("darwin linux win32".split())
+
+    def pytest_runtest_setup(item):
+        if isinstance(item, item.Function):
+            plat = sys.platform
+            if not item.get_marker(plat):
+                if ALL.intersection(item.keywords):
+                    pytest.skip("cannot run on platform %s" %(plat))
+
+then tests will be skipped if they were specified for a different platform.
+Let's do a little test file to show how this looks like::
+
+    # content of test_plat.py
+
+    import pytest
+
+    @pytest.mark.darwin
+    def test_if_apple_is_evil():
+        pass
+
+    @pytest.mark.linux
+    def test_if_linux_works():
+        pass
+
+    @pytest.mark.win32
+    def test_if_win32_crashes():
+        pass
+
+    def test_runs_everywhere():
+        pass
+
+then you will see two tests skipped and two executed tests as expected::
+
+    $ pytest -rs # this option reports skip reasons
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 4 items
+    
+    test_plat.py s.s.                                                    [100%]
+    ========================= short test summary info ==========================
+    SKIP [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
+    
+    =================== 2 passed, 2 skipped in 0.12 seconds ====================
+
+Note that if you specify a platform via the marker-command line option like this::
+
+    $ pytest -m linux
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 4 items
+    
+    test_plat.py .                                                       [100%]
+    
+    ============================ 3 tests deselected ============================
+    ================== 1 passed, 3 deselected in 0.12 seconds ==================
+
+then the unmarked-tests will not be run.  It is thus a way to restrict the run to the specific tests.
+
+Automatically adding markers based on test names
+--------------------------------------------------------
+
+.. regendoc:wipe
+
+If you a test suite where test function names indicate a certain
+type of test, you can implement a hook that automatically defines
+markers so that you can use the ``-m`` option with it. Let's look
+at this test module::
+
+    # content of test_module.py
+
+    def test_interface_simple():
+        assert 0
+
+    def test_interface_complex():
+        assert 0
+
+    def test_event_simple():
+        assert 0
+
+    def test_something_else():
+        assert 0
+
+We want to dynamically define two markers and can do it in a
+``conftest.py`` plugin::
+
+    # content of conftest.py
+
+    import pytest
+    def pytest_collection_modifyitems(items):
+        for item in items:
+            if "interface" in item.nodeid:
+                item.add_marker(pytest.mark.interface)
+            elif "event" in item.nodeid:
+                item.add_marker(pytest.mark.event)
+
+We can now use the ``-m option`` to select one set::
+
+  $ pytest -m interface --tb=short
+  =========================== test session starts ============================
+  platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+  rootdir: $REGENDOC_TMPDIR, inifile:
+  collected 4 items
+  
+  test_module.py FF                                                    [100%]
+  
+  ================================= FAILURES =================================
+  __________________________ test_interface_simple ___________________________
+  test_module.py:3: in test_interface_simple
+      assert 0
+  E   assert 0
+  __________________________ test_interface_complex __________________________
+  test_module.py:6: in test_interface_complex
+      assert 0
+  E   assert 0
+  ============================ 2 tests deselected ============================
+  ================== 2 failed, 2 deselected in 0.12 seconds ==================
+
+or to select both "event" and "interface" tests::
+
+  $ pytest -m "interface or event" --tb=short
+  =========================== test session starts ============================
+  platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+  rootdir: $REGENDOC_TMPDIR, inifile:
+  collected 4 items
+  
+  test_module.py FFF                                                   [100%]
+  
+  ================================= FAILURES =================================
+  __________________________ test_interface_simple ___________________________
+  test_module.py:3: in test_interface_simple
+      assert 0
+  E   assert 0
+  __________________________ test_interface_complex __________________________
+  test_module.py:6: in test_interface_complex
+      assert 0
+  E   assert 0
+  ____________________________ test_event_simple _____________________________
+  test_module.py:9: in test_event_simple
+      assert 0
+  E   assert 0
+  ============================ 1 tests deselected ============================
+  ================== 3 failed, 1 deselected in 0.12 seconds ==================
diff --git a/tools/third_party/pytest/doc/en/example/multipython.py b/tools/third_party/pytest/doc/en/example/multipython.py
new file mode 100644
index 0000000..66079be
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/multipython.py
@@ -0,0 +1,52 @@
+"""
+module containing a parametrized tests testing cross-python
+serialization via the pickle module.
+"""
+import py
+import pytest
+import _pytest._code
+
+pythonlist = ['python2.7', 'python3.4', 'python3.5']
+@pytest.fixture(params=pythonlist)
+def python1(request, tmpdir):
+    picklefile = tmpdir.join("data.pickle")
+    return Python(request.param, picklefile)
+
+@pytest.fixture(params=pythonlist)
+def python2(request, python1):
+    return Python(request.param, python1.picklefile)
+
+class Python(object):
+    def __init__(self, version, picklefile):
+        self.pythonpath = py.path.local.sysfind(version)
+        if not self.pythonpath:
+            pytest.skip("%r not found" %(version,))
+        self.picklefile = picklefile
+    def dumps(self, obj):
+        dumpfile = self.picklefile.dirpath("dump.py")
+        dumpfile.write(_pytest._code.Source("""
+            import pickle
+            f = open(%r, 'wb')
+            s = pickle.dump(%r, f, protocol=2)
+            f.close()
+        """ % (str(self.picklefile), obj)))
+        py.process.cmdexec("%s %s" %(self.pythonpath, dumpfile))
+
+    def load_and_is_true(self, expression):
+        loadfile = self.picklefile.dirpath("load.py")
+        loadfile.write(_pytest._code.Source("""
+            import pickle
+            f = open(%r, 'rb')
+            obj = pickle.load(f)
+            f.close()
+            res = eval(%r)
+            if not res:
+                raise SystemExit(1)
+        """ % (str(self.picklefile), expression)))
+        print (loadfile)
+        py.process.cmdexec("%s %s" %(self.pythonpath, loadfile))
+
+@pytest.mark.parametrize("obj", [42, {}, {1:3},])
+def test_basic_objects(python1, python2, obj):
+    python1.dumps(obj)
+    python2.load_and_is_true("obj == %s" % obj)
diff --git a/tools/third_party/pytest/doc/en/example/nonpython.rst b/tools/third_party/pytest/doc/en/example/nonpython.rst
new file mode 100644
index 0000000..cf72c72
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/nonpython.rst
@@ -0,0 +1,91 @@
+
+.. _`non-python tests`:
+
+Working with non-python tests
+====================================================
+
+.. _`yaml plugin`:
+
+A basic example for specifying tests in Yaml files
+--------------------------------------------------------------
+
+.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
+.. _`PyYAML`: http://pypi.python.org/pypi/PyYAML/
+
+Here is an example ``conftest.py`` (extracted from Ali Afshnars special purpose `pytest-yamlwsgi`_ plugin).   This ``conftest.py`` will  collect ``test*.yml`` files and will execute the yaml-formatted content as custom tests:
+
+.. include:: nonpython/conftest.py
+    :literal:
+
+You can create a simple example file:
+
+.. include:: nonpython/test_simple.yml
+    :literal:
+
+and if you installed `PyYAML`_ or a compatible YAML-parser you can
+now execute the test specification::
+
+    nonpython $ pytest test_simple.yml
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
+    collected 2 items
+    
+    test_simple.yml F.                                                   [100%]
+    
+    ================================= FAILURES =================================
+    ______________________________ usecase: hello ______________________________
+    usecase execution failed
+       spec failed: 'some': 'other'
+       no further details known at this point.
+    ==================== 1 failed, 1 passed in 0.12 seconds ====================
+
+.. regendoc:wipe
+
+You get one dot for the passing ``sub1: sub1`` check and one failure.
+Obviously in the above ``conftest.py`` you'll want to implement a more
+interesting interpretation of the yaml-values.  You can easily write
+your own domain specific testing language this way.
+
+.. note::
+
+    ``repr_failure(excinfo)`` is called for representing test failures.
+    If you create custom collection nodes you can return an error
+    representation string of your choice.  It
+    will be reported as a (red) string.
+
+``reportinfo()`` is used for representing the test location and is also
+consulted when reporting in ``verbose`` mode::
+
+    nonpython $ pytest -v
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
+    collecting ... collected 2 items
+    
+    test_simple.yml::hello FAILED                                        [ 50%]
+    test_simple.yml::ok PASSED                                           [100%]
+    
+    ================================= FAILURES =================================
+    ______________________________ usecase: hello ______________________________
+    usecase execution failed
+       spec failed: 'some': 'other'
+       no further details known at this point.
+    ==================== 1 failed, 1 passed in 0.12 seconds ====================
+
+.. regendoc:wipe
+
+While developing your custom test collection and execution it's also
+interesting to just look at the collection tree::
+
+    nonpython $ pytest --collect-only
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
+    collected 2 items
+    <YamlFile 'test_simple.yml'>
+      <YamlItem 'hello'>
+      <YamlItem 'ok'>
+    
+    ======================= no tests ran in 0.12 seconds =======================
diff --git a/tools/pytest/doc/en/example/nonpython/__init__.py b/tools/third_party/pytest/doc/en/example/nonpython/__init__.py
similarity index 100%
rename from tools/pytest/doc/en/example/nonpython/__init__.py
rename to tools/third_party/pytest/doc/en/example/nonpython/__init__.py
diff --git a/tools/third_party/pytest/doc/en/example/nonpython/conftest.py b/tools/third_party/pytest/doc/en/example/nonpython/conftest.py
new file mode 100644
index 0000000..baff300
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/nonpython/conftest.py
@@ -0,0 +1,40 @@
+# content of conftest.py
+
+import pytest
+
+def pytest_collect_file(parent, path):
+    if path.ext == ".yml" and path.basename.startswith("test"):
+        return YamlFile(path, parent)
+
+class YamlFile(pytest.File):
+    def collect(self):
+        import yaml # we need a yaml parser, e.g. PyYAML
+        raw = yaml.safe_load(self.fspath.open())
+        for name, spec in sorted(raw.items()):
+            yield YamlItem(name, self, spec)
+
+class YamlItem(pytest.Item):
+    def __init__(self, name, parent, spec):
+        super(YamlItem, self).__init__(name, parent)
+        self.spec = spec
+
+    def runtest(self):
+        for name, value in sorted(self.spec.items()):
+            # some custom test execution (dumb example follows)
+            if name != value:
+                raise YamlException(self, name, value)
+
+    def repr_failure(self, excinfo):
+        """ called when self.runtest() raises an exception. """
+        if isinstance(excinfo.value, YamlException):
+            return "\n".join([
+                "usecase execution failed",
+                "   spec failed: %r: %r" % excinfo.value.args[1:3],
+                "   no further details known at this point."
+            ])
+
+    def reportinfo(self):
+        return self.fspath, 0, "usecase: %s" % self.name
+
+class YamlException(Exception):
+    """ custom exception for error reporting. """
diff --git a/tools/pytest/doc/en/example/nonpython/test_simple.yml b/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yml
similarity index 100%
rename from tools/pytest/doc/en/example/nonpython/test_simple.yml
rename to tools/third_party/pytest/doc/en/example/nonpython/test_simple.yml
diff --git a/tools/third_party/pytest/doc/en/example/parametrize.rst b/tools/third_party/pytest/doc/en/example/parametrize.rst
new file mode 100644
index 0000000..dd01b25
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/parametrize.rst
@@ -0,0 +1,536 @@
+
+.. _paramexamples:
+
+Parametrizing tests
+=================================================
+
+.. currentmodule:: _pytest.python
+
+``pytest`` allows to easily parametrize test functions.
+For basic docs, see :ref:`parametrize-basics`.
+
+In the following we provide some examples using
+the builtin mechanisms.
+
+Generating parameters combinations, depending on command line
+----------------------------------------------------------------------------
+
+.. regendoc:wipe
+
+Let's say we want to execute a test with different computation
+parameters and the parameter range shall be determined by a command
+line argument.  Let's first write a simple (do-nothing) computation test::
+
+    # content of test_compute.py
+
+    def test_compute(param1):
+        assert param1 < 4
+
+Now we add a test configuration like this::
+
+    # content of conftest.py
+
+    def pytest_addoption(parser):
+        parser.addoption("--all", action="store_true",
+            help="run all combinations")
+
+    def pytest_generate_tests(metafunc):
+        if 'param1' in metafunc.fixturenames:
+            if metafunc.config.getoption('all'):
+                end = 5
+            else:
+                end = 2
+            metafunc.parametrize("param1", range(end))
+
+This means that we only run 2 tests if we do not pass ``--all``::
+
+    $ pytest -q test_compute.py
+    ..                                                                   [100%]
+    2 passed in 0.12 seconds
+
+We run only two computations, so we see two dots.
+let's run the full monty::
+
+    $ pytest -q --all
+    ....F                                                                [100%]
+    ================================= FAILURES =================================
+    _____________________________ test_compute[4] ______________________________
+    
+    param1 = 4
+    
+        def test_compute(param1):
+    >       assert param1 < 4
+    E       assert 4 < 4
+    
+    test_compute.py:3: AssertionError
+    1 failed, 4 passed in 0.12 seconds
+
+As expected when running the full range of ``param1`` values
+we'll get an error on the last one.
+
+
+Different options for test IDs
+------------------------------------
+
+pytest will build a string that is the test ID for each set of values in a
+parametrized test. These IDs can be used with ``-k`` to select specific cases
+to run, and they will also identify the specific case when one is failing.
+Running pytest with ``--collect-only`` will show the generated IDs.
+
+Numbers, strings, booleans and None will have their usual string representation
+used in the test ID. For other objects, pytest will make a string based on
+the argument name::
+
+    # content of test_time.py
+
+    import pytest
+
+    from datetime import datetime, timedelta
+
+    testdata = [
+        (datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
+        (datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
+    ]
+
+
+    @pytest.mark.parametrize("a,b,expected", testdata)
+    def test_timedistance_v0(a, b, expected):
+        diff = a - b
+        assert diff == expected
+
+
+    @pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
+    def test_timedistance_v1(a, b, expected):
+        diff = a - b
+        assert diff == expected
+
+
+    def idfn(val):
+        if isinstance(val, (datetime,)):
+            # note this wouldn't show any hours/minutes/seconds
+            return val.strftime('%Y%m%d')
+
+
+    @pytest.mark.parametrize("a,b,expected", testdata, ids=idfn)
+    def test_timedistance_v2(a, b, expected):
+        diff = a - b
+        assert diff == expected
+
+    @pytest.mark.parametrize("a,b,expected", [
+        pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11),
+                     timedelta(1), id='forward'),
+        pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12),
+                     timedelta(-1), id='backward'),
+    ])
+    def test_timedistance_v3(a, b, expected):
+        diff = a - b
+        assert diff == expected
+
+In ``test_timedistance_v0``, we let pytest generate the test IDs.
+
+In ``test_timedistance_v1``, we specified ``ids`` as a list of strings which were
+used as the test IDs. These are succinct, but can be a pain to maintain.
+
+In ``test_timedistance_v2``, we specified ``ids`` as a function that can generate a
+string representation to make part of the test ID. So our ``datetime`` values use the
+label generated by ``idfn``, but because we didn't generate a label for ``timedelta``
+objects, they are still using the default pytest representation::
+
+
+    $ pytest test_time.py --collect-only
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 8 items
+    <Module 'test_time.py'>
+      <Function 'test_timedistance_v0[a0-b0-expected0]'>
+      <Function 'test_timedistance_v0[a1-b1-expected1]'>
+      <Function 'test_timedistance_v1[forward]'>
+      <Function 'test_timedistance_v1[backward]'>
+      <Function 'test_timedistance_v2[20011212-20011211-expected0]'>
+      <Function 'test_timedistance_v2[20011211-20011212-expected1]'>
+      <Function 'test_timedistance_v3[forward]'>
+      <Function 'test_timedistance_v3[backward]'>
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs
+together with the actual data, instead of listing them separately.
+
+A quick port of "testscenarios"
+------------------------------------
+
+.. _`test scenarios`: http://pypi.python.org/pypi/testscenarios/
+
+Here is a quick port to run tests configured with `test scenarios`_,
+an add-on from Robert Collins for the standard unittest framework. We
+only have to work a bit to construct the correct arguments for pytest's
+:py:func:`Metafunc.parametrize`::
+
+    # content of test_scenarios.py
+
+    def pytest_generate_tests(metafunc):
+        idlist = []
+        argvalues = []
+        for scenario in metafunc.cls.scenarios:
+            idlist.append(scenario[0])
+            items = scenario[1].items()
+            argnames = [x[0] for x in items]
+            argvalues.append(([x[1] for x in items]))
+        metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
+
+    scenario1 = ('basic', {'attribute': 'value'})
+    scenario2 = ('advanced', {'attribute': 'value2'})
+
+    class TestSampleWithScenarios(object):
+        scenarios = [scenario1, scenario2]
+
+        def test_demo1(self, attribute):
+            assert isinstance(attribute, str)
+
+        def test_demo2(self, attribute):
+            assert isinstance(attribute, str)
+
+this is a fully self-contained example which you can run with::
+
+    $ pytest test_scenarios.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 4 items
+    
+    test_scenarios.py ....                                               [100%]
+    
+    ========================= 4 passed in 0.12 seconds =========================
+
+If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function::
+
+
+    $ pytest --collect-only test_scenarios.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 4 items
+    <Module 'test_scenarios.py'>
+      <Class 'TestSampleWithScenarios'>
+        <Instance '()'>
+          <Function 'test_demo1[basic]'>
+          <Function 'test_demo2[basic]'>
+          <Function 'test_demo1[advanced]'>
+          <Function 'test_demo2[advanced]'>
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+Note that we told ``metafunc.parametrize()`` that your scenario values
+should be considered class-scoped.  With pytest-2.3 this leads to a
+resource-based ordering.
+
+Deferring the setup of parametrized resources
+---------------------------------------------------
+
+.. regendoc:wipe
+
+The parametrization of test functions happens at collection
+time.  It is a good idea to setup expensive resources like DB
+connections or subprocess only when the actual test is run.
+Here is a simple example how you can achieve that, first
+the actual test requiring a ``db`` object::
+
+    # content of test_backends.py
+
+    import pytest
+    def test_db_initialized(db):
+        # a dummy test
+        if db.__class__.__name__ == "DB2":
+            pytest.fail("deliberately failing for demo purposes")
+
+We can now add a test configuration that generates two invocations of
+the ``test_db_initialized`` function and also implements a factory that
+creates a database object for the actual test invocations::
+
+    # content of conftest.py
+    import pytest
+
+    def pytest_generate_tests(metafunc):
+        if 'db' in metafunc.fixturenames:
+            metafunc.parametrize("db", ['d1', 'd2'], indirect=True)
+
+    class DB1(object):
+        "one database object"
+    class DB2(object):
+        "alternative database object"
+
+    @pytest.fixture
+    def db(request):
+        if request.param == "d1":
+            return DB1()
+        elif request.param == "d2":
+            return DB2()
+        else:
+            raise ValueError("invalid internal test config")
+
+Let's first see how it looks like at collection time::
+
+    $ pytest test_backends.py --collect-only
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    <Module 'test_backends.py'>
+      <Function 'test_db_initialized[d1]'>
+      <Function 'test_db_initialized[d2]'>
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+And then when we run the test::
+
+    $ pytest -q test_backends.py
+    .F                                                                   [100%]
+    ================================= FAILURES =================================
+    _________________________ test_db_initialized[d2] __________________________
+    
+    db = <conftest.DB2 object at 0xdeadbeef>
+    
+        def test_db_initialized(db):
+            # a dummy test
+            if db.__class__.__name__ == "DB2":
+    >           pytest.fail("deliberately failing for demo purposes")
+    E           Failed: deliberately failing for demo purposes
+    
+    test_backends.py:6: Failed
+    1 failed, 1 passed in 0.12 seconds
+
+The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed.  Our ``db`` fixture function has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase.
+
+.. regendoc:wipe
+
+Apply indirect on particular arguments
+---------------------------------------------------
+
+Very often parametrization uses more than one argument name. There is opportunity to apply ``indirect``
+parameter on particular arguments. It can be done by passing list or tuple of
+arguments' names to ``indirect``. In the example below there is a function ``test_indirect`` which uses
+two fixtures: ``x`` and ``y``. Here we give to indirect the list, which contains the name of the
+fixture ``x``. The indirect parameter will be applied to this argument only, and the value ``a``
+will be passed to respective fixture function::
+
+    # content of test_indirect_list.py
+
+    import pytest
+    @pytest.fixture(scope='function')
+    def x(request):
+        return request.param * 3
+
+    @pytest.fixture(scope='function')
+    def y(request):
+        return request.param * 2
+
+    @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
+    def test_indirect(x,y):
+        assert x == 'aaa'
+        assert y == 'b'
+
+The result of this test will be successful::
+
+    $ pytest test_indirect_list.py --collect-only
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    <Module 'test_indirect_list.py'>
+      <Function 'test_indirect[a-b]'>
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+.. regendoc:wipe
+
+Parametrizing test methods through per-class configuration
+--------------------------------------------------------------
+
+.. _`unittest parametrizer`: https://github.com/testing-cabal/unittest-ext/blob/master/params.py
+
+
+Here is an example ``pytest_generate_tests`` function implementing a
+parametrization scheme similar to Michael Foord's `unittest
+parametrizer`_ but in a lot less code::
+
+    # content of ./test_parametrize.py
+    import pytest
+
+    def pytest_generate_tests(metafunc):
+        # called once per each test function
+        funcarglist = metafunc.cls.params[metafunc.function.__name__]
+        argnames = sorted(funcarglist[0])
+        metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
+                for funcargs in funcarglist])
+
+    class TestClass(object):
+        # a map specifying multiple argument sets for a test method
+        params = {
+            'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ],
+            'test_zerodivision': [dict(a=1, b=0), ],
+        }
+
+        def test_equals(self, a, b):
+            assert a == b
+
+        def test_zerodivision(self, a, b):
+            pytest.raises(ZeroDivisionError, "a/b")
+
+Our test generator looks up a class-level definition which specifies which
+argument sets to use for each test function.  Let's run it::
+
+    $ pytest -q
+    F..                                                                  [100%]
+    ================================= FAILURES =================================
+    ________________________ TestClass.test_equals[1-2] ________________________
+    
+    self = <test_parametrize.TestClass object at 0xdeadbeef>, a = 1, b = 2
+    
+        def test_equals(self, a, b):
+    >       assert a == b
+    E       assert 1 == 2
+    
+    test_parametrize.py:18: AssertionError
+    1 failed, 2 passed in 0.12 seconds
+
+Indirect parametrization with multiple fixtures
+--------------------------------------------------------------
+
+Here is a stripped down real-life example of using parametrized
+testing for testing serialization of objects between different python
+interpreters.  We define a ``test_basic_objects`` function which
+is to be run with different sets of arguments for its three arguments:
+
+* ``python1``: first python interpreter, run to pickle-dump an object to a file
+* ``python2``: second interpreter, run to pickle-load an object from a file
+* ``obj``: object to be dumped/loaded
+
+.. literalinclude:: multipython.py
+
+Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
+
+   . $ pytest -rs -q multipython.py
+   ...........................                                          [100%]
+   27 passed in 0.12 seconds
+
+Indirect parametrization of optional implementations/imports
+--------------------------------------------------------------------
+
+If you want to compare the outcomes of several implementations of a given
+API, you can write test functions that receive the already imported implementations
+and get skipped in case the implementation is not importable/available.  Let's
+say we have a "base" implementation and the other (possibly optimized ones)
+need to provide similar results::
+
+    # content of conftest.py
+
+    import pytest
+
+    @pytest.fixture(scope="session")
+    def basemod(request):
+        return pytest.importorskip("base")
+
+    @pytest.fixture(scope="session", params=["opt1", "opt2"])
+    def optmod(request):
+        return pytest.importorskip(request.param)
+
+And then a base implementation of a simple function::
+
+    # content of base.py
+    def func1():
+        return 1
+
+And an optimized version::
+
+    # content of opt1.py
+    def func1():
+        return 1.0001
+
+And finally a little test module::
+
+    # content of test_module.py
+
+    def test_func1(basemod, optmod):
+        assert round(basemod.func1(), 3) == round(optmod.func1(), 3)
+
+
+If you run this with reporting for skips enabled::
+
+    $ pytest -rs test_module.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    
+    test_module.py .s                                                    [100%]
+    ========================= short test summary info ==========================
+    SKIP [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
+    
+    =================== 1 passed, 1 skipped in 0.12 seconds ====================
+
+You'll see that we don't have a ``opt2`` module and thus the second test run
+of our ``test_func1`` was skipped.  A few notes:
+
+- the fixture functions in the ``conftest.py`` file are "session-scoped" because we
+  don't need to import more than once
+
+- if you have multiple test functions and a skipped import, you will see
+  the ``[1]`` count increasing in the report
+
+- you can put :ref:`@pytest.mark.parametrize <@pytest.mark.parametrize>` style
+  parametrization on the test functions to parametrize input/output
+  values as well.
+
+
+Set marks or test ID for individual parametrized test
+--------------------------------------------------------------------
+
+Use ``pytest.param`` to apply marks or set test ID to individual parametrized test.
+For example::
+
+    # content of test_pytest_param_example.py
+    import pytest
+    @pytest.mark.parametrize('test_input,expected', [
+        ('3+5', 8),
+        pytest.param('1+7', 8,
+                     marks=pytest.mark.basic),
+        pytest.param('2+4', 6,
+                     marks=pytest.mark.basic,
+                     id='basic_2+4'),
+        pytest.param('6*9', 42,
+                     marks=[pytest.mark.basic, pytest.mark.xfail],
+                     id='basic_6*9'),
+    ])
+    def test_eval(test_input, expected):
+        assert eval(test_input) == expected
+    
+In this example, we have 4 parametrized tests. Except for the first test,
+we mark the rest three parametrized tests with the custom marker ``basic``,
+and for the fourth test we also use the built-in mark ``xfail`` to indicate this 
+test is expected to fail. For explicitness, we set test ids for some tests.
+
+Then run ``pytest`` with verbose mode and with only the ``basic`` marker::
+
+    pytest -v -m basic
+    ============================================ test session starts =============================================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 4 items
+
+    test_pytest_param_example.py::test_eval[1+7-8] PASSED
+    test_pytest_param_example.py::test_eval[basic_2+4] PASSED
+    test_pytest_param_example.py::test_eval[basic_6*9] xfail
+    ========================================== short test summary info ===========================================
+    XFAIL test_pytest_param_example.py::test_eval[basic_6*9]
+
+    ============================================= 1 tests deselected =============================================
+
+As the result:
+
+- Four tests were collected
+- One test was deselected because it doesn't have the ``basic`` mark.
+- Three tests with the ``basic`` mark was selected.
+- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing.
+- The test ``test_eval[basic_2+4]`` passed.
+- The test ``test_eval[basic_6*9]`` was expected to fail and did fail.
diff --git a/tools/pytest/doc/en/example/py2py3/conftest.py b/tools/third_party/pytest/doc/en/example/py2py3/conftest.py
similarity index 100%
rename from tools/pytest/doc/en/example/py2py3/conftest.py
rename to tools/third_party/pytest/doc/en/example/py2py3/conftest.py
diff --git a/tools/pytest/doc/en/example/py2py3/test_py2.py b/tools/third_party/pytest/doc/en/example/py2py3/test_py2.py
similarity index 100%
rename from tools/pytest/doc/en/example/py2py3/test_py2.py
rename to tools/third_party/pytest/doc/en/example/py2py3/test_py2.py
diff --git a/tools/pytest/doc/en/example/py2py3/test_py3.py b/tools/third_party/pytest/doc/en/example/py2py3/test_py3.py
similarity index 100%
rename from tools/pytest/doc/en/example/py2py3/test_py3.py
rename to tools/third_party/pytest/doc/en/example/py2py3/test_py3.py
diff --git a/tools/third_party/pytest/doc/en/example/pythoncollection.py b/tools/third_party/pytest/doc/en/example/pythoncollection.py
new file mode 100644
index 0000000..9c4bd31
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/pythoncollection.py
@@ -0,0 +1,11 @@
+
+# run this with $ pytest --collect-only test_collectonly.py
+#
+def test_function():
+    pass
+
+class TestClass(object):
+    def test_method(self):
+        pass
+    def test_anothermethod(self):
+        pass
diff --git a/tools/third_party/pytest/doc/en/example/pythoncollection.rst b/tools/third_party/pytest/doc/en/example/pythoncollection.rst
new file mode 100644
index 0000000..c9d31d7
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/pythoncollection.rst
@@ -0,0 +1,239 @@
+Changing standard (Python) test discovery
+===============================================
+
+Ignore paths during test collection
+-----------------------------------
+
+You can easily ignore certain test directories and modules during collection
+by passing the ``--ignore=path`` option on the cli. ``pytest`` allows multiple
+``--ignore`` options. Example::
+
+    tests/
+    |-- example
+    |   |-- test_example_01.py
+    |   |-- test_example_02.py
+    |   '-- test_example_03.py
+    |-- foobar
+    |   |-- test_foobar_01.py
+    |   |-- test_foobar_02.py
+    |   '-- test_foobar_03.py
+    '-- hello
+        '-- world
+            |-- test_world_01.py
+            |-- test_world_02.py
+            '-- test_world_03.py
+
+Now if you invoke ``pytest`` with ``--ignore=tests/foobar/test_foobar_03.py --ignore=tests/hello/``,
+you will see that ``pytest`` only collects test-modules, which do not match the patterns specified::
+
+    ========= test session starts ==========
+    platform darwin -- Python 2.7.10, pytest-2.8.2, py-1.4.30, pluggy-0.3.1
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 5 items
+
+    tests/example/test_example_01.py .
+    tests/example/test_example_02.py .
+    tests/example/test_example_03.py .
+    tests/foobar/test_foobar_01.py .
+    tests/foobar/test_foobar_02.py .
+
+    ======= 5 passed in 0.02 seconds =======
+
+
+Keeping duplicate paths specified from command line
+----------------------------------------------------
+
+Default behavior of ``pytest`` is to ignore duplicate paths specified from the command line.
+Example::
+
+    py.test path_a path_a
+
+    ...
+    collected 1 item
+    ...
+
+Just collect tests once.
+
+To collect duplicate tests, use the ``--keep-duplicates`` option on the cli.
+Example::
+
+    py.test --keep-duplicates path_a path_a
+
+    ...
+    collected 2 items
+    ...
+
+As the collector just works on directories, if you specify twice a single test file, ``pytest`` will
+still collect it twice, no matter if the ``--keep-duplicates`` is not specified.
+Example::
+
+    py.test test_a.py test_a.py
+
+    ...
+    collected 2 items
+    ...
+
+
+Changing directory recursion
+-----------------------------------------------------
+
+You can set the :confval:`norecursedirs` option in an ini-file, for example your ``pytest.ini`` in the project root directory::
+
+    # content of pytest.ini
+    [pytest]
+    norecursedirs = .svn _build tmp*
+
+This would tell ``pytest`` to not recurse into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory.
+
+.. _`change naming conventions`:
+
+Changing naming conventions
+-----------------------------------------------------
+
+You can configure different naming conventions by setting
+the :confval:`python_files`, :confval:`python_classes` and
+:confval:`python_functions` configuration options.  Example::
+
+    # content of pytest.ini
+    # can also be defined in tox.ini or setup.cfg file, although the section
+    # name in setup.cfg files should be "tool:pytest"
+    [pytest]
+    python_files=check_*.py
+    python_classes=Check
+    python_functions=*_check
+
+This would make ``pytest`` look for tests in files that match the ``check_*
+.py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods
+that match ``*_check``.  For example, if we have::
+
+    # content of check_myapp.py
+    class CheckMyApp(object):
+        def simple_check(self):
+            pass
+        def complex_check(self):
+            pass
+
+then the test collection looks like this::
+
+    $ pytest --collect-only
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
+    collected 2 items
+    <Module 'check_myapp.py'>
+      <Class 'CheckMyApp'>
+        <Instance '()'>
+          <Function 'simple_check'>
+          <Function 'complex_check'>
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+.. note::
+
+   the ``python_functions`` and ``python_classes`` options has no effect
+   for ``unittest.TestCase`` test discovery because pytest delegates
+   detection of test case methods to unittest code.
+
+Interpreting cmdline arguments as Python packages
+-----------------------------------------------------
+
+You can use the ``--pyargs`` option to make ``pytest`` try
+interpreting arguments as python package names, deriving
+their file system path and then running the test. For
+example if you have unittest2 installed you can type::
+
+    pytest --pyargs unittest2.test.test_skipping -q
+
+which would run the respective test module.  Like with
+other options, through an ini-file and the :confval:`addopts` option you
+can make this change more permanently::
+
+    # content of pytest.ini
+    [pytest]
+    addopts = --pyargs
+
+Now a simple invocation of ``pytest NAME`` will check
+if NAME exists as an importable package/module and otherwise
+treat it as a filesystem path.
+
+Finding out what is collected
+-----------------------------------------------
+
+You can always peek at the collection tree without running tests like this::
+
+    . $ pytest --collect-only pythoncollection.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
+    collected 3 items
+    <Module 'CWD/pythoncollection.py'>
+      <Function 'test_function'>
+      <Class 'TestClass'>
+        <Instance '()'>
+          <Function 'test_method'>
+          <Function 'test_anothermethod'>
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+.. _customizing-test-collection:
+
+Customizing test collection
+---------------------------
+
+.. regendoc:wipe
+
+You can easily instruct ``pytest`` to discover tests from every Python file::
+
+    # content of pytest.ini
+    [pytest]
+    python_files = *.py
+
+However, many projects will have a ``setup.py`` which they don't want to be
+imported. Moreover, there may files only importable by a specific python
+version. For such cases you can dynamically define files to be ignored by
+listing them in a ``conftest.py`` file::
+
+    # content of conftest.py
+    import sys
+
+    collect_ignore = ["setup.py"]
+    if sys.version_info[0] > 2:
+        collect_ignore.append("pkg/module_py2.py")
+
+and then if you have a module file like this::
+
+    # content of pkg/module_py2.py
+    def test_only_on_python2():
+        try:
+            assert 0
+        except Exception, e:
+            pass
+
+and a ``setup.py`` dummy file like this::
+
+    # content of setup.py
+    0/0  # will raise exception if imported
+
+If you run with a Python 2 interpreter then you will find the one test and will
+leave out the ``setup.py`` file::
+
+    #$ pytest --collect-only
+    ====== test session starts ======
+    platform linux2 -- Python 2.7.10, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
+    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
+    collected 1 items
+    <Module 'pkg/module_py2.py'>
+      <Function 'test_only_on_python2'>
+
+    ====== no tests ran in 0.04 seconds ======
+
+If you run with a Python 3 interpreter both the one test and the ``setup.py``
+file will be left out::
+
+    $ pytest --collect-only
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
+    collected 0 items
+    
+    ======================= no tests ran in 0.12 seconds =======================
diff --git a/tools/third_party/pytest/doc/en/example/reportingdemo.rst b/tools/third_party/pytest/doc/en/example/reportingdemo.rst
new file mode 100644
index 0000000..9edc02b
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/reportingdemo.rst
@@ -0,0 +1,604 @@
+
+.. _`tbreportdemo`:
+
+Demo of Python failure reports with pytest
+==================================================
+
+Here is a nice run of several tens of failures
+and how ``pytest`` presents things (unfortunately
+not showing the nice colors here in the HTML that you
+get on the terminal - we are working on that)::
+
+    assertion $ pytest failure_demo.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR/assertion, inifile:
+    collected 42 items
+    
+    failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF           [100%]
+    
+    ================================= FAILURES =================================
+    ____________________________ test_generative[0] ____________________________
+    
+    param1 = 3, param2 = 6
+    
+        def test_generative(param1, param2):
+    >       assert param1 * 2 < param2
+    E       assert (3 * 2) < 6
+    
+    failure_demo.py:16: AssertionError
+    _________________________ TestFailing.test_simple __________________________
+    
+    self = <failure_demo.TestFailing object at 0xdeadbeef>
+    
+        def test_simple(self):
+            def f():
+                return 42
+            def g():
+                return 43
+        
+    >       assert f() == g()
+    E       assert 42 == 43
+    E        +  where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
+    E        +  and   43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
+    
+    failure_demo.py:29: AssertionError
+    ____________________ TestFailing.test_simple_multiline _____________________
+    
+    self = <failure_demo.TestFailing object at 0xdeadbeef>
+    
+        def test_simple_multiline(self):
+            otherfunc_multi(
+                      42,
+    >                 6*9)
+    
+    failure_demo.py:34: 
+    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
+    
+    a = 42, b = 54
+    
+        def otherfunc_multi(a,b):
+    >       assert (a ==
+                    b)
+    E       assert 42 == 54
+    
+    failure_demo.py:12: AssertionError
+    ___________________________ TestFailing.test_not ___________________________
+    
+    self = <failure_demo.TestFailing object at 0xdeadbeef>
+    
+        def test_not(self):
+            def f():
+                return 42
+    >       assert not f()
+    E       assert not 42
+    E        +  where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
+    
+    failure_demo.py:39: AssertionError
+    _________________ TestSpecialisedExplanations.test_eq_text _________________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_text(self):
+    >       assert 'spam' == 'eggs'
+    E       AssertionError: assert 'spam' == 'eggs'
+    E         - spam
+    E         + eggs
+    
+    failure_demo.py:43: AssertionError
+    _____________ TestSpecialisedExplanations.test_eq_similar_text _____________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_similar_text(self):
+    >       assert 'foo 1 bar' == 'foo 2 bar'
+    E       AssertionError: assert 'foo 1 bar' == 'foo 2 bar'
+    E         - foo 1 bar
+    E         ?     ^
+    E         + foo 2 bar
+    E         ?     ^
+    
+    failure_demo.py:46: AssertionError
+    ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_multiline_text(self):
+    >       assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
+    E       AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
+    E           foo
+    E         - spam
+    E         + eggs
+    E           bar
+    
+    failure_demo.py:49: AssertionError
+    ______________ TestSpecialisedExplanations.test_eq_long_text _______________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_long_text(self):
+            a = '1'*100 + 'a' + '2'*100
+            b = '1'*100 + 'b' + '2'*100
+    >       assert a == b
+    E       AssertionError: assert '111111111111...2222222222222' == '1111111111111...2222222222222'
+    E         Skipping 90 identical leading characters in diff, use -v to show
+    E         Skipping 91 identical trailing characters in diff, use -v to show
+    E         - 1111111111a222222222
+    E         ?           ^
+    E         + 1111111111b222222222
+    E         ?           ^
+    
+    failure_demo.py:54: AssertionError
+    _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_long_text_multiline(self):
+            a = '1\n'*100 + 'a' + '2\n'*100
+            b = '1\n'*100 + 'b' + '2\n'*100
+    >       assert a == b
+    E       AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n'
+    E         Skipping 190 identical leading characters in diff, use -v to show
+    E         Skipping 191 identical trailing characters in diff, use -v to show
+    E           1
+    E           1
+    E           1
+    E           1
+    E           1...
+    E         
+    E         ...Full output truncated (7 lines hidden), use '-vv' to show
+    
+    failure_demo.py:59: AssertionError
+    _________________ TestSpecialisedExplanations.test_eq_list _________________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_list(self):
+    >       assert [0, 1, 2] == [0, 1, 3]
+    E       assert [0, 1, 2] == [0, 1, 3]
+    E         At index 2 diff: 2 != 3
+    E         Use -v to get the full diff
+    
+    failure_demo.py:62: AssertionError
+    ______________ TestSpecialisedExplanations.test_eq_list_long _______________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_list_long(self):
+            a = [0]*100 + [1] + [3]*100
+            b = [0]*100 + [2] + [3]*100
+    >       assert a == b
+    E       assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
+    E         At index 100 diff: 1 != 2
+    E         Use -v to get the full diff
+    
+    failure_demo.py:67: AssertionError
+    _________________ TestSpecialisedExplanations.test_eq_dict _________________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_dict(self):
+    >       assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
+    E       AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
+    E         Omitting 1 identical items, use -vv to show
+    E         Differing items:
+    E         {'b': 1} != {'b': 2}
+    E         Left contains more items:
+    E         {'c': 0}
+    E         Right contains more items:
+    E         {'d': 0}...
+    E         
+    E         ...Full output truncated (2 lines hidden), use '-vv' to show
+    
+    failure_demo.py:70: AssertionError
+    _________________ TestSpecialisedExplanations.test_eq_set __________________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_set(self):
+    >       assert set([0, 10, 11, 12]) == set([0, 20, 21])
+    E       AssertionError: assert {0, 10, 11, 12} == {0, 20, 21}
+    E         Extra items in the left set:
+    E         10
+    E         11
+    E         12
+    E         Extra items in the right set:
+    E         20
+    E         21...
+    E         
+    E         ...Full output truncated (2 lines hidden), use '-vv' to show
+    
+    failure_demo.py:73: AssertionError
+    _____________ TestSpecialisedExplanations.test_eq_longer_list ______________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_eq_longer_list(self):
+    >       assert [1,2] == [1,2,3]
+    E       assert [1, 2] == [1, 2, 3]
+    E         Right contains more items, first extra item: 3
+    E         Use -v to get the full diff
+    
+    failure_demo.py:76: AssertionError
+    _________________ TestSpecialisedExplanations.test_in_list _________________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_in_list(self):
+    >       assert 1 in [0, 2, 3, 4, 5]
+    E       assert 1 in [0, 2, 3, 4, 5]
+    
+    failure_demo.py:79: AssertionError
+    __________ TestSpecialisedExplanations.test_not_in_text_multiline __________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_not_in_text_multiline(self):
+            text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
+    >       assert 'foo' not in text
+    E       AssertionError: assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail'
+    E         'foo' is contained here:
+    E           some multiline
+    E           text
+    E           which
+    E           includes foo
+    E         ?          +++
+    E           and a...
+    E         
+    E         ...Full output truncated (2 lines hidden), use '-vv' to show
+    
+    failure_demo.py:83: AssertionError
+    ___________ TestSpecialisedExplanations.test_not_in_text_single ____________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_not_in_text_single(self):
+            text = 'single foo line'
+    >       assert 'foo' not in text
+    E       AssertionError: assert 'foo' not in 'single foo line'
+    E         'foo' is contained here:
+    E           single foo line
+    E         ?        +++
+    
+    failure_demo.py:87: AssertionError
+    _________ TestSpecialisedExplanations.test_not_in_text_single_long _________
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_not_in_text_single_long(self):
+            text = 'head ' * 50 + 'foo ' + 'tail ' * 20
+    >       assert 'foo' not in text
+    E       AssertionError: assert 'foo' not in 'head head head head hea...ail tail tail tail tail '
+    E         'foo' is contained here:
+    E           head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail 
+    E         ?           +++
+    
+    failure_demo.py:91: AssertionError
+    ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
+    
+    self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
+    
+        def test_not_in_text_single_long_term(self):
+            text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
+    >       assert 'f'*70 not in text
+    E       AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail '
+    E         'ffffffffffffffffff...fffffffffffffffffff' is contained here:
+    E           head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail 
+    E         ?           ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+    
+    failure_demo.py:95: AssertionError
+    ______________________________ test_attribute ______________________________
+    
+        def test_attribute():
+            class Foo(object):
+                b = 1
+            i = Foo()
+    >       assert i.b == 2
+    E       assert 1 == 2
+    E        +  where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
+    
+    failure_demo.py:102: AssertionError
+    _________________________ test_attribute_instance __________________________
+    
+        def test_attribute_instance():
+            class Foo(object):
+                b = 1
+    >       assert Foo().b == 2
+    E       AssertionError: assert 1 == 2
+    E        +  where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
+    E        +    where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
+    
+    failure_demo.py:108: AssertionError
+    __________________________ test_attribute_failure __________________________
+    
+        def test_attribute_failure():
+            class Foo(object):
+                def _get_b(self):
+                    raise Exception('Failed to get attrib')
+                b = property(_get_b)
+            i = Foo()
+    >       assert i.b == 2
+    
+    failure_demo.py:117: 
+    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
+    
+    self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
+    
+        def _get_b(self):
+    >       raise Exception('Failed to get attrib')
+    E       Exception: Failed to get attrib
+    
+    failure_demo.py:114: Exception
+    _________________________ test_attribute_multiple __________________________
+    
+        def test_attribute_multiple():
+            class Foo(object):
+                b = 1
+            class Bar(object):
+                b = 2
+    >       assert Foo().b == Bar().b
+    E       AssertionError: assert 1 == 2
+    E        +  where 1 = <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef>.b
+    E        +    where <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Foo'>()
+    E        +  and   2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
+    E        +    where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
+    
+    failure_demo.py:125: AssertionError
+    __________________________ TestRaises.test_raises __________________________
+    
+    self = <failure_demo.TestRaises object at 0xdeadbeef>
+    
+        def test_raises(self):
+            s = 'qwe'
+    >       raises(TypeError, "int(s)")
+    
+    failure_demo.py:134: 
+    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
+    
+    >   int(s)
+    E   ValueError: invalid literal for int() with base 10: 'qwe'
+    
+    <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:580>:1: ValueError
+    ______________________ TestRaises.test_raises_doesnt _______________________
+    
+    self = <failure_demo.TestRaises object at 0xdeadbeef>
+    
+        def test_raises_doesnt(self):
+    >       raises(IOError, "int('3')")
+    E       Failed: DID NOT RAISE <class 'OSError'>
+    
+    failure_demo.py:137: Failed
+    __________________________ TestRaises.test_raise ___________________________
+    
+    self = <failure_demo.TestRaises object at 0xdeadbeef>
+    
+        def test_raise(self):
+    >       raise ValueError("demo error")
+    E       ValueError: demo error
+    
+    failure_demo.py:140: ValueError
+    ________________________ TestRaises.test_tupleerror ________________________
+    
+    self = <failure_demo.TestRaises object at 0xdeadbeef>
+    
+        def test_tupleerror(self):
+    >       a,b = [1]
+    E       ValueError: not enough values to unpack (expected 2, got 1)
+    
+    failure_demo.py:143: ValueError
+    ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
+    
+    self = <failure_demo.TestRaises object at 0xdeadbeef>
+    
+        def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
+            l = [1,2,3]
+            print ("l is %r" % l)
+    >       a,b = l.pop()
+    E       TypeError: 'int' object is not iterable
+    
+    failure_demo.py:148: TypeError
+    --------------------------- Captured stdout call ---------------------------
+    l is [1, 2, 3]
+    ________________________ TestRaises.test_some_error ________________________
+    
+    self = <failure_demo.TestRaises object at 0xdeadbeef>
+    
+        def test_some_error(self):
+    >       if namenotexi:
+    E       NameError: name 'namenotexi' is not defined
+    
+    failure_demo.py:151: NameError
+    ____________________ test_dynamic_compile_shows_nicely _____________________
+    
+        def test_dynamic_compile_shows_nicely():
+            src = 'def foo():\n assert 1 == 0\n'
+            name = 'abc-123'
+            module = py.std.imp.new_module(name)
+            code = _pytest._code.compile(src, name, 'exec')
+            py.builtin.exec_(code, module.__dict__)
+            py.std.sys.modules[name] = module
+    >       module.foo()
+    
+    failure_demo.py:166: 
+    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
+    
+        def foo():
+    >    assert 1 == 0
+    E    AssertionError
+    
+    <2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:163>:2: AssertionError
+    ____________________ TestMoreErrors.test_complex_error _____________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_complex_error(self):
+            def f():
+                return 44
+            def g():
+                return 43
+    >       somefunc(f(), g())
+    
+    failure_demo.py:176: 
+    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
+    failure_demo.py:9: in somefunc
+        otherfunc(x,y)
+    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
+    
+    a = 44, b = 43
+    
+        def otherfunc(a,b):
+    >       assert a==b
+    E       assert 44 == 43
+    
+    failure_demo.py:6: AssertionError
+    ___________________ TestMoreErrors.test_z1_unpack_error ____________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_z1_unpack_error(self):
+            l = []
+    >       a,b  = l
+    E       ValueError: not enough values to unpack (expected 2, got 0)
+    
+    failure_demo.py:180: ValueError
+    ____________________ TestMoreErrors.test_z2_type_error _____________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_z2_type_error(self):
+            l = 3
+    >       a,b  = l
+    E       TypeError: 'int' object is not iterable
+    
+    failure_demo.py:184: TypeError
+    ______________________ TestMoreErrors.test_startswith ______________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_startswith(self):
+            s = "123"
+            g = "456"
+    >       assert s.startswith(g)
+    E       AssertionError: assert False
+    E        +  where False = <built-in method startswith of str object at 0xdeadbeef>('456')
+    E        +    where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
+    
+    failure_demo.py:189: AssertionError
+    __________________ TestMoreErrors.test_startswith_nested ___________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_startswith_nested(self):
+            def f():
+                return "123"
+            def g():
+                return "456"
+    >       assert f().startswith(g())
+    E       AssertionError: assert False
+    E        +  where False = <built-in method startswith of str object at 0xdeadbeef>('456')
+    E        +    where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
+    E        +      where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
+    E        +    and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
+    
+    failure_demo.py:196: AssertionError
+    _____________________ TestMoreErrors.test_global_func ______________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_global_func(self):
+    >       assert isinstance(globf(42), float)
+    E       assert False
+    E        +  where False = isinstance(43, float)
+    E        +    where 43 = globf(42)
+    
+    failure_demo.py:199: AssertionError
+    _______________________ TestMoreErrors.test_instance _______________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_instance(self):
+            self.x = 6*7
+    >       assert self.x != 42
+    E       assert 42 != 42
+    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
+    
+    failure_demo.py:203: AssertionError
+    _______________________ TestMoreErrors.test_compare ________________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_compare(self):
+    >       assert globf(10) < 5
+    E       assert 11 < 5
+    E        +  where 11 = globf(10)
+    
+    failure_demo.py:206: AssertionError
+    _____________________ TestMoreErrors.test_try_finally ______________________
+    
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
+    
+        def test_try_finally(self):
+            x = 1
+            try:
+    >           assert x == 0
+    E           assert 1 == 0
+    
+    failure_demo.py:211: AssertionError
+    ___________________ TestCustomAssertMsg.test_single_line ___________________
+    
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
+    
+        def test_single_line(self):
+            class A(object):
+                a = 1
+            b = 2
+    >       assert A.a == b, "A.a appears not to be b"
+    E       AssertionError: A.a appears not to be b
+    E       assert 1 == 2
+    E        +  where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
+    
+    failure_demo.py:222: AssertionError
+    ____________________ TestCustomAssertMsg.test_multiline ____________________
+    
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
+    
+        def test_multiline(self):
+            class A(object):
+                a = 1
+            b = 2
+    >       assert A.a == b, "A.a appears not to be b\n" \
+                "or does not appear to be b\none of those"
+    E       AssertionError: A.a appears not to be b
+    E         or does not appear to be b
+    E         one of those
+    E       assert 1 == 2
+    E        +  where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
+    
+    failure_demo.py:228: AssertionError
+    ___________________ TestCustomAssertMsg.test_custom_repr ___________________
+    
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
+    
+        def test_custom_repr(self):
+            class JSON(object):
+                a = 1
+                def __repr__(self):
+                    return "This is JSON\n{\n  'foo': 'bar'\n}"
+            a = JSON()
+            b = 2
+    >       assert a.a == b, a
+    E       AssertionError: This is JSON
+    E         {
+    E           'foo': 'bar'
+    E         }
+    E       assert 1 == 2
+    E        +  where 1 = This is JSON\n{\n  'foo': 'bar'\n}.a
+    
+    failure_demo.py:238: AssertionError
+    ============================= warnings summary =============================
+    None
+      Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
+      Please use Metafunc.parametrize instead.
+    
+    -- Docs: http://doc.pytest.org/en/latest/warnings.html
+    ================== 42 failed, 1 warnings in 0.12 seconds ===================
diff --git a/tools/third_party/pytest/doc/en/example/simple.rst b/tools/third_party/pytest/doc/en/example/simple.rst
new file mode 100644
index 0000000..678a0db
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/simple.rst
@@ -0,0 +1,847 @@
+
+
+Basic patterns and examples
+==========================================================
+
+Pass different values to a test function, depending on command line options
+----------------------------------------------------------------------------
+
+.. regendoc:wipe
+
+Suppose we want to write a test that depends on a command line option.
+Here is a basic pattern to achieve this:
+
+.. code-block:: python
+
+    # content of test_sample.py
+    def test_answer(cmdopt):
+        if cmdopt == "type1":
+            print ("first")
+        elif cmdopt == "type2":
+            print ("second")
+        assert 0 # to see what was printed
+
+
+For this to work we need to add a command line option and
+provide the ``cmdopt`` through a :ref:`fixture function <fixture function>`:
+
+.. code-block:: python
+
+    # content of conftest.py
+    import pytest
+
+    def pytest_addoption(parser):
+        parser.addoption("--cmdopt", action="store", default="type1",
+            help="my option: type1 or type2")
+
+    @pytest.fixture
+    def cmdopt(request):
+        return request.config.getoption("--cmdopt")
+
+Let's run this without supplying our new option::
+
+    $ pytest -q test_sample.py
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    _______________________________ test_answer ________________________________
+    
+    cmdopt = 'type1'
+    
+        def test_answer(cmdopt):
+            if cmdopt == "type1":
+                print ("first")
+            elif cmdopt == "type2":
+                print ("second")
+    >       assert 0 # to see what was printed
+    E       assert 0
+    
+    test_sample.py:6: AssertionError
+    --------------------------- Captured stdout call ---------------------------
+    first
+    1 failed in 0.12 seconds
+
+And now with supplying a command line option::
+
+    $ pytest -q --cmdopt=type2
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    _______________________________ test_answer ________________________________
+    
+    cmdopt = 'type2'
+    
+        def test_answer(cmdopt):
+            if cmdopt == "type1":
+                print ("first")
+            elif cmdopt == "type2":
+                print ("second")
+    >       assert 0 # to see what was printed
+    E       assert 0
+    
+    test_sample.py:6: AssertionError
+    --------------------------- Captured stdout call ---------------------------
+    second
+    1 failed in 0.12 seconds
+
+You can see that the command line option arrived in our test.  This
+completes the basic pattern.  However, one often rather wants to process
+command line options outside of the test and rather pass in different or
+more complex objects.
+
+Dynamically adding command line options
+--------------------------------------------------------------
+
+.. regendoc:wipe
+
+Through :confval:`addopts` you can statically add command line
+options for your project.  You can also dynamically modify
+the command line arguments before they get processed:
+
+.. code-block:: python
+
+    # content of conftest.py
+    import sys
+    def pytest_cmdline_preparse(args):
+        if 'xdist' in sys.modules: # pytest-xdist plugin
+            import multiprocessing
+            num = max(multiprocessing.cpu_count() / 2, 1)
+            args[:] = ["-n", str(num)] + args
+
+If you have the `xdist plugin <https://pypi.python.org/pypi/pytest-xdist>`_ installed
+you will now always perform test runs using a number
+of subprocesses close to your CPU. Running in an empty
+directory with the above conftest.py::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 0 items
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+.. _`excontrolskip`:
+
+Control skipping of tests according to command line option
+--------------------------------------------------------------
+
+.. regendoc:wipe
+
+Here is a ``conftest.py`` file adding a ``--runslow`` command
+line option to control skipping of ``pytest.mark.slow`` marked tests:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    import pytest
+    def pytest_addoption(parser):
+        parser.addoption("--runslow", action="store_true",
+                         default=False, help="run slow tests")
+
+    def pytest_collection_modifyitems(config, items):
+        if config.getoption("--runslow"):
+            # --runslow given in cli: do not skip slow tests
+            return
+        skip_slow = pytest.mark.skip(reason="need --runslow option to run")
+        for item in items:
+            if "slow" in item.keywords:
+                item.add_marker(skip_slow)
+
+We can now write a test module like this:
+
+.. code-block:: python
+
+    # content of test_module.py
+    import pytest
+
+
+    def test_func_fast():
+        pass
+
+
+    @pytest.mark.slow
+    def test_func_slow():
+        pass
+
+and when running it will see a skipped "slow" test::
+
+    $ pytest -rs    # "-rs" means report details on the little 's'
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    
+    test_module.py .s                                                    [100%]
+    ========================= short test summary info ==========================
+    SKIP [1] test_module.py:8: need --runslow option to run
+    
+    =================== 1 passed, 1 skipped in 0.12 seconds ====================
+
+Or run it including the ``slow`` marked test::
+
+    $ pytest --runslow
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    
+    test_module.py ..                                                    [100%]
+    
+    ========================= 2 passed in 0.12 seconds =========================
+
+Writing well integrated assertion helpers
+--------------------------------------------------
+
+.. regendoc:wipe
+
+If you have a test helper function called from a test you can
+use the ``pytest.fail`` marker to fail a test with a certain message.
+The test support function will not show up in the traceback if you
+set the ``__tracebackhide__`` option somewhere in the helper function.
+Example:
+
+.. code-block:: python
+
+    # content of test_checkconfig.py
+    import pytest
+    def checkconfig(x):
+        __tracebackhide__ = True
+        if not hasattr(x, "config"):
+            pytest.fail("not configured: %s" %(x,))
+
+    def test_something():
+        checkconfig(42)
+
+The ``__tracebackhide__`` setting influences ``pytest`` showing
+of tracebacks: the ``checkconfig`` function will not be shown
+unless the ``--full-trace`` command line option is specified.
+Let's run our little function::
+
+    $ pytest -q test_checkconfig.py
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    ______________________________ test_something ______________________________
+    
+        def test_something():
+    >       checkconfig(42)
+    E       Failed: not configured: 42
+    
+    test_checkconfig.py:8: Failed
+    1 failed in 0.12 seconds
+
+If you only want to hide certain exceptions, you can set ``__tracebackhide__``
+to a callable which gets the ``ExceptionInfo`` object. You can for example use
+this to make sure unexpected exception types aren't hidden:
+
+.. code-block:: python
+
+    import operator
+    import pytest
+
+    class ConfigException(Exception):
+        pass
+
+    def checkconfig(x):
+        __tracebackhide__ = operator.methodcaller('errisinstance', ConfigException)
+        if not hasattr(x, "config"):
+            raise ConfigException("not configured: %s" %(x,))
+
+    def test_something():
+        checkconfig(42)
+
+This will avoid hiding the exception traceback on unrelated exceptions (i.e.
+bugs in assertion helpers).
+
+
+Detect if running from within a pytest run
+--------------------------------------------------------------
+
+.. regendoc:wipe
+
+Usually it is a bad idea to make application code
+behave differently if called from a test.  But if you
+absolutely must find out if your application code is
+running from a test you can do something like this:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    def pytest_configure(config):
+        import sys
+        sys._called_from_test = True
+
+    def pytest_unconfigure(config):
+        import sys
+        del sys._called_from_test
+
+and then check for the ``sys._called_from_test`` flag:
+
+.. code-block:: python
+
+    if hasattr(sys, '_called_from_test'):
+        # called from within a test run
+    else:
+        # called "normally"
+
+accordingly in your application.  It's also a good idea
+to use your own application module rather than ``sys``
+for handling flag.
+
+Adding info to test report header
+--------------------------------------------------------------
+
+.. regendoc:wipe
+
+It's easy to present extra information in a ``pytest`` run:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    def pytest_report_header(config):
+        return "project deps: mylib-1.1"
+
+which will add the string to the test header accordingly::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    project deps: mylib-1.1
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 0 items
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+.. regendoc:wipe
+
+It is also possible to return a list of strings which will be considered as several
+lines of information. You may consider ``config.getoption('verbose')`` in order to
+display more information if applicable:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    def pytest_report_header(config):
+        if config.getoption('verbose') > 0:
+            return ["info1: did you know that ...", "did you?"]
+
+which will add info only when run with "--v"::
+
+    $ pytest -v
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    info1: did you know that ...
+    did you?
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 0 items
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+and nothing when run plainly::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 0 items
+    
+    ======================= no tests ran in 0.12 seconds =======================
+
+profiling test duration
+--------------------------
+
+.. regendoc:wipe
+
+.. versionadded: 2.2
+
+If you have a slow running large test suite you might want to find
+out which tests are the slowest. Let's make an artificial test suite:
+
+.. code-block:: python
+
+    # content of test_some_are_slow.py
+    import time
+
+    def test_funcfast():
+        time.sleep(0.1)
+
+    def test_funcslow1():
+        time.sleep(0.2)
+
+    def test_funcslow2():
+        time.sleep(0.3)
+
+Now we can profile which test functions execute the slowest::
+
+    $ pytest --durations=3
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 3 items
+    
+    test_some_are_slow.py ...                                            [100%]
+    
+    ========================= slowest 3 test durations =========================
+    0.30s call     test_some_are_slow.py::test_funcslow2
+    0.20s call     test_some_are_slow.py::test_funcslow1
+    0.10s call     test_some_are_slow.py::test_funcfast
+    ========================= 3 passed in 0.12 seconds =========================
+
+incremental testing - test steps
+---------------------------------------------------
+
+.. regendoc:wipe
+
+Sometimes you may have a testing situation which consists of a series
+of test steps.  If one step fails it makes no sense to execute further
+steps as they are all expected to fail anyway and their tracebacks
+add no insight.  Here is a simple ``conftest.py`` file which introduces
+an ``incremental`` marker which is to be used on classes:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    import pytest
+
+    def pytest_runtest_makereport(item, call):
+        if "incremental" in item.keywords:
+            if call.excinfo is not None:
+                parent = item.parent
+                parent._previousfailed = item
+
+    def pytest_runtest_setup(item):
+        if "incremental" in item.keywords:
+            previousfailed = getattr(item.parent, "_previousfailed", None)
+            if previousfailed is not None:
+                pytest.xfail("previous test failed (%s)" %previousfailed.name)
+
+These two hook implementations work together to abort incremental-marked
+tests in a class.  Here is a test module example:
+
+.. code-block:: python
+
+    # content of test_step.py
+
+    import pytest
+
+    @pytest.mark.incremental
+    class TestUserHandling(object):
+        def test_login(self):
+            pass
+        def test_modification(self):
+            assert 0
+        def test_deletion(self):
+            pass
+
+    def test_normal():
+        pass
+
+If we run this::
+
+    $ pytest -rx
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 4 items
+    
+    test_step.py .Fx.                                                    [100%]
+    ========================= short test summary info ==========================
+    XFAIL test_step.py::TestUserHandling::()::test_deletion
+      reason: previous test failed (test_modification)
+    
+    ================================= FAILURES =================================
+    ____________________ TestUserHandling.test_modification ____________________
+    
+    self = <test_step.TestUserHandling object at 0xdeadbeef>
+    
+        def test_modification(self):
+    >       assert 0
+    E       assert 0
+    
+    test_step.py:9: AssertionError
+    ============== 1 failed, 2 passed, 1 xfailed in 0.12 seconds ===============
+
+We'll see that ``test_deletion`` was not executed because ``test_modification``
+failed.  It is reported as an "expected failure".
+
+
+Package/Directory-level fixtures (setups)
+-------------------------------------------------------
+
+If you have nested test directories, you can have per-directory fixture scopes
+by placing fixture functions in a ``conftest.py`` file in that directory
+You can use all types of fixtures including :ref:`autouse fixtures
+<autouse fixtures>` which are the equivalent of xUnit's setup/teardown
+concept.  It's however recommended to have explicit fixture references in your
+tests or test classes rather than relying on implicitly executing
+setup/teardown functions, especially if they are far away from the actual tests.
+
+Here is an example for making a ``db`` fixture available in a directory:
+
+.. code-block:: python
+
+    # content of a/conftest.py
+    import pytest
+
+    class DB(object):
+        pass
+
+    @pytest.fixture(scope="session")
+    def db():
+        return DB()
+
+and then a test module in that directory:
+
+.. code-block:: python
+
+    # content of a/test_db.py
+    def test_a1(db):
+        assert 0, db  # to show value
+
+another test module:
+
+.. code-block:: python
+
+    # content of a/test_db2.py
+    def test_a2(db):
+        assert 0, db  # to show value
+
+and then a module in a sister directory which will not see
+the ``db`` fixture:
+
+.. code-block:: python
+
+    # content of b/test_error.py
+    def test_root(db):  # no db here, will error out
+        pass
+
+We can run this::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 7 items
+    
+    test_step.py .Fx.                                                    [ 57%]
+    a/test_db.py F                                                       [ 71%]
+    a/test_db2.py F                                                      [ 85%]
+    b/test_error.py E                                                    [100%]
+    
+    ================================== ERRORS ==================================
+    _______________________ ERROR at setup of test_root ________________________
+    file $REGENDOC_TMPDIR/b/test_error.py, line 1
+      def test_root(db):  # no db here, will error out
+    E       fixture 'db' not found
+    >       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_xml_property, recwarn, tmpdir, tmpdir_factory
+    >       use 'pytest --fixtures [testpath]' for help on them.
+    
+    $REGENDOC_TMPDIR/b/test_error.py:1
+    ================================= FAILURES =================================
+    ____________________ TestUserHandling.test_modification ____________________
+    
+    self = <test_step.TestUserHandling object at 0xdeadbeef>
+    
+        def test_modification(self):
+    >       assert 0
+    E       assert 0
+    
+    test_step.py:9: AssertionError
+    _________________________________ test_a1 __________________________________
+    
+    db = <conftest.DB object at 0xdeadbeef>
+    
+        def test_a1(db):
+    >       assert 0, db  # to show value
+    E       AssertionError: <conftest.DB object at 0xdeadbeef>
+    E       assert 0
+    
+    a/test_db.py:2: AssertionError
+    _________________________________ test_a2 __________________________________
+    
+    db = <conftest.DB object at 0xdeadbeef>
+    
+        def test_a2(db):
+    >       assert 0, db  # to show value
+    E       AssertionError: <conftest.DB object at 0xdeadbeef>
+    E       assert 0
+    
+    a/test_db2.py:2: AssertionError
+    ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.12 seconds ==========
+
+The two test modules in the ``a`` directory see the same ``db`` fixture instance
+while the one test in the sister-directory ``b`` doesn't see it.  We could of course
+also define a ``db`` fixture in that sister directory's ``conftest.py`` file.
+Note that each fixture is only instantiated if there is a test actually needing
+it (unless you use "autouse" fixture which are always executed ahead of the first test
+executing).
+
+
+post-process test reports / failures
+---------------------------------------
+
+If you want to postprocess test reports and need access to the executing
+environment you can implement a hook that gets called when the test
+"report" object is about to be created.  Here we write out all failing
+test calls and also access a fixture (if it was used by the test) in
+case you want to query/look at it during your post processing.  In our
+case we just write some information out to a ``failures`` file:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    import pytest
+    import os.path
+
+    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
+    def pytest_runtest_makereport(item, call):
+        # execute all other hooks to obtain the report object
+        outcome = yield
+        rep = outcome.get_result()
+
+        # we only look at actual failing test calls, not setup/teardown
+        if rep.when == "call" and rep.failed:
+            mode = "a" if os.path.exists("failures") else "w"
+            with open("failures", mode) as f:
+                # let's also access a fixture for the fun of it
+                if "tmpdir" in item.fixturenames:
+                    extra = " (%s)" % item.funcargs["tmpdir"]
+                else:
+                    extra = ""
+
+                f.write(rep.nodeid + extra + "\n")
+
+
+if you then have failing tests:
+
+.. code-block:: python
+
+    # content of test_module.py
+    def test_fail1(tmpdir):
+        assert 0
+    def test_fail2():
+        assert 0
+
+and run them::
+
+    $ pytest test_module.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    
+    test_module.py FF                                                    [100%]
+    
+    ================================= FAILURES =================================
+    ________________________________ test_fail1 ________________________________
+    
+    tmpdir = local('PYTEST_TMPDIR/test_fail10')
+    
+        def test_fail1(tmpdir):
+    >       assert 0
+    E       assert 0
+    
+    test_module.py:2: AssertionError
+    ________________________________ test_fail2 ________________________________
+    
+        def test_fail2():
+    >       assert 0
+    E       assert 0
+    
+    test_module.py:4: AssertionError
+    ========================= 2 failed in 0.12 seconds =========================
+
+you will have a "failures" file which contains the failing test ids::
+
+    $ cat failures
+    test_module.py::test_fail1 (PYTEST_TMPDIR/test_fail10)
+    test_module.py::test_fail2
+
+Making test result information available in fixtures
+-----------------------------------------------------------
+
+.. regendoc:wipe
+
+If you want to make test result reports available in fixture finalizers
+here is a little example implemented via a local plugin:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    import pytest
+
+    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
+    def pytest_runtest_makereport(item, call):
+        # execute all other hooks to obtain the report object
+        outcome = yield
+        rep = outcome.get_result()
+
+        # set a report attribute for each phase of a call, which can
+        # be "setup", "call", "teardown"
+
+        setattr(item, "rep_" + rep.when, rep)
+
+
+    @pytest.fixture
+    def something(request):
+        yield
+        # request.node is an "item" because we use the default
+        # "function" scope
+        if request.node.rep_setup.failed:
+            print ("setting up a test failed!", request.node.nodeid)
+        elif request.node.rep_setup.passed:
+            if request.node.rep_call.failed:
+                print ("executing test failed", request.node.nodeid)
+
+
+if you then have failing tests:
+
+.. code-block:: python
+
+    # content of test_module.py
+
+    import pytest
+
+    @pytest.fixture
+    def other():
+        assert 0
+
+    def test_setup_fails(something, other):
+        pass
+
+    def test_call_fails(something):
+        assert 0
+
+    def test_fail2():
+        assert 0
+
+and run it::
+
+    $ pytest -s test_module.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 3 items
+    
+    test_module.py Esetting up a test failed! test_module.py::test_setup_fails
+    Fexecuting test failed test_module.py::test_call_fails
+    F                                                   [100%]
+    
+    ================================== ERRORS ==================================
+    ____________________ ERROR at setup of test_setup_fails ____________________
+    
+        @pytest.fixture
+        def other():
+    >       assert 0
+    E       assert 0
+    
+    test_module.py:6: AssertionError
+    ================================= FAILURES =================================
+    _____________________________ test_call_fails ______________________________
+    
+    something = None
+    
+        def test_call_fails(something):
+    >       assert 0
+    E       assert 0
+    
+    test_module.py:12: AssertionError
+    ________________________________ test_fail2 ________________________________
+    
+        def test_fail2():
+    >       assert 0
+    E       assert 0
+    
+    test_module.py:15: AssertionError
+    ==================== 2 failed, 1 error in 0.12 seconds =====================
+
+You'll see that the fixture finalizers could use the precise reporting
+information.
+
+``PYTEST_CURRENT_TEST`` environment variable
+--------------------------------------------
+
+.. versionadded:: 3.2
+
+Sometimes a test session might get stuck and there might be no easy way to figure out
+which test got stuck, for example if pytest was run in quiet mode (``-q``) or you don't have access to the console
+output. This is particularly a problem if the problem helps only sporadically, the famous "flaky" kind of tests.
+
+``pytest`` sets a ``PYTEST_CURRENT_TEST`` environment variable when running tests, which can be inspected
+by process monitoring utilities or libraries like `psutil <https://pypi.python.org/pypi/psutil>`_ to discover which
+test got stuck if necessary:
+
+.. code-block:: python
+
+    import psutil
+
+    for pid in psutil.pids():
+        environ = psutil.Process(pid).environ()
+        if 'PYTEST_CURRENT_TEST' in environ:
+            print(f'pytest process {pid} running: {environ["PYTEST_CURRENT_TEST"]}')
+
+During the test session pytest will set ``PYTEST_CURRENT_TEST`` to the current test
+:ref:`nodeid <nodeids>` and the current stage, which can be ``setup``, ``call``
+and ``teardown``.
+
+For example, when running a single test function named ``test_foo`` from ``foo_module.py``,
+``PYTEST_CURRENT_TEST`` will be set to:
+
+#. ``foo_module.py::test_foo (setup)``
+#. ``foo_module.py::test_foo (call)``
+#. ``foo_module.py::test_foo (teardown)``
+
+In that order.
+
+.. note::
+
+    The contents of ``PYTEST_CURRENT_TEST`` is meant to be human readable and the actual format
+    can be changed between releases (even bug fixes) so it shouldn't be relied on for scripting
+    or automation.
+
+Freezing pytest 
+---------------
+
+If you freeze your application using a tool like
+`PyInstaller <https://pyinstaller.readthedocs.io>`_
+in order to distribute it to your end-users, it is a good idea to also package
+your test runner and run your tests using the frozen application. This way packaging
+errors such as dependencies not being included into the executable can be detected early
+while also allowing you to send test files to users so they can run them in their
+machines, which can be useful to obtain more information about a hard to reproduce bug.
+
+Fortunately recent ``PyInstaller`` releases already have a custom hook
+for pytest, but if you are using another tool to freeze executables 
+such as ``cx_freeze`` or ``py2exe``, you can use ``pytest.freeze_includes()``
+to obtain the full list of internal pytest modules. How to configure the tools
+to find the internal modules varies from tool to tool, however.
+
+Instead of freezing the pytest runner as a separate executable, you can make 
+your frozen program work as the pytest runner by some clever
+argument handling during program startup. This allows you to 
+have a single executable, which is usually more convenient.
+
+.. code-block:: python
+
+    # contents of app_main.py
+    import sys
+
+    if len(sys.argv) > 1 and sys.argv[1] == '--pytest':
+        import pytest
+        sys.exit(pytest.main(sys.argv[2:]))
+    else:
+        # normal application execution: at this point argv can be parsed
+        # by your argument-parsing library of choice as usual
+        ...
+
+
+This allows you to execute tests using the frozen
+application with standard ``pytest`` command-line options::
+
+    ./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/
diff --git a/tools/third_party/pytest/doc/en/example/special.rst b/tools/third_party/pytest/doc/en/example/special.rst
new file mode 100644
index 0000000..4437e1c
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/example/special.rst
@@ -0,0 +1,72 @@
+A session-fixture which can look at all collected tests
+----------------------------------------------------------------
+
+A session-scoped fixture effectively has access to all
+collected test items.  Here is an example of a fixture
+function which walks all collected tests and looks
+if their test class defines a ``callme`` method and
+calls it::
+
+    # content of conftest.py
+
+    import pytest
+
+    @pytest.fixture(scope="session", autouse=True)
+    def callattr_ahead_of_alltests(request):
+        print ("callattr_ahead_of_alltests called")
+        seen = set([None])
+        session = request.node
+        for item in session.items:
+            cls = item.getparent(pytest.Class)
+            if cls not in seen:
+                if hasattr(cls.obj, "callme"):
+                   cls.obj.callme()
+                seen.add(cls)
+
+test classes may now define a ``callme`` method which
+will be called ahead of running any tests::
+
+    # content of test_module.py
+
+    class TestHello(object):
+        @classmethod
+        def callme(cls):
+            print ("callme called!")
+
+        def test_method1(self):
+            print ("test_method1 called")
+
+        def test_method2(self):
+            print ("test_method1 called")
+
+    class TestOther(object):
+        @classmethod
+        def callme(cls):
+            print ("callme other called")
+        def test_other(self):
+            print ("test other")
+
+    # works with unittest as well ...
+    import unittest
+
+    class SomeTest(unittest.TestCase):
+        @classmethod
+        def callme(self):
+            print ("SomeTest callme called")
+
+        def test_unit1(self):
+            print ("test_unit1 method called")
+
+If you run this without output capturing::
+
+    $ pytest -q -s test_module.py
+    callattr_ahead_of_alltests called
+    callme called!
+    callme other called
+    SomeTest callme called
+    test_method1 called
+    .test_method1 called
+    .test other
+    .test_unit1 method called
+    .                                                                 [100%]
+    4 passed in 0.12 seconds
diff --git a/tools/pytest/doc/en/example/xfail_demo.py b/tools/third_party/pytest/doc/en/example/xfail_demo.py
similarity index 100%
rename from tools/pytest/doc/en/example/xfail_demo.py
rename to tools/third_party/pytest/doc/en/example/xfail_demo.py
diff --git a/tools/third_party/pytest/doc/en/existingtestsuite.rst b/tools/third_party/pytest/doc/en/existingtestsuite.rst
new file mode 100644
index 0000000..d304b30
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/existingtestsuite.rst
@@ -0,0 +1,34 @@
+.. _existingtestsuite:
+
+Using pytest with an existing test suite
+===========================================
+
+Pytest can be used with most existing test suites, but its
+behavior differs from other test runners such as :ref:`nose <noseintegration>` or
+Python's default unittest framework.
+
+Before using this section you will want to :ref:`install pytest <getstarted>`.
+
+Running an existing test suite with pytest
+---------------------------------------------
+
+Say you want to contribute to an existing repository somewhere.
+After pulling the code into your development space using some
+flavor of version control and (optionally) setting up a virtualenv
+you will want to run::
+
+    cd <repository>
+    pip install -e .  # Environment dependent alternatives include
+                      # 'python setup.py develop' and 'conda develop'
+
+in your project root.  This will set up a symlink to your code in
+site-packages, allowing you to edit your code while your tests
+run against it as if it were installed.
+
+Setting up your project in development mode lets you avoid having to
+reinstall every time you want to run your tests, and is less brittle than
+mucking about with sys.path to point your tests at local code.
+
+Also consider using :ref:`tox <use tox>`.
+
+.. include:: links.inc
diff --git a/tools/third_party/pytest/doc/en/faq.rst b/tools/third_party/pytest/doc/en/faq.rst
new file mode 100644
index 0000000..27d74e1
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/faq.rst
@@ -0,0 +1,156 @@
+Some Issues and Questions
+==================================
+
+.. note::
+
+    This FAQ is here only mostly for historic reasons.  Checkout
+    `pytest Q&A at Stackoverflow <http://stackoverflow.com/search?q=pytest>`_
+    for many questions and answers related to pytest and/or use
+    :ref:`contact channels` to get help.
+
+On naming, nosetests, licensing and magic
+------------------------------------------------
+
+How does pytest relate to nose and unittest?
++++++++++++++++++++++++++++++++++++++++++++++++++
+
+``pytest`` and nose_ share basic philosophy when it comes
+to running and writing Python tests.  In fact, you can run many tests
+written for nose with ``pytest``.  nose_ was originally created
+as a clone of ``pytest`` when ``pytest`` was in the ``0.8`` release
+cycle.  Note that starting with pytest-2.0 support for running unittest
+test suites is majorly improved.
+
+how does pytest relate to twisted's trial?
+++++++++++++++++++++++++++++++++++++++++++++++
+
+Since some time ``pytest`` has builtin support for supporting tests
+written using trial. It does not itself start a reactor, however,
+and does not handle Deferreds returned from a test in pytest style.
+If you are using trial's unittest.TestCase chances are that you can
+just run your tests even if you return Deferreds.  In addition,
+there also is a dedicated `pytest-twisted
+<http://pypi.python.org/pypi/pytest-twisted>`_ plugin which allows you to
+return deferreds from pytest-style tests, allowing the use of
+:ref:`fixtures` and other features.
+
+how does pytest work with Django?
+++++++++++++++++++++++++++++++++++++++++++++++
+
+In 2012, some work is going into the `pytest-django plugin <http://pypi.python.org/pypi/pytest-django>`_.  It substitutes the usage of Django's
+``manage.py test`` and allows the use of all pytest features_ most of which
+are not available from Django directly.
+
+.. _features: features.html
+
+
+What's this "magic" with pytest? (historic notes)
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Around 2007 (version ``0.8``) some people thought that ``pytest``
+was using too much "magic".  It had been part of the `pylib`_ which
+contains a lot of unrelated python library code.  Around 2010 there
+was a major cleanup refactoring, which removed unused or deprecated code
+and resulted in the new ``pytest`` PyPI package which strictly contains
+only test-related code.  This release also brought a complete pluginification
+such that the core is around 300 lines of code and everything else is
+implemented in plugins.  Thus ``pytest`` today is a small, universally runnable
+and customizable testing framework for Python.   Note, however, that
+``pytest`` uses metaprogramming techniques and reading its source is
+thus likely not something for Python beginners.
+
+A second "magic" issue was the assert statement debugging feature.
+Nowadays, ``pytest`` explicitly rewrites assert statements in test modules
+in order to provide more useful :ref:`assert feedback <assertfeedback>`.
+This completely avoids previous issues of confusing assertion-reporting.
+It also means, that you can use Python's ``-O`` optimization without losing
+assertions in test modules.
+
+You can also turn off all assertion interaction using the
+``--assert=plain`` option.
+
+.. _`py namespaces`: index.html
+.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
+
+
+Why can I use both ``pytest`` and ``py.test`` commands?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+pytest used to be part of the py package, which provided several developer
+utilities, all starting with ``py.<TAB>``, thus providing nice TAB-completion.
+If you install ``pip install pycmd`` you get these tools from a separate
+package. Once ``pytest`` became a separate package, the ``py.test`` name was
+retained due to avoid a naming conflict with another tool. This conflict was
+eventually resolved, and the ``pytest`` command was therefore introduced. In
+future versions of pytest, we may deprecate and later remove the ``py.test``
+command to avoid perpetuating the confusion.
+
+pytest fixtures, parametrized tests
+-------------------------------------------------------
+
+.. _funcargs: funcargs.html
+
+Is using pytest fixtures versus xUnit setup a style question?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+For simple applications and for people experienced with nose_ or
+unittest-style test setup using `xUnit style setup`_ probably
+feels natural.  For larger test suites, parametrized testing
+or setup of complex test resources using fixtures_ may feel more natural.
+Moreover, fixtures are ideal for writing advanced test support
+code (like e.g. the monkeypatch_, the tmpdir_ or capture_ fixtures)
+because the support code can register setup/teardown functions
+in a managed class/module/function scope.
+
+.. _monkeypatch: monkeypatch.html
+.. _tmpdir: tmpdir.html
+.. _capture: capture.html
+.. _fixtures: fixture.html
+
+.. _`why pytest_pyfuncarg__ methods?`:
+
+.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
+
+Can I yield multiple values from a fixture function?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+There are two conceptual reasons why yielding from a factory function
+is not possible:
+
+* If multiple factories yielded values there would
+  be no natural place to determine the combination
+  policy - in real-world examples some combinations
+  often should not run.
+
+* Calling factories for obtaining test function arguments
+  is part of setting up and running a test.  At that
+  point it is not possible to add new test calls to
+  the test collection anymore.
+
+However, with pytest-2.3 you can use the :ref:`@pytest.fixture` decorator
+and specify ``params`` so that all tests depending on the factory-created
+resource will run multiple times with different parameters.
+
+You can also use the ``pytest_generate_tests`` hook to
+implement the `parametrization scheme of your choice`_. See also
+:ref:`paramexamples` for more examples.
+
+.. _`parametrization scheme of your choice`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
+
+pytest interaction with other packages
+---------------------------------------------------
+
+Issues with pytest, multiprocess and setuptools?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+On Windows the multiprocess package will instantiate sub processes
+by pickling and thus implicitly re-import a lot of local modules.
+Unfortunately, setuptools-0.6.11 does not ``if __name__=='__main__'``
+protect its generated command line script.  This leads to infinite
+recursion when running a test that instantiates Processes.
+
+As of mid-2013, there shouldn't be a problem anymore when you
+use the standard setuptools (note that distribute has been merged
+back into setuptools which is now shipped directly with virtualenv).
+
+.. include:: links.inc
diff --git a/tools/third_party/pytest/doc/en/fixture.rst b/tools/third_party/pytest/doc/en/fixture.rst
new file mode 100644
index 0000000..01a941d
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/fixture.rst
@@ -0,0 +1,1088 @@
+.. _fixture:
+.. _fixtures:
+.. _`fixture functions`:
+
+pytest fixtures: explicit, modular, scalable
+========================================================
+
+.. currentmodule:: _pytest.python
+
+.. versionadded:: 2.0/2.3/2.4
+
+.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
+.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
+.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
+
+The `purpose of test fixtures`_ is to provide a fixed baseline
+upon which tests can reliably and repeatedly execute.   pytest fixtures
+offer dramatic improvements over the classic xUnit style of setup/teardown
+functions:
+
+* fixtures have explicit names and are activated by declaring their use
+  from test functions, modules, classes or whole projects.
+
+* fixtures are implemented in a modular manner, as each fixture name
+  triggers a *fixture function* which can itself use other fixtures.
+
+* fixture management scales from simple unit to complex
+  functional testing, allowing to parametrize fixtures and tests according
+  to configuration and component options, or to re-use fixtures
+  across function, class, module or whole test session scopes.
+
+In addition, pytest continues to support :ref:`xunitsetup`.  You can mix
+both styles, moving incrementally from classic to new style, as you
+prefer.  You can also start out from existing :ref:`unittest.TestCase
+style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
+
+
+.. _`funcargs`:
+.. _`funcarg mechanism`:
+.. _`fixture function`:
+.. _`@pytest.fixture`:
+.. _`pytest.fixture`:
+
+Fixtures as Function arguments
+-----------------------------------------
+
+Test functions can receive fixture objects by naming them as an input
+argument. For each argument name, a fixture function with that name provides
+the fixture object.  Fixture functions are registered by marking them with
+:py:func:`@pytest.fixture <_pytest.python.fixture>`.  Let's look at a simple
+self-contained test module containing a fixture and a test function
+using it::
+
+    # content of ./test_smtpsimple.py
+    import pytest
+
+    @pytest.fixture
+    def smtp():
+        import smtplib
+        return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
+
+    def test_ehlo(smtp):
+        response, msg = smtp.ehlo()
+        assert response == 250
+        assert 0 # for demo purposes
+
+Here, the ``test_ehlo`` needs the ``smtp`` fixture value.  pytest
+will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
+marked ``smtp`` fixture function.  Running the test looks like this::
+
+    $ pytest test_smtpsimple.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_smtpsimple.py F                                                 [100%]
+    
+    ================================= FAILURES =================================
+    ________________________________ test_ehlo _________________________________
+    
+    smtp = <smtplib.SMTP object at 0xdeadbeef>
+    
+        def test_ehlo(smtp):
+            response, msg = smtp.ehlo()
+            assert response == 250
+    >       assert 0 # for demo purposes
+    E       assert 0
+    
+    test_smtpsimple.py:11: AssertionError
+    ========================= 1 failed in 0.12 seconds =========================
+
+In the failure traceback we see that the test function was called with a
+``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture
+function.  The test function fails on our deliberate ``assert 0``.  Here is
+the exact protocol used by ``pytest`` to call the test function this way:
+
+1. pytest :ref:`finds <test discovery>` the ``test_ehlo`` because
+   of the ``test_`` prefix.  The test function needs a function argument
+   named ``smtp``.  A matching fixture function is discovered by
+   looking for a fixture-marked function named ``smtp``.
+
+2. ``smtp()`` is called to create an instance.
+
+3. ``test_ehlo(<SMTP instance>)`` is called and fails in the last
+   line of the test function.
+
+Note that if you misspell a function argument or want
+to use one that isn't available, you'll see an error
+with a list of available function arguments.
+
+.. note::
+
+    You can always issue::
+
+        pytest --fixtures test_simplefactory.py
+
+    to see available fixtures.
+
+Fixtures: a prime example of dependency injection
+---------------------------------------------------
+
+Fixtures allow test functions to easily receive and work
+against specific pre-initialized application objects without having
+to care about import/setup/cleanup details.
+It's a prime example of `dependency injection`_ where fixture
+functions take the role of the *injector* and test functions are the
+*consumers* of fixture objects.
+
+.. _`conftest.py`:
+.. _`conftest`:
+
+``conftest.py``: sharing fixture functions
+------------------------------------------
+
+If during implementing your tests you realize that you
+want to use a fixture function from multiple test files you can move it
+to a ``conftest.py`` file.
+You don't need to import the fixture you want to use in a test, it
+automatically gets discovered by pytest. The discovery of
+fixture functions starts at test classes, then test modules, then
+``conftest.py`` files and finally builtin and third party plugins.
+
+You can also use the ``conftest.py`` file to implement 
+:ref:`local per-directory plugins <conftest.py plugins>`.
+
+Sharing test data
+-----------------
+
+If you want to make test data from files available to your tests, a good way
+to do this is by loading these data in a fixture for use by your tests.
+This makes use of the automatic caching mechanisms of pytest.
+
+Another good approach is by adding the data files in the ``tests`` folder.
+There are also community plugins available to help managing this aspect of 
+testing, e.g. `pytest-datadir <https://github.com/gabrielcnr/pytest-datadir>`__ 
+and `pytest-datafiles <https://pypi.python.org/pypi/pytest-datafiles>`__. 
+
+.. _smtpshared:
+
+Scope: sharing a fixture instance across tests in a class, module or session
+----------------------------------------------------------------------------
+
+.. regendoc:wipe
+
+Fixtures requiring network access depend on connectivity and are
+usually time-expensive to create.  Extending the previous example, we
+can add a ``scope='module'`` parameter to the
+:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation
+to cause the decorated ``smtp`` fixture function to only be invoked once
+per test *module* (the default is to invoke once per test *function*).
+Multiple test functions in a test module will thus
+each receive the same ``smtp`` fixture instance, thus saving time.
+
+The next example puts the fixture function into a separate ``conftest.py`` file 
+so that tests from multiple test modules in the directory can
+access the fixture function::
+
+    # content of conftest.py
+    import pytest
+    import smtplib
+
+    @pytest.fixture(scope="module")
+    def smtp():
+        return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
+
+The name of the fixture again is ``smtp`` and you can access its result by
+listing the name ``smtp`` as an input parameter in any test or fixture
+function (in or below the directory where ``conftest.py`` is located)::
+
+    # content of test_module.py
+
+    def test_ehlo(smtp):
+        response, msg = smtp.ehlo()
+        assert response == 250
+        assert b"smtp.gmail.com" in msg
+        assert 0  # for demo purposes
+
+    def test_noop(smtp):
+        response, msg = smtp.noop()
+        assert response == 250
+        assert 0  # for demo purposes
+
+We deliberately insert failing ``assert 0`` statements in order to
+inspect what is going on and can now run the tests::
+
+    $ pytest test_module.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    
+    test_module.py FF                                                    [100%]
+    
+    ================================= FAILURES =================================
+    ________________________________ test_ehlo _________________________________
+    
+    smtp = <smtplib.SMTP object at 0xdeadbeef>
+    
+        def test_ehlo(smtp):
+            response, msg = smtp.ehlo()
+            assert response == 250
+            assert b"smtp.gmail.com" in msg
+    >       assert 0  # for demo purposes
+    E       assert 0
+    
+    test_module.py:6: AssertionError
+    ________________________________ test_noop _________________________________
+    
+    smtp = <smtplib.SMTP object at 0xdeadbeef>
+    
+        def test_noop(smtp):
+            response, msg = smtp.noop()
+            assert response == 250
+    >       assert 0  # for demo purposes
+    E       assert 0
+    
+    test_module.py:11: AssertionError
+    ========================= 2 failed in 0.12 seconds =========================
+
+You see the two ``assert 0`` failing and more importantly you can also see
+that the same (module-scoped) ``smtp`` object was passed into the two
+test functions because pytest shows the incoming argument values in the
+traceback.  As a result, the two test functions using ``smtp`` run as
+quick as a single one because they reuse the same instance.
+
+If you decide that you rather want to have a session-scoped ``smtp``
+instance, you can simply declare it:
+
+.. code-block:: python
+
+    @pytest.fixture(scope="session")
+    def smtp(...):
+        # the returned fixture value will be shared for
+        # all tests needing it
+
+Finally, the ``class`` scope will invoke the fixture once per test *class*.
+
+.. _`finalization`:
+
+Fixture finalization / executing teardown code
+-------------------------------------------------------------
+
+pytest supports execution of fixture specific finalization code
+when the fixture goes out of scope.  By using a ``yield`` statement instead of ``return``, all
+the code after the *yield* statement serves as the teardown code:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    import smtplib
+    import pytest
+
+    @pytest.fixture(scope="module")
+    def smtp():
+        smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
+        yield smtp  # provide the fixture value
+        print("teardown smtp")
+        smtp.close()
+
+The ``print`` and ``smtp.close()`` statements will execute when the last test in
+the module has finished execution, regardless of the exception status of the
+tests.
+
+Let's execute it::
+
+    $ pytest -s -q --tb=no
+    FF                                                                   [100%]teardown smtp
+    
+    2 failed in 0.12 seconds
+
+We see that the ``smtp`` instance is finalized after the two
+tests finished execution.  Note that if we decorated our fixture
+function with ``scope='function'`` then fixture setup and cleanup would
+occur around each single test.  In either case the test
+module itself does not need to change or know about these details
+of fixture setup.
+
+Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements:
+
+.. code-block:: python
+
+    # content of test_yield2.py
+
+    import smtplib
+    import pytest
+
+    @pytest.fixture(scope="module")
+    def smtp():
+        with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp:
+            yield smtp  # provide the fixture value
+
+
+The ``smtp`` connection will be closed after the test finished execution
+because the ``smtp`` object automatically closes when
+the ``with`` statement ends.
+
+Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
+*teardown* code (after the ``yield``) will not be called.
+
+An alternative option for executing *teardown* code is to
+make use of the ``addfinalizer`` method of the `request-context`_ object to register
+finalization functions.
+
+Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup:
+
+.. code-block:: python
+
+    # content of conftest.py
+    import smtplib
+    import pytest
+
+    @pytest.fixture(scope="module")
+    def smtp(request):
+        smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
+        def fin():
+            print ("teardown smtp")
+            smtp.close()
+        request.addfinalizer(fin)
+        return smtp  # provide the fixture value
+
+
+Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
+ends, but ``addfinalizer`` has two key differences over ``yield``:
+
+1. It is possible to register multiple finalizer functions.
+
+2. Finalizers will always be called regardless if the fixture *setup* code raises an exception.
+   This is handy to properly close all resources created by a fixture even if one of them
+   fails to be created/acquired::
+
+        @pytest.fixture
+        def equipments(request):
+            r = []
+            for port in ('C1', 'C3', 'C28'):
+                equip = connect(port)
+                request.addfinalizer(equip.disconnect)
+                r.append(equip)
+            return r
+
+   In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
+   be properly closed. Of course, if an exception happens before the finalize function is
+   registered then it will not be executed.
+
+
+.. _`request-context`:
+
+Fixtures can introspect the requesting test context
+-------------------------------------------------------------
+
+Fixture function can accept the :py:class:`request <FixtureRequest>` object
+to introspect the "requesting" test function, class or module context.
+Further extending the previous ``smtp`` fixture example, let's
+read an optional server URL from the test module which uses our fixture::
+
+    # content of conftest.py
+    import pytest
+    import smtplib
+
+    @pytest.fixture(scope="module")
+    def smtp(request):
+        server = getattr(request.module, "smtpserver", "smtp.gmail.com")
+        smtp = smtplib.SMTP(server, 587, timeout=5)
+        yield smtp
+        print ("finalizing %s (%s)" % (smtp, server))
+        smtp.close()
+
+We use the ``request.module`` attribute to optionally obtain an
+``smtpserver`` attribute from the test module.  If we just execute
+again, nothing much has changed::
+
+    $ pytest -s -q --tb=no
+    FF                                                                   [100%]finalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
+    
+    2 failed in 0.12 seconds
+
+Let's quickly create another test module that actually sets the
+server URL in its module namespace::
+
+    # content of test_anothersmtp.py
+
+    smtpserver = "mail.python.org"  # will be read by smtp fixture
+
+    def test_showhelo(smtp):
+        assert 0, smtp.helo()
+
+Running it::
+
+    $ pytest -qq --tb=short test_anothersmtp.py
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    ______________________________ test_showhelo _______________________________
+    test_anothersmtp.py:5: in test_showhelo
+        assert 0, smtp.helo()
+    E   AssertionError: (250, b'mail.python.org')
+    E   assert 0
+    ------------------------- Captured stdout teardown -------------------------
+    finalizing <smtplib.SMTP object at 0xdeadbeef> (mail.python.org)
+
+voila! The ``smtp`` fixture function picked up our mail server name
+from the module namespace.
+
+.. _`fixture-parametrize`:
+
+Parametrizing fixtures
+-----------------------------------------------------------------
+
+Fixture functions can be parametrized in which case they will be called
+multiple times, each time executing the set of dependent tests, i. e. the
+tests that depend on this fixture.  Test functions do usually not need
+to be aware of their re-running.  Fixture parametrization helps to
+write exhaustive functional tests for components which themselves can be
+configured in multiple ways.
+
+Extending the previous example, we can flag the fixture to create two
+``smtp`` fixture instances which will cause all tests using the fixture
+to run twice.  The fixture function gets access to each parameter
+through the special :py:class:`request <FixtureRequest>` object::
+
+    # content of conftest.py
+    import pytest
+    import smtplib
+
+    @pytest.fixture(scope="module",
+                    params=["smtp.gmail.com", "mail.python.org"])
+    def smtp(request):
+        smtp = smtplib.SMTP(request.param, 587, timeout=5)
+        yield smtp
+        print ("finalizing %s" % smtp)
+        smtp.close()
+
+The main change is the declaration of ``params`` with
+:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
+for each of which the fixture function will execute and can access
+a value via ``request.param``.  No test function code needs to change.
+So let's just do another run::
+
+    $ pytest -q test_module.py
+    FFFF                                                                 [100%]
+    ================================= FAILURES =================================
+    ________________________ test_ehlo[smtp.gmail.com] _________________________
+    
+    smtp = <smtplib.SMTP object at 0xdeadbeef>
+    
+        def test_ehlo(smtp):
+            response, msg = smtp.ehlo()
+            assert response == 250
+            assert b"smtp.gmail.com" in msg
+    >       assert 0  # for demo purposes
+    E       assert 0
+    
+    test_module.py:6: AssertionError
+    ________________________ test_noop[smtp.gmail.com] _________________________
+    
+    smtp = <smtplib.SMTP object at 0xdeadbeef>
+    
+        def test_noop(smtp):
+            response, msg = smtp.noop()
+            assert response == 250
+    >       assert 0  # for demo purposes
+    E       assert 0
+    
+    test_module.py:11: AssertionError
+    ________________________ test_ehlo[mail.python.org] ________________________
+    
+    smtp = <smtplib.SMTP object at 0xdeadbeef>
+    
+        def test_ehlo(smtp):
+            response, msg = smtp.ehlo()
+            assert response == 250
+    >       assert b"smtp.gmail.com" in msg
+    E       AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
+    
+    test_module.py:5: AssertionError
+    -------------------------- Captured stdout setup ---------------------------
+    finalizing <smtplib.SMTP object at 0xdeadbeef>
+    ________________________ test_noop[mail.python.org] ________________________
+    
+    smtp = <smtplib.SMTP object at 0xdeadbeef>
+    
+        def test_noop(smtp):
+            response, msg = smtp.noop()
+            assert response == 250
+    >       assert 0  # for demo purposes
+    E       assert 0
+    
+    test_module.py:11: AssertionError
+    ------------------------- Captured stdout teardown -------------------------
+    finalizing <smtplib.SMTP object at 0xdeadbeef>
+    4 failed in 0.12 seconds
+
+We see that our two test functions each ran twice, against the different
+``smtp`` instances.  Note also, that with the ``mail.python.org``
+connection the second test fails in ``test_ehlo`` because a
+different server string is expected than what arrived.
+
+pytest will build a string that is the test ID for each fixture value
+in a parametrized fixture, e.g. ``test_ehlo[smtp.gmail.com]`` and
+``test_ehlo[mail.python.org]`` in the above examples.  These IDs can
+be used with ``-k`` to select specific cases to run, and they will
+also identify the specific case when one is failing.  Running pytest
+with ``--collect-only`` will show the generated IDs.
+
+Numbers, strings, booleans and None will have their usual string
+representation used in the test ID. For other objects, pytest will
+make a string based on the argument name.  It is possible to customise
+the string used in a test ID for a certain fixture value by using the
+``ids`` keyword argument::
+
+   # content of test_ids.py
+   import pytest
+
+   @pytest.fixture(params=[0, 1], ids=["spam", "ham"])
+   def a(request):
+       return request.param
+
+   def test_a(a):
+       pass
+
+   def idfn(fixture_value):
+       if fixture_value == 0:
+           return "eggs"
+       else:
+           return None
+
+   @pytest.fixture(params=[0, 1], ids=idfn)
+   def b(request):
+       return request.param
+
+   def test_b(b):
+       pass
+
+The above shows how ``ids`` can be either a list of strings to use or
+a function which will be called with the fixture value and then
+has to return a string to use.  In the latter case if the function
+return ``None`` then pytest's auto-generated ID will be used.
+
+Running the above tests results in the following test IDs being used::
+
+   $ pytest --collect-only
+   =========================== test session starts ============================
+   platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+   rootdir: $REGENDOC_TMPDIR, inifile:
+   collected 10 items
+   <Module 'test_anothersmtp.py'>
+     <Function 'test_showhelo[smtp.gmail.com]'>
+     <Function 'test_showhelo[mail.python.org]'>
+   <Module 'test_ids.py'>
+     <Function 'test_a[spam]'>
+     <Function 'test_a[ham]'>
+     <Function 'test_b[eggs]'>
+     <Function 'test_b[1]'>
+   <Module 'test_module.py'>
+     <Function 'test_ehlo[smtp.gmail.com]'>
+     <Function 'test_noop[smtp.gmail.com]'>
+     <Function 'test_ehlo[mail.python.org]'>
+     <Function 'test_noop[mail.python.org]'>
+   
+   ======================= no tests ran in 0.12 seconds =======================
+
+.. _`interdependent fixtures`:
+
+Modularity: using fixtures from a fixture function
+----------------------------------------------------------
+
+You can not only use fixtures in test functions but fixture functions
+can use other fixtures themselves.  This contributes to a modular design
+of your fixtures and allows re-use of framework-specific fixtures across
+many projects.  As a simple example, we can extend the previous example
+and instantiate an object ``app`` where we stick the already defined
+``smtp`` resource into it::
+
+    # content of test_appsetup.py
+
+    import pytest
+
+    class App(object):
+        def __init__(self, smtp):
+            self.smtp = smtp
+
+    @pytest.fixture(scope="module")
+    def app(smtp):
+        return App(smtp)
+
+    def test_smtp_exists(app):
+        assert app.smtp
+
+Here we declare an ``app`` fixture which receives the previously defined
+``smtp`` fixture and instantiates an ``App`` object with it.  Let's run it::
+
+    $ pytest -v test_appsetup.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 2 items
+    
+    test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED            [ 50%]
+    test_appsetup.py::test_smtp_exists[mail.python.org] PASSED           [100%]
+    
+    ========================= 2 passed in 0.12 seconds =========================
+
+Due to the parametrization of ``smtp`` the test will run twice with two
+different ``App`` instances and respective smtp servers.  There is no
+need for the ``app`` fixture to be aware of the ``smtp`` parametrization
+as pytest will fully analyse the fixture dependency graph.
+
+Note, that the ``app`` fixture has a scope of ``module`` and uses a
+module-scoped ``smtp`` fixture.  The example would still work if ``smtp``
+was cached on a ``session`` scope: it is fine for fixtures to use
+"broader" scoped fixtures but not the other way round:
+A session-scoped fixture could not use a module-scoped one in a
+meaningful way.
+
+
+.. _`automatic per-resource grouping`:
+
+Automatic grouping of tests by fixture instances
+----------------------------------------------------------
+
+.. regendoc: wipe
+
+pytest minimizes the number of active fixtures during test runs.
+If you have a parametrized fixture, then all the tests using it will
+first execute with one instance and then finalizers are called
+before the next fixture instance is created.  Among other things,
+this eases testing of applications which create and use global state.
+
+The following example uses two parametrized fixture, one of which is
+scoped on a per-module basis, and all the functions perform ``print`` calls
+to show the setup/teardown flow::
+
+    # content of test_module.py
+    import pytest
+
+    @pytest.fixture(scope="module", params=["mod1", "mod2"])
+    def modarg(request):
+        param = request.param
+        print ("  SETUP modarg %s" % param)
+        yield param
+        print ("  TEARDOWN modarg %s" % param)
+
+    @pytest.fixture(scope="function", params=[1,2])
+    def otherarg(request):
+        param = request.param
+        print ("  SETUP otherarg %s" % param)
+        yield param
+        print ("  TEARDOWN otherarg %s" % param)
+
+    def test_0(otherarg):
+        print ("  RUN test0 with otherarg %s" % otherarg)
+    def test_1(modarg):
+        print ("  RUN test1 with modarg %s" % modarg)
+    def test_2(otherarg, modarg):
+        print ("  RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg))
+
+
+Let's run the tests in verbose mode and with looking at the print-output::
+
+    $ pytest -v -s test_module.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
+    cachedir: .cache
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collecting ... collected 8 items
+    
+    test_module.py::test_0[1]   SETUP otherarg 1
+      RUN test0 with otherarg 1
+    PASSED                                     [ 12%]  TEARDOWN otherarg 1
+    
+    test_module.py::test_0[2]   SETUP otherarg 2
+      RUN test0 with otherarg 2
+    PASSED                                     [ 25%]  TEARDOWN otherarg 2
+    
+    test_module.py::test_1[mod1]   SETUP modarg mod1
+      RUN test1 with modarg mod1
+    PASSED                                  [ 37%]
+    test_module.py::test_2[1-mod1]   SETUP otherarg 1
+      RUN test2 with otherarg 1 and modarg mod1
+    PASSED                                [ 50%]  TEARDOWN otherarg 1
+    
+    test_module.py::test_2[2-mod1]   SETUP otherarg 2
+      RUN test2 with otherarg 2 and modarg mod1
+    PASSED                                [ 62%]  TEARDOWN otherarg 2
+    
+    test_module.py::test_1[mod2]   TEARDOWN modarg mod1
+      SETUP modarg mod2
+      RUN test1 with modarg mod2
+    PASSED                                  [ 75%]
+    test_module.py::test_2[1-mod2]   SETUP otherarg 1
+      RUN test2 with otherarg 1 and modarg mod2
+    PASSED                                [ 87%]  TEARDOWN otherarg 1
+    
+    test_module.py::test_2[2-mod2]   SETUP otherarg 2
+      RUN test2 with otherarg 2 and modarg mod2
+    PASSED                                [100%]  TEARDOWN otherarg 2
+      TEARDOWN modarg mod2
+    
+    
+    ========================= 8 passed in 0.12 seconds =========================
+
+You can see that the parametrized module-scoped ``modarg`` resource caused an
+ordering of test execution that lead to the fewest possible "active" resources.
+The finalizer for the ``mod1`` parametrized resource was executed before the
+``mod2`` resource was setup.
+
+In particular notice that test_0 is completely independent and finishes first.
+Then test_1 is executed with ``mod1``, then test_2 with ``mod1``, then test_1
+with ``mod2`` and finally test_2 with ``mod2``.
+
+The ``otherarg`` parametrized resource (having function scope) was set up before
+and teared down after every test that used it.
+
+
+.. _`usefixtures`:
+
+Using fixtures from classes, modules or projects
+----------------------------------------------------------------------
+
+.. regendoc:wipe
+
+Sometimes test functions do not directly need access to a fixture object.
+For example, tests may require to operate with an empty directory as the
+current working directory but otherwise do not care for the concrete
+directory.  Here is how you can use the standard `tempfile
+<http://docs.python.org/library/tempfile.html>`_ and pytest fixtures to
+achieve it.  We separate the creation of the fixture into a conftest.py
+file::
+
+    # content of conftest.py
+
+    import pytest
+    import tempfile
+    import os
+
+    @pytest.fixture()
+    def cleandir():
+        newpath = tempfile.mkdtemp()
+        os.chdir(newpath)
+
+and declare its use in a test module via a ``usefixtures`` marker::
+
+    # content of test_setenv.py
+    import os
+    import pytest
+
+    @pytest.mark.usefixtures("cleandir")
+    class TestDirectoryInit(object):
+        def test_cwd_starts_empty(self):
+            assert os.listdir(os.getcwd()) == []
+            with open("myfile", "w") as f:
+                f.write("hello")
+
+        def test_cwd_again_starts_empty(self):
+            assert os.listdir(os.getcwd()) == []
+
+Due to the ``usefixtures`` marker, the ``cleandir`` fixture
+will be required for the execution of each test method, just as if
+you specified a "cleandir" function argument to each of them.  Let's run it
+to verify our fixture is activated and the tests pass::
+
+    $ pytest -q
+    ..                                                                   [100%]
+    2 passed in 0.12 seconds
+
+You can specify multiple fixtures like this:
+
+.. code-block:: python
+
+    @pytest.mark.usefixtures("cleandir", "anotherfixture")
+
+and you may specify fixture usage at the test module level, using
+a generic feature of the mark mechanism:
+
+.. code-block:: python
+
+    pytestmark = pytest.mark.usefixtures("cleandir")
+
+Note that the assigned variable *must* be called ``pytestmark``, assigning e.g.
+``foomark`` will not activate the fixtures.
+
+Lastly you can put fixtures required by all tests in your project
+into an ini-file:
+
+.. code-block:: ini
+
+    # content of pytest.ini
+    [pytest]
+    usefixtures = cleandir
+
+
+.. _`autouse`:
+.. _`autouse fixtures`:
+
+Autouse fixtures (xUnit setup on steroids)
+----------------------------------------------------------------------
+
+.. regendoc:wipe
+
+Occasionally, you may want to have fixtures get invoked automatically
+without declaring a function argument explicitly or a `usefixtures`_ decorator.
+As a practical example, suppose we have a database fixture which has a
+begin/rollback/commit architecture and we want to automatically surround
+each test method by a transaction and a rollback.  Here is a dummy
+self-contained implementation of this idea::
+
+    # content of test_db_transact.py
+
+    import pytest
+
+    class DB(object):
+        def __init__(self):
+            self.intransaction = []
+        def begin(self, name):
+            self.intransaction.append(name)
+        def rollback(self):
+            self.intransaction.pop()
+
+    @pytest.fixture(scope="module")
+    def db():
+        return DB()
+
+    class TestClass(object):
+        @pytest.fixture(autouse=True)
+        def transact(self, request, db):
+            db.begin(request.function.__name__)
+            yield
+            db.rollback()
+
+        def test_method1(self, db):
+            assert db.intransaction == ["test_method1"]
+
+        def test_method2(self, db):
+            assert db.intransaction == ["test_method2"]
+
+The class-level ``transact`` fixture is marked with *autouse=true*
+which implies that all test methods in the class will use this fixture
+without a need to state it in the test function signature or with a
+class-level ``usefixtures`` decorator.
+
+If we run it, we get two passing tests::
+
+    $ pytest -q
+    ..                                                                   [100%]
+    2 passed in 0.12 seconds
+
+Here is how autouse fixtures work in other scopes:
+
+- autouse fixtures obey the ``scope=`` keyword-argument: if an autouse fixture
+  has ``scope='session'`` it will only be run once, no matter where it is
+  defined. ``scope='class'`` means it will be run once per class, etc.
+
+- if an autouse fixture is defined in a test module, all its test
+  functions automatically use it.
+
+- if an autouse fixture is defined in a conftest.py file then all tests in
+  all test modules below its directory will invoke the fixture.
+
+- lastly, and **please use that with care**: if you define an autouse
+  fixture in a plugin, it will be invoked for all tests in all projects
+  where the plugin is installed.  This can be useful if a fixture only
+  anyway works in the presence of certain settings e. g. in the ini-file.  Such
+  a global fixture should always quickly determine if it should do
+  any work and avoid otherwise expensive imports or computation.
+
+Note that the above ``transact`` fixture may very well be a fixture that
+you want to make available in your project without having it generally
+active.  The canonical way to do that is to put the transact definition
+into a conftest.py file **without** using ``autouse``::
+
+    # content of conftest.py
+    @pytest.fixture
+    def transact(request, db):
+        db.begin()
+        yield
+        db.rollback()
+
+and then e.g. have a TestClass using it by declaring the need::
+
+    @pytest.mark.usefixtures("transact")
+    class TestClass(object):
+        def test_method1(self):
+            ...
+
+All test methods in this TestClass will use the transaction fixture while
+other test classes or functions in the module will not use it unless
+they also add a ``transact`` reference.
+
+Overriding fixtures on various levels
+-------------------------------------
+
+In relatively large test suite, you most likely need to ``override`` a ``global`` or ``root`` fixture with a ``locally``
+defined one, keeping the test code readable and maintainable.
+
+Override a fixture on a folder (conftest) level
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Given the tests file structure is:
+
+::
+
+    tests/
+        __init__.py
+
+        conftest.py
+            # content of tests/conftest.py
+            import pytest
+
+            @pytest.fixture
+            def username():
+                return 'username'
+
+        test_something.py
+            # content of tests/test_something.py
+            def test_username(username):
+                assert username == 'username'
+
+        subfolder/
+            __init__.py
+
+            conftest.py
+                # content of tests/subfolder/conftest.py
+                import pytest
+
+                @pytest.fixture
+                def username(username):
+                    return 'overridden-' + username
+
+            test_something.py
+                # content of tests/subfolder/test_something.py
+                def test_username(username):
+                    assert username == 'overridden-username'
+
+As you can see, a fixture with the same name can be overridden for certain test folder level.
+Note that the ``base`` or ``super`` fixture can be accessed from the ``overriding``
+fixture easily - used in the example above.
+
+Override a fixture on a test module level
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Given the tests file structure is:
+
+::
+
+    tests/
+        __init__.py
+
+        conftest.py
+            # content of tests/conftest.py
+            @pytest.fixture
+            def username():
+                return 'username'
+
+        test_something.py
+            # content of tests/test_something.py
+            import pytest
+
+            @pytest.fixture
+            def username(username):
+                return 'overridden-' + username
+
+            def test_username(username):
+                assert username == 'overridden-username'
+
+        test_something_else.py
+            # content of tests/test_something_else.py
+            import pytest
+
+            @pytest.fixture
+            def username(username):
+                return 'overridden-else-' + username
+
+            def test_username(username):
+                assert username == 'overridden-else-username'
+
+In the example above, a fixture with the same name can be overridden for certain test module.
+
+
+Override a fixture with direct test parametrization
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Given the tests file structure is:
+
+::
+
+    tests/
+        __init__.py
+
+        conftest.py
+            # content of tests/conftest.py
+            import pytest
+
+            @pytest.fixture
+            def username():
+                return 'username'
+
+            @pytest.fixture
+            def other_username(username):
+                return 'other-' + username
+
+        test_something.py
+            # content of tests/test_something.py
+            import pytest
+
+            @pytest.mark.parametrize('username', ['directly-overridden-username'])
+            def test_username(username):
+                assert username == 'directly-overridden-username'
+
+            @pytest.mark.parametrize('username', ['directly-overridden-username-other'])
+            def test_username_other(other_username):
+                assert other_username == 'other-directly-overridden-username-other'
+
+In the example above, a fixture value is overridden by the test parameter value. Note that the value of the fixture
+can be overridden this way even if the test doesn't use it directly (doesn't mention it in the function prototype).
+
+
+Override a parametrized fixture with non-parametrized one and vice versa
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Given the tests file structure is:
+
+::
+
+    tests/
+        __init__.py
+
+        conftest.py
+            # content of tests/conftest.py
+            import pytest
+
+            @pytest.fixture(params=['one', 'two', 'three'])
+            def parametrized_username(request):
+                return request.param
+
+            @pytest.fixture
+            def non_parametrized_username(request):
+                return 'username'
+
+        test_something.py
+            # content of tests/test_something.py
+            import pytest
+
+            @pytest.fixture
+            def parametrized_username():
+                return 'overridden-username'
+
+            @pytest.fixture(params=['one', 'two', 'three'])
+            def non_parametrized_username(request):
+                return request.param
+
+            def test_username(parametrized_username):
+                assert parametrized_username == 'overridden-username'
+
+            def test_parametrized_username(non_parametrized_username):
+                assert non_parametrized_username in ['one', 'two', 'three']
+
+        test_something_else.py
+            # content of tests/test_something_else.py
+            def test_username(parametrized_username):
+                assert parametrized_username in ['one', 'two', 'three']
+
+            def test_username(non_parametrized_username):
+                assert non_parametrized_username == 'username'
+
+In the example above, a parametrized fixture is overridden with a non-parametrized version, and
+a non-parametrized fixture is overridden with a parametrized version for certain test module.
+The same applies for the test folder level obviously.
diff --git a/tools/third_party/pytest/doc/en/funcarg_compare.rst b/tools/third_party/pytest/doc/en/funcarg_compare.rst
new file mode 100644
index 0000000..b857a01
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/funcarg_compare.rst
@@ -0,0 +1,218 @@
+:orphan:
+
+.. _`funcargcompare`:
+
+pytest-2.3: reasoning for fixture/funcarg evolution
+=============================================================
+
+**Target audience**: Reading this document requires basic knowledge of 
+python testing, xUnit setup methods and the (previous) basic pytest 
+funcarg mechanism, see http://pytest.org/2.2.4/funcargs.html 
+If you are new to pytest, then you can simply ignore this
+section and read the other sections.
+
+.. currentmodule:: _pytest
+
+Shortcomings of the previous ``pytest_funcarg__`` mechanism
+--------------------------------------------------------------
+
+The pre pytest-2.3 funcarg mechanism calls a factory each time a
+funcarg for a test function is required.  If a factory wants to
+re-use a resource across different scopes, it often used 
+the ``request.cached_setup()`` helper to manage caching of 
+resources.  Here is a basic example how we could implement 
+a per-session Database object::
+
+    # content of conftest.py 
+    class Database(object):
+        def __init__(self):
+            print ("database instance created")
+        def destroy(self):
+            print ("database instance destroyed")
+
+    def pytest_funcarg__db(request):
+        return request.cached_setup(setup=DataBase, 
+                                    teardown=lambda db: db.destroy,
+                                    scope="session")
+
+There are several limitations and difficulties with this approach:
+
+1. Scoping funcarg resource creation is not straight forward, instead one must
+   understand the intricate cached_setup() method mechanics.
+
+2. parametrizing the "db" resource is not straight forward: 
+   you need to apply a "parametrize" decorator or implement a
+   :py:func:`~hookspec.pytest_generate_tests` hook 
+   calling :py:func:`~python.Metafunc.parametrize` which
+   performs parametrization at the places where the resource 
+   is used.  Moreover, you need to modify the factory to use an 
+   ``extrakey`` parameter containing ``request.param`` to the 
+   :py:func:`~python.Request.cached_setup` call.
+
+3. Multiple parametrized session-scoped resources will be active
+   at the same time, making it hard for them to affect global state
+   of the application under test.
+
+4. there is no way how you can make use of funcarg factories
+   in xUnit setup methods.
+
+5. A non-parametrized fixture function cannot use a parametrized 
+   funcarg resource if it isn't stated in the test function signature.
+
+All of these limitations are addressed with pytest-2.3 and its
+improved :ref:`fixture mechanism <fixture>`.
+
+
+Direct scoping of fixture/funcarg factories
+--------------------------------------------------------
+
+Instead of calling cached_setup() with a cache scope, you can use the
+:ref:`@pytest.fixture <pytest.fixture>` decorator and directly state
+the scope::
+
+    @pytest.fixture(scope="session")
+    def db(request):
+        # factory will only be invoked once per session - 
+        db = DataBase()
+        request.addfinalizer(db.destroy)  # destroy when session is finished
+        return db
+
+This factory implementation does not need to call ``cached_setup()`` anymore
+because it will only be invoked once per session.  Moreover, the 
+``request.addfinalizer()`` registers a finalizer according to the specified
+resource scope on which the factory function is operating.
+
+
+Direct parametrization of funcarg resource factories 
+----------------------------------------------------------
+
+Previously, funcarg factories could not directly cause parametrization.
+You needed to specify a ``@parametrize`` decorator on your test function
+or implement a ``pytest_generate_tests`` hook to perform
+parametrization, i.e. calling a test multiple times with different value
+sets.  pytest-2.3 introduces a decorator for use on the factory itself::
+
+    @pytest.fixture(params=["mysql", "pg"])
+    def db(request):
+        ... # use request.param
+
+Here the factory will be invoked twice (with the respective "mysql" 
+and "pg" values set as ``request.param`` attributes) and all of 
+the tests requiring "db" will run twice as well.  The "mysql" and 
+"pg" values will also be used for reporting the test-invocation variants.
+
+This new way of parametrizing funcarg factories should in many cases
+allow to re-use already written factories because effectively
+``request.param`` was already used when test functions/classes were
+parametrized via
+:py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls.
+
+Of course it's perfectly fine to combine parametrization and scoping::
+
+    @pytest.fixture(scope="session", params=["mysql", "pg"])
+    def db(request):
+        if request.param == "mysql":
+            db = MySQL()
+        elif request.param == "pg":
+            db = PG()
+        request.addfinalizer(db.destroy)  # destroy when session is finished
+        return db
+
+This would execute all tests requiring the per-session "db" resource twice,
+receiving the values created by the two respective invocations to the
+factory function.
+
+
+No ``pytest_funcarg__`` prefix when using @fixture decorator
+-------------------------------------------------------------------
+
+When using the ``@fixture`` decorator the name of the function
+denotes the name under which the resource can be accessed as a function
+argument::
+
+    @pytest.fixture()
+    def db(request):
+        ...
+
+The name under which the funcarg resource can be requested is ``db``.
+
+You can still use the "old" non-decorator way of specifying funcarg factories 
+aka::
+
+    def pytest_funcarg__db(request):
+        ...
+
+
+But it is then not possible to define scoping and parametrization.
+It is thus recommended to use the factory decorator.
+
+
+solving per-session setup / autouse fixtures
+--------------------------------------------------------------
+
+pytest for a long time offered a pytest_configure and a pytest_sessionstart
+hook which are often used to setup global resources.  This suffers from
+several problems:
+
+1. in distributed testing the master process would setup test resources
+   that are never needed because it only co-ordinates the test run
+   activities of the slave processes.  
+
+2. if you only perform a collection (with "--collect-only") 
+   resource-setup will still be executed.  
+
+3. If a pytest_sessionstart is contained in some subdirectories
+   conftest.py file, it will not be called.  This stems from the
+   fact that this hook is actually used for reporting, in particular
+   the test-header with platform/custom information.
+
+Moreover, it was not easy to define a scoped setup from plugins or
+conftest files other than to implement a ``pytest_runtest_setup()`` hook
+and caring for scoping/caching yourself.  And it's virtually impossible
+to do this with parametrization as ``pytest_runtest_setup()`` is called
+during test execution and parametrization happens at collection time.
+
+It follows that pytest_configure/session/runtest_setup are often not
+appropriate for implementing common fixture needs.  Therefore,
+pytest-2.3 introduces :ref:`autouse fixtures` which fully
+integrate with the generic :ref:`fixture mechanism <fixture>`
+and obsolete many prior uses of pytest hooks.
+
+funcargs/fixture discovery now happens at collection time
+---------------------------------------------------------------------
+
+Since pytest-2.3, discovery of fixture/funcarg factories are taken care of
+at collection time.  This is more efficient especially for large test suites.
+Moreover, a call to "pytest --collect-only" should be able to in the future
+show a lot of setup-information and thus presents a nice method to get an
+overview of fixture management in your project.
+
+.. _`compatibility notes`:
+
+.. _`funcargscompat`:
+
+Conclusion and compatibility notes
+---------------------------------------------------------
+
+**funcargs** were originally introduced to pytest-2.0.  In pytest-2.3 
+the mechanism was extended and refined and is now described as
+fixtures:
+
+* previously funcarg factories were specified with a special 
+  ``pytest_funcarg__NAME`` prefix instead of using the 
+  ``@pytest.fixture`` decorator.
+
+* Factories received a ``request`` object which managed caching through
+  ``request.cached_setup()`` calls and allowed using other funcargs via 
+  ``request.getfuncargvalue()`` calls.  These intricate APIs made it hard 
+  to do proper parametrization and implement resource caching. The
+  new :py:func:`pytest.fixture` decorator allows to declare the scope
+  and let pytest figure things out for you.
+
+* if you used parametrization and funcarg factories which made use of
+  ``request.cached_setup()`` it is recommended to invest a few minutes
+  and simplify your fixture function code to use the :ref:`@pytest.fixture`
+  decorator instead.  This will also allow to take advantage of 
+  the automatic per-resource grouping of tests.
+
+
diff --git a/tools/pytest/doc/en/funcargs.rst b/tools/third_party/pytest/doc/en/funcargs.rst
similarity index 100%
rename from tools/pytest/doc/en/funcargs.rst
rename to tools/third_party/pytest/doc/en/funcargs.rst
diff --git a/tools/third_party/pytest/doc/en/genapi.py b/tools/third_party/pytest/doc/en/genapi.py
new file mode 100644
index 0000000..0ede44f
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/genapi.py
@@ -0,0 +1,41 @@
+import textwrap
+import inspect
+
+class Writer(object):
+    def __init__(self, clsname):
+        self.clsname = clsname
+
+    def __enter__(self):
+        self.file = open("%s.api" % self.clsname, "w")
+        return self
+
+    def __exit__(self, *args):
+        self.file.close()
+        print "wrote", self.file.name
+
+    def line(self, line):
+        self.file.write(line+"\n")
+
+    def docmethod(self, method):
+        doc = " ".join(method.__doc__.split())
+        indent = "         "
+        w = textwrap.TextWrapper(initial_indent=indent,
+                                 subsequent_indent=indent)
+
+        spec = inspect.getargspec(method)
+        del spec.args[0]
+        self.line(".. py:method:: " + method.__name__ +
+                  inspect.formatargspec(*spec))
+        self.line("")
+        self.line(w.fill(doc))
+        self.line("")
+
+def pytest_funcarg__a(request):
+    with Writer("request") as writer:
+        writer.docmethod(request.getfixturevalue)
+        writer.docmethod(request.cached_setup)
+        writer.docmethod(request.addfinalizer)
+        writer.docmethod(request.applymarker)
+
+def test_hello(a):
+    pass
diff --git a/tools/third_party/pytest/doc/en/getting-started.rst b/tools/third_party/pytest/doc/en/getting-started.rst
new file mode 100644
index 0000000..64b0108
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/getting-started.rst
@@ -0,0 +1,198 @@
+Installation and Getting Started
+===================================
+
+**Pythons**: Python 2.7, 3.4, 3.5, 3.6, Jython, PyPy-2.3
+
+**Platforms**: Unix/Posix and Windows
+
+**PyPI package name**: `pytest <http://pypi.python.org/pypi/pytest>`_
+
+**dependencies**: `py <http://pypi.python.org/pypi/py>`_,
+`colorama (Windows) <http://pypi.python.org/pypi/colorama>`_,
+
+**documentation as PDF**: `download latest <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
+
+.. _`getstarted`:
+.. _installation:
+
+Installation
+----------------------------------------
+
+Installation::
+
+    pip install -U pytest
+
+To check your installation has installed the correct version::
+
+    $ pytest --version
+    This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py
+
+.. _`simpletest`:
+
+Our first test run
+----------------------------------------------------------
+
+Let's create a first test file with a simple test function::
+
+    # content of test_sample.py
+    def func(x):
+        return x + 1
+
+    def test_answer():
+        assert func(3) == 5
+
+That's it. You can execute the test function now::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_sample.py F                                                     [100%]
+    
+    ================================= FAILURES =================================
+    _______________________________ test_answer ________________________________
+    
+        def test_answer():
+    >       assert func(3) == 5
+    E       assert 4 == 5
+    E        +  where 4 = func(3)
+    
+    test_sample.py:5: AssertionError
+    ========================= 1 failed in 0.12 seconds =========================
+
+We got a failure report because our little ``func(3)`` call did not return ``5``.
+
+.. note::
+
+    You can simply use the ``assert`` statement for asserting test
+    expectations.  pytest's :ref:`assert introspection` will intelligently
+    report intermediate values of the assert expression freeing
+    you from the need to learn the many names of `JUnit legacy methods`_.
+
+.. _`JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
+
+.. _`assert statement`: http://docs.python.org/reference/simple_stmts.html#the-assert-statement
+
+Running multiple tests
+----------------------------------------------------------
+
+``pytest`` will run all files in the current directory and its subdirectories of the form test_*.py or \*_test.py. More generally, it follows :ref:`standard test discovery rules <test discovery>`.
+
+
+Asserting that a certain exception is raised
+--------------------------------------------------------------
+
+If you want to assert that some code raises an exception you can
+use the ``raises`` helper::
+
+    # content of test_sysexit.py
+    import pytest
+    def f():
+        raise SystemExit(1)
+
+    def test_mytest():
+        with pytest.raises(SystemExit):
+            f()
+
+Running it with, this time in "quiet" reporting mode::
+
+    $ pytest -q test_sysexit.py
+    .                                                                    [100%]
+    1 passed in 0.12 seconds
+
+Grouping multiple tests in a class
+--------------------------------------------------------------
+
+Once you start to have more than a few tests it often makes sense
+to group tests logically, in classes and modules.  Let's write a class
+containing two tests::
+
+    # content of test_class.py
+    class TestClass(object):
+        def test_one(self):
+            x = "this"
+            assert 'h' in x
+
+        def test_two(self):
+            x = "hello"
+            assert hasattr(x, 'check')
+
+The two tests are found because of the standard :ref:`test discovery`.
+There is no need to subclass anything.  We can simply
+run the module by passing its filename::
+
+    $ pytest -q test_class.py
+    .F                                                                   [100%]
+    ================================= FAILURES =================================
+    ____________________________ TestClass.test_two ____________________________
+    
+    self = <test_class.TestClass object at 0xdeadbeef>
+    
+        def test_two(self):
+            x = "hello"
+    >       assert hasattr(x, 'check')
+    E       AssertionError: assert False
+    E        +  where False = hasattr('hello', 'check')
+    
+    test_class.py:8: AssertionError
+    1 failed, 1 passed in 0.12 seconds
+
+The first test passed, the second failed. Again we can easily see
+the intermediate values used in the assertion, helping us to
+understand the reason for the failure.
+
+Going functional: requesting a unique temporary directory
+--------------------------------------------------------------
+
+For functional tests one often needs to create some files
+and pass them to application objects.  pytest provides
+:ref:`builtinfixtures` which allow to request arbitrary
+resources, for example a unique temporary directory::
+
+    # content of test_tmpdir.py
+    def test_needsfiles(tmpdir):
+        print (tmpdir)
+        assert 0
+
+We list the name ``tmpdir`` in the test function signature and
+``pytest`` will lookup and call a fixture factory to create the resource
+before performing the test function call.  Let's just run it::
+
+    $ pytest -q test_tmpdir.py
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    _____________________________ test_needsfiles ______________________________
+    
+    tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')
+    
+        def test_needsfiles(tmpdir):
+            print (tmpdir)
+    >       assert 0
+    E       assert 0
+    
+    test_tmpdir.py:3: AssertionError
+    --------------------------- Captured stdout call ---------------------------
+    PYTEST_TMPDIR/test_needsfiles0
+    1 failed in 0.12 seconds
+
+Before the test runs, a unique-per-test-invocation temporary directory
+was created.  More info at :ref:`tmpdir handling`.
+
+You can find out what kind of builtin :ref:`fixtures` exist by typing::
+
+    pytest --fixtures   # shows builtin and custom fixtures
+
+Where to go next
+-------------------------------------
+
+Here are a few suggestions where to go next:
+
+* :ref:`cmdline` for command line invocation examples
+* :ref:`good practices <goodpractices>` for virtualenv, test layout
+* :ref:`existingtestsuite` for working with pre-existing tests
+* :ref:`fixtures` for providing a functional baseline to your tests
+* :ref:`plugins` managing and writing plugins
+
+.. include:: links.inc
diff --git a/tools/third_party/pytest/doc/en/goodpractices.rst b/tools/third_party/pytest/doc/en/goodpractices.rst
new file mode 100644
index 0000000..16fdd24
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/goodpractices.rst
@@ -0,0 +1,299 @@
+.. highlightlang:: python
+.. _`goodpractices`:
+
+Good Integration Practices
+=================================================
+
+
+.. _`test discovery`:
+.. _`Python test discovery`:
+
+Conventions for Python test discovery
+-------------------------------------------------
+
+``pytest`` implements the following standard test discovery:
+
+* If no arguments are specified then collection starts from :confval:`testpaths`
+  (if configured) or the current directory. Alternatively, command line arguments
+  can be used in any combination of directories, file names or node ids.
+* Recurse into directories, unless they match :confval:`norecursedirs`.
+* In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
+* From those files, collect test items:
+
+  * ``test_`` prefixed test functions or methods outside of class
+  * ``test_`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method)
+
+For examples of how to customize your test discovery :doc:`example/pythoncollection`.
+
+Within Python modules, ``pytest`` also discovers tests using the standard
+:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
+
+
+Choosing a test layout / import rules
+-------------------------------------
+
+``pytest`` supports two common test layouts:
+
+Tests outside application code
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Putting tests into an extra directory outside your actual application code
+might be useful if you have many functional tests or for other reasons want
+to keep tests separate from actual application code (often a good idea)::
+
+    setup.py
+    mypkg/
+        __init__.py
+        app.py
+        view.py
+    tests/
+        test_app.py
+        test_view.py
+        ...
+
+This way your tests can run easily against an installed version
+of ``mypkg``.
+
+Note that using this scheme your test files must have **unique names**, because
+``pytest`` will import them as *top-level* modules since there are no packages
+to derive a full package name from. In other words, the test files in the example above will
+be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
+``sys.path``.
+
+If you need to have test modules with the same name, you might add ``__init__.py`` files to your
+``tests`` folder and subfolders, changing them to packages::
+
+    setup.py
+    mypkg/
+        ...
+    tests/
+        __init__.py
+        foo/
+            __init__.py
+            test_view.py
+        bar/
+            __init__.py
+            test_view.py
+
+Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing
+you to have modules with the same name. But now this introduces a subtle problem: in order to load
+the test modules from the ``tests`` directory, pytest prepends the root of the repository to
+``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
+This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
+because you want to test the *installed* version of your package, not the local code from the repository.
+
+In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
+sub-directory of your root::
+
+    setup.py
+    src/
+        mypkg/
+            __init__.py
+            app.py
+            view.py
+    tests/
+        __init__.py
+        foo/
+            __init__.py
+            test_view.py
+        bar/
+            __init__.py
+            test_view.py
+
+
+This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
+`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
+
+Tests as part of application code
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Inlining test directories into your application package
+is useful if you have direct relation between tests and application modules and
+want to distribute them along with your application::
+
+    setup.py
+    mypkg/
+        __init__.py
+        app.py
+        view.py
+        test/
+            __init__.py
+            test_app.py
+            test_view.py
+            ...
+
+In this scheme, it is easy to run your tests using the ``--pyargs`` option::
+
+    pytest --pyargs mypkg
+
+``pytest`` will discover where ``mypkg`` is installed and collect tests from there.
+
+Note that this layout also works in conjunction with the ``src`` layout mentioned in the previous section.
+
+
+.. note::
+
+    You can use Python3 namespace packages (PEP420) for your application
+    but pytest will still perform `test package name`_ discovery based on the
+    presence of ``__init__.py`` files.  If you use one of the
+    two recommended file system layouts above but leave away the ``__init__.py``
+    files from your directories it should just work on Python3.3 and above.  From
+    "inlined tests", however, you will need to use absolute imports for
+    getting at your application code.
+
+.. _`test package name`:
+
+.. note::
+
+    If ``pytest`` finds a "a/b/test_module.py" test file while
+    recursing into the filesystem it determines the import name
+    as follows:
+
+    * determine ``basedir``: this is the first "upward" (towards the root)
+      directory not containing an ``__init__.py``.  If e.g. both ``a``
+      and ``b`` contain an ``__init__.py`` file then the parent directory
+      of ``a`` will become the ``basedir``.
+
+    * perform ``sys.path.insert(0, basedir)`` to make the test module
+      importable under the fully qualified import name.
+
+    * ``import a.b.test_module`` where the path is determined
+      by converting path separators ``/`` into "." characters.  This means
+      you must follow the convention of having directory and file
+      names map directly to the import names.
+
+    The reason for this somewhat evolved importing technique is
+    that in larger projects multiple test modules might import
+    from each other and thus deriving a canonical import name helps
+    to avoid surprises such as a test module getting imported twice.
+
+
+.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
+.. _`buildout`: http://www.buildout.org/
+.. _pip: http://pypi.python.org/pypi/pip
+
+.. _`use tox`:
+
+Tox
+------
+
+For development, we recommend to use virtualenv_ environments and pip_
+for installing your application and any dependencies
+as well as the ``pytest`` package itself. This ensures your code and
+dependencies are isolated from the system Python installation.
+
+You can then install your package in "editable" mode::
+
+     pip install -e .
+
+which lets you change your source code (both tests and application) and rerun tests at will.
+This is similar to running `python setup.py develop` or `conda develop` in that it installs
+your package using a symlink to your development code.
+
+Once you are done with your work and want to make sure that your actual
+package passes all tests you may want to look into `tox`_, the
+virtualenv test automation tool and its `pytest support
+<https://tox.readthedocs.io/en/latest/example/pytest.html>`_.
+Tox helps you to setup virtualenv environments with pre-defined
+dependencies and then executing a pre-configured test command with
+options.  It will run tests against the installed package and not
+against your source code checkout, helping to detect packaging
+glitches.
+
+
+Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
+--------------------------------------------------------------------------
+
+You can integrate test runs into your setuptools based project
+with the `pytest-runner <https://pypi.python.org/pypi/pytest-runner>`_ plugin.
+
+Add this to ``setup.py`` file:
+
+.. code-block:: python
+
+    from setuptools import setup
+
+    setup(
+        #...,
+        setup_requires=['pytest-runner', ...],
+        tests_require=['pytest', ...],
+        #...,
+    )
+
+
+And create an alias into ``setup.cfg`` file:
+
+
+.. code-block:: ini
+
+    [aliases]
+    test=pytest
+
+If you now type::
+
+    python setup.py test
+
+this will execute your tests using ``pytest-runner``. As this is a
+standalone version of ``pytest`` no prior installation whatsoever is
+required for calling the test command. You can also pass additional
+arguments to pytest such as your test directory or other
+options using ``--addopts``.
+
+You can also specify other pytest-ini options in your ``setup.cfg`` file
+by putting them into a ``[tool:pytest]`` section:
+
+.. code-block:: ini
+
+    [tool:pytest]
+    addopts = --verbose
+    python_files = testing/*/*.py
+
+
+Manual Integration
+^^^^^^^^^^^^^^^^^^
+
+If for some reason you don't want/can't use ``pytest-runner``, you can write
+your own setuptools Test command for invoking pytest.
+
+.. code-block:: python
+
+    import sys
+
+    from setuptools.command.test import test as TestCommand
+
+
+    class PyTest(TestCommand):
+        user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")]
+
+        def initialize_options(self):
+            TestCommand.initialize_options(self)
+            self.pytest_args = ''
+
+        def run_tests(self):
+            import shlex
+            #import here, cause outside the eggs aren't loaded
+            import pytest
+            errno = pytest.main(shlex.split(self.pytest_args))
+            sys.exit(errno)
+
+
+    setup(
+        #...,
+        tests_require=['pytest'],
+        cmdclass = {'test': PyTest},
+        )
+
+Now if you run::
+
+    python setup.py test
+
+this will download ``pytest`` if needed and then run your tests
+as you would expect it to. You can pass a single string of arguments
+using the ``--pytest-args`` or ``-a`` command-line option. For example::
+
+    python setup.py test -a "--durations=5"
+
+is equivalent to running ``pytest --durations=5``.
+
+
+.. include:: links.inc
diff --git a/tools/third_party/pytest/doc/en/historical-notes.rst b/tools/third_party/pytest/doc/en/historical-notes.rst
new file mode 100644
index 0000000..028ceff
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/historical-notes.rst
@@ -0,0 +1,177 @@
+Historical Notes
+================
+
+This page lists features or behavior from previous versions of pytest which have changed over the years. They are
+kept here as a historical note so users looking at old code can find documentation related to them.
+
+cache plugin integrated into the core
+-------------------------------------
+
+.. versionadded:: 2.8
+
+The functionality of the :ref:`core cache <cache>` plugin was previously distributed
+as a third party plugin named ``pytest-cache``.  The core plugin
+is compatible regarding command line options and API usage except that you
+can only store/receive data between test runs that is json-serializable.
+
+
+funcargs and ``pytest_funcarg__``
+---------------------------------
+
+.. versionchanged:: 2.3
+
+In versions prior to 2.3 there was no ``@pytest.fixture`` marker
+and you had to use a magic ``pytest_funcarg__NAME`` prefix
+for the fixture factory.  This remains and will remain supported
+but is not anymore advertised as the primary means of declaring fixture
+functions.
+
+
+``@pytest.yield_fixture`` decorator
+-----------------------------------
+
+.. versionchanged:: 2.10
+
+Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
+had to mark a fixture using the ``yield_fixture`` marker. From 2.10 onward, normal
+fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
+and considered deprecated.
+
+
+``[pytest]`` header in ``setup.cfg``
+------------------------------------
+
+.. versionchanged:: 3.0
+
+Prior to 3.0, the supported section name was ``[pytest]``. Due to how
+this may collide with some distutils commands, the recommended
+section name for ``setup.cfg`` files is now ``[tool:pytest]``.
+
+Note that for ``pytest.ini`` and ``tox.ini`` files the section
+name is ``[pytest]``.
+
+
+Applying marks to ``@pytest.mark.parametrize`` parameters
+---------------------------------------------------------
+
+.. versionchanged:: 3.1
+
+Prior to version 3.1 the supported mechanism for marking values
+used the syntax::
+
+    import pytest
+    @pytest.mark.parametrize("test_input,expected", [
+        ("3+5", 8),
+        ("2+4", 6),
+        pytest.mark.xfail(("6*9", 42),),
+    ])
+    def test_eval(test_input, expected):
+        assert eval(test_input) == expected
+
+
+This was an initial hack to support the feature but soon was demonstrated to be incomplete,
+broken for passing functions or applying multiple marks with the same name but different parameters.
+
+The old syntax is planned to be removed in pytest-4.0.
+
+
+``@pytest.mark.parametrize`` argument names as a tuple
+------------------------------------------------------
+
+.. versionchanged:: 2.4
+
+In versions prior to 2.4 one needed to specify the argument
+names as a tuple.  This remains valid but the simpler ``"name1,name2,..."``
+comma-separated-string syntax is now advertised first because
+it's easier to write and produces less line noise.
+
+
+setup: is now an "autouse fixture"
+----------------------------------
+
+.. versionchanged:: 2.3
+
+During development prior to the pytest-2.3 release the name
+``pytest.setup`` was used but before the release it was renamed
+and moved to become part of the general fixture mechanism,
+namely :ref:`autouse fixtures`
+
+
+.. _string conditions:
+
+Conditions as strings instead of booleans
+-----------------------------------------
+
+.. versionchanged:: 2.4
+
+Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
+to use strings::
+
+    import sys
+    @pytest.mark.skipif("sys.version_info >= (3,3)")
+    def test_function():
+        ...
+
+During test function setup the skipif condition is evaluated by calling
+``eval('sys.version_info >= (3,0)', namespace)``.  The namespace contains
+all the module globals, and ``os`` and ``sys`` as a minimum.
+
+Since pytest-2.4 :ref:`boolean conditions <condition booleans>` are considered preferable
+because markers can then be freely imported between test modules.
+With strings you need to import not only the marker but all variables
+used by the marker, which violates encapsulation.
+
+The reason for specifying the condition as a string was that ``pytest`` can
+report a summary of skip conditions based purely on the condition string.
+With conditions as booleans you are required to specify a ``reason`` string.
+
+Note that string conditions will remain fully supported and you are free
+to use them if you have no need for cross-importing markers.
+
+The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
+or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
+dictionary which is constructed as follows:
+
+* the namespace is initialized by putting the ``sys`` and ``os`` modules
+  and the pytest ``config`` object into it.
+
+* updated with the module globals of the test function for which the
+  expression is applied.
+
+The pytest ``config`` object allows you to skip based on a test
+configuration value which you might have added::
+
+    @pytest.mark.skipif("not config.getvalue('db')")
+    def test_function(...):
+        ...
+
+The equivalent with "boolean conditions" is::
+
+    @pytest.mark.skipif(not pytest.config.getvalue("db"),
+                        reason="--db was not specified")
+    def test_function(...):
+        pass
+
+.. note::
+
+    You cannot use ``pytest.config.getvalue()`` in code
+    imported before pytest's argument parsing takes place.  For example,
+    ``conftest.py`` files are imported before command line parsing and thus
+    ``config.getvalue()`` will not execute correctly.
+
+``pytest.set_trace()``
+----------------------
+
+.. versionchanged:: 2.4
+
+Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``::
+
+    import pytest
+    def test_function():
+        ...
+        pytest.set_trace()    # invoke PDB debugger and tracing
+
+
+This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
+
+For more details see :ref:`breakpoints`.
diff --git a/tools/pytest/doc/en/img/cramer2.png b/tools/third_party/pytest/doc/en/img/cramer2.png
similarity index 100%
rename from tools/pytest/doc/en/img/cramer2.png
rename to tools/third_party/pytest/doc/en/img/cramer2.png
Binary files differ
diff --git a/tools/pytest/doc/en/img/freiburg2.jpg b/tools/third_party/pytest/doc/en/img/freiburg2.jpg
similarity index 100%
rename from tools/pytest/doc/en/img/freiburg2.jpg
rename to tools/third_party/pytest/doc/en/img/freiburg2.jpg
Binary files differ
diff --git a/tools/pytest/doc/en/img/gaynor3.png b/tools/third_party/pytest/doc/en/img/gaynor3.png
similarity index 100%
rename from tools/pytest/doc/en/img/gaynor3.png
rename to tools/third_party/pytest/doc/en/img/gaynor3.png
Binary files differ
diff --git a/tools/pytest/doc/en/img/keleshev.png b/tools/third_party/pytest/doc/en/img/keleshev.png
similarity index 100%
rename from tools/pytest/doc/en/img/keleshev.png
rename to tools/third_party/pytest/doc/en/img/keleshev.png
Binary files differ
diff --git a/tools/pytest/doc/en/img/pullrequest.png b/tools/third_party/pytest/doc/en/img/pullrequest.png
similarity index 100%
rename from tools/pytest/doc/en/img/pullrequest.png
rename to tools/third_party/pytest/doc/en/img/pullrequest.png
Binary files differ
diff --git a/tools/pytest/doc/en/img/pylib.png b/tools/third_party/pytest/doc/en/img/pylib.png
similarity index 100%
rename from tools/pytest/doc/en/img/pylib.png
rename to tools/third_party/pytest/doc/en/img/pylib.png
Binary files differ
diff --git a/tools/pytest/doc/en/img/pytest1.png b/tools/third_party/pytest/doc/en/img/pytest1.png
similarity index 100%
rename from tools/pytest/doc/en/img/pytest1.png
rename to tools/third_party/pytest/doc/en/img/pytest1.png
Binary files differ
diff --git a/tools/pytest/doc/en/img/pytest1favi.ico b/tools/third_party/pytest/doc/en/img/pytest1favi.ico
similarity index 100%
rename from tools/pytest/doc/en/img/pytest1favi.ico
rename to tools/third_party/pytest/doc/en/img/pytest1favi.ico
Binary files differ
diff --git a/tools/pytest/doc/en/img/theuni.png b/tools/third_party/pytest/doc/en/img/theuni.png
similarity index 100%
rename from tools/pytest/doc/en/img/theuni.png
rename to tools/third_party/pytest/doc/en/img/theuni.png
Binary files differ
diff --git a/tools/third_party/pytest/doc/en/index.rst b/tools/third_party/pytest/doc/en/index.rst
new file mode 100644
index 0000000..66c59f0
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/index.rst
@@ -0,0 +1,90 @@
+:orphan:
+
+.. _features:
+
+pytest: helps you write better programs
+=======================================
+
+
+The ``pytest`` framework makes it easy to write small tests, yet
+scales to support complex functional testing for applications and libraries.
+
+An example of a simple test:
+
+.. code-block:: python
+
+    # content of test_sample.py
+    def inc(x):
+        return x + 1
+
+    def test_answer():
+        assert inc(3) == 5
+
+
+To execute it::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_sample.py F                                                     [100%]
+    
+    ================================= FAILURES =================================
+    _______________________________ test_answer ________________________________
+    
+        def test_answer():
+    >       assert inc(3) == 5
+    E       assert 4 == 5
+    E        +  where 4 = inc(3)
+    
+    test_sample.py:5: AssertionError
+    ========================= 1 failed in 0.12 seconds =========================
+
+Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used.
+See :ref:`Getting Started <getstarted>` for more examples.
+
+
+Features
+--------
+
+- Detailed info on failing :ref:`assert statements <assert>` (no need to remember ``self.assert*`` names);
+
+- :ref:`Auto-discovery <test discovery>` of test modules and functions;
+
+- :ref:`Modular fixtures <fixture>` for managing small or parametrized long-lived test resources;
+
+- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box;
+
+- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
+
+- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
+
+
+Documentation
+-------------
+
+Please see :ref:`Contents <toc>` for full documentation, including installation, tutorials and PDF documents.
+
+
+Bugs/Requests
+-------------
+
+Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
+
+
+Changelog
+---------
+
+Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each version.
+
+
+License
+-------
+
+Copyright Holger Krekel and others, 2004-2017.
+
+Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
+
+.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
diff --git a/tools/third_party/pytest/doc/en/license.rst b/tools/third_party/pytest/doc/en/license.rst
new file mode 100644
index 0000000..b8c0dce
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/license.rst
@@ -0,0 +1,32 @@
+.. _license:
+
+License
+-------
+
+Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
+
+::
+
+    The MIT License (MIT)
+
+    Copyright (c) 2004-2017 Holger Krekel and others
+
+    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.
+
+.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
diff --git a/tools/third_party/pytest/doc/en/links.inc b/tools/third_party/pytest/doc/en/links.inc
new file mode 100644
index 0000000..b69390b
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/links.inc
@@ -0,0 +1,21 @@
+
+.. _`skipping plugin`: plugin/skipping.html
+.. _`funcargs mechanism`: funcargs.html
+.. _`doctest.py`: http://docs.python.org/library/doctest.html
+.. _`xUnit style setup`: xunit_setup.html
+.. _`pytest_nose`: plugin/nose.html
+.. _`reStructured Text`: http://docutils.sourceforge.net
+.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
+.. _nose: https://nose.readthedocs.io/en/latest/
+.. _pytest: http://pypi.python.org/pypi/pytest
+.. _mercurial: http://mercurial.selenic.com/wiki/
+.. _`setuptools`: http://pypi.python.org/pypi/setuptools
+.. _`easy_install`:
+.. _`distribute docs`:
+.. _`distribute`: http://pypi.python.org/pypi/distribute
+.. _`pip`: http://pypi.python.org/pypi/pip
+.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
+.. _hudson: http://hudson-ci.org/
+.. _jenkins: http://jenkins-ci.org/
+.. _tox: http://testrun.org/tox
+.. _pylib: https://py.readthedocs.io/en/latest/
diff --git a/tools/third_party/pytest/doc/en/logging.rst b/tools/third_party/pytest/doc/en/logging.rst
new file mode 100644
index 0000000..e3bf560
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/logging.rst
@@ -0,0 +1,192 @@
+.. _logging:
+
+Logging
+-------
+
+.. versionadded 3.3.0
+
+.. note::
+
+   This feature is a drop-in replacement for the `pytest-catchlog
+   <https://pypi.org/project/pytest-catchlog/>`_ plugin and they will conflict
+   with each other. The backward compatibility API with ``pytest-capturelog``
+   has been dropped when this feature was introduced, so if for that reason you
+   still need ``pytest-catchlog`` you can disable the internal feature by
+   adding to your ``pytest.ini``:
+
+   .. code-block:: ini
+
+       [pytest]
+           addopts=-p no:logging
+
+Log messages are captured by default and for each failed test will be shown in
+the same manner as captured stdout and stderr.
+
+Running without options::
+
+    pytest
+
+Shows failed tests like so::
+
+    ----------------------- Captured stdlog call ----------------------
+    test_reporting.py    26 INFO     text going to logger
+    ----------------------- Captured stdout call ----------------------
+    text going to stdout
+    ----------------------- Captured stderr call ----------------------
+    text going to stderr
+    ==================== 2 failed in 0.02 seconds =====================
+
+By default each captured log message shows the module, line number, log level
+and message.  Showing the exact module and line number is useful for testing and
+debugging.  If desired the log format and date format can be specified to
+anything that the logging module supports.
+
+Running pytest specifying formatting options::
+
+    pytest --log-format="%(asctime)s %(levelname)s %(message)s" \
+            --log-date-format="%Y-%m-%d %H:%M:%S"
+
+Shows failed tests like so::
+
+    ----------------------- Captured stdlog call ----------------------
+    2010-04-10 14:48:44 INFO text going to logger
+    ----------------------- Captured stdout call ----------------------
+    text going to stdout
+    ----------------------- Captured stderr call ----------------------
+    text going to stderr
+    ==================== 2 failed in 0.02 seconds =====================
+
+These options can also be customized through a configuration file:
+
+.. code-block:: ini
+
+    [pytest]
+    log_format = %(asctime)s %(levelname)s %(message)s
+    log_date_format = %Y-%m-%d %H:%M:%S
+
+Further it is possible to disable reporting logs on failed tests completely
+with::
+
+    pytest --no-print-logs
+
+Or in you ``pytest.ini``:
+
+.. code-block:: ini
+
+  [pytest]
+  log_print = False
+
+
+Shows failed tests in the normal manner as no logs were captured::
+
+    ----------------------- Captured stdout call ----------------------
+    text going to stdout
+    ----------------------- Captured stderr call ----------------------
+    text going to stderr
+    ==================== 2 failed in 0.02 seconds =====================
+
+Inside tests it is possible to change the log level for the captured log
+messages.  This is supported by the ``caplog`` fixture::
+
+    def test_foo(caplog):
+        caplog.set_level(logging.INFO)
+        pass
+
+By default the level is set on the handler used to catch the log messages,
+however as a convenience it is also possible to set the log level of any
+logger::
+
+    def test_foo(caplog):
+        caplog.set_level(logging.CRITICAL, logger='root.baz')
+        pass
+
+It is also possible to use a context manager to temporarily change the log
+level::
+
+    def test_bar(caplog):
+        with caplog.at_level(logging.INFO):
+            pass
+
+Again, by default the level of the handler is affected but the level of any
+logger can be changed instead with::
+
+    def test_bar(caplog):
+        with caplog.at_level(logging.CRITICAL, logger='root.baz'):
+            pass
+
+Lastly all the logs sent to the logger during the test run are made available on
+the fixture in the form of both the LogRecord instances and the final log text.
+This is useful for when you want to assert on the contents of a message::
+
+    def test_baz(caplog):
+        func_under_test()
+        for record in caplog.records:
+            assert record.levelname != 'CRITICAL'
+        assert 'wally' not in caplog.text
+
+For all the available attributes of the log records see the
+``logging.LogRecord`` class.
+
+You can also resort to ``record_tuples`` if all you want to do is to ensure,
+that certain messages have been logged under a given logger name with a given
+severity and message::
+
+    def test_foo(caplog):
+        logging.getLogger().info('boo %s', 'arg')
+
+        assert caplog.record_tuples == [
+            ('root', logging.INFO, 'boo arg'),
+        ]
+
+You can call ``caplog.clear()`` to reset the captured log records in a test::
+
+    def test_something_with_clearing_records(caplog):
+        some_method_that_creates_log_records()
+        caplog.clear()
+        your_test_method()
+        assert ['Foo'] == [rec.message for rec in caplog.records]
+
+Live Logs
+^^^^^^^^^
+
+By default, pytest will output any logging records with a level higher or
+equal to WARNING. In order to actually see these logs in the console you have to
+disable pytest output capture by passing ``-s``.
+
+You can specify the logging level for which log records with equal or higher
+level are printed to the console by passing ``--log-cli-level``. This setting
+accepts the logging level names as seen in python's documentation or an integer
+as the logging level num.
+
+Additionally, you can also specify ``--log-cli-format`` and
+``--log-cli-date-format`` which mirror and default to ``--log-format`` and
+``--log-date-format`` if not provided, but are applied only to the console
+logging handler.
+
+All of the CLI log options can also be set in the configuration INI file. The
+option names are:
+
+* ``log_cli_level``
+* ``log_cli_format``
+* ``log_cli_date_format``
+
+If you need to record the whole test suite logging calls to a file, you can pass
+``--log-file=/path/to/log/file``. This log file is opened in write mode which
+means that it will be overwritten at each run tests session.
+
+You can also specify the logging level for the log file by passing
+``--log-file-level``. This setting accepts the logging level names as seen in
+python's documentation(ie, uppercased level names) or an integer as the logging
+level num.
+
+Additionally, you can also specify ``--log-file-format`` and
+``--log-file-date-format`` which are equal to ``--log-format`` and
+``--log-date-format`` but are applied to the log file logging handler.
+
+All of the log file options can also be set in the configuration INI file. The
+option names are:
+
+* ``log_file``
+* ``log_file_level``
+* ``log_file_format``
+* ``log_file_date_format``
diff --git a/tools/third_party/pytest/doc/en/mark.rst b/tools/third_party/pytest/doc/en/mark.rst
new file mode 100644
index 0000000..0b0e072
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/mark.rst
@@ -0,0 +1,41 @@
+
+.. _mark:
+
+Marking test functions with attributes
+=================================================================
+
+.. currentmodule:: _pytest.mark
+
+By using the ``pytest.mark`` helper you can easily set
+metadata on your test functions. There are
+some builtin markers, for example:
+
+* :ref:`skip <skip>` - always skip a test function
+* :ref:`skipif <skipif>` - skip a test function if a certain condition is met
+* :ref:`xfail <xfail>` - produce an "expected failure" outcome if a certain
+  condition is met
+* :ref:`parametrize <parametrizemark>` to perform multiple calls
+  to the same test function.
+
+It's easy to create custom markers or to apply markers
+to whole test classes or modules. See :ref:`mark examples` for examples
+which also serve as documentation.
+
+.. note::
+
+    Marks can only be applied to tests, having no effect on
+    :ref:`fixtures <fixtures>`.
+
+
+API reference for mark related objects
+------------------------------------------------
+
+.. autoclass:: MarkGenerator
+    :members:
+
+.. autoclass:: MarkDecorator
+    :members:
+
+.. autoclass:: MarkInfo
+    :members:
+
diff --git a/tools/third_party/pytest/doc/en/monkeypatch.rst b/tools/third_party/pytest/doc/en/monkeypatch.rst
new file mode 100644
index 0000000..0c07b2f
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/monkeypatch.rst
@@ -0,0 +1,75 @@
+
+Monkeypatching/mocking modules and environments
+================================================================
+
+.. currentmodule:: _pytest.monkeypatch
+
+Sometimes tests need to invoke functionality which depends
+on global settings or which invokes code which cannot be easily
+tested such as network access.  The ``monkeypatch`` fixture
+helps you to safely set/delete an attribute, dictionary item or
+environment variable or to modify ``sys.path`` for importing.
+See the `monkeypatch blog post`_ for some introduction material
+and a discussion of its motivation.
+
+.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
+
+
+Simple example: monkeypatching functions
+---------------------------------------------------
+
+If you want to pretend that ``os.expanduser`` returns a certain
+directory, you can use the :py:meth:`monkeypatch.setattr` method to
+patch this function before calling into a function which uses it::
+
+    # content of test_module.py
+    import os.path
+    def getssh(): # pseudo application code
+        return os.path.join(os.path.expanduser("~admin"), '.ssh')
+
+    def test_mytest(monkeypatch):
+        def mockreturn(path):
+            return '/abc'
+        monkeypatch.setattr(os.path, 'expanduser', mockreturn)
+        x = getssh()
+        assert x == '/abc/.ssh'
+
+Here our test function monkeypatches ``os.path.expanduser`` and
+then calls into a function that calls it.  After the test function 
+finishes the ``os.path.expanduser`` modification will be undone.
+
+example: preventing "requests" from remote operations
+------------------------------------------------------
+
+If you want to prevent the "requests" library from performing http
+requests in all your tests, you can do::
+
+    # content of conftest.py
+    import pytest
+    @pytest.fixture(autouse=True)
+    def no_requests(monkeypatch):
+        monkeypatch.delattr("requests.sessions.Session.request")
+
+This autouse fixture will be executed for each test function and it
+will delete the method ``request.session.Session.request`` 
+so that any attempts within tests to create http requests will fail.
+
+
+.. note::
+    
+    Be advised that it is not recommended to patch builtin functions such as ``open``,
+    ``compile``, etc., because it might break pytest's internals. If that's
+    unavoidable, passing ``--tb=native``, ``--assert=plain`` and ``--capture=no`` might 
+    help although there's no guarantee.
+    
+
+Method reference of the monkeypatch fixture
+-------------------------------------------
+
+.. autoclass:: MonkeyPatch
+    :members:
+
+``monkeypatch.setattr/delattr/delitem/delenv()`` all
+by default raise an Exception if the target does not exist.
+Pass ``raising=False`` if you want to skip this check.
+
diff --git a/tools/pytest/doc/en/naming20.rst b/tools/third_party/pytest/doc/en/naming20.rst
similarity index 100%
rename from tools/pytest/doc/en/naming20.rst
rename to tools/third_party/pytest/doc/en/naming20.rst
diff --git a/tools/third_party/pytest/doc/en/nose.rst b/tools/third_party/pytest/doc/en/nose.rst
new file mode 100644
index 0000000..10a1063
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/nose.rst
@@ -0,0 +1,75 @@
+.. _`noseintegration`:
+
+Running tests written for nose
+=======================================
+
+.. include:: links.inc
+
+``pytest`` has basic support for running tests written for nose_.
+
+.. _nosestyle:
+
+Usage
+-------------
+
+After :ref:`installation` type::
+
+    python setup.py develop  # make sure tests can import our package
+    pytest  # instead of 'nosetests'
+
+and you should be able to run your nose style tests and
+make use of pytest's capabilities.
+
+Supported nose Idioms
+----------------------
+
+* setup and teardown at module/class/method level
+* SkipTest exceptions and markers
+* setup/teardown decorators
+* ``yield``-based tests and their setup (considered deprecated as of pytest 3.0)
+* ``__test__`` attribute on modules/classes/functions
+* general usage of nose utilities
+
+Unsupported idioms / known issues
+----------------------------------
+
+- unittest-style ``setUp, tearDown, setUpClass, tearDownClass``
+  are recognized only on ``unittest.TestCase`` classes but not
+  on plain classes.  ``nose`` supports these methods also on plain
+  classes but pytest deliberately does not.  As nose and pytest already
+  both support ``setup_class, teardown_class, setup_method, teardown_method``
+  it doesn't seem useful to duplicate the unittest-API like nose does.
+  If you however rather think pytest should support the unittest-spelling on
+  plain classes please post `to this issue
+  <https://github.com/pytest-dev/pytest/issues/377/>`_.
+
+- nose imports test modules with the same import path (e.g.
+  ``tests.test_mod``) but different file system paths
+  (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
+  by extending sys.path/import semantics.   pytest does not do that
+  but there is discussion in `#268 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support.  Note that
+  `nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.io/en/latest/differences.html#test-discovery-and-loading>`_.
+
+  If you place a conftest.py file in the root directory of your project
+  (as determined by pytest) pytest will run tests "nose style" against
+  the code below that directory by adding it to your ``sys.path`` instead of
+  running against your installed code.
+
+  You may find yourself wanting to do this if you ran ``python setup.py install``
+  to set up your project, as opposed to ``python setup.py develop`` or any of
+  the package manager equivalents.  Installing with develop in a
+  virtual environment like Tox is recommended over this pattern.
+
+- nose-style doctests are not collected and executed correctly,
+  also doctest fixtures don't work.
+
+- no nose-configuration is recognized.
+
+- ``yield``-based methods don't support ``setup`` properly because
+  the ``setup`` method is always called in the same class instance.
+  There are no plans to fix this currently because ``yield``-tests
+  are deprecated in pytest 3.0, with ``pytest.mark.parametrize``
+  being the recommended alternative.
+
+
+
diff --git a/tools/third_party/pytest/doc/en/parametrize.rst b/tools/third_party/pytest/doc/en/parametrize.rst
new file mode 100644
index 0000000..7a4ac2e
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/parametrize.rst
@@ -0,0 +1,217 @@
+
+.. _`test generators`:
+.. _`parametrizing-tests`:
+.. _`parametrized test functions`:
+.. _`parametrize`:
+
+.. _`parametrize-basics`:
+
+Parametrizing fixtures and test functions
+==========================================================================
+
+pytest enables test parametrization at several levels:
+
+- :py:func:`pytest.fixture` allows one to :ref:`parametrize fixture 
+  functions <fixture-parametrize>`.
+
+* `@pytest.mark.parametrize`_ allows one to define multiple sets of 
+  arguments and fixtures at the test function or class.
+
+* `pytest_generate_tests`_ allows one to define custom parametrization 
+  schemes or extensions.
+
+.. _parametrizemark:
+.. _`@pytest.mark.parametrize`:
+
+
+``@pytest.mark.parametrize``: parametrizing test functions
+---------------------------------------------------------------------
+
+.. regendoc: wipe
+
+.. versionadded:: 2.2
+.. versionchanged:: 2.4
+    Several improvements.
+
+The builtin ``pytest.mark.parametrize`` decorator enables
+parametrization of arguments for a test function.  Here is a typical example
+of a test function that implements checking that a certain input leads
+to an expected output::
+
+    # content of test_expectation.py
+    import pytest
+    @pytest.mark.parametrize("test_input,expected", [
+        ("3+5", 8),
+        ("2+4", 6),
+        ("6*9", 42),
+    ])
+    def test_eval(test_input, expected):
+        assert eval(test_input) == expected
+
+Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)``
+tuples so that the ``test_eval`` function will run three times using
+them in turn::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 3 items
+    
+    test_expectation.py ..F                                              [100%]
+    
+    ================================= FAILURES =================================
+    ____________________________ test_eval[6*9-42] _____________________________
+    
+    test_input = '6*9', expected = 42
+    
+        @pytest.mark.parametrize("test_input,expected", [
+            ("3+5", 8),
+            ("2+4", 6),
+            ("6*9", 42),
+        ])
+        def test_eval(test_input, expected):
+    >       assert eval(test_input) == expected
+    E       AssertionError: assert 54 == 42
+    E        +  where 54 = eval('6*9')
+    
+    test_expectation.py:8: AssertionError
+    ==================== 1 failed, 2 passed in 0.12 seconds ====================
+
+As designed in this example, only one pair of input/output values fails
+the simple test function.  And as usual with test function arguments,
+you can see the ``input`` and ``output`` values in the traceback.
+
+Note that you could also use the parametrize marker on a class or a module
+(see :ref:`mark`) which would invoke several functions with the argument sets.
+
+It is also possible to mark individual test instances within parametrize,
+for example with the builtin ``mark.xfail``::
+
+    # content of test_expectation.py
+    import pytest
+    @pytest.mark.parametrize("test_input,expected", [
+        ("3+5", 8),
+        ("2+4", 6),
+        pytest.param("6*9", 42,
+                     marks=pytest.mark.xfail),
+    ])
+    def test_eval(test_input, expected):
+        assert eval(test_input) == expected
+
+Let's run this::
+
+    $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 3 items
+    
+    test_expectation.py ..x                                              [100%]
+    
+    =================== 2 passed, 1 xfailed in 0.12 seconds ====================
+
+The one parameter set which caused a failure previously now
+shows up as an "xfailed (expected to fail)" test.
+
+To get all combinations of multiple parametrized arguments you can stack
+``parametrize`` decorators::
+
+    import pytest
+    @pytest.mark.parametrize("x", [0, 1])
+    @pytest.mark.parametrize("y", [2, 3])
+    def test_foo(x, y):
+        pass
+
+This will run the test with the arguments set to ``x=0/y=2``, ``x=0/y=3``, ``x=1/y=2`` and
+``x=1/y=3``.
+
+.. _`pytest_generate_tests`:
+
+Basic ``pytest_generate_tests`` example
+---------------------------------------------
+
+Sometimes you may want to implement your own parametrization scheme
+or implement some dynamism for determining the parameters or scope
+of a fixture.   For this, you can use the ``pytest_generate_tests`` hook
+which is called when collecting a test function.  Through the passed in
+``metafunc`` object you can inspect the requesting test context and, most
+importantly, you can call ``metafunc.parametrize()`` to cause
+parametrization.
+
+For example, let's say we want to run a test taking string inputs which
+we want to set via a new ``pytest`` command line option.  Let's first write
+a simple test accepting a ``stringinput`` fixture function argument::
+
+    # content of test_strings.py
+
+    def test_valid_string(stringinput):
+        assert stringinput.isalpha()
+
+Now we add a ``conftest.py`` file containing the addition of a
+command line option and the parametrization of our test function::
+
+    # content of conftest.py
+
+    def pytest_addoption(parser):
+        parser.addoption("--stringinput", action="append", default=[],
+            help="list of stringinputs to pass to test functions")
+
+    def pytest_generate_tests(metafunc):
+        if 'stringinput' in metafunc.fixturenames:
+            metafunc.parametrize("stringinput",
+                                 metafunc.config.getoption('stringinput'))
+
+If we now pass two stringinput values, our test will run twice::
+
+    $ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
+    ..                                                                   [100%]
+    2 passed in 0.12 seconds
+
+Let's also run with a stringinput that will lead to a failing test::
+
+    $ pytest -q --stringinput="!" test_strings.py
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    ___________________________ test_valid_string[!] ___________________________
+    
+    stringinput = '!'
+    
+        def test_valid_string(stringinput):
+    >       assert stringinput.isalpha()
+    E       AssertionError: assert False
+    E        +  where False = <built-in method isalpha of str object at 0xdeadbeef>()
+    E        +    where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
+    
+    test_strings.py:3: AssertionError
+    1 failed in 0.12 seconds
+
+As expected our test function fails.
+
+If you don't specify a stringinput it will be skipped because
+``metafunc.parametrize()`` will be called with an empty parameter
+list::
+
+    $ pytest -q -rs test_strings.py
+    s                                                                    [100%]
+    ========================= short test summary info ==========================
+    SKIP [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
+    1 skipped in 0.12 seconds
+
+Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across 
+those sets cannot be duplicated, otherwise an error will be raised.
+
+More examples
+-------------
+
+For further examples, you might want to look at :ref:`more
+parametrization examples <paramexamples>`.
+
+.. _`metafunc object`:
+
+The **metafunc** object
+-------------------------------------------
+
+.. currentmodule:: _pytest.python
+.. autoclass:: Metafunc
+    :members:
diff --git a/tools/third_party/pytest/doc/en/plugins.rst b/tools/third_party/pytest/doc/en/plugins.rst
new file mode 100644
index 0000000..400418a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/plugins.rst
@@ -0,0 +1,154 @@
+.. _`external plugins`:
+.. _`extplugins`:
+.. _`using plugins`:
+
+Installing and Using plugins
+============================
+
+This section talks about installing and using third party plugins.
+For writing your own plugins, please refer to :ref:`writing-plugins`.
+
+Installing a third party plugin can be easily done with ``pip``::
+
+    pip install pytest-NAME
+    pip uninstall pytest-NAME
+
+If a plugin is installed, ``pytest`` automatically finds and integrates it,
+there is no need to activate it.
+
+Here is a little annotated list for some popular plugins:
+
+.. _`django`: https://www.djangoproject.com/
+
+* `pytest-django <http://pypi.python.org/pypi/pytest-django>`_: write tests
+  for `django`_ apps, using pytest integration.
+
+* `pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_: write tests
+  for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
+  processing deferreds from test functions.
+
+* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
+  coverage reporting, compatible with distributed testing
+
+* `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_:
+  to distribute tests to CPUs and remote hosts, to run in boxed
+  mode which allows to survive segmentation faults, to run in
+  looponfailing mode, automatically re-running failing tests
+  on file changes.
+
+* `pytest-instafail <http://pypi.python.org/pypi/pytest-instafail>`_:
+  to report failures while the test run is happening.
+
+* `pytest-bdd <http://pypi.python.org/pypi/pytest-bdd>`_ and
+  `pytest-konira <http://pypi.python.org/pypi/pytest-konira>`_
+  to write tests using behaviour-driven testing.
+
+* `pytest-timeout <http://pypi.python.org/pypi/pytest-timeout>`_:
+  to timeout tests based on function marks or global definitions.
+
+* `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_:
+  a ``--pep8`` option to enable PEP8 compliance checking.
+
+* `pytest-flakes <https://pypi.python.org/pypi/pytest-flakes>`_:
+  check source code with pyflakes.
+
+* `oejskit <http://pypi.python.org/pypi/oejskit>`_:
+  a plugin to run javascript unittests in live browsers.
+
+To see a complete list of all plugins with their latest testing
+status against different pytest and Python versions, please visit
+`plugincompat <http://plugincompat.herokuapp.com/>`_.
+
+You may also discover more plugins through a `pytest- pypi.python.org search`_.
+
+.. _`available installable plugins`:
+.. _`pytest- pypi.python.org search`: http://pypi.python.org/pypi?%3Aaction=search&term=pytest-&submit=search
+
+
+Requiring/Loading plugins in a test module or conftest file
+-----------------------------------------------------------
+
+You can require plugins in a test module or a conftest file like this::
+
+    pytest_plugins = "myapp.testsupport.myplugin",
+
+When the test module or conftest plugin is loaded the specified plugins
+will be loaded as well.
+
+    pytest_plugins = "myapp.testsupport.myplugin"
+
+which will import the specified module as a ``pytest`` plugin.
+
+.. _`findpluginname`:
+
+Finding out which plugins are active
+------------------------------------
+
+If you want to find out which plugins are active in your
+environment you can type::
+
+    pytest --trace-config
+
+and will get an extended test header which shows activated plugins
+and their names. It will also print local plugins aka
+:ref:`conftest.py <conftest.py plugins>` files when they are loaded.
+
+.. _`cmdunregister`:
+
+Deactivating / unregistering a plugin by name
+---------------------------------------------
+
+You can prevent plugins from loading or unregister them::
+
+    pytest -p no:NAME
+
+This means that any subsequent try to activate/load the named
+plugin will not work.
+
+If you want to unconditionally disable a plugin for a project, you can add
+this option to your ``pytest.ini`` file:
+
+.. code-block:: ini
+
+      [pytest]
+      addopts = -p no:NAME
+
+Alternatively to disable it only in certain environments (for example in a
+CI server), you can set ``PYTEST_ADDOPTS`` environment variable to
+``-p no:name``.
+
+See :ref:`findpluginname` for how to obtain the name of a plugin.
+
+.. _`builtin plugins`:
+
+Pytest default plugin reference
+-------------------------------
+
+
+You can find the source code for the following plugins
+in the `pytest repository <https://github.com/pytest-dev/pytest>`_.
+
+.. autosummary::
+
+    _pytest.assertion
+    _pytest.cacheprovider
+    _pytest.capture
+    _pytest.config
+    _pytest.doctest
+    _pytest.helpconfig
+    _pytest.junitxml
+    _pytest.mark
+    _pytest.monkeypatch
+    _pytest.nose
+    _pytest.pastebin
+    _pytest.debugging
+    _pytest.pytester
+    _pytest.python
+    _pytest.recwarn
+    _pytest.resultlog
+    _pytest.runner
+    _pytest.main
+    _pytest.skipping
+    _pytest.terminal
+    _pytest.tmpdir
+    _pytest.unittest
diff --git a/tools/third_party/pytest/doc/en/projects.rst b/tools/third_party/pytest/doc/en/projects.rst
new file mode 100644
index 0000000..86df99a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/projects.rst
@@ -0,0 +1,85 @@
+.. _projects:
+
+.. image:: img/gaynor3.png
+   :width: 400px
+   :align: right
+
+.. image:: img/theuni.png
+   :width: 400px
+   :align: right
+
+.. image:: img/cramer2.png
+   :width: 400px
+   :align: right
+
+.. image:: img/keleshev.png
+   :width: 400px
+   :align: right
+
+
+Project examples
+==========================
+
+Here are some examples of projects using ``pytest`` (please send notes via :ref:`contact`):
+
+* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over
+  `21000 tests <http://buildbot.pypy.org/summary?branch=%3Ctrunk%3E>`_
+* the `MoinMoin <http://moinmo.in>`_ Wiki Engine
+* `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking
+* `Astropy <http://www.astropy.org/>`_ and `affiliated packages <http://www.astropy.org/affiliated/index.html>`_
+* `tox <http://testrun.org/tox>`_, virtualenv/Hudson integration tool
+* `PIDA <http://pida.co.uk>`_ framework for integrated development
+* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
+* `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB
+* `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities
+* `six <http://pypi.python.org/pypi/six/>`_ Python 2 and 3 compatibility utilities
+* `pediapress <http://code.pediapress.com/wiki/wiki>`_ MediaWiki articles
+* `mwlib <http://pypi.python.org/pypi/mwlib>`_ mediawiki parser and utility library
+* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion
+* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
+* `pylib <http://py.rtfd.org>`_ cross-platform path, IO, dynamic code library
+* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes
+* `bbfreeze <http://pypi.python.org/pypi/bbfreeze>`_ create standalone executables from Python scripts
+* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
+* `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem
+* `waskr <http://code.google.com/p/waskr/>`_ WSGI Stats Middleware
+* `guachi <http://code.google.com/p/guachi/>`_ global persistent configs for Python modules
+* `Circuits <http://pypi.python.org/pypi/circuits>`_ lightweight Event Driven Framework
+* `pygtk-helpers <http://bitbucket.org/aafshar/pygtkhelpers-main/>`_ easy interaction with PyGTK
+* `QuantumCore <http://quantumcore.org/>`_ statusmessage and repoze openid plugin
+* `pydataportability <http://pydataportability.net/>`_ libraries for managing the open web
+* `XIST <http://www.livinglogic.de/Python/xist/>`_ extensible HTML/XML generator
+* `tiddlyweb <http://pypi.python.org/pypi/tiddlyweb>`_ optionally headless, extensible RESTful datastore
+* `fancycompleter <http://bitbucket.org/antocuni/fancycompleter/src>`_ for colorful tab-completion
+* `Paludis <http://paludis.exherbo.org/>`_ tools for Gentoo Paludis package manager
+* `Gerald <http://halfcooked.com/code/gerald/>`_ schema comparison tool
+* `abjad <http://code.google.com/p/abjad/>`_ Python API for Formalized Score control
+* `bu <http://packages.python.org/bu/>`_ a microscopic build system
+* `katcp <https://bitbucket.org/hodgestar/katcp>`_ Telescope communication protocol over Twisted
+* `kss plugin timer <http://pypi.python.org/pypi/kss.plugin.timer>`_
+* `pyudev <https://pyudev.readthedocs.io/en/latest/tests/plugins.html>`_ a pure Python binding to the Linux library libudev
+* `pytest-localserver <https://bitbucket.org/pytest-dev/pytest-localserver/>`_ a plugin for pytest that provides an httpserver and smtpserver
+* `pytest-monkeyplus <http://pypi.python.org/pypi/pytest-monkeyplus/>`_ a plugin that extends monkeypatch
+
+These projects help integrate ``pytest`` into other Python frameworks:
+
+* `pytest-django <http://pypi.python.org/pypi/pytest-django/>`_ for Django
+* `zope.pytest <http://packages.python.org/zope.pytest/>`_ for Zope and Grok
+* `pytest_gae <http://pypi.python.org/pypi/pytest_gae/0.2.1>`_ for Google App Engine
+* There is `some work <https://github.com/Kotti/Kotti/blob/master/kotti/testing.py>`_ underway for Kotti, a CMS built in Pyramid/Pylons
+
+
+Some organisations using pytest
+-----------------------------------
+
+* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
+* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests
+* `Tandberg <http://www.tandberg.com/>`_
+* `Shootq <http://web.shootq.com/>`_
+* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
+* `cellzome <http://www.cellzome.com/>`_
+* `Open End, Gothenborg <http://www.openend.se>`_
+* `Laboratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_
+* `merlinux, Germany <http://merlinux.eu>`_
+* `ESSS, Brazil <http://www.esss.com.br>`_
+* many more ... (please be so kind to send a note via :ref:`contact`)
diff --git a/tools/third_party/pytest/doc/en/proposals/parametrize_with_fixtures.rst b/tools/third_party/pytest/doc/en/proposals/parametrize_with_fixtures.rst
new file mode 100644
index 0000000..146032a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/proposals/parametrize_with_fixtures.rst
@@ -0,0 +1,160 @@
+:orphan:
+
+===================================
+PROPOSAL: Parametrize with fixtures
+===================================
+
+.. warning::
+
+    This document outlines a proposal around using fixtures as input
+    of parametrized tests or fixtures.
+
+Problem
+-------
+
+As a user I have functional tests that I would like to run against various
+scenarios.
+
+In this particular example we want to generate a new project based on a
+cookiecutter template. We want to test default values but also data that
+emulates user input.
+
+- use default values
+
+- emulate user input
+
+  - specify 'author'
+
+  - specify 'project_slug'
+
+  - specify 'author' and 'project_slug'
+
+This is how a functional test could look like:
+
+.. code-block:: python
+
+    import pytest
+
+    @pytest.fixture
+    def default_context():
+        return {'extra_context': {}}
+
+
+    @pytest.fixture(params=[
+        {'author': 'alice'},
+        {'project_slug': 'helloworld'},
+        {'author': 'bob', 'project_slug': 'foobar'},
+    ])
+    def extra_context(request):
+        return {'extra_context': request.param}
+
+
+    @pytest.fixture(params=['default', 'extra'])
+    def context(request):
+        if request.param == 'default':
+            return request.getfuncargvalue('default_context')
+        else:
+            return request.getfuncargvalue('extra_context')
+
+
+    def test_generate_project(cookies, context):
+        """Call the cookiecutter API to generate a new project from a
+        template.
+        """
+        result = cookies.bake(extra_context=context)
+
+        assert result.exit_code == 0
+        assert result.exception is None
+        assert result.project.isdir()
+
+
+Issues
+------
+
+* By using ``request.getfuncargvalue()`` we rely on actual fixture function
+  execution to know what fixtures are involved, due to it's dynamic nature
+* More importantly, ``request.getfuncargvalue()`` cannot be combined with
+  parametrized fixtures, such as ``extra_context``
+* This is very inconvenient if you wish to extend an existing test suite by
+  certain parameters for fixtures that are already used by tests
+
+pytest version 3.0 reports an error if you try to run above code::
+
+    Failed: The requested fixture has no parameter defined for the current
+    test.
+
+    Requested fixture 'extra_context'
+
+
+Proposed solution
+-----------------
+
+A new function that can be used in modules can be used to dynamically define
+fixtures from existing ones.
+
+.. code-block:: python
+
+    pytest.define_combined_fixture(
+        name='context',
+        fixtures=['default_context', 'extra_context'],
+    )
+
+The new fixture ``context`` inherits the scope from the used fixtures and yield
+the following values.
+
+- ``{}``
+
+- ``{'author': 'alice'}``
+
+- ``{'project_slug': 'helloworld'}``
+
+- ``{'author': 'bob', 'project_slug': 'foobar'}``
+
+Alternative approach
+--------------------
+
+A new helper function named ``fixture_request`` would tell pytest to yield
+all parameters marked as a fixture.
+
+.. note::
+
+    The `pytest-lazy-fixture <https://pypi.python.org/pypi/pytest-lazy-fixture>`_ plugin implements a very
+    similar solution to the proposal below, make sure to check it out.
+
+.. code-block:: python
+
+    @pytest.fixture(params=[
+        pytest.fixture_request('default_context'),
+        pytest.fixture_request('extra_context'),
+    ])
+    def context(request):
+        """Returns all values for ``default_context``, one-by-one before it
+        does the same for ``extra_context``.
+
+        request.param:
+            - {}
+            - {'author': 'alice'}
+            - {'project_slug': 'helloworld'}
+            - {'author': 'bob', 'project_slug': 'foobar'}
+        """
+        return request.param
+
+The same helper can be used in combination with ``pytest.mark.parametrize``.
+
+.. code-block:: python
+
+
+    @pytest.mark.parametrize(
+        'context, expected_response_code',
+        [
+            (pytest.fixture_request('default_context'), 0),
+            (pytest.fixture_request('extra_context'), 0),
+        ],
+    )
+    def test_generate_project(cookies, context, exit_code):
+        """Call the cookiecutter API to generate a new project from a
+        template.
+        """
+        result = cookies.bake(extra_context=context)
+
+        assert result.exit_code == exit_code
diff --git a/tools/pytest/doc/en/pytest.ini b/tools/third_party/pytest/doc/en/pytest.ini
similarity index 100%
rename from tools/pytest/doc/en/pytest.ini
rename to tools/third_party/pytest/doc/en/pytest.ini
diff --git a/tools/third_party/pytest/doc/en/pythonpath.rst b/tools/third_party/pytest/doc/en/pythonpath.rst
new file mode 100644
index 0000000..b647427
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/pythonpath.rst
@@ -0,0 +1,76 @@
+.. _pythonpath:
+
+pytest import mechanisms and ``sys.path``/``PYTHONPATH``
+========================================================
+
+Here's a list of scenarios where pytest may need to change ``sys.path`` in order
+to import test modules or ``conftest.py`` files.
+
+Test modules / ``conftest.py`` files inside packages
+----------------------------------------------------
+
+Consider this file and directory layout::
+
+    root/
+    |- foo/
+       |- __init__.py
+       |- conftest.py
+       |- bar/
+          |- __init__.py
+          |- tests/
+             |- __init__.py
+             |- test_foo.py
+
+
+When executing::
+
+    pytest root/
+
+
+
+pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a package given that
+there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the
+last folder which still contains an ``__init__.py`` file in order to find the package *root* (in
+this case ``foo/``). To load the module, it will insert ``root/``  to the front of
+``sys.path`` (if not there already) in order to load
+``test_foo.py`` as the *module* ``foo.bar.tests.test_foo``.
+
+The same logic applies to the ``conftest.py`` file: it will be imported as ``foo.conftest`` module.
+
+Preserving the full package name is important when tests live in a package to avoid problems
+and allow test modules to have duplicated names. This is also discussed in details in
+:ref:`test discovery`.
+
+Standalone test modules / ``conftest.py`` files
+-----------------------------------------------
+
+Consider this file and directory layout::
+
+    root/
+    |- foo/
+       |- conftest.py
+       |- bar/
+          |- tests/
+             |- test_foo.py
+
+
+When executing::
+
+    pytest root/
+
+pytest will find ``foo/bar/tests/test_foo.py`` and realize it is NOT part of a package given that
+there's no ``__init__.py`` file in the same folder. It will then add ``root/foo/bar/tests`` to
+``sys.path`` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
+with the ``conftest.py`` file by adding ``root/foo`` to ``sys.path`` to import it as ``conftest``.
+
+For this reason this layout cannot have test modules with the same name, as they all will be
+imported in the global import namespace.
+
+This is also discussed in details in :ref:`test discovery`.
+
+Invoking ``pytest`` versus ``python -m pytest``
+-----------------------------------------------
+
+Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly
+equivalent behaviour, except that the former call will add the current directory to ``sys.path``.
+See also :ref:`cmdline`.
diff --git a/tools/third_party/pytest/doc/en/recwarn.rst b/tools/third_party/pytest/doc/en/recwarn.rst
new file mode 100644
index 0000000..513af0d
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/recwarn.rst
@@ -0,0 +1,3 @@
+:orphan:
+
+This page has been moved, please see :ref:`assertwarnings`.
diff --git a/tools/third_party/pytest/doc/en/requirements.txt b/tools/third_party/pytest/doc/en/requirements.txt
new file mode 100644
index 0000000..72bb60a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/requirements.txt
@@ -0,0 +1,3 @@
+# pinning sphinx to 1.4.* due to search issues with rtd:
+# https://github.com/rtfd/readthedocs-sphinx-ext/issues/25
+sphinx ==1.4.*
diff --git a/tools/third_party/pytest/doc/en/skipping.rst b/tools/third_party/pytest/doc/en/skipping.rst
new file mode 100644
index 0000000..7e00192
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/skipping.rst
@@ -0,0 +1,378 @@
+.. _`skip and xfail`:
+
+.. _skipping:
+
+Skip and xfail: dealing with tests that cannot succeed
+======================================================
+
+You can mark test functions that cannot be run on certain platforms
+or that you expect to fail so pytest can deal with them accordingly and
+present a summary of the test session, while keeping the test suite *green*.
+
+A **skip** means that you expect your test to pass only if some conditions are met,
+otherwise pytest should skip running the test altogether. Common examples are skipping
+windows-only tests on non-windows platforms, or skipping tests that depend on an external
+resource which is not available at the moment (for example a database).
+
+A **xfail** means that you expect a test to fail for some reason.
+A common example is a test for a feature not yet implemented, or a bug not yet fixed.
+When a test passes despite being expected to fail (marked with ``pytest.mark.xfail``),
+it's an **xpass** and will be reported in the test summary.
+
+``pytest`` counts and lists *skip* and *xfail* tests separately. Detailed
+information about skipped/xfailed tests is not shown by default to avoid
+cluttering the output.  You can use the ``-r`` option to see details
+corresponding to the "short" letters shown in the test progress::
+
+    pytest -rxXs  # show extra info on xfailed, xpassed, and skipped tests
+
+More details on the ``-r`` option can be found by running ``pytest -h``.
+
+(See :ref:`how to change command line options defaults`)
+
+.. _skipif:
+.. _skip:
+.. _`condition booleans`:
+
+Skipping test functions
+-----------------------
+
+.. versionadded:: 2.9
+
+The simplest way to skip a test function is to mark it with the ``skip`` decorator
+which may be passed an optional ``reason``:
+
+.. code-block:: python
+
+    @pytest.mark.skip(reason="no way of currently testing this")
+    def test_the_unknown():
+        ...
+
+
+Alternatively, it is also possible to skip imperatively during test execution or setup
+by calling the ``pytest.skip(reason)`` function:
+
+.. code-block:: python
+
+    def test_function():
+        if not valid_config():
+            pytest.skip("unsupported configuration")
+
+It is also possible to skip the whole module using
+``pytest.skip(reason, allow_module_level=True)`` at the module level:
+
+.. code-block:: python
+
+    import pytest
+
+    if not pytest.config.getoption("--custom-flag"):
+        pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
+
+The imperative method is useful when it is not possible to evaluate the skip condition
+during import time.
+
+``skipif``
+~~~~~~~~~~
+
+.. versionadded:: 2.0
+
+If you wish to skip something conditionally then you can use ``skipif`` instead.
+Here is an example of marking a test function to be skipped
+when run on a Python3.6 interpreter::
+
+    import sys
+    @pytest.mark.skipif(sys.version_info < (3,6),
+                        reason="requires python3.6")
+    def test_function():
+        ...
+
+If the condition evaluates to ``True`` during collection, the test function will be skipped,
+with the specified reason appearing in the summary when using ``-rs``.
+
+You can share ``skipif`` markers between modules.  Consider this test module::
+
+    # content of test_mymodule.py
+    import mymodule
+    minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1),
+                                    reason="at least mymodule-1.1 required")
+    @minversion
+    def test_function():
+        ...
+
+You can import the marker and reuse it in another test module::
+
+    # test_myothermodule.py
+    from test_mymodule import minversion
+
+    @minversion
+    def test_anotherfunction():
+        ...
+
+For larger test suites it's usually a good idea to have one file
+where you define the markers which you then consistently apply
+throughout your test suite.
+
+Alternatively, you can use :ref:`condition strings
+<string conditions>` instead of booleans, but they can't be shared between modules easily
+so they are supported mainly for backward compatibility reasons.
+
+
+Skip all test functions of a class or module
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can use the ``skipif`` marker (as any other marker) on classes::
+
+    @pytest.mark.skipif(sys.platform == 'win32',
+                        reason="does not run on windows")
+    class TestPosixCalls(object):
+
+        def test_function(self):
+            "will not be setup or run under 'win32' platform"
+
+If the condition is ``True``, this marker will produce a skip result for
+each of the test methods of that class.
+
+.. warning::
+
+   The use of ``skipif`` on classes that use inheritance is strongly
+   discouraged. `A Known bug <https://github.com/pytest-dev/pytest/issues/568>`_
+   in pytest's markers may cause unexpected behavior in super classes.
+
+If you want to skip all test functions of a module, you may use
+the ``pytestmark`` name on the global level:
+
+.. code-block:: python
+
+    # test_module.py
+    pytestmark = pytest.mark.skipif(...)
+
+If multiple ``skipif`` decorators are applied to a test function, it
+will be skipped if any of the skip conditions is true.
+
+.. _`whole class- or module level`: mark.html#scoped-marking
+
+
+Skipping files or directories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes you may need to skip an entire file or directory, for example if the
+tests rely on Python version-specific features or contain code that you do not
+wish pytest to run. In this case, you must exclude the files and directories
+from collection. Refer to :ref:`customizing-test-collection` for more
+information.
+
+
+Skipping on a missing import dependency
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can use the following helper at module level
+or within a test or test setup function::
+
+    docutils = pytest.importorskip("docutils")
+
+If ``docutils`` cannot be imported here, this will lead to a
+skip outcome of the test.  You can also skip based on the
+version number of a library::
+
+    docutils = pytest.importorskip("docutils", minversion="0.3")
+
+The version will be read from the specified
+module's ``__version__`` attribute.
+
+Summary
+~~~~~~~
+
+Here's a quick guide on how to skip tests in a module in different situations:
+
+1. Skip all tests in a module unconditionally:
+
+  .. code-block:: python
+
+        pytestmark = pytest.mark.skip('all tests still WIP')
+
+2. Skip all tests in a module based on some condition:
+
+  .. code-block:: python
+
+        pytestmark = pytest.mark.skipif(sys.platform == 'win32', 'tests for linux only')
+
+3. Skip all tests in a module if some import is missing:
+
+  .. code-block:: python
+
+        pexpect = pytest.importorskip('pexpect')
+
+
+.. _xfail:
+
+XFail: mark test functions as expected to fail
+----------------------------------------------
+
+You can use the ``xfail`` marker to indicate that you
+expect a test to fail::
+
+    @pytest.mark.xfail
+    def test_function():
+        ...
+
+This test will be run but no traceback will be reported
+when it fails. Instead terminal reporting will list it in the
+"expected to fail" (``XFAIL``) or "unexpectedly passing" (``XPASS``) sections.
+
+Alternatively, you can also mark a test as ``XFAIL`` from within a test or setup function
+imperatively:
+
+.. code-block:: python
+
+    def test_function():
+        if not valid_config():
+            pytest.xfail("failing configuration (but should work)")
+
+This will unconditionally make ``test_function`` ``XFAIL``. Note that no other code is executed
+after ``pytest.xfail`` call, differently from the marker. That's because it is implemented
+internally by raising a known exception.
+
+Here's the signature of the ``xfail`` **marker** (not the function), using Python 3 keyword-only
+arguments syntax:
+
+.. code-block:: python
+
+    def xfail(condition=None, *, reason=None, raises=None, run=True, strict=False):
+
+
+
+
+``strict`` parameter
+~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 2.9
+
+Both ``XFAIL`` and ``XPASS`` don't fail the test suite, unless the ``strict`` keyword-only
+parameter is passed as ``True``:
+
+.. code-block:: python
+
+    @pytest.mark.xfail(strict=True)
+    def test_function():
+        ...
+
+
+This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.
+
+You can change the default value of the ``strict`` parameter using the
+``xfail_strict`` ini option:
+
+.. code-block:: ini
+
+    [pytest]
+    xfail_strict=true
+
+
+``reason`` parameter
+~~~~~~~~~~~~~~~~~~~~
+
+As with skipif_ you can also mark your expectation of a failure
+on a particular platform::
+
+    @pytest.mark.xfail(sys.version_info >= (3,6),
+                       reason="python3.6 api changes")
+    def test_function():
+        ...
+
+
+``raises`` parameter
+~~~~~~~~~~~~~~~~~~~~
+
+If you want to be more specific as to why the test is failing, you can specify
+a single exception, or a list of exceptions, in the ``raises`` argument.
+
+.. code-block:: python
+
+    @pytest.mark.xfail(raises=RuntimeError)
+    def test_function():
+        ...
+
+Then the test will be reported as a regular failure if it fails with an
+exception not mentioned in ``raises``.
+
+``run`` parameter
+~~~~~~~~~~~~~~~~~
+
+If a test should be marked as xfail and reported as such but should not be
+even executed, use the ``run`` parameter as ``False``:
+
+.. code-block:: python
+
+    @pytest.mark.xfail(run=False)
+    def test_function():
+        ...
+
+This is specially useful for xfailing tests that are crashing the interpreter and should be
+investigated later.
+
+
+Ignoring xfail
+~~~~~~~~~~~~~~
+
+By specifying on the commandline::
+
+    pytest --runxfail
+
+you can force the running and reporting of an ``xfail`` marked test
+as if it weren't marked at all. This also causes ``pytest.xfail`` to produce no effect.
+
+Examples
+~~~~~~~~
+
+Here is a simple test file with the several usages:
+
+.. literalinclude:: example/xfail_demo.py
+
+Running it with the report-on-xfail option gives this output::
+
+    example $ pytest -rx xfail_demo.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR/example, inifile:
+    collected 7 items
+    
+    xfail_demo.py xxxxxxx                                                [100%]
+    ========================= short test summary info ==========================
+    XFAIL xfail_demo.py::test_hello
+    XFAIL xfail_demo.py::test_hello2
+      reason: [NOTRUN] 
+    XFAIL xfail_demo.py::test_hello3
+      condition: hasattr(os, 'sep')
+    XFAIL xfail_demo.py::test_hello4
+      bug 110
+    XFAIL xfail_demo.py::test_hello5
+      condition: pytest.__version__[0] != "17"
+    XFAIL xfail_demo.py::test_hello6
+      reason: reason
+    XFAIL xfail_demo.py::test_hello7
+    
+    ======================== 7 xfailed in 0.12 seconds =========================
+
+.. _`skip/xfail with parametrize`:
+
+Skip/xfail with parametrize
+---------------------------
+
+It is possible to apply markers like skip and xfail to individual
+test instances when using parametrize:
+
+.. code-block:: python
+
+    import pytest
+
+    @pytest.mark.parametrize(("n", "expected"), [
+        (1, 2),
+    pytest.param(1, 0, marks=pytest.mark.xfail),
+	pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
+        (2, 3),
+        (3, 4),
+        (4, 5),
+    pytest.param(10, 11, marks=pytest.mark.skipif(sys.version_info >= (3, 0), reason="py2k")),
+    ])
+    def test_increment(n, expected):
+        assert n + 1 == expected
diff --git a/tools/third_party/pytest/doc/en/talks.rst b/tools/third_party/pytest/doc/en/talks.rst
new file mode 100644
index 0000000..bf593db
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/talks.rst
@@ -0,0 +1,105 @@
+
+Talks and Tutorials
+==========================
+
+..
+   .. sidebar:: Next Open Trainings
+
+      `Professional Testing with Python
+      <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_,
+      26-28 April 2017, Leipzig, Germany.
+
+.. _`funcargs`: funcargs.html
+
+Books
+---------------------------------------------
+
+- `Python Testing with pytest, by Brian Okken (2017)
+  <https://pragprog.com/book/bopytest/python-testing-with-pytest>`_.
+
+Talks and blog postings
+---------------------------------------------
+
+- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016)
+  <https://www.youtube.com/watch?v=_92nfdd5nK8>`_.
+
+- `pytest - Rapid Simple Testing, Florian Bruhin, Swiss Python Summit 2016
+  <https://www.youtube.com/watch?v=rCBHkQ_LVIs>`_.
+
+- `Improve your testing with Pytest and Mock, Gabe Hollombe, PyCon SG 2015
+  <https://www.youtube.com/watch?v=RcN26hznmk4>`_.
+
+- `Introduction to pytest, Andreas Pelme, EuroPython 2014
+  <https://www.youtube.com/watch?v=LdVJj65ikRY>`_.
+
+- `Advanced Uses of py.test Fixtures, Floris Bruynooghe, EuroPython
+  2014 <https://www.youtube.com/watch?v=IBC_dxr-4ps>`_.
+
+- `Why i use py.test and maybe you should too, Andy Todd, Pycon AU 2013
+  <https://www.youtube.com/watch?v=P-AhpukDIik>`_
+
+- `3-part blog series about pytest from @pydanny alias Daniel Greenfeld (January
+  2014) <http://pydanny.com/pytest-no-boilerplate-testing.html>`_
+
+- `pytest: helps you write better Django apps, Andreas Pelme, DjangoCon
+  Europe 2014 <https://www.youtube.com/watch?v=aaArYVh6XSM>`_.
+
+- :ref:`fixtures`
+
+- `Testing Django Applications with pytest, Andreas Pelme, EuroPython
+  2013 <https://www.youtube.com/watch?v=aUf8Fkb7TaY>`_.
+
+- `Testes pythonics com py.test, Vinicius Belchior Assef Neto, Plone
+  Conf 2013, Brazil <https://www.youtube.com/watch?v=QUKoq2K7bis>`_.
+
+- `Introduction to py.test fixtures, FOSDEM 2013, Floris Bruynooghe
+  <https://www.youtube.com/watch?v=bJhRW4eZMco>`_.
+
+- `pytest feature and release highlights, Holger Krekel (GERMAN, October 2013)
+  <http://pyvideo.org/video/2429/pytest-feature-and-new-release-highlights>`_
+
+- `pytest introduction from Brian Okken (January 2013)
+  <http://pythontesting.net/framework/pytest-introduction/>`_
+
+- pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
+- `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_
+
+- `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API)
+
+Test parametrization:
+
+- `generating parametrized tests with fixtures`_.
+- `test generators and cached setup`_
+- `parametrizing tests, generalized`_ (blog post)
+- `putting test-hooks into local or global plugins`_ (blog post)
+
+Assertion introspection:
+
+- `(07/2011) Behind the scenes of pytest's new assertion rewriting
+  <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_
+
+Distributed testing:
+
+- `simultaneously test your code on all platforms`_ (blog entry)
+
+Plugin specific examples:
+
+- `skipping slow tests by default in pytest`_ (blog entry)
+
+- `many examples in the docs for plugins`_
+
+.. _`skipping slow tests by default in pytest`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
+.. _`many examples in the docs for plugins`: plugins.html
+.. _`monkeypatch plugin`: monkeypatch.html
+.. _`application setup in test functions with fixtures`: fixture.html#interdependent-fixtures
+.. _`simultaneously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
+.. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
+.. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/
+.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
+.. _`generating parametrized tests with fixtures`: parametrize.html#test-generators
+.. _`test generators and cached setup`: http://bruynooghe.blogspot.com/2010/06/pytest-test-generators-and-cached-setup.html
+
+
+
+
+
diff --git a/tools/third_party/pytest/doc/en/test/attic.rst b/tools/third_party/pytest/doc/en/test/attic.rst
new file mode 100644
index 0000000..0694466
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/attic.rst
@@ -0,0 +1,117 @@
+===============================================
+ATTIC documentation
+===============================================
+
+XXX REVIEW and remove the below  XXX
+
+Customizing the testing process
+===============================
+
+writing conftest.py files
+-----------------------------------
+
+You may put conftest.py files containing project-specific
+configuration in your project's root directory, it's usually
+best to put it just into the same directory level as your
+topmost ``__init__.py``.  In fact, ``pytest`` performs
+an "upwards" search starting from the directory that you specify
+to be tested and will lookup configuration values right-to-left.
+You may have options that reside e.g. in your home directory
+but note that project specific settings will be considered
+first.  There is a flag that helps you debugging your
+conftest.py configurations::
+
+    pytest --trace-config
+
+
+customizing the collecting and running process
+-----------------------------------------------
+
+To introduce different test items you can create
+one or more ``conftest.py`` files in your project.
+When the collection process traverses directories
+and modules the default collectors will produce
+custom Collectors and Items if they are found
+in a local ``conftest.py`` file.
+
+
+Customizing the collection process in a module
+----------------------------------------------
+
+If you have a module where you want to take responsibility for
+collecting your own test Items and possibly even for executing
+a test then you can provide `generative tests`_ that yield
+callables and possibly arguments as a tuple.   This is especially
+useful for calling application test machinery with different
+parameter sets but counting each of the calls as a separate
+tests.
+
+.. _`generative tests`: features.html#generative-tests
+
+The other extension possibility is about
+specifying a custom test ``Item`` class which
+is responsible for setting up and executing an underlying
+test.  Or you can extend the collection process for a whole
+directory tree by putting Items in a ``conftest.py`` configuration file.
+The collection process dynamically consults the *chain of conftest.py*
+modules to determine collectors and items at ``Directory``, ``Module``,
+``Class``, ``Function`` or ``Generator`` level respectively.
+
+Customizing execution of Items and Functions
+----------------------------------------------------
+
+- ``pytest.Function`` test items control execution
+  of a test function through its ``function.runtest()`` method.
+  This method is responsible for performing setup and teardown
+  ("Test Fixtures") for a test Function.
+
+- ``Function.execute(target, *args)`` methods are invoked by
+  the default ``Function.run()`` to actually execute a python
+  function with the given (usually empty set of) arguments.
+
+.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev
+
+
+.. _`test generators`: funcargs.html#test-generators
+
+.. _`generative tests`:
+
+generative tests: yielding parametrized tests
+====================================================
+
+Deprecated since 1.0 in favour of `test generators`_.
+
+*Generative tests* are test methods that are *generator functions* which
+``yield`` callables and their arguments.  This is useful for running a
+test function multiple times against different parameters.  Example::
+
+    def test_generative():
+        for x in (42,17,49):
+            yield check, x
+
+    def check(arg):
+        assert arg % 7 == 0   # second generated tests fails!
+
+Note that ``test_generative()`` will cause three tests
+to get run, notably ``check(42)``, ``check(17)`` and ``check(49)``
+of which the middle one will obviously fail.
+
+To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example::
+
+    def test_generative():
+        for x in (42,17,49):
+            yield "case %d" % x, check, x
+
+
+disabling a test class
+----------------------
+
+If you want to disable a complete test class you
+can set the class-level attribute ``disabled``.
+For example, in order to avoid running some tests on Win32::
+
+    class TestPosixOnly(object):
+        disabled = sys.platform == 'win32'
+
+        def test_xxx(self):
+            ...
diff --git a/tools/pytest/doc/en/test/config.html b/tools/third_party/pytest/doc/en/test/config.html
similarity index 100%
rename from tools/pytest/doc/en/test/config.html
rename to tools/third_party/pytest/doc/en/test/config.html
diff --git a/tools/pytest/doc/en/test/dist.html b/tools/third_party/pytest/doc/en/test/dist.html
similarity index 100%
rename from tools/pytest/doc/en/test/dist.html
rename to tools/third_party/pytest/doc/en/test/dist.html
diff --git a/tools/pytest/doc/en/test/extend.html b/tools/third_party/pytest/doc/en/test/extend.html
similarity index 100%
rename from tools/pytest/doc/en/test/extend.html
rename to tools/third_party/pytest/doc/en/test/extend.html
diff --git a/tools/pytest/doc/en/test/index.rst b/tools/third_party/pytest/doc/en/test/index.rst
similarity index 100%
rename from tools/pytest/doc/en/test/index.rst
rename to tools/third_party/pytest/doc/en/test/index.rst
diff --git a/tools/third_party/pytest/doc/en/test/mission.rst b/tools/third_party/pytest/doc/en/test/mission.rst
new file mode 100644
index 0000000..51c252d
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/mission.rst
@@ -0,0 +1,13 @@
+
+Mission
+====================================
+
+``pytest`` strives to make testing a fun and no-boilerplate effort.
+
+The tool is distributed as a `pytest` package.  Its project independent
+``pytest`` command line tool helps you to:
+
+* rapidly collect and run tests
+* run unit- or doctests, functional or integration tests
+* distribute tests to multiple environments
+* use local or global plugins for custom test types and setup
diff --git a/tools/third_party/pytest/doc/en/test/plugin/cov.rst b/tools/third_party/pytest/doc/en/test/plugin/cov.rst
new file mode 100644
index 0000000..541c7ef
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/cov.rst
@@ -0,0 +1,230 @@
+
+produce code coverage reports using the 'coverage' package, including support for distributed testing.
+======================================================================================================
+
+
+.. contents::
+  :local:
+
+This plugin produces coverage reports.  It supports centralised testing and distributed testing in
+both load and each modes.  It also supports coverage of subprocesses.
+
+All features offered by the coverage package should be available, either through pytest-cov or
+through coverage's config file.
+
+
+Installation
+------------
+
+The `pytest-cov`_ package may be installed with pip or easy_install::
+
+    pip install pytest-cov
+    easy_install pytest-cov
+
+.. _`pytest-cov`: http://pypi.python.org/pypi/pytest-cov/
+
+
+Uninstallation
+--------------
+
+Uninstalling packages is supported by pip::
+
+    pip uninstall pytest-cov
+
+However easy_install does not provide an uninstall facility.
+
+.. IMPORTANT::
+
+    Ensure that you manually delete the init_covmain.pth file in your
+    site-packages directory.
+
+    This file starts coverage collection of subprocesses if appropriate during
+    site initialization at python startup.
+
+
+Usage
+-----
+
+Centralised Testing
+~~~~~~~~~~~~~~~~~~~
+
+Centralised testing will report on the combined coverage of the main process and all of it's
+subprocesses.
+
+Running centralised testing::
+
+    pytest --cov myproj tests/
+
+Shows a terminal report::
+
+    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+    Name                 Stmts   Miss  Cover
+    ----------------------------------------
+    myproj/__init__          2      0   100%
+    myproj/myproj          257     13    94%
+    myproj/feature4286      94      7    92%
+    ----------------------------------------
+    TOTAL                  353     20    94%
+
+
+Distributed Testing: Load
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Distributed testing with dist mode set to load will report on the combined coverage of all slaves.
+The slaves may be spread out over any number of hosts and each slave may be located anywhere on the
+file system.  Each slave will have it's subprocesses measured.
+
+Running distributed testing with dist mode set to load::
+
+    pytest --cov myproj -n 2 tests/
+
+Shows a terminal report::
+
+    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+    Name                 Stmts   Miss  Cover
+    ----------------------------------------
+    myproj/__init__          2      0   100%
+    myproj/myproj          257     13    94%
+    myproj/feature4286      94      7    92%
+    ----------------------------------------
+    TOTAL                  353     20    94%
+
+
+Again but spread over different hosts and different directories::
+
+    pytest --cov myproj --dist load
+            --tx ssh=memedough@host1//chdir=testenv1
+            --tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python
+            --rsyncdir myproj --rsyncdir tests --rsync examples
+            tests/
+
+Shows a terminal report::
+
+    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+    Name                 Stmts   Miss  Cover
+    ----------------------------------------
+    myproj/__init__          2      0   100%
+    myproj/myproj          257     13    94%
+    myproj/feature4286      94      7    92%
+    ----------------------------------------
+    TOTAL                  353     20    94%
+
+
+Distributed Testing: Each
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Distributed testing with dist mode set to each will report on the combined coverage of all slaves.
+Since each slave is running all tests this allows generating a combined coverage report for multiple
+environments.
+
+Running distributed testing with dist mode set to each::
+
+    pytest --cov myproj --dist each
+            --tx popen//chdir=/tmp/testenv3//python=/usr/local/python27/bin/python
+            --tx ssh=memedough@host2//chdir=/tmp/testenv4//python=/tmp/env2/bin/python
+            --rsyncdir myproj --rsyncdir tests --rsync examples
+            tests/
+
+Shows a terminal report::
+
+    ---------------------------------------- coverage ----------------------------------------
+                              platform linux2, python 2.6.5-final-0
+                              platform linux2, python 2.7.0-final-0
+    Name                 Stmts   Miss  Cover
+    ----------------------------------------
+    myproj/__init__          2      0   100%
+    myproj/myproj          257     13    94%
+    myproj/feature4286      94      7    92%
+    ----------------------------------------
+    TOTAL                  353     20    94%
+
+
+Reporting
+---------
+
+It is possible to generate any combination of the reports for a single test run.
+
+The available reports are terminal (with or without missing line numbers shown), HTML, XML and
+annotated source code.
+
+The terminal report without line numbers (default)::
+
+    pytest --cov-report term --cov myproj tests/
+
+    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+    Name                 Stmts   Miss  Cover
+    ----------------------------------------
+    myproj/__init__          2      0   100%
+    myproj/myproj          257     13    94%
+    myproj/feature4286      94      7    92%
+    ----------------------------------------
+    TOTAL                  353     20    94%
+
+
+The terminal report with line numbers::
+
+    pytest --cov-report term-missing --cov myproj tests/
+
+    -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+    Name                 Stmts   Miss  Cover   Missing
+    --------------------------------------------------
+    myproj/__init__          2      0   100%
+    myproj/myproj          257     13    94%   24-26, 99, 149, 233-236, 297-298, 369-370
+    myproj/feature4286      94      7    92%   183-188, 197
+    --------------------------------------------------
+    TOTAL                  353     20    94%
+
+
+The remaining three reports output to files without showing anything on the terminal (useful for
+when the output is going to a continuous integration server)::
+
+    pytest --cov-report html --cov-report xml --cov-report annotate --cov myproj tests/
+
+
+Coverage Data File
+------------------
+
+The data file is erased at the beginning of testing to ensure clean data for each test run.
+
+The data file is left at the end of testing so that it is possible to use normal coverage tools to
+examine it.
+
+
+Limitations
+-----------
+
+For distributed testing the slaves must have the pytest-cov package installed.  This is needed since
+the plugin must be registered through setuptools / distribute for pytest to start the plugin on the
+slave.
+
+For subprocess measurement environment variables must make it from the main process to the
+subprocess.  The python used by the subprocess must have pytest-cov installed.  The subprocess must
+do normal site initialization so that the environment variables can be detected and coverage
+started.
+
+
+Acknowledgments
+----------------
+
+Holger Krekel for pytest with its distributed testing support.
+
+Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs.
+
+Whilst this plugin has been built fresh from the ground up to support distributed testing it has
+been influenced by the work done on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and
+nose-cover (Jason Pellerin) which are other coverage plugins for pytest and nose respectively.
+
+No doubt others have contributed to these tools as well.
+
+command line options
+--------------------
+
+
+``--cov=path``
+    measure coverage for filesystem path (multi-allowed)
+``--cov-report=type``
+    type of report to generate: term, term-missing, annotate, html, xml (multi-allowed)
+``--cov-config=path``
+    config file for coverage, default: .coveragerc
+
+.. include:: links.txt
diff --git a/tools/third_party/pytest/doc/en/test/plugin/coverage.rst b/tools/third_party/pytest/doc/en/test/plugin/coverage.rst
new file mode 100644
index 0000000..71139d0
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/coverage.rst
@@ -0,0 +1,51 @@
+
+Write and report coverage data with the 'coverage' package.
+===========================================================
+
+
+.. contents::
+  :local:
+
+Note: Original code by Ross Lawley. 
+
+Install
+--------------
+
+Use pip to (un)install::
+
+    pip install pytest-coverage 
+    pip uninstall pytest-coverage 
+
+or alternatively use easy_install to install::
+
+    easy_install pytest-coverage 
+
+
+Usage 
+-------------
+
+To get full test coverage reports for a particular package type::
+
+    pytest --cover-report=report
+
+command line options
+--------------------
+
+
+``--cover=COVERPACKAGES``
+    (multi allowed) only include info from specified package.
+``--cover-report=REPORT_TYPE``
+    html: Directory for html output.
+                    report: Output a text report.
+                    annotate: Annotate your source code for which lines were executed and which were not.
+                    xml: Output an xml report compatible with the cobertura plugin for hudson.
+``--cover-directory=DIRECTORY``
+    Directory for the reports (html / annotate results) defaults to ./coverage
+``--cover-xml-file=XML_FILE``
+    File for the xml report defaults to ./coverage.xml
+``--cover-show-missing``
+    Show missing files
+``--cover-ignore-errors=IGNORE_ERRORS``
+    Ignore errors of finding source files for code.
+
+.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/django.rst b/tools/third_party/pytest/doc/en/test/plugin/django.rst
similarity index 100%
rename from tools/pytest/doc/en/test/plugin/django.rst
rename to tools/third_party/pytest/doc/en/test/plugin/django.rst
diff --git a/tools/third_party/pytest/doc/en/test/plugin/figleaf.rst b/tools/third_party/pytest/doc/en/test/plugin/figleaf.rst
new file mode 100644
index 0000000..0c1603a
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/figleaf.rst
@@ -0,0 +1,44 @@
+
+report test coverage using the 'figleaf' package.
+=================================================
+
+
+.. contents::
+  :local:
+
+Install
+---------------
+
+To install the plugin issue::
+
+    easy_install pytest-figleaf  # or
+    pip install pytest-figleaf   
+
+and if you are using pip you can also uninstall::
+
+    pip uninstall pytest-figleaf
+
+
+Usage
+---------------
+
+After installation you can simply type::
+
+    pytest --figleaf [...]
+
+to enable figleaf coverage in your test run.  A default ".figleaf" data file
+and "html" directory will be created.  You can use command line options
+to control where data and html files are created.
+
+command line options
+--------------------
+
+
+``--figleaf``
+    trace python coverage with figleaf and write HTML for files below the current working dir
+``--fig-data=dir``
+    set tracing file, default: ".figleaf".
+``--fig-html=dir``
+    set html reporting dir, default "html".
+
+.. include:: links.txt
diff --git a/tools/third_party/pytest/doc/en/test/plugin/helpconfig.rst b/tools/third_party/pytest/doc/en/test/plugin/helpconfig.rst
new file mode 100644
index 0000000..326b75c
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/helpconfig.rst
@@ -0,0 +1,36 @@
+
+provide version info, conftest/environment config names.
+========================================================
+
+
+.. contents::
+  :local:
+
+
+
+command line options
+--------------------
+
+
+``--version``
+    display py lib version and import information.
+``-p name``
+    early-load given plugin (multi-allowed).
+``--trace-config``
+    trace considerations of conftest.py files.
+``--debug``
+    generate and show internal debugging information.
+``--help-config``
+    show available conftest.py and ENV-variable names.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+1. Download `pytest_helpconfig.py`_ plugin source code
+2. put it somewhere as ``pytest_helpconfig.py`` into your import path
+3. a subsequent ``pytest`` run will use your local version
+
+Checkout customize_, other plugins_ or `get in contact`_.
+
+.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/index.rst b/tools/third_party/pytest/doc/en/test/plugin/index.rst
similarity index 100%
rename from tools/pytest/doc/en/test/plugin/index.rst
rename to tools/third_party/pytest/doc/en/test/plugin/index.rst
diff --git a/tools/third_party/pytest/doc/en/test/plugin/links.rst b/tools/third_party/pytest/doc/en/test/plugin/links.rst
new file mode 100644
index 0000000..6dec2b4
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/links.rst
@@ -0,0 +1,45 @@
+.. _`helpconfig`: helpconfig.html
+.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_recwarn.py
+.. _`unittest`: unittest.html
+.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_monkeypatch.py
+.. _`pastebin`: pastebin.html
+.. _`skipping`: skipping.html
+.. _`plugins`: index.html
+.. _`mark`: mark.html
+.. _`tmpdir`: tmpdir.html
+.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_doctest.py
+.. _`capture`: capture.html
+.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_nose.py
+.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_restdoc.py
+.. _`restdoc`: restdoc.html
+.. _`xdist`: xdist.html
+.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_pastebin.py
+.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_tmpdir.py
+.. _`terminal`: terminal.html
+.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_hooklog.py
+.. _`capturelog`: capturelog.html
+.. _`junitxml`: junitxml.html
+.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_skipping.py
+.. _`checkout the pytest development version`: ../../install.html#checkout
+.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_helpconfig.py
+.. _`oejskit`: oejskit.html
+.. _`doctest`: doctest.html
+.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_mark.py
+.. _`get in contact`: ../../contact.html
+.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_capture.py
+.. _`figleaf`: figleaf.html
+.. _`customize`: ../customize.html
+.. _`hooklog`: hooklog.html
+.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_terminal.py
+.. _`recwarn`: recwarn.html
+.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_pdb.py
+.. _`monkeypatch`: monkeypatch.html
+.. _`coverage`: coverage.html
+.. _`resultlog`: resultlog.html
+.. _`cov`: cov.html
+.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_junitxml.py
+.. _`django`: django.html
+.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_unittest.py
+.. _`nose`: nose.html
+.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_resultlog.py
+.. _`pdb`: pdb.html
diff --git a/tools/third_party/pytest/doc/en/test/plugin/nose.rst b/tools/third_party/pytest/doc/en/test/plugin/nose.rst
new file mode 100644
index 0000000..9eeae5f
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/nose.rst
@@ -0,0 +1,56 @@
+
+nose-compatibility plugin: allow to run nose test suites natively.
+==================================================================
+
+
+.. contents::
+  :local:
+
+This is an experimental plugin for allowing to run tests written
+in 'nosetests' style with ``pytest``.
+
+Usage
+-------------
+
+type::
+
+    pytest  # instead of 'nosetests'
+
+and you should be able to run nose style tests and at the same
+time can make full use of pytest's capabilities.
+
+Supported nose Idioms
+----------------------
+
+* setup and teardown at module/class/method level
+* SkipTest exceptions and markers
+* setup/teardown decorators
+* yield-based tests and their setup
+* general usage of nose utilities
+
+Unsupported idioms / issues
+----------------------------------
+
+- nose-style doctests are not collected and executed correctly,
+  also fixtures don't work.
+
+- no nose-configuration is recognized
+
+If you find other issues or have suggestions please run::
+
+    pytest --pastebin=all
+
+and send the resulting URL to a ``pytest`` contact channel,
+at best to the mailing list.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+1. Download `pytest_nose.py`_ plugin source code
+2. put it somewhere as ``pytest_nose.py`` into your import path
+3. a subsequent ``pytest`` run will use your local version
+
+Checkout customize_, other plugins_ or `get in contact`_.
+
+.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/plugin/oejskit.rst b/tools/third_party/pytest/doc/en/test/plugin/oejskit.rst
similarity index 100%
rename from tools/pytest/doc/en/test/plugin/oejskit.rst
rename to tools/third_party/pytest/doc/en/test/plugin/oejskit.rst
diff --git a/tools/third_party/pytest/doc/en/test/plugin/terminal.rst b/tools/third_party/pytest/doc/en/test/plugin/terminal.rst
new file mode 100644
index 0000000..e07d4f7
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/terminal.rst
@@ -0,0 +1,38 @@
+
+Implements terminal reporting of the full testing process.
+==========================================================
+
+
+.. contents::
+  :local:
+
+This is a good source for looking at the various reporting hooks.
+
+command line options
+--------------------
+
+
+``-v, --verbose``
+    increase verbosity.
+``-r chars``
+    show extra test summary info as specified by chars (f)ailed, (s)skipped, (x)failed, (X)passed.
+``-l, --showlocals``
+    show locals in tracebacks (disabled by default).
+``--tb=style``
+    traceback print mode (long/short/line/no).
+``--full-trace``
+    don't cut any tracebacks (default is to cut).
+``--fixtures``
+    show available function arguments, sorted by plugin
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+1. Download `pytest_terminal.py`_ plugin source code
+2. put it somewhere as ``pytest_terminal.py`` into your import path
+3. a subsequent ``pytest`` run will use your local version
+
+Checkout customize_, other plugins_ or `get in contact`_.
+
+.. include:: links.txt
diff --git a/tools/third_party/pytest/doc/en/test/plugin/xdist.rst b/tools/third_party/pytest/doc/en/test/plugin/xdist.rst
new file mode 100644
index 0000000..506d240
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/test/plugin/xdist.rst
@@ -0,0 +1,172 @@
+
+loop on failing tests, distribute test runs to CPUs and hosts.
+==============================================================
+
+
+.. contents::
+  :local:
+
+The `pytest-xdist`_ plugin extends ``pytest`` with some unique
+test execution modes:
+
+* Looponfail: run your tests repeatedly in a subprocess.  After each run
+  ``pytest`` waits until a file in your project changes and then re-runs the
+  previously failing tests.  This is repeated until all tests pass after which
+  again a full run is performed.
+
+* Load-balancing: if you have multiple CPUs or hosts you can use
+  those for a combined test run.  This allows to speed up
+  development or to use special resources of remote machines.
+
+* Multi-Platform coverage: you can specify different Python interpreters
+  or different platforms and run tests in parallel on all of them.
+
+Before running tests remotely, ``pytest`` efficiently synchronizes your
+program source code to the remote place.  All test results
+are reported back and displayed to your local test session.
+You may specify different Python versions and interpreters.
+
+.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist
+
+Usage examples
+---------------------
+
+Speed up test runs by sending tests to multiple CPUs
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+To send tests to multiple CPUs, type::
+
+    pytest -n NUM
+
+Especially for longer running tests or tests requiring
+a lot of IO this can lead to considerable speed ups.
+
+
+Running tests in a Python subprocess
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+To instantiate a python2.4 sub process and send tests to it, you may type::
+
+    pytest -d --tx popen//python=python2.4
+
+This will start a subprocess which is run with the "python2.4"
+Python interpreter, found in your system binary lookup path.
+
+If you prefix the --tx option value like this::
+
+    --tx 3*popen//python=python2.4
+
+then three subprocesses would be created and tests
+will be load-balanced across these three processes.
+
+
+Sending tests to remote SSH accounts
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Suppose you have a package ``mypkg`` which contains some
+tests that you can successfully run locally. And you
+have a ssh-reachable machine ``myhost``.  Then
+you can ad-hoc distribute your tests by typing::
+
+    pytest -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg
+
+This will synchronize your ``mypkg`` package directory
+to a remote ssh account and then locally collect tests
+and send them to remote places for execution.
+
+You can specify multiple ``--rsyncdir`` directories
+to be sent to the remote side.
+
+**NOTE:** For ``pytest`` to collect and send tests correctly
+you not only need to make sure all code and tests
+directories are rsynced, but that any test (sub) directory
+also has an ``__init__.py`` file because internally
+``pytest`` references tests using their fully qualified python
+module path.  **You will otherwise get strange errors**
+during setup of the remote side.
+
+Sending tests to remote Socket Servers
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Download the single-module `socketserver.py`_ Python program
+and run it like this::
+
+    python socketserver.py
+
+It will tell you that it starts listening on the default
+port.  You can now on your home machine specify this
+new socket host with something like this::
+
+    pytest -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
+
+
+.. _`atonce`:
+
+Running tests on many platforms at once
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+The basic command to run tests on multiple platforms is::
+
+    pytest --dist=each --tx=spec1 --tx=spec2
+
+If you specify a windows host, an OSX host and a Linux
+environment this command will send each tests to all
+platforms - and report back failures from all platforms
+at once.   The specifications strings use the `xspec syntax`_.
+
+.. _`xspec syntax`: http://codespeak.net/execnet/trunk/basics.html#xspec
+
+.. _`socketserver.py`: http://codespeak.net/svn/py/dist/py/execnet/script/socketserver.py
+
+.. _`execnet`: http://codespeak.net/execnet
+
+Specifying test exec environments in a conftest.py
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Instead of specifying command line options, you can
+put options values in a ``conftest.py`` file like this::
+
+    option_tx = ['ssh=myhost//python=python2.7', 'popen//python=python2.7']
+    option_dist = True
+
+Any commandline ``--tx`` specifications  will add to the list of
+available execution environments.
+
+Specifying "rsync" dirs in a conftest.py
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+In your ``mypkg/conftest.py`` you may specify directories to synchronise
+or to exclude::
+
+    rsyncdirs = ['.', '../plugins']
+    rsyncignore = ['_cache']
+
+These directory specifications are relative to the directory
+where the ``conftest.py`` is found.
+
+command line options
+--------------------
+
+
+``-f, --looponfail``
+    run tests in subprocess, wait for modified files and re-run failing test set until all pass.
+``-n numprocesses``
+    shortcut for '--dist=load --tx=NUM*popen'
+``--boxed``
+    box each test run in a separate process (unix)
+``--dist=distmode``
+    set mode for distributing tests to exec environments.
+
+    each: send each test to each available environment.
+
+    load: send each test to one available environment so it is run only once.
+
+    (default) no: run tests inprocess, don't distribute.
+``--tx=xspec``
+    add a test execution environment. some examples: --tx popen//python=python2.7 --tx socket=192.168.1.102:8888 --tx ssh=user@codespeak.net//chdir=testcache
+``-d``
+    load-balance tests.  shortcut for '--dist=load'
+``--rsyncdir=dir1``
+    add directory for rsyncing to remote tx nodes.
+
+.. include:: links.txt
diff --git a/tools/pytest/doc/en/test/test.html b/tools/third_party/pytest/doc/en/test/test.html
similarity index 100%
rename from tools/pytest/doc/en/test/test.html
rename to tools/third_party/pytest/doc/en/test/test.html
diff --git a/tools/third_party/pytest/doc/en/tmpdir.rst b/tools/third_party/pytest/doc/en/tmpdir.rst
new file mode 100644
index 0000000..b817448
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/tmpdir.rst
@@ -0,0 +1,111 @@
+
+.. _`tmpdir handling`:
+.. _tmpdir:
+
+Temporary directories and files
+================================================
+
+The 'tmpdir' fixture
+--------------------
+
+You can use the ``tmpdir`` fixture which will
+provide a temporary directory unique to the test invocation,
+created in the `base temporary directory`_.
+
+``tmpdir`` is a `py.path.local`_ object which offers ``os.path`` methods
+and more.  Here is an example test usage::
+
+    # content of test_tmpdir.py
+    import os
+    def test_create_file(tmpdir):
+        p = tmpdir.mkdir("sub").join("hello.txt")
+        p.write("content")
+        assert p.read() == "content"
+        assert len(tmpdir.listdir()) == 1
+        assert 0
+
+Running this would result in a passed test except for the last
+``assert 0`` line which we use to look at values::
+
+    $ pytest test_tmpdir.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_tmpdir.py F                                                     [100%]
+    
+    ================================= FAILURES =================================
+    _____________________________ test_create_file _____________________________
+    
+    tmpdir = local('PYTEST_TMPDIR/test_create_file0')
+    
+        def test_create_file(tmpdir):
+            p = tmpdir.mkdir("sub").join("hello.txt")
+            p.write("content")
+            assert p.read() == "content"
+            assert len(tmpdir.listdir()) == 1
+    >       assert 0
+    E       assert 0
+    
+    test_tmpdir.py:7: AssertionError
+    ========================= 1 failed in 0.12 seconds =========================
+
+The 'tmpdir_factory' fixture
+----------------------------
+
+.. versionadded:: 2.8
+
+The ``tmpdir_factory`` is a session-scoped fixture which can be used
+to create arbitrary temporary directories from any other fixture or test.
+
+For example, suppose your test suite needs a large image on disk, which is
+generated procedurally. Instead of computing the same image for each test
+that uses it into its own ``tmpdir``, you can generate it once per-session
+to save time:
+
+.. code-block:: python
+
+    # contents of conftest.py
+    import pytest
+
+    @pytest.fixture(scope='session')
+    def image_file(tmpdir_factory):
+        img = compute_expensive_image()
+        fn = tmpdir_factory.mktemp('data').join('img.png')
+        img.save(str(fn))
+        return fn
+
+    # contents of test_image.py
+    def test_histogram(image_file):
+        img = load_image(image_file)
+        # compute and test histogram
+
+``tmpdir_factory`` instances have the following methods:
+
+.. currentmodule:: _pytest.tmpdir
+
+.. automethod:: TempdirFactory.mktemp
+.. automethod:: TempdirFactory.getbasetemp
+
+.. _`base temporary directory`:
+
+The default base temporary directory
+-----------------------------------------------
+
+Temporary directories are by default created as sub-directories of
+the system temporary directory.  The base name will be ``pytest-NUM`` where
+``NUM`` will be incremented with each test run.  Moreover, entries older
+than 3 temporary directories will be removed.
+
+You can override the default temporary directory setting like this::
+
+    pytest --basetemp=mydir
+
+When distributing tests on the local machine, ``pytest`` takes care to
+configure a basetemp directory for the sub processes such that all temporary
+data lands below a single per-test run basetemp directory.
+
+.. _`py.path.local`: http://py.rtfd.org/en/latest/path.html
+
+
diff --git a/tools/third_party/pytest/doc/en/unittest.rst b/tools/third_party/pytest/doc/en/unittest.rst
new file mode 100644
index 0000000..b44bda4
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/unittest.rst
@@ -0,0 +1,245 @@
+
+.. _`unittest.TestCase`:
+.. _`unittest`:
+
+unittest.TestCase Support
+=========================
+
+``pytest`` supports running Python ``unittest``-based tests out of the box.
+It's meant for leveraging existing ``unittest``-based test suites
+to use pytest as a test runner and also allow to incrementally adapt
+the test suite to take full advantage of pytest's features.
+
+To run an existing ``unittest``-style test suite using ``pytest``, type::
+
+    pytest tests
+
+
+pytest will automatically collect ``unittest.TestCase`` subclasses and
+their ``test`` methods in ``test_*.py`` or ``*_test.py`` files.
+
+Almost all ``unittest`` features are supported:
+
+* ``@unittest.skip`` style decorators;
+* ``setUp/tearDown``;
+* ``setUpClass/tearDownClass()``;
+
+.. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
+.. _`setUpModule/tearDownModule`: https://docs.python.org/3/library/unittest.html#setupmodule-and-teardownmodule
+.. _`subtests`: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests
+
+Up to this point pytest does not have support for the following features:
+
+* `load_tests protocol`_;
+* `setUpModule/tearDownModule`_;
+* `subtests`_;
+
+Benefits out of the box
+-----------------------
+
+By running your test suite with pytest you can make use of several features,
+in most cases without having to modify existing code:
+
+* Obtain :ref:`more informative tracebacks <tbreportdemo>`;
+* :ref:`stdout and stderr <captures>` capturing;
+* :ref:`Test selection options <select-tests>` using ``-k`` and ``-m`` flags;
+* :ref:`maxfail`;
+* :ref:`--pdb <pdb-option>` command-line option for debugging on test failures
+  (see :ref:`note <pdb-unittest-note>` below);
+* Distribute tests to multiple CPUs using the `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_ plugin;
+* Use :ref:`plain assert-statements <assert>` instead of ``self.assert*`` functions (`unittest2pytest
+  <https://pypi.python.org/pypi/unittest2pytest/>`__ is immensely helpful in this);
+
+
+pytest features in ``unittest.TestCase`` subclasses
+---------------------------------------------------
+
+The following pytest features work in ``unittest.TestCase`` subclasses:
+
+* :ref:`Marks <mark>`: :ref:`skip <skip>`, :ref:`skipif <skipif>`, :ref:`xfail <xfail>`;
+* :ref:`Auto-use fixtures <mixing-fixtures>`;
+
+The following pytest features **do not** work, and probably
+never will due to different design philosophies:
+
+* :ref:`Fixtures <fixture>` (except for ``autouse`` fixtures, see :ref:`below <mixing-fixtures>`);
+* :ref:`Parametrization <parametrize>`;
+* :ref:`Custom hooks <writing-plugins>`;
+
+
+Third party plugins may or may not work well, depending on the plugin and the test suite.
+
+.. _mixing-fixtures:
+
+Mixing pytest fixtures into ``unittest.TestCase`` subclasses using marks
+------------------------------------------------------------------------
+
+Running your unittest with ``pytest`` allows you to use its
+:ref:`fixture mechanism <fixture>` with ``unittest.TestCase`` style
+tests.  Assuming you have at least skimmed the pytest fixture features,
+let's jump-start into an example that integrates a pytest ``db_class``
+fixture, setting up a class-cached database object, and then reference
+it from a unittest-style test::
+
+    # content of conftest.py
+
+    # we define a fixture function below and it will be "used" by
+    # referencing its name from tests
+
+    import pytest
+
+    @pytest.fixture(scope="class")
+    def db_class(request):
+        class DummyDB(object):
+            pass
+        # set a class attribute on the invoking test context 
+        request.cls.db = DummyDB()
+
+This defines a fixture function ``db_class`` which - if used - is 
+called once for each test class and which sets the class-level 
+``db`` attribute to a ``DummyDB`` instance.  The fixture function
+achieves this by receiving a special ``request`` object which gives
+access to :ref:`the requesting test context <request-context>` such
+as the ``cls`` attribute, denoting the class from which the fixture 
+is used.  This architecture de-couples fixture writing from actual test
+code and allows re-use of the fixture by a minimal reference, the fixture
+name.  So let's write an actual ``unittest.TestCase`` class using our 
+fixture definition::
+
+    # content of test_unittest_db.py
+
+    import unittest
+    import pytest
+
+    @pytest.mark.usefixtures("db_class")
+    class MyTest(unittest.TestCase):
+        def test_method1(self):
+            assert hasattr(self, "db")
+            assert 0, self.db   # fail for demo purposes
+
+        def test_method2(self):
+            assert 0, self.db   # fail for demo purposes
+
+The ``@pytest.mark.usefixtures("db_class")`` class-decorator makes sure that 
+the pytest fixture function ``db_class`` is called once per class.
+Due to the deliberately failing assert statements, we can take a look at
+the ``self.db`` values in the traceback::
+
+    $ pytest test_unittest_db.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 2 items
+    
+    test_unittest_db.py FF                                               [100%]
+    
+    ================================= FAILURES =================================
+    ___________________________ MyTest.test_method1 ____________________________
+    
+    self = <test_unittest_db.MyTest testMethod=test_method1>
+    
+        def test_method1(self):
+            assert hasattr(self, "db")
+    >       assert 0, self.db   # fail for demo purposes
+    E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef>
+    E       assert 0
+    
+    test_unittest_db.py:9: AssertionError
+    ___________________________ MyTest.test_method2 ____________________________
+    
+    self = <test_unittest_db.MyTest testMethod=test_method2>
+    
+        def test_method2(self):
+    >       assert 0, self.db   # fail for demo purposes
+    E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef>
+    E       assert 0
+    
+    test_unittest_db.py:12: AssertionError
+    ========================= 2 failed in 0.12 seconds =========================
+
+This default pytest traceback shows that the two test methods
+share the same ``self.db`` instance which was our intention
+when writing the class-scoped fixture function above.
+
+
+Using autouse fixtures and accessing other fixtures
+---------------------------------------------------
+
+Although it's usually better to explicitly declare use of fixtures you need
+for a given test, you may sometimes want to have fixtures that are 
+automatically used in a given context.  After all, the traditional 
+style of unittest-setup mandates the use of this implicit fixture writing
+and chances are, you are used to it or like it.  
+
+You can flag fixture functions with ``@pytest.fixture(autouse=True)``
+and define the fixture function in the context where you want it used.
+Let's look at an ``initdir`` fixture which makes all test methods of a
+``TestCase`` class execute in a temporary directory with a
+pre-initialized ``samplefile.ini``.  Our ``initdir`` fixture itself uses
+the pytest builtin :ref:`tmpdir <tmpdir>` fixture to delegate the
+creation of a per-test temporary directory::
+
+    # content of test_unittest_cleandir.py
+    import pytest
+    import unittest
+
+    class MyTest(unittest.TestCase):
+
+        @pytest.fixture(autouse=True)
+        def initdir(self, tmpdir):
+            tmpdir.chdir() # change to pytest-provided temporary directory
+            tmpdir.join("samplefile.ini").write("# testdata")
+
+        def test_method(self):
+            with open("samplefile.ini") as f:
+                s = f.read()
+            assert "testdata" in s
+
+Due to the ``autouse`` flag the ``initdir`` fixture function will be
+used for all methods of the class where it is defined.  This is a
+shortcut for using a ``@pytest.mark.usefixtures("initdir")`` marker
+on the class like in the previous example.
+
+Running this test module ...::
+
+    $ pytest -q test_unittest_cleandir.py
+    .                                                                    [100%]
+    1 passed in 0.12 seconds
+
+... gives us one passed test because the ``initdir`` fixture function
+was executed ahead of the ``test_method``.
+
+.. note::
+
+   ``unittest.TestCase`` methods cannot directly receive fixture
+   arguments as implementing that is likely to inflict
+   on the ability to run general unittest.TestCase test suites.
+
+   The above ``usefixtures`` and ``autouse`` examples should help to mix in
+   pytest fixtures into unittest suites.
+
+   You can also gradually move away from subclassing from ``unittest.TestCase`` to *plain asserts*
+   and then start to benefit from the full pytest feature set step by step.
+
+.. _pdb-unittest-note:
+
+.. note::
+
+    Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
+    disable tearDown and cleanup methods for the case that an Exception
+    occurs. This allows proper post mortem debugging for all applications
+    which have significant logic in their tearDown machinery. However,
+    supporting this feature has the following side effect: If people
+    overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to
+    to overwrite ``debug`` in the same way  (this is also true for standard
+    unittest).
+
+.. note::
+
+    Due to architectural differences between the two frameworks, setup and
+    teardown for ``unittest``-based tests is performed during the ``call`` phase
+    of testing instead of in ``pytest``'s standard ``setup`` and ``teardown``
+    stages. This can be important to understand in some situations, particularly
+    when reasoning about errors. For example, if a ``unittest``-based suite
+    exhibits errors during setup, ``pytest`` will report no errors during its
+    ``setup`` phase and will instead raise the error during ``call``.
diff --git a/tools/third_party/pytest/doc/en/usage.rst b/tools/third_party/pytest/doc/en/usage.rst
new file mode 100644
index 0000000..6091db8
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/usage.rst
@@ -0,0 +1,392 @@
+
+.. _usage:
+
+Usage and Invocations
+==========================================
+
+
+.. _cmdline:
+
+Calling pytest through ``python -m pytest``
+-----------------------------------------------------
+
+.. versionadded:: 2.0
+
+You can invoke testing through the Python interpreter from the command line::
+
+    python -m pytest [...]
+
+This is almost equivalent to invoking the command line script ``pytest [...]``
+directly, except that calling via ``python`` will also add the current directory to ``sys.path``.
+
+Possible exit codes
+--------------------------------------------------------------
+
+Running ``pytest`` can result in six different exit codes:
+
+:Exit code 0: All tests were collected and passed successfully
+:Exit code 1: Tests were collected and run but some of the tests failed
+:Exit code 2: Test execution was interrupted by the user
+:Exit code 3: Internal error happened while executing tests
+:Exit code 4: pytest command line usage error
+:Exit code 5: No tests were collected
+
+Getting help on version, option names, environment variables
+--------------------------------------------------------------
+
+::
+
+    pytest --version   # shows where pytest was imported from
+    pytest --fixtures  # show available builtin function arguments
+    pytest -h | --help # show help on command line and config file options
+
+
+.. _maxfail:
+
+Stopping after the first (or N) failures
+---------------------------------------------------
+
+To stop the testing process after the first (N) failures::
+
+    pytest -x            # stop after first failure
+    pytest --maxfail=2    # stop after two failures
+
+.. _select-tests:
+
+Specifying tests / selecting tests
+---------------------------------------------------
+
+Pytest supports several ways to run and select tests from the command-line.
+
+**Run tests in a module**
+
+::
+
+    pytest test_mod.py
+
+**Run tests in a directory**
+
+::
+
+    pytest testing/
+
+**Run tests by keyword expressions**
+
+::
+
+    pytest -k "MyClass and not method"
+
+This will run tests which contain names that match the given *string expression*, which can
+include Python operators that use filenames, class names and function names as variables.
+The example above will run ``TestMyClass.test_something``  but not ``TestMyClass.test_method_simple``.
+
+.. _nodeids:
+
+**Run tests by node ids**
+
+Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed
+by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters.
+
+To run a specific test within a module::
+
+    pytest test_mod.py::test_func
+
+
+Another example specifying a test method in the command line::
+
+    pytest test_mod.py::TestClass::test_method
+
+**Run tests by marker expressions**
+
+::
+
+    pytest -m slow
+
+Will run all tests which are decorated with the ``@pytest.mark.slow`` decorator.
+
+For more information see :ref:`marks <mark>`.
+
+**Run tests from packages**
+
+::
+
+    pytest --pyargs pkg.testing
+     
+This will import ``pkg.testing`` and use its filesystem location to find and run tests from.
+    
+
+Modifying Python traceback printing
+----------------------------------------------
+
+Examples for modifying traceback printing::
+
+    pytest --showlocals # show local variables in tracebacks
+    pytest -l           # show local variables (shortcut)
+
+    pytest --tb=auto    # (default) 'long' tracebacks for the first and last
+                         # entry, but 'short' style for the other entries
+    pytest --tb=long    # exhaustive, informative traceback formatting
+    pytest --tb=short   # shorter traceback format
+    pytest --tb=line    # only one line per failure
+    pytest --tb=native  # Python standard library formatting
+    pytest --tb=no      # no traceback at all
+
+The ``--full-trace`` causes very long traces to be printed on error (longer
+than ``--tb=long``). It also ensures that a stack trace is printed on
+**KeyboardInterrupt** (Ctrl+C).
+This is very useful if the tests are taking too long and you interrupt them
+with Ctrl+C to find out where the tests are *hanging*. By default no output
+will be shown (because KeyboardInterrupt is caught by pytest). By using this
+option you make sure a trace is shown.
+
+
+.. _pdb-option:
+
+Dropping to PDB_ (Python Debugger) on failures
+-----------------------------------------------
+
+.. _PDB: http://docs.python.org/library/pdb.html
+
+Python comes with a builtin Python debugger called PDB_.  ``pytest``
+allows one to drop into the PDB_ prompt via a command line option::
+
+    pytest --pdb
+
+This will invoke the Python debugger on every failure.  Often you might
+only want to do this for the first failing test to understand a certain
+failure situation::
+
+    pytest -x --pdb   # drop to PDB on first failure, then end test session
+    pytest --pdb --maxfail=3  # drop to PDB for first three failures
+
+Note that on any failure the exception information is stored on
+``sys.last_value``, ``sys.last_type`` and ``sys.last_traceback``. In
+interactive use, this allows one to drop into postmortem debugging with
+any debug tool. One can also manually access the exception information,
+for example::
+
+    >>> import sys
+    >>> sys.last_traceback.tb_lineno
+    42
+    >>> sys.last_value
+    AssertionError('assert result == "ok"',)
+
+.. _breakpoints:
+
+Setting breakpoints
+-------------------
+
+.. versionadded: 2.4.0
+
+To set a breakpoint in your code use the native Python ``import pdb;pdb.set_trace()`` call
+in your code and pytest automatically disables its output capture for that test:
+
+* Output capture in other tests is not affected.
+* Any prior test output that has already been captured and will be processed as
+  such.
+* Any later output produced within the same test will not be captured and will
+  instead get sent directly to ``sys.stdout``. Note that this holds true even
+  for test output occurring after you exit the interactive PDB_ tracing session
+  and continue with the regular test run.
+
+.. _durations:
+
+Profiling test execution duration
+-------------------------------------
+
+.. versionadded: 2.2
+
+To get a list of the slowest 10 test durations::
+
+    pytest --durations=10
+
+
+Creating JUnitXML format files
+----------------------------------------------------
+
+To create result files which can be read by Jenkins_ or other Continuous
+integration servers, use this invocation::
+
+    pytest --junitxml=path
+
+to create an XML file at ``path``.
+
+.. versionadded:: 3.1
+
+To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file:
+
+.. code-block:: ini
+
+    [pytest]
+    junit_suite_name = my_suite
+
+record_xml_property
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 2.8
+
+If you want to log additional information for a test, you can use the
+``record_xml_property`` fixture:
+
+.. code-block:: python
+
+    def test_function(record_xml_property):
+        record_xml_property("example_key", 1)
+        assert 0
+
+This will add an extra property ``example_key="1"`` to the generated
+``testcase`` tag:
+
+.. code-block:: xml
+
+    <testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009">
+      <properties>
+        <property name="example_key" value="1" />
+      </properties>
+    </testcase>
+
+.. warning::
+
+    ``record_xml_property`` is an experimental feature, and its interface might be replaced
+    by something more powerful and general in future versions. The
+    functionality per-se will be kept, however.
+
+    Currently it does not work when used with the ``pytest-xdist`` plugin.
+
+    Also please note that using this feature will break any schema verification.
+    This might be a problem when used with some CI servers.
+
+LogXML: add_global_property
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.0
+
+If you want to add a properties node in the testsuite level, which may contains properties that are relevant
+to all testcases you can use ``LogXML.add_global_properties``
+
+.. code-block:: python
+
+    import pytest
+
+    @pytest.fixture(scope="session")
+    def log_global_env_facts(f):
+
+        if pytest.config.pluginmanager.hasplugin('junitxml'):
+            my_junit = getattr(pytest.config, '_xml', None)
+
+        my_junit.add_global_property('ARCH', 'PPC')
+        my_junit.add_global_property('STORAGE_TYPE', 'CEPH')
+
+    @pytest.mark.usefixtures(log_global_env_facts)
+    def start_and_prepare_env():
+        pass
+
+    class TestMe(object):
+        def test_foo(self):
+            assert True
+
+This will add a property node below the testsuite node to the generated xml:
+
+.. code-block:: xml
+
+    <testsuite errors="0" failures="0" name="pytest" skips="0" tests="1" time="0.006">
+      <properties>
+        <property name="ARCH" value="PPC"/>
+        <property name="STORAGE_TYPE" value="CEPH"/>
+      </properties>
+      <testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/>
+    </testsuite>
+
+.. warning::
+
+    This is an experimental feature, and its interface might be replaced
+    by something more powerful and general in future versions. The
+    functionality per-se will be kept.
+
+Creating resultlog format files
+----------------------------------------------------
+
+.. deprecated:: 3.0
+
+    This option is rarely used and is scheduled for removal in 4.0.
+
+    An alternative for users which still need similar functionality is to use the
+    `pytest-tap <https://pypi.python.org/pypi/pytest-tap>`_ plugin which provides
+    a stream of test data.
+
+    If you have any concerns, please don't hesitate to
+    `open an issue <https://github.com/pytest-dev/pytest/issues>`_.
+
+To create plain-text machine-readable result files you can issue::
+
+    pytest --resultlog=path
+
+and look at the content at the ``path`` location.  Such files are used e.g.
+by the `PyPy-test`_ web page to show test results over several revisions.
+
+.. _`PyPy-test`: http://buildbot.pypy.org/summary
+
+
+Sending test report to online pastebin service
+-----------------------------------------------------
+
+**Creating a URL for each test failure**::
+
+    pytest --pastebin=failed
+
+This will submit test run information to a remote Paste service and
+provide a URL for each failure.  You may select tests as usual or add
+for example ``-x`` if you only want to send one particular failure.
+
+**Creating a URL for a whole test session log**::
+
+    pytest --pastebin=all
+
+Currently only pasting to the http://bpaste.net service is implemented.
+
+Disabling plugins
+-----------------
+
+To disable loading specific plugins at invocation time, use the ``-p`` option
+together with the prefix ``no:``.
+
+Example: to disable loading the plugin ``doctest``, which is responsible for
+executing doctest tests from text files, invoke pytest like this::
+
+    pytest -p no:doctest
+
+.. _`pytest.main-usage`:
+
+Calling pytest from Python code
+----------------------------------------------------
+
+.. versionadded:: 2.0
+
+You can invoke ``pytest`` from Python code directly::
+
+    pytest.main()
+
+this acts as if you would call "pytest" from the command line.
+It will not raise ``SystemExit`` but return the exitcode instead.
+You can pass in options and arguments::
+
+    pytest.main(['-x', 'mytestdir'])
+
+You can specify additional plugins to ``pytest.main``::
+
+    # content of myinvoke.py
+    import pytest
+    class MyPlugin(object):
+        def pytest_sessionfinish(self):
+            print("*** test run reporting finishing")
+
+    pytest.main(["-qq"], plugins=[MyPlugin()])
+
+Running it will show that ``MyPlugin`` was added and its
+hook was invoked::
+
+    $ python myinvoke.py
+    *** test run reporting finishing
+    
+
+.. include:: links.inc
diff --git a/tools/third_party/pytest/doc/en/warnings.rst b/tools/third_party/pytest/doc/en/warnings.rst
new file mode 100644
index 0000000..f249d7e
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/warnings.rst
@@ -0,0 +1,299 @@
+.. _`warnings`:
+
+Warnings Capture
+================
+
+.. versionadded:: 3.1
+
+Starting from version ``3.1``, pytest now automatically catches warnings during test execution
+and displays them at the end of the session::
+
+    # content of test_show_warnings.py
+    import warnings
+
+    def api_v1():
+        warnings.warn(UserWarning("api v1, should use functions from v2"))
+        return 1
+
+    def test_one():
+        assert api_v1() == 1
+
+Running pytest now produces this output::
+
+    $ pytest test_show_warnings.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
+    rootdir: $REGENDOC_TMPDIR, inifile:
+    collected 1 item
+    
+    test_show_warnings.py .                                              [100%]
+    
+    ============================= warnings summary =============================
+    test_show_warnings.py::test_one
+      $REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
+        warnings.warn(UserWarning("api v1, should use functions from v2"))
+    
+    -- Docs: http://doc.pytest.org/en/latest/warnings.html
+    =================== 1 passed, 1 warnings in 0.12 seconds ===================
+
+Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``.
+
+The ``-W`` flag can be passed to control which warnings will be displayed or even turn
+them into errors::
+
+    $ pytest -q test_show_warnings.py -W error::UserWarning
+    F                                                                    [100%]
+    ================================= FAILURES =================================
+    _________________________________ test_one _________________________________
+    
+        def test_one():
+    >       assert api_v1() == 1
+    
+    test_show_warnings.py:8: 
+    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
+    
+        def api_v1():
+    >       warnings.warn(UserWarning("api v1, should use functions from v2"))
+    E       UserWarning: api v1, should use functions from v2
+    
+    test_show_warnings.py:4: UserWarning
+    1 failed in 0.12 seconds
+
+The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
+For example, the configuration below will ignore all user warnings, but will transform
+all other warnings into errors.
+
+.. code-block:: ini
+
+    [pytest]
+    filterwarnings =
+        error
+        ignore::UserWarning
+
+
+When a warning matches more than one option in the list, the action for the last matching option
+is performed.
+
+Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
+`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
+documentation for other examples and advanced usage.
+
+``@pytest.mark.filterwarnings``
+-------------------------------
+
+.. versionadded:: 3.2
+
+You can use the ``@pytest.mark.filterwarnings`` to add warning filters to specific test items,
+allowing you to have finer control of which warnings should be captured at test, class or
+even module level:
+
+.. code-block:: python
+
+    import warnings
+
+    def api_v1():
+        warnings.warn(UserWarning("api v1, should use functions from v2"))
+        return 1
+
+    @pytest.mark.filterwarnings('ignore:api v1')
+    def test_one():
+        assert api_v1() == 1
+
+
+Filters applied using a mark take precedence over filters passed on the command line or configured
+by the ``filterwarnings`` ini option.
+
+You may apply a filter to all tests of a class by using the ``filterwarnings`` mark as a class
+decorator or to all tests in a module by setting the ``pytestmark`` variable:
+
+.. code-block:: python
+
+    # turns all warnings into errors for this module
+    pytestmark = pytest.mark.filterwarnings('error')
+
+
+.. note::
+
+    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are hidden by the standard library
+    by default so you have to explicitly configure them to be displayed in your ``pytest.ini``:
+
+    .. code-block:: ini
+
+        [pytest]
+        filterwarnings =
+            once::DeprecationWarning
+            once::PendingDeprecationWarning
+
+
+*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
+*plugin.*
+
+.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W
+.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
+.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
+
+
+Disabling warning capture
+-------------------------
+
+This feature is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
+
+    .. code-block:: ini
+
+        [pytest]
+        addopts = -p no:warnings
+
+Or passing ``-p no:warnings`` in the command-line.
+
+.. _`asserting warnings`:
+
+.. _assertwarnings:
+
+.. _`asserting warnings with the warns function`:
+
+.. _warns:
+
+Asserting warnings with the warns function
+-----------------------------------------------
+
+.. versionadded:: 2.8
+
+You can check that code raises a particular warning using ``pytest.warns``,
+which works in a similar manner to :ref:`raises <assertraises>`::
+
+    import warnings
+    import pytest
+
+    def test_warning():
+        with pytest.warns(UserWarning):
+            warnings.warn("my warning", UserWarning)
+
+The test will fail if the warning in question is not raised. The keyword
+argument ``match`` to assert that the exception matches a text or regex::
+
+    >>> with warns(UserWarning, match='must be 0 or None'):
+    ...     warnings.warn("value must be 0 or None", UserWarning)
+
+    >>> with warns(UserWarning, match=r'must be \d+$'):
+    ...     warnings.warn("value must be 42", UserWarning)
+
+    >>> with warns(UserWarning, match=r'must be \d+$'):
+    ...     warnings.warn("this is not here", UserWarning)
+    Traceback (most recent call last):
+      ...
+    Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
+
+You can also call ``pytest.warns`` on a function or code string::
+
+    pytest.warns(expected_warning, func, *args, **kwargs)
+    pytest.warns(expected_warning, "func(*args, **kwargs)")
+
+The function also returns a list of all raised warnings (as
+``warnings.WarningMessage`` objects), which you can query for
+additional information::
+
+    with pytest.warns(RuntimeWarning) as record:
+        warnings.warn("another warning", RuntimeWarning)
+
+    # check that only one warning was raised
+    assert len(record) == 1
+    # check that the message matches
+    assert record[0].message.args[0] == "another warning"
+
+Alternatively, you can examine raised warnings in detail using the
+:ref:`recwarn <recwarn>` fixture (see below).
+
+.. note::
+    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
+    differently; see :ref:`ensuring_function_triggers`.
+
+.. _`recording warnings`:
+
+.. _recwarn:
+
+Recording warnings
+------------------------
+
+You can record raised warnings either using ``pytest.warns`` or with
+the ``recwarn`` fixture.
+
+To record with ``pytest.warns`` without asserting anything about the warnings,
+pass ``None`` as the expected warning type::
+
+    with pytest.warns(None) as record:
+        warnings.warn("user", UserWarning)
+        warnings.warn("runtime", RuntimeWarning)
+
+    assert len(record) == 2
+    assert str(record[0].message) == "user"
+    assert str(record[1].message) == "runtime"
+
+The ``recwarn`` fixture will record warnings for the whole function::
+
+    import warnings
+
+    def test_hello(recwarn):
+        warnings.warn("hello", UserWarning)
+        assert len(recwarn) == 1
+        w = recwarn.pop(UserWarning)
+        assert issubclass(w.category, UserWarning)
+        assert str(w.message) == "hello"
+        assert w.filename
+        assert w.lineno
+
+Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
+warnings: a WarningsRecorder instance. To view the recorded warnings, you can
+iterate over this instance, call ``len`` on it to get the number of recorded
+warnings, or index into it to get a particular recorded warning. It also
+provides these methods:
+
+.. autoclass:: _pytest.recwarn.WarningsRecorder()
+    :members:
+
+Each recorded warning has the attributes ``message``, ``category``,
+``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
+class of the warning. The ``message`` is the warning itself; calling
+``str(message)`` will return the actual message of the warning.
+
+.. note::
+    :class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1
+
+.. note::
+    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
+    differently; see :ref:`ensuring_function_triggers`.
+
+.. _`ensuring a function triggers a deprecation warning`:
+
+.. _ensuring_function_triggers:
+
+Ensuring a function triggers a deprecation warning
+-------------------------------------------------------
+
+You can also call a global helper for checking
+that a certain function call triggers a ``DeprecationWarning`` or
+``PendingDeprecationWarning``::
+
+    import pytest
+
+    def test_global():
+        pytest.deprecated_call(myfunction, 17)
+
+By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
+caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
+them. If you wish to record them in your own code, use the
+command ``warnings.simplefilter('always')``::
+
+    import warnings
+    import pytest
+
+    def test_deprecation(recwarn):
+        warnings.simplefilter('always')
+        warnings.warn("deprecated", DeprecationWarning)
+        assert len(recwarn) == 1
+        assert recwarn.pop(DeprecationWarning)
+
+You can also use it as a contextmanager::
+
+    def test_global():
+        with pytest.deprecated_call():
+            myobject.deprecated_method()
diff --git a/tools/third_party/pytest/doc/en/writing_plugins.rst b/tools/third_party/pytest/doc/en/writing_plugins.rst
new file mode 100644
index 0000000..eb52558
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/writing_plugins.rst
@@ -0,0 +1,739 @@
+.. _plugins:
+.. _`writing-plugins`:
+
+Writing plugins
+===============
+
+It is easy to implement `local conftest plugins`_ for your own project
+or `pip-installable plugins`_ that can be used throughout many projects,
+including third party projects.  Please refer to :ref:`using plugins` if you
+only want to use but not write plugins.
+
+A plugin contains one or multiple hook functions. :ref:`Writing hooks <writinghooks>`
+explains the basics and details of how you can write a hook function yourself.
+``pytest`` implements all aspects of configuration, collection, running and
+reporting by calling `well specified hooks`_ of the following plugins:
+
+* :ref:`builtin plugins`: loaded from pytest's internal ``_pytest`` directory.
+
+* :ref:`external plugins <extplugins>`: modules discovered through
+  `setuptools entry points`_
+
+* `conftest.py plugins`_: modules auto-discovered in test directories
+
+In principle, each hook call is a ``1:N`` Python function call where ``N`` is the
+number of registered implementation functions for a given specification.
+All specifications and implementations follow the ``pytest_`` prefix
+naming convention, making them easy to distinguish and find.
+
+.. _`pluginorder`:
+
+Plugin discovery order at tool startup
+--------------------------------------
+
+``pytest`` loads plugin modules at tool startup in the following way:
+
+* by loading all builtin plugins
+
+* by loading all plugins registered through `setuptools entry points`_.
+
+* by pre-scanning the command line for the ``-p name`` option
+  and loading the specified plugin before actual command line parsing.
+
+* by loading all :file:`conftest.py` files as inferred by the command line
+  invocation:
+
+  - if no test paths are specified use current dir as a test path
+  - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
+    to the directory part of the first test path.
+
+  Note that pytest does not find ``conftest.py`` files in deeper nested
+  sub directories at tool startup.  It is usually a good idea to keep
+  your ``conftest.py`` file in the top level test or project root directory.
+
+* by recursively loading all plugins specified by the
+  ``pytest_plugins`` variable in ``conftest.py`` files
+
+
+.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
+.. _`conftest.py plugins`:
+.. _`localplugin`:
+.. _`local conftest plugins`:
+
+conftest.py: local per-directory plugins
+----------------------------------------
+
+Local ``conftest.py`` plugins contain directory-specific hook
+implementations.  Hook Session and test running activities will
+invoke all hooks defined in ``conftest.py`` files closer to the
+root of the filesystem.  Example of implementing the
+``pytest_runtest_setup`` hook so that is called for tests in the ``a``
+sub directory but not for other directories::
+
+    a/conftest.py:
+        def pytest_runtest_setup(item):
+            # called for running each test in 'a' directory
+            print ("setting up", item)
+
+    a/test_sub.py:
+        def test_sub():
+            pass
+
+    test_flat.py:
+        def test_flat():
+            pass
+
+Here is how you might run it::
+
+     pytest test_flat.py   # will not show "setting up"
+     pytest a/test_sub.py  # will show "setting up"
+
+.. note::
+    If you have ``conftest.py`` files which do not reside in a
+    python package directory (i.e. one containing an ``__init__.py``) then
+    "import conftest" can be ambiguous because there might be other
+    ``conftest.py`` files as well on your ``PYTHONPATH`` or ``sys.path``.
+    It is thus good practice for projects to either put ``conftest.py``
+    under a package scope or to never import anything from a
+    ``conftest.py`` file.
+
+    See also: :ref:`pythonpath`.
+
+
+Writing your own plugin
+-----------------------
+
+.. _`setuptools`: http://pypi.python.org/pypi/setuptools
+
+If you want to write a plugin, there are many real-life examples
+you can copy from:
+
+* a custom collection example plugin: :ref:`yaml plugin`
+* around 20 :ref:`builtin plugins` which provide pytest's own functionality
+* many `external plugins <http://plugincompat.herokuapp.com>`_ providing additional features
+
+All of these plugins implement the documented `well specified hooks`_
+to extend and add functionality.
+
+.. note::
+    Make sure to check out the excellent
+    `cookiecutter-pytest-plugin <https://github.com/pytest-dev/cookiecutter-pytest-plugin>`_
+    project, which is a `cookiecutter template <https://github.com/audreyr/cookiecutter>`_
+    for authoring plugins.
+
+    The template provides an excellent starting point with a working plugin,
+    tests running with tox, a comprehensive README file as well as a
+    pre-configured entry-point.
+
+Also consider :ref:`contributing your plugin to pytest-dev<submitplugin>`
+once it has some happy users other than yourself.
+
+
+.. _`setuptools entry points`:
+.. _`pip-installable plugins`:
+
+Making your plugin installable by others
+----------------------------------------
+
+If you want to make your plugin externally available, you
+may define a so-called entry point for your distribution so
+that ``pytest`` finds your plugin module.  Entry points are
+a feature that is provided by `setuptools`_. pytest looks up
+the ``pytest11`` entrypoint to discover its
+plugins and you can thus make your plugin available by defining
+it in your setuptools-invocation:
+
+.. sourcecode:: python
+
+    # sample ./setup.py file
+    from setuptools import setup
+
+    setup(
+        name="myproject",
+        packages = ['myproject']
+
+        # the following makes a plugin available to pytest
+        entry_points = {
+            'pytest11': [
+                'name_of_plugin = myproject.pluginmodule',
+            ]
+        },
+
+        # custom PyPI classifier for pytest plugins
+        classifiers=[
+            "Framework :: Pytest",
+        ],
+    )
+
+If a package is installed this way, ``pytest`` will load
+``myproject.pluginmodule`` as a plugin which can define
+`well specified hooks`_.
+
+.. note::
+
+    Make sure to include ``Framework :: Pytest`` in your list of
+    `PyPI classifiers <https://python-packaging-user-guide.readthedocs.io/distributing/#classifiers>`_
+    to make it easy for users to find your plugin.
+
+
+Assertion Rewriting
+-------------------
+
+One of the main features of ``pytest`` is the use of plain assert
+statements and the detailed introspection of expressions upon
+assertion failures.  This is provided by "assertion rewriting" which
+modifies the parsed AST before it gets compiled to bytecode.  This is
+done via a :pep:`302` import hook which gets installed early on when
+``pytest`` starts up and will perform this rewriting when modules get
+imported.  However since we do not want to test different bytecode
+then you will run in production this hook only rewrites test modules
+themselves as well as any modules which are part of plugins.  Any
+other imported module will not be rewritten and normal assertion
+behaviour will happen.
+
+If you have assertion helpers in other modules where you would need
+assertion rewriting to be enabled you need to ask ``pytest``
+explicitly to rewrite this module before it gets imported.
+
+.. autofunction:: pytest.register_assert_rewrite
+
+This is especially important when you write a pytest plugin which is
+created using a package.  The import hook only treats ``conftest.py``
+files and any modules which are listed in the ``pytest11`` entrypoint
+as plugins.  As an example consider the following package::
+
+   pytest_foo/__init__.py
+   pytest_foo/plugin.py
+   pytest_foo/helper.py
+
+With the following typical ``setup.py`` extract:
+
+.. code-block:: python
+
+   setup(
+      ...
+      entry_points={'pytest11': ['foo = pytest_foo.plugin']},
+      ...
+   )
+
+In this case only ``pytest_foo/plugin.py`` will be rewritten.  If the
+helper module also contains assert statements which need to be
+rewritten it needs to be marked as such, before it gets imported.
+This is easiest by marking it for rewriting inside the
+``__init__.py`` module, which will always be imported first when a
+module inside a package is imported.  This way ``plugin.py`` can still
+import ``helper.py`` normally.  The contents of
+``pytest_foo/__init__.py`` will then need to look like this:
+
+.. code-block:: python
+
+   import pytest
+
+   pytest.register_assert_rewrite('pytest_foo.helper')
+
+
+
+Requiring/Loading plugins in a test module or conftest file
+-----------------------------------------------------------
+
+You can require plugins in a test module or a ``conftest.py`` file like this:
+
+.. code-block:: python
+
+    pytest_plugins = ["name1", "name2"]
+
+When the test module or conftest plugin is loaded the specified plugins
+will be loaded as well. Any module can be blessed as a plugin, including internal
+application modules:
+
+.. code-block:: python
+
+    pytest_plugins = "myapp.testsupport.myplugin"
+
+``pytest_plugins`` variables are processed recursively, so note that in the example above
+if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents
+of the variable will also be loaded as plugins, and so on.
+
+This mechanism makes it easy to share fixtures within applications or even
+external applications without the need to create external plugins using
+the ``setuptools``'s entry point technique.
+
+Plugins imported by ``pytest_plugins`` will also automatically be marked
+for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
+However for this to have any effect the module must not be
+imported already; if it was already imported at the time the
+``pytest_plugins`` statement is processed, a warning will result and
+assertions inside the plugin will not be rewritten.  To fix this you
+can either call :func:`pytest.register_assert_rewrite` yourself before
+the module is imported, or you can arrange the code to delay the
+importing until after the plugin is registered.
+
+
+Accessing another plugin by name
+--------------------------------
+
+If a plugin wants to collaborate with code from
+another plugin it can obtain a reference through
+the plugin manager like this:
+
+.. sourcecode:: python
+
+    plugin = config.pluginmanager.getplugin("name_of_plugin")
+
+If you want to look at the names of existing plugins, use
+the ``--trace-config`` option.
+
+Testing plugins
+---------------
+
+pytest comes with a plugin named ``pytester`` that helps you write tests for
+your plugin code. The plugin is disabled by default, so you will have to enable
+it before you can use it.
+
+You can do so by adding the following line to a ``conftest.py`` file in your
+testing directory:
+
+.. code-block:: python
+
+    # content of conftest.py
+
+    pytest_plugins = ["pytester"]
+
+Alternatively you can invoke pytest with the ``-p pytester`` command line
+option.
+
+This will allow you to use the :py:class:`testdir <_pytest.pytester.Testdir>`
+fixture for testing your plugin code.
+
+Let's demonstrate what you can do with the plugin with an example. Imagine we
+developed a plugin that provides a fixture ``hello`` which yields a function
+and we can invoke this function with one optional parameter. It will return a
+string value of ``Hello World!`` if we do not supply a value or ``Hello
+{value}!`` if we do supply a string value.
+
+.. code-block:: python
+
+    # -*- coding: utf-8 -*-
+
+    import pytest
+
+    def pytest_addoption(parser):
+        group = parser.getgroup('helloworld')
+        group.addoption(
+            '--name',
+            action='store',
+            dest='name',
+            default='World',
+            help='Default "name" for hello().'
+        )
+
+    @pytest.fixture
+    def hello(request):
+        name = request.config.getoption('name')
+
+        def _hello(name=None):
+            if not name:
+                name = request.config.getoption('name')
+            return "Hello {name}!".format(name=name)
+
+        return _hello
+
+
+Now the ``testdir`` fixture provides a convenient API for creating temporary
+``conftest.py`` files and test files. It also allows us to run the tests and
+return a result object, with which we can assert the tests' outcomes.
+
+.. code-block:: python
+
+    def test_hello(testdir):
+        """Make sure that our plugin works."""
+
+        # create a temporary conftest.py file
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture(params=[
+                "Brianna",
+                "Andreas",
+                "Floris",
+            ])
+            def name(request):
+                return request.param
+        """)
+
+        # create a temporary pytest test file
+        testdir.makepyfile("""
+            def test_hello_default(hello):
+                assert hello() == "Hello World!"
+
+            def test_hello_name(hello, name):
+                assert hello(name) == "Hello {0}!".format(name)
+        """)
+
+        # run all tests with pytest
+        result = testdir.runpytest()
+
+        # check that all 4 tests passed
+        result.assert_outcomes(passed=4)
+
+
+For more information about the result object that ``runpytest()`` returns, and
+the methods that it provides please check out the :py:class:`RunResult
+<_pytest.pytester.RunResult>` documentation.
+
+
+.. _`writinghooks`:
+
+Writing hook functions
+======================
+
+
+.. _validation:
+
+hook function validation and execution
+--------------------------------------
+
+pytest calls hook functions from registered plugins for any
+given hook specification.  Let's look at a typical hook function
+for the ``pytest_collection_modifyitems(session, config,
+items)`` hook which pytest calls after collection of all test items is
+completed.
+
+When we implement a ``pytest_collection_modifyitems`` function in our plugin
+pytest will during registration verify that you use argument
+names which match the specification and bail out if not.
+
+Let's look at a possible implementation:
+
+.. code-block:: python
+
+    def pytest_collection_modifyitems(config, items):
+        # called after collection is completed
+        # you can modify the ``items`` list
+
+Here, ``pytest`` will pass in ``config`` (the pytest config object)
+and ``items`` (the list of collected test items) but will not pass
+in the ``session`` argument because we didn't list it in the function
+signature.  This dynamic "pruning" of arguments allows ``pytest`` to
+be "future-compatible": we can introduce new hook named parameters without
+breaking the signatures of existing hook implementations.  It is one of
+the reasons for the general long-lived compatibility of pytest plugins.
+
+Note that hook functions other than ``pytest_runtest_*`` are not
+allowed to raise exceptions.  Doing so will break the pytest run.
+
+
+
+.. _firstresult:
+
+firstresult: stop at first non-None result
+-------------------------------------------
+
+Most calls to ``pytest`` hooks result in a **list of results** which contains
+all non-None results of the called hook functions.
+
+Some hook specifications use the ``firstresult=True`` option so that the hook
+call only executes until the first of N registered functions returns a
+non-None result which is then taken as result of the overall hook call.
+The remaining hook functions will not be called in this case.
+
+
+hookwrapper: executing around other hooks
+-------------------------------------------------
+
+.. currentmodule:: _pytest.core
+
+.. versionadded:: 2.7
+
+pytest plugins can implement hook wrappers which wrap the execution
+of other hook implementations.  A hook wrapper is a generator function
+which yields exactly once. When pytest invokes hooks it first executes
+hook wrappers and passes the same arguments as to the regular hooks.
+
+At the yield point of the hook wrapper pytest will execute the next hook
+implementations and return their result to the yield point in the form of
+a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
+exception info.  The yield point itself will thus typically not raise
+exceptions (unless there are bugs).
+
+Here is an example definition of a hook wrapper::
+
+    import pytest
+
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_pyfunc_call(pyfuncitem):
+        # do whatever you want before the next hook executes
+
+        outcome = yield
+        # outcome.excinfo may be None or a (cls, val, tb) tuple
+
+        res = outcome.get_result()  # will raise if outcome was exception
+        # postprocess result
+
+Note that hook wrappers don't return results themselves, they merely
+perform tracing or other side effects around the actual hook implementations.
+If the result of the underlying hook is a mutable object, they may modify
+that result but it's probably better to avoid it.
+
+
+Hook function ordering / call example
+-------------------------------------
+
+For any given hook specification there may be more than one
+implementation and we thus generally view ``hook`` execution as a
+``1:N`` function call where ``N`` is the number of registered functions.
+There are ways to influence if a hook implementation comes before or
+after others, i.e.  the position in the ``N``-sized list of functions:
+
+.. code-block:: python
+
+    # Plugin 1
+    @pytest.hookimpl(tryfirst=True)
+    def pytest_collection_modifyitems(items):
+        # will execute as early as possible
+
+    # Plugin 2
+    @pytest.hookimpl(trylast=True)
+    def pytest_collection_modifyitems(items):
+        # will execute as late as possible
+
+    # Plugin 3
+    @pytest.hookimpl(hookwrapper=True)
+    def pytest_collection_modifyitems(items):
+        # will execute even before the tryfirst one above!
+        outcome = yield
+        # will execute after all non-hookwrappers executed
+
+Here is the order of execution:
+
+1. Plugin3's pytest_collection_modifyitems called until the yield point
+   because it is a hook wrapper.
+
+2. Plugin1's pytest_collection_modifyitems is called because it is marked
+   with ``tryfirst=True``.
+
+3. Plugin2's pytest_collection_modifyitems is called because it is marked
+   with ``trylast=True`` (but even without this mark it would come after
+   Plugin1).
+
+4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
+   point.  The yield receives a :py:class:`Result <pluggy._Result>` instance which encapsulates
+   the result from calling the non-wrappers.  Wrappers shall not modify the result.
+
+It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
+``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
+among each other.
+
+
+Declaring new hooks
+------------------------
+
+.. currentmodule:: _pytest.hookspec
+
+Plugins and ``conftest.py`` files may declare new hooks that can then be
+implemented by other plugins in order to alter behaviour or interact with
+the new plugin:
+
+.. autofunction:: pytest_addhooks
+
+Hooks are usually declared as do-nothing functions that contain only
+documentation describing when the hook will be called and what return values
+are expected.
+
+For an example, see `newhooks.py`_ from `xdist <https://github.com/pytest-dev/pytest-xdist>`_.
+
+.. _`newhooks.py`: https://github.com/pytest-dev/pytest-xdist/blob/974bd566c599dc6a9ea291838c6f226197208b46/xdist/newhooks.py
+
+
+Optionally using hooks from 3rd party plugins
+---------------------------------------------
+
+Using new hooks from plugins as explained above might be a little tricky
+because of the standard :ref:`validation mechanism <validation>`:
+if you depend on a plugin that is not installed, validation will fail and
+the error message will not make much sense to your users.
+
+One approach is to defer the hook implementation to a new plugin instead of
+declaring the hook functions directly in your plugin module, for example::
+
+    # contents of myplugin.py
+
+    class DeferPlugin(object):
+        """Simple plugin to defer pytest-xdist hook functions."""
+
+        def pytest_testnodedown(self, node, error):
+            """standard xdist hook function.
+            """
+
+    def pytest_configure(config):
+        if config.pluginmanager.hasplugin('xdist'):
+            config.pluginmanager.register(DeferPlugin())
+
+This has the added benefit of allowing you to conditionally install hooks
+depending on which plugins are installed.
+
+.. _`well specified hooks`:
+
+.. currentmodule:: _pytest.hookspec
+
+pytest hook reference
+=====================
+
+
+Initialization, command line and configuration hooks
+----------------------------------------------------
+
+.. autofunction:: pytest_load_initial_conftests
+.. autofunction:: pytest_cmdline_preparse
+.. autofunction:: pytest_cmdline_parse
+.. autofunction:: pytest_addoption
+.. autofunction:: pytest_cmdline_main
+.. autofunction:: pytest_configure
+.. autofunction:: pytest_unconfigure
+
+Generic "runtest" hooks
+-----------------------
+
+All runtest related hooks receive a :py:class:`pytest.Item <_pytest.main.Item>` object.
+
+.. autofunction:: pytest_runtest_protocol
+.. autofunction:: pytest_runtest_setup
+.. autofunction:: pytest_runtest_call
+.. autofunction:: pytest_runtest_teardown
+.. autofunction:: pytest_runtest_makereport
+
+For deeper understanding you may look at the default implementation of
+these hooks in :py:mod:`_pytest.runner` and maybe also
+in :py:mod:`_pytest.pdb` which interacts with :py:mod:`_pytest.capture`
+and its input/output capturing in order to immediately drop
+into interactive debugging when a test failure occurs.
+
+The :py:mod:`_pytest.terminal` reported specifically uses
+the reporting hook to print information about a test run.
+
+Collection hooks
+----------------
+
+``pytest`` calls the following hooks for collecting files and directories:
+
+.. autofunction:: pytest_ignore_collect
+.. autofunction:: pytest_collect_directory
+.. autofunction:: pytest_collect_file
+
+For influencing the collection of objects in Python modules
+you can use the following hook:
+
+.. autofunction:: pytest_pycollect_makeitem
+.. autofunction:: pytest_generate_tests
+.. autofunction:: pytest_make_parametrize_id
+
+After collection is complete, you can modify the order of
+items, delete or otherwise amend the test items:
+
+.. autofunction:: pytest_collection_modifyitems
+
+Reporting hooks
+---------------
+
+Session related reporting hooks:
+
+.. autofunction:: pytest_collectstart
+.. autofunction:: pytest_itemcollected
+.. autofunction:: pytest_collectreport
+.. autofunction:: pytest_deselected
+.. autofunction:: pytest_report_header
+.. autofunction:: pytest_report_collectionfinish
+.. autofunction:: pytest_report_teststatus
+.. autofunction:: pytest_terminal_summary
+.. autofunction:: pytest_fixture_setup
+.. autofunction:: pytest_fixture_post_finalizer
+
+And here is the central hook for reporting about
+test execution:
+
+.. autofunction:: pytest_runtest_logreport
+
+You can also use this hook to customize assertion representation for some
+types:
+
+.. autofunction:: pytest_assertrepr_compare
+
+
+Debugging/Interaction hooks
+---------------------------
+
+There are few hooks which can be used for special
+reporting or interaction with exceptions:
+
+.. autofunction:: pytest_internalerror
+.. autofunction:: pytest_keyboard_interrupt
+.. autofunction:: pytest_exception_interact
+.. autofunction:: pytest_enter_pdb
+
+
+Reference of objects involved in hooks
+======================================
+
+.. autoclass:: _pytest.config.Config()
+    :members:
+
+.. autoclass:: _pytest.config.Parser()
+    :members:
+
+.. autoclass:: _pytest.main.Node()
+    :members:
+
+.. autoclass:: _pytest.main.Collector()
+    :members:
+    :show-inheritance:
+
+.. autoclass:: _pytest.main.Item()
+    :members:
+    :show-inheritance:
+
+.. autoclass:: _pytest.python.Module()
+    :members:
+    :show-inheritance:
+
+.. autoclass:: _pytest.python.Class()
+    :members:
+    :show-inheritance:
+
+.. autoclass:: _pytest.python.Function()
+    :members:
+    :show-inheritance:
+
+.. autoclass:: _pytest.fixtures.FixtureDef()
+    :members:
+    :show-inheritance:
+
+.. autoclass:: _pytest.runner.CallInfo()
+    :members:
+
+.. autoclass:: _pytest.runner.TestReport()
+    :members:
+    :inherited-members:
+
+.. autoclass:: pluggy._Result
+    :members:
+
+.. autofunction:: _pytest.config.get_plugin_manager()
+
+.. autoclass:: _pytest.config.PytestPluginManager()
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+.. autoclass:: pluggy.PluginManager()
+    :members:
+
+.. currentmodule:: _pytest.pytester
+
+.. autoclass:: Testdir()
+    :members: runpytest,runpytest_subprocess,runpytest_inprocess,makeconftest,makepyfile
+
+.. autoclass:: RunResult()
+    :members:
+
+.. autoclass:: LineMatcher()
+    :members:
diff --git a/tools/third_party/pytest/doc/en/xunit_setup.rst b/tools/third_party/pytest/doc/en/xunit_setup.rst
new file mode 100644
index 0000000..148fb12
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/xunit_setup.rst
@@ -0,0 +1,99 @@
+
+.. _`classic xunit`:
+.. _xunitsetup:
+
+classic xunit-style setup
+========================================
+
+This section describes a classic and popular way how you can implement
+fixtures (setup and teardown test state) on a per-module/class/function basis.  
+
+
+.. note::
+
+    While these setup/teardown methods are simple and familiar to those
+    coming from a ``unittest`` or nose ``background``, you may also consider
+    using pytest's more powerful :ref:`fixture mechanism
+    <fixture>` which leverages the concept of dependency injection, allowing
+    for a more modular and more scalable approach for managing test state,
+    especially for larger projects and for functional testing.  You can
+    mix both fixture mechanisms in the same file but
+    test methods of ``unittest.TestCase`` subclasses
+    cannot receive fixture arguments.
+
+
+Module level setup/teardown
+--------------------------------------
+
+If you have multiple test functions and test classes in a single
+module you can optionally implement the following fixture methods
+which will usually be called once for all the functions::
+
+    def setup_module(module):
+        """ setup any state specific to the execution of the given module."""
+
+    def teardown_module(module):
+        """ teardown any state that was previously setup with a setup_module
+        method.
+        """
+
+As of pytest-3.0, the ``module`` parameter is optional.
+
+Class level setup/teardown
+----------------------------------
+
+Similarly, the following methods are called at class level before
+and after all test methods of the class are called::
+
+    @classmethod
+    def setup_class(cls):
+        """ setup any state specific to the execution of the given class (which
+        usually contains tests).
+        """
+
+    @classmethod
+    def teardown_class(cls):
+        """ teardown any state that was previously setup with a call to
+        setup_class.
+        """
+
+Method and function level setup/teardown
+-----------------------------------------------
+
+Similarly, the following methods are called around each method invocation::
+
+    def setup_method(self, method):
+        """ setup any state tied to the execution of the given method in a
+        class.  setup_method is invoked for every test method of a class.
+        """
+
+    def teardown_method(self, method):
+        """ teardown any state that was previously setup with a setup_method
+        call.
+        """
+
+As of pytest-3.0, the ``method`` parameter is optional.
+
+If you would rather define test functions directly at module level
+you can also use the following functions to implement fixtures::
+
+    def setup_function(function):
+        """ setup any state tied to the execution of the given function.
+        Invoked for every test function in the module.
+        """
+
+    def teardown_function(function):
+        """ teardown any state that was previously setup with a setup_function
+        call.
+        """
+
+As of pytest-3.0, the ``function`` parameter is optional.
+
+Remarks:
+
+* It is possible for setup/teardown pairs to be invoked multiple times
+  per testing process.
+* teardown functions are not called if the corresponding setup function existed
+  and failed/was skipped.
+
+.. _`unittest.py module`: http://docs.python.org/library/unittest.html
diff --git a/tools/third_party/pytest/doc/en/yieldfixture.rst b/tools/third_party/pytest/doc/en/yieldfixture.rst
new file mode 100644
index 0000000..6fd1eda
--- /dev/null
+++ b/tools/third_party/pytest/doc/en/yieldfixture.rst
@@ -0,0 +1,18 @@
+:orphan:
+
+.. _yieldfixture:
+
+"yield_fixture" functions
+---------------------------------------------------------------
+
+.. deprecated:: 3.0
+
+.. versionadded:: 2.4
+
+.. important::
+    Since pytest-3.0, fixtures using the normal ``fixture`` decorator can use a ``yield``
+    statement to provide fixture values and execute teardown code, exactly like ``yield_fixture``
+    in previous versions.
+
+    Marking functions as ``yield_fixture`` is still supported, but deprecated and should not
+    be used in new code.
diff --git a/tools/third_party/pytest/extra/get_issues.py b/tools/third_party/pytest/extra/get_issues.py
new file mode 100644
index 0000000..2a8f8c3
--- /dev/null
+++ b/tools/third_party/pytest/extra/get_issues.py
@@ -0,0 +1,84 @@
+import json
+import py
+import textwrap
+
+issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues"
+
+import requests
+
+
+def get_issues():
+    issues = []
+    url = issues_url
+    while 1:
+        get_data = {"state": "all"}
+        r = requests.get(url, params=get_data)
+        data = r.json()
+        if r.status_code == 403:
+            # API request limit exceeded
+            print(data['message'])
+            exit(1)
+        issues.extend(data)
+
+        # Look for next page
+        links = requests.utils.parse_header_links(r.headers['Link'])
+        another_page = False
+        for link in links:
+            if link['rel'] == 'next':
+                url = link['url']
+                another_page = True
+        if not another_page:
+            return issues
+
+
+def main(args):
+    cachefile = py.path.local(args.cache)
+    if not cachefile.exists() or args.refresh:
+        issues = get_issues()
+        cachefile.write(json.dumps(issues))
+    else:
+        issues = json.loads(cachefile.read())
+
+    open_issues = [x for x in issues if x["state"] == "open"]
+
+    open_issues.sort(key=lambda x: x["number"])
+    report(open_issues)
+
+
+def _get_kind(issue):
+    labels = [l['name'] for l in issue['labels']]
+    for key in ('bug', 'enhancement', 'proposal'):
+        if key in labels:
+            return key
+    return 'issue'
+
+
+def report(issues):
+    for issue in issues:
+        title = issue["title"]
+        body = issue["body"]
+        kind = _get_kind(issue)
+        status = issue["state"]
+        number = issue["number"]
+        link = "https://github.com/pytest-dev/pytest/issues/%s/" % number
+        print("----")
+        print(status, kind, link)
+        print(title)
+        #print()
+        #lines = body.split("\n")
+        #print ("\n".join(lines[:3]))
+        #if len(lines) > 3 or len(body) > 240:
+        #    print ("...")
+    print("\n\nFound %s open issues" % len(issues))
+
+
+if __name__ == "__main__":
+    import argparse
+    parser = argparse.ArgumentParser("process bitbucket issues")
+    parser.add_argument("--refresh", action="store_true",
+                        help="invalidate cache, refresh issues")
+    parser.add_argument("--cache", action="store", default="issues.json",
+                        help="cache file")
+    args = parser.parse_args()
+    main(args)
+
diff --git a/tools/pytest/extra/setup-py.test/setup.py b/tools/third_party/pytest/extra/setup-py.test/setup.py
similarity index 100%
rename from tools/pytest/extra/setup-py.test/setup.py
rename to tools/third_party/pytest/extra/setup-py.test/setup.py
diff --git a/tools/third_party/pytest/pyproject.toml b/tools/third_party/pytest/pyproject.toml
new file mode 100644
index 0000000..88571e2
--- /dev/null
+++ b/tools/third_party/pytest/pyproject.toml
@@ -0,0 +1,35 @@
+[tool.towncrier]
+package = "pytest"
+filename = "CHANGELOG.rst"
+directory = "changelog/"
+template = "changelog/_template.rst"
+
+  [[tool.towncrier.type]]
+  directory = "removal"
+  name = "Deprecations and Removals"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "feature"
+  name = "Features"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "bugfix"
+  name = "Bug Fixes"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "vendor"
+  name = "Vendored Libraries"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "doc"
+  name = "Improved Documentation"
+  showcontent = true
+
+  [[tool.towncrier.type]]
+  directory = "trivial"
+  name = "Trivial/Internal Changes"
+  showcontent = true
diff --git a/tools/third_party/pytest/pytest.py b/tools/third_party/pytest/pytest.py
new file mode 100644
index 0000000..2b681b6
--- /dev/null
+++ b/tools/third_party/pytest/pytest.py
@@ -0,0 +1,77 @@
+# PYTHON_ARGCOMPLETE_OK
+"""
+pytest: unit and functional testing with Python.
+"""
+
+
+# else we are imported
+
+from _pytest.config import (
+    main, UsageError, cmdline,
+    hookspec, hookimpl
+)
+from _pytest.fixtures import fixture, yield_fixture
+from _pytest.assertion import register_assert_rewrite
+from _pytest.freeze_support import freeze_includes
+from _pytest import __version__
+from _pytest.debugging import pytestPDB as __pytestPDB
+from _pytest.recwarn import warns, deprecated_call
+from _pytest.outcomes import fail, skip, importorskip, exit, xfail
+from _pytest.mark import MARK_GEN as mark, param
+from _pytest.main import Item, Collector, File, Session
+from _pytest.fixtures import fillfixtures as _fillfuncargs
+from _pytest.python import (
+    Module, Class, Instance, Function, Generator,
+)
+
+from _pytest.python_api import approx, raises
+
+set_trace = __pytestPDB.set_trace
+
+__all__ = [
+    'main',
+    'UsageError',
+    'cmdline',
+    'hookspec',
+    'hookimpl',
+    '__version__',
+    'register_assert_rewrite',
+    'freeze_includes',
+    'set_trace',
+    'warns',
+    'deprecated_call',
+    'fixture',
+    'yield_fixture',
+    'fail',
+    'skip',
+    'xfail',
+    'importorskip',
+    'exit',
+    'mark',
+    'param',
+    'approx',
+    '_fillfuncargs',
+
+    'Item',
+    'File',
+    'Collector',
+    'Session',
+    'Module',
+    'Class',
+    'Instance',
+    'Function',
+    'Generator',
+    'raises',
+
+
+]
+
+if __name__ == '__main__':
+    # if run as a script or by 'python -m pytest'
+    # we trigger the below "else" condition by the following import
+    import pytest
+    raise SystemExit(pytest.main())
+else:
+
+    from _pytest.compat import _setup_collect_fakemodule
+    _setup_collect_fakemodule()
diff --git a/tools/third_party/pytest/scripts/call-tox.bat b/tools/third_party/pytest/scripts/call-tox.bat
new file mode 100644
index 0000000..86fb25c
--- /dev/null
+++ b/tools/third_party/pytest/scripts/call-tox.bat
@@ -0,0 +1,8 @@
+REM skip "coveralls" run in PRs or forks
+if "%TOXENV%" == "coveralls" (
+    if not defined COVERALLS_REPO_TOKEN (
+        echo skipping coveralls run because COVERALLS_REPO_TOKEN is not defined
+        exit /b 0
+    )
+)
+C:\Python36\python -m tox
diff --git a/tools/third_party/pytest/scripts/check-rst.py b/tools/third_party/pytest/scripts/check-rst.py
new file mode 100644
index 0000000..57f7175
--- /dev/null
+++ b/tools/third_party/pytest/scripts/check-rst.py
@@ -0,0 +1,11 @@
+
+from __future__ import print_function
+
+import subprocess
+import glob
+import sys
+
+sys.exit(subprocess.call([
+    'rst-lint', '--encoding', 'utf-8',
+    'CHANGELOG.rst', 'HOWTORELEASE.rst', 'README.rst',
+] + glob.glob('changelog/[0-9]*.*')))
diff --git a/tools/third_party/pytest/scripts/install-pypy.bat b/tools/third_party/pytest/scripts/install-pypy.bat
new file mode 100644
index 0000000..8012ea4
--- /dev/null
+++ b/tools/third_party/pytest/scripts/install-pypy.bat
@@ -0,0 +1,6 @@
+REM install pypy using choco
+REM redirect to a file because choco install python.pypy is too noisy. If the command fails, write output to console
+choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1)
+set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy
+echo PyPy installed
+pypy --version
diff --git a/tools/third_party/pytest/setup.cfg b/tools/third_party/pytest/setup.cfg
new file mode 100644
index 0000000..816539e
--- /dev/null
+++ b/tools/third_party/pytest/setup.cfg
@@ -0,0 +1,20 @@
+[build_sphinx]
+source-dir = doc/en/
+build-dir = doc/build
+all_files = 1
+
+[upload_sphinx]
+upload-dir = doc/en/build/html
+
+[bdist_wheel]
+universal = 1
+
+[check-manifest]
+ignore =
+  _pytest/_version.py
+
+[metadata]
+license_file = LICENSE
+
+[devpi:upload]
+formats = sdist.tgz,bdist_wheel
diff --git a/tools/third_party/pytest/setup.py b/tools/third_party/pytest/setup.py
new file mode 100644
index 0000000..3eb38ef
--- /dev/null
+++ b/tools/third_party/pytest/setup.py
@@ -0,0 +1,114 @@
+import os
+import sys
+import setuptools
+import pkg_resources
+from setuptools import setup, Command
+
+classifiers = [
+    'Development Status :: 6 - Mature',
+    'Intended Audience :: Developers',
+    'License :: OSI Approved :: MIT License',
+    'Operating System :: POSIX',
+    'Operating System :: Microsoft :: Windows',
+    'Operating System :: MacOS :: MacOS X',
+    'Topic :: Software Development :: Testing',
+    'Topic :: Software Development :: Libraries',
+    'Topic :: Utilities',
+] + [
+    ('Programming Language :: Python :: %s' % x)
+    for x in '2 2.7 3 3.4 3.5 3.6'.split()
+]
+
+with open('README.rst') as fd:
+    long_description = fd.read()
+
+
+def has_environment_marker_support():
+    """
+    Tests that setuptools has support for PEP-426 environment marker support.
+
+    The first known release to support it is 0.7 (and the earliest on PyPI seems to be 0.7.2
+    so we're using that), see: http://pythonhosted.org/setuptools/history.html#id142
+
+    References:
+
+    * https://wheel.readthedocs.io/en/latest/index.html#defining-conditional-dependencies
+    * https://www.python.org/dev/peps/pep-0426/#environment-markers
+    """
+    try:
+        return pkg_resources.parse_version(setuptools.__version__) >= pkg_resources.parse_version('0.7.2')
+    except Exception as exc:
+        sys.stderr.write("Could not test setuptool's version: %s\n" % exc)
+        return False
+
+
+def main():
+    extras_require = {}
+    install_requires = [
+        'py>=1.5.0',
+        'six>=1.10.0',
+        'setuptools',
+        'attrs>=17.2.0',
+    ]
+    # if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
+    # used by tox.ini to test with pluggy master
+    if '_PYTEST_SETUP_SKIP_PLUGGY_DEP' not in os.environ:
+        install_requires.append('pluggy>=0.5,<0.7')
+    if has_environment_marker_support():
+        extras_require[':python_version<"3.0"'] = ['funcsigs']
+        extras_require[':sys_platform=="win32"'] = ['colorama']
+    else:
+        if sys.platform == 'win32':
+            install_requires.append('colorama')
+        if sys.version_info < (3, 0):
+            install_requires.append('funcsigs')
+
+    setup(
+        name='pytest',
+        description='pytest: simple powerful testing with Python',
+        long_description=long_description,
+        use_scm_version={
+            'write_to': '_pytest/_version.py',
+        },
+        url='http://pytest.org',
+        license='MIT license',
+        platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
+        author=(
+            'Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, '
+            'Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others'),
+        entry_points={'console_scripts': [
+            'pytest=pytest:main', 'py.test=pytest:main']},
+        classifiers=classifiers,
+        keywords="test unittest",
+        cmdclass={'test': PyTest},
+        # the following should be enabled for release
+        setup_requires=['setuptools-scm'],
+        python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
+        install_requires=install_requires,
+        extras_require=extras_require,
+        packages=['_pytest', '_pytest.assertion', '_pytest._code'],
+        py_modules=['pytest'],
+        zip_safe=False,
+    )
+
+
+class PyTest(Command):
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        import subprocess
+        PPATH = [x for x in os.environ.get('PYTHONPATH', '').split(':') if x]
+        PPATH.insert(0, os.getcwd())
+        os.environ['PYTHONPATH'] = ':'.join(PPATH)
+        errno = subprocess.call([sys.executable, 'pytest.py', '--ignore=doc'])
+        raise SystemExit(errno)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/third_party/pytest/tasks/__init__.py b/tools/third_party/pytest/tasks/__init__.py
new file mode 100644
index 0000000..8ea038f
--- /dev/null
+++ b/tools/third_party/pytest/tasks/__init__.py
@@ -0,0 +1,12 @@
+"""
+Invoke tasks to help with pytest development and release process.
+"""
+
+import invoke
+
+from . import generate
+
+
+ns = invoke.Collection(
+    generate,
+)
diff --git a/tools/third_party/pytest/tasks/generate.py b/tools/third_party/pytest/tasks/generate.py
new file mode 100644
index 0000000..fa8ee65
--- /dev/null
+++ b/tools/third_party/pytest/tasks/generate.py
@@ -0,0 +1,162 @@
+import os
+from pathlib import Path
+from subprocess import check_output, check_call
+
+import invoke
+
+
+@invoke.task(help={
+    'version': 'version being released',
+})
+def announce(ctx, version):
+    """Generates a new release announcement entry in the docs."""
+    # Get our list of authors
+    stdout = check_output(["git", "describe", "--abbrev=0", '--tags'])
+    stdout = stdout.decode('utf-8')
+    last_version = stdout.strip()
+
+    stdout = check_output(["git", "log", "{}..HEAD".format(last_version), "--format=%aN"])
+    stdout = stdout.decode('utf-8')
+
+    contributors = set(stdout.splitlines())
+
+    template_name = 'release.minor.rst' if version.endswith('.0') else 'release.patch.rst'
+    template_text = Path(__file__).parent.joinpath(template_name).read_text(encoding='UTF-8')
+
+    contributors_text = '\n'.join('* {}'.format(name) for name in sorted(contributors)) + '\n'
+    text = template_text.format(version=version, contributors=contributors_text)
+
+    target = Path(__file__).parent.joinpath('../doc/en/announce/release-{}.rst'.format(version))
+    target.write_text(text, encoding='UTF-8')
+    print("[generate.announce] Generated {}".format(target.name))
+
+    # Update index with the new release entry
+    index_path = Path(__file__).parent.joinpath('../doc/en/announce/index.rst')
+    lines = index_path.read_text(encoding='UTF-8').splitlines()
+    indent = '   '
+    for index, line in enumerate(lines):
+        if line.startswith('{}release-'.format(indent)):
+            new_line = indent + target.stem
+            if line != new_line:
+                lines.insert(index, new_line)
+                index_path.write_text('\n'.join(lines) + '\n', encoding='UTF-8')
+                print("[generate.announce] Updated {}".format(index_path.name))
+            else:
+                print("[generate.announce] Skip {} (already contains release)".format(index_path.name))
+            break
+
+    check_call(['git', 'add', str(target)])
+
+
+@invoke.task()
+def regen(ctx):
+    """Call regendoc tool to update examples and pytest output in the docs."""
+    print("[generate.regen] Updating docs")
+    check_call(['tox', '-e', 'regen'])
+
+
+@invoke.task()
+def make_tag(ctx, version):
+    """Create a new (local) tag for the release, only if the repository is clean."""
+    from git import Repo
+
+    repo = Repo('.')
+    if repo.is_dirty():
+        print('Current repository is dirty. Please commit any changes and try again.')
+        raise invoke.Exit(code=2)
+
+    tag_names = [x.name for x in repo.tags]
+    if version in tag_names:
+        print("[generate.make_tag] Delete existing tag {}".format(version))
+        repo.delete_tag(version)
+
+    print("[generate.make_tag] Create tag {}".format(version))
+    repo.create_tag(version)
+
+
+@invoke.task()
+def devpi_upload(ctx, version, user, password=None):
+    """Creates and uploads a package to devpi for testing."""
+    if password:
+        print("[generate.devpi_upload] devpi login {}".format(user))
+        check_call(['devpi', 'login', user, '--password', password])
+
+    check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
+    
+    env = os.environ.copy()
+    env['SETUPTOOLS_SCM_PRETEND_VERSION'] = version
+    check_call(['devpi', 'upload', '--formats', 'sdist,bdist_wheel'], env=env)
+    print("[generate.devpi_upload] package uploaded")
+
+
+@invoke.task(help={
+    'version': 'version being released',
+    'user': 'name of the user on devpi to stage the generated package',
+    'password': 'user password on devpi to stage the generated package '
+                '(if not given assumed logged in)',
+})
+def pre_release(ctx, version, user, password=None):
+    """Generates new docs, release announcements and uploads a new release to devpi for testing."""
+    announce(ctx, version)
+    regen(ctx)
+    changelog(ctx, version, write_out=True)
+
+    msg = 'Preparing release version {}'.format(version)
+    check_call(['git', 'commit', '-a', '-m', msg])
+    
+    make_tag(ctx, version)
+
+    devpi_upload(ctx, version=version, user=user, password=password)
+    
+    print()
+    print('[generate.pre_release] Please push your branch and open a PR.')
+
+
+@invoke.task(help={
+    'version': 'version being released',
+    'user': 'name of the user on devpi to stage the generated package',
+    'pypi_name': 'name of the pypi configuration section in your ~/.pypirc',
+})
+def publish_release(ctx, version, user, pypi_name):
+    """Publishes a package previously created by the 'pre_release' command."""
+    from git import Repo
+    repo = Repo('.')
+    tag_names = [x.name for x in repo.tags]
+    if version not in tag_names:
+        print('Could not find tag for version {}, exiting...'.format(version))
+        raise invoke.Exit(code=2)
+
+    check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
+    check_call(['devpi', 'push', 'pytest=={}'.format(version), 'pypi:{}'.format(pypi_name)])
+    check_call(['git', 'push', 'git@github.com:pytest-dev/pytest.git', version])
+
+    emails = [
+        'pytest-dev@python.org',
+        'python-announce-list@python.org'
+    ]
+    if version.endswith('.0'):
+        emails.append('testing-in-python@lists.idyll.org')
+    print('Version {} has been published to PyPI!'.format(version))
+    print()
+    print('Please send an email announcement with the contents from:')
+    print()
+    print('  doc/en/announce/release-{}.rst'.format(version))
+    print()
+    print('To the following mail lists:')
+    print()
+    print(' ', ','.join(emails))
+    print()
+    print('And announce it on twitter adding the #pytest hash tag.')
+
+
+@invoke.task(help={
+    'version': 'version being released',
+    'write_out': 'write changes to the actial changelog'
+})
+def changelog(ctx, version, write_out=False):
+    if write_out:
+        addopts = []
+    else:
+        addopts = ['--draft']
+    check_call(['towncrier', '--version', version] + addopts)
+
diff --git a/tools/third_party/pytest/tasks/release.minor.rst b/tools/third_party/pytest/tasks/release.minor.rst
new file mode 100644
index 0000000..3c0b7d7
--- /dev/null
+++ b/tools/third_party/pytest/tasks/release.minor.rst
@@ -0,0 +1,27 @@
+pytest-{version}
+=======================================
+
+The pytest team is proud to announce the {version} release!
+
+pytest is a mature Python testing tool with more than a 1600 tests
+against itself, passing on many different interpreters and platforms.
+
+This release contains a number of bugs fixes and improvements, so users are encouraged
+to take a look at the CHANGELOG:
+
+    http://doc.pytest.org/en/latest/changelog.html
+
+For complete documentation, please visit:
+
+    http://docs.pytest.org
+
+As usual, you can upgrade from pypi via:
+
+    pip install -U pytest
+
+Thanks to all who contributed to this release, among them:
+
+{contributors}
+
+Happy testing,
+The Pytest Development Team
diff --git a/tools/third_party/pytest/tasks/release.patch.rst b/tools/third_party/pytest/tasks/release.patch.rst
new file mode 100644
index 0000000..56764b9
--- /dev/null
+++ b/tools/third_party/pytest/tasks/release.patch.rst
@@ -0,0 +1,17 @@
+pytest-{version}
+=======================================
+
+pytest {version} has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+  
+The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
+
+Thanks to all who contributed to this release, among them:
+
+{contributors}
+
+Happy testing,
+The pytest Development Team
diff --git a/tools/third_party/pytest/tasks/requirements.txt b/tools/third_party/pytest/tasks/requirements.txt
new file mode 100644
index 0000000..6392de0
--- /dev/null
+++ b/tools/third_party/pytest/tasks/requirements.txt
@@ -0,0 +1,5 @@
+invoke
+tox
+gitpython
+towncrier
+wheel
diff --git a/tools/third_party/pytest/testing/acceptance_test.py b/tools/third_party/pytest/testing/acceptance_test.py
new file mode 100644
index 0000000..a783854
--- /dev/null
+++ b/tools/third_party/pytest/testing/acceptance_test.py
@@ -0,0 +1,850 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+import os
+import sys
+
+import _pytest._code
+import py
+import pytest
+from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
+
+
+class TestGeneralUsage(object):
+    def test_config_error(self, testdir):
+        testdir.makeconftest("""
+            def pytest_configure(config):
+                import pytest
+                raise pytest.UsageError("hello")
+        """)
+        result = testdir.runpytest(testdir.tmpdir)
+        assert result.ret != 0
+        result.stderr.fnmatch_lines([
+            '*ERROR: hello'
+        ])
+
+    def test_root_conftest_syntax_error(self, testdir):
+        testdir.makepyfile(conftest="raise SyntaxError\n")
+        result = testdir.runpytest()
+        result.stderr.fnmatch_lines(["*raise SyntaxError*"])
+        assert result.ret != 0
+
+    def test_early_hook_error_issue38_1(self, testdir):
+        testdir.makeconftest("""
+            def pytest_sessionstart():
+                0 / 0
+        """)
+        result = testdir.runpytest(testdir.tmpdir)
+        assert result.ret != 0
+        # tracestyle is native by default for hook failures
+        result.stdout.fnmatch_lines([
+            '*INTERNALERROR*File*conftest.py*line 2*',
+            '*0 / 0*',
+        ])
+        result = testdir.runpytest(testdir.tmpdir, "--fulltrace")
+        assert result.ret != 0
+        # tracestyle is native by default for hook failures
+        result.stdout.fnmatch_lines([
+            '*INTERNALERROR*def pytest_sessionstart():*',
+            '*INTERNALERROR*0 / 0*',
+        ])
+
+    def test_early_hook_configure_error_issue38(self, testdir):
+        testdir.makeconftest("""
+            def pytest_configure():
+                0 / 0
+        """)
+        result = testdir.runpytest(testdir.tmpdir)
+        assert result.ret != 0
+        # here we get it on stderr
+        result.stderr.fnmatch_lines([
+            '*INTERNALERROR*File*conftest.py*line 2*',
+            '*0 / 0*',
+        ])
+
+    def test_file_not_found(self, testdir):
+        result = testdir.runpytest("asd")
+        assert result.ret != 0
+        result.stderr.fnmatch_lines(["ERROR: file not found*asd"])
+
+    def test_file_not_found_unconfigure_issue143(self, testdir):
+        testdir.makeconftest("""
+            def pytest_configure():
+                print("---configure")
+            def pytest_unconfigure():
+                print("---unconfigure")
+        """)
+        result = testdir.runpytest("-s", "asd")
+        assert result.ret == 4  # EXIT_USAGEERROR
+        result.stderr.fnmatch_lines(["ERROR: file not found*asd"])
+        result.stdout.fnmatch_lines([
+            "*---configure",
+            "*---unconfigure",
+        ])
+
+    def test_config_preparse_plugin_option(self, testdir):
+        testdir.makepyfile(pytest_xyz="""
+            def pytest_addoption(parser):
+                parser.addoption("--xyz", dest="xyz", action="store")
+        """)
+        testdir.makepyfile(test_one="""
+            def test_option(pytestconfig):
+                assert pytestconfig.option.xyz == "123"
+        """)
+        result = testdir.runpytest("-p", "pytest_xyz", "--xyz=123", syspathinsert=True)
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            '*1 passed*',
+        ])
+
+    def test_assertion_magic(self, testdir):
+        p = testdir.makepyfile("""
+            def test_this():
+                x = 0
+                assert x
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            ">       assert x",
+            "E       assert 0",
+        ])
+        assert result.ret == 1
+
+    def test_nested_import_error(self, testdir):
+        p = testdir.makepyfile("""
+                import import_fails
+                def test_this():
+                    assert import_fails.a == 1
+        """)
+        testdir.makepyfile(import_fails="import does_not_work")
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            # XXX on jython this fails:  ">   import import_fails",
+            "ImportError while importing test module*",
+            "*No module named *does_not_work*",
+        ])
+        assert result.ret == 2
+
+    def test_not_collectable_arguments(self, testdir):
+        p1 = testdir.makepyfile("")
+        p2 = testdir.makefile(".pyc", "123")
+        result = testdir.runpytest(p1, p2)
+        assert result.ret
+        result.stderr.fnmatch_lines([
+            "*ERROR: not found:*%s" % (p2.basename,)
+        ])
+
+    def test_issue486_better_reporting_on_conftest_load_failure(self, testdir):
+        testdir.makepyfile("")
+        testdir.makeconftest("import qwerty")
+        result = testdir.runpytest("--help")
+        result.stdout.fnmatch_lines("""
+            *--version*
+            *warning*conftest.py*
+        """)
+        result = testdir.runpytest()
+        result.stderr.fnmatch_lines("""
+            *ERROR*could not load*conftest.py*
+        """)
+
+    def test_early_skip(self, testdir):
+        testdir.mkdir("xyz")
+        testdir.makeconftest("""
+            import pytest
+            def pytest_collect_directory():
+                pytest.skip("early")
+        """)
+        result = testdir.runpytest()
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        result.stdout.fnmatch_lines([
+            "*1 skip*"
+        ])
+
+    def test_issue88_initial_file_multinodes(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            class MyFile(pytest.File):
+                def collect(self):
+                    return [MyItem("hello", parent=self)]
+            def pytest_collect_file(path, parent):
+                return MyFile(path, parent)
+            class MyItem(pytest.Item):
+                pass
+        """)
+        p = testdir.makepyfile("def test_hello(): pass")
+        result = testdir.runpytest(p, "--collect-only")
+        result.stdout.fnmatch_lines([
+            "*MyFile*test_issue88*",
+            "*Module*test_issue88*",
+        ])
+
+    def test_issue93_initialnode_importing_capturing(self, testdir):
+        testdir.makeconftest("""
+            import sys
+            print ("should not be seen")
+            sys.stderr.write("stder42\\n")
+        """)
+        result = testdir.runpytest()
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        assert "should not be seen" not in result.stdout.str()
+        assert "stderr42" not in result.stderr.str()
+
+    def test_conftest_printing_shows_if_error(self, testdir):
+        testdir.makeconftest("""
+            print ("should be seen")
+            assert 0
+        """)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        assert "should be seen" in result.stdout.str()
+
+    @pytest.mark.skipif(not hasattr(py.path.local, 'mksymlinkto'),
+                        reason="symlink not available on this platform")
+    def test_chdir(self, testdir):
+        testdir.tmpdir.join("py").mksymlinkto(py._pydir)
+        p = testdir.tmpdir.join("main.py")
+        p.write(_pytest._code.Source("""
+            import sys, os
+            sys.path.insert(0, '')
+            import py
+            print (py.__file__)
+            print (py.__path__)
+            os.chdir(os.path.dirname(os.getcwd()))
+            print (py.log)
+        """))
+        result = testdir.runpython(p)
+        assert not result.ret
+
+    def test_issue109_sibling_conftests_not_loaded(self, testdir):
+        sub1 = testdir.tmpdir.mkdir("sub1")
+        sub2 = testdir.tmpdir.mkdir("sub2")
+        sub1.join("conftest.py").write("assert 0")
+        result = testdir.runpytest(sub2)
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        sub2.ensure("__init__.py")
+        p = sub2.ensure("test_hello.py")
+        result = testdir.runpytest(p)
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        result = testdir.runpytest(sub1)
+        assert result.ret == EXIT_USAGEERROR
+
+    def test_directory_skipped(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            def pytest_ignore_collect():
+                pytest.skip("intentional")
+        """)
+        testdir.makepyfile("def test_hello(): pass")
+        result = testdir.runpytest()
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        result.stdout.fnmatch_lines([
+            "*1 skipped*"
+        ])
+
+    def test_multiple_items_per_collector_byid(self, testdir):
+        c = testdir.makeconftest("""
+            import pytest
+            class MyItem(pytest.Item):
+                def runtest(self):
+                    pass
+            class MyCollector(pytest.File):
+                def collect(self):
+                    return [MyItem(name="xyz", parent=self)]
+            def pytest_collect_file(path, parent):
+                if path.basename.startswith("conftest"):
+                    return MyCollector(path, parent)
+        """)
+        result = testdir.runpytest(c.basename + "::" + "xyz")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*1 pass*",
+        ])
+
+    def test_skip_on_generated_funcarg_id(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall({'x': 3}, id='hello-123')
+            def pytest_runtest_setup(item):
+                print (item.keywords)
+                if 'hello-123' in item.keywords:
+                    pytest.skip("hello")
+                assert 0
+        """)
+        p = testdir.makepyfile("""def test_func(x): pass""")
+        res = testdir.runpytest(p)
+        assert res.ret == 0
+        res.stdout.fnmatch_lines(["*1 skipped*"])
+
+    def test_direct_addressing_selects(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall({'i': 1}, id="1")
+                metafunc.addcall({'i': 2}, id="2")
+            def test_func(i):
+                pass
+        """)
+        res = testdir.runpytest(p.basename + "::" + "test_func[1]")
+        assert res.ret == 0
+        res.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_direct_addressing_notfound(self, testdir):
+        p = testdir.makepyfile("""
+            def test_func():
+                pass
+        """)
+        res = testdir.runpytest(p.basename + "::" + "test_notfound")
+        assert res.ret
+        res.stderr.fnmatch_lines(["*ERROR*not found*"])
+
+    def test_docstring_on_hookspec(self):
+        from _pytest import hookspec
+        for name, value in vars(hookspec).items():
+            if name.startswith("pytest_"):
+                assert value.__doc__, "no docstring for %s" % name
+
+    def test_initialization_error_issue49(self, testdir):
+        testdir.makeconftest("""
+            def pytest_configure():
+                x
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 3  # internal error
+        result.stderr.fnmatch_lines([
+            "INTERNAL*pytest_configure*",
+            "INTERNAL*x*",
+        ])
+        assert 'sessionstarttime' not in result.stderr.str()
+
+    @pytest.mark.parametrize('lookfor', ['test_fun.py::test_a'])
+    def test_issue134_report_error_when_collecting_member(self, testdir, lookfor):
+        testdir.makepyfile(test_fun="""
+            def test_a():
+                pass
+            def""")
+        result = testdir.runpytest(lookfor)
+        result.stdout.fnmatch_lines(['*SyntaxError*'])
+        if '::' in lookfor:
+            result.stderr.fnmatch_lines([
+                '*ERROR*',
+            ])
+            assert result.ret == 4  # usage error only if item not found
+
+    def test_report_all_failed_collections_initargs(self, testdir):
+        testdir.makepyfile(test_a="def", test_b="def")
+        result = testdir.runpytest("test_a.py::a", "test_b.py::b")
+        result.stderr.fnmatch_lines([
+            "*ERROR*test_a.py::a*",
+            "*ERROR*test_b.py::b*",
+        ])
+
+    @pytest.mark.usefixtures('recwarn')
+    def test_namespace_import_doesnt_confuse_import_hook(self, testdir):
+        """
+        Ref #383. Python 3.3's namespace package messed with our import hooks
+        Importing a module that didn't exist, even if the ImportError was
+        gracefully handled, would make our test crash.
+
+        Use recwarn here to silence this warning in Python 2.7:
+            ImportWarning: Not importing directory '...\not_a_package': missing __init__.py
+        """
+        testdir.mkdir('not_a_package')
+        p = testdir.makepyfile("""
+            try:
+                from not_a_package import doesnt_exist
+            except ImportError:
+                # We handle the import error gracefully here
+                pass
+
+            def test_whatever():
+                pass
+        """)
+        res = testdir.runpytest(p.basename)
+        assert res.ret == 0
+
+    def test_unknown_option(self, testdir):
+        result = testdir.runpytest("--qwlkej")
+        result.stderr.fnmatch_lines("""
+            *unrecognized*
+        """)
+
+    def test_getsourcelines_error_issue553(self, testdir, monkeypatch):
+        monkeypatch.setattr("inspect.getsourcelines", None)
+        p = testdir.makepyfile("""
+            def raise_error(obj):
+                raise IOError('source code not available')
+
+            import inspect
+            inspect.getsourcelines = raise_error
+
+            def test_foo(invalid_fixture):
+                pass
+        """)
+        res = testdir.runpytest(p)
+        res.stdout.fnmatch_lines([
+            "*source code not available*",
+            "E*fixture 'invalid_fixture' not found",
+        ])
+
+    def test_plugins_given_as_strings(self, tmpdir, monkeypatch):
+        """test that str values passed to main() as `plugins` arg
+        are interpreted as module names to be imported and registered.
+        #855.
+        """
+        with pytest.raises(ImportError) as excinfo:
+            pytest.main([str(tmpdir)], plugins=['invalid.module'])
+        assert 'invalid' in str(excinfo.value)
+
+        p = tmpdir.join('test_test_plugins_given_as_strings.py')
+        p.write('def test_foo(): pass')
+        mod = py.std.types.ModuleType("myplugin")
+        monkeypatch.setitem(sys.modules, 'myplugin', mod)
+        assert pytest.main(args=[str(tmpdir)], plugins=['myplugin']) == 0
+
+    def test_parametrized_with_bytes_regex(self, testdir):
+        p = testdir.makepyfile("""
+            import re
+            import pytest
+            @pytest.mark.parametrize('r', [re.compile(b'foo')])
+            def test_stuff(r):
+                pass
+        """
+                               )
+        res = testdir.runpytest(p)
+        res.stdout.fnmatch_lines([
+            '*1 passed*'
+        ])
+
+    def test_parametrized_with_null_bytes(self, testdir):
+        """Test parametrization with values that contain null bytes and unicode characters (#2644, #2957)"""
+        p = testdir.makepyfile(u"""
+            # encoding: UTF-8
+            import pytest
+
+            @pytest.mark.parametrize("data", [b"\\x00", "\\x00", u'ação'])
+            def test_foo(data):
+                assert data
+        """)
+        res = testdir.runpytest(p)
+        res.assert_outcomes(passed=3)
+
+
+class TestInvocationVariants(object):
+    def test_earlyinit(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            assert hasattr(pytest, 'mark')
+        """)
+        result = testdir.runpython(p)
+        assert result.ret == 0
+
+    @pytest.mark.xfail("sys.platform.startswith('java')")
+    def test_pydoc(self, testdir):
+        for name in ('py.test', 'pytest'):
+            result = testdir.runpython_c("import %s;help(%s)" % (name, name))
+            assert result.ret == 0
+            s = result.stdout.str()
+            assert 'MarkGenerator' in s
+
+    def test_import_star_py_dot_test(self, testdir):
+        p = testdir.makepyfile("""
+            from py.test import *
+            #collect
+            #cmdline
+            #Item
+            # assert collect.Item is Item
+            # assert collect.Collector is Collector
+            main
+            skip
+            xfail
+        """)
+        result = testdir.runpython(p)
+        assert result.ret == 0
+
+    def test_import_star_pytest(self, testdir):
+        p = testdir.makepyfile("""
+            from pytest import *
+            #Item
+            #File
+            main
+            skip
+            xfail
+        """)
+        result = testdir.runpython(p)
+        assert result.ret == 0
+
+    def test_double_pytestcmdline(self, testdir):
+        p = testdir.makepyfile(run="""
+            import pytest
+            pytest.main()
+            pytest.main()
+        """)
+        testdir.makepyfile("""
+            def test_hello():
+                pass
+        """)
+        result = testdir.runpython(p)
+        result.stdout.fnmatch_lines([
+            "*1 passed*",
+            "*1 passed*",
+        ])
+
+    def test_python_minus_m_invocation_ok(self, testdir):
+        p1 = testdir.makepyfile("def test_hello(): pass")
+        res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
+        assert res.ret == 0
+
+    def test_python_minus_m_invocation_fail(self, testdir):
+        p1 = testdir.makepyfile("def test_fail(): 0/0")
+        res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
+        assert res.ret == 1
+
+    def test_python_pytest_package(self, testdir):
+        p1 = testdir.makepyfile("def test_pass(): pass")
+        res = testdir.run(py.std.sys.executable, "-m", "pytest", str(p1))
+        assert res.ret == 0
+        res.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_equivalence_pytest_pytest(self):
+        assert pytest.main == py.test.cmdline.main
+
+    def test_invoke_with_string(self, capsys):
+        retcode = pytest.main("-h")
+        assert not retcode
+        out, err = capsys.readouterr()
+        assert "--help" in out
+        pytest.raises(ValueError, lambda: pytest.main(0))
+
+    def test_invoke_with_path(self, tmpdir, capsys):
+        retcode = pytest.main(tmpdir)
+        assert retcode == EXIT_NOTESTSCOLLECTED
+        out, err = capsys.readouterr()
+
+    def test_invoke_plugin_api(self, testdir, capsys):
+        class MyPlugin(object):
+            def pytest_addoption(self, parser):
+                parser.addoption("--myopt")
+
+        pytest.main(["-h"], plugins=[MyPlugin()])
+        out, err = capsys.readouterr()
+        assert "--myopt" in out
+
+    def test_pyargs_importerror(self, testdir, monkeypatch):
+        monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
+        path = testdir.mkpydir("tpkg")
+        path.join("test_hello.py").write('raise ImportError')
+
+        result = testdir.runpytest_subprocess("--pyargs", "tpkg.test_hello")
+        assert result.ret != 0
+
+        result.stdout.fnmatch_lines([
+            "collected*0*items*/*1*errors"
+        ])
+
+    def test_cmdline_python_package(self, testdir, monkeypatch):
+        import warnings
+        monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
+        path = testdir.mkpydir("tpkg")
+        path.join("test_hello.py").write("def test_hello(): pass")
+        path.join("test_world.py").write("def test_world(): pass")
+        result = testdir.runpytest("--pyargs", "tpkg")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*2 passed*"
+        ])
+        result = testdir.runpytest("--pyargs", "tpkg.test_hello")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*1 passed*"
+        ])
+
+        def join_pythonpath(what):
+            cur = py.std.os.environ.get('PYTHONPATH')
+            if cur:
+                return str(what) + os.pathsep + cur
+            return what
+        empty_package = testdir.mkpydir("empty_package")
+        monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package))
+        # the path which is not a package raises a warning on pypy;
+        # no idea why only pypy and not normal python warn about it here
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', ImportWarning)
+            result = testdir.runpytest("--pyargs", ".")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*2 passed*"
+        ])
+
+        monkeypatch.setenv('PYTHONPATH', join_pythonpath(testdir))
+        result = testdir.runpytest("--pyargs", "tpkg.test_missing")
+        assert result.ret != 0
+        result.stderr.fnmatch_lines([
+            "*not*found*test_missing*",
+        ])
+
+    def test_cmdline_python_namespace_package(self, testdir, monkeypatch):
+        """
+        test --pyargs option with namespace packages (#1567)
+        """
+        monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False)
+
+        search_path = []
+        for dirname in "hello", "world":
+            d = testdir.mkdir(dirname)
+            search_path.append(d)
+            ns = d.mkdir("ns_pkg")
+            ns.join("__init__.py").write(
+                "__import__('pkg_resources').declare_namespace(__name__)")
+            lib = ns.mkdir(dirname)
+            lib.ensure("__init__.py")
+            lib.join("test_{0}.py".format(dirname)). \
+                write("def test_{0}(): pass\n"
+                      "def test_other():pass".format(dirname))
+
+        # The structure of the test directory is now:
+        # .
+        # ├── hello
+        # │   └── ns_pkg
+        # │       ├── __init__.py
+        # │       └── hello
+        # │           ├── __init__.py
+        # │           └── test_hello.py
+        # └── world
+        #     └── ns_pkg
+        #         ├── __init__.py
+        #         └── world
+        #             ├── __init__.py
+        #             └── test_world.py
+
+        def join_pythonpath(*dirs):
+            cur = py.std.os.environ.get('PYTHONPATH')
+            if cur:
+                dirs += (cur,)
+            return os.pathsep.join(str(p) for p in dirs)
+        monkeypatch.setenv('PYTHONPATH', join_pythonpath(*search_path))
+        for p in search_path:
+            monkeypatch.syspath_prepend(p)
+
+        os.chdir('world')
+        # mixed module and filenames:
+        result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
+        testdir.chdir()
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*test_hello.py::test_hello*PASSED*",
+            "*test_hello.py::test_other*PASSED*",
+            "*test_world.py::test_world*PASSED*",
+            "*test_world.py::test_other*PASSED*",
+            "*4 passed*"
+        ])
+
+        # specify tests within a module
+        result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*test_world.py::test_other*PASSED*",
+            "*1 passed*"
+        ])
+
+    def test_cmdline_python_package_not_exists(self, testdir):
+        result = testdir.runpytest("--pyargs", "tpkgwhatv")
+        assert result.ret
+        result.stderr.fnmatch_lines([
+            "ERROR*file*or*package*not*found*",
+        ])
+
+    @pytest.mark.xfail(reason="decide: feature or bug")
+    def test_noclass_discovery_if_not_testcase(self, testdir):
+        testpath = testdir.makepyfile("""
+            import unittest
+            class TestHello(object):
+                def test_hello(self):
+                    assert self.attr
+
+            class RealTest(unittest.TestCase, TestHello):
+                attr = 42
+        """)
+        reprec = testdir.inline_run(testpath)
+        reprec.assertoutcome(passed=1)
+
+    def test_doctest_id(self, testdir):
+        testdir.makefile('.txt', """
+            >>> x=3
+            >>> x
+            4
+        """)
+        result = testdir.runpytest("-rf")
+        lines = result.stdout.str().splitlines()
+        for line in lines:
+            if line.startswith("FAIL "):
+                testid = line[5:].strip()
+                break
+        result = testdir.runpytest(testid, '-rf')
+        result.stdout.fnmatch_lines([
+            line,
+            "*1 failed*",
+        ])
+
+    def test_core_backward_compatibility(self):
+        """Test backward compatibility for get_plugin_manager function. See #787."""
+        import _pytest.config
+        assert type(_pytest.config.get_plugin_manager()) is _pytest.config.PytestPluginManager
+
+    def test_has_plugin(self, request):
+        """Test hasplugin function of the plugin manager (#932)."""
+        assert request.config.pluginmanager.hasplugin('python')
+
+
+class TestDurations(object):
+    source = """
+        import time
+        frag = 0.002
+        def test_something():
+            pass
+        def test_2():
+            time.sleep(frag*5)
+        def test_1():
+            time.sleep(frag)
+        def test_3():
+            time.sleep(frag*10)
+    """
+
+    def test_calls(self, testdir):
+        testdir.makepyfile(self.source)
+        result = testdir.runpytest("--durations=10")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines_random([
+            "*durations*",
+            "*call*test_3*",
+            "*call*test_2*",
+            "*call*test_1*",
+        ])
+
+    def test_calls_show_2(self, testdir):
+        testdir.makepyfile(self.source)
+        result = testdir.runpytest("--durations=2")
+        assert result.ret == 0
+        lines = result.stdout.get_lines_after("*slowest*durations*")
+        assert "4 passed" in lines[2]
+
+    def test_calls_showall(self, testdir):
+        testdir.makepyfile(self.source)
+        result = testdir.runpytest("--durations=0")
+        assert result.ret == 0
+        for x in "123":
+            for y in 'call', :  # 'setup', 'call', 'teardown':
+                for line in result.stdout.lines:
+                    if ("test_%s" % x) in line and y in line:
+                        break
+                else:
+                    raise AssertionError("not found %s %s" % (x, y))
+
+    def test_with_deselected(self, testdir):
+        testdir.makepyfile(self.source)
+        result = testdir.runpytest("--durations=2", "-k test_1")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*durations*",
+            "*call*test_1*",
+        ])
+
+    def test_with_failing_collection(self, testdir):
+        testdir.makepyfile(self.source)
+        testdir.makepyfile(test_collecterror="""xyz""")
+        result = testdir.runpytest("--durations=2", "-k test_1")
+        assert result.ret == 2
+        result.stdout.fnmatch_lines([
+            "*Interrupted: 1 errors during collection*",
+        ])
+        # Collection errors abort test execution, therefore no duration is
+        # output
+        assert "duration" not in result.stdout.str()
+
+    def test_with_not(self, testdir):
+        testdir.makepyfile(self.source)
+        result = testdir.runpytest("-k not 1")
+        assert result.ret == 0
+
+
+class TestDurationWithFixture(object):
+    source = """
+        import time
+        frag = 0.001
+        def setup_function(func):
+            time.sleep(frag * 3)
+        def test_1():
+            time.sleep(frag*2)
+        def test_2():
+            time.sleep(frag)
+    """
+
+    def test_setup_function(self, testdir):
+        testdir.makepyfile(self.source)
+        result = testdir.runpytest("--durations=10")
+        assert result.ret == 0
+
+        result.stdout.fnmatch_lines_random("""
+            *durations*
+            * setup *test_1*
+            * call *test_1*
+        """)
+
+
+def test_zipimport_hook(testdir, tmpdir):
+    """Test package loader is being used correctly (see #1837)."""
+    zipapp = pytest.importorskip('zipapp')
+    testdir.tmpdir.join('app').ensure(dir=1)
+    testdir.makepyfile(**{
+        'app/foo.py': """
+            import pytest
+            def main():
+                pytest.main(['--pyarg', 'foo'])
+        """,
+    })
+    target = tmpdir.join('foo.zip')
+    zipapp.create_archive(str(testdir.tmpdir.join('app')), str(target), main='foo:main')
+    result = testdir.runpython(target)
+    assert result.ret == 0
+    result.stderr.fnmatch_lines(['*not found*foo*'])
+    assert 'INTERNALERROR>' not in result.stdout.str()
+
+
+def test_import_plugin_unicode_name(testdir):
+    testdir.makepyfile(
+        myplugin='',
+    )
+    testdir.makepyfile("""
+        def test(): pass
+    """)
+    testdir.makeconftest("""
+        pytest_plugins = [u'myplugin']
+    """)
+    r = testdir.runpytest()
+    assert r.ret == 0
+
+
+def test_deferred_hook_checking(testdir):
+    """
+    Check hooks as late as possible (#1821).
+    """
+    testdir.syspathinsert()
+    testdir.makepyfile(**{
+        'plugin.py': """
+        class Hooks:
+            def pytest_my_hook(self, config):
+                pass
+
+        def pytest_configure(config):
+            config.pluginmanager.add_hookspecs(Hooks)
+        """,
+        'conftest.py': """
+            pytest_plugins = ['plugin']
+            def pytest_my_hook(config):
+                return 40
+        """,
+        'test_foo.py': """
+            def test(request):
+                assert request.config.hook.pytest_my_hook(config=request.config) == [40]
+        """
+    })
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(['* 1 passed *'])
diff --git a/tools/third_party/pytest/testing/code/test_code.py b/tools/third_party/pytest/testing/code/test_code.py
new file mode 100644
index 0000000..209a8ef
--- /dev/null
+++ b/tools/third_party/pytest/testing/code/test_code.py
@@ -0,0 +1,196 @@
+# coding: utf-8
+from __future__ import absolute_import, division, print_function
+import sys
+
+import _pytest._code
+import py
+import pytest
+from test_excinfo import TWMock
+
+
+def test_ne():
+    code1 = _pytest._code.Code(compile('foo = "bar"', '', 'exec'))
+    assert code1 == code1
+    code2 = _pytest._code.Code(compile('foo = "baz"', '', 'exec'))
+    assert code2 != code1
+
+
+def test_code_gives_back_name_for_not_existing_file():
+    name = 'abc-123'
+    co_code = compile("pass\n", name, 'exec')
+    assert co_code.co_filename == name
+    code = _pytest._code.Code(co_code)
+    assert str(code.path) == name
+    assert code.fullsource is None
+
+
+def test_code_with_class():
+    class A(object):
+        pass
+    pytest.raises(TypeError, "_pytest._code.Code(A)")
+
+
+if True:
+    def x():
+        pass
+
+
+def test_code_fullsource():
+    code = _pytest._code.Code(x)
+    full = code.fullsource
+    assert 'test_code_fullsource()' in str(full)
+
+
+def test_code_source():
+    code = _pytest._code.Code(x)
+    src = code.source()
+    expected = """def x():
+    pass"""
+    assert str(src) == expected
+
+
+def test_frame_getsourcelineno_myself():
+    def func():
+        return sys._getframe(0)
+    f = func()
+    f = _pytest._code.Frame(f)
+    source, lineno = f.code.fullsource, f.lineno
+    assert source[lineno].startswith("        return sys._getframe(0)")
+
+
+def test_getstatement_empty_fullsource():
+    def func():
+        return sys._getframe(0)
+    f = func()
+    f = _pytest._code.Frame(f)
+    prop = f.code.__class__.fullsource
+    try:
+        f.code.__class__.fullsource = None
+        assert f.statement == _pytest._code.Source("")
+    finally:
+        f.code.__class__.fullsource = prop
+
+
+def test_code_from_func():
+    co = _pytest._code.Code(test_frame_getsourcelineno_myself)
+    assert co.firstlineno
+    assert co.path
+
+
+def test_unicode_handling():
+    value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
+
+    def f():
+        raise Exception(value)
+
+    excinfo = pytest.raises(Exception, f)
+    str(excinfo)
+    if sys.version_info[0] < 3:
+        unicode(excinfo)
+
+
+@pytest.mark.skipif(sys.version_info[0] >= 3, reason='python 2 only issue')
+def test_unicode_handling_syntax_error():
+    value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
+
+    def f():
+        raise SyntaxError('invalid syntax', (None, 1, 3, value))
+
+    excinfo = pytest.raises(Exception, f)
+    str(excinfo)
+    if sys.version_info[0] < 3:
+        unicode(excinfo)
+
+
+def test_code_getargs():
+    def f1(x):
+        pass
+    c1 = _pytest._code.Code(f1)
+    assert c1.getargs(var=True) == ('x',)
+
+    def f2(x, *y):
+        pass
+    c2 = _pytest._code.Code(f2)
+    assert c2.getargs(var=True) == ('x', 'y')
+
+    def f3(x, **z):
+        pass
+    c3 = _pytest._code.Code(f3)
+    assert c3.getargs(var=True) == ('x', 'z')
+
+    def f4(x, *y, **z):
+        pass
+    c4 = _pytest._code.Code(f4)
+    assert c4.getargs(var=True) == ('x', 'y', 'z')
+
+
+def test_frame_getargs():
+    def f1(x):
+        return sys._getframe(0)
+    fr1 = _pytest._code.Frame(f1('a'))
+    assert fr1.getargs(var=True) == [('x', 'a')]
+
+    def f2(x, *y):
+        return sys._getframe(0)
+    fr2 = _pytest._code.Frame(f2('a', 'b', 'c'))
+    assert fr2.getargs(var=True) == [('x', 'a'), ('y', ('b', 'c'))]
+
+    def f3(x, **z):
+        return sys._getframe(0)
+    fr3 = _pytest._code.Frame(f3('a', b='c'))
+    assert fr3.getargs(var=True) == [('x', 'a'), ('z', {'b': 'c'})]
+
+    def f4(x, *y, **z):
+        return sys._getframe(0)
+    fr4 = _pytest._code.Frame(f4('a', 'b', c='d'))
+    assert fr4.getargs(var=True) == [('x', 'a'), ('y', ('b',)),
+                                     ('z', {'c': 'd'})]
+
+
+class TestExceptionInfo(object):
+
+    def test_bad_getsource(self):
+        try:
+            if False:
+                pass
+            else:
+                assert False
+        except AssertionError:
+            exci = _pytest._code.ExceptionInfo()
+        assert exci.getrepr()
+
+
+class TestTracebackEntry(object):
+
+    def test_getsource(self):
+        try:
+            if False:
+                pass
+            else:
+                assert False
+        except AssertionError:
+            exci = _pytest._code.ExceptionInfo()
+        entry = exci.traceback[0]
+        source = entry.getsource()
+        assert len(source) == 6
+        assert 'assert False' in source[5]
+
+
+class TestReprFuncArgs(object):
+
+    def test_not_raise_exception_with_mixed_encoding(self):
+        from _pytest._code.code import ReprFuncArgs
+
+        tw = TWMock()
+
+        args = [
+            ('unicode_string', u"São Paulo"),
+            ('utf8_string', 'S\xc3\xa3o Paulo'),
+        ]
+
+        r = ReprFuncArgs(args)
+        r.toterminal(tw)
+        if sys.version_info[0] >= 3:
+            assert tw.lines[0] == 'unicode_string = São Paulo, utf8_string = São Paulo'
+        else:
+            assert tw.lines[0] == 'unicode_string = São Paulo, utf8_string = São Paulo'
diff --git a/tools/third_party/pytest/testing/code/test_excinfo.py b/tools/third_party/pytest/testing/code/test_excinfo.py
new file mode 100644
index 0000000..34db8ff
--- /dev/null
+++ b/tools/third_party/pytest/testing/code/test_excinfo.py
@@ -0,0 +1,1245 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+import operator
+import _pytest
+import py
+import pytest
+from _pytest._code.code import (
+    ExceptionInfo,
+    FormattedExcinfo,
+    ReprExceptionInfo,
+    ExceptionChainRepr)
+
+from test_source import astonly
+
+try:
+    import importlib
+except ImportError:
+    invalidate_import_caches = None
+else:
+    invalidate_import_caches = getattr(importlib, "invalidate_caches", None)
+
+queue = py.builtin._tryimport('queue', 'Queue')
+
+failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
+
+pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
+
+
+class TWMock(object):
+    WRITE = object()
+
+    def __init__(self):
+        self.lines = []
+        self.is_writing = False
+
+    def sep(self, sep, line=None):
+        self.lines.append((sep, line))
+
+    def write(self, msg, **kw):
+        self.lines.append((TWMock.WRITE, msg))
+
+    def line(self, line, **kw):
+        self.lines.append(line)
+
+    def markup(self, text, **kw):
+        return text
+
+    def get_write_msg(self, idx):
+        flag, msg = self.lines[idx]
+        assert flag == TWMock.WRITE
+        return msg
+
+    fullwidth = 80
+
+
+def test_excinfo_simple():
+    try:
+        raise ValueError
+    except ValueError:
+        info = _pytest._code.ExceptionInfo()
+    assert info.type == ValueError
+
+
+def test_excinfo_getstatement():
+    def g():
+        raise ValueError
+
+    def f():
+        g()
+
+    try:
+        f()
+    except ValueError:
+        excinfo = _pytest._code.ExceptionInfo()
+    linenumbers = [_pytest._code.getrawcode(f).co_firstlineno - 1 + 4,
+                   _pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
+                   _pytest._code.getrawcode(g).co_firstlineno - 1 + 1, ]
+    values = list(excinfo.traceback)
+    foundlinenumbers = [x.lineno for x in values]
+    assert foundlinenumbers == linenumbers
+    # for x in info:
+    #    print "%s:%d  %s" %(x.path.relto(root), x.lineno, x.statement)
+    # xxx
+
+# testchain for getentries test below
+
+
+def f():
+    #
+    raise ValueError
+    #
+
+
+def g():
+    #
+    __tracebackhide__ = True
+    f()
+    #
+
+
+def h():
+    #
+    g()
+    #
+
+
+class TestTraceback_f_g_h(object):
+    def setup_method(self, method):
+        try:
+            h()
+        except ValueError:
+            self.excinfo = _pytest._code.ExceptionInfo()
+
+    def test_traceback_entries(self):
+        tb = self.excinfo.traceback
+        entries = list(tb)
+        assert len(tb) == 4  # maybe fragile test
+        assert len(entries) == 4  # maybe fragile test
+        names = ['f', 'g', 'h']
+        for entry in entries:
+            try:
+                names.remove(entry.frame.code.name)
+            except ValueError:
+                pass
+        assert not names
+
+    def test_traceback_entry_getsource(self):
+        tb = self.excinfo.traceback
+        s = str(tb[-1].getsource())
+        assert s.startswith("def f():")
+        assert s.endswith("raise ValueError")
+
+    @astonly
+    @failsonjython
+    def test_traceback_entry_getsource_in_construct(self):
+        source = _pytest._code.Source("""\
+            def xyz():
+                try:
+                    raise ValueError
+                except somenoname:
+                    pass
+            xyz()
+        """)
+        try:
+            exec(source.compile())
+        except NameError:
+            tb = _pytest._code.ExceptionInfo().traceback
+            print(tb[-1].getsource())
+            s = str(tb[-1].getsource())
+            assert s.startswith("def xyz():\n    try:")
+            assert s.strip().endswith("except somenoname:")
+
+    def test_traceback_cut(self):
+        co = _pytest._code.Code(f)
+        path, firstlineno = co.path, co.firstlineno
+        traceback = self.excinfo.traceback
+        newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
+        assert len(newtraceback) == 1
+        newtraceback = traceback.cut(path=path, lineno=firstlineno + 2)
+        assert len(newtraceback) == 1
+
+    def test_traceback_cut_excludepath(self, testdir):
+        p = testdir.makepyfile("def f(): raise ValueError")
+        excinfo = pytest.raises(ValueError, "p.pyimport().f()")
+        basedir = py.path.local(pytest.__file__).dirpath()
+        newtraceback = excinfo.traceback.cut(excludepath=basedir)
+        for x in newtraceback:
+            if hasattr(x, 'path'):
+                assert not py.path.local(x.path).relto(basedir)
+        assert newtraceback[-1].frame.code.path == p
+
+    def test_traceback_filter(self):
+        traceback = self.excinfo.traceback
+        ntraceback = traceback.filter()
+        assert len(ntraceback) == len(traceback) - 1
+
+    @pytest.mark.parametrize('tracebackhide, matching', [
+        (lambda info: True, True),
+        (lambda info: False, False),
+        (operator.methodcaller('errisinstance', ValueError), True),
+        (operator.methodcaller('errisinstance', IndexError), False),
+    ])
+    def test_traceback_filter_selective(self, tracebackhide, matching):
+        def f():
+            #
+            raise ValueError
+            #
+
+        def g():
+            #
+            __tracebackhide__ = tracebackhide
+            f()
+            #
+
+        def h():
+            #
+            g()
+            #
+
+        excinfo = pytest.raises(ValueError, h)
+        traceback = excinfo.traceback
+        ntraceback = traceback.filter()
+        print('old: {0!r}'.format(traceback))
+        print('new: {0!r}'.format(ntraceback))
+
+        if matching:
+            assert len(ntraceback) == len(traceback) - 2
+        else:
+            # -1 because of the __tracebackhide__ in pytest.raises
+            assert len(ntraceback) == len(traceback) - 1
+
+    def test_traceback_recursion_index(self):
+        def f(n):
+            if n < 10:
+                n += 1
+            f(n)
+        excinfo = pytest.raises(RuntimeError, f, 8)
+        traceback = excinfo.traceback
+        recindex = traceback.recursionindex()
+        assert recindex == 3
+
+    def test_traceback_only_specific_recursion_errors(self, monkeypatch):
+        def f(n):
+            if n == 0:
+                raise RuntimeError("hello")
+            f(n - 1)
+
+        excinfo = pytest.raises(RuntimeError, f, 100)
+        monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
+        repr = excinfo.getrepr()
+        assert "RuntimeError: hello" in str(repr.reprcrash)
+
+    def test_traceback_no_recursion_index(self):
+        def do_stuff():
+            raise RuntimeError
+
+        def reraise_me():
+            import sys
+            exc, val, tb = sys.exc_info()
+            py.builtin._reraise(exc, val, tb)
+
+        def f(n):
+            try:
+                do_stuff()
+            except:  # noqa
+                reraise_me()
+
+        excinfo = pytest.raises(RuntimeError, f, 8)
+        traceback = excinfo.traceback
+        recindex = traceback.recursionindex()
+        assert recindex is None
+
+    def test_traceback_messy_recursion(self):
+        # XXX: simplified locally testable version
+        decorator = pytest.importorskip('decorator').decorator
+
+        def log(f, *k, **kw):
+            print('%s %s' % (k, kw))
+            f(*k, **kw)
+        log = decorator(log)
+
+        def fail():
+            raise ValueError('')
+
+        fail = log(log(fail))
+
+        excinfo = pytest.raises(ValueError, fail)
+        assert excinfo.traceback.recursionindex() is None
+
+    def test_traceback_getcrashentry(self):
+        def i():
+            __tracebackhide__ = True
+            raise ValueError
+
+        def h():
+            i()
+
+        def g():
+            __tracebackhide__ = True
+            h()
+
+        def f():
+            g()
+
+        excinfo = pytest.raises(ValueError, f)
+        tb = excinfo.traceback
+        entry = tb.getcrashentry()
+        co = _pytest._code.Code(h)
+        assert entry.frame.code.path == co.path
+        assert entry.lineno == co.firstlineno + 1
+        assert entry.frame.code.name == 'h'
+
+    def test_traceback_getcrashentry_empty(self):
+        def g():
+            __tracebackhide__ = True
+            raise ValueError
+
+        def f():
+            __tracebackhide__ = True
+            g()
+
+        excinfo = pytest.raises(ValueError, f)
+        tb = excinfo.traceback
+        entry = tb.getcrashentry()
+        co = _pytest._code.Code(g)
+        assert entry.frame.code.path == co.path
+        assert entry.lineno == co.firstlineno + 2
+        assert entry.frame.code.name == 'g'
+
+
+def test_excinfo_exconly():
+    excinfo = pytest.raises(ValueError, h)
+    assert excinfo.exconly().startswith('ValueError')
+    excinfo = pytest.raises(ValueError,
+                            "raise ValueError('hello\\nworld')")
+    msg = excinfo.exconly(tryshort=True)
+    assert msg.startswith('ValueError')
+    assert msg.endswith("world")
+
+
+def test_excinfo_repr():
+    excinfo = pytest.raises(ValueError, h)
+    s = repr(excinfo)
+    assert s == "<ExceptionInfo ValueError tblen=4>"
+
+
+def test_excinfo_str():
+    excinfo = pytest.raises(ValueError, h)
+    s = str(excinfo)
+    assert s.startswith(__file__[:-9])  # pyc file and $py.class
+    assert s.endswith("ValueError")
+    assert len(s.split(":")) >= 3  # on windows it's 4
+
+
+def test_excinfo_errisinstance():
+    excinfo = pytest.raises(ValueError, h)
+    assert excinfo.errisinstance(ValueError)
+
+
+def test_excinfo_no_sourcecode():
+    try:
+        exec("raise ValueError()")
+    except ValueError:
+        excinfo = _pytest._code.ExceptionInfo()
+    s = str(excinfo.traceback[-1])
+    assert s == "  File '<string>':1 in <module>\n  ???\n"
+
+
+def test_excinfo_no_python_sourcecode(tmpdir):
+    # XXX: simplified locally testable version
+    tmpdir.join('test.txt').write("{{ h()}}:")
+
+    jinja2 = pytest.importorskip('jinja2')
+    loader = jinja2.FileSystemLoader(str(tmpdir))
+    env = jinja2.Environment(loader=loader)
+    template = env.get_template('test.txt')
+    excinfo = pytest.raises(ValueError,
+                            template.render, h=h)
+    for item in excinfo.traceback:
+        print(item)  # XXX: for some reason jinja.Template.render is printed in full
+        item.source  # shouldnt fail
+        if item.path.basename == 'test.txt':
+            assert str(item.source) == '{{ h()}}:'
+
+
+def test_entrysource_Queue_example():
+    try:
+        queue.Queue().get(timeout=0.001)
+    except queue.Empty:
+        excinfo = _pytest._code.ExceptionInfo()
+    entry = excinfo.traceback[-1]
+    source = entry.getsource()
+    assert source is not None
+    s = str(source).strip()
+    assert s.startswith("def get")
+
+
+def test_codepath_Queue_example():
+    try:
+        queue.Queue().get(timeout=0.001)
+    except queue.Empty:
+        excinfo = _pytest._code.ExceptionInfo()
+    entry = excinfo.traceback[-1]
+    path = entry.path
+    assert isinstance(path, py.path.local)
+    assert path.basename.lower() == "queue.py"
+    assert path.check()
+
+
+def test_match_succeeds():
+    with pytest.raises(ZeroDivisionError) as excinfo:
+        0 // 0
+    excinfo.match(r'.*zero.*')
+
+
+def test_match_raises_error(testdir):
+    testdir.makepyfile("""
+        import pytest
+        def test_division_zero():
+            with pytest.raises(ZeroDivisionError) as excinfo:
+                0 / 0
+            excinfo.match(r'[123]+')
+    """)
+    result = testdir.runpytest()
+    assert result.ret != 0
+    result.stdout.fnmatch_lines([
+        "*AssertionError*Pattern*[123]*not found*",
+    ])
+
+
+class TestFormattedExcinfo(object):
+
+    @pytest.fixture
+    def importasmod(self, request):
+        def importasmod(source):
+            source = _pytest._code.Source(source)
+            tmpdir = request.getfixturevalue("tmpdir")
+            modpath = tmpdir.join("mod.py")
+            tmpdir.ensure("__init__.py")
+            modpath.write(source)
+            if invalidate_import_caches is not None:
+                invalidate_import_caches()
+            return modpath.pyimport()
+        return importasmod
+
+    def excinfo_from_exec(self, source):
+        source = _pytest._code.Source(source).strip()
+        try:
+            exec(source.compile())
+        except KeyboardInterrupt:
+            raise
+        except:  # noqa
+            return _pytest._code.ExceptionInfo()
+        assert 0, "did not raise"
+
+    def test_repr_source(self):
+        pr = FormattedExcinfo()
+        source = _pytest._code.Source("""
+            def f(x):
+                pass
+        """).strip()
+        pr.flow_marker = "|"
+        lines = pr.get_source(source, 0)
+        assert len(lines) == 2
+        assert lines[0] == "|   def f(x):"
+        assert lines[1] == "        pass"
+
+    def test_repr_source_excinfo(self):
+        """ check if indentation is right """
+        pr = FormattedExcinfo()
+        excinfo = self.excinfo_from_exec("""
+                def f():
+                    assert 0
+                f()
+        """)
+        pr = FormattedExcinfo()
+        source = pr._getentrysource(excinfo.traceback[-1])
+        lines = pr.get_source(source, 1, excinfo)
+        assert lines == [
+            '    def f():',
+            '>       assert 0',
+            'E       AssertionError'
+        ]
+
+    def test_repr_source_not_existing(self):
+        pr = FormattedExcinfo()
+        co = compile("raise ValueError()", "", "exec")
+        try:
+            exec(co)
+        except ValueError:
+            excinfo = _pytest._code.ExceptionInfo()
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
+        if py.std.sys.version_info[0] >= 3:
+            assert repr.chain[0][0].reprentries[1].lines[0] == ">   ???"
+
+    def test_repr_many_line_source_not_existing(self):
+        pr = FormattedExcinfo()
+        co = compile("""
+a = 1
+raise ValueError()
+""", "", "exec")
+        try:
+            exec(co)
+        except ValueError:
+            excinfo = _pytest._code.ExceptionInfo()
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
+        if py.std.sys.version_info[0] >= 3:
+            assert repr.chain[0][0].reprentries[1].lines[0] == ">   ???"
+
+    def test_repr_source_failing_fullsource(self):
+        pr = FormattedExcinfo()
+
+        class FakeCode(object):
+            class raw(object):
+                co_filename = '?'
+
+            path = '?'
+            firstlineno = 5
+
+            def fullsource(self):
+                return None
+
+            fullsource = property(fullsource)
+
+        class FakeFrame(object):
+            code = FakeCode()
+            f_locals = {}
+            f_globals = {}
+
+        class FakeTracebackEntry(_pytest._code.Traceback.Entry):
+            def __init__(self, tb, excinfo=None):
+                self.lineno = 5 + 3
+
+            @property
+            def frame(self):
+                return FakeFrame()
+
+        class Traceback(_pytest._code.Traceback):
+            Entry = FakeTracebackEntry
+
+        class FakeExcinfo(_pytest._code.ExceptionInfo):
+            typename = "Foo"
+            value = Exception()
+
+            def __init__(self):
+                pass
+
+            def exconly(self, tryshort):
+                return "EXC"
+
+            def errisinstance(self, cls):
+                return False
+
+        excinfo = FakeExcinfo()
+
+        class FakeRawTB(object):
+            tb_next = None
+
+        tb = FakeRawTB()
+        excinfo.traceback = Traceback(tb)
+
+        fail = IOError()  # noqa
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
+        if py.std.sys.version_info[0] >= 3:
+            assert repr.chain[0][0].reprentries[0].lines[0] == ">   ???"
+
+        fail = py.error.ENOENT  # noqa
+        repr = pr.repr_excinfo(excinfo)
+        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
+        if py.std.sys.version_info[0] >= 3:
+            assert repr.chain[0][0].reprentries[0].lines[0] == ">   ???"
+
+    def test_repr_local(self):
+        p = FormattedExcinfo(showlocals=True)
+        loc = {'y': 5, 'z': 7, 'x': 3, '@x': 2, '__builtins__': {}}
+        reprlocals = p.repr_locals(loc)
+        assert reprlocals.lines
+        assert reprlocals.lines[0] == '__builtins__ = <builtins>'
+        assert reprlocals.lines[1] == 'x          = 3'
+        assert reprlocals.lines[2] == 'y          = 5'
+        assert reprlocals.lines[3] == 'z          = 7'
+
+    def test_repr_tracebackentry_lines(self, importasmod):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello\\nworld")
+        """)
+        excinfo = pytest.raises(ValueError, mod.func1)
+        excinfo.traceback = excinfo.traceback.filter()
+        p = FormattedExcinfo()
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
+
+        # test as intermittent entry
+        lines = reprtb.lines
+        assert lines[0] == '    def func1():'
+        assert lines[1] == '>       raise ValueError("hello\\nworld")'
+
+        # test as last entry
+        p = FormattedExcinfo(showlocals=True)
+        repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        lines = repr_entry.lines
+        assert lines[0] == '    def func1():'
+        assert lines[1] == '>       raise ValueError("hello\\nworld")'
+        assert lines[2] == 'E       ValueError: hello'
+        assert lines[3] == 'E       world'
+        assert not lines[4:]
+
+        loc = repr_entry.reprlocals is not None
+        loc = repr_entry.reprfileloc
+        assert loc.path == mod.__file__
+        assert loc.lineno == 3
+        # assert loc.message == "ValueError: hello"
+
+    def test_repr_tracebackentry_lines2(self, importasmod):
+        mod = importasmod("""
+            def func1(m, x, y, z):
+                raise ValueError("hello\\nworld")
+        """)
+        excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
+        excinfo.traceback = excinfo.traceback.filter()
+        entry = excinfo.traceback[-1]
+        p = FormattedExcinfo(funcargs=True)
+        reprfuncargs = p.repr_args(entry)
+        assert reprfuncargs.args[0] == ('m', repr("m" * 90))
+        assert reprfuncargs.args[1] == ('x', '5')
+        assert reprfuncargs.args[2] == ('y', '13')
+        assert reprfuncargs.args[3] == ('z', repr("z" * 120))
+
+        p = FormattedExcinfo(funcargs=True)
+        repr_entry = p.repr_traceback_entry(entry)
+        assert repr_entry.reprfuncargs.args == reprfuncargs.args
+        tw = TWMock()
+        repr_entry.toterminal(tw)
+        assert tw.lines[0] == "m = " + repr('m' * 90)
+        assert tw.lines[1] == "x = 5, y = 13"
+        assert tw.lines[2] == "z = " + repr('z' * 120)
+
+    def test_repr_tracebackentry_lines_var_kw_args(self, importasmod):
+        mod = importasmod("""
+            def func1(x, *y, **z):
+                raise ValueError("hello\\nworld")
+        """)
+        excinfo = pytest.raises(ValueError, mod.func1, 'a', 'b', c='d')
+        excinfo.traceback = excinfo.traceback.filter()
+        entry = excinfo.traceback[-1]
+        p = FormattedExcinfo(funcargs=True)
+        reprfuncargs = p.repr_args(entry)
+        assert reprfuncargs.args[0] == ('x', repr('a'))
+        assert reprfuncargs.args[1] == ('y', repr(('b',)))
+        assert reprfuncargs.args[2] == ('z', repr({'c': 'd'}))
+
+        p = FormattedExcinfo(funcargs=True)
+        repr_entry = p.repr_traceback_entry(entry)
+        assert repr_entry.reprfuncargs.args == reprfuncargs.args
+        tw = TWMock()
+        repr_entry.toterminal(tw)
+        assert tw.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"
+
+    def test_repr_tracebackentry_short(self, importasmod):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello")
+            def entry():
+                func1()
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+        p = FormattedExcinfo(style="short")
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
+        lines = reprtb.lines
+        basename = py.path.local(mod.__file__).basename
+        assert lines[0] == '    func1()'
+        assert basename in str(reprtb.reprfileloc.path)
+        assert reprtb.reprfileloc.lineno == 5
+
+        # test last entry
+        p = FormattedExcinfo(style="short")
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        lines = reprtb.lines
+        assert lines[0] == '    raise ValueError("hello")'
+        assert lines[1] == 'E   ValueError: hello'
+        assert basename in str(reprtb.reprfileloc.path)
+        assert reprtb.reprfileloc.lineno == 3
+
+    def test_repr_tracebackentry_no(self, importasmod):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello")
+            def entry():
+                func1()
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+        p = FormattedExcinfo(style="no")
+        p.repr_traceback_entry(excinfo.traceback[-2])
+
+        p = FormattedExcinfo(style="no")
+        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        lines = reprentry.lines
+        assert lines[0] == 'E   ValueError: hello'
+        assert not lines[1:]
+
+    def test_repr_traceback_tbfilter(self, importasmod):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+        p = FormattedExcinfo(tbfilter=True)
+        reprtb = p.repr_traceback(excinfo)
+        assert len(reprtb.reprentries) == 2
+        p = FormattedExcinfo(tbfilter=False)
+        reprtb = p.repr_traceback(excinfo)
+        assert len(reprtb.reprentries) == 3
+
+    def test_traceback_short_no_source(self, importasmod, monkeypatch):
+        mod = importasmod("""
+            def func1():
+                raise ValueError("hello")
+            def entry():
+                func1()
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+        from _pytest._code.code import Code
+        monkeypatch.setattr(Code, 'path', 'bogus')
+        excinfo.traceback[0].frame.code.path = "bogus"
+        p = FormattedExcinfo(style="short")
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
+        lines = reprtb.lines
+        last_p = FormattedExcinfo(style="short")
+        last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+        last_lines = last_reprtb.lines
+        monkeypatch.undo()
+        assert lines[0] == '    func1()'
+
+        assert last_lines[0] == '    raise ValueError("hello")'
+        assert last_lines[1] == 'E   ValueError: hello'
+
+    def test_repr_traceback_and_excinfo(self, importasmod):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+
+        for style in ("long", "short"):
+            p = FormattedExcinfo(style=style)
+            reprtb = p.repr_traceback(excinfo)
+            assert len(reprtb.reprentries) == 2
+            assert reprtb.style == style
+            assert not reprtb.extraline
+            repr = p.repr_excinfo(excinfo)
+            assert repr.reprtraceback
+            assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries)
+            if py.std.sys.version_info[0] >= 3:
+                assert repr.chain[0][0]
+                assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries)
+            assert repr.reprcrash.path.endswith("mod.py")
+            assert repr.reprcrash.message == "ValueError: 0"
+
+    def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+
+        p = FormattedExcinfo()
+
+        def raiseos():
+            raise OSError(2)
+
+        monkeypatch.setattr(py.std.os, 'getcwd', raiseos)
+        assert p._makepath(__file__) == __file__
+        p.repr_traceback(excinfo)
+
+    def test_repr_excinfo_addouterr(self, importasmod):
+        mod = importasmod("""
+            def entry():
+                raise ValueError()
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+        repr = excinfo.getrepr()
+        repr.addsection("title", "content")
+        twmock = TWMock()
+        repr.toterminal(twmock)
+        assert twmock.lines[-1] == "content"
+        assert twmock.lines[-2] == ("-", "title")
+
+    def test_repr_excinfo_reprcrash(self, importasmod):
+        mod = importasmod("""
+            def entry():
+                raise ValueError()
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+        repr = excinfo.getrepr()
+        assert repr.reprcrash.path.endswith("mod.py")
+        assert repr.reprcrash.lineno == 3
+        assert repr.reprcrash.message == "ValueError"
+        assert str(repr.reprcrash).endswith("mod.py:3: ValueError")
+
+    def test_repr_traceback_recursion(self, importasmod):
+        mod = importasmod("""
+            def rec2(x):
+                return rec1(x+1)
+            def rec1(x):
+                return rec2(x-1)
+            def entry():
+                rec1(42)
+        """)
+        excinfo = pytest.raises(RuntimeError, mod.entry)
+
+        for style in ("short", "long", "no"):
+            p = FormattedExcinfo(style="short")
+            reprtb = p.repr_traceback(excinfo)
+            assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
+            assert str(reprtb)
+
+    def test_reprexcinfo_getrepr(self, importasmod):
+        mod = importasmod("""
+            def f(x):
+                raise ValueError(x)
+            def entry():
+                f(0)
+        """)
+        excinfo = pytest.raises(ValueError, mod.entry)
+
+        for style in ("short", "long", "no"):
+            for showlocals in (True, False):
+                repr = excinfo.getrepr(style=style, showlocals=showlocals)
+                if py.std.sys.version_info[0] < 3:
+                    assert isinstance(repr, ReprExceptionInfo)
+                assert repr.reprtraceback.style == style
+                if py.std.sys.version_info[0] >= 3:
+                    assert isinstance(repr, ExceptionChainRepr)
+                    for repr in repr.chain:
+                        assert repr[0].style == style
+
+    def test_reprexcinfo_unicode(self):
+        from _pytest._code.code import TerminalRepr
+
+        class MyRepr(TerminalRepr):
+            def toterminal(self, tw):
+                tw.line(py.builtin._totext("я", "utf-8"))
+
+        x = py.builtin._totext(MyRepr())
+        assert x == py.builtin._totext("я", "utf-8")
+
+    def test_toterminal_long(self, importasmod):
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = pytest.raises(ValueError, mod.f)
+        excinfo.traceback = excinfo.traceback.filter()
+        repr = excinfo.getrepr()
+        tw = TWMock()
+        repr.toterminal(tw)
+        assert tw.lines[0] == ""
+        tw.lines.pop(0)
+        assert tw.lines[0] == "    def f():"
+        assert tw.lines[1] == ">       g(3)"
+        assert tw.lines[2] == ""
+        line = tw.get_write_msg(3)
+        assert line.endswith("mod.py")
+        assert tw.lines[4] == (":5: ")
+        assert tw.lines[5] == ("_ ", None)
+        assert tw.lines[6] == ""
+        assert tw.lines[7] == "    def g(x):"
+        assert tw.lines[8] == ">       raise ValueError(x)"
+        assert tw.lines[9] == "E       ValueError: 3"
+        assert tw.lines[10] == ""
+        line = tw.get_write_msg(11)
+        assert line.endswith("mod.py")
+        assert tw.lines[12] == ":3: ValueError"
+
+    def test_toterminal_long_missing_source(self, importasmod, tmpdir):
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = pytest.raises(ValueError, mod.f)
+        tmpdir.join('mod.py').remove()
+        excinfo.traceback = excinfo.traceback.filter()
+        repr = excinfo.getrepr()
+        tw = TWMock()
+        repr.toterminal(tw)
+        assert tw.lines[0] == ""
+        tw.lines.pop(0)
+        assert tw.lines[0] == ">   ???"
+        assert tw.lines[1] == ""
+        line = tw.get_write_msg(2)
+        assert line.endswith("mod.py")
+        assert tw.lines[3] == ":5: "
+        assert tw.lines[4] == ("_ ", None)
+        assert tw.lines[5] == ""
+        assert tw.lines[6] == ">   ???"
+        assert tw.lines[7] == "E   ValueError: 3"
+        assert tw.lines[8] == ""
+        line = tw.get_write_msg(9)
+        assert line.endswith("mod.py")
+        assert tw.lines[10] == ":3: ValueError"
+
+    def test_toterminal_long_incomplete_source(self, importasmod, tmpdir):
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = pytest.raises(ValueError, mod.f)
+        tmpdir.join('mod.py').write('asdf')
+        excinfo.traceback = excinfo.traceback.filter()
+        repr = excinfo.getrepr()
+        tw = TWMock()
+        repr.toterminal(tw)
+        assert tw.lines[0] == ""
+        tw.lines.pop(0)
+        assert tw.lines[0] == ">   ???"
+        assert tw.lines[1] == ""
+        line = tw.get_write_msg(2)
+        assert line.endswith("mod.py")
+        assert tw.lines[3] == ":5: "
+        assert tw.lines[4] == ("_ ", None)
+        assert tw.lines[5] == ""
+        assert tw.lines[6] == ">   ???"
+        assert tw.lines[7] == "E   ValueError: 3"
+        assert tw.lines[8] == ""
+        line = tw.get_write_msg(9)
+        assert line.endswith("mod.py")
+        assert tw.lines[10] == ":3: ValueError"
+
+    def test_toterminal_long_filenames(self, importasmod):
+        mod = importasmod("""
+            def f():
+                raise ValueError()
+        """)
+        excinfo = pytest.raises(ValueError, mod.f)
+        tw = TWMock()
+        path = py.path.local(mod.__file__)
+        old = path.dirpath().chdir()
+        try:
+            repr = excinfo.getrepr(abspath=False)
+            repr.toterminal(tw)
+            x = py.path.local().bestrelpath(path)
+            if len(x) < len(str(path)):
+                msg = tw.get_write_msg(-2)
+                assert msg == "mod.py"
+                assert tw.lines[-1] == ":3: ValueError"
+
+            repr = excinfo.getrepr(abspath=True)
+            repr.toterminal(tw)
+            msg = tw.get_write_msg(-2)
+            assert msg == path
+            line = tw.lines[-1]
+            assert line == ":3: ValueError"
+        finally:
+            old.chdir()
+
+    @pytest.mark.parametrize('reproptions', [
+        {'style': style, 'showlocals': showlocals,
+         'funcargs': funcargs, 'tbfilter': tbfilter
+         } for style in ("long", "short", "no")
+        for showlocals in (True, False)
+        for tbfilter in (True, False)
+        for funcargs in (True, False)])
+    def test_format_excinfo(self, importasmod, reproptions):
+        mod = importasmod("""
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g(3)
+        """)
+        excinfo = pytest.raises(ValueError, mod.f)
+        tw = py.io.TerminalWriter(stringio=True)
+        repr = excinfo.getrepr(**reproptions)
+        repr.toterminal(tw)
+        assert tw.stringio.getvalue()
+
+    def test_traceback_repr_style(self, importasmod):
+        mod = importasmod("""
+            def f():
+                g()
+            def g():
+                h()
+            def h():
+                i()
+            def i():
+                raise ValueError()
+        """)
+        excinfo = pytest.raises(ValueError, mod.f)
+        excinfo.traceback = excinfo.traceback.filter()
+        excinfo.traceback[1].set_repr_style("short")
+        excinfo.traceback[2].set_repr_style("short")
+        r = excinfo.getrepr(style="long")
+        tw = TWMock()
+        r.toterminal(tw)
+        for line in tw.lines:
+            print(line)
+        assert tw.lines[0] == ""
+        assert tw.lines[1] == "    def f():"
+        assert tw.lines[2] == ">       g()"
+        assert tw.lines[3] == ""
+        msg = tw.get_write_msg(4)
+        assert msg.endswith("mod.py")
+        assert tw.lines[5] == ":3: "
+        assert tw.lines[6] == ("_ ", None)
+        tw.get_write_msg(7)
+        assert tw.lines[8].endswith("in g")
+        assert tw.lines[9] == "    h()"
+        tw.get_write_msg(10)
+        assert tw.lines[11].endswith("in h")
+        assert tw.lines[12] == "    i()"
+        assert tw.lines[13] == ("_ ", None)
+        assert tw.lines[14] == ""
+        assert tw.lines[15] == "    def i():"
+        assert tw.lines[16] == ">       raise ValueError()"
+        assert tw.lines[17] == "E       ValueError"
+        assert tw.lines[18] == ""
+        msg = tw.get_write_msg(19)
+        msg.endswith("mod.py")
+        assert tw.lines[20] == ":9: ValueError"
+
+    @pytest.mark.skipif("sys.version_info[0] < 3")
+    def test_exc_chain_repr(self, importasmod):
+        mod = importasmod("""
+            class Err(Exception):
+                pass
+            def f():
+                try:
+                    g()
+                except Exception as e:
+                    raise Err() from e
+                finally:
+                    h()
+            def g():
+                raise ValueError()
+
+            def h():
+                raise AttributeError()
+        """)
+        excinfo = pytest.raises(AttributeError, mod.f)
+        r = excinfo.getrepr(style="long")
+        tw = TWMock()
+        r.toterminal(tw)
+        for line in tw.lines:
+            print(line)
+        assert tw.lines[0] == ""
+        assert tw.lines[1] == "    def f():"
+        assert tw.lines[2] == "        try:"
+        assert tw.lines[3] == ">           g()"
+        assert tw.lines[4] == ""
+        line = tw.get_write_msg(5)
+        assert line.endswith('mod.py')
+        assert tw.lines[6] == ':6: '
+        assert tw.lines[7] == ("_ ", None)
+        assert tw.lines[8] == ""
+        assert tw.lines[9] == "    def g():"
+        assert tw.lines[10] == ">       raise ValueError()"
+        assert tw.lines[11] == "E       ValueError"
+        assert tw.lines[12] == ""
+        line = tw.get_write_msg(13)
+        assert line.endswith('mod.py')
+        assert tw.lines[14] == ':12: ValueError'
+        assert tw.lines[15] == ""
+        assert tw.lines[16] == "The above exception was the direct cause of the following exception:"
+        assert tw.lines[17] == ""
+        assert tw.lines[18] == "    def f():"
+        assert tw.lines[19] == "        try:"
+        assert tw.lines[20] == "            g()"
+        assert tw.lines[21] == "        except Exception as e:"
+        assert tw.lines[22] == ">           raise Err() from e"
+        assert tw.lines[23] == "E           test_exc_chain_repr0.mod.Err"
+        assert tw.lines[24] == ""
+        line = tw.get_write_msg(25)
+        assert line.endswith('mod.py')
+        assert tw.lines[26] == ":8: Err"
+        assert tw.lines[27] == ""
+        assert tw.lines[28] == "During handling of the above exception, another exception occurred:"
+        assert tw.lines[29] == ""
+        assert tw.lines[30] == "    def f():"
+        assert tw.lines[31] == "        try:"
+        assert tw.lines[32] == "            g()"
+        assert tw.lines[33] == "        except Exception as e:"
+        assert tw.lines[34] == "            raise Err() from e"
+        assert tw.lines[35] == "        finally:"
+        assert tw.lines[36] == ">           h()"
+        assert tw.lines[37] == ""
+        line = tw.get_write_msg(38)
+        assert line.endswith('mod.py')
+        assert tw.lines[39] == ":10: "
+        assert tw.lines[40] == ('_ ', None)
+        assert tw.lines[41] == ""
+        assert tw.lines[42] == "    def h():"
+        assert tw.lines[43] == ">       raise AttributeError()"
+        assert tw.lines[44] == "E       AttributeError"
+        assert tw.lines[45] == ""
+        line = tw.get_write_msg(46)
+        assert line.endswith('mod.py')
+        assert tw.lines[47] == ":15: AttributeError"
+
+    @pytest.mark.skipif("sys.version_info[0] < 3")
+    def test_exc_repr_with_raise_from_none_chain_suppression(self, importasmod):
+        mod = importasmod("""
+            def f():
+                try:
+                    g()
+                except Exception:
+                    raise AttributeError() from None
+            def g():
+                raise ValueError()
+        """)
+        excinfo = pytest.raises(AttributeError, mod.f)
+        r = excinfo.getrepr(style="long")
+        tw = TWMock()
+        r.toterminal(tw)
+        for line in tw.lines:
+            print(line)
+        assert tw.lines[0] == ""
+        assert tw.lines[1] == "    def f():"
+        assert tw.lines[2] == "        try:"
+        assert tw.lines[3] == "            g()"
+        assert tw.lines[4] == "        except Exception:"
+        assert tw.lines[5] == ">           raise AttributeError() from None"
+        assert tw.lines[6] == "E           AttributeError"
+        assert tw.lines[7] == ""
+        line = tw.get_write_msg(8)
+        assert line.endswith('mod.py')
+        assert tw.lines[9] == ":6: AttributeError"
+        assert len(tw.lines) == 10
+
+    @pytest.mark.skipif("sys.version_info[0] < 3")
+    @pytest.mark.parametrize('reason, description', [
+        ('cause', 'The above exception was the direct cause of the following exception:'),
+        ('context', 'During handling of the above exception, another exception occurred:'),
+    ])
+    def test_exc_chain_repr_without_traceback(self, importasmod, reason, description):
+        """
+        Handle representation of exception chains where one of the exceptions doesn't have a
+        real traceback, such as those raised in a subprocess submitted by the multiprocessing
+        module (#1984).
+        """
+        from _pytest.pytester import LineMatcher
+        exc_handling_code = ' from e' if reason == 'cause' else ''
+        mod = importasmod("""
+            def f():
+                try:
+                    g()
+                except Exception as e:
+                    raise RuntimeError('runtime problem'){exc_handling_code}
+            def g():
+                raise ValueError('invalid value')
+        """.format(exc_handling_code=exc_handling_code))
+
+        with pytest.raises(RuntimeError) as excinfo:
+            mod.f()
+
+        # emulate the issue described in #1984
+        attr = '__%s__' % reason
+        getattr(excinfo.value, attr).__traceback__ = None
+
+        r = excinfo.getrepr()
+        tw = py.io.TerminalWriter(stringio=True)
+        tw.hasmarkup = False
+        r.toterminal(tw)
+
+        matcher = LineMatcher(tw.stringio.getvalue().splitlines())
+        matcher.fnmatch_lines([
+            "ValueError: invalid value",
+            description,
+            "* except Exception as e:",
+            "> * raise RuntimeError('runtime problem')" + exc_handling_code,
+            "E *RuntimeError: runtime problem",
+        ])
+
+
+@pytest.mark.parametrize("style", ["short", "long"])
+@pytest.mark.parametrize("encoding", [None, "utf8", "utf16"])
+def test_repr_traceback_with_unicode(style, encoding):
+    msg = u'☹'
+    if encoding is not None:
+        msg = msg.encode(encoding)
+    try:
+        raise RuntimeError(msg)
+    except RuntimeError:
+        e_info = ExceptionInfo()
+    formatter = FormattedExcinfo(style=style)
+    repr_traceback = formatter.repr_traceback(e_info)
+    assert repr_traceback is not None
+
+
+def test_cwd_deleted(testdir):
+    testdir.makepyfile("""
+        def test(tmpdir):
+            tmpdir.chdir()
+            tmpdir.remove()
+            assert False
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(['* 1 failed in *'])
+    assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str()
+
+
+def test_exception_repr_extraction_error_on_recursion():
+    """
+    Ensure we can properly detect a recursion error even
+    if some locals raise error on comparision (#2459).
+    """
+    class numpy_like(object):
+
+        def __eq__(self, other):
+            if type(other) is numpy_like:
+                raise ValueError('The truth value of an array '
+                                 'with more than one element is ambiguous.')
+
+    def a(x):
+        return b(numpy_like())
+
+    def b(x):
+        return a(numpy_like())
+
+    try:
+        a(numpy_like())
+    except:  # noqa
+        from _pytest._code.code import ExceptionInfo
+        from _pytest.pytester import LineMatcher
+        exc_info = ExceptionInfo()
+
+        matcher = LineMatcher(str(exc_info.getrepr()).splitlines())
+        matcher.fnmatch_lines([
+            '!!! Recursion error detected, but an error occurred locating the origin of recursion.',
+            '*The following exception happened*',
+            '*ValueError: The truth value of an array*',
+        ])
+
+
+def test_no_recursion_index_on_recursion_error():
+    """
+    Ensure that we don't break in case we can't find the recursion index
+    during a recursion error (#2486).
+    """
+    try:
+        class RecursionDepthError(object):
+            def __getattr__(self, attr):
+                return getattr(self, '_' + attr)
+
+        RecursionDepthError().trigger
+    except:  # noqa
+        from _pytest._code.code import ExceptionInfo
+        exc_info = ExceptionInfo()
+        assert 'maximum recursion' in str(exc_info.getrepr())
+    else:
+        assert 0
diff --git a/tools/third_party/pytest/testing/code/test_source.py b/tools/third_party/pytest/testing/code/test_source.py
new file mode 100644
index 0000000..8eda68a
--- /dev/null
+++ b/tools/third_party/pytest/testing/code/test_source.py
@@ -0,0 +1,680 @@
+# flake8: noqa
+# disable flake check on this file because some constructs are strange
+# or redundant on purpose and can't be disable on a line-by-line basis
+from __future__ import absolute_import, division, print_function
+import sys
+
+import _pytest._code
+import py
+import pytest
+from _pytest._code import Source
+from _pytest._code.source import _ast
+
+if _ast is not None:
+    astonly = pytest.mark.nothing
+else:
+    astonly = pytest.mark.xfail("True", reason="only works with AST-compile")
+
+failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
+
+
+def test_source_str_function():
+    x = Source("3")
+    assert str(x) == "3"
+
+    x = Source("   3")
+    assert str(x) == "3"
+
+    x = Source("""
+        3
+    """, rstrip=False)
+    assert str(x) == "\n3\n    "
+
+    x = Source("""
+        3
+    """, rstrip=True)
+    assert str(x) == "\n3"
+
+
+def test_unicode():
+    try:
+        unicode
+    except NameError:
+        return
+    x = Source(unicode("4"))
+    assert str(x) == "4"
+    co = _pytest._code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval')
+    val = eval(co)
+    assert isinstance(val, unicode)
+
+
+def test_source_from_function():
+    source = _pytest._code.Source(test_source_str_function)
+    assert str(source).startswith('def test_source_str_function():')
+
+
+def test_source_from_method():
+    class TestClass(object):
+        def test_method(self):
+            pass
+    source = _pytest._code.Source(TestClass().test_method)
+    assert source.lines == ["def test_method(self):",
+                            "    pass"]
+
+
+def test_source_from_lines():
+    lines = ["a \n", "b\n", "c"]
+    source = _pytest._code.Source(lines)
+    assert source.lines == ['a ', 'b', 'c']
+
+
+def test_source_from_inner_function():
+    def f():
+        pass
+    source = _pytest._code.Source(f, deindent=False)
+    assert str(source).startswith('    def f():')
+    source = _pytest._code.Source(f)
+    assert str(source).startswith('def f():')
+
+
+def test_source_putaround_simple():
+    source = Source("raise ValueError")
+    source = source.putaround(
+        "try:", """\
+        except ValueError:
+            x = 42
+        else:
+            x = 23""")
+    assert str(source) == """\
+try:
+    raise ValueError
+except ValueError:
+    x = 42
+else:
+    x = 23"""
+
+
+def test_source_putaround():
+    source = Source()
+    source = source.putaround("""
+        if 1:
+            x=1
+    """)
+    assert str(source).strip() == "if 1:\n    x=1"
+
+
+def test_source_strips():
+    source = Source("")
+    assert source == Source()
+    assert str(source) == ''
+    assert source.strip() == source
+
+
+def test_source_strip_multiline():
+    source = Source()
+    source.lines = ["", " hello", "  "]
+    source2 = source.strip()
+    assert source2.lines == [" hello"]
+
+
+def test_syntaxerror_rerepresentation():
+    ex = pytest.raises(SyntaxError, _pytest._code.compile, 'xyz xyz')
+    assert ex.value.lineno == 1
+    assert ex.value.offset in (4, 7)  # XXX pypy/jython versus cpython?
+    assert ex.value.text.strip(), 'x x'
+
+
+def test_isparseable():
+    assert Source("hello").isparseable()
+    assert Source("if 1:\n  pass").isparseable()
+    assert Source(" \nif 1:\n  pass").isparseable()
+    assert not Source("if 1:\n").isparseable()
+    assert not Source(" \nif 1:\npass").isparseable()
+    assert not Source(chr(0)).isparseable()
+
+
+class TestAccesses(object):
+    source = Source("""\
+        def f(x):
+            pass
+        def g(x):
+            pass
+    """)
+
+    def test_getrange(self):
+        x = self.source[0:2]
+        assert x.isparseable()
+        assert len(x.lines) == 2
+        assert str(x) == "def f(x):\n    pass"
+
+    def test_getline(self):
+        x = self.source[0]
+        assert x == "def f(x):"
+
+    def test_len(self):
+        assert len(self.source) == 4
+
+    def test_iter(self):
+        values = [x for x in self.source]
+        assert len(values) == 4
+
+
+class TestSourceParsingAndCompiling(object):
+    source = Source("""\
+        def f(x):
+            assert (x ==
+                    3 +
+                    4)
+    """).strip()
+
+    def test_compile(self):
+        co = _pytest._code.compile("x=3")
+        d = {}
+        exec(co, d)
+        assert d['x'] == 3
+
+    def test_compile_and_getsource_simple(self):
+        co = _pytest._code.compile("x=3")
+        exec(co)
+        source = _pytest._code.Source(co)
+        assert str(source) == "x=3"
+
+    def test_compile_and_getsource_through_same_function(self):
+        def gensource(source):
+            return _pytest._code.compile(source)
+        co1 = gensource("""
+            def f():
+                raise KeyError()
+        """)
+        co2 = gensource("""
+            def f():
+                raise ValueError()
+        """)
+        source1 = py.std.inspect.getsource(co1)
+        assert 'KeyError' in source1
+        source2 = py.std.inspect.getsource(co2)
+        assert 'ValueError' in source2
+
+    def test_getstatement(self):
+        # print str(self.source)
+        ass = str(self.source[1:])
+        for i in range(1, 4):
+            # print "trying start in line %r" % self.source[i]
+            s = self.source.getstatement(i)
+            #x = s.deindent()
+            assert str(s) == ass
+
+    def test_getstatementrange_triple_quoted(self):
+        # print str(self.source)
+        source = Source("""hello('''
+        ''')""")
+        s = source.getstatement(0)
+        assert s == str(source)
+        s = source.getstatement(1)
+        assert s == str(source)
+
+    @astonly
+    def test_getstatementrange_within_constructs(self):
+        source = Source("""\
+            try:
+                try:
+                    raise ValueError
+                except SomeThing:
+                    pass
+            finally:
+                42
+        """)
+        assert len(source) == 7
+        # check all lineno's that could occur in a traceback
+        # assert source.getstatementrange(0) == (0, 7)
+        # assert source.getstatementrange(1) == (1, 5)
+        assert source.getstatementrange(2) == (2, 3)
+        assert source.getstatementrange(3) == (3, 4)
+        assert source.getstatementrange(4) == (4, 5)
+        # assert source.getstatementrange(5) == (0, 7)
+        assert source.getstatementrange(6) == (6, 7)
+
+    def test_getstatementrange_bug(self):
+        source = Source("""\
+            try:
+                x = (
+                   y +
+                   z)
+            except:
+                pass
+        """)
+        assert len(source) == 6
+        assert source.getstatementrange(2) == (1, 4)
+
+    def test_getstatementrange_bug2(self):
+        source = Source("""\
+            assert (
+                33
+                ==
+                [
+                  X(3,
+                      b=1, c=2
+                   ),
+                ]
+              )
+        """)
+        assert len(source) == 9
+        assert source.getstatementrange(5) == (0, 9)
+
+    def test_getstatementrange_ast_issue58(self):
+        source = Source("""\
+
+            def test_some():
+                for a in [a for a in
+                    CAUSE_ERROR]: pass
+
+            x = 3
+        """)
+        assert getstatement(2, source).lines == source.lines[2:3]
+        assert getstatement(3, source).lines == source.lines[3:4]
+
+    def test_getstatementrange_out_of_bounds_py3(self):
+        source = Source("if xxx:\n   from .collections import something")
+        r = source.getstatementrange(1)
+        assert r == (1, 2)
+
+    def test_getstatementrange_with_syntaxerror_issue7(self):
+        source = Source(":")
+        pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
+
+    def test_compile_to_ast(self):
+        import ast
+        source = Source("x = 4")
+        mod = source.compile(flag=ast.PyCF_ONLY_AST)
+        assert isinstance(mod, ast.Module)
+        compile(mod, "<filename>", "exec")
+
+    def test_compile_and_getsource(self):
+        co = self.source.compile()
+        py.builtin.exec_(co, globals())
+        f(7)
+        excinfo = pytest.raises(AssertionError, "f(6)")
+        frame = excinfo.traceback[-1].frame
+        stmt = frame.code.fullsource.getstatement(frame.lineno)
+        # print "block", str(block)
+        assert str(stmt).strip().startswith('assert')
+
+    @pytest.mark.parametrize('name', ['', None, 'my'])
+    def test_compilefuncs_and_path_sanity(self, name):
+        def check(comp, name):
+            co = comp(self.source, name)
+            if not name:
+                expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2)
+            else:
+                expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2)
+            fn = co.co_filename
+            assert fn.endswith(expected)
+
+        mycode = _pytest._code.Code(self.test_compilefuncs_and_path_sanity)
+        mylineno = mycode.firstlineno
+        mypath = mycode.path
+
+        for comp in _pytest._code.compile, _pytest._code.Source.compile:
+            check(comp, name)
+
+    def test_offsetless_synerr(self):
+        pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval')
+
+
+def test_getstartingblock_singleline():
+    class A(object):
+        def __init__(self, *args):
+            frame = sys._getframe(1)
+            self.source = _pytest._code.Frame(frame).statement
+
+    x = A('x', 'y')
+
+    values = [i for i in x.source.lines if i.strip()]
+    assert len(values) == 1
+
+
+def test_getline_finally():
+    def c(): pass
+    excinfo = pytest.raises(TypeError, """
+           teardown = None
+           try:
+                c(1)
+           finally:
+                if teardown:
+                    teardown()
+    """)
+    source = excinfo.traceback[-1].statement
+    assert str(source).strip() == 'c(1)'
+
+
+def test_getfuncsource_dynamic():
+    source = """
+        def f():
+            raise ValueError
+
+        def g(): pass
+    """
+    co = _pytest._code.compile(source)
+    py.builtin.exec_(co, globals())
+    assert str(_pytest._code.Source(f)).strip() == 'def f():\n    raise ValueError'
+    assert str(_pytest._code.Source(g)).strip() == 'def g(): pass'
+
+
+def test_getfuncsource_with_multine_string():
+    def f():
+        c = '''while True:
+    pass
+'''
+    assert str(_pytest._code.Source(f)).strip() == "def f():\n    c = '''while True:\n    pass\n'''"
+
+
+def test_deindent():
+    from _pytest._code.source import deindent as deindent
+    assert deindent(['\tfoo', '\tbar', ]) == ['foo', 'bar']
+
+    def f():
+        c = '''while True:
+    pass
+'''
+    import inspect
+    lines = deindent(inspect.getsource(f).splitlines())
+    assert lines == ["def f():", "    c = '''while True:", "    pass", "'''"]
+
+    source = """
+        def f():
+            def g():
+                pass
+    """
+    lines = deindent(source.splitlines())
+    assert lines == ['', 'def f():', '    def g():', '        pass', '    ']
+
+
+def test_source_of_class_at_eof_without_newline(tmpdir):
+    # this test fails because the implicit inspect.getsource(A) below
+    # does not return the "x = 1" last line.
+    source = _pytest._code.Source('''
+        class A(object):
+            def method(self):
+                x = 1
+    ''')
+    path = tmpdir.join("a.py")
+    path.write(source)
+    s2 = _pytest._code.Source(tmpdir.join("a.py").pyimport().A)
+    assert str(source).strip() == str(s2).strip()
+
+
+if True:
+    def x():
+        pass
+
+
+def test_getsource_fallback():
+    from _pytest._code.source import getsource
+    expected = """def x():
+    pass"""
+    src = getsource(x)
+    assert src == expected
+
+
+def test_idem_compile_and_getsource():
+    from _pytest._code.source import getsource
+    expected = "def x(): pass"
+    co = _pytest._code.compile(expected)
+    src = getsource(co)
+    assert src == expected
+
+
+def test_findsource_fallback():
+    from _pytest._code.source import findsource
+    src, lineno = findsource(x)
+    assert 'test_findsource_simple' in str(src)
+    assert src[lineno] == '    def x():'
+
+
+def test_findsource():
+    from _pytest._code.source import findsource
+    co = _pytest._code.compile("""if 1:
+    def x():
+        pass
+""")
+
+    src, lineno = findsource(co)
+    assert 'if 1:' in str(src)
+
+    d = {}
+    eval(co, d)
+    src, lineno = findsource(d['x'])
+    assert 'if 1:' in str(src)
+    assert src[lineno] == "    def x():"
+
+
+def test_getfslineno():
+    from _pytest._code import getfslineno
+
+    def f(x):
+        pass
+
+    fspath, lineno = getfslineno(f)
+
+    assert fspath.basename == "test_source.py"
+    assert lineno == _pytest._code.getrawcode(f).co_firstlineno - 1  # see findsource
+
+    class A(object):
+        pass
+
+    fspath, lineno = getfslineno(A)
+
+    _, A_lineno = py.std.inspect.findsource(A)
+    assert fspath.basename == "test_source.py"
+    assert lineno == A_lineno
+
+    assert getfslineno(3) == ("", -1)
+
+    class B(object):
+        pass
+    B.__name__ = "B2"
+    assert getfslineno(B)[1] == -1
+
+
+def test_code_of_object_instance_with_call():
+    class A(object):
+        pass
+    pytest.raises(TypeError, lambda: _pytest._code.Source(A()))
+
+    class WithCall(object):
+        def __call__(self):
+            pass
+
+    code = _pytest._code.Code(WithCall())
+    assert 'pass' in str(code.source())
+
+    class Hello(object):
+        def __call__(self):
+            pass
+    pytest.raises(TypeError, lambda: _pytest._code.Code(Hello))
+
+
+def getstatement(lineno, source):
+    from _pytest._code.source import getstatementrange_ast
+    source = _pytest._code.Source(source, deindent=False)
+    ast, start, end = getstatementrange_ast(lineno, source)
+    return source[start:end]
+
+
+def test_oneline():
+    source = getstatement(0, "raise ValueError")
+    assert str(source) == "raise ValueError"
+
+
+def test_comment_and_no_newline_at_end():
+    from _pytest._code.source import getstatementrange_ast
+    source = Source(['def test_basic_complex():',
+                     '    assert 1 == 2',
+                     '# vim: filetype=pyopencl:fdm=marker'])
+    ast, start, end = getstatementrange_ast(1, source)
+    assert end == 2
+
+
+def test_oneline_and_comment():
+    source = getstatement(0, "raise ValueError\n#hello")
+    assert str(source) == "raise ValueError"
+
+
+@pytest.mark.xfail(hasattr(sys, "pypy_version_info"),
+                   reason='does not work on pypy')
+def test_comments():
+    source = '''def test():
+    "comment 1"
+    x = 1
+      # comment 2
+    # comment 3
+
+    assert False
+
+"""
+comment 4
+"""
+'''
+    for line in range(2, 6):
+        assert str(getstatement(line, source)) == '    x = 1'
+    for line in range(6, 10):
+        assert str(getstatement(line, source)) == '    assert False'
+    assert str(getstatement(10, source)) == '"""'
+
+
+def test_comment_in_statement():
+    source = '''test(foo=1,
+    # comment 1
+    bar=2)
+'''
+    for line in range(1, 3):
+        assert str(getstatement(line, source)) == \
+            'test(foo=1,\n    # comment 1\n    bar=2)'
+
+
+def test_single_line_else():
+    source = getstatement(1, "if False: 2\nelse: 3")
+    assert str(source) == "else: 3"
+
+
+def test_single_line_finally():
+    source = getstatement(1, "try: 1\nfinally: 3")
+    assert str(source) == "finally: 3"
+
+
+def test_issue55():
+    source = ('def round_trip(dinp):\n  assert 1 == dinp\n'
+              'def test_rt():\n  round_trip("""\n""")\n')
+    s = getstatement(3, source)
+    assert str(s) == '  round_trip("""\n""")'
+
+
+def XXXtest_multiline():
+    source = getstatement(0, """\
+raise ValueError(
+    23
+)
+x = 3
+""")
+    assert str(source) == "raise ValueError(\n    23\n)"
+
+
+class TestTry(object):
+    pytestmark = astonly
+    source = """\
+try:
+    raise ValueError
+except Something:
+    raise IndexError(1)
+else:
+    raise KeyError()
+"""
+
+    def test_body(self):
+        source = getstatement(1, self.source)
+        assert str(source) == "    raise ValueError"
+
+    def test_except_line(self):
+        source = getstatement(2, self.source)
+        assert str(source) == "except Something:"
+
+    def test_except_body(self):
+        source = getstatement(3, self.source)
+        assert str(source) == "    raise IndexError(1)"
+
+    def test_else(self):
+        source = getstatement(5, self.source)
+        assert str(source) == "    raise KeyError()"
+
+
+class TestTryFinally(object):
+    source = """\
+try:
+    raise ValueError
+finally:
+    raise IndexError(1)
+"""
+
+    def test_body(self):
+        source = getstatement(1, self.source)
+        assert str(source) == "    raise ValueError"
+
+    def test_finally(self):
+        source = getstatement(3, self.source)
+        assert str(source) == "    raise IndexError(1)"
+
+
+class TestIf(object):
+    pytestmark = astonly
+    source = """\
+if 1:
+    y = 3
+elif False:
+    y = 5
+else:
+    y = 7
+"""
+
+    def test_body(self):
+        source = getstatement(1, self.source)
+        assert str(source) == "    y = 3"
+
+    def test_elif_clause(self):
+        source = getstatement(2, self.source)
+        assert str(source) == "elif False:"
+
+    def test_elif(self):
+        source = getstatement(3, self.source)
+        assert str(source) == "    y = 5"
+
+    def test_else(self):
+        source = getstatement(5, self.source)
+        assert str(source) == "    y = 7"
+
+
+def test_semicolon():
+    s = """\
+hello ; pytest.skip()
+"""
+    source = getstatement(0, s)
+    assert str(source) == s.strip()
+
+
+def test_def_online():
+    s = """\
+def func(): raise ValueError(42)
+
+def something():
+    pass
+"""
+    source = getstatement(0, s)
+    assert str(source) == "def func(): raise ValueError(42)"
+
+
+def XXX_test_expression_multiline():
+    source = """\
+something
+'''
+'''"""
+    result = getstatement(1, source)
+    assert str(result) == "'''\n'''"
diff --git a/tools/third_party/pytest/testing/code/test_source_multiline_block.py b/tools/third_party/pytest/testing/code/test_source_multiline_block.py
new file mode 100644
index 0000000..b356d19
--- /dev/null
+++ b/tools/third_party/pytest/testing/code/test_source_multiline_block.py
@@ -0,0 +1,26 @@
+# flake8: noqa
+import sys
+
+import _pytest._code
+
+
+def test_getstartingblock_multiline():
+    """
+    This test was originally found in test_source.py, but it depends on the weird
+    formatting of the ``x = A`` construct seen here and our autopep8 tool can only exclude entire
+    files (it does not support excluding lines/blocks using the traditional #noqa comment yet,
+    see hhatto/autopep8#307). It was considered better to just move this single test to its own
+    file and exclude it from autopep8 than try to complicate things.
+    """
+    class A(object):
+        def __init__(self, *args):
+            frame = sys._getframe(1)
+            self.source = _pytest._code.Frame(frame).statement
+
+    x = A('x',
+          'y'
+          ,
+          'z')
+
+    values = [i for i in x.source.lines if i.strip()]
+    assert len(values) == 4
diff --git a/tools/third_party/pytest/testing/deprecated_test.py b/tools/third_party/pytest/testing/deprecated_test.py
new file mode 100644
index 0000000..11c4ad4
--- /dev/null
+++ b/tools/third_party/pytest/testing/deprecated_test.py
@@ -0,0 +1,127 @@
+from __future__ import absolute_import, division, print_function
+import pytest
+
+
+def test_yield_tests_deprecation(testdir):
+    testdir.makepyfile("""
+        def func1(arg, arg2):
+            assert arg == arg2
+        def test_gen():
+            yield "m1", func1, 15, 3*5
+            yield "m2", func1, 42, 6*7
+        def test_gen2():
+            for k in range(10):
+                yield func1, 1, 1
+    """)
+    result = testdir.runpytest('-ra')
+    result.stdout.fnmatch_lines([
+        '*yield tests are deprecated, and scheduled to be removed in pytest 4.0*',
+        '*2 passed*',
+    ])
+    assert result.stdout.str().count('yield tests are deprecated') == 2
+
+
+def test_funcarg_prefix_deprecation(testdir):
+    testdir.makepyfile("""
+        def pytest_funcarg__value():
+            return 10
+
+        def test_funcarg_prefix(value):
+            assert value == 10
+    """)
+    result = testdir.runpytest('-ra')
+    result.stdout.fnmatch_lines([
+        ('*pytest_funcarg__value: '
+         'declaring fixtures using "pytest_funcarg__" prefix is deprecated '
+         'and scheduled to be removed in pytest 4.0.  '
+         'Please remove the prefix and use the @pytest.fixture decorator instead.'),
+        '*1 passed*',
+    ])
+
+
+def test_pytest_setup_cfg_deprecated(testdir):
+    testdir.makefile('.cfg', setup='''
+        [pytest]
+        addopts = --verbose
+    ''')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(['*pytest*section in setup.cfg files is deprecated*use*tool:pytest*instead*'])
+
+
+def test_str_args_deprecated(tmpdir, testdir):
+    """Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
+    from _pytest.main import EXIT_NOTESTSCOLLECTED
+    warnings = []
+
+    class Collect(object):
+        def pytest_logwarning(self, message):
+            warnings.append(message)
+
+    ret = pytest.main("%s -x" % tmpdir, plugins=[Collect()])
+    testdir.delete_loaded_modules()
+    msg = ('passing a string to pytest.main() is deprecated, '
+           'pass a list of arguments instead.')
+    assert msg in warnings
+    assert ret == EXIT_NOTESTSCOLLECTED
+
+
+def test_getfuncargvalue_is_deprecated(request):
+    pytest.deprecated_call(request.getfuncargvalue, 'tmpdir')
+
+
+def test_resultlog_is_deprecated(testdir):
+    result = testdir.runpytest('--help')
+    result.stdout.fnmatch_lines(['*DEPRECATED path for machine-readable result log*'])
+
+    testdir.makepyfile('''
+        def test():
+            pass
+    ''')
+    result = testdir.runpytest('--result-log=%s' % testdir.tmpdir.join('result.log'))
+    result.stdout.fnmatch_lines([
+        '*--result-log is deprecated and scheduled for removal in pytest 4.0*',
+        '*See https://docs.pytest.org/*/usage.html#creating-resultlog-format-files for more information*',
+    ])
+
+
+@pytest.mark.filterwarnings('always:Metafunc.addcall is deprecated')
+def test_metafunc_addcall_deprecated(testdir):
+    testdir.makepyfile("""
+        def pytest_generate_tests(metafunc):
+            metafunc.addcall({'i': 1})
+            metafunc.addcall({'i': 2})
+        def test_func(i):
+            pass
+    """)
+    res = testdir.runpytest('-s')
+    assert res.ret == 0
+    res.stdout.fnmatch_lines([
+        "*Metafunc.addcall is deprecated*",
+        "*2 passed, 2 warnings*",
+    ])
+
+
+def test_terminal_reporter_writer_attr(pytestconfig):
+    """Check that TerminalReporter._tw is also available as 'writer' (#2984)
+    This attribute is planned to be deprecated in 3.4.
+    """
+    try:
+        import xdist  # noqa
+        pytest.skip('xdist workers disable the terminal reporter plugin')
+    except ImportError:
+        pass
+    terminal_reporter = pytestconfig.pluginmanager.get_plugin('terminalreporter')
+    assert terminal_reporter.writer is terminal_reporter._tw
+
+
+def test_pytest_catchlog_deprecated(testdir):
+    testdir.makepyfile("""
+        def test_func(pytestconfig):
+            pytestconfig.pluginmanager.register(None, 'pytest_catchlog')
+    """)
+    res = testdir.runpytest()
+    assert res.ret == 0
+    res.stdout.fnmatch_lines([
+        "*pytest-catchlog plugin has been merged into the core*",
+        "*1 passed, 1 warnings*",
+    ])
diff --git a/tools/third_party/pytest/testing/freeze/.gitignore b/tools/third_party/pytest/testing/freeze/.gitignore
new file mode 100644
index 0000000..490310b
--- /dev/null
+++ b/tools/third_party/pytest/testing/freeze/.gitignore
@@ -0,0 +1,3 @@
+build/
+dist/
+*.spec
\ No newline at end of file
diff --git a/tools/third_party/pytest/testing/freeze/create_executable.py b/tools/third_party/pytest/testing/freeze/create_executable.py
new file mode 100644
index 0000000..f4f6088
--- /dev/null
+++ b/tools/third_party/pytest/testing/freeze/create_executable.py
@@ -0,0 +1,12 @@
+"""
+Generates an executable with pytest runner embedded using PyInstaller.
+"""
+if __name__ == '__main__':
+    import pytest
+    import subprocess
+
+    hidden = []
+    for x in pytest.freeze_includes():
+        hidden.extend(['--hidden-import', x])
+    args = ['pyinstaller', '--noconfirm'] + hidden + ['runtests_script.py']
+    subprocess.check_call(' '.join(args), shell=True)
diff --git a/tools/third_party/pytest/testing/freeze/runtests_script.py b/tools/third_party/pytest/testing/freeze/runtests_script.py
new file mode 100644
index 0000000..d281601
--- /dev/null
+++ b/tools/third_party/pytest/testing/freeze/runtests_script.py
@@ -0,0 +1,9 @@
+"""
+This is the script that is actually frozen into an executable: simply executes
+py.test main().
+"""
+
+if __name__ == '__main__':
+    import sys
+    import pytest
+    sys.exit(pytest.main())
diff --git a/tools/pytest/testing/cx_freeze/tests/test_doctest.txt b/tools/third_party/pytest/testing/freeze/tests/test_doctest.txt
similarity index 100%
rename from tools/pytest/testing/cx_freeze/tests/test_doctest.txt
rename to tools/third_party/pytest/testing/freeze/tests/test_doctest.txt
diff --git a/tools/third_party/pytest/testing/freeze/tests/test_trivial.py b/tools/third_party/pytest/testing/freeze/tests/test_trivial.py
new file mode 100644
index 0000000..45622b8
--- /dev/null
+++ b/tools/third_party/pytest/testing/freeze/tests/test_trivial.py
@@ -0,0 +1,7 @@
+
+def test_upper():
+    assert 'foo'.upper() == 'FOO'
+
+
+def test_lower():
+    assert 'FOO'.lower() == 'foo'
diff --git a/tools/third_party/pytest/testing/freeze/tox_run.py b/tools/third_party/pytest/testing/freeze/tox_run.py
new file mode 100644
index 0000000..3fc3880
--- /dev/null
+++ b/tools/third_party/pytest/testing/freeze/tox_run.py
@@ -0,0 +1,12 @@
+"""
+Called by tox.ini: uses the generated executable to run the tests in ./tests/
+directory.
+"""
+if __name__ == '__main__':
+    import os
+    import sys
+
+    executable = os.path.join(os.getcwd(), 'dist', 'runtests_script', 'runtests_script')
+    if sys.platform.startswith('win'):
+        executable += '.exe'
+    sys.exit(os.system('%s tests' % executable))
diff --git a/tools/third_party/pytest/testing/logging/test_fixture.py b/tools/third_party/pytest/testing/logging/test_fixture.py
new file mode 100644
index 0000000..c27b311
--- /dev/null
+++ b/tools/third_party/pytest/testing/logging/test_fixture.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+import logging
+
+
+logger = logging.getLogger(__name__)
+sublogger = logging.getLogger(__name__ + '.baz')
+
+
+def test_fixture_help(testdir):
+    result = testdir.runpytest('--fixtures')
+    result.stdout.fnmatch_lines(['*caplog*'])
+
+
+def test_change_level(caplog):
+    caplog.set_level(logging.INFO)
+    logger.debug('handler DEBUG level')
+    logger.info('handler INFO level')
+
+    caplog.set_level(logging.CRITICAL, logger=sublogger.name)
+    sublogger.warning('logger WARNING level')
+    sublogger.critical('logger CRITICAL level')
+
+    assert 'DEBUG' not in caplog.text
+    assert 'INFO' in caplog.text
+    assert 'WARNING' not in caplog.text
+    assert 'CRITICAL' in caplog.text
+
+
+def test_with_statement(caplog):
+    with caplog.at_level(logging.INFO):
+        logger.debug('handler DEBUG level')
+        logger.info('handler INFO level')
+
+        with caplog.at_level(logging.CRITICAL, logger=sublogger.name):
+            sublogger.warning('logger WARNING level')
+            sublogger.critical('logger CRITICAL level')
+
+    assert 'DEBUG' not in caplog.text
+    assert 'INFO' in caplog.text
+    assert 'WARNING' not in caplog.text
+    assert 'CRITICAL' in caplog.text
+
+
+def test_log_access(caplog):
+    logger.info('boo %s', 'arg')
+    assert caplog.records[0].levelname == 'INFO'
+    assert caplog.records[0].msg == 'boo %s'
+    assert 'boo arg' in caplog.text
+
+
+def test_record_tuples(caplog):
+    logger.info('boo %s', 'arg')
+
+    assert caplog.record_tuples == [
+        (__name__, logging.INFO, 'boo arg'),
+    ]
+
+
+def test_unicode(caplog):
+    logger.info(u'bū')
+    assert caplog.records[0].levelname == 'INFO'
+    assert caplog.records[0].msg == u'bū'
+    assert u'bū' in caplog.text
+
+
+def test_clear(caplog):
+    logger.info(u'bū')
+    assert len(caplog.records)
+    caplog.clear()
+    assert not len(caplog.records)
diff --git a/tools/third_party/pytest/testing/logging/test_reporting.py b/tools/third_party/pytest/testing/logging/test_reporting.py
new file mode 100644
index 0000000..c02ee21
--- /dev/null
+++ b/tools/third_party/pytest/testing/logging/test_reporting.py
@@ -0,0 +1,398 @@
+# -*- coding: utf-8 -*-
+import os
+import pytest
+
+
+def test_nothing_logged(testdir):
+    testdir.makepyfile('''
+        import sys
+
+        def test_foo():
+            sys.stdout.write('text going to stdout')
+            sys.stderr.write('text going to stderr')
+            assert False
+        ''')
+    result = testdir.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
+                                 'text going to stdout'])
+    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
+                                 'text going to stderr'])
+    with pytest.raises(pytest.fail.Exception):
+        result.stdout.fnmatch_lines(['*- Captured *log call -*'])
+
+
+def test_messages_logged(testdir):
+    testdir.makepyfile('''
+        import sys
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            sys.stdout.write('text going to stdout')
+            sys.stderr.write('text going to stderr')
+            logger.info('text going to logger')
+            assert False
+        ''')
+    result = testdir.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured *log call -*',
+                                 '*text going to logger*'])
+    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
+                                 'text going to stdout'])
+    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
+                                 'text going to stderr'])
+
+
+def test_setup_logging(testdir):
+    testdir.makepyfile('''
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def setup_function(function):
+            logger.info('text going to logger from setup')
+
+        def test_foo():
+            logger.info('text going to logger from call')
+            assert False
+        ''')
+    result = testdir.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured *log setup -*',
+                                 '*text going to logger from setup*',
+                                 '*- Captured *log call -*',
+                                 '*text going to logger from call*'])
+
+
+def test_teardown_logging(testdir):
+    testdir.makepyfile('''
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            logger.info('text going to logger from call')
+
+        def teardown_function(function):
+            logger.info('text going to logger from teardown')
+            assert False
+        ''')
+    result = testdir.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured *log call -*',
+                                 '*text going to logger from call*',
+                                 '*- Captured *log teardown -*',
+                                 '*text going to logger from teardown*'])
+
+
+def test_disable_log_capturing(testdir):
+    testdir.makepyfile('''
+        import sys
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            sys.stdout.write('text going to stdout')
+            logger.warning('catch me if you can!')
+            sys.stderr.write('text going to stderr')
+            assert False
+        ''')
+    result = testdir.runpytest('--no-print-logs')
+    print(result.stdout)
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
+                                 'text going to stdout'])
+    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
+                                 'text going to stderr'])
+    with pytest.raises(pytest.fail.Exception):
+        result.stdout.fnmatch_lines(['*- Captured *log call -*'])
+
+
+def test_disable_log_capturing_ini(testdir):
+    testdir.makeini(
+        '''
+        [pytest]
+        log_print=False
+        '''
+    )
+    testdir.makepyfile('''
+        import sys
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            sys.stdout.write('text going to stdout')
+            logger.warning('catch me if you can!')
+            sys.stderr.write('text going to stderr')
+            assert False
+        ''')
+    result = testdir.runpytest()
+    print(result.stdout)
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(['*- Captured stdout call -*',
+                                 'text going to stdout'])
+    result.stdout.fnmatch_lines(['*- Captured stderr call -*',
+                                 'text going to stderr'])
+    with pytest.raises(pytest.fail.Exception):
+        result.stdout.fnmatch_lines(['*- Captured *log call -*'])
+
+
+def test_log_cli_default_level(testdir):
+    # Default log file level
+    testdir.makepyfile('''
+        import pytest
+        import logging
+        def test_log_cli(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_cli_handler.level == logging.WARNING
+            logging.getLogger('catchlog').info("This log message won't be shown")
+            logging.getLogger('catchlog').warning("This log message will be shown")
+            print('PASSED')
+    ''')
+
+    result = testdir.runpytest('-s')
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_cli_default_level.py PASSED',
+    ])
+    result.stderr.fnmatch_lines([
+        "* This log message will be shown"
+    ])
+    for line in result.errlines:
+        try:
+            assert "This log message won't be shown" in line
+            pytest.fail("A log message was shown and it shouldn't have been")
+        except AssertionError:
+            continue
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+
+
+def test_log_cli_level(testdir):
+    # Default log file level
+    testdir.makepyfile('''
+        import pytest
+        import logging
+        def test_log_cli(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_cli_handler.level == logging.INFO
+            logging.getLogger('catchlog').debug("This log message won't be shown")
+            logging.getLogger('catchlog').info("This log message will be shown")
+            print('PASSED')
+    ''')
+
+    result = testdir.runpytest('-s', '--log-cli-level=INFO')
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_cli_level.py PASSED',
+    ])
+    result.stderr.fnmatch_lines([
+        "* This log message will be shown"
+    ])
+    for line in result.errlines:
+        try:
+            assert "This log message won't be shown" in line
+            pytest.fail("A log message was shown and it shouldn't have been")
+        except AssertionError:
+            continue
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+
+    result = testdir.runpytest('-s', '--log-level=INFO')
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_cli_level.py PASSED',
+    ])
+    result.stderr.fnmatch_lines([
+        "* This log message will be shown"
+    ])
+    for line in result.errlines:
+        try:
+            assert "This log message won't be shown" in line
+            pytest.fail("A log message was shown and it shouldn't have been")
+        except AssertionError:
+            continue
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+
+
+def test_log_cli_ini_level(testdir):
+    testdir.makeini(
+        """
+        [pytest]
+        log_cli_level = INFO
+        """)
+    testdir.makepyfile('''
+        import pytest
+        import logging
+        def test_log_cli(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_cli_handler.level == logging.INFO
+            logging.getLogger('catchlog').debug("This log message won't be shown")
+            logging.getLogger('catchlog').info("This log message will be shown")
+            print('PASSED')
+    ''')
+
+    result = testdir.runpytest('-s')
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_cli_ini_level.py PASSED',
+    ])
+    result.stderr.fnmatch_lines([
+        "* This log message will be shown"
+    ])
+    for line in result.errlines:
+        try:
+            assert "This log message won't be shown" in line
+            pytest.fail("A log message was shown and it shouldn't have been")
+        except AssertionError:
+            continue
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+
+
+def test_log_file_cli(testdir):
+    # Default log file level
+    testdir.makepyfile('''
+        import pytest
+        import logging
+        def test_log_file(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_file_handler.level == logging.WARNING
+            logging.getLogger('catchlog').info("This log message won't be shown")
+            logging.getLogger('catchlog').warning("This log message will be shown")
+            print('PASSED')
+    ''')
+
+    log_file = testdir.tmpdir.join('pytest.log').strpath
+
+    result = testdir.runpytest('-s', '--log-file={0}'.format(log_file))
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_file_cli.py PASSED',
+    ])
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+    assert os.path.isfile(log_file)
+    with open(log_file) as rfh:
+        contents = rfh.read()
+        assert "This log message will be shown" in contents
+        assert "This log message won't be shown" not in contents
+
+
+def test_log_file_cli_level(testdir):
+    # Default log file level
+    testdir.makepyfile('''
+        import pytest
+        import logging
+        def test_log_file(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_file_handler.level == logging.INFO
+            logging.getLogger('catchlog').debug("This log message won't be shown")
+            logging.getLogger('catchlog').info("This log message will be shown")
+            print('PASSED')
+    ''')
+
+    log_file = testdir.tmpdir.join('pytest.log').strpath
+
+    result = testdir.runpytest('-s',
+                               '--log-file={0}'.format(log_file),
+                               '--log-file-level=INFO')
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_file_cli_level.py PASSED',
+    ])
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+    assert os.path.isfile(log_file)
+    with open(log_file) as rfh:
+        contents = rfh.read()
+        assert "This log message will be shown" in contents
+        assert "This log message won't be shown" not in contents
+
+
+def test_log_file_ini(testdir):
+    log_file = testdir.tmpdir.join('pytest.log').strpath
+
+    testdir.makeini(
+        """
+        [pytest]
+        log_file={0}
+        """.format(log_file))
+    testdir.makepyfile('''
+        import pytest
+        import logging
+        def test_log_file(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_file_handler.level == logging.WARNING
+            logging.getLogger('catchlog').info("This log message won't be shown")
+            logging.getLogger('catchlog').warning("This log message will be shown")
+            print('PASSED')
+    ''')
+
+    result = testdir.runpytest('-s')
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_file_ini.py PASSED',
+    ])
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+    assert os.path.isfile(log_file)
+    with open(log_file) as rfh:
+        contents = rfh.read()
+        assert "This log message will be shown" in contents
+        assert "This log message won't be shown" not in contents
+
+
+def test_log_file_ini_level(testdir):
+    log_file = testdir.tmpdir.join('pytest.log').strpath
+
+    testdir.makeini(
+        """
+        [pytest]
+        log_file={0}
+        log_file_level = INFO
+        """.format(log_file))
+    testdir.makepyfile('''
+        import pytest
+        import logging
+        def test_log_file(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_file_handler.level == logging.INFO
+            logging.getLogger('catchlog').debug("This log message won't be shown")
+            logging.getLogger('catchlog').info("This log message will be shown")
+            print('PASSED')
+    ''')
+
+    result = testdir.runpytest('-s')
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines([
+        'test_log_file_ini_level.py PASSED',
+    ])
+
+    # make sure that that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+    assert os.path.isfile(log_file)
+    with open(log_file) as rfh:
+        contents = rfh.read()
+        assert "This log message will be shown" in contents
+        assert "This log message won't be shown" not in contents
diff --git a/tools/third_party/pytest/testing/python/approx.py b/tools/third_party/pytest/testing/python/approx.py
new file mode 100644
index 0000000..300e1ce
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/approx.py
@@ -0,0 +1,392 @@
+# encoding: utf-8
+import operator
+import sys
+import pytest
+import doctest
+
+from pytest import approx
+from operator import eq, ne
+from decimal import Decimal
+from fractions import Fraction
+inf, nan = float('inf'), float('nan')
+
+
+class MyDocTestRunner(doctest.DocTestRunner):
+
+    def __init__(self):
+        doctest.DocTestRunner.__init__(self)
+
+    def report_failure(self, out, test, example, got):
+        raise AssertionError("'{}' evaluates to '{}', not '{}'".format(
+            example.source.strip(), got.strip(), example.want.strip()))
+
+
+class TestApprox(object):
+
+    def test_repr_string(self):
+        plus_minus = u'\u00b1' if sys.version_info[0] > 2 else u'+-'
+        tol1, tol2, infr = '1.0e-06', '2.0e-06', 'inf'
+        assert repr(approx(1.0)) == '1.0 {pm} {tol1}'.format(pm=plus_minus, tol1=tol1)
+        assert repr(approx([1.0, 2.0])) == 'approx([1.0 {pm} {tol1}, 2.0 {pm} {tol2}])'.format(
+            pm=plus_minus, tol1=tol1, tol2=tol2)
+        assert repr(approx((1.0, 2.0))) == 'approx((1.0 {pm} {tol1}, 2.0 {pm} {tol2}))'.format(
+            pm=plus_minus, tol1=tol1, tol2=tol2)
+        assert repr(approx(inf)) == 'inf'
+        assert repr(approx(1.0, rel=nan)) == '1.0 {pm} ???'.format(pm=plus_minus)
+        assert repr(approx(1.0, rel=inf)) == '1.0 {pm} {infr}'.format(pm=plus_minus, infr=infr)
+        assert repr(approx(1.0j, rel=inf)) == '1j'
+
+        # Dictionaries aren't ordered, so we need to check both orders.
+        assert repr(approx({'a': 1.0, 'b': 2.0})) in (
+            "approx({{'a': 1.0 {pm} {tol1}, 'b': 2.0 {pm} {tol2}}})".format(pm=plus_minus, tol1=tol1, tol2=tol2),
+            "approx({{'b': 2.0 {pm} {tol2}, 'a': 1.0 {pm} {tol1}}})".format(pm=plus_minus, tol1=tol1, tol2=tol2),
+        )
+
+    def test_operator_overloading(self):
+        assert 1 == approx(1, rel=1e-6, abs=1e-12)
+        assert not (1 != approx(1, rel=1e-6, abs=1e-12))
+        assert 10 != approx(1, rel=1e-6, abs=1e-12)
+        assert not (10 == approx(1, rel=1e-6, abs=1e-12))
+
+    def test_exactly_equal(self):
+        examples = [
+            (2.0, 2.0),
+            (0.1e200, 0.1e200),
+            (1.123e-300, 1.123e-300),
+            (12345, 12345.0),
+            (0.0, -0.0),
+            (345678, 345678),
+            (Decimal('1.0001'), Decimal('1.0001')),
+            (Fraction(1, 3), Fraction(-1, -3)),
+        ]
+        for a, x in examples:
+            assert a == approx(x)
+
+    def test_opposite_sign(self):
+        examples = [
+            (eq, 1e-100, -1e-100),
+            (ne, 1e100, -1e100),
+        ]
+        for op, a, x in examples:
+            assert op(a, approx(x))
+
+    def test_zero_tolerance(self):
+        within_1e10 = [
+            (1.1e-100, 1e-100),
+            (-1.1e-100, -1e-100),
+        ]
+        for a, x in within_1e10:
+            assert x == approx(x, rel=0.0, abs=0.0)
+            assert a != approx(x, rel=0.0, abs=0.0)
+            assert a == approx(x, rel=0.0, abs=5e-101)
+            assert a != approx(x, rel=0.0, abs=5e-102)
+            assert a == approx(x, rel=5e-1, abs=0.0)
+            assert a != approx(x, rel=5e-2, abs=0.0)
+
+    def test_negative_tolerance(self):
+        # Negative tolerances are not allowed.
+        illegal_kwargs = [
+            dict(rel=-1e100),
+            dict(abs=-1e100),
+            dict(rel=1e100, abs=-1e100),
+            dict(rel=-1e100, abs=1e100),
+            dict(rel=-1e100, abs=-1e100),
+        ]
+        for kwargs in illegal_kwargs:
+            with pytest.raises(ValueError):
+                1.1 == approx(1, **kwargs)
+
+    def test_inf_tolerance(self):
+        # Everything should be equal if the tolerance is infinite.
+        large_diffs = [
+            (1, 1000),
+            (1e-50, 1e50),
+            (-1.0, -1e300),
+            (0.0, 10),
+        ]
+        for a, x in large_diffs:
+            assert a != approx(x, rel=0.0, abs=0.0)
+            assert a == approx(x, rel=inf, abs=0.0)
+            assert a == approx(x, rel=0.0, abs=inf)
+            assert a == approx(x, rel=inf, abs=inf)
+
+    def test_inf_tolerance_expecting_zero(self):
+        # If the relative tolerance is zero but the expected value is infinite,
+        # the actual tolerance is a NaN, which should be an error.
+        illegal_kwargs = [
+            dict(rel=inf, abs=0.0),
+            dict(rel=inf, abs=inf),
+        ]
+        for kwargs in illegal_kwargs:
+            with pytest.raises(ValueError):
+                1 == approx(0, **kwargs)
+
+    def test_nan_tolerance(self):
+        illegal_kwargs = [
+            dict(rel=nan),
+            dict(abs=nan),
+            dict(rel=nan, abs=nan),
+        ]
+        for kwargs in illegal_kwargs:
+            with pytest.raises(ValueError):
+                1.1 == approx(1, **kwargs)
+
+    def test_reasonable_defaults(self):
+        # Whatever the defaults are, they should work for numbers close to 1
+        # than have a small amount of floating-point error.
+        assert 0.1 + 0.2 == approx(0.3)
+
+    def test_default_tolerances(self):
+        # This tests the defaults as they are currently set.  If you change the
+        # defaults, this test will fail but you should feel free to change it.
+        # None of the other tests (except the doctests) should be affected by
+        # the choice of defaults.
+        examples = [
+            # Relative tolerance used.
+            (eq, 1e100 + 1e94, 1e100),
+            (ne, 1e100 + 2e94, 1e100),
+            (eq, 1e0 + 1e-6, 1e0),
+            (ne, 1e0 + 2e-6, 1e0),
+            # Absolute tolerance used.
+            (eq, 1e-100, + 1e-106),
+            (eq, 1e-100, + 2e-106),
+            (eq, 1e-100, 0),
+        ]
+        for op, a, x in examples:
+            assert op(a, approx(x))
+
+    def test_custom_tolerances(self):
+        assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e0)
+        assert 1e8 + 1e0 == approx(1e8, rel=5e-9, abs=5e0)
+        assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e-1)
+        assert 1e8 + 1e0 != approx(1e8, rel=5e-9, abs=5e-1)
+
+        assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-8)
+        assert 1e0 + 1e-8 == approx(1e0, rel=5e-9, abs=5e-8)
+        assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-9)
+        assert 1e0 + 1e-8 != approx(1e0, rel=5e-9, abs=5e-9)
+
+        assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-16)
+        assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-9, abs=5e-16)
+        assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-17)
+        assert 1e-8 + 1e-16 != approx(1e-8, rel=5e-9, abs=5e-17)
+
+    def test_relative_tolerance(self):
+        within_1e8_rel = [
+            (1e8 + 1e0, 1e8),
+            (1e0 + 1e-8, 1e0),
+            (1e-8 + 1e-16, 1e-8),
+        ]
+        for a, x in within_1e8_rel:
+            assert a == approx(x, rel=5e-8, abs=0.0)
+            assert a != approx(x, rel=5e-9, abs=0.0)
+
+    def test_absolute_tolerance(self):
+        within_1e8_abs = [
+            (1e8 + 9e-9, 1e8),
+            (1e0 + 9e-9, 1e0),
+            (1e-8 + 9e-9, 1e-8),
+        ]
+        for a, x in within_1e8_abs:
+            assert a == approx(x, rel=0, abs=5e-8)
+            assert a != approx(x, rel=0, abs=5e-9)
+
+    def test_expecting_zero(self):
+        examples = [
+            (ne, 1e-6, 0.0),
+            (ne, -1e-6, 0.0),
+            (eq, 1e-12, 0.0),
+            (eq, -1e-12, 0.0),
+            (ne, 2e-12, 0.0),
+            (ne, -2e-12, 0.0),
+            (ne, inf, 0.0),
+            (ne, nan, 0.0),
+        ]
+        for op, a, x in examples:
+            assert op(a, approx(x, rel=0.0, abs=1e-12))
+            assert op(a, approx(x, rel=1e-6, abs=1e-12))
+
+    def test_expecting_inf(self):
+        examples = [
+            (eq, inf, inf),
+            (eq, -inf, -inf),
+            (ne, inf, -inf),
+            (ne, 0.0, inf),
+            (ne, nan, inf),
+        ]
+        for op, a, x in examples:
+            assert op(a, approx(x))
+
+    def test_expecting_nan(self):
+        examples = [
+            (eq, nan, nan),
+            (eq, -nan, -nan),
+            (eq, nan, -nan),
+            (ne, 0.0, nan),
+            (ne, inf, nan),
+        ]
+        for op, a, x in examples:
+            # Nothing is equal to NaN by default.
+            assert a != approx(x)
+
+            # If ``nan_ok=True``, then NaN is equal to NaN.
+            assert op(a, approx(x, nan_ok=True))
+
+    def test_int(self):
+        within_1e6 = [
+            (1000001, 1000000),
+            (-1000001, -1000000),
+        ]
+        for a, x in within_1e6:
+            assert a == approx(x, rel=5e-6, abs=0)
+            assert a != approx(x, rel=5e-7, abs=0)
+            assert approx(x, rel=5e-6, abs=0) == a
+            assert approx(x, rel=5e-7, abs=0) != a
+
+    def test_decimal(self):
+        within_1e6 = [
+            (Decimal('1.000001'), Decimal('1.0')),
+            (Decimal('-1.000001'), Decimal('-1.0')),
+        ]
+        for a, x in within_1e6:
+            assert a == approx(x, rel=Decimal('5e-6'), abs=0)
+            assert a != approx(x, rel=Decimal('5e-7'), abs=0)
+            assert approx(x, rel=Decimal('5e-6'), abs=0) == a
+            assert approx(x, rel=Decimal('5e-7'), abs=0) != a
+
+    def test_fraction(self):
+        within_1e6 = [
+            (1 + Fraction(1, 1000000), Fraction(1)),
+            (-1 - Fraction(-1, 1000000), Fraction(-1)),
+        ]
+        for a, x in within_1e6:
+            assert a == approx(x, rel=5e-6, abs=0)
+            assert a != approx(x, rel=5e-7, abs=0)
+            assert approx(x, rel=5e-6, abs=0) == a
+            assert approx(x, rel=5e-7, abs=0) != a
+
+    def test_complex(self):
+        within_1e6 = [
+            (1.000001 + 1.0j, 1.0 + 1.0j),
+            (1.0 + 1.000001j, 1.0 + 1.0j),
+            (-1.000001 + 1.0j, -1.0 + 1.0j),
+            (1.0 - 1.000001j, 1.0 - 1.0j),
+        ]
+        for a, x in within_1e6:
+            assert a == approx(x, rel=5e-6, abs=0)
+            assert a != approx(x, rel=5e-7, abs=0)
+            assert approx(x, rel=5e-6, abs=0) == a
+            assert approx(x, rel=5e-7, abs=0) != a
+
+    def test_list(self):
+        actual = [1 + 1e-7, 2 + 1e-8]
+        expected = [1, 2]
+
+        # Return false if any element is outside the tolerance.
+        assert actual == approx(expected, rel=5e-7, abs=0)
+        assert actual != approx(expected, rel=5e-8, abs=0)
+        assert approx(expected, rel=5e-7, abs=0) == actual
+        assert approx(expected, rel=5e-8, abs=0) != actual
+
+    def test_list_wrong_len(self):
+        assert [1, 2] != approx([1])
+        assert [1, 2] != approx([1, 2, 3])
+
+    def test_tuple(self):
+        actual = (1 + 1e-7, 2 + 1e-8)
+        expected = (1, 2)
+
+        # Return false if any element is outside the tolerance.
+        assert actual == approx(expected, rel=5e-7, abs=0)
+        assert actual != approx(expected, rel=5e-8, abs=0)
+        assert approx(expected, rel=5e-7, abs=0) == actual
+        assert approx(expected, rel=5e-8, abs=0) != actual
+
+    def test_tuple_wrong_len(self):
+        assert (1, 2) != approx((1,))
+        assert (1, 2) != approx((1, 2, 3))
+
+    def test_dict(self):
+        actual = {'a': 1 + 1e-7, 'b': 2 + 1e-8}
+        # Dictionaries became ordered in python3.6, so switch up the order here
+        # to make sure it doesn't matter.
+        expected = {'b': 2, 'a': 1}
+
+        # Return false if any element is outside the tolerance.
+        assert actual == approx(expected, rel=5e-7, abs=0)
+        assert actual != approx(expected, rel=5e-8, abs=0)
+        assert approx(expected, rel=5e-7, abs=0) == actual
+        assert approx(expected, rel=5e-8, abs=0) != actual
+
+    def test_dict_wrong_len(self):
+        assert {'a': 1, 'b': 2} != approx({'a': 1})
+        assert {'a': 1, 'b': 2} != approx({'a': 1, 'c': 2})
+        assert {'a': 1, 'b': 2} != approx({'a': 1, 'b': 2, 'c': 3})
+
+    def test_numpy_array(self):
+        np = pytest.importorskip('numpy')
+
+        actual = np.array([1 + 1e-7, 2 + 1e-8])
+        expected = np.array([1, 2])
+
+        # Return false if any element is outside the tolerance.
+        assert actual == approx(expected, rel=5e-7, abs=0)
+        assert actual != approx(expected, rel=5e-8, abs=0)
+        assert approx(expected, rel=5e-7, abs=0) == expected
+        assert approx(expected, rel=5e-8, abs=0) != actual
+
+        # Should be able to compare lists with numpy arrays.
+        assert list(actual) == approx(expected, rel=5e-7, abs=0)
+        assert list(actual) != approx(expected, rel=5e-8, abs=0)
+        assert actual == approx(list(expected), rel=5e-7, abs=0)
+        assert actual != approx(list(expected), rel=5e-8, abs=0)
+
+    def test_numpy_array_wrong_shape(self):
+        np = pytest.importorskip('numpy')
+
+        a12 = np.array([[1, 2]])
+        a21 = np.array([[1], [2]])
+
+        assert a12 != approx(a21)
+        assert a21 != approx(a12)
+
+    def test_doctests(self):
+        parser = doctest.DocTestParser()
+        test = parser.get_doctest(
+            approx.__doc__,
+            {'approx': approx},
+            approx.__name__,
+            None, None,
+        )
+        runner = MyDocTestRunner()
+        runner.run(test)
+
+    def test_unicode_plus_minus(self, testdir):
+        """
+        Comparing approx instances inside lists should not produce an error in the detailed diff.
+        Integration test for issue #2111.
+        """
+        testdir.makepyfile("""
+            import pytest
+            def test_foo():
+                assert [3] == [pytest.approx(4)]
+        """)
+        expected = '4.0e-06'
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            '*At index 0 diff: 3 != 4 * {0}'.format(expected),
+            '=* 1 failed in *=',
+        ])
+
+    @pytest.mark.parametrize('op', [
+        pytest.param(operator.le, id='<='),
+        pytest.param(operator.lt, id='<'),
+        pytest.param(operator.ge, id='>='),
+        pytest.param(operator.gt, id='>'),
+    ])
+    def test_comparison_operator_type_error(self, op):
+        """
+        pytest.approx should raise TypeError for operators other than == and != (#2003).
+        """
+        with pytest.raises(TypeError):
+            op(1, approx(1, rel=1e-6, abs=1e-12))
diff --git a/tools/third_party/pytest/testing/python/collect.py b/tools/third_party/pytest/testing/python/collect.py
new file mode 100644
index 0000000..16c2154
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/collect.py
@@ -0,0 +1,1402 @@
+# -*- coding: utf-8 -*-
+import os
+import sys
+from textwrap import dedent
+
+import _pytest._code
+import py
+import pytest
+from _pytest.main import (
+    Collector,
+    EXIT_NOTESTSCOLLECTED
+)
+
+
+ignore_parametrized_marks = pytest.mark.filterwarnings('ignore:Applying marks directly to parameters')
+
+
+class TestModule(object):
+    def test_failing_import(self, testdir):
+        modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
+        pytest.raises(Collector.CollectError, modcol.collect)
+
+    def test_import_duplicate(self, testdir):
+        a = testdir.mkdir("a")
+        b = testdir.mkdir("b")
+        p = a.ensure("test_whatever.py")
+        p.pyimport()
+        del py.std.sys.modules['test_whatever']
+        b.ensure("test_whatever.py")
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*import*mismatch*",
+            "*imported*test_whatever*",
+            "*%s*" % a.join("test_whatever.py"),
+            "*not the same*",
+            "*%s*" % b.join("test_whatever.py"),
+            "*HINT*",
+        ])
+
+    def test_import_prepend_append(self, testdir, monkeypatch):
+        syspath = list(sys.path)
+        monkeypatch.setattr(sys, "path", syspath)
+        root1 = testdir.mkdir("root1")
+        root2 = testdir.mkdir("root2")
+        root1.ensure("x456.py")
+        root2.ensure("x456.py")
+        p = root2.join("test_x456.py")
+        monkeypatch.syspath_prepend(str(root1))
+        p.write(dedent("""\
+            import x456
+            def test():
+                assert x456.__file__.startswith(%r)
+        """ % str(root2)))
+        with root2.as_cwd():
+            reprec = testdir.inline_run("--import-mode=append")
+            reprec.assertoutcome(passed=0, failed=1)
+            reprec = testdir.inline_run()
+            reprec.assertoutcome(passed=1)
+
+    def test_syntax_error_in_module(self, testdir):
+        modcol = testdir.getmodulecol("this is a syntax error")
+        pytest.raises(modcol.CollectError, modcol.collect)
+        pytest.raises(modcol.CollectError, modcol.collect)
+
+    def test_module_considers_pluginmanager_at_import(self, testdir):
+        modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
+        pytest.raises(ImportError, lambda: modcol.obj)
+
+    def test_invalid_test_module_name(self, testdir):
+        a = testdir.mkdir('a')
+        a.ensure('test_one.part1.py')
+        result = testdir.runpytest("-rw")
+        result.stdout.fnmatch_lines([
+            "ImportError while importing test module*test_one.part1*",
+            "Hint: make sure your test modules/packages have valid Python names.",
+        ])
+
+    @pytest.mark.parametrize('verbose', [0, 1, 2])
+    def test_show_traceback_import_error(self, testdir, verbose):
+        """Import errors when collecting modules should display the traceback (#1976).
+
+        With low verbosity we omit pytest and internal modules, otherwise show all traceback entries.
+        """
+        testdir.makepyfile(
+            foo_traceback_import_error="""
+               from bar_traceback_import_error import NOT_AVAILABLE
+           """,
+            bar_traceback_import_error="",
+        )
+        testdir.makepyfile("""
+               import foo_traceback_import_error
+        """)
+        args = ('-v',) * verbose
+        result = testdir.runpytest(*args)
+        result.stdout.fnmatch_lines([
+            "ImportError while importing test module*",
+            "Traceback:",
+            "*from bar_traceback_import_error import NOT_AVAILABLE",
+            "*cannot import name *NOT_AVAILABLE*",
+        ])
+        assert result.ret == 2
+
+        stdout = result.stdout.str()
+        for name in ('_pytest', os.path.join('py', '_path')):
+            if verbose == 2:
+                assert name in stdout
+            else:
+                assert name not in stdout
+
+    def test_show_traceback_import_error_unicode(self, testdir):
+        """Check test modules collected which raise ImportError with unicode messages
+        are handled properly (#2336).
+        """
+        testdir.makepyfile(u"""
+            # -*- coding: utf-8 -*-
+            raise ImportError(u'Something bad happened ☺')
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "ImportError while importing test module*",
+            "Traceback:",
+            "*raise ImportError*Something bad happened*",
+        ])
+        assert result.ret == 2
+
+
+class TestClass(object):
+    def test_class_with_init_warning(self, testdir):
+        testdir.makepyfile("""
+            class TestClass1(object):
+                def __init__(self):
+                    pass
+        """)
+        result = testdir.runpytest("-rw")
+        result.stdout.fnmatch_lines([
+            "*cannot collect test class 'TestClass1' because it has a __init__ constructor",
+        ])
+
+    def test_class_subclassobject(self, testdir):
+        testdir.getmodulecol("""
+            class test(object):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*collected 0*",
+        ])
+
+    def test_static_method(self, testdir):
+        """Support for collecting staticmethod tests (#2528, #2699)"""
+        testdir.getmodulecol("""
+            import pytest
+            class Test(object):
+                @staticmethod
+                def test_something():
+                    pass
+
+                @pytest.fixture
+                def fix(self):
+                    return 1
+
+                @staticmethod
+                def test_fix(fix):
+                    assert fix == 1
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*collected 2 items*",
+            "*2 passed in*",
+        ])
+
+    def test_setup_teardown_class_as_classmethod(self, testdir):
+        testdir.makepyfile(test_mod1="""
+            class TestClassMethod(object):
+                @classmethod
+                def setup_class(cls):
+                    pass
+                def test_1(self):
+                    pass
+                @classmethod
+                def teardown_class(cls):
+                    pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*1 passed*",
+        ])
+
+    def test_issue1035_obj_has_getattr(self, testdir):
+        modcol = testdir.getmodulecol("""
+            class Chameleon(object):
+                def __getattr__(self, name):
+                    return True
+            chameleon = Chameleon()
+        """)
+        colitems = modcol.collect()
+        assert len(colitems) == 0
+
+    def test_issue1579_namedtuple(self, testdir):
+        testdir.makepyfile("""
+            import collections
+
+            TestCase = collections.namedtuple('TestCase', ['a'])
+        """)
+        result = testdir.runpytest('-rw')
+        result.stdout.fnmatch_lines(
+            "*cannot collect test class 'TestCase' "
+            "because it has a __new__ constructor*"
+        )
+
+    def test_issue2234_property(self, testdir):
+        testdir.makepyfile("""
+            class TestCase(object):
+                @property
+                def prop(self):
+                    raise NotImplementedError()
+        """)
+        result = testdir.runpytest()
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+
+
+class TestGenerator(object):
+    def test_generative_functions(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def func1(arg, arg2):
+                assert arg == arg2
+
+            def test_gen():
+                yield func1, 17, 3*5
+                yield func1, 42, 6*7
+        """)
+        colitems = modcol.collect()
+        assert len(colitems) == 1
+        gencol = colitems[0]
+        assert isinstance(gencol, pytest.Generator)
+        gencolitems = gencol.collect()
+        assert len(gencolitems) == 2
+        assert isinstance(gencolitems[0], pytest.Function)
+        assert isinstance(gencolitems[1], pytest.Function)
+        assert gencolitems[0].name == '[0]'
+        assert gencolitems[0].obj.__name__ == 'func1'
+
+    def test_generative_methods(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def func1(arg, arg2):
+                assert arg == arg2
+            class TestGenMethods(object):
+                def test_gen(self):
+                    yield func1, 17, 3*5
+                    yield func1, 42, 6*7
+        """)
+        gencol = modcol.collect()[0].collect()[0].collect()[0]
+        assert isinstance(gencol, pytest.Generator)
+        gencolitems = gencol.collect()
+        assert len(gencolitems) == 2
+        assert isinstance(gencolitems[0], pytest.Function)
+        assert isinstance(gencolitems[1], pytest.Function)
+        assert gencolitems[0].name == '[0]'
+        assert gencolitems[0].obj.__name__ == 'func1'
+
+    def test_generative_functions_with_explicit_names(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def func1(arg, arg2):
+                assert arg == arg2
+
+            def test_gen():
+                yield "seventeen", func1, 17, 3*5
+                yield "fortytwo", func1, 42, 6*7
+        """)
+        colitems = modcol.collect()
+        assert len(colitems) == 1
+        gencol = colitems[0]
+        assert isinstance(gencol, pytest.Generator)
+        gencolitems = gencol.collect()
+        assert len(gencolitems) == 2
+        assert isinstance(gencolitems[0], pytest.Function)
+        assert isinstance(gencolitems[1], pytest.Function)
+        assert gencolitems[0].name == "['seventeen']"
+        assert gencolitems[0].obj.__name__ == 'func1'
+        assert gencolitems[1].name == "['fortytwo']"
+        assert gencolitems[1].obj.__name__ == 'func1'
+
+    def test_generative_functions_unique_explicit_names(self, testdir):
+        # generative
+        modcol = testdir.getmodulecol("""
+            def func(): pass
+            def test_gen():
+                yield "name", func
+                yield "name", func
+        """)
+        colitems = modcol.collect()
+        assert len(colitems) == 1
+        gencol = colitems[0]
+        assert isinstance(gencol, pytest.Generator)
+        pytest.raises(ValueError, "gencol.collect()")
+
+    def test_generative_methods_with_explicit_names(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def func1(arg, arg2):
+                assert arg == arg2
+            class TestGenMethods(object):
+                def test_gen(self):
+                    yield "m1", func1, 17, 3*5
+                    yield "m2", func1, 42, 6*7
+        """)
+        gencol = modcol.collect()[0].collect()[0].collect()[0]
+        assert isinstance(gencol, pytest.Generator)
+        gencolitems = gencol.collect()
+        assert len(gencolitems) == 2
+        assert isinstance(gencolitems[0], pytest.Function)
+        assert isinstance(gencolitems[1], pytest.Function)
+        assert gencolitems[0].name == "['m1']"
+        assert gencolitems[0].obj.__name__ == 'func1'
+        assert gencolitems[1].name == "['m2']"
+        assert gencolitems[1].obj.__name__ == 'func1'
+
+    def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
+        o = testdir.makepyfile("""
+            from __future__ import print_function
+            def test_generative_order_of_execution():
+                import py, pytest
+                test_list = []
+                expected_list = list(range(6))
+
+                def list_append(item):
+                    test_list.append(item)
+
+                def assert_order_of_execution():
+                    print('expected order', expected_list)
+                    print('but got       ', test_list)
+                    assert test_list == expected_list
+
+                for i in expected_list:
+                    yield list_append, i
+                yield assert_order_of_execution
+        """)
+        reprec = testdir.inline_run(o)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert passed == 7
+        assert not skipped and not failed
+
+    def test_order_of_execution_generator_different_codeline(self, testdir):
+        o = testdir.makepyfile("""
+            from __future__ import print_function
+            def test_generative_tests_different_codeline():
+                import py, pytest
+                test_list = []
+                expected_list = list(range(3))
+
+                def list_append_2():
+                    test_list.append(2)
+
+                def list_append_1():
+                    test_list.append(1)
+
+                def list_append_0():
+                    test_list.append(0)
+
+                def assert_order_of_execution():
+                    print('expected order', expected_list)
+                    print('but got       ', test_list)
+                    assert test_list == expected_list
+
+                yield list_append_0
+                yield list_append_1
+                yield list_append_2
+                yield assert_order_of_execution
+        """)
+        reprec = testdir.inline_run(o)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert passed == 4
+        assert not skipped and not failed
+
+    def test_setupstate_is_preserved_134(self, testdir):
+        # yield-based tests are messy wrt to setupstate because
+        # during collection they already invoke setup functions
+        # and then again when they are run.  For now, we want to make sure
+        # that the old 1.3.4 behaviour is preserved such that all
+        # yielded functions all share the same "self" instance that
+        # has been used during collection.
+        o = testdir.makepyfile("""
+            setuplist = []
+            class TestClass(object):
+                def setup_method(self, func):
+                    #print "setup_method", self, func
+                    setuplist.append(self)
+                    self.init = 42
+
+                def teardown_method(self, func):
+                    self.init = None
+
+                def test_func1(self):
+                    pass
+
+                def test_func2(self):
+                    yield self.func2
+                    yield self.func2
+
+                def func2(self):
+                    assert self.init
+
+            def test_setuplist():
+                # once for test_func2 during collection
+                # once for test_func1 during test run
+                # once for test_func2 during test run
+                #print setuplist
+                assert len(setuplist) == 3, len(setuplist)
+                assert setuplist[0] == setuplist[2], setuplist
+                assert setuplist[1] != setuplist[2], setuplist
+        """)
+        reprec = testdir.inline_run(o, '-v')
+        passed, skipped, failed = reprec.countoutcomes()
+        assert passed == 4
+        assert not skipped and not failed
+
+
+class TestFunction(object):
+    def test_getmodulecollector(self, testdir):
+        item = testdir.getitem("def test_func(): pass")
+        modcol = item.getparent(pytest.Module)
+        assert isinstance(modcol, pytest.Module)
+        assert hasattr(modcol.obj, 'test_func')
+
+    def test_function_as_object_instance_ignored(self, testdir):
+        testdir.makepyfile("""
+            class A(object):
+                def __call__(self, tmpdir):
+                    0/0
+
+            test_a = A()
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome()
+
+    def test_function_equality(self, testdir, tmpdir):
+        from _pytest.fixtures import FixtureManager
+        config = testdir.parseconfigure()
+        session = testdir.Session(config)
+        session._fixturemanager = FixtureManager(session)
+
+        def func1():
+            pass
+
+        def func2():
+            pass
+
+        f1 = pytest.Function(name="name", parent=session, config=config,
+                             args=(1,), callobj=func1)
+        assert f1 == f1
+        f2 = pytest.Function(name="name", config=config,
+                             callobj=func2, parent=session)
+        assert f1 != f2
+
+    def test_issue197_parametrize_emptyset(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.parametrize('arg', [])
+            def test_function(arg):
+                pass
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(skipped=1)
+
+    def test_single_tuple_unwraps_values(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.parametrize(('arg',), [(1,)])
+            def test_function(arg):
+                assert arg == 1
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_issue213_parametrize_value_no_equal(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            class A(object):
+                def __eq__(self, other):
+                    raise ValueError("not possible")
+            @pytest.mark.parametrize('arg', [A()])
+            def test_function(arg):
+                assert arg.__class__.__name__ == "A"
+        """)
+        reprec = testdir.inline_run("--fulltrace")
+        reprec.assertoutcome(passed=1)
+
+    def test_parametrize_with_non_hashable_values(self, testdir):
+        """Test parametrization with non-hashable values."""
+        testdir.makepyfile("""
+            archival_mapping = {
+                '1.0': {'tag': '1.0'},
+                '1.2.2a1': {'tag': 'release-1.2.2a1'},
+            }
+
+            import pytest
+            @pytest.mark.parametrize('key value'.split(),
+                                     archival_mapping.items())
+            def test_archival_to_version(key, value):
+                assert key in archival_mapping
+                assert value == archival_mapping[key]
+        """)
+        rec = testdir.inline_run()
+        rec.assertoutcome(passed=2)
+
+    def test_parametrize_with_non_hashable_values_indirect(self, testdir):
+        """Test parametrization with non-hashable values with indirect parametrization."""
+        testdir.makepyfile("""
+            archival_mapping = {
+                '1.0': {'tag': '1.0'},
+                '1.2.2a1': {'tag': 'release-1.2.2a1'},
+            }
+
+            import pytest
+
+            @pytest.fixture
+            def key(request):
+                return request.param
+
+            @pytest.fixture
+            def value(request):
+                return request.param
+
+            @pytest.mark.parametrize('key value'.split(),
+                                     archival_mapping.items(), indirect=True)
+            def test_archival_to_version(key, value):
+                assert key in archival_mapping
+                assert value == archival_mapping[key]
+        """)
+        rec = testdir.inline_run()
+        rec.assertoutcome(passed=2)
+
+    def test_parametrize_overrides_fixture(self, testdir):
+        """Test parametrization when parameter overrides existing fixture with same name."""
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def value():
+                return 'value'
+
+            @pytest.mark.parametrize('value',
+                                     ['overridden'])
+            def test_overridden_via_param(value):
+                assert value == 'overridden'
+
+            @pytest.mark.parametrize('somevalue', ['overridden'])
+            def test_not_overridden(value, somevalue):
+                assert value == 'value'
+                assert somevalue == 'overridden'
+
+            @pytest.mark.parametrize('other,value', [('foo', 'overridden')])
+            def test_overridden_via_multiparam(other, value):
+                assert other == 'foo'
+                assert value == 'overridden'
+        """)
+        rec = testdir.inline_run()
+        rec.assertoutcome(passed=3)
+
+    def test_parametrize_overrides_parametrized_fixture(self, testdir):
+        """Test parametrization when parameter overrides existing parametrized fixture with same name."""
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(params=[1, 2])
+            def value(request):
+                return request.param
+
+            @pytest.mark.parametrize('value',
+                                     ['overridden'])
+            def test_overridden_via_param(value):
+                assert value == 'overridden'
+        """)
+        rec = testdir.inline_run()
+        rec.assertoutcome(passed=1)
+
+    @ignore_parametrized_marks
+    def test_parametrize_with_mark(self, testdir):
+        items = testdir.getitems("""
+            import pytest
+            @pytest.mark.foo
+            @pytest.mark.parametrize('arg', [
+                1,
+                pytest.mark.bar(pytest.mark.baz(2))
+            ])
+            def test_function(arg):
+                pass
+        """)
+        keywords = [item.keywords for item in items]
+        assert 'foo' in keywords[0] and 'bar' not in keywords[0] and 'baz' not in keywords[0]
+        assert 'foo' in keywords[1] and 'bar' in keywords[1] and 'baz' in keywords[1]
+
+    def test_function_equality_with_callspec(self, testdir, tmpdir):
+        items = testdir.getitems("""
+            import pytest
+            @pytest.mark.parametrize('arg', [1,2])
+            def test_function(arg):
+                pass
+        """)
+        assert items[0] != items[1]
+        assert not (items[0] == items[1])
+
+    def test_pyfunc_call(self, testdir):
+        item = testdir.getitem("def test_func(): raise ValueError")
+        config = item.config
+
+        class MyPlugin1(object):
+            def pytest_pyfunc_call(self, pyfuncitem):
+                raise ValueError
+
+        class MyPlugin2(object):
+            def pytest_pyfunc_call(self, pyfuncitem):
+                return True
+
+        config.pluginmanager.register(MyPlugin1())
+        config.pluginmanager.register(MyPlugin2())
+        config.hook.pytest_runtest_setup(item=item)
+        config.hook.pytest_pyfunc_call(pyfuncitem=item)
+
+    def test_multiple_parametrize(self, testdir):
+        modcol = testdir.getmodulecol("""
+            import pytest
+            @pytest.mark.parametrize('x', [0, 1])
+            @pytest.mark.parametrize('y', [2, 3])
+            def test1(x, y):
+                pass
+        """)
+        colitems = modcol.collect()
+        assert colitems[0].name == 'test1[2-0]'
+        assert colitems[1].name == 'test1[2-1]'
+        assert colitems[2].name == 'test1[3-0]'
+        assert colitems[3].name == 'test1[3-1]'
+
+    def test_issue751_multiple_parametrize_with_ids(self, testdir):
+        modcol = testdir.getmodulecol("""
+            import pytest
+            @pytest.mark.parametrize('x', [0], ids=['c'])
+            @pytest.mark.parametrize('y', [0, 1], ids=['a', 'b'])
+            class Test(object):
+                def test1(self, x, y):
+                    pass
+                def test2(self, x, y):
+                    pass
+        """)
+        colitems = modcol.collect()[0].collect()[0].collect()
+        assert colitems[0].name == 'test1[a-c]'
+        assert colitems[1].name == 'test1[b-c]'
+        assert colitems[2].name == 'test2[a-c]'
+        assert colitems[3].name == 'test2[b-c]'
+
+    @ignore_parametrized_marks
+    def test_parametrize_skipif(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            m = pytest.mark.skipif('True')
+
+            @pytest.mark.parametrize('x', [0, 1, m(2)])
+            def test_skip_if(x):
+                assert x < 2
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
+
+    @ignore_parametrized_marks
+    def test_parametrize_skip(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            m = pytest.mark.skip('')
+
+            @pytest.mark.parametrize('x', [0, 1, m(2)])
+            def test_skip(x):
+                assert x < 2
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
+
+    @ignore_parametrized_marks
+    def test_parametrize_skipif_no_skip(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            m = pytest.mark.skipif('False')
+
+            @pytest.mark.parametrize('x', [0, 1, m(2)])
+            def test_skipif_no_skip(x):
+                assert x < 2
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('* 1 failed, 2 passed in *')
+
+    @ignore_parametrized_marks
+    def test_parametrize_xfail(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            m = pytest.mark.xfail('True')
+
+            @pytest.mark.parametrize('x', [0, 1, m(2)])
+            def test_xfail(x):
+                assert x < 2
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('* 2 passed, 1 xfailed in *')
+
+    @ignore_parametrized_marks
+    def test_parametrize_passed(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            m = pytest.mark.xfail('True')
+
+            @pytest.mark.parametrize('x', [0, 1, m(2)])
+            def test_xfail(x):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('* 2 passed, 1 xpassed in *')
+
+    @ignore_parametrized_marks
+    def test_parametrize_xfail_passed(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            m = pytest.mark.xfail('False')
+
+            @pytest.mark.parametrize('x', [0, 1, m(2)])
+            def test_passed(x):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('* 3 passed in *')
+
+    def test_function_original_name(self, testdir):
+        items = testdir.getitems("""
+            import pytest
+            @pytest.mark.parametrize('arg', [1,2])
+            def test_func(arg):
+                pass
+        """)
+        assert [x.originalname for x in items] == ['test_func', 'test_func']
+
+
+class TestSorting(object):
+    def test_check_equality(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def test_pass(): pass
+            def test_fail(): assert 0
+        """)
+        fn1 = testdir.collect_by_name(modcol, "test_pass")
+        assert isinstance(fn1, pytest.Function)
+        fn2 = testdir.collect_by_name(modcol, "test_pass")
+        assert isinstance(fn2, pytest.Function)
+
+        assert fn1 == fn2
+        assert fn1 != modcol
+        if py.std.sys.version_info < (3, 0):
+            assert cmp(fn1, fn2) == 0
+        assert hash(fn1) == hash(fn2)
+
+        fn3 = testdir.collect_by_name(modcol, "test_fail")
+        assert isinstance(fn3, pytest.Function)
+        assert not (fn1 == fn3)
+        assert fn1 != fn3
+
+        for fn in fn1, fn2, fn3:
+            assert fn != 3
+            assert fn != modcol
+            assert fn != [1, 2, 3]
+            assert [1, 2, 3] != fn
+            assert modcol != fn
+
+    def test_allow_sane_sorting_for_decorators(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def dec(f):
+                g = lambda: f(2)
+                g.place_as = f
+                return g
+
+
+            def test_b(y):
+                pass
+            test_b = dec(test_b)
+
+            def test_a(y):
+                pass
+            test_a = dec(test_a)
+        """)
+        colitems = modcol.collect()
+        assert len(colitems) == 2
+        assert [item.name for item in colitems] == ['test_b', 'test_a']
+
+
+class TestConftestCustomization(object):
+    def test_pytest_pycollect_module(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            class MyModule(pytest.Module):
+                pass
+            def pytest_pycollect_makemodule(path, parent):
+                if path.basename == "test_xyz.py":
+                    return MyModule(path, parent)
+        """)
+        testdir.makepyfile("def test_some(): pass")
+        testdir.makepyfile(test_xyz="def test_func(): pass")
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*<Module*test_pytest*",
+            "*<MyModule*xyz*",
+        ])
+
+    def test_customized_pymakemodule_issue205_subdir(self, testdir):
+        b = testdir.mkdir("a").mkdir("b")
+        b.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+            @pytest.hookimpl(hookwrapper=True)
+            def pytest_pycollect_makemodule():
+                outcome = yield
+                mod = outcome.get_result()
+                mod.obj.hello = "world"
+        """))
+        b.join("test_module.py").write(_pytest._code.Source("""
+            def test_hello():
+                assert hello == "world"
+        """))
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_customized_pymakeitem(self, testdir):
+        b = testdir.mkdir("a").mkdir("b")
+        b.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+            @pytest.hookimpl(hookwrapper=True)
+            def pytest_pycollect_makeitem():
+                outcome = yield
+                if outcome.excinfo is None:
+                    result = outcome.get_result()
+                    if result:
+                        for func in result:
+                            func._some123 = "world"
+        """))
+        b.join("test_module.py").write(_pytest._code.Source("""
+            import pytest
+
+            @pytest.fixture()
+            def obj(request):
+                return request.node._some123
+            def test_hello(obj):
+                assert obj == "world"
+        """))
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_pytest_pycollect_makeitem(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            class MyFunction(pytest.Function):
+                pass
+            def pytest_pycollect_makeitem(collector, name, obj):
+                if name == "some":
+                    return MyFunction(name, collector)
+        """)
+        testdir.makepyfile("def some(): pass")
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*MyFunction*some*",
+        ])
+
+    def test_makeitem_non_underscore(self, testdir, monkeypatch):
+        modcol = testdir.getmodulecol("def _hello(): pass")
+        values = []
+        monkeypatch.setattr(pytest.Module, 'makeitem',
+                            lambda self, name, obj: values.append(name))
+        values = modcol.collect()
+        assert '_hello' not in values
+
+    def test_issue2369_collect_module_fileext(self, testdir):
+        """Ensure we can collect files with weird file extensions as Python
+        modules (#2369)"""
+        # We'll implement a little finder and loader to import files containing
+        # Python source code whose file extension is ".narf".
+        testdir.makeconftest("""
+            import sys, os, imp
+            from _pytest.python import Module
+
+            class Loader:
+                def load_module(self, name):
+                    return imp.load_source(name, name + ".narf")
+            class Finder:
+                def find_module(self, name, path=None):
+                    if os.path.exists(name + ".narf"):
+                        return Loader()
+            sys.meta_path.append(Finder())
+
+            def pytest_collect_file(path, parent):
+                if path.ext == ".narf":
+                    return Module(path, parent)""")
+        testdir.makefile(".narf", """
+            def test_something():
+                assert 1 + 1 == 2""")
+        # Use runpytest_subprocess, since we're futzing with sys.meta_path.
+        result = testdir.runpytest_subprocess()
+        result.stdout.fnmatch_lines('*1 passed*')
+
+
+def test_setup_only_available_in_subdir(testdir):
+    sub1 = testdir.mkpydir("sub1")
+    sub2 = testdir.mkpydir("sub2")
+    sub1.join("conftest.py").write(_pytest._code.Source("""
+        import pytest
+        def pytest_runtest_setup(item):
+            assert item.fspath.purebasename == "test_in_sub1"
+        def pytest_runtest_call(item):
+            assert item.fspath.purebasename == "test_in_sub1"
+        def pytest_runtest_teardown(item):
+            assert item.fspath.purebasename == "test_in_sub1"
+    """))
+    sub2.join("conftest.py").write(_pytest._code.Source("""
+        import pytest
+        def pytest_runtest_setup(item):
+            assert item.fspath.purebasename == "test_in_sub2"
+        def pytest_runtest_call(item):
+            assert item.fspath.purebasename == "test_in_sub2"
+        def pytest_runtest_teardown(item):
+            assert item.fspath.purebasename == "test_in_sub2"
+    """))
+    sub1.join("test_in_sub1.py").write("def test_1(): pass")
+    sub2.join("test_in_sub2.py").write("def test_2(): pass")
+    result = testdir.runpytest("-v", "-s")
+    result.assert_outcomes(passed=2)
+
+
+def test_modulecol_roundtrip(testdir):
+    modcol = testdir.getmodulecol("pass", withinit=True)
+    trail = modcol.nodeid
+    newcol = modcol.session.perform_collect([trail], genitems=0)[0]
+    assert modcol.name == newcol.name
+
+
+class TestTracebackCutting(object):
+    def test_skip_simple(self):
+        excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")')
+        assert excinfo.traceback[-1].frame.code.name == "skip"
+        assert excinfo.traceback[-1].ishidden()
+
+    def test_traceback_argsetup(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def hello(request):
+                raise ValueError("xyz")
+        """)
+        p = testdir.makepyfile("def test(hello): pass")
+        result = testdir.runpytest(p)
+        assert result.ret != 0
+        out = result.stdout.str()
+        assert "xyz" in out
+        assert "conftest.py:5: ValueError" in out
+        numentries = out.count("_ _ _")  # separator for traceback entries
+        assert numentries == 0
+
+        result = testdir.runpytest("--fulltrace", p)
+        out = result.stdout.str()
+        assert "conftest.py:5: ValueError" in out
+        numentries = out.count("_ _ _ _")  # separator for traceback entries
+        assert numentries > 3
+
+    def test_traceback_error_during_import(self, testdir):
+        testdir.makepyfile("""
+            x = 1
+            x = 2
+            x = 17
+            asd
+        """)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        out = result.stdout.str()
+        assert "x = 1" not in out
+        assert "x = 2" not in out
+        result.stdout.fnmatch_lines([
+            " *asd*",
+            "E*NameError*",
+        ])
+        result = testdir.runpytest("--fulltrace")
+        out = result.stdout.str()
+        assert "x = 1" in out
+        assert "x = 2" in out
+        result.stdout.fnmatch_lines([
+            ">*asd*",
+            "E*NameError*",
+        ])
+
+    def test_traceback_filter_error_during_fixture_collection(self, testdir):
+        """integration test for issue #995.
+        """
+        testdir.makepyfile("""
+            import pytest
+
+            def fail_me(func):
+                ns = {}
+                exec('def w(): raise ValueError("fail me")', ns)
+                return ns['w']
+
+            @pytest.fixture(scope='class')
+            @fail_me
+            def fail_fixture():
+                pass
+
+            def test_failing_fixture(fail_fixture):
+               pass
+        """)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        out = result.stdout.str()
+        assert "INTERNALERROR>" not in out
+        result.stdout.fnmatch_lines([
+            "*ValueError: fail me*",
+            "* 1 error in *",
+        ])
+
+    def test_filter_traceback_generated_code(self):
+        """test that filter_traceback() works with the fact that
+        py.code.Code.path attribute might return an str object.
+        In this case, one of the entries on the traceback was produced by
+        dynamically generated code.
+        See: https://bitbucket.org/pytest-dev/py/issues/71
+        This fixes #995.
+        """
+        from _pytest.python import filter_traceback
+        try:
+            ns = {}
+            exec('def foo(): raise ValueError', ns)
+            ns['foo']()
+        except ValueError:
+            _, _, tb = sys.exc_info()
+
+        tb = _pytest._code.Traceback(tb)
+        assert isinstance(tb[-1].path, str)
+        assert not filter_traceback(tb[-1])
+
+    def test_filter_traceback_path_no_longer_valid(self, testdir):
+        """test that filter_traceback() works with the fact that
+        py.code.Code.path attribute might return an str object.
+        In this case, one of the files in the traceback no longer exists.
+        This fixes #1133.
+        """
+        from _pytest.python import filter_traceback
+        testdir.syspathinsert()
+        testdir.makepyfile(filter_traceback_entry_as_str='''
+            def foo():
+                raise ValueError
+        ''')
+        try:
+            import filter_traceback_entry_as_str
+            filter_traceback_entry_as_str.foo()
+        except ValueError:
+            _, _, tb = sys.exc_info()
+
+        testdir.tmpdir.join('filter_traceback_entry_as_str.py').remove()
+        tb = _pytest._code.Traceback(tb)
+        assert isinstance(tb[-1].path, str)
+        assert filter_traceback(tb[-1])
+
+
+class TestReportInfo(object):
+    def test_itemreport_reportinfo(self, testdir, linecomp):
+        testdir.makeconftest("""
+            import pytest
+            class MyFunction(pytest.Function):
+                def reportinfo(self):
+                    return "ABCDE", 42, "custom"
+            def pytest_pycollect_makeitem(collector, name, obj):
+                if name == "test_func":
+                    return MyFunction(name, parent=collector)
+        """)
+        item = testdir.getitem("def test_func(): pass")
+        item.config.pluginmanager.getplugin("runner")
+        assert item.location == ("ABCDE", 42, "custom")
+
+    def test_func_reportinfo(self, testdir):
+        item = testdir.getitem("def test_func(): pass")
+        fspath, lineno, modpath = item.reportinfo()
+        assert fspath == item.fspath
+        assert lineno == 0
+        assert modpath == "test_func"
+
+    def test_class_reportinfo(self, testdir):
+        modcol = testdir.getmodulecol("""
+            # lineno 0
+            class TestClass(object):
+                def test_hello(self): pass
+        """)
+        classcol = testdir.collect_by_name(modcol, "TestClass")
+        fspath, lineno, msg = classcol.reportinfo()
+        assert fspath == modcol.fspath
+        assert lineno == 1
+        assert msg == "TestClass"
+
+    def test_generator_reportinfo(self, testdir):
+        modcol = testdir.getmodulecol("""
+            # lineno 0
+            def test_gen():
+                def check(x):
+                    assert x
+                yield check, 3
+        """)
+        gencol = testdir.collect_by_name(modcol, "test_gen")
+        fspath, lineno, modpath = gencol.reportinfo()
+        assert fspath == modcol.fspath
+        assert lineno == 1
+        assert modpath == "test_gen"
+
+        genitem = gencol.collect()[0]
+        fspath, lineno, modpath = genitem.reportinfo()
+        assert fspath == modcol.fspath
+        assert lineno == 2
+        assert modpath == "test_gen[0]"
+        """
+            def test_func():
+                pass
+            def test_genfunc():
+                def check(x):
+                    pass
+                yield check, 3
+            class TestClass(object):
+                def test_method(self):
+                    pass
+       """
+
+    def test_reportinfo_with_nasty_getattr(self, testdir):
+        # https://github.com/pytest-dev/pytest/issues/1204
+        modcol = testdir.getmodulecol("""
+            # lineno 0
+            class TestClass(object):
+                def __getattr__(self, name):
+                    return "this is not an int"
+
+                def test_foo(self):
+                    pass
+        """)
+        classcol = testdir.collect_by_name(modcol, "TestClass")
+        instance = classcol.collect()[0]
+        fspath, lineno, msg = instance.reportinfo()
+
+
+def test_customized_python_discovery(testdir):
+    testdir.makeini("""
+        [pytest]
+        python_files=check_*.py
+        python_classes=Check
+        python_functions=check
+    """)
+    p = testdir.makepyfile("""
+        def check_simple():
+            pass
+        class CheckMyApp(object):
+            def check_meth(self):
+                pass
+    """)
+    p2 = p.new(basename=p.basename.replace("test", "check"))
+    p.move(p2)
+    result = testdir.runpytest("--collect-only", "-s")
+    result.stdout.fnmatch_lines([
+        "*check_customized*",
+        "*check_simple*",
+        "*CheckMyApp*",
+        "*check_meth*",
+    ])
+
+    result = testdir.runpytest()
+    assert result.ret == 0
+    result.stdout.fnmatch_lines([
+        "*2 passed*",
+    ])
+
+
+def test_customized_python_discovery_functions(testdir):
+    testdir.makeini("""
+        [pytest]
+        python_functions=_test
+    """)
+    testdir.makepyfile("""
+        def _test_underscore():
+            pass
+    """)
+    result = testdir.runpytest("--collect-only", "-s")
+    result.stdout.fnmatch_lines([
+        "*_test_underscore*",
+    ])
+
+    result = testdir.runpytest()
+    assert result.ret == 0
+    result.stdout.fnmatch_lines([
+        "*1 passed*",
+    ])
+
+
+def test_collector_attributes(testdir):
+    testdir.makeconftest("""
+        import pytest
+        def pytest_pycollect_makeitem(collector):
+            assert collector.Function == pytest.Function
+            assert collector.Class == pytest.Class
+            assert collector.Instance == pytest.Instance
+            assert collector.Module == pytest.Module
+    """)
+    testdir.makepyfile("""
+         def test_hello():
+            pass
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*1 passed*",
+    ])
+
+
+def test_customize_through_attributes(testdir):
+    testdir.makeconftest("""
+        import pytest
+        class MyFunction(pytest.Function):
+            pass
+        class MyInstance(pytest.Instance):
+            Function = MyFunction
+        class MyClass(pytest.Class):
+            Instance = MyInstance
+
+        def pytest_pycollect_makeitem(collector, name, obj):
+            if name.startswith("MyTestClass"):
+                return MyClass(name, parent=collector)
+    """)
+    testdir.makepyfile("""
+         class MyTestClass(object):
+            def test_hello(self):
+                pass
+    """)
+    result = testdir.runpytest("--collect-only")
+    result.stdout.fnmatch_lines([
+        "*MyClass*",
+        "*MyInstance*",
+        "*MyFunction*test_hello*",
+    ])
+
+
+def test_unorderable_types(testdir):
+    testdir.makepyfile("""
+        class TestJoinEmpty(object):
+            pass
+
+        def make_test():
+            class Test(object):
+                pass
+            Test.__name__ = "TestFoo"
+            return Test
+        TestFoo = make_test()
+    """)
+    result = testdir.runpytest()
+    assert "TypeError" not in result.stdout.str()
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+
+
+def test_collect_functools_partial(testdir):
+    """
+    Test that collection of functools.partial object works, and arguments
+    to the wrapped functions are dealt correctly (see #811).
+    """
+    testdir.makepyfile("""
+        import functools
+        import pytest
+
+        @pytest.fixture
+        def fix1():
+            return 'fix1'
+
+        @pytest.fixture
+        def fix2():
+            return 'fix2'
+
+        def check1(i, fix1):
+            assert i == 2
+            assert fix1 == 'fix1'
+
+        def check2(fix1, i):
+            assert i == 2
+            assert fix1 == 'fix1'
+
+        def check3(fix1, i, fix2):
+            assert i == 2
+            assert fix1 == 'fix1'
+            assert fix2 == 'fix2'
+
+        test_ok_1 = functools.partial(check1, i=2)
+        test_ok_2 = functools.partial(check1, i=2, fix1='fix1')
+        test_ok_3 = functools.partial(check1, 2)
+        test_ok_4 = functools.partial(check2, i=2)
+        test_ok_5 = functools.partial(check3, i=2)
+        test_ok_6 = functools.partial(check3, i=2, fix1='fix1')
+
+        test_fail_1 = functools.partial(check2, 2)
+        test_fail_2 = functools.partial(check3, 2)
+    """)
+    result = testdir.inline_run()
+    result.assertoutcome(passed=6, failed=2)
+
+
+def test_dont_collect_non_function_callable(testdir):
+    """Test for issue https://github.com/pytest-dev/pytest/issues/331
+
+    In this case an INTERNALERROR occurred trying to report the failure of
+    a test like this one because py test failed to get the source lines.
+    """
+    testdir.makepyfile("""
+        class Oh(object):
+            def __call__(self):
+                pass
+
+        test_a = Oh()
+
+        def test_real():
+            pass
+    """)
+    result = testdir.runpytest('-rw')
+    result.stdout.fnmatch_lines([
+        '*collected 1 item*',
+        "*cannot collect 'test_a' because it is not a function*",
+        '*1 passed, 1 warnings in *',
+    ])
+
+
+def test_class_injection_does_not_break_collection(testdir):
+    """Tests whether injection during collection time will terminate testing.
+
+    In this case the error should not occur if the TestClass itself
+    is modified during collection time, and the original method list
+    is still used for collection.
+    """
+    testdir.makeconftest("""
+        from test_inject import TestClass
+        def pytest_generate_tests(metafunc):
+            TestClass.changed_var = {}
+    """)
+    testdir.makepyfile(test_inject='''
+         class TestClass(object):
+            def test_injection(self):
+                """Test being parametrized."""
+                pass
+    ''')
+    result = testdir.runpytest()
+    assert "RuntimeError: dictionary changed size during iteration" not in result.stdout.str()
+    result.stdout.fnmatch_lines(['*1 passed*'])
+
+
+def test_syntax_error_with_non_ascii_chars(testdir):
+    """Fix decoding issue while formatting SyntaxErrors during collection (#578)
+    """
+    testdir.makepyfile(u"""
+    # -*- coding: UTF-8 -*-
+
+    ☃
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*ERROR collecting*',
+        '*SyntaxError*',
+        '*1 error in*',
+    ])
+
+
+def test_skip_duplicates_by_default(testdir):
+    """Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609)
+
+    Ignore duplicate directories.
+    """
+    a = testdir.mkdir("a")
+    fh = a.join("test_a.py")
+    fh.write(_pytest._code.Source("""
+        import pytest
+        def test_real():
+            pass
+    """))
+    result = testdir.runpytest(a.strpath, a.strpath)
+    result.stdout.fnmatch_lines([
+        '*collected 1 item*',
+    ])
+
+
+def test_keep_duplicates(testdir):
+    """Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609)
+
+    Use --keep-duplicates to collect tests from duplicate directories.
+    """
+    a = testdir.mkdir("a")
+    fh = a.join("test_a.py")
+    fh.write(_pytest._code.Source("""
+        import pytest
+        def test_real():
+            pass
+    """))
+    result = testdir.runpytest("--keep-duplicates", a.strpath, a.strpath)
+    result.stdout.fnmatch_lines([
+        '*collected 2 item*',
+    ])
diff --git a/tools/third_party/pytest/testing/python/fixture.py b/tools/third_party/pytest/testing/python/fixture.py
new file mode 100644
index 0000000..b159e8e
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/fixture.py
@@ -0,0 +1,3171 @@
+from textwrap import dedent
+
+import _pytest._code
+import pytest
+from _pytest.pytester import get_public_names
+from _pytest.fixtures import FixtureLookupError
+from _pytest import fixtures
+
+
+def test_getfuncargnames():
+    def f():
+        pass
+    assert not fixtures.getfuncargnames(f)
+
+    def g(arg):
+        pass
+    assert fixtures.getfuncargnames(g) == ('arg',)
+
+    def h(arg1, arg2="hello"):
+        pass
+    assert fixtures.getfuncargnames(h) == ('arg1',)
+
+    def h(arg1, arg2, arg3="hello"):
+        pass
+    assert fixtures.getfuncargnames(h) == ('arg1', 'arg2')
+
+    class A(object):
+        def f(self, arg1, arg2="hello"):
+            pass
+
+        @staticmethod
+        def static(arg1, arg2):
+            pass
+
+    assert fixtures.getfuncargnames(A().f) == ('arg1',)
+    assert fixtures.getfuncargnames(A.static, cls=A) == ('arg1', 'arg2')
+
+
+class TestFillFixtures(object):
+    def test_fillfuncargs_exposed(self):
+        # used by oejskit, kept for compatibility
+        assert pytest._fillfuncargs == fixtures.fillfixtures
+
+    def test_funcarg_lookupfails(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def xyzsomething(request):
+                return 42
+
+            def test_func(some):
+                pass
+        """)
+        result = testdir.runpytest()  # "--collect-only")
+        assert result.ret != 0
+        result.stdout.fnmatch_lines([
+            "*def test_func(some)*",
+            "*fixture*some*not found*",
+            "*xyzsomething*",
+        ])
+
+    def test_funcarg_basic(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+
+            @pytest.fixture
+            def some(request):
+                return request.function.__name__
+            @pytest.fixture
+            def other(request):
+                return 42
+            def test_func(some, other):
+                pass
+        """)
+        fixtures.fillfixtures(item)
+        del item.funcargs["request"]
+        assert len(get_public_names(item.funcargs)) == 2
+        assert item.funcargs['some'] == "test_func"
+        assert item.funcargs['other'] == 42
+
+    def test_funcarg_lookup_modulelevel(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def something(request):
+                return request.function.__name__
+
+            class TestClass(object):
+                def test_method(self, something):
+                    assert something == "test_method"
+            def test_func(something):
+                assert something == "test_func"
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_funcarg_lookup_classlevel(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            class TestClass(object):
+
+                @pytest.fixture
+                def something(self, request):
+                    return request.instance
+
+                def test_method(self, something):
+                    assert something is self
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*1 passed*"
+        ])
+
+    def test_conftest_funcargs_only_available_in_subdir(self, testdir):
+        sub1 = testdir.mkpydir("sub1")
+        sub2 = testdir.mkpydir("sub2")
+        sub1.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+            @pytest.fixture
+            def arg1(request):
+                pytest.raises(Exception, "request.getfixturevalue('arg2')")
+        """))
+        sub2.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+            @pytest.fixture
+            def arg2(request):
+                pytest.raises(Exception, "request.getfixturevalue('arg1')")
+        """))
+
+        sub1.join("test_in_sub1.py").write("def test_1(arg1): pass")
+        sub2.join("test_in_sub2.py").write("def test_2(arg2): pass")
+        result = testdir.runpytest("-v")
+        result.assert_outcomes(passed=2)
+
+    def test_extend_fixture_module_class(self, testdir):
+        testfile = testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def spam():
+                return 'spam'
+
+            class TestSpam(object):
+
+                 @pytest.fixture
+                 def spam(self, spam):
+                     return spam * 2
+
+                 def test_spam(self, spam):
+                     assert spam == 'spamspam'
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*1 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_extend_fixture_conftest_module(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def spam():
+                return 'spam'
+        """)
+        testfile = testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def spam(spam):
+                return spam * 2
+
+            def test_spam(spam):
+                assert spam == 'spamspam'
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*1 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_extend_fixture_conftest_conftest(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def spam():
+                return 'spam'
+        """)
+        pkg = testdir.mkpydir("pkg")
+        pkg.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+
+            @pytest.fixture
+            def spam(spam):
+                return spam * 2
+        """))
+        testfile = pkg.join("test_spam.py")
+        testfile.write(_pytest._code.Source("""
+            def test_spam(spam):
+                assert spam == "spamspam"
+        """))
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*1 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_extend_fixture_conftest_plugin(self, testdir):
+        testdir.makepyfile(testplugin="""
+            import pytest
+
+            @pytest.fixture
+            def foo():
+                return 7
+        """)
+        testdir.syspathinsert()
+        testdir.makeconftest("""
+            import pytest
+
+            pytest_plugins = 'testplugin'
+
+            @pytest.fixture
+            def foo(foo):
+                return foo + 7
+        """)
+        testdir.makepyfile("""
+            def test_foo(foo):
+                assert foo == 14
+        """)
+        result = testdir.runpytest('-s')
+        assert result.ret == 0
+
+    def test_extend_fixture_plugin_plugin(self, testdir):
+        # Two plugins should extend each order in loading order
+        testdir.makepyfile(testplugin0="""
+            import pytest
+
+            @pytest.fixture
+            def foo():
+                return 7
+        """)
+        testdir.makepyfile(testplugin1="""
+            import pytest
+
+            @pytest.fixture
+            def foo(foo):
+                return foo + 7
+        """)
+        testdir.syspathinsert()
+        testdir.makepyfile("""
+            pytest_plugins = ['testplugin0', 'testplugin1']
+
+            def test_foo(foo):
+                assert foo == 14
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 0
+
+    def test_override_parametrized_fixture_conftest_module(self, testdir):
+        """Test override of the parametrized fixture with non-parametrized one on the test module level."""
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture(params=[1, 2, 3])
+            def spam(request):
+                return request.param
+        """)
+        testfile = testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def spam():
+                return 'spam'
+
+            def test_spam(spam):
+                assert spam == 'spam'
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*1 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_override_parametrized_fixture_conftest_conftest(self, testdir):
+        """Test override of the parametrized fixture with non-parametrized one on the conftest level."""
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture(params=[1, 2, 3])
+            def spam(request):
+                return request.param
+        """)
+        subdir = testdir.mkpydir('subdir')
+        subdir.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+
+            @pytest.fixture
+            def spam():
+                return 'spam'
+        """))
+        testfile = subdir.join("test_spam.py")
+        testfile.write(_pytest._code.Source("""
+            def test_spam(spam):
+                assert spam == "spam"
+        """))
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*1 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_override_non_parametrized_fixture_conftest_module(self, testdir):
+        """Test override of the non-parametrized fixture with parametrized one on the test module level."""
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def spam():
+                return 'spam'
+        """)
+        testfile = testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(params=[1, 2, 3])
+            def spam(request):
+                return request.param
+
+            params = {'spam': 1}
+
+            def test_spam(spam):
+                assert spam == params['spam']
+                params['spam'] += 1
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*3 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*3 passed*"])
+
+    def test_override_non_parametrized_fixture_conftest_conftest(self, testdir):
+        """Test override of the non-parametrized fixture with parametrized one on the conftest level."""
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def spam():
+                return 'spam'
+        """)
+        subdir = testdir.mkpydir('subdir')
+        subdir.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+
+            @pytest.fixture(params=[1, 2, 3])
+            def spam(request):
+                return request.param
+        """))
+        testfile = subdir.join("test_spam.py")
+        testfile.write(_pytest._code.Source("""
+            params = {'spam': 1}
+
+            def test_spam(spam):
+                assert spam == params['spam']
+                params['spam'] += 1
+        """))
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*3 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*3 passed*"])
+
+    def test_override_autouse_fixture_with_parametrized_fixture_conftest_conftest(self, testdir):
+        """Test override of the autouse fixture with parametrized one on the conftest level.
+        This test covers the issue explained in issue 1601
+        """
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture(autouse=True)
+            def spam():
+                return 'spam'
+        """)
+        subdir = testdir.mkpydir('subdir')
+        subdir.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+
+            @pytest.fixture(params=[1, 2, 3])
+            def spam(request):
+                return request.param
+        """))
+        testfile = subdir.join("test_spam.py")
+        testfile.write(_pytest._code.Source("""
+            params = {'spam': 1}
+
+            def test_spam(spam):
+                assert spam == params['spam']
+                params['spam'] += 1
+        """))
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(["*3 passed*"])
+        result = testdir.runpytest(testfile)
+        result.stdout.fnmatch_lines(["*3 passed*"])
+
+    def test_autouse_fixture_plugin(self, testdir):
+        # A fixture from a plugin has no baseid set, which screwed up
+        # the autouse fixture handling.
+        testdir.makepyfile(testplugin="""
+            import pytest
+
+            @pytest.fixture(autouse=True)
+            def foo(request):
+                request.function.foo = 7
+        """)
+        testdir.syspathinsert()
+        testdir.makepyfile("""
+            pytest_plugins = 'testplugin'
+
+            def test_foo(request):
+                assert request.function.foo == 7
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 0
+
+    def test_funcarg_lookup_error(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def a_fixture(): pass
+
+            @pytest.fixture
+            def b_fixture(): pass
+
+            @pytest.fixture
+            def c_fixture(): pass
+
+            @pytest.fixture
+            def d_fixture(): pass
+        """)
+        testdir.makepyfile("""
+            def test_lookup_error(unknown):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERROR at setup of test_lookup_error*",
+            "  def test_lookup_error(unknown):*",
+            "E       fixture 'unknown' not found",
+            ">       available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*",  # sorted
+            ">       use 'py*test --fixtures *' for help on them.",
+            "*1 error*",
+        ])
+        assert "INTERNAL" not in result.stdout.str()
+
+    def test_fixture_excinfo_leak(self, testdir):
+        # on python2 sys.excinfo would leak into fixture executions
+        testdir.makepyfile("""
+            import sys
+            import traceback
+            import pytest
+
+            @pytest.fixture
+            def leak():
+                if sys.exc_info()[0]:  # python3 bug :)
+                    traceback.print_exc()
+                #fails
+                assert sys.exc_info() == (None, None, None)
+
+            def test_leak(leak):
+                if sys.exc_info()[0]:  # python3 bug :)
+                    traceback.print_exc()
+                assert sys.exc_info() == (None, None, None)
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 0
+
+
+class TestRequestBasic(object):
+    def test_request_attributes(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+
+            @pytest.fixture
+            def something(request): pass
+            def test_func(something): pass
+        """)
+        req = fixtures.FixtureRequest(item)
+        assert req.function == item.obj
+        assert req.keywords == item.keywords
+        assert hasattr(req.module, 'test_func')
+        assert req.cls is None
+        assert req.function.__name__ == "test_func"
+        assert req.config == item.config
+        assert repr(req).find(req.function.__name__) != -1
+
+    def test_request_attributes_method(self, testdir):
+        item, = testdir.getitems("""
+            import pytest
+            class TestB(object):
+
+                @pytest.fixture
+                def something(self, request):
+                    return 1
+                def test_func(self, something):
+                    pass
+        """)
+        req = item._request
+        assert req.cls.__name__ == "TestB"
+        assert req.instance.__class__ == req.cls
+
+    def test_request_contains_funcarg_arg2fixturedefs(self, testdir):
+        modcol = testdir.getmodulecol("""
+            import pytest
+            @pytest.fixture
+            def something(request):
+                pass
+            class TestClass(object):
+                def test_method(self, something):
+                    pass
+        """)
+        item1, = testdir.genitems([modcol])
+        assert item1.name == "test_method"
+        arg2fixturedefs = fixtures.FixtureRequest(item1)._arg2fixturedefs
+        assert len(arg2fixturedefs) == 1
+        assert arg2fixturedefs['something'][0].argname == "something"
+
+    def test_getfixturevalue_recursive(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def something(request):
+                return 1
+        """)
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def something(request):
+                return request.getfixturevalue("something") + 1
+            def test_func(something):
+                assert something == 2
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    @pytest.mark.parametrize(
+        'getfixmethod', ('getfixturevalue', 'getfuncargvalue'))
+    def test_getfixturevalue(self, testdir, getfixmethod):
+        item = testdir.getitem("""
+            import pytest
+            values = [2]
+            @pytest.fixture
+            def something(request): return 1
+            @pytest.fixture
+            def other(request):
+                return values.pop()
+            def test_func(something): pass
+        """)
+        import contextlib
+        if getfixmethod == 'getfuncargvalue':
+            warning_expectation = pytest.warns(DeprecationWarning)
+        else:
+            # see #1830 for a cleaner way to accomplish this
+            @contextlib.contextmanager
+            def expecting_no_warning():
+                yield
+
+            warning_expectation = expecting_no_warning()
+
+        req = item._request
+        with warning_expectation:
+            fixture_fetcher = getattr(req, getfixmethod)
+            with pytest.raises(FixtureLookupError):
+                fixture_fetcher("notexists")
+            val = fixture_fetcher("something")
+            assert val == 1
+            val = fixture_fetcher("something")
+            assert val == 1
+            val2 = fixture_fetcher("other")
+            assert val2 == 2
+            val2 = fixture_fetcher("other")  # see about caching
+            assert val2 == 2
+            pytest._fillfuncargs(item)
+            assert item.funcargs["something"] == 1
+            assert len(get_public_names(item.funcargs)) == 2
+            assert "request" in item.funcargs
+
+    def test_request_addfinalizer(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            teardownlist = []
+            @pytest.fixture
+            def something(request):
+                request.addfinalizer(lambda: teardownlist.append(1))
+            def test_func(something): pass
+        """)
+        item.session._setupstate.prepare(item)
+        pytest._fillfuncargs(item)
+        # successively check finalization calls
+        teardownlist = item.getparent(pytest.Module).obj.teardownlist
+        ss = item.session._setupstate
+        assert not teardownlist
+        ss.teardown_exact(item, None)
+        print(ss.stack)
+        assert teardownlist == [1]
+
+    def test_mark_as_fixture_with_prefix_and_decorator_fails(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def pytest_funcarg__marked_with_prefix_and_decorator():
+                pass
+        """)
+        result = testdir.runpytest_subprocess()
+        assert result.ret != 0
+        result.stdout.fnmatch_lines([
+            "*AssertionError: fixtures cannot have*@pytest.fixture*",
+            "*pytest_funcarg__marked_with_prefix_and_decorator*"
+        ])
+
+    def test_request_addfinalizer_failing_setup(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = [1]
+            @pytest.fixture
+            def myfix(request):
+                request.addfinalizer(values.pop)
+                assert 0
+            def test_fix(myfix):
+                pass
+            def test_finalizer_ran():
+                assert not values
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(failed=1, passed=1)
+
+    def test_request_addfinalizer_failing_setup_module(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = [1, 2]
+            @pytest.fixture(scope="module")
+            def myfix(request):
+                request.addfinalizer(values.pop)
+                request.addfinalizer(values.pop)
+                assert 0
+            def test_fix(myfix):
+                pass
+        """)
+        reprec = testdir.inline_run("-s")
+        mod = reprec.getcalls("pytest_runtest_setup")[0].item.module
+        assert not mod.values
+
+    def test_request_addfinalizer_partial_setup_failure(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture
+            def something(request):
+                request.addfinalizer(lambda: values.append(None))
+            def test_func(something, missingarg):
+                pass
+            def test_second():
+                assert len(values) == 1
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*1 error*"  # XXX the whole module collection fails
+        ])
+
+    def test_request_subrequest_addfinalizer_exceptions(self, testdir):
+        """
+        Ensure exceptions raised during teardown by a finalizer are suppressed
+        until all finalizers are called, re-raising the first exception (#2440)
+        """
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            def _excepts(where):
+                raise Exception('Error in %s fixture' % where)
+            @pytest.fixture
+            def subrequest(request):
+                return request
+            @pytest.fixture
+            def something(subrequest):
+                subrequest.addfinalizer(lambda: values.append(1))
+                subrequest.addfinalizer(lambda: values.append(2))
+                subrequest.addfinalizer(lambda: _excepts('something'))
+            @pytest.fixture
+            def excepts(subrequest):
+                subrequest.addfinalizer(lambda: _excepts('excepts'))
+                subrequest.addfinalizer(lambda: values.append(3))
+            def test_first(something, excepts):
+                pass
+            def test_second():
+                assert values == [3, 2, 1]
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            '*Exception: Error in excepts fixture',
+            '* 2 passed, 1 error in *',
+        ])
+
+    def test_request_getmodulepath(self, testdir):
+        modcol = testdir.getmodulecol("def test_somefunc(): pass")
+        item, = testdir.genitems([modcol])
+        req = fixtures.FixtureRequest(item)
+        assert req.fspath == modcol.fspath
+
+    def test_request_fixturenames(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            from _pytest.pytester import get_public_names
+            @pytest.fixture()
+            def arg1():
+                pass
+            @pytest.fixture()
+            def farg(arg1):
+                pass
+            @pytest.fixture(autouse=True)
+            def sarg(tmpdir):
+                pass
+            def test_function(request, farg):
+                assert set(get_public_names(request.fixturenames)) == \
+                       set(["tmpdir", "sarg", "arg1", "request", "farg",
+                            "tmpdir_factory"])
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_funcargnames_compatattr(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                assert metafunc.funcargnames == metafunc.fixturenames
+            @pytest.fixture
+            def fn(request):
+                assert request._pyfuncitem.funcargnames == \
+                       request._pyfuncitem.fixturenames
+                return request.funcargnames, request.fixturenames
+
+            def test_hello(fn):
+                assert fn[0] == fn[1]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_setupdecorator_and_xunit(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(scope='module', autouse=True)
+            def setup_module():
+                values.append("module")
+            @pytest.fixture(autouse=True)
+            def setup_function():
+                values.append("function")
+
+            def test_func():
+                pass
+
+            class TestClass(object):
+                @pytest.fixture(scope="class", autouse=True)
+                def setup_class(self):
+                    values.append("class")
+                @pytest.fixture(autouse=True)
+                def setup_method(self):
+                    values.append("method")
+                def test_method(self):
+                    pass
+            def test_all():
+                assert values == ["module", "function", "class",
+                             "function", "method", "function"]
+        """)
+        reprec = testdir.inline_run("-v")
+        reprec.assertoutcome(passed=3)
+
+    def test_fixtures_sub_subdir_normalize_sep(self, testdir):
+        # this tests that normalization of nodeids takes place
+        b = testdir.mkdir("tests").mkdir("unit")
+        b.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+            @pytest.fixture
+            def arg1():
+                pass
+        """))
+        p = b.join("test_module.py")
+        p.write("def test_func(arg1): pass")
+        result = testdir.runpytest(p, "--fixtures")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines("""
+            *fixtures defined*conftest*
+            *arg1*
+        """)
+
+    def test_show_fixtures_color_yes(self, testdir):
+        testdir.makepyfile("def test_this(): assert 1")
+        result = testdir.runpytest('--color=yes', '--fixtures')
+        assert '\x1b[32mtmpdir' in result.stdout.str()
+
+    def test_newstyle_with_request(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture()
+            def arg(request):
+                pass
+            def test_1(arg):
+                pass
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_setupcontext_no_param(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(params=[1,2])
+            def arg(request):
+                return request.param
+
+            @pytest.fixture(autouse=True)
+            def mysetup(request, arg):
+                assert not hasattr(request, "param")
+            def test_1(arg):
+                assert arg in (1,2)
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+
+class TestRequestMarking(object):
+    def test_applymarker(self, testdir):
+        item1, item2 = testdir.getitems("""
+            import pytest
+
+            @pytest.fixture
+            def something(request):
+                pass
+            class TestClass(object):
+                def test_func1(self, something):
+                    pass
+                def test_func2(self, something):
+                    pass
+        """)
+        req1 = fixtures.FixtureRequest(item1)
+        assert 'xfail' not in item1.keywords
+        req1.applymarker(pytest.mark.xfail)
+        assert 'xfail' in item1.keywords
+        assert 'skipif' not in item1.keywords
+        req1.applymarker(pytest.mark.skipif)
+        assert 'skipif' in item1.keywords
+        pytest.raises(ValueError, "req1.applymarker(42)")
+
+    def test_accesskeywords(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture()
+            def keywords(request):
+                return request.keywords
+            @pytest.mark.XYZ
+            def test_function(keywords):
+                assert keywords["XYZ"]
+                assert "abc" not in keywords
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_accessmarker_dynamic(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            @pytest.fixture()
+            def keywords(request):
+                return request.keywords
+
+            @pytest.fixture(scope="class", autouse=True)
+            def marking(request):
+                request.applymarker(pytest.mark.XYZ("hello"))
+        """)
+        testdir.makepyfile("""
+            import pytest
+            def test_fun1(keywords):
+                assert keywords["XYZ"] is not None
+                assert "abc" not in keywords
+            def test_fun2(keywords):
+                assert keywords["XYZ"] is not None
+                assert "abc" not in keywords
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+
+class TestRequestCachedSetup(object):
+    def test_request_cachedsetup_defaultmodule(self, testdir):
+        reprec = testdir.inline_runsource("""
+            mysetup = ["hello",].pop
+
+            import pytest
+
+            @pytest.fixture
+            def something(request):
+                return request.cached_setup(mysetup, scope="module")
+
+            def test_func1(something):
+                assert something == "hello"
+            class TestClass(object):
+                def test_func1a(self, something):
+                    assert something == "hello"
+        """)
+        reprec.assertoutcome(passed=2)
+
+    def test_request_cachedsetup_class(self, testdir):
+        reprec = testdir.inline_runsource("""
+            mysetup = ["hello", "hello2", "hello3"].pop
+
+            import pytest
+            @pytest.fixture
+            def something(request):
+                return request.cached_setup(mysetup, scope="class")
+            def test_func1(something):
+                assert something == "hello3"
+            def test_func2(something):
+                assert something == "hello2"
+            class TestClass(object):
+                def test_func1a(self, something):
+                    assert something == "hello"
+                def test_func2b(self, something):
+                    assert something == "hello"
+        """)
+        reprec.assertoutcome(passed=4)
+
+    def test_request_cachedsetup_extrakey(self, testdir):
+        item1 = testdir.getitem("def test_func(): pass")
+        req1 = fixtures.FixtureRequest(item1)
+        values = ["hello", "world"]
+
+        def setup():
+            return values.pop()
+
+        ret1 = req1.cached_setup(setup, extrakey=1)
+        ret2 = req1.cached_setup(setup, extrakey=2)
+        assert ret2 == "hello"
+        assert ret1 == "world"
+        ret1b = req1.cached_setup(setup, extrakey=1)
+        ret2b = req1.cached_setup(setup, extrakey=2)
+        assert ret1 == ret1b
+        assert ret2 == ret2b
+
+    def test_request_cachedsetup_cache_deletion(self, testdir):
+        item1 = testdir.getitem("def test_func(): pass")
+        req1 = fixtures.FixtureRequest(item1)
+        values = []
+
+        def setup():
+            values.append("setup")
+
+        def teardown(val):
+            values.append("teardown")
+
+        req1.cached_setup(setup, teardown, scope="function")
+        assert values == ['setup']
+        # artificial call of finalizer
+        setupstate = req1._pyfuncitem.session._setupstate
+        setupstate._callfinalizers(item1)
+        assert values == ["setup", "teardown"]
+        req1.cached_setup(setup, teardown, scope="function")
+        assert values == ["setup", "teardown", "setup"]
+        setupstate._callfinalizers(item1)
+        assert values == ["setup", "teardown", "setup", "teardown"]
+
+    def test_request_cached_setup_two_args(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def arg1(request):
+                return request.cached_setup(lambda: 42)
+            @pytest.fixture
+            def arg2(request):
+                return request.cached_setup(lambda: 17)
+            def test_two_different_setups(arg1, arg2):
+                assert arg1 != arg2
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*1 passed*"
+        ])
+
+    def test_request_cached_setup_getfixturevalue(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def arg1(request):
+                arg1 = request.getfixturevalue("arg2")
+                return request.cached_setup(lambda: arg1 + 1)
+            @pytest.fixture
+            def arg2(request):
+                return request.cached_setup(lambda: 10)
+            def test_two_funcarg(arg1):
+                assert arg1 == 11
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*1 passed*"
+        ])
+
+    def test_request_cached_setup_functional(self, testdir):
+        testdir.makepyfile(test_0="""
+            import pytest
+            values = []
+            @pytest.fixture
+            def something(request):
+                val = request.cached_setup(fsetup, fteardown)
+                return val
+            def fsetup(mycache=[1]):
+                values.append(mycache.pop())
+                return values
+            def fteardown(something):
+                values.remove(something[0])
+                values.append(2)
+            def test_list_once(something):
+                assert something == [1]
+            def test_list_twice(something):
+                assert something == [1]
+        """)
+        testdir.makepyfile(test_1="""
+            import test_0 # should have run already
+            def test_check_test0_has_teardown_correct():
+                assert test_0.values == [2]
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*3 passed*"
+        ])
+
+    def test_issue117_sessionscopeteardown(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def app(request):
+                app = request.cached_setup(
+                    scope='session',
+                    setup=lambda: 0,
+                    teardown=lambda x: 3/x)
+                return app
+            def test_func(app):
+                pass
+        """)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        result.stdout.fnmatch_lines([
+            "*3/x*",
+            "*ZeroDivisionError*",
+        ])
+
+
+class TestFixtureUsages(object):
+    def test_noargfixturedec(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture
+            def arg1():
+                return 1
+
+            def test_func(arg1):
+                assert arg1 == 1
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_receives_funcargs(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture()
+            def arg1():
+                return 1
+
+            @pytest.fixture()
+            def arg2(arg1):
+                return arg1 + 1
+
+            def test_add(arg2):
+                assert arg2 == 2
+            def test_all(arg1, arg2):
+                assert arg1 == 1
+                assert arg2 == 2
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_receives_funcargs_scope_mismatch(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope="function")
+            def arg1():
+                return 1
+
+            @pytest.fixture(scope="module")
+            def arg2(arg1):
+                return arg1 + 1
+
+            def test_add(arg2):
+                assert arg2 == 2
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ScopeMismatch*involved factories*",
+            "* def arg2*",
+            "* def arg1*",
+            "*1 error*"
+        ])
+
+    def test_receives_funcargs_scope_mismatch_issue660(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope="function")
+            def arg1():
+                return 1
+
+            @pytest.fixture(scope="module")
+            def arg2(arg1):
+                return arg1 + 1
+
+            def test_add(arg1, arg2):
+                assert arg2 == 2
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ScopeMismatch*involved factories*",
+            "* def arg2*",
+            "*1 error*"
+        ])
+
+    def test_invalid_scope(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope="functions")
+            def badscope():
+                pass
+
+            def test_nothing(badscope):
+                pass
+        """)
+        result = testdir.runpytest_inprocess()
+        result.stdout.fnmatch_lines(
+            ("*ValueError: fixture badscope from test_invalid_scope.py has an unsupported"
+             " scope value 'functions'")
+        )
+
+    def test_funcarg_parametrized_and_used_twice(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(params=[1,2])
+            def arg1(request):
+                values.append(1)
+                return request.param
+
+            @pytest.fixture()
+            def arg2(arg1):
+                return arg1 + 1
+
+            def test_add(arg1, arg2):
+                assert arg2 == arg1 + 1
+                assert len(values) == arg1
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*2 passed*"
+        ])
+
+    def test_factory_uses_unknown_funcarg_as_dependency_error(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture()
+            def fail(missing):
+                return
+
+            @pytest.fixture()
+            def call_fail(fail):
+                return
+
+            def test_missing(call_fail):
+                pass
+            """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            *pytest.fixture()*
+            *def call_fail(fail)*
+            *pytest.fixture()*
+            *def fail*
+            *fixture*'missing'*not found*
+        """)
+
+    def test_factory_setup_as_classes_fails(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            class arg1(object):
+                def __init__(self, request):
+                    self.x = 1
+            arg1 = pytest.fixture()(arg1)
+
+        """)
+        reprec = testdir.inline_run()
+        values = reprec.getfailedcollections()
+        assert len(values) == 1
+
+    def test_request_can_be_overridden(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture()
+            def request(request):
+                request.a = 1
+                return request
+            def test_request(request):
+                assert request.a == 1
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_usefixtures_marker(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            values = []
+
+            @pytest.fixture(scope="class")
+            def myfix(request):
+                request.cls.hello = "world"
+                values.append(1)
+
+            class TestClass(object):
+                def test_one(self):
+                    assert self.hello == "world"
+                    assert len(values) == 1
+                def test_two(self):
+                    assert self.hello == "world"
+                    assert len(values) == 1
+            pytest.mark.usefixtures("myfix")(TestClass)
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_usefixtures_ini(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            usefixtures = myfix
+        """)
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture(scope="class")
+            def myfix(request):
+                request.cls.hello = "world"
+
+        """)
+        testdir.makepyfile("""
+            class TestClass(object):
+                def test_one(self):
+                    assert self.hello == "world"
+                def test_two(self):
+                    assert self.hello == "world"
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_usefixtures_seen_in_showmarkers(self, testdir):
+        result = testdir.runpytest("--markers")
+        result.stdout.fnmatch_lines("""
+            *usefixtures(fixturename1*mark tests*fixtures*
+        """)
+
+    def test_request_instance_issue203(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            class TestClass(object):
+                @pytest.fixture
+                def setup1(self, request):
+                    assert self == request.instance
+                    self.arg1 = 1
+                def test_hello(self, setup1):
+                    assert self.arg1 == 1
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_fixture_parametrized_with_iterator(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            values = []
+            def f():
+                yield 1
+                yield 2
+            dec = pytest.fixture(scope="module", params=f())
+
+            @dec
+            def arg(request):
+                return request.param
+            @dec
+            def arg2(request):
+                return request.param
+
+            def test_1(arg):
+                values.append(arg)
+            def test_2(arg2):
+                values.append(arg2*10)
+        """)
+        reprec = testdir.inline_run("-v")
+        reprec.assertoutcome(passed=4)
+        values = reprec.getcalls("pytest_runtest_call")[0].item.module.values
+        assert values == [1, 2, 10, 20]
+
+
+class TestFixtureManagerParseFactories(object):
+
+    @pytest.fixture
+    def testdir(self, request):
+        testdir = request.getfixturevalue("testdir")
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture
+            def hello(request):
+                return "conftest"
+
+            @pytest.fixture
+            def fm(request):
+                return request._fixturemanager
+
+            @pytest.fixture
+            def item(request):
+                return request._pyfuncitem
+        """)
+        return testdir
+
+    def test_parsefactories_evil_objects_issue214(self, testdir):
+        testdir.makepyfile("""
+            class A(object):
+                def __call__(self):
+                    pass
+                def __getattr__(self, name):
+                    raise RuntimeError()
+            a = A()
+            def test_hello():
+                pass
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1, failed=0)
+
+    def test_parsefactories_conftest(self, testdir):
+        testdir.makepyfile("""
+            def test_hello(item, fm):
+                for name in ("fm", "hello", "item"):
+                    faclist = fm.getfixturedefs(name, item.nodeid)
+                    assert len(faclist) == 1
+                    fac = faclist[0]
+                    assert fac.func.__name__ == name
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=1)
+
+    def test_parsefactories_conftest_and_module_and_class(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def hello(request):
+                return "module"
+            class TestClass(object):
+                @pytest.fixture
+                def hello(self, request):
+                    return "class"
+                def test_hello(self, item, fm):
+                    faclist = fm.getfixturedefs("hello", item.nodeid)
+                    print (faclist)
+                    assert len(faclist) == 3
+                    assert faclist[0].func(item._request) == "conftest"
+                    assert faclist[1].func(item._request) == "module"
+                    assert faclist[2].func(item._request) == "class"
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=1)
+
+    def test_parsefactories_relative_node_ids(self, testdir):
+        # example mostly taken from:
+        # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html
+        runner = testdir.mkdir("runner")
+        package = testdir.mkdir("package")
+        package.join("conftest.py").write(dedent("""\
+            import pytest
+            @pytest.fixture
+            def one():
+                return 1
+        """))
+        package.join("test_x.py").write(dedent("""\
+            def test_x(one):
+                assert one == 1
+        """))
+        sub = package.mkdir("sub")
+        sub.join("__init__.py").ensure()
+        sub.join("conftest.py").write(dedent("""\
+            import pytest
+            @pytest.fixture
+            def one():
+                return 2
+        """))
+        sub.join("test_y.py").write(dedent("""\
+            def test_x(one):
+                assert one == 2
+        """))
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+        with runner.as_cwd():
+            reprec = testdir.inline_run("..")
+            reprec.assertoutcome(passed=2)
+
+
+class TestAutouseDiscovery(object):
+
+    @pytest.fixture
+    def testdir(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            @pytest.fixture(autouse=True)
+            def perfunction(request, tmpdir):
+                pass
+
+            @pytest.fixture()
+            def arg1(tmpdir):
+                pass
+            @pytest.fixture(autouse=True)
+            def perfunction2(arg1):
+                pass
+
+            @pytest.fixture
+            def fm(request):
+                return request._fixturemanager
+
+            @pytest.fixture
+            def item(request):
+                return request._pyfuncitem
+        """)
+        return testdir
+
+    def test_parsefactories_conftest(self, testdir):
+        testdir.makepyfile("""
+            from _pytest.pytester import get_public_names
+            def test_check_setup(item, fm):
+                autousenames = fm._getautousenames(item.nodeid)
+                assert len(get_public_names(autousenames)) == 2
+                assert "perfunction2" in autousenames
+                assert "perfunction" in autousenames
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=1)
+
+    def test_two_classes_separated_autouse(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            class TestA(object):
+                values = []
+                @pytest.fixture(autouse=True)
+                def setup1(self):
+                    self.values.append(1)
+                def test_setup1(self):
+                    assert self.values == [1]
+            class TestB(object):
+                values = []
+                @pytest.fixture(autouse=True)
+                def setup2(self):
+                    self.values.append(1)
+                def test_setup2(self):
+                    assert self.values == [1]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_setup_at_classlevel(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            class TestClass(object):
+                @pytest.fixture(autouse=True)
+                def permethod(self, request):
+                    request.instance.funcname = request.function.__name__
+                def test_method1(self):
+                    assert self.funcname == "test_method1"
+                def test_method2(self):
+                    assert self.funcname == "test_method2"
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=2)
+
+    @pytest.mark.xfail(reason="'enabled' feature not implemented")
+    def test_setup_enabled_functionnode(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            def enabled(parentnode, markers):
+                return "needsdb" in markers
+
+            @pytest.fixture(params=[1,2])
+            def db(request):
+                return request.param
+
+            @pytest.fixture(enabled=enabled, autouse=True)
+            def createdb(db):
+                pass
+
+            def test_func1(request):
+                assert "db" not in request.fixturenames
+
+            @pytest.mark.needsdb
+            def test_func2(request):
+                assert "db" in request.fixturenames
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=2)
+
+    def test_callables_nocode(self, testdir):
+        """
+        a imported mock.call would break setup/factory discovery
+        due to it being callable and __code__ not being a code object
+        """
+        testdir.makepyfile("""
+           class _call(tuple):
+               def __call__(self, *k, **kw):
+                   pass
+               def __getattr__(self, k):
+                   return self
+
+           call = _call()
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(failed=0, passed=0)
+
+    def test_autouse_in_conftests(self, testdir):
+        a = testdir.mkdir("a")
+        b = testdir.mkdir("a1")
+        conftest = testdir.makeconftest("""
+            import pytest
+            @pytest.fixture(autouse=True)
+            def hello():
+                xxx
+        """)
+        conftest.move(a.join(conftest.basename))
+        a.join("test_something.py").write("def test_func(): pass")
+        b.join("test_otherthing.py").write("def test_func(): pass")
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            *1 passed*1 error*
+        """)
+
+    def test_autouse_in_module_and_two_classes(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(autouse=True)
+            def append1():
+                values.append("module")
+            def test_x():
+                assert values == ["module"]
+
+            class TestA(object):
+                @pytest.fixture(autouse=True)
+                def append2(self):
+                    values.append("A")
+                def test_hello(self):
+                    assert values == ["module", "module", "A"], values
+            class TestA2(object):
+                def test_world(self):
+                    assert values == ["module", "module", "A", "module"], values
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=3)
+
+
+class TestAutouseManagement(object):
+    def test_autouse_conftest_mid_directory(self, testdir):
+        pkgdir = testdir.mkpydir("xyz123")
+        pkgdir.join("conftest.py").write(_pytest._code.Source("""
+            import pytest
+            @pytest.fixture(autouse=True)
+            def app():
+                import sys
+                sys._myapp = "hello"
+        """))
+        t = pkgdir.ensure("tests", "test_app.py")
+        t.write(_pytest._code.Source("""
+            import sys
+            def test_app():
+                assert sys._myapp == "hello"
+        """))
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=1)
+
+    def test_autouse_honored_for_yield(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(autouse=True)
+            def tst():
+                global x
+                x = 3
+            def test_gen():
+                def f(hello):
+                    assert x == abs(hello)
+                yield f, 3
+                yield f, -3
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_funcarg_and_setup(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(scope="module")
+            def arg():
+                values.append(1)
+                return 0
+            @pytest.fixture(scope="module", autouse=True)
+            def something(arg):
+                values.append(2)
+
+            def test_hello(arg):
+                assert len(values) == 2
+                assert values == [1,2]
+                assert arg == 0
+
+            def test_hello2(arg):
+                assert len(values) == 2
+                assert values == [1,2]
+                assert arg == 0
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_uses_parametrized_resource(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(params=[1,2])
+            def arg(request):
+                return request.param
+
+            @pytest.fixture(autouse=True)
+            def something(arg):
+                values.append(arg)
+
+            def test_hello():
+                if len(values) == 1:
+                    assert values == [1]
+                elif len(values) == 2:
+                    assert values == [1, 2]
+                else:
+                    0/0
+
+        """)
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=2)
+
+    def test_session_parametrized_function(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            values = []
+
+            @pytest.fixture(scope="session", params=[1,2])
+            def arg(request):
+               return request.param
+
+            @pytest.fixture(scope="function", autouse=True)
+            def append(request, arg):
+                if request.function.__name__ == "test_some":
+                    values.append(arg)
+
+            def test_some():
+                pass
+
+            def test_result(arg):
+                assert len(values) == arg
+                assert values[:arg] == [1,2][:arg]
+        """)
+        reprec = testdir.inline_run("-v", "-s")
+        reprec.assertoutcome(passed=4)
+
+    def test_class_function_parametrization_finalization(self, testdir):
+        p = testdir.makeconftest("""
+            import pytest
+            import pprint
+
+            values = []
+
+            @pytest.fixture(scope="function", params=[1,2])
+            def farg(request):
+                return request.param
+
+            @pytest.fixture(scope="class", params=list("ab"))
+            def carg(request):
+                return request.param
+
+            @pytest.fixture(scope="function", autouse=True)
+            def append(request, farg, carg):
+                def fin():
+                    values.append("fin_%s%s" % (carg, farg))
+                request.addfinalizer(fin)
+        """)
+        testdir.makepyfile("""
+            import pytest
+
+            class TestClass(object):
+                def test_1(self):
+                    pass
+            class TestClass2(object):
+                def test_2(self):
+                    pass
+        """)
+        confcut = "--confcutdir={0}".format(testdir.tmpdir)
+        reprec = testdir.inline_run("-v", "-s", confcut)
+        reprec.assertoutcome(passed=8)
+        config = reprec.getcalls("pytest_unconfigure")[0].config
+        values = config.pluginmanager._getconftestmodules(p)[0].values
+        assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
+
+    def test_scope_ordering(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(scope="function", autouse=True)
+            def fappend2():
+                values.append(2)
+            @pytest.fixture(scope="class", autouse=True)
+            def classappend3():
+                values.append(3)
+            @pytest.fixture(scope="module", autouse=True)
+            def mappend():
+                values.append(1)
+
+            class TestHallo(object):
+                def test_method(self):
+                    assert values == [1,3,2]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_parametrization_setup_teardown_ordering(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            def pytest_generate_tests(metafunc):
+                if metafunc.cls is not None:
+                    metafunc.parametrize("item", [1,2], scope="class")
+            class TestClass(object):
+                @pytest.fixture(scope="class", autouse=True)
+                def addteardown(self, item, request):
+                    values.append("setup-%d" % item)
+                    request.addfinalizer(lambda: values.append("teardown-%d" % item))
+                def test_step1(self, item):
+                    values.append("step1-%d" % item)
+                def test_step2(self, item):
+                    values.append("step2-%d" % item)
+
+            def test_finish():
+                print (values)
+                assert values == ["setup-1", "step1-1", "step2-1", "teardown-1",
+                             "setup-2", "step1-2", "step2-2", "teardown-2",]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=5)
+
+    def test_ordering_autouse_before_explicit(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            values = []
+            @pytest.fixture(autouse=True)
+            def fix1():
+                values.append(1)
+            @pytest.fixture()
+            def arg1():
+                values.append(2)
+            def test_hello(arg1):
+                assert values == [1,2]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    @pytest.mark.issue226
+    @pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00", "p01"])
+    @pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10", "p11"])
+    def test_ordering_dependencies_torndown_first(self, testdir, param1, param2):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(%(param1)s)
+            def arg1(request):
+                request.addfinalizer(lambda: values.append("fin1"))
+                values.append("new1")
+            @pytest.fixture(%(param2)s)
+            def arg2(request, arg1):
+                request.addfinalizer(lambda: values.append("fin2"))
+                values.append("new2")
+
+            def test_arg(arg2):
+                pass
+            def test_check():
+                assert values == ["new1", "new2", "fin2", "fin1"]
+        """ % locals())
+        reprec = testdir.inline_run("-s")
+        reprec.assertoutcome(passed=2)
+
+
+class TestFixtureMarker(object):
+    def test_parametrize(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(params=["a", "b", "c"])
+            def arg(request):
+                return request.param
+            values = []
+            def test_param(arg):
+                values.append(arg)
+            def test_result():
+                assert values == list("abc")
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=4)
+
+    def test_multiple_parametrization_issue_736(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(params=[1,2,3])
+            def foo(request):
+                return request.param
+
+            @pytest.mark.parametrize('foobar', [4,5,6])
+            def test_issue(foo, foobar):
+                assert foo in [1,2,3]
+                assert foobar in [4,5,6]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=9)
+
+    @pytest.mark.parametrize('param_args', ["'fixt, val'", "'fixt,val'", "['fixt', 'val']", "('fixt', 'val')"])
+    def test_override_parametrized_fixture_issue_979(self, testdir, param_args):
+        """Make sure a parametrized argument can override a parametrized fixture.
+
+        This was a regression introduced in the fix for #736.
+        """
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(params=[1, 2])
+            def fixt(request):
+                return request.param
+
+            @pytest.mark.parametrize(%s, [(3, 'x'), (4, 'x')])
+            def test_foo(fixt, val):
+                pass
+        """ % param_args)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_scope_session(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(scope="module")
+            def arg():
+                values.append(1)
+                return 1
+
+            def test_1(arg):
+                assert arg == 1
+            def test_2(arg):
+                assert arg == 1
+                assert len(values) == 1
+            class TestClass(object):
+                def test3(self, arg):
+                    assert arg == 1
+                    assert len(values) == 1
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=3)
+
+    def test_scope_session_exc(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(scope="session")
+            def fix():
+                values.append(1)
+                pytest.skip('skipping')
+
+            def test_1(fix):
+                pass
+            def test_2(fix):
+                pass
+            def test_last():
+                assert values == [1]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(skipped=2, passed=1)
+
+    def test_scope_session_exc_two_fix(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            m = []
+            @pytest.fixture(scope="session")
+            def a():
+                values.append(1)
+                pytest.skip('skipping')
+            @pytest.fixture(scope="session")
+            def b(a):
+                m.append(1)
+
+            def test_1(b):
+                pass
+            def test_2(b):
+                pass
+            def test_last():
+                assert values == [1]
+                assert m == []
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(skipped=2, passed=1)
+
+    def test_scope_exc(self, testdir):
+        testdir.makepyfile(
+            test_foo="""
+                def test_foo(fix):
+                    pass
+            """,
+            test_bar="""
+                def test_bar(fix):
+                    pass
+            """,
+            conftest="""
+                import pytest
+                reqs = []
+                @pytest.fixture(scope="session")
+                def fix(request):
+                    reqs.append(1)
+                    pytest.skip()
+                @pytest.fixture
+                def req_list():
+                    return reqs
+            """,
+            test_real="""
+                def test_last(req_list):
+                    assert req_list == [1]
+            """
+        )
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(skipped=2, passed=1)
+
+    def test_scope_module_uses_session(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(scope="module")
+            def arg():
+                values.append(1)
+                return 1
+
+            def test_1(arg):
+                assert arg == 1
+            def test_2(arg):
+                assert arg == 1
+                assert len(values) == 1
+            class TestClass(object):
+                def test3(self, arg):
+                    assert arg == 1
+                    assert len(values) == 1
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=3)
+
+    def test_scope_module_and_finalizer(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            finalized_list = []
+            created_list = []
+            @pytest.fixture(scope="module")
+            def arg(request):
+                created_list.append(1)
+                assert request.scope == "module"
+                request.addfinalizer(lambda: finalized_list.append(1))
+            @pytest.fixture
+            def created(request):
+                return len(created_list)
+            @pytest.fixture
+            def finalized(request):
+                return len(finalized_list)
+        """)
+        testdir.makepyfile(
+            test_mod1="""
+                def test_1(arg, created, finalized):
+                    assert created == 1
+                    assert finalized == 0
+                def test_2(arg, created, finalized):
+                    assert created == 1
+                    assert finalized == 0""",
+            test_mod2="""
+                def test_3(arg, created, finalized):
+                    assert created == 2
+                    assert finalized == 1""",
+            test_mode3="""
+                def test_4(arg, created, finalized):
+                    assert created == 3
+                    assert finalized == 2
+            """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=4)
+
+    @pytest.mark.parametrize("method", [
+        'request.getfixturevalue("arg")',
+        'request.cached_setup(lambda: None, scope="function")',
+    ], ids=["getfixturevalue", "cached_setup"])
+    def test_scope_mismatch_various(self, testdir, method):
+        testdir.makeconftest("""
+            import pytest
+            finalized = []
+            created = []
+            @pytest.fixture(scope="function")
+            def arg(request):
+                pass
+        """)
+        testdir.makepyfile(
+            test_mod1="""
+                import pytest
+                @pytest.fixture(scope="session")
+                def arg(request):
+                    %s
+                def test_1(arg):
+                    pass
+            """ % method)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        result.stdout.fnmatch_lines([
+            "*ScopeMismatch*You tried*function*session*request*",
+        ])
+
+    def test_register_only_with_mark(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            @pytest.fixture()
+            def arg():
+                return 1
+        """)
+        testdir.makepyfile(
+            test_mod1="""
+                import pytest
+                @pytest.fixture()
+                def arg(arg):
+                    return arg + 1
+                def test_1(arg):
+                    assert arg == 2
+            """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_parametrize_and_scope(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope="module", params=["a", "b", "c"])
+            def arg(request):
+                return request.param
+            values = []
+            def test_param(arg):
+                values.append(arg)
+        """)
+        reprec = testdir.inline_run("-v")
+        reprec.assertoutcome(passed=3)
+        values = reprec.getcalls("pytest_runtest_call")[0].item.module.values
+        assert len(values) == 3
+        assert "a" in values
+        assert "b" in values
+        assert "c" in values
+
+    def test_scope_mismatch(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            @pytest.fixture(scope="function")
+            def arg(request):
+                pass
+        """)
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope="session")
+            def arg(arg):
+                pass
+            def test_mismatch(arg):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ScopeMismatch*",
+            "*1 error*",
+        ])
+
+    def test_parametrize_separated_order(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(scope="module", params=[1, 2])
+            def arg(request):
+                return request.param
+
+            values = []
+            def test_1(arg):
+                values.append(arg)
+            def test_2(arg):
+                values.append(arg)
+        """)
+        reprec = testdir.inline_run("-v")
+        reprec.assertoutcome(passed=4)
+        values = reprec.getcalls("pytest_runtest_call")[0].item.module.values
+        assert values == [1, 1, 2, 2]
+
+    def test_module_parametrized_ordering(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            console_output_style=classic
+        """)
+        testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture(scope="session", params="s1 s2".split())
+            def sarg():
+                pass
+            @pytest.fixture(scope="module", params="m1 m2".split())
+            def marg():
+                pass
+        """)
+        testdir.makepyfile(test_mod1="""
+            def test_func(sarg):
+                pass
+            def test_func1(marg):
+                pass
+        """, test_mod2="""
+            def test_func2(sarg):
+                pass
+            def test_func3(sarg, marg):
+                pass
+            def test_func3b(sarg, marg):
+                pass
+            def test_func4(marg):
+                pass
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines("""
+            test_mod1.py::test_func[s1] PASSED
+            test_mod2.py::test_func2[s1] PASSED
+            test_mod2.py::test_func3[s1-m1] PASSED
+            test_mod2.py::test_func3b[s1-m1] PASSED
+            test_mod2.py::test_func3[s1-m2] PASSED
+            test_mod2.py::test_func3b[s1-m2] PASSED
+            test_mod1.py::test_func[s2] PASSED
+            test_mod2.py::test_func2[s2] PASSED
+            test_mod2.py::test_func3[s2-m1] PASSED
+            test_mod2.py::test_func3b[s2-m1] PASSED
+            test_mod2.py::test_func4[m1] PASSED
+            test_mod2.py::test_func3[s2-m2] PASSED
+            test_mod2.py::test_func3b[s2-m2] PASSED
+            test_mod2.py::test_func4[m2] PASSED
+            test_mod1.py::test_func1[m1] PASSED
+            test_mod1.py::test_func1[m2] PASSED
+        """)
+
+    def test_class_ordering(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            console_output_style=classic
+        """)
+        testdir.makeconftest("""
+            import pytest
+
+            values = []
+
+            @pytest.fixture(scope="function", params=[1,2])
+            def farg(request):
+                return request.param
+
+            @pytest.fixture(scope="class", params=list("ab"))
+            def carg(request):
+                return request.param
+
+            @pytest.fixture(scope="function", autouse=True)
+            def append(request, farg, carg):
+                def fin():
+                    values.append("fin_%s%s" % (carg, farg))
+                request.addfinalizer(fin)
+        """)
+        testdir.makepyfile("""
+            import pytest
+
+            class TestClass2(object):
+                def test_1(self):
+                    pass
+                def test_2(self):
+                    pass
+            class TestClass(object):
+                def test_3(self):
+                    pass
+        """)
+        result = testdir.runpytest("-vs")
+        result.stdout.fnmatch_lines("""
+            test_class_ordering.py::TestClass2::test_1[1-a] PASSED
+            test_class_ordering.py::TestClass2::test_1[2-a] PASSED
+            test_class_ordering.py::TestClass2::test_2[1-a] PASSED
+            test_class_ordering.py::TestClass2::test_2[2-a] PASSED
+            test_class_ordering.py::TestClass2::test_1[1-b] PASSED
+            test_class_ordering.py::TestClass2::test_1[2-b] PASSED
+            test_class_ordering.py::TestClass2::test_2[1-b] PASSED
+            test_class_ordering.py::TestClass2::test_2[2-b] PASSED
+            test_class_ordering.py::TestClass::test_3[1-a] PASSED
+            test_class_ordering.py::TestClass::test_3[2-a] PASSED
+            test_class_ordering.py::TestClass::test_3[1-b] PASSED
+            test_class_ordering.py::TestClass::test_3[2-b] PASSED
+        """)
+
+    def test_parametrize_separated_order_higher_scope_first(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(scope="function", params=[1, 2])
+            def arg(request):
+                param = request.param
+                request.addfinalizer(lambda: values.append("fin:%s" % param))
+                values.append("create:%s" % param)
+                return request.param
+
+            @pytest.fixture(scope="module", params=["mod1", "mod2"])
+            def modarg(request):
+                param = request.param
+                request.addfinalizer(lambda: values.append("fin:%s" % param))
+                values.append("create:%s" % param)
+                return request.param
+
+            values = []
+            def test_1(arg):
+                values.append("test1")
+            def test_2(modarg):
+                values.append("test2")
+            def test_3(arg, modarg):
+                values.append("test3")
+            def test_4(modarg, arg):
+                values.append("test4")
+        """)
+        reprec = testdir.inline_run("-v")
+        reprec.assertoutcome(passed=12)
+        values = reprec.getcalls("pytest_runtest_call")[0].item.module.values
+        expected = [
+            'create:1', 'test1', 'fin:1', 'create:2', 'test1',
+            'fin:2', 'create:mod1', 'test2', 'create:1', 'test3',
+            'fin:1', 'create:2', 'test3', 'fin:2', 'create:1',
+            'test4', 'fin:1', 'create:2', 'test4', 'fin:2',
+            'fin:mod1', 'create:mod2', 'test2', 'create:1', 'test3',
+            'fin:1', 'create:2', 'test3', 'fin:2', 'create:1',
+            'test4', 'fin:1', 'create:2', 'test4', 'fin:2',
+            'fin:mod2']
+        import pprint
+        pprint.pprint(list(zip(values, expected)))
+        assert values == expected
+
+    def test_parametrized_fixture_teardown_order(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(params=[1,2], scope="class")
+            def param1(request):
+                return request.param
+
+            values = []
+
+            class TestClass(object):
+                @classmethod
+                @pytest.fixture(scope="class", autouse=True)
+                def setup1(self, request, param1):
+                    values.append(1)
+                    request.addfinalizer(self.teardown1)
+                @classmethod
+                def teardown1(self):
+                    assert values.pop() == 1
+                @pytest.fixture(scope="class", autouse=True)
+                def setup2(self, request, param1):
+                    values.append(2)
+                    request.addfinalizer(self.teardown2)
+                @classmethod
+                def teardown2(self):
+                    assert values.pop() == 2
+                def test(self):
+                    pass
+
+            def test_finish():
+                assert not values
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines("""
+            *3 passed*
+        """)
+        assert "error" not in result.stdout.str()
+
+    def test_fixture_finalizer(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            import sys
+
+            @pytest.fixture
+            def browser(request):
+
+                def finalize():
+                    sys.stdout.write('Finalized')
+                request.addfinalizer(finalize)
+                return {}
+        """)
+        b = testdir.mkdir("subdir")
+        b.join("test_overridden_fixture_finalizer.py").write(dedent("""
+            import pytest
+            @pytest.fixture
+            def browser(browser):
+                browser['visited'] = True
+                return browser
+
+            def test_browser(browser):
+                assert browser['visited'] is True
+        """))
+        reprec = testdir.runpytest("-s")
+        for test in ['test_browser']:
+            reprec.stdout.fnmatch_lines('*Finalized*')
+
+    def test_class_scope_with_normal_tests(self, testdir):
+        testpath = testdir.makepyfile("""
+            import pytest
+
+            class Box(object):
+                value = 0
+
+            @pytest.fixture(scope='class')
+            def a(request):
+                Box.value += 1
+                return Box.value
+
+            def test_a(a):
+                assert a == 1
+
+            class Test1(object):
+                def test_b(self, a):
+                    assert a == 2
+
+            class Test2(object):
+                def test_c(self, a):
+                    assert a == 3""")
+        reprec = testdir.inline_run(testpath)
+        for test in ['test_a', 'test_b', 'test_c']:
+            assert reprec.matchreport(test).passed
+
+    def test_request_is_clean(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(params=[1, 2])
+            def fix(request):
+                request.addfinalizer(lambda: values.append(request.param))
+            def test_fix(fix):
+                pass
+        """)
+        reprec = testdir.inline_run("-s")
+        values = reprec.getcalls("pytest_runtest_call")[0].item.module.values
+        assert values == [1, 2]
+
+    def test_parametrize_separated_lifecycle(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            values = []
+            @pytest.fixture(scope="module", params=[1, 2])
+            def arg(request):
+                x = request.param
+                request.addfinalizer(lambda: values.append("fin%s" % x))
+                return request.param
+            def test_1(arg):
+                values.append(arg)
+            def test_2(arg):
+                values.append(arg)
+        """)
+        reprec = testdir.inline_run("-vs")
+        reprec.assertoutcome(passed=4)
+        values = reprec.getcalls("pytest_runtest_call")[0].item.module.values
+        import pprint
+        pprint.pprint(values)
+        # assert len(values) == 6
+        assert values[0] == values[1] == 1
+        assert values[2] == "fin1"
+        assert values[3] == values[4] == 2
+        assert values[5] == "fin2"
+
+    def test_parametrize_function_scoped_finalizers_called(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(scope="function", params=[1, 2])
+            def arg(request):
+                x = request.param
+                request.addfinalizer(lambda: values.append("fin%s" % x))
+                return request.param
+
+            values = []
+            def test_1(arg):
+                values.append(arg)
+            def test_2(arg):
+                values.append(arg)
+            def test_3():
+                assert len(values) == 8
+                assert values == [1, "fin1", 2, "fin2", 1, "fin1", 2, "fin2"]
+        """)
+        reprec = testdir.inline_run("-v")
+        reprec.assertoutcome(passed=5)
+
+    @pytest.mark.issue246
+    @pytest.mark.parametrize("scope", ["session", "function", "module"])
+    def test_finalizer_order_on_parametrization(self, scope, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+
+            @pytest.fixture(scope=%(scope)r, params=["1"])
+            def fix1(request):
+                return request.param
+
+            @pytest.fixture(scope=%(scope)r)
+            def fix2(request, base):
+                def cleanup_fix2():
+                    assert not values, "base should not have been finalized"
+                request.addfinalizer(cleanup_fix2)
+
+            @pytest.fixture(scope=%(scope)r)
+            def base(request, fix1):
+                def cleanup_base():
+                    values.append("fin_base")
+                    print ("finalizing base")
+                request.addfinalizer(cleanup_base)
+
+            def test_begin():
+                pass
+            def test_baz(base, fix2):
+                pass
+            def test_other():
+                pass
+        """ % {"scope": scope})
+        reprec = testdir.inline_run("-lvs")
+        reprec.assertoutcome(passed=3)
+
+    @pytest.mark.issue396
+    def test_class_scope_parametrization_ordering(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            @pytest.fixture(params=["John", "Doe"], scope="class")
+            def human(request):
+                request.addfinalizer(lambda: values.append("fin %s" % request.param))
+                return request.param
+
+            class TestGreetings(object):
+                def test_hello(self, human):
+                    values.append("test_hello")
+
+            class TestMetrics(object):
+                def test_name(self, human):
+                    values.append("test_name")
+
+                def test_population(self, human):
+                    values.append("test_population")
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=6)
+        values = reprec.getcalls("pytest_runtest_call")[0].item.module.values
+        assert values == ["test_hello", "fin John", "test_hello", "fin Doe",
+                          "test_name", "test_population", "fin John",
+                          "test_name", "test_population", "fin Doe"]
+
+    def test_parametrize_setup_function(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(scope="module", params=[1, 2])
+            def arg(request):
+                return request.param
+
+            @pytest.fixture(scope="module", autouse=True)
+            def mysetup(request, arg):
+                request.addfinalizer(lambda: values.append("fin%s" % arg))
+                values.append("setup%s" % arg)
+
+            values = []
+            def test_1(arg):
+                values.append(arg)
+            def test_2(arg):
+                values.append(arg)
+            def test_3():
+                import pprint
+                pprint.pprint(values)
+                if arg == 1:
+                    assert values == ["setup1", 1, 1, ]
+                elif arg == 2:
+                    assert values == ["setup1", 1, 1, "fin1",
+                                 "setup2", 2, 2, ]
+
+        """)
+        reprec = testdir.inline_run("-v")
+        reprec.assertoutcome(passed=6)
+
+    def test_fixture_marked_function_not_collected_as_test(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture
+            def test_app():
+                return 1
+
+            def test_something(test_app):
+                assert test_app == 1
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_params_and_ids(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(params=[object(), object()],
+                            ids=['alpha', 'beta'])
+            def fix(request):
+                return request.param
+
+            def test_foo(fix):
+                assert 1
+        """)
+        res = testdir.runpytest('-v')
+        res.stdout.fnmatch_lines([
+            '*test_foo*alpha*',
+            '*test_foo*beta*'])
+
+    def test_params_and_ids_yieldfixture(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.yield_fixture(params=[object(), object()],
+                                  ids=['alpha', 'beta'])
+            def fix(request):
+                 yield request.param
+
+            def test_foo(fix):
+                assert 1
+        """)
+        res = testdir.runpytest('-v')
+        res.stdout.fnmatch_lines([
+            '*test_foo*alpha*',
+            '*test_foo*beta*'])
+
+    @pytest.mark.issue920
+    def test_deterministic_fixture_collection(self, testdir, monkeypatch):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(scope="module",
+                            params=["A",
+                                    "B",
+                                    "C"])
+            def A(request):
+                return request.param
+
+            @pytest.fixture(scope="module",
+                            params=["DDDDDDDDD", "EEEEEEEEEEEE", "FFFFFFFFFFF", "banansda"])
+            def B(request, A):
+                return request.param
+
+            def test_foo(B):
+                # Something funky is going on here.
+                # Despite specified seeds, on what is collected,
+                # sometimes we get unexpected passes. hashing B seems
+                # to help?
+                assert hash(B) or True
+            """)
+        monkeypatch.setenv("PYTHONHASHSEED", "1")
+        out1 = testdir.runpytest_subprocess("-v")
+        monkeypatch.setenv("PYTHONHASHSEED", "2")
+        out2 = testdir.runpytest_subprocess("-v")
+        out1 = [line for line in out1.outlines if line.startswith("test_deterministic_fixture_collection.py::test_foo")]
+        out2 = [line for line in out2.outlines if line.startswith("test_deterministic_fixture_collection.py::test_foo")]
+        assert len(out1) == 12
+        assert out1 == out2
+
+
+class TestRequestScopeAccess(object):
+    pytestmark = pytest.mark.parametrize(("scope", "ok", "error"), [
+        ["session", "", "fspath class function module"],
+        ["module", "module fspath", "cls function"],
+        ["class", "module fspath cls", "function"],
+        ["function", "module fspath cls function", ""]
+    ])
+
+    def test_setup(self, testdir, scope, ok, error):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope=%r, autouse=True)
+            def myscoped(request):
+                for x in %r:
+                    assert hasattr(request, x)
+                for x in %r:
+                    pytest.raises(AttributeError, lambda:
+                        getattr(request, x))
+                assert request.session
+                assert request.config
+            def test_func():
+                pass
+        """ % (scope, ok.split(), error.split()))
+        reprec = testdir.inline_run("-l")
+        reprec.assertoutcome(passed=1)
+
+    def test_funcarg(self, testdir, scope, ok, error):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope=%r)
+            def arg(request):
+                for x in %r:
+                    assert hasattr(request, x)
+                for x in %r:
+                    pytest.raises(AttributeError, lambda:
+                        getattr(request, x))
+                assert request.session
+                assert request.config
+            def test_func(arg):
+                pass
+        """ % (scope, ok.split(), error.split()))
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+
+class TestErrors(object):
+    def test_subfactory_missing_funcarg(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture()
+            def gen(qwe123):
+                return 1
+            def test_something(gen):
+                pass
+        """)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        result.stdout.fnmatch_lines([
+            "*def gen(qwe123):*",
+            "*fixture*qwe123*not found*",
+            "*1 error*",
+        ])
+
+    def test_issue498_fixture_finalizer_failing(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture
+            def fix1(request):
+                def f():
+                    raise KeyError
+                request.addfinalizer(f)
+                return object()
+
+            values = []
+            def test_1(fix1):
+                values.append(fix1)
+            def test_2(fix1):
+                values.append(fix1)
+            def test_3():
+                assert values[0] != values[1]
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            *ERROR*teardown*test_1*
+            *KeyError*
+            *ERROR*teardown*test_2*
+            *KeyError*
+            *3 pass*2 error*
+        """)
+
+    def test_setupfunc_missing_funcarg(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(autouse=True)
+            def gen(qwe123):
+                return 1
+            def test_something():
+                pass
+        """)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        result.stdout.fnmatch_lines([
+            "*def gen(qwe123):*",
+            "*fixture*qwe123*not found*",
+            "*1 error*",
+        ])
+
+
+class TestShowFixtures(object):
+    def test_funcarg_compat(self, testdir):
+        config = testdir.parseconfigure("--funcargs")
+        assert config.option.showfixtures
+
+    def test_show_fixtures(self, testdir):
+        result = testdir.runpytest("--fixtures")
+        result.stdout.fnmatch_lines([
+            "*tmpdir*",
+            "*temporary directory*",
+        ])
+
+    def test_show_fixtures_verbose(self, testdir):
+        result = testdir.runpytest("--fixtures", "-v")
+        result.stdout.fnmatch_lines([
+            "*tmpdir*--*tmpdir.py*",
+            "*temporary directory*",
+        ])
+
+    def test_show_fixtures_testmodule(self, testdir):
+        p = testdir.makepyfile('''
+            import pytest
+            @pytest.fixture
+            def _arg0():
+                """ hidden """
+            @pytest.fixture
+            def arg1():
+                """  hello world """
+        ''')
+        result = testdir.runpytest("--fixtures", p)
+        result.stdout.fnmatch_lines("""
+            *tmpdir
+            *fixtures defined from*
+            *arg1*
+            *hello world*
+        """)
+        assert "arg0" not in result.stdout.str()
+
+    @pytest.mark.parametrize("testmod", [True, False])
+    def test_show_fixtures_conftest(self, testdir, testmod):
+        testdir.makeconftest('''
+            import pytest
+            @pytest.fixture
+            def arg1():
+                """  hello world """
+        ''')
+        if testmod:
+            testdir.makepyfile("""
+                def test_hello():
+                    pass
+            """)
+        result = testdir.runpytest("--fixtures")
+        result.stdout.fnmatch_lines("""
+            *tmpdir*
+            *fixtures defined from*conftest*
+            *arg1*
+            *hello world*
+        """)
+
+    def test_show_fixtures_trimmed_doc(self, testdir):
+        p = testdir.makepyfile(dedent('''
+            import pytest
+            @pytest.fixture
+            def arg1():
+                """
+                line1
+                line2
+
+                """
+            @pytest.fixture
+            def arg2():
+                """
+                line1
+                line2
+
+                """
+        '''))
+        result = testdir.runpytest("--fixtures", p)
+        result.stdout.fnmatch_lines(dedent("""
+            * fixtures defined from test_show_fixtures_trimmed_doc *
+            arg2
+                line1
+                line2
+            arg1
+                line1
+                line2
+
+        """))
+
+    def test_show_fixtures_indented_doc(self, testdir):
+        p = testdir.makepyfile(dedent('''
+            import pytest
+            @pytest.fixture
+            def fixture1():
+                """
+                line1
+                    indented line
+                """
+        '''))
+        result = testdir.runpytest("--fixtures", p)
+        result.stdout.fnmatch_lines(dedent("""
+            * fixtures defined from test_show_fixtures_indented_doc *
+            fixture1
+                line1
+                    indented line
+        """))
+
+    def test_show_fixtures_indented_doc_first_line_unindented(self, testdir):
+        p = testdir.makepyfile(dedent('''
+            import pytest
+            @pytest.fixture
+            def fixture1():
+                """line1
+                line2
+                    indented line
+                """
+        '''))
+        result = testdir.runpytest("--fixtures", p)
+        result.stdout.fnmatch_lines(dedent("""
+            * fixtures defined from test_show_fixtures_indented_doc_first_line_unindented *
+            fixture1
+                line1
+                line2
+                    indented line
+        """))
+
+    def test_show_fixtures_indented_in_class(self, testdir):
+        p = testdir.makepyfile(dedent('''
+            import pytest
+            class TestClass:
+                @pytest.fixture
+                def fixture1(self):
+                    """line1
+                    line2
+                        indented line
+                    """
+        '''))
+        result = testdir.runpytest("--fixtures", p)
+        result.stdout.fnmatch_lines(dedent("""
+            * fixtures defined from test_show_fixtures_indented_in_class *
+            fixture1
+                line1
+                line2
+                    indented line
+        """))
+
+    def test_show_fixtures_different_files(self, testdir):
+        """
+        #833: --fixtures only shows fixtures from first file
+        """
+        testdir.makepyfile(test_a='''
+            import pytest
+
+            @pytest.fixture
+            def fix_a():
+                """Fixture A"""
+                pass
+
+            def test_a(fix_a):
+                pass
+        ''')
+        testdir.makepyfile(test_b='''
+            import pytest
+
+            @pytest.fixture
+            def fix_b():
+                """Fixture B"""
+                pass
+
+            def test_b(fix_b):
+                pass
+        ''')
+        result = testdir.runpytest("--fixtures")
+        result.stdout.fnmatch_lines("""
+            * fixtures defined from test_a *
+            fix_a
+                Fixture A
+
+            * fixtures defined from test_b *
+            fix_b
+                Fixture B
+        """)
+
+    def test_show_fixtures_with_same_name(self, testdir):
+        testdir.makeconftest('''
+            import pytest
+            @pytest.fixture
+            def arg1():
+                """Hello World in conftest.py"""
+                return "Hello World"
+        ''')
+        testdir.makepyfile('''
+            def test_foo(arg1):
+                assert arg1 == "Hello World"
+        ''')
+        testdir.makepyfile('''
+            import pytest
+            @pytest.fixture
+            def arg1():
+                """Hi from test module"""
+                return "Hi"
+            def test_bar(arg1):
+                assert arg1 == "Hi"
+        ''')
+        result = testdir.runpytest("--fixtures")
+        result.stdout.fnmatch_lines('''
+            * fixtures defined from conftest *
+            arg1
+                Hello World in conftest.py
+
+            * fixtures defined from test_show_fixtures_with_same_name *
+            arg1
+                Hi from test module
+        ''')
+
+
+@pytest.mark.parametrize('flavor', ['fixture', 'yield_fixture'])
+class TestContextManagerFixtureFuncs(object):
+
+    def test_simple(self, testdir, flavor):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.{flavor}
+            def arg1():
+                print ("setup")
+                yield 1
+                print ("teardown")
+            def test_1(arg1):
+                print ("test1 %s" % arg1)
+            def test_2(arg1):
+                print ("test2 %s" % arg1)
+                assert 0
+        """.format(flavor=flavor))
+        result = testdir.runpytest("-s")
+        result.stdout.fnmatch_lines("""
+            *setup*
+            *test1 1*
+            *teardown*
+            *setup*
+            *test2 1*
+            *teardown*
+        """)
+
+    def test_scoped(self, testdir, flavor):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.{flavor}(scope="module")
+            def arg1():
+                print ("setup")
+                yield 1
+                print ("teardown")
+            def test_1(arg1):
+                print ("test1 %s" % arg1)
+            def test_2(arg1):
+                print ("test2 %s" % arg1)
+        """.format(flavor=flavor))
+        result = testdir.runpytest("-s")
+        result.stdout.fnmatch_lines("""
+            *setup*
+            *test1 1*
+            *test2 1*
+            *teardown*
+        """)
+
+    def test_setup_exception(self, testdir, flavor):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.{flavor}(scope="module")
+            def arg1():
+                pytest.fail("setup")
+                yield 1
+            def test_1(arg1):
+                pass
+        """.format(flavor=flavor))
+        result = testdir.runpytest("-s")
+        result.stdout.fnmatch_lines("""
+            *pytest.fail*setup*
+            *1 error*
+        """)
+
+    def test_teardown_exception(self, testdir, flavor):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.{flavor}(scope="module")
+            def arg1():
+                yield 1
+                pytest.fail("teardown")
+            def test_1(arg1):
+                pass
+        """.format(flavor=flavor))
+        result = testdir.runpytest("-s")
+        result.stdout.fnmatch_lines("""
+            *pytest.fail*teardown*
+            *1 passed*1 error*
+        """)
+
+    def test_yields_more_than_one(self, testdir, flavor):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.{flavor}(scope="module")
+            def arg1():
+                yield 1
+                yield 2
+            def test_1(arg1):
+                pass
+        """.format(flavor=flavor))
+        result = testdir.runpytest("-s")
+        result.stdout.fnmatch_lines("""
+            *fixture function*
+            *test_yields*:2*
+        """)
+
+    def test_custom_name(self, testdir, flavor):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.{flavor}(name='meow')
+            def arg1():
+                return 'mew'
+            def test_1(meow):
+                print(meow)
+        """.format(flavor=flavor))
+        result = testdir.runpytest("-s")
+        result.stdout.fnmatch_lines("*mew*")
+
+
+class TestParameterizedSubRequest(object):
+    def test_call_from_fixture(self, testdir):
+        testfile = testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(params=[0, 1, 2])
+            def fix_with_param(request):
+                return request.param
+
+            @pytest.fixture
+            def get_named_fixture(request):
+                return request.getfixturevalue('fix_with_param')
+
+            def test_foo(request, get_named_fixture):
+                pass
+            """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            E*Failed: The requested fixture has no parameter defined for the current test.
+            E*
+            E*Requested fixture 'fix_with_param' defined in:
+            E*{0}:4
+            E*Requested here:
+            E*{1}:9
+            *1 error*
+            """.format(testfile.basename, testfile.basename))
+
+    def test_call_from_test(self, testdir):
+        testfile = testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(params=[0, 1, 2])
+            def fix_with_param(request):
+                return request.param
+
+            def test_foo(request):
+                request.getfixturevalue('fix_with_param')
+            """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            E*Failed: The requested fixture has no parameter defined for the current test.
+            E*
+            E*Requested fixture 'fix_with_param' defined in:
+            E*{0}:4
+            E*Requested here:
+            E*{1}:8
+            *1 failed*
+            """.format(testfile.basename, testfile.basename))
+
+    def test_external_fixture(self, testdir):
+        conffile = testdir.makeconftest("""
+            import pytest
+
+            @pytest.fixture(params=[0, 1, 2])
+            def fix_with_param(request):
+                return request.param
+            """)
+
+        testfile = testdir.makepyfile("""
+            def test_foo(request):
+                request.getfixturevalue('fix_with_param')
+            """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            E*Failed: The requested fixture has no parameter defined for the current test.
+            E*
+            E*Requested fixture 'fix_with_param' defined in:
+            E*{0}:4
+            E*Requested here:
+            E*{1}:2
+            *1 failed*
+            """.format(conffile.basename, testfile.basename))
+
+    def test_non_relative_path(self, testdir):
+        tests_dir = testdir.mkdir('tests')
+        fixdir = testdir.mkdir('fixtures')
+        fixfile = fixdir.join("fix.py")
+        fixfile.write(_pytest._code.Source("""
+            import pytest
+
+            @pytest.fixture(params=[0, 1, 2])
+            def fix_with_param(request):
+                return request.param
+            """))
+
+        testfile = tests_dir.join("test_foos.py")
+        testfile.write(_pytest._code.Source("""
+            from fix import fix_with_param
+
+            def test_foo(request):
+                request.getfixturevalue('fix_with_param')
+            """))
+
+        tests_dir.chdir()
+        testdir.syspathinsert(fixdir)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            E*Failed: The requested fixture has no parameter defined for the current test.
+            E*
+            E*Requested fixture 'fix_with_param' defined in:
+            E*{0}:5
+            E*Requested here:
+            E*{1}:5
+            *1 failed*
+            """.format(fixfile.strpath, testfile.basename))
+
+
+def test_pytest_fixture_setup_and_post_finalizer_hook(testdir):
+    testdir.makeconftest("""
+        from __future__ import print_function
+        def pytest_fixture_setup(fixturedef, request):
+            print('ROOT setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
+        def pytest_fixture_post_finalizer(fixturedef, request):
+            print('ROOT finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
+    """)
+    testdir.makepyfile(**{
+        'tests/conftest.py': """
+            from __future__ import print_function
+            def pytest_fixture_setup(fixturedef, request):
+                print('TESTS setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
+            def pytest_fixture_post_finalizer(fixturedef, request):
+                print('TESTS finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name))
+        """,
+        'tests/test_hooks.py': """
+            from __future__ import print_function
+            import pytest
+
+            @pytest.fixture()
+            def my_fixture():
+                return 'some'
+
+            def test_func(my_fixture):
+                print('TEST test_func')
+                assert my_fixture == 'some'
+        """
+    })
+    result = testdir.runpytest("-s")
+    assert result.ret == 0
+    result.stdout.fnmatch_lines([
+        "*TESTS setup hook called for my_fixture from test_func*",
+        "*ROOT setup hook called for my_fixture from test_func*",
+        "*TEST test_func*",
+        "*TESTS finalizer hook called for my_fixture from test_func*",
+        "*ROOT finalizer hook called for my_fixture from test_func*",
+    ])
diff --git a/tools/third_party/pytest/testing/python/integration.py b/tools/third_party/pytest/testing/python/integration.py
new file mode 100644
index 0000000..6ea29fa
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/integration.py
@@ -0,0 +1,384 @@
+import pytest
+from _pytest import python
+from _pytest import runner
+
+
+class TestOEJSKITSpecials(object):
+    def test_funcarg_non_pycollectobj(self, testdir):  # rough jstests usage
+        testdir.makeconftest("""
+            import pytest
+            def pytest_pycollect_makeitem(collector, name, obj):
+                if name == "MyClass":
+                    return MyCollector(name, parent=collector)
+            class MyCollector(pytest.Collector):
+                def reportinfo(self):
+                    return self.fspath, 3, "xyz"
+        """)
+        modcol = testdir.getmodulecol("""
+            import pytest
+            @pytest.fixture
+            def arg1(request):
+                return 42
+            class MyClass(object):
+                pass
+        """)
+        # this hook finds funcarg factories
+        rep = runner.collect_one_node(collector=modcol)
+        clscol = rep.result[0]
+        clscol.obj = lambda arg1: None
+        clscol.funcargs = {}
+        pytest._fillfuncargs(clscol)
+        assert clscol.funcargs['arg1'] == 42
+
+    def test_autouse_fixture(self, testdir):  # rough jstests usage
+        testdir.makeconftest("""
+            import pytest
+            def pytest_pycollect_makeitem(collector, name, obj):
+                if name == "MyClass":
+                    return MyCollector(name, parent=collector)
+            class MyCollector(pytest.Collector):
+                def reportinfo(self):
+                    return self.fspath, 3, "xyz"
+        """)
+        modcol = testdir.getmodulecol("""
+            import pytest
+            @pytest.fixture(autouse=True)
+            def hello():
+                pass
+            @pytest.fixture
+            def arg1(request):
+                return 42
+            class MyClass(object):
+                pass
+        """)
+        # this hook finds funcarg factories
+        rep = runner.collect_one_node(modcol)
+        clscol = rep.result[0]
+        clscol.obj = lambda: None
+        clscol.funcargs = {}
+        pytest._fillfuncargs(clscol)
+        assert not clscol.funcargs
+
+
+def test_wrapped_getfslineno():
+    def func():
+        pass
+
+    def wrap(f):
+        func.__wrapped__ = f
+        func.patchings = ["qwe"]
+        return func
+
+    @wrap
+    def wrapped_func(x, y, z):
+        pass
+    fs, lineno = python.getfslineno(wrapped_func)
+    fs2, lineno2 = python.getfslineno(wrap)
+    assert lineno > lineno2, "getfslineno does not unwrap correctly"
+
+
+class TestMockDecoration(object):
+    def test_wrapped_getfuncargnames(self):
+        from _pytest.compat import getfuncargnames
+
+        def wrap(f):
+
+            def func():
+                pass
+
+            func.__wrapped__ = f
+            return func
+
+        @wrap
+        def f(x):
+            pass
+
+        values = getfuncargnames(f)
+        assert values == ("x",)
+
+    def test_wrapped_getfuncargnames_patching(self):
+        from _pytest.compat import getfuncargnames
+
+        def wrap(f):
+            def func():
+                pass
+            func.__wrapped__ = f
+            func.patchings = ["qwe"]
+            return func
+
+        @wrap
+        def f(x, y, z):
+            pass
+
+        values = getfuncargnames(f)
+        assert values == ("y", "z")
+
+    def test_unittest_mock(self, testdir):
+        pytest.importorskip("unittest.mock")
+        testdir.makepyfile("""
+            import unittest.mock
+            class T(unittest.TestCase):
+                @unittest.mock.patch("os.path.abspath")
+                def test_hello(self, abspath):
+                    import os
+                    os.path.abspath("hello")
+                    abspath.assert_any_call("hello")
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_unittest_mock_and_fixture(self, testdir):
+        pytest.importorskip("unittest.mock")
+        testdir.makepyfile("""
+            import os.path
+            import unittest.mock
+            import pytest
+
+            @pytest.fixture
+            def inject_me():
+                pass
+
+            @unittest.mock.patch.object(os.path, "abspath",
+                                        new=unittest.mock.MagicMock)
+            def test_hello(inject_me):
+                import os
+                os.path.abspath("hello")
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_mock(self, testdir):
+        pytest.importorskip("mock", "1.0.1")
+        testdir.makepyfile("""
+            import os
+            import unittest
+            import mock
+
+            class T(unittest.TestCase):
+                @mock.patch("os.path.abspath")
+                def test_hello(self, abspath):
+                    os.path.abspath("hello")
+                    abspath.assert_any_call("hello")
+            def mock_basename(path):
+                return "mock_basename"
+            @mock.patch("os.path.abspath")
+            @mock.patch("os.path.normpath")
+            @mock.patch("os.path.basename", new=mock_basename)
+            def test_someting(normpath, abspath, tmpdir):
+                abspath.return_value = "this"
+                os.path.normpath(os.path.abspath("hello"))
+                normpath.assert_any_call("this")
+                assert os.path.basename("123") == "mock_basename"
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+        calls = reprec.getcalls("pytest_runtest_logreport")
+        funcnames = [call.report.location[2] for call in calls
+                     if call.report.when == "call"]
+        assert funcnames == ["T.test_hello", "test_someting"]
+
+    def test_mock_sorting(self, testdir):
+        pytest.importorskip("mock", "1.0.1")
+        testdir.makepyfile("""
+            import os
+            import mock
+
+            @mock.patch("os.path.abspath")
+            def test_one(abspath):
+                pass
+            @mock.patch("os.path.abspath")
+            def test_two(abspath):
+                pass
+            @mock.patch("os.path.abspath")
+            def test_three(abspath):
+                pass
+        """)
+        reprec = testdir.inline_run()
+        calls = reprec.getreports("pytest_runtest_logreport")
+        calls = [x for x in calls if x.when == "call"]
+        names = [x.nodeid.split("::")[-1] for x in calls]
+        assert names == ["test_one", "test_two", "test_three"]
+
+    def test_mock_double_patch_issue473(self, testdir):
+        pytest.importorskip("mock", "1.0.1")
+        testdir.makepyfile("""
+            from mock import patch
+            from pytest import mark
+
+            @patch('os.getcwd')
+            @patch('os.path')
+            @mark.slow
+            class TestSimple(object):
+                def test_simple_thing(self, mock_path, mock_getcwd):
+                    pass
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+
+class TestReRunTests(object):
+    def test_rerun(self, testdir):
+        testdir.makeconftest("""
+            from _pytest.runner import runtestprotocol
+            def pytest_runtest_protocol(item, nextitem):
+                runtestprotocol(item, log=False, nextitem=nextitem)
+                runtestprotocol(item, log=True, nextitem=nextitem)
+        """)
+        testdir.makepyfile("""
+            import pytest
+            count = 0
+            req = None
+            @pytest.fixture
+            def fix(request):
+                global count, req
+                assert request != req
+                req = request
+                print ("fix count %s" % count)
+                count += 1
+            def test_fix(fix):
+                pass
+        """)
+        result = testdir.runpytest("-s")
+        result.stdout.fnmatch_lines("""
+            *fix count 0*
+            *fix count 1*
+        """)
+        result.stdout.fnmatch_lines("""
+            *2 passed*
+        """)
+
+
+def test_pytestconfig_is_session_scoped():
+    from _pytest.fixtures import pytestconfig
+    assert pytestconfig._pytestfixturefunction.scope == "session"
+
+
+class TestNoselikeTestAttribute(object):
+    def test_module_with_global_test(self, testdir):
+        testdir.makepyfile("""
+            __test__ = False
+            def test_hello():
+                pass
+        """)
+        reprec = testdir.inline_run()
+        assert not reprec.getfailedcollections()
+        calls = reprec.getreports("pytest_runtest_logreport")
+        assert not calls
+
+    def test_class_and_method(self, testdir):
+        testdir.makepyfile("""
+            __test__ = True
+            def test_func():
+                pass
+            test_func.__test__ = False
+
+            class TestSome(object):
+                __test__ = False
+                def test_method(self):
+                    pass
+        """)
+        reprec = testdir.inline_run()
+        assert not reprec.getfailedcollections()
+        calls = reprec.getreports("pytest_runtest_logreport")
+        assert not calls
+
+    def test_unittest_class(self, testdir):
+        testdir.makepyfile("""
+            import unittest
+            class TC(unittest.TestCase):
+                def test_1(self):
+                    pass
+            class TC2(unittest.TestCase):
+                __test__ = False
+                def test_2(self):
+                    pass
+        """)
+        reprec = testdir.inline_run()
+        assert not reprec.getfailedcollections()
+        call = reprec.getcalls("pytest_collection_modifyitems")[0]
+        assert len(call.items) == 1
+        assert call.items[0].cls.__name__ == "TC"
+
+    def test_class_with_nasty_getattr(self, testdir):
+        """Make sure we handle classes with a custom nasty __getattr__ right.
+
+        With a custom __getattr__ which e.g. returns a function (like with a
+        RPC wrapper), we shouldn't assume this meant "__test__ = True".
+        """
+        # https://github.com/pytest-dev/pytest/issues/1204
+        testdir.makepyfile("""
+            class MetaModel(type):
+
+                def __getattr__(cls, key):
+                    return lambda: None
+
+
+            BaseModel = MetaModel('Model', (), {})
+
+
+            class Model(BaseModel):
+
+                __metaclass__ = MetaModel
+
+                def test_blah(self):
+                    pass
+        """)
+        reprec = testdir.inline_run()
+        assert not reprec.getfailedcollections()
+        call = reprec.getcalls("pytest_collection_modifyitems")[0]
+        assert not call.items
+
+
+@pytest.mark.issue351
+class TestParameterize(object):
+
+    def test_idfn_marker(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            def idfn(param):
+                if param == 0:
+                    return 'spam'
+                elif param == 1:
+                    return 'ham'
+                else:
+                    return None
+
+            @pytest.mark.parametrize('a,b', [(0, 2), (1, 2)], ids=idfn)
+            def test_params(a, b):
+                pass
+        """)
+        res = testdir.runpytest('--collect-only')
+        res.stdout.fnmatch_lines([
+            "*spam-2*",
+            "*ham-2*",
+        ])
+
+    def test_idfn_fixture(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            def idfn(param):
+                if param == 0:
+                    return 'spam'
+                elif param == 1:
+                    return 'ham'
+                else:
+                    return None
+
+            @pytest.fixture(params=[0, 1], ids=idfn)
+            def a(request):
+                return request.param
+
+            @pytest.fixture(params=[1, 2], ids=idfn)
+            def b(request):
+                return request.param
+
+            def test_params(a, b):
+                pass
+        """)
+        res = testdir.runpytest('--collect-only')
+        res.stdout.fnmatch_lines([
+            "*spam-2*",
+            "*ham-2*",
+        ])
diff --git a/tools/third_party/pytest/testing/python/metafunc.py b/tools/third_party/pytest/testing/python/metafunc.py
new file mode 100644
index 0000000..2ffb7bb
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/metafunc.py
@@ -0,0 +1,1561 @@
+# -*- coding: utf-8 -*-
+import re
+import sys
+
+import _pytest._code
+import py
+import pytest
+from _pytest import python, fixtures
+
+import hypothesis
+from hypothesis import strategies
+
+PY3 = sys.version_info >= (3, 0)
+
+
+class TestMetafunc(object):
+    def Metafunc(self, func):
+        # the unit tests of this class check if things work correctly
+        # on the funcarg level, so we don't need a full blown
+        # initiliazation
+        class FixtureInfo(object):
+            name2fixturedefs = None
+
+            def __init__(self, names):
+                self.names_closure = names
+
+        names = fixtures.getfuncargnames(func)
+        fixtureinfo = FixtureInfo(names)
+        return python.Metafunc(func, fixtureinfo, None)
+
+    def test_no_funcargs(self, testdir):
+        def function():
+            pass
+        metafunc = self.Metafunc(function)
+        assert not metafunc.fixturenames
+        repr(metafunc._calls)
+
+    def test_function_basic(self):
+        def func(arg1, arg2="qwe"):
+            pass
+        metafunc = self.Metafunc(func)
+        assert len(metafunc.fixturenames) == 1
+        assert 'arg1' in metafunc.fixturenames
+        assert metafunc.function is func
+        assert metafunc.cls is None
+
+    def test_addcall_no_args(self):
+        def func(arg1):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.addcall()
+        assert len(metafunc._calls) == 1
+        call = metafunc._calls[0]
+        assert call.id == "0"
+        assert not hasattr(call, 'param')
+
+    def test_addcall_id(self):
+        def func(arg1):
+            pass
+        metafunc = self.Metafunc(func)
+        pytest.raises(ValueError, "metafunc.addcall(id=None)")
+
+        metafunc.addcall(id=1)
+        pytest.raises(ValueError, "metafunc.addcall(id=1)")
+        pytest.raises(ValueError, "metafunc.addcall(id='1')")
+        metafunc.addcall(id=2)
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].id == "1"
+        assert metafunc._calls[1].id == "2"
+
+    def test_addcall_param(self):
+        def func(arg1):
+            pass
+        metafunc = self.Metafunc(func)
+
+        class obj(object):
+            pass
+
+        metafunc.addcall(param=obj)
+        metafunc.addcall(param=obj)
+        metafunc.addcall(param=1)
+        assert len(metafunc._calls) == 3
+        assert metafunc._calls[0].getparam("arg1") == obj
+        assert metafunc._calls[1].getparam("arg1") == obj
+        assert metafunc._calls[2].getparam("arg1") == 1
+
+    def test_addcall_funcargs(self):
+        def func(x):
+            pass
+
+        metafunc = self.Metafunc(func)
+
+        class obj(object):
+            pass
+
+        metafunc.addcall(funcargs={"x": 2})
+        metafunc.addcall(funcargs={"x": 3})
+        pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})")
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].funcargs == {'x': 2}
+        assert metafunc._calls[1].funcargs == {'x': 3}
+        assert not hasattr(metafunc._calls[1], 'param')
+
+    def test_parametrize_error(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.parametrize("x", [1, 2])
+        pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5, 6]))
+        pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5, 6]))
+        metafunc.parametrize("y", [1, 2])
+        pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6]))
+        pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6]))
+
+    def test_parametrize_bad_scope(self, testdir):
+        def func(x):
+            pass
+        metafunc = self.Metafunc(func)
+        try:
+            metafunc.parametrize("x", [1], scope='doggy')
+        except ValueError as ve:
+            assert "has an unsupported scope value 'doggy'" in str(ve)
+
+    def test_parametrize_and_id(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+
+        metafunc.parametrize("x", [1, 2], ids=['basic', 'advanced'])
+        metafunc.parametrize("y", ["abc", "def"])
+        ids = [x.id for x in metafunc._calls]
+        assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"]
+
+    def test_parametrize_and_id_unicode(self):
+        """Allow unicode strings for "ids" parameter in Python 2 (##1905)"""
+        def func(x):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.parametrize("x", [1, 2], ids=[u'basic', u'advanced'])
+        ids = [x.id for x in metafunc._calls]
+        assert ids == [u"basic", u"advanced"]
+
+    def test_parametrize_with_wrong_number_of_ids(self, testdir):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+
+        pytest.raises(ValueError, lambda:
+                      metafunc.parametrize("x", [1, 2], ids=['basic']))
+
+        pytest.raises(ValueError, lambda:
+                      metafunc.parametrize(("x", "y"), [("abc", "def"),
+                                                        ("ghi", "jkl")], ids=["one"]))
+
+    @pytest.mark.issue510
+    def test_parametrize_empty_list(self):
+        def func(y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.parametrize("y", [])
+        assert 'skip' == metafunc._calls[0].marks[0].name
+
+    def test_parametrize_with_userobjects(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+
+        class A(object):
+            pass
+
+        metafunc.parametrize("x", [A(), A()])
+        metafunc.parametrize("y", list("ab"))
+        assert metafunc._calls[0].id == "x0-a"
+        assert metafunc._calls[1].id == "x0-b"
+        assert metafunc._calls[2].id == "x1-a"
+        assert metafunc._calls[3].id == "x1-b"
+
+    @hypothesis.given(strategies.text() | strategies.binary())
+    def test_idval_hypothesis(self, value):
+        from _pytest.python import _idval
+        escaped = _idval(value, 'a', 6, None)
+        assert isinstance(escaped, str)
+        if PY3:
+            escaped.encode('ascii')
+        else:
+            escaped.decode('ascii')
+
+    def test_unicode_idval(self):
+        """This tests that Unicode strings outside the ASCII character set get
+        escaped, using byte escapes if they're in that range or unicode
+        escapes if they're not.
+
+        """
+        from _pytest.python import _idval
+        values = [
+            (
+                u'',
+                ''
+            ),
+            (
+                u'ascii',
+                'ascii'
+            ),
+            (
+                u'ação',
+                'a\\xe7\\xe3o'
+            ),
+            (
+                u'josé@blah.com',
+                'jos\\xe9@blah.com'
+            ),
+            (
+                u'δοκ.ιμή@παράδειγμα.δοκιμή',
+                '\\u03b4\\u03bf\\u03ba.\\u03b9\\u03bc\\u03ae@\\u03c0\\u03b1\\u03c1\\u03ac\\u03b4\\u03b5\\u03b9\\u03b3'
+                '\\u03bc\\u03b1.\\u03b4\\u03bf\\u03ba\\u03b9\\u03bc\\u03ae'
+            ),
+        ]
+        for val, expected in values:
+            assert _idval(val, 'a', 6, None) == expected
+
+    def test_bytes_idval(self):
+        """unittest for the expected behavior to obtain ids for parametrized
+        bytes values:
+        - python2: non-ascii strings are considered bytes and formatted using
+        "binary escape", where any byte < 127 is escaped into its hex form.
+        - python3: bytes objects are always escaped using "binary escape".
+        """
+        from _pytest.python import _idval
+        values = [
+            (b'', ''),
+            (b'\xc3\xb4\xff\xe4', '\\xc3\\xb4\\xff\\xe4'),
+            (b'ascii', 'ascii'),
+            (u'αρά'.encode('utf-8'), '\\xce\\xb1\\xcf\\x81\\xce\\xac'),
+        ]
+        for val, expected in values:
+            assert _idval(val, 'a', 6, None) == expected
+
+    @pytest.mark.issue250
+    def test_idmaker_autoname(self):
+        from _pytest.python import idmaker
+        result = idmaker(("a", "b"), [pytest.param("string", 1.0),
+                                      pytest.param("st-ring", 2.0)])
+        assert result == ["string-1.0", "st-ring-2.0"]
+
+        result = idmaker(("a", "b"), [pytest.param(object(), 1.0),
+                                      pytest.param(object(), object())])
+        assert result == ["a0-1.0", "a1-b1"]
+        # unicode mixing, issue250
+        result = idmaker(
+            (py.builtin._totext("a"), "b"),
+            [pytest.param({}, b'\xc3\xb4')])
+        assert result == ['a0-\\xc3\\xb4']
+
+    def test_idmaker_with_bytes_regex(self):
+        from _pytest.python import idmaker
+        result = idmaker(("a"), [pytest.param(re.compile(b'foo'), 1.0)])
+        assert result == ["foo"]
+
+    def test_idmaker_native_strings(self):
+        from _pytest.python import idmaker
+        totext = py.builtin._totext
+        result = idmaker(("a", "b"), [
+            pytest.param(1.0, -1.1),
+            pytest.param(2, -202),
+            pytest.param("three", "three hundred"),
+            pytest.param(True, False),
+            pytest.param(None, None),
+            pytest.param(re.compile('foo'), re.compile('bar')),
+            pytest.param(str, int),
+            pytest.param(list("six"), [66, 66]),
+            pytest.param(set([7]), set("seven")),
+            pytest.param(tuple("eight"), (8, -8, 8)),
+            pytest.param(b'\xc3\xb4', b"name"),
+            pytest.param(b'\xc3\xb4', totext("other")),
+        ])
+        assert result == ["1.0--1.1",
+                          "2--202",
+                          "three-three hundred",
+                          "True-False",
+                          "None-None",
+                          "foo-bar",
+                          "str-int",
+                          "a7-b7",
+                          "a8-b8",
+                          "a9-b9",
+                          "\\xc3\\xb4-name",
+                          "\\xc3\\xb4-other",
+                          ]
+
+    def test_idmaker_enum(self):
+        from _pytest.python import idmaker
+        enum = pytest.importorskip("enum")
+        e = enum.Enum("Foo", "one, two")
+        result = idmaker(("a", "b"), [pytest.param(e.one, e.two)])
+        assert result == ["Foo.one-Foo.two"]
+
+    @pytest.mark.issue351
+    def test_idmaker_idfn(self):
+        from _pytest.python import idmaker
+
+        def ids(val):
+            if isinstance(val, Exception):
+                return repr(val)
+
+        result = idmaker(("a", "b"), [
+            pytest.param(10.0, IndexError()),
+            pytest.param(20, KeyError()),
+            pytest.param("three", [1, 2, 3]),
+        ], idfn=ids)
+        assert result == ["10.0-IndexError()",
+                          "20-KeyError()",
+                          "three-b2",
+                          ]
+
+    @pytest.mark.issue351
+    def test_idmaker_idfn_unique_names(self):
+        from _pytest.python import idmaker
+
+        def ids(val):
+            return 'a'
+
+        result = idmaker(("a", "b"), [pytest.param(10.0, IndexError()),
+                                      pytest.param(20, KeyError()),
+                                      pytest.param("three", [1, 2, 3]),
+                                      ], idfn=ids)
+        assert result == ["a-a0",
+                          "a-a1",
+                          "a-a2",
+                          ]
+
+    @pytest.mark.issue351
+    def test_idmaker_idfn_exception(self):
+        from _pytest.python import idmaker
+        from _pytest.recwarn import WarningsRecorder
+
+        class BadIdsException(Exception):
+            pass
+
+        def ids(val):
+            raise BadIdsException("ids raised")
+
+        rec = WarningsRecorder()
+        with rec:
+            idmaker(("a", "b"), [
+                pytest.param(10.0, IndexError()),
+                pytest.param(20, KeyError()),
+                pytest.param("three", [1, 2, 3]),
+            ], idfn=ids)
+
+        assert [str(i.message) for i in rec.list] == [
+            "Raised while trying to determine id of parameter a at position 0."
+            "\nUpdate your code as this will raise an error in pytest-4.0.",
+            "Raised while trying to determine id of parameter b at position 0."
+            "\nUpdate your code as this will raise an error in pytest-4.0.",
+            "Raised while trying to determine id of parameter a at position 1."
+            "\nUpdate your code as this will raise an error in pytest-4.0.",
+            "Raised while trying to determine id of parameter b at position 1."
+            "\nUpdate your code as this will raise an error in pytest-4.0.",
+            "Raised while trying to determine id of parameter a at position 2."
+            "\nUpdate your code as this will raise an error in pytest-4.0.",
+            "Raised while trying to determine id of parameter b at position 2."
+            "\nUpdate your code as this will raise an error in pytest-4.0.",
+        ]
+
+    def test_parametrize_ids_exception(self, testdir):
+        """
+        :param testdir: the instance of Testdir class, a temporary
+        test directory.
+        """
+        testdir.makepyfile("""
+                import pytest
+
+                def ids(arg):
+                    raise Exception("bad ids")
+
+                @pytest.mark.parametrize("arg", ["a", "b"], ids=ids)
+                def test_foo(arg):
+                    pass
+            """)
+        with pytest.warns(DeprecationWarning):
+            result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "<Module 'test_parametrize_ids_exception.py'>",
+            "  <Function 'test_foo[a]'>",
+            "  <Function 'test_foo[b]'>",
+        ])
+
+    def test_idmaker_with_ids(self):
+        from _pytest.python import idmaker
+        result = idmaker(("a", "b"), [pytest.param(1, 2),
+                                      pytest.param(3, 4)],
+                         ids=["a", None])
+        assert result == ["a", "3-4"]
+
+    def test_idmaker_with_paramset_id(self):
+        from _pytest.python import idmaker
+        result = idmaker(("a", "b"), [pytest.param(1, 2, id="me"),
+                                      pytest.param(3, 4, id="you")],
+                         ids=["a", None])
+        assert result == ["me", "you"]
+
+    def test_idmaker_with_ids_unique_names(self):
+        from _pytest.python import idmaker
+        result = idmaker(("a"), map(pytest.param, [1, 2, 3, 4, 5]),
+                         ids=["a", "a", "b", "c", "b"])
+        assert result == ["a0", "a1", "b0", "c", "b1"]
+
+    def test_addcall_and_parametrize(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.addcall({'x': 1})
+        metafunc.parametrize('y', [2, 3])
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].funcargs == {'x': 1, 'y': 2}
+        assert metafunc._calls[1].funcargs == {'x': 1, 'y': 3}
+        assert metafunc._calls[0].id == "0-2"
+        assert metafunc._calls[1].id == "0-3"
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.parametrize('x', [1], indirect=True)
+        metafunc.parametrize('y', [2, 3], indirect=True)
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].funcargs == {}
+        assert metafunc._calls[1].funcargs == {}
+        assert metafunc._calls[0].params == dict(x=1, y=2)
+        assert metafunc._calls[1].params == dict(x=1, y=3)
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect_list(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.parametrize('x, y', [('a', 'b')], indirect=['x'])
+        assert metafunc._calls[0].funcargs == dict(y='b')
+        assert metafunc._calls[0].params == dict(x='a')
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect_list_all(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'y'])
+        assert metafunc._calls[0].funcargs == {}
+        assert metafunc._calls[0].params == dict(x='a', y='b')
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect_list_empty(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.parametrize('x, y', [('a', 'b')], indirect=[])
+        assert metafunc._calls[0].funcargs == dict(x='a', y='b')
+        assert metafunc._calls[0].params == {}
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect_list_functional(self, testdir):
+        """
+        Test parametrization with 'indirect' parameter applied on
+        particular arguments. As y is is direct, its value should
+        be used directly rather than being passed to the fixture
+        y.
+
+        :param testdir: the instance of Testdir class, a temporary
+        test directory.
+        """
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope='function')
+            def x(request):
+                return request.param * 3
+            @pytest.fixture(scope='function')
+            def y(request):
+                return request.param * 2
+            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
+            def test_simple(x,y):
+                assert len(x) == 3
+                assert len(y) == 1
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*test_simple*a-b*",
+            "*1 passed*",
+        ])
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect_list_error(self, testdir):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        with pytest.raises(ValueError):
+            metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'z'])
+
+    @pytest.mark.issue714
+    def test_parametrize_uses_no_fixture_error_indirect_false(self, testdir):
+        """The 'uses no fixture' error tells the user at collection time
+        that the parametrize data they've set up doesn't correspond to the
+        fixtures in their test function, rather than silently ignoring this
+        and letting the test potentially pass.
+        """
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=False)
+            def test_simple(x):
+                assert len(x) == 3
+        """)
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*uses no argument 'y'*",
+        ])
+
+    @pytest.mark.issue714
+    def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope='function')
+            def x(request):
+                return request.param * 3
+            @pytest.fixture(scope='function')
+            def y(request):
+                return request.param * 2
+
+            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=True)
+            def test_simple(x):
+                assert len(x) == 3
+        """)
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*uses no fixture 'y'*",
+        ])
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect_uses_no_fixture_error_indirect_string(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope='function')
+            def x(request):
+                return request.param * 3
+
+            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect='y')
+            def test_simple(x):
+                assert len(x) == 3
+        """)
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*uses no fixture 'y'*",
+        ])
+
+    @pytest.mark.issue714
+    def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope='function')
+            def x(request):
+                return request.param * 3
+
+            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['y'])
+            def test_simple(x):
+                assert len(x) == 3
+        """)
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*uses no fixture 'y'*",
+        ])
+
+    @pytest.mark.issue714
+    def test_parametrize_argument_not_in_indirect_list(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope='function')
+            def x(request):
+                return request.param * 3
+
+            @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
+            def test_simple(x):
+                assert len(x) == 3
+        """)
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*uses no argument 'y'*",
+        ])
+
+    def test_addcalls_and_parametrize_indirect(self):
+        def func(x, y):
+            pass
+        metafunc = self.Metafunc(func)
+        metafunc.addcall(param="123")
+        metafunc.parametrize('x', [1], indirect=True)
+        metafunc.parametrize('y', [2, 3], indirect=True)
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].funcargs == {}
+        assert metafunc._calls[1].funcargs == {}
+        assert metafunc._calls[0].params == dict(x=1, y=2)
+        assert metafunc._calls[1].params == dict(x=1, y=3)
+
+    def test_parametrize_functional(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize('x', [1,2], indirect=True)
+                metafunc.parametrize('y', [2])
+            @pytest.fixture
+            def x(request):
+                return request.param * 10
+
+            def test_simple(x,y):
+                assert x in (10,20)
+                assert y == 2
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*test_simple*1-2*",
+            "*test_simple*2-2*",
+            "*2 passed*",
+        ])
+
+    def test_parametrize_onearg(self):
+        metafunc = self.Metafunc(lambda x: None)
+        metafunc.parametrize("x", [1, 2])
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].funcargs == dict(x=1)
+        assert metafunc._calls[0].id == "1"
+        assert metafunc._calls[1].funcargs == dict(x=2)
+        assert metafunc._calls[1].id == "2"
+
+    def test_parametrize_onearg_indirect(self):
+        metafunc = self.Metafunc(lambda x: None)
+        metafunc.parametrize("x", [1, 2], indirect=True)
+        assert metafunc._calls[0].params == dict(x=1)
+        assert metafunc._calls[0].id == "1"
+        assert metafunc._calls[1].params == dict(x=2)
+        assert metafunc._calls[1].id == "2"
+
+    def test_parametrize_twoargs(self):
+        metafunc = self.Metafunc(lambda x, y: None)
+        metafunc.parametrize(("x", "y"), [(1, 2), (3, 4)])
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].funcargs == dict(x=1, y=2)
+        assert metafunc._calls[0].id == "1-2"
+        assert metafunc._calls[1].funcargs == dict(x=3, y=4)
+        assert metafunc._calls[1].id == "3-4"
+
+    def test_parametrize_multiple_times(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            pytestmark = pytest.mark.parametrize("x", [1,2])
+            def test_func(x):
+                assert 0, x
+            class TestClass(object):
+                pytestmark = pytest.mark.parametrize("y", [3,4])
+                def test_meth(self, x, y):
+                    assert 0, x
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 1
+        result.assert_outcomes(failed=6)
+
+    def test_parametrize_CSV(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.parametrize("x, y,", [(1,2), (2,3)])
+            def test_func(x, y):
+                assert x+1 == y
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    def test_parametrize_class_scenarios(self, testdir):
+        testdir.makepyfile("""
+        # same as doc/en/example/parametrize scenario example
+        def pytest_generate_tests(metafunc):
+            idlist = []
+            argvalues = []
+            for scenario in metafunc.cls.scenarios:
+                idlist.append(scenario[0])
+                items = scenario[1].items()
+                argnames = [x[0] for x in items]
+                argvalues.append(([x[1] for x in items]))
+            metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
+
+        class Test(object):
+               scenarios = [['1', {'arg': {1: 2}, "arg2": "value2"}],
+                            ['2', {'arg':'value2', "arg2": "value2"}]]
+
+               def test_1(self, arg, arg2):
+                  pass
+
+               def test_2(self, arg2, arg):
+                  pass
+
+               def test_3(self, arg, arg2):
+                  pass
+        """)
+        result = testdir.runpytest("-v")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines("""
+            *test_1*1*
+            *test_2*1*
+            *test_3*1*
+            *test_1*2*
+            *test_2*2*
+            *test_3*2*
+            *6 passed*
+        """)
+
+    def test_format_args(self):
+        def function1():
+            pass
+        assert fixtures._format_args(function1) == '()'
+
+        def function2(arg1):
+            pass
+        assert fixtures._format_args(function2) == "(arg1)"
+
+        def function3(arg1, arg2="qwe"):
+            pass
+        assert fixtures._format_args(function3) == "(arg1, arg2='qwe')"
+
+        def function4(arg1, *args, **kwargs):
+            pass
+        assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)"
+
+
+class TestMetafuncFunctional(object):
+    def test_attributes(self, testdir):
+        p = testdir.makepyfile("""
+            # assumes that generate/provide runs in the same process
+            import py, pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(param=metafunc)
+
+            @pytest.fixture
+            def metafunc(request):
+                assert request._pyfuncitem._genid == "0"
+                return request.param
+
+            def test_function(metafunc, pytestconfig):
+                assert metafunc.config == pytestconfig
+                assert metafunc.module.__name__ == __name__
+                assert metafunc.function == test_function
+                assert metafunc.cls is None
+
+            class TestClass(object):
+                def test_method(self, metafunc, pytestconfig):
+                    assert metafunc.config == pytestconfig
+                    assert metafunc.module.__name__ == __name__
+                    if py.std.sys.version_info > (3, 0):
+                        unbound = TestClass.test_method
+                    else:
+                        unbound = TestClass.test_method.im_func
+                    # XXX actually have an unbound test function here?
+                    assert metafunc.function == unbound
+                    assert metafunc.cls == TestClass
+        """)
+        result = testdir.runpytest(p, "-v")
+        result.assert_outcomes(passed=2)
+
+    def test_addcall_with_two_funcargs_generators(self, testdir):
+        testdir.makeconftest("""
+            def pytest_generate_tests(metafunc):
+                assert "arg1" in metafunc.fixturenames
+                metafunc.addcall(funcargs=dict(arg1=1, arg2=2))
+        """)
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(funcargs=dict(arg1=1, arg2=1))
+
+            class TestClass(object):
+                def test_myfunc(self, arg1, arg2):
+                    assert arg1 == arg2
+        """)
+        result = testdir.runpytest("-v", p)
+        result.stdout.fnmatch_lines([
+            "*test_myfunc*0*PASS*",
+            "*test_myfunc*1*FAIL*",
+            "*1 failed, 1 passed*"
+        ])
+
+    def test_two_functions(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(param=10)
+                metafunc.addcall(param=20)
+
+            import pytest
+            @pytest.fixture
+            def arg1(request):
+                return request.param
+
+            def test_func1(arg1):
+                assert arg1 == 10
+            def test_func2(arg1):
+                assert arg1 in (10, 20)
+        """)
+        result = testdir.runpytest("-v", p)
+        result.stdout.fnmatch_lines([
+            "*test_func1*0*PASS*",
+            "*test_func1*1*FAIL*",
+            "*test_func2*PASS*",
+            "*1 failed, 3 passed*"
+        ])
+
+    def test_noself_in_method(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                assert 'xyz' not in metafunc.fixturenames
+
+            class TestHello(object):
+                def test_hello(xyz):
+                    pass
+        """)
+        result = testdir.runpytest(p)
+        result.assert_outcomes(passed=1)
+
+    def test_generate_plugin_and_module(self, testdir):
+        testdir.makeconftest("""
+            def pytest_generate_tests(metafunc):
+                assert "arg1" in metafunc.fixturenames
+                metafunc.addcall(id="world", param=(2,100))
+        """)
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(param=(1,1), id="hello")
+
+            import pytest
+            @pytest.fixture
+            def arg1(request):
+                return request.param[0]
+            @pytest.fixture
+            def arg2(request):
+                return request.param[1]
+
+            class TestClass(object):
+                def test_myfunc(self, arg1, arg2):
+                    assert arg1 == arg2
+        """)
+        result = testdir.runpytest("-v", p)
+        result.stdout.fnmatch_lines([
+            "*test_myfunc*hello*PASS*",
+            "*test_myfunc*world*FAIL*",
+            "*1 failed, 1 passed*"
+        ])
+
+    def test_generate_tests_in_class(self, testdir):
+        p = testdir.makepyfile("""
+            class TestClass(object):
+                def pytest_generate_tests(self, metafunc):
+                    metafunc.addcall(funcargs={'hello': 'world'}, id="hello")
+
+                def test_myfunc(self, hello):
+                    assert hello == "world"
+        """)
+        result = testdir.runpytest("-v", p)
+        result.stdout.fnmatch_lines([
+            "*test_myfunc*hello*PASS*",
+            "*1 passed*"
+        ])
+
+    def test_two_functions_not_same_instance(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall({'arg1': 10})
+                metafunc.addcall({'arg1': 20})
+
+            class TestClass(object):
+                def test_func(self, arg1):
+                    assert not hasattr(self, 'x')
+                    self.x = 1
+        """)
+        result = testdir.runpytest("-v", p)
+        result.stdout.fnmatch_lines([
+            "*test_func*0*PASS*",
+            "*test_func*1*PASS*",
+            "*2 pass*",
+        ])
+
+    def test_issue28_setup_method_in_generate_tests(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall({'arg1': 1})
+
+            class TestClass(object):
+                def test_method(self, arg1):
+                    assert arg1 == self.val
+                def setup_method(self, func):
+                    self.val = 1
+            """)
+        result = testdir.runpytest(p)
+        result.assert_outcomes(passed=1)
+
+    def test_parametrize_functional2(self, testdir):
+        testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize("arg1", [1,2])
+                metafunc.parametrize("arg2", [4,5])
+            def test_hello(arg1, arg2):
+                assert 0, (arg1, arg2)
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*(1, 4)*",
+            "*(1, 5)*",
+            "*(2, 4)*",
+            "*(2, 5)*",
+            "*4 failed*",
+        ])
+
+    def test_parametrize_and_inner_getfixturevalue(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize("arg1", [1], indirect=True)
+                metafunc.parametrize("arg2", [10], indirect=True)
+
+            import pytest
+            @pytest.fixture
+            def arg1(request):
+                x = request.getfixturevalue("arg2")
+                return x + request.param
+
+            @pytest.fixture
+            def arg2(request):
+                return request.param
+
+            def test_func1(arg1, arg2):
+                assert arg1 == 11
+        """)
+        result = testdir.runpytest("-v", p)
+        result.stdout.fnmatch_lines([
+            "*test_func1*1*PASS*",
+            "*1 passed*"
+        ])
+
+    def test_parametrize_on_setup_arg(self, testdir):
+        p = testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                assert "arg1" in metafunc.fixturenames
+                metafunc.parametrize("arg1", [1], indirect=True)
+
+            import pytest
+            @pytest.fixture
+            def arg1(request):
+                return request.param
+
+            @pytest.fixture
+            def arg2(request, arg1):
+                return 10 * arg1
+
+            def test_func(arg2):
+                assert arg2 == 10
+        """)
+        result = testdir.runpytest("-v", p)
+        result.stdout.fnmatch_lines([
+            "*test_func*1*PASS*",
+            "*1 passed*"
+        ])
+
+    def test_parametrize_with_ids(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            console_output_style=classic
+        """)
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize(("a", "b"), [(1,1), (1,2)],
+                                     ids=["basic", "advanced"])
+
+            def test_function(a, b):
+                assert a == b
+        """)
+        result = testdir.runpytest("-v")
+        assert result.ret == 1
+        result.stdout.fnmatch_lines_random([
+            "*test_function*basic*PASSED",
+            "*test_function*advanced*FAILED",
+        ])
+
+    def test_parametrize_without_ids(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize(("a", "b"),
+                                     [(1,object()), (1.3,object())])
+
+            def test_function(a, b):
+                assert 1
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines("""
+            *test_function*1-b0*
+            *test_function*1.3-b1*
+        """)
+
+    def test_parametrize_with_None_in_ids(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize(("a", "b"), [(1,1), (1,1), (1,2)],
+                                     ids=["basic", None, "advanced"])
+
+            def test_function(a, b):
+                assert a == b
+        """)
+        result = testdir.runpytest("-v")
+        assert result.ret == 1
+        result.stdout.fnmatch_lines_random([
+            "*test_function*basic*PASSED*",
+            "*test_function*1-1*PASSED*",
+            "*test_function*advanced*FAILED*",
+        ])
+
+    def test_fixture_parametrized_empty_ids(self, testdir):
+        """Fixtures parametrized with empty ids cause an internal error (#1849)."""
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.fixture(scope="module", ids=[], params=[])
+            def temp(request):
+               return request.param
+
+            def test_temp(temp):
+                 pass
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['* 1 skipped *'])
+
+    def test_parametrized_empty_ids(self, testdir):
+        """Tests parametrized with empty ids cause an internal error (#1849)."""
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.mark.parametrize('temp', [], ids=list())
+            def test_temp(temp):
+                 pass
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['* 1 skipped *'])
+
+    def test_parametrized_ids_invalid_type(self, testdir):
+        """Tests parametrized with ids as non-strings (#1857)."""
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.mark.parametrize("x, expected", [(10, 20), (40, 80)], ids=(None, 2))
+            def test_ids_numbers(x,expected):
+                assert x * 2 == expected
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['*ids must be list of strings, found: 2 (type: int)*'])
+
+    def test_parametrize_with_identical_ids_get_unique_names(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize(("a", "b"), [(1,1), (1,2)],
+                                     ids=["a", "a"])
+
+            def test_function(a, b):
+                assert a == b
+        """)
+        result = testdir.runpytest("-v")
+        assert result.ret == 1
+        result.stdout.fnmatch_lines_random([
+            "*test_function*a0*PASSED*",
+            "*test_function*a1*FAILED*"
+        ])
+
+    @pytest.mark.parametrize(("scope", "length"),
+                             [("module", 2), ("function", 4)])
+    def test_parametrize_scope_overrides(self, testdir, scope, length):
+        testdir.makepyfile("""
+            import pytest
+            values = []
+            def pytest_generate_tests(metafunc):
+                if "arg" in metafunc.funcargnames:
+                    metafunc.parametrize("arg", [1,2], indirect=True,
+                                         scope=%r)
+            @pytest.fixture
+            def arg(request):
+                values.append(request.param)
+                return request.param
+            def test_hello(arg):
+                assert arg in (1,2)
+            def test_world(arg):
+                assert arg in (1,2)
+            def test_checklength():
+                assert len(values) == %d
+        """ % (scope, length))
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=5)
+
+    def test_parametrize_issue323(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture(scope='module', params=range(966))
+            def foo(request):
+                return request.param
+
+            def test_it(foo):
+                pass
+            def test_it2(foo):
+                pass
+        """)
+        reprec = testdir.inline_run("--collect-only")
+        assert not reprec.getcalls("pytest_internalerror")
+
+    def test_usefixtures_seen_in_generate_tests(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                assert "abc" in metafunc.fixturenames
+                metafunc.parametrize("abc", [1])
+
+            @pytest.mark.usefixtures("abc")
+            def test_function():
+                pass
+        """)
+        reprec = testdir.runpytest()
+        reprec.assert_outcomes(passed=1)
+
+    def test_generate_tests_only_done_in_subdir(self, testdir):
+        sub1 = testdir.mkpydir("sub1")
+        sub2 = testdir.mkpydir("sub2")
+        sub1.join("conftest.py").write(_pytest._code.Source("""
+            def pytest_generate_tests(metafunc):
+                assert metafunc.function.__name__ == "test_1"
+        """))
+        sub2.join("conftest.py").write(_pytest._code.Source("""
+            def pytest_generate_tests(metafunc):
+                assert metafunc.function.__name__ == "test_2"
+        """))
+        sub1.join("test_in_sub1.py").write("def test_1(): pass")
+        sub2.join("test_in_sub2.py").write("def test_2(): pass")
+        result = testdir.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1)
+        result.assert_outcomes(passed=3)
+
+    def test_generate_same_function_names_issue403(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            def make_tests():
+                @pytest.mark.parametrize("x", range(2))
+                def test_foo(x):
+                    pass
+                return test_foo
+
+            test_x = make_tests()
+            test_y = make_tests()
+        """)
+        reprec = testdir.runpytest()
+        reprec.assert_outcomes(passed=4)
+
+    @pytest.mark.issue463
+    @pytest.mark.parametrize('attr', ['parametrise', 'parameterize',
+                                      'parameterise'])
+    def test_parametrize_misspelling(self, testdir, attr):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.mark.{0}("x", range(2))
+            def test_foo(x):
+                pass
+        """.format(attr))
+        reprec = testdir.inline_run('--collectonly')
+        failures = reprec.getfailures()
+        assert len(failures) == 1
+        expectederror = "MarkerError: test_foo has '{0}', spelling should be 'parametrize'".format(attr)
+        assert expectederror in failures[0].longrepr.reprcrash.message
+
+
+class TestMetafuncFunctionalAuto(object):
+    """
+    Tests related to automatically find out the correct scope for parametrized tests (#1832).
+    """
+
+    def test_parametrize_auto_scope(self, testdir):
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.fixture(scope='session', autouse=True)
+            def fixture():
+                return 1
+
+            @pytest.mark.parametrize('animal', ["dog", "cat"])
+            def test_1(animal):
+                assert animal in ('dog', 'cat')
+
+            @pytest.mark.parametrize('animal', ['fish'])
+            def test_2(animal):
+                assert animal == 'fish'
+
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['* 3 passed *'])
+
+    def test_parametrize_auto_scope_indirect(self, testdir):
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.fixture(scope='session')
+            def echo(request):
+                return request.param
+
+            @pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=['echo'])
+            def test_1(animal, echo):
+                assert animal in ('dog', 'cat')
+                assert echo in (1, 2, 3)
+
+            @pytest.mark.parametrize('animal, echo', [('fish', 3)], indirect=['echo'])
+            def test_2(animal, echo):
+                assert animal == 'fish'
+                assert echo in (1, 2, 3)
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['* 3 passed *'])
+
+    def test_parametrize_auto_scope_override_fixture(self, testdir):
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.fixture(scope='session', autouse=True)
+            def animal():
+                return 'fox'
+
+            @pytest.mark.parametrize('animal', ["dog", "cat"])
+            def test_1(animal):
+                assert animal in ('dog', 'cat')
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['* 2 passed *'])
+
+    def test_parametrize_all_indirects(self, testdir):
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.fixture()
+            def animal(request):
+                return request.param
+
+            @pytest.fixture(scope='session')
+            def echo(request):
+                return request.param
+
+            @pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=True)
+            def test_1(animal, echo):
+                assert animal in ('dog', 'cat')
+                assert echo in (1, 2, 3)
+
+            @pytest.mark.parametrize('animal, echo', [("fish", 3)], indirect=True)
+            def test_2(animal, echo):
+                assert animal == 'fish'
+                assert echo in (1, 2, 3)
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['* 3 passed *'])
+
+    def test_parametrize_issue634(self, testdir):
+        testdir.makepyfile('''
+            import pytest
+
+            @pytest.fixture(scope='module')
+            def foo(request):
+                print('preparing foo-%d' % request.param)
+                return 'foo-%d' % request.param
+
+            def test_one(foo):
+                pass
+
+            def test_two(foo):
+                pass
+
+            test_two.test_with = (2, 3)
+
+            def pytest_generate_tests(metafunc):
+                params = (1, 2, 3, 4)
+                if not 'foo' in metafunc.fixturenames:
+                    return
+
+                test_with = getattr(metafunc.function, 'test_with', None)
+                if test_with:
+                    params = test_with
+                metafunc.parametrize('foo', params, indirect=True)
+        ''')
+        result = testdir.runpytest("-s")
+        output = result.stdout.str()
+        assert output.count('preparing foo-2') == 1
+        assert output.count('preparing foo-3') == 1
+
+
+@pytest.mark.filterwarnings('ignore:Applying marks directly to parameters')
+@pytest.mark.issue308
+class TestMarkersWithParametrization(object):
+
+    def test_simple_mark(self, testdir):
+        s = """
+            import pytest
+
+            @pytest.mark.foo
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                pytest.mark.bar((1, 3)),
+                (2, 3),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        items = testdir.getitems(s)
+        assert len(items) == 3
+        for item in items:
+            assert 'foo' in item.keywords
+        assert 'bar' not in items[0].keywords
+        assert 'bar' in items[1].keywords
+        assert 'bar' not in items[2].keywords
+
+    def test_select_based_on_mark(self, testdir):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                pytest.mark.foo((2, 3)),
+                (3, 4),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        testdir.makepyfile(s)
+        rec = testdir.inline_run("-m", 'foo')
+        passed, skipped, fail = rec.listoutcomes()
+        assert len(passed) == 1
+        assert len(skipped) == 0
+        assert len(fail) == 0
+
+    @pytest.mark.xfail(reason="is this important to support??")
+    def test_nested_marks(self, testdir):
+        s = """
+            import pytest
+            mastermark = pytest.mark.foo(pytest.mark.bar)
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                mastermark((1, 3)),
+                (2, 3),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        items = testdir.getitems(s)
+        assert len(items) == 3
+        for mark in ['foo', 'bar']:
+            assert mark not in items[0].keywords
+            assert mark in items[1].keywords
+            assert mark not in items[2].keywords
+
+    def test_simple_xfail(self, testdir):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                pytest.mark.xfail((1, 3)),
+                (2, 3),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        # xfail is skip??
+        reprec.assertoutcome(passed=2, skipped=1)
+
+    def test_simple_xfail_single_argname(self, testdir):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize("n", [
+                2,
+                pytest.mark.xfail(3),
+                4,
+            ])
+            def test_isEven(n):
+                assert n % 2 == 0
+        """
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2, skipped=1)
+
+    def test_xfail_with_arg(self, testdir):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                pytest.mark.xfail("True")((1, 3)),
+                (2, 3),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2, skipped=1)
+
+    def test_xfail_with_kwarg(self, testdir):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                pytest.mark.xfail(reason="some bug")((1, 3)),
+                (2, 3),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2, skipped=1)
+
+    def test_xfail_with_arg_and_kwarg(self, testdir):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                pytest.mark.xfail("True", reason="some bug")((1, 3)),
+                (2, 3),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2, skipped=1)
+
+    @pytest.mark.parametrize('strict', [True, False])
+    def test_xfail_passing_is_xpass(self, testdir, strict):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                (1, 2),
+                pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})((2, 3)),
+                (3, 4),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """.format(strict=strict)
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        passed, failed = (2, 1) if strict else (3, 0)
+        reprec.assertoutcome(passed=passed, failed=failed)
+
+    def test_parametrize_called_in_generate_tests(self, testdir):
+        s = """
+            import pytest
+
+
+            def pytest_generate_tests(metafunc):
+                passingTestData = [(1, 2),
+                                   (2, 3)]
+                failingTestData = [(1, 3),
+                                   (2, 2)]
+
+                testData = passingTestData + [pytest.mark.xfail(d)
+                                  for d in failingTestData]
+                metafunc.parametrize(("n", "expected"), testData)
+
+
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2, skipped=2)
+
+    @pytest.mark.issue290
+    def test_parametrize_ID_generation_string_int_works(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def myfixture():
+                return 'example'
+            @pytest.mark.parametrize(
+                'limit', (0, '0'))
+            def test_limit(limit, myfixture):
+                return
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
+    @pytest.mark.parametrize('strict', [True, False])
+    def test_parametrize_marked_value(self, testdir, strict):
+        s = """
+            import pytest
+
+            @pytest.mark.parametrize(("n", "expected"), [
+                pytest.param(
+                    2,3,
+                    marks=pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict}),
+                ),
+                pytest.param(
+                    2,3,
+                    marks=[pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})],
+                ),
+            ])
+            def test_increment(n, expected):
+                assert n + 1 == expected
+        """.format(strict=strict)
+        testdir.makepyfile(s)
+        reprec = testdir.inline_run()
+        passed, failed = (0, 2) if strict else (2, 0)
+        reprec.assertoutcome(passed=passed, failed=failed)
+
+    def test_pytest_make_parametrize_id(self, testdir):
+        testdir.makeconftest("""
+            def pytest_make_parametrize_id(config, val):
+                return str(val * 2)
+        """)
+        testdir.makepyfile("""
+                import pytest
+
+                @pytest.mark.parametrize("x", range(2))
+                def test_func(x):
+                    pass
+                """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*test_func*0*PASS*",
+            "*test_func*2*PASS*",
+        ])
+
+    def test_pytest_make_parametrize_id_with_argname(self, testdir):
+        testdir.makeconftest("""
+            def pytest_make_parametrize_id(config, val, argname):
+                return str(val * 2 if argname == 'x' else val * 10)
+        """)
+        testdir.makepyfile("""
+                import pytest
+
+                @pytest.mark.parametrize("x", range(2))
+                def test_func_a(x):
+                    pass
+
+                @pytest.mark.parametrize("y", [1])
+                def test_func_b(y):
+                    pass
+                """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*test_func_a*0*PASS*",
+            "*test_func_a*2*PASS*",
+            "*test_func_b*10*PASS*",
+        ])
diff --git a/tools/third_party/pytest/testing/python/raises.py b/tools/third_party/pytest/testing/python/raises.py
new file mode 100644
index 0000000..321ee34
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/raises.py
@@ -0,0 +1,134 @@
+import pytest
+import sys
+
+
+class TestRaises(object):
+    def test_raises(self):
+        source = "int('qwe')"
+        excinfo = pytest.raises(ValueError, source)
+        code = excinfo.traceback[-1].frame.code
+        s = str(code.fullsource)
+        assert s == source
+
+    def test_raises_exec(self):
+        pytest.raises(ValueError, "a,x = []")
+
+    def test_raises_syntax_error(self):
+        pytest.raises(SyntaxError, "qwe qwe qwe")
+
+    def test_raises_function(self):
+        pytest.raises(ValueError, int, 'hello')
+
+    def test_raises_callable_no_exception(self):
+        class A(object):
+            def __call__(self):
+                pass
+        try:
+            pytest.raises(ValueError, A())
+        except pytest.raises.Exception:
+            pass
+
+    def test_raises_as_contextmanager(self, testdir):
+        testdir.makepyfile("""
+            from __future__ import with_statement
+            import py, pytest
+            import _pytest._code
+
+            def test_simple():
+                with pytest.raises(ZeroDivisionError) as excinfo:
+                    assert isinstance(excinfo, _pytest._code.ExceptionInfo)
+                    1/0
+                print (excinfo)
+                assert excinfo.type == ZeroDivisionError
+                assert isinstance(excinfo.value, ZeroDivisionError)
+
+            def test_noraise():
+                with pytest.raises(pytest.raises.Exception):
+                    with pytest.raises(ValueError):
+                           int()
+
+            def test_raise_wrong_exception_passes_by():
+                with pytest.raises(ZeroDivisionError):
+                    with pytest.raises(ValueError):
+                           1/0
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            '*3 passed*',
+        ])
+
+    def test_noclass(self):
+        with pytest.raises(TypeError):
+            pytest.raises('wrong', lambda: None)
+
+    def test_tuple(self):
+        with pytest.raises((KeyError, ValueError)):
+            raise KeyError('oops')
+
+    def test_no_raise_message(self):
+        try:
+            pytest.raises(ValueError, int, '0')
+        except pytest.raises.Exception as e:
+            assert e.msg == "DID NOT RAISE {0}".format(repr(ValueError))
+        else:
+            assert False, "Expected pytest.raises.Exception"
+
+        try:
+            with pytest.raises(ValueError):
+                pass
+        except pytest.raises.Exception as e:
+            assert e.msg == "DID NOT RAISE {0}".format(repr(ValueError))
+        else:
+            assert False, "Expected pytest.raises.Exception"
+
+    def test_custom_raise_message(self):
+        message = "TEST_MESSAGE"
+        try:
+            with pytest.raises(ValueError, message=message):
+                pass
+        except pytest.raises.Exception as e:
+            assert e.msg == message
+        else:
+            assert False, "Expected pytest.raises.Exception"
+
+    @pytest.mark.parametrize('method', ['function', 'with'])
+    def test_raises_cyclic_reference(self, method):
+        """
+        Ensure pytest.raises does not leave a reference cycle (#1965).
+        """
+        import gc
+
+        class T(object):
+            def __call__(self):
+                raise ValueError
+
+        t = T()
+        if method == 'function':
+            pytest.raises(ValueError, t)
+        else:
+            with pytest.raises(ValueError):
+                t()
+
+        # ensure both forms of pytest.raises don't leave exceptions in sys.exc_info()
+        assert sys.exc_info() == (None, None, None)
+
+        del t
+
+        # ensure the t instance is not stuck in a cyclic reference
+        for o in gc.get_objects():
+            assert type(o) is not T
+
+    def test_raises_match(self):
+        msg = r"with base \d+"
+        with pytest.raises(ValueError, match=msg):
+            int('asdf')
+
+        msg = "with base 10"
+        with pytest.raises(ValueError, match=msg):
+            int('asdf')
+
+        msg = "with base 16"
+        expr = r"Pattern '{0}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format(msg)
+        with pytest.raises(AssertionError, match=expr):
+            with pytest.raises(ValueError, match=msg):
+                int('asdf', base=10)
diff --git a/tools/third_party/pytest/testing/python/setup_only.py b/tools/third_party/pytest/testing/python/setup_only.py
new file mode 100644
index 0000000..ab34312
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/setup_only.py
@@ -0,0 +1,243 @@
+import pytest
+
+
+@pytest.fixture(params=['--setup-only', '--setup-plan', '--setup-show'],
+                scope='module')
+def mode(request):
+    return request.param
+
+
+def test_show_only_active_fixtures(testdir, mode):
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def _arg0():
+            """hidden arg0 fixture"""
+        @pytest.fixture
+        def arg1():
+            """arg1 docstring"""
+        def test_arg1(arg1):
+            pass
+    ''')
+
+    result = testdir.runpytest(mode, p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*SETUP    F arg1*',
+        '*test_arg1 (fixtures used: arg1)*',
+        '*TEARDOWN F arg1*',
+    ])
+    assert "_arg0" not in result.stdout.str()
+
+
+def test_show_different_scopes(testdir, mode):
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def arg_function():
+            """function scoped fixture"""
+        @pytest.fixture(scope='session')
+        def arg_session():
+            """session scoped fixture"""
+        def test_arg1(arg_session, arg_function):
+            pass
+    ''')
+
+    result = testdir.runpytest(mode, p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        'SETUP    S arg_session*',
+        '*SETUP    F arg_function*',
+        '*test_arg1 (fixtures used: arg_function, arg_session)*',
+        '*TEARDOWN F arg_function*',
+        'TEARDOWN S arg_session*',
+    ])
+
+
+def test_show_nested_fixtures(testdir, mode):
+    testdir.makeconftest('''
+        import pytest
+        @pytest.fixture(scope='session')
+        def arg_same():
+            """session scoped fixture"""
+        ''')
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture(scope='function')
+        def arg_same(arg_same):
+            """function scoped fixture"""
+        def test_arg1(arg_same):
+            pass
+    ''')
+
+    result = testdir.runpytest(mode, p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        'SETUP    S arg_same*',
+        '*SETUP    F arg_same (fixtures used: arg_same)*',
+        '*test_arg1 (fixtures used: arg_same)*',
+        '*TEARDOWN F arg_same*',
+        'TEARDOWN S arg_same*',
+    ])
+
+
+def test_show_fixtures_with_autouse(testdir, mode):
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def arg_function():
+            """function scoped fixture"""
+        @pytest.fixture(scope='session', autouse=True)
+        def arg_session():
+            """session scoped fixture"""
+        def test_arg1(arg_function):
+            pass
+    ''')
+
+    result = testdir.runpytest(mode, p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        'SETUP    S arg_session*',
+        '*SETUP    F arg_function*',
+        '*test_arg1 (fixtures used: arg_function, arg_session)*',
+    ])
+
+
+def test_show_fixtures_with_parameters(testdir, mode):
+    testdir.makeconftest('''
+        import pytest
+        @pytest.fixture(scope='session', params=['foo', 'bar'])
+        def arg_same():
+            """session scoped fixture"""
+        ''')
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture(scope='function')
+        def arg_other(arg_same):
+            """function scoped fixture"""
+        def test_arg1(arg_other):
+            pass
+    ''')
+
+    result = testdir.runpytest(mode, p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        'SETUP    S arg_same?foo?',
+        'TEARDOWN S arg_same?foo?',
+        'SETUP    S arg_same?bar?',
+        'TEARDOWN S arg_same?bar?',
+    ])
+
+
+def test_show_fixtures_with_parameter_ids(testdir, mode):
+    testdir.makeconftest('''
+        import pytest
+        @pytest.fixture(
+            scope='session', params=['foo', 'bar'], ids=['spam', 'ham'])
+        def arg_same():
+            """session scoped fixture"""
+        ''')
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture(scope='function')
+        def arg_other(arg_same):
+            """function scoped fixture"""
+        def test_arg1(arg_other):
+            pass
+    ''')
+
+    result = testdir.runpytest(mode, p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        'SETUP    S arg_same?spam?',
+        'SETUP    S arg_same?ham?',
+    ])
+
+
+def test_show_fixtures_with_parameter_ids_function(testdir, mode):
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture(params=['foo', 'bar'], ids=lambda p: p.upper())
+        def foobar():
+            pass
+        def test_foobar(foobar):
+            pass
+    ''')
+
+    result = testdir.runpytest(mode, p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*SETUP    F foobar?FOO?',
+        '*SETUP    F foobar?BAR?',
+    ])
+
+
+def test_dynamic_fixture_request(testdir):
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture()
+        def dynamically_requested_fixture():
+            pass
+        @pytest.fixture()
+        def dependent_fixture(request):
+            request.getfixturevalue('dynamically_requested_fixture')
+        def test_dyn(dependent_fixture):
+            pass
+    ''')
+
+    result = testdir.runpytest('--setup-only', p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*SETUP    F dynamically_requested_fixture',
+        '*TEARDOWN F dynamically_requested_fixture'
+    ])
+
+
+def test_capturing(testdir):
+    p = testdir.makepyfile('''
+        import pytest, sys
+        @pytest.fixture()
+        def one():
+            sys.stdout.write('this should be captured')
+            sys.stderr.write('this should also be captured')
+        @pytest.fixture()
+        def two(one):
+            assert 0
+        def test_capturing(two):
+            pass
+    ''')
+
+    result = testdir.runpytest('--setup-only', p)
+    result.stdout.fnmatch_lines([
+        'this should be captured',
+        'this should also be captured'
+    ])
+
+
+def test_show_fixtures_and_execute_test(testdir):
+    """ Verifies that setups are shown and tests are executed. """
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def arg():
+            assert True
+        def test_arg(arg):
+            assert False
+    ''')
+
+    result = testdir.runpytest("--setup-show", p)
+    assert result.ret == 1
+
+    result.stdout.fnmatch_lines([
+        '*SETUP    F arg*',
+        '*test_arg (fixtures used: arg)F*',
+        '*TEARDOWN F arg*',
+    ])
diff --git a/tools/third_party/pytest/testing/python/setup_plan.py b/tools/third_party/pytest/testing/python/setup_plan.py
new file mode 100644
index 0000000..8c98224
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/setup_plan.py
@@ -0,0 +1,19 @@
+def test_show_fixtures_and_test(testdir):
+    """ Verifies that fixtures are not executed. """
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def arg():
+            assert False
+        def test_arg(arg):
+            assert False
+    ''')
+
+    result = testdir.runpytest("--setup-plan", p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*SETUP    F arg*',
+        '*test_arg (fixtures used: arg)',
+        '*TEARDOWN F arg*',
+    ])
diff --git a/tools/third_party/pytest/testing/python/show_fixtures_per_test.py b/tools/third_party/pytest/testing/python/show_fixtures_per_test.py
new file mode 100644
index 0000000..741f339
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/show_fixtures_per_test.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+
+
+def test_no_items_should_not_show_output(testdir):
+    result = testdir.runpytest('--fixtures-per-test')
+    assert 'fixtures used by' not in result.stdout.str()
+    assert result.ret == 0
+
+
+def test_fixtures_in_module(testdir):
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def _arg0():
+            """hidden arg0 fixture"""
+        @pytest.fixture
+        def arg1():
+            """arg1 docstring"""
+        def test_arg1(arg1):
+            pass
+    ''')
+
+    result = testdir.runpytest("--fixtures-per-test", p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*fixtures used by test_arg1*',
+        '*(test_fixtures_in_module.py:9)*',
+        'arg1',
+        '    arg1 docstring',
+    ])
+    assert "_arg0" not in result.stdout.str()
+
+
+def test_fixtures_in_conftest(testdir):
+    testdir.makeconftest('''
+        import pytest
+        @pytest.fixture
+        def arg1():
+            """arg1 docstring"""
+        @pytest.fixture
+        def arg2():
+            """arg2 docstring"""
+        @pytest.fixture
+        def arg3(arg1, arg2):
+            """arg3
+            docstring
+            """
+    ''')
+    p = testdir.makepyfile('''
+        def test_arg2(arg2):
+            pass
+        def test_arg3(arg3):
+            pass
+    ''')
+    result = testdir.runpytest("--fixtures-per-test", p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*fixtures used by test_arg2*',
+        '*(test_fixtures_in_conftest.py:2)*',
+        'arg2',
+        '    arg2 docstring',
+        '*fixtures used by test_arg3*',
+        '*(test_fixtures_in_conftest.py:4)*',
+        'arg1',
+        '    arg1 docstring',
+        'arg2',
+        '    arg2 docstring',
+        'arg3',
+        '    arg3',
+        '    docstring',
+    ])
+
+
+def test_should_show_fixtures_used_by_test(testdir):
+    testdir.makeconftest('''
+        import pytest
+        @pytest.fixture
+        def arg1():
+            """arg1 from conftest"""
+        @pytest.fixture
+        def arg2():
+            """arg2 from conftest"""
+    ''')
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def arg1():
+            """arg1 from testmodule"""
+        def test_args(arg1, arg2):
+            pass
+    ''')
+    result = testdir.runpytest("--fixtures-per-test", p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*fixtures used by test_args*',
+        '*(test_should_show_fixtures_used_by_test.py:6)*',
+        'arg1',
+        '    arg1 from testmodule',
+        'arg2',
+        '    arg2 from conftest',
+    ])
+
+
+def test_verbose_include_private_fixtures_and_loc(testdir):
+    testdir.makeconftest('''
+        import pytest
+        @pytest.fixture
+        def _arg1():
+            """_arg1 from conftest"""
+        @pytest.fixture
+        def arg2(_arg1):
+            """arg2 from conftest"""
+    ''')
+    p = testdir.makepyfile('''
+        import pytest
+        @pytest.fixture
+        def arg3():
+            """arg3 from testmodule"""
+        def test_args(arg2, arg3):
+            pass
+    ''')
+    result = testdir.runpytest("--fixtures-per-test", "-v", p)
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*fixtures used by test_args*',
+        '*(test_verbose_include_private_fixtures_and_loc.py:6)*',
+        '_arg1 -- conftest.py:3',
+        '    _arg1 from conftest',
+        'arg2 -- conftest.py:6',
+        '    arg2 from conftest',
+        'arg3 -- test_verbose_include_private_fixtures_and_loc.py:3',
+        '    arg3 from testmodule',
+    ])
+
+
+def test_doctest_items(testdir):
+    testdir.makepyfile('''
+        def foo():
+            """
+            >>> 1 + 1
+            2
+            """
+    ''')
+    testdir.maketxtfile('''
+        >>> 1 + 1
+        2
+    ''')
+    result = testdir.runpytest("--fixtures-per-test", "--doctest-modules",
+                               "--doctest-glob=*.txt", "-v")
+    assert result.ret == 0
+
+    result.stdout.fnmatch_lines([
+        '*collected 2 items*',
+    ])
diff --git a/tools/third_party/pytest/testing/python/test_deprecations.py b/tools/third_party/pytest/testing/python/test_deprecations.py
new file mode 100644
index 0000000..5001f76
--- /dev/null
+++ b/tools/third_party/pytest/testing/python/test_deprecations.py
@@ -0,0 +1,22 @@
+import pytest
+
+from _pytest.python import PyCollector
+
+
+class PyCollectorMock(PyCollector):
+    """evil hack"""
+
+    def __init__(self):
+        self.called = False
+
+    def _makeitem(self, *k):
+        """hack to disable the actual behaviour"""
+        self.called = True
+
+
+def test_pycollector_makeitem_is_deprecated():
+
+    collector = PyCollectorMock()
+    with pytest.deprecated_call():
+        collector.makeitem('foo', 'bar')
+    assert collector.called
diff --git a/tools/third_party/pytest/testing/test_argcomplete.py b/tools/third_party/pytest/testing/test_argcomplete.py
new file mode 100644
index 0000000..c926125
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_argcomplete.py
@@ -0,0 +1,97 @@
+from __future__ import absolute_import, division, print_function
+import py
+import pytest
+
+# test for _argcomplete but not specific for any application
+
+
+def equal_with_bash(prefix, ffc, fc, out=None):
+    res = ffc(prefix)
+    res_bash = set(fc(prefix))
+    retval = set(res) == res_bash
+    if out:
+        out.write('equal_with_bash %s %s\n' % (retval, res))
+        if not retval:
+            out.write(' python - bash: %s\n' % (set(res) - res_bash))
+            out.write(' bash - python: %s\n' % (res_bash - set(res)))
+    return retval
+
+# copied from argcomplete.completers as import from there
+# also pulls in argcomplete.__init__ which opens filedescriptor 9
+# this gives an IOError at the end of testrun
+
+
+def _wrapcall(*args, **kargs):
+    try:
+        if py.std.sys.version_info > (2, 7):
+            return py.std.subprocess.check_output(*args, **kargs).decode().splitlines()
+        if 'stdout' in kargs:
+            raise ValueError('stdout argument not allowed, it will be overridden.')
+        process = py.std.subprocess.Popen(
+            stdout=py.std.subprocess.PIPE, *args, **kargs)
+        output, unused_err = process.communicate()
+        retcode = process.poll()
+        if retcode:
+            cmd = kargs.get("args")
+            if cmd is None:
+                cmd = args[0]
+            raise py.std.subprocess.CalledProcessError(retcode, cmd)
+        return output.decode().splitlines()
+    except py.std.subprocess.CalledProcessError:
+        return []
+
+
+class FilesCompleter(object):
+    'File completer class, optionally takes a list of allowed extensions'
+
+    def __init__(self, allowednames=(), directories=True):
+        # Fix if someone passes in a string instead of a list
+        if type(allowednames) is str:
+            allowednames = [allowednames]
+
+        self.allowednames = [x.lstrip('*').lstrip('.') for x in allowednames]
+        self.directories = directories
+
+    def __call__(self, prefix, **kwargs):
+        completion = []
+        if self.allowednames:
+            if self.directories:
+                files = _wrapcall(['bash', '-c',
+                                   "compgen -A directory -- '{p}'".format(p=prefix)])
+                completion += [f + '/' for f in files]
+            for x in self.allowednames:
+                completion += _wrapcall(['bash', '-c',
+                                         "compgen -A file -X '!*.{0}' -- '{p}'".format(x, p=prefix)])
+        else:
+            completion += _wrapcall(['bash', '-c',
+                                     "compgen -A file -- '{p}'".format(p=prefix)])
+
+            anticomp = _wrapcall(['bash', '-c',
+                                  "compgen -A directory -- '{p}'".format(p=prefix)])
+
+            completion = list(set(completion) - set(anticomp))
+
+            if self.directories:
+                completion += [f + '/' for f in anticomp]
+        return completion
+
+
+class TestArgComplete(object):
+    @pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
+    def test_compare_with_compgen(self):
+        from _pytest._argcomplete import FastFilesCompleter
+        ffc = FastFilesCompleter()
+        fc = FilesCompleter()
+        for x in ['/', '/d', '/data', 'qqq', '']:
+            assert equal_with_bash(x, ffc, fc, out=py.std.sys.stdout)
+
+    @pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
+    def test_remove_dir_prefix(self):
+        """this is not compatible with compgen but it is with bash itself:
+        ls /usr/<TAB>
+        """
+        from _pytest._argcomplete import FastFilesCompleter
+        ffc = FastFilesCompleter()
+        fc = FilesCompleter()
+        for x in '/usr/'.split():
+            assert not equal_with_bash(x, ffc, fc, out=py.std.sys.stdout)
diff --git a/tools/third_party/pytest/testing/test_assertion.py b/tools/third_party/pytest/testing/test_assertion.py
new file mode 100644
index 0000000..328fe7f
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_assertion.py
@@ -0,0 +1,1066 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+import sys
+import textwrap
+
+import _pytest.assertion as plugin
+import py
+import pytest
+from _pytest.assertion import util
+from _pytest.assertion import truncate
+
+PY3 = sys.version_info >= (3, 0)
+
+
+@pytest.fixture
+def mock_config():
+
+    class Config(object):
+        verbose = False
+
+        def getoption(self, name):
+            if name == 'verbose':
+                return self.verbose
+            raise KeyError('Not mocked out: %s' % name)
+
+    return Config()
+
+
+class TestImportHookInstallation(object):
+
+    @pytest.mark.parametrize('initial_conftest', [True, False])
+    @pytest.mark.parametrize('mode', ['plain', 'rewrite'])
+    def test_conftest_assertion_rewrite(self, testdir, initial_conftest, mode):
+        """Test that conftest files are using assertion rewrite on import.
+        (#1619)
+        """
+        testdir.tmpdir.join('foo/tests').ensure(dir=1)
+        conftest_path = 'conftest.py' if initial_conftest else 'foo/conftest.py'
+        contents = {
+            conftest_path: """
+                import pytest
+                @pytest.fixture
+                def check_first():
+                    def check(values, value):
+                        assert values.pop(0) == value
+                    return check
+            """,
+            'foo/tests/test_foo.py': """
+                def test(check_first):
+                    check_first([10, 30], 30)
+            """
+        }
+        testdir.makepyfile(**contents)
+        result = testdir.runpytest_subprocess('--assert=%s' % mode)
+        if mode == 'plain':
+            expected = 'E       AssertionError'
+        elif mode == 'rewrite':
+            expected = '*assert 10 == 30*'
+        else:
+            assert 0
+        result.stdout.fnmatch_lines([expected])
+
+    def test_rewrite_assertions_pytester_plugin(self, testdir):
+        """
+        Assertions in the pytester plugin must also benefit from assertion
+        rewriting (#1920).
+        """
+        testdir.makepyfile("""
+            pytest_plugins = ['pytester']
+            def test_dummy_failure(testdir):  # how meta!
+                testdir.makepyfile('def test(): assert 0')
+                r = testdir.inline_run()
+                r.assertoutcome(passed=1)
+        """)
+        result = testdir.runpytest_subprocess()
+        result.stdout.fnmatch_lines([
+            '*assert 1 == 0*',
+        ])
+
+    @pytest.mark.parametrize('mode', ['plain', 'rewrite'])
+    def test_pytest_plugins_rewrite(self, testdir, mode):
+        contents = {
+            'conftest.py': """
+                pytest_plugins = ['ham']
+            """,
+            'ham.py': """
+                import pytest
+                @pytest.fixture
+                def check_first():
+                    def check(values, value):
+                        assert values.pop(0) == value
+                    return check
+            """,
+            'test_foo.py': """
+                def test_foo(check_first):
+                    check_first([10, 30], 30)
+            """,
+        }
+        testdir.makepyfile(**contents)
+        result = testdir.runpytest_subprocess('--assert=%s' % mode)
+        if mode == 'plain':
+            expected = 'E       AssertionError'
+        elif mode == 'rewrite':
+            expected = '*assert 10 == 30*'
+        else:
+            assert 0
+        result.stdout.fnmatch_lines([expected])
+
+    @pytest.mark.parametrize('mode', ['str', 'list'])
+    def test_pytest_plugins_rewrite_module_names(self, testdir, mode):
+        """Test that pluginmanager correct marks pytest_plugins variables
+        for assertion rewriting if they are defined as plain strings or
+        list of strings (#1888).
+        """
+        plugins = '"ham"' if mode == 'str' else '["ham"]'
+        contents = {
+            'conftest.py': """
+                pytest_plugins = {plugins}
+            """.format(plugins=plugins),
+            'ham.py': """
+                import pytest
+            """,
+            'test_foo.py': """
+                def test_foo(pytestconfig):
+                    assert 'ham' in pytestconfig.pluginmanager.rewrite_hook._must_rewrite
+            """,
+        }
+        testdir.makepyfile(**contents)
+        result = testdir.runpytest_subprocess('--assert=rewrite')
+        assert result.ret == 0
+
+    def test_pytest_plugins_rewrite_module_names_correctly(self, testdir):
+        """Test that we match files correctly when they are marked for rewriting (#2939)."""
+        contents = {
+            'conftest.py': """
+                pytest_plugins = "ham"
+            """,
+            'ham.py': "",
+            'hamster.py': "",
+            'test_foo.py': """
+                def test_foo(pytestconfig):
+                    assert pytestconfig.pluginmanager.rewrite_hook.find_module('ham') is not None
+                    assert pytestconfig.pluginmanager.rewrite_hook.find_module('hamster') is None
+            """,
+        }
+        testdir.makepyfile(**contents)
+        result = testdir.runpytest_subprocess('--assert=rewrite')
+        assert result.ret == 0
+
+    @pytest.mark.parametrize('mode', ['plain', 'rewrite'])
+    @pytest.mark.parametrize('plugin_state', ['development', 'installed'])
+    def test_installed_plugin_rewrite(self, testdir, mode, plugin_state):
+        # Make sure the hook is installed early enough so that plugins
+        # installed via setuptools are rewritten.
+        testdir.tmpdir.join('hampkg').ensure(dir=1)
+        contents = {
+            'hampkg/__init__.py': """
+                import pytest
+
+                @pytest.fixture
+                def check_first2():
+                    def check(values, value):
+                        assert values.pop(0) == value
+                    return check
+            """,
+            'spamplugin.py': """
+            import pytest
+            from hampkg import check_first2
+
+            @pytest.fixture
+            def check_first():
+                def check(values, value):
+                    assert values.pop(0) == value
+                return check
+            """,
+            'mainwrapper.py': """
+            import pytest, pkg_resources
+
+            plugin_state = "{plugin_state}"
+
+            class DummyDistInfo(object):
+                project_name = 'spam'
+                version = '1.0'
+
+                def _get_metadata(self, name):
+                    # 'RECORD' meta-data only available in installed plugins
+                    if name == 'RECORD' and plugin_state == "installed":
+                        return ['spamplugin.py,sha256=abc,123',
+                                'hampkg/__init__.py,sha256=abc,123']
+                    # 'SOURCES.txt' meta-data only available for plugins in development mode
+                    elif name == 'SOURCES.txt' and plugin_state == "development":
+                        return ['spamplugin.py',
+                                'hampkg/__init__.py']
+                    return []
+
+            class DummyEntryPoint(object):
+                name = 'spam'
+                module_name = 'spam.py'
+                attrs = ()
+                extras = None
+                dist = DummyDistInfo()
+
+                def load(self, require=True, *args, **kwargs):
+                    import spamplugin
+                    return spamplugin
+
+            def iter_entry_points(name):
+                yield DummyEntryPoint()
+
+            pkg_resources.iter_entry_points = iter_entry_points
+            pytest.main()
+            """.format(plugin_state=plugin_state),
+            'test_foo.py': """
+            def test(check_first):
+                check_first([10, 30], 30)
+
+            def test2(check_first2):
+                check_first([10, 30], 30)
+            """,
+        }
+        testdir.makepyfile(**contents)
+        result = testdir.run(sys.executable, 'mainwrapper.py', '-s', '--assert=%s' % mode)
+        if mode == 'plain':
+            expected = 'E       AssertionError'
+        elif mode == 'rewrite':
+            expected = '*assert 10 == 30*'
+        else:
+            assert 0
+        result.stdout.fnmatch_lines([expected])
+
+    def test_rewrite_ast(self, testdir):
+        testdir.tmpdir.join('pkg').ensure(dir=1)
+        contents = {
+            'pkg/__init__.py': """
+                import pytest
+                pytest.register_assert_rewrite('pkg.helper')
+            """,
+            'pkg/helper.py': """
+                def tool():
+                    a, b = 2, 3
+                    assert a == b
+            """,
+            'pkg/plugin.py': """
+                import pytest, pkg.helper
+                @pytest.fixture
+                def tool():
+                    return pkg.helper.tool
+            """,
+            'pkg/other.py': """
+                values = [3, 2]
+                def tool():
+                    assert values.pop() == 3
+            """,
+            'conftest.py': """
+                pytest_plugins = ['pkg.plugin']
+            """,
+            'test_pkg.py': """
+                import pkg.other
+                def test_tool(tool):
+                    tool()
+                def test_other():
+                    pkg.other.tool()
+            """,
+        }
+        testdir.makepyfile(**contents)
+        result = testdir.runpytest_subprocess('--assert=rewrite')
+        result.stdout.fnmatch_lines(['>*assert a == b*',
+                                     'E*assert 2 == 3*',
+                                     '>*assert values.pop() == 3*',
+                                     'E*AssertionError'])
+
+    def test_register_assert_rewrite_checks_types(self):
+        with pytest.raises(TypeError):
+            pytest.register_assert_rewrite(['pytest_tests_internal_non_existing'])
+        pytest.register_assert_rewrite('pytest_tests_internal_non_existing',
+                                       'pytest_tests_internal_non_existing2')
+
+
+class TestBinReprIntegration(object):
+
+    def test_pytest_assertrepr_compare_called(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            values = []
+            def pytest_assertrepr_compare(op, left, right):
+                values.append((op, left, right))
+
+            @pytest.fixture
+            def list(request):
+                return values
+        """)
+        testdir.makepyfile("""
+            def test_hello():
+                assert 0 == 1
+            def test_check(list):
+                assert list == [("==", 0, 1)]
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*test_hello*FAIL*",
+            "*test_check*PASS*",
+        ])
+
+
+def callequal(left, right, verbose=False):
+    config = mock_config()
+    config.verbose = verbose
+    return plugin.pytest_assertrepr_compare(config, '==', left, right)
+
+
+class TestAssert_reprcompare(object):
+    def test_different_types(self):
+        assert callequal([0, 1], 'foo') is None
+
+    def test_summary(self):
+        summary = callequal([0, 1], [0, 2])[0]
+        assert len(summary) < 65
+
+    def test_text_diff(self):
+        diff = callequal('spam', 'eggs')[1:]
+        assert '- spam' in diff
+        assert '+ eggs' in diff
+
+    def test_text_skipping(self):
+        lines = callequal('a' * 50 + 'spam', 'a' * 50 + 'eggs')
+        assert 'Skipping' in lines[1]
+        for line in lines:
+            assert 'a' * 50 not in line
+
+    def test_text_skipping_verbose(self):
+        lines = callequal('a' * 50 + 'spam', 'a' * 50 + 'eggs', verbose=True)
+        assert '- ' + 'a' * 50 + 'spam' in lines
+        assert '+ ' + 'a' * 50 + 'eggs' in lines
+
+    def test_multiline_text_diff(self):
+        left = 'foo\nspam\nbar'
+        right = 'foo\neggs\nbar'
+        diff = callequal(left, right)
+        assert '- spam' in diff
+        assert '+ eggs' in diff
+
+    def test_list(self):
+        expl = callequal([0, 1], [0, 2])
+        assert len(expl) > 1
+
+    @pytest.mark.parametrize(
+        ['left', 'right', 'expected'], [
+            ([0, 1], [0, 2], """
+                Full diff:
+                - [0, 1]
+                ?     ^
+                + [0, 2]
+                ?     ^
+            """),
+            ({0: 1}, {0: 2}, """
+                Full diff:
+                - {0: 1}
+                ?     ^
+                + {0: 2}
+                ?     ^
+            """),
+            (set([0, 1]), set([0, 2]), """
+                Full diff:
+                - set([0, 1])
+                ?         ^
+                + set([0, 2])
+                ?         ^
+            """ if not PY3 else """
+                Full diff:
+                - {0, 1}
+                ?     ^
+                + {0, 2}
+                ?     ^
+            """)
+        ]
+    )
+    def test_iterable_full_diff(self, left, right, expected):
+        """Test the full diff assertion failure explanation.
+
+        When verbose is False, then just a -v notice to get the diff is rendered,
+        when verbose is True, then ndiff of the pprint is returned.
+        """
+        expl = callequal(left, right, verbose=False)
+        assert expl[-1] == 'Use -v to get the full diff'
+        expl = '\n'.join(callequal(left, right, verbose=True))
+        assert expl.endswith(textwrap.dedent(expected).strip())
+
+    def test_list_different_lengths(self):
+        expl = callequal([0, 1], [0, 1, 2])
+        assert len(expl) > 1
+        expl = callequal([0, 1, 2], [0, 1])
+        assert len(expl) > 1
+
+    def test_dict(self):
+        expl = callequal({'a': 0}, {'a': 1})
+        assert len(expl) > 1
+
+    def test_dict_omitting(self):
+        lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1})
+        assert lines[1].startswith('Omitting 1 identical item')
+        assert 'Common items' not in lines
+        for line in lines[1:]:
+            assert 'b' not in line
+
+    def test_dict_omitting_with_verbosity_1(self):
+        """ Ensure differing items are visible for verbosity=1 (#1512) """
+        lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1}, verbose=1)
+        assert lines[1].startswith('Omitting 1 identical item')
+        assert lines[2].startswith('Differing items')
+        assert lines[3] == "{'a': 0} != {'a': 1}"
+        assert 'Common items' not in lines
+
+    def test_dict_omitting_with_verbosity_2(self):
+        lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1}, verbose=2)
+        assert lines[1].startswith('Common items:')
+        assert 'Omitting' not in lines[1]
+        assert lines[2] == "{'b': 1}"
+
+    def test_set(self):
+        expl = callequal(set([0, 1]), set([0, 2]))
+        assert len(expl) > 1
+
+    def test_frozenzet(self):
+        expl = callequal(frozenset([0, 1]), set([0, 2]))
+        assert len(expl) > 1
+
+    def test_Sequence(self):
+        col = py.builtin._tryimport(
+            "collections.abc",
+            "collections",
+            "sys")
+        if not hasattr(col, "MutableSequence"):
+            pytest.skip("cannot import MutableSequence")
+        MutableSequence = col.MutableSequence
+
+        class TestSequence(MutableSequence):  # works with a Sequence subclass
+            def __init__(self, iterable):
+                self.elements = list(iterable)
+
+            def __getitem__(self, item):
+                return self.elements[item]
+
+            def __len__(self):
+                return len(self.elements)
+
+            def __setitem__(self, item, value):
+                pass
+
+            def __delitem__(self, item):
+                pass
+
+            def insert(self, item, index):
+                pass
+
+        expl = callequal(TestSequence([0, 1]), list([0, 2]))
+        assert len(expl) > 1
+
+    def test_list_tuples(self):
+        expl = callequal([], [(1, 2)])
+        assert len(expl) > 1
+        expl = callequal([(1, 2)], [])
+        assert len(expl) > 1
+
+    def test_list_bad_repr(self):
+        class A(object):
+            def __repr__(self):
+                raise ValueError(42)
+        expl = callequal([], [A()])
+        assert 'ValueError' in "".join(expl)
+        expl = callequal({}, {'1': A()})
+        assert 'faulty' in "".join(expl)
+
+    def test_one_repr_empty(self):
+        """
+        the faulty empty string repr did trigger
+        a unbound local error in _diff_text
+        """
+        class A(str):
+            def __repr__(self):
+                return ''
+        expl = callequal(A(), '')
+        assert not expl
+
+    def test_repr_no_exc(self):
+        expl = ' '.join(callequal('foo', 'bar'))
+        assert 'raised in repr()' not in expl
+
+    def test_unicode(self):
+        left = py.builtin._totext('£€', 'utf-8')
+        right = py.builtin._totext('£', 'utf-8')
+        expl = callequal(left, right)
+        assert expl[0] == py.builtin._totext("'£€' == '£'", 'utf-8')
+        assert expl[1] == py.builtin._totext('- £€', 'utf-8')
+        assert expl[2] == py.builtin._totext('+ £', 'utf-8')
+
+    def test_nonascii_text(self):
+        """
+        :issue: 877
+        non ascii python2 str caused a UnicodeDecodeError
+        """
+        class A(str):
+            def __repr__(self):
+                return '\xff'
+        expl = callequal(A(), '1')
+        assert expl
+
+    def test_format_nonascii_explanation(self):
+        assert util.format_explanation('λ')
+
+    def test_mojibake(self):
+        # issue 429
+        left = 'e'
+        right = '\xc3\xa9'
+        if not isinstance(left, py.builtin.bytes):
+            left = py.builtin.bytes(left, 'utf-8')
+            right = py.builtin.bytes(right, 'utf-8')
+        expl = callequal(left, right)
+        for line in expl:
+            assert isinstance(line, py.builtin.text)
+        msg = py.builtin._totext('\n').join(expl)
+        assert msg
+
+
+class TestFormatExplanation(object):
+
+    def test_special_chars_full(self, testdir):
+        # Issue 453, for the bug this would raise IndexError
+        testdir.makepyfile("""
+            def test_foo():
+                assert '\\n}' == ''
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*AssertionError*",
+        ])
+
+    def test_fmt_simple(self):
+        expl = 'assert foo'
+        assert util.format_explanation(expl) == 'assert foo'
+
+    def test_fmt_where(self):
+        expl = '\n'.join(['assert 1',
+                          '{1 = foo',
+                          '} == 2'])
+        res = '\n'.join(['assert 1 == 2',
+                         ' +  where 1 = foo'])
+        assert util.format_explanation(expl) == res
+
+    def test_fmt_and(self):
+        expl = '\n'.join(['assert 1',
+                          '{1 = foo',
+                          '} == 2',
+                          '{2 = bar',
+                          '}'])
+        res = '\n'.join(['assert 1 == 2',
+                         ' +  where 1 = foo',
+                         ' +  and   2 = bar'])
+        assert util.format_explanation(expl) == res
+
+    def test_fmt_where_nested(self):
+        expl = '\n'.join(['assert 1',
+                          '{1 = foo',
+                          '{foo = bar',
+                          '}',
+                          '} == 2'])
+        res = '\n'.join(['assert 1 == 2',
+                         ' +  where 1 = foo',
+                         ' +    where foo = bar'])
+        assert util.format_explanation(expl) == res
+
+    def test_fmt_newline(self):
+        expl = '\n'.join(['assert "foo" == "bar"',
+                          '~- foo',
+                          '~+ bar'])
+        res = '\n'.join(['assert "foo" == "bar"',
+                         '  - foo',
+                         '  + bar'])
+        assert util.format_explanation(expl) == res
+
+    def test_fmt_newline_escaped(self):
+        expl = '\n'.join(['assert foo == bar',
+                          'baz'])
+        res = 'assert foo == bar\\nbaz'
+        assert util.format_explanation(expl) == res
+
+    def test_fmt_newline_before_where(self):
+        expl = '\n'.join(['the assertion message here',
+                          '>assert 1',
+                          '{1 = foo',
+                          '} == 2',
+                          '{2 = bar',
+                          '}'])
+        res = '\n'.join(['the assertion message here',
+                         'assert 1 == 2',
+                         ' +  where 1 = foo',
+                         ' +  and   2 = bar'])
+        assert util.format_explanation(expl) == res
+
+    def test_fmt_multi_newline_before_where(self):
+        expl = '\n'.join(['the assertion',
+                          '~message here',
+                          '>assert 1',
+                          '{1 = foo',
+                          '} == 2',
+                          '{2 = bar',
+                          '}'])
+        res = '\n'.join(['the assertion',
+                         '  message here',
+                         'assert 1 == 2',
+                         ' +  where 1 = foo',
+                         ' +  and   2 = bar'])
+        assert util.format_explanation(expl) == res
+
+
+class TestTruncateExplanation(object):
+
+    """ Confirm assertion output is truncated as expected """
+
+    # The number of lines in the truncation explanation message. Used
+    # to calculate that results have the expected length.
+    LINES_IN_TRUNCATION_MSG = 2
+
+    def test_doesnt_truncate_when_input_is_empty_list(self):
+        expl = []
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100)
+        assert result == expl
+
+    def test_doesnt_truncate_at_when_input_is_5_lines_and_LT_max_chars(self):
+        expl = ['a' * 100 for x in range(5)]
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
+        assert result == expl
+
+    def test_truncates_at_8_lines_when_given_list_of_empty_strings(self):
+        expl = ['' for x in range(50)]
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100)
+        assert result != expl
+        assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
+        assert "Full output truncated" in result[-1]
+        assert "43 lines hidden" in result[-1]
+        last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
+        assert last_line_before_trunc_msg.endswith("...")
+
+    def test_truncates_at_8_lines_when_first_8_lines_are_LT_max_chars(self):
+        expl = ['a' for x in range(100)]
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
+        assert result != expl
+        assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
+        assert "Full output truncated" in result[-1]
+        assert "93 lines hidden" in result[-1]
+        last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
+        assert last_line_before_trunc_msg.endswith("...")
+
+    def test_truncates_at_8_lines_when_first_8_lines_are_EQ_max_chars(self):
+        expl = ['a' * 80 for x in range(16)]
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
+        assert result != expl
+        assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
+        assert "Full output truncated" in result[-1]
+        assert "9 lines hidden" in result[-1]
+        last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
+        assert last_line_before_trunc_msg.endswith("...")
+
+    def test_truncates_at_4_lines_when_first_4_lines_are_GT_max_chars(self):
+        expl = ['a' * 250 for x in range(10)]
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=999)
+        assert result != expl
+        assert len(result) == 4 + self.LINES_IN_TRUNCATION_MSG
+        assert "Full output truncated" in result[-1]
+        assert "7 lines hidden" in result[-1]
+        last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
+        assert last_line_before_trunc_msg.endswith("...")
+
+    def test_truncates_at_1_line_when_first_line_is_GT_max_chars(self):
+        expl = ['a' * 250 for x in range(1000)]
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100)
+        assert result != expl
+        assert len(result) == 1 + self.LINES_IN_TRUNCATION_MSG
+        assert "Full output truncated" in result[-1]
+        assert "1000 lines hidden" in result[-1]
+        last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
+        assert last_line_before_trunc_msg.endswith("...")
+
+    def test_full_output_truncated(self, monkeypatch, testdir):
+        """ Test against full runpytest() output. """
+
+        line_count = 7
+        line_len = 100
+        expected_truncated_lines = 2
+        testdir.makepyfile(r"""
+            def test_many_lines():
+                a = list([str(i)[0] * %d for i in range(%d)])
+                b = a[::2]
+                a = '\n'.join(map(str, a))
+                b = '\n'.join(map(str, b))
+                assert a == b
+        """ % (line_len, line_count))
+        monkeypatch.delenv('CI', raising=False)
+
+        result = testdir.runpytest()
+        # without -vv, truncate the message showing a few diff lines only
+        result.stdout.fnmatch_lines([
+            "*- 1*",
+            "*- 3*",
+            "*- 5*",
+            "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines,
+        ])
+
+        result = testdir.runpytest('-vv')
+        result.stdout.fnmatch_lines([
+            "* 6*",
+        ])
+
+        monkeypatch.setenv('CI', '1')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "* 6*",
+        ])
+
+
+def test_python25_compile_issue257(testdir):
+    testdir.makepyfile("""
+        def test_rewritten():
+            assert 1 == 2
+        # some comment
+    """)
+    result = testdir.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines("""
+            *E*assert 1 == 2*
+            *1 failed*
+    """)
+
+
+def test_rewritten(testdir):
+    testdir.makepyfile("""
+        def test_rewritten():
+            assert "@py_builtins" in globals()
+    """)
+    assert testdir.runpytest().ret == 0
+
+
+def test_reprcompare_notin(mock_config):
+    detail = plugin.pytest_assertrepr_compare(
+        mock_config, 'not in', 'foo', 'aaafoobbb')[1:]
+    assert detail == ["'foo' is contained here:", '  aaafoobbb', '?    +++']
+
+
+def test_pytest_assertrepr_compare_integration(testdir):
+    testdir.makepyfile("""
+        def test_hello():
+            x = set(range(100))
+            y = x.copy()
+            y.remove(50)
+            assert x == y
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*def test_hello():*",
+        "*assert x == y*",
+        "*E*Extra items*left*",
+        "*E*50*",
+    ])
+
+
+def test_sequence_comparison_uses_repr(testdir):
+    testdir.makepyfile("""
+        def test_hello():
+            x = set("hello x")
+            y = set("hello y")
+            assert x == y
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*def test_hello():*",
+        "*assert x == y*",
+        "*E*Extra items*left*",
+        "*E*'x'*",
+        "*E*Extra items*right*",
+        "*E*'y'*",
+    ])
+
+
+def test_assertrepr_loaded_per_dir(testdir):
+    testdir.makepyfile(test_base=['def test_base(): assert 1 == 2'])
+    a = testdir.mkdir('a')
+    a_test = a.join('test_a.py')
+    a_test.write('def test_a(): assert 1 == 2')
+    a_conftest = a.join('conftest.py')
+    a_conftest.write('def pytest_assertrepr_compare(): return ["summary a"]')
+    b = testdir.mkdir('b')
+    b_test = b.join('test_b.py')
+    b_test.write('def test_b(): assert 1 == 2')
+    b_conftest = b.join('conftest.py')
+    b_conftest.write('def pytest_assertrepr_compare(): return ["summary b"]')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*def test_base():*',
+        '*E*assert 1 == 2*',
+        '*def test_a():*',
+        '*E*assert summary a*',
+        '*def test_b():*',
+        '*E*assert summary b*'])
+
+
+def test_assertion_options(testdir):
+    testdir.makepyfile("""
+        def test_hello():
+            x = 3
+            assert x == 4
+    """)
+    result = testdir.runpytest()
+    assert "3 == 4" in result.stdout.str()
+    result = testdir.runpytest_subprocess("--assert=plain")
+    assert "3 == 4" not in result.stdout.str()
+
+
+def test_triple_quoted_string_issue113(testdir):
+    testdir.makepyfile("""
+        def test_hello():
+            assert "" == '''
+    '''""")
+    result = testdir.runpytest("--fulltrace")
+    result.stdout.fnmatch_lines([
+        "*1 failed*",
+    ])
+    assert 'SyntaxError' not in result.stdout.str()
+
+
+def test_traceback_failure(testdir):
+    p1 = testdir.makepyfile("""
+        def g():
+            return 2
+        def f(x):
+            assert x == g()
+        def test_onefails():
+            f(3)
+    """)
+    result = testdir.runpytest(p1, "--tb=long")
+    result.stdout.fnmatch_lines([
+        "*test_traceback_failure.py F*",
+        "====* FAILURES *====",
+        "____*____",
+        "",
+        "    def test_onefails():",
+        ">       f(3)",
+        "",
+        "*test_*.py:6: ",
+        "_ _ _ *",
+        # "",
+        "    def f(x):",
+        ">       assert x == g()",
+        "E       assert 3 == 2",
+        "E        +  where 2 = g()",
+        "",
+        "*test_traceback_failure.py:4: AssertionError"
+    ])
+
+    result = testdir.runpytest(p1)  # "auto"
+    result.stdout.fnmatch_lines([
+        "*test_traceback_failure.py F*",
+        "====* FAILURES *====",
+        "____*____",
+        "",
+        "    def test_onefails():",
+        ">       f(3)",
+        "",
+        "*test_*.py:6: ",
+        "",
+        "    def f(x):",
+        ">       assert x == g()",
+        "E       assert 3 == 2",
+        "E        +  where 2 = g()",
+        "",
+        "*test_traceback_failure.py:4: AssertionError"
+    ])
+
+
+@pytest.mark.skipif(sys.version_info[:2] <= (3, 3), reason='Python 3.4+ shows chained exceptions on multiprocess')
+def test_exception_handling_no_traceback(testdir):
+    """
+    Handle chain exceptions in tasks submitted by the multiprocess module (#1984).
+    """
+    p1 = testdir.makepyfile("""
+        from multiprocessing import Pool
+
+        def process_task(n):
+            assert n == 10
+
+        def multitask_job():
+            tasks = [1]
+            with Pool(processes=1) as pool:
+                pool.map(process_task, tasks)
+
+        def test_multitask_job():
+            multitask_job()
+    """)
+    result = testdir.runpytest(p1, "--tb=long")
+    result.stdout.fnmatch_lines([
+        "====* FAILURES *====",
+        "*multiprocessing.pool.RemoteTraceback:*",
+        "Traceback (most recent call last):",
+        "*assert n == 10",
+        "The above exception was the direct cause of the following exception:",
+        "> * multitask_job()",
+    ])
+
+
+@pytest.mark.skipif("'__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')")
+def test_warn_missing(testdir):
+    testdir.makepyfile("")
+    result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h")
+    result.stderr.fnmatch_lines([
+        "*WARNING*assert statements are not executed*",
+    ])
+    result = testdir.run(sys.executable, "-OO", "-m", "pytest")
+    result.stderr.fnmatch_lines([
+        "*WARNING*assert statements are not executed*",
+    ])
+
+
+def test_recursion_source_decode(testdir):
+    testdir.makepyfile("""
+        def test_something():
+            pass
+    """)
+    testdir.makeini("""
+        [pytest]
+        python_files = *.py
+    """)
+    result = testdir.runpytest("--collect-only")
+    result.stdout.fnmatch_lines("""
+        <Module*>
+    """)
+
+
+def test_AssertionError_message(testdir):
+    testdir.makepyfile("""
+        def test_hello():
+            x,y = 1,2
+            assert 0, (x,y)
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines("""
+        *def test_hello*
+        *assert 0, (x,y)*
+        *AssertionError: (1, 2)*
+    """)
+
+
+@pytest.mark.skipif(PY3, reason='This bug does not exist on PY3')
+def test_set_with_unsortable_elements():
+    # issue #718
+    class UnsortableKey(object):
+        def __init__(self, name):
+            self.name = name
+
+        def __lt__(self, other):
+            raise RuntimeError()
+
+        def __repr__(self):
+            return 'repr({0})'.format(self.name)
+
+        def __eq__(self, other):
+            return self.name == other.name
+
+        def __hash__(self):
+            return hash(self.name)
+
+    left_set = set(UnsortableKey(str(i)) for i in range(1, 3))
+    right_set = set(UnsortableKey(str(i)) for i in range(2, 4))
+    expl = callequal(left_set, right_set, verbose=True)
+    # skip first line because it contains the "construction" of the set, which does not have a guaranteed order
+    expl = expl[1:]
+    dedent = textwrap.dedent("""
+        Extra items in the left set:
+        repr(1)
+        Extra items in the right set:
+        repr(3)
+        Full diff (fallback to calling repr on each item):
+        - repr(1)
+        repr(2)
+        + repr(3)
+    """).strip()
+    assert '\n'.join(expl) == dedent
+
+
+def test_diff_newline_at_end(monkeypatch, testdir):
+    testdir.makepyfile(r"""
+        def test_diff():
+            assert 'asdf' == 'asdf\n'
+    """)
+
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(r"""
+        *assert 'asdf' == 'asdf\n'
+        *  - asdf
+        *  + asdf
+        *  ?     +
+    """)
+
+
+def test_assert_tuple_warning(testdir):
+    testdir.makepyfile("""
+        def test_tuple():
+            assert(False, 'you shall not pass')
+    """)
+    result = testdir.runpytest('-rw')
+    result.stdout.fnmatch_lines([
+        '*test_assert_tuple_warning.py:2',
+        '*assertion is always true*',
+    ])
+
+
+def test_assert_indirect_tuple_no_warning(testdir):
+    testdir.makepyfile("""
+        def test_tuple():
+            tpl = ('foo', 'bar')
+            assert tpl
+    """)
+    result = testdir.runpytest('-rw')
+    output = '\n'.join(result.stdout.lines)
+    assert 'WR1' not in output
+
+
+def test_assert_with_unicode(monkeypatch, testdir):
+    testdir.makepyfile(u"""
+        # -*- coding: utf-8 -*-
+        def test_unicode():
+            assert u'유니코드' == u'Unicode'
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(['*AssertionError*'])
+
+
+def test_raise_unprintable_assertion_error(testdir):
+    testdir.makepyfile(r"""
+        def test_raise_assertion_error():
+            raise AssertionError('\xff')
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([r">       raise AssertionError('\xff')", 'E       AssertionError: *'])
+
+
+def test_raise_assertion_error_raisin_repr(testdir):
+    testdir.makepyfile(u"""
+        class RaisingRepr(object):
+            def __repr__(self):
+                raise Exception()
+        def test_raising_repr():
+            raise AssertionError(RaisingRepr())
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(['E       AssertionError: <unprintable AssertionError object>'])
+
+
+def test_issue_1944(testdir):
+    testdir.makepyfile("""
+        def f():
+            return
+
+        assert f() == 10
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(["*1 error*"])
+    assert "AttributeError: 'Module' object has no attribute '_obj'" not in result.stdout.str()
diff --git a/tools/third_party/pytest/testing/test_assertrewrite.py b/tools/third_party/pytest/testing/test_assertrewrite.py
new file mode 100644
index 0000000..0e22c6d
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_assertrewrite.py
@@ -0,0 +1,996 @@
+from __future__ import absolute_import, division, print_function
+
+import glob
+import os
+import py_compile
+import stat
+import sys
+import zipfile
+import py
+import pytest
+
+import _pytest._code
+from _pytest.assertion import util
+from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG, AssertionRewritingHook
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+
+ast = pytest.importorskip("ast")
+if sys.platform.startswith("java"):
+    # XXX should be xfail
+    pytest.skip("assert rewrite does currently not work on jython")
+
+
+def setup_module(mod):
+    mod._old_reprcompare = util._reprcompare
+    _pytest._code._reprcompare = None
+
+
+def teardown_module(mod):
+    util._reprcompare = mod._old_reprcompare
+    del mod._old_reprcompare
+
+
+def rewrite(src):
+    tree = ast.parse(src)
+    rewrite_asserts(tree)
+    return tree
+
+
+def getmsg(f, extra_ns=None, must_pass=False):
+    """Rewrite the assertions in f, run it, and get the failure message."""
+    src = '\n'.join(_pytest._code.Code(f).source().lines)
+    mod = rewrite(src)
+    code = compile(mod, "<test>", "exec")
+    ns = {}
+    if extra_ns is not None:
+        ns.update(extra_ns)
+    py.builtin.exec_(code, ns)
+    func = ns[f.__name__]
+    try:
+        func()
+    except AssertionError:
+        if must_pass:
+            pytest.fail("shouldn't have raised")
+        s = str(sys.exc_info()[1])
+        if not s.startswith("assert"):
+            return "AssertionError: " + s
+        return s
+    else:
+        if not must_pass:
+            pytest.fail("function didn't raise at all")
+
+
+class TestAssertionRewrite(object):
+
+    def test_place_initial_imports(self):
+        s = """'Doc string'\nother = stuff"""
+        m = rewrite(s)
+        # Module docstrings in 3.7 are part of Module node, it's not in the body
+        # so we remove it so the following body items have the same indexes on
+        # all Python versions
+        if sys.version_info < (3, 7):
+            assert isinstance(m.body[0], ast.Expr)
+            assert isinstance(m.body[0].value, ast.Str)
+            del m.body[0]
+        for imp in m.body[0:2]:
+            assert isinstance(imp, ast.Import)
+            assert imp.lineno == 2
+            assert imp.col_offset == 0
+        assert isinstance(m.body[2], ast.Assign)
+        s = """from __future__ import with_statement\nother_stuff"""
+        m = rewrite(s)
+        assert isinstance(m.body[0], ast.ImportFrom)
+        for imp in m.body[1:3]:
+            assert isinstance(imp, ast.Import)
+            assert imp.lineno == 2
+            assert imp.col_offset == 0
+        assert isinstance(m.body[3], ast.Expr)
+        s = """'doc string'\nfrom __future__ import with_statement"""
+        m = rewrite(s)
+        if sys.version_info < (3, 7):
+            assert isinstance(m.body[0], ast.Expr)
+            assert isinstance(m.body[0].value, ast.Str)
+            del m.body[0]
+        assert isinstance(m.body[0], ast.ImportFrom)
+        for imp in m.body[1:3]:
+            assert isinstance(imp, ast.Import)
+            assert imp.lineno == 2
+            assert imp.col_offset == 0
+        s = """'doc string'\nfrom __future__ import with_statement\nother"""
+        m = rewrite(s)
+        if sys.version_info < (3, 7):
+            assert isinstance(m.body[0], ast.Expr)
+            assert isinstance(m.body[0].value, ast.Str)
+            del m.body[0]
+        assert isinstance(m.body[0], ast.ImportFrom)
+        for imp in m.body[1:3]:
+            assert isinstance(imp, ast.Import)
+            assert imp.lineno == 3
+            assert imp.col_offset == 0
+        assert isinstance(m.body[3], ast.Expr)
+        s = """from . import relative\nother_stuff"""
+        m = rewrite(s)
+        for imp in m.body[0:2]:
+            assert isinstance(imp, ast.Import)
+            assert imp.lineno == 1
+            assert imp.col_offset == 0
+        assert isinstance(m.body[3], ast.Expr)
+
+    def test_dont_rewrite(self):
+        s = """'PYTEST_DONT_REWRITE'\nassert 14"""
+        m = rewrite(s)
+        if sys.version_info < (3, 7):
+            assert len(m.body) == 2
+            assert isinstance(m.body[0], ast.Expr)
+            assert isinstance(m.body[0].value, ast.Str)
+            del m.body[0]
+        else:
+            assert len(m.body) == 1
+        assert m.body[0].msg is None
+
+    def test_name(self):
+        def f():
+            assert False
+        assert getmsg(f) == "assert False"
+
+        def f():
+            f = False
+            assert f
+
+        assert getmsg(f) == "assert False"
+
+        def f():
+            assert a_global  # noqa
+
+        assert getmsg(f, {"a_global": False}) == "assert False"
+
+        def f():
+            assert sys == 42
+
+        assert getmsg(f, {"sys": sys}) == "assert sys == 42"
+
+        def f():
+            assert cls == 42  # noqa
+
+        class X(object):
+            pass
+
+        assert getmsg(f, {"cls": X}) == "assert cls == 42"
+
+    def test_assert_already_has_message(self):
+        def f():
+            assert False, "something bad!"
+        assert getmsg(f) == "AssertionError: something bad!\nassert False"
+
+    def test_assertion_message(self, testdir):
+        testdir.makepyfile("""
+            def test_foo():
+                assert 1 == 2, "The failure message"
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*AssertionError*The failure message*",
+            "*assert 1 == 2*",
+        ])
+
+    def test_assertion_message_multiline(self, testdir):
+        testdir.makepyfile("""
+            def test_foo():
+                assert 1 == 2, "A multiline\\nfailure message"
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*AssertionError*A multiline*",
+            "*failure message*",
+            "*assert 1 == 2*",
+        ])
+
+    def test_assertion_message_tuple(self, testdir):
+        testdir.makepyfile("""
+            def test_foo():
+                assert 1 == 2, (1, 2)
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*AssertionError*%s*" % repr((1, 2)),
+            "*assert 1 == 2*",
+        ])
+
+    def test_assertion_message_expr(self, testdir):
+        testdir.makepyfile("""
+            def test_foo():
+                assert 1 == 2, 1 + 2
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*AssertionError*3*",
+            "*assert 1 == 2*",
+        ])
+
+    def test_assertion_message_escape(self, testdir):
+        testdir.makepyfile("""
+            def test_foo():
+                assert 1 == 2, 'To be escaped: %'
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*AssertionError: To be escaped: %",
+            "*assert 1 == 2",
+        ])
+
+    def test_boolop(self):
+        def f():
+            f = g = False
+            assert f and g
+
+        assert getmsg(f) == "assert (False)"
+
+        def f():
+            f = True
+            g = False
+            assert f and g
+
+        assert getmsg(f) == "assert (True and False)"
+
+        def f():
+            f = False
+            g = True
+            assert f and g
+
+        assert getmsg(f) == "assert (False)"
+
+        def f():
+            f = g = False
+            assert f or g
+
+        assert getmsg(f) == "assert (False or False)"
+
+        def f():
+            f = g = False
+            assert not f and not g
+
+        getmsg(f, must_pass=True)
+
+        def x():
+            return False
+
+        def f():
+            assert x() and x()
+
+        assert getmsg(f, {"x": x}) == """assert (False)
+ +  where False = x()"""
+
+        def f():
+            assert False or x()
+
+        assert getmsg(f, {"x": x}) == """assert (False or False)
+ +  where False = x()"""
+
+        def f():
+            assert 1 in {} and 2 in {}
+
+        assert getmsg(f) == "assert (1 in {})"
+
+        def f():
+            x = 1
+            y = 2
+            assert x in {1: None} and y in {}
+
+        assert getmsg(f) == "assert (1 in {1: None} and 2 in {})"
+
+        def f():
+            f = True
+            g = False
+            assert f or g
+
+        getmsg(f, must_pass=True)
+
+        def f():
+            f = g = h = lambda: True
+            assert f() and g() and h()
+
+        getmsg(f, must_pass=True)
+
+    def test_short_circuit_evaluation(self):
+        def f():
+            assert True or explode  # noqa
+
+        getmsg(f, must_pass=True)
+
+        def f():
+            x = 1
+            assert x == 1 or x == 2
+
+        getmsg(f, must_pass=True)
+
+    def test_unary_op(self):
+        def f():
+            x = True
+            assert not x
+
+        assert getmsg(f) == "assert not True"
+
+        def f():
+            x = 0
+            assert ~x + 1
+
+        assert getmsg(f) == "assert (~0 + 1)"
+
+        def f():
+            x = 3
+            assert -x + x
+
+        assert getmsg(f) == "assert (-3 + 3)"
+
+        def f():
+            x = 0
+            assert +x + x
+
+        assert getmsg(f) == "assert (+0 + 0)"
+
+    def test_binary_op(self):
+        def f():
+            x = 1
+            y = -1
+            assert x + y
+
+        assert getmsg(f) == "assert (1 + -1)"
+
+        def f():
+            assert not 5 % 4
+        assert getmsg(f) == "assert not (5 % 4)"
+
+    def test_boolop_percent(self):
+        def f():
+            assert 3 % 2 and False
+
+        assert getmsg(f) == "assert ((3 % 2) and False)"
+
+        def f():
+            assert False or 4 % 2
+        assert getmsg(f) == "assert (False or (4 % 2))"
+
+    @pytest.mark.skipif("sys.version_info < (3,5)")
+    def test_at_operator_issue1290(self, testdir):
+        testdir.makepyfile("""
+            class Matrix(object):
+                def __init__(self, num):
+                    self.num = num
+                def __matmul__(self, other):
+                    return self.num * other.num
+
+            def test_multmat_operator():
+                assert Matrix(2) @ Matrix(3) == 6""")
+        testdir.runpytest().assert_outcomes(passed=1)
+
+    def test_call(self):
+        def g(a=42, *args, **kwargs):
+            return False
+
+        ns = {"g": g}
+
+        def f():
+            assert g()
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = g()"""
+
+        def f():
+            assert g(1)
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = g(1)"""
+
+        def f():
+            assert g(1, 2)
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = g(1, 2)"""
+
+        def f():
+            assert g(1, g=42)
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = g(1, g=42)"""
+
+        def f():
+            assert g(1, 3, g=23)
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = g(1, 3, g=23)"""
+
+        def f():
+            seq = [1, 2, 3]
+            assert g(*seq)
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = g(*[1, 2, 3])"""
+
+        def f():
+            x = "a"
+            assert g(**{x: 2})
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = g(**{'a': 2})"""
+
+    def test_attribute(self):
+        class X(object):
+            g = 3
+
+        ns = {"x": X}
+
+        def f():
+            assert not x.g  # noqa
+
+        assert getmsg(f, ns) == """assert not 3
+ +  where 3 = x.g"""
+
+        def f():
+            x.a = False  # noqa
+            assert x.a   # noqa
+
+        assert getmsg(f, ns) == """assert False
+ +  where False = x.a"""
+
+    def test_comparisons(self):
+
+        def f():
+            a, b = range(2)
+            assert b < a
+
+        assert getmsg(f) == """assert 1 < 0"""
+
+        def f():
+            a, b, c = range(3)
+            assert a > b > c
+
+        assert getmsg(f) == """assert 0 > 1"""
+
+        def f():
+            a, b, c = range(3)
+            assert a < b > c
+
+        assert getmsg(f) == """assert 1 > 2"""
+
+        def f():
+            a, b, c = range(3)
+            assert a < b <= c
+
+        getmsg(f, must_pass=True)
+
+        def f():
+            a, b, c = range(3)
+            assert a < b
+            assert b < c
+
+        getmsg(f, must_pass=True)
+
+    def test_len(self):
+
+        def f():
+            values = list(range(10))
+            assert len(values) == 11
+
+        assert getmsg(f).startswith("""assert 10 == 11
+ +  where 10 = len([""")
+
+    def test_custom_reprcompare(self, monkeypatch):
+        def my_reprcompare(op, left, right):
+            return "42"
+
+        monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
+
+        def f():
+            assert 42 < 3
+
+        assert getmsg(f) == "assert 42"
+
+        def my_reprcompare(op, left, right):
+            return "%s %s %s" % (left, op, right)
+
+        monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
+
+        def f():
+            assert 1 < 3 < 5 <= 4 < 7
+
+        assert getmsg(f) == "assert 5 <= 4"
+
+    def test_assert_raising_nonzero_in_comparison(self):
+        def f():
+            class A(object):
+
+                def __nonzero__(self):
+                    raise ValueError(42)
+
+                def __lt__(self, other):
+                    return A()
+
+                def __repr__(self):
+                    return "<MY42 object>"
+
+            def myany(x):
+                return False
+
+            assert myany(A() < 0)
+
+        assert "<MY42 object> < 0" in getmsg(f)
+
+    def test_formatchar(self):
+        def f():
+            assert "%test" == "test"
+
+        assert getmsg(f).startswith("assert '%test' == 'test'")
+
+    def test_custom_repr(self):
+        def f():
+            class Foo(object):
+                a = 1
+
+                def __repr__(self):
+                    return "\n{ \n~ \n}"
+
+            f = Foo()
+            assert 0 == f.a
+
+        assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0]
+
+
+class TestRewriteOnImport(object):
+
+    def test_pycache_is_a_file(self, testdir):
+        testdir.tmpdir.join("__pycache__").write("Hello")
+        testdir.makepyfile("""
+            def test_rewritten():
+                assert "@py_builtins" in globals()""")
+        assert testdir.runpytest().ret == 0
+
+    def test_pycache_is_readonly(self, testdir):
+        cache = testdir.tmpdir.mkdir("__pycache__")
+        old_mode = cache.stat().mode
+        cache.chmod(old_mode ^ stat.S_IWRITE)
+        testdir.makepyfile("""
+            def test_rewritten():
+                assert "@py_builtins" in globals()""")
+        try:
+            assert testdir.runpytest().ret == 0
+        finally:
+            cache.chmod(old_mode)
+
+    def test_zipfile(self, testdir):
+        z = testdir.tmpdir.join("myzip.zip")
+        z_fn = str(z)
+        f = zipfile.ZipFile(z_fn, "w")
+        try:
+            f.writestr("test_gum/__init__.py", "")
+            f.writestr("test_gum/test_lizard.py", "")
+        finally:
+            f.close()
+        z.chmod(256)
+        testdir.makepyfile("""
+            import sys
+            sys.path.append(%r)
+            import test_gum.test_lizard""" % (z_fn,))
+        assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED
+
+    def test_readonly(self, testdir):
+        sub = testdir.mkdir("testing")
+        sub.join("test_readonly.py").write(
+            py.builtin._totext("""
+def test_rewritten():
+    assert "@py_builtins" in globals()
+            """).encode("utf-8"), "wb")
+        old_mode = sub.stat().mode
+        sub.chmod(320)
+        try:
+            assert testdir.runpytest().ret == 0
+        finally:
+            sub.chmod(old_mode)
+
+    def test_dont_write_bytecode(self, testdir, monkeypatch):
+        testdir.makepyfile("""
+            import os
+            def test_no_bytecode():
+                assert "__pycache__" in __cached__
+                assert not os.path.exists(__cached__)
+                assert not os.path.exists(os.path.dirname(__cached__))""")
+        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
+        assert testdir.runpytest_subprocess().ret == 0
+
+    def test_orphaned_pyc_file(self, testdir):
+        if sys.version_info < (3, 0) and hasattr(sys, 'pypy_version_info'):
+            pytest.skip("pypy2 doesn't run orphaned pyc files")
+
+        testdir.makepyfile("""
+            import orphan
+            def test_it():
+                assert orphan.value == 17
+            """)
+        testdir.makepyfile(orphan="""
+            value = 17
+            """)
+        py_compile.compile("orphan.py")
+        os.remove("orphan.py")
+
+        # Python 3 puts the .pyc files in a __pycache__ directory, and will
+        # not import from there without source.  It will import a .pyc from
+        # the source location though.
+        if not os.path.exists("orphan.pyc"):
+            pycs = glob.glob("__pycache__/orphan.*.pyc")
+            assert len(pycs) == 1
+            os.rename(pycs[0], "orphan.pyc")
+
+        assert testdir.runpytest().ret == 0
+
+    @pytest.mark.skipif('"__pypy__" in sys.modules')
+    def test_pyc_vs_pyo(self, testdir, monkeypatch):
+        testdir.makepyfile("""
+            import pytest
+            def test_optimized():
+                "hello"
+                assert test_optimized.__doc__ is None"""
+                           )
+        p = py.path.local.make_numbered_dir(prefix="runpytest-", keep=None,
+                                            rootdir=testdir.tmpdir)
+        tmp = "--basetemp=%s" % p
+        monkeypatch.setenv("PYTHONOPTIMIZE", "2")
+        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
+        assert testdir.runpytest_subprocess(tmp).ret == 0
+        tagged = "test_pyc_vs_pyo." + PYTEST_TAG
+        assert tagged + ".pyo" in os.listdir("__pycache__")
+        monkeypatch.undo()
+        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
+        assert testdir.runpytest_subprocess(tmp).ret == 1
+        assert tagged + ".pyc" in os.listdir("__pycache__")
+
+    def test_package(self, testdir):
+        pkg = testdir.tmpdir.join("pkg")
+        pkg.mkdir()
+        pkg.join("__init__.py").ensure()
+        pkg.join("test_blah.py").write("""
+def test_rewritten():
+    assert "@py_builtins" in globals()""")
+        assert testdir.runpytest().ret == 0
+
+    def test_translate_newlines(self, testdir):
+        content = "def test_rewritten():\r\n assert '@py_builtins' in globals()"
+        b = content.encode("utf-8")
+        testdir.tmpdir.join("test_newlines.py").write(b, "wb")
+        assert testdir.runpytest().ret == 0
+
+    @pytest.mark.skipif(sys.version_info < (3, 4),
+                        reason='packages without __init__.py not supported on python 2')
+    def test_package_without__init__py(self, testdir):
+        pkg = testdir.mkdir('a_package_without_init_py')
+        pkg.join('module.py').ensure()
+        testdir.makepyfile("import a_package_without_init_py.module")
+        assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED
+
+    def test_rewrite_warning(self, pytestconfig, monkeypatch):
+        hook = AssertionRewritingHook(pytestconfig)
+        warnings = []
+
+        def mywarn(code, msg):
+            warnings.append((code, msg))
+
+        monkeypatch.setattr(hook.config, 'warn', mywarn)
+        hook.mark_rewrite('_pytest')
+        assert '_pytest' in warnings[0][1]
+
+    def test_rewrite_module_imported_from_conftest(self, testdir):
+        testdir.makeconftest('''
+            import test_rewrite_module_imported
+        ''')
+        testdir.makepyfile(test_rewrite_module_imported='''
+            def test_rewritten():
+                assert "@py_builtins" in globals()
+        ''')
+        assert testdir.runpytest_subprocess().ret == 0
+
+    def test_remember_rewritten_modules(self, pytestconfig, testdir, monkeypatch):
+        """
+        AssertionRewriteHook should remember rewritten modules so it
+        doesn't give false positives (#2005).
+        """
+        monkeypatch.syspath_prepend(testdir.tmpdir)
+        testdir.makepyfile(test_remember_rewritten_modules='')
+        warnings = []
+        hook = AssertionRewritingHook(pytestconfig)
+        monkeypatch.setattr(hook.config, 'warn', lambda code, msg: warnings.append(msg))
+        hook.find_module('test_remember_rewritten_modules')
+        hook.load_module('test_remember_rewritten_modules')
+        hook.mark_rewrite('test_remember_rewritten_modules')
+        hook.mark_rewrite('test_remember_rewritten_modules')
+        assert warnings == []
+
+    def test_rewrite_warning_using_pytest_plugins(self, testdir):
+        testdir.makepyfile(**{
+            'conftest.py': "pytest_plugins = ['core', 'gui', 'sci']",
+            'core.py': "",
+            'gui.py': "pytest_plugins = ['core', 'sci']",
+            'sci.py': "pytest_plugins = ['core']",
+            'test_rewrite_warning_pytest_plugins.py': "def test(): pass",
+        })
+        testdir.chdir()
+        result = testdir.runpytest_subprocess()
+        result.stdout.fnmatch_lines(['*= 1 passed in *=*'])
+        assert 'pytest-warning summary' not in result.stdout.str()
+
+    def test_rewrite_warning_using_pytest_plugins_env_var(self, testdir, monkeypatch):
+        monkeypatch.setenv('PYTEST_PLUGINS', 'plugin')
+        testdir.makepyfile(**{
+            'plugin.py': "",
+            'test_rewrite_warning_using_pytest_plugins_env_var.py': """
+                import plugin
+                pytest_plugins = ['plugin']
+                def test():
+                    pass
+            """,
+        })
+        testdir.chdir()
+        result = testdir.runpytest_subprocess()
+        result.stdout.fnmatch_lines(['*= 1 passed in *=*'])
+        assert 'pytest-warning summary' not in result.stdout.str()
+
+    @pytest.mark.skipif(sys.version_info[0] > 2, reason='python 2 only')
+    def test_rewrite_future_imports(self, testdir):
+        """Test that rewritten modules don't inherit the __future__ flags
+        from the assertrewrite module.
+
+        assertion.rewrite imports __future__.division (and others), so
+        ensure rewritten modules don't inherit those flags.
+
+        The test below will fail if __future__.division is enabled
+        """
+        testdir.makepyfile('''
+            def test():
+                x = 1 / 2
+                assert type(x) is int
+        ''')
+        result = testdir.runpytest()
+        assert result.ret == 0
+
+
+class TestAssertionRewriteHookDetails(object):
+    def test_loader_is_package_false_for_module(self, testdir):
+        testdir.makepyfile(test_fun="""
+            def test_loader():
+                assert not __loader__.is_package(__name__)
+            """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "* 1 passed*",
+        ])
+
+    def test_loader_is_package_true_for_package(self, testdir):
+        testdir.makepyfile(test_fun="""
+            def test_loader():
+                assert not __loader__.is_package(__name__)
+
+            def test_fun():
+                assert __loader__.is_package('fun')
+
+            def test_missing():
+                assert not __loader__.is_package('pytest_not_there')
+            """)
+        testdir.mkpydir('fun')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            '* 3 passed*',
+        ])
+
+    @pytest.mark.skipif("sys.version_info[0] >= 3")
+    @pytest.mark.xfail("hasattr(sys, 'pypy_translation_info')")
+    def test_assume_ascii(self, testdir):
+        content = "u'\xe2\x99\xa5\x01\xfe'"
+        testdir.tmpdir.join("test_encoding.py").write(content, "wb")
+        res = testdir.runpytest()
+        assert res.ret != 0
+        assert "SyntaxError: Non-ASCII character" in res.stdout.str()
+
+    @pytest.mark.skipif("sys.version_info[0] >= 3")
+    def test_detect_coding_cookie(self, testdir):
+        testdir.makepyfile(test_cookie="""
+            # -*- coding: utf-8 -*-
+            u"St\xc3\xa4d"
+            def test_rewritten():
+                assert "@py_builtins" in globals()""")
+        assert testdir.runpytest().ret == 0
+
+    @pytest.mark.skipif("sys.version_info[0] >= 3")
+    def test_detect_coding_cookie_second_line(self, testdir):
+        testdir.makepyfile(test_cookie="""
+            # -*- coding: utf-8 -*-
+            u"St\xc3\xa4d"
+            def test_rewritten():
+                assert "@py_builtins" in globals()""")
+        assert testdir.runpytest().ret == 0
+
+    @pytest.mark.skipif("sys.version_info[0] >= 3")
+    def test_detect_coding_cookie_crlf(self, testdir):
+        testdir.makepyfile(test_cookie="""
+            # -*- coding: utf-8 -*-
+            u"St\xc3\xa4d"
+            def test_rewritten():
+                assert "@py_builtins" in globals()""")
+        assert testdir.runpytest().ret == 0
+
+    def test_sys_meta_path_munged(self, testdir):
+        testdir.makepyfile("""
+            def test_meta_path():
+                import sys; sys.meta_path = []""")
+        assert testdir.runpytest().ret == 0
+
+    def test_write_pyc(self, testdir, tmpdir, monkeypatch):
+        from _pytest.assertion.rewrite import _write_pyc
+        from _pytest.assertion import AssertionState
+        try:
+            import __builtin__ as b
+        except ImportError:
+            import builtins as b
+        config = testdir.parseconfig([])
+        state = AssertionState(config, "rewrite")
+        source_path = tmpdir.ensure("source.py")
+        pycpath = tmpdir.join("pyc").strpath
+        assert _write_pyc(state, [1], source_path.stat(), pycpath)
+
+        def open(*args):
+            e = IOError()
+            e.errno = 10
+            raise e
+
+        monkeypatch.setattr(b, "open", open)
+        assert not _write_pyc(state, [1], source_path.stat(), pycpath)
+
+    def test_resources_provider_for_loader(self, testdir):
+        """
+        Attempts to load resources from a package should succeed normally,
+        even when the AssertionRewriteHook is used to load the modules.
+
+        See #366 for details.
+        """
+        pytest.importorskip("pkg_resources")
+
+        testdir.mkpydir('testpkg')
+        contents = {
+            'testpkg/test_pkg': """
+                import pkg_resources
+
+                import pytest
+                from _pytest.assertion.rewrite import AssertionRewritingHook
+
+                def test_load_resource():
+                    assert isinstance(__loader__, AssertionRewritingHook)
+                    res = pkg_resources.resource_string(__name__, 'resource.txt')
+                    res = res.decode('ascii')
+                    assert res == 'Load me please.'
+                """,
+        }
+        testdir.makepyfile(**contents)
+        testdir.maketxtfile(**{'testpkg/resource': "Load me please."})
+
+        result = testdir.runpytest_subprocess()
+        result.assert_outcomes(passed=1)
+
+    def test_read_pyc(self, tmpdir):
+        """
+        Ensure that the `_read_pyc` can properly deal with corrupted pyc files.
+        In those circumstances it should just give up instead of generating
+        an exception that is propagated to the caller.
+        """
+        import py_compile
+        from _pytest.assertion.rewrite import _read_pyc
+
+        source = tmpdir.join('source.py')
+        pyc = source + 'c'
+
+        source.write('def test(): pass')
+        py_compile.compile(str(source), str(pyc))
+
+        contents = pyc.read(mode='rb')
+        strip_bytes = 20  # header is around 8 bytes, strip a little more
+        assert len(contents) > strip_bytes
+        pyc.write(contents[:strip_bytes], mode='wb')
+
+        assert _read_pyc(source, str(pyc)) is None  # no error
+
+    def test_reload_is_same(self, testdir):
+        # A file that will be picked up during collecting.
+        testdir.tmpdir.join("file.py").ensure()
+        testdir.tmpdir.join("pytest.ini").write(py.std.textwrap.dedent("""
+            [pytest]
+            python_files = *.py
+        """))
+
+        testdir.makepyfile(test_fun="""
+            import sys
+            try:
+                from imp import reload
+            except ImportError:
+                pass
+
+            def test_loader():
+                import file
+                assert sys.modules["file"] is reload(file)
+            """)
+        result = testdir.runpytest('-s')
+        result.stdout.fnmatch_lines([
+            "* 1 passed*",
+        ])
+
+    def test_get_data_support(self, testdir):
+        """Implement optional PEP302 api (#808).
+        """
+        path = testdir.mkpydir("foo")
+        path.join("test_foo.py").write(_pytest._code.Source("""
+            class Test(object):
+                def test_foo(self):
+                    import pkgutil
+                    data = pkgutil.get_data('foo.test_foo', 'data.txt')
+                    assert data == b'Hey'
+        """))
+        path.join('data.txt').write('Hey')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*1 passed*')
+
+
+def test_issue731(testdir):
+    testdir.makepyfile("""
+    class LongReprWithBraces(object):
+        def __repr__(self):
+           return 'LongReprWithBraces({' + ('a' * 80) + '}' + ('a' * 120) + ')'
+
+        def some_method(self):
+            return False
+
+    def test_long_repr():
+        obj = LongReprWithBraces()
+        assert obj.some_method()
+    """)
+    result = testdir.runpytest()
+    assert 'unbalanced braces' not in result.stdout.str()
+
+
+class TestIssue925(object):
+    def test_simple_case(self, testdir):
+        testdir.makepyfile("""
+        def test_ternary_display():
+            assert (False == False) == False
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*E*assert (False == False) == False')
+
+    def test_long_case(self, testdir):
+        testdir.makepyfile("""
+        def test_ternary_display():
+             assert False == (False == True) == True
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*E*assert (False == True) == True')
+
+    def test_many_brackets(self, testdir):
+        testdir.makepyfile("""
+            def test_ternary_display():
+                 assert True == ((False == True) == True)
+            """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*E*assert True == ((False == True) == True)')
+
+
+class TestIssue2121():
+    def test_simple(self, testdir):
+        testdir.tmpdir.join("tests/file.py").ensure().write("""
+def test_simple_failure():
+    assert 1 + 1 == 3
+""")
+        testdir.tmpdir.join("pytest.ini").write(py.std.textwrap.dedent("""
+            [pytest]
+            python_files = tests/**.py
+        """))
+
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*E*assert (1 + 1) == 3')
diff --git a/tools/third_party/pytest/testing/test_cache.py b/tools/third_party/pytest/testing/test_cache.py
new file mode 100755
index 0000000..a37170c
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_cache.py
@@ -0,0 +1,605 @@
+from __future__ import absolute_import, division, print_function
+import sys
+import py
+import _pytest
+import pytest
+import os
+import shutil
+
+pytest_plugins = "pytester",
+
+
+class TestNewAPI(object):
+    def test_config_cache_makedir(self, testdir):
+        testdir.makeini("[pytest]")
+        config = testdir.parseconfigure()
+        with pytest.raises(ValueError):
+            config.cache.makedir("key/name")
+
+        p = config.cache.makedir("name")
+        assert p.check()
+
+    def test_config_cache_dataerror(self, testdir):
+        testdir.makeini("[pytest]")
+        config = testdir.parseconfigure()
+        cache = config.cache
+        pytest.raises(TypeError, lambda: cache.set("key/name", cache))
+        config.cache.set("key/name", 0)
+        config.cache._getvaluepath("key/name").write("123invalid")
+        val = config.cache.get("key/name", -2)
+        assert val == -2
+
+    def test_cache_writefail_cachfile_silent(self, testdir):
+        testdir.makeini("[pytest]")
+        testdir.tmpdir.join('.cache').write('gone wrong')
+        config = testdir.parseconfigure()
+        cache = config.cache
+        cache.set('test/broken', [])
+
+    @pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows')
+    def test_cache_writefail_permissions(self, testdir):
+        testdir.makeini("[pytest]")
+        testdir.tmpdir.ensure_dir('.cache').chmod(0)
+        config = testdir.parseconfigure()
+        cache = config.cache
+        cache.set('test/broken', [])
+
+    @pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows')
+    def test_cache_failure_warns(self, testdir):
+        testdir.tmpdir.ensure_dir('.cache').chmod(0)
+        testdir.makepyfile("""
+            def test_error():
+                raise Exception
+
+        """)
+        result = testdir.runpytest('-rw')
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*could not create cache path*",
+            "*1 warnings*",
+        ])
+
+    def test_config_cache(self, testdir):
+        testdir.makeconftest("""
+            def pytest_configure(config):
+                # see that we get cache information early on
+                assert hasattr(config, "cache")
+        """)
+        testdir.makepyfile("""
+            def test_session(pytestconfig):
+                assert hasattr(pytestconfig, "cache")
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_cachefuncarg(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def test_cachefuncarg(cache):
+                val = cache.get("some/thing", None)
+                assert val is None
+                cache.set("some/thing", [1])
+                pytest.raises(TypeError, lambda: cache.get("some/thing"))
+                val = cache.get("some/thing", [])
+                assert val == [1]
+        """)
+        result = testdir.runpytest()
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_custom_rel_cache_dir(self, testdir):
+        rel_cache_dir = os.path.join('custom_cache_dir', 'subdir')
+        testdir.makeini("""
+            [pytest]
+            cache_dir = {cache_dir}
+        """.format(cache_dir=rel_cache_dir))
+        testdir.makepyfile(test_errored='def test_error():\n    assert False')
+        testdir.runpytest()
+        assert testdir.tmpdir.join(rel_cache_dir).isdir()
+
+    def test_custom_abs_cache_dir(self, testdir, tmpdir_factory):
+        tmp = str(tmpdir_factory.mktemp('tmp'))
+        abs_cache_dir = os.path.join(tmp, 'custom_cache_dir')
+        testdir.makeini("""
+            [pytest]
+            cache_dir = {cache_dir}
+        """.format(cache_dir=abs_cache_dir))
+        testdir.makepyfile(test_errored='def test_error():\n    assert False')
+        testdir.runpytest()
+        assert py.path.local(abs_cache_dir).isdir()
+
+    def test_custom_cache_dir_with_env_var(self, testdir, monkeypatch):
+        monkeypatch.setenv('env_var', 'custom_cache_dir')
+        testdir.makeini("""
+            [pytest]
+            cache_dir = {cache_dir}
+        """.format(cache_dir='$env_var'))
+        testdir.makepyfile(test_errored='def test_error():\n    assert False')
+        testdir.runpytest()
+        assert testdir.tmpdir.join('custom_cache_dir').isdir()
+
+
+def test_cache_reportheader(testdir):
+    testdir.makepyfile("""
+        def test_hello():
+            pass
+    """)
+    result = testdir.runpytest("-v")
+    result.stdout.fnmatch_lines([
+        "cachedir: .cache"
+    ])
+
+
+def test_cache_show(testdir):
+    result = testdir.runpytest("--cache-show")
+    assert result.ret == 0
+    result.stdout.fnmatch_lines([
+        "*cache is empty*"
+    ])
+    testdir.makeconftest("""
+        def pytest_configure(config):
+            config.cache.set("my/name", [1,2,3])
+            config.cache.set("other/some", {1:2})
+            dp = config.cache.makedir("mydb")
+            dp.ensure("hello")
+            dp.ensure("world")
+    """)
+    result = testdir.runpytest()
+    assert result.ret == 5  # no tests executed
+    result = testdir.runpytest("--cache-show")
+    result.stdout.fnmatch_lines_random([
+        "*cachedir:*",
+        "-*cache values*-",
+        "*my/name contains:",
+        "  [1, 2, 3]",
+        "*other/some contains*",
+        "  {*1*: 2}",
+        "-*cache directories*-",
+        "*mydb/hello*length 0*",
+        "*mydb/world*length 0*",
+    ])
+
+
+class TestLastFailed(object):
+
+    def test_lastfailed_usecase(self, testdir, monkeypatch):
+        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
+        p = testdir.makepyfile("""
+            def test_1():
+                assert 0
+            def test_2():
+                assert 0
+            def test_3():
+                assert 1
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*2 failed*",
+        ])
+        p.write(_pytest._code.Source("""
+            def test_1():
+                assert 1
+
+            def test_2():
+                assert 1
+
+            def test_3():
+                assert 0
+        """))
+        result = testdir.runpytest("--lf")
+        result.stdout.fnmatch_lines([
+            "*2 passed*1 desel*",
+        ])
+        result = testdir.runpytest("--lf")
+        result.stdout.fnmatch_lines([
+            "*1 failed*2 passed*",
+        ])
+        result = testdir.runpytest("--lf", "--cache-clear")
+        result.stdout.fnmatch_lines([
+            "*1 failed*2 passed*",
+        ])
+
+        # Run this again to make sure clear-cache is robust
+        if os.path.isdir('.cache'):
+            shutil.rmtree('.cache')
+        result = testdir.runpytest("--lf", "--cache-clear")
+        result.stdout.fnmatch_lines([
+            "*1 failed*2 passed*",
+        ])
+
+    def test_failedfirst_order(self, testdir):
+        testdir.tmpdir.join('test_a.py').write(_pytest._code.Source("""
+            def test_always_passes():
+                assert 1
+        """))
+        testdir.tmpdir.join('test_b.py').write(_pytest._code.Source("""
+            def test_always_fails():
+                assert 0
+        """))
+        result = testdir.runpytest()
+        # Test order will be collection order; alphabetical
+        result.stdout.fnmatch_lines([
+            "test_a.py*",
+            "test_b.py*",
+        ])
+        result = testdir.runpytest("--ff")
+        # Test order will be failing tests firs
+        result.stdout.fnmatch_lines([
+            "test_b.py*",
+            "test_a.py*",
+        ])
+
+    def test_lastfailed_failedfirst_order(self, testdir):
+        testdir.makepyfile(**{
+            'test_a.py': """
+                def test_always_passes():
+                    assert 1
+            """,
+            'test_b.py': """
+                def test_always_fails():
+                    assert 0
+            """,
+        })
+        result = testdir.runpytest()
+        # Test order will be collection order; alphabetical
+        result.stdout.fnmatch_lines([
+            "test_a.py*",
+            "test_b.py*",
+        ])
+        result = testdir.runpytest("--lf", "--ff")
+        # Test order will be failing tests firs
+        result.stdout.fnmatch_lines([
+            "test_b.py*",
+        ])
+        assert 'test_a.py' not in result.stdout.str()
+
+    def test_lastfailed_difference_invocations(self, testdir, monkeypatch):
+        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
+        testdir.makepyfile(test_a="""
+            def test_a1():
+                assert 0
+            def test_a2():
+                assert 1
+        """, test_b="""
+            def test_b1():
+                assert 0
+        """)
+        p = testdir.tmpdir.join("test_a.py")
+        p2 = testdir.tmpdir.join("test_b.py")
+
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*2 failed*",
+        ])
+        result = testdir.runpytest("--lf", p2)
+        result.stdout.fnmatch_lines([
+            "*1 failed*",
+        ])
+        p2.write(_pytest._code.Source("""
+            def test_b1():
+                assert 1
+        """))
+        result = testdir.runpytest("--lf", p2)
+        result.stdout.fnmatch_lines([
+            "*1 passed*",
+        ])
+        result = testdir.runpytest("--lf", p)
+        result.stdout.fnmatch_lines([
+            "*1 failed*1 desel*",
+        ])
+
+    def test_lastfailed_usecase_splice(self, testdir, monkeypatch):
+        monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
+        testdir.makepyfile("""
+            def test_1():
+                assert 0
+        """)
+        p2 = testdir.tmpdir.join("test_something.py")
+        p2.write(_pytest._code.Source("""
+            def test_2():
+                assert 0
+        """))
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*2 failed*",
+        ])
+        result = testdir.runpytest("--lf", p2)
+        result.stdout.fnmatch_lines([
+            "*1 failed*",
+        ])
+        result = testdir.runpytest("--lf")
+        result.stdout.fnmatch_lines([
+            "*2 failed*",
+        ])
+
+    def test_lastfailed_xpass(self, testdir):
+        testdir.inline_runsource("""
+            import pytest
+            @pytest.mark.xfail
+            def test_hello():
+                assert 1
+        """)
+        config = testdir.parseconfigure()
+        lastfailed = config.cache.get("cache/lastfailed", -1)
+        assert lastfailed == -1
+
+    def test_non_serializable_parametrize(self, testdir):
+        """Test that failed parametrized tests with unmarshable parameters
+        don't break pytest-cache.
+        """
+        testdir.makepyfile(r"""
+            import pytest
+
+            @pytest.mark.parametrize('val', [
+                b'\xac\x10\x02G',
+            ])
+            def test_fail(val):
+                assert False
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*1 failed in*')
+
+    def test_terminal_report_lastfailed(self, testdir):
+        test_a = testdir.makepyfile(test_a="""
+            def test_a1():
+                pass
+            def test_a2():
+                pass
+        """)
+        test_b = testdir.makepyfile(test_b="""
+            def test_b1():
+                assert 0
+            def test_b2():
+                assert 0
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            'collected 4 items',
+            '*2 failed, 2 passed in*',
+        ])
+
+        result = testdir.runpytest('--lf')
+        result.stdout.fnmatch_lines([
+            'collected 4 items',
+            'run-last-failure: rerun previous 2 failures',
+            '*2 failed, 2 deselected in*',
+        ])
+
+        result = testdir.runpytest(test_a, '--lf')
+        result.stdout.fnmatch_lines([
+            'collected 2 items',
+            'run-last-failure: run all (no recorded failures)',
+            '*2 passed in*',
+        ])
+
+        result = testdir.runpytest(test_b, '--lf')
+        result.stdout.fnmatch_lines([
+            'collected 2 items',
+            'run-last-failure: rerun previous 2 failures',
+            '*2 failed in*',
+        ])
+
+        result = testdir.runpytest('test_b.py::test_b1', '--lf')
+        result.stdout.fnmatch_lines([
+            'collected 1 item',
+            'run-last-failure: rerun previous 1 failure',
+            '*1 failed in*',
+        ])
+
+    def test_terminal_report_failedfirst(self, testdir):
+        testdir.makepyfile(test_a="""
+            def test_a1():
+                assert 0
+            def test_a2():
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            'collected 2 items',
+            '*1 failed, 1 passed in*',
+        ])
+
+        result = testdir.runpytest('--ff')
+        result.stdout.fnmatch_lines([
+            'collected 2 items',
+            'run-last-failure: rerun previous 1 failure first',
+            '*1 failed, 1 passed in*',
+        ])
+
+    def test_lastfailed_collectfailure(self, testdir, monkeypatch):
+
+        testdir.makepyfile(test_maybe="""
+            import py
+            env = py.std.os.environ
+            if '1' == env['FAILIMPORT']:
+                raise ImportError('fail')
+            def test_hello():
+                assert '0' == env['FAILTEST']
+        """)
+
+        def rlf(fail_import, fail_run):
+            monkeypatch.setenv('FAILIMPORT', fail_import)
+            monkeypatch.setenv('FAILTEST', fail_run)
+
+            testdir.runpytest('-q')
+            config = testdir.parseconfigure()
+            lastfailed = config.cache.get("cache/lastfailed", -1)
+            return lastfailed
+
+        lastfailed = rlf(fail_import=0, fail_run=0)
+        assert lastfailed == -1
+
+        lastfailed = rlf(fail_import=1, fail_run=0)
+        assert list(lastfailed) == ['test_maybe.py']
+
+        lastfailed = rlf(fail_import=0, fail_run=1)
+        assert list(lastfailed) == ['test_maybe.py::test_hello']
+
+    def test_lastfailed_failure_subset(self, testdir, monkeypatch):
+
+        testdir.makepyfile(test_maybe="""
+            import py
+            env = py.std.os.environ
+            if '1' == env['FAILIMPORT']:
+                raise ImportError('fail')
+            def test_hello():
+                assert '0' == env['FAILTEST']
+        """)
+
+        testdir.makepyfile(test_maybe2="""
+            import py
+            env = py.std.os.environ
+            if '1' == env['FAILIMPORT']:
+                raise ImportError('fail')
+            def test_hello():
+                assert '0' == env['FAILTEST']
+
+            def test_pass():
+                pass
+        """)
+
+        def rlf(fail_import, fail_run, args=()):
+            monkeypatch.setenv('FAILIMPORT', fail_import)
+            monkeypatch.setenv('FAILTEST', fail_run)
+
+            result = testdir.runpytest('-q', '--lf', *args)
+            config = testdir.parseconfigure()
+            lastfailed = config.cache.get("cache/lastfailed", -1)
+            return result, lastfailed
+
+        result, lastfailed = rlf(fail_import=0, fail_run=0)
+        assert lastfailed == -1
+        result.stdout.fnmatch_lines([
+            '*3 passed*',
+        ])
+
+        result, lastfailed = rlf(fail_import=1, fail_run=0)
+        assert sorted(list(lastfailed)) == ['test_maybe.py', 'test_maybe2.py']
+
+        result, lastfailed = rlf(fail_import=0, fail_run=0,
+                                 args=('test_maybe2.py',))
+        assert list(lastfailed) == ['test_maybe.py']
+
+        # edge case of test selection - even if we remember failures
+        # from other tests we still need to run all tests if no test
+        # matches the failures
+        result, lastfailed = rlf(fail_import=0, fail_run=0,
+                                 args=('test_maybe2.py',))
+        assert list(lastfailed) == ['test_maybe.py']
+        result.stdout.fnmatch_lines([
+            '*2 passed*',
+        ])
+
+    def test_lastfailed_creates_cache_when_needed(self, testdir):
+        # Issue #1342
+        testdir.makepyfile(test_empty='')
+        testdir.runpytest('-q', '--lf')
+        assert not os.path.exists('.cache')
+
+        testdir.makepyfile(test_successful='def test_success():\n    assert True')
+        testdir.runpytest('-q', '--lf')
+        assert not os.path.exists('.cache')
+
+        testdir.makepyfile(test_errored='def test_error():\n    assert False')
+        testdir.runpytest('-q', '--lf')
+        assert os.path.exists('.cache')
+
+    def test_xfail_not_considered_failure(self, testdir):
+        testdir.makepyfile('''
+            import pytest
+            @pytest.mark.xfail
+            def test():
+                assert 0
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*1 xfailed*')
+        assert self.get_cached_last_failed(testdir) == []
+
+    def test_xfail_strict_considered_failure(self, testdir):
+        testdir.makepyfile('''
+            import pytest
+            @pytest.mark.xfail(strict=True)
+            def test():
+                pass
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines('*1 failed*')
+        assert self.get_cached_last_failed(testdir) == ['test_xfail_strict_considered_failure.py::test']
+
+    @pytest.mark.parametrize('mark', ['mark.xfail', 'mark.skip'])
+    def test_failed_changed_to_xfail_or_skip(self, testdir, mark):
+        testdir.makepyfile('''
+            import pytest
+            def test():
+                assert 0
+        ''')
+        result = testdir.runpytest()
+        assert self.get_cached_last_failed(testdir) == ['test_failed_changed_to_xfail_or_skip.py::test']
+        assert result.ret == 1
+
+        testdir.makepyfile('''
+            import pytest
+            @pytest.{mark}
+            def test():
+                assert 0
+        '''.format(mark=mark))
+        result = testdir.runpytest()
+        assert result.ret == 0
+        assert self.get_cached_last_failed(testdir) == []
+        assert result.ret == 0
+
+    def get_cached_last_failed(self, testdir):
+        config = testdir.parseconfigure()
+        return sorted(config.cache.get("cache/lastfailed", {}))
+
+    def test_cache_cumulative(self, testdir):
+        """
+        Test workflow where user fixes errors gradually file by file using --lf.
+        """
+        # 1. initial run
+        test_bar = testdir.makepyfile(test_bar="""
+            def test_bar_1():
+                pass
+            def test_bar_2():
+                assert 0
+        """)
+        test_foo = testdir.makepyfile(test_foo="""
+            def test_foo_3():
+                pass
+            def test_foo_4():
+                assert 0
+        """)
+        testdir.runpytest()
+        assert self.get_cached_last_failed(testdir) == ['test_bar.py::test_bar_2', 'test_foo.py::test_foo_4']
+
+        # 2. fix test_bar_2, run only test_bar.py
+        testdir.makepyfile(test_bar="""
+            def test_bar_1():
+                pass
+            def test_bar_2():
+                pass
+        """)
+        result = testdir.runpytest(test_bar)
+        result.stdout.fnmatch_lines('*2 passed*')
+        # ensure cache does not forget that test_foo_4 failed once before
+        assert self.get_cached_last_failed(testdir) == ['test_foo.py::test_foo_4']
+
+        result = testdir.runpytest('--last-failed')
+        result.stdout.fnmatch_lines('*1 failed, 3 deselected*')
+        assert self.get_cached_last_failed(testdir) == ['test_foo.py::test_foo_4']
+
+        # 3. fix test_foo_4, run only test_foo.py
+        test_foo = testdir.makepyfile(test_foo="""
+            def test_foo_3():
+                pass
+            def test_foo_4():
+                pass
+        """)
+        result = testdir.runpytest(test_foo, '--last-failed')
+        result.stdout.fnmatch_lines('*1 passed, 1 deselected*')
+        assert self.get_cached_last_failed(testdir) == []
+
+        result = testdir.runpytest('--last-failed')
+        result.stdout.fnmatch_lines('*4 passed*')
+        assert self.get_cached_last_failed(testdir) == []
diff --git a/tools/third_party/pytest/testing/test_capture.py b/tools/third_party/pytest/testing/test_capture.py
new file mode 100644
index 0000000..f769a72
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_capture.py
@@ -0,0 +1,1274 @@
+from __future__ import absolute_import, division, print_function
+# note: py.io capture tests where copied from
+# pylib 1.4.20.dev2 (rev 13d9af95547e)
+from __future__ import with_statement
+import pickle
+import os
+import sys
+from io import UnsupportedOperation
+
+import _pytest._code
+import py
+import pytest
+import contextlib
+
+from _pytest import capture
+from _pytest.capture import CaptureManager
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+
+
+needsosdup = pytest.mark.xfail("not hasattr(os, 'dup')")
+
+if sys.version_info >= (3, 0):
+    def tobytes(obj):
+        if isinstance(obj, str):
+            obj = obj.encode('UTF-8')
+        assert isinstance(obj, bytes)
+        return obj
+
+    def totext(obj):
+        if isinstance(obj, bytes):
+            obj = str(obj, 'UTF-8')
+        assert isinstance(obj, str)
+        return obj
+else:
+    def tobytes(obj):
+        if isinstance(obj, unicode):
+            obj = obj.encode('UTF-8')
+        assert isinstance(obj, str)
+        return obj
+
+    def totext(obj):
+        if isinstance(obj, str):
+            obj = unicode(obj, 'UTF-8')
+        assert isinstance(obj, unicode)
+        return obj
+
+
+def oswritebytes(fd, obj):
+    os.write(fd, tobytes(obj))
+
+
+def StdCaptureFD(out=True, err=True, in_=True):
+    return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture)
+
+
+def StdCapture(out=True, err=True, in_=True):
+    return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture)
+
+
+class TestCaptureManager(object):
+    def test_getmethod_default_no_fd(self, monkeypatch):
+        from _pytest.capture import pytest_addoption
+        from _pytest.config import Parser
+        parser = Parser()
+        pytest_addoption(parser)
+        default = parser._groups[0].options[0].default
+        assert default == "fd" if hasattr(os, "dup") else "sys"
+        parser = Parser()
+        monkeypatch.delattr(os, 'dup', raising=False)
+        pytest_addoption(parser)
+        assert parser._groups[0].options[0].default == "sys"
+
+    @needsosdup
+    @pytest.mark.parametrize("method",
+                             ['no', 'sys', pytest.mark.skipif('not hasattr(os, "dup")', 'fd')])
+    def test_capturing_basic_api(self, method):
+        capouter = StdCaptureFD()
+        old = sys.stdout, sys.stderr, sys.stdin
+        try:
+            capman = CaptureManager(method)
+            capman.start_global_capturing()
+            outerr = capman.suspend_global_capture()
+            assert outerr == ("", "")
+            outerr = capman.suspend_global_capture()
+            assert outerr == ("", "")
+            print("hello")
+            out, err = capman.suspend_global_capture()
+            if method == "no":
+                assert old == (sys.stdout, sys.stderr, sys.stdin)
+            else:
+                assert not out
+            capman.resume_global_capture()
+            print("hello")
+            out, err = capman.suspend_global_capture()
+            if method != "no":
+                assert out == "hello\n"
+            capman.stop_global_capturing()
+        finally:
+            capouter.stop_capturing()
+
+    @needsosdup
+    def test_init_capturing(self):
+        capouter = StdCaptureFD()
+        try:
+            capman = CaptureManager("fd")
+            capman.start_global_capturing()
+            pytest.raises(AssertionError, "capman.start_global_capturing()")
+            capman.stop_global_capturing()
+        finally:
+            capouter.stop_capturing()
+
+
+@pytest.mark.parametrize("method", ['fd', 'sys'])
+def test_capturing_unicode(testdir, method):
+    if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (2, 2):
+        pytest.xfail("does not work on pypy < 2.2")
+    if sys.version_info >= (3, 0):
+        obj = "'b\u00f6y'"
+    else:
+        obj = "u'\u00f6y'"
+    testdir.makepyfile("""
+        # coding=utf8
+        # taken from issue 227 from nosetests
+        def test_unicode():
+            import sys
+            print (sys.stdout)
+            print (%s)
+    """ % obj)
+    result = testdir.runpytest("--capture=%s" % method)
+    result.stdout.fnmatch_lines([
+        "*1 passed*"
+    ])
+
+
+@pytest.mark.parametrize("method", ['fd', 'sys'])
+def test_capturing_bytes_in_utf8_encoding(testdir, method):
+    testdir.makepyfile("""
+        def test_unicode():
+            print ('b\\u00f6y')
+    """)
+    result = testdir.runpytest("--capture=%s" % method)
+    result.stdout.fnmatch_lines([
+        "*1 passed*"
+    ])
+
+
+def test_collect_capturing(testdir):
+    p = testdir.makepyfile("""
+        print ("collect %s failure" % 13)
+        import xyz42123
+    """)
+    result = testdir.runpytest(p)
+    result.stdout.fnmatch_lines([
+        "*Captured stdout*",
+        "*collect 13 failure*",
+    ])
+
+
+class TestPerTestCapturing(object):
+    def test_capture_and_fixtures(self, testdir):
+        p = testdir.makepyfile("""
+            def setup_module(mod):
+                print ("setup module")
+            def setup_function(function):
+                print ("setup " + function.__name__)
+            def test_func1():
+                print ("in func1")
+                assert 0
+            def test_func2():
+                print ("in func2")
+                assert 0
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "setup module*",
+            "setup test_func1*",
+            "in func1*",
+            "setup test_func2*",
+            "in func2*",
+        ])
+
+    @pytest.mark.xfail(reason="unimplemented feature")
+    def test_capture_scope_cache(self, testdir):
+        p = testdir.makepyfile("""
+            import sys
+            def setup_module(func):
+                print ("module-setup")
+            def setup_function(func):
+                print ("function-setup")
+            def test_func():
+                print ("in function")
+                assert 0
+            def teardown_function(func):
+                print ("in teardown")
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*test_func():*",
+            "*Captured stdout during setup*",
+            "module-setup*",
+            "function-setup*",
+            "*Captured stdout*",
+            "in teardown*",
+        ])
+
+    def test_no_carry_over(self, testdir):
+        p = testdir.makepyfile("""
+            def test_func1():
+                print ("in func1")
+            def test_func2():
+                print ("in func2")
+                assert 0
+        """)
+        result = testdir.runpytest(p)
+        s = result.stdout.str()
+        assert "in func1" not in s
+        assert "in func2" in s
+
+    def test_teardown_capturing(self, testdir):
+        p = testdir.makepyfile("""
+            def setup_function(function):
+                print ("setup func1")
+            def teardown_function(function):
+                print ("teardown func1")
+                assert 0
+            def test_func1():
+                print ("in func1")
+                pass
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            '*teardown_function*',
+            '*Captured stdout*',
+            "setup func1*",
+            "in func1*",
+            "teardown func1*",
+            # "*1 fixture failure*"
+        ])
+
+    def test_teardown_capturing_final(self, testdir):
+        p = testdir.makepyfile("""
+            def teardown_module(mod):
+                print ("teardown module")
+                assert 0
+            def test_func():
+                pass
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*def teardown_module(mod):*",
+            "*Captured stdout*",
+            "*teardown module*",
+            "*1 error*",
+        ])
+
+    def test_capturing_outerr(self, testdir):
+        p1 = testdir.makepyfile("""
+            import sys
+            def test_capturing():
+                print (42)
+                sys.stderr.write(str(23))
+            def test_capturing_error():
+                print (1)
+                sys.stderr.write(str(2))
+                raise ValueError
+        """)
+        result = testdir.runpytest(p1)
+        result.stdout.fnmatch_lines([
+            "*test_capturing_outerr.py .F*",
+            "====* FAILURES *====",
+            "____*____",
+            "*test_capturing_outerr.py:8: ValueError",
+            "*--- Captured stdout *call*",
+            "1",
+            "*--- Captured stderr *call*",
+            "2",
+        ])
+
+
+class TestLoggingInteraction(object):
+    def test_logging_stream_ownership(self, testdir):
+        p = testdir.makepyfile("""
+            def test_logging():
+                import logging
+                import pytest
+                stream = capture.CaptureIO()
+                logging.basicConfig(stream=stream)
+                stream.close() # to free memory/release resources
+        """)
+        result = testdir.runpytest_subprocess(p)
+        assert result.stderr.str().find("atexit") == -1
+
+    def test_logging_and_immediate_setupteardown(self, testdir):
+        p = testdir.makepyfile("""
+            import logging
+            def setup_function(function):
+                logging.warn("hello1")
+
+            def test_logging():
+                logging.warn("hello2")
+                assert 0
+
+            def teardown_function(function):
+                logging.warn("hello3")
+                assert 0
+        """)
+        for optargs in (('--capture=sys',), ('--capture=fd',)):
+            print(optargs)
+            result = testdir.runpytest_subprocess(p, *optargs)
+            s = result.stdout.str()
+            result.stdout.fnmatch_lines([
+                "*WARN*hello3",  # errors show first!
+                "*WARN*hello1",
+                "*WARN*hello2",
+            ])
+            # verify proper termination
+            assert "closed" not in s
+
+    def test_logging_and_crossscope_fixtures(self, testdir):
+        p = testdir.makepyfile("""
+            import logging
+            def setup_module(function):
+                logging.warn("hello1")
+
+            def test_logging():
+                logging.warn("hello2")
+                assert 0
+
+            def teardown_module(function):
+                logging.warn("hello3")
+                assert 0
+        """)
+        for optargs in (('--capture=sys',), ('--capture=fd',)):
+            print(optargs)
+            result = testdir.runpytest_subprocess(p, *optargs)
+            s = result.stdout.str()
+            result.stdout.fnmatch_lines([
+                "*WARN*hello3",  # errors come first
+                "*WARN*hello1",
+                "*WARN*hello2",
+            ])
+            # verify proper termination
+            assert "closed" not in s
+
+    def test_conftestlogging_is_shown(self, testdir):
+        testdir.makeconftest("""
+                import logging
+                logging.basicConfig()
+                logging.warn("hello435")
+        """)
+        # make sure that logging is still captured in tests
+        result = testdir.runpytest_subprocess("-s", "-p", "no:capturelog")
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        result.stderr.fnmatch_lines([
+            "WARNING*hello435*",
+        ])
+        assert 'operation on closed file' not in result.stderr.str()
+
+    def test_conftestlogging_and_test_logging(self, testdir):
+        testdir.makeconftest("""
+                import logging
+                logging.basicConfig()
+        """)
+        # make sure that logging is still captured in tests
+        p = testdir.makepyfile("""
+            def test_hello():
+                import logging
+                logging.warn("hello433")
+                assert 0
+        """)
+        result = testdir.runpytest_subprocess(p, "-p", "no:capturelog")
+        assert result.ret != 0
+        result.stdout.fnmatch_lines([
+            "WARNING*hello433*",
+        ])
+        assert 'something' not in result.stderr.str()
+        assert 'operation on closed file' not in result.stderr.str()
+
+
+class TestCaptureFixture(object):
+    @pytest.mark.parametrize("opt", [[], ["-s"]])
+    def test_std_functional(self, testdir, opt):
+        reprec = testdir.inline_runsource("""
+            def test_hello(capsys):
+                print (42)
+                out, err = capsys.readouterr()
+                assert out.startswith("42")
+        """, *opt)
+        reprec.assertoutcome(passed=1)
+
+    def test_capsyscapfd(self, testdir):
+        p = testdir.makepyfile("""
+            def test_one(capsys, capfd):
+                pass
+            def test_two(capfd, capsys):
+                pass
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*ERROR*setup*test_one*",
+            "E*capfd*capsys*same*time*",
+            "*ERROR*setup*test_two*",
+            "E*capsys*capfd*same*time*",
+            "*2 error*"])
+
+    def test_capturing_getfixturevalue(self, testdir):
+        """Test that asking for "capfd" and "capsys" using request.getfixturevalue
+        in the same test is an error.
+        """
+        testdir.makepyfile("""
+            def test_one(capsys, request):
+                request.getfixturevalue("capfd")
+            def test_two(capfd, request):
+                request.getfixturevalue("capsys")
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*test_one*",
+            "*capsys*capfd*same*time*",
+            "*test_two*",
+            "*capfd*capsys*same*time*",
+            "*2 failed in*",
+        ])
+
+    def test_capsyscapfdbinary(self, testdir):
+        p = testdir.makepyfile("""
+            def test_one(capsys, capfdbinary):
+                pass
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*ERROR*setup*test_one*",
+            "E*capfdbinary*capsys*same*time*",
+            "*1 error*"])
+
+    @pytest.mark.parametrize("method", ["sys", "fd"])
+    def test_capture_is_represented_on_failure_issue128(self, testdir, method):
+        p = testdir.makepyfile("""
+            def test_hello(cap%s):
+                print ("xxx42xxx")
+                assert 0
+        """ % method)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "xxx42xxx",
+        ])
+
+    @needsosdup
+    def test_stdfd_functional(self, testdir):
+        reprec = testdir.inline_runsource("""
+            def test_hello(capfd):
+                import os
+                os.write(1, "42".encode('ascii'))
+                out, err = capfd.readouterr()
+                assert out.startswith("42")
+                capfd.close()
+        """)
+        reprec.assertoutcome(passed=1)
+
+    @needsosdup
+    def test_capfdbinary(self, testdir):
+        reprec = testdir.inline_runsource("""
+            def test_hello(capfdbinary):
+                import os
+                # some likely un-decodable bytes
+                os.write(1, b'\\xfe\\x98\\x20')
+                out, err = capfdbinary.readouterr()
+                assert out == b'\\xfe\\x98\\x20'
+                assert err == b''
+        """)
+        reprec.assertoutcome(passed=1)
+
+    @pytest.mark.skipif(
+        sys.version_info < (3,),
+        reason='only have capsysbinary in python 3',
+    )
+    def test_capsysbinary(self, testdir):
+        reprec = testdir.inline_runsource("""
+            def test_hello(capsysbinary):
+                import sys
+                # some likely un-decodable bytes
+                sys.stdout.buffer.write(b'\\xfe\\x98\\x20')
+                out, err = capsysbinary.readouterr()
+                assert out == b'\\xfe\\x98\\x20'
+                assert err == b''
+        """)
+        reprec.assertoutcome(passed=1)
+
+    @pytest.mark.skipif(
+        sys.version_info >= (3,),
+        reason='only have capsysbinary in python 3',
+    )
+    def test_capsysbinary_forbidden_in_python2(self, testdir):
+        testdir.makepyfile("""
+            def test_hello(capsysbinary):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*test_hello*",
+            "*capsysbinary is only supported on python 3*",
+            "*1 error in*",
+        ])
+
+    def test_partial_setup_failure(self, testdir):
+        p = testdir.makepyfile("""
+            def test_hello(capsys, missingarg):
+                pass
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*test_partial_setup_failure*",
+            "*1 error*",
+        ])
+
+    @needsosdup
+    def test_keyboardinterrupt_disables_capturing(self, testdir):
+        p = testdir.makepyfile("""
+            def test_hello(capfd):
+                import os
+                os.write(1, str(42).encode('ascii'))
+                raise KeyboardInterrupt()
+        """)
+        result = testdir.runpytest_subprocess(p)
+        result.stdout.fnmatch_lines([
+            "*KeyboardInterrupt*"
+        ])
+        assert result.ret == 2
+
+    @pytest.mark.issue14
+    def test_capture_and_logging(self, testdir):
+        p = testdir.makepyfile("""
+            import logging
+            def test_log(capsys):
+                logging.error('x')
+            """)
+        result = testdir.runpytest_subprocess(p)
+        assert 'closed' not in result.stderr.str()
+
+    @pytest.mark.parametrize('fixture', ['capsys', 'capfd'])
+    @pytest.mark.parametrize('no_capture', [True, False])
+    def test_disabled_capture_fixture(self, testdir, fixture, no_capture):
+        testdir.makepyfile("""
+            def test_disabled({fixture}):
+                print('captured before')
+                with {fixture}.disabled():
+                    print('while capture is disabled')
+                print('captured after')
+                assert {fixture}.readouterr() == ('captured before\\ncaptured after\\n', '')
+
+            def test_normal():
+                print('test_normal executed')
+        """.format(fixture=fixture))
+        args = ('-s',) if no_capture else ()
+        result = testdir.runpytest_subprocess(*args)
+        result.stdout.fnmatch_lines("""
+            *while capture is disabled*
+        """)
+        assert 'captured before' not in result.stdout.str()
+        assert 'captured after' not in result.stdout.str()
+        if no_capture:
+            assert 'test_normal executed' in result.stdout.str()
+        else:
+            assert 'test_normal executed' not in result.stdout.str()
+
+    @pytest.mark.parametrize('fixture', ['capsys', 'capfd'])
+    def test_fixture_use_by_other_fixtures(self, testdir, fixture):
+        """
+        Ensure that capsys and capfd can be used by other fixtures during setup and teardown.
+        """
+        testdir.makepyfile("""
+            from __future__ import print_function
+            import sys
+            import pytest
+
+            @pytest.fixture
+            def captured_print({fixture}):
+                print('stdout contents begin')
+                print('stderr contents begin', file=sys.stderr)
+                out, err = {fixture}.readouterr()
+
+                yield out, err
+
+                print('stdout contents end')
+                print('stderr contents end', file=sys.stderr)
+                out, err = {fixture}.readouterr()
+                assert out == 'stdout contents end\\n'
+                assert err == 'stderr contents end\\n'
+
+            def test_captured_print(captured_print):
+                out, err = captured_print
+                assert out == 'stdout contents begin\\n'
+                assert err == 'stderr contents begin\\n'
+        """.format(fixture=fixture))
+        result = testdir.runpytest_subprocess()
+        result.stdout.fnmatch_lines("*1 passed*")
+        assert 'stdout contents begin' not in result.stdout.str()
+        assert 'stderr contents begin' not in result.stdout.str()
+
+
+def test_setup_failure_does_not_kill_capturing(testdir):
+    sub1 = testdir.mkpydir("sub1")
+    sub1.join("conftest.py").write(_pytest._code.Source("""
+        def pytest_runtest_setup(item):
+            raise ValueError(42)
+    """))
+    sub1.join("test_mod.py").write("def test_func1(): pass")
+    result = testdir.runpytest(testdir.tmpdir, '--traceconfig')
+    result.stdout.fnmatch_lines([
+        "*ValueError(42)*",
+        "*1 error*"
+    ])
+
+
+def test_fdfuncarg_skips_on_no_osdup(testdir):
+    testdir.makepyfile("""
+        import os
+        if hasattr(os, 'dup'):
+            del os.dup
+        def test_hello(capfd):
+            pass
+    """)
+    result = testdir.runpytest_subprocess("--capture=no")
+    result.stdout.fnmatch_lines([
+        "*1 skipped*"
+    ])
+
+
+def test_capture_conftest_runtest_setup(testdir):
+    testdir.makeconftest("""
+        def pytest_runtest_setup():
+            print ("hello19")
+    """)
+    testdir.makepyfile("def test_func(): pass")
+    result = testdir.runpytest()
+    assert result.ret == 0
+    assert 'hello19' not in result.stdout.str()
+
+
+def test_capture_badoutput_issue412(testdir):
+    testdir.makepyfile("""
+        import os
+
+        def test_func():
+            omg = bytearray([1,129,1])
+            os.write(1, omg)
+            assert 0
+        """)
+    result = testdir.runpytest('--cap=fd')
+    result.stdout.fnmatch_lines('''
+        *def test_func*
+        *assert 0*
+        *Captured*
+        *1 failed*
+    ''')
+
+
+def test_capture_early_option_parsing(testdir):
+    testdir.makeconftest("""
+        def pytest_runtest_setup():
+            print ("hello19")
+    """)
+    testdir.makepyfile("def test_func(): pass")
+    result = testdir.runpytest("-vs")
+    assert result.ret == 0
+    assert 'hello19' in result.stdout.str()
+
+
+def test_capture_binary_output(testdir):
+    testdir.makepyfile(r"""
+        import pytest
+
+        def test_a():
+            import sys
+            import subprocess
+            subprocess.call([sys.executable, __file__])
+
+        def test_foo():
+            import os;os.write(1, b'\xc3')
+
+        if __name__ == '__main__':
+            test_foo()
+        """)
+    result = testdir.runpytest('--assert=plain')
+    result.assert_outcomes(passed=2)
+
+
+def test_error_during_readouterr(testdir):
+    """Make sure we suspend capturing if errors occur during readouterr"""
+    testdir.makepyfile(pytest_xyz="""
+        from _pytest.capture import FDCapture
+        def bad_snap(self):
+            raise Exception('boom')
+        assert FDCapture.snap
+        FDCapture.snap = bad_snap
+    """)
+    result = testdir.runpytest_subprocess(
+        "-p", "pytest_xyz", "--version", syspathinsert=True
+    )
+    result.stderr.fnmatch_lines([
+        "*in bad_snap",
+        "    raise Exception('boom')",
+        "Exception: boom",
+    ])
+
+
+class TestCaptureIO(object):
+    def test_text(self):
+        f = capture.CaptureIO()
+        f.write("hello")
+        s = f.getvalue()
+        assert s == "hello"
+        f.close()
+
+    def test_unicode_and_str_mixture(self):
+        f = capture.CaptureIO()
+        if sys.version_info >= (3, 0):
+            f.write("\u00f6")
+            pytest.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))")
+        else:
+            f.write(unicode("\u00f6", 'UTF-8'))
+            f.write("hello")  # bytes
+            s = f.getvalue()
+            f.close()
+            assert isinstance(s, unicode)
+
+    @pytest.mark.skipif(
+        sys.version_info[0] == 2,
+        reason='python 3 only behaviour',
+    )
+    def test_write_bytes_to_buffer(self):
+        """In python3, stdout / stderr are text io wrappers (exposing a buffer
+        property of the underlying bytestream).  See issue #1407
+        """
+        f = capture.CaptureIO()
+        f.buffer.write(b'foo\r\n')
+        assert f.getvalue() == 'foo\r\n'
+
+
+def test_bytes_io():
+    f = py.io.BytesIO()
+    f.write(tobytes("hello"))
+    pytest.raises(TypeError, "f.write(totext('hello'))")
+    s = f.getvalue()
+    assert s == tobytes("hello")
+
+
+def test_dontreadfrominput():
+    from _pytest.capture import DontReadFromInput
+    f = DontReadFromInput()
+    assert not f.isatty()
+    pytest.raises(IOError, f.read)
+    pytest.raises(IOError, f.readlines)
+    pytest.raises(IOError, iter, f)
+    pytest.raises(UnsupportedOperation, f.fileno)
+    f.close()  # just for completeness
+
+
+@pytest.mark.skipif('sys.version_info < (3,)', reason='python2 has no buffer')
+def test_dontreadfrominput_buffer_python3():
+    from _pytest.capture import DontReadFromInput
+    f = DontReadFromInput()
+    fb = f.buffer
+    assert not fb.isatty()
+    pytest.raises(IOError, fb.read)
+    pytest.raises(IOError, fb.readlines)
+    pytest.raises(IOError, iter, fb)
+    pytest.raises(ValueError, fb.fileno)
+    f.close()  # just for completeness
+
+
+@pytest.mark.skipif('sys.version_info >= (3,)', reason='python2 has no buffer')
+def test_dontreadfrominput_buffer_python2():
+    from _pytest.capture import DontReadFromInput
+    f = DontReadFromInput()
+    with pytest.raises(AttributeError):
+        f.buffer
+    f.close()  # just for completeness
+
+
+@pytest.yield_fixture
+def tmpfile(testdir):
+    f = testdir.makepyfile("").open('wb+')
+    yield f
+    if not f.closed:
+        f.close()
+
+
+@needsosdup
+def test_dupfile(tmpfile):
+    flist = []
+    for i in range(5):
+        nf = capture.safe_text_dupfile(tmpfile, "wb")
+        assert nf != tmpfile
+        assert nf.fileno() != tmpfile.fileno()
+        assert nf not in flist
+        print(i, end="", file=nf)
+        flist.append(nf)
+
+    fname_open = flist[0].name
+    assert fname_open == repr(flist[0].buffer)
+
+    for i in range(5):
+        f = flist[i]
+        f.close()
+    fname_closed = flist[0].name
+    assert fname_closed == repr(flist[0].buffer)
+    assert fname_closed != fname_open
+    tmpfile.seek(0)
+    s = tmpfile.read()
+    assert "01234" in repr(s)
+    tmpfile.close()
+    assert fname_closed == repr(flist[0].buffer)
+
+
+def test_dupfile_on_bytesio():
+    io = py.io.BytesIO()
+    f = capture.safe_text_dupfile(io, "wb")
+    f.write("hello")
+    assert io.getvalue() == b"hello"
+    assert 'BytesIO object' in f.name
+
+
+def test_dupfile_on_textio():
+    io = py.io.TextIO()
+    f = capture.safe_text_dupfile(io, "wb")
+    f.write("hello")
+    assert io.getvalue() == "hello"
+    assert not hasattr(f, 'name')
+
+
+@contextlib.contextmanager
+def lsof_check():
+    pid = os.getpid()
+    try:
+        out = py.process.cmdexec("lsof -p %d" % pid)
+    except (py.process.cmdexec.Error, UnicodeDecodeError):
+        # about UnicodeDecodeError, see note on pytester
+        pytest.skip("could not run 'lsof'")
+    yield
+    out2 = py.process.cmdexec("lsof -p %d" % pid)
+    len1 = len([x for x in out.split("\n") if "REG" in x])
+    len2 = len([x for x in out2.split("\n") if "REG" in x])
+    assert len2 < len1 + 3, out2
+
+
+class TestFDCapture(object):
+    pytestmark = needsosdup
+
+    def test_simple(self, tmpfile):
+        fd = tmpfile.fileno()
+        cap = capture.FDCapture(fd)
+        data = tobytes("hello")
+        os.write(fd, data)
+        s = cap.snap()
+        cap.done()
+        assert not s
+        cap = capture.FDCapture(fd)
+        cap.start()
+        os.write(fd, data)
+        s = cap.snap()
+        cap.done()
+        assert s == "hello"
+
+    def test_simple_many(self, tmpfile):
+        for i in range(10):
+            self.test_simple(tmpfile)
+
+    def test_simple_many_check_open_files(self, testdir):
+        with lsof_check():
+            with testdir.makepyfile("").open('wb+') as tmpfile:
+                self.test_simple_many(tmpfile)
+
+    def test_simple_fail_second_start(self, tmpfile):
+        fd = tmpfile.fileno()
+        cap = capture.FDCapture(fd)
+        cap.done()
+        pytest.raises(ValueError, cap.start)
+
+    def test_stderr(self):
+        cap = capture.FDCapture(2)
+        cap.start()
+        print("hello", file=sys.stderr)
+        s = cap.snap()
+        cap.done()
+        assert s == "hello\n"
+
+    def test_stdin(self, tmpfile):
+        cap = capture.FDCapture(0)
+        cap.start()
+        x = os.read(0, 100).strip()
+        cap.done()
+        assert x == tobytes('')
+
+    def test_writeorg(self, tmpfile):
+        data1, data2 = tobytes("foo"), tobytes("bar")
+        cap = capture.FDCapture(tmpfile.fileno())
+        cap.start()
+        tmpfile.write(data1)
+        tmpfile.flush()
+        cap.writeorg(data2)
+        scap = cap.snap()
+        cap.done()
+        assert scap == totext(data1)
+        with open(tmpfile.name, 'rb') as stmp_file:
+            stmp = stmp_file.read()
+            assert stmp == data2
+
+    def test_simple_resume_suspend(self, tmpfile):
+        with saved_fd(1):
+            cap = capture.FDCapture(1)
+            cap.start()
+            data = tobytes("hello")
+            os.write(1, data)
+            sys.stdout.write("whatever")
+            s = cap.snap()
+            assert s == "hellowhatever"
+            cap.suspend()
+            os.write(1, tobytes("world"))
+            sys.stdout.write("qlwkej")
+            assert not cap.snap()
+            cap.resume()
+            os.write(1, tobytes("but now"))
+            sys.stdout.write(" yes\n")
+            s = cap.snap()
+            assert s == "but now yes\n"
+            cap.suspend()
+            cap.done()
+            pytest.raises(AttributeError, cap.suspend)
+
+
+@contextlib.contextmanager
+def saved_fd(fd):
+    new_fd = os.dup(fd)
+    try:
+        yield
+    finally:
+        os.dup2(new_fd, fd)
+        os.close(new_fd)
+
+
+class TestStdCapture(object):
+    captureclass = staticmethod(StdCapture)
+
+    @contextlib.contextmanager
+    def getcapture(self, **kw):
+        cap = self.__class__.captureclass(**kw)
+        cap.start_capturing()
+        try:
+            yield cap
+        finally:
+            cap.stop_capturing()
+
+    def test_capturing_done_simple(self):
+        with self.getcapture() as cap:
+            sys.stdout.write("hello")
+            sys.stderr.write("world")
+            out, err = cap.readouterr()
+        assert out == "hello"
+        assert err == "world"
+
+    def test_capturing_reset_simple(self):
+        with self.getcapture() as cap:
+            print("hello world")
+            sys.stderr.write("hello error\n")
+            out, err = cap.readouterr()
+        assert out == "hello world\n"
+        assert err == "hello error\n"
+
+    def test_capturing_readouterr(self):
+        with self.getcapture() as cap:
+            print("hello world")
+            sys.stderr.write("hello error\n")
+            out, err = cap.readouterr()
+            assert out == "hello world\n"
+            assert err == "hello error\n"
+            sys.stderr.write("error2")
+            out, err = cap.readouterr()
+        assert err == "error2"
+
+    def test_capture_results_accessible_by_attribute(self):
+        with self.getcapture() as cap:
+            sys.stdout.write("hello")
+            sys.stderr.write("world")
+            capture_result = cap.readouterr()
+        assert capture_result.out == "hello"
+        assert capture_result.err == "world"
+
+    def test_capturing_readouterr_unicode(self):
+        with self.getcapture() as cap:
+            print("hx\xc4\x85\xc4\x87")
+            out, err = cap.readouterr()
+        assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8")
+
+    @pytest.mark.skipif('sys.version_info >= (3,)',
+                        reason='text output different for bytes on python3')
+    def test_capturing_readouterr_decode_error_handling(self):
+        with self.getcapture() as cap:
+            # triggered a internal error in pytest
+            print('\xa6')
+            out, err = cap.readouterr()
+        assert out == py.builtin._totext('\ufffd\n', 'unicode-escape')
+
+    def test_reset_twice_error(self):
+        with self.getcapture() as cap:
+            print("hello")
+            out, err = cap.readouterr()
+        pytest.raises(ValueError, cap.stop_capturing)
+        assert out == "hello\n"
+        assert not err
+
+    def test_capturing_modify_sysouterr_in_between(self):
+        oldout = sys.stdout
+        olderr = sys.stderr
+        with self.getcapture() as cap:
+            sys.stdout.write("hello")
+            sys.stderr.write("world")
+            sys.stdout = capture.CaptureIO()
+            sys.stderr = capture.CaptureIO()
+            print("not seen")
+            sys.stderr.write("not seen\n")
+            out, err = cap.readouterr()
+        assert out == "hello"
+        assert err == "world"
+        assert sys.stdout == oldout
+        assert sys.stderr == olderr
+
+    def test_capturing_error_recursive(self):
+        with self.getcapture() as cap1:
+            print("cap1")
+            with self.getcapture() as cap2:
+                print("cap2")
+                out2, err2 = cap2.readouterr()
+                out1, err1 = cap1.readouterr()
+        assert out1 == "cap1\n"
+        assert out2 == "cap2\n"
+
+    def test_just_out_capture(self):
+        with self.getcapture(out=True, err=False) as cap:
+            sys.stdout.write("hello")
+            sys.stderr.write("world")
+            out, err = cap.readouterr()
+        assert out == "hello"
+        assert not err
+
+    def test_just_err_capture(self):
+        with self.getcapture(out=False, err=True) as cap:
+            sys.stdout.write("hello")
+            sys.stderr.write("world")
+            out, err = cap.readouterr()
+        assert err == "world"
+        assert not out
+
+    def test_stdin_restored(self):
+        old = sys.stdin
+        with self.getcapture(in_=True):
+            newstdin = sys.stdin
+        assert newstdin != sys.stdin
+        assert sys.stdin is old
+
+    def test_stdin_nulled_by_default(self):
+        print("XXX this test may well hang instead of crashing")
+        print("XXX which indicates an error in the underlying capturing")
+        print("XXX mechanisms")
+        with self.getcapture():
+            pytest.raises(IOError, "sys.stdin.read()")
+
+
+class TestStdCaptureFD(TestStdCapture):
+    pytestmark = needsosdup
+    captureclass = staticmethod(StdCaptureFD)
+
+    def test_simple_only_fd(self, testdir):
+        testdir.makepyfile("""
+            import os
+            def test_x():
+                os.write(1, "hello\\n".encode("ascii"))
+                assert 0
+        """)
+        result = testdir.runpytest_subprocess()
+        result.stdout.fnmatch_lines("""
+            *test_x*
+            *assert 0*
+            *Captured stdout*
+        """)
+
+    def test_intermingling(self):
+        with self.getcapture() as cap:
+            oswritebytes(1, "1")
+            sys.stdout.write(str(2))
+            sys.stdout.flush()
+            oswritebytes(1, "3")
+            oswritebytes(2, "a")
+            sys.stderr.write("b")
+            sys.stderr.flush()
+            oswritebytes(2, "c")
+            out, err = cap.readouterr()
+        assert out == "123"
+        assert err == "abc"
+
+    def test_many(self, capfd):
+        with lsof_check():
+            for i in range(10):
+                cap = StdCaptureFD()
+                cap.stop_capturing()
+
+
+class TestStdCaptureFDinvalidFD(object):
+    pytestmark = needsosdup
+
+    def test_stdcapture_fd_invalid_fd(self, testdir):
+        testdir.makepyfile("""
+            import os
+            from _pytest import capture
+            def StdCaptureFD(out=True, err=True, in_=True):
+                return capture.MultiCapture(out, err, in_,
+                                              Capture=capture.FDCapture)
+            def test_stdout():
+                os.close(1)
+                cap = StdCaptureFD(out=True, err=False, in_=False)
+                cap.stop_capturing()
+            def test_stderr():
+                os.close(2)
+                cap = StdCaptureFD(out=False, err=True, in_=False)
+                cap.stop_capturing()
+            def test_stdin():
+                os.close(0)
+                cap = StdCaptureFD(out=False, err=False, in_=True)
+                cap.stop_capturing()
+        """)
+        result = testdir.runpytest_subprocess("--capture=fd")
+        assert result.ret == 0
+        assert result.parseoutcomes()['passed'] == 3
+
+
+def test_capture_not_started_but_reset():
+    capsys = StdCapture()
+    capsys.stop_capturing()
+
+
+def test_using_capsys_fixture_works_with_sys_stdout_encoding(capsys):
+    test_text = 'test text'
+
+    print(test_text.encode(sys.stdout.encoding, 'replace'))
+    (out, err) = capsys.readouterr()
+    assert out
+    assert err == ''
+
+
+def test_capsys_results_accessible_by_attribute(capsys):
+    sys.stdout.write("spam")
+    sys.stderr.write("eggs")
+    capture_result = capsys.readouterr()
+    assert capture_result.out == "spam"
+    assert capture_result.err == "eggs"
+
+
+@needsosdup
+@pytest.mark.parametrize('use', [True, False])
+def test_fdcapture_tmpfile_remains_the_same(tmpfile, use):
+    if not use:
+        tmpfile = True
+    cap = StdCaptureFD(out=False, err=tmpfile)
+    try:
+        cap.start_capturing()
+        capfile = cap.err.tmpfile
+        cap.readouterr()
+    finally:
+        cap.stop_capturing()
+    capfile2 = cap.err.tmpfile
+    assert capfile2 == capfile
+
+
+@needsosdup
+def test_close_and_capture_again(testdir):
+    testdir.makepyfile("""
+        import os
+        def test_close():
+            os.close(1)
+        def test_capture_again():
+            os.write(1, b"hello\\n")
+            assert 0
+    """)
+    result = testdir.runpytest_subprocess()
+    result.stdout.fnmatch_lines("""
+        *test_capture_again*
+        *assert 0*
+        *stdout*
+        *hello*
+    """)
+
+
+@pytest.mark.parametrize('method', ['SysCapture', 'FDCapture'])
+def test_capturing_and_logging_fundamentals(testdir, method):
+    if method == "StdCaptureFD" and not hasattr(os, 'dup'):
+        pytest.skip("need os.dup")
+    # here we check a fundamental feature
+    p = testdir.makepyfile("""
+        import sys, os
+        import py, logging
+        from _pytest import capture
+        cap = capture.MultiCapture(out=False, in_=False,
+                                     Capture=capture.%s)
+        cap.start_capturing()
+
+        logging.warn("hello1")
+        outerr = cap.readouterr()
+        print ("suspend, captured %%s" %%(outerr,))
+        logging.warn("hello2")
+
+        cap.pop_outerr_to_orig()
+        logging.warn("hello3")
+
+        outerr = cap.readouterr()
+        print ("suspend2, captured %%s" %% (outerr,))
+    """ % (method,))
+    result = testdir.runpython(p)
+    result.stdout.fnmatch_lines("""
+        suspend, captured*hello1*
+        suspend2, captured*WARNING:root:hello3*
+    """)
+    result.stderr.fnmatch_lines("""
+        WARNING:root:hello2
+    """)
+    assert "atexit" not in result.stderr.str()
+
+
+def test_error_attribute_issue555(testdir):
+    testdir.makepyfile("""
+        import sys
+        def test_capattr():
+            assert sys.stdout.errors == "strict"
+            assert sys.stderr.errors == "strict"
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+@pytest.mark.skipif(not sys.platform.startswith('win') and sys.version_info[:2] >= (3, 6),
+                    reason='only py3.6+ on windows')
+def test_py36_windowsconsoleio_workaround_non_standard_streams():
+    """
+    Ensure _py36_windowsconsoleio_workaround function works with objects that
+    do not implement the full ``io``-based stream protocol, for example execnet channels (#2666).
+    """
+    from _pytest.capture import _py36_windowsconsoleio_workaround
+
+    class DummyStream:
+        def write(self, s):
+            pass
+
+    stream = DummyStream()
+    _py36_windowsconsoleio_workaround(stream)
+
+
+def test_dontreadfrominput_has_encoding(testdir):
+    testdir.makepyfile("""
+        import sys
+        def test_capattr():
+            # should not raise AttributeError
+            assert sys.stdout.encoding
+            assert sys.stderr.encoding
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+def test_pickling_and_unpickling_encoded_file():
+    # See https://bitbucket.org/pytest-dev/pytest/pull-request/194
+    # pickle.loads() raises infinite recursion if
+    # EncodedFile.__getattr__ is not implemented properly
+    ef = capture.EncodedFile(None, None)
+    ef_as_str = pickle.dumps(ef)
+    pickle.loads(ef_as_str)
diff --git a/tools/third_party/pytest/testing/test_collection.py b/tools/third_party/pytest/testing/test_collection.py
new file mode 100644
index 0000000..563ed04
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_collection.py
@@ -0,0 +1,857 @@
+from __future__ import absolute_import, division, print_function
+import pytest
+import py
+
+import _pytest._code
+from _pytest.main import Session, EXIT_NOTESTSCOLLECTED, _in_venv
+
+
+class TestCollector(object):
+    def test_collect_versus_item(self):
+        from pytest import Collector, Item
+        assert not issubclass(Collector, Item)
+        assert not issubclass(Item, Collector)
+
+    def test_compat_attributes(self, testdir, recwarn):
+        modcol = testdir.getmodulecol("""
+            def test_pass(): pass
+            def test_fail(): assert 0
+        """)
+        recwarn.clear()
+        assert modcol.Module == pytest.Module
+        assert modcol.Class == pytest.Class
+        assert modcol.Item == pytest.Item
+        assert modcol.File == pytest.File
+        assert modcol.Function == pytest.Function
+
+    def test_check_equality(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def test_pass(): pass
+            def test_fail(): assert 0
+        """)
+        fn1 = testdir.collect_by_name(modcol, "test_pass")
+        assert isinstance(fn1, pytest.Function)
+        fn2 = testdir.collect_by_name(modcol, "test_pass")
+        assert isinstance(fn2, pytest.Function)
+
+        assert fn1 == fn2
+        assert fn1 != modcol
+        if py.std.sys.version_info < (3, 0):
+            assert cmp(fn1, fn2) == 0
+        assert hash(fn1) == hash(fn2)
+
+        fn3 = testdir.collect_by_name(modcol, "test_fail")
+        assert isinstance(fn3, pytest.Function)
+        assert not (fn1 == fn3)
+        assert fn1 != fn3
+
+        for fn in fn1, fn2, fn3:
+            assert fn != 3
+            assert fn != modcol
+            assert fn != [1, 2, 3]
+            assert [1, 2, 3] != fn
+            assert modcol != fn
+
+    def test_getparent(self, testdir):
+        modcol = testdir.getmodulecol("""
+            class TestClass(object):
+                 def test_foo():
+                     pass
+        """)
+        cls = testdir.collect_by_name(modcol, "TestClass")
+        fn = testdir.collect_by_name(
+            testdir.collect_by_name(cls, "()"), "test_foo")
+
+        parent = fn.getparent(pytest.Module)
+        assert parent is modcol
+
+        parent = fn.getparent(pytest.Function)
+        assert parent is fn
+
+        parent = fn.getparent(pytest.Class)
+        assert parent is cls
+
+    def test_getcustomfile_roundtrip(self, testdir):
+        hello = testdir.makefile(".xxx", hello="world")
+        testdir.makepyfile(conftest="""
+            import pytest
+            class CustomFile(pytest.File):
+                pass
+            def pytest_collect_file(path, parent):
+                if path.ext == ".xxx":
+                    return CustomFile(path, parent=parent)
+        """)
+        node = testdir.getpathnode(hello)
+        assert isinstance(node, pytest.File)
+        assert node.name == "hello.xxx"
+        nodes = node.session.perform_collect([node.nodeid], genitems=False)
+        assert len(nodes) == 1
+        assert isinstance(nodes[0], pytest.File)
+
+    def test_can_skip_class_with_test_attr(self, testdir):
+        """Assure test class is skipped when using `__test__=False` (See #2007)."""
+        testdir.makepyfile("""
+            class TestFoo(object):
+                __test__ = False
+                def __init__(self):
+                    pass
+                def test_foo():
+                    assert True
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            'collected 0 items',
+            '*no tests ran in*',
+        ])
+
+
+class TestCollectFS(object):
+    def test_ignored_certain_directories(self, testdir):
+        tmpdir = testdir.tmpdir
+        tmpdir.ensure("build", 'test_notfound.py')
+        tmpdir.ensure("dist", 'test_notfound.py')
+        tmpdir.ensure("_darcs", 'test_notfound.py')
+        tmpdir.ensure("CVS", 'test_notfound.py')
+        tmpdir.ensure("{arch}", 'test_notfound.py')
+        tmpdir.ensure(".whatever", 'test_notfound.py')
+        tmpdir.ensure(".bzr", 'test_notfound.py')
+        tmpdir.ensure("normal", 'test_found.py')
+        for x in tmpdir.visit("test_*.py"):
+            x.write("def test_hello(): pass")
+
+        result = testdir.runpytest("--collect-only")
+        s = result.stdout.str()
+        assert "test_notfound" not in s
+        assert "test_found" in s
+
+    @pytest.mark.parametrize('fname',
+                             ("activate", "activate.csh", "activate.fish",
+                              "Activate", "Activate.bat", "Activate.ps1"))
+    def test_ignored_virtualenvs(self, testdir, fname):
+        bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin"
+        testdir.tmpdir.ensure("virtual", bindir, fname)
+        testfile = testdir.tmpdir.ensure("virtual", "test_invenv.py")
+        testfile.write("def test_hello(): pass")
+
+        # by default, ignore tests inside a virtualenv
+        result = testdir.runpytest()
+        assert "test_invenv" not in result.stdout.str()
+        # allow test collection if user insists
+        result = testdir.runpytest("--collect-in-virtualenv")
+        assert "test_invenv" in result.stdout.str()
+        # allow test collection if user directly passes in the directory
+        result = testdir.runpytest("virtual")
+        assert "test_invenv" in result.stdout.str()
+
+    @pytest.mark.parametrize('fname',
+                             ("activate", "activate.csh", "activate.fish",
+                              "Activate", "Activate.bat", "Activate.ps1"))
+    def test_ignored_virtualenvs_norecursedirs_precedence(self, testdir, fname):
+        bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin"
+        # norecursedirs takes priority
+        testdir.tmpdir.ensure(".virtual", bindir, fname)
+        testfile = testdir.tmpdir.ensure(".virtual", "test_invenv.py")
+        testfile.write("def test_hello(): pass")
+        result = testdir.runpytest("--collect-in-virtualenv")
+        assert "test_invenv" not in result.stdout.str()
+        # ...unless the virtualenv is explicitly given on the CLI
+        result = testdir.runpytest("--collect-in-virtualenv", ".virtual")
+        assert "test_invenv" in result.stdout.str()
+
+    @pytest.mark.parametrize('fname',
+                             ("activate", "activate.csh", "activate.fish",
+                              "Activate", "Activate.bat", "Activate.ps1"))
+    def test__in_venv(self, testdir, fname):
+        """Directly test the virtual env detection function"""
+        bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin"
+        # no bin/activate, not a virtualenv
+        base_path = testdir.tmpdir.mkdir('venv')
+        assert _in_venv(base_path) is False
+        # with bin/activate, totally a virtualenv
+        base_path.ensure(bindir, fname)
+        assert _in_venv(base_path) is True
+
+    def test_custom_norecursedirs(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            norecursedirs = mydir xyz*
+        """)
+        tmpdir = testdir.tmpdir
+        tmpdir.ensure("mydir", "test_hello.py").write("def test_1(): pass")
+        tmpdir.ensure("xyz123", "test_2.py").write("def test_2(): 0/0")
+        tmpdir.ensure("xy", "test_ok.py").write("def test_3(): pass")
+        rec = testdir.inline_run()
+        rec.assertoutcome(passed=1)
+        rec = testdir.inline_run("xyz123/test_2.py")
+        rec.assertoutcome(failed=1)
+
+    def test_testpaths_ini(self, testdir, monkeypatch):
+        testdir.makeini("""
+            [pytest]
+            testpaths = gui uts
+        """)
+        tmpdir = testdir.tmpdir
+        tmpdir.ensure("env", "test_1.py").write("def test_env(): pass")
+        tmpdir.ensure("gui", "test_2.py").write("def test_gui(): pass")
+        tmpdir.ensure("uts", "test_3.py").write("def test_uts(): pass")
+
+        # executing from rootdir only tests from `testpaths` directories
+        # are collected
+        items, reprec = testdir.inline_genitems('-v')
+        assert [x.name for x in items] == ['test_gui', 'test_uts']
+
+        # check that explicitly passing directories in the command-line
+        # collects the tests
+        for dirname in ('env', 'gui', 'uts'):
+            items, reprec = testdir.inline_genitems(tmpdir.join(dirname))
+            assert [x.name for x in items] == ['test_%s' % dirname]
+
+        # changing cwd to each subdirectory and running pytest without
+        # arguments collects the tests in that directory normally
+        for dirname in ('env', 'gui', 'uts'):
+            monkeypatch.chdir(testdir.tmpdir.join(dirname))
+            items, reprec = testdir.inline_genitems()
+            assert [x.name for x in items] == ['test_%s' % dirname]
+
+
+class TestCollectPluginHookRelay(object):
+    def test_pytest_collect_file(self, testdir):
+        wascalled = []
+
+        class Plugin(object):
+            def pytest_collect_file(self, path, parent):
+                if not path.basename.startswith("."):
+                    # Ignore hidden files, e.g. .testmondata.
+                    wascalled.append(path)
+
+        testdir.makefile(".abc", "xyz")
+        pytest.main([testdir.tmpdir], plugins=[Plugin()])
+        assert len(wascalled) == 1
+        assert wascalled[0].ext == '.abc'
+
+    def test_pytest_collect_directory(self, testdir):
+        wascalled = []
+
+        class Plugin(object):
+            def pytest_collect_directory(self, path, parent):
+                wascalled.append(path.basename)
+
+        testdir.mkdir("hello")
+        testdir.mkdir("world")
+        pytest.main(testdir.tmpdir, plugins=[Plugin()])
+        assert "hello" in wascalled
+        assert "world" in wascalled
+
+
+class TestPrunetraceback(object):
+
+    def test_custom_repr_failure(self, testdir):
+        p = testdir.makepyfile("""
+            import not_exists
+        """)
+        testdir.makeconftest("""
+            import pytest
+            def pytest_collect_file(path, parent):
+                return MyFile(path, parent)
+            class MyError(Exception):
+                pass
+            class MyFile(pytest.File):
+                def collect(self):
+                    raise MyError()
+                def repr_failure(self, excinfo):
+                    if excinfo.errisinstance(MyError):
+                        return "hello world"
+                    return pytest.File.repr_failure(self, excinfo)
+        """)
+
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*ERROR collecting*",
+            "*hello world*",
+        ])
+
+    @pytest.mark.xfail(reason="other mechanism for adding to reporting needed")
+    def test_collect_report_postprocessing(self, testdir):
+        p = testdir.makepyfile("""
+            import not_exists
+        """)
+        testdir.makeconftest("""
+            import pytest
+            @pytest.hookimpl(hookwrapper=True)
+            def pytest_make_collect_report():
+                outcome = yield
+                rep = outcome.get_result()
+                rep.headerlines += ["header1"]
+                outcome.force_result(rep)
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*ERROR collecting*",
+            "*header1*",
+        ])
+
+
+class TestCustomConftests(object):
+    def test_ignore_collect_path(self, testdir):
+        testdir.makeconftest("""
+            def pytest_ignore_collect(path, config):
+                return path.basename.startswith("x") or \
+                       path.basename == "test_one.py"
+        """)
+        sub = testdir.mkdir("xy123")
+        sub.ensure("test_hello.py").write("syntax error")
+        sub.join("conftest.py").write("syntax error")
+        testdir.makepyfile("def test_hello(): pass")
+        testdir.makepyfile(test_one="syntax error")
+        result = testdir.runpytest("--fulltrace")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_ignore_collect_not_called_on_argument(self, testdir):
+        testdir.makeconftest("""
+            def pytest_ignore_collect(path, config):
+                return True
+        """)
+        p = testdir.makepyfile("def test_hello(): pass")
+        result = testdir.runpytest(p)
+        assert result.ret == 0
+        result.stdout.fnmatch_lines("*1 passed*")
+        result = testdir.runpytest()
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        result.stdout.fnmatch_lines("*collected 0 items*")
+
+    def test_collectignore_exclude_on_option(self, testdir):
+        testdir.makeconftest("""
+            collect_ignore = ['hello', 'test_world.py']
+            def pytest_addoption(parser):
+                parser.addoption("--XX", action="store_true", default=False)
+            def pytest_configure(config):
+                if config.getvalue("XX"):
+                    collect_ignore[:] = []
+        """)
+        testdir.mkdir("hello")
+        testdir.makepyfile(test_world="def test_hello(): pass")
+        result = testdir.runpytest()
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        assert "passed" not in result.stdout.str()
+        result = testdir.runpytest("--XX")
+        assert result.ret == 0
+        assert "passed" in result.stdout.str()
+
+    def test_pytest_fs_collect_hooks_are_seen(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            class MyModule(pytest.Module):
+                pass
+            def pytest_collect_file(path, parent):
+                if path.ext == ".py":
+                    return MyModule(path, parent)
+        """)
+        testdir.mkdir("sub")
+        testdir.makepyfile("def test_x(): pass")
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*MyModule*",
+            "*test_x*"
+        ])
+
+    def test_pytest_collect_file_from_sister_dir(self, testdir):
+        sub1 = testdir.mkpydir("sub1")
+        sub2 = testdir.mkpydir("sub2")
+        conf1 = testdir.makeconftest("""
+            import pytest
+            class MyModule1(pytest.Module):
+                pass
+            def pytest_collect_file(path, parent):
+                if path.ext == ".py":
+                    return MyModule1(path, parent)
+        """)
+        conf1.move(sub1.join(conf1.basename))
+        conf2 = testdir.makeconftest("""
+            import pytest
+            class MyModule2(pytest.Module):
+                pass
+            def pytest_collect_file(path, parent):
+                if path.ext == ".py":
+                    return MyModule2(path, parent)
+        """)
+        conf2.move(sub2.join(conf2.basename))
+        p = testdir.makepyfile("def test_x(): pass")
+        p.copy(sub1.join(p.basename))
+        p.copy(sub2.join(p.basename))
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*MyModule1*",
+            "*MyModule2*",
+            "*test_x*"
+        ])
+
+
+class TestSession(object):
+    def test_parsearg(self, testdir):
+        p = testdir.makepyfile("def test_func(): pass")
+        subdir = testdir.mkdir("sub")
+        subdir.ensure("__init__.py")
+        target = subdir.join(p.basename)
+        p.move(target)
+        subdir.chdir()
+        config = testdir.parseconfig(p.basename)
+        rcol = Session(config=config)
+        assert rcol.fspath == subdir
+        parts = rcol._parsearg(p.basename)
+
+        assert parts[0] == target
+        assert len(parts) == 1
+        parts = rcol._parsearg(p.basename + "::test_func")
+        assert parts[0] == target
+        assert parts[1] == "test_func"
+        assert len(parts) == 2
+
+    def test_collect_topdir(self, testdir):
+        p = testdir.makepyfile("def test_func(): pass")
+        id = "::".join([p.basename, "test_func"])
+        # XXX migrate to collectonly? (see below)
+        config = testdir.parseconfig(id)
+        topdir = testdir.tmpdir
+        rcol = Session(config)
+        assert topdir == rcol.fspath
+        # rootid = rcol.nodeid
+        # root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0]
+        # assert root2 == rcol, rootid
+        colitems = rcol.perform_collect([rcol.nodeid], genitems=False)
+        assert len(colitems) == 1
+        assert colitems[0].fspath == p
+
+    def get_reported_items(self, hookrec):
+        """Return pytest.Item instances reported by the pytest_collectreport hook"""
+        calls = hookrec.getcalls('pytest_collectreport')
+        return [x for call in calls for x in call.report.result
+                if isinstance(x, pytest.Item)]
+
+    def test_collect_protocol_single_function(self, testdir):
+        p = testdir.makepyfile("def test_func(): pass")
+        id = "::".join([p.basename, "test_func"])
+        items, hookrec = testdir.inline_genitems(id)
+        item, = items
+        assert item.name == "test_func"
+        newid = item.nodeid
+        assert newid == id
+        py.std.pprint.pprint(hookrec.calls)
+        topdir = testdir.tmpdir  # noqa
+        hookrec.assert_contains([
+            ("pytest_collectstart", "collector.fspath == topdir"),
+            ("pytest_make_collect_report", "collector.fspath == topdir"),
+            ("pytest_collectstart", "collector.fspath == p"),
+            ("pytest_make_collect_report", "collector.fspath == p"),
+            ("pytest_pycollect_makeitem", "name == 'test_func'"),
+            ("pytest_collectreport", "report.result[0].name == 'test_func'"),
+        ])
+        # ensure we are reporting the collection of the single test item (#2464)
+        assert [x.name for x in self.get_reported_items(hookrec)] == ['test_func']
+
+    def test_collect_protocol_method(self, testdir):
+        p = testdir.makepyfile("""
+            class TestClass(object):
+                def test_method(self):
+                    pass
+        """)
+        normid = p.basename + "::TestClass::()::test_method"
+        for id in [p.basename,
+                   p.basename + "::TestClass",
+                   p.basename + "::TestClass::()",
+                   normid,
+                   ]:
+            items, hookrec = testdir.inline_genitems(id)
+            assert len(items) == 1
+            assert items[0].name == "test_method"
+            newid = items[0].nodeid
+            assert newid == normid
+            # ensure we are reporting the collection of the single test item (#2464)
+            assert [x.name for x in self.get_reported_items(hookrec)] == ['test_method']
+
+    def test_collect_custom_nodes_multi_id(self, testdir):
+        p = testdir.makepyfile("def test_func(): pass")
+        testdir.makeconftest("""
+            import pytest
+            class SpecialItem(pytest.Item):
+                def runtest(self):
+                    return # ok
+            class SpecialFile(pytest.File):
+                def collect(self):
+                    return [SpecialItem(name="check", parent=self)]
+            def pytest_collect_file(path, parent):
+                if path.basename == %r:
+                    return SpecialFile(fspath=path, parent=parent)
+        """ % p.basename)
+        id = p.basename
+
+        items, hookrec = testdir.inline_genitems(id)
+        py.std.pprint.pprint(hookrec.calls)
+        assert len(items) == 2
+        hookrec.assert_contains([
+            ("pytest_collectstart",
+                "collector.fspath == collector.session.fspath"),
+            ("pytest_collectstart",
+                "collector.__class__.__name__ == 'SpecialFile'"),
+            ("pytest_collectstart",
+                "collector.__class__.__name__ == 'Module'"),
+            ("pytest_pycollect_makeitem", "name == 'test_func'"),
+            ("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
+        ])
+        assert len(self.get_reported_items(hookrec)) == 2
+
+    def test_collect_subdir_event_ordering(self, testdir):
+        p = testdir.makepyfile("def test_func(): pass")
+        aaa = testdir.mkpydir("aaa")
+        test_aaa = aaa.join("test_aaa.py")
+        p.move(test_aaa)
+
+        items, hookrec = testdir.inline_genitems()
+        assert len(items) == 1
+        py.std.pprint.pprint(hookrec.calls)
+        hookrec.assert_contains([
+            ("pytest_collectstart", "collector.fspath == test_aaa"),
+            ("pytest_pycollect_makeitem", "name == 'test_func'"),
+            ("pytest_collectreport",
+             "report.nodeid.startswith('aaa/test_aaa.py')"),
+        ])
+
+    def test_collect_two_commandline_args(self, testdir):
+        p = testdir.makepyfile("def test_func(): pass")
+        aaa = testdir.mkpydir("aaa")
+        bbb = testdir.mkpydir("bbb")
+        test_aaa = aaa.join("test_aaa.py")
+        p.copy(test_aaa)
+        test_bbb = bbb.join("test_bbb.py")
+        p.move(test_bbb)
+
+        id = "."
+
+        items, hookrec = testdir.inline_genitems(id)
+        assert len(items) == 2
+        py.std.pprint.pprint(hookrec.calls)
+        hookrec.assert_contains([
+            ("pytest_collectstart", "collector.fspath == test_aaa"),
+            ("pytest_pycollect_makeitem", "name == 'test_func'"),
+            ("pytest_collectreport", "report.nodeid == 'aaa/test_aaa.py'"),
+            ("pytest_collectstart", "collector.fspath == test_bbb"),
+            ("pytest_pycollect_makeitem", "name == 'test_func'"),
+            ("pytest_collectreport", "report.nodeid == 'bbb/test_bbb.py'"),
+        ])
+
+    def test_serialization_byid(self, testdir):
+        testdir.makepyfile("def test_func(): pass")
+        items, hookrec = testdir.inline_genitems()
+        assert len(items) == 1
+        item, = items
+        items2, hookrec = testdir.inline_genitems(item.nodeid)
+        item2, = items2
+        assert item2.name == item.name
+        assert item2.fspath == item.fspath
+
+    def test_find_byid_without_instance_parents(self, testdir):
+        p = testdir.makepyfile("""
+            class TestClass(object):
+                def test_method(self):
+                    pass
+        """)
+        arg = p.basename + "::TestClass::test_method"
+        items, hookrec = testdir.inline_genitems(arg)
+        assert len(items) == 1
+        item, = items
+        assert item.nodeid.endswith("TestClass::()::test_method")
+        # ensure we are reporting the collection of the single test item (#2464)
+        assert [x.name for x in self.get_reported_items(hookrec)] == ['test_method']
+
+
+class Test_getinitialnodes(object):
+    def test_global_file(self, testdir, tmpdir):
+        x = tmpdir.ensure("x.py")
+        with tmpdir.as_cwd():
+            config = testdir.parseconfigure(x)
+        col = testdir.getnode(config, x)
+        assert isinstance(col, pytest.Module)
+        assert col.name == 'x.py'
+        assert col.parent.parent is None
+        for col in col.listchain():
+            assert col.config is config
+
+    def test_pkgfile(self, testdir):
+        tmpdir = testdir.tmpdir
+        subdir = tmpdir.join("subdir")
+        x = subdir.ensure("x.py")
+        subdir.ensure("__init__.py")
+        with subdir.as_cwd():
+            config = testdir.parseconfigure(x)
+        col = testdir.getnode(config, x)
+        assert isinstance(col, pytest.Module)
+        assert col.name == 'x.py'
+        assert col.parent.parent is None
+        for col in col.listchain():
+            assert col.config is config
+
+
+class Test_genitems(object):
+    def test_check_collect_hashes(self, testdir):
+        p = testdir.makepyfile("""
+            def test_1():
+                pass
+
+            def test_2():
+                pass
+        """)
+        p.copy(p.dirpath(p.purebasename + "2" + ".py"))
+        items, reprec = testdir.inline_genitems(p.dirpath())
+        assert len(items) == 4
+        for numi, i in enumerate(items):
+            for numj, j in enumerate(items):
+                if numj != numi:
+                    assert hash(i) != hash(j)
+                    assert i != j
+
+    def test_example_items1(self, testdir):
+        p = testdir.makepyfile('''
+            def testone():
+                pass
+
+            class TestX(object):
+                def testmethod_one(self):
+                    pass
+
+            class TestY(TestX):
+                pass
+        ''')
+        items, reprec = testdir.inline_genitems(p)
+        assert len(items) == 3
+        assert items[0].name == 'testone'
+        assert items[1].name == 'testmethod_one'
+        assert items[2].name == 'testmethod_one'
+
+        # let's also test getmodpath here
+        assert items[0].getmodpath() == "testone"
+        assert items[1].getmodpath() == "TestX.testmethod_one"
+        assert items[2].getmodpath() == "TestY.testmethod_one"
+
+        s = items[0].getmodpath(stopatmodule=False)
+        assert s.endswith("test_example_items1.testone")
+        print(s)
+
+    def test_class_and_functions_discovery_using_glob(self, testdir):
+        """
+        tests that python_classes and python_functions config options work
+        as prefixes and glob-like patterns (issue #600).
+        """
+        testdir.makeini("""
+            [pytest]
+            python_classes = *Suite Test
+            python_functions = *_test test
+        """)
+        p = testdir.makepyfile('''
+            class MyTestSuite(object):
+                def x_test(self):
+                    pass
+
+            class TestCase(object):
+                def test_y(self):
+                    pass
+        ''')
+        items, reprec = testdir.inline_genitems(p)
+        ids = [x.getmodpath() for x in items]
+        assert ids == ['MyTestSuite.x_test', 'TestCase.test_y']
+
+
+def test_matchnodes_two_collections_same_file(testdir):
+    testdir.makeconftest("""
+        import pytest
+        def pytest_configure(config):
+            config.pluginmanager.register(Plugin2())
+
+        class Plugin2(object):
+            def pytest_collect_file(self, path, parent):
+                if path.ext == ".abc":
+                    return MyFile2(path, parent)
+
+        def pytest_collect_file(path, parent):
+            if path.ext == ".abc":
+                return MyFile1(path, parent)
+
+        class MyFile1(pytest.Item, pytest.File):
+            def runtest(self):
+                pass
+        class MyFile2(pytest.File):
+            def collect(self):
+                return [Item2("hello", parent=self)]
+
+        class Item2(pytest.Item):
+            def runtest(self):
+                pass
+    """)
+    p = testdir.makefile(".abc", "")
+    result = testdir.runpytest()
+    assert result.ret == 0
+    result.stdout.fnmatch_lines([
+        "*2 passed*",
+    ])
+    res = testdir.runpytest("%s::hello" % p.basename)
+    res.stdout.fnmatch_lines([
+        "*1 passed*",
+    ])
+
+
+class TestNodekeywords(object):
+    def test_no_under(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def test_pass(): pass
+            def test_fail(): assert 0
+        """)
+        values = list(modcol.keywords)
+        assert modcol.name in values
+        for x in values:
+            assert not x.startswith("_")
+        assert modcol.name in repr(modcol.keywords)
+
+    def test_issue345(self, testdir):
+        testdir.makepyfile("""
+            def test_should_not_be_selected():
+                assert False, 'I should not have been selected to run'
+
+            def test___repr__():
+                pass
+        """)
+        reprec = testdir.inline_run("-k repr")
+        reprec.assertoutcome(passed=1, failed=0)
+
+
+COLLECTION_ERROR_PY_FILES = dict(
+    test_01_failure="""
+        def test_1():
+            assert False
+        """,
+    test_02_import_error="""
+        import asdfasdfasdf
+        def test_2():
+            assert True
+        """,
+    test_03_import_error="""
+        import asdfasdfasdf
+        def test_3():
+            assert True
+    """,
+    test_04_success="""
+        def test_4():
+            assert True
+    """,
+)
+
+
+def test_exit_on_collection_error(testdir):
+    """Verify that all collection errors are collected and no tests executed"""
+    testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)
+
+    res = testdir.runpytest()
+    assert res.ret == 2
+
+    res.stdout.fnmatch_lines([
+        "collected 2 items / 2 errors",
+        "*ERROR collecting test_02_import_error.py*",
+        "*No module named *asdfa*",
+        "*ERROR collecting test_03_import_error.py*",
+        "*No module named *asdfa*",
+    ])
+
+
+def test_exit_on_collection_with_maxfail_smaller_than_n_errors(testdir):
+    """
+    Verify collection is aborted once maxfail errors are encountered ignoring
+    further modules which would cause more collection errors.
+    """
+    testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)
+
+    res = testdir.runpytest("--maxfail=1")
+    assert res.ret == 1
+
+    res.stdout.fnmatch_lines([
+        "*ERROR collecting test_02_import_error.py*",
+        "*No module named *asdfa*",
+    ])
+
+    assert 'test_03' not in res.stdout.str()
+
+
+def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir):
+    """
+    Verify the test run aborts due to collection errors even if maxfail count of
+    errors was not reached.
+    """
+    testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)
+
+    res = testdir.runpytest("--maxfail=4")
+    assert res.ret == 2
+
+    res.stdout.fnmatch_lines([
+        "collected 2 items / 2 errors",
+        "*ERROR collecting test_02_import_error.py*",
+        "*No module named *asdfa*",
+        "*ERROR collecting test_03_import_error.py*",
+        "*No module named *asdfa*",
+    ])
+
+
+def test_continue_on_collection_errors(testdir):
+    """
+    Verify tests are executed even when collection errors occur when the
+    --continue-on-collection-errors flag is set
+    """
+    testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)
+
+    res = testdir.runpytest("--continue-on-collection-errors")
+    assert res.ret == 1
+
+    res.stdout.fnmatch_lines([
+        "collected 2 items / 2 errors",
+        "*1 failed, 1 passed, 2 error*",
+    ])
+
+
+def test_continue_on_collection_errors_maxfail(testdir):
+    """
+    Verify tests are executed even when collection errors occur and that maxfail
+    is honoured (including the collection error count).
+    4 tests: 2 collection errors + 1 failure + 1 success
+    test_4 is never executed because the test run is with --maxfail=3 which
+    means it is interrupted after the 2 collection errors + 1 failure.
+    """
+    testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)
+
+    res = testdir.runpytest("--continue-on-collection-errors", "--maxfail=3")
+    assert res.ret == 1
+
+    res.stdout.fnmatch_lines([
+        "collected 2 items / 2 errors",
+        "*1 failed, 2 error*",
+    ])
+
+
+def test_fixture_scope_sibling_conftests(testdir):
+    """Regression test case for https://github.com/pytest-dev/pytest/issues/2836"""
+    foo_path = testdir.mkpydir("foo")
+    foo_path.join("conftest.py").write(_pytest._code.Source("""
+        import pytest
+        @pytest.fixture
+        def fix():
+            return 1
+    """))
+    foo_path.join("test_foo.py").write("def test_foo(fix): assert fix == 1")
+
+    # Tests in `food/` should not see the conftest fixture from `foo/`
+    food_path = testdir.mkpydir("food")
+    food_path.join("test_food.py").write("def test_food(fix): assert fix == 1")
+
+    res = testdir.runpytest()
+    assert res.ret == 1
+
+    res.stdout.fnmatch_lines([
+        "*ERROR at setup of test_food*",
+        "E*fixture 'fix' not found",
+        "*1 passed, 1 error*",
+    ])
diff --git a/tools/third_party/pytest/testing/test_compat.py b/tools/third_party/pytest/testing/test_compat.py
new file mode 100644
index 0000000..c74801c
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_compat.py
@@ -0,0 +1,101 @@
+from __future__ import absolute_import, division, print_function
+import sys
+
+import pytest
+from _pytest.compat import is_generator, get_real_func, safe_getattr
+from _pytest.outcomes import OutcomeException
+
+
+def test_is_generator():
+    def zap():
+        yield
+
+    def foo():
+        pass
+
+    assert is_generator(zap)
+    assert not is_generator(foo)
+
+
+def test_real_func_loop_limit():
+
+    class Evil(object):
+        def __init__(self):
+            self.left = 1000
+
+        def __repr__(self):
+            return "<Evil left={left}>".format(left=self.left)
+
+        def __getattr__(self, attr):
+            if not self.left:
+                raise RuntimeError('its over')
+            self.left -= 1
+            return self
+
+    evil = Evil()
+
+    with pytest.raises(ValueError):
+        res = get_real_func(evil)
+        print(res)
+
+
+@pytest.mark.skipif(sys.version_info < (3, 4),
+                    reason='asyncio available in Python 3.4+')
+def test_is_generator_asyncio(testdir):
+    testdir.makepyfile("""
+        from _pytest.compat import is_generator
+        import asyncio
+        @asyncio.coroutine
+        def baz():
+            yield from [1,2,3]
+
+        def test_is_generator_asyncio():
+            assert not is_generator(baz)
+    """)
+    # avoid importing asyncio into pytest's own process,
+    # which in turn imports logging (#8)
+    result = testdir.runpytest_subprocess()
+    result.stdout.fnmatch_lines(['*1 passed*'])
+
+
+@pytest.mark.skipif(sys.version_info < (3, 5),
+                    reason='async syntax available in Python 3.5+')
+def test_is_generator_async_syntax(testdir):
+    testdir.makepyfile("""
+        from _pytest.compat import is_generator
+        def test_is_generator_py35():
+            async def foo():
+                await foo()
+
+            async def bar():
+                pass
+
+            assert not is_generator(foo)
+            assert not is_generator(bar)
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(['*1 passed*'])
+
+
+class ErrorsHelper(object):
+    @property
+    def raise_exception(self):
+        raise Exception('exception should be catched')
+
+    @property
+    def raise_fail(self):
+        pytest.fail('fail should be catched')
+
+
+def test_helper_failures():
+    helper = ErrorsHelper()
+    with pytest.raises(Exception):
+        helper.raise_exception
+    with pytest.raises(OutcomeException):
+        helper.raise_fail
+
+
+def test_safe_getattr():
+    helper = ErrorsHelper()
+    assert safe_getattr(helper, 'raise_exception', 'default') == 'default'
+    assert safe_getattr(helper, 'raise_fail', 'default') == 'default'
diff --git a/tools/third_party/pytest/testing/test_config.py b/tools/third_party/pytest/testing/test_config.py
new file mode 100644
index 0000000..44b8c31
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_config.py
@@ -0,0 +1,862 @@
+from __future__ import absolute_import, division, print_function
+import sys
+import py
+import pytest
+
+import _pytest._code
+from _pytest.config import getcfg, get_common_ancestor, determine_setup, _iter_rewritable_modules
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+
+
+class TestParseIni(object):
+
+    @pytest.mark.parametrize('section, filename',
+                             [('pytest', 'pytest.ini'), ('tool:pytest', 'setup.cfg')])
+    def test_getcfg_and_config(self, testdir, tmpdir, section, filename):
+        sub = tmpdir.mkdir("sub")
+        sub.chdir()
+        tmpdir.join(filename).write(_pytest._code.Source("""
+            [{section}]
+            name = value
+        """.format(section=section)))
+        rootdir, inifile, cfg = getcfg([sub])
+        assert cfg['name'] == "value"
+        config = testdir.parseconfigure(sub)
+        assert config.inicfg['name'] == 'value'
+
+    def test_getcfg_empty_path(self):
+        """correctly handle zero length arguments (a la pytest '')"""
+        getcfg([''])
+
+    def test_append_parse_args(self, testdir, tmpdir, monkeypatch):
+        monkeypatch.setenv('PYTEST_ADDOPTS', '--color no -rs --tb="short"')
+        tmpdir.join("pytest.ini").write(_pytest._code.Source("""
+            [pytest]
+            addopts = --verbose
+        """))
+        config = testdir.parseconfig(tmpdir)
+        assert config.option.color == 'no'
+        assert config.option.reportchars == 's'
+        assert config.option.tbstyle == 'short'
+        assert config.option.verbose
+
+    def test_tox_ini_wrong_version(self, testdir):
+        testdir.makefile('.ini', tox="""
+            [pytest]
+            minversion=9.0
+        """)
+        result = testdir.runpytest()
+        assert result.ret != 0
+        result.stderr.fnmatch_lines([
+            "*tox.ini:2*requires*9.0*actual*"
+        ])
+
+    @pytest.mark.parametrize("section, name", [
+        ('tool:pytest', 'setup.cfg'),
+        ('pytest', 'tox.ini'),
+        ('pytest', 'pytest.ini')],
+    )
+    def test_ini_names(self, testdir, name, section):
+        testdir.tmpdir.join(name).write(py.std.textwrap.dedent("""
+            [{section}]
+            minversion = 1.0
+        """.format(section=section)))
+        config = testdir.parseconfig()
+        assert config.getini("minversion") == "1.0"
+
+    def test_toxini_before_lower_pytestini(self, testdir):
+        sub = testdir.tmpdir.mkdir("sub")
+        sub.join("tox.ini").write(py.std.textwrap.dedent("""
+            [pytest]
+            minversion = 2.0
+        """))
+        testdir.tmpdir.join("pytest.ini").write(py.std.textwrap.dedent("""
+            [pytest]
+            minversion = 1.5
+        """))
+        config = testdir.parseconfigure(sub)
+        assert config.getini("minversion") == "2.0"
+
+    @pytest.mark.xfail(reason="probably not needed")
+    def test_confcutdir(self, testdir):
+        sub = testdir.mkdir("sub")
+        sub.chdir()
+        testdir.makeini("""
+            [pytest]
+            addopts = --qwe
+        """)
+        result = testdir.inline_run("--confcutdir=.")
+        assert result.ret == 0
+
+
+class TestConfigCmdlineParsing(object):
+    def test_parsing_again_fails(self, testdir):
+        config = testdir.parseconfig()
+        pytest.raises(AssertionError, lambda: config.parse([]))
+
+    def test_explicitly_specified_config_file_is_loaded(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("custom", "")
+        """)
+        testdir.makeini("""
+            [pytest]
+            custom = 0
+        """)
+        testdir.makefile(".cfg", custom="""
+            [pytest]
+            custom = 1
+        """)
+        config = testdir.parseconfig("-c", "custom.cfg")
+        assert config.getini("custom") == "1"
+
+    def test_absolute_win32_path(self, testdir):
+        temp_cfg_file = testdir.makefile(".cfg", custom="""
+            [pytest]
+            addopts = --version
+        """)
+        from os.path import normpath
+        temp_cfg_file = normpath(str(temp_cfg_file))
+        ret = pytest.main("-c " + temp_cfg_file)
+        assert ret == _pytest.main.EXIT_OK
+
+
+class TestConfigAPI(object):
+    def test_config_trace(self, testdir):
+        config = testdir.parseconfig()
+        values = []
+        config.trace.root.setwriter(values.append)
+        config.trace("hello")
+        assert len(values) == 1
+        assert values[0] == "hello [config]\n"
+
+    def test_config_getoption(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addoption("--hello", "-X", dest="hello")
+        """)
+        config = testdir.parseconfig("--hello=this")
+        for x in ("hello", "--hello", "-X"):
+            assert config.getoption(x) == "this"
+        pytest.raises(ValueError, "config.getoption('qweqwe')")
+
+    @pytest.mark.skipif('sys.version_info[0] < 3')
+    def test_config_getoption_unicode(self, testdir):
+        testdir.makeconftest("""
+            from __future__ import unicode_literals
+
+            def pytest_addoption(parser):
+                parser.addoption('--hello', type=str)
+        """)
+        config = testdir.parseconfig('--hello=this')
+        assert config.getoption('hello') == 'this'
+
+    def test_config_getvalueorskip(self, testdir):
+        config = testdir.parseconfig()
+        pytest.raises(pytest.skip.Exception,
+                      "config.getvalueorskip('hello')")
+        verbose = config.getvalueorskip("verbose")
+        assert verbose == config.option.verbose
+
+    def test_config_getvalueorskip_None(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addoption("--hello")
+        """)
+        config = testdir.parseconfig()
+        with pytest.raises(pytest.skip.Exception):
+            config.getvalueorskip('hello')
+
+    def test_getoption(self, testdir):
+        config = testdir.parseconfig()
+        with pytest.raises(ValueError):
+            config.getvalue('x')
+        assert config.getoption("x", 1) == 1
+
+    def test_getconftest_pathlist(self, testdir, tmpdir):
+        somepath = tmpdir.join("x", "y", "z")
+        p = tmpdir.join("conftest.py")
+        p.write("pathlist = ['.', %r]" % str(somepath))
+        config = testdir.parseconfigure(p)
+        assert config._getconftest_pathlist('notexist', path=tmpdir) is None
+        pl = config._getconftest_pathlist('pathlist', path=tmpdir)
+        print(pl)
+        assert len(pl) == 2
+        assert pl[0] == tmpdir
+        assert pl[1] == somepath
+
+    def test_addini(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("myname", "my new ini value")
+        """)
+        testdir.makeini("""
+            [pytest]
+            myname=hello
+        """)
+        config = testdir.parseconfig()
+        val = config.getini("myname")
+        assert val == "hello"
+        pytest.raises(ValueError, config.getini, 'other')
+
+    def test_addini_pathlist(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("paths", "my new ini value", type="pathlist")
+                parser.addini("abc", "abc value")
+        """)
+        p = testdir.makeini("""
+            [pytest]
+            paths=hello world/sub.py
+        """)
+        config = testdir.parseconfig()
+        values = config.getini("paths")
+        assert len(values) == 2
+        assert values[0] == p.dirpath('hello')
+        assert values[1] == p.dirpath('world/sub.py')
+        pytest.raises(ValueError, config.getini, 'other')
+
+    def test_addini_args(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("args", "new args", type="args")
+                parser.addini("a2", "", "args", default="1 2 3".split())
+        """)
+        testdir.makeini("""
+            [pytest]
+            args=123 "123 hello" "this"
+        """)
+        config = testdir.parseconfig()
+        values = config.getini("args")
+        assert len(values) == 3
+        assert values == ["123", "123 hello", "this"]
+        values = config.getini("a2")
+        assert values == list("123")
+
+    def test_addini_linelist(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("xy", "", type="linelist")
+                parser.addini("a2", "", "linelist")
+        """)
+        testdir.makeini("""
+            [pytest]
+            xy= 123 345
+                second line
+        """)
+        config = testdir.parseconfig()
+        values = config.getini("xy")
+        assert len(values) == 2
+        assert values == ["123 345", "second line"]
+        values = config.getini("a2")
+        assert values == []
+
+    @pytest.mark.parametrize('str_val, bool_val',
+                             [('True', True), ('no', False), ('no-ini', True)])
+    def test_addini_bool(self, testdir, str_val, bool_val):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("strip", "", type="bool", default=True)
+        """)
+        if str_val != 'no-ini':
+            testdir.makeini("""
+                [pytest]
+                strip=%s
+            """ % str_val)
+        config = testdir.parseconfig()
+        assert config.getini("strip") is bool_val
+
+    def test_addinivalue_line_existing(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("xy", "", type="linelist")
+        """)
+        testdir.makeini("""
+            [pytest]
+            xy= 123
+        """)
+        config = testdir.parseconfig()
+        values = config.getini("xy")
+        assert len(values) == 1
+        assert values == ["123"]
+        config.addinivalue_line("xy", "456")
+        values = config.getini("xy")
+        assert len(values) == 2
+        assert values == ["123", "456"]
+
+    def test_addinivalue_line_new(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("xy", "", type="linelist")
+        """)
+        config = testdir.parseconfig()
+        assert not config.getini("xy")
+        config.addinivalue_line("xy", "456")
+        values = config.getini("xy")
+        assert len(values) == 1
+        assert values == ["456"]
+        config.addinivalue_line("xy", "123")
+        values = config.getini("xy")
+        assert len(values) == 2
+        assert values == ["456", "123"]
+
+    def test_confcutdir_check_isdir(self, testdir):
+        """Give an error if --confcutdir is not a valid directory (#2078)"""
+        with pytest.raises(pytest.UsageError):
+            testdir.parseconfig('--confcutdir', testdir.tmpdir.join('file').ensure(file=1))
+        with pytest.raises(pytest.UsageError):
+            testdir.parseconfig('--confcutdir', testdir.tmpdir.join('inexistant'))
+        config = testdir.parseconfig('--confcutdir', testdir.tmpdir.join('dir').ensure(dir=1))
+        assert config.getoption('confcutdir') == str(testdir.tmpdir.join('dir'))
+
+    @pytest.mark.parametrize('names, expected', [
+        (['bar.py'], ['bar']),
+        (['foo', 'bar.py'], []),
+        (['foo', 'bar.pyc'], []),
+        (['foo', '__init__.py'], ['foo']),
+        (['foo', 'bar', '__init__.py'], []),
+    ])
+    def test_iter_rewritable_modules(self, names, expected):
+        assert list(_iter_rewritable_modules(['/'.join(names)])) == expected
+
+
+class TestConfigFromdictargs(object):
+    def test_basic_behavior(self):
+        from _pytest.config import Config
+        option_dict = {
+            'verbose': 444,
+            'foo': 'bar',
+            'capture': 'no',
+        }
+        args = ['a', 'b']
+
+        config = Config.fromdictargs(option_dict, args)
+        with pytest.raises(AssertionError):
+            config.parse(['should refuse to parse again'])
+        assert config.option.verbose == 444
+        assert config.option.foo == 'bar'
+        assert config.option.capture == 'no'
+        assert config.args == args
+
+    def test_origargs(self):
+        """Show that fromdictargs can handle args in their "orig" format"""
+        from _pytest.config import Config
+        option_dict = {}
+        args = ['-vvvv', '-s', 'a', 'b']
+
+        config = Config.fromdictargs(option_dict, args)
+        assert config.args == ['a', 'b']
+        assert config._origargs == args
+        assert config.option.verbose == 4
+        assert config.option.capture == 'no'
+
+    def test_inifilename(self, tmpdir):
+        tmpdir.join("foo/bar.ini").ensure().write(_pytest._code.Source("""
+            [pytest]
+            name = value
+        """))
+
+        from _pytest.config import Config
+        inifile = '../../foo/bar.ini'
+        option_dict = {
+            'inifilename': inifile,
+            'capture': 'no',
+        }
+
+        cwd = tmpdir.join('a/b')
+        cwd.join('pytest.ini').ensure().write(_pytest._code.Source("""
+            [pytest]
+            name = wrong-value
+            should_not_be_set = true
+        """))
+        with cwd.ensure(dir=True).as_cwd():
+            config = Config.fromdictargs(option_dict, ())
+
+        assert config.args == [str(cwd)]
+        assert config.option.inifilename == inifile
+        assert config.option.capture == 'no'
+
+        # this indicates this is the file used for getting configuration values
+        assert config.inifile == inifile
+        assert config.inicfg.get('name') == 'value'
+        assert config.inicfg.get('should_not_be_set') is None
+
+
+def test_options_on_small_file_do_not_blow_up(testdir):
+    def runfiletest(opts):
+        reprec = testdir.inline_run(*opts)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert failed == 2
+        assert skipped == passed == 0
+    path = testdir.makepyfile("""
+        def test_f1(): assert 0
+        def test_f2(): assert 0
+    """)
+
+    for opts in ([], ['-l'], ['-s'], ['--tb=no'], ['--tb=short'],
+                 ['--tb=long'], ['--fulltrace'],
+                 ['--traceconfig'], ['-v'], ['-v', '-v']):
+        runfiletest(opts + [path])
+
+
+def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
+    pkg_resources = pytest.importorskip("pkg_resources")
+
+    def my_iter(name):
+        assert name == "pytest11"
+
+        class Dist(object):
+            project_name = 'spam'
+            version = '1.0'
+
+            def _get_metadata(self, name):
+                return ['foo.txt,sha256=abc,123']
+
+        class EntryPoint(object):
+            name = "mytestplugin"
+            dist = Dist()
+
+            def load(self):
+                class PseudoPlugin(object):
+                    x = 42
+                return PseudoPlugin()
+
+        return iter([EntryPoint()])
+
+    monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+    testdir.makeconftest("""
+        pytest_plugins = "mytestplugin",
+    """)
+    monkeypatch.setenv("PYTEST_PLUGINS", "mytestplugin")
+    config = testdir.parseconfig()
+    plugin = config.pluginmanager.getplugin("mytestplugin")
+    assert plugin.x == 42
+
+
+def test_setuptools_importerror_issue1479(testdir, monkeypatch):
+    pkg_resources = pytest.importorskip("pkg_resources")
+
+    def my_iter(name):
+        assert name == "pytest11"
+
+        class Dist(object):
+            project_name = 'spam'
+            version = '1.0'
+
+            def _get_metadata(self, name):
+                return ['foo.txt,sha256=abc,123']
+
+        class EntryPoint(object):
+            name = "mytestplugin"
+            dist = Dist()
+
+            def load(self):
+                raise ImportError("Don't hide me!")
+
+        return iter([EntryPoint()])
+
+    monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+    with pytest.raises(ImportError):
+        testdir.parseconfig()
+
+
+@pytest.mark.parametrize('block_it', [True, False])
+def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block_it):
+    pkg_resources = pytest.importorskip("pkg_resources")
+
+    plugin_module_placeholder = object()
+
+    def my_iter(name):
+        assert name == "pytest11"
+
+        class Dist(object):
+            project_name = 'spam'
+            version = '1.0'
+
+            def _get_metadata(self, name):
+                return ['foo.txt,sha256=abc,123']
+
+        class EntryPoint(object):
+            name = "mytestplugin"
+            dist = Dist()
+
+            def load(self):
+                return plugin_module_placeholder
+
+        return iter([EntryPoint()])
+
+    monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+    args = ("-p", "no:mytestplugin") if block_it else ()
+    config = testdir.parseconfig(*args)
+    config.pluginmanager.import_plugin("mytestplugin")
+    if block_it:
+        assert "mytestplugin" not in sys.modules
+        assert config.pluginmanager.get_plugin('mytestplugin') is None
+    else:
+        assert config.pluginmanager.get_plugin('mytestplugin') is plugin_module_placeholder
+
+
+def test_cmdline_processargs_simple(testdir):
+    testdir.makeconftest("""
+        def pytest_cmdline_preparse(args):
+            args.append("-h")
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*pytest*",
+        "*-h*",
+    ])
+
+
+def test_invalid_options_show_extra_information(testdir):
+    """display extra information when pytest exits due to unrecognized
+    options in the command-line"""
+    testdir.makeini("""
+        [pytest]
+        addopts = --invalid-option
+    """)
+    result = testdir.runpytest()
+    result.stderr.fnmatch_lines([
+        "*error: unrecognized arguments: --invalid-option*",
+        "*  inifile: %s*" % testdir.tmpdir.join('tox.ini'),
+        "*  rootdir: %s*" % testdir.tmpdir,
+    ])
+
+
+@pytest.mark.parametrize('args', [
+    ['dir1', 'dir2', '-v'],
+    ['dir1', '-v', 'dir2'],
+    ['dir2', '-v', 'dir1'],
+    ['-v', 'dir2', 'dir1'],
+])
+def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args):
+    """
+    Consider all arguments in the command-line for rootdir and inifile
+    discovery, even if they happen to occur after an option. #949
+    """
+    # replace "dir1" and "dir2" from "args" into their real directory
+    root = testdir.tmpdir.mkdir('myroot')
+    d1 = root.mkdir('dir1')
+    d2 = root.mkdir('dir2')
+    for i, arg in enumerate(args):
+        if arg == 'dir1':
+            args[i] = d1
+        elif arg == 'dir2':
+            args[i] = d2
+    with root.as_cwd():
+        result = testdir.runpytest(*args)
+    result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile:'])
+
+
+@pytest.mark.skipif("sys.platform == 'win32'")
+def test_toolongargs_issue224(testdir):
+    result = testdir.runpytest("-m", "hello" * 500)
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+
+
+def test_config_in_subdirectory_colon_command_line_issue2148(testdir):
+    conftest_source = '''
+        def pytest_addoption(parser):
+            parser.addini('foo', 'foo')
+    '''
+
+    testdir.makefile('.ini', **{
+        'pytest': '[pytest]\nfoo = root',
+        'subdir/pytest': '[pytest]\nfoo = subdir',
+    })
+
+    testdir.makepyfile(**{
+        'conftest': conftest_source,
+        'subdir/conftest': conftest_source,
+        'subdir/test_foo': '''
+            def test_foo(pytestconfig):
+                assert pytestconfig.getini('foo') == 'subdir'
+        '''})
+
+    result = testdir.runpytest('subdir/test_foo.py::test_foo')
+    assert result.ret == 0
+
+
+def test_notify_exception(testdir, capfd):
+    config = testdir.parseconfig()
+    excinfo = pytest.raises(ValueError, "raise ValueError(1)")
+    config.notify_exception(excinfo)
+    out, err = capfd.readouterr()
+    assert "ValueError" in err
+
+    class A(object):
+        def pytest_internalerror(self, excrepr):
+            return True
+
+    config.pluginmanager.register(A())
+    config.notify_exception(excinfo)
+    out, err = capfd.readouterr()
+    assert not err
+
+
+def test_load_initial_conftest_last_ordering(testdir):
+    from _pytest.config import get_config
+    pm = get_config().pluginmanager
+
+    class My(object):
+        def pytest_load_initial_conftests(self):
+            pass
+
+    m = My()
+    pm.register(m)
+    hc = pm.hook.pytest_load_initial_conftests
+    values = hc._nonwrappers + hc._wrappers
+    expected = [
+        "_pytest.config",
+        'test_config',
+        '_pytest.capture',
+    ]
+    assert [x.function.__module__ for x in values] == expected
+
+
+def test_get_plugin_specs_as_list():
+    from _pytest.config import _get_plugin_specs_as_list
+    with pytest.raises(pytest.UsageError):
+        _get_plugin_specs_as_list(set(['foo']))
+    with pytest.raises(pytest.UsageError):
+        _get_plugin_specs_as_list(dict())
+
+    assert _get_plugin_specs_as_list(None) == []
+    assert _get_plugin_specs_as_list('') == []
+    assert _get_plugin_specs_as_list('foo') == ['foo']
+    assert _get_plugin_specs_as_list('foo,bar') == ['foo', 'bar']
+    assert _get_plugin_specs_as_list(['foo', 'bar']) == ['foo', 'bar']
+    assert _get_plugin_specs_as_list(('foo', 'bar')) == ['foo', 'bar']
+
+
+class TestWarning(object):
+    def test_warn_config(self, testdir):
+        testdir.makeconftest("""
+            values = []
+            def pytest_configure(config):
+                config.warn("C1", "hello")
+            def pytest_logwarning(code, message):
+                if message == "hello" and code == "C1":
+                    values.append(1)
+        """)
+        testdir.makepyfile("""
+            def test_proper(pytestconfig):
+                import conftest
+                assert conftest.values == [1]
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_warn_on_test_item_from_request(self, testdir, request):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def fix(request):
+                request.node.warn("T1", "hello")
+
+            def test_hello(fix):
+                pass
+        """)
+        result = testdir.runpytest("--disable-pytest-warnings")
+        assert result.parseoutcomes()["warnings"] > 0
+        assert "hello" not in result.stdout.str()
+
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            ===*warnings summary*===
+            *test_warn_on_test_item_from_request.py::test_hello*
+            *hello*
+        """)
+
+
+class TestRootdir(object):
+    def test_simple_noini(self, tmpdir):
+        assert get_common_ancestor([tmpdir]) == tmpdir
+        a = tmpdir.mkdir("a")
+        assert get_common_ancestor([a, tmpdir]) == tmpdir
+        assert get_common_ancestor([tmpdir, a]) == tmpdir
+        with tmpdir.as_cwd():
+            assert get_common_ancestor([]) == tmpdir
+            no_path = tmpdir.join('does-not-exist')
+            assert get_common_ancestor([no_path]) == tmpdir
+            assert get_common_ancestor([no_path.join('a')]) == tmpdir
+
+    @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
+    def test_with_ini(self, tmpdir, name):
+        inifile = tmpdir.join(name)
+        inifile.write("[pytest]\n")
+
+        a = tmpdir.mkdir("a")
+        b = a.mkdir("b")
+        for args in ([tmpdir], [a], [b]):
+            rootdir, inifile, inicfg = determine_setup(None, args)
+            assert rootdir == tmpdir
+            assert inifile == inifile
+        rootdir, inifile, inicfg = determine_setup(None, [b, a])
+        assert rootdir == tmpdir
+        assert inifile == inifile
+
+    @pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
+    def test_pytestini_overides_empty_other(self, tmpdir, name):
+        inifile = tmpdir.ensure("pytest.ini")
+        a = tmpdir.mkdir("a")
+        a.ensure(name)
+        rootdir, inifile, inicfg = determine_setup(None, [a])
+        assert rootdir == tmpdir
+        assert inifile == inifile
+
+    def test_setuppy_fallback(self, tmpdir):
+        a = tmpdir.mkdir("a")
+        a.ensure("setup.cfg")
+        tmpdir.ensure("setup.py")
+        rootdir, inifile, inicfg = determine_setup(None, [a])
+        assert rootdir == tmpdir
+        assert inifile is None
+        assert inicfg == {}
+
+    def test_nothing(self, tmpdir, monkeypatch):
+        monkeypatch.chdir(str(tmpdir))
+        rootdir, inifile, inicfg = determine_setup(None, [tmpdir])
+        assert rootdir == tmpdir
+        assert inifile is None
+        assert inicfg == {}
+
+    def test_with_specific_inifile(self, tmpdir):
+        inifile = tmpdir.ensure("pytest.ini")
+        rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir])
+        assert rootdir == tmpdir
+
+
+class TestOverrideIniArgs(object):
+    @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
+    def test_override_ini_names(self, testdir, name):
+        testdir.tmpdir.join(name).write(py.std.textwrap.dedent("""
+            [pytest]
+            custom = 1.0"""))
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("custom", "")""")
+        testdir.makepyfile("""
+            def test_pass(pytestconfig):
+                ini_val = pytestconfig.getini("custom")
+                print('\\ncustom_option:%s\\n' % ini_val)""")
+
+        result = testdir.runpytest("--override-ini", "custom=2.0", "-s")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["custom_option:2.0"])
+
+        result = testdir.runpytest("--override-ini", "custom=2.0",
+                                   "--override-ini=custom=3.0", "-s")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["custom_option:3.0"])
+
+    def test_override_ini_pathlist(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                parser.addini("paths", "my new ini value", type="pathlist")""")
+        testdir.makeini("""
+            [pytest]
+            paths=blah.py""")
+        testdir.makepyfile("""
+            import py.path
+            def test_pathlist(pytestconfig):
+                config_paths = pytestconfig.getini("paths")
+                print(config_paths)
+                for cpf in config_paths:
+                    print('\\nuser_path:%s' % cpf.basename)""")
+        result = testdir.runpytest("--override-ini",
+                                   'paths=foo/bar1.py foo/bar2.py', "-s")
+        result.stdout.fnmatch_lines(["user_path:bar1.py",
+                                     "user_path:bar2.py"])
+
+    def test_override_multiple_and_default(self, testdir):
+        testdir.makeconftest("""
+            def pytest_addoption(parser):
+                addini = parser.addini
+                addini("custom_option_1", "", default="o1")
+                addini("custom_option_2", "", default="o2")
+                addini("custom_option_3", "", default=False, type="bool")
+                addini("custom_option_4", "", default=True, type="bool")""")
+        testdir.makeini("""
+            [pytest]
+            custom_option_1=custom_option_1
+            custom_option_2=custom_option_2""")
+        testdir.makepyfile("""
+            def test_multiple_options(pytestconfig):
+                prefix = "custom_option"
+                for x in range(1, 5):
+                    ini_value=pytestconfig.getini("%s_%d" % (prefix, x))
+                    print('\\nini%d:%s' % (x, ini_value))""")
+        result = testdir.runpytest(
+            "--override-ini", 'custom_option_1=fulldir=/tmp/user1',
+            'custom_option_2=url=/tmp/user2?a=b&d=e',
+            "-o", 'custom_option_3=True',
+            "-o", 'custom_option_4=no', "-s")
+        result.stdout.fnmatch_lines(["ini1:fulldir=/tmp/user1",
+                                     "ini2:url=/tmp/user2?a=b&d=e",
+                                     "ini3:True",
+                                     "ini4:False"])
+
+    def test_override_ini_usage_error_bad_style(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            xdist_strict=False
+        """)
+        result = testdir.runpytest("--override-ini", 'xdist_strict True', "-s")
+        result.stderr.fnmatch_lines(["*ERROR* *expects option=value*"])
+
+    @pytest.mark.parametrize('with_ini', [True, False])
+    def test_override_ini_handled_asap(self, testdir, with_ini):
+        """-o should be handled as soon as possible and always override what's in ini files (#2238)"""
+        if with_ini:
+            testdir.makeini("""
+                [pytest]
+                python_files=test_*.py
+            """)
+        testdir.makepyfile(unittest_ini_handle="""
+            def test():
+                pass
+        """)
+        result = testdir.runpytest("--override-ini", 'python_files=unittest_*.py')
+        result.stdout.fnmatch_lines(["*1 passed in*"])
+
+    def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch):
+        monkeypatch.chdir(str(tmpdir))
+        a = tmpdir.mkdir("a")
+        b = tmpdir.mkdir("b")
+        rootdir, inifile, inicfg = determine_setup(None, [a, b])
+        assert rootdir == tmpdir
+        assert inifile is None
+
+    def test_with_arg_outside_cwd_with_inifile(self, tmpdir):
+        a = tmpdir.mkdir("a")
+        b = tmpdir.mkdir("b")
+        inifile = a.ensure("pytest.ini")
+        rootdir, parsed_inifile, inicfg = determine_setup(None, [a, b])
+        assert rootdir == a
+        assert inifile == parsed_inifile
+
+    @pytest.mark.parametrize('dirs', ([], ['does-not-exist'],
+                                      ['a/does-not-exist']))
+    def test_with_non_dir_arg(self, dirs, tmpdir):
+        with tmpdir.ensure(dir=True).as_cwd():
+            rootdir, inifile, inicfg = determine_setup(None, dirs)
+            assert rootdir == tmpdir
+            assert inifile is None
+
+    def test_with_existing_file_in_subdir(self, tmpdir):
+        a = tmpdir.mkdir("a")
+        a.ensure("exist")
+        with tmpdir.as_cwd():
+            rootdir, inifile, inicfg = determine_setup(None, ['a/exist'])
+            assert rootdir == tmpdir
+            assert inifile is None
+
+    def test_addopts_before_initini(self, testdir, tmpdir, monkeypatch):
+        cache_dir = '.custom_cache'
+        monkeypatch.setenv('PYTEST_ADDOPTS', '-o cache_dir=%s' % cache_dir)
+        from _pytest.config import get_config
+        config = get_config()
+        config._preparse([], addopts=True)
+        assert config._override_ini == [['cache_dir=%s' % cache_dir]]
diff --git a/tools/third_party/pytest/testing/test_conftest.py b/tools/third_party/pytest/testing/test_conftest.py
new file mode 100644
index 0000000..c0411b7
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_conftest.py
@@ -0,0 +1,478 @@
+from __future__ import absolute_import, division, print_function
+from textwrap import dedent
+
+import _pytest._code
+import py
+import pytest
+from _pytest.config import PytestPluginManager
+from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
+
+
+@pytest.fixture(scope="module", params=["global", "inpackage"])
+def basedir(request, tmpdir_factory):
+    from _pytest.tmpdir import tmpdir
+    tmpdir = tmpdir(request, tmpdir_factory)
+    tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
+    tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
+    if request.param == "inpackage":
+        tmpdir.ensure("adir/__init__.py")
+        tmpdir.ensure("adir/b/__init__.py")
+    return tmpdir
+
+
+def ConftestWithSetinitial(path):
+    conftest = PytestPluginManager()
+    conftest_setinitial(conftest, [path])
+    return conftest
+
+
+def conftest_setinitial(conftest, args, confcutdir=None):
+    class Namespace(object):
+        def __init__(self):
+            self.file_or_dir = args
+            self.confcutdir = str(confcutdir)
+            self.noconftest = False
+    conftest._set_initial_conftests(Namespace())
+
+
+class TestConftestValueAccessGlobal(object):
+    def test_basic_init(self, basedir):
+        conftest = PytestPluginManager()
+        p = basedir.join("adir")
+        assert conftest._rget_with_confmod("a", p)[1] == 1
+
+    def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
+        conftest = PytestPluginManager()
+        len(conftest._path2confmods)
+        conftest._getconftestmodules(basedir)
+        snap1 = len(conftest._path2confmods)
+        # assert len(conftest._path2confmods) == snap1 + 1
+        conftest._getconftestmodules(basedir.join('adir'))
+        assert len(conftest._path2confmods) == snap1 + 1
+        conftest._getconftestmodules(basedir.join('b'))
+        assert len(conftest._path2confmods) == snap1 + 2
+
+    def test_value_access_not_existing(self, basedir):
+        conftest = ConftestWithSetinitial(basedir)
+        with pytest.raises(KeyError):
+            conftest._rget_with_confmod('a', basedir)
+
+    def test_value_access_by_path(self, basedir):
+        conftest = ConftestWithSetinitial(basedir)
+        adir = basedir.join("adir")
+        assert conftest._rget_with_confmod("a", adir)[1] == 1
+        assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
+
+    def test_value_access_with_confmod(self, basedir):
+        startdir = basedir.join("adir", "b")
+        startdir.ensure("xx", dir=True)
+        conftest = ConftestWithSetinitial(startdir)
+        mod, value = conftest._rget_with_confmod("a", startdir)
+        assert value == 1.5
+        path = py.path.local(mod.__file__)
+        assert path.dirpath() == basedir.join("adir", "b")
+        assert path.purebasename.startswith("conftest")
+
+
+def test_conftest_in_nonpkg_with_init(tmpdir):
+    tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3")
+    tmpdir.ensure("adir-1.0/b/conftest.py").write("b=2 ; a = 1.5")
+    tmpdir.ensure("adir-1.0/b/__init__.py")
+    tmpdir.ensure("adir-1.0/__init__.py")
+    ConftestWithSetinitial(tmpdir.join("adir-1.0", "b"))
+
+
+def test_doubledash_considered(testdir):
+    conf = testdir.mkdir("--option")
+    conf.join("conftest.py").ensure()
+    conftest = PytestPluginManager()
+    conftest_setinitial(conftest, [conf.basename, conf.basename])
+    values = conftest._getconftestmodules(conf)
+    assert len(values) == 1
+
+
+def test_issue151_load_all_conftests(testdir):
+    names = "code proj src".split()
+    for name in names:
+        p = testdir.mkdir(name)
+        p.ensure("conftest.py")
+
+    conftest = PytestPluginManager()
+    conftest_setinitial(conftest, names)
+    d = list(conftest._conftestpath2mod.values())
+    assert len(d) == len(names)
+
+
+def test_conftest_global_import(testdir):
+    testdir.makeconftest("x=3")
+    p = testdir.makepyfile("""
+        import py, pytest
+        from _pytest.config import PytestPluginManager
+        conf = PytestPluginManager()
+        mod = conf._importconftest(py.path.local("conftest.py"))
+        assert mod.x == 3
+        import conftest
+        assert conftest is mod, (conftest, mod)
+        subconf = py.path.local().ensure("sub", "conftest.py")
+        subconf.write("y=4")
+        mod2 = conf._importconftest(subconf)
+        assert mod != mod2
+        assert mod2.y == 4
+        import conftest
+        assert conftest is mod2, (conftest, mod)
+    """)
+    res = testdir.runpython(p)
+    assert res.ret == 0
+
+
+def test_conftestcutdir(testdir):
+    conf = testdir.makeconftest("")
+    p = testdir.mkdir("x")
+    conftest = PytestPluginManager()
+    conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
+    values = conftest._getconftestmodules(p)
+    assert len(values) == 0
+    values = conftest._getconftestmodules(conf.dirpath())
+    assert len(values) == 0
+    assert conf not in conftest._conftestpath2mod
+    # but we can still import a conftest directly
+    conftest._importconftest(conf)
+    values = conftest._getconftestmodules(conf.dirpath())
+    assert values[0].__file__.startswith(str(conf))
+    # and all sub paths get updated properly
+    values = conftest._getconftestmodules(p)
+    assert len(values) == 1
+    assert values[0].__file__.startswith(str(conf))
+
+
+def test_conftestcutdir_inplace_considered(testdir):
+    conf = testdir.makeconftest("")
+    conftest = PytestPluginManager()
+    conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
+    values = conftest._getconftestmodules(conf.dirpath())
+    assert len(values) == 1
+    assert values[0].__file__.startswith(str(conf))
+
+
+@pytest.mark.parametrize("name", 'test tests whatever .dotdir'.split())
+def test_setinitial_conftest_subdirs(testdir, name):
+    sub = testdir.mkdir(name)
+    subconftest = sub.ensure("conftest.py")
+    conftest = PytestPluginManager()
+    conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
+    if name not in ('whatever', '.dotdir'):
+        assert subconftest in conftest._conftestpath2mod
+        assert len(conftest._conftestpath2mod) == 1
+    else:
+        assert subconftest not in conftest._conftestpath2mod
+        assert len(conftest._conftestpath2mod) == 0
+
+
+def test_conftest_confcutdir(testdir):
+    testdir.makeconftest("assert 0")
+    x = testdir.mkdir("x")
+    x.join("conftest.py").write(_pytest._code.Source("""
+        def pytest_addoption(parser):
+            parser.addoption("--xyz", action="store_true")
+    """))
+    result = testdir.runpytest("-h", "--confcutdir=%s" % x, x)
+    result.stdout.fnmatch_lines(["*--xyz*"])
+    assert 'warning: could not load initial' not in result.stdout.str()
+
+
+def test_no_conftest(testdir):
+    testdir.makeconftest("assert 0")
+    result = testdir.runpytest("--noconftest")
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+
+    result = testdir.runpytest()
+    assert result.ret == EXIT_USAGEERROR
+
+
+def test_conftest_existing_resultlog(testdir):
+    x = testdir.mkdir("tests")
+    x.join("conftest.py").write(_pytest._code.Source("""
+        def pytest_addoption(parser):
+            parser.addoption("--xyz", action="store_true")
+    """))
+    testdir.makefile(ext=".log", result="")  # Writes result.log
+    result = testdir.runpytest("-h", "--resultlog", "result.log")
+    result.stdout.fnmatch_lines(["*--xyz*"])
+
+
+def test_conftest_existing_junitxml(testdir):
+    x = testdir.mkdir("tests")
+    x.join("conftest.py").write(_pytest._code.Source("""
+        def pytest_addoption(parser):
+            parser.addoption("--xyz", action="store_true")
+    """))
+    testdir.makefile(ext=".xml", junit="")  # Writes junit.xml
+    result = testdir.runpytest("-h", "--junitxml", "junit.xml")
+    result.stdout.fnmatch_lines(["*--xyz*"])
+
+
+def test_conftest_import_order(testdir, monkeypatch):
+    ct1 = testdir.makeconftest("")
+    sub = testdir.mkdir("sub")
+    ct2 = sub.join("conftest.py")
+    ct2.write("")
+
+    def impct(p):
+        return p
+
+    conftest = PytestPluginManager()
+    conftest._confcutdir = testdir.tmpdir
+    monkeypatch.setattr(conftest, '_importconftest', impct)
+    assert conftest._getconftestmodules(sub) == [ct1, ct2]
+
+
+def test_fixture_dependency(testdir, monkeypatch):
+    ct1 = testdir.makeconftest("")
+    ct1 = testdir.makepyfile("__init__.py")
+    ct1.write("")
+    sub = testdir.mkdir("sub")
+    sub.join("__init__.py").write("")
+    sub.join("conftest.py").write(py.std.textwrap.dedent("""
+        import pytest
+
+        @pytest.fixture
+        def not_needed():
+            assert False, "Should not be called!"
+
+        @pytest.fixture
+        def foo():
+            assert False, "Should not be called!"
+
+        @pytest.fixture
+        def bar(foo):
+            return 'bar'
+    """))
+    subsub = sub.mkdir("subsub")
+    subsub.join("__init__.py").write("")
+    subsub.join("test_bar.py").write(py.std.textwrap.dedent("""
+        import pytest
+
+        @pytest.fixture
+        def bar():
+            return 'sub bar'
+
+        def test_event_fixture(bar):
+            assert bar == 'sub bar'
+    """))
+    result = testdir.runpytest("sub")
+    result.stdout.fnmatch_lines(["*1 passed*"])
+
+
+def test_conftest_found_with_double_dash(testdir):
+    sub = testdir.mkdir("sub")
+    sub.join("conftest.py").write(py.std.textwrap.dedent("""
+        def pytest_addoption(parser):
+            parser.addoption("--hello-world", action="store_true")
+    """))
+    p = sub.join("test_hello.py")
+    p.write(py.std.textwrap.dedent("""
+        import pytest
+        def test_hello(found):
+            assert found == 1
+    """))
+    result = testdir.runpytest(str(p) + "::test_hello", "-h")
+    result.stdout.fnmatch_lines("""
+        *--hello-world*
+    """)
+
+
+class TestConftestVisibility(object):
+    def _setup_tree(self, testdir):  # for issue616
+        # example mostly taken from:
+        # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html
+        runner = testdir.mkdir("empty")
+        package = testdir.mkdir("package")
+
+        package.join("conftest.py").write(dedent("""\
+            import pytest
+            @pytest.fixture
+            def fxtr():
+                return "from-package"
+        """))
+        package.join("test_pkgroot.py").write(dedent("""\
+            def test_pkgroot(fxtr):
+                assert fxtr == "from-package"
+        """))
+
+        swc = package.mkdir("swc")
+        swc.join("__init__.py").ensure()
+        swc.join("conftest.py").write(dedent("""\
+            import pytest
+            @pytest.fixture
+            def fxtr():
+                return "from-swc"
+        """))
+        swc.join("test_with_conftest.py").write(dedent("""\
+            def test_with_conftest(fxtr):
+                assert fxtr == "from-swc"
+
+        """))
+
+        snc = package.mkdir("snc")
+        snc.join("__init__.py").ensure()
+        snc.join("test_no_conftest.py").write(dedent("""\
+            def test_no_conftest(fxtr):
+                assert fxtr == "from-package"   # No local conftest.py, so should
+                                                # use value from parent dir's
+
+        """))
+        print("created directory structure:")
+        for x in testdir.tmpdir.visit():
+            print("   " + x.relto(testdir.tmpdir))
+
+        return {
+            "runner": runner,
+            "package": package,
+            "swc": swc,
+            "snc": snc}
+
+    # N.B.: "swc" stands for "subdir with conftest.py"
+    #       "snc" stands for "subdir no [i.e. without] conftest.py"
+    @pytest.mark.parametrize("chdir,testarg,expect_ntests_passed", [
+        # Effective target: package/..
+        ("runner", "..", 3),
+        ("package", "..", 3),
+        ("swc", "../..", 3),
+        ("snc", "../..", 3),
+
+        # Effective target: package
+        ("runner", "../package", 3),
+        ("package", ".", 3),
+        ("swc", "..", 3),
+        ("snc", "..", 3),
+
+        # Effective target: package/swc
+        ("runner", "../package/swc", 1),
+        ("package", "./swc", 1),
+        ("swc", ".", 1),
+        ("snc", "../swc", 1),
+
+        # Effective target: package/snc
+        ("runner", "../package/snc", 1),
+        ("package", "./snc", 1),
+        ("swc", "../snc", 1),
+        ("snc", ".", 1),
+    ])
+    @pytest.mark.issue616
+    def test_parsefactories_relative_node_ids(
+            self, testdir, chdir, testarg, expect_ntests_passed):
+        dirs = self._setup_tree(testdir)
+        print("pytest run in cwd: %s" % (
+              dirs[chdir].relto(testdir.tmpdir)))
+        print("pytestarg        : %s" % (testarg))
+        print("expected pass    : %s" % (expect_ntests_passed))
+        with dirs[chdir].as_cwd():
+            reprec = testdir.inline_run(testarg, "-q", "--traceconfig")
+            reprec.assertoutcome(passed=expect_ntests_passed)
+
+
+@pytest.mark.parametrize('confcutdir,passed,error', [
+    ('.', 2, 0),
+    ('src', 1, 1),
+    (None, 1, 1),
+])
+def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error):
+    """Test that conftest files are detected only up to a ini file, unless
+    an explicit --confcutdir option is given.
+    """
+    root = testdir.tmpdir
+    src = root.join('src').ensure(dir=1)
+    src.join('pytest.ini').write('[pytest]')
+    src.join('conftest.py').write(_pytest._code.Source("""
+        import pytest
+        @pytest.fixture
+        def fix1(): pass
+    """))
+    src.join('test_foo.py').write(_pytest._code.Source("""
+        def test_1(fix1):
+            pass
+        def test_2(out_of_reach):
+            pass
+    """))
+    root.join('conftest.py').write(_pytest._code.Source("""
+        import pytest
+        @pytest.fixture
+        def out_of_reach(): pass
+    """))
+
+    args = [str(src)]
+    if confcutdir:
+        args = ['--confcutdir=%s' % root.join(confcutdir)]
+    result = testdir.runpytest(*args)
+    match = ''
+    if passed:
+        match += '*%d passed*' % passed
+    if error:
+        match += '*%d error*' % error
+    result.stdout.fnmatch_lines(match)
+
+
+def test_issue1073_conftest_special_objects(testdir):
+    testdir.makeconftest("""
+        class DontTouchMe(object):
+            def __getattr__(self, x):
+                raise Exception('cant touch me')
+
+        x = DontTouchMe()
+    """)
+    testdir.makepyfile("""
+        def test_some():
+            pass
+    """)
+    res = testdir.runpytest()
+    assert res.ret == 0
+
+
+def test_conftest_exception_handling(testdir):
+    testdir.makeconftest('''
+        raise ValueError()
+    ''')
+    testdir.makepyfile("""
+        def test_some():
+            pass
+    """)
+    res = testdir.runpytest()
+    assert res.ret == 4
+    assert 'raise ValueError()' in [line.strip() for line in res.errlines]
+
+
+def test_hook_proxy(testdir):
+    """Session's gethookproxy() would cache conftests incorrectly (#2016).
+    It was decided to remove the cache altogether.
+    """
+    testdir.makepyfile(**{
+        'root/demo-0/test_foo1.py': "def test1(): pass",
+
+        'root/demo-a/test_foo2.py': "def test1(): pass",
+        'root/demo-a/conftest.py': """
+            def pytest_ignore_collect(path, config):
+                return True
+            """,
+
+        'root/demo-b/test_foo3.py': "def test1(): pass",
+        'root/demo-c/test_foo4.py': "def test1(): pass",
+    })
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*test_foo1.py*',
+        '*test_foo3.py*',
+        '*test_foo4.py*',
+        '*3 passed*',
+    ])
+
+
+def test_required_option_help(testdir):
+    testdir.makeconftest("assert 0")
+    x = testdir.mkdir("x")
+    x.join("conftest.py").write(_pytest._code.Source("""
+        def pytest_addoption(parser):
+            parser.addoption("--xyz", action="store_true", required=True)
+    """))
+    result = testdir.runpytest("-h", x)
+    assert 'argument --xyz is required' not in result.stdout.str()
+    assert 'general:' in result.stdout.str()
diff --git a/tools/third_party/pytest/testing/test_doctest.py b/tools/third_party/pytest/testing/test_doctest.py
new file mode 100644
index 0000000..b15067f
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_doctest.py
@@ -0,0 +1,1003 @@
+# encoding: utf-8
+from __future__ import absolute_import, division, print_function
+import sys
+import _pytest._code
+from _pytest.compat import MODULE_NOT_FOUND_ERROR
+from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
+import pytest
+
+
+class TestDoctests(object):
+
+    def test_collect_testtextfile(self, testdir):
+        w = testdir.maketxtfile(whatever="")
+        checkfile = testdir.maketxtfile(test_something="""
+            alskdjalsdk
+            >>> i = 5
+            >>> i-1
+            4
+        """)
+
+        for x in (testdir.tmpdir, checkfile):
+            # print "checking that %s returns custom items" % (x,)
+            items, reprec = testdir.inline_genitems(x)
+            assert len(items) == 1
+            assert isinstance(items[0], DoctestItem)
+            assert isinstance(items[0].parent, DoctestTextfile)
+        # Empty file has no items.
+        items, reprec = testdir.inline_genitems(w)
+        assert len(items) == 0
+
+    def test_collect_module_empty(self, testdir):
+        path = testdir.makepyfile(whatever="#")
+        for p in (path, testdir.tmpdir):
+            items, reprec = testdir.inline_genitems(p,
+                                                    '--doctest-modules')
+            assert len(items) == 0
+
+    def test_collect_module_single_modulelevel_doctest(self, testdir):
+        path = testdir.makepyfile(whatever='""">>> pass"""')
+        for p in (path, testdir.tmpdir):
+            items, reprec = testdir.inline_genitems(p,
+                                                    '--doctest-modules')
+            assert len(items) == 1
+            assert isinstance(items[0], DoctestItem)
+            assert isinstance(items[0].parent, DoctestModule)
+
+    def test_collect_module_two_doctest_one_modulelevel(self, testdir):
+        path = testdir.makepyfile(whatever="""
+            '>>> x = None'
+            def my_func():
+                ">>> magic = 42 "
+        """)
+        for p in (path, testdir.tmpdir):
+            items, reprec = testdir.inline_genitems(p,
+                                                    '--doctest-modules')
+            assert len(items) == 2
+            assert isinstance(items[0], DoctestItem)
+            assert isinstance(items[1], DoctestItem)
+            assert isinstance(items[0].parent, DoctestModule)
+            assert items[0].parent is items[1].parent
+
+    def test_collect_module_two_doctest_no_modulelevel(self, testdir):
+        path = testdir.makepyfile(whatever="""
+            '# Empty'
+            def my_func():
+                ">>> magic = 42 "
+            def unuseful():
+                '''
+                # This is a function
+                # >>> # it doesn't have any doctest
+                '''
+            def another():
+                '''
+                # This is another function
+                >>> import os # this one does have a doctest
+                '''
+        """)
+        for p in (path, testdir.tmpdir):
+            items, reprec = testdir.inline_genitems(p,
+                                                    '--doctest-modules')
+            assert len(items) == 2
+            assert isinstance(items[0], DoctestItem)
+            assert isinstance(items[1], DoctestItem)
+            assert isinstance(items[0].parent, DoctestModule)
+            assert items[0].parent is items[1].parent
+
+    def test_simple_doctestfile(self, testdir):
+        p = testdir.maketxtfile(test_doc="""
+            >>> x = 1
+            >>> x == 1
+            False
+        """)
+        reprec = testdir.inline_run(p, )
+        reprec.assertoutcome(failed=1)
+
+    def test_new_pattern(self, testdir):
+        p = testdir.maketxtfile(xdoc="""
+            >>> x = 1
+            >>> x == 1
+            False
+        """)
+        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
+        reprec.assertoutcome(failed=1)
+
+    def test_multiple_patterns(self, testdir):
+        """Test support for multiple --doctest-glob arguments (#1255).
+        """
+        testdir.maketxtfile(xdoc="""
+            >>> 1
+            1
+        """)
+        testdir.makefile('.foo', test="""
+            >>> 1
+            1
+        """)
+        testdir.maketxtfile(test_normal="""
+            >>> 1
+            1
+        """)
+        expected = set(['xdoc.txt', 'test.foo', 'test_normal.txt'])
+        assert set(x.basename for x in testdir.tmpdir.listdir()) == expected
+        args = ["--doctest-glob=xdoc*.txt", "--doctest-glob=*.foo"]
+        result = testdir.runpytest(*args)
+        result.stdout.fnmatch_lines([
+            '*test.foo *',
+            '*xdoc.txt *',
+            '*2 passed*',
+        ])
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            '*test_normal.txt *',
+            '*1 passed*',
+        ])
+
+    @pytest.mark.parametrize(
+        '   test_string,    encoding',
+        [
+            (u'foo', 'ascii'),
+            (u'öäü', 'latin1'),
+            (u'öäü', 'utf-8')
+        ]
+    )
+    def test_encoding(self, testdir, test_string, encoding):
+        """Test support for doctest_encoding ini option.
+        """
+        testdir.makeini("""
+            [pytest]
+            doctest_encoding={0}
+        """.format(encoding))
+        doctest = u"""
+            >>> u"{0}"
+            {1}
+        """.format(test_string, repr(test_string))
+        testdir._makefile(".txt", [doctest], {}, encoding=encoding)
+
+        result = testdir.runpytest()
+
+        result.stdout.fnmatch_lines([
+            '*1 passed*',
+        ])
+
+    def test_doctest_unexpected_exception(self, testdir):
+        testdir.maketxtfile("""
+            >>> i = 0
+            >>> 0 / i
+            2
+        """)
+        result = testdir.runpytest("--doctest-modules")
+        result.stdout.fnmatch_lines([
+            "*unexpected_exception*",
+            "*>>> i = 0*",
+            "*>>> 0 / i*",
+            "*UNEXPECTED*ZeroDivision*",
+        ])
+
+    def test_docstring_partial_context_around_error(self, testdir):
+        """Test that we show some context before the actual line of a failing
+        doctest.
+        """
+        testdir.makepyfile('''
+            def foo():
+                """
+                text-line-1
+                text-line-2
+                text-line-3
+                text-line-4
+                text-line-5
+                text-line-6
+                text-line-7
+                text-line-8
+                text-line-9
+                text-line-10
+                text-line-11
+                >>> 1 + 1
+                3
+
+                text-line-after
+                """
+        ''')
+        result = testdir.runpytest('--doctest-modules')
+        result.stdout.fnmatch_lines([
+            '*docstring_partial_context_around_error*',
+            '005*text-line-3',
+            '006*text-line-4',
+            '013*text-line-11',
+            '014*>>> 1 + 1',
+            'Expected:',
+            '    3',
+            'Got:',
+            '    2',
+        ])
+        # lines below should be trimmed out
+        assert 'text-line-2' not in result.stdout.str()
+        assert 'text-line-after' not in result.stdout.str()
+
+    def test_docstring_full_context_around_error(self, testdir):
+        """Test that we show the whole context before the actual line of a failing
+        doctest, provided that the context is up to 10 lines long.
+        """
+        testdir.makepyfile('''
+            def foo():
+                """
+                text-line-1
+                text-line-2
+
+                >>> 1 + 1
+                3
+                """
+        ''')
+        result = testdir.runpytest('--doctest-modules')
+        result.stdout.fnmatch_lines([
+            '*docstring_full_context_around_error*',
+            '003*text-line-1',
+            '004*text-line-2',
+            '006*>>> 1 + 1',
+            'Expected:',
+            '    3',
+            'Got:',
+            '    2',
+        ])
+
+    def test_doctest_linedata_missing(self, testdir):
+        testdir.tmpdir.join('hello.py').write(_pytest._code.Source("""
+            class Fun(object):
+                @property
+                def test(self):
+                    '''
+                    >>> a = 1
+                    >>> 1/0
+                    '''
+            """))
+        result = testdir.runpytest("--doctest-modules")
+        result.stdout.fnmatch_lines([
+            "*hello*",
+            "*EXAMPLE LOCATION UNKNOWN, not showing all tests of that example*",
+            "*1/0*",
+            "*UNEXPECTED*ZeroDivision*",
+            "*1 failed*",
+        ])
+
+    def test_doctest_unex_importerror_only_txt(self, testdir):
+        testdir.maketxtfile("""
+            >>> import asdalsdkjaslkdjasd
+            >>>
+        """)
+        result = testdir.runpytest()
+        # doctest is never executed because of error during hello.py collection
+        result.stdout.fnmatch_lines([
+            "*>>> import asdals*",
+            "*UNEXPECTED*{e}*".format(e=MODULE_NOT_FOUND_ERROR),
+            "{e}: No module named *asdal*".format(e=MODULE_NOT_FOUND_ERROR),
+        ])
+
+    def test_doctest_unex_importerror_with_module(self, testdir):
+        testdir.tmpdir.join("hello.py").write(_pytest._code.Source("""
+            import asdalsdkjaslkdjasd
+        """))
+        testdir.maketxtfile("""
+            >>> import hello
+            >>>
+        """)
+        result = testdir.runpytest("--doctest-modules")
+        # doctest is never executed because of error during hello.py collection
+        result.stdout.fnmatch_lines([
+            "*ERROR collecting hello.py*",
+            "*{e}: No module named *asdals*".format(e=MODULE_NOT_FOUND_ERROR),
+            "*Interrupted: 1 errors during collection*",
+        ])
+
+    def test_doctestmodule(self, testdir):
+        p = testdir.makepyfile("""
+            '''
+                >>> x = 1
+                >>> x == 1
+                False
+
+            '''
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(failed=1)
+
+    def test_doctestmodule_external_and_issue116(self, testdir):
+        p = testdir.mkpydir("hello")
+        p.join("__init__.py").write(_pytest._code.Source("""
+            def somefunc():
+                '''
+                    >>> i = 0
+                    >>> i + 1
+                    2
+                '''
+        """))
+        result = testdir.runpytest(p, "--doctest-modules")
+        result.stdout.fnmatch_lines([
+            '004 *>>> i = 0',
+            '005 *>>> i + 1',
+            '*Expected:',
+            "*    2",
+            "*Got:",
+            "*    1",
+            "*:5: DocTestFailure"
+        ])
+
+    def test_txtfile_failing(self, testdir):
+        p = testdir.maketxtfile("""
+            >>> i = 0
+            >>> i + 1
+            2
+        """)
+        result = testdir.runpytest(p, "-s")
+        result.stdout.fnmatch_lines([
+            '001 >>> i = 0',
+            '002 >>> i + 1',
+            'Expected:',
+            "    2",
+            "Got:",
+            "    1",
+            "*test_txtfile_failing.txt:2: DocTestFailure"
+        ])
+
+    def test_txtfile_with_fixtures(self, testdir):
+        p = testdir.maketxtfile("""
+            >>> dir = getfixture('tmpdir')
+            >>> type(dir).__name__
+            'LocalPath'
+        """)
+        reprec = testdir.inline_run(p, )
+        reprec.assertoutcome(passed=1)
+
+    def test_txtfile_with_usefixtures_in_ini(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            usefixtures = myfixture
+        """)
+        testdir.makeconftest("""
+            import pytest
+            @pytest.fixture
+            def myfixture(monkeypatch):
+                monkeypatch.setenv("HELLO", "WORLD")
+        """)
+
+        p = testdir.maketxtfile("""
+            >>> import os
+            >>> os.environ["HELLO"]
+            'WORLD'
+        """)
+        reprec = testdir.inline_run(p, )
+        reprec.assertoutcome(passed=1)
+
+    def test_doctestmodule_with_fixtures(self, testdir):
+        p = testdir.makepyfile("""
+            '''
+                >>> dir = getfixture('tmpdir')
+                >>> type(dir).__name__
+                'LocalPath'
+            '''
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(passed=1)
+
+    def test_doctestmodule_three_tests(self, testdir):
+        p = testdir.makepyfile("""
+            '''
+            >>> dir = getfixture('tmpdir')
+            >>> type(dir).__name__
+            'LocalPath'
+            '''
+            def my_func():
+                '''
+                >>> magic = 42
+                >>> magic - 42
+                0
+                '''
+            def unuseful():
+                pass
+            def another():
+                '''
+                >>> import os
+                >>> os is os
+                True
+                '''
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(passed=3)
+
+    def test_doctestmodule_two_tests_one_fail(self, testdir):
+        p = testdir.makepyfile("""
+            class MyClass(object):
+                def bad_meth(self):
+                    '''
+                    >>> magic = 42
+                    >>> magic
+                    0
+                    '''
+                def nice_meth(self):
+                    '''
+                    >>> magic = 42
+                    >>> magic - 42
+                    0
+                    '''
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(failed=1, passed=1)
+
+    def test_ignored_whitespace(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE
+        """)
+        p = testdir.makepyfile("""
+            class MyClass(object):
+                '''
+                >>> a = "foo    "
+                >>> print(a)
+                foo
+                '''
+                pass
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(passed=1)
+
+    def test_non_ignored_whitespace(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            doctest_optionflags = ELLIPSIS
+        """)
+        p = testdir.makepyfile("""
+            class MyClass(object):
+                '''
+                >>> a = "foo    "
+                >>> print(a)
+                foo
+                '''
+                pass
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(failed=1, passed=0)
+
+    def test_ignored_whitespace_glob(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE
+        """)
+        p = testdir.maketxtfile(xdoc="""
+            >>> a = "foo    "
+            >>> print(a)
+            foo
+        """)
+        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
+        reprec.assertoutcome(passed=1)
+
+    def test_non_ignored_whitespace_glob(self, testdir):
+        testdir.makeini("""
+            [pytest]
+            doctest_optionflags = ELLIPSIS
+        """)
+        p = testdir.maketxtfile(xdoc="""
+            >>> a = "foo    "
+            >>> print(a)
+            foo
+        """)
+        reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
+        reprec.assertoutcome(failed=1, passed=0)
+
+    def test_contains_unicode(self, testdir):
+        """Fix internal error with docstrings containing non-ascii characters.
+        """
+        testdir.makepyfile(u'''
+            # encoding: utf-8
+            def foo():
+                """
+                >>> name = 'с' # not letter 'c' but instead Cyrillic 's'.
+                'anything'
+                """
+        ''')
+        result = testdir.runpytest('--doctest-modules')
+        result.stdout.fnmatch_lines([
+            'Got nothing',
+            '* 1 failed in*',
+        ])
+
+    def test_ignore_import_errors_on_doctest(self, testdir):
+        p = testdir.makepyfile("""
+            import asdf
+
+            def add_one(x):
+                '''
+                >>> add_one(1)
+                2
+                '''
+                return x + 1
+        """)
+
+        reprec = testdir.inline_run(p, "--doctest-modules",
+                                    "--doctest-ignore-import-errors")
+        reprec.assertoutcome(skipped=1, failed=1, passed=0)
+
+    def test_junit_report_for_doctest(self, testdir):
+        """
+        #713: Fix --junit-xml option when used with --doctest-modules.
+        """
+        p = testdir.makepyfile("""
+            def foo():
+                '''
+                >>> 1 + 1
+                3
+                '''
+                pass
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules",
+                                    "--junit-xml=junit.xml")
+        reprec.assertoutcome(failed=1)
+
+    def test_unicode_doctest(self, testdir):
+        """
+        Test case for issue 2434: DecodeError on Python 2 when doctest contains non-ascii
+        characters.
+        """
+        p = testdir.maketxtfile(test_unicode_doctest="""
+            .. doctest::
+
+                >>> print(
+                ...    "Hi\\n\\nByé")
+                Hi
+                ...
+                Byé
+                >>> 1/0  # Byé
+                1
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            '*UNEXPECTED EXCEPTION: ZeroDivisionError*',
+            '*1 failed*',
+        ])
+
+    def test_unicode_doctest_module(self, testdir):
+        """
+        Test case for issue 2434: DecodeError on Python 2 when doctest docstring
+        contains non-ascii characters.
+        """
+        p = testdir.makepyfile(test_unicode_doctest_module="""
+            # -*- encoding: utf-8 -*-
+            from __future__ import unicode_literals
+
+            def fix_bad_unicode(text):
+                '''
+                    >>> print(fix_bad_unicode('único'))
+                    único
+                '''
+                return "único"
+        """)
+        result = testdir.runpytest(p, '--doctest-modules')
+        result.stdout.fnmatch_lines(['* 1 passed *'])
+
+    def test_reportinfo(self, testdir):
+        '''
+        Test case to make sure that DoctestItem.reportinfo() returns lineno.
+        '''
+        p = testdir.makepyfile(test_reportinfo="""
+            def foo(x):
+                '''
+                    >>> foo('a')
+                    'b'
+                '''
+                return 'c'
+        """)
+        items, reprec = testdir.inline_genitems(p, '--doctest-modules')
+        reportinfo = items[0].reportinfo()
+        assert reportinfo[1] == 1
+
+    def test_valid_setup_py(self, testdir):
+        '''
+        Test to make sure that pytest ignores valid setup.py files when ran
+        with --doctest-modules
+        '''
+        p = testdir.makepyfile(setup="""
+            from setuptools import setup, find_packages
+            setup(name='sample',
+                  version='0.0',
+                  description='description',
+                  packages=find_packages()
+            )
+        """)
+        result = testdir.runpytest(p, '--doctest-modules')
+        result.stdout.fnmatch_lines(['*collected 0 items*'])
+
+    def test_invalid_setup_py(self, testdir):
+        '''
+        Test to make sure that pytest reads setup.py files that are not used
+        for python packages when ran with --doctest-modules
+        '''
+        p = testdir.makepyfile(setup="""
+            def test_foo():
+                return 'bar'
+        """)
+        result = testdir.runpytest(p, '--doctest-modules')
+        result.stdout.fnmatch_lines(['*collected 1 item*'])
+
+
+class TestLiterals(object):
+
+    @pytest.mark.parametrize('config_mode', ['ini', 'comment'])
+    def test_allow_unicode(self, testdir, config_mode):
+        """Test that doctests which output unicode work in all python versions
+        tested by pytest when the ALLOW_UNICODE option is used (either in
+        the ini file or by an inline comment).
+        """
+        if config_mode == 'ini':
+            testdir.makeini('''
+            [pytest]
+            doctest_optionflags = ALLOW_UNICODE
+            ''')
+            comment = ''
+        else:
+            comment = '#doctest: +ALLOW_UNICODE'
+
+        testdir.maketxtfile(test_doc="""
+            >>> b'12'.decode('ascii') {comment}
+            '12'
+        """.format(comment=comment))
+        testdir.makepyfile(foo="""
+            def foo():
+              '''
+              >>> b'12'.decode('ascii') {comment}
+              '12'
+              '''
+        """.format(comment=comment))
+        reprec = testdir.inline_run("--doctest-modules")
+        reprec.assertoutcome(passed=2)
+
+    @pytest.mark.parametrize('config_mode', ['ini', 'comment'])
+    def test_allow_bytes(self, testdir, config_mode):
+        """Test that doctests which output bytes work in all python versions
+        tested by pytest when the ALLOW_BYTES option is used (either in
+        the ini file or by an inline comment)(#1287).
+        """
+        if config_mode == 'ini':
+            testdir.makeini('''
+            [pytest]
+            doctest_optionflags = ALLOW_BYTES
+            ''')
+            comment = ''
+        else:
+            comment = '#doctest: +ALLOW_BYTES'
+
+        testdir.maketxtfile(test_doc="""
+            >>> b'foo'  {comment}
+            'foo'
+        """.format(comment=comment))
+        testdir.makepyfile(foo="""
+            def foo():
+              '''
+              >>> b'foo'  {comment}
+              'foo'
+              '''
+        """.format(comment=comment))
+        reprec = testdir.inline_run("--doctest-modules")
+        reprec.assertoutcome(passed=2)
+
+    def test_unicode_string(self, testdir):
+        """Test that doctests which output unicode fail in Python 2 when
+        the ALLOW_UNICODE option is not used. The same test should pass
+        in Python 3.
+        """
+        testdir.maketxtfile(test_doc="""
+            >>> b'12'.decode('ascii')
+            '12'
+        """)
+        reprec = testdir.inline_run()
+        passed = int(sys.version_info[0] >= 3)
+        reprec.assertoutcome(passed=passed, failed=int(not passed))
+
+    def test_bytes_literal(self, testdir):
+        """Test that doctests which output bytes fail in Python 3 when
+        the ALLOW_BYTES option is not used. The same test should pass
+        in Python 2 (#1287).
+        """
+        testdir.maketxtfile(test_doc="""
+            >>> b'foo'
+            'foo'
+        """)
+        reprec = testdir.inline_run()
+        passed = int(sys.version_info[0] == 2)
+        reprec.assertoutcome(passed=passed, failed=int(not passed))
+
+
+class TestDoctestSkips(object):
+    """
+    If all examples in a doctest are skipped due to the SKIP option, then
+    the tests should be SKIPPED rather than PASSED. (#957)
+    """
+
+    @pytest.fixture(params=['text', 'module'])
+    def makedoctest(self, testdir, request):
+        def makeit(doctest):
+            mode = request.param
+            if mode == 'text':
+                testdir.maketxtfile(doctest)
+            else:
+                assert mode == 'module'
+                testdir.makepyfile('"""\n%s"""' % doctest)
+
+        return makeit
+
+    def test_one_skipped(self, testdir, makedoctest):
+        makedoctest("""
+            >>> 1 + 1  # doctest: +SKIP
+            2
+            >>> 2 + 2
+            4
+        """)
+        reprec = testdir.inline_run("--doctest-modules")
+        reprec.assertoutcome(passed=1)
+
+    def test_one_skipped_failed(self, testdir, makedoctest):
+        makedoctest("""
+            >>> 1 + 1  # doctest: +SKIP
+            2
+            >>> 2 + 2
+            200
+        """)
+        reprec = testdir.inline_run("--doctest-modules")
+        reprec.assertoutcome(failed=1)
+
+    def test_all_skipped(self, testdir, makedoctest):
+        makedoctest("""
+            >>> 1 + 1  # doctest: +SKIP
+            2
+            >>> 2 + 2  # doctest: +SKIP
+            200
+        """)
+        reprec = testdir.inline_run("--doctest-modules")
+        reprec.assertoutcome(skipped=1)
+
+    def test_vacuous_all_skipped(self, testdir, makedoctest):
+        makedoctest('')
+        reprec = testdir.inline_run("--doctest-modules")
+        reprec.assertoutcome(passed=0, skipped=0)
+
+
+class TestDoctestAutoUseFixtures(object):
+
+    SCOPES = ['module', 'session', 'class', 'function']
+
+    def test_doctest_module_session_fixture(self, testdir):
+        """Test that session fixtures are initialized for doctest modules (#768)
+        """
+        # session fixture which changes some global data, which will
+        # be accessed by doctests in a module
+        testdir.makeconftest("""
+            import pytest
+            import sys
+
+            @pytest.yield_fixture(autouse=True, scope='session')
+            def myfixture():
+                assert not hasattr(sys, 'pytest_session_data')
+                sys.pytest_session_data = 1
+                yield
+                del sys.pytest_session_data
+        """)
+        testdir.makepyfile(foo="""
+            import sys
+
+            def foo():
+              '''
+              >>> assert sys.pytest_session_data == 1
+              '''
+
+            def bar():
+              '''
+              >>> assert sys.pytest_session_data == 1
+              '''
+        """)
+        result = testdir.runpytest("--doctest-modules")
+        result.stdout.fnmatch_lines('*2 passed*')
+
+    @pytest.mark.parametrize('scope', SCOPES)
+    @pytest.mark.parametrize('enable_doctest', [True, False])
+    def test_fixture_scopes(self, testdir, scope, enable_doctest):
+        """Test that auto-use fixtures work properly with doctest modules.
+        See #1057 and #1100.
+        """
+        testdir.makeconftest('''
+            import pytest
+
+            @pytest.fixture(autouse=True, scope="{scope}")
+            def auto(request):
+                return 99
+        '''.format(scope=scope))
+        testdir.makepyfile(test_1='''
+            def test_foo():
+                """
+                >>> getfixture('auto') + 1
+                100
+                """
+            def test_bar():
+                assert 1
+        ''')
+        params = ('--doctest-modules',) if enable_doctest else ()
+        passes = 3 if enable_doctest else 2
+        result = testdir.runpytest(*params)
+        result.stdout.fnmatch_lines(['*=== %d passed in *' % passes])
+
+    @pytest.mark.parametrize('scope', SCOPES)
+    @pytest.mark.parametrize('autouse', [True, False])
+    @pytest.mark.parametrize('use_fixture_in_doctest', [True, False])
+    def test_fixture_module_doctest_scopes(self, testdir, scope, autouse,
+                                           use_fixture_in_doctest):
+        """Test that auto-use fixtures work properly with doctest files.
+        See #1057 and #1100.
+        """
+        testdir.makeconftest('''
+            import pytest
+
+            @pytest.fixture(autouse={autouse}, scope="{scope}")
+            def auto(request):
+                return 99
+        '''.format(scope=scope, autouse=autouse))
+        if use_fixture_in_doctest:
+            testdir.maketxtfile(test_doc="""
+                >>> getfixture('auto')
+                99
+            """)
+        else:
+            testdir.maketxtfile(test_doc="""
+                >>> 1 + 1
+                2
+            """)
+        result = testdir.runpytest('--doctest-modules')
+        assert 'FAILURES' not in str(result.stdout.str())
+        result.stdout.fnmatch_lines(['*=== 1 passed in *'])
+
+    @pytest.mark.parametrize('scope', SCOPES)
+    def test_auto_use_request_attributes(self, testdir, scope):
+        """Check that all attributes of a request in an autouse fixture
+        behave as expected when requested for a doctest item.
+        """
+        testdir.makeconftest('''
+            import pytest
+
+            @pytest.fixture(autouse=True, scope="{scope}")
+            def auto(request):
+                if "{scope}" == 'module':
+                    assert request.module is None
+                if "{scope}" == 'class':
+                    assert request.cls is None
+                if "{scope}" == 'function':
+                    assert request.function is None
+                return 99
+        '''.format(scope=scope))
+        testdir.maketxtfile(test_doc="""
+            >>> 1 + 1
+            2
+        """)
+        result = testdir.runpytest('--doctest-modules')
+        assert 'FAILURES' not in str(result.stdout.str())
+        result.stdout.fnmatch_lines(['*=== 1 passed in *'])
+
+
+class TestDoctestNamespaceFixture(object):
+
+    SCOPES = ['module', 'session', 'class', 'function']
+
+    @pytest.mark.parametrize('scope', SCOPES)
+    def test_namespace_doctestfile(self, testdir, scope):
+        """
+        Check that inserting something into the namespace works in a
+        simple text file doctest
+        """
+        testdir.makeconftest("""
+            import pytest
+            import contextlib
+
+            @pytest.fixture(autouse=True, scope="{scope}")
+            def add_contextlib(doctest_namespace):
+                doctest_namespace['cl'] = contextlib
+        """.format(scope=scope))
+        p = testdir.maketxtfile("""
+            >>> print(cl.__name__)
+            contextlib
+        """)
+        reprec = testdir.inline_run(p)
+        reprec.assertoutcome(passed=1)
+
+    @pytest.mark.parametrize('scope', SCOPES)
+    def test_namespace_pyfile(self, testdir, scope):
+        """
+        Check that inserting something into the namespace works in a
+        simple Python file docstring doctest
+        """
+        testdir.makeconftest("""
+            import pytest
+            import contextlib
+
+            @pytest.fixture(autouse=True, scope="{scope}")
+            def add_contextlib(doctest_namespace):
+                doctest_namespace['cl'] = contextlib
+        """.format(scope=scope))
+        p = testdir.makepyfile("""
+            def foo():
+                '''
+                >>> print(cl.__name__)
+                contextlib
+                '''
+        """)
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(passed=1)
+
+
+class TestDoctestReportingOption(object):
+    def _run_doctest_report(self, testdir, format):
+        testdir.makepyfile("""
+            def foo():
+                '''
+                >>> foo()
+                   a  b
+                0  1  4
+                1  2  4
+                2  3  6
+                '''
+                print('   a  b\\n'
+                      '0  1  4\\n'
+                      '1  2  5\\n'
+                      '2  3  6')
+            """)
+        return testdir.runpytest("--doctest-modules", "--doctest-report", format)
+
+    @pytest.mark.parametrize('format', ['udiff', 'UDIFF', 'uDiFf'])
+    def test_doctest_report_udiff(self, testdir, format):
+        result = self._run_doctest_report(testdir, format)
+        result.stdout.fnmatch_lines([
+            '     0  1  4',
+            '    -1  2  4',
+            '    +1  2  5',
+            '     2  3  6',
+        ])
+
+    def test_doctest_report_cdiff(self, testdir):
+        result = self._run_doctest_report(testdir, 'cdiff')
+        result.stdout.fnmatch_lines([
+            '         a  b',
+            '      0  1  4',
+            '    ! 1  2  4',
+            '      2  3  6',
+            '    --- 1,4 ----',
+            '         a  b',
+            '      0  1  4',
+            '    ! 1  2  5',
+            '      2  3  6',
+        ])
+
+    def test_doctest_report_ndiff(self, testdir):
+        result = self._run_doctest_report(testdir, 'ndiff')
+        result.stdout.fnmatch_lines([
+            '         a  b',
+            '      0  1  4',
+            '    - 1  2  4',
+            '    ?       ^',
+            '    + 1  2  5',
+            '    ?       ^',
+            '      2  3  6',
+        ])
+
+    @pytest.mark.parametrize('format', ['none', 'only_first_failure'])
+    def test_doctest_report_none_or_only_first_failure(self, testdir, format):
+        result = self._run_doctest_report(testdir, format)
+        result.stdout.fnmatch_lines([
+            'Expected:',
+            '       a  b',
+            '    0  1  4',
+            '    1  2  4',
+            '    2  3  6',
+            'Got:',
+            '       a  b',
+            '    0  1  4',
+            '    1  2  5',
+            '    2  3  6',
+        ])
+
+    def test_doctest_report_invalid(self, testdir):
+        result = self._run_doctest_report(testdir, 'obviously_invalid_format')
+        result.stderr.fnmatch_lines([
+            "*error: argument --doctest-report: invalid choice: 'obviously_invalid_format' (choose from*"
+        ])
diff --git a/tools/third_party/pytest/testing/test_entry_points.py b/tools/third_party/pytest/testing/test_entry_points.py
new file mode 100644
index 0000000..6ca68b4
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_entry_points.py
@@ -0,0 +1,14 @@
+from __future__ import absolute_import, division, print_function
+import pkg_resources
+
+import pytest
+
+
+@pytest.mark.parametrize("entrypoint", ['py.test', 'pytest'])
+def test_entry_point_exist(entrypoint):
+    assert entrypoint in pkg_resources.get_entry_map('pytest')['console_scripts']
+
+
+def test_pytest_entry_points_are_identical():
+    entryMap = pkg_resources.get_entry_map('pytest')['console_scripts']
+    assert entryMap['pytest'].module_name == entryMap['py.test'].module_name
diff --git a/tools/third_party/pytest/testing/test_helpconfig.py b/tools/third_party/pytest/testing/test_helpconfig.py
new file mode 100644
index 0000000..845005a
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_helpconfig.py
@@ -0,0 +1,77 @@
+from __future__ import absolute_import, division, print_function
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+import pytest
+
+
+def test_version(testdir, pytestconfig):
+    result = testdir.runpytest("--version")
+    assert result.ret == 0
+    # p = py.path.local(py.__file__).dirpath()
+    result.stderr.fnmatch_lines([
+        '*pytest*%s*imported from*' % (pytest.__version__, )
+    ])
+    if pytestconfig.pluginmanager.list_plugin_distinfo():
+        result.stderr.fnmatch_lines([
+            "*setuptools registered plugins:",
+            "*at*",
+        ])
+
+
+def test_help(testdir):
+    result = testdir.runpytest("--help")
+    assert result.ret == 0
+    result.stdout.fnmatch_lines("""
+        *-v*verbose*
+        *setup.cfg*
+        *minversion*
+        *to see*markers*pytest --markers*
+        *to see*fixtures*pytest --fixtures*
+    """)
+
+
+def test_hookvalidation_unknown(testdir):
+    testdir.makeconftest("""
+        def pytest_hello(xyz):
+            pass
+    """)
+    result = testdir.runpytest()
+    assert result.ret != 0
+    result.stdout.fnmatch_lines([
+        '*unknown hook*pytest_hello*'
+    ])
+
+
+def test_hookvalidation_optional(testdir):
+    testdir.makeconftest("""
+        import pytest
+        @pytest.hookimpl(optionalhook=True)
+        def pytest_hello(xyz):
+            pass
+    """)
+    result = testdir.runpytest()
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+
+
+def test_traceconfig(testdir):
+    result = testdir.runpytest("--traceconfig")
+    result.stdout.fnmatch_lines([
+        "*using*pytest*py*",
+        "*active plugins*",
+    ])
+
+
+def test_debug(testdir, monkeypatch):
+    result = testdir.runpytest_subprocess("--debug")
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+    p = testdir.tmpdir.join("pytestdebug.log")
+    assert "pytest_sessionstart" in p.read()
+
+
+def test_PYTEST_DEBUG(testdir, monkeypatch):
+    monkeypatch.setenv("PYTEST_DEBUG", "1")
+    result = testdir.runpytest_subprocess()
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+    result.stderr.fnmatch_lines([
+        "*pytest_plugin_registered*",
+        "*manager*PluginManager*"
+    ])
diff --git a/tools/third_party/pytest/testing/test_junitxml.py b/tools/third_party/pytest/testing/test_junitxml.py
new file mode 100644
index 0000000..b604c02
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_junitxml.py
@@ -0,0 +1,1062 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+from xml.dom import minidom
+import py
+import sys
+import os
+from _pytest.junitxml import LogXML
+import pytest
+
+
+def runandparse(testdir, *args):
+    resultpath = testdir.tmpdir.join("junit.xml")
+    result = testdir.runpytest("--junitxml=%s" % resultpath, *args)
+    xmldoc = minidom.parse(str(resultpath))
+    return result, DomNode(xmldoc)
+
+
+def assert_attr(node, **kwargs):
+    __tracebackhide__ = True
+
+    def nodeval(node, name):
+        anode = node.getAttributeNode(name)
+        if anode is not None:
+            return anode.value
+
+    expected = dict((name, str(value)) for name, value in kwargs.items())
+    on_node = dict((name, nodeval(node, name)) for name in expected)
+    assert on_node == expected
+
+
+class DomNode(object):
+    def __init__(self, dom):
+        self.__node = dom
+
+    def __repr__(self):
+        return self.__node.toxml()
+
+    def find_first_by_tag(self, tag):
+        return self.find_nth_by_tag(tag, 0)
+
+    def _by_tag(self, tag):
+        return self.__node.getElementsByTagName(tag)
+
+    def find_nth_by_tag(self, tag, n):
+        items = self._by_tag(tag)
+        try:
+            nth = items[n]
+        except IndexError:
+            pass
+        else:
+            return type(self)(nth)
+
+    def find_by_tag(self, tag):
+        t = type(self)
+        return [t(x) for x in self.__node.getElementsByTagName(tag)]
+
+    def __getitem__(self, key):
+        node = self.__node.getAttributeNode(key)
+        if node is not None:
+            return node.value
+
+    def assert_attr(self, **kwargs):
+        __tracebackhide__ = True
+        return assert_attr(self.__node, **kwargs)
+
+    def toxml(self):
+        return self.__node.toxml()
+
+    @property
+    def text(self):
+        return self.__node.childNodes[0].wholeText
+
+    @property
+    def tag(self):
+        return self.__node.tagName
+
+    @property
+    def next_siebling(self):
+        return type(self)(self.__node.nextSibling)
+
+
+class TestPython(object):
+    def test_summing_simple(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def test_pass():
+                pass
+            def test_fail():
+                assert 0
+            def test_skip():
+                pytest.skip("")
+            @pytest.mark.xfail
+            def test_xfail():
+                assert 0
+            @pytest.mark.xfail
+            def test_xpass():
+                assert 1
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(name="pytest", errors=0, failures=1, skips=2, tests=5)
+
+    def test_summing_simple_with_errors(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture
+            def fixture():
+                raise Exception()
+            def test_pass():
+                pass
+            def test_fail():
+                assert 0
+            def test_error(fixture):
+                pass
+            @pytest.mark.xfail
+            def test_xfail():
+                assert False
+            @pytest.mark.xfail(strict=True)
+            def test_xpass():
+                assert True
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(name="pytest", errors=1, failures=2, skips=1, tests=5)
+
+    def test_timing_function(self, testdir):
+        testdir.makepyfile("""
+            import time, pytest
+            def setup_module():
+                time.sleep(0.01)
+            def teardown_module():
+                time.sleep(0.01)
+            def test_sleep():
+                time.sleep(0.01)
+        """)
+        result, dom = runandparse(testdir)
+        node = dom.find_first_by_tag("testsuite")
+        tnode = node.find_first_by_tag("testcase")
+        val = tnode["time"]
+        assert round(float(val), 2) >= 0.03
+
+    def test_setup_error(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def arg(request):
+                raise ValueError()
+            def test_function(arg):
+                pass
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(errors=1, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_setup_error.py",
+            line="5",
+            classname="test_setup_error",
+            name="test_function")
+        fnode = tnode.find_first_by_tag("error")
+        fnode.assert_attr(message="test setup failure")
+        assert "ValueError" in fnode.toxml()
+
+    def test_teardown_error(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def arg():
+                yield
+                raise ValueError()
+            def test_function(arg):
+                pass
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_teardown_error.py",
+            line="6",
+            classname="test_teardown_error",
+            name="test_function")
+        fnode = tnode.find_first_by_tag("error")
+        fnode.assert_attr(message="test teardown failure")
+        assert "ValueError" in fnode.toxml()
+
+    def test_call_failure_teardown_error(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def arg():
+                yield
+                raise Exception("Teardown Exception")
+            def test_function(arg):
+                raise Exception("Call Exception")
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(errors=1, failures=1, tests=1)
+        first, second = dom.find_by_tag("testcase")
+        if not first or not second or first == second:
+            assert 0
+        fnode = first.find_first_by_tag("failure")
+        fnode.assert_attr(message="Exception: Call Exception")
+        snode = second.find_first_by_tag("error")
+        snode.assert_attr(message="test teardown failure")
+
+    def test_skip_contains_name_reason(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def test_skip():
+                pytest.skip("hello23")
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret == 0
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(skips=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_skip_contains_name_reason.py",
+            line="1",
+            classname="test_skip_contains_name_reason",
+            name="test_skip")
+        snode = tnode.find_first_by_tag("skipped")
+        snode.assert_attr(type="pytest.skip", message="hello23", )
+
+    def test_mark_skip_contains_name_reason(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip(reason="hello24")
+            def test_skip():
+                assert True
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret == 0
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(skips=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_mark_skip_contains_name_reason.py",
+            line="1",
+            classname="test_mark_skip_contains_name_reason",
+            name="test_skip")
+        snode = tnode.find_first_by_tag("skipped")
+        snode.assert_attr(type="pytest.skip", message="hello24", )
+
+    def test_mark_skipif_contains_name_reason(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            GLOBAL_CONDITION = True
+            @pytest.mark.skipif(GLOBAL_CONDITION, reason="hello25")
+            def test_skip():
+                assert True
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret == 0
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(skips=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_mark_skipif_contains_name_reason.py",
+            line="2",
+            classname="test_mark_skipif_contains_name_reason",
+            name="test_skip")
+        snode = tnode.find_first_by_tag("skipped")
+        snode.assert_attr(type="pytest.skip", message="hello25", )
+
+    def test_mark_skip_doesnt_capture_output(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip(reason="foo")
+            def test_skip():
+                print("bar!")
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret == 0
+        node_xml = dom.find_first_by_tag("testsuite").toxml()
+        assert "bar!" not in node_xml
+
+    def test_classname_instance(self, testdir):
+        testdir.makepyfile("""
+            class TestClass(object):
+                def test_method(self):
+                    assert 0
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(failures=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_classname_instance.py",
+            line="1",
+            classname="test_classname_instance.TestClass",
+            name="test_method")
+
+    def test_classname_nested_dir(self, testdir):
+        p = testdir.tmpdir.ensure("sub", "test_hello.py")
+        p.write("def test_func(): 0/0")
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(failures=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file=os.path.join("sub", "test_hello.py"),
+            line="0",
+            classname="sub.test_hello",
+            name="test_func")
+
+    def test_internal_error(self, testdir):
+        testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0")
+        testdir.makepyfile("def test_function(): pass")
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(errors=1, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(classname="pytest", name="internal")
+        fnode = tnode.find_first_by_tag("error")
+        fnode.assert_attr(message="internal error")
+        assert "Division" in fnode.toxml()
+
+    def test_failure_function(self, testdir):
+        testdir.makepyfile("""
+            import sys
+            def test_fail():
+                print ("hello-stdout")
+                sys.stderr.write("hello-stderr\\n")
+                raise ValueError(42)
+        """)
+
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(failures=1, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_failure_function.py",
+            line="1",
+            classname="test_failure_function",
+            name="test_fail")
+        fnode = tnode.find_first_by_tag("failure")
+        fnode.assert_attr(message="ValueError: 42")
+        assert "ValueError" in fnode.toxml()
+        systemout = fnode.next_siebling
+        assert systemout.tag == "system-out"
+        assert "hello-stdout" in systemout.toxml()
+        systemerr = systemout.next_siebling
+        assert systemerr.tag == "system-err"
+        assert "hello-stderr" in systemerr.toxml()
+
+    def test_failure_verbose_message(self, testdir):
+        testdir.makepyfile("""
+            import sys
+            def test_fail():
+                assert 0, "An error"
+        """)
+
+        result, dom = runandparse(testdir)
+        node = dom.find_first_by_tag("testsuite")
+        tnode = node.find_first_by_tag("testcase")
+        fnode = tnode.find_first_by_tag("failure")
+        fnode.assert_attr(message="AssertionError: An error assert 0")
+
+    def test_failure_escape(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.parametrize('arg1', "<&'", ids="<&'")
+            def test_func(arg1):
+                print(arg1)
+                assert 0
+        """)
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(failures=3, tests=3)
+
+        for index, char in enumerate("<&'"):
+
+            tnode = node.find_nth_by_tag("testcase", index)
+            tnode.assert_attr(
+                file="test_failure_escape.py",
+                line="1",
+                classname="test_failure_escape",
+                name="test_func[%s]" % char)
+            sysout = tnode.find_first_by_tag('system-out')
+            text = sysout.text
+            assert text == '%s\n' % char
+
+    def test_junit_prefixing(self, testdir):
+        testdir.makepyfile("""
+            def test_func():
+                assert 0
+            class TestHello(object):
+                def test_hello(self):
+                    pass
+        """)
+        result, dom = runandparse(testdir, "--junitprefix=xyz")
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(failures=1, tests=2)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_junit_prefixing.py",
+            line="0",
+            classname="xyz.test_junit_prefixing",
+            name="test_func")
+        tnode = node.find_nth_by_tag("testcase", 1)
+        tnode.assert_attr(
+            file="test_junit_prefixing.py",
+            line="3",
+            classname="xyz.test_junit_prefixing."
+            "TestHello",
+            name="test_hello")
+
+    def test_xfailure_function(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def test_xfail():
+                pytest.xfail("42")
+        """)
+        result, dom = runandparse(testdir)
+        assert not result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(skips=1, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_xfailure_function.py",
+            line="1",
+            classname="test_xfailure_function",
+            name="test_xfail")
+        fnode = tnode.find_first_by_tag("skipped")
+        fnode.assert_attr(message="expected test failure")
+        # assert "ValueError" in fnode.toxml()
+
+    def test_xfailure_xpass(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail
+            def test_xpass():
+                pass
+        """)
+        result, dom = runandparse(testdir)
+        # assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(skips=0, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_xfailure_xpass.py",
+            line="1",
+            classname="test_xfailure_xpass",
+            name="test_xpass")
+
+    def test_xfailure_xpass_strict(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail(strict=True, reason="This needs to fail!")
+            def test_xpass():
+                pass
+        """)
+        result, dom = runandparse(testdir)
+        # assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(skips=0, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_xfailure_xpass_strict.py",
+            line="1",
+            classname="test_xfailure_xpass_strict",
+            name="test_xpass")
+        fnode = tnode.find_first_by_tag("failure")
+        fnode.assert_attr(message="[XPASS(strict)] This needs to fail!")
+
+    def test_collect_error(self, testdir):
+        testdir.makepyfile("syntax error")
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(errors=1, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(
+            file="test_collect_error.py",
+            name="test_collect_error")
+        assert tnode["line"] is None
+        fnode = tnode.find_first_by_tag("error")
+        fnode.assert_attr(message="collection failure")
+        assert "SyntaxError" in fnode.toxml()
+
+    def test_unicode(self, testdir):
+        value = 'hx\xc4\x85\xc4\x87\n'
+        testdir.makepyfile("""
+            # coding: latin1
+            def test_hello():
+                print (%r)
+                assert 0
+        """ % value)
+        result, dom = runandparse(testdir)
+        assert result.ret == 1
+        tnode = dom.find_first_by_tag("testcase")
+        fnode = tnode.find_first_by_tag("failure")
+        if not sys.platform.startswith("java"):
+            assert "hx" in fnode.toxml()
+
+    def test_assertion_binchars(self, testdir):
+        """this test did fail when the escaping wasnt strict"""
+        testdir.makepyfile("""
+
+            M1 = '\x01\x02\x03\x04'
+            M2 = '\x01\x02\x03\x05'
+
+            def test_str_compare():
+                assert M1 == M2
+            """)
+        result, dom = runandparse(testdir)
+        print(dom.toxml())
+
+    def test_pass_captures_stdout(self, testdir):
+        testdir.makepyfile("""
+            def test_pass():
+                print('hello-stdout')
+        """)
+        result, dom = runandparse(testdir)
+        node = dom.find_first_by_tag("testsuite")
+        pnode = node.find_first_by_tag("testcase")
+        systemout = pnode.find_first_by_tag("system-out")
+        assert "hello-stdout" in systemout.toxml()
+
+    def test_pass_captures_stderr(self, testdir):
+        testdir.makepyfile("""
+            import sys
+            def test_pass():
+                sys.stderr.write('hello-stderr')
+        """)
+        result, dom = runandparse(testdir)
+        node = dom.find_first_by_tag("testsuite")
+        pnode = node.find_first_by_tag("testcase")
+        systemout = pnode.find_first_by_tag("system-err")
+        assert "hello-stderr" in systemout.toxml()
+
+    def test_setup_error_captures_stdout(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            @pytest.fixture
+            def arg(request):
+                print('hello-stdout')
+                raise ValueError()
+            def test_function(arg):
+                pass
+        """)
+        result, dom = runandparse(testdir)
+        node = dom.find_first_by_tag("testsuite")
+        pnode = node.find_first_by_tag("testcase")
+        systemout = pnode.find_first_by_tag("system-out")
+        assert "hello-stdout" in systemout.toxml()
+
+    def test_setup_error_captures_stderr(self, testdir):
+        testdir.makepyfile("""
+            import sys
+            import pytest
+
+            @pytest.fixture
+            def arg(request):
+                sys.stderr.write('hello-stderr')
+                raise ValueError()
+            def test_function(arg):
+                pass
+        """)
+        result, dom = runandparse(testdir)
+        node = dom.find_first_by_tag("testsuite")
+        pnode = node.find_first_by_tag("testcase")
+        systemout = pnode.find_first_by_tag("system-err")
+        assert "hello-stderr" in systemout.toxml()
+
+    def test_avoid_double_stdout(self, testdir):
+        testdir.makepyfile("""
+            import sys
+            import pytest
+
+            @pytest.fixture
+            def arg(request):
+                yield
+                sys.stdout.write('hello-stdout teardown')
+                raise ValueError()
+            def test_function(arg):
+                sys.stdout.write('hello-stdout call')
+        """)
+        result, dom = runandparse(testdir)
+        node = dom.find_first_by_tag("testsuite")
+        pnode = node.find_first_by_tag("testcase")
+        systemout = pnode.find_first_by_tag("system-out")
+        assert "hello-stdout call" in systemout.toxml()
+        assert "hello-stdout teardown" in systemout.toxml()
+
+
+def test_mangle_test_address():
+    from _pytest.junitxml import mangle_test_address
+    address = '::'.join(
+        ["a/my.py.thing.py", "Class", "()", "method", "[a-1-::]"])
+    newnames = mangle_test_address(address)
+    assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"]
+
+
+def test_dont_configure_on_slaves(tmpdir):
+    gotten = []
+
+    class FakeConfig(object):
+        def __init__(self):
+            self.pluginmanager = self
+            self.option = self
+
+        def getini(self, name):
+            return "pytest"
+
+        junitprefix = None
+        # XXX: shouldnt need tmpdir ?
+        xmlpath = str(tmpdir.join('junix.xml'))
+        register = gotten.append
+
+    fake_config = FakeConfig()
+    from _pytest import junitxml
+    junitxml.pytest_configure(fake_config)
+    assert len(gotten) == 1
+    FakeConfig.slaveinput = None
+    junitxml.pytest_configure(fake_config)
+    assert len(gotten) == 1
+
+
+class TestNonPython(object):
+    def test_summing_simple(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            def pytest_collect_file(path, parent):
+                if path.ext == ".xyz":
+                    return MyItem(path, parent)
+            class MyItem(pytest.Item):
+                def __init__(self, path, parent):
+                    super(MyItem, self).__init__(path.basename, parent)
+                    self.fspath = path
+                def runtest(self):
+                    raise ValueError(42)
+                def repr_failure(self, excinfo):
+                    return "custom item runtest failed"
+        """)
+        testdir.tmpdir.join("myfile.xyz").write("hello")
+        result, dom = runandparse(testdir)
+        assert result.ret
+        node = dom.find_first_by_tag("testsuite")
+        node.assert_attr(errors=0, failures=1, skips=0, tests=1)
+        tnode = node.find_first_by_tag("testcase")
+        tnode.assert_attr(name="myfile.xyz")
+        fnode = tnode.find_first_by_tag("failure")
+        fnode.assert_attr(message="custom item runtest failed")
+        assert "custom item runtest failed" in fnode.toxml()
+
+
+def test_nullbyte(testdir):
+    # A null byte can not occur in XML (see section 2.2 of the spec)
+    testdir.makepyfile("""
+        import sys
+        def test_print_nullbyte():
+            sys.stdout.write('Here the null -->' + chr(0) + '<--')
+            sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')
+            assert False
+    """)
+    xmlf = testdir.tmpdir.join('junit.xml')
+    testdir.runpytest('--junitxml=%s' % xmlf)
+    text = xmlf.read()
+    assert '\x00' not in text
+    assert '#x00' in text
+
+
+def test_nullbyte_replace(testdir):
+    # Check if the null byte gets replaced
+    testdir.makepyfile("""
+        import sys
+        def test_print_nullbyte():
+            sys.stdout.write('Here the null -->' + chr(0) + '<--')
+            sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')
+            assert False
+    """)
+    xmlf = testdir.tmpdir.join('junit.xml')
+    testdir.runpytest('--junitxml=%s' % xmlf)
+    text = xmlf.read()
+    assert '#x0' in text
+
+
+def test_invalid_xml_escape():
+    # Test some more invalid xml chars, the full range should be
+    # tested really but let's just thest the edges of the ranges
+    # intead.
+    # XXX This only tests low unicode character points for now as
+    #     there are some issues with the testing infrastructure for
+    #     the higher ones.
+    # XXX Testing 0xD (\r) is tricky as it overwrites the just written
+    #     line in the output, so we skip it too.
+    global unichr
+    try:
+        unichr(65)
+    except NameError:
+        unichr = chr
+    invalid = (0x00, 0x1, 0xB, 0xC, 0xE, 0x19, 27,  # issue #126
+               0xD800, 0xDFFF, 0xFFFE, 0x0FFFF)  # , 0x110000)
+    valid = (0x9, 0xA, 0x20, )
+    # 0xD, 0xD7FF, 0xE000, 0xFFFD, 0x10000, 0x10FFFF)
+
+    from _pytest.junitxml import bin_xml_escape
+
+    for i in invalid:
+        got = bin_xml_escape(unichr(i)).uniobj
+        if i <= 0xFF:
+            expected = '#x%02X' % i
+        else:
+            expected = '#x%04X' % i
+        assert got == expected
+    for i in valid:
+        assert chr(i) == bin_xml_escape(unichr(i)).uniobj
+
+
+def test_logxml_path_expansion(tmpdir, monkeypatch):
+    home_tilde = py.path.local(os.path.expanduser('~')).join('test.xml')
+
+    xml_tilde = LogXML('~%stest.xml' % tmpdir.sep, None)
+    assert xml_tilde.logfile == home_tilde
+
+    # this is here for when $HOME is not set correct
+    monkeypatch.setenv("HOME", tmpdir)
+    home_var = os.path.normpath(os.path.expandvars('$HOME/test.xml'))
+
+    xml_var = LogXML('$HOME%stest.xml' % tmpdir.sep, None)
+    assert xml_var.logfile == home_var
+
+
+def test_logxml_changingdir(testdir):
+    testdir.makepyfile("""
+        def test_func():
+            import os
+            os.chdir("a")
+    """)
+    testdir.tmpdir.mkdir("a")
+    result = testdir.runpytest("--junitxml=a/x.xml")
+    assert result.ret == 0
+    assert testdir.tmpdir.join("a/x.xml").check()
+
+
+def test_logxml_makedir(testdir):
+    """--junitxml should automatically create directories for the xml file"""
+    testdir.makepyfile("""
+        def test_pass():
+            pass
+    """)
+    result = testdir.runpytest("--junitxml=path/to/results.xml")
+    assert result.ret == 0
+    assert testdir.tmpdir.join("path/to/results.xml").check()
+
+
+def test_logxml_check_isdir(testdir):
+    """Give an error if --junit-xml is a directory (#2089)"""
+    result = testdir.runpytest("--junit-xml=.")
+    result.stderr.fnmatch_lines(["*--junitxml must be a filename*"])
+
+
+def test_escaped_parametrized_names_xml(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.parametrize('char', [u"\\x00"])
+        def test_func(char):
+            assert char
+    """)
+    result, dom = runandparse(testdir)
+    assert result.ret == 0
+    node = dom.find_first_by_tag("testcase")
+    node.assert_attr(name="test_func[\\x00]")
+
+
+def test_double_colon_split_function_issue469(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.parametrize('param', ["double::colon"])
+        def test_func(param):
+            pass
+    """)
+    result, dom = runandparse(testdir)
+    assert result.ret == 0
+    node = dom.find_first_by_tag("testcase")
+    node.assert_attr(classname="test_double_colon_split_function_issue469")
+    node.assert_attr(name='test_func[double::colon]')
+
+
+def test_double_colon_split_method_issue469(testdir):
+    testdir.makepyfile("""
+        import pytest
+        class TestClass(object):
+            @pytest.mark.parametrize('param', ["double::colon"])
+            def test_func(self, param):
+                pass
+    """)
+    result, dom = runandparse(testdir)
+    assert result.ret == 0
+    node = dom.find_first_by_tag("testcase")
+    node.assert_attr(
+        classname="test_double_colon_split_method_issue469.TestClass")
+    node.assert_attr(name='test_func[double::colon]')
+
+
+def test_unicode_issue368(testdir):
+    path = testdir.tmpdir.join("test.xml")
+    log = LogXML(str(path), None)
+    ustr = py.builtin._totext("ВНИ!", "utf-8")
+    from _pytest.runner import BaseReport
+
+    class Report(BaseReport):
+        longrepr = ustr
+        sections = []
+        nodeid = "something"
+        location = 'tests/filename.py', 42, 'TestClass.method'
+
+    test_report = Report()
+
+    # hopefully this is not too brittle ...
+    log.pytest_sessionstart()
+    node_reporter = log._opentestcase(test_report)
+    node_reporter.append_failure(test_report)
+    node_reporter.append_collect_error(test_report)
+    node_reporter.append_collect_skipped(test_report)
+    node_reporter.append_error(test_report)
+    test_report.longrepr = "filename", 1, ustr
+    node_reporter.append_skipped(test_report)
+    test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣"
+    node_reporter.append_skipped(test_report)
+    test_report.wasxfail = ustr
+    node_reporter.append_skipped(test_report)
+    log.pytest_sessionfinish()
+
+
+def test_record_property(testdir):
+    testdir.makepyfile("""
+        import pytest
+
+        @pytest.fixture
+        def other(record_xml_property):
+            record_xml_property("bar", 1)
+        def test_record(record_xml_property, other):
+            record_xml_property("foo", "<1");
+    """)
+    result, dom = runandparse(testdir, '-rw')
+    node = dom.find_first_by_tag("testsuite")
+    tnode = node.find_first_by_tag("testcase")
+    psnode = tnode.find_first_by_tag('properties')
+    pnodes = psnode.find_by_tag('property')
+    pnodes[0].assert_attr(name="bar", value="1")
+    pnodes[1].assert_attr(name="foo", value="<1")
+    result.stdout.fnmatch_lines([
+        'test_record_property.py::test_record',
+        '*record_xml_property*experimental*',
+    ])
+
+
+def test_record_property_same_name(testdir):
+    testdir.makepyfile("""
+        def test_record_with_same_name(record_xml_property):
+            record_xml_property("foo", "bar")
+            record_xml_property("foo", "baz")
+    """)
+    result, dom = runandparse(testdir, '-rw')
+    node = dom.find_first_by_tag("testsuite")
+    tnode = node.find_first_by_tag("testcase")
+    psnode = tnode.find_first_by_tag('properties')
+    pnodes = psnode.find_by_tag('property')
+    pnodes[0].assert_attr(name="foo", value="bar")
+    pnodes[1].assert_attr(name="foo", value="baz")
+
+
+def test_random_report_log_xdist(testdir):
+    """xdist calls pytest_runtest_logreport as they are executed by the slaves,
+    with nodes from several nodes overlapping, so junitxml must cope with that
+    to produce correct reports. #1064
+    """
+    pytest.importorskip('xdist')
+    testdir.makepyfile("""
+        import pytest, time
+        @pytest.mark.parametrize('i', list(range(30)))
+        def test_x(i):
+            assert i != 22
+    """)
+    _, dom = runandparse(testdir, '-n2')
+    suite_node = dom.find_first_by_tag("testsuite")
+    failed = []
+    for case_node in suite_node.find_by_tag("testcase"):
+        if case_node.find_first_by_tag('failure'):
+            failed.append(case_node['name'])
+
+    assert failed == ['test_x[22]']
+
+
+def test_runs_twice(testdir):
+    f = testdir.makepyfile('''
+        def test_pass():
+            pass
+    ''')
+
+    result, dom = runandparse(testdir, f, f)
+    assert 'INTERNALERROR' not in result.stdout.str()
+    first, second = [x['classname'] for x in dom.find_by_tag("testcase")]
+    assert first == second
+
+
+@pytest.mark.xfail(reason='hangs', run=False)
+def test_runs_twice_xdist(testdir):
+    pytest.importorskip('xdist')
+    f = testdir.makepyfile('''
+        def test_pass():
+            pass
+    ''')
+
+    result, dom = runandparse(
+        testdir, f,
+        '--dist', 'each', '--tx', '2*popen',)
+    assert 'INTERNALERROR' not in result.stdout.str()
+    first, second = [x['classname'] for x in dom.find_by_tag("testcase")]
+    assert first == second
+
+
+def test_fancy_items_regression(testdir):
+    # issue 1259
+    testdir.makeconftest("""
+        import pytest
+        class FunItem(pytest.Item):
+            def runtest(self):
+                pass
+        class NoFunItem(pytest.Item):
+            def runtest(self):
+                pass
+
+        class FunCollector(pytest.File):
+            def collect(self):
+                return [
+                    FunItem('a', self),
+                    NoFunItem('a', self),
+                    NoFunItem('b', self),
+                ]
+
+        def pytest_collect_file(path, parent):
+            if path.check(ext='.py'):
+                return FunCollector(path, parent)
+    """)
+
+    testdir.makepyfile('''
+        def test_pass():
+            pass
+    ''')
+
+    result, dom = runandparse(testdir)
+
+    assert 'INTERNALERROR' not in result.stdout.str()
+
+    items = sorted(
+        '%(classname)s %(name)s %(file)s' % x
+
+        for x in dom.find_by_tag("testcase"))
+    import pprint
+    pprint.pprint(items)
+    assert items == [
+        u'conftest a conftest.py',
+        u'conftest a conftest.py',
+        u'conftest b conftest.py',
+        u'test_fancy_items_regression a test_fancy_items_regression.py',
+        u'test_fancy_items_regression a test_fancy_items_regression.py',
+        u'test_fancy_items_regression b test_fancy_items_regression.py',
+        u'test_fancy_items_regression test_pass'
+        u' test_fancy_items_regression.py',
+    ]
+
+
+def test_global_properties(testdir):
+    path = testdir.tmpdir.join("test_global_properties.xml")
+    log = LogXML(str(path), None)
+    from _pytest.runner import BaseReport
+
+    class Report(BaseReport):
+        sections = []
+        nodeid = "test_node_id"
+
+    log.pytest_sessionstart()
+    log.add_global_property('foo', 1)
+    log.add_global_property('bar', 2)
+    log.pytest_sessionfinish()
+
+    dom = minidom.parse(str(path))
+
+    properties = dom.getElementsByTagName('properties')
+
+    assert (properties.length == 1), "There must be one <properties> node"
+
+    property_list = dom.getElementsByTagName('property')
+
+    assert (property_list.length == 2), "There most be only 2 property nodes"
+
+    expected = {'foo': '1', 'bar': '2'}
+    actual = {}
+
+    for p in property_list:
+        k = str(p.getAttribute('name'))
+        v = str(p.getAttribute('value'))
+        actual[k] = v
+
+    assert actual == expected
+
+
+def test_url_property(testdir):
+    test_url = "http://www.github.com/pytest-dev"
+    path = testdir.tmpdir.join("test_url_property.xml")
+    log = LogXML(str(path), None)
+    from _pytest.runner import BaseReport
+
+    class Report(BaseReport):
+        longrepr = "FooBarBaz"
+        sections = []
+        nodeid = "something"
+        location = 'tests/filename.py', 42, 'TestClass.method'
+        url = test_url
+
+    test_report = Report()
+
+    log.pytest_sessionstart()
+    node_reporter = log._opentestcase(test_report)
+    node_reporter.append_failure(test_report)
+    log.pytest_sessionfinish()
+
+    test_case = minidom.parse(str(path)).getElementsByTagName('testcase')[0]
+
+    assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml"
+
+
+@pytest.mark.parametrize('suite_name', ['my_suite', ''])
+def test_set_suite_name(testdir, suite_name):
+    if suite_name:
+        testdir.makeini("""
+            [pytest]
+            junit_suite_name={0}
+        """.format(suite_name))
+        expected = suite_name
+    else:
+        expected = 'pytest'
+    testdir.makepyfile("""
+        import pytest
+
+        def test_func():
+            pass
+    """)
+    result, dom = runandparse(testdir)
+    assert result.ret == 0
+    node = dom.find_first_by_tag("testsuite")
+    node.assert_attr(name=expected)
diff --git a/tools/third_party/pytest/testing/test_mark.py b/tools/third_party/pytest/testing/test_mark.py
new file mode 100644
index 0000000..46bf0b0
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_mark.py
@@ -0,0 +1,878 @@
+from __future__ import absolute_import, division, print_function
+import os
+import sys
+
+import pytest
+from _pytest.mark import MarkGenerator as Mark, ParameterSet, transfer_markers
+
+
+class TestMark(object):
+    def test_markinfo_repr(self):
+        from _pytest.mark import MarkInfo, Mark
+        m = MarkInfo(Mark("hello", (1, 2), {}))
+        repr(m)
+
+    @pytest.mark.parametrize('attr', ['mark', 'param'])
+    @pytest.mark.parametrize('modulename', ['py.test', 'pytest'])
+    def test_pytest_exists_in_namespace_all(self, attr, modulename):
+        module = sys.modules[modulename]
+        assert attr in module.__all__
+
+    def test_pytest_mark_notcallable(self):
+        mark = Mark()
+        pytest.raises((AttributeError, TypeError), mark)
+
+    def test_mark_with_param(self):
+        def some_function(abc):
+            pass
+
+        class SomeClass(object):
+            pass
+
+        assert pytest.mark.fun(some_function) is some_function
+        assert pytest.mark.fun.with_args(some_function) is not some_function
+
+        assert pytest.mark.fun(SomeClass) is SomeClass
+        assert pytest.mark.fun.with_args(SomeClass) is not SomeClass
+
+    def test_pytest_mark_name_starts_with_underscore(self):
+        mark = Mark()
+        pytest.raises(AttributeError, getattr, mark, '_some_name')
+
+    def test_pytest_mark_bare(self):
+        mark = Mark()
+
+        def f():
+            pass
+
+        mark.hello(f)
+        assert f.hello
+
+    def test_pytest_mark_keywords(self):
+        mark = Mark()
+
+        def f():
+            pass
+
+        mark.world(x=3, y=4)(f)
+        assert f.world
+        assert f.world.kwargs['x'] == 3
+        assert f.world.kwargs['y'] == 4
+
+    def test_apply_multiple_and_merge(self):
+        mark = Mark()
+
+        def f():
+            pass
+
+        mark.world
+        mark.world(x=3)(f)
+        assert f.world.kwargs['x'] == 3
+        mark.world(y=4)(f)
+        assert f.world.kwargs['x'] == 3
+        assert f.world.kwargs['y'] == 4
+        mark.world(y=1)(f)
+        assert f.world.kwargs['y'] == 1
+        assert len(f.world.args) == 0
+
+    def test_pytest_mark_positional(self):
+        mark = Mark()
+
+        def f():
+            pass
+
+        mark.world("hello")(f)
+        assert f.world.args[0] == "hello"
+        mark.world("world")(f)
+
+    def test_pytest_mark_positional_func_and_keyword(self):
+        mark = Mark()
+
+        def f():
+            raise Exception
+
+        m = mark.world(f, omega="hello")
+
+        def g():
+            pass
+
+        assert m(g) == g
+        assert g.world.args[0] is f
+        assert g.world.kwargs["omega"] == "hello"
+
+    def test_pytest_mark_reuse(self):
+        mark = Mark()
+
+        def f():
+            pass
+
+        w = mark.some
+        w("hello", reason="123")(f)
+        assert f.some.args[0] == "hello"
+        assert f.some.kwargs['reason'] == "123"
+
+        def g():
+            pass
+
+        w("world", reason2="456")(g)
+        assert g.some.args[0] == "world"
+        assert 'reason' not in g.some.kwargs
+        assert g.some.kwargs['reason2'] == "456"
+
+
+def test_marked_class_run_twice(testdir, request):
+    """Test fails file is run twice that contains marked class.
+    See issue#683.
+    """
+    py_file = testdir.makepyfile("""
+    import pytest
+    @pytest.mark.parametrize('abc', [1, 2, 3])
+    class Test1(object):
+        def test_1(self, abc):
+            assert abc in [1, 2, 3]
+    """)
+    file_name = os.path.basename(py_file.strpath)
+    rec = testdir.inline_run(file_name, file_name)
+    rec.assertoutcome(passed=6)
+
+
+def test_ini_markers(testdir):
+    testdir.makeini("""
+        [pytest]
+        markers =
+            a1: this is a webtest marker
+            a2: this is a smoke marker
+    """)
+    testdir.makepyfile("""
+        def test_markers(pytestconfig):
+            markers = pytestconfig.getini("markers")
+            print (markers)
+            assert len(markers) >= 2
+            assert markers[0].startswith("a1:")
+            assert markers[1].startswith("a2:")
+    """)
+    rec = testdir.inline_run()
+    rec.assertoutcome(passed=1)
+
+
+def test_markers_option(testdir):
+    testdir.makeini("""
+        [pytest]
+        markers =
+            a1: this is a webtest marker
+            a1some: another marker
+            nodescription
+    """)
+    result = testdir.runpytest("--markers", )
+    result.stdout.fnmatch_lines([
+        "*a1*this is a webtest*",
+        "*a1some*another marker",
+        "*nodescription*",
+    ])
+
+
+def test_ini_markers_whitespace(testdir):
+    testdir.makeini("""
+        [pytest]
+        markers =
+            a1 : this is a whitespace marker
+    """)
+    testdir.makepyfile("""
+        import pytest
+
+        @pytest.mark.a1
+        def test_markers():
+            assert True
+    """)
+    rec = testdir.inline_run("--strict", "-m", "a1")
+    rec.assertoutcome(passed=1)
+
+
+def test_marker_without_description(testdir):
+    testdir.makefile(".cfg", setup="""
+        [tool:pytest]
+        markers=slow
+    """)
+    testdir.makeconftest("""
+        import pytest
+        pytest.mark.xfail('FAIL')
+    """)
+    ftdir = testdir.mkdir("ft1_dummy")
+    testdir.tmpdir.join("conftest.py").move(ftdir.join("conftest.py"))
+    rec = testdir.runpytest_subprocess("--strict")
+    rec.assert_outcomes()
+
+
+def test_markers_option_with_plugin_in_current_dir(testdir):
+    testdir.makeconftest('pytest_plugins = "flip_flop"')
+    testdir.makepyfile(flip_flop="""\
+        def pytest_configure(config):
+            config.addinivalue_line("markers", "flip:flop")
+
+        def pytest_generate_tests(metafunc):
+            try:
+                mark = metafunc.function.flipper
+            except AttributeError:
+                return
+            metafunc.parametrize("x", (10, 20))""")
+    testdir.makepyfile("""\
+        import pytest
+        @pytest.mark.flipper
+        def test_example(x):
+            assert x""")
+
+    result = testdir.runpytest("--markers")
+    result.stdout.fnmatch_lines(["*flip*flop*"])
+
+
+def test_mark_on_pseudo_function(testdir):
+    testdir.makepyfile("""
+        import pytest
+
+        @pytest.mark.r(lambda x: 0/0)
+        def test_hello():
+            pass
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+def test_strict_prohibits_unregistered_markers(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.unregisteredmark
+        def test_hello():
+            pass
+    """)
+    result = testdir.runpytest("--strict")
+    assert result.ret != 0
+    result.stdout.fnmatch_lines([
+        "*unregisteredmark*not*registered*",
+    ])
+
+
+@pytest.mark.parametrize("spec", [
+    ("xyz", ("test_one",)),
+    ("xyz and xyz2", ()),
+    ("xyz2", ("test_two",)),
+    ("xyz or xyz2", ("test_one", "test_two"),)
+])
+def test_mark_option(spec, testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.xyz
+        def test_one():
+            pass
+        @pytest.mark.xyz2
+        def test_two():
+            pass
+    """)
+    opt, passed_result = spec
+    rec = testdir.inline_run("-m", opt)
+    passed, skipped, fail = rec.listoutcomes()
+    passed = [x.nodeid.split("::")[-1] for x in passed]
+    assert len(passed) == len(passed_result)
+    assert list(passed) == list(passed_result)
+
+
+@pytest.mark.parametrize("spec", [
+    ("interface", ("test_interface",)),
+    ("not interface", ("test_nointer",)),
+])
+def test_mark_option_custom(spec, testdir):
+    testdir.makeconftest("""
+        import pytest
+        def pytest_collection_modifyitems(items):
+            for item in items:
+                if "interface" in item.nodeid:
+                    item.keywords["interface"] = pytest.mark.interface
+    """)
+    testdir.makepyfile("""
+        def test_interface():
+            pass
+        def test_nointer():
+            pass
+    """)
+    opt, passed_result = spec
+    rec = testdir.inline_run("-m", opt)
+    passed, skipped, fail = rec.listoutcomes()
+    passed = [x.nodeid.split("::")[-1] for x in passed]
+    assert len(passed) == len(passed_result)
+    assert list(passed) == list(passed_result)
+
+
+@pytest.mark.parametrize("spec", [
+    ("interface", ("test_interface",)),
+    ("not interface", ("test_nointer", "test_pass")),
+    ("pass", ("test_pass",)),
+    ("not pass", ("test_interface", "test_nointer")),
+])
+def test_keyword_option_custom(spec, testdir):
+    testdir.makepyfile("""
+        def test_interface():
+            pass
+        def test_nointer():
+            pass
+        def test_pass():
+            pass
+    """)
+    opt, passed_result = spec
+    rec = testdir.inline_run("-k", opt)
+    passed, skipped, fail = rec.listoutcomes()
+    passed = [x.nodeid.split("::")[-1] for x in passed]
+    assert len(passed) == len(passed_result)
+    assert list(passed) == list(passed_result)
+
+
+@pytest.mark.parametrize("spec", [
+    ("None", ("test_func[None]",)),
+    ("1.3", ("test_func[1.3]",)),
+    ("2-3", ("test_func[2-3]",))
+])
+def test_keyword_option_parametrize(spec, testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.parametrize("arg", [None, 1.3, "2-3"])
+        def test_func(arg):
+            pass
+    """)
+    opt, passed_result = spec
+    rec = testdir.inline_run("-k", opt)
+    passed, skipped, fail = rec.listoutcomes()
+    passed = [x.nodeid.split("::")[-1] for x in passed]
+    assert len(passed) == len(passed_result)
+    assert list(passed) == list(passed_result)
+
+
+def test_parametrized_collected_from_command_line(testdir):
+    """Parametrized test not collected if test named specified
+       in command line issue#649.
+    """
+    py_file = testdir.makepyfile("""
+        import pytest
+        @pytest.mark.parametrize("arg", [None, 1.3, "2-3"])
+        def test_func(arg):
+            pass
+    """)
+    file_name = os.path.basename(py_file.strpath)
+    rec = testdir.inline_run(file_name + "::" + "test_func")
+    rec.assertoutcome(passed=3)
+
+
+def test_parametrized_collect_with_wrong_args(testdir):
+    """Test collect parametrized func with wrong number of args."""
+    py_file = testdir.makepyfile("""
+        import pytest
+
+        @pytest.mark.parametrize('foo, bar', [(1, 2, 3)])
+        def test_func(foo, bar):
+            pass
+    """)
+
+    result = testdir.runpytest(py_file)
+    result.stdout.fnmatch_lines([
+        'E   ValueError: In "parametrize" the number of values ((1, 2, 3)) '
+        'must be equal to the number of names ([\'foo\', \'bar\'])'
+    ])
+
+
+def test_parametrized_with_kwargs(testdir):
+    """Test collect parametrized func with wrong number of args."""
+    py_file = testdir.makepyfile("""
+        import pytest
+
+        @pytest.fixture(params=[1,2])
+        def a(request):
+            return request.param
+
+        @pytest.mark.parametrize(argnames='b', argvalues=[1, 2])
+        def test_func(a, b):
+            pass
+    """)
+
+    result = testdir.runpytest(py_file)
+    assert(result.ret == 0)
+
+
+class TestFunctional(object):
+
+    def test_mark_per_function(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.mark.hello
+            def test_hello():
+                assert hasattr(test_hello, 'hello')
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_mark_per_module(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            pytestmark = pytest.mark.hello
+            def test_func():
+                pass
+        """)
+        keywords = item.keywords
+        assert 'hello' in keywords
+
+    def test_marklist_per_class(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            class TestClass(object):
+                pytestmark = [pytest.mark.hello, pytest.mark.world]
+                def test_func(self):
+                    assert TestClass.test_func.hello
+                    assert TestClass.test_func.world
+        """)
+        keywords = item.keywords
+        assert 'hello' in keywords
+
+    def test_marklist_per_module(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            pytestmark = [pytest.mark.hello, pytest.mark.world]
+            class TestClass(object):
+                def test_func(self):
+                    assert TestClass.test_func.hello
+                    assert TestClass.test_func.world
+        """)
+        keywords = item.keywords
+        assert 'hello' in keywords
+        assert 'world' in keywords
+
+    def test_mark_per_class_decorator(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.hello
+            class TestClass(object):
+                def test_func(self):
+                    assert TestClass.test_func.hello
+        """)
+        keywords = item.keywords
+        assert 'hello' in keywords
+
+    def test_mark_per_class_decorator_plus_existing_dec(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.hello
+            class TestClass(object):
+                pytestmark = pytest.mark.world
+                def test_func(self):
+                    assert TestClass.test_func.hello
+                    assert TestClass.test_func.world
+        """)
+        keywords = item.keywords
+        assert 'hello' in keywords
+        assert 'world' in keywords
+
+    def test_merging_markers(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            pytestmark = pytest.mark.hello("pos1", x=1, y=2)
+            class TestClass(object):
+                # classlevel overrides module level
+                pytestmark = pytest.mark.hello(x=3)
+                @pytest.mark.hello("pos0", z=4)
+                def test_func(self):
+                    pass
+        """)
+        items, rec = testdir.inline_genitems(p)
+        item, = items
+        keywords = item.keywords
+        marker = keywords['hello']
+        assert marker.args == ("pos0", "pos1")
+        assert marker.kwargs == {'x': 1, 'y': 2, 'z': 4}
+
+        # test the new __iter__ interface
+        values = list(marker)
+        assert len(values) == 3
+        assert values[0].args == ("pos0",)
+        assert values[1].args == ()
+        assert values[2].args == ("pos1", )
+
+    @pytest.mark.xfail(reason='unfixed')
+    def test_merging_markers_deep(self, testdir):
+        # issue 199 - propagate markers into nested classes
+        p = testdir.makepyfile("""
+            import pytest
+            class TestA(object):
+                pytestmark = pytest.mark.a
+                def test_b(self):
+                    assert True
+                class TestC(object):
+                    # this one didnt get marked
+                    def test_d(self):
+                        assert True
+        """)
+        items, rec = testdir.inline_genitems(p)
+        for item in items:
+            print(item, item.keywords)
+            assert 'a' in item.keywords
+
+    def test_mark_decorator_subclass_does_not_propagate_to_base(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+
+            @pytest.mark.a
+            class Base(object): pass
+
+            @pytest.mark.b
+            class Test1(Base):
+                def test_foo(self): pass
+
+            class Test2(Base):
+                def test_bar(self): pass
+        """)
+        items, rec = testdir.inline_genitems(p)
+        self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',))
+
+    @pytest.mark.issue568
+    @pytest.mark.xfail(reason="markers smear on methods of base classes")
+    def test_mark_should_not_pass_to_siebling_class(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+
+            class TestBase(object):
+                def test_foo(self):
+                    pass
+
+            @pytest.mark.b
+            class TestSub(TestBase):
+                pass
+
+
+            class TestOtherSub(TestBase):
+                pass
+
+        """)
+        items, rec = testdir.inline_genitems(p)
+        base_item, sub_item, sub_item_other = items
+        assert not hasattr(base_item.obj, 'b')
+        assert not hasattr(sub_item_other.obj, 'b')
+
+    def test_mark_decorator_baseclasses_merged(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+
+            @pytest.mark.a
+            class Base(object): pass
+
+            @pytest.mark.b
+            class Base2(Base): pass
+
+            @pytest.mark.c
+            class Test1(Base2):
+                def test_foo(self): pass
+
+            class Test2(Base2):
+                @pytest.mark.d
+                def test_bar(self): pass
+        """)
+        items, rec = testdir.inline_genitems(p)
+        self.assert_markers(items, test_foo=('a', 'b', 'c'),
+                            test_bar=('a', 'b', 'd'))
+
+    def test_mark_with_wrong_marker(self, testdir):
+        reprec = testdir.inline_runsource("""
+                import pytest
+                class pytestmark(object):
+                    pass
+                def test_func():
+                    pass
+        """)
+        values = reprec.getfailedcollections()
+        assert len(values) == 1
+        assert "TypeError" in str(values[0].longrepr)
+
+    def test_mark_dynamically_in_funcarg(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            @pytest.fixture
+            def arg(request):
+                request.applymarker(pytest.mark.hello)
+            def pytest_terminal_summary(terminalreporter):
+                values = terminalreporter.stats['passed']
+                terminalreporter._tw.line("keyword: %s" % values[0].keywords)
+        """)
+        testdir.makepyfile("""
+            def test_func(arg):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "keyword: *hello*"
+        ])
+
+    def test_merging_markers_two_functions(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.mark.hello("pos1", z=4)
+            @pytest.mark.hello("pos0", z=3)
+            def test_func():
+                pass
+        """)
+        items, rec = testdir.inline_genitems(p)
+        item, = items
+        keywords = item.keywords
+        marker = keywords['hello']
+        values = list(marker)
+        assert len(values) == 2
+        assert values[0].args == ("pos0",)
+        assert values[1].args == ("pos1",)
+
+    def test_no_marker_match_on_unmarked_names(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.mark.shouldmatch
+            def test_marked():
+                assert 1
+
+            def test_unmarked():
+                assert 1
+        """)
+        reprec = testdir.inline_run("-m", "test_unmarked", p)
+        passed, skipped, failed = reprec.listoutcomes()
+        assert len(passed) + len(skipped) + len(failed) == 0
+        dlist = reprec.getcalls("pytest_deselected")
+        deselected_tests = dlist[0].items
+        assert len(deselected_tests) == 2
+
+    def test_keywords_at_node_level(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.fixture(scope="session", autouse=True)
+            def some(request):
+                request.keywords["hello"] = 42
+                assert "world" not in request.keywords
+
+            @pytest.fixture(scope="function", autouse=True)
+            def funcsetup(request):
+                assert "world" in request.keywords
+                assert "hello" in  request.keywords
+
+            @pytest.mark.world
+            def test_function():
+                pass
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_keyword_added_for_session(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            def pytest_collection_modifyitems(session):
+                session.add_marker("mark1")
+                session.add_marker(pytest.mark.mark2)
+                session.add_marker(pytest.mark.mark3)
+                pytest.raises(ValueError, lambda:
+                        session.add_marker(10))
+        """)
+        testdir.makepyfile("""
+            def test_some(request):
+                assert "mark1" in request.keywords
+                assert "mark2" in request.keywords
+                assert "mark3" in request.keywords
+                assert 10 not in request.keywords
+                marker = request.node.get_marker("mark1")
+                assert marker.name == "mark1"
+                assert marker.args == ()
+                assert marker.kwargs == {}
+        """)
+        reprec = testdir.inline_run("-m", "mark1")
+        reprec.assertoutcome(passed=1)
+
+    def assert_markers(self, items, **expected):
+        """assert that given items have expected marker names applied to them.
+        expected should be a dict of (item name -> seq of expected marker names)
+
+        .. note:: this could be moved to ``testdir`` if proven to be useful
+        to other modules.
+        """
+        from _pytest.mark import MarkInfo
+        items = dict((x.name, x) for x in items)
+        for name, expected_markers in expected.items():
+            markers = items[name].keywords._markers
+            marker_names = set([name for (name, v) in markers.items()
+                                if isinstance(v, MarkInfo)])
+            assert marker_names == set(expected_markers)
+
+    @pytest.mark.xfail(reason='callspec2.setmulti misuses keywords')
+    @pytest.mark.issue1540
+    def test_mark_from_parameters(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+
+            pytestmark = pytest.mark.skipif(True, reason='skip all')
+
+            # skipifs inside fixture params
+            params = [pytest.mark.skipif(False, reason='dont skip')('parameter')]
+
+
+            @pytest.fixture(params=params)
+            def parameter(request):
+                return request.param
+
+
+            def test_1(parameter):
+                assert True
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(skipped=1)
+
+
+class TestKeywordSelection(object):
+
+    def test_select_simple(self, testdir):
+        file_test = testdir.makepyfile("""
+            def test_one():
+                assert 0
+            class TestClass(object):
+                def test_method_one(self):
+                    assert 42 == 43
+        """)
+
+        def check(keyword, name):
+            reprec = testdir.inline_run("-s", "-k", keyword, file_test)
+            passed, skipped, failed = reprec.listoutcomes()
+            assert len(failed) == 1
+            assert failed[0].nodeid.split("::")[-1] == name
+            assert len(reprec.getcalls('pytest_deselected')) == 1
+
+        for keyword in ['test_one', 'est_on']:
+            check(keyword, 'test_one')
+        check('TestClass and test', 'test_method_one')
+
+    @pytest.mark.parametrize("keyword", [
+        'xxx', 'xxx and test_2', 'TestClass', 'xxx and not test_1',
+        'TestClass and test_2', 'xxx and TestClass and test_2'])
+    def test_select_extra_keywords(self, testdir, keyword):
+        p = testdir.makepyfile(test_select="""
+            def test_1():
+                pass
+            class TestClass(object):
+                def test_2(self):
+                    pass
+        """)
+        testdir.makepyfile(conftest="""
+            import pytest
+            @pytest.hookimpl(hookwrapper=True)
+            def pytest_pycollect_makeitem(name):
+                outcome = yield
+                if name == "TestClass":
+                    item = outcome.get_result()
+                    item.extra_keyword_matches.add("xxx")
+        """)
+        reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword)
+        print("keyword", repr(keyword))
+        passed, skipped, failed = reprec.listoutcomes()
+        assert len(passed) == 1
+        assert passed[0].nodeid.endswith("test_2")
+        dlist = reprec.getcalls("pytest_deselected")
+        assert len(dlist) == 1
+        assert dlist[0].items[0].name == 'test_1'
+
+    def test_select_starton(self, testdir):
+        threepass = testdir.makepyfile(test_threepass="""
+            def test_one(): assert 1
+            def test_two(): assert 1
+            def test_three(): assert 1
+        """)
+        reprec = testdir.inline_run("-k", "test_two:", threepass)
+        passed, skipped, failed = reprec.listoutcomes()
+        assert len(passed) == 2
+        assert not failed
+        dlist = reprec.getcalls("pytest_deselected")
+        assert len(dlist) == 1
+        item = dlist[0].items[0]
+        assert item.name == "test_one"
+
+    def test_keyword_extra(self, testdir):
+        p = testdir.makepyfile("""
+           def test_one():
+               assert 0
+           test_one.mykeyword = True
+        """)
+        reprec = testdir.inline_run("-k", "mykeyword", p)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert failed == 1
+
+    @pytest.mark.xfail
+    def test_keyword_extra_dash(self, testdir):
+        p = testdir.makepyfile("""
+           def test_one():
+               assert 0
+           test_one.mykeyword = True
+        """)
+        # with argparse the argument to an option cannot
+        # start with '-'
+        reprec = testdir.inline_run("-k", "-mykeyword", p)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert passed + skipped + failed == 0
+
+    def test_no_magic_values(self, testdir):
+        """Make sure the tests do not match on magic values,
+        no double underscored values, like '__dict__',
+        and no instance values, like '()'.
+        """
+        p = testdir.makepyfile("""
+            def test_one(): assert 1
+        """)
+
+        def assert_test_is_not_selected(keyword):
+            reprec = testdir.inline_run("-k", keyword, p)
+            passed, skipped, failed = reprec.countoutcomes()
+            dlist = reprec.getcalls("pytest_deselected")
+            assert passed + skipped + failed == 0
+            deselected_tests = dlist[0].items
+            assert len(deselected_tests) == 1
+
+        assert_test_is_not_selected("__")
+        assert_test_is_not_selected("()")
+
+
+@pytest.mark.parametrize('argval, expected', [
+    (pytest.mark.skip()((1, 2)),
+     ParameterSet(values=(1, 2), marks=[pytest.mark.skip], id=None)),
+    (pytest.mark.xfail(pytest.mark.skip()((1, 2))),
+     ParameterSet(values=(1, 2),
+                  marks=[pytest.mark.xfail, pytest.mark.skip], id=None)),
+
+])
+@pytest.mark.filterwarnings('ignore')
+def test_parameterset_extractfrom(argval, expected):
+    extracted = ParameterSet.extract_from(argval)
+    assert extracted == expected
+
+
+def test_legacy_transfer():
+
+    class FakeModule(object):
+        pytestmark = []
+
+    class FakeClass(object):
+        pytestmark = pytest.mark.nofun
+
+    @pytest.mark.fun
+    def fake_method(self):
+        pass
+
+    transfer_markers(fake_method, FakeClass, FakeModule)
+
+    # legacy marks transfer smeared
+    assert fake_method.nofun
+    assert fake_method.fun
+    # pristine marks dont transfer
+    assert fake_method.pytestmark == [pytest.mark.fun.mark]
+
+
+class TestMarkDecorator(object):
+
+    @pytest.mark.parametrize('lhs, rhs, expected', [
+        (pytest.mark.foo(), pytest.mark.foo(), True),
+        (pytest.mark.foo(), pytest.mark.bar(), False),
+        (pytest.mark.foo(), 'bar', False),
+        ('foo', pytest.mark.bar(), False)
+    ])
+    def test__eq__(self, lhs, rhs, expected):
+        assert (lhs == rhs) == expected
diff --git a/tools/third_party/pytest/testing/test_modimport.py b/tools/third_party/pytest/testing/test_modimport.py
new file mode 100644
index 0000000..2ab86bf
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_modimport.py
@@ -0,0 +1,25 @@
+import py
+import subprocess
+import sys
+import pytest
+import _pytest
+
+MODSET = [
+    x for x in py.path.local(_pytest.__file__).dirpath().visit('*.py')
+    if x.purebasename != '__init__'
+]
+
+
+@pytest.mark.parametrize('modfile', MODSET, ids=lambda x: x.purebasename)
+def test_fileimport(modfile):
+    # this test ensures all internal packages can import
+    # without needing the pytest namespace being set
+    # this is critical for the initialization of xdist
+
+    res = subprocess.call([
+        sys.executable,
+        '-c', 'import sys, py; py.path.local(sys.argv[1]).pyimport()',
+        modfile.strpath,
+    ])
+    if res:
+        pytest.fail("command result %s" % res)
diff --git a/tools/third_party/pytest/testing/test_monkeypatch.py b/tools/third_party/pytest/testing/test_monkeypatch.py
new file mode 100644
index 0000000..4427908
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_monkeypatch.py
@@ -0,0 +1,329 @@
+from __future__ import absolute_import, division, print_function
+import os
+import sys
+import textwrap
+
+import pytest
+from _pytest.monkeypatch import MonkeyPatch
+
+
+@pytest.fixture
+def mp():
+    cwd = os.getcwd()
+    sys_path = list(sys.path)
+    yield MonkeyPatch()
+    sys.path[:] = sys_path
+    os.chdir(cwd)
+
+
+def test_setattr():
+    class A(object):
+        x = 1
+
+    monkeypatch = MonkeyPatch()
+    pytest.raises(AttributeError, "monkeypatch.setattr(A, 'notexists', 2)")
+    monkeypatch.setattr(A, 'y', 2, raising=False)
+    assert A.y == 2
+    monkeypatch.undo()
+    assert not hasattr(A, 'y')
+
+    monkeypatch = MonkeyPatch()
+    monkeypatch.setattr(A, 'x', 2)
+    assert A.x == 2
+    monkeypatch.setattr(A, 'x', 3)
+    assert A.x == 3
+    monkeypatch.undo()
+    assert A.x == 1
+
+    A.x = 5
+    monkeypatch.undo()  # double-undo makes no modification
+    assert A.x == 5
+
+
+class TestSetattrWithImportPath(object):
+    def test_string_expression(self, monkeypatch):
+        monkeypatch.setattr("os.path.abspath", lambda x: "hello2")
+        assert os.path.abspath("123") == "hello2"
+
+    def test_string_expression_class(self, monkeypatch):
+        monkeypatch.setattr("_pytest.config.Config", 42)
+        import _pytest
+        assert _pytest.config.Config == 42
+
+    def test_unicode_string(self, monkeypatch):
+        monkeypatch.setattr("_pytest.config.Config", 42)
+        import _pytest
+        assert _pytest.config.Config == 42
+        monkeypatch.delattr("_pytest.config.Config")
+
+    def test_wrong_target(self, monkeypatch):
+        pytest.raises(TypeError, lambda: monkeypatch.setattr(None, None))
+
+    def test_unknown_import(self, monkeypatch):
+        pytest.raises(ImportError,
+                      lambda: monkeypatch.setattr("unkn123.classx", None))
+
+    def test_unknown_attr(self, monkeypatch):
+        pytest.raises(AttributeError,
+                      lambda: monkeypatch.setattr("os.path.qweqwe", None))
+
+    def test_unknown_attr_non_raising(self, monkeypatch):
+        # https://github.com/pytest-dev/pytest/issues/746
+        monkeypatch.setattr('os.path.qweqwe', 42, raising=False)
+        assert os.path.qweqwe == 42
+
+    def test_delattr(self, monkeypatch):
+        monkeypatch.delattr("os.path.abspath")
+        assert not hasattr(os.path, "abspath")
+        monkeypatch.undo()
+        assert os.path.abspath
+
+
+def test_delattr():
+    class A(object):
+        x = 1
+
+    monkeypatch = MonkeyPatch()
+    monkeypatch.delattr(A, 'x')
+    assert not hasattr(A, 'x')
+    monkeypatch.undo()
+    assert A.x == 1
+
+    monkeypatch = MonkeyPatch()
+    monkeypatch.delattr(A, 'x')
+    pytest.raises(AttributeError, "monkeypatch.delattr(A, 'y')")
+    monkeypatch.delattr(A, 'y', raising=False)
+    monkeypatch.setattr(A, 'x', 5, raising=False)
+    assert A.x == 5
+    monkeypatch.undo()
+    assert A.x == 1
+
+
+def test_setitem():
+    d = {'x': 1}
+    monkeypatch = MonkeyPatch()
+    monkeypatch.setitem(d, 'x', 2)
+    monkeypatch.setitem(d, 'y', 1700)
+    monkeypatch.setitem(d, 'y', 1700)
+    assert d['x'] == 2
+    assert d['y'] == 1700
+    monkeypatch.setitem(d, 'x', 3)
+    assert d['x'] == 3
+    monkeypatch.undo()
+    assert d['x'] == 1
+    assert 'y' not in d
+    d['x'] = 5
+    monkeypatch.undo()
+    assert d['x'] == 5
+
+
+def test_setitem_deleted_meanwhile():
+    d = {}
+    monkeypatch = MonkeyPatch()
+    monkeypatch.setitem(d, 'x', 2)
+    del d['x']
+    monkeypatch.undo()
+    assert not d
+
+
+@pytest.mark.parametrize("before", [True, False])
+def test_setenv_deleted_meanwhile(before):
+    key = "qwpeoip123"
+    if before:
+        os.environ[key] = "world"
+    monkeypatch = MonkeyPatch()
+    monkeypatch.setenv(key, 'hello')
+    del os.environ[key]
+    monkeypatch.undo()
+    if before:
+        assert os.environ[key] == "world"
+        del os.environ[key]
+    else:
+        assert key not in os.environ
+
+
+def test_delitem():
+    d = {'x': 1}
+    monkeypatch = MonkeyPatch()
+    monkeypatch.delitem(d, 'x')
+    assert 'x' not in d
+    monkeypatch.delitem(d, 'y', raising=False)
+    pytest.raises(KeyError, "monkeypatch.delitem(d, 'y')")
+    assert not d
+    monkeypatch.setitem(d, 'y', 1700)
+    assert d['y'] == 1700
+    d['hello'] = 'world'
+    monkeypatch.setitem(d, 'x', 1500)
+    assert d['x'] == 1500
+    monkeypatch.undo()
+    assert d == {'hello': 'world', 'x': 1}
+
+
+def test_setenv():
+    monkeypatch = MonkeyPatch()
+    monkeypatch.setenv('XYZ123', 2)
+    import os
+    assert os.environ['XYZ123'] == "2"
+    monkeypatch.undo()
+    assert 'XYZ123' not in os.environ
+
+
+def test_delenv():
+    name = 'xyz1234'
+    assert name not in os.environ
+    monkeypatch = MonkeyPatch()
+    pytest.raises(KeyError, "monkeypatch.delenv(%r, raising=True)" % name)
+    monkeypatch.delenv(name, raising=False)
+    monkeypatch.undo()
+    os.environ[name] = "1"
+    try:
+        monkeypatch = MonkeyPatch()
+        monkeypatch.delenv(name)
+        assert name not in os.environ
+        monkeypatch.setenv(name, "3")
+        assert os.environ[name] == "3"
+        monkeypatch.undo()
+        assert os.environ[name] == "1"
+    finally:
+        if name in os.environ:
+            del os.environ[name]
+
+
+def test_setenv_prepend():
+    import os
+    monkeypatch = MonkeyPatch()
+    monkeypatch.setenv('XYZ123', 2, prepend="-")
+    assert os.environ['XYZ123'] == "2"
+    monkeypatch.setenv('XYZ123', 3, prepend="-")
+    assert os.environ['XYZ123'] == "3-2"
+    monkeypatch.undo()
+    assert 'XYZ123' not in os.environ
+
+
+def test_monkeypatch_plugin(testdir):
+    reprec = testdir.inline_runsource("""
+        def test_method(monkeypatch):
+            assert monkeypatch.__class__.__name__ == "MonkeyPatch"
+    """)
+    res = reprec.countoutcomes()
+    assert tuple(res) == (1, 0, 0), res
+
+
+def test_syspath_prepend(mp):
+    old = list(sys.path)
+    mp.syspath_prepend('world')
+    mp.syspath_prepend('hello')
+    assert sys.path[0] == "hello"
+    assert sys.path[1] == "world"
+    mp.undo()
+    assert sys.path == old
+    mp.undo()
+    assert sys.path == old
+
+
+def test_syspath_prepend_double_undo(mp):
+    mp.syspath_prepend('hello world')
+    mp.undo()
+    sys.path.append('more hello world')
+    mp.undo()
+    assert sys.path[-1] == 'more hello world'
+
+
+def test_chdir_with_path_local(mp, tmpdir):
+    mp.chdir(tmpdir)
+    assert os.getcwd() == tmpdir.strpath
+
+
+def test_chdir_with_str(mp, tmpdir):
+    mp.chdir(tmpdir.strpath)
+    assert os.getcwd() == tmpdir.strpath
+
+
+def test_chdir_undo(mp, tmpdir):
+    cwd = os.getcwd()
+    mp.chdir(tmpdir)
+    mp.undo()
+    assert os.getcwd() == cwd
+
+
+def test_chdir_double_undo(mp, tmpdir):
+    mp.chdir(tmpdir.strpath)
+    mp.undo()
+    tmpdir.chdir()
+    mp.undo()
+    assert os.getcwd() == tmpdir.strpath
+
+
+def test_issue185_time_breaks(testdir):
+    testdir.makepyfile("""
+        import time
+        def test_m(monkeypatch):
+            def f():
+                raise Exception
+            monkeypatch.setattr(time, "time", f)
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines("""
+        *1 passed*
+    """)
+
+
+def test_importerror(testdir):
+    p = testdir.mkpydir("package")
+    p.join("a.py").write(textwrap.dedent("""\
+        import doesnotexist
+
+        x = 1
+    """))
+    testdir.tmpdir.join("test_importerror.py").write(textwrap.dedent("""\
+        def test_importerror(monkeypatch):
+            monkeypatch.setattr('package.a.x', 2)
+    """))
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines("""
+        *import error in package.a: No module named {0}doesnotexist{0}*
+    """.format("'" if sys.version_info > (3, 0) else ""))
+
+
+class SampleNew(object):
+    @staticmethod
+    def hello():
+        return True
+
+
+class SampleNewInherit(SampleNew):
+    pass
+
+
+class SampleOld(object):
+    # oldstyle on python2
+    @staticmethod
+    def hello():
+        return True
+
+
+class SampleOldInherit(SampleOld):
+    pass
+
+
+@pytest.mark.parametrize('Sample', [
+    SampleNew, SampleNewInherit,
+    SampleOld, SampleOldInherit,
+], ids=['new', 'new-inherit', 'old', 'old-inherit'])
+def test_issue156_undo_staticmethod(Sample):
+    monkeypatch = MonkeyPatch()
+
+    monkeypatch.setattr(Sample, 'hello', None)
+    assert Sample.hello is None
+
+    monkeypatch.undo()
+    assert Sample.hello()
+
+
+def test_issue1338_name_resolving():
+    pytest.importorskip('requests')
+    monkeypatch = MonkeyPatch()
+    try:
+        monkeypatch.delattr('requests.sessions.Session.request')
+    finally:
+        monkeypatch.undo()
diff --git a/tools/third_party/pytest/testing/test_nodes.py b/tools/third_party/pytest/testing/test_nodes.py
new file mode 100644
index 0000000..6f4540f
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_nodes.py
@@ -0,0 +1,18 @@
+import pytest
+
+from _pytest import nodes
+
+
+@pytest.mark.parametrize("baseid, nodeid, expected", (
+    ('', '', True),
+    ('', 'foo', True),
+    ('', 'foo/bar', True),
+    ('', 'foo/bar::TestBaz::()', True),
+    ('foo', 'food', False),
+    ('foo/bar::TestBaz::()', 'foo/bar', False),
+    ('foo/bar::TestBaz::()', 'foo/bar::TestBop::()', False),
+    ('foo/bar', 'foo/bar::TestBop::()', True),
+))
+def test_ischildnode(baseid, nodeid, expected):
+    result = nodes.ischildnode(baseid, nodeid)
+    assert result is expected
diff --git a/tools/third_party/pytest/testing/test_nose.py b/tools/third_party/pytest/testing/test_nose.py
new file mode 100644
index 0000000..df3e1a9
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_nose.py
@@ -0,0 +1,409 @@
+from __future__ import absolute_import, division, print_function
+import pytest
+
+
+def setup_module(mod):
+    mod.nose = pytest.importorskip("nose")
+
+
+def test_nose_setup(testdir):
+    p = testdir.makepyfile("""
+        values = []
+        from nose.tools import with_setup
+
+        @with_setup(lambda: values.append(1), lambda: values.append(2))
+        def test_hello():
+            assert values == [1]
+
+        def test_world():
+            assert values == [1,2]
+
+        test_hello.setup = lambda: values.append(1)
+        test_hello.teardown = lambda: values.append(2)
+    """)
+    result = testdir.runpytest(p, '-p', 'nose')
+    result.assert_outcomes(passed=2)
+
+
+def test_setup_func_with_setup_decorator():
+    from _pytest.nose import call_optional
+    values = []
+
+    class A(object):
+        @pytest.fixture(autouse=True)
+        def f(self):
+            values.append(1)
+
+    call_optional(A(), "f")
+    assert not values
+
+
+def test_setup_func_not_callable():
+    from _pytest.nose import call_optional
+
+    class A(object):
+        f = 1
+
+    call_optional(A(), "f")
+
+
+def test_nose_setup_func(testdir):
+    p = testdir.makepyfile("""
+        from nose.tools import with_setup
+
+        values = []
+
+        def my_setup():
+            a = 1
+            values.append(a)
+
+        def my_teardown():
+            b = 2
+            values.append(b)
+
+        @with_setup(my_setup, my_teardown)
+        def test_hello():
+            print (values)
+            assert values == [1]
+
+        def test_world():
+            print (values)
+            assert values == [1,2]
+
+    """)
+    result = testdir.runpytest(p, '-p', 'nose')
+    result.assert_outcomes(passed=2)
+
+
+def test_nose_setup_func_failure(testdir):
+    p = testdir.makepyfile("""
+        from nose.tools import with_setup
+
+        values = []
+        my_setup = lambda x: 1
+        my_teardown = lambda x: 2
+
+        @with_setup(my_setup, my_teardown)
+        def test_hello():
+            print (values)
+            assert values == [1]
+
+        def test_world():
+            print (values)
+            assert values == [1,2]
+
+    """)
+    result = testdir.runpytest(p, '-p', 'nose')
+    result.stdout.fnmatch_lines([
+        "*TypeError: <lambda>()*"
+    ])
+
+
+def test_nose_setup_func_failure_2(testdir):
+    testdir.makepyfile("""
+        values = []
+
+        my_setup = 1
+        my_teardown = 2
+
+        def test_hello():
+            assert values == []
+
+        test_hello.setup = my_setup
+        test_hello.teardown = my_teardown
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+def test_nose_setup_partial(testdir):
+    pytest.importorskip("functools")
+    p = testdir.makepyfile("""
+        from functools import partial
+
+        values = []
+
+        def my_setup(x):
+            a = x
+            values.append(a)
+
+        def my_teardown(x):
+            b = x
+            values.append(b)
+
+        my_setup_partial = partial(my_setup, 1)
+        my_teardown_partial = partial(my_teardown, 2)
+
+        def test_hello():
+            print (values)
+            assert values == [1]
+
+        def test_world():
+            print (values)
+            assert values == [1,2]
+
+        test_hello.setup = my_setup_partial
+        test_hello.teardown = my_teardown_partial
+    """)
+    result = testdir.runpytest(p, '-p', 'nose')
+    result.stdout.fnmatch_lines([
+        "*2 passed*"
+    ])
+
+
+def test_nose_test_generator_fixtures(testdir):
+    p = testdir.makepyfile("""
+        # taken from nose-0.11.1 unit_tests/test_generator_fixtures.py
+        from nose.tools import eq_
+        called = []
+
+        def outer_setup():
+            called.append('outer_setup')
+
+        def outer_teardown():
+            called.append('outer_teardown')
+
+        def inner_setup():
+            called.append('inner_setup')
+
+        def inner_teardown():
+            called.append('inner_teardown')
+
+        def test_gen():
+            called[:] = []
+            for i in range(0, 5):
+                yield check, i
+
+        def check(i):
+            expect = ['outer_setup']
+            for x in range(0, i):
+                expect.append('inner_setup')
+                expect.append('inner_teardown')
+            expect.append('inner_setup')
+            eq_(called, expect)
+
+
+        test_gen.setup = outer_setup
+        test_gen.teardown = outer_teardown
+        check.setup = inner_setup
+        check.teardown = inner_teardown
+
+        class TestClass(object):
+            def setup(self):
+                print ("setup called in %s" % self)
+                self.called = ['setup']
+
+            def teardown(self):
+                print ("teardown called in %s" % self)
+                eq_(self.called, ['setup'])
+                self.called.append('teardown')
+
+            def test(self):
+                print ("test called in %s" % self)
+                for i in range(0, 5):
+                    yield self.check, i
+
+            def check(self, i):
+                print ("check called in %s" % self)
+                expect = ['setup']
+                #for x in range(0, i):
+                #    expect.append('setup')
+                #    expect.append('teardown')
+                #expect.append('setup')
+                eq_(self.called, expect)
+    """)
+    result = testdir.runpytest(p, '-p', 'nose')
+    result.stdout.fnmatch_lines([
+        "*10 passed*"
+    ])
+
+
+def test_module_level_setup(testdir):
+    testdir.makepyfile("""
+        from nose.tools import with_setup
+        items = {}
+
+        def setup():
+            items[1]=1
+
+        def teardown():
+            del items[1]
+
+        def setup2():
+            items[2] = 2
+
+        def teardown2():
+            del items[2]
+
+        def test_setup_module_setup():
+            assert items[1] == 1
+
+        @with_setup(setup2, teardown2)
+        def test_local_setup():
+            assert items[2] == 2
+            assert 1 not in items
+    """)
+    result = testdir.runpytest('-p', 'nose')
+    result.stdout.fnmatch_lines([
+        "*2 passed*",
+    ])
+
+
+def test_nose_style_setup_teardown(testdir):
+    testdir.makepyfile("""
+        values = []
+
+        def setup_module():
+            values.append(1)
+
+        def teardown_module():
+            del values[0]
+
+        def test_hello():
+            assert values == [1]
+
+        def test_world():
+            assert values == [1]
+        """)
+    result = testdir.runpytest('-p', 'nose')
+    result.stdout.fnmatch_lines([
+        "*2 passed*",
+    ])
+
+
+def test_nose_setup_ordering(testdir):
+    testdir.makepyfile("""
+        def setup_module(mod):
+            mod.visited = True
+
+        class TestClass(object):
+            def setup(self):
+                assert visited
+            def test_first(self):
+                pass
+        """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*1 passed*",
+    ])
+
+
+def test_apiwrapper_problem_issue260(testdir):
+    # this would end up trying a call a optional teardown on the class
+    # for plain unittests we dont want nose behaviour
+    testdir.makepyfile("""
+        import unittest
+        class TestCase(unittest.TestCase):
+            def setup(self):
+                #should not be called in unittest testcases
+                assert 0, 'setup'
+            def teardown(self):
+                #should not be called in unittest testcases
+                assert 0, 'teardown'
+            def setUp(self):
+                print('setup')
+            def tearDown(self):
+                print('teardown')
+            def test_fun(self):
+                pass
+        """)
+    result = testdir.runpytest()
+    result.assert_outcomes(passed=1)
+
+
+def test_setup_teardown_linking_issue265(testdir):
+    # we accidentally didnt integrate nose setupstate with normal setupstate
+    # this test ensures that won't happen again
+    testdir.makepyfile('''
+        import pytest
+
+        class TestGeneric(object):
+            def test_nothing(self):
+                """Tests the API of the implementation (for generic and specialized)."""
+
+        @pytest.mark.skipif("True", reason=
+                    "Skip tests to check if teardown is skipped as well.")
+        class TestSkipTeardown(TestGeneric):
+
+            def setup(self):
+                """Sets up my specialized implementation for $COOL_PLATFORM."""
+                raise Exception("should not call setup for skipped tests")
+
+            def teardown(self):
+                """Undoes the setup."""
+                raise Exception("should not call teardown for skipped tests")
+        ''')
+    reprec = testdir.runpytest()
+    reprec.assert_outcomes(passed=1, skipped=1)
+
+
+def test_SkipTest_during_collection(testdir):
+    p = testdir.makepyfile("""
+        import nose
+        raise nose.SkipTest("during collection")
+        def test_failing():
+            assert False
+        """)
+    result = testdir.runpytest(p)
+    result.assert_outcomes(skipped=1)
+
+
+def test_SkipTest_in_test(testdir):
+    testdir.makepyfile("""
+        import nose
+
+        def test_skipping():
+            raise nose.SkipTest("in test")
+        """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(skipped=1)
+
+
+def test_istest_function_decorator(testdir):
+    p = testdir.makepyfile("""
+        import nose.tools
+        @nose.tools.istest
+        def not_test_prefix():
+            pass
+        """)
+    result = testdir.runpytest(p)
+    result.assert_outcomes(passed=1)
+
+
+def test_nottest_function_decorator(testdir):
+    testdir.makepyfile("""
+        import nose.tools
+        @nose.tools.nottest
+        def test_prefix():
+            pass
+        """)
+    reprec = testdir.inline_run()
+    assert not reprec.getfailedcollections()
+    calls = reprec.getreports("pytest_runtest_logreport")
+    assert not calls
+
+
+def test_istest_class_decorator(testdir):
+    p = testdir.makepyfile("""
+        import nose.tools
+        @nose.tools.istest
+        class NotTestPrefix(object):
+            def test_method(self):
+                pass
+        """)
+    result = testdir.runpytest(p)
+    result.assert_outcomes(passed=1)
+
+
+def test_nottest_class_decorator(testdir):
+    testdir.makepyfile("""
+        import nose.tools
+        @nose.tools.nottest
+        class TestPrefix(object):
+            def test_method(self):
+                pass
+        """)
+    reprec = testdir.inline_run()
+    assert not reprec.getfailedcollections()
+    calls = reprec.getreports("pytest_runtest_logreport")
+    assert not calls
diff --git a/tools/third_party/pytest/testing/test_parseopt.py b/tools/third_party/pytest/testing/test_parseopt.py
new file mode 100644
index 0000000..9215925
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_parseopt.py
@@ -0,0 +1,309 @@
+from __future__ import absolute_import, division, print_function
+import sys
+import os
+import py
+import pytest
+from _pytest import config as parseopt
+
+
+@pytest.fixture
+def parser():
+    return parseopt.Parser()
+
+
+class TestParser(object):
+    def test_no_help_by_default(self, capsys):
+        parser = parseopt.Parser(usage="xyz")
+        pytest.raises(SystemExit, lambda: parser.parse(["-h"]))
+        out, err = capsys.readouterr()
+        assert err.find("error: unrecognized arguments") != -1
+
+    def test_argument(self):
+        with pytest.raises(parseopt.ArgumentError):
+            # need a short or long option
+            argument = parseopt.Argument()
+        argument = parseopt.Argument('-t')
+        assert argument._short_opts == ['-t']
+        assert argument._long_opts == []
+        assert argument.dest == 't'
+        argument = parseopt.Argument('-t', '--test')
+        assert argument._short_opts == ['-t']
+        assert argument._long_opts == ['--test']
+        assert argument.dest == 'test'
+        argument = parseopt.Argument('-t', '--test', dest='abc')
+        assert argument.dest == 'abc'
+        assert str(argument) == (
+            "Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')"
+        )
+
+    def test_argument_type(self):
+        argument = parseopt.Argument('-t', dest='abc', type=int)
+        assert argument.type is int
+        argument = parseopt.Argument('-t', dest='abc', type=str)
+        assert argument.type is str
+        argument = parseopt.Argument('-t', dest='abc', type=float)
+        assert argument.type is float
+        with pytest.warns(DeprecationWarning):
+            with pytest.raises(KeyError):
+                argument = parseopt.Argument('-t', dest='abc', type='choice')
+        argument = parseopt.Argument('-t', dest='abc', type=str,
+                                     choices=['red', 'blue'])
+        assert argument.type is str
+
+    def test_argument_processopt(self):
+        argument = parseopt.Argument('-t', type=int)
+        argument.default = 42
+        argument.dest = 'abc'
+        res = argument.attrs()
+        assert res['default'] == 42
+        assert res['dest'] == 'abc'
+
+    def test_group_add_and_get(self, parser):
+        group = parser.getgroup("hello", description="desc")
+        assert group.name == "hello"
+        assert group.description == "desc"
+
+    def test_getgroup_simple(self, parser):
+        group = parser.getgroup("hello", description="desc")
+        assert group.name == "hello"
+        assert group.description == "desc"
+        group2 = parser.getgroup("hello")
+        assert group2 is group
+
+    def test_group_ordering(self, parser):
+        parser.getgroup("1")
+        parser.getgroup("2")
+        parser.getgroup("3", after="1")
+        groups = parser._groups
+        groups_names = [x.name for x in groups]
+        assert groups_names == list("132")
+
+    def test_group_addoption(self):
+        group = parseopt.OptionGroup("hello")
+        group.addoption("--option1", action="store_true")
+        assert len(group.options) == 1
+        assert isinstance(group.options[0], parseopt.Argument)
+
+    def test_group_addoption_conflict(self):
+        group = parseopt.OptionGroup("hello again")
+        group.addoption("--option1", "--option-1", action="store_true")
+        with pytest.raises(ValueError) as err:
+            group.addoption("--option1", "--option-one", action="store_true")
+        assert str(set(["--option1"])) in str(err.value)
+
+    def test_group_shortopt_lowercase(self, parser):
+        group = parser.getgroup("hello")
+        pytest.raises(ValueError, """
+            group.addoption("-x", action="store_true")
+        """)
+        assert len(group.options) == 0
+        group._addoption("-x", action="store_true")
+        assert len(group.options) == 1
+
+    def test_parser_addoption(self, parser):
+        group = parser.getgroup("custom options")
+        assert len(group.options) == 0
+        group.addoption("--option1", action="store_true")
+        assert len(group.options) == 1
+
+    def test_parse(self, parser):
+        parser.addoption("--hello", dest="hello", action="store")
+        args = parser.parse(['--hello', 'world'])
+        assert args.hello == "world"
+        assert not getattr(args, parseopt.FILE_OR_DIR)
+
+    def test_parse2(self, parser):
+        args = parser.parse([py.path.local()])
+        assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local()
+
+    def test_parse_known_args(self, parser):
+        parser.parse_known_args([py.path.local()])
+        parser.addoption("--hello", action="store_true")
+        ns = parser.parse_known_args(["x", "--y", "--hello", "this"])
+        assert ns.hello
+        assert ns.file_or_dir == ['x']
+
+    def test_parse_known_and_unknown_args(self, parser):
+        parser.addoption("--hello", action="store_true")
+        ns, unknown = parser.parse_known_and_unknown_args(["x", "--y",
+                                                           "--hello", "this"])
+        assert ns.hello
+        assert ns.file_or_dir == ['x']
+        assert unknown == ['--y', 'this']
+
+    def test_parse_will_set_default(self, parser):
+        parser.addoption("--hello", dest="hello", default="x", action="store")
+        option = parser.parse([])
+        assert option.hello == "x"
+        del option.hello
+        parser.parse_setoption([], option)
+        assert option.hello == "x"
+
+    def test_parse_setoption(self, parser):
+        parser.addoption("--hello", dest="hello", action="store")
+        parser.addoption("--world", dest="world", default=42)
+
+        class A(object):
+            pass
+
+        option = A()
+        args = parser.parse_setoption(['--hello', 'world'], option)
+        assert option.hello == "world"
+        assert option.world == 42
+        assert not args
+
+    def test_parse_special_destination(self, parser):
+        parser.addoption("--ultimate-answer", type=int)
+        args = parser.parse(['--ultimate-answer', '42'])
+        assert args.ultimate_answer == 42
+
+    def test_parse_split_positional_arguments(self, parser):
+        parser.addoption("-R", action='store_true')
+        parser.addoption("-S", action='store_false')
+        args = parser.parse(['-R', '4', '2', '-S'])
+        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
+        args = parser.parse(['-R', '-S', '4', '2', '-R'])
+        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
+        assert args.R is True
+        assert args.S is False
+        args = parser.parse(['-R', '4', '-S', '2'])
+        assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2']
+        assert args.R is True
+        assert args.S is False
+
+    def test_parse_defaultgetter(self):
+        def defaultget(option):
+            if not hasattr(option, 'type'):
+                return
+            if option.type is int:
+                option.default = 42
+            elif option.type is str:
+                option.default = "world"
+        parser = parseopt.Parser(processopt=defaultget)
+        parser.addoption("--this", dest="this", type=int, action="store")
+        parser.addoption("--hello", dest="hello", type=str, action="store")
+        parser.addoption("--no", dest="no", action="store_true")
+        option = parser.parse([])
+        assert option.hello == "world"
+        assert option.this == 42
+        assert option.no is False
+
+    def test_drop_short_helper(self):
+        parser = py.std.argparse.ArgumentParser(formatter_class=parseopt.DropShorterLongHelpFormatter)
+        parser.add_argument('-t', '--twoword', '--duo', '--two-word', '--two',
+                            help='foo').map_long_option = {'two': 'two-word'}
+        # throws error on --deux only!
+        parser.add_argument('-d', '--deuxmots', '--deux-mots',
+                            action='store_true', help='foo').map_long_option = {'deux': 'deux-mots'}
+        parser.add_argument('-s', action='store_true', help='single short')
+        parser.add_argument('--abc', '-a',
+                            action='store_true', help='bar')
+        parser.add_argument('--klm', '-k', '--kl-m',
+                            action='store_true', help='bar')
+        parser.add_argument('-P', '--pq-r', '-p', '--pqr',
+                            action='store_true', help='bar')
+        parser.add_argument('--zwei-wort', '--zweiwort', '--zweiwort',
+                            action='store_true', help='bar')
+        parser.add_argument('-x', '--exit-on-first', '--exitfirst',
+                            action='store_true', help='spam').map_long_option = {'exitfirst': 'exit-on-first'}
+        parser.add_argument('files_and_dirs', nargs='*')
+        args = parser.parse_args(['-k', '--duo', 'hallo', '--exitfirst'])
+        assert args.twoword == 'hallo'
+        assert args.klm is True
+        assert args.zwei_wort is False
+        assert args.exit_on_first is True
+        assert args.s is False
+        args = parser.parse_args(['--deux-mots'])
+        with pytest.raises(AttributeError):
+            assert args.deux_mots is True
+        assert args.deuxmots is True
+        args = parser.parse_args(['file', 'dir'])
+        assert '|'.join(args.files_and_dirs) == 'file|dir'
+
+    def test_drop_short_0(self, parser):
+        parser.addoption('--funcarg', '--func-arg', action='store_true')
+        parser.addoption('--abc-def', '--abc-def', action='store_true')
+        parser.addoption('--klm-hij', action='store_true')
+        args = parser.parse(['--funcarg', '--k'])
+        assert args.funcarg is True
+        assert args.abc_def is False
+        assert args.klm_hij is True
+
+    def test_drop_short_2(self, parser):
+        parser.addoption('--func-arg', '--doit', action='store_true')
+        args = parser.parse(['--doit'])
+        assert args.func_arg is True
+
+    def test_drop_short_3(self, parser):
+        parser.addoption('--func-arg', '--funcarg', '--doit', action='store_true')
+        args = parser.parse(['abcd'])
+        assert args.func_arg is False
+        assert args.file_or_dir == ['abcd']
+
+    def test_drop_short_help0(self, parser, capsys):
+        parser.addoption('--func-args', '--doit', help='foo',
+                         action='store_true')
+        parser.parse([])
+        help = parser.optparser.format_help()
+        assert '--func-args, --doit  foo' in help
+
+    # testing would be more helpful with all help generated
+    def test_drop_short_help1(self, parser, capsys):
+        group = parser.getgroup("general")
+        group.addoption('--doit', '--func-args', action='store_true', help='foo')
+        group._addoption("-h", "--help", action="store_true", dest="help",
+                         help="show help message and configuration info")
+        parser.parse(['-h'])
+        help = parser.optparser.format_help()
+        assert '-doit, --func-args  foo' in help
+
+    def test_multiple_metavar_help(self, parser):
+        """
+        Help text for options with a metavar tuple should display help
+        in the form "--preferences=value1 value2 value3" (#2004).
+        """
+        group = parser.getgroup("general")
+        group.addoption('--preferences', metavar=('value1', 'value2', 'value3'), nargs=3)
+        group._addoption("-h", "--help", action="store_true", dest="help")
+        parser.parse(['-h'])
+        help = parser.optparser.format_help()
+        assert '--preferences=value1 value2 value3' in help
+
+
+def test_argcomplete(testdir, monkeypatch):
+    if not py.path.local.sysfind('bash'):
+        pytest.skip("bash not available")
+    script = str(testdir.tmpdir.join("test_argcomplete"))
+    pytest_bin = sys.argv[0]
+    if "pytest" not in os.path.basename(pytest_bin):
+        pytest.skip("need to be run with pytest executable, not %s" % (pytest_bin,))
+
+    with open(str(script), 'w') as fp:
+        # redirect output from argcomplete to stdin and stderr is not trivial
+        # http://stackoverflow.com/q/12589419/1307905
+        # so we use bash
+        fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" %s 8>&1 9>&2' % pytest_bin)
+    # alternative would be exteneded Testdir.{run(),_run(),popen()} to be able
+    # to handle a keyword argument env that replaces os.environ in popen or
+    # extends the copy, advantage: could not forget to restore
+    monkeypatch.setenv('_ARGCOMPLETE', "1")
+    monkeypatch.setenv('_ARGCOMPLETE_IFS', "\x0b")
+    monkeypatch.setenv('COMP_WORDBREAKS', ' \\t\\n"\\\'><=;|&(:')
+
+    arg = '--fu'
+    monkeypatch.setenv('COMP_LINE', "pytest " + arg)
+    monkeypatch.setenv('COMP_POINT', str(len("pytest " + arg)))
+    result = testdir.run('bash', str(script), arg)
+    if result.ret == 255:
+        # argcomplete not found
+        pytest.skip("argcomplete not available")
+    elif not result.stdout.str():
+        pytest.skip("bash provided no output, argcomplete not available?")
+    else:
+        result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
+    os.mkdir('test_argcomplete.d')
+    arg = 'test_argc'
+    monkeypatch.setenv('COMP_LINE', "pytest " + arg)
+    monkeypatch.setenv('COMP_POINT', str(len('pytest ' + arg)))
+    result = testdir.run('bash', str(script), arg)
+    result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"])
diff --git a/tools/third_party/pytest/testing/test_pastebin.py b/tools/third_party/pytest/testing/test_pastebin.py
new file mode 100644
index 0000000..6b1742d
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_pastebin.py
@@ -0,0 +1,117 @@
+# encoding: utf-8
+from __future__ import absolute_import, division, print_function
+import sys
+import pytest
+
+
+class TestPasteCapture(object):
+
+    @pytest.fixture
+    def pastebinlist(self, monkeypatch, request):
+        pastebinlist = []
+        plugin = request.config.pluginmanager.getplugin('pastebin')
+        monkeypatch.setattr(plugin, 'create_new_paste', pastebinlist.append)
+        return pastebinlist
+
+    def test_failed(self, testdir, pastebinlist):
+        testpath = testdir.makepyfile("""
+            import pytest
+            def test_pass():
+                pass
+            def test_fail():
+                assert 0
+            def test_skip():
+                pytest.skip("")
+        """)
+        reprec = testdir.inline_run(testpath, "--paste=failed")
+        assert len(pastebinlist) == 1
+        s = pastebinlist[0]
+        assert s.find("def test_fail") != -1
+        assert reprec.countoutcomes() == [1, 1, 1]
+
+    def test_all(self, testdir, pastebinlist):
+        from _pytest.pytester import LineMatcher
+        testpath = testdir.makepyfile("""
+            import pytest
+            def test_pass():
+                pass
+            def test_fail():
+                assert 0
+            def test_skip():
+                pytest.skip("")
+        """)
+        reprec = testdir.inline_run(testpath, "--pastebin=all", '-v')
+        assert reprec.countoutcomes() == [1, 1, 1]
+        assert len(pastebinlist) == 1
+        contents = pastebinlist[0].decode('utf-8')
+        matcher = LineMatcher(contents.splitlines())
+        matcher.fnmatch_lines([
+            '*test_pass PASSED*',
+            '*test_fail FAILED*',
+            '*test_skip SKIPPED*',
+            '*== 1 failed, 1 passed, 1 skipped in *'
+        ])
+
+    def test_non_ascii_paste_text(self, testdir):
+        """Make sure that text which contains non-ascii characters is pasted
+        correctly. See #1219.
+        """
+        testdir.makepyfile(test_unicode="""
+            # encoding: utf-8
+            def test():
+                assert '☺' == 1
+        """)
+        result = testdir.runpytest('--pastebin=all')
+        if sys.version_info[0] == 3:
+            expected_msg = "*assert '☺' == 1*"
+        else:
+            expected_msg = "*assert '\\xe2\\x98\\xba' == 1*"
+        result.stdout.fnmatch_lines([
+            expected_msg,
+            "*== 1 failed in *",
+            '*Sending information to Paste Service*',
+        ])
+
+
+class TestPaste(object):
+
+    @pytest.fixture
+    def pastebin(self, request):
+        return request.config.pluginmanager.getplugin('pastebin')
+
+    @pytest.fixture
+    def mocked_urlopen(self, monkeypatch):
+        """
+        monkeypatch the actual urlopen calls done by the internal plugin
+        function that connects to bpaste service.
+        """
+        calls = []
+
+        def mocked(url, data):
+            calls.append((url, data))
+
+            class DummyFile(object):
+                def read(self):
+                    # part of html of a normal response
+                    return b'View <a href="/raw/3c0c6750bd">raw</a>.'
+            return DummyFile()
+
+        if sys.version_info < (3, 0):
+            import urllib
+            monkeypatch.setattr(urllib, 'urlopen', mocked)
+        else:
+            import urllib.request
+            monkeypatch.setattr(urllib.request, 'urlopen', mocked)
+        return calls
+
+    def test_create_new_paste(self, pastebin, mocked_urlopen):
+        result = pastebin.create_new_paste(b'full-paste-contents')
+        assert result == 'https://bpaste.net/show/3c0c6750bd'
+        assert len(mocked_urlopen) == 1
+        url, data = mocked_urlopen[0]
+        assert type(data) is bytes
+        lexer = 'python3' if sys.version_info[0] == 3 else 'python'
+        assert url == 'https://bpaste.net'
+        assert 'lexer=%s' % lexer in data.decode()
+        assert 'code=full-paste-contents' in data.decode()
+        assert 'expiry=1week' in data.decode()
diff --git a/tools/third_party/pytest/testing/test_pdb.py b/tools/third_party/pytest/testing/test_pdb.py
new file mode 100644
index 0000000..70a5c3c
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_pdb.py
@@ -0,0 +1,406 @@
+from __future__ import absolute_import, division, print_function
+import sys
+import platform
+
+import _pytest._code
+import pytest
+
+
+def runpdb_and_get_report(testdir, source):
+    p = testdir.makepyfile(source)
+    result = testdir.runpytest_inprocess("--pdb", p)
+    reports = result.reprec.getreports("pytest_runtest_logreport")
+    assert len(reports) == 3, reports  # setup/call/teardown
+    return reports[1]
+
+
+@pytest.fixture
+def custom_pdb_calls():
+    called = []
+
+    # install dummy debugger class and track which methods were called on it
+    class _CustomPdb(object):
+        def __init__(self, *args, **kwargs):
+            called.append("init")
+
+        def reset(self):
+            called.append("reset")
+
+        def interaction(self, *args):
+            called.append("interaction")
+
+    _pytest._CustomPdb = _CustomPdb
+    return called
+
+
+class TestPDB(object):
+
+    @pytest.fixture
+    def pdblist(self, request):
+        monkeypatch = request.getfixturevalue("monkeypatch")
+        pdblist = []
+
+        def mypdb(*args):
+            pdblist.append(args)
+
+        plugin = request.config.pluginmanager.getplugin('debugging')
+        monkeypatch.setattr(plugin, 'post_mortem', mypdb)
+        return pdblist
+
+    def test_pdb_on_fail(self, testdir, pdblist):
+        rep = runpdb_and_get_report(testdir, """
+            def test_func():
+                assert 0
+        """)
+        assert rep.failed
+        assert len(pdblist) == 1
+        tb = _pytest._code.Traceback(pdblist[0][0])
+        assert tb[-1].name == "test_func"
+
+    def test_pdb_on_xfail(self, testdir, pdblist):
+        rep = runpdb_and_get_report(testdir, """
+            import pytest
+            @pytest.mark.xfail
+            def test_func():
+                assert 0
+        """)
+        assert "xfail" in rep.keywords
+        assert not pdblist
+
+    def test_pdb_on_skip(self, testdir, pdblist):
+        rep = runpdb_and_get_report(testdir, """
+            import pytest
+            def test_func():
+                pytest.skip("hello")
+        """)
+        assert rep.skipped
+        assert len(pdblist) == 0
+
+    def test_pdb_on_BdbQuit(self, testdir, pdblist):
+        rep = runpdb_and_get_report(testdir, """
+            import bdb
+            def test_func():
+                raise bdb.BdbQuit
+        """)
+        assert rep.failed
+        assert len(pdblist) == 0
+
+    def test_pdb_interaction(self, testdir):
+        p1 = testdir.makepyfile("""
+            def test_1():
+                i = 0
+                assert i == 1
+        """)
+        child = testdir.spawn_pytest("--pdb %s" % p1)
+        child.expect(".*def test_1")
+        child.expect(".*i = 0")
+        child.expect("(Pdb)")
+        child.sendeof()
+        rest = child.read().decode("utf8")
+        assert "1 failed" in rest
+        assert "def test_1" not in rest
+        self.flush(child)
+
+    @staticmethod
+    def flush(child):
+        if platform.system() == 'Darwin':
+            return
+        if child.isalive():
+            child.wait()
+
+    def test_pdb_unittest_postmortem(self, testdir):
+        p1 = testdir.makepyfile("""
+            import unittest
+            class Blub(unittest.TestCase):
+                def tearDown(self):
+                    self.filename = None
+                def test_false(self):
+                    self.filename = 'debug' + '.me'
+                    assert 0
+        """)
+        child = testdir.spawn_pytest("--pdb %s" % p1)
+        child.expect('(Pdb)')
+        child.sendline('p self.filename')
+        child.sendeof()
+        rest = child.read().decode("utf8")
+        assert 'debug.me' in rest
+        self.flush(child)
+
+    def test_pdb_unittest_skip(self, testdir):
+        """Test for issue #2137"""
+        p1 = testdir.makepyfile("""
+            import unittest
+            @unittest.skipIf(True, 'Skipping also with pdb active')
+            class MyTestCase(unittest.TestCase):
+                def test_one(self):
+                    assert 0
+        """)
+        child = testdir.spawn_pytest("-rs --pdb %s" % p1)
+        child.expect('Skipping also with pdb active')
+        child.expect('1 skipped in')
+        child.sendeof()
+        self.flush(child)
+
+    def test_pdb_interaction_capture(self, testdir):
+        p1 = testdir.makepyfile("""
+            def test_1():
+                print("getrekt")
+                assert False
+        """)
+        child = testdir.spawn_pytest("--pdb %s" % p1)
+        child.expect("getrekt")
+        child.expect("(Pdb)")
+        child.sendeof()
+        rest = child.read().decode("utf8")
+        assert "1 failed" in rest
+        assert "getrekt" not in rest
+        self.flush(child)
+
+    def test_pdb_interaction_exception(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            def globalfunc():
+                pass
+            def test_1():
+                pytest.raises(ValueError, globalfunc)
+        """)
+        child = testdir.spawn_pytest("--pdb %s" % p1)
+        child.expect(".*def test_1")
+        child.expect(".*pytest.raises.*globalfunc")
+        child.expect("(Pdb)")
+        child.sendline("globalfunc")
+        child.expect(".*function")
+        child.sendeof()
+        child.expect("1 failed")
+        self.flush(child)
+
+    def test_pdb_interaction_on_collection_issue181(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            xxx
+        """)
+        child = testdir.spawn_pytest("--pdb %s" % p1)
+        # child.expect(".*import pytest.*")
+        child.expect("(Pdb)")
+        child.sendeof()
+        child.expect("1 error")
+        self.flush(child)
+
+    def test_pdb_interaction_on_internal_error(self, testdir):
+        testdir.makeconftest("""
+            def pytest_runtest_protocol():
+                0/0
+        """)
+        p1 = testdir.makepyfile("def test_func(): pass")
+        child = testdir.spawn_pytest("--pdb %s" % p1)
+        # child.expect(".*import pytest.*")
+        child.expect("(Pdb)")
+        child.sendeof()
+        self.flush(child)
+
+    def test_pdb_interaction_capturing_simple(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            def test_1():
+                i = 0
+                print ("hello17")
+                pytest.set_trace()
+                x = 3
+        """)
+        child = testdir.spawn_pytest(str(p1))
+        child.expect("test_1")
+        child.expect("x = 3")
+        child.expect("(Pdb)")
+        child.sendeof()
+        rest = child.read().decode("utf-8")
+        assert "1 failed" in rest
+        assert "def test_1" in rest
+        assert "hello17" in rest  # out is captured
+        self.flush(child)
+
+    def test_pdb_set_trace_interception(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pdb
+            def test_1():
+                pdb.set_trace()
+        """)
+        child = testdir.spawn_pytest(str(p1))
+        child.expect("test_1")
+        child.expect("(Pdb)")
+        child.sendeof()
+        rest = child.read().decode("utf8")
+        assert "1 failed" in rest
+        assert "reading from stdin while output" not in rest
+        self.flush(child)
+
+    def test_pdb_and_capsys(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            def test_1(capsys):
+                print ("hello1")
+                pytest.set_trace()
+        """)
+        child = testdir.spawn_pytest(str(p1))
+        child.expect("test_1")
+        child.send("capsys.readouterr()\n")
+        child.expect("hello1")
+        child.sendeof()
+        child.read()
+        self.flush(child)
+
+    def test_set_trace_capturing_afterwards(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pdb
+            def test_1():
+                pdb.set_trace()
+            def test_2():
+                print ("hello")
+                assert 0
+        """)
+        child = testdir.spawn_pytest(str(p1))
+        child.expect("test_1")
+        child.send("c\n")
+        child.expect("test_2")
+        child.expect("Captured")
+        child.expect("hello")
+        child.sendeof()
+        child.read()
+        self.flush(child)
+
+    def test_pdb_interaction_doctest(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            def function_1():
+                '''
+                >>> i = 0
+                >>> assert i == 1
+                '''
+        """)
+        child = testdir.spawn_pytest("--doctest-modules --pdb %s" % p1)
+        child.expect("(Pdb)")
+        child.sendline('i')
+        child.expect("0")
+        child.expect("(Pdb)")
+        child.sendeof()
+        rest = child.read().decode("utf8")
+        assert "1 failed" in rest
+        self.flush(child)
+
+    def test_pdb_interaction_capturing_twice(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            def test_1():
+                i = 0
+                print ("hello17")
+                pytest.set_trace()
+                x = 3
+                print ("hello18")
+                pytest.set_trace()
+                x = 4
+        """)
+        child = testdir.spawn_pytest(str(p1))
+        child.expect("test_1")
+        child.expect("x = 3")
+        child.expect("(Pdb)")
+        child.sendline('c')
+        child.expect("x = 4")
+        child.sendeof()
+        rest = child.read().decode("utf8")
+        assert "1 failed" in rest
+        assert "def test_1" in rest
+        assert "hello17" in rest  # out is captured
+        assert "hello18" in rest  # out is captured
+        self.flush(child)
+
+    def test_pdb_used_outside_test(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            pytest.set_trace()
+            x = 5
+        """)
+        child = testdir.spawn("%s %s" % (sys.executable, p1))
+        child.expect("x = 5")
+        child.sendeof()
+        self.flush(child)
+
+    def test_pdb_used_in_generate_tests(self, testdir):
+        p1 = testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                pytest.set_trace()
+                x = 5
+            def test_foo(a):
+                pass
+        """)
+        child = testdir.spawn_pytest(str(p1))
+        child.expect("x = 5")
+        child.sendeof()
+        self.flush(child)
+
+    def test_pdb_collection_failure_is_shown(self, testdir):
+        p1 = testdir.makepyfile("""xxx """)
+        result = testdir.runpytest_subprocess("--pdb", p1)
+        result.stdout.fnmatch_lines([
+            "*NameError*xxx*",
+            "*1 error*",
+        ])
+
+    def test_enter_pdb_hook_is_called(self, testdir):
+        testdir.makeconftest("""
+            def pytest_enter_pdb(config):
+                assert config.testing_verification == 'configured'
+                print 'enter_pdb_hook'
+
+            def pytest_configure(config):
+                config.testing_verification = 'configured'
+        """)
+        p1 = testdir.makepyfile("""
+            import pytest
+
+            def test_foo():
+                pytest.set_trace()
+        """)
+        child = testdir.spawn_pytest(str(p1))
+        child.expect("enter_pdb_hook")
+        child.send('c\n')
+        child.sendeof()
+        self.flush(child)
+
+    def test_pdb_custom_cls(self, testdir, custom_pdb_calls):
+        p1 = testdir.makepyfile("""xxx """)
+        result = testdir.runpytest_inprocess(
+            "--pdb", "--pdbcls=_pytest:_CustomPdb", p1)
+        result.stdout.fnmatch_lines([
+            "*NameError*xxx*",
+            "*1 error*",
+        ])
+        assert custom_pdb_calls == ["init", "reset", "interaction"]
+
+    def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls):
+        p1 = testdir.makepyfile("""xxx """)
+        result = testdir.runpytest_inprocess(
+            "--pdbcls=_pytest:_CustomPdb", p1)
+        result.stdout.fnmatch_lines([
+            "*NameError*xxx*",
+            "*1 error*",
+        ])
+        assert custom_pdb_calls == []
+
+    def test_pdb_custom_cls_with_settrace(self, testdir, monkeypatch):
+        testdir.makepyfile(custom_pdb="""
+            class CustomPdb(object):
+                def set_trace(*args, **kwargs):
+                    print 'custom set_trace>'
+         """)
+        p1 = testdir.makepyfile("""
+            import pytest
+
+            def test_foo():
+                pytest.set_trace()
+        """)
+        monkeypatch.setenv('PYTHONPATH', str(testdir.tmpdir))
+        child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1))
+
+        child.expect('custom set_trace>')
+        if child.isalive():
+            child.wait()
diff --git a/tools/third_party/pytest/testing/test_pluginmanager.py b/tools/third_party/pytest/testing/test_pluginmanager.py
new file mode 100644
index 0000000..6192176
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_pluginmanager.py
@@ -0,0 +1,361 @@
+# encoding: UTF-8
+from __future__ import absolute_import, division, print_function
+import pytest
+import py
+import os
+
+from _pytest.config import get_config, PytestPluginManager
+from _pytest.main import EXIT_NOTESTSCOLLECTED, Session
+
+
+@pytest.fixture
+def pytestpm():
+    return PytestPluginManager()
+
+
+class TestPytestPluginInteractions(object):
+    def test_addhooks_conftestplugin(self, testdir):
+        testdir.makepyfile(newhooks="""
+            def pytest_myhook(xyz):
+                "new hook"
+        """)
+        conf = testdir.makeconftest("""
+            import sys ; sys.path.insert(0, '.')
+            import newhooks
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(newhooks)
+            def pytest_myhook(xyz):
+                return xyz + 1
+        """)
+        config = get_config()
+        pm = config.pluginmanager
+        pm.hook.pytest_addhooks.call_historic(
+            kwargs=dict(pluginmanager=config.pluginmanager))
+        config.pluginmanager._importconftest(conf)
+        # print(config.pluginmanager.get_plugins())
+        res = config.hook.pytest_myhook(xyz=10)
+        assert res == [11]
+
+    def test_addhooks_nohooks(self, testdir):
+        testdir.makeconftest("""
+            import sys
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(sys)
+        """)
+        res = testdir.runpytest()
+        assert res.ret != 0
+        res.stderr.fnmatch_lines([
+            "*did not find*sys*"
+        ])
+
+    def test_namespace_early_from_import(self, testdir):
+        p = testdir.makepyfile("""
+            from pytest import Item
+            from pytest import Item as Item2
+            assert Item is Item2
+        """)
+        result = testdir.runpython(p)
+        assert result.ret == 0
+
+    def test_do_ext_namespace(self, testdir):
+        testdir.makeconftest("""
+            def pytest_namespace():
+                return {'hello': 'world'}
+        """)
+        p = testdir.makepyfile("""
+            from pytest import hello
+            import pytest
+            def test_hello():
+                assert hello == "world"
+                assert 'hello' in pytest.__all__
+        """)
+        reprec = testdir.inline_run(p)
+        reprec.assertoutcome(passed=1)
+
+    def test_do_option_postinitialize(self, testdir):
+        config = testdir.parseconfigure()
+        assert not hasattr(config.option, 'test123')
+        p = testdir.makepyfile("""
+            def pytest_addoption(parser):
+                parser.addoption('--test123', action="store_true",
+                    default=True)
+        """)
+        config.pluginmanager._importconftest(p)
+        assert config.option.test123
+
+    def test_configure(self, testdir):
+        config = testdir.parseconfig()
+        values = []
+
+        class A(object):
+            def pytest_configure(self, config):
+                values.append(self)
+
+        config.pluginmanager.register(A())
+        assert len(values) == 0
+        config._do_configure()
+        assert len(values) == 1
+        config.pluginmanager.register(A())  # leads to a configured() plugin
+        assert len(values) == 2
+        assert values[0] != values[1]
+
+        config._ensure_unconfigure()
+        config.pluginmanager.register(A())
+        assert len(values) == 2
+
+    def test_hook_tracing(self):
+        pytestpm = get_config().pluginmanager  # fully initialized with plugins
+        saveindent = []
+
+        class api1(object):
+            def pytest_plugin_registered(self):
+                saveindent.append(pytestpm.trace.root.indent)
+
+        class api2(object):
+            def pytest_plugin_registered(self):
+                saveindent.append(pytestpm.trace.root.indent)
+                raise ValueError()
+
+        values = []
+        pytestpm.trace.root.setwriter(values.append)
+        undo = pytestpm.enable_tracing()
+        try:
+            indent = pytestpm.trace.root.indent
+            p = api1()
+            pytestpm.register(p)
+            assert pytestpm.trace.root.indent == indent
+            assert len(values) >= 2
+            assert 'pytest_plugin_registered' in values[0]
+            assert 'finish' in values[1]
+
+            values[:] = []
+            with pytest.raises(ValueError):
+                pytestpm.register(api2())
+            assert pytestpm.trace.root.indent == indent
+            assert saveindent[0] > indent
+        finally:
+            undo()
+
+    def test_hook_proxy(self, testdir):
+        """Test the gethookproxy function(#2016)"""
+        config = testdir.parseconfig()
+        session = Session(config)
+        testdir.makepyfile(**{
+            'tests/conftest.py': '',
+            'tests/subdir/conftest.py': '',
+        })
+
+        conftest1 = testdir.tmpdir.join('tests/conftest.py')
+        conftest2 = testdir.tmpdir.join('tests/subdir/conftest.py')
+
+        config.pluginmanager._importconftest(conftest1)
+        ihook_a = session.gethookproxy(testdir.tmpdir.join('tests'))
+        assert ihook_a is not None
+        config.pluginmanager._importconftest(conftest2)
+        ihook_b = session.gethookproxy(testdir.tmpdir.join('tests'))
+        assert ihook_a is not ihook_b
+
+    def test_warn_on_deprecated_addhooks(self, pytestpm):
+        warnings = []
+
+        class get_warnings(object):
+            def pytest_logwarning(self, code, fslocation, message, nodeid):
+                warnings.append(message)
+
+        class Plugin(object):
+            def pytest_testhook():
+                pass
+
+        pytestpm.register(get_warnings())
+        before = list(warnings)
+        pytestpm.addhooks(Plugin())
+        assert len(warnings) == len(before) + 1
+        assert "deprecated" in warnings[-1]
+
+
+def test_namespace_has_default_and_env_plugins(testdir):
+    p = testdir.makepyfile("""
+        import pytest
+        pytest.mark
+    """)
+    result = testdir.runpython(p)
+    assert result.ret == 0
+
+
+def test_default_markers(testdir):
+    result = testdir.runpytest("--markers")
+    result.stdout.fnmatch_lines([
+        "*tryfirst*first*",
+        "*trylast*last*",
+    ])
+
+
+def test_importplugin_error_message(testdir, pytestpm):
+    """Don't hide import errors when importing plugins and provide
+    an easy to debug message.
+
+    See #375 and #1998.
+    """
+    testdir.syspathinsert(testdir.tmpdir)
+    testdir.makepyfile(qwe="""
+        # encoding: UTF-8
+        def test_traceback():
+            raise ImportError(u'Not possible to import: ☺')
+        test_traceback()
+    """)
+    with pytest.raises(ImportError) as excinfo:
+        pytestpm.import_plugin("qwe")
+
+    expected_message = '.*Error importing plugin "qwe": Not possible to import: .'
+    expected_traceback = ".*in test_traceback"
+    assert py.std.re.match(expected_message, str(excinfo.value))
+    assert py.std.re.match(expected_traceback, str(excinfo.traceback[-1]))
+
+
+class TestPytestPluginManager(object):
+    def test_register_imported_modules(self):
+        pm = PytestPluginManager()
+        mod = py.std.types.ModuleType("x.y.pytest_hello")
+        pm.register(mod)
+        assert pm.is_registered(mod)
+        values = pm.get_plugins()
+        assert mod in values
+        pytest.raises(ValueError, "pm.register(mod)")
+        pytest.raises(ValueError, lambda: pm.register(mod))
+        # assert not pm.is_registered(mod2)
+        assert pm.get_plugins() == values
+
+    def test_canonical_import(self, monkeypatch):
+        mod = py.std.types.ModuleType("pytest_xyz")
+        monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
+        pm = PytestPluginManager()
+        pm.import_plugin('pytest_xyz')
+        assert pm.get_plugin('pytest_xyz') == mod
+        assert pm.is_registered(mod)
+
+    def test_consider_module(self, testdir, pytestpm):
+        testdir.syspathinsert()
+        testdir.makepyfile(pytest_p1="#")
+        testdir.makepyfile(pytest_p2="#")
+        mod = py.std.types.ModuleType("temp")
+        mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
+        pytestpm.consider_module(mod)
+        assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
+        assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
+
+    def test_consider_module_import_module(self, testdir):
+        pytestpm = get_config().pluginmanager
+        mod = py.std.types.ModuleType("x")
+        mod.pytest_plugins = "pytest_a"
+        aplugin = testdir.makepyfile(pytest_a="#")
+        reprec = testdir.make_hook_recorder(pytestpm)
+        # syspath.prepend(aplugin.dirpath())
+        py.std.sys.path.insert(0, str(aplugin.dirpath()))
+        pytestpm.consider_module(mod)
+        call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
+        assert call.plugin.__name__ == "pytest_a"
+
+        # check that it is not registered twice
+        pytestpm.consider_module(mod)
+        values = reprec.getcalls("pytest_plugin_registered")
+        assert len(values) == 1
+
+    def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
+        monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
+        with pytest.raises(ImportError):
+            pytestpm.consider_env()
+
+    def test_plugin_skip(self, testdir, monkeypatch):
+        p = testdir.makepyfile(skipping1="""
+            import pytest
+            pytest.skip("hello")
+        """)
+        p.copy(p.dirpath("skipping2.py"))
+        monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
+        result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True)
+        assert result.ret == EXIT_NOTESTSCOLLECTED
+        result.stdout.fnmatch_lines([
+            "*skipped plugin*skipping1*hello*",
+            "*skipped plugin*skipping2*hello*",
+        ])
+
+    def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
+        testdir.syspathinsert()
+        testdir.makepyfile(xy123="#")
+        monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
+        l1 = len(pytestpm.get_plugins())
+        pytestpm.consider_env()
+        l2 = len(pytestpm.get_plugins())
+        assert l2 == l1 + 1
+        assert pytestpm.get_plugin('xy123')
+        pytestpm.consider_env()
+        l3 = len(pytestpm.get_plugins())
+        assert l2 == l3
+
+    def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
+        testdir.makepyfile(pytest_x500="#")
+        p = testdir.makepyfile("""
+            import pytest
+            def test_hello(pytestconfig):
+                plugin = pytestconfig.pluginmanager.get_plugin('pytest_x500')
+                assert plugin is not None
+        """)
+        monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
+        result = testdir.runpytest(p, syspathinsert=True)
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(["*1 passed*"])
+
+    def test_import_plugin_importname(self, testdir, pytestpm):
+        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
+        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
+
+        testdir.syspathinsert()
+        pluginname = "pytest_hello"
+        testdir.makepyfile(**{pluginname: ""})
+        pytestpm.import_plugin("pytest_hello")
+        len1 = len(pytestpm.get_plugins())
+        pytestpm.import_plugin("pytest_hello")
+        len2 = len(pytestpm.get_plugins())
+        assert len1 == len2
+        plugin1 = pytestpm.get_plugin("pytest_hello")
+        assert plugin1.__name__.endswith('pytest_hello')
+        plugin2 = pytestpm.get_plugin("pytest_hello")
+        assert plugin2 is plugin1
+
+    def test_import_plugin_dotted_name(self, testdir, pytestpm):
+        pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
+        pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
+
+        testdir.syspathinsert()
+        testdir.mkpydir("pkg").join("plug.py").write("x=3")
+        pluginname = "pkg.plug"
+        pytestpm.import_plugin(pluginname)
+        mod = pytestpm.get_plugin("pkg.plug")
+        assert mod.x == 3
+
+    def test_consider_conftest_deps(self, testdir, pytestpm):
+        mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
+        with pytest.raises(ImportError):
+            pytestpm.consider_conftest(mod)
+
+
+class TestPytestPluginManagerBootstrapming(object):
+    def test_preparse_args(self, pytestpm):
+        pytest.raises(ImportError, lambda:
+                      pytestpm.consider_preparse(["xyz", "-p", "hello123"]))
+
+    def test_plugin_prevent_register(self, pytestpm):
+        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
+        l1 = pytestpm.get_plugins()
+        pytestpm.register(42, name="abc")
+        l2 = pytestpm.get_plugins()
+        assert len(l2) == len(l1)
+        assert 42 not in l2
+
+    def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
+        pytestpm.register(42, name="abc")
+        l1 = pytestpm.get_plugins()
+        assert 42 in l1
+        pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
+        l2 = pytestpm.get_plugins()
+        assert 42 not in l2
diff --git a/tools/third_party/pytest/testing/test_pytester.py b/tools/third_party/pytest/testing/test_pytester.py
new file mode 100644
index 0000000..9508c29
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_pytester.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+import pytest
+import os
+from _pytest.pytester import HookRecorder
+from _pytest.config import PytestPluginManager
+from _pytest.main import EXIT_OK, EXIT_TESTSFAILED
+
+
+def test_make_hook_recorder(testdir):
+    item = testdir.getitem("def test_func(): pass")
+    recorder = testdir.make_hook_recorder(item.config.pluginmanager)
+    assert not recorder.getfailures()
+
+    pytest.xfail("internal reportrecorder tests need refactoring")
+
+    class rep(object):
+        excinfo = None
+        passed = False
+        failed = True
+        skipped = False
+        when = "call"
+
+    recorder.hook.pytest_runtest_logreport(report=rep)
+    failures = recorder.getfailures()
+    assert failures == [rep]
+    failures = recorder.getfailures()
+    assert failures == [rep]
+
+    class rep(object):
+        excinfo = None
+        passed = False
+        failed = False
+        skipped = True
+        when = "call"
+    rep.passed = False
+    rep.skipped = True
+    recorder.hook.pytest_runtest_logreport(report=rep)
+
+    modcol = testdir.getmodulecol("")
+    rep = modcol.config.hook.pytest_make_collect_report(collector=modcol)
+    rep.passed = False
+    rep.failed = True
+    rep.skipped = False
+    recorder.hook.pytest_collectreport(report=rep)
+
+    passed, skipped, failed = recorder.listoutcomes()
+    assert not passed and skipped and failed
+
+    numpassed, numskipped, numfailed = recorder.countoutcomes()
+    assert numpassed == 0
+    assert numskipped == 1
+    assert numfailed == 1
+    assert len(recorder.getfailedcollections()) == 1
+
+    recorder.unregister()
+    recorder.clear()
+    recorder.hook.pytest_runtest_logreport(report=rep)
+    pytest.raises(ValueError, "recorder.getfailures()")
+
+
+def test_parseconfig(testdir):
+    config1 = testdir.parseconfig()
+    config2 = testdir.parseconfig()
+    assert config2 != config1
+    assert config1 != pytest.config
+
+
+def test_testdir_runs_with_plugin(testdir):
+    testdir.makepyfile("""
+        pytest_plugins = "pytester"
+        def test_hello(testdir):
+            assert 1
+    """)
+    result = testdir.runpytest()
+    result.assert_outcomes(passed=1)
+
+
+def make_holder():
+    class apiclass(object):
+        def pytest_xyz(self, arg):
+            "x"
+
+        def pytest_xyz_noarg(self):
+            "x"
+
+    apimod = type(os)('api')
+
+    def pytest_xyz(arg):
+        "x"
+
+    def pytest_xyz_noarg():
+        "x"
+
+    apimod.pytest_xyz = pytest_xyz
+    apimod.pytest_xyz_noarg = pytest_xyz_noarg
+    return apiclass, apimod
+
+
+@pytest.mark.parametrize("holder", make_holder())
+def test_hookrecorder_basic(holder):
+    pm = PytestPluginManager()
+    pm.addhooks(holder)
+    rec = HookRecorder(pm)
+    pm.hook.pytest_xyz(arg=123)
+    call = rec.popcall("pytest_xyz")
+    assert call.arg == 123
+    assert call._name == "pytest_xyz"
+    pytest.raises(pytest.fail.Exception, "rec.popcall('abc')")
+    pm.hook.pytest_xyz_noarg()
+    call = rec.popcall("pytest_xyz_noarg")
+    assert call._name == "pytest_xyz_noarg"
+
+
+def test_makepyfile_unicode(testdir):
+    global unichr
+    try:
+        unichr(65)
+    except NameError:
+        unichr = chr
+    testdir.makepyfile(unichr(0xfffd))
+
+
+def test_makepyfile_utf8(testdir):
+    """Ensure makepyfile accepts utf-8 bytes as input (#2738)"""
+    utf8_contents = u"""
+        def setup_function(function):
+            mixed_encoding = u'São Paulo'
+    """.encode('utf-8')
+    p = testdir.makepyfile(utf8_contents)
+    assert u"mixed_encoding = u'São Paulo'".encode('utf-8') in p.read('rb')
+
+
+def test_inline_run_clean_modules(testdir):
+    test_mod = testdir.makepyfile("def test_foo(): assert True")
+    result = testdir.inline_run(str(test_mod))
+    assert result.ret == EXIT_OK
+    # rewrite module, now test should fail if module was re-imported
+    test_mod.write("def test_foo(): assert False")
+    result2 = testdir.inline_run(str(test_mod))
+    assert result2.ret == EXIT_TESTSFAILED
+
+
+def test_assert_outcomes_after_pytest_erro(testdir):
+    testdir.makepyfile("def test_foo(): assert True")
+
+    result = testdir.runpytest('--unexpected-argument')
+    with pytest.raises(ValueError, message="Pytest terminal report not found"):
+        result.assert_outcomes(passed=0)
diff --git a/tools/third_party/pytest/testing/test_recwarn.py b/tools/third_party/pytest/testing/test_recwarn.py
new file mode 100644
index 0000000..31e7046
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_recwarn.py
@@ -0,0 +1,310 @@
+from __future__ import absolute_import, division, print_function
+import warnings
+import re
+import py
+
+import pytest
+from _pytest.recwarn import WarningsRecorder
+
+
+def test_recwarn_functional(testdir):
+    reprec = testdir.inline_runsource("""
+        import warnings
+        def test_method(recwarn):
+            warnings.warn("hello")
+            warn = recwarn.pop()
+            assert isinstance(warn.message, UserWarning)
+    """)
+    res = reprec.countoutcomes()
+    assert tuple(res) == (1, 0, 0), res
+
+
+class TestWarningsRecorderChecker(object):
+    def test_recording(self):
+        rec = WarningsRecorder()
+        with rec:
+            assert not rec.list
+            py.std.warnings.warn_explicit("hello", UserWarning, "xyz", 13)
+            assert len(rec.list) == 1
+            py.std.warnings.warn(DeprecationWarning("hello"))
+            assert len(rec.list) == 2
+            warn = rec.pop()
+            assert str(warn.message) == "hello"
+            values = rec.list
+            rec.clear()
+            assert len(rec.list) == 0
+            assert values is rec.list
+            pytest.raises(AssertionError, "rec.pop()")
+
+    def test_typechecking(self):
+        from _pytest.recwarn import WarningsChecker
+        with pytest.raises(TypeError):
+            WarningsChecker(5)
+        with pytest.raises(TypeError):
+            WarningsChecker(('hi', RuntimeWarning))
+        with pytest.raises(TypeError):
+            WarningsChecker([DeprecationWarning, RuntimeWarning])
+
+    def test_invalid_enter_exit(self):
+        # wrap this test in WarningsRecorder to ensure warning state gets reset
+        with WarningsRecorder():
+            with pytest.raises(RuntimeError):
+                rec = WarningsRecorder()
+                rec.__exit__(None, None, None)  # can't exit before entering
+
+            with pytest.raises(RuntimeError):
+                rec = WarningsRecorder()
+                with rec:
+                    with rec:
+                        pass  # can't enter twice
+
+
+class TestDeprecatedCall(object):
+    """test pytest.deprecated_call()"""
+
+    def dep(self, i, j=None):
+        if i == 0:
+            py.std.warnings.warn("is deprecated", DeprecationWarning,
+                                 stacklevel=1)
+        return 42
+
+    def dep_explicit(self, i):
+        if i == 0:
+            py.std.warnings.warn_explicit("dep_explicit", category=DeprecationWarning,
+                                          filename="hello", lineno=3)
+
+    def test_deprecated_call_raises(self):
+        with pytest.raises(AssertionError) as excinfo:
+            pytest.deprecated_call(self.dep, 3, 5)
+        assert 'Did not produce' in str(excinfo)
+
+    def test_deprecated_call(self):
+        pytest.deprecated_call(self.dep, 0, 5)
+
+    def test_deprecated_call_ret(self):
+        ret = pytest.deprecated_call(self.dep, 0)
+        assert ret == 42
+
+    def test_deprecated_call_preserves(self):
+        onceregistry = py.std.warnings.onceregistry.copy()
+        filters = py.std.warnings.filters[:]
+        warn = py.std.warnings.warn
+        warn_explicit = py.std.warnings.warn_explicit
+        self.test_deprecated_call_raises()
+        self.test_deprecated_call()
+        assert onceregistry == py.std.warnings.onceregistry
+        assert filters == py.std.warnings.filters
+        assert warn is py.std.warnings.warn
+        assert warn_explicit is py.std.warnings.warn_explicit
+
+    def test_deprecated_explicit_call_raises(self):
+        with pytest.raises(AssertionError):
+            pytest.deprecated_call(self.dep_explicit, 3)
+
+    def test_deprecated_explicit_call(self):
+        pytest.deprecated_call(self.dep_explicit, 0)
+        pytest.deprecated_call(self.dep_explicit, 0)
+
+    @pytest.mark.parametrize('mode', ['context_manager', 'call'])
+    def test_deprecated_call_no_warning(self, mode):
+        """Ensure deprecated_call() raises the expected failure when its block/function does
+        not raise a deprecation warning.
+        """
+        def f():
+            pass
+
+        msg = 'Did not produce DeprecationWarning or PendingDeprecationWarning'
+        with pytest.raises(AssertionError, matches=msg):
+            if mode == 'call':
+                pytest.deprecated_call(f)
+            else:
+                with pytest.deprecated_call():
+                    f()
+
+    @pytest.mark.parametrize('warning_type', [PendingDeprecationWarning, DeprecationWarning])
+    @pytest.mark.parametrize('mode', ['context_manager', 'call'])
+    @pytest.mark.parametrize('call_f_first', [True, False])
+    @pytest.mark.filterwarnings('ignore')
+    def test_deprecated_call_modes(self, warning_type, mode, call_f_first):
+        """Ensure deprecated_call() captures a deprecation warning as expected inside its
+        block/function.
+        """
+        def f():
+            warnings.warn(warning_type("hi"))
+            return 10
+
+        # ensure deprecated_call() can capture the warning even if it has already been triggered
+        if call_f_first:
+            assert f() == 10
+        if mode == 'call':
+            assert pytest.deprecated_call(f) == 10
+        else:
+            with pytest.deprecated_call():
+                assert f() == 10
+
+    @pytest.mark.parametrize('mode', ['context_manager', 'call'])
+    def test_deprecated_call_exception_is_raised(self, mode):
+        """If the block of the code being tested by deprecated_call() raises an exception,
+        it must raise the exception undisturbed.
+        """
+        def f():
+            raise ValueError('some exception')
+
+        with pytest.raises(ValueError, match='some exception'):
+            if mode == 'call':
+                pytest.deprecated_call(f)
+            else:
+                with pytest.deprecated_call():
+                    f()
+
+    def test_deprecated_call_specificity(self):
+        other_warnings = [Warning, UserWarning, SyntaxWarning, RuntimeWarning,
+                          FutureWarning, ImportWarning, UnicodeWarning]
+        for warning in other_warnings:
+            def f():
+                warnings.warn(warning("hi"))
+
+            with pytest.raises(AssertionError):
+                pytest.deprecated_call(f)
+            with pytest.raises(AssertionError):
+                with pytest.deprecated_call():
+                    f()
+
+
+class TestWarns(object):
+    def test_strings(self):
+        # different messages, b/c Python suppresses multiple identical warnings
+        source1 = "warnings.warn('w1', RuntimeWarning)"
+        source2 = "warnings.warn('w2', RuntimeWarning)"
+        source3 = "warnings.warn('w3', RuntimeWarning)"
+        pytest.warns(RuntimeWarning, source1)
+        pytest.raises(pytest.fail.Exception,
+                      lambda: pytest.warns(UserWarning, source2))
+        pytest.warns(RuntimeWarning, source3)
+
+    def test_function(self):
+        pytest.warns(SyntaxWarning,
+                     lambda msg: warnings.warn(msg, SyntaxWarning), "syntax")
+
+    def test_warning_tuple(self):
+        pytest.warns((RuntimeWarning, SyntaxWarning),
+                     lambda: warnings.warn('w1', RuntimeWarning))
+        pytest.warns((RuntimeWarning, SyntaxWarning),
+                     lambda: warnings.warn('w2', SyntaxWarning))
+        pytest.raises(pytest.fail.Exception,
+                      lambda: pytest.warns(
+                          (RuntimeWarning, SyntaxWarning),
+                          lambda: warnings.warn('w3', UserWarning)))
+
+    def test_as_contextmanager(self):
+        with pytest.warns(RuntimeWarning):
+            warnings.warn("runtime", RuntimeWarning)
+
+        with pytest.warns(UserWarning):
+            warnings.warn("user", UserWarning)
+
+        with pytest.raises(pytest.fail.Exception) as excinfo:
+            with pytest.warns(RuntimeWarning):
+                warnings.warn("user", UserWarning)
+        excinfo.match(r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) was emitted. "
+                      r"The list of emitted warnings is: \[UserWarning\('user',\)\].")
+
+        with pytest.raises(pytest.fail.Exception) as excinfo:
+            with pytest.warns(UserWarning):
+                warnings.warn("runtime", RuntimeWarning)
+        excinfo.match(r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. "
+                      r"The list of emitted warnings is: \[RuntimeWarning\('runtime',\)\].")
+
+        with pytest.raises(pytest.fail.Exception) as excinfo:
+            with pytest.warns(UserWarning):
+                pass
+        excinfo.match(r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. "
+                      r"The list of emitted warnings is: \[\].")
+
+        warning_classes = (UserWarning, FutureWarning)
+        with pytest.raises(pytest.fail.Exception) as excinfo:
+            with pytest.warns(warning_classes) as warninfo:
+                warnings.warn("runtime", RuntimeWarning)
+                warnings.warn("import", ImportWarning)
+
+        message_template = ("DID NOT WARN. No warnings of type {0} was emitted. "
+                            "The list of emitted warnings is: {1}.")
+        excinfo.match(re.escape(message_template.format(warning_classes,
+                                                        [each.message for each in warninfo])))
+
+    def test_record(self):
+        with pytest.warns(UserWarning) as record:
+            warnings.warn("user", UserWarning)
+
+        assert len(record) == 1
+        assert str(record[0].message) == "user"
+
+    def test_record_only(self):
+        with pytest.warns(None) as record:
+            warnings.warn("user", UserWarning)
+            warnings.warn("runtime", RuntimeWarning)
+
+        assert len(record) == 2
+        assert str(record[0].message) == "user"
+        assert str(record[1].message) == "runtime"
+
+    def test_record_by_subclass(self):
+        with pytest.warns(Warning) as record:
+            warnings.warn("user", UserWarning)
+            warnings.warn("runtime", RuntimeWarning)
+
+        assert len(record) == 2
+        assert str(record[0].message) == "user"
+        assert str(record[1].message) == "runtime"
+
+        class MyUserWarning(UserWarning):
+            pass
+
+        class MyRuntimeWarning(RuntimeWarning):
+            pass
+
+        with pytest.warns((UserWarning, RuntimeWarning)) as record:
+            warnings.warn("user", MyUserWarning)
+            warnings.warn("runtime", MyRuntimeWarning)
+
+        assert len(record) == 2
+        assert str(record[0].message) == "user"
+        assert str(record[1].message) == "runtime"
+
+    def test_double_test(self, testdir):
+        """If a test is run again, the warning should still be raised"""
+        testdir.makepyfile('''
+            import pytest
+            import warnings
+
+            @pytest.mark.parametrize('run', [1, 2])
+            def test(run):
+                with pytest.warns(RuntimeWarning):
+                    warnings.warn("runtime", RuntimeWarning)
+        ''')
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['*2 passed in*'])
+
+    def test_match_regex(self):
+        with pytest.warns(UserWarning, match=r'must be \d+$'):
+            warnings.warn("value must be 42", UserWarning)
+
+        with pytest.raises(pytest.fail.Exception):
+            with pytest.warns(UserWarning, match=r'must be \d+$'):
+                warnings.warn("this is not here", UserWarning)
+
+        with pytest.raises(pytest.fail.Exception):
+            with pytest.warns(FutureWarning, match=r'must be \d+$'):
+                warnings.warn("value must be 42", UserWarning)
+
+    def test_one_from_multiple_warns(self):
+        with pytest.warns(UserWarning, match=r'aaa'):
+            warnings.warn("cccccccccc", UserWarning)
+            warnings.warn("bbbbbbbbbb", UserWarning)
+            warnings.warn("aaaaaaaaaa", UserWarning)
+
+    def test_none_of_multiple_warns(self):
+        with pytest.raises(pytest.fail.Exception):
+            with pytest.warns(UserWarning, match=r'aaa'):
+                warnings.warn("bbbbbbbbbb", UserWarning)
+                warnings.warn("cccccccccc", UserWarning)
diff --git a/tools/third_party/pytest/testing/test_resultlog.py b/tools/third_party/pytest/testing/test_resultlog.py
new file mode 100644
index 0000000..b7dd268
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_resultlog.py
@@ -0,0 +1,228 @@
+from __future__ import absolute_import, division, print_function
+import os
+
+import _pytest._code
+import py
+import pytest
+from _pytest.main import Node, Item, FSCollector
+from _pytest.resultlog import generic_path, ResultLog, \
+    pytest_configure, pytest_unconfigure
+
+
+def test_generic_path(testdir):
+    from _pytest.main import Session
+    config = testdir.parseconfig()
+    session = Session(config)
+    p1 = Node('a', config=config, session=session)
+    # assert p1.fspath is None
+    p2 = Node('B', parent=p1)
+    p3 = Node('()', parent=p2)
+    item = Item('c', parent=p3)
+
+    res = generic_path(item)
+    assert res == 'a.B().c'
+
+    p0 = FSCollector('proj/test', config=config, session=session)
+    p1 = FSCollector('proj/test/a', parent=p0)
+    p2 = Node('B', parent=p1)
+    p3 = Node('()', parent=p2)
+    p4 = Node('c', parent=p3)
+    item = Item('[1]', parent=p4)
+
+    res = generic_path(item)
+    assert res == 'test/a:B().c[1]'
+
+
+def test_write_log_entry():
+    reslog = ResultLog(None, None)
+    reslog.logfile = py.io.TextIO()
+    reslog.write_log_entry('name', '.', '')
+    entry = reslog.logfile.getvalue()
+    assert entry[-1] == '\n'
+    entry_lines = entry.splitlines()
+    assert len(entry_lines) == 1
+    assert entry_lines[0] == '. name'
+
+    reslog.logfile = py.io.TextIO()
+    reslog.write_log_entry('name', 's', 'Skipped')
+    entry = reslog.logfile.getvalue()
+    assert entry[-1] == '\n'
+    entry_lines = entry.splitlines()
+    assert len(entry_lines) == 2
+    assert entry_lines[0] == 's name'
+    assert entry_lines[1] == ' Skipped'
+
+    reslog.logfile = py.io.TextIO()
+    reslog.write_log_entry('name', 's', 'Skipped\n')
+    entry = reslog.logfile.getvalue()
+    assert entry[-1] == '\n'
+    entry_lines = entry.splitlines()
+    assert len(entry_lines) == 2
+    assert entry_lines[0] == 's name'
+    assert entry_lines[1] == ' Skipped'
+
+    reslog.logfile = py.io.TextIO()
+    longrepr = ' tb1\n tb 2\nE tb3\nSome Error'
+    reslog.write_log_entry('name', 'F', longrepr)
+    entry = reslog.logfile.getvalue()
+    assert entry[-1] == '\n'
+    entry_lines = entry.splitlines()
+    assert len(entry_lines) == 5
+    assert entry_lines[0] == 'F name'
+    assert entry_lines[1:] == [' ' + line for line in longrepr.splitlines()]
+
+
+class TestWithFunctionIntegration(object):
+    # XXX (hpk) i think that the resultlog plugin should
+    # provide a Parser object so that one can remain
+    # ignorant regarding formatting details.
+    def getresultlog(self, testdir, arg):
+        resultlog = testdir.tmpdir.join("resultlog")
+        testdir.plugins.append("resultlog")
+        args = ["--resultlog=%s" % resultlog] + [arg]
+        testdir.runpytest(*args)
+        return [x for x in resultlog.readlines(cr=0) if x]
+
+    def test_collection_report(self, testdir):
+        ok = testdir.makepyfile(test_collection_ok="")
+        fail = testdir.makepyfile(test_collection_fail="XXX")
+        lines = self.getresultlog(testdir, ok)
+        assert not lines
+
+        lines = self.getresultlog(testdir, fail)
+        assert lines
+        assert lines[0].startswith("F ")
+        assert lines[0].endswith("test_collection_fail.py"), lines[0]
+        for x in lines[1:]:
+            assert x.startswith(" ")
+        assert "XXX" in "".join(lines[1:])
+
+    def test_log_test_outcomes(self, testdir):
+        mod = testdir.makepyfile(test_mod="""
+            import pytest
+            def test_pass(): pass
+            def test_skip(): pytest.skip("hello")
+            def test_fail(): raise ValueError("FAIL")
+
+            @pytest.mark.xfail
+            def test_xfail(): raise ValueError("XFAIL")
+            @pytest.mark.xfail
+            def test_xpass(): pass
+
+        """)
+        lines = self.getresultlog(testdir, mod)
+        assert len(lines) >= 3
+        assert lines[0].startswith(". ")
+        assert lines[0].endswith("test_pass")
+        assert lines[1].startswith("s "), lines[1]
+        assert lines[1].endswith("test_skip")
+        assert lines[2].find("hello") != -1
+
+        assert lines[3].startswith("F ")
+        assert lines[3].endswith("test_fail")
+        tb = "".join(lines[4:8])
+        assert tb.find('raise ValueError("FAIL")') != -1
+
+        assert lines[8].startswith('x ')
+        tb = "".join(lines[8:14])
+        assert tb.find('raise ValueError("XFAIL")') != -1
+
+        assert lines[14].startswith('X ')
+        assert len(lines) == 15
+
+    @pytest.mark.parametrize("style", ("native", "long", "short"))
+    def test_internal_exception(self, style):
+        # they are produced for example by a teardown failing
+        # at the end of the run or a failing hook invocation
+        try:
+            raise ValueError
+        except ValueError:
+            excinfo = _pytest._code.ExceptionInfo()
+        reslog = ResultLog(None, py.io.TextIO())
+        reslog.pytest_internalerror(excinfo.getrepr(style=style))
+        entry = reslog.logfile.getvalue()
+        entry_lines = entry.splitlines()
+
+        assert entry_lines[0].startswith('! ')
+        if style != "native":
+            assert os.path.basename(__file__)[:-9] in entry_lines[0]  # .pyc/class
+        assert entry_lines[-1][0] == ' '
+        assert 'ValueError' in entry
+
+
+def test_generic(testdir, LineMatcher):
+    testdir.plugins.append("resultlog")
+    testdir.makepyfile("""
+        import pytest
+        def test_pass():
+            pass
+        def test_fail():
+            assert 0
+        def test_skip():
+            pytest.skip("")
+        @pytest.mark.xfail
+        def test_xfail():
+            assert 0
+        @pytest.mark.xfail(run=False)
+        def test_xfail_norun():
+            assert 0
+    """)
+    testdir.runpytest("--resultlog=result.log")
+    lines = testdir.tmpdir.join("result.log").readlines(cr=0)
+    LineMatcher(lines).fnmatch_lines([
+        ". *:test_pass",
+        "F *:test_fail",
+        "s *:test_skip",
+        "x *:test_xfail",
+        "x *:test_xfail_norun",
+    ])
+
+
+def test_makedir_for_resultlog(testdir, LineMatcher):
+    """--resultlog should automatically create directories for the log file"""
+    testdir.plugins.append("resultlog")
+    testdir.makepyfile("""
+        import pytest
+        def test_pass():
+            pass
+    """)
+    testdir.runpytest("--resultlog=path/to/result.log")
+    lines = testdir.tmpdir.join("path/to/result.log").readlines(cr=0)
+    LineMatcher(lines).fnmatch_lines([
+        ". *:test_pass",
+    ])
+
+
+def test_no_resultlog_on_slaves(testdir):
+    config = testdir.parseconfig("-p", "resultlog", "--resultlog=resultlog")
+
+    assert not hasattr(config, '_resultlog')
+    pytest_configure(config)
+    assert hasattr(config, '_resultlog')
+    pytest_unconfigure(config)
+    assert not hasattr(config, '_resultlog')
+
+    config.slaveinput = {}
+    pytest_configure(config)
+    assert not hasattr(config, '_resultlog')
+    pytest_unconfigure(config)
+    assert not hasattr(config, '_resultlog')
+
+
+def test_failure_issue380(testdir):
+    testdir.makeconftest("""
+        import pytest
+        class MyCollector(pytest.File):
+            def collect(self):
+                raise ValueError()
+            def repr_failure(self, excinfo):
+                return "somestring"
+        def pytest_collect_file(path, parent):
+            return MyCollector(parent=parent, fspath=path)
+    """)
+    testdir.makepyfile("""
+        def test_func():
+            pass
+    """)
+    result = testdir.runpytest("--resultlog=log")
+    assert result.ret == 2
diff --git a/tools/third_party/pytest/testing/test_runner.py b/tools/third_party/pytest/testing/test_runner.py
new file mode 100644
index 0000000..c8e2a64
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_runner.py
@@ -0,0 +1,813 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+import _pytest._code
+import os
+import py
+import pytest
+import sys
+from _pytest import runner, main, outcomes
+
+
+class TestSetupState(object):
+    def test_setup(self, testdir):
+        ss = runner.SetupState()
+        item = testdir.getitem("def test_func(): pass")
+        values = [1]
+        ss.prepare(item)
+        ss.addfinalizer(values.pop, colitem=item)
+        assert values
+        ss._pop_and_teardown()
+        assert not values
+
+    def test_teardown_exact_stack_empty(self, testdir):
+        item = testdir.getitem("def test_func(): pass")
+        ss = runner.SetupState()
+        ss.teardown_exact(item, None)
+        ss.teardown_exact(item, None)
+        ss.teardown_exact(item, None)
+
+    def test_setup_fails_and_failure_is_cached(self, testdir):
+        item = testdir.getitem("""
+            def setup_module(mod):
+                raise ValueError(42)
+            def test_func(): pass
+        """)  # noqa
+        ss = runner.SetupState()
+        pytest.raises(ValueError, lambda: ss.prepare(item))
+        pytest.raises(ValueError, lambda: ss.prepare(item))
+
+    def test_teardown_multiple_one_fails(self, testdir):
+        r = []
+
+        def fin1():
+            r.append('fin1')
+
+        def fin2():
+            raise Exception('oops')
+
+        def fin3():
+            r.append('fin3')
+
+        item = testdir.getitem("def test_func(): pass")
+        ss = runner.SetupState()
+        ss.addfinalizer(fin1, item)
+        ss.addfinalizer(fin2, item)
+        ss.addfinalizer(fin3, item)
+        with pytest.raises(Exception) as err:
+            ss._callfinalizers(item)
+        assert err.value.args == ('oops',)
+        assert r == ['fin3', 'fin1']
+
+    def test_teardown_multiple_fail(self, testdir):
+        # Ensure the first exception is the one which is re-raised.
+        # Ideally both would be reported however.
+        def fin1():
+            raise Exception('oops1')
+
+        def fin2():
+            raise Exception('oops2')
+
+        item = testdir.getitem("def test_func(): pass")
+        ss = runner.SetupState()
+        ss.addfinalizer(fin1, item)
+        ss.addfinalizer(fin2, item)
+        with pytest.raises(Exception) as err:
+            ss._callfinalizers(item)
+        assert err.value.args == ('oops2',)
+
+
+class BaseFunctionalTests(object):
+    def test_passfunction(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                pass
+        """)
+        rep = reports[1]
+        assert rep.passed
+        assert not rep.failed
+        assert rep.outcome == "passed"
+        assert not rep.longrepr
+
+    def test_failfunction(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                assert 0
+        """)
+        rep = reports[1]
+        assert not rep.passed
+        assert not rep.skipped
+        assert rep.failed
+        assert rep.when == "call"
+        assert rep.outcome == "failed"
+        # assert isinstance(rep.longrepr, ReprExceptionInfo)
+
+    def test_skipfunction(self, testdir):
+        reports = testdir.runitem("""
+            import pytest
+            def test_func():
+                pytest.skip("hello")
+        """)
+        rep = reports[1]
+        assert not rep.failed
+        assert not rep.passed
+        assert rep.skipped
+        assert rep.outcome == "skipped"
+        # assert rep.skipped.when == "call"
+        # assert rep.skipped.when == "call"
+        # assert rep.skipped == "%sreason == "hello"
+        # assert rep.skipped.location.lineno == 3
+        # assert rep.skipped.location.path
+        # assert not rep.skipped.failurerepr
+
+    def test_skip_in_setup_function(self, testdir):
+        reports = testdir.runitem("""
+            import pytest
+            def setup_function(func):
+                pytest.skip("hello")
+            def test_func():
+                pass
+        """)
+        print(reports)
+        rep = reports[0]
+        assert not rep.failed
+        assert not rep.passed
+        assert rep.skipped
+        # assert rep.skipped.reason == "hello"
+        # assert rep.skipped.location.lineno == 3
+        # assert rep.skipped.location.lineno == 3
+        assert len(reports) == 2
+        assert reports[1].passed  # teardown
+
+    def test_failure_in_setup_function(self, testdir):
+        reports = testdir.runitem("""
+            import pytest
+            def setup_function(func):
+                raise ValueError(42)
+            def test_func():
+                pass
+        """)
+        rep = reports[0]
+        assert not rep.skipped
+        assert not rep.passed
+        assert rep.failed
+        assert rep.when == "setup"
+        assert len(reports) == 2
+
+    def test_failure_in_teardown_function(self, testdir):
+        reports = testdir.runitem("""
+            import pytest
+            def teardown_function(func):
+                raise ValueError(42)
+            def test_func():
+                pass
+        """)
+        print(reports)
+        assert len(reports) == 3
+        rep = reports[2]
+        assert not rep.skipped
+        assert not rep.passed
+        assert rep.failed
+        assert rep.when == "teardown"
+        # assert rep.longrepr.reprcrash.lineno == 3
+        # assert rep.longrepr.reprtraceback.reprentries
+
+    def test_custom_failure_repr(self, testdir):
+        testdir.makepyfile(conftest="""
+            import pytest
+            class Function(pytest.Function):
+                def repr_failure(self, excinfo):
+                    return "hello"
+        """)
+        reports = testdir.runitem("""
+            import pytest
+            def test_func():
+                assert 0
+        """)
+        rep = reports[1]
+        assert not rep.skipped
+        assert not rep.passed
+        assert rep.failed
+        # assert rep.outcome.when == "call"
+        # assert rep.failed.where.lineno == 3
+        # assert rep.failed.where.path.basename == "test_func.py"
+        # assert rep.failed.failurerepr == "hello"
+
+    def test_teardown_final_returncode(self, testdir):
+        rec = testdir.inline_runsource("""
+            def test_func():
+                pass
+            def teardown_function(func):
+                raise ValueError(42)
+        """)
+        assert rec.ret == 1
+
+    def test_exact_teardown_issue90(self, testdir):
+        rec = testdir.inline_runsource("""
+            import pytest
+
+            class TestClass(object):
+                def test_method(self):
+                    pass
+                def teardown_class(cls):
+                    raise Exception()
+
+            def test_func():
+                import sys
+                # on python2 exc_info is keept till a function exits
+                # so we would end up calling test functions while
+                # sys.exc_info would return the indexerror
+                # from guessing the lastitem
+                excinfo = sys.exc_info()
+                import traceback
+                assert excinfo[0] is None, \
+                       traceback.format_exception(*excinfo)
+            def teardown_function(func):
+                raise ValueError(42)
+        """)
+        reps = rec.getreports("pytest_runtest_logreport")
+        print(reps)
+        for i in range(2):
+            assert reps[i].nodeid.endswith("test_method")
+            assert reps[i].passed
+        assert reps[2].when == "teardown"
+        assert reps[2].failed
+        assert len(reps) == 6
+        for i in range(3, 5):
+            assert reps[i].nodeid.endswith("test_func")
+            assert reps[i].passed
+        assert reps[5].when == "teardown"
+        assert reps[5].nodeid.endswith("test_func")
+        assert reps[5].failed
+
+    def test_exact_teardown_issue1206(self, testdir):
+        """issue shadowing error with wrong number of arguments on teardown_method."""
+        rec = testdir.inline_runsource("""
+            import pytest
+
+            class TestClass(object):
+                def teardown_method(self, x, y, z):
+                    pass
+
+                def test_method(self):
+                    assert True
+        """)
+        reps = rec.getreports("pytest_runtest_logreport")
+        print(reps)
+        assert len(reps) == 3
+        #
+        assert reps[0].nodeid.endswith("test_method")
+        assert reps[0].passed
+        assert reps[0].when == 'setup'
+        #
+        assert reps[1].nodeid.endswith("test_method")
+        assert reps[1].passed
+        assert reps[1].when == 'call'
+        #
+        assert reps[2].nodeid.endswith("test_method")
+        assert reps[2].failed
+        assert reps[2].when == "teardown"
+        assert reps[2].longrepr.reprcrash.message in (
+            # python3 error
+            "TypeError: teardown_method() missing 2 required positional arguments: 'y' and 'z'",
+            # python2 error
+            'TypeError: teardown_method() takes exactly 4 arguments (2 given)'
+        )
+
+    def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
+        testdir.makepyfile(conftest="""
+            import pytest
+            class Function(pytest.Function):
+                def repr_failure(self, excinfo):
+                    assert 0
+        """)
+        reports = testdir.runitem("""
+            def setup_function(func):
+                raise ValueError(42)
+            def test_func():
+                pass
+        """)
+        assert len(reports) == 2
+        rep = reports[0]
+        print(rep)
+        assert not rep.skipped
+        assert not rep.passed
+        assert rep.failed
+        # assert rep.outcome.when == "setup"
+        # assert rep.outcome.where.lineno == 3
+        # assert rep.outcome.where.path.basename == "test_func.py"
+        # assert instanace(rep.failed.failurerepr, PythonFailureRepr)
+
+    def test_systemexit_does_not_bail_out(self, testdir):
+        try:
+            reports = testdir.runitem("""
+                def test_func():
+                    raise SystemExit(42)
+            """)
+        except SystemExit:
+            pytest.fail("runner did not catch SystemExit")
+        rep = reports[1]
+        assert rep.failed
+        assert rep.when == "call"
+
+    def test_exit_propagates(self, testdir):
+        try:
+            testdir.runitem("""
+                import pytest
+                def test_func():
+                    raise pytest.exit.Exception()
+            """)
+        except pytest.exit.Exception:
+            pass
+        else:
+            pytest.fail("did not raise")
+
+
+class TestExecutionNonForked(BaseFunctionalTests):
+    def getrunner(self):
+        def f(item):
+            return runner.runtestprotocol(item, log=False)
+        return f
+
+    def test_keyboardinterrupt_propagates(self, testdir):
+        try:
+            testdir.runitem("""
+                def test_func():
+                    raise KeyboardInterrupt("fake")
+            """)
+        except KeyboardInterrupt:
+            pass
+        else:
+            pytest.fail("did not raise")
+
+
+class TestExecutionForked(BaseFunctionalTests):
+    pytestmark = pytest.mark.skipif("not hasattr(os, 'fork')")
+
+    def getrunner(self):
+        # XXX re-arrange this test to live in pytest-xdist
+        boxed = pytest.importorskip("xdist.boxed")
+        return boxed.forked_run_report
+
+    def test_suicide(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                import os
+                os.kill(os.getpid(), 15)
+        """)
+        rep = reports[0]
+        assert rep.failed
+        assert rep.when == "???"
+
+
+class TestSessionReports(object):
+    def test_collect_result(self, testdir):
+        col = testdir.getmodulecol("""
+            def test_func1():
+                pass
+            class TestClass(object):
+                pass
+        """)
+        rep = runner.collect_one_node(col)
+        assert not rep.failed
+        assert not rep.skipped
+        assert rep.passed
+        locinfo = rep.location
+        assert locinfo[0] == col.fspath.basename
+        assert not locinfo[1]
+        assert locinfo[2] == col.fspath.basename
+        res = rep.result
+        assert len(res) == 2
+        assert res[0].name == "test_func1"
+        assert res[1].name == "TestClass"
+
+
+reporttypes = [
+    runner.BaseReport,
+    runner.TestReport,
+    runner.TeardownErrorReport,
+    runner.CollectReport,
+]
+
+
+@pytest.mark.parametrize('reporttype', reporttypes, ids=[x.__name__ for x in reporttypes])
+def test_report_extra_parameters(reporttype):
+    if hasattr(py.std.inspect, 'signature'):
+        args = list(py.std.inspect.signature(reporttype.__init__).parameters.keys())[1:]
+    else:
+        args = py.std.inspect.getargspec(reporttype.__init__)[0][1:]
+    basekw = dict.fromkeys(args, [])
+    report = reporttype(newthing=1, **basekw)
+    assert report.newthing == 1
+
+
+def test_callinfo():
+    ci = runner.CallInfo(lambda: 0, '123')
+    assert ci.when == "123"
+    assert ci.result == 0
+    assert "result" in repr(ci)
+    ci = runner.CallInfo(lambda: 0 / 0, '123')
+    assert ci.when == "123"
+    assert not hasattr(ci, 'result')
+    assert ci.excinfo
+    assert "exc" in repr(ci)
+
+# design question: do we want general hooks in python files?
+# then something like the following functional tests makes sense
+
+
+@pytest.mark.xfail
+def test_runtest_in_module_ordering(testdir):
+    p1 = testdir.makepyfile("""
+        import pytest
+        def pytest_runtest_setup(item): # runs after class-level!
+            item.function.mylist.append("module")
+        class TestClass(object):
+            def pytest_runtest_setup(self, item):
+                assert not hasattr(item.function, 'mylist')
+                item.function.mylist = ['class']
+            @pytest.fixture
+            def mylist(self, request):
+                return request.function.mylist
+            def pytest_runtest_call(self, item, __multicall__):
+                try:
+                    __multicall__.execute()
+                except ValueError:
+                    pass
+            def test_hello1(self, mylist):
+                assert mylist == ['class', 'module'], mylist
+                raise ValueError()
+            def test_hello2(self, mylist):
+                assert mylist == ['class', 'module'], mylist
+        def pytest_runtest_teardown(item):
+            del item.function.mylist
+    """)
+    result = testdir.runpytest(p1)
+    result.stdout.fnmatch_lines([
+        "*2 passed*"
+    ])
+
+
+def test_outcomeexception_exceptionattributes():
+    outcome = outcomes.OutcomeException('test')
+    assert outcome.args[0] == outcome.msg
+
+
+def test_outcomeexception_passes_except_Exception():
+    with pytest.raises(outcomes.OutcomeException):
+        try:
+            raise outcomes.OutcomeException('test')
+        except Exception:
+            pass
+
+
+def test_pytest_exit():
+    try:
+        pytest.exit("hello")
+    except pytest.exit.Exception:
+        excinfo = _pytest._code.ExceptionInfo()
+        assert excinfo.errisinstance(KeyboardInterrupt)
+
+
+def test_pytest_fail():
+    try:
+        pytest.fail("hello")
+    except pytest.fail.Exception:
+        excinfo = _pytest._code.ExceptionInfo()
+        s = excinfo.exconly(tryshort=True)
+        assert s.startswith("Failed")
+
+
+def test_pytest_exit_msg(testdir):
+    testdir.makeconftest("""
+    import pytest
+
+    def pytest_configure(config):
+        pytest.exit('oh noes')
+    """)
+    result = testdir.runpytest()
+    result.stderr.fnmatch_lines([
+        "Exit: oh noes",
+    ])
+
+
+def test_pytest_fail_notrace(testdir):
+    testdir.makepyfile("""
+        import pytest
+        def test_hello():
+            pytest.fail("hello", pytrace=False)
+        def teardown_function(function):
+            pytest.fail("world", pytrace=False)
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "world",
+        "hello",
+    ])
+    assert 'def teardown_function' not in result.stdout.str()
+
+
+@pytest.mark.parametrize('str_prefix', ['u', ''])
+def test_pytest_fail_notrace_non_ascii(testdir, str_prefix):
+    """Fix pytest.fail with pytrace=False with non-ascii characters (#1178).
+
+    This tests with native and unicode strings containing non-ascii chars.
+    """
+    testdir.makepyfile(u"""
+        # coding: utf-8
+        import pytest
+
+        def test_hello():
+            pytest.fail(%s'oh oh: ☺', pytrace=False)
+    """ % str_prefix)
+    result = testdir.runpytest()
+    if sys.version_info[0] >= 3:
+        result.stdout.fnmatch_lines(['*test_hello*', "oh oh: ☺"])
+    else:
+        result.stdout.fnmatch_lines(['*test_hello*', "oh oh: *"])
+    assert 'def test_hello' not in result.stdout.str()
+
+
+def test_pytest_no_tests_collected_exit_status(testdir):
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines('*collected 0 items*')
+    assert result.ret == main.EXIT_NOTESTSCOLLECTED
+
+    testdir.makepyfile(test_foo="""
+        def test_foo():
+            assert 1
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines('*collected 1 item*')
+    result.stdout.fnmatch_lines('*1 passed*')
+    assert result.ret == main.EXIT_OK
+
+    result = testdir.runpytest('-k nonmatch')
+    result.stdout.fnmatch_lines('*collected 1 item*')
+    result.stdout.fnmatch_lines('*1 deselected*')
+    assert result.ret == main.EXIT_NOTESTSCOLLECTED
+
+
+def test_exception_printing_skip():
+    try:
+        pytest.skip("hello")
+    except pytest.skip.Exception:
+        excinfo = _pytest._code.ExceptionInfo()
+        s = excinfo.exconly(tryshort=True)
+        assert s.startswith("Skipped")
+
+
+def test_importorskip(monkeypatch):
+    importorskip = pytest.importorskip
+
+    def f():
+        importorskip("asdlkj")
+
+    try:
+        sys = importorskip("sys")  # noqa
+        assert sys == py.std.sys
+        # path = pytest.importorskip("os.path")
+        # assert path == py.std.os.path
+        excinfo = pytest.raises(pytest.skip.Exception, f)
+        path = py.path.local(excinfo.getrepr().reprcrash.path)
+        # check that importorskip reports the actual call
+        # in this test the test_runner.py file
+        assert path.purebasename == "test_runner"
+        pytest.raises(SyntaxError, "pytest.importorskip('x y z')")
+        pytest.raises(SyntaxError, "pytest.importorskip('x=y')")
+        mod = py.std.types.ModuleType("hello123")
+        mod.__version__ = "1.3"
+        monkeypatch.setitem(sys.modules, "hello123", mod)
+        pytest.raises(pytest.skip.Exception, """
+            pytest.importorskip("hello123", minversion="1.3.1")
+        """)
+        mod2 = pytest.importorskip("hello123", minversion="1.3")
+        assert mod2 == mod
+    except pytest.skip.Exception:
+        print(_pytest._code.ExceptionInfo())
+        pytest.fail("spurious skip")
+
+
+def test_importorskip_imports_last_module_part():
+    ospath = pytest.importorskip("os.path")
+    assert os.path == ospath
+
+
+def test_importorskip_dev_module(monkeypatch):
+    try:
+        mod = py.std.types.ModuleType("mockmodule")
+        mod.__version__ = '0.13.0.dev-43290'
+        monkeypatch.setitem(sys.modules, 'mockmodule', mod)
+        mod2 = pytest.importorskip('mockmodule', minversion='0.12.0')
+        assert mod2 == mod
+        pytest.raises(pytest.skip.Exception, """
+            pytest.importorskip('mockmodule1', minversion='0.14.0')""")
+    except pytest.skip.Exception:
+        print(_pytest._code.ExceptionInfo())
+        pytest.fail("spurious skip")
+
+
+def test_importorskip_module_level(testdir):
+    """importorskip must be able to skip entire modules when used at module level"""
+    testdir.makepyfile('''
+        import pytest
+        foobarbaz = pytest.importorskip("foobarbaz")
+
+        def test_foo():
+            pass
+    ''')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(['*collected 0 items / 1 skipped*'])
+
+
+def test_pytest_cmdline_main(testdir):
+    p = testdir.makepyfile("""
+        import pytest
+        def test_hello():
+            assert 1
+        if __name__ == '__main__':
+           pytest.cmdline.main([__file__])
+    """)
+    import subprocess
+    popen = subprocess.Popen([sys.executable, str(p)], stdout=subprocess.PIPE)
+    popen.communicate()
+    ret = popen.wait()
+    assert ret == 0
+
+
+def test_unicode_in_longrepr(testdir):
+    testdir.makeconftest("""
+        # -*- coding: utf-8 -*-
+        import pytest
+        @pytest.hookimpl(hookwrapper=True)
+        def pytest_runtest_makereport():
+            outcome = yield
+            rep = outcome.get_result()
+            if rep.when == "call":
+                rep.longrepr = u'ä'
+    """)
+    testdir.makepyfile("""
+        def test_out():
+            assert 0
+    """)
+    result = testdir.runpytest()
+    assert result.ret == 1
+    assert "UnicodeEncodeError" not in result.stderr.str()
+
+
+def test_failure_in_setup(testdir):
+    testdir.makepyfile("""
+        def setup_module():
+            0/0
+        def test_func():
+            pass
+    """)
+    result = testdir.runpytest("--tb=line")
+    assert "def setup_module" not in result.stdout.str()
+
+
+def test_makereport_getsource(testdir):
+    testdir.makepyfile("""
+        def test_foo():
+            if False: pass
+            else: assert False
+    """)
+    result = testdir.runpytest()
+    assert 'INTERNALERROR' not in result.stdout.str()
+    result.stdout.fnmatch_lines(['*else: assert False*'])
+
+
+def test_makereport_getsource_dynamic_code(testdir, monkeypatch):
+    """Test that exception in dynamically generated code doesn't break getting the source line."""
+    import inspect
+    original_findsource = inspect.findsource
+
+    def findsource(obj, *args, **kwargs):
+        # Can be triggered by dynamically created functions
+        if obj.__name__ == 'foo':
+            raise IndexError()
+        return original_findsource(obj, *args, **kwargs)
+
+    monkeypatch.setattr(inspect, 'findsource', findsource)
+
+    testdir.makepyfile("""
+        import pytest
+
+        @pytest.fixture
+        def foo(missing):
+            pass
+
+        def test_fix(foo):
+            assert False
+    """)
+    result = testdir.runpytest('-vv')
+    assert 'INTERNALERROR' not in result.stdout.str()
+    result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"])
+
+
+def test_store_except_info_on_eror():
+    """ Test that upon test failure, the exception info is stored on
+    sys.last_traceback and friends.
+    """
+    # Simulate item that raises a specific exception
+    class ItemThatRaises(object):
+        nodeid = 'item_that_raises'
+
+        def runtest(self):
+            raise IndexError('TEST')
+    try:
+        runner.pytest_runtest_call(ItemThatRaises())
+    except IndexError:
+        pass
+    # Check that exception info is stored on sys
+    assert sys.last_type is IndexError
+    assert sys.last_value.args[0] == 'TEST'
+    assert sys.last_traceback
+
+
+def test_current_test_env_var(testdir, monkeypatch):
+    pytest_current_test_vars = []
+    monkeypatch.setattr(sys, 'pytest_current_test_vars', pytest_current_test_vars, raising=False)
+    testdir.makepyfile('''
+        import pytest
+        import sys
+        import os
+
+        @pytest.fixture
+        def fix():
+            sys.pytest_current_test_vars.append(('setup', os.environ['PYTEST_CURRENT_TEST']))
+            yield
+            sys.pytest_current_test_vars.append(('teardown', os.environ['PYTEST_CURRENT_TEST']))
+
+        def test(fix):
+            sys.pytest_current_test_vars.append(('call', os.environ['PYTEST_CURRENT_TEST']))
+    ''')
+    result = testdir.runpytest_inprocess()
+    assert result.ret == 0
+    test_id = 'test_current_test_env_var.py::test'
+    assert pytest_current_test_vars == [
+        ('setup', test_id + ' (setup)'), ('call', test_id + ' (call)'), ('teardown', test_id + ' (teardown)')]
+    assert 'PYTEST_CURRENT_TEST' not in os.environ
+
+
+class TestReportContents(object):
+    """
+    Test user-level API of ``TestReport`` objects.
+    """
+
+    def getrunner(self):
+        return lambda item: runner.runtestprotocol(item, log=False)
+
+    def test_longreprtext_pass(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                pass
+        """)
+        rep = reports[1]
+        assert rep.longreprtext == ''
+
+    def test_longreprtext_failure(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                x = 1
+                assert x == 4
+        """)
+        rep = reports[1]
+        assert 'assert 1 == 4' in rep.longreprtext
+
+    def test_captured_text(self, testdir):
+        reports = testdir.runitem("""
+            import pytest
+            import sys
+
+            @pytest.fixture
+            def fix():
+                sys.stdout.write('setup: stdout\\n')
+                sys.stderr.write('setup: stderr\\n')
+                yield
+                sys.stdout.write('teardown: stdout\\n')
+                sys.stderr.write('teardown: stderr\\n')
+                assert 0
+
+            def test_func(fix):
+                sys.stdout.write('call: stdout\\n')
+                sys.stderr.write('call: stderr\\n')
+                assert 0
+        """)
+        setup, call, teardown = reports
+        assert setup.capstdout == 'setup: stdout\n'
+        assert call.capstdout == 'setup: stdout\ncall: stdout\n'
+        assert teardown.capstdout == 'setup: stdout\ncall: stdout\nteardown: stdout\n'
+
+        assert setup.capstderr == 'setup: stderr\n'
+        assert call.capstderr == 'setup: stderr\ncall: stderr\n'
+        assert teardown.capstderr == 'setup: stderr\ncall: stderr\nteardown: stderr\n'
+
+    def test_no_captured_text(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                pass
+        """)
+        rep = reports[1]
+        assert rep.capstdout == ''
+        assert rep.capstderr == ''
diff --git a/tools/third_party/pytest/testing/test_runner_xunit.py b/tools/third_party/pytest/testing/test_runner_xunit.py
new file mode 100644
index 0000000..fc931f8
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_runner_xunit.py
@@ -0,0 +1,319 @@
+"""
+ test correct setup/teardowns at
+ module, class, and instance level
+"""
+from __future__ import absolute_import, division, print_function
+import pytest
+
+
+def test_module_and_function_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        modlevel = []
+        def setup_module(module):
+            assert not modlevel
+            module.modlevel.append(42)
+
+        def teardown_module(module):
+            modlevel.pop()
+
+        def setup_function(function):
+            function.answer = 17
+
+        def teardown_function(function):
+            del function.answer
+
+        def test_modlevel():
+            assert modlevel[0] == 42
+            assert test_modlevel.answer == 17
+
+        class TestFromClass(object):
+            def test_module(self):
+                assert modlevel[0] == 42
+                assert not hasattr(test_modlevel, 'answer')
+    """)
+    rep = reprec.matchreport("test_modlevel")
+    assert rep.passed
+    rep = reprec.matchreport("test_module")
+    assert rep.passed
+
+
+def test_module_setup_failure_no_teardown(testdir):
+    reprec = testdir.inline_runsource("""
+        values = []
+        def setup_module(module):
+            values.append(1)
+            0/0
+
+        def test_nothing():
+            pass
+
+        def teardown_module(module):
+            values.append(2)
+    """)
+    reprec.assertoutcome(failed=1)
+    calls = reprec.getcalls("pytest_runtest_setup")
+    assert calls[0].item.module.values == [1]
+
+
+def test_setup_function_failure_no_teardown(testdir):
+    reprec = testdir.inline_runsource("""
+        modlevel = []
+        def setup_function(function):
+            modlevel.append(1)
+            0/0
+
+        def teardown_function(module):
+            modlevel.append(2)
+
+        def test_func():
+            pass
+    """)
+    calls = reprec.getcalls("pytest_runtest_setup")
+    assert calls[0].item.module.modlevel == [1]
+
+
+def test_class_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSimpleClassSetup(object):
+            clslevel = []
+            def setup_class(cls):
+                cls.clslevel.append(23)
+
+            def teardown_class(cls):
+                cls.clslevel.pop()
+
+            def test_classlevel(self):
+                assert self.clslevel[0] == 23
+
+        class TestInheritedClassSetupStillWorks(TestSimpleClassSetup):
+            def test_classlevel_anothertime(self):
+                assert self.clslevel == [23]
+
+        def test_cleanup():
+            assert not TestSimpleClassSetup.clslevel
+            assert not TestInheritedClassSetupStillWorks.clslevel
+    """)
+    reprec.assertoutcome(passed=1 + 2 + 1)
+
+
+def test_class_setup_failure_no_teardown(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSimpleClassSetup(object):
+            clslevel = []
+            def setup_class(cls):
+                0/0
+
+            def teardown_class(cls):
+                cls.clslevel.append(1)
+
+            def test_classlevel(self):
+                pass
+
+        def test_cleanup():
+            assert not TestSimpleClassSetup.clslevel
+    """)
+    reprec.assertoutcome(failed=1, passed=1)
+
+
+def test_method_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSetupMethod(object):
+            def setup_method(self, meth):
+                self.methsetup = meth
+            def teardown_method(self, meth):
+                del self.methsetup
+
+            def test_some(self):
+                assert self.methsetup == self.test_some
+
+            def test_other(self):
+                assert self.methsetup == self.test_other
+    """)
+    reprec.assertoutcome(passed=2)
+
+
+def test_method_setup_failure_no_teardown(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestMethodSetup(object):
+            clslevel = []
+            def setup_method(self, method):
+                self.clslevel.append(1)
+                0/0
+
+            def teardown_method(self, method):
+                self.clslevel.append(2)
+
+            def test_method(self):
+                pass
+
+        def test_cleanup():
+            assert TestMethodSetup.clslevel == [1]
+    """)
+    reprec.assertoutcome(failed=1, passed=1)
+
+
+def test_method_generator_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSetupTeardownOnInstance(object):
+            def setup_class(cls):
+                cls.classsetup = True
+
+            def setup_method(self, method):
+                self.methsetup = method
+
+            def test_generate(self):
+                assert self.classsetup
+                assert self.methsetup == self.test_generate
+                yield self.generated, 5
+                yield self.generated, 2
+
+            def generated(self, value):
+                assert self.classsetup
+                assert self.methsetup == self.test_generate
+                assert value == 5
+    """)
+    reprec.assertoutcome(passed=1, failed=1)
+
+
+def test_func_generator_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        import sys
+
+        def setup_module(mod):
+            print ("setup_module")
+            mod.x = []
+
+        def setup_function(fun):
+            print ("setup_function")
+            x.append(1)
+
+        def teardown_function(fun):
+            print ("teardown_function")
+            x.pop()
+
+        def test_one():
+            assert x == [1]
+            def check():
+                print ("check")
+                sys.stderr.write("e\\n")
+                assert x == [1]
+            yield check
+            assert x == [1]
+    """)
+    rep = reprec.matchreport("test_one", names="pytest_runtest_logreport")
+    assert rep.passed
+
+
+def test_method_setup_uses_fresh_instances(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSelfState1(object):
+            memory = []
+            def test_hello(self):
+                self.memory.append(self)
+
+            def test_afterhello(self):
+                assert self != self.memory[0]
+    """)
+    reprec.assertoutcome(passed=2, failed=0)
+
+
+def test_setup_that_skips_calledagain(testdir):
+    p = testdir.makepyfile("""
+        import pytest
+        def setup_module(mod):
+            pytest.skip("x")
+        def test_function1():
+            pass
+        def test_function2():
+            pass
+    """)
+    reprec = testdir.inline_run(p)
+    reprec.assertoutcome(skipped=2)
+
+
+def test_setup_fails_again_on_all_tests(testdir):
+    p = testdir.makepyfile("""
+        import pytest
+        def setup_module(mod):
+            raise ValueError(42)
+        def test_function1():
+            pass
+        def test_function2():
+            pass
+    """)
+    reprec = testdir.inline_run(p)
+    reprec.assertoutcome(failed=2)
+
+
+def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
+    p = testdir.makepyfile("""
+        import pytest
+        def setup_module(mod):
+            raise ValueError(42)
+        @pytest.fixture
+        def hello(request):
+            raise ValueError("xyz43")
+        def test_function1(hello):
+            pass
+        def test_function2(hello):
+            pass
+    """)
+    result = testdir.runpytest(p)
+    result.stdout.fnmatch_lines([
+        "*function1*",
+        "*ValueError*42*",
+        "*function2*",
+        "*ValueError*42*",
+        "*2 error*"
+    ])
+    assert "xyz43" not in result.stdout.str()
+
+
+@pytest.mark.parametrize('arg', ['', 'arg'])
+def test_setup_teardown_function_level_with_optional_argument(testdir, monkeypatch, arg):
+    """parameter to setup/teardown xunit-style functions parameter is now optional (#1728)."""
+    import sys
+    trace_setups_teardowns = []
+    monkeypatch.setattr(sys, 'trace_setups_teardowns', trace_setups_teardowns, raising=False)
+    p = testdir.makepyfile("""
+        import pytest
+        import sys
+
+        trace = sys.trace_setups_teardowns.append
+
+        def setup_module({arg}): trace('setup_module')
+        def teardown_module({arg}): trace('teardown_module')
+
+        def setup_function({arg}): trace('setup_function')
+        def teardown_function({arg}): trace('teardown_function')
+
+        def test_function_1(): pass
+        def test_function_2(): pass
+
+        class Test(object):
+            def setup_method(self, {arg}): trace('setup_method')
+            def teardown_method(self, {arg}): trace('teardown_method')
+
+            def test_method_1(self): pass
+            def test_method_2(self): pass
+    """.format(arg=arg))
+    result = testdir.inline_run(p)
+    result.assertoutcome(passed=4)
+
+    expected = [
+        'setup_module',
+
+        'setup_function',
+        'teardown_function',
+        'setup_function',
+        'teardown_function',
+
+        'setup_method',
+        'teardown_method',
+
+        'setup_method',
+        'teardown_method',
+
+        'teardown_module',
+    ]
+    assert trace_setups_teardowns == expected
diff --git a/tools/third_party/pytest/testing/test_session.py b/tools/third_party/pytest/testing/test_session.py
new file mode 100644
index 0000000..9ec13f5
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_session.py
@@ -0,0 +1,255 @@
+from __future__ import absolute_import, division, print_function
+import pytest
+
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+
+
+class SessionTests(object):
+    def test_basic_testitem_events(self, testdir):
+        tfile = testdir.makepyfile("""
+            def test_one():
+                pass
+            def test_one_one():
+                assert 0
+            def test_other():
+                raise ValueError(23)
+            class TestClass(object):
+                def test_two(self, someargs):
+                    pass
+        """)
+        reprec = testdir.inline_run(tfile)
+        passed, skipped, failed = reprec.listoutcomes()
+        assert len(skipped) == 0
+        assert len(passed) == 1
+        assert len(failed) == 3
+
+        def end(x):
+            return x.nodeid.split("::")[-1]
+
+        assert end(failed[0]) == "test_one_one"
+        assert end(failed[1]) == "test_other"
+        itemstarted = reprec.getcalls("pytest_itemcollected")
+        assert len(itemstarted) == 4
+        # XXX check for failing funcarg setup
+        # colreports = reprec.getcalls("pytest_collectreport")
+        # assert len(colreports) == 4
+        # assert colreports[1].report.failed
+
+    def test_nested_import_error(self, testdir):
+        tfile = testdir.makepyfile("""
+            import import_fails
+            def test_this():
+                assert import_fails.a == 1
+        """, import_fails="""
+            import does_not_work
+            a = 1
+        """)
+        reprec = testdir.inline_run(tfile)
+        values = reprec.getfailedcollections()
+        assert len(values) == 1
+        out = str(values[0].longrepr)
+        assert out.find('does_not_work') != -1
+
+    def test_raises_output(self, testdir):
+        reprec = testdir.inline_runsource("""
+            import pytest
+            def test_raises_doesnt():
+                pytest.raises(ValueError, int, "3")
+        """)
+        passed, skipped, failed = reprec.listoutcomes()
+        assert len(failed) == 1
+        out = failed[0].longrepr.reprcrash.message
+        if not out.find("DID NOT RAISE") != -1:
+            print(out)
+            pytest.fail("incorrect raises() output")
+
+    def test_generator_yields_None(self, testdir):
+        reprec = testdir.inline_runsource("""
+            def test_1():
+                yield None
+        """)
+        failures = reprec.getfailedcollections()
+        out = failures[0].longrepr.reprcrash.message
+        i = out.find('TypeError')
+        assert i != -1
+
+    def test_syntax_error_module(self, testdir):
+        reprec = testdir.inline_runsource("this is really not python")
+        values = reprec.getfailedcollections()
+        assert len(values) == 1
+        out = str(values[0].longrepr)
+        assert out.find(str('not python')) != -1
+
+    def test_exit_first_problem(self, testdir):
+        reprec = testdir.inline_runsource("""
+            def test_one(): assert 0
+            def test_two(): assert 0
+        """, '--exitfirst')
+        passed, skipped, failed = reprec.countoutcomes()
+        assert failed == 1
+        assert passed == skipped == 0
+
+    def test_maxfail(self, testdir):
+        reprec = testdir.inline_runsource("""
+            def test_one(): assert 0
+            def test_two(): assert 0
+            def test_three(): assert 0
+        """, '--maxfail=2')
+        passed, skipped, failed = reprec.countoutcomes()
+        assert failed == 2
+        assert passed == skipped == 0
+
+    def test_broken_repr(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            class BrokenRepr1(object):
+                foo=0
+                def __repr__(self):
+                    raise Exception("Ha Ha fooled you, I'm a broken repr().")
+
+            class TestBrokenClass(object):
+                def test_explicit_bad_repr(self):
+                    t = BrokenRepr1()
+                    pytest.raises(Exception, 'repr(t)')
+
+                def test_implicit_bad_repr1(self):
+                    t = BrokenRepr1()
+                    assert t.foo == 1
+
+        """)
+        reprec = testdir.inline_run(p)
+        passed, skipped, failed = reprec.listoutcomes()
+        assert len(failed) == 1
+        out = failed[0].longrepr.reprcrash.message
+        assert out.find("""[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]""") != -1  # '
+
+    def test_skip_file_by_conftest(self, testdir):
+        testdir.makepyfile(conftest="""
+            import pytest
+            def pytest_collect_file():
+                pytest.skip("intentional")
+        """, test_file="""
+            def test_one(): pass
+        """)
+        try:
+            reprec = testdir.inline_run(testdir.tmpdir)
+        except pytest.skip.Exception:
+            pytest.fail("wrong skipped caught")
+        reports = reprec.getreports("pytest_collectreport")
+        assert len(reports) == 1
+        assert reports[0].skipped
+
+
+class TestNewSession(SessionTests):
+
+    def test_order_of_execution(self, testdir):
+        reprec = testdir.inline_runsource("""
+            values = []
+            def test_1():
+                values.append(1)
+            def test_2():
+                values.append(2)
+            def test_3():
+                assert values == [1,2]
+            class Testmygroup(object):
+                reslist = values
+                def test_1(self):
+                    self.reslist.append(1)
+                def test_2(self):
+                    self.reslist.append(2)
+                def test_3(self):
+                    self.reslist.append(3)
+                def test_4(self):
+                    assert self.reslist == [1,2,1,2,3]
+        """)
+        passed, skipped, failed = reprec.countoutcomes()
+        assert failed == skipped == 0
+        assert passed == 7
+        # also test listnames() here ...
+
+    def test_collect_only_with_various_situations(self, testdir):
+        p = testdir.makepyfile(
+            test_one="""
+                def test_one():
+                    raise ValueError()
+
+                class TestX(object):
+                    def test_method_one(self):
+                        pass
+
+                class TestY(TestX):
+                    pass
+            """,
+            test_three="xxxdsadsadsadsa",
+            __init__=""
+        )
+        reprec = testdir.inline_run('--collect-only', p.dirpath())
+
+        itemstarted = reprec.getcalls("pytest_itemcollected")
+        assert len(itemstarted) == 3
+        assert not reprec.getreports("pytest_runtest_logreport")
+        started = reprec.getcalls("pytest_collectstart")
+        finished = reprec.getreports("pytest_collectreport")
+        assert len(started) == len(finished)
+        assert len(started) == 7  # XXX extra TopCollector
+        colfail = [x for x in finished if x.failed]
+        assert len(colfail) == 1
+
+    def test_minus_x_import_error(self, testdir):
+        testdir.makepyfile(__init__="")
+        testdir.makepyfile(test_one="xxxx", test_two="yyyy")
+        reprec = testdir.inline_run("-x", testdir.tmpdir)
+        finished = reprec.getreports("pytest_collectreport")
+        colfail = [x for x in finished if x.failed]
+        assert len(colfail) == 1
+
+    def test_minus_x_overridden_by_maxfail(self, testdir):
+        testdir.makepyfile(__init__="")
+        testdir.makepyfile(test_one="xxxx", test_two="yyyy", test_third="zzz")
+        reprec = testdir.inline_run("-x", "--maxfail=2", testdir.tmpdir)
+        finished = reprec.getreports("pytest_collectreport")
+        colfail = [x for x in finished if x.failed]
+        assert len(colfail) == 2
+
+
+def test_plugin_specify(testdir):
+    pytest.raises(ImportError, """
+            testdir.parseconfig("-p", "nqweotexistent")
+    """)
+    # pytest.raises(ImportError,
+    #    "config.do_configure(config)"
+    # )
+
+
+def test_plugin_already_exists(testdir):
+    config = testdir.parseconfig("-p", "terminal")
+    assert config.option.plugins == ['terminal']
+    config._do_configure()
+    config._ensure_unconfigure()
+
+
+def test_exclude(testdir):
+    hellodir = testdir.mkdir("hello")
+    hellodir.join("test_hello.py").write("x y syntaxerror")
+    hello2dir = testdir.mkdir("hello2")
+    hello2dir.join("test_hello2.py").write("x y syntaxerror")
+    testdir.makepyfile(test_ok="def test_pass(): pass")
+    result = testdir.runpytest("--ignore=hello", "--ignore=hello2")
+    assert result.ret == 0
+    result.stdout.fnmatch_lines(["*1 passed*"])
+
+
+def test_sessionfinish_with_start(testdir):
+    testdir.makeconftest("""
+        import os
+        values = []
+        def pytest_sessionstart():
+            values.append(os.getcwd())
+            os.chdir("..")
+
+        def pytest_sessionfinish():
+            assert values[0] == os.getcwd()
+
+    """)
+    res = testdir.runpytest("--collect-only")
+    assert res.ret == EXIT_NOTESTSCOLLECTED
diff --git a/tools/third_party/pytest/testing/test_skipping.py b/tools/third_party/pytest/testing/test_skipping.py
new file mode 100644
index 0000000..9789448
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_skipping.py
@@ -0,0 +1,1067 @@
+from __future__ import absolute_import, division, print_function
+import pytest
+import sys
+
+from _pytest.skipping import MarkEvaluator, folded_skips, pytest_runtest_setup
+from _pytest.runner import runtestprotocol
+
+
+class TestEvaluator(object):
+    def test_no_marker(self, testdir):
+        item = testdir.getitem("def test_func(): pass")
+        evalskipif = MarkEvaluator(item, 'skipif')
+        assert not evalskipif
+        assert not evalskipif.istrue()
+
+    def test_marked_no_args(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xyz
+            def test_func():
+                pass
+        """)
+        ev = MarkEvaluator(item, 'xyz')
+        assert ev
+        assert ev.istrue()
+        expl = ev.getexplanation()
+        assert expl == ""
+        assert not ev.get("run", False)
+
+    def test_marked_one_arg(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xyz("hasattr(os, 'sep')")
+            def test_func():
+                pass
+        """)
+        ev = MarkEvaluator(item, 'xyz')
+        assert ev
+        assert ev.istrue()
+        expl = ev.getexplanation()
+        assert expl == "condition: hasattr(os, 'sep')"
+
+    @pytest.mark.skipif('sys.version_info[0] >= 3')
+    def test_marked_one_arg_unicode(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xyz(u"hasattr(os, 'sep')")
+            def test_func():
+                pass
+        """)
+        ev = MarkEvaluator(item, 'xyz')
+        assert ev
+        assert ev.istrue()
+        expl = ev.getexplanation()
+        assert expl == "condition: hasattr(os, 'sep')"
+
+    def test_marked_one_arg_with_reason(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xyz("hasattr(os, 'sep')", attr=2, reason="hello world")
+            def test_func():
+                pass
+        """)
+        ev = MarkEvaluator(item, 'xyz')
+        assert ev
+        assert ev.istrue()
+        expl = ev.getexplanation()
+        assert expl == "hello world"
+        assert ev.get("attr") == 2
+
+    def test_marked_one_arg_twice(self, testdir):
+        lines = [
+            '''@pytest.mark.skipif("not hasattr(os, 'murks')")''',
+            '''@pytest.mark.skipif("hasattr(os, 'murks')")'''
+        ]
+        for i in range(0, 2):
+            item = testdir.getitem("""
+                import pytest
+                %s
+                %s
+                def test_func():
+                    pass
+            """ % (lines[i], lines[(i + 1) % 2]))
+            ev = MarkEvaluator(item, 'skipif')
+            assert ev
+            assert ev.istrue()
+            expl = ev.getexplanation()
+            assert expl == "condition: not hasattr(os, 'murks')"
+
+    def test_marked_one_arg_twice2(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.skipif("hasattr(os, 'murks')")
+            @pytest.mark.skipif("not hasattr(os, 'murks')")
+            def test_func():
+                pass
+        """)
+        ev = MarkEvaluator(item, 'skipif')
+        assert ev
+        assert ev.istrue()
+        expl = ev.getexplanation()
+        assert expl == "condition: not hasattr(os, 'murks')"
+
+    def test_marked_skip_with_not_string(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.skipif(False)
+            def test_func():
+                pass
+        """)
+        ev = MarkEvaluator(item, 'skipif')
+        exc = pytest.raises(pytest.fail.Exception, ev.istrue)
+        assert """Failed: you need to specify reason=STRING when using booleans as conditions.""" in exc.value.msg
+
+    def test_skipif_class(self, testdir):
+        item, = testdir.getitems("""
+            import pytest
+            class TestClass(object):
+                pytestmark = pytest.mark.skipif("config._hackxyz")
+                def test_func(self):
+                    pass
+        """)
+        item.config._hackxyz = 3
+        ev = MarkEvaluator(item, 'skipif')
+        assert ev.istrue()
+        expl = ev.getexplanation()
+        assert expl == "condition: config._hackxyz"
+
+
+class TestXFail(object):
+
+    @pytest.mark.parametrize('strict', [True, False])
+    def test_xfail_simple(self, testdir, strict):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xfail(strict=%s)
+            def test_func():
+                assert 0
+        """ % strict)
+        reports = runtestprotocol(item, log=False)
+        assert len(reports) == 3
+        callreport = reports[1]
+        assert callreport.skipped
+        assert callreport.wasxfail == ""
+
+    def test_xfail_xpassed(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xfail(reason="this is an xfail")
+            def test_func():
+                assert 1
+        """)
+        reports = runtestprotocol(item, log=False)
+        assert len(reports) == 3
+        callreport = reports[1]
+        assert callreport.passed
+        assert callreport.wasxfail == "this is an xfail"
+
+    def test_xfail_xpassed_strict(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xfail(strict=True, reason="nope")
+            def test_func():
+                assert 1
+        """)
+        reports = runtestprotocol(item, log=False)
+        assert len(reports) == 3
+        callreport = reports[1]
+        assert callreport.failed
+        assert callreport.longrepr == "[XPASS(strict)] nope"
+        assert not hasattr(callreport, "wasxfail")
+
+    def test_xfail_run_anyway(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail
+            def test_func():
+                assert 0
+            def test_func2():
+                pytest.xfail("hello")
+        """)
+        result = testdir.runpytest("--runxfail")
+        result.stdout.fnmatch_lines([
+            "*def test_func():*",
+            "*assert 0*",
+            "*1 failed*1 pass*",
+        ])
+
+    def test_xfail_evalfalse_but_fails(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.xfail('False')
+            def test_func():
+                assert 0
+        """)
+        reports = runtestprotocol(item, log=False)
+        callreport = reports[1]
+        assert callreport.failed
+        assert not hasattr(callreport, "wasxfail")
+        assert 'xfail' in callreport.keywords
+
+    def test_xfail_not_report_default(self, testdir):
+        p = testdir.makepyfile(test_one="""
+            import pytest
+            @pytest.mark.xfail
+            def test_this():
+                assert 0
+        """)
+        testdir.runpytest(p, '-v')
+        # result.stdout.fnmatch_lines([
+        #    "*HINT*use*-r*"
+        # ])
+
+    def test_xfail_not_run_xfail_reporting(self, testdir):
+        p = testdir.makepyfile(test_one="""
+            import pytest
+            @pytest.mark.xfail(run=False, reason="noway")
+            def test_this():
+                assert 0
+            @pytest.mark.xfail("True", run=False)
+            def test_this_true():
+                assert 0
+            @pytest.mark.xfail("False", run=False, reason="huh")
+            def test_this_false():
+                assert 1
+        """)
+        result = testdir.runpytest(p, '-rx', )
+        result.stdout.fnmatch_lines([
+            "*test_one*test_this*",
+            "*NOTRUN*noway",
+            "*test_one*test_this_true*",
+            "*NOTRUN*condition:*True*",
+            "*1 passed*",
+        ])
+
+    def test_xfail_not_run_no_setup_run(self, testdir):
+        p = testdir.makepyfile(test_one="""
+            import pytest
+            @pytest.mark.xfail(run=False, reason="hello")
+            def test_this():
+                assert 0
+            def setup_module(mod):
+                raise ValueError(42)
+        """)
+        result = testdir.runpytest(p, '-rx', )
+        result.stdout.fnmatch_lines([
+            "*test_one*test_this*",
+            "*NOTRUN*hello",
+            "*1 xfailed*",
+        ])
+
+    def test_xfail_xpass(self, testdir):
+        p = testdir.makepyfile(test_one="""
+            import pytest
+            @pytest.mark.xfail
+            def test_that():
+                assert 1
+        """)
+        result = testdir.runpytest(p, '-rX')
+        result.stdout.fnmatch_lines([
+            "*XPASS*test_that*",
+            "*1 xpassed*"
+        ])
+        assert result.ret == 0
+
+    def test_xfail_imperative(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            def test_this():
+                pytest.xfail("hello")
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*1 xfailed*",
+        ])
+        result = testdir.runpytest(p, "-rx")
+        result.stdout.fnmatch_lines([
+            "*XFAIL*test_this*",
+            "*reason:*hello*",
+        ])
+        result = testdir.runpytest(p, "--runxfail")
+        result.stdout.fnmatch_lines("*1 pass*")
+
+    def test_xfail_imperative_in_setup_function(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            def setup_function(function):
+                pytest.xfail("hello")
+
+            def test_this():
+                assert 0
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*1 xfailed*",
+        ])
+        result = testdir.runpytest(p, "-rx")
+        result.stdout.fnmatch_lines([
+            "*XFAIL*test_this*",
+            "*reason:*hello*",
+        ])
+        result = testdir.runpytest(p, "--runxfail")
+        result.stdout.fnmatch_lines("""
+            *def test_this*
+            *1 fail*
+        """)
+
+    def xtest_dynamic_xfail_set_during_setup(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            def setup_function(function):
+                pytest.mark.xfail(function)
+            def test_this():
+                assert 0
+            def test_that():
+                assert 1
+        """)
+        result = testdir.runpytest(p, '-rxX')
+        result.stdout.fnmatch_lines([
+            "*XFAIL*test_this*",
+            "*XPASS*test_that*",
+        ])
+
+    def test_dynamic_xfail_no_run(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.fixture
+            def arg(request):
+                request.applymarker(pytest.mark.xfail(run=False))
+            def test_this(arg):
+                assert 0
+        """)
+        result = testdir.runpytest(p, '-rxX')
+        result.stdout.fnmatch_lines([
+            "*XFAIL*test_this*",
+            "*NOTRUN*",
+        ])
+
+    def test_dynamic_xfail_set_during_funcarg_setup(self, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.fixture
+            def arg(request):
+                request.applymarker(pytest.mark.xfail)
+            def test_this2(arg):
+                assert 0
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*1 xfailed*",
+        ])
+
+    @pytest.mark.parametrize('expected, actual, matchline',
+                             [('TypeError', 'TypeError', "*1 xfailed*"),
+                              ('(AttributeError, TypeError)', 'TypeError', "*1 xfailed*"),
+                              ('TypeError', 'IndexError', "*1 failed*"),
+                              ('(AttributeError, TypeError)', 'IndexError', "*1 failed*"),
+                              ])
+    def test_xfail_raises(self, expected, actual, matchline, testdir):
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail(raises=%s)
+            def test_raises():
+                raise %s()
+        """ % (expected, actual))
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            matchline,
+        ])
+
+    def test_strict_sanity(self, testdir):
+        """sanity check for xfail(strict=True): a failing test should behave
+        exactly like a normal xfail.
+        """
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail(reason='unsupported feature', strict=True)
+            def test_foo():
+                assert 0
+        """)
+        result = testdir.runpytest(p, '-rxX')
+        result.stdout.fnmatch_lines([
+            '*XFAIL*',
+            '*unsupported feature*',
+        ])
+        assert result.ret == 0
+
+    @pytest.mark.parametrize('strict', [True, False])
+    def test_strict_xfail(self, testdir, strict):
+        p = testdir.makepyfile("""
+            import pytest
+
+            @pytest.mark.xfail(reason='unsupported feature', strict=%s)
+            def test_foo():
+                with open('foo_executed', 'w'): pass  # make sure test executes
+        """ % strict)
+        result = testdir.runpytest(p, '-rxX')
+        if strict:
+            result.stdout.fnmatch_lines([
+                '*test_foo*',
+                '*XPASS(strict)*unsupported feature*',
+            ])
+        else:
+            result.stdout.fnmatch_lines([
+                '*test_strict_xfail*',
+                'XPASS test_strict_xfail.py::test_foo unsupported feature',
+            ])
+        assert result.ret == (1 if strict else 0)
+        assert testdir.tmpdir.join('foo_executed').isfile()
+
+    @pytest.mark.parametrize('strict', [True, False])
+    def test_strict_xfail_condition(self, testdir, strict):
+        p = testdir.makepyfile("""
+            import pytest
+
+            @pytest.mark.xfail(False, reason='unsupported feature', strict=%s)
+            def test_foo():
+                pass
+        """ % strict)
+        result = testdir.runpytest(p, '-rxX')
+        result.stdout.fnmatch_lines('*1 passed*')
+        assert result.ret == 0
+
+    @pytest.mark.parametrize('strict', [True, False])
+    def test_xfail_condition_keyword(self, testdir, strict):
+        p = testdir.makepyfile("""
+            import pytest
+
+            @pytest.mark.xfail(condition=False, reason='unsupported feature', strict=%s)
+            def test_foo():
+                pass
+        """ % strict)
+        result = testdir.runpytest(p, '-rxX')
+        result.stdout.fnmatch_lines('*1 passed*')
+        assert result.ret == 0
+
+    @pytest.mark.parametrize('strict_val', ['true', 'false'])
+    def test_strict_xfail_default_from_file(self, testdir, strict_val):
+        testdir.makeini('''
+            [pytest]
+            xfail_strict = %s
+        ''' % strict_val)
+        p = testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail(reason='unsupported feature')
+            def test_foo():
+                pass
+        """)
+        result = testdir.runpytest(p, '-rxX')
+        strict = strict_val == 'true'
+        result.stdout.fnmatch_lines('*1 failed*' if strict else '*1 xpassed*')
+        assert result.ret == (1 if strict else 0)
+
+
+class TestXFailwithSetupTeardown(object):
+    def test_failing_setup_issue9(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def setup_function(func):
+                assert 0
+
+            @pytest.mark.xfail
+            def test_func():
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*1 xfail*",
+        ])
+
+    def test_failing_teardown_issue9(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def teardown_function(func):
+                assert 0
+
+            @pytest.mark.xfail
+            def test_func():
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*1 xfail*",
+        ])
+
+
+class TestSkip(object):
+    def test_skip_class(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip
+            class TestSomething(object):
+                def test_foo(self):
+                    pass
+                def test_bar(self):
+                    pass
+
+            def test_baz():
+                pass
+        """)
+        rec = testdir.inline_run()
+        rec.assertoutcome(skipped=2, passed=1)
+
+    def test_skips_on_false_string(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip('False')
+            def test_foo():
+                pass
+        """)
+        rec = testdir.inline_run()
+        rec.assertoutcome(skipped=1)
+
+    def test_arg_as_reason(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip('testing stuff')
+            def test_bar():
+                pass
+        """)
+        result = testdir.runpytest('-rs')
+        result.stdout.fnmatch_lines([
+            "*testing stuff*",
+            "*1 skipped*",
+        ])
+
+    def test_skip_no_reason(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip
+            def test_foo():
+                pass
+        """)
+        result = testdir.runpytest('-rs')
+        result.stdout.fnmatch_lines([
+            "*unconditional skip*",
+            "*1 skipped*",
+        ])
+
+    def test_skip_with_reason(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip(reason="for lolz")
+            def test_bar():
+                pass
+        """)
+        result = testdir.runpytest('-rs')
+        result.stdout.fnmatch_lines([
+            "*for lolz*",
+            "*1 skipped*",
+        ])
+
+    def test_only_skips_marked_test(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip
+            def test_foo():
+                pass
+            @pytest.mark.skip(reason="nothing in particular")
+            def test_bar():
+                pass
+            def test_baz():
+                assert True
+        """)
+        result = testdir.runpytest('-rs')
+        result.stdout.fnmatch_lines([
+            "*nothing in particular*",
+            "*1 passed*2 skipped*",
+        ])
+
+    def test_strict_and_skip(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skip
+            def test_hello():
+                pass
+        """)
+        result = testdir.runpytest("-rs")
+        result.stdout.fnmatch_lines([
+            "*unconditional skip*",
+            "*1 skipped*",
+        ])
+
+
+class TestSkipif(object):
+    def test_skipif_conditional(self, testdir):
+        item = testdir.getitem("""
+            import pytest
+            @pytest.mark.skipif("hasattr(os, 'sep')")
+            def test_func():
+                pass
+        """)  # noqa
+        x = pytest.raises(pytest.skip.Exception, lambda:
+                          pytest_runtest_setup(item))
+        assert x.value.msg == "condition: hasattr(os, 'sep')"
+
+    @pytest.mark.parametrize('params', [
+        '"hasattr(sys, \'platform\')"',
+        'True, reason="invalid platform"',
+    ])
+    def test_skipif_reporting(self, testdir, params):
+        p = testdir.makepyfile(test_foo="""
+            import pytest
+            @pytest.mark.skipif(%(params)s)
+            def test_that():
+                assert 0
+        """ % dict(params=params))
+        result = testdir.runpytest(p, '-s', '-rs')
+        result.stdout.fnmatch_lines([
+            "*SKIP*1*test_foo.py*platform*",
+            "*1 skipped*"
+        ])
+        assert result.ret == 0
+
+    @pytest.mark.parametrize('marker, msg1, msg2', [
+        ('skipif', 'SKIP', 'skipped'),
+        ('xfail', 'XPASS', 'xpassed'),
+    ])
+    def test_skipif_reporting_multiple(self, testdir, marker, msg1, msg2):
+        testdir.makepyfile(test_foo="""
+            import pytest
+            @pytest.mark.{marker}(False, reason='first_condition')
+            @pytest.mark.{marker}(True, reason='second_condition')
+            def test_foobar():
+                assert 1
+        """.format(marker=marker))
+        result = testdir.runpytest('-s', '-rsxX')
+        result.stdout.fnmatch_lines([
+            "*{msg1}*test_foo.py*second_condition*".format(msg1=msg1),
+            "*1 {msg2}*".format(msg2=msg2),
+        ])
+        assert result.ret == 0
+
+
+def test_skip_not_report_default(testdir):
+    p = testdir.makepyfile(test_one="""
+        import pytest
+        def test_this():
+            pytest.skip("hello")
+    """)
+    result = testdir.runpytest(p, '-v')
+    result.stdout.fnmatch_lines([
+        # "*HINT*use*-r*",
+        "*1 skipped*",
+    ])
+
+
+def test_skipif_class(testdir):
+    p = testdir.makepyfile("""
+        import pytest
+
+        class TestClass(object):
+            pytestmark = pytest.mark.skipif("True")
+            def test_that(self):
+                assert 0
+            def test_though(self):
+                assert 0
+    """)
+    result = testdir.runpytest(p)
+    result.stdout.fnmatch_lines([
+        "*2 skipped*"
+    ])
+
+
+def test_skip_reasons_folding():
+    path = "xyz"
+    lineno = 3
+    message = "justso"
+    longrepr = (path, lineno, message)
+
+    class X(object):
+        pass
+    ev1 = X()
+    ev1.when = "execute"
+    ev1.skipped = True
+    ev1.longrepr = longrepr
+
+    ev2 = X()
+    ev2.when = "execute"
+    ev2.longrepr = longrepr
+    ev2.skipped = True
+
+    # ev3 might be a collection report
+    ev3 = X()
+    ev3.longrepr = longrepr
+    ev3.skipped = True
+
+    values = folded_skips([ev1, ev2, ev3])
+    assert len(values) == 1
+    num, fspath, lineno, reason = values[0]
+    assert num == 3
+    assert fspath == path
+    assert lineno == lineno
+    assert reason == message
+
+
+def test_skipped_reasons_functional(testdir):
+    testdir.makepyfile(
+        test_one="""
+            from conftest import doskip
+            def setup_function(func):
+                doskip()
+            def test_func():
+                pass
+            class TestClass(object):
+                def test_method(self):
+                    doskip()
+       """,
+        conftest="""
+            import pytest
+            def doskip():
+                pytest.skip('test')
+        """
+    )
+    result = testdir.runpytest('-rs')
+    result.stdout.fnmatch_lines([
+        "*SKIP*2*conftest.py:4: test",
+    ])
+    assert result.ret == 0
+
+
+def test_skipped_folding(testdir):
+    testdir.makepyfile(
+        test_one="""
+            import pytest
+            pytestmark = pytest.mark.skip("Folding")
+            def setup_function(func):
+                pass
+            def test_func():
+                pass
+            class TestClass(object):
+                def test_method(self):
+                    pass
+       """,
+    )
+    result = testdir.runpytest('-rs')
+    result.stdout.fnmatch_lines([
+        "*SKIP*2*test_one.py: Folding"
+    ])
+    assert result.ret == 0
+
+
+def test_reportchars(testdir):
+    testdir.makepyfile("""
+        import pytest
+        def test_1():
+            assert 0
+        @pytest.mark.xfail
+        def test_2():
+            assert 0
+        @pytest.mark.xfail
+        def test_3():
+            pass
+        def test_4():
+            pytest.skip("four")
+    """)
+    result = testdir.runpytest("-rfxXs")
+    result.stdout.fnmatch_lines([
+        "FAIL*test_1*",
+        "XFAIL*test_2*",
+        "XPASS*test_3*",
+        "SKIP*four*",
+    ])
+
+
+def test_reportchars_error(testdir):
+    testdir.makepyfile(
+        conftest="""
+        def pytest_runtest_teardown():
+            assert 0
+        """,
+        test_simple="""
+        def test_foo():
+            pass
+        """)
+    result = testdir.runpytest('-rE')
+    result.stdout.fnmatch_lines([
+        'ERROR*test_foo*',
+    ])
+
+
+def test_reportchars_all(testdir):
+    testdir.makepyfile("""
+        import pytest
+        def test_1():
+            assert 0
+        @pytest.mark.xfail
+        def test_2():
+            assert 0
+        @pytest.mark.xfail
+        def test_3():
+            pass
+        def test_4():
+            pytest.skip("four")
+    """)
+    result = testdir.runpytest("-ra")
+    result.stdout.fnmatch_lines([
+        "FAIL*test_1*",
+        "SKIP*four*",
+        "XFAIL*test_2*",
+        "XPASS*test_3*",
+    ])
+
+
+def test_reportchars_all_error(testdir):
+    testdir.makepyfile(
+        conftest="""
+        def pytest_runtest_teardown():
+            assert 0
+        """,
+        test_simple="""
+        def test_foo():
+            pass
+        """)
+    result = testdir.runpytest('-ra')
+    result.stdout.fnmatch_lines([
+        'ERROR*test_foo*',
+    ])
+
+
+@pytest.mark.xfail("hasattr(sys, 'pypy_version_info')")
+def test_errors_in_xfail_skip_expressions(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.skipif("asd")
+        def test_nameerror():
+            pass
+        @pytest.mark.xfail("syntax error")
+        def test_syntax():
+            pass
+
+        def test_func():
+            pass
+    """)
+    result = testdir.runpytest()
+    markline = "                ^"
+    if sys.platform.startswith("java"):
+        # XXX report this to java
+        markline = "*" + markline[8:]
+    result.stdout.fnmatch_lines([
+        "*ERROR*test_nameerror*",
+        "*evaluating*skipif*expression*",
+        "*asd*",
+        "*ERROR*test_syntax*",
+        "*evaluating*xfail*expression*",
+        "    syntax error",
+        markline,
+        "SyntaxError: invalid syntax",
+        "*1 pass*2 error*",
+    ])
+
+
+def test_xfail_skipif_with_globals(testdir):
+    testdir.makepyfile("""
+        import pytest
+        x = 3
+        @pytest.mark.skipif("x == 3")
+        def test_skip1():
+            pass
+        @pytest.mark.xfail("x == 3")
+        def test_boolean():
+            assert 0
+    """)
+    result = testdir.runpytest("-rsx")
+    result.stdout.fnmatch_lines([
+        "*SKIP*x == 3*",
+        "*XFAIL*test_boolean*",
+        "*x == 3*",
+    ])
+
+
+def test_direct_gives_error(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.skipif(True)
+        def test_skip1():
+            pass
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*1 error*",
+    ])
+
+
+def test_default_markers(testdir):
+    result = testdir.runpytest("--markers")
+    result.stdout.fnmatch_lines([
+        "*skipif(*condition)*skip*",
+        "*xfail(*condition, reason=None, run=True, raises=None, strict=False)*expected failure*",
+    ])
+
+
+def test_xfail_test_setup_exception(testdir):
+    testdir.makeconftest("""
+            def pytest_runtest_setup():
+                0 / 0
+        """)
+    p = testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail
+            def test_func():
+                assert 0
+        """)
+    result = testdir.runpytest(p)
+    assert result.ret == 0
+    assert 'xfailed' in result.stdout.str()
+    assert 'xpassed' not in result.stdout.str()
+
+
+def test_imperativeskip_on_xfail_test(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.xfail
+        def test_that_fails():
+            assert 0
+
+        @pytest.mark.skipif("True")
+        def test_hello():
+            pass
+    """)
+    testdir.makeconftest("""
+        import pytest
+        def pytest_runtest_setup(item):
+            pytest.skip("abc")
+    """)
+    result = testdir.runpytest("-rsxX")
+    result.stdout.fnmatch_lines_random("""
+        *SKIP*abc*
+        *SKIP*condition: True*
+        *2 skipped*
+    """)
+
+
+class TestBooleanCondition(object):
+    def test_skipif(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skipif(True, reason="True123")
+            def test_func1():
+                pass
+            @pytest.mark.skipif(False, reason="True123")
+            def test_func2():
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines("""
+            *1 passed*1 skipped*
+        """)
+
+    def test_skipif_noreason(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.skipif(True)
+            def test_func():
+                pass
+        """)
+        result = testdir.runpytest("-rs")
+        result.stdout.fnmatch_lines("""
+            *1 error*
+        """)
+
+    def test_xfail(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.xfail(True, reason="True123")
+            def test_func():
+                assert 0
+        """)
+        result = testdir.runpytest("-rxs")
+        result.stdout.fnmatch_lines("""
+            *XFAIL*
+            *True123*
+            *1 xfail*
+        """)
+
+
+def test_xfail_item(testdir):
+    # Ensure pytest.xfail works with non-Python Item
+    testdir.makeconftest("""
+        import pytest
+
+        class MyItem(pytest.Item):
+            nodeid = 'foo'
+            def runtest(self):
+                pytest.xfail("Expected Failure")
+
+        def pytest_collect_file(path, parent):
+            return MyItem("foo", parent)
+    """)
+    result = testdir.inline_run()
+    passed, skipped, failed = result.listoutcomes()
+    assert not failed
+    xfailed = [r for r in skipped if hasattr(r, 'wasxfail')]
+    assert xfailed
+
+
+def test_module_level_skip_error(testdir):
+    """
+    Verify that using pytest.skip at module level causes a collection error
+    """
+    testdir.makepyfile("""
+        import pytest
+        @pytest.skip
+        def test_func():
+            assert True
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(
+        "*Using pytest.skip outside of a test is not allowed*"
+    )
+
+
+def test_module_level_skip_with_allow_module_level(testdir):
+    """
+    Verify that using pytest.skip(allow_module_level=True) is allowed
+    """
+    testdir.makepyfile("""
+        import pytest
+        pytest.skip("skip_module_level", allow_module_level=True)
+
+        def test_func():
+            assert 0
+    """)
+    result = testdir.runpytest("-rxs")
+    result.stdout.fnmatch_lines(
+        "*SKIP*skip_module_level"
+    )
+
+
+def test_invalid_skip_keyword_parameter(testdir):
+    """
+    Verify that using pytest.skip() with unknown parameter raises an error
+    """
+    testdir.makepyfile("""
+        import pytest
+        pytest.skip("skip_module_level", unknown=1)
+
+        def test_func():
+            assert 0
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines(
+        "*TypeError:*['unknown']*"
+    )
+
+
+def test_mark_xfail_item(testdir):
+    # Ensure pytest.mark.xfail works with non-Python Item
+    testdir.makeconftest("""
+        import pytest
+
+        class MyItem(pytest.Item):
+            nodeid = 'foo'
+            def setup(self):
+                marker = pytest.mark.xfail(True, reason="Expected failure")
+                self.add_marker(marker)
+            def runtest(self):
+                assert False
+
+        def pytest_collect_file(path, parent):
+            return MyItem("foo", parent)
+    """)
+    result = testdir.inline_run()
+    passed, skipped, failed = result.listoutcomes()
+    assert not failed
+    xfailed = [r for r in skipped if hasattr(r, 'wasxfail')]
+    assert xfailed
diff --git a/tools/third_party/pytest/testing/test_terminal.py b/tools/third_party/pytest/testing/test_terminal.py
new file mode 100644
index 0000000..97c2f71
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_terminal.py
@@ -0,0 +1,1039 @@
+"""
+terminal reporting of the full testing process.
+"""
+from __future__ import absolute_import, division, print_function
+import collections
+import sys
+
+import pluggy
+import _pytest._code
+import py
+import pytest
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt
+from _pytest.terminal import build_summary_stats_line, _plugin_nameversions
+
+
+DistInfo = collections.namedtuple('DistInfo', ['project_name', 'version'])
+
+
+class Option(object):
+    def __init__(self, verbose=False, fulltrace=False):
+        self.verbose = verbose
+        self.fulltrace = fulltrace
+
+    @property
+    def args(self):
+        values = []
+        if self.verbose:
+            values.append('-v')
+        if self.fulltrace:
+            values.append('--fulltrace')
+        return values
+
+
+def pytest_generate_tests(metafunc):
+    if "option" in metafunc.fixturenames:
+        metafunc.addcall(id="default",
+                         funcargs={'option': Option(verbose=False)})
+        metafunc.addcall(id="verbose",
+                         funcargs={'option': Option(verbose=True)})
+        metafunc.addcall(id="quiet",
+                         funcargs={'option': Option(verbose=-1)})
+        metafunc.addcall(id="fulltrace",
+                         funcargs={'option': Option(fulltrace=True)})
+
+
+@pytest.mark.parametrize('input,expected', [
+    ([DistInfo(project_name='test', version=1)], ['test-1']),
+    ([DistInfo(project_name='pytest-test', version=1)], ['test-1']),
+    ([
+        DistInfo(project_name='test', version=1),
+        DistInfo(project_name='test', version=1)
+    ], ['test-1']),
+], ids=['normal', 'prefix-strip', 'deduplicate'])
+def test_plugin_nameversion(input, expected):
+    pluginlist = [(None, x) for x in input]
+    result = _plugin_nameversions(pluginlist)
+    assert result == expected
+
+
+class TestTerminal(object):
+    def test_pass_skip_fail(self, testdir, option):
+        testdir.makepyfile("""
+            import pytest
+            def test_ok():
+                pass
+            def test_skip():
+                pytest.skip("xx")
+            def test_func():
+                assert 0
+        """)
+        result = testdir.runpytest(*option.args)
+        if option.verbose:
+            result.stdout.fnmatch_lines([
+                "*test_pass_skip_fail.py::test_ok PASS*",
+                "*test_pass_skip_fail.py::test_skip SKIP*",
+                "*test_pass_skip_fail.py::test_func FAIL*",
+            ])
+        else:
+            result.stdout.fnmatch_lines([
+                "*test_pass_skip_fail.py .sF*"
+            ])
+        result.stdout.fnmatch_lines([
+            "    def test_func():",
+            ">       assert 0",
+            "E       assert 0",
+        ])
+
+    def test_internalerror(self, testdir, linecomp):
+        modcol = testdir.getmodulecol("def test_one(): pass")
+        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
+        excinfo = pytest.raises(ValueError, "raise ValueError('hello')")
+        rep.pytest_internalerror(excinfo.getrepr())
+        linecomp.assert_contains_lines([
+            "INTERNALERROR> *ValueError*hello*"
+        ])
+
+    def test_writeline(self, testdir, linecomp):
+        modcol = testdir.getmodulecol("def test_one(): pass")
+        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
+        rep.write_fspath_result(modcol.nodeid, ".")
+        rep.write_line("hello world")
+        lines = linecomp.stringio.getvalue().split('\n')
+        assert not lines[0]
+        assert lines[1].endswith(modcol.name + " .")
+        assert lines[2] == "hello world"
+
+    def test_show_runtest_logstart(self, testdir, linecomp):
+        item = testdir.getitem("def test_func(): pass")
+        tr = TerminalReporter(item.config, file=linecomp.stringio)
+        item.config.pluginmanager.register(tr)
+        location = item.reportinfo()
+        tr.config.hook.pytest_runtest_logstart(nodeid=item.nodeid,
+                                               location=location, fspath=str(item.fspath))
+        linecomp.assert_contains_lines([
+            "*test_show_runtest_logstart.py*"
+        ])
+
+    def test_runtest_location_shown_before_test_starts(self, testdir):
+        testdir.makepyfile("""
+            def test_1():
+                import time
+                time.sleep(20)
+        """)
+        child = testdir.spawn_pytest("")
+        child.expect(".*test_runtest_location.*py")
+        child.sendeof()
+        child.kill(15)
+
+    def test_itemreport_subclasses_show_subclassed_file(self, testdir):
+        testdir.makepyfile(test_p1="""
+            class BaseTests(object):
+                def test_p1(self):
+                    pass
+            class TestClass(BaseTests):
+                pass
+        """)
+        p2 = testdir.makepyfile(test_p2="""
+            from test_p1 import BaseTests
+            class TestMore(BaseTests):
+                pass
+        """)
+        result = testdir.runpytest(p2)
+        result.stdout.fnmatch_lines([
+            "*test_p2.py .*",
+            "*1 passed*",
+        ])
+        result = testdir.runpytest("-v", p2)
+        result.stdout.fnmatch_lines([
+            "*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED*",
+        ])
+
+    def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir):
+        a = testdir.mkpydir("a123")
+        a.join("test_hello123.py").write(_pytest._code.Source("""
+            class TestClass(object):
+                def test_method(self):
+                    pass
+        """))
+        result = testdir.runpytest("-v")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*a123/test_hello123.py*PASS*",
+        ])
+        assert " <- " not in result.stdout.str()
+
+    def test_keyboard_interrupt(self, testdir, option):
+        testdir.makepyfile("""
+            def test_foobar():
+                assert 0
+            def test_spamegg():
+                import py; pytest.skip('skip me please!')
+            def test_interrupt_me():
+                raise KeyboardInterrupt   # simulating the user
+        """)
+
+        result = testdir.runpytest(*option.args, no_reraise_ctrlc=True)
+        result.stdout.fnmatch_lines([
+            "    def test_foobar():",
+            ">       assert 0",
+            "E       assert 0",
+            "*_keyboard_interrupt.py:6: KeyboardInterrupt*",
+        ])
+        if option.fulltrace:
+            result.stdout.fnmatch_lines([
+                "*raise KeyboardInterrupt   # simulating the user*",
+            ])
+        else:
+            result.stdout.fnmatch_lines([
+                "to show a full traceback on KeyboardInterrupt use --fulltrace"
+            ])
+        result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
+
+    def test_keyboard_in_sessionstart(self, testdir):
+        testdir.makeconftest("""
+            def pytest_sessionstart():
+                raise KeyboardInterrupt
+        """)
+        testdir.makepyfile("""
+            def test_foobar():
+                pass
+        """)
+
+        result = testdir.runpytest(no_reraise_ctrlc=True)
+        assert result.ret == 2
+        result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
+
+    def test_collect_single_item(self, testdir):
+        """Use singular 'item' when reporting a single test item"""
+        testdir.makepyfile("""
+            def test_foobar():
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines(['collected 1 item'])
+
+    def test_rewrite(self, testdir, monkeypatch):
+        config = testdir.parseconfig()
+        f = py.io.TextIO()
+        monkeypatch.setattr(f, 'isatty', lambda *args: True)
+        tr = TerminalReporter(config, f)
+        tr._tw.fullwidth = 10
+        tr.write('hello')
+        tr.rewrite('hey', erase=True)
+        assert f.getvalue() == 'hello' + '\r' + 'hey' + (6 * ' ')
+
+
+class TestCollectonly(object):
+    def test_collectonly_basic(self, testdir):
+        testdir.makepyfile("""
+            def test_func():
+                pass
+        """)
+        result = testdir.runpytest("--collect-only",)
+        result.stdout.fnmatch_lines([
+            "<Module 'test_collectonly_basic.py'>",
+            "  <Function 'test_func'>",
+        ])
+
+    def test_collectonly_skipped_module(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            pytest.skip("hello")
+        """)
+        result = testdir.runpytest("--collect-only", "-rs")
+        result.stdout.fnmatch_lines([
+            "*ERROR collecting*",
+        ])
+
+    def test_collectonly_failed_module(self, testdir):
+        testdir.makepyfile("""raise ValueError(0)""")
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*raise ValueError*",
+            "*1 error*",
+        ])
+
+    def test_collectonly_fatal(self, testdir):
+        testdir.makeconftest("""
+            def pytest_collectstart(collector):
+                assert 0, "urgs"
+        """)
+        result = testdir.runpytest("--collect-only")
+        result.stdout.fnmatch_lines([
+            "*INTERNAL*args*"
+        ])
+        assert result.ret == 3
+
+    def test_collectonly_simple(self, testdir):
+        p = testdir.makepyfile("""
+            def test_func1():
+                pass
+            class TestClass(object):
+                def test_method(self):
+                    pass
+        """)
+        result = testdir.runpytest("--collect-only", p)
+        # assert stderr.startswith("inserting into sys.path")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*<Module '*.py'>",
+            "* <Function 'test_func1'*>",
+            "* <Class 'TestClass'>",
+            # "*  <Instance '()'>",
+            "*   <Function 'test_method'*>",
+        ])
+
+    def test_collectonly_error(self, testdir):
+        p = testdir.makepyfile("import Errlkjqweqwe")
+        result = testdir.runpytest("--collect-only", p)
+        assert result.ret == 2
+        result.stdout.fnmatch_lines(_pytest._code.Source("""
+            *ERROR*
+            *ImportError*
+            *No module named *Errlk*
+            *1 error*
+        """).strip())
+
+    def test_collectonly_missing_path(self, testdir):
+        """this checks issue 115,
+            failure in parseargs will cause session
+            not to have the items attribute
+        """
+        result = testdir.runpytest("--collect-only", "uhm_missing_path")
+        assert result.ret == 4
+        result.stderr.fnmatch_lines([
+            '*ERROR: file not found*',
+        ])
+
+    def test_collectonly_quiet(self, testdir):
+        testdir.makepyfile("def test_foo(): pass")
+        result = testdir.runpytest("--collect-only", "-q")
+        result.stdout.fnmatch_lines([
+            '*test_foo*',
+        ])
+
+    def test_collectonly_more_quiet(self, testdir):
+        testdir.makepyfile(test_fun="def test_foo(): pass")
+        result = testdir.runpytest("--collect-only", "-qq")
+        result.stdout.fnmatch_lines([
+            '*test_fun.py: 1*',
+        ])
+
+
+def test_repr_python_version(monkeypatch):
+    try:
+        monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
+        assert repr_pythonversion() == "2.5.1-final-0"
+        py.std.sys.version_info = x = (2, 3)
+        assert repr_pythonversion() == str(x)
+    finally:
+        monkeypatch.undo()  # do this early as pytest can get confused
+
+
+class TestFixtureReporting(object):
+    def test_setup_fixture_error(self, testdir):
+        testdir.makepyfile("""
+            def setup_function(function):
+                print ("setup func")
+                assert 0
+            def test_nada():
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERROR at setup of test_nada*",
+            "*setup_function(function):*",
+            "*setup func*",
+            "*assert 0*",
+            "*1 error*",
+        ])
+        assert result.ret != 0
+
+    def test_teardown_fixture_error(self, testdir):
+        testdir.makepyfile("""
+            def test_nada():
+                pass
+            def teardown_function(function):
+                print ("teardown func")
+                assert 0
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERROR at teardown*",
+            "*teardown_function(function):*",
+            "*assert 0*",
+            "*Captured stdout*",
+            "*teardown func*",
+            "*1 passed*1 error*",
+        ])
+
+    def test_teardown_fixture_error_and_test_failure(self, testdir):
+        testdir.makepyfile("""
+            def test_fail():
+                assert 0, "failingfunc"
+
+            def teardown_function(function):
+                print ("teardown func")
+                assert False
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERROR at teardown of test_fail*",
+            "*teardown_function(function):*",
+            "*assert False*",
+            "*Captured stdout*",
+            "*teardown func*",
+
+            "*test_fail*",
+            "*def test_fail():",
+            "*failingfunc*",
+            "*1 failed*1 error*",
+        ])
+
+    def test_setup_teardown_output_and_test_failure(self, testdir):
+        """ Test for issue #442 """
+        testdir.makepyfile("""
+            def setup_function(function):
+                print ("setup func")
+
+            def test_fail():
+                assert 0, "failingfunc"
+
+            def teardown_function(function):
+                print ("teardown func")
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*test_fail*",
+            "*def test_fail():",
+            "*failingfunc*",
+            "*Captured stdout setup*",
+            "*setup func*",
+            "*Captured stdout teardown*",
+            "*teardown func*",
+
+            "*1 failed*",
+        ])
+
+
+class TestTerminalFunctional(object):
+    def test_deselected(self, testdir):
+        testpath = testdir.makepyfile("""
+                def test_one():
+                    pass
+                def test_two():
+                    pass
+                def test_three():
+                    pass
+           """
+                                      )
+        result = testdir.runpytest("-k", "test_two:", testpath)
+        result.stdout.fnmatch_lines([
+            "*test_deselected.py ..*",
+            "=* 1 test*deselected *=",
+        ])
+        assert result.ret == 0
+
+    def test_no_skip_summary_if_failure(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def test_ok():
+                pass
+            def test_fail():
+                assert 0
+            def test_skip():
+                pytest.skip("dontshow")
+        """)
+        result = testdir.runpytest()
+        assert result.stdout.str().find("skip test summary") == -1
+        assert result.ret == 1
+
+    def test_passes(self, testdir):
+        p1 = testdir.makepyfile("""
+            def test_passes():
+                pass
+            class TestClass(object):
+                def test_method(self):
+                    pass
+        """)
+        old = p1.dirpath().chdir()
+        try:
+            result = testdir.runpytest()
+        finally:
+            old.chdir()
+        result.stdout.fnmatch_lines([
+            "test_passes.py ..*",
+            "* 2 pass*",
+        ])
+        assert result.ret == 0
+
+    def test_header_trailer_info(self, testdir):
+        testdir.makepyfile("""
+            def test_passes():
+                pass
+        """)
+        result = testdir.runpytest()
+        verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
+        result.stdout.fnmatch_lines([
+            "*===== test session starts ====*",
+            "platform %s -- Python %s*pytest-%s*py-%s*pluggy-%s" % (
+                py.std.sys.platform, verinfo,
+                pytest.__version__, py.__version__, pluggy.__version__),
+            "*test_header_trailer_info.py .*",
+            "=* 1 passed*in *.[0-9][0-9] seconds *=",
+        ])
+        if pytest.config.pluginmanager.list_plugin_distinfo():
+            result.stdout.fnmatch_lines([
+                "plugins: *",
+            ])
+
+    def test_showlocals(self, testdir):
+        p1 = testdir.makepyfile("""
+            def test_showlocals():
+                x = 3
+                y = "x" * 5000
+                assert 0
+        """)
+        result = testdir.runpytest(p1, '-l')
+        result.stdout.fnmatch_lines([
+            # "_ _ * Locals *",
+            "x* = 3",
+            "y* = 'xxxxxx*"
+        ])
+
+    def test_verbose_reporting(self, testdir, pytestconfig):
+        p1 = testdir.makepyfile("""
+            import pytest
+            def test_fail():
+                raise ValueError()
+            def test_pass():
+                pass
+            class TestClass(object):
+                def test_skip(self):
+                    pytest.skip("hello")
+            def test_gen():
+                def check(x):
+                    assert x == 1
+                yield check, 0
+        """)
+        result = testdir.runpytest(p1, '-v')
+        result.stdout.fnmatch_lines([
+            "*test_verbose_reporting.py::test_fail *FAIL*",
+            "*test_verbose_reporting.py::test_pass *PASS*",
+            "*test_verbose_reporting.py::TestClass::test_skip *SKIP*",
+            "*test_verbose_reporting.py::test_gen*0* *FAIL*",
+        ])
+        assert result.ret == 1
+
+        if not pytestconfig.pluginmanager.get_plugin("xdist"):
+            pytest.skip("xdist plugin not installed")
+
+        result = testdir.runpytest(p1, '-v', '-n 1')
+        result.stdout.fnmatch_lines([
+            "*FAIL*test_verbose_reporting.py::test_fail*",
+        ])
+        assert result.ret == 1
+
+    def test_quiet_reporting(self, testdir):
+        p1 = testdir.makepyfile("def test_pass(): pass")
+        result = testdir.runpytest(p1, '-q')
+        s = result.stdout.str()
+        assert 'test session starts' not in s
+        assert p1.basename not in s
+        assert "===" not in s
+        assert "passed" in s
+
+    def test_more_quiet_reporting(self, testdir):
+        p1 = testdir.makepyfile("def test_pass(): pass")
+        result = testdir.runpytest(p1, '-qq')
+        s = result.stdout.str()
+        assert 'test session starts' not in s
+        assert p1.basename not in s
+        assert "===" not in s
+        assert "passed" not in s
+
+    def test_report_collectionfinish_hook(self, testdir):
+        testdir.makeconftest("""
+            def pytest_report_collectionfinish(config, startdir, items):
+                return ['hello from hook: {0} items'.format(len(items))]
+        """)
+        testdir.makepyfile("""
+            import pytest
+            @pytest.mark.parametrize('i', range(3))
+            def test(i):
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "collected 3 items",
+            "hello from hook: 3 items",
+        ])
+
+
+def test_fail_extra_reporting(testdir):
+    testdir.makepyfile("def test_this(): assert 0")
+    result = testdir.runpytest()
+    assert 'short test summary' not in result.stdout.str()
+    result = testdir.runpytest('-rf')
+    result.stdout.fnmatch_lines([
+        "*test summary*",
+        "FAIL*test_fail_extra_reporting*",
+    ])
+
+
+def test_fail_reporting_on_pass(testdir):
+    testdir.makepyfile("def test_this(): assert 1")
+    result = testdir.runpytest('-rf')
+    assert 'short test summary' not in result.stdout.str()
+
+
+def test_pass_extra_reporting(testdir):
+    testdir.makepyfile("def test_this(): assert 1")
+    result = testdir.runpytest()
+    assert 'short test summary' not in result.stdout.str()
+    result = testdir.runpytest('-rp')
+    result.stdout.fnmatch_lines([
+        "*test summary*",
+        "PASS*test_pass_extra_reporting*",
+    ])
+
+
+def test_pass_reporting_on_fail(testdir):
+    testdir.makepyfile("def test_this(): assert 0")
+    result = testdir.runpytest('-rp')
+    assert 'short test summary' not in result.stdout.str()
+
+
+def test_pass_output_reporting(testdir):
+    testdir.makepyfile("""
+        def test_pass_output():
+            print("Four score and seven years ago...")
+    """)
+    result = testdir.runpytest()
+    assert 'Four score and seven years ago...' not in result.stdout.str()
+    result = testdir.runpytest('-rP')
+    result.stdout.fnmatch_lines([
+        "Four score and seven years ago...",
+    ])
+
+
+def test_color_yes(testdir):
+    testdir.makepyfile("def test_this(): assert 1")
+    result = testdir.runpytest('--color=yes')
+    assert 'test session starts' in result.stdout.str()
+    assert '\x1b[1m' in result.stdout.str()
+
+
+def test_color_no(testdir):
+    testdir.makepyfile("def test_this(): assert 1")
+    result = testdir.runpytest('--color=no')
+    assert 'test session starts' in result.stdout.str()
+    assert '\x1b[1m' not in result.stdout.str()
+
+
+@pytest.mark.parametrize('verbose', [True, False])
+def test_color_yes_collection_on_non_atty(testdir, verbose):
+    """skip collect progress report when working on non-terminals.
+    #1397
+    """
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.parametrize('i', range(10))
+        def test_this(i):
+            assert 1
+    """)
+    args = ['--color=yes']
+    if verbose:
+        args.append('-vv')
+    result = testdir.runpytest(*args)
+    assert 'test session starts' in result.stdout.str()
+    assert '\x1b[1m' in result.stdout.str()
+    assert 'collecting 10 items' not in result.stdout.str()
+    if verbose:
+        assert 'collecting ...' in result.stdout.str()
+    assert 'collected 10 items' in result.stdout.str()
+
+
+def test_getreportopt():
+    class config(object):
+        class option(object):
+            reportchars = ""
+            disable_warnings = True
+
+    config.option.reportchars = "sf"
+    assert getreportopt(config) == "sf"
+
+    config.option.reportchars = "sfxw"
+    assert getreportopt(config) == "sfx"
+
+    config.option.reportchars = "sfx"
+    config.option.disable_warnings = False
+    assert getreportopt(config) == "sfxw"
+
+    config.option.reportchars = "sfxw"
+    config.option.disable_warnings = False
+    assert getreportopt(config) == "sfxw"
+
+
+def test_terminalreporter_reportopt_addopts(testdir):
+    testdir.makeini("[pytest]\naddopts=-rs")
+    testdir.makepyfile("""
+        import pytest
+
+        @pytest.fixture
+        def tr(request):
+            tr = request.config.pluginmanager.getplugin("terminalreporter")
+            return tr
+        def test_opt(tr):
+            assert tr.hasopt('skipped')
+            assert not tr.hasopt('qwe')
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*1 passed*"
+    ])
+
+
+def test_tbstyle_short(testdir):
+    p = testdir.makepyfile("""
+        import pytest
+
+        @pytest.fixture
+        def arg(request):
+            return 42
+        def test_opt(arg):
+            x = 0
+            assert x
+    """)
+    result = testdir.runpytest("--tb=short")
+    s = result.stdout.str()
+    assert 'arg = 42' not in s
+    assert 'x = 0' not in s
+    result.stdout.fnmatch_lines([
+        "*%s:8*" % p.basename,
+        "    assert x",
+        "E   assert*",
+    ])
+    result = testdir.runpytest()
+    s = result.stdout.str()
+    assert 'x = 0' in s
+    assert 'assert x' in s
+
+
+def test_traceconfig(testdir, monkeypatch):
+    result = testdir.runpytest("--traceconfig")
+    result.stdout.fnmatch_lines([
+        "*active plugins*"
+    ])
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+
+
+class TestGenericReporting(object):
+    """ this test class can be subclassed with a different option
+        provider to run e.g. distributed tests.
+    """
+
+    def test_collect_fail(self, testdir, option):
+        testdir.makepyfile("import xyz\n")
+        result = testdir.runpytest(*option.args)
+        result.stdout.fnmatch_lines([
+            "ImportError while importing*",
+            "*No module named *xyz*",
+            "*1 error*",
+        ])
+
+    def test_maxfailures(self, testdir, option):
+        testdir.makepyfile("""
+            def test_1():
+                assert 0
+            def test_2():
+                assert 0
+            def test_3():
+                assert 0
+        """)
+        result = testdir.runpytest("--maxfail=2", *option.args)
+        result.stdout.fnmatch_lines([
+            "*def test_1():*",
+            "*def test_2():*",
+            "*2 failed*",
+        ])
+
+    def test_tb_option(self, testdir, option):
+        testdir.makepyfile("""
+            import pytest
+            def g():
+                raise IndexError
+            def test_func():
+                print (6*7)
+                g()  # --calling--
+        """)
+        for tbopt in ["long", "short", "no"]:
+            print('testing --tb=%s...' % tbopt)
+            result = testdir.runpytest('--tb=%s' % tbopt)
+            s = result.stdout.str()
+            if tbopt == "long":
+                assert 'print (6*7)' in s
+            else:
+                assert 'print (6*7)' not in s
+            if tbopt != "no":
+                assert '--calling--' in s
+                assert 'IndexError' in s
+            else:
+                assert 'FAILURES' not in s
+                assert '--calling--' not in s
+                assert 'IndexError' not in s
+
+    def test_tb_crashline(self, testdir, option):
+        p = testdir.makepyfile("""
+            import pytest
+            def g():
+                raise IndexError
+            def test_func1():
+                print (6*7)
+                g()  # --calling--
+            def test_func2():
+                assert 0, "hello"
+        """)
+        result = testdir.runpytest("--tb=line")
+        bn = p.basename
+        result.stdout.fnmatch_lines([
+            "*%s:3: IndexError*" % bn,
+            "*%s:8: AssertionError: hello*" % bn,
+        ])
+        s = result.stdout.str()
+        assert "def test_func2" not in s
+
+    def test_pytest_report_header(self, testdir, option):
+        testdir.makeconftest("""
+            def pytest_sessionstart(session):
+                session.config._somevalue = 42
+            def pytest_report_header(config):
+                return "hello: %s" % config._somevalue
+        """)
+        testdir.mkdir("a").join("conftest.py").write("""
+def pytest_report_header(config, startdir):
+    return ["line1", str(startdir)]
+""")
+        result = testdir.runpytest("a")
+        result.stdout.fnmatch_lines([
+            "*hello: 42*",
+            "line1",
+            str(testdir.tmpdir),
+        ])
+
+
+@pytest.mark.xfail("not hasattr(os, 'dup')")
+def test_fdopen_kept_alive_issue124(testdir):
+    testdir.makepyfile("""
+        import os, sys
+        k = []
+        def test_open_file_and_keep_alive(capfd):
+            stdout = os.fdopen(1, 'w', 1)
+            k.append(stdout)
+
+        def test_close_kept_alive_file():
+            stdout = k.pop()
+            stdout.close()
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "*2 passed*"
+    ])
+
+
+def test_tbstyle_native_setup_error(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.fixture
+        def setup_error_fixture():
+            raise Exception("error in exception")
+
+        def test_error_fixture(setup_error_fixture):
+            pass
+    """)
+    result = testdir.runpytest("--tb=native")
+    result.stdout.fnmatch_lines([
+        '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*'
+    ])
+
+
+def test_terminal_summary(testdir):
+    testdir.makeconftest("""
+        def pytest_terminal_summary(terminalreporter, exitstatus):
+            w = terminalreporter
+            w.section("hello")
+            w.line("world")
+            w.line("exitstatus: {0}".format(exitstatus))
+    """)
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines("""
+        *==== hello ====*
+        world
+        exitstatus: 5
+    """)
+
+
+def test_terminal_summary_warnings_are_displayed(testdir):
+    """Test that warnings emitted during pytest_terminal_summary are displayed.
+    (#1305).
+    """
+    testdir.makeconftest("""
+        def pytest_terminal_summary(terminalreporter):
+            config = terminalreporter.config
+            config.warn('C1', 'internal warning')
+    """)
+    result = testdir.runpytest('-rw')
+    result.stdout.fnmatch_lines([
+        '*internal warning',
+        '*== 1 warnings in *',
+    ])
+
+
+@pytest.mark.parametrize("exp_color, exp_line, stats_arg", [
+    # The method under test only cares about the length of each
+    # dict value, not the actual contents, so tuples of anything
+    # suffice
+
+    # Important statuses -- the highest priority of these always wins
+    ("red", "1 failed", {"failed": (1,)}),
+    ("red", "1 failed, 1 passed", {"failed": (1,), "passed": (1,)}),
+
+    ("red", "1 error", {"error": (1,)}),
+    ("red", "1 passed, 1 error", {"error": (1,), "passed": (1,)}),
+
+    # (a status that's not known to the code)
+    ("yellow", "1 weird", {"weird": (1,)}),
+    ("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}),
+
+    ("yellow", "1 warnings", {"warnings": (1,)}),
+    ("yellow", "1 passed, 1 warnings", {"warnings": (1,),
+                                        "passed": (1,)}),
+
+    ("green", "5 passed", {"passed": (1, 2, 3, 4, 5)}),
+
+
+    # "Boring" statuses.  These have no effect on the color of the summary
+    # line.  Thus, if *every* test has a boring status, the summary line stays
+    # at its default color, i.e. yellow, to warn the user that the test run
+    # produced no useful information
+    ("yellow", "1 skipped", {"skipped": (1,)}),
+    ("green", "1 passed, 1 skipped", {"skipped": (1,), "passed": (1,)}),
+
+    ("yellow", "1 deselected", {"deselected": (1,)}),
+    ("green", "1 passed, 1 deselected", {"deselected": (1,), "passed": (1,)}),
+
+    ("yellow", "1 xfailed", {"xfailed": (1,)}),
+    ("green", "1 passed, 1 xfailed", {"xfailed": (1,), "passed": (1,)}),
+
+    ("yellow", "1 xpassed", {"xpassed": (1,)}),
+    ("green", "1 passed, 1 xpassed", {"xpassed": (1,), "passed": (1,)}),
+
+    # Likewise if no tests were found at all
+    ("yellow", "no tests ran", {}),
+
+    # Test the empty-key special case
+    ("yellow", "no tests ran", {"": (1,)}),
+    ("green", "1 passed", {"": (1,), "passed": (1,)}),
+
+
+    # A couple more complex combinations
+    ("red", "1 failed, 2 passed, 3 xfailed",
+        {"passed": (1, 2), "failed": (1,), "xfailed": (1, 2, 3)}),
+
+    ("green", "1 passed, 2 skipped, 3 deselected, 2 xfailed",
+        {"passed": (1,),
+         "skipped": (1, 2),
+         "deselected": (1, 2, 3),
+         "xfailed": (1, 2)}),
+])
+def test_summary_stats(exp_line, exp_color, stats_arg):
+    print("Based on stats: %s" % stats_arg)
+    print("Expect summary: \"%s\"; with color \"%s\"" % (exp_line, exp_color))
+    (line, color) = build_summary_stats_line(stats_arg)
+    print("Actually got:   \"%s\"; with color \"%s\"" % (line, color))
+    assert line == exp_line
+    assert color == exp_color
+
+
+def test_no_trailing_whitespace_after_inifile_word(testdir):
+    result = testdir.runpytest('')
+    assert 'inifile:\n' in result.stdout.str()
+
+    testdir.makeini('[pytest]')
+    result = testdir.runpytest('')
+    assert 'inifile: tox.ini\n' in result.stdout.str()
+
+
+class TestProgress:
+
+    @pytest.fixture
+    def many_tests_file(self, testdir):
+        testdir.makepyfile(
+            test_bar="""
+                import pytest
+                @pytest.mark.parametrize('i', range(10))
+                def test_bar(i): pass
+            """,
+            test_foo="""
+                import pytest
+                @pytest.mark.parametrize('i', range(5))
+                def test_foo(i): pass
+            """,
+            test_foobar="""
+                import pytest
+                @pytest.mark.parametrize('i', range(5))
+                def test_foobar(i): pass
+            """,
+        )
+
+    def test_zero_tests_collected(self, testdir):
+        """Some plugins (testmon for example) might issue pytest_runtest_logreport without any tests being
+        actually collected (#2971)."""
+        testdir.makeconftest("""
+        def pytest_collection_modifyitems(items, config):
+            from _pytest.runner import CollectReport
+            for node_id in ('nodeid1', 'nodeid2'):
+                rep = CollectReport(node_id, 'passed', None, None)
+                rep.when = 'passed'
+                rep.duration = 0.1
+                config.hook.pytest_runtest_logreport(report=rep)
+        """)
+        output = testdir.runpytest()
+        assert 'ZeroDivisionError' not in output.stdout.str()
+        output.stdout.fnmatch_lines([
+            '=* 2 passed in *=',
+        ])
+
+    def test_normal(self, many_tests_file, testdir):
+        output = testdir.runpytest()
+        output.stdout.re_match_lines([
+            r'test_bar.py \.{10} \s+ \[ 50%\]',
+            r'test_foo.py \.{5} \s+ \[ 75%\]',
+            r'test_foobar.py \.{5} \s+ \[100%\]',
+        ])
+
+    def test_verbose(self, many_tests_file, testdir):
+        output = testdir.runpytest('-v')
+        output.stdout.re_match_lines([
+            r'test_bar.py::test_bar\[0\] PASSED \s+ \[  5%\]',
+            r'test_foo.py::test_foo\[4\] PASSED \s+ \[ 75%\]',
+            r'test_foobar.py::test_foobar\[4\] PASSED \s+ \[100%\]',
+        ])
+
+    def test_xdist_normal(self, many_tests_file, testdir):
+        pytest.importorskip('xdist')
+        output = testdir.runpytest('-n2')
+        output.stdout.re_match_lines([
+            r'\.{20} \s+ \[100%\]',
+        ])
+
+    def test_xdist_verbose(self, many_tests_file, testdir):
+        pytest.importorskip('xdist')
+        output = testdir.runpytest('-n2', '-v')
+        output.stdout.re_match_lines_random([
+            r'\[gw\d\] \[\s*\d+%\] PASSED test_bar.py::test_bar\[1\]',
+            r'\[gw\d\] \[\s*\d+%\] PASSED test_foo.py::test_foo\[1\]',
+            r'\[gw\d\] \[\s*\d+%\] PASSED test_foobar.py::test_foobar\[1\]',
+        ])
diff --git a/tools/third_party/pytest/testing/test_tmpdir.py b/tools/third_party/pytest/testing/test_tmpdir.py
new file mode 100644
index 0000000..467e772
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_tmpdir.py
@@ -0,0 +1,188 @@
+from __future__ import absolute_import, division, print_function
+import sys
+import py
+import pytest
+
+from _pytest.tmpdir import tmpdir
+
+
+def test_funcarg(testdir):
+    testdir.makepyfile("""
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(id='a')
+                metafunc.addcall(id='b')
+            def test_func(tmpdir): pass
+    """)
+    from _pytest.tmpdir import TempdirFactory
+    reprec = testdir.inline_run()
+    calls = reprec.getcalls("pytest_runtest_setup")
+    item = calls[0].item
+    config = item.config
+    tmpdirhandler = TempdirFactory(config)
+    item._initrequest()
+    p = tmpdir(item._request, tmpdirhandler)
+    assert p.check()
+    bn = p.basename.strip("0123456789")
+    assert bn.endswith("test_func_a_")
+    item.name = "qwe/\\abc"
+    p = tmpdir(item._request, tmpdirhandler)
+    assert p.check()
+    bn = p.basename.strip("0123456789")
+    assert bn == "qwe__abc"
+
+
+def test_ensuretemp(recwarn):
+    d1 = pytest.ensuretemp('hello')
+    d2 = pytest.ensuretemp('hello')
+    assert d1 == d2
+    assert d1.check(dir=1)
+
+
+class TestTempdirHandler(object):
+    def test_mktemp(self, testdir):
+        from _pytest.tmpdir import TempdirFactory
+        config = testdir.parseconfig()
+        config.option.basetemp = testdir.mkdir("hello")
+        t = TempdirFactory(config)
+        tmp = t.mktemp("world")
+        assert tmp.relto(t.getbasetemp()) == "world0"
+        tmp = t.mktemp("this")
+        assert tmp.relto(t.getbasetemp()).startswith("this")
+        tmp2 = t.mktemp("this")
+        assert tmp2.relto(t.getbasetemp()).startswith("this")
+        assert tmp2 != tmp
+
+
+class TestConfigTmpdir(object):
+    def test_getbasetemp_custom_removes_old(self, testdir):
+        mytemp = testdir.tmpdir.join("xyz")
+        p = testdir.makepyfile("""
+            def test_1(tmpdir):
+                pass
+        """)
+        testdir.runpytest(p, '--basetemp=%s' % mytemp)
+        mytemp.check()
+        mytemp.ensure("hello")
+
+        testdir.runpytest(p, '--basetemp=%s' % mytemp)
+        mytemp.check()
+        assert not mytemp.join("hello").check()
+
+
+def test_basetemp(testdir):
+    mytemp = testdir.tmpdir.mkdir("mytemp")
+    p = testdir.makepyfile("""
+        import pytest
+        def test_1():
+            pytest.ensuretemp("hello")
+    """)
+    result = testdir.runpytest(p, '--basetemp=%s' % mytemp)
+    assert result.ret == 0
+    assert mytemp.join('hello').check()
+
+
+@pytest.mark.skipif(not hasattr(py.path.local, 'mksymlinkto'),
+                    reason="symlink not available on this platform")
+def test_tmpdir_always_is_realpath(testdir):
+    # the reason why tmpdir should be a realpath is that
+    # when you cd to it and do "os.getcwd()" you will anyway
+    # get the realpath.  Using the symlinked path can thus
+    # easily result in path-inequality
+    # XXX if that proves to be a problem, consider using
+    # os.environ["PWD"]
+    realtemp = testdir.tmpdir.mkdir("myrealtemp")
+    linktemp = testdir.tmpdir.join("symlinktemp")
+    linktemp.mksymlinkto(realtemp)
+    p = testdir.makepyfile("""
+        def test_1(tmpdir):
+            import os
+            assert os.path.realpath(str(tmpdir)) == str(tmpdir)
+    """)
+    result = testdir.runpytest("-s", p, '--basetemp=%s/bt' % linktemp)
+    assert not result.ret
+
+
+def test_tmpdir_too_long_on_parametrization(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.mark.parametrize("arg", ["1"*1000])
+        def test_some(arg, tmpdir):
+            tmpdir.ensure("hello")
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+def test_tmpdir_factory(testdir):
+    testdir.makepyfile("""
+        import pytest
+        @pytest.fixture(scope='session')
+        def session_dir(tmpdir_factory):
+            return tmpdir_factory.mktemp('data', numbered=False)
+        def test_some(session_dir):
+            session_dir.isdir()
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+def test_tmpdir_fallback_tox_env(testdir, monkeypatch):
+    """Test that tmpdir works even if environment variables required by getpass
+    module are missing (#1010).
+    """
+    monkeypatch.delenv('USER', raising=False)
+    monkeypatch.delenv('USERNAME', raising=False)
+    testdir.makepyfile("""
+        import pytest
+        def test_some(tmpdir):
+            assert tmpdir.isdir()
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+@pytest.fixture
+def break_getuser(monkeypatch):
+    monkeypatch.setattr('os.getuid', lambda: -1)
+    # taken from python 2.7/3.4
+    for envvar in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):
+        monkeypatch.delenv(envvar, raising=False)
+
+
+@pytest.mark.usefixtures("break_getuser")
+@pytest.mark.skipif(sys.platform.startswith('win'), reason='no os.getuid on windows')
+def test_tmpdir_fallback_uid_not_found(testdir):
+    """Test that tmpdir works even if the current process's user id does not
+    correspond to a valid user.
+    """
+
+    testdir.makepyfile("""
+        import pytest
+        def test_some(tmpdir):
+            assert tmpdir.isdir()
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
+
+
+@pytest.mark.usefixtures("break_getuser")
+@pytest.mark.skipif(sys.platform.startswith('win'), reason='no os.getuid on windows')
+def test_get_user_uid_not_found():
+    """Test that get_user() function works even if the current process's
+    user id does not correspond to a valid user (e.g. running pytest in a
+    Docker container with 'docker run -u'.
+    """
+    from _pytest.tmpdir import get_user
+    assert get_user() is None
+
+
+@pytest.mark.skipif(not sys.platform.startswith('win'), reason='win only')
+def test_get_user(monkeypatch):
+    """Test that get_user() function works even if environment variables
+    required by getpass module are missing from the environment on Windows
+    (#1010).
+    """
+    from _pytest.tmpdir import get_user
+    monkeypatch.delenv('USER', raising=False)
+    monkeypatch.delenv('USERNAME', raising=False)
+    assert get_user() is None
diff --git a/tools/third_party/pytest/testing/test_unittest.py b/tools/third_party/pytest/testing/test_unittest.py
new file mode 100644
index 0000000..e197735
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_unittest.py
@@ -0,0 +1,830 @@
+from __future__ import absolute_import, division, print_function
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+import pytest
+import gc
+
+
+def test_simple_unittest(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        class MyTestCase(unittest.TestCase):
+            def testpassing(self):
+                self.assertEqual('foo', 'foo')
+            def test_failing(self):
+                self.assertEqual('foo', 'bar')
+    """)
+    reprec = testdir.inline_run(testpath)
+    assert reprec.matchreport("testpassing").passed
+    assert reprec.matchreport("test_failing").failed
+
+
+def test_runTest_method(testdir):
+    testdir.makepyfile("""
+        import unittest
+        class MyTestCaseWithRunTest(unittest.TestCase):
+            def runTest(self):
+                self.assertEqual('foo', 'foo')
+        class MyTestCaseWithoutRunTest(unittest.TestCase):
+            def runTest(self):
+                self.assertEqual('foo', 'foo')
+            def test_something(self):
+                pass
+        """)
+    result = testdir.runpytest("-v")
+    result.stdout.fnmatch_lines("""
+        *MyTestCaseWithRunTest::runTest*
+        *MyTestCaseWithoutRunTest::test_something*
+        *2 passed*
+    """)
+
+
+def test_isclasscheck_issue53(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        class _E(object):
+            def __getattr__(self, tag):
+                pass
+        E = _E()
+    """)
+    result = testdir.runpytest(testpath)
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+
+
+def test_setup(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        class MyTestCase(unittest.TestCase):
+            def setUp(self):
+                self.foo = 1
+            def setup_method(self, method):
+                self.foo2 = 1
+            def test_both(self):
+                self.assertEqual(1, self.foo)
+                assert self.foo2 == 1
+            def teardown_method(self, method):
+                assert 0, "42"
+
+    """)
+    reprec = testdir.inline_run("-s", testpath)
+    assert reprec.matchreport("test_both", when="call").passed
+    rep = reprec.matchreport("test_both", when="teardown")
+    assert rep.failed and '42' in str(rep.longrepr)
+
+
+def test_setUpModule(testdir):
+    testpath = testdir.makepyfile("""
+        values = []
+
+        def setUpModule():
+            values.append(1)
+
+        def tearDownModule():
+            del values[0]
+
+        def test_hello():
+            assert values == [1]
+
+        def test_world():
+            assert values == [1]
+        """)
+    result = testdir.runpytest(testpath)
+    result.stdout.fnmatch_lines([
+        "*2 passed*",
+    ])
+
+
+def test_setUpModule_failing_no_teardown(testdir):
+    testpath = testdir.makepyfile("""
+        values = []
+
+        def setUpModule():
+            0/0
+
+        def tearDownModule():
+            values.append(1)
+
+        def test_hello():
+            pass
+    """)
+    reprec = testdir.inline_run(testpath)
+    reprec.assertoutcome(passed=0, failed=1)
+    call = reprec.getcalls("pytest_runtest_setup")[0]
+    assert not call.item.module.values
+
+
+def test_new_instances(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        class MyTestCase(unittest.TestCase):
+            def test_func1(self):
+                self.x = 2
+            def test_func2(self):
+                assert not hasattr(self, 'x')
+    """)
+    reprec = testdir.inline_run(testpath)
+    reprec.assertoutcome(passed=2)
+
+
+def test_teardown(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        class MyTestCase(unittest.TestCase):
+            values = []
+            def test_one(self):
+                pass
+            def tearDown(self):
+                self.values.append(None)
+        class Second(unittest.TestCase):
+            def test_check(self):
+                self.assertEqual(MyTestCase.values, [None])
+    """)
+    reprec = testdir.inline_run(testpath)
+    passed, skipped, failed = reprec.countoutcomes()
+    assert failed == 0, failed
+    assert passed == 2
+    assert passed + skipped + failed == 2
+
+
+def test_teardown_issue1649(testdir):
+    """
+    Are TestCase objects cleaned up? Often unittest TestCase objects set
+    attributes that are large and expensive during setUp.
+
+    The TestCase will not be cleaned up if the test fails, because it
+    would then exist in the stackframe.
+    """
+    testpath = testdir.makepyfile("""
+        import unittest
+        class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase):
+            def setUp(self):
+                self.an_expensive_object = 1
+            def test_demo(self):
+                pass
+
+    """)
+    testdir.inline_run("-s", testpath)
+    gc.collect()
+    for obj in gc.get_objects():
+        assert type(obj).__name__ != 'TestCaseObjectsShouldBeCleanedUp'
+
+
+def test_unittest_skip_issue148(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+
+        @unittest.skip("hello")
+        class MyTestCase(unittest.TestCase):
+            @classmethod
+            def setUpClass(self):
+                xxx
+            def test_one(self):
+                pass
+            @classmethod
+            def tearDownClass(self):
+                xxx
+    """)
+    reprec = testdir.inline_run(testpath)
+    reprec.assertoutcome(skipped=1)
+
+
+def test_method_and_teardown_failing_reporting(testdir):
+    testdir.makepyfile("""
+        import unittest, pytest
+        class TC(unittest.TestCase):
+            def tearDown(self):
+                assert 0, "down1"
+            def test_method(self):
+                assert False, "down2"
+    """)
+    result = testdir.runpytest("-s")
+    assert result.ret == 1
+    result.stdout.fnmatch_lines([
+        "*tearDown*",
+        "*assert 0*",
+        "*test_method*",
+        "*assert False*",
+        "*1 failed*1 error*",
+    ])
+
+
+def test_setup_failure_is_shown(testdir):
+    testdir.makepyfile("""
+        import unittest
+        import pytest
+        class TC(unittest.TestCase):
+            def setUp(self):
+                assert 0, "down1"
+            def test_method(self):
+                print ("never42")
+                xyz
+    """)
+    result = testdir.runpytest("-s")
+    assert result.ret == 1
+    result.stdout.fnmatch_lines([
+        "*setUp*",
+        "*assert 0*down1*",
+        "*1 failed*",
+    ])
+    assert 'never42' not in result.stdout.str()
+
+
+def test_setup_setUpClass(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        import pytest
+        class MyTestCase(unittest.TestCase):
+            x = 0
+            @classmethod
+            def setUpClass(cls):
+                cls.x += 1
+            def test_func1(self):
+                assert self.x == 1
+            def test_func2(self):
+                assert self.x == 1
+            @classmethod
+            def tearDownClass(cls):
+                cls.x -= 1
+        def test_teareddown():
+            assert MyTestCase.x == 0
+    """)
+    reprec = testdir.inline_run(testpath)
+    reprec.assertoutcome(passed=3)
+
+
+def test_setup_class(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        import pytest
+        class MyTestCase(unittest.TestCase):
+            x = 0
+            def setup_class(cls):
+                cls.x += 1
+            def test_func1(self):
+                assert self.x == 1
+            def test_func2(self):
+                assert self.x == 1
+            def teardown_class(cls):
+                cls.x -= 1
+        def test_teareddown():
+            assert MyTestCase.x == 0
+    """)
+    reprec = testdir.inline_run(testpath)
+    reprec.assertoutcome(passed=3)
+
+
+@pytest.mark.parametrize("type", ['Error', 'Failure'])
+def test_testcase_adderrorandfailure_defers(testdir, type):
+    testdir.makepyfile("""
+        from unittest import TestCase
+        import pytest
+        class MyTestCase(TestCase):
+            def run(self, result):
+                excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0)
+                try:
+                    result.add%s(self, excinfo._excinfo)
+                except KeyboardInterrupt:
+                    raise
+                except:
+                    pytest.fail("add%s should not raise")
+            def test_hello(self):
+                pass
+    """ % (type, type))
+    result = testdir.runpytest()
+    assert 'should not raise' not in result.stdout.str()
+
+
+@pytest.mark.parametrize("type", ['Error', 'Failure'])
+def test_testcase_custom_exception_info(testdir, type):
+    testdir.makepyfile("""
+        from unittest import TestCase
+        import py, pytest
+        import _pytest._code
+        class MyTestCase(TestCase):
+            def run(self, result):
+                excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0)
+                # we fake an incompatible exception info
+                from _pytest.monkeypatch import MonkeyPatch
+                mp = MonkeyPatch()
+                def t(*args):
+                    mp.undo()
+                    raise TypeError()
+                mp.setattr(_pytest._code, 'ExceptionInfo', t)
+                try:
+                    excinfo = excinfo._excinfo
+                    result.add%(type)s(self, excinfo)
+                finally:
+                    mp.undo()
+            def test_hello(self):
+                pass
+    """ % locals())
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        "NOTE: Incompatible Exception Representation*",
+        "*ZeroDivisionError*",
+        "*1 failed*",
+    ])
+
+
+def test_testcase_totally_incompatible_exception_info(testdir):
+    item, = testdir.getitems("""
+        from unittest import TestCase
+        class MyTestCase(TestCase):
+            def test_hello(self):
+                pass
+    """)
+    item.addError(None, 42)
+    excinfo = item._excinfo.pop(0)
+    assert 'ERROR: Unknown Incompatible' in str(excinfo.getrepr())
+
+
+def test_module_level_pytestmark(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+        import pytest
+        pytestmark = pytest.mark.xfail
+        class MyTestCase(unittest.TestCase):
+            def test_func1(self):
+                assert 0
+    """)
+    reprec = testdir.inline_run(testpath, "-s")
+    reprec.assertoutcome(skipped=1)
+
+
+class TestTrialUnittest(object):
+    def setup_class(cls):
+        cls.ut = pytest.importorskip("twisted.trial.unittest")
+        # on windows trial uses a socket for a reactor and apparently doesn't close it properly
+        # https://twistedmatrix.com/trac/ticket/9227
+        cls.ignore_unclosed_socket_warning = ('-W', 'always')
+
+    def test_trial_testcase_runtest_not_collected(self, testdir):
+        testdir.makepyfile("""
+            from twisted.trial.unittest import TestCase
+
+            class TC(TestCase):
+                def test_hello(self):
+                    pass
+        """)
+        reprec = testdir.inline_run(*self.ignore_unclosed_socket_warning)
+        reprec.assertoutcome(passed=1)
+        testdir.makepyfile("""
+            from twisted.trial.unittest import TestCase
+
+            class TC(TestCase):
+                def runTest(self):
+                    pass
+        """)
+        reprec = testdir.inline_run(*self.ignore_unclosed_socket_warning)
+        reprec.assertoutcome(passed=1)
+
+    def test_trial_exceptions_with_skips(self, testdir):
+        testdir.makepyfile("""
+            from twisted.trial import unittest
+            import pytest
+            class TC(unittest.TestCase):
+                def test_hello(self):
+                    pytest.skip("skip_in_method")
+                @pytest.mark.skipif("sys.version_info != 1")
+                def test_hello2(self):
+                    pass
+                @pytest.mark.xfail(reason="iwanto")
+                def test_hello3(self):
+                    assert 0
+                def test_hello4(self):
+                    pytest.xfail("i2wanto")
+                def test_trial_skip(self):
+                    pass
+                test_trial_skip.skip = "trialselfskip"
+
+                def test_trial_todo(self):
+                    assert 0
+                test_trial_todo.todo = "mytodo"
+
+                def test_trial_todo_success(self):
+                    pass
+                test_trial_todo_success.todo = "mytodo"
+
+            class TC2(unittest.TestCase):
+                def setup_class(cls):
+                    pytest.skip("skip_in_setup_class")
+                def test_method(self):
+                    pass
+        """)
+        from _pytest.compat import _is_unittest_unexpected_success_a_failure
+        should_fail = _is_unittest_unexpected_success_a_failure()
+        result = testdir.runpytest("-rxs", *self.ignore_unclosed_socket_warning)
+        result.stdout.fnmatch_lines_random([
+            "*XFAIL*test_trial_todo*",
+            "*trialselfskip*",
+            "*skip_in_setup_class*",
+            "*iwanto*",
+            "*i2wanto*",
+            "*sys.version_info*",
+            "*skip_in_method*",
+            "*1 failed*4 skipped*3 xfailed*" if should_fail else "*4 skipped*3 xfail*1 xpass*",
+        ])
+        assert result.ret == (1 if should_fail else 0)
+
+    def test_trial_error(self, testdir):
+        testdir.makepyfile("""
+            from twisted.trial.unittest import TestCase
+            from twisted.internet.defer import Deferred
+            from twisted.internet import reactor
+
+            class TC(TestCase):
+                def test_one(self):
+                    crash
+
+                def test_two(self):
+                    def f(_):
+                        crash
+
+                    d = Deferred()
+                    d.addCallback(f)
+                    reactor.callLater(0.3, d.callback, None)
+                    return d
+
+                def test_three(self):
+                    def f():
+                        pass # will never get called
+                    reactor.callLater(0.3, f)
+                # will crash at teardown
+
+                def test_four(self):
+                    def f(_):
+                        reactor.callLater(0.3, f)
+                        crash
+
+                    d = Deferred()
+                    d.addCallback(f)
+                    reactor.callLater(0.3, d.callback, None)
+                    return d
+                # will crash both at test time and at teardown
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERRORS*",
+            "*DelayedCalls*",
+            "*test_four*",
+            "*NameError*crash*",
+            "*test_one*",
+            "*NameError*crash*",
+            "*test_three*",
+            "*DelayedCalls*",
+            "*test_two*",
+            "*crash*",
+        ])
+
+    def test_trial_pdb(self, testdir):
+        p = testdir.makepyfile("""
+            from twisted.trial import unittest
+            import pytest
+            class TC(unittest.TestCase):
+                def test_hello(self):
+                    assert 0, "hellopdb"
+        """)
+        child = testdir.spawn_pytest(p)
+        child.expect("hellopdb")
+        child.sendeof()
+
+    def test_trial_testcase_skip_property(self, testdir):
+        testpath = testdir.makepyfile("""
+            from twisted.trial import unittest
+            class MyTestCase(unittest.TestCase):
+                skip = 'dont run'
+                def test_func(self):
+                    pass
+            """)
+        reprec = testdir.inline_run(testpath, "-s")
+        reprec.assertoutcome(skipped=1)
+
+    def test_trial_testfunction_skip_property(self, testdir):
+        testpath = testdir.makepyfile("""
+            from twisted.trial import unittest
+            class MyTestCase(unittest.TestCase):
+                def test_func(self):
+                    pass
+                test_func.skip = 'dont run'
+            """)
+        reprec = testdir.inline_run(testpath, "-s")
+        reprec.assertoutcome(skipped=1)
+
+    def test_trial_testcase_todo_property(self, testdir):
+        testpath = testdir.makepyfile("""
+            from twisted.trial import unittest
+            class MyTestCase(unittest.TestCase):
+                todo = 'dont run'
+                def test_func(self):
+                    assert 0
+            """)
+        reprec = testdir.inline_run(testpath, "-s")
+        reprec.assertoutcome(skipped=1)
+
+    def test_trial_testfunction_todo_property(self, testdir):
+        testpath = testdir.makepyfile("""
+            from twisted.trial import unittest
+            class MyTestCase(unittest.TestCase):
+                def test_func(self):
+                    assert 0
+                test_func.todo = 'dont run'
+            """)
+        reprec = testdir.inline_run(testpath, "-s", *self.ignore_unclosed_socket_warning)
+        reprec.assertoutcome(skipped=1)
+
+
+def test_djangolike_testcase(testdir):
+    # contributed from Morten Breekevold
+    testdir.makepyfile("""
+        from unittest import TestCase, main
+
+        class DjangoLikeTestCase(TestCase):
+
+            def setUp(self):
+                print ("setUp()")
+
+            def test_presetup_has_been_run(self):
+                print ("test_thing()")
+                self.assertTrue(hasattr(self, 'was_presetup'))
+
+            def tearDown(self):
+                print ("tearDown()")
+
+            def __call__(self, result=None):
+                try:
+                    self._pre_setup()
+                except (KeyboardInterrupt, SystemExit):
+                    raise
+                except Exception:
+                    import sys
+                    result.addError(self, sys.exc_info())
+                    return
+                super(DjangoLikeTestCase, self).__call__(result)
+                try:
+                    self._post_teardown()
+                except (KeyboardInterrupt, SystemExit):
+                    raise
+                except Exception:
+                    import sys
+                    result.addError(self, sys.exc_info())
+                    return
+
+            def _pre_setup(self):
+                print ("_pre_setup()")
+                self.was_presetup = True
+
+            def _post_teardown(self):
+                print ("_post_teardown()")
+    """)
+    result = testdir.runpytest("-s")
+    assert result.ret == 0
+    result.stdout.fnmatch_lines([
+        "*_pre_setup()*",
+        "*setUp()*",
+        "*test_thing()*",
+        "*tearDown()*",
+        "*_post_teardown()*",
+    ])
+
+
+def test_unittest_not_shown_in_traceback(testdir):
+    testdir.makepyfile("""
+        import unittest
+        class t(unittest.TestCase):
+            def test_hello(self):
+                x = 3
+                self.assertEqual(x, 4)
+    """)
+    res = testdir.runpytest()
+    assert "failUnlessEqual" not in res.stdout.str()
+
+
+def test_unorderable_types(testdir):
+    testdir.makepyfile("""
+        import unittest
+        class TestJoinEmpty(unittest.TestCase):
+            pass
+
+        def make_test():
+            class Test(unittest.TestCase):
+                pass
+            Test.__name__ = "TestFoo"
+            return Test
+        TestFoo = make_test()
+    """)
+    result = testdir.runpytest()
+    assert "TypeError" not in result.stdout.str()
+    assert result.ret == EXIT_NOTESTSCOLLECTED
+
+
+def test_unittest_typerror_traceback(testdir):
+    testdir.makepyfile("""
+        import unittest
+        class TestJoinEmpty(unittest.TestCase):
+            def test_hello(self, arg1):
+                pass
+    """)
+    result = testdir.runpytest()
+    assert "TypeError" in result.stdout.str()
+    assert result.ret == 1
+
+
+@pytest.mark.parametrize('runner', ['pytest', 'unittest'])
+def test_unittest_expected_failure_for_failing_test_is_xfail(testdir, runner):
+    script = testdir.makepyfile("""
+        import unittest
+        class MyTestCase(unittest.TestCase):
+            @unittest.expectedFailure
+            def test_failing_test_is_xfail(self):
+                assert False
+        if __name__ == '__main__':
+            unittest.main()
+    """)
+    if runner == 'pytest':
+        result = testdir.runpytest("-rxX")
+        result.stdout.fnmatch_lines([
+            "*XFAIL*MyTestCase*test_failing_test_is_xfail*",
+            "*1 xfailed*",
+        ])
+    else:
+        result = testdir.runpython(script)
+        result.stderr.fnmatch_lines([
+            "*1 test in*",
+            "*OK*(expected failures=1)*",
+        ])
+    assert result.ret == 0
+
+
+@pytest.mark.parametrize('runner', ['pytest', 'unittest'])
+def test_unittest_expected_failure_for_passing_test_is_fail(testdir, runner):
+    script = testdir.makepyfile("""
+        import unittest
+        class MyTestCase(unittest.TestCase):
+            @unittest.expectedFailure
+            def test_passing_test_is_fail(self):
+                assert True
+        if __name__ == '__main__':
+            unittest.main()
+    """)
+    from _pytest.compat import _is_unittest_unexpected_success_a_failure
+    should_fail = _is_unittest_unexpected_success_a_failure()
+    if runner == 'pytest':
+        result = testdir.runpytest("-rxX")
+        result.stdout.fnmatch_lines([
+            "*MyTestCase*test_passing_test_is_fail*",
+            "*1 failed*" if should_fail else "*1 xpassed*",
+        ])
+    else:
+        result = testdir.runpython(script)
+        result.stderr.fnmatch_lines([
+            "*1 test in*",
+            "*(unexpected successes=1)*",
+        ])
+
+    assert result.ret == (1 if should_fail else 0)
+
+
+@pytest.mark.parametrize('fix_type, stmt', [
+    ('fixture', 'return'),
+    ('yield_fixture', 'yield'),
+])
+def test_unittest_setup_interaction(testdir, fix_type, stmt):
+    testdir.makepyfile("""
+        import unittest
+        import pytest
+        class MyTestCase(unittest.TestCase):
+            @pytest.{fix_type}(scope="class", autouse=True)
+            def perclass(self, request):
+                request.cls.hello = "world"
+                {stmt}
+            @pytest.{fix_type}(scope="function", autouse=True)
+            def perfunction(self, request):
+                request.instance.funcname = request.function.__name__
+                {stmt}
+
+            def test_method1(self):
+                assert self.funcname == "test_method1"
+                assert self.hello == "world"
+
+            def test_method2(self):
+                assert self.funcname == "test_method2"
+
+            def test_classattr(self):
+                assert self.__class__.hello == "world"
+    """.format(fix_type=fix_type, stmt=stmt))
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines("*3 passed*")
+
+
+def test_non_unittest_no_setupclass_support(testdir):
+    testpath = testdir.makepyfile("""
+        class TestFoo(object):
+            x = 0
+
+            @classmethod
+            def setUpClass(cls):
+                cls.x = 1
+
+            def test_method1(self):
+                assert self.x == 0
+
+            @classmethod
+            def tearDownClass(cls):
+                cls.x = 1
+
+        def test_not_teareddown():
+            assert TestFoo.x == 0
+
+    """)
+    reprec = testdir.inline_run(testpath)
+    reprec.assertoutcome(passed=2)
+
+
+def test_no_teardown_if_setupclass_failed(testdir):
+    testpath = testdir.makepyfile("""
+        import unittest
+
+        class MyTestCase(unittest.TestCase):
+            x = 0
+
+            @classmethod
+            def setUpClass(cls):
+                cls.x = 1
+                assert False
+
+            def test_func1(self):
+                cls.x = 10
+
+            @classmethod
+            def tearDownClass(cls):
+                cls.x = 100
+
+        def test_notTornDown():
+            assert MyTestCase.x == 1
+    """)
+    reprec = testdir.inline_run(testpath)
+    reprec.assertoutcome(passed=1, failed=1)
+
+
+def test_issue333_result_clearing(testdir):
+    testdir.makeconftest("""
+        import pytest
+        @pytest.hookimpl(hookwrapper=True)
+        def pytest_runtest_call(item):
+            yield
+            assert 0
+    """)
+    testdir.makepyfile("""
+        import unittest
+        class TestIt(unittest.TestCase):
+            def test_func(self):
+                0/0
+    """)
+
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(failed=1)
+
+
+def test_unittest_raise_skip_issue748(testdir):
+    testdir.makepyfile(test_foo="""
+        import unittest
+
+        class MyTestCase(unittest.TestCase):
+            def test_one(self):
+                raise unittest.SkipTest('skipping due to reasons')
+    """)
+    result = testdir.runpytest("-v", '-rs')
+    result.stdout.fnmatch_lines("""
+        *SKIP*[1]*test_foo.py*skipping due to reasons*
+        *1 skipped*
+    """)
+
+
+def test_unittest_skip_issue1169(testdir):
+    testdir.makepyfile(test_foo="""
+        import unittest
+
+        class MyTestCase(unittest.TestCase):
+            @unittest.skip("skipping due to reasons")
+            def test_skip(self):
+                 self.fail()
+        """)
+    result = testdir.runpytest("-v", '-rs')
+    result.stdout.fnmatch_lines("""
+        *SKIP*[1]*skipping due to reasons*
+        *1 skipped*
+    """)
+
+
+def test_class_method_containing_test_issue1558(testdir):
+    testdir.makepyfile(test_foo="""
+        import unittest
+
+        class MyTestCase(unittest.TestCase):
+            def test_should_run(self):
+                pass
+            def test_should_not_run(self):
+                pass
+            test_should_not_run.__test__ = False
+    """)
+    reprec = testdir.inline_run()
+    reprec.assertoutcome(passed=1)
diff --git a/tools/third_party/pytest/testing/test_warnings.py b/tools/third_party/pytest/testing/test_warnings.py
new file mode 100644
index 0000000..02400bd
--- /dev/null
+++ b/tools/third_party/pytest/testing/test_warnings.py
@@ -0,0 +1,258 @@
+# -*- coding: utf8 -*-
+from __future__ import unicode_literals
+
+import sys
+
+import pytest
+
+
+WARNINGS_SUMMARY_HEADER = 'warnings summary'
+
+
+@pytest.fixture
+def pyfile_with_warnings(testdir, request):
+    """
+    Create a test file which calls a function in a module which generates warnings.
+    """
+    testdir.syspathinsert()
+    test_name = request.function.__name__
+    module_name = test_name.lstrip('test_') + '_module'
+    testdir.makepyfile(**{
+        module_name: '''
+            import warnings
+            def foo():
+                warnings.warn(UserWarning("user warning"))
+                warnings.warn(RuntimeWarning("runtime warning"))
+                return 1
+        ''',
+        test_name: '''
+            import {module_name}
+            def test_func():
+                assert {module_name}.foo() == 1
+        '''.format(module_name=module_name)
+    })
+
+
+@pytest.mark.filterwarnings('always')
+def test_normal_flow(testdir, pyfile_with_warnings):
+    """
+    Check that the warnings section is displayed, containing test node ids followed by
+    all warnings generated by that test node.
+    """
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*== %s ==*' % WARNINGS_SUMMARY_HEADER,
+
+        '*test_normal_flow.py::test_func',
+
+        '*normal_flow_module.py:3: UserWarning: user warning',
+        '*  warnings.warn(UserWarning("user warning"))',
+
+        '*normal_flow_module.py:4: RuntimeWarning: runtime warning',
+        '*  warnings.warn(RuntimeWarning("runtime warning"))',
+        '* 1 passed, 2 warnings*',
+    ])
+    assert result.stdout.str().count('test_normal_flow.py::test_func') == 1
+
+
+@pytest.mark.filterwarnings('always')
+def test_setup_teardown_warnings(testdir, pyfile_with_warnings):
+    testdir.makepyfile('''
+        import warnings
+        import pytest
+
+        @pytest.fixture
+        def fix():
+            warnings.warn(UserWarning("warning during setup"))
+            yield
+            warnings.warn(UserWarning("warning during teardown"))
+
+        def test_func(fix):
+            pass
+    ''')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*== %s ==*' % WARNINGS_SUMMARY_HEADER,
+
+        '*test_setup_teardown_warnings.py:6: UserWarning: warning during setup',
+        '*warnings.warn(UserWarning("warning during setup"))',
+
+        '*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown',
+        '*warnings.warn(UserWarning("warning during teardown"))',
+        '* 1 passed, 2 warnings*',
+    ])
+
+
+@pytest.mark.parametrize('method', ['cmdline', 'ini'])
+def test_as_errors(testdir, pyfile_with_warnings, method):
+    args = ('-W', 'error') if method == 'cmdline' else ()
+    if method == 'ini':
+        testdir.makeini('''
+            [pytest]
+            filterwarnings= error
+            ''')
+    result = testdir.runpytest(*args)
+    result.stdout.fnmatch_lines([
+        'E       UserWarning: user warning',
+        'as_errors_module.py:3: UserWarning',
+        '* 1 failed in *',
+    ])
+
+
+@pytest.mark.parametrize('method', ['cmdline', 'ini'])
+def test_ignore(testdir, pyfile_with_warnings, method):
+    args = ('-W', 'ignore') if method == 'cmdline' else ()
+    if method == 'ini':
+        testdir.makeini('''
+        [pytest]
+        filterwarnings= ignore
+        ''')
+
+    result = testdir.runpytest(*args)
+    result.stdout.fnmatch_lines([
+        '* 1 passed in *',
+    ])
+    assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
+
+
+@pytest.mark.skipif(sys.version_info < (3, 0),
+                    reason='warnings message is unicode is ok in python3')
+@pytest.mark.filterwarnings('always')
+def test_unicode(testdir, pyfile_with_warnings):
+    testdir.makepyfile('''
+        # -*- coding: utf8 -*-
+        import warnings
+        import pytest
+
+
+        @pytest.fixture
+        def fix():
+            warnings.warn(u"测试")
+            yield
+
+        def test_func(fix):
+            pass
+    ''')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*== %s ==*' % WARNINGS_SUMMARY_HEADER,
+        '*test_unicode.py:8: UserWarning: \u6d4b\u8bd5*',
+        '* 1 passed, 1 warnings*',
+    ])
+
+
+@pytest.mark.skipif(sys.version_info >= (3, 0),
+                    reason='warnings message is broken as it is not str instance')
+def test_py2_unicode(testdir, pyfile_with_warnings):
+    if getattr(sys, "pypy_version_info", ())[:2] == (5, 9) and sys.platform.startswith('win'):
+        pytest.xfail("fails with unicode error on PyPy2 5.9 and Windows (#2905)")
+    testdir.makepyfile('''
+        # -*- coding: utf8 -*-
+        import warnings
+        import pytest
+
+
+        @pytest.fixture
+        def fix():
+            warnings.warn(u"测试")
+            yield
+
+        @pytest.mark.filterwarnings('always')
+        def test_func(fix):
+            pass
+    ''')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*== %s ==*' % WARNINGS_SUMMARY_HEADER,
+
+        '*test_py2_unicode.py:8: UserWarning: \\u6d4b\\u8bd5',
+        '*warnings.warn(u"\u6d4b\u8bd5")',
+        '*warnings.py:*: UnicodeWarning: Warning is using unicode non*',
+        '* 1 passed, 2 warnings*',
+    ])
+
+
+def test_py2_unicode_ascii(testdir):
+    """Ensure that our warning about 'unicode warnings containing non-ascii messages'
+    does not trigger with ascii-convertible messages"""
+    testdir.makeini('[pytest]')
+    testdir.makepyfile('''
+        import pytest
+        import warnings
+
+        @pytest.mark.filterwarnings('always')
+        def test_func():
+            warnings.warn(u"hello")
+    ''')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*== %s ==*' % WARNINGS_SUMMARY_HEADER,
+        '*warnings.warn(u"hello")',
+        '* 1 passed, 1 warnings in*'
+    ])
+
+
+def test_works_with_filterwarnings(testdir):
+    """Ensure our warnings capture does not mess with pre-installed filters (#2430)."""
+    testdir.makepyfile('''
+        import warnings
+
+        class MyWarning(Warning):
+            pass
+
+        warnings.filterwarnings("error", category=MyWarning)
+
+        class TestWarnings(object):
+            def test_my_warning(self):
+                try:
+                    warnings.warn(MyWarning("warn!"))
+                    assert False
+                except MyWarning:
+                    assert True
+    ''')
+    result = testdir.runpytest()
+    result.stdout.fnmatch_lines([
+        '*== 1 passed in *',
+    ])
+
+
+@pytest.mark.parametrize('default_config', ['ini', 'cmdline'])
+def test_filterwarnings_mark(testdir, default_config):
+    """
+    Test ``filterwarnings`` mark works and takes precedence over command line and ini options.
+    """
+    if default_config == 'ini':
+        testdir.makeini("""
+            [pytest]
+            filterwarnings = always
+        """)
+    testdir.makepyfile("""
+        import warnings
+        import pytest
+
+        @pytest.mark.filterwarnings('ignore::RuntimeWarning')
+        def test_ignore_runtime_warning():
+            warnings.warn(RuntimeWarning())
+
+        @pytest.mark.filterwarnings('error')
+        def test_warning_error():
+            warnings.warn(RuntimeWarning())
+
+        def test_show_warning():
+            warnings.warn(RuntimeWarning())
+    """)
+    result = testdir.runpytest('-W always' if default_config == 'cmdline' else '')
+    result.stdout.fnmatch_lines(['*= 1 failed, 2 passed, 1 warnings in *'])
+
+
+def test_non_string_warning_argument(testdir):
+    """Non-str argument passed to warning breaks pytest (#2956)"""
+    testdir.makepyfile("""
+        import warnings
+        import pytest
+
+        def test():
+            warnings.warn(UserWarning(1, u'foo'))
+    """)
+    result = testdir.runpytest('-W', 'always')
+    result.stdout.fnmatch_lines(['*= 1 passed, 1 warnings in *'])
diff --git a/tools/third_party/pytest/tox.ini b/tools/third_party/pytest/tox.ini
new file mode 100644
index 0000000..900b602
--- /dev/null
+++ b/tools/third_party/pytest/tox.ini
@@ -0,0 +1,220 @@
+[tox]
+minversion = 2.0
+distshare = {homedir}/.tox/distshare
+# make sure to update environment list in travis.yml and appveyor.yml
+envlist =
+    linting
+    py27
+    py34
+    py35
+    py36
+    py37
+    pypy
+    {py27,py36}-{pexpect,xdist,trial,numpy,pluggymaster}
+    py27-nobyte
+    doctesting
+    py35-freeze
+    docs
+
+[testenv]
+commands = pytest --lsof -ra {posargs:testing}
+passenv = USER USERNAME
+deps =
+    hypothesis>=3.5.2
+    nose
+    mock
+    requests
+
+[testenv:py27-subprocess]
+changedir = .
+deps =
+    pytest-xdist>=1.13
+    mock
+    nose
+commands =
+    pytest -n3 -ra --runpytest=subprocess {posargs:testing}
+
+
+[testenv:linting]
+skipsdist = True
+usedevelop = True
+basepython = python2.7
+deps =
+    flake8
+    # pygments required by rst-lint
+    pygments
+    restructuredtext_lint
+commands =
+    flake8 pytest.py _pytest testing setup.py pytest.py
+    {envpython} scripts/check-rst.py
+
+[testenv:py27-xdist]
+deps =
+    pytest-xdist>=1.13
+    mock
+    nose
+    hypothesis>=3.5.2
+changedir=testing
+commands =
+    pytest -n1 -ra {posargs:.}
+
+[testenv:py36-xdist]
+deps = {[testenv:py27-xdist]deps}
+commands =
+    pytest -n3 -ra {posargs:testing}
+
+[testenv:py27-pexpect]
+changedir = testing
+platform = linux|darwin
+deps = pexpect
+commands =
+    pytest -ra test_pdb.py test_terminal.py test_unittest.py
+
+[testenv:py36-pexpect]
+changedir = testing
+platform = linux|darwin
+deps = {[testenv:py27-pexpect]deps}
+commands =
+    pytest -ra test_pdb.py test_terminal.py test_unittest.py
+
+[testenv:py27-nobyte]
+deps =
+    pytest-xdist>=1.13
+    hypothesis>=3.5.2
+distribute = true
+changedir=testing
+setenv =
+    PYTHONDONTWRITEBYTECODE=1
+commands =
+    pytest -n3 -ra {posargs:.}
+
+[testenv:py27-trial]
+deps = twisted
+commands =
+    pytest -ra {posargs:testing/test_unittest.py}
+
+[testenv:py36-trial]
+deps = {[testenv:py27-trial]deps}
+commands =
+    pytest -ra {posargs:testing/test_unittest.py}
+
+[testenv:py27-numpy]
+deps=numpy
+commands=
+  pytest -ra {posargs:testing/python/approx.py}
+
+[testenv:py36-numpy]
+deps=numpy
+commands=
+  pytest -ra {posargs:testing/python/approx.py}
+
+[testenv:py27-pluggymaster]
+setenv=
+    _PYTEST_SETUP_SKIP_PLUGGY_DEP=1
+deps =
+    {[testenv]deps}
+    git+https://github.com/pytest-dev/pluggy.git@master
+
+[testenv:py35-pluggymaster]
+setenv=
+    _PYTEST_SETUP_SKIP_PLUGGY_DEP=1
+deps =
+    {[testenv:py27-pluggymaster]deps}
+    git+https://github.com/pytest-dev/pluggy.git@master
+
+[testenv:docs]
+skipsdist = True
+usedevelop = True
+basepython = python
+changedir = doc/en
+deps =
+    sphinx
+    PyYAML
+
+commands =
+    sphinx-build -W -b html . _build
+
+[testenv:doctesting]
+basepython = python
+usedevelop = True
+skipsdist = True
+# ensure the given pyargs cant mean anytrhing else
+changedir = doc/
+deps =
+    PyYAML
+commands =
+    pytest -ra en
+    pytest --doctest-modules --pyargs _pytest
+
+[testenv:regen]
+changedir = doc/en
+skipsdist = True
+basepython = python3.5
+deps =
+    sphinx
+    PyYAML
+    regendoc>=0.6.1
+whitelist_externals =
+    rm
+    make
+commands =
+    rm -rf /tmp/doc-exec*
+    make regen
+
+[testenv:fix-lint]
+skipsdist = True
+usedevelop = True
+deps =
+    autopep8
+commands =
+    autopep8 --in-place -r --max-line-length=120 --exclude=test_source_multiline_block.py _pytest testing setup.py pytest.py
+
+[testenv:jython]
+changedir = testing
+commands =
+    {envpython} {envbindir}/py.test-jython -ra {posargs}
+
+[testenv:py35-freeze]
+changedir = testing/freeze
+deps = pyinstaller
+commands =
+    {envpython} create_executable.py
+    {envpython} tox_run.py
+
+
+[testenv:coveralls]
+passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN
+usedevelop = True
+changedir = .
+deps =
+    {[testenv]deps}
+    coveralls
+commands =
+    coverage run --source=_pytest -m pytest testing
+    coverage report -m
+    coveralls
+
+[pytest]
+minversion = 2.0
+plugins = pytester
+#--pyargs --doctest-modules --ignore=.tox
+addopts = -ra -p pytester --ignore=testing/cx_freeze
+rsyncdirs = tox.ini pytest.py _pytest testing
+python_files = test_*.py *_test.py testing/*/*.py
+python_classes = Test Acceptance
+python_functions = test
+norecursedirs = .tox ja .hg cx_freeze_source
+xfail_strict=true
+filterwarnings =
+    error
+    # produced by path.local
+    ignore:bad escape.*:DeprecationWarning:re
+    # produced by path.readlines
+    ignore:.*U.*mode is deprecated:DeprecationWarning
+    # produced by pytest-xdist
+    ignore:.*type argument to addoption.*:DeprecationWarning
+    # produced by python >=3.5 on execnet (pytest-xdist)
+    ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning
+
+[flake8]
+max-line-length = 120
diff --git a/tools/tox.ini b/tools/tox.ini
index 3b97ae4..1db1b4e 100644
--- a/tools/tox.ini
+++ b/tools/tox.ini
@@ -3,8 +3,12 @@
 skipsdist=True
 
 [testenv]
+# flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
 deps =
-  flake8
+  flake8==3.5.0
+  pycodestyle==2.3.1
+  pyflakes==1.6.0
+  pep8-naming==0.4.1
   pytest
   pytest-cov
   mock
@@ -12,13 +16,35 @@
   pytest-catchlog
 
 commands =
-  pytest --cov
+  pytest --cov {posargs}
   flake8
 
 passenv =
   HYPOTHESIS_PROFILE
 
 [flake8]
-ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E901,F401,F821,F841
+# flake8 config should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
+select = E,W,F,N
+# E128: continuation line under-indented for visual indent
+# E129: visually indented line with same indent as next logical line
+# E221: multiple spaces before operator
+# E226: missing whitespace around arithmetic operator
+# E231: missing whitespace after ‘,’, ‘;’, or ‘:’
+# E251: unexpected spaces around keyword / parameter equals
+# E265: block comment should start with ‘# ‘
+# E302: expected 2 blank lines, found 0
+# E303: too many blank lines (3)
+# E305: expected 2 blank lines after end of function or class
+# E402: module level import not at top of file
+# E731: do not assign a lambda expression, use a def
+# E901: SyntaxError or IndentationError
+# W601: .has_key() is deprecated, use ‘in’
+# F401: module imported but unused
+# F403: ‘from module import *’ used; unable to detect undefined names
+# F405: name may be undefined, or defined from star imports: module
+# F841: local variable name is assigned to but never used
+# N801: class names should use CapWords convention
+# N802: function name should be lowercase
+ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E731,E901,W601,F401,F403,F405,F841,N801,N802
 max-line-length = 141
-exclude = .tox,html5lib,py,pytest,pywebsocket,six,_venv,webencodings,wptserve/docs,wptserve/tests/functional/docroot/,wpt,wptrunner
+exclude = .tox,html5lib,third_party/py,third_party/pytest,third_party/funcsigs,third_party/attrs,third_party/pluggy/,pywebsocket,six,_venv,webencodings,wptserve/docs,wptserve/tests/functional/docroot/,wpt,wptrunner
diff --git a/tools/webdriver/webdriver/client.py b/tools/webdriver/webdriver/client.py
index 4defb75..a8abda0 100644
--- a/tools/webdriver/webdriver/client.py
+++ b/tools/webdriver/webdriver/client.py
@@ -4,9 +4,7 @@
 import protocol
 import transport
 
-from mozlog import get_default_logger
-
-logger = get_default_logger()
+from six import string_types
 
 
 def command(func):
@@ -42,7 +40,7 @@
     def _set(self, key, secs):
         body = {key: secs * 1000}
         timeouts = self.session.send_session_command("POST", "timeouts", body)
-        return timeouts[key]
+        return None
 
     @property
     def script(self):
@@ -313,7 +311,7 @@
         cookie = {"name": name,
                   "value": None}
 
-        if isinstance(name, (str, unicode)):
+        if isinstance(name, string_types):
             cookie["value"] = value
         elif hasattr(value, "value"):
             cookie["value"] = value.value
@@ -369,6 +367,9 @@
         self.alert = UserPrompt(self)
         self.actions = Actions(self)
 
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.session_id or "(disconnected)")
+
     def __eq__(self, other):
         return (self.session_id is not None and isinstance(other, Session) and
                 self.session_id == other.session_id)
@@ -535,7 +536,12 @@
 
     @command
     def close(self):
-        return self.send_session_command("DELETE", "window")
+        handles = self.send_session_command("DELETE", "window")
+        if len(handles) == 0:
+            # With no more open top-level browsing contexts, the session is closed.
+            self.session_id = None
+
+        return handles
 
     @property
     @command
@@ -556,17 +562,23 @@
         return self.send_session_command("GET", url, {})
 
     @command
-    def set_cookie(self, name, value, path=None, domain=None, secure=None, expiry=None):
-        body = {"name": name,
-                "value": value}
-        if path is not None:
-            body["path"] = path
+    def set_cookie(self, name, value, path=None, domain=None,
+            secure=None, expiry=None, http_only=None):
+        body = {
+            "name": name,
+            "value": value,
+        }
+
         if domain is not None:
             body["domain"] = domain
-        if secure is not None:
-            body["secure"] = secure
         if expiry is not None:
             body["expiry"] = expiry
+        if http_only is not None:
+            body["httpOnly"] = http_only
+        if path is not None:
+            body["path"] = path
+        if secure is not None:
+            body["secure"] = secure
         self.send_session_command("POST", "cookie", {"cookie": body})
 
     def delete_cookie(self, name=None):
@@ -630,6 +642,9 @@
         assert id not in self.session._element_cache
         self.session._element_cache[self.id] = self
 
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.id)
+
     def __eq__(self, other):
         return (isinstance(other, Element) and self.id == other.id and
                 self.session == other.session)
diff --git a/tools/webdriver/webdriver/error.py b/tools/webdriver/webdriver/error.py
index 69f6e45..ecfe891 100644
--- a/tools/webdriver/webdriver/error.py
+++ b/tools/webdriver/webdriver/error.py
@@ -8,17 +8,25 @@
 
     def __init__(self, message, stacktrace=None):
         super(WebDriverException, self)
+        self.message = message
         self.stacktrace = stacktrace
 
     def __repr__(self):
-        return "<%s http_status=%d>" % (self.__class__.__name__, self.http_status)
+        return "<%s http_status=%s>" % (self.__class__.__name__, self.http_status)
 
     def __str__(self):
-        return ("%s (%d)\n"
-            "\n"
+        message = "%s (%s): %s\n" % (self.status_code, self.http_status, self.message)
+        if self.stacktrace:
+            message += ("\n"
             "Remote-end stacktrace:\n"
             "\n"
-            "%s" % (self.status_code, self.http_status, self.stacktrace))
+            "%s" % self.stacktrace)
+        return message
+
+
+class ElementClickInterceptedException(WebDriverException):
+    http_status = 400
+    status_code = "element click intercepted"
 
 
 class ElementNotSelectableException(WebDriverException):
diff --git a/tools/webdriver/webdriver/protocol.py b/tools/webdriver/webdriver/protocol.py
index 80f358c..ea0c793 100644
--- a/tools/webdriver/webdriver/protocol.py
+++ b/tools/webdriver/webdriver/protocol.py
@@ -16,7 +16,7 @@
             return [self.default(x) for x in obj]
         elif isinstance(obj, webdriver.Element):
             return {webdriver.Element.identifier: obj.id}
-        return super(ProtocolEncoder, self).default(obj)
+        return super(Encoder, self).default(obj)
 
 
 class Decoder(json.JSONDecoder):
diff --git a/tools/webdriver/webdriver/servo.py b/tools/webdriver/webdriver/servo.py
deleted file mode 100644
index 2e0b722..0000000
--- a/tools/webdriver/webdriver/servo.py
+++ /dev/null
@@ -1,18 +0,0 @@
-class ServoExtensionCommands(object):
-    def __init__(self, session):
-        self.session = session
-
-    @command
-    def get_prefs(self, *prefs):
-        body = {"prefs": list(prefs)}
-        return self.session.send_command("POST", "servo/prefs/get", body)
-
-    @command
-    def set_prefs(self, prefs):
-        body = {"prefs": prefs}
-        return self.session.send_command("POST", "servo/prefs/set", body)
-
-    @command
-    def reset_prefs(self, *prefs):
-        body = {"prefs": list(prefs)}
-        return self.session.send_command("POST", "servo/prefs/reset", body)
diff --git a/tools/webdriver/webdriver/transport.py b/tools/webdriver/webdriver/transport.py
index b198b19..960cb37 100644
--- a/tools/webdriver/webdriver/transport.py
+++ b/tools/webdriver/webdriver/transport.py
@@ -4,6 +4,7 @@
 
 import error
 
+from six import text_type
 
 """Implements HTTP transport for the WebDriver wire protocol."""
 
@@ -38,8 +39,8 @@
         try:
             body = json.load(http_response, cls=decoder, **kwargs)
         except ValueError:
-            raise ValueError("Failed to decode response body as JSON:\n"
-                "%s" % json.dumps(body, indent=2))
+            raise ValueError("Failed to decode response body as JSON:\n" +
+                http_response.read())
 
         return cls(http_response.status, body)
 
@@ -130,13 +131,16 @@
         if body is None and method == "POST":
             body = {}
 
-        try:
-            payload = json.dumps(body, cls=encoder, **codec_kwargs)
-        except ValueError:
-            raise ValueError("Failed to encode request body as JSON:\n"
-                "%s" % json.dumps(body, indent=2))
-        if isinstance(payload, unicode):
-            payload = body.encode("utf-8")
+        payload = None
+        if body is not None:
+            try:
+                payload = json.dumps(body, cls=encoder, **codec_kwargs)
+            except ValueError:
+                raise ValueError("Failed to encode request body as JSON:\n"
+                    "%s" % json.dumps(body, indent=2))
+
+            if isinstance(payload, text_type):
+                payload = body.encode("utf-8")
 
         if headers is None:
             headers = {}
diff --git a/tools/wpt/browser.py b/tools/wpt/browser.py
index 40d94d5..1932cf4 100644
--- a/tools/wpt/browser.py
+++ b/tools/wpt/browser.py
@@ -4,9 +4,14 @@
 import re
 import shutil
 import stat
+import subprocess
+import sys
+import tempfile
 from abc import ABCMeta, abstractmethod
 from ConfigParser import RawConfigParser
+from datetime import datetime, timedelta
 from distutils.spawn import find_executable
+from io import BytesIO
 
 from utils import call, get, untar, unzip
 
@@ -14,26 +19,38 @@
 
 uname = platform.uname()
 
-def path(path, exe):
-    path = path.replace("/", os.path.sep)
-    if exe and uname[0] == "Windows":
-        path += ".exe"
-    return path
-
 
 class Browser(object):
     __metaclass__ = ABCMeta
 
     @abstractmethod
     def install(self, dest=None):
+        """Install the browser."""
         return NotImplemented
 
     @abstractmethod
-    def install_webdriver(self):
+    def install_webdriver(self, dest=None):
+        """Install the WebDriver implementation for this browser."""
         return NotImplemented
 
     @abstractmethod
-    def version(self):
+    def find_binary(self):
+        """Find the binary of the browser.
+
+        If the WebDriver for the browser is able to find the binary itself, this
+        method doesn't need to be implemented, in which case NotImplementedError
+        is suggested to be raised to prevent accidental use.
+        """
+        return NotImplemented
+
+    @abstractmethod
+    def find_webdriver(self):
+        """Find the binary of the WebDriver."""
+        return NotImplemented
+
+    @abstractmethod
+    def version(self, binary=None):
+        """Retrieve the release version of the installed browser."""
         return NotImplemented
 
     @abstractmethod
@@ -41,12 +58,6 @@
         """Name of the browser-specific wptrunner requirements file"""
         return NotImplemented
 
-    def prepare_environment(self):
-        """Do any additional setup of the environment required to start the
-           browser successfully
-        """
-        pass
-
 
 class Firefox(Browser):
     """Firefox-specific interface.
@@ -55,30 +66,10 @@
     """
 
     product = "firefox"
-    binary = "firefox/firefox"
-    platform_ini = "firefox/platform.ini"
+    binary = "browsers/firefox/firefox"
+    platform_ini = "browsers/firefox/platform.ini"
     requirements = "requirements_firefox.txt"
 
-
-    def platform_string(self):
-        platform = {
-            "Linux": "linux",
-            "Windows": "win",
-            "Darwin": "mac"
-        }.get(uname[0])
-
-        if platform is None:
-            raise ValueError("Unable to construct a valid Firefox package name for current platform")
-
-        if platform == "linux":
-            bits = "-%s" % uname[4]
-        elif platform == "win":
-            bits = "64" if uname[4] == "x86_64" else "32"
-        else:
-            bits = ""
-
-        return "%s%s" % (platform, bits)
-
     def platform_string_geckodriver(self):
         platform = {
             "Linux": "linux",
@@ -96,26 +87,87 @@
 
         return "%s%s" % (platform, bits)
 
-    def latest_nightly_listing(self):
-        return get("https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/")
-
-    def get_from_nightly(self, pattern):
-        index = self.latest_nightly_listing()
-        filename = re.compile(pattern).search(index.text).group(1)
-        return get("https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/%s" %
-                   filename)
-
     def install(self, dest=None):
         """Install Firefox."""
+
+        from mozdownload import FactoryScraper
+        import mozinstall
+
+        platform = {
+            "Linux": "linux",
+            "Windows": "win",
+            "Darwin": "mac"
+        }.get(uname[0])
+
+        if platform is None:
+            raise ValueError("Unable to construct a valid Firefox package name for current platform")
+
         if dest is None:
-            dest = os.getcwd()
+            # os.getcwd() doesn't include the venv path
+            dest = os.path.join(os.getcwd(), "_venv")
 
-        resp = self.get_from_nightly("<a[^>]*>(firefox-\d+\.\d(?:\w\d)?.en-US.%s\.tar\.bz2)" % self.platform_string())
-        untar(resp.raw, dest=dest)
-        return find_executable("firefox", os.path.join(dest, "firefox"))
+        dest = os.path.join(dest, "browsers")
 
-    def find_binary(self, path=None):
-        return find_executable("firefox", path)
+        filename = FactoryScraper("daily", branch="mozilla-central", destination=dest).download()
+
+        try:
+            mozinstall.install(filename, dest)
+        except mozinstall.mozinstall.InstallError as e:
+            if platform == "mac" and os.path.exists(os.path.join(dest, "Firefox Nightly.app")):
+                # mozinstall will fail if nightly is already installed in the venv because
+                # mac installation uses shutil.copy_tree
+                mozinstall.uninstall(os.path.join(dest, "Firefox Nightly.app"))
+                mozinstall.install(filename, dest)
+            else:
+                raise
+
+        os.remove(filename)
+        return self.find_binary_path(dest)
+
+    def find_binary_path(self, path=None):
+        """Looks for the firefox binary in the virtual environment"""
+
+        platform = {
+            "Linux": "linux",
+            "Windows": "win",
+            "Darwin": "mac"
+        }.get(uname[0])
+
+        if path is None:
+            #os.getcwd() doesn't include the venv path
+            path = os.path.join(os.getcwd(), "_venv", "browsers")
+
+        binary = None
+
+        if platform == "linux":
+            binary = find_executable("firefox", os.path.join(path, "firefox"))
+        elif platform == "win":
+            import mozinstall
+            binary = mozinstall.get_binary(path, "firefox")
+        elif platform == "mac":
+            binary = find_executable("firefox", os.path.join(path, "Firefox Nightly.app", "Contents", "MacOS"))
+
+        return binary
+
+    def find_binary(self, venv_path=None):
+        if venv_path is None:
+            venv_path = os.path.join(os.getcwd(), "_venv")
+
+        binary = self.find_binary_path(os.path.join(venv_path, "browsers"))
+
+        if not binary and uname[0] == "Darwin":
+            macpaths = ["/Applications/FirefoxNightly.app/Contents/MacOS",
+                        os.path.expanduser("~/Applications/FirefoxNightly.app/Contents/MacOS"),
+                        "/Applications/Firefox Developer Edition.app/Contents/MacOS",
+                        os.path.expanduser("~/Applications/Firefox Developer Edition.app/Contents/MacOS"),
+                        "/Applications/Firefox.app/Contents/MacOS",
+                        os.path.expanduser("~/Applications/Firefox.app/Contents/MacOS")]
+            return find_executable("firefox", os.pathsep.join(macpaths))
+
+        if binary is None:
+            return find_executable("firefox")
+
+        return binary
 
     def find_certutil(self):
         path = find_executable("certutil")
@@ -128,36 +180,67 @@
     def find_webdriver(self):
         return find_executable("geckodriver")
 
-    def install_certutil(self, dest=None):
-        # TODO: this doesn't really work because it just gets the binary, and is missing the
-        # libnss3 library. Getting that means either downloading the corresponding Firefox
-        # and extracting the library (which is hard on mac becase DMG), or maybe downloading from
-        # nss's treeherder builds?
+    def get_version_and_channel(self, binary):
+        version_string = call(binary, "--version").strip()
+        m = re.match(r"Mozilla Firefox (\d+\.\d+(?:\.\d+)?)(a|b)?", version_string)
+        if not m:
+            return None, "nightly"
+        version, status = m.groups()
+        channel = {"a": "nightly", "b": "beta"}
+        return version, channel.get(status, "stable")
+
+    def get_profile_bundle_url(self, version, channel):
+        if channel == "stable":
+            repo = "https://hg.mozilla.org/releases/mozilla-release"
+            tag = "FIREFOX_%s_RELEASE" % version.replace(".", "_")
+        else:
+            repo = "https://hg.mozilla.org/mozilla-central"
+            if channel == "beta":
+                tag = "FIREFOX_%s_BETA" % version.split(".", 1)[0]
+            else:
+                # Always use tip as the tag for nightly; this isn't quite right
+                # but to do better we need the actual build revision, which we
+                # can get if we have an application.ini file
+                tag = "tip"
+
+        return "%s/archive/%s.zip/testing/profiles/" % (repo, tag)
+
+    def install_prefs(self, binary, dest=None):
+        version, channel = self.get_version_and_channel(binary)
         if dest is None:
             dest = os.pwd
 
-        # Don't create a path like bin/bin/certutil
-        split = os.path.split(dest)
-        if split[1] == "bin":
-            dest = split[0]
+        dest = os.path.join(dest, "profiles", channel, version)
+        have_cache = False
+        if os.path.exists(dest):
+            if channel != "nightly":
+                have_cache = True
+            else:
+                now = datetime.now()
+                have_cache = (datetime.fromtimestamp(os.stat(dest).st_mtime) >
+                              now - timedelta(days=1))
 
-        resp = self.get_from_nightly(
-            "<a[^>]*>(firefox-\d+\.\d(?:\w\d)?.en-US.%s\.common\.tests.zip)</a>" % self.platform_string())
-        bin_path = path("bin/certutil", exe=True)
-        unzip(resp.raw, dest=dest, limit=[bin_path])
-
-        return os.path.join(dest, bin_path)
-
-    def install_prefs(self, dest=None):
-        if dest is None:
-            dest = os.pwd
-
-        dest = os.path.join(dest, "profiles")
-        if not os.path.exists(dest):
+        # If we don't have a recent download, grab and extract the latest one
+        if not have_cache:
+            if os.path.exists(dest):
+                shutil.rmtree(dest)
             os.makedirs(dest)
-        with open(os.path.join(dest, "prefs_general.js"), "wb") as f:
-            resp = get("https://hg.mozilla.org/mozilla-central/raw-file/tip/testing/profiles/prefs_general.js")
-            f.write(resp.content)
+
+            url = self.get_profile_bundle_url(version, channel)
+
+            print("Installing test prefs from %s" % url)
+            try:
+                extract_dir = tempfile.mkdtemp()
+                unzip(get(url).raw, dest=extract_dir)
+
+                profiles = os.path.join(extract_dir, os.listdir(extract_dir)[0], 'testing', 'profiles')
+                for name in os.listdir(profiles):
+                    path = os.path.join(profiles, name)
+                    shutil.move(path, dest)
+            finally:
+                shutil.rmtree(extract_dir)
+        else:
+            print("Using cached test prefs from %s" % dest)
 
         return dest
 
@@ -193,27 +276,35 @@
             untar(get(url).raw, dest=dest)
         return find_executable(os.path.join(dest, "geckodriver"))
 
-    def version(self, root):
+    def version(self, binary=None):
         """Retrieve the release version of the installed browser."""
-        platform_info = RawConfigParser()
-
-        with open(os.path.join(root, self.platform_ini), "r") as fp:
-            platform_info.readfp(BytesIO(fp.read()))
-            return "BuildID %s; SourceStamp %s" % (
-                platform_info.get("Build", "BuildID"),
-                platform_info.get("Build", "SourceStamp"))
+        binary = binary or self.find_binary()
+        version_string = call(binary, "--version").strip()
+        m = re.match(r"Mozilla Firefox (.*)", version_string)
+        if not m:
+            return None
+        return m.group(1)
 
 
 class Chrome(Browser):
     """Chrome-specific interface.
 
-    Includes installation, webdriver installation, and wptrunner setup methods.
+    Includes webdriver installation, and wptrunner setup methods.
     """
 
     product = "chrome"
-    binary = "/usr/bin/google-chrome"
     requirements = "requirements_chrome.txt"
 
+    @property
+    def binary(self):
+        if uname[0] == "Linux":
+            return "/usr/bin/google-chrome"
+        if uname[0] == "Darwin":
+            return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+        # TODO Windows?
+        logger.warn("Unable to find the browser binary.")
+        return None
+
     def install(self, dest=None):
         raise NotImplementedError
 
@@ -236,11 +327,13 @@
 
         return "%s%s" % (platform, bits)
 
+    def find_binary(self):
+        raise NotImplementedError
+
     def find_webdriver(self):
         return find_executable("chromedriver")
 
     def install_webdriver(self, dest=None):
-        """Install latest Webdriver."""
         if dest is None:
             dest = os.pwd
         latest = get("http://chromedriver.storage.googleapis.com/LATEST_RELEASE").text.strip()
@@ -253,42 +346,63 @@
         os.chmod(path, st.st_mode | stat.S_IEXEC)
         return path
 
-    def version(self, root):
-        """Retrieve the release version of the installed browser."""
-        output = call(self.binary, "--version")
-        return re.search(r"[0-9\.]+( [a-z]+)?$", output.strip()).group(0)
+    def version(self, binary=None):
+        binary = binary or self.binary
+        try:
+            version_string = call(binary, "--version").strip()
+        except subprocess.CalledProcessError:
+            logger.warn("Failed to call %s", binary)
+            return None
+        m = re.match(r"Google Chrome (.*)", version_string)
+        if not m:
+            logger.warn("Failed to extract version from: s%", version_string)
+            return None
+        return m.group(1)
 
-    def prepare_environment(self):
-        # https://bugs.chromium.org/p/chromium/issues/detail?id=713947
-        logger.debug("DBUS_SESSION_BUS_ADDRESS %s" % os.environ.get("DBUS_SESSION_BUS_ADDRESS"))
-        if "DBUS_SESSION_BUS_ADDRESS" not in os.environ:
-            if find_executable("dbus-launch"):
-                logger.debug("Attempting to start dbus")
-                dbus_conf = subprocess.check_output(["dbus-launch"])
-                logger.debug(dbus_conf)
 
-                # From dbus-launch(1):
-                #
-                # > When dbus-launch prints bus information to standard output,
-                # > by default it is in a simple key-value pairs format.
-                for line in dbus_conf.strip().split("\n"):
-                    key, _, value = line.partition("=")
-                    os.environ[key] = value
-            else:
-                logger.critical("dbus not running and can't be started")
-                sys.exit(1)
+class ChromeAndroid(Browser):
+    """Chrome-specific interface for Android.
+
+    Includes webdriver installation.
+    """
+
+    product = "chrome_android"
+    requirements = "requirements_chrome_android.txt"
+
+    def install(self, dest=None):
+        raise NotImplementedError
+
+    def find_binary(self):
+        raise NotImplementedError
+
+    def find_webdriver(self):
+        return find_executable("chromedriver")
+
+    def install_webdriver(self, dest=None):
+        chrome = Chrome()
+        return chrome.install_webdriver(dest)
+
+    def version(self, binary):
+        return None
 
 
 class Opera(Browser):
     """Opera-specific interface.
 
-    Includes installation, webdriver installation, and wptrunner setup methods.
+    Includes webdriver installation, and wptrunner setup methods.
     """
 
     product = "opera"
-    binary = "/usr/bin/opera"
     requirements = "requirements_opera.txt"
 
+    @property
+    def binary(self):
+        if uname[0] == "Linux":
+            return "/usr/bin/opera"
+        # TODO Windows, Mac?
+        logger.warn("Unable to find the browser binary.")
+        return None
+
     def install(self, dest=None):
         raise NotImplementedError
 
@@ -311,11 +425,13 @@
 
         return "%s%s" % (platform, bits)
 
+    def find_binary(self):
+        raise NotImplementedError
+
     def find_webdriver(self):
         return find_executable("operadriver")
 
     def install_webdriver(self, dest=None):
-        """Install latest Webdriver."""
         if dest is None:
             dest = os.pwd
         latest = get("https://api.github.com/repos/operasoftware/operachromiumdriver/releases/latest").json()["tag_name"]
@@ -332,37 +448,19 @@
         os.chmod(path, st.st_mode | stat.S_IEXEC)
         return path
 
-    def version(self, root):
+    def version(self, binary):
         """Retrieve the release version of the installed browser."""
-        output = call(self.binary, "--version")
+        binary = binary or self.binary
+        try:
+            output = call(binary, "--version")
+        except subprocess.CalledProcessError:
+            logger.warn("Failed to call %s", binary)
+            return None
         return re.search(r"[0-9\.]+( [a-z]+)?$", output.strip()).group(0)
 
-    def prepare_environment(self):
-        # https://bugs.chromium.org/p/chromium/issues/detail?id=713947
-        logger.debug("DBUS_SESSION_BUS_ADDRESS %s" % os.environ.get("DBUS_SESSION_BUS_ADDRESS"))
-        if "DBUS_SESSION_BUS_ADDRESS" not in os.environ:
-            if find_executable("dbus-launch"):
-                logger.debug("Attempting to start dbus")
-                dbus_conf = subprocess.check_output(["dbus-launch"])
-                logger.debug(dbus_conf)
-
-                # From dbus-launch(1):
-                #
-                # > When dbus-launch prints bus information to standard output,
-                # > by default it is in a simple key-value pairs format.
-                for line in dbus_conf.strip().split("\n"):
-                    key, _, value = line.partition("=")
-                    os.environ[key] = value
-            else:
-                logger.critical("dbus not running and can't be started")
-                sys.exit(1)
-
 
 class Edge(Browser):
-    """Edge-specific interface.
-
-    Includes installation, webdriver installation, and wptrunner setup methods.
-    """
+    """Edge-specific interface."""
 
     product = "edge"
     requirements = "requirements_edge.txt"
@@ -370,22 +468,21 @@
     def install(self, dest=None):
         raise NotImplementedError
 
+    def find_binary(self):
+        raise NotImplementedError
+
     def find_webdriver(self):
         return find_executable("MicrosoftWebDriver")
 
     def install_webdriver(self, dest=None):
-        """Install latest Webdriver."""
         raise NotImplementedError
 
-    def version(self):
-        raise NotImplementedError
+    def version(self, binary):
+        return None
 
 
 class InternetExplorer(Browser):
-    """Internet Explorer-specific interface.
-
-    Includes installation, webdriver installation, and wptrunner setup methods.
-    """
+    """Internet Explorer-specific interface."""
 
     product = "ie"
     requirements = "requirements_ie.txt"
@@ -393,47 +490,99 @@
     def install(self, dest=None):
         raise NotImplementedError
 
+    def find_binary(self):
+        raise NotImplementedError
+
     def find_webdriver(self):
         return find_executable("IEDriverServer.exe")
 
     def install_webdriver(self, dest=None):
-        """Install latest Webdriver."""
         raise NotImplementedError
 
-    def version(self):
-        raise NotImplementedError
+    def version(self, binary):
+        return None
 
 
-class Servo(Browser):
-    """Servo-specific interface.
+class Safari(Browser):
+    """Safari-specific interface.
 
     Includes installation, webdriver installation, and wptrunner setup methods.
     """
 
-    product = "servo"
-    requirements = "requirements_servo.txt"
+    product = "safari"
+    requirements = "requirements_safari.txt"
 
     def install(self, dest=None):
         raise NotImplementedError
 
-    def find_binary(self, path=None):
+    def find_binary(self):
+        raise NotImplementedError
+
+    def find_webdriver(self):
+        return find_executable("safaridriver")
+
+    def install_webdriver(self):
+        raise NotImplementedError
+
+    def version(self, binary):
+        return None
+
+
+class Servo(Browser):
+    """Servo-specific interface."""
+
+    product = "servo"
+    requirements = "requirements_servo.txt"
+
+    def platform_components(self):
+        platform = {
+            "Linux": "linux",
+            "Windows": "win",
+            "Darwin": "mac"
+        }.get(uname[0])
+
+        if platform is None:
+            raise ValueError("Unable to construct a valid Servo package for current platform")
+
+        if platform == "linux":
+            extension = ".tar.gz"
+            decompress = untar
+        elif platform == "win" or platform == "mac":
+            raise ValueError("Unable to construct a valid Servo package for current platform")
+
+        return (platform, extension, decompress)
+
+    def install(self, dest=None):
+        """Install latest Browser Engine."""
+        if dest is None:
+            dest = os.pwd
+
+        platform, extension, decompress = self.platform_components()
+        url = "https://download.servo.org/nightly/%s/servo-latest%s" % (platform, extension)
+
+        decompress(get(url).raw, dest=dest)
+        path = find_executable("servo", os.path.join(dest, "servo"))
+        st = os.stat(path)
+        os.chmod(path, st.st_mode | stat.S_IEXEC)
+        return path
+
+    def find_binary(self):
         return find_executable("servo")
 
     def find_webdriver(self):
         return None
 
-    def install_webdriver(self):
+    def install_webdriver(self, dest=None):
         raise NotImplementedError
 
-    def version(self, root):
-        return None
+    def version(self, binary):
+        """Retrieve the release version of the installed browser."""
+        output = call(binary, "--version")
+        return re.search(r"[0-9\.]+( [a-z]+)?$", output.strip()).group(0)
 
 
 class Sauce(Browser):
-    """Sauce-specific interface.
-
-    Includes installation, webdriver installation, and wptrunner setup methods.
-    """
+    """Sauce-specific interface."""
 
     product = "sauce"
     requirements = "requirements_sauce.txt"
@@ -441,6 +590,28 @@
     def install(self, dest=None):
         raise NotImplementedError
 
+    def find_binary(self):
+        raise NotImplementedError
+
+    def find_webdriver(self):
+        raise NotImplementedError
+
+    def install_webdriver(self, dest=None):
+        raise NotImplementedError
+
+    def version(self, binary):
+        return None
+
+
+class WebKit(Browser):
+    """WebKit-specific interface."""
+
+    product = "webkit"
+    requirements = "requirements_webkit.txt"
+
+    def install(self, dest=None):
+        raise NotImplementedError
+
     def find_binary(self, path=None):
         return None
 
@@ -450,5 +621,5 @@
     def install_webdriver(self):
         raise NotImplementedError
 
-    def version(self, root):
+    def version(self, binary):
         return None
diff --git a/tools/wpt/commands.json b/tools/wpt/commands.json
index 2e4b96f..0ea6b25 100644
--- a/tools/wpt/commands.json
+++ b/tools/wpt/commands.json
@@ -1,10 +1,15 @@
 {
     "run": {"path": "run.py", "script": "run", "parser": "create_parser", "help": "Run tests in a browser",
             "virtualenv": true, "install": ["requests"], "requirements": ["../wptrunner/requirements.txt"]},
+    "update-expectations": {"path": "update.py", "script": "update_expectations",
+                            "parser": "create_parser_update", "help": "Update expectations files from raw logs.",
+                            "virtualenv": true, "install": ["requests"],
+                            "requirements": ["../wptrunner/requirements.txt"]},
     "files-changed": {"path": "testfiles.py", "script": "run_changed_files", "parser": "get_parser",
                       "help": "Get a list of files that have changed", "virtualenv": false},
     "tests-affected": {"path": "testfiles.py", "script": "run_tests_affected", "parser": "get_parser_affected",
                        "help": "Get a list of tests affected by changes", "virtualenv": false},
-    "install": {"path": "install.py", "script": "run", "parser": "get_parser", "help": "Install browser components"},
+    "install": {"path": "install.py", "script": "run", "parser": "get_parser", "help": "Install browser components",
+                "install": ["mozdownload", "mozinstall"]},
     "branch-point": {"path": "testfiles.py", "script": "display_branch_point", "parser": null, "help": "Print branch point from master", "virtualenv": false}
 }
diff --git a/tools/wpt/install.py b/tools/wpt/install.py
index 408744a..d779651 100644
--- a/tools/wpt/install.py
+++ b/tools/wpt/install.py
@@ -39,8 +39,3 @@
     subclass = getattr(browser, name.title())
     sys.stdout.write('Now installing %s %s...\n' % (name, component))
     getattr(subclass(), method)(dest=destination)
-
-
-if __name__ == '__main__':
-    args = parser.parse_args()
-    run(None, **vars(args))
diff --git a/tools/wpt/markdown.py b/tools/wpt/markdown.py
index 8701891..8b5ff80 100644
--- a/tools/wpt/markdown.py
+++ b/tools/wpt/markdown.py
@@ -38,18 +38,3 @@
     for row in data:
         log("|%s|" % "|".join(" %s" % row[i].ljust(max_widths[i] - 1) for i in cols))
     log("")
-
-
-def err_string(results_dict, iterations):
-    """Create and return string with errors from test run."""
-    rv = []
-    total_results = sum(results_dict.values())
-    for key, value in sorted(results_dict.items()):
-        rv.append("%s%s" %
-                  (key, ": %s/%s" % (value, iterations) if value != iterations else ""))
-    if total_results < iterations:
-        rv.append("MISSING: %s/%s" % (iterations - total_results, iterations))
-    rv = ", ".join(rv)
-    if is_inconsistent(results_dict, iterations):
-        rv = "**%s**" % rv
-    return rv
diff --git a/tools/wpt/requirements.txt b/tools/wpt/requirements.txt
index 7369cb8..271baf7 100644
--- a/tools/wpt/requirements.txt
+++ b/tools/wpt/requirements.txt
@@ -1 +1 @@
-requests==2.14.2
+requests==2.18.4
diff --git a/tools/wpt/run.py b/tools/wpt/run.py
index d1e3e49..2c8edbb 100644
--- a/tools/wpt/run.py
+++ b/tools/wpt/run.py
@@ -11,6 +11,8 @@
 sys.path.insert(0, os.path.abspath(os.path.join(wpt_root, "tools")))
 
 from . import browser, utils, virtualenv
+from ..serve import serve
+
 logger = None
 
 
@@ -94,18 +96,19 @@
 
 def check_environ(product):
     if product not in ("firefox", "servo"):
-        expected_hosts = ["web-platform.test",
-                          "www.web-platform.test",
-                          "www1.web-platform.test",
-                          "www2.web-platform.test",
-                          "xn--n8j6ds53lwwkrqhv28a.web-platform.test",
-                          "xn--lve-6lad.web-platform.test",
-                          "nonexistent-origin.web-platform.test"]
+        config = serve.load_config(os.path.join(wpt_root, "config.json"))
+        expected_hosts = set(config.domains_set)
+        is_windows = platform.uname()[0] == "Windows"
+
+        if is_windows:
+            expected_hosts.update(config.not_domains_set)
+
         missing_hosts = set(expected_hosts)
-        if platform.uname()[0] != "Windows":
-            hosts_path = "/etc/hosts"
-        else:
+        if is_windows:
             hosts_path = "C:\Windows\System32\drivers\etc\hosts"
+        else:
+            hosts_path = "/etc/hosts"
+
         with open(hosts_path, "r") as f:
             for line in f:
                 line = line.split("#", 1)[0].strip()
@@ -114,13 +117,17 @@
                 for host in hosts:
                     missing_hosts.discard(host)
             if missing_hosts:
-                raise WptrunError("""Missing hosts file configuration. Expected entries like:
+                if is_windows:
+                    message = """Missing hosts file configuration. Run
 
-%s
+python wpt make-hosts-file | Out-File %SystemRoot%\System32\drivers\etc\hosts -Encoding ascii -Append
 
-See README.md for more details.""" % "\n".join("%s\t%s" %
-                                               ("127.0.0.1" if "nonexistent" not in host else "0.0.0.0", host)
-                                               for host in expected_hosts))
+in PowerShell with Administrator privileges.""" % hosts_path
+                else:
+                    message = """Missing hosts file configuration. Run
+
+./wpt make-hosts-file | sudo tee -a %s""" % hosts_path
+                raise WptrunError(message)
 
 
 class BrowserSetup(object):
@@ -147,8 +154,10 @@
         if self.prompt_install(self.name):
             return self.browser.install(venv.path)
 
-    def setup(self, kwargs):
+    def install_requirements(self):
         self.venv.install_requirements(os.path.join(wpt_root, "tools", "wptrunner", self.browser.requirements))
+
+    def setup(self, kwargs):
         self.setup_kwargs(kwargs)
 
 
@@ -158,7 +167,7 @@
 
     def setup_kwargs(self, kwargs):
         if kwargs["binary"] is None:
-            binary = self.browser.find_binary()
+            binary = self.browser.find_binary(self.venv.path)
             if binary is None:
                 raise WptrunError("""Firefox binary not found on $PATH.
 
@@ -196,8 +205,7 @@
                 kwargs["test_types"].remove("wdspec")
 
         if kwargs["prefs_root"] is None:
-            print("Downloading gecko prefs")
-            prefs_root = self.browser.install_prefs(self.venv.path)
+            prefs_root = self.browser.install_prefs(kwargs["binary"], self.venv.path)
             kwargs["prefs_root"] = prefs_root
 
 
@@ -224,6 +232,29 @@
                 raise WptrunError("Unable to locate or install chromedriver binary")
 
 
+class ChromeAndroid(BrowserSetup):
+    name = "chrome_android"
+    browser_cls = browser.ChromeAndroid
+
+    def setup_kwargs(self, kwargs):
+        if kwargs["webdriver_binary"] is None:
+            webdriver_binary = self.browser.find_webdriver()
+
+            if webdriver_binary is None:
+                install = self.prompt_install("chromedriver")
+
+                if install:
+                    print("Downloading chromedriver")
+                    webdriver_binary = self.browser.install_webdriver(dest=self.venv.bin_path)
+            else:
+                print("Using webdriver binary %s" % webdriver_binary)
+
+            if webdriver_binary:
+                kwargs["webdriver_binary"] = webdriver_binary
+            else:
+                raise WptrunError("Unable to locate or install chromedriver binary")
+
+
 class Opera(BrowserSetup):
     name = "opera"
     browser_cls = browser.Opera
@@ -289,6 +320,23 @@
             kwargs["webdriver_binary"] = webdriver_binary
 
 
+class Safari(BrowserSetup):
+    name = "safari"
+    browser_cls = browser.Safari
+
+    def install(self, venv):
+        raise NotImplementedError
+
+    def setup_kwargs(self, kwargs):
+        if kwargs["webdriver_binary"] is None:
+            webdriver_binary = self.browser.find_webdriver()
+
+            if webdriver_binary is None:
+                raise WptrunError("Unable to locate safaridriver binary")
+
+            kwargs["webdriver_binary"] = webdriver_binary
+
+
 class Sauce(BrowserSetup):
     name = "sauce"
     browser_cls = browser.Sauce
@@ -307,7 +355,8 @@
     browser_cls = browser.Servo
 
     def install(self, venv):
-        raise NotImplementedError
+        if self.prompt_install(self.name):
+            return self.browser.install(venv.path)
 
     def setup_kwargs(self, kwargs):
         if kwargs["binary"] is None:
@@ -318,14 +367,28 @@
             kwargs["binary"] = binary
 
 
+class WebKit(BrowserSetup):
+    name = "webkit"
+    browser_cls = browser.WebKit
+
+    def install(self, venv):
+        raise NotImplementedError
+
+    def setup_kwargs(self, kwargs):
+        pass
+
+
 product_setup = {
     "firefox": Firefox,
     "chrome": Chrome,
+    "chrome_android": ChromeAndroid,
     "edge": Edge,
     "ie": InternetExplorer,
+    "safari": Safari,
     "servo": Servo,
     "sauce": Sauce,
     "opera": Opera,
+    "webkit": WebKit,
 }
 
 
@@ -350,6 +413,7 @@
         raise WptrunError("Unsupported product %s" % kwargs["product"])
 
     setup_cls = product_setup[kwargs["product"]](venv, prompt, sub_product)
+    setup_cls.install_requirements()
 
     if install:
         logger.info("Installing browser")
@@ -363,6 +427,7 @@
 
     venv.install_requirements(os.path.join(wptrunner_path, "requirements.txt"))
 
+    kwargs['browser_version'] = setup_cls.browser.version(kwargs.get("binary"))
     return kwargs
 
 
diff --git a/tools/wpt/testfiles.py b/tools/wpt/testfiles.py
index e6e673d..d7779fe 100644
--- a/tools/wpt/testfiles.py
+++ b/tools/wpt/testfiles.py
@@ -6,6 +6,8 @@
 import subprocess
 import sys
 
+from collections import OrderedDict
+
 from ..manifest import manifest, update
 
 here = os.path.dirname(__file__)
@@ -42,25 +44,45 @@
         # This is a PR, so the base branch is in TRAVIS_BRANCH
         travis_branch = os.environ.get("TRAVIS_BRANCH")
         assert travis_branch, "TRAVIS_BRANCH environment variable is defined"
-        branch_point = git("rev-parse", travis_branch)
+        branch_point = git("merge-base", "HEAD", travis_branch)
     else:
         # Otherwise we aren't on a PR, so we try to find commits that are only in the
         # current branch c.f.
         # http://stackoverflow.com/questions/13460152/find-first-ancestor-commit-in-another-branch
+
+        # parse HEAD into an object ref
         head = git("rev-parse", "HEAD")
-        not_heads = [item for item in git("rev-parse", "--not", "--all").split("\n")
-                     if item.strip() and head not in item]
-        commits = git("rev-list", "HEAD", *not_heads).split("\n")
+
+        # get everything in refs/heads and refs/remotes that doesn't include HEAD
+        not_heads = [item for item in git("rev-parse", "--not", "--branches", "--remotes").split("\n")
+                     if item != "^%s" % head]
+
+        # get all commits on HEAD but not reachable from anything in not_heads
+        commits = git("rev-list", "--topo-order", "--parents", "HEAD", *not_heads)
+        commit_parents = OrderedDict()
+        if commits:
+            for line in commits.split("\n"):
+                line_commits = line.split(" ")
+                commit_parents[line_commits[0]] = line_commits[1:]
+
         branch_point = None
-        if len(commits):
-            first_commit = commits[-1]
-            if first_commit:
-                branch_point = git("rev-parse", first_commit + "^")
+
+        # if there are any commits, take the first parent that is not in commits
+        for commit, parents in commit_parents.iteritems():
+            for parent in parents:
+                if parent not in commit_parents:
+                    branch_point = parent
+                    break
+
+            if branch_point:
+                break
+
+        # if we had any commits, we should now have a branch point
+        assert branch_point or not commit_parents
 
         # The above heuristic will fail in the following cases:
         #
-        # - The current branch has fallen behind the version retrieved via the above
-        #   `fetch` invocation
+        # - The current branch has fallen behind the remote version
         # - Changes on the current branch were rebased and therefore do not exist on any
         #   other branch. This will result in the selection of a commit that is earlier
         #   in the history than desired (as determined by calculating the later of the
diff --git a/tools/wpt/tests/latest_mozilla_central.txt b/tools/wpt/tests/latest_mozilla_central.txt
new file mode 100644
index 0000000..7078a36
--- /dev/null
+++ b/tools/wpt/tests/latest_mozilla_central.txt
@@ -0,0 +1,20834 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<title>Directory Listing: /pub/firefox/nightly/latest-mozilla-central/</title>
+	</head>
+	<body>
+		<h1>Index of /pub/firefox/nightly/latest-mozilla-central/</h1>
+		<table>
+			<tr>
+				<th>Type</th>
+				<th>Name</th>
+				<th>Size</th>
+				<th>Last Modified</th>
+			</tr>
+			
+			<tr>
+				<td>Dir</td>
+				<td><a href="/pub/firefox/nightly/">..</a></td>
+				<td></td>
+				<td></td>
+			</tr>
+			
+			
+			<tr>
+				<td>Dir</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/mar-tools/">mar-tools/</a></td>
+				<td></td>
+				<td></td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/Firefox%20Installer.en-US.exe">Firefox Installer.en-US.exe</a></td>
+				<td>269K</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/README">README</a></td>
+				<td>82</td>
+				<td>17-Nov-2015 10:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.langpack.xpi">firefox-57.0a1.en-US.langpack.xpi</a></td>
+				<td>424K</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.awsy.tests.zip">firefox-57.0a1.en-US.linux-i686.awsy.tests.zip</a></td>
+				<td>14K</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.checksums">firefox-57.0a1.en-US.linux-i686.checksums</a></td>
+				<td>8K</td>
+				<td>21-Sep-2017 12:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.checksums.asc">firefox-57.0a1.en-US.linux-i686.checksums.asc</a></td>
+				<td>836</td>
+				<td>21-Sep-2017 12:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.common.tests.zip">firefox-57.0a1.en-US.linux-i686.common.tests.zip</a></td>
+				<td>45M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.complete.mar">firefox-57.0a1.en-US.linux-i686.complete.mar</a></td>
+				<td>47M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.cppunittest.tests.zip">firefox-57.0a1.en-US.linux-i686.cppunittest.tests.zip</a></td>
+				<td>13M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.crashreporter-symbols.zip">firefox-57.0a1.en-US.linux-i686.crashreporter-symbols.zip</a></td>
+				<td>108M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.mochitest.tests.zip">firefox-57.0a1.en-US.linux-i686.mochitest.tests.zip</a></td>
+				<td>73M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.mozinfo.json">firefox-57.0a1.en-US.linux-i686.mozinfo.json</a></td>
+				<td>871</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.reftest.tests.zip">firefox-57.0a1.en-US.linux-i686.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.talos.tests.zip">firefox-57.0a1.en-US.linux-i686.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>21-Sep-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.tar.bz2">firefox-57.0a1.en-US.linux-i686.tar.bz2</a></td>
+				<td>60M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.tar.bz2.asc">firefox-57.0a1.en-US.linux-i686.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.test_packages.json">firefox-57.0a1.en-US.linux-i686.test_packages.json</a></td>
+				<td>1K</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.txt">firefox-57.0a1.en-US.linux-i686.txt</a></td>
+				<td>99</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.web-platform.tests.tar.gz">firefox-57.0a1.en-US.linux-i686.web-platform.tests.tar.gz</a></td>
+				<td>49M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686.xpcshell.tests.zip">firefox-57.0a1.en-US.linux-i686.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-i686_info.txt">firefox-57.0a1.en-US.linux-i686_info.txt</a></td>
+				<td>23</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.awsy.tests.zip">firefox-57.0a1.en-US.linux-x86_64.awsy.tests.zip</a></td>
+				<td>14K</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.checksums">firefox-57.0a1.en-US.linux-x86_64.checksums</a></td>
+				<td>8K</td>
+				<td>21-Sep-2017 12:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.checksums.asc">firefox-57.0a1.en-US.linux-x86_64.checksums.asc</a></td>
+				<td>836</td>
+				<td>21-Sep-2017 12:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.common.tests.zip">firefox-57.0a1.en-US.linux-x86_64.common.tests.zip</a></td>
+				<td>52M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.complete.mar">firefox-57.0a1.en-US.linux-x86_64.complete.mar</a></td>
+				<td>47M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.cppunittest.tests.zip">firefox-57.0a1.en-US.linux-x86_64.cppunittest.tests.zip</a></td>
+				<td>13M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.crashreporter-symbols.zip">firefox-57.0a1.en-US.linux-x86_64.crashreporter-symbols.zip</a></td>
+				<td>103M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.json">firefox-57.0a1.en-US.linux-x86_64.json</a></td>
+				<td>877</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.mochitest.tests.zip">firefox-57.0a1.en-US.linux-x86_64.mochitest.tests.zip</a></td>
+				<td>73M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.mozinfo.json">firefox-57.0a1.en-US.linux-x86_64.mozinfo.json</a></td>
+				<td>876</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.reftest.tests.zip">firefox-57.0a1.en-US.linux-x86_64.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.talos.tests.zip">firefox-57.0a1.en-US.linux-x86_64.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.tar.bz2">firefox-57.0a1.en-US.linux-x86_64.tar.bz2</a></td>
+				<td>59M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.tar.bz2.asc">firefox-57.0a1.en-US.linux-x86_64.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.test_packages.json">firefox-57.0a1.en-US.linux-x86_64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.txt">firefox-57.0a1.en-US.linux-x86_64.txt</a></td>
+				<td>99</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz">firefox-57.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz</a></td>
+				<td>49M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64.xpcshell.tests.zip">firefox-57.0a1.en-US.linux-x86_64.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.linux-x86_64_info.txt">firefox-57.0a1.en-US.linux-x86_64_info.txt</a></td>
+				<td>23</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.awsy.tests.zip">firefox-57.0a1.en-US.mac.awsy.tests.zip</a></td>
+				<td>14K</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.checksums">firefox-57.0a1.en-US.mac.checksums</a></td>
+				<td>7K</td>
+				<td>21-Sep-2017 11:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.checksums.asc">firefox-57.0a1.en-US.mac.checksums.asc</a></td>
+				<td>836</td>
+				<td>21-Sep-2017 11:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.common.tests.zip">firefox-57.0a1.en-US.mac.common.tests.zip</a></td>
+				<td>35M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.complete.mar">firefox-57.0a1.en-US.mac.complete.mar</a></td>
+				<td>46M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.cppunittest.tests.zip">firefox-57.0a1.en-US.mac.cppunittest.tests.zip</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.crashreporter-symbols.zip">firefox-57.0a1.en-US.mac.crashreporter-symbols.zip</a></td>
+				<td>118M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.dmg">firefox-57.0a1.en-US.mac.dmg</a></td>
+				<td>63M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.json">firefox-57.0a1.en-US.mac.json</a></td>
+				<td>1K</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.mochitest.tests.zip">firefox-57.0a1.en-US.mac.mochitest.tests.zip</a></td>
+				<td>72M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.mozinfo.json">firefox-57.0a1.en-US.mac.mozinfo.json</a></td>
+				<td>877</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.reftest.tests.zip">firefox-57.0a1.en-US.mac.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.talos.tests.zip">firefox-57.0a1.en-US.mac.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.test_packages.json">firefox-57.0a1.en-US.mac.test_packages.json</a></td>
+				<td>1K</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.txt">firefox-57.0a1.en-US.mac.txt</a></td>
+				<td>99</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.web-platform.tests.tar.gz">firefox-57.0a1.en-US.mac.web-platform.tests.tar.gz</a></td>
+				<td>49M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac.xpcshell.tests.zip">firefox-57.0a1.en-US.mac.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.mac_info.txt">firefox-57.0a1.en-US.mac_info.txt</a></td>
+				<td>23</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.awsy.tests.zip">firefox-57.0a1.en-US.win32.awsy.tests.zip</a></td>
+				<td>14K</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.checksums">firefox-57.0a1.en-US.win32.checksums</a></td>
+				<td>8K</td>
+				<td>21-Sep-2017 13:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.checksums.asc">firefox-57.0a1.en-US.win32.checksums.asc</a></td>
+				<td>836</td>
+				<td>21-Sep-2017 13:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.common.tests.zip">firefox-57.0a1.en-US.win32.common.tests.zip</a></td>
+				<td>38M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.complete.mar">firefox-57.0a1.en-US.win32.complete.mar</a></td>
+				<td>38M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.cppunittest.tests.zip">firefox-57.0a1.en-US.win32.cppunittest.tests.zip</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.crashreporter-symbols.zip">firefox-57.0a1.en-US.win32.crashreporter-symbols.zip</a></td>
+				<td>39M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.installer-stub.exe">firefox-57.0a1.en-US.win32.installer-stub.exe</a></td>
+				<td>288K</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.installer.exe">firefox-57.0a1.en-US.win32.installer.exe</a></td>
+				<td>36M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.json">firefox-57.0a1.en-US.win32.json</a></td>
+				<td>832</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.mochitest.tests.zip">firefox-57.0a1.en-US.win32.mochitest.tests.zip</a></td>
+				<td>72M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.mozinfo.json">firefox-57.0a1.en-US.win32.mozinfo.json</a></td>
+				<td>844</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.reftest.tests.zip">firefox-57.0a1.en-US.win32.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.talos.tests.zip">firefox-57.0a1.en-US.win32.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.test_packages.json">firefox-57.0a1.en-US.win32.test_packages.json</a></td>
+				<td>1K</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.txt">firefox-57.0a1.en-US.win32.txt</a></td>
+				<td>100</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.web-platform.tests.tar.gz">firefox-57.0a1.en-US.win32.web-platform.tests.tar.gz</a></td>
+				<td>49M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.xpcshell.tests.zip">firefox-57.0a1.en-US.win32.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32.zip">firefox-57.0a1.en-US.win32.zip</a></td>
+				<td>52M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win32_info.txt">firefox-57.0a1.en-US.win32_info.txt</a></td>
+				<td>23</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.awsy.tests.zip">firefox-57.0a1.en-US.win64.awsy.tests.zip</a></td>
+				<td>14K</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.checksums">firefox-57.0a1.en-US.win64.checksums</a></td>
+				<td>7K</td>
+				<td>21-Sep-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.checksums.asc">firefox-57.0a1.en-US.win64.checksums.asc</a></td>
+				<td>836</td>
+				<td>21-Sep-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.common.tests.zip">firefox-57.0a1.en-US.win64.common.tests.zip</a></td>
+				<td>38M</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.complete.mar">firefox-57.0a1.en-US.win64.complete.mar</a></td>
+				<td>41M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.cppunittest.tests.zip">firefox-57.0a1.en-US.win64.cppunittest.tests.zip</a></td>
+				<td>9M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.crashreporter-symbols.zip">firefox-57.0a1.en-US.win64.crashreporter-symbols.zip</a></td>
+				<td>34M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.installer.exe">firefox-57.0a1.en-US.win64.installer.exe</a></td>
+				<td>38M</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.json">firefox-57.0a1.en-US.win64.json</a></td>
+				<td>834</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.mochitest.tests.zip">firefox-57.0a1.en-US.win64.mochitest.tests.zip</a></td>
+				<td>72M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.mozinfo.json">firefox-57.0a1.en-US.win64.mozinfo.json</a></td>
+				<td>847</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.reftest.tests.zip">firefox-57.0a1.en-US.win64.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.talos.tests.zip">firefox-57.0a1.en-US.win64.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.test_packages.json">firefox-57.0a1.en-US.win64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.txt">firefox-57.0a1.en-US.win64.txt</a></td>
+				<td>100</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.web-platform.tests.tar.gz">firefox-57.0a1.en-US.win64.web-platform.tests.tar.gz</a></td>
+				<td>49M</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.xpcshell.tests.zip">firefox-57.0a1.en-US.win64.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64.zip">firefox-57.0a1.en-US.win64.zip</a></td>
+				<td>56M</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-57.0a1.en-US.win64_info.txt">firefox-57.0a1.en-US.win64_info.txt</a></td>
+				<td>23</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.langpack.xpi">firefox-58.0a1.en-US.langpack.xpi</a></td>
+				<td>433K</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.awsy.tests.zip">firefox-58.0a1.en-US.linux-i686.awsy.tests.zip</a></td>
+				<td>16K</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.checksums">firefox-58.0a1.en-US.linux-i686.checksums</a></td>
+				<td>8K</td>
+				<td>13-Nov-2017 00:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.checksums.asc">firefox-58.0a1.en-US.linux-i686.checksums.asc</a></td>
+				<td>836</td>
+				<td>13-Nov-2017 00:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.common.tests.zip">firefox-58.0a1.en-US.linux-i686.common.tests.zip</a></td>
+				<td>47M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.complete.mar">firefox-58.0a1.en-US.linux-i686.complete.mar</a></td>
+				<td>43M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.cppunittest.tests.zip">firefox-58.0a1.en-US.linux-i686.cppunittest.tests.zip</a></td>
+				<td>11M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.crashreporter-symbols.zip">firefox-58.0a1.en-US.linux-i686.crashreporter-symbols.zip</a></td>
+				<td>121M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.json">firefox-58.0a1.en-US.linux-i686.json</a></td>
+				<td>911</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.mochitest.tests.zip">firefox-58.0a1.en-US.linux-i686.mochitest.tests.zip</a></td>
+				<td>73M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.mozinfo.json">firefox-58.0a1.en-US.linux-i686.mozinfo.json</a></td>
+				<td>871</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.reftest.tests.zip">firefox-58.0a1.en-US.linux-i686.reftest.tests.zip</a></td>
+				<td>57M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.talos.tests.zip">firefox-58.0a1.en-US.linux-i686.talos.tests.zip</a></td>
+				<td>17M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.tar.bz2">firefox-58.0a1.en-US.linux-i686.tar.bz2</a></td>
+				<td>56M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.tar.bz2.asc">firefox-58.0a1.en-US.linux-i686.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.test_packages.json">firefox-58.0a1.en-US.linux-i686.test_packages.json</a></td>
+				<td>1K</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.txt">firefox-58.0a1.en-US.linux-i686.txt</a></td>
+				<td>99</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.web-platform.tests.tar.gz">firefox-58.0a1.en-US.linux-i686.web-platform.tests.tar.gz</a></td>
+				<td>46M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686.xpcshell.tests.zip">firefox-58.0a1.en-US.linux-i686.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-i686_info.txt">firefox-58.0a1.en-US.linux-i686_info.txt</a></td>
+				<td>23</td>
+				<td>13-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.awsy.tests.zip">firefox-58.0a1.en-US.linux-x86_64.awsy.tests.zip</a></td>
+				<td>16K</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.checksums">firefox-58.0a1.en-US.linux-x86_64.checksums</a></td>
+				<td>8K</td>
+				<td>13-Nov-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.checksums.asc">firefox-58.0a1.en-US.linux-x86_64.checksums.asc</a></td>
+				<td>836</td>
+				<td>13-Nov-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.common.tests.zip">firefox-58.0a1.en-US.linux-x86_64.common.tests.zip</a></td>
+				<td>55M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.complete.mar">firefox-58.0a1.en-US.linux-x86_64.complete.mar</a></td>
+				<td>47M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.cppunittest.tests.zip">firefox-58.0a1.en-US.linux-x86_64.cppunittest.tests.zip</a></td>
+				<td>13M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.crashreporter-symbols.zip">firefox-58.0a1.en-US.linux-x86_64.crashreporter-symbols.zip</a></td>
+				<td>105M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.json">firefox-58.0a1.en-US.linux-x86_64.json</a></td>
+				<td>877</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.mochitest.tests.zip">firefox-58.0a1.en-US.linux-x86_64.mochitest.tests.zip</a></td>
+				<td>73M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.mozinfo.json">firefox-58.0a1.en-US.linux-x86_64.mozinfo.json</a></td>
+				<td>876</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.reftest.tests.zip">firefox-58.0a1.en-US.linux-x86_64.reftest.tests.zip</a></td>
+				<td>57M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.talos.tests.zip">firefox-58.0a1.en-US.linux-x86_64.talos.tests.zip</a></td>
+				<td>17M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.tar.bz2">firefox-58.0a1.en-US.linux-x86_64.tar.bz2</a></td>
+				<td>60M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.tar.bz2.asc">firefox-58.0a1.en-US.linux-x86_64.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.test_packages.json">firefox-58.0a1.en-US.linux-x86_64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.txt">firefox-58.0a1.en-US.linux-x86_64.txt</a></td>
+				<td>99</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz">firefox-58.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz</a></td>
+				<td>46M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64.xpcshell.tests.zip">firefox-58.0a1.en-US.linux-x86_64.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.linux-x86_64_info.txt">firefox-58.0a1.en-US.linux-x86_64_info.txt</a></td>
+				<td>23</td>
+				<td>13-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.awsy.tests.zip">firefox-58.0a1.en-US.mac.awsy.tests.zip</a></td>
+				<td>16K</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.checksums">firefox-58.0a1.en-US.mac.checksums</a></td>
+				<td>7K</td>
+				<td>12-Nov-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.checksums.asc">firefox-58.0a1.en-US.mac.checksums.asc</a></td>
+				<td>836</td>
+				<td>12-Nov-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.common.tests.zip">firefox-58.0a1.en-US.mac.common.tests.zip</a></td>
+				<td>36M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.complete.mar">firefox-58.0a1.en-US.mac.complete.mar</a></td>
+				<td>47M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.cppunittest.tests.zip">firefox-58.0a1.en-US.mac.cppunittest.tests.zip</a></td>
+				<td>8M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.crashreporter-symbols.zip">firefox-58.0a1.en-US.mac.crashreporter-symbols.zip</a></td>
+				<td>118M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.dmg">firefox-58.0a1.en-US.mac.dmg</a></td>
+				<td>63M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.json">firefox-58.0a1.en-US.mac.json</a></td>
+				<td>1K</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.mochitest.tests.zip">firefox-58.0a1.en-US.mac.mochitest.tests.zip</a></td>
+				<td>72M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.mozinfo.json">firefox-58.0a1.en-US.mac.mozinfo.json</a></td>
+				<td>877</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.reftest.tests.zip">firefox-58.0a1.en-US.mac.reftest.tests.zip</a></td>
+				<td>57M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.talos.tests.zip">firefox-58.0a1.en-US.mac.talos.tests.zip</a></td>
+				<td>17M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.test_packages.json">firefox-58.0a1.en-US.mac.test_packages.json</a></td>
+				<td>1K</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.txt">firefox-58.0a1.en-US.mac.txt</a></td>
+				<td>99</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.web-platform.tests.tar.gz">firefox-58.0a1.en-US.mac.web-platform.tests.tar.gz</a></td>
+				<td>46M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac.xpcshell.tests.zip">firefox-58.0a1.en-US.mac.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.mac_info.txt">firefox-58.0a1.en-US.mac_info.txt</a></td>
+				<td>23</td>
+				<td>12-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.awsy.tests.zip">firefox-58.0a1.en-US.win32.awsy.tests.zip</a></td>
+				<td>16K</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.checksums">firefox-58.0a1.en-US.win32.checksums</a></td>
+				<td>8K</td>
+				<td>13-Nov-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.checksums.asc">firefox-58.0a1.en-US.win32.checksums.asc</a></td>
+				<td>836</td>
+				<td>13-Nov-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.common.tests.zip">firefox-58.0a1.en-US.win32.common.tests.zip</a></td>
+				<td>38M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.complete.mar">firefox-58.0a1.en-US.win32.complete.mar</a></td>
+				<td>39M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.cppunittest.tests.zip">firefox-58.0a1.en-US.win32.cppunittest.tests.zip</a></td>
+				<td>8M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.crashreporter-symbols.zip">firefox-58.0a1.en-US.win32.crashreporter-symbols.zip</a></td>
+				<td>39M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.installer-stub.exe">firefox-58.0a1.en-US.win32.installer-stub.exe</a></td>
+				<td>269K</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.installer.exe">firefox-58.0a1.en-US.win32.installer.exe</a></td>
+				<td>37M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.json">firefox-58.0a1.en-US.win32.json</a></td>
+				<td>846</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.mochitest.tests.zip">firefox-58.0a1.en-US.win32.mochitest.tests.zip</a></td>
+				<td>72M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.mozinfo.json">firefox-58.0a1.en-US.win32.mozinfo.json</a></td>
+				<td>844</td>
+				<td>13-Nov-2017 00:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.reftest.tests.zip">firefox-58.0a1.en-US.win32.reftest.tests.zip</a></td>
+				<td>57M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.talos.tests.zip">firefox-58.0a1.en-US.win32.talos.tests.zip</a></td>
+				<td>17M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.test_packages.json">firefox-58.0a1.en-US.win32.test_packages.json</a></td>
+				<td>1K</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.txt">firefox-58.0a1.en-US.win32.txt</a></td>
+				<td>100</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.web-platform.tests.tar.gz">firefox-58.0a1.en-US.win32.web-platform.tests.tar.gz</a></td>
+				<td>46M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.xpcshell.tests.zip">firefox-58.0a1.en-US.win32.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32.zip">firefox-58.0a1.en-US.win32.zip</a></td>
+				<td>54M</td>
+				<td>13-Nov-2017 00:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win32_info.txt">firefox-58.0a1.en-US.win32_info.txt</a></td>
+				<td>23</td>
+				<td>13-Nov-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.awsy.tests.zip">firefox-58.0a1.en-US.win64.awsy.tests.zip</a></td>
+				<td>16K</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.checksums">firefox-58.0a1.en-US.win64.checksums</a></td>
+				<td>7K</td>
+				<td>13-Nov-2017 00:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.checksums.asc">firefox-58.0a1.en-US.win64.checksums.asc</a></td>
+				<td>836</td>
+				<td>13-Nov-2017 00:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.common.tests.zip">firefox-58.0a1.en-US.win64.common.tests.zip</a></td>
+				<td>38M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.complete.mar">firefox-58.0a1.en-US.win64.complete.mar</a></td>
+				<td>42M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.cppunittest.tests.zip">firefox-58.0a1.en-US.win64.cppunittest.tests.zip</a></td>
+				<td>9M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.crashreporter-symbols.zip">firefox-58.0a1.en-US.win64.crashreporter-symbols.zip</a></td>
+				<td>34M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.installer.exe">firefox-58.0a1.en-US.win64.installer.exe</a></td>
+				<td>39M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.json">firefox-58.0a1.en-US.win64.json</a></td>
+				<td>856</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.mochitest.tests.zip">firefox-58.0a1.en-US.win64.mochitest.tests.zip</a></td>
+				<td>72M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.mozinfo.json">firefox-58.0a1.en-US.win64.mozinfo.json</a></td>
+				<td>847</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.reftest.tests.zip">firefox-58.0a1.en-US.win64.reftest.tests.zip</a></td>
+				<td>57M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.talos.tests.zip">firefox-58.0a1.en-US.win64.talos.tests.zip</a></td>
+				<td>17M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.test_packages.json">firefox-58.0a1.en-US.win64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.txt">firefox-58.0a1.en-US.win64.txt</a></td>
+				<td>100</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.web-platform.tests.tar.gz">firefox-58.0a1.en-US.win64.web-platform.tests.tar.gz</a></td>
+				<td>46M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.xpcshell.tests.zip">firefox-58.0a1.en-US.win64.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64.zip">firefox-58.0a1.en-US.win64.zip</a></td>
+				<td>58M</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-58.0a1.en-US.win64_info.txt">firefox-58.0a1.en-US.win64_info.txt</a></td>
+				<td>23</td>
+				<td>13-Nov-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.langpack.xpi">firefox-59.0a1.en-US.langpack.xpi</a></td>
+				<td>431K</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.awsy.tests.zip">firefox-59.0a1.en-US.linux-i686.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.checksums">firefox-59.0a1.en-US.linux-i686.checksums</a></td>
+				<td>8K</td>
+				<td>22-Jan-2018 12:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.checksums.asc">firefox-59.0a1.en-US.linux-i686.checksums.asc</a></td>
+				<td>836</td>
+				<td>22-Jan-2018 12:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.common.tests.zip">firefox-59.0a1.en-US.linux-i686.common.tests.zip</a></td>
+				<td>54M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.complete.mar">firefox-59.0a1.en-US.linux-i686.complete.mar</a></td>
+				<td>44M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.cppunittest.tests.zip">firefox-59.0a1.en-US.linux-i686.cppunittest.tests.zip</a></td>
+				<td>11M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.crashreporter-symbols.zip">firefox-59.0a1.en-US.linux-i686.crashreporter-symbols.zip</a></td>
+				<td>104M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.json">firefox-59.0a1.en-US.linux-i686.json</a></td>
+				<td>866</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.mochitest.tests.zip">firefox-59.0a1.en-US.linux-i686.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.mozinfo.json">firefox-59.0a1.en-US.linux-i686.mozinfo.json</a></td>
+				<td>897</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.reftest.tests.zip">firefox-59.0a1.en-US.linux-i686.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.talos.tests.zip">firefox-59.0a1.en-US.linux-i686.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.tar.bz2">firefox-59.0a1.en-US.linux-i686.tar.bz2</a></td>
+				<td>56M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.tar.bz2.asc">firefox-59.0a1.en-US.linux-i686.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.test_packages.json">firefox-59.0a1.en-US.linux-i686.test_packages.json</a></td>
+				<td>1K</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.txt">firefox-59.0a1.en-US.linux-i686.txt</a></td>
+				<td>99</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.web-platform.tests.tar.gz">firefox-59.0a1.en-US.linux-i686.web-platform.tests.tar.gz</a></td>
+				<td>47M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686.xpcshell.tests.zip">firefox-59.0a1.en-US.linux-i686.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-i686_info.txt">firefox-59.0a1.en-US.linux-i686_info.txt</a></td>
+				<td>23</td>
+				<td>22-Jan-2018 11:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.awsy.tests.zip">firefox-59.0a1.en-US.linux-x86_64.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.checksums">firefox-59.0a1.en-US.linux-x86_64.checksums</a></td>
+				<td>8K</td>
+				<td>22-Jan-2018 11:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.checksums.asc">firefox-59.0a1.en-US.linux-x86_64.checksums.asc</a></td>
+				<td>836</td>
+				<td>22-Jan-2018 11:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.common.tests.zip">firefox-59.0a1.en-US.linux-x86_64.common.tests.zip</a></td>
+				<td>55M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.complete.mar">firefox-59.0a1.en-US.linux-x86_64.complete.mar</a></td>
+				<td>48M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.cppunittest.tests.zip">firefox-59.0a1.en-US.linux-x86_64.cppunittest.tests.zip</a></td>
+				<td>13M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.crashreporter-symbols.zip">firefox-59.0a1.en-US.linux-x86_64.crashreporter-symbols.zip</a></td>
+				<td>91M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.json">firefox-59.0a1.en-US.linux-x86_64.json</a></td>
+				<td>832</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.mochitest.tests.zip">firefox-59.0a1.en-US.linux-x86_64.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.mozinfo.json">firefox-59.0a1.en-US.linux-x86_64.mozinfo.json</a></td>
+				<td>902</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.reftest.tests.zip">firefox-59.0a1.en-US.linux-x86_64.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.talos.tests.zip">firefox-59.0a1.en-US.linux-x86_64.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.tar.bz2">firefox-59.0a1.en-US.linux-x86_64.tar.bz2</a></td>
+				<td>60M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.tar.bz2.asc">firefox-59.0a1.en-US.linux-x86_64.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.test_packages.json">firefox-59.0a1.en-US.linux-x86_64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.txt">firefox-59.0a1.en-US.linux-x86_64.txt</a></td>
+				<td>99</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz">firefox-59.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz</a></td>
+				<td>47M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64.xpcshell.tests.zip">firefox-59.0a1.en-US.linux-x86_64.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.linux-x86_64_info.txt">firefox-59.0a1.en-US.linux-x86_64_info.txt</a></td>
+				<td>23</td>
+				<td>22-Jan-2018 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.awsy.tests.zip">firefox-59.0a1.en-US.mac.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.checksums">firefox-59.0a1.en-US.mac.checksums</a></td>
+				<td>7K</td>
+				<td>22-Jan-2018 11:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.checksums.asc">firefox-59.0a1.en-US.mac.checksums.asc</a></td>
+				<td>836</td>
+				<td>22-Jan-2018 11:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.common.tests.zip">firefox-59.0a1.en-US.mac.common.tests.zip</a></td>
+				<td>34M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.complete.mar">firefox-59.0a1.en-US.mac.complete.mar</a></td>
+				<td>47M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.cppunittest.tests.zip">firefox-59.0a1.en-US.mac.cppunittest.tests.zip</a></td>
+				<td>9M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.crashreporter-symbols.zip">firefox-59.0a1.en-US.mac.crashreporter-symbols.zip</a></td>
+				<td>102M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.dmg">firefox-59.0a1.en-US.mac.dmg</a></td>
+				<td>64M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.json">firefox-59.0a1.en-US.mac.json</a></td>
+				<td>1K</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.mochitest.tests.zip">firefox-59.0a1.en-US.mac.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.mozinfo.json">firefox-59.0a1.en-US.mac.mozinfo.json</a></td>
+				<td>903</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.reftest.tests.zip">firefox-59.0a1.en-US.mac.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.talos.tests.zip">firefox-59.0a1.en-US.mac.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.test_packages.json">firefox-59.0a1.en-US.mac.test_packages.json</a></td>
+				<td>1K</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.txt">firefox-59.0a1.en-US.mac.txt</a></td>
+				<td>99</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.web-platform.tests.tar.gz">firefox-59.0a1.en-US.mac.web-platform.tests.tar.gz</a></td>
+				<td>47M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac.xpcshell.tests.zip">firefox-59.0a1.en-US.mac.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.mac_info.txt">firefox-59.0a1.en-US.mac_info.txt</a></td>
+				<td>23</td>
+				<td>22-Jan-2018 11:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.awsy.tests.zip">firefox-59.0a1.en-US.win32.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.checksums">firefox-59.0a1.en-US.win32.checksums</a></td>
+				<td>8K</td>
+				<td>22-Jan-2018 12:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.checksums.asc">firefox-59.0a1.en-US.win32.checksums.asc</a></td>
+				<td>836</td>
+				<td>22-Jan-2018 12:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.common.tests.zip">firefox-59.0a1.en-US.win32.common.tests.zip</a></td>
+				<td>36M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.complete.mar">firefox-59.0a1.en-US.win32.complete.mar</a></td>
+				<td>40M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.cppunittest.tests.zip">firefox-59.0a1.en-US.win32.cppunittest.tests.zip</a></td>
+				<td>8M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.crashreporter-symbols.zip">firefox-59.0a1.en-US.win32.crashreporter-symbols.zip</a></td>
+				<td>32M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.installer-stub.exe">firefox-59.0a1.en-US.win32.installer-stub.exe</a></td>
+				<td>269K</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.installer.exe">firefox-59.0a1.en-US.win32.installer.exe</a></td>
+				<td>37M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.json">firefox-59.0a1.en-US.win32.json</a></td>
+				<td>846</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.mochitest.tests.zip">firefox-59.0a1.en-US.win32.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.mozinfo.json">firefox-59.0a1.en-US.win32.mozinfo.json</a></td>
+				<td>870</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.reftest.tests.zip">firefox-59.0a1.en-US.win32.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.talos.tests.zip">firefox-59.0a1.en-US.win32.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.test_packages.json">firefox-59.0a1.en-US.win32.test_packages.json</a></td>
+				<td>1K</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.txt">firefox-59.0a1.en-US.win32.txt</a></td>
+				<td>99</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.web-platform.tests.tar.gz">firefox-59.0a1.en-US.win32.web-platform.tests.tar.gz</a></td>
+				<td>47M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.xpcshell.tests.zip">firefox-59.0a1.en-US.win32.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32.zip">firefox-59.0a1.en-US.win32.zip</a></td>
+				<td>55M</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win32_info.txt">firefox-59.0a1.en-US.win32_info.txt</a></td>
+				<td>23</td>
+				<td>22-Jan-2018 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.awsy.tests.zip">firefox-59.0a1.en-US.win64.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.checksums">firefox-59.0a1.en-US.win64.checksums</a></td>
+				<td>7K</td>
+				<td>22-Jan-2018 12:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.checksums.asc">firefox-59.0a1.en-US.win64.checksums.asc</a></td>
+				<td>836</td>
+				<td>22-Jan-2018 12:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.common.tests.zip">firefox-59.0a1.en-US.win64.common.tests.zip</a></td>
+				<td>37M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.complete.mar">firefox-59.0a1.en-US.win64.complete.mar</a></td>
+				<td>43M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.cppunittest.tests.zip">firefox-59.0a1.en-US.win64.cppunittest.tests.zip</a></td>
+				<td>9M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.crashreporter-symbols.zip">firefox-59.0a1.en-US.win64.crashreporter-symbols.zip</a></td>
+				<td>27M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.installer.exe">firefox-59.0a1.en-US.win64.installer.exe</a></td>
+				<td>40M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.json">firefox-59.0a1.en-US.win64.json</a></td>
+				<td>856</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.mochitest.tests.zip">firefox-59.0a1.en-US.win64.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.mozinfo.json">firefox-59.0a1.en-US.win64.mozinfo.json</a></td>
+				<td>873</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.reftest.tests.zip">firefox-59.0a1.en-US.win64.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.talos.tests.zip">firefox-59.0a1.en-US.win64.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.test_packages.json">firefox-59.0a1.en-US.win64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.txt">firefox-59.0a1.en-US.win64.txt</a></td>
+				<td>99</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.web-platform.tests.tar.gz">firefox-59.0a1.en-US.win64.web-platform.tests.tar.gz</a></td>
+				<td>47M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.xpcshell.tests.zip">firefox-59.0a1.en-US.win64.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64.zip">firefox-59.0a1.en-US.win64.zip</a></td>
+				<td>59M</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-59.0a1.en-US.win64_info.txt">firefox-59.0a1.en-US.win64_info.txt</a></td>
+				<td>23</td>
+				<td>22-Jan-2018 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.langpack.xpi">firefox-60.0a1.en-US.langpack.xpi</a></td>
+				<td>434K</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.awsy.tests.zip">firefox-60.0a1.en-US.linux-i686.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.checksums">firefox-60.0a1.en-US.linux-i686.checksums</a></td>
+				<td>8K</td>
+				<td>15-Feb-2018 12:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.checksums.asc">firefox-60.0a1.en-US.linux-i686.checksums.asc</a></td>
+				<td>836</td>
+				<td>15-Feb-2018 12:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.common.tests.zip">firefox-60.0a1.en-US.linux-i686.common.tests.zip</a></td>
+				<td>53M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.complete.mar">firefox-60.0a1.en-US.linux-i686.complete.mar</a></td>
+				<td>44M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.cppunittest.tests.zip">firefox-60.0a1.en-US.linux-i686.cppunittest.tests.zip</a></td>
+				<td>11M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.crashreporter-symbols.zip">firefox-60.0a1.en-US.linux-i686.crashreporter-symbols.zip</a></td>
+				<td>104M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.json">firefox-60.0a1.en-US.linux-i686.json</a></td>
+				<td>850</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.mochitest.tests.zip">firefox-60.0a1.en-US.linux-i686.mochitest.tests.zip</a></td>
+				<td>75M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.mozinfo.json">firefox-60.0a1.en-US.linux-i686.mozinfo.json</a></td>
+				<td>897</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.reftest.tests.zip">firefox-60.0a1.en-US.linux-i686.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.talos.tests.zip">firefox-60.0a1.en-US.linux-i686.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.tar.bz2">firefox-60.0a1.en-US.linux-i686.tar.bz2</a></td>
+				<td>56M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.tar.bz2.asc">firefox-60.0a1.en-US.linux-i686.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.test_packages.json">firefox-60.0a1.en-US.linux-i686.test_packages.json</a></td>
+				<td>1K</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.txt">firefox-60.0a1.en-US.linux-i686.txt</a></td>
+				<td>99</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.web-platform.tests.tar.gz">firefox-60.0a1.en-US.linux-i686.web-platform.tests.tar.gz</a></td>
+				<td>48M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686.xpcshell.tests.zip">firefox-60.0a1.en-US.linux-i686.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-i686_info.txt">firefox-60.0a1.en-US.linux-i686_info.txt</a></td>
+				<td>23</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.awsy.tests.zip">firefox-60.0a1.en-US.linux-x86_64.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.checksums">firefox-60.0a1.en-US.linux-x86_64.checksums</a></td>
+				<td>8K</td>
+				<td>15-Feb-2018 12:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.checksums.asc">firefox-60.0a1.en-US.linux-x86_64.checksums.asc</a></td>
+				<td>836</td>
+				<td>15-Feb-2018 12:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.common.tests.zip">firefox-60.0a1.en-US.linux-x86_64.common.tests.zip</a></td>
+				<td>54M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.complete.mar">firefox-60.0a1.en-US.linux-x86_64.complete.mar</a></td>
+				<td>48M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.cppunittest.tests.zip">firefox-60.0a1.en-US.linux-x86_64.cppunittest.tests.zip</a></td>
+				<td>13M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.crashreporter-symbols.zip">firefox-60.0a1.en-US.linux-x86_64.crashreporter-symbols.zip</a></td>
+				<td>91M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.json">firefox-60.0a1.en-US.linux-x86_64.json</a></td>
+				<td>816</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.mochitest.tests.zip">firefox-60.0a1.en-US.linux-x86_64.mochitest.tests.zip</a></td>
+				<td>75M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.mozinfo.json">firefox-60.0a1.en-US.linux-x86_64.mozinfo.json</a></td>
+				<td>902</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.reftest.tests.zip">firefox-60.0a1.en-US.linux-x86_64.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.talos.tests.zip">firefox-60.0a1.en-US.linux-x86_64.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.tar.bz2">firefox-60.0a1.en-US.linux-x86_64.tar.bz2</a></td>
+				<td>60M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.tar.bz2.asc">firefox-60.0a1.en-US.linux-x86_64.tar.bz2.asc</a></td>
+				<td>836</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.test_packages.json">firefox-60.0a1.en-US.linux-x86_64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.txt">firefox-60.0a1.en-US.linux-x86_64.txt</a></td>
+				<td>99</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz">firefox-60.0a1.en-US.linux-x86_64.web-platform.tests.tar.gz</a></td>
+				<td>48M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64.xpcshell.tests.zip">firefox-60.0a1.en-US.linux-x86_64.xpcshell.tests.zip</a></td>
+				<td>10M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.linux-x86_64_info.txt">firefox-60.0a1.en-US.linux-x86_64_info.txt</a></td>
+				<td>23</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.awsy.tests.zip">firefox-60.0a1.en-US.mac.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.checksums">firefox-60.0a1.en-US.mac.checksums</a></td>
+				<td>7K</td>
+				<td>15-Feb-2018 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.checksums.asc">firefox-60.0a1.en-US.mac.checksums.asc</a></td>
+				<td>836</td>
+				<td>15-Feb-2018 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.common.tests.zip">firefox-60.0a1.en-US.mac.common.tests.zip</a></td>
+				<td>34M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.complete.mar">firefox-60.0a1.en-US.mac.complete.mar</a></td>
+				<td>48M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.cppunittest.tests.zip">firefox-60.0a1.en-US.mac.cppunittest.tests.zip</a></td>
+				<td>9M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.crashreporter-symbols.zip">firefox-60.0a1.en-US.mac.crashreporter-symbols.zip</a></td>
+				<td>117M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.dmg">firefox-60.0a1.en-US.mac.dmg</a></td>
+				<td>65M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.json">firefox-60.0a1.en-US.mac.json</a></td>
+				<td>1K</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.mochitest.tests.zip">firefox-60.0a1.en-US.mac.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.mozinfo.json">firefox-60.0a1.en-US.mac.mozinfo.json</a></td>
+				<td>903</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.reftest.tests.zip">firefox-60.0a1.en-US.mac.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.talos.tests.zip">firefox-60.0a1.en-US.mac.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.test_packages.json">firefox-60.0a1.en-US.mac.test_packages.json</a></td>
+				<td>1K</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.txt">firefox-60.0a1.en-US.mac.txt</a></td>
+				<td>99</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.web-platform.tests.tar.gz">firefox-60.0a1.en-US.mac.web-platform.tests.tar.gz</a></td>
+				<td>48M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac.xpcshell.tests.zip">firefox-60.0a1.en-US.mac.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.mac_info.txt">firefox-60.0a1.en-US.mac_info.txt</a></td>
+				<td>23</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.awsy.tests.zip">firefox-60.0a1.en-US.win32.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.checksums">firefox-60.0a1.en-US.win32.checksums</a></td>
+				<td>8K</td>
+				<td>15-Feb-2018 13:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.checksums.asc">firefox-60.0a1.en-US.win32.checksums.asc</a></td>
+				<td>836</td>
+				<td>15-Feb-2018 13:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.common.tests.zip">firefox-60.0a1.en-US.win32.common.tests.zip</a></td>
+				<td>36M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.complete.mar">firefox-60.0a1.en-US.win32.complete.mar</a></td>
+				<td>40M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.cppunittest.tests.zip">firefox-60.0a1.en-US.win32.cppunittest.tests.zip</a></td>
+				<td>8M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.crashreporter-symbols.zip">firefox-60.0a1.en-US.win32.crashreporter-symbols.zip</a></td>
+				<td>32M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.installer-stub.exe">firefox-60.0a1.en-US.win32.installer-stub.exe</a></td>
+				<td>269K</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.installer.exe">firefox-60.0a1.en-US.win32.installer.exe</a></td>
+				<td>37M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.json">firefox-60.0a1.en-US.win32.json</a></td>
+				<td>830</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.mochitest.tests.zip">firefox-60.0a1.en-US.win32.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.mozinfo.json">firefox-60.0a1.en-US.win32.mozinfo.json</a></td>
+				<td>870</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.reftest.tests.zip">firefox-60.0a1.en-US.win32.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.talos.tests.zip">firefox-60.0a1.en-US.win32.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.test_packages.json">firefox-60.0a1.en-US.win32.test_packages.json</a></td>
+				<td>1K</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.txt">firefox-60.0a1.en-US.win32.txt</a></td>
+				<td>99</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.web-platform.tests.tar.gz">firefox-60.0a1.en-US.win32.web-platform.tests.tar.gz</a></td>
+				<td>48M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.xpcshell.tests.zip">firefox-60.0a1.en-US.win32.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32.zip">firefox-60.0a1.en-US.win32.zip</a></td>
+				<td>55M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win32_info.txt">firefox-60.0a1.en-US.win32_info.txt</a></td>
+				<td>23</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.awsy.tests.zip">firefox-60.0a1.en-US.win64.awsy.tests.zip</a></td>
+				<td>15K</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.checksums">firefox-60.0a1.en-US.win64.checksums</a></td>
+				<td>7K</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.checksums.asc">firefox-60.0a1.en-US.win64.checksums.asc</a></td>
+				<td>836</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.common.tests.zip">firefox-60.0a1.en-US.win64.common.tests.zip</a></td>
+				<td>37M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.complete.mar">firefox-60.0a1.en-US.win64.complete.mar</a></td>
+				<td>43M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.cppunittest.tests.zip">firefox-60.0a1.en-US.win64.cppunittest.tests.zip</a></td>
+				<td>9M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.crashreporter-symbols.zip">firefox-60.0a1.en-US.win64.crashreporter-symbols.zip</a></td>
+				<td>28M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.installer.exe">firefox-60.0a1.en-US.win64.installer.exe</a></td>
+				<td>40M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.json">firefox-60.0a1.en-US.win64.json</a></td>
+				<td>840</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.mochitest.tests.zip">firefox-60.0a1.en-US.win64.mochitest.tests.zip</a></td>
+				<td>74M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.mozinfo.json">firefox-60.0a1.en-US.win64.mozinfo.json</a></td>
+				<td>873</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.reftest.tests.zip">firefox-60.0a1.en-US.win64.reftest.tests.zip</a></td>
+				<td>58M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.talos.tests.zip">firefox-60.0a1.en-US.win64.talos.tests.zip</a></td>
+				<td>13M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.test_packages.json">firefox-60.0a1.en-US.win64.test_packages.json</a></td>
+				<td>1K</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.txt">firefox-60.0a1.en-US.win64.txt</a></td>
+				<td>99</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.web-platform.tests.tar.gz">firefox-60.0a1.en-US.win64.web-platform.tests.tar.gz</a></td>
+				<td>48M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.xpcshell.tests.zip">firefox-60.0a1.en-US.win64.xpcshell.tests.zip</a></td>
+				<td>9M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64.zip">firefox-60.0a1.en-US.win64.zip</a></td>
+				<td>59M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-60.0a1.en-US.win64_info.txt">firefox-60.0a1.en-US.win64_info.txt</a></td>
+				<td>23</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170917100334-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170917100334-20170920220431.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170917220255-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170917220255-20170920220431.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170917220255-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170917220255-20170921100141.partial.mar</a></td>
+				<td>9M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918100059-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918100059-20170920220431.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918100059-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918100059-20170921100141.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918220054-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918220054-20170920220431.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918220054-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170918220054-20170921100141.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-i686-en-US-20170920220431-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-i686-en-US-20170920220431-20170921100141.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Sep-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170917100334-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170917100334-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 14:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170917220255-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170917220255-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 14:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170917220255-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170917220255-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918100059-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918100059-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 14:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918100059-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918100059-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918100059-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918100059-20170920100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 14:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170920100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170918220054-20170920220431.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 00:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170920100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170920220431.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919100405-20170921100141.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919220202-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919220202-20170920100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Sep-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919220202-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919220202-20170920220431.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 00:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919220202-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170919220202-20170921100141.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170920100426-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170920100426-20170920220431.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170920100426-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170920100426-20170921100141.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170920220431-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-linux-x86_64-en-US-20170920220431-20170921100141.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170917100334-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170917100334-20170919100405.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 12:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170917220255-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170917220255-20170919100405.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 12:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170917220255-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170917220255-20170919220202.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170918100059-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170918100059-20170919100405.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 12:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170918100059-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170918100059-20170919220202.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170918100059-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170918100059-20170920100426.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Sep-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170919100405.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 12:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170919220202.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 23:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170920100426.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Sep-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170918220054-20170920220431.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Sep-2017 23:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170919220202.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Sep-2017 23:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170920100426.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Sep-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170920220431.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Sep-2017 23:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170919100405-20170921100141.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170919220202-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170919220202-20170920100426.partial.mar</a></td>
+				<td>3M</td>
+				<td>20-Sep-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170919220202-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170919220202-20170920220431.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Sep-2017 23:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170919220202-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170919220202-20170921100141.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170920100426-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170920100426-20170920220431.partial.mar</a></td>
+				<td>3M</td>
+				<td>20-Sep-2017 23:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170920100426-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170920100426-20170921100141.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-mac-en-US-20170920220431-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-mac-en-US-20170920220431-20170921100141.partial.mar</a></td>
+				<td>3M</td>
+				<td>21-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170917100334-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170917100334-20170919100405.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Sep-2017 15:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170917220255-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170917220255-20170919100405.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Sep-2017 15:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170917220255-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170917220255-20170919220202.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Sep-2017 00:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170918100059-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170918100059-20170919100405.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Sep-2017 15:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170918100059-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170918100059-20170919220202.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Sep-2017 00:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170918100059-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170918100059-20170920100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Sep-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170919100405.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Sep-2017 15:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170919220202.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Sep-2017 00:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170920100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Sep-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170918220054-20170920220431.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Sep-2017 00:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170919220202.partial.mar</a></td>
+				<td>5M</td>
+				<td>20-Sep-2017 00:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170920100426.partial.mar</a></td>
+				<td>5M</td>
+				<td>20-Sep-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170920220431.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Sep-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170919100405-20170921100141.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170919220202-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170919220202-20170920100426.partial.mar</a></td>
+				<td>5M</td>
+				<td>20-Sep-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170919220202-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170919220202-20170920220431.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Sep-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170919220202-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170919220202-20170921100141.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170920100426-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170920100426-20170920220431.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Sep-2017 00:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170920100426-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170920100426-20170921100141.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win32-en-US-20170920220431-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win32-en-US-20170920220431-20170921100141.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Sep-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170917100334-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170917100334-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 15:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170917220255-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170917220255-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 15:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170917220255-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170917220255-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170918100059-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170918100059-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 15:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170918100059-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170918100059-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170918100059-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170918100059-20170920100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170919100405.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170919100405.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Sep-2017 15:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170920100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170918220054-20170920220431.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 01:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170919220202.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170919220202.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 00:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170920100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Sep-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170920220431.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Sep-2017 01:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170919100405-20170921100141.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170919220202-20170920100426.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170919220202-20170920100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Sep-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170919220202-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170919220202-20170920220431.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 01:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170919220202-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170919220202-20170921100141.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170920100426-20170920220431.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170920100426-20170920220431.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 01:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170920100426-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170920100426-20170921100141.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-57.0a1-win64-en-US-20170920220431-20170921100141.partial.mar">firefox-mozilla-central-57.0a1-win64-en-US-20170920220431-20170921100141.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170918100059-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170918100059-20170921220243.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 00:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170918220054-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170918220054-20170921220243.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Sep-2017 00:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170918220054-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170918220054-20170922100051.partial.mar</a></td>
+				<td>9M</td>
+				<td>22-Sep-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170920220431-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170920220431-20170921220243.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Sep-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170920220431-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170920220431-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170920220431-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170920220431-20170922220129.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170921220243.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 00:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170922220129.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921100141-20170923100045.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170922100051.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Sep-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170922220129.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Sep-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170923100045.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170921220243-20170923220337.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Sep-2017 01:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170922220129.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170923100045.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170923220337.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 01:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922100051-20170924100550.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 13:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170923100045.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170923220337.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 01:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170924100550.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 13:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170922220129-20170924220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170923220337.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 01:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170924100550.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170924220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923100045-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170924100550.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170924220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170923220337-20170925220207.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 00:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170924220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170925220207.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924100550-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 12:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170925220207.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 00:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 12:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170924220116-20170926220106.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Sep-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170925220207.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 12:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170926220106.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Sep-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925100307-20170927100120.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Sep-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 12:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170926220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170927100120.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Sep-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170925220207-20170928100123.partial.mar</a></td>
+				<td>9M</td>
+				<td>28-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170926220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170927100120.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170928100123.partial.mar</a></td>
+				<td>9M</td>
+				<td>28-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926100259-20170928220658.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 00:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170927100120.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170928100123.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170928220658.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 00:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170926220106-20170929100122.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 12:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170928100123.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170928220658.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 00:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170929100122.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 12:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170927100120-20170929220356.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170928220658.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 00:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170929100122.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 12:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170929220356.partial.mar</a></td>
+				<td>9M</td>
+				<td>30-Sep-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928100123-20170930100302.partial.mar</a></td>
+				<td>9M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170929100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 12:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170929220356.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170930100302.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170928220658-20170930220116.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20170929220356.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20170930100302.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20170930220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929100122-20171001100335.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20170930100302.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20170930220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20171001100335.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170929220356-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20170930220116.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20171001100335.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930100302-20171002100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171001100335.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171002100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20170930220116-20171002220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171002100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171002220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001100335-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171002100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171002220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171001220301-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171002220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171003100226.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Oct-2017 12:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002100134-20171004100049.partial.mar</a></td>
+				<td>10M</td>
+				<td>04-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171004100049.partial.mar</a></td>
+				<td>10M</td>
+				<td>04-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171002220204-20171004220309.partial.mar</a></td>
+				<td>11M</td>
+				<td>05-Oct-2017 00:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171004100049.partial.mar</a></td>
+				<td>10M</td>
+				<td>04-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171004220309.partial.mar</a></td>
+				<td>11M</td>
+				<td>05-Oct-2017 00:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003100226-20171005100211.partial.mar</a></td>
+				<td>11M</td>
+				<td>05-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171004100049.partial.mar</a></td>
+				<td>9M</td>
+				<td>04-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171004220309.partial.mar</a></td>
+				<td>10M</td>
+				<td>05-Oct-2017 00:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171005100211.partial.mar</a></td>
+				<td>10M</td>
+				<td>05-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171003220138-20171005220204.partial.mar</a></td>
+				<td>11M</td>
+				<td>06-Oct-2017 00:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171004220309.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171005100211.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171005220204.partial.mar</a></td>
+				<td>8M</td>
+				<td>06-Oct-2017 00:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004100049-20171006100327.partial.mar</a></td>
+				<td>9M</td>
+				<td>06-Oct-2017 12:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171005100211.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171005220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 00:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171006100327.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Oct-2017 12:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171004220309-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171005220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 00:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171006100327.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Oct-2017 12:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005100211-20171007100142.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171006100327.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 12:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171006220306.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171007100142.partial.mar</a></td>
+				<td>9M</td>
+				<td>07-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171005220204-20171007220156.partial.mar</a></td>
+				<td>9M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171007100142.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171007220156.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006100327-20171008131700.partial.mar</a></td>
+				<td>9M</td>
+				<td>08-Oct-2017 15:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171007100142.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171007220156.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171008131700.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 15:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171006220306-20171008220130.partial.mar</a></td>
+				<td>9M</td>
+				<td>09-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171007220156.partial.mar</a></td>
+				<td>5M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171008131700.partial.mar</a></td>
+				<td>7M</td>
+				<td>08-Oct-2017 15:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171008220130.partial.mar</a></td>
+				<td>9M</td>
+				<td>09-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007100142-20171009100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171008131700.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 15:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171008220130.partial.mar</a></td>
+				<td>9M</td>
+				<td>09-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171009100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171007220156-20171009220104.partial.mar</a></td>
+				<td>9M</td>
+				<td>10-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171008220130.partial.mar</a></td>
+				<td>8M</td>
+				<td>09-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171009100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008131700-20171010100200.partial.mar</a></td>
+				<td>9M</td>
+				<td>10-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171009100134.partial.mar</a></td>
+				<td>9M</td>
+				<td>09-Oct-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171009220104.partial.mar</a></td>
+				<td>9M</td>
+				<td>10-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171010100200.partial.mar</a></td>
+				<td>10M</td>
+				<td>10-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171008220130-20171010220102.partial.mar</a></td>
+				<td>10M</td>
+				<td>11-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171010100200.partial.mar</a></td>
+				<td>9M</td>
+				<td>10-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171010220102.partial.mar</a></td>
+				<td>9M</td>
+				<td>11-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009100134-20171011100133.partial.mar</a></td>
+				<td>9M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171010100200.partial.mar</a></td>
+				<td>7M</td>
+				<td>10-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171010220102.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171011100133.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171009220104-20171011220113.partial.mar</a></td>
+				<td>9M</td>
+				<td>12-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171010220102.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171011100133.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171011220113.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171012100228.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010100200-20171012105833.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 15:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171011100133.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171011220113.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171012100228.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171010220102-20171012105833.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 15:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171011220113.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171012100228.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171012105833.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 15:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011100133-20171012220111.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 00:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171012100228.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171012105833.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 15:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171012220111.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 00:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171011220113-20171013100112.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012100228-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012100228-20171012220111.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 00:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012100228-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012100228-20171013100112.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012100228-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012100228-20171013220204.partial.mar</a></td>
+				<td>9M</td>
+				<td>14-Oct-2017 01:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171012220111.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 00:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171013100112.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171013220204.partial.mar</a></td>
+				<td>9M</td>
+				<td>14-Oct-2017 01:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012105833-20171014100219.partial.mar</a></td>
+				<td>11M</td>
+				<td>14-Oct-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171013100112.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171013220204.partial.mar</a></td>
+				<td>9M</td>
+				<td>14-Oct-2017 01:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171014100219.partial.mar</a></td>
+				<td>10M</td>
+				<td>14-Oct-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171012220111-20171014220542.partial.mar</a></td>
+				<td>11M</td>
+				<td>15-Oct-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 01:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171014220542.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013100112-20171015100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171014220542.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171015100127.partial.mar</a></td>
+				<td>9M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171013220204-20171015220106.partial.mar</a></td>
+				<td>9M</td>
+				<td>16-Oct-2017 01:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171014220542.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171015100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171015220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 01:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014100219-20171016100113.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171015100127.partial.mar</a></td>
+				<td>5M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171015220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 01:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171014220542-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 01:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171015220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 01:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 01:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015100127-20171017100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 01:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171017100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171015220106-20171017141229.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 17:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 01:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171017100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171017141229.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 17:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016100113-20171017220415.partial.mar</a></td>
+				<td>8M</td>
+				<td>18-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171017100127.partial.mar</a></td>
+				<td>9M</td>
+				<td>17-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171017141229.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 17:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171017220415.partial.mar</a></td>
+				<td>9M</td>
+				<td>18-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171016220427-20171018100140.partial.mar</a></td>
+				<td>9M</td>
+				<td>18-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171017141229.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 17:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171017220415.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171018100140.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017100127-20171018220049.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171017220415.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171018100140.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171018220049.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017141229-20171019100107.partial.mar</a></td>
+				<td>8M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171018100140.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171018220049.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171019100107.partial.mar</a></td>
+				<td>8M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171017220415-20171019222141.partial.mar</a></td>
+				<td>9M</td>
+				<td>20-Oct-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171018220049.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171019100107.partial.mar</a></td>
+				<td>8M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171019222141.partial.mar</a></td>
+				<td>9M</td>
+				<td>20-Oct-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018100140-20171020100426.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171019100107.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171019222141.partial.mar</a></td>
+				<td>9M</td>
+				<td>20-Oct-2017 01:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171020100426.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171018220049-20171020221129.partial.mar</a></td>
+				<td>9M</td>
+				<td>21-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171019222141.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171020100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171020221129.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019100107-20171021100029.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171020100426.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171020221129.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171021100029.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171019222141-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171020221129.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171021100029.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020100426-20171022100058.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171021100029.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171021220121.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Oct-2017 02:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171022100058.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171020221129-20171022220103.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171022100058.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171022220103.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021100029-20171023100252.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Oct-2017 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171022100058.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171022220103.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171023100252.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171021220121-20171023220222.partial.mar</a></td>
+				<td>13M</td>
+				<td>24-Oct-2017 01:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171022220103.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171023100252.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171023220222.partial.mar</a></td>
+				<td>13M</td>
+				<td>24-Oct-2017 01:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022100058-20171024100135.partial.mar</a></td>
+				<td>13M</td>
+				<td>24-Oct-2017 12:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171023100252.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171023220222.partial.mar</a></td>
+				<td>13M</td>
+				<td>24-Oct-2017 01:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171024100135.partial.mar</a></td>
+				<td>13M</td>
+				<td>24-Oct-2017 12:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171022220103-20171024220325.partial.mar</a></td>
+				<td>13M</td>
+				<td>25-Oct-2017 00:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171023220222.partial.mar</a></td>
+				<td>13M</td>
+				<td>24-Oct-2017 01:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171024100135.partial.mar</a></td>
+				<td>13M</td>
+				<td>24-Oct-2017 12:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171024220325.partial.mar</a></td>
+				<td>13M</td>
+				<td>25-Oct-2017 00:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023100252-20171025100449.partial.mar</a></td>
+				<td>13M</td>
+				<td>25-Oct-2017 12:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171024100135.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Oct-2017 12:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171024220325.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Oct-2017 00:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171025100449.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Oct-2017 12:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171023220222-20171025230440.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171024220325.partial.mar</a></td>
+				<td>4M</td>
+				<td>25-Oct-2017 00:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171025100449.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Oct-2017 12:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171025230440.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Oct-2017 02:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024100135-20171026100047.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171025100449.partial.mar</a></td>
+				<td>4M</td>
+				<td>25-Oct-2017 12:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171025230440.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171026100047.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171024220325-20171026221945.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 07:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171025230440.partial.mar</a></td>
+				<td>3M</td>
+				<td>26-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171026100047.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171026221945.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 07:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025100449-20171027100103.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 15:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171026100047.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171026221945.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 07:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171027100103.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 15:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171025230440-20171027220059.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 02:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171026221945.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Oct-2017 07:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171027100103.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 14:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171027220059.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 02:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026100047-20171028100423.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171027100103.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Oct-2017 14:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171027220059.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 02:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171028100423.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171026221945-20171028220326.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171027220059.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 02:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171028100423.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171028220326.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027100103-20171029102300.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 14:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171028100423.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171028220326.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171029102300.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 14:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171027220059-20171029220112.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 03:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171028220326.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171029102300.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 14:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171029220112.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 03:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028100423-20171030103605.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 20:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171029102300.partial.mar</a></td>
+				<td>1M</td>
+				<td>29-Oct-2017 14:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171029220112.partial.mar</a></td>
+				<td>2M</td>
+				<td>30-Oct-2017 03:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171030103605.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171031220132.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171028220326-20171031235118.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 10:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171029220112.partial.mar</a></td>
+				<td>1M</td>
+				<td>30-Oct-2017 03:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171030103605.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 20:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171031220132.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029102300-20171031235118.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 10:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171030103605.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171031220132.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171031235118.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 10:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171029220112-20171101104430.partial.mar</a></td>
+				<td>38M</td>
+				<td>01-Nov-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171031220132.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171031235118.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 10:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171101104430.partial.mar</a></td>
+				<td>38M</td>
+				<td>01-Nov-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171030103605-20171101220120.partial.mar</a></td>
+				<td>38M</td>
+				<td>02-Nov-2017 00:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031220132-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031220132-20171101104430.partial.mar</a></td>
+				<td>38M</td>
+				<td>01-Nov-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031220132-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031220132-20171101220120.partial.mar</a></td>
+				<td>38M</td>
+				<td>02-Nov-2017 00:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031220132-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031220132-20171102100041.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Nov-2017 12:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171101104430.partial.mar</a></td>
+				<td>38M</td>
+				<td>01-Nov-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171101220120.partial.mar</a></td>
+				<td>38M</td>
+				<td>02-Nov-2017 00:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171102100041.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Nov-2017 12:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171031235118-20171102222620.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Nov-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171101220120.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 00:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171102100041.partial.mar</a></td>
+				<td>25M</td>
+				<td>02-Nov-2017 12:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171102222620.partial.mar</a></td>
+				<td>25M</td>
+				<td>03-Nov-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101104430-20171103100331.partial.mar</a></td>
+				<td>25M</td>
+				<td>03-Nov-2017 12:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171102100041.partial.mar</a></td>
+				<td>24M</td>
+				<td>02-Nov-2017 12:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171102222620.partial.mar</a></td>
+				<td>25M</td>
+				<td>03-Nov-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171103100331.partial.mar</a></td>
+				<td>24M</td>
+				<td>03-Nov-2017 12:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171101220120-20171103220715.partial.mar</a></td>
+				<td>24M</td>
+				<td>04-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171102222620.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171103100331.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Nov-2017 12:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171103220715.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102100041-20171104100412.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Nov-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171103100331.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 12:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171103220715.partial.mar</a></td>
+				<td>4M</td>
+				<td>04-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171104100412.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171102222620-20171104220420.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171103220715.partial.mar</a></td>
+				<td>3M</td>
+				<td>04-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171104100412.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171104220420.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103100331-20171105100353.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Nov-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171104100412.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171104220420.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171105100353.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Nov-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171103220715-20171105220721.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171104220420.partial.mar</a></td>
+				<td>3M</td>
+				<td>05-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171105100353.partial.mar</a></td>
+				<td>3M</td>
+				<td>05-Nov-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171105220721.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104100412-20171106100122.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Nov-2017 11:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104220420-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104220420-20171105100353.partial.mar</a></td>
+				<td>46K</td>
+				<td>05-Nov-2017 12:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104220420-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104220420-20171105220721.partial.mar</a></td>
+				<td>469K</td>
+				<td>06-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104220420-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171104220420-20171106100122.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 11:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171105100353-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171105100353-20171105220721.partial.mar</a></td>
+				<td>466K</td>
+				<td>06-Nov-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171105100353-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171105100353-20171106100122.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 11:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-i686-en-US-20171105220721-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-i686-en-US-20171105220721-20171106100122.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 11:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170919220202-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170919220202-20170921220243.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Sep-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920100426-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920100426-20170921220243.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Sep-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920100426-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920100426-20170922100051.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Sep-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920220431-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920220431-20170921220243.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920220431-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920220431-20170922100051.partial.mar</a></td>
+				<td>8M</td>
+				<td>22-Sep-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920220431-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170920220431-20170922220129.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Sep-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170921220243.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Sep-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170922220129.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Sep-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921100141-20170923100045.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Sep-2017 13:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170922220129.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Sep-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170923100045.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Sep-2017 13:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170921220243-20170923220337.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Sep-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170922220129.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170923100045.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170923220337.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Sep-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922100051-20170924100550.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Sep-2017 13:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170923100045.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170923220337.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Sep-2017 00:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170924100550.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Sep-2017 13:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170922220129-20170924220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170923220337.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 00:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170924100550.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 13:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170924220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923100045-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170924100550.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 13:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170924220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170925100307.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170923220337-20170925220207.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170924220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170925220207.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924100550-20170926100259.partial.mar</a></td>
+				<td>8M</td>
+				<td>26-Sep-2017 12:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170925220207.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170926100259.partial.mar</a></td>
+				<td>8M</td>
+				<td>26-Sep-2017 12:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170924220116-20170926220106.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Sep-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170925220207.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170926100259.partial.mar</a></td>
+				<td>8M</td>
+				<td>26-Sep-2017 12:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170926220106.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Sep-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925100307-20170928100123.partial.mar</a></td>
+				<td>9M</td>
+				<td>28-Sep-2017 12:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 12:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170926220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170928100123.partial.mar</a></td>
+				<td>9M</td>
+				<td>28-Sep-2017 12:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170925220207-20170928220658.partial.mar</a></td>
+				<td>9M</td>
+				<td>29-Sep-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170926220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170928100123.partial.mar</a></td>
+				<td>9M</td>
+				<td>28-Sep-2017 12:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170928220658.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926100259-20170929100122.partial.mar</a></td>
+				<td>9M</td>
+				<td>29-Sep-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170928100123.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Sep-2017 12:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170928220658.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170929100122.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170926220106-20170929220356.partial.mar</a></td>
+				<td>9M</td>
+				<td>30-Sep-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170928220658.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170929100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170929220356.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928100123-20170930100302.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170929100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170929220356.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170930100302.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170928220658-20170930220116.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20170929220356.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20170930100302.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20170930220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929100122-20171001100335.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20170930100302.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20170930220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20171001100335.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170929220356-20171001220301.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20170930220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20171001100335.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20171001220301.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930100302-20171002100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171001100335.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 12:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171002100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20170930220116-20171002220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171002100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 12:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171002220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001100335-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171002100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 12:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171002220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171001220301-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171002220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 00:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002100134-20171004220309.partial.mar</a></td>
+				<td>11M</td>
+				<td>05-Oct-2017 00:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171004220309.partial.mar</a></td>
+				<td>11M</td>
+				<td>05-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171002220204-20171005100211.partial.mar</a></td>
+				<td>11M</td>
+				<td>05-Oct-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171004220309.partial.mar</a></td>
+				<td>10M</td>
+				<td>05-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171005100211.partial.mar</a></td>
+				<td>11M</td>
+				<td>05-Oct-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003100226-20171005220204.partial.mar</a></td>
+				<td>11M</td>
+				<td>06-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171004220309.partial.mar</a></td>
+				<td>10M</td>
+				<td>05-Oct-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171005100211.partial.mar</a></td>
+				<td>10M</td>
+				<td>05-Oct-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171005220204.partial.mar</a></td>
+				<td>11M</td>
+				<td>06-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171003220138-20171006100327.partial.mar</a></td>
+				<td>10M</td>
+				<td>06-Oct-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171005100211.partial.mar</a></td>
+				<td>6M</td>
+				<td>05-Oct-2017 12:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171005220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171006100327.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Oct-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171004220309-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171005220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171006100327.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005100211-20171007100142.partial.mar</a></td>
+				<td>9M</td>
+				<td>07-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171006100327.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171006220306.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 00:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171007100142.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171005220204-20171007220156.partial.mar</a></td>
+				<td>9M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171007100142.partial.mar</a></td>
+				<td>9M</td>
+				<td>07-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171007220156.partial.mar</a></td>
+				<td>9M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006100327-20171008131700.partial.mar</a></td>
+				<td>9M</td>
+				<td>08-Oct-2017 15:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171007100142.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171007220156.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171008131700.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 15:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171006220306-20171008220130.partial.mar</a></td>
+				<td>8M</td>
+				<td>09-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171007220156.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 00:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171008131700.partial.mar</a></td>
+				<td>7M</td>
+				<td>08-Oct-2017 15:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171008220130.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007100142-20171009100134.partial.mar</a></td>
+				<td>8M</td>
+				<td>09-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171008131700.partial.mar</a></td>
+				<td>7M</td>
+				<td>08-Oct-2017 15:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171008220130.partial.mar</a></td>
+				<td>6M</td>
+				<td>09-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171009100134.partial.mar</a></td>
+				<td>8M</td>
+				<td>09-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171007220156-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 00:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171008220130.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 00:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171009100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 00:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008131700-20171010100200.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 12:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171009100134.partial.mar</a></td>
+				<td>8M</td>
+				<td>09-Oct-2017 12:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 00:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171010100200.partial.mar</a></td>
+				<td>9M</td>
+				<td>10-Oct-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171008220130-20171010220102.partial.mar</a></td>
+				<td>9M</td>
+				<td>11-Oct-2017 00:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 00:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171010100200.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171010220102.partial.mar</a></td>
+				<td>9M</td>
+				<td>11-Oct-2017 00:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009100134-20171011100133.partial.mar</a></td>
+				<td>9M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171010100200.partial.mar</a></td>
+				<td>7M</td>
+				<td>10-Oct-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171010220102.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 00:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171011100133.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171009220104-20171011220113.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 00:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171010220102.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 00:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171011100133.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171011220113.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 00:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171012100228.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010100200-20171012105833.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 15:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171011100133.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 17:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171011220113.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 00:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171012100228.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171010220102-20171012105833.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 15:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171011220113.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 00:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171012100228.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171012105833.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 15:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011100133-20171012220111.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171012100228.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171012105833.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 15:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171012220111.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171011220113-20171013100112.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 12:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012100228-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012100228-20171012220111.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012100228-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012100228-20171013100112.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 12:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012100228-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012100228-20171013220204.partial.mar</a></td>
+				<td>9M</td>
+				<td>14-Oct-2017 00:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171012220111.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 00:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171013100112.partial.mar</a></td>
+				<td>9M</td>
+				<td>13-Oct-2017 12:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171013220204.partial.mar</a></td>
+				<td>9M</td>
+				<td>14-Oct-2017 00:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012105833-20171014100219.partial.mar</a></td>
+				<td>10M</td>
+				<td>14-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171013100112.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 12:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171013220204.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 00:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171014100219.partial.mar</a></td>
+				<td>10M</td>
+				<td>14-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171012220111-20171014220542.partial.mar</a></td>
+				<td>10M</td>
+				<td>15-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 00:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171014220542.partial.mar</a></td>
+				<td>9M</td>
+				<td>15-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013100112-20171015100127.partial.mar</a></td>
+				<td>9M</td>
+				<td>15-Oct-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171014220542.partial.mar</a></td>
+				<td>9M</td>
+				<td>15-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171015100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 13:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171013220204-20171015220106.partial.mar</a></td>
+				<td>8M</td>
+				<td>16-Oct-2017 01:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171014220542.partial.mar</a></td>
+				<td>7M</td>
+				<td>15-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171015100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171015220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 01:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014100219-20171016100113.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171015100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 13:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171015220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 01:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171016100113.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 12:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171014220542-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 01:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171015220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>16-Oct-2017 01:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171016100113.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 01:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015100127-20171017100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 12:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171016100113.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171016220427.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 01:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171017100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 12:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171015220106-20171017141229.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 17:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 01:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171017100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 12:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171017141229.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 17:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016100113-20171017220415.partial.mar</a></td>
+				<td>8M</td>
+				<td>18-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171017100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 12:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171017141229.partial.mar</a></td>
+				<td>8M</td>
+				<td>17-Oct-2017 17:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171017220415.partial.mar</a></td>
+				<td>9M</td>
+				<td>18-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171016220427-20171018100140.partial.mar</a></td>
+				<td>8M</td>
+				<td>18-Oct-2017 12:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171017141229.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 17:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171017220415.partial.mar</a></td>
+				<td>8M</td>
+				<td>18-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171018100140.partial.mar</a></td>
+				<td>8M</td>
+				<td>18-Oct-2017 12:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017100127-20171018220049.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171017220415.partial.mar</a></td>
+				<td>8M</td>
+				<td>18-Oct-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171018100140.partial.mar</a></td>
+				<td>8M</td>
+				<td>18-Oct-2017 12:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171018220049.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017141229-20171019100107.partial.mar</a></td>
+				<td>8M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171018100140.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 12:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171018220049.partial.mar</a></td>
+				<td>8M</td>
+				<td>19-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171019100107.partial.mar</a></td>
+				<td>8M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171017220415-20171019222141.partial.mar</a></td>
+				<td>9M</td>
+				<td>20-Oct-2017 01:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171018220049.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 01:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171019100107.partial.mar</a></td>
+				<td>8M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171019222141.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 01:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018100140-20171020100426.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 12:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171019100107.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171019222141.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 01:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171020100426.partial.mar</a></td>
+				<td>8M</td>
+				<td>20-Oct-2017 12:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171018220049-20171020221129.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171019222141.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 01:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171020100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 12:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171020221129.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019100107-20171021100029.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171020100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 12:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171020221129.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171021100029.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171019222141-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171020221129.partial.mar</a></td>
+				<td>8M</td>
+				<td>21-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171021100029.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020100426-20171022100058.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171021100029.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171022100058.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171020221129-20171022220103.partial.mar</a></td>
+				<td>8M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171021220121.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Oct-2017 02:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171022100058.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171022220103.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021100029-20171023100252.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171022100058.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 12:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171022220103.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171023100252.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171021220121-20171023220222.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 00:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171022220103.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 00:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171023100252.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171023220222.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 00:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022100058-20171024100135.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 12:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171023100252.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171023220222.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 00:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171024100135.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 12:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171022220103-20171024220325.partial.mar</a></td>
+				<td>9M</td>
+				<td>25-Oct-2017 00:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171023220222.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 00:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171024100135.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 12:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171024220325.partial.mar</a></td>
+				<td>8M</td>
+				<td>25-Oct-2017 00:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023100252-20171025100449.partial.mar</a></td>
+				<td>8M</td>
+				<td>25-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171024100135.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 12:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171024220325.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 00:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171025100449.partial.mar</a></td>
+				<td>8M</td>
+				<td>25-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171023220222-20171025230440.partial.mar</a></td>
+				<td>8M</td>
+				<td>26-Oct-2017 02:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171024220325.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 00:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171025100449.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171025230440.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 02:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024100135-20171026100047.partial.mar</a></td>
+				<td>8M</td>
+				<td>26-Oct-2017 14:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171025100449.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 12:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171025230440.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 02:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171026100047.partial.mar</a></td>
+				<td>8M</td>
+				<td>26-Oct-2017 14:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171024220325-20171026221945.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Oct-2017 02:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171025230440.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 02:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 14:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171026221945.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Oct-2017 02:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025100449-20171027100103.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Oct-2017 15:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 14:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171026221945.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Oct-2017 02:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171027100103.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 15:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171025230440-20171027220059.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Oct-2017 02:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171027100103.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 15:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171027220059.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Oct-2017 02:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026100047-20171028100423.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Oct-2017 14:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171027100103.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 15:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171027220059.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Oct-2017 02:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171028100423.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Oct-2017 14:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171026221945-20171028220326.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Oct-2017 01:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171027220059.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Oct-2017 02:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171028100423.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Oct-2017 14:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171028220326.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 01:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027100103-20171029102300.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 14:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171028100423.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Oct-2017 14:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171028220326.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 01:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171029102300.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 14:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171027220059-20171029220112.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 03:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171028220326.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 01:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171029102300.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 14:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171029220112.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 03:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028100423-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 19:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171029102300.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 14:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171029220112.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 03:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 19:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171031220132.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 03:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171028220326-20171031235118.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 10:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171029220112.partial.mar</a></td>
+				<td>5M</td>
+				<td>30-Oct-2017 03:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 19:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171031220132.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 03:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029102300-20171031235118.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 10:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 19:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171031220132.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 03:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171031235118.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 10:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171029220112-20171101104430.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 16:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171031220132.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 03:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171031235118.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 10:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171101104430.partial.mar</a></td>
+				<td>8M</td>
+				<td>01-Nov-2017 16:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171030103605-20171101220120.partial.mar</a></td>
+				<td>9M</td>
+				<td>02-Nov-2017 00:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031220132-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031220132-20171101104430.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 16:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031220132-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031220132-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031220132-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031220132-20171102222620.partial.mar</a></td>
+				<td>8M</td>
+				<td>03-Nov-2017 00:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171101104430.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 16:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171102222620.partial.mar</a></td>
+				<td>8M</td>
+				<td>03-Nov-2017 00:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171031235118-20171103100331.partial.mar</a></td>
+				<td>8M</td>
+				<td>03-Nov-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171102222620.partial.mar</a></td>
+				<td>8M</td>
+				<td>03-Nov-2017 00:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171103100331.partial.mar</a></td>
+				<td>8M</td>
+				<td>03-Nov-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101104430-20171103220715.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171102222620.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 00:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171103100331.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171103220715.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171101220120-20171104100412.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171103100331.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171103220715.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171104100412.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171102222620-20171104220420.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171103220715.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171104100412.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171104220420.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103100331-20171105100353.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Nov-2017 12:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171104100412.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171104220420.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171105100353.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Nov-2017 12:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171103220715-20171105220721.partial.mar</a></td>
+				<td>8M</td>
+				<td>06-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171104220420.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Nov-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171105100353.partial.mar</a></td>
+				<td>6M</td>
+				<td>05-Nov-2017 12:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171105220721.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104100412-20171106100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104220420-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104220420-20171105100353.partial.mar</a></td>
+				<td>6M</td>
+				<td>05-Nov-2017 12:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104220420-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104220420-20171105220721.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104220420-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171104220420-20171106100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171105100353-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171105100353-20171105220721.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 00:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171105100353-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171105100353-20171106100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171105220721-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-linux-x86_64-en-US-20171105220721-20171106100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 12:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170919220202-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170919220202-20170921220243.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Sep-2017 23:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170920100426-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170920100426-20170921220243.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Sep-2017 23:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170920100426-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170920100426-20170922100051.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Sep-2017 11:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170920220431-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170920220431-20170921220243.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Sep-2017 23:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170920220431-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170920220431-20170922100051.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Sep-2017 11:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170920220431-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170920220431-20170922220129.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170921220243.partial.mar</a></td>
+				<td>3M</td>
+				<td>21-Sep-2017 23:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170922100051.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Sep-2017 11:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170922220129.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921100141-20170923100045.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 11:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170922100051.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Sep-2017 11:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170922220129.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 23:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170923100045.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 11:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170921220243-20170923220337.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170922220129.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Sep-2017 23:34</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170923100045.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Sep-2017 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170923220337.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Sep-2017 23:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922100051-20170924100550.partial.mar</a></td>
+				<td>4M</td>
+				<td>24-Sep-2017 12:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170923100045.partial.mar</a></td>
+				<td>3M</td>
+				<td>23-Sep-2017 11:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170923220337.partial.mar</a></td>
+				<td>3M</td>
+				<td>23-Sep-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170924100550.partial.mar</a></td>
+				<td>3M</td>
+				<td>24-Sep-2017 12:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170922220129-20170924220116.partial.mar</a></td>
+				<td>3M</td>
+				<td>24-Sep-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170923220337.partial.mar</a></td>
+				<td>1M</td>
+				<td>23-Sep-2017 23:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170924100550.partial.mar</a></td>
+				<td>2M</td>
+				<td>24-Sep-2017 12:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170924220116.partial.mar</a></td>
+				<td>3M</td>
+				<td>24-Sep-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923100045-20170925100307.partial.mar</a></td>
+				<td>4M</td>
+				<td>25-Sep-2017 11:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170924100550.partial.mar</a></td>
+				<td>2M</td>
+				<td>24-Sep-2017 12:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170924220116.partial.mar</a></td>
+				<td>3M</td>
+				<td>24-Sep-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170925100307.partial.mar</a></td>
+				<td>3M</td>
+				<td>25-Sep-2017 11:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170923220337-20170925220207.partial.mar</a></td>
+				<td>4M</td>
+				<td>25-Sep-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170924220116.partial.mar</a></td>
+				<td>3M</td>
+				<td>24-Sep-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170925100307.partial.mar</a></td>
+				<td>3M</td>
+				<td>25-Sep-2017 11:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170925220207.partial.mar</a></td>
+				<td>3M</td>
+				<td>25-Sep-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924100550-20170926100259.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170925100307.partial.mar</a></td>
+				<td>3M</td>
+				<td>25-Sep-2017 11:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170925220207.partial.mar</a></td>
+				<td>3M</td>
+				<td>25-Sep-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170926100259.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170924220116-20170926220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 23:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170925220207.partial.mar</a></td>
+				<td>1M</td>
+				<td>25-Sep-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170926100259.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Sep-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170926220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 23:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925100307-20170927100120.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170926100259.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Sep-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170926220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 23:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170927100120.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170925220207-20170928100123.partial.mar</a></td>
+				<td>6M</td>
+				<td>28-Sep-2017 11:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170926220106.partial.mar</a></td>
+				<td>3M</td>
+				<td>26-Sep-2017 23:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170927100120.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170928100123.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Sep-2017 11:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926100259-20170928220658.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Sep-2017 23:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170927100120.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170928100123.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Sep-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170928220658.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Sep-2017 23:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170926220106-20170929100122.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170928100123.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Sep-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170928220658.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Sep-2017 23:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170929100122.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170927100120-20170929220356.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 23:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170928220658.partial.mar</a></td>
+				<td>3M</td>
+				<td>28-Sep-2017 23:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170929100122.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170929220356.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 23:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928100123-20170930100302.partial.mar</a></td>
+				<td>5M</td>
+				<td>30-Sep-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170929100122.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Sep-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170929220356.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 23:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170930100302.partial.mar</a></td>
+				<td>5M</td>
+				<td>30-Sep-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170928220658-20170930220116.partial.mar</a></td>
+				<td>5M</td>
+				<td>30-Sep-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20170929220356.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Sep-2017 23:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20170930100302.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Sep-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20170930220116.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Sep-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929100122-20171001100335.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20170930100302.partial.mar</a></td>
+				<td>2M</td>
+				<td>30-Sep-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20170930220116.partial.mar</a></td>
+				<td>3M</td>
+				<td>30-Sep-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20171001100335.partial.mar</a></td>
+				<td>3M</td>
+				<td>01-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170929220356-20171001220301.partial.mar</a></td>
+				<td>3M</td>
+				<td>01-Oct-2017 23:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20170930220116.partial.mar</a></td>
+				<td>1006K</td>
+				<td>30-Sep-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20171001100335.partial.mar</a></td>
+				<td>3M</td>
+				<td>01-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20171001220301.partial.mar</a></td>
+				<td>3M</td>
+				<td>01-Oct-2017 23:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930100302-20171002100134.partial.mar</a></td>
+				<td>4M</td>
+				<td>02-Oct-2017 11:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171001100335.partial.mar</a></td>
+				<td>3M</td>
+				<td>01-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171001220301.partial.mar</a></td>
+				<td>3M</td>
+				<td>01-Oct-2017 23:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171002100134.partial.mar</a></td>
+				<td>4M</td>
+				<td>02-Oct-2017 11:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171002223859.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20170930220116-20171002223859.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 00:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171001220301.partial.mar</a></td>
+				<td>2M</td>
+				<td>01-Oct-2017 23:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171002100134.partial.mar</a></td>
+				<td>3M</td>
+				<td>02-Oct-2017 11:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171002223859.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171002223859.partial.mar</a></td>
+				<td>3M</td>
+				<td>03-Oct-2017 00:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001100335-20171003100226.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 11:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171002100134.partial.mar</a></td>
+				<td>3M</td>
+				<td>02-Oct-2017 11:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171002223859.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171002223859.partial.mar</a></td>
+				<td>3M</td>
+				<td>03-Oct-2017 00:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171003100226.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 11:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171001220301-20171003220138.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171002223859.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171002223859.partial.mar</a></td>
+				<td>1M</td>
+				<td>03-Oct-2017 00:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171003100226.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 11:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171003220138.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002100134-20171004100049.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Oct-2017 11:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171003100226.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 11:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171003220138.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171004100049.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Oct-2017 11:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171002223859-20171004220309.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Oct-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171003220138.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171004100049.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Oct-2017 11:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171004220309.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003100226-20171005100211.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Oct-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171004100049.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Oct-2017 11:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171004220309.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171005100211.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Oct-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171003220138-20171005220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Oct-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171004220309.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Oct-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171005100211.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Oct-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171005220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Oct-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004100049-20171006100327.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171005100211.partial.mar</a></td>
+				<td>4M</td>
+				<td>05-Oct-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171005220204.partial.mar</a></td>
+				<td>4M</td>
+				<td>05-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171006100327.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171004220309-20171006220306.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 23:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171005220204.partial.mar</a></td>
+				<td>103K</td>
+				<td>05-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171006100327.partial.mar</a></td>
+				<td>109K</td>
+				<td>06-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171006220306.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Oct-2017 23:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005100211-20171007100142.partial.mar</a></td>
+				<td>5M</td>
+				<td>07-Oct-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171006100327.partial.mar</a></td>
+				<td>108K</td>
+				<td>06-Oct-2017 11:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171006220306.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Oct-2017 23:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171007100142.partial.mar</a></td>
+				<td>5M</td>
+				<td>07-Oct-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171005220204-20171007220156.partial.mar</a></td>
+				<td>5M</td>
+				<td>07-Oct-2017 23:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171006220306.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Oct-2017 23:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171007100142.partial.mar</a></td>
+				<td>5M</td>
+				<td>07-Oct-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171007220156.partial.mar</a></td>
+				<td>5M</td>
+				<td>07-Oct-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006100327-20171008131700.partial.mar</a></td>
+				<td>5M</td>
+				<td>08-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171007100142.partial.mar</a></td>
+				<td>4M</td>
+				<td>07-Oct-2017 11:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171007220156.partial.mar</a></td>
+				<td>4M</td>
+				<td>07-Oct-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171008131700.partial.mar</a></td>
+				<td>4M</td>
+				<td>08-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171006220306-20171008220130.partial.mar</a></td>
+				<td>4M</td>
+				<td>08-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171007220156.partial.mar</a></td>
+				<td>1M</td>
+				<td>07-Oct-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171008131700.partial.mar</a></td>
+				<td>3M</td>
+				<td>08-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171008220130.partial.mar</a></td>
+				<td>3M</td>
+				<td>08-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007100142-20171009100134.partial.mar</a></td>
+				<td>4M</td>
+				<td>09-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171008131700.partial.mar</a></td>
+				<td>3M</td>
+				<td>08-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171008220130.partial.mar</a></td>
+				<td>3M</td>
+				<td>08-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171009100134.partial.mar</a></td>
+				<td>4M</td>
+				<td>09-Oct-2017 11:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171007220156-20171009220104.partial.mar</a></td>
+				<td>4M</td>
+				<td>09-Oct-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171008220130.partial.mar</a></td>
+				<td>1M</td>
+				<td>08-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171009100134.partial.mar</a></td>
+				<td>3M</td>
+				<td>09-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171009220104.partial.mar</a></td>
+				<td>4M</td>
+				<td>09-Oct-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008131700-20171010100200.partial.mar</a></td>
+				<td>5M</td>
+				<td>10-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171009100134.partial.mar</a></td>
+				<td>3M</td>
+				<td>09-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171009220104.partial.mar</a></td>
+				<td>4M</td>
+				<td>09-Oct-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171010100200.partial.mar</a></td>
+				<td>5M</td>
+				<td>10-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171008220130-20171010220102.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 23:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171009220104.partial.mar</a></td>
+				<td>4M</td>
+				<td>09-Oct-2017 23:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171010100200.partial.mar</a></td>
+				<td>5M</td>
+				<td>10-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171010220102.partial.mar</a></td>
+				<td>5M</td>
+				<td>10-Oct-2017 23:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009100134-20171011100133.partial.mar</a></td>
+				<td>6M</td>
+				<td>11-Oct-2017 18:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171010100200.partial.mar</a></td>
+				<td>4M</td>
+				<td>10-Oct-2017 11:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171010220102.partial.mar</a></td>
+				<td>5M</td>
+				<td>10-Oct-2017 23:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171011100133.partial.mar</a></td>
+				<td>5M</td>
+				<td>11-Oct-2017 18:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171009220104-20171011220113.partial.mar</a></td>
+				<td>6M</td>
+				<td>11-Oct-2017 23:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171010220102.partial.mar</a></td>
+				<td>4M</td>
+				<td>10-Oct-2017 23:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171011100133.partial.mar</a></td>
+				<td>5M</td>
+				<td>11-Oct-2017 18:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171011220113.partial.mar</a></td>
+				<td>5M</td>
+				<td>11-Oct-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171012100228.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010100200-20171012105833.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171011100133.partial.mar</a></td>
+				<td>4M</td>
+				<td>11-Oct-2017 18:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171011220113.partial.mar</a></td>
+				<td>5M</td>
+				<td>11-Oct-2017 23:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171012100228.partial.mar</a></td>
+				<td>5M</td>
+				<td>12-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171010220102-20171012105833.partial.mar</a></td>
+				<td>5M</td>
+				<td>12-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171011220113.partial.mar</a></td>
+				<td>4M</td>
+				<td>11-Oct-2017 23:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171012100228.partial.mar</a></td>
+				<td>4M</td>
+				<td>12-Oct-2017 11:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171012105833.partial.mar</a></td>
+				<td>4M</td>
+				<td>12-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011100133-20171012220111.partial.mar</a></td>
+				<td>5M</td>
+				<td>12-Oct-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171012100228.partial.mar</a></td>
+				<td>4M</td>
+				<td>12-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171012105833.partial.mar</a></td>
+				<td>3M</td>
+				<td>12-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171012220111.partial.mar</a></td>
+				<td>4M</td>
+				<td>12-Oct-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171011220113-20171013100112.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012100228-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012100228-20171012220111.partial.mar</a></td>
+				<td>3M</td>
+				<td>12-Oct-2017 23:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012100228-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012100228-20171013100112.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012100228-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012100228-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 23:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171012220111.partial.mar</a></td>
+				<td>4M</td>
+				<td>12-Oct-2017 23:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171013100112.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 23:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012105833-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 11:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171013100112.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 11:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 23:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 11:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171012220111-20171014220542.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171013220204.partial.mar</a></td>
+				<td>4M</td>
+				<td>13-Oct-2017 23:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171014100219.partial.mar</a></td>
+				<td>5M</td>
+				<td>14-Oct-2017 11:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171014220542.partial.mar</a></td>
+				<td>6M</td>
+				<td>14-Oct-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013100112-20171015100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 12:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171014100219.partial.mar</a></td>
+				<td>5M</td>
+				<td>14-Oct-2017 11:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171014220542.partial.mar</a></td>
+				<td>5M</td>
+				<td>14-Oct-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171015100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 12:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171013220204-20171015220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>15-Oct-2017 23:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171014220542.partial.mar</a></td>
+				<td>3M</td>
+				<td>14-Oct-2017 23:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171015100127.partial.mar</a></td>
+				<td>3M</td>
+				<td>15-Oct-2017 12:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171015220106.partial.mar</a></td>
+				<td>3M</td>
+				<td>15-Oct-2017 23:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014100219-20171016100113.partial.mar</a></td>
+				<td>4M</td>
+				<td>16-Oct-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171015100127.partial.mar</a></td>
+				<td>3M</td>
+				<td>15-Oct-2017 12:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171015220106.partial.mar</a></td>
+				<td>3M</td>
+				<td>15-Oct-2017 23:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171016100113.partial.mar</a></td>
+				<td>4M</td>
+				<td>16-Oct-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171014220542-20171016220427.partial.mar</a></td>
+				<td>4M</td>
+				<td>16-Oct-2017 23:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171015220106.partial.mar</a></td>
+				<td>2M</td>
+				<td>15-Oct-2017 23:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171016100113.partial.mar</a></td>
+				<td>3M</td>
+				<td>16-Oct-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171016220427.partial.mar</a></td>
+				<td>3M</td>
+				<td>16-Oct-2017 23:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015100127-20171017100127.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171016100113.partial.mar</a></td>
+				<td>3M</td>
+				<td>16-Oct-2017 11:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171016220427.partial.mar</a></td>
+				<td>3M</td>
+				<td>16-Oct-2017 23:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171017100127.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171015220106-20171017141229.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 15:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171016220427.partial.mar</a></td>
+				<td>103K</td>
+				<td>16-Oct-2017 23:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171017100127.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171017141229.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 15:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016100113-20171017220415.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 23:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171017100127.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171017141229.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 15:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171017220415.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 23:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171016220427-20171018100140.partial.mar</a></td>
+				<td>5M</td>
+				<td>18-Oct-2017 11:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171017141229.partial.mar</a></td>
+				<td>115K</td>
+				<td>17-Oct-2017 15:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171017220415.partial.mar</a></td>
+				<td>4M</td>
+				<td>17-Oct-2017 23:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171018100140.partial.mar</a></td>
+				<td>4M</td>
+				<td>18-Oct-2017 11:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017100127-20171018220049.partial.mar</a></td>
+				<td>4M</td>
+				<td>18-Oct-2017 23:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171017220415.partial.mar</a></td>
+				<td>4M</td>
+				<td>17-Oct-2017 23:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171018100140.partial.mar</a></td>
+				<td>4M</td>
+				<td>18-Oct-2017 11:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171018220049.partial.mar</a></td>
+				<td>4M</td>
+				<td>18-Oct-2017 23:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017141229-20171019100107.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Oct-2017 11:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171018100140.partial.mar</a></td>
+				<td>3M</td>
+				<td>18-Oct-2017 11:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171018220049.partial.mar</a></td>
+				<td>3M</td>
+				<td>18-Oct-2017 23:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171019100107.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Oct-2017 11:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171017220415-20171019222141.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171018220049.partial.mar</a></td>
+				<td>998K</td>
+				<td>18-Oct-2017 23:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171019100107.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Oct-2017 11:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171019222141.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018100140-20171020100426.partial.mar</a></td>
+				<td>5M</td>
+				<td>20-Oct-2017 11:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171019100107.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Oct-2017 11:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171019222141.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171020100426.partial.mar</a></td>
+				<td>5M</td>
+				<td>20-Oct-2017 11:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171018220049-20171020221129.partial.mar</a></td>
+				<td>5M</td>
+				<td>20-Oct-2017 23:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171019222141.partial.mar</a></td>
+				<td>3M</td>
+				<td>20-Oct-2017 00:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171020100426.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Oct-2017 11:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171020221129.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Oct-2017 23:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019100107-20171021100029.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Oct-2017 11:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171020100426.partial.mar</a></td>
+				<td>3M</td>
+				<td>20-Oct-2017 11:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171020221129.partial.mar</a></td>
+				<td>4M</td>
+				<td>20-Oct-2017 23:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171021100029.partial.mar</a></td>
+				<td>4M</td>
+				<td>21-Oct-2017 11:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171019222141-20171021220121.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Oct-2017 00:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171020221129.partial.mar</a></td>
+				<td>3M</td>
+				<td>20-Oct-2017 23:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171021100029.partial.mar</a></td>
+				<td>3M</td>
+				<td>21-Oct-2017 11:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171021220121.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 00:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020100426-20171022100058.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171021100029.partial.mar</a></td>
+				<td>3M</td>
+				<td>21-Oct-2017 11:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171021220121.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 00:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171022100058.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171020221129-20171022220103.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 23:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171021220121.partial.mar</a></td>
+				<td>1M</td>
+				<td>22-Oct-2017 00:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171022100058.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171022220103.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 23:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021100029-20171023100252.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171022100058.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171022220103.partial.mar</a></td>
+				<td>3M</td>
+				<td>22-Oct-2017 23:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171023100252.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171021220121-20171023220222.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Oct-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171022220103.partial.mar</a></td>
+				<td>1M</td>
+				<td>22-Oct-2017 23:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171023100252.partial.mar</a></td>
+				<td>3M</td>
+				<td>23-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171023220222.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Oct-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022100058-20171024100135.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171023100252.partial.mar</a></td>
+				<td>3M</td>
+				<td>23-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171023220222.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Oct-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171024100135.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171022220103-20171024220325.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171023220222.partial.mar</a></td>
+				<td>3M</td>
+				<td>23-Oct-2017 23:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171024100135.partial.mar</a></td>
+				<td>4M</td>
+				<td>24-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171024220325.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023100252-20171025100449.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171024100135.partial.mar</a></td>
+				<td>4M</td>
+				<td>24-Oct-2017 11:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171024220325.partial.mar</a></td>
+				<td>4M</td>
+				<td>24-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171025100449.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171023220222-20171025230440.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171024220325.partial.mar</a></td>
+				<td>4M</td>
+				<td>24-Oct-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171025100449.partial.mar</a></td>
+				<td>4M</td>
+				<td>25-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171025230440.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024100135-20171026100047.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171025100449.partial.mar</a></td>
+				<td>4M</td>
+				<td>25-Oct-2017 11:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171025230440.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171026100047.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171024220325-20171026221945.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171025230440.partial.mar</a></td>
+				<td>3M</td>
+				<td>26-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171026100047.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 12:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171026221945.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025100449-20171027100103.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171026100047.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171026221945.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171027100103.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171025230440-20171027220059.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 00:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171026221945.partial.mar</a></td>
+				<td>3M</td>
+				<td>27-Oct-2017 00:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171027100103.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171027220059.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 00:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026100047-20171028100423.partial.mar</a></td>
+				<td>5M</td>
+				<td>28-Oct-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171027100103.partial.mar</a></td>
+				<td>4M</td>
+				<td>27-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171027220059.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 00:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171028100423.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171026221945-20171028220326.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 23:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171027220059.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 00:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171028100423.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171028220326.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 23:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027100103-20171029102300.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171028100423.partial.mar</a></td>
+				<td>3M</td>
+				<td>28-Oct-2017 13:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171028220326.partial.mar</a></td>
+				<td>4M</td>
+				<td>28-Oct-2017 23:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171029102300.partial.mar</a></td>
+				<td>4M</td>
+				<td>29-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171027220059-20171029220112.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 02:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171028220326.partial.mar</a></td>
+				<td>3M</td>
+				<td>28-Oct-2017 23:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171029102300.partial.mar</a></td>
+				<td>3M</td>
+				<td>29-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171029220112.partial.mar</a></td>
+				<td>3M</td>
+				<td>30-Oct-2017 02:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171030100132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171030100132.partial.mar</a></td>
+				<td>3M</td>
+				<td>30-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028100423-20171030103605.partial.mar</a></td>
+				<td>4M</td>
+				<td>30-Oct-2017 18:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171029102300.partial.mar</a></td>
+				<td>1M</td>
+				<td>29-Oct-2017 12:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171029220112.partial.mar</a></td>
+				<td>2M</td>
+				<td>30-Oct-2017 02:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171030100132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171030100132.partial.mar</a></td>
+				<td>2M</td>
+				<td>30-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171028220326-20171030103605.partial.mar</a></td>
+				<td>3M</td>
+				<td>30-Oct-2017 18:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171029220112.partial.mar</a></td>
+				<td>2M</td>
+				<td>30-Oct-2017 02:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171030100132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171030100132.partial.mar</a></td>
+				<td>2M</td>
+				<td>30-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171030103605.partial.mar</a></td>
+				<td>3M</td>
+				<td>30-Oct-2017 18:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171031220132.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029102300-20171031235118.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 10:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171030100132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171030100132.partial.mar</a></td>
+				<td>100K</td>
+				<td>30-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171030103605.partial.mar</a></td>
+				<td>3M</td>
+				<td>30-Oct-2017 18:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171031220132.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171029220112-20171031235118.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 10:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171030100132-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171030100132-20171031220132.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171030100132-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171030100132-20171031235118.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 10:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171030100132-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171030100132-20171101104430.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 16:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171031220132.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171031235118.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 10:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171101104430.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 16:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171030103605-20171101220120.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Nov-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171031220132-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171031220132-20171101104430.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 16:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171031220132-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171031220132-20171101220120.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171031220132-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171031220132-20171102100041.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171101104430.partial.mar</a></td>
+				<td>3M</td>
+				<td>01-Nov-2017 16:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171101220120.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171102100041.partial.mar</a></td>
+				<td>4M</td>
+				<td>02-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171031235118-20171102222620.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Nov-2017 00:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171101220120.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Nov-2017 23:22</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171102100041.partial.mar</a></td>
+				<td>4M</td>
+				<td>02-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171102222620.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 00:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101104430-20171103100331.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171102100041.partial.mar</a></td>
+				<td>3M</td>
+				<td>02-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171102222620.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 00:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171103100331.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171101220120-20171103220715.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 23:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171102222620.partial.mar</a></td>
+				<td>3M</td>
+				<td>03-Nov-2017 00:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171103100331.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171103220715.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 23:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102100041-20171104100412.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 11:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171103100331.partial.mar</a></td>
+				<td>3M</td>
+				<td>03-Nov-2017 11:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171103220715.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Nov-2017 23:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171104100412.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 11:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171102222620-20171104220420.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171103220715.partial.mar</a></td>
+				<td>3M</td>
+				<td>03-Nov-2017 23:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171104100412.partial.mar</a></td>
+				<td>4M</td>
+				<td>04-Nov-2017 11:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171104220420.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103100331-20171105100353.partial.mar</a></td>
+				<td>4M</td>
+				<td>05-Nov-2017 11:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171104100412.partial.mar</a></td>
+				<td>4M</td>
+				<td>04-Nov-2017 11:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171104220420.partial.mar</a></td>
+				<td>4M</td>
+				<td>04-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171105100353.partial.mar</a></td>
+				<td>4M</td>
+				<td>05-Nov-2017 11:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171103220715-20171105220721.partial.mar</a></td>
+				<td>4M</td>
+				<td>05-Nov-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171104220420.partial.mar</a></td>
+				<td>3M</td>
+				<td>04-Nov-2017 23:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171105100353.partial.mar</a></td>
+				<td>3M</td>
+				<td>05-Nov-2017 11:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171105220721.partial.mar</a></td>
+				<td>3M</td>
+				<td>05-Nov-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171104100412-20171106100122.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171104220420-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171104220420-20171105100353.partial.mar</a></td>
+				<td>104K</td>
+				<td>05-Nov-2017 11:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171104220420-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171104220420-20171105220721.partial.mar</a></td>
+				<td>1M</td>
+				<td>05-Nov-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171104220420-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171104220420-20171106100122.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 11:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171105100353-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171105100353-20171105220721.partial.mar</a></td>
+				<td>1M</td>
+				<td>05-Nov-2017 23:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171105100353-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171105100353-20171106100122.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-mac-en-US-20171105220721-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-mac-en-US-20171105220721-20171106100122.partial.mar</a></td>
+				<td>3M</td>
+				<td>06-Nov-2017 11:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170919220202-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170919220202-20170921220243.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170920100426-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170920100426-20170921220243.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170920100426-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170920100426-20170922100051.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Sep-2017 13:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170920220431-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170920220431-20170921220243.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170920220431-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170920220431-20170922100051.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Sep-2017 13:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170920220431-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170920220431-20170922220129.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170921220243.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 01:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170922100051.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Sep-2017 13:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170922220129.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921100141-20170923100045.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 13:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170922100051.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Sep-2017 13:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170922220129.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170923100045.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 13:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170921220243-20170923220337.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170922220129.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170923100045.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 13:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170923220337.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922100051-20170924100550.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 14:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170923100045.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Sep-2017 13:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170923220337.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170924100550.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 14:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170922220129-20170924220116.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 02:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170923220337.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170923220337.partial.mar</a></td>
+				<td>4M</td>
+				<td>24-Sep-2017 01:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170924100550.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 14:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170924220116.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 02:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923100045-20170925100307.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 12:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170924100550.partial.mar</a></td>
+				<td>5M</td>
+				<td>24-Sep-2017 14:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170924220116.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 02:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170925100307.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 12:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170923220337-20170925220207.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170924220116.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 02:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170925100307.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 12:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170925220207.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924100550-20170926100259.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170925100307.partial.mar</a></td>
+				<td>5M</td>
+				<td>25-Sep-2017 12:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170925220207.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170926100259.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170924220116-20170926220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170925220207.partial.mar</a></td>
+				<td>4M</td>
+				<td>26-Sep-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170926100259.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170926220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925100307-20170927100120.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170926100259.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170926220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170927100120.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170925220207-20170928100123.partial.mar</a></td>
+				<td>6M</td>
+				<td>28-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170926220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>27-Sep-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170927100120.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170928100123.partial.mar</a></td>
+				<td>6M</td>
+				<td>28-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926100259-20170928220658.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170927100120.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170928100123.partial.mar</a></td>
+				<td>6M</td>
+				<td>28-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170928220658.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170926220106-20170929100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170928100123.partial.mar</a></td>
+				<td>6M</td>
+				<td>28-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170928220658.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170929100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170927100120-20170929220356.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170928220658.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170929100122.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 13:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170929220356.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928100123-20170930100302.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 13:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170929100122.partial.mar</a></td>
+				<td>5M</td>
+				<td>29-Sep-2017 13:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170929220356.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170930100302.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 13:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170928220658-20170930220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20170929220356.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20170930100302.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 13:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20170930220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929100122-20171001100335.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20170930100302.partial.mar</a></td>
+				<td>5M</td>
+				<td>30-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20170930220116.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20171001100335.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Oct-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170929220356-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20170930220116.partial.mar</a></td>
+				<td>4M</td>
+				<td>01-Oct-2017 01:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20171001100335.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Oct-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20171001220301.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930100302-20171002100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Oct-2017 13:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171001100335.partial.mar</a></td>
+				<td>5M</td>
+				<td>01-Oct-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171001220301.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171002100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Oct-2017 13:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20170930220116-20171002220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171001220301.partial.mar</a></td>
+				<td>4M</td>
+				<td>02-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171002100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Oct-2017 13:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171002220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001100335-20171003100226.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171002100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>02-Oct-2017 13:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171002220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171003100226.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171001220301-20171003220138.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Oct-2017 00:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171002220204.partial.mar</a></td>
+				<td>4M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171003100226.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171003220138.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Oct-2017 00:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002100134-20171004100049.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 12:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171003100226.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171003220138.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Oct-2017 00:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171004100049.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 12:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171002220204-20171004220309.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171003220138.partial.mar</a></td>
+				<td>5M</td>
+				<td>04-Oct-2017 00:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171004100049.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 12:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171004220309.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003100226-20171005100211.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Oct-2017 12:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171004100049.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 12:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171004220309.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171005100211.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Oct-2017 12:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171003220138-20171005220204.partial.mar</a></td>
+				<td>8M</td>
+				<td>06-Oct-2017 01:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171004220309.partial.mar</a></td>
+				<td>6M</td>
+				<td>05-Oct-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171005100211.partial.mar</a></td>
+				<td>6M</td>
+				<td>05-Oct-2017 12:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171005220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Oct-2017 01:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004100049-20171006100327.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171005100211.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Oct-2017 12:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171005220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 01:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171006100327.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171004220309-20171006220306.partial.mar</a></td>
+				<td>6M</td>
+				<td>07-Oct-2017 00:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171005220204.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Oct-2017 01:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171006100327.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171006220306.partial.mar</a></td>
+				<td>5M</td>
+				<td>07-Oct-2017 00:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005100211-20171007100142.partial.mar</a></td>
+				<td>6M</td>
+				<td>07-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171006100327.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171006220306.partial.mar</a></td>
+				<td>6M</td>
+				<td>07-Oct-2017 00:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171007100142.partial.mar</a></td>
+				<td>6M</td>
+				<td>07-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171005220204-20171007220156.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171006220306.partial.mar</a></td>
+				<td>6M</td>
+				<td>07-Oct-2017 00:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171007100142.partial.mar</a></td>
+				<td>6M</td>
+				<td>07-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171007220156.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006100327-20171008131700.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 16:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171007100142.partial.mar</a></td>
+				<td>6M</td>
+				<td>07-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171007220156.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171008131700.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 16:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171006220306-20171008220130.partial.mar</a></td>
+				<td>6M</td>
+				<td>09-Oct-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171007220156.partial.mar</a></td>
+				<td>4M</td>
+				<td>08-Oct-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171008131700.partial.mar</a></td>
+				<td>5M</td>
+				<td>08-Oct-2017 16:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171008220130.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007100142-20171009100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 12:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171008131700.partial.mar</a></td>
+				<td>5M</td>
+				<td>08-Oct-2017 16:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171008220130.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 00:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171009100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 12:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171007220156-20171009220104.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171008220130.partial.mar</a></td>
+				<td>4M</td>
+				<td>09-Oct-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171009100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 12:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171009220104.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008131700-20171010100200.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 13:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171009100134.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 12:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171009220104.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 01:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171010100200.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 13:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171008220130-20171010220102.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 01:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171009220104.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171010100200.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171010220102.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 01:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009100134-20171011100133.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 17:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171010100200.partial.mar</a></td>
+				<td>6M</td>
+				<td>10-Oct-2017 13:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171010220102.partial.mar</a></td>
+				<td>6M</td>
+				<td>11-Oct-2017 01:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171011100133.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 17:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171009220104-20171011220113.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 01:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171010220102.partial.mar</a></td>
+				<td>6M</td>
+				<td>11-Oct-2017 01:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171011100133.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 17:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171011220113.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 01:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171012100228.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 14:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010100200-20171012105833.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 15:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171011100133.partial.mar</a></td>
+				<td>6M</td>
+				<td>11-Oct-2017 17:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171011220113.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 01:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171012100228.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 14:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171010220102-20171012105833.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 15:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171011220113.partial.mar</a></td>
+				<td>5M</td>
+				<td>12-Oct-2017 01:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171012100228.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 14:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171012105833.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 15:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011100133-20171012220111.partial.mar</a></td>
+				<td>6M</td>
+				<td>13-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171012100228.partial.mar</a></td>
+				<td>5M</td>
+				<td>12-Oct-2017 14:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171012105833.partial.mar</a></td>
+				<td>5M</td>
+				<td>12-Oct-2017 15:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171012220111.partial.mar</a></td>
+				<td>6M</td>
+				<td>13-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171011220113-20171013100112.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 13:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012100228-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012100228-20171012220111.partial.mar</a></td>
+				<td>6M</td>
+				<td>13-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012100228-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012100228-20171013100112.partial.mar</a></td>
+				<td>6M</td>
+				<td>13-Oct-2017 13:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012100228-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012100228-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171012220111.partial.mar</a></td>
+				<td>6M</td>
+				<td>13-Oct-2017 00:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171013100112.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 13:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012105833-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171013100112.partial.mar</a></td>
+				<td>6M</td>
+				<td>13-Oct-2017 13:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171013220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>14-Oct-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171014100219.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 12:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171012220111-20171014220542.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171013220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>14-Oct-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171014100219.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171014220542.partial.mar</a></td>
+				<td>7M</td>
+				<td>15-Oct-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013100112-20171015100127.partial.mar</a></td>
+				<td>7M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171014100219.partial.mar</a></td>
+				<td>6M</td>
+				<td>14-Oct-2017 12:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171014220542.partial.mar</a></td>
+				<td>7M</td>
+				<td>15-Oct-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171015100127.partial.mar</a></td>
+				<td>7M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171013220204-20171015220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 02:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171014220542.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 01:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171015100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171015220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 02:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014100219-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 14:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171015100127.partial.mar</a></td>
+				<td>5M</td>
+				<td>15-Oct-2017 14:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171015220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>16-Oct-2017 02:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 14:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171014220542-20171016220427.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 01:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171015220106.partial.mar</a></td>
+				<td>4M</td>
+				<td>16-Oct-2017 02:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 14:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171016220427.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 01:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015100127-20171017100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 14:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171016220427.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 01:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171017100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171015220106-20171017141229.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 18:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171016220427.partial.mar</a></td>
+				<td>4M</td>
+				<td>17-Oct-2017 01:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171017100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171017141229.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 18:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016100113-20171017220415.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 01:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171017100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171017141229.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 18:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171017220415.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 01:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171016220427-20171018100140.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171017141229.partial.mar</a></td>
+				<td>4M</td>
+				<td>17-Oct-2017 18:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171017220415.partial.mar</a></td>
+				<td>5M</td>
+				<td>18-Oct-2017 01:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171018100140.partial.mar</a></td>
+				<td>5M</td>
+				<td>18-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017100127-20171018220049.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171017220415.partial.mar</a></td>
+				<td>5M</td>
+				<td>18-Oct-2017 01:10</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171018100140.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171018220049.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017141229-20171019100107.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Oct-2017 13:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171018100140.partial.mar</a></td>
+				<td>5M</td>
+				<td>18-Oct-2017 12:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171018220049.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171019100107.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Oct-2017 13:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171017220415-20171019222141.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 01:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171018220049.partial.mar</a></td>
+				<td>4M</td>
+				<td>19-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171019100107.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Oct-2017 13:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171019222141.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 01:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018100140-20171020100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171019100107.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Oct-2017 13:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171019222141.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 01:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171020100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171018220049-20171020221129.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Oct-2017 01:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171019222141.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 01:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171020100426.partial.mar</a></td>
+				<td>5M</td>
+				<td>20-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171020221129.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Oct-2017 01:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019100107-20171021100029.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171020100426.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171020221129.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Oct-2017 01:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171021100029.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171019222141-20171021220121.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 02:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171020221129.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Oct-2017 01:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171021100029.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171021220121.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Oct-2017 02:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020100426-20171022100058.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171021100029.partial.mar</a></td>
+				<td>5M</td>
+				<td>21-Oct-2017 13:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171021220121.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Oct-2017 02:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171022100058.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171020221129-20171022220103.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171021220121.partial.mar</a></td>
+				<td>4M</td>
+				<td>22-Oct-2017 02:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171022100058.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171022220103.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021100029-20171023100252.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 13:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171022100058.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171022220103.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171023100252.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 13:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171021220121-20171023220222.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171022220103.partial.mar</a></td>
+				<td>4M</td>
+				<td>23-Oct-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171023100252.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Oct-2017 13:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171023220222.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022100058-20171024100135.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171023100252.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Oct-2017 13:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171023220222.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171024100135.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171022220103-20171024220325.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171023220222.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 00:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171024100135.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171024220325.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023100252-20171025100449.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171024100135.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Oct-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171024220325.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171025100449.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171023220222-20171025230440.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Oct-2017 02:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171024220325.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 00:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171025100449.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171025230440.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Oct-2017 02:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024100135-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 15:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171025100449.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171025230440.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Oct-2017 02:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 15:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171024220325-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171025230440.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Oct-2017 02:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171026100047.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Oct-2017 15:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025100449-20171027100103.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 15:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171026100047.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Oct-2017 15:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171027100103.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 15:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171025230440-20171027220059.partial.mar</a></td>
+				<td>14M</td>
+				<td>28-Oct-2017 03:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171026221945.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Oct-2017 02:35</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171027100103.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Oct-2017 15:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171027220059.partial.mar</a></td>
+				<td>14M</td>
+				<td>28-Oct-2017 03:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026100047-20171028100423.partial.mar</a></td>
+				<td>14M</td>
+				<td>28-Oct-2017 15:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171027100103.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Oct-2017 15:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171027220059.partial.mar</a></td>
+				<td>14M</td>
+				<td>28-Oct-2017 03:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171028100423.partial.mar</a></td>
+				<td>14M</td>
+				<td>28-Oct-2017 15:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171026221945-20171028220326.partial.mar</a></td>
+				<td>14M</td>
+				<td>29-Oct-2017 01:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171027220059.partial.mar</a></td>
+				<td>14M</td>
+				<td>28-Oct-2017 03:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171028100423.partial.mar</a></td>
+				<td>14M</td>
+				<td>28-Oct-2017 15:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171028220326.partial.mar</a></td>
+				<td>14M</td>
+				<td>29-Oct-2017 01:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027100103-20171029102300.partial.mar</a></td>
+				<td>14M</td>
+				<td>29-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171028100423.partial.mar</a></td>
+				<td>6M</td>
+				<td>28-Oct-2017 15:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171028220326.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 01:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171029102300.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171027220059-20171029220112.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 04:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171028220326.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 01:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171029102300.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171029220112.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 04:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028100423-20171030103605.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171029102300.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 14:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171029220112.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 04:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171030103605.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171031220132.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 09:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171028220326-20171031235118.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 10:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171029220112.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 04:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171030103605.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171031220132.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 09:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029102300-20171031235118.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 10:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171030103605.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171031220132.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 09:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171031235118.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 10:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171029220112-20171101104430.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 16:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171031220132.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 09:54</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171031235118.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 10:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171101104430.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 16:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171030103605-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171031220132-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171031220132-20171101104430.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 16:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171031220132-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171031220132-20171101220120.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171031220132-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171031220132-20171102100041.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 12:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171101104430.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Nov-2017 16:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171101220120.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171102100041.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 12:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171031235118-20171102222620.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 01:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171101220120.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171102100041.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 12:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171102222620.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 01:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101104430-20171103100331.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 12:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171102100041.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Nov-2017 12:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171102222620.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Nov-2017 01:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171103100331.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Nov-2017 12:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171101220120-20171103220715.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171102222620.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Nov-2017 01:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171103100331.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Nov-2017 12:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171103220715.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102100041-20171104100412.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 12:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171103100331.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Nov-2017 12:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171103220715.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171104100412.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171102222620-20171104220420.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Nov-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171103220715.partial.mar</a></td>
+				<td>6M</td>
+				<td>04-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171104100412.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171104220420.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Nov-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103100331-20171105100353.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Nov-2017 12:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171104100412.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 12:45</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171104220420.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Nov-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171105100353.partial.mar</a></td>
+				<td>8M</td>
+				<td>05-Nov-2017 12:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171103220715-20171105220721.partial.mar</a></td>
+				<td>8M</td>
+				<td>06-Nov-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171104220420.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Nov-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171105100353.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Nov-2017 12:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171105220721.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Nov-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171104100412-20171106100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Nov-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171104220420-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171104220420-20171105100353.partial.mar</a></td>
+				<td>4M</td>
+				<td>05-Nov-2017 12:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171104220420-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171104220420-20171105220721.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Nov-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171104220420-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171104220420-20171106100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171105100353-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171105100353-20171105220721.partial.mar</a></td>
+				<td>4M</td>
+				<td>06-Nov-2017 00:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171105100353-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171105100353-20171106100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win32-en-US-20171105220721-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win32-en-US-20171105220721-20171106100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Nov-2017 12:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170919220202-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170919220202-20170921220243.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170920100426-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170920100426-20170921220243.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170920100426-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170920100426-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170920220431-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170920220431-20170921220243.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Sep-2017 01:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170920220431-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170920220431-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170920220431-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170920220431-20170922220129.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 02:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170921220243.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170921220243.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Sep-2017 01:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170922220129.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 02:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921100141-20170923100045.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170922100051.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170922100051.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Sep-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170922220129.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 02:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170923100045.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170921220243-20170924100550.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Sep-2017 14:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170922220129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170922220129.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 02:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170923100045.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Sep-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170924100550.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 14:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922100051-20170924220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 02:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170923100045.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170923100045.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Sep-2017 13:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170924100550.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 14:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170924220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 02:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170922220129-20170925100307.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 13:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170924100550.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170924100550.partial.mar</a></td>
+				<td>6M</td>
+				<td>24-Sep-2017 14:30</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170924220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 02:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170925100307.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 13:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170923100045-20170925220207.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170924220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170924220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 02:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170925100307.partial.mar</a></td>
+				<td>6M</td>
+				<td>25-Sep-2017 13:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170925220207.partial.mar</a></td>
+				<td>6M</td>
+				<td>26-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924100550-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170925100307.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170925100307.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Sep-2017 13:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170925220207.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170924220116-20170926220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170925220207.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170925220207.partial.mar</a></td>
+				<td>5M</td>
+				<td>26-Sep-2017 01:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 13:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170926220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925100307-20170927100120.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 13:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170926100259.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170926100259.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Sep-2017 13:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170926220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170927100120.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 13:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170925220207-20170928100123.partial.mar</a></td>
+				<td>8M</td>
+				<td>28-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170926220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170926220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 01:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170927100120.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Sep-2017 13:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170928100123.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926100259-20170928220658.partial.mar</a></td>
+				<td>8M</td>
+				<td>29-Sep-2017 01:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170927100120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170927100120.partial.mar</a></td>
+				<td>6M</td>
+				<td>27-Sep-2017 13:29</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170928100123.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170928220658.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 01:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170926220106-20170929100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170928100123.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170928100123.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Sep-2017 13:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170928220658.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 01:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170929100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 13:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170927100120-20170929220356.partial.mar</a></td>
+				<td>8M</td>
+				<td>30-Sep-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170928220658.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170928220658.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Sep-2017 01:24</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170929100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Sep-2017 13:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170929220356.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928100123-20170930100302.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170929100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170929100122.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Sep-2017 13:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170929220356.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170930100302.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170928220658-20170930220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20170929220356.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20170929220356.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20170930100302.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Sep-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20170930220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929100122-20171001100335.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 13:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20170930100302.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20170930100302.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Sep-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20170930220116.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20171001100335.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 13:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170929220356-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 01:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20170930220116.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20170930220116.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 01:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20171001100335.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Oct-2017 13:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 01:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930100302-20171002100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171001100335.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171001100335.partial.mar</a></td>
+				<td>6M</td>
+				<td>01-Oct-2017 13:03</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171001220301.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 01:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171002100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20170930220116-20171002220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171001220301.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171001220301.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 01:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171002100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171002220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001100335-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171002100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171002100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>02-Oct-2017 13:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171002220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:49</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171001220301-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171002220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171002220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>03-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171003100226.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002100134-20171004100049.partial.mar</a></td>
+				<td>9M</td>
+				<td>04-Oct-2017 12:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171003100226.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171003100226.partial.mar</a></td>
+				<td>6M</td>
+				<td>03-Oct-2017 12:48</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171004100049.partial.mar</a></td>
+				<td>9M</td>
+				<td>04-Oct-2017 12:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171002220204-20171004220309.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171003220138.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171003220138.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171004100049.partial.mar</a></td>
+				<td>9M</td>
+				<td>04-Oct-2017 12:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171004220309.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003100226-20171005100211.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Oct-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171004100049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171004100049.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Oct-2017 12:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171004220309.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171005100211.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Oct-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171003220138-20171005220204.partial.mar</a></td>
+				<td>9M</td>
+				<td>06-Oct-2017 01:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171004220309.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171004220309.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Oct-2017 01:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171005100211.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Oct-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171005220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 01:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004100049-20171006100327.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Oct-2017 13:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171005100211.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171005100211.partial.mar</a></td>
+				<td>6M</td>
+				<td>05-Oct-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171005220204.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Oct-2017 01:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171006100327.partial.mar</a></td>
+				<td>6M</td>
+				<td>06-Oct-2017 13:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171004220309-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171005220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171005220204.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 01:26</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171006100327.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 13:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005100211-20171007100142.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 12:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171006100327.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171006100327.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Oct-2017 13:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171007100142.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 12:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171005220204-20171007220156.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 00:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171006220306.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171006220306.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171007100142.partial.mar</a></td>
+				<td>7M</td>
+				<td>07-Oct-2017 12:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171007220156.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 00:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006100327-20171008131700.partial.mar</a></td>
+				<td>7M</td>
+				<td>08-Oct-2017 16:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171007100142.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171007100142.partial.mar</a></td>
+				<td>8M</td>
+				<td>07-Oct-2017 12:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171007220156.partial.mar</a></td>
+				<td>7M</td>
+				<td>08-Oct-2017 00:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171008131700.partial.mar</a></td>
+				<td>8M</td>
+				<td>08-Oct-2017 16:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171006220306-20171008220130.partial.mar</a></td>
+				<td>8M</td>
+				<td>09-Oct-2017 01:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171007220156.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171007220156.partial.mar</a></td>
+				<td>6M</td>
+				<td>08-Oct-2017 00:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171008131700.partial.mar</a></td>
+				<td>7M</td>
+				<td>08-Oct-2017 16:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171008220130.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 01:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007100142-20171009100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171008131700.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171008131700.partial.mar</a></td>
+				<td>7M</td>
+				<td>08-Oct-2017 16:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171008220130.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 01:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171009100134.partial.mar</a></td>
+				<td>6M</td>
+				<td>09-Oct-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171007220156-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171008220130.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171008220130.partial.mar</a></td>
+				<td>5M</td>
+				<td>09-Oct-2017 01:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171009100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008131700-20171010100200.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 13:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171009100134.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171009100134.partial.mar</a></td>
+				<td>7M</td>
+				<td>09-Oct-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171009220104.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171010100200.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 13:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171008220130-20171010220102.partial.mar</a></td>
+				<td>9M</td>
+				<td>11-Oct-2017 01:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171009220104.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171009220104.partial.mar</a></td>
+				<td>7M</td>
+				<td>10-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171010100200.partial.mar</a></td>
+				<td>8M</td>
+				<td>10-Oct-2017 13:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171010220102.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 01:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009100134-20171011100133.partial.mar</a></td>
+				<td>9M</td>
+				<td>11-Oct-2017 17:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171010100200.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171010100200.partial.mar</a></td>
+				<td>7M</td>
+				<td>10-Oct-2017 13:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171010220102.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 01:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171011100133.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 17:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171009220104-20171011220113.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 01:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171010220102.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171010220102.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 01:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171011100133.partial.mar</a></td>
+				<td>8M</td>
+				<td>11-Oct-2017 17:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171011220113.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 01:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171012100228.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 14:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010100200-20171012105833.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 16:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171011100133.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171011100133.partial.mar</a></td>
+				<td>7M</td>
+				<td>11-Oct-2017 17:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171011220113.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 01:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171012100228.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 14:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171010220102-20171012105833.partial.mar</a></td>
+				<td>8M</td>
+				<td>12-Oct-2017 16:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171011220113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171011220113.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 01:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171012100228.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 14:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171012105833.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 16:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011100133-20171012220111.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171012100228.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171012100228.partial.mar</a></td>
+				<td>6M</td>
+				<td>12-Oct-2017 14:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171012105833.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171012105833.partial.mar</a></td>
+				<td>7M</td>
+				<td>12-Oct-2017 16:06</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171012220111.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171011220113-20171013100112.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012100228-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012100228-20171012220111.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012100228-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012100228-20171013100112.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012100228-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012100228-20171013220204.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 02:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171012220111.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171012220111.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 00:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171013100112.partial.mar</a></td>
+				<td>8M</td>
+				<td>13-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171013220204.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 02:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012105833-20171014100219.partial.mar</a></td>
+				<td>9M</td>
+				<td>14-Oct-2017 13:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171013100112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171013100112.partial.mar</a></td>
+				<td>7M</td>
+				<td>13-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 02:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 13:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171012220111-20171014220542.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 02:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171013220204.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171013220204.partial.mar</a></td>
+				<td>7M</td>
+				<td>14-Oct-2017 02:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 13:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171014220542.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 02:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013100112-20171015100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 15:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171014100219.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171014100219.partial.mar</a></td>
+				<td>8M</td>
+				<td>14-Oct-2017 13:11</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171014220542.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 02:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171015100127.partial.mar</a></td>
+				<td>8M</td>
+				<td>15-Oct-2017 15:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171013220204-20171015220106.partial.mar</a></td>
+				<td>8M</td>
+				<td>16-Oct-2017 02:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171014220542.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171014220542.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 02:15</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171015100127.partial.mar</a></td>
+				<td>7M</td>
+				<td>15-Oct-2017 15:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171015220106.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 02:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014100219-20171016100113.partial.mar</a></td>
+				<td>7M</td>
+				<td>16-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171015100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171015100127.partial.mar</a></td>
+				<td>6M</td>
+				<td>15-Oct-2017 15:16</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171015220106.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 02:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171014220542-20171016220427.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 02:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171015220106.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171015220106.partial.mar</a></td>
+				<td>5M</td>
+				<td>16-Oct-2017 02:08</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171016220427.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 02:04</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015100127-20171017100127.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171016100113.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171016100113.partial.mar</a></td>
+				<td>6M</td>
+				<td>16-Oct-2017 14:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171016220427.partial.mar</a></td>
+				<td>6M</td>
+				<td>17-Oct-2017 02:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171017100127.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171015220106-20171017141229.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 18:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171016220427.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171016220427.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 02:05</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171017100127.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171017141229.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 18:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016100113-20171017220415.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171017100127.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171017100127.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 13:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171017141229.partial.mar</a></td>
+				<td>7M</td>
+				<td>17-Oct-2017 18:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171017220415.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171016220427-20171018100140.partial.mar</a></td>
+				<td>7M</td>
+				<td>18-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171017141229.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171017141229.partial.mar</a></td>
+				<td>5M</td>
+				<td>17-Oct-2017 18:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171017220415.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171018100140.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017100127-20171018220049.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Oct-2017 01:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171017220415.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171017220415.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 01:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171018100140.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171018220049.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Oct-2017 01:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017141229-20171019100107.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 13:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171018100140.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171018100140.partial.mar</a></td>
+				<td>6M</td>
+				<td>18-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171018220049.partial.mar</a></td>
+				<td>6M</td>
+				<td>19-Oct-2017 01:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171019100107.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 13:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171017220415-20171019222141.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 02:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171018220049.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171018220049.partial.mar</a></td>
+				<td>5M</td>
+				<td>19-Oct-2017 01:25</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171019100107.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 13:52</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171019222141.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 02:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018100140-20171020100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171019100107.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171019100107.partial.mar</a></td>
+				<td>7M</td>
+				<td>19-Oct-2017 13:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171019222141.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 02:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171020100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171018220049-20171020221129.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171019222141.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171019222141.partial.mar</a></td>
+				<td>6M</td>
+				<td>20-Oct-2017 02:33</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171020100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171020221129.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019100107-20171021100029.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171020100426.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171020100426.partial.mar</a></td>
+				<td>7M</td>
+				<td>20-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171020221129.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171021100029.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171019222141-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171020221129.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171020221129.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 01:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171021100029.partial.mar</a></td>
+				<td>6M</td>
+				<td>21-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171021220121.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 02:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020100426-20171022100058.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 13:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171021100029.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171021100029.partial.mar</a></td>
+				<td>7M</td>
+				<td>21-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171021220121.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 02:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171022100058.partial.mar</a></td>
+				<td>7M</td>
+				<td>22-Oct-2017 13:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171020221129-20171022220103.partial.mar</a></td>
+				<td>7M</td>
+				<td>23-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171021220121.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171021220121.partial.mar</a></td>
+				<td>5M</td>
+				<td>22-Oct-2017 02:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171022100058.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 13:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171022220103.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021100029-20171023100252.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171022100058.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171022100058.partial.mar</a></td>
+				<td>6M</td>
+				<td>22-Oct-2017 13:31</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171022220103.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171023100252.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171021220121-20171023220222.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171022220103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171022220103.partial.mar</a></td>
+				<td>5M</td>
+				<td>23-Oct-2017 01:14</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171023100252.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171023220222.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022100058-20171024100135.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171023100252.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171023100252.partial.mar</a></td>
+				<td>6M</td>
+				<td>23-Oct-2017 13:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171023220222.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171024100135.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171022220103-20171024220325.partial.mar</a></td>
+				<td>8M</td>
+				<td>25-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171023220222.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171023220222.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 00:38</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171024100135.partial.mar</a></td>
+				<td>8M</td>
+				<td>24-Oct-2017 13:18</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171024220325.partial.mar</a></td>
+				<td>8M</td>
+				<td>25-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023100252-20171025100449.partial.mar</a></td>
+				<td>8M</td>
+				<td>25-Oct-2017 13:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171024100135.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171024100135.partial.mar</a></td>
+				<td>7M</td>
+				<td>24-Oct-2017 13:19</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171024220325.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171025100449.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 13:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171023220222-20171025230440.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171024220325.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171024220325.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 01:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171025100449.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 13:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171025230440.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024100135-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 15:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171025100449.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171025100449.partial.mar</a></td>
+				<td>7M</td>
+				<td>25-Oct-2017 13:32</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171025230440.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 15:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171024220325-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171025230440.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171025230440.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 02:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 15:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025100449-20171027100103.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Oct-2017 15:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171026100047.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171026100047.partial.mar</a></td>
+				<td>7M</td>
+				<td>26-Oct-2017 15:23</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171027100103.partial.mar</a></td>
+				<td>8M</td>
+				<td>27-Oct-2017 15:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171025230440-20171027220059.partial.mar</a></td>
+				<td>17M</td>
+				<td>28-Oct-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171026221945.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171026221945.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 02:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171027100103.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 15:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171027220059.partial.mar</a></td>
+				<td>17M</td>
+				<td>28-Oct-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026100047-20171028100423.partial.mar</a></td>
+				<td>17M</td>
+				<td>28-Oct-2017 17:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171027100103.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171027100103.partial.mar</a></td>
+				<td>7M</td>
+				<td>27-Oct-2017 15:28</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171027220059.partial.mar</a></td>
+				<td>17M</td>
+				<td>28-Oct-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171028100423.partial.mar</a></td>
+				<td>17M</td>
+				<td>28-Oct-2017 17:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171026221945-20171028220326.partial.mar</a></td>
+				<td>17M</td>
+				<td>29-Oct-2017 01:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171027220059.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171027220059.partial.mar</a></td>
+				<td>16M</td>
+				<td>28-Oct-2017 03:17</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171028100423.partial.mar</a></td>
+				<td>16M</td>
+				<td>28-Oct-2017 17:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171028220326.partial.mar</a></td>
+				<td>16M</td>
+				<td>29-Oct-2017 01:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027100103-20171029102300.partial.mar</a></td>
+				<td>16M</td>
+				<td>29-Oct-2017 14:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171028100423.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171028100423.partial.mar</a></td>
+				<td>7M</td>
+				<td>28-Oct-2017 17:01</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171028220326.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 01:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171029102300.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 14:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171027220059-20171029220112.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 05:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171028220326.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171028220326.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 01:36</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171029102300.partial.mar</a></td>
+				<td>7M</td>
+				<td>29-Oct-2017 14:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171029220112.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 05:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028100423-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171029102300.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171029102300.partial.mar</a></td>
+				<td>6M</td>
+				<td>29-Oct-2017 14:57</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171029220112.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 05:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171031220132.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 08:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171028220326-20171031235118.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 10:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171029220112.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171029220112.partial.mar</a></td>
+				<td>6M</td>
+				<td>30-Oct-2017 05:07</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171031220132.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 08:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029102300-20171031235118.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 10:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171030103605.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171030103605.partial.mar</a></td>
+				<td>7M</td>
+				<td>30-Oct-2017 20:42</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171031220132.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 08:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171031235118.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 10:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171029220112-20171101104430.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 16:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171031220132.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171031220132.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 08:12</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171031235118.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171031235118.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 10:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171101104430.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 16:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171030103605-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171031220132-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171031220132-20171101104430.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 16:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171031220132-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171031220132-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:41</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171031220132-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171031220132-20171102100041.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 12:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171101104430.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171101104430.partial.mar</a></td>
+				<td>7M</td>
+				<td>01-Nov-2017 16:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171102100041.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 12:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171031235118-20171102222620.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171101220120.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171101220120.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 00:40</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171102100041.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 12:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171102222620.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101104430-20171103100331.partial.mar</a></td>
+				<td>8M</td>
+				<td>03-Nov-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171102100041.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171102100041.partial.mar</a></td>
+				<td>7M</td>
+				<td>02-Nov-2017 12:53</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171102222620.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171103100331.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171101220120-20171103220715.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 00:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171102222620.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171102222620.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 01:27</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171103100331.partial.mar</a></td>
+				<td>8M</td>
+				<td>03-Nov-2017 13:00</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171103220715.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 00:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102100041-20171104100412.partial.mar</a></td>
+				<td>9M</td>
+				<td>04-Nov-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171103100331.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171103100331.partial.mar</a></td>
+				<td>7M</td>
+				<td>03-Nov-2017 12:59</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171103220715.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 00:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171104100412.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 12:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171102222620-20171104220420.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Nov-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171103220715.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171103220715.partial.mar</a></td>
+				<td>7M</td>
+				<td>04-Nov-2017 00:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171104100412.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 12:46</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171104220420.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Nov-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103100331-20171105100353.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Nov-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171104100412.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171104100412.partial.mar</a></td>
+				<td>8M</td>
+				<td>04-Nov-2017 12:47</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171104220420.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Nov-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171105100353.partial.mar</a></td>
+				<td>9M</td>
+				<td>05-Nov-2017 12:43</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171103220715-20171105220721.partial.mar</a></td>
+				<td>9M</td>
+				<td>06-Nov-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171104220420.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171104220420.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Nov-2017 00:58</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171105100353.partial.mar</a></td>
+				<td>7M</td>
+				<td>05-Nov-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171105220721.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Nov-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171104100412-20171106100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Nov-2017 12:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171104220420-20171105100353.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171104220420-20171105100353.partial.mar</a></td>
+				<td>5M</td>
+				<td>05-Nov-2017 12:44</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171104220420-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171104220420-20171105220721.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Nov-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171104220420-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171104220420-20171106100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Nov-2017 12:51</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171105100353-20171105220721.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171105100353-20171105220721.partial.mar</a></td>
+				<td>5M</td>
+				<td>06-Nov-2017 00:55</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171105100353-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171105100353-20171106100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Nov-2017 12:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/firefox-mozilla-central-58.0a1-win64-en-US-20171105220721-20171106100122.partial.mar">firefox-mozilla-central-58.0a1-win64-en-US-20171105220721-20171106100122.partial.mar</a></td>
+				<td>7M</td>
+				<td>06-Nov-2017 12:50</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/jsshell-linux-i686.zip">jsshell-linux-i686.zip</a></td>
+				<td>8M</td>
+				<td>15-Feb-2018 12:39</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/jsshell-linux-x86_64.zip">jsshell-linux-x86_64.zip</a></td>
+				<td>10M</td>
+				<td>15-Feb-2018 12:13</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/jsshell-mac.zip">jsshell-mac.zip</a></td>
+				<td>10M</td>
+				<td>15-Feb-2018 11:37</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/jsshell-mac64.zip">jsshell-mac64.zip</a></td>
+				<td>10M</td>
+				<td>13-Dec-2016 12:02</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/jsshell-win32.zip">jsshell-win32.zip</a></td>
+				<td>8M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/jsshell-win64.zip">jsshell-win64.zip</a></td>
+				<td>9M</td>
+				<td>15-Feb-2018 13:20</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/mozharness.zip">mozharness.zip</a></td>
+				<td>2M</td>
+				<td>15-Feb-2018 13:21</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/setup-stub.exe">setup-stub.exe</a></td>
+				<td>1M</td>
+				<td>26-Jul-2017 11:56</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/setup.exe">setup.exe</a></td>
+				<td>643K</td>
+				<td>26-Jul-2017 12:09</td>
+			</tr>
+			
+			
+			
+			<tr>
+				<td>File</td>
+				<td><a href="/pub/firefox/nightly/latest-mozilla-central/toolchains.json">toolchains.json</a></td>
+				<td>1K</td>
+				<td>26-Jul-2017 12:09</td>
+			</tr>
+			
+			
+		</table>
+	</body>
+</html>
diff --git a/tools/wpt/tests/test_stability.py b/tools/wpt/tests/test_stability.py
new file mode 100644
index 0000000..f2dd012
--- /dev/null
+++ b/tools/wpt/tests/test_stability.py
@@ -0,0 +1,7 @@
+from tools.wpt import stability
+
+def test_is_inconsistent():
+    assert stability.is_inconsistent({"PASS": 10}, 10) is False
+    assert stability.is_inconsistent({"PASS": 9}, 10) is True
+    assert stability.is_inconsistent({"PASS": 9, "FAIL": 1}, 10) is True
+    assert stability.is_inconsistent({"PASS": 8, "FAIL": 1}, 10) is True
diff --git a/tools/wpt/tests/test_wpt.py b/tools/wpt/tests/test_wpt.py
index d340217..ada7cc7 100644
--- a/tools/wpt/tests/test_wpt.py
+++ b/tools/wpt/tests/test_wpt.py
@@ -1,7 +1,10 @@
+import errno
 import os
 import shutil
 import socket
 import subprocess
+import sys
+import tempfile
 import time
 import urllib2
 
@@ -10,7 +13,45 @@
 from tools.wpt import wpt
 
 
-# Tests currently don't work on Windows for path reasons
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+def is_port_8000_in_use():
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    try:
+        s.bind(("127.0.0.1", 8000))
+    except socket.error as e:
+        if e.errno == errno.EADDRINUSE:
+            return True
+        else:
+            raise e
+    finally:
+        s.close()
+    return False
+
+
+@pytest.fixture(scope="module")
+def manifest_dir():
+    def update_manifest():
+        with pytest.raises(SystemExit) as excinfo:
+            wpt.main(argv=["manifest", "--no-download", "--path", os.path.join(path, "MANIFEST.json")])
+        assert excinfo.value.code == 0
+
+    if os.environ.get('TRAVIS') == "true":
+        path = "~/meta"
+        update_manifest()
+        yield path
+    else:
+        try:
+            path = tempfile.mkdtemp()
+            old_path = os.path.join(wpt.localpaths.repo_root, "MANIFEST.json")
+            if os.path.exists(os.path.join(wpt.localpaths.repo_root, "MANIFEST.json")):
+                shutil.copyfile(old_path, os.path.join(path, "MANIFEST.json"))
+            update_manifest()
+            yield path
+        finally:
+            shutil.rmtree(path)
+
 
 def test_missing():
     with pytest.raises(SystemExit):
@@ -25,17 +66,89 @@
     assert excinfo.value.code == 0
 
 
-def test_run_firefox():
+@pytest.mark.slow
+def test_list_tests(manifest_dir):
+    """The `--list-tests` option should not produce an error under normal
+    conditions."""
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run", "--metadata", manifest_dir, "--list-tests",
+                       "--yes", "chrome", "/dom/nodes/Element-tagName.html"])
+    assert excinfo.value.code == 0
+
+
+@pytest.mark.slow
+def test_list_tests_missing_manifest(manifest_dir):
+    """The `--list-tests` option should not produce an error in the absence of
+    a test manifest file."""
+
+    os.remove(os.path.join(manifest_dir, "MANIFEST.json"))
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run",
+                       # This test triggers the creation of a new manifest
+                       # file which is not necessary to ensure successful
+                       # process completion. Specifying the current directory
+                       # as the tests source via the --tests` option
+                       # drastically reduces the time to execute the test.
+                       "--tests", here,
+                       "--metadata", manifest_dir,
+                       "--list-tests",
+                       "--yes",
+                       "firefox", "/dom/nodes/Element-tagName.html"])
+
+    assert excinfo.value.code == 0
+
+
+@pytest.mark.slow
+def test_list_tests_invalid_manifest(manifest_dir):
+    """The `--list-tests` option should not produce an error in the presence of
+    a malformed test manifest file."""
+
+    manifest_filename = os.path.join(manifest_dir, "MANIFEST.json")
+
+    assert os.path.isfile(manifest_filename)
+
+    with open(manifest_filename, "a+") as handle:
+        handle.write("extra text which invalidates the file")
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run",
+                       # This test triggers the creation of a new manifest
+                       # file which is not necessary to ensure successful
+                       # process completion. Specifying the current directory
+                       # as the tests source via the --tests` option
+                       # drastically reduces the time to execute the test.
+                       "--tests", here,
+                       "--metadata", manifest_dir,
+                       "--list-tests",
+                       "--yes",
+                       "firefox", "/dom/nodes/Element-tagName.html"])
+
+    assert excinfo.value.code == 0
+
+
+@pytest.mark.slow
+@pytest.mark.remote_network
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
+def test_run_firefox(manifest_dir):
     # TODO: It seems like there's a bug in argparse that makes this argument order required
     # should try to work around that
+    if is_port_8000_in_use():
+        pytest.skip("port 8000 already in use")
+
     os.environ["MOZ_HEADLESS"] = "1"
     try:
-        fx_path = os.path.join(wpt.localpaths.repo_root, "_venv", "firefox")
+        if sys.platform == "darwin":
+            fx_path = os.path.join(wpt.localpaths.repo_root, "_venv", "browsers", "Firefox Nightly.app")
+        else:
+            fx_path = os.path.join(wpt.localpaths.repo_root, "_venv", "browsers", "firefox")
         if os.path.exists(fx_path):
             shutil.rmtree(fx_path)
         with pytest.raises(SystemExit) as excinfo:
             wpt.main(argv=["run", "--no-pause", "--install-browser", "--yes",
-                           "--metadata", "~/meta/",
+                           "--metadata", manifest_dir,
                            "firefox", "/dom/nodes/Element-tagName.html"])
         assert os.path.exists(fx_path)
         shutil.rmtree(fx_path)
@@ -44,14 +157,70 @@
         del os.environ["MOZ_HEADLESS"]
 
 
-def test_run_chrome():
+@pytest.mark.slow
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
+def test_run_chrome(manifest_dir):
+    if is_port_8000_in_use():
+        pytest.skip("port 8000 already in use")
+
     with pytest.raises(SystemExit) as excinfo:
         wpt.main(argv=["run", "--yes", "--no-pause", "--binary-arg", "headless",
-                       "--metadata", "~/meta/",
+                       "--metadata", manifest_dir,
                        "chrome", "/dom/nodes/Element-tagName.html"])
     assert excinfo.value.code == 0
 
 
+@pytest.mark.slow
+@pytest.mark.remote_network
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
+def test_run_zero_tests():
+    """A test execution describing zero tests should be reported as an error
+    even in the presence of the `--no-fail-on-unexpected` option."""
+    if is_port_8000_in_use():
+        pytest.skip("port 8000 already in use")
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run", "--yes", "--no-pause", "--binary-arg", "headless",
+                       "chrome", "/non-existent-dir/non-existent-file.html"])
+    assert excinfo.value.code != 0
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run", "--yes", "--no-pause", "--binary-arg", "headless",
+                       "--no-fail-on-unexpected",
+                       "chrome", "/non-existent-dir/non-existent-file.html"])
+    assert excinfo.value.code != 0
+
+@pytest.mark.slow
+@pytest.mark.remote_network
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
+def test_run_failing_test():
+    """Failing tests should be reported with a non-zero exit status unless the
+    `--no-fail-on-unexpected` option has been specified."""
+    if is_port_8000_in_use():
+        pytest.skip("port 8000 already in use")
+    failing_test = "/infrastructure/expected-fail/failing-test.html"
+
+    assert os.path.isfile("../../%s" % failing_test)
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run", "--yes", "--no-pause", "--binary-arg", "headless",
+                       "chrome", failing_test])
+    assert excinfo.value.code != 0
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run", "--yes", "--no-pause", "--binary-arg", "headless",
+                       "--no-fail-on-unexpected",
+                       "chrome", failing_test])
+    assert excinfo.value.code == 0
+
+
+@pytest.mark.slow
+@pytest.mark.remote_network
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
 def test_install_chromedriver():
     chromedriver_path = os.path.join(wpt.localpaths.repo_root, "_venv", "bin", "chromedriver")
     if os.path.exists(chromedriver_path):
@@ -63,8 +232,16 @@
     os.unlink(chromedriver_path)
 
 
+@pytest.mark.slow
+@pytest.mark.remote_network
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
 def test_install_firefox():
-    fx_path = os.path.join(wpt.localpaths.repo_root, "_venv", "firefox")
+
+    if sys.platform == "darwin":
+        fx_path = os.path.join(wpt.localpaths.repo_root, "_venv", "browsers", "Firefox Nightly.app")
+    else:
+        fx_path = os.path.join(wpt.localpaths.repo_root, "_venv", "browsers", "firefox")
     if os.path.exists(fx_path):
         shutil.rmtree(fx_path)
     with pytest.raises(SystemExit) as excinfo:
@@ -74,6 +251,8 @@
     shutil.rmtree(fx_path)
 
 
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
 def test_files_changed(capsys):
     commit = "9047ac1d9f51b1e9faa4f9fad9c47d109609ab09"
     with pytest.raises(SystemExit) as excinfo:
@@ -109,24 +288,28 @@
     assert compile_ignore_rule("foobar/baz/**").pattern == "^foobar/baz/.*$"
 
 
-def test_tests_affected(capsys):
+@pytest.mark.slow  # this updates the manifest
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
+def test_tests_affected(capsys, manifest_dir):
     # This doesn't really work properly for random commits because we test the files in
     # the current working directory for references to the changed files, not the ones at
-    # that specific commit. But we can at least test it returns something sensible
-    commit = "9047ac1d9f51b1e9faa4f9fad9c47d109609ab09"
+    # that specific commit. But we can at least test it returns something sensible.
+    # The test will fail if the file we assert is renamed, so we choose a stable one.
+    commit = "3a055e818218f548db240c316654f3cc1aeeb733"
     with pytest.raises(SystemExit) as excinfo:
-        wpt.main(argv=["tests-affected", "--metadata", "~/meta/", "%s~..%s" % (commit, commit)])
+        wpt.main(argv=["tests-affected", "--metadata", manifest_dir, "%s~..%s" % (commit, commit)])
     assert excinfo.value.code == 0
     out, err = capsys.readouterr()
-    assert "html/browsers/offline/appcache/workers/appcache-worker.html" in out
+    assert "infrastructure/reftest-wait.html" in out
 
 
+@pytest.mark.slow
+@pytest.mark.xfail(sys.platform == "win32",
+                   reason="Tests currently don't work on Windows for path reasons")
 def test_serve():
-    def test():
-        s = socket.socket()
-        s.connect(("127.0.0.1", 8000))
-    with pytest.raises(socket.error):
-        test()
+    if is_port_8000_in_use():
+        pytest.skip("port 8000 already in use")
 
     p = subprocess.Popen([os.path.join(wpt.localpaths.repo_root, "wpt"), "serve"],
                          preexec_fn=os.setsid)
@@ -134,8 +317,10 @@
     start = time.time()
     try:
         while True:
+            if p.poll() is not None:
+                assert False, "server not running"
             if time.time() - start > 60:
-                assert False
+                assert False, "server did not start responding within 60s"
             try:
                 resp = urllib2.urlopen("http://web-platform.test:8000")
                 print resp
diff --git a/tools/wpt/tox.ini b/tools/wpt/tox.ini
index 5836a52..229bc4b 100644
--- a/tools/wpt/tox.ini
+++ b/tools/wpt/tox.ini
@@ -1,10 +1,9 @@
 [tox]
-envlist = py27
+envlist = py27,py27-flake8
 skipsdist=True
 
 [testenv]
 deps =
-  flake8
   pytest
   pytest-cov
   hypothesis
@@ -13,9 +12,41 @@
   -r{toxinidir}/../wptrunner/requirements_firefox.txt
 
 commands =
-  pytest --cov
-  flake8
+  pytest --cov {posargs}
+
+[testenv:py27-flake8]
+# flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
+deps =
+     flake8==3.5.0
+     pycodestyle==2.3.1
+     pyflakes==1.6.0
+     pep8-naming==0.4.1
+
+commands =
+     flake8 {posargs}
 
 [flake8]
-ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E901,F401,F821,F841
+# flake8 config should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
+select = E,W,F,N
+# E128: continuation line under-indented for visual indent
+# E129: visually indented line with same indent as next logical line
+# E221: multiple spaces before operator
+# E226: missing whitespace around arithmetic operator
+# E231: missing whitespace after ‘,’, ‘;’, or ‘:’
+# E251: unexpected spaces around keyword / parameter equals
+# E265: block comment should start with ‘# ‘
+# E302: expected 2 blank lines, found 0
+# E303: too many blank lines (3)
+# E305: expected 2 blank lines after end of function or class
+# E402: module level import not at top of file
+# E731: do not assign a lambda expression, use a def
+# E901: SyntaxError or IndentationError
+# W601: .has_key() is deprecated, use ‘in’
+# F401: module imported but unused
+# F403: ‘from module import *’ used; unable to detect undefined names
+# F405: name may be undefined, or defined from star imports: module
+# F841: local variable name is assigned to but never used
+# N801: class names should use CapWords convention
+# N802: function name should be lowercase
+ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E731,E901,W601,F401,F403,F405,F841,N801,N802
 max-line-length = 141
diff --git a/tools/wpt/update.py b/tools/wpt/update.py
new file mode 100644
index 0000000..a4a7b62
--- /dev/null
+++ b/tools/wpt/update.py
@@ -0,0 +1,32 @@
+import os
+import sys
+
+wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+sys.path.insert(0, os.path.abspath(os.path.join(wpt_root, "tools")))
+
+
+def create_parser_update():
+    from wptrunner import wptcommandline
+
+    return wptcommandline.create_parser_update()
+
+
+def update_expectations(venv, **kwargs):
+    from wptrunner import wptcommandline
+    from wptrunner.update import setup_logging, WPTUpdate
+
+    if not kwargs["tests_root"]:
+        kwargs["tests_root"] = wpt_root
+
+    if not kwargs["manifest_path"]:
+        kwargs["manifest_path"] = os.path.join(wpt_root, "MANIFEST.json")
+
+    if "product" not in kwargs["extra_property"]:
+        kwargs["extra_property"].append("product")
+
+    wptcommandline.check_args_update(kwargs)
+
+    logger = setup_logging(kwargs, {"mach": sys.stdout})
+
+    updater = WPTUpdate(logger, **kwargs)
+    updater.run()
diff --git a/tools/wpt/utils.py b/tools/wpt/utils.py
index e1f4cd4..e8edc0b 100644
--- a/tools/wpt/utils.py
+++ b/tools/wpt/utils.py
@@ -27,7 +27,7 @@
                 value = value()
             if not value:
                 if err_fn is not None:
-                    return err_fn(kwargs, "Failed to find %s" % desc)
+                    return err_fn(self, "Failed to find %s" % desc)
                 else:
                     return
             self[name] = value
diff --git a/tools/wpt/virtualenv.py b/tools/wpt/virtualenv.py
index 8f36aa9..b8454c9 100644
--- a/tools/wpt/virtualenv.py
+++ b/tools/wpt/virtualenv.py
@@ -1,4 +1,5 @@
 import os
+import shutil
 import sys
 import logging
 from distutils.spawn import find_executable
@@ -21,7 +22,7 @@
     def create(self):
         if os.path.exists(self.path):
             shutil.rmtree(self.path)
-        call(self.virtualenv, self.path)
+        call(self.virtualenv, self.path, "-p", sys.executable)
 
     @property
     def bin_path(self):
diff --git a/tools/wptrunner/MANIFEST.in b/tools/wptrunner/MANIFEST.in
index 2be4d91..d36344f 100644
--- a/tools/wptrunner/MANIFEST.in
+++ b/tools/wptrunner/MANIFEST.in
@@ -1,9 +1,6 @@
 exclude MANIFEST.in
 include requirements.txt
-include wptrunner/browsers/b2g_setup/*
 include wptrunner.default.ini
 include wptrunner/testharness_runner.html
 include wptrunner/*.js
 include wptrunner/executors/*.js
-include wptrunner/config.json
-include wptrunner/browsers/server-locations.txt
\ No newline at end of file
diff --git a/tools/wptrunner/OWNERS b/tools/wptrunner/OWNERS
deleted file mode 100644
index 4bf48ad..0000000
--- a/tools/wptrunner/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-@bobholt
diff --git a/tools/wptrunner/README.rst b/tools/wptrunner/README.rst
index 780518a..8917922 100644
--- a/tools/wptrunner/README.rst
+++ b/tools/wptrunner/README.rst
@@ -23,7 +23,7 @@
 following are most significant:
 
 ``--product`` (defaults to `firefox`)
-  The product to test against: `b2g`, `chrome`, `firefox`, or `servo`.
+  The product to test against: `chrome`, `firefox`, or `servo`.
 
 ``--binary`` (required if product is `firefox` or `servo`)
   The path to a binary file for the product (browser) to test against.
@@ -179,7 +179,7 @@
 
   key: value
 
-Note that unlike ini files, only `:` is a valid seperator; `=` will
+Note that unlike ini files, only `:` is a valid separator; `=` will
 not work as expected. Key-value pairs may also have conditional
 values of the form::
 
@@ -197,7 +197,7 @@
 
   if debug and (platform == "linux" or platform == "osx"): FAIL
 
-For test expectations the avaliable variables are those in the
+For test expectations the available variables are those in the
 `run_info` which for desktop are `version`, `os`, `bits`, `processor`,
 `debug` and `product`.
 
diff --git a/tools/wptrunner/docs/conf.py b/tools/wptrunner/docs/conf.py
index 39e5cc4..b58f313 100644
--- a/tools/wptrunner/docs/conf.py
+++ b/tools/wptrunner/docs/conf.py
@@ -186,22 +186,22 @@
 # -- Options for LaTeX output ---------------------------------------------
 
 latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
+    # The paper size ('letterpaper' or 'a4paper').
+    #'papersize': 'letterpaper',
 
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
+    # The font size ('10pt', '11pt' or '12pt').
+    #'pointsize': '10pt',
 
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+    # Additional stuff for the LaTeX preamble.
+    #'preamble': '',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-  ('index', 'wptrunner.tex', u'wptrunner Documentation',
-   u'James Graham', 'manual'),
+    ('index', 'wptrunner.tex', u'wptrunner Documentation',
+     u'James Graham', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -244,9 +244,9 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'wptrunner', u'wptrunner Documentation',
-   u'James Graham', 'wptrunner', 'One line description of project.',
-   'Miscellaneous'),
+    ('index', 'wptrunner', u'wptrunner Documentation',
+     u'James Graham', 'wptrunner', 'One line description of project.',
+     'Miscellaneous'),
 ]
 
 # Documents to append as an appendix to all manuals.
@@ -264,4 +264,4 @@
 
 # Example configuration for intersphinx: refer to the Python standard library.
 intersphinx_mapping = {'python': ('http://docs.python.org/', None),
-                       'mozlog': ('http://mozbase.readthedocs.org/en/latest/', None)}
+                       'mozlog': ('https://firefox-source-docs.mozilla.org/', None)}
diff --git a/tools/wptrunner/docs/usage.rst b/tools/wptrunner/docs/usage.rst
index 8e74a43..258cca6 100644
--- a/tools/wptrunner/docs/usage.rst
+++ b/tools/wptrunner/docs/usage.rst
@@ -54,7 +54,7 @@
 takes multiple options, of which the following are most significant:
 
 ``--product`` (defaults to `firefox`)
-  The product to test against: `b2g`, `chrome`, `firefox`, or `servo`.
+  The product to test against: `chrome`, `firefox`, or `servo`.
 
 ``--binary`` (required if product is `firefox` or `servo`)
   The path to a binary file for the product (browser) to test against.
diff --git a/tools/wptrunner/requirements.txt b/tools/wptrunner/requirements.txt
index 8281f6d..6da3a33 100644
--- a/tools/wptrunner/requirements.txt
+++ b/tools/wptrunner/requirements.txt
@@ -1,5 +1,5 @@
-html5lib >= 0.99
-mozinfo >= 0.7
-mozlog >= 3.5
-mozdebug >= 0.1
-urllib3[secure]
+html5lib == 1.0.1
+mozinfo == 0.10
+mozlog == 3.7
+mozdebug == 0.1
+urllib3[secure] == 1.22
diff --git a/tools/wptrunner/requirements_chrome.txt b/tools/wptrunner/requirements_chrome.txt
index a2f5442..f10d718 100644
--- a/tools/wptrunner/requirements_chrome.txt
+++ b/tools/wptrunner/requirements_chrome.txt
@@ -1,2 +1,2 @@
-mozprocess >= 0.19
-selenium >= 2.41.0
+mozprocess == 0.26
+selenium==3.12.0
diff --git a/tools/wptrunner/requirements_chrome_android.txt b/tools/wptrunner/requirements_chrome_android.txt
new file mode 100644
index 0000000..f10d718
--- /dev/null
+++ b/tools/wptrunner/requirements_chrome_android.txt
@@ -0,0 +1,2 @@
+mozprocess == 0.26
+selenium==3.12.0
diff --git a/tools/wptrunner/requirements_edge.txt b/tools/wptrunner/requirements_edge.txt
index a2f5442..f10d718 100644
--- a/tools/wptrunner/requirements_edge.txt
+++ b/tools/wptrunner/requirements_edge.txt
@@ -1,2 +1,2 @@
-mozprocess >= 0.19
-selenium >= 2.41.0
+mozprocess == 0.26
+selenium==3.12.0
diff --git a/tools/wptrunner/requirements_firefox.txt b/tools/wptrunner/requirements_firefox.txt
index 44b7f5a..0eb26e7 100644
--- a/tools/wptrunner/requirements_firefox.txt
+++ b/tools/wptrunner/requirements_firefox.txt
@@ -1,6 +1,8 @@
-marionette_driver >= 2.4
-mozprofile >= 0.21
-mozprocess >= 0.19
-mozcrash >= 0.13
-mozrunner >= 6.7
-mozleak >= 0.1
+marionette_driver==2.6.0
+mozprofile==1.1.0
+mozprocess == 0.26
+mozcrash == 1.0
+mozrunner == 7.0.0
+mozleak == 0.1
+mozinstall == 1.15
+mozdownload == 1.23
diff --git a/tools/wptrunner/requirements_ie.txt b/tools/wptrunner/requirements_ie.txt
index a2f5442..f10d718 100644
--- a/tools/wptrunner/requirements_ie.txt
+++ b/tools/wptrunner/requirements_ie.txt
@@ -1,2 +1,2 @@
-mozprocess >= 0.19
-selenium >= 2.41.0
+mozprocess == 0.26
+selenium==3.12.0
diff --git a/tools/wptrunner/requirements_opera.txt b/tools/wptrunner/requirements_opera.txt
index a2f5442..f10d718 100644
--- a/tools/wptrunner/requirements_opera.txt
+++ b/tools/wptrunner/requirements_opera.txt
@@ -1,2 +1,2 @@
-mozprocess >= 0.19
-selenium >= 2.41.0
+mozprocess == 0.26
+selenium==3.12.0
diff --git a/tools/wptrunner/requirements_safari.txt b/tools/wptrunner/requirements_safari.txt
new file mode 100644
index 0000000..f10d718
--- /dev/null
+++ b/tools/wptrunner/requirements_safari.txt
@@ -0,0 +1,2 @@
+mozprocess == 0.26
+selenium==3.12.0
diff --git a/tools/wptrunner/requirements_sauce.txt b/tools/wptrunner/requirements_sauce.txt
index 7b828f8..5ea06d3 100644
--- a/tools/wptrunner/requirements_sauce.txt
+++ b/tools/wptrunner/requirements_sauce.txt
@@ -1,2 +1,3 @@
-mozprocess >= 0.19
-selenium >= 3.3.0
+mozprocess == 0.26
+selenium==3.12.0
+requests==2.18.4
diff --git a/tools/wptrunner/requirements_servo.txt b/tools/wptrunner/requirements_servo.txt
index 22bcfa1..9115b7a 100644
--- a/tools/wptrunner/requirements_servo.txt
+++ b/tools/wptrunner/requirements_servo.txt
@@ -1 +1 @@
-mozprocess >= 0.19
+mozprocess == 0.26
diff --git a/tools/wptrunner/requirements_webkit.txt b/tools/wptrunner/requirements_webkit.txt
new file mode 100644
index 0000000..25cc584
--- /dev/null
+++ b/tools/wptrunner/requirements_webkit.txt
@@ -0,0 +1,2 @@
+mozprocess == 0.26
+selenium == 3.9.0
diff --git a/tools/wptrunner/setup.py b/tools/wptrunner/setup.py
index 7ec189f..63d3edd 100644
--- a/tools/wptrunner/setup.py
+++ b/tools/wptrunner/setup.py
@@ -50,16 +50,13 @@
                                   "executors/reftest-wait.js",
                                   "testharnessreport.js",
                                   "testharness_runner.html",
-                                  "config.json",
                                   "wptrunner.default.ini",
-                                  "browsers/server-locations.txt",
-                                  "browsers/b2g_setup/*",
                                   "browsers/sauce_setup/*",
                                   "prefs/*"]},
       include_package_data=True,
       data_files=[("requirements", requirements_files)],
       install_requires=deps
-     )
+      )
 
 if "install" in sys.argv:
     path = os.path.relpath(os.path.join(sys.prefix, "requirements"), os.curdir)
diff --git a/tools/wptrunner/test/test.py b/tools/wptrunner/test/test.py
index 034e317..622934a 100644
--- a/tools/wptrunner/test/test.py
+++ b/tools/wptrunner/test/test.py
@@ -156,7 +156,8 @@
         run(config, args)
     except Exception:
         if args.pdb:
-            import pdb, traceback
+            import pdb
+            import traceback
             print traceback.format_exc()
             pdb.post_mortem()
         else:
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_ref_timeout-ref.html b/tools/wptrunner/test/testdata/reftest/reftest_ref_timeout-ref.html
deleted file mode 100644
index 04cbb71..0000000
--- a/tools/wptrunner/test/testdata/reftest/reftest_ref_timeout-ref.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<html class="reftest-wait">
-<title>rel=match that should time out in the ref</title>
-<link rel=match href=reftest_ref_timeout-ref.html>
-<style>
-:root {background-color:green}
-</style>
diff --git a/tools/wptrunner/test/testdata/reftest/reftest_wait_0.html b/tools/wptrunner/test/testdata/reftest/reftest_wait_0.html
deleted file mode 100644
index 4f92715..0000000
--- a/tools/wptrunner/test/testdata/reftest/reftest_wait_0.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<html class="reftest-wait">
-<title>rel=match that should fail</title>
-<link rel=match href=red.html>
-<style>
-:root {background-color:red}
-</style>
-<script>
-setTimeout(function() {
-  document.documentElement.style.backgroundColor = "green";
-  document.documentElement.className = "";
-}, 2000);
-</script>
-</html>
diff --git a/tools/wptrunner/tox.ini b/tools/wptrunner/tox.ini
index 7909704..f63e2c0 100644
--- a/tools/wptrunner/tox.ini
+++ b/tools/wptrunner/tox.ini
@@ -2,16 +2,61 @@
 xfail_strict=true
 
 [tox]
-envlist = {py27,pypy}-{base,b2g,chrome,firefox,servo}
+envlist = {py27}-{base,chrome,edge,firefox,ie,opera,safari,sauce,servo},py27-flake8
 
 [testenv]
 deps =
      pytest>=2.9
      pytest-cov
      pytest-xdist
+     mock
      -r{toxinidir}/requirements.txt
      chrome: -r{toxinidir}/requirements_chrome.txt
+     edge: -r{toxinidir}/requirements_edge.txt
      firefox: -r{toxinidir}/requirements_firefox.txt
+     ie: -r{toxinidir}/requirements_ie.txt
+     opera: -r{toxinidir}/requirements_opera.txt
+     safari: -r{toxinidir}/requirements_safari.txt
+     sauce: -r{toxinidir}/requirements_sauce.txt
      servo: -r{toxinidir}/requirements_servo.txt
 
-commands = pytest --cov
+commands = pytest {posargs:--cov}
+
+setenv = CURRENT_TOX_ENV = {envname}
+
+[testenv:py27-flake8]
+# flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
+deps =
+     flake8==3.5.0
+     pycodestyle==2.3.1
+     pyflakes==1.6.0
+     pep8-naming==0.4.1
+
+commands =
+     flake8
+
+[flake8]
+# flake8 config should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
+select = E,W,F,N
+# E128: continuation line under-indented for visual indent
+# E129: visually indented line with same indent as next logical line
+# E221: multiple spaces before operator
+# E226: missing whitespace around arithmetic operator
+# E231: missing whitespace after ‘,’, ‘;’, or ‘:’
+# E251: unexpected spaces around keyword / parameter equals
+# E265: block comment should start with ‘# ‘
+# E302: expected 2 blank lines, found 0
+# E303: too many blank lines (3)
+# E305: expected 2 blank lines after end of function or class
+# E402: module level import not at top of file
+# E731: do not assign a lambda expression, use a def
+# E901: SyntaxError or IndentationError
+# W601: .has_key() is deprecated, use ‘in’
+# F401: module imported but unused
+# F403: ‘from module import *’ used; unable to detect undefined names
+# F405: name may be undefined, or defined from star imports: module
+# F841: local variable name is assigned to but never used
+# N801: class names should use CapWords convention
+# N802: function name should be lowercase
+ignore = E128,E129,E221,E226,E231,E251,E265,E302,E303,E305,E402,E731,E901,W601,F401,F403,F405,F841,N801,N802
+max-line-length = 141
diff --git a/tools/wptrunner/wptrunner/browsers/__init__.py b/tools/wptrunner/wptrunner/browsers/__init__.py
index a5c2533..6f0c49e 100644
--- a/tools/wptrunner/wptrunner/browsers/__init__.py
+++ b/tools/wptrunner/wptrunner/browsers/__init__.py
@@ -23,10 +23,13 @@
 """
 
 product_list = ["chrome",
+                "chrome_android",
                 "edge",
                 "firefox",
                 "ie",
+                "safari",
                 "sauce",
                 "servo",
                 "servodriver",
-                "opera"]
+                "opera",
+                "webkit"]
diff --git a/tools/wptrunner/wptrunner/browsers/b2g_setup/certtest_app.zip b/tools/wptrunner/wptrunner/browsers/b2g_setup/certtest_app.zip
deleted file mode 100644
index f9cbd53..0000000
--- a/tools/wptrunner/wptrunner/browsers/b2g_setup/certtest_app.zip
+++ /dev/null
Binary files differ
diff --git a/tools/wptrunner/wptrunner/browsers/chrome.py b/tools/wptrunner/wptrunner/browsers/chrome.py
index 215fccf..e50c592 100644
--- a/tools/wptrunner/wptrunner/browsers/chrome.py
+++ b/tools/wptrunner/wptrunner/browsers/chrome.py
@@ -60,8 +60,7 @@
 
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true"}
+    return {}
 
 
 class ChromeBrowser(Browser):
diff --git a/tools/wptrunner/wptrunner/browsers/chrome_android.py b/tools/wptrunner/wptrunner/browsers/chrome_android.py
new file mode 100644
index 0000000..985b1fe
--- /dev/null
+++ b/tools/wptrunner/wptrunner/browsers/chrome_android.py
@@ -0,0 +1,121 @@
+import subprocess
+
+from ..config import *
+from .base import Browser, ExecutorBrowser, require_arg
+from ..webdriver_server import ChromeDriverServer
+from ..executors import executor_kwargs as base_executor_kwargs
+from ..executors.executorselenium import (SeleniumTestharnessExecutor,
+                                          SeleniumRefTestExecutor)
+from ..executors.executorchrome import ChromeDriverWdspecExecutor
+
+
+__wptrunner__ = {"product": "chrome_android",
+                 "check_args": "check_args",
+                 "browser": "ChromeAndroidBrowser",
+                 "executor": {"testharness": "SeleniumTestharnessExecutor",
+                              "reftest": "SeleniumRefTestExecutor",
+                              "wdspec": "ChromeDriverWdspecExecutor"},
+                 "browser_kwargs": "browser_kwargs",
+                 "executor_kwargs": "executor_kwargs",
+                 "env_extras": "env_extras",
+                 "env_options": "env_options"}
+
+_wptserve_ports = set()
+
+
+def check_args(**kwargs):
+    require_arg(kwargs, "webdriver_binary")
+
+
+def browser_kwargs(test_type, run_info_data, **kwargs):
+    return {"binary": kwargs["binary"],
+            "webdriver_binary": kwargs["webdriver_binary"],
+            "webdriver_args": kwargs.get("webdriver_args")}
+
+
+def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
+                    **kwargs):
+    from selenium.webdriver import DesiredCapabilities
+
+    # Use extend() to modify the global list in place.
+    _wptserve_ports.update(set(
+        server_config['ports']['http'] + server_config['ports']['https'] +
+        server_config['ports']['ws'] + server_config['ports']['wss']
+    ))
+
+    executor_kwargs = base_executor_kwargs(test_type, server_config,
+                                           cache_manager, **kwargs)
+    executor_kwargs["close_after_done"] = True
+    capabilities = dict(DesiredCapabilities.CHROME.items())
+    capabilities["chromeOptions"] = {}
+    # required to start on mobile
+    capabilities["chromeOptions"]["androidPackage"] = "com.google.android.apps.chrome"
+
+    for (kwarg, capability) in [("binary", "binary"), ("binary_args", "args")]:
+        if kwargs[kwarg] is not None:
+            capabilities["chromeOptions"][capability] = kwargs[kwarg]
+    if test_type == "testharness":
+        capabilities["useAutomationExtension"] = False
+        capabilities["excludeSwitches"] = ["enable-automation"]
+    if test_type == "wdspec":
+        capabilities["chromeOptions"]["w3c"] = True
+    executor_kwargs["capabilities"] = capabilities
+    return executor_kwargs
+
+
+def env_extras(**kwargs):
+    return []
+
+
+def env_options():
+    return {}
+
+
+class ChromeAndroidBrowser(Browser):
+    """Chrome is backed by chromedriver, which is supplied through
+    ``wptrunner.webdriver.ChromeDriverServer``.
+    """
+
+    def __init__(self, logger, binary, webdriver_binary="chromedriver",
+                 webdriver_args=None):
+        """Creates a new representation of Chrome.  The `binary` argument gives
+        the browser binary to use for testing."""
+        Browser.__init__(self, logger)
+        self.binary = binary
+        self.server = ChromeDriverServer(self.logger,
+                                         binary=webdriver_binary,
+                                         args=webdriver_args)
+
+    def _adb_run(self, args):
+        self.logger.info('adb ' + ' '.join(args))
+        subprocess.check_call(['adb'] + args)
+
+    def setup(self):
+        self._adb_run(['wait-for-device'])
+        self._adb_run(['forward', '--remove-all'])
+        self._adb_run(['reverse', '--remove-all'])
+        for port in _wptserve_ports:
+            self._adb_run(['reverse', 'tcp:%d' % port, 'tcp:%d' % port])
+
+    def start(self, **kwargs):
+        self.server.start(block=False)
+
+    def stop(self, force=False):
+        self.server.stop(force=force)
+
+    def pid(self):
+        return self.server.pid
+
+    def is_alive(self):
+        # TODO(ato): This only indicates the driver is alive,
+        # and doesn't say anything about whether a browser session
+        # is active.
+        return self.server.is_alive()
+
+    def cleanup(self):
+        self.stop()
+        self._adb_run(['forward', '--remove-all'])
+        self._adb_run(['reverse', '--remove-all'])
+
+    def executor_browser(self):
+        return ExecutorBrowser, {"webdriver_url": self.server.url}
diff --git a/tools/wptrunner/wptrunner/browsers/edge.py b/tools/wptrunner/wptrunner/browsers/edge.py
index db4ae00..b0ec6c1 100644
--- a/tools/wptrunner/wptrunner/browsers/edge.py
+++ b/tools/wptrunner/wptrunner/browsers/edge.py
@@ -16,13 +16,22 @@
                  "env_extras": "env_extras",
                  "env_options": "env_options"}
 
+def get_timeout_multiplier(test_type, run_info_data, **kwargs):
+    if kwargs["timeout_multiplier"] is not None:
+        return kwargs["timeout_multiplier"]
+    if test_type == "wdspec":
+        return 10
+    return 1
 
 def check_args(**kwargs):
     require_arg(kwargs, "webdriver_binary")
 
 def browser_kwargs(test_type, run_info_data, **kwargs):
     return {"webdriver_binary": kwargs["webdriver_binary"],
-            "webdriver_args": kwargs.get("webdriver_args")}
+            "webdriver_args": kwargs.get("webdriver_args"),
+            "timeout_multiplier": get_timeout_multiplier(test_type,
+                                                         run_info_data,
+                                                         **kwargs)}
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
@@ -31,6 +40,9 @@
     executor_kwargs = base_executor_kwargs(test_type, server_config,
                                            cache_manager, **kwargs)
     executor_kwargs["close_after_done"] = True
+    executor_kwargs["timeout_multiplier"] = get_timeout_multiplier(test_type,
+                                                                   run_info_data,
+                                                                   **kwargs)
     executor_kwargs["capabilities"] = dict(DesiredCapabilities.EDGE.items())
     return executor_kwargs
 
@@ -38,20 +50,22 @@
     return []
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true",
-            "supports_debugger": False}
+    return {"supports_debugger": False}
 
 class EdgeBrowser(Browser):
     used_ports = set()
+    init_timeout = 60
 
-    def __init__(self, logger, webdriver_binary, webdriver_args=None):
+    def __init__(self, logger, webdriver_binary, timeout_multiplier=None, webdriver_args=None):
         Browser.__init__(self, logger)
         self.server = EdgeDriverServer(self.logger,
                                        binary=webdriver_binary,
                                        args=webdriver_args)
         self.webdriver_host = "localhost"
         self.webdriver_port = self.server.port
+        if timeout_multiplier:
+            self.init_timeout = self.init_timeout * timeout_multiplier
+
 
     def start(self, **kwargs):
         print self.server.url
diff --git a/tools/wptrunner/wptrunner/browsers/firefox.py b/tools/wptrunner/wptrunner/browsers/firefox.py
index 552cd15..68017ab 100644
--- a/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -1,14 +1,15 @@
+import json
 import os
 import platform
 import signal
 import subprocess
 import sys
+import tempfile
 
 import mozinfo
 import mozleak
 from mozprocess import ProcessHandler
 from mozprofile import FirefoxProfile, Preferences
-from mozprofile.permissions import ServerLocations
 from mozrunner import FirefoxRunner
 from mozrunner.utils import get_stack_fixer_function
 from mozcrash import mozcrash
@@ -23,7 +24,6 @@
 from ..executors.executormarionette import (MarionetteTestharnessExecutor,
                                             MarionetteRefTestExecutor,
                                             MarionetteWdspecExecutor)
-from ..environment import hostnames
 
 
 here = os.path.join(os.path.split(__file__)[0])
@@ -51,7 +51,10 @@
         else:
             return 2
     elif run_info_data["debug"] or run_info_data.get("asan"):
-        return 3
+        if run_info_data.get("ccov"):
+            return 4
+        else:
+            return 3
     return 1
 
 
@@ -77,7 +80,8 @@
                                                          **kwargs),
             "leak_check": kwargs["leak_check"],
             "stylo_threads": kwargs["stylo_threads"],
-            "chaos_mode_flags": kwargs["chaos_mode_flags"]}
+            "chaos_mode_flags": kwargs["chaos_mode_flags"],
+            "config": kwargs["config"]}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
@@ -93,15 +97,15 @@
         executor_kwargs["reftest_internal"] = kwargs["reftest_internal"]
         executor_kwargs["reftest_screenshot"] = kwargs["reftest_screenshot"]
     if test_type == "wdspec":
-        fxOptions = {}
+        options = {}
         if kwargs["binary"]:
-            fxOptions["binary"] = kwargs["binary"]
+            options["binary"] = kwargs["binary"]
         if kwargs["binary_args"]:
-            fxOptions["args"] = kwargs["binary_args"]
-        fxOptions["prefs"] = {
-            "network.dns.localDomains": ",".join(hostnames)
+            options["args"] = kwargs["binary_args"]
+        options["prefs"] = {
+            "network.dns.localDomains": ",".join(server_config.domains_set)
         }
-        capabilities["moz:firefoxOptions"] = fxOptions
+        capabilities["moz:firefoxOptions"] = options
     if kwargs["certutil_binary"] is None:
         capabilities["acceptInsecureCerts"] = True
     if capabilities:
@@ -114,10 +118,13 @@
 
 
 def env_options():
-    return {"host": "127.0.0.1",
-            "external_host": "web-platform.test",
-            "bind_hostname": "false",
-            "certificate_domain": "web-platform.test",
+    # The server host is set to 127.0.0.1 as Firefox is configured (through the
+    # network.dns.localDomains preference set below) to resolve the test
+    # domains to localhost without relying on the network stack.
+    #
+    # https://github.com/w3c/web-platform-tests/pull/9480
+    return {"server_host": "127.0.0.1",
+            "bind_address": False,
             "supports_debugger": True}
 
 
@@ -127,8 +134,8 @@
 
 
 def update_properties():
-    return (["debug", "stylo", "e10s", "os", "version", "processor", "bits"],
-            {"debug", "e10s", "stylo"})
+    return (["debug", "webrender", "e10s", "os", "version", "processor", "bits"],
+            {"debug", "e10s", "webrender"})
 
 
 class FirefoxBrowser(Browser):
@@ -140,7 +147,7 @@
                  symbols_path=None, stackwalk_binary=None, certutil_binary=None,
                  ca_certificate_path=None, e10s=False, stackfix_dir=None,
                  binary_args=None, timeout_multiplier=None, leak_check=False, stylo_threads=1,
-                 chaos_mode_flags=None):
+                 chaos_mode_flags=None, config=None):
         Browser.__init__(self, logger)
         self.binary = binary
         self.prefs_root = prefs_root
@@ -156,6 +163,7 @@
         self.certutil_binary = certutil_binary
         self.e10s = e10s
         self.binary_args = binary_args
+        self.config = config
         if stackfix_dir:
             self.stack_fixer = get_stack_fixer_function(stackfix_dir,
                                                         self.symbols_path)
@@ -186,15 +194,12 @@
         if self.chaos_mode_flags is not None:
             env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
 
-        locations = ServerLocations(filename=os.path.join(here, "server-locations.txt"))
-
         preferences = self.load_prefs()
 
-        self.profile = FirefoxProfile(locations=locations,
-                                      preferences=preferences)
+        self.profile = FirefoxProfile(preferences=preferences)
         self.profile.set_preferences({"marionette.port": self.marionette_port,
                                       "dom.disable_open_during_load": False,
-                                      "network.dns.localDomains": ",".join(hostnames),
+                                      "network.dns.localDomains": ",".join(self.config.domains_set),
                                       "network.proxy.type": 0,
                                       "places.history.enabled": False,
                                       "dom.send_after_paint_to_content": True,
@@ -241,11 +246,23 @@
     def load_prefs(self):
         prefs = Preferences()
 
-        prefs_path = os.path.join(self.prefs_root, "prefs_general.js")
-        if os.path.exists(prefs_path):
-            prefs.add(Preferences.read_prefs(prefs_path))
-        else:
-            self.logger.warning("Failed to find base prefs file in %s" % prefs_path)
+        pref_paths = []
+        prefs_general = os.path.join(self.prefs_root, 'prefs_general.js')
+        if os.path.isfile(prefs_general):
+            # Old preference file used in Firefox 60 and earlier (remove when no longer supported)
+            pref_paths.append(prefs_general)
+
+        profiles = os.path.join(self.prefs_root, 'profiles.json')
+        if os.path.isfile(profiles):
+            with open(profiles, 'r') as fh:
+                for name in json.load(fh)['web-platform-tests']:
+                    pref_paths.append(os.path.join(self.prefs_root, name, 'user.js'))
+
+        for path in pref_paths:
+            if os.path.exists(path):
+                prefs.add(Preferences.read_prefs(path))
+            else:
+                self.logger.warning("Failed to find base prefs file in %s" % path)
 
         # Add any custom preferences
         prefs.add(self.extra_prefs, cast=True)
@@ -298,12 +315,15 @@
 
     def on_output(self, line):
         """Write a line of output from the firefox process to the log"""
-        data = line.decode("utf8", "replace")
-        if self.stack_fixer:
-            data = self.stack_fixer(data)
-        self.logger.process_output(self.pid(),
-                                   data,
-                                   command=" ".join(self.runner.command))
+        if "GLib-GObject-CRITICAL" in line:
+            return
+        if line:
+            data = line.decode("utf8", "replace")
+            if self.stack_fixer:
+                data = self.stack_fixer(data)
+            self.logger.process_output(self.pid(),
+                                      data,
+                                      command=" ".join(self.runner.command))
 
     def is_alive(self):
         if self.runner:
@@ -361,7 +381,7 @@
 
         env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]])
                         if env_var in env else certutil_dir).encode(
-                                sys.getfilesystemencoding() or 'utf-8', 'replace')
+                            sys.getfilesystemencoding() or 'utf-8', 'replace')
 
         def certutil(*args):
             cmd = [self.certutil_binary] + list(args)
diff --git a/tools/wptrunner/wptrunner/browsers/ie.py b/tools/wptrunner/wptrunner/browsers/ie.py
index 553372f..97d96ec 100644
--- a/tools/wptrunner/wptrunner/browsers/ie.py
+++ b/tools/wptrunner/wptrunner/browsers/ie.py
@@ -28,10 +28,10 @@
                     **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
-    ieOptions = {}
-    ieOptions["requireWindowFocus"] = True
+    options = {}
+    options["requireWindowFocus"] = True
     capabilities = {}
-    capabilities["se:ieOptions"] = ieOptions
+    capabilities["se:ieOptions"] = options
     executor_kwargs = base_executor_kwargs(test_type, server_config,
                                            cache_manager, **kwargs)
     executor_kwargs["close_after_done"] = True
@@ -42,16 +42,14 @@
     return []
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true",
-            "supports_debugger": False}
+    return {"supports_debugger": False}
 
 class InternetExplorerBrowser(Browser):
     used_ports = set()
 
     def __init__(self, logger, webdriver_binary, webdriver_args=None):
         Browser.__init__(self, logger)
-        self.server = InterentExplorerDriverServer(self.logger,
+        self.server = InternetExplorerDriverServer(self.logger,
                                                    binary=webdriver_binary,
                                                    args=webdriver_args)
         self.webdriver_host = "localhost"
diff --git a/tools/wptrunner/wptrunner/browsers/opera.py b/tools/wptrunner/wptrunner/browsers/opera.py
index 57edfa0..bfacfa5 100644
--- a/tools/wptrunner/wptrunner/browsers/opera.py
+++ b/tools/wptrunner/wptrunner/browsers/opera.py
@@ -60,8 +60,7 @@
 
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true"}
+    return {}
 
 
 class OperaBrowser(Browser):
diff --git a/tools/wptrunner/wptrunner/browsers/safari.py b/tools/wptrunner/wptrunner/browsers/safari.py
new file mode 100644
index 0000000..3b99d22
--- /dev/null
+++ b/tools/wptrunner/wptrunner/browsers/safari.py
@@ -0,0 +1,84 @@
+from .base import Browser, ExecutorBrowser, require_arg
+from ..webdriver_server import SafariDriverServer
+from ..executors import executor_kwargs as base_executor_kwargs
+from ..executors.executorselenium import (SeleniumTestharnessExecutor,
+                                          SeleniumRefTestExecutor)
+
+
+__wptrunner__ = {"product": "safari",
+                 "check_args": "check_args",
+                 "browser": "SafariBrowser",
+                 "executor": {"testharness": "SeleniumTestharnessExecutor",
+                              "reftest": "SeleniumRefTestExecutor"},
+                 "browser_kwargs": "browser_kwargs",
+                 "executor_kwargs": "executor_kwargs",
+                 "env_extras": "env_extras",
+                 "env_options": "env_options"}
+
+
+def check_args(**kwargs):
+    require_arg(kwargs, "webdriver_binary")
+
+
+def browser_kwargs(test_type, run_info_data, **kwargs):
+    return {"webdriver_binary": kwargs["webdriver_binary"],
+            "webdriver_args": kwargs.get("webdriver_args")}
+
+
+def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
+                    **kwargs):
+    from selenium.webdriver import DesiredCapabilities
+
+    executor_kwargs = base_executor_kwargs(test_type, server_config,
+                                           cache_manager, **kwargs)
+    executor_kwargs["close_after_done"] = True
+    executor_kwargs["capabilities"] = dict(DesiredCapabilities.SAFARI.items())
+    if kwargs["binary"] is not None:
+        raise ValueError("Safari doesn't support setting executable location")
+
+    return executor_kwargs
+
+
+def env_extras(**kwargs):
+    return []
+
+
+def env_options():
+    return {}
+
+
+class SafariBrowser(Browser):
+    """Safari is backed by safaridriver, which is supplied through
+    ``wptrunner.webdriver.SafariDriverServer``.
+    """
+
+    def __init__(self, logger, webdriver_binary, webdriver_args=None):
+        """Creates a new representation of Safari.  The `webdriver_binary`
+        argument gives the WebDriver binary to use for testing. (The browser
+        binary location cannot be specified, as Safari and SafariDriver are
+        coupled.)"""
+        Browser.__init__(self, logger)
+        self.server = SafariDriverServer(self.logger,
+                                         binary=webdriver_binary,
+                                         args=webdriver_args)
+
+    def start(self, **kwargs):
+        self.server.start(block=False)
+
+    def stop(self, force=False):
+        self.server.stop(force=force)
+
+    def pid(self):
+        return self.server.pid
+
+    def is_alive(self):
+        # TODO(ato): This only indicates the driver is alive,
+        # and doesn't say anything about whether a browser session
+        # is active.
+        return self.server.is_alive()
+
+    def cleanup(self):
+        self.stop()
+
+    def executor_browser(self):
+        return ExecutorBrowser, {"webdriver_url": self.server.url}
diff --git a/tools/wptrunner/wptrunner/browsers/sauce.py b/tools/wptrunner/wptrunner/browsers/sauce.py
index 25eeb56..709af41 100644
--- a/tools/wptrunner/wptrunner/browsers/sauce.py
+++ b/tools/wptrunner/wptrunner/browsers/sauce.py
@@ -113,9 +113,7 @@
 
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true",
-            "supports_debugger": False}
+    return {"supports_debugger": False}
 
 
 def get_tar(url, dest):
@@ -134,8 +132,23 @@
         self.sauce_connect_binary = kwargs.get("sauce_connect_binary")
         self.sc_process = None
         self.temp_dir = None
+        self.env_config = None
 
-    def __enter__(self, options):
+    def __call__(self, env_options, env_config):
+        self.env_config = env_config
+
+        return self
+
+    def __enter__(self):
+        # Because this class implements the context manager protocol, it is
+        # possible for instances to be provided to the `with` statement
+        # directly. This class implements the callable protocol so that data
+        # which is not available during object initialization can be provided
+        # prior to this moment. Instances must be invoked in preparation for
+        # the context manager protocol, but this additional constraint is not
+        # itself part of the protocol.
+        assert self.env_config is not None, 'The instance has been invoked.'
+
         if not self.sauce_connect_binary:
             self.temp_dir = tempfile.mkdtemp()
             get_tar("https://saucelabs.com/downloads/sc-4.4.9-linux.tar.gz", self.temp_dir)
@@ -153,16 +166,34 @@
             "--metrics-address=0.0.0.0:9876",
             "--readyfile=./sauce_is_ready",
             "--tunnel-domains",
-            "web-platform.test",
-            "*.web-platform.test"
+            ",".join(self.env_config.domains_set)
         ])
-        while not os.path.exists('./sauce_is_ready') and not self.sc_process.poll():
-            time.sleep(5)
 
-        if self.sc_process.returncode is not None and self.sc_process.returncode > 0:
+        # Timeout config vars
+        each_sleep_secs = 1
+        max_wait = 30
+        kill_wait = 5
+
+        tot_wait = 0
+        while not os.path.exists('./sauce_is_ready') and self.sc_process.poll() is None:
+            if tot_wait >= max_wait:
+                self.sc_process.terminate()
+                while self.sc_process.poll() is None:
+                    time.sleep(each_sleep_secs)
+                    tot_wait += each_sleep_secs
+                    if tot_wait >= (max_wait + kill_wait):
+                        self.sc_process.kill()
+                        break
+                raise SauceException("Sauce Connect Proxy was not ready after %d seconds" % tot_wait)
+
+            time.sleep(each_sleep_secs)
+            tot_wait += each_sleep_secs
+
+        if self.sc_process.returncode is not None:
             raise SauceException("Unable to start Sauce Connect Proxy. Process exited with code %s", self.sc_process.returncode)
 
     def __exit__(self, exc_type, exc_val, exc_tb):
+        self.env_config = None
         self.sc_process.terminate()
         if self.temp_dir and os.path.exists(self.temp_dir):
             try:
diff --git a/tools/wptrunner/wptrunner/browsers/sauce_setup/edge-prerun.bat b/tools/wptrunner/wptrunner/browsers/sauce_setup/edge-prerun.bat
index 4554894..9d0878e 100644
--- a/tools/wptrunner/wptrunner/browsers/sauce_setup/edge-prerun.bat
+++ b/tools/wptrunner/wptrunner/browsers/sauce_setup/edge-prerun.bat
@@ -1,2 +1,9 @@
 @echo off
 reg add "HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\microsoft.microsoftedge_8wekyb3d8bbwe\MicrosoftEdge\New Windows" /v "PopupMgr" /t REG_SZ /d no
+
+
+REM Download and install the Ahem font
+REM - https://wiki.saucelabs.com/display/DOCS/Downloading+Files+to+a+Sauce+Labs+Virtual+Machine+Prior+to+Testing
+REM - https://superuser.com/questions/201896/how-do-i-install-a-font-from-the-windows-command-prompt
+bitsadmin.exe /transfer "JobName" https://github.com/w3c/web-platform-tests/raw/master/fonts/Ahem.ttf "%WINDIR%\Fonts\Ahem.ttf"
+reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Ahem (TrueType)" /t REG_SZ /d Ahem.ttf /f
diff --git a/tools/wptrunner/wptrunner/browsers/sauce_setup/safari-prerun.sh b/tools/wptrunner/wptrunner/browsers/sauce_setup/safari-prerun.sh
index 85c72e6..06c48bd 100644
--- a/tools/wptrunner/wptrunner/browsers/sauce_setup/safari-prerun.sh
+++ b/tools/wptrunner/wptrunner/browsers/sauce_setup/safari-prerun.sh
@@ -1,2 +1,3 @@
 #!/bin/bash
+curl https://raw.githubusercontent.com/w3c/web-platform-tests/master/fonts/Ahem.ttf > ~/Library/Fonts/Ahem.ttf
 defaults write com.apple.Safari com.apple.Safari.ContentPageGroupIdentifier.WebKit2JavaScriptCanOpenWindowsAutomatically -bool true
diff --git a/tools/wptrunner/wptrunner/browsers/server-locations.txt b/tools/wptrunner/wptrunner/browsers/server-locations.txt
deleted file mode 100644
index 5dcaf4b..0000000
--- a/tools/wptrunner/wptrunner/browsers/server-locations.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-http://localhost:8000    primary
-
-http://web-platform.test:8000
-http://www.web-platform.test:8000
-http://www1.web-platform.test:8000
-http://www2.web-platform.test:8000
-http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8000
-http://xn--lve-6lad.web-platform.test:8000
-
-http://web-platform.test:8001
-http://www.web-platform.test:8001
-http://www1.web-platform.test:8001
-http://www2.web-platform.test:8001
-http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8001
-http://xn--lve-6lad.web-platform.test:8001
-
-https://web-platform.test:8443
-https://www.web-platform.test:8443
-https://www1.web-platform.test:8443
-https://www2.web-platform.test:8443
-https://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8443
-https://xn--lve-6lad.web-platform.test:8443
-
-# These are actually ws servers, but until mozprofile is
-# fixed we have to pretend that they are http servers
-http://web-platform.test:8888
-http://www.web-platform.test:8888
-http://www1.web-platform.test:8888
-http://www2.web-platform.test:8888
-http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8888
-http://xn--lve-6lad.web-platform.test:8888
diff --git a/tools/wptrunner/wptrunner/browsers/servo.py b/tools/wptrunner/wptrunner/browsers/servo.py
index 89d69ab..dd54fb0 100644
--- a/tools/wptrunner/wptrunner/browsers/servo.py
+++ b/tools/wptrunner/wptrunner/browsers/servo.py
@@ -42,6 +42,8 @@
     rv = base_executor_kwargs(test_type, server_config,
                               cache_manager, **kwargs)
     rv["pause_after_test"] = kwargs["pause_after_test"]
+    if test_type == "wdspec":
+        rv["capabilities"] = {}
     return rv
 
 
@@ -50,9 +52,8 @@
 
 
 def env_options():
-    return {"host": "127.0.0.1",
-            "external_host": "web-platform.test",
-            "bind_hostname": "true",
+    return {"server_host": "127.0.0.1",
+            "bind_address": False,
             "testharnessreport": "testharnessreport-servo.js",
             "supports_debugger": True}
 
diff --git a/tools/wptrunner/wptrunner/browsers/servodriver.py b/tools/wptrunner/wptrunner/browsers/servodriver.py
index c251de8..f2ee00a 100644
--- a/tools/wptrunner/wptrunner/browsers/servodriver.py
+++ b/tools/wptrunner/wptrunner/browsers/servodriver.py
@@ -1,9 +1,12 @@
 import os
+import shutil
 import subprocess
 import tempfile
 
 from mozprocess import ProcessHandler
 
+from serve.serve import make_hosts_file
+
 from .base import Browser, require_arg, get_free_port, browser_command, ExecutorBrowser
 from ..executors import executor_kwargs as base_executor_kwargs
 from ..executors.executorservodriver import (ServoWebDriverTestharnessExecutor,
@@ -26,14 +29,6 @@
     "update_properties": "update_properties",
 }
 
-hosts_text = """127.0.0.1 web-platform.test
-127.0.0.1 www.web-platform.test
-127.0.0.1 www1.web-platform.test
-127.0.0.1 www2.web-platform.test
-127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1 xn--lve-6lad.web-platform.test
-"""
-
 
 def check_args(**kwargs):
     require_arg(kwargs, "binary")
@@ -58,9 +53,7 @@
 
 
 def env_options():
-    return {"host": "127.0.0.1",
-            "external_host": "web-platform.test",
-            "bind_hostname": "true",
+    return {"server_host": "127.0.0.1",
             "testharnessreport": "testharnessreport-servodriver.js",
             "supports_debugger": True}
 
@@ -69,10 +62,10 @@
     return ["debug", "os", "version", "processor", "bits"], None
 
 
-def make_hosts_file():
+def write_hosts_file(config):
     hosts_fd, hosts_path = tempfile.mkstemp()
     with os.fdopen(hosts_fd, "w") as f:
-        f.write(hosts_text)
+        f.write(make_hosts_file(config, "127.0.0.1"))
     return hosts_path
 
 
@@ -87,7 +80,7 @@
         self.webdriver_port = None
         self.proc = None
         self.debug_info = debug_info
-        self.hosts_path = make_hosts_file()
+        self.hosts_path = write_hosts_file()
         self.command = None
         self.user_stylesheets = user_stylesheets if user_stylesheets else []
 
@@ -158,6 +151,7 @@
 
     def cleanup(self):
         self.stop()
+        shutil.rmtree(os.path.dirname(self.hosts_file))
 
     def executor_browser(self):
         assert self.webdriver_port is not None
diff --git a/tools/wptrunner/wptrunner/browsers/webkit.py b/tools/wptrunner/wptrunner/browsers/webkit.py
new file mode 100644
index 0000000..7d95d43
--- /dev/null
+++ b/tools/wptrunner/wptrunner/browsers/webkit.py
@@ -0,0 +1,98 @@
+from .base import Browser, ExecutorBrowser, require_arg
+from ..executors import executor_kwargs as base_executor_kwargs
+from ..executors.executorselenium import (SeleniumTestharnessExecutor,
+                                          SeleniumRefTestExecutor)
+from ..executors.executorwebkit import WebKitDriverWdspecExecutor
+from ..webdriver_server import WebKitDriverServer
+
+
+__wptrunner__ = {"product": "webkit",
+                 "check_args": "check_args",
+                 "browser": "WebKitBrowser",
+                 "browser_kwargs": "browser_kwargs",
+                 "executor": {"testharness": "SeleniumTestharnessExecutor",
+                              "reftest": "SeleniumRefTestExecutor",
+                              "wdspec": "WebKitDriverWdspecExecutor"},
+                 "executor_kwargs": "executor_kwargs",
+                 "env_extras": "env_extras",
+                 "env_options": "env_options"}
+
+
+def check_args(**kwargs):
+    require_arg(kwargs, "binary")
+    require_arg(kwargs, "webdriver_binary")
+    require_arg(kwargs, "webkit_port")
+
+
+def browser_kwargs(test_type, run_info_data, **kwargs):
+    return {"binary": kwargs["binary"],
+            "webdriver_binary": kwargs["webdriver_binary"],
+            "webdriver_args": kwargs.get("webdriver_args")}
+
+
+def capabilities_for_port(webkit_port, binary, binary_args):
+    from selenium.webdriver import DesiredCapabilities
+
+    if webkit_port == "gtk":
+        capabilities = dict(DesiredCapabilities.WEBKITGTK.copy())
+        capabilities["webkitgtk:browserOptions"] = {
+            "binary": binary,
+            "args": binary_args
+        }
+        return capabilities
+
+    return {}
+
+
+def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
+                    **kwargs):
+    executor_kwargs = base_executor_kwargs(test_type, server_config,
+                                           cache_manager, **kwargs)
+    executor_kwargs["close_after_done"] = True
+    capabilities = capabilities_for_port(kwargs["webkit_port"],
+                                         kwargs["binary"],
+                                         kwargs.get("binary_args", []))
+    executor_kwargs["capabilities"] = capabilities
+    return executor_kwargs
+
+
+def env_extras(**kwargs):
+    return []
+
+
+def env_options():
+    return {}
+
+
+class WebKitBrowser(Browser):
+    """Generic WebKit browser is backed by WebKit's WebDriver implementation,
+    which is supplied through ``wptrunner.webdriver.WebKitDriverServer``.
+    """
+
+    def __init__(self, logger, binary, webdriver_binary=None,
+                 webdriver_args=None):
+        Browser.__init__(self, logger)
+        self.binary = binary
+        self.server = WebKitDriverServer(self.logger, binary=webdriver_binary,
+                                         args=webdriver_args)
+
+    def start(self, **kwargs):
+        self.server.start(block=False)
+
+    def stop(self, force=False):
+        self.server.stop(force=force)
+
+    def pid(self):
+        return self.server.pid
+
+    def is_alive(self):
+        # TODO(ato): This only indicates the driver is alive,
+        # and doesn't say anything about whether a browser session
+        # is active.
+        return self.server.is_alive()
+
+    def cleanup(self):
+        self.stop()
+
+    def executor_browser(self):
+        return ExecutorBrowser, {"webdriver_url": self.server.url}
diff --git a/tools/wptrunner/wptrunner/config.json b/tools/wptrunner/wptrunner/config.json
deleted file mode 100644
index d146424..0000000
--- a/tools/wptrunner/wptrunner/config.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{"host": "%(host)s",
- "ports":{"http":[8000, 8001],
-          "https":[8443],
-          "ws":[8888]},
- "check_subdomains":false,
- "bind_hostname":%(bind_hostname)s,
- "ssl":{}}
diff --git a/tools/wptrunner/wptrunner/environment.py b/tools/wptrunner/wptrunner/environment.py
index e873440..309174c 100644
--- a/tools/wptrunner/wptrunner/environment.py
+++ b/tools/wptrunner/wptrunner/environment.py
@@ -10,20 +10,12 @@
 
 from wptlogging import LogLevelRewriter
 from wptserve.handlers import StringHandler
+from wptserve import sslutils
 
 here = os.path.split(__file__)[0]
 repo_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir, os.pardir))
 
 serve = None
-sslutils = None
-
-
-hostnames = ["web-platform.test",
-             "www.web-platform.test",
-             "www1.web-platform.test",
-             "www2.web-platform.test",
-             "xn--n8j6ds53lwwkrqhv28a.web-platform.test",
-             "xn--lve-6lad.web-platform.test"]
 
 
 def do_delayed_imports(logger, test_paths):
@@ -37,15 +29,8 @@
     try:
         from tools.serve import serve
     except ImportError:
-        from wpt_tools.serve import serve
-    except ImportError:
         failed.append("serve")
 
-    try:
-        import sslutils
-    except ImportError:
-        failed.append("sslutils")
-
     if failed:
         logger.critical(
             "Failed to import %s. Ensure that tests path %s contains web-platform-tests" %
@@ -63,7 +48,7 @@
     elif kwargs["ssl_type"] == "pregenerated":
         args = {"host_key_path": kwargs["host_key_path"],
                 "host_cert_path": kwargs["host_cert_path"],
-                 "ca_cert_path": kwargs["ca_cert_path"]}
+                "ca_cert_path": kwargs["ca_cert_path"]}
     else:
         args = {}
     return args
@@ -86,7 +71,6 @@
         self.ssl_env = ssl_env
         self.server = None
         self.config = None
-        self.external_config = None
         self.pause_after_test = pause_after_test
         self.test_server_port = options.pop("test_server_port", True)
         self.debug_info = debug_info
@@ -95,19 +79,30 @@
         self.cache_manager = multiprocessing.Manager()
         self.stash = serve.stash.StashServer()
         self.env_extras = env_extras
+        self.env_extras_cms = None
 
 
     def __enter__(self):
         self.stash.__enter__()
         self.ssl_env.__enter__()
         self.cache_manager.__enter__()
-        for cm in self.env_extras:
-            cm.__enter__(self.options)
-        self.setup_server_logging()
+
         self.config = self.load_config()
-        serve.set_computed_defaults(self.config)
-        self.external_config, self.servers = serve.start(self.config, self.ssl_env,
-                                                         self.get_routes())
+        self.setup_server_logging()
+
+        assert self.env_extras_cms is None, (
+            "A TestEnvironment object cannot be nested")
+
+        self.env_extras_cms = []
+
+        for env in self.env_extras:
+            cm = env(self.options, self.config)
+            cm.__enter__()
+            self.env_extras_cms.append(cm)
+
+        self.servers = serve.start(self.config,
+                                   self.ssl_env,
+                                   self.get_routes())
         if self.options.get("supports_debugger") and self.debug_info and self.debug_info.interactive:
             self.ignore_interrupts()
         return self
@@ -118,8 +113,11 @@
         for scheme, servers in self.servers.iteritems():
             for port, server in servers:
                 server.kill()
-        for cm in self.env_extras:
+        for cm in self.env_extras_cms:
             cm.__exit__(exc_type, exc_val, exc_tb)
+
+        self.env_extras_cms = None
+
         self.cache_manager.__exit__(exc_type, exc_val, exc_tb)
         self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
         self.stash.__exit__()
@@ -131,34 +129,34 @@
         signal.signal(signal.SIGINT, signal.SIG_DFL)
 
     def load_config(self):
-        default_config_path = os.path.join(serve_path(self.test_paths), "config.default.json")
-        local_config_path = os.path.join(here, "config.json")
+        override_path = os.path.join(serve_path(self.test_paths), "config.json")
 
-        with open(default_config_path) as f:
-            default_config = json.load(f)
+        config = serve.Config(override_ssl_env=self.ssl_env)
 
-        with open(local_config_path) as f:
-            data = f.read()
-            local_config = json.loads(data % self.options)
+        config.ports = {
+            "http": [8000, 8001],
+            "https": [8443],
+            "ws": [8888],
+            "wss": [8889],
+        }
 
-        #TODO: allow non-default configuration for ssl
+        if os.path.exists(override_path):
+            with open(override_path) as f:
+                override_obj = json.load(f)
+            config.update(override_obj)
 
-        local_config["external_host"] = self.options.get("external_host", None)
-        local_config["ssl"]["encrypt_after_connect"] = self.options.get("encrypt_after_connect", False)
+        config.check_subdomains = False
+        config.ssl = {}
 
-        config = serve.merge_json(default_config, local_config)
-        config["doc_root"] = serve_path(self.test_paths)
+        if "browser_host" in self.options:
+            config.browser_host = self.options["browser_host"]
 
-        if not self.ssl_env.ssl_enabled:
-            config["ports"]["https"] = [None]
+        if "bind_address" in self.options:
+            config.bind_address = self.options["bind_address"]
 
-        host = self.options.get("certificate_domain", config["host"])
-        hosts = [host]
-        hosts.extend("%s.%s" % (item[0], host) for item in serve.get_subdomains(host).values())
-        key_file, certificate = self.ssl_env.host_cert_path(hosts)
-
-        config["key_file"] = key_file
-        config["certificate"] = certificate
+        config.server_host = self.options.get("server_host", None)
+        config.ssl["encrypt_after_connect"] = self.options.get("encrypt_after_connect", False)
+        config.doc_root = serve_path(self.test_paths)
 
         return config
 
@@ -186,10 +184,14 @@
         for path, format_args, content_type, route in [
                 ("testharness_runner.html", {}, "text/html", "/testharness_runner.html"),
                 (self.options.get("testharnessreport", "testharnessreport.js"),
-                 {"output": self.pause_after_test}, "text/javascript",
+                 {"output": self.pause_after_test}, "text/javascript;charset=utf8",
                  "/resources/testharnessreport.js")]:
             path = os.path.normpath(os.path.join(here, path))
-            route_builder.add_static(path, format_args, content_type, route)
+            # Note that .headers. files don't apply to static routes, so we need to
+            # readd any static headers here.
+            headers = {"Cache-Control": "max-age=3600"}
+            route_builder.add_static(path, format_args, content_type, route,
+                                     headers=headers)
 
         data = b""
         with open(os.path.join(repo_root, "resources", "testdriver.js"), "rb") as fp:
@@ -211,25 +213,28 @@
 
     def ensure_started(self):
         # Pause for a while to ensure that the server has a chance to start
-        for _ in xrange(20):
+        for _ in xrange(60):
             failed = self.test_servers()
             if not failed:
                 return
             time.sleep(0.5)
-        raise EnvironmentError("Servers failed to start (scheme:port): %s" % ("%s:%s" for item in failed))
+        raise EnvironmentError("Servers failed to start: %s" %
+                               ", ".join("%s:%s" % item for item in failed))
 
     def test_servers(self):
         failed = []
+        host = self.config["server_host"]
         for scheme, servers in self.servers.iteritems():
             for port, server in servers:
                 if self.test_server_port:
                     s = socket.socket()
                     try:
-                        s.connect((self.config["host"], port))
+                        s.connect((host, port))
                     except socket.error:
-                        failed.append((scheme, port))
+                        failed.append((host, port))
                     finally:
                         s.close()
 
                 if not server.is_alive():
                     failed.append((scheme, port))
+        return failed
diff --git a/tools/wptrunner/wptrunner/executors/base.py b/tools/wptrunner/wptrunner/executors/base.py
index b33bc42..08030b3 100644
--- a/tools/wptrunner/wptrunner/executors/base.py
+++ b/tools/wptrunner/wptrunner/executors/base.py
@@ -8,12 +8,13 @@
 from abc import ABCMeta, abstractmethod
 
 from ..testrunner import Stop
+from protocol import Protocol, BaseProtocolPart
 
 here = os.path.split(__file__)[0]
 
 # Extra timeout to use after internal test timeout at which the harness
 # should force a timeout
-extra_timeout = 5 # seconds
+extra_timeout = 5  # seconds
 
 
 def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
@@ -67,8 +68,8 @@
                                       (result_url, test.url))
         harness_result = test.result_cls(self.harness_codes[status], message)
         return (harness_result,
-                [test.subtest_result_cls(name, self.test_codes[status], message, stack)
-                 for name, status, message, stack in subtest_results])
+                [test.subtest_result_cls(st_name, self.test_codes[st_status], st_message, st_stack)
+                 for st_name, st_status, st_message, st_stack in subtest_results])
 
 
 testharness_result_converter = TestharnessResultConverter()
@@ -103,6 +104,7 @@
     test_type = None
     convert_result = None
     supports_testdriver = False
+    supports_jsshell = False
 
     def __init__(self, browser, server_config, timeout_multiplier=1,
                  debug_info=None, **kwargs):
@@ -113,7 +115,7 @@
         :param browser: ExecutorBrowser instance providing properties of the
                         browser that will be tested.
         :param server_config: Dictionary of wptserve server configuration of the
-                              form stored in TestEnvironment.external_config
+                              form stored in TestEnvironment.config
         :param timeout_multiplier: Multiplier relative to base timeout to use
                                    when setting test timeout.
         """
@@ -124,7 +126,7 @@
         self.debug_info = debug_info
         self.last_environment = {"protocol": "http",
                                  "prefs": {}}
-        self.protocol = None # This must be set in subclasses
+        self.protocol = None  # This must be set in subclasses
 
     @property
     def logger(self):
@@ -152,10 +154,10 @@
         :param test: The test to run"""
         if test.environment != self.last_environment:
             self.on_environment_change(test.environment)
-
         try:
             result = self.do_test(test)
         except Exception as e:
+            self.logger.warning(traceback.format_exc(e))
             result = self.result_from_exception(test, e)
 
         if result is Stop:
@@ -171,7 +173,7 @@
 
     def server_url(self, protocol):
         return "%s://%s:%s" % (protocol,
-                               self.server_config["host"],
+                               self.server_config["browser_host"],
                                self.server_config["ports"][protocol][0])
 
     def test_url(self, test):
@@ -192,13 +194,16 @@
         if hasattr(e, "status") and e.status in test.result_cls.statuses:
             status = e.status
         else:
-            status = "ERROR"
+            status = "INTERNAL-ERROR"
         message = unicode(getattr(e, "message", ""))
         if message:
             message += "\n"
         message += traceback.format_exc(e)
         return test.result_cls(status, message), []
 
+    def wait(self):
+        self.protocol.base.wait()
+
 
 class TestharnessExecutor(TestExecutor):
     convert_result = testharness_result_converter
@@ -357,37 +362,16 @@
         return (test.result_cls(*data), [])
 
     def do_wdspec(self, session_config, path, timeout):
-        harness_result = ("OK", None)
-        subtest_results = pytestrunner.run(path,
-                                           self.server_config,
-                                           session_config,
-                                           timeout=timeout)
-        return (harness_result, subtest_results)
+        return pytestrunner.run(path,
+                                self.server_config,
+                                session_config,
+                                timeout=timeout)
 
     def do_delayed_imports(self):
         global pytestrunner
         from . import pytestrunner
 
 
-class Protocol(object):
-    def __init__(self, executor, browser):
-        self.executor = executor
-        self.browser = browser
-
-    @property
-    def logger(self):
-        return self.executor.logger
-
-    def setup(self, runner):
-        pass
-
-    def teardown(self):
-        pass
-
-    def wait(self):
-        pass
-
-
 class WdspecRun(object):
     def __init__(self, func, session, path, timeout):
         self.func = func
@@ -423,14 +407,40 @@
             if message:
                 message += "\n"
             message += traceback.format_exc(e)
-            self.result = False, ("ERROR", message)
+            self.result = False, ("INTERNAL-ERROR", message)
         finally:
             self.result_flag.set()
 
 
+class ConnectionlessBaseProtocolPart(BaseProtocolPart):
+    def execute_script(self, script, async=False):
+        pass
+
+    def set_timeout(self, timeout):
+        pass
+
+    def wait(self):
+        pass
+
+    def set_window(self, handle):
+        pass
+
+
+class ConnectionlessProtocol(Protocol):
+    implements = [ConnectionlessBaseProtocolPart]
+
+    def connect(self):
+        pass
+
+    def after_connect(self):
+        pass
+
+
 class WebDriverProtocol(Protocol):
     server_cls = None
 
+    implements = [ConnectionlessBaseProtocolPart]
+
     def __init__(self, executor, browser):
         Protocol.__init__(self, executor, browser)
         self.webdriver_binary = executor.webdriver_binary
@@ -439,24 +449,21 @@
         self.session_config = None
         self.server = None
 
-    def setup(self, runner):
+    def connect(self):
         """Connect to browser via the HTTP server."""
-        try:
-            self.server = self.server_cls(
-                self.logger,
-                binary=self.webdriver_binary,
-                args=self.webdriver_args)
-            self.server.start(block=False)
-            self.logger.info(
-                "WebDriver HTTP server listening at %s" % self.server.url)
-            self.session_config = {"host": self.server.host,
-                                   "port": self.server.port,
-                                   "capabilities": self.capabilities}
-        except Exception:
-            self.logger.error(traceback.format_exc())
-            self.executor.runner.send_message("init_failed")
-        else:
-            self.executor.runner.send_message("init_succeeded")
+        self.server = self.server_cls(
+            self.logger,
+            binary=self.webdriver_binary,
+            args=self.webdriver_args)
+        self.server.start(block=False)
+        self.logger.info(
+            "WebDriver HTTP server listening at %s" % self.server.url)
+        self.session_config = {"host": self.server.host,
+                               "port": self.server.port,
+                               "capabilities": self.capabilities}
+
+    def after_connect(self):
+        pass
 
     def teardown(self):
         if self.server is not None and self.server.is_alive:
@@ -478,3 +485,98 @@
         conn.request("HEAD", self.server.base_path + "invalid")
         res = conn.getresponse()
         return res.status == 404
+
+
+class CallbackHandler(object):
+    """Handle callbacks from testdriver-using tests.
+
+    The default implementation here makes sense for things that are roughly like
+    WebDriver. Things that are more different to WebDriver may need to create a
+    fully custom implementation."""
+
+    def __init__(self, logger, protocol, test_window):
+        self.protocol = protocol
+        self.test_window = test_window
+        self.logger = logger
+        self.callbacks = {
+            "action": self.process_action,
+            "complete": self.process_complete
+        }
+
+        self.actions = {
+            "click": ClickAction(self.logger, self.protocol),
+            "send_keys": SendKeysAction(self.logger, self.protocol)
+        }
+
+    def __call__(self, result):
+        url, command, payload = result
+        self.logger.debug("Got async callback: %s" % result[1])
+        try:
+            callback = self.callbacks[command]
+        except KeyError:
+            raise ValueError("Unknown callback type %r" % result[1])
+        return callback(url, payload)
+
+    def process_complete(self, url, payload):
+        rv = [url] + payload
+        return True, rv
+
+    def process_action(self, url, payload):
+        parent = self.protocol.base.current_window
+        try:
+            self.protocol.base.set_window(self.test_window)
+            action = payload["action"]
+            self.logger.debug("Got action: %s" % action)
+            try:
+                action_handler = self.actions[action]
+            except KeyError:
+                raise ValueError("Unknown action %s" % action)
+            try:
+                action_handler(payload)
+            except Exception as e:
+                self.logger.warning("Action %s failed" % action)
+                self.logger.warning(traceback.format_exc())
+                self._send_message("complete", "failure")
+            else:
+                self.logger.debug("Action %s completed" % action)
+                self._send_message("complete", "success")
+        finally:
+            self.protocol.base.set_window(parent)
+
+        return False, None
+
+    def _send_message(self, message_type, status, message=None):
+        self.protocol.testdriver.send_message(message_type, status, message=message)
+
+
+class ClickAction(object):
+    def __init__(self, logger, protocol):
+        self.logger = logger
+        self.protocol = protocol
+
+    def __call__(self, payload):
+        selector = payload["selector"]
+        elements = self.protocol.select.elements_by_selector(selector)
+        if len(elements) == 0:
+            raise ValueError("Selector matches no elements")
+        elif len(elements) > 1:
+            raise ValueError("Selector matches multiple elements")
+        self.logger.debug("Clicking element: %s" % selector)
+        self.protocol.click.element(elements[0])
+
+
+class SendKeysAction(object):
+    def __init__(self, logger, protocol):
+        self.logger = logger
+        self.protocol = protocol
+
+    def __call__(self, payload):
+        selector = payload["selector"]
+        keys = payload["keys"]
+        elements = self.protocol.select.elements_by_selector(selector)
+        if len(elements) == 0:
+            raise ValueError("Selector matches no elements")
+        elif len(elements) > 1:
+            raise ValueError("Selector matches multiple elements")
+        self.logger.debug("Sending keys to element: %s" % selector)
+        self.protocol.send_keys.send_keys(elements[0], keys)
diff --git a/tools/wptrunner/wptrunner/executors/executormarionette.py b/tools/wptrunner/wptrunner/executors/executormarionette.py
index edea3f1..bff509c 100644
--- a/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -1,3 +1,4 @@
+import json
 import os
 import socket
 import threading
@@ -11,8 +12,8 @@
 
 here = os.path.join(os.path.split(__file__)[0])
 
-from .base import (ExecutorException,
-                   Protocol,
+from .base import (CallbackHandler,
+                   ExecutorException,
                    RefTestExecutor,
                    RefTestImplementation,
                    TestExecutor,
@@ -24,12 +25,19 @@
                    testharness_result_converter,
                    reftest_result_converter,
                    strip_server)
-
+from .protocol import (BaseProtocolPart,
+                       TestharnessProtocolPart,
+                       PrefsProtocolPart,
+                       Protocol,
+                       StorageProtocolPart,
+                       SelectorProtocolPart,
+                       ClickProtocolPart,
+                       SendKeysProtocolPart,
+                       TestDriverProtocolPart)
 from ..testrunner import Stop
 from ..webdriver_server import GeckoDriverServer
 
 
-
 def do_delayed_imports():
     global errors, marionette
 
@@ -42,78 +50,17 @@
         from marionette_driver import marionette, errors
 
 
-class MarionetteProtocol(Protocol):
-    def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1):
-        do_delayed_imports()
-
-        Protocol.__init__(self, executor, browser)
-        self.marionette = None
-        self.marionette_port = browser.marionette_port
-        self.capabilities = capabilities
-        self.timeout_multiplier = timeout_multiplier
+class MarionetteBaseProtocolPart(BaseProtocolPart):
+    def __init__(self, parent):
+        super(MarionetteBaseProtocolPart, self).__init__(parent)
         self.timeout = None
-        self.runner_handle = None
 
-    def setup(self, runner):
-        """Connect to browser via Marionette."""
-        Protocol.setup(self, runner)
+    def setup(self):
+        self.marionette = self.parent.marionette
 
-        self.logger.debug("Connecting to Marionette on port %i" % self.marionette_port)
-        startup_timeout = marionette.Marionette.DEFAULT_STARTUP_TIMEOUT * self.timeout_multiplier
-        self.marionette = marionette.Marionette(host='localhost',
-                                                port=self.marionette_port,
-                                                socket_timeout=None,
-                                                startup_timeout=startup_timeout)
-        try:
-            self.logger.debug("Waiting for Marionette connection")
-            while True:
-                try:
-                    self.marionette.raise_for_port()
-                    break
-                except IOError:
-                    # When running in a debugger wait indefinitely for Firefox to start
-                    if self.executor.debug_info is None:
-                        raise
-
-            self.logger.debug("Starting Marionette session")
-            self.marionette.start_session()
-            self.logger.debug("Marionette session started")
-
-        except Exception as e:
-            self.logger.warning("Failed to start a Marionette session: %s" % e)
-            self.executor.runner.send_message("init_failed")
-
-        else:
-            try:
-                self.after_connect()
-            except Exception:
-                self.logger.warning("Post-connection steps failed")
-                self.logger.error(traceback.format_exc())
-                self.executor.runner.send_message("init_failed")
-            else:
-                self.executor.runner.send_message("init_succeeded")
-
-    def teardown(self):
-        try:
-            self.marionette._request_in_app_shutdown()
-            self.marionette.delete_session(send_request=False, reset_session_id=True)
-        except Exception:
-            # This is typically because the session never started
-            pass
-        if self.marionette is not None:
-            del self.marionette
-
-    @property
-    def is_alive(self):
-        """Check if the Marionette connection is still active."""
-        try:
-            self.marionette.current_window_handle
-        except Exception:
-            return False
-        return True
-
-    def after_connect(self):
-        self.load_runner(self.executor.last_environment["protocol"])
+    def execute_script(self, script, async=False):
+        method = self.marionette.execute_async_script if async else self.marionette.execute_script
+        return method(script, new_sandbox=False, sandbox=None)
 
     def set_timeout(self, timeout):
         """Set the Marionette script timeout.
@@ -121,52 +68,16 @@
         :param timeout: Script timeout in seconds
 
         """
-        self.marionette.timeout.script = timeout
-        self.timeout = timeout
+        if timeout != self.timeout:
+            self.marionette.timeout.script = timeout
+            self.timeout = timeout
 
-    def load_runner(self, protocol):
-        # Check if we previously had a test window open, and if we did make sure it's closed
-        self.marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
-        url = urlparse.urljoin(self.executor.server_url(protocol), "/testharness_runner.html")
-        self.logger.debug("Loading %s" % url)
-        self.runner_handle = self.marionette.current_window_handle
-        try:
-            self.marionette.navigate(url)
-        except Exception as e:
-            self.logger.critical(
-                "Loading initial page %s failed. Ensure that the "
-                "there are no other programs bound to this port and "
-                "that your firewall rules or network setup does not "
-                "prevent access.\e%s" % (url, traceback.format_exc(e)))
-        self.marionette.execute_script(
-            "document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
+    @property
+    def current_window(self):
+        return self.marionette.current_window_handle
 
-    def close_old_windows(self, protocol):
-        handles = self.marionette.window_handles
-        runner_handle = None
-        try:
-            handles.remove(self.runner_handle)
-            runner_handle = self.runner_handle
-        except ValueError:
-            # The runner window probably changed id but we can restore it
-            # This isn't supposed to happen, but marionette ids are not yet stable
-            # We assume that the first handle returned corresponds to the runner,
-            # but it hopefully doesn't matter too much if that assumption is
-            # wrong since we reload the runner in that tab anyway.
-            runner_handle = handles.pop(0)
-
-        for handle in handles:
-            try:
-                self.marionette.switch_to_window(handle)
-                self.marionette.close()
-            except errors.NoSuchWindowException:
-                # We might have raced with the previous test to close this
-                # window, skip it.
-                pass
-
-        self.marionette.switch_to_window(runner_handle)
-        if runner_handle != self.runner_handle:
-            self.load_runner(protocol)
+    def set_window(self, handle):
+        self.marionette.switch_to_window(handle)
 
     def wait(self):
         try:
@@ -175,9 +86,12 @@
             # This can happen if there was a crash
             return
         if socket_timeout:
-            self.marionette.timeout.script = socket_timeout / 2
+            try:
+                self.marionette.timeout.script = socket_timeout / 2
+            except (socket.error, IOError):
+                self.logger.debug("Socket closed")
+                return
 
-        self.marionette.switch_to_window(self.runner_handle)
         while True:
             try:
                 self.marionette.execute_async_script("")
@@ -194,20 +108,106 @@
                 self.logger.warning(traceback.format_exc(e))
                 break
 
-    def on_environment_change(self, old_environment, new_environment):
-        #Unset all the old prefs
-        for name in old_environment.get("prefs", {}).iterkeys():
-            value = self.executor.original_pref_values[name]
-            if value is None:
-                self.clear_user_pref(name)
+
+class MarionetteTestharnessProtocolPart(TestharnessProtocolPart):
+    def __init__(self, parent):
+        super(MarionetteTestharnessProtocolPart, self).__init__(parent)
+        self.runner_handle = None
+
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def load_runner(self, url_protocol):
+        # Check if we previously had a test window open, and if we did make sure it's closed
+        self.parent.base.execute_script("if (window.win) {window.win.close()}")
+        url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
+                               "/testharness_runner.html")
+        self.logger.debug("Loading %s" % url)
+        self.runner_handle = self.marionette.current_window_handle
+        try:
+            self.dismiss_alert(lambda: self.marionette.navigate(url))
+        except Exception as e:
+            self.logger.critical(
+                "Loading initial page %s failed. Ensure that the "
+                "there are no other programs bound to this port and "
+                "that your firewall rules or network setup does not "
+                "prevent access.\e%s" % (url, traceback.format_exc(e)))
+            raise
+        self.parent.base.execute_script(
+            "document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
+
+    def close_old_windows(self, url_protocol):
+        handles = self.marionette.window_handles
+        runner_handle = None
+        try:
+            handles.remove(self.runner_handle)
+            runner_handle = self.runner_handle
+        except ValueError:
+            # The runner window probably changed id but we can restore it
+            # This isn't supposed to happen, but marionette ids are not yet stable
+            # We assume that the first handle returned corresponds to the runner,
+            # but it hopefully doesn't matter too much if that assumption is
+            # wrong since we reload the runner in that tab anyway.
+            runner_handle = handles.pop(0)
+
+        for handle in handles:
+            try:
+                self.dismiss_alert(lambda: self.marionette.switch_to_window(handle))
+                self.marionette.switch_to_window(handle)
+                self.marionette.close()
+            except errors.NoSuchWindowException:
+                # We might have raced with the previous test to close this
+                # window, skip it.
+                pass
+
+        self.marionette.switch_to_window(runner_handle)
+        if runner_handle != self.runner_handle:
+            self.load_runner(url_protocol)
+        return self.runner_handle
+
+    def dismiss_alert(self, f):
+        while True:
+            try:
+                f()
+            except errors.UnexpectedAlertOpen:
+                alert = self.marionette.switch_to_alert()
+                try:
+                    alert.dismiss()
+                except errors.NoAlertPresentException:
+                    pass
             else:
-                self.set_pref(name, value)
+                break
 
-        for name, value in new_environment.get("prefs", {}).iteritems():
-            self.executor.original_pref_values[name] = self.get_pref(name)
-            self.set_pref(name, value)
+    def get_test_window(self, window_id, parent):
+        test_window = None
+        if window_id:
+            try:
+                # Try this, it's in Level 1 but nothing supports it yet
+                win_s = self.parent.base.execute_script("return window['%s'];" % self.window_id)
+                win_obj = json.loads(win_s)
+                test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
+            except Exception:
+                pass
 
-    def set_pref(self, name, value):
+        if test_window is None:
+            after = self.marionette.window_handles
+            if len(after) == 2:
+                test_window = next(iter(set(after) - set([parent])))
+            elif after[0] == parent and len(after) > 2:
+                # Hope the first one here is the test window
+                test_window = after[1]
+            else:
+                raise Exception("unable to find test window")
+
+        assert test_window != parent
+        return test_window
+
+
+class MarionettePrefsProtocolPart(PrefsProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def set(self, name, value):
         if value.lower() not in ("true", "false"):
             try:
                 int(value)
@@ -239,7 +239,7 @@
         with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
             self.marionette.execute_script(script)
 
-    def clear_user_pref(self, name):
+    def clear(self, name):
         self.logger.info("Clearing pref %s" % (name))
         script = """
             let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
@@ -250,7 +250,7 @@
         with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
             self.marionette.execute_script(script)
 
-    def get_pref(self, name):
+    def get(self, name):
         script = """
             let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
                                           .getService(Components.interfaces.nsIPrefBranch);
@@ -270,6 +270,11 @@
         with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
             self.marionette.execute_script(script)
 
+
+class MarionetteStorageProtocolPart(StorageProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
     def clear_origin(self, url):
         self.logger.info("Clearing origin %s" % (url))
         script = """
@@ -288,34 +293,146 @@
             self.marionette.execute_script(script)
 
 
+class MarionetteSelectorProtocolPart(SelectorProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def elements_by_selector(self, selector):
+        return self.marionette.find_elements("css selector", selector)
+
+
+class MarionetteClickProtocolPart(ClickProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def element(self, element):
+        return element.click()
+
+class MarionetteSendKeysProtocolPart(SendKeysProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def send_keys(self, element, keys):
+        return element.send_keys(keys)
+
+class MarionetteTestDriverProtocolPart(TestDriverProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def send_message(self, message_type, status, message=None):
+        obj = {
+            "type": "testdriver-%s" % str(message_type),
+            "status": str(status)
+        }
+        if message:
+            obj["message"] = str(message)
+        self.parent.base.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
+
+
+class MarionetteProtocol(Protocol):
+    implements = [MarionetteBaseProtocolPart,
+                  MarionetteTestharnessProtocolPart,
+                  MarionettePrefsProtocolPart,
+                  MarionetteStorageProtocolPart,
+                  MarionetteSelectorProtocolPart,
+                  MarionetteClickProtocolPart,
+                  MarionetteSendKeysProtocolPart,
+                  MarionetteTestDriverProtocolPart]
+
+    def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1):
+        do_delayed_imports()
+
+        super(MarionetteProtocol, self).__init__(executor, browser)
+        self.marionette = None
+        self.marionette_port = browser.marionette_port
+        self.capabilities = capabilities
+        self.timeout_multiplier = timeout_multiplier
+        self.runner_handle = None
+
+    def connect(self):
+        self.logger.debug("Connecting to Marionette on port %i" % self.marionette_port)
+        startup_timeout = marionette.Marionette.DEFAULT_STARTUP_TIMEOUT * self.timeout_multiplier
+        self.marionette = marionette.Marionette(host='localhost',
+                                                port=self.marionette_port,
+                                                socket_timeout=None,
+                                                startup_timeout=startup_timeout)
+
+        self.logger.debug("Waiting for Marionette connection")
+        while True:
+            try:
+                self.marionette.raise_for_port()
+                break
+            except IOError:
+                # When running in a debugger wait indefinitely for Firefox to start
+                if self.executor.debug_info is None:
+                    raise
+
+        self.logger.debug("Starting Marionette session")
+        self.marionette.start_session()
+        self.logger.debug("Marionette session started")
+
+    def after_connect(self):
+        self.testharness.load_runner(self.executor.last_environment["protocol"])
+
+    def teardown(self):
+        try:
+            self.marionette._request_in_app_shutdown()
+            self.marionette.delete_session(send_request=False)
+        except Exception:
+            # This is typically because the session never started
+            pass
+        if self.marionette is not None:
+            del self.marionette
+        super(MarionetteProtocol, self).teardown()
+
+    @property
+    def is_alive(self):
+        try:
+            self.marionette.current_window_handle
+        except Exception:
+            return False
+        return True
+
+    def on_environment_change(self, old_environment, new_environment):
+        #Unset all the old prefs
+        for name in old_environment.get("prefs", {}).iterkeys():
+            value = self.executor.original_pref_values[name]
+            if value is None:
+                self.prefs.clear(name)
+            else:
+                self.prefs.set(name, value)
+
+        for name, value in new_environment.get("prefs", {}).iteritems():
+            self.executor.original_pref_values[name] = self.prefs.get(name)
+            self.prefs.set(name, value)
+
+
 class ExecuteAsyncScriptRun(object):
     def __init__(self, logger, func, protocol, url, timeout):
         self.logger = logger
         self.result = (None, None)
         self.protocol = protocol
-        self.marionette = protocol.marionette
         self.func = func
         self.url = url
         self.timeout = timeout
         self.result_flag = threading.Event()
 
     def run(self):
-        index = self.url.rfind("/storage/");
+        index = self.url.rfind("/storage/")
         if index != -1:
             # Clear storage
-            self.protocol.clear_origin(self.url)
+            self.protocol.storage.clear_origin(self.url)
 
         timeout = self.timeout
 
         try:
             if timeout is not None:
-                if timeout + extra_timeout != self.protocol.timeout:
-                    self.protocol.set_timeout(timeout + extra_timeout)
+                self.protocol.base.set_timeout(timeout + extra_timeout)
             else:
                 # We just want it to never time out, really, but marionette doesn't
                 # make that possible. It also seems to time out immediately if the
                 # timeout is set too high. This works at least.
-                self.protocol.set_timeout(2**28 - 1)
+                self.protocol.base.set_timeout(2**28 - 1)
         except IOError:
             self.logger.error("Lost marionette connection before starting test")
             return Stop
@@ -337,14 +454,14 @@
             # We didn't get any data back from the test, so check if the
             # browser is still responsive
             if self.protocol.is_alive:
-                self.result = False, ("ERROR", None)
+                self.result = False, ("INTERNAL-ERROR", None)
             else:
                 self.result = False, ("CRASH", None)
         return self.result
 
     def _run(self):
         try:
-            self.result = True, self.func(self.marionette, self.url, self.timeout)
+            self.result = True, self.func(self.protocol, self.url, self.timeout)
         except errors.ScriptTimeoutException:
             self.logger.debug("Got a marionette timeout")
             self.result = False, ("EXTERNAL-TIMEOUT", None)
@@ -358,13 +475,16 @@
             if message:
                 message += "\n"
             message += traceback.format_exc(e)
-            self.result = False, ("ERROR", e)
+            self.logger.warning(message)
+            self.result = False, ("INTERNAL-ERROR", None)
 
         finally:
             self.result_flag.set()
 
 
 class MarionetteTestharnessExecutor(TestharnessExecutor):
+    supports_testdriver = True
+
     def __init__(self, browser, server_config, timeout_multiplier=1,
                  close_after_done=True, debug_info=None, capabilities=None,
                  **kwargs):
@@ -374,7 +494,8 @@
                                      debug_info=debug_info)
 
         self.protocol = MarionetteProtocol(self, browser, capabilities, timeout_multiplier)
-        self.script = open(os.path.join(here, "testharness_marionette.js")).read()
+        self.script = open(os.path.join(here, "testharness_webdriver.js")).read()
+        self.script_resume = open(os.path.join(here, "testharness_webdriver_resume.js")).read()
         self.close_after_done = close_after_done
         self.window_id = str(uuid.uuid4())
 
@@ -390,7 +511,7 @@
         self.protocol.on_environment_change(self.last_environment, new_environment)
 
         if new_environment["protocol"] != self.last_environment["protocol"]:
-            self.protocol.load_runner(new_environment["protocol"])
+            self.protocol.testharness.load_runner(new_environment["protocol"])
 
     def do_test(self, test):
         timeout = (test.timeout * self.timeout_multiplier if self.debug_info is None
@@ -406,24 +527,37 @@
 
         return (test.result_cls(*data), [])
 
-    def do_testharness(self, marionette, url, timeout):
-        if self.close_after_done:
-            marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
-            self.protocol.close_old_windows(self.protocol)
+    def do_testharness(self, protocol, url, timeout):
+        protocol.base.execute_script("if (window.win) {window.win.close()}")
+        parent_window = protocol.testharness.close_old_windows(protocol)
 
         if timeout is not None:
             timeout_ms = str(timeout * 1000)
         else:
             timeout_ms = "null"
 
-        script = self.script % {"abs_url": url,
-                                "url": strip_server(url),
-                                "window_id": self.window_id,
-                                "timeout_multiplier": self.timeout_multiplier,
-                                "timeout": timeout_ms,
-                                "explicit_timeout": timeout is None}
+        format_map = {"abs_url": url,
+                      "url": strip_server(url),
+                      "window_id": self.window_id,
+                      "timeout_multiplier": self.timeout_multiplier,
+                      "timeout": timeout_ms,
+                      "explicit_timeout": timeout is None}
 
-        rv = marionette.execute_async_script(script, new_sandbox=False)
+        script = self.script % format_map
+
+        rv = protocol.base.execute_script(script)
+        test_window = protocol.testharness.get_test_window(self.window_id, parent_window)
+
+        handler = CallbackHandler(self.logger, protocol, test_window)
+        while True:
+            result = protocol.base.execute_script(
+                self.script_resume % format_map, async=True)
+            if result is None:
+                # This can happen if we get an content process crash
+                return None
+            done, rv = handler(result)
+            if done:
+                break
         return rv
 
 
@@ -487,8 +621,8 @@
                 self.has_window = False
 
             if not self.has_window:
-                self.protocol.marionette.execute_script(self.script)
-                self.protocol.marionette.switch_to_window(self.protocol.marionette.window_handles[-1])
+                self.protocol.base.execute_script(self.script)
+                self.protocol.base.set_window(self.protocol.marionette.window_handles[-1])
                 self.has_window = True
 
         result = self.implementation.run_test(test)
@@ -499,7 +633,7 @@
         assert viewport_size is None
         assert dpi is None
 
-        timeout =  self.timeout_multiplier * test.timeout if self.debug_info is None else None
+        timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
 
         test_url = self.test_url(test)
 
@@ -509,12 +643,12 @@
                                      test_url,
                                      timeout).run()
 
-    def _screenshot(self, marionette, url, timeout):
-        marionette.navigate(url)
+    def _screenshot(self, protocol, url, timeout):
+        protocol.marionette.navigate(url)
 
-        marionette.execute_async_script(self.wait_script)
+        protocol.base.execute_script(self.wait_script, async=True)
 
-        screenshot = marionette.screenshot(full=False)
+        screenshot = protocol.marionette.screenshot(full=False)
         # strip off the data:img/png, part of the url
         if screenshot.startswith("data:image/png;base64,"):
             screenshot = screenshot.split(",", 1)[1]
@@ -564,7 +698,7 @@
             self.executor.protocol.marionette.set_context(self.executor.protocol.marionette.CONTEXT_CONTENT)
         except Exception as e:
             # Ignore errors during teardown
-            self.logger.warning(traceback.traceback.format_exc(e))
+            self.logger.warning(traceback.format_exc(e))
 
 
 
diff --git a/tools/wptrunner/wptrunner/executors/executorselenium.py b/tools/wptrunner/wptrunner/executors/executorselenium.py
index 2e84ab6..98c49bd 100644
--- a/tools/wptrunner/wptrunner/executors/executorselenium.py
+++ b/tools/wptrunner/wptrunner/executors/executorselenium.py
@@ -8,12 +8,19 @@
 import urlparse
 import uuid
 
-from .base import (Protocol,
+from .base import (CallbackHandler,
                    RefTestExecutor,
                    RefTestImplementation,
                    TestharnessExecutor,
                    extra_timeout,
                    strip_server)
+from .protocol import (BaseProtocolPart,
+                       TestharnessProtocolPart,
+                       Protocol,
+                       SelectorProtocolPart,
+                       ClickProtocolPart,
+                       SendKeysProtocolPart,
+                       TestDriverProtocolPart)
 from ..testrunner import Stop
 
 here = os.path.join(os.path.split(__file__)[0])
@@ -32,51 +39,156 @@
     from selenium.webdriver.remote.remote_connection import RemoteConnection
 
 
+class SeleniumBaseProtocolPart(BaseProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def execute_script(self, script, async=False):
+        method = self.webdriver.execute_async_script if async else self.webdriver.execute_script
+        return method(script)
+
+    def set_timeout(self, timeout):
+        self.webdriver.set_script_timeout(timeout * 1000)
+
+    @property
+    def current_window(self):
+        return self.webdriver.current_window_handle
+
+    def set_window(self, handle):
+        self.webdriver.switch_to_window(handle)
+
+    def wait(self):
+        while True:
+            try:
+                self.webdriver.execute_async_script("")
+            except exceptions.TimeoutException:
+                pass
+            except (socket.timeout, exceptions.NoSuchWindowException,
+                    exceptions.ErrorInResponseException, IOError):
+                break
+            except Exception as e:
+                self.logger.error(traceback.format_exc(e))
+                break
+
+
+class SeleniumTestharnessProtocolPart(TestharnessProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def load_runner(self, url_protocol):
+        url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
+                               "/testharness_runner.html")
+        self.logger.debug("Loading %s" % url)
+        self.webdriver.get(url)
+        self.webdriver.execute_script("document.title = '%s'" %
+                                      threading.current_thread().name.replace("'", '"'))
+
+    def close_old_windows(self):
+        exclude = self.webdriver.current_window_handle
+        handles = [item for item in self.webdriver.window_handles if item != exclude]
+        for handle in handles:
+            try:
+                self.webdriver.switch_to_window(handle)
+                self.webdriver.close()
+            except exceptions.NoSuchWindowException:
+                pass
+        self.webdriver.switch_to_window(exclude)
+        return exclude
+
+    def get_test_window(self, window_id, parent):
+        test_window = None
+        if window_id:
+            try:
+                # Try this, it's in Level 1 but nothing supports it yet
+                win_s = self.webdriver.execute_script("return window['%s'];" % self.window_id)
+                win_obj = json.loads(win_s)
+                test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
+            except Exception:
+                pass
+
+        if test_window is None:
+            after = self.webdriver.window_handles
+            if len(after) == 2:
+                test_window = next(iter(set(after) - set([parent])))
+            elif after[0] == parent and len(after) > 2:
+                # Hope the first one here is the test window
+                test_window = after[1]
+            else:
+                raise Exception("unable to find test window")
+
+        assert test_window != parent
+        return test_window
+
+
+class SeleniumSelectorProtocolPart(SelectorProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def elements_by_selector(self, selector):
+        return self.webdriver.find_elements_by_css_selector(selector)
+
+
+class SeleniumClickProtocolPart(ClickProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def element(self, element):
+        return element.click()
+
+class SeleniumSendKeysProtocolPart(SendKeysProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def send_keys(self, element, keys):
+        return element.send_keys(keys)
+
+
+class SeleniumTestDriverProtocolPart(TestDriverProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def send_message(self, message_type, status, message=None):
+        obj = {
+            "type": "testdriver-%s" % str(message_type),
+            "status": str(status)
+        }
+        if message:
+            obj["message"] = str(message)
+        self.webdriver.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
+
+
 class SeleniumProtocol(Protocol):
+    implements = [SeleniumBaseProtocolPart,
+                  SeleniumTestharnessProtocolPart,
+                  SeleniumSelectorProtocolPart,
+                  SeleniumClickProtocolPart,
+                  SeleniumSendKeysProtocolPart,
+                  SeleniumTestDriverProtocolPart]
+
     def __init__(self, executor, browser, capabilities, **kwargs):
         do_delayed_imports()
 
-        Protocol.__init__(self, executor, browser)
+        super(SeleniumProtocol, self).__init__(executor, browser)
         self.capabilities = capabilities
         self.url = browser.webdriver_url
         self.webdriver = None
 
-    def setup(self, runner):
+    def connect(self):
         """Connect to browser via Selenium's WebDriver implementation."""
-        self.runner = runner
         self.logger.debug("Connecting to Selenium on URL: %s" % self.url)
 
-        session_started = False
-        try:
-            self.webdriver = webdriver.Remote(command_executor=RemoteConnection(self.url.strip("/"),
-                                                                                resolve_ip=False),
-                                              desired_capabilities=self.capabilities)
-        except:
-            self.logger.warning(
-                "Connecting to Selenium failed:\n%s" % traceback.format_exc())
-        else:
-            self.logger.debug("Selenium session started")
-            session_started = True
+        self.webdriver = webdriver.Remote(command_executor=RemoteConnection(self.url.strip("/"),
+                                                                            resolve_ip=False),
+                                          desired_capabilities=self.capabilities)
 
-        if not session_started:
-            self.logger.warning("Failed to connect to Selenium")
-            self.executor.runner.send_message("init_failed")
-        else:
-            try:
-                self.after_connect()
-            except:
-                print >> sys.stderr, traceback.format_exc()
-                self.logger.warning(
-                    "Failed to connect to navigate initial page")
-                self.executor.runner.send_message("init_failed")
-            else:
-                self.executor.runner.send_message("init_succeeded")
+    def after_conect(self):
+        pass
 
     def teardown(self):
         self.logger.debug("Hanging up on Selenium session")
         try:
             self.webdriver.quit()
-        except:
+        except Exception:
             pass
         del self.webdriver
 
@@ -90,35 +202,14 @@
         return True
 
     def after_connect(self):
-        self.load_runner("http")
-
-    def load_runner(self, protocol):
-        url = urlparse.urljoin(self.executor.server_url(protocol),
-                               "/testharness_runner.html")
-        self.logger.debug("Loading %s" % url)
-        self.webdriver.get(url)
-        self.webdriver.execute_script("document.title = '%s'" %
-                                      threading.current_thread().name.replace("'", '"'))
-
-    def wait(self):
-        while True:
-            try:
-                self.webdriver.execute_async_script("");
-            except exceptions.TimeoutException:
-                pass
-            except (socket.timeout, exceptions.NoSuchWindowException,
-                    exceptions.ErrorInResponseException, IOError):
-                break
-            except Exception as e:
-                self.logger.error(traceback.format_exc(e))
-                break
+        self.testharness.load_runner(self.executor.last_environment["protocol"])
 
 
 class SeleniumRun(object):
-    def __init__(self, func, webdriver, url, timeout):
+    def __init__(self, func, protocol, url, timeout):
         self.func = func
         self.result = None
-        self.webdriver = webdriver
+        self.protocol = protocol
         self.url = url
         self.timeout = timeout
         self.result_flag = threading.Event()
@@ -127,7 +218,7 @@
         timeout = self.timeout
 
         try:
-            self.webdriver.set_script_timeout((timeout + extra_timeout) * 1000)
+            self.protocol.base.set_timeout((timeout + extra_timeout))
         except exceptions.ErrorInResponseException:
             self.logger.error("Lost WebDriver connection")
             return Stop
@@ -144,7 +235,7 @@
 
     def _run(self):
         try:
-            self.result = True, self.func(self.webdriver, self.url, self.timeout)
+            self.result = True, self.func(self.protocol, self.url, self.timeout)
         except exceptions.TimeoutException:
             self.result = False, ("EXTERNAL-TIMEOUT", None)
         except (socket.timeout, exceptions.ErrorInResponseException):
@@ -154,7 +245,7 @@
             if message:
                 message += "\n"
             message += traceback.format_exc(e)
-            self.result = False, ("ERROR", e)
+            self.result = False, ("INTERNAL-ERROR", e)
         finally:
             self.result_flag.set()
 
@@ -182,13 +273,13 @@
 
     def on_environment_change(self, new_environment):
         if new_environment["protocol"] != self.last_environment["protocol"]:
-            self.protocol.load_runner(new_environment["protocol"])
+            self.protocol.testharness.load_runner(new_environment["protocol"])
 
     def do_test(self, test):
         url = self.test_url(test)
 
         success, data = SeleniumRun(self.do_testharness,
-                                    self.protocol.webdriver,
+                                    self.protocol,
                                     url,
                                     test.timeout * self.timeout_multiplier).run()
 
@@ -197,111 +288,28 @@
 
         return (test.result_cls(*data), [])
 
-    def do_testharness(self, webdriver, url, timeout):
+    def do_testharness(self, protocol, url, timeout):
         format_map = {"abs_url": url,
                       "url": strip_server(url),
                       "window_id": self.window_id,
                       "timeout_multiplier": self.timeout_multiplier,
                       "timeout": timeout * 1000}
 
-        parent = webdriver.current_window_handle
-        handles = [item for item in webdriver.window_handles if item != parent]
-        for handle in handles:
-            try:
-                webdriver.switch_to_window(handle)
-                webdriver.close()
-            except exceptions.NoSuchWindowException:
-                pass
-        webdriver.switch_to_window(parent)
+        parent_window = protocol.testharness.close_old_windows()
+        # Now start the test harness
+        protocol.base.execute_script(self.script % format_map)
+        test_window = protocol.testharness.get_test_window(webdriver, parent_window)
 
-        webdriver.execute_script(self.script % format_map)
-        try:
-            # Try this, it's in Level 1 but nothing supports it yet
-            win_s = webdriver.execute_script("return window['%s'];" % self.window_id)
-            win_obj = json.loads(win_s)
-            test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
-        except:
-            after = webdriver.window_handles
-            if len(after) == 2:
-                test_window = next(iter(set(after) - set([parent])))
-            elif after[0] == parent and len(after) > 2:
-                # Hope the first one here is the test window
-                test_window = after[1]
-            else:
-                raise Exception("unable to find test window")
-        assert test_window != parent
-
-        handler = CallbackHandler(webdriver, test_window, self.logger)
+        handler = CallbackHandler(self.logger, protocol, test_window)
         while True:
-            result = webdriver.execute_async_script(
-                self.script_resume % format_map)
+            result = protocol.base.execute_script(
+                self.script_resume % format_map, async=True)
             done, rv = handler(result)
             if done:
                 break
         return rv
 
 
-class CallbackHandler(object):
-    def __init__(self, webdriver, test_window, logger):
-        self.webdriver = webdriver
-        self.test_window = test_window
-        self.logger = logger
-
-    def __call__(self, result):
-        self.logger.debug("Got async callback: %s" % result[1])
-        try:
-            attr = getattr(self, "process_%s" % result[1])
-        except AttributeError:
-            raise ValueError("Unknown callback type %r" % result[1])
-        else:
-            return attr(result)
-
-    def process_complete(self, result):
-        rv = [result[0]] + result[2]
-        return True, rv
-
-    def process_action(self, result):
-        parent = self.webdriver.current_window_handle
-        try:
-            self.webdriver.switch_to.window(self.test_window)
-            action = result[2]["action"]
-            self.logger.debug("Got action: %s" % action)
-            if action == "click":
-                selector = result[2]["selector"]
-                elements = self.webdriver.find_elements_by_css_selector(selector)
-                if len(elements) == 0:
-                    raise ValueError("Selector matches no elements")
-                elif len(elements) > 1:
-                    raise ValueError("Selector matches multiple elements")
-                self.logger.debug("Clicking element: %s" % selector)
-                try:
-                    elements[0].click()
-                except (exceptions.ElementNotInteractableException,
-                        exceptions.ElementNotVisibleException) as e:
-                    self._send_message("complete",
-                                       "failure",
-                                       e)
-                    self.logger.debug("Clicking element failed: %s" % str(e))
-                else:
-                    self._send_message("complete",
-                                       "success")
-                    self.logger.debug("Clicking element succeeded")
-        finally:
-            self.webdriver.switch_to.window(parent)
-
-        return False, None
-
-    def _send_message(self, message_type, status, message=None):
-        obj = {
-            "type": "testdriver-%s" % str(message_type),
-            "status": str(status)
-        }
-        if message:
-            obj["message"] = str(message)
-        self.webdriver.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
-
-
-
 class SeleniumRefTestExecutor(RefTestExecutor):
     def __init__(self, browser, server_config, timeout_multiplier=1,
                  screenshot_cache=None, close_after_done=True,
@@ -342,11 +350,12 @@
         assert dpi is None
 
         return SeleniumRun(self._screenshot,
-                           self.protocol.webdriver,
+                           self.protocol,
                            self.test_url(test),
                            test.timeout).run()
 
-    def _screenshot(self, webdriver, url, timeout):
+    def _screenshot(self, protocol, url, timeout):
+        webdriver = protocol.webdriver
         webdriver.get(url)
 
         webdriver.execute_async_script(self.wait_script)
diff --git a/tools/wptrunner/wptrunner/executors/executorservo.py b/tools/wptrunner/wptrunner/executors/executorservo.py
index 97fe304..fb79cf2 100644
--- a/tools/wptrunner/wptrunner/executors/executorservo.py
+++ b/tools/wptrunner/wptrunner/executors/executorservo.py
@@ -13,12 +13,15 @@
 
 from mozprocess import ProcessHandler
 
+from serve.serve import make_hosts_file
+
 from .base import (ExecutorException,
-                   Protocol,
+                   ConnectionlessProtocol,
                    RefTestImplementation,
                    testharness_result_converter,
                    reftest_result_converter,
-                   WdspecExecutor)
+                   WdspecExecutor,
+                   WebDriverProtocol)
 from .process import ProcessTestExecutor
 from ..browsers.base import browser_command
 from ..wpttest import WdspecResult, WdspecSubtestResult
@@ -28,20 +31,12 @@
 pytestrunner = None
 webdriver = None
 
-extra_timeout = 5 # seconds
+extra_timeout = 5  # seconds
 
-hosts_text = """127.0.0.1 web-platform.test
-127.0.0.1 www.web-platform.test
-127.0.0.1 www1.web-platform.test
-127.0.0.1 www2.web-platform.test
-127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1 xn--lve-6lad.web-platform.test
-"""
-
-def make_hosts_file():
+def write_hosts_file(config):
     hosts_fd, hosts_path = tempfile.mkstemp()
     with os.fdopen(hosts_fd, "w") as f:
-        f.write(hosts_text)
+        f.write(make_hosts_file(config, "127.0.0.1"))
     return hosts_path
 
 
@@ -56,8 +51,8 @@
         self.pause_after_test = pause_after_test
         self.result_data = None
         self.result_flag = None
-        self.protocol = Protocol(self, browser)
-        self.hosts_path = make_hosts_file()
+        self.protocol = ConnectionlessProtocol(self, browser)
+        self.hosts_path = write_hosts_file(server_config)
 
     def teardown(self):
         try:
@@ -187,11 +182,11 @@
                                      timeout_multiplier=timeout_multiplier,
                                      debug_info=debug_info)
 
-        self.protocol = Protocol(self, browser)
+        self.protocol = ConnectionlessProtocol(self, browser)
         self.screenshot_cache = screenshot_cache
         self.implementation = RefTestImplementation(self)
         self.tempdir = tempfile.mkdtemp()
-        self.hosts_path = make_hosts_file()
+        self.hosts_path = write_hosts_file(server_config)
 
     def teardown(self):
         try:
@@ -286,82 +281,10 @@
                                        line,
                                        " ".join(self.command))
 
-class ServoWdspecProtocol(Protocol):
-    def __init__(self, executor, browser):
-        self.do_delayed_imports()
-        Protocol.__init__(self, executor, browser)
-        self.session = None
-        self.server = None
 
-    def setup(self, runner):
-        try:
-            self.server = ServoDriverServer(self.logger, binary=self.browser.binary, binary_args=self.browser.binary_args)
-            self.server.start(block=False)
-            self.logger.info(
-                "WebDriver HTTP server listening at %s" % self.server.url)
-
-            self.logger.info(
-                "Establishing new WebDriver session with %s" % self.server.url)
-            self.session = webdriver.Session(
-                self.server.host, self.server.port, self.server.base_path)
-        except Exception:
-            self.logger.error(traceback.format_exc())
-            self.executor.runner.send_message("init_failed")
-        else:
-            self.executor.runner.send_message("init_succeeded")
-
-    def teardown(self):
-        if self.server is not None:
-            try:
-                if self.session.session_id is not None:
-                    self.session.end()
-            except Exception:
-                pass
-            if self.server.is_alive:
-                self.server.stop()
-
-    @property
-    def is_alive(self):
-        conn = httplib.HTTPConnection(self.server.host, self.server.port)
-        conn.request("HEAD", self.server.base_path + "invalid")
-        res = conn.getresponse()
-        return res.status == 404
-
-    def do_delayed_imports(self):
-        global pytestrunner, webdriver
-        from . import pytestrunner
-        import webdriver
+class ServoDriverProtocol(WebDriverProtocol):
+    server_cls = ServoDriverServer
 
 
 class ServoWdspecExecutor(WdspecExecutor):
-    def __init__(self, browser, server_config,
-                 timeout_multiplier=1, close_after_done=True, debug_info=None,
-                 **kwargs):
-        WdspecExecutor.__init__(self, browser, server_config,
-                                timeout_multiplier=timeout_multiplier,
-                                debug_info=debug_info)
-        self.protocol = ServoWdspecProtocol(self, browser)
-
-    def is_alive(self):
-        return self.protocol.is_alive
-
-    def on_environment_change(self, new_environment):
-        pass
-
-    def do_test(self, test):
-        timeout = test.timeout * self.timeout_multiplier + extra_timeout
-
-        success, data = WdspecRun(self.do_wdspec,
-                                  self.protocol.session,
-                                  test.path,
-                                  timeout).run()
-
-        if success:
-            return self.convert_result(test, data)
-
-        return (test.result_cls(*data), [])
-
-    def do_wdspec(self, session, path, timeout):
-        harness_result = ("OK", None)
-        subtest_results = pytestrunner.run(path, session, timeout=timeout)
-        return (harness_result, subtest_results)
+    protocol_cls = ServoDriverProtocol
diff --git a/tools/wptrunner/wptrunner/executors/executorservodriver.py b/tools/wptrunner/wptrunner/executors/executorservodriver.py
index af09da6..626c987 100644
--- a/tools/wptrunner/wptrunner/executors/executorservodriver.py
+++ b/tools/wptrunner/wptrunner/executors/executorservodriver.py
@@ -33,34 +33,21 @@
         self.port = browser.webdriver_port
         self.session = None
 
-    def setup(self, runner):
+    def connect(self):
         """Connect to browser via WebDriver."""
-        self.runner = runner
-
         url = "http://%s:%d" % (self.host, self.port)
-        session_started = False
-        try:
-            self.session = webdriver.Session(self.host, self.port,
-                extension=webdriver.servo.ServoCommandExtensions)
-            self.session.start()
-        except:
-            self.logger.warning(
-                "Connecting with WebDriver failed:\n%s" % traceback.format_exc())
-        else:
-            self.logger.debug("session started")
-            session_started = True
+        self.session = webdriver.Session(self.host, self.port,
+                                         extension=webdriver.servo.ServoCommandExtensions)
+        self.session.start()
 
-        if not session_started:
-            self.logger.warning("Failed to connect via WebDriver")
-            self.executor.runner.send_message("init_failed")
-        else:
-            self.executor.runner.send_message("init_succeeded")
+    def after_connect(self):
+        pass
 
     def teardown(self):
         self.logger.debug("Hanging up on WebDriver session")
         try:
             self.session.end()
-        except:
+        except Exception:
             pass
 
     def is_alive(self):
@@ -72,9 +59,6 @@
             return False
         return True
 
-    def after_connect(self):
-        pass
-
     def wait(self):
         while True:
             try:
@@ -125,7 +109,7 @@
             if message:
                 message += "\n"
             message += traceback.format_exc(e)
-            self.result = False, ("ERROR", e)
+            self.result = False, ("INTERNAL-ERROR", e)
         finally:
             self.result_flag.set()
 
@@ -230,7 +214,7 @@
             if message:
                 message += "\n"
             message += traceback.format_exc(e)
-            return test.result_cls("ERROR", message), []
+            return test.result_cls("INTERNAL-ERROR", message), []
 
     def screenshot(self, test, viewport_size, dpi):
         # https://github.com/w3c/wptrunner/issues/166
diff --git a/tools/wptrunner/wptrunner/executors/executorwebkit.py b/tools/wptrunner/wptrunner/executors/executorwebkit.py
new file mode 100644
index 0000000..c728ae1
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/executorwebkit.py
@@ -0,0 +1,10 @@
+from ..webdriver_server import WebKitDriverServer
+from .base import WdspecExecutor, WebDriverProtocol
+
+
+class WebKitDriverProtocol(WebDriverProtocol):
+    server_cls = WebKitDriverServer
+
+
+class WebKitDriverWdspecExecutor(WdspecExecutor):
+    protocol_cls = WebKitDriverProtocol
diff --git a/tools/wptrunner/wptrunner/executors/protocol.py b/tools/wptrunner/wptrunner/executors/protocol.py
new file mode 100644
index 0000000..3c938f0
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/protocol.py
@@ -0,0 +1,292 @@
+import traceback
+from abc import ABCMeta, abstractmethod
+
+
+class Protocol(object):
+    """Backend for a specific browser-control protocol.
+
+    Each Protocol is composed of a set of ProtocolParts that implement
+    the APIs required for specific interactions. This reflects the fact
+    that not all implementaions will support exactly the same feature set.
+    Each ProtocolPart is exposed directly on the protocol through an accessor
+    attribute with a name given by its `name` property.
+
+    :param Executor executor: The Executor instance that's using this Protocol
+    :param Browser browser: The Browser using this protocol"""
+    __metaclass__ = ABCMeta
+
+    implements = []
+
+    def __init__(self, executor, browser):
+        self.executor = executor
+        self.browser = browser
+
+        for cls in self.implements:
+            name = cls.name
+            assert not hasattr(self, name)
+            setattr(self, name, cls(self))
+
+    @property
+    def logger(self):
+        """:returns: Current logger"""
+        return self.executor.logger
+
+    @property
+    def is_alive(self):
+        """Is the browser connection still active
+
+        :returns: A boolean indicating whether the connection is still active."""
+        return True
+
+    def setup(self, runner):
+        """Handle protocol setup, and send a message to the runner to indicate
+        success or failure."""
+        msg = None
+        try:
+            msg = "Failed to start protocol connection"
+            self.connect()
+
+            msg = None
+
+            for cls in self.implements:
+                getattr(self, cls.name).setup()
+
+            msg = "Post-connection steps failed"
+            self.after_connect()
+        except Exception:
+            if msg is not None:
+                self.logger.warning(msg)
+            self.logger.error(traceback.format_exc())
+            self.executor.runner.send_message("init_failed")
+            return
+        else:
+            self.executor.runner.send_message("init_succeeded")
+
+    @abstractmethod
+    def connect(self):
+        """Make a connection to the remote browser"""
+        pass
+
+    @abstractmethod
+    def after_connect(self):
+        """Run any post-connection steps. This happens after the ProtocolParts are
+        initalized so can depend on a fully-populated object."""
+        pass
+
+    def teardown(self):
+        """Run cleanup steps after the tests are finished."""
+        for cls in self.implements:
+            getattr(self, cls.name).teardown()
+
+
+class ProtocolPart(object):
+    """Base class  for all ProtocolParts.
+
+    :param Protocol parent: The parent protocol"""
+    __metaclass__ = ABCMeta
+
+    name = None
+
+    def __init__(self, parent):
+        self.parent = parent
+
+    @property
+    def logger(self):
+        """:returns: Current logger"""
+        return self.parent.logger
+
+    def setup(self):
+        """Run any setup steps required for the ProtocolPart."""
+        pass
+
+    def teardown(self):
+        """Run any teardown steps required for the ProtocolPart."""
+        pass
+
+
+class BaseProtocolPart(ProtocolPart):
+    """Generic bits of protocol that are required for multiple test types"""
+    __metaclass__ = ABCMeta
+
+    name = "base"
+
+    @abstractmethod
+    def execute_script(self, script, async=False):
+        """Execute javascript in the current Window.
+
+        :param str script: The js source to execute. This is implicitly wrapped in a function.
+        :param bool async: Whether the script is asynchronous in the webdriver
+                           sense i.e. whether the return value is the result of
+                           the initial function call or if it waits for some callback.
+        :returns: The result of the script execution.
+        """
+        pass
+
+    @abstractmethod
+    def set_timeout(self, timeout):
+        """Set the timeout for script execution.
+
+        :param timeout: Script timeout in seconds"""
+        pass
+
+    @abstractmethod
+    def wait(self):
+        """Wait indefinitely for the browser to close"""
+        pass
+
+    @property
+    def current_window(self):
+        """Return a handle identifying the current top level browsing context
+
+        :returns: A protocol-specific handle"""
+        pass
+
+    @abstractmethod
+    def set_window(self, handle):
+        """Set the top level browsing context to one specified by a given handle.
+
+        :param handle: A protocol-specific handle identifying a top level browsing
+                       context."""
+        pass
+
+
+class TestharnessProtocolPart(ProtocolPart):
+    """Protocol part required to run testharness tests."""
+    __metaclass__ = ABCMeta
+
+    name = "testharness"
+
+    @abstractmethod
+    def load_runner(self, url_protocol):
+        """Load the initial page used to control the tests.
+
+        :param str url_protocol: "https" or "http" depending on the test metadata.
+        """
+        pass
+
+    @abstractmethod
+    def close_old_windows(self, url_protocol):
+        """Close existing windows except for the initial runner window.
+        After calling this method there must be exactly one open window that
+        contains the initial runner page.
+
+        :param str url_protocol: "https" or "http" depending on the test metadata.
+        """
+        pass
+
+    @abstractmethod
+    def get_test_window(self, window_id, parent):
+        """Get the window handle dorresponding to the window containing the
+        currently active test.
+
+        :param window_id: A string containing the DOM name of the Window that
+        contains the test, or None.
+        :param parent: The handle of the runner window.
+        :returns: A protocol-specific window handle.
+        """
+        pass
+
+
+class PrefsProtocolPart(ProtocolPart):
+    """Protocol part that allows getting and setting browser prefs."""
+    __metaclass__ = ABCMeta
+
+    name = "prefs"
+
+    @abstractmethod
+    def set(self, name, value):
+        """Set the named pref to value.
+
+        :param name: A pref name of browser-specific type
+        :param value: A pref value of browser-specific type"""
+        pass
+
+    @abstractmethod
+    def get(self, name):
+        """Get the current value of a named pref
+
+        :param name: A pref name of browser-specific type
+        :returns: A pref value of browser-specific type"""
+        pass
+
+    @abstractmethod
+    def clear(self, name):
+        """Reset the value of a named pref back to the default.
+
+        :param name: A pref name of browser-specific type"""
+        pass
+
+
+class StorageProtocolPart(ProtocolPart):
+    """Protocol part for manipulating browser storage."""
+    __metaclass__ = ABCMeta
+
+    name = "storage"
+
+    @abstractmethod
+    def clear_origin(self, url):
+        """Clear all the storage for a specified origin.
+
+        :param url: A url belonging to the origin"""
+        pass
+
+
+class SelectorProtocolPart(ProtocolPart):
+    """Protocol part for selecting elements on the page."""
+    __metaclass__ = ABCMeta
+
+    name = "select"
+
+    @abstractmethod
+    def elements_by_selector(self, selector):
+        """Select elements matching a CSS selector
+
+        :param str selector: The CSS selector
+        :returns: A list of protocol-specific handles to elements"""
+        pass
+
+
+class ClickProtocolPart(ProtocolPart):
+    """Protocol part for performing trusted clicks"""
+    __metaclass__ = ABCMeta
+
+    name = "click"
+
+    @abstractmethod
+    def element(self, element):
+        """Perform a trusted click somewhere on a specific element.
+
+        :param element: A protocol-specific handle to an element."""
+        pass
+
+class SendKeysProtocolPart(ProtocolPart):
+    """Protocol part for performing trusted clicks"""
+    __metaclass__ = ABCMeta
+
+    name = "send_keys"
+
+    @abstractmethod
+    def send_keys(self, element, keys):
+        """Send keys to a specific element.
+
+        :param element: A protocol-specific handle to an element.
+        :param keys: A protocol-specific handle to a string of input keys."""
+        pass
+
+
+class TestDriverProtocolPart(ProtocolPart):
+    """Protocol part that implements the basic functionality required for
+    all testdriver-based tests."""
+    __metaclass__ = ABCMeta
+
+    name = "testdriver"
+
+    @abstractmethod
+    def send_message(self, message_type, status, message=None):
+        """Send a testdriver message to the browser.
+
+        :param str message_type: The kind of the message.
+        :param str status: Either "failure" or "success" depending on whether the
+                           previous command succeeded.
+        :param str message: Additional data to add to the message."""
+        pass
diff --git a/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py b/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py
index 611a498..c1936eb 100644
--- a/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py
+++ b/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py
@@ -1,4 +1,5 @@
-"""Provides interface to deal with pytest.
+"""
+Provides interface to deal with pytest.
 
 Usage::
 
@@ -24,7 +25,8 @@
 
 
 def run(path, server_config, session_config, timeout=0):
-    """Run Python test at ``path`` in pytest.  The provided ``session``
+    """
+    Run Python test at ``path`` in pytest.  The provided ``session``
     is exposed as a fixture available in the scope of the test functions.
 
     :param path: Path to the test file.
@@ -33,36 +35,52 @@
     :param timeout: Duration before interrupting potentially hanging
         tests.  If 0, there is no timeout.
 
-    :returns: List of subtest results, which are tuples of (test id,
-        status, message, stacktrace).
+    :returns: (<harness result>, [<subtest result>, ...]),
+        where <subtest result> is (test id, status, message, stacktrace).
     """
-
     if pytest is None:
         do_delayed_imports()
 
-    recorder = SubtestResultRecorder()
-
     os.environ["WD_HOST"] = session_config["host"]
     os.environ["WD_PORT"] = str(session_config["port"])
     os.environ["WD_CAPABILITIES"] = json.dumps(session_config["capabilities"])
-    os.environ["WD_SERVER_CONFIG"] = json.dumps(server_config)
+    os.environ["WD_SERVER_CONFIG"] = json.dumps(server_config.as_dict())
 
-    plugins = [recorder]
-
-    # TODO(ato): Deal with timeouts
+    harness = HarnessResultRecorder()
+    subtests = SubtestResultRecorder()
 
     with TemporaryDirectory() as cache:
-        pytest.main(["--strict",  # turn warnings into errors
-                     "--verbose",  # show each individual subtest
-                     "--capture", "no",  # enable stdout/stderr from tests
-                     "--basetemp", cache,  # temporary directory
-                     "--showlocals",  # display contents of variables in local scope
-                     "-p", "no:mozlog",  # use the WPT result recorder
-                     "-p", "no:cacheprovider",  # disable state preservation across invocations
-                     path],
-                    plugins=plugins)
+        try:
+            pytest.main(["--strict",  # turn warnings into errors
+                         "-vv",  # show each individual subtest and full failure logs
+                         "--capture", "no",  # enable stdout/stderr from tests
+                         "--basetemp", cache,  # temporary directory
+                         "--showlocals",  # display contents of variables in local scope
+                         "-p", "no:mozlog",  # use the WPT result recorder
+                         "-p", "no:cacheprovider",  # disable state preservation across invocations
+                         "-o=console_output_style=classic",  # disable test progress bar
+                         path],
+                        plugins=[harness, subtests])
+        except Exception as e:
+            harness.outcome = ("INTERNAL-ERROR", str(e))
 
-    return recorder.results
+    return (harness.outcome, subtests.results)
+
+
+class HarnessResultRecorder(object):
+    outcomes = {
+        "failed": "ERROR",
+        "passed": "OK",
+        "skipped": "SKIP",
+    }
+
+    def __init__(self):
+        # we are ok unless told otherwise
+        self.outcome = ("OK", None)
+
+    def pytest_collectreport(self, report):
+        harness_result = self.outcomes[report.outcome]
+        self.outcome = (harness_result, None)
 
 
 class SubtestResultRecorder(object):
@@ -84,7 +102,19 @@
         self.record(report.nodeid, "PASS")
 
     def record_fail(self, report):
-        self.record(report.nodeid, "FAIL", stack=report.longrepr)
+        # pytest outputs the stacktrace followed by an error message prefixed
+        # with "E   ", e.g.
+        #
+        #        def test_example():
+        #  >         assert "fuu" in "foobar"
+        #  > E       AssertionError: assert 'fuu' in 'foobar'
+        message = ""
+        for line in report.longreprtext.splitlines():
+            if line.startswith("E   "):
+                message = line[1:].strip()
+                break
+
+        self.record(report.nodeid, "FAIL", message=message, stack=report.longrepr)
 
     def record_error(self, report):
         # error in setup/teardown
@@ -100,7 +130,7 @@
     def record(self, test, status, message=None, stack=None):
         if stack is not None:
             stack = str(stack)
-        new_result = (test, status, message, stack)
+        new_result = (test.split("::")[-1], status, message, stack)
         self.results.append(new_result)
 
 
diff --git a/tools/wptrunner/wptrunner/executors/reftest-wait_marionette.js b/tools/wptrunner/wptrunner/executors/reftest-wait_marionette.js
index c226027..61f894f 100644
--- a/tools/wptrunner/wptrunner/executors/reftest-wait_marionette.js
+++ b/tools/wptrunner/wptrunner/executors/reftest-wait_marionette.js
@@ -1,7 +1,9 @@
+var callback = arguments[arguments.length - 1];
+
 function test(x) {
   if (!root.classList.contains("reftest-wait")) {
     observer.disconnect();
-    marionetteScriptFinished();
+    callback();
   }
 }
 
diff --git a/tools/wptrunner/wptrunner/executors/testharness_marionette.js b/tools/wptrunner/wptrunner/executors/testharness_marionette.js
deleted file mode 100644
index e2b70a0..0000000
--- a/tools/wptrunner/wptrunner/executors/testharness_marionette.js
+++ /dev/null
@@ -1,31 +0,0 @@
-window.wrappedJSObject.timeout_multiplier = %(timeout_multiplier)d;
-window.wrappedJSObject.explicit_timeout = %(explicit_timeout)d;
-
-window.wrappedJSObject.addEventListener("message", function listener(event) {
-    if (event.data.type != "complete") {
-        return;
-    }
-    window.wrappedJSObject.removeEventListener("message", listener);
-    clearTimeout(timer);
-    var tests = event.data.tests;
-    var status = event.data.status;
-
-    var subtest_results = tests.map(function (x) {
-        return [x.name, x.status, x.message, x.stack]
-    });
-
-    marionetteScriptFinished(["%(url)s",
-                              status.status,
-                              status.message,
-                              status.stack,
-                              subtest_results]);
-}, false);
-
-window.wrappedJSObject.win = window.open("%(abs_url)s", "%(window_id)s");
-
-var timer = null;
-if (%(timeout)s) {
-    timer = setTimeout(function() {
-        window.wrappedJSObject.win.timeout();
-    }, %(timeout)s);
-}
diff --git a/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js b/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js
index 7229777..7a2df98 100644
--- a/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js
+++ b/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js
@@ -26,6 +26,8 @@
     });
     payload = data;
     break;
+  default:
+    return;
   }
 
   callback(["%(url)s", data.type, payload]);
diff --git a/tools/wptrunner/wptrunner/font.py b/tools/wptrunner/wptrunner/font.py
index fdf1b12..a370f5f 100644
--- a/tools/wptrunner/wptrunner/font.py
+++ b/tools/wptrunner/wptrunner/font.py
@@ -2,9 +2,10 @@
 import logging
 import os
 import platform
+import plistlib
 
 from shutil import copy2, rmtree
-from subprocess import call
+from subprocess import call, check_output
 
 HERE = os.path.split(__file__)[0]
 SYSTEM = platform.system().lower()
@@ -17,7 +18,10 @@
         self.created_dir = False
         self.fonts = fonts
 
-    def __enter__(self, options=None):
+    def __call__(self, env_options=None, env_config=None):
+        return self
+
+    def __enter__(self):
         for _, font_path in self.fonts.items():
             font_name = font_path.split('/')[-1]
             install = getattr(self, 'install_%s_font' % SYSTEM, None)
@@ -68,9 +72,19 @@
         if not os.path.exists(self.font_dir):
             os.makedirs(self.font_dir)
             self.created_dir = True
-        if not os.path.exists(os.path.join(self.font_dir, font_name)):
+        installed_font_path = os.path.join(self.font_dir, font_name)
+        if not os.path.exists(installed_font_path):
             copy2(font_path, self.font_dir)
-        return True
+
+        # Per https://github.com/web-platform-tests/results-collection/issues/218
+        # installing Ahem on macOS is flaky, so check if it actually installed
+        fonts = check_output(['/usr/sbin/system_profiler', '-xml', 'SPFontsDataType'])
+        fonts = plistlib.readPlistFromString(fonts)
+        assert len(fonts) == 1
+        for font in fonts[0]['_items']:
+            if font['path'] == installed_font_path:
+                return True
+        return False
 
     def install_windows_font(self, _, font_path):
         hwnd_broadcast = 0xFFFF
diff --git a/tools/wptrunner/wptrunner/formatters.py b/tools/wptrunner/wptrunner/formatters.py
index 0e888ce..24d0653 100755
--- a/tools/wptrunner/wptrunner/formatters.py
+++ b/tools/wptrunner/wptrunner/formatters.py
@@ -8,15 +8,20 @@
 
     def __init__(self):
         self.raw_results = {}
+        self.results = {}
+
+    def suite_start(self, data):
+        self.results['run_info'] = data['run_info']
+        self.results['time_start'] = data['time']
 
     def suite_end(self, data):
-        results = {}
-        results["results"] = []
+        self.results['time_end'] = data['time']
+        self.results["results"] = []
         for test_name in self.raw_results:
             result = {"test": test_name}
             result.update(self.raw_results[test_name])
-            results["results"].append(result)
-        return json.dumps(results)
+            self.results["results"].append(result)
+        return json.dumps(self.results)
 
     def find_or_create_test(self, data):
         test_name = data["test"]
diff --git a/tools/wptrunner/wptrunner/hosts.py b/tools/wptrunner/wptrunner/hosts.py
deleted file mode 100644
index 915c17f..0000000
--- a/tools/wptrunner/wptrunner/hosts.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from __future__ import unicode_literals
-
-
-class HostsLine(object):
-    def __init__(self, ip_address, canonical_hostname, aliases=None, comment=None):
-        self.ip_address = ip_address
-        self.canonical_hostname = canonical_hostname
-        self.aliases = aliases if aliases is not None else []
-        self.comment = comment
-        if self.ip_address is None:
-            assert self.canonical_hostname is None
-            assert not self.aliases
-            assert self.comment is not None
-
-    @classmethod
-    def from_string(cls, line):
-        if not line.strip():
-            return
-
-        line = line.strip()
-
-        ip_address = None
-        canonical_hostname = None
-        aliases = []
-        comment = None
-
-        comment_parts = line.split("#", 1)
-        if len(comment_parts) > 1:
-            comment = comment_parts[1]
-
-        data = comment_parts[0].strip()
-
-        if data:
-            fields = data.split()
-            if len(fields) < 2:
-                raise ValueError("Invalid hosts line")
-
-            ip_address = fields[0]
-            canonical_hostname = fields[1]
-            aliases = fields[2:]
-
-        return cls(ip_address, canonical_hostname, aliases, comment)
-
-
-class HostsFile(object):
-    def __init__(self):
-        self.data = []
-        self.by_hostname = {}
-
-    def set_host(self, host):
-        if host.canonical_hostname is None:
-            self.data.append(host)
-        elif host.canonical_hostname in self.by_hostname:
-            old_host = self.by_hostname[host.canonical_hostname]
-            old_host.ip_address = host.ip_address
-            old_host.aliases = host.aliases
-            old_host.comment = host.comment
-        else:
-            self.data.append(host)
-            self.by_hostname[host.canonical_hostname] = host
-
-    @classmethod
-    def from_file(cls, f):
-        rv = cls()
-        for line in f:
-            host = HostsLine.from_string(line)
-            if host is not None:
-                rv.set_host(host)
-        return rv
-
-    def to_string(self):
-        field_widths = [0, 0]
-        for line in self.data:
-            if line.ip_address is not None:
-                field_widths[0] = max(field_widths[0], len(line.ip_address))
-                field_widths[1] = max(field_widths[1], len(line.canonical_hostname))
-
-        lines = []
-
-        for host in self.data:
-            line = ""
-            if host.ip_address is not None:
-                ip_string = host.ip_address.ljust(field_widths[0])
-                hostname_str = host.canonical_hostname
-                if host.aliases:
-                    hostname_str = "%s %s" % (hostname_str.ljust(field_widths[1]),
-                                              " ".join(host.aliases))
-                line = "%s %s" % (ip_string, hostname_str)
-            if host.comment:
-                if line:
-                    line += " "
-                line += "#%s" % host.comment
-            lines.append(line)
-
-        lines.append("")
-
-        return "\n".join(lines)
-
-    def to_file(self, f):
-        f.write(self.to_string().encode("utf8"))
diff --git a/tools/wptrunner/wptrunner/manifestupdate.py b/tools/wptrunner/wptrunner/manifestupdate.py
index fd3f82a..1d3c173 100644
--- a/tools/wptrunner/wptrunner/manifestupdate.py
+++ b/tools/wptrunner/wptrunner/manifestupdate.py
@@ -247,8 +247,8 @@
                         boolean_properties=self.root.boolean_properties)
                 except ConditionError as e:
                     if stability is not None:
-                       self.set("disabled", stability or "unstable", e.cond.children[0])
-                       self.new_disabled = True
+                        self.set("disabled", stability or "unstable", e.cond.children[0])
+                        self.new_disabled = True
                     else:
                         print "Conflicting test results for %s, cannot update" % self.root.test_path
                     return
diff --git a/tools/wptrunner/wptrunner/metadata.py b/tools/wptrunner/wptrunner/metadata.py
index 923ac79..92d77ba 100644
--- a/tools/wptrunner/wptrunner/metadata.py
+++ b/tools/wptrunner/wptrunner/metadata.py
@@ -65,9 +65,9 @@
                 for test in tree.iterchildren():
                     for subtest in test.iterchildren():
                         if subtest.new_disabled:
-                            print os.path.dirname(subtest.root.test_path) + "/" + subtest.name
+                            print "disabled: %s" % os.path.dirname(subtest.root.test_path) + "/" + subtest.name
                     if test.new_disabled:
-                        print test.root.test_path
+                        print "disabled: %s" % test.root.test_path
 
     results_changed = [item.test_path for item in expected_map.itervalues() if item.modified]
 
@@ -355,7 +355,7 @@
 
     # Remove expected data for tests that no longer exist
     for test in expected_manifest.iterchildren():
-        if not test.id in tests_by_id:
+        if test.id not in tests_by_id:
             test.remove()
 
     # Add tests that don't have expected data
diff --git a/tools/wptrunner/wptrunner/reduce.py b/tools/wptrunner/wptrunner/reduce.py
index d245ee3..f5c60bf 100644
--- a/tools/wptrunner/wptrunner/reduce.py
+++ b/tools/wptrunner/wptrunner/reduce.py
@@ -47,6 +47,7 @@
         self.test_type = kwargs["test_types"][0]
         run_info = wpttest.get_run_info(kwargs["metadata_root"],
                                         kwargs["product"],
+                                        browser_version=kwargs.get("browser_version"),
                                         debug=False)
         test_filter = wptrunner.TestFilter(include=kwargs["include"])
         self.test_loader = wptrunner.TestLoader(kwargs["tests_root"],
diff --git a/tools/wptrunner/wptrunner/stability.py b/tools/wptrunner/wptrunner/stability.py
index 6eb0604..e1709c5 100644
--- a/tools/wptrunner/wptrunner/stability.py
+++ b/tools/wptrunner/wptrunner/stability.py
@@ -274,6 +274,6 @@
             write_summary(logger, step_results, "FAIL")
             return 1
 
-        step_results.append((desc,  "PASS"))
+        step_results.append((desc, "PASS"))
 
     write_summary(logger, step_results, "PASS")
diff --git a/tools/wptrunner/wptrunner/testdriver-extra.js b/tools/wptrunner/wptrunner/testdriver-extra.js
index 856a33e..ef962d3 100644
--- a/tools/wptrunner/wptrunner/testdriver-extra.js
+++ b/tools/wptrunner/wptrunner/testdriver-extra.js
@@ -60,4 +60,14 @@
         window.opener.postMessage({"type": "action", "action": "click", "selector": selector}, "*");
         return pending_promise;
     };
+
+    window.test_driver_internal.send_keys = function(element, keys) {
+        const selector = get_selector(element);
+        const pending_promise = new Promise(function(resolve, reject) {
+            pending_resolve = resolve;
+            pending_reject = reject;
+        });
+        window.opener.postMessage({"type": "action", "action": "send_keys", "selector": selector, "keys": keys}, "*");
+        return pending_promise;
+    };
 })();
diff --git a/tools/wptrunner/wptrunner/testdriver-vendor.js b/tools/wptrunner/wptrunner/testdriver-vendor.js
new file mode 100644
index 0000000..3e88403
--- /dev/null
+++ b/tools/wptrunner/wptrunner/testdriver-vendor.js
@@ -0,0 +1 @@
+// This file intentionally left blank
diff --git a/tools/wptrunner/wptrunner/testloader.py b/tools/wptrunner/wptrunner/testloader.py
index 0eb78bb..6aaf5f1 100644
--- a/tools/wptrunner/wptrunner/testloader.py
+++ b/tools/wptrunner/wptrunner/testloader.py
@@ -93,7 +93,7 @@
         for i, (test_type, test_path, tests) in enumerate(manifest_items):
             test_dir = tuple(os.path.split(test_path)[0].split(os.path.sep)[:3])
 
-            if not test_dir in by_dir:
+            if test_dir not in by_dir:
                 by_dir[test_dir] = PathData(test_dir)
 
             data = by_dir[test_dir]
@@ -261,7 +261,7 @@
                 return self.paths.popleft()
 
             @property
-            def badness(self_):
+            def badness(self_):  # noqa: N805
                 """Badness metric for this chunk"""
                 return self._badness(self_.time)
 
@@ -376,6 +376,7 @@
             if test.tags & self.tags:
                 yield test
 
+
 class ManifestLoader(object):
     def __init__(self, test_paths, force_manifest_update=False, manifest_download=False):
         do_delayed_imports()
@@ -415,10 +416,12 @@
                 with open(manifest_path) as f:
                     json_data = json.load(f)
             except IOError:
-                #If the existing file doesn't exist just create one from scratch
-                pass
+                self.logger.info("Unable to find test manifest")
+            except ValueError:
+                self.logger.info("Unable to parse test manifest")
 
         if not json_data:
+            self.logger.info("Creating test manifest")
             manifest_file = manifest.Manifest(url_base)
         else:
             try:
@@ -430,8 +433,7 @@
 
         manifest.write(manifest_file, manifest_path)
 
-    def load_manifest(self, tests_path, metadata_path, url_base="/"):
-        manifest_path = os.path.join(metadata_path, "MANIFEST.json")
+    def load_manifest(self, tests_path, manifest_path, url_base="/", **kwargs):
         if (not os.path.exists(manifest_path) or
             self.force_manifest_update):
             self.update_manifest(manifest_path, tests_path, url_base, download=self.manifest_download)
@@ -444,6 +446,7 @@
 
         return manifest_file
 
+
 def iterfilter(filters, iter):
     for f in filters:
         iter = f(iter)
@@ -587,6 +590,7 @@
         self.current_metadata = None
 
     @abstractmethod
+    # noqa: N805
     #@classmethod (doesn't compose with @abstractmethod)
     def make_queue(cls, tests, **kwargs):
         pass
diff --git a/tools/wptrunner/wptrunner/testrunner.py b/tools/wptrunner/wptrunner/testrunner.py
index 6441828..257e789 100644
--- a/tools/wptrunner/wptrunner/testrunner.py
+++ b/tools/wptrunner/wptrunner/testrunner.py
@@ -108,7 +108,7 @@
             raise
 
     def wait(self):
-        self.executor.protocol.wait()
+        self.executor.wait()
         self.send_message("wait_finished")
 
     def send_message(self, command, *args):
@@ -120,6 +120,13 @@
                  executor_browser_cls, executor_browser_kwargs,
                  stop_flag):
     """Launch a TestRunner in a new process"""
+    def log(level, msg):
+        runner_result_queue.put(("log", (level, {"message": msg})))
+
+    def handle_error(e):
+        log("critical", traceback.format_exc())
+        stop_flag.set()
+
     try:
         browser = executor_browser_cls(**executor_browser_kwargs)
         executor = executor_cls(browser, **executor_kwargs)
@@ -128,10 +135,10 @@
                 runner.run()
             except KeyboardInterrupt:
                 stop_flag.set()
-    except Exception:
-        runner_result_queue.put(("log", ("critical", {"message": traceback.format_exc()})))
-        print >> sys.stderr, traceback.format_exc()
-        stop_flag.set()
+            except Exception as e:
+                handle_error(e)
+    except Exception as e:
+        handle_error(e)
     finally:
         runner_command_queue = None
         runner_result_queue = None
@@ -188,7 +195,7 @@
             self.logger.debug("Starting browser with settings %r" % self.browser_settings)
             self.browser.start(**self.browser_settings)
             self.browser_pid = self.browser.pid()
-        except:
+        except Exception:
             self.logger.warning("Failure during init %s" % traceback.format_exc())
             if self.init_timer is not None:
                 self.init_timer.cancel()
@@ -222,7 +229,6 @@
     def cleanup(self):
         if self.init_timer is not None:
             self.init_timer.cancel()
-        self.browser.cleanup()
 
     def check_for_crashes(self):
         self.browser.check_for_crashes()
@@ -298,6 +304,7 @@
         # This is started in the actual new thread
         self.logger = None
 
+        self.test_count = 0
         self.unexpected_count = 0
 
         # This may not really be what we want
@@ -389,6 +396,7 @@
         }
         try:
             command, data = self.command_queue.get(True, 1)
+            self.logger.debug("Got command: %r" % command)
         except IOError:
             self.logger.error("Got IOError from poll")
             return RunnerManagerState.restarting(0)
@@ -552,16 +560,22 @@
                                     expected=expected,
                                     stack=result.stack)
 
-        # TODO: consider changing result if there is a crash dump file
-
-        # Write the result of the test harness
+        # We have a couple of status codes that are used internally, but not exposed to the
+        # user. These are used to indicate that some possibly-broken state was reached
+        # and we should restart the runner before the next test.
+        # INTERNAL-ERROR indicates a Python exception was caught in the harness
+        # EXTERNAL-TIMEOUT indicates we had to forcibly kill the browser from the harness
+        # because the test didn't return a result after reaching the test-internal timeout
+        status_subns = {"INTERNAL-ERROR": "ERROR",
+                        "EXTERNAL-TIMEOUT": "TIMEOUT"}
         expected = test.expected()
-        status = file_result.status if file_result.status != "EXTERNAL-TIMEOUT" else "TIMEOUT"
+        status = status_subns.get(file_result.status, file_result.status)
 
-        if file_result.status in  ("TIMEOUT", "EXTERNAL-TIMEOUT"):
+        if file_result.status in ("TIMEOUT", "EXTERNAL-TIMEOUT", "INTERNAL-ERROR"):
             if self.browser.check_for_crashes():
                 status = "CRASH"
 
+        self.test_count += 1
         is_unexpected = expected != status
         if is_unexpected:
             self.unexpected_count += 1
@@ -576,11 +590,12 @@
                              extra=file_result.extra)
 
         restart_before_next = (test.restart_after or
-                               file_result.status in ("CRASH", "EXTERNAL-TIMEOUT") or
-                               ((subtest_unexpected or is_unexpected)
-                                and self.restart_on_unexpected))
+                               file_result.status in ("CRASH", "EXTERNAL-TIMEOUT", "INTERNAL-ERROR") or
+                               ((subtest_unexpected or is_unexpected) and
+                                self.restart_on_unexpected))
 
-        if (self.pause_after_test or
+        if (not file_result.status == "CRASH" and
+            self.pause_after_test or
             (self.pause_on_unexpected and (subtest_unexpected or is_unexpected))):
             self.logger.info("Pausing until the browser exits")
             self.send_message("wait")
@@ -676,7 +691,18 @@
             self.browser.cleanup()
         while True:
             try:
-                self.logger.warning(" ".join(map(repr, self.command_queue.get_nowait())))
+                cmd, data = self.command_queue.get_nowait()
+            except Empty:
+                break
+            else:
+                if cmd == "log":
+                    self.log(*data)
+                else:
+                    self.logger.warning("%r: %r" % (cmd, data))
+        while True:
+            try:
+                cmd, data = self.remote_queue.get_nowait()
+                self.logger.warning("%r: %r" % (cmd, data))
             except Empty:
                 break
 
@@ -771,5 +797,8 @@
         self.stop_flag.set()
         self.logger.debug("Stop flag set in ManagerGroup")
 
+    def test_count(self):
+        return sum(item.test_count for item in self.pool)
+
     def unexpected_count(self):
         return sum(item.unexpected_count for item in self.pool)
diff --git a/tools/wptrunner/wptrunner/tests/base.py b/tools/wptrunner/wptrunner/tests/base.py
new file mode 100644
index 0000000..b5173f3
--- /dev/null
+++ b/tools/wptrunner/wptrunner/tests/base.py
@@ -0,0 +1,60 @@
+import os
+import sys
+
+from os.path import dirname, join
+
+import pytest
+
+sys.path.insert(0, join(dirname(__file__), "..", ".."))
+
+from wptrunner import browsers
+
+
+_products = browsers.product_list
+_active_products = set()
+
+if "CURRENT_TOX_ENV" in os.environ:
+    current_tox_env_split = os.environ["CURRENT_TOX_ENV"].split("-")
+
+    tox_env_extra_browsers = {
+        "chrome": {"chrome_android"},
+        "servo": {"servodriver"},
+    }
+
+    _active_products = set(_products) & set(current_tox_env_split)
+    for product in frozenset(_active_products):
+        _active_products |= tox_env_extra_browsers.get(product, set())
+else:
+    _active_products = set(_products)
+
+
+class all_products(object):
+    def __init__(self, arg, marks={}):
+        self.arg = arg
+        self.marks = marks
+
+    def __call__(self, f):
+        params = []
+        for product in _products:
+            if product in self.marks:
+                params.append(pytest.param(product, marks=self.marks[product]))
+            else:
+                params.append(product)
+        return pytest.mark.parametrize(self.arg, params)(f)
+
+
+class active_products(object):
+    def __init__(self, arg, marks={}):
+        self.arg = arg
+        self.marks = marks
+
+    def __call__(self, f):
+        params = []
+        for product in _products:
+            if product not in _active_products:
+                params.append(pytest.param(product, marks=pytest.mark.skip(reason="wrong toxenv")))
+            elif product in self.marks:
+                params.append(pytest.param(product, marks=self.marks[product]))
+            else:
+                params.append(product)
+        return pytest.mark.parametrize(self.arg, params)(f)
diff --git a/tools/py/testing/log/__init__.py b/tools/wptrunner/wptrunner/tests/browsers/__init__.py
similarity index 100%
copy from tools/py/testing/log/__init__.py
copy to tools/wptrunner/wptrunner/tests/browsers/__init__.py
diff --git a/tools/wptrunner/wptrunner/tests/browsers/test_sauce.py b/tools/wptrunner/wptrunner/tests/browsers/test_sauce.py
new file mode 100644
index 0000000..db0de8f
--- /dev/null
+++ b/tools/wptrunner/wptrunner/tests/browsers/test_sauce.py
@@ -0,0 +1,132 @@
+import sys
+from os.path import join, dirname
+
+import mock
+import pytest
+
+sys.path.insert(0, join(dirname(__file__), "..", "..", ".."))
+
+sauce = pytest.importorskip("wptrunner.browsers.sauce")
+
+from wptserve.config import Config
+
+
+def test_sauceconnect_success():
+    with mock.patch.object(sauce.SauceConnect, "upload_prerun_exec"),\
+            mock.patch.object(sauce.subprocess, "Popen") as Popen,\
+            mock.patch.object(sauce.os.path, "exists") as exists:
+        # Act as if it's still running
+        Popen.return_value.poll.return_value = None
+        Popen.return_value.returncode = None
+        # Act as if file created
+        exists.return_value = True
+
+        sauce_connect = sauce.SauceConnect(
+            sauce_user="aaa",
+            sauce_key="bbb",
+            sauce_tunnel_id="ccc",
+            sauce_connect_binary="ddd")
+
+        env_config = Config(browser_host="example.net")
+        sauce_connect(None, env_config)
+        with sauce_connect:
+            pass
+
+
+@pytest.mark.parametrize("readyfile,returncode", [
+    (True, 0),
+    (True, 1),
+    (True, 2),
+    (False, 0),
+    (False, 1),
+    (False, 2),
+])
+def test_sauceconnect_failure_exit(readyfile, returncode):
+    with mock.patch.object(sauce.SauceConnect, "upload_prerun_exec"),\
+            mock.patch.object(sauce.subprocess, "Popen") as Popen,\
+            mock.patch.object(sauce.os.path, "exists") as exists,\
+            mock.patch.object(sauce.time, "sleep") as sleep:
+        Popen.return_value.poll.return_value = returncode
+        Popen.return_value.returncode = returncode
+        exists.return_value = readyfile
+
+        sauce_connect = sauce.SauceConnect(
+            sauce_user="aaa",
+            sauce_key="bbb",
+            sauce_tunnel_id="ccc",
+            sauce_connect_binary="ddd")
+
+        env_config = Config(browser_host="example.net")
+        sauce_connect(None, env_config)
+        with pytest.raises(sauce.SauceException):
+            with sauce_connect:
+                pass
+
+        # Given we appear to exit immediately with these mocks, sleep shouldn't be called
+        sleep.assert_not_called()
+
+
+def test_sauceconnect_failure_never_ready():
+    with mock.patch.object(sauce.SauceConnect, "upload_prerun_exec"),\
+            mock.patch.object(sauce.subprocess, "Popen") as Popen,\
+            mock.patch.object(sauce.os.path, "exists") as exists,\
+            mock.patch.object(sauce.time, "sleep") as sleep:
+        Popen.return_value.poll.return_value = None
+        Popen.return_value.returncode = None
+        exists.return_value = False
+
+        sauce_connect = sauce.SauceConnect(
+            sauce_user="aaa",
+            sauce_key="bbb",
+            sauce_tunnel_id="ccc",
+            sauce_connect_binary="ddd")
+
+        env_config = Config(browser_host="example.net")
+        sauce_connect(None, env_config)
+        with pytest.raises(sauce.SauceException):
+            with sauce_connect:
+                pass
+
+        # We should sleep while waiting for it to create the readyfile
+        sleep.assert_called()
+
+        # Check we actually kill it after termination fails
+        Popen.return_value.terminate.assert_called()
+        Popen.return_value.kill.assert_called()
+
+
+def test_sauceconnect_tunnel_domains():
+    with mock.patch.object(sauce.SauceConnect, "upload_prerun_exec"),\
+            mock.patch.object(sauce.subprocess, "Popen") as Popen,\
+            mock.patch.object(sauce.os.path, "exists") as exists:
+        Popen.return_value.poll.return_value = None
+        Popen.return_value.returncode = None
+        exists.return_value = True
+
+        sauce_connect = sauce.SauceConnect(
+            sauce_user="aaa",
+            sauce_key="bbb",
+            sauce_tunnel_id="ccc",
+            sauce_connect_binary="ddd")
+
+        env_config = Config(browser_host="example.net",
+                            alternate_hosts={"alt": "example.org"},
+                            subdomains={"a", "b"},
+                            not_subdomains={"x", "y"})
+        sauce_connect(None, env_config)
+        with sauce_connect:
+            Popen.assert_called_once()
+            args, kwargs = Popen.call_args
+            cmd = args[0]
+            assert "--tunnel-domains" in cmd
+            i = cmd.index("--tunnel-domains")
+            rest = cmd[i+1:]
+            assert len(rest) >= 1
+            if len(rest) > 1:
+                assert rest[1].startswith("-"), "--tunnel-domains takes a comma separated list (not a space separated list)"
+            assert set(rest[0].split(",")) == {'example.net',
+                                               'a.example.net',
+                                               'b.example.net',
+                                               'example.org',
+                                               'a.example.org',
+                                               'b.example.org'}
diff --git a/tools/wptrunner/wptrunner/tests/test_hosts.py b/tools/wptrunner/wptrunner/tests/test_hosts.py
deleted file mode 100644
index 808b816..0000000
--- a/tools/wptrunner/wptrunner/tests/test_hosts.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import unittest
-import sys
-from os.path import join, dirname
-from cStringIO import StringIO
-
-sys.path.insert(0, join(dirname(__file__), "..", ".."))
-
-from wptrunner import hosts
-
-
-class HostsTest(unittest.TestCase):
-    def do_test(self, input, expected):
-        host_file = hosts.HostsFile.from_file(StringIO(input))
-        self.assertEquals(host_file.to_string(), expected)
-
-    def test_simple(self):
-        self.do_test("""127.0.0.1    \tlocalhost  alias # comment
-# Another comment""",
-                     """127.0.0.1 localhost alias # comment
-# Another comment
-""")
-
-    def test_blank_lines(self):
-        self.do_test("""127.0.0.1    \tlocalhost  alias # comment
-
-\r
-    \t
-# Another comment""",
-                     """127.0.0.1 localhost alias # comment
-# Another comment
-""")
-
-    def test_whitespace(self):
-        self.do_test("""    \t127.0.0.1    \tlocalhost  alias # comment     \r
-    \t# Another comment""",
-                     """127.0.0.1 localhost alias # comment
-# Another comment
-""")
-
-    def test_alignment(self):
-        self.do_test("""127.0.0.1    \tlocalhost  alias
-192.168.1.1 another_host    another_alias
-""","""127.0.0.1   localhost    alias
-192.168.1.1 another_host another_alias
-"""
-)
-
-    def test_multiple_same_name(self):
-        # The semantics are that we overwrite earlier entries with the same name
-        self.do_test("""127.0.0.1    \tlocalhost  alias
-192.168.1.1 localhost    another_alias""","""192.168.1.1 localhost another_alias
-"""
-)
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tools/wptrunner/wptrunner/tests/test_products.py b/tools/wptrunner/wptrunner/tests/test_products.py
new file mode 100644
index 0000000..8ece29b
--- /dev/null
+++ b/tools/wptrunner/wptrunner/tests/test_products.py
@@ -0,0 +1,63 @@
+import os
+import sys
+
+from os.path import join, dirname
+
+import mock
+import pytest
+
+from .base import all_products, active_products
+
+sys.path.insert(0, join(dirname(__file__), "..", "..", "..", ".."))  # repo root
+from tools import localpaths
+from wptserve import sslutils
+
+from wptrunner import environment
+from wptrunner import products
+
+test_paths = {"/": {"tests_path": join(dirname(__file__), "..", "..", "..", "..")}}  # repo root
+environment.do_delayed_imports(None, test_paths)
+
+
+@active_products("product")
+def test_load_active_product(product):
+    """test we can successfully load the product of the current testenv"""
+    products.load_product({}, product)
+    # test passes if it doesn't throw
+
+
+@all_products("product")
+def test_load_all_products(product):
+    """test every product either loads or throws ImportError"""
+    try:
+        products.load_product({}, product)
+    except ImportError:
+        pass
+
+
+@active_products("product", marks={
+    "sauce": pytest.mark.skip("needs env extras kwargs"),
+})
+def test_server_start_config(product):
+    (check_args,
+     target_browser_cls, get_browser_kwargs,
+     executor_classes, get_executor_kwargs,
+     env_options, get_env_extras, run_info_extras) = products.load_product({}, product)
+
+    env_extras = get_env_extras()
+
+    with mock.patch.object(environment.serve, "start") as start:
+        with environment.TestEnvironment(test_paths,
+                                         sslutils.environments["none"](None),
+                                         False,
+                                         None,
+                                         env_options,
+                                         env_extras) as test_environment:
+            start.assert_called_once()
+            args = start.call_args
+            config = args[0][0]
+            if "server_host" in env_options:
+                assert config["server_host"] == env_options["server_host"]
+            else:
+                assert config["server_host"] == config["browser_host"]
+            assert isinstance(config["bind_address"], bool)
diff --git a/tools/wptrunner/wptrunner/update/__init__.py b/tools/wptrunner/wptrunner/update/__init__.py
index 497cb34..99dac6b 100644
--- a/tools/wptrunner/wptrunner/update/__init__.py
+++ b/tools/wptrunner/wptrunner/update/__init__.py
@@ -44,4 +44,3 @@
     assert structuredlog.get_default_logger() is not None
     success = run_update(logger, **args)
     sys.exit(0 if success else 1)
-
diff --git a/tools/wptrunner/wptrunner/update/metadata.py b/tools/wptrunner/wptrunner/update/metadata.py
index 67f46de..c70ec61 100644
--- a/tools/wptrunner/wptrunner/update/metadata.py
+++ b/tools/wptrunner/wptrunner/update/metadata.py
@@ -4,14 +4,14 @@
 
 from base import Step, StepRunner
 
+
 class GetUpdatePropertyList(Step):
     provides = ["property_order", "boolean_properties"]
 
-
     def create(self, state):
         property_order, boolean_properties = products.load_product_update(
             state.config, state.product)
-        state.property_order = property_order
+        state.property_order = property_order + state.extra_properties
         state.boolean_properties = boolean_properties
 
 
diff --git a/tools/wptrunner/wptrunner/update/sync.py b/tools/wptrunner/wptrunner/update/sync.py
index 40bd1d7..f2660e5 100644
--- a/tools/wptrunner/wptrunner/update/sync.py
+++ b/tools/wptrunner/wptrunner/update/sync.py
@@ -81,7 +81,6 @@
             shutil.copy2(source_path, dest_path)
 
     for source, destination in [("testharness_runner.html", ""),
-                                ("testharnessreport.js", "resources/"),
                                 ("testdriver-vendor.js", "resources/")]:
         source_path = os.path.join(here, os.pardir, source)
         dest_path = os.path.join(dest, destination, os.path.split(source)[1])
@@ -110,7 +109,7 @@
                          state.sync["branch"],
                          state.local_branch)
         sync_path = os.path.abspath(sync_tree.root)
-        if not sync_path in sys.path:
+        if sync_path not in sys.path:
             from update import setup_paths
             setup_paths(sync_path)
 
diff --git a/tools/wptrunner/wptrunner/update/tree.py b/tools/wptrunner/wptrunner/update/tree.py
index 01df0b4..2e35236 100644
--- a/tools/wptrunner/wptrunner/update/tree.py
+++ b/tools/wptrunner/wptrunner/update/tree.py
@@ -75,7 +75,7 @@
             kwargs["repo"] = path
         try:
             hg("root", **kwargs)
-        except:
+        except Exception:
             return False
         return True
 
@@ -131,11 +131,11 @@
 class GitTree(object):
     name = "git"
 
-    def __init__(self, root=None):
+    def __init__(self, root=None, log_error=True):
         if root is None:
-            root = git("rev-parse", "--show-toplevel").strip()
+            root = git("rev-parse", "--show-toplevel", log_error=log_error).strip()
         self.root = root
-        self.git = vcs.bind_to_repo(git, self.root)
+        self.git = vcs.bind_to_repo(git, self.root, log_error=log_error)
         self.message = None
         self.commit_cls = Commit
 
@@ -155,7 +155,7 @@
             kwargs["repo"] = path
         try:
             git("rev-parse", "--show-toplevel", **kwargs)
-        except:
+        except Exception:
             return False
         return True
 
@@ -305,8 +305,8 @@
 
     def paths(self):
         """List paths in the tree"""
-        repo_paths = [self.root] +  [os.path.join(self.root, path)
-                                     for path in self.submodules()]
+        repo_paths = [self.root] + [os.path.join(self.root, path)
+                                    for path in self.submodules()]
 
         rv = []
 
diff --git a/tools/wptrunner/wptrunner/update/update.py b/tools/wptrunner/wptrunner/update/update.py
index d9ef40f..91029c5 100644
--- a/tools/wptrunner/wptrunner/update/update.py
+++ b/tools/wptrunner/wptrunner/update/update.py
@@ -11,10 +11,7 @@
 
 def setup_paths(sync_path):
     sys.path.insert(0, os.path.abspath(sync_path))
-    try:
-        from tools import localpaths
-    except ImportError:
-        from wpt_tools import localpaths
+    from tools import localpaths
 
 class LoadConfig(Step):
     """Step for loading configuration from the ini file and kwargs."""
@@ -95,6 +92,7 @@
             state.suite_name = kwargs["suite_name"]
             state.product = kwargs["product"]
             state.config = kwargs["config"]
+            state.extra_properties = kwargs["extra_property"]
             runner = MetadataUpdateRunner(self.logger, state)
             runner.run()
 
diff --git a/tools/wptrunner/wptrunner/vcs.py b/tools/wptrunner/wptrunner/vcs.py
index 16d53af..ab72cf8 100644
--- a/tools/wptrunner/wptrunner/vcs.py
+++ b/tools/wptrunner/wptrunner/vcs.py
@@ -15,7 +15,7 @@
         repo = kwargs.pop("repo", None)
         log_error = kwargs.pop("log_error", True)
         if kwargs:
-            raise TypeError, kwargs
+            raise TypeError(kwargs)
 
         args = list(args)
 
@@ -27,6 +27,10 @@
         logger.debug(" ".join(command_line))
         try:
             return subprocess.check_output(command_line, stderr=subprocess.STDOUT, **proc_kwargs)
+        except OSError as e:
+            if log_error:
+                logger.error(e)
+            raise
         except subprocess.CalledProcessError as e:
             if log_error:
                 logger.error(e.output)
@@ -37,13 +41,13 @@
 hg = vcs("hg")
 
 
-def bind_to_repo(vcs_func, repo):
-    return partial(vcs_func, repo=repo)
+def bind_to_repo(vcs_func, repo, log_error=True):
+    return partial(vcs_func, repo=repo, log_error=log_error)
 
 
-def is_git_root(path):
+def is_git_root(path, log_error=True):
     try:
-        rv = git("rev-parse", "--show-cdup", repo=path)
+        rv = git("rev-parse", "--show-cdup", repo=path, log_error=log_error)
     except subprocess.CalledProcessError:
         return False
     return rv == "\n"
diff --git a/tools/wptrunner/wptrunner/webdriver_server.py b/tools/wptrunner/wptrunner/webdriver_server.py
index 4a3c48c..5822b36 100644
--- a/tools/wptrunner/wptrunner/webdriver_server.py
+++ b/tools/wptrunner/wptrunner/webdriver_server.py
@@ -13,7 +13,7 @@
 
 __all__ = ["SeleniumServer", "ChromeDriverServer", "OperaDriverServer",
            "GeckoDriverServer", "InternetExplorerDriverServer", "EdgeDriverServer",
-           "ServoDriverServer", "WebDriverServer"]
+           "ServoDriverServer", "WebKitDriverServer", "WebDriverServer"]
 
 
 class WebDriverServer(object):
@@ -60,21 +60,22 @@
             env=self.env,
             storeOutput=False)
 
+        self.logger.debug("Starting WebDriver: %s" % ' '.join(self._cmd))
         try:
             self._proc.run()
         except OSError as e:
             if e.errno == errno.ENOENT:
                 raise IOError(
-                    "WebDriver HTTP server executable not found: %s" % self.binary)
+                    "WebDriver executable not found: %s" % self.binary)
             raise
 
         self.logger.debug(
-            "Waiting for server to become accessible: %s" % self.url)
+            "Waiting for WebDriver to become accessible: %s" % self.url)
         try:
             wait_for_service((self.host, self.port))
-        except:
+        except Exception:
             self.logger.error(
-                "WebDriver HTTP server was not accessible "
+                "WebDriver was not accessible "
                 "within the timeout:\n%s" % traceback.format_exc())
             raise
 
@@ -125,8 +126,6 @@
 
 
 class ChromeDriverServer(WebDriverServer):
-    default_base_path = "/"
-
     def __init__(self, logger, binary="chromedriver", port=None,
                  base_path="", args=None):
         WebDriverServer.__init__(
@@ -138,27 +137,7 @@
                 cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args
 
 class EdgeDriverServer(WebDriverServer):
-    default_base_path = "/"
-
     def __init__(self, logger, binary="microsoftwebdriver.exe", port=None,
-                 base_path="", args=None):
-        WebDriverServer.__init__(
-            self, logger, binary, port=port, base_path=base_path, args=args)
-
-    def make_command(self):
-        return [self.binary,
-                cmd_arg("port", str(self.port)),
-                cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args
-
-class OperaDriverServer(ChromeDriverServer):
-    def __init__(self, logger, binary="operadriver", port=None,
-                 base_path="", args=None):
-        ChromeDriverServer.__init__(
-            self, logger, binary, port=port, base_path=base_path, args=args)
-
-
-class EdgeDriverServer(WebDriverServer):
-    def __init__(self, logger, binary="MicrosoftWebDriver.exe", port=None,
                  base_path="", host="localhost", args=None):
         WebDriverServer.__init__(
             self, logger, binary, host=host, port=port, args=args)
@@ -167,6 +146,12 @@
         return [self.binary,
                 "--port=%s" % str(self.port)] + self._args
 
+class OperaDriverServer(ChromeDriverServer):
+    def __init__(self, logger, binary="operadriver", port=None,
+                 base_path="", args=None):
+        ChromeDriverServer.__init__(
+            self, logger, binary, port=port, base_path=base_path, args=args)
+
 
 class InternetExplorerDriverServer(WebDriverServer):
     def __init__(self, logger, binary="IEDriverServer.exe", port=None,
@@ -194,11 +179,22 @@
                 "--port", str(self.port)] + self._args
 
 
+class SafariDriverServer(WebDriverServer):
+    def __init__(self, logger, binary="safaridriver", port=None, args=None):
+        WebDriverServer.__init__(
+            self, logger, binary, port=port, args=args)
+
+    def make_command(self):
+        return [self.binary,
+                "--port=%s" % str(self.port)] + self._args
+
+
 class ServoDriverServer(WebDriverServer):
-    def __init__(self, logger, binary="servo", binary_args=None, host="127.0.0.1", port=None):
+    def __init__(self, logger, binary="servo", binary_args=None, host="127.0.0.1",
+                 port=None, args=None):
         env = os.environ.copy()
         env["RUST_BACKTRACE"] = "1"
-        WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env)
+        WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env, args=args)
         self.binary_args = binary_args
 
     def make_command(self):
@@ -211,6 +207,14 @@
         return command
 
 
+class WebKitDriverServer(WebDriverServer):
+    def __init__(self, logger, binary=None, port=None, args=None):
+        WebDriverServer.__init__(self, logger, binary, port=port, args=args)
+
+    def make_command(self):
+        return [self.binary, "--port=%s" % str(self.port)] + self._args
+
+
 def cmd_arg(name, value=None):
     prefix = "-" if platform.system() == "Windows" else "--"
     rv = prefix + name
diff --git a/tools/wptrunner/wptrunner/wptcommandline.py b/tools/wptrunner/wptrunner/wptcommandline.py
index b232462..c4a0193 100644
--- a/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/tools/wptrunner/wptrunner/wptcommandline.py
@@ -4,6 +4,7 @@
 import sys
 from collections import OrderedDict
 from distutils.spawn import find_executable
+from datetime import datetime, timedelta
 
 import config
 import wpttest
@@ -28,7 +29,7 @@
     if value_func is None:
         value_func = lambda x: x is not None
 
-    if not name in kwargs or not value_func(kwargs[name]):
+    if name not in kwargs or not value_func(kwargs[name]):
         print >> sys.stderr, "Missing required argument %s" % name
         sys.exit(1)
 
@@ -65,6 +66,10 @@
 
     parser.add_argument("--no-capture-stdio", action="store_true", default=False,
                         help="Don't capture stdio and write to logging")
+    parser.add_argument("--no-fail-on-unexpected", action="store_false",
+                        default=True,
+                        dest="fail_on_unexpected",
+                        help="Exit with status code 0 when test expectations are violated")
 
     mode_group = parser.add_argument_group("Mode")
     mode_group.add_argument("--list-test-groups", action="store_true",
@@ -82,6 +87,36 @@
     mode_group.add_argument("--verify-log-full", action="store_true",
                             default=False,
                             help="Output per-iteration test results when running verify")
+    mode_group.add_argument("--verify-repeat-loop", action="store",
+                            default=10,
+                            help="Number of iterations for a run that reloads each test without restart.",
+                            type=int)
+    mode_group.add_argument("--verify-repeat-restart", action="store",
+                            default=5,
+                            help="Number of iterations, for a run that restarts the runner between each iteration",
+                            type=int)
+    chaos_mode_group = mode_group.add_mutually_exclusive_group()
+    chaos_mode_group.add_argument("--verify-no-chaos-mode", action="store_false",
+                                  default=True,
+                                  dest="verify_chaos_mode",
+                                  help="Disable chaos mode when running on Firefox")
+    chaos_mode_group.add_argument("--verify-chaos-mode", action="store_true",
+                                  default=True,
+                                  dest="verify_chaos_mode",
+                                  help="Enable chaos mode when running on Firefox")
+    mode_group.add_argument("--verify-max-time", action="store",
+                            default=None,
+                            help="The maximum number of minutes for the job to run",
+                            type=lambda x: timedelta(minutes=float(x)))
+    output_results_group = mode_group.add_mutually_exclusive_group()
+    output_results_group.add_argument("--verify-no-output-results", action="store_false",
+                                      dest="verify_output_results",
+                                      default=True,
+                                      help="Prints individuals test results and messages")
+    output_results_group.add_argument("--verify-output-results", action="store_true",
+                                      dest="verify_output_results",
+                                      default=True,
+                                      help="Disable printing individuals test results and messages")
 
     test_selection_group = parser.add_argument_group("Test Selection")
     test_selection_group.add_argument("--test-types", action="store",
@@ -97,7 +132,8 @@
     test_selection_group.add_argument("--skip-timeout", action="store_true",
                                       help="Skip tests that are expected to time out")
     test_selection_group.add_argument("--tag", action="append", dest="tags",
-                                      help="Labels applied to tests to include in the run. Labels starting dir: are equivalent to top-level directories.")
+                                      help="Labels applied to tests to include in the run. "
+                                           "Labels starting dir: are equivalent to top-level directories.")
 
     debugging_group = parser.add_argument_group("Debugging")
     debugging_group.add_argument('--debugger', const="__default__", nargs="?",
@@ -144,6 +180,8 @@
                               help="Path to root directory containing test metadata"),
     config_group.add_argument("--tests", action="store", type=abs_path, dest="tests_root",
                               help="Path to root directory containing test files"),
+    config_group.add_argument("--manifest", action="store", type=abs_path, dest="manifest_path",
+                              help="Path to test manifest (default is ${metadata_root}/MANIFEST.json)")
     config_group.add_argument("--run-info", action="store", type=abs_path,
                               help="Path to directory containing extra json files to add to run info")
     config_group.add_argument("--product", action="store", choices=product_choices,
@@ -244,6 +282,10 @@
                              dest="sauce_connect_binary",
                              help="Path to Sauce Connect binary")
 
+    webkit_group = parser.add_argument_group("WebKit-specific")
+    webkit_group.add_argument("--webkit-port", dest="webkit_port",
+                             help="WebKit port")
+
     parser.add_argument("test_list", nargs="*",
                         help="List of URLs for tests to run, or paths including tests to run. "
                              "(equivalent to --include)")
@@ -296,9 +338,16 @@
             kwargs["test_paths"]["/"] = {}
         kwargs["test_paths"]["/"]["metadata_path"] = kwargs["metadata_root"]
 
+    if kwargs.get("manifest_path"):
+        if "/" not in kwargs["test_paths"]:
+            kwargs["test_paths"]["/"] = {}
+        kwargs["test_paths"]["/"]["manifest_path"] = kwargs["manifest_path"]
+
     kwargs["suite_name"] = kwargs["config"].get("web-platform-tests", {}).get("name", "web-platform-tests")
 
 
+    check_paths(kwargs)
+
 def get_test_paths(config):
     # Set up test_paths
     test_paths = OrderedDict()
@@ -309,7 +358,10 @@
             url_base = manifest_opts.get("url_base", "/")
             test_paths[url_base] = {
                 "tests_path": manifest_opts.get_path("tests"),
-                "metadata_path": manifest_opts.get_path("metadata")}
+                "metadata_path": manifest_opts.get_path("metadata"),
+            }
+            if "manifest" in manifest_opts:
+                test_paths[url_base]["manifest_path"] = manifest_opts.get_path("manifest")
 
     return test_paths
 
@@ -325,17 +377,23 @@
         return None
 
 
-def check_args(kwargs):
-    set_from_config(kwargs)
-
+def check_paths(kwargs):
     for test_paths in kwargs["test_paths"].itervalues():
         if not ("tests_path" in test_paths and
                 "metadata_path" in test_paths):
             print "Fatal: must specify both a test path and metadata path"
             sys.exit(1)
+        if "manifest_path" not in test_paths:
+            test_paths["manifest_path"] = os.path.join(test_paths["metadata_path"],
+                                                       "MANIFEST.json")
         for key, path in test_paths.iteritems():
             name = key.split("_", 1)[0]
 
+            if name == "manifest":
+                # For the manifest we can create it later, so just check the path
+                # actually exists
+                path = os.path.dirname(path)
+
             if not os.path.exists(path):
                 print "Fatal: %s path %s does not exist" % (name, path)
                 sys.exit(1)
@@ -344,6 +402,10 @@
                 print "Fatal: %s path %s is not a directory" % (name, path)
                 sys.exit(1)
 
+
+def check_args(kwargs):
+    set_from_config(kwargs)
+
     if kwargs["product"] is None:
         kwargs["product"] = "firefox"
 
@@ -466,6 +528,8 @@
                         help="Path to the folder containing test metadata"),
     parser.add_argument("--tests", action="store", type=abs_path, dest="tests_root",
                         help="Path to web-platform-tests"),
+    parser.add_argument("--manifest", action="store", type=abs_path, dest="manifest_path",
+                        help="Path to test manifest (default is ${metadata_root}/MANIFEST.json)")
     parser.add_argument("--sync-path", action="store", type=abs_path,
                         help="Path to store git checkout of web-platform-tests during update"),
     parser.add_argument("--remote_url", action="store",
@@ -479,7 +543,8 @@
                         help="Don't create a VCS commit containing the changes.")
     parser.add_argument("--sync", dest="sync", action="store_true", default=False,
                         help="Sync the tests with the latest from upstream (implies --patch)")
-    parser.add_argument("--ignore-existing", action="store_true", help="When updating test results only consider results from the logfiles provided, not existing expectations.")
+    parser.add_argument("--ignore-existing", action="store_true",
+                        help="When updating test results only consider results from the logfiles provided, not existing expectations.")
     parser.add_argument("--stability", nargs="?", action="store", const="unstable", default=None,
         help=("Reason for disabling tests. When updating test results, disable tests that have "
               "inconsistent results across many runs with the given reason."))
@@ -489,6 +554,8 @@
                         help="List of glob-style paths to exclude when syncing tests")
     parser.add_argument("--include", action="store", nargs="*",
                         help="List of glob-style paths to include which would otherwise be excluded when syncing tests")
+    parser.add_argument("--extra-property", action="append", default=[],
+                        help="Extra property from run_info.json to use in metadata update")
     # Should make this required iff run=logfile
     parser.add_argument("run_log", nargs="*", type=abs_path,
                         help="Log file from run of tests")
diff --git a/tools/wptrunner/wptrunner/wptlogging.py b/tools/wptrunner/wptrunner/wptlogging.py
index 4d32061..26d174c 100644
--- a/tools/wptrunner/wptrunner/wptlogging.py
+++ b/tools/wptrunner/wptrunner/wptlogging.py
@@ -1,13 +1,20 @@
 import logging
 import sys
 import threading
+from Queue import Empty
 from StringIO import StringIO
 from multiprocessing import Queue
 
-from mozlog import commandline, stdadapter
+from mozlog import commandline, stdadapter, set_default_logger
+from mozlog.structuredlog import StructuredLogger
 
 def setup(args, defaults):
-    logger = commandline.setup_logging("web-platform-tests", args, defaults)
+    logger = args.pop('log', None)
+    if logger:
+        set_default_logger(logger)
+        StructuredLogger._logger_states["web-platform-tests"] = logger._state
+    else:
+        logger = commandline.setup_logging("web-platform-tests", args, defaults)
     setup_stdlib_logger()
 
     for name in args.keys():
@@ -45,7 +52,6 @@
         return self.inner(data)
 
 
-
 class LogThread(threading.Thread):
     def __init__(self, queue, logger, level):
         self.queue = queue
@@ -120,5 +126,10 @@
                 self.logging_queue.put(None)
                 if self.logging_thread is not None:
                     self.logging_thread.join(10)
+                while not self.logging_queue.empty():
+                    try:
+                        self.logger.warning("Dropping log message: %r", self.logging_queue.get())
+                    except Exception:
+                        pass
                 self.logging_queue.close()
                 self.logger.info("queue closed")
diff --git a/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py b/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py
index 6db2cbb..6908ea4 100644
--- a/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py
+++ b/tools/wptrunner/wptrunner/wptmanifest/tests/test_serializer.py
@@ -211,15 +211,15 @@
 """)
 
     def test_atom_1(self):
-            self.compare(r"""key: @True
+        self.compare(r"""key: @True
 """)
 
     def test_atom_2(self):
-            self.compare(r"""key: @False
+        self.compare(r"""key: @False
 """)
 
     def test_atom_3(self):
-            self.compare(r"""key: @Reset
+        self.compare(r"""key: @Reset
 """)
 
     def test_atom_4(self):
diff --git a/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py b/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py
index 88176c5..a64ce0c 100644
--- a/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py
+++ b/tools/wptrunner/wptrunner/wptmanifest/tests/test_tokenizer.py
@@ -145,8 +145,7 @@
                       (token_types.string, r"\nb")])
 
     def test_list_0(self):
-        self.compare(
-"""
+        self.compare("""
 key: []""",
             [(token_types.string, "key"),
              (token_types.separator, ":"),
@@ -154,8 +153,7 @@
              (token_types.list_end, "]")])
 
     def test_list_1(self):
-        self.compare(
-"""
+        self.compare("""
 key: [a, "b"]""",
             [(token_types.string, "key"),
              (token_types.separator, ":"),
@@ -165,8 +163,7 @@
              (token_types.list_end, "]")])
 
     def test_list_2(self):
-        self.compare(
-"""
+        self.compare("""
 key: [a,
       b]""",
             [(token_types.string, "key"),
@@ -177,8 +174,7 @@
              (token_types.list_end, "]")])
 
     def test_list_3(self):
-        self.compare(
-"""
+        self.compare("""
 key: [a, #b]
       c]""",
             [(token_types.string, "key"),
@@ -199,18 +195,16 @@
             c]""")
 
     def test_list_6(self):
-        self.compare(
-"""key: [a , b]""",
-        [(token_types.string, "key"),
-         (token_types.separator, ":"),
-         (token_types.list_start, "["),
-         (token_types.string, "a"),
-         (token_types.string, "b"),
-         (token_types.list_end, "]")])
+        self.compare("""key: [a , b]""",
+            [(token_types.string, "key"),
+             (token_types.separator, ":"),
+             (token_types.list_start, "["),
+             (token_types.string, "a"),
+             (token_types.string, "b"),
+             (token_types.list_end, "]")])
 
     def test_expr_0(self):
-        self.compare(
-"""
+        self.compare("""
 key:
   if cond == 1: value""",
             [(token_types.string, "key"),
@@ -224,8 +218,7 @@
              (token_types.string, "value")])
 
     def test_expr_1(self):
-        self.compare(
-"""
+        self.compare("""
 key:
   if cond == 1: value1
   value2""",
@@ -241,8 +234,7 @@
              (token_types.string, "value2")])
 
     def test_expr_2(self):
-        self.compare(
-"""
+        self.compare("""
 key:
   if cond=="1": value""",
             [(token_types.string, "key"),
@@ -256,8 +248,7 @@
              (token_types.string, "value")])
 
     def test_expr_3(self):
-        self.compare(
-"""
+        self.compare("""
 key:
   if cond==1.1: value""",
             [(token_types.string, "key"),
@@ -271,8 +262,7 @@
              (token_types.string, "value")])
 
     def test_expr_4(self):
-        self.compare(
-            """
+        self.compare("""
 key:
   if cond==1.1 and cond2 == "a": value""",
             [(token_types.string, "key"),
@@ -290,8 +280,7 @@
              (token_types.string, "value")])
 
     def test_expr_5(self):
-        self.compare(
-"""
+        self.compare("""
 key:
   if (cond==1.1 ): value""",
             [(token_types.string, "key"),
@@ -307,8 +296,7 @@
              (token_types.string, "value")])
 
     def test_expr_6(self):
-        self.compare(
-"""
+        self.compare("""
 key:
   if "\\ttest": value""",
             [(token_types.string, "key"),
@@ -322,27 +310,26 @@
     def test_expr_7(self):
         with self.assertRaises(parser.ParseError):
             self.tokenize(
-"""
+                """
 key:
   if 1A: value""")
 
     def test_expr_8(self):
         with self.assertRaises(parser.ParseError):
             self.tokenize(
-"""
+                """
 key:
   if 1a: value""")
 
     def test_expr_9(self):
         with self.assertRaises(parser.ParseError):
             self.tokenize(
-"""
+                """
 key:
   if 1.1.1: value""")
 
     def test_expr_10(self):
-        self.compare(
-"""
+        self.compare("""
 key:
   if 1.: value""",
             [(token_types.string, "key"),
diff --git a/tools/wptrunner/wptrunner/wptrunner.py b/tools/wptrunner/wptrunner/wptrunner.py
index be81eb9..bd2b3ad 100644
--- a/tools/wptrunner/wptrunner/wptrunner.py
+++ b/tools/wptrunner/wptrunner/wptrunner.py
@@ -43,7 +43,9 @@
     if run_info_extras is None:
         run_info_extras = {}
 
-    run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=debug,
+    run_info = wpttest.get_run_info(kwargs["run_info"], product,
+                                    browser_version=kwargs.get("browser_version"),
+                                    debug=debug,
                                     extras=run_info_extras)
 
     test_manifests = testloader.ManifestLoader(test_paths, force_manifest_update=kwargs["manifest_update"],
@@ -153,7 +155,9 @@
             ))
 
         if "test_loader" in kwargs:
-            run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=None,
+            run_info = wpttest.get_run_info(kwargs["run_info"], product,
+                                            browser_version=kwargs.get("browser_version"),
+                                            debug=None,
                                             extras=run_info_extras(**kwargs))
             test_loader = kwargs["test_loader"]
         else:
@@ -173,6 +177,7 @@
 
         logger.info("Using %i client processes" % kwargs["processes"])
 
+        test_total = 0
         unexpected_total = 0
 
         kwargs["pause_after_test"] = get_pause_after_test(test_loader, **kwargs)
@@ -200,8 +205,9 @@
                 elif repeat > 1:
                     logger.info("Repetition %i / %i" % (repeat_count, repeat))
 
+                test_count = 0
                 unexpected_count = 0
-                logger.suite_start(test_loader.test_ids, run_info)
+                logger.suite_start(test_loader.test_ids, name='web-platform-test', run_info=run_info)
                 for test_type in kwargs["test_types"]:
                     logger.info("Running %s tests" % test_type)
 
@@ -218,11 +224,12 @@
                     browser_kwargs = get_browser_kwargs(test_type,
                                                         run_info,
                                                         ssl_env=ssl_env,
+                                                        config=test_environment.config,
                                                         **kwargs)
 
                     executor_cls = executor_classes.get(test_type)
                     executor_kwargs = get_executor_kwargs(test_type,
-                                                          test_environment.external_config,
+                                                          test_environment.config,
                                                           test_environment.cache_manager,
                                                           run_info,
                                                           **kwargs)
@@ -242,6 +249,12 @@
                             if test.testdriver and not executor_cls.supports_testdriver:
                                 logger.test_start(test.id)
                                 logger.test_end(test.id, status="SKIP")
+                            elif test.jsshell and not executor_cls.supports_jsshell:
+                                # We expect that tests for JavaScript shells
+                                # will not be run along with tests that run in
+                                # a full web browser, so we silently skip them
+                                # here.
+                                pass
                             else:
                                 run_tests["testharness"].append(test)
                     else:
@@ -266,20 +279,36 @@
                             logger.critical("Main thread got signal")
                             manager_group.stop()
                             raise
+                    test_count += manager_group.test_count()
                     unexpected_count += manager_group.unexpected_count()
 
+                test_total += test_count
                 unexpected_total += unexpected_count
                 logger.info("Got %i unexpected results" % unexpected_count)
                 if repeat_until_unexpected and unexpected_total > 0:
                     break
                 logger.suite_end()
+
+    if test_total == 0:
+        logger.error("No tests ran")
+        return False
+
+    if unexpected_total and not kwargs["fail_on_unexpected"]:
+        logger.info("Tolerating %s unexpected results" % unexpected_total)
+        return True
+
     return unexpected_total == 0
 
 
 def check_stability(**kwargs):
     import stability
-    return stability.check_stability(logger, **kwargs)
-
+    return stability.check_stability(logger,
+                                     max_time=kwargs['verify_max_time'],
+                                     chaos_mode=kwargs['verify_chaos_mode'],
+                                     repeat_loop=kwargs['verify_repeat_loop'],
+                                     repeat_restart=kwargs['verify_repeat_restart'],
+                                     output_results=kwargs['verify_output_results'],
+                                     **kwargs)
 
 def start(**kwargs):
     if kwargs["list_test_groups"]:
@@ -307,7 +336,8 @@
         return start(**kwargs)
     except Exception:
         if kwargs["pdb"]:
-            import pdb, traceback
+            import pdb
+            import traceback
             print traceback.format_exc()
             pdb.post_mortem()
         else:
diff --git a/tools/wptrunner/wptrunner/wpttest.py b/tools/wptrunner/wptrunner/wpttest.py
index d283e85..9e4290d 100644
--- a/tools/wptrunner/wptrunner/wpttest.py
+++ b/tools/wptrunner/wptrunner/wpttest.py
@@ -1,4 +1,5 @@
 import os
+import subprocess
 from collections import defaultdict
 
 from wptmanifest.parser import atoms
@@ -36,7 +37,7 @@
 
 class TestharnessResult(Result):
     default_expected = "OK"
-    statuses = set(["OK", "ERROR", "TIMEOUT", "EXTERNAL-TIMEOUT", "CRASH"])
+    statuses = set(["OK", "ERROR", "INTERNAL-ERROR", "TIMEOUT", "EXTERNAL-TIMEOUT", "CRASH"])
 
 
 class TestharnessSubtestResult(SubtestResult):
@@ -46,12 +47,13 @@
 
 class ReftestResult(Result):
     default_expected = "PASS"
-    statuses = set(["PASS", "FAIL", "ERROR", "TIMEOUT", "EXTERNAL-TIMEOUT", "CRASH"])
+    statuses = set(["PASS", "FAIL", "ERROR", "INTERNAL-ERROR", "TIMEOUT", "EXTERNAL-TIMEOUT",
+                    "CRASH"])
 
 
 class WdspecResult(Result):
     default_expected = "OK"
-    statuses = set(["OK", "ERROR", "TIMEOUT", "EXTERNAL-TIMEOUT", "CRASH"])
+    statuses = set(["OK", "ERROR", "INTERNAL-ERROR", "TIMEOUT", "EXTERNAL-TIMEOUT", "CRASH"])
 
 
 class WdspecSubtestResult(SubtestResult):
@@ -64,11 +66,20 @@
 
 
 class RunInfo(dict):
-    def __init__(self, metadata_root, product, debug, extras=None):
+    def __init__(self, metadata_root, product, debug, browser_version=None, extras=None):
         import mozinfo
-
         self._update_mozinfo(metadata_root)
         self.update(mozinfo.info)
+
+        from update.tree import GitTree
+        try:
+            # GitTree.__init__ throws if we are not in a git tree.
+            rev = GitTree(log_error=False).rev
+        except (OSError, subprocess.CalledProcessError):
+            rev = None
+        if rev:
+            self["revision"] = rev
+
         self["product"] = product
         if debug is not None:
             self["debug"] = debug
@@ -81,6 +92,8 @@
             self["stylo"] = True
         if "STYLO_FORCE_DISABLED" in os.environ:
             self["stylo"] = False
+        if browser_version:
+            self["browser_version"] = browser_version
         if extras is not None:
             self.update(extras)
 
@@ -243,17 +256,20 @@
     test_type = "testharness"
 
     def __init__(self, tests_root, url, inherit_metadata, test_metadata,
-                 timeout=None, path=None, protocol="http", testdriver=False):
+                 timeout=None, path=None, protocol="http", testdriver=False,
+                 jsshell=False):
         Test.__init__(self, tests_root, url, inherit_metadata, test_metadata, timeout,
                       path, protocol)
 
         self.testdriver = testdriver
+        self.jsshell = jsshell
 
     @classmethod
     def from_manifest(cls, manifest_item, inherit_metadata, test_metadata):
         timeout = cls.long_timeout if manifest_item.timeout == "long" else cls.default_timeout
         protocol = "https" if hasattr(manifest_item, "https") and manifest_item.https else "http"
         testdriver = manifest_item.testdriver if hasattr(manifest_item, "testdriver") else False
+        jsshell = manifest_item.jsshell if hasattr(manifest_item, "jsshell") else False
         return cls(manifest_item.source_file.tests_root,
                    manifest_item.url,
                    inherit_metadata,
@@ -261,7 +277,8 @@
                    timeout=timeout,
                    path=manifest_item.source_file.path,
                    protocol=protocol,
-                   testdriver=testdriver)
+                   testdriver=testdriver,
+                   jsshell=jsshell)
 
     @property
     def id(self):
@@ -356,7 +373,7 @@
         return node
 
     def update_metadata(self, metadata):
-        if not "url_count" in metadata:
+        if "url_count" not in metadata:
             metadata["url_count"] = defaultdict(int)
         for reference, _ in self.references:
             # We assume a naive implementation in which a url with multiple
diff --git a/tools/wptserve/docs/pipes.rst b/tools/wptserve/docs/pipes.rst
index c606140..8faeee4 100644
--- a/tools/wptserve/docs/pipes.rst
+++ b/tools/wptserve/docs/pipes.rst
@@ -11,6 +11,10 @@
 This would serve bytes 1 to 199, inclusive, of foo.txt with the HTTP status
 code 404.
 
+.. note::
+   If you write directly to the response socket using ResponseWriter,
+   or when using the asis handler, only the trickle pipe will affect the response.
+
 There are several built-in pipe functions, and it is possible to add
 more using the `@pipe` decorator on a function, if required.
 
diff --git a/tools/wptserve/docs/stash.rst b/tools/wptserve/docs/stash.rst
index 821c2d3..49846f2 100644
--- a/tools/wptserve/docs/stash.rst
+++ b/tools/wptserve/docs/stash.rst
@@ -3,7 +3,7 @@
 
 Object for storing cross-request state. This is unusual in that keys
 must be UUIDs, in order to prevent different clients setting the same
-key, and values are write-once, read-once to minimise the chances of
+key, and values are write-once, read-once to minimize the chances of
 state persisting indefinitely. The stash defines two operations;
 `put`, to add state and `take` to remove state. Furthermore, the view
 of the stash is path-specific; by default a request will only see the
diff --git a/tools/wptserve/setup.py b/tools/wptserve/setup.py
index c71f60a..194c337 100644
--- a/tools/wptserve/setup.py
+++ b/tools/wptserve/setup.py
@@ -1,6 +1,6 @@
 from setuptools import setup
 
-PACKAGE_VERSION = '1.4.0'
+PACKAGE_VERSION = '2.0'
 deps = ["six>=1.8"]
 
 setup(name='wptserve',
@@ -16,7 +16,7 @@
       author_email='james@hoppipolla.co.uk',
       url='http://wptserve.readthedocs.org/',
       license='BSD',
-      packages=['wptserve'],
+      packages=['wptserve', 'wptserve.sslutils'],
       include_package_data=True,
       zip_safe=False,
       install_requires=deps
diff --git a/tools/wptserve/tests/functional/docroot/sub_file_hash.sub.txt b/tools/wptserve/tests/functional/docroot/sub_file_hash.sub.txt
new file mode 100644
index 0000000..369ac8a
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/sub_file_hash.sub.txt
@@ -0,0 +1,6 @@
+md5: {{file_hash(md5, sub_file_hash_subject.txt)}}
+sha1: {{file_hash(sha1, sub_file_hash_subject.txt)}}
+sha224: {{file_hash(sha224, sub_file_hash_subject.txt)}}
+sha256: {{file_hash(sha256, sub_file_hash_subject.txt)}}
+sha384: {{file_hash(sha384, sub_file_hash_subject.txt)}}
+sha512: {{file_hash(sha512, sub_file_hash_subject.txt)}}
diff --git a/tools/wptserve/tests/functional/docroot/sub_file_hash_subject.txt b/tools/wptserve/tests/functional/docroot/sub_file_hash_subject.txt
new file mode 100644
index 0000000..d567d28
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/sub_file_hash_subject.txt
@@ -0,0 +1,2 @@
+This file is used to verify expected behavior of the `file_hash` "sub"
+function.
diff --git a/tools/wptserve/tests/functional/docroot/sub_file_hash_unrecognized.sub.txt b/tools/wptserve/tests/functional/docroot/sub_file_hash_unrecognized.sub.txt
new file mode 100644
index 0000000..5f1281d
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/sub_file_hash_unrecognized.sub.txt
@@ -0,0 +1 @@
+{{file_hash(sha007, sub_file_hash_subject.txt)}}
diff --git a/tools/wptserve/tests/functional/docroot/sub_location.sub.txt b/tools/wptserve/tests/functional/docroot/sub_location.sub.txt
new file mode 100644
index 0000000..6129abd
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/sub_location.sub.txt
@@ -0,0 +1,8 @@
+host: {{location[host]}}
+hostname: {{location[hostname]}}
+path: {{location[path]}}
+pathname: {{location[pathname]}}
+port: {{location[port]}}
+query: {{location[query]}}
+scheme: {{location[scheme]}}
+server: {{location[server]}}
diff --git a/tools/wptserve/tests/functional/docroot/sub_url_base.sub.txt b/tools/wptserve/tests/functional/docroot/sub_url_base.sub.txt
new file mode 100644
index 0000000..889cd07
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/sub_url_base.sub.txt
@@ -0,0 +1 @@
+Before {{url_base}} After
diff --git a/tools/wptserve/tests/functional/docroot/sub_uuid.sub.txt b/tools/wptserve/tests/functional/docroot/sub_uuid.sub.txt
new file mode 100644
index 0000000..fd968fe
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/sub_uuid.sub.txt
@@ -0,0 +1 @@
+Before {{uuid()}} After
diff --git a/tools/wptserve/tests/functional/docroot/sub_var.sub.txt b/tools/wptserve/tests/functional/docroot/sub_var.sub.txt
new file mode 100644
index 0000000..9492ec1
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/sub_var.sub.txt
@@ -0,0 +1 @@
+{{$first:host}} {{$second:ports[http][0]}} A {{$second}} B {{$first}} C
diff --git a/tools/wptserve/tests/functional/docroot/subdir/example_module.py b/tools/wptserve/tests/functional/docroot/subdir/example_module.py
new file mode 100644
index 0000000..b8e5c35
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/subdir/example_module.py
@@ -0,0 +1,2 @@
+def module_function():
+    return [("Content-Type", "text/plain")], "PASS"
diff --git a/tools/wptserve/tests/functional/docroot/subdir/import_handler.py b/tools/wptserve/tests/functional/docroot/subdir/import_handler.py
new file mode 100644
index 0000000..3b42b29
--- /dev/null
+++ b/tools/wptserve/tests/functional/docroot/subdir/import_handler.py
@@ -0,0 +1,5 @@
+import example_module
+
+
+def main(request, response):
+    return example_module.module_function()
diff --git a/tools/wptserve/tests/functional/test_handlers.py b/tools/wptserve/tests/functional/test_handlers.py
index b9bcb3a..6769bd3 100644
--- a/tools/wptserve/tests/functional/test_handlers.py
+++ b/tools/wptserve/tests/functional/test_handlers.py
@@ -1,5 +1,6 @@
 import json
 import os
+import sys
 import unittest
 import uuid
 
@@ -228,6 +229,7 @@
         self.assertEqual("test-value", resp.info()["test-header"])
         self.assertEqual({"data": "test data"}, json.load(resp))
 
+
 class TestPythonHandler(TestUsingServer):
     def test_string(self):
         resp = self.request("/test_string.py")
@@ -250,6 +252,17 @@
         self.assertEqual("PASS", resp.info()["X-Test"])
         self.assertEqual("PASS", resp.read())
 
+    def test_import(self):
+        dir_name = os.path.join(doc_root, "subdir")
+        assert dir_name not in sys.path
+        assert "test_module" not in sys.modules
+        resp = self.request("/subdir/import_handler.py")
+        assert dir_name not in sys.path
+        assert "test_module" not in sys.modules
+        self.assertEqual(200, resp.getcode())
+        self.assertEqual("text/plain", resp.info()["Content-Type"])
+        self.assertEqual("PASS", resp.read())
+
     def test_no_main(self):
         with pytest.raises(HTTPError) as cm:
             self.request("/no_main.py")
diff --git a/tools/wptserve/tests/functional/test_pipes.py b/tools/wptserve/tests/functional/test_pipes.py
index bd38f2e..069bd76 100644
--- a/tools/wptserve/tests/functional/test_pipes.py
+++ b/tools/wptserve/tests/functional/test_pipes.py
@@ -1,9 +1,12 @@
 import os
 import unittest
+import urllib2
 import time
+import json
 
 import pytest
 
+wptserve = pytest.importorskip("wptserve")
 from .base import TestUsingServer, doc_root
 
 
@@ -56,16 +59,59 @@
         expected = "localhost localhost %i" % self.server.port
         self.assertEqual(resp.read().rstrip(), expected)
 
+    def test_sub_file_hash(self):
+        resp = self.request("/sub_file_hash.sub.txt")
+        expected = """
+md5: JmI1W8fMHfSfCarYOSxJcw==
+sha1: nqpWqEw4IW8NjD6R375gtrQvtTo=
+sha224: RqQ6fMmta6n9TuA/vgTZK2EqmidqnrwBAmQLRQ==
+sha256: G6Ljg1uPejQxqFmvFOcV/loqnjPTW5GSOePOfM/u0jw=
+sha384: lkXHChh1BXHN5nT5BYhi1x67E1CyYbPKRKoF2LTm5GivuEFpVVYtvEBHtPr74N9E
+sha512: r8eLGRTc7ZznZkFjeVLyo6/FyQdra9qmlYCwKKxm3kfQAswRS9+3HsYk3thLUhcFmmWhK4dXaICz
+JwGFonfXwg=="""
+        self.assertEqual(resp.read().rstrip(), expected.strip())
+
+    def test_sub_file_hash_unrecognized(self):
+        with self.assertRaises(urllib2.HTTPError):
+            self.request("/sub_file_hash_unrecognized.sub.txt")
+
     def test_sub_headers(self):
         resp = self.request("/sub_headers.txt", query="pipe=sub", headers={"X-Test": "PASS"})
         expected = "PASS"
         self.assertEqual(resp.read().rstrip(), expected)
 
+    def test_sub_location(self):
+        resp = self.request("/sub_location.sub.txt?query_string")
+        expected = """
+host: localhost:{0}
+hostname: localhost
+path: /sub_location.sub.txt
+pathname: /sub_location.sub.txt
+port: {0}
+query: ?query_string
+scheme: http
+server: http://localhost:{0}""".format(self.server.port)
+        self.assertEqual(resp.read().rstrip(), expected.strip())
+
     def test_sub_params(self):
         resp = self.request("/sub_params.txt", query="test=PASS&pipe=sub")
         expected = "PASS"
         self.assertEqual(resp.read().rstrip(), expected)
 
+    def test_sub_url_base(self):
+        resp = self.request("/sub_url_base.sub.txt")
+        self.assertEqual(resp.read().rstrip(), "Before / After")
+
+    def test_sub_uuid(self):
+        resp = self.request("/sub_uuid.sub.txt")
+        self.assertRegexpMatches(resp.read().rstrip(), r"Before [a-f0-9-]+ After")
+
+    def test_sub_var(self):
+        resp = self.request("/sub_var.sub.txt")
+        port = self.server.port
+        expected = "localhost %s A %s B localhost C" % (port, port)
+        self.assertEqual(resp.read().rstrip(), expected)
+
 class TestTrickle(TestUsingServer):
     def test_trickle(self):
         #Actually testing that the response trickles in is not that easy
@@ -82,5 +128,72 @@
         self.assertEqual(resp.info()["Pragma"], "no-cache")
         self.assertEqual(resp.info()["Expires"], "0")
 
+class TestPipesWithVariousHandlers(TestUsingServer):
+    def test_with_python_file_handler(self):
+        resp = self.request("/test_string.py", query="pipe=slice(null,2)")
+        self.assertEqual(resp.read(), "PA")
+
+    def test_with_python_func_handler(self):
+        @wptserve.handlers.handler
+        def handler(request, response):
+            return "PASS"
+        route = ("GET", "/test/test_pipes_1/", handler)
+        self.server.router.register(*route)
+        resp = self.request(route[1], query="pipe=slice(null,2)")
+        self.assertEqual(resp.read(), "PA")
+
+    def test_with_python_func_handler_using_response_writer(self):
+        @wptserve.handlers.handler
+        def handler(request, response):
+            response.writer.write_content("PASS")
+        route = ("GET", "/test/test_pipes_1/", handler)
+        self.server.router.register(*route)
+        resp = self.request(route[1], query="pipe=slice(null,2)")
+        # slice has not been applied to the response, because response.writer was used.
+        self.assertEqual(resp.read(), "PASS")
+
+    def test_header_pipe_with_python_func_using_response_writer(self):
+        @wptserve.handlers.handler
+        def handler(request, response):
+            response.writer.write_content("CONTENT")
+        route = ("GET", "/test/test_pipes_1/", handler)
+        self.server.router.register(*route)
+        resp = self.request(route[1], query="pipe=header(X-TEST,FAIL)")
+        # header pipe was ignored, because response.writer was used.
+        self.assertFalse(resp.info().get("X-TEST"))
+        self.assertEqual(resp.read(), "CONTENT")
+
+    def test_with_json_handler(self):
+        @wptserve.handlers.json_handler
+        def handler(request, response):
+            return json.dumps({'data': 'PASS'})
+        route = ("GET", "/test/test_pipes_2/", handler)
+        self.server.router.register(*route)
+        resp = self.request(route[1], query="pipe=slice(null,2)")
+        self.assertEqual(resp.read(), '"{')
+
+    def test_slice_with_as_is_handler(self):
+        resp = self.request("/test.asis", query="pipe=slice(null,2)")
+        self.assertEqual(202, resp.getcode())
+        self.assertEqual("Giraffe", resp.msg)
+        self.assertEqual("PASS", resp.info()["X-Test"])
+        # slice has not been applied to the response, because response.writer was used.
+        self.assertEqual("Content", resp.read())
+
+    def test_headers_with_as_is_handler(self):
+        resp = self.request("/test.asis", query="pipe=header(X-TEST,FAIL)")
+        self.assertEqual(202, resp.getcode())
+        self.assertEqual("Giraffe", resp.msg)
+        # header pipe was ignored.
+        self.assertEqual("PASS", resp.info()["X-TEST"])
+        self.assertEqual("Content", resp.read())
+
+    def test_trickle_with_as_is_handler(self):
+        t0 = time.time()
+        resp = self.request("/test.asis", query="pipe=trickle(1:d2:5:d1:r2)")
+        t1 = time.time()
+        self.assertTrue('Content' in resp.read())
+        self.assertGreater(6, t1-t0)
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tools/wptserve/tests/test_config.py b/tools/wptserve/tests/test_config.py
new file mode 100644
index 0000000..2c5f3f9
--- /dev/null
+++ b/tools/wptserve/tests/test_config.py
@@ -0,0 +1,393 @@
+import logging
+import pickle
+from logging import handlers
+
+import pytest
+
+config = pytest.importorskip("wptserve.config")
+
+
+def test_renamed_are_renamed():
+    assert len(set(config._renamed_props.viewkeys()) & set(config.Config._default.viewkeys())) == 0
+
+
+def test_renamed_exist():
+    assert set(config._renamed_props.viewvalues()).issubset(set(config.Config._default.viewkeys()))
+
+
+@pytest.mark.parametrize("base, override, expected", [
+    ({"a": 1}, {"a": 2}, {"a": 2}),
+    ({"a": 1}, {"b": 2}, {"a": 1}),
+    ({"a": {"b": 1}}, {"a": {}}, {"a": {"b": 1}}),
+    ({"a": {"b": 1}}, {"a": {"b": 2}}, {"a": {"b": 2}}),
+    ({"a": {"b": 1}}, {"a": {"b": 2, "c": 3}}, {"a": {"b": 2}}),
+    pytest.param({"a": {"b": 1}}, {"a": 2}, {"a": 1}, marks=pytest.mark.xfail),
+    pytest.param({"a": 1}, {"a": {"b": 2}}, {"a": 1}, marks=pytest.mark.xfail),
+])
+def test_merge_dict(base, override, expected):
+    assert expected == config._merge_dict(base, override)
+
+
+def test_logger_created():
+    c = config.Config()
+    assert c.logger is not None
+
+
+def test_logger_preserved():
+    logger = logging.getLogger("test_logger_preserved")
+    logger.setLevel(logging.DEBUG)
+
+    c = config.Config(logger=logger)
+    assert c.logger is logger
+
+
+def test_init_basic_prop():
+    c = config.Config(browser_host="foo.bar")
+    assert c.browser_host == "foo.bar"
+
+
+def test_init_prefixed_prop():
+    c = config.Config(doc_root="/")
+    assert c._doc_root == "/"
+
+
+def test_init_renamed_host():
+    logger = logging.getLogger("test_init_renamed_host")
+    logger.setLevel(logging.DEBUG)
+    handler = handlers.BufferingHandler(100)
+    logger.addHandler(handler)
+
+    c = config.Config(logger=logger, host="foo.bar")
+    assert c.logger is logger
+    assert len(handler.buffer) == 1
+    assert "browser_host" in handler.buffer[0].getMessage()  # check we give the new name in the message
+    assert not hasattr(c, "host")
+    assert c.browser_host == "foo.bar"
+
+
+def test_init_bogus():
+    with pytest.raises(TypeError) as e:
+        config.Config(foo=1, bar=2)
+    assert "foo" in e.value.message
+    assert "bar" in e.value.message
+
+
+def test_getitem():
+    c = config.Config(browser_host="foo.bar")
+    assert c["browser_host"] == "foo.bar"
+
+
+def test_no_setitem():
+    c = config.Config()
+    with pytest.raises(TypeError):
+        c["browser_host"] = "foo.bar"
+
+
+def test_iter():
+    c = config.Config()
+    s = set(iter(c))
+    assert "browser_host" in s
+    assert "host" not in s
+    assert "__getitem__" not in s
+    assert "_browser_host" not in s
+
+
+def test_assignment():
+    c = config.Config()
+    c.browser_host = "foo.bar"
+    assert c.browser_host == "foo.bar"
+
+
+def test_update_basic():
+    c = config.Config()
+    c.update({"browser_host": "foo.bar"})
+    assert c.browser_host == "foo.bar"
+
+
+def test_update_prefixed():
+    c = config.Config()
+    c.update({"doc_root": "/"})
+    assert c._doc_root == "/"
+
+
+def test_update_renamed_host():
+    logger = logging.getLogger("test_update_renamed_host")
+    logger.setLevel(logging.DEBUG)
+    handler = handlers.BufferingHandler(100)
+    logger.addHandler(handler)
+
+    c = config.Config(logger=logger)
+    assert c.logger is logger
+    assert len(handler.buffer) == 0
+
+    c.update({"host": "foo.bar"})
+
+    assert len(handler.buffer) == 1
+    assert "browser_host" in handler.buffer[0].getMessage()  # check we give the new name in the message
+    assert not hasattr(c, "host")
+    assert c.browser_host == "foo.bar"
+
+
+def test_update_bogus():
+    c = config.Config()
+    with pytest.raises(KeyError):
+        c.update({"foobar": 1})
+
+
+def test_ports_auto():
+    c = config.Config(ports={"http": ["auto"]},
+                      ssl={"type": "none"})
+    ports = c.ports
+    assert set(ports.keys()) == {"http"}
+    assert len(ports["http"]) == 1
+    assert isinstance(ports["http"][0], int)
+
+
+def test_ports_auto_mutate():
+    c = config.Config(ports={"http": [1001]},
+                      ssl={"type": "none"})
+    orig_ports = c.ports
+    assert set(orig_ports.keys()) == {"http"}
+    assert orig_ports["http"] == [1001]
+
+    c.ports = {"http": ["auto"]}
+    new_ports = c.ports
+    assert set(new_ports.keys()) == {"http"}
+    assert len(new_ports["http"]) == 1
+    assert isinstance(new_ports["http"][0], int)
+
+
+def test_ports_auto_roundtrip():
+    c = config.Config(ports={"http": ["auto"]},
+                      ssl={"type": "none"})
+    old_ports = c.ports
+    c.ports = old_ports
+    new_ports = c.ports
+    assert old_ports == new_ports
+
+
+def test_ports_idempotent():
+    c = config.Config(ports={"http": ["auto"]},
+                      ssl={"type": "none"})
+    ports_a = c.ports
+    ports_b = c.ports
+    assert ports_a == ports_b
+
+
+def test_ports_explicit():
+    c = config.Config(ports={"http": [1001]},
+                      ssl={"type": "none"})
+    ports = c.ports
+    assert set(ports.keys()) == {"http"}
+    assert ports["http"] == [1001]
+
+
+def test_ports_no_ssl():
+    c = config.Config(ports={"http": [1001], "https": [1002], "ws": [1003], "wss": [1004]},
+                      ssl={"type": "none"})
+    ports = c.ports
+    assert set(ports.keys()) == {"http", "https", "ws", "wss"}
+    assert ports["http"] == [1001]
+    assert ports["https"] == [None]
+    assert ports["ws"] == [1003]
+    assert ports["wss"] == [None]
+
+
+def test_ports_openssl():
+    c = config.Config(ports={"http": [1001], "https": [1002], "ws": [1003], "wss": [1004]},
+                      ssl={"type": "openssl"})
+    ports = c.ports
+    assert set(ports.keys()) == {"http", "https", "ws", "wss"}
+    assert ports["http"] == [1001]
+    assert ports["https"] == [1002]
+    assert ports["ws"] == [1003]
+    assert ports["wss"] == [1004]
+
+
+def test_init_doc_root():
+    c = config.Config(doc_root="/")
+    assert c._doc_root == "/"
+    assert c.doc_root == "/"
+
+
+def test_set_doc_root():
+    c = config.Config()
+    c.doc_root = "/"
+    assert c._doc_root == "/"
+    assert c.doc_root == "/"
+
+
+def test_server_host_from_browser_host():
+    c = config.Config(browser_host="foo.bar")
+    assert c.server_host == "foo.bar"
+
+
+def test_init_server_host():
+    c = config.Config(server_host="foo.bar")
+    assert c.browser_host == "localhost"  # check this hasn't changed
+    assert c._server_host == "foo.bar"
+    assert c.server_host == "foo.bar"
+
+
+def test_set_server_host():
+    c = config.Config()
+    c.server_host = "/"
+    assert c.browser_host == "localhost"  # check this hasn't changed
+    assert c._server_host == "/"
+    assert c.server_host == "/"
+
+
+def test_domains():
+    c = config.Config(browser_host="foo.bar",
+                      alternate_hosts={"alt": "foo2.bar"},
+                      subdomains={"a", "b"},
+                      not_subdomains={"x", "y"})
+    domains = c.domains
+    assert domains == {
+        "": {
+            "": "foo.bar",
+            "a": "a.foo.bar",
+            "b": "b.foo.bar",
+        },
+        "alt": {
+            "": "foo2.bar",
+            "a": "a.foo2.bar",
+            "b": "b.foo2.bar",
+        },
+    }
+
+
+def test_not_domains():
+    c = config.Config(browser_host="foo.bar",
+                      alternate_hosts={"alt": "foo2.bar"},
+                      subdomains={"a", "b"},
+                      not_subdomains={"x", "y"})
+    not_domains = c.not_domains
+    assert not_domains == {
+        "": {
+            "x": "x.foo.bar",
+            "y": "y.foo.bar",
+        },
+        "alt": {
+            "x": "x.foo2.bar",
+            "y": "y.foo2.bar",
+        },
+    }
+
+
+def test_domains_not_domains_intersection():
+    c = config.Config(browser_host="foo.bar",
+                      alternate_hosts={"alt": "foo2.bar"},
+                      subdomains={"a", "b"},
+                      not_subdomains={"x", "y"})
+    domains = c.domains
+    not_domains = c.not_domains
+    assert len(set(domains.iterkeys()) ^ set(not_domains.iterkeys())) == 0
+    for host in domains.iterkeys():
+        host_domains = domains[host]
+        host_not_domains = not_domains[host]
+        assert len(set(host_domains.iterkeys()) & set(host_not_domains.iterkeys())) == 0
+        assert len(set(host_domains.itervalues()) & set(host_not_domains.itervalues())) == 0
+
+
+def test_all_domains():
+    c = config.Config(browser_host="foo.bar",
+                      alternate_hosts={"alt": "foo2.bar"},
+                      subdomains={"a", "b"},
+                      not_subdomains={"x", "y"})
+    all_domains = c.all_domains
+    assert all_domains == {
+        "": {
+            "": "foo.bar",
+            "a": "a.foo.bar",
+            "b": "b.foo.bar",
+            "x": "x.foo.bar",
+            "y": "y.foo.bar",
+        },
+        "alt": {
+            "": "foo2.bar",
+            "a": "a.foo2.bar",
+            "b": "b.foo2.bar",
+            "x": "x.foo2.bar",
+            "y": "y.foo2.bar",
+        },
+    }
+
+
+def test_domains_set():
+    c = config.Config(browser_host="foo.bar",
+                      alternate_hosts={"alt": "foo2.bar"},
+                      subdomains={"a", "b"},
+                      not_subdomains={"x", "y"})
+    domains_set = c.domains_set
+    assert domains_set == {
+        "foo.bar",
+        "a.foo.bar",
+        "b.foo.bar",
+        "foo2.bar",
+        "a.foo2.bar",
+        "b.foo2.bar",
+    }
+
+
+def test_not_domains_set():
+    c = config.Config(browser_host="foo.bar",
+                      alternate_hosts={"alt": "foo2.bar"},
+                      subdomains={"a", "b"},
+                      not_subdomains={"x", "y"})
+    not_domains_set = c.not_domains_set
+    assert not_domains_set == {
+        "x.foo.bar",
+        "y.foo.bar",
+        "x.foo2.bar",
+        "y.foo2.bar",
+    }
+
+
+def test_all_domains_set():
+    c = config.Config(browser_host="foo.bar",
+                      alternate_hosts={"alt": "foo2.bar"},
+                      subdomains={"a", "b"},
+                      not_subdomains={"x", "y"})
+    all_domains_set = c.all_domains_set
+    assert all_domains_set == {
+        "foo.bar",
+        "a.foo.bar",
+        "b.foo.bar",
+        "x.foo.bar",
+        "y.foo.bar",
+        "foo2.bar",
+        "a.foo2.bar",
+        "b.foo2.bar",
+        "x.foo2.bar",
+        "y.foo2.bar",
+    }
+
+
+def test_ssl_env_override():
+    c = config.Config(override_ssl_env="foobar")
+    assert c.ssl_env == "foobar"
+
+
+def test_ssl_env_none():
+    c = config.Config(ssl={"type": "none"})
+    assert c.ssl_env is not None
+    assert c.ssl_env.ssl_enabled is False
+
+
+def test_ssl_env_openssl():
+    c = config.Config(ssl={"type": "openssl", "openssl": {"openssl_binary": "foobar"}})
+    assert c.ssl_env is not None
+    assert c.ssl_env.ssl_enabled is True
+    assert c.ssl_env.binary == "foobar"
+
+
+def test_ssl_env_bogus():
+    c = config.Config(ssl={"type": "foobar"})
+    with pytest.raises(ValueError):
+        c.ssl_env
+
+
+def test_pickle():
+    # Ensure that the config object can be pickled
+    pickle.dumps(config.Config())
diff --git a/tools/wptserve/tests/test_replacement_tokenizer.py b/tools/wptserve/tests/test_replacement_tokenizer.py
new file mode 100644
index 0000000..ea0c0c2
--- /dev/null
+++ b/tools/wptserve/tests/test_replacement_tokenizer.py
@@ -0,0 +1,37 @@
+import pytest
+
+from wptserve.pipes import ReplacementTokenizer
+
+@pytest.mark.parametrize(
+    "content,expected",
+    [
+        ["aaa", [('ident', 'aaa')]],
+        ["bbb()", [('ident', 'bbb'), ('arguments', [])]],
+        ["$ccc:ddd", [('var', '$ccc'), ('ident', 'ddd')]],
+        ["$eee", [('ident', '$eee')]],
+        ["fff[0]", [('ident', 'fff'), ('index', 0)]],
+        ["ggg[hhh]", [('ident', 'ggg'), ('index', u'hhh')]],
+        ["[iii]", [('index', u'iii')]],
+        ["jjj['kkk']", [('ident', 'jjj'), ('index', u"'kkk'")]],
+        ["lll[]", [('ident', 'lll'), ('index', u"")]],
+        ["111", [('ident', u'111')]],
+        ["$111", [('ident', u'$111')]],
+    ]
+)
+def test_tokenizer(content, expected):
+    tokenizer = ReplacementTokenizer()
+    tokens = tokenizer.tokenize(content)
+    assert expected == tokens
+
+
+@pytest.mark.parametrize(
+    "content,expected",
+    [
+        ["/", []],
+        ["$aaa: BBB", [('var', '$aaa')]],
+    ]
+)
+def test_tokenizer_errors(content, expected):
+    tokenizer = ReplacementTokenizer()
+    tokens = tokenizer.tokenize(content)
+    assert expected == tokens
diff --git a/tools/wptserve/wptserve/config.py b/tools/wptserve/wptserve/config.py
new file mode 100644
index 0000000..3e4729a
--- /dev/null
+++ b/tools/wptserve/wptserve/config.py
@@ -0,0 +1,283 @@
+import logging
+import os
+
+from collections import defaultdict, Mapping
+
+import sslutils
+from .utils import get_port
+
+
+_renamed_props = {
+    "host": "browser_host",
+    "bind_hostname": "bind_address",
+    "external_host": "server_host",
+    "host_ip": "server_host",
+}
+
+
+def _merge_dict(base_dict, override_dict):
+    rv = base_dict.copy()
+    for key, value in base_dict.iteritems():
+        if key in override_dict:
+            if isinstance(value, dict):
+                rv[key] = _merge_dict(value, override_dict[key])
+            else:
+                rv[key] = override_dict[key]
+    return rv
+
+
+class Config(Mapping):
+    """wptserve config
+
+    Inherits from Mapping for backwards compatibility with the old dict-based config"""
+
+    _default = {
+        "browser_host": "localhost",
+        "alternate_hosts": {},
+        "doc_root": os.path.dirname("__file__"),
+        "server_host": None,
+        "ports": {"http": [8000]},
+        "check_subdomains": True,
+        "log_level": "debug",
+        "bind_address": True,
+        "ssl": {
+            "type": "none",
+            "encrypt_after_connect": False,
+            "openssl": {
+                "openssl_binary": "openssl",
+                "base_path": "_certs",
+                "force_regenerate": False,
+                "base_conf_path": None
+            },
+            "pregenerated": {
+                "host_key_path": None,
+                "host_cert_path": None,
+            },
+        },
+        "aliases": []
+    }
+
+    def __init__(self,
+                 logger=None,
+                 subdomains=set(),
+                 not_subdomains=set(),
+                 **kwargs):
+
+        self.log_level = kwargs.get("log_level", "DEBUG")
+
+        if logger is None:
+            self._logger_name = "web-platform-tests"
+        else:
+            level_name = logging.getLevelName(logger.level)
+            if level_name != "NOTSET":
+                self.log_level = level_name
+            self._logger_name = logger.name
+
+        for k, v in self._default.iteritems():
+            setattr(self, k, kwargs.pop(k, v))
+
+        self.subdomains = subdomains
+        self.not_subdomains = not_subdomains
+
+        for k, new_k in _renamed_props.iteritems():
+            if k in kwargs:
+                self.logger.warning(
+                    "%s in config is deprecated; use %s instead" % (
+                        k,
+                        new_k
+                    )
+                )
+                setattr(self, new_k, kwargs.pop(k))
+
+        self.override_ssl_env = kwargs.pop("override_ssl_env", None)
+
+        if kwargs:
+            raise TypeError("__init__() got unexpected keyword arguments %r" % (tuple(kwargs),))
+
+    def __getitem__(self, k):
+        try:
+            return getattr(self, k)
+        except AttributeError:
+            raise KeyError(k)
+
+    def __iter__(self):
+        return iter([x for x in dir(self) if not x.startswith("_")])
+
+    def __len__(self):
+        return len([x for x in dir(self) if not x.startswith("_")])
+
+    def update(self, override):
+        """Load an overrides dict to override config values"""
+        override = override.copy()
+
+        for k in self._default:
+            if k in override:
+                self._set_override(k, override.pop(k))
+
+        for k, new_k in _renamed_props.iteritems():
+            if k in override:
+                self.logger.warning(
+                    "%s in config is deprecated; use %s instead" % (
+                        k,
+                        new_k
+                    )
+                )
+                self._set_override(new_k, override.pop(k))
+
+        if override:
+            k = next(iter(override))
+            raise KeyError("unknown config override '%s'" % k)
+
+    def _set_override(self, k, v):
+        old_v = getattr(self, k)
+        if isinstance(old_v, dict):
+            setattr(self, k, _merge_dict(old_v, v))
+        else:
+            setattr(self, k, v)
+
+    @property
+    def ports(self):
+        # To make this method thread-safe, we write to a temporary dict first,
+        # and change self._computed_ports to the new dict at last atomically.
+        new_ports = defaultdict(list)
+
+        try:
+            old_ports = self._computed_ports
+        except AttributeError:
+            old_ports = {}
+
+        for scheme, ports in self._ports.iteritems():
+            for i, port in enumerate(ports):
+                if scheme in ["wss", "https"] and not self.ssl_env.ssl_enabled:
+                    port = None
+                if port == "auto":
+                    try:
+                        port = old_ports[scheme][i]
+                    except (KeyError, IndexError):
+                        port = get_port(self.server_host)
+                else:
+                    port = port
+                new_ports[scheme].append(port)
+
+        self._computed_ports = new_ports
+        return self._computed_ports
+
+    @ports.setter
+    def ports(self, v):
+        self._ports = v
+
+    @property
+    def doc_root(self):
+        return self._doc_root
+
+    @doc_root.setter
+    def doc_root(self, v):
+        self._doc_root = v
+
+    @property
+    def server_host(self):
+        return self._server_host if self._server_host is not None else self.browser_host
+
+    @server_host.setter
+    def server_host(self, v):
+        self._server_host = v
+
+    @property
+    def domains(self):
+        hosts = self.alternate_hosts.copy()
+        assert "" not in hosts
+        hosts[""] = self.browser_host
+
+        rv = {}
+        for name, host in hosts.iteritems():
+            rv[name] = {subdomain: (subdomain.encode("idna") + u"." + host)
+                        for subdomain in self.subdomains}
+            rv[name][""] = host
+        return rv
+
+    @property
+    def not_domains(self):
+        hosts = self.alternate_hosts.copy()
+        assert "" not in hosts
+        hosts[""] = self.browser_host
+
+        rv = {}
+        for name, host in hosts.iteritems():
+            rv[name] = {subdomain: (subdomain.encode("idna") + u"." + host)
+                        for subdomain in self.not_subdomains}
+        return rv
+
+    @property
+    def all_domains(self):
+        rv = self.domains.copy()
+        nd = self.not_domains
+        for host in rv:
+            rv[host].update(nd[host])
+        return rv
+
+    @property
+    def domains_set(self):
+        return {domain
+                for per_host_domains in self.domains.itervalues()
+                for domain in per_host_domains.itervalues()}
+
+    @property
+    def not_domains_set(self):
+        return {domain
+                for per_host_domains in self.not_domains.itervalues()
+                for domain in per_host_domains.itervalues()}
+
+    @property
+    def all_domains_set(self):
+        return self.domains_set | self.not_domains_set
+
+    @property
+    def paths(self):
+        return {"doc_root": self.doc_root}
+
+    @property
+    def ssl_env(self):
+        try:
+            if self.override_ssl_env is not None:
+                return self.override_ssl_env
+        except AttributeError:
+            pass
+
+        implementation_type = self.ssl["type"]
+
+        try:
+            cls = sslutils.environments[implementation_type]
+        except KeyError:
+            raise ValueError("%s is not a vaid ssl type." % implementation_type)
+        kwargs = self.ssl.get(implementation_type, {}).copy()
+        return cls(self.logger, **kwargs)
+
+    @property
+    def ssl_config(self):
+        key_path, cert_path = self.ssl_env.host_cert_path(self.domains_set)
+        return {"key_path": key_path,
+                "cert_path": cert_path,
+                "encrypt_after_connect": self.ssl["encrypt_after_connect"]}
+
+    @property
+    def log_level(self):
+        return getattr(logging, self._log_level)
+
+    @log_level.setter
+    def log_level(self, value):
+        self._log_level = value.upper()
+
+    @property
+    def logger(self):
+        logger = logging.getLogger(self._logger_name)
+        logger.setLevel(self.log_level)
+        return logger
+
+    def as_dict(self):
+        rv = {
+            "domains": list(self.domains),
+            "sundomains": list(self.subdomains),
+        }
+        for item in self._default.iterkeys():
+            rv[item] = getattr(self, item)
+        return rv
diff --git a/tools/wptserve/wptserve/handlers.py b/tools/wptserve/wptserve/handlers.py
index c962a6c..4536c06 100644
--- a/tools/wptserve/wptserve/handlers.py
+++ b/tools/wptserve/wptserve/handlers.py
@@ -1,6 +1,7 @@
 import cgi
 import json
 import os
+import sys
 import traceback
 
 from six.moves.urllib.parse import parse_qs, quote, unquote, urljoin
@@ -231,16 +232,24 @@
     def __call__(self, request, response):
         path = filesystem_path(self.base_path, request, self.url_base)
 
+        sys_path = sys.path[:]
+        sys_modules = sys.modules.copy()
         try:
             environ = {"__file__": path}
+            sys.path.insert(0, os.path.dirname(path))
             execfile(path, environ, environ)
             if "main" in environ:
                 handler = FunctionHandler(environ["main"])
                 handler(request, response)
+                wrap_pipeline(path, request, response)
             else:
                 raise HTTPException(500, "No main function in script %s" % path)
         except IOError:
             raise HTTPException(404)
+        finally:
+            sys.path = sys_path
+            sys.modules = sys_modules
+
 
 python_script_handler = PythonScriptHandler()
 
@@ -251,6 +260,8 @@
     def __call__(self, request, response):
         try:
             rv = self.func(request, response)
+        except HTTPException:
+            raise
         except Exception:
             msg = traceback.format_exc()
             raise HTTPException(500, message=msg)
@@ -267,6 +278,7 @@
             else:
                 content = rv
             response.content = content
+            wrap_pipeline('', request, response)
 
 
 #The generic name here is so that this can be used as a decorator
@@ -309,6 +321,7 @@
         try:
             with open(path) as f:
                 response.writer.write_content(f.read())
+            wrap_pipeline(path, request, response)
             response.close_connection = True
         except IOError:
             raise HTTPException(404)
@@ -363,7 +376,7 @@
 
         self.resp_headers = [("Content-Type", content_type)]
         for k, v in headers.iteritems():
-            resp_headers.append((k.replace("_", "-"), v))
+            self.resp_headers.append((k.replace("_", "-"), v))
 
         self.handler = handler(self.handle_request)
 
diff --git a/tools/wptserve/wptserve/pipes.py b/tools/wptserve/wptserve/pipes.py
index 7203815..9c13965 100644
--- a/tools/wptserve/wptserve/pipes.py
+++ b/tools/wptserve/wptserve/pipes.py
@@ -1,11 +1,15 @@
 from cgi import escape
+from collections import deque
 import gzip as gzip_module
+import hashlib
+import os
 import re
 import time
 import types
 import uuid
 from cStringIO import StringIO
 
+from six import text_type
 
 def resolve_content(response):
     return b"".join(item for item in response.iter_content(read_file=True))
@@ -276,18 +280,22 @@
 
 
 class ReplacementTokenizer(object):
-    def ident(scanner, token):
+    def arguments(self, token):
+        unwrapped = token[1:-1]
+        return ("arguments", re.split(r",\s*", token[1:-1]) if unwrapped else [])
+
+    def ident(self, token):
         return ("ident", token)
 
-    def index(scanner, token):
+    def index(self, token):
         token = token[1:-1]
         try:
             token = int(token)
         except ValueError:
-            token = unicode(token, "utf8")
+            token = token.decode('utf8')
         return ("index", token)
 
-    def var(scanner, token):
+    def var(self, token):
         token = token[:-1]
         return ("var", token)
 
@@ -295,8 +303,9 @@
         return self.scanner.scan(string)[0]
 
     scanner = re.Scanner([(r"\$\w+:", var),
-                          (r"\$?\w+(?:\(\))?", ident),
-                          (r"\[[^\]]*\]", index)])
+                          (r"\$?\w+", ident),
+                          (r"\[[^\]]*\]", index),
+                          (r"\([^)]*\)", arguments)])
 
 
 class FirstWrapper(object):
@@ -338,6 +347,11 @@
       A dictionary of query parameters supplied with the request.
     uuid()
       A pesudo-random UUID suitable for usage with stash
+    file_hash(algorithm, filepath)
+      The cryptographic hash of a file. Supported algorithms: md5, sha1,
+      sha224, sha256, sha384, and sha512. For example:
+
+        {{file_hash(md5, dom/interfaces.html)}}
 
     So for example in a setup running on localhost with a www
     subdomain and a http server on ports 80 and 81::
@@ -350,7 +364,7 @@
     It is also possible to assign a value to a variable name, which must start with
     the $ character, using the ":" syntax e.g.
 
-    {{$id:uuid()}
+    {{$id:uuid()}}
 
     Later substitutions in the same file may then refer to the variable
     by name e.g.
@@ -364,6 +378,39 @@
     response.content = new_content
     return response
 
+class SubFunctions(object):
+    @staticmethod
+    def uuid(request):
+        return str(uuid.uuid4())
+
+    # Maintain a whitelist of supported algorithms, restricted to those that
+    # are available on all platforms [1]. This ensures that test authors do not
+    # unknowingly introduce platform-specific tests.
+    #
+    # [1] https://docs.python.org/2/library/hashlib.html
+    supported_algorithms = ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")
+
+    @staticmethod
+    def file_hash(request, algorithm, path):
+        if algorithm not in SubFunctions.supported_algorithms:
+            raise ValueError("Unsupported encryption algorithm: '%s'" % algorithm)
+
+        hash_obj = getattr(hashlib, algorithm)()
+        absolute_path = os.path.join(request.doc_root, path)
+
+        try:
+            with open(absolute_path) as f:
+                hash_obj.update(f.read())
+        except IOError:
+            # In this context, an unhandled IOError will be interpreted by the
+            # server as an indication that the template file is non-existent.
+            # Although the generic "Exception" is less precise, it avoids
+            # triggering a potentially-confusing HTTP 404 error in cases where
+            # the path to the file to be hashed is invalid.
+            raise Exception('Cannot open file for hash computation: "%s"' % absolute_path)
+
+        return hash_obj.digest().encode('base64').strip()
+
 def template(request, content, escape_type="html"):
     #TODO: There basically isn't any error handling here
     tokenizer = ReplacementTokenizer()
@@ -374,25 +421,35 @@
         content, = match.groups()
 
         tokens = tokenizer.tokenize(content)
+        tokens = deque(tokens)
 
-        if tokens[0][0] == "var":
-            variable = tokens[0][1]
-            tokens = tokens[1:]
+        token_type, field = tokens.popleft()
+
+        if token_type == "var":
+            variable = field
+            token_type, field = tokens.popleft()
         else:
             variable = None
 
-        assert tokens[0][0] == "ident" and all(item[0] == "index" for item in tokens[1:]), tokens
-
-        field = tokens[0][1]
+        if token_type != "ident":
+            raise Exception("unexpected token type %s (token '%r'), expected ident" % (token_type, field))
 
         if field in variables:
             value = variables[field]
+        elif hasattr(SubFunctions, field):
+            value = getattr(SubFunctions, field)
         elif field == "headers":
             value = request.headers
         elif field == "GET":
             value = FirstWrapper(request.GET)
+        elif field == "hosts":
+            value = request.server.config.all_domains
+        elif field == "domains":
+            value = request.server.config.all_domains[""]
+        elif field == "host":
+            value = request.server.config["browser_host"]
         elif field in request.server.config:
-            value = request.server.config[tokens[0][1]]
+            value = request.server.config[field]
         elif field == "location":
             value = {"server": "%s://%s:%s" % (request.url_parts.scheme,
                                                request.url_parts.hostname,
@@ -405,15 +462,21 @@
                      "path": request.url_parts.path,
                      "pathname": request.url_parts.path,
                      "query": "?%s" % request.url_parts.query}
-        elif field == "uuid()":
-            value = str(uuid.uuid4())
         elif field == "url_base":
             value = request.url_base
         else:
             raise Exception("Undefined template variable %s" % field)
 
-        for item in tokens[1:]:
-            value = value[item[1]]
+        while tokens:
+            ttype, field = tokens.popleft()
+            if ttype == "index":
+                value = value[field]
+            elif ttype == "arguments":
+                value = value(request, *field)
+            else:
+                raise Exception(
+                    "unexpected token type %s (token '%r'), expected ident or arguments" % (ttype, field)
+                )
 
         assert isinstance(value, (int,) + types.StringTypes), tokens
 
@@ -425,7 +488,7 @@
 
         #Should possibly support escaping for other contexts e.g. script
         #TODO: read the encoding of the response
-        return escape_func(unicode(value)).encode("utf-8")
+        return escape_func(text_type(value)).encode("utf-8")
 
     template_regexp = re.compile(r"{{([^}]*)}}")
     new_content = template_regexp.sub(config_replacement, content)
diff --git a/tools/wptserve/wptserve/request.py b/tools/wptserve/wptserve/request.py
index 4476634..7f49681 100644
--- a/tools/wptserve/wptserve/request.py
+++ b/tools/wptserve/wptserve/request.py
@@ -348,12 +348,22 @@
 class RequestHeaders(dict):
     """Dictionary-like API for accessing request headers."""
     def __init__(self, items):
-        for key, value in zip(items.keys(), items.values()):
-            key = key.lower()
-            if key in self:
-                self[key].append(value)
+        for header in items.keys():
+            key = header.lower()
+            # get all headers with the same name
+            values = items.getallmatchingheaders(header)
+            if len(values) > 1:
+                # collect the multiple variations of the current header
+                multiples = []
+                # loop through the values from getallmatchingheaders
+                for value in values:
+                    # getallmatchingheaders returns raw header lines, so
+                    # split to get name, value
+                    multiples.append(value.split(':', 1)[1].strip())
+                dict.__setitem__(self, key, multiples)
             else:
-                dict.__setitem__(self, key, [value])
+                dict.__setitem__(self, key, [items[header]])
+
 
     def __getitem__(self, key):
         """Get all headers of a certain (case-insensitive) name. If there is
diff --git a/tools/wptserve/wptserve/response.py b/tools/wptserve/wptserve/response.py
index 50ff00d..20b7b42 100644
--- a/tools/wptserve/wptserve/response.py
+++ b/tools/wptserve/wptserve/response.py
@@ -9,6 +9,8 @@
 from .constants import response_codes
 from .logger import get_logger
 
+from six import string_types, binary_type, text_type
+
 missing = object()
 
 class Response(object):
@@ -400,7 +402,7 @@
             if name.lower() not in self._headers_seen:
                 self.write_header(name, f())
 
-        if (type(self._response.content) in (str, unicode) and
+        if (isinstance(self._response.content, string_types) and
             "content-length" not in self._headers_seen):
             #Would be nice to avoid double-encoding here
             self.write_header("Content-Length", len(self.encode(self._response.content)))
@@ -457,9 +459,9 @@
 
     def encode(self, data):
         """Convert unicode to bytes according to response.encoding."""
-        if isinstance(data, str):
+        if isinstance(data, binary_type):
             return data
-        elif isinstance(data, unicode):
+        elif isinstance(data, text_type):
             return data.encode(self._response.encoding)
         else:
             raise ValueError
diff --git a/tools/wptserve/wptserve/server.py b/tools/wptserve/wptserve/server.py
index 2a83bd8..95d2320 100644
--- a/tools/wptserve/wptserve/server.py
+++ b/tools/wptserve/wptserve/server.py
@@ -13,6 +13,7 @@
 from six.moves.urllib.parse import urlsplit, urlunsplit
 
 from . import routes as default_routes
+from .config import Config
 from .logger import get_logger
 from .request import Server, Request
 from .response import Response
@@ -110,14 +111,15 @@
     # Ensure that we don't hang on shutdown waiting for requests
     daemon_threads = True
 
-    def __init__(self, server_address, RequestHandlerClass, router, rewriter, bind_hostname,
+    def __init__(self, server_address, request_handler_cls,
+                 router, rewriter, bind_address,
                  config=None, use_ssl=False, key_file=None, certificate=None,
                  encrypt_after_connect=False, latency=None, **kwargs):
         """Server for HTTP(s) Requests
 
         :param server_address: tuple of (server_name, port)
 
-        :param RequestHandlerClass: BaseHTTPRequestHandler-like class to use for
+        :param request_handler_cls: BaseHTTPRequestHandler-like class to use for
                                     handling requests.
 
         :param router: Router instance to use for matching requests to handler
@@ -140,10 +142,10 @@
                                       This enables the server to act as a
                                       self-proxy.
 
-        :param bind_hostname True to bind the server to both the hostname and
-                             port specified in the server_address parameter.
-                             False to bind the server only to the port in the
-                             server_address parameter, but not to the hostname.
+        :param bind_address True to bind the server to both the IP address and
+                            port specified in the server_address parameter.
+                            False to bind the server only to the port in the
+                            server_address parameter, but not to the address.
         :param latency: Delay in ms to wait before seving each response, or
                         callable that returns a delay in ms
         """
@@ -155,21 +157,20 @@
 
         self.latency = latency
 
-        if bind_hostname:
+        if bind_address:
             hostname_port = server_address
         else:
             hostname_port = ("",server_address[1])
 
         #super doesn't work here because BaseHTTPServer.HTTPServer is old-style
-        BaseHTTPServer.HTTPServer.__init__(self, hostname_port, RequestHandlerClass, **kwargs)
+        BaseHTTPServer.HTTPServer.__init__(self, hostname_port, request_handler_cls, **kwargs)
 
         if config is not None:
             Server.config = config
         else:
             self.logger.debug("Using default configuration")
-            Server.config = {"host": server_address[0],
-                             "domains": {"": server_address[0]},
-                             "ports": {"http": [self.server_address[1]]}}
+            Server.config = Config(browser_host=server_address[0],
+                                   ports={"http": [self.server_address[1]]})
 
 
         self.key_file = key_file
@@ -342,7 +343,7 @@
     :param rewrites: List of rewrites with which to initialize the rewriter_cls
     :param config: Dictionary holding environment configuration settings for
                    handlers to read, or None to use the default values.
-    :param bind_hostname: Boolean indicating whether to bind server to hostname.
+    :param bind_address: Boolean indicating whether to bind server to IP address.
     :param latency: Delay in ms to wait before seving each response, or
                     callable that returns a delay in ms
 
@@ -380,7 +381,7 @@
                  server_cls=None, handler_cls=WebTestRequestHandler,
                  use_ssl=False, key_file=None, certificate=None, encrypt_after_connect=False,
                  router_cls=Router, doc_root=os.curdir, routes=None,
-                 rewriter_cls=RequestRewriter, bind_hostname=True, rewrites=None,
+                 rewriter_cls=RequestRewriter, bind_address=True, rewrites=None,
                  latency=None, config=None):
 
         if routes is None:
@@ -398,9 +399,10 @@
             server_cls = WebTestServer
 
         if use_ssl:
-            if key_file is not None:
-                assert os.path.exists(key_file)
-            assert certificate is not None and os.path.exists(certificate)
+            if not os.path.exists(key_file):
+                raise ValueError("SSL certificate not found: {}".format(key_file))
+            if not os.path.exists(certificate):
+                raise ValueError("SSL key not found: {}".format(certificate))
 
         try:
             self.httpd = server_cls((host, port),
@@ -408,7 +410,7 @@
                                     self.router,
                                     self.rewriter,
                                     config=config,
-                                    bind_hostname=bind_hostname,
+                                    bind_address=bind_address,
                                     use_ssl=use_ssl,
                                     key_file=key_file,
                                     certificate=certificate,
diff --git a/tools/sslutils/__init__.py b/tools/wptserve/wptserve/sslutils/__init__.py
similarity index 100%
rename from tools/sslutils/__init__.py
rename to tools/wptserve/wptserve/sslutils/__init__.py
diff --git a/tools/wptserve/wptserve/sslutils/base.py b/tools/wptserve/wptserve/sslutils/base.py
new file mode 100644
index 0000000..c95b693
--- /dev/null
+++ b/tools/wptserve/wptserve/sslutils/base.py
@@ -0,0 +1,17 @@
+class NoSSLEnvironment(object):
+    ssl_enabled = False
+
+    def __init__(self, *args, **kwargs):
+        pass
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        pass
+
+    def host_cert_path(self, hosts):
+        return None, None
+
+    def ca_cert_path(self):
+        return None
diff --git a/tools/wptserve/wptserve/sslutils/openssl.py b/tools/wptserve/wptserve/sslutils/openssl.py
new file mode 100644
index 0000000..6f345eb
--- /dev/null
+++ b/tools/wptserve/wptserve/sslutils/openssl.py
@@ -0,0 +1,422 @@
+import functools
+import os
+import random
+import shutil
+import subprocess
+import tempfile
+from datetime import datetime, timedelta
+
+# Amount of time beyond the present to consider certificates "expired." This
+# allows certificates to be proactively re-generated in the "buffer" period
+# prior to their exact expiration time.
+CERT_EXPIRY_BUFFER = dict(hours=6)
+
+class OpenSSL(object):
+    def __init__(self, logger, binary, base_path, conf_path, hosts, duration,
+                 base_conf_path=None):
+        """Context manager for interacting with OpenSSL.
+        Creates a config file for the duration of the context.
+
+        :param logger: stdlib logger or python structured logger
+        :param binary: path to openssl binary
+        :param base_path: path to directory for storing certificates
+        :param conf_path: path for configuration file storing configuration data
+        :param hosts: list of hosts to include in configuration (or None if not
+                      generating host certificates)
+        :param duration: Certificate duration in days"""
+
+        self.base_path = base_path
+        self.binary = binary
+        self.conf_path = conf_path
+        self.base_conf_path = base_conf_path
+        self.logger = logger
+        self.proc = None
+        self.cmd = []
+        self.hosts = hosts
+        self.duration = duration
+
+    def __enter__(self):
+        with open(self.conf_path, "w") as f:
+            f.write(get_config(self.base_path, self.hosts, self.duration))
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        os.unlink(self.conf_path)
+
+    def log(self, line):
+        if hasattr(self.logger, "process_output"):
+            self.logger.process_output(self.proc.pid if self.proc is not None else None,
+                                       line.decode("utf8", "replace"),
+                                       command=" ".join(self.cmd))
+        else:
+            self.logger.debug(line)
+
+    def __call__(self, cmd, *args, **kwargs):
+        """Run a command using OpenSSL in the current context.
+
+        :param cmd: The openssl subcommand to run
+        :param *args: Additional arguments to pass to the command
+        """
+        self.cmd = [self.binary, cmd]
+        if cmd != "x509":
+            self.cmd += ["-config", self.conf_path]
+        self.cmd += list(args)
+
+        # Copy the environment, converting to plain strings. Windows
+        # StartProcess is picky about all the keys/values being plain strings,
+        # but at least in MSYS shells, the os.environ dictionary can be mixed.
+        env = {}
+        for k, v in os.environ.iteritems():
+            try:
+                env[k.encode("utf8")] = v.encode("utf8")
+            except UnicodeDecodeError:
+                pass
+
+        if self.base_conf_path is not None:
+            env["OPENSSL_CONF"] = self.base_conf_path.encode("utf8")
+
+        self.proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                                     env=env)
+        stdout, stderr = self.proc.communicate()
+        self.log(stdout)
+        if self.proc.returncode != 0:
+            raise subprocess.CalledProcessError(self.proc.returncode, self.cmd,
+                                                output=stdout)
+
+        self.cmd = []
+        self.proc = None
+        return stdout
+
+
+def make_subject(common_name,
+                 country=None,
+                 state=None,
+                 locality=None,
+                 organization=None,
+                 organization_unit=None):
+    args = [("country", "C"),
+            ("state", "ST"),
+            ("locality", "L"),
+            ("organization", "O"),
+            ("organization_unit", "OU"),
+            ("common_name", "CN")]
+
+    rv = []
+
+    for var, key in args:
+        value = locals()[var]
+        if value is not None:
+            rv.append("/%s=%s" % (key, value.replace("/", "\\/")))
+
+    return "".join(rv)
+
+def make_alt_names(hosts):
+    rv = []
+    for name in hosts:
+        rv.append("DNS:%s" % name)
+    return ",".join(rv)
+
+def get_config(root_dir, hosts, duration=30):
+    if hosts is None:
+        san_line = ""
+    else:
+        san_line = "subjectAltName = %s" % make_alt_names(hosts)
+
+    if os.path.sep == "\\":
+        # This seems to be needed for the Shining Light OpenSSL on
+        # Windows, at least.
+        root_dir = root_dir.replace("\\", "\\\\")
+
+    rv = """[ ca ]
+default_ca = CA_default
+
+[ CA_default ]
+dir = %(root_dir)s
+certs = $dir
+new_certs_dir = $certs
+crl_dir = $dir%(sep)scrl
+database = $dir%(sep)sindex.txt
+private_key = $dir%(sep)scacert.key
+certificate = $dir%(sep)scacert.pem
+serial = $dir%(sep)sserial
+crldir = $dir%(sep)scrl
+crlnumber = $dir%(sep)scrlnumber
+crl = $crldir%(sep)scrl.pem
+RANDFILE = $dir%(sep)sprivate%(sep)s.rand
+x509_extensions = usr_cert
+name_opt        = ca_default
+cert_opt        = ca_default
+default_days = %(duration)d
+default_crl_days = %(duration)d
+default_md = sha256
+preserve = no
+policy = policy_anything
+copy_extensions = copy
+
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[ req ]
+default_bits = 2048
+default_keyfile  = privkey.pem
+distinguished_name = req_distinguished_name
+attributes = req_attributes
+x509_extensions = v3_ca
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+string_mask = utf8only
+req_extensions = v3_req
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = AU
+countryName_min = 2
+countryName_max = 2
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default =
+localityName = Locality Name (eg, city)
+0.organizationName = Organization Name
+0.organizationName_default = Web Platform Tests
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_max = 64
+emailAddress = Email Address
+emailAddress_max = 64
+
+[ req_attributes ]
+
+[ usr_cert ]
+basicConstraints=CA:false
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+[ v3_req ]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = serverAuth
+%(san_line)s
+
+[ v3_ca ]
+basicConstraints = CA:true
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer:always
+keyUsage = keyCertSign
+""" % {"root_dir": root_dir,
+       "san_line": san_line,
+       "duration": duration,
+       "sep": os.path.sep.replace("\\", "\\\\")}
+
+    return rv
+
+class OpenSSLEnvironment(object):
+    ssl_enabled = True
+
+    def __init__(self, logger, openssl_binary="openssl", base_path=None,
+                 password="web-platform-tests", force_regenerate=False,
+                 duration=30, base_conf_path=None):
+        """SSL environment that creates a local CA and host certificate using OpenSSL.
+
+        By default this will look in base_path for existing certificates that are still
+        valid and only create new certificates if there aren't any. This behaviour can
+        be adjusted using the force_regenerate option.
+
+        :param logger: a stdlib logging compatible logger or mozlog structured logger
+        :param openssl_binary: Path to the OpenSSL binary
+        :param base_path: Path in which certificates will be stored. If None, a temporary
+                          directory will be used and removed when the server shuts down
+        :param password: Password to use
+        :param force_regenerate: Always create a new certificate even if one already exists.
+        """
+        self.logger = logger
+
+        self.temporary = False
+        if base_path is None:
+            base_path = tempfile.mkdtemp()
+            self.temporary = True
+
+        self.base_path = os.path.abspath(base_path)
+        self.password = password
+        self.force_regenerate = force_regenerate
+        self.duration = duration
+        self.base_conf_path = base_conf_path
+
+        self.path = None
+        self.binary = openssl_binary
+        self.openssl = None
+
+        self._ca_cert_path = None
+        self._ca_key_path = None
+        self.host_certificates = {}
+
+    def __enter__(self):
+        if not os.path.exists(self.base_path):
+            os.makedirs(self.base_path)
+
+        path = functools.partial(os.path.join, self.base_path)
+
+        with open(path("index.txt"), "w"):
+            pass
+        with open(path("serial"), "w") as f:
+            serial = "%x" % random.randint(0, 1000000)
+            if len(serial) % 2:
+                serial = "0" + serial
+            f.write(serial)
+
+        self.path = path
+
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        if self.temporary:
+            shutil.rmtree(self.base_path)
+
+    def _config_openssl(self, hosts):
+        conf_path = self.path("openssl.cfg")
+        return OpenSSL(self.logger, self.binary, self.base_path, conf_path, hosts,
+                       self.duration, self.base_conf_path)
+
+    def ca_cert_path(self):
+        """Get the path to the CA certificate file, generating a
+        new one if needed"""
+        if self._ca_cert_path is None and not self.force_regenerate:
+            self._load_ca_cert()
+        if self._ca_cert_path is None:
+            self._generate_ca()
+        return self._ca_cert_path
+
+    def _load_ca_cert(self):
+        key_path = self.path("cacert.key")
+        cert_path = self.path("cacert.pem")
+
+        if self.check_key_cert(key_path, cert_path, None):
+            self.logger.info("Using existing CA cert")
+            self._ca_key_path, self._ca_cert_path = key_path, cert_path
+
+    def check_key_cert(self, key_path, cert_path, hosts):
+        """Check that a key and cert file exist and are valid"""
+        if not os.path.exists(key_path) or not os.path.exists(cert_path):
+            return False
+
+        with self._config_openssl(hosts) as openssl:
+            end_date_str = openssl("x509",
+                                   "-noout",
+                                   "-enddate",
+                                   "-in", cert_path).split("=", 1)[1].strip()
+            # Not sure if this works in other locales
+            end_date = datetime.strptime(end_date_str, "%b %d %H:%M:%S %Y %Z")
+            time_buffer = timedelta(**CERT_EXPIRY_BUFFER)
+            # Because `strptime` does not account for time zone offsets, it is
+            # always in terms of UTC, so the current time should be calculated
+            # accordingly.
+            if end_date < datetime.utcnow() + time_buffer:
+                return False
+
+        #TODO: check the key actually signed the cert.
+        return True
+
+    def _generate_ca(self):
+        path = self.path
+        self.logger.info("Generating new CA in %s" % self.base_path)
+
+        key_path = path("cacert.key")
+        req_path = path("careq.pem")
+        cert_path = path("cacert.pem")
+
+        with self._config_openssl(None) as openssl:
+            openssl("req",
+                    "-batch",
+                    "-new",
+                    "-newkey", "rsa:2048",
+                    "-keyout", key_path,
+                    "-out", req_path,
+                    "-subj", make_subject("web-platform-tests"),
+                    "-passout", "pass:%s" % self.password)
+
+            openssl("ca",
+                    "-batch",
+                    "-create_serial",
+                    "-keyfile", key_path,
+                    "-passin", "pass:%s" % self.password,
+                    "-selfsign",
+                    "-extensions", "v3_ca",
+                    "-in", req_path,
+                    "-out", cert_path)
+
+        os.unlink(req_path)
+
+        self._ca_key_path, self._ca_cert_path = key_path, cert_path
+
+    def host_cert_path(self, hosts):
+        """Get a tuple of (private key path, certificate path) for a host,
+        generating new ones if necessary.
+
+        hosts must be a list of all hosts to appear on the certificate, with
+        the primary hostname first."""
+        hosts = tuple(sorted(hosts, key=lambda x:-len(x)))
+        if hosts not in self.host_certificates:
+            if not self.force_regenerate:
+                key_cert = self._load_host_cert(hosts)
+            else:
+                key_cert = None
+            if key_cert is None:
+                key, cert = self._generate_host_cert(hosts)
+            else:
+                key, cert = key_cert
+            self.host_certificates[hosts] = key, cert
+
+        return self.host_certificates[hosts]
+
+    def _load_host_cert(self, hosts):
+        host = hosts[0]
+        key_path = self.path("%s.key" % host)
+        cert_path = self.path("%s.pem" % host)
+
+        # TODO: check that this cert was signed by the CA cert
+        if self.check_key_cert(key_path, cert_path, hosts):
+            self.logger.info("Using existing host cert")
+            return key_path, cert_path
+
+    def _generate_host_cert(self, hosts):
+        host = hosts[0]
+        if self._ca_key_path is None:
+            self._generate_ca()
+        ca_key_path = self._ca_key_path
+
+        assert os.path.exists(ca_key_path)
+
+        path = self.path
+
+        req_path = path("wpt.req")
+        cert_path = path("%s.pem" % host)
+        key_path = path("%s.key" % host)
+
+        self.logger.info("Generating new host cert")
+
+        with self._config_openssl(hosts) as openssl:
+            openssl("req",
+                    "-batch",
+                    "-newkey", "rsa:2048",
+                    "-keyout", key_path,
+                    "-in", ca_key_path,
+                    "-nodes",
+                    "-out", req_path)
+
+            openssl("ca",
+                    "-batch",
+                    "-in", req_path,
+                    "-passin", "pass:%s" % self.password,
+                    "-subj", make_subject(host),
+                    "-out", cert_path)
+
+        os.unlink(req_path)
+
+        return key_path, cert_path
diff --git a/tools/sslutils/pregenerated.py b/tools/wptserve/wptserve/sslutils/pregenerated.py
similarity index 100%
rename from tools/sslutils/pregenerated.py
rename to tools/wptserve/wptserve/sslutils/pregenerated.py
diff --git a/tools/wptserve/wptserve/stash.py b/tools/wptserve/wptserve/stash.py
index b6bd6ee..69fa441 100644
--- a/tools/wptserve/wptserve/stash.py
+++ b/tools/wptserve/wptserve/stash.py
@@ -2,7 +2,8 @@
 import json
 import os
 import uuid
-from multiprocessing.managers import BaseManager, DictProxy
+import threading
+from multiprocessing.managers import AcquirerProxy, BaseManager, DictProxy
 
 class ServerDictManager(BaseManager):
     shared_data = {}
@@ -13,11 +14,13 @@
 ServerDictManager.register("get_dict",
                            callable=_get_shared,
                            proxytype=DictProxy)
+ServerDictManager.register('Lock', threading.Lock, AcquirerProxy)
 
 class ClientDictManager(BaseManager):
     pass
 
 ClientDictManager.register("get_dict")
+ClientDictManager.register("Lock")
 
 class StashServer(object):
     def __init__(self, address=None, authkey=None):
@@ -53,6 +56,22 @@
     return (manager, manager._address, manager._authkey)
 
 
+class LockWrapper(object):
+    def __init__(self, lock):
+        self.lock = lock
+
+    def acquire(self):
+        self.lock.acquire()
+
+    def release(self):
+        self.lock.release()
+
+    def __enter__(self):
+        self.acquire()
+
+    def __exit__(self, *args, **kwargs):
+        self.release()
+
 #TODO: Consider expiring values after some fixed time for long-running
 #servers
 
@@ -81,21 +100,23 @@
     """
 
     _proxy = None
+    lock = None
 
     def __init__(self, default_path, address=None, authkey=None):
         self.default_path = default_path
-        self.data = self._get_proxy(address, authkey)
+        self._get_proxy(address, authkey)
+        self.data = Stash._proxy
 
     def _get_proxy(self, address=None, authkey=None):
         if address is None and authkey is None:
             Stash._proxy = {}
+            Stash.lock = threading.Lock()
 
         if Stash._proxy is None:
             manager = ClientDictManager(address, authkey)
             manager.connect()
             Stash._proxy = manager.get_dict()
-
-        return Stash._proxy
+            Stash.lock = LockWrapper(manager.Lock())
 
     def _wrap_key(self, key, path):
         if path is None:
diff --git a/tools/wptserve/wptserve/utils.py b/tools/wptserve/wptserve/utils.py
index e57ff19..288f066 100644
--- a/tools/wptserve/wptserve/utils.py
+++ b/tools/wptserve/wptserve/utils.py
@@ -1,3 +1,5 @@
+import socket
+
 def invert_dict(dict):
     rv = {}
     for key, values in dict.iteritems():
@@ -12,3 +14,93 @@
     def __init__(self, code, message=""):
         self.code = code
         self.message = message
+
+
+def _open_socket(host, port):
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    if port != 0:
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    sock.bind((host, port))
+    sock.listen(5)
+    return sock
+
+def is_bad_port(port):
+    """
+    Bad port as per https://fetch.spec.whatwg.org/#port-blocking
+    """
+    return port in [
+        1,     # tcpmux
+        7,     # echo
+        9,     # discard
+        11,    # systat
+        13,    # daytime
+        15,    # netstat
+        17,    # qotd
+        19,    # chargen
+        20,    # ftp-data
+        21,    # ftp
+        22,    # ssh
+        23,    # telnet
+        25,    # smtp
+        37,    # time
+        42,    # name
+        43,    # nicname
+        53,    # domain
+        77,    # priv-rjs
+        79,    # finger
+        87,    # ttylink
+        95,    # supdup
+        101,   # hostriame
+        102,   # iso-tsap
+        103,   # gppitnp
+        104,   # acr-nema
+        109,   # pop2
+        110,   # pop3
+        111,   # sunrpc
+        113,   # auth
+        115,   # sftp
+        117,   # uucp-path
+        119,   # nntp
+        123,   # ntp
+        135,   # loc-srv / epmap
+        139,   # netbios
+        143,   # imap2
+        179,   # bgp
+        389,   # ldap
+        465,   # smtp+ssl
+        512,   # print / exec
+        513,   # login
+        514,   # shell
+        515,   # printer
+        526,   # tempo
+        530,   # courier
+        531,   # chat
+        532,   # netnews
+        540,   # uucp
+        556,   # remotefs
+        563,   # nntp+ssl
+        587,   # smtp
+        601,   # syslog-conn
+        636,   # ldap+ssl
+        993,   # imap+ssl
+        995,   # pop3+ssl
+        2049,  # nfs
+        3659,  # apple-sasl
+        4045,  # lockd
+        6000,  # x11
+        6665,  # irc (alternate)
+        6666,  # irc (alternate)
+        6667,  # irc (default)
+        6668,  # irc (alternate)
+        6669,  # irc (alternate)
+    ]
+
+def get_port(host):
+    port = 0
+    while True:
+        free_socket = _open_socket(host, 0)
+        port = free_socket.getsockname()[1]
+        free_socket.close()
+        if not is_bad_port(port):
+            break
+    return port
diff --git a/touch-events/create-touch-touchlist.html b/touch-events/create-touch-touchlist.html
index abd0f48..2e6e49f 100644
--- a/touch-events/create-touch-touchlist.html
+++ b/touch-events/create-touch-touchlist.html
@@ -9,17 +9,6 @@
 <div id="target0"></div>
 <script>
 test(function() {
-    var testTarget = document.getElementById('target0');
-    var touch1 = document.createTouch(window, testTarget, 42, 15, 20, 35, 40);
-    assert_equals(touch1.target, testTarget, "touch.target is target0");
-    assert_equals(touch1.identifier, 42, "touch.identifier is requested value");
-    assert_equals(touch1.pageX, 15, "touch.pageX is requested value");
-    assert_equals(touch1.pageY, 20, "touch.pageY is requested value");
-    assert_equals(touch1.screenX, 35, "touch.screenX is requested value");
-    assert_equals(touch1.screenY, 40, "touch.screenY is requested value");
-}, "document.createTouch exists and creates a Touch object with requested properties");
-
-test(function() {
     var touchList = document.createTouchList();
     assert_equals(touchList.length, 0, "touchList.length is 0");
     check_TouchList_object(touchList);
@@ -27,7 +16,7 @@
 
 test(function() {
     var testTarget = document.getElementById('target0');
-    var touch1 = document.createTouch(window, testTarget, 42, 15, 20, 35, 40);
+    var touch1 = new Touch({identifier: 123, target: testTarget});
     var touchList = document.createTouchList(touch1);
     assert_equals(touchList.length, 1, "touchList.length is 1");
     assert_equals(touchList.item(0), touch1, "touchList.item(0) is touch1");
@@ -36,8 +25,8 @@
 
 test(function() {
     var testTarget = document.getElementById('target0');
-    var touch1 = document.createTouch(window, testTarget, 42, 15, 20, 35, 40);
-    var touch2 = document.createTouch(window, target0, 44, 25, 30, 45, 50);
+    var touch1 = new Touch({identifier: 123, target: testTarget});
+    var touch2 = new Touch({identifier: 124, target: target0});
     var touchList = document.createTouchList(touch1, touch2);
     assert_equals(touchList.length, 2, "touchList.length is 2");
     assert_equals(touchList.item(0), touch1, "touchList.item(0) is touch1");
diff --git a/touch-events/historical.html b/touch-events/historical.html
index e6fd598..e5db6ef 100644
--- a/touch-events/historical.html
+++ b/touch-events/historical.html
@@ -6,6 +6,22 @@
 <script src="touch-support.js"></script>
 <body>
 <script>
+[
+  "webkitRadiusX",
+  "webkitRadiusY",
+  "webkitRotationAngle",
+  "webkitForce",
+].forEach(function(name) {
+  test(function() {
+    assert_false(name in Touch.prototype,
+                 "Should not be supported on the prototype");
+
+    var touch = new Touch({identifier: 12341, target:document.body});
+    assert_false(name in touch,
+                 "Should not be supported on the instance");
+  }, "Touch::" + name);
+});
+
 test(function() {
   assert_false("identifiedTouch" in TouchList.prototype,
                "Should not be supported on the prototype");
@@ -14,4 +30,21 @@
   assert_false("identifiedTouch" in touchList,
                "Should not be supported on the instance");
 }, "TouchList::identifiedTouch");
+
+test(function() {
+  assert_false("initTouchEvent" in TouchEvent.prototype,
+               "Should not be supported on the prototype");
+
+  var touchEvent = document.createEvent("touchevent");
+  assert_false("initTouchEvent" in touchEvent,
+               "Should not be supported on the instance");
+}, "TouchEvent::initTouchEvent");
+
+test(function() {
+  assert_false("createTouch" in Document.prototype,
+               "Should not be supported on the prototype");
+
+  assert_false("createTouch" in document,
+               "Should not be supported on the instance");
+}, "Document::createTouch");
 </script>
diff --git a/touch-events/multi-touch-interactions-manual.html b/touch-events/multi-touch-interactions-manual.html
index e9835d9..45e53af 100644
--- a/touch-events/multi-touch-interactions-manual.html
+++ b/touch-events/multi-touch-interactions-manual.html
@@ -1,7 +1,7 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-	Test cases for Touch Events v1 Recommendation
+    Test cases for Touch Events v1 Recommendation
     http://www.w3.org/TR/touch-events/
 
     These tests are based on Mozilla-Nokia-Google's single-touch
@@ -21,29 +21,29 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="multi-touch-interactions.js"></script>
 <style>
-	div {
-		margin: 0em;
-		padding: 1.5em;
-	}
-	#target0 {
-		background: yellow;
-		border: 1px solid orange;
-	}
-	#target1 {
-		background: lightblue;
-		border: 1px solid blue;
-	}
+    div {
+        margin: 0em;
+        padding: 1.5em;
+    }
+    #target0 {
+        background: yellow;
+        border: 1px solid orange;
+    }
+    #target1 {
+        background: lightblue;
+        border: 1px solid blue;
+    }
 </style>
 </head>
 <body onload="run()">
-	<h1>Touch Events: Multi-Touch Interaction Test</h1>
-	<div id="target0">
-		Touch this box with one finger, then another one...
-	</div>
-	<div id="target1">
-		...then drag to this box, then touch with a third finger, and lift all your fingers.
-	</div>
-	<div id="debug"></div>
-	<div id="log"></div>
+    <h1>Touch Events: Multi-Touch Interaction Test</h1>
+    <div id="target0">
+        Touch this box with one finger, then another one...
+    </div>
+    <div id="target1">
+        ...then drag to this box, then touch with a third finger, and lift all your fingers.
+    </div>
+    <div id="debug"></div>
+    <div id="log"></div>
 </body>
 </html>
diff --git a/touch-events/multi-touch-interactions.js b/touch-events/multi-touch-interactions.js
index 5248e6a..955e327 100644
--- a/touch-events/multi-touch-interactions.js
+++ b/touch-events/multi-touch-interactions.js
@@ -4,368 +4,368 @@
 
 function debug_print (x) {
 /* uncomment below statement to show debug messages */
-//	document.getElementById("debug").innerHTML += x;
+// document.getElementById("debug").innerHTML += x;
 }
 
 var starting_elements = {};
 
 function check_list_subset_of_targetlist(list, list_name, targetlist, targetlist_name) {
-	var exist_in_targetlist;
-	for(i=0; i<list.length; i++) {
-		exist_in_targetlist=false;
-		for(j=0; j<targetlist.length; j++)
-			if(list.item(i).identifier==targetlist.item(j).identifier)
-				exist_in_targetlist=true;
+    var exist_in_targetlist;
+    for(i=0; i<list.length; i++) {
+        exist_in_targetlist=false;
+        for(j=0; j<targetlist.length; j++)
+            if(list.item(i).identifier==targetlist.item(j).identifier)
+                exist_in_targetlist=true;
 
-		assert_true(exist_in_targetlist, list_name + ".item("+i+") exists in " + targetlist_name);
-	}
+        assert_true(exist_in_targetlist, list_name + ".item("+i+") exists in " + targetlist_name);
+    }
 }
 
 function check_list_subset_of_two_targetlists(list, list_name, targetlist1, targetlist1_name, targetlist2, targetlist2_name) {
-	var exist_in_targetlists;
-	for(i=0; i<list.length; i++) {
-		exist_in_targetlists=false;
-		for(j=0; j<targetlist1.length; j++)
-			if(list.item(i).identifier==targetlist1.item(j).identifier)
-				exist_in_targetlists=true;
+    var exist_in_targetlists;
+    for(i=0; i<list.length; i++) {
+        exist_in_targetlists=false;
+        for(j=0; j<targetlist1.length; j++)
+            if(list.item(i).identifier==targetlist1.item(j).identifier)
+                exist_in_targetlists=true;
 
-		if(!exist_in_targetlists)
-			for(j=0; j<targetlist2.length; j++)
-				if(list.item(i).identifier==targetlist2.item(j).identifier)
-					exist_in_targetlists=true;
+        if(!exist_in_targetlists)
+            for(j=0; j<targetlist2.length; j++)
+                if(list.item(i).identifier==targetlist2.item(j).identifier)
+                    exist_in_targetlists=true;
 
-		assert_true(exist_in_targetlists, list_name + ".item("+i+") exists in " + targetlist1_name + " or " + targetlist2_name);
-	}
+        assert_true(exist_in_targetlists, list_name + ".item("+i+") exists in " + targetlist1_name + " or " + targetlist2_name);
+    }
 
 }
 
 function is_at_least_one_item_in_targetlist(list, targetlist) {
-	for(i=0; i<list.length; i++)
-		for(j=0; j<targetlist.length; j++)
-			if(list.item(i).identifier==targetlist.item(j).identifier)
-				return true;
+    for(i=0; i<list.length; i++)
+        for(j=0; j<targetlist.length; j++)
+            if(list.item(i).identifier==targetlist.item(j).identifier)
+                return true;
 
-	return false;
+    return false;
 }
 
 function check_no_item_in_targetlist(list, list_name, targetlist, targetlist_name) {
-	for(i=0; i<list.length; i++)
-		for(j=0; j<targetlist.length; j++) {
-			assert_false(list.item(i).identifier==targetlist.item(j).identifier, list_name + ".item("+i+") exists in " + targetlist_name);
-			return;
-		}
+    for(i=0; i<list.length; i++)
+        for(j=0; j<targetlist.length; j++) {
+            assert_false(list.item(i).identifier==targetlist.item(j).identifier, list_name + ".item("+i+") exists in " + targetlist_name);
+            return;
+        }
 }
 
 function check_targets(list, target) {
-	for(i=0; i<list.length; i++)
-		assert_true(list.item(i).target==target, "item(" + i + ").target is element receiving event");
+    for(i=0; i<list.length; i++)
+        assert_true(list.item(i).target==target, "item(" + i + ").target is element receiving event");
 }
 
 function check_starting_elements(list) {
-	for (i=0; i<list.length; i++) {
-		assert_equals(list.item(i).target, starting_elements[list.item(i).identifier], "item(" + i + ").target matches starting element");
-	}
+    for (i=0; i<list.length; i++) {
+        assert_equals(list.item(i).target, starting_elements[list.item(i).identifier], "item(" + i + ").target matches starting element");
+    }
 }
 
 function run() {
-	var target0 = document.getElementById("target0");
-	var target1 = document.getElementById("target1");
+    var target0 = document.getElementById("target0");
+    var target1 = document.getElementById("target1");
 
-	var test_touchstart = async_test("touchstart event received");
-	var test_touchmove = async_test("touchmove event received");
-	var test_touchend = async_test("touchend event received");
-	var test_mousedown = async_test("Interaction with mouse events");
+    var test_touchstart = async_test("touchstart event received");
+    var test_touchmove = async_test("touchmove event received");
+    var test_touchend = async_test("touchend event received");
+    var test_mousedown = async_test("Interaction with mouse events");
 
-	var touchstart_received = 0;
-	var touchmove_received = 0;
-	var touchend_received = 0;
-	var touchstart_identifier;
+    var touchstart_received = 0;
+    var touchmove_received = 0;
+    var touchend_received = 0;
+    var touchstart_identifier;
 
-	// last received touch lists for comparison
-	var last_touches;
-	var last_targetTouches={};
-	var last_changedTouches={};
+    // last received touch lists for comparison
+    var last_touches;
+    var last_targetTouches={};
+    var last_changedTouches={};
 
-	on_event(window, "touchstart", function onTouchStart(ev) {
-		// process event only if it's targeted at target0 or target1
-		if(ev.target != target0 && ev.target != target1 )
-			return;
+    on_event(window, "touchstart", function onTouchStart(ev) {
+        // process event only if it's targeted at target0 or target1
+        if(ev.target != target0 && ev.target != target1 )
+            return;
 
-		ev.preventDefault();
+        ev.preventDefault();
 
-		if(!touchstart_received) {
-			// Check event ordering TA: 1.6.1
-			test_touchstart.step(function() {
-				assert_true(touchmove_received==0, "touchstart precedes touchmove");
-				assert_true(touchend_received==0, "touchstart precedes touchend");
-			});
-			test_touchstart.done();
-			test_mousedown.done(); // If we got here, then the mouse event test is not needed.
-		}
-		touchstart_received++;
+        if(!touchstart_received) {
+            // Check event ordering TA: 1.6.1
+            test_touchstart.step(function() {
+                assert_true(touchmove_received==0, "touchstart precedes touchmove");
+                assert_true(touchend_received==0, "touchstart precedes touchend");
+            });
+            test_touchstart.done();
+            test_mousedown.done(); // If we got here, then the mouse event test is not needed.
+        }
+        touchstart_received++;
 
-		// TA: 1.3.2.2, 1.3.2.4
-		test(function() {
-			assert_true(ev.changedTouches.length >= 1, "changedTouches.length is at least 1");
-			assert_true(ev.changedTouches.length <= ev.touches.length, "changedTouches.length is smaller than touches.length");
-			check_list_subset_of_targetlist(ev.changedTouches, "changedTouches", ev.touches, "touches");
-		}, "touchstart #" + touchstart_received + ": changedTouches is a subset of touches");
+        // TA: 1.3.2.2, 1.3.2.4
+        test(function() {
+            assert_true(ev.changedTouches.length >= 1, "changedTouches.length is at least 1");
+            assert_true(ev.changedTouches.length <= ev.touches.length, "changedTouches.length is smaller than touches.length");
+            check_list_subset_of_targetlist(ev.changedTouches, "changedTouches", ev.touches, "touches");
+        }, "touchstart #" + touchstart_received + ": changedTouches is a subset of touches");
 
-		// TA: 1.3.3.2, 1.3.3.3
-		test(function() {
-			assert_true(ev.targetTouches.length >= 1, "targetTouches.length is at least 1");
-			assert_true(ev.targetTouches.length <= ev.touches.length, "targetTouches.length is smaller than touches.length");
-			check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.touches, "touches");
-		}, "touchstart #" + touchstart_received + ": targetTouches is a subset of touches");
+        // TA: 1.3.3.2, 1.3.3.3
+        test(function() {
+            assert_true(ev.targetTouches.length >= 1, "targetTouches.length is at least 1");
+            assert_true(ev.targetTouches.length <= ev.touches.length, "targetTouches.length is smaller than touches.length");
+            check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.touches, "touches");
+        }, "touchstart #" + touchstart_received + ": targetTouches is a subset of touches");
 
-		// TA: 1.3.3.9
-		test(function() {
-			check_targets(ev.targetTouches, ev.target);
-		}, "touchstart #" + touchstart_received + ": targets of targetTouches are correct");
+        // TA: 1.3.3.9
+        test(function() {
+            check_targets(ev.targetTouches, ev.target);
+        }, "touchstart #" + touchstart_received + ": targets of targetTouches are correct");
 
-		// TA: 1.3.4.2
-		test(function() {
-			assert_true(ev.touches.length >= 1, "touches.length is at least 1");
-		}, "touchstart #" + touchstart_received + ": touches.length is valid");
+        // TA: 1.3.4.2
+        test(function() {
+            assert_true(ev.touches.length >= 1, "touches.length is at least 1");
+        }, "touchstart #" + touchstart_received + ": touches.length is valid");
 
-		if(touchstart_received == 1) {
-			// TA: 1.3.3.5, 1.3.3.7
-			test(function() {
-				assert_true(ev.targetTouches.length <= ev.changedTouches.length, "targetTouches.length is smaller than changedTouches.length");
-				check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.changedTouches, "changedTouches");
-			}, "touchstart #" + touchstart_received + ": targetTouches is a subset of changedTouches");
+        if(touchstart_received == 1) {
+            // TA: 1.3.3.5, 1.3.3.7
+            test(function() {
+                assert_true(ev.targetTouches.length <= ev.changedTouches.length, "targetTouches.length is smaller than changedTouches.length");
+                check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.changedTouches, "changedTouches");
+            }, "touchstart #" + touchstart_received + ": targetTouches is a subset of changedTouches");
 
-			// TA: 1.3.4.3
-			test(function() {
-				assert_true(ev.touches.length==ev.changedTouches.length, "touches and changedTouches have the same length");
-			}, "touchstart #" + touchstart_received + ": touches and changedTouches have the same length");
-		} else {
-			// TA: 1.3.3.6
-			test(function() {
-				var diff_in_targetTouches = ev.targetTouches.length - (last_targetTouches[ev.target.id] ? last_targetTouches[ev.target.id].length : 0);
-				assert_true(diff_in_targetTouches > 0, "targetTouches.length is larger than last received targetTouches.length");
-				assert_true(diff_in_targetTouches <= ev.changedTouches.length, "change in targetTouches.length is smaller than changedTouches.length");
-			}, "touchstart #" + touchstart_received + ": change in targetTouches.length is valid");
+            // TA: 1.3.4.3
+            test(function() {
+                assert_true(ev.touches.length==ev.changedTouches.length, "touches and changedTouches have the same length");
+            }, "touchstart #" + touchstart_received + ": touches and changedTouches have the same length");
+        } else {
+            // TA: 1.3.3.6
+            test(function() {
+                var diff_in_targetTouches = ev.targetTouches.length - (last_targetTouches[ev.target.id] ? last_targetTouches[ev.target.id].length : 0);
+                assert_true(diff_in_targetTouches > 0, "targetTouches.length is larger than last received targetTouches.length");
+                assert_true(diff_in_targetTouches <= ev.changedTouches.length, "change in targetTouches.length is smaller than changedTouches.length");
+            }, "touchstart #" + touchstart_received + ": change in targetTouches.length is valid");
 
-			// TA: 1.3.3.8
-			test(function() {
-				assert_true(is_at_least_one_item_in_targetlist(ev.targetTouches, ev.changedTouches), "at least one item of targetTouches is in changedTouches");
-			}, "touchstart #" + touchstart_received + ": at least one targetTouches item in changedTouches");
+            // TA: 1.3.3.8
+            test(function() {
+                assert_true(is_at_least_one_item_in_targetlist(ev.targetTouches, ev.changedTouches), "at least one item of targetTouches is in changedTouches");
+            }, "touchstart #" + touchstart_received + ": at least one targetTouches item in changedTouches");
 
-			// TA: 1.3.4.4
-			test(function() {
-				var diff_in_touches = ev.touches.length - last_touches.length;
-				assert_true(diff_in_touches > 0, "touches.length is larger than last received touches.length");
-				assert_true(diff_in_touches == ev.changedTouches.length, "change in touches.length equals changedTouches.length");
-			}, "touchstart #" + touchstart_received + ": change in touches.length is valid");
+            // TA: 1.3.4.4
+            test(function() {
+                var diff_in_touches = ev.touches.length - last_touches.length;
+                assert_true(diff_in_touches > 0, "touches.length is larger than last received touches.length");
+                assert_true(diff_in_touches == ev.changedTouches.length, "change in touches.length equals changedTouches.length");
+            }, "touchstart #" + touchstart_received + ": change in touches.length is valid");
 
-			// TA: 1.3.4.5
-			test(function() {
-				check_list_subset_of_two_targetlists(ev.touches, "touches", ev.changedTouches, "changedTouches", last_touches, "last touches");
-			}, "touchstart #" + touchstart_received + ": touches is subset of {changedTouches, last received touches}");
-		}
+            // TA: 1.3.4.5
+            test(function() {
+                check_list_subset_of_two_targetlists(ev.touches, "touches", ev.changedTouches, "changedTouches", last_touches, "last touches");
+            }, "touchstart #" + touchstart_received + ": touches is subset of {changedTouches, last received touches}");
+        }
 
-		// save starting element of each new touch point
-		for (i=0; i<ev.changedTouches.length; i++) {
-			starting_elements[ev.changedTouches.item(i).identifier] = ev.changedTouches.item(i).target;
-		}
+        // save starting element of each new touch point
+        for (i=0; i<ev.changedTouches.length; i++) {
+            starting_elements[ev.changedTouches.item(i).identifier] = ev.changedTouches.item(i).target;
+        }
 
-		last_touches = ev.touches;
-		last_targetTouches[ev.target.id] = ev.targetTouches;
-		last_changedTouches = {};	// changedTouches are only saved for touchend events
-	});
+        last_touches = ev.touches;
+        last_targetTouches[ev.target.id] = ev.targetTouches;
+        last_changedTouches = {};    // changedTouches are only saved for touchend events
+    });
 
-	on_event(window, "touchmove", function onTouchMove(ev) {
-		// process event only if it's targeted at target0 or target1
-		if(ev.target != target0 && ev.target != target1 )
-			return;
+    on_event(window, "touchmove", function onTouchMove(ev) {
+        // process event only if it's targeted at target0 or target1
+        if(ev.target != target0 && ev.target != target1 )
+            return;
 
-		ev.preventDefault();
+        ev.preventDefault();
 
-		// TA: 1.6.1
-		test_touchmove.step(function() {
-			assert_true(touchstart_received>0, "touchmove follows touchstart");
-			// assert_false(touchend_received, "touchmove precedes touchend"); // this applies to scenario tests
-		});
-		test_touchmove.done();
+        // TA: 1.6.1
+        test_touchmove.step(function() {
+            assert_true(touchstart_received>0, "touchmove follows touchstart");
+            // assert_false(touchend_received, "touchmove precedes touchend"); // this applies to scenario tests
+        });
+        test_touchmove.done();
 
-		touchmove_received++;
+        touchmove_received++;
 
-		// do the detailed checking only for a few times
-		if(touchmove_received<6) {
-			// TA: 1.4.2.2, 1.4.2.4
-			test(function() {
-				assert_true(ev.changedTouches.length >= 1, "changedTouches.length is at least 1");
-				assert_true(ev.changedTouches.length <= ev.touches.length, "changedTouches.length is smaller than touches.length");
-				check_list_subset_of_targetlist(ev.changedTouches, "changedTouches", ev.touches, "touches");
-			}, "touchmove #" + touchmove_received + ": changedTouches is a subset of touches");
+        // do the detailed checking only for a few times
+        if(touchmove_received<6) {
+            // TA: 1.4.2.2, 1.4.2.4
+            test(function() {
+                assert_true(ev.changedTouches.length >= 1, "changedTouches.length is at least 1");
+                assert_true(ev.changedTouches.length <= ev.touches.length, "changedTouches.length is smaller than touches.length");
+                check_list_subset_of_targetlist(ev.changedTouches, "changedTouches", ev.touches, "touches");
+            }, "touchmove #" + touchmove_received + ": changedTouches is a subset of touches");
 
-			// TA: 1.4.3.2, 1.4.3.4
-			test(function() {
-				assert_true(ev.targetTouches.length >= 1, "targetTouches.length is at least 1");
-				assert_true(ev.targetTouches.length <= ev.touches.length, "targetTouches.length is smaller than touches.length");
-				check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.touches, "touches");
-			}, "touchmove #" + touchmove_received + ": targetTouches is a subset of touches");
+            // TA: 1.4.3.2, 1.4.3.4
+            test(function() {
+                assert_true(ev.targetTouches.length >= 1, "targetTouches.length is at least 1");
+                assert_true(ev.targetTouches.length <= ev.touches.length, "targetTouches.length is smaller than touches.length");
+                check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.touches, "touches");
+            }, "touchmove #" + touchmove_received + ": targetTouches is a subset of touches");
 
-			// TA: 1.4.3.6
-			test(function() {
-				assert_true(is_at_least_one_item_in_targetlist(ev.targetTouches, ev.changedTouches), "at least one item of targetTouches is in changedTouches");
-			}, "touchmove #" + touchmove_received + ": at least one targetTouches item in changedTouches");
+            // TA: 1.4.3.6
+            test(function() {
+                assert_true(is_at_least_one_item_in_targetlist(ev.targetTouches, ev.changedTouches), "at least one item of targetTouches is in changedTouches");
+            }, "touchmove #" + touchmove_received + ": at least one targetTouches item in changedTouches");
 
-			// TA: 1.4.3.8
-			test(function() {
-				check_targets(ev.targetTouches, ev.target);
-			}, "touchmove #" + touchmove_received + ": targets of targetTouches are correct");
+            // TA: 1.4.3.8
+            test(function() {
+                check_targets(ev.targetTouches, ev.target);
+            }, "touchmove #" + touchmove_received + ": targets of targetTouches are correct");
 
-			// TA: 1.4.4.2
-			test(function() {
-				assert_true(ev.touches.length==last_touches.length, "length of touches is same as length of last received touches");
-				check_list_subset_of_targetlist(ev.touches, "touches", last_touches, "last received touches");
-			}, "touchmove #" + touchmove_received + ": touches must be same as last received touches");
+            // TA: 1.4.4.2
+            test(function() {
+                assert_true(ev.touches.length==last_touches.length, "length of touches is same as length of last received touches");
+                check_list_subset_of_targetlist(ev.touches, "touches", last_touches, "last received touches");
+            }, "touchmove #" + touchmove_received + ": touches must be same as last received touches");
 
-			// TA: 1.6.3
-			check_starting_elements(ev.changedTouches);
-		}
+            // TA: 1.6.3
+            check_starting_elements(ev.changedTouches);
+        }
 
-		last_touches = ev.touches;
-		last_targetTouches[ev.target.id] = ev.targetTouches;
-		last_changedTouches = {};	// changedTouches are only saved for touchend events
-	});
+        last_touches = ev.touches;
+        last_targetTouches[ev.target.id] = ev.targetTouches;
+        last_changedTouches = {};    // changedTouches are only saved for touchend events
+    });
 
-	on_event(window, "touchend", function onTouchEnd(ev) {
-		// process event only if it's targeted at target0 or target1
-		if(ev.target != target0 && ev.target != target1 )
-			return;
+    on_event(window, "touchend", function onTouchEnd(ev) {
+        // process event only if it's targeted at target0 or target1
+        if(ev.target != target0 && ev.target != target1 )
+            return;
 
-		test_touchend.step(function() {
-			assert_true(touchstart_received>0, "touchend follows touchstart");
-		});
-		test_touchend.done();
+        test_touchend.step(function() {
+            assert_true(touchstart_received>0, "touchend follows touchstart");
+        });
+        test_touchend.done();
 
-		touchend_received++;
+        touchend_received++;
 
-		debug_print("touchend #" + touchend_received + ":<br>");
-		debug_print("changedTouches.length=" + ev.changedTouches.length + "<br>");
-		debug_print("targetTouches.length=" + ev.targetTouches.length + "<br>");
-		debug_print("touches.length=" + ev.touches.length + "<br>");
-		for(i=0; i<ev.changedTouches.length; i++)
-			debug_print("changedTouches.item(" + i + ").target=" + ev.changedTouches.item(i).target.id + "<br>");
+        debug_print("touchend #" + touchend_received + ":<br>");
+        debug_print("changedTouches.length=" + ev.changedTouches.length + "<br>");
+        debug_print("targetTouches.length=" + ev.targetTouches.length + "<br>");
+        debug_print("touches.length=" + ev.touches.length + "<br>");
+        for(i=0; i<ev.changedTouches.length; i++)
+            debug_print("changedTouches.item(" + i + ").target=" + ev.changedTouches.item(i).target.id + "<br>");
 
-		// TA: 1.5.2.2
-		test(function() {
-			assert_true(ev.changedTouches.length >= 1, "changedTouches.length is at least 1");
-		}, "touchend #" + touchend_received + ": length of changedTouches is valid");
+        // TA: 1.5.2.2
+        test(function() {
+            assert_true(ev.changedTouches.length >= 1, "changedTouches.length is at least 1");
+        }, "touchend #" + touchend_received + ": length of changedTouches is valid");
 
-		// TA: 1.5.2.3
-		test(function() {
-			check_list_subset_of_targetlist(ev.changedTouches, "changedTouches", last_touches, "last received touches");
-		}, "touchend #" + touchend_received + ": changedTouches is a subset of last received touches");
+        // TA: 1.5.2.3
+        test(function() {
+            check_list_subset_of_targetlist(ev.changedTouches, "changedTouches", last_touches, "last received touches");
+        }, "touchend #" + touchend_received + ": changedTouches is a subset of last received touches");
 
-		// TA: 1.5.2.4, 1.5.2.5
-		test(function() {
-			check_no_item_in_targetlist(ev.changedTouches, "changedTouches", ev.touches, "touches");
-			check_no_item_in_targetlist(ev.changedTouches, "changedTouches", ev.targetTouches, "targetTouches");
-		}, "touchend #" + touchend_received + ": no item in changedTouches are in touches or targetTouches");
+        // TA: 1.5.2.4, 1.5.2.5
+        test(function() {
+            check_no_item_in_targetlist(ev.changedTouches, "changedTouches", ev.touches, "touches");
+            check_no_item_in_targetlist(ev.changedTouches, "changedTouches", ev.targetTouches, "targetTouches");
+        }, "touchend #" + touchend_received + ": no item in changedTouches are in touches or targetTouches");
 
-		// TA: 1.5.2.6
-		test(function() {
-			var found=false;
-			for (i=0; i<ev.changedTouches.length; i++)
-				if (ev.changedTouches.item(i).target == ev.target)
-					found=true;
-			assert_true(found, "at least one item in changedTouches has matching target");
-		}, "touchend #" + touchend_received + ": at least one item in changedTouches targeted at this element");
+        // TA: 1.5.2.6
+        test(function() {
+            var found=false;
+            for (i=0; i<ev.changedTouches.length; i++)
+                if (ev.changedTouches.item(i).target == ev.target)
+                    found=true;
+            assert_true(found, "at least one item in changedTouches has matching target");
+        }, "touchend #" + touchend_received + ": at least one item in changedTouches targeted at this element");
 
-		// TA: 1.5.3.2, 1.5.3.3
-		test(function() {
-			assert_true(ev.targetTouches.length >= 0, "targetTouches.length is non-negative");
-			assert_true(ev.targetTouches.length <= ev.touches.length, "targetTouches.length is smaller than touches.length");
-			check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.touches, "touches");
-		}, "touchend #" + touchend_received + ": targetTouches is a subset of touches");
+        // TA: 1.5.3.2, 1.5.3.3
+        test(function() {
+            assert_true(ev.targetTouches.length >= 0, "targetTouches.length is non-negative");
+            assert_true(ev.targetTouches.length <= ev.touches.length, "targetTouches.length is smaller than touches.length");
+            check_list_subset_of_targetlist(ev.targetTouches, "targetTouches", ev.touches, "touches");
+        }, "touchend #" + touchend_received + ": targetTouches is a subset of touches");
 
-		// TA: 1.5.3.5 (new)
-		test(function() {
-			check_targets(ev.targetTouches, ev.target);
-		}, "touchend #" + touchend_received + ": targets of targetTouches are correct");
+        // TA: 1.5.3.5 (new)
+        test(function() {
+            check_targets(ev.targetTouches, ev.target);
+        }, "touchend #" + touchend_received + ": targets of targetTouches are correct");
 
-		// In some cases, when multiple touch points are released simultaneously
-		// the UA would dispatch the "same" touchend event (same changedTouches, same touches, but possibly different targetTouches)
-		// to each of the elements that are starting elements of the released touch points.
-		// in these situations, the subsequent events are exempt from TA 1.5.3.4 and 1.5.4.2
-		var same_event_as_last = false;
-		if (last_changedTouches && last_changedTouches.length==ev.changedTouches.length) {
-			same_event_as_last = true; // assume true until proven otherwise
-			for (i=0; i<last_changedTouches.length; i++) {
-				var match = false;
-				for (j=0; j<ev.changedTouches.length; j++)
-					if (last_changedTouches.item(i) == ev.changedTouches.item(j)) {
-						match = true;
-						break;
-					}
-				if (!match)
-					same_event_as_last = false;
-			}
-		}
+        // In some cases, when multiple touch points are released simultaneously
+        // the UA would dispatch the "same" touchend event (same changedTouches, same touches, but possibly different targetTouches)
+        // to each of the elements that are starting elements of the released touch points.
+        // in these situations, the subsequent events are exempt from TA 1.5.3.4 and 1.5.4.2
+        var same_event_as_last = false;
+        if (last_changedTouches && last_changedTouches.length==ev.changedTouches.length) {
+            same_event_as_last = true; // assume true until proven otherwise
+            for (i=0; i<last_changedTouches.length; i++) {
+                var match = false;
+                for (j=0; j<ev.changedTouches.length; j++)
+                    if (last_changedTouches.item(i) == ev.changedTouches.item(j)) {
+                        match = true;
+                        break;
+                    }
+                if (!match)
+                    same_event_as_last = false;
+            }
+        }
 
-		if (!same_event_as_last) {
-			// TA: 1.5.3.4
-			// Getting semi-random failures on this and 1.5.4.2.
-			// See 1.5.4.2. Not sure if it's the same issue...
-			test(function() {
-				assert_true(last_targetTouches[ev.target.id].length > 0, "last received targetTouches.length is not zero");
-				var diff_in_targetTouches = last_targetTouches[ev.target.id].length - ev.targetTouches.length;
-				debug_print("diff_in_targetTouches=" + diff_in_targetTouches + "<br>");
-				assert_true(diff_in_targetTouches > 0, "targetTouches.length is smaller than last received targetTouches.length");
-				assert_true(diff_in_targetTouches <= ev.changedTouches.length, "change in targetTouches.length is smaller than changedTouches.length");
-			}, "touchend #" + touchend_received + ": change in targetTouches.length is valid");
+        if (!same_event_as_last) {
+            // TA: 1.5.3.4
+            // Getting semi-random failures on this and 1.5.4.2.
+            // See 1.5.4.2. Not sure if it's the same issue...
+            test(function() {
+                assert_true(last_targetTouches[ev.target.id].length > 0, "last received targetTouches.length is not zero");
+                var diff_in_targetTouches = last_targetTouches[ev.target.id].length - ev.targetTouches.length;
+                debug_print("diff_in_targetTouches=" + diff_in_targetTouches + "<br>");
+                assert_true(diff_in_targetTouches > 0, "targetTouches.length is smaller than last received targetTouches.length");
+                assert_true(diff_in_targetTouches <= ev.changedTouches.length, "change in targetTouches.length is smaller than changedTouches.length");
+            }, "touchend #" + touchend_received + ": change in targetTouches.length is valid");
 
-			// TA: 1.5.4.2
-			// Getting semi-random failures on this and 1.5.3.4.
-			// It looks like if fingers are lifted simultaneously, the "same" touchend event can be dispatched to two target elements
-			// but adapted to the element (same touches, changedTouches but different targetTouches).
-			// When one event is processed after another, ev.touches would end up being identical to last_touches, leading  to failure.
-			// Question is why done() does not stop the processing of the latter event.
-			test(function() {
-				assert_true(last_touches.length > 0, "last received touches.length is not zero");
-				var diff_in_touches = last_touches.length - ev.touches.length;
-				debug_print("diff_in_touches=" + diff_in_touches + "<br>");
-				assert_true(diff_in_touches > 0, "touches.length is smaller than last received touches.length");
-				assert_equals(diff_in_touches, ev.changedTouches.length, "change in touches.length equals changedTouches.length");
-			}, "touchend #" + touchend_received + ": change in touches.length is valid");
-		}
+            // TA: 1.5.4.2
+            // Getting semi-random failures on this and 1.5.3.4.
+            // It looks like if fingers are lifted simultaneously, the "same" touchend event can be dispatched to two target elements
+            // but adapted to the element (same touches, changedTouches but different targetTouches).
+            // When one event is processed after another, ev.touches would end up being identical to last_touches, leading  to failure.
+            // Question is why done() does not stop the processing of the latter event.
+            test(function() {
+                assert_true(last_touches.length > 0, "last received touches.length is not zero");
+                var diff_in_touches = last_touches.length - ev.touches.length;
+                debug_print("diff_in_touches=" + diff_in_touches + "<br>");
+                assert_true(diff_in_touches > 0, "touches.length is smaller than last received touches.length");
+                assert_equals(diff_in_touches, ev.changedTouches.length, "change in touches.length equals changedTouches.length");
+            }, "touchend #" + touchend_received + ": change in touches.length is valid");
+        }
 
-		// TA: 1.6.4
-		debug_print("touchend #" + touchend_received + ": TA 1.6.4<br>");
-		test(function() {
-			check_starting_elements(ev.changedTouches);
-		}, "touchend #" + touchend_received + ": event dispatched to correct element<br>");
+        // TA: 1.6.4
+        debug_print("touchend #" + touchend_received + ": TA 1.6.4<br>");
+        test(function() {
+            check_starting_elements(ev.changedTouches);
+        }, "touchend #" + touchend_received + ": event dispatched to correct element<br>");
 
-		debug_print("touchend #" + touchend_received + ": saving touch lists<br>");
+        debug_print("touchend #" + touchend_received + ": saving touch lists<br>");
 
-		last_touches = ev.touches;
-		last_targetTouches[ev.target.id] = ev.targetTouches;
-		last_changedTouches = ev.changedTouches;
+        last_touches = ev.touches;
+        last_targetTouches[ev.target.id] = ev.targetTouches;
+        last_changedTouches = ev.changedTouches;
 
-		debug_print("touchend #" + touchend_received + ": done<br>");
-		if(ev.touches.length==0)
-			done();
-	});
+        debug_print("touchend #" + touchend_received + ": done<br>");
+        if(ev.touches.length==0)
+            done();
+    });
 
-	on_event(target0, "mousedown", function onMouseDown(ev) {
-		test_mousedown.step(function() {
-			assert_true(touchstart_received,
-				"The touchstart event must be dispatched before any mouse " +
-				"events. (If this fails, it might mean that the user agent does " +
-				"not implement W3C touch events at all.)"
-			);
-		});
-		test_mousedown.done();
+    on_event(target0, "mousedown", function onMouseDown(ev) {
+        test_mousedown.step(function() {
+            assert_true(touchstart_received,
+                "The touchstart event must be dispatched before any mouse " +
+                "events. (If this fails, it might mean that the user agent does " +
+                "not implement W3C touch events at all.)"
+            );
+        });
+        test_mousedown.done();
 
-		if (!touchstart_received) {
-			// Abort the tests.  If touch events are not supported, then most of
-			// the other event handlers will never be called, and the test will
-			// time out with misleading results.
-			done();
-		}
-	});
+        if (!touchstart_received) {
+            // Abort the tests.  If touch events are not supported, then most of
+            // the other event handlers will never be called, and the test will
+            // time out with misleading results.
+            done();
+        }
+    });
 }
diff --git a/touch-events/multi-touch-interfaces-manual.html b/touch-events/multi-touch-interfaces-manual.html
index 6d0f7d4..e9f1b91 100644
--- a/touch-events/multi-touch-interfaces-manual.html
+++ b/touch-events/multi-touch-interfaces-manual.html
@@ -19,6 +19,7 @@
 <title>Touch Events Multi-Touch Interface Tests</title>
 <meta name="viewport" content="width=device-width">
 <script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 <script>
     setup({explicit_done: true});
 
@@ -147,127 +148,127 @@
             default:
                 break;
             }
-       });
+        });
     }
 
     function is_touch_over_element(touch, element) {
-      var bounds = element.getBoundingClientRect();
-      return touch.pageX >= bounds.left && touch.pageX <= bounds.right &&
-          touch.pageY >= bounds.top && touch.pageY <= bounds.bottom;
+        var bounds = element.getBoundingClientRect();
+        return touch.pageX >= bounds.left && touch.pageX <= bounds.right &&
+               touch.pageY >= bounds.top && touch.pageY <= bounds.bottom;
     }
 
     function check_touch_clientXY(touch) {
-      assert_equals(touch.clientX, touch.pageX - window.pageXOffset, "touch.clientX is touch.pageX - window.pageXOffset.");
-      assert_equals(touch.clientY, touch.pageY - window.pageYOffset, "touch.clientY is touch.pageY - window.pageYOffset.");
+        assert_equals(touch.clientX, touch.pageX - window.pageXOffset, "touch.clientX is touch.pageX - window.pageXOffset.");
+        assert_equals(touch.clientY, touch.pageY - window.pageYOffset, "touch.clientY is touch.pageY - window.pageYOffset.");
     }
 
     function run() {
-		var target0 = document.getElementById("target0");
-		var target1 = document.getElementById("target1");
+        var target0 = document.getElementById("target0");
+        var target1 = document.getElementById("target1");
 
-		var test_touchstart = async_test("touchstart event received");
-		var test_touchmove = async_test("touchmove event received");
-		var test_touchend = async_test("touchend event received");
-		var test_mousedown = async_test("Interaction with mouse events");
+        var test_touchstart = async_test("touchstart event received");
+        var test_touchmove = async_test("touchmove event received");
+        var test_touchend = async_test("touchend event received");
+        var test_mousedown = async_test("Interaction with mouse events");
 
-		var touchstart_received = 0;
-		var touchmove_received = false;
-		var touchend_received = false;
-		var invalid_touchmove_received = false;
+        var touchstart_received = 0;
+        var touchmove_received = false;
+        var touchend_received = false;
+        var invalid_touchmove_received = false;
 
-		on_event(target0, "touchstart", function onTouchStart(ev) {
-			ev.preventDefault();
+        on_event(target0, "touchstart", function onTouchStart(ev) {
+            ev.preventDefault();
 
-			if(!touchstart_received) {
-				// Check event ordering TA: 1.6.2
-				test_touchstart.step(function() {
-					assert_false(touchmove_received, "touchstart precedes touchmove");
-					assert_false(touchend_received, "touchstart precedes touchend");
-				});
-				test_touchstart.done();
-				test_mousedown.done(); // If we got here, then the mouse event test is not needed.
-			}
+            if(!touchstart_received) {
+                // Check event ordering TA: 1.6.2
+                test_touchstart.step(function() {
+                    assert_false(touchmove_received, "touchstart precedes touchmove");
+                    assert_false(touchend_received, "touchstart precedes touchend");
+                });
+                test_touchstart.done();
+                test_mousedown.done(); // If we got here, then the mouse event test is not needed.
+            }
 
-			if(++touchstart_received <= 2)
-				check_TouchEvent(ev);
-		});
+            if(++touchstart_received <= 2)
+                check_TouchEvent(ev);
+        });
 
-		on_event(target0, "touchmove", function onTouchMove(ev) {
-			ev.preventDefault();
+        on_event(target0, "touchmove", function onTouchMove(ev) {
+            ev.preventDefault();
 
-			if (touchmove_received)
-			  return;
-			touchmove_received = true;
+            if (touchmove_received)
+              return;
+            touchmove_received = true;
 
-			test_touchmove.step(function() {
-				assert_true(touchstart_received>0, "touchmove follows touchstart");
-				assert_false(touchend_received, "touchmove precedes touchend");
-			});
-			test_touchmove.done();
+            test_touchmove.step(function() {
+                assert_true(touchstart_received>0, "touchmove follows touchstart");
+                assert_false(touchend_received, "touchmove precedes touchend");
+            });
+            test_touchmove.done();
 
-			check_TouchEvent(ev);
-		});
+            check_TouchEvent(ev);
+        });
 
-		on_event(target1, "touchmove", function onTouchMove(ev) {
-			invalid_touchmove_received = true;
-		});
+        on_event(target1, "touchmove", function onTouchMove(ev) {
+            invalid_touchmove_received = true;
+        });
 
-		on_event(window, "touchend", function onTouchEnd(ev) {
-			touchend_received = true;
+        on_event(window, "touchend", function onTouchEnd(ev) {
+            touchend_received = true;
 
-			test_touchend.step(function() {
-				assert_true(touchstart_received>0, "touchend follows touchstart");
-				assert_true(touchmove_received, "touchend follows touchmove");
-				assert_false(invalid_touchmove_received, "touchmove dispatched to correct target");
-			});
-			test_touchend.done();
+            test_touchend.step(function() {
+                assert_true(touchstart_received>0, "touchend follows touchstart");
+                assert_true(touchmove_received, "touchend follows touchmove");
+                assert_false(invalid_touchmove_received, "touchmove dispatched to correct target");
+            });
+            test_touchend.done();
 
-			check_TouchEvent(ev);
-			done();
-		});
+            check_TouchEvent(ev);
+            done();
+        });
 
-		on_event(target0, "mousedown", function onMouseDown(ev) {
-			test_mousedown.step(function() {
-				assert_true(touchstart_received,
-					"The touchstart event must be dispatched before any mouse " +
-					"events. (If this fails, it might mean that the user agent does " +
-					"not implement W3C touch events at all.)"
-				);
-			});
-			test_mousedown.done();
+        on_event(target0, "mousedown", function onMouseDown(ev) {
+            test_mousedown.step(function() {
+                assert_true(touchstart_received,
+                    "The touchstart event must be dispatched before any mouse " +
+                    "events. (If this fails, it might mean that the user agent does " +
+                    "not implement W3C touch events at all.)"
+                );
+            });
+            test_mousedown.done();
 
-			if (!touchstart_received) {
-				// Abort the tests.  If touch events are not supported, then most of
-				// the other event handlers will never be called, and the test will
-				// time out with misleading results.
-				done();
-			}
-		});
-	}
+            if (!touchstart_received) {
+                // Abort the tests.  If touch events are not supported, then most of
+                // the other event handlers will never be called, and the test will
+                // time out with misleading results.
+                done();
+            }
+        });
+    }
 </script>
 <style>
-	div {
-		margin: 0em;
-		padding: 2em;
-	}
-	#target0 {
-		background: yellow;
-		border: 1px solid orange;
-	}
-	#target1 {
-		background: lightblue;
-		border: 1px solid blue;
-	}
+    div {
+        margin: 0em;
+        padding: 2em;
+    }
+    #target0 {
+        background: yellow;
+        border: 1px solid orange;
+    }
+    #target1 {
+        background: lightblue;
+        border: 1px solid blue;
+    }
 </style>
 </head>
 <body onload="run()">
-	<h1>Touch Events: multi-touch interface tests</h1>
-	<div id="target0">
-		Touch this box with one finger, then another one...
-	</div>
-	<div id="target1">
-		...then drag to this box and lift your fingers.
-	</div>
-	<div id="log"></div>
+    <h1>Touch Events: multi-touch interface tests</h1>
+    <div id="target0">
+        Touch this box with one finger, then another one...
+    </div>
+    <div id="target1">
+        ...then drag to this box and lift your fingers.
+    </div>
+    <div id="log"></div>
 </body>
 </html>
diff --git a/touch-events/single-touch-manual.html b/touch-events/single-touch-manual.html
index f1f643c..1b47a64 100644
--- a/touch-events/single-touch-manual.html
+++ b/touch-events/single-touch-manual.html
@@ -15,6 +15,7 @@
   <title>Touch Events Single Touch Tests</title>
   <meta name="viewport" content="width=device-width">
   <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
   <script>
     setup({explicit_done: true});
 
diff --git a/trusted-types/OWNERS b/trusted-types/OWNERS
new file mode 100644
index 0000000..12f907c
--- /dev/null
+++ b/trusted-types/OWNERS
@@ -0,0 +1 @@
+@mikewest
diff --git a/uievents/mouse/layout_change_should_fire_mouseover-manual.html b/uievents/mouse/layout_change_should_fire_mouseover-manual.html
new file mode 100644
index 0000000..f36fadb
--- /dev/null
+++ b/uievents/mouse/layout_change_should_fire_mouseover-manual.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Mouseover/enter is sent on layout change</title>
+        <meta name="viewport" content="width=device-width">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <style>
+            #spacer {
+                height: 100px;
+                width: 100px;
+            }
+            #red {
+                background-color: rgb(255, 0, 0);
+                position: absolute;
+                z-index: 0;
+                left: 0px;
+                top: 0px;
+                height: 100px;
+                width: 100px;
+            }
+            #blue {
+                background-color: rgb(0, 0, 255);
+                position: absolute;
+                z-index: 1;
+                left: 0px;
+                top: 0px;
+                height: 100px;
+                width: 100px;
+            }
+            #blue:hover {
+                background-color: rgb(255, 255, 0);
+            }
+        </style>
+    </head>
+    <body onload="run();">
+        <div id="spacer"></div>
+        <div id="red"></div>
+        <h4>Test Description: Tests that the mouseover event is fired and the element has a hover effect when the element underneath the mouse cursor is changed.
+            <ol>
+                <li>Put your mouse over the red rectangle</li>
+                <li>Click the primary mouse button</li>
+            </ol>
+        </h4>
+        <script type="text/javascript">
+            var testMouseOver = async_test('Tests that the mouseover event is fired and the element has a hover effect when the element underneath the mouse cursor is changed.');
+
+            var eventList = [];
+            function addBlue() {
+                document.body.innerHTML += '<div id="blue"></div>';
+                var blue = document.getElementById("blue");
+                var events = ['mouseover', 'mousemove', 'mouseout', 'mouseenter', 'mouseleave'];
+                events.forEach(function (event) {
+                    blue.addEventListener(event, checkHoverEffect);
+                });
+                testMouseOver.step_timeout(function () {
+                   checkEventSequence();
+                }, 2500);
+            }
+
+            function checkEventSequence() {
+                var result = eventList.join();
+                assert_true(result == 'mouseover,mouseenter');
+                testMouseOver.done();
+            }
+
+            function run() {
+                document.addEventListener('click', addBlue);
+            }
+
+            function checkHoverEffect(event) {
+                eventList.push(event.type);
+                testMouseOver.step(function () {
+                  assert_equals(event.target.id, "blue");
+                  assert_equals(getComputedStyle(event.target).backgroundColor, "rgb(255, 255, 0)");
+                  if (event.type == "mouseenter") {
+                      checkEventSequence();
+                  }
+              });
+            }
+        </script>
+    </body>
+</html>
diff --git a/uievents/mouse/mouse_buttons_back_forward-manual.html b/uievents/mouse/mouse_buttons_back_forward-manual.html
new file mode 100644
index 0000000..7a676db
--- /dev/null
+++ b/uievents/mouse/mouse_buttons_back_forward-manual.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8" />
+        <title>Mouse Button Back/Forward</title>
+        <link rel="author" title="Google" href="http://www.google.com/" />
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script>
+            var testMouseUp = async_test('Tests that the mouseup is preventable.');
+            var received_back = false;
+            var received_forward = false;
+            window.addEventListener('mouseup', function(e) {
+              if (e.button == 3) {
+                received_back = true;
+                e.preventDefault();
+              } else if (e.button == 4) {
+                received_forward = true;
+                e.preventDefault();
+              }
+              if (received_back && received_forward) {
+                testMouseUp.done();
+              }
+            });
+        </script>
+
+    </head>
+    <body id="target">
+        <h4>Test Description: Tests that the mouseup event is prevented.
+            <ol>
+                <li>Click the back mouse button</li>
+                <li>Click the back mouse forward</li>
+            </ol>
+        </h4>
+    </body>
+</html>
diff --git a/update-built-tests.sh b/update-built-tests.sh
index 63722f4..f44dd5e 100755
--- a/update-built-tests.sh
+++ b/update-built-tests.sh
@@ -5,3 +5,4 @@
 infrastructure/assumptions/tools/build.sh
 html/tools/build.sh
 offscreen-canvas/tools/build.sh
+python mimesniff/mime-types/resources/generated-mime-types.py
diff --git a/upgrade-insecure-requests/OWNERS b/upgrade-insecure-requests/OWNERS
new file mode 100644
index 0000000..12f907c
--- /dev/null
+++ b/upgrade-insecure-requests/OWNERS
@@ -0,0 +1 @@
+@mikewest
diff --git a/upgrade-insecure-requests/link-upgrade.sub.https.html b/upgrade-insecure-requests/link-upgrade.sub.https.html
new file mode 100644
index 0000000..46788e8
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade.sub.https.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+
+  <head>
+    <meta name="timeout" content="long">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+
+  <body>
+    <script>
+      const test_data = [
+        {
+          url : './link-upgrade/basic-link-no-upgrade.sub.html',
+          done_message : 'basic-link-no-upgrade',
+        },
+        {
+          url : './link-upgrade/basic-link-upgrade.sub.html',
+          done_message : 'basic-link-upgrade',
+        },
+        {
+          url : './link-upgrade/iframe-link-upgrade.sub.html',
+          done_message : 'iframe-link-upgrade',
+        },
+        {
+          url : './link-upgrade/iframe-top-navigation-no-upgrade-1.sub.html',
+          done_message : 'iframe-top-navigation-no-upgrade-1',
+        },
+        {
+          url : './link-upgrade/iframe-top-navigation-no-upgrade-2.sub.html',
+          done_message : 'iframe-top-navigation-no-upgrade-2',
+        },
+        {
+          url : './link-upgrade/iframe-top-navigation-upgrade-1.sub.html',
+          done_message : 'iframe-top-navigation-upgrade-1',
+        },
+        {
+          url : './link-upgrade/iframe-top-navigation-upgrade-2.sub.html',
+          done_message : 'iframe-top-navigation-upgrade-2',
+        },
+        {
+          url : './link-upgrade/iframe-top-navigation-upgrade-meta.sub.html',
+          done_message : 'iframe-top-navigation-upgrade-meta',
+        },
+      ];
+      for(let i = 0; i<test_data.length; i+=1) {
+        let data = test_data[i];
+        let test = async_test(data.url);
+        test.step(function() {
+          let w = window.open(data.url, data.url);
+          this.add_cleanup(() => w.close());
+          assert_true(w != undefined, "Popup must not be blocked");
+
+          window.addEventListener("message", event => {
+            if (event.data == data.done_message)
+              test.done();
+          });
+        });
+
+        test.step_timeout(function(){test.force_timeout()}, 10000);
+      }
+    </script>
+  </body>
+
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/basic-link-no-upgrade.sub.html b/upgrade-insecure-requests/link-upgrade/basic-link-no-upgrade.sub.html
new file mode 100644
index 0000000..c1a600f
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/basic-link-no-upgrade.sub.html
@@ -0,0 +1,19 @@
+<html>
+  <title>Upgrade Insecure Requests: link no upgrade.</title>
+  <head>
+    <script>
+      function click() {
+        document.getElementById("link").click();
+      }
+    </script>
+  </head>
+  <body onload="click()">
+    <!--
+    This is a bit of a hack. UPGRADE doesn't upgrade the port number. So if
+    the url is upgraded, the url becomes invalid (https over the http port).
+    The expected behavior is that the url is not upgraded and the page loads.
+    -->
+    <a id="link"
+      href="http://{{domains[www]}}:{{ports[http][0]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html?message=basic-link-no-upgrade"> Click me </a>
+  </body>
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/basic-link-no-upgrade.sub.html.headers b/upgrade-insecure-requests/link-upgrade/basic-link-no-upgrade.sub.html.headers
new file mode 100644
index 0000000..602d9dc
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/basic-link-no-upgrade.sub.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/upgrade-insecure-requests/link-upgrade/basic-link-upgrade.sub.html b/upgrade-insecure-requests/link-upgrade/basic-link-upgrade.sub.html
new file mode 100644
index 0000000..ef41d3e
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/basic-link-upgrade.sub.html
@@ -0,0 +1,19 @@
+<html>
+  <title>Upgrade Insecure Requests: link upgrade.</title>
+  <head>
+    <script>
+      function click() {
+        document.getElementById("link").click();
+      }
+    </script>
+  </head>
+  <body onload="click()">
+    <!--
+    This is a bit of a hack. UPGRADE doesn't upgrade the port number, so we
+    specify this non-existent URL ('http' over https port). If UPGRADE doesn't
+    work, it won't load. The expected behavior is that the url is upgraded and
+    the page loads.
+    -->
+    <a id="link" href="http://{{host}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html?message=basic-link-upgrade"> Click me </a>
+  </body>
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/basic-link-upgrade.sub.html.headers b/upgrade-insecure-requests/link-upgrade/basic-link-upgrade.sub.html.headers
new file mode 100644
index 0000000..602d9dc
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/basic-link-upgrade.sub.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-link-upgrade.sub.html b/upgrade-insecure-requests/link-upgrade/iframe-link-upgrade.sub.html
new file mode 100644
index 0000000..ed058b6
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-link-upgrade.sub.html
@@ -0,0 +1,11 @@
+<!--
+  This document has set "Content-Security-Policy: upgrade-insecure-requests". It
+  contains an iframe. This iframe clicks on a link to the same host. The link
+  must be upgraded.
+
+  This is a bit of a hack. UPGRADE doesn't upgrade the port number. So if
+  the url is not upgraded, the url is invalid (http over the https port).
+  The expected behavior is that the url is upgraded and the page loads.
+-->
+<iframe src= "./resources/click-on-link.sub.html?url=http://{{host}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html%3Fmessage=iframe-link-upgrade">
+</iframe>
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-link-upgrade.sub.html.headers b/upgrade-insecure-requests/link-upgrade/iframe-link-upgrade.sub.html.headers
new file mode 100644
index 0000000..602d9dc
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-link-upgrade.sub.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-1.sub.html b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-1.sub.html
new file mode 100644
index 0000000..1b514e2
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-1.sub.html
@@ -0,0 +1,16 @@
+<html>
+  <head>
+    <title>Upgrade Insecure Requests: top-frame navigation inside iframe (no upgrade expected)</title>
+  </head>
+  <body>
+    <!--
+    This is a bit of a hack. UPGRADE doesn't upgrade the port number. So if the
+    url is upgraded, the url becomes invalid (https over the http port). The
+    expected behavior is that the url is not upgraded and the page loads.
+    -->
+    <iframe
+      sandbox="allow-scripts allow-top-navigation"
+      src="https://{{domains[www]}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame.sub.html?url=http://{{domains[www]}}:{{ports[http][1]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html%3Fmessage=iframe-top-navigation-no-upgrade-1"
+    ></iframe>
+  </body>
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-1.sub.html.headers b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-1.sub.html.headers
new file mode 100644
index 0000000..602d9dc
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-1.sub.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-2.sub.html b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-2.sub.html
new file mode 100644
index 0000000..386a86f
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-no-upgrade-2.sub.html
@@ -0,0 +1,16 @@
+<html>
+  <head>
+    <title>Upgrade Insecure Requests: top-frame navigation inside iframe (no upgrade expected)</title>
+  </head>
+  <body>
+    <!--
+    This is a bit of a hack. UPGRADE doesn't upgrade the port number. So if the
+    url is upgraded, the url becomes invalid (https over the http port). The
+    expected behavior is that the url is not upgraded and the page loads.
+    -->
+    <iframe
+      sandbox="allow-scripts allow-top-navigation"
+      src="https://{{domains[www]}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html?url=http://{{host}}:{{ports[http][0]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html%3Fmessage=iframe-top-navigation-no-upgrade-2"
+    ></iframe>
+  </body>
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-1.sub.html b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-1.sub.html
new file mode 100644
index 0000000..13c76e82
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-1.sub.html
@@ -0,0 +1,17 @@
+<html>
+  <head>
+    <title>Upgrade Insecure Requests: top-frame navigation inside iframe (upgrade expected)</title>
+  </head>
+  <body>
+    <!--
+    This is a bit of a hack. UPGRADE doesn't upgrade the port number, so we
+    specify this non-existent URL ('http' over https port). If UPGRADE doesn't
+    work, it won't load. The expected behavior is that the url is upgraded and
+    the page loads.
+    -->
+    <iframe
+      sandbox="allow-scripts allow-top-navigation"
+      src="https://{{domains[www]}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame.sub.html?url=http://{{host}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html%3Fmessage=iframe-top-navigation-upgrade-1"
+    ></iframe>
+  </body>
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-1.sub.html.headers b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-1.sub.html.headers
new file mode 100644
index 0000000..602d9dc
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-1.sub.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-2.sub.html b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-2.sub.html
new file mode 100644
index 0000000..651d76d
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-2.sub.html
@@ -0,0 +1,17 @@
+<html>
+  <head>
+    <title>Upgrade Insecure Requests: top-frame navigation inside iframe (upgrade expected)</title>
+  </head>
+  <body>
+    <!--
+    This is a bit of a hack. UPGRADE doesn't upgrade the port number, so we
+    specify this non-existent URL ('http' over https port). If UPGRADE doesn't
+    work, it won't load. The expected behavior is that the url is upgraded and
+    the page loads.
+    -->
+    <iframe
+      sandbox="allow-scripts allow-top-navigation"
+      src="https://{{domains[www]}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html?url=http://{{domains[www]}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html%3Fmessage=iframe-top-navigation-upgrade-2"
+    ></iframe>
+  </body>
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-2.sub.html.headers b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-2.sub.html.headers
new file mode 100644
index 0000000..602d9dc
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-2.sub.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-meta.sub.html b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-meta.sub.html
new file mode 100644
index 0000000..e43050e
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/iframe-top-navigation-upgrade-meta.sub.html
@@ -0,0 +1,31 @@
+<html>
+  <head>
+    <title>Upgrade Insecure Requests: top-frame navigation inside iframe (upgrade expected)</title>
+    <script>
+      function iframe_onload() {
+        var iframe = document.getElementsByTagName("iframe")[0];
+        iframe.onload = null;
+
+        // Enable upgrade-insecure-requests dynamically.
+        var meta = document.createElement('meta');
+        meta.httpEquiv = "Content-Security-Policy";
+        meta.content = "upgrade-insecure-requests";
+        document.getElementsByTagName('head')[0].appendChild(meta);
+
+        // This is a bit of a hack. UPGRADE doesn't upgrade the port number,
+        // so we specify this non-existent URL ('http' over port https port). If
+        // UPGRADE doesn't work, it won't load. The expected behavior is that
+        // the url is upgraded and the page loads.
+        iframe.src =
+          "https://{{domains[www]}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame.sub.html?url=http://{{host}}:{{ports[https][0]}}/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html%3Fmessage=iframe-top-navigation-upgrade-meta"
+      }
+    </script>
+  </head>
+  <body>
+    <iframe
+      sandbox = "allow-scripts allow-top-navigation"
+      src = "./resources/dummy.html"
+      onload = "iframe_onload()"
+    ></iframe>
+  </body>
+</html>
diff --git a/upgrade-insecure-requests/link-upgrade/resources/click-on-link.sub.html b/upgrade-insecure-requests/link-upgrade/resources/click-on-link.sub.html
new file mode 100644
index 0000000..d2899c8
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/resources/click-on-link.sub.html
@@ -0,0 +1,10 @@
+<body>
+  coucou
+  <a href="{{GET[url]}}">Click me</a>
+</body>
+
+<script>
+  window.addEventListener("load", function() {
+    document.getElementsByTagName("a")[0].click();
+ })
+</script>
diff --git a/upgrade-insecure-requests/link-upgrade/resources/dummy.html b/upgrade-insecure-requests/link-upgrade/resources/dummy.html
new file mode 100644
index 0000000..1c56b51
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/resources/dummy.html
@@ -0,0 +1 @@
+I am not an interesting file...
diff --git a/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html b/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html
new file mode 100644
index 0000000..3533290
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html
@@ -0,0 +1,5 @@
+<script>
+  window.addEventListener("load", function() {
+    window.top.location.href = "{{GET[url]}}";
+  })
+</script>
diff --git a/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html.headers b/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html.headers
new file mode 100644
index 0000000..602d9dc
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame-upgrade.sub.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame.sub.html b/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame.sub.html
new file mode 100644
index 0000000..3533290
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/resources/navigate-top-frame.sub.html
@@ -0,0 +1,5 @@
+<script>
+  window.addEventListener("load", function() {
+    window.top.location.href = "{{GET[url]}}";
+  })
+</script>
diff --git a/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html b/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html
new file mode 100644
index 0000000..5cf2df5
--- /dev/null
+++ b/upgrade-insecure-requests/link-upgrade/resources/post-message-to-opener.sub.html
@@ -0,0 +1,3 @@
+<script>
+  top.opener.postMessage("{{GET[message]}}", "*");
+</script>
diff --git a/url/README.md b/url/README.md
index 4cb3c00..cc63177 100644
--- a/url/README.md
+++ b/url/README.md
@@ -1,10 +1,10 @@
 These tests are for browsers, but the data for
-`a-element.html`, `url-constructor.html`, and `a-element-xhtml.xhtml`
+`a-element.html`, `url-constructor.html`, `a-element-xhtml.xhtml`, and `failure.html`
 is in `urltestdata.json` and can be re-used by non-browser implementations.
 This file contains a JSON array of comments as strings and test cases as objects.
 The keys for each test case are:
 
-* `base`: an absolute URL as a string whose [parsing] without a base of its own should succeed.
+* `base`: an absolute URL as a string whose [parsing] without a base of its own must succeed.
   This key is always present,
   and may have a value like `"about:blank"` when `input` is an absolute URL.
 * `input`: an URL as a string to be [parsed][parsing] with `base` as its base URL.
@@ -19,5 +19,11 @@
     The `origin` key may be missing.
     In that case, the API’s `origin` attribute is not tested.
 
+In addition to testing that parsing `input` against `base` gives the result, a test harness for the
+`URL` constructor (or similar APIs) should additionally test the following pattern: if `failure` is
+true, parsing `about:blank` against `base` must give failure. This tests that the logic for
+converting base URLs into strings properly fails the whole parsing algorithm if the base URL cannot
+be parsed.
+
 [parsing]: https://url.spec.whatwg.org/#concept-basic-url-parser
 [API]: https://url.spec.whatwg.org/#api
diff --git a/url/failure.html b/url/failure.html
index d097d4e..8ae9da7 100644
--- a/url/failure.html
+++ b/url/failure.html
@@ -16,7 +16,13 @@
 
     const name = test.input + " should throw"
 
-    self.test(() => { // new URL itself is already tested by url-constructor.html
+    self.test(() => { // URL's constructor's first argument is tested by url-constructor.html
+      // If a URL fails to parse with any valid base, it must also fail to parse with no base, i.e.
+      // when used as a base URL itself.
+      assert_throws(new TypeError(), () => new URL("about:blank", test.input));
+    }, "URL's constructor's base argument: " + name)
+
+    self.test(() => {
       const url = new URL("about:blank")
       assert_throws(new TypeError, () => url.href = test.input)
     }, "URL's href: " + name)
diff --git a/url/interfaces.any.js b/url/interfaces.any.js
index aa5d5c8..dbbbca4 100644
--- a/url/interfaces.any.js
+++ b/url/interfaces.any.js
@@ -1,51 +1,15 @@
 // META: script=/resources/WebIDLParser.js
 // META: script=/resources/idlharness.js
 
-let idlArray,
-    idl = `[Constructor(USVString url, optional USVString base),
- Exposed=(Window,Worker),
- LegacyWindowAlias=webkitURL]
-interface URL {
-  stringifier attribute USVString href;
-  readonly attribute USVString origin;
-           attribute USVString protocol;
-           attribute USVString username;
-           attribute USVString password;
-           attribute USVString host;
-           attribute USVString hostname;
-           attribute USVString port;
-           attribute USVString pathname;
-           attribute USVString search;
-  [SameObject] readonly attribute URLSearchParams searchParams;
-           attribute USVString hash;
-
-  USVString toJSON();
-};
-
-[Constructor(optional (sequence<sequence<USVString>> or record<USVString, USVString> or USVString) init = ""),
- Exposed=(Window,Worker)]
-interface URLSearchParams {
-  void append(USVString name, USVString value);
-  void delete(USVString name);
-  USVString? get(USVString name);
-  sequence<USVString> getAll(USVString name);
-  boolean has(USVString name);
-  void set(USVString name, USVString value);
-
-  void sort();
-
-  iterable<USVString, USVString>;
-  stringifier;
-};`;
-setup(function() {
-  idlArray = new IdlArray();
-  idlArray.add_idls(idl);
-}, {explicit_done:true});
-
-idlArray.add_objects({
-  URL: ['new URL("http://foo")'],
-  URLSearchParams: ['new URLSearchParams("hi=there&thank=you")']
-});
-idlArray.test();
-
-done();
+promise_test(async() => {
+  const text = await fetch('/interfaces/url.idl')
+    .then(response => response.text());
+  const idlArray = new IdlArray();
+  idlArray.add_idls(text);
+  idlArray.add_objects({
+    URL: ['new URL("http://foo")'],
+    URLSearchParams: ['new URLSearchParams("hi=there&thank=you")']
+  });
+  idlArray.test();
+  done();
+}, 'Test driver');
diff --git a/url/setters_tests.json b/url/setters_tests.json
index f35db16..db23d92 100644
--- a/url/setters_tests.json
+++ b/url/setters_tests.json
@@ -720,6 +720,17 @@
             }
         },
         {
+            "comment": "Port number is removed if new port is scheme default and existing URL has a non-default port",
+            "href": "http://example.net:8080",
+            "new_value": "example.com:80",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
             "comment": "Stuff after a / delimiter is ignored",
             "href": "http://example.net/path",
             "new_value": "example.com/stuff",
@@ -1786,12 +1797,52 @@
             }
         },
         {
+            "href": "http://example.net",
+            "new_value": "#foo bar",
+            "expected": {
+                "href": "http://example.net/#foo%20bar",
+                "hash": "#foo%20bar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo\"bar",
+            "expected": {
+                "href": "http://example.net/#foo%22bar",
+                "hash": "#foo%22bar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo<bar",
+            "expected": {
+                "href": "http://example.net/#foo%3Cbar",
+                "hash": "#foo%3Cbar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo>bar",
+            "expected": {
+                "href": "http://example.net/#foo%3Ebar",
+                "hash": "#foo%3Ebar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo`bar",
+            "expected": {
+                "href": "http://example.net/#foo%60bar",
+                "hash": "#foo%60bar"
+            }
+        },
+        {
             "comment": "Simple percent-encoding; nuls, tabs, and newlines are removed",
             "href": "a:/",
             "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
             "expected": {
-                "href": "a:/#%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9",
-                "hash": "#%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+                "href": "a:/#%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9",
+                "hash": "#%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
             }
         },
         {
diff --git a/url/urlsearchparams-foreach.html b/url/urlsearchparams-foreach.html
index faa699c..29c0ee8 100644
--- a/url/urlsearchparams-foreach.html
+++ b/url/urlsearchparams-foreach.html
@@ -36,4 +36,48 @@
         assert_unreached(i);
     }
 }, "empty");
+
+test(function() {
+    const url = new URL("http://localhost/query?param0=0&param1=1&param2=2");
+    const searchParams = url.searchParams;
+    const seen = [];
+    for (param of searchParams) {
+        if (param[0] === 'param0') {
+            searchParams.delete('param1');
+        }
+        seen.push(param);
+    }
+
+    assert_array_equals(seen[0], ["param0", "0"]);
+    assert_array_equals(seen[1], ["param2", "2"]);
+}, "delete next param during iteration");
+
+test(function() {
+    const url = new URL("http://localhost/query?param0=0&param1=1&param2=2");
+    const searchParams = url.searchParams;
+    const seen = [];
+    for (param of searchParams) {
+        if (param[0] === 'param0') {
+            searchParams.delete('param0');
+            // 'param1=1' is now in the first slot, so the next iteration will see 'param2=2'.
+        } else {
+            seen.push(param);
+        }
+    }
+
+    assert_array_equals(seen[0], ["param2", "2"]);
+}, "delete current param during iteration");
+
+test(function() {
+    const url = new URL("http://localhost/query?param0=0&param1=1&param2=2");
+    const searchParams = url.searchParams;
+    const seen = [];
+    for (param of searchParams) {
+        seen.push(param[0]);
+        searchParams.delete(param[0]);
+    }
+
+    assert_array_equals(seen, ["param0", "param2"], "param1 should not have been seen by the loop");
+    assert_equals(String(searchParams), "param1=1", "param1 should remain");
+}, "delete every param seen during iteration");
 </script>
diff --git a/url/urltestdata.json b/url/urltestdata.json
index 0691761..8c87da2 100644
--- a/url/urltestdata.json
+++ b/url/urltestdata.json
@@ -153,7 +153,7 @@
   {
     "input": "http://f:21/ b ? d # e ",
     "base": "http://example.org/foo/bar",
-    "href": "http://f:21/%20b%20?%20d%20# e",
+    "href": "http://f:21/%20b%20?%20d%20#%20e",
     "origin": "http://f:21",
     "protocol": "http:",
     "username": "",
@@ -163,12 +163,12 @@
     "port": "21",
     "pathname": "/%20b%20",
     "search": "?%20d%20",
-    "hash": "# e"
+    "hash": "#%20e"
   },
   {
     "input": "lolscheme:x x#x x",
     "base": "about:blank",
-    "href": "lolscheme:x x#x x",
+    "href": "lolscheme:x x#x%20x",
     "protocol": "lolscheme:",
     "username": "",
     "password": "",
@@ -177,7 +177,7 @@
     "port": "",
     "pathname": "x x",
     "search": "",
-    "hash": "#x x"
+    "hash": "#x%20x"
   },
   {
     "input": "http://f:/c",
@@ -2260,7 +2260,7 @@
   {
     "input": "http://www.google.com/foo?bar=baz# »",
     "base": "about:blank",
-    "href": "http://www.google.com/foo?bar=baz# %C2%BB",
+    "href": "http://www.google.com/foo?bar=baz#%20%C2%BB",
     "origin": "http://www.google.com",
     "protocol": "http:",
     "username": "",
@@ -2270,12 +2270,12 @@
     "port": "",
     "pathname": "/foo",
     "search": "?bar=baz",
-    "hash": "# %C2%BB"
+    "hash": "#%20%C2%BB"
   },
   {
     "input": "data:test# »",
     "base": "about:blank",
-    "href": "data:test# %C2%BB",
+    "href": "data:test#%20%C2%BB",
     "origin": "null",
     "protocol": "data:",
     "username": "",
@@ -2285,7 +2285,7 @@
     "port": "",
     "pathname": "test",
     "search": "",
-    "hash": "# %C2%BB"
+    "hash": "#%20%C2%BB"
   },
   {
     "input": "http://www.google.com",
@@ -4787,6 +4787,70 @@
     "searchParams": "qux=",
     "hash": "#foo%08bar"
   },
+  {
+    "input": "http://foo.bar/baz?qux#foo\"bar",
+    "base": "about:blank",
+    "href": "http://foo.bar/baz?qux#foo%22bar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%22bar"
+  },
+  {
+    "input": "http://foo.bar/baz?qux#foo<bar",
+    "base": "about:blank",
+    "href": "http://foo.bar/baz?qux#foo%3Cbar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%3Cbar"
+  },
+  {
+    "input": "http://foo.bar/baz?qux#foo>bar",
+    "base": "about:blank",
+    "href": "http://foo.bar/baz?qux#foo%3Ebar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%3Ebar"
+  },
+  {
+    "input": "http://foo.bar/baz?qux#foo`bar",
+    "base": "about:blank",
+    "href": "http://foo.bar/baz?qux#foo%60bar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%60bar"
+  },
   "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)",
   {
     "input": "http://192.168.257",
@@ -6457,28 +6521,35 @@
     "search": "?a",
     "hash": "#%GH"
   },
-  "Bad bases",
+  "URLs that require a non-about:blank base. (Also serve as invalid base tests.)",
   {
-    "input": "test-a.html",
-    "base": "a",
+    "input": "a",
+    "base": "about:blank",
     "failure": true
   },
   {
-    "input": "test-a-slash.html",
-    "base": "a/",
+    "input": "a/",
+    "base": "about:blank",
     "failure": true
   },
   {
-    "input": "test-a-slash-slash.html",
-    "base": "a//",
+    "input": "a//",
+    "base": "about:blank",
     "failure": true
   },
+  "Bases that don't fail to parse but fail to be bases",
   {
     "input": "test-a-colon.html",
     "base": "a:",
     "failure": true
   },
   {
+    "input": "test-a-colon-b.html",
+    "base": "a:b",
+    "failure": true
+  },
+  "Other base URL tests, that must succeed",
+  {
     "input": "test-a-colon-slash.html",
     "base": "a:/",
     "href": "a:/test-a-colon-slash.html",
@@ -6507,11 +6578,6 @@
     "hash": ""
   },
   {
-    "input": "test-a-colon-b.html",
-    "base": "a:b",
-    "failure": true
-  },
-  {
     "input": "test-a-colon-slash-b.html",
     "base": "a:/b",
     "href": "a:/test-a-colon-slash-b.html",
diff --git a/user-timing/clearMarks.html b/user-timing/clearMarks.html
new file mode 100644
index 0000000..22d6726
--- /dev/null
+++ b/user-timing/clearMarks.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>functionality test of window.performance.clearMarks</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/performance-timeline-utils.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({ explicit_done: true });
+
+function onload_test()
+{
+    const entrylist_checker = new performance_entrylist_checker('mark');
+    const string_mark_names = mark_names.map(function (x) { return String(x)});
+    mark_names.forEach(performance.mark, performance);
+
+    for (let i = 0; i < mark_names.length; ++i)
+    {
+        performance.clearMarks(mark_names[i]);
+        const retained_entries = performance.getEntriesByType('mark');
+        const non_retained_entries = performance.getEntriesByName(mark_names[i], 'mark');
+        entrylist_checker.entrylist_check(retained_entries, mark_names.length - i - 1, string_mark_names,
+            'First loop: checking entries after removing "' + mark_names[i] + '". ');
+        test_equals(non_retained_entries.length, 0,
+            'First loop: marks that we cleared for "' + mark_names[i] + '" should not exist anymore.');
+    }
+
+    mark_names.forEach(performance.mark, performance);
+    performance.clearMarks();
+    test_equals(performance.getEntriesByType('mark').length, 0, 'No marks should exist after we clear all.');
+
+    // Following cases test clear existed mark name that is tied for two times.
+    mark_names.forEach(performance.mark, performance);
+    mark_names.forEach(performance.mark, performance);
+
+    for (let i = 0; i < mark_names.length; ++i)
+    {
+        performance.clearMarks(mark_names[i]);
+        const retained_entries = performance.getEntriesByType('mark');
+        const non_retained_entries = performance.getEntriesByName(mark_names[i], 'mark');
+        entrylist_checker.entrylist_check(retained_entries, (mark_names.length - i - 1) * 2, string_mark_names,
+            'Second loop: checking entries after removing "' + mark_names[i] + '". ');
+        test_equals(non_retained_entries.length, 0,
+            'Second loop: marks that we cleared for "' + mark_names[i] + '" should not exist anymore.');
+    }
+
+    // Following cases test clear functionality when mark names are tied for two times.
+    mark_names.forEach(performance.mark, performance);
+    mark_names.forEach(performance.mark, performance);
+    var entry_number_before_useless_clear = performance.getEntriesByType('Mark').length;
+    performance.clearMarks('NonExist');
+    var entry_number_after_useless_clear = performance.getEntriesByType('Mark').length;
+    test_equals(entry_number_before_useless_clear, entry_number_after_useless_clear, 'Nothing should happen if we clear a non-exist mark.');
+
+    performance.clearMarks();
+    test_equals(performance.getEntriesByType('mark').length, 0, 'No marks should exist when we clear all.');
+
+    done();
+}
+</script>
+</head>
+<body onload=onload_test()>
+    <h1>Description</h1>
+    <p>This test validates functionality of the interface window.performance.clearMarks.</p>
+    <div id="log"></div>
+</body>
+</html>
diff --git a/user-timing/clearMeasures.html b/user-timing/clearMeasures.html
new file mode 100644
index 0000000..488bece
--- /dev/null
+++ b/user-timing/clearMeasures.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>functionality test of window.performance.clearMeasures</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/performance-timeline-utils.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({ explicit_done: true });
+
+function onload_test()
+{
+    const context = new PerformanceContext(window.performance);
+    const entrylist_checker = new performance_entrylist_checker('measure');
+    const measure_names = measures.map(function(x) {return x[0];});
+
+    mark_names.forEach(context.mark, context);
+    measures.forEach(context.initialMeasures, context);
+    for (let i = 0; i < measures.length; ++i)
+    {
+        context.clearMeasures(measures[i][0]);
+        const retained_entries = context.getEntriesByType('measure');
+        const non_retained_entries = context.getEntriesByName(measures[i][0], 'measure');
+        entrylist_checker.entrylist_check(retained_entries, measures.length - i - 1, measure_names,
+            'First loop: checking entries after removing "' + measures[i][0] + '". ');
+        test_equals(non_retained_entries.length, 0,
+            'First loop: measure "' + measures[i][0] + '" should not exist anymore after we cleared it.');
+    }
+
+    measures.forEach(context.initialMeasures, context);
+    context.clearMeasures();
+    test_equals(context.getEntriesByType('measure').length, 0, 'No measures should exist after we clear all (after first loop).');
+
+    // Following cases test clear existed measure name that is tied twice.
+    measures.forEach(context.initialMeasures, context);
+    mark_names.forEach(context.mark, context);
+    measures.forEach(context.initialMeasures, context);
+    for (let i = 0; i < measures.length; ++i)
+    {
+        context.clearMeasures(measures[i][0]);
+        const retained_entries = context.getEntriesByType('measure');
+        const non_retained_entries = context.getEntriesByName(measures[i][0], 'measure');
+        entrylist_checker.entrylist_check(retained_entries, (measures.length - i - 1) * 2, measure_names,
+            'Second loop: checking entries after removing "' + measures[i][0] + '". ');
+        test_equals(non_retained_entries.length, 0,
+            'Second loop: measure "' + measures[i][0] +'" should not exist anymore after we cleared it.');
+    }
+
+    // Following cases test clear functionality when measure names are tied twice.
+    measures.forEach(context.initialMeasures, context);
+    measures.forEach(context.initialMeasures, context);
+    const entry_number_before_useless_clear = context.getEntriesByType('measure').length;
+    context.clearMeasures('NonExist');
+    const entry_number_after_useless_clear = context.getEntriesByType('measure').length;
+    test_equals(entry_number_before_useless_clear, entry_number_after_useless_clear, 'Nothing should happen if we clear a non-exist measure');
+    context.clearMeasures();
+    test_equals(context.getEntriesByType('measure').length, 0, 'No measures should exist when we clear all (after second loop).');
+
+    done();
+}
+</script>
+</head>
+<body onload=onload_test()>
+    <h1>Description</h1>
+    <p>This test validates functionality of the interface window.performance.clearMeasures.</p>
+    <div id="log"></div>
+</body>
+</html>
diff --git a/user-timing/invoke_with_timing_attributes.html b/user-timing/invoke_with_timing_attributes.html
index dc355a9..6c06f5e 100644
--- a/user-timing/invoke_with_timing_attributes.html
+++ b/user-timing/invoke_with_timing_attributes.html
@@ -24,8 +24,8 @@
 
 function emit_test2(attrName) {
     test(function() {
-        assert_throws("SyntaxError", function() { window.performance.measure(attrName); });
-    }, "performance.measure should throw if used with timing attribute " + attrName);
+        window.performance.measure(attrName);
+    }, "performance.measure should not throw if used with timing attribute " + attrName);
 }
 for (var i in timingAttributes) {
   emit_test2(timingAttributes[i]);
diff --git a/user-timing/mark.html b/user-timing/mark.html
new file mode 100644
index 0000000..a7074f1
--- /dev/null
+++ b/user-timing/mark.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>functionality test of window.performance.mark</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/performance-timeline-utils.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({ explicit_done: true });
+
+function onload_test()
+{
+    const entrylist_checker = new performance_entrylist_checker('mark');
+    const string_mark_names = mark_names.map(function (x) { return String(x)});
+
+    test_equals(performance.getEntriesByType("mark").length, 0, 'There should be ' + 0 + ' marks');
+    mark_names.forEach(performance.mark, performance);
+    let mark_entrylist = performance.getEntriesByType('mark');
+
+    entrylist_checker.entrylist_check(mark_entrylist, mark_names.length, string_mark_names, 'Checking all entries.');
+
+    for (let i = 0; i < mark_entrylist.length; ++i)
+    {
+        const mark_entrylist_by_name = performance.getEntriesByName(mark_entrylist[i].name, 'mark');
+        entrylist_checker.entrylist_check(mark_entrylist_by_name, 1, string_mark_names,
+            'First loop: checking entry of name "' + mark_entrylist[i].name + '".');
+    }
+
+    mark_names.forEach(performance.mark, performance);
+    mark_entrylist = performance.getEntriesByType('mark');
+    entrylist_checker.entrylist_check(mark_entrylist, mark_names.length * 2, string_mark_names, 'Checking all doubly marked entries.');
+
+    for (let i = 0; i < mark_entrylist.length; ++i)
+    {
+        const mark_entrylist_by_name = performance.getEntriesByName(mark_entrylist[i].name, 'mark');
+        entrylist_checker.entrylist_check(mark_entrylist_by_name, 2, string_mark_names,
+            'Second loop step ' + i + ': checking entries of name "' + mark_entrylist[i].name + '".');
+    }
+
+    done();
+}
+</script>
+</head>
+<body onload=onload_test()>
+    <h1>Description</h1>
+    <p>This test validates functionality of the interface window.performance.mark.</p>
+    <div id="log"></div>
+</body>
+</html>
diff --git a/user-timing/mark_exceptions.html b/user-timing/mark_exceptions.html
index 477b3fa..e1f4c4e 100644
--- a/user-timing/mark_exceptions.html
+++ b/user-timing/mark_exceptions.html
@@ -18,6 +18,12 @@
     }, "window.performance.mark(\"" + attrName + "\") throws a SyntaxError exception.");
 }
 
+test(() => {
+    assert_throws(new TypeError(), function() {
+        window.performance.mark();
+    });
+}, 'window.performance.mark() throws a TypeError exception.')
+
 // loop through mark scenarios
 for (var i in timingAttributes) {
     test_exception(timingAttributes[i]);
diff --git a/user-timing/measure_associated_with_navigation_timing.html b/user-timing/measure_associated_with_navigation_timing.html
new file mode 100644
index 0000000..9fd4664
--- /dev/null
+++ b/user-timing/measure_associated_with_navigation_timing.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>functionality test of window.performance.measure</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/performance-timeline-utils.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({ explicit_done: true });
+
+function onload_test()
+{
+    const measures_for_timing_order = [
+        ['nav2now', 'navigationStart'],
+        ['loadTime', 'navigationStart', 'loadEventEnd'],
+        ['loadEventEnd2a', 'loadEventEnd', 'abc'],
+        ['nav2a', 'navigationStart', 'abc'],
+        ['domComplete2a', 'domComplete', 'abc'],
+        ['negativeValue', 1, 'navigationStart'],
+    ];
+    const context = new PerformanceContext(window.performance);
+
+    mark_names.forEach(context.mark, context);
+    measures_for_timing_order.forEach(context.initialMeasures, context);
+    test_greater_than(context.getEntriesByName('nav2now', 'measure')[0].duration, 0, 'Measure of navigationStart to now should be positive value.');
+    test_greater_than(context.getEntriesByName('loadTime', 'measure')[0].duration, 0, 'Measure of navigationStart to loadEventEnd should be positive value.');
+    test_greater_than(0, context.getEntriesByName('negativeValue', 'measure')[0].duration, 'Measure of current mark to navigationStart should be negative value.');
+    test_equals(context.getEntriesByName('loadTime', 'measure')[0].duration + context.getEntriesByName('loadEventEnd2a', 'measure')[0].duration, context.getEntriesByName('nav2a', 'measure')[0].duration, 'loadTime plus loadEventEnd to a mark "a" should equal to navigationStart to "a".');
+
+    // Following cases test for scenarios that measure names are tied twice.
+    mark_names.forEach(context.mark, context);
+    measures_for_timing_order.forEach(context.initialMeasures, context);
+
+    test_greater_than(context.getEntriesByName('nav2now', 'measure')[1].duration, context.getEntriesByName('nav2now', 'measure')[0].duration, 'Second measure of current mark to navigationStart should be negative value.');
+    test_equals(context.getEntriesByName('loadTime', 'measure')[0].duration, context.getEntriesByName('loadTime', 'measure')[1].duration, 'Measures of loadTime should have same duration.');
+    test_greater_than(context.getEntriesByName('domComplete2a', 'measure')[1].duration, context.getEntriesByName('domComplete2a', 'measure')[0].duration, 'Measure from domComplete event to most recent mark "a" should have longer duration.');
+    test_greater_than(context.getEntriesByName('negativeValue', 'measure')[0].duration, context.getEntriesByName('negativeValue', 'measure')[1].duration, 'Measure from most recent mark to navigationStart should have longer duration.');
+
+    done();
+}
+</script>
+</head>
+<body onload="setTimeout(onload_test,0)">
+    <h1>Description</h1>
+    <p>This test validates functionality of the interface window.performance.measure using keywords from the Navigation Timing spec.</p>
+    <div id="log"></div>
+</body>
+</html>
diff --git a/user-timing/measure_exception.html b/user-timing/measure_exception.html
new file mode 100644
index 0000000..8783ff7
--- /dev/null
+++ b/user-timing/measure_exception.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>exception test of window.performance.measure</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/performance-timeline-utils.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+</head>
+
+<body>
+<h1>Description</h1>
+<p>This test validates all exception scenarios of method window.performance.measure in User Timing API</p>
+
+<div id="log"></div>
+<script>
+performance.mark('ExistMark');
+test_method_throw_exception('performance.measure()', TypeError());
+test_method_throw_exception('performance.measure("Exception1", "NonExistMark1")', 'SYNTAX_ERR');
+test_method_throw_exception('performance.measure("Exception2", "NonExistMark1", "navigationStart")', 'SYNTAX_ERR');
+test_method_throw_exception('performance.measure("Exception3", "navigationStart", "NonExistMark1")', 'SYNTAX_ERR');
+test_method_throw_exception('performance.measure("Exception4", "NonExistMark1", "ExistMark")', 'SYNTAX_ERR');
+test_method_throw_exception('performance.measure("Exception5", "ExistMark", "NonExistMark1")', 'SYNTAX_ERR');
+test_method_throw_exception('performance.measure("Exception6", "NonExistMark1", "NonExistMark2")', 'SYNTAX_ERR');
+test_method_throw_exception('performance.measure("Exception7", "redirectStart")', 'INVALID_ACCESS_ERR');
+</script>
+</body>
+</html>
diff --git a/user-timing/measure_exceptions_navigation_timing.html b/user-timing/measure_exceptions_navigation_timing.html
index 77c1e48..1633221 100644
--- a/user-timing/measure_exceptions_navigation_timing.html
+++ b/user-timing/measure_exceptions_navigation_timing.html
@@ -59,7 +59,7 @@
                               "attribute with a value of 0, throws a InvalidAccessError exception.");
     </script>
     </head>
-    <body onload="onload_test();">
+    <body>
         <h1>Description</h1>
         <p><code>window.performance.measure()</code> method throws a InvalidAccessError
            whenever a navigation timing attribute with a value of zero is provided as the startMark or endMark.
diff --git a/user-timing/measures.html b/user-timing/measures.html
new file mode 100644
index 0000000..d0ab11e
--- /dev/null
+++ b/user-timing/measures.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>functionality test of window.performance.measure</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/performance-timeline-utils.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({ explicit_done: true });
+
+function onload_test()
+{
+    const context = new PerformanceContext(window.performance);
+    const entrylist_checker = new performance_entrylist_checker('measure');
+    const measure_names = measures.map(function(x) {return x[0];});
+
+    test_equals(context.getEntriesByType('measure').length, 0, 'There should be ' + 0 + ' entries returned.');
+
+    mark_names.forEach(context.mark, context);
+    measures.forEach(context.initialMeasures, context);
+
+    let measure_entrylist = context.getEntriesByType('measure');
+    entrylist_checker.entrylist_check(measure_entrylist, measures.length, measure_names,
+        'Checking all entries.');
+
+    for (let i = 0; i < measure_entrylist.length; ++i)
+    {
+        const measure_entrylist_by_name = context.getEntriesByName(measure_entrylist[i].name, 'measure');
+        entrylist_checker.entrylist_check(measure_entrylist_by_name, 1, measure_names,
+            'First loop: checking entry of name "' + measure_entrylist[i].name + '".');
+    }
+
+    // Following cases test for scenarios that measure names are tied for two times
+    mark_names.forEach(context.mark, context);
+    measures.forEach(context.initialMeasures, context);
+
+    measure_entrylist = context.getEntriesByType('measure');
+    entrylist_checker.entrylist_check(measure_entrylist, measures.length * 2, measure_names,
+        'Checking all doubly measured entries.');
+
+    for (let i = 0; i < measure_entrylist.length; ++i)
+    {
+        const measure_entrylist_by_name = context.getEntriesByName(measure_entrylist[i].name, 'measure');
+        entrylist_checker.entrylist_check(measure_entrylist_by_name, 2, measure_names,
+            'Second loop step ' + i + ': checking entry of name "' + measure_entrylist[i].name + '".');
+    }
+
+    done();
+}
+</script>
+</head>
+<body onload=onload_test()>
+    <h1>Description</h1>
+    <p>This test validates functionality of the interface window.performance.measure.</p>
+    <div id="log"></div>
+</body>
+</html>
diff --git a/user-timing/resources/webperftestharnessextension.js b/user-timing/resources/webperftestharnessextension.js
index 579ee90..822376b 100644
--- a/user-timing/resources/webperftestharnessextension.js
+++ b/user-timing/resources/webperftestharnessextension.js
@@ -88,28 +88,29 @@
         }
     }
 }
+
 function performance_entrylist_checker(type)
 {
-    var entryType = type;
+    const entryType = type;
 
-    function entry_check(entry, expectedNames)
+    function entry_check(entry, expectedNames, testDescription = '')
     {
-        var msg = 'Entry \"' + entry.name + '\" should be one that we have set.';
+        const msg = testDescription + 'Entry \"' + entry.name + '\" should be one that we have set.';
         wp_test(function() { assert_in_array(entry.name, expectedNames, msg); }, msg);
-        test_equals(entry.entryType, entryType, 'entryType should be \"' + entryType + '\".');
+        test_equals(entry.entryType, entryType, testDescription + 'entryType should be \"' + entryType + '\".');
         if (type === "measure") {
-            test_true(isFinite(entry.startTime), 'startTime should be a number.');
-            test_true(isFinite(entry.duration), 'duration should be a number.');
+            test_true(isFinite(entry.startTime), testDescription + 'startTime should be a number.');
+            test_true(isFinite(entry.duration), testDescription + 'duration should be a number.');
         } else if (type === "mark") {
-            test_greater_than(entry.startTime, 0, 'startTime should greater than 0.');
-            test_equals(entry.duration, 0, 'duration of mark should be 0.');
+            test_greater_than(entry.startTime, 0, testDescription + 'startTime should greater than 0.');
+            test_equals(entry.duration, 0, testDescription + 'duration of mark should be 0.');
         }
     }
 
     function entrylist_order_check(entryList)
     {
-        var inOrder = true;
-        for (var i = 0; i < entryList.length - 1; ++i)
+        let inOrder = true;
+        for (let i = 0; i < entryList.length - 1; ++i)
         {
             if (entryList[i + 1].startTime < entryList[i].startTime) {
                 inOrder = false;
@@ -119,13 +120,13 @@
         return inOrder;
     }
 
-    function entrylist_check(entryList, expectedLength, expectedNames)
+    function entrylist_check(entryList, expectedLength, expectedNames, testDescription = '')
     {
-        test_equals(entryList.length, expectedLength, 'There should be ' + expectedLength + ' entries.');
-        test_true(entrylist_order_check(entryList), 'Entries in entrylist should be in order.');
-        for (var i = 0; i < entryList.length; ++i)
+        test_equals(entryList.length, expectedLength, testDescription + 'There should be ' + expectedLength + ' entries.');
+        test_true(entrylist_order_check(entryList), testDescription + 'Entries in entrylist should be in order.');
+        for (let i = 0; i < entryList.length; ++i)
         {
-            entry_check(entryList[i], expectedNames);
+            entry_check(entryList[i], expectedNames, testDescription + 'Entry_list ' + i + '. ');
         }
     }
 
diff --git a/user-timing/user-timing-tojson.html b/user-timing/user-timing-tojson.html
new file mode 100644
index 0000000..6aef7fa
--- /dev/null
+++ b/user-timing/user-timing-tojson.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+const keys = [
+  'name',
+  'entryType',
+  'startTime',
+  'duration',
+];
+test(() => {
+  performance.mark('a');
+  const markEntries = performance.getEntriesByType('mark');
+  assert_equals(1, markEntries.length);
+  const markEntry = markEntries[0];
+  assert_equals(markEntry.entryType, 'mark');
+  assert_equals(typeof(markEntry.toJSON), 'function');
+  const markJSON = markEntry.toJSON();
+  assert_equals(typeof(markJSON), 'object');
+  for (const key of keys) {
+    assert_equals(markJSON[key], markEntry[key], `PerformanceMark ${key} entry does not match its toJSON value`);
+  }
+}, 'Test toJSON() in PerformanceMark');
+
+test(() => {
+  performance.measure('m');
+  const measureEntries = performance.getEntriesByType('measure');
+  assert_equals(1, measureEntries.length);
+  const measureEntry = measureEntries[0];
+  assert_equals(measureEntry.entryType, 'measure');
+  assert_equals(typeof(measureEntry.toJSON), 'function');
+  const measureJSON = measureEntry.toJSON();
+  assert_equals(typeof(measureJSON), 'object');
+  for (const key of keys) {
+    assert_equals(measureJSON[key], measureEntry[key], `PerformanceMeasure ${key} entry does not match its toJSON value`);
+  }
+}, 'Test toJSON() in PerformanceMeasure');
+</script>
+</body>
+</html>
diff --git a/visual-viewport/viewport-resize-event-on-load-overflowing-page.html b/visual-viewport/viewport-resize-event-on-load-overflowing-page.html
index b0603c8..c4b3687 100644
--- a/visual-viewport/viewport-resize-event-on-load-overflowing-page.html
+++ b/visual-viewport/viewport-resize-event-on-load-overflowing-page.html
@@ -26,11 +26,13 @@
                 });
 
                 window.addEventListener('load', function() {
-                  requestAnimationFrame(
-                      t.step_func_done(function() {
-                          var isOverlay = calculateScrollbarThickness() == 0;
-                          assert_equals(numViewResizes, isOverlay ? 0 : 1);
-                      }));
+                  requestAnimationFrame(function() {
+                    requestAnimationFrame(
+                        t.step_func_done(function() {
+                            var isOverlay = calculateScrollbarThickness() == 0;
+                            assert_equals(numViewResizes, isOverlay ? 0 : 1);
+                        }));
+                    });
                 });
             }
         </script>
diff --git a/wai-aria/alertdialog_modal_false-manual.html b/wai-aria/alertdialog_modal_false-manual.html
index 6dd2cee..ee64c9d 100644
--- a/wai-aria/alertdialog_modal_false-manual.html
+++ b/wai-aria/alertdialog_modal_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>alertdialog modal false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Pane"
                ],
diff --git a/wai-aria/alertdialog_modal_true-manual.html b/wai-aria/alertdialog_modal_true-manual.html
index 5ff5660..4affe2f 100644
--- a/wai-aria/alertdialog_modal_true-manual.html
+++ b/wai-aria/alertdialog_modal_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>alertdialog modal true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -56,7 +55,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Pane"
                ],
diff --git a/wai-aria/application_activedescendant-manual.html b/wai-aria/application_activedescendant-manual.html
index 5a103d9..a6ef85a 100644
--- a/wai-aria/application_activedescendant-manual.html
+++ b/wai-aria/application_activedescendant-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>application activedescendant</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -92,13 +91,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Pane"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "application"
                ],
diff --git a/wai-aria/application_activedescendant_value_changes-manual.html b/wai-aria/application_activedescendant_value_changes-manual.html
index 04260aa..d923c84 100644
--- a/wai-aria/application_activedescendant_value_changes-manual.html
+++ b/wai-aria/application_activedescendant_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>application activedescendant value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -87,7 +86,7 @@
                   "event",
                   "type",
                   "is",
-                  "IA2_EVENT_ACTIVE_DESCENDANT_CHANGED"
+                  "EVENT_OBJECT_FOCUS"
                ]
             ],
             "MSAA" : [
@@ -113,13 +112,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Pane"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "application"
                ],
diff --git a/wai-aria/aria-current_not_declared-manual.html b/wai-aria/aria-current_not_declared-manual.html
index 25cf95c..ecf95bb 100644
--- a/wai-aria/aria-current_not_declared-manual.html
+++ b/wai-aria/aria-current_not_declared-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current not declared</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "role",
+                  "ControlType",
                   "is",
                   "ListItem"
                ],
@@ -90,7 +89,7 @@
                   "property",
                   "current",
                   "is",
-                  "false"
+                  ""
                ]
             ]
          },
diff --git a/wai-aria/aria-current_with_value_changes-manual.html b/wai-aria/aria-current_with_value_changes-manual.html
index f4297f1..d3f1eae 100644
--- a/wai-aria/aria-current_with_value_changes-manual.html
+++ b/wai-aria/aria-current_with_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -75,9 +74,9 @@
             "UIA" : [
                [
                   "property",
-                  "objectAttributes",
-                  "contains",
-                  "current:false"
+                  "current",
+                  "is",
+                  "false"
                ],
                [
                   "event",
diff --git a/wai-aria/aria-current_with_value_date-manual.html b/wai-aria/aria-current_with_value_date-manual.html
index 8f4cb42..b15f5ec 100644
--- a/wai-aria/aria-current_with_value_date-manual.html
+++ b/wai-aria/aria-current_with_value_date-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value date</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,8 +81,8 @@
             "UIA" : [
                [
                   "property",
-                  "role",
-                  "is",
+                  "controlPatterns",
+                  "contains",
                   "TableItem"
                ],
                [
diff --git a/wai-aria/aria-current_with_value_location-manual.html b/wai-aria/aria-current_with_value_location-manual.html
index 4e44443..e8cf762 100644
--- a/wai-aria/aria-current_with_value_location-manual.html
+++ b/wai-aria/aria-current_with_value_location-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value location</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "role",
+                  "ControlType",
                   "is",
                   "Image"
                ],
diff --git a/wai-aria/aria-current_with_value_page-manual.html b/wai-aria/aria-current_with_value_page-manual.html
index d8fe5a4..ba48b73 100644
--- a/wai-aria/aria-current_with_value_page-manual.html
+++ b/wai-aria/aria-current_with_value_page-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value page</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "role",
+                  "ControlType",
                   "is",
                   "HyperLink"
                ],
diff --git a/wai-aria/aria-current_with_value_step-manual.html b/wai-aria/aria-current_with_value_step-manual.html
index 4b730e7..6f4d598 100644
--- a/wai-aria/aria-current_with_value_step-manual.html
+++ b/wai-aria/aria-current_with_value_step-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value step</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "role",
+                  "ControlType",
                   "is",
                   "HyperLink"
                ],
diff --git a/wai-aria/aria-current_with_value_time-manual.html b/wai-aria/aria-current_with_value_time-manual.html
index ae78d83..046fb81 100644
--- a/wai-aria/aria-current_with_value_time-manual.html
+++ b/wai-aria/aria-current_with_value_time-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value time</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,8 +81,8 @@
             "UIA" : [
                [
                   "property",
-                  "role",
-                  "is",
+                  "controlPatterns",
+                  "contains",
                   "TableItem"
                ],
                [
diff --git a/wai-aria/aria-current_with_value_true-manual.html b/wai-aria/aria-current_with_value_true-manual.html
index 57dcf29..3ce799f 100644
--- a/wai-aria/aria-current_with_value_true-manual.html
+++ b/wai-aria/aria-current_with_value_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "role",
+                  "ControlType",
                   "is",
                   "ListItem"
                ],
diff --git a/wai-aria/aria-current_with_value_unspecified-manual.html b/wai-aria/aria-current_with_value_unspecified-manual.html
index 74486b8..c017a00 100644
--- a/wai-aria/aria-current_with_value_unspecified-manual.html
+++ b/wai-aria/aria-current_with_value_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-current with value unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "role",
+                  "ControlType",
                   "is",
                   "ListItem"
                ],
@@ -84,7 +83,7 @@
                   "property",
                   "current",
                   "is",
-                  "false"
+                  ""
                ]
             ]
          },
diff --git a/wai-aria/aria-details_pointing_to_details_element-manual.html b/wai-aria/aria-details_pointing_to_details_element-manual.html
index bec86ac..6a0d9d7 100644
--- a/wai-aria/aria-details_pointing_to_details_element-manual.html
+++ b/wai-aria/aria-details_pointing_to_details_element-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-details pointing to details element</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/aria-details_pointing_to_div_element-manual.html b/wai-aria/aria-details_pointing_to_div_element-manual.html
index 239f3cc..8a46e78 100644
--- a/wai-aria/aria-details_pointing_to_div_element-manual.html
+++ b/wai-aria/aria-details_pointing_to_div_element-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>aria-details pointing to div element</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/article_in_feed_posinset_and_setsize-manual.html b/wai-aria/article_in_feed_posinset_and_setsize-manual.html
index d7145bd..9c89dc3 100644
--- a/wai-aria/article_in_feed_posinset_and_setsize-manual.html
+++ b/wai-aria/article_in_feed_posinset_and_setsize-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>article in feed posinset and setsize</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -124,13 +123,13 @@
             "UIA" : [
                [
                   "property",
-                  "control type",
+                  "ControlType",
                   "is",
                   "Group"
                ],
                [
                   "property",
-                  "localize control type",
+                  "LocalizedControlType",
                   "is",
                   "article"
                ],
diff --git a/wai-aria/article_in_feed_setsize_-1-manual.html b/wai-aria/article_in_feed_setsize_-1-manual.html
index 0c27017..0552f92 100644
--- a/wai-aria/article_in_feed_setsize_-1-manual.html
+++ b/wai-aria/article_in_feed_setsize_-1-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>article in feed setsize -1</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,13 +99,13 @@
             "UIA" : [
                [
                   "property",
-                  "control type",
+                  "ControlType",
                   "is",
                   "Group"
                ],
                [
                   "property",
-                  "localize control type",
+                  "LocalizedControlType",
                   "is",
                   "article"
                ],
diff --git a/wai-aria/article_not_in_feed_posinset_and_setsize-manual.html b/wai-aria/article_not_in_feed_posinset_and_setsize-manual.html
index ab45edc..91cb0e0 100644
--- a/wai-aria/article_not_in_feed_posinset_and_setsize-manual.html
+++ b/wai-aria/article_not_in_feed_posinset_and_setsize-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>article not in feed posinset and setsize</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,13 +129,13 @@
             "UIA" : [
                [
                   "property",
-                  "control type",
+                  "ControlType",
                   "is",
                   "Group"
                ],
                [
                   "property",
-                  "localize control type",
+                  "LocalizedControlType",
                   "is",
                   "article"
                ],
diff --git a/wai-aria/button_haspopup_dialog-manual.html b/wai-aria/button_haspopup_dialog-manual.html
index e69e6c9..8ba0a22 100644
--- a/wai-aria/button_haspopup_dialog-manual.html
+++ b/wai-aria/button_haspopup_dialog-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup dialog</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_emptystring-manual.html b/wai-aria/button_haspopup_emptystring-manual.html
index c41c728..e909148 100644
--- a/wai-aria/button_haspopup_emptystring-manual.html
+++ b/wai-aria/button_haspopup_emptystring-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup emptystring</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_false-manual.html b/wai-aria/button_haspopup_false-manual.html
index a41063c..9718899 100644
--- a/wai-aria/button_haspopup_false-manual.html
+++ b/wai-aria/button_haspopup_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_foo-manual.html b/wai-aria/button_haspopup_foo-manual.html
index 92bf3b3..a09d21c 100644
--- a/wai-aria/button_haspopup_foo-manual.html
+++ b/wai-aria/button_haspopup_foo-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup foo</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_grid-manual.html b/wai-aria/button_haspopup_grid-manual.html
index 52d325a..57c8efb 100644
--- a/wai-aria/button_haspopup_grid-manual.html
+++ b/wai-aria/button_haspopup_grid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup grid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_listbox-manual.html b/wai-aria/button_haspopup_listbox-manual.html
index f8494ea..4191fc4 100644
--- a/wai-aria/button_haspopup_listbox-manual.html
+++ b/wai-aria/button_haspopup_listbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup listbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_menu-manual.html b/wai-aria/button_haspopup_menu-manual.html
index 261f448..9378993 100644
--- a/wai-aria/button_haspopup_menu-manual.html
+++ b/wai-aria/button_haspopup_menu-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup menu</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_tree-manual.html b/wai-aria/button_haspopup_tree-manual.html
index 5dd268d..e62b87f 100644
--- a/wai-aria/button_haspopup_tree-manual.html
+++ b/wai-aria/button_haspopup_tree-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup tree</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_true-manual.html b/wai-aria/button_haspopup_true-manual.html
index 0d7b660..a6d9465 100644
--- a/wai-aria/button_haspopup_true-manual.html
+++ b/wai-aria/button_haspopup_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/button_haspopup_unspecified-manual.html b/wai-aria/button_haspopup_unspecified-manual.html
index b03efad..96d7095 100644
--- a/wai-aria/button_haspopup_unspecified-manual.html
+++ b/wai-aria/button_haspopup_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button haspopup unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -99,10 +98,10 @@
                   "Button"
                ],
                [
-                  "Control Pattern",
-                  "ExpandCollapse",
-                  "exists",
-                  "false"
+                  "property",
+                  "controlPatterns",
+                  "doesNotContain",
+                  "ExpandCollapse"
                ]
             ]
          },
diff --git a/wai-aria/button_roledescription_empty-manual.html b/wai-aria/button_roledescription_empty-manual.html
index 0de1df2..d9eb0fe 100644
--- a/wai-aria/button_roledescription_empty-manual.html
+++ b/wai-aria/button_roledescription_empty-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button roledescription empty</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,13 +69,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "Button"
                ]
diff --git a/wai-aria/button_roledescription_valid-manual.html b/wai-aria/button_roledescription_valid-manual.html
index a3ffbc0..cdcf607 100644
--- a/wai-aria/button_roledescription_valid-manual.html
+++ b/wai-aria/button_roledescription_valid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button roledescription valid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,13 +69,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "SassyButton"
                ]
diff --git a/wai-aria/button_roledescription_whitespace_only-manual.html b/wai-aria/button_roledescription_whitespace_only-manual.html
index 8edd5b9..59e4637 100644
--- a/wai-aria/button_roledescription_whitespace_only-manual.html
+++ b/wai-aria/button_roledescription_whitespace_only-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>button roledescription whitespace only</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,13 +69,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "Button"
                ]
diff --git a/wai-aria/cell-manual.html b/wai-aria/cell-manual.html
index f7ea068..d9f0eaf 100644
--- a/wai-aria/cell-manual.html
+++ b/wai-aria/cell-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -161,9 +160,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-colspan_2_on_div-manual.html b/wai-aria/cell_aria-colspan_2_on_div-manual.html
index 88168fc..fa120dc 100644
--- a/wai-aria/cell_aria-colspan_2_on_div-manual.html
+++ b/wai-aria/cell_aria-colspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-colspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,9 +81,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3-manual.html b/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3-manual.html
index 38b7c4c..a7c65fe 100644
--- a/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3-manual.html
+++ b/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-colspan 2 on td html colspan 3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,9 +75,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_headers_and_border-manual.html b/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_headers_and_border-manual.html
index b859a1d..057918c 100644
--- a/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_headers_and_border-manual.html
+++ b/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_headers_and_border-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-colspan 2 on td html colspan 3 with headers and border</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,9 +75,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_three_actual_columns-manual.html b/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_three_actual_columns-manual.html
index 76a7f29..2c05e6e 100644
--- a/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_three_actual_columns-manual.html
+++ b/wai-aria/cell_aria-colspan_2_on_td_html_colspan_3_with_three_actual_columns-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-colspan 2 on td html colspan 3 with three actual columns</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,9 +69,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-colspan_2_on_td_with_html_colspan_not_specified-manual.html b/wai-aria/cell_aria-colspan_2_on_td_with_html_colspan_not_specified-manual.html
index 182eefb..0621207 100644
--- a/wai-aria/cell_aria-colspan_2_on_td_with_html_colspan_not_specified-manual.html
+++ b/wai-aria/cell_aria-colspan_2_on_td_with_html_colspan_not_specified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-colspan 2 on td with html colspan not specified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,9 +81,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-rowspan_2_on_div-manual.html b/wai-aria/cell_aria-rowspan_2_on_div-manual.html
index d32621b..e943038 100644
--- a/wai-aria/cell_aria-rowspan_2_on_div-manual.html
+++ b/wai-aria/cell_aria-rowspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-rowspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,9 +81,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3-manual.html b/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3-manual.html
index 949af27..86ea1a4 100644
--- a/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3-manual.html
+++ b/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-rowspan 2 on td html rowspan 3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,9 +75,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3_with_three_actual_rows-manual.html b/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3_with_three_actual_rows-manual.html
index a34f7a1..f157204 100644
--- a/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3_with_three_actual_rows-manual.html
+++ b/wai-aria/cell_aria-rowspan_2_on_td_html_rowspan_3_with_three_actual_rows-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-rowspan 2 on td html rowspan 3 with three actual rows</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,9 +69,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_aria-rowspan_2_on_td_with_html_rowspan_not_specified-manual.html b/wai-aria/cell_aria-rowspan_2_on_td_with_html_rowspan_not_specified-manual.html
index c257e78..0978186 100644
--- a/wai-aria/cell_aria-rowspan_2_on_td_with_html_rowspan_not_specified-manual.html
+++ b/wai-aria/cell_aria-rowspan_2_on_td_with_html_rowspan_not_specified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell aria-rowspan 2 on td with html rowspan not specified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,9 +81,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_colindex_4-manual.html b/wai-aria/cell_colindex_4-manual.html
index 78769af..0200306 100644
--- a/wai-aria/cell_colindex_4-manual.html
+++ b/wai-aria/cell_colindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell colindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,9 +81,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/cell_rowindex_4-manual.html b/wai-aria/cell_rowindex_4-manual.html
index b4ff297..ea2937e 100644
--- a/wai-aria/cell_rowindex_4-manual.html
+++ b/wai-aria/cell_rowindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>cell rowindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,9 +75,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "TableItem"
+                  "DataItem"
                ],
                [
                   "property",
diff --git a/wai-aria/checkbox_readonly_false-manual.html b/wai-aria/checkbox_readonly_false-manual.html
index 59e25a6..558dc8e 100644
--- a/wai-aria/checkbox_readonly_false-manual.html
+++ b/wai-aria/checkbox_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>checkbox readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Checkbox"
                ],
diff --git a/wai-aria/checkbox_readonly_true-manual.html b/wai-aria/checkbox_readonly_true-manual.html
index e73f898..f3c8790 100644
--- a/wai-aria/checkbox_readonly_true-manual.html
+++ b/wai-aria/checkbox_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>checkbox readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Checkbox"
                ],
diff --git a/wai-aria/checkbox_readonly_unspecified-manual.html b/wai-aria/checkbox_readonly_unspecified-manual.html
index 73a3b93..583985f 100644
--- a/wai-aria/checkbox_readonly_unspecified-manual.html
+++ b/wai-aria/checkbox_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>checkbox readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Checkbox"
                ],
diff --git a/wai-aria/columnheader_aria-colspan_2_on_div-manual.html b/wai-aria/columnheader_aria-colspan_2_on_div-manual.html
index 7b151c5..4789ced 100644
--- a/wai-aria/columnheader_aria-colspan_2_on_div-manual.html
+++ b/wai-aria/columnheader_aria-colspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-colspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3-manual.html b/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3-manual.html
index 51824ee4..2ad8cb4 100644
--- a/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3-manual.html
+++ b/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-colspan 2 on th html colspan 3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3_with_three_actual_columns-manual.html b/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3_with_three_actual_columns-manual.html
index a26daed..3ac8701 100644
--- a/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3_with_three_actual_columns-manual.html
+++ b/wai-aria/columnheader_aria-colspan_2_on_th_html_colspan_3_with_three_actual_columns-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-colspan 2 on th html colspan 3 with three actual columns</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_aria-colspan_2_on_th_with_html_colspan_not_specified-manual.html b/wai-aria/columnheader_aria-colspan_2_on_th_with_html_colspan_not_specified-manual.html
index 44d11c1..82c7599 100644
--- a/wai-aria/columnheader_aria-colspan_2_on_th_with_html_colspan_not_specified-manual.html
+++ b/wai-aria/columnheader_aria-colspan_2_on_th_with_html_colspan_not_specified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-colspan 2 on th with html colspan not specified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_aria-rowspan_2_on_div-manual.html b/wai-aria/columnheader_aria-rowspan_2_on_div-manual.html
index cae113b..00c0068 100644
--- a/wai-aria/columnheader_aria-rowspan_2_on_div-manual.html
+++ b/wai-aria/columnheader_aria-rowspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-rowspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3-manual.html b/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3-manual.html
index efff45f..256a3d7 100644
--- a/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3-manual.html
+++ b/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-rowspan 2 on th html rowspan 3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3_with_three_actual_rows-manual.html b/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3_with_three_actual_rows-manual.html
index 3d964ed..6327e5f 100644
--- a/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3_with_three_actual_rows-manual.html
+++ b/wai-aria/columnheader_aria-rowspan_2_on_th_html_rowspan_3_with_three_actual_rows-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-rowspan 2 on th html rowspan 3 with three actual rows</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_aria-rowspan_2_on_th_with_html_rowspan_not_specified-manual.html b/wai-aria/columnheader_aria-rowspan_2_on_th_with_html_rowspan_not_specified-manual.html
index f2298d0..425eb5b 100644
--- a/wai-aria/columnheader_aria-rowspan_2_on_th_with_html_rowspan_not_specified-manual.html
+++ b/wai-aria/columnheader_aria-rowspan_2_on_th_with_html_rowspan_not_specified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader aria-rowspan 2 on th with html rowspan not specified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_colindex_4-manual.html b/wai-aria/columnheader_colindex_4-manual.html
index c48e5f1..f44e834 100644
--- a/wai-aria/columnheader_colindex_4-manual.html
+++ b/wai-aria/columnheader_colindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader colindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/columnheader_rowindex_4-manual.html b/wai-aria/columnheader_rowindex_4-manual.html
index 1e45ca3..7ab7e9c 100644
--- a/wai-aria/columnheader_rowindex_4-manual.html
+++ b/wai-aria/columnheader_rowindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader rowindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
@@ -102,7 +101,7 @@
   <p>This test examines the ARIA properties for columnheader rowindex 4.</p>
     <div role="table">
     <div role="row">
-       <div id="test" role="columnheader" aria-rowindex ="4">test cell</div>
+       <div id="test" role="columnheader" aria-rowindex="4">test cell</div>
     </div>
   </div>
 
diff --git a/wai-aria/columnheader_selected_false_not_automatically_propagated-manual.html b/wai-aria/columnheader_selected_false_not_automatically_propagated-manual.html
index 4a90597..7624f2a 100644
--- a/wai-aria/columnheader_selected_false_not_automatically_propagated-manual.html
+++ b/wai-aria/columnheader_selected_false_not_automatically_propagated-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader selected false not automatically propagated</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/columnheader_selected_true_not_automatically_propagated-manual.html b/wai-aria/columnheader_selected_true_not_automatically_propagated-manual.html
index 8d000b1..1bc364c 100644
--- a/wai-aria/columnheader_selected_true_not_automatically_propagated-manual.html
+++ b/wai-aria/columnheader_selected_true_not_automatically_propagated-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>columnheader selected true not automatically propagated</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/combobox_controls_an_invalid_id-manual.html b/wai-aria/combobox_controls_an_invalid_id-manual.html
index 88b4229..91188d8 100644
--- a/wai-aria/combobox_controls_an_invalid_id-manual.html
+++ b/wai-aria/combobox_controls_an_invalid_id-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox controls an invalid ID</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/combobox_haspopup_dialog-manual.html b/wai-aria/combobox_haspopup_dialog-manual.html
index 4a52872..d9cf618 100644
--- a/wai-aria/combobox_haspopup_dialog-manual.html
+++ b/wai-aria/combobox_haspopup_dialog-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup dialog</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -118,7 +117,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_haspopup_false-manual.html b/wai-aria/combobox_haspopup_false-manual.html
index 5a6036a..2b0ca94 100644
--- a/wai-aria/combobox_haspopup_false-manual.html
+++ b/wai-aria/combobox_haspopup_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_haspopup_grid-manual.html b/wai-aria/combobox_haspopup_grid-manual.html
index a68ce44..a3929cf 100644
--- a/wai-aria/combobox_haspopup_grid-manual.html
+++ b/wai-aria/combobox_haspopup_grid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup grid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_haspopup_listbox-manual.html b/wai-aria/combobox_haspopup_listbox-manual.html
index 4d97620..e1c9084 100644
--- a/wai-aria/combobox_haspopup_listbox-manual.html
+++ b/wai-aria/combobox_haspopup_listbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup listbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_haspopup_menu-manual.html b/wai-aria/combobox_haspopup_menu-manual.html
index 8be6b44..a9439ed 100644
--- a/wai-aria/combobox_haspopup_menu-manual.html
+++ b/wai-aria/combobox_haspopup_menu-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup menu</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_haspopup_tree-manual.html b/wai-aria/combobox_haspopup_tree-manual.html
index 546e426..a0602b5 100644
--- a/wai-aria/combobox_haspopup_tree-manual.html
+++ b/wai-aria/combobox_haspopup_tree-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup tree</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_haspopup_true-manual.html b/wai-aria/combobox_haspopup_true-manual.html
index 5f32404..c7548ce 100644
--- a/wai-aria/combobox_haspopup_true-manual.html
+++ b/wai-aria/combobox_haspopup_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -118,7 +117,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_haspopup_unspecified-manual.html b/wai-aria/combobox_haspopup_unspecified-manual.html
index f2d3599..cd61076 100644
--- a/wai-aria/combobox_haspopup_unspecified-manual.html
+++ b/wai-aria/combobox_haspopup_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox haspopup unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -118,7 +117,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_orientation_horizontal-manual.html b/wai-aria/combobox_orientation_horizontal-manual.html
index 1d173cd..a5bcc70 100644
--- a/wai-aria/combobox_orientation_horizontal-manual.html
+++ b/wai-aria/combobox_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_orientation_unspecified-manual.html b/wai-aria/combobox_orientation_unspecified-manual.html
index a31cce5..20bd53d 100644
--- a/wai-aria/combobox_orientation_unspecified-manual.html
+++ b/wai-aria/combobox_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,15 +87,21 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
                [
                   "property",
                   "IUIAutomationElement.Orientation",
-                  "is",
-                  "null"
+                  "isNot",
+                  "vertical"
+               ],
+               [
+                  "property",
+                  "IUIAutomationElement.Orientation",
+                  "isNot",
+                  "horizontal"
                ]
             ]
          },
diff --git a/wai-aria/combobox_orientation_vertical-manual.html b/wai-aria/combobox_orientation_vertical-manual.html
index 40e131e..189345c 100644
--- a/wai-aria/combobox_orientation_vertical-manual.html
+++ b/wai-aria/combobox_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_readonly_false-manual.html b/wai-aria/combobox_readonly_false-manual.html
index 476bf2f..978ab11 100644
--- a/wai-aria/combobox_readonly_false-manual.html
+++ b/wai-aria/combobox_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_readonly_true-manual.html b/wai-aria/combobox_readonly_true-manual.html
index a748476..26a4302 100644
--- a/wai-aria/combobox_readonly_true-manual.html
+++ b/wai-aria/combobox_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -118,7 +117,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/combobox_readonly_unspecified-manual.html b/wai-aria/combobox_readonly_unspecified-manual.html
index 1ee51e5..4ad87b2 100644
--- a/wai-aria/combobox_readonly_unspecified-manual.html
+++ b/wai-aria/combobox_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>combobox readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Combobox"
                ],
diff --git a/wai-aria/dialog_modal_false-manual.html b/wai-aria/dialog_modal_false-manual.html
index 254dbe5..6c906d3 100644
--- a/wai-aria/dialog_modal_false-manual.html
+++ b/wai-aria/dialog_modal_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>dialog modal false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Pane"
                ],
diff --git a/wai-aria/dialog_modal_true-manual.html b/wai-aria/dialog_modal_true-manual.html
index 2ce3fe6..02cbfb5 100644
--- a/wai-aria/dialog_modal_true-manual.html
+++ b/wai-aria/dialog_modal_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>dialog modal true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -56,7 +55,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Pane"
                ],
diff --git a/wai-aria/dialog_modal_unspecified-manual.html b/wai-aria/dialog_modal_unspecified-manual.html
index 80366a6..edd4605 100644
--- a/wai-aria/dialog_modal_unspecified-manual.html
+++ b/wai-aria/dialog_modal_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>dialog modal unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Pane"
                ],
diff --git a/wai-aria/div_element_without_role_roledescription_valid-manual.html b/wai-aria/div_element_without_role_roledescription_valid-manual.html
index 091c034..68b6cc5 100644
--- a/wai-aria/div_element_without_role_roledescription_valid-manual.html
+++ b/wai-aria/div_element_without_role_roledescription_valid-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>div element without role roledescription valid</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,7 +99,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Group"
                ]
diff --git a/wai-aria/errormessage_object_in_invalid_state-manual.html b/wai-aria/errormessage_object_in_invalid_state-manual.html
index fee1f88..9996568 100644
--- a/wai-aria/errormessage_object_in_invalid_state-manual.html
+++ b/wai-aria/errormessage_object_in_invalid_state-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>errormessage object in invalid state</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/errormessage_object_in_valid_state-manual.html b/wai-aria/errormessage_object_in_valid_state-manual.html
index ccad458..7c194c6 100644
--- a/wai-aria/errormessage_object_in_valid_state-manual.html
+++ b/wai-aria/errormessage_object_in_valid_state-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>errormessage object in valid state</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -38,15 +37,15 @@
                   "property",
                   "relations",
                   "doesNotContain",
-                  "IA2_RELATION_ERROR_MESSAGE"
+                  "IA2_RELATION_ERROR"
                ]
             ],
             "UIA" : [
                [
                   "property",
                   "IUIAutomationElement.ControllerFor",
-                  "exists",
-                  "false"
+                  "is",
+                  ""
                ]
             ]
          },
diff --git a/wai-aria/feed-manual.html b/wai-aria/feed-manual.html
index ad9f1da..91fc677 100644
--- a/wai-aria/feed-manual.html
+++ b/wai-aria/feed-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>feed</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,13 +69,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Group"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "feed"
                ]
diff --git a/wai-aria/figure-manual.html b/wai-aria/figure-manual.html
index 5111686..d609735 100644
--- a/wai-aria/figure-manual.html
+++ b/wai-aria/figure-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>figure</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,13 +69,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Group"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "figure"
                ]
diff --git a/wai-aria/grid_aria-readonly_false_automatically_propagated-manual.html b/wai-aria/grid_aria-readonly_false_automatically_propagated-manual.html
index 3676a10c..d373a30 100644
--- a/wai-aria/grid_aria-readonly_false_automatically_propagated-manual.html
+++ b/wai-aria/grid_aria-readonly_false_automatically_propagated-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid aria-readonly false automatically propagated</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
@@ -145,7 +144,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
@@ -208,7 +207,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_aria-readonly_true_automatically_propagated-manual.html b/wai-aria/grid_aria-readonly_true_automatically_propagated-manual.html
index 0be4cb4..7b6a22e 100644
--- a/wai-aria/grid_aria-readonly_true_automatically_propagated-manual.html
+++ b/wai-aria/grid_aria-readonly_true_automatically_propagated-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid aria-readonly true automatically propagated</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
@@ -84,7 +83,7 @@
                   "property",
                   "controlPatterns",
                   "contains",
-                  "selectionitem"
+                  "SelectionItem"
                ],
                [
                   "property",
@@ -145,7 +144,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
@@ -208,7 +207,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_busy_false-manual.html b/wai-aria/grid_busy_false-manual.html
index 1a9572c..48edba5 100644
--- a/wai-aria/grid_busy_false-manual.html
+++ b/wai-aria/grid_busy_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid busy false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/grid_busy_true-manual.html b/wai-aria/grid_busy_true-manual.html
index 37c79ad..ddcdf01 100644
--- a/wai-aria/grid_busy_true-manual.html
+++ b/wai-aria/grid_busy_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid busy true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -106,7 +105,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/grid_busy_value_changes-manual.html b/wai-aria/grid_busy_value_changes-manual.html
index 3752951..4891198 100644
--- a/wai-aria/grid_busy_value_changes-manual.html
+++ b/wai-aria/grid_busy_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid busy value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -149,7 +148,7 @@
                ],
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/grid_colcount_8-manual.html b/wai-aria/grid_colcount_8-manual.html
index 5064172..0f03b12 100644
--- a/wai-aria/grid_colcount_8-manual.html
+++ b/wai-aria/grid_colcount_8-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid colcount 8</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/grid_columnheader_readonly_false-manual.html b/wai-aria/grid_columnheader_readonly_false-manual.html
index 6ff7fe3..bb8dd34 100644
--- a/wai-aria/grid_columnheader_readonly_false-manual.html
+++ b/wai-aria/grid_columnheader_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid columnheader readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_columnheader_readonly_true-manual.html b/wai-aria/grid_columnheader_readonly_true-manual.html
index d90852a..a81e4cb 100644
--- a/wai-aria/grid_columnheader_readonly_true-manual.html
+++ b/wai-aria/grid_columnheader_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid columnheader readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_columnheader_readonly_unspecified-manual.html b/wai-aria/grid_columnheader_readonly_unspecified-manual.html
index 35910c3..d45d3b9 100644
--- a/wai-aria/grid_columnheader_readonly_unspecified-manual.html
+++ b/wai-aria/grid_columnheader_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid columnheader readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -62,7 +61,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_columnheader_required_false-manual.html b/wai-aria/grid_columnheader_required_false-manual.html
index 45a5002..b8f7ae3 100644
--- a/wai-aria/grid_columnheader_required_false-manual.html
+++ b/wai-aria/grid_columnheader_required_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid columnheader required false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_columnheader_required_true-manual.html b/wai-aria/grid_columnheader_required_true-manual.html
index 41c936f..76155ab 100644
--- a/wai-aria/grid_columnheader_required_true-manual.html
+++ b/wai-aria/grid_columnheader_required_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid columnheader required true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_columnheader_required_unspecified-manual.html b/wai-aria/grid_columnheader_required_unspecified-manual.html
index feeef96..491cbed 100644
--- a/wai-aria/grid_columnheader_required_unspecified-manual.html
+++ b/wai-aria/grid_columnheader_required_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid columnheader required unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_rowcount_3-manual.html b/wai-aria/grid_rowcount_3-manual.html
index 6a4b140..3079b59 100644
--- a/wai-aria/grid_rowcount_3-manual.html
+++ b/wai-aria/grid_rowcount_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid rowcount 3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/grid_rowheader_readonly_false-manual.html b/wai-aria/grid_rowheader_readonly_false-manual.html
index d7e923f..883918f 100644
--- a/wai-aria/grid_rowheader_readonly_false-manual.html
+++ b/wai-aria/grid_rowheader_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid rowheader readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -62,7 +61,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_rowheader_readonly_true-manual.html b/wai-aria/grid_rowheader_readonly_true-manual.html
index 62351e0..5d53c36 100644
--- a/wai-aria/grid_rowheader_readonly_true-manual.html
+++ b/wai-aria/grid_rowheader_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid rowheader readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -62,7 +61,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_rowheader_readonly_unspecified-manual.html b/wai-aria/grid_rowheader_readonly_unspecified-manual.html
index 1815252..f9321ca 100644
--- a/wai-aria/grid_rowheader_readonly_unspecified-manual.html
+++ b/wai-aria/grid_rowheader_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid rowheader readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -62,7 +61,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_rowheader_required_false-manual.html b/wai-aria/grid_rowheader_required_false-manual.html
index 92aab07..4fafba5 100644
--- a/wai-aria/grid_rowheader_required_false-manual.html
+++ b/wai-aria/grid_rowheader_required_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid rowheader required false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_rowheader_required_true-manual.html b/wai-aria/grid_rowheader_required_true-manual.html
index 69df488..0f14d2d 100644
--- a/wai-aria/grid_rowheader_required_true-manual.html
+++ b/wai-aria/grid_rowheader_required_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid rowheader required true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/grid_rowheader_required_unspecified-manual.html b/wai-aria/grid_rowheader_required_unspecified-manual.html
index e70bd6a..208f0c5 100644
--- a/wai-aria/grid_rowheader_required_unspecified-manual.html
+++ b/wai-aria/grid_rowheader_required_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>grid rowheader required unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -70,7 +69,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/gridcell_aria-colspan_2_on_div-manual.html b/wai-aria/gridcell_aria-colspan_2_on_div-manual.html
index 9ef3f28..c151087 100644
--- a/wai-aria/gridcell_aria-colspan_2_on_div-manual.html
+++ b/wai-aria/gridcell_aria-colspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>gridcell aria-colspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/gridcell_aria-rowspan_2_on_div-manual.html b/wai-aria/gridcell_aria-rowspan_2_on_div-manual.html
index ffb187d..318709b 100644
--- a/wai-aria/gridcell_aria-rowspan_2_on_div-manual.html
+++ b/wai-aria/gridcell_aria-rowspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>gridcell aria-rowspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/gridcell_colindex_4-manual.html b/wai-aria/gridcell_colindex_4-manual.html
index d90bee3..6ef4fb3 100644
--- a/wai-aria/gridcell_colindex_4-manual.html
+++ b/wai-aria/gridcell_colindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>gridcell colindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/gridcell_rowindex_4-manual.html b/wai-aria/gridcell_rowindex_4-manual.html
index 478fc14..8b9daaa 100644
--- a/wai-aria/gridcell_rowindex_4-manual.html
+++ b/wai-aria/gridcell_rowindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>gridcell rowindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/group_hidden_undefined_element_not_rendered-manual.html b/wai-aria/group_hidden_undefined_element_not_rendered-manual.html
index 0f3ea8a..ef94785 100644
--- a/wai-aria/group_hidden_undefined_element_not_rendered-manual.html
+++ b/wai-aria/group_hidden_undefined_element_not_rendered-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>group hidden undefined element not rendered</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -21,7 +20,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -29,7 +28,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -37,7 +36,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -45,7 +44,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -53,7 +52,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ]
diff --git a/wai-aria/group_hidden_undefined_element_rendered-manual.html b/wai-aria/group_hidden_undefined_element_rendered-manual.html
index 8f8baf8..92c067c 100644
--- a/wai-aria/group_hidden_undefined_element_rendered-manual.html
+++ b/wai-aria/group_hidden_undefined_element_rendered-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>group hidden undefined element rendered</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Group"
                ],
diff --git a/wai-aria/heading_level_unspecified-manual.html b/wai-aria/heading_level_unspecified-manual.html
index 69962b7..819bc7b 100644
--- a/wai-aria/heading_level_unspecified-manual.html
+++ b/wai-aria/heading_level_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>heading level unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -80,15 +79,15 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Text"
                ],
                [
                   "property",
-                  "StyleId_Heading2",
+                  "StyleId_Heading",
                   "is",
-                  "present"
+                  "2"
                ],
                [
                   "property",
diff --git a/wai-aria/keyshortcuts_multiple_shortcuts-manual.html b/wai-aria/keyshortcuts_multiple_shortcuts-manual.html
index f28e9d3..ce4074a 100644
--- a/wai-aria/keyshortcuts_multiple_shortcuts-manual.html
+++ b/wai-aria/keyshortcuts_multiple_shortcuts-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>keyshortcuts multiple shortcuts</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -48,7 +47,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
diff --git a/wai-aria/keyshortcuts_one_shortcut-manual.html b/wai-aria/keyshortcuts_one_shortcut-manual.html
index aecb17e..d0238db 100644
--- a/wai-aria/keyshortcuts_one_shortcut-manual.html
+++ b/wai-aria/keyshortcuts_one_shortcut-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>keyshortcuts one shortcut</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -48,7 +47,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
diff --git a/wai-aria/listbox_busy_false-manual.html b/wai-aria/listbox_busy_false-manual.html
index 59e3ad6..9f6b8b3 100644
--- a/wai-aria/listbox_busy_false-manual.html
+++ b/wai-aria/listbox_busy_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox busy false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listbox_busy_true-manual.html b/wai-aria/listbox_busy_true-manual.html
index 5a618aa..c8388f7 100644
--- a/wai-aria/listbox_busy_true-manual.html
+++ b/wai-aria/listbox_busy_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox busy true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -74,7 +73,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listbox_orientation_horizontal-manual.html b/wai-aria/listbox_orientation_horizontal-manual.html
index 8d75c69..c718a54 100644
--- a/wai-aria/listbox_orientation_horizontal-manual.html
+++ b/wai-aria/listbox_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listbox_orientation_unspecified-manual.html b/wai-aria/listbox_orientation_unspecified-manual.html
index 3627627..7dd2762 100644
--- a/wai-aria/listbox_orientation_unspecified-manual.html
+++ b/wai-aria/listbox_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listbox_orientation_vertical-manual.html b/wai-aria/listbox_orientation_vertical-manual.html
index ba8b77b..8bcca03 100644
--- a/wai-aria/listbox_orientation_vertical-manual.html
+++ b/wai-aria/listbox_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listbox_readonly_false-manual.html b/wai-aria/listbox_readonly_false-manual.html
index 4733d64..b3bde92 100644
--- a/wai-aria/listbox_readonly_false-manual.html
+++ b/wai-aria/listbox_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listbox_readonly_true-manual.html b/wai-aria/listbox_readonly_true-manual.html
index 3a9f794..3021391 100644
--- a/wai-aria/listbox_readonly_true-manual.html
+++ b/wai-aria/listbox_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listbox_readonly_unspecified-manual.html b/wai-aria/listbox_readonly_unspecified-manual.html
index 15ed93f..9283ad2 100644
--- a/wai-aria/listbox_readonly_unspecified-manual.html
+++ b/wai-aria/listbox_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listbox readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/listitem_setsize_-1-manual.html b/wai-aria/listitem_setsize_-1-manual.html
index 48cd2d0..7537a4b 100644
--- a/wai-aria/listitem_setsize_-1-manual.html
+++ b/wai-aria/listitem_setsize_-1-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>listitem setsize -1</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "control type",
+                  "ControlType",
                   "is",
                   "ListItem"
                ],
@@ -108,7 +107,7 @@
                   "property",
                   "controlPatterns",
                   "contains",
-                  "selectionitem"
+                  "SelectionItem"
                ]
             ]
          },
diff --git a/wai-aria/menu_orientation_horizontal-manual.html b/wai-aria/menu_orientation_horizontal-manual.html
index d7a00c9..184c420 100644
--- a/wai-aria/menu_orientation_horizontal-manual.html
+++ b/wai-aria/menu_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menu orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Menu"
                ],
diff --git a/wai-aria/menu_orientation_unspecified-manual.html b/wai-aria/menu_orientation_unspecified-manual.html
index f0a0266..07e8e69 100644
--- a/wai-aria/menu_orientation_unspecified-manual.html
+++ b/wai-aria/menu_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menu orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Menu"
                ],
diff --git a/wai-aria/menu_orientation_vertical-manual.html b/wai-aria/menu_orientation_vertical-manual.html
index 530918a..10a65e9 100644
--- a/wai-aria/menu_orientation_vertical-manual.html
+++ b/wai-aria/menu_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menu orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Menu"
                ],
diff --git a/wai-aria/menubar_busy_false-manual.html b/wai-aria/menubar_busy_false-manual.html
index e86c24a..8224d79 100644
--- a/wai-aria/menubar_busy_false-manual.html
+++ b/wai-aria/menubar_busy_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menubar busy false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuBar"
                ],
diff --git a/wai-aria/menubar_busy_true-manual.html b/wai-aria/menubar_busy_true-manual.html
index 6665c95..b1695dc 100644
--- a/wai-aria/menubar_busy_true-manual.html
+++ b/wai-aria/menubar_busy_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menubar busy true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuBar"
                ],
diff --git a/wai-aria/menubar_orientation_horizontal-manual.html b/wai-aria/menubar_orientation_horizontal-manual.html
index e480aa3..e47a68e 100644
--- a/wai-aria/menubar_orientation_horizontal-manual.html
+++ b/wai-aria/menubar_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menubar orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuBar"
                ],
diff --git a/wai-aria/menubar_orientation_unspecified-manual.html b/wai-aria/menubar_orientation_unspecified-manual.html
index dbb1623..0e945ae 100644
--- a/wai-aria/menubar_orientation_unspecified-manual.html
+++ b/wai-aria/menubar_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menubar orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuBar"
                ],
diff --git a/wai-aria/menubar_orientation_vertical-manual.html b/wai-aria/menubar_orientation_vertical-manual.html
index a258dd8..c0830ab 100644
--- a/wai-aria/menubar_orientation_vertical-manual.html
+++ b/wai-aria/menubar_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menubar orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuBar"
                ],
diff --git a/wai-aria/menuitem_posinset_and_setsize-manual.html b/wai-aria/menuitem_posinset_and_setsize-manual.html
index a8bfb63..ac0ec88 100644
--- a/wai-aria/menuitem_posinset_and_setsize-manual.html
+++ b/wai-aria/menuitem_posinset_and_setsize-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitem posinset and setsize</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -106,8 +105,8 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
-                  "isNot",
+                  "ControlType",
+                  "is",
                   "MenuItem"
                ],
                [
diff --git a/wai-aria/menuitemcheckbox_posinset_and_setsize-manual.html b/wai-aria/menuitemcheckbox_posinset_and_setsize-manual.html
index ec0c7a3..cf867c1 100644
--- a/wai-aria/menuitemcheckbox_posinset_and_setsize-manual.html
+++ b/wai-aria/menuitemcheckbox_posinset_and_setsize-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemcheckbox posinset and setsize</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -136,8 +135,8 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
-                  "isNot",
+                  "ControlType",
+                  "is",
                   "MenuItem"
                ],
                [
diff --git a/wai-aria/menuitemcheckbox_readonly_false-manual.html b/wai-aria/menuitemcheckbox_readonly_false-manual.html
index bee4e4f..59f12ed 100644
--- a/wai-aria/menuitemcheckbox_readonly_false-manual.html
+++ b/wai-aria/menuitemcheckbox_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemcheckbox readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuItem"
                ],
diff --git a/wai-aria/menuitemcheckbox_readonly_true-manual.html b/wai-aria/menuitemcheckbox_readonly_true-manual.html
index e30b1bd..faa9703 100644
--- a/wai-aria/menuitemcheckbox_readonly_true-manual.html
+++ b/wai-aria/menuitemcheckbox_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemcheckbox readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuItem"
                ],
diff --git a/wai-aria/menuitemcheckbox_readonly_unspecified-manual.html b/wai-aria/menuitemcheckbox_readonly_unspecified-manual.html
index b9997ab..d9b5dc2 100644
--- a/wai-aria/menuitemcheckbox_readonly_unspecified-manual.html
+++ b/wai-aria/menuitemcheckbox_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemcheckbox readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuItem"
                ],
diff --git a/wai-aria/menuitemradio_posinset_and_setsize-manual.html b/wai-aria/menuitemradio_posinset_and_setsize-manual.html
index 2d32a57..d512e95 100644
--- a/wai-aria/menuitemradio_posinset_and_setsize-manual.html
+++ b/wai-aria/menuitemradio_posinset_and_setsize-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemradio posinset and setsize</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -136,8 +135,8 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
-                  "isNot",
+                  "ControlType",
+                  "is",
                   "MenuItem"
                ],
                [
diff --git a/wai-aria/menuitemradio_readonly_false-manual.html b/wai-aria/menuitemradio_readonly_false-manual.html
index d707725..164bde7 100644
--- a/wai-aria/menuitemradio_readonly_false-manual.html
+++ b/wai-aria/menuitemradio_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemradio readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuItem"
                ],
@@ -96,7 +95,7 @@
                   "property",
                   "controlPatterns",
                   "contains",
-                  "SelectItem"
+                  "SelectionItem"
                ],
                [
                   "property",
diff --git a/wai-aria/menuitemradio_readonly_true-manual.html b/wai-aria/menuitemradio_readonly_true-manual.html
index 990b4fa..70690f3 100644
--- a/wai-aria/menuitemradio_readonly_true-manual.html
+++ b/wai-aria/menuitemradio_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemradio readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuItem"
                ],
@@ -108,7 +107,7 @@
                   "property",
                   "controlPatterns",
                   "contains",
-                  "SelectItem"
+                  "SelectionItem"
                ],
                [
                   "property",
diff --git a/wai-aria/menuitemradio_readonly_unspecified-manual.html b/wai-aria/menuitemradio_readonly_unspecified-manual.html
index ccad591..a4257c4 100644
--- a/wai-aria/menuitemradio_readonly_unspecified-manual.html
+++ b/wai-aria/menuitemradio_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>menuitemradio readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "MenuItem"
                ],
@@ -96,7 +95,7 @@
                   "property",
                   "controlPatterns",
                   "contains",
-                  "SelectItem"
+                  "SelectionItem"
                ],
                [
                   "property",
diff --git a/wai-aria/none-manual.html b/wai-aria/none-manual.html
index a286a59..ded346f 100644
--- a/wai-aria/none-manual.html
+++ b/wai-aria/none-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>none</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -21,7 +20,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -29,7 +28,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -37,7 +36,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -45,7 +44,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ],
@@ -53,7 +52,7 @@
                [
                   "property",
                   "accessible",
-                  "exists",
+                  "is",
                   "false"
                ]
             ]
diff --git a/wai-aria/option_selected_false-manual.html b/wai-aria/option_selected_false-manual.html
index e77d50c..02b1ec8 100644
--- a/wai-aria/option_selected_false-manual.html
+++ b/wai-aria/option_selected_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>option selected false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ListItem"
                ],
diff --git a/wai-aria/option_selected_true-manual.html b/wai-aria/option_selected_true-manual.html
index b9839f8..8bb7dd7 100644
--- a/wai-aria/option_selected_true-manual.html
+++ b/wai-aria/option_selected_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>option selected true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ListItem"
                ],
diff --git a/wai-aria/option_selected_undefined-manual.html b/wai-aria/option_selected_undefined-manual.html
index f13ccfa..5930de9 100644
--- a/wai-aria/option_selected_undefined-manual.html
+++ b/wai-aria/option_selected_undefined-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>option selected undefined</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ListItem"
                ],
diff --git a/wai-aria/option_selected_value_changes-manual.html b/wai-aria/option_selected_value_changes-manual.html
index f11e489..2c02a87 100644
--- a/wai-aria/option_selected_value_changes-manual.html
+++ b/wai-aria/option_selected_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>option selected value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/radiogroup_orientation_horizontal-manual.html b/wai-aria/radiogroup_orientation_horizontal-manual.html
index 82b91ec..4943335 100644
--- a/wai-aria/radiogroup_orientation_horizontal-manual.html
+++ b/wai-aria/radiogroup_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radiogroup orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/radiogroup_orientation_unspecified-manual.html b/wai-aria/radiogroup_orientation_unspecified-manual.html
index 97e5e5a..40ceab7 100644
--- a/wai-aria/radiogroup_orientation_unspecified-manual.html
+++ b/wai-aria/radiogroup_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radiogroup orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/radiogroup_orientation_vertical-manual.html b/wai-aria/radiogroup_orientation_vertical-manual.html
index 4442f25..def4543 100644
--- a/wai-aria/radiogroup_orientation_vertical-manual.html
+++ b/wai-aria/radiogroup_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radiogroup orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/radiogroup_readonly_false-manual.html b/wai-aria/radiogroup_readonly_false-manual.html
index e99875a..102f517 100644
--- a/wai-aria/radiogroup_readonly_false-manual.html
+++ b/wai-aria/radiogroup_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radiogroup readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/radiogroup_readonly_true-manual.html b/wai-aria/radiogroup_readonly_true-manual.html
index 9955b9d..0c97e24 100644
--- a/wai-aria/radiogroup_readonly_true-manual.html
+++ b/wai-aria/radiogroup_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radiogroup readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/radiogroup_readonly_unspecified-manual.html b/wai-aria/radiogroup_readonly_unspecified-manual.html
index d822301..6e8c641 100644
--- a/wai-aria/radiogroup_readonly_unspecified-manual.html
+++ b/wai-aria/radiogroup_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>radiogroup readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "List"
                ],
diff --git a/wai-aria/region_with_name-manual.html b/wai-aria/region_with_name-manual.html
index 0763660..ba868a7 100644
--- a/wai-aria/region_with_name-manual.html
+++ b/wai-aria/region_with_name-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>region with name</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/region_without_name-manual.html b/wai-aria/region_without_name-manual.html
index bf2333e..84ea665 100644
--- a/wai-aria/region_without_name-manual.html
+++ b/wai-aria/region_without_name-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>region without name</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/row_colindex_4-manual.html b/wai-aria/row_colindex_4-manual.html
index 060217f..82ee257 100644
--- a/wai-aria/row_colindex_4-manual.html
+++ b/wai-aria/row_colindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>row colindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -64,7 +63,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
@@ -109,6 +108,20 @@
                   "is",
                   "4"
                ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "DataItem"
+               ],
+               [
+                  "property",
+                  "Column",
+                  "is",
+                  "3"
+               ]
             ]
          },
          "title" : "step 2",
diff --git a/wai-aria/row_rowindex_4-manual.html b/wai-aria/row_rowindex_4-manual.html
index e391dff..1333219 100644
--- a/wai-aria/row_rowindex_4-manual.html
+++ b/wai-aria/row_rowindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>row rowindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -64,7 +63,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
@@ -109,6 +108,20 @@
                   "is",
                   "4"
                ]
+            ],
+            "UIA" : [
+               [
+                  "property",
+                  "ControlType",
+                  "is",
+                  "DataItem"
+               ],
+               [
+                  "property",
+                  "Row",
+                  "is",
+                  "3"
+               ]
             ]
          },
          "title" : "step 2",
diff --git a/wai-aria/rowheader_aria-colspan_2_on_div-manual.html b/wai-aria/rowheader_aria-colspan_2_on_div-manual.html
index 883ebe0..3d82888 100644
--- a/wai-aria/rowheader_aria-colspan_2_on_div-manual.html
+++ b/wai-aria/rowheader_aria-colspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowheader aria-colspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/rowheader_aria-rowspan_2_on_div-manual.html b/wai-aria/rowheader_aria-rowspan_2_on_div-manual.html
index e5c331f..4ddf4f5 100644
--- a/wai-aria/rowheader_aria-rowspan_2_on_div-manual.html
+++ b/wai-aria/rowheader_aria-rowspan_2_on_div-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowheader aria-rowspan 2 on div</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/rowheader_colindex_4-manual.html b/wai-aria/rowheader_colindex_4-manual.html
index a48cd2f..8b1eb47 100644
--- a/wai-aria/rowheader_colindex_4-manual.html
+++ b/wai-aria/rowheader_colindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowheader colindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
diff --git a/wai-aria/rowheader_rowindex_4-manual.html b/wai-aria/rowheader_rowindex_4-manual.html
index a146fc9..716403c 100644
--- a/wai-aria/rowheader_rowindex_4-manual.html
+++ b/wai-aria/rowheader_rowindex_4-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowheader rowindex 4</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "HeaderItem"
                ],
@@ -102,7 +101,7 @@
   <p>This test examines the ARIA properties for rowheader rowindex 4.</p>
     <div role="table">
     <div role="row">
-       <div id="test" role="rowheader" aria-rowindex ="4">test cell</div>
+       <div id="test" role="rowheader" aria-rowindex="4">test cell</div>
     </div>
   </div>
 
diff --git a/wai-aria/rowheader_selected_false_not_automatically_propagated-manual.html b/wai-aria/rowheader_selected_false_not_automatically_propagated-manual.html
index 948b11e..7cd9a55 100644
--- a/wai-aria/rowheader_selected_false_not_automatically_propagated-manual.html
+++ b/wai-aria/rowheader_selected_false_not_automatically_propagated-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowheader selected false not automatically propagated</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/rowheader_selected_true_not_automatically_propagated-manual.html b/wai-aria/rowheader_selected_true_not_automatically_propagated-manual.html
index 26aab4e..51b2b68 100644
--- a/wai-aria/rowheader_selected_true_not_automatically_propagated-manual.html
+++ b/wai-aria/rowheader_selected_true_not_automatically_propagated-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>rowheader selected true not automatically propagated</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataItem"
                ],
diff --git a/wai-aria/scrollbar_all_values_unspecified-manual.html b/wai-aria/scrollbar_all_values_unspecified-manual.html
index 00d9314..910fe2b 100644
--- a/wai-aria/scrollbar_all_values_unspecified-manual.html
+++ b/wai-aria/scrollbar_all_values_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>scrollbar all values unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "scrollbar"
                ],
diff --git a/wai-aria/scrollbar_only_valuenow_unspecified-manual.html b/wai-aria/scrollbar_only_valuenow_unspecified-manual.html
index 8efb7ed..73e3158 100644
--- a/wai-aria/scrollbar_only_valuenow_unspecified-manual.html
+++ b/wai-aria/scrollbar_only_valuenow_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>scrollbar only valuenow unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ScrollBar"
                ],
diff --git a/wai-aria/scrollbar_orientation_unspecified-manual.html b/wai-aria/scrollbar_orientation_unspecified-manual.html
index 890a464..32791ae 100644
--- a/wai-aria/scrollbar_orientation_unspecified-manual.html
+++ b/wai-aria/scrollbar_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>scrollbar orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ScrollBar"
                ],
@@ -108,7 +107,7 @@
                   "property",
                   "interfaces",
                   "contains",
-                  "Range control pattern"
+                  "RangeValue"
                ]
             ]
          },
diff --git a/wai-aria/searchbox-manual.html b/wai-aria/searchbox-manual.html
index 016f77a..979bd0f 100644
--- a/wai-aria/searchbox-manual.html
+++ b/wai-aria/searchbox-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,13 +87,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ]
diff --git a/wai-aria/searchbox_activedescendant-manual.html b/wai-aria/searchbox_activedescendant-manual.html
index 3976c26..a72cf93 100644
--- a/wai-aria/searchbox_activedescendant-manual.html
+++ b/wai-aria/searchbox_activedescendant-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox activedescendant</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,13 +111,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_activedescendant_value_changes-manual.html b/wai-aria/searchbox_activedescendant_value_changes-manual.html
index 9f2507c..956f931 100644
--- a/wai-aria/searchbox_activedescendant_value_changes-manual.html
+++ b/wai-aria/searchbox_activedescendant_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox activedescendant value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -105,7 +104,7 @@
                   "event",
                   "type",
                   "is",
-                  "IA2_EVENT_ACTIVE_DESCENDANT_CHANGED"
+                  "EVENT_OBJECT_FOCUS"
                ]
             ],
             "MSAA" : [
@@ -131,13 +130,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_autocomplete_both-manual.html b/wai-aria/searchbox_autocomplete_both-manual.html
index 1b67134..ea8b698 100644
--- a/wai-aria/searchbox_autocomplete_both-manual.html
+++ b/wai-aria/searchbox_autocomplete_both-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox autocomplete both</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/searchbox_autocomplete_inline-manual.html b/wai-aria/searchbox_autocomplete_inline-manual.html
index 4aa12aa..77e7076 100644
--- a/wai-aria/searchbox_autocomplete_inline-manual.html
+++ b/wai-aria/searchbox_autocomplete_inline-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox autocomplete inline</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/searchbox_autocomplete_list-manual.html b/wai-aria/searchbox_autocomplete_list-manual.html
index 778dfe9..0ce8068 100644
--- a/wai-aria/searchbox_autocomplete_list-manual.html
+++ b/wai-aria/searchbox_autocomplete_list-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox autocomplete list</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/searchbox_autocomplete_none-manual.html b/wai-aria/searchbox_autocomplete_none-manual.html
index 2374461..341d75c 100644
--- a/wai-aria/searchbox_autocomplete_none-manual.html
+++ b/wai-aria/searchbox_autocomplete_none-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox autocomplete none</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/searchbox_autocomplete_unspecified-manual.html b/wai-aria/searchbox_autocomplete_unspecified-manual.html
index 8b13749..59becae 100644
--- a/wai-aria/searchbox_autocomplete_unspecified-manual.html
+++ b/wai-aria/searchbox_autocomplete_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox autocomplete unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -102,7 +101,7 @@
                   "property",
                   "autocomplete",
                   "is",
-                  "none"
+                  ""
                ]
             ]
          },
diff --git a/wai-aria/searchbox_multiline_false-manual.html b/wai-aria/searchbox_multiline_false-manual.html
index 6c0053a..8f49eb6 100644
--- a/wai-aria/searchbox_multiline_false-manual.html
+++ b/wai-aria/searchbox_multiline_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox multiline false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,13 +93,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ]
diff --git a/wai-aria/searchbox_multiline_true-manual.html b/wai-aria/searchbox_multiline_true-manual.html
index 311cbf5..47e6c3a 100644
--- a/wai-aria/searchbox_multiline_true-manual.html
+++ b/wai-aria/searchbox_multiline_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox multiline true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,21 +99,21 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
                [
                   "property",
-                  "AriaProperties",
-                  "contains",
-                  "multiline:true"
+                  "AriaProperties.multiline",
+                  "is",
+                  "true"
                ]
             ]
          },
diff --git a/wai-aria/searchbox_multiline_unspecified-manual.html b/wai-aria/searchbox_multiline_unspecified-manual.html
index 240698f..5c1d7f5 100644
--- a/wai-aria/searchbox_multiline_unspecified-manual.html
+++ b/wai-aria/searchbox_multiline_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox multiline unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,13 +75,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ]
diff --git a/wai-aria/searchbox_placeholder-manual.html b/wai-aria/searchbox_placeholder-manual.html
index 086508f..da51345 100644
--- a/wai-aria/searchbox_placeholder-manual.html
+++ b/wai-aria/searchbox_placeholder-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox placeholder</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,13 +99,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_readonly_false-manual.html b/wai-aria/searchbox_readonly_false-manual.html
index 2c8d4ae..574666a 100644
--- a/wai-aria/searchbox_readonly_false-manual.html
+++ b/wai-aria/searchbox_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,13 +99,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_readonly_true-manual.html b/wai-aria/searchbox_readonly_true-manual.html
index ba0f388..833f1bc 100644
--- a/wai-aria/searchbox_readonly_true-manual.html
+++ b/wai-aria/searchbox_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,13 +99,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_readonly_unspecified-manual.html b/wai-aria/searchbox_readonly_unspecified-manual.html
index c3f49a2..51b9893 100644
--- a/wai-aria/searchbox_readonly_unspecified-manual.html
+++ b/wai-aria/searchbox_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,13 +99,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_required_false-manual.html b/wai-aria/searchbox_required_false-manual.html
index 6d57677..1bfc895 100644
--- a/wai-aria/searchbox_required_false-manual.html
+++ b/wai-aria/searchbox_required_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox required false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,13 +87,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_required_true-manual.html b/wai-aria/searchbox_required_true-manual.html
index 764fe33..7e26e5a 100644
--- a/wai-aria/searchbox_required_true-manual.html
+++ b/wai-aria/searchbox_required_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox required true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,13 +87,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/searchbox_required_unspecified-manual.html b/wai-aria/searchbox_required_unspecified-manual.html
index 592e0e4..13489ad 100644
--- a/wai-aria/searchbox_required_unspecified-manual.html
+++ b/wai-aria/searchbox_required_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>searchbox required unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,13 +87,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "search box"
                ],
diff --git a/wai-aria/separator_focusable_all_values_unspecified-manual.html b/wai-aria/separator_focusable_all_values_unspecified-manual.html
index 971f454..fb8e852 100644
--- a/wai-aria/separator_focusable_all_values_unspecified-manual.html
+++ b/wai-aria/separator_focusable_all_values_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator focusable all values unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Thumb"
                ],
diff --git a/wai-aria/separator_focusable_only_valuenow_unspecified-manual.html b/wai-aria/separator_focusable_only_valuenow_unspecified-manual.html
index abc35fe..680c2a3 100644
--- a/wai-aria/separator_focusable_only_valuenow_unspecified-manual.html
+++ b/wai-aria/separator_focusable_only_valuenow_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator focusable only valuenow unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Thumb"
                ],
diff --git a/wai-aria/separator_focusable_valuetext-manual.html b/wai-aria/separator_focusable_valuetext-manual.html
index edbb7bd..5992f15 100644
--- a/wai-aria/separator_focusable_valuetext-manual.html
+++ b/wai-aria/separator_focusable_valuetext-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator focusable valuetext</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -148,7 +147,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Thumb"
                ],
@@ -178,12 +177,6 @@
                ],
                [
                   "property",
-                  "controlPatterns",
-                  "contains",
-                  "Value"
-               ],
-               [
-                  "property",
                   "Value.Value",
                   "is",
                   "Bonaire"
diff --git a/wai-aria/separator_orientation_unspecified-manual.html b/wai-aria/separator_orientation_unspecified-manual.html
index 3b496a8..3a3901b 100644
--- a/wai-aria/separator_orientation_unspecified-manual.html
+++ b/wai-aria/separator_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Separator"
                ],
diff --git a/wai-aria/separator_unfocusable_all_values_unspecified-manual.html b/wai-aria/separator_unfocusable_all_values_unspecified-manual.html
index 8ab8e96..171370f 100644
--- a/wai-aria/separator_unfocusable_all_values_unspecified-manual.html
+++ b/wai-aria/separator_unfocusable_all_values_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator unfocusable all values unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -62,7 +61,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Separator"
                ]
diff --git a/wai-aria/separator_unfocusable_valuetext-manual.html b/wai-aria/separator_unfocusable_valuetext-manual.html
index 71527bf..db8ee63 100644
--- a/wai-aria/separator_unfocusable_valuetext-manual.html
+++ b/wai-aria/separator_unfocusable_valuetext-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>separator unfocusable valuetext</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Separator"
                ],
@@ -90,7 +89,7 @@
                   "property",
                   "controlPatterns",
                   "doesNotContain",
-                  "Value"
+                  "RangeValue"
                ]
             ]
          },
diff --git a/wai-aria/slider_all_values_unspecified-manual.html b/wai-aria/slider_all_values_unspecified-manual.html
index 2b3e440..0dea62e 100644
--- a/wai-aria/slider_all_values_unspecified-manual.html
+++ b/wai-aria/slider_all_values_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>slider all values unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Slider"
                ],
diff --git a/wai-aria/slider_only_valuenow_unspecified-manual.html b/wai-aria/slider_only_valuenow_unspecified-manual.html
index 9421dfc..ffcbc78 100644
--- a/wai-aria/slider_only_valuenow_unspecified-manual.html
+++ b/wai-aria/slider_only_valuenow_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>slider only valuenow unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Slider"
                ],
diff --git a/wai-aria/slider_orientation_unspecified-manual.html b/wai-aria/slider_orientation_unspecified-manual.html
index d882fc4..295b0a6 100644
--- a/wai-aria/slider_orientation_unspecified-manual.html
+++ b/wai-aria/slider_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>slider orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Slider"
                ],
@@ -108,7 +107,7 @@
                   "property",
                   "interfaces",
                   "contains",
-                  "Range control pattern"
+                  "RangeValue"
                ]
             ]
          },
diff --git a/wai-aria/slider_readonly_false-manual.html b/wai-aria/slider_readonly_false-manual.html
index 666cb12..f5a7098 100644
--- a/wai-aria/slider_readonly_false-manual.html
+++ b/wai-aria/slider_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>slider readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Slider"
                ],
diff --git a/wai-aria/slider_readonly_true-manual.html b/wai-aria/slider_readonly_true-manual.html
index f33e07e..2588079 100644
--- a/wai-aria/slider_readonly_true-manual.html
+++ b/wai-aria/slider_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>slider readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Slider"
                ],
diff --git a/wai-aria/slider_readonly_unspecified-manual.html b/wai-aria/slider_readonly_unspecified-manual.html
index 86f7500..271325a 100644
--- a/wai-aria/slider_readonly_unspecified-manual.html
+++ b/wai-aria/slider_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>slider readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Slider"
                ],
diff --git a/wai-aria/spinbutton_all_values_unspecified-manual.html b/wai-aria/spinbutton_all_values_unspecified-manual.html
index eb7e288..8da69af 100644
--- a/wai-aria/spinbutton_all_values_unspecified-manual.html
+++ b/wai-aria/spinbutton_all_values_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>spinbutton all values unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Spinner"
                ],
diff --git a/wai-aria/spinbutton_only_aria-valuenow_unspecified-manual.html b/wai-aria/spinbutton_only_aria-valuenow_unspecified-manual.html
index 8a9dc74..bbb9470 100644
--- a/wai-aria/spinbutton_only_aria-valuenow_unspecified-manual.html
+++ b/wai-aria/spinbutton_only_aria-valuenow_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>spinbutton only aria-valuenow unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -130,7 +129,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Spinner"
                ],
diff --git a/wai-aria/spinbutton_readonly_false-manual.html b/wai-aria/spinbutton_readonly_false-manual.html
index bda4b58..5e9429c 100644
--- a/wai-aria/spinbutton_readonly_false-manual.html
+++ b/wai-aria/spinbutton_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>spinbutton readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Spinner"
                ],
diff --git a/wai-aria/spinbutton_readonly_true-manual.html b/wai-aria/spinbutton_readonly_true-manual.html
index 6234e50..a7a3abe 100644
--- a/wai-aria/spinbutton_readonly_true-manual.html
+++ b/wai-aria/spinbutton_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>spinbutton readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Spinner"
                ],
diff --git a/wai-aria/spinbutton_readonly_unspecified-manual.html b/wai-aria/spinbutton_readonly_unspecified-manual.html
index c96f2e0..0976b07 100644
--- a/wai-aria/spinbutton_readonly_unspecified-manual.html
+++ b/wai-aria/spinbutton_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>spinbutton readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Spinner"
                ],
diff --git a/wai-aria/switch_checked_false-manual.html b/wai-aria/switch_checked_false-manual.html
index f499550..ac61802 100644
--- a/wai-aria/switch_checked_false-manual.html
+++ b/wai-aria/switch_checked_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch checked false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,13 +85,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
diff --git a/wai-aria/switch_checked_mixed-manual.html b/wai-aria/switch_checked_mixed-manual.html
index e6320e0..a674c7d 100644
--- a/wai-aria/switch_checked_mixed-manual.html
+++ b/wai-aria/switch_checked_mixed-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch checked mixed</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -92,13 +91,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
diff --git a/wai-aria/switch_checked_true-manual.html b/wai-aria/switch_checked_true-manual.html
index 8ae8579..e23e723 100644
--- a/wai-aria/switch_checked_true-manual.html
+++ b/wai-aria/switch_checked_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch checked true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
@@ -106,7 +105,7 @@
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
diff --git a/wai-aria/switch_checked_undefined-manual.html b/wai-aria/switch_checked_undefined-manual.html
index 7ae70c6..4f5399e 100644
--- a/wai-aria/switch_checked_undefined-manual.html
+++ b/wai-aria/switch_checked_undefined-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch checked undefined</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,13 +85,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
diff --git a/wai-aria/switch_checked_value_changes-manual.html b/wai-aria/switch_checked_value_changes-manual.html
index 699f332..b58e52f 100644
--- a/wai-aria/switch_checked_value_changes-manual.html
+++ b/wai-aria/switch_checked_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch checked value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -119,13 +118,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
@@ -139,7 +138,7 @@
                   "event",
                   "type",
                   "is",
-                  "Toggle.ToggleStateProperty"
+                  "TogglePattern.ToggleStateProperty"
                ]
             ]
          },
diff --git a/wai-aria/switch_readonly_false-manual.html b/wai-aria/switch_readonly_false-manual.html
index 4afbff1..e68b6bb 100644
--- a/wai-aria/switch_readonly_false-manual.html
+++ b/wai-aria/switch_readonly_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch readonly false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -80,13 +79,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
diff --git a/wai-aria/switch_readonly_true-manual.html b/wai-aria/switch_readonly_true-manual.html
index eab0bf5..605d2d0 100644
--- a/wai-aria/switch_readonly_true-manual.html
+++ b/wai-aria/switch_readonly_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch readonly true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -80,13 +79,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
diff --git a/wai-aria/switch_readonly_unspecified-manual.html b/wai-aria/switch_readonly_unspecified-manual.html
index f3e1f6b..0fd582c 100644
--- a/wai-aria/switch_readonly_unspecified-manual.html
+++ b/wai-aria/switch_readonly_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>switch readonly unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -80,13 +79,13 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Button"
                ],
                [
                   "property",
-                  "Localized Control Type",
+                  "LocalizedControlType",
                   "is",
                   "toggleswitch"
                ],
diff --git a/wai-aria/tab_posinset_and_setsize-manual.html b/wai-aria/tab_posinset_and_setsize-manual.html
index 6f3d796..f27f485 100644
--- a/wai-aria/tab_posinset_and_setsize-manual.html
+++ b/wai-aria/tab_posinset_and_setsize-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tab posinset and setsize</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -142,8 +141,8 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
-                  "isNot",
+                  "ControlType",
+                  "is",
                   "TabItem"
                ],
                [
diff --git a/wai-aria/table_colcount_-1-manual.html b/wai-aria/table_colcount_-1-manual.html
index 7516426..2135f6e 100644
--- a/wai-aria/table_colcount_-1-manual.html
+++ b/wai-aria/table_colcount_-1-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>table colcount -1</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Table"
                ],
diff --git a/wai-aria/table_colcount_8-manual.html b/wai-aria/table_colcount_8-manual.html
index daaa03f..1b8e0af 100644
--- a/wai-aria/table_colcount_8-manual.html
+++ b/wai-aria/table_colcount_8-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>table colcount 8</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -76,7 +75,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Table"
                ],
diff --git a/wai-aria/table_rowcount_-1-manual.html b/wai-aria/table_rowcount_-1-manual.html
index 8ca6717..5e7b5b8 100644
--- a/wai-aria/table_rowcount_-1-manual.html
+++ b/wai-aria/table_rowcount_-1-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>table rowcount -1</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Table"
                ],
diff --git a/wai-aria/table_rowcount_3-manual.html b/wai-aria/table_rowcount_3-manual.html
index bb685bd..5bfad69 100644
--- a/wai-aria/table_rowcount_3-manual.html
+++ b/wai-aria/table_rowcount_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>table rowcount 3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Table"
                ],
diff --git a/wai-aria/tablist_orientation_horizontal-manual.html b/wai-aria/tablist_orientation_horizontal-manual.html
index a01acf1..104668c 100644
--- a/wai-aria/tablist_orientation_horizontal-manual.html
+++ b/wai-aria/tablist_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tablist orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,7 +99,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Tab"
                ],
diff --git a/wai-aria/tablist_orientation_unspecified-manual.html b/wai-aria/tablist_orientation_unspecified-manual.html
index f4524c2..001c2ee 100644
--- a/wai-aria/tablist_orientation_unspecified-manual.html
+++ b/wai-aria/tablist_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tablist orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Tab"
                ],
diff --git a/wai-aria/tablist_orientation_vertical-manual.html b/wai-aria/tablist_orientation_vertical-manual.html
index 9cf69ca..f6fe16e 100644
--- a/wai-aria/tablist_orientation_vertical-manual.html
+++ b/wai-aria/tablist_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tablist orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Tab"
                ],
diff --git a/wai-aria/term_role-manual.html b/wai-aria/term_role-manual.html
index d583765..ac5030d 100644
--- a/wai-aria/term_role-manual.html
+++ b/wai-aria/term_role-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>term role</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -74,9 +73,9 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
-                  "ListItem"
+                  "Text"
                ]
             ]
          },
diff --git a/wai-aria/textbox_placeholder-manual.html b/wai-aria/textbox_placeholder-manual.html
index 6c234a1..fdf0635 100644
--- a/wai-aria/textbox_placeholder-manual.html
+++ b/wai-aria/textbox_placeholder-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>textbox placeholder</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,7 +99,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Edit"
                ],
diff --git a/wai-aria/toolbar_orientation_horizontal-manual.html b/wai-aria/toolbar_orientation_horizontal-manual.html
index e2327e7..1b8fda8 100644
--- a/wai-aria/toolbar_orientation_horizontal-manual.html
+++ b/wai-aria/toolbar_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>toolbar orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ToolBar"
                ],
diff --git a/wai-aria/toolbar_orientation_unspecified-manual.html b/wai-aria/toolbar_orientation_unspecified-manual.html
index a08e45a..4607511 100644
--- a/wai-aria/toolbar_orientation_unspecified-manual.html
+++ b/wai-aria/toolbar_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>toolbar orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ToolBar"
                ],
diff --git a/wai-aria/toolbar_orientation_vertical-manual.html b/wai-aria/toolbar_orientation_vertical-manual.html
index 92a39cd..792952d 100644
--- a/wai-aria/toolbar_orientation_vertical-manual.html
+++ b/wai-aria/toolbar_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>toolbar orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -88,7 +87,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "ToolBar"
                ],
diff --git a/wai-aria/tools/make_tests.pl b/wai-aria/tools/make_tests.pl
index 36de449..01cb5b0 100644
--- a/wai-aria/tools/make_tests.pl
+++ b/wai-aria/tools/make_tests.pl
@@ -230,6 +230,9 @@
         $theCode =~ s/ +$//;
         $theCode =~ s/\t/ /g;
         $theCode .= $_;
+        # In MediaWiki, to display & symbol escapes as literal text, one
+        # must use "&amp;&" for the "&" character. We need to undo that.
+        $theCode =~ s/&amp;(\S)/&$1/g;
       }
     }
   } elsif ($state == 3) {
@@ -490,7 +493,6 @@
   <head>
     <title>$title</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wai-aria/tree_orientation_horizontal-manual.html b/wai-aria/tree_orientation_horizontal-manual.html
index 7478821..15813f5 100644
--- a/wai-aria/tree_orientation_horizontal-manual.html
+++ b/wai-aria/tree_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tree orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Tree"
                ],
diff --git a/wai-aria/tree_orientation_unspecified-manual.html b/wai-aria/tree_orientation_unspecified-manual.html
index ada952d..dfc1bdc 100644
--- a/wai-aria/tree_orientation_unspecified-manual.html
+++ b/wai-aria/tree_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tree orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Tree"
                ],
diff --git a/wai-aria/tree_orientation_vertical-manual.html b/wai-aria/tree_orientation_vertical-manual.html
index 799413b..b9afa33 100644
--- a/wai-aria/tree_orientation_vertical-manual.html
+++ b/wai-aria/tree_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>tree orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -94,7 +93,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "Tree"
                ],
diff --git a/wai-aria/treegrid_colcount_8-manual.html b/wai-aria/treegrid_colcount_8-manual.html
index b44672c..a3e9ea1 100644
--- a/wai-aria/treegrid_colcount_8-manual.html
+++ b/wai-aria/treegrid_colcount_8-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treegrid colcount 8</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/treegrid_orientation_horizontal-manual.html b/wai-aria/treegrid_orientation_horizontal-manual.html
index 180aebd..756153a 100644
--- a/wai-aria/treegrid_orientation_horizontal-manual.html
+++ b/wai-aria/treegrid_orientation_horizontal-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treegrid orientation horizontal</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,7 +99,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/treegrid_orientation_unspecified-manual.html b/wai-aria/treegrid_orientation_unspecified-manual.html
index 4bc2a05..d536e9a 100644
--- a/wai-aria/treegrid_orientation_unspecified-manual.html
+++ b/wai-aria/treegrid_orientation_unspecified-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treegrid orientation unspecified</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -112,7 +111,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/treegrid_orientation_vertical-manual.html b/wai-aria/treegrid_orientation_vertical-manual.html
index 3bed156..62a19d0 100644
--- a/wai-aria/treegrid_orientation_vertical-manual.html
+++ b/wai-aria/treegrid_orientation_vertical-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treegrid orientation vertical</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -100,7 +99,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/treegrid_rowcount_3-manual.html b/wai-aria/treegrid_rowcount_3-manual.html
index 15b63f3..9d9b8ac 100644
--- a/wai-aria/treegrid_rowcount_3-manual.html
+++ b/wai-aria/treegrid_rowcount_3-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treegrid rowcount 3</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -82,7 +81,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "DataGrid"
                ],
diff --git a/wai-aria/treeitem_selected_false-manual.html b/wai-aria/treeitem_selected_false-manual.html
index f401a52..684fda6 100644
--- a/wai-aria/treeitem_selected_false-manual.html
+++ b/wai-aria/treeitem_selected_false-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treeitem selected false</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "TreeItem"
                ],
diff --git a/wai-aria/treeitem_selected_true-manual.html b/wai-aria/treeitem_selected_true-manual.html
index 80cf627..b951c54 100644
--- a/wai-aria/treeitem_selected_true-manual.html
+++ b/wai-aria/treeitem_selected_true-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treeitem selected true</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "TreeItem"
                ],
diff --git a/wai-aria/treeitem_selected_undefined-manual.html b/wai-aria/treeitem_selected_undefined-manual.html
index a7ca892..dddfd38 100644
--- a/wai-aria/treeitem_selected_undefined-manual.html
+++ b/wai-aria/treeitem_selected_undefined-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treeitem selected undefined</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
@@ -86,7 +85,7 @@
             "UIA" : [
                [
                   "property",
-                  "Control Type",
+                  "ControlType",
                   "is",
                   "TreeItem"
                ],
diff --git a/wai-aria/treeitem_selected_value_changes-manual.html b/wai-aria/treeitem_selected_value_changes-manual.html
index 06b48fc..69734c8 100644
--- a/wai-aria/treeitem_selected_value_changes-manual.html
+++ b/wai-aria/treeitem_selected_value_changes-manual.html
@@ -3,7 +3,6 @@
   <head>
     <title>treeitem selected value changes</title>
     <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
-    <link rel="stylesheet" href="/resources/testharness.css">
     <link rel="stylesheet" href="/wai-aria/scripts/manual.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
diff --git a/wake-lock/wakelock-object-is-independent.https.html b/wake-lock/wakelock-object-is-independent.https.html
deleted file mode 100644
index f0493ee..0000000
--- a/wake-lock/wakelock-object-is-independent.https.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WakeLock object is independent</title>
-<link rel="help" href="https://w3c.github.io/wake-lock/">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<body>
-<script id="iframe" type="text/plain">
-let iframeWakeLock, iframeRequest;
-window.onmessage = async message => {
-  switch(message.data) {
-  case "INIT":
-    iframeWakeLock = await navigator.getWakeLock("screen");
-    parent.postMessage(iframeWakeLock.active, "*");
-    break;
-  case "ACQUIRED":
-    iframeRequest = iframeWakeLock.createRequest();
-    parent.postMessage(iframeWakeLock.active, "*");
-    break;
-  case "RELEASED":
-    iframeRequest.cancel();
-    parent.postMessage(iframeWakeLock.active, "*");
-    break;
-  default:
-    parent.postMessage("unknown operation", "*");
-  }
-}
-</script>
-
-<script>
-function load_iframe() {
-  return new Promise(resolve => {
-    const iframe = document.createElement("iframe");
-    iframe.onload = () => { resolve(iframe); };
-    iframe.srcdoc = "<script>" +
-                    document.getElementById('iframe').textContent +
-                    "<\/script>";
-    document.body.appendChild(iframe);
-  });
-}
-
-function wait_for_message(iframe) {
-  return new Promise(resolve => {
-    self.addEventListener("message", function listener(e) {
-      if (e.source === iframe.contentWindow) {
-        resolve(e.data);
-        self.removeEventListener("message", listener);
-      }
-    });
-  });
-}
-
-promise_test(async t => {
-  const wakeLock1 = await navigator.getWakeLock("screen");
-  const wakeLock2 = await navigator.getWakeLock("screen");
-  const iframe = await load_iframe();
-
-  //when wakeLock1 is acquired, wakeLock2 and iframeWakeLock are still inactived
-  iframe.contentWindow.postMessage("INIT", "*");
-  let request1 = wakeLock1.createRequest();
-  let isActive1 = await wait_for_message(iframe);
-  assert_true(wakeLock1.active, "the active is true when wakeLock1 is acquired");
-  assert_false(wakeLock2.active, "the active is false before wakeLock2 is acquired");
-  assert_false(isActive1, "the active is false before iframeWakeLock is acquired");
-
-  //when wakeLock2 and iframeWakeLock are acquired, release wakeLock1
-  iframe.contentWindow.postMessage("ACQUIRED", "*");
-  let isActive2 = await wait_for_message(iframe);
-  request1.cancel();
-  let request2 = wakeLock2.createRequest();
-  assert_false(wakeLock1.active, "the active is false when wakeLock1 is released");
-  assert_true(wakeLock2.active, "the active is true when wakeLock2 is acquired");
-  assert_true(isActive2, "the active is true when iframeWakeLock is acquired");
-
-  //release all WakeLock objects
-  iframe.contentWindow.postMessage("RELEASED", "*");
-  let isActive3 = await wait_for_message(iframe);
-  request2.cancel();
-  assert_false(wakeLock1.active, "the active is false when wakeLock1 is released");
-  assert_false(wakeLock2.active, "the active is false when wakeLock2 is released");
-  assert_false(isActive3, "the active is false when iframeWakeLock is released");
-}, "Test that the WakeLock object is independent.");
-</script>
-</body>
diff --git a/wake-lock/wakelock-promise.https.html b/wake-lock/wakelock-promise.https.html
new file mode 100644
index 0000000..3d4c486
--- /dev/null
+++ b/wake-lock/wakelock-promise.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>navigator.getWakeLock() for the same Document always return same WakeLock promise</title>
+<link rel="help" href="https://w3c.github.io/wake-lock/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(async t => {
+  const wakeLock1 = await navigator.getWakeLock("screen");
+  const wakeLock2 = await navigator.getWakeLock("screen");
+  assert_equals(wakeLock1, wakeLock2);
+}, "navigator.getWakeLock() for the same Document always return same WakeLock promise");
+</script>
diff --git a/wake-lock/wakelock-state-is-global.https.html b/wake-lock/wakelock-state-is-global.https.html
new file mode 100644
index 0000000..98cebe0
--- /dev/null
+++ b/wake-lock/wakelock-state-is-global.https.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>wake lock state should be global</title>
+<link rel="help" href="https://w3c.github.io/wake-lock/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script id="iframe" type="text/plain">
+let iframeWakeLock, iframeRequest;
+window.onmessage = async message => {
+  switch(message.data) {
+  case "ACQUIRED":
+    iframeWakeLock = await navigator.getWakeLock("screen");
+    iframeRequest = iframeWakeLock.createRequest();
+    parent.postMessage(iframeWakeLock.active, "*");
+    break;
+  case "RELEASED":
+    iframeRequest.cancel();
+    parent.postMessage(iframeWakeLock.active, "*");
+    break;
+  default:
+    parent.postMessage("unknown operation", "*");
+  }
+}
+</script>
+
+<script>
+function load_iframe() {
+  return new Promise(resolve => {
+    const iframe = document.createElement("iframe");
+    iframe.onload = () => { resolve(iframe); };
+    iframe.srcdoc = "<script>" +
+                    document.getElementById('iframe').textContent +
+                    "<\/script>";
+    document.body.appendChild(iframe);
+  });
+}
+
+function wait_for_message(iframe) {
+  return new Promise(resolve => {
+    self.addEventListener("message", function listener(e) {
+      if (e.source === iframe.contentWindow) {
+        resolve(e.data);
+        self.removeEventListener("message", listener);
+      }
+    });
+  });
+}
+
+promise_test(async t => {
+  const wakeLock = await navigator.getWakeLock("screen");
+  const iframe = await load_iframe();
+  const eventWatcher = new EventWatcher(t, wakeLock, "activechange");
+
+  assert_false(wakeLock.active, "wakeLock is initially false");
+
+  //when iframe wake lock is acquired, parent wake lock should be actived
+  iframe.contentWindow.postMessage("ACQUIRED", "*");
+  const isActive1 = await wait_for_message(iframe);
+  await eventWatcher.wait_for("activechange");
+  assert_true(isActive1, "the iframe wake lock state is actived when iframe wake lock is acquired");
+  assert_true(wakeLock.active, "the wake lock state is actived when iframe wake lock is acquired");
+
+  //when iframe wake lock is released, parent wake lock should be inactived
+  iframe.contentWindow.postMessage("RELEASED", "*");
+  const isActive2 = await wait_for_message(iframe);
+  eventWatcher.wait_for("activechange");
+  assert_false(isActive2, "the iframe wake lock state is inactived when iframe wake lock is released");
+  assert_false(wakeLock.active, "the wake lock state is inactived when iframe wake lock is released");
+}, "Test that wake lock state should be global");
+</script>
+</body>
diff --git a/wake-lock/wakelockrequest-is-independent.https.html b/wake-lock/wakelockrequest-is-independent.https.html
new file mode 100644
index 0000000..a113b00
--- /dev/null
+++ b/wake-lock/wakelockrequest-is-independent.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WakeLockRequest object is independent</title>
+<link rel="help" href="https://w3c.github.io/wake-lock/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(async t => {
+  const wakeLock1 = await navigator.getWakeLock("screen");
+  const wakeLock2 = await navigator.getWakeLock("screen");
+  const request1 = wakeLock1.createRequest();
+  const request2 = wakeLock2.createRequest();
+  assert_not_equals(request1, request2);
+}, "Test that the WakeLockRequest object is independent.");
+</script>
diff --git a/wasm/OWNERS b/wasm/OWNERS
new file mode 100644
index 0000000..2e6844b
--- /dev/null
+++ b/wasm/OWNERS
@@ -0,0 +1,2 @@
+@titzer
+@Ms2ger
diff --git a/wasm/many-memories.window.js b/wasm/many-memories.window.js
deleted file mode 100644
index 0613a00..0000000
--- a/wasm/many-memories.window.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This test makes sure browsers behave reasonably when asked to allocate a
-// large number of WebAssembly.Memory objects at once.
-test(function() {
-  let memories = [];
-  try {
-    for (let i = 0; i < 600; i++) {
-      memories.push(new WebAssembly.Memory({initial: 1}));
-    }
-  } catch (e) {
-    if (e instanceof RangeError) {
-      return;
-    }
-    throw e;
-  }
-}, "WebAssembly#CreateManyMemories");
diff --git a/wasm/wasm_local_iframe_test.html b/wasm/wasm_local_iframe_test.html
index 9ccebdc..0f4fbd0 100644
--- a/wasm/wasm_local_iframe_test.html
+++ b/wasm/wasm_local_iframe_test.html
@@ -2,16 +2,18 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/load_wasm.js"></script>
-<iframe src="resources/frame.html" id="iframe"></iframe>
 <script>
-  promise_test(async function() {
-    var mod = await createWasmModule();
-    assert_true(mod instanceof WebAssembly.Module);
-    var ans = await new Promise((resolve, reject) => {
-      var iframe = document.getElementById("iframe").contentWindow;
-      iframe.postMessage(mod, '*');
-      window.addEventListener("message", (reply) => resolve(reply.data), false);
-    });
-    assert_equals(ans, 43);
-  }, "send wasm module to iframe");
+  function runTests(iframe) {
+      iframe = iframe.contentWindow;
+      promise_test(async function() {
+        var mod = await createWasmModule();
+        assert_true(mod instanceof WebAssembly.Module);
+        var ans = await new Promise((resolve, reject) => {
+          iframe.postMessage(mod, '*');
+          window.addEventListener("message", (reply) => resolve(reply.data), false);
+        });
+        assert_equals(ans, 43);
+      }, "send wasm module to iframe");
+  }
 </script>
+<iframe src="resources/frame.html" onload="runTests(this)"></iframe>
diff --git a/web-animations/README.md b/web-animations/README.md
index 1bc3512..f6efbf9 100644
--- a/web-animations/README.md
+++ b/web-animations/README.md
@@ -1,7 +1,7 @@
 Web Animations Test Suite
 =========================
 
-Specification: https://w3c.github.io/web-animations/
+Specification: https://drafts.csswg.org/web-animations/
 
 
 Guidelines for writing tests
@@ -98,10 +98,19 @@
     Remember, even if we do need to make all tests take, say 200s each, text
     editors are very good at search and replace.
 
-*   Use the `assert_times_equal` assertion for comparing calculated times.
-    It tests times are equal using the precision recommended in the spec whilst
-    allowing implementations to override the function to meet their own
-    precision requirements.
+*   Use the `assert_times_equal` assertion for comparing times returned from
+    the API. This asserts that the time values are equal using a tolerance
+    based on the precision recommended in the spec. This tolerance is applied
+    to *both* of the values being compared. That is, it effectively allows
+    double the epsilon that is used when comparing with an absolute value.
+
+    For comparing a time value returned from the API to an absolute value, use
+    `assert_time_equals_literal`. This tests that the actual value is equal to
+    the expected value within the precision recommended in the spec.
+
+    Both `assert_times_equal` and `assert_time_equals_literal` are defined in a
+    way that implementations can override them to meet their own precision
+    requirements.
 
 *   There are quite a few bad tests in the repository. We're learning as
     we go. Don't just copy them blindly&mdash;please fix them!
diff --git a/web-animations/animation-model/animation-types/accumulation-per-property.html b/web-animations/animation-model/animation-types/accumulation-per-property.html
index 3a7201e..870e489 100644
--- a/web-animations/animation-model/animation-types/accumulation-per-property.html
+++ b/web-animations/animation-model/animation-types/accumulation-per-property.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Accumulation for each property</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#animation-types">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/animation-model/animation-types/addition-per-property.html b/web-animations/animation-model/animation-types/addition-per-property.html
index c66efd3..6125631 100644
--- a/web-animations/animation-model/animation-types/addition-per-property.html
+++ b/web-animations/animation-model/animation-types/addition-per-property.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Addition for each property</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#animation-types">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/animation-model/animation-types/discrete.html b/web-animations/animation-model/animation-types/discrete.html
index add3429..76f42bc 100644
--- a/web-animations/animation-model/animation-types/discrete.html
+++ b/web-animations/animation-model/animation-types/discrete.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Discrete animation type</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#discrete-animation-type">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#discrete-animation-type">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/animation-model/animation-types/interpolation-per-property.html b/web-animations/animation-model/animation-types/interpolation-per-property.html
index 94ce9dd..2c335da 100644
--- a/web-animations/animation-model/animation-types/interpolation-per-property.html
+++ b/web-animations/animation-model/animation-types/interpolation-per-property.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Interpolation for each property</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#animation-types">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/animation-model/animation-types/property-list.js b/web-animations/animation-model/animation-types/property-list.js
index 46829c3..ec0c33f 100644
--- a/web-animations/animation-model/animation-types/property-list.js
+++ b/web-animations/animation-model/animation-types/property-list.js
@@ -425,7 +425,7 @@
       { type: 'discrete', options: [ [ '"a"', '"b"' ] ] }
     ],
     setup: t => {
-      return createPseudo(t, 'before');
+      return getPseudoElement(t, 'before');
     }
   },
   'counter-increment': {
@@ -529,8 +529,8 @@
     ]
   },
   'font-stretch': {
-    // https://drafts.csswg.org/css-fonts-3/#propdef-font-stretch
-    types: [ 'fontStretch' ]
+    // https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch
+    types: [ 'percentage' ]
   },
   'font-style': {
     // https://drafts.csswg.org/css-fonts/#propdef-font-style
@@ -751,19 +751,19 @@
   'justify-content': {
     // https://drafts.csswg.org/css-align/#propdef-justify-content
     types: [
-      { type: 'discrete', options: [ [ 'baseline', 'last baseline' ] ] }
+      { type: 'discrete', options: [ [ 'start', 'end' ] ] }
     ]
   },
   'justify-items': {
     // https://drafts.csswg.org/css-align/#propdef-justify-items
     types: [
-      { type: 'discrete', options: [ [ 'baseline', 'last baseline' ] ] }
+      { type: 'discrete', options: [ [ 'start', 'end' ] ] }
     ]
   },
   'justify-self': {
     // https://drafts.csswg.org/css-align/#propdef-justify-self
     types: [
-      { type: 'discrete', options: [ [ 'baseline', 'last baseline' ] ] }
+      { type: 'discrete', options: [ [ 'start', 'end' ] ] }
     ]
   },
   'left': {
@@ -1054,12 +1054,6 @@
     types: [
     ]
   },
-  'overflow-clip-box': {
-    // https://developer.mozilla.org/en/docs/Web/CSS/overflow-clip-box
-    types: [
-      { type: 'discrete', options: [ [ 'padding-box', 'content-box' ] ] }
-    ]
-  },
   'overflow-wrap': {
     // https://drafts.csswg.org/css-text-3/#propdef-overflow-wrap
     types: [
@@ -1411,6 +1405,25 @@
       { type: 'discrete', options: [ [ 'flat', 'preserve-3d' ] ] }
     ]
   },
+  'rotate': {
+    // https://drafts.csswg.org/css-transforms-2/#individual-transforms
+    types: [ 'rotateList' ]
+  },
+  'translate': {
+    // https://drafts.csswg.org/css-transforms-2/#individual-transforms
+    types: [ 'translateList' ],
+    setup: t => {
+      // We need to set a width/height for resolving percentages against.
+      const element = createElement(t);
+      element.style.width = '100px';
+      element.style.height = '100px';
+      return element;
+    }
+  },
+  'scale': {
+    // https://drafts.csswg.org/css-transforms-2/#individual-transforms
+    types: [ 'scaleList' ]
+  },
   'unicode-bidi': {
     // https://drafts.csswg.org/css-writing-modes-3/#propdef-unicode-bidi
     types: [
@@ -1525,6 +1538,18 @@
   }
 }
 
+function testAnimationSampleRotate3d(animation, idlName, testSamples) {
+  const target = animation.effect.target;
+  for (const testSample of testSamples) {
+    animation.currentTime = testSample.time;
+    const actual = getComputedStyle(target)[idlName];
+    const expected = testSample.expected;
+    assert_rotate3d_equals(actual, expected,
+                         `The value should be ${expected} at`
+                         + ` ${testSample.time}ms but got ${actual}`);
+  }
+}
+
 function createTestElement(t, setup) {
   return setup ? setup(t) : createElement(t);
 }
@@ -1554,7 +1579,7 @@
 }
 
 function propertyToIDL(property) {
-  // https://w3c.github.io/web-animations/#animation-property-name-to-idl-attribute-name
+  // https://drafts.csswg.org/web-animations/#animation-property-name-to-idl-attribute-name
   if (property === 'float') {
     return 'cssFloat';
   }
diff --git a/web-animations/animation-model/animation-types/property-types.js b/web-animations/animation-model/animation-types/property-types.js
index eb9c8c1..60c7d2f 100644
--- a/web-animations/animation-model/animation-types/property-types.js
+++ b/web-animations/animation-model/animation-types/property-types.js
@@ -1583,6 +1583,382 @@
   },
 };
 
+const rotateListType = {
+  testInterpolation: (property, setup) => {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate(
+        {
+          [idlName]: ['45deg', '135deg'],
+        },
+        1000
+      );
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '90deg' }]);
+    }, `${property} without rotation axes`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation =
+        target.animate({ [idlName]: [ '0 1 0 0deg',
+                                      '0 1 0 90deg'] },
+                       1000);
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 500, expected: '0 1 0 45deg' }]);
+    }, `${property} with rotation axes`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+
+      const animation =
+        target.animate({ [idlName]: [ '0 1 0 0deg',
+                                      '0 1 0 720deg'] },
+                       1000);
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 250, expected: '0 1 0 180deg' }]);
+    }, `${property} with rotation axes and range over 360 degrees`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation =
+        target.animate({ [idlName]: [ '0 1 0 0deg',
+                                      '0 1 1 90deg'] },
+                       1000);
+
+      testAnimationSampleRotate3d(animation, idlName,
+        [{ time: 500, expected: '0 0.707107 0.707107 45deg' }]);
+    }, `${property} with different rotation axes`);
+  },
+  testAddition: function(property, setup) {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '45deg';
+      const animation = target.animate({ [idlName]: ['-90deg', '90deg'] },
+                                       { duration: 1000, fill: 'both',
+                                         composite: 'add' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '-45deg' },
+         { time: 1000, expected: '135deg' }]);
+    }, `${property} without rotation axes`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      // Rotation specified in transform property should not affect the computed
+      // value of |property|.
+      target.style.transform = 'rotate(20deg)';
+      target.style[idlName] = '0 1 0 -45deg';
+      const animation =
+        target.animate({ [idlName]: ['0 1 0 90deg',
+                                     '0 1 0 180deg'] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '0 1 0 45deg' },
+         { time: 1000, expected: '0 1 0 135deg' }]);
+    }, `${property} with underlying transform`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation =
+        target.animate({ [idlName]: [ '0 1 0 0deg',
+                                      '1 1 1 270deg'] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      testAnimationSampleRotate3d(animation, idlName,
+        [{ time: 500, expected: '0.57735 0.57735 0.57735 135deg' }]);
+    }, `${property} with different rotation axes`);
+  },
+  testAccumulation: function(property, setup) {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '45deg';
+      const animation = target.animate({ [idlName]: ['-90deg', '90deg'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'accumulate' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '-45deg' },
+         { time: 1000, expected: '135deg' }]);
+    }, `${property} without rotation axes`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style.transform = 'translateX(100px)';
+      target.style[idlName] = '1 0 0 -45deg';
+      const animation =
+        target.animate({ [idlName]: ['1 0 0 90deg',
+                                     '1 0 0 180deg'] },
+                       { duration: 1000, fill: 'both', composite: 'accumulate' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '1 0 0 45deg' },
+         { time: 1000, expected: '1 0 0 135deg' }]);
+    }, `${property} with underlying transform`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation =
+        target.animate({ [idlName]: [ '0 1 0 0deg',
+                                      '1 0 1 180deg'] },
+                       { duration: 1000, fill: 'both', composite: 'accumulate' });
+
+      testAnimationSampleRotate3d(animation, idlName,
+        [{ time: 500, expected: '0.707107 0 0.707107 90deg' }]);
+    }, `${property} with different rotation axes`);
+  },
+};
+
+const translateListType = {
+  testInterpolation: (property, setup) => {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate(
+        {
+          [idlName]: ['200px', '400px'],
+        },
+        1000
+      );
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '300px' }]);
+    }, `${property} with two unspecified values`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate(
+        {
+          [idlName]: ['200px -200px', '400px 400px'],
+        },
+        1000
+      );
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '300px 100px' }]);
+    }, `${property} with one unspecified value`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate(
+        {
+          [idlName]: ['200px -200px 600px', '400px 400px -200px'],
+        },
+        1000
+      );
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '300px 100px 200px' }]);
+    }, `${property} with all three values specified`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate(
+        {
+          [idlName]: ['0% -101px 600px', '400px 50% -200px'],
+        },
+        1000
+      );
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '200px -25.5px 200px' }]);
+    }, `${property} with combination of percentages and lengths`);
+  },
+  testAddition: function(property, setup) {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '100px';
+      const animation = target.animate({ [idlName]: ['-200px', '500px'] },
+                                       { duration: 1000, fill: 'both',
+                                         composite: 'add' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0,    expected: '-100px' },
+          { time: 1000, expected: '600px' }]);
+
+    }, `${property}`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.transform = 'translateY(100px)';
+      target.style[idlName] = '100px';
+      const animation = target.animate({ [idlName]: ['-200px', '500px'] },
+                                       { duration: 1000, fill: 'both',
+                                         composite: 'add' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0,    expected: '-100px' },
+          { time: 1000, expected: '600px' }]);
+
+    }, `${property} with underlying transform`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '50%';
+      const animation = target.animate({ [idlName]: ['-200px', '500px'] },
+                                       { duration: 1000, fill: 'both',
+                                         composite: 'add' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0,    expected: '-150px' },
+          { time: 1000, expected: '550px' }]);
+
+    }, `${property} with underlying percentage value`);
+  },
+  testAccumulation: function(property, setup) {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '100px';
+      const animation = target.animate({ [idlName]: ['-200px', '500px'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'accumulate' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0,    expected: '-100px' },
+          { time: 1000, expected: '600px' }]);
+    }, `${property}`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.transform = 'translateY(100px)';
+      target.style[idlName] = '100px';
+      const animation = target.animate({ [idlName]: ['-200px', '500px'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'accumulate' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0,    expected: '-100px' },
+          { time: 1000, expected: '600px' }]);
+    }, `${property} with transform`);
+  },
+};
+
+const scaleListType = {
+  testInterpolation: (property, setup) => {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate({ [idlName]: ['3', '5'] },
+                                       1000);
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '4' }]);
+    }, `${property} with two unspecified values`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate({ [idlName]: ['3 3', '5 5'] },
+                                       1000);
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '4 4' }]);
+    }, `${property} with one unspecified value`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      const animation = target.animate({ [idlName]: ['3 3 3', '5 5 5'] },
+                                       1000);
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 500,  expected: '4 4 4' }]);
+    }, `${property}`);
+  },
+  testAddition: function(property, setup) {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '2';
+      const animation = target.animate({ [idlName]: ['-3', '5'] },
+                                       { duration: 1000, fill: 'both',
+                                         composite: 'add' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '-6' },
+         { time: 1000, expected: '10' }]);
+    }, `${property} with two unspecified values`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '2 2';
+      const animation = target.animate({ [idlName]: ['-3 -3', '5 5'] },
+                                       { duration: 1000, fill: 'both',
+                                         composite: 'add' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '-6 -6' },
+         { time: 1000, expected: '10 10' }]);
+    }, `${property} with one unspecified value`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '2 2 2';
+      const animation = target.animate({ [idlName]: ['-1 -2 -3', '4 5 6'] },
+                                       { duration: 1000, fill: 'both',
+                                         composite: 'add' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '-2 -4 -6' },
+         { time: 1000, expected: '8 10 12' }]);
+    }, `${property}`);
+  },
+  testAccumulation: function(property, setup) {
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '2';
+      const animation = target.animate({ [idlName]: ['-3', '5'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'accumulate' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '-2' },
+         { time: 1000, expected: '6' }]);
+    }, `${property} with two unspecified values`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '2 2';
+      const animation = target.animate({ [idlName]: ['-3 -3', '5 5'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'accumulate' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '-2 -2' },
+         { time: 1000, expected: '6 6' }]);
+    }, `${property} with one unspecified value`);
+
+    test(t => {
+      const idlName = propertyToIDL(property);
+      const target = createTestElement(t, setup);
+      target.style[idlName] = '2 2 2';
+      const animation = target.animate({ [idlName]: ['-1 -2 -3', '4 5 6'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'accumulate' });
+
+      testAnimationSamples(animation, idlName,
+        [{ time: 0,    expected: '0 -1 -2' },
+         { time: 1000, expected: '5 6 7' }]);
+    }, `${property}`);
+  },
+};
+
 const filterListType = {
   testInterpolation: (property, setup) => {
     test(t => {
@@ -2190,54 +2566,6 @@
   },
 }
 
-const fontStretchType = {
-  testInterpolation: (property, setup) => {
-    test(t => {
-      const idlName = propertyToIDL(property);
-      const target = createTestElement(t, setup);
-      const animation =
-        target.animate({ [idlName]: ['ultra-condensed', 'extra-condensed'] },
-                       { duration: 1000, fill: 'both' });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 499,  expected: 'ultra-condensed' },
-                            { time: 500,  expected: 'extra-condensed' }]);
-    }, `${property} supports animating as a font-stretch (adjacent values)`);
-
-    test(t => {
-      const idlName = propertyToIDL(property);
-      const target = createTestElement(t, setup);
-      const animation =
-        target.animate({ [idlName]: ['ultra-condensed', 'condensed'] },
-                       { duration: 1000, fill: 'both' });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 500,  expected: 'extra-condensed' }]);
-    }, `${property} supports animating as a font-stretch (between value)`);
-  },
-
-  testAdditionOrAccumulation: (property, setup, composite) => {
-    test(t => {
-      const idlName = propertyToIDL(property);
-      const target = createTestElement(t, setup);
-      target.style[idlName] = 'condensed';
-      const animation =
-        target.animate({ [idlName]: ['expanded', 'ultra-expanded'] },
-                       { duration: 1000, composite });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0, expected: 'normal' },
-                            { time: 250, expected: 'semi-expanded' }]);
-    },
-    `${property} uses font-stretch behavior for composite type ${composite}`);
-  },
-
-  testAddition: function(property, setup) {
-    this.testAdditionOrAccumulation(property, setup, 'add');
-  },
-
-  testAccumulation: function(property, setup) {
-    this.testAdditionOrAccumulation(property, setup, 'accumulate');
-  },
-}
-
 const fontVariationSettingsType = {
   testInterpolation: (property, setup) => {
     test(t => {
@@ -2316,12 +2644,14 @@
   positiveNumber: positiveNumberType,
   opacity: opacityType,
   transformList: transformListType,
+  rotateList: rotateListType,
+  translateList: translateListType,
+  scaleList: scaleListType,
   visibility: visibilityType,
   boxShadowList: boxShadowListType,
   textShadowList: textShadowListType,
   rect: rectType,
   position: positionType,
   dasharray: dasharrayType,
-  fontStretch: fontStretchType,
   fontVariationSettings: fontVariationSettingsType,
 };
diff --git a/web-animations/animation-model/combining-effects/applying-the-composited-result.html b/web-animations/animation-model/combining-effects/applying-the-composited-result.html
new file mode 100644
index 0000000..336115e
--- /dev/null
+++ b/web-animations/animation-model/combining-effects/applying-the-composited-result.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Applying the composited result</title>
+<link rel="help"
+  href="https://drafts.csswg.org/web-animations-1/#applying-the-composited-result">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../../testcommon.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const div = createDiv(t);
+  div.style.marginLeft = '10px';
+  const animation = div.animate(
+    { marginLeft: ['100px', '200px'] },
+    100 * MS_PER_SEC
+  );
+  await animation.ready;
+
+  animation.finish();
+
+  const marginLeft = parseFloat(getComputedStyle(div).marginLeft);
+  assert_equals(marginLeft, 10, 'The computed style should be reset');
+}, 'Finishing an animation that does not fill forwards causes its animation'
+   + ' style to be cleared');
+
+</script>
diff --git a/web-animations/animation-model/combining-effects/effect-composition.html b/web-animations/animation-model/combining-effects/effect-composition.html
index d70d9d2..78f46c9 100644
--- a/web-animations/animation-model/combining-effects/effect-composition.html
+++ b/web-animations/animation-model/combining-effects/effect-composition.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Effect composition</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#effect-composition">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#effect-composition">
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/animation-model/keyframe-effects/effect-value-context.html b/web-animations/animation-model/keyframe-effects/effect-value-context.html
index 4115ed6..3730a02 100644
--- a/web-animations/animation-model/keyframe-effects/effect-value-context.html
+++ b/web-animations/animation-model/keyframe-effects/effect-value-context.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>The effect value of a keyframe effect: Property values that depend on
   their context (target element)</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#calculating-computed-keyframes">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#calculating-computed-keyframes">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html b/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
index 29da85e..142d49d 100644
--- a/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
+++ b/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html
@@ -2,8 +2,8 @@
 <meta charset=utf-8>
 <title>The effect value of a keyframe effect: Applying the iteration composite
   operation</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
-<link rel="help" href="https://w3c.github.io/web-animations/#effect-accumulation-section">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#effect-accumulation-section">
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src="../../testcommon.js"></script>
@@ -20,13 +20,13 @@
                   iterations: 10,
                   iterationComposite: 'accumulate' });
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).alignContent, 'flex-end',
     'Animated align-content style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).alignContent, 'flex-start',
     'Animated align-content style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).alignContent, 'flex-end',
     'Animated align-content style at 50s of the third iteration');
 }, 'iteration composition of discrete type animation (align-content)');
@@ -41,13 +41,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '5px',
     'Animated margin-left style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).marginLeft, '20px',
     'Animated margin-left style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '25px',
     'Animated margin-left style at 50s of the third iteration');
 }, 'iteration composition of <length> type animation');
@@ -66,13 +66,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '25px',
     'Animated width style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).width, '100px',
     'Animated width style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '125px',
     'Animated width style at 50s of the third iteration');
 }, 'iteration composition of <percentage> type animation');
@@ -87,13 +87,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).color, 'rgb(60, 60, 60)',
     'Animated color style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).color, 'rgb(240, 240, 240)',
     'Animated color style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).color, 'rgb(255, 255, 255)',
     'Animated color style at 50s of the third iteration');
 }, 'iteration composition of <color> type animation');
@@ -108,13 +108,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).color, 'rgb(30, 90, 30)',
     'Animated color style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).color, 'rgb(120, 240, 120)',
     'Animated color style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   // The green color is (240 + 180) / 2 = 210
   assert_equals(getComputedStyle(div).color, 'rgb(150, 210, 150)',
     'Animated color style at 50s of the third iteration');
@@ -131,13 +131,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).flexGrow, '5',
     'Animated flex-grow style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).flexGrow, '20',
     'Animated flex-grow style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).flexGrow, '25',
     'Animated flex-grow style at 50s of the third iteration');
 }, 'iteration composition of <number> type animation');
@@ -154,13 +154,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).clip, 'rect(5px, 5px, 5px, 5px)',
     'Animated clip style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).clip, 'rect(20px, 20px, 20px, 20px)',
     'Animated clip style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).clip, 'rect(25px, 25px, 25px, 25px)',
     'Animated clip style at 50s of the third iteration');
 }, 'iteration composition of <shape> type animation');
@@ -175,13 +175,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '5px',
     'Animated calc width style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).width, '20px',
     'Animated calc width style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '25px',
     'Animated calc width style at 50s of the third iteration');
 }, 'iteration composition of <calc()> value animation');
@@ -200,15 +200,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width, '10px',
     // 100px * 5% + 5px
     'Animated calc width style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).width,
     '40px', // 100px * (10% + 10%) + (10px + 10px)
     'Animated calc width style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).width,
     '50px', // (40px + 60px) / 2
     'Animated calc width style at 50s of the third iteration');
@@ -225,13 +225,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).opacity, '0.2',
     'Animated opacity style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).opacity, '0.8',
     'Animated opacity style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).opacity, '1', // (0.8 + 1.2) * 0.5
     'Animated opacity style at 50s of the third iteration');
 }, 'iteration composition of opacity animation');
@@ -247,15 +247,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).boxShadow,
     'rgb(60, 60, 60) 5px 5px 5px 0px',
     'Animated box-shadow style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).boxShadow,
     'rgb(240, 240, 240) 20px 20px 20px 0px',
     'Animated box-shadow style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).boxShadow,
     'rgb(255, 255, 255) 25px 25px 25px 0px',
     'Animated box-shadow style at 50s of the third iteration');
@@ -271,13 +271,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter, 'blur(5px)',
     'Animated filter blur style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter, 'blur(20px)',
     'Animated filter blur style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter, 'blur(25px)',
     'Animated filter blur style at 50s of the third iteration');
 }, 'iteration composition of filter blur animation');
@@ -293,15 +293,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(1.4)',
     'Animated filter brightness style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(2.6)', // brightness(1) + brightness(0.8) + brightness(0.8)
     'Animated filter brightness style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(3)', // (brightness(2.6) + brightness(3.4)) * 0.5
     'Animated filter brightness style at 50s of the third iteration');
@@ -318,15 +318,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(0.5)',
     'Animated filter brightness style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(0)', // brightness(1) is an identity element, not accumulated.
     'Animated filter brightness style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(0.5)', // brightness(1) is an identity element, not accumulated.
     'Animated filter brightness style at 50s of the third iteration');
@@ -343,15 +343,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'drop-shadow(rgb(60, 60, 60) 5px 5px 5px)',
     'Animated filter drop-shadow style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'drop-shadow(rgb(240, 240, 240) 20px 20px 20px)',
     'Animated filter drop-shadow style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'drop-shadow(rgb(255, 255, 255) 25px 25px 25px)',
     'Animated filter drop-shadow style at 50s of the third iteration');
@@ -368,15 +368,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(1.5) contrast(1.5)',
     'Animated filter list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(3) contrast(3)',
     'Animated filter list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'brightness(3.5) contrast(3.5)',
     'Animated filter list at 50s of the third iteration');
@@ -393,18 +393,18 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'contrast(2) brightness(2)', // discrete
     'Animated filter list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     // We can't accumulate 'contrast(2) brightness(2)' onto
     // the first list 'brightness(1) contrast(1)' because of
     // mismatch of the order.
     'brightness(1) contrast(1)',
     'Animated filter list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     // We *can* accumulate 'contrast(2) brightness(2)' onto
     // the same list 'contrast(2) brightness(2)' here.
@@ -424,15 +424,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'sepia(0.5) contrast(1.5)',
     'Animated filter list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).filter,
     'sepia(2) contrast(3)',
     'Animated filter list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).filter,
     'sepia(2.5) contrast(3.5)',
     'Animated filter list at 50s of the third iteration');
@@ -448,15 +448,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 0)', // rotate(90deg)
     'Animated transform(rotate) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 0, 0)', // rotate(360deg)
     'Animated transform(rotate) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 0)', // rotate(450deg)
     'Animated transform(rotate) style at 50s of the third iteration');
@@ -472,16 +472,16 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5)
     'Animated transform(scale) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 0, 0, 0, 0, 0)', // scale(0); scale(1) is an identity element,
                                 // not accumulated.
     'Animated transform(scale) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5); scale(1) an identity
                                     // element, not accumulated.
@@ -498,15 +498,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1.5, 0, 0, 1.5, 0, 0)', // scale(1.5)
     'Animated transform(scale) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(3, 0, 0, 3, 0, 0)', // scale(1 + (2 -1) + (2 -1))
     'Animated transform(scale) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(3.5, 0, 0, 3.5, 0, 0)', // (scale(3) + scale(4)) * 0.5
     'Animated transform(scale) style at 50s of the third iteration');
@@ -522,15 +522,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 0, 0)', // scale(1)
     'Animated transform(scale) style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(2, 0, 0, 2, 0, 0)', // (scale(0) + scale(2-1)*2)
     'Animated transform(scale) style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(3, 0, 0, 3, 0, 0)', // (scale(2) + scale(4)) * 0.5
     'Animated transform(scale) style at 50s of the third iteration');
@@ -547,15 +547,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 5)', // rotate(90deg) translateX(5px)
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 20, 0)', // rotate(360deg) translateX(20px)
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(0, 1, -1, 0, 0, 25)', // rotate(450deg) translateX(25px)
     'Animated transform list at 50s of the third iteration');
@@ -572,16 +572,16 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(2.5, 0, 0, 2.5, 15, 0)',
     'Animated transform of matrix function at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // scale(2) + (scale(3-1)*2) + translateX(30px)*2
     'matrix(6, 0, 0, 6, 60, 0)',
     'Animated transform of matrix function at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // from: matrix(6, 0, 0, 6, 60, 0)
     // to:   matrix(7, 0, 0, 7, 90, 0)
@@ -601,20 +601,20 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(2, 0, 0, 2,  0, 0) = translateX(0px) scale(2)
     //                 and matrix(3, 0, 0, 3, 30, 0) = scale(3) translateX(10px)
     'matrix(2.5, 0, 0, 2.5, 15, 0)',
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // 'from' and 'to' value are mismatched, so accumulate
     // matrix(2, 0, 0, 2, 0, 0) onto matrix(3, 0, 0, 3, 30, 0) * 2
     //  = scale(2) + (scale(3-1)*2) + translateX(30px)*2
     'matrix(6, 0, 0, 6, 60, 0)',
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(6, 0, 0, 6, 60, 0)
     //                 and matrix(7, 0, 0, 7, 210, 0) = scale(7) translate(30px)
@@ -636,20 +636,20 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(1, 0, 0, 1,  0, 0) = translateX(0px)
     //                 and matrix(2, 0, 0, 2, 20, 0) = scale(2) translateX(10px)
     'matrix(1.5, 0, 0, 1.5, 10, 0)',
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // 'from' and 'to' value are mismatched, so accumulate
     // matrix(1, 0, 0, 1, 0, 0) onto matrix(2, 0, 0, 2, 20, 0) * 2
     //  = scale(1) + (scale(2-1)*2) + translateX(20px)*2
     'matrix(3, 0, 0, 3, 40, 0)',
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // Interpolate between matrix(3, 0, 0, 3, 40, 0)
     //                 and matrix(4, 0, 0, 4, 120, 0) =
@@ -670,17 +670,17 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateX(none) -> translateX(10px) @ 50%
     'matrix(1, 0, 0, 1, 5, 0)',
     'Animated transform list at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateX(10px * 2 + none) -> translateX(10px * 2 + 10px) @ 0%
     'matrix(1, 0, 0, 1, 20, 0)',
     'Animated transform list at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateX(10px * 2 + none) -> translateX(10px * 2 + 10px) @ 50%
     'matrix(1, 0, 0, 1, 25, 0)',
@@ -704,16 +704,16 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 40, 1)',
     'Animated transform of matrix3d function at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // translateZ(30px) + (translateZ(50px)*2)
     'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1)',
     'Animated transform of matrix3d function at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     // from: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1)
     // to:   matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 150, 1)
@@ -736,11 +736,11 @@
   assert_matrix_equals(getComputedStyle(div).transform,
     'matrix(1, 0, 0, 1, 0, 0)', // Actually not rotated at all.
     'Animated transform of rotate3d function at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     rotate3dToMatrix3d(1, 1, 0, Math.PI), // 180deg
     'Animated transform of rotate3d function at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_matrix_equals(getComputedStyle(div).transform,
     rotate3dToMatrix3d(1, 1, 0, 225 * Math.PI / 180), //((270 + 180) * 0.5)deg
     'Animated transform of rotate3d function at 50s of the third iteration');
@@ -756,13 +756,13 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '15px',
     'Animated margin-left style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).marginLeft, '50px', // 10px + 20px + 20px
     'Animated margin-left style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '55px', // (50px + 60px) * 0.5
     'Animated margin-left style at 50s of the third iteration');
 }, 'iteration composition starts with non-zero value animation');
@@ -777,15 +777,15 @@
                   iterationComposite: 'accumulate' });
   anim.pause();
 
-  anim.currentTime = anim.effect.timing.duration / 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft,
     '0px',
     'Animated margin-left style at 50s of the first iteration');
-  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.currentTime = anim.effect.getComputedTiming().duration * 2;
   assert_equals(getComputedStyle(div).marginLeft,
     '-10px', // 10px + -10px + -10px
     'Animated margin-left style at 0s of the third iteration');
-  anim.currentTime += anim.effect.timing.duration / 2;
+  anim.currentTime += anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft,
     '-20px', // (-10px + -30px) * 0.5
     'Animated margin-left style at 50s of the third iteration');
@@ -801,17 +801,22 @@
   anim.pause();
 
   anim.currentTime =
-    anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2;
+    anim.effect.getComputedTiming().duration * 2 +
+    anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '25px',
     'Animated style at 50s of the third iteration');
 
   // double its duration.
-  anim.effect.timing.duration = anim.effect.timing.duration * 2;
+  anim.effect.updateTiming({
+    duration: anim.effect.getComputedTiming().duration * 2
+  });
   assert_equals(getComputedStyle(div).marginLeft, '12.5px',
     'Animated style at 25s of the first iteration');
 
   // half of original.
-  anim.effect.timing.duration = anim.effect.timing.duration / 4;
+  anim.effect.updateTiming({
+    duration: anim.effect.getComputedTiming().duration / 4
+  });
   assert_equals(getComputedStyle(div).marginLeft, '50px',
       'Animated style at 50s of the fourth iteration');
 }, 'duration changes with an iteration composition operation of accumulate');
diff --git a/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html b/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html
index 011b76f..2a41f04 100644
--- a/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html
+++ b/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>The effect value of a keyframe effect: Overlapping keyframes</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html b/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html
index 1f7065c..6ad9cd2 100644
--- a/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html
+++ b/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>The effect value of a keyframe effect: Calculating the transformed
   distance between keyframes</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/Animatable/animate-no-browsing-context.html b/web-animations/interfaces/Animatable/animate-no-browsing-context.html
index e4fd70a..61a7502 100644
--- a/web-animations/interfaces/Animatable/animate-no-browsing-context.html
+++ b/web-animations/interfaces/Animatable/animate-no-browsing-context.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>Animatable.animate in combination with elements in documents
        without a browsing context</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animatable-animate">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -76,16 +76,15 @@
     const div = xhrdoc.getElementById('test');
     anim = div.animate(null);
     anim.timeline = document.timeline;
-    assert_equals(anim.playState, 'pending',
-                  'The animation should be initially pending');
+    assert_true(anim.pending, 'The animation should be initially pending');
     return waitForAnimationFrames(2);
   }).then(() => {
     // Because the element is in a document without a browsing context, it will
     // not be rendered and hence the user agent will never deem it ready to
     // animate.
-    assert_equals(anim.playState, 'pending',
-                  'The animation should still be pending after replacing'
-                  + ' the document timeline');
+    assert_true(anim.pending,
+                'The animation should still be pending after replacing'
+                + ' the document timeline');
   });
 }, 'Replacing the timeline of an animation targetting an element in a'
    + ' document without a browsing context leaves it in the pending state');
diff --git a/web-animations/interfaces/Animatable/animate.html b/web-animations/interfaces/Animatable/animate.html
index 5464711..cbec1b0 100644
--- a/web-animations/interfaces/Animatable/animate.html
+++ b/web-animations/interfaces/Animatable/animate.html
@@ -1,13 +1,15 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animatable.animate</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animatable-animate">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
 <script src="../../resources/easing-tests.js"></script>
 <script src="../../resources/keyframe-utils.js"></script>
 <script src="../../resources/keyframe-tests.js"></script>
+<script src="../../resources/timing-utils.js"></script>
+<script src="../../resources/timing-tests.js"></script>
 <body>
 <div id="log"></div>
 <iframe width="10" height="10" id="iframe"></iframe>
@@ -17,8 +19,7 @@
 // Tests on Element
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null);
+  const anim = createDiv(t).animate(null);
   assert_class_string(anim, 'Animation', 'Returned object is an Animation');
 }, 'Element.animate() creates an Animation object');
 
@@ -57,26 +58,9 @@
 }, 'Element.animate() creates an Animation object with a KeyframeEffect'
    + ' that is created in the relevant realm of the target element');
 
-test(t => {
-  const iframe = window.frames[0];
-  const div = createDiv(t, iframe.document);
-  const anim = div.animate(null);
-  assert_equals(Object.getPrototypeOf(anim.effect.timing),
-                iframe.AnimationEffectTiming.prototype,
-                'The prototype of the created AnimationEffectTiming is that'
-                + ' defined on the relevant global for the target element');
-  assert_not_equals(Object.getPrototypeOf(anim.effect.timing),
-                    AnimationEffectTiming.prototype,
-                    'The prototype of the created AnimationEffectTiming is NOT'
-                    + ' that of the current global');
-}, 'Element.animate() creates an Animation object with a KeyframeEffect'
-   + ' whose AnimationEffectTiming object is created in the relevant realm'
-   + ' of the target element');
-
 for (const subtest of gEmptyKeyframeListTests) {
   test(t => {
-    const div = createDiv(t);
-    const anim = div.animate(subtest, 2000);
+    const anim = createDiv(t).animate(subtest, 2000);
     assert_not_equals(anim, null);
   }, 'Element.animate() accepts empty keyframe lists ' +
      `(input: ${JSON.stringify(subtest)})`);
@@ -84,8 +68,7 @@
 
 for (const subtest of gKeyframesTests) {
   test(t => {
-    const div = createDiv(t);
-    const anim = div.animate(subtest.input, 2000);
+    const anim = createDiv(t).animate(subtest.input, 2000);
     assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output);
   }, `Element.animate() accepts ${subtest.desc}`);
 }
@@ -99,54 +82,101 @@
   }, `Element.animate() does not accept ${subtest.desc}`);
 }
 
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  assert_equals(anim.effect.getTiming().duration, 2000);
+  assert_default_timing_except(anim.effect, ['duration']);
+}, 'Element.animate() accepts a double as an options argument');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: Infinity, fill: 'forwards' });
+  assert_equals(anim.effect.getTiming().duration, Infinity);
+  assert_equals(anim.effect.getTiming().fill, 'forwards');
+  assert_default_timing_except(anim.effect, ['duration', 'fill']);
+}, 'Element.animate() accepts a KeyframeAnimationOptions argument');
+
+test(t => {
+  const anim = createDiv(t).animate(null);
+  assert_default_timing_except(anim.effect, []);
+}, 'Element.animate() accepts an absent options argument');
+
+for (const invalid of gBadDelayValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { delay: invalid });
+    });
+  }, `Element.animate() does not accept invalid delay value: ${invalid}`);
+}
+
+test(t => {
+  const anim = createDiv(t).animate(null, { duration: 'auto' });
+  assert_equals(anim.effect.getTiming().duration, 'auto', 'set duration \'auto\'');
+  assert_equals(anim.effect.getComputedTiming().duration, 0,
+                'getComputedTiming() after set duration \'auto\'');
+}, 'Element.animate() accepts a duration of \'auto\' using a dictionary'
+   + ' object');
+
+for (const invalid of gBadDurationValues) {
+  if (typeof invalid === 'string' && !isNaN(parseFloat(invalid))) {
+    continue;
+  }
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, invalid);
+    });
+  }, 'Element.animate() does not accept invalid duration value: '
+     + (typeof invalid === 'string' ? `"${invalid}"` : invalid));
+}
+
+for (const invalid of gBadDurationValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { duration: invalid });
+    });
+  }, 'Element.animate() does not accept invalid duration value: '
+     + (typeof invalid === 'string' ? `"${invalid}"` : invalid)
+     + ' using a dictionary object');
+}
+
 for (const invalidEasing of gInvalidEasings) {
   test(t => {
-    const div = createDiv(t);
     assert_throws(new TypeError, () => {
-      div.animate({ easing: invalidEasing }, 2000);
+      createDiv(t).animate({ easing: invalidEasing }, 2000);
     });
   }, `Element.animate() does not accept invalid easing: '${invalidEasing}'`);
 }
 
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_equals(anim.effect.timing.duration, 2000);
-  // Also check that unspecified parameters receive their default values
-  assert_equals(anim.effect.timing.fill, 'auto');
-}, 'Element.animate() accepts a double as an options argument');
+for (const invalid of gBadIterationStartValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { iterationStart: invalid });
+    });
+  }, 'Element.animate() does not accept invalid iterationStart value: ' +
+     invalid);
+}
+
+for (const invalid of gBadIterationsValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { iterations: invalid });
+    });
+  }, 'Element.animate() does not accept invalid iterations value: ' +
+     invalid);
+}
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: Infinity, fill: 'forwards' });
-  assert_equals(anim.effect.timing.duration, Infinity);
-  assert_equals(anim.effect.timing.fill, 'forwards');
-  // Also check that unspecified parameters receive their default values
-  assert_equals(anim.effect.timing.direction, 'normal');
-}, 'Element.animate() accepts a KeyframeAnimationOptions argument');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] });
-  assert_equals(anim.effect.timing.duration, 'auto');
-}, 'Element.animate() accepts an absent options argument');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  const anim = createDiv(t).animate(null, 2000);
   assert_equals(anim.id, '');
 }, 'Element.animate() correctly sets the id attribute when no id is specified');
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, { id: 'test' });
+  const anim = createDiv(t).animate(null, { id: 'test' });
   assert_equals(anim.id, 'test');
 }, 'Element.animate() correctly sets the id attribute');
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  const anim = createDiv(t).animate(null, 2000);
   assert_equals(anim.timeline, document.timeline);
 }, 'Element.animate() correctly sets the Animation\'s timeline');
 
@@ -157,7 +187,7 @@
 
   iframe.addEventListener('load', t.step_func(() => {
     const div = createDiv(t, iframe.contentDocument);
-    const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+    const anim = div.animate(null, 2000);
     assert_equals(anim.timeline, iframe.contentDocument.timeline);
     iframe.remove();
     t.done();
@@ -168,21 +198,20 @@
    'triggered on an element in a different document');
 
 test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_equals(anim.playState, 'pending');
+  const anim = createDiv(t).animate(null, 2000);
+  assert_equals(anim.playState, 'running');
 }, 'Element.animate() calls play on the Animation');
 
 // Tests on CSSPseudoElement
 
 test(t => {
-  const pseudoTarget = createPseudo(t, 'before');
+  const pseudoTarget = getPseudoElement(t, 'before');
   const anim = pseudoTarget.animate(null);
   assert_class_string(anim, 'Animation', 'The returned object is an Animation');
 }, 'CSSPseudoElement.animate() creates an Animation object');
 
 test(t => {
-  const pseudoTarget = createPseudo(t, 'before');
+  const pseudoTarget = getPseudoElement(t, 'before');
   const anim = pseudoTarget.animate(null);
   assert_equals(anim.effect.target, pseudoTarget,
                 'The returned Animation targets to the correct object');
diff --git a/web-animations/interfaces/Animatable/getAnimations.html b/web-animations/interfaces/Animatable/getAnimations.html
index ec50df3..182b5cb 100644
--- a/web-animations/interfaces/Animatable/getAnimations.html
+++ b/web-animations/interfaces/Animatable/getAnimations.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animatable.getAnimations</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animatable-getanimations">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-getanimations">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -75,12 +75,14 @@
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned when it is finished');
 
-  animation.effect.timing.duration += 100 * MS_PER_SEC;
+  animation.effect.updateTiming({
+    duration: animation.effect.getTiming().duration + 100 * MS_PER_SEC,
+  });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after extending the'
                       + ' duration');
 
-  animation.effect.timing.duration = 0;
+  animation.effect.updateTiming({ duration: 0 });
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned after setting the'
                       + ' duration to zero');
@@ -91,13 +93,13 @@
   const div = createDiv(t);
   const animation = div.animate(null, 100 * MS_PER_SEC);
 
-  animation.effect.timing.endDelay = -200 * MS_PER_SEC;
+  animation.effect.updateTiming({ endDelay: -200 * MS_PER_SEC });
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned after setting a'
                       + ' negative end delay such that the end time is less'
                       + ' than the current time');
 
-  animation.effect.timing.endDelay = 100 * MS_PER_SEC;
+  animation.effect.updateTiming({ endDelay: 100 * MS_PER_SEC });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after setting a positive'
                       + ' end delay such that the end time is more than the'
@@ -113,17 +115,17 @@
   assert_array_equals(div.getAnimations(), [],
                       'Animation should not be returned when it is finished');
 
-  animation.effect.timing.iterations = 10;
+  animation.effect.updateTiming({ iterations: 10 });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after inreasing the'
                       + ' number of iterations');
 
-  animation.effect.timing.iterations = 0;
+  animation.effect.updateTiming({ iterations: 0 });
   assert_array_equals(div.getAnimations(), [],
                       'Animations should not be returned after setting the'
                       + ' iteration count to zero');
 
-  animation.effect.timing.iterations = Infinity;
+  animation.effect.updateTiming({ iterations: Infinity });
   assert_array_equals(div.getAnimations(), [animation],
                       'Animation should be returned after inreasing the'
                       + ' number of iterations to infinity');
diff --git a/web-animations/interfaces/Animation/cancel.html b/web-animations/interfaces/Animation/cancel.html
index a342b2e..711a339 100644
--- a/web-animations/interfaces/Animation/cancel.html
+++ b/web-animations/interfaces/Animation/cancel.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.cancel</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-cancel">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-cancel">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -29,7 +29,7 @@
   const div = createDiv(t);
   const animation = div.animate({ marginLeft: ['100px', '200px'] },
                                 100 * MS_PER_SEC);
-  animation.effect.timing.easing = 'linear';
+  animation.effect.updateTiming({ easing: 'linear' });
   animation.cancel();
   assert_equals(getComputedStyle(div).marginLeft, '0px',
                 'margin-left style is not animated after cancelling');
diff --git a/web-animations/interfaces/Animation/constructor.html b/web-animations/interfaces/Animation/constructor.html
index 6b7978c..fcbaab1 100644
--- a/web-animations/interfaces/Animation/constructor.html
+++ b/web-animations/interfaces/Animation/constructor.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation constructor</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-animation">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-animation">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -14,7 +14,7 @@
 const gTarget = document.getElementById('target');
 
 function createEffect() {
-  return new KeyframeEffectReadOnly(gTarget, { opacity: [0, 1] });
+  return new KeyframeEffect(gTarget, { opacity: [0, 1] });
 }
 
 function createNull() {
@@ -82,10 +82,9 @@
 }
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null,
-                                            { left: ['10px', '20px'] },
-                                            { duration: 10000,
-                                              fill: 'forwards' });
+  const effect = new KeyframeEffect(null,
+                                    { left: ['10px', '20px'] },
+                                    { duration: 10000, fill: 'forwards' });
   const anim = new Animation(effect, document.timeline);
   anim.pause();
   assert_equals(effect.getComputedTiming().progress, 0.0);
@@ -100,7 +99,7 @@
 
   iframe.addEventListener('load', t.step_func(() => {
     const div = createDiv(t, iframe.contentDocument);
-    const effect = new KeyframeEffectReadOnly(div, null, 10000);
+    const effect = new KeyframeEffect(div, null, 10000);
     const anim = new Animation(effect);
     assert_equals(anim.timeline, document.timeline);
     iframe.remove();
diff --git a/web-animations/interfaces/Animation/effect.html b/web-animations/interfaces/Animation/effect.html
index 339028e..cb8bc09 100644
--- a/web-animations/interfaces/Animation/effect.html
+++ b/web-animations/interfaces/Animation/effect.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.effect</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-effect">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-effect">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -14,7 +14,7 @@
   const anim = new Animation();
   assert_equals(anim.effect, null, 'initial effect is null');
 
-  const newEffect = new KeyframeEffectReadOnly(createDiv(t), null);
+  const newEffect = new KeyframeEffect(createDiv(t), null);
   anim.effect = newEffect;
   assert_equals(anim.effect, newEffect, 'new effect is set');
 }, 'effect is set correctly.');
diff --git a/web-animations/interfaces/Animation/finish.html b/web-animations/interfaces/Animation/finish.html
deleted file mode 100644
index c413d04..0000000
--- a/web-animations/interfaces/Animation/finish.html
+++ /dev/null
@@ -1,246 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Animation.finish</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-finish">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-const gKeyFrames = { 'marginLeft': ['100px', '200px'] };
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.playbackRate = 0;
-
-  assert_throws({name: 'InvalidStateError'}, () => {
-    animation.finish();
-  });
-}, 'Test exceptions when finishing non-running animation');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames,
-                                { duration : 100 * MS_PER_SEC,
-                                  iterations : Infinity });
-
-  assert_throws({name: 'InvalidStateError'}, () => {
-    animation.finish();
-  });
-}, 'Test exceptions when finishing infinite animation');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.finish();
-
-  assert_equals(animation.currentTime, 100 * MS_PER_SEC,
-                'After finishing, the currentTime should be set to the end ' +
-                'of the active duration');
-}, 'Test finishing of animation');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-   // 1s past effect end
-  animation.currentTime =
-    animation.effect.getComputedTiming().endTime + 1 * MS_PER_SEC;
-  animation.finish();
-
-  assert_equals(animation.currentTime, 100 * MS_PER_SEC,
-                'After finishing, the currentTime should be set back to the ' +
-                'end of the active duration');
-}, 'Test finishing of animation with a current time past the effect end');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.currentTime = 100 * MS_PER_SEC;
-  return animation.finished.then(() => {
-    animation.playbackRate = -1;
-    animation.finish();
-
-    assert_equals(animation.currentTime, 0,
-                  'After finishing a reversed animation the currentTime ' +
-                  'should be set to zero');
-  });
-}, 'Test finishing of reversed animation');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.currentTime = 100 * MS_PER_SEC;
-  return animation.finished.then(() => {
-    animation.playbackRate = -1;
-    animation.currentTime = -1000;
-    animation.finish();
-
-    assert_equals(animation.currentTime, 0,
-                  'After finishing a reversed animation the currentTime ' +
-                  'should be set back to zero');
-  });
-}, 'Test finishing of reversed animation with a current time less than zero');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.pause();
-  return animation.ready.then(() => {
-    animation.finish();
-
-    assert_equals(animation.playState, 'finished',
-                  'The play state of a paused animation should become ' +
-                  '"finished" after finish() is called');
-    assert_times_equal(animation.startTime,
-                       animation.timeline.currentTime - 100 * MS_PER_SEC,
-                       'The start time of a paused animation should be set ' +
-                       'after calling finish()');
-  });
-}, 'Test finish() while paused');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.pause();
-  // Update playbackRate so we can test that the calculated startTime
-  // respects it
-  animation.playbackRate = 2;
-  // While animation is still pause-pending call finish()
-  animation.finish();
-
-  assert_equals(animation.playState, 'finished',
-                'The play state of a pause-pending animation should become ' +
-                '"finished" after finish() is called');
-  assert_times_equal(animation.startTime,
-                     animation.timeline.currentTime - 100 * MS_PER_SEC / 2,
-                     'The start time of a pause-pending animation should ' +
-                     'be set after calling finish()');
-}, 'Test finish() while pause-pending with positive playbackRate');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.pause();
-  animation.playbackRate = -2;
-  animation.finish();
-
-  assert_equals(animation.playState, 'finished',
-                'The play state of a pause-pending animation should become ' +
-                '"finished" after finish() is called');
-  assert_equals(animation.startTime, animation.timeline.currentTime,
-                'The start time of a pause-pending animation should be ' +
-                'set after calling finish()');
-}, 'Test finish() while pause-pending with negative playbackRate');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  animation.playbackRate = 0.5;
-  animation.finish();
-
-  assert_equals(animation.playState, 'finished',
-                'The play state of a play-pending animation should become ' +
-                '"finished" after finish() is called');
-  assert_times_equal(animation.startTime,
-                     animation.timeline.currentTime - 100 * MS_PER_SEC / 0.5,
-                     'The start time of a play-pending animation should ' +
-                     'be set after calling finish()');
-}, 'Test finish() while play-pending');
-
-// FIXME: Add a test for when we are play-pending without an active timeline.
-// - In that case even after calling finish() we should still be pending but
-//   the current time should be updated
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  return animation.ready.then(() => {
-    animation.pause();
-    animation.play();
-    // We are now in the unusual situation of being play-pending whilst having
-    // a resolved start time. Check that finish() still triggers a transition
-    // to the finished state immediately.
-    animation.finish();
-
-    assert_equals(animation.playState, 'finished',
-                  'After aborting a pause then calling finish() the play ' +
-                  'state of an animation should become "finished" immediately');
-  });
-}, 'Test finish() during aborted pause');
-
-promise_test(t => {
-  const div = createDiv(t);
-  div.style.marginLeft = '10px';
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  return animation.ready.then(() => {
-    animation.finish();
-    const marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-
-    assert_equals(marginLeft, 10,
-                  'The computed style should be reset when finish() is ' +
-                  'called');
-  });
-}, 'Test resetting of computed style');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC);
-  let resolvedFinished = false;
-  animation.finished.then(() => {
-    resolvedFinished = true;
-  });
-
-  return animation.ready.then(() => {
-    animation.finish();
-  }).then(() => {
-    assert_true(resolvedFinished,
-      'Animation.finished should be resolved soon after ' +
-      'Animation.finish()');
-  });
-}, 'Test finish() resolves finished promise synchronously');
-
-promise_test(t => {
-  const effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC);
-  const animation = new Animation(effect, document.timeline);
-  let resolvedFinished = false;
-  animation.finished.then(() => {
-    resolvedFinished = true;
-  });
-
-  return animation.ready.then(() => {
-    animation.finish();
-  }).then(() => {
-    assert_true(resolvedFinished,
-                'Animation.finished should be resolved soon after ' +
-                'Animation.finish()');
-  });
-}, 'Test finish() resolves finished promise synchronously with an animation ' +
-   'without a target');
-
-promise_test(t => {
-  const effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC);
-  const animation = new Animation(effect, document.timeline);
-  animation.play();
-
-  let resolvedFinished = false;
-  animation.finished.then(() => {
-    resolvedFinished = true;
-  });
-
-  return animation.ready.then(() => {
-    animation.currentTime = animation.effect.getComputedTiming().endTime - 1;
-    return waitForAnimationFrames(2);
-  }).then(() => {
-    assert_true(resolvedFinished,
-                'Animation.finished should be resolved soon after ' +
-                'Animation finishes normally');
-  });
-}, 'Test normally finished animation resolves finished promise synchronously ' +
-   'with an animation without a target');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/Animation/finished.html b/web-animations/interfaces/Animation/finished.html
index 7d0aa38..563d4ba 100644
--- a/web-animations/interfaces/Animation/finished.html
+++ b/web-animations/interfaces/Animation/finished.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.finished</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-finished">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-finished">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -163,7 +163,7 @@
   animation.cancel();
 
   return retPromise;
-}, 'finished promise is rejected when an animation is cancelled by calling ' +
+}, 'finished promise is rejected when an animation is canceled by calling ' +
    'cancel()');
 
 promise_test(t => {
@@ -175,30 +175,9 @@
     animation.cancel();
     assert_not_equals(animation.finished, previousFinishedPromise,
                       'A new finished promise should be created when'
-                      + ' cancelling a finished animation');
+                      + ' canceling a finished animation');
   });
-}, 'cancelling an already-finished animation replaces the finished promise');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 100 * MS_PER_SEC);
-  animation.cancel();
-  // The spec says we still create a new finished promise and reject the old
-  // one even if we're already idle. That behavior might change, but for now
-  // test that we do that.
-  const retPromise = animation.finished.catch(err => {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-  });
-
-  // Redundant call to cancel();
-  const previousFinishedPromise = animation.finished;
-  animation.cancel();
-  assert_not_equals(animation.finished, previousFinishedPromise,
-                    'A redundant call to cancel() should still generate a new'
-                    + ' finished promise');
-  return retPromise;
-}, 'cancelling an idle animation still replaces the finished promise');
+}, 'canceling an already-finished animation replaces the finished promise');
 
 promise_test(t => {
   const div = createDiv(t);
@@ -210,7 +189,7 @@
   animation.currentTime = HALF_DUR;
   return animation.ready.then(() => {
     currentTimeBeforeShortening = animation.currentTime;
-    animation.effect.timing.duration = QUARTER_DUR;
+    animation.effect.updateTiming({ duration: QUARTER_DUR });
     // Below we use gotNextFrame to check that shortening of the animation
     // duration causes the finished promise to resolve, rather than it just
     // getting resolved on the next animation frame. This relies on the fact
@@ -227,7 +206,7 @@
     assert_equals(animation.currentTime, currentTimeBeforeShortening,
                   'currentTime should be unchanged when duration shortened');
     const previousFinishedPromise = animation.finished;
-    animation.effect.timing.duration = 100 * MS_PER_SEC;
+    animation.effect.updateTiming({ duration: 100 * MS_PER_SEC });
     assert_not_equals(animation.finished, previousFinishedPromise,
                       'Finished promise should change after lengthening the ' +
                       'duration causes the animation to become active');
diff --git a/web-animations/interfaces/Animation/id.html b/web-animations/interfaces/Animation/id.html
index b41076a..5b9586b 100644
--- a/web-animations/interfaces/Animation/id.html
+++ b/web-animations/interfaces/Animation/id.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.id</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-id">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-id">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/Animation/idlharness.html b/web-animations/interfaces/Animation/idlharness.html
index 74813e1..4d78ca5 100644
--- a/web-animations/interfaces/Animation/idlharness.html
+++ b/web-animations/interfaces/Animation/idlharness.html
@@ -1,47 +1,29 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Animation IDL</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#animation">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#animation">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
 <div id="log"></div>
-<script type="text/plain" id="Animation-IDL">
-enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
-
-[Constructor (optional AnimationEffectReadOnly? effect = null,
-              optional AnimationTimeline? timeline)]
-interface Animation : EventTarget {
-             attribute DOMString                id;
-             attribute AnimationEffectReadOnly? effect;
-             attribute AnimationTimeline?       timeline;
-             attribute double?                  startTime;
-             attribute double?                  currentTime;
-             attribute double                   playbackRate;
-    readonly attribute AnimationPlayState       playState;
-    readonly attribute Promise<Animation>       ready;
-    readonly attribute Promise<Animation>       finished;
-             attribute EventHandler             onfinish;
-             attribute EventHandler             oncancel;
-    void cancel ();
-    void finish ();
-    void play ();
-    void pause ();
-    void reverse ();
-};
-</script>
 <script>
 'use strict';
 
-const idlArray = new IdlArray();
+promise_test(async () => {
+  const idl = await fetch('/interfaces/web-animations.idl').then(r => r.text());
+  const dom = await fetch('/interfaces/dom.idl').then(r => r.text());
+  const html = await fetch('/interfaces/html.idl').then(r => r.text());
 
-idlArray.add_untested_idls('interface AnimationTimeline {};');
-idlArray.add_untested_idls('interface EventHandler {};');
-idlArray.add_untested_idls('interface EventTarget {};');
-idlArray.add_idls(document.getElementById('Animation-IDL').textContent);
-idlArray.add_objects( { Animation: ['new Animation()'] } );
-
-idlArray.test();
+  const idlArray = new IdlArray();
+  idlArray.add_idls(idl, {only: ['Animation', 'AnimationPlayState']});
+  idlArray.add_dependency_idls(idl);
+  idlArray.add_dependency_idls(dom);
+  idlArray.add_dependency_idls(html);
+  idlArray.add_untested_idls('interface CSSPseudoElement {};');
+  idlArray.add_objects( { Animation: ['new Animation()'] } );
+  idlArray.test();
+  done();
+}, 'Animation interface.');
 
 </script>
diff --git a/web-animations/interfaces/Animation/oncancel.html b/web-animations/interfaces/Animation/oncancel.html
index 5f29dab..3e918a4 100644
--- a/web-animations/interfaces/Animation/oncancel.html
+++ b/web-animations/interfaces/Animation/oncancel.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.oncancel</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-oncancel">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-oncancel">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/Animation/onfinish.html b/web-animations/interfaces/Animation/onfinish.html
index 0313385..79d1d70 100644
--- a/web-animations/interfaces/Animation/onfinish.html
+++ b/web-animations/interfaces/Animation/onfinish.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.onfinish</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-onfinish">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-onfinish">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/Animation/pause.html b/web-animations/interfaces/Animation/pause.html
index 3be8bc3..0649c2f 100644
--- a/web-animations/interfaces/Animation/pause.html
+++ b/web-animations/interfaces/Animation/pause.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.pause</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-pause">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-pause">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -39,12 +39,12 @@
 
   assert_equals(animation.currentTime, 0, 'currentTime is set to 0');
   assert_equals(animation.startTime, null, 'startTime is not set');
-  assert_equals(animation.playState, 'pending', 'initially pause-pending');
+  assert_equals(animation.playState, 'paused', 'in paused play state');
+  assert_true(animation.pending, 'initially pause-pending');
 
   // Check it still resolves as expected
   return animation.ready.then(() => {
-    assert_equals(animation.playState, 'paused',
-                  'resolves to paused state asynchronously');
+    assert_false(animation.pending, 'no longer pending');
     assert_equals(animation.currentTime, 0,
                   'keeps the initially set currentTime');
   });
diff --git a/web-animations/interfaces/Animation/pending.html b/web-animations/interfaces/Animation/pending.html
new file mode 100644
index 0000000..fe7efe0
--- /dev/null
+++ b/web-animations/interfaces/Animation/pending.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.pending</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-pending">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(t => {
+  const div = createDiv(t);
+  const animation = div.animate({}, 100 * MS_PER_SEC);
+
+  assert_true(animation.pending);
+  return animation.ready.then(() => {
+    assert_false(animation.pending);
+  });
+}, 'reports true -> false when initially played');
+
+promise_test(t => {
+  const div = createDiv(t);
+  const animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.pause();
+
+  assert_true(animation.pending);
+  return animation.ready.then(() => {
+    assert_false(animation.pending);
+  });
+}, 'reports true -> false when paused');
+
+</script>
+</body>
diff --git a/web-animations/interfaces/Animation/play.html b/web-animations/interfaces/Animation/play.html
index d3e0122..3e8f923 100644
--- a/web-animations/interfaces/Animation/play.html
+++ b/web-animations/interfaces/Animation/play.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.play</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-play">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-play">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/Animation/playState.html b/web-animations/interfaces/Animation/playState.html
deleted file mode 100644
index cf6ce66..0000000
--- a/web-animations/interfaces/Animation/playState.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Animation.playState</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-playstate">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 100 * MS_PER_SEC);
-
-  assert_equals(animation.playState, 'pending');
-  return animation.ready.then(() => {
-    assert_equals(animation.playState, 'running');
-  });
-}, 'Animation.playState reports \'pending\'->\'running\' when initially ' +
-   'played');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 100 * MS_PER_SEC);
-  animation.pause();
-
-  assert_equals(animation.playState, 'pending');
-  return animation.ready.then(() => {
-    assert_equals(animation.playState, 'paused');
-  });
-}, 'Animation.playState reports \'pending\'->\'paused\' when pausing');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 100 * MS_PER_SEC);
-  animation.cancel();
-  assert_equals(animation.playState, 'idle');
-}, 'Animation.playState is \'idle\' when canceled.');
-
-test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 100 * MS_PER_SEC);
-  animation.cancel();
-  animation.currentTime = 50 * MS_PER_SEC;
-  assert_equals(animation.playState, 'paused',
-                'After seeking an idle animation, it is effectively paused');
-}, 'Animation.playState is \'paused\' after cancelling an animation, ' +
-   'seeking it makes it paused');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/Animation/playbackRate.html b/web-animations/interfaces/Animation/playbackRate.html
deleted file mode 100644
index 4f9bed1..0000000
--- a/web-animations/interfaces/Animation/playbackRate.html
+++ /dev/null
@@ -1,86 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Animation.playbackRate</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-playbackrate">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-function assert_playbackrate(animation,
-                             previousAnimationCurrentTime,
-                             previousTimelineCurrentTime,
-                             description) {
-  const accuracy = 0.001; /* accuracy of DOMHighResTimeStamp */
-  const animationCurrentTimeDifference =
-    animation.currentTime - previousAnimationCurrentTime;
-  const timelineCurrentTimeDifference =
-    animation.timeline.currentTime - previousTimelineCurrentTime;
-
-  assert_approx_equals(animationCurrentTimeDifference,
-                       timelineCurrentTimeDifference * animation.playbackRate,
-                       accuracy,
-                       description);
-}
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(null, 100 * MS_PER_SEC);
-  return animation.ready.then(() => {
-    animation.currentTime = 7 * MS_PER_SEC; // ms
-    animation.playbackRate = 0.5;
-
-    assert_equals(animation.currentTime, 7 * MS_PER_SEC,
-      'Reducing Animation.playbackRate should not change the currentTime ' +
-      'of a playing animation');
-    animation.playbackRate = 2;
-    assert_equals(animation.currentTime, 7 * MS_PER_SEC,
-      'Increasing Animation.playbackRate should not change the currentTime ' +
-      'of a playing animation');
-  });
-}, 'Test the initial effect of setting playbackRate on currentTime');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(null, 100 * MS_PER_SEC);
-  animation.playbackRate = 2;
-  let previousTimelineCurrentTime;
-  let previousAnimationCurrentTime;
-  return animation.ready.then(() => {
-    previousAnimationCurrentTime = animation.currentTime;
-    previousTimelineCurrentTime = animation.timeline.currentTime;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_playbackrate(animation,
-      previousAnimationCurrentTime,
-      previousTimelineCurrentTime,
-      'animation.currentTime should be 2 times faster than timeline.');
-  });
-}, 'Test the effect of setting playbackRate on currentTime');
-
-promise_test(t => {
-  const div = createDiv(t);
-  const animation = div.animate(null, 100 * MS_PER_SEC);
-  animation.playbackRate = 2;
-  let previousTimelineCurrentTime;
-  let previousAnimationCurrentTime;
-  return animation.ready.then(() => {
-    previousAnimationCurrentTime = animation.currentTime;
-    previousTimelineCurrentTime = animation.timeline.currentTime;
-    animation.playbackRate = 1;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(animation.playbackRate, 1,
-      'sanity check: animation.playbackRate is still 1.');
-    assert_playbackrate(animation,
-      previousAnimationCurrentTime,
-      previousTimelineCurrentTime,
-      'animation.currentTime should be the same speed as timeline now.');
-  });
-}, 'Test the effect of setting playbackRate while playing animation');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/Animation/ready.html b/web-animations/interfaces/Animation/ready.html
index eb8713d..461c4e9 100644
--- a/web-animations/interfaces/Animation/ready.html
+++ b/web-animations/interfaces/Animation/ready.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Animation.ready</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-ready">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-ready">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/Animation/startTime.html b/web-animations/interfaces/Animation/startTime.html
index fab8534..61f7695 100644
--- a/web-animations/interfaces/Animation/startTime.html
+++ b/web-animations/interfaces/Animation/startTime.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>Animation.startTime</title>
 <link rel="help"
-href="https://w3c.github.io/web-animations/#dom-animation-starttime">
+href="https://drafts.csswg.org/web-animations/#dom-animation-starttime">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/AnimationEffect/getComputedTiming.html b/web-animations/interfaces/AnimationEffect/getComputedTiming.html
new file mode 100644
index 0000000..10bd193
--- /dev/null
+++ b/web-animations/interfaces/AnimationEffect/getComputedTiming.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>AnimationEffect.getComputedTiming</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animationeffect-getcomputedtiming">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  const effect = new KeyframeEffect(null, null);
+
+  const ct = effect.getComputedTiming();
+  assert_equals(ct.delay, 0, 'computed delay');
+  assert_equals(ct.endDelay, 0, 'computed endDelay');
+  assert_equals(ct.fill, 'none', 'computed fill');
+  assert_equals(ct.iterationStart, 0.0, 'computed iterationStart');
+  assert_equals(ct.iterations, 1.0, 'computed iterations');
+  assert_equals(ct.duration, 0, 'computed duration');
+  assert_equals(ct.direction, 'normal', 'computed direction');
+  assert_equals(ct.easing, 'linear', 'computed easing');
+}, 'values of getComputedTiming() when a KeyframeEffect is ' +
+   'constructed without any KeyframeEffectOptions object');
+
+const gGetComputedTimingTests = [
+  { desc:     'an empty KeyframeEffectOptions object',
+    input:    { },
+    expected: { } },
+  { desc:     'a normal KeyframeEffectOptions object',
+    input:    { delay: 1000,
+                endDelay: 2000,
+                fill: 'auto',
+                iterationStart: 0.5,
+                iterations: 5.5,
+                duration: 'auto',
+                direction: 'alternate',
+                easing: 'steps(2)' },
+    expected: { delay: 1000,
+                endDelay: 2000,
+                fill: 'none',
+                iterationStart: 0.5,
+                iterations: 5.5,
+                duration: 0,
+                direction: 'alternate',
+                easing: 'steps(2)' } },
+  { desc:     'a double value',
+    input:    3000,
+    timing:   { duration: 3000 },
+    expected: { delay: 0,
+                fill: 'none',
+                iterations: 1,
+                duration: 3000,
+                direction: 'normal' } },
+  { desc:     '+Infinity',
+    input:    Infinity,
+    expected: { duration: Infinity } },
+  { desc:     'an Infinity duration',
+    input:    { duration: Infinity },
+    expected: { duration: Infinity } },
+  { desc:     'an auto duration',
+    input:    { duration: 'auto' },
+    expected: { duration: 0 } },
+  { desc:     'an Infinity iterations',
+    input:    { iterations: Infinity },
+    expected: { iterations: Infinity } },
+  { desc:     'an auto fill',
+    input:    { fill: 'auto' },
+    expected: { fill: 'none' } },
+  { desc:     'a forwards fill',
+    input:    { fill: 'forwards' },
+    expected: { fill: 'forwards' } }
+];
+
+for (const stest of gGetComputedTimingTests) {
+  test(t => {
+    const effect = new KeyframeEffect(null, null, stest.input);
+
+    // Helper function to provide default expected values when the test does
+    // not supply them.
+    const expected = (field, defaultValue) => {
+      return field in stest.expected ? stest.expected[field] : defaultValue;
+    };
+
+    const ct = effect.getComputedTiming();
+    assert_equals(ct.delay, expected('delay', 0),
+                  'computed delay');
+    assert_equals(ct.endDelay, expected('endDelay', 0),
+                  'computed endDelay');
+    assert_equals(ct.fill, expected('fill', 'none'),
+                  'computed fill');
+    assert_equals(ct.iterationStart, expected('iterationStart', 0),
+                  'computed iterations');
+    assert_equals(ct.iterations, expected('iterations', 1),
+                  'computed iterations');
+    assert_equals(ct.duration, expected('duration', 0),
+                  'computed duration');
+    assert_equals(ct.direction, expected('direction', 'normal'),
+                  'computed direction');
+    assert_equals(ct.easing, expected('easing', 'linear'),
+                  'computed easing');
+
+  }, 'values of getComputedTiming() when a KeyframeEffect is'
+     + ` constructed by ${stest.desc}`);
+}
+
+const gActiveDurationTests = [
+  { desc:     'an empty KeyframeEffectOptions object',
+    input:    { },
+    expected: 0 },
+  { desc:     'a non-zero duration and default iteration count',
+    input:    { duration: 1000 },
+    expected: 1000 },
+  { desc:     'a non-zero duration and integral iteration count',
+    input:    { duration: 1000, iterations: 7 },
+    expected: 7000 },
+  { desc:     'a non-zero duration and fractional iteration count',
+    input:    { duration: 1000, iterations: 2.5 },
+    expected: 2500 },
+  { desc:     'an non-zero duration and infinite iteration count',
+    input:    { duration: 1000, iterations: Infinity },
+    expected: Infinity },
+  { desc:     'an non-zero duration and zero iteration count',
+    input:    { duration: 1000, iterations: 0 },
+    expected: 0 },
+  { desc:     'a zero duration and default iteration count',
+    input:    { duration: 0 },
+    expected: 0 },
+  { desc:     'a zero duration and fractional iteration count',
+    input:    { duration: 0, iterations: 2.5 },
+    expected: 0 },
+  { desc:     'a zero duration and infinite iteration count',
+    input:    { duration: 0, iterations: Infinity },
+    expected: 0 },
+  { desc:     'a zero duration and zero iteration count',
+    input:    { duration: 0, iterations: 0 },
+    expected: 0 },
+  { desc:     'an infinite duration and default iteration count',
+    input:    { duration: Infinity },
+    expected: Infinity },
+  { desc:     'an infinite duration and zero iteration count',
+    input:    { duration: Infinity, iterations: 0 },
+    expected: 0 },
+  { desc:     'an infinite duration and fractional iteration count',
+    input:    { duration: Infinity, iterations: 2.5 },
+    expected: Infinity },
+  { desc:     'an infinite duration and infinite iteration count',
+    input:    { duration: Infinity, iterations: Infinity },
+    expected: Infinity },
+];
+
+for (const stest of gActiveDurationTests) {
+  test(t => {
+    const effect = new KeyframeEffect(null, null, stest.input);
+
+    assert_equals(effect.getComputedTiming().activeDuration,
+                  stest.expected);
+
+  }, `getComputedTiming().activeDuration for ${stest.desc}`);
+}
+
+const gEndTimeTests = [
+  { desc:     'an empty KeyframeEffectOptions object',
+    input:    { },
+    expected: 0 },
+  { desc:     'a non-zero duration and default iteration count',
+    input:    { duration: 1000 },
+    expected: 1000 },
+  { desc:     'a non-zero duration and non-default iteration count',
+    input:    { duration: 1000, iterations: 2.5 },
+    expected: 2500 },
+  { desc:     'a non-zero duration and non-zero delay',
+    input:    { duration: 1000, delay: 1500 },
+    expected: 2500 },
+  { desc:     'a non-zero duration, non-zero delay and non-default iteration',
+    input:    { duration: 1000, delay: 1500, iterations: 2 },
+    expected: 3500 },
+  { desc:     'an infinite iteration count',
+    input:    { duration: 1000, iterations: Infinity },
+    expected: Infinity },
+  { desc:     'an infinite duration',
+    input:    { duration: Infinity, iterations: 10 },
+    expected: Infinity },
+  { desc:     'an infinite duration and delay',
+    input:    { duration: Infinity, iterations: 10, delay: 1000 },
+    expected: Infinity },
+  { desc:     'an infinite duration and negative delay',
+    input:    { duration: Infinity, iterations: 10, delay: -1000 },
+    expected: Infinity },
+  { desc:     'an non-zero duration and negative delay',
+    input:    { duration: 1000, iterations: 2, delay: -1000 },
+    expected: 1000 },
+  { desc:     'an non-zero duration and negative delay greater than active ' +
+              'duration',
+    input:    { duration: 1000, iterations: 2, delay: -3000 },
+    expected: 0 },
+  { desc:     'a zero duration and negative delay',
+    input:    { duration: 0, iterations: 2, delay: -1000 },
+    expected: 0 }
+];
+
+for (const stest of gEndTimeTests) {
+  test(t => {
+    const effect = new KeyframeEffect(null, null, stest.input);
+
+    assert_equals(effect.getComputedTiming().endTime,
+                  stest.expected);
+
+  }, `getComputedTiming().endTime for ${stest.desc}`);
+}
+</script>
+</body>
diff --git a/web-animations/interfaces/AnimationEffect/updateTiming.html b/web-animations/interfaces/AnimationEffect/updateTiming.html
new file mode 100644
index 0000000..746f0d7
--- /dev/null
+++ b/web-animations/interfaces/AnimationEffect/updateTiming.html
@@ -0,0 +1,475 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>AnimationEffect.updateTiming</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations-1/#dom-animationeffect-updatetiming">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<script src="../../resources/easing-tests.js"></script>
+<script src="../../resources/timing-tests.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+// ------------------------------
+//  delay
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 100);
+  anim.effect.updateTiming({ delay: 100 });
+  assert_equals(anim.effect.getTiming().delay, 100, 'set delay 100');
+  assert_equals(anim.effect.getComputedTiming().delay, 100,
+                  'getComputedTiming() after set delay 100');
+}, 'Allows setting the delay to a positive number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 100);
+  anim.effect.updateTiming({ delay: -100 });
+  assert_equals(anim.effect.getTiming().delay, -100, 'set delay -100');
+  assert_equals(anim.effect.getComputedTiming().delay, -100,
+                'getComputedTiming() after set delay -100');
+}, 'Allows setting the delay to a negative number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 100);
+  anim.effect.updateTiming({ delay: 100 });
+  assert_equals(anim.effect.getComputedTiming().progress, null);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, null);
+}, 'Allows setting the delay of an animation in progress: positive delay that'
+   + ' causes the animation to be no longer in-effect');
+
+test(t => {
+  const anim = createDiv(t).animate(null, { fill: 'both', duration: 100 });
+  anim.effect.updateTiming({ delay: -50 });
+  assert_equals(anim.effect.getComputedTiming().progress, 0.5);
+}, 'Allows setting the delay of an animation in progress: negative delay that'
+   + ' seeks into the active interval');
+
+test(t => {
+  const anim = createDiv(t).animate(null, { fill: 'both', duration: 100 });
+  anim.effect.updateTiming({ delay: -100 });
+  assert_equals(anim.effect.getComputedTiming().progress, 1);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 0);
+}, 'Allows setting the delay of an animation in progress: large negative delay'
+   + ' that causes the animation to be finished');
+
+for (const invalid of gBadDelayValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    assert_throws({ name: 'TypeError' }, () => {
+      anim.effect.updateTiming({ delay: invalid });
+    });
+  }, `Throws when setting invalid delay value: ${invalid}`);
+}
+
+
+// ------------------------------
+//  endDelay
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ endDelay: 123.45 });
+  assert_time_equals_literal(anim.effect.getTiming().endDelay, 123.45,
+                             'set endDelay 123.45');
+  assert_time_equals_literal(anim.effect.getComputedTiming().endDelay, 123.45,
+                             'getComputedTiming() after set endDelay 123.45');
+}, 'Allows setting the endDelay to a positive number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ endDelay: -1000 });
+  assert_equals(anim.effect.getTiming().endDelay, -1000, 'set endDelay -1000');
+  assert_equals(anim.effect.getComputedTiming().endDelay, -1000,
+                'getComputedTiming() after set endDelay -1000');
+}, 'Allows setting the endDelay to a negative number');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  assert_throws({ name: 'TypeError' }, () => {
+    anim.effect.updateTiming({ endDelay: Infinity });
+  });
+}, 'Throws when setting the endDelay to infinity');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  assert_throws({ name: 'TypeError' }, () => {
+    anim.effect.updateTiming({ endDelay: -Infinity });
+  });
+}, 'Throws when setting the endDelay to negative infinity');
+
+
+// ------------------------------
+//  fill
+// ------------------------------
+
+for (const fill of ['none', 'forwards', 'backwards', 'both']) {
+  test(t => {
+    const anim = createDiv(t).animate(null, 100);
+    anim.effect.updateTiming({ fill });
+    assert_equals(anim.effect.getTiming().fill, fill, 'set fill ' + fill);
+    assert_equals(anim.effect.getComputedTiming().fill, fill,
+                  'getComputedTiming() after set fill ' + fill);
+  }, `Allows setting the fill to '${fill}'`);
+}
+
+
+// ------------------------------
+//  iterationStart
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterationStart: 0.2,
+                                      iterations: 1,
+                                      fill: 'both',
+                                      duration: 100,
+                                      delay: 1 });
+  anim.effect.updateTiming({ iterationStart: 2.5 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
+}, 'Allows setting the iterationStart of an animation in progress:'
+   + ' backwards-filling');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterationStart: 0.2,
+                                      iterations: 1,
+                                      fill: 'both',
+                                      duration: 100,
+                                      delay: 0 });
+  anim.effect.updateTiming({ iterationStart: 2.5 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
+}, 'Allows setting the iterationStart of an animation in progress:'
+   + ' active phase');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterationStart: 0.2,
+                                      iterations: 1,
+                                      fill: 'both',
+                                      duration: 100,
+                                      delay: 0 });
+  anim.finish();
+  anim.effect.updateTiming({ iterationStart: 2.5 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 3);
+}, 'Allows setting the iterationStart of an animation in progress:'
+   + ' forwards-filling');
+
+for (const invalid of gBadIterationStartValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    assert_throws({ name: 'TypeError' }, () => {
+      anim.effect.updateTiming({ iterationStart: invalid });
+    }, `setting ${invalid}`);
+  }, `Throws when setting invalid iterationStart value: ${invalid}`);
+}
+
+// ------------------------------
+//  iterations
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ iterations: 2 });
+  assert_equals(anim.effect.getTiming().iterations, 2, 'set duration 2');
+  assert_equals(anim.effect.getComputedTiming().iterations, 2,
+                       'getComputedTiming() after set iterations 2');
+}, 'Allows setting iterations to a double value');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+  anim.effect.updateTiming({ iterations: Infinity });
+  assert_equals(anim.effect.getTiming().iterations, Infinity,
+                'set duration Infinity');
+  assert_equals(anim.effect.getComputedTiming().iterations, Infinity,
+                       'getComputedTiming() after set iterations Infinity');
+}, 'Allows setting iterations to infinity');
+
+for (const invalid of gBadIterationsValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    assert_throws({ name: 'TypeError' }, () => {
+      anim.effect.updateTiming({ iterations: invalid });
+    });
+  }, `Throws when setting invalid iterations value: ${invalid}`);
+}
+
+test(t => {
+  const anim = createDiv(t).animate(null, { duration: 100000, fill: 'both' });
+
+  anim.finish();
+
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress when animation is finished');
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+                'current iteration when animation is finished');
+
+  anim.effect.updateTiming({ iterations: 2 });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress,
+                             0,
+                             'progress after adding an iteration');
+  assert_time_equals_literal(anim.effect.getComputedTiming().currentIteration,
+                             1,
+                             'current iteration after adding an iteration');
+
+  anim.effect.updateTiming({ iterations: 0 });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress after setting iterations to zero');
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
+                'current iteration after setting iterations to zero');
+
+  anim.effect.updateTiming({ iterations: Infinity });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress after setting iterations to Infinity');
+  assert_equals(anim.effect.getComputedTiming().currentIteration, 1,
+                'current iteration after setting iterations to Infinity');
+}, 'Allows setting the iterations of an animation in progress');
+
+
+// ------------------------------
+//  duration
+// ------------------------------
+
+for (const duration of gGoodDurationValues) {
+  test(t => {
+    const anim = createDiv(t).animate(null, 2000);
+    anim.effect.updateTiming({ duration: duration.specified });
+    if (typeof duration.specified === 'number') {
+      assert_time_equals_literal(anim.effect.getTiming().duration,
+                                 duration.specified,
+                                 'Updates specified duration');
+    } else {
+      assert_equals(anim.effect.getTiming().duration, duration.specified,
+                    'Updates specified duration');
+    }
+    assert_time_equals_literal(anim.effect.getComputedTiming().duration,
+                               duration.computed,
+                               'Updates computed duration');
+  }, `Allows setting the duration to ${duration.specified}`);
+}
+
+for (const invalid of gBadDurationValues) {
+  test(t => {
+    assert_throws(new TypeError, () => {
+      createDiv(t).animate(null, { duration: invalid });
+    });
+  }, 'Throws when setting invalid duration: '
+     + (typeof invalid === 'string' ? `"${invalid}"` : invalid));
+}
+
+test(t => {
+  const anim = createDiv(t).animate(null, { duration: 100000, fill: 'both' });
+  anim.finish();
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress when animation is finished');
+  anim.effect.updateTiming({ duration: anim.effect.getTiming().duration * 2 });
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5,
+                             'progress after doubling the duration');
+  anim.effect.updateTiming({ duration: 0 });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after setting duration to zero');
+  anim.effect.updateTiming({ duration: 'auto' });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after setting duration to \'auto\'');
+}, 'Allows setting the duration of an animation in progress');
+
+promise_test(t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  return anim.ready.then(() => {
+    const originalStartTime   = anim.startTime;
+    const originalCurrentTime = anim.currentTime;
+    assert_time_equals_literal(
+      anim.effect.getComputedTiming().duration,
+      100 * MS_PER_SEC,
+      'Initial duration should be as set on KeyframeEffect'
+    );
+
+    anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
+    assert_time_equals_literal(
+      anim.effect.getComputedTiming().duration,
+      200 * MS_PER_SEC,
+      'Effect duration should have been updated'
+    );
+    assert_times_equal(anim.startTime, originalStartTime,
+                       'startTime should be unaffected by changing effect ' +
+                       'duration');
+    assert_times_equal(anim.currentTime, originalCurrentTime,
+                       'currentTime should be unaffected by changing effect ' +
+                       'duration');
+  });
+}, 'Allows setting the duration of an animation in progress such that the' +
+   ' the start and current time do not change');
+
+
+// ------------------------------
+//  direction
+// ------------------------------
+
+test(t => {
+  const anim = createDiv(t).animate(null, 2000);
+
+  const directions = ['normal', 'reverse', 'alternate', 'alternate-reverse'];
+  for (const direction of directions) {
+    anim.effect.updateTiming({ direction: direction });
+    assert_equals(anim.effect.getTiming().direction, direction,
+                  `set direction to ${direction}`);
+  }
+}, 'Allows setting the direction to each of the possible keywords');
+
+test(t => {
+  const anim = createDiv(t).animate(null, {
+    duration: 10000,
+    direction: 'normal',
+  });
+  anim.currentTime = 7000;
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
+                             'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'reverse' });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
+                             'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'reverse\'');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: 10000, direction: 'normal' });
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'reverse' });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'reverse\' while at start of active interval');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { fill: 'backwards',
+                                      duration: 10000,
+                                      delay: 10000,
+                                      direction: 'normal' });
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'reverse' });
+
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'reverse\' while filling backwards');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterations: 2,
+                                      duration: 10000,
+                                      direction: 'normal' });
+  anim.currentTime = 17000;
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
+                             'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'alternate' });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
+                             'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'normal\' to'
+   + ' \'alternate\'');
+
+test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { iterations: 2,
+                                      duration: 10000,
+                                      direction: 'alternate' });
+  anim.currentTime = 17000;
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3,
+                             'progress before updating direction');
+
+  anim.effect.updateTiming({ direction: 'alternate-reverse' });
+
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.7,
+                             'progress after updating direction');
+}, 'Allows setting the direction of an animation in progress from \'alternate\''
+   + ' to \'alternate-reverse\'');
+
+
+// ------------------------------
+//  easing
+// ------------------------------
+
+function assert_progress(animation, currentTime, easingFunction) {
+  animation.currentTime = currentTime;
+  const portion = currentTime / animation.effect.getTiming().duration;
+  assert_approx_equals(animation.effect.getComputedTiming().progress,
+                       easingFunction(portion),
+                       0.01,
+                       'The progress of the animation should be approximately'
+                       + ` ${easingFunction(portion)} at ${currentTime}ms`);
+}
+
+for (const options of gEasingTests) {
+  test(t => {
+    const target = createDiv(t);
+    const anim = target.animate(null,
+                                { duration: 1000 * MS_PER_SEC,
+                                  fill: 'forwards' });
+    anim.effect.updateTiming({ easing: options.easing });
+    assert_equals(anim.effect.getTiming().easing,
+                  options.serialization || options.easing);
+
+    const easing = options.easingFunction;
+    assert_progress(anim, 0, easing);
+    assert_progress(anim, 250 * MS_PER_SEC, easing);
+    assert_progress(anim, 500 * MS_PER_SEC, easing);
+    assert_progress(anim, 750 * MS_PER_SEC, easing);
+    assert_progress(anim, 1000 * MS_PER_SEC, easing);
+  }, `Allows setting the easing to a ${options.desc}`);
+}
+
+for (const easing of gRoundtripEasings) {
+  test(t => {
+    const anim = createDiv(t).animate(null);
+    anim.effect.updateTiming({ easing: easing });
+    assert_equals(anim.effect.getTiming().easing, easing);
+  }, `Updates the specified value when setting the easing to '${easing}'`);
+}
+
+test(t => {
+  const delay = 1000 * MS_PER_SEC;
+
+  const target = createDiv(t);
+  const anim = target.animate(null,
+                              { duration: 1000 * MS_PER_SEC,
+                                fill: 'both',
+                                delay: delay,
+                                easing: 'steps(2, start)' });
+
+  anim.effect.updateTiming({ easing: 'steps(2, end)' });
+  assert_equals(anim.effect.getComputedTiming().progress, 0,
+                'easing replace to steps(2, end) at before phase');
+
+  anim.currentTime = delay + 750 * MS_PER_SEC;
+  assert_equals(anim.effect.getComputedTiming().progress, 0.5,
+                'change currentTime to active phase');
+
+  anim.effect.updateTiming({ easing: 'steps(2, start)' });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'easing replace to steps(2, start) at active phase');
+
+  anim.currentTime = delay + 1500 * MS_PER_SEC;
+  anim.effect.updateTiming({ easing: 'steps(2, end)' });
+  assert_equals(anim.effect.getComputedTiming().progress, 1,
+                'easing replace to steps(2, end) again at after phase');
+}, 'Allows setting the easing of an animation in progress');
+
+</script>
+</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/delay.html b/web-animations/interfaces/AnimationEffectTiming/delay.html
deleted file mode 100644
index 8014945..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/delay.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.delay</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-delay">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.delay, 0);
-}, 'Has the default value 0');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-  anim.effect.timing.delay = 100;
-  assert_equals(anim.effect.timing.delay, 100, 'set delay 100');
-  assert_equals(anim.effect.getComputedTiming().delay, 100,
-                  'getComputedTiming() after set delay 100');
-}, 'Can be set to a positive number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-  anim.effect.timing.delay = -100;
-  assert_equals(anim.effect.timing.delay, -100, 'set delay -100');
-  assert_equals(anim.effect.getComputedTiming().delay, -100,
-                'getComputedTiming() after set delay -100');
-}, 'Can be set to a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-  anim.effect.timing.delay = 100;
-  assert_equals(anim.effect.getComputedTiming().progress, null);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, null);
-}, 'Can set a positive delay on an animation without a backwards fill to'
-   + ' make it no longer active');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { fill: 'both',
-                             duration: 100 });
-  anim.effect.timing.delay = -50;
-  assert_equals(anim.effect.getComputedTiming().progress, 0.5);
-}, 'Can set a negative delay to seek into the active interval');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { fill: 'both',
-                             duration: 100 });
-  anim.effect.timing.delay = -100;
-  assert_equals(anim.effect.getComputedTiming().progress, 1);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 0);
-}, 'Can set a large negative delay to finishing an animation');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null);
-  for (let invalid of [NaN, Infinity]) {
-    assert_throws({ name: 'TypeError' }, () => {
-      anim.effect.timing.delay = invalid;
-    }, `setting ${invalid}`);
-    assert_throws({ name: 'TypeError' }, () => {
-      div.animate({}, { delay: invalid });
-    }, `animate() with ${invalid}`);
-  }
-}, 'Throws when setting invalid values');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/direction.html b/web-animations/interfaces/AnimationEffectTiming/direction.html
deleted file mode 100644
index 2fb3002..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/direction.html
+++ /dev/null
@@ -1,108 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.direction</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-direction">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.direction, 'normal');
-}, 'Has the default value \'normal\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-
-  const directions = ['normal', 'reverse', 'alternate', 'alternate-reverse'];
-  for (const direction of directions) {
-    anim.effect.timing.direction = direction;
-    assert_equals(anim.effect.timing.direction, direction,
-                  `set direction to ${direction}`);
-  }
-}, 'Can be set to each of the possible keywords');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null, { duration: 10000, direction: 'normal' });
-  anim.currentTime = 7000;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.7,
-                     'progress before updating direction');
-
-  anim.effect.timing.direction = 'reverse';
-
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.3,
-                     'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'reverse\' while in progress');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: 10000,
-                             direction: 'normal' });
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress before updating direction');
-
-  anim.effect.timing.direction = 'reverse';
-
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'reverse\' while at start of active'
-   + ' interval');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { fill: 'backwards',
-                             duration: 10000,
-                             delay: 10000,
-                             direction: 'normal' });
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress before updating direction');
-
-  anim.effect.timing.direction = 'reverse';
-
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'reverse\' while filling backwards');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterations: 2,
-                             duration: 10000,
-                             direction: 'normal' });
-  anim.currentTime = 17000;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.7,
-                     'progress before updating direction');
-
-  anim.effect.timing.direction = 'alternate';
-
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.3,
-                     'progress after updating direction');
-}, 'Can be changed from \'normal\' to \'alternate\' while in progress');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterations: 2,
-                             duration: 10000,
-                             direction: 'alternate' });
-  anim.currentTime = 17000;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.3,
-                     'progress before updating direction');
-
-  anim.effect.timing.direction = 'alternate-reverse';
-
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.7,
-                     'progress after updating direction');
-}, 'Can be changed from \'alternate\' to \'alternate-reverse\' while in'
-   + ' progress');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/duration.html b/web-animations/interfaces/AnimationEffectTiming/duration.html
deleted file mode 100644
index 98ddd68..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/duration.html
+++ /dev/null
@@ -1,190 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.duration</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-duration">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.duration, 'auto');
-}, 'Has the default value \'auto\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.duration = 123.45;
-  assert_times_equal(anim.effect.timing.duration, 123.45,
-                     'set duration 123.45');
-  assert_times_equal(anim.effect.getComputedTiming().duration, 123.45,
-                     'getComputedTiming() after set duration 123.45');
-}, 'Can be set to a double value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.duration = 'auto';
-  assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\'');
-  assert_equals(anim.effect.getComputedTiming().duration, 0,
-                'getComputedTiming() after set duration \'auto\'');
-}, 'Can be set to the string \'auto\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 'auto' });
-  assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\'');
-  assert_equals(anim.effect.getComputedTiming().duration, 0,
-                'getComputedTiming() after set duration \'auto\'');
-}, 'Can be set to \'auto\' using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.duration = Infinity;
-  assert_equals(anim.effect.timing.duration, Infinity, 'set duration Infinity');
-  assert_equals(anim.effect.getComputedTiming().duration, Infinity,
-                'getComputedTiming() after set duration Infinity');
-}, 'Can be set to Infinity');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, -1);
-  });
-}, 'animate() throws when passed a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, -Infinity);
-  });
-}, 'animate() throws when passed negative Infinity');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, NaN);
-  });
-}, 'animate() throws when passed a NaN value');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: -1 });
-  });
-}, 'animate() throws when passed a negative number using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: -Infinity });
-  });
-}, 'animate() throws when passed negative Infinity using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: NaN });
-  });
-}, 'animate() throws when passed a NaN value using a dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: 'abc' });
-  });
-}, 'animate() throws when passed a string other than \'auto\' using a'
-   + ' dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  assert_throws({ name: 'TypeError' }, () => {
-    div.animate({ opacity: [ 0, 1 ] }, { duration: '100' });
-  });
-}, 'animate() throws when passed a string containing a number using a'
-   + ' dictionary object');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = -1;
-  });
-}, 'Throws when setting a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = -Infinity;
-  });
-}, 'Throws when setting negative infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = NaN;
-  });
-}, 'Throws when setting a NaN value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = 'abc';
-  });
-}, 'Throws when setting a string other than \'auto\'');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.duration = '100';
-  });
-}, 'Throws when setting a string containing a number');
-
-promise_test(t => {
-  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  return anim.ready.then(() => {
-    const originalStartTime   = anim.startTime;
-    const originalCurrentTime = anim.currentTime;
-    assert_equals(anim.effect.getComputedTiming().duration, 100 * MS_PER_SEC,
-                  'Initial duration should be as set on KeyframeEffect');
-
-    anim.effect.timing.duration = 200 * MS_PER_SEC;
-    assert_equals(anim.effect.getComputedTiming().duration, 200 * MS_PER_SEC,
-                  'Effect duration should have been updated');
-    assert_times_equal(anim.startTime, originalStartTime,
-                       'startTime should be unaffected by changing effect ' +
-                       'duration');
-    assert_times_equal(anim.currentTime, originalCurrentTime,
-                       'currentTime should be unaffected by changing effect ' +
-                       'duration');
-  });
-}, 'Extending an effect\'s duration does not change the start or current time');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null, { duration: 100000, fill: 'both' });
-  anim.finish();
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress when animation is finished');
-  anim.effect.timing.duration *= 2;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5,
-                     'progress after doubling the duration');
-  anim.effect.timing.duration = 0;
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after setting duration to zero');
-  anim.effect.timing.duration = 'auto';
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress after setting duration to \'auto\'');
-}, 'Can be updated while the animation is in progress');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/easing.html b/web-animations/interfaces/AnimationEffectTiming/easing.html
deleted file mode 100644
index 28f8a53..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/easing.html
+++ /dev/null
@@ -1,96 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.easing</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-easing">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<script src="../../resources/easing-tests.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.easing, 'linear');
-}, 'Has the default value \'linear\'');
-
-function assert_progress(animation, currentTime, easingFunction) {
-  animation.currentTime = currentTime;
-  const portion = currentTime / animation.effect.timing.duration;
-  assert_approx_equals(animation.effect.getComputedTiming().progress,
-                       easingFunction(portion),
-                       0.01,
-                       'The progress of the animation should be approximately'
-                       + ` ${easingFunction(portion)} at ${currentTime}ms`);
-}
-
-for (const options of gEasingTests) {
-  test(t => {
-    const target = createDiv(t);
-    const anim = target.animate([ { opacity: 0 }, { opacity: 1 } ],
-                                { duration: 1000 * MS_PER_SEC,
-                                  fill: 'forwards' });
-    anim.effect.timing.easing = options.easing;
-    assert_equals(anim.effect.timing.easing,
-                  options.serialization || options.easing);
-
-    const easing = options.easingFunction;
-    assert_progress(anim, 0, easing);
-    assert_progress(anim, 250 * MS_PER_SEC, easing);
-    assert_progress(anim, 500 * MS_PER_SEC, easing);
-    assert_progress(anim, 750 * MS_PER_SEC, easing);
-    assert_progress(anim, 1000 * MS_PER_SEC, easing);
-  }, options.desc);
-}
-
-for (const invalidEasing of gInvalidEasings) {
-  test(t => {
-    const div = createDiv(t);
-    const anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
-    assert_throws({ name: 'TypeError' },
-                  () => {
-                    anim.effect.timing.easing = invalidEasing;
-                  });
-  }, `Throws on invalid easing: '${invalidEasing}'`);
-}
-
-for (const easing of gRoundtripEasings) {
-  test(t => {
-    const anim = createDiv(t).animate(null);
-    anim.effect.timing.easing = easing;
-    assert_equals(anim.effect.timing.easing, easing);
-  }, `Canonical easing '${easing}' is returned as set`);
-}
-
-test(t => {
-  const delay = 1000 * MS_PER_SEC;
-
-  const target = createDiv(t);
-  const anim = target.animate([ { opacity: 0 }, { opacity: 1 } ],
-                              { duration: 1000 * MS_PER_SEC,
-                                fill: 'both',
-                                delay: delay,
-                                easing: 'steps(2, start)' });
-
-  anim.effect.timing.easing = 'steps(2, end)';
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'easing replace to steps(2, end) at before phase');
-
-  anim.currentTime = delay + 750 * MS_PER_SEC;
-  assert_equals(anim.effect.getComputedTiming().progress, 0.5,
-                'change currentTime to active phase');
-
-  anim.effect.timing.easing = 'steps(2, start)';
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'easing replace to steps(2, start) at active phase');
-
-  anim.currentTime = delay + 1500 * MS_PER_SEC;
-  anim.effect.timing.easing = 'steps(2, end)';
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'easing replace to steps(2, end) again at after phase');
-}, 'Allows the easing to be changed while the animation is in progress');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/endDelay.html b/web-animations/interfaces/AnimationEffectTiming/endDelay.html
deleted file mode 100644
index 7b4ceca..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/endDelay.html
+++ /dev/null
@@ -1,89 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.endDelay</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-enddelay">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.endDelay, 0);
-}, 'Has the default value 0');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.endDelay = 123.45;
-  assert_times_equal(anim.effect.timing.endDelay, 123.45,
-                     'set endDelay 123.45');
-  assert_times_equal(anim.effect.getComputedTiming().endDelay, 123.45,
-                     'getComputedTiming() after set endDelay 123.45');
-}, 'Can be set to a positive number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.endDelay = -1000;
-  assert_equals(anim.effect.timing.endDelay, -1000, 'set endDelay -1000');
-  assert_equals(anim.effect.getComputedTiming().endDelay, -1000,
-                'getComputedTiming() after set endDelay -1000');
-}, 'Can be set to a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.endDelay = Infinity;
-  }, 'we can not assign Infinity to timing.endDelay');
-}, 'Throws when setting infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.endDelay = -Infinity;
-  }, 'we can not assign negative Infinity to timing.endDelay');
-}, 'Throws when setting negative infinity');
-
-async_test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: 100000, endDelay: 50000 });
-  anim.onfinish = t.step_func(event => {
-    assert_unreached('finish event should not be fired');
-  });
-
-  anim.ready.then(() => {
-    anim.currentTime = 100000;
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(() => {
-    t.done();
-  }));
-}, 'finish event is not fired at the end of the active interval when the'
-   + ' endDelay has not expired');
-
-async_test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { duration: 100000, endDelay: 30000 });
-  anim.ready.then(() => {
-    anim.currentTime = 110000; // during endDelay
-    anim.onfinish = t.step_func(event => {
-      assert_unreached('onfinish event should not be fired during endDelay');
-    });
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(() => {
-    anim.onfinish = t.step_func(event => {
-      t.done();
-    });
-    anim.currentTime = 130000; // after endTime
-  }));
-}, 'finish event is fired after the endDelay has expired');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/fill.html b/web-animations/interfaces/AnimationEffectTiming/fill.html
deleted file mode 100644
index 5686b58..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/fill.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.fill</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-fill">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.fill, 'auto');
-}, 'Has the default value \'auto\'');
-
-for (const fill of ['none', 'forwards', 'backwards', 'both']) {
-  test(t => {
-    const div = createDiv(t);
-    const anim = div.animate({ opacity: [ 0, 1 ] }, 100);
-    anim.effect.timing.fill = fill;
-    assert_equals(anim.effect.timing.fill, fill, 'set fill ' + fill);
-    assert_equals(anim.effect.getComputedTiming().fill, fill,
-                  'getComputedTiming() after set fill ' + fill);
-  }, `Can set fill to ${fill}`);
-}
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html b/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html
deleted file mode 100644
index ee0a5e2..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html
+++ /dev/null
@@ -1,201 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.getComputedTiming</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffectreadonly-getcomputedtiming">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const effect = new KeyframeEffect(null, null);
-
-  const ct = effect.getComputedTiming();
-  assert_equals(ct.delay, 0, 'computed delay');
-  assert_equals(ct.fill, 'none', 'computed fill');
-  assert_equals(ct.iterations, 1.0, 'computed iterations');
-  assert_equals(ct.duration, 0, 'computed duration');
-  assert_equals(ct.direction, 'normal', 'computed direction');
-}, 'values of getComputedTiming() when a KeyframeEffect is ' +
-   'constructed without any KeyframeEffectOptions object');
-
-const gGetComputedTimingTests = [
-  { desc:     'an empty KeyframeEffectOptions object',
-    input:    { },
-    expected: { } },
-  { desc:     'a normal KeyframeEffectOptions object',
-    input:    { delay: 1000,
-                fill: 'auto',
-                iterations: 5.5,
-                duration: 'auto',
-                direction: 'alternate' },
-    expected: { delay: 1000,
-                fill: 'none',
-                iterations: 5.5,
-                duration: 0,
-                direction: 'alternate' } },
-  { desc:     'a double value',
-    input:    3000,
-    timing:   { duration: 3000 },
-    expected: { delay: 0,
-                fill: 'none',
-                iterations: 1,
-                duration: 3000,
-                direction: 'normal' } },
-  { desc:     '+Infinity',
-    input:    Infinity,
-    expected: { duration: Infinity } },
-  { desc:     'an Infinity duration',
-    input:    { duration: Infinity },
-    expected: { duration: Infinity } },
-  { desc:     'an auto duration',
-    input:    { duration: 'auto' },
-    expected: { duration: 0 } },
-  { desc:     'an Infinity iterations',
-    input:    { iterations: Infinity },
-    expected: { iterations: Infinity } },
-  { desc:     'an auto fill',
-    input:    { fill: 'auto' },
-    expected: { fill: 'none' } },
-  { desc:     'a forwards fill',
-    input:    { fill: 'forwards' },
-    expected: { fill: 'forwards' } }
-];
-
-for (const stest of gGetComputedTimingTests) {
-  test(t => {
-    const effect = new KeyframeEffect(null, null, stest.input);
-
-    // Helper function to provide default expected values when the test does
-    // not supply them.
-    const expected = (field, defaultValue) => {
-      return field in stest.expected ? stest.expected[field] : defaultValue;
-    };
-
-    const ct = effect.getComputedTiming();
-    assert_equals(ct.delay, expected('delay', 0),
-                  'computed delay');
-    assert_equals(ct.fill, expected('fill', 'none'),
-                  'computed fill');
-    assert_equals(ct.iterations, expected('iterations', 1),
-                  'computed iterations');
-    assert_equals(ct.duration, expected('duration', 0),
-                  'computed duration');
-    assert_equals(ct.direction, expected('direction', 'normal'),
-                  'computed direction');
-
-  }, 'values of getComputedTiming() when a KeyframeEffect is'
-     + ` constructed by ${stest.desc}`);
-}
-
-const gActiveDurationTests = [
-  { desc:     'an empty KeyframeEffectOptions object',
-    input:    { },
-    expected: 0 },
-  { desc:     'a non-zero duration and default iteration count',
-    input:    { duration: 1000 },
-    expected: 1000 },
-  { desc:     'a non-zero duration and integral iteration count',
-    input:    { duration: 1000, iterations: 7 },
-    expected: 7000 },
-  { desc:     'a non-zero duration and fractional iteration count',
-    input:    { duration: 1000, iterations: 2.5 },
-    expected: 2500 },
-  { desc:     'an non-zero duration and infinite iteration count',
-    input:    { duration: 1000, iterations: Infinity },
-    expected: Infinity },
-  { desc:     'an non-zero duration and zero iteration count',
-    input:    { duration: 1000, iterations: 0 },
-    expected: 0 },
-  { desc:     'a zero duration and default iteration count',
-    input:    { duration: 0 },
-    expected: 0 },
-  { desc:     'a zero duration and fractional iteration count',
-    input:    { duration: 0, iterations: 2.5 },
-    expected: 0 },
-  { desc:     'a zero duration and infinite iteration count',
-    input:    { duration: 0, iterations: Infinity },
-    expected: 0 },
-  { desc:     'a zero duration and zero iteration count',
-    input:    { duration: 0, iterations: 0 },
-    expected: 0 },
-  { desc:     'an infinite duration and default iteration count',
-    input:    { duration: Infinity },
-    expected: Infinity },
-  { desc:     'an infinite duration and zero iteration count',
-    input:    { duration: Infinity, iterations: 0 },
-    expected: 0 },
-  { desc:     'an infinite duration and fractional iteration count',
-    input:    { duration: Infinity, iterations: 2.5 },
-    expected: Infinity },
-  { desc:     'an infinite duration and infinite iteration count',
-    input:    { duration: Infinity, iterations: Infinity },
-    expected: Infinity },
-];
-
-for (const stest of gActiveDurationTests) {
-  test(t => {
-    const effect = new KeyframeEffect(null, null, stest.input);
-
-    assert_equals(effect.getComputedTiming().activeDuration,
-                  stest.expected);
-
-  }, `getComputedTiming().activeDuration for ${stest.desc}`);
-}
-
-const gEndTimeTests = [
-  { desc:     'an empty KeyframeEffectOptions object',
-    input:    { },
-    expected: 0 },
-  { desc:     'a non-zero duration and default iteration count',
-    input:    { duration: 1000 },
-    expected: 1000 },
-  { desc:     'a non-zero duration and non-default iteration count',
-    input:    { duration: 1000, iterations: 2.5 },
-    expected: 2500 },
-  { desc:     'a non-zero duration and non-zero delay',
-    input:    { duration: 1000, delay: 1500 },
-    expected: 2500 },
-  { desc:     'a non-zero duration, non-zero delay and non-default iteration',
-    input:    { duration: 1000, delay: 1500, iterations: 2 },
-    expected: 3500 },
-  { desc:     'an infinite iteration count',
-    input:    { duration: 1000, iterations: Infinity },
-    expected: Infinity },
-  { desc:     'an infinite duration',
-    input:    { duration: Infinity, iterations: 10 },
-    expected: Infinity },
-  { desc:     'an infinite duration and delay',
-    input:    { duration: Infinity, iterations: 10, delay: 1000 },
-    expected: Infinity },
-  { desc:     'an infinite duration and negative delay',
-    input:    { duration: Infinity, iterations: 10, delay: -1000 },
-    expected: Infinity },
-  { desc:     'an non-zero duration and negative delay',
-    input:    { duration: 1000, iterations: 2, delay: -1000 },
-    expected: 1000 },
-  { desc:     'an non-zero duration and negative delay greater than active ' +
-              'duration',
-    input:    { duration: 1000, iterations: 2, delay: -3000 },
-    expected: 0 },
-  { desc:     'a zero duration and negative delay',
-    input:    { duration: 0, iterations: 2, delay: -1000 },
-    expected: 0 }
-];
-
-for (const stest of gEndTimeTests) {
-  test(t => {
-    const effect = new KeyframeEffect(null, null, stest.input);
-
-    assert_equals(effect.getComputedTiming().endTime,
-                  stest.expected);
-
-  }, `getComputedTiming().endTime for ${stest.desc}`);
-}
-
-done();
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/idlharness.html b/web-animations/interfaces/AnimationEffectTiming/idlharness.html
deleted file mode 100644
index 866d5bf..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/idlharness.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming and AnimationEffectTimingReadOnly IDL</title>
-<link rel="help"
-      href="https://w3c.github.io/web-animations/#animationeffecttiming">
-<link rel="help"
-      href="https://w3c.github.io/web-animations/#animationeffecttimingreadonly">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/resources/idlharness.js"></script>
-<div id="log"></div>
-<script type="text/plain" id="AnimationEffectTimingReadOnly-IDL">
-enum FillMode { "none", "forwards", "backwards", "both", "auto" };
-enum PlaybackDirection {
-  "normal",
-  "reverse",
-  "alternate",
-  "alternate-reverse"
-};
-
-dictionary AnimationEffectTimingProperties {
-  double                             delay = 0.0;
-  double                             endDelay = 0.0;
-  FillMode                           fill = "auto";
-  double                             iterationStart = 0.0;
-  unrestricted double                iterations = 1.0;
-  (unrestricted double or DOMString) duration = "auto";
-  PlaybackDirection                  direction = "normal";
-  DOMString                          easing = "linear";
-};
-
-[Exposed=Window]
-interface AnimationEffectTimingReadOnly {
-  readonly attribute double                             delay;
-  readonly attribute double                             endDelay;
-  readonly attribute FillMode                           fill;
-  readonly attribute double                             iterationStart;
-  readonly attribute unrestricted double                iterations;
-  readonly attribute (unrestricted double or DOMString) duration;
-  readonly attribute PlaybackDirection                  direction;
-  readonly attribute DOMString                          easing;
-};
-</script>
-<script type="text/plain" id="AnimationEffectTiming-IDL">
-[Exposed=Window]
-interface AnimationEffectTiming : AnimationEffectTimingReadOnly {
-  inherit attribute double                             delay;
-  inherit attribute double                             endDelay;
-  inherit attribute FillMode                           fill;
-  inherit attribute double                             iterationStart;
-  inherit attribute unrestricted double                iterations;
-  inherit attribute (unrestricted double or DOMString) duration;
-  inherit attribute PlaybackDirection                  direction;
-  inherit attribute DOMString                          easing;
-};
-</script>
-<script>
-'use strict';
-
-const idlArray = new IdlArray();
-
-idlArray.add_idls(
-  document.getElementById('AnimationEffectTimingReadOnly-IDL').textContent
-);
-idlArray.add_idls(
-  document.getElementById('AnimationEffectTiming-IDL').textContent
-);
-
-idlArray.add_objects({
-  AnimationEffectTiming: [
-    '(new KeyframeEffect(null, null)).timing'
-  ],
-  AnimationEffectTimingReadOnly: [
-    '(new KeyframeEffectReadOnly(null, null)).timing'
-  ],
-});
-idlArray.test();
-
-</script>
diff --git a/web-animations/interfaces/AnimationEffectTiming/iterationStart.html b/web-animations/interfaces/AnimationEffectTiming/iterationStart.html
deleted file mode 100644
index 8ba01a9..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/iterationStart.html
+++ /dev/null
@@ -1,72 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.iterationStart</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-iterationstart">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.iterationStart, 0);
-}, 'Has the default value 0');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterationStart: 0.2,
-                             iterations: 1,
-                             fill: 'both',
-                             duration: 100,
-                             delay: 1 });
-  anim.effect.timing.iterationStart = 2.5;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
-}, 'Changing the value updates computed timing when backwards-filling');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterationStart: 0.2,
-                             iterations: 1,
-                             fill: 'both',
-                             duration: 100,
-                             delay: 0 });
-  anim.effect.timing.iterationStart = 2.5;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
-}, 'Changing the value updates computed timing during the active phase');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] },
-                           { iterationStart: 0.2,
-                             iterations: 1,
-                             fill: 'both',
-                             duration: 100,
-                             delay: 0 });
-  anim.finish();
-  anim.effect.timing.iterationStart = 2.5;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 3);
-}, 'Changing the value updates computed timing when forwards-filling');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null);
-  for (let invalid of [-1, NaN, Infinity]) {
-    assert_throws({ name: 'TypeError' }, () => {
-      anim.effect.timing.iterationStart = invalid;
-    }, `setting ${invalid}`);
-    assert_throws({ name: 'TypeError' }, () => {
-      div.animate({}, { iterationStart: invalid });
-    }, `animate() with ${invalid}`);
-  }
-}, 'Throws when setting invalid values');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationEffectTiming/iterations.html b/web-animations/interfaces/AnimationEffectTiming/iterations.html
deleted file mode 100644
index b8192e0..0000000
--- a/web-animations/interfaces/AnimationEffectTiming/iterations.html
+++ /dev/null
@@ -1,94 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>AnimationEffectTiming.iterations</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-iterations">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const anim = createDiv(t).animate(null);
-  assert_equals(anim.effect.timing.iterations, 1);
-}, 'Has the default value 1');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.iterations = 2;
-  assert_equals(anim.effect.timing.iterations, 2, 'set duration 2');
-  assert_equals(anim.effect.getComputedTiming().iterations, 2,
-                       'getComputedTiming() after set iterations 2');
-}, 'Can be set to a double value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  anim.effect.timing.iterations = Infinity;
-  assert_equals(anim.effect.timing.iterations, Infinity, 'set duration Infinity');
-  assert_equals(anim.effect.getComputedTiming().iterations, Infinity,
-                       'getComputedTiming() after set iterations Infinity');
-}, 'Can be set to infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.iterations = -1;
-  });
-}, 'Throws when setting a negative number');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.iterations = -Infinity;
-  });
-}, 'Throws when setting negative infinity');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
-  assert_throws({ name: 'TypeError' }, () => {
-    anim.effect.timing.iterations = NaN;
-  });
-}, 'Throws when setting a NaN value');
-
-test(t => {
-  const div = createDiv(t);
-  const anim = div.animate(null, { duration: 100000, fill: 'both' });
-
-  anim.finish();
-
-  assert_equals(anim.effect.getComputedTiming().progress, 1,
-                'progress when animation is finished');
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
-                'current iteration when animation is finished');
-
-  anim.effect.timing.iterations = 2;
-
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0,
-                     'progress after adding an iteration');
-  assert_times_equal(anim.effect.getComputedTiming().currentIteration, 1,
-                     'current iteration after adding an iteration');
-
-  anim.effect.timing.iterations = 0;
-
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress after setting iterations to zero');
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
-                'current iteration after setting iterations to zero');
-
-  anim.effect.timing.iterations = Infinity;
-
-  assert_equals(anim.effect.getComputedTiming().progress, 0,
-                'progress after setting iterations to Infinity');
-  assert_equals(anim.effect.getComputedTiming().currentIteration, 1,
-                'current iteration after setting iterations to Infinity');
-}, 'Can be updated while the animation is in progress');
-
-</script>
-</body>
diff --git a/web-animations/interfaces/AnimationPlaybackEvent/constructor.html b/web-animations/interfaces/AnimationPlaybackEvent/constructor.html
index 41cf603..1c40a3f 100644
--- a/web-animations/interfaces/AnimationPlaybackEvent/constructor.html
+++ b/web-animations/interfaces/AnimationPlaybackEvent/constructor.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>AnimationPlaybackEvent constructor</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#dom-animationplaybackevent-animationplaybackevent">
+      href="https://drafts.csswg.org/web-animations/#dom-animationplaybackevent-animationplaybackevent">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
diff --git a/web-animations/interfaces/AnimationPlaybackEvent/idlharness.html b/web-animations/interfaces/AnimationPlaybackEvent/idlharness.html
index f4e3e8c..9570c0f 100644
--- a/web-animations/interfaces/AnimationPlaybackEvent/idlharness.html
+++ b/web-animations/interfaces/AnimationPlaybackEvent/idlharness.html
@@ -2,45 +2,38 @@
 <meta charset=utf-8>
 <title>AnimationPlaybackEvent IDL</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#animationplaybackevent">
+      href="https://drafts.csswg.org/web-animations/#animationplaybackevent">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
 <div id="log"></div>
-<script type="text/plain" id="AnimationPlaybackEvent-IDL">
-dictionary EventInit {
-  boolean bubbles = false;
-  boolean cancelable = false;
-  boolean composed = false;
-};
-dictionary AnimationPlaybackEventInit : EventInit {
-  double? currentTime = null;
-  double? timelineTime = null;
-};
-
-[Exposed=Window,
- Constructor (DOMString type,
-              optional AnimationPlaybackEventInit eventInitDict
-)]
-interface AnimationPlaybackEvent : Event {
-  readonly attribute double? currentTime;
-  readonly attribute double? timelineTime;
-};
-</script>
 <script>
 'use strict';
 
-const idlArray = new IdlArray();
+promise_test(async () => {
+  const text = await fetch('/interfaces/web-animations.idl').then(response =>
+    response.text(),
+  );
+  const idlArray = new IdlArray();
+  idlArray.add_untested_idls(`dictionary EventInit {
+    boolean bubbles = false;
+    boolean cancelable = false;
+    boolean composed = false;
+  };`);
+  idlArray.add_idls(text, {
+    only: [
+      'AnimationPlaybackEventInit',
+      'AnimationPlaybackEvent',
+    ]
+  });
 
-idlArray.add_untested_idls('interface Event {};');
-idlArray.add_idls(
-  document.getElementById('AnimationPlaybackEvent-IDL').textContent
-);
-idlArray.add_objects({
-  AnimationPlaybackEvent: [ 'new AnimationPlaybackEvent(\'cancel\')' ],
-});
+  idlArray.add_untested_idls('interface Event {};');
+  idlArray.add_objects({
+    AnimationPlaybackEvent: ['new AnimationPlaybackEvent(\'cancel\')'],
+  });
 
-idlArray.test();
-
+  idlArray.test();
+  done();
+}, 'AnimationPlaybackEvent interface.');
 </script>
diff --git a/web-animations/interfaces/Document/getAnimations.html b/web-animations/interfaces/Document/getAnimations.html
index 9533bee..6b8534b 100644
--- a/web-animations/interfaces/Document/getAnimations.html
+++ b/web-animations/interfaces/Document/getAnimations.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Document.getAnimations</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-document-getanimations">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-document-getanimations">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -42,7 +42,20 @@
 }, 'Test the order of document.getAnimations with script generated animations')
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC);
+  // This element exists but is not a descendent of any document, so isn't
+  // picked up by getAnimations.
+  const div = document.createElement('div');
+  const anim = div.animate(gKeyFrames, 100 * MS_PER_SEC);
+  assert_equals(document.getAnimations().length, 0);
+
+  // Now connect the div; it should appear in the list of animations.
+  document.body.appendChild(div);
+  t.add_cleanup(() => { div.remove(); });
+  assert_equals(document.getAnimations().length, 1);
+}, 'Test document.getAnimations for a disconnected node');
+
+test(t => {
+  const effect = new KeyframeEffect(null, gKeyFrames, 100 * MS_PER_SEC);
   const anim = new Animation(effect, document.timeline);
   anim.play();
 
@@ -51,5 +64,26 @@
                 'elements in this document');
 }, 'Test document.getAnimations with null target');
 
+async_test(t => {
+  const iframe = document.createElement('iframe');
+
+  iframe.addEventListener("load", t.step_func_done(function() {
+    const div = createDiv(t, iframe.contentDocument)
+    const effect = new KeyframeEffect(div, null, 100 * MS_PER_SEC);
+    const anim = new Animation(effect, document.timeline);
+    anim.play();
+
+    // The animation's timeline is from the main document, but the effect's
+    // target element is part of the iframe document and that is what matters
+    // for getAnimations.
+    assert_equals(document.getAnimations().length, 0);
+    assert_equals(iframe.contentDocument.getAnimations().length, 1);
+    anim.finish();
+  }));
+
+  document.body.appendChild(iframe);
+  t.add_cleanup(function() { document.body.removeChild(iframe); });
+}, 'Test document.getAnimations for elements inside same-origin iframes');
+
 </script>
 </body>
diff --git a/web-animations/interfaces/Document/timeline.html b/web-animations/interfaces/Document/timeline.html
index a0d7c9f..b8b4d74 100644
--- a/web-animations/interfaces/Document/timeline.html
+++ b/web-animations/interfaces/Document/timeline.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Document.timeline</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-document-timeline">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-document-timeline">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/DocumentTimeline/constructor.html b/web-animations/interfaces/DocumentTimeline/constructor.html
index 47d353c..e4714be 100644
--- a/web-animations/interfaces/DocumentTimeline/constructor.html
+++ b/web-animations/interfaces/DocumentTimeline/constructor.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>DocumentTimeline constructor tests</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#the-documenttimeline-interface">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#the-documenttimeline-interface">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/DocumentTimeline/idlharness.html b/web-animations/interfaces/DocumentTimeline/idlharness.html
index 639aa2e..6b22c91 100644
--- a/web-animations/interfaces/DocumentTimeline/idlharness.html
+++ b/web-animations/interfaces/DocumentTimeline/idlharness.html
@@ -1,36 +1,30 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>DocumentTimeline IDL</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#documenttimeline">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#documenttimeline">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
 <div id="log"></div>
-<script type="text/plain" id="AnimationTimeline-IDL">
-interface AnimationTimeline {
-  readonly attribute double? currentTime;
-};
-</script>
-<script type="text/plain" id="DocumentTimeline-IDL">
-dictionary DocumentTimelineOptions {
-  DOMHighResTimeStamp originTime = 0;
-};
-[Constructor (optional DocumentTimelineOptions options)]
-interface DocumentTimeline : AnimationTimeline {
-};
-</script>
 <script>
 'use strict';
 
-const idlArray = new IdlArray();
+promise_test(async () => {
+  const text = await fetch('/interfaces/web-animations.idl').then(response =>
+    response.text(),
+  );
+  const idlArray = new IdlArray();
+  idlArray.add_idls(text, {
+    only: [
+      'AnimationTimeline',
+      'DocumentTimelineOptions',
+      'DocumentTimeline',
+    ]
+  });
+  idlArray.add_objects({ DocumentTimeline: ['document.timeline'] });
 
-idlArray.add_idls(
-  document.getElementById('AnimationTimeline-IDL').textContent);
-idlArray.add_idls(
-  document.getElementById('DocumentTimeline-IDL').textContent);
-idlArray.add_objects( { DocumentTimeline: ['document.timeline'] } );
-
-idlArray.test();
-
+  idlArray.test();
+  done();
+}, 'DocumentTimeline interface.');
 </script>
diff --git a/web-animations/interfaces/KeyframeEffect/composite.html b/web-animations/interfaces/KeyframeEffect/composite.html
index 6447dd2..82ac633 100644
--- a/web-animations/interfaces/KeyframeEffect/composite.html
+++ b/web-animations/interfaces/KeyframeEffect/composite.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>KeyframeEffect.composite</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#dom-keyframeeffect-composite">
+      href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-composite">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -29,8 +29,8 @@
 
   anim.effect.composite = 'add';
   const keyframes = anim.effect.getKeyframes();
-  assert_equals(keyframes[0].composite, undefined,
-                'unspecified keyframe composite value should be absent even ' +
+  assert_equals(keyframes[0].composite, null,
+                'unspecified keyframe composite value should be null even ' +
                 'if effect composite is set');
 }, 'Unspecified keyframe composite value when setting effect composite');
 
diff --git a/web-animations/interfaces/KeyframeEffect/constructor.html b/web-animations/interfaces/KeyframeEffect/constructor.html
index 41cc396..250a8c7 100644
--- a/web-animations/interfaces/KeyframeEffect/constructor.html
+++ b/web-animations/interfaces/KeyframeEffect/constructor.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>KeyframeEffect and KeyframeEffectReadOnly constructor</title>
+<title>KeyframeEffect constructor</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#dom-keyframeeffect-keyframeeffect">
+      href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-keyframeeffect">
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly">
+      href="https://drafts.csswg.org/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -21,49 +21,48 @@
 
 test(t => {
   for (const frames of gEmptyKeyframeListTests) {
-    assert_equals(new KeyframeEffectReadOnly(target, frames)
-                        .getKeyframes().length,
+    assert_equals(new KeyframeEffect(target, frames).getKeyframes().length,
                   0, `number of frames for ${JSON.stringify(frames)}`);
   }
-}, 'A KeyframeEffectReadOnly can be constructed with no frames');
+}, 'A KeyframeEffect can be constructed with no frames');
 
 test(t => {
   for (const subtest of gEasingParsingTests) {
     const easing = subtest[0];
     const expected = subtest[1];
-    const effect = new KeyframeEffectReadOnly(target, {
+    const effect = new KeyframeEffect(target, {
       left: ['10px', '20px']
     }, { easing: easing });
-    assert_equals(effect.timing.easing, expected,
+    assert_equals(effect.getTiming().easing, expected,
                   `resulting easing for '${easing}'`);
   }
 }, 'easing values are parsed correctly when passed to the ' +
-   'KeyframeEffectReadOnly constructor in KeyframeEffectOptions');
+   'KeyframeEffect constructor in KeyframeEffectOptions');
 
 test(t => {
   for (const invalidEasing of gInvalidEasings) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, null, { easing: invalidEasing });
+      new KeyframeEffect(target, null, { easing: invalidEasing });
     }, `TypeError is thrown for easing '${invalidEasing}'`);
   }
 }, 'Invalid easing values are correctly rejected when passed to the ' +
-   'KeyframeEffectReadOnly constructor in KeyframeEffectOptions');
+   'KeyframeEffect constructor in KeyframeEffectOptions');
 
 test(t => {
   const getKeyframe =
     composite => ({ left: [ '10px', '20px' ], composite: composite });
   for (const composite of gGoodKeyframeCompositeValueTests) {
-    const effect = new KeyframeEffectReadOnly(target, getKeyframe(composite));
+    const effect = new KeyframeEffect(target, getKeyframe(composite));
     assert_equals(effect.getKeyframes()[0].composite, composite,
                   `resulting composite for '${composite}'`);
   }
-  for (const composite of gBadCompositeValueTests) {
+  for (const composite of gBadKeyframeCompositeValueTests) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, getKeyframe(composite));
+      new KeyframeEffect(target, getKeyframe(composite));
     });
   }
 }, 'composite values are parsed correctly when passed to the ' +
-   'KeyframeEffectReadOnly constructor in property-indexed keyframes');
+   'KeyframeEffect constructor in property-indexed keyframes');
 
 test(t => {
   const getKeyframes = composite =>
@@ -72,64 +71,62 @@
       { offset: 1, left: '20px' }
     ];
   for (const composite of gGoodKeyframeCompositeValueTests) {
-    const effect = new KeyframeEffectReadOnly(target, getKeyframes(composite));
+    const effect = new KeyframeEffect(target, getKeyframes(composite));
     assert_equals(effect.getKeyframes()[0].composite, composite,
                   `resulting composite for '${composite}'`);
   }
-  for (const composite of gBadCompositeValueTests) {
+  for (const composite of gBadKeyframeCompositeValueTests) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, getKeyframes(composite));
+      new KeyframeEffect(target, getKeyframes(composite));
     });
   }
 }, 'composite values are parsed correctly when passed to the ' +
-   'KeyframeEffectReadOnly constructor in regular keyframes');
+   'KeyframeEffect constructor in regular keyframes');
 
 test(t => {
   for (const composite of gGoodOptionsCompositeValueTests) {
-    const effect = new KeyframeEffectReadOnly(target, {
+    const effect = new KeyframeEffect(target, {
       left: ['10px', '20px']
-    }, { composite: composite });
-    assert_equals(effect.getKeyframes()[0].composite, undefined,
+    }, { composite });
+    assert_equals(effect.getKeyframes()[0].composite, null,
                   `resulting composite for '${composite}'`);
   }
-  for (const composite of gBadCompositeValueTests) {
+  for (const composite of gBadOptionsCompositeValueTests) {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, {
+      new KeyframeEffect(target, {
         left: ['10px', '20px']
       }, { composite: composite });
     });
   }
-}, 'composite value is absent if the composite operation specified on the ' +
+}, 'composite value is null if the composite operation specified on the ' +
    'keyframe effect is being used');
 
 for (const subtest of gKeyframesTests) {
   test(t => {
-    const effect = new KeyframeEffectReadOnly(target, subtest.input);
+    const effect = new KeyframeEffect(target, subtest.input);
     assert_frame_lists_equal(effect.getKeyframes(), subtest.output);
-  }, `A KeyframeEffectReadOnly can be constructed with ${subtest.desc}`);
+  }, `A KeyframeEffect can be constructed with ${subtest.desc}`);
 
   test(t => {
-    const effect = new KeyframeEffectReadOnly(target, subtest.input);
-    const secondEffect =
-      new KeyframeEffectReadOnly(target, effect.getKeyframes());
+    const effect = new KeyframeEffect(target, subtest.input);
+    const secondEffect = new KeyframeEffect(target, effect.getKeyframes());
     assert_frame_lists_equal(secondEffect.getKeyframes(),
                              effect.getKeyframes());
-  }, `A KeyframeEffectReadOnly constructed with ${subtest.desc} roundtrips`);
+  }, `A KeyframeEffect constructed with ${subtest.desc} roundtrips`);
 }
 
 for (const subtest of gInvalidKeyframesTests) {
   test(t => {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target, subtest.input);
+      new KeyframeEffect(target, subtest.input);
     });
-  }, `KeyframeEffectReadOnly constructor throws with ${subtest.desc}`);
+  }, `KeyframeEffect constructor throws with ${subtest.desc}`);
 }
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(target,
-                                            { left: ['10px', '20px'] });
+  const effect = new KeyframeEffect(target, { left: ['10px', '20px'] });
 
-  const timing = effect.timing;
+  const timing = effect.getTiming();
   assert_equals(timing.delay, 0, 'default delay');
   assert_equals(timing.endDelay, 0, 'default endDelay');
   assert_equals(timing.fill, 'auto', 'default fill');
@@ -142,14 +139,12 @@
   assert_equals(effect.composite, 'replace', 'default composite');
   assert_equals(effect.iterationComposite, 'replace',
                 'default iterationComposite');
-}, 'A KeyframeEffectReadOnly constructed without any ' +
-   'KeyframeEffectOptions object');
+}, 'A KeyframeEffect constructed without any KeyframeEffectOptions object');
 
 for (const subtest of gKeyframeEffectOptionTests) {
   test(t => {
-    const effect = new KeyframeEffectReadOnly(target,
-                                              { left: ['10px', '20px'] },
-                                              subtest.input);
+    const effect = new KeyframeEffect(target, { left: ['10px', '20px'] },
+                                      subtest.input);
 
     // Helper function to provide default expected values when the test does
     // not supply them.
@@ -157,7 +152,7 @@
       return field in subtest.expected ? subtest.expected[field] : defaultValue;
     };
 
-    const timing = effect.timing;
+    const timing = effect.getTiming();
     assert_equals(timing.delay, expected('delay', 0),
                   'timing delay');
     assert_equals(timing.fill, expected('fill', 'auto'),
@@ -169,27 +164,24 @@
     assert_equals(timing.direction, expected('direction', 'normal'),
                   'timing direction');
 
-  }, `A KeyframeEffectReadOnly constructed by ${subtest.desc}`);
+  }, `A KeyframeEffect constructed by ${subtest.desc}`);
 }
 
 for (const subtest of gInvalidKeyframeEffectOptionTests) {
   test(t => {
     assert_throws(new TypeError, () => {
-      new KeyframeEffectReadOnly(target,
-                                 { left: ['10px', '20px'] },
-                                 subtest.input);
+      new KeyframeEffect(target, { left: ['10px', '20px'] }, subtest.input);
     });
-  }, `Invalid KeyframeEffectReadOnly option by ${subtest.desc}`);
+  }, `Invalid KeyframeEffect option by ${subtest.desc}`);
 }
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null,
-                                            { left: ['10px', '20px'] },
-                                            { duration: 100 * MS_PER_SEC,
-                                              fill: 'forwards' });
+  const effect = new KeyframeEffect(null, { left: ['10px', '20px'] },
+                                    { duration: 100 * MS_PER_SEC,
+                                      fill: 'forwards' });
   assert_equals(effect.target, null,
                 'Effect created with null target has correct target');
-}, 'A KeyframeEffectReadOnly constructed with null target');
+}, 'A KeyframeEffect constructed with null target');
 
 test(t => {
   const test_error = { name: 'test' };
diff --git a/web-animations/interfaces/KeyframeEffect/copy-constructor.html b/web-animations/interfaces/KeyframeEffect/copy-constructor.html
index f6bb45a..e3bc0db 100644
--- a/web-animations/interfaces/KeyframeEffect/copy-constructor.html
+++ b/web-animations/interfaces/KeyframeEffect/copy-constructor.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>KeyframeEffect and KeyframeEffectReadOnly copy constructor</title>
+<title>KeyframeEffect copy constructor</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#dom-keyframeeffect-keyframeeffect-source">
+      href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-keyframeeffect-source">
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly-source">
+      href="https://drafts.csswg.org/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly-source">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -14,21 +14,21 @@
 'use strict';
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(createDiv(t), null);
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
+  const effect = new KeyframeEffect(createDiv(t), null);
+  const copiedEffect = new KeyframeEffect(effect);
   assert_equals(copiedEffect.target, effect.target, 'same target');
-}, 'Copied KeyframeEffectReadOnly has the same target');
+}, 'Copied KeyframeEffect has the same target');
 
 test(t => {
   const effect =
-    new KeyframeEffectReadOnly(null,
-                               [ { marginLeft: '0px' },
-                                 { marginLeft: '-20px', easing: 'ease-in',
-                                   offset: 0.1 },
-                                 { marginLeft: '100px', easing: 'ease-out' },
-                                 { marginLeft: '50px' } ]);
+    new KeyframeEffect(null,
+                       [ { marginLeft: '0px' },
+                         { marginLeft: '-20px', easing: 'ease-in',
+                           offset: 0.1 },
+                         { marginLeft: '100px', easing: 'ease-out' },
+                         { marginLeft: '50px' } ]);
 
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
+  const copiedEffect = new KeyframeEffect(effect);
   const keyframesA = effect.getKeyframes();
   const keyframesB = copiedEffect.getKeyframes();
   assert_equals(keyframesA.length, keyframesB.length, 'same keyframes length');
@@ -50,34 +50,33 @@
     assert_equals(keyframesA[i].marginLeft, keyframesB[i].marginLeft,
                   `Keyframe ${i} has the same property value pair`);
   }
-}, 'Copied KeyframeEffectReadOnly has the same keyframes');
+}, 'Copied KeyframeEffect has the same keyframes');
 
 test(t => {
   const effect =
-    new KeyframeEffectReadOnly(null, null,
-                               { iterationComposite: 'accumulate' });
+    new KeyframeEffect(null, null, { iterationComposite: 'accumulate' });
 
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
+  const copiedEffect = new KeyframeEffect(effect);
   assert_equals(copiedEffect.iterationComposite, effect.iterationComposite,
                 'same iterationCompositeOperation');
   assert_equals(copiedEffect.composite, effect.composite,
                 'same compositeOperation');
-}, 'Copied KeyframeEffectReadOnly has the same KeyframeEffectOptions');
+}, 'Copied KeyframeEffect has the same KeyframeEffectOptions');
 
 test(t => {
-  const effect = new KeyframeEffectReadOnly(null, null,
-                                            { duration: 100 * MS_PER_SEC,
-                                              delay: -1 * MS_PER_SEC,
-                                              endDelay: 2 * MS_PER_SEC,
-                                              fill: 'forwards',
-                                              iterationStart: 2,
-                                              iterations: 20,
-                                              easing: 'ease-out',
-                                              direction: 'alternate' } );
+  const effect = new KeyframeEffect(null, null,
+                                    { duration: 100 * MS_PER_SEC,
+                                      delay: -1 * MS_PER_SEC,
+                                      endDelay: 2 * MS_PER_SEC,
+                                      fill: 'forwards',
+                                      iterationStart: 2,
+                                      iterations: 20,
+                                      easing: 'ease-out',
+                                      direction: 'alternate' } );
 
-  const copiedEffect = new KeyframeEffectReadOnly(effect);
-  const timingA = effect.timing;
-  const timingB = copiedEffect.timing;
+  const copiedEffect = new KeyframeEffect(effect);
+  const timingA = effect.getTiming();
+  const timingB = copiedEffect.getTiming();
   assert_not_equals(timingA, timingB, 'different timing objects');
   assert_equals(timingA.delay, timingB.delay, 'same delay');
   assert_equals(timingA.endDelay, timingB.endDelay, 'same endDelay');
@@ -88,19 +87,7 @@
   assert_equals(timingA.duration, timingB.duration, 'same duration');
   assert_equals(timingA.direction, timingB.direction, 'same direction');
   assert_equals(timingA.easing, timingB.easing, 'same easing');
-}, 'Copied KeyframeEffectReadOnly has the same timing content');
-
-test(t => {
-  const effect = new KeyframeEffectReadOnly(createDiv(t), null);
-  assert_equals(effect.constructor.name, 'KeyframeEffectReadOnly');
-  assert_equals(effect.timing.constructor.name,
-                'AnimationEffectTimingReadOnly');
-
-  // Make a mutable copy
-  const copiedEffect = new KeyframeEffect(effect);
-  assert_equals(copiedEffect.constructor.name, 'KeyframeEffect');
-  assert_equals(copiedEffect.timing.constructor.name, 'AnimationEffectTiming');
-}, 'KeyframeEffect constructed from a KeyframeEffectReadOnly is mutable');
+}, 'Copied KeyframeEffect has the same timing content');
 
 </script>
 </body>
diff --git a/web-animations/interfaces/KeyframeEffect/idlharness.html b/web-animations/interfaces/KeyframeEffect/idlharness.html
index c397234..00cdd99 100644
--- a/web-animations/interfaces/KeyframeEffect/idlharness.html
+++ b/web-animations/interfaces/KeyframeEffect/idlharness.html
@@ -1,24 +1,16 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>KeyframeEffect IDL</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#keyframeeffect">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#keyframeeffect">
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#keyframeeffectreadonly">
+      href="https://drafts.csswg.org/web-animations/#keyframeeffectreadonly">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
 <div id="log"></div>
-<script type="text/plain" id="AnimationEffectTimingReadOnly-IDL">
-enum FillMode { "none", "forwards", "backwards", "both", "auto" };
-enum PlaybackDirection {
-  "normal",
-  "reverse",
-  "alternate",
-  "alternate-reverse"
-};
-
-dictionary AnimationEffectTimingProperties {
+<script type="text/plain" id="AnimationEffect-IDL">
+dictionary EffectTiming {
   double                             delay = 0.0;
   double                             endDelay = 0.0;
   FillMode                           fill = "auto";
@@ -29,91 +21,59 @@
   DOMString                          easing = "linear";
 };
 
-[Exposed=Window]
-interface AnimationEffectTimingReadOnly {
-  readonly attribute double                             delay;
-  readonly attribute double                             endDelay;
-  readonly attribute FillMode                           fill;
-  readonly attribute double                             iterationStart;
-  readonly attribute unrestricted double                iterations;
-  readonly attribute (unrestricted double or DOMString) duration;
-  readonly attribute PlaybackDirection                  direction;
-  readonly attribute DOMString                          easing;
+dictionary OptionalEffectTiming {
+  double                              delay;
+  double                              endDelay;
+  FillMode                            fill;
+  double                              iterationStart;
+  unrestricted double                 iterations;
+  (unrestricted double or DOMString)  duration;
+  PlaybackDirection                   direction;
+  DOMString                           easing;
 };
-</script>
-<script type="text/plain" id="AnimationEffectReadOnly-IDL">
-dictionary ComputedTimingProperties : AnimationEffectTimingProperties {
-  unrestricted double  endTime;
-  unrestricted double  activeDuration;
-  double?              localTime;
-  double?              progress;
-  unrestricted double? currentIteration;
+
+dictionary ComputedEffectTiming : EffectTiming {
+  unrestricted double   endTime = 0.0;
+  unrestricted double   activeDuration = 0.0;
+  double?               localTime = null;
+  double?               progress = null;
+  unrestricted double?  currentIteration = null;
 };
 
 [Exposed=Window]
-interface AnimationEffectReadOnly {
-  readonly attribute AnimationEffectTimingReadOnly timing;
-  ComputedTimingProperties getComputedTiming();
-};
-</script>
-<script type="text/plain" id="KeyframeEffectReadOnly-IDL">
-enum IterationCompositeOperation { "replace", "accumulate" };
-enum CompositeOperation { "replace", "add", "accumulate" };
-
-dictionary KeyframeEffectOptions : AnimationEffectTimingProperties {
-  IterationCompositeOperation iterationComposite = "replace";
-  CompositeOperation          composite = "replace";
-};
-
-[Exposed=Window,
- Constructor ((Element or CSSPseudoElement)? target,
-              object? keyframes,
-              optional (unrestricted double or KeyframeEffectOptions) options),
- Constructor (KeyframeEffectReadOnly source)]
-interface KeyframeEffectReadOnly : AnimationEffectReadOnly {
-  readonly attribute (Element or CSSPseudoElement)? target;
-  readonly attribute IterationCompositeOperation    iterationComposite;
-  readonly attribute CompositeOperation             composite;
-  sequence<object> getKeyframes ();
-};
-</script>
-<script type="text/plain" id="KeyframeEffect-IDL">
-[Exposed=Window,
- Constructor ((Element or CSSPseudoElement)? target,
-              object? keyframes,
-              optional (unrestricted double or KeyframeEffectOptions) options),
- Constructor (KeyframeEffectReadOnly source)]
-interface KeyframeEffect : KeyframeEffectReadOnly {
-  inherit attribute (Element or CSSPseudoElement)? target;
-  inherit attribute IterationCompositeOperation    iterationComposite;
-  inherit attribute CompositeOperation             composite;
-  void setKeyframes (object? keyframes);
+interface AnimationEffect {
+  EffectTiming getTiming();
+  ComputedEffectTiming getComputedTiming();
+  void updateTiming(optional OptionalEffectTiming timing);
 };
 </script>
 <script>
 'use strict';
 
-const idlArray = new IdlArray();
+promise_test(async () => {
+  const idlArray = new IdlArray();
+  idlArray.add_untested_idls(
+    document.getElementById('AnimationEffect-IDL').textContent
+  );
+  const text = await fetch('/interfaces/web-animations.idl').then(response =>
+    response.text(),
+  );
+  idlArray.add_idls(text, {
+    only: [
+      'IterationCompositeOperation',
+      'CompositeOperation',
+      'KeyframeEffectOptions',
+      'KeyframeEffect',
+    ]
+  });
 
-idlArray.add_untested_idls('interface CSSPseudoElement {};');
-idlArray.add_untested_idls('interface Element {};');
-idlArray.add_untested_idls(
-  document.getElementById('AnimationEffectTimingReadOnly-IDL').textContent
-);
-idlArray.add_idls(
-  document.getElementById('AnimationEffectReadOnly-IDL').textContent
-);
-idlArray.add_idls(
-  document.getElementById('KeyframeEffectReadOnly-IDL').textContent
-);
-idlArray.add_idls(
-  document.getElementById('KeyframeEffect-IDL').textContent
-);
-idlArray.add_objects({
-  KeyframeEffect: ['new KeyframeEffect(null, null)'],
-  KeyframeEffectReadOnly: ['new KeyframeEffectReadOnly(null, null)'],
-});
+  idlArray.add_untested_idls('interface CSSPseudoElement {};');
+  idlArray.add_untested_idls('interface Element {};');
+  idlArray.add_objects({
+    KeyframeEffect: ['new KeyframeEffect(null, null)'],
+  });
 
-idlArray.test();
-
+  idlArray.test();
+  done();
+}, 'KeyframeEffect interface.');
 </script>
diff --git a/web-animations/interfaces/KeyframeEffect/iterationComposite.html b/web-animations/interfaces/KeyframeEffect/iterationComposite.html
index cab51d0..bbb8ee2 100644
--- a/web-animations/interfaces/KeyframeEffect/iterationComposite.html
+++ b/web-animations/interfaces/KeyframeEffect/iterationComposite.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>KeyframeEffect.iterationComposite</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-keyframeeffect-iterationcomposite">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-iterationcomposite">
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src="../../testcommon.js"></script>
@@ -19,7 +19,8 @@
   anim.pause();
 
   anim.currentTime =
-    anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2;
+    anim.effect.getComputedTiming().duration * 2 +
+    anim.effect.getComputedTiming().duration / 2;
   assert_equals(getComputedStyle(div).marginLeft, '25px',
     'Animated style at 50s of the third iteration');
 
diff --git a/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html b/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
index b1de5bd..1b28210 100644
--- a/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
+++ b/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Processing a keyframes argument (property access)</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#processing-a-keyframes-argument">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#processing-a-keyframes-argument">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -13,7 +13,7 @@
 'use strict';
 
 // This file only tests the KeyframeEffect constructor since it is
-// assumed that the implementation of the KeyframeEffectReadOnly constructor,
+// assumed that the implementation of the KeyframeEffect constructor,
 // Animatable.animate() method, and KeyframeEffect.setKeyframes() method will
 // all share common machinery and it is not necessary to test each method.
 
@@ -180,9 +180,27 @@
     { done: true },
   ]));
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', left: '100px' },
-    { offset: null, computedOffset: 0.5, easing: 'linear', left: '300px' },
-    { offset: null, computedOffset: 1, easing: 'linear', left: '200px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      left: '100px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 0.5,
+      easing: 'linear',
+      left: '300px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      left: '200px',
+      composite: null,
+    },
   ]);
 }, 'Keyframes are read from a custom iterator');
 
@@ -197,9 +215,27 @@
   keyframes.offset = '0.1';
   const effect = new KeyframeEffect(null, keyframes);
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', left: '100px' },
-    { offset: null, computedOffset: 0.5, easing: 'linear', left: '300px' },
-    { offset: null, computedOffset: 1, easing: 'linear', left: '200px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      left: '100px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 0.5,
+      easing: 'linear',
+      left: '300px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      left: '200px',
+      composite: null,
+    },
   ]);
 }, '\'easing\' and \'offset\' are ignored on iterable objects');
 
@@ -211,11 +247,29 @@
     { done: true },
   ]));
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', left: '100px',
-      top: '200px' },
-    { offset: null, computedOffset: 0.5, easing: 'linear', left: '300px' },
-    { offset: null, computedOffset: 1, easing: 'linear', left: '200px',
-      top: '100px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      left: '100px',
+      top: '200px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 0.5,
+      easing: 'linear',
+      left: '300px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      left: '200px',
+      top: '100px',
+      composite: null,
+    },
   ]);
 }, 'Keyframes are read from a custom iterator with multiple properties'
    + ' specified');
@@ -228,14 +282,46 @@
     { done: true },
   ]));
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', left: '100px' },
-    { offset: 0.75, computedOffset: 0.75, easing: 'linear', left: '250px' },
-    { offset: null, computedOffset: 1, easing: 'linear', left: '200px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      left: '100px',
+      composite: null,
+    },
+    {
+      offset: 0.75,
+      computedOffset: 0.75,
+      easing: 'linear',
+      left: '250px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      left: '200px',
+      composite: null,
+    },
   ]);
 }, 'Keyframes are read from a custom iterator with where an offset is'
    + ' specified');
 
 test(() => {
+  const test_error = { name: 'test' };
+  const bad_keyframe = { get left() { throw test_error; } };
+  assert_throws(test_error, () => {
+    new KeyframeEffect(null, createIterable([
+      { done: false, value: { left: '100px' } },
+      { done: false, value: bad_keyframe },
+      { done: false, value: { left: '200px' } },
+      { done: true },
+    ]));
+  });
+}, 'If a keyframe throws for an animatable property, that exception should be'
+    + ' propagated');
+
+test(() => {
   assert_throws({ name: 'TypeError' }, () => {
     new KeyframeEffect(null, createIterable([
       { done: false, value: { left: '100px' } },
@@ -249,15 +335,83 @@
 
 test(() => {
   const effect = new KeyframeEffect(null, createIterable([
+    { done: false, value: { left: '100px' } },
+    { done: false },  // No value member; keyframe is undefined.
+    { done: false, value: { left: '200px' } },
+    { done: true },
+  ]));
+  assert_frame_lists_equal(effect.getKeyframes(), [
+    { left: '100px', offset: null, computedOffset: 0, easing: 'linear', composite: null },
+    { offset: null, computedOffset: 0.5, easing: 'linear', composite: null },
+    { left: '200px', offset: null, computedOffset: 1, easing: 'linear', composite: null },
+  ]);
+}, 'An undefined keyframe returned from a custom iterator should be treated as a'
+    + ' default keyframe');
+
+test(() => {
+  const effect = new KeyframeEffect(null, createIterable([
+    { done: false, value: { left: '100px' } },
+    { done: false, value: null },
+    { done: false, value: { left: '200px' } },
+    { done: true },
+  ]));
+  assert_frame_lists_equal(effect.getKeyframes(), [
+    { left: '100px', offset: null, computedOffset: 0, easing: 'linear', composite: null },
+    { offset: null, computedOffset: 0.5, easing: 'linear', composite: null },
+    { left: '200px', offset: null, computedOffset: 1, easing: 'linear', composite: null },
+  ]);
+}, 'A null keyframe returned from a custom iterator should be treated as a'
+    + ' default keyframe');
+
+test(() => {
+  const effect = new KeyframeEffect(null, createIterable([
     { done: false, value: { left: ['100px', '200px'] } },
     { done: true },
   ]));
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 1, easing: 'linear' }
+    { offset: null, computedOffset: 1, easing: 'linear', composite: null }
   ]);
 }, 'A list of values returned from a custom iterator should be ignored');
 
 test(() => {
+  const test_error = { name: 'test' };
+  const keyframe_obj = {
+    [Symbol.iterator]() {
+      return { next() { throw test_error; } };
+    },
+  };
+  assert_throws(test_error, () => {
+    new KeyframeEffect(null, keyframe_obj);
+  });
+}, 'If a custom iterator throws from next(), the exception should be rethrown');
+
+// Test handling of invalid Symbol.iterator
+
+test(() => {
+  const test_error = { name: 'test' };
+  const keyframe_obj = {
+    [Symbol.iterator]() {
+      throw test_error;
+    },
+  };
+  assert_throws(test_error, () => {
+    new KeyframeEffect(null, keyframe_obj);
+  });
+}, 'Accessing a Symbol.iterator property that throws should rethrow');
+
+test(() => {
+  const keyframe_obj = {
+    [Symbol.iterator]() {
+      return 42;  // Not an object.
+    },
+  };
+  assert_throws({ name: 'TypeError' }, () => {
+    new KeyframeEffect(null, keyframe_obj);
+  });
+}, 'A non-object returned from the Symbol.iterator property should cause a'
+    + ' TypeError to be thrown');
+
+test(() => {
   const keyframe = {};
   Object.defineProperty(keyframe, 'width', { value: '200px' });
   Object.defineProperty(keyframe, 'height', {
@@ -270,8 +424,20 @@
   const effect = new KeyframeEffect(null, [keyframe, { height: '200px' }]);
 
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', height: '100px' },
-    { offset: null, computedOffset: 1, easing: 'linear', height: '200px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      height: '100px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      height: '200px',
+      composite: null,
+    },
   ]);
 }, 'Only enumerable properties on keyframes are read');
 
@@ -289,8 +455,20 @@
   const effect = new KeyframeEffect(null, [keyframe, { top: '200px' }]);
 
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', top: '100px' },
-    { offset: null, computedOffset: 1, easing: 'linear', top: '200px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      top: '100px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      top: '200px',
+      composite: null,
+    },
   ]);
 }, 'Only properties defined directly on keyframes are read');
 
@@ -305,8 +483,20 @@
   const effect = new KeyframeEffect(null, keyframes);
 
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', height: '100px' },
-    { offset: null, computedOffset: 1, easing: 'linear', height: '200px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      height: '100px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      height: '200px',
+      composite: null,
+    },
   ]);
 }, 'Only enumerable properties on property-indexed keyframes are read');
 
@@ -324,8 +514,20 @@
   const effect = new KeyframeEffect(null, keyframes);
 
   assert_frame_lists_equal(effect.getKeyframes(), [
-    { offset: null, computedOffset: 0, easing: 'linear', top: '100px' },
-    { offset: null, computedOffset: 1, easing: 'linear', top: '200px' },
+    {
+      offset: null,
+      computedOffset: 0,
+      easing: 'linear',
+      top: '100px',
+      composite: null,
+    },
+    {
+      offset: null,
+      computedOffset: 1,
+      easing: 'linear',
+      top: '200px',
+      composite: null,
+    },
   ]);
 }, 'Only properties defined directly on property-indexed keyframes are read');
 
diff --git a/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002.html b/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002.html
index 13715bc..8233dc0 100644
--- a/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002.html
+++ b/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Processing a keyframes argument (easing)</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#processing-a-keyframes-argument">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#processing-a-keyframes-argument">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/interfaces/KeyframeEffect/setKeyframes.html b/web-animations/interfaces/KeyframeEffect/setKeyframes.html
index 1ea4090..675a705 100644
--- a/web-animations/interfaces/KeyframeEffect/setKeyframes.html
+++ b/web-animations/interfaces/KeyframeEffect/setKeyframes.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>KeyframeEffect.setKeyframes</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#dom-keyframeeffect-setkeyframes">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -39,5 +39,17 @@
     });
   }, `KeyframeEffect constructor throws with ${subtest.desc}`);
 }
+
+test(t => {
+  const frames1 = [ { left: '100px' }, { left: '200px' } ];
+  const frames2 = [ { left: '200px' }, { left: '300px' } ];
+
+  const animation = target.animate(frames1, 1000);
+  animation.currentTime = 500;
+  assert_equals(getComputedStyle(target).left, "150px");
+
+  animation.effect.setKeyframes(frames2);
+  assert_equals(getComputedStyle(target).left, "250px");
+}, 'Changes made via setKeyframes should be immediately visible in style');
 </script>
 </body>
diff --git a/web-animations/interfaces/KeyframeEffect/target.html b/web-animations/interfaces/KeyframeEffect/target.html
index a00c96c..b8fc05b 100644
--- a/web-animations/interfaces/KeyframeEffect/target.html
+++ b/web-animations/interfaces/KeyframeEffect/target.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>KeyframeEffect.target</title>
 <link rel="help"
-  href="https://w3c.github.io/web-animations/#dom-keyframeeffect-target">
+  href="https://drafts.csswg.org/web-animations/#dom-keyframeeffect-target">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/resources/effect-tests.js b/web-animations/resources/effect-tests.js
index f3f7fc2..8a18ab1 100644
--- a/web-animations/resources/effect-tests.js
+++ b/web-animations/resources/effect-tests.js
@@ -28,7 +28,7 @@
   const timing = effect.getComputedTiming();
 
   // The following calculations are based on the definitions here:
-  // https://w3c.github.io/web-animations/#animation-effect-phases-and-states
+  // https://drafts.csswg.org/web-animations/#animation-effect-phases-and-states
   const beforeActive = Math.max(Math.min(timing.delay, timing.endTime), 0);
   const activeAfter =
     Math.max(Math.min(timing.delay + timing.activeDuration, timing.endTime), 0);
diff --git a/web-animations/resources/keyframe-tests.js b/web-animations/resources/keyframe-tests.js
index 3aa1c6d..f32f5ce 100644
--- a/web-animations/resources/keyframe-tests.js
+++ b/web-animations/resources/keyframe-tests.js
@@ -12,14 +12,18 @@
 // ------------------------------
 
 const gGoodKeyframeCompositeValueTests = [
-  'replace', 'add', 'accumulate', undefined
+  'replace', 'add', 'accumulate', null
+];
+
+const gBadKeyframeCompositeValueTests = [
+  'unrecognised', 'replace ', 'Replace'
 ];
 
 const gGoodOptionsCompositeValueTests = [
   'replace', 'add', 'accumulate'
 ];
 
-const gBadCompositeValueTests = [
+const gBadOptionsCompositeValueTests = [
   'unrecognised', 'replace ', 'Replace', null
 ];
 
@@ -50,9 +54,7 @@
   // Object.assign instead.
   const result = {};
   Object.assign(result, offset, props, { easing });
-  if (composite) {
-    result.composite = composite;
-  }
+  result.composite = composite || null;
   return result;
 };
 
diff --git a/web-animations/resources/timing-tests.js b/web-animations/resources/timing-tests.js
new file mode 100644
index 0000000..4b0f021
--- /dev/null
+++ b/web-animations/resources/timing-tests.js
@@ -0,0 +1,46 @@
+'use strict';
+
+// =================================
+//
+// Common timing parameter test data
+//
+// =================================
+
+
+// ------------------------------
+//  Delay values
+// ------------------------------
+
+const gBadDelayValues = [
+  NaN, Infinity, -Infinity
+];
+
+// ------------------------------
+//  Duration values
+// ------------------------------
+
+const gGoodDurationValues = [
+  { specified: 123.45, computed: 123.45 },
+  { specified: 'auto', computed: 0 },
+  { specified: Infinity, computed: Infinity },
+];
+
+const gBadDurationValues = [
+  -1, NaN, -Infinity, 'abc', '100'
+];
+
+// ------------------------------
+//  iterationStart values
+// ------------------------------
+
+const gBadIterationStartValues = [
+  -1, NaN, Infinity, -Infinity
+];
+
+// ------------------------------
+//  iterations values
+// ------------------------------
+
+const gBadIterationsValues = [
+  -1, -Infinity, NaN
+];
diff --git a/web-animations/resources/timing-utils.js b/web-animations/resources/timing-utils.js
new file mode 100644
index 0000000..d7267f9
--- /dev/null
+++ b/web-animations/resources/timing-utils.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// =======================================
+//
+// Utility functions for testing timing
+//
+// =======================================
+
+
+// ------------------------------
+//  Helper functions
+// ------------------------------
+
+// Utility function to check that a subset of timing properties have their
+// default values.
+function assert_default_timing_except(effect, propertiesToSkip) {
+  const defaults = {
+    delay: 0,
+    endDelay: 0,
+    fill: 'auto',
+    iterationStart: 0,
+    iterations: 1,
+    duration: 'auto',
+    direction: 'normal',
+    easing: 'linear',
+  };
+
+  for (const prop of Object.keys(defaults)) {
+    if (propertiesToSkip.includes(prop)) {
+      continue;
+    }
+
+    assert_equals(
+      effect.getTiming()[prop],
+      defaults[prop],
+      `${prop} parameter has default value:`
+    );
+  }
+}
diff --git a/web-animations/testcommon.js b/web-animations/testcommon.js
index e2062b4..1a8ca79 100644
--- a/web-animations/testcommon.js
+++ b/web-animations/testcommon.js
@@ -14,17 +14,29 @@
 
 // The recommended minimum precision to use for time values[1].
 //
-// [1] https://w3c.github.io/web-animations/#precision-of-time-values
+// [1] https://drafts.csswg.org/web-animations/#precision-of-time-values
 const TIME_PRECISION = 0.0005; // ms
 
 // Allow implementations to substitute an alternative method for comparing
 // times based on their precision requirements.
 if (!window.assert_times_equal) {
   window.assert_times_equal = (actual, expected, description) => {
-    assert_approx_equals(actual, expected, TIME_PRECISION, description);
+    assert_approx_equals(actual, expected, TIME_PRECISION * 2, description);
   };
 }
 
+// Allow implementations to substitute an alternative method for comparing
+// a time value based on its precision requirements with a fixed value.
+if (!window.assert_time_equals_literal) {
+  window.assert_time_equals_literal = (actual, expected, description) => {
+    if (Math.abs(expected) === Infinity) {
+      assert_equals(actual, expected, description);
+    } else {
+      assert_approx_equals(actual, expected, TIME_PRECISION, description);
+    }
+  }
+}
+
 // creates div element, appends it to the document body and
 // removes the created element during test cleanup
 function createDiv(test, doc) {
@@ -76,7 +88,7 @@
 }
 
 // Create a pseudo element
-function createPseudo(test, type) {
+function getPseudoElement(test, type) {
   createStyle(test, { '@keyframes anim': '',
                       [`.pseudo::${type}`]: 'animation: anim 10s; ' +
                                             'content: \'\';'  });
@@ -85,8 +97,6 @@
   const anims = document.getAnimations();
   assert_true(anims.length >= 1);
   const anim = anims[anims.length - 1];
-  assert_equals(anim.effect.target.parentElement, div);
-  assert_equals(anim.effect.target.type, `::${type}`);
   anim.cancel();
   return anim.effect.target;
 }
@@ -176,6 +186,21 @@
   });
 }
 
+
+// Waits for a requestAnimationFrame callback in the next refresh driver tick.
+function waitForNextFrame() {
+  const timeAtStart = document.timeline.currentTime;
+  return new Promise(resolve => {
+    window.requestAnimationFrame(() => {
+      if (timeAtStart === document.timeline.currentTime) {
+        window.requestAnimationFrame(resolve);
+      } else {
+        resolve();
+      }
+    });
+  });
+}
+
 // Returns 'matrix()' or 'matrix3d()' function string generated from an array.
 function createMatrixFromArray(array) {
   return (array.length == 16 ? 'matrix3d' : 'matrix') + `(${array.join()})`;
@@ -239,3 +264,25 @@
       `expected ${expected} but got ${actual}: ${description}`);
   }
 }
+
+// Compare rotate3d vector like '0 1 0 45deg' with tolerances.
+function assert_rotate3d_equals(actual, expected, description) {
+  const rotationRegExp =/^((([+-]?\d+(\.+\d+)?\s){3})?\d+(\.+\d+)?)deg/;
+
+  assert_regexp_match(actual, rotationRegExp,
+    'Actual value is not a rotate3d vector')
+  assert_regexp_match(expected, rotationRegExp,
+    'Expected value is not a rotate3d vector');
+
+  const actualRotationVector =
+    actual.match(rotationRegExp)[1].split(' ').map(Number);
+  const expectedRotationVector =
+    expected.match(rotationRegExp)[1].split(' ').map(Number);
+
+  assert_equals(actualRotationVector.length, expectedRotationVector.length,
+    `dimension of the matrix: ${description}`);
+  for (let i = 0; i < actualRotationVector.length; i++) {
+    assert_approx_equals(actualRotationVector[i], expectedRotationVector[i], 0.0001,
+      `expected ${expected} but got ${actual}: ${description}`);
+  }
+}
diff --git a/web-animations/timing-model/animation-effects/active-time.html b/web-animations/timing-model/animation-effects/active-time.html
index c5d91fc..a2feb23 100644
--- a/web-animations/timing-model/animation-effects/active-time.html
+++ b/web-animations/timing-model/animation-effects/active-time.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Active time</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#calculating-the-active-time">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#calculating-the-active-time">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -25,19 +25,19 @@
 test(t => {
   const anim = createDiv(t).animate(null, 1000);
   anim.currentTime = 500;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
 }, 'Active time in active phase and no start delay is the local time');
 
 test(t => {
   const anim = createDiv(t).animate(null, { duration: 1000, delay: 500 });
   anim.currentTime = 1000;
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
 }, 'Active time in active phase and positive start delay is the local time'
    + ' minus the start delay');
 
 test(t => {
   const anim = createDiv(t).animate(null, { duration: 1000, delay: -500 });
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
 }, 'Active time in active phase and negative start delay is the local time'
    + ' minus the start delay');
 
@@ -58,7 +58,7 @@
                                             fill: 'forwards' });
   anim.finish();
   assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.3);
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3);
 }, 'Active time in after phase with forwards fill is the active duration');
 
 test(t => {
@@ -79,7 +79,7 @@
                                             fill: 'forwards' });
   anim.finish();
   assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.3);
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3);
 }, 'Active time in after phase with forwards fill and positive end delay'
    + ' is the active duration');
 
@@ -91,7 +91,7 @@
                                             fill: 'forwards' });
   anim.finish();
   assert_equals(anim.effect.getComputedTiming().currentIteration, 1);
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.5);
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.5);
 }, 'Active time in after phase with forwards fill and negative end delay'
    + ' is the active duration + end delay');
 
@@ -127,7 +127,7 @@
                                             fill: 'both' });
   anim.finish();
   assert_equals(anim.effect.getComputedTiming().currentIteration, 2);
-  assert_times_equal(anim.effect.getComputedTiming().progress, 0.3);
+  assert_time_equals_literal(anim.effect.getComputedTiming().progress, 0.3);
 }, 'Active time in after phase with \'both\' fill is the active duration');
 
 test(t => {
diff --git a/web-animations/timing-model/animation-effects/current-iteration.html b/web-animations/timing-model/animation-effects/current-iteration.html
index 0ac920d..24464ce 100644
--- a/web-animations/timing-model/animation-effects/current-iteration.html
+++ b/web-animations/timing-model/animation-effects/current-iteration.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Current iteration</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#current-iteration">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#current-iteration">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/timing-model/animation-effects/local-time.html b/web-animations/timing-model/animation-effects/local-time.html
index 54455a1..79437d9 100644
--- a/web-animations/timing-model/animation-effects/local-time.html
+++ b/web-animations/timing-model/animation-effects/local-time.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Local time</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#local-time">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#local-time">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/timing-model/animation-effects/phases-and-states.html b/web-animations/timing-model/animation-effects/phases-and-states.html
index b3e8f78..b62726c 100644
--- a/web-animations/timing-model/animation-effects/phases-and-states.html
+++ b/web-animations/timing-model/animation-effects/phases-and-states.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Phases and states</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#animation-effect-phases-and-states">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-effect-phases-and-states">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -22,7 +22,7 @@
   if (phase === 'active') {
     // If the fill mode is 'none', then progress will only be non-null if we
     // are in the active phase.
-    animation.effect.timing.fill = 'none';
+    animation.effect.updateTiming({ fill: 'none' });
     assert_not_equals(animation.effect.getComputedTiming().progress, null,
                       'Animation effect is in active phase when current time'
                       + ` is ${currentTime}ms`);
@@ -31,15 +31,15 @@
     // phase is to toggle the fill mode. For example, if the progress is null
     // will the fill node is 'none' but non-null when the fill mode is
     // 'backwards' then we are in the before phase.
-    animation.effect.timing.fill = 'none';
+    animation.effect.updateTiming({ fill: 'none' });
     assert_equals(animation.effect.getComputedTiming().progress, null,
                   `Animation effect is in ${phase} phase when current time`
                   + ` is ${currentTime}ms`
                   + ' (progress is null with \'none\' fill mode)');
 
-    animation.effect.timing.fill = phase === 'before'
-                                   ? 'backwards'
-                                   : 'forwards';
+    animation.effect.updateTiming({
+      fill: phase === 'before' ? 'backwards' : 'forwards',
+    });
     assert_not_equals(animation.effect.getComputedTiming().progress, null,
                       `Animation effect is in ${phase} phase when current time`
                       + ` is ${currentTime}ms`
diff --git a/web-animations/timing-model/animation-effects/simple-iteration-progress.html b/web-animations/timing-model/animation-effects/simple-iteration-progress.html
index 22331f4..3c42f79 100644
--- a/web-animations/timing-model/animation-effects/simple-iteration-progress.html
+++ b/web-animations/timing-model/animation-effects/simple-iteration-progress.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>Simple iteration progress</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#simple-iteration-progress">
+      href="https://drafts.csswg.org/web-animations/#simple-iteration-progress">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/timing-model/animations/canceling-an-animation.html b/web-animations/timing-model/animations/canceling-an-animation.html
index e4cafc0..87b3ee0 100644
--- a/web-animations/timing-model/animations/canceling-an-animation.html
+++ b/web-animations/timing-model/animations/canceling-an-animation.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>Canceling an animation</title>
 <link rel="help"
-    href="https://w3c.github.io/web-animations/#canceling-an-animation-section">
+    href="https://drafts.csswg.org/web-animations/#canceling-an-animation-section">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -26,29 +26,28 @@
 }, 'A play-pending ready promise should be rejected when the animation is'
    + ' canceled');
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  return animation.ready.then(() => {
-    animation.pause();
-    // Set up listeners on pause-pending ready promise
-    const retPromise = animation.ready.then(() => {
-      assert_unreached('ready promise was fulfilled');
-    }).catch(err => {
-      assert_equals(err.name, 'AbortError',
-                    'ready promise is rejected with AbortError');
-    });
-    animation.cancel();
-    return retPromise;
-  });
+  await animation.ready;
+
+  // Make it pause-pending
+  animation.pause();
+
+  // We need to store the original ready promise since cancel() will
+  // replace it
+  const originalPromise = animation.ready;
+  animation.cancel();
+
+  await promise_rejects(t, 'AbortError', originalPromise,
+                        'Cancel should abort ready promise');
 }, 'A pause-pending ready promise should be rejected when the animation is'
    + ' canceled');
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null);
   animation.cancel();
-  return animation.ready.then(p => {
-    assert_equals(p, animation);
-  });
+  const promiseResult = await animation.ready;
+  assert_equals(promiseResult, animation);
 }, 'When an animation is canceled, it should create a resolved Promise');
 
 test(t => {
@@ -58,5 +57,43 @@
   assert_not_equals(animation.ready, promise);
 }, 'The ready promise should be replaced when the animation is canceled');
 
+promise_test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, null, { duration: 1000 }),
+    null
+  );
+  assert_equals(animation.playState, 'idle',
+                'The animation should be initially idle');
+
+  animation.finished.then(t.step_func(() => {
+    assert_unreached('Finished promise should not resolve');
+  }), t.step_func(() => {
+    assert_unreached('Finished promise should not reject');
+  }));
+
+  animation.cancel();
+
+  return waitForAnimationFrames(3);
+}, 'The finished promise should NOT be rejected if the animation is already'
+   + ' idle');
+
+promise_test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, null, { duration: 1000 }),
+    null
+  );
+  assert_equals(animation.playState, 'idle',
+                'The animation should be initially idle');
+
+  animation.oncancel = t.step_func(() => {
+    assert_unreached('Cancel event should not be fired');
+  });
+
+  animation.cancel();
+
+  return waitForAnimationFrames(3);
+}, 'The cancel event should NOT be fired if the animation is already'
+   + ' idle');
+
 </script>
 </body>
diff --git a/web-animations/timing-model/animations/current-time.html b/web-animations/timing-model/animations/current-time.html
deleted file mode 100644
index 680d8ad..0000000
--- a/web-animations/timing-model/animations/current-time.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Current time</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#current-time">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  animation.play();
-  assert_equals(animation.currentTime, 0,
-    'Current time returns the hold time set when entering the play-pending ' +
-    'state');
-}, 'The current time returns the hold time when set');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-
-  return animation.ready.then(() => {
-    assert_equals(animation.currentTime, null);
-  });
-}, 'The current time is unresolved when there is no associated timeline ' +
-   '(and no hold time is set)');
-
-// FIXME: Test that the current time is unresolved when we have an inactive
-// timeline if we find a way of creating an inactive timeline!
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  animation.startTime = null;
-  assert_equals(animation.currentTime, null);
-}, 'The current time is unresolved when the start time is unresolved ' +
-   '(and no hold time is set)');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  animation.playbackRate = 2;
-  animation.startTime = document.timeline.currentTime - 25 * MS_PER_SEC;
-
-  const timelineTime = document.timeline.currentTime;
-  const startTime = animation.startTime;
-  const playbackRate = animation.playbackRate;
-  assert_times_equal(animation.currentTime,
-                     (timelineTime - startTime) * playbackRate,
-                     'Animation has a unresolved start time');
-}, 'The current time is calculated from the timeline time, start time and ' +
-   'playback rate');
-
-promise_test(t => {
-  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  animation.playbackRate = 0;
-
-  return animation.ready.then(() => waitForAnimationFrames(1))
-  .then(() => {
-    assert_times_equal(animation.currentTime, 0);
-  });
-}, 'The current time does not progress if playback rate is 0');
-
-</script>
-</body>
diff --git a/web-animations/timing-model/animations/finishing-an-animation.html b/web-animations/timing-model/animations/finishing-an-animation.html
index 566fb3c..833f074 100644
--- a/web-animations/timing-model/animations/finishing-an-animation.html
+++ b/web-animations/timing-model/animations/finishing-an-animation.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>Finishing an animation</title>
 <link rel="help"
-  href="https://w3c.github.io/web-animations/#finishing-an-animation-section">
+  href="https://drafts.csswg.org/web-animations/#finishing-an-animation-section">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -11,7 +11,208 @@
 <script>
 'use strict';
 
-promise_test(t => {
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  animation.playbackRate = 0;
+
+  assert_throws({name: 'InvalidStateError'}, () => {
+    animation.finish();
+  });
+}, 'Finishing an animation with a zero playback rate throws');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null,
+                                { duration : 100 * MS_PER_SEC,
+                                  iterations : Infinity });
+
+  assert_throws({name: 'InvalidStateError'}, () => {
+    animation.finish();
+  });
+}, 'Finishing an infinite animation throws');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  animation.finish();
+
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC,
+    'After finishing, the currentTime should be set to the end of the'
+    + ' active duration');
+}, 'Finishing an animation seeks to the end time');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+   // 1s past effect end
+  animation.currentTime =
+    animation.effect.getComputedTiming().endTime + 1 * MS_PER_SEC;
+  animation.finish();
+
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC,
+    'After finishing, the currentTime should be set back to the end of the'
+    + ' active duration');
+}, 'Finishing an animation with a current time past the effect end jumps'
+   + ' back to the end');
+
+promise_test(async t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 100 * MS_PER_SEC;
+  await animation.finished;
+
+  animation.playbackRate = -1;
+  animation.finish();
+
+  assert_equals(animation.currentTime, 0,
+                'After finishing a reversed animation the currentTime ' +
+                'should be set to zero');
+}, 'Finishing a reversed animation jumps to zero time');
+
+promise_test(async t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 100 * MS_PER_SEC;
+  await animation.finished;
+
+  animation.playbackRate = -1;
+  animation.currentTime = -1000;
+  animation.finish();
+
+  assert_equals(animation.currentTime, 0,
+                'After finishing a reversed animation the currentTime ' +
+                'should be set back to zero');
+}, 'Finishing a reversed animation with a current time less than zero'
+   + ' makes it jump back to zero');
+
+promise_test(async t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  animation.pause();
+  await animation.ready;
+
+  animation.finish();
+
+  assert_equals(animation.playState, 'finished',
+                'The play state of a paused animation should become ' +
+                '"finished"');
+  assert_times_equal(animation.startTime,
+                     animation.timeline.currentTime - 100 * MS_PER_SEC,
+                     'The start time of a paused animation should be set');
+}, 'Finishing a paused animation resolves the start time');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  // Update playbackRate so we can test that the calculated startTime
+  // respects it
+  animation.playbackRate = 2;
+  animation.pause();
+  // While animation is still pause-pending call finish()
+  animation.finish();
+
+  assert_false(animation.pending);
+  assert_equals(animation.playState, 'finished',
+                'The play state of a pause-pending animation should become ' +
+                '"finished"');
+  assert_times_equal(animation.startTime,
+                     animation.timeline.currentTime - 100 * MS_PER_SEC / 2,
+                     'The start time of a pause-pending animation should ' +
+                     'be set');
+}, 'Finishing a pause-pending animation resolves the pending task'
+   + ' immediately and update the start time');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  animation.playbackRate = -2;
+  animation.pause();
+  animation.finish();
+
+  assert_false(animation.pending);
+  assert_equals(animation.playState, 'finished',
+                'The play state of a pause-pending animation should become ' +
+                '"finished"');
+  assert_times_equal(animation.startTime, animation.timeline.currentTime,
+                     'The start time of a pause-pending animation should be ' +
+                     'set');
+}, 'Finishing a pause-pending animation with negative playback rate'
+   + ' resolves the pending task immediately');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  animation.playbackRate = 0.5;
+  animation.finish();
+
+  assert_false(animation.pending);
+  assert_equals(animation.playState, 'finished',
+                'The play state of a play-pending animation should become ' +
+                '"finished"');
+  assert_times_equal(animation.startTime,
+                     animation.timeline.currentTime - 100 * MS_PER_SEC / 0.5,
+                     'The start time of a play-pending animation should ' +
+                     'be set');
+}, 'Finishing an animation while play-pending resolves the pending'
+   + ' task immediately');
+
+// FIXME: Add a test for when we are play-pending without an active timeline.
+// - In that case even after calling finish() we should still be pending but
+//   the current time should be updated
+
+promise_test(async t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.pause();
+  animation.play();
+  // We are now in the unusual situation of being play-pending whilst having
+  // a resolved start time. Check that finish() still triggers a transition
+  // to the finished state immediately.
+  animation.finish();
+
+  assert_equals(animation.playState, 'finished',
+                'After aborting a pause then finishing an animation its play ' +
+                'state should become "finished" immediately');
+}, 'Finishing an animation during an aborted pause makes it finished'
+   + ' immediately');
+
+promise_test(async t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+  let resolvedFinished = false;
+  animation.finished.then(() => {
+    resolvedFinished = true;
+  });
+
+  await animation.ready;
+
+  animation.finish();
+  await Promise.resolve();
+
+  assert_true(resolvedFinished, 'finished promise should be resolved');
+}, 'Finishing an animation resolves the finished promise synchronously');
+
+promise_test(async t => {
+  const effect = new KeyframeEffect(null, null, 100 * MS_PER_SEC);
+  const animation = new Animation(effect, document.timeline);
+  let resolvedFinished = false;
+  animation.finished.then(() => {
+    resolvedFinished = true;
+  });
+
+  await animation.ready;
+
+  animation.finish();
+  await Promise.resolve();
+
+  assert_true(resolvedFinished, 'finished promise should be resolved');
+}, 'Finishing an animation without a target resolves the finished promise'
+   + ' synchronously');
+
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   const promise = animation.ready;
   let readyResolved = false;
@@ -19,13 +220,69 @@
   animation.finish();
   animation.ready.then(() => { readyResolved = true; });
 
-  return animation.finished.then(p => {
-    assert_equals(p, animation);
-    assert_equals(animation.ready, promise);
-    assert_true(readyResolved);
+  const promiseResult = await animation.finished;
+
+  assert_equals(promiseResult, animation);
+  assert_equals(animation.ready, promise);
+  assert_true(readyResolved);
+}, 'A pending ready promise is resolved and not replaced when the animation'
+   + ' is finished');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  assert_true(animation.pending);
+
+  animation.finish();
+  assert_false(animation.pending);
+  assert_equals(animation.playbackRate, 2);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+}, 'A pending playback rate should be applied immediately when an animation'
+   + ' is finished');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.updatePlaybackRate(0);
+
+  assert_throws('InvalidStateError', () => {
+    animation.finish();
   });
-}, 'A pending ready promise should be resolved and not replaced when the'
-   + ' animation is finished');
+}, 'An exception should be thrown if the effective playback rate is zero');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, {
+    duration: 100 * MS_PER_SEC,
+    iterations: Infinity
+  });
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.playbackRate = -1;
+  await animation.ready;
+
+  animation.updatePlaybackRate(1);
+
+  assert_throws('InvalidStateError', () => {
+    animation.finish();
+  });
+}, 'An exception should be thrown when finishing if the effective playback rate'
+   + ' is positive and the target effect end is infinity');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, {
+    duration: 100 * MS_PER_SEC,
+    iterations: Infinity
+  });
+  await animation.ready;
+
+  animation.updatePlaybackRate(-1);
+
+  animation.finish();
+  // Should not have thrown
+}, 'An exception is NOT thrown when finishing if the effective playback rate'
+   + ' is negative and the target effect end is infinity');
 
 </script>
 </body>
diff --git a/web-animations/timing-model/animations/pausing-an-animation.html b/web-animations/timing-model/animations/pausing-an-animation.html
index bb75dcc..c9aabba 100644
--- a/web-animations/timing-model/animations/pausing-an-animation.html
+++ b/web-animations/timing-model/animations/pausing-an-animation.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>Pausing an animation</title>
 <link rel="help"
-  href="https://w3c.github.io/web-animations/#pausing-an-animation-section">
+  href="https://drafts.csswg.org/web-animations/#pausing-an-animation-section">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -11,17 +11,36 @@
 <script>
 'use strict';
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   const promise = animation.ready;
   animation.pause();
-  return promise.then(p => {
-    assert_equals(p, animation);
-    assert_equals(animation.ready, promise);
-    assert_equals(animation.playState, 'paused');
-  });
+
+  const promiseResult = await promise;
+
+  assert_equals(promiseResult, animation);
+  assert_equals(animation.ready, promise);
+  assert_false(animation.pending, 'No longer pause-pending');
 }, 'A pending ready promise should be resolved and not replaced when the'
    + ' animation is paused');
 
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  // Let animation start roughly half-way through
+  animation.currentTime = 50 * MS_PER_SEC;
+  await animation.ready;
+
+  // Go pause-pending and also set a pending playback rate
+  animation.pause();
+  animation.updatePlaybackRate(0.5);
+
+  await animation.ready;
+  // If the current time was updated using the new playback rate it will jump
+  // back to 25s but if we correctly used the old playback rate the current time
+  // will be >50s.
+  assert_greater_than(animation.currentTime, 50 * MS_PER_SEC);
+}, 'A pause-pending animation maintains the current time when applying a'
+   + ' pending playback rate');
+
 </script>
 </body>
diff --git a/web-animations/timing-model/animations/play-states.html b/web-animations/timing-model/animations/play-states.html
new file mode 100644
index 0000000..5d8fdea
--- /dev/null
+++ b/web-animations/timing-model/animations/play-states.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Play states</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#play-states">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+  assert_equals(animation.currentTime, null,
+                'Current time should be initially unresolved');
+
+  assert_equals(animation.playState, 'idle');
+}, 'reports \'idle\' for an animation with an unresolved current time'
+   + ' and no pending tasks')
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate({}, 100 * MS_PER_SEC);
+
+  animation.pause();
+
+  assert_equals(animation.playState, 'paused');
+}, 'reports \'paused\' for an animation with a pending pause task');
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+
+  animation.currentTime = 0;
+  assert_equals(animation.startTime, null,
+                'Start time should still be unresolved after setting current'
+                + ' time');
+
+  assert_equals(animation.playState, 'paused');
+}, 'reports \'paused\' for an animation with a resolved current time and'
+   + ' unresolved start time')
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+
+  animation.startTime = document.timeline.currentTime;
+  assert_not_equals(animation.currentTime, null,
+                    'Current time should be resolved after setting start time');
+
+  assert_equals(animation.playState, 'running');
+}, 'reports \'running\' for an animation with a resolved start time and'
+   + ' current time');
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+  animation.startTime = document.timeline.currentTime;
+
+  animation.currentTime = 100 * MS_PER_SEC;
+
+  assert_equals(animation.playState, 'finished');
+}, 'reports \'finished\' when playback rate > 0 and'
+   + ' current time = target effect end');
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+  animation.startTime = document.timeline.currentTime;
+
+  animation.playbackRate = 0;
+  animation.currentTime = 100 * MS_PER_SEC;
+
+  assert_equals(animation.playState, 'running');
+}, 'reports \'running\' when playback rate = 0 and'
+   + ' current time = target effect end');
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+  animation.startTime = document.timeline.currentTime;
+
+  animation.playbackRate = -1;
+  animation.currentTime = 100 * MS_PER_SEC;
+
+  assert_equals(animation.playState, 'running');
+}, 'reports \'running\' when playback rate < 0 and'
+   + ' current time = target effect end');
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+  animation.startTime = document.timeline.currentTime;
+
+  animation.currentTime = 0;
+
+  assert_equals(animation.playState, 'running');
+}, 'reports \'running\' when playback rate > 0 and'
+   + ' current time = 0');
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+  animation.startTime = document.timeline.currentTime;
+
+  animation.playbackRate = 0;
+  animation.currentTime = 0;
+
+  assert_equals(animation.playState, 'running');
+}, 'reports \'running\' when playback rate = 0 and'
+   + ' current time = 0');
+
+test(t => {
+  const animation = new Animation(
+    new KeyframeEffect(null, {}, 100 * MS_PER_SEC)
+  );
+  animation.startTime = document.timeline.currentTime;
+
+  animation.playbackRate = -1;
+  animation.currentTime = 0;
+
+  assert_equals(animation.playState, 'finished');
+}, 'reports \'finished\' when playback rate < 0 and'
+   + ' current time = 0');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate({}, 0);
+  assert_equals(animation.startTime, null,
+                'Sanity check: start time should be unresolved');
+
+  assert_equals(animation.playState, 'finished');
+}, 'reports \'finished\' when playback rate > 0 and'
+   + ' current time = target effect end and there is a pending play task');
+
+test(t => {
+  const div = createDiv(t);
+  const animation = div.animate({}, 100 * MS_PER_SEC);
+  assert_equals(animation.startTime, null,
+                'Sanity check: start time should be unresolved');
+
+  assert_equals(animation.playState, 'running');
+}, 'reports \'running\' when playback rate > 0 and'
+   + ' current time < target effect end and there is a pending play task');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/playing-an-animation.html b/web-animations/timing-model/animations/playing-an-animation.html
index 43e9a6b..fb7020c 100644
--- a/web-animations/timing-model/animations/playing-an-animation.html
+++ b/web-animations/timing-model/animations/playing-an-animation.html
@@ -2,7 +2,7 @@
 <meta charset=utf-8>
 <title>Playing an animation</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#playing-an-animation-section">
+      href="https://drafts.csswg.org/web-animations/#playing-an-animation-section">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -14,26 +14,26 @@
 test(t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   animation.currentTime = 1 * MS_PER_SEC;
-  assert_times_equal(animation.currentTime, 1 * MS_PER_SEC);
+  assert_time_equals_literal(animation.currentTime, 1 * MS_PER_SEC);
   animation.play();
-  assert_times_equal(animation.currentTime, 1 * MS_PER_SEC);
+  assert_time_equals_literal(animation.currentTime, 1 * MS_PER_SEC);
 }, 'Playing a running animation leaves the current time unchanged');
 
 test(t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   animation.finish();
-  assert_times_equal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
   animation.play();
-  assert_times_equal(animation.currentTime, 0);
+  assert_time_equals_literal(animation.currentTime, 0);
 }, 'Playing a finished animation seeks back to the start');
 
 test(t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   animation.playbackRate = -1;
   animation.currentTime = 0;
-  assert_times_equal(animation.currentTime, 0);
+  assert_time_equals_literal(animation.currentTime, 0);
   animation.play();
-  assert_times_equal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
 }, 'Playing a finished and reversed animation seeks to end');
 
 test(t => {
@@ -45,15 +45,31 @@
 }, 'The ready promise should be replaced if the animation is not already'
    + ' pending');
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   const promise = animation.ready;
-  return promise.then(p => {
-    assert_equals(p, animation);
-    assert_equals(animation.ready, promise);
-  });
+  const promiseResult = await promise;
+  assert_equals(promiseResult, animation);
+  assert_equals(animation.ready, promise);
 }, 'A pending ready promise should be resolved and not replaced when the'
    + ' animation enters the running state');
 
+promise_test(async t => {
+  // Seek animation beyond target end
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = -100 * MS_PER_SEC;
+  await animation.ready;
+
+  // Set pending playback rate to the opposite direction
+  animation.updatePlaybackRate(-1);
+  assert_true(animation.pending);
+  assert_equals(animation.playbackRate, 1);
+
+  // When we play, we should seek to the target end, NOT to zero (which
+  // is where we would seek to if we used the playbackRate of 1.
+  animation.play();
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+}, 'A pending playback rate is used when determining auto-rewind behavior');
+
 </script>
 </body>
diff --git a/web-animations/timing-model/animations/reversing-an-animation.html b/web-animations/timing-model/animations/reversing-an-animation.html
index 5c96a6f..41302d9 100644
--- a/web-animations/timing-model/animations/reversing-an-animation.html
+++ b/web-animations/timing-model/animations/reversing-an-animation.html
@@ -1,8 +1,8 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
-<title>Reverse an animation</title>
+<title>Reversing an animation</title>
 <link rel="help"
-      href="https://w3c.github.io/web-animations/#reverse-an-animation">
+      href="https://drafts.csswg.org/web-animations/#reversing-an-animation-section">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -11,38 +11,43 @@
 <script>
 'use strict';
 
-promise_test(t => {
+promise_test(async t => {
   const div = createDiv(t);
   const animation = div.animate({}, { duration: 100 * MS_PER_SEC,
                                       iterations: Infinity });
 
+  await animation.ready;
   // Wait a frame because if currentTime is still 0 when we call
   // reverse(), it will throw (per spec).
-  return animation.ready.then(waitForAnimationFrames(1)).then(() => {
-    assert_greater_than_equal(animation.currentTime, 0,
-      'currentTime expected to be greater than 0, one frame after starting');
-    animation.currentTime = 50 * MS_PER_SEC;
-    const previousPlaybackRate = animation.playbackRate;
-    animation.reverse();
-    assert_equals(animation.playbackRate, -previousPlaybackRate,
-      'playbackRate should be inverted');
-  });
+  await waitForAnimationFrames(1);
+
+  assert_greater_than_equal(animation.currentTime, 0,
+    'currentTime expected to be greater than 0, one frame after starting');
+  animation.currentTime = 50 * MS_PER_SEC;
+  const previousPlaybackRate = animation.playbackRate;
+  animation.reverse();
+  assert_equals(animation.playbackRate, previousPlaybackRate,
+                'Playback rate should not have changed');
+  await animation.ready;
+
+  assert_equals(animation.playbackRate, -previousPlaybackRate,
+                'Playback rate should be inverted');
 }, 'Reversing an animation inverts the playback rate');
 
-promise_test(t => {
+promise_test(async t => {
   const div = createDiv(t);
   const animation = div.animate({}, { duration: 100 * MS_PER_SEC,
                                       iterations: Infinity });
   animation.currentTime = 50 * MS_PER_SEC;
   animation.pause();
 
-  return animation.ready.then(() => {
-    animation.reverse();
-    return animation.ready;
-  }).then(() => {
-    assert_equals(animation.playState, 'running',
-      'Animation.playState should be "running" after reverse()');
-  });
+  await animation.ready;
+
+  animation.reverse();
+  await animation.ready;
+
+  assert_equals(animation.playState, 'running',
+    'Animation.playState should be "running" after reverse()');
 }, 'Reversing an animation plays a pausing animation');
 
 test(t => {
@@ -60,16 +65,16 @@
   const div = createDiv(t);
   const animation = div.animate({}, { duration: 200 * MS_PER_SEC,
                                       delay: -100 * MS_PER_SEC });
-  assert_equals(animation.playState, 'pending',
-    'The playState is pending before we call reverse');
+  assert_true(animation.pending,
+              'The animation is pending before we call reverse');
 
   animation.reverse();
 
-  assert_equals(animation.playState, 'pending',
-    'The playState is still pending after calling reverse');
+  assert_true(animation.pending,
+              'The animation is still pending after calling reverse');
 }, 'Reversing an animation does not cause it to leave the pending state');
 
-promise_test(t => {
+promise_test(async t => {
   const div = createDiv(t);
   const animation = div.animate({}, { duration: 200 * MS_PER_SEC,
                                       delay: -100 * MS_PER_SEC });
@@ -78,10 +83,9 @@
 
   animation.reverse();
 
-  return Promise.resolve(() => {
-    assert_false(readyResolved,
-                 'ready promise should not have been resolved yet');
-  });
+  await Promise.resolve();
+  assert_false(readyResolved,
+               'ready promise should not have been resolved yet');
 }, 'Reversing an animation does not cause it to resolve the ready promise');
 
 test(t => {
@@ -149,13 +153,16 @@
 }, 'Reversing an animation when playbackRate > 0 and currentTime < 0 ' +
    'and the target effect end is positive infinity should throw an exception');
 
-test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate({}, { duration: 100 * MS_PER_SEC,
                                                iterations: Infinity });
   animation.currentTime = -200 * MS_PER_SEC;
 
   try { animation.reverse(); } catch(e) { }
 
+  assert_equals(animation.playbackRate, 1, 'playbackRate is unchanged');
+
+  await animation.ready;
   assert_equals(animation.playbackRate, 1, 'playbackRate remains unchanged');
 }, 'When reversing throws an exception, the playback rate remains unchanged');
 
@@ -191,18 +198,18 @@
    'and the target effect end is positive infinity should make it play ' +
    'from the start');
 
-test(t => {
+promise_test(async t => {
   const div = createDiv(t);
   const animation = div.animate({}, 100 * MS_PER_SEC);
   animation.playbackRate = 0;
   animation.currentTime = 50 * MS_PER_SEC;
   animation.reverse();
 
+  await animation.ready;
   assert_equals(animation.playbackRate, 0,
     'reverse() should preserve playbackRate if the playbackRate == 0');
   assert_equals(animation.currentTime, 50 * MS_PER_SEC,
     'reverse() should not affect the currentTime if the playbackRate == 0');
-  t.done();
 }, 'Reversing when when playbackRate == 0 should preserve the current ' +
    'time and playback rate');
 
@@ -215,5 +222,33 @@
 }, 'Reversing an animation without an active timeline throws an ' +
    'InvalidStateError');
 
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  animation.reverse();
+
+  await animation.ready;
+  assert_equals(animation.playbackRate, -2);
+}, 'Reversing should use the negative pending playback rate');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, {
+    duration: 100 * MS_PER_SEC,
+    iterations: Infinity,
+  });
+  animation.currentTime = -200 * MS_PER_SEC;
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  assert_throws('InvalidStateError', () => { animation.reverse(); });
+  assert_equals(animation.playbackRate, 1);
+
+  await animation.ready;
+  assert_equals(animation.playbackRate, 2);
+}, 'When reversing fails, it should restore any previous pending playback'
+   + ' rate');
+
 </script>
 </body>
diff --git a/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html b/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html
new file mode 100644
index 0000000..41e9776
--- /dev/null
+++ b/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Seamlessly updating the playback rate of an animation</title>
+<link rel="help"
+  href="https://drafts.csswg.org/web-animations-1/#seamlessly-updating-the-playback-rate-of-an-animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.currentTime = 50 * MS_PER_SEC;
+
+  animation.updatePlaybackRate(0.5);
+  await animation.ready;
+  // Since the animation is in motion (and we want to test it while it is in
+  // motion!) we can't assert that the current time == 50s but we can check
+  // that the current time is NOT re-calculated by simply substituting in the
+  // new playback rate (i.e. without adjusting the start time). If that were
+  // the case the currentTime would jump to 25s. So we just test the currentTime
+  // hasn't gone backwards.
+  assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC,
+    'Reducing the playback rate should not change the current time ' +
+    'of a playing animation');
+
+  animation.updatePlaybackRate(2);
+  await animation.ready;
+  // Likewise, we test here that the current time does not jump to 100s as it
+  // would if we naively applied a playbackRate of 2 without adjusting the
+  // startTime.
+  assert_less_than(animation.currentTime, 100 * MS_PER_SEC,
+    'Increasing the playback rate should not change the current time ' +
+    'of a playing animation');
+}, 'Updating the playback rate maintains the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  assert_false(animation.pending);
+  animation.updatePlaybackRate(2);
+  assert_true(animation.pending);
+}, 'Updating the playback rate while running makes the animation pending');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  assert_true(animation.pending);
+
+  animation.updatePlaybackRate(0.5);
+
+  // Check that the hold time is updated as expected
+  assert_time_equals_literal(animation.currentTime, 50 * MS_PER_SEC);
+
+  await animation.ready;
+
+  // As above, check that the currentTime is not calculated by simply
+  // substituting in the updated playbackRate without updating the startTime.
+  assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC,
+    'Reducing the playback rate should not change the current time ' +
+    'of a play-pending animation');
+}, 'Updating the playback rate on a play-pending animation maintains'
+   + ' the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  await animation.ready;
+
+  animation.pause();
+  animation.updatePlaybackRate(0.5);
+
+  assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC);
+}, 'Updating the playback rate on a pause-pending animation maintains'
+   + ' the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+
+  animation.updatePlaybackRate(2);
+  animation.updatePlaybackRate(3);
+  animation.updatePlaybackRate(4);
+
+  assert_equals(animation.playbackRate, 1);
+  await animation.ready;
+
+  assert_equals(animation.playbackRate, 4);
+}, 'If a pending playback rate is set multiple times, the latest wins');
+
+test(t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.cancel();
+
+  animation.updatePlaybackRate(2);
+  assert_equals(animation.playbackRate, 2);
+  assert_false(animation.pending);
+}, 'In the idle state, the playback rate is applied immediately');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.pause();
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  assert_equals(animation.playbackRate, 2);
+  assert_false(animation.pending);
+}, 'In the paused state, the playback rate is applied immediately');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.finish();
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+
+  animation.updatePlaybackRate(2);
+  assert_equals(animation.playbackRate, 2);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+}, 'Updating the playback rate on a finished animation maintains'
+   + ' the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.finish();
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+
+  animation.updatePlaybackRate(0);
+  assert_equals(animation.playbackRate, 0);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+}, 'Updating the playback rate to zero on a finished animation maintains'
+   + ' the current time');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/set-the-animation-start-time.html b/web-animations/timing-model/animations/set-the-animation-start-time.html
deleted file mode 100644
index 48071c0..0000000
--- a/web-animations/timing-model/animations/set-the-animation-start-time.html
+++ /dev/null
@@ -1,200 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Set the animation start time</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#set-the-animation-start-time">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-test(t => {
-  // It should only be possible to set *either* the start time or the current
-  // time for an animation that does not have an active timeline.
-
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-
-  assert_equals(animation.currentTime, null, 'Intial current time');
-  assert_equals(animation.startTime, null, 'Intial start time');
-
-  animation.currentTime = 1000;
-  assert_equals(animation.currentTime, 1000,
-                'Setting the current time succeeds');
-  assert_equals(animation.startTime, null,
-                'Start time remains null after setting current time');
-
-  animation.startTime = 1000;
-  assert_equals(animation.startTime, 1000,
-                'Setting the start time succeeds');
-  assert_equals(animation.currentTime, null,
-                'Setting the start time clears the current time');
-
-  animation.startTime = null;
-  assert_equals(animation.startTime, null,
-                'Setting the start time to an unresolved time succeeds');
-  assert_equals(animation.currentTime, null, 'The current time is unaffected');
-
-}, 'Setting the start time of an animation without an active timeline');
-
-test(t => {
-  // Setting an unresolved start time on an animation without an active
-  // timeline should not clear the current time.
-
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-
-  assert_equals(animation.currentTime, null, 'Intial current time');
-  assert_equals(animation.startTime, null, 'Intial start time');
-
-  animation.currentTime = 1000;
-  assert_equals(animation.currentTime, 1000,
-                'Setting the current time succeeds');
-  assert_equals(animation.startTime, null,
-                'Start time remains null after setting current time');
-
-  animation.startTime = null;
-  assert_equals(animation.startTime, null, 'Start time remains unresolved');
-  assert_equals(animation.currentTime, 1000, 'Current time is unaffected');
-
-}, 'Setting an unresolved start time an animation without an active timeline'
-   + ' does not clear the current time');
-
-   test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  // So long as a hold time is set, querying the current time will return
-  // the hold time.
-
-  // Since the start time is unresolved at this point, setting the current time
-  // will set the hold time
-  animation.currentTime = 1000;
-  assert_equals(animation.currentTime, 1000,
-                'The current time is calculated from the hold time');
-
-  // If we set the start time, however, we should clear the hold time.
-  animation.startTime = document.timeline.currentTime - 2000;
-  assert_times_equal(animation.currentTime, 2000,
-                     'The current time is calculated from the start time,'
-                     + ' not the hold time');
-
-  // Sanity check
-  assert_equals(animation.playState, 'running',
-                'Animation reports it is running after setting a resolved'
-                + ' start time');
-}, 'Setting the start time clears the hold time');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  // Set up a running animation (i.e. both start time and current time
-  // are resolved).
-  animation.startTime = document.timeline.currentTime - 1000;
-  assert_equals(animation.playState, 'running');
-  assert_times_equal(animation.currentTime, 1000,
-                     'Current time is resolved for a running animation')
-
-  // Clear start time
-  animation.startTime = null;
-  assert_times_equal(animation.currentTime, 1000,
-                     'Hold time is set after start time is made unresolved');
-  assert_equals(animation.playState, 'paused',
-                'Animation reports it is paused after setting an unresolved'
-                + ' start time');
-}, 'Setting an unresolved start time sets the hold time');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  let readyPromiseCallbackCalled = false;
-  animation.ready.then(() => { readyPromiseCallbackCalled = true; } );
-
-  // Put the animation in the play-pending state
-  animation.play();
-
-  // Sanity check
-  assert_equals(animation.playState, 'pending',
-                'Animation is in play-pending state');
-
-  // Setting the start time should resolve the 'ready' promise, i.e.
-  // it should schedule a microtask to run the promise callbacks.
-  animation.startTime = document.timeline.currentTime;
-  assert_false(readyPromiseCallbackCalled,
-               'Ready promise callback is not called synchronously');
-
-  // If we schedule another microtask then it should run immediately after
-  // the ready promise resolution microtask.
-  return Promise.resolve().then(() => {
-    assert_true(readyPromiseCallbackCalled,
-                'Ready promise callback called after setting startTime');
-  });
-}, 'Setting the start time resolves a pending ready promise');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  let readyPromiseCallbackCalled = false;
-  animation.ready.then(() => { readyPromiseCallbackCalled = true; } );
-
-  // Put the animation in the pause-pending state
-  animation.startTime = document.timeline.currentTime;
-  animation.pause();
-
-  // Sanity check
-  assert_equals(animation.playState, 'pending',
-                'Animation is in pause-pending state');
-
-  // Setting the start time should resolve the 'ready' promise although
-  // the resolution callbacks when be run in a separate microtask.
-  animation.startTime = null;
-  assert_false(readyPromiseCallbackCalled,
-               'Ready promise callback is not called synchronously');
-
-  return Promise.resolve().then(() => {
-    assert_true(readyPromiseCallbackCalled,
-                'Ready promise callback called after setting startTime');
-  });
-}, 'Setting the start time resolves a pending pause task');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-
-  // Set start time such that the current time is past the end time
-  animation.startTime = document.timeline.currentTime
-                        - 110 * MS_PER_SEC;
-  assert_equals(animation.playState, 'finished',
-                'Seeked to finished state using the startTime');
-
-  // If the 'did seek' flag is true, the current time should be greater than
-  // the effect end.
-  assert_greater_than(animation.currentTime,
-                      animation.effect.getComputedTiming().endTime,
-                      'Setting the start time updated the finished state with'
-                      + ' the \'did seek\' flag set to true');
-
-  // Furthermore, that time should persist if we have correctly updated
-  // the hold time
-  const finishedCurrentTime = animation.currentTime;
-  return waitForAnimationFrames(1).then(() => {
-    assert_equals(animation.currentTime, finishedCurrentTime,
-                  'Current time does not change after seeking past the effect'
-                  + ' end time by setting the current time');
-  });
-}, 'Setting the start time updates the finished state');
-
-</script>
-</body>
diff --git a/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html b/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html
deleted file mode 100644
index 8f835cc..0000000
--- a/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html
+++ /dev/null
@@ -1,95 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Setting the target effect</title>
-<link rel='help' href='https://w3c.github.io/web-animations/#setting-the-target-effect'>
-<script src='/resources/testharness.js'></script>
-<script src='/resources/testharnessreport.js'></script>
-<script src='../../testcommon.js'></script>
-<body>
-<div id='log'></div>
-<script>
-'use strict';
-
-promise_test(t => {
-  const anim = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] },
-                                    100 * MS_PER_SEC);
-  assert_equals(anim.playState, 'pending');
-
-  const retPromise = anim.ready.then(() => {
-    assert_unreached('ready promise is fulfilled');
-  }).catch(err => {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-  });
-
-  anim.effect = null;
-  assert_equals(anim.playState, 'paused');
-
-  return retPromise;
-}, 'If new effect is null and old effect is not null, we reset the pending ' +
-   'tasks and ready promise is rejected');
-
-promise_test(t => {
-  const anim = new Animation();
-  anim.pause();
-  assert_equals(anim.playState, 'pending');
-
-  anim.effect = new KeyframeEffectReadOnly(createDiv(t),
-                                           { marginLeft: [ '0px', '100px' ] },
-                                           100 * MS_PER_SEC);
-  assert_equals(anim.playState, 'pending');
-
-  return anim.ready.then(() => {
-    assert_equals(anim.playState, 'paused');
-  });
-}, 'If animation has a pending pause task, reschedule that task to run ' +
-   'as soon as animation is ready.');
-
-promise_test(t => {
-  const anim = new Animation();
-  anim.play();
-  assert_equals(anim.playState, 'pending');
-
-  anim.effect = new KeyframeEffectReadOnly(createDiv(t),
-                                           { marginLeft: [ '0px', '100px' ] },
-                                           100 * MS_PER_SEC);
-  assert_equals(anim.playState, 'pending');
-
-  return anim.ready.then(() => {
-    assert_equals(anim.playState, 'running');
-  });
-}, 'If animation has a pending play task, reschedule that task to run ' +
-   'as soon as animation is ready to play new effect.');
-
-promise_test(t => {
-  const animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] },
-                                     100 * MS_PER_SEC);
-  const animB = new Animation();
-
-  return animA.ready.then(() => {
-    animB.effect = animA.effect;
-    assert_equals(animA.effect, null);
-    assert_equals(animA.playState, 'finished');
-  });
-}, 'When setting the effect of an animation to the effect of an existing ' +
-   'animation, the existing animation\'s target effect should be set to null.');
-
-test(t => {
-  const animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] },
-                                     100 * MS_PER_SEC);
-  const animB = new Animation();
-  const effect = animA.effect;
-  animA.currentTime = 50 * MS_PER_SEC;
-  animB.currentTime = 20 * MS_PER_SEC;
-  assert_equals(effect.getComputedTiming().progress, 0.5,
-                'Original timing comes from first animation');
-  animB.effect = effect;
-  assert_equals(effect.getComputedTiming().progress, 0.2,
-                'After setting the effect on a different animation, ' +
-                'it uses the new animation\'s timing');
-}, 'After setting the target effect of animation to the target effect of an ' +
-   'existing animation, the target effect\'s timing is updated to reflect ' +
-   'the current time of the new animation.');
-
-</script>
-</body>
diff --git a/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html b/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html
deleted file mode 100644
index 63662fd..0000000
--- a/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html
+++ /dev/null
@@ -1,276 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Setting the timeline</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#setting-the-timeline">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<body>
-<div id="log"></div>
-<script>
-'use strict';
-
-// ---------------------------------------------------------------------
-//
-// Tests from no timeline to timeline
-//
-// ---------------------------------------------------------------------
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.currentTime = 50 * MS_PER_SEC;
-  assert_equals(animation.playState, 'paused');
-
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'paused');
-  assert_times_equal(animation.currentTime, 50 * MS_PER_SEC);
-}, 'After setting timeline on paused animation it is still paused');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.currentTime = 200 * MS_PER_SEC;
-  assert_equals(animation.playState, 'paused');
-
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'paused');
-  assert_times_equal(animation.currentTime, 200 * MS_PER_SEC);
-}, 'After setting timeline on animation paused outside active interval'
-   + ' it is still paused');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  assert_equals(animation.playState, 'idle');
-
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'idle');
-}, 'After setting timeline on an idle animation without a start time'
-   + ' it is still idle');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.startTime = document.timeline.currentTime;
-  assert_equals(animation.playState, 'idle');
-
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'running');
-}, 'After setting timeline on an idle animation with a start time'
-   + ' it is running');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.startTime = document.timeline.currentTime - 200 * MS_PER_SEC;
-  assert_equals(animation.playState, 'idle');
-
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'finished');
-}, 'After setting timeline on an idle animation with a sufficiently ancient'
-   + ' start time it is finished');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.play();
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'pending');
-}, 'After setting timeline on a play-pending animation it is still pending');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.play();
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = document.timeline;
-
-  return animation.ready.then(() => {
-    assert_equals(animation.playState, 'running');
-  });
-}, 'After setting timeline on a play-pending animation it begins playing'
-   + ' after pending');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.startTime = document.timeline.currentTime;
-  animation.pause();
-  animation.timeline = null;
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'pending');
-}, 'After setting timeline on a pause-pending animation it is still pending');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  null);
-  animation.startTime = document.timeline.currentTime;
-  animation.pause();
-  animation.timeline = null;
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = document.timeline;
-
-  return animation.ready.then(() => {
-    assert_equals(animation.playState, 'paused');
-  });
-}, 'After setting timeline on a pause-pending animation it becomes paused'
-   + ' after pending');
-
-// ---------------------------------------------------------------------
-//
-// Tests from timeline to no timeline
-//
-// ---------------------------------------------------------------------
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-  animation.currentTime = 50 * MS_PER_SEC;
-  assert_equals(animation.playState, 'paused');
-
-  animation.timeline = null;
-
-  assert_equals(animation.playState, 'paused');
-  assert_times_equal(animation.currentTime, 50 * MS_PER_SEC);
-}, 'After clearing timeline on paused animation it is still paused');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-  const initialStartTime = document.timeline.currentTime - 200 * MS_PER_SEC;
-  animation.startTime = initialStartTime;
-  assert_equals(animation.playState, 'finished');
-
-  animation.timeline = null;
-
-  assert_equals(animation.playState, 'idle');
-  assert_times_equal(animation.startTime, initialStartTime);
-}, 'After clearing timeline on finished animation it is idle');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-  const initialStartTime = document.timeline.currentTime - 50 * MS_PER_SEC;
-  animation.startTime = initialStartTime;
-  assert_equals(animation.playState, 'running');
-
-  animation.timeline = null;
-
-  assert_equals(animation.playState, 'idle');
-  assert_times_equal(animation.startTime, initialStartTime);
-}, 'After clearing timeline on running animation it is idle');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-  assert_equals(animation.playState, 'idle');
-
-  animation.timeline = null;
-
-  assert_equals(animation.playState, 'idle');
-  assert_equals(animation.startTime, null);
-}, 'After clearing timeline on idle animation it is still idle');
-
-test(t => {
-  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = null;
-
-  assert_equals(animation.playState, 'pending');
-}, 'After clearing timeline on play-pending animation it is still pending');
-
-promise_test(t => {
-  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = null;
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'pending');
-  return animation.ready.then(() => {
-    assert_equals(animation.playState, 'running');
-  });
-}, 'After clearing and re-setting timeline on play-pending animation it'
-   + ' begins to play');
-
-test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-  animation.startTime = document.timeline.currentTime;
-  animation.pause();
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = null;
-
-  assert_equals(animation.playState, 'pending');
-}, 'After clearing timeline on a pause-pending animation it is still pending');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-  animation.startTime = document.timeline.currentTime;
-  animation.pause();
-  assert_equals(animation.playState, 'pending');
-
-  animation.timeline = null;
-  animation.timeline = document.timeline;
-
-  assert_equals(animation.playState, 'pending');
-  return animation.ready.then(() => {
-    assert_equals(animation.playState, 'paused');
-  });
-}, 'After clearing and re-setting timeline on a pause-pending animation it'
-   + ' becomes paused');
-
-promise_test(t => {
-  const animation =
-    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
-                  document.timeline);
-  const initialStartTime = document.timeline.currentTime - 50 * MS_PER_SEC;
-  animation.startTime = initialStartTime;
-  animation.pause();
-  animation.play();
-
-  animation.timeline = null;
-  animation.timeline = document.timeline;
-  assert_equals(animation.playState, 'pending');
-
-  return animation.ready.then(() => {
-    assert_equals(animation.playState, 'running');
-    assert_times_equal(animation.startTime, initialStartTime);
-  });
-}, 'After clearing and re-setting timeline on an animation in the middle of'
-   + ' an aborted pause, it continues playing using the same start time');
-
-</script>
-</body>
diff --git a/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html b/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html
new file mode 100644
index 0000000..b1fc43e
--- /dev/null
+++ b/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Setting the current time of an animation</title>
+<link rel="help"
+  href="https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../../testcommon.js'></script>
+<body>
+<div id='log'></div>
+<script>
+'use strict';
+
+test(t => {
+  const anim = new Animation();
+  assert_equals(anim.playState, 'idle');
+  assert_equals(anim.currentTime, null);
+
+  // This should not throw because the currentTime is already null.
+  anim.currentTime = null;
+}, 'Setting the current time of a pending animation to unresolved does not'
+   + ' throw a TypeError');
+
+promise_test(async t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await anim.ready;
+
+  assert_greater_than_equal(anim.currentTime, 0);
+  assert_throws({ name: 'TypeError' }, () => {
+    anim.currentTime = null;
+  });
+}, 'Setting the current time of a playing animation to unresolved throws a'
+   + ' TypeError');
+
+promise_test(async t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await anim.ready;
+  anim.pause();
+
+  assert_greater_than_equal(anim.currentTime, 0);
+  assert_throws({ name: 'TypeError' }, () => {
+    anim.currentTime = null;
+  });
+}, 'Setting the current time of a paused animation to unresolved throws a'
+   + ' TypeError');
+
+promise_test(async t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await anim.ready;
+  anim.pause();
+
+  // We should be pause-pending now
+  assert_true(anim.pending);
+  assert_equals(anim.playState, 'paused');
+
+  // Apply a pending playback rate
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+
+  // Setting the current time should apply the pending playback rate
+  anim.currentTime = 50 * MS_PER_SEC;
+  assert_equals(anim.playbackRate, 2);
+  assert_false(anim.pending);
+
+  // Sanity check that the current time is preserved
+  assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC);
+}, 'Setting the current time of a pausing animation applies a pending playback'
+   + ' rate');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html b/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
new file mode 100644
index 0000000..9d07d53
--- /dev/null
+++ b/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Setting the playback rate of an animation</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-playback-rate-of-an-animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.playbackRate = 2;
+  await animation.ready;
+
+  const previousAnimationCurrentTime = animation.currentTime;
+  const previousTimelineCurrentTime = animation.timeline.currentTime;
+
+  await waitForAnimationFrames(1);
+
+  const animationCurrentTimeDifference =
+    animation.currentTime - previousAnimationCurrentTime;
+  const timelineCurrentTimeDifference =
+    animation.timeline.currentTime - previousTimelineCurrentTime;
+
+  assert_times_equal(
+    animationCurrentTimeDifference,
+    timelineCurrentTimeDifference * animation.playbackRate,
+    'The current time should increase two times faster than timeline'
+  );
+}, 'The playback rate affects the rate of progress of the current time');
+
+test(t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.playbackRate = 2;
+  assert_time_equals_literal(animation.currentTime, 50 * MS_PER_SEC);
+}, 'Setting the playback rate while play-pending preserves the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  await animation.ready;
+  animation.playbackRate = 2;
+  assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC);
+  assert_less_than(animation.currentTime, 100 * MS_PER_SEC);
+}, 'Setting the playback rate while playing preserves the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.updatePlaybackRate(2);
+  animation.playbackRate = 1;
+  await animation.ready;
+  assert_equals(animation.playbackRate, 1);
+}, 'Setting the playback rate should clear any pending playback rate');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html b/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html
new file mode 100644
index 0000000..2c4922b
--- /dev/null
+++ b/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html
@@ -0,0 +1,247 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Setting the start time of an animation</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  // It should only be possible to set *either* the start time or the current
+  // time for an animation that does not have an active timeline.
+
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+
+  assert_equals(animation.currentTime, null, 'Intial current time');
+  assert_equals(animation.startTime, null, 'Intial start time');
+
+  animation.currentTime = 1000;
+  assert_equals(animation.currentTime, 1000,
+                'Setting the current time succeeds');
+  assert_equals(animation.startTime, null,
+                'Start time remains null after setting current time');
+
+  animation.startTime = 1000;
+  assert_equals(animation.startTime, 1000,
+                'Setting the start time succeeds');
+  assert_equals(animation.currentTime, null,
+                'Setting the start time clears the current time');
+
+  animation.startTime = null;
+  assert_equals(animation.startTime, null,
+                'Setting the start time to an unresolved time succeeds');
+  assert_equals(animation.currentTime, null, 'The current time is unaffected');
+
+}, 'Setting the start time of an animation without an active timeline');
+
+test(t => {
+  // Setting an unresolved start time on an animation without an active
+  // timeline should not clear the current time.
+
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+
+  assert_equals(animation.currentTime, null, 'Intial current time');
+  assert_equals(animation.startTime, null, 'Intial start time');
+
+  animation.currentTime = 1000;
+  assert_equals(animation.currentTime, 1000,
+                'Setting the current time succeeds');
+  assert_equals(animation.startTime, null,
+                'Start time remains null after setting current time');
+
+  animation.startTime = null;
+  assert_equals(animation.startTime, null, 'Start time remains unresolved');
+  assert_equals(animation.currentTime, 1000, 'Current time is unaffected');
+
+}, 'Setting an unresolved start time an animation without an active timeline'
+   + ' does not clear the current time');
+
+   test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  // So long as a hold time is set, querying the current time will return
+  // the hold time.
+
+  // Since the start time is unresolved at this point, setting the current time
+  // will set the hold time
+  animation.currentTime = 1000;
+  assert_equals(animation.currentTime, 1000,
+                'The current time is calculated from the hold time');
+
+  // If we set the start time, however, we should clear the hold time.
+  animation.startTime = document.timeline.currentTime - 2000;
+  assert_time_equals_literal(animation.currentTime, 2000,
+                             'The current time is calculated from the start'
+                             + ' time, not the hold time');
+
+  // Sanity check
+  assert_equals(animation.playState, 'running',
+                'Animation reports it is running after setting a resolved'
+                + ' start time');
+}, 'Setting the start time clears the hold time');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  // Set up a running animation (i.e. both start time and current time
+  // are resolved).
+  animation.startTime = document.timeline.currentTime - 1000;
+  assert_equals(animation.playState, 'running');
+  assert_time_equals_literal(animation.currentTime, 1000,
+                             'Current time is resolved for a running animation');
+
+  // Clear start time
+  animation.startTime = null;
+  assert_time_equals_literal(animation.currentTime, 1000,
+                             'Hold time is set after start time is made'
+                             + ' unresolved');
+  assert_equals(animation.playState, 'paused',
+                'Animation reports it is paused after setting an unresolved'
+                + ' start time');
+}, 'Setting an unresolved start time sets the hold time');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  let readyPromiseCallbackCalled = false;
+  animation.ready.then(() => { readyPromiseCallbackCalled = true; } );
+
+  // Put the animation in the play-pending state
+  animation.play();
+
+  // Sanity check
+  assert_true(animation.pending && animation.playState === 'running',
+              'Animation is in play-pending state');
+
+  // Setting the start time should resolve the 'ready' promise, i.e.
+  // it should schedule a microtask to run the promise callbacks.
+  animation.startTime = document.timeline.currentTime;
+  assert_false(readyPromiseCallbackCalled,
+               'Ready promise callback is not called synchronously');
+
+  // If we schedule another microtask then it should run immediately after
+  // the ready promise resolution microtask.
+  await Promise.resolve();
+  assert_true(readyPromiseCallbackCalled,
+              'Ready promise callback called after setting startTime');
+}, 'Setting the start time resolves a pending ready promise');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  let readyPromiseCallbackCalled = false;
+  animation.ready.then(() => { readyPromiseCallbackCalled = true; } );
+
+  // Put the animation in the pause-pending state
+  animation.startTime = document.timeline.currentTime;
+  animation.pause();
+
+  // Sanity check
+  assert_true(animation.pending && animation.playState === 'paused',
+              'Animation is in pause-pending state');
+
+  // Setting the start time should resolve the 'ready' promise although
+  // the resolution callbacks when be run in a separate microtask.
+  animation.startTime = null;
+  assert_false(readyPromiseCallbackCalled,
+               'Ready promise callback is not called synchronously');
+
+  await Promise.resolve();
+  assert_true(readyPromiseCallbackCalled,
+              'Ready promise callback called after setting startTime');
+}, 'Setting the start time resolves a pending pause task');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  // Set start time such that the current time is past the end time
+  animation.startTime = document.timeline.currentTime
+                        - 110 * MS_PER_SEC;
+  assert_equals(animation.playState, 'finished',
+                'Seeked to finished state using the startTime');
+
+  // If the 'did seek' flag is true, the current time should be greater than
+  // the effect end.
+  assert_greater_than(animation.currentTime,
+                      animation.effect.getComputedTiming().endTime,
+                      'Setting the start time updated the finished state with'
+                      + ' the \'did seek\' flag set to true');
+
+  // Furthermore, that time should persist if we have correctly updated
+  // the hold time
+  const finishedCurrentTime = animation.currentTime;
+  await waitForAnimationFrames(1);
+  assert_equals(animation.currentTime, finishedCurrentTime,
+                'Current time does not change after seeking past the effect'
+                + ' end time by setting the current time');
+}, 'Setting the start time updates the finished state');
+
+promise_test(async t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+
+  // We should be play-pending now
+  assert_true(anim.pending);
+  assert_equals(anim.playState, 'running');
+
+  // Apply a pending playback rate
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+  assert_true(anim.pending);
+
+  // Setting the start time should apply the pending playback rate
+  anim.startTime = anim.timeline.currentTime - 25 * MS_PER_SEC;
+  assert_equals(anim.playbackRate, 2);
+  assert_false(anim.pending);
+
+  // Sanity check that the start time is preserved and current time is
+  // calculated using the new playback rate
+  assert_times_equal(anim.startTime,
+                     anim.timeline.currentTime - 25 * MS_PER_SEC);
+  assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC);
+}, 'Setting the start time of a play-pending animation applies a pending playback rate');
+
+promise_test(async t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await anim.ready;
+
+  // We should be running now
+  assert_false(anim.pending);
+  assert_equals(anim.playState, 'running');
+
+  // Apply a pending playback rate
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+  assert_true(anim.pending);
+
+  // Setting the start time should apply the pending playback rate
+  anim.startTime = anim.timeline.currentTime - 25 * MS_PER_SEC;
+  assert_equals(anim.playbackRate, 2);
+  assert_false(anim.pending);
+
+  // Sanity check that the start time is preserved and current time is
+  // calculated using the new playback rate
+  assert_times_equal(anim.startTime,
+                     anim.timeline.currentTime - 25 * MS_PER_SEC);
+  assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC);
+}, 'Setting the start time of a playing animation applies a pending playback rate');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html b/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
new file mode 100644
index 0000000..daa73f5
--- /dev/null
+++ b/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Setting the target effect of an animation</title>
+<link rel='help' href='https://drafts.csswg.org/web-animations/#setting-the-target-effect'>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../../testcommon.js'></script>
+<body>
+<div id='log'></div>
+<script>
+'use strict';
+
+promise_test(t => {
+  const anim = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] },
+                                    100 * MS_PER_SEC);
+  assert_true(anim.pending);
+
+  const retPromise = anim.ready.then(() => {
+    assert_unreached('ready promise is fulfilled');
+  }).catch(err => {
+    assert_equals(err.name, 'AbortError',
+                  'ready promise is rejected with AbortError');
+  });
+
+  anim.effect = null;
+  // This is a bit odd, see: https://github.com/w3c/web-animations/issues/207
+  assert_equals(anim.playState, 'paused');
+  assert_false(anim.pending);
+
+  return retPromise;
+}, 'If new effect is null and old effect is not null, we reset the pending ' +
+   'tasks and ready promise is rejected');
+
+promise_test(async t => {
+  const anim = new Animation();
+  anim.pause();
+  assert_true(anim.pending);
+
+  anim.effect = new KeyframeEffect(createDiv(t),
+                                   { marginLeft: [ '0px', '100px' ] },
+                                   100 * MS_PER_SEC);
+  assert_true(anim.pending);
+  await anim.ready;
+
+  assert_false(anim.pending);
+  assert_equals(anim.playState, 'paused');
+}, 'If animation has a pending pause task, reschedule that task to run ' +
+   'as soon as animation is ready.');
+
+promise_test(async t => {
+  const anim = new Animation();
+  anim.play();
+  assert_true(anim.pending);
+
+  anim.effect = new KeyframeEffect(createDiv(t),
+                                   { marginLeft: [ '0px', '100px' ] },
+                                   100 * MS_PER_SEC);
+  assert_true(anim.pending);
+  await anim.ready;
+
+  assert_false(anim.pending);
+  assert_equals(anim.playState, 'running');
+}, 'If animation has a pending play task, reschedule that task to run ' +
+   'as soon as animation is ready to play new effect.');
+
+promise_test(async t => {
+  const animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] },
+                                     100 * MS_PER_SEC);
+  const animB = new Animation();
+
+  await animA.ready;
+
+  animB.effect = animA.effect;
+  assert_equals(animA.effect, null);
+  assert_equals(animA.playState, 'finished');
+}, 'When setting the effect of an animation to the effect of an existing ' +
+   'animation, the existing animation\'s target effect should be set to null.');
+
+test(t => {
+  const animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] },
+                                     100 * MS_PER_SEC);
+  const animB = new Animation();
+  const effect = animA.effect;
+  animA.currentTime = 50 * MS_PER_SEC;
+  animB.currentTime = 20 * MS_PER_SEC;
+  assert_equals(effect.getComputedTiming().progress, 0.5,
+                'Original timing comes from first animation');
+  animB.effect = effect;
+  assert_equals(effect.getComputedTiming().progress, 0.2,
+                'After setting the effect on a different animation, ' +
+                'it uses the new animation\'s timing');
+}, 'After setting the target effect of animation to the target effect of an ' +
+   'existing animation, the target effect\'s timing is updated to reflect ' +
+   'the current time of the new animation.');
+
+test(t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+
+  anim.effect = null;
+  assert_equals(anim.playbackRate, 2);
+}, 'Setting the target effect to null causes a pending playback rate to be'
+   + ' applied');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html b/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html
new file mode 100644
index 0000000..dd86175
--- /dev/null
+++ b/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html
@@ -0,0 +1,255 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Setting the timeline of an animation</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-timeline">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+// ---------------------------------------------------------------------
+//
+// Tests from no timeline to timeline
+//
+// ---------------------------------------------------------------------
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+  animation.currentTime = 50 * MS_PER_SEC;
+  assert_equals(animation.playState, 'paused');
+
+  animation.timeline = document.timeline;
+
+  assert_equals(animation.playState, 'paused');
+  assert_time_equals_literal(animation.currentTime, 50 * MS_PER_SEC);
+}, 'After setting timeline on paused animation it is still paused');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+  animation.currentTime = 200 * MS_PER_SEC;
+  assert_equals(animation.playState, 'paused');
+
+  animation.timeline = document.timeline;
+
+  assert_equals(animation.playState, 'paused');
+  assert_time_equals_literal(animation.currentTime, 200 * MS_PER_SEC);
+}, 'After setting timeline on animation paused outside active interval'
+   + ' it is still paused');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+  assert_equals(animation.playState, 'idle');
+
+  animation.timeline = document.timeline;
+
+  assert_equals(animation.playState, 'idle');
+}, 'After setting timeline on an idle animation without a start time'
+   + ' it is still idle');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+  animation.startTime = document.timeline.currentTime;
+  assert_equals(animation.playState, 'idle');
+
+  animation.timeline = document.timeline;
+
+  assert_equals(animation.playState, 'running');
+}, 'After setting timeline on an idle animation with a start time'
+   + ' it is running');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+  animation.startTime = document.timeline.currentTime - 200 * MS_PER_SEC;
+  assert_equals(animation.playState, 'idle');
+
+  animation.timeline = document.timeline;
+
+  assert_equals(animation.playState, 'finished');
+}, 'After setting timeline on an idle animation with a sufficiently ancient'
+   + ' start time it is finished');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+  animation.play();
+  assert_true(animation.pending && animation.playState === 'running',
+              'Animation is initially play-pending');
+
+  animation.timeline = document.timeline;
+
+  assert_true(animation.pending && animation.playState === 'running',
+              'Animation is still play-pending after setting timeline');
+
+  await animation.ready;
+  assert_true(!animation.pending && animation.playState === 'running',
+              'Animation plays after it finishes pending');
+}, 'After setting timeline on a play-pending animation it begins playing'
+   + ' after pending');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+  animation.startTime = document.timeline.currentTime;
+  animation.pause();
+  animation.timeline = null;
+  assert_true(animation.pending && animation.playState === 'paused',
+              'Animation is initially pause-pending');
+
+  animation.timeline = document.timeline;
+
+  assert_true(animation.pending && animation.playState === 'paused',
+              'Animation is still pause-pending after setting timeline');
+
+  await animation.ready;
+  assert_true(!animation.pending && animation.playState === 'paused',
+              'Animation pauses after it finishes pending');
+}, 'After setting timeline on a pause-pending animation it becomes paused'
+   + ' after pending');
+
+// ---------------------------------------------------------------------
+//
+// Tests from timeline to no timeline
+//
+// ---------------------------------------------------------------------
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+  animation.currentTime = 50 * MS_PER_SEC;
+  assert_false(animation.pending);
+  assert_equals(animation.playState, 'paused');
+
+  animation.timeline = null;
+
+  assert_false(animation.pending);
+  assert_equals(animation.playState, 'paused');
+  assert_time_equals_literal(animation.currentTime, 50 * MS_PER_SEC);
+}, 'After clearing timeline on paused animation it is still paused');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+  const initialStartTime = document.timeline.currentTime - 200 * MS_PER_SEC;
+  animation.startTime = initialStartTime;
+  assert_equals(animation.playState, 'finished');
+
+  animation.timeline = null;
+
+  assert_equals(animation.playState, 'idle');
+  assert_times_equal(animation.startTime, initialStartTime);
+}, 'After clearing timeline on finished animation it is idle');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+  const initialStartTime = document.timeline.currentTime - 50 * MS_PER_SEC;
+  animation.startTime = initialStartTime;
+  assert_equals(animation.playState, 'running');
+
+  animation.timeline = null;
+
+  assert_equals(animation.playState, 'idle');
+  assert_times_equal(animation.startTime, initialStartTime);
+}, 'After clearing timeline on running animation it is idle');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+  assert_equals(animation.playState, 'idle');
+
+  animation.timeline = null;
+
+  assert_equals(animation.playState, 'idle');
+  assert_equals(animation.startTime, null);
+}, 'After clearing timeline on idle animation it is still idle');
+
+test(t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  assert_true(animation.pending && animation.playState === 'running');
+
+  animation.timeline = null;
+
+  assert_true(animation.pending && animation.playState === 'running');
+}, 'After clearing timeline on play-pending animation it is still pending');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  assert_true(animation.pending && animation.playState === 'running');
+
+  animation.timeline = null;
+  animation.timeline = document.timeline;
+
+  assert_true(animation.pending && animation.playState === 'running');
+  await animation.ready;
+  assert_true(!animation.pending && animation.playState === 'running');
+}, 'After clearing and re-setting timeline on play-pending animation it'
+   + ' begins to play');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+  animation.startTime = document.timeline.currentTime;
+  animation.pause();
+  assert_true(animation.pending && animation.playState === 'paused');
+
+  animation.timeline = null;
+
+  assert_true(animation.pending && animation.playState === 'paused');
+}, 'After clearing timeline on a pause-pending animation it is still pending');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+  animation.startTime = document.timeline.currentTime;
+  animation.pause();
+  assert_true(animation.pending && animation.playState === 'paused');
+
+  animation.timeline = null;
+  animation.timeline = document.timeline;
+
+  assert_true(animation.pending && animation.playState === 'paused');
+  await animation.ready;
+  assert_true(!animation.pending && animation.playState === 'paused');
+}, 'After clearing and re-setting timeline on a pause-pending animation it'
+   + ' completes pausing');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+  const initialStartTime = document.timeline.currentTime - 50 * MS_PER_SEC;
+  animation.startTime = initialStartTime;
+  animation.pause();
+  animation.play();
+
+  animation.timeline = null;
+  animation.timeline = document.timeline;
+
+  await animation.ready;
+  assert_times_equal(animation.startTime, initialStartTime);
+}, 'After clearing and re-setting timeline on an animation in the middle of'
+   + ' an aborted pause, it continues playing using the same start time');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/the-current-time-of-an-animation.html b/web-animations/timing-model/animations/the-current-time-of-an-animation.html
new file mode 100644
index 0000000..a0e1a11
--- /dev/null
+++ b/web-animations/timing-model/animations/the-current-time-of-an-animation.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>The current time of an animation</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#the-current-time-of-an-animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  animation.play();
+  assert_equals(animation.currentTime, 0,
+    'Current time returns the hold time set when entering the play-pending ' +
+    'state');
+}, 'The current time returns the hold time when set');
+
+promise_test(async t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  null);
+
+  await animation.ready;
+  assert_equals(animation.currentTime, null);
+}, 'The current time is unresolved when there is no associated timeline ' +
+   '(and no hold time is set)');
+
+// FIXME: Test that the current time is unresolved when we have an inactive
+// timeline if we find a way of creating an inactive timeline!
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  animation.startTime = null;
+  assert_equals(animation.currentTime, null);
+}, 'The current time is unresolved when the start time is unresolved ' +
+   '(and no hold time is set)');
+
+test(t => {
+  const animation =
+    new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
+                  document.timeline);
+
+  animation.playbackRate = 2;
+  animation.startTime = document.timeline.currentTime - 25 * MS_PER_SEC;
+
+  const timelineTime = document.timeline.currentTime;
+  const startTime = animation.startTime;
+  const playbackRate = animation.playbackRate;
+  assert_times_equal(animation.currentTime,
+                     (timelineTime - startTime) * playbackRate,
+                     'Animation has a unresolved start time');
+}, 'The current time is calculated from the timeline time, start time and ' +
+   'playback rate');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.playbackRate = 0;
+
+  await animation.ready;
+  await waitForAnimationFrames(1);
+  assert_time_equals_literal(animation.currentTime, 0);
+}, 'The current time does not progress if playback rate is 0');
+
+</script>
+</body>
diff --git a/web-animations/timing-model/animations/updating-the-finished-state.html b/web-animations/timing-model/animations/updating-the-finished-state.html
index c77c4d7..8e02fed 100644
--- a/web-animations/timing-model/animations/updating-the-finished-state.html
+++ b/web-animations/timing-model/animations/updating-the-finished-state.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Updating the finished state</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#updating-the-finished-state">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#updating-the-finished-state">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -20,32 +20,33 @@
 // (Also the start time is resolved and there is pending task)
 
 // Did seek = false
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
 
   // Here and in the following tests we wait until ready resolves as
   // otherwise we don't have a resolved start time. We test the case
   // where the start time is unresolved in a subsequent test.
-  return anim.ready.then(() => {
-    // Seek to 1ms before the target end and then wait 1ms
-    anim.currentTime = 100 * MS_PER_SEC - 1;
-    return waitForAnimationFramesWithDelay(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, 100 * MS_PER_SEC,
-                  'Hold time is set to target end clamping current time');
-  });
+  await anim.ready;
+
+  // Seek to 1ms before the target end and then wait 1ms
+  anim.currentTime = 100 * MS_PER_SEC - 1;
+  await waitForAnimationFramesWithDelay(1);
+
+  assert_equals(anim.currentTime, 100 * MS_PER_SEC,
+                'Hold time is set to target end clamping current time');
 }, 'Updating the finished state when playing past end');
 
 // Did seek = true
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  return anim.ready.then(() => {
-    anim.currentTime = 200 * MS_PER_SEC;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, 200 * MS_PER_SEC,
-                  'Hold time is set so current time should NOT change');
-  });
+
+  await anim.ready;
+
+  anim.currentTime = 200 * MS_PER_SEC;
+  await waitForNextFrame();
+
+  assert_equals(anim.currentTime, 200 * MS_PER_SEC,
+                'Hold time is set so current time should NOT change');
 }, 'Updating the finished state when seeking past end');
 
 // Test current time == target end
@@ -59,15 +60,15 @@
 // (on the subsequent tick the hold time will be set to the same value anyway).
 
 // Did seek = true
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
-  return anim.ready.then(() => {
-    anim.currentTime = 100 * MS_PER_SEC;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, 100 * MS_PER_SEC,
-                  'Hold time is set so current time should NOT change');
-  });
+  await anim.ready;
+
+  anim.currentTime = 100 * MS_PER_SEC;
+  await waitForNextFrame();
+
+  assert_equals(anim.currentTime, 100 * MS_PER_SEC,
+                'Hold time is set so current time should NOT change');
 }, 'Updating the finished state when seeking exactly to end');
 
 
@@ -75,49 +76,51 @@
 // (Also the start time is resolved and there is pending task)
 
 // Did seek = false
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.playbackRate = -1;
   anim.play(); // Make sure animation is not initially finished
-  return anim.ready.then(() => {
-    // Seek to 1ms before 0 and then wait 1ms
-    anim.currentTime = 1;
-    return waitForAnimationFramesWithDelay(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, 0 * MS_PER_SEC,
-                  'Hold time is set to zero clamping current time');
-  });
+
+  await anim.ready;
+
+  // Seek to 1ms before 0 and then wait 1ms
+  anim.currentTime = 1;
+  await waitForAnimationFramesWithDelay(1);
+
+  assert_equals(anim.currentTime, 0 * MS_PER_SEC,
+                'Hold time is set to zero clamping current time');
 }, 'Updating the finished state when playing in reverse past zero');
 
 // Did seek = true
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.playbackRate = -1;
   anim.play();
-  return anim.ready.then(() => {
-    anim.currentTime = -100 * MS_PER_SEC;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, -100 * MS_PER_SEC,
-                  'Hold time is set so current time should NOT change');
-  });
+
+  await anim.ready;
+
+  anim.currentTime = -100 * MS_PER_SEC;
+  await waitForNextFrame();
+
+  assert_equals(anim.currentTime, -100 * MS_PER_SEC,
+                'Hold time is set so current time should NOT change');
 }, 'Updating the finished state when seeking a reversed animation past zero');
 
 // As before, it's difficult to test current time == 0 for did seek = false but
 // it doesn't really matter.
 
 // Did seek = true
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.playbackRate = -1;
   anim.play();
-  return anim.ready.then(() => {
-    anim.currentTime = 0;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, 0 * MS_PER_SEC,
-                  'Hold time is set so current time should NOT change');
-  });
+  await anim.ready;
+
+  anim.currentTime = 0;
+  await waitForNextFrame();
+
+  assert_equals(anim.currentTime, 0 * MS_PER_SEC,
+                'Hold time is set so current time should NOT change');
 }, 'Updating the finished state when seeking a reversed animation exactly'
    + ' to zero');
 
@@ -126,41 +129,43 @@
 // (Also the start time is resolved and there is pending task)
 
 // Did seek = false; playback rate > 0
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
 
   // We want to test that the hold time is cleared so first we need to
   // put the animation in a state where the hold time is set.
   anim.finish();
-  return anim.ready.then(() => {
-    assert_equals(anim.currentTime, 100 * MS_PER_SEC,
-                  'Hold time is initially set');
-    // Then extend the duration so that the hold time is cleared and on
-    // the next tick the current time will increase.
-    anim.effect.timing.duration *= 2;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_greater_than(anim.currentTime, 100 * MS_PER_SEC,
-                        'Hold time is not set so current time should increase');
+  await anim.ready;
+
+  assert_equals(anim.currentTime, 100 * MS_PER_SEC,
+                'Hold time is initially set');
+  // Then extend the duration so that the hold time is cleared and on
+  // the next tick the current time will increase.
+  anim.effect.updateTiming({
+    duration: anim.effect.getComputedTiming().duration * 2,
   });
+  await waitForNextFrame();
+
+  assert_greater_than(anim.currentTime, 100 * MS_PER_SEC,
+                      'Hold time is not set so current time should increase');
 }, 'Updating the finished state when playing before end');
 
 // Did seek = true; playback rate > 0
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.finish();
-  return anim.ready.then(() => {
-    anim.currentTime = 50 * MS_PER_SEC;
-    // When did seek = true, updating the finished state: (i) updates
-    // the animation's start time and (ii) clears the hold time.
-    // We can test both by checking that the currentTime is initially
-    // updated and then increases.
-    assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_greater_than(anim.currentTime, 50 * MS_PER_SEC,
-                        'Hold time is not set so current time should increase');
-  });
+  await anim.ready;
+
+  anim.currentTime = 50 * MS_PER_SEC;
+  // When did seek = true, updating the finished state: (i) updates
+  // the animation's start time and (ii) clears the hold time.
+  // We can test both by checking that the currentTime is initially
+  // updated and then increases.
+  assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
+  await waitForNextFrame();
+
+  assert_greater_than(anim.currentTime, 50 * MS_PER_SEC,
+                      'Hold time is not set so current time should increase');
 }, 'Updating the finished state when seeking before end');
 
 // Did seek = false; playback rate < 0
@@ -177,84 +182,84 @@
 // will set did seek = true).
 
 // Did seek = true; playback rate < 0
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.playbackRate = -1;
-  return anim.ready.then(() => {
-    anim.currentTime = 50 * MS_PER_SEC;
-    assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_less_than(anim.currentTime, 50 * MS_PER_SEC,
-                     'Hold time is not set so current time should decrease');
-  });
+  await anim.ready;
+
+  anim.currentTime = 50 * MS_PER_SEC;
+  assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
+  await waitForNextFrame();
+
+  assert_less_than(anim.currentTime, 50 * MS_PER_SEC,
+                    'Hold time is not set so current time should decrease');
 }, 'Updating the finished state when seeking a reversed animation before end');
 
 // CASE 4: playback rate == 0
 
 // current time < 0
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.playbackRate = 0;
-  return anim.ready.then(() => {
-    anim.currentTime = -100 * MS_PER_SEC;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, -100 * MS_PER_SEC,
-                  'Hold time should not be cleared so current time should'
-                  + ' NOT change');
-  });
+  await anim.ready;
+
+  anim.currentTime = -100 * MS_PER_SEC;
+  await waitForNextFrame();
+
+  assert_equals(anim.currentTime, -100 * MS_PER_SEC,
+                'Hold time should not be cleared so current time should'
+                + ' NOT change');
 }, 'Updating the finished state when playback rate is zero and the'
    + ' current time is less than zero');
 
 // current time < target end
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.playbackRate = 0;
-  return anim.ready.then(() => {
-    anim.currentTime = 50 * MS_PER_SEC;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, 50 * MS_PER_SEC,
-                  'Hold time should not be cleared so current time should'
-                  + ' NOT change');
-  });
+  await anim.ready;
+
+  anim.currentTime = 50 * MS_PER_SEC;
+  await waitForNextFrame();
+
+  assert_equals(anim.currentTime, 50 * MS_PER_SEC,
+                'Hold time should not be cleared so current time should'
+                + ' NOT change');
 }, 'Updating the finished state when playback rate is zero and the'
    + ' current time is less than end');
 
 // current time > target end
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.playbackRate = 0;
-  return anim.ready.then(() => {
-    anim.currentTime = 200 * MS_PER_SEC;
-    return waitForAnimationFrames(1);
-  }).then(() => {
-    assert_equals(anim.currentTime, 200 * MS_PER_SEC,
-                  'Hold time should not be cleared so current time should'
-                  + ' NOT change');
-  });
+  await anim.ready;
+
+  anim.currentTime = 200 * MS_PER_SEC;
+  await waitForNextFrame();
+
+  assert_equals(anim.currentTime, 200 * MS_PER_SEC,
+                'Hold time should not be cleared so current time should'
+                + ' NOT change');
 }, 'Updating the finished state when playback rate is zero and the'
    + ' current time is greater than end');
 
 // CASE 5: current time unresolved
 
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.cancel();
   // Trigger a change that will cause the "update the finished state"
   // procedure to run.
-  anim.effect.timing.duration = 200 * MS_PER_SEC;
+  anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
   assert_equals(anim.currentTime, null,
                 'The animation hold time / start time should not be updated');
   // The "update the finished state" procedure is supposed to run after any
   // change to timing, but just in case an implementation defers that, let's
   // wait a frame and check that the hold time / start time has still not been
   // updated.
-  return waitForAnimationFrames(1).then(() => {
-    assert_equals(anim.currentTime, null,
-                  'The animation hold time / start time should not be updated');
-  });
+  await waitForAnimationFrames(1);
+
+  assert_equals(anim.currentTime, null,
+                'The animation hold time / start time should not be updated');
 }, 'Updating the finished state when current time is unresolved');
 
 // CASE 6: has a pending task
@@ -270,7 +275,7 @@
   // is greater than the target end. At this point the "update the finished
   // state" procedure should run and if we fail to check for a pending task
   // we will set the hold time to the target end, i.e. 50ms.
-  anim.effect.timing.duration = 50 * MS_PER_SEC;
+  anim.effect.updateTiming({ duration: 50 * MS_PER_SEC });
   assert_equals(anim.currentTime, 75 * MS_PER_SEC,
                 'Hold time should not be updated');
 }, 'Updating the finished state when there is a pending task');
@@ -278,7 +283,7 @@
 // CASE 7: start time unresolved
 
 // Did seek = false
-promise_test(t => {
+promise_test(async t => {
   const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
   anim.cancel();
   // Make it so that only the start time is unresolved (to avoid overlapping
@@ -286,13 +291,13 @@
   anim.currentTime = 150 * MS_PER_SEC;
   // Trigger a change that will cause the "update the finished state"
   // procedure to run (did seek = false).
-  anim.effect.timing.duration = 200 * MS_PER_SEC;
-  return waitForAnimationFrames(1).then(() => {
-    assert_equals(anim.currentTime, 150 * MS_PER_SEC,
-                  'The animation hold time should not be updated');
-    assert_equals(anim.startTime, null,
-                  'The animation start time should not be updated');
-  });
+  anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
+  await waitForAnimationFrames(1);
+
+  assert_equals(anim.currentTime, 150 * MS_PER_SEC,
+                'The animation hold time should not be updated');
+  assert_equals(anim.startTime, null,
+                'The animation start time should not be updated');
 }, 'Updating the finished state when start time is unresolved and'
    + ' did seek = false');
 
@@ -337,53 +342,61 @@
 }, 'Finish notification steps don\'t run when the animation seeks to finish'
    + ' and then seeks back again');
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 1);
-  return animation.ready.then(() => {
-    return waitForFinishEventAndPromise(animation);
-  });
+  await animation.ready;
+
+  return waitForFinishEventAndPromise(animation);
 }, 'Finish notification steps run when the animation completes normally');
 
-promise_test(t => {
+promise_test(async t => {
+  const effect = new KeyframeEffect(null, null, 1);
+  const animation = new Animation(effect, document.timeline);
+  animation.play();
+  await animation.ready;
+
+  return waitForFinishEventAndPromise(animation);
+}, 'Finish notification steps run when an animation without a target'
+   + ' effect completes normally');
+
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 1);
-  return animation.ready.then(() => {
-    animation.currentTime = 10;
-    return waitForFinishEventAndPromise(animation);
-  });
+  await animation.ready;
+
+  animation.currentTime = 10;
+  return waitForFinishEventAndPromise(animation);
 }, 'Finish notification steps run when the animation seeks past finish');
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 1);
-  return animation.ready.then(() => {
-    // Register for notifications now since once we seek away from being
-    // finished the 'finished' promise will be replaced.
-    const finishNotificationSteps = waitForFinishEventAndPromise(animation);
-    animation.finish();
-    animation.currentTime = 0;
-    animation.pause();
-    return finishNotificationSteps;
-  });
+  await animation.ready;
+
+  // Register for notifications now since once we seek away from being
+  // finished the 'finished' promise will be replaced.
+  const finishNotificationSteps = waitForFinishEventAndPromise(animation);
+  animation.finish();
+  animation.currentTime = 0;
+  animation.pause();
+  return finishNotificationSteps;
 }, 'Finish notification steps run when the animation completes with .finish(),'
    + ' even if we then seek away');
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 1);
   const initialFinishedPromise = animation.finished;
+  await animation.finished;
 
-  return animation.finished.then(target => {
-    animation.currentTime = 0;
-    assert_not_equals(initialFinishedPromise, animation.finished);
-  });
+  animation.currentTime = 0;
+  assert_not_equals(initialFinishedPromise, animation.finished);
 }, 'Animation finished promise is replaced after seeking back to start');
 
-promise_test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate(null, 1);
   const initialFinishedPromise = animation.finished;
+  await animation.finished;
 
-  return animation.finished.then(target => {
-    animation.play();
-    assert_not_equals(initialFinishedPromise, animation.finished);
-  });
+  animation.play();
+  assert_not_equals(initialFinishedPromise, animation.finished);
 }, 'Animation finished promise is replaced after replaying from start');
 
 async_test(t => {
@@ -406,5 +419,38 @@
   };
 }, 'Animation finish event is fired again after replaying from start');
 
+async_test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: 100000, endDelay: 50000 });
+  anim.onfinish = t.step_func(event => {
+    assert_unreached('finish event should not be fired');
+  });
+
+  anim.ready.then(() => {
+    anim.currentTime = 100000;
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(() => {
+    t.done();
+  }));
+}, 'finish event is not fired at the end of the active interval when the'
+   + ' endDelay has not expired');
+
+async_test(t => {
+  const anim = createDiv(t).animate(null,
+                                    { duration: 100000, endDelay: 30000 });
+  anim.ready.then(() => {
+    anim.currentTime = 110000; // during endDelay
+    anim.onfinish = t.step_func(event => {
+      assert_unreached('onfinish event should not be fired during endDelay');
+    });
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(() => {
+    anim.onfinish = t.step_func(event => {
+      t.done();
+    });
+    anim.currentTime = 130000; // after endTime
+  }));
+}, 'finish event is fired after the endDelay has expired');
+
 </script>
 </body>
diff --git a/web-animations/timing-model/time-transformations/transformed-progress.html b/web-animations/timing-model/time-transformations/transformed-progress.html
index 946babf..839ebe1 100644
--- a/web-animations/timing-model/time-transformations/transformed-progress.html
+++ b/web-animations/timing-model/time-transformations/transformed-progress.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Transformed progress</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#calculating-the-transformed-progress">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#calculating-the-transformed-progress">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/timing-model/timelines/document-timelines.html b/web-animations/timing-model/timelines/document-timelines.html
index 545ec2c..bc3edff 100644
--- a/web-animations/timing-model/timelines/document-timelines.html
+++ b/web-animations/timing-model/timelines/document-timelines.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Document timelines</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#document-timelines">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#document-timelines">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
diff --git a/web-animations/timing-model/timelines/timelines.html b/web-animations/timing-model/timelines/timelines.html
index 0f7db3f..50bb3ac 100644
--- a/web-animations/timing-model/timelines/timelines.html
+++ b/web-animations/timing-model/timelines/timelines.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Timelines</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#timelines">
+<link rel="help" href="https://drafts.csswg.org/web-animations/#timelines">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../testcommon.js"></script>
@@ -71,4 +71,17 @@
 }, 'Timeline time should be the same for all RAF callbacks in an animation'
    + ' frame');
 
+async_test(t => {
+  const div = createDiv(t);
+  const animation = div.animate(null, 100 * MS_PER_SEC);
+
+  animation.ready.then(t.step_func(() => {
+    const readyTimelineTime = document.timeline.currentTime;
+    requestAnimationFrame(t.step_func_done(() => {
+      assert_equals(readyTimelineTime, document.timeline.currentTime,
+                    'There should be a microtask checkpoint');
+    }));
+  }));
+}, 'Performs a microtask checkpoint after updating timelins');
+
 </script>
diff --git a/web-nfc/idlharness.https.html b/web-nfc/idlharness.https.html
index 68de007..d4a95b2 100644
--- a/web-nfc/idlharness.https.html
+++ b/web-nfc/idlharness.https.html
@@ -7,89 +7,19 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
-<style>
-  pre {
-    display: none;
-  }
-</style>
-<div id='log'></div>
-
-<pre id="idl">
-interface Navigator {
-};
-</pre>
-
-<pre id="web-nfc">
-dictionary NFCMessage {
-    sequence<NFCRecord> records;
-    USVString           url;
-};
-
-typedef (DOMString or unrestricted double or ArrayBuffer or Dictionary) NFCRecordData;
-
-dictionary NFCRecord {
-    NFCRecordType recordType;
-    USVString     mediaType;
-    NFCRecordData data;
-};
-
-enum NFCRecordType {
-    "empty",
-    "text",
-    "url",
-    "json",
-    "opaque"
-};
-
-partial interface Navigator {
-    readonly attribute NFC nfc;
-};
-
-typedef (DOMString or ArrayBuffer or NFCMessage) NFCPushMessage;
-
-interface NFC {
-    Promise<void> push(NFCPushMessage message, optional NFCPushOptions options);
-    Promise<void> cancelPush(optional NFCPushTarget target = "any");
-    Promise<long> watch(MessageCallback callback,
-                        optional NFCWatchOptions options);
-    Promise<void> cancelWatch(optional long id);
-};
-
-callback MessageCallback = void (NFCMessage message);
-
-dictionary NFCPushOptions {
-    NFCPushTarget       target = "any";
-    unrestricted double timeout = Infinity;
-    boolean             ignoreRead = true;
-};
-
-enum NFCPushTarget {
-    "tag",
-    "peer",
-    "any"
-};
-
-dictionary NFCWatchOptions {
-    USVString      url = "";
-    NFCRecordType? recordType;
-    USVString      mediaType = "";
-    NFCWatchMode   mode = "web-nfc-only";
-};
-
-enum NFCWatchMode {
-    "web-nfc-only",
-    "any"
-};
-</pre>
-
 <script>
-setup(() => {
-  "use strict";
+"use strict";
+
+promise_test(async () => {
   const idl_array = new IdlArray();
-  idl_array.add_untested_idls(document.getElementById('idl').textContent);
-  idl_array.add_idls(document.getElementById('web-nfc').textContent);
-  idl_array.add_objects({ NFC: ["navigator.nfc"] });
+  const nfc_idl = await fetch("/interfaces/web-nfc.idl").then(r => r.text());
+
+  idl_array.add_untested_idls('interface Navigator {};');
+  idl_array.add_idls(nfc_idl);
+  idl_array.add_objects({
+    Navigator: ['navigator'],
+    NFC: ["navigator.nfc"]
+  });
   idl_array.test();
-  done();
-}, { explicit_done: true });
+}, "Test IDL implementation of Web NFC API");
 </script>
diff --git a/web-nfc/nfc_insecure_context.html b/web-nfc/nfc_insecure_context.html
index 9745fc2..e2a92f9 100644
--- a/web-nfc/nfc_insecure_context.html
+++ b/web-nfc/nfc_insecure_context.html
@@ -6,34 +6,19 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
-
 <h2>Note</h2>
 <ol>
   <li>
     Run test is an insecure context, e.g. http://example.com/
   </li>
 </ol>
-
-<div id="log"></div>
-
 <script>
 
 "use strict";
 
-promise_test(t => {
-  return promise_rejects(t, 'SecurityError', navigator.nfc.push(test_text_data));
-}, "nfc.push should fail with SecurityError in an insecure context.");
-
-promise_test(t => {
-  return promise_rejects(t, 'SecurityError', navigator.nfc.cancelPush());
-}, "nfc.cancelPush should fail with SecurityError in an insecure context.");
-
-promise_test(t => {
-  return promise_rejects(t, 'SecurityError', navigator.nfc.watch(noop));
-}, "nfc.watch should fail with SecurityError in an insecure context.");
-
-promise_test(t => {
-  return promise_rejects(t, 'SecurityError', navigator.nfc.cancelWatch());
-}, "nfc.cancelWatch should fail with SecurityError in an insecure context.");
+test(t => {
+  assert_false(isSecureContext);
+  assert_false('nfc' in navigator);
+}, 'navigator.nfc requires a secure context');
 
 </script>
diff --git a/web-nfc/nfc_push_ArrayBuffer-manual.https.html b/web-nfc/nfc_push_ArrayBuffer-manual.https.html
index 89dbff0..8d12e42 100644
--- a/web-nfc/nfc_push_ArrayBuffer-manual.https.html
+++ b/web-nfc/nfc_push_ArrayBuffer-manual.https.html
@@ -3,11 +3,11 @@
 <title>Web NFC Test: push ArrayBuffer message</title>
 <link rel="author" title="Intel" href="http://www.intel.com"/>
 <link rel="help" href="https://w3c.github.io/web-nfc/"/>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
 <meta name="flags" content="interact">
-<meta name="timeout" content="long">
 
 <p>Tap an NFC tag to the test device with NFC support.</p>
 
diff --git a/web-nfc/nfc_push_DOMString-manual.https.html b/web-nfc/nfc_push_DOMString-manual.https.html
index 9782f67..a6c3df8 100644
--- a/web-nfc/nfc_push_DOMString-manual.https.html
+++ b/web-nfc/nfc_push_DOMString-manual.https.html
@@ -3,11 +3,11 @@
 <title>Web NFC Test: push DOMString message</title>
 <link rel="author" title="Intel" href="http://www.intel.com"/>
 <link rel="help" href="https://w3c.github.io/web-nfc/"/>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
 <meta name="flags" content="interact">
-<meta name="timeout" content="long">
 
 <p>Tap an NFC tag to the test device with NFC support.</p>
 
diff --git a/web-nfc/nfc_recordType_empty-manual.https.html b/web-nfc/nfc_recordType_empty-manual.https.html
index a213537..213d6fe 100644
--- a/web-nfc/nfc_recordType_empty-manual.https.html
+++ b/web-nfc/nfc_recordType_empty-manual.https.html
@@ -3,11 +3,11 @@
 <title>Web NFC Test: push and watch empty records</title>
 <link rel="author" title="Intel" href="http://www.intel.com"/>
 <link rel="help" href="https://w3c.github.io/web-nfc/"/>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
 <meta name="flags" content="interact">
-<meta name="timeout" content="long">
 
 <p>Tap an NFC tag to the test device with NFC support.</p>
 
diff --git a/web-nfc/nfc_recordType_json-manual.https.html b/web-nfc/nfc_recordType_json-manual.https.html
index 883bbe7..6a0129e 100644
--- a/web-nfc/nfc_recordType_json-manual.https.html
+++ b/web-nfc/nfc_recordType_json-manual.https.html
@@ -3,11 +3,11 @@
 <title>Web NFC Test: push and watch json records</title>
 <link rel="author" title="Intel" href="http://www.intel.com"/>
 <link rel="help" href="https://w3c.github.io/web-nfc/"/>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
 <meta name="flags" content="interact">
-<meta name="timeout" content="long">
 
 <p>Tap an NFC tag to the test device with NFC support.</p>
 
diff --git a/web-nfc/nfc_recordType_opaque-manual.https.html b/web-nfc/nfc_recordType_opaque-manual.https.html
index 81e9ec1..8035a30 100644
--- a/web-nfc/nfc_recordType_opaque-manual.https.html
+++ b/web-nfc/nfc_recordType_opaque-manual.https.html
@@ -3,11 +3,11 @@
 <title>Web NFC Test: push and watch opaque records</title>
 <link rel="author" title="Intel" href="http://www.intel.com"/>
 <link rel="help" href="https://w3c.github.io/web-nfc/"/>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
 <meta name="flags" content="interact">
-<meta name="timeout" content="long">
 
 <p>Tap an NFC tag to the test device with NFC support.</p>
 
diff --git a/web-nfc/nfc_recordType_text-manual.https.html b/web-nfc/nfc_recordType_text-manual.https.html
index 279bd66..9b0ee27 100644
--- a/web-nfc/nfc_recordType_text-manual.https.html
+++ b/web-nfc/nfc_recordType_text-manual.https.html
@@ -3,11 +3,11 @@
 <title>Web NFC Test: push and watch text records</title>
 <link rel="author" title="Intel" href="http://www.intel.com"/>
 <link rel="help" href="https://w3c.github.io/web-nfc/"/>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
 <meta name="flags" content="interact">
-<meta name="timeout" content="long">
 
 <p>Tap an NFC tag to the test device with NFC support.</p>
 
diff --git a/web-nfc/nfc_recordType_url-manual.https.html b/web-nfc/nfc_recordType_url-manual.https.html
index 70f9823..b2e1cfe 100644
--- a/web-nfc/nfc_recordType_url-manual.https.html
+++ b/web-nfc/nfc_recordType_url-manual.https.html
@@ -3,11 +3,11 @@
 <title>Web NFC Test: push and watch url records</title>
 <link rel="author" title="Intel" href="http://www.intel.com"/>
 <link rel="help" href="https://w3c.github.io/web-nfc/"/>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/nfc_help.js"></script>
 <meta name="flags" content="interact">
-<meta name="timeout" content="long">
 
 <p>Tap an NFC tag to the test device with NFC support.</p>
 
diff --git a/webaudio/idlharness.https.html b/webaudio/idlharness.https.html
new file mode 100644
index 0000000..09ec2c4
--- /dev/null
+++ b/webaudio/idlharness.https.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAudio IDL tests</title>
+<link rel="help" href="https://webaudio.github.io/web-audio-api/"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const [html, dom, uievents, mediacapture, webaudio] = await Promise.all([
+    // Needed for EventTarget, HTMLMediaElement
+    '/interfaces/html.idl',
+
+    // Needed for Event, EventListener
+    '/interfaces/dom.idl',
+
+    // Needed for MouseEvent
+    '/interfaces/uievents.idl',
+
+    // Needed for MediaStream, MediaStreamTrack
+    '/interfaces/mediacapture-main.idl',
+
+    '/interfaces/web-audio-api.idl'
+  ].map(url => fetch(url).then(response => response.text())));
+
+  const idl_array = new IdlArray();
+
+  // Dependencies of HTML
+  idl_array.add_untested_idls('interface LinkStyle {};');
+  idl_array.add_untested_idls('interface SVGElement {};');
+  idl_array.add_untested_idls('interface WorkletGlobalScope {};');
+  idl_array.add_untested_idls(html);
+  idl_array.add_untested_idls(uievents, { only: [
+    'MouseEvent',
+    'MouseEventInit',
+    'EventModifierInit',
+    'UIEvent',
+    'UIEventInit',
+  ]});
+
+  idl_array.add_untested_idls(dom);
+  idl_array.add_untested_idls(mediacapture);
+  idl_array.add_untested_idls('interface Worklet {};');
+
+  idl_array.add_idls(webaudio);
+
+  const sample_rate = 44100;
+  const context = new AudioContext;
+  const buffer = new AudioBuffer({length: 1, sampleRate: sample_rate});
+  await context.audioWorklet.addModule(
+    'the-audio-api/the-audioworklet-interface/processors/dummy-processor.js');
+  const worklet_node = new AudioWorkletNode(context, 'dummy');
+
+  idl_array.add_objects({
+    BaseAudioContext: [],
+    AudioContext: [context],
+    OfflineAudioContext: [new OfflineAudioContext(1, 1, sample_rate)],
+    OfflineAudioCompletionEvent: [
+      new OfflineAudioCompletionEvent('', {renderedBuffer: buffer})],
+    AudioBuffer: [buffer],
+    AudioNode: [],
+    AudioParam: [new AudioBufferSourceNode(context).playbackRate],
+    AudioScheduledSourceNode: [],
+    AnalyserNode: [new AnalyserNode(context)],
+    AudioBufferSourceNode: [new AudioBufferSourceNode(context)],
+    AudioDestinationNode: [context.destination],
+    AudioListener: [context.listener],
+    AudioProcessingEvent: [new AudioProcessingEvent('', {
+      playbackTime: 0, inputBuffer: buffer, outputBuffer: buffer
+    })],
+    BiquadFilterNode: [new BiquadFilterNode(context)],
+    ChannelMergerNode: [new ChannelMergerNode(context)],
+    ChannelSplitterNode: [new ChannelSplitterNode(context)],
+    ConstantSourceNode: [new ConstantSourceNode(context)],
+    ConvolverNode: [new ConvolverNode(context)],
+    DelayNode: [new DelayNode(context)],
+    DynamicsCompressorNode: [new DynamicsCompressorNode(context)],
+    GainNode: [new GainNode(context)],
+    IIRFilterNode: [
+      new IIRFilterNode(context, {feedforward: [1], feedback: [1]})],
+    MediaElementAudioSourceNode: [
+      new MediaElementAudioSourceNode(context, {mediaElement: new Audio})],
+    MediaStreamAudioDestinationNode: [
+      new MediaStreamAudioDestinationNode(context)],
+    MediaStreamAudioSourceNode: [],
+    MediaStreamTrackAudioSourceNode: [],
+    OscillatorNode: [new OscillatorNode(context)],
+    PannerNode: [new PannerNode(context)],
+    PeriodicWave: [new PeriodicWave(context)],
+    ScriptProcessorNode: [context.createScriptProcessor()],
+    StereoPannerNode: [new StereoPannerNode(context)],
+    WaveShaperNode: [new WaveShaperNode(context)],
+    AudioWorklet: [context.audioWorklet],
+    AudioWorkletGlobalScope: [],
+    AudioParamMap: [worklet_node.parameters],
+    AudioWorkletNode: [worklet_node],
+    AudioWorkletProcessor: [],
+  });
+  idl_array.test();
+
+}, 'Test driver');
+</script>
diff --git a/webaudio/js/helpers.js b/webaudio/js/helpers.js
index 650d25c..fbbfc8e 100644
--- a/webaudio/js/helpers.js
+++ b/webaudio/js/helpers.js
@@ -5,7 +5,7 @@
 function trimEmptyElements(array) {
   var start = 0;
   var end = array.length;
-  
+
   while (start < array.length) {
     if (array[start] !== 0) {
       break;
@@ -115,7 +115,7 @@
  * + skipOfflineContextTests: optional. when true, skips running tests on an offline
  *                            context by circumventing testOnOfflineContext.
  *
- * [0]: http://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests
+ * [0]: https://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests
  */
 function runTest(name)
 {
diff --git a/webaudio/resources/audio-param.js b/webaudio/resources/audio-param.js
new file mode 100644
index 0000000..bc33fe8
--- /dev/null
+++ b/webaudio/resources/audio-param.js
@@ -0,0 +1,44 @@
+// Define functions that implement the formulas for AudioParam automations.
+
+// AudioParam linearRamp value at time t for a linear ramp between (t0, v0) and
+// (t1, v1).  It is assumed that t0 <= t.  Results are undefined otherwise.
+function audioParamLinearRamp(t, v0, t0, v1, t1) {
+  if (t >= t1)
+    return v1;
+  return (v0 + (v1 - v0) * (t - t0) / (t1 - t0))
+}
+
+// AudioParam exponentialRamp value at time t for an exponential ramp between
+// (t0, v0) and (t1, v1). It is assumed that t0 <= t.  Results are undefined
+// otherwise.
+function audioParamExponentialRamp(t, v0, t0, v1, t1) {
+  if (t >= t1)
+    return v1;
+  return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
+}
+
+// AudioParam setTarget value at time t for a setTarget curve starting at (t0,
+// v0) with a final value of vFainal and a time constant of timeConstant.  It is
+// assumed that t0 <= t.  Results are undefined otherwise.
+function audioParamSetTarget(t, v0, t0, vFinal, timeConstant) {
+  return vFinal + (v0 - vFinal) * Math.exp(-(t - t0) / timeConstant);
+}
+
+// AudioParam setValueCurve value at time t for a setValueCurve starting at time
+// t0 with curve, curve, and duration duration.  The sample rate is sampleRate.
+// It is assumed that t0 <= t.
+function audioParamSetValueCurve(t, curve, t0, duration) {
+  if (t > t0 + duration)
+    return curve[curve.length - 1];
+
+  let curvePointsPerSecond = (curve.length - 1) / duration;
+
+  let virtualIndex = (t - t0) * curvePointsPerSecond;
+  let index = Math.floor(virtualIndex);
+
+  let delta = virtualIndex - index;
+
+  let c0 = curve[index];
+  let c1 = curve[Math.min(index + 1, curve.length - 1)];
+  return c0 + (c1 - c0) * delta;
+}
diff --git a/webaudio/resources/audionodeoptions.js b/webaudio/resources/audionodeoptions.js
new file mode 100644
index 0000000..293e8e0
--- /dev/null
+++ b/webaudio/resources/audionodeoptions.js
@@ -0,0 +1,251 @@
+// Test that constructor for the node with name |nodeName| handles the
+// various possible values for channelCount, channelCountMode, and
+// channelInterpretation.
+
+// The |should| parameter is the test function from new |Audit|.
+function testAudioNodeOptions(should, context, nodeName, expectedNodeOptions) {
+  if (expectedNodeOptions === undefined)
+    expectedNodeOptions = {};
+  let node;
+
+  // Test that we can set channelCount and that errors are thrown for
+  // invalid values
+  let testChannelCount = 17;
+  if (expectedNodeOptions.channelCount) {
+    testChannelCount = expectedNodeOptions.channelCount.value;
+  }
+  should(
+      () => {
+        node = new window[nodeName](
+            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
+              channelCount: testChannelCount
+            }));
+      },
+      'new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '}}')
+      .notThrow();
+  should(node.channelCount, 'node.channelCount').beEqualTo(testChannelCount);
+
+  if (expectedNodeOptions.channelCount &&
+      expectedNodeOptions.channelCount.isFixed) {
+    // The channel count is fixed.  Verify that we throw an error if
+    // we try to change it. Arbitrarily set the count to be one more
+    // than the expected value.
+    testChannelCount = expectedNodeOptions.channelCount.value + 1;
+    should(
+        () => {
+          node = new window[nodeName](
+              context,
+              Object.assign(
+                  {}, expectedNodeOptions.additionalOptions,
+                  {channelCount: testChannelCount}));
+        },
+        'new ' + nodeName + '(c, {channelCount: ' + testChannelCount + '}}')
+        .throw(expectedNodeOptions.channelCount.errorType || 'TypeError');
+  } else {
+    // The channel count is not fixed.  Try to set the count to invalid
+    // values and make sure an error is thrown.
+    let errorType = 'NotSupportedError';
+
+    [0, 99].forEach(testValue => {
+      should(() => {
+        node = new window[nodeName](
+            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
+              channelCount: testValue
+            }));
+      }, `new ${nodeName}(c, {channelCount: ${testValue}})`).throw(errorType);
+    });
+  }
+
+  // Test channelCountMode
+  let testChannelCountMode = 'max';
+  if (expectedNodeOptions.channelCountMode) {
+    testChannelCountMode = expectedNodeOptions.channelCountMode.value;
+  }
+  should(
+      () => {
+        node = new window[nodeName](
+            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
+              channelCountMode: testChannelCountMode
+            }));
+      },
+      'new ' + nodeName + '(c, {channelCountMode: "' + testChannelCountMode +
+          '"}')
+      .notThrow();
+  should(node.channelCountMode, 'node.channelCountMode')
+      .beEqualTo(testChannelCountMode);
+
+  if (expectedNodeOptions.channelCountMode &&
+      expectedNodeOptions.channelCountMode.isFixed) {
+    // Channel count mode is fixed.  Test setting to something else throws.
+    ['max', 'clamped-max', 'explicit'].forEach(testValue => {
+      if (testValue !== expectedNodeOptions.channelCountMode.value) {
+        should(
+            () => {
+              node = new window[nodeName](
+                  context,
+                  Object.assign(
+                      {}, expectedNodeOptions.additionalOptions,
+                      {channelCountMode: testValue}));
+            },
+            `new ${nodeName}(c, {channelCountMode: "${testValue}"})`)
+            .throw(expectedNodeOptions.channelCountMode.errorType);
+      }
+    });
+  } else {
+    // Mode is not fixed. Verify that we can set the mode to all valid
+    // values, and that we throw for invalid values.
+
+    let testValues = ['max', 'clamped-max', 'explicit'];
+
+    testValues.forEach(testValue => {
+      should(() => {
+        node = new window[nodeName](
+            context, Object.assign({}, expectedNodeOptions.additionalOptions, {
+              channelCountMode: testValue
+            }));
+      }, `new ${nodeName}(c, {channelCountMode: "${testValue}"})`).notThrow();
+      should(
+          node.channelCountMode, 'node.channelCountMode after valid setter')
+          .beEqualTo(testValue);
+
+    });
+
+    should(
+        () => {
+          node = new window[nodeName](
+              context,
+              Object.assign(
+                  {}, expectedNodeOptions.additionalOptions,
+                  {channelCountMode: 'foobar'}));
+        },
+        'new ' + nodeName + '(c, {channelCountMode: "foobar"}')
+        .throw('TypeError');
+    should(node.channelCountMode, 'node.channelCountMode after invalid setter')
+        .beEqualTo(testValues[testValues.length - 1]);
+  }
+
+  // Test channelInterpretation
+  if (expectedNodeOptions.channelInterpretation &&
+      expectedNodeOptions.channelInterpretation.isFixed) {
+    // The channel interpretation is fixed.  Verify that we throw an
+    // error if we try to change it.
+    ['speakers', 'discrete'].forEach(testValue => {
+      if (testValue !== expectedNodeOptions.channelInterpretation.value) {
+        should(
+            () => {
+              node = new window[nodeName](
+                  context,
+                  Object.assign(
+                      {}, expectedNodeOptions.additionOptions,
+                      {channelInterpretation: testValue}));
+            },
+            `new ${nodeName}(c, {channelInterpretation: "${testValue}"})`)
+            .throw(expectedNodeOptions.channelInterpretation.errorType);
+      }
+    });
+  } else {
+    // Channel interpretation is not fixed. Verify that we can set it
+    // to all possible values.
+    should(
+        () => {
+          node = new window[nodeName](
+              context,
+              Object.assign(
+                  {}, expectedNodeOptions.additionalOptions,
+                  {channelInterpretation: 'speakers'}));
+        },
+        'new ' + nodeName + '(c, {channelInterpretation: "speakers"})')
+        .notThrow();
+    should(node.channelInterpretation, 'node.channelInterpretation')
+        .beEqualTo('speakers');
+
+    should(
+        () => {
+          node = new window[nodeName](
+              context,
+              Object.assign(
+                  {}, expectedNodeOptions.additionalOptions,
+                  {channelInterpretation: 'discrete'}));
+        },
+        'new ' + nodeName + '(c, {channelInterpretation: "discrete"})')
+        .notThrow();
+    should(node.channelInterpretation, 'node.channelInterpretation')
+        .beEqualTo('discrete');
+
+    should(
+        () => {
+          node = new window[nodeName](
+              context,
+              Object.assign(
+                  {}, expectedNodeOptions.additionalOptions,
+                  {channelInterpretation: 'foobar'}));
+        },
+        'new ' + nodeName + '(c, {channelInterpretation: "foobar"})')
+        .throw('TypeError');
+    should(
+        node.channelInterpretation,
+        'node.channelInterpretation after invalid setter')
+        .beEqualTo('discrete');
+  }
+}
+
+function initializeContext(should) {
+  let c;
+  should(() => {
+    c = new OfflineAudioContext(1, 1, 48000);
+  }, 'context = new OfflineAudioContext(...)').notThrow();
+
+  return c;
+}
+
+function testInvalidConstructor(should, name, context) {
+  should(() => {
+    new window[name]();
+  }, 'new ' + name + '()').throw('TypeError');
+  should(() => {
+    new window[name](1);
+  }, 'new ' + name + '(1)').throw('TypeError');
+  should(() => {
+    new window[name](context, 42);
+  }, 'new ' + name + '(context, 42)').throw('TypeError');
+}
+
+function testDefaultConstructor(should, name, context, options) {
+  let node;
+
+  let message = options.prefix + ' = new ' + name + '(context';
+  if (options.constructorOptions)
+    message += ', ' + JSON.stringify(options.constructorOptions);
+  message += ')'
+
+  should(() => {
+    node = new window[name](context, options.constructorOptions);
+  }, message).notThrow();
+
+  should(node instanceof window[name], options.prefix + ' instanceof ' + name)
+      .beEqualTo(true);
+  should(node.numberOfInputs, options.prefix + '.numberOfInputs')
+      .beEqualTo(options.numberOfInputs);
+  should(node.numberOfOutputs, options.prefix + '.numberOfOutputs')
+      .beEqualTo(options.numberOfOutputs);
+  should(node.channelCount, options.prefix + '.channelCount')
+      .beEqualTo(options.channelCount);
+  should(node.channelCountMode, options.prefix + '.channelCountMode')
+      .beEqualTo(options.channelCountMode);
+  should(node.channelInterpretation, options.prefix + '.channelInterpretation')
+      .beEqualTo(options.channelInterpretation);
+
+  return node;
+}
+
+function testDefaultAttributes(should, node, prefix, items) {
+  items.forEach((item) => {
+    let attr = node[item.name];
+    if (attr instanceof AudioParam) {
+      should(attr.value, prefix + '.' + item.name + '.value')
+          .beEqualTo(item.value);
+    } else {
+      should(attr, prefix + '.' + item.name).beEqualTo(item.value);
+    }
+  });
+}
diff --git a/webaudio/resources/biquad-filters.js b/webaudio/resources/biquad-filters.js
new file mode 100644
index 0000000..4674363
--- /dev/null
+++ b/webaudio/resources/biquad-filters.js
@@ -0,0 +1,376 @@
+// A biquad filter has a z-transform of
+// H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
+//
+// The formulas for the various filters were taken from
+// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt.
+
+
+// Lowpass filter.
+function createLowpassFilter(freq, q, gain) {
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+
+  if (freq == 1) {
+    // The formula below works, except for roundoff.  When freq = 1,
+    // the filter is just a wire, so hardwire the coefficients.
+    b0 = 1;
+    b1 = 0;
+    b2 = 0;
+    a0 = 1;
+    a1 = 0;
+    a2 = 0;
+  } else {
+    let theta = Math.PI * freq;
+    let alpha = Math.sin(theta) / (2 * Math.pow(10, q / 20));
+    let cosw = Math.cos(theta);
+    let beta = (1 - cosw) / 2;
+
+    b0 = beta;
+    b1 = 2 * beta;
+    b2 = beta;
+    a0 = 1 + alpha;
+    a1 = -2 * cosw;
+    a2 = 1 - alpha;
+  }
+
+  return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+}
+
+function createHighpassFilter(freq, q, gain) {
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+
+  if (freq == 1) {
+    // The filter is 0
+    b0 = 0;
+    b1 = 0;
+    b2 = 0;
+    a0 = 1;
+    a1 = 0;
+    a2 = 0;
+  } else if (freq == 0) {
+    // The filter is 1.  Computation of coefficients below is ok, but
+    // there's a pole at 1 and a zero at 1, so round-off could make
+    // the filter unstable.
+    b0 = 1;
+    b1 = 0;
+    b2 = 0;
+    a0 = 1;
+    a1 = 0;
+    a2 = 0;
+  } else {
+    let theta = Math.PI * freq;
+    let alpha = Math.sin(theta) / (2 * Math.pow(10, q / 20));
+    let cosw = Math.cos(theta);
+    let beta = (1 + cosw) / 2;
+
+    b0 = beta;
+    b1 = -2 * beta;
+    b2 = beta;
+    a0 = 1 + alpha;
+    a1 = -2 * cosw;
+    a2 = 1 - alpha;
+  }
+
+  return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+}
+
+function normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2) {
+  let scale = 1 / a0;
+
+  return {
+    b0: b0 * scale,
+    b1: b1 * scale,
+    b2: b2 * scale,
+    a1: a1 * scale,
+    a2: a2 * scale
+  };
+}
+
+function createBandpassFilter(freq, q, gain) {
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+  let coef;
+
+  if (freq > 0 && freq < 1) {
+    let w0 = Math.PI * freq;
+    if (q > 0) {
+      let alpha = Math.sin(w0) / (2 * q);
+      let k = Math.cos(w0);
+
+      b0 = alpha;
+      b1 = 0;
+      b2 = -alpha;
+      a0 = 1 + alpha;
+      a1 = -2 * k;
+      a2 = 1 - alpha;
+
+      coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+    } else {
+      // q = 0, and frequency is not 0 or 1.  The above formula has a
+      // divide by zero problem.  The limit of the z-transform as q
+      // approaches 0 is 1, so set the filter that way.
+      coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0};
+    }
+  } else {
+    // When freq = 0 or 1, the z-transform is identically 0,
+    // independent of q.
+    coef = { b0: 0, b1: 0, b2: 0, a1: 0, a2: 0 }
+  }
+
+  return coef;
+}
+
+function createLowShelfFilter(freq, q, gain) {
+  // q not used
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+  let coef;
+
+  let S = 1;
+  let A = Math.pow(10, gain / 40);
+
+  if (freq == 1) {
+    // The filter is just a constant gain
+    coef = {b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0};
+  } else if (freq == 0) {
+    // The filter is 1
+    coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0};
+  } else {
+    let w0 = Math.PI * freq;
+    let alpha = 1 / 2 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
+    let k = Math.cos(w0);
+    let k2 = 2 * Math.sqrt(A) * alpha;
+    let Ap1 = A + 1;
+    let Am1 = A - 1;
+
+    b0 = A * (Ap1 - Am1 * k + k2);
+    b1 = 2 * A * (Am1 - Ap1 * k);
+    b2 = A * (Ap1 - Am1 * k - k2);
+    a0 = Ap1 + Am1 * k + k2;
+    a1 = -2 * (Am1 + Ap1 * k);
+    a2 = Ap1 + Am1 * k - k2;
+    coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+  }
+
+  return coef;
+}
+
+function createHighShelfFilter(freq, q, gain) {
+  // q not used
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+  let coef;
+
+  let A = Math.pow(10, gain / 40);
+
+  if (freq == 1) {
+    // When freq = 1, the z-transform is 1
+    coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0};
+  } else if (freq > 0) {
+    let w0 = Math.PI * freq;
+    let S = 1;
+    let alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
+    let k = Math.cos(w0);
+    let k2 = 2 * Math.sqrt(A) * alpha;
+    let Ap1 = A + 1;
+    let Am1 = A - 1;
+
+    b0 = A * (Ap1 + Am1 * k + k2);
+    b1 = -2 * A * (Am1 + Ap1 * k);
+    b2 = A * (Ap1 + Am1 * k - k2);
+    a0 = Ap1 - Am1 * k + k2;
+    a1 = 2 * (Am1 - Ap1 * k);
+    a2 = Ap1 - Am1 * k - k2;
+
+    coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+  } else {
+    // When freq = 0, the filter is just a gain
+    coef = {b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0};
+  }
+
+  return coef;
+}
+
+function createPeakingFilter(freq, q, gain) {
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+  let coef;
+
+  let A = Math.pow(10, gain / 40);
+
+  if (freq > 0 && freq < 1) {
+    if (q > 0) {
+      let w0 = Math.PI * freq;
+      let alpha = Math.sin(w0) / (2 * q);
+      let k = Math.cos(w0);
+
+      b0 = 1 + alpha * A;
+      b1 = -2 * k;
+      b2 = 1 - alpha * A;
+      a0 = 1 + alpha / A;
+      a1 = -2 * k;
+      a2 = 1 - alpha / A;
+
+      coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+    } else {
+      // q = 0, we have a divide by zero problem in the formulas
+      // above.  But if we look at the z-transform, we see that the
+      // limit as q approaches 0 is A^2.
+      coef = {b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0};
+    }
+  } else {
+    // freq = 0 or 1, the z-transform is 1
+    coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0};
+  }
+
+  return coef;
+}
+
+function createNotchFilter(freq, q, gain) {
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+  let coef;
+
+  if (freq > 0 && freq < 1) {
+    if (q > 0) {
+      let w0 = Math.PI * freq;
+      let alpha = Math.sin(w0) / (2 * q);
+      let k = Math.cos(w0);
+
+      b0 = 1;
+      b1 = -2 * k;
+      b2 = 1;
+      a0 = 1 + alpha;
+      a1 = -2 * k;
+      a2 = 1 - alpha;
+      coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+    } else {
+      // When q = 0, we get a divide by zero above.  The limit of the
+      // z-transform as q approaches 0 is 0, so set the coefficients
+      // appropriately.
+      coef = {b0: 0, b1: 0, b2: 0, a1: 0, a2: 0};
+    }
+  } else {
+    // When freq = 0 or 1, the z-transform is 1
+    coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0};
+  }
+
+  return coef;
+}
+
+function createAllpassFilter(freq, q, gain) {
+  let b0;
+  let b1;
+  let b2;
+  let a0;
+  let a1;
+  let a2;
+  let coef;
+
+  if (freq > 0 && freq < 1) {
+    if (q > 0) {
+      let w0 = Math.PI * freq;
+      let alpha = Math.sin(w0) / (2 * q);
+      let k = Math.cos(w0);
+
+      b0 = 1 - alpha;
+      b1 = -2 * k;
+      b2 = 1 + alpha;
+      a0 = 1 + alpha;
+      a1 = -2 * k;
+      a2 = 1 - alpha;
+      coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+    } else {
+      // q = 0
+      coef = {b0: -1, b1: 0, b2: 0, a1: 0, a2: 0};
+    }
+  } else {
+    coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0};
+  }
+
+  return coef;
+}
+
+function filterData(filterCoef, signal, len) {
+  let y = new Array(len);
+  let b0 = filterCoef.b0;
+  let b1 = filterCoef.b1;
+  let b2 = filterCoef.b2;
+  let a1 = filterCoef.a1;
+  let a2 = filterCoef.a2;
+
+  // Prime the pump. (Assumes the signal has length >= 2!)
+  y[0] = b0 * signal[0];
+  y[1] = b0 * signal[1] + b1 * signal[0] - a1 * y[0];
+
+  // Filter all of the signal that we have.
+  for (let k = 2; k < Math.min(signal.length, len); ++k) {
+    y[k] = b0 * signal[k] + b1 * signal[k - 1] + b2 * signal[k - 2] -
+        a1 * y[k - 1] - a2 * y[k - 2];
+  }
+
+  // If we need to filter more, but don't have any signal left,
+  // assume the signal is zero.
+  for (let k = signal.length; k < len; ++k) {
+    y[k] = -a1 * y[k - 1] - a2 * y[k - 2];
+  }
+
+  return y;
+}
+
+// Map the filter type name to a function that computes the filter coefficents
+// for the given filter type.
+let filterCreatorFunction = {
+  'lowpass': createLowpassFilter,
+  'highpass': createHighpassFilter,
+  'bandpass': createBandpassFilter,
+  'lowshelf': createLowShelfFilter,
+  'highshelf': createHighShelfFilter,
+  'peaking': createPeakingFilter,
+  'notch': createNotchFilter,
+  'allpass': createAllpassFilter
+};
+
+let filterTypeName = {
+  'lowpass': 'Lowpass filter',
+  'highpass': 'Highpass filter',
+  'bandpass': 'Bandpass filter',
+  'lowshelf': 'Lowshelf filter',
+  'highshelf': 'Highshelf filter',
+  'peaking': 'Peaking filter',
+  'notch': 'Notch filter',
+  'allpass': 'Allpass filter'
+};
+
+function createFilter(filterType, freq, q, gain) {
+  return filterCreatorFunction[filterType](freq, q, gain);
+}
diff --git a/webaudio/resources/biquad-testing.js b/webaudio/resources/biquad-testing.js
new file mode 100644
index 0000000..7a0b6e6
--- /dev/null
+++ b/webaudio/resources/biquad-testing.js
@@ -0,0 +1,171 @@
+// Globals, to make testing and debugging easier.
+let context;
+let filter;
+let signal;
+let renderedBuffer;
+let renderedData;
+
+let sampleRate = 44100.0;
+let pulseLengthFrames = .1 * sampleRate;
+
+// Maximum allowed error for the test to succeed.  Experimentally determined.
+let maxAllowedError = 5.9e-8;
+
+// This must be large enough so that the filtered result is
+// essentially zero.  See comments for createTestAndRun.
+let timeStep = .1;
+
+// Maximum number of filters we can process (mostly for setting the
+// render length correctly.)
+let maxFilters = 5;
+
+// How long to render.  Must be long enough for all of the filters we
+// want to test.
+let renderLengthSeconds = timeStep * (maxFilters + 1);
+
+let renderLengthSamples = Math.round(renderLengthSeconds * sampleRate);
+
+// Number of filters that will be processed.
+let nFilters;
+
+function createImpulseBuffer(context, length) {
+  let impulse = context.createBuffer(1, length, context.sampleRate);
+  let data = impulse.getChannelData(0);
+  for (let k = 1; k < data.length; ++k) {
+    data[k] = 0;
+  }
+  data[0] = 1;
+
+  return impulse;
+}
+
+
+function createTestAndRun(context, filterType, testParameters) {
+  // To test the filters, we apply a signal (an impulse) to each of
+  // the specified filters, with each signal starting at a different
+  // time.  The output of the filters is summed together at the
+  // output.  Thus for filter k, the signal input to the filter
+  // starts at time k * timeStep.  For this to work well, timeStep
+  // must be large enough for the output of each filter to have
+  // decayed to zero with timeStep seconds.  That way the filter
+  // outputs don't interfere with each other.
+
+  let filterParameters = testParameters.filterParameters;
+  nFilters = Math.min(filterParameters.length, maxFilters);
+
+  signal = new Array(nFilters);
+  filter = new Array(nFilters);
+
+  impulse = createImpulseBuffer(context, pulseLengthFrames);
+
+  // Create all of the signal sources and filters that we need.
+  for (let k = 0; k < nFilters; ++k) {
+    signal[k] = context.createBufferSource();
+    signal[k].buffer = impulse;
+
+    filter[k] = context.createBiquadFilter();
+    filter[k].type = filterType;
+    filter[k].frequency.value =
+        context.sampleRate / 2 * filterParameters[k].cutoff;
+    filter[k].detune.value = (filterParameters[k].detune === undefined) ?
+        0 :
+        filterParameters[k].detune;
+    filter[k].Q.value = filterParameters[k].q;
+    filter[k].gain.value = filterParameters[k].gain;
+
+    signal[k].connect(filter[k]);
+    filter[k].connect(context.destination);
+
+    signal[k].start(timeStep * k);
+  }
+
+  return context.startRendering().then(buffer => {
+    checkFilterResponse(buffer, filterType, testParameters);
+  });
+}
+
+function addSignal(dest, src, destOffset) {
+  // Add src to dest at the given dest offset.
+  for (let k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) {
+    dest[k] += src[j];
+  }
+}
+
+function generateReference(filterType, filterParameters) {
+  let result = new Array(renderLengthSamples);
+  let data = new Array(renderLengthSamples);
+  // Initialize the result array and data.
+  for (let k = 0; k < result.length; ++k) {
+    result[k] = 0;
+    data[k] = 0;
+  }
+  // Make data an impulse.
+  data[0] = 1;
+
+  for (let k = 0; k < nFilters; ++k) {
+    // Filter an impulse
+    let detune = (filterParameters[k].detune === undefined) ?
+        0 :
+        filterParameters[k].detune;
+    let frequency = filterParameters[k].cutoff *
+        Math.pow(2, detune / 1200);  // Apply detune, converting from Cents.
+
+    let filterCoef = createFilter(
+        filterType, frequency, filterParameters[k].q, filterParameters[k].gain);
+    let y = filterData(filterCoef, data, renderLengthSamples);
+
+    // Accumulate this filtered data into the final output at the desired
+    // offset.
+    addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate));
+  }
+
+  return result;
+}
+
+function checkFilterResponse(renderedBuffer, filterType, testParameters) {
+  let filterParameters = testParameters.filterParameters;
+  let maxAllowedError = testParameters.threshold;
+  let should = testParameters.should;
+
+  renderedData = renderedBuffer.getChannelData(0);
+
+  reference = generateReference(filterType, filterParameters);
+
+  let len = Math.min(renderedData.length, reference.length);
+
+  let success = true;
+
+  // Maximum error between rendered data and expected data
+  let maxError = 0;
+
+  // Sample offset where the maximum error occurred.
+  let maxPosition = 0;
+
+  // Number of infinities or NaNs that occurred in the rendered data.
+  let invalidNumberCount = 0;
+
+  should(nFilters, 'Number of filters tested')
+      .beEqualTo(filterParameters.length);
+
+  // Compare the rendered signal with our reference, keeping
+  // track of the maximum difference (and the offset of the max
+  // difference.)  Check for bad numbers in the rendered output
+  // too.  There shouldn't be any.
+  for (let k = 0; k < len; ++k) {
+    let err = Math.abs(renderedData[k] - reference[k]);
+    if (err > maxError) {
+      maxError = err;
+      maxPosition = k;
+    }
+    if (!isValidNumber(renderedData[k])) {
+      ++invalidNumberCount;
+    }
+  }
+
+  should(
+      invalidNumberCount, 'Number of non-finite values in the rendered output')
+      .beEqualTo(0);
+
+  should(maxError, 'Max error in ' + filterTypeName[filterType] + ' response')
+      .beLessThanOrEqualTo(maxAllowedError);
+}
diff --git a/webaudio/resources/convolution-testing.js b/webaudio/resources/convolution-testing.js
new file mode 100644
index 0000000..c976f86
--- /dev/null
+++ b/webaudio/resources/convolution-testing.js
@@ -0,0 +1,168 @@
+let sampleRate = 44100.0;
+
+let renderLengthSeconds = 8;
+let pulseLengthSeconds = 1;
+let pulseLengthFrames = pulseLengthSeconds * sampleRate;
+
+function createSquarePulseBuffer(context, sampleFrameLength) {
+  let audioBuffer =
+      context.createBuffer(1, sampleFrameLength, context.sampleRate);
+
+  let n = audioBuffer.length;
+  let data = audioBuffer.getChannelData(0);
+
+  for (let i = 0; i < n; ++i)
+    data[i] = 1;
+
+  return audioBuffer;
+}
+
+// The triangle buffer holds the expected result of the convolution.
+// It linearly ramps up from 0 to its maximum value (at the center)
+// then linearly ramps down to 0.  The center value corresponds to the
+// point where the two square pulses overlap the most.
+function createTrianglePulseBuffer(context, sampleFrameLength) {
+  let audioBuffer =
+      context.createBuffer(1, sampleFrameLength, context.sampleRate);
+
+  let n = audioBuffer.length;
+  let halfLength = n / 2;
+  let data = audioBuffer.getChannelData(0);
+
+  for (let i = 0; i < halfLength; ++i)
+    data[i] = i + 1;
+
+  for (let i = halfLength; i < n; ++i)
+    data[i] = n - i - 1;
+
+  return audioBuffer;
+}
+
+function log10(x) {
+  return Math.log(x) / Math.LN10;
+}
+
+function linearToDecibel(x) {
+  return 20 * log10(x);
+}
+
+// Verify that the rendered result is very close to the reference
+// triangular pulse.
+function checkTriangularPulse(rendered, reference, should) {
+  let match = true;
+  let maxDelta = 0;
+  let valueAtMaxDelta = 0;
+  let maxDeltaIndex = 0;
+
+  for (let i = 0; i < reference.length; ++i) {
+    let diff = rendered[i] - reference[i];
+    let x = Math.abs(diff);
+    if (x > maxDelta) {
+      maxDelta = x;
+      valueAtMaxDelta = reference[i];
+      maxDeltaIndex = i;
+    }
+  }
+
+  // allowedDeviationFraction was determined experimentally.  It
+  // is the threshold of the relative error at the maximum
+  // difference between the true triangular pulse and the
+  // rendered pulse.
+  let allowedDeviationDecibels = -124.41;
+  let maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta);
+
+  should(
+      maxDeviationDecibels,
+      'Deviation (in dB) of triangular portion of convolution')
+      .beLessThanOrEqualTo(allowedDeviationDecibels);
+
+  return match;
+}
+
+// Verify that the rendered data is close to zero for the first part
+// of the tail.
+function checkTail1(data, reference, breakpoint, should) {
+  let isZero = true;
+  let tail1Max = 0;
+
+  for (let i = reference.length; i < reference.length + breakpoint; ++i) {
+    let mag = Math.abs(data[i]);
+    if (mag > tail1Max) {
+      tail1Max = mag;
+    }
+  }
+
+  // Let's find the peak of the reference (even though we know a
+  // priori what it is).
+  let refMax = 0;
+  for (let i = 0; i < reference.length; ++i) {
+    refMax = Math.max(refMax, Math.abs(reference[i]));
+  }
+
+  // This threshold is experimentally determined by examining the
+  // value of tail1MaxDecibels.
+  let threshold1 = -129.7;
+
+  let tail1MaxDecibels = linearToDecibel(tail1Max / refMax);
+  should(tail1MaxDecibels, 'Deviation in first part of tail of convolutions')
+      .beLessThanOrEqualTo(threshold1);
+
+  return isZero;
+}
+
+// Verify that the second part of the tail of the convolution is
+// exactly zero.
+function checkTail2(data, reference, breakpoint, should) {
+  let isZero = true;
+  let tail2Max = 0;
+  // For the second part of the tail, the maximum value should be
+  // exactly zero.
+  let threshold2 = 0;
+  for (let i = reference.length + breakpoint; i < data.length; ++i) {
+    if (Math.abs(data[i]) > 0) {
+      isZero = false;
+      break;
+    }
+  }
+
+  should(isZero, 'Rendered signal after tail of convolution is silent')
+      .beTrue();
+
+  return isZero;
+}
+
+function checkConvolvedResult(renderedBuffer, trianglePulse, should) {
+  let referenceData = trianglePulse.getChannelData(0);
+  let renderedData = renderedBuffer.getChannelData(0);
+
+  let success = true;
+
+  // Verify the triangular pulse is actually triangular.
+
+  success =
+      success && checkTriangularPulse(renderedData, referenceData, should);
+
+  // Make sure that portion after convolved portion is totally
+  // silent.  But round-off prevents this from being completely
+  // true.  At the end of the triangle, it should be close to
+  // zero.  If we go farther out, it should be even closer and
+  // eventually zero.
+
+  // For the tail of the convolution (where the result would be
+  // theoretically zero), we partition the tail into two
+  // parts.  The first is the at the beginning of the tail,
+  // where we tolerate a small but non-zero value.  The second part is
+  // farther along the tail where the result should be zero.
+
+  // breakpoint is the point dividing the first two tail parts
+  // we're looking at.  Experimentally determined.
+  let breakpoint = 12800;
+
+  success =
+      success && checkTail1(renderedData, referenceData, breakpoint, should);
+
+  success =
+      success && checkTail2(renderedData, referenceData, breakpoint, should);
+
+  should(success, 'Test signal convolved').message('correctly', 'incorrectly');
+}
diff --git a/webaudio/resources/delay-testing.js b/webaudio/resources/delay-testing.js
new file mode 100644
index 0000000..9033da6
--- /dev/null
+++ b/webaudio/resources/delay-testing.js
@@ -0,0 +1,66 @@
+let sampleRate = 44100.0;
+
+let renderLengthSeconds = 4;
+let delayTimeSeconds = 0.5;
+let toneLengthSeconds = 2;
+
+function createToneBuffer(context, frequency, numberOfCycles, sampleRate) {
+  let duration = numberOfCycles / frequency;
+  let sampleFrameLength = duration * sampleRate;
+
+  let audioBuffer = context.createBuffer(1, sampleFrameLength, sampleRate);
+
+  let n = audioBuffer.length;
+  let data = audioBuffer.getChannelData(0);
+
+  for (let i = 0; i < n; ++i)
+    data[i] = Math.sin(frequency * 2.0 * Math.PI * i / sampleRate);
+
+  return audioBuffer;
+}
+
+function checkDelayedResult(renderedBuffer, toneBuffer, should) {
+  let sourceData = toneBuffer.getChannelData(0);
+  let renderedData = renderedBuffer.getChannelData(0);
+
+  let delayTimeFrames = delayTimeSeconds * sampleRate;
+  let toneLengthFrames = toneLengthSeconds * sampleRate;
+
+  let success = true;
+
+  let n = renderedBuffer.length;
+
+  for (let i = 0; i < n; ++i) {
+    if (i < delayTimeFrames) {
+      // Check that initial portion is 0 (since signal is delayed).
+      if (renderedData[i] != 0) {
+        should(
+            renderedData[i], 'Initial portion expected to be 0 at frame ' + i)
+            .beEqualTo(0);
+        success = false;
+        break;
+      }
+    } else if (i >= delayTimeFrames && i < delayTimeFrames + toneLengthFrames) {
+      // Make sure that the tone data is delayed by exactly the expected number
+      // of frames.
+      let j = i - delayTimeFrames;
+      if (renderedData[i] != sourceData[j]) {
+        should(renderedData[i], 'Actual data at frame ' + i)
+            .beEqualTo(sourceData[j]);
+        success = false;
+        break;
+      }
+    } else {
+      // Make sure we have silence after the delayed tone.
+      if (renderedData[i] != 0) {
+        should(renderedData[j], 'Final portion at frame ' + i).beEqualTo(0);
+        success = false;
+        break;
+      }
+    }
+  }
+
+  should(
+      success, 'Delaying test signal by ' + delayTimeSeconds + ' sec was done')
+      .message('correctly', 'incorrectly')
+}
diff --git a/webaudio/resources/distance-model-testing.js b/webaudio/resources/distance-model-testing.js
new file mode 100644
index 0000000..1b9adde
--- /dev/null
+++ b/webaudio/resources/distance-model-testing.js
@@ -0,0 +1,193 @@
+let sampleRate = 44100.0;
+
+// How many panner nodes to create for the test.
+let nodesToCreate = 100;
+
+// Time step when each panner node starts.
+let timeStep = 0.001;
+
+// Make sure we render long enough to get all of our nodes.
+let renderLengthSeconds = timeStep * (nodesToCreate + 1);
+
+// Length of an impulse signal.
+let pulseLengthFrames = Math.round(timeStep * sampleRate);
+
+// Globals to make debugging a little easier.
+let context;
+let impulse;
+let bufferSource;
+let panner;
+let position;
+let time;
+
+// For the record, these distance formulas were taken from the OpenAL
+// spec
+// (http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specification.pdf),
+// not the code.  The Web Audio spec follows the OpenAL formulas.
+
+function linearDistance(panner, x, y, z) {
+  let distance = Math.sqrt(x * x + y * y + z * z);
+  distance = Math.min(distance, panner.maxDistance);
+  let rolloff = panner.rolloffFactor;
+  let gain =
+      (1 -
+       rolloff * (distance - panner.refDistance) /
+           (panner.maxDistance - panner.refDistance));
+
+  return gain;
+}
+
+function inverseDistance(panner, x, y, z) {
+  let distance = Math.sqrt(x * x + y * y + z * z);
+  distance = Math.min(distance, panner.maxDistance);
+  let rolloff = panner.rolloffFactor;
+  let gain = panner.refDistance /
+      (panner.refDistance + rolloff * (distance - panner.refDistance));
+
+  return gain;
+}
+
+function exponentialDistance(panner, x, y, z) {
+  let distance = Math.sqrt(x * x + y * y + z * z);
+  distance = Math.min(distance, panner.maxDistance);
+  let rolloff = panner.rolloffFactor;
+  let gain = Math.pow(distance / panner.refDistance, -rolloff);
+
+  return gain;
+}
+
+// Map the distance model to the function that implements the model
+let distanceModelFunction = {
+  'linear': linearDistance,
+  'inverse': inverseDistance,
+  'exponential': exponentialDistance
+};
+
+function createGraph(context, distanceModel, nodeCount) {
+  bufferSource = new Array(nodeCount);
+  panner = new Array(nodeCount);
+  position = new Array(nodeCount);
+  time = new Array(nodesToCreate);
+
+  impulse = createImpulseBuffer(context, pulseLengthFrames);
+
+  // Create all the sources and panners.
+  //
+  // We MUST use the EQUALPOWER panning model so that we can easily
+  // figure out the gain introduced by the panner.
+  //
+  // We want to stay in the middle of the panning range, which means
+  // we want to stay on the z-axis.  If we don't, then the effect of
+  // panning model will be much more complicated.  We're not testing
+  // the panner, but the distance model, so we want the panner effect
+  // to be simple.
+  //
+  // The panners are placed at a uniform intervals between the panner
+  // reference distance and the panner max distance.  The source is
+  // also started at regular intervals.
+  for (let k = 0; k < nodeCount; ++k) {
+    bufferSource[k] = context.createBufferSource();
+    bufferSource[k].buffer = impulse;
+
+    panner[k] = context.createPanner();
+    panner[k].panningModel = 'equalpower';
+    panner[k].distanceModel = distanceModel;
+
+    let distanceStep =
+        (panner[k].maxDistance - panner[k].refDistance) / nodeCount;
+    position[k] = distanceStep * k + panner[k].refDistance;
+    panner[k].setPosition(0, 0, position[k]);
+
+    bufferSource[k].connect(panner[k]);
+    panner[k].connect(context.destination);
+
+    time[k] = k * timeStep;
+    bufferSource[k].start(time[k]);
+  }
+}
+
+// distanceModel should be the distance model string like
+// "linear", "inverse", or "exponential".
+function createTestAndRun(context, distanceModel, should) {
+  // To test the distance models, we create a number of panners at
+  // uniformly spaced intervals on the z-axis.  Each of these are
+  // started at equally spaced time intervals.  After rendering the
+  // signals, we examine where each impulse is located and the
+  // attenuation of the impulse.  The attenuation is compared
+  // against our expected attenuation.
+
+  createGraph(context, distanceModel, nodesToCreate);
+
+  return context.startRendering().then(
+      buffer => checkDistanceResult(buffer, distanceModel, should));
+}
+
+// The gain caused by the EQUALPOWER panning model, if we stay on the
+// z axis, with the default orientations.
+function equalPowerGain() {
+  return Math.SQRT1_2;
+}
+
+function checkDistanceResult(renderedBuffer, model, should) {
+  renderedData = renderedBuffer.getChannelData(0);
+
+  // The max allowed error between the actual gain and the expected
+  // value.  This is determined experimentally.  Set to 0 to see
+  // what the actual errors are.
+  let maxAllowedError = 3.3e-6;
+
+  let success = true;
+
+  // Number of impulses we found in the rendered result.
+  let impulseCount = 0;
+
+  // Maximum relative error in the gain of the impulses.
+  let maxError = 0;
+
+  // Array of locations of the impulses that were not at the
+  // expected location.  (Contains the actual and expected frame
+  // of the impulse.)
+  let impulsePositionErrors = new Array();
+
+  // Step through the rendered data to find all the non-zero points
+  // so we can find where our distance-attenuated impulses are.
+  // These are tested against the expected attenuations at that
+  // distance.
+  for (let k = 0; k < renderedData.length; ++k) {
+    if (renderedData[k] != 0) {
+      // Convert from string to index.
+      let distanceFunction = distanceModelFunction[model];
+      let expected =
+          distanceFunction(panner[impulseCount], 0, 0, position[impulseCount]);
+
+      // Adjust for the center-panning of the EQUALPOWER panning
+      // model that we're using.
+      expected *= equalPowerGain();
+
+      let error = Math.abs(renderedData[k] - expected) / Math.abs(expected);
+
+      maxError = Math.max(maxError, Math.abs(error));
+
+      should(renderedData[k]).beCloseTo(expected, {threshold: maxAllowedError});
+
+      // Keep track of any impulses that aren't where we expect them
+      // to be.
+      let expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate);
+      if (k != expectedOffset) {
+        impulsePositionErrors.push({actual: k, expected: expectedOffset});
+      }
+      ++impulseCount;
+    }
+  }
+  should(impulseCount, 'Number of impulses').beEqualTo(nodesToCreate);
+
+  should(maxError, 'Max error in distance gains')
+      .beLessThanOrEqualTo(maxAllowedError);
+
+  // Display any timing errors that we found.
+  if (impulsePositionErrors.length > 0) {
+    let actual = impulsePositionErrors.map(x => x.actual);
+    let expected = impulsePositionErrors.map(x => x.expected);
+    should(actual, 'Actual impulse positions found').beEqualToArray(expected);
+  }
+}
diff --git a/webaudio/resources/merger-testing.js b/webaudio/resources/merger-testing.js
new file mode 100644
index 0000000..4477ec0
--- /dev/null
+++ b/webaudio/resources/merger-testing.js
@@ -0,0 +1,24 @@
+// This file is for the audiochannelmerger-* layout tests.
+// Requires |audio-testing.js| to work properly.
+
+function testMergerInput(should, config) {
+  let context = new OfflineAudioContext(config.numberOfChannels, 128, 44100);
+  let merger = context.createChannelMerger(config.numberOfChannels);
+  let source = context.createBufferSource();
+  source.buffer = createConstantBuffer(context, 128, config.testBufferContent);
+
+  // Connect the output of source into the specified input of merger.
+  if (config.mergerInputIndex)
+    source.connect(merger, 0, config.mergerInputIndex);
+  else
+    source.connect(merger);
+  merger.connect(context.destination);
+  source.start();
+
+  return context.startRendering().then(function(buffer) {
+    let prefix = config.testBufferContent.length + '-channel source: ';
+    for (let i = 0; i < config.numberOfChannels; i++)
+      should(buffer.getChannelData(i), prefix + 'Channel #' + i)
+          .beConstantValueOf(config.expected[i]);
+  });
+}
diff --git a/webaudio/resources/mix-testing.js b/webaudio/resources/mix-testing.js
new file mode 100644
index 0000000..63c8e1a
--- /dev/null
+++ b/webaudio/resources/mix-testing.js
@@ -0,0 +1,23 @@
+let toneLengthSeconds = 1;
+
+// Create a buffer with multiple channels.
+// The signal frequency in each channel is the multiple of that in the first
+// channel.
+function createToneBuffer(context, frequency, duration, numberOfChannels) {
+  let sampleRate = context.sampleRate;
+  let sampleFrameLength = duration * sampleRate;
+
+  let audioBuffer =
+      context.createBuffer(numberOfChannels, sampleFrameLength, sampleRate);
+
+  let n = audioBuffer.length;
+
+  for (let k = 0; k < numberOfChannels; ++k) {
+    let data = audioBuffer.getChannelData(k);
+
+    for (let i = 0; i < n; ++i)
+      data[i] = Math.sin(frequency * (k + 1) * 2.0 * Math.PI * i / sampleRate);
+  }
+
+  return audioBuffer;
+}
diff --git a/webaudio/resources/mixing-rules.js b/webaudio/resources/mixing-rules.js
new file mode 100644
index 0000000..e06a146
--- /dev/null
+++ b/webaudio/resources/mixing-rules.js
@@ -0,0 +1,350 @@
+// Utilities for mixing rule testing.
+// http://webaudio.github.io/web-audio-api/#channel-up-mixing-and-down-mixing
+
+
+/**
+ * Create an n-channel buffer, with all sample data zero except for a shifted
+ * impulse. The impulse position depends on the channel index. For example, for
+ * a 4-channel buffer:
+ *  channel 0: 1 0 0 0 0 0 0 0
+ *  channel 1: 0 1 0 0 0 0 0 0
+ *  channel 2: 0 0 1 0 0 0 0 0
+ *  channel 3: 0 0 0 1 0 0 0 0
+ * @param  {AudioContext} context     Associated AudioContext.
+ * @param  {Number} numberOfChannels  Number of channels of test buffer.
+ * @param  {Number} frameLength       Buffer length in frames.
+ * @return {AudioBuffer}
+ */
+function createShiftedImpulseBuffer(context, numberOfChannels, frameLength) {
+  let shiftedImpulseBuffer =
+      context.createBuffer(numberOfChannels, frameLength, context.sampleRate);
+  for (let channel = 0; channel < numberOfChannels; ++channel) {
+    let data = shiftedImpulseBuffer.getChannelData(channel);
+    data[channel] = 1;
+  }
+
+  return shiftedImpulseBuffer;
+}
+
+/**
+ * Create a string that displays the content of AudioBuffer.
+ * @param  {AudioBuffer} audioBuffer  AudioBuffer object to stringify.
+ * @param  {Number} frameLength       Number of frames to be printed.
+ * @param  {Number} frameOffset       Starting frame position for printing.
+ * @return {String}
+ */
+function stringifyBuffer(audioBuffer, frameLength, frameOffset) {
+  frameOffset = (frameOffset || 0);
+
+  let stringifiedBuffer = '';
+  for (let channel = 0; channel < audioBuffer.numberOfChannels; ++channel) {
+    let channelData = audioBuffer.getChannelData(channel);
+    for (let i = 0; i < frameLength; ++i)
+      stringifiedBuffer += channelData[i + frameOffset] + ' ';
+    stringifiedBuffer += '\n';
+  }
+
+  return stringifiedBuffer;
+}
+
+/**
+ * Compute number of channels from the connection.
+ * http://webaudio.github.io/web-audio-api/#dfn-computednumberofchannels
+ * @param  {String} connections         A string specifies the connection. For
+ *                                      example, the string "128" means 3
+ *                                      connections, having 1, 2, and 8 channels
+ *                                      respectively.
+ * @param  {Number} channelCount        Channel count.
+ * @param  {String} channelCountMode    Channel count mode.
+ * @return {Number}                     Computed number of channels.
+ */
+function computeNumberOfChannels(connections, channelCount, channelCountMode) {
+  if (channelCountMode == 'explicit')
+    return channelCount;
+
+  // Must have at least one channel.
+  let computedNumberOfChannels = 1;
+
+  // Compute "computedNumberOfChannels" based on all the connections.
+  for (let i = 0; i < connections.length; ++i) {
+    let connectionNumberOfChannels = parseInt(connections[i]);
+    computedNumberOfChannels =
+        Math.max(computedNumberOfChannels, connectionNumberOfChannels);
+  }
+
+  if (channelCountMode == 'clamped-max')
+    computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount);
+
+  return computedNumberOfChannels;
+}
+
+/**
+ * Apply up/down-mixing (in-place summing) based on 'speaker' interpretation.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function speakersSum(input, output) {
+  if (input.length != output.length) {
+    throw '[mixing-rules.js] speakerSum(): buffer lengths mismatch (input: ' +
+        input.length + ', output: ' + output.length + ')';
+  }
+
+  if (input.numberOfChannels === output.numberOfChannels) {
+    for (let channel = 0; channel < output.numberOfChannels; ++channel) {
+      let inputChannel = input.getChannelData(channel);
+      let outputChannel = output.getChannelData(channel);
+      for (let i = 0; i < outputChannel.length; i++)
+        outputChannel[i] += inputChannel[i];
+    }
+  } else if (input.numberOfChannels < output.numberOfChannels) {
+    processUpMix(input, output);
+  } else {
+    processDownMix(input, output);
+  }
+}
+
+/**
+ * In-place summing to |output| based on 'discrete' channel interpretation.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function discreteSum(input, output) {
+  if (input.length != output.length) {
+    throw '[mixing-rules.js] speakerSum(): buffer lengths mismatch (input: ' +
+        input.length + ', output: ' + output.length + ')';
+  }
+
+  let numberOfChannels =
+      Math.min(input.numberOfChannels, output.numberOfChannels)
+
+          for (let channel = 0; channel < numberOfChannels; ++channel) {
+    let inputChannel = input.getChannelData(channel);
+    let outputChannel = output.getChannelData(channel);
+    for (let i = 0; i < outputChannel.length; i++)
+      outputChannel[i] += inputChannel[i];
+  }
+}
+
+/**
+ * Perform up-mix by in-place summing to |output| buffer.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function processUpMix(input, output) {
+  let numberOfInputChannels = input.numberOfChannels;
+  let numberOfOutputChannels = output.numberOfChannels;
+  let i, length = output.length;
+
+  // Up-mixing: 1 -> 2, 1 -> 4
+  //   output.L += input
+  //   output.R += input
+  //   output.SL += 0 (in the case of 1 -> 4)
+  //   output.SR += 0 (in the case of 1 -> 4)
+  if ((numberOfInputChannels === 1 && numberOfOutputChannels === 2) ||
+      (numberOfInputChannels === 1 && numberOfOutputChannels === 4)) {
+    let inputChannel = input.getChannelData(0);
+    let outputChannel0 = output.getChannelData(0);
+    let outputChannel1 = output.getChannelData(1);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel[i];
+      outputChannel1[i] += inputChannel[i];
+    }
+
+    return;
+  }
+
+  // Up-mixing: 1 -> 5.1
+  //   output.L += 0
+  //   output.R += 0
+  //   output.C += input
+  //   output.LFE += 0
+  //   output.SL += 0
+  //   output.SR += 0
+  if (numberOfInputChannels == 1 && numberOfOutputChannels == 6) {
+    let inputChannel = input.getChannelData(0);
+    let outputChannel2 = output.getChannelData(2);
+    for (i = 0; i < length; i++)
+      outputChannel2[i] += inputChannel[i];
+
+    return;
+  }
+
+  // Up-mixing: 2 -> 4, 2 -> 5.1
+  //   output.L += input.L
+  //   output.R += input.R
+  //   output.C += 0 (in the case of 2 -> 5.1)
+  //   output.LFE += 0 (in the case of 2 -> 5.1)
+  //   output.SL += 0
+  //   output.SR += 0
+  if ((numberOfInputChannels === 2 && numberOfOutputChannels === 4) ||
+      (numberOfInputChannels === 2 && numberOfOutputChannels === 6)) {
+    let inputChannel0 = input.getChannelData(0);
+    let inputChannel1 = input.getChannelData(1);
+    let outputChannel0 = output.getChannelData(0);
+    let outputChannel1 = output.getChannelData(1);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i];
+      outputChannel1[i] += inputChannel1[i];
+    }
+
+    return;
+  }
+
+  // Up-mixing: 4 -> 5.1
+  //   output.L += input.L
+  //   output.R += input.R
+  //   output.C += 0
+  //   output.LFE += 0
+  //   output.SL += input.SL
+  //   output.SR += input.SR
+  if (numberOfInputChannels === 4 && numberOfOutputChannels === 6) {
+    let inputChannel0 = input.getChannelData(0);    // input.L
+    let inputChannel1 = input.getChannelData(1);    // input.R
+    let inputChannel2 = input.getChannelData(2);    // input.SL
+    let inputChannel3 = input.getChannelData(3);    // input.SR
+    let outputChannel0 = output.getChannelData(0);  // output.L
+    let outputChannel1 = output.getChannelData(1);  // output.R
+    let outputChannel4 = output.getChannelData(4);  // output.SL
+    let outputChannel5 = output.getChannelData(5);  // output.SR
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i];
+      outputChannel1[i] += inputChannel1[i];
+      outputChannel4[i] += inputChannel2[i];
+      outputChannel5[i] += inputChannel3[i];
+    }
+
+    return;
+  }
+
+  // All other cases, fall back to the discrete sum.
+  discreteSum(input, output);
+}
+
+/**
+ * Perform down-mix by in-place summing to |output| buffer.
+ * @param  {AudioBuffer} input          Input audio buffer.
+ * @param  {AudioBuffer} output         Output audio buffer.
+ */
+function processDownMix(input, output) {
+  let numberOfInputChannels = input.numberOfChannels;
+  let numberOfOutputChannels = output.numberOfChannels;
+  let i, length = output.length;
+
+  // Down-mixing: 2 -> 1
+  //   output += 0.5 * (input.L + input.R)
+  if (numberOfInputChannels === 2 && numberOfOutputChannels === 1) {
+    let inputChannel0 = input.getChannelData(0);  // input.L
+    let inputChannel1 = input.getChannelData(1);  // input.R
+    let outputChannel0 = output.getChannelData(0);
+    for (i = 0; i < length; i++)
+      outputChannel0[i] += 0.5 * (inputChannel0[i] + inputChannel1[i]);
+
+    return;
+  }
+
+  // Down-mixing: 4 -> 1
+  //   output += 0.25 * (input.L + input.R + input.SL + input.SR)
+  if (numberOfInputChannels === 4 && numberOfOutputChannels === 1) {
+    let inputChannel0 = input.getChannelData(0);  // input.L
+    let inputChannel1 = input.getChannelData(1);  // input.R
+    let inputChannel2 = input.getChannelData(2);  // input.SL
+    let inputChannel3 = input.getChannelData(3);  // input.SR
+    let outputChannel0 = output.getChannelData(0);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += 0.25 *
+          (inputChannel0[i] + inputChannel1[i] + inputChannel2[i] +
+           inputChannel3[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 5.1 -> 1
+  //   output += sqrt(1/2) * (input.L + input.R) + input.C
+  //            + 0.5 * (input.SL + input.SR)
+  if (numberOfInputChannels === 6 && numberOfOutputChannels === 1) {
+    let inputChannel0 = input.getChannelData(0);  // input.L
+    let inputChannel1 = input.getChannelData(1);  // input.R
+    let inputChannel2 = input.getChannelData(2);  // input.C
+    let inputChannel4 = input.getChannelData(4);  // input.SL
+    let inputChannel5 = input.getChannelData(5);  // input.SR
+    let outputChannel0 = output.getChannelData(0);
+    let scaleSqrtHalf = Math.sqrt(0.5);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] +=
+          scaleSqrtHalf * (inputChannel0[i] + inputChannel1[i]) +
+          inputChannel2[i] + 0.5 * (inputChannel4[i] + inputChannel5[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 4 -> 2
+  //   output.L += 0.5 * (input.L + input.SL)
+  //   output.R += 0.5 * (input.R + input.SR)
+  if (numberOfInputChannels == 4 && numberOfOutputChannels == 2) {
+    let inputChannel0 = input.getChannelData(0);    // input.L
+    let inputChannel1 = input.getChannelData(1);    // input.R
+    let inputChannel2 = input.getChannelData(2);    // input.SL
+    let inputChannel3 = input.getChannelData(3);    // input.SR
+    let outputChannel0 = output.getChannelData(0);  // output.L
+    let outputChannel1 = output.getChannelData(1);  // output.R
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += 0.5 * (inputChannel0[i] + inputChannel2[i]);
+      outputChannel1[i] += 0.5 * (inputChannel1[i] + inputChannel3[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 5.1 -> 2
+  //   output.L += input.L + sqrt(1/2) * (input.C + input.SL)
+  //   output.R += input.R + sqrt(1/2) * (input.C + input.SR)
+  if (numberOfInputChannels == 6 && numberOfOutputChannels == 2) {
+    let inputChannel0 = input.getChannelData(0);    // input.L
+    let inputChannel1 = input.getChannelData(1);    // input.R
+    let inputChannel2 = input.getChannelData(2);    // input.C
+    let inputChannel4 = input.getChannelData(4);    // input.SL
+    let inputChannel5 = input.getChannelData(5);    // input.SR
+    let outputChannel0 = output.getChannelData(0);  // output.L
+    let outputChannel1 = output.getChannelData(1);  // output.R
+    let scaleSqrtHalf = Math.sqrt(0.5);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i] +
+          scaleSqrtHalf * (inputChannel2[i] + inputChannel4[i]);
+      outputChannel1[i] += inputChannel1[i] +
+          scaleSqrtHalf * (inputChannel2[i] + inputChannel5[i]);
+    }
+
+    return;
+  }
+
+  // Down-mixing: 5.1 -> 4
+  //   output.L += input.L + sqrt(1/2) * input.C
+  //   output.R += input.R + sqrt(1/2) * input.C
+  //   output.SL += input.SL
+  //   output.SR += input.SR
+  if (numberOfInputChannels === 6 && numberOfOutputChannels === 4) {
+    let inputChannel0 = input.getChannelData(0);    // input.L
+    let inputChannel1 = input.getChannelData(1);    // input.R
+    let inputChannel2 = input.getChannelData(2);    // input.C
+    let inputChannel4 = input.getChannelData(4);    // input.SL
+    let inputChannel5 = input.getChannelData(5);    // input.SR
+    let outputChannel0 = output.getChannelData(0);  // output.L
+    let outputChannel1 = output.getChannelData(1);  // output.R
+    let outputChannel2 = output.getChannelData(2);  // output.SL
+    let outputChannel3 = output.getChannelData(3);  // output.SR
+    let scaleSqrtHalf = Math.sqrt(0.5);
+    for (i = 0; i < length; i++) {
+      outputChannel0[i] += inputChannel0[i] + scaleSqrtHalf * inputChannel2[i];
+      outputChannel1[i] += inputChannel1[i] + scaleSqrtHalf * inputChannel2[i];
+      outputChannel2[i] += inputChannel4[i];
+      outputChannel3[i] += inputChannel5[i];
+    }
+
+    return;
+  }
+
+  // All other cases, fall back to the discrete sum.
+  discreteSum(input, output);
+}
diff --git a/webaudio/resources/panner-formulas.js b/webaudio/resources/panner-formulas.js
new file mode 100644
index 0000000..ae6f516
--- /dev/null
+++ b/webaudio/resources/panner-formulas.js
@@ -0,0 +1,190 @@
+// For the record, these distance formulas were taken from the OpenAL
+// spec
+// (http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specification.pdf),
+// not the code.  The Web Audio spec follows the OpenAL formulas.
+
+function linearDistance(panner, x, y, z) {
+  let distance = Math.sqrt(x * x + y * y + z * z);
+  let dref = Math.min(panner.refDistance, panner.maxDistance);
+  let dmax = Math.max(panner.refDistance, panner.maxDistance);
+  distance = Math.max(Math.min(distance, dmax), dref);
+  let rolloff = Math.max(Math.min(panner.rolloffFactor, 1), 0);
+  if (dref === dmax)
+    return 1 - rolloff;
+
+  let gain = (1 - rolloff * (distance - dref) / (dmax - dref));
+
+  return gain;
+}
+
+function inverseDistance(panner, x, y, z) {
+  let distance = Math.sqrt(x * x + y * y + z * z);
+  distance = Math.max(distance, panner.refDistance);
+  let rolloff = panner.rolloffFactor;
+  let gain = panner.refDistance /
+      (panner.refDistance +
+       rolloff * (Math.max(distance, panner.refDistance) - panner.refDistance));
+
+  return gain;
+}
+
+function exponentialDistance(panner, x, y, z) {
+  let distance = Math.sqrt(x * x + y * y + z * z);
+  distance = Math.max(distance, panner.refDistance);
+  let rolloff = panner.rolloffFactor;
+  let gain = Math.pow(distance / panner.refDistance, -rolloff);
+
+  return gain;
+}
+
+// Simple implementations of 3D vectors implemented as a 3-element array.
+
+// x - y
+function vec3Sub(x, y) {
+  let z = new Float32Array(3);
+  z[0] = x[0] - y[0];
+  z[1] = x[1] - y[1];
+  z[2] = x[2] - y[2];
+
+  return z;
+}
+
+// x/|x|
+function vec3Normalize(x) {
+  let mag = Math.hypot(...x);
+  return x.map(function(c) {
+    return c / mag;
+  });
+}
+
+// x == 0?
+function vec3IsZero(x) {
+  return x[0] === 0 && x[1] === 0 && x[2] === 0;
+}
+
+// Vector cross product
+function vec3Cross(u, v) {
+  let cross = new Float32Array(3);
+  cross[0] = u[1] * v[2] - u[2] * v[1];
+  cross[1] = u[2] * v[0] - u[0] * v[2];
+  cross[2] = u[0] * v[1] - u[1] * v[0];
+  return cross;
+}
+
+// Dot product
+function vec3Dot(x, y) {
+  return x[0] * y[0] + x[1] * y[1] + x[2] * y[2];
+}
+
+// a*x, for scalar a
+function vec3Scale(a, x) {
+  return x.map(function(c) {
+    return a * c;
+  });
+}
+
+function calculateAzimuth(source, listener, listenerForward, listenerUp) {
+  let sourceListener = vec3Sub(source, listener);
+
+  if (vec3IsZero(sourceListener))
+    return 0;
+
+  sourceListener = vec3Normalize(sourceListener);
+
+  let listenerRight = vec3Normalize(vec3Cross(listenerForward, listenerUp));
+  let listenerForwardNorm = vec3Normalize(listenerForward);
+
+  let up = vec3Cross(listenerRight, listenerForwardNorm);
+  let upProjection = vec3Dot(sourceListener, up);
+
+  let projectedSource =
+      vec3Normalize(vec3Sub(sourceListener, vec3Scale(upProjection, up)));
+
+  let azimuth =
+      180 / Math.PI * Math.acos(vec3Dot(projectedSource, listenerRight));
+
+  // Source in front or behind the listener
+  let frontBack = vec3Dot(projectedSource, listenerForwardNorm);
+  if (frontBack < 0)
+    azimuth = 360 - azimuth;
+
+  // Make azimuth relative to "front" and not "right" listener vector.
+  if (azimuth >= 0 && azimuth <= 270)
+    azimuth = 90 - azimuth;
+  else
+    azimuth = 450 - azimuth;
+
+  // We don't need elevation, so we're skipping that computation.
+  return azimuth;
+}
+
+// Map our position angle to the azimuth angle (in degrees).
+//
+// An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg.
+function angleToAzimuth(angle) {
+  return 90 - angle * 180 / Math.PI;
+}
+
+// The gain caused by the EQUALPOWER panning model
+function equalPowerGain(azimuth, numberOfChannels) {
+  let halfPi = Math.PI / 2;
+
+  if (azimuth < -90)
+    azimuth = -180 - azimuth;
+  else
+    azimuth = 180 - azimuth;
+
+  if (numberOfChannels == 1) {
+    let panPosition = (azimuth + 90) / 180;
+
+    let gainL = Math.cos(halfPi * panPosition);
+    let gainR = Math.sin(halfPi * panPosition);
+
+    return {left: gainL, right: gainR};
+  } else {
+    if (azimuth <= 0) {
+      let panPosition = (azimuth + 90) / 90;
+
+      let gainL = Math.cos(halfPi * panPosition);
+      let gainR = Math.sin(halfPi * panPosition);
+
+      return {left: gainL, right: gainR};
+    } else {
+      let panPosition = azimuth / 90;
+
+      let gainL = Math.cos(halfPi * panPosition);
+      let gainR = Math.sin(halfPi * panPosition);
+
+      return {left: gainL, right: gainR};
+    }
+  }
+}
+
+function applyPanner(azimuth, srcL, srcR, numberOfChannels) {
+  let length = srcL.length;
+  let outL = new Float32Array(length);
+  let outR = new Float32Array(length);
+
+  if (numberOfChannels == 1) {
+    for (let k = 0; k < length; ++k) {
+      let gains = equalPowerGain(azimuth[k], numberOfChannels);
+
+      outL[k] = srcL[k] * gains.left;
+      outR[k] = srcR[k] * gains.right;
+    }
+  } else {
+    for (let k = 0; k < length; ++k) {
+      let gains = equalPowerGain(azimuth[k], numberOfChannels);
+
+      if (azimuth[k] <= 0) {
+        outL[k] = srcL[k] + srcR[k] * gains.left;
+        outR[k] = srcR[k] * gains.right;
+      } else {
+        outL[k] = srcL[k] * gains.left;
+        outR[k] = srcR[k] + srcL[k] * gains.right;
+      }
+    }
+  }
+
+  return {left: outL, right: outR};
+}
diff --git a/webaudio/resources/panner-model-testing.js b/webaudio/resources/panner-model-testing.js
new file mode 100644
index 0000000..662fb1d
--- /dev/null
+++ b/webaudio/resources/panner-model-testing.js
@@ -0,0 +1,181 @@
+let sampleRate = 44100.0;
+
+let numberOfChannels = 1;
+
+// Time step when each panner node starts.
+let timeStep = 0.001;
+
+// Length of the impulse signal.
+let pulseLengthFrames = Math.round(timeStep * sampleRate);
+
+// How many panner nodes to create for the test
+let nodesToCreate = 100;
+
+// Be sure we render long enough for all of our nodes.
+let renderLengthSeconds = timeStep * (nodesToCreate + 1);
+
+// These are global mostly for debugging.
+let context;
+let impulse;
+let bufferSource;
+let panner;
+let position;
+let time;
+
+let renderedBuffer;
+let renderedLeft;
+let renderedRight;
+
+function createGraph(context, nodeCount, positionSetter) {
+  bufferSource = new Array(nodeCount);
+  panner = new Array(nodeCount);
+  position = new Array(nodeCount);
+  time = new Array(nodeCount);
+  // Angle between panner locations.  (nodeCount - 1 because we want
+  // to include both 0 and 180 deg.
+  let angleStep = Math.PI / (nodeCount - 1);
+
+  if (numberOfChannels == 2) {
+    impulse = createStereoImpulseBuffer(context, pulseLengthFrames);
+  } else
+    impulse = createImpulseBuffer(context, pulseLengthFrames);
+
+  for (let k = 0; k < nodeCount; ++k) {
+    bufferSource[k] = context.createBufferSource();
+    bufferSource[k].buffer = impulse;
+
+    panner[k] = context.createPanner();
+    panner[k].panningModel = 'equalpower';
+    panner[k].distanceModel = 'linear';
+
+    let angle = angleStep * k;
+    position[k] = {angle: angle, x: Math.cos(angle), z: Math.sin(angle)};
+    positionSetter(panner[k], position[k].x, 0, position[k].z);
+
+    bufferSource[k].connect(panner[k]);
+    panner[k].connect(context.destination);
+
+    // Start the source
+    time[k] = k * timeStep;
+    bufferSource[k].start(time[k]);
+  }
+}
+
+function createTestAndRun(
+    context, should, nodeCount, numberOfSourceChannels, positionSetter) {
+  numberOfChannels = numberOfSourceChannels;
+
+  createGraph(context, nodeCount, positionSetter);
+
+  return context.startRendering().then(buffer => checkResult(buffer, should));
+}
+
+// Map our position angle to the azimuth angle (in degrees).
+//
+// An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg.
+function angleToAzimuth(angle) {
+  return 90 - angle * 180 / Math.PI;
+}
+
+// The gain caused by the EQUALPOWER panning model
+function equalPowerGain(angle) {
+  let azimuth = angleToAzimuth(angle);
+
+  if (numberOfChannels == 1) {
+    let panPosition = (azimuth + 90) / 180;
+
+    let gainL = Math.cos(0.5 * Math.PI * panPosition);
+    let gainR = Math.sin(0.5 * Math.PI * panPosition);
+
+    return {left: gainL, right: gainR};
+  } else {
+    if (azimuth <= 0) {
+      let panPosition = (azimuth + 90) / 90;
+
+      let gainL = 1 + Math.cos(0.5 * Math.PI * panPosition);
+      let gainR = Math.sin(0.5 * Math.PI * panPosition);
+
+      return {left: gainL, right: gainR};
+    } else {
+      let panPosition = azimuth / 90;
+
+      let gainL = Math.cos(0.5 * Math.PI * panPosition);
+      let gainR = 1 + Math.sin(0.5 * Math.PI * panPosition);
+
+      return {left: gainL, right: gainR};
+    }
+  }
+}
+
+function checkResult(renderedBuffer, should) {
+  renderedLeft = renderedBuffer.getChannelData(0);
+  renderedRight = renderedBuffer.getChannelData(1);
+
+  // The max error we allow between the rendered impulse and the
+  // expected value.  This value is experimentally determined.  Set
+  // to 0 to make the test fail to see what the actual error is.
+  let maxAllowedError = 1.3e-6;
+
+  let success = true;
+
+  // Number of impulses found in the rendered result.
+  let impulseCount = 0;
+
+  // Max (relative) error and the index of the maxima for the left
+  // and right channels.
+  let maxErrorL = 0;
+  let maxErrorIndexL = 0;
+  let maxErrorR = 0;
+  let maxErrorIndexR = 0;
+
+  // Number of impulses that don't match our expected locations.
+  let timeCount = 0;
+
+  // Locations of where the impulses aren't at the expected locations.
+  let timeErrors = new Array();
+
+  for (let k = 0; k < renderedLeft.length; ++k) {
+    // We assume that the left and right channels start at the same instant.
+    if (renderedLeft[k] != 0 || renderedRight[k] != 0) {
+      // The expected gain for the left and right channels.
+      let pannerGain = equalPowerGain(position[impulseCount].angle);
+      let expectedL = pannerGain.left;
+      let expectedR = pannerGain.right;
+
+      // Absolute error in the gain.
+      let errorL = Math.abs(renderedLeft[k] - expectedL);
+      let errorR = Math.abs(renderedRight[k] - expectedR);
+
+      if (Math.abs(errorL) > maxErrorL) {
+        maxErrorL = Math.abs(errorL);
+        maxErrorIndexL = impulseCount;
+      }
+      if (Math.abs(errorR) > maxErrorR) {
+        maxErrorR = Math.abs(errorR);
+        maxErrorIndexR = impulseCount;
+      }
+
+      // Keep track of the impulses that didn't show up where we
+      // expected them to be.
+      let expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate);
+      if (k != expectedOffset) {
+        timeErrors[timeCount] = {actual: k, expected: expectedOffset};
+        ++timeCount;
+      }
+      ++impulseCount;
+    }
+  }
+
+  should(impulseCount, 'Number of impulses found').beEqualTo(nodesToCreate);
+
+  should(
+      timeErrors.map(x => x.actual),
+      'Offsets of impulses at the wrong position')
+      .beEqualToArray(timeErrors.map(x => x.expected));
+
+  should(maxErrorL, 'Error in left channel gain values')
+      .beLessThanOrEqualTo(maxAllowedError);
+
+  should(maxErrorR, 'Error in right channel gain values')
+      .beLessThanOrEqualTo(maxAllowedError);
+}
diff --git a/webaudio/resources/stereopanner-testing.js b/webaudio/resources/stereopanner-testing.js
new file mode 100644
index 0000000..d6238a9
--- /dev/null
+++ b/webaudio/resources/stereopanner-testing.js
@@ -0,0 +1,203 @@
+let StereoPannerTest = (function() {
+
+  // Constants
+  let PI_OVER_TWO = Math.PI * 0.5;
+
+  let gSampleRate = 44100;
+
+  // Time step when each panner node starts.
+  let gTimeStep = 0.001;
+
+  // How many panner nodes to create for the test
+  let gNodesToCreate = 100;
+
+  // Total render length for all of our nodes.
+  let gRenderLength = gTimeStep * (gNodesToCreate + 1) + gSampleRate;
+
+  // Calculates channel gains based on equal power panning model.
+  // See: http://webaudio.github.io/web-audio-api/#panning-algorithm
+  function getChannelGain(pan, numberOfChannels) {
+    // The internal panning clips the pan value between -1, 1.
+    pan = Math.min(Math.max(pan, -1), 1);
+    let gainL, gainR;
+    // Consider number of channels and pan value's polarity.
+    if (numberOfChannels == 1) {
+      let panRadian = (pan * 0.5 + 0.5) * PI_OVER_TWO;
+      gainL = Math.cos(panRadian);
+      gainR = Math.sin(panRadian);
+    } else {
+      let panRadian = (pan <= 0 ? pan + 1 : pan) * PI_OVER_TWO;
+      if (pan <= 0) {
+        gainL = 1 + Math.cos(panRadian);
+        gainR = Math.sin(panRadian);
+      } else {
+        gainL = Math.cos(panRadian);
+        gainR = 1 + Math.sin(panRadian);
+      }
+    }
+    return {gainL: gainL, gainR: gainR};
+  }
+
+
+  /**
+   * Test implementation class.
+   * @param {Object} options Test options
+   * @param {Object} options.description Test description
+   * @param {Object} options.numberOfInputChannels Number of input channels
+   */
+  function Test(should, options) {
+    // Primary test flag.
+    this.success = true;
+
+    this.should = should;
+    this.context = null;
+    this.prefix = options.prefix;
+    this.numberOfInputChannels = (options.numberOfInputChannels || 1);
+    switch (this.numberOfInputChannels) {
+      case 1:
+        this.description = 'Test for mono input';
+        break;
+      case 2:
+        this.description = 'Test for stereo input';
+        break;
+    }
+
+    // Onset time position of each impulse.
+    this.onsets = [];
+
+    // Pan position value of each impulse.
+    this.panPositions = [];
+
+    // Locations of where the impulses aren't at the expected locations.
+    this.errors = [];
+
+    // The index of the current impulse being verified.
+    this.impulseIndex = 0;
+
+    // The max error we allow between the rendered impulse and the
+    // expected value.  This value is experimentally determined.  Set
+    // to 0 to make the test fail to see what the actual error is.
+    this.maxAllowedError = 1.3e-6;
+
+    // Max (absolute) error and the index of the maxima for the left
+    // and right channels.
+    this.maxErrorL = 0;
+    this.maxErrorR = 0;
+    this.maxErrorIndexL = 0;
+    this.maxErrorIndexR = 0;
+
+    // The maximum value to use for panner pan value. The value will range from
+    // -panLimit to +panLimit.
+    this.panLimit = 1.0625;
+  }
+
+
+  Test.prototype.init = function() {
+    this.context = new OfflineAudioContext(2, gRenderLength, gSampleRate);
+  };
+
+  // Prepare an audio graph for testing. Create multiple impulse generators and
+  // panner nodes, then play them sequentially while varying the pan position.
+  Test.prototype.prepare = function() {
+    let impulse;
+    let impulseLength = Math.round(gTimeStep * gSampleRate);
+    let sources = [];
+    let panners = [];
+
+    // Moves the pan value for each panner by pan step unit from -2 to 2.
+    // This is to check if the internal panning value is clipped properly.
+    let panStep = (2 * this.panLimit) / (gNodesToCreate - 1);
+
+    if (this.numberOfInputChannels === 1) {
+      impulse = createImpulseBuffer(this.context, impulseLength);
+    } else {
+      impulse = createStereoImpulseBuffer(this.context, impulseLength);
+    }
+
+    for (let i = 0; i < gNodesToCreate; i++) {
+      sources[i] = this.context.createBufferSource();
+      panners[i] = this.context.createStereoPanner();
+      sources[i].connect(panners[i]);
+      panners[i].connect(this.context.destination);
+      sources[i].buffer = impulse;
+      panners[i].pan.value = this.panPositions[i] = panStep * i - this.panLimit;
+
+      // Store the onset time position of impulse.
+      this.onsets[i] = gTimeStep * i;
+
+      sources[i].start(this.onsets[i]);
+    }
+  };
+
+
+  Test.prototype.verify = function() {
+    let chanL = this.renderedBufferL;
+    let chanR = this.renderedBufferR;
+    for (let i = 0; i < chanL.length; i++) {
+      // Left and right channels must start at the same instant.
+      if (chanL[i] !== 0 || chanR[i] !== 0) {
+        // Get amount of error between actual and expected gain.
+        let expected = getChannelGain(
+            this.panPositions[this.impulseIndex], this.numberOfInputChannels);
+        let errorL = Math.abs(chanL[i] - expected.gainL);
+        let errorR = Math.abs(chanR[i] - expected.gainR);
+
+        if (errorL > this.maxErrorL) {
+          this.maxErrorL = errorL;
+          this.maxErrorIndexL = this.impulseIndex;
+        }
+        if (errorR > this.maxErrorR) {
+          this.maxErrorR = errorR;
+          this.maxErrorIndexR = this.impulseIndex;
+        }
+
+        // Keep track of the impulses that didn't show up where we expected
+        // them to be.
+        let expectedOffset =
+            timeToSampleFrame(this.onsets[this.impulseIndex], gSampleRate);
+        if (i != expectedOffset) {
+          this.errors.push({actual: i, expected: expectedOffset});
+        }
+
+        this.impulseIndex++;
+      }
+    }
+  };
+
+
+  Test.prototype.showResult = function() {
+    this.should(this.impulseIndex, this.prefix + 'Number of impulses found')
+        .beEqualTo(gNodesToCreate);
+
+    this.should(
+            this.errors.length,
+            this.prefix + 'Number of impulse at the wrong offset')
+        .beEqualTo(0);
+
+    this.should(this.maxErrorL, this.prefix + 'Left channel error magnitude')
+        .beLessThanOrEqualTo(this.maxAllowedError);
+
+    this.should(this.maxErrorR, this.prefix + 'Right channel error magnitude')
+        .beLessThanOrEqualTo(this.maxAllowedError);
+  };
+
+  Test.prototype.run = function() {
+
+    this.init();
+    this.prepare();
+
+    return this.context.startRendering().then(renderedBuffer => {
+      this.renderedBufferL = renderedBuffer.getChannelData(0);
+      this.renderedBufferR = renderedBuffer.getChannelData(1);
+      this.verify();
+      this.showResult();
+    });
+  };
+
+  return {
+    create: function(should, options) {
+      return new Test(should, options);
+    }
+  };
+
+})();
diff --git a/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.html b/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.html
new file mode 100644
index 0000000..2112ede
--- /dev/null
+++ b/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: AnalyserNode
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'AnalyserNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'AnalyserNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 1,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'fftSize', value: 2048},
+          {name: 'frequencyBinCount', value: 1024},
+          {name: 'minDecibels', value: -100}, {name: 'maxDecibels', value: -30},
+          {name: 'smoothingTimeConstant', value: 0.8}
+        ]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'AnalyserNode');
+        task.done();
+      });
+
+      audit.define('constructor with options', (task, should) => {
+        let options = {
+          fftSize: 32,
+          maxDecibels: 1,
+          minDecibels: -13,
+          // Choose a value that can be represented the same as a float and as a
+          // double.
+          smoothingTimeConstant: 0.125
+        };
+
+        let node;
+        should(
+            () => {
+              node = new AnalyserNode(context, options);
+            },
+            'node1 = new AnalyserNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        should(node instanceof AnalyserNode, 'node1 instanceof AnalyserNode')
+            .beEqualTo(true);
+        should(node.fftSize, 'node1.fftSize').beEqualTo(options.fftSize);
+        should(node.maxDecibels, 'node1.maxDecibels')
+            .beEqualTo(options.maxDecibels);
+        should(node.minDecibels, 'node1.minDecibels')
+            .beEqualTo(options.minDecibels);
+        should(node.smoothingTimeConstant, 'node1.smoothingTimeConstant')
+            .beEqualTo(options.smoothingTimeConstant);
+
+        task.done();
+      });
+
+      audit.define('construct invalid options', (task, should) => {
+        let node;
+
+        should(
+            () => {
+              node = new AnalyserNode(context, {fftSize: 33});
+            },
+            'node = new AnalyserNode(c, { fftSize: 33 })')
+            .throw('IndexSizeError');
+        should(
+            () => {
+              node = new AnalyserNode(context, {maxDecibels: -500});
+            },
+            'node = new AnalyserNode(c, { maxDecibels: -500 })')
+            .throw('IndexSizeError');
+        should(
+            () => {
+              node = new AnalyserNode(context, {minDecibels: -10});
+            },
+            'node = new AnalyserNode(c, { minDecibels: -10 })')
+            .throw('IndexSizeError');
+        should(
+            () => {
+              node = new AnalyserNode(context, {smoothingTimeConstant: 2});
+            },
+            'node = new AnalyserNode(c, { smoothingTimeConstant: 2 })')
+            .throw('IndexSizeError');
+        should(function() {
+          node = new AnalyserNode(context, {frequencyBinCount: 33});
+        }, 'node = new AnalyserNode(c, { frequencyBinCount: 33 })').notThrow();
+        should(node.frequencyBinCount, 'node.frequencyBinCount')
+            .beEqualTo(1024);
+
+        task.done();
+      });
+
+      audit.define('setting min/max', (task, should) => {
+        let node;
+
+        // Recall the default values of minDecibels and maxDecibels are -100,
+        // and -30, respectively.  Setting both values in the constructor should
+        // not signal an error in any of the following cases.
+        let options = {minDecibels: -10, maxDecibels: 20};
+        should(
+            () => {
+              node = new AnalyserNode(context, options);
+            },
+            'node = new AnalyserNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        options = {maxDecibels: 20, minDecibels: -10};
+        should(
+            () => {
+              node = new AnalyserNode(context, options);
+            },
+            'node = new AnalyserNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        options = {minDecibels: -200, maxDecibels: -150};
+        should(
+            () => {
+              node = new AnalyserNode(context, options);
+            },
+            'node = new AnalyserNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        options = {maxDecibels: -150, minDecibels: -200};
+        should(
+            () => {
+              node = new AnalyserNode(context, options);
+            },
+            'node = new AnalyserNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        // But these should signal because minDecibel > maxDecibel
+        options = {maxDecibels: -150, minDecibels: -10};
+        should(
+            () => {
+              node = new AnalyserNode(context, options);
+            },
+            'node = new AnalyserNode(c, ' + JSON.stringify(options) + ')')
+            .throw('IndexSizeError');
+
+        options = {minDecibels: -10, maxDecibels: -150};
+        should(
+            () => {
+              node = new AnalyserNode(context, options);
+            },
+            'node = new AnalyserNode(c, ' + JSON.stringify(options) + ')')
+            .throw('IndexSizeError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.html b/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.html
new file mode 100644
index 0000000..e176d61
--- /dev/null
+++ b/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      realtimeanalyser-basic.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context = 0;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('Basic AnalyserNode test', function(task, should) {
+        context = new AudioContext();
+        let analyser = context.createAnalyser();
+
+        should(analyser.numberOfInputs, 'Number of inputs for AnalyserNode')
+            .beEqualTo(1);
+
+        should(analyser.numberOfOutputs, 'Number of outputs for AnalyserNode')
+            .beEqualTo(1);
+
+        should(analyser.minDecibels, 'Default minDecibels value')
+            .beEqualTo(-100);
+
+        should(analyser.maxDecibels, 'Default maxDecibels value')
+            .beEqualTo(-30);
+
+        should(
+            analyser.smoothingTimeConstant,
+            'Default smoothingTimeConstant value')
+            .beEqualTo(0.8);
+
+        let expectedValue = -50 - (1 / 3);
+        analyser.minDecibels = expectedValue;
+
+        should(analyser.minDecibels, 'node.minDecibels = ' + expectedValue)
+            .beEqualTo(expectedValue);
+
+        expectedValue = -40 - (1 / 3);
+        analyser.maxDecibels = expectedValue;
+
+        should(analyser.maxDecibels, 'node.maxDecibels = ' + expectedValue)
+            .beEqualTo(expectedValue);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-scaling.html b/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-scaling.html
new file mode 100644
index 0000000..6a0ab7d
--- /dev/null
+++ b/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-scaling.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      realtimeanalyser-fft-scaling.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <div id="description"></div>
+    <div id="console"></div>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // The number of analysers. We have analysers from size for each of the
+      // possible sizes of 2^5 to 2^15 for a total of 11.
+      let numberOfAnalysers = 11;
+      let sampleRate = 44100;
+      let nyquistFrequency = sampleRate / 2;
+
+      // Frequency of the sine wave test signal.  Should be high enough so that
+      // we get at least one full cycle for the 32-point FFT.  This should also
+      // be such that the frequency should be exactly in one of the FFT bins for
+      // each of the possible FFT sizes.
+      let oscFrequency = nyquistFrequency / 16;
+
+      // The actual peak values from each analyser.  Useful for examining the
+      // actual peak values.
+      let peakValue = new Array(numberOfAnalysers);
+
+      // For a 0dBFS sine wave, we would expect the FFT magnitude to be 0dB as
+      // well, but the analyzer node applies a Blackman window (to smooth the
+      // estimate).  This reduces the energy of the signal so the FFT peak is
+      // less than 0dB.  The threshold value given here was determined
+      // experimentally.
+      //
+      // See https://code.google.com/p/chromium/issues/detail?id=341596.
+      let peakThreshold = [
+        -14.43, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56, -13.56,
+        -13.56, -13.56
+      ];
+
+      function checkResult(order, analyser, should) {
+        return function() {
+          let index = order - 5;
+          let fftSize = 1 << order;
+          let fftData = new Float32Array(fftSize);
+          analyser.getFloatFrequencyData(fftData);
+
+          // Compute the frequency bin that should contain the peak.
+          let expectedBin =
+              analyser.frequencyBinCount * (oscFrequency / nyquistFrequency);
+
+          // Find the actual bin by finding the bin containing the peak.
+          let actualBin = 0;
+          peakValue[index] = -1000;
+          for (k = 0; k < analyser.frequencyBinCount; ++k) {
+            if (fftData[k] > peakValue[index]) {
+              actualBin = k;
+              peakValue[index] = fftData[k];
+            }
+          }
+
+          should(actualBin, (1 << order) + '-point FFT peak position')
+              .beEqualTo(expectedBin);
+
+          should(
+              peakValue[index], (1 << order) + '-point FFT peak value in dBFS')
+              .beGreaterThanOrEqualTo(peakThreshold[index]);
+        }
+      }
+
+      audit.define(
+          {
+            label: 'FFT scaling tests',
+            description: 'Test Scaling of FFT in AnalyserNode'
+          },
+          function(task, should) {
+            let tests = [];
+            for (let k = 5; k <= 15; ++k)
+              tests.push(runTest(k, should));
+
+            // The order in which the tests finish is not important.
+            Promise.all(tests).then(task.done.bind(task));
+          });
+
+      function runTest(order, should) {
+        let context = new OfflineAudioContext(1, 1 << order, sampleRate);
+        // Use a sine wave oscillator as the reference source signal.
+        let osc = context.createOscillator();
+        osc.type = 'sine';
+        osc.frequency.value = oscFrequency;
+        osc.connect(context.destination);
+
+        let analyser = context.createAnalyser();
+        // No smoothing to simplify the analysis of the result.
+        analyser.smoothingTimeConstant = 0;
+        analyser.fftSize = 1 << order;
+        osc.connect(analyser);
+
+        osc.start();
+        return context.startRendering().then(() => {
+          checkResult(order, analyser, should)();
+        });
+      }
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.html b/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.html
new file mode 100644
index 0000000..b3de37f
--- /dev/null
+++ b/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      realtimeanalyser-fft-sizing.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      function doTest(fftSize, illegal, should) {
+        let c = new OfflineAudioContext(1, 1000, 44100);
+        let a = c.createAnalyser();
+        let message = 'Setting fftSize to ' + fftSize;
+        let tester = function() {
+          a.fftSize = fftSize;
+        };
+
+        if (illegal) {
+          should(tester, message).throw('IndexSizeError');
+        } else {
+          should(tester, message).notThrow();
+        }
+      }
+
+      audit.define(
+          {
+            label: 'FFT size test',
+            description: 'Test that re-sizing the FFT arrays does not fail.'
+          },
+          function(task, should) {
+            doTest(-1, true, should);
+            doTest(0, true, should);
+            doTest(1, true, should);
+            for (let i = 2; i <= 0x20000; i *= 2) {
+              if (i >= 32 && i <= 32768)
+                doTest(i, false, should);
+              else
+                doTest(i, true, should);
+              doTest(i + 1, true, should);
+            }
+
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer-copy-channel.html b/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer-copy-channel.html
new file mode 100644
index 0000000..7d0af70
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer-copy-channel.html
@@ -0,0 +1,331 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Basic Functionality of AudioBuffer.copyFromChannel and
+      AudioBuffer.copyToChannel
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Define utility routines.
+
+      // Initialize the AudioBuffer |buffer| with a ramp signal on each channel.
+      // The ramp starts at channel number + 1.
+      function initializeAudioBufferRamp(buffer) {
+        for (let c = 0; c < buffer.numberOfChannels; ++c) {
+          let d = buffer.getChannelData(c);
+          for (let k = 0; k < d.length; ++k) {
+            d[k] = k + c + 1;
+          }
+        }
+      }
+
+      // Create a Float32Array of length |length| and initialize the array to
+      // -1.
+      function createInitializedF32Array(length) {
+        let x = new Float32Array(length);
+        for (let k = 0; k < length; ++k) {
+          x[k] = -1;
+        }
+        return x;
+      }
+
+      // Create a Float32Array of length |length| that is initialized to be a
+      // ramp starting at 1.
+      function createFloat32RampArray(length) {
+        let x = new Float32Array(length);
+        for (let k = 0; k < x.length; ++k) {
+          x[k] = k + 1;
+        }
+
+        return x;
+      }
+
+      // Test that the array |x| is a ramp starting at value |start| of length
+      // |length|, starting at |startIndex| in the array.  |startIndex| is
+      // optional and defaults to 0. Any other values must be -1.
+      function shouldBeRamp(
+          should, testName, x, startValue, length, startIndex) {
+        let k;
+        let startingIndex = startIndex || 0;
+        let expected = Array(x.length);
+
+        // Fill the expected array with the correct results.
+
+        // The initial part (if any) must be -1.
+        for (k = 0; k < startingIndex; ++k) {
+          expected[k] = -1;
+        }
+
+        // The second part should be a ramp starting with |startValue|
+        for (; k < startingIndex + length; ++k) {
+          expected[k] = startValue + k - startingIndex;
+        }
+
+        // The last part (if any) should be -1.
+        for (; k < x.length; ++k) {
+          expected[k] = -1;
+        }
+
+        should(x, testName, {numberOfArrayLog: 32}).beEqualToArray(expected);
+      }
+
+      let audit = Audit.createTaskRunner();
+
+      let context = new AudioContext();
+      // Temp array for testing exceptions for copyToChannel/copyFromChannel.
+      // The length is arbitrary.
+      let x = new Float32Array(8);
+
+      // Number of frames in the AudioBuffer for testing.  This is pretty
+      // arbitrary so choose a fairly small value.
+      let bufferLength = 16;
+
+      // Number of channels in the AudioBuffer.  Also arbitrary, but it should
+      // be greater than 1 for test coverage.
+      let numberOfChannels = 3;
+
+      // AudioBuffer that will be used for testing copyFrom and copyTo.
+      let buffer = context.createBuffer(
+          numberOfChannels, bufferLength, context.sampleRate);
+
+      let initialValues = Array(numberOfChannels);
+
+      // Initialize things
+      audit.define('initialize', (task, should) => {
+        // Initialize to -1.
+        initialValues.fill(-1);
+        should(initialValues, 'Initialized values').beConstantValueOf(-1)
+        task.done();
+      });
+
+      // Test that expected exceptions are signaled for copyFrom.
+      audit.define('copyFrom-exceptions', (task, should) => {
+        should(
+            AudioBuffer.prototype.copyFromChannel,
+            'AudioBuffer.prototype.copyFromChannel')
+            .exist();
+
+        should(
+            () => {
+              buffer = context.createBuffer(
+                  numberOfChannels, bufferLength, context.sampleRate);
+            },
+            '0: buffer = context.createBuffer(' + numberOfChannels + ', ' +
+                bufferLength + ', context.sampleRate)')
+            .notThrow();
+        should(() => {
+          buffer.copyFromChannel(null, 0);
+        }, '1: buffer.copyFromChannel(null, 0)').throw('TypeError');
+        should(() => {
+          buffer.copyFromChannel(context, 0);
+        }, '2: buffer.copyFromChannel(context, 0)').throw('TypeError');
+        should(() => {
+          buffer.copyFromChannel(x, -1);
+        }, '3: buffer.copyFromChannel(x, -1)').throw('IndexSizeError');
+        should(
+            () => {
+              buffer.copyFromChannel(x, numberOfChannels);
+            },
+            '4: buffer.copyFromChannel(x, ' + numberOfChannels + ')')
+            .throw('IndexSizeError');
+        ;
+        should(() => {
+          buffer.copyFromChannel(x, 0, -1);
+        }, '5: buffer.copyFromChannel(x, 0, -1)').throw('IndexSizeError');
+        should(
+            () => {
+              buffer.copyFromChannel(x, 0, bufferLength);
+            },
+            '6: buffer.copyFromChannel(x, 0, ' + bufferLength + ')')
+            .throw('IndexSizeError');
+
+        should(() => {
+          buffer.copyFromChannel(x, 3);
+        }, '7: buffer.copyFromChannel(x, 3)').throw('IndexSizeError');
+
+        if (window.SharedArrayBuffer) {
+          let shared_buffer = new Float32Array(new SharedArrayBuffer(32));
+          should(
+              () => {
+                buffer.copyFromChannel(shared_buffer, 0);
+              },
+              '8: buffer.copyFromChannel(SharedArrayBuffer view, 0)')
+              .throw('TypeError');
+
+          should(
+              () => {
+                buffer.copyFromChannel(shared_buffer, 0, 0);
+              },
+              '9: buffer.copyFromChannel(SharedArrayBuffer view, 0, 0)')
+              .throw('TypeError');
+        }
+
+        task.done();
+      });
+
+      // Test that expected exceptions are signaled for copyTo.
+      audit.define('copyTo-exceptions', (task, should) => {
+        should(
+            AudioBuffer.prototype.copyToChannel,
+            'AudioBuffer.prototype.copyToChannel')
+            .exist();
+        should(() => {
+          buffer.copyToChannel(null, 0);
+        }, '0: buffer.copyToChannel(null, 0)').throw('TypeError');
+        should(() => {
+          buffer.copyToChannel(context, 0);
+        }, '1: buffer.copyToChannel(context, 0)').throw('TypeError');
+        should(() => {
+          buffer.copyToChannel(x, -1);
+        }, '2: buffer.copyToChannel(x, -1)').throw('IndexSizeError');
+        should(
+            () => {
+              buffer.copyToChannel(x, numberOfChannels);
+            },
+            '3: buffer.copyToChannel(x, ' + numberOfChannels + ')')
+            .throw('IndexSizeError');
+        should(() => {
+          buffer.copyToChannel(x, 0, -1);
+        }, '4: buffer.copyToChannel(x, 0, -1)').throw('IndexSizeError');
+        should(
+            () => {
+              buffer.copyToChannel(x, 0, bufferLength);
+            },
+            '5: buffer.copyToChannel(x, 0, ' + bufferLength + ')')
+            .throw('IndexSizeError');
+
+        should(() => {
+          buffer.copyToChannel(x, 3);
+        }, '6: buffer.copyToChannel(x, 3)').throw('IndexSizeError');
+
+        if (window.SharedArrayBuffer) {
+          let shared_buffer = new Float32Array(new SharedArrayBuffer(32));
+          should(
+              () => {
+                buffer.copyToChannel(shared_buffer, 0);
+              },
+              '7: buffer.copyToChannel(SharedArrayBuffer view, 0)')
+              .throw('TypeError');
+
+          should(
+              () => {
+                buffer.copyToChannel(shared_buffer, 0, 0);
+              },
+              '8: buffer.copyToChannel(SharedArrayBuffer view, 0, 0)')
+              .throw('TypeError');
+        }
+
+        task.done();
+      });
+
+      // Test copyFromChannel
+      audit.define('copyFrom-validate', (task, should) => {
+        // Initialize the AudioBuffer to a ramp for testing copyFrom.
+        initializeAudioBufferRamp(buffer);
+
+        // Test copyFrom operation with a short destination array, filling the
+        // destination completely.
+        for (let c = 0; c < numberOfChannels; ++c) {
+          let dst8 = createInitializedF32Array(8);
+          buffer.copyFromChannel(dst8, c);
+          shouldBeRamp(
+              should, 'buffer.copyFromChannel(dst8, ' + c + ')', dst8, c + 1, 8)
+        }
+
+        // Test copyFrom operation with a short destination array using a
+        // non-zero start index that still fills the destination completely.
+        for (let c = 0; c < numberOfChannels; ++c) {
+          let dst8 = createInitializedF32Array(8);
+          buffer.copyFromChannel(dst8, c, 1);
+          shouldBeRamp(
+              should, 'buffer.copyFromChannel(dst8, ' + c + ', 1)', dst8, c + 2,
+              8)
+        }
+
+        // Test copyFrom operation with a short destination array using a
+        // non-zero start index that does not fill the destinatiom completely.
+        // The extra elements should be unchanged.
+        for (let c = 0; c < numberOfChannels; ++c) {
+          let dst8 = createInitializedF32Array(8);
+          let startInChannel = bufferLength - 5;
+          buffer.copyFromChannel(dst8, c, startInChannel);
+          shouldBeRamp(
+              should,
+              'buffer.copyFromChannel(dst8, ' + c + ', ' + startInChannel + ')',
+              dst8, c + 1 + startInChannel, bufferLength - startInChannel);
+        }
+
+        // Copy operation with the destination longer than the buffer, leaving
+        // the trailing elements of the destination untouched.
+        for (let c = 0; c < numberOfChannels; ++c) {
+          let dst26 = createInitializedF32Array(bufferLength + 10);
+          buffer.copyFromChannel(dst26, c);
+          shouldBeRamp(
+              should, 'buffer.copyFromChannel(dst26, ' + c + ')', dst26, c + 1,
+              bufferLength);
+        }
+
+        task.done();
+      });
+
+      // Test copyTo
+      audit.define('copyTo-validate', (task, should) => {
+        // Create a source consisting of a ramp starting at 1, longer than the
+        // AudioBuffer
+        let src = createFloat32RampArray(bufferLength + 10);
+
+        // Test copyTo with AudioBuffer shorter than Float32Array. The
+        // AudioBuffer should be completely filled with the Float32Array.
+        should(
+            () => {
+              buffer =
+                  createConstantBuffer(context, bufferLength, initialValues);
+            },
+            'buffer = createConstantBuffer(context, ' + bufferLength + ', [' +
+                initialValues + '])')
+            .notThrow();
+
+        for (let c = 0; c < numberOfChannels; ++c) {
+          buffer.copyToChannel(src, c);
+          shouldBeRamp(
+              should, 'buffer.copyToChannel(src, ' + c + ')',
+              buffer.getChannelData(c), 1, bufferLength);
+        }
+
+        // Test copyTo with AudioBuffer longer than the Float32Array.  The tail
+        // of the AudioBuffer should be unchanged.
+        buffer = createConstantBuffer(context, bufferLength, initialValues);
+        let src10 = createFloat32RampArray(10);
+        for (let c = 0; c < numberOfChannels; ++c) {
+          buffer.copyToChannel(src10, c);
+          shouldBeRamp(
+              should, 'buffer.copyToChannel(src10, ' + c + ')',
+              buffer.getChannelData(c), 1, 10);
+        }
+
+        // Test copyTo with non-default startInChannel.  Part of the AudioBuffer
+        // should filled with the beginning and end sections untouched.
+        buffer = createConstantBuffer(context, bufferLength, initialValues);
+        for (let c = 0; c < numberOfChannels; ++c) {
+          let startInChannel = 5;
+          buffer.copyToChannel(src10, c, startInChannel);
+
+          shouldBeRamp(
+              should,
+              'buffer.copyToChannel(src10, ' + c + ', ' + startInChannel + ')',
+              buffer.getChannelData(c), 1, src10.length, startInChannel);
+        }
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer-getChannelData.html b/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer-getChannelData.html
new file mode 100644
index 0000000..612a91c
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer-getChannelData.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioBuffer.getChannelData() Returns the Same Object
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+      let renderDuration = 0.5;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('buffer-eq', (task, should) => {
+        // Verify that successive calls to getChannelData return the same
+        // buffer.
+        let context = new AudioContext();
+        let channelCount = 2;
+        let frameLength = 1000;
+        let buffer =
+            context.createBuffer(channelCount, frameLength, context.sampleRate);
+
+        for (let c = 0; c < channelCount; ++c) {
+          let a = buffer.getChannelData(c);
+          let b = buffer.getChannelData(c);
+
+          let message = 'buffer.getChannelData(' + c + ')';
+          should(a === b, message + ' === ' + message).beEqualTo(true);
+        }
+
+        task.done();
+      });
+
+      audit.define('buffer-not-eq', (task, should) => {
+        let context = new AudioContext();
+        let channelCount = 2;
+        let frameLength = 1000;
+        let buffer1 =
+            context.createBuffer(channelCount, frameLength, context.sampleRate);
+        let buffer2 =
+            context.createBuffer(channelCount, frameLength, context.sampleRate);
+        let success = true;
+
+        for (let c = 0; c < channelCount; ++c) {
+          let a = buffer1.getChannelData(c);
+          let b = buffer2.getChannelData(c);
+
+          let message = 'getChannelData(' + c + ')';
+          should(a === b, 'buffer1.' + message + ' === buffer2.' + message)
+                  .beEqualTo(false) &&
+              success;
+        }
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer.html b/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer.html
new file mode 100644
index 0000000..07a34f0
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiobuffer-interface/audiobuffer.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audiobuffer.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 44100.0
+      let lengthInSeconds = 2;
+      let numberOfChannels = 4;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('Basic tests for AudioBuffer', function(task, should) {
+        let context = new AudioContext();
+        let buffer = context.createBuffer(
+            numberOfChannels, sampleRate * lengthInSeconds, sampleRate);
+
+        // Just for printing out a message describing what "buffer" is in the
+        // following tests.
+        should(
+            true,
+            'buffer = context.createBuffer(' + numberOfChannels + ', ' +
+                (sampleRate * lengthInSeconds) + ', ' + sampleRate + ')')
+            .beTrue();
+
+        should(buffer.sampleRate, 'buffer.sampleRate').beEqualTo(sampleRate);
+
+        should(buffer.length, 'buffer.length')
+            .beEqualTo(sampleRate * lengthInSeconds);
+
+        should(buffer.duration, 'buffer.duration').beEqualTo(lengthInSeconds);
+
+        should(buffer.numberOfChannels, 'buffer.numberOfChannels')
+            .beEqualTo(numberOfChannels);
+
+        for (let index = 0; index < buffer.numberOfChannels; ++index) {
+          should(
+              buffer.getChannelData(index) instanceof window.Float32Array,
+              'buffer.getChannelData(' + index +
+                  ') instanceof window.Float32Array')
+              .beTrue();
+        }
+
+        should(
+            function() {
+              buffer.getChannelData(buffer.numberOfChannels);
+            },
+            'buffer.getChannelData(' + buffer.numberOfChannels + ')')
+            .throw('IndexSizeError');
+
+        let buffer2 = context.createBuffer(1, 1000, 24576);
+        let expectedDuration = 1000 / 24576;
+
+        should(
+            buffer2.duration, 'context.createBuffer(1, 1000, 24576).duration')
+            .beEqualTo(expectedDuration);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiobuffer-interface/ctor-audiobuffer.html b/webaudio/the-audio-api/the-audiobuffer-interface/ctor-audiobuffer.html
new file mode 100644
index 0000000..f6032f2
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiobuffer-interface/ctor-audiobuffer.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: AudioBuffer
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        should(() => {
+          new AudioBuffer();
+        }, 'new AudioBuffer()').throw('TypeError');
+        should(() => {
+          new AudioBuffer(1);
+        }, 'new AudioBuffer(1)').throw('TypeError');
+        should(() => {
+          new AudioBuffer(Date, 42);
+        }, 'new AudioBuffer(Date, 42)').throw('TypeError');
+
+        task.done();
+      });
+
+      audit.define('required options', (task, should) => {
+        let buffer;
+
+        // The length and sampleRate attributes are required; all others are
+        // optional.
+        should(() => {
+          new AudioBuffer({});
+        }, 'buffer = new AudioBuffer({})').throw('TypeError');
+
+        should(() => {
+          new AudioBuffer({length: 1});
+        }, 'buffer = new AudioBuffer({length: 1})').throw('TypeError');
+
+        should(() => {
+          new AudioBuffer({sampleRate: 48000});
+        }, 'buffer = new AudioBuffer({sampleRate: 48000})').throw('TypeError');
+
+        should(() => {
+          buffer = new AudioBuffer({numberOfChannels: 1});
+        }, 'buffer = new AudioBuffer({numberOfChannels: 1}').throw('TypeError');
+
+        // Length and sampleRate are required, but others are optional.
+        should(
+            () => {
+              buffer =
+                  new AudioBuffer({length: 21, sampleRate: context.sampleRate});
+            },
+            'buffer0 = new AudioBuffer({length: 21, sampleRate: ' +
+                context.sampleRate + '}')
+            .notThrow();
+        // Verify the buffer has the correct values.
+        should(buffer.numberOfChannels, 'buffer0.numberOfChannels')
+            .beEqualTo(1);
+        should(buffer.length, 'buffer0.length').beEqualTo(21);
+        should(buffer.sampleRate, 'buffer0.sampleRate')
+            .beEqualTo(context.sampleRate);
+
+        should(
+            () => {
+              buffer = new AudioBuffer(
+                  {numberOfChannels: 3, length: 1, sampleRate: 48000});
+            },
+            'buffer1 = new AudioBuffer(' +
+                '{numberOfChannels: 3, length: 1, sampleRate: 48000})')
+            .notThrow();
+        // Verify the buffer has the correct values.
+        should(buffer.numberOfChannels, 'buffer1.numberOfChannels')
+            .beEqualTo(3);
+        should(buffer.length, 'buffer1.length').beEqualTo(1);
+        should(buffer.sampleRate, 'buffer1.sampleRate').beEqualTo(48000);
+
+        task.done();
+      });
+
+      audit.define('invalid option values', (task, should) => {
+        let options = {numberOfChannels: 0, length: 1, sampleRate: 16000};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        options = {numberOfChannels: 99, length: 0, sampleRate: 16000};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        options = {numberOfChannels: 1, length: 0, sampleRate: 16000};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        options = {numberOfChannels: 1, length: 1, sampleRate: 100};
+        should(
+            () => {
+              let buffer = new AudioBuffer(options);
+            },
+            'new AudioBuffer(' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let buffer;
+
+        let options = {numberOfChannels: 5, length: 17, sampleRate: 16000};
+        should(
+            () => {
+              buffer = new AudioBuffer(options);
+            },
+            'buffer = new AudioBuffer(' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        should(buffer.numberOfChannels, 'buffer.numberOfChannels')
+            .beEqualTo(options.numberOfChannels);
+        should(buffer.length, 'buffer.length').beEqualTo(options.length);
+        should(buffer.sampleRate, 'buffer.sampleRate').beEqualTo(16000);
+
+        task.done();
+      });
+
+      audit.define('valid constructor', (task, should) => {
+        let buffer;
+
+        let options = {numberOfChannels: 3, length: 42, sampleRate: 54321};
+
+        let message = 'new AudioBuffer(' + JSON.stringify(options) + ')';
+        should(() => {
+          buffer = new AudioBuffer(options);
+        }, message).notThrow();
+
+        should(buffer.numberOfChannels, 'buffer.numberOfChannels')
+            .beEqualTo(options.numberOfChannels);
+
+        should(buffer.length, 'buffer.length').beEqualTo(options.length);
+
+        should(buffer.sampleRate, 'buffer.sampleRate')
+            .beEqualTo(options.sampleRate);
+
+        // Verify that we actually got the right number of channels
+        for (let k = 0; k < options.numberOfChannels; ++k) {
+          let data;
+          let message = 'buffer.getChannelData(' + k + ')';
+          should(() => {
+            data = buffer.getChannelData(k);
+          }, message).notThrow();
+
+          should(data.length, message + ' length').beEqualTo(options.length);
+        }
+
+        should(
+            () => {
+              buffer.getChannelData(options.numberOfChannels);
+            },
+            'buffer.getChannelData(' + options.numberOfChannels + ')')
+            .throw('IndexSizeError');
+
+        task.done();
+      });
+
+      audit.define('multiple contexts', (task, should) => {
+        // Test that an AudioBuffer can be used for different contexts.
+        let buffer =
+            new AudioBuffer({length: 128, sampleRate: context.sampleRate});
+
+        // Don't use getChannelData here because we want to be able to use
+        // |data| to compare the final results of playing out this buffer.  (If
+        // we did, |data| gets detached when the sources play.)
+        let data = new Float32Array(buffer.length);
+        for (let k = 0; k < data.length; ++k)
+          data[k] = 1 + k;
+        buffer.copyToChannel(data, 0);
+
+        let c1 = new OfflineAudioContext(1, 128, context.sampleRate);
+        let c2 = new OfflineAudioContext(1, 128, context.sampleRate);
+
+        let s1 = new AudioBufferSourceNode(c1, {buffer: buffer});
+        let s2 = new AudioBufferSourceNode(c2, {buffer: buffer});
+
+        s1.connect(c1.destination);
+        s2.connect(c2.destination);
+
+        s1.start();
+        s2.start();
+
+        Promise
+            .all([
+              c1.startRendering().then(function(resultBuffer) {
+                return should(resultBuffer.getChannelData(0), 'c1 result')
+                    .beEqualToArray(data);
+              }),
+              c2.startRendering().then(function(resultBuffer) {
+                return should(resultBuffer.getChannelData(0), 'c2 result')
+                    .beEqualToArray(data);
+              }),
+            ])
+            .then(returnValues => {
+              should(
+                  returnValues[0] && returnValues[1],
+                  'AudioBuffer shared between two different contexts')
+                  .message('correctly', 'incorrectly');
+              task.done();
+            });
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiobuffersourcenode-interface/ctor-audiobuffersource.html b/webaudio/the-audio-api/the-audiobuffersourcenode-interface/ctor-audiobuffersource.html
new file mode 100644
index 0000000..c1c3203
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiobuffersourcenode-interface/ctor-audiobuffersource.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: AudioBufferSource
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'AudioBufferSourceNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node =
+            testDefaultConstructor(should, 'AudioBufferSourceNode', context, {
+              prefix: prefix,
+              numberOfInputs: 0,
+              numberOfOutputs: 1,
+              channelCount: 2,
+              channelCountMode: 'max',
+              channelInterpretation: 'speakers'
+            });
+
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'buffer', value: null},
+          {name: 'detune', value: 0},
+          {name: 'loop', value: false},
+          {name: 'loopEnd', value: 0.0},
+          {name: 'loopStart', value: 0.0},
+          {name: 'playbackRate', value: 1.0},
+        ]);
+
+        task.done();
+      });
+
+      audit.define('nullable buffer', (task, should) => {
+        let node;
+        let options = {buffer: null};
+
+        should(
+            () => {
+              node = new AudioBufferSourceNode(context, options);
+            },
+            'node1 = new AudioBufferSourceNode(c, ' + JSON.stringify(options))
+            .notThrow();
+
+        should(node.buffer, 'node1.buffer').beEqualTo(null);
+
+        task.done();
+      });
+
+      audit.define('constructor options', (task, should) => {
+        let node;
+        let buffer = context.createBuffer(2, 1000, context.sampleRate);
+
+        let options = {
+          buffer: buffer,
+          detune: .5,
+          loop: true,
+          loopEnd: (buffer.length / 2) / context.sampleRate,
+          loopStart: 5 / context.sampleRate,
+          playbackRate: .75
+        };
+
+        let message = 'node = new AudioBufferSourceNode(c, ' +
+            JSON.stringify(options) + ')';
+
+        should(() => {
+          node = new AudioBufferSourceNode(context, options);
+        }, message).notThrow();
+
+        // Use the factory method to create an equivalent node and compare the
+        // results from the constructor against this node.
+        let factoryNode = context.createBufferSource();
+        factoryNode.buffer = options.buffer;
+        factoryNode.detune.value = options.detune;
+        factoryNode.loop = options.loop;
+        factoryNode.loopEnd = options.loopEnd;
+        factoryNode.loopStart = options.loopStart;
+        factoryNode.playbackRate.value = options.playbackRate;
+
+        should(node.buffer === buffer, 'node2.buffer === buffer')
+            .beEqualTo(true);
+        should(node.detune.value, 'node2.detune.value')
+            .beEqualTo(factoryNode.detune.value);
+        should(node.loop, 'node2.loop').beEqualTo(factoryNode.loop);
+        should(node.loopEnd, 'node2.loopEnd').beEqualTo(factoryNode.loopEnd);
+        should(node.loopStart, 'node2.loopStart')
+            .beEqualTo(factoryNode.loopStart);
+        should(node.playbackRate.value, 'node2.playbackRate.value')
+            .beEqualTo(factoryNode.playbackRate.value);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-getoutputtimestamp.html b/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-getoutputtimestamp.html
new file mode 100644
index 0000000..cd09696
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-getoutputtimestamp.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Testing AudioContext.getOutputTimestamp() method
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define('getoutputtimestamp-initial-values', function(task, should) {
+        let context = new AudioContext;
+        let timestamp = context.getOutputTimestamp();
+
+        should(timestamp.contextTime, 'timestamp.contextTime').exist();
+        should(timestamp.performanceTime, 'timestamp.performanceTime').exist();
+
+        should(timestamp.contextTime, 'timestamp.contextTime').beEqualTo(0);
+        should(timestamp.performanceTime, 'timestamp.performanceTime')
+            .beEqualTo(0);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-suspend-resume.html b/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-suspend-resume.html
new file mode 100644
index 0000000..48506d6
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-suspend-resume.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioContext.suspend() and AudioContext.resume()
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let offlineContext;
+      let osc;
+      let p1;
+      let p2;
+      let p3;
+
+      let sampleRate = 44100;
+      let durationInSeconds = 1;
+
+      let audit = Audit.createTaskRunner();
+
+      // Task: test suspend().
+      audit.define(
+          {
+            label: 'test-suspend',
+            description: 'Test suspend() for offline context'
+          },
+          function(task, should) {
+            // Test suspend/resume.  Ideally this test is best with a online
+            // AudioContext, but content shell doesn't really have a working
+            // online AudioContext. Hence, use an OfflineAudioContext. Not all
+            // possible scenarios can be easily checked with an offline context
+            // instead of an online context.
+
+            // Create an audio context with an oscillator.
+            should(
+                () => {
+                  offlineContext = new OfflineAudioContext(
+                      1, durationInSeconds * sampleRate, sampleRate);
+                },
+                'offlineContext = new OfflineAudioContext(1, ' +
+                    (durationInSeconds * sampleRate) + ', ' + sampleRate + ')')
+                .notThrow();
+            osc = offlineContext.createOscillator();
+            osc.connect(offlineContext.destination);
+
+            // Verify the state.
+            should(offlineContext.state, 'offlineContext.state')
+                .beEqualTo('suspended');
+
+            // Multiple calls to suspend() should not be a problem. But we can't
+            // test that on an offline context.  Thus, check that suspend() on
+            // an OfflineAudioContext rejects the promise.
+            should(
+                () => p1 = offlineContext.suspend(),
+                'p1 = offlineContext.suspend()')
+                .notThrow();
+            should(p1 instanceof Promise, 'p1 instanceof Promise').beTrue();
+
+            should(p1, 'p1').beRejected().then(task.done.bind(task));
+          });
+
+
+      // Task: test resume().
+      audit.define(
+          {
+            label: 'test-resume',
+            description: 'Test resume() for offline context'
+          },
+          function(task, should) {
+            // Multiple calls to resume should not be a problem. But we can't
+            // test that on an offline context. Thus, check that resume() on an
+            // OfflineAudioContext rejects the promise.
+            should(
+                () => p2 = offlineContext.resume(),
+                'p2 = offlineContext.resume()')
+                .notThrow();
+            should(p2 instanceof Promise, 'p2 instanceof Promise').beTrue();
+
+            // Resume doesn't actually resume an offline context
+            should(offlineContext.state, 'After resume, offlineContext.state')
+                .beEqualTo('suspended');
+            should(p2, 'p2').beRejected().then(task.done.bind(task));
+          });
+
+      // Task: test the state after context closed.
+      audit.define(
+          {
+            label: 'test-after-close',
+            description: 'Test state after context closed'
+          },
+          function(task, should) {
+            // Render the offline context.
+            osc.start();
+
+            // Test suspend/resume in tested promise pattern. We don't care
+            // about the actual result of the offline rendering.
+            should(
+                () => p3 = offlineContext.startRendering(),
+                'p3 = offlineContext.startRendering()')
+                .notThrow();
+
+            p3.then(() => {
+              should(offlineContext.state, 'After close, offlineContext.state')
+                  .beEqualTo('closed');
+
+              // suspend() should be rejected on a closed context.
+              should(offlineContext.suspend(), 'offlineContext.suspend()')
+                  .beRejected()
+                  .then(() => {
+                    // resume() should be rejected on closed context.
+                    should(offlineContext.resume(), 'offlineContext.resume()')
+                        .beRejected()
+                        .then(task.done.bind(task));
+                  })
+            });
+          });
+
+      audit.define(
+          {
+            label: 'resume-running-context',
+            description: 'Test resuming a running context'
+          },
+          (task, should) => {
+            let context;
+            should(() => context = new AudioContext(), 'Create online context')
+                .notThrow();
+
+            should(context.state, 'context.state').beEqualTo('running');
+            should(context.resume(), 'context.resume')
+                .beResolved()
+                .then(() => {
+                  should(context.state, 'context.state after resume')
+                      .beEqualTo('running');
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html b/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
new file mode 100644
index 0000000..295cd5a
--- /dev/null
+++ b/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioContextOptions
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+      let defaultLatency;
+      let interactiveLatency;
+      let balancedLatency;
+      let playbackLatency;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test-audiocontextoptions-latencyHint-basic',
+            description: 'Test creating contexts with basic latencyHint types.'
+          },
+          function(task, should) {
+            let closingPromises = [];
+
+            // Verify that an AudioContext can be created with default options.
+            should(function() {
+              context = new AudioContext()
+            }, 'context = new AudioContext()').notThrow();
+
+            should(context.sampleRate,
+              `context.sampleRate (${context.sampleRate} Hz)`).beGreaterThan(0);
+
+            defaultLatency = context.baseLatency;
+            should(defaultLatency, 'default baseLatency').beGreaterThan(0);
+
+            // Verify that an AudioContext can be created with the expected
+            // latency types.
+            should(
+                function() {
+                  context = new AudioContext({'latencyHint': 'interactive'})
+                },
+                'context = new AudioContext({\'latencyHint\': \'interactive\'})')
+                .notThrow();
+
+            interactiveLatency = context.baseLatency;
+            should(interactiveLatency, 'interactive baseLatency')
+                .beEqualTo(defaultLatency);
+            closingPromises.push(context.close());
+
+            should(
+                function() {
+                  context = new AudioContext({'latencyHint': 'balanced'})
+                },
+                'context = new AudioContext({\'latencyHint\': \'balanced\'})')
+                .notThrow();
+
+            balancedLatency = context.baseLatency;
+            should(balancedLatency, 'balanced baseLatency')
+                .beGreaterThanOrEqualTo(interactiveLatency);
+            closingPromises.push(context.close());
+
+            should(
+                function() {
+                  context = new AudioContext({'latencyHint': 'playback'})
+                },
+                'context = new AudioContext({\'latencyHint\': \'playback\'})')
+                .notThrow();
+
+            playbackLatency = context.baseLatency;
+            should(playbackLatency, 'playback baseLatency')
+                .beGreaterThanOrEqualTo(balancedLatency);
+            closingPromises.push(context.close());
+
+            Promise.all(closingPromises).then(function() {
+              task.done();
+            });
+          });
+
+      audit.define(
+          {
+            label: 'test-audiocontextoptions-latencyHint-double',
+            description:
+                'Test creating contexts with explicit latencyHint values.'
+          },
+          function(task, should) {
+            let closingPromises = [];
+
+            // Verify too small exact latency clamped to 'interactive'
+            should(
+                function() {
+                  context =
+                      new AudioContext({'latencyHint': interactiveLatency / 2})
+                },
+                'context = new AudioContext({\'latencyHint\': ' +
+                    'interactiveLatency/2})')
+                .notThrow();
+            should(context.baseLatency, 'double-constructor baseLatency small')
+                .beLessThanOrEqualTo(interactiveLatency);
+            closingPromises.push(context.close());
+
+            // Verify that exact latency in range works as expected
+            let validLatency = (interactiveLatency + playbackLatency) / 2;
+            should(
+                function() {
+                  context = new AudioContext({'latencyHint': validLatency})
+                },
+                'context = new AudioContext({\'latencyHint\': validLatency})')
+                .notThrow();
+            should(
+                context.baseLatency, 'double-constructor baseLatency inrange 1')
+                .beGreaterThanOrEqualTo(interactiveLatency);
+            should(
+                context.baseLatency, 'double-constructor baseLatency inrange 2')
+                .beLessThanOrEqualTo(playbackLatency);
+            closingPromises.push(context.close());
+
+            // Verify too big exact latency clamped to some value
+            let context1;
+            let context2;
+            should(function() {
+              context1 =
+                  new AudioContext({'latencyHint': playbackLatency * 10});
+              context2 =
+                  new AudioContext({'latencyHint': playbackLatency * 20});
+            }, 'creating two high latency contexts').notThrow();
+            should(context1.baseLatency, 'high latency context baseLatency')
+                .beEqualTo(context2.baseLatency);
+            should(context1.baseLatency, 'high latency context baseLatency')
+                .beGreaterThan(interactiveLatency);
+            closingPromises.push(context1.close());
+            closingPromises.push(context2.close());
+
+            // Verify that invalid latencyHint values are rejected.
+            should(
+                function() {
+                  context = new AudioContext({'latencyHint': 'foo'})
+                },
+                'context = new AudioContext({\'latencyHint\': \'foo\'})')
+                .throw('TypeError');
+
+            // Verify that no extra options can be passed into the
+            // AudioContextOptions.
+            should(
+                function() {
+                  context = new AudioContext('latencyHint')
+                },
+                'context = new AudioContext(\'latencyHint\')')
+                .throw('TypeError');
+
+            Promise.all(closingPromises).then(function() {
+              task.done();
+            });
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html b/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html
new file mode 100644
index 0000000..a1c3273
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html
@@ -0,0 +1,277 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audionode-channel-rules.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/mixing-rules.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+      let context = 0;
+      let sampleRate = 44100;
+      let renderNumberOfChannels = 8;
+      let singleTestFrameLength = 8;
+      let testBuffers;
+
+      // A list of connections to an AudioNode input, each of which is to be
+      // used in one or more specific test cases.  Each element in the list is a
+      // string, with the number of connections corresponding to the length of
+      // the string, and each character in the string is from '1' to '8'
+      // representing a 1 to 8 channel connection (from an AudioNode output).
+
+      // For example, the string "128" means 3 connections, having 1, 2, and 8
+      // channels respectively.
+
+      let connectionsList = [
+        '1', '2', '3', '4', '5', '6', '7', '8', '11', '12', '14', '18', '111',
+        '122', '123', '124', '128'
+      ];
+
+      // A list of mixing rules, each of which will be tested against all of the
+      // connections in connectionsList.
+      let mixingRulesList = [
+        {
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        },
+        {
+          channelCount: 4,
+          channelCountMode: 'clamped-max',
+          channelInterpretation: 'speakers'
+        },
+
+        // Test up-down-mix to some explicit speaker layouts.
+        {
+          channelCount: 1,
+          channelCountMode: 'explicit',
+          channelInterpretation: 'speakers'
+        },
+        {
+          channelCount: 2,
+          channelCountMode: 'explicit',
+          channelInterpretation: 'speakers'
+        },
+        {
+          channelCount: 4,
+          channelCountMode: 'explicit',
+          channelInterpretation: 'speakers'
+        },
+        {
+          channelCount: 6,
+          channelCountMode: 'explicit',
+          channelInterpretation: 'speakers'
+        },
+
+        {
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'discrete'
+        },
+        {
+          channelCount: 4,
+          channelCountMode: 'clamped-max',
+          channelInterpretation: 'discrete'
+        },
+        {
+          channelCount: 4,
+          channelCountMode: 'explicit',
+          channelInterpretation: 'discrete'
+        },
+        {
+          channelCount: 8,
+          channelCountMode: 'explicit',
+          channelInterpretation: 'discrete'
+        },
+      ];
+
+      let numberOfTests = mixingRulesList.length * connectionsList.length;
+
+      // Print out the information for an individual test case.
+      function printTestInformation(
+          testNumber, actualBuffer, expectedBuffer, frameLength, frameOffset) {
+        let actual = stringifyBuffer(actualBuffer, frameLength);
+        let expected =
+            stringifyBuffer(expectedBuffer, frameLength, frameOffset);
+        debug('TEST CASE #' + testNumber + '\n');
+        debug('actual channels:\n' + actual);
+        debug('expected channels:\n' + expected);
+      }
+
+      function scheduleTest(
+          testNumber, connections, channelCount, channelCountMode,
+          channelInterpretation) {
+        let mixNode = context.createGain();
+        mixNode.channelCount = channelCount;
+        mixNode.channelCountMode = channelCountMode;
+        mixNode.channelInterpretation = channelInterpretation;
+        mixNode.connect(context.destination);
+
+        for (let i = 0; i < connections.length; ++i) {
+          let connectionNumberOfChannels =
+              connections.charCodeAt(i) - '0'.charCodeAt(0);
+
+          let source = context.createBufferSource();
+          // Get a buffer with the right number of channels, converting from
+          // 1-based to 0-based index.
+          let buffer = testBuffers[connectionNumberOfChannels - 1];
+          source.buffer = buffer;
+          source.connect(mixNode);
+
+          // Start at the right offset.
+          let sampleFrameOffset = testNumber * singleTestFrameLength;
+          let time = sampleFrameOffset / sampleRate;
+          source.start(time);
+        }
+      }
+
+      function checkTestResult(
+          renderedBuffer, testNumber, connections, channelCount,
+          channelCountMode, channelInterpretation, should) {
+        let s = 'connections: ' + connections + ', ' + channelCountMode;
+
+        // channelCount is ignored in "max" mode.
+        if (channelCountMode == 'clamped-max' ||
+            channelCountMode == 'explicit') {
+          s += '(' + channelCount + ')';
+        }
+
+        s += ', ' + channelInterpretation;
+
+        let computedNumberOfChannels = computeNumberOfChannels(
+            connections, channelCount, channelCountMode);
+
+        // Create a zero-initialized silent AudioBuffer with
+        // computedNumberOfChannels.
+        let destBuffer = context.createBuffer(
+            computedNumberOfChannels, singleTestFrameLength,
+            context.sampleRate);
+
+        // Mix all of the connections into the destination buffer.
+        for (let i = 0; i < connections.length; ++i) {
+          let connectionNumberOfChannels =
+              connections.charCodeAt(i) - '0'.charCodeAt(0);
+          let sourceBuffer =
+              testBuffers[connectionNumberOfChannels - 1];  // convert from
+                                                            // 1-based to
+                                                            // 0-based index
+
+          if (channelInterpretation == 'speakers') {
+            speakersSum(sourceBuffer, destBuffer);
+          } else if (channelInterpretation == 'discrete') {
+            discreteSum(sourceBuffer, destBuffer);
+          } else {
+            alert('Invalid channel interpretation!');
+          }
+        }
+
+        // Use this when debugging mixing rules.
+        // printTestInformation(testNumber, renderedBuffer, destBuffer,
+        // singleTestFrameLength, sampleFrameOffset);
+
+        // Validate that destBuffer matches the rendered output.  We need to
+        // check the rendered output at a specific sample-frame-offset
+        // corresponding to the specific test case we're checking for based on
+        // testNumber.
+
+        let sampleFrameOffset = testNumber * singleTestFrameLength;
+        for (let c = 0; c < renderNumberOfChannels; ++c) {
+          let renderedData = renderedBuffer.getChannelData(c);
+          for (let frame = 0; frame < singleTestFrameLength; ++frame) {
+            let renderedValue = renderedData[frame + sampleFrameOffset];
+
+            let expectedValue = 0;
+            if (c < destBuffer.numberOfChannels) {
+              let expectedData = destBuffer.getChannelData(c);
+              expectedValue = expectedData[frame];
+            }
+
+            // We may need to add an epsilon in the comparison if we add more
+            // test vectors.
+            if (renderedValue != expectedValue) {
+              let message = s + 'rendered: ' + renderedValue +
+                  ' expected: ' + expectedValue + ' channel: ' + c +
+                  ' frame: ' + frame;
+              // testFailed(s);
+              should(renderedValue, s).beEqualTo(expectedValue);
+              return;
+            }
+          }
+        }
+
+        should(true, s).beTrue();
+      }
+
+      function checkResult(buffer, should) {
+        // Sanity check result.
+        should(buffer.length, 'Rendered number of frames')
+            .beEqualTo(numberOfTests * singleTestFrameLength);
+        should(buffer.numberOfChannels, 'Rendered number of channels')
+            .beEqualTo(renderNumberOfChannels);
+
+        // Check all the tests.
+        let testNumber = 0;
+        for (let m = 0; m < mixingRulesList.length; ++m) {
+          let mixingRules = mixingRulesList[m];
+          for (let i = 0; i < connectionsList.length; ++i, ++testNumber) {
+            checkTestResult(
+                buffer, testNumber, connectionsList[i],
+                mixingRules.channelCount, mixingRules.channelCountMode,
+                mixingRules.channelInterpretation, should);
+          }
+        }
+      }
+
+      audit.define(
+          {label: 'test', description: 'Channel mixing rules for AudioNodes'},
+          function(task, should) {
+
+            // Create 8-channel offline audio context.  Each test will render 8
+            // sample-frames starting at sample-frame position testNumber * 8.
+            let totalFrameLength = numberOfTests * singleTestFrameLength;
+            context = new OfflineAudioContext(
+                renderNumberOfChannels, totalFrameLength, sampleRate);
+
+            // Set destination to discrete mixing.
+            context.destination.channelCount = renderNumberOfChannels;
+            context.destination.channelCountMode = 'explicit';
+            context.destination.channelInterpretation = 'discrete';
+
+            // Create test buffers from 1 to 8 channels.
+            testBuffers = new Array();
+            for (let i = 0; i < renderNumberOfChannels; ++i) {
+              testBuffers[i] = createShiftedImpulseBuffer(
+                  context, i + 1, singleTestFrameLength);
+            }
+
+            // Schedule all the tests.
+            let testNumber = 0;
+            for (let m = 0; m < mixingRulesList.length; ++m) {
+              let mixingRules = mixingRulesList[m];
+              for (let i = 0; i < connectionsList.length; ++i, ++testNumber) {
+                scheduleTest(
+                    testNumber, connectionsList[i], mixingRules.channelCount,
+                    mixingRules.channelCountMode,
+                    mixingRules.channelInterpretation);
+              }
+            }
+
+            // Render then check results.
+            // context.oncomplete = checkResult;
+            context.startRendering().then(buffer => {
+              checkResult(buffer, should);
+              task.done();
+            });
+            ;
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audionode-interface/audionode-connect-method-chaining.html b/webaudio/the-audio-api/the-audionode-interface/audionode-connect-method-chaining.html
new file mode 100644
index 0000000..0a8c731
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/audionode-connect-method-chaining.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audionode-connect-method-chaining.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // AudioNode dictionary with associated arguments.
+      let nodeDictionary = [
+        {name: 'Analyser'}, {name: 'BiquadFilter'}, {name: 'BufferSource'},
+        {name: 'ChannelMerger', args: [6]},
+        {name: 'ChannelSplitter', args: [6]}, {name: 'Convolver'},
+        {name: 'Delay', args: []}, {name: 'DynamicsCompressor'}, {name: 'Gain'},
+        {name: 'Oscillator'}, {name: 'Panner'},
+        {name: 'ScriptProcessor', args: [512, 1, 1]}, {name: 'StereoPanner'},
+        {name: 'WaveShaper'}
+      ];
+
+
+      function verifyReturnedNode(should, config) {
+        should(
+            config.destination === config.returned,
+            'The return value of ' + config.desc + ' matches the destination ' +
+                config.returned.constructor.name)
+            .beEqualTo(true);
+      }
+
+      // Test utility for batch method checking: in order to test 3 method
+      // signatures, so we create 3 dummy destinations.
+      // 1) .connect(GainNode)
+      // 2) .connect(BiquadFilterNode, output)
+      // 3) .connect(ChannelMergerNode, output, input)
+      function testConnectMethod(context, should, options) {
+        let source =
+            context['create' + options.name].apply(context, options.args);
+        let sourceName = source.constructor.name;
+
+        let destination1 = context.createGain();
+        verifyReturnedNode(should, {
+          source: source,
+          destination: destination1,
+          returned: source.connect(destination1),
+          desc: sourceName + '.connect(' + destination1.constructor.name + ')'
+        });
+
+        let destination2 = context.createBiquadFilter();
+        verifyReturnedNode(should, {
+          source: source,
+          destination: destination2,
+          returned: source.connect(destination2, 0),
+          desc:
+              sourceName + '.connect(' + destination2.constructor.name + ', 0)'
+        });
+
+        let destination3 = context.createChannelMerger();
+        verifyReturnedNode(should, {
+          source: source,
+          destination: destination3,
+          returned: source.connect(destination3, 0, 1),
+          desc: sourceName + '.connect(' + destination3.constructor.name +
+              ', 0, 1)'
+        });
+      }
+
+
+      let audit = Audit.createTaskRunner();
+
+      // Task: testing entries from the dictionary.
+      audit.define('from-dictionary', (task, should) => {
+        let context = new AudioContext();
+
+        for (let i = 0; i < nodeDictionary.length; i++)
+          testConnectMethod(context, should, nodeDictionary[i]);
+
+        task.done();
+      });
+
+      // Task: testing Media* nodes.
+      audit.define('media-group', (task, should) => {
+        let context = new AudioContext();
+
+        // Test MediaElementSourceNode needs an <audio> element.
+        let mediaElement = document.createElement('audio');
+        testConnectMethod(
+            context, should,
+            {name: 'MediaElementSource', args: [mediaElement]});
+
+        testConnectMethod(context, should, {name: 'MediaStreamDestination'});
+
+        // MediaStreamSourceNode requires 'stream' object to be constructed,
+        // which is a part of MediaStreamDestinationNode.
+        let streamDestination = context.createMediaStreamDestination();
+        let stream = streamDestination.stream;
+        testConnectMethod(
+            context, should, {name: 'MediaStreamSource', args: [stream]});
+
+        task.done();
+      });
+
+      // Task: test the exception thrown by invalid operation.
+      audit.define('invalid-operation', (task, should) => {
+        let contextA = new AudioContext();
+        let contextB = new AudioContext();
+        let gain1 = contextA.createGain();
+        let gain2 = contextA.createGain();
+
+        // Test if the first connection throws correctly. The first gain node
+        // does not have the second output, so it should throw.
+        should(function() {
+          gain1.connect(gain2, 1).connect(contextA.destination);
+        }, 'Connecting with an invalid output').throw('IndexSizeError');
+
+        // Test if the second connection throws correctly. The contextB's
+        // destination is not compatible with the nodes from contextA, thus the
+        // first connection succeeds but the second one should throw.
+        should(
+            function() {
+              gain1.connect(gain2).connect(contextB.destination);
+            },
+            'Connecting to a node from the different context')
+            .throw('InvalidAccessError');
+
+        task.done();
+      });
+
+      // Task: verify if the method chaining actually works.
+      audit.define('verification', (task, should) => {
+        // We pick the lowest sample rate allowed to run the test efficiently.
+        let context = new OfflineAudioContext(1, 128, 3000);
+
+        let constantBuffer = createConstantBuffer(context, 1, 1.0);
+
+        let source = context.createBufferSource();
+        source.buffer = constantBuffer;
+        source.loop = true;
+
+        let gain1 = context.createGain();
+        gain1.gain.value = 0.5;
+        let gain2 = context.createGain();
+        gain2.gain.value = 0.25;
+
+        source.connect(gain1).connect(gain2).connect(context.destination);
+        source.start();
+
+        context.startRendering()
+            .then(function(buffer) {
+              should(
+                  buffer.getChannelData(0),
+                  'The output of chained connection of gain nodes')
+                  .beConstantValueOf(0.125);
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audionode-interface/audionode-connect-order.html b/webaudio/the-audio-api/the-audionode-interface/audionode-connect-order.html
new file mode 100644
index 0000000..eca15de
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/audionode-connect-order.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audionode-connect-order.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+      let sampleRate = 44100.0;
+      let renderLengthSeconds = 0.125;
+      let delayTimeSeconds = 0.1;
+
+      function createSinWaveBuffer(context, lengthInSeconds, frequency) {
+        let audioBuffer =
+            context.createBuffer(1, lengthInSeconds * sampleRate, sampleRate);
+
+        let n = audioBuffer.length;
+        let data = audioBuffer.getChannelData(0);
+
+        for (let i = 0; i < n; ++i) {
+          data[i] = Math.sin(frequency * 2 * Math.PI * i / sampleRate);
+        }
+
+        return audioBuffer;
+      }
+
+      audit.define(
+          {
+            label: 'Test connections',
+            description:
+                'AudioNode connection order doesn\'t trigger assertion errors'
+          },
+          function(task, should) {
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                1, sampleRate * renderLengthSeconds, sampleRate);
+            let toneBuffer =
+                createSinWaveBuffer(context, renderLengthSeconds, 880);
+
+            let bufferSource = context.createBufferSource();
+            bufferSource.buffer = toneBuffer;
+            bufferSource.connect(context.destination);
+
+            let delay = context.createDelay();
+            delay.delayTime.value = delayTimeSeconds;
+
+            // We connect delay node to gain node before anything is connected
+            // to delay node itself.  We do this because we try to trigger the
+            // ASSERT which might be fired due to AudioNode connection order,
+            // especially when gain node and delay node is involved e.g.
+            // https://bugs.webkit.org/show_bug.cgi?id=76685.
+
+            should(() => {
+              let gain = context.createGain();
+              gain.connect(context.destination);
+              delay.connect(gain);
+            }, 'Connecting nodes').notThrow();
+
+            bufferSource.start(0);
+
+            let promise = context.startRendering();
+
+            should(promise, 'OfflineContext startRendering()')
+                .beResolved()
+                .then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect-audioparam.html b/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect-audioparam.html
new file mode 100644
index 0000000..c3d3fae
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect-audioparam.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audionode-disconnect-audioparam.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let renderQuantum = 128;
+
+      let sampleRate = 44100;
+      let renderDuration = 0.5;
+      let disconnectTime = 0.5 * renderDuration;
+
+      let audit = Audit.createTaskRunner();
+
+      // Calculate the index for disconnection.
+      function getDisconnectIndex(disconnectTime) {
+        let disconnectIndex = disconnectTime * sampleRate;
+        return disconnectIndex -= (disconnectIndex) % renderQuantum;
+      }
+
+      // Get the index of value change.
+      function getValueChangeIndex(array, targetValue) {
+        return array.findIndex(function(element, index) {
+          if (element === targetValue)
+            return true;
+        });
+      }
+
+      // Task 1: test disconnect(AudioParam) method.
+      audit.define('disconnect(AudioParam)', (task, should) => {
+
+        // Creates a buffer source with value [1] and then connect it to two
+        // gain nodes in series. The output of the buffer source is lowered by
+        // half
+        // (* 0.5) and then connected to two |.gain| AudioParams in each gain
+        // node.
+        //
+        //  (1) bufferSource => gain1 => gain2
+        //  (2) bufferSource => half => gain1.gain
+        //  (3) half => gain2.gain
+        //
+        // This graph should produce the output of 2.25 (= 1 * 1.5 * 1.5). After
+        // disconnecting (3), it should produce 1.5.
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+        let source = context.createBufferSource();
+        let buffer1ch = createConstantBuffer(context, 1, 1);
+        let half = context.createGain();
+        let gain1 = context.createGain();
+        let gain2 = context.createGain();
+
+        source.buffer = buffer1ch;
+        source.loop = true;
+        half.gain.value = 0.5;
+
+        source.connect(gain1);
+        gain1.connect(gain2);
+        gain2.connect(context.destination);
+        source.connect(half);
+
+        // Connecting |half| to both |gain1.gain| and |gain2.gain| amplifies the
+        // signal by 2.25 (= 1.5 * 1.5) because each gain node amplifies the
+        // signal by 1.5 (= 1.0 + 0.5).
+        half.connect(gain1.gain);
+        half.connect(gain2.gain);
+
+        source.start();
+
+        // Schedule the disconnection at the half of render duration.
+        context.suspend(disconnectTime).then(function() {
+          half.disconnect(gain2.gain);
+          context.resume();
+        });
+
+        context.startRendering()
+            .then(function(buffer) {
+              let channelData = buffer.getChannelData(0);
+              let disconnectIndex = getDisconnectIndex(disconnectTime);
+              let valueChangeIndex = getValueChangeIndex(channelData, 1.5);
+
+              // Expected values are: 1 * 1.5 * 1.5 -> 1 * 1.5 = [2.25, 1.5]
+              should(channelData, 'Channel #0').containValues([2.25, 1.5]);
+              should(valueChangeIndex, 'The index of value change')
+                  .beEqualTo(disconnectIndex);
+
+            })
+            .then(() => task.done());
+      });
+
+      // Task 2: test disconnect(AudioParam, output) method.
+      audit.define('disconnect(AudioParam, output)', (task, should) => {
+
+        // Create a 2-channel buffer source with [1, 2] in each channel and
+        // make a serial connection through gain1 and gain 2. The make the
+        // buffer source half with a gain node and connect it to a 2-output
+        // splitter. Connect each output to 2 gain AudioParams respectively.
+        //
+        //    (1) bufferSource => gain1 => gain2
+        //    (2) bufferSource => half => splitter(2)
+        //    (3) splitter#0 => gain1.gain
+        //    (4) splitter#1 => gain2.gain
+        //
+        // This graph should produce 3 (= 1 * 1.5 * 2) and 6 (= 2 * 1.5 * 2) for
+        // each channel. After disconnecting (4), it should output 1.5 and 3.
+        let context =
+            new OfflineAudioContext(2, renderDuration * sampleRate, sampleRate);
+        let source = context.createBufferSource();
+        let buffer2ch = createConstantBuffer(context, 1, [1, 2]);
+        let splitter = context.createChannelSplitter(2);
+        let half = context.createGain();
+        let gain1 = context.createGain();
+        let gain2 = context.createGain();
+
+        source.buffer = buffer2ch;
+        source.loop = true;
+        half.gain.value = 0.5;
+
+        source.connect(gain1);
+        gain1.connect(gain2);
+        gain2.connect(context.destination);
+
+        // |source| originally is [1, 2] but it becomes [0.5, 1] after 0.5 gain.
+        // Each splitter's output will be applied to |gain1.gain| and
+        // |gain2.gain| respectively in an additive fashion.
+        source.connect(half);
+        half.connect(splitter);
+
+        // This amplifies the signal by 1.5. (= 1.0 + 0.5)
+        splitter.connect(gain1.gain, 0);
+
+        // This amplifies the signal by 2. (= 1.0 + 1.0)
+        splitter.connect(gain2.gain, 1);
+
+        source.start();
+
+        // Schedule the disconnection at the half of render duration.
+        context.suspend(disconnectTime).then(function() {
+          splitter.disconnect(gain2.gain, 1);
+          context.resume();
+        });
+
+        context.startRendering()
+            .then(function(buffer) {
+              let channelData0 = buffer.getChannelData(0);
+              let channelData1 = buffer.getChannelData(1);
+
+              let disconnectIndex = getDisconnectIndex(disconnectTime);
+              let valueChangeIndexCh0 = getValueChangeIndex(channelData0, 1.5);
+              let valueChangeIndexCh1 = getValueChangeIndex(channelData1, 3);
+
+              // Expected values are: 1 * 1.5 * 2 -> 1 * 1.5 = [3, 1.5]
+              should(channelData0, 'Channel #0').containValues([3, 1.5]);
+              should(
+                  valueChangeIndexCh0,
+                  'The index of value change in channel #0')
+                  .beEqualTo(disconnectIndex);
+
+              // Expected values are: 2 * 1.5 * 2 -> 2 * 1.5 = [6, 3]
+              should(channelData1, 'Channel #1').containValues([6, 3]);
+              should(
+                  valueChangeIndexCh1,
+                  'The index of value change in channel #1')
+                  .beEqualTo(disconnectIndex);
+
+            })
+            .then(() => task.done());
+      });
+
+      // Task 3: exception checks.
+      audit.define('exceptions', (task, should) => {
+        let context = new AudioContext();
+        let gain1 = context.createGain();
+        let splitter = context.createChannelSplitter(2);
+        let gain2 = context.createGain();
+        let gain3 = context.createGain();
+
+        // Connect a splitter to gain nodes and merger so we can test the
+        // possible ways of disconnecting the nodes to verify that appropriate
+        // exceptions are thrown.
+        gain1.connect(splitter);
+        splitter.connect(gain2.gain, 0);
+        splitter.connect(gain3.gain, 1);
+        gain2.connect(gain3);
+        gain3.connect(context.destination);
+
+        // gain1 is not connected to gain3.gain. Exception should be thrown.
+        should(function() {
+          gain1.disconnect(gain3.gain);
+        }, 'gain1.disconnect(gain3.gain)').throw('InvalidAccessError');
+
+        // When the output index is good but the destination is invalid.
+        should(function() {
+          splitter.disconnect(gain1.gain, 1);
+        }, 'splitter.disconnect(gain1.gain, 1)').throw('InvalidAccessError');
+
+        // When both arguments are wrong, throw IndexSizeError first.
+        should(function() {
+          splitter.disconnect(gain1.gain, 2);
+        }, 'splitter.disconnect(gain1.gain, 2)').throw('IndexSizeError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect.html b/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect.html
new file mode 100644
index 0000000..b29c09d
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect.html
@@ -0,0 +1,298 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audionode-disconnect.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Task 1: test disconnect() method.
+      audit.define('disconnect()', (task, should) => {
+
+        // Connect a source to multiple gain nodes, each connected to the
+        // destination. Then disconnect the source. The expected output should
+        // be all zeros since the source was disconnected.
+        let context = new OfflineAudioContext(1, 128, 44100);
+        let source = context.createBufferSource();
+        let buffer1ch = createConstantBuffer(context, 128, [1]);
+        let gain1 = context.createGain();
+        let gain2 = context.createGain();
+        let gain3 = context.createGain();
+
+        source.buffer = buffer1ch;
+
+        source.connect(gain1);
+        source.connect(gain2);
+        source.connect(gain3);
+        gain1.connect(context.destination);
+        gain2.connect(context.destination);
+        gain3.connect(context.destination);
+        source.start();
+
+        // This disconnects everything.
+        source.disconnect();
+
+        context.startRendering()
+            .then(function(buffer) {
+
+              // With everything disconnected, the result should be zero.
+              should(buffer.getChannelData(0), 'Channel #0')
+                  .beConstantValueOf(0);
+
+            })
+            .then(() => task.done());
+      });
+
+      // Task 2: test disconnect(output) method.
+      audit.define('disconnect(output)', (task, should) => {
+
+        // Create multiple connections from each output of a ChannelSplitter
+        // to a gain node. Then test if disconnecting a single output of
+        // splitter is actually disconnected.
+        let context = new OfflineAudioContext(1, 128, 44100);
+        let source = context.createBufferSource();
+        let buffer3ch = createConstantBuffer(context, 128, [1, 2, 3]);
+        let splitter = context.createChannelSplitter(3);
+        let sum = context.createGain();
+
+        source.buffer = buffer3ch;
+
+        source.connect(splitter);
+        splitter.connect(sum, 0);
+        splitter.connect(sum, 1);
+        splitter.connect(sum, 2);
+        sum.connect(context.destination);
+        source.start();
+
+        // This disconnects the second output.
+        splitter.disconnect(1);
+
+        context.startRendering()
+            .then(function(buffer) {
+
+              // The rendered channel should contain 4. (= 1 + 0 + 3)
+              should(buffer.getChannelData(0), 'Channel #0')
+                  .beConstantValueOf(4);
+
+            })
+            .then(() => task.done());
+      });
+
+      // Task 3: test disconnect(AudioNode) method.
+      audit.define('disconnect(AudioNode)', (task, should) => {
+
+        // Connect a source to multiple gain nodes. Then test if disconnecting a
+        // single destination selectively works correctly.
+        let context = new OfflineAudioContext(1, 128, 44100);
+        let source = context.createBufferSource();
+        let buffer1ch = createConstantBuffer(context, 128, [1]);
+        let gain1 = context.createGain();
+        let gain2 = context.createGain();
+        let gain3 = context.createGain();
+        let orphan = context.createGain();
+
+        source.buffer = buffer1ch;
+
+        source.connect(gain1);
+        source.connect(gain2);
+        source.connect(gain3);
+        gain1.connect(context.destination);
+        gain2.connect(context.destination);
+        gain3.connect(context.destination);
+        source.start();
+
+        source.disconnect(gain2);
+
+        context.startRendering()
+            .then(function(buffer) {
+
+              // The |sum| gain node should produce value 2. (1 + 0 + 1 = 2)
+              should(buffer.getChannelData(0), 'Channel #0')
+                  .beConstantValueOf(2);
+
+            })
+            .then(() => task.done());
+      });
+
+      // Task 4: test disconnect(AudioNode, output) method.
+      audit.define('disconnect(AudioNode, output)', (task, should) => {
+
+        // Connect a buffer with 2 channels with each containing 1 and 2
+        // respectively to a ChannelSplitter, then connect the splitter to 2
+        // gain nodes as shown below:
+        //   (1) splitter#0 => gain1
+        //   (2) splitter#0 => gain2
+        //   (3) splitter#1 => gain2
+        // Then disconnect (2) and verify if the selective disconnection on a
+        // specified output of the destination node works correctly.
+        let context = new OfflineAudioContext(1, 128, 44100);
+        let source = context.createBufferSource();
+        let buffer2ch = createConstantBuffer(context, 128, [1, 2]);
+        let splitter = context.createChannelSplitter(2);
+        let gain1 = context.createGain();
+        let gain2 = context.createGain();
+
+        source.buffer = buffer2ch;
+
+        source.connect(splitter);
+        splitter.connect(gain1, 0);  // gain1 gets channel 0.
+        splitter.connect(gain2, 0);  // gain2 sums channel 0 and 1.
+        splitter.connect(gain2, 1);
+        gain1.connect(context.destination);
+        gain2.connect(context.destination);
+        source.start();
+
+        splitter.disconnect(gain2, 0);  // Now gain2 gets [2]
+
+        context.startRendering()
+            .then(function(buffer) {
+
+              // The sum of gain1 and gain2 should produce value 3. (= 1 + 2)
+              should(buffer.getChannelData(0), 'Channel #0')
+                  .beConstantValueOf(3);
+
+            })
+            .then(() => task.done());
+      });
+
+      // Task 5: test disconnect(AudioNode, output, input) method.
+      audit.define('disconnect(AudioNode, output, input)', (task, should) => {
+
+        // Create a 3-channel buffer with [1, 2, 3] in each channel and then
+        // pass it through a splitter and a merger. Each input/output of the
+        // splitter and the merger is connected in a sequential order as shown
+        // below.
+        //   (1) splitter#0 => merger#0
+        //   (2) splitter#1 => merger#1
+        //   (3) splitter#2 => merger#2
+        // Then disconnect (3) and verify if each channel contains [1] and [2]
+        // respectively.
+        let context = new OfflineAudioContext(3, 128, 44100);
+        let source = context.createBufferSource();
+        let buffer3ch = createConstantBuffer(context, 128, [1, 2, 3]);
+        let splitter = context.createChannelSplitter(3);
+        let merger = context.createChannelMerger(3);
+
+        source.buffer = buffer3ch;
+
+        source.connect(splitter);
+        splitter.connect(merger, 0, 0);
+        splitter.connect(merger, 1, 1);
+        splitter.connect(merger, 2, 2);
+        merger.connect(context.destination);
+        source.start();
+
+        splitter.disconnect(merger, 2, 2);
+
+        context.startRendering()
+            .then(function(buffer) {
+
+              // Each channel should have 1, 2, and 0 respectively.
+              should(buffer.getChannelData(0), 'Channel #0')
+                  .beConstantValueOf(1);
+              should(buffer.getChannelData(1), 'Channel #1')
+                  .beConstantValueOf(2);
+              should(buffer.getChannelData(2), 'Channel #2')
+                  .beConstantValueOf(0);
+
+            })
+            .then(() => task.done());
+      });
+
+      // Task 6: exception checks.
+      audit.define('exceptions', (task, should) => {
+        let context = new OfflineAudioContext(2, 128, 44100);
+        let gain1 = context.createGain();
+        let splitter = context.createChannelSplitter(2);
+        let merger = context.createChannelMerger(2);
+        let gain2 = context.createGain();
+        let gain3 = context.createGain();
+
+        // Connect a splitter to gain nodes and merger so we can test the
+        // possible ways of disconnecting the nodes to verify that appropriate
+        // exceptions are thrown.
+        gain1.connect(splitter);
+        splitter.connect(gain2, 0);
+        splitter.connect(gain3, 1);
+        splitter.connect(merger, 0, 0);
+        splitter.connect(merger, 1, 1);
+        gain2.connect(gain3);
+        gain3.connect(context.destination);
+        merger.connect(context.destination);
+
+        // There is no output #2. An exception should be thrown.
+        should(function() {
+          splitter.disconnect(2);
+        }, 'splitter.disconnect(2)').throw('IndexSizeError');
+
+        // Disconnecting the output already disconnected should not throw.
+        should(function() {
+          splitter.disconnect(1);
+          splitter.disconnect(1);
+        }, 'Disconnecting a connection twice').notThrow();
+
+        // gain1 is not connected gain2. An exception should be thrown.
+        should(function() {
+          gain1.disconnect(gain2);
+        }, 'gain1.disconnect(gain2)').throw('InvalidAccessError');
+
+        // gain1 and gain3 are not connected. An exception should be thrown.
+        should(function() {
+          gain1.disconnect(gain3);
+        }, 'gain1.disconnect(gain3)').throw('InvalidAccessError');
+
+        // There is no output #2 in the splitter. An exception should be thrown.
+        should(function() {
+          splitter.disconnect(gain2, 2);
+        }, 'splitter.disconnect(gain2, 2)').throw('IndexSizeError');
+
+        // The splitter and gain1 are not connected. An exception should be
+        // thrown.
+        should(function() {
+          splitter.disconnect(gain1, 0);
+        }, 'splitter.disconnect(gain1, 0)').throw('InvalidAccessError');
+
+        // The splitter output #0 and the gain3 output #0 are not connected. An
+        // exception should be thrown.
+        should(function() {
+          splitter.disconnect(gain3, 0, 0);
+        }, 'splitter.disconnect(gain3, 0, 0)').throw('InvalidAccessError');
+
+        // The output index is out of bound. An exception should be thrown.
+        should(function() {
+          splitter.disconnect(merger, 3, 0);
+        }, 'splitter.disconnect(merger, 3, 0)').throw('IndexSizeError');
+
+        task.done();
+      });
+
+      audit.define('disabled-outputs', (task, should) => {
+        // See crbug.com/656652
+        let context = new OfflineAudioContext(2, 1024, 44100);
+        let g1 = context.createGain();
+        let g2 = context.createGain();
+        g1.connect(g2);
+        g1.disconnect(g2);
+        let g3 = context.createGain();
+        g2.connect(g3);
+        g1.connect(g2);
+        context.startRendering()
+            .then(function() {
+              // If we make it here, we passed.
+              should(true, 'Disabled outputs handled')
+                  .message('correctly', 'inccorrectly');
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audionode-interface/audionode.html b/webaudio/the-audio-api/the-audionode-interface/audionode.html
new file mode 100644
index 0000000..b75cd0b
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/audionode.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audionode.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <div id="description"></div>
+    <div id="console"></div>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let context = 0;
+      let context2 = 0;
+      let context3 = 0;
+
+      audit.define(
+          {label: 'test', description: 'Basic tests for AudioNode API.'},
+          function(task, should) {
+
+            context = new AudioContext();
+            window.audioNode = context.createBufferSource();
+
+            // Check input and output numbers of AudioSourceNode.
+            should(audioNode.numberOfInputs, 'AudioBufferSource.numberOfInputs')
+                .beEqualTo(0);
+            should(
+                audioNode.numberOfOutputs, 'AudioBufferSource.numberOfOutputs')
+                .beEqualTo(1);
+
+            // Check input and output numbers of AudioDestinationNode
+            should(
+                context.destination.numberOfInputs,
+                'AudioContext.destination.numberOfInputs')
+                .beEqualTo(1);
+            should(
+                context.destination.numberOfOutputs,
+                'AudioContext.destination.numberOfOutputs')
+                .beEqualTo(0);
+
+            // Try calling connect() method with illegal values.
+            should(
+                () => audioNode.connect(0, 0, 0), 'audioNode.connect(0, 0, 0)')
+                .throw('TypeError');
+            should(
+                () => audioNode.connect(null, 0, 0),
+                'audioNode.connect(null, 0, 0)')
+                .throw('TypeError');
+            should(
+                () => audioNode.connect(context.destination, 5, 0),
+                'audioNode.connect(context.destination, 5, 0)')
+                .throw('IndexSizeError');
+            should(
+                () => audioNode.connect(context.destination, 0, 5),
+                'audioNode.connect(context.destination, 0, 5)')
+                .throw('IndexSizeError');
+
+            should(
+                () => audioNode.connect(context.destination, 0, 0),
+                'audioNode.connect(context.destination, 0, 0)')
+                .notThrow();
+
+            // Create a new context and try to connect the other context's node
+            // to this one.
+            context2 = new AudioContext();
+            should(
+                () => window.audioNode.connect(context2.destination),
+                'Connecting a node to a different context')
+                .throw('InvalidAccessError');
+
+            // 3-arg AudioContext doesn't create an offline context anymore.
+            should(
+                () => context3 = new AudioContext(1, 44100, 44100),
+                'context3 = new AudioContext(1, 44100, 44100)')
+                .throw('TypeError');
+
+            // Ensure it is an EventTarget
+            should(
+                audioNode instanceof EventTarget, 'AudioNode is an EventTarget')
+                .beTrue();
+
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audionode-interface/channel-mode-interp-basic.html b/webaudio/the-audio-api/the-audionode-interface/channel-mode-interp-basic.html
new file mode 100644
index 0000000..35cfca8
--- /dev/null
+++ b/webaudio/the-audio-api/the-audionode-interface/channel-mode-interp-basic.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Setting of channelCountMode and channelInterpretation
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Fairly arbitrary sample rate and number of frames, except the number of
+      // frames should be more than a few render quantums.
+      let sampleRate = 16000;
+      let renderFrames = 10 * 128;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('interp', (task, should) => {
+        let context = new OfflineAudioContext(1, renderFrames, sampleRate);
+        let node = context.createGain();
+
+        // Set a new interpretation and verify that it changed.
+        node.channelInterpretation = 'discrete';
+        let value = node.channelInterpretation;
+        should(value, 'node.channelInterpretation').beEqualTo('discrete');
+        node.connect(context.destination);
+
+        context.startRendering()
+            .then(function(buffer) {
+              // After rendering, the value should have been changed.
+              should(
+                  node.channelInterpretation,
+                  'After rendering node.channelInterpretation')
+                  .beEqualTo('discrete');
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('mode', (task, should) => {
+        let context = new OfflineAudioContext(1, renderFrames, sampleRate);
+        let node = context.createGain();
+
+        // Set a new mode and verify that it changed.
+        node.channelCountMode = 'explicit';
+        let value = node.channelCountMode;
+        should(value, 'node.channelCountMode').beEqualTo('explicit');
+        node.connect(context.destination);
+
+        context.startRendering()
+            .then(function(buffer) {
+              // After rendering, the value should have been changed.
+              should(
+                  node.channelCountMode,
+                  'After rendering node.channelCountMode')
+                  .beEqualTo('explicit');
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-connect-audioratesignal.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-connect-audioratesignal.html
new file mode 100644
index 0000000..517d64f
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-connect-audioratesignal.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<!-- 
+Tests that an audio-rate signal (AudioNode output) can be connected to an
+AudioParam.  Specifically, this tests that an audio-rate signal coming from an
+AudioBufferSourceNode playing an AudioBuffer containing a specific curve can be
+connected to an AudioGainNode's .gain attribute (an AudioParam).  Another
+AudioBufferSourceNode will be the audio source having its gain changed.  We load
+this one with an AudioBuffer containing a constant value of 1.  Thus it's easy
+to check that the resultant signal should be equal to the gain-scaling curve.
+-->
+<html>
+  <head>
+    <title>
+      audioparam-connect-audioratesignal.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 44100.0;
+      let lengthInSeconds = 1;
+
+      let context = 0;
+      let constantOneBuffer = 0;
+      let linearRampBuffer = 0;
+
+      function checkResult(renderedBuffer, should) {
+        let renderedData = renderedBuffer.getChannelData(0);
+        let expectedData = linearRampBuffer.getChannelData(0);
+        let n = renderedBuffer.length;
+
+        should(n, 'Rendered signal length').beEqualTo(linearRampBuffer.length);
+
+        // Check that the rendered result exactly matches the buffer used to
+        // control gain.  This is because we're changing the gain of a signal
+        // having constant value 1.
+        let success = true;
+        for (let i = 0; i < n; ++i) {
+          if (renderedData[i] != expectedData[i]) {
+            success = false;
+            break;
+          }
+        }
+
+        should(
+            success,
+            'Rendered signal exactly matches the audio-rate gain changing signal')
+            .beTrue();
+      }
+
+      audit.define('test', function(task, should) {
+        let sampleFrameLength = sampleRate * lengthInSeconds;
+
+        // Create offline audio context.
+        context = new OfflineAudioContext(1, sampleFrameLength, sampleRate);
+
+        // Create buffer used by the source which will have its gain controlled.
+        constantOneBuffer = createConstantBuffer(context, sampleFrameLength, 1);
+
+        // Create buffer used to control gain.
+        linearRampBuffer = createLinearRampBuffer(context, sampleFrameLength);
+
+        // Create the two sources.
+
+        let constantSource = context.createBufferSource();
+        constantSource.buffer = constantOneBuffer;
+
+        let gainChangingSource = context.createBufferSource();
+        gainChangingSource.buffer = linearRampBuffer;
+
+        // Create a gain node controlling the gain of constantSource and make
+        // the connections.
+        let gainNode = context.createGain();
+
+        // Intrinsic baseline gain of zero.
+        gainNode.gain.value = 0;
+
+        constantSource.connect(gainNode);
+        gainNode.connect(context.destination);
+
+        // Connect an audio-rate signal to control the .gain AudioParam.
+        // This is the heart of what is being tested.
+        gainChangingSource.connect(gainNode.gain);
+
+        // Start both sources at time 0.
+        constantSource.start(0);
+        gainChangingSource.start(0);
+
+        context.startRendering().then(buffer => {
+          checkResult(buffer, should);
+          task.done();
+        });
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-exceptional-values.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-exceptional-values.html
new file mode 100644
index 0000000..c2d18de
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-exceptional-values.html
@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audioparam-exceptional-values.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Context to use for all of the tests.  The context isn't used for any
+      // processing; just need one for creating a gain node, which is used for
+      // all the tests.
+      let context;
+
+      // For these values, AudioParam methods should throw a Typeerror because
+      // they are not finite values.
+      let nonFiniteValues = [Infinity, -Infinity, NaN];
+
+      audit.define('initialize', (task, should) => {
+        should(() => {
+          // Context for testing.  Rendering isn't done, so any valid values can
+          // be used here so might as well make them small.
+          context = new OfflineAudioContext(1, 1, 8000);
+        }, 'Creating context for testing').notThrow();
+
+        task.done();
+      });
+
+      audit.define(
+          {
+            label: 'test value',
+            description: 'Test non-finite arguments for AudioParam value'
+          },
+          (task, should) => {
+            let gain = context.createGain();
+
+            // Default method for generating the arguments for an automation
+            // method for testing the value of the automation.
+            let defaultFuncArg = (value) => [value, 1];
+
+            // Test the value parameter
+            doTests(should, gain, 'TypeError', nonFiniteValues, [
+              {automationName: 'setValueAtTime', funcArg: defaultFuncArg}, {
+                automationName: 'linearRampToValueAtTime',
+                funcArg: defaultFuncArg
+              },
+              {
+                automationName: 'exponentialRampToValueAtTime',
+                funcArg: defaultFuncArg
+              },
+              {
+                automationName: 'setTargetAtTime',
+                funcArg: (value) => [value, 1, 1]
+              }
+            ]);
+            task.done();
+          });
+
+      audit.define(
+          {
+            label: 'test time',
+            description: 'Test non-finite arguments for AudioParam time'
+          },
+          (task, should) => {
+            let gain = context.createGain();
+
+            // Default method for generating the arguments for an automation
+            // method for testing the time parameter of the automation.
+            let defaultFuncArg = (startTime) => [1, startTime];
+
+            // Test the time parameter
+            doTests(should, gain, 'TypeError', nonFiniteValues, [
+              {automationName: 'setValueAtTime', funcArg: defaultFuncArg},
+              {
+                automationName: 'linearRampToValueAtTime',
+                funcArg: defaultFuncArg
+              },
+              {
+                automationName: 'exponentialRampToValueAtTime',
+                funcArg: defaultFuncArg
+              },
+              // Test start time for setTarget
+              {
+                automationName: 'setTargetAtTime',
+                funcArg: (startTime) => [1, startTime, 1]
+              },
+              // Test time constant for setTarget
+              {
+                automationName: 'setTargetAtTime',
+                funcArg: (timeConstant) => [1, 1, timeConstant]
+              },
+            ]);
+
+            task.done();
+          });
+
+      audit.define(
+          {
+            label: 'test setValueCurve',
+            description: 'Test non-finite arguments for setValueCurveAtTime'
+          },
+          (task, should) => {
+            let gain = context.createGain();
+
+            // Just an array for use by setValueCurveAtTime. The length and
+            // contents of the array are not important.
+            let curve = new Float32Array(3);
+
+            doTests(should, gain, 'TypeError', nonFiniteValues, [
+              {
+                automationName: 'setValueCurveAtTime',
+                funcArg: (startTime) => [curve, startTime, 1]
+              },
+            ]);
+
+            // Non-finite values for the curve should signal an error
+            doTests(
+                should, gain, 'TypeError',
+                [[1, 2, Infinity, 3], [1, NaN, 2, 3]], [{
+                  automationName: 'setValueCurveAtTime',
+                  funcArg: (c) => [c, 1, 1]
+                }]);
+
+            task.done();
+          });
+
+      audit.define(
+          {
+            label: 'special cases 1',
+            description: 'Test exceptions for finite values'
+          },
+          (task, should) => {
+            let gain = context.createGain();
+
+            // Default method for generating the arguments for an automation
+            // method for testing the time parameter of the automation.
+            let defaultFuncArg = (startTime) => [1, startTime];
+
+            // Test the time parameter
+            let curve = new Float32Array(3);
+            doTests(should, gain, 'RangeError', [-1], [
+              {automationName: 'setValueAtTime', funcArg: defaultFuncArg},
+              {
+                automationName: 'linearRampToValueAtTime',
+                funcArg: defaultFuncArg
+              },
+              {
+                automationName: 'exponentialRampToValueAtTime',
+                funcArg: defaultFuncArg
+              },
+              {
+                automationName: 'setTargetAtTime',
+                funcArg: (startTime) => [1, startTime, 1]
+              },
+              // Test time constant
+              {
+                automationName: 'setTargetAtTime',
+                funcArg: (timeConstant) => [1, 1, timeConstant]
+              },
+              // startTime and duration for setValueCurve
+              {
+                automationName: 'setValueCurveAtTime',
+                funcArg: (startTime) => [curve, startTime, 1]
+              },
+              {
+                automationName: 'setValueCurveAtTime',
+                funcArg: (duration) => [curve, 1, duration]
+              },
+            ]);
+
+            // One final test for setValueCurve: duration can't be 0.
+            should(
+                () => gain.gain.setValueCurveAtTime(curve, 1, 0),
+                'gain.gain.setValueCurveAtTime(curve, 1, 0)')
+                .throw('RangeError');
+
+            task.done();
+          });
+
+      audit.define(
+          {
+            label: 'special cases 2',
+            description: 'Test special cases for expeonentialRamp'
+          },
+          (task, should) => {
+            let gain = context.createGain();
+
+            doTests(should, gain, 'RangeError', [0, -1e-100, 1e-100], [{
+                      automationName: 'exponentialRampToValueAtTime',
+                      funcArg: (value) => [value, 1]
+                    }]);
+
+            task.done();
+          });
+
+      audit.run();
+
+      // Run test over the set of values in |testValues| for all of the
+      // automation methods in |testMethods|.  The expected error type is
+      // |errorName|. |testMethods| is an array of dictionaries with attributes
+      // |automationName| giving the name of the automation method to be tested
+      // and |funcArg| being a function of one parameter that produces an array
+      // that will be used as the argument to the automation method.
+      function doTests(should, node, errorName, testValues, testMethods) {
+        testValues.forEach(value => {
+          testMethods.forEach(method => {
+            let args = method.funcArg(value);
+            let message = 'gain.gain.' + method.automationName + '(' +
+                argString(args) + ')';
+            should(() => node.gain[method.automationName](...args), message)
+                .throw(errorName);
+          });
+        });
+      }
+
+      // Specialized printer for automation arguments so that messages make
+      // sense.  We assume the first element is either a number or an array.  If
+      // it's an array, there are always three elements, and we want to print
+      // out the brackets for the array argument.
+      function argString(arg) {
+        if (typeof(arg[0]) === 'number') {
+          return arg.toString();
+        }
+
+        return '[' + arg[0] + '],' + arg[1] + ',' + arg[2];
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-exponentialRampToValueAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-exponentialRampToValueAtTime.html
new file mode 100644
index 0000000..bec4c12
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-exponentialRampToValueAtTime.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioParam.exponentialRampToValueAtTime
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Play a long DC signal out through an AudioGainNode, and call
+      // setValueAtTime() and exponentialRampToValueAtTime() at regular
+      // intervals to set the starting and ending values for an exponential
+      // ramp.  Each time interval has a ramp with a different starting and
+      // ending value so that there is a discontinuity at each time interval
+      // boundary.  The discontinuity is for testing timing.  Also, we alternate
+      // between an increasing and decreasing ramp for each interval.
+
+      // Number of tests to run.
+      let numberOfTests = 100;
+
+      // Max allowed difference between the rendered data and the expected
+      // result.
+      let maxAllowedError = 1.222e-5;
+
+      // The AudioGainNode starts with this value instead of the default value.
+      let initialValue = 100;
+
+      // Set the gain node value to the specified value at the specified time.
+      function setValue(value, time) {
+        gainNode.gain.setValueAtTime(value, time);
+      }
+
+      // Generate an exponential ramp ending at time |endTime| with an ending
+      // value of |value|.
+      function generateRamp(value, startTime, endTime){
+          // |startTime| is ignored because the exponential ramp
+          // uses the value from the setValueAtTime() call above.
+          gainNode.gain.exponentialRampToValueAtTime(value, endTime)}
+
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'AudioParam exponentialRampToValueAtTime() functionality'
+          },
+          function(task, should) {
+            createAudioGraphAndTest(
+                task, should, numberOfTests, initialValue, setValue,
+                generateRamp, 'exponentialRampToValueAtTime()', maxAllowedError,
+                createExponentialRampArray);
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-large-endtime.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-large-endtime.html
new file mode 100644
index 0000000..d8f38ee
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-large-endtime.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      AudioParam with Huge End Time
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+      // Render for some small (but fairly arbitrary) time.
+      let renderDuration = 0.125;
+      // Any huge time value that won't fit in a size_t (2^64 on a 64-bit
+      // machine).
+      let largeTime = 1e300;
+
+      let audit = Audit.createTaskRunner();
+
+      // See crbug.com/582701.  Create an audioparam with a huge end time and
+      // verify that to automation is run.  We don't care about the actual
+      // results, just that it runs.
+
+      // Test linear ramp with huge end time
+      audit.define('linearRamp', (task, should) => {
+        let graph = createGraph();
+        graph.gain.gain.linearRampToValueAtTime(0.1, largeTime);
+
+        graph.source.start();
+        graph.context.startRendering()
+            .then(function(buffer) {
+              should(true, 'linearRampToValue(0.1, ' + largeTime + ')')
+                  .message('successfully rendered', 'unsuccessfully rendered');
+            })
+            .then(() => task.done());
+      });
+
+      // Test exponential ramp with huge end time
+      audit.define('exponentialRamp', (task, should) => {
+        let graph = createGraph();
+        graph.gain.gain.exponentialRampToValueAtTime(.1, largeTime);
+
+        graph.source.start();
+        graph.context.startRendering()
+            .then(function(buffer) {
+              should(true, 'exponentialRampToValue(0.1, ' + largeTime + ')')
+                  .message('successfully rendered', 'unsuccessfully rendered');
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+
+      // Create the graph and return the context, the source, and the gain node.
+      function createGraph() {
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+        let src = context.createBufferSource();
+        src.buffer = createConstantBuffer(context, 1, 1);
+        src.loop = true;
+        let gain = context.createGain();
+        src.connect(gain);
+        gain.connect(context.destination);
+        gain.gain.setValueAtTime(1, 0.1 / sampleRate);
+
+        return {context: context, gain: gain, source: src};
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-linearRampToValueAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-linearRampToValueAtTime.html
new file mode 100644
index 0000000..509c254
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-linearRampToValueAtTime.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioParam.linearRampToValueAtTime
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Play a long DC signal out through an AudioGainNode, and call
+      // setValueAtTime() and linearRampToValueAtTime() at regular intervals to
+      // set the starting and ending values for a linear ramp. Each time
+      // interval has a ramp with a different starting and ending value so that
+      // there is a discontinuity at each time interval boundary.  The
+      // discontinuity is for testing timing.  Also, we alternate between an
+      // increasing and decreasing ramp for each interval.
+
+      // Number of tests to run.
+      let numberOfTests = 100;
+
+      // Max allowed difference between the rendered data and the expected
+      // result.
+      let maxAllowedError = 1.865e-6;
+
+      // Set the gain node value to the specified value at the specified time.
+      function setValue(value, time) {
+        gainNode.gain.setValueAtTime(value, time);
+      }
+
+      // Generate a linear ramp ending at time |endTime| with an ending value of
+      // |value|.
+      function generateRamp(value, startTime, endTime){
+          // |startTime| is ignored because the linear ramp uses the value from
+          // the
+          // setValueAtTime() call above.
+          gainNode.gain.linearRampToValueAtTime(value, endTime)}
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'AudioParam linearRampToValueAtTime() functionality'
+          },
+          function(task, should) {
+            createAudioGraphAndTest(
+                task, should, numberOfTests, 1, setValue, generateRamp,
+                'linearRampToValueAtTime()', maxAllowedError,
+                createLinearRampArray);
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-method-chaining.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-method-chaining.html
new file mode 100644
index 0000000..ed25a01
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-method-chaining.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audioparam-method-chaining.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 8000;
+
+      // Create a dummy array for setValueCurveAtTime method.
+      let curveArray = new Float32Array([5.0, 6.0]);
+
+      // AudioNode dictionary with associated dummy arguments.
+      let methodDictionary = [
+        {name: 'setValueAtTime', args: [1.0, 0.0]},
+        {name: 'linearRampToValueAtTime', args: [2.0, 1.0]},
+        {name: 'exponentialRampToValueAtTime', args: [3.0, 2.0]},
+        {name: 'setTargetAtTime', args: [4.0, 2.0, 0.5]},
+        {name: 'setValueCurveAtTime', args: [curveArray, 5.0, 1.0]},
+        {name: 'cancelScheduledValues', args: [6.0]}
+      ];
+
+      let audit = Audit.createTaskRunner();
+
+      // Task: testing entries from the dictionary.
+      audit.define('from-dictionary', (task, should) => {
+        let context = new AudioContext();
+
+        methodDictionary.forEach(function(method) {
+          let sourceParam = context.createGain().gain;
+          should(
+              sourceParam === sourceParam[method.name](...method.args),
+              'The return value of ' + sourceParam.constructor.name + '.' +
+                  method.name + '()' +
+                  ' matches the source AudioParam')
+              .beEqualTo(true);
+
+        });
+
+        task.done();
+      });
+
+      // Task: test method chaining with invalid operation.
+      audit.define('invalid-operation', (task, should) => {
+        let context = new OfflineAudioContext(1, sampleRate, sampleRate);
+        let osc = context.createOscillator();
+        let amp1 = context.createGain();
+        let amp2 = context.createGain();
+
+        osc.connect(amp1);
+        osc.connect(amp2);
+        amp1.connect(context.destination);
+        amp2.connect(context.destination);
+
+        // The first operation fails with an exception, thus the second one
+        // should not have effect on the parameter value. Instead, it should
+        // maintain the default value of 1.0.
+        should(
+            function() {
+              amp1.gain.setValueAtTime(0.25, -1.0)
+                  .linearRampToValueAtTime(2.0, 1.0);
+            },
+            'Calling setValueAtTime() with a negative end time')
+            .throw('RangeError');
+
+        // The first operation succeeds but the second fails due to zero target
+        // value for the exponential ramp. Thus only the first should have
+        // effect on the parameter value, setting the value to 0.5.
+        should(
+            function() {
+              amp2.gain.setValueAtTime(0.5, 0.0).exponentialRampToValueAtTime(
+                  0.0, 1.0);
+            },
+            'Calling exponentialRampToValueAtTime() with a zero target value')
+            .throw('RangeError');
+
+        osc.start();
+        osc.stop(1.0);
+
+        context.startRendering()
+            .then(function(buffer) {
+              should(amp1.gain.value, 'The gain value of the first gain node')
+                  .beEqualTo(1.0);
+              should(amp2.gain.value, 'The gain value of the second gain node')
+                  .beEqualTo(0.5);
+            })
+            .then(() => task.done());
+      });
+
+      // Task: verify if the method chaining actually works. Create an arbitrary
+      // envelope and compare the result with the expected one created by JS
+      // code.
+      audit.define('verification', (task, should) => {
+        let context = new OfflineAudioContext(1, sampleRate * 4, sampleRate);
+        let constantBuffer = createConstantBuffer(context, 1, 1.0);
+
+        let source = context.createBufferSource();
+        source.buffer = constantBuffer;
+        source.loop = true;
+
+        let envelope = context.createGain();
+
+        source.connect(envelope);
+        envelope.connect(context.destination);
+
+        envelope.gain.setValueAtTime(0.0, 0.0)
+            .linearRampToValueAtTime(1.0, 1.0)
+            .exponentialRampToValueAtTime(0.5, 2.0)
+            .setTargetAtTime(0.001, 2.0, 0.5);
+
+        source.start();
+
+        context.startRendering()
+            .then(function(buffer) {
+              let expectedEnvelope =
+                  createLinearRampArray(0.0, 1.0, 0.0, 1.0, sampleRate);
+              expectedEnvelope.push(...createExponentialRampArray(
+                  1.0, 2.0, 1.0, 0.5, sampleRate));
+              expectedEnvelope.push(...createExponentialApproachArray(
+                  2.0, 4.0, 0.5, 0.001, sampleRate, 0.5));
+
+              // There are slight differences between JS implementation of
+              // AudioParam envelope and the internal implementation. (i.e.
+              // double/float and rounding up) The error threshold is adjusted
+              // empirically through the local testing.
+              should(buffer.getChannelData(0), 'The rendered envelope')
+                  .beCloseToArray(
+                      expectedEnvelope, {absoluteThreshold: 4.0532e-6});
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-setTargetAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setTargetAtTime.html
new file mode 100644
index 0000000..faf00c0
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setTargetAtTime.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioParam.setTargetAtTime
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Play a long DC signal out through an AudioGainNode, and call
+      // setValueAtTime() and setTargetAtTime at regular intervals to set the
+      // starting value and the target value. Each time interval has a ramp with
+      // a different starting and target value so that there is a discontinuity
+      // at each time interval boundary. The discontinuity is for testing
+      // timing.  Also, we alternate between an increasing and decreasing ramp
+      // for each interval.
+
+      // Number of tests to run.
+      let numberOfTests = 100;
+
+      // Max allowed difference between the rendered data and the expected
+      // result.
+      let maxAllowedError = 6.5683e-4
+
+      // The AudioGainNode starts with this value instead of the default value.
+      let initialValue = 100;
+
+      // Set the gain node value to the specified value at the specified time.
+      function setValue(value, time) {
+        gainNode.gain.setValueAtTime(value, time);
+      }
+
+      // Generate an exponential approach starting at |startTime| with a target
+      // value of |value|.
+      function automation(value, startTime, endTime){
+          // endTime is not used for setTargetAtTime.
+          gainNode.gain.setTargetAtTime(value, startTime, timeConstant)}
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'AudioParam setTargetAtTime() functionality.'
+          },
+          function(task, should) {
+            createAudioGraphAndTest(
+                task, should, numberOfTests, initialValue, setValue, automation,
+                'setTargetAtTime()', maxAllowedError,
+                createExponentialApproachArray);
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueAtTime.html
new file mode 100644
index 0000000..ab2edfd
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueAtTime.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audioparam-setValueAtTime.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Play a long DC signal out through an AudioGainNode, and call
+      // setValueAtTime() at regular intervals to set the value for the duration
+      // of the interval.  Each time interval has different value so that there
+      // is a discontinuity at each time interval boundary.  The discontinuity
+      // is for testing timing.
+
+      // Number of tests to run.
+      let numberOfTests = 100;
+
+      // Max allowed difference between the rendered data and the expected
+      // result.
+      let maxAllowedError = 6e-8;
+
+      // Set the gain node value to the specified value at the specified time.
+      function setValue(value, time) {
+        gainNode.gain.setValueAtTime(value, time);
+      }
+
+      // For testing setValueAtTime(), we don't need to do anything for
+      // automation. because the value at the beginning of the interval is set
+      // by setValue and it remains constant for the duration, which is what we
+      // want.
+      function automation(value, startTime, endTime) {
+        // Do nothing.
+      }
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'AudioParam setValueAtTime() functionality.'
+          },
+          function(task, should) {
+            createAudioGraphAndTest(
+                task, should, numberOfTests, 1, setValue, automation,
+                'setValueAtTime()', maxAllowedError, createConstantArray);
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurve-exceptions.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurve-exceptions.html
new file mode 100644
index 0000000..590dcbd
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurve-exceptions.html
@@ -0,0 +1,320 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Exceptions from setValueCurveAtTime
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+      // Some short duration because we don't need to run the test for very
+      // long.
+      let testDurationSec = 0.125;
+      let testDurationFrames = testDurationSec * sampleRate;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('setValueCurve', (task, should) => {
+        let success = true;
+        let context =
+            new OfflineAudioContext(1, testDurationFrames, sampleRate);
+        let g = context.createGain();
+        let curve = new Float32Array(2);
+
+        // Start time and duration for setValueCurveAtTime
+        let curveStartTime = 0.1 * testDurationSec;
+        let duration = 0.1 * testDurationSec;
+
+        // Some time that is known to during the setValueCurveTime interval.
+        let automationTime = curveStartTime + duration / 2;
+
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(curve, curveStartTime, duration);
+            },
+            'setValueCurveAtTime(curve, ' + curveStartTime + ', ' + duration +
+                ')')
+            .notThrow();
+
+        should(
+            function() {
+              g.gain.setValueAtTime(1, automationTime);
+            },
+            'setValueAtTime(1, ' + automationTime + ')')
+            .throw('NotSupportedError');
+
+        should(
+            function() {
+              g.gain.linearRampToValueAtTime(1, automationTime);
+            },
+            'linearRampToValueAtTime(1, ' + automationTime + ')')
+            .throw('NotSupportedError');
+
+        should(
+            function() {
+              g.gain.exponentialRampToValueAtTime(1, automationTime);
+            },
+            'exponentialRampToValueAtTime(1, ' + automationTime + ')')
+            .throw('NotSupportedError');
+
+        should(
+            function() {
+              g.gain.setTargetAtTime(1, automationTime, 1);
+            },
+            'setTargetAtTime(1, ' + automationTime + ', 1)')
+            .throw('NotSupportedError');
+
+        should(
+            function() {
+              g.gain.setValueAtTime(1, curveStartTime + 1.1 * duration);
+            },
+            'setValueAtTime(1, ' + (curveStartTime + 1.1 * duration) + ')')
+            .notThrow();
+
+        task.done();
+      });
+
+      audit.define('automations', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, testDurationFrames, sampleRate);
+        let g = context.createGain();
+
+        let curve = new Float32Array(2);
+        // Start time and duration for setValueCurveAtTime
+        let startTime = 0;
+        let timeInterval = testDurationSec / 10;
+        let time;
+
+        startTime += timeInterval;
+        should(() => {
+          g.gain.linearRampToValueAtTime(1, startTime);
+        }, 'linearRampToValueAtTime(1, ' + startTime + ')').notThrow();
+
+        startTime += timeInterval;
+        should(() => {
+          g.gain.exponentialRampToValueAtTime(1, startTime);
+        }, 'exponentialRampToValueAtTime(1, ' + startTime + ')').notThrow();
+
+        startTime += timeInterval;
+        should(() => {
+          g.gain.setTargetAtTime(1, startTime, 0.1);
+        }, 'setTargetAtTime(1, ' + startTime + ', 0.1)').notThrow();
+
+        startTime += timeInterval;
+        should(() => {
+          g.gain.setValueCurveAtTime(curve, startTime, 0.1);
+        }, 'setValueCurveAtTime(curve, ' + startTime + ', 0.1)').notThrow();
+
+        // Now try to setValueCurve that overlaps each of the above automations
+        startTime = timeInterval / 2;
+
+        for (let k = 0; k < 4; ++k) {
+          time = startTime + timeInterval * k;
+          should(
+              () => {
+                g.gain.setValueCurveAtTime(curve, time, 0.01);
+              },
+              'setValueCurveAtTime(curve, ' + time + ', 0.01)')
+              .throw('NotSupportedError');
+        }
+
+        // Elements of setValueCurve should be finite.
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(
+                  Float32Array.from([NaN, NaN]), time, 0.01);
+            },
+            'setValueCurveAtTime([NaN, NaN], ' + time + ', 0.01)')
+            .throw('TypeError');
+
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(
+                  Float32Array.from([1, Infinity]), time, 0.01);
+            },
+            'setValueCurveAtTime([1, Infinity], ' + time + ', 0.01)')
+            .throw('TypeError');
+
+        let d = context.createDelay();
+        // Check that we get warnings for out-of-range values and also throw for
+        // non-finite values.
+        should(
+            () => {
+              d.delayTime.setValueCurveAtTime(
+                  Float32Array.from([1, 5]), time, 0.01);
+            },
+            'delayTime.setValueCurveAtTime([1, 5], ' + time + ', 0.01)')
+            .notThrow();
+
+        should(
+            () => {
+              d.delayTime.setValueCurveAtTime(
+                  Float32Array.from([1, 5, Infinity]), time, 0.01);
+            },
+            'delayTime.setValueCurveAtTime([1, 5, Infinity], ' + time +
+                ', 0.01)')
+            .throw('TypeError');
+
+        // One last test that prints out lots of digits for the time.
+        time = Math.PI / 100;
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(curve, time, 0.01);
+            },
+            'setValueCurveAtTime(curve, ' + time + ', 0.01)')
+            .throw('NotSupportedError');
+
+        task.done();
+      });
+
+      audit.define('catch-exception', (task, should) => {
+        // Verify that the curve isn't inserted into the time line even if we
+        // catch the exception.
+        let success = true;
+        let context =
+            new OfflineAudioContext(1, testDurationFrames, sampleRate);
+        let gain = context.createGain();
+        let source = context.createBufferSource();
+        let buffer = context.createBuffer(1, 1, context.sampleRate);
+        buffer.getChannelData(0)[0] = 1;
+        source.buffer = buffer;
+        source.loop = true;
+
+        source.connect(gain);
+        gain.connect(context.destination);
+
+        gain.gain.setValueAtTime(1, 0);
+        try {
+          // The value curve has an invalid element. This automation shouldn't
+          // be inserted into the timeline at all.
+          gain.gain.setValueCurveAtTime(
+              Float32Array.from([0, NaN]), 128 / context.sampleRate, .5);
+        } catch (e) {
+        };
+        source.start();
+
+        context.startRendering()
+            .then(function(resultBuffer) {
+              // Since the setValueCurve wasn't inserted, the output should be
+              // exactly 1 for the entire duration.
+              should(
+                  resultBuffer.getChannelData(0),
+                  'Handled setValueCurve exception so output')
+                  .beConstantValueOf(1);
+
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('start-end', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, testDurationFrames, sampleRate);
+        let g = context.createGain();
+        let curve = new Float32Array(2);
+
+        // Verify that a setValueCurve can start at the end of an automation.
+        let time = 0;
+        let timeInterval = testDurationSec / 50;
+        should(() => {
+          g.gain.setValueAtTime(1, time);
+        }, 'setValueAtTime(1, ' + time + ')').notThrow();
+
+        time += timeInterval;
+        should(() => {
+          g.gain.linearRampToValueAtTime(0, time);
+        }, 'linearRampToValueAtTime(0, ' + time + ')').notThrow();
+
+        // setValueCurve starts at the end of the linear ramp. This should be
+        // fine.
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(curve, time, timeInterval);
+            },
+            'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
+            .notThrow();
+
+        // exponentialRamp ending one interval past the setValueCurve should be
+        // fine.
+        time += 2 * timeInterval;
+        should(() => {
+          g.gain.exponentialRampToValueAtTime(1, time);
+        }, 'exponentialRampToValueAtTime(1, ' + time + ')').notThrow();
+
+        // setValueCurve starts at the end of the exponential ramp. This should
+        // be fine.
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(curve, time, timeInterval);
+            },
+            'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
+            .notThrow();
+
+        // setValueCurve at the end of the setValueCurve should be fine.
+        time += timeInterval;
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(curve, time, timeInterval);
+            },
+            'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
+            .notThrow();
+
+        // setValueAtTime at the end of setValueCurve should be fine.
+        time += timeInterval;
+        should(() => {
+          g.gain.setValueAtTime(0, time);
+        }, 'setValueAtTime(0, ' + time + ')').notThrow();
+
+        // setValueCurve at the end of setValueAtTime should be fine.
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(curve, time, timeInterval);
+            },
+            'setValueCurveAtTime(..., ' + time + ', ' + timeInterval + ')')
+            .notThrow();
+
+        // setTarget starting at the end of setValueCurve should be fine.
+        time += timeInterval;
+        should(() => {
+          g.gain.setTargetAtTime(1, time, 1);
+        }, 'setTargetAtTime(1, ' + time + ', 1)').notThrow();
+
+        task.done();
+      });
+
+      audit.define('curve lengths', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, testDurationFrames, sampleRate);
+        let g = context.createGain();
+        let time = 0;
+
+        // Check for invalid curve lengths
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(Float32Array.from([]), time, 0.01);
+            },
+            'setValueCurveAtTime([], ' + time + ', 0.01)')
+            .throw('InvalidStateError');
+
+        should(
+            () => {
+              g.gain.setValueCurveAtTime(Float32Array.from([1]), time, 0.01);
+            },
+            'setValueCurveAtTime([1], ' + time + ', 0.01)')
+            .throw('InvalidStateError');
+
+        should(() => {
+          g.gain.setValueCurveAtTime(Float32Array.from([1, 2]), time, 0.01);
+        }, 'setValueCurveAtTime([1,2], ' + time + ', 0.01)').notThrow();
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime.html
new file mode 100644
index 0000000..de84062
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioParam.setValueCurveAtTime
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Play a long DC signal out through an AudioGainNode and for each time
+      // interval call setValueCurveAtTime() to set the values for the duration
+      // of the interval.  Each curve is a sine wave, and we assume that the
+      // time interval is not an exact multiple of the period. This causes a
+      // discontinuity between time intervals which is used to test timing.
+
+      // Number of tests to run.
+      let numberOfTests = 20;
+
+      // Max allowed difference between the rendered data and the expected
+      // result. Because of the linear interpolation, the rendered curve isn't
+      // exactly the same as the reference.  This value is experimentally
+      // determined.
+      let maxAllowedError = 3.7194e-6;
+
+      // The amplitude of the sine wave.
+      let sineAmplitude = 1;
+
+      // Frequency of the sine wave.
+      let freqHz = 440;
+
+      // Curve to use for setValueCurveAtTime().
+      let curve;
+
+      // Sets the curve data for the entire time interval.
+      function automation(value, startTime, endTime) {
+        gainNode.gain.setValueCurveAtTime(
+            curve, startTime, endTime - startTime);
+      }
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'AudioParam setValueCurveAtTime() functionality.'
+          },
+          function(task, should) {
+            // The curve of values to use.
+            curve = createSineWaveArray(
+                timeInterval, freqHz, sineAmplitude, sampleRate);
+
+            createAudioGraphAndTest(
+                task, should, numberOfTests, sineAmplitude,
+                function(k) {
+                  // Don't need to set the value.
+                },
+                automation, 'setValueCurveAtTime()', maxAllowedError,
+                createReferenceSineArray,
+                2 * Math.PI * sineAmplitude * freqHz / sampleRate,
+                differenceErrorMetric);
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/audioparam-summingjunction.html b/webaudio/the-audio-api/the-audioparam-interface/audioparam-summingjunction.html
new file mode 100644
index 0000000..9084942
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/audioparam-summingjunction.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<!--
+Tests that multiple audio-rate signals (AudioNode outputs) can be connected to an AudioParam
+and that these signals are summed, along with the AudioParams intrinsic value.
+-->
+<html>
+  <head>
+    <title>
+      audioparam-summingjunction.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/mix-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 44100.0;
+      let lengthInSeconds = 1;
+
+      let context = 0;
+
+      // Buffers used by the two gain controlling sources.
+      let linearRampBuffer;
+      let toneBuffer;
+      let toneFrequency = 440;
+
+      // Arbitrary non-zero value.
+      let baselineGain = 5;
+
+      // Allow for a small round-off error.
+      let maxAllowedError = 1e-6;
+
+      function checkResult(renderedBuffer, should) {
+        let renderedData = renderedBuffer.getChannelData(0);
+
+        // Get buffer data from the two sources used to control gain.
+        let linearRampData = linearRampBuffer.getChannelData(0);
+        let toneData = toneBuffer.getChannelData(0);
+
+        let n = renderedBuffer.length;
+
+        should(n, 'Rendered signal length').beEqualTo(linearRampBuffer.length);
+
+        // Check that the rendered result exactly matches the sum of the
+        // intrinsic gain plus the two sources used to control gain. This is
+        // because we're changing the gain of a signal having constant value 1.
+        let success = true;
+        for (let i = 0; i < n; ++i) {
+          let expectedValue = baselineGain + linearRampData[i] + toneData[i];
+          let error = Math.abs(expectedValue - renderedData[i]);
+
+          if (error > maxAllowedError) {
+            success = false;
+            break;
+          }
+        }
+
+        should(
+            success,
+            'Rendered signal matches sum of two audio-rate gain changing signals plus baseline gain')
+            .beTrue();
+      }
+
+      audit.define('test', function(task, should) {
+        let sampleFrameLength = sampleRate * lengthInSeconds;
+
+        // Create offline audio context.
+        context = new OfflineAudioContext(1, sampleFrameLength, sampleRate);
+
+        // Create buffer used by the source which will have its gain controlled.
+        let constantOneBuffer =
+            createConstantBuffer(context, sampleFrameLength, 1);
+        let constantSource = context.createBufferSource();
+        constantSource.buffer = constantOneBuffer;
+
+        // Create 1st buffer used to control gain (a linear ramp).
+        linearRampBuffer = createLinearRampBuffer(context, sampleFrameLength);
+        let gainSource1 = context.createBufferSource();
+        gainSource1.buffer = linearRampBuffer;
+
+        // Create 2st buffer used to control gain (a simple sine wave tone).
+        toneBuffer =
+            createToneBuffer(context, toneFrequency, lengthInSeconds, 1);
+        let gainSource2 = context.createBufferSource();
+        gainSource2.buffer = toneBuffer;
+
+        // Create a gain node controlling the gain of constantSource and make
+        // the connections.
+        let gainNode = context.createGain();
+
+        // Intrinsic baseline gain.
+        // This gain value should be summed with gainSource1 and gainSource2.
+        gainNode.gain.value = baselineGain;
+
+        constantSource.connect(gainNode);
+        gainNode.connect(context.destination);
+
+        // Connect two audio-rate signals to control the .gain AudioParam.
+        gainSource1.connect(gainNode.gain);
+        gainSource2.connect(gainNode.gain);
+
+        // Start all sources at time 0.
+        constantSource.start(0);
+        gainSource1.start(0);
+        gainSource2.start(0);
+
+        context.startRendering().then(buffer => {
+          checkResult(buffer, should);
+          task.done();
+        });
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/automation-rate-testing.js b/webaudio/the-audio-api/the-audioparam-interface/automation-rate-testing.js
new file mode 100644
index 0000000..46d9d4a
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/automation-rate-testing.js
@@ -0,0 +1,117 @@
+// Test k-rate vs a-rate AudioParams.
+//
+// |options| describes how the testing of the AudioParam should be done:
+//
+//   nodeName:  name of the AudioNode to be tested
+//   nodeOptions:  options to be used in the AudioNode constructor
+//
+//   prefix: Prefix for all output messages (to make them unique for
+//           testharness)
+//
+//   rateSettings: A vector of dictionaries specifying how to set the automation
+//                 rate(s):
+//       name: Name of the AudioParam
+//       value: The automation rate for the AudioParam given by |name|.
+//
+//   automations: A vector of dictionaries specifying how to automate each
+//                AudioParam:
+//       name: Name of the AudioParam
+//
+//       methods: A vector of dictionaries specifying the automation methods to
+//                be used for testing:
+//           name: Automation method to call
+//           options: Arguments for the automation method
+//
+// Testing is somewhat rudimentary.  We create two nodes of the same type.  One
+// node uses the default automation rates for each AudioParam (expecting them to
+// be a-rate).  The second node sets the automation rate of AudioParams to
+// "k-rate".  The set is speciified by |options.rateSettings|.
+//
+// For both of these nodes, the same set of automation methods (given by
+// |options.automations|) is applied.  A simple oscillator is connected to each
+// node which in turn are connected to different channels of an offline context.
+// Channel 0 is the k-rate node output; channel 1, the a-rate output; and
+// channel 3, the difference between the outputs.
+//
+// Success is declared if the difference signal is not exactly zero.  This means
+// the the automations did different things, as expected.
+//
+// The promise from |startRendering| is returned.
+function doTest(context, should, options) {
+  let merger = new ChannelMergerNode(
+      context, {numberOfInputs: context.destination.numberOfChannels});
+  merger.connect(context.destination);
+
+  let src = new OscillatorNode(context);
+  let kRateNode = new window[options.nodeName](context, options.nodeOptions);
+  let aRateNode = new window[options.nodeName](context, options.nodeOptions);
+  let inverter = new GainNode(context, {gain: -1});
+
+  // Set kRateNode filter to use k-rate params.
+  options.rateSettings.forEach(setting => {
+    kRateNode[setting.name].automationRate = setting.value;
+    // Mostly for documentation in the output.  These should always
+    // pass.
+    should(
+        kRateNode[setting.name].automationRate,
+        `${options.prefix}: Setting ${
+                                      setting.name
+                                    }.automationRate to "${setting.value}"`)
+        .beEqualTo(setting.value);
+  });
+
+  // Run through all automations for each node separately. (Mostly to keep
+  // output of automations together.)
+  options.automations.forEach(param => {
+    param.methods.forEach(method => {
+      // Most for documentation in the output.  These should never throw.
+      let message = `${param.name}.${method.name}(${method.options})`
+      should(() => {
+        kRateNode[param.name][method.name](...method.options);
+      }, options.prefix + ': k-rate node: ' + message).notThrow();
+    });
+  });
+  options.automations.forEach(param => {
+    param.methods.forEach(method => {
+      // Most for documentation in the output.  These should never throw.
+      let message = `${param.name}.${method.name}(${method.options})`
+      should(() => {
+        aRateNode[param.name][method.name](...method.options);
+      }, options.prefix + ': a-rate node:' + message).notThrow();
+    });
+  });
+
+  // The k-rate result is channel 0, and the a-rate result is channel 1.
+  src.connect(kRateNode).connect(merger, 0, 0);
+  src.connect(aRateNode).connect(merger, 0, 1);
+
+  // Compute the difference between the a-rate and k-rate results and send
+  // that to channel 2.
+  kRateNode.connect(merger, 0, 2);
+  aRateNode.connect(inverter).connect(merger, 0, 2);
+
+  src.start();
+  return context.startRendering().then(renderedBuffer => {
+    let kRateOutput = renderedBuffer.getChannelData(0);
+    let aRateOutput = renderedBuffer.getChannelData(1);
+    let diff = renderedBuffer.getChannelData(2);
+
+    // Some informative messages to print out values of the k-rate and
+    // a-rate outputs.  These should always pass.
+    should(
+        kRateOutput, `${options.prefix}: Output of k-rate ${options.nodeName}`)
+        .beEqualToArray(kRateOutput);
+    should(
+        aRateOutput, `${options.prefix}: Output of a-rate ${options.nodeName}`)
+        .beEqualToArray(aRateOutput);
+
+    // The real test.  If k-rate AudioParam is working correctly, the
+    // k-rate result MUST differ from the a-rate result.
+    should(
+        diff,
+        `${
+           options.prefix
+         }: Difference between a-rate and k-rate ${options.nodeName}`)
+        .notBeConstantValueOf(0);
+  });
+}
diff --git a/webaudio/the-audio-api/the-audioparam-interface/automation-rate.html b/webaudio/the-audio-api/the-audioparam-interface/automation-rate.html
new file mode 100644
index 0000000..a3c789e
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/automation-rate.html
@@ -0,0 +1,167 @@
+<!doctype html>
+<html>
+  <head>
+    <title>AudioParam.automationRate tests</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+
+  <body>
+    <script>
+      // For each node that has an AudioParam, verify that the default
+      // |automationRate| has the expected value and that we can change it or
+      // throw an error if it can't be changed.
+
+      // Any valid sample rate is fine; we don't actually render anything in the
+      // tests.
+      let sampleRate = 8000;
+
+      let audit = Audit.createTaskRunner();
+
+      // Array of tests.  Each test is a dictonary consisting of the name of the
+      // node and an array specifying the AudioParam's of the node.  This array
+      // in turn gives the name of the AudioParam, the default value for the
+      // |automationRate|, and whether it is fixed (isFixed).
+      const tests = [
+        {
+          nodeName: 'AudioBufferSourceNode',
+          audioParams: [
+            {name: 'detune', defaultRate: 'k-rate', isFixed: true},
+            {name: 'playbackRate', defaultRate: 'k-rate', isFixed: true}
+          ]
+        },
+        {
+          nodeName: 'BiquadFilterNode',
+          audioParams: [
+            {name: 'frequency', defaultRate: 'a-rate', isFixed: false},
+            {name: 'detune', defaultRate: 'a-rate', isFixed: false},
+            {name: 'Q', defaultRate: 'a-rate', isFixed: false},
+            {name: 'gain', defaultRate: 'a-rate', isFixed: false},
+          ]
+        },
+        {
+          nodeName: 'ConstantSourceNode',
+          audioParams: [{name: 'offset', defaultRate: 'a-rate', isFixed: false}]
+        },
+        {
+          nodeName: 'DelayNode',
+          audioParams:
+              [{name: 'delayTime', defaultRate: 'a-rate', isFixed: false}]
+        },
+        {
+          nodeName: 'DynamicsCompressorNode',
+          audioParams: [
+            {name: 'threshold', defaultRate: 'k-rate', isFixed: true},
+            {name: 'knee', defaultRate: 'k-rate', isFixed: true},
+            {name: 'ratio', defaultRate: 'k-rate', isFixed: true},
+            {name: 'attack', defaultRate: 'k-rate', isFixed: true},
+            {name: 'release', defaultRate: 'k-rate', isFixed: true}
+          ]
+        },
+        {
+          nodeName: 'GainNode',
+          audioParams: [{name: 'gain', defaultRate: 'a-rate', isFixed: false}]
+        },
+        {
+          nodeName: 'OscillatorNode',
+          audioParams: [
+            {name: 'frequency', defaultRate: 'a-rate', isFixed: false},
+            {name: 'detune', defaultRate: 'a-rate', isFixed: false}
+          ]
+        },
+        {
+          nodeName: 'PannerNode',
+          audioParams: [
+            {name: 'positionX', defaultRate: 'a-rate', isFixed: false},
+            {name: 'positionY', defaultRate: 'a-rate', isFixed: false},
+            {name: 'positionZ', defaultRate: 'a-rate', isFixed: false},
+            {name: 'orientationX', defaultRate: 'a-rate', isFixed: false},
+            {name: 'orientationY', defaultRate: 'a-rate', isFixed: false},
+            {name: 'orientationZ', defaultRate: 'a-rate', isFixed: false},
+          ]
+        },
+        {
+          nodeName: 'StereoPannerNode',
+          audioParams: [{name: 'pan', defaultRate: 'a-rate', isFixed: false}]
+        },
+      ];
+
+      tests.forEach(test => {
+        // Define a separate test for each test entry.
+        audit.define(test.nodeName, (task, should) => {
+          let context = new OfflineAudioContext(
+              {length: sampleRate, sampleRate: sampleRate});
+          // Construct the node and test each AudioParam of the node.
+          let node = new window[test.nodeName](context);
+          test.audioParams.forEach(param => {
+            testAudioParam(
+                should, {nodeName: test.nodeName, node: node, param: param});
+          });
+
+          task.done();
+        });
+      });
+
+      // AudioListener needs it's own special test since it's not a node.
+      audit.define('AudioListener', (task, should) => {
+        let context = new OfflineAudioContext(
+            {length: sampleRate, sampleRate: sampleRate});
+
+        [{name: 'positionX', defaultRate: 'a-rate', isFixed: false},
+         {name: 'positionY', defaultRate: 'a-rate', isFixed: false},
+         {name: 'positionZ', defaultRate: 'a-rate', isFixed: false},
+         {name: 'forwardX', defaultRate: 'a-rate', isFixed: false},
+         {name: 'forwardY', defaultRate: 'a-rate', isFixed: false},
+         {name: 'forwardZ', defaultRate: 'a-rate', isFixed: false},
+         {name: 'upX', defaultRate: 'a-rate', isFixed: false},
+         {name: 'upY', defaultRate: 'a-rate', isFixed: false},
+         {name: 'upZ', defaultRate: 'a-rate', isFixed: false},
+        ].forEach(param => {
+          testAudioParam(should, {
+            nodeName: 'AudioListener',
+            node: context.listener,
+            param: param
+          });
+        });
+        task.done();
+      });
+
+      audit.run();
+
+      function testAudioParam(should, options) {
+        let param = options.param;
+        let audioParam = options.node[param.name];
+        let defaultRate = param.defaultRate;
+
+        // Verify that the default value is correct.
+        should(
+            audioParam.automationRate,
+            `Default ${options.nodeName}.${param.name}.automationRate`)
+            .beEqualTo(defaultRate);
+
+        // Try setting the rate to a different rate.  If the |automationRate|
+        // is fixed, expect an error.  Otherwise, expect no error and expect
+        // the value is changed to the new value.
+        let newRate = defaultRate === 'a-rate' ? 'k-rate' : 'a-rate';
+        let setMessage = `Set ${
+                                options.nodeName
+                              }.${param.name}.automationRate to "${newRate}"`
+
+        if (param.isFixed) {
+          should(() => audioParam.automationRate = newRate, setMessage)
+              .throw('InvalidStateError');
+        }
+        else {
+          should(() => audioParam.automationRate = newRate, setMessage)
+              .notThrow();
+          should(
+              audioParam.automationRate,
+              `${options.nodeName}.${param.name}.automationRate`)
+              .beEqualTo(newRate);
+        }
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html b/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html
new file mode 100644
index 0000000..eab77c4
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html
@@ -0,0 +1,314 @@
+<!doctype html>
+<html>
+  <head>
+    <title>
+      Test Handling of Event Insertion
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audio-param.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Use a power of two for the sample rate so there's no round-off in
+      // computing time from frame.
+      let sampleRate = 16384;
+
+      audit.define(
+          {label: 'Insert same event at same time'}, (task, should) => {
+            // Context for testing.
+            let context = new OfflineAudioContext(
+                {length: 16384, sampleRate: sampleRate});
+
+            // The source node to use.  Automations will be scheduled here.
+            let src = new ConstantSourceNode(context, {offset: 0});
+            src.connect(context.destination);
+
+            // An array of tests to be done.  Each entry specifies the event
+            // type and the event time.  The events are inserted in the order
+            // given (in |values|), and the second event should replace the
+            // first, as required by the spec.
+            let testCases = [
+              {
+                event: 'setValueAtTime',
+                frame: RENDER_QUANTUM_FRAMES,
+                values: [99, 1],
+                outputTestFrame: RENDER_QUANTUM_FRAMES,
+                expectedOutputValue: 1
+              },
+              {
+                event: 'linearRampToValueAtTime',
+                frame: 2 * RENDER_QUANTUM_FRAMES,
+                values: [99, 2],
+                outputTestFrame: 2 * RENDER_QUANTUM_FRAMES,
+                expectedOutputValue: 2
+              },
+              {
+                event: 'exponentialRampToValueAtTime',
+                frame: 3 * RENDER_QUANTUM_FRAMES,
+                values: [99, 3],
+                outputTestFrame: 3 * RENDER_QUANTUM_FRAMES,
+                expectedOutputValue: 3
+              },
+              {
+                event: 'setValueCurveAtTime',
+                frame: 3 * RENDER_QUANTUM_FRAMES,
+                values: [[98, 99], [3, 4]],
+                extraArgs: RENDER_QUANTUM_FRAMES / context.sampleRate,
+                outputTestFrame: 4 * RENDER_QUANTUM_FRAMES,
+                expectedOutputValue: 4
+              }
+            ];
+
+            testCases.forEach(entry => {
+              entry.values.forEach(value => {
+                let eventTime = entry.frame / context.sampleRate;
+                let message = eventToString(
+                    entry.event, value, eventTime, entry.extraArgs);
+                // This is mostly to print out the event that is getting
+                // inserted.  It should never ever throw.
+                should(() => {
+                  src.offset[entry.event](value, eventTime, entry.extraArgs);
+                }, message).notThrow();
+              });
+            });
+
+            src.start();
+
+            context.startRendering()
+                .then(audioBuffer => {
+                  let audio = audioBuffer.getChannelData(0);
+
+                  // Look through the test cases to figure out what the correct
+                  // output values should be.
+                  testCases.forEach(entry => {
+                    let expected = entry.expectedOutputValue;
+                    let frame = entry.outputTestFrame;
+                    let time = frame / context.sampleRate;
+                    should(
+                        audio[frame], `Output at frame ${frame} (time ${time})`)
+                        .beEqualTo(expected);
+                  });
+                })
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'Linear + Expo',
+            description: 'Different events at same time'
+          },
+          (task, should) => {
+            // Should be a linear ramp up to the event time, and after a
+            // constant value because the exponential ramp has ended.
+            let testCase = [
+              {event: 'linearRampToValueAtTime', value: 2, relError: 0},
+              {event: 'setValueAtTime', value: 99},
+              {event: 'exponentialRampToValueAtTime', value: 3},
+            ];
+            let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
+            let prefix = 'Linear+Expo: ';
+
+            testEventInsertion(prefix, should, eventFrame, testCase)
+                .then(expectConstant(prefix, should, eventFrame, testCase))
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'Expo + Linear',
+            description: 'Different events at same time',
+          },
+          (task, should) => {
+            // Should be an exponential ramp up to the event time, and after a
+            // constant value because the linear ramp has ended.
+            let testCase = [
+              {
+                event: 'exponentialRampToValueAtTime',
+                value: 3,
+                relError: 4.2533e-6
+              },
+              {event: 'setValueAtTime', value: 99},
+              {event: 'linearRampToValueAtTime', value: 2},
+            ];
+            let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
+            let prefix = 'Expo+Linear: ';
+
+            testEventInsertion(prefix, should, eventFrame, testCase)
+                .then(expectConstant(prefix, should, eventFrame, testCase))
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'Linear + SetTarget',
+            description: 'Different events at same time',
+          },
+          (task, should) => {
+            // Should be a linear ramp up to the event time, and then a
+            // decaying value.
+            let testCase = [
+              {event: 'linearRampToValueAtTime', value: 3, relError: 0},
+              {event: 'setValueAtTime', value: 100},
+              {event: 'setTargetAtTime', value: 0, extraArgs: 0.1},
+            ];
+            let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
+            let prefix = 'Linear+SetTarget: ';
+
+            testEventInsertion(prefix, should, eventFrame, testCase)
+                .then(audioBuffer => {
+                  let audio = audioBuffer.getChannelData(0);
+                  let prefix = 'Linear+SetTarget: ';
+                  let eventTime = eventFrame / sampleRate;
+                  let expectedValue = methodMap[testCase[0].event](
+                      (eventFrame - 1) / sampleRate, 1, 0, testCase[0].value,
+                      eventTime);
+                  should(
+                      audio[eventFrame - 1],
+                      prefix +
+                          `At time ${
+                                     (eventFrame - 1) / sampleRate
+                                   } (frame ${eventFrame - 1}) output`)
+                      .beCloseTo(
+                          expectedValue,
+                          {threshold: testCase[0].relError || 0});
+
+                  // The setValue should have taken effect
+                  should(
+                      audio[eventFrame],
+                      prefix +
+                          `At time ${eventTime} (frame ${eventFrame}) output`)
+                      .beEqualTo(testCase[1].value);
+
+                  // The final event is setTarget.  Compute the expected output.
+                  let actual = audio.slice(eventFrame);
+                  let expected = new Float32Array(actual.length);
+                  for (let k = 0; k < expected.length; ++k) {
+                    let t = (eventFrame + k) / sampleRate;
+                    expected[k] = audioParamSetTarget(
+                        t, testCase[1].value, eventTime, testCase[2].value,
+                        testCase[2].extraArgs);
+                  }
+                  should(
+                      actual,
+                      prefix +
+                          `At time ${eventTime} (frame ${
+                                                         eventFrame
+                                                       }) and later`)
+                      .beCloseToArray(expected, {relativeThreshold: 1.7807e-7});
+                })
+                .then(() => task.done());
+          });
+
+
+      audit.run();
+
+      // Takes a list of |testCases| consisting of automation methods and
+      // schedules them to occur at |eventFrame|. |prefix| is a prefix for
+      // messages produced by |should|.
+      //
+      // Each item in |testCases| is a dictionary with members:
+      //   event     - the name of automation method to be inserted,
+      //   value     - the value for the event,
+      //   extraArgs - extra arguments if the event needs more than the value
+      //               and time (such as setTargetAtTime).
+      function testEventInsertion(prefix, should, eventFrame, testCases) {
+        let context = new OfflineAudioContext(
+            {length: 4 * RENDER_QUANTUM_FRAMES, sampleRate: sampleRate});
+
+        // The source node to use.  Automations will be scheduled here.
+        let src = new ConstantSourceNode(context, {offset: 0});
+        src.connect(context.destination);
+
+        // Initialize value to 1 at the beginning.
+        src.offset.setValueAtTime(1, 0);
+
+        // Test automations have this event time.
+        let eventTime = eventFrame / context.sampleRate;
+
+        // Sanity check that context is long enough for the test
+        should(
+            eventFrame < context.length,
+            prefix + 'Context length is long enough for the test')
+            .beTrue();
+
+        // Automations to be tested.  The first event should be the actual
+        // output up to the event time.  The last event should be the final
+        // output from the event time and onwards.
+        testCases.forEach(entry => {
+          should(
+              () => {
+                src.offset[entry.event](
+                    entry.value, eventTime, entry.extraArgs);
+              },
+              prefix +
+                  eventToString(
+                      entry.event, entry.value, eventTime, entry.extraArgs))
+              .notThrow();
+        });
+
+        src.start();
+
+        return context.startRendering();
+      }
+
+      // Verify output of test where the final value of the automation is
+      // expected to be constant.
+      function expectConstant(prefix, should, eventFrame, testCases) {
+        return audioBuffer => {
+          let audio = audioBuffer.getChannelData(0);
+
+          let eventTime = eventFrame / sampleRate;
+
+          // Compute the expected value of the first automation one frame before
+          // the event time.  This is a quick check that the correct automation
+          // was done.
+          let expectedValue = methodMap[testCases[0].event](
+              (eventFrame - 1) / sampleRate, 1, 0, testCases[0].value,
+              eventTime);
+          should(
+              audio[eventFrame - 1],
+              prefix +
+                  `At time ${
+                             (eventFrame - 1) / sampleRate
+                           } (frame ${eventFrame - 1}) output`)
+              .beCloseTo(expectedValue, {threshold: testCases[0].relError});
+
+          // The last event scheduled is expected to set the value for all
+          // future times.  Verify that the output has the expected value.
+          should(
+              audio.slice(eventFrame),
+              prefix +
+                  `At time ${eventTime} (frame ${
+                                                 eventFrame
+                                               }) and later, output`)
+              .beConstantValueOf(testCases[testCases.length - 1].value);
+        };
+      }
+
+      // Convert an automation method to a string for printing.
+      function eventToString(method, value, time, extras) {
+        let string = method + '(';
+        string += (value instanceof Array) ? `[${value}]` : value;
+        string += ', ' + time;
+        if (extras) {
+          string += ', ' + extras;
+        }
+        string += ')';
+        return string;
+      }
+
+      // Map between the automation method name and a function that computes the
+      // output value of the automation method.
+      const methodMap = {
+        linearRampToValueAtTime: audioParamLinearRamp,
+        exponentialRampToValueAtTime: audioParamExponentialRamp,
+        setValueAtTime: (t, v) => v
+      };
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/idl-test.html b/webaudio/the-audio-api/the-audioparam-interface/idl-test.html
index 5c378f8..45ddad5 100644
--- a/webaudio/the-audio-api/the-audioparam-interface/idl-test.html
+++ b/webaudio/the-audio-api/the-audioparam-interface/idl-test.html
@@ -6,7 +6,6 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/idlharness.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
-<script src="/webaudio/js/helpers.js"></script>
 <style type="text/css">
     #audio-param-idl
     { visibility:hidden; height: 0px;}
@@ -43,7 +42,7 @@
   <script>
 (function() {
   var idl_array = new IdlArray();
-  idl_array.add_untested_idls(document.getElementById("audio-param-idl").textContent);
+  idl_array.add_idls(document.getElementById("audio-param-idl").textContent);
 
   delay_time = (new AudioContext).createDelay().delayTime;
 
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-audioworklet.https.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-audioworklet.https.html
new file mode 100644
index 0000000..e891da6
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-audioworklet.https.html
@@ -0,0 +1,79 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParam of AudioWorkletNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+
+  <body>
+    <script>
+      const audit = Audit.createTaskRunner();
+
+      // Use the worklet gain node to test k-rate parameters.
+      const filePath =
+          '../the-audioworklet-interface/processors/gain-processor.js';
+
+      // Context for testing
+      let context;
+
+      audit.define('Create Test Worklet', (task, should) => {
+
+        // Arbitrary sample rate and duration.
+        const sampleRate = 8000;
+
+        // Only new a few render quanta to verify things are working.
+        const testDuration = 4 * 128 / sampleRate;
+
+        context = new OfflineAudioContext({
+          numberOfChannels: 3,
+          sampleRate: sampleRate,
+          length: testDuration * sampleRate
+        });
+
+        should(
+            context.audioWorklet.addModule(filePath),
+            'Construction of AudioWorklet')
+            .beResolved()
+            .then(() => task.done());
+      });
+
+      audit.define('AudioWorklet k-rate AudioParam', (task, should) => {
+        let src = new ConstantSourceNode(context);
+
+        let kRateNode = new AudioWorkletNode(context, 'gain');
+
+        src.connect(kRateNode).connect(context.destination);
+
+        let kRateParam = kRateNode.parameters.get('gain');
+        kRateParam.automationRate = 'k-rate';
+
+        // Automate the gain
+        kRateParam.setValueAtTime(0, 0);
+        kRateParam.linearRampToValueAtTime(
+            10, context.length / context.sampleRate);
+
+        src.start();
+
+        context.startRendering()
+            .then(audioBuffer => {
+              let output = audioBuffer.getChannelData(0);
+
+              // Verify that the output from the worklet is step-wise
+              // constant.
+              for (let k = 0; k < output.length; k += 128) {
+                should(
+                    output.slice(k, k + 128),
+                    ` k-rate output [${k}: ${k + 127}]`)
+                    .beConstantValueOf(output[k]);
+              }
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad.html
new file mode 100644
index 0000000..85ae4f1
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad.html
@@ -0,0 +1,111 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParams of BiquadFilterNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="automation-rate-testing.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {task: 'BiquadFilter-0', label: 'Biquad k-rate AudioParams (all)'},
+          (task, should) => {
+            // Arbitrary sample rate and duration.
+            let sampleRate = 8000;
+            let testDuration = 1;
+            let context = new OfflineAudioContext({
+              numberOfChannels: 3,
+              sampleRate: sampleRate,
+              length: testDuration * sampleRate
+            });
+
+            doTest(context, should, {
+              nodeName: 'BiquadFilterNode',
+              nodeOptions: {type: 'lowpass'},
+              prefix: 'All k-rate params',
+              // Set all AudioParams to k-rate
+              rateSettings: [
+                {name: 'Q', value: 'k-rate'},
+                {name: 'detune', value: 'k-rate'},
+                {name: 'frequency', value: 'k-rate'},
+                {name: 'gain', value: 'k-rate'},
+              ],
+              // Automate just the frequency
+              automations: [{
+                name: 'frequency',
+                methods: [
+                  {name: 'setValueAtTime', options: [350, 0]}, {
+                    name: 'linearRampToValueAtTime',
+                    options: [0, testDuration]
+                  }
+                ]
+              }]
+            }).then(() => task.done());
+          });
+
+      // Define a test where we verify that a k-rate audio param produces
+      // different results from an a-rate audio param for each of the audio
+      // params of a biquad.
+      //
+      // Each entry gives the name of the AudioParam, an initial value to be
+      // used with setValueAtTime, and a final value to be used with
+      // linearRampToValueAtTime. (See |doTest| for details as well.)
+
+      [{name: 'Q',
+        initial: 1,
+        final: 10
+       },
+       {name: 'detune',
+        initial: 0,
+        final: 1200
+       },
+       {name: 'frequency',
+        initial: 350,
+        final: 0
+       },
+       {name: 'gain',
+        initial: 10,
+        final: 0
+       }].forEach(paramProperty => {
+        audit.define('Biquad k-rate ' + paramProperty.name, (task, should) => {
+          // Arbitrary sample rate and duration.
+          let sampleRate = 8000;
+          let testDuration = 1;
+          let context = new OfflineAudioContext({
+            numberOfChannels: 3,
+            sampleRate: sampleRate,
+            length: testDuration * sampleRate
+          });
+
+          doTest(context, should, {
+            nodeName: 'BiquadFilterNode',
+            nodeOptions: {type: 'peaking', Q: 1, gain: 10},
+            prefix: `k-rate ${paramProperty.name}`,
+            // Just set the frequency to k-rate
+            rateSettings: [
+              {name: paramProperty.name, value: 'k-rate'},
+            ],
+            // Automate just the given AudioParam
+            automations: [{
+              name: paramProperty.name,
+              methods: [
+                {name: 'setValueAtTime', options: [paramProperty.initial, 0]}, {
+                  name: 'linearRampToValueAtTime',
+                  options: [paramProperty.final, testDuration]
+                }
+              ]
+            }]
+          }).then(() => task.done());
+        });
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-constant-source.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-constant-source.html
new file mode 100644
index 0000000..5c421d0
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-constant-source.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParam of ConstantSourceNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      audit.define('ConstantSource k-rate offset', (task, should) => {
+        // Arbitrary sample rate and duration.
+        let sampleRate = 8000;
+
+        // Only new a few render quanta to verify things are working.
+        let testDuration = 4 * 128 / sampleRate;
+
+        let context = new OfflineAudioContext({
+          numberOfChannels: 3,
+          sampleRate: sampleRate,
+          length: testDuration * sampleRate
+        });
+
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.numberOfChannels});
+        merger.connect(context.destination);
+        let inverter = new GainNode(context, {gain: -1});
+        inverter.connect(merger, 0, 2);
+
+        let kRateNode = new ConstantSourceNode(context);
+        let aRateNode = new ConstantSourceNode(context);
+
+        kRateNode.connect(merger, 0, 0);
+        aRateNode.connect(merger, 0, 1);
+
+        kRateNode.connect(merger, 0, 2);
+        aRateNode.connect(inverter);
+
+        // Set the rate
+        kRateNode.offset.automationRate = 'k-rate';
+
+        // Automate the offset
+        kRateNode.offset.setValueAtTime(0, 0);
+        kRateNode.offset.linearRampToValueAtTime(10, testDuration);
+
+        aRateNode.offset.setValueAtTime(0, 0);
+        aRateNode.offset.linearRampToValueAtTime(10, testDuration);
+
+        kRateNode.start();
+        aRateNode.start();
+
+        context.startRendering()
+            .then(audioBuffer => {
+              let kRateOut = audioBuffer.getChannelData(0);
+              let aRateOut = audioBuffer.getChannelData(1);
+              let diff = audioBuffer.getChannelData(2);
+
+              // Verify that the outputs are different.
+              should(diff, 'Difference between a-rate and k-rate outputs')
+                  .notBeConstantValueOf(0);
+
+              // Verify that the constant source node output is step-wise
+              // constant.
+              for (let k = 0; k < kRateOut.length; k += 128) {
+                should(
+                    kRateOut.slice(k, k + 128),
+                    `k-rate output [${k}: ${k + 127}]`)
+                    .beConstantValueOf(kRateOut[k]);
+              }
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-delay.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-delay.html
new file mode 100644
index 0000000..5465c39
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-delay.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParam of DelayNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="automation-rate-testing.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      audit.define('Test k-rate DelayNode', (task, should) => {
+        // Arbitrary sample rate and duration.
+        let sampleRate = 8000;
+        let testDuration = 1;
+        let context = new OfflineAudioContext({
+          numberOfChannels: 3,
+          sampleRate: sampleRate,
+          length: testDuration * sampleRate
+        });
+
+
+        doTest(context, should, {
+          nodeName: 'DelayNode',
+          nodeOptions: null,
+          prefix: 'DelayNode',
+          // Set all AudioParams to k-rate
+          rateSettings: [{name: 'delayTime', value: 'k-rate'}],
+          // Automate just the frequency
+          automations: [{
+            name: 'delayTime',
+            methods: [
+              {name: 'setValueAtTime', options: [0, 0]}, {
+                name: 'linearRampToValueAtTime',
+                options: [.5, testDuration]
+              }
+            ]
+          }]
+        }).then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-gain.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-gain.html
new file mode 100644
index 0000000..887d9f7
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-gain.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParam of GainNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="automation-rate-testing.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      audit.define('Test k-rate GainNode', (task, should) => {
+        // Arbitrary sample rate and duration.
+        let sampleRate = 8000;
+        let testDuration = 1;
+        let context = new OfflineAudioContext({
+          numberOfChannels: 3,
+          sampleRate: sampleRate,
+          length: testDuration * sampleRate
+        });
+
+
+        doTest(context, should, {
+          nodeName: 'GainNode',
+          nodeOptions: null,
+          prefix: 'GainNode',
+          // Set AudioParam to k-rate
+          rateSettings: [{name: 'gain', value: 'k-rate'}],
+          // Automate
+          automations: [{
+            name: 'gain',
+            methods: [
+              {name: 'setValueAtTime', options: [1, 0]},
+              {name: 'linearRampToValueAtTime', options: [0, testDuration]}
+            ]
+          }]
+        }).then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-oscillator.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-oscillator.html
new file mode 100644
index 0000000..1672f0d
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-oscillator.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParams of OscillatorNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      // Arbitrary sample rate and duration.
+      let sampleRate = 8000;
+
+      // Only new a few render quanta to verify things are working.
+      let testDuration = 4 * 128 / sampleRate;
+
+      [{name: 'detune', initial: 0, final: 1200}, {
+        name: 'frequency',
+        initial: 440,
+        final: sampleRate / 2
+      }].forEach(paramProperty => {
+        audit.define(
+            'Oscillator k-rate ' + paramProperty.name, (task, should) => {
+              let context = new OfflineAudioContext({
+                numberOfChannels: 3,
+                sampleRate: sampleRate,
+                length: testDuration * sampleRate
+              });
+
+              let merger = new ChannelMergerNode(
+                  context, {numberOfInputs: context.numberOfChannels});
+              merger.connect(context.destination);
+              let inverter = new GainNode(context, {gain: -1});
+              inverter.connect(merger, 0, 2);
+
+              let kRateNode = new OscillatorNode(context);
+              let aRateNode = new OscillatorNode(context);
+
+              kRateNode.connect(merger, 0, 0);
+              aRateNode.connect(merger, 0, 1);
+
+              kRateNode.connect(merger, 0, 2);
+              aRateNode.connect(inverter);
+
+              // Set the rate
+              kRateNode[paramProperty.name].automationRate = 'k-rate';
+
+              // Automate the offset
+              kRateNode[paramProperty.name].setValueAtTime(
+                  paramProperty.initial, 0);
+              kRateNode[paramProperty.name].linearRampToValueAtTime(
+                  paramProperty.final, testDuration);
+
+              aRateNode[paramProperty.name].setValueAtTime(
+                  paramProperty.initial, 0);
+              aRateNode[paramProperty.name].linearRampToValueAtTime(
+                  paramProperty.final, testDuration);
+
+              kRateNode.start();
+              aRateNode.start();
+
+              context.startRendering()
+                  .then(audioBuffer => {
+                    let kRateOut = audioBuffer.getChannelData(0);
+                    let aRateOut = audioBuffer.getChannelData(1);
+                    let diff = audioBuffer.getChannelData(2);
+
+                    // Verify that the outputs are different.
+                    should(
+                        diff,
+                        'k-rate ' + paramProperty.name +
+                            ': Difference between a-rate and k-rate outputs')
+                        .notBeConstantValueOf(0);
+
+                  })
+                  .then(() => task.done());
+            });
+      });
+
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-panner.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-panner.html
new file mode 100644
index 0000000..1e9e900
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-panner.html
@@ -0,0 +1,172 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParams of PannerNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="automation-rate-testing.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      // Define a test where we verify that a k-rate audio param produces
+      // different results from an a-rate audio param for each of the audio
+      // params of a biquad.
+      //
+      // Each entry gives the name of the AudioParam, an initial value to be
+      // used with setValueAtTime, and a final value to be used with
+      // linearRampToValueAtTime. (See |doTest| for details as well.)
+
+      [{name: 'positionX', initial: 0, final: 1000},
+       {name: 'positionY', initial: 0, final: 1000},
+       {name: 'orientationX', initial: .1, final: 1000},
+       {name: 'orientationY', initial: .1, final: 1000},
+       {name: 'orientationZ', initial: .1, final: 1000},
+      ].forEach(paramProperty => {
+        audit.define('Panner k-rate ' + paramProperty.name, (task, should) => {
+          // Arbitrary sample rate and duration.
+          let sampleRate = 8000;
+          let testDuration = 1;
+          let context = new OfflineAudioContext({
+            numberOfChannels: 3,
+            sampleRate: sampleRate,
+            length: testDuration * sampleRate
+          });
+
+          doTest(context, should, {
+            nodeName: 'PannerNode',
+            // Make the source directional so orientation matters, and set some
+            // defaults for the position and orientation so that we're not on an
+            // axis where the azimuth and elevation might be constant when
+            // moving one of the AudioParams.
+            nodeOptions: {
+              distanceModel: 'inverse',
+              coneOuterAngle: 360,
+              coneInnerAngle: 10,
+              positionX: 10,
+              positionY: 10,
+              positionZ: 10,
+              orientationX: 1,
+              orientationY: 1,
+              orientationZ: 1
+            },
+            prefix: `k-rate ${paramProperty.name}`,
+            // Just set the frequency to k-rate
+            rateSettings: [
+              {name: paramProperty.name, value: 'k-rate'},
+            ],
+            // Automate just the given AudioParam
+            automations: [{
+              name: paramProperty.name,
+              methods: [
+                {name: 'setValueAtTime', options: [paramProperty.initial, 0]}, {
+                  name: 'linearRampToValueAtTime',
+                  options: [paramProperty.final, testDuration]
+                }
+              ]
+            }]
+          }).then(() => task.done());
+        });
+      });
+
+      // Test k-rate automation of the listener.  The intial and final
+      // automation values are pretty arbitrary, except that they should be such
+      // that the panner and listener produces non-constant output.
+      [{name: 'positionX', initial: [1, 0], final: [1000, 1]},
+       {name: 'positionY', initial: [1, 0], final: [1000, 1]},
+       {name: 'positionZ', initial: [1, 0], final: [1000, 1]},
+       {name: 'forwardX', initial: [-1, 0], final: [1, 1]},
+       {name: 'forwardY', initial: [-1, 0], final: [1, 1]},
+       {name: 'forwardZ', initial: [-1, 0], final: [1, 1]},
+       {name: 'upX', initial: [-1, 0], final: [1000, 1]},
+       {name: 'upY', initial: [-1, 0], final: [1000, 1]},
+       {name: 'upZ', initial: [-1, 0], final: [1000, 1]},
+      ].forEach(paramProperty => {
+        audit.define(
+            'Listener k-rate ' + paramProperty.name, (task, should) => {
+              // Arbitrary sample rate and duration.
+              let sampleRate = 8000;
+              let testDuration = 5 * 128 / sampleRate;
+              let context = new OfflineAudioContext({
+                numberOfChannels: 1,
+                sampleRate: sampleRate,
+                length: testDuration * sampleRate
+              });
+
+              doListenerTest(context, should, {
+                param: paramProperty.name,
+                initial: paramProperty.initial,
+                final: paramProperty.final
+              }).then(() => task.done());
+            });
+      });
+
+      audit.run();
+
+      function doListenerTest(context, should, options) {
+        let src = new ConstantSourceNode(context);
+        let panner = new PannerNode(context, {
+          distanceModel: 'inverse',
+          coneOuterAngle: 360,
+          coneInnerAngle: 10,
+          positionX: 10,
+          positionY: 10,
+          positionZ: 10,
+          orientationX: 1,
+          orientationY: 1,
+          orientationZ: 1
+        });
+
+        src.connect(panner).connect(context.destination);
+
+        src.start();
+
+        let listener = context.listener;
+
+        // Set listener properties to "random" values so that motion on one of
+        // the attributes actually changes things relative to the panner
+        // location.
+        listener.positionX.value = -1;
+        listener.positionY.value = 1;
+        listener.positionZ.value = -1;
+        listener.forwardX.value = -1;
+        listener.forwardY.value = 1;
+        listener.forwardZ.value = -1;
+        listener.upX.value = 1;
+        listener.upY.value = 1;
+        listener.upZ.value = 1;
+
+        let audioParam = listener[options.param];
+        audioParam.automationRate = 'k-rate';
+
+        let prefix = `Listener ${options.param}`;
+        should(audioParam.automationRate, prefix + '.automationRate')
+            .beEqualTo('k-rate');
+        should(() => {
+          audioParam.setValueAtTime(...options.initial);
+        }, prefix + `.setValueAtTime(${options.initial})`).notThrow();
+        should(() => {
+          audioParam.linearRampToValueAtTime(...options.final);
+        }, prefix + `.linearRampToValueAtTime(${options.final})`).notThrow();
+
+        return context.startRendering().then(renderedBuffer => {
+          let prefix = `Listener k-rate ${options.param}: `;
+          let output = renderedBuffer.getChannelData(0);
+          // Sanity check that the output isn't constant.
+          should(output, prefix + `Output`).notBeConstantValueOf(output[0]);
+
+          // Verify that the output is constant over each render quantum
+          for (let k = 0; k < output.length; k += 128) {
+            should(
+                output.slice(k, k + 128), prefix + `Output [${k}, ${k + 127}]`)
+                .beConstantValueOf(output[k]);
+          }
+        });
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/k-rate-stereo-panner.html b/webaudio/the-audio-api/the-audioparam-interface/k-rate-stereo-panner.html
new file mode 100644
index 0000000..06905b8
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/k-rate-stereo-panner.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test k-rate AudioParam of StereoPannerNode</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="automation-rate-testing.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      audit.define('Test k-rate StereoPannerNode', (task, should) => {
+        // Arbitrary sample rate and duration.
+        let sampleRate = 8000;
+        let testDuration = 1;
+        let context = new OfflineAudioContext({
+          numberOfChannels: 3,
+          sampleRate: sampleRate,
+          length: testDuration * sampleRate
+        });
+
+        doTest(context, should, {
+          nodeName: 'StereoPannerNode',
+          nodeOptions: null,
+          prefix: 'StereoPannerNode',
+          // Set all AudioParams to k-rate.
+          rateSettings: [{name: 'pan', value: 'k-rate'}],
+          // Automate just the frequency.
+          automations: [{
+            name: 'pan',
+            methods: [
+              {name: 'setValueAtTime', options: [0, 0]}, {
+                name: 'linearRampToValueAtTime',
+                options: [.5, testDuration]
+              }
+            ]
+          }]
+        }).then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/retrospective-exponentialRampToValueAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/retrospective-exponentialRampToValueAtTime.html
index 20d3d59..978aeb9 100644
--- a/webaudio/the-audio-api/the-audioparam-interface/retrospective-exponentialRampToValueAtTime.html
+++ b/webaudio/the-audio-api/the-audioparam-interface/retrospective-exponentialRampToValueAtTime.html
@@ -1,51 +1,70 @@
 <!doctype html>
 <meta charset=utf-8>
-<title>Test exponentialRampToValue with end time in the past</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script>
-function do_test(t, context) {
-  var source = context.createConstantSource();
-  source.start();
+<html>
+  <head>
+    <title>Test exponentialRampToValue with end time in the past</title>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="retrospective-test.js"></script>
+  </head>
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
 
-  var test = context.createGain();
-  test.gain.exponentialRampToValueAtTime(0.1, 0.5*context.currentTime);
-  test.gain.exponentialRampToValueAtTime(0.9, 2.0);
+      audit.define(
+          {
+            label: 'test',
+            description: 'Test exponentialRampToValue with end time in the past'
+          },
+          (task, should) => {
+            let {context, source, test, reference} = setupRetrospectiveGraph();
 
-  var reference = context.createGain();
-  reference.gain.exponentialRampToValueAtTime(0.1, context.currentTime);
-  reference.gain.exponentialRampToValueAtTime(0.9, 2.0);
+            // Suspend the context at this frame so we can synchronously set up
+            // automations.
+            const suspendFrame = 128;
 
-  source.connect(test);
-  source.connect(reference);
+            context.suspend(suspendFrame / context.sampleRate)
+                .then(() => {
+                  // Call setTargetAtTime with a time in the past
+                  test.gain.exponentialRampToValueAtTime(
+                      0.1, 0.5 * context.currentTime);
+                  test.gain.exponentialRampToValueAtTime(0.9, 1.0);
 
-  var merger = context.createChannelMerger();
-  test.connect(merger, 0, 0);
-  reference.connect(merger, 0, 1);
+                  reference.gain.exponentialRampToValueAtTime(
+                      0.1, context.currentTime);
+                  reference.gain.exponentialRampToValueAtTime(0.9, 1.0);
+                })
+                .then(() => context.resume());
 
-  var processor = context.createScriptProcessor(0, 2, 0);
-  merger.connect(processor);
-  processor.onaudioprocess =
-    t.step_func_done((e) => {
-      source.stop();
-      processor.onaudioprocess = null;
+            source.start();
 
-      var testValue = e.inputBuffer.getChannelData(0)[0];
-      var referenceValue = e.inputBuffer.getChannelData(1)[0];
+            context.startRendering()
+                .then(resultBuffer => {
+                  let testValue = resultBuffer.getChannelData(0);
+                  let referenceValue = resultBuffer.getChannelData(1);
 
-      assert_equals(testValue, referenceValue,
-                        "value matches expected");
-    });
-}
+                  // Until the suspendFrame, both should be exactly equal to 1.
+                  should(
+                      testValue.slice(0, suspendFrame),
+                      `Test[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
+                  should(
+                      referenceValue.slice(0, suspendFrame),
+                      `Reference[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
 
-async_test(function(t) {
-  var context = new AudioContext;
-  (function waitForTimeAdvance() {
-    if (context.currentTime == 0) {
-      t.step_timeout(waitForTimeAdvance, 0);
-    } else {
-      do_test(t, context);
-    }
-  })();
-});
-</script>
+                  // After the suspendFrame, both should be equal (and not
+                  // constant)
+                  should(
+                      testValue.slice(suspendFrame), `Test[${suspendFrame}:]`)
+                      .beEqualToArray(referenceValue.slice(suspendFrame));
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>       
diff --git a/webaudio/the-audio-api/the-audioparam-interface/retrospective-linearRampToValueAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/retrospective-linearRampToValueAtTime.html
index 1594a30..42af6a7 100644
--- a/webaudio/the-audio-api/the-audioparam-interface/retrospective-linearRampToValueAtTime.html
+++ b/webaudio/the-audio-api/the-audioparam-interface/retrospective-linearRampToValueAtTime.html
@@ -1,51 +1,70 @@
 <!doctype html>
 <meta charset=utf-8>
-<title>Test linearRampToValue with end time in the past</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script>
-function do_test(t, context) {
-  var source = context.createConstantSource();
-  source.start();
+<html>
+  <head>
+    <title>Test linearRampToValue with end time in the past</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="retrospective-test.js"></script>
+  </head>
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
 
-  var test = context.createGain();
-  test.gain.linearRampToValueAtTime(0.1, 0.5*context.currentTime);
-  test.gain.linearRampToValueAtTime(0.9, 2.0);
+      audit.define(
+          {
+            label: 'test',
+            description: 'Test linearRampToValue with end time in the past'
+          },
+          (task, should) => {
+            let {context, source, test, reference} = setupRetrospectiveGraph();
 
-  var reference = context.createGain();
-  reference.gain.linearRampToValueAtTime(0.1, context.currentTime);
-  reference.gain.linearRampToValueAtTime(0.9, 2.0);
+            // Suspend the context at this frame so we can synchronously set up
+            // automations.
+            const suspendFrame = 128;
 
-  source.connect(test);
-  source.connect(reference);
+            context.suspend(suspendFrame / context.sampleRate)
+                .then(() => {
+                  // Call setTargetAtTime with a time in the past
+                  test.gain.linearRampToValueAtTime(
+                      0.1, 0.5 * context.currentTime);
+                  test.gain.linearRampToValueAtTime(0.9, 1.0);
 
-  var merger = context.createChannelMerger();
-  test.connect(merger, 0, 0);
-  reference.connect(merger, 0, 1);
+                  reference.gain.linearRampToValueAtTime(
+                      0.1, context.currentTime);
+                  reference.gain.linearRampToValueAtTime(0.9, 1.0);
+                })
+                .then(() => context.resume());
 
-  var processor = context.createScriptProcessor(0, 2, 0);
-  merger.connect(processor);
-  processor.onaudioprocess =
-    t.step_func_done((e) => {
-      source.stop();
-      processor.onaudioprocess = null;
+            source.start();
 
-      var testValue = e.inputBuffer.getChannelData(0)[0];
-      var referenceValue = e.inputBuffer.getChannelData(1)[0];
+            context.startRendering()
+                .then(resultBuffer => {
+                  let testValue = resultBuffer.getChannelData(0);
+                  let referenceValue = resultBuffer.getChannelData(1);
 
-      assert_equals(testValue, referenceValue,
-                        "value matches expected");
-    });
-}
+                  // Until the suspendFrame, both should be exactly equal to 1.
+                  should(
+                      testValue.slice(0, suspendFrame),
+                      `Test[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
+                  should(
+                      referenceValue.slice(0, suspendFrame),
+                      `Reference[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
 
-async_test(function(t) {
-  var context = new AudioContext;
-  (function waitForTimeAdvance() {
-    if (context.currentTime == 0) {
-      t.step_timeout(waitForTimeAdvance, 0);
-    } else {
-      do_test(t, context);
-    }
-  })();
-});
-</script>
+                  // After the suspendFrame, both should be equal (and not
+                  // constant)
+                  should(
+                      testValue.slice(suspendFrame), `Test[${suspendFrame}:]`)
+                      .beEqualToArray(referenceValue.slice(suspendFrame));
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>       
diff --git a/webaudio/the-audio-api/the-audioparam-interface/retrospective-setTargetAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/retrospective-setTargetAtTime.html
index 9b04fe2..5342790 100644
--- a/webaudio/the-audio-api/the-audioparam-interface/retrospective-setTargetAtTime.html
+++ b/webaudio/the-audio-api/the-audioparam-interface/retrospective-setTargetAtTime.html
@@ -1,51 +1,80 @@
 <!doctype html>
 <meta charset=utf-8>
-<title>Test setTargetAtTime with start time in the past</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script>
-function do_test(t, context) {
-  var source = context.createConstantSource();
-  source.start();
+<html>
+  <head>
+    <title>Test setTargetAtTime with start time in the past</title>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>    
+    <script>
+      let audit = Audit.createTaskRunner();
 
-  var test = context.createGain();
-  test.gain.setTargetAtTime(0.1, 0.5*context.currentTime, 0.1);
-  test.gain.linearRampToValueAtTime(0.9, 2.0);
+      audit.define(
+          {
+            label: 'test',
+            description: 'Test setTargetAtTime with start time in the past'
+          },
+          (task, should) => {
+            // Use a sample rate that is a power of two to eliminate round-off
+            // in computing the currentTime.
+            let context = new OfflineAudioContext(2, 16384, 16384);
+            let source = new ConstantSourceNode(context);
 
-  var reference = context.createGain();
-  reference.gain.setTargetAtTime(0.1, context.currentTime, 0.1);
-  reference.gain.linearRampToValueAtTime(0.9, 2.0);
+            // Suspend the context at this frame so we can synchronously set up
+            // automations.
+            const suspendFrame = 128;
 
-  source.connect(test);
-  source.connect(reference);
+            let test = new GainNode(context);
+            let reference = new GainNode(context);
 
-  var merger = context.createChannelMerger();
-  test.connect(merger, 0, 0);
-  reference.connect(merger, 0, 1);
+            source.connect(test);
+            source.connect(reference);
 
-  var processor = context.createScriptProcessor(0, 2, 0);
-  merger.connect(processor);
-  processor.onaudioprocess =
-    t.step_func_done((e) => {
-      source.stop();
-      processor.onaudioprocess = null;
+            let merger = new ChannelMergerNode(
+                context, {numberOfInputs: context.destination.channelCount});
+            test.connect(merger, 0, 0);
+            reference.connect(merger, 0, 1);
 
-      var testValue = e.inputBuffer.getChannelData(0)[0];
-      var referenceValue = e.inputBuffer.getChannelData(1)[0];
+            merger.connect(context.destination);
 
-      assert_equals(testValue, referenceValue,
-                        "value matches expected");
-    });
-}
+            context.suspend(suspendFrame / context.sampleRate)
+                .then(() => {
+                  // Call setTargetAtTime with a time in the past
+                  test.gain.setTargetAtTime(0.1, 0.5*context.currentTime, 0.1);
+                  reference.gain.setTargetAtTime(0.1, context.currentTime, 0.1);
+                })
+                .then(() => context.resume());
 
-async_test(function(t) {
-  var context = new AudioContext;
-  (function waitForTimeAdvance() {
-    if (context.currentTime == 0) {
-      t.step_timeout(waitForTimeAdvance, 0);
-    } else {
-      do_test(t, context);
-    }
-  })();
-});
-</script>
+            source.start();
+
+            context.startRendering()
+                .then(resultBuffer => {
+                  let testValue = resultBuffer.getChannelData(0);
+                  let referenceValue = resultBuffer.getChannelData(1);
+
+                  // Until the suspendFrame, both should be exactly equal to 1.
+                  should(
+                      testValue.slice(0, suspendFrame),
+                      `Test[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
+                  should(
+                      referenceValue.slice(0, suspendFrame),
+                      `Reference[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
+
+                  // After the suspendFrame, both should be equal (and not
+                  // constant)
+                  should(
+                      testValue.slice(suspendFrame), `Test[${suspendFrame}:]`)
+                      .beEqualToArray(referenceValue.slice(suspendFrame));
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueAtTime.html
index b9657ef..32cdc63 100644
--- a/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueAtTime.html
+++ b/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueAtTime.html
@@ -1,54 +1,74 @@
 <!DOCTYPE html>
-<title>Test setValueAtTime with startTime in the past</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-function do_test(t, context) {
-  var source = context.createConstantSource();
-  source.start();
+<html>
+  <head>
+    <title>Test setValueAtTime with startTime in the past</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="retrospective-test.js"></script>
+  </head>
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
 
-  // Use a ramp of slope 1/sample to measure time.
-  // The end value is the extent of exact precision in single precision float.
-  const rampEnd = Math.pow(2, 24);
-  const rampEndSeconds = rampEnd / context.sampleRate;
-  var test = context.createGain();
-  test.gain.setValueAtTime(0.0, 0.5*context.currentTime);
-  test.gain.linearRampToValueAtTime(rampEnd, rampEndSeconds);
+      audit.define(
+          {
+            label: 'test',
+            description: 'Test setValueAtTime with startTime in the past'
+          },
+          (task, should) => {
+            let {context, source, test, reference} = setupRetrospectiveGraph();
 
-  var reference = context.createGain();
-  reference.gain.setValueAtTime(0.0, context.currentTime);
-  reference.gain.linearRampToValueAtTime(rampEnd, rampEndSeconds);
+            // Suspend the context at this frame so we can synchronously set up
+            // automations.
+            const suspendFrame = 128;
 
-  source.connect(test);
-  source.connect(reference);
+            // Use a ramp of slope 1 per frame to measure time.
+            // The end value is the extent of exact precision in single
+            // precision float.
+            const rampEnd = context.length - suspendFrame;
+            const rampEndSeconds = context.length / context.sampleRate;
 
-  var merger = context.createChannelMerger();
-  test.connect(merger, 0, 0);
-  reference.connect(merger, 0, 1);
+            context.suspend(suspendFrame / context.sampleRate)
+                .then(() => {
+                  // Call setValueAtTime with a time in the past
+                  test.gain.setValueAtTime(0.0, 0.5 * context.currentTime);
+                  test.gain.linearRampToValueAtTime(rampEnd, rampEndSeconds);
 
-  var processor = context.createScriptProcessor(0, 2, 0);
-  merger.connect(processor);
-  processor.onaudioprocess =
-    t.step_func_done((e) => {
-      source.stop();
-      processor.onaudioprocess = null;
+                  reference.gain.setValueAtTime(0.0, context.currentTime);
+                  reference.gain.linearRampToValueAtTime(
+                      rampEnd, rampEndSeconds);
+                })
+                .then(() => context.resume());
 
-      var testValue = e.inputBuffer.getChannelData(0)[0];
-      var referenceValue = e.inputBuffer.getChannelData(1)[0];
+            source.start();
 
-      assert_equals(testValue, referenceValue,
-                        "ramp value matches expected");
-    });
-}
+            context.startRendering()
+                .then(resultBuffer => {
+                  let testValue = resultBuffer.getChannelData(0);
+                  let referenceValue = resultBuffer.getChannelData(1);
 
-async_test(function(t) {
-  var context = new AudioContext;
-  (function waitForTimeAdvance() {
-    if (context.currentTime == 0) {
-      t.step_timeout(waitForTimeAdvance, 0);
-    } else {
-      do_test(t, context);
-    }
-  })();
-});
-</script>
+                  // Until the suspendFrame, both should be exactly equal to 1.
+                  should(
+                      testValue.slice(0, suspendFrame),
+                      `Test[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
+                  should(
+                      referenceValue.slice(0, suspendFrame),
+                      `Reference[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
+
+                  // After the suspendFrame, both should be equal (and not
+                  // constant)
+                  should(
+                      testValue.slice(suspendFrame), `Test[${suspendFrame}:]`)
+                      .beEqualToArray(referenceValue.slice(suspendFrame));
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime.html b/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime.html
index 008b240..451b6ea 100644
--- a/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime.html
+++ b/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime.html
@@ -1,49 +1,67 @@
 <!doctype html>
-<meta charset=utf-8>
-<title>Test SetValueCurve with start time in the past</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script>
-function do_test(t, context) {
-  var source = context.createConstantSource();
-  source.start();
+<html>
+  <head>
+    <title>Test SetValueCurve with start time in the past</title>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="retrospective-test.js"></script>
+  </head>
+  </body>
+    <script>
+      let audit = Audit.createTaskRunner();
 
-  var test = context.createGain();
-  test.gain.setValueCurveAtTime(new Float32Array([1.0, 0.1]), 0.0, 1.0);
+      audit.define(
+          {
+            label: 'test',
+            description: 'Test SetValueCurve with start time in the past'
+          },
+          (task, should) => {
+            let {context, source, test, reference} = setupRetrospectiveGraph();
 
-  var reference = context.createGain();
-  reference.gain.setValueCurveAtTime(new Float32Array([1.0, 0.1]), 0.5*context.currentTime, 1.0);
+            // Suspend the context at this frame so we can synchronously set up
+            // automations.
+            const suspendFrame = 128;
 
-  source.connect(test);
-  source.connect(reference);
+            context.suspend(suspendFrame / context.sampleRate)
+                .then(() => {
+                  // Call setValueAtTime with a time in the past
+                  test.gain.setValueCurveAtTime(
+                      new Float32Array([1.0, 0.1]), 0.5 * context.currentTime,
+                      1.0);
+                  reference.gain.setValueCurveAtTime(
+                      new Float32Array([1.0, 0.1]), context.currentTime, 1.0);
+                })
+                .then(() => context.resume());
 
-  var merger = context.createChannelMerger();
-  test.connect(merger, 0, 0);
-  reference.connect(merger, 0, 1);
+            source.start();
 
-  var processor = context.createScriptProcessor(0, 2, 0);
-  merger.connect(processor);
-  processor.onaudioprocess =
-    t.step_func_done((e) => {
-      source.stop();
-      processor.onaudioprocess = null;
+            context.startRendering()
+                .then(resultBuffer => {
+                  let testValue = resultBuffer.getChannelData(0);
+                  let referenceValue = resultBuffer.getChannelData(1);
 
-      var testValue = e.inputBuffer.getChannelData(0)[0];
-      var referenceValue = e.inputBuffer.getChannelData(1)[0];
+                  // Until the suspendFrame, both should be exactly equal to 1.
+                  should(
+                      testValue.slice(0, suspendFrame),
+                      `Test[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
+                  should(
+                      referenceValue.slice(0, suspendFrame),
+                      `Reference[0:${suspendFrame - 1}]`)
+                      .beConstantValueOf(1);
 
-      assert_equals(testValue, referenceValue,
-                        "value matches expected");
-    });
-}
+                  // After the suspendFrame, both should be equal (and not
+                  // constant)
+                  should(
+                      testValue.slice(suspendFrame), `Test[${suspendFrame}:]`)
+                      .beEqualToArray(referenceValue.slice(suspendFrame));
+                })
+                .then(() => task.done());
+          });
 
-async_test(function(t) {
-  var context = new AudioContext;
-  (function waitForTimeAdvance() {
-    if (context.currentTime == 0) {
-      t.step_timeout(waitForTimeAdvance, 0);
-    } else {
-      do_test(t, context);
-    }
-  })();
-});
-</script>
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioparam-interface/retrospective-test.js b/webaudio/the-audio-api/the-audioparam-interface/retrospective-test.js
new file mode 100644
index 0000000..bbda190
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioparam-interface/retrospective-test.js
@@ -0,0 +1,29 @@
+// Create an audio graph on an offline context that consists of a
+// constant source and two gain nodes. One of the nodes is the node te
+// be tested and the other is the reference node.  The output from the
+// test node is in channel 0 of the offline context; the reference
+// node is in channel 1.
+//
+// Returns a dictionary with the context, source node, the test node,
+// and the reference node.
+function setupRetrospectiveGraph() {
+  // Use a sample rate that is a power of two to eliminate round-off
+  // in computing the currentTime.
+  let context = new OfflineAudioContext(2, 16384, 16384);
+  let source = new ConstantSourceNode(context);
+
+  let test = new GainNode(context);
+  let reference = new GainNode(context);
+
+  source.connect(test);
+  source.connect(reference);
+
+  let merger = new ChannelMergerNode(
+      context, {numberOfInputs: context.destination.channelCount});
+  test.connect(merger, 0, 0);
+  reference.connect(merger, 0, 1);
+
+  merger.connect(context.destination);
+
+  return {context: context, source: source, test: test, reference: reference};
+}
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-addmodule-resolution.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-addmodule-resolution.https.html
new file mode 100644
index 0000000..e946212
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-addmodule-resolution.https.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test the invocation order of AudioWorklet.addModule() and BaseAudioContext
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 48000;
+      let realtimeContext = new AudioContext();
+      let offlineContext = new OfflineAudioContext(1, sampleRate, sampleRate);
+
+      let filePath = 'processors/dummy-processor.js';
+
+      // Test if the browser does not crash upon addModule() call after the
+      // realtime context construction.
+      audit.define(
+          {label: 'module-loading-after-realtime-context-creation'},
+          (task, should) => {
+            let dummyWorkletNode =
+                new AudioWorkletNode(realtimeContext, 'dummy');
+            dummyWorkletNode.connect(realtimeContext.destination);
+            should(dummyWorkletNode instanceof AudioWorkletNode,
+                   '"dummyWorkletNode" is an instance of AudioWorkletNode ' +
+                   'from realtime context')
+                .beTrue();
+            task.done();
+          });
+
+      // Test if the browser does not crash upon addModule() call after the
+      // offline context construction.
+      audit.define(
+          {label: 'module-loading-after-offline-context-creation'},
+          (task, should) => {
+            let dummyWorkletNode =
+                new AudioWorkletNode(offlineContext, 'dummy');
+            dummyWorkletNode.connect(offlineContext.destination);
+            should(dummyWorkletNode instanceof AudioWorkletNode,
+                   '"dummyWorkletNode" is an instance of AudioWorkletNode ' +
+                   'from offline context')
+                .beTrue();
+            task.done();
+          });
+
+      Promise.all([
+          realtimeContext.audioWorklet.addModule(filePath),
+          offlineContext.audioWorklet.addModule(filePath)
+        ]).then(() => {
+          audit.run();
+        });
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-audioparam.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-audioparam.https.html
new file mode 100644
index 0000000..8e51470
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-audioparam.https.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioWorkletNode's basic AudioParam features
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 48000;
+      let renderLength = 48000 * 0.6;
+      let context;
+
+      let filePath = 'processors/gain-processor.js';
+
+      // Sets up AudioWorklet and OfflineAudioContext.
+      audit.define('Initializing AudioWorklet and Context', (task, should) => {
+        context = new OfflineAudioContext(1, renderLength, sampleRate);
+        context.audioWorklet.addModule(filePath).then(() => {
+          task.done();
+        });
+      });
+
+      // Verifies the functionality of AudioParam in AudioWorkletNode by
+      // comparing (canceling out) values from GainNode and AudioWorkletNode
+      // with simple gain computation code by AudioParam.
+      audit.define(
+          'Verifying AudioParam in AudioWorkletNode',
+          (task, should) => {
+            let constantSourceNode = new ConstantSourceNode(context);
+            let gainNode = new GainNode(context);
+            let inverterNode = new GainNode(context, {gain: -1});
+            let gainWorkletNode = new AudioWorkletNode(context, 'gain');
+            let gainWorkletParam = gainWorkletNode.parameters.get('gain');
+
+            // Test default value and setter/getter functionality.
+            should(gainWorkletParam.value,
+                   'Default gain value of gainWorkletNode')
+                .beEqualTo(Math.fround(0.707));
+            gainWorkletParam.value = 0.1;
+            should(gainWorkletParam.value,
+                   'Value of gainWorkletParam after setter = 0.1')
+                .beEqualTo(Math.fround(0.1));
+
+            constantSourceNode.connect(gainNode)
+                .connect(inverterNode)
+                .connect(context.destination);
+            constantSourceNode.connect(gainWorkletNode)
+                .connect(context.destination);
+
+            // With arbitrary times and values, test all possible AudioParam
+            // automations.
+            [gainNode.gain, gainWorkletParam].forEach((param) => {
+              param.setValueAtTime(0, 0);
+              param.linearRampToValueAtTime(1, 0.1);
+              param.exponentialRampToValueAtTime(0.5, 0.2);
+              param.setValueCurveAtTime([0, 2, 0.3], 0.2, 0.1);
+              param.setTargetAtTime(0.01, 0.4, 0.5);
+            });
+
+            // Test if the setter works correctly in the middle of rendering.
+            context.suspend(0.5).then(() => {
+              gainNode.gain.value = 1.5;
+              gainWorkletParam.value = 1.5;
+              context.resume();
+            });
+
+            constantSourceNode.start();
+            context.startRendering().then((renderedBuffer) => {
+              should(renderedBuffer.getChannelData(0),
+                     'The rendered buffer')
+                  .beConstantValueOf(0);
+              task.done();
+            });
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-messageport.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-messageport.https.html
new file mode 100644
index 0000000..546bd1d
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-messageport.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test MessagePort in AudioWorkletNode and AudioWorkletProcessor
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let context = new AudioContext();
+
+      let filePath = 'processors/port-processor.js';
+
+      // Creates an AudioWorkletNode and sets an EventHandler on MessagePort
+      // object. The associated PortProcessor will post a message upon its
+      // construction. Test if the message is received correctly.
+      audit.define(
+          'Test postMessage from AudioWorkletProcessor to AudioWorkletNode',
+          (task, should) => {
+            let porterWorkletNode =
+                new AudioWorkletNode(context, 'port-processor');
+
+            // Upon the creation of PortProcessor, it will post a message to the
+            // node with 'created' status.
+            porterWorkletNode.port.onmessage = (event) => {
+              should(event.data.state,
+                     'The initial message from PortProcessor')
+                  .beEqualTo('created');
+              task.done();
+            };
+          });
+
+      // PortProcessor is supposed to echo the message back to the
+      // AudioWorkletNode.
+      audit.define(
+          'Test postMessage from AudioWorkletNode to AudioWorkletProcessor',
+          (task, should) => {
+            let porterWorkletNode =
+                new AudioWorkletNode(context, 'port-processor');
+
+            porterWorkletNode.port.onmessage = (event) => {
+              // Ignore if the delivered message has |state|. This is already
+              // tested in the previous task.
+              if (event.data.state)
+                return;
+
+              should(event.data.message,
+                     'The response from PortProcessor')
+                  .beEqualTo('hello');
+              task.done();
+            };
+
+            porterWorkletNode.port.postMessage('hello');
+          });
+
+      context.audioWorklet.addModule(filePath).then(() => {
+        audit.run();
+      });
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletglobalscope-sample-rate.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletglobalscope-sample-rate.https.html
new file mode 100644
index 0000000..d87e35b
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletglobalscope-sample-rate.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test sampleRate in AudioWorkletGlobalScope
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 48000;
+      let renderLength = 512;
+      let context = new OfflineAudioContext(1, renderLength, sampleRate);
+
+      let filePath = 'processors/one-pole-processor.js';
+
+      // Without rendering the context, attempt to access |sampleRate| in the
+      // global scope as soon as it is created.
+      audit.define(
+          'Query |sampleRate| upon AudioWorkletGlobalScope construction',
+          (task, should) => {
+            let onePoleFilterNode =
+                new AudioWorkletNode(context, 'one-pole-filter');
+            let frequencyParam = onePoleFilterNode.parameters.get('frequency');
+
+            should(frequencyParam.maxValue,
+                   'frequencyParam.maxValue')
+                .beEqualTo(0.5 * context.sampleRate);
+
+            task.done();
+          });
+
+      context.audioWorklet.addModule(filePath).then(() => {
+        audit.run();
+      });
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletglobalscope-timing-info.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletglobalscope-timing-info.https.html
new file mode 100644
index 0000000..79d402c
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletglobalscope-timing-info.https.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test currentTime and currentFrame in AudioWorkletGlobalScope
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 48000;
+      let renderLength = 512;
+      let context = new OfflineAudioContext(1, renderLength, sampleRate);
+
+      let filePath = 'processors/timing-info-processor.js';
+
+      audit.define(
+          'Check the timing information from AudioWorkletProcessor',
+          (task, should) => {
+            let portWorkletNode =
+                new AudioWorkletNode(context, 'timing-info-processor');
+            portWorkletNode.connect(context.destination);
+
+            // Suspend at render quantum boundary and check the timing
+            // information between the main thread and the rendering thread.
+            [0, 128, 256, 384].map((suspendFrame) => {
+              context.suspend(suspendFrame/sampleRate).then(() => {
+                portWorkletNode.port.onmessage = (event) => {
+                  should(event.data.currentFrame,
+                         'currentFrame from the processor at ' + suspendFrame)
+                      .beEqualTo(suspendFrame);
+                  should(event.data.currentTime,
+                         'currentTime from the processor at '
+                             + context.currentTime)
+                      .beEqualTo(context.currentTime);
+                  context.resume();
+                };
+
+                portWorkletNode.port.postMessage('query-timing-info');
+              });
+            });
+
+            context.startRendering().then(() => {
+              task.done();
+            });
+          });
+
+      context.audioWorklet.addModule(filePath).then(() => {
+        audit.run();
+      });
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-automatic-pull.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-automatic-pull.https.html
new file mode 100644
index 0000000..330b359
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-automatic-pull.https.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioWorkletNode's automatic pull feature
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      const audit = Audit.createTaskRunner();
+
+      // Arbitrary sample rate. Anything should work.
+      const sampleRate = 48000;
+      const renderLength = RENDER_QUANTUM_FRAMES * 2;
+      const channelCount = 1;
+      const filePath = 'processors/zero-output-processor.js';
+
+      const sourceOffset = 0.5;
+
+      // Connect a constant source node to the zero-output AudioWorkletNode.
+      // Then verify if it captures the data correctly.
+      audit.define('setup-worklet', (task, should) => {
+        const context =
+            new OfflineAudioContext(channelCount, renderLength, sampleRate);
+
+        context.audioWorklet.addModule(filePath).then(() => {
+          let testSource =
+              new ConstantSourceNode(context, { offset: sourceOffset });
+          let zeroOutputWorkletNode =
+              new AudioWorkletNode(context, 'zero-output-processor', {
+                numberOfInputs: 1,
+                numberOfOutputs: 0,
+                processorOptions: {
+                  bufferLength: renderLength,
+                  channeCount: channelCount
+                }
+              });
+
+          // Start the source and stop at the first render quantum.
+          testSource.connect(zeroOutputWorkletNode);
+          testSource.start();
+          testSource.stop(RENDER_QUANTUM_FRAMES/sampleRate);
+
+          zeroOutputWorkletNode.port.onmessage = (event) => {
+            // The |capturedBuffer| can be multichannel. Iterate through it.
+            for (let i = 0; i < event.data.capturedBuffer.length; ++i) {
+              let buffer = event.data.capturedBuffer[i];
+              // Split the captured buffer in half for the easier test.
+              should(buffer.subarray(0, RENDER_QUANTUM_FRAMES),
+                     'The first half of the captured buffer')
+                  .beConstantValueOf(sourceOffset);
+              should(buffer.subarray(RENDER_QUANTUM_FRAMES, renderLength),
+                     'The second half of the captured buffer')
+                  .beConstantValueOf(0);
+            }
+            task.done();
+          };
+
+          // Starts the rendering, but we don't need the rendered buffer from
+          // the context.
+          context.startRendering();
+        });
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
+
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-channel-count.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-channel-count.https.html
new file mode 100644
index 0000000..11c237f
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-channel-count.https.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioWorkletNode's dynamic channel count feature
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Arbitrary numbers used to align the test with render quantum boundary.
+      let sampleRate = RENDER_QUANTUM_FRAMES * 100;
+      let renderLength = RENDER_QUANTUM_FRAMES * 2;
+      let context;
+
+      let filePath = 'processors/gain-processor.js';
+
+      let testChannelValues = [1, 2, 3];
+
+      // Creates a 3-channel buffer and play with BufferSourceNode. The source
+      // goes through a bypass AudioWorkletNode (gain value of 1).
+      audit.define('setup-buffer-and-worklet', (task, should) => {
+        context = new OfflineAudioContext(testChannelValues.length,
+                                          renderLength,
+                                          sampleRate);
+
+        // Explicitly sets the destination channelCountMode and
+        // channelInterpretation to make sure the result does no mixing.
+        context.channeCountMode = 'explicit';
+        context.channelInterpretation = 'discrete';
+
+        context.audioWorklet.addModule(filePath).then(() => {
+          let testBuffer = createConstantBuffer(context, 1, testChannelValues);
+          let sourceNode = new AudioBufferSourceNode(context);
+          let gainWorkletNode = new AudioWorkletNode(context, 'gain');
+
+          gainWorkletNode.parameters.get('gain').value = 1.0;
+          sourceNode.connect(gainWorkletNode).connect(context.destination);
+
+          // Suspend the context at 128 sample frames and play the source with
+          // the assigned buffer.
+          context.suspend(RENDER_QUANTUM_FRAMES/sampleRate).then(() => {
+            sourceNode.buffer = testBuffer;
+            sourceNode.loop = true;
+            sourceNode.start();
+            context.resume();
+          });
+          task.done();
+        });
+      });
+
+      // Verifies if the rendered buffer has all zero for the first half (before
+      // 128 samples) and the expected values for the second half.
+      audit.define('verify-rendered-buffer', (task, should) => {
+          context.startRendering().then(renderedBuffer => {
+            testChannelValues.forEach((value, index) => {
+              let channelData = renderedBuffer.getChannelData(index);
+              should(channelData.subarray(0, RENDER_QUANTUM_FRAMES),
+                     'First half of Channel #' + index)
+                  .beConstantValueOf(0);
+              should(channelData.subarray(RENDER_QUANTUM_FRAMES, renderLength),
+                     'Second half of Channel #' + index)
+                  .beConstantValueOf(value);
+            });
+            task.done();
+          });
+        });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-construction.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-construction.https.html
new file mode 100644
index 0000000..7cfd423
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-construction.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test the construction of AudioWorkletNode with real-time context
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let realtimeContext = new AudioContext();
+
+      let filePath = 'processors/dummy-processor.js';
+
+      // Test if an exception is thrown correctly when AWN constructor is
+      // invoked before resolving |.addModule()| promise.
+      audit.define(
+          {label: 'construction-before-module-loading'},
+          (task, should) => {
+            should(() => new AudioWorkletNode(realtimeContext, 'dummy'),
+                   'Creating a node before loading a module should throw.')
+                .throw('InvalidStateError');
+
+            task.done();
+          });
+
+      // Test the construction of AudioWorkletNode after the resolution of
+      // |.addModule()|. Also the constructor must throw an exception when
+      // a unregistered node name was given.
+      audit.define(
+          {label: 'construction-after-module-loading'},
+          (task, should) => {
+            realtimeContext.audioWorklet.addModule(filePath).then(() => {
+              let dummyWorkletNode =
+                  new AudioWorkletNode(realtimeContext, 'dummy');
+              should(dummyWorkletNode instanceof AudioWorkletNode,
+                     '"dummyWorkletNode" is an instance of AudioWorkletNode')
+                  .beTrue();
+              should(() => new AudioWorkletNode(realtimeContext, 'foobar'),
+                     'Unregistered name "foobar" must throw an exception.')
+                  .throw();
+              task.done();
+            });
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-constructor-options.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-constructor-options.https.html
new file mode 100644
index 0000000..254c07e
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-constructor-options.https.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test of AudioWorkletNodeOptions
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      const sampleRate = 48000;
+
+      const audit = Audit.createTaskRunner();
+      let context;
+
+      let filePath = 'processors/dummy-processor.js';
+
+      // Load script file and create a OfflineAudiocontext.
+      audit.define('setup', (task, should) => {
+        context = new OfflineAudioContext(1, 1, sampleRate);
+        context.audioWorklet.addModule(filePath).then(() => {
+          task.done();
+        });
+      });
+
+      // Test AudioWorkletNode construction without AudioWorkletNodeOptions.
+      audit.define('without-audio-node-options', (task, should) => {
+        let testNode;
+        should(
+            () => testNode = new AudioWorkletNode(context, 'dummy'),
+            'Creating AudioWOrkletNode without options')
+            .notThrow();
+        should(testNode instanceof AudioWorkletNode,
+               'testNode is instance of AudioWorkletNode').beEqualTo(true);
+        should(testNode.numberOfInputs,
+               'testNode.numberOfInputs (default)').beEqualTo(1);
+        should(testNode.numberOfOutputs,
+               'testNode.numberOfOutputs (default)').beEqualTo(1);
+        should(testNode.channelCount,
+               'testNode.channelCount (default)').beEqualTo(2);
+        should(testNode.channelCountMode,
+               'testNode.channelCountMode (default)').beEqualTo('max');
+        should(testNode.channelInterpretation,
+               'testNode.channelInterpretation (default)')
+            .beEqualTo('speakers');
+        task.done();
+      });
+
+      // Test AudioWorkletNode constructor with AudioNodeOptions.
+      audit.define('audio-node-options', (task, should) => {
+        const options = {
+          numberOfInputs: 7,
+          numberOfOutputs: 18,
+          channelCount: 4,
+          channelCountMode: 'clamped-max',
+          channelInterpretation: 'discrete'
+        };
+        const optionsString = JSON.stringify(options);
+
+        let testNode;
+        should(
+            () => testNode = new AudioWorkletNode(context, 'dummy', options),
+            'Creating AudioWOrkletNode with options: ' + optionsString)
+            .notThrow();
+        should(testNode.numberOfInputs,
+               'testNode.numberOfInputs').beEqualTo(options.numberOfInputs);
+        should(testNode.numberOfOutputs,
+               'testNode.numberOfOutputs').beEqualTo(options.numberOfOutputs);
+        should(testNode.channelCount,
+               'testNode.channelCount').beEqualTo(options.channelCount);
+        should(testNode.channelCountMode,
+               'testNode.channelCountMode').beEqualTo(options.channelCountMode);
+        should(testNode.channelInterpretation,
+               'testNode.channelInterpretation')
+            .beEqualTo(options.channelInterpretation);
+
+        task.done();
+      });
+
+      // Test AudioWorkletNode.channelCount.
+      audit.define('channel-count', (task, should) => {
+        const options1 = {channelCount: 17};
+        let testNode = new AudioWorkletNode(context, 'dummy', options1);
+        should(testNode.channelCount, 'testNode.channelCount')
+            .beEqualTo(options1.channelCount);
+
+        const options2 = {channelCount: 0};
+        should(
+            () => new AudioWorkletNode(context, 'dummy', options2),
+            'Creating AudioWorkletNode with channelCount 0')
+            .throw('NotSupportedError');
+
+        const options3 = {channelCount: 33};
+        should(
+            () => new AudioWorkletNode(context, 'dummy', options3),
+            'Creating AudioWorkletNode with channelCount 33')
+            .throw('NotSupportedError');
+
+        task.done();
+      });
+
+      // Test AudioWorkletNode.channelCountMode.
+      audit.define('channel-count-mode', (task, should) => {
+        const channelCountModes = ['max', 'clamped-max', 'explicit'];
+        channelCountModes.forEach((mode) => {
+          const options = {channelCountMode: mode};
+          let testNode = new AudioWorkletNode(context, 'dummy', options);
+          should(testNode.channelCountMode,
+                 'testNode.channelCountMode (set via options.' + mode + ')')
+              .beEqualTo(options.channelCountMode);
+        });
+
+        const options1 = {channelCountMode: 'foobar'};
+        should(
+            () => new AudioWorkletNode(context, 'dummy', options1),
+            'Creating AudioWorkletNode with channelCountMode "foobar"')
+            .throw('TypeError');
+
+        task.done();
+      });
+
+      // Test AudioWorkletNode.channelInterpretation.
+      audit.define('channel-interpretation', (task, should) => {
+        const channelInterpretations = ['speakers', 'discrete'];
+        channelInterpretations.forEach((interpretation) => {
+          const options = {channelInterpretation: interpretation};
+          let testNode = new AudioWorkletNode(context, 'dummy', options);
+          should(
+              testNode.channelInterpretation,
+              'testNode.channelInterpretation (set via options.' +
+                  interpretation + ')')
+              .beEqualTo(options.channelInterpretation);
+        });
+
+        const options1 = {channelInterpretation: 'foobar'};
+        should(
+            () => new AudioWorkletNode(context, 'dummy', options1),
+            'Creating AudioWorkletNode with channelCountMode "foobar"')
+            .throw('TypeError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-disconnected-input.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-disconnected-input.https.html
new file mode 100644
index 0000000..c58502a
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-disconnected-input.https.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test AudioWorkletNode's Disconnected Input Array Length
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Arbitrary numbers used to align the test with render quantum boundary.
+      // The sample rate is a power of two to eliminate roundoff in computing
+      // the suspend time needed for the test.
+      let sampleRate = 16384;
+      let renderLength = 8 * RENDER_QUANTUM_FRAMES;
+      let context;
+
+      let filePath = 'processors/input-length-processor.js';
+
+      let testChannelValues = [1, 2, 3];
+
+      // Creates a 3-channel buffer and play with BufferSourceNode. The source
+      // goes through a bypass AudioWorkletNode (gain value of 1).
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'Input array length should be zero for disconnected input'
+          },
+          (task, should) => {
+            context = new OfflineAudioContext({
+              numberOfChannels: 1,
+              length: renderLength,
+              sampleRate: sampleRate
+            });
+
+            context.audioWorklet.addModule(filePath).then(() => {
+              let sourceNode = new ConstantSourceNode(context);
+              let workletNode =
+                  new AudioWorkletNode(context, 'input-length-processor');
+
+              workletNode.connect(context.destination);
+
+              // Connect the source now.
+              let connectFrame = RENDER_QUANTUM_FRAMES;
+
+              context.suspend(connectFrame / sampleRate)
+                  .then(() => {
+                    sourceNode.connect(workletNode);
+                  })
+                  .then(() => context.resume());
+              ;
+
+              // Then disconnect the source after a few renders
+              let disconnectFrame = 3 * RENDER_QUANTUM_FRAMES;
+              context.suspend(disconnectFrame / sampleRate)
+                  .then(() => {
+                    sourceNode.disconnect(workletNode);
+                  })
+                  .then(() => context.resume());
+
+              sourceNode.start();
+              context.startRendering()
+                  .then(resultBuffer => {
+                    let data = resultBuffer.getChannelData(0);
+
+                    should(
+                        data.slice(0, connectFrame),
+                        'Before connecting the source: Input array length')
+                        .beConstantValueOf(0);
+
+                    // Find where the output is no longer 0.
+                    let nonZeroIndex = data.findIndex(x => x > 0);
+                    should(nonZeroIndex, 'First non-zero output')
+                        .beEqualTo(connectFrame);
+
+                    should(
+                        data.slice(
+                            nonZeroIndex,
+                            nonZeroIndex + (disconnectFrame - connectFrame)),
+                        'While source is connected: Input array length')
+                        .beConstantValueOf(RENDER_QUANTUM_FRAMES);
+                    should(
+                        data.slice(disconnectFrame),
+                        'After disconnecting the source: Input array length')
+                        .beConstantValueOf(0);
+                  })
+                  .then(() => task.done());
+            });
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-onerror.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-onerror.https.html
new file mode 100644
index 0000000..0a9966a
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-onerror.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test onprocessorerror handler in AudioWorkletNode
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      const sampleRate = 48000;
+      const renderLength = sampleRate * 0.1;
+      let context = new OfflineAudioContext(1, renderLength, sampleRate);
+
+      let filePath = 'processors/error-processor.js';
+
+      // Test |onprocessorerror| called upon failure of processor constructor.
+      audit.define('constructor-error',
+          (task, should) => {
+            let constructorErrorWorkletNode =
+                new AudioWorkletNode(context, 'constructor-error');
+            constructorErrorWorkletNode.onprocessorerror = () => {
+              // Without 'processorerror' event callback, this test will be
+              // timed out.
+              task.done();
+            };
+          });
+
+      // Test |onprocessorerror| called upon failure of process() method.
+      audit.define('process-error',
+          (task, should) => {
+            let processErrorWorkletNode =
+                new AudioWorkletNode(context, 'process-error');
+            processErrorWorkletNode.connect(context.destination);
+            processErrorWorkletNode.onprocessorerror = () => {
+              // Without 'processorerror' event callback, this test will be
+              // timed out.
+              task.done();
+            };
+
+            context.startRendering();
+          });
+
+      // 'error-processor.js' contains 2 class definitions represents an error
+      // in the constructor and an error in the process method respectively.
+      context.audioWorklet.addModule(filePath).then(() => {
+        audit.run();
+      });
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-options.https.html b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-options.https.html
new file mode 100644
index 0000000..ea840ed
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-options.https.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test cross-thread passing of AudioWorkletNodeOptions
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      const audit = Audit.createTaskRunner();
+      const context = new AudioContext();
+
+      let filePath = 'processors/option-test-processor.js';
+
+      // Create a OptionTestProcessor and feed |processorData| to it. The
+      // processor should echo the received data to the node's |onmessage|
+      // handler.
+      audit.define('valid-processor-data', (task, should) => {
+        context.audioWorklet.addModule(filePath).then(() => {
+          let processorOptions = {
+            description: 'foo',
+            payload: [0, 1, 2, 3]
+          };
+
+          let optionTestNode =
+              new AudioWorkletNode(context, 'option-test-processor', {
+                processorOptions: processorOptions
+              });
+
+          optionTestNode.port.onmessage = (event) => {
+            should(event.data.processorOptions.description,
+                   '|description| field in processorOptions from processor("' +
+                       event.data.processorOptions.description + '")')
+                .beEqualTo(processorOptions.description,
+                           'the field in node constructor options ("' +
+                           processorOptions.description + '")');
+            should(event.data.processorOptions.payload,
+                   '|payload| array in processorOptions from processor([' +
+                       event.data.processorOptions.payload + '])')
+                .beEqualToArray([0, 1, 2, 3],
+                                'the array in node constructor options ([' +
+                                event.data.processorOptions.payload + '])');
+            task.done();
+          };
+        });
+      });
+
+
+      // Passing empty option dictionary should work without a problem.
+      audit.define('empty-option', (task, should) => {
+        context.audioWorklet.addModule(filePath).then(() => {
+          let optionTestNode =
+              new AudioWorkletNode(context, 'option-test-processor');
+
+          optionTestNode.port.onmessage = (event) => {
+            should(Object.keys(event.data).length,
+                   'Number of properties in data from processor')
+                .beEqualTo(2);
+            should(event.data.numberOfInputs,
+                   '|numberOfInputs| field in data from processor')
+                .beEqualTo(1);
+            should(event.data.numberOfOutputs,
+                   '|numberOfOutputs| field in data from processor')
+                .beEqualToArray(1);
+            task.done();
+          };
+        });
+      });
+
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/baseaudiocontext-audioworklet.https.html b/webaudio/the-audio-api/the-audioworklet-interface/baseaudiocontext-audioworklet.https.html
new file mode 100644
index 0000000..4281f56
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/baseaudiocontext-audioworklet.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Checking BaseAudioContext.audioWorklet
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let realtimeContext = new AudioContext();
+      let offlineContext = new OfflineAudioContext(1, 1, 44100);
+
+      // Test if AudioWorklet exists.
+      audit.define('Test if AudioWorklet exists', (task, should) => {
+        should(realtimeContext.audioWorklet instanceof AudioWorklet &&
+               offlineContext.audioWorklet instanceof AudioWorklet,
+               'BaseAudioContext.audioWorklet is an instance of AudioWorklet')
+            .beTrue();
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/dummy-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/dummy-processor.js
new file mode 100644
index 0000000..11155d5
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/dummy-processor.js
@@ -0,0 +1,18 @@
+/**
+ * @class DummyProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * This processor class demonstrates the bare-bone structure of the processor.
+ */
+class DummyProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+  }
+
+  process(inputs, outputs, parameters) {
+    // Doesn't do anything here.
+    return true;
+  }
+}
+
+registerProcessor('dummy', DummyProcessor);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/error-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/error-processor.js
new file mode 100644
index 0000000..3b010db
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/error-processor.js
@@ -0,0 +1,33 @@
+/**
+ * @class ConstructorErrorProcessor
+ * @extends AudioWorkletProcessor
+ */
+class ConstructorErrorProcessor extends AudioWorkletProcessor {
+  constructor() {
+    throw 'ConstructorErrorProcessor: an error thrown from constructor.';
+  }
+
+  process() {
+    return true;
+  }
+}
+
+
+/**
+ * @class ProcessErrorProcessor
+ * @extends AudioWorkletProcessor
+ */
+class ProcessErrorProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+  }
+
+  process() {
+    throw 'ProcessErrorProcessor: an error throw from process method.';
+    return true;
+  }
+}
+
+
+registerProcessor('constructor-error', ConstructorErrorProcessor);
+registerProcessor('process-error', ProcessErrorProcessor);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/gain-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/gain-processor.js
new file mode 100644
index 0000000..f5143b3
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/gain-processor.js
@@ -0,0 +1,33 @@
+/**
+ * @class GainProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * This processor class demonstrates the bare-bone structure of the processor.
+ */
+class GainProcessor extends AudioWorkletProcessor {
+  static get parameterDescriptors() {
+    return [
+      {name: 'gain', defaultValue: 0.707}
+    ];
+  }
+
+  constructor() {
+    super();
+  }
+
+  process(inputs, outputs, parameters) {
+    let input = inputs[0];
+    let output = outputs[0];
+    let gain = parameters.gain;
+    for (let channel = 0; channel < input.length; ++channel) {
+      let inputChannel = input[channel];
+      let outputChannel = output[channel];
+      for (let i = 0; i < inputChannel.length; ++i)
+        outputChannel[i] = inputChannel[i] * gain[i];
+    }
+
+    return true;
+  }
+}
+
+registerProcessor('gain', GainProcessor);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/input-length-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/input-length-processor.js
new file mode 100644
index 0000000..cc0968d
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/input-length-processor.js
@@ -0,0 +1,25 @@
+/**
+ * @class InputLengthProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * This processor class just sets the output to the length of the
+ * input array for verifying that the input length changes when the
+ * input is disconnected.
+ */
+class InputLengthProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+  }
+
+  process(inputs, outputs, parameters) {
+    let input = inputs[0];
+    let output = outputs[0];
+
+    // Set output channel to the length of the input channel array.
+    output[0].fill(input[0].length);
+
+    return true;
+  }
+}
+
+registerProcessor('input-length-processor', InputLengthProcessor);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/one-pole-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/one-pole-processor.js
new file mode 100644
index 0000000..0bcc43f
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/one-pole-processor.js
@@ -0,0 +1,49 @@
+/**
+ * @class OnePoleFilter
+ * @extends AudioWorkletProcessor
+ *
+ * A simple One-pole filter.
+ */
+
+class OnePoleFilter extends AudioWorkletProcessor {
+
+  // This gets evaluated as soon as the global scope is created.
+  static get parameterDescriptors() {
+    return [{
+      name: 'frequency',
+      defaultValue: 250,
+      minValue: 0,
+      maxValue: 0.5 * sampleRate
+    }];
+  }
+
+  constructor() {
+    super();
+    this.updateCoefficientsWithFrequency_(250);
+  }
+
+  updateCoefficientsWithFrequency_(frequency) {
+    this.b1_ = Math.exp(-2 * Math.PI * frequency / sampleRate);
+    this.a0_ = 1.0 - this.b1_;
+    this.z1_ = 0;
+  }
+
+  process(inputs, outputs, parameters) {
+    let input = inputs[0];
+    let output = outputs[0];
+    let frequency = parameters.frequency;
+    for (let channel = 0; channel < output.length; ++channel) {
+      let inputChannel = input[channel];
+      let outputChannel = output[channel];
+      for (let i = 0; i < outputChannel.length; ++i) {
+        this.updateCoefficientsWithFrequency_(frequency[i]);
+        this.z1_ = inputChannel[i] * this.a0_ + this.z1_ * this.b1_;
+        outputChannel[i] = this.z1_;
+      }
+    }
+
+    return true;
+  }
+}
+
+registerProcessor('one-pole-filter', OnePoleFilter);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/option-test-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/option-test-processor.js
new file mode 100644
index 0000000..27e1da6
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/option-test-processor.js
@@ -0,0 +1,19 @@
+/**
+ * @class OptionTestProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * This processor class demonstrates the option passing feature by echoing the
+ * received |nodeOptions| back to the node.
+ */
+class OptionTestProcessor extends AudioWorkletProcessor {
+  constructor(nodeOptions) {
+    super();
+    this.port.postMessage(nodeOptions);
+  }
+
+  process() {
+    return true;
+  }
+}
+
+registerProcessor('option-test-processor', OptionTestProcessor);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/port-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/port-processor.js
new file mode 100644
index 0000000..5a8baf0
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/port-processor.js
@@ -0,0 +1,29 @@
+/**
+ * @class PortProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * This processor class demonstrates the message port functionality.
+ */
+class PortProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+    this.port.onmessage = this.handleMessage.bind(this);
+    this.port.postMessage({
+      state: 'created',
+      timeStamp: currentTime
+    });
+  }
+
+  handleMessage(event) {
+    this.port.postMessage({
+      message: event.data,
+      timeStamp: currentTime
+    });
+  }
+
+  process() {
+    return true;
+  }
+}
+
+registerProcessor('port-processor', PortProcessor);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/timing-info-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/timing-info-processor.js
new file mode 100644
index 0000000..714e32d
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/timing-info-processor.js
@@ -0,0 +1,25 @@
+/**
+ * @class TimingInfoProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * This processor class is to test the timing information in AWGS.
+ */
+class TimingInfoProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+    this.port.onmessage = this.echoMessage.bind(this);
+  }
+
+  echoMessage(event) {
+    this.port.postMessage({
+      currentTime: currentTime,
+      currentFrame: currentFrame
+    });
+  }
+
+  process() {
+    return true;
+  }
+}
+
+registerProcessor('timing-info-processor', TimingInfoProcessor);
diff --git a/webaudio/the-audio-api/the-audioworklet-interface/processors/zero-output-processor.js b/webaudio/the-audio-api/the-audioworklet-interface/processors/zero-output-processor.js
new file mode 100644
index 0000000..b97ed6e
--- /dev/null
+++ b/webaudio/the-audio-api/the-audioworklet-interface/processors/zero-output-processor.js
@@ -0,0 +1,42 @@
+/**
+ * @class ZeroOutputProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * This processor accumulates the incoming buffer and send the buffered data
+ * to the main thread when it reaches the specified frame length. The processor
+ * only supports the single input.
+ */
+
+const kRenderQuantumFrames = 128;
+
+class ZeroOuttputProcessor extends AudioWorkletProcessor {
+  constructor(options) {
+    super();
+
+    this._framesRequested = options.processorOptions.bufferLength;
+    this._framesCaptured = 0;
+    this._buffer = [];
+    for (let i = 0; i < options.processorOptions.channeCount; ++i) {
+      this._buffer[i] = new Float32Array(this._framesRequested);
+    }
+  }
+
+  process(inputs) {
+    let input = inputs[0];
+    let startIndex = this._framesCaptured;
+    let endIndex = startIndex + kRenderQuantumFrames;
+    for (let i = 0; i < this._buffer.length; ++i) {
+      this._buffer[i].subarray(startIndex, endIndex).set(input[i]);
+    }
+    this._framesCaptured = endIndex;
+
+    if (this._framesCaptured >= this._framesRequested) {
+      this.port.postMessage({ capturedBuffer: this._buffer });
+      return false;
+    } else {
+      return true;
+    }
+  }
+}
+
+registerProcessor('zero-output-processor', ZeroOuttputProcessor);
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-allpass.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-allpass.html
new file mode 100644
index 0000000..86618f9
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-allpass.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-allpass.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad allpass filter'},
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            let filterParameters = [
+              {cutoff: 0, q: 10, gain: 1},
+              {cutoff: 1, q: 10, gain: 1},
+              {cutoff: .5, q: 0, gain: 1},
+              {cutoff: 0.25, q: 10, gain: 1},
+            ];
+            createTestAndRun(context, 'allpass', {
+              should: should,
+              threshold: 3.9337e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-automation.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-automation.html
new file mode 100644
index 0000000..54b2142
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-automation.html
@@ -0,0 +1,406 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Biquad Automation Test
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/audioparam-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Don't need to run these tests at high sampling rate, so just use a low
+      // one to reduce memory usage and complexity.
+      let sampleRate = 16000;
+
+      // How long to render for each test.
+      let renderDuration = 0.25;
+      // Where to end the automations.  Fairly arbitrary, but must end before
+      // the renderDuration.
+      let automationEndTime = renderDuration / 2;
+
+      let audit = Audit.createTaskRunner();
+
+      // The definition of the linear ramp automation function.
+      function linearRamp(t, v0, v1, t0, t1) {
+        return v0 + (v1 - v0) * (t - t0) / (t1 - t0);
+      }
+
+      // Generate the filter coefficients for the specified filter using the
+      // given parameters for the given duration.  |filterTypeFunction| is a
+      // function that returns the filter coefficients for one set of
+      // parameters.  |parameters| is a property bag that contains the start and
+      // end values (as an array) for each of the biquad attributes.  The
+      // properties are |freq|, |Q|, |gain|, and |detune|.  |duration| is the
+      // number of seconds for which the coefficients are generated.
+      //
+      // A property bag with properties |b0|, |b1|, |b2|, |a1|, |a2|.  Each
+      // propery is an array consisting of the coefficients for the time-varying
+      // biquad filter.
+      function generateFilterCoefficients(
+          filterTypeFunction, parameters, duration) {
+        let renderEndFrame = Math.ceil(renderDuration * sampleRate);
+        let endFrame = Math.ceil(duration * sampleRate);
+        let nCoef = renderEndFrame;
+        let b0 = new Float64Array(nCoef);
+        let b1 = new Float64Array(nCoef);
+        let b2 = new Float64Array(nCoef);
+        let a1 = new Float64Array(nCoef);
+        let a2 = new Float64Array(nCoef);
+
+        let k = 0;
+        // If the property is not given, use the defaults.
+        let freqs = parameters.freq || [350, 350];
+        let qs = parameters.Q || [1, 1];
+        let gains = parameters.gain || [0, 0];
+        let detunes = parameters.detune || [0, 0];
+
+        for (let frame = 0; frame <= endFrame; ++frame) {
+          // Apply linear ramp at frame |frame|.
+          let f =
+              linearRamp(frame / sampleRate, freqs[0], freqs[1], 0, duration);
+          let q = linearRamp(frame / sampleRate, qs[0], qs[1], 0, duration);
+          let g =
+              linearRamp(frame / sampleRate, gains[0], gains[1], 0, duration);
+          let d = linearRamp(
+              frame / sampleRate, detunes[0], detunes[1], 0, duration);
+
+          // Compute actual frequency parameter
+          f = f * Math.pow(2, d / 1200);
+
+          // Compute filter coefficients
+          let coef = filterTypeFunction(f / (sampleRate / 2), q, g);
+          b0[k] = coef.b0;
+          b1[k] = coef.b1;
+          b2[k] = coef.b2;
+          a1[k] = coef.a1;
+          a2[k] = coef.a2;
+          ++k;
+        }
+
+        // Fill the rest of the arrays with the constant value to the end of
+        // the rendering duration.
+        b0.fill(b0[endFrame], endFrame + 1);
+        b1.fill(b1[endFrame], endFrame + 1);
+        b2.fill(b2[endFrame], endFrame + 1);
+        a1.fill(a1[endFrame], endFrame + 1);
+        a2.fill(a2[endFrame], endFrame + 1);
+
+        return {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2};
+      }
+
+      // Apply the given time-varying biquad filter to the given signal,
+      // |signal|.  |coef| should be the time-varying coefficients of the
+      // filter, as returned by |generateFilterCoefficients|.
+      function timeVaryingFilter(signal, coef) {
+        let length = signal.length;
+        // Use double precision for the internal computations.
+        let y = new Float64Array(length);
+
+        // Prime the pump. (Assumes the signal has length >= 2!)
+        y[0] = coef.b0[0] * signal[0];
+        y[1] =
+            coef.b0[1] * signal[1] + coef.b1[1] * signal[0] - coef.a1[1] * y[0];
+
+        for (let n = 2; n < length; ++n) {
+          y[n] = coef.b0[n] * signal[n] + coef.b1[n] * signal[n - 1] +
+              coef.b2[n] * signal[n - 2];
+          y[n] -= coef.a1[n] * y[n - 1] + coef.a2[n] * y[n - 2];
+        }
+
+        // But convert the result to single precision for comparison.
+        return y.map(Math.fround);
+      }
+
+      // Configure the audio graph using |context|.  Returns the biquad filter
+      // node and the AudioBuffer used for the source.
+      function configureGraph(context, toneFrequency) {
+        // The source is just a simple sine wave.
+        let src = context.createBufferSource();
+        let b =
+            context.createBuffer(1, renderDuration * sampleRate, sampleRate);
+        let data = b.getChannelData(0);
+        let omega = 2 * Math.PI * toneFrequency / sampleRate;
+        for (let k = 0; k < data.length; ++k) {
+          data[k] = Math.sin(omega * k);
+        }
+        src.buffer = b;
+        let f = context.createBiquadFilter();
+        src.connect(f);
+        f.connect(context.destination);
+
+        src.start();
+
+        return {filter: f, source: b};
+      }
+
+      function createFilterVerifier(
+          should, filterCreator, threshold, parameters, input, message) {
+        return function(resultBuffer) {
+          let actual = resultBuffer.getChannelData(0);
+          let coefs = generateFilterCoefficients(
+              filterCreator, parameters, automationEndTime);
+
+          reference = timeVaryingFilter(input, coefs);
+
+          should(actual, message).beCloseToArray(reference, {
+            absoluteThreshold: threshold
+          });
+        };
+      }
+
+      // Automate just the frequency parameter.  A bandpass filter is used where
+      // the center frequency is swept across the source (which is a simple
+      // tone).
+      audit.define('automate-freq', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+        // Center frequency of bandpass filter and also the frequency of the
+        // test tone.
+        let centerFreq = 10 * 440;
+
+        // Sweep the frequency +/- 5*440 Hz from the center.  This should cause
+        // the output to be low at the beginning and end of the test where the
+        // tone is outside the pass band of the filter, but high in the middle
+        // of the automation time where the tone is near the center of the pass
+        // band.  Make sure the frequency sweep stays inside the Nyquist
+        // frequency.
+        let parameters = {freq: [centerFreq - 5 * 440, centerFreq + 5 * 440]};
+        let graph = configureGraph(context, centerFreq);
+        let f = graph.filter;
+        let b = graph.source;
+
+        f.type = 'bandpass';
+        f.frequency.setValueAtTime(parameters.freq[0], 0);
+        f.frequency.linearRampToValueAtTime(
+            parameters.freq[1], automationEndTime);
+
+        context.startRendering()
+            .then(createFilterVerifier(
+                should, createBandpassFilter, 4.6455e-6, parameters,
+                b.getChannelData(0),
+                'Output of bandpass filter with frequency automation'))
+            .then(() => task.done());
+      });
+
+      // Automate just the Q parameter.  A bandpass filter is used where the Q
+      // of the filter is swept.
+      audit.define('automate-q', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+        // The frequency of the test tone.
+        let centerFreq = 440;
+
+        // Sweep the Q paramter between 1 and 200.  This will cause the output
+        // of the filter to pass most of the tone at the beginning to passing
+        // less of the tone at the end.  This is because we set center frequency
+        // of the bandpass filter to be slightly off from the actual tone.
+        let parameters = {
+          Q: [1, 200],
+          // Center frequency of the bandpass filter is just 25 Hz above the
+          // tone frequency.
+          freq: [centerFreq + 25, centerFreq + 25]
+        };
+        let graph = configureGraph(context, centerFreq);
+        let f = graph.filter;
+        let b = graph.source;
+
+        f.type = 'bandpass';
+        f.frequency.value = parameters.freq[0];
+        f.Q.setValueAtTime(parameters.Q[0], 0);
+        f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime);
+
+        context.startRendering()
+            .then(createFilterVerifier(
+                should, createBandpassFilter, 9.8348e-7, parameters,
+                b.getChannelData(0),
+                'Output of bandpass filter with Q automation'))
+            .then(() => task.done());
+      });
+
+      // Automate just the gain of the lowshelf filter.  A test tone will be in
+      // the lowshelf part of the filter.  The output will vary as the gain of
+      // the lowshelf is changed.
+      audit.define('automate-gain', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+        // Frequency of the test tone.
+        let centerFreq = 440;
+
+        // Set the cutoff frequency of the lowshelf to be significantly higher
+        // than the test tone. Sweep the gain from 20 dB to -20 dB.  (We go from
+        // 20 to -20 to easily verify that the filter didn't go unstable.)
+        let parameters = {freq: [3500, 3500], gain: [20, -20]};
+        let graph = configureGraph(context, centerFreq);
+        let f = graph.filter;
+        let b = graph.source;
+
+        f.type = 'lowshelf';
+        f.frequency.value = parameters.freq[0];
+        f.gain.setValueAtTime(parameters.gain[0], 0);
+        f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime);
+
+        context.startRendering()
+            .then(createFilterVerifier(
+                should, createLowShelfFilter, 2.7657e-5, parameters,
+                b.getChannelData(0),
+                'Output of lowshelf filter with gain automation'))
+            .then(() => task.done());
+      });
+
+      // Automate just the detune parameter.  Basically the same test as for the
+      // frequncy parameter but we just use the detune parameter to modulate the
+      // frequency parameter.
+      audit.define('automate-detune', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+        let centerFreq = 10 * 440;
+        let parameters = {
+          freq: [centerFreq, centerFreq],
+          detune: [-10 * 1200, 10 * 1200]
+        };
+        let graph = configureGraph(context, centerFreq);
+        let f = graph.filter;
+        let b = graph.source;
+
+        f.type = 'bandpass';
+        f.frequency.value = parameters.freq[0];
+        f.detune.setValueAtTime(parameters.detune[0], 0);
+        f.detune.linearRampToValueAtTime(
+            parameters.detune[1], automationEndTime);
+
+        context.startRendering()
+            .then(createFilterVerifier(
+                should, createBandpassFilter, 3.1471e-5, parameters,
+                b.getChannelData(0),
+                'Output of bandpass filter with detune automation'))
+            .then(() => task.done());
+      });
+
+      // Automate all of the filter parameters at once.  This is a basic check
+      // that everything is working.  A peaking filter is used because it uses
+      // all of the parameters.
+      audit.define('automate-all', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+        let graph = configureGraph(context, 10 * 440);
+        let f = graph.filter;
+        let b = graph.source;
+
+        // Sweep all of the filter parameters.  These are pretty much arbitrary.
+        let parameters = {
+          freq: [8000, 100],
+          Q: [f.Q.value, .0001],
+          gain: [f.gain.value, 20],
+          detune: [2400, -2400]
+        };
+
+        f.type = 'peaking';
+        // Set starting points for all parameters of the filter.  Start at 10
+        // kHz for the center frequency, and the defaults for Q and gain.
+        f.frequency.setValueAtTime(parameters.freq[0], 0);
+        f.Q.setValueAtTime(parameters.Q[0], 0);
+        f.gain.setValueAtTime(parameters.gain[0], 0);
+        f.detune.setValueAtTime(parameters.detune[0], 0);
+
+        // Linear ramp each parameter
+        f.frequency.linearRampToValueAtTime(
+            parameters.freq[1], automationEndTime);
+        f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime);
+        f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime);
+        f.detune.linearRampToValueAtTime(
+            parameters.detune[1], automationEndTime);
+
+        context.startRendering()
+            .then(createFilterVerifier(
+                should, createPeakingFilter, 6.2907e-4, parameters,
+                b.getChannelData(0),
+                'Output of peaking filter with automation of all parameters'))
+            .then(() => task.done());
+      });
+
+      // Test that modulation of the frequency parameter of the filter works.  A
+      // sinusoid of 440 Hz is the test signal that is applied to a bandpass
+      // biquad filter.  The frequency parameter of the filter is modulated by a
+      // sinusoid at 103 Hz, and the frequency modulation varies from 116 to 412
+      // Hz.  (This test was taken from the description in
+      // https://github.com/WebAudio/web-audio-api/issues/509#issuecomment-94731355)
+      audit.define('modulation', (task, should) => {
+        let context =
+            new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+        // Create a graph with the sinusoidal source at 440 Hz as the input to a
+        // biquad filter.
+        let graph = configureGraph(context, 440);
+        let f = graph.filter;
+        let b = graph.source;
+
+        f.type = 'bandpass';
+        f.Q.value = 5;
+        f.frequency.value = 264;
+
+        // Create the modulation source, a sinusoid with frequency 103 Hz and
+        // amplitude 148.  (The amplitude of 148 is added to the filter's
+        // frequency value of 264 to produce a sinusoidal modulation of the
+        // frequency parameter from 116 to 412 Hz.)
+        let mod = context.createBufferSource();
+        let mbuffer =
+            context.createBuffer(1, renderDuration * sampleRate, sampleRate);
+        let d = mbuffer.getChannelData(0);
+        let omega = 2 * Math.PI * 103 / sampleRate;
+        for (let k = 0; k < d.length; ++k) {
+          d[k] = 148 * Math.sin(omega * k);
+        }
+        mod.buffer = mbuffer;
+
+        mod.connect(f.frequency);
+
+        mod.start();
+        context.startRendering()
+            .then(function(resultBuffer) {
+              let actual = resultBuffer.getChannelData(0);
+              // Compute the filter coefficients using the mod sine wave
+
+              let endFrame = Math.ceil(renderDuration * sampleRate);
+              let nCoef = endFrame;
+              let b0 = new Float64Array(nCoef);
+              let b1 = new Float64Array(nCoef);
+              let b2 = new Float64Array(nCoef);
+              let a1 = new Float64Array(nCoef);
+              let a2 = new Float64Array(nCoef);
+
+              // Generate the filter coefficients when the frequency varies from
+              // 116 to 248 Hz using the 103 Hz sinusoid.
+              for (let k = 0; k < nCoef; ++k) {
+                let freq = f.frequency.value + d[k];
+                let c = createBandpassFilter(
+                    freq / (sampleRate / 2), f.Q.value, f.gain.value);
+                b0[k] = c.b0;
+                b1[k] = c.b1;
+                b2[k] = c.b2;
+                a1[k] = c.a1;
+                a2[k] = c.a2;
+              }
+              reference = timeVaryingFilter(
+                  b.getChannelData(0),
+                  {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2});
+
+              should(
+                  actual,
+                  'Output of bandpass filter with sinusoidal modulation of bandpass center frequency')
+                  .beCloseToArray(reference, {absoluteThreshold: 3.9787e-5});
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-bandpass.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-bandpass.html
new file mode 100644
index 0000000..166aa9b
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-bandpass.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-bandpass.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad bandpass filter.'},
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            // The filters we want to test.
+            let filterParameters = [
+              {cutoff: 0, q: 0, gain: 1},
+              {cutoff: 1, q: 0, gain: 1},
+              {cutoff: 0.5, q: 0, gain: 1},
+              {cutoff: 0.25, q: 1, gain: 1},
+            ];
+
+            createTestAndRun(context, 'bandpass', {
+              should: should,
+              threshold: 2.2501e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-basic.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-basic.html
new file mode 100644
index 0000000..c4f7c07
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-basic.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Basic BiquadFilterNode Properties
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+      let testFrames = 100;
+
+      // Global context that can be used by the individual tasks. It must be
+      // defined by the initialize task.
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        should(() => {
+          context = new OfflineAudioContext(1, testFrames, sampleRate);
+        }, 'Initialize context for testing').notThrow();
+        task.done();
+      });
+
+      audit.define('existence', (task, should) => {
+        should(context.createBiquadFilter, 'context.createBiquadFilter')
+            .exist();
+        task.done();
+      });
+
+      audit.define('parameters', (task, should) => {
+        // Create a really simple IIR filter. Doesn't much matter what.
+        let coef = Float32Array.from([1]);
+
+        let f = context.createBiquadFilter(coef, coef);
+
+        should(f.numberOfInputs, 'numberOfInputs').beEqualTo(1);
+        should(f.numberOfOutputs, 'numberOfOutputs').beEqualTo(1);
+        should(f.channelCountMode, 'channelCountMode').beEqualTo('max');
+        should(f.channelInterpretation, 'channelInterpretation')
+            .beEqualTo('speakers');
+
+        task.done();
+      });
+
+      audit.define('exceptions-createBiquadFilter', (task, should) => {
+        should(function() {
+          // Two args are required.
+          context.createBiquadFilter();
+        }, 'createBiquadFilter()').notThrow();
+
+        task.done();
+      });
+
+      audit.define('exceptions-getFrequencyData', (task, should) => {
+        // Create a really simple IIR filter. Doesn't much matter what.
+        let coef = Float32Array.from([1]);
+
+        let f = context.createBiquadFilter(coef, coef);
+
+        should(
+            function() {
+              // frequencyHz can't be null.
+              f.getFrequencyResponse(
+                  null, new Float32Array(1), new Float32Array(1));
+            },
+            'getFrequencyResponse(' +
+                'null, ' +
+                'new Float32Array(1), ' +
+                'new Float32Array(1))')
+            .throw('TypeError');
+
+        should(
+            function() {
+              // magResponse can't be null.
+              f.getFrequencyResponse(
+                  new Float32Array(1), null, new Float32Array(1));
+            },
+            'getFrequencyResponse(' +
+                'new Float32Array(1), ' +
+                'null, ' +
+                'new Float32Array(1))')
+            .throw('TypeError');
+
+        should(
+            function() {
+              // phaseResponse can't be null.
+              f.getFrequencyResponse(
+                  new Float32Array(1), new Float32Array(1), null);
+            },
+            'getFrequencyResponse(' +
+                'new Float32Array(1), ' +
+                'new Float32Array(1), ' +
+                'null)')
+            .throw('TypeError');
+
+        should(
+            function() {
+              // magResponse array must the same length as frequencyHz
+              f.getFrequencyResponse(
+                  new Float32Array(10), new Float32Array(1),
+                  new Float32Array(20));
+            },
+            'getFrequencyResponse(' +
+                'new Float32Array(10), ' +
+                'new Float32Array(1), ' +
+                'new Float32Array(20))')
+            .throw('InvalidAccessError');
+
+        should(
+            function() {
+              // phaseResponse array must be the same length as frequencyHz
+              f.getFrequencyResponse(
+                  new Float32Array(10), new Float32Array(20),
+                  new Float32Array(1));
+            },
+            'getFrequencyResponse(' +
+                'new Float32Array(10), ' +
+                'new Float32Array(20), ' +
+                'new Float32Array(1))')
+            .throw('InvalidAccessError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-getFrequencyResponse.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-getFrequencyResponse.html
new file mode 100644
index 0000000..83f057f
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-getFrequencyResponse.html
@@ -0,0 +1,335 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test BiquadFilter getFrequencyResponse() functionality
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Test the frequency response of a biquad filter.  We compute the
+      // frequency response for a simple peaking biquad filter and compare it
+      // with the expected frequency response.  The actual filter used doesn't
+      // matter since we're testing getFrequencyResponse and not the actual
+      // filter output. The filters are extensively tested in other biquad
+      // tests.
+
+      // The magnitude response of the biquad filter.
+      let magResponse;
+
+      // The phase response of the biquad filter.
+      let phaseResponse;
+
+      // Number of frequency samples to take.
+      let numberOfFrequencies = 1000;
+
+      // The filter parameters.
+      let filterCutoff = 1000;  // Hz.
+      let filterQ = 1;
+      let filterGain = 5;  // Decibels.
+
+      // The maximum allowed error in the magnitude response.
+      let maxAllowedMagError = 9.775e-7;
+
+      // The maximum allowed error in the phase response.
+      let maxAllowedPhaseError = 5.4187e-8;
+
+      // The magnitudes and phases of the reference frequency response.
+      let expectedMagnitudes;
+      let expectedPhases;
+
+      // Convert frequency in Hz to a normalized frequency between 0 to 1 with 1
+      // corresponding to the Nyquist frequency.
+      function normalizedFrequency(freqHz, sampleRate) {
+        let nyquist = sampleRate / 2;
+        return freqHz / nyquist;
+      }
+
+      // Get the filter response at a (normalized) frequency |f| for the filter
+      // with coefficients |coef|.
+      function getResponseAt(coef, f) {
+        let b0 = coef.b0;
+        let b1 = coef.b1;
+        let b2 = coef.b2;
+        let a1 = coef.a1;
+        let a2 = coef.a2;
+
+        // H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
+        //
+        // Compute H(exp(i * pi * f)).  No native complex numbers in javascript,
+        // so break H(exp(i * pi * // f)) in to the real and imaginary parts of
+        // the numerator and denominator.  Let omega = pi * f. Then the
+        // numerator is
+        //
+        // b0 + b1 * cos(omega) + b2 * cos(2 * omega) - i * (b1 * sin(omega) +
+        // b2 * sin(2 * omega))
+        //
+        // and the denominator is
+        //
+        // 1 + a1 * cos(omega) + a2 * cos(2 * omega) - i * (a1 * sin(omega) + a2
+        // * sin(2 * omega))
+        //
+        // Compute the magnitude and phase from the real and imaginary parts.
+
+        let omega = Math.PI * f;
+        let numeratorReal =
+            b0 + b1 * Math.cos(omega) + b2 * Math.cos(2 * omega);
+        let numeratorImag = -(b1 * Math.sin(omega) + b2 * Math.sin(2 * omega));
+        let denominatorReal =
+            1 + a1 * Math.cos(omega) + a2 * Math.cos(2 * omega);
+        let denominatorImag =
+            -(a1 * Math.sin(omega) + a2 * Math.sin(2 * omega));
+
+        let magnitude = Math.sqrt(
+            (numeratorReal * numeratorReal + numeratorImag * numeratorImag) /
+            (denominatorReal * denominatorReal +
+             denominatorImag * denominatorImag));
+        let phase = Math.atan2(numeratorImag, numeratorReal) -
+            Math.atan2(denominatorImag, denominatorReal);
+
+        if (phase >= Math.PI) {
+          phase -= 2 * Math.PI;
+        } else if (phase <= -Math.PI) {
+          phase += 2 * Math.PI;
+        }
+
+        return {magnitude: magnitude, phase: phase};
+      }
+
+      // Compute the reference frequency response for the biquad filter |filter|
+      // at the frequency samples given by |frequencies|.
+      function frequencyResponseReference(filter, frequencies) {
+        let sampleRate = filter.context.sampleRate;
+        let normalizedFreq =
+            normalizedFrequency(filter.frequency.value, sampleRate);
+        let filterCoefficients = createFilter(
+            filter.type, normalizedFreq, filter.Q.value, filter.gain.value);
+
+        let magnitudes = [];
+        let phases = [];
+
+        for (let k = 0; k < frequencies.length; ++k) {
+          let response = getResponseAt(
+              filterCoefficients,
+              normalizedFrequency(frequencies[k], sampleRate));
+          magnitudes.push(response.magnitude);
+          phases.push(response.phase);
+        }
+
+        return {magnitudes: magnitudes, phases: phases};
+      }
+
+      // Compute a set of linearly spaced frequencies.
+      function createFrequencies(nFrequencies, sampleRate) {
+        let frequencies = new Float32Array(nFrequencies);
+        let nyquist = sampleRate / 2;
+        let freqDelta = nyquist / nFrequencies;
+
+        for (let k = 0; k < nFrequencies; ++k) {
+          frequencies[k] = k * freqDelta;
+        }
+
+        return frequencies;
+      }
+
+      function linearToDecibels(x) {
+        if (x) {
+          return 20 * Math.log(x) / Math.LN10;
+        } else {
+          return -1000;
+        }
+      }
+
+      // Look through the array and find any NaN or infinity. Returns the index
+      // of the first occurence or -1 if none.
+      function findBadNumber(signal) {
+        for (let k = 0; k < signal.length; ++k) {
+          if (!isValidNumber(signal[k])) {
+            return k;
+          }
+        }
+        return -1;
+      }
+
+      // Compute absolute value of the difference between phase angles, taking
+      // into account the wrapping of phases.
+      function absolutePhaseDifference(x, y) {
+        let diff = Math.abs(x - y);
+
+        if (diff > Math.PI) {
+          diff = 2 * Math.PI - diff;
+        }
+        return diff;
+      }
+
+      // Compare the frequency response with our expected response.
+      function compareResponses(
+          should, filter, frequencies, magResponse, phaseResponse) {
+        let expectedResponse = frequencyResponseReference(filter, frequencies);
+
+        expectedMagnitudes = expectedResponse.magnitudes;
+        expectedPhases = expectedResponse.phases;
+
+        let n = magResponse.length;
+        let badResponse = false;
+
+        let maxMagError = -1;
+        let maxMagErrorIndex = -1;
+
+        let k;
+        let hasBadNumber;
+
+        hasBadNumber = findBadNumber(magResponse);
+        badResponse = !should(
+                           hasBadNumber >= 0 ? 1 : 0,
+                           'Number of non-finite values in magnitude response')
+                           .beEqualTo(0);
+
+        hasBadNumber = findBadNumber(phaseResponse);
+        badResponse = !should(
+                           hasBadNumber >= 0 ? 1 : 0,
+                           'Number of non-finte values in phase response')
+                           .beEqualTo(0);
+
+        // These aren't testing the implementation itself.  Instead, these are
+        // sanity checks on the reference.  Failure here does not imply an error
+        // in the implementation.
+        hasBadNumber = findBadNumber(expectedMagnitudes);
+        badResponse =
+            !should(
+                 hasBadNumber >= 0 ? 1 : 0,
+                 'Number of non-finite values in the expected magnitude response')
+                 .beEqualTo(0);
+
+        hasBadNumber = findBadNumber(expectedPhases);
+        badResponse =
+            !should(
+                 hasBadNumber >= 0 ? 1 : 0,
+                 'Number of non-finite values in expected phase response')
+                 .beEqualTo(0);
+
+        // If we found a NaN or infinity, the following tests aren't very
+        // helpful, especially for NaN. We run them anyway, after printing a
+        // warning message.
+        should(
+            !badResponse,
+            'Actual and expected results contained only finite values')
+            .beTrue();
+
+        for (k = 0; k < n; ++k) {
+          let error = Math.abs(
+              linearToDecibels(magResponse[k]) -
+              linearToDecibels(expectedMagnitudes[k]));
+          if (error > maxMagError) {
+            maxMagError = error;
+            maxMagErrorIndex = k;
+          }
+        }
+
+        should(
+            linearToDecibels(maxMagError),
+            'Max error (' + linearToDecibels(maxMagError) +
+                ' dB) of magnitude response at frequency ' +
+                frequencies[maxMagErrorIndex] + ' Hz')
+            .beLessThanOrEqualTo(linearToDecibels(maxAllowedMagError));
+        let maxPhaseError = -1;
+        let maxPhaseErrorIndex = -1;
+
+        for (k = 0; k < n; ++k) {
+          let error =
+              absolutePhaseDifference(phaseResponse[k], expectedPhases[k]);
+          if (error > maxPhaseError) {
+            maxPhaseError = error;
+            maxPhaseErrorIndex = k;
+          }
+        }
+
+        should(
+            radToDegree(maxPhaseError),
+            'Max error (' + radToDegree(maxPhaseError) +
+                ' deg) in phase response at frequency ' +
+                frequencies[maxPhaseErrorIndex] + ' Hz')
+            .beLessThanOrEqualTo(radToDegree(maxAllowedPhaseError));
+      }
+
+      function radToDegree(rad) {
+        // Radians to degrees
+        return rad * 180 / Math.PI;
+      }
+
+      audit.define(
+          {label: 'test', description: 'Biquad frequency response'},
+          function(task, should) {
+            context = new AudioContext();
+
+            filter = context.createBiquadFilter();
+
+            // Arbitrarily test a peaking filter, but any kind of filter can be
+            // tested.
+            filter.type = 'peaking';
+            filter.frequency.value = filterCutoff;
+            filter.Q.value = filterQ;
+            filter.gain.value = filterGain;
+
+            let frequencies =
+                createFrequencies(numberOfFrequencies, context.sampleRate);
+            magResponse = new Float32Array(numberOfFrequencies);
+            phaseResponse = new Float32Array(numberOfFrequencies);
+
+            filter.getFrequencyResponse(
+                frequencies, magResponse, phaseResponse);
+            compareResponses(
+                should, filter, frequencies, magResponse, phaseResponse);
+
+            task.done();
+          });
+
+      audit.define(
+          {
+            label: 'getFrequencyResponse',
+            description: 'Test out-of-bounds frequency values'
+          },
+          (task, should) => {
+            let context = new OfflineAudioContext(1, 1, sampleRate);
+            let filter = new BiquadFilterNode(context);
+
+            // Frequencies to test.  These are all outside the valid range of
+            // frequencies of 0 to Nyquist.
+            let freq = new Float32Array(2);
+            freq[0] = -1;
+            freq[1] = context.sampleRate / 2 + 1;
+
+            let mag = new Float32Array(freq.length);
+            let phase = new Float32Array(freq.length);
+
+            filter.getFrequencyResponse(freq, mag, phase);
+
+            // Verify that the returned magnitude and phase entries are alL NaN
+            // since the frequencies are outside the valid range
+            for (let k = 0; k < mag.length; ++k) {
+              should(mag[k],
+                  'Magnitude response at frequency ' + freq[k])
+                  .beNaN();
+            }
+
+            for (let k = 0; k < phase.length; ++k) {
+              should(phase[k],
+                  'Phase response at frequency ' + freq[k])
+                  .beNaN();
+            }
+
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highpass.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highpass.html
new file mode 100644
index 0000000..45c335b
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highpass.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-highpass.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad highpass filter'},
+          function(task, should) {
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            // The filters we want to test.
+            let filterParameters = [
+              {cutoff: 0, q: 1, gain: 1},
+              {cutoff: 1, q: 1, gain: 1},
+              {cutoff: 0.25, q: 1, gain: 1},
+            ];
+
+            createTestAndRun(context, 'highpass', {
+              should: should,
+              threshold: 1.5487e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highshelf.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highshelf.html
new file mode 100644
index 0000000..345195f
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highshelf.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-highshelf.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad highshelf filter'},
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            // The filters we want to test.
+            let filterParameters = [
+              {cutoff: 0, q: 10, gain: 10},
+              {cutoff: 1, q: 10, gain: 10},
+              {cutoff: 0.25, q: 10, gain: 10},
+            ];
+
+            createTestAndRun(context, 'highshelf', {
+              should: should,
+              threshold: 6.2577e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html
new file mode 100644
index 0000000..d20786e
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-lowpass.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad lowpass filter'},
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            // The filters we want to test.
+            let filterParameters = [
+              {cutoff: 0, q: 1, gain: 1},
+              {cutoff: 1, q: 1, gain: 1},
+              {cutoff: 0.25, q: 1, gain: 1},
+              {cutoff: 0.25, q: 1, gain: 1, detune: 100},
+              {cutoff: 0.01, q: 1, gain: 1, detune: -200},
+            ];
+
+            createTestAndRun(context, 'lowpass', {
+              should: should,
+              threshold: 9.7869e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowshelf.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowshelf.html
new file mode 100644
index 0000000..ab76cef
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowshelf.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-lowshelf.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad lowshelf filter'},
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            // The filters we want to test.
+            let filterParameters = [
+              {cutoff: 0, q: 10, gain: 10},
+              {cutoff: 1, q: 10, gain: 10},
+              {cutoff: 0.25, q: 10, gain: 10},
+            ];
+
+            createTestAndRun(context, 'lowshelf', {
+              should: should,
+              threshold: 3.8349e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-notch.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-notch.html
new file mode 100644
index 0000000..98e6e6e
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-notch.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-notch.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad notch filter'},
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            let filterParameters = [
+              {cutoff: 0, q: 10, gain: 1},
+              {cutoff: 1, q: 10, gain: 1},
+              {cutoff: .5, q: 0, gain: 1},
+              {cutoff: 0.25, q: 10, gain: 1},
+            ];
+
+            createTestAndRun(context, 'notch', {
+              should: should,
+              threshold: 1.9669e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-peaking.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-peaking.html
new file mode 100644
index 0000000..90b7c15
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-peaking.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-peaking.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+    <script src="/webaudio/resources/biquad-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Biquad peaking filter'},
+          function(task, should) {
+
+            window.jsTestIsAsync = true;
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            // The filters we want to test.
+            let filterParameters = [
+              {cutoff: 0, q: 10, gain: 10},
+              {cutoff: 1, q: 10, gain: 10},
+              {cutoff: .5, q: 0, gain: 10},
+              {cutoff: 0.25, q: 10, gain: 10},
+            ];
+
+            createTestAndRun(context, 'peaking', {
+              should: should,
+              threshold: 5.8234e-8,
+              filterParameters: filterParameters
+            }).then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-tail.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-tail.html
new file mode 100644
index 0000000..3141bf7
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-tail.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Biquad Tail Output
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // A high sample rate shows the issue more clearly.
+      let sampleRate = 192000;
+      // Some short duration because we don't need to run the test for very
+      // long.
+      let testDurationSec = 0.5;
+      let testDurationFrames = testDurationSec * sampleRate;
+
+      // Amplitude experimentally determined to give a biquad output close to 1.
+      // (No attempt was made to produce exactly 1; it's not needed.)
+      let sourceAmplitude = 100;
+
+      // The output of the biquad filter should not change by more than this
+      // much between output samples.  Threshold was determined experimentally.
+      let glitchThreshold = 0.012968;
+
+      // Test that a Biquad filter doesn't have it's output terminated because
+      // the input has gone away.  Generally, when a source node is finished, it
+      // disconnects itself from any downstream nodes.  This is the correct
+      // behavior.  Nodes that have no inputs (disconnected) are generally
+      // assumed to output zeroes.  This is also desired behavior.  However,
+      // biquad filters have memory so they should not suddenly output zeroes
+      // when the input is disconnected.  This test checks to see if the output
+      // doesn't suddenly change to zero.
+      audit.define(
+          {label: 'test', description: 'Biquad Tail Output'},
+          function(task, should) {
+            let context =
+                new OfflineAudioContext(1, testDurationFrames, sampleRate);
+
+            // Create an impulse source.
+            let buffer = context.createBuffer(1, 1, context.sampleRate);
+            buffer.getChannelData(0)[0] = sourceAmplitude;
+            let source = context.createBufferSource();
+            source.buffer = buffer;
+
+            // Create the biquad filter. It doesn't really matter what kind, so
+            // the default filter type and parameters is fine.  Connect the
+            // source to it.
+            let biquad = context.createBiquadFilter();
+            source.connect(biquad);
+            biquad.connect(context.destination);
+
+            source.start();
+
+            context.startRendering().then(function(result) {
+              // There should be no large discontinuities in the output
+              should(result.getChannelData(0), 'Biquad output')
+                  .notGlitch(glitchThreshold);
+              task.done();
+            })
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/biquadfilternode-basic.html b/webaudio/the-audio-api/the-biquadfilternode-interface/biquadfilternode-basic.html
new file mode 100644
index 0000000..7e71d07
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/biquadfilternode-basic.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquadfilternode-basic.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Basic tests for BiquadFilterNode'},
+          function(task, should) {
+
+            let context = new AudioContext();
+            let filter = context.createBiquadFilter();
+
+            should(filter.numberOfInputs, 'Number of inputs').beEqualTo(1);
+
+            should(filter.numberOfOutputs, 'Number of outputs').beEqualTo(1);
+
+            should(filter.type, 'Default filter type').beEqualTo('lowpass');
+
+            should(filter.frequency.value, 'Default frequency value')
+                .beEqualTo(350);
+
+            should(filter.Q.value, 'Default Q value').beEqualTo(1);
+
+            should(filter.gain.value, 'Default gain value').beEqualTo(0);
+
+            // Check that all legal filter types can be set.
+            let filterTypeArray = [
+              {type: 'lowpass'}, {type: 'highpass'}, {type: 'bandpass'},
+              {type: 'lowshelf'}, {type: 'highshelf'}, {type: 'peaking'},
+              {type: 'notch'}, {type: 'allpass'}
+            ];
+
+            for (let i = 0; i < filterTypeArray.length; ++i) {
+              should(
+                  () => filter.type = filterTypeArray[i].type,
+                  'Setting filter.type to ' + filterTypeArray[i].type)
+                  .notThrow();
+              should(filter.type, 'Filter type is')
+                  .beEqualTo(filterTypeArray[i].type);
+            }
+
+
+            // Check that numerical values are no longer supported
+            filter.type = 99;
+            should(filter.type, 'Setting filter.type to (invalid) 99')
+                .notBeEqualTo(99);
+
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/ctor-biquadfilter.html b/webaudio/the-audio-api/the-biquadfilternode-interface/ctor-biquadfilter.html
new file mode 100644
index 0000000..e63479f
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/ctor-biquadfilter.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: BiquadFilter
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'BiquadFilterNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'BiquadFilterNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'type', value: 'lowpass'}, {name: 'Q', value: 1},
+          {name: 'detune', value: 0}, {name: 'frequency', value: 350},
+          {name: 'gain', value: 0.0}
+        ]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'BiquadFilterNode');
+        task.done();
+      });
+
+      audit.define('construct with options', (task, should) => {
+        let node;
+        let options = {
+          type: 'highpass',
+          frequency: 512,
+          detune: 1,
+          Q: 5,
+          gain: 3,
+        };
+
+        should(
+            () => {
+              node = new BiquadFilterNode(context, options);
+            },
+            'node = new BiquadFilterNode(..., ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        // Test that attributes are set according to the option values.
+        should(node.type, 'node.type').beEqualTo(options.type);
+        should(node.frequency.value, 'node.frequency.value')
+            .beEqualTo(options.frequency);
+        should(node.detune.value, 'node.detuen.value')
+            .beEqualTo(options.detune);
+        should(node.Q.value, 'node.Q.value').beEqualTo(options.Q);
+        should(node.gain.value, 'node.gain.value').beEqualTo(options.gain);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html b/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html
new file mode 100644
index 0000000..d54bc0b
--- /dev/null
+++ b/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html
@@ -0,0 +1,288 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      biquad-bandpass.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/biquad-filters.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // In the tests below, the initial values are not important, except that
+      // we wanted them to be all different so that the output contains
+      // different values for the first few samples.  Otherwise, the actual
+      // values don't really matter.  A peaking filter is used because the
+      // frequency, Q, gain, and detune parameters are used by this filter.
+      //
+      // Also, for the changeList option, the times and new values aren't really
+      // important.  They just need to change so that we can verify that the
+      // outputs from the .value setter still matches the output from the
+      // corresponding setValueAtTime.
+      audit.define(
+          {label: 'Test 0', description: 'No dezippering for frequency'},
+          (task, should) => {
+            doTest(should, {
+              paramName: 'frequency',
+              initializer: {type: 'peaking', Q: 1, gain: 5},
+              changeList:
+                  [{quantum: 2, newValue: 800}, {quantum: 7, newValue: 200}],
+              threshold: 3.0399e-6
+            }).then(() => task.done());
+          });
+
+      audit.define(
+          {label: 'Test 1', description: 'No dezippering for detune'},
+          (task, should) => {
+            doTest(should, {
+              paramName: 'detune',
+              initializer:
+                  {type: 'peaking', frequency: 400, Q: 3, detune: 33, gain: 10},
+              changeList:
+                  [{quantum: 2, newValue: 1000}, {quantum: 5, newValue: -400}],
+              threshold: 4.0532e-6
+            }).then(() => task.done());
+          });
+
+      audit.define(
+          {label: 'Test 2', description: 'No dezippering for Q'},
+          (task, should) => {
+            doTest(should, {
+              paramName: 'Q',
+              initializer: {type: 'peaking', Q: 5},
+              changeList:
+                  [{quantum: 2, newValue: 10}, {quantum: 8, newValue: -10}]
+            }).then(() => task.done());
+          });
+
+      audit.define(
+          {label: 'Test 3', description: 'No dezippering for gain'},
+          (task, should) => {
+            doTest(should, {
+              paramName: 'gain',
+              initializer: {type: 'peaking', gain: 1},
+              changeList:
+                  [{quantum: 2, newValue: 5}, {quantum: 6, newValue: -.3}],
+              threshold: 1.9074e-6
+            }).then(() => task.done());
+          });
+
+      // This test compares the filter output against a JS implementation of the
+      // filter.  We're only testing a change in the frequency for a lowpass
+      // filter.  This assumes we don't need to test other AudioParam changes
+      // with JS code because any mistakes would be exposed in the tests above.
+      audit.define(
+          {
+            label: 'Test 4',
+            description: 'No dezippering of frequency vs JS filter'
+          },
+          (task, should) => {
+            // Channel 0 is the source, channel 1 is the filtered output.
+            let context = new OfflineAudioContext(2, 2048, 16384);
+
+            let merger = new ChannelMergerNode(
+                context, {numberOfInputs: context.destination.channelCount});
+            merger.connect(context.destination);
+
+            let src = new OscillatorNode(context);
+            let f = new BiquadFilterNode(context, {type: 'lowpass'});
+
+            // Remember the initial filter parameters.
+            let initialFilter = {
+              type: f.type,
+              frequency: f.frequency.value,
+              gain: f.gain.value,
+              detune: f.detune.value,
+              Q: f.Q.value
+            };
+
+            src.connect(merger, 0, 0);
+            src.connect(f).connect(merger, 0, 1);
+
+            // Apply the filter change at frame |changeFrame| with a new
+            // frequency value of |newValue|.
+            let changeFrame = 2 * RENDER_QUANTUM_FRAMES;
+            let newValue = 750;
+
+            context.suspend(changeFrame / context.sampleRate)
+                .then(() => f.frequency.value = newValue)
+                .then(() => context.resume());
+
+            src.start();
+
+            context.startRendering()
+                .then(audio => {
+                  let signal = audio.getChannelData(0);
+                  let actual = audio.getChannelData(1);
+
+                  // Get initial filter coefficients and updated coefficients
+                  let nyquistFreq = context.sampleRate / 2;
+                  let initialCoef = createFilter(
+                      initialFilter.type, initialFilter.frequency / nyquistFreq,
+                      initialFilter.Q, initialFilter.gain);
+
+                  let finalCoef = createFilter(
+                      f.type, f.frequency.value / nyquistFreq, f.Q.value,
+                      f.gain.value);
+
+                  let expected = new Float32Array(signal.length);
+
+                  // Filter the initial part of the signal.
+                  expected[0] =
+                      filterSample(signal[0], initialCoef, 0, 0, 0, 0);
+                  expected[1] = filterSample(
+                      signal[1], initialCoef, expected[0], 0, signal[0], 0);
+
+                  for (let k = 2; k < changeFrame; ++k) {
+                    expected[k] = filterSample(
+                        signal[k], initialCoef, expected[k - 1],
+                        expected[k - 2], signal[k - 1], signal[k - 2]);
+                  }
+
+                  // Filter the rest of the input with the new coefficients
+                  for (let k = changeFrame; k < signal.length; ++k) {
+                    expected[k] = filterSample(
+                        signal[k], finalCoef, expected[k - 1], expected[k - 2],
+                        signal[k - 1], signal[k - 2]);
+                  }
+
+                  // The JS filter should match the actual output.
+                  let match =
+                      should(actual, 'Output from ' + f.type + ' filter')
+                          .beCloseToArray(
+                              expected, {absoluteThreshold: 4.7684e-7});
+                  should(match, 'Output matches JS filter results').beTrue();
+                })
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {label: 'Test 5', description: 'Test with modulation'},
+          (task, should) => {
+            doTest(should, {
+              prefix: 'Modulation: ',
+              paramName: 'frequency',
+              initializer: {type: 'peaking', Q: 5, gain: 5},
+              modulation: true,
+              changeList:
+                  [{quantum: 2, newValue: 10}, {quantum: 8, newValue: -10}]
+            }).then(() => task.done());
+
+          });
+
+      audit.run();
+
+      // Run test, returning the promise from startRendering. |options|
+      // specifies the parameters for the test. |options.paramName| is the name
+      // of the AudioParam of the filter that is being tested.
+      // |options.initializer| is the initial value to be used in constructing
+      // the filter. |options.changeList| is an array consisting of dictionary
+      // with two members: |quantum| is the rendering quantum at which time we
+      // want to change the AudioParam value, and |newValue| is the value to be
+      // used.
+      function doTest(should, options) {
+        let paramName = options.paramName;
+        let newValue = options.newValue;
+        let prefix = options.prefix || '';
+
+        // Create offline audio context.  The sample rate should be a power of
+        // two to eliminate any round-off errors in computing the time at which
+        // to suspend the context for the parameter change.  The length is
+        // fairly arbitrary as long as it's big enough to the changeList
+        // values. There are two channels:  channel 0 is output for the filter
+        // under test, and channel 1 is the output of referencef filter.
+        let context = new OfflineAudioContext(2, 2048, 16384);
+
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+        merger.connect(context.destination);
+
+        let src = new OscillatorNode(context);
+
+        // |f0| is the filter under test that will have its AudioParam value
+        // changed. |f1| is the reference filter that uses setValueAtTime to
+        // update the AudioParam value.
+        let f0 = new BiquadFilterNode(context, options.initializer);
+        let f1 = new BiquadFilterNode(context, options.initializer);
+
+        src.connect(f0).connect(merger, 0, 0);
+        src.connect(f1).connect(merger, 0, 1);
+
+        // Modulate the AudioParam with an input signal, if requested.
+        if (options.modulation) {
+          // The modulation signal is a sine wave with amplitude 1/3 the cutoff
+          // frequency of the test filter.  The amplitude is fairly arbitrary,
+          // but we want it to be a significant fraction of the cutoff so that
+          // the cutoff varies quite a bit in the test.
+          let mod =
+              new OscillatorNode(context, {type: 'sawtooth', frequency: 1000});
+          let modGain = new GainNode(context, {gain: f0.frequency.value / 3});
+          mod.connect(modGain);
+          modGain.connect(f0[paramName]);
+          modGain.connect(f1[paramName]);
+          mod.start();
+        }
+        // Output a message showing where we're starting from.
+        should(f0[paramName].value, prefix + `At time 0, ${paramName}`)
+            .beEqualTo(f0[paramName].value);
+
+        // Schedule all of the desired changes from |changeList|.
+        options.changeList.forEach(change => {
+          let changeTime =
+              change.quantum * RENDER_QUANTUM_FRAMES / context.sampleRate;
+          let value = change.newValue;
+
+          // Just output a message to show what we're doing.
+          should(value, prefix + `At time ${changeTime}, ${paramName}`)
+              .beEqualTo(value);
+
+          // Update the AudioParam value of each filter using setValueAtTime or
+          // the value setter.
+          f1[paramName].setValueAtTime(value, changeTime);
+          context.suspend(changeTime)
+              .then(() => f0[paramName].value = value)
+              .then(() => context.resume());
+        });
+
+        src.start();
+
+        return context.startRendering().then(audio => {
+          let actual = audio.getChannelData(0);
+          let expected = audio.getChannelData(1);
+
+          // The output from both filters MUST match exactly if dezippering has
+          // been properly removed.
+          let match = should(actual, `${prefix}Output from ${paramName} setter`)
+                          .beCloseToArray(
+                              expected, {absoluteThreshold: options.threshold});
+
+          // Just an extra message saying that what we're comparing, to make the
+          // output clearer. (Not really neceesary, but nice.)
+          should(
+              match,
+              `${prefix}Output from ${
+                                      paramName
+                                    } setter matches setValueAtTime output`)
+              .beTrue();
+        });
+      }
+
+      // Filter one sample:
+      //
+      //   y[n] = b0 * x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]
+      //
+      // where |x| is x[n], |xn1| is x[n-1], |xn2| is x[n-2], |yn1| is y[n-1],
+      // and |yn2| is y[n-2].  |coef| is a dictonary of the filter coefficients
+      // |b0|, |b1|, |b2|, |a1|, and |a2|.
+      function filterSample(x, coef, yn1, yn2, xn1, xn2) {
+        return coef.b0 * x + coef.b1 * xn1 + coef.b2 * xn2 - coef.a1 * yn1 -
+            coef.a2 * yn2;
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-basic.html b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-basic.html
new file mode 100644
index 0000000..f967f06
--- /dev/null
+++ b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-basic.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audiochannelmerger-basic.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Task: Checking constraints in ChannelMergerNode.
+      audit.define('exceptions-channels', (task, should) => {
+        let context = new OfflineAudioContext(2, 128, 44100);
+        let merger;
+
+        should(function() {
+          merger = context.createChannelMerger();
+        }, 'context.createChannelMerger()').notThrow();
+
+        should(function() {
+          merger = context.createChannelMerger(0);
+        }, 'context.createChannelMerger(0)').throw('IndexSizeError');
+
+        should(function() {
+          merger = context.createChannelMerger(32);
+        }, 'context.createChannelMerger(32)').notThrow();
+
+        // Can't create a channel merger with 33 channels because the audio
+        // context has a 32-channel-limit in Chrome.
+        should(function() {
+          merger = context.createChannelMerger(33);
+        }, 'context.createChannelMerger(33)').throw('IndexSizeError');
+
+        task.done();
+      });
+
+      // Task: checking the channel-related properties have the correct value
+      // and can't be changed.
+      audit.define('exceptions-properties', (task, should) => {
+        let context = new OfflineAudioContext(2, 128, 44100);
+        let merger = context.createChannelMerger();
+
+        should(merger.channelCount, 'merger.channelCount').beEqualTo(1);
+
+        should(function() {
+          merger.channelCount = 3;
+        }, 'merger.channelCount = 3').throw('InvalidStateError');
+
+        should(merger.channelCountMode, 'merger.channelCountMode')
+            .beEqualTo('explicit');
+
+        should(function() {
+          merger.channelCountMode = 'max';
+        }, 'merger.channelCountMode = "max"').throw('InvalidStateError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-disconnect.html b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-disconnect.html
new file mode 100644
index 0000000..5fb18c8
--- /dev/null
+++ b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-disconnect.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audiochannelmerger-disconnect.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let renderQuantum = 128;
+
+      let numberOfChannels = 2;
+      let sampleRate = 44100;
+      let renderDuration = 0.5;
+      let disconnectTime = 0.5 * renderDuration;
+
+      let audit = Audit.createTaskRunner();
+
+      // Task: Check if the merger outputs a silent channel when an input is
+      // disconnected.
+      audit.define('silent-disconnect', (task, should) => {
+        let context = new OfflineAudioContext(
+            numberOfChannels, renderDuration * sampleRate, sampleRate);
+        let merger = context.createChannelMerger();
+        let source1 = context.createBufferSource();
+        let source2 = context.createBufferSource();
+
+        // Create and assign a constant buffer.
+        let bufferDCOffset = createConstantBuffer(context, 1, 1);
+        source1.buffer = source2.buffer = bufferDCOffset;
+        source1.loop = source2.loop = true;
+
+        // Connect the output of source into the 4th input of merger. The merger
+        // should produce 6 channel output.
+        source1.connect(merger, 0, 0);
+        source2.connect(merger, 0, 1);
+        merger.connect(context.destination);
+        source1.start();
+        source2.start();
+
+        // Schedule the disconnection of |source2| at the half of render
+        // duration.
+        context.suspend(disconnectTime).then(function() {
+          source2.disconnect();
+          context.resume();
+        });
+
+        context.startRendering()
+            .then(function(buffer) {
+              // The entire first channel of the output should be 1.
+              should(buffer.getChannelData(0), 'Channel #0')
+                  .beConstantValueOf(1);
+
+              // Calculate the first zero index in the second channel.
+              let channel1 = buffer.getChannelData(1);
+              let disconnectIndex = disconnectTime * sampleRate;
+              disconnectIndex -= (disconnectIndex) % renderQuantum;
+              let firstZeroIndex = channel1.findIndex(function(element, index) {
+                if (element === 0)
+                  return index;
+              });
+
+              // The second channel should contain 1, and 0 after the
+              // disconnection.
+              should(channel1, 'Channel #1').containValues([1, 0]);
+              should(
+                  firstZeroIndex, 'The index of first zero in the channel #1')
+                  .beEqualTo(disconnectIndex);
+
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-input-non-default.html b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-input-non-default.html
new file mode 100644
index 0000000..6fe77ab
--- /dev/null
+++ b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-input-non-default.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audiochannelmerger-input-non-default.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/merger-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+
+      // Task: Check if an inactive input renders a silent mono channel in the
+      // output.
+      audit.define('silent-channel', (task, should) => {
+        testMergerInput(should, {
+          numberOfChannels: 7,
+
+          // Create a mono source buffer filled with '1'.
+          testBufferContent: [1],
+
+          // Connect the output of source into the 7th input of merger.
+          mergerInputIndex: 6,
+
+          // 7th channel should be '1'.
+          expected: [0, 0, 0, 0, 0, 0, 1],
+        }).then(() => task.done());
+      });
+
+
+      // Task: Check if a stereo input is being down-mixed to mono channel
+      // correctly based on the mixing rule.
+      audit.define('stereo-down-mixing', (task, should) => {
+        testMergerInput(should, {
+          numberOfChannels: 7,
+
+          // Create a stereo buffer filled with '1' and '2' for left and right
+          // channels respectively.
+          testBufferContent: [1, 2],
+
+          // Connect the output of source into the 7th input of merger.
+          mergerInputIndex: 6,
+
+          // The result of summed and down-mixed stereo audio should be 1.5.
+          // (= 1 * 0.5 + 2 * 0.5)
+          expected: [0, 0, 0, 0, 0, 0, 1.5],
+        }).then(() => task.done());
+      });
+
+
+      // Task: Check if 3-channel input gets processed by the 'discrete' mixing
+      // rule.
+      audit.define('undefined-channel-layout', (task, should) => {
+        testMergerInput(should, {
+          numberOfChannels: 7,
+
+          // Create a 3-channel buffer filled with '1', '2', and '3'
+          // respectively.
+          testBufferContent: [1, 2, 3],
+
+          // Connect the output of source into the 7th input of merger.
+          mergerInputIndex: 6,
+
+          // The result of summed stereo audio should be 1 because 3-channel is
+          // not a canonical layout, so the input channel 2 and 3 should be
+          // dropped by 'discrete' mixing rule.
+          expected: [0, 0, 0, 0, 0, 0, 1],
+        }).then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-input.html b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-input.html
new file mode 100644
index 0000000..66a70dc
--- /dev/null
+++ b/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-input.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      audiochannelmerger-input.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/merger-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Task: Check if an inactive input renders a silent mono channel in the
+      // output.
+      audit.define('silent-channel', (task, should) => {
+        testMergerInput(should, {
+          numberOfChannels: 6,
+
+          // Create a mono source buffer filled with '1'.
+          testBufferContent: [1],
+
+          // Connect the output of source into the 4th input of merger.
+          mergerInputIndex: 3,
+
+          // All channels should contain 0, except channel 4 which should be 1.
+          expected: [0, 0, 0, 1, 0, 0],
+        }).then(() => task.done());
+      });
+
+
+      // Task: Check if a stereo input is being down-mixed to mono channel
+      // correctly based on the mixing rule.
+      audit.define('stereo-down-mixing', (task, should) => {
+        testMergerInput(should, {
+          numberOfChannels: 6,
+
+          // Create a stereo buffer filled with '1' and '2' for left and right
+          // channels respectively.
+          testBufferContent: [1, 2],
+
+          // Connect the output of source into the 1st input of merger.
+          mergerInputIndex: undefined,
+
+          // The result of summed and down-mixed stereo audio should be 1.5.
+          // (= 1 * 0.5 + 2 * 0.5)
+          expected: [1.5, 0, 0, 0, 0, 0],
+        }).then(() => task.done());
+      });
+
+
+      // Task: Check if 3-channel input gets processed by the 'discrete' mixing
+      // rule.
+      audit.define('undefined-channel-layout', (task, should) => {
+        testMergerInput(should, {
+          numberOfChannels: 6,
+
+          // Create a 3-channel buffer filled with '1', '2', and '3'
+          // respectively.
+          testBufferContent: [1, 2, 3],
+
+          // Connect the output of source into the 1st input of merger.
+          mergerInputIndex: undefined,
+
+          // The result of summed stereo audio should be 1 because 3-channel is
+          // not a canonical layout, so the input channel 2 and 3 should be
+          // dropped by 'discrete' mixing rule.
+          expected: [1, 0, 0, 0, 0, 0],
+        }).then(() => task.done());
+      });
+
+
+      // Task: Merging two inputs into a single stereo stream.
+      audit.define('merging-to-stereo', (task, should) => {
+
+        // For this test, the number of channel should be 2.
+        let context = new OfflineAudioContext(2, 128, 44100);
+        let merger = context.createChannelMerger();
+        let source1 = context.createBufferSource();
+        let source2 = context.createBufferSource();
+
+        // Create a DC offset buffer (mono) filled with 1 and assign it to BS
+        // nodes.
+        let positiveDCOffset = createConstantBuffer(context, 128, 1);
+        let negativeDCOffset = createConstantBuffer(context, 128, -1);
+        source1.buffer = positiveDCOffset;
+        source2.buffer = negativeDCOffset;
+
+        // Connect: BS#1 => merger_input#0, BS#2 => Inverter => merger_input#1
+        source1.connect(merger, 0, 0);
+        source2.connect(merger, 0, 1);
+        merger.connect(context.destination);
+        source1.start();
+        source2.start();
+
+        context.startRendering().then(function(buffer) {
+
+          // Channel#0 = 1, Channel#1 = -1
+          should(buffer.getChannelData(0), 'Channel #0').beConstantValueOf(1);
+          should(buffer.getChannelData(1), 'Channel #1').beConstantValueOf(-1);
+
+          task.done();
+        });
+      });
+
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelmergernode-interface/ctor-channelmerger.html b/webaudio/the-audio-api/the-channelmergernode-interface/ctor-channelmerger.html
new file mode 100644
index 0000000..115bd99
--- /dev/null
+++ b/webaudio/the-audio-api/the-channelmergernode-interface/ctor-channelmerger.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: ChannelMerger
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'ChannelMergerNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node =
+            testDefaultConstructor(should, 'ChannelMergerNode', context, {
+              prefix: prefix,
+              numberOfInputs: 6,
+              numberOfOutputs: 1,
+              channelCount: 1,
+              channelCountMode: 'explicit',
+              channelInterpretation: 'speakers'
+            });
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'ChannelMergerNode', {
+          channelCount:
+              {value: 1, isFixed: true, errorType: 'InvalidStateError'},
+          channelCountMode: {
+            value: 'explicit',
+            isFixed: true,
+            errorType: 'InvalidStateError'
+          }
+        });
+        task.done();
+      });
+
+      audit.define('constructor options', (task, should) => {
+        let node;
+        let options = {
+          numberOfInputs: 3,
+          numberOfOutputs: 9,
+          channelInterpretation: 'discrete'
+        };
+
+        should(
+            () => {
+              node = new ChannelMergerNode(context, options);
+            },
+            'node1 = new ChannelMergerNode(context, ' +
+                JSON.stringify(options) + ')')
+            .notThrow();
+
+        should(node.numberOfInputs, 'node1.numberOfInputs')
+            .beEqualTo(options.numberOfInputs);
+        should(node.numberOfOutputs, 'node1.numberOfOutputs').beEqualTo(1);
+        should(node.channelInterpretation, 'node1.channelInterpretation')
+            .beEqualTo(options.channelInterpretation);
+
+        options = {numberOfInputs: 99};
+        should(
+            () => {
+              node = new ChannelMergerNode(context, options);
+            },
+            'new ChannelMergerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('IndexSizeError');
+
+        options = {channelCount: 3};
+        should(
+            () => {
+              node = new ChannelMergerNode(context, options);
+            },
+            'new ChannelMergerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('InvalidStateError');
+
+        options = {channelCountMode: 'max'};
+        should(
+            () => {
+              node = new ChannelMergerNode(context, options);
+            },
+            'new ChannelMergerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('InvalidStateError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelsplitternode-interface/audiochannelsplitter.html b/webaudio/the-audio-api/the-channelsplitternode-interface/audiochannelsplitter.html
new file mode 100644
index 0000000..e044991
--- /dev/null
+++ b/webaudio/the-audio-api/the-channelsplitternode-interface/audiochannelsplitter.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<!--
+Tests that AudioChannelSplitter works correctly.
+-->
+<html>
+  <head>
+    <title>
+      audiochannelsplitter.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 44100.0;
+      let lengthInSampleFrames = 512;
+
+      let context = 0;
+      let sourceBuffer;
+      let sourceNode;
+      let channelSplitter;
+      let channelMerger;
+
+      function createStereoBufferWithDCOffset(length, sampleRate, offset) {
+        let buffer = context.createBuffer(2, length, sampleRate);
+        let n = buffer.length;
+        let channelL = buffer.getChannelData(0);
+        let channelR = buffer.getChannelData(1);
+
+        for (let i = 0; i < n; ++i) {
+          channelL[i] = offset;
+          channelR[i] = -1.0 * offset;
+        }
+
+        return buffer;
+      }
+
+      // checkResult() checks that the rendered buffer is stereo and that the
+      // left channel is all -1 and right channel all +1. In other words, we've
+      // reversed the order of the two channels.
+      function checkResult(buffer, should) {
+        let success = true;
+
+        if (buffer.numberOfChannels == 2) {
+          let bufferDataL = buffer.getChannelData(0);
+          let bufferDataR = buffer.getChannelData(1);
+
+          success = should(bufferDataL, 'Left channel').beConstantValueOf(-1) &&
+              success;
+          success = should(bufferDataR, 'Right channel').beConstantValueOf(1) &&
+              success;
+        } else {
+          success = false;
+        }
+
+        should(success, 'Left and right channels were exchanged')
+            .message('correctly', 'incorrectly');
+      }
+
+      audit.define(
+          {
+            label: 'construction',
+            description: 'Construction of ChannelSplitterNode'
+          },
+          function(task, should) {
+
+            // Create stereo offline audio context.
+            context =
+                new OfflineAudioContext(2, lengthInSampleFrames, sampleRate);
+
+            let splitternode;
+            should(() => {
+              let splitternode = context.createChannelSplitter(0);
+            }, 'createChannelSplitter(0)').throw('IndexSizeError');
+
+            should(() => {
+              splitternode = context.createChannelSplitter(33);
+            }, 'createChannelSplitter(33)').throw('IndexSizeError');
+
+            should(() => {
+              splitternode = context.createChannelSplitter(32);
+            }, 'splitternode = context.createChannelSplitter(32)').notThrow();
+
+            should(splitternode.numberOfOutputs, 'splitternode.numberOfOutputs')
+                .beEqualTo(32);
+            should(splitternode.numberOfInputs, 'splitternode.numberOfInputs')
+                .beEqualTo(1)
+
+            should(() => {
+              splitternode = context.createChannelSplitter();
+            }, 'splitternode = context.createChannelSplitter()').notThrow();
+
+            should(splitternode.numberOfOutputs, 'splitternode.numberOfOutputs')
+                .beEqualTo(6);
+
+            task.done();
+          });
+
+      audit.define(
+          {
+            label: 'functionality',
+            description: 'Functionality of ChannelSplitterNode'
+          },
+          function(task, should) {
+
+            // Create a stereo buffer, with all +1 values in left channel, all
+            // -1 in right channel.
+            sourceBuffer = createStereoBufferWithDCOffset(
+                lengthInSampleFrames, sampleRate, 1);
+
+            sourceNode = context.createBufferSource();
+            sourceNode.buffer = sourceBuffer;
+
+            // Create a channel splitter and connect it so that it split the
+            // stereo stream into two mono streams.
+            channelSplitter = context.createChannelSplitter(2);
+            sourceNode.connect(channelSplitter);
+
+            // Create a channel merger to merge the output of channel splitter.
+            channelMerger = context.createChannelMerger();
+            channelMerger.connect(context.destination);
+
+            // When merging, exchange channel layout: left->right, right->left
+            channelSplitter.connect(channelMerger, 0, 1);
+            channelSplitter.connect(channelMerger, 1, 0);
+
+            sourceNode.start(0);
+
+            context.startRendering()
+                .then(buffer => checkResult(buffer, should))
+                .then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html b/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html
new file mode 100644
index 0000000..7fa9d6f
--- /dev/null
+++ b/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: ChannelSplitter
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'ChannelSplitterNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        testDefaultConstructor(should, 'ChannelSplitterNode', context, {
+          prefix: 'node0',
+          numberOfInputs: 1,
+          numberOfOutputs: 6,
+          channelCount: 6,
+          channelCountMode: 'explicit',
+          channelInterpretation: 'discrete'
+        });
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'ChannelSplitterNode', {
+          channelCount:
+              {value: 6, isFixed: true, errorType: 'InvalidStateError'},
+          channelCountMode: {
+            value: 'explicit',
+            isFixed: true,
+          },
+          channelInterpretation: {
+            value: 'discrete',
+            isFixed: true,
+            errorType: 'InvalidStateError'
+          },
+        });
+        task.done();
+      });
+
+      audit.define('constructor options', (task, should) => {
+        let node;
+        let options = {
+          numberOfInputs: 3,
+          numberOfOutputs: 9,
+          channelInterpretation: 'discrete'
+        };
+
+        should(
+            () => {
+              node = new ChannelSplitterNode(context, options);
+            },
+            'node1 = new ChannelSplitterNode(context, ' +
+                JSON.stringify(options) + ')')
+            .notThrow();
+
+        should(node.numberOfInputs, 'node1.numberOfInputs').beEqualTo(1);
+        should(node.numberOfOutputs, 'node1.numberOfOutputs')
+            .beEqualTo(options.numberOfOutputs);
+        should(node.channelInterpretation, 'node1.channelInterpretation')
+            .beEqualTo(options.channelInterpretation);
+
+        options = {numberOfOutputs: 99};
+        should(
+            () => {
+              node = new ChannelSplitterNode(context, options);
+            },
+            'new ChannelSplitterNode(c, ' + JSON.stringify(options) + ')')
+            .throw('IndexSizeError');
+
+        options = {channelCount: 3};
+        should(
+            () => {
+              node = new ChannelSplitterNode(context, options);
+            },
+            'new ChannelSplitterNode(c, ' + JSON.stringify(options) + ')')
+            .throw('InvalidStateError');
+
+        options = {channelCountMode: 'max'};
+        should(
+            () => {
+              node = new ChannelSplitterNode(context, options);
+            },
+            'new ChannelSplitterNode(c, ' + JSON.stringify(options) + ')')
+            .throw('InvalidStateError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-constantsourcenode-interface/ctor-constantsource.html b/webaudio/the-audio-api/the-constantsourcenode-interface/ctor-constantsource.html
new file mode 100644
index 0000000..ea4a65e
--- /dev/null
+++ b/webaudio/the-audio-api/the-constantsourcenode-interface/ctor-constantsource.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: ConstantSource
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'ConstantSourceNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node =
+            testDefaultConstructor(should, 'ConstantSourceNode', context, {
+              prefix: prefix,
+              numberOfInputs: 0,
+              numberOfOutputs: 1,
+              channelCount: 2,
+              channelCountMode: 'max',
+              channelInterpretation: 'speakers'
+            });
+
+        testDefaultAttributes(
+            should, node, prefix, [{name: 'offset', value: 1}]);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/convolution-mono-mono.html b/webaudio/the-audio-api/the-convolvernode-interface/convolution-mono-mono.html
new file mode 100644
index 0000000..570efeb
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/convolution-mono-mono.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      convolution-mono-mono.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/convolution-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // description("Tests ConvolverNode processing a mono channel with mono
+      // impulse response.");
+
+      // To test the convolver, we convolve two square pulses together to
+      // produce a triangular pulse.  To verify the result is correct we
+      // check several parts of the result.  First, we make sure the initial
+      // part of the result is zero (due to the latency in the convolver).
+      // Next, the triangular pulse should match the theoretical result to
+      // within some roundoff.  After the triangular pulse, the result
+      // should be exactly zero, but round-off prevents that.  We make sure
+      // the part after the pulse is sufficiently close to zero.  Finally,
+      // the result should be exactly zero because the inputs are exactly
+      // zero.
+      audit.define('test', function(task, should) {
+        // Create offline audio context.
+        let context = new OfflineAudioContext(
+            2, sampleRate * renderLengthSeconds, sampleRate);
+
+        let squarePulse = createSquarePulseBuffer(context, pulseLengthFrames);
+        let trianglePulse =
+            createTrianglePulseBuffer(context, 2 * pulseLengthFrames);
+
+        let bufferSource = context.createBufferSource();
+        bufferSource.buffer = squarePulse;
+
+        let convolver = context.createConvolver();
+        convolver.normalize = false;
+        convolver.buffer = squarePulse;
+
+        bufferSource.connect(convolver);
+        convolver.connect(context.destination);
+
+        bufferSource.start(0);
+
+        context.startRendering()
+            .then(buffer => {
+              checkConvolvedResult(buffer, trianglePulse, should);
+            })
+            .then(task.done.bind(task));
+        ;
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/convolver-cascade.html b/webaudio/the-audio-api/the-convolvernode-interface/convolver-cascade.html
new file mode 100644
index 0000000..20bdfbd
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/convolver-cascade.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Cascade of Mono Convolvers
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // Arbitrary sample rate and reasonably short duration
+      let sampleRate = 8000;
+      let duration = 0.25;
+      let renderFrames = duration * sampleRate;
+
+      audit.define(
+          {label: 'cascade-mono', description: 'Cascaded mono convolvers'},
+          (task, should) => {
+            // Cascade two convolvers with mono responses and verify that the
+            // output is not silent.
+            let context = new OfflineAudioContext(1, renderFrames, sampleRate);
+
+            let b0 =
+                new AudioBuffer({length: 5, sampleRate: context.sampleRate});
+            b0.getChannelData(0)[1] = 1;
+            let c0 = new ConvolverNode(
+                context, {disableNormalization: true, buffer: b0});
+
+            let b1 =
+                new AudioBuffer({length: 5, sampleRate: context.sampleRate});
+            b1.getChannelData(0)[2] = 1;
+
+            let c1 = new ConvolverNode(
+                context, {disableNormalization: true, buffer: b1});
+
+            let src = new OscillatorNode(context);
+
+            src.connect(c0).connect(c1).connect(context.destination);
+
+            src.start();
+
+            context.startRendering()
+                .then(audioBuffer => {
+                  // Just verify the output is not silent
+                  let audio = audioBuffer.getChannelData(0);
+
+                  should(audio, 'Output of cascaded mono convolvers')
+                      .notBeConstantValueOf(0);
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/convolver-channels.html b/webaudio/the-audio-api/the-convolvernode-interface/convolver-channels.html
new file mode 100644
index 0000000..11d6f33
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/convolver-channels.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Supported Number of Channels for ConvolverNode
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define('channel-count-test', (task, should) => {
+        // Just need a context to create nodes on, so any allowed length and
+        // rate is ok.
+        let context = new OfflineAudioContext(1, 1, 48000);
+
+        let success = true;
+
+        for (let count = 1; count <= 32; ++count) {
+          let convolver = context.createConvolver();
+          let buffer = context.createBuffer(count, 1, context.sampleRate);
+          let message = 'ConvolverNode with buffer of ' + count + ' channels';
+
+          if (count == 1 || count == 2 || count == 4) {
+            // These are the only valid channel counts for the buffer.
+            should(() => convolver.buffer = buffer, message).notThrow();
+          } else {
+            should(() => convolver.buffer = buffer, message)
+                .throw('NotSupportedError');
+          }
+        }
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-1-chan.html b/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-1-chan.html
new file mode 100644
index 0000000..2867468
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-1-chan.html
@@ -0,0 +1,232 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Convolver Channel Outputs for Response with 1 channel
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Test various convolver configurations when the convolver response has
+      // one channel (mono).
+      //
+      // Fairly arbitrary sample rate, except that we want the rate to be a
+      // power of two so that 1/sampleRate is exactly respresentable as a
+      // single-precision float.
+      let sampleRate = 8192;
+
+      // A fairly arbitrary number of frames, except the number of frames should
+      // be more than a few render quanta.
+      let renderFrames = 10 * 128;
+
+      let audit = Audit.createTaskRunner();
+
+      // Convolver response
+      let response;
+
+      audit.define(
+          {
+            label: 'initialize',
+            description: 'Convolver response with one channel'
+          },
+          (task, should) => {
+            // Convolver response
+            should(
+                () => {
+                  response = new AudioBuffer(
+                      {numberOfChannels: 1, length: 2, sampleRate: sampleRate});
+                  response.getChannelData(0)[1] = 1;
+                },
+                'new AudioBuffer({numberOfChannels: 1, length: 2, sampleRate: ' +
+                    sampleRate + '})')
+                .notThrow();
+
+            task.done();
+          });
+
+      audit.define(
+          {label: '1-channel input', description: 'produces 1-channel output'},
+          (task, should) => {
+            // Create a 3-channel context:  channel 0 = convolver under test,
+            // channel 1: test that convolver output is not stereo, channel 2:
+            // expected output.  The context MUST be discrete so that the
+            // channels don't get mixed in some unexpected way.
+            let context = new OfflineAudioContext(3, renderFrames, sampleRate);
+            context.destination.channelInterpretation = 'discrete';
+
+            let src = new OscillatorNode(context);
+            let conv = new ConvolverNode(
+                context, {disableNormalization: true, buffer: response});
+
+            // Splitter node to verify that the output of the convolver is mono.
+            // channelInterpretation must be 'discrete' so we don't do any
+            // mixing of the input to the node.
+            let splitter = new ChannelSplitterNode(
+                context,
+                {numberOfOutputs: 2, channelInterpretation: 'discrete'});
+
+            // Final merger to feed all of the individual channels into the
+            // destination.
+            let merger = new ChannelMergerNode(context, {numberOfInputs: 3});
+
+            src.connect(conv).connect(splitter);
+            splitter.connect(merger, 0, 0);
+            splitter.connect(merger, 1, 1);
+
+            // The convolver response is a 1-sample delay.  Use a delay node to
+            // implement this.
+            let delay =
+                new DelayNode(context, {delayTime: 1 / context.sampleRate});
+            src.connect(delay);
+            delay.connect(merger, 0, 2);
+
+            merger.connect(context.destination);
+
+            src.start();
+
+            context.startRendering()
+                .then(audioBuffer => {
+                  // Extract out the three channels
+                  let actual = audioBuffer.getChannelData(0);
+                  let c1 = audioBuffer.getChannelData(1);
+                  let expected = audioBuffer.getChannelData(2);
+
+                  // c1 is expected to be zero.
+                  should(c1, '1: Channel 1').beConstantValueOf(0);
+
+                  // The expected and actual results should be identical
+                  should(actual, 'Convolver output')
+                    .beCloseToArray(expected, {absoluteThreshold: 4.1724e-7});
+                })
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {label: '2-channel input', description: 'produces 2-channel output'},
+          (task, should) => {
+            downMixTest({numberOfInputs: 2, prefix: '2'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '3-channel input',
+            description: '3->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            downMixTest({numberOfInputs: 3, prefix: '3'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '4-channel input',
+            description: '4->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            downMixTest({numberOfInputs: 4, prefix: '4'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '5.1-channel input',
+            description: '5.1->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            downMixTest({numberOfInputs: 6, prefix: '5.1'}, should)
+                .then(() => task.done());
+          });
+
+      function downMixTest(options, should) {
+        // Create an 4-channel offline context.  The first two channels are for
+        // the stereo output of the convolver and the next two channels are for
+        // the reference stereo signal.
+        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
+        context.destination.channelInterpretation = 'discrete';
+
+        // Create oscillators for use as the input.  The type and frequency is
+        // arbitrary except that oscillators must be different.
+        let src = new Array(options.numberOfInputs);
+        for (let k = 0; k < src.length; ++k) {
+          src[k] = new OscillatorNode(
+              context, {type: 'square', frequency: 440 + 220 * k});
+        }
+
+        // Merger to combine the oscillators into one output stream.
+        let srcMerger =
+            new ChannelMergerNode(context, {numberOfInputs: src.length});
+
+        for (let k = 0; k < src.length; ++k) {
+          src[k].connect(srcMerger, 0, k);
+        }
+
+        // Convolver under test.
+        let conv = new ConvolverNode(
+            context, {disableNormalization: true, buffer: response});
+        srcMerger.connect(conv);
+
+        // Splitter to get individual channels of the convolver output so we can
+        // feed them (eventually) to the context in the right set of channels.
+        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        conv.connect(splitter);
+
+        // Reference graph consists of a delay node to simulate the response of
+        // the convolver.  (The convolver response is designed this way.)
+        let delay = new DelayNode(context, {delayTime: 1 / context.sampleRate});
+
+        // Gain node to mix the sources to stereo in the desired way.  (Could be
+        // done in the delay node, but let's keep the mixing separated from the
+        // functionality.)
+        let gainMixer = new GainNode(
+            context, {channelCount: 2, channelCountMode: 'explicit'});
+        srcMerger.connect(gainMixer);
+
+        // Splitter to extract the channels of the reference signal.
+        let refSplitter =
+            new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        gainMixer.connect(delay).connect(refSplitter);
+
+        // Final merger to bring back the individual channels from the convolver
+        // and the reference in the right order for the destination.
+        let finalMerger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+
+        // First two channels are for the convolver output, and the next two are
+        // for the reference.
+        splitter.connect(finalMerger, 0, 0);
+        splitter.connect(finalMerger, 1, 1);
+        refSplitter.connect(finalMerger, 0, 2);
+        refSplitter.connect(finalMerger, 1, 3);
+
+        finalMerger.connect(context.destination);
+
+        // Start the sources at last.
+        for (let k = 0; k < src.length; ++k) {
+          src[k].start();
+        }
+
+        return context.startRendering().then(audioBuffer => {
+          // Extract the various channels out
+          let actual0 = audioBuffer.getChannelData(0);
+          let actual1 = audioBuffer.getChannelData(1);
+          let expected0 = audioBuffer.getChannelData(2);
+          let expected1 = audioBuffer.getChannelData(3);
+
+          // Verify that each output channel of the convolver matches
+          // the delayed signal from the reference
+          should(actual0, options.prefix + ': Channel 0')
+              .beEqualToArray(expected0);
+          should(actual1, options.prefix + ': Channel 1')
+              .beEqualToArray(expected1);
+        });
+      }
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-2-chan.html b/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-2-chan.html
new file mode 100644
index 0000000..b30ef1f
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-2-chan.html
@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Convolver Channel Outputs for Response with 2 channels
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Test various convolver configurations when the convolver response has
+      // a stereo response.
+
+      // Fairly arbitrary sample rate, except that we want the rate to be a
+      // power of two so that 1/sampleRate is exactly respresentable as a
+      // single-precision float.
+      let sampleRate = 8192;
+
+      // A fairly arbitrary number of frames, except the number of frames should
+      // be more than a few render quanta.
+      let renderFrames = 10 * 128;
+
+      let audit = Audit.createTaskRunner();
+
+      // Convolver response
+      let response;
+
+      audit.define(
+          {
+            label: 'initialize',
+            description: 'Convolver response with one channel'
+          },
+          (task, should) => {
+            // Convolver response
+            should(
+                () => {
+                  response = new AudioBuffer(
+                      {numberOfChannels: 2, length: 4, sampleRate: sampleRate});
+                  // Each channel of the response is a simple impulse (with
+                  // different delay) so that we can use a DelayNode to simulate
+                  // the convolver output.  Channel k is delayed by k+1 frames.
+                  for (let k = 0; k < response.numberOfChannels; ++k) {
+                    response.getChannelData(k)[k + 1] = 1;
+                  }
+                },
+                'new AudioBuffer({numberOfChannels: 2, length: 4, sampleRate: ' +
+                    sampleRate + '})')
+                .notThrow();
+
+            task.done();
+          });
+
+      audit.define(
+          {label: '1-channel input', description: 'produces 2-channel output'},
+          (task, should) => {
+            stereoResponseTest({numberOfInputs: 1, prefix: '1', absoluteThresholds:
+            [3.5763e-7, 4.7684e-7]}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {label: '2-channel input', description: 'produces 2-channel output'},
+          (task, should) => {
+            stereoResponseTest({numberOfInputes: 2, prefix: '2', absoluteThresholds:
+            [3.5763e-7, 4.7684e-7]}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '3-channel input',
+            description: '3->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            stereoResponseTest({numberOfInputs: 3, prefix: '3', absoluteThresholds:
+            [3.5763e-7, 3.5763e-7]}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '4-channel input',
+            description: '4->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            stereoResponseTest({numberOfInputs: 4, prefix: '4', absoluteThresholds:
+            [3.5763e-7, 2.9803e-7]}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '5.1-channel input',
+            description: '5.1->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            stereoResponseTest({numberOfInputs: 6, prefix: '5.1', absoluteThresholds:
+            [7.1526e-7, 7.1526e-7]}, should)
+                .then(() => task.done());
+          });
+
+      function stereoResponseTest(options, should) {
+        // Create an 4-channel offline context.  The first two channels are for
+        // the stereo output of the convolver and the next two channels are for
+        // the reference stereo signal.
+        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
+        context.destination.channelInterpretation = 'discrete';
+
+        // Create oscillators for use as the input.  The type and frequency is
+        // arbitrary except that oscillators must be different.
+        let src = new Array(options.numberOfInputs);
+        for (let k = 0; k < src.length; ++k) {
+          src[k] = new OscillatorNode(
+              context, {type: 'square', frequency: 440 + 220 * k});
+        }
+
+        // Merger to combine the oscillators into one output stream.
+        let srcMerger =
+            new ChannelMergerNode(context, {numberOfInputs: src.length});
+
+        for (let k = 0; k < src.length; ++k) {
+          src[k].connect(srcMerger, 0, k);
+        }
+
+        // Convolver under test.
+        let conv = new ConvolverNode(
+            context, {disableNormalization: true, buffer: response});
+        srcMerger.connect(conv);
+
+        // Splitter to get individual channels of the convolver output so we can
+        // feed them (eventually) to the context in the right set of channels.
+        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        conv.connect(splitter);
+
+        // Reference graph consists of a delays node to simulate the response of
+        // the convolver.  (The convolver response is designed this way.)
+        let delay = new Array(2);
+        for (let k = 0; k < delay.length; ++k) {
+          delay[k] = new DelayNode(context, {
+            delayTime: (k + 1) / context.sampleRate,
+            channelCount: 1,
+            channelCountMode: 'explicit'
+          });
+        }
+
+        // Gain node to mix the sources to stereo in the desired way.  (Could be
+        // done in the delay node, but let's keep the mixing separated from the
+        // functionality.)
+        let gainMixer = new GainNode(
+            context, {channelCount: 2, channelCountMode: 'explicit'});
+        srcMerger.connect(gainMixer);
+
+        // Splitter to extract the channels of the reference signal.
+        let refSplitter =
+            new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        gainMixer.connect(refSplitter);
+
+        // Connect each channel to the delay nodes
+        for (let k = 0; k < delay.length; ++k) {
+          refSplitter.connect(delay[k], k);
+        }
+
+        // Final merger to bring back the individual channels from the convolver
+        // and the reference in the right order for the destination.
+        let finalMerger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+
+        // First two channels are for the convolver output, and the next two are
+        // for the reference.
+        splitter.connect(finalMerger, 0, 0);
+        splitter.connect(finalMerger, 1, 1);
+        delay[0].connect(finalMerger, 0, 2);
+        delay[1].connect(finalMerger, 0, 3);
+
+        finalMerger.connect(context.destination);
+
+        // Start the sources at last.
+        for (let k = 0; k < src.length; ++k) {
+          src[k].start();
+        }
+
+        return context.startRendering().then(audioBuffer => {
+          // Extract the various channels out
+          let actual0 = audioBuffer.getChannelData(0);
+          let actual1 = audioBuffer.getChannelData(1);
+          let expected0 = audioBuffer.getChannelData(2);
+          let expected1 = audioBuffer.getChannelData(3);
+
+          // Verify that each output channel of the convolver matches
+          // the delayed signal from the reference
+          should(actual0, options.prefix + ': Channel 0')
+              .beCloseToArray(expected0, {absoluteThreshold: options.absoluteThresholds[0]});
+          should(actual1, options.prefix + ': Channel 1')
+              .beCloseToArray(expected1, {absoluteThreshold: options.absoluteThresholds[1]});
+        });
+      }
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-4-chan.html b/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-4-chan.html
new file mode 100644
index 0000000..2e8a1c7
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/convolver-response-4-chan.html
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Convolver Channel Outputs for Response with 4 channels
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Test various convolver configurations when the convolver response has
+      // a four channels.
+
+      // Fairly arbitrary sample rate, except that we want the rate to be a
+      // power of two so that 1/sampleRate is exactly respresentable as a
+      // single-precision float.
+      let sampleRate = 8192;
+
+      // A fairly arbitrary number of frames, except the number of frames should
+      // be more than a few render quanta.
+      let renderFrames = 10 * 128;
+
+      let audit = Audit.createTaskRunner();
+
+      // Convolver response
+      let response;
+
+      audit.define(
+          {
+            label: 'initialize',
+            description: 'Convolver response with one channel'
+          },
+          (task, should) => {
+            // Convolver response
+            should(
+                () => {
+                  response = new AudioBuffer(
+                      {numberOfChannels: 4, length: 8, sampleRate: sampleRate});
+                  // Each channel of the response is a simple impulse (with
+                  // different delay) so that we can use a DelayNode to simulate
+                  // the convolver output.  Channel k is delayed by k+1 frames.
+                  for (let k = 0; k < response.numberOfChannels; ++k) {
+                    response.getChannelData(k)[k + 1] = 1;
+                  }
+                },
+                'new AudioBuffer({numberOfChannels: 2, length: 4, sampleRate: ' +
+                    sampleRate + '})')
+                .notThrow();
+
+            task.done();
+          });
+
+      audit.define(
+          {label: '1-channel input', description: 'produces 2-channel output'},
+          (task, should) => {
+            fourChannelResponseTest({numberOfInputs: 1, prefix: '1'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {label: '2-channel input', description: 'produces 2-channel output'},
+          (task, should) => {
+            fourChannelResponseTest({numberOfInputs: 2, prefix: '2'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '3-channel input',
+            description: '3->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            fourChannelResponseTest({numberOfInputs: 3, prefix: '3'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '4-channel input',
+            description: '4->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            fourChannelResponseTest({numberOfInputs: 4, prefix: '4'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: '5.1-channel input',
+            description: '5.1->2 downmix producing 2-channel output'
+          },
+          (task, should) => {
+            fourChannelResponseTest({numberOfInputs: 6, prefix: '5.1'}, should)
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'delayed buffer set',
+            description: 'Delayed set of 4-channel response'
+          },
+          (task, should) => {
+            // Don't really care about the output for this test.  It's to verify
+            // we don't crash in a debug build when setting the convolver buffer
+            // after creating the graph.
+            let context = new OfflineAudioContext(1, renderFrames, sampleRate);
+            let src = new OscillatorNode(context);
+            let convolver =
+                new ConvolverNode(context, {disableNormalization: true});
+            let buffer = new AudioBuffer({
+              numberOfChannels: 4,
+              length: 4,
+              sampleRate: context.sampleRate
+            });
+
+            // Impulse responses for the convolver aren't important, as long as
+            // it's not all zeroes.
+            for (let k = 0; k < buffer.numberOfChannels; ++k) {
+              buffer.getChannelData(k).fill(1);
+            }
+
+            src.connect(convolver).connect(context.destination);
+
+            // Set the buffer after a few render quanta have passed.  The actual
+            // value must be least one, but is otherwise arbitrary.
+            context.suspend(512 / context.sampleRate)
+                .then(() => convolver.buffer = buffer)
+                .then(() => context.resume());
+
+            src.start();
+            context.startRendering()
+                .then(audioBuffer => {
+                  // Just make sure output is not silent.
+                  should(
+                      audioBuffer.getChannelData(0),
+                      'Output with delayed setting of convolver buffer')
+                      .notBeConstantValueOf(0);
+                })
+                .then(() => task.done());
+          });
+
+      function fourChannelResponseTest(options, should) {
+        // Create an 4-channel offline context.  The first two channels are for
+        // the stereo output of the convolver and the next two channels are for
+        // the reference stereo signal.
+        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
+        context.destination.channelInterpretation = 'discrete';
+
+        // Create oscillators for use as the input.  The type and frequency is
+        // arbitrary except that oscillators must be different.
+        let src = new Array(options.numberOfInputs);
+        for (let k = 0; k < src.length; ++k) {
+          src[k] = new OscillatorNode(
+              context, {type: 'square', frequency: 440 + 220 * k});
+        }
+
+        // Merger to combine the oscillators into one output stream.
+        let srcMerger =
+            new ChannelMergerNode(context, {numberOfInputs: src.length});
+
+        for (let k = 0; k < src.length; ++k) {
+          src[k].connect(srcMerger, 0, k);
+        }
+
+        // Convolver under test.
+        let conv = new ConvolverNode(
+            context, {disableNormalization: true, buffer: response});
+        srcMerger.connect(conv);
+
+        // Splitter to get individual channels of the convolver output so we can
+        // feed them (eventually) to the context in the right set of channels.
+        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        conv.connect(splitter);
+
+        // Reference graph consists of a delays node to simulate the response of
+        // the convolver.  (The convolver response is designed this way.)
+        let delay = new Array(4);
+        for (let k = 0; k < delay.length; ++k) {
+          delay[k] = new DelayNode(context, {
+            delayTime: (k + 1) / context.sampleRate,
+            channelCount: 1,
+            channelCountMode: 'explicit'
+          });
+        }
+
+        // Gain node to mix the sources to stereo in the desired way.  (Could be
+        // done in the delay node, but let's keep the mixing separated from the
+        // functionality.)
+        let gainMixer = new GainNode(
+            context, {channelCount: 2, channelCountMode: 'explicit'});
+        srcMerger.connect(gainMixer);
+
+        // Splitter to extract the channels of the reference signal.
+        let refSplitter =
+            new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        gainMixer.connect(refSplitter);
+
+        // Connect the left channel to the first two nodes and the right channel
+        // to the second two as required for "true" stereo matrix response.
+        for (let k = 0; k < 2; ++k) {
+          refSplitter.connect(delay[k], 0, 0);
+          refSplitter.connect(delay[k + 2], 1, 0);
+        }
+
+        // Gain nodes to sum the responses to stereo
+        let gain = new Array(2);
+        for (let k = 0; k < gain.length; ++k) {
+          gain[k] = new GainNode(context, {
+            channelCount: 1,
+            channelCountMode: 'explicit',
+            channelInterpretation: 'discrete'
+          });
+        }
+
+        delay[0].connect(gain[0]);
+        delay[2].connect(gain[0]);
+        delay[1].connect(gain[1]);
+        delay[3].connect(gain[1]);
+
+        // Final merger to bring back the individual channels from the convolver
+        // and the reference in the right order for the destination.
+        let finalMerger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+
+        // First two channels are for the convolver output, and the next two are
+        // for the reference.
+        splitter.connect(finalMerger, 0, 0);
+        splitter.connect(finalMerger, 1, 1);
+        gain[0].connect(finalMerger, 0, 2);
+        gain[1].connect(finalMerger, 0, 3);
+
+        finalMerger.connect(context.destination);
+
+        // Start the sources at last.
+        for (let k = 0; k < src.length; ++k) {
+          src[k].start();
+        }
+
+        return context.startRendering().then(audioBuffer => {
+          // Extract the various channels out
+          let actual0 = audioBuffer.getChannelData(0);
+          let actual1 = audioBuffer.getChannelData(1);
+          let expected0 = audioBuffer.getChannelData(2);
+          let expected1 = audioBuffer.getChannelData(3);
+
+          // Verify that each output channel of the convolver matches
+          // the delayed signal from the reference
+          should(actual0, options.prefix + ': Channel 0')
+              .beEqualToArray(expected0);
+          should(actual1, options.prefix + ': Channel 1')
+              .beEqualToArray(expected1);
+        });
+      }
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/convolver-setBuffer-null.html b/webaudio/the-audio-api/the-convolvernode-interface/convolver-setBuffer-null.html
new file mode 100644
index 0000000..d35b8ec
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/convolver-setBuffer-null.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      convolver-setBuffer-null.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define('test', function(task, should) {
+        let context = new AudioContext();
+        let conv = context.createConvolver();
+
+        should(() => {
+          conv.buffer = null;
+        }, 'Setting ConvolverNode impulse response buffer to null').notThrow();
+        should(conv.buffer === null, 'conv.buffer === null').beTrue();
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-convolvernode-interface/ctor-convolver.html b/webaudio/the-audio-api/the-convolvernode-interface/ctor-convolver.html
new file mode 100644
index 0000000..cf81833
--- /dev/null
+++ b/webaudio/the-audio-api/the-convolvernode-interface/ctor-convolver.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: Convolver
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'ConvolverNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'ConvolverNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'clamped-max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(
+            should, node, prefix,
+            [{name: 'normalize', value: true}, {name: 'buffer', value: null}]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'ConvolverNode', {
+          channelCount:
+              {value: 2, isFixed: true, errorType: 'NotSupportedError'},
+          channelCountMode: {
+            value: 'clamped-max',
+            isFixed: true,
+            errorType: 'NotSupportedError'
+          },
+        });
+        task.done();
+      });
+
+      audit.define('nullable buffer', (task, should) => {
+        let node;
+        let options = {buffer: null};
+
+        should(
+            () => {
+              node = new ConvolverNode(context, options);
+            },
+            'node1 = new ConvolverNode(c, ' + JSON.stringify(options))
+            .notThrow();
+
+        should(node.buffer, 'node1.buffer').beEqualTo(null);
+
+        task.done();
+      });
+
+      audit.define('construct with options', (task, should) => {
+        let buf = context.createBuffer(1, 1, context.sampleRate);
+        let options = {buffer: buf, disableNormalization: false};
+
+        let message =
+            'node = new ConvolverNode(c, ' + JSON.stringify(options) + ')';
+
+        let node;
+        should(() => {
+          node = new ConvolverNode(context, options);
+        }, message).notThrow();
+
+        should(node instanceof ConvolverNode, 'node1 instanceOf ConvolverNode')
+            .beEqualTo(true);
+        should(node.buffer === options.buffer, 'node1.buffer === <buf>')
+            .beEqualTo(true);
+        should(node.normalize, 'node1.normalize')
+            .beEqualTo(!options.disableNormalization);
+
+        options.buffer = null;
+        options.disableNormalization = true;
+
+        message =
+            'node2 = new ConvolverNode(, ' + JSON.stringify(options) + ')';
+
+        should(() => {
+          node = new ConvolverNode(context, options);
+        }, message).notThrow();
+        should(node.buffer, 'node2.buffer').beEqualTo(null);
+        should(node.normalize, 'node2.normalize')
+            .beEqualTo(!options.disableNormalization);
+
+        options.disableNormalization = false;
+        message = 'node3 = new ConvolverNode(context, ' +
+            JSON.stringify(options) + ')';
+
+        should(() => {
+          node = new ConvolverNode(context, options);
+        }, message).notThrow();
+        should(node.buffer, 'node3.buffer').beEqualTo(null);
+        should(node.normalize, 'node3.normalize')
+            .beEqualTo(!options.disableNormalization);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/ctor-delay.html b/webaudio/the-audio-api/the-delaynode-interface/ctor-delay.html
new file mode 100644
index 0000000..e7ccefc
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/ctor-delay.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: Delay
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'DelayNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'DelayNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(
+            should, node, prefix, [{name: 'delayTime', value: 0}]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'DelayNode');
+        task.done();
+      });
+
+      audit.define('constructor options', (task, should) => {
+        let node;
+        let options = {
+          delayTime: 0.5,
+          maxDelayTime: 1.5,
+        };
+
+        should(
+            () => {
+              node = new DelayNode(context, options);
+            },
+            'node1 = new DelayNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        should(node.delayTime.value, 'node1.delayTime.value')
+            .beEqualTo(options.delayTime);
+        should(node.delayTime.maxValue, 'node1.delayTime.maxValue')
+            .beEqualTo(options.maxDelayTime);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-default-delay.html b/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-default-delay.html
new file mode 100644
index 0000000..ef526c9
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-default-delay.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      delaynode-max-default-delay.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/delay-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'DelayNode with delay set to default maximum delay'
+          },
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                1, sampleRate * renderLengthSeconds, sampleRate);
+            let toneBuffer = createToneBuffer(
+                context, 20, 20 * toneLengthSeconds, sampleRate);  // 20Hz tone
+
+            let bufferSource = context.createBufferSource();
+            bufferSource.buffer = toneBuffer;
+
+            let delay = context.createDelay();
+            delayTimeSeconds = 1;
+            delay.delayTime.value = delayTimeSeconds;
+
+            bufferSource.connect(delay);
+            delay.connect(context.destination);
+            bufferSource.start(0);
+
+            context.startRendering()
+                .then(buffer => checkDelayedResult(buffer, toneBuffer, should))
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-nondefault-delay.html b/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-nondefault-delay.html
new file mode 100644
index 0000000..3be0725
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-nondefault-delay.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      delaynode-max-nondefault-delay.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/delay-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'DelayNode with delay set to non-default maximum delay'
+          },
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                1, sampleRate * renderLengthSeconds, sampleRate);
+            let toneBuffer = createToneBuffer(
+                context, 20, 20 * toneLengthSeconds, sampleRate);  // 20Hz tone
+
+            let bufferSource = context.createBufferSource();
+            bufferSource.buffer = toneBuffer;
+
+            let maxDelay = 1.5;
+            let delay = context.createDelay(maxDelay);
+            delayTimeSeconds = maxDelay;
+            delay.delayTime.value = delayTimeSeconds;
+
+            bufferSource.connect(delay);
+            delay.connect(context.destination);
+            bufferSource.start(0);
+
+            context.startRendering()
+                .then(buffer => checkDelayedResult(buffer, toneBuffer, should))
+                .then(() => task.done());
+            ;
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelay.html b/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelay.html
new file mode 100644
index 0000000..a43ceeb
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelay.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      delaynode-maxdelay.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/delay-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'Basic functionality of DelayNode with a non-default max delay time'
+          },
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                1, sampleRate * renderLengthSeconds, sampleRate);
+            let toneBuffer = createToneBuffer(
+                context, 20, 20 * toneLengthSeconds, sampleRate);  // 20Hz tone
+
+            let bufferSource = context.createBufferSource();
+            bufferSource.buffer = toneBuffer;
+
+            // Create a delay node with an explicit max delay time (greater than
+            // the default of 1 second).
+            let delay = context.createDelay(2);
+            // Set the delay time to a value greater than the default max delay
+            // so we can verify the delay is working for this case.
+            delayTimeSeconds = 1.5;
+            delay.delayTime.value = delayTimeSeconds;
+
+            bufferSource.connect(delay);
+            delay.connect(context.destination);
+            bufferSource.start(0);
+
+            context.startRendering()
+                .then(buffer => checkDelayedResult(buffer, toneBuffer, should))
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelaylimit.html b/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelaylimit.html
new file mode 100644
index 0000000..3f4c020
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelaylimit.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      delaynode-maxdelaylimit.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/delay-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'Tests attribute and maximum allowed delay of DelayNode'
+          },
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                1, sampleRate * renderLengthSeconds, sampleRate);
+            let toneBuffer = createToneBuffer(
+                context, 20, 20 * toneLengthSeconds, sampleRate);  // 20Hz tone
+
+            let bufferSource = context.createBufferSource();
+            bufferSource.buffer = toneBuffer;
+
+            window.context = context;
+            should(() => context.createDelay(180)).throw();
+            should(() => context.createDelay(0)).throw();
+            should(() => context.createDelay(-1)).throw();
+            should(() => context.createDelay(NaN)).throw();
+            ;
+            let delay = context.createDelay(179);
+            delay.delayTime.value = delayTimeSeconds;
+            window.delay = delay;
+            should(
+                delay.delayTime.value,
+                'delay.delayTime.value = ' + delayTimeSeconds)
+                .beEqualTo(delayTimeSeconds);
+
+            bufferSource.connect(delay);
+            delay.connect(context.destination);
+            bufferSource.start(0);
+
+            context.startRendering()
+                .then(buffer => checkDelayedResult(buffer, toneBuffer, should))
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/delaynode-scheduling.html b/webaudio/the-audio-api/the-delaynode-interface/delaynode-scheduling.html
new file mode 100644
index 0000000..af6c549
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/delaynode-scheduling.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      delaynode-scheduling.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/delay-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'DelayNode delayTime parameter can be scheduled at a given time'
+          },
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                1, sampleRate * renderLengthSeconds, sampleRate);
+            let toneBuffer = createToneBuffer(
+                context, 20, 20 * toneLengthSeconds, sampleRate);  // 20Hz tone
+
+            let bufferSource = context.createBufferSource();
+            bufferSource.buffer = toneBuffer;
+
+            let delay = context.createDelay();
+
+            // Schedule delay time at time zero.
+            delay.delayTime.setValueAtTime(delayTimeSeconds, 0);
+
+            bufferSource.connect(delay);
+            delay.connect(context.destination);
+            bufferSource.start(0);
+
+            context.startRendering()
+                .then(buffer => checkDelayedResult(buffer, toneBuffer, should))
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/delaynode.html b/webaudio/the-audio-api/the-delaynode-interface/delaynode.html
new file mode 100644
index 0000000..da508e4
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/delaynode.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      delaynode.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/delay-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'Tests attribute and basic functionality of DelayNode'
+          },
+          function(task, should) {
+
+            // Create offline audio context.
+            let context = new OfflineAudioContext(
+                1, sampleRate * renderLengthSeconds, sampleRate);
+            let toneBuffer = createToneBuffer(
+                context, 20, 20 * toneLengthSeconds, sampleRate);  // 20Hz tone
+
+            let bufferSource = context.createBufferSource();
+            bufferSource.buffer = toneBuffer;
+
+            let delay = context.createDelay();
+
+            window.delay = delay;
+            should(delay.numberOfInputs, 'delay.numberOfInputs').beEqualTo(1);
+            should(delay.numberOfOutputs, 'delay.numberOfOutputs').beEqualTo(1);
+            should(delay.delayTime.defaultValue, 'delay.delayTime.defaultValue')
+                .beEqualTo(0.0);
+            should(delay.delayTime.value, 'delay.delayTime.value')
+                .beEqualTo(0.0);
+
+            delay.delayTime.value = delayTimeSeconds;
+            should(
+                delay.delayTime.value,
+                'delay.delayTime.value = ' + delayTimeSeconds)
+                .beEqualTo(delayTimeSeconds);
+
+            bufferSource.connect(delay);
+            delay.connect(context.destination);
+            bufferSource.start(0);
+
+            context.startRendering()
+                .then(buffer => checkDelayedResult(buffer, toneBuffer, should))
+                .then(task.done.bind(task));
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/idl-test.html b/webaudio/the-audio-api/the-delaynode-interface/idl-test.html
deleted file mode 100644
index 5922b96..0000000
--- a/webaudio/the-audio-api/the-delaynode-interface/idl-test.html
+++ /dev/null
@@ -1,156 +0,0 @@
-<!DOCTYPE html>
-<html class="a">
-<head>
-<title>DelayNode IDL Test</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/idlharness.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/webaudio/js/helpers.js"></script>
-<style type="text/css">
-    #event-target-idl,
-    #base-audio-context-idl,
-    #audio-node-idl,
-    #audio-param-idl
-    { visibility:hidden; height: 0px;}
-  </style>
-</head>
-<body class="a">
-
-   <pre id="event-target-idl">interface EventTarget {
-  void addEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
-  void removeEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
-  boolean dispatchEvent(Event event);
-};
-
-/*
-callback interface EventListener {
-  void handleEvent(Event event);
-};
-*/
-// Callback interfaces are not supported yet, but that's ok
-interface EventListener {};
-</pre>
-
-   <pre id="base-audio-context-idl">callback DecodeErrorCallback = void (DOMException error);
-
-callback DecodeSuccessCallback = void (AudioBuffer decodedData);
-
-interface BaseAudioContext : EventTarget {
-    readonly        attribute AudioDestinationNode destination;
-    readonly        attribute float                sampleRate;
-    readonly        attribute double               currentTime;
-    readonly        attribute AudioListener        listener;
-    readonly        attribute AudioContextState    state;
-    readonly        attribute double               baseLatency;
-    Promise<void>          resume ();
-                    attribute EventHandler         onstatechange;
-    AudioBuffer            createBuffer (unsigned long numberOfChannels, unsigned long length, float sampleRate);
-    Promise<AudioBuffer>   decodeAudioData (ArrayBuffer audioData, optional DecodeSuccessCallback successCallback, optional DecodeErrorCallback errorCallback);
-    AudioBufferSourceNode  createBufferSource ();
-    ConstantSourceNode     createConstantSource ();
-    ScriptProcessorNode    createScriptProcessor (optional unsigned long bufferSize = 0
-              , optional unsigned long numberOfInputChannels = 2
-              , optional unsigned long numberOfOutputChannels = 2
-              );
-    AnalyserNode           createAnalyser ();
-    GainNode               createGain ();
-    DelayNode              createDelay (optional double maxDelayTime);
-    BiquadFilterNode       createBiquadFilter ();
-    IIRFilterNode          createIIRFilter (sequence<double> feedforward, sequence<double> feedback);
-    WaveShaperNode         createWaveShaper ();
-    PannerNode             createPanner ();
-    StereoPannerNode       createStereoPanner ();
-    ConvolverNode          createConvolver ();
-    ChannelSplitterNode    createChannelSplitter (optional unsigned long numberOfOutputs = 6
-              );
-    ChannelMergerNode      createChannelMerger (optional unsigned long numberOfInputs = 6
-              );
-    DynamicsCompressorNode createDynamicsCompressor ();
-    OscillatorNode         createOscillator ();
-    PeriodicWave           createPeriodicWave (Float32Array real, Float32Array imag, optional PeriodicWaveConstraints constraints);
-};</pre>
-
-   <pre id="audio-node-idl">enum ChannelCountMode {
-    "max",
-    "clamped-max",
-    "explicit"
-};
-
-enum ChannelInterpretation {
-    "speakers",
-    "discrete"
-};
-
-interface AudioNode : EventTarget {
-
-    void connect(AudioNode destination, optional unsigned long output = 0, optional unsigned long input = 0);
-    void connect(AudioParam destination, optional unsigned long output = 0);
-    void disconnect(optional unsigned long output = 0);
-
-    readonly attribute BaseAudioContext context;
-    readonly attribute unsigned long numberOfInputs;
-    readonly attribute unsigned long numberOfOutputs;
-
-    // Channel up-mixing and down-mixing rules for all inputs.
-    attribute unsigned long channelCount;
-    attribute ChannelCountMode channelCountMode;
-    attribute ChannelInterpretation channelInterpretation;
-
-};</pre>
-
-   <pre id="audio-param-idl">interface AudioParam {
-
-                    attribute float value;
-    readonly        attribute float defaultValue;
-    readonly        attribute float minValue;
-    readonly        attribute float maxValue;
-
-    // Parameter automation.
-    void setValueAtTime(float value, double startTime);
-    void linearRampToValueAtTime(float value, double endTime);
-    void exponentialRampToValueAtTime(float value, double endTime);
-
-    // Exponentially approach the target value with a rate having the given time constant.
-    void setTargetAtTime(float target, double startTime, double timeConstant);
-
-    // Sets an array of arbitrary parameter values starting at time for the given duration.
-    // The number of values will be scaled to fit into the desired duration.
-    void setValueCurveAtTime(Float32Array values, double startTime, double duration);
-
-    // Cancels all scheduled parameter changes with times greater than or equal to startTime.
-    void cancelScheduledValues(double startTime);
-
-};</pre>
-
-<pre id="delay-node-idl">dictionary DelayOptions : AudioNodeOptions {
-             double maxDelayTime = 1;
-             double delayTime = 0;
-};
-
-[Constructor(BaseAudioContext context, optional DelayOptions options)]
-interface DelayNode : AudioNode {
-
-    readonly attribute AudioParam delayTime;
-
-};</pre>
-
-  <div id="log"></div>
-
-  <script>
-(function() {
-  var idl_array = new IdlArray();
-  idl_array.add_untested_idls(document.getElementById("event-target-idl").textContent);
-  idl_array.add_untested_idls(document.getElementById("base-audio-context-idl").textContent);
-  idl_array.add_untested_idls(document.getElementById("audio-node-idl").textContent);
-  idl_array.add_untested_idls(document.getElementById("audio-param-idl").textContent);
-  idl_array.add_idls(document.getElementById("delay-node-idl").textContent);
-
-  delay_node = (new AudioContext).createDelay();
-
-  idl_array.add_objects({DelayNode: ["delay_node"]});
-  idl_array.test();
-})();
-  </script>
-</body>
-</html>
diff --git a/webaudio/the-audio-api/the-delaynode-interface/no-dezippering.html b/webaudio/the-audio-api/the-delaynode-interface/no-dezippering.html
new file mode 100644
index 0000000..7857cf1
--- /dev/null
+++ b/webaudio/the-audio-api/the-delaynode-interface/no-dezippering.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test DelayNode Has No Dezippering
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // The sample rate must be a power of two to avoid any round-off errors in
+      // computing when to suspend a context on a rendering quantum boundary.
+      // Otherwise this is pretty arbitrary.
+      let sampleRate = 16384;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test0', description: 'Test DelayNode has no dezippering'},
+          (task, should) => {
+            let context = new OfflineAudioContext(1, sampleRate, sampleRate);
+
+            // Simple integer ramp for testing delay node
+            let buffer = new AudioBuffer(
+                {length: context.length, sampleRate: context.sampleRate});
+            let rampData = buffer.getChannelData(0);
+            for (let k = 0; k < rampData.length; ++k) {
+              rampData[k] = k + 1;
+            }
+
+            // |delay0Frame| is the initial delay in frames. |delay1Frame| is
+            // the new delay in frames.  These must be integers.
+            let delay0Frame = 64;
+            let delay1Frame = 16;
+
+            let src = new AudioBufferSourceNode(context, {buffer: buffer});
+            let delay = new DelayNode(
+                context, {delayTime: delay0Frame / context.sampleRate});
+
+            src.connect(delay).connect(context.destination);
+
+            // After a render quantum, change the delay to |delay1Frame|.
+            context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate)
+                .then(() => {
+                  delay.delayTime.value = delay1Frame / context.sampleRate;
+                })
+                .then(() => context.resume());
+
+            src.start();
+            context.startRendering()
+                .then(renderedBuffer => {
+                  let renderedData = renderedBuffer.getChannelData(0);
+
+                  // The first |delay0Frame| frames should be zero.
+                  should(
+                      renderedData.slice(0, delay0Frame),
+                      'output[0:' + (delay0Frame - 1) + ']')
+                      .beConstantValueOf(0);
+
+                  // Now we have the ramp should show up from the delay.
+                  let ramp0 =
+                      new Float32Array(RENDER_QUANTUM_FRAMES - delay0Frame);
+                  for (let k = 0; k < ramp0.length; ++k) {
+                    ramp0[k] = rampData[k];
+                  }
+
+                  should(
+                      renderedData.slice(delay0Frame, RENDER_QUANTUM_FRAMES),
+                      'output[' + delay0Frame + ':' +
+                          (RENDER_QUANTUM_FRAMES - 1) + ']')
+                      .beEqualToArray(ramp0);
+
+                  // After one rendering quantum, the delay is changed to
+                  // |delay1Frame|.
+                  let ramp1 =
+                      new Float32Array(context.length - RENDER_QUANTUM_FRAMES);
+                  for (let k = 0; k < ramp1.length; ++k) {
+                    // ramp1[k] = 1 + k + RENDER_QUANTUM_FRAMES - delay1Frame;
+                    ramp1[k] =
+                        rampData[k + RENDER_QUANTUM_FRAMES - delay1Frame];
+                  }
+                  should(
+                      renderedData.slice(RENDER_QUANTUM_FRAMES),
+                      'output[' + RENDER_QUANTUM_FRAMES + ':]')
+                      .beEqualToArray(ramp1);
+                })
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {label: 'test1', description: 'Test value setter and setValueAtTime'},
+          (task, should) => {
+            testWithAutomation(should, {prefix: ''}).then(() => task.done());
+          });
+
+      audit.define(
+          {label: 'test2', description: 'Test value setter and modulation'},
+          (task, should) => {
+            testWithAutomation(should, {
+              prefix: 'With modulation: ',
+              modulator: true
+            }).then(() => task.done());
+          });
+
+      // Compare .value setter with setValueAtTime, Optionally allow modulation
+      // of |delayTime|.
+      function testWithAutomation(should, options) {
+        let prefix = options.prefix;
+        // Channel 0 is the output of delay node using the setter and channel 1
+        // is the output using setValueAtTime.
+        let context = new OfflineAudioContext(2, sampleRate, sampleRate);
+
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+        merger.connect(context.destination);
+
+        let src = new OscillatorNode(context);
+
+        // |delay0Frame| is the initial delay value in frames. |delay1Frame| is
+        // the new delay in frames. The values here are constrained only by the
+        // constraints for a DelayNode.  These are pretty arbitrary except we
+        // wanted them to be fractional so as not be on a frame boundary to
+        // test interpolation compared with |setValueAtTime()|..
+        let delay0Frame = 3.1;
+        let delay1Frame = 47.2;
+
+        let delayTest = new DelayNode(
+            context, {delayTime: delay0Frame / context.sampleRate});
+        let delayRef = new DelayNode(
+            context, {delayTime: delay0Frame / context.sampleRate});
+
+        src.connect(delayTest).connect(merger, 0, 0);
+        src.connect(delayRef).connect(merger, 0, 1);
+
+        if (options.modulator) {
+          // Fairly arbitrary modulation of the delay time, with a peak
+          // variation of 10 ms.
+          let mod = new OscillatorNode(context, {frequency: 1000});
+          let modGain = new GainNode(context, {gain: .01});
+          mod.connect(modGain);
+          modGain.connect(delayTest.delayTime);
+          modGain.connect(delayRef.delayTime);
+          mod.start();
+        }
+
+        // The time at which the delay time of |delayTest| node will be
+        // changed.  This MUST be on a render quantum boundary, but is
+        // otherwise arbitrary.
+        let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;
+
+        // Schedule the delay change on |delayRef| and also apply the value
+        // setter for |delayTest| at |changeTime|.
+        delayRef.delayTime.setValueAtTime(
+            delay1Frame / context.sampleRate, changeTime);
+        context.suspend(changeTime)
+            .then(() => {
+              delayTest.delayTime.value = delay1Frame / context.sampleRate;
+            })
+            .then(() => context.resume());
+
+        src.start();
+
+        return context.startRendering().then(renderedBuffer => {
+          let actual = renderedBuffer.getChannelData(0);
+          let expected = renderedBuffer.getChannelData(1);
+
+          let match = should(actual, prefix + '.value setter output')
+                          .beEqualToArray(expected);
+          should(
+              match,
+              prefix + '.value setter output matches setValueAtTime output')
+              .beTrue();
+        });
+      }
+
+      audit.run();
+
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-dynamicscompressornode-interface/ctor-dynamicscompressor.html b/webaudio/the-audio-api/the-dynamicscompressornode-interface/ctor-dynamicscompressor.html
new file mode 100644
index 0000000..799c187
--- /dev/null
+++ b/webaudio/the-audio-api/the-dynamicscompressornode-interface/ctor-dynamicscompressor.html
@@ -0,0 +1,196 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: DynamicsCompressor
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'DynamicsCompressorNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node =
+            testDefaultConstructor(should, 'DynamicsCompressorNode', context, {
+              prefix: prefix,
+              numberOfInputs: 1,
+              numberOfOutputs: 1,
+              channelCount: 2,
+              channelCountMode: 'clamped-max',
+              channelInterpretation: 'speakers'
+            });
+
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'threshold', value: -24}, {name: 'knee', value: 30},
+          {name: 'ratio', value: 12}, {name: 'reduction', value: 0},
+          {name: 'attack', value: Math.fround(0.003)},
+          {name: 'release', value: 0.25}
+        ]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        // Can't use testAudioNodeOptions because the constraints for this node
+        // are not supported there.
+
+        // Array of test options to be run.  Each entry is a dictionary where
+        // |testAttribute| is the name of the attribute to be tested,
+        // |testValue| is the value to be used, and |expectedErrorType| is the
+        // error type if the test is expected to throw an error.
+        // |expectedErrorType| should be set only if the test does throw.
+        let testOptions = [
+          // Test channel count
+          {
+            testAttribute: 'channelCount',
+            testValue: 1,
+          },
+          {
+            testAttribute: 'channelCount',
+            testValue: 2,
+          },
+          {
+            testAttribute: 'channelCount',
+            testValue: 0,
+            expectedErrorType: 'NotSupportedError'
+          },
+          {
+            testAttribute: 'channelCount',
+            testValue: 3,
+            expectedErrorType: 'NotSupportedError'
+          },
+          {
+            testAttribute: 'channelCount',
+            testValue: 99,
+            expectedErrorType: 'NotSupportedError'
+          },
+          // Test channel count mode
+          {
+            testAttribute: 'channelCountMode',
+            testValue: 'clamped-max',
+          },
+          {
+            testAttribute: 'channelCountMode',
+            testValue: 'explicit',
+          },
+          {
+            testAttribute: 'channelCountMode',
+            testValue: 'max',
+            expectedErrorType: 'NotSupportedError'
+          },
+          {
+            testAttribute: 'channelCountMode',
+            testValue: 'foobar',
+            expectedErrorType: 'TypeError'
+          },
+          // Test channel interpretation
+          {
+            testAttribute: 'channelInterpretation',
+            testValue: 'speakers',
+          },
+          {
+            testAttribute: 'channelInterpretation',
+            testValue: 'discrete',
+          },
+          {
+            testAttribute: 'channelInterpretation',
+            testValue: 'foobar',
+            expectedErrorType: 'TypeError'
+          }
+        ];
+
+        testOptions.forEach((option) => {
+          let nodeOptions = {};
+          nodeOptions[option.testAttribute] = option.testValue;
+
+          testNode(should, context, {
+            nodeOptions: nodeOptions,
+            testAttribute: option.testAttribute,
+            expectedValue: option.testValue,
+            expectedErrorType: option.expectedErrorType
+          });
+        });
+
+        task.done();
+      });
+
+      audit.define('constructor with options', (task, should) => {
+        let node;
+        let options =
+            {threshold: -33, knee: 15, ratio: 7, attack: 0.625, release: 0.125};
+
+        should(
+            () => {
+              node = new DynamicsCompressorNode(context, options);
+            },
+            'node1 = new DynamicsCompressorNode(c, ' + JSON.stringify(options) +
+                ')')
+            .notThrow();
+        should(
+            node instanceof DynamicsCompressorNode,
+            'node1 instanceof DynamicsCompressorNode')
+            .beEqualTo(true);
+
+        should(node.threshold.value, 'node1.threshold.value')
+            .beEqualTo(options.threshold);
+        should(node.knee.value, 'node1.knee.value').beEqualTo(options.knee);
+        should(node.ratio.value, 'node1.ratio.value').beEqualTo(options.ratio);
+        should(node.attack.value, 'node1.attack.value')
+            .beEqualTo(options.attack);
+        should(node.release.value, 'node1.release.value')
+            .beEqualTo(options.release);
+
+        should(node.channelCount, 'node1.channelCount').beEqualTo(2);
+        should(node.channelCountMode, 'node1.channelCountMode')
+            .beEqualTo('clamped-max');
+        should(node.channelInterpretation, 'node1.channelInterpretation')
+            .beEqualTo('speakers');
+
+        task.done();
+      });
+
+      audit.run();
+
+      // Test possible options for DynamicsCompressor constructor.
+      function testNode(should, context, options) {
+        // Node to be tested
+        let node;
+
+        let createNodeFunction = () => {
+          return () => node =
+                     new DynamicsCompressorNode(context, options.nodeOptions);
+        };
+
+        let message = 'new DynamicsCompressorNode(c, ' +
+            JSON.stringify(options.nodeOptions) + ')';
+
+        if (options.expectedErrorType) {
+          should(createNodeFunction(), message)
+              .throw(options.expectedErrorType);
+        } else {
+          should(createNodeFunction(), message).notThrow();
+          should(node[options.testAttribute], 'node.' + options.testAttribute)
+              .beEqualTo(options.expectedValue);
+        }
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-dynamicscompressornode-interface/dynamicscompressor-basic.html b/webaudio/the-audio-api/the-dynamicscompressornode-interface/dynamicscompressor-basic.html
new file mode 100644
index 0000000..6c60201
--- /dev/null
+++ b/webaudio/the-audio-api/the-dynamicscompressornode-interface/dynamicscompressor-basic.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      dynamicscompressor-basic.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+      let context;
+      let compressor;
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'Basic tests for DynamicsCompressorNode API'
+          },
+          function(task, should) {
+
+            context = new AudioContext();
+            compressor = context.createDynamicsCompressor();
+
+            should(compressor.threshold.value, 'compressor.threshold.value')
+                .beEqualTo(-24);
+            should(compressor.knee.value, 'compressor.knee.value')
+                .beEqualTo(30);
+            should(compressor.ratio.value, 'compressor.ratio.value')
+                .beEqualTo(12);
+            should(compressor.attack.value, 'compressor.attack.value')
+                .beEqualTo(Math.fround(0.003));
+            should(compressor.release.value, 'compressor.release.value')
+                .beEqualTo(0.25);
+            should(typeof compressor.reduction, 'typeof compressor.reduction')
+                .beEqualTo('number');
+            should(compressor.reduction, 'compressor.reduction').beEqualTo(0);
+
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-gainnode-interface/ctor-gain.html b/webaudio/the-audio-api/the-gainnode-interface/ctor-gain.html
new file mode 100644
index 0000000..dec273e
--- /dev/null
+++ b/webaudio/the-audio-api/the-gainnode-interface/ctor-gain.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: Gain
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'GainNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'GainNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(should, node, prefix, [{name: 'gain', value: 1}]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'GainNode');
+        task.done();
+      });
+
+      audit.define('constructor with options', (task, should) => {
+        let node;
+        let options = {
+          gain: -2,
+        };
+
+        should(
+            () => {
+              node = new GainNode(context, options);
+            },
+            'node1 = new GainNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node instanceof GainNode, 'node1 instanceof GainNode')
+            .beEqualTo(true);
+
+        should(node.gain.value, 'node1.gain.value').beEqualTo(options.gain);
+
+        should(node.channelCount, 'node1.channelCount').beEqualTo(2);
+        should(node.channelCountMode, 'node1.channelCountMode')
+            .beEqualTo('max');
+        should(node.channelInterpretation, 'node1.channelInterpretation')
+            .beEqualTo('speakers');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-gainnode-interface/gain-basic.html b/webaudio/the-audio-api/the-gainnode-interface/gain-basic.html
new file mode 100644
index 0000000..de2ba11
--- /dev/null
+++ b/webaudio/the-audio-api/the-gainnode-interface/gain-basic.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!--
+Verifies GainNode attributes and their type.
+-->
+<html>
+  <head>
+    <title>
+      gain-basic.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define('test', function(task, should) {
+        // Create audio context.
+        let context = new AudioContext();
+
+        // Create gain node.
+        let gainNode = context.createGain();
+
+        should(
+            gainNode.gain instanceof AudioParam,
+            'gainNode.gain instanceof AudioParam')
+            .beTrue();
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-gainnode-interface/gain.html b/webaudio/the-audio-api/the-gainnode-interface/gain.html
new file mode 100644
index 0000000..cff609d
--- /dev/null
+++ b/webaudio/the-audio-api/the-gainnode-interface/gain.html
@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Basic GainNode Functionality 
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Tests that GainNode is properly scaling the gain.  We'll render 11
+      // notes, starting at a gain of 1.0, decreasing in gain by 0.1.  The 11th
+      // note will be of gain 0.0, so it should be silent (at the end in the
+      // rendered output).
+
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 44100.0;
+      let bufferDurationSeconds = 0.125;
+      let numberOfNotes = 11;
+      let noteSpacing = bufferDurationSeconds +
+          0.020;  // leave 20ms of silence between each "note"
+      let lengthInSeconds = numberOfNotes * noteSpacing;
+
+      let context = 0;
+      let sinWaveBuffer = 0;
+
+      // Create a stereo AudioBuffer of duration |lengthInSeconds| consisting of
+      // a pure sine wave with the given |frequency|.  Both channels contain the
+      // same data.
+      function createSinWaveBuffer(lengthInSeconds, frequency) {
+        let audioBuffer =
+            context.createBuffer(2, lengthInSeconds * sampleRate, sampleRate);
+
+        let n = audioBuffer.length;
+        let channelL = audioBuffer.getChannelData(0);
+        let channelR = audioBuffer.getChannelData(1);
+
+        for (let i = 0; i < n; ++i) {
+          channelL[i] = Math.sin(frequency * 2.0 * Math.PI * i / sampleRate);
+          channelR[i] = channelL[i];
+        }
+
+        return audioBuffer;
+      }
+
+      function playNote(time, gain, merger) {
+        let source = context.createBufferSource();
+        source.buffer = sinWaveBuffer;
+
+        let gainNode = context.createGain();
+        gainNode.gain.value = gain;
+
+        let sourceSplitter = context.createChannelSplitter(2);
+        let gainSplitter = context.createChannelSplitter(2);
+
+        // Split the stereo channels from the source output and the gain output
+        // and merge them into the desired channels of the merger.
+        source.connect(gainNode).connect(gainSplitter);
+        source.connect(sourceSplitter);
+
+        gainSplitter.connect(merger, 0, 0);
+        gainSplitter.connect(merger, 1, 1);
+        sourceSplitter.connect(merger, 0, 2);
+        sourceSplitter.connect(merger, 1, 3);
+
+        source.start(time);
+      }
+
+      audit.define(
+          {label: 'create context', description: 'Create context for test'},
+          function(task, should) {
+            // Create offline audio context.
+            context = new OfflineAudioContext(
+                4, sampleRate * lengthInSeconds, sampleRate);
+            task.done();
+          });
+
+      audit.define(
+          {label: 'test', description: 'GainNode functionality'},
+          function(task, should) {
+            let merger = new ChannelMergerNode(
+                context, {numberOfInputs: context.destination.channelCount});
+            merger.connect(context.destination);
+
+            // Create a buffer for a short "note".
+            sinWaveBuffer = createSinWaveBuffer(bufferDurationSeconds, 880.0);
+
+            let startTimes = [];
+            let gainValues = [];
+
+            // Render 11 notes, starting at a gain of 1.0, decreasing in gain by
+            // 0.1. The last note will be of gain 0.0, so shouldn't be
+            // perceptible in the rendered output.
+            for (let i = 0; i < numberOfNotes; ++i) {
+              let time = i * noteSpacing;
+              let gain = 1.0 - i / (numberOfNotes - 1);
+              startTimes.push(time);
+              gainValues.push(gain);
+              playNote(time, gain, merger);
+            }
+
+            context.startRendering()
+                .then(buffer => {
+                  let actual0 = buffer.getChannelData(0);
+                  let actual1 = buffer.getChannelData(1);
+                  let reference0 = buffer.getChannelData(2);
+                  let reference1 = buffer.getChannelData(3);
+
+                  // It's ok to a frame too long since the sine pulses are
+                  // followed by silence.
+                  let bufferDurationFrames =
+                      Math.ceil(bufferDurationSeconds * context.sampleRate);
+
+                  // Apply the gains to the reference signal.
+                  for (let k = 0; k < startTimes.length; ++k) {
+                    // It's ok to be a frame early because the sine pulses are
+                    // preceded by silence.
+                    let startFrame =
+                        Math.floor(startTimes[k] * context.sampleRate);
+                    let gain = gainValues[k];
+                    for (let n = 0; n < bufferDurationFrames; ++n) {
+                      reference0[startFrame + n] *= gain;
+                      reference1[startFrame + n] *= gain;
+                    }
+                  }
+
+                  // Verify the channels are clsoe to the reference.
+                  should(actual0, 'Left output from gain node')
+                      .beCloseToArray(
+                          reference0, {relativeThreshold: 1.1908e-7});
+                  should(actual1, 'Right output from gain node')
+                      .beCloseToArray(
+                          reference1, {relativeThreshold: 1.1908e-7});
+
+                  // Test the SNR too for both channels.
+                  let snr0 = 10 * Math.log10(computeSNR(actual0, reference0));
+                  let snr1 = 10 * Math.log10(computeSNR(actual1, reference1));
+                  should(snr0, 'Left SNR (in dB)')
+                      .beGreaterThanOrEqualTo(148.69);
+                  should(snr1, 'Right SNR (in dB)')
+                      .beGreaterThanOrEqualTo(148.69);
+                })
+                .then(() => task.done());
+            ;
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-gainnode-interface/idl-test.html b/webaudio/the-audio-api/the-gainnode-interface/idl-test.html
deleted file mode 100644
index 7ad8bc9..0000000
--- a/webaudio/the-audio-api/the-gainnode-interface/idl-test.html
+++ /dev/null
@@ -1,154 +0,0 @@
-<!DOCTYPE html>
-<html class="a">
-<head>
-<title>GainNode IDL Test</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/idlharness.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/webaudio/js/helpers.js"></script>
-<style type="text/css">
-    #event-target-idl,
-    #base-audio-context-idl,
-    #audio-node-idl,
-    #audio-param-idl
-    { visibility:hidden; height: 0px;}
-  </style>
-</head>
-<body class="a">
-
-   <pre id="event-target-idl">interface EventTarget {
-  void addEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
-  void removeEventListener(DOMString type, EventListener? callback, optional boolean capture = false);
-  boolean dispatchEvent(Event event);
-};
-
-/*
-callback interface EventListener {
-  void handleEvent(Event event);
-};
-*/
-// Callback interfaces are not supported yet, but that's ok
-interface EventListener {};
-</pre>
-
-   <pre id="base-audio-context-idl">callback DecodeErrorCallback = void (DOMException error);
-callback DecodeSuccessCallback = void (AudioBuffer decodedData);
-
-interface BaseAudioContext : EventTarget {
-    readonly        attribute AudioDestinationNode destination;
-    readonly        attribute float                sampleRate;
-    readonly        attribute double               currentTime;
-    readonly        attribute AudioListener        listener;
-    readonly        attribute AudioContextState    state;
-    readonly        attribute double               baseLatency;
-    Promise<void>          resume ();
-                    attribute EventHandler         onstatechange;
-    AudioBuffer            createBuffer (unsigned long numberOfChannels, unsigned long length, float sampleRate);
-    Promise<AudioBuffer>   decodeAudioData (ArrayBuffer audioData, optional DecodeSuccessCallback successCallback, optional DecodeErrorCallback errorCallback);
-    AudioBufferSourceNode  createBufferSource ();
-    ConstantSourceNode     createConstantSource ();
-    ScriptProcessorNode    createScriptProcessor (optional unsigned long bufferSize = 0
-              , optional unsigned long numberOfInputChannels = 2
-              , optional unsigned long numberOfOutputChannels = 2
-              );
-    AnalyserNode           createAnalyser ();
-    GainNode               createGain ();
-    DelayNode              createDelay (optional double maxDelayTime);
-    BiquadFilterNode       createBiquadFilter ();
-    IIRFilterNode          createIIRFilter (sequence<double> feedforward, sequence<double> feedback);
-    WaveShaperNode         createWaveShaper ();
-    PannerNode             createPanner ();
-    StereoPannerNode       createStereoPanner ();
-    ConvolverNode          createConvolver ();
-    ChannelSplitterNode    createChannelSplitter (optional unsigned long numberOfOutputs = 6
-              );
-    ChannelMergerNode      createChannelMerger (optional unsigned long numberOfInputs = 6
-              );
-    DynamicsCompressorNode createDynamicsCompressor ();
-    OscillatorNode         createOscillator ();
-    PeriodicWave           createPeriodicWave (Float32Array real, Float32Array imag, optional PeriodicWaveConstraints constraints);
-};</pre>
-
-   <pre id="audio-node-idl">enum ChannelCountMode {
-    "max",
-    "clamped-max",
-    "explicit"
-};
-
-enum ChannelInterpretation {
-    "speakers",
-    "discrete"
-};
-
-interface AudioNode : EventTarget {
-
-    void connect(AudioNode destination, optional unsigned long output = 0, optional unsigned long input = 0);
-    void connect(AudioParam destination, optional unsigned long output = 0);
-    void disconnect(optional unsigned long output = 0);
-
-    readonly attribute BaseAudioContext context;
-    readonly attribute unsigned long numberOfInputs;
-    readonly attribute unsigned long numberOfOutputs;
-
-    // Channel up-mixing and down-mixing rules for all inputs.
-    attribute unsigned long channelCount;
-    attribute ChannelCountMode channelCountMode;
-    attribute ChannelInterpretation channelInterpretation;
-
-};</pre>
-
-   <pre id="audio-param-idl">interface AudioParam {
-
-                    attribute float value;
-    readonly        attribute float defaultValue;
-    readonly        attribute float minValue;
-    readonly        attribute float maxValue;
-
-    // Parameter automation.
-    void setValueAtTime(float value, double startTime);
-    void linearRampToValueAtTime(float value, double endTime);
-    void exponentialRampToValueAtTime(float value, double endTime);
-
-    // Exponentially approach the target value with a rate having the given time constant.
-    void setTargetAtTime(float target, double startTime, double timeConstant);
-
-    // Sets an array of arbitrary parameter values starting at time for the given duration.
-    // The number of values will be scaled to fit into the desired duration.
-    void setValueCurveAtTime(Float32Array values, double startTime, double duration);
-
-    // Cancels all scheduled parameter changes with times greater than or equal to startTime.
-    void cancelScheduledValues(double startTime);
-
-};</pre>
-
-<pre id="gain-node-idl">dictionary GainOptions : AudioNodeOptions {
-             float gain = 1.0;
-};
-
-[Constructor(BaseAudioContext context, optional GainOptions options)]
-interface GainNode : AudioNode {
-
-    readonly attribute AudioParam gain;
-
-};</pre>
-
-  <div id="log"></div>
-
-  <script>
-(function() {
-  var idl_array = new IdlArray();
-  idl_array.add_untested_idls(document.getElementById("event-target-idl").textContent);
-  idl_array.add_untested_idls(document.getElementById("base-audio-context-idl").textContent);
-  idl_array.add_untested_idls(document.getElementById("audio-node-idl").textContent);
-  idl_array.add_untested_idls(document.getElementById("audio-param-idl").textContent);
-  idl_array.add_idls(document.getElementById("gain-node-idl").textContent);
-
-  gain_node = (new AudioContext).createGain();
-
-  idl_array.add_objects({GainNode: ["gain_node"]});
-  idl_array.test();
-})();
-  </script>
-</body>
-</html>
diff --git a/webaudio/the-audio-api/the-gainnode-interface/no-dezippering.html b/webaudio/the-audio-api/the-gainnode-interface/no-dezippering.html
new file mode 100644
index 0000000..6326d00
--- /dev/null
+++ b/webaudio/the-audio-api/the-gainnode-interface/no-dezippering.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Gain Dezippering Test: Dezippering Removed
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test0', description: 'Dezippering of GainNode removed'},
+          (task, should) => {
+            // Only need a few frames to verify that dezippering has been
+            // removed from the GainNode.  Sample rate is pretty arbitrary.
+            let context = new OfflineAudioContext(1, 1024, 16000);
+
+            // Send a unit source to the gain node so we can measure the effect
+            // of the gain node.
+            let src = new ConstantSourceNode(context, {offset: 1});
+            let g = new GainNode(context, {gain: 1});
+            src.connect(g).connect(context.destination);
+
+            context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate)
+                .then(() => {
+                  g.gain.value = .5;
+                })
+                .then(() => context.resume());
+
+            src.start();
+
+            context.startRendering()
+                .then(audio => {
+                  let c = audio.getChannelData(0);
+
+                  // If dezippering has been removed, the gain output should
+                  // instantly jump at frame 128 to 0.5.
+                  should(c.slice(0, 128), 'output[0:127]').beConstantValueOf(1);
+                  should(c.slice(128), 'output[128:]').beConstantValueOf(0.5);
+                })
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'test2',
+            description: 'Compare value setter and setValueAtTime'
+          },
+          (task, should) => {
+            testWithAutomation(should, {prefix: ''}).then(() => task.done());
+          });
+
+      audit.define(
+          {label: 'test3', description: 'Automation effects'},
+          (task, should) => {
+            testWithAutomation(should, {
+              prefix: 'With modulation: ',
+              modulator: true
+            }).then(() => task.done());
+          });
+
+      audit.run();
+
+      function testWithAutomation(should, options) {
+        // Sample rate must be a power of two to eliminate round-off in
+        // computing the time at render quantum boundaries.
+        let context = new OfflineAudioContext(2, 1024, 16384);
+        let merger = new ChannelMergerNode(context, {numberOfChannels: 2});
+        merger.connect(context.destination);
+
+        let src = new OscillatorNode(context);
+        let gainTest = new GainNode(context);
+        let gainRef = new GainNode(context);
+
+        src.connect(gainTest).connect(merger, 0, 0);
+        src.connect(gainRef).connect(merger, 0, 1);
+
+        if (options.modulator) {
+          let mod = new OscillatorNode(context, {frequency: 1000});
+          let modGain = new GainNode(context);
+          mod.connect(modGain);
+          modGain.connect(gainTest.gain);
+          modGain.connect(gainRef.gain);
+          mod.start();
+        }
+
+        // Change the gains. Must do the change on a render boundary!
+        let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;
+        let newGain = .3;
+
+        gainRef.gain.setValueAtTime(newGain, changeTime);
+        context.suspend(changeTime)
+            .then(() => gainTest.gain.value = newGain)
+            .then(() => context.resume());
+
+        src.start();
+
+        return context.startRendering().then(audio => {
+          let actual = audio.getChannelData(0);
+          let expected = audio.getChannelData(1);
+
+          // The values using the .value setter must be identical to the
+          // values using setValueAtTime.
+          let match = should(actual, options.prefix + '.value setter output')
+                          .beEqualToArray(expected);
+
+          should(
+              match,
+              options.prefix +
+                  '.value setter output matches setValueAtTime output')
+              .beTrue();
+        });
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-iirfilternode-interface/ctor-iirfilter.html b/webaudio/the-audio-api/the-iirfilternode-interface/ctor-iirfilter.html
new file mode 100644
index 0000000..bb89512
--- /dev/null
+++ b/webaudio/the-audio-api/the-iirfilternode-interface/ctor-iirfilter.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: IIRFilter
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'IIRFilterNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'IIRFilterNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers',
+          constructorOptions: {feedforward: [1], feedback: [1, -.9]}
+        });
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(
+            should, context, 'IIRFilterNode',
+            {additionalOptions: {feedforward: [1, 1], feedback: [1, .5]}});
+        task.done();
+      });
+
+      audit.define('constructor options', (task, should) => {
+        let node;
+
+        let options = {feedback: [1, .5]};
+        should(
+            () => {
+              node = new IIRFilterNode(context, options);
+            },
+            'node = new IIRFilterNode(, ' + JSON.stringify(options) + ')')
+            .throw('TypeError');
+
+        options = {feedforward: [1, 0.5]};
+        should(
+            () => {
+              node = new IIRFilterNode(context, options);
+            },
+            'node = new IIRFilterNode(c, ' + JSON.stringify(options) + ')')
+            .throw('TypeError');
+
+        task.done();
+      });
+
+      // Test functionality of constructor.  This is needed because we have no
+      // way of determining if the filter coefficients were were actually set
+      // appropriately.
+
+      // TODO(rtoy): This functionality test should be moved out to a separate
+      // file.
+      audit.define('functionality', (task, should) => {
+        let options = {feedback: [1, .5], feedforward: [1, 1]};
+
+        // Create two-channel offline context; sample rate and length are fairly
+        // arbitrary.  Channel 0 contains the test output and channel 1 contains
+        // the expected output.
+        let sampleRate = 48000;
+        let renderLength = 0.125;
+        let testContext =
+            new OfflineAudioContext(2, renderLength * sampleRate, sampleRate);
+
+        // The test node uses the constructor.  The reference node creates the
+        // same filter but uses the old factory method.
+        let testNode = new IIRFilterNode(testContext, options);
+        let refNode = testContext.createIIRFilter(
+            Float32Array.from(options.feedforward),
+            Float32Array.from(options.feedback));
+
+        let source = testContext.createOscillator();
+        source.connect(testNode);
+        source.connect(refNode);
+
+        let merger = testContext.createChannelMerger(
+            testContext.destination.channelCount);
+
+        testNode.connect(merger, 0, 0);
+        refNode.connect(merger, 0, 1);
+
+        merger.connect(testContext.destination);
+
+        source.start();
+        testContext.startRendering()
+            .then(function(resultBuffer) {
+              let actual = resultBuffer.getChannelData(0);
+              let expected = resultBuffer.getChannelData(1);
+
+              // The output from the two channels should be exactly equal
+              // because exactly the same IIR filter should have been created.
+              should(actual, 'Output of filter using new IIRFilter(...)')
+                  .beEqualToArray(expected);
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter-basic.html b/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter-basic.html
new file mode 100644
index 0000000..da36d58
--- /dev/null
+++ b/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter-basic.html
@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Basic IIRFilterNode Properties
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+      let testFrames = 100;
+
+      // Global context that can be used by the individual tasks. It must be
+      // defined by the initialize task.
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        should(() => {
+          context = new OfflineAudioContext(1, testFrames, sampleRate);
+        }, 'Initialize context for testing').notThrow();
+        task.done();
+      });
+
+      audit.define('existence', (task, should) => {
+        should(context.createIIRFilter, 'context.createIIRFilter').exist();
+        task.done();
+      });
+
+      audit.define('parameters', (task, should) => {
+        // Create a really simple IIR filter. Doesn't much matter what.
+        let coef = Float32Array.from([1]);
+
+        let f = context.createIIRFilter(coef, coef);
+
+        should(f.numberOfInputs, 'numberOfInputs').beEqualTo(1);
+        should(f.numberOfOutputs, 'numberOfOutputs').beEqualTo(1);
+        should(f.channelCountMode, 'channelCountMode').beEqualTo('max');
+        should(f.channelInterpretation, 'channelInterpretation')
+            .beEqualTo('speakers');
+
+        task.done();
+      });
+
+      audit.define('exceptions-createIIRFilter', (task, should) => {
+        should(function() {
+          // Two args are required.
+          context.createIIRFilter();
+        }, 'createIIRFilter()').throw('TypeError');
+
+        should(function() {
+          // Two args are required.
+          context.createIIRFilter(new Float32Array(1));
+        }, 'createIIRFilter(new Float32Array(1))').throw('TypeError');
+
+        should(function() {
+          // null is not valid
+          context.createIIRFilter(null, null);
+        }, 'createIIRFilter(null, null)').throw('TypeError');
+
+        should(function() {
+          // There has to be at least one coefficient.
+          context.createIIRFilter([], []);
+        }, 'createIIRFilter([], [])').throw('NotSupportedError');
+
+        should(function() {
+          // There has to be at least one coefficient.
+          context.createIIRFilter([1], []);
+        }, 'createIIRFilter([1], [])').throw('NotSupportedError');
+
+        should(function() {
+          // There has to be at least one coefficient.
+          context.createIIRFilter([], [1]);
+        }, 'createIIRFilter([], [1])').throw('NotSupportedError');
+
+        should(
+            function() {
+              // Max allowed size for the coefficient arrays.
+              let fb = new Float32Array(20);
+              fb[0] = 1;
+              context.createIIRFilter(fb, fb);
+            },
+            'createIIRFilter(new Float32Array(20), new Float32Array(20))')
+            .notThrow();
+
+        should(
+            function() {
+              // Max allowed size for the feedforward coefficient array.
+              let coef = new Float32Array(21);
+              coef[0] = 1;
+              context.createIIRFilter(coef, [1]);
+            },
+            'createIIRFilter(new Float32Array(21), [1])')
+            .throw('NotSupportedError');
+
+        should(
+            function() {
+              // Max allowed size for the feedback coefficient array.
+              let coef = new Float32Array(21);
+              coef[0] = 1;
+              context.createIIRFilter([1], coef);
+            },
+            'createIIRFilter([1], new Float32Array(21))')
+            .throw('NotSupportedError');
+
+        should(
+            function() {
+              // First feedback coefficient can't be 0.
+              context.createIIRFilter([1], new Float32Array(2));
+            },
+            'createIIRFilter([1], new Float32Array(2))')
+            .throw('InvalidStateError');
+
+        should(
+            function() {
+              // feedforward coefficients can't all be zero.
+              context.createIIRFilter(new Float32Array(10), [1]);
+            },
+            'createIIRFilter(new Float32Array(10), [1])')
+            .throw('InvalidStateError');
+
+        should(function() {
+          // Feedback coefficients must be finite.
+          context.createIIRFilter([1], [1, Infinity, NaN]);
+        }, 'createIIRFilter([1], [1, NaN, Infinity])').throw('TypeError');
+
+        should(function() {
+          // Feedforward coefficients must be finite.
+          context.createIIRFilter([1, Infinity, NaN], [1]);
+        }, 'createIIRFilter([1, NaN, Infinity], [1])').throw('TypeError');
+
+        should(function() {
+          // Test that random junk in the array is converted to NaN.
+          context.createIIRFilter([1, 'abc', []], [1]);
+        }, 'createIIRFilter([1, \'abc\', []], [1])').throw('TypeError');
+
+        task.done();
+      });
+
+      audit.define('exceptions-getFrequencyData', (task, should) => {
+        // Create a really simple IIR filter. Doesn't much matter what.
+        let coef = Float32Array.from([1]);
+
+        let f = context.createIIRFilter(coef, coef);
+
+        should(
+            function() {
+              // frequencyHz can't be null.
+              f.getFrequencyResponse(
+                  null, new Float32Array(1), new Float32Array(1));
+            },
+            'getFrequencyResponse(null, new Float32Array(1), new Float32Array(1))')
+            .throw('TypeError');
+
+        should(
+            function() {
+              // magResponse can't be null.
+              f.getFrequencyResponse(
+                  new Float32Array(1), null, new Float32Array(1));
+            },
+            'getFrequencyResponse(new Float32Array(1), null, new Float32Array(1))')
+            .throw('TypeError');
+
+        should(
+            function() {
+              // phaseResponse can't be null.
+              f.getFrequencyResponse(
+                  new Float32Array(1), new Float32Array(1), null);
+            },
+            'getFrequencyResponse(new Float32Array(1), new Float32Array(1), null)')
+            .throw('TypeError');
+
+        should(
+            function() {
+              // magResponse array must the same length as frequencyHz
+              f.getFrequencyResponse(
+                  new Float32Array(10), new Float32Array(1),
+                  new Float32Array(20));
+            },
+            'getFrequencyResponse(new Float32Array(10), new Float32Array(1), new Float32Array(20))')
+            .throw('InvalidAccessError');
+
+        should(
+            function() {
+              // phaseResponse array must be the same length as frequencyHz
+              f.getFrequencyResponse(
+                  new Float32Array(10), new Float32Array(20),
+                  new Float32Array(1));
+            },
+            'getFrequencyResponse(new Float32Array(10), new Float32Array(20), new Float32Array(1))')
+            .throw('InvalidAccessError');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter-getFrequencyResponse.html b/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter-getFrequencyResponse.html
new file mode 100644
index 0000000..c98555f
--- /dev/null
+++ b/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter-getFrequencyResponse.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test IIRFilter getFrequencyResponse() functionality
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/biquad-filters.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+      // Some short duration; we're not actually looking at the rendered output.
+      let testDurationSec = 0.01;
+
+      // Number of frequency samples to take.
+      let numberOfFrequencies = 1000;
+
+      let audit = Audit.createTaskRunner();
+
+
+      // Compute a set of linearly spaced frequencies.
+      function createFrequencies(nFrequencies, sampleRate) {
+        let frequencies = new Float32Array(nFrequencies);
+        let nyquist = sampleRate / 2;
+        let freqDelta = nyquist / nFrequencies;
+
+        for (let k = 0; k < nFrequencies; ++k) {
+          frequencies[k] = k * freqDelta;
+        }
+
+        return frequencies;
+      }
+
+      audit.define('1-pole IIR', (task, should) => {
+        let context = new OfflineAudioContext(
+            1, testDurationSec * sampleRate, sampleRate);
+
+        let iir = context.createIIRFilter([1], [1, -0.9]);
+        let frequencies =
+            createFrequencies(numberOfFrequencies, context.sampleRate);
+
+        let iirMag = new Float32Array(numberOfFrequencies);
+        let iirPhase = new Float32Array(numberOfFrequencies);
+        let trueMag = new Float32Array(numberOfFrequencies);
+        let truePhase = new Float32Array(numberOfFrequencies);
+
+        // The IIR filter is
+        //   H(z) = 1/(1 - 0.9*z^(-1)).
+        //
+        // The frequency response is
+        //   H(exp(j*w)) = 1/(1 - 0.9*exp(-j*w)).
+        //
+        // Thus, the magnitude is
+        //   |H(exp(j*w))| = 1/sqrt(1.81-1.8*cos(w)).
+        //
+        // The phase is
+        //   arg(H(exp(j*w)) = atan(0.9*sin(w)/(.9*cos(w)-1))
+
+        let frequencyScale = Math.PI / (sampleRate / 2);
+
+        for (let k = 0; k < frequencies.length; ++k) {
+          let omega = frequencyScale * frequencies[k];
+          trueMag[k] = 1 / Math.sqrt(1.81 - 1.8 * Math.cos(omega));
+          truePhase[k] =
+              Math.atan(0.9 * Math.sin(omega) / (0.9 * Math.cos(omega) - 1));
+        }
+
+        iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
+
+        // Thresholds were experimentally determined.
+        should(iirMag, '1-pole IIR Magnitude Response')
+            .beCloseToArray(trueMag, {absoluteThreshold: 2.8611e-6});
+        should(iirPhase, '1-pole IIR Phase Response')
+            .beCloseToArray(truePhase, {absoluteThreshold: 1.7882e-7});
+
+        task.done();
+      });
+
+      audit.define('compare IIR and biquad', (task, should) => {
+        // Create an IIR filter equivalent to the biquad filter. Compute the
+        // frequency response for both and verify that they are the same.
+        let context = new OfflineAudioContext(
+            1, testDurationSec * sampleRate, sampleRate);
+
+        let biquad = context.createBiquadFilter();
+        let coef = createFilter(
+            biquad.type, biquad.frequency.value / (context.sampleRate / 2),
+            biquad.Q.value, biquad.gain.value);
+
+        let iir = context.createIIRFilter(
+            [coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]);
+
+        let frequencies =
+            createFrequencies(numberOfFrequencies, context.sampleRate);
+        let biquadMag = new Float32Array(numberOfFrequencies);
+        let biquadPhase = new Float32Array(numberOfFrequencies);
+        let iirMag = new Float32Array(numberOfFrequencies);
+        let iirPhase = new Float32Array(numberOfFrequencies);
+
+        biquad.getFrequencyResponse(frequencies, biquadMag, biquadPhase);
+        iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
+
+        // Thresholds were experimentally determined.
+        should(iirMag, 'IIR Magnitude Response').beCloseToArray(biquadMag, {
+          absoluteThreshold: 2.7419e-5
+        });
+        should(iirPhase, 'IIR Phase Response').beCloseToArray(biquadPhase, {
+          absoluteThreshold: 2.7657e-5
+        });
+
+        task.done();
+      });
+
+      audit.define(
+          {
+            label: 'getFrequencyResponse',
+            description: 'Test out-of-bounds frequency values'
+          },
+          (task, should) => {
+            let context = new OfflineAudioContext(1, 1, sampleRate);
+            let filter = new IIRFilterNode(
+                context, {feedforward: [1], feedback: [1, -.9]});
+
+            // Frequencies to test.  These are all outside the valid range of
+            // frequencies of 0 to Nyquist.
+            let freq = new Float32Array(2);
+            freq[0] = -1;
+            freq[1] = context.sampleRate / 2 + 1;
+
+            let mag = new Float32Array(freq.length);
+            let phase = new Float32Array(freq.length);
+
+            filter.getFrequencyResponse(freq, mag, phase);
+
+            // Verify that the returned magnitude and phase entries are alL NaN
+            // since the frequencies are outside the valid range
+            for (let k = 0; k < mag.length; ++k) {
+              should(mag[k],
+                  'Magnitude response at frequency ' + freq[k])
+                  .beNaN();
+            }
+
+            for (let k = 0; k < phase.length; ++k) {
+              should(phase[k],
+                  'Phase response at frequency ' + freq[k])
+                  .beNaN();
+            }
+
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter.html b/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter.html
new file mode 100644
index 0000000..aa38a6b
--- /dev/null
+++ b/webaudio/the-audio-api/the-iirfilternode-interface/iirfilter.html
@@ -0,0 +1,572 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Basic IIRFilterNode Operation
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/biquad-filters.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 24000;
+      let testDurationSec = 0.25;
+      let testFrames = testDurationSec * sampleRate;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('coefficient-normalization', (task, should) => {
+        // Test that the feedback coefficients are normalized.  Do this be
+        // creating two IIRFilterNodes.  One has normalized coefficients, and
+        // one doesn't.  Compute the difference and make sure they're the same.
+        let context = new OfflineAudioContext(2, testFrames, sampleRate);
+
+        // Use a simple impulse as the source.
+        let buffer = context.createBuffer(1, 1, sampleRate);
+        buffer.getChannelData(0)[0] = 1;
+        let source = context.createBufferSource();
+        source.buffer = buffer;
+
+        // Gain node for computing the difference between the filters.
+        let gain = context.createGain();
+        gain.gain.value = -1;
+
+        // The IIR filters.  Use a common feedforward array.
+        let ff = [1];
+
+        let fb1 = [1, .9];
+
+        let fb2 = new Float64Array(2);
+        // Scale the feedback coefficients by an arbitrary factor.
+        let coefScaleFactor = 2;
+        for (let k = 0; k < fb2.length; ++k) {
+          fb2[k] = coefScaleFactor * fb1[k];
+        }
+
+        let iir1;
+        let iir2;
+
+        should(function() {
+          iir1 = context.createIIRFilter(ff, fb1);
+        }, 'createIIRFilter with normalized coefficients').notThrow();
+
+        should(function() {
+          iir2 = context.createIIRFilter(ff, fb2);
+        }, 'createIIRFilter with unnormalized coefficients').notThrow();
+
+        // Create the graph.  The output of iir1 (normalized coefficients) is
+        // channel 0, and the output of iir2 (unnormalized coefficients), with
+        // appropriate scaling, is channel 1.
+        let merger = context.createChannelMerger(2);
+        source.connect(iir1);
+        source.connect(iir2);
+        iir1.connect(merger, 0, 0);
+        iir2.connect(gain);
+
+        // The gain for the gain node should be set to compensate for the
+        // scaling of the coefficients.  Since iir2 has scaled the coefficients
+        // by coefScaleFactor, the output is reduced by the same factor, so
+        // adjust the gain to scale the output of iir2 back up.
+        gain.gain.value = coefScaleFactor;
+        gain.connect(merger, 0, 1);
+
+        merger.connect(context.destination);
+
+        source.start();
+
+        // Rock and roll!
+
+        context.startRendering()
+            .then(function(result) {
+              // Find the max amplitude of the result, which should be near
+              // zero.
+              let iir1Data = result.getChannelData(0);
+              let iir2Data = result.getChannelData(1);
+
+              // Threshold isn't exactly zero because the arithmetic is done
+              // differently between the IIRFilterNode and the BiquadFilterNode.
+              should(
+                  iir2Data,
+                  'Output of IIR filter with unnormalized coefficients')
+                  .beCloseToArray(iir1Data, {absoluteThreshold: 2.1958e-38});
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('one-zero', (task, should) => {
+        // Create a simple 1-zero filter and compare with the expected output.
+        let context = new OfflineAudioContext(1, testFrames, sampleRate);
+
+        // Use a simple impulse as the source
+        let buffer = context.createBuffer(1, 1, sampleRate);
+        buffer.getChannelData(0)[0] = 1;
+        let source = context.createBufferSource();
+        source.buffer = buffer;
+
+        // The filter is y(n) = 0.5*(x(n) + x(n-1)), a simple 2-point moving
+        // average.  This is rather arbitrary; keep it simple.
+
+        let iir = context.createIIRFilter([0.5, 0.5], [1]);
+
+        // Create the graph
+        source.connect(iir);
+        iir.connect(context.destination);
+
+        // Rock and roll!
+        source.start();
+
+        context.startRendering()
+            .then(function(result) {
+              let actual = result.getChannelData(0);
+              let expected = new Float64Array(testFrames);
+              // The filter is a simple 2-point moving average of an impulse, so
+              // the first two values are non-zero and the rest are zero.
+              expected[0] = 0.5;
+              expected[1] = 0.5;
+              should(actual, 'IIR 1-zero output').beCloseToArray(expected, {
+                absoluteThreshold: 0
+              });
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('one-pole', (task, should) => {
+        // Create a simple 1-pole filter and compare with the expected output.
+
+        // The filter is y(n) + c*y(n-1)= x(n).  The analytical response is
+        // (-c)^n, so choose a suitable number of frames to run the test for
+        // where the output isn't flushed to zero.
+        let c = 0.9;
+        let eps = 1e-20;
+        let duration = Math.floor(Math.log(eps) / Math.log(Math.abs(c)));
+        let context = new OfflineAudioContext(1, duration, sampleRate);
+
+        // Use a simple impulse as the source
+        let buffer = context.createBuffer(1, 1, sampleRate);
+        buffer.getChannelData(0)[0] = 1;
+        let source = context.createBufferSource();
+        source.buffer = buffer;
+
+        let iir = context.createIIRFilter([1], [1, c]);
+
+        // Create the graph
+        source.connect(iir);
+        iir.connect(context.destination);
+
+        // Rock and roll!
+        source.start();
+
+        context.startRendering()
+            .then(function(result) {
+              let actual = result.getChannelData(0);
+              let expected = new Float64Array(actual.length);
+
+              // The filter is a simple 1-pole filter: y(n) = -c*y(n-k)+x(n),
+              // with an impulse as the input.
+              expected[0] = 1;
+              for (k = 1; k < testFrames; ++k) {
+                expected[k] = -c * expected[k - 1];
+              }
+
+              // Threshold isn't exactly zero due to round-off in the
+              // single-precision IIRFilterNode computations versus the
+              // double-precision Javascript computations.
+              should(actual, 'IIR 1-pole output').beCloseToArray(expected, {
+                absoluteThreshold: 2.7657e-8
+              });
+            })
+            .then(() => task.done());
+      });
+
+      // Return a function suitable for use as a defineTask function.  This
+      // function creates an IIRFilterNode equivalent to the specified
+      // BiquadFilterNode and compares the outputs.  The outputs from the two
+      // filters should be virtually identical.
+      function testWithBiquadFilter(filterType, errorThreshold, snrThreshold) {
+        return (task, should) => {
+          let context = new OfflineAudioContext(2, testFrames, sampleRate);
+
+          // Use a constant (step function) as the source
+          let buffer = createConstantBuffer(context, testFrames, 1);
+          let source = context.createBufferSource();
+          source.buffer = buffer;
+
+
+          // Create the biquad.  Choose some rather arbitrary values for Q and
+          // gain for the biquad so that the shelf filters aren't identical.
+          let biquad = context.createBiquadFilter();
+          biquad.type = filterType;
+          biquad.Q.value = 10;
+          biquad.gain.value = 10;
+
+          // Create the equivalent IIR Filter node by computing the coefficients
+          // of the given biquad filter type.
+          let nyquist = sampleRate / 2;
+          let coef = createFilter(
+              filterType, biquad.frequency.value / nyquist, biquad.Q.value,
+              biquad.gain.value);
+
+          let iir = context.createIIRFilter(
+              [coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]);
+
+          let merger = context.createChannelMerger(2);
+          // Create the graph
+          source.connect(biquad);
+          source.connect(iir);
+
+          biquad.connect(merger, 0, 0);
+          iir.connect(merger, 0, 1);
+
+          merger.connect(context.destination);
+
+          // Rock and roll!
+          source.start();
+
+          context.startRendering()
+              .then(function(result) {
+                // Find the max amplitude of the result, which should be near
+                // zero.
+                let expected = result.getChannelData(0);
+                let actual = result.getChannelData(1);
+
+                // On MacOSX, WebAudio uses an optimized Biquad implementation
+                // that is different from the implementation used for Linux and
+                // Windows.  This will cause the output to differ, even if the
+                // threshold passes.  Thus, only print out a very small number
+                // of elements of the array where we have tested that they are
+                // consistent.
+                should(actual, 'IIRFilter for Biquad ' + filterType)
+                    .beCloseToArray(expected, errorThreshold);
+
+                let snr = 10 * Math.log10(computeSNR(actual, expected));
+                should(snr, 'SNR for IIRFIlter for Biquad ' + filterType)
+                    .beGreaterThanOrEqualTo(snrThreshold);
+              })
+              .then(() => task.done());
+        };
+      }
+
+      // Thresholds here are experimentally determined.
+      let biquadTestConfigs = [
+        {
+          filterType: 'lowpass',
+          snrThreshold: 91.221,
+          errorThreshold: {relativeThreshold: 4.9834e-5}
+        },
+        {
+          filterType: 'highpass',
+          snrThreshold: 105.4590,
+          errorThreshold: {absoluteThreshold: 2.9e-6, relativeThreshold: 3e-5}
+        },
+        {
+          filterType: 'bandpass',
+          snrThreshold: 104.060,
+          errorThreshold: {absoluteThreshold: 2e-7, relativeThreshold: 8.7e-4}
+        },
+        {
+          filterType: 'notch',
+          snrThreshold: 91.312,
+          errorThreshold: {absoluteThreshold: 0, relativeThreshold: 4.22e-5}
+        },
+        {
+          filterType: 'allpass',
+          snrThreshold: 91.319,
+          errorThreshold: {absoluteThreshold: 0, relativeThreshold: 4.31e-5}
+        },
+        {
+          filterType: 'lowshelf',
+          snrThreshold: 90.609,
+          errorThreshold: {absoluteThreshold: 0, relativeThreshold: 2.98e-5}
+        },
+        {
+          filterType: 'highshelf',
+          snrThreshold: 103.159,
+          errorThreshold: {absoluteThreshold: 0, relativeThreshold: 1.24e-5}
+        },
+        {
+          filterType: 'peaking',
+          snrThreshold: 91.504,
+          errorThreshold: {absoluteThreshold: 0, relativeThreshold: 5.05e-5}
+        }
+      ];
+
+      // Create a set of tasks based on biquadTestConfigs.
+      for (k = 0; k < biquadTestConfigs.length; ++k) {
+        let config = biquadTestConfigs[k];
+        let name = k + ': ' + config.filterType;
+        audit.define(
+            name,
+            testWithBiquadFilter(
+                config.filterType, config.errorThreshold, config.snrThreshold));
+      }
+
+      audit.define('multi-channel', (task, should) => {
+        // Multi-channel test.  Create a biquad filter and the equivalent IIR
+        // filter.  Filter the same multichannel signal and compare the results.
+        let nChannels = 3;
+        let context =
+            new OfflineAudioContext(nChannels, testFrames, sampleRate);
+
+        // Create a set of oscillators as the multi-channel source.
+        let source = [];
+
+        for (k = 0; k < nChannels; ++k) {
+          source[k] = context.createOscillator();
+          source[k].type = 'sawtooth';
+          // The frequency of the oscillator is pretty arbitrary, but each
+          // oscillator should have a different frequency.
+          source[k].frequency.value = 100 + k * 100;
+        }
+
+        let merger = context.createChannelMerger(3);
+
+        let biquad = context.createBiquadFilter();
+
+        // Create the equivalent IIR Filter node.
+        let nyquist = sampleRate / 2;
+        let coef = createFilter(
+            biquad.type, biquad.frequency.value / nyquist, biquad.Q.value,
+            biquad.gain.value);
+        let fb = [1, coef.a1, coef.a2];
+        let ff = [coef.b0, coef.b1, coef.b2];
+
+        let iir = context.createIIRFilter(ff, fb);
+        // Gain node to compute the difference between the IIR and biquad
+        // filter.
+        let gain = context.createGain();
+        gain.gain.value = -1;
+
+        // Create the graph.
+        for (k = 0; k < nChannels; ++k)
+          source[k].connect(merger, 0, k);
+
+        merger.connect(biquad);
+        merger.connect(iir);
+        iir.connect(gain);
+        biquad.connect(context.destination);
+        gain.connect(context.destination);
+
+        for (k = 0; k < nChannels; ++k)
+          source[k].start();
+
+        context.startRendering()
+            .then(function(result) {
+              let errorThresholds = [3.7671e-5, 3.0071e-5, 2.6241e-5];
+
+              // Check the difference signal on each channel
+              for (channel = 0; channel < result.numberOfChannels; ++channel) {
+                // Find the max amplitude of the result, which should be near
+                // zero.
+                let data = result.getChannelData(channel);
+                let maxError =
+                    data.reduce(function(reducedValue, currentValue) {
+                      return Math.max(reducedValue, Math.abs(currentValue));
+                    });
+
+                should(
+                    maxError,
+                    'Max difference between IIR and Biquad on channel ' +
+                        channel)
+                    .beLessThanOrEqualTo(errorThresholds[channel]);
+              }
+
+            })
+            .then(() => task.done());
+      });
+
+      // Apply an IIRFilter to the given input signal.
+      //
+      // IIR filter in the time domain is
+      //
+      //   y[n] = sum(ff[k]*x[n-k], k, 0, M) - sum(fb[k]*y[n-k], k, 1, N)
+      //
+      function iirFilter(input, feedforward, feedback) {
+        // For simplicity, create an x buffer that contains the input, and a y
+        // buffer that contains the output.  Both of these buffers have an
+        // initial work space to implement the initial memory of the filter.
+        let workSize = Math.max(feedforward.length, feedback.length);
+        let x = new Float32Array(input.length + workSize);
+
+        // Float64 because we want to match the implementation that uses doubles
+        // to minimize roundoff.
+        let y = new Float64Array(input.length + workSize);
+
+        // Copy the input over.
+        for (let k = 0; k < input.length; ++k)
+          x[k + feedforward.length] = input[k];
+
+        // Run the filter
+        for (let n = 0; n < input.length; ++n) {
+          let index = n + workSize;
+          let yn = 0;
+          for (let k = 0; k < feedforward.length; ++k)
+            yn += feedforward[k] * x[index - k];
+          for (let k = 0; k < feedback.length; ++k)
+            yn -= feedback[k] * y[index - k];
+
+          y[index] = yn;
+        }
+
+        return y.slice(workSize).map(Math.fround);
+      }
+
+      // Cascade the two given biquad filters to create one IIR filter.
+      function cascadeBiquads(f1Coef, f2Coef) {
+        // The biquad filters are:
+        //
+        // f1 = (b10 + b11/z + b12/z^2)/(1 + a11/z + a12/z^2);
+        // f2 = (b20 + b21/z + b22/z^2)/(1 + a21/z + a22/z^2);
+        //
+        // To cascade them, multiply the two transforms together to get a fourth
+        // order IIR filter.
+
+        let numProduct = [
+          f1Coef.b0 * f2Coef.b0, f1Coef.b0 * f2Coef.b1 + f1Coef.b1 * f2Coef.b0,
+          f1Coef.b0 * f2Coef.b2 + f1Coef.b1 * f2Coef.b1 + f1Coef.b2 * f2Coef.b0,
+          f1Coef.b1 * f2Coef.b2 + f1Coef.b2 * f2Coef.b1, f1Coef.b2 * f2Coef.b2
+        ];
+
+        let denProduct = [
+          1, f2Coef.a1 + f1Coef.a1,
+          f2Coef.a2 + f1Coef.a1 * f2Coef.a1 + f1Coef.a2,
+          f1Coef.a1 * f2Coef.a2 + f1Coef.a2 * f2Coef.a1, f1Coef.a2 * f2Coef.a2
+        ];
+
+        return {
+          ff: numProduct, fb: denProduct
+        }
+      }
+
+      // Find the magnitude of the root of the quadratic that has the maximum
+      // magnitude.
+      //
+      // The quadratic is z^2 + a1 * z + a2 and we want the root z that has the
+      // largest magnitude.
+      function largestRootMagnitude(a1, a2) {
+        let discriminant = a1 * a1 - 4 * a2;
+        if (discriminant < 0) {
+          // Complex roots:  -a1/2 +/- i*sqrt(-d)/2.  Thus the magnitude of each
+          // root is the same and is sqrt(a1^2/4 + |d|/4)
+          let d = Math.sqrt(-discriminant);
+          return Math.hypot(a1 / 2, d / 2);
+        } else {
+          // Real roots
+          let d = Math.sqrt(discriminant);
+          return Math.max(Math.abs((-a1 + d) / 2), Math.abs((-a1 - d) / 2));
+        }
+      }
+
+      audit.define('4th-order-iir', (task, should) => {
+        // Cascade 2 lowpass biquad filters and compare that with the equivalent
+        // 4th order IIR filter.
+
+        let nyquist = sampleRate / 2;
+        // Compute the coefficients of a lowpass filter.
+
+        // First some preliminary stuff.  Compute the coefficients of the
+        // biquad.  This is used to figure out how frames to use in the test.
+        let biquadType = 'lowpass';
+        let biquadCutoff = 350;
+        let biquadQ = 5;
+        let biquadGain = 1;
+
+        let coef = createFilter(
+            biquadType, biquadCutoff / nyquist, biquadQ, biquadGain);
+
+        // Cascade the biquads together to create an equivalent IIR filter.
+        let cascade = cascadeBiquads(coef, coef);
+
+        // Since we're cascading two identical biquads, the root of denominator
+        // of the IIR filter is repeated, so the root of the denominator with
+        // the largest magnitude occurs twice.  The impulse response of the IIR
+        // filter will be roughly c*(r*r)^n at time n, where r is the root of
+        // largest magnitude.  This approximation gets better as n increases.
+        // We can use this to get a rough idea of when the response has died
+        // down to a small value.
+
+        // This is the value we will use to determine how many frames to render.
+        // Rendering too many is a waste of time and also makes it hard to
+        // compare the actual result to the expected because the magnitudes are
+        // so small that they could be mostly round-off noise.
+        //
+        // Find magnitude of the root with largest magnitude
+        let rootMagnitude = largestRootMagnitude(coef.a1, coef.a2);
+
+        // Find n such that |r|^(2*n) <= eps.  That is, n = log(eps)/(2*log(r)).
+        // Somewhat arbitrarily choose eps = 1e-20;
+        let eps = 1e-20;
+        let framesForTest =
+            Math.floor(Math.log(eps) / (2 * Math.log(rootMagnitude)));
+
+        // We're ready to create the graph for the test.  The offline context
+        // has two channels: channel 0 is the expected (cascaded biquad) result
+        // and channel 1 is the actual IIR filter result.
+        let context = new OfflineAudioContext(2, framesForTest, sampleRate);
+
+        // Use a simple impulse with a large (arbitrary) amplitude as the source
+        let amplitude = 1;
+        let buffer = context.createBuffer(1, testFrames, sampleRate);
+        buffer.getChannelData(0)[0] = amplitude;
+        let source = context.createBufferSource();
+        source.buffer = buffer;
+
+        // Create the two biquad filters.  Doesn't really matter what, but for
+        // simplicity we choose identical lowpass filters with the same
+        // parameters.
+        let biquad1 = context.createBiquadFilter();
+        biquad1.type = biquadType;
+        biquad1.frequency.value = biquadCutoff;
+        biquad1.Q.value = biquadQ;
+
+        let biquad2 = context.createBiquadFilter();
+        biquad2.type = biquadType;
+        biquad2.frequency.value = biquadCutoff;
+        biquad2.Q.value = biquadQ;
+
+        let iir = context.createIIRFilter(cascade.ff, cascade.fb);
+
+        // Create the merger to get the signals into multiple channels
+        let merger = context.createChannelMerger(2);
+
+        // Create the graph, filtering the source through two biquads.
+        source.connect(biquad1);
+        biquad1.connect(biquad2);
+        biquad2.connect(merger, 0, 0);
+
+        source.connect(iir);
+        iir.connect(merger, 0, 1);
+
+        merger.connect(context.destination);
+
+        // Now filter the source through the IIR filter.
+        let y = iirFilter(buffer.getChannelData(0), cascade.ff, cascade.fb);
+
+        // Rock and roll!
+        source.start();
+
+        context.startRendering()
+            .then(function(result) {
+              let expected = result.getChannelData(0);
+              let actual = result.getChannelData(1);
+
+              should(actual, '4-th order IIRFilter (biquad ref)')
+                  .beCloseToArray(expected, {
+                    // Thresholds experimentally determined.
+                    absoluteThreshold: 1.59e-7,
+                    relativeThreshold: 2.11e-5,
+                  });
+
+              let snr = 10 * Math.log10(computeSNR(actual, expected));
+              should(snr, 'SNR of 4-th order IIRFilter (biquad ref)')
+                  .beGreaterThanOrEqualTo(108.947);
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html b/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
index e718ebb..2c1162d 100644
--- a/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
+++ b/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
@@ -85,6 +85,7 @@
    // firefox seems to process events after disconnect
    processor.removeEventListener('audioprocess', processListener)
 
+   // Note: the expected result is from a mono source file.
    var expectedBuffer = expected[0];
 
    // Trim the actual elements because we don't have a fine-grained
@@ -110,7 +111,7 @@
        "comparing expected and rendered buffers (channel 0)");
      assert_array_approx_equals(
        actualTrimmedC1,
-       trimEmptyElements(expectedBuffer.getChannelData(1)),
+       trimEmptyElements(expectedBuffer.getChannelData(0)),
        1e-4,
        "comparing expected and rendered buffers (channel 1)");
    }, "All data processed correctly");
diff --git a/webaudio/the-audio-api/the-offlineaudiocontext-interface/ctor-offlineaudiocontext.html b/webaudio/the-audio-api/the-offlineaudiocontext-interface/ctor-offlineaudiocontext.html
new file mode 100644
index 0000000..79aafe7
--- /dev/null
+++ b/webaudio/the-audio-api/the-offlineaudiocontext-interface/ctor-offlineaudiocontext.html
@@ -0,0 +1,203 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Test Constructor: OfflineAudioContext</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      // Just a simple test of the 3-arg constructor; This should be
+      // well-covered by other layout tests that use the 3-arg constructor.
+      audit.define(
+          {label: 'basic', description: 'Old-style constructor'},
+          (task, should) => {
+            let context;
+
+            // First and only arg should be a dictionary.
+            should(() => {
+              new OfflineAudioContext(3);
+            }, 'new OfflineAudioContext(3)').throw('TypeError');
+
+            // Constructor needs 1 or 3 args, so 2 should throw.
+            should(() => {
+              new OfflineAudioContext(3, 42);
+            }, 'new OfflineAudioContext(3, 42)').throw('TypeError');
+
+            // Valid constructor
+            should(() => {
+              context = new OfflineAudioContext(3, 42, 12345);
+            }, 'context = new OfflineAudioContext(3, 42, 12345)').notThrow();
+
+            // Verify that the context was constructed correctly.
+            should(context.length, 'context.length').beEqualTo(42);
+            should(context.sampleRate, 'context.sampleRate').beEqualTo(12345);
+            should(
+                context.destination.channelCount,
+                'context.destination.channelCount')
+                .beEqualTo(3);
+            should(
+                context.destination.channelCountMode,
+                'context.destination.channelCountMode')
+                .beEqualTo('explicit');
+            should(
+                context.destination.channelInterpretation,
+                'context.destination.channelInterpretation')
+                .beEqualTo('speakers');
+            task.done();
+          });
+
+      // Test constructor throws an error if the required members of the
+      // dictionary are not given.
+      audit.define(
+          {label: 'options-1', description: 'Required options'},
+          (task, should) => {
+            let context2;
+
+            // No args should throw
+            should(() => {
+              new OfflineAudioContext();
+            }, 'new OfflineAudioContext()').throw('TypeError');
+
+            // Empty OfflineAudioContextOptions should throw
+            should(() => {
+              new OfflineAudioContext({});
+            }, 'new OfflineAudioContext({})').throw('TypeError');
+
+            let options = {length: 42};
+            // sampleRate is required.
+            should(
+                () => {
+                  new OfflineAudioContext(options);
+                },
+                'new OfflineAudioContext(' + JSON.stringify(options) + ')')
+                .throw('TypeError');
+
+            options = {sampleRate: 12345};
+            // length is required.
+            should(
+                () => {
+                  new OfflineAudioContext(options);
+                },
+                'new OfflineAudioContext(' + JSON.stringify(options) + ')')
+                .throw('TypeError');
+
+            // Valid constructor.  Verify that the resulting context has the
+            // correct values.
+            options = {length: 42, sampleRate: 12345};
+            should(
+                () => {
+                  context2 = new OfflineAudioContext(options);
+                },
+                'c2 = new OfflineAudioContext(' + JSON.stringify(options) + ')')
+                .notThrow();
+            should(
+                context2.destination.channelCount,
+                'c2.destination.channelCount')
+                .beEqualTo(1);
+            should(context2.length, 'c2.length').beEqualTo(options.length);
+            should(context2.sampleRate, 'c2.sampleRate')
+                .beEqualTo(options.sampleRate);
+            should(
+                context2.destination.channelCountMode,
+                'c2.destination.channelCountMode')
+                .beEqualTo('explicit');
+            should(
+                context2.destination.channelInterpretation,
+                'c2.destination.channelInterpretation')
+                .beEqualTo('speakers');
+
+            task.done();
+          });
+
+      // Constructor should throw errors for invalid values specified by
+      // OfflineAudioContextOptions.
+      audit.define(
+          {label: 'options-2', description: 'Invalid options'},
+          (task, should) => {
+            let options = {length: 42, sampleRate: 8000, numberOfChannels: 33};
+
+            // channelCount too large.
+            should(
+                () => {
+                  new OfflineAudioContext(options);
+                },
+                'new OfflineAudioContext(' + JSON.stringify(options) + ')')
+                .throw('NotSupportedError');
+
+            // length cannot be 0
+            options = {length: 0, sampleRate: 8000};
+            should(
+                () => {
+                  new OfflineAudioContext(options);
+                },
+                'new OfflineAudioContext(' + JSON.stringify(options) + ')')
+                .throw('NotSupportedError');
+
+            // sampleRate outside valid range
+            options = {length: 1, sampleRate: 1};
+            should(
+                () => {
+                  new OfflineAudioContext(options);
+                },
+                'new OfflineAudioContext(' + JSON.stringify(options) + ')')
+                .throw('NotSupportedError');
+
+            task.done();
+          });
+
+      audit.define(
+          {label: 'options-3', description: 'Valid options'},
+          (task, should) => {
+            let context;
+            let options = {
+              length: 1,
+              sampleRate: 8000,
+            };
+
+            // Verify context with valid constructor has the correct values.
+            should(
+                () => {
+                  context = new OfflineAudioContext(options);
+                },
+                'c = new OfflineAudioContext' + JSON.stringify(options) + ')')
+                .notThrow();
+            should(context.length, 'c.length').beEqualTo(options.length);
+            should(context.sampleRate, 'c.sampleRate')
+                .beEqualTo(options.sampleRate);
+            should(
+                context.destination.channelCount, 'c.destination.channelCount')
+                .beEqualTo(1);
+            should(
+                context.destination.channelCountMode,
+                'c.destination.channelCountMode')
+                .beEqualTo('explicit');
+            should(
+                context.destination.channelInterpretation,
+                'c.destination.channelCountMode')
+                .beEqualTo('speakers');
+
+            options.numberOfChannels = 7;
+            should(
+                () => {
+                  context = new OfflineAudioContext(options);
+                },
+                'c = new OfflineAudioContext' + JSON.stringify(options) + ')')
+                .notThrow();
+            should(
+                context.destination.channelCount, 'c.destination.channelCount')
+                .beEqualTo(options.numberOfChannels);
+
+            task.done();
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html b/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html
new file mode 100644
index 0000000..aaf77ae
--- /dev/null
+++ b/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: Oscillator
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'OscillatorNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'OscillatorNode', context, {
+          prefix: prefix,
+          numberOfInputs: 0,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(
+            should, node, prefix,
+            [{name: 'type', value: 'sine'}, {name: 'frequency', value: 440}]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'OscillatorNode');
+        task.done();
+      });
+
+      audit.define('constructor options', (task, should) => {
+        let node;
+        let options = {type: 'sawtooth', detune: 7, frequency: 918};
+
+        should(
+            () => {
+              node = new OscillatorNode(context, options);
+            },
+            'node1 = new OscillatorNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+
+        should(node.type, 'node1.type').beEqualTo(options.type);
+        should(node.detune.value, 'node1.detune.value')
+            .beEqualTo(options.detune);
+        should(node.frequency.value, 'node1.frequency.value')
+            .beEqualTo(options.frequency);
+
+        should(node.channelCount, 'node1.channelCount').beEqualTo(2);
+        should(node.channelCountMode, 'node1.channelCountMode')
+            .beEqualTo('max');
+        should(node.channelInterpretation, 'node1.channelInterpretation')
+            .beEqualTo('speakers');
+
+        // Test that type and periodicWave options work as described.
+        options = {
+          type: 'sine',
+          periodicWave: new PeriodicWave(context, {real: [1, 1]})
+        };
+        should(() => {
+          node = new OscillatorNode(context, options);
+        }, 'new OscillatorNode(c, ' + JSON.stringify(options) + ')').notThrow();
+
+        options = {type: 'custom'};
+        should(
+            () => {
+              node = new OscillatorNode(context, options);
+            },
+            'new OscillatorNode(c, ' + JSON.stringify(options) + ')')
+            .throw('InvalidStateError');
+
+        options = {
+          type: 'custom',
+          periodicWave: new PeriodicWave(context, {real: [1, 1]})
+        };
+        should(() => {
+          node = new OscillatorNode(context, options);
+        }, 'new OscillatorNode(, ' + JSON.stringify(options) + ')').notThrow();
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/ctor-panner.html b/webaudio/the-audio-api/the-pannernode-interface/ctor-panner.html
new file mode 100644
index 0000000..48b368d
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/ctor-panner.html
@@ -0,0 +1,274 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: Panner
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'PannerNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'PannerNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'clamped-max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'panningModel', value: 'equalpower'},
+          {name: 'positionX', value: 0}, {name: 'positionY', value: 0},
+          {name: 'positionZ', value: 0}, {name: 'orientationX', value: 1},
+          {name: 'orientationY', value: 0}, {name: 'orientationZ', value: 0},
+          {name: 'distanceModel', value: 'inverse'},
+          {name: 'refDistance', value: 1}, {name: 'maxDistance', value: 10000},
+          {name: 'rolloffFactor', value: 1},
+          {name: 'coneInnerAngle', value: 360},
+          {name: 'coneOuterAngle', value: 360},
+          {name: 'coneOuterGain', value: 0}
+        ]);
+
+        // Test the listener too, while we're at it.
+        let listenerAttributes = [
+          {name: 'positionX', value: 0},
+          {name: 'positionY', value: 0},
+          {name: 'positionZ', value: 0},
+          {name: 'forwardX', value: 0},
+          {name: 'forwardY', value: 0},
+          {name: 'forwardZ', value: -1},
+          {name: 'upX', value: 0},
+          {name: 'upY', value: 1},
+          {name: 'upZ', value: 0},
+        ];
+
+        listenerAttributes.forEach((item) => {
+          should(
+              context.listener[item.name].value,
+              'context.listener.' + item.name + '.value')
+              .beEqualTo(item.value);
+        });
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        // Can't use testAudioNodeOptions because the constraints for this node
+        // are not supported there.
+        let node;
+        let success = true;
+
+        // Test that we can set the channel count to 1 or 2.
+        let options = {channelCount: 1};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'node1 = new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node.channelCount, 'node1.channelCount')
+            .beEqualTo(options.channelCount);
+
+        options = {channelCount: 2};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'node2 = new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node.channelCount, 'node2.channelCount')
+            .beEqualTo(options.channelCount);
+
+        // Test that other channel counts throw an error
+        options = {channelCount: 0};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        options = {channelCount: 3};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        options = {channelCount: 99};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        // Test channelCountMode.  A mode of "max" is illegal, but others are
+        // ok.
+        options = {channelCountMode: 'clamped-max'};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'node3 = new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node.channelCountMode, 'node3.channelCountMode')
+            .beEqualTo(options.channelCountMode);
+
+        options = {channelCountMode: 'explicit'};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'node4 = new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node.channelCountMode, 'node4.channelCountMode')
+            .beEqualTo(options.channelCountMode);
+
+        options = {channelCountMode: 'max'};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('NotSupportedError');
+
+        options = {channelCountMode: 'foobar'};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'new PannerNode(c, " + JSON.stringify(options) + ")')
+            .throw('TypeError');
+
+        // Test channelInterpretation.
+        options = {channelInterpretation: 'speakers'};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'node5 = new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node.channelInterpretation, 'node5.channelInterpretation')
+            .beEqualTo(options.channelInterpretation);
+
+        options = {channelInterpretation: 'discrete'};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'node6 = new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node.channelInterpretation, 'node6.channelInterpretation')
+            .beEqualTo(options.channelInterpretation);
+
+        options = {channelInterpretation: 'foobar'};
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .throw('TypeError');
+
+        task.done();
+      });
+
+      audit.define('constructor with options', (task, should) => {
+        let node;
+        let success = true;
+        let options = {
+          panningModel: 'HRTF',
+          // We use full double float values here to verify also that the actual
+          // AudioParam value is properly rounded to a float.  The actual value
+          // is immaterial as long as x != Math.fround(x).
+          positionX: Math.SQRT2,
+          positionY: 2 * Math.SQRT2,
+          positionZ: 3 * Math.SQRT2,
+          orientationX: -Math.SQRT2,
+          orientationY: -2 * Math.SQRT2,
+          orientationZ: -3 * Math.SQRT2,
+          distanceModel: 'linear',
+          // We use full double float values here to verify also that the actual
+          // attribute is a double float.  The actual value is immaterial as
+          // long as x != Math.fround(x).
+          refDistance: Math.PI,
+          maxDistance: 2 * Math.PI,
+          rolloffFactor: 3 * Math.PI,
+          coneInnerAngle: 4 * Math.PI,
+          coneOuterAngle: 5 * Math.PI,
+          coneOuterGain: 6 * Math.PI
+        };
+
+        should(
+            () => {
+              node = new PannerNode(context, options);
+            },
+            'node = new PannerNode(c, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(node instanceof PannerNode, 'node instanceof PannerNode')
+            .beEqualTo(true);
+
+        should(node.panningModel, 'node.panningModel')
+            .beEqualTo(options.panningModel);
+        should(node.positionX.value, 'node.positionX.value')
+            .beEqualTo(Math.fround(options.positionX));
+        should(node.positionY.value, 'node.positionY.value')
+            .beEqualTo(Math.fround(options.positionY));
+        should(node.positionZ.value, 'node.positionZ.value')
+            .beEqualTo(Math.fround(options.positionZ));
+        should(node.orientationX.value, 'node.orientationX.value')
+            .beEqualTo(Math.fround(options.orientationX));
+        should(node.orientationY.value, 'node.orientationY.value')
+            .beEqualTo(Math.fround(options.orientationY));
+        should(node.orientationZ.value, 'node.orientationZ.value')
+            .beEqualTo(Math.fround(options.orientationZ));
+        should(node.distanceModel, 'node.distanceModel')
+            .beEqualTo(options.distanceModel);
+        should(node.refDistance, 'node.refDistance')
+            .beEqualTo(options.refDistance);
+        should(node.maxDistance, 'node.maxDistance')
+            .beEqualTo(options.maxDistance);
+        should(node.rolloffFactor, 'node.rolloffFactor')
+            .beEqualTo(options.rolloffFactor);
+        should(node.coneInnerAngle, 'node.coneInnerAngle')
+            .beEqualTo(options.coneInnerAngle);
+        should(node.coneOuterAngle, 'node.coneOuterAngle')
+            .beEqualTo(options.coneOuterAngle);
+        should(node.coneOuterGain, 'node.coneOuterGain')
+            .beEqualTo(options.coneOuterGain);
+
+        should(node.channelCount, 'node.channelCount').beEqualTo(2);
+        should(node.channelCountMode, 'node.channelCountMode')
+            .beEqualTo('clamped-max');
+        should(node.channelInterpretation, 'node.channelInterpretation')
+            .beEqualTo('speakers');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/distance-exponential.html b/webaudio/the-audio-api/the-pannernode-interface/distance-exponential.html
new file mode 100644
index 0000000..383e2c6
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/distance-exponential.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      distance-exponential.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/distance-model-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description: 'Exponential distance model for PannerNode'
+          },
+          (task, should) => {
+            // Create offline audio context.
+            context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            createTestAndRun(context, 'exponential', should)
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/distance-inverse.html b/webaudio/the-audio-api/the-pannernode-interface/distance-inverse.html
new file mode 100644
index 0000000..a4ff984
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/distance-inverse.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      distance-inverse.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/distance-model-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define('test', (task, should) => {
+        // Create offline audio context.
+        context = new OfflineAudioContext(
+            2, sampleRate * renderLengthSeconds, sampleRate);
+
+        createTestAndRun(context, 'inverse', should).then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/distance-linear.html b/webaudio/the-audio-api/the-pannernode-interface/distance-linear.html
new file mode 100644
index 0000000..812fea3
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/distance-linear.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      distance-linear.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/distance-model-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'test', description: 'Linear distance model PannerNode'},
+          (task, should) => {
+            // Create offline audio context.
+            context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            createTestAndRun(context, 'linear', should).then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/panner-automation-basic.html b/webaudio/the-audio-api/the-pannernode-interface/panner-automation-basic.html
new file mode 100644
index 0000000..5c3df0e
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/panner-automation-basic.html
@@ -0,0 +1,298 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Basic PannerNode with Automation Position Properties
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/panner-formulas.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+
+      // These tests are quite slow, so don't run for many frames.  256 frames
+      // should be enough to demonstrate that automations are working.
+      let renderFrames = 256;
+      let renderDuration = renderFrames / sampleRate;
+
+      let audit = Audit.createTaskRunner();
+
+      // Array of tests for setting the panner positions.  These tests basically
+      // verify that the position setters for the panner and listener are
+      // working correctly.
+      let testConfig = [
+        {
+          setter: 'positionX',
+        },
+        {
+          setter: 'positionY',
+        },
+        {
+          setter: 'positionZ',
+        }
+      ];
+
+      // Create tests for the panner position setters.  Both mono and steroe
+      // sources are tested.
+      for (let k = 0; k < testConfig.length; ++k) {
+        let config = testConfig[k];
+        // Function to create the test to define the test.
+        let tester = function(config, channelCount) {
+          return (task, should) => {
+            let nodes = createGraph(channelCount);
+            let {context, source, panner} = nodes;
+
+            let message = channelCount == 1 ? 'Mono' : 'Stereo';
+            message += ' panner.' + config.setter;
+
+            testPositionSetter(should, {
+              nodes: nodes,
+              pannerSetter: panner[config.setter],
+              message: message
+            }).then(() => task.done());
+          }
+        };
+
+        audit.define('Stereo panner.' + config.setter, tester(config, 2));
+        audit.define('Mono panner.' + config.setter, tester(config, 1));
+      }
+
+      // Create tests for the listener position setters.  Both mono and steroe
+      // sources are tested.
+      for (let k = 0; k < testConfig.length; ++k) {
+        let config = testConfig[k];
+        // Function to create the test to define the test.
+        let tester = function(config, channelCount) {
+          return (task, should) => {
+            let nodes = createGraph(channelCount);
+            let {context, source, panner} = nodes;
+
+            let message = channelCount == 1 ? 'Mono' : 'Stereo';
+            message += ' listener.' + config.setter;
+
+            // Some relatively arbitrary (non-default) position for the source
+            // location.
+            panner.setPosition(1, 0, 1);
+
+            testPositionSetter(should, {
+              nodes: nodes,
+              pannerSetter: context.listener[config.setter],
+              message: message
+            }).then(() => task.done());
+          }
+        };
+
+        audit.define('Stereo listener.' + config.setter, tester(config, 2));
+        audit.define('Mono listener.' + config.setter, tester(config, 1));
+      }
+
+      // Test setPosition method.
+      audit.define('setPosition', (task, should) => {
+        let {context, panner, source} = createGraph(2);
+
+        // Initialize source position (values don't really matter).
+        panner.setPosition(1, 1, 1);
+
+        // After some (unimportant) time, move the panner to a (any) new
+        // location.
+        let suspendFrame = 128;
+        context.suspend(suspendFrame / sampleRate)
+            .then(function() {
+              panner.setPosition(-100, 2000, 8000);
+            })
+            .then(context.resume.bind(context));
+
+        context.startRendering()
+            .then(function(resultBuffer) {
+              verifyPannerOutputChanged(
+                  should, resultBuffer,
+                  {message: 'setPosition', suspendFrame: suspendFrame});
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('orientation setter', (task, should) => {
+        let {context, panner, source} = createGraph(2);
+
+        // For orientation to matter, we need to make the source directional,
+        // and also move away from the listener (because the default location is
+        // 0,0,0).
+        panner.setPosition(0, 0, 1);
+        panner.coneInnerAngle = 0;
+        panner.coneOuterAngle = 360;
+        panner.coneOuterGain = .001;
+
+        // After some (unimportant) time, change the panner orientation to a new
+        // orientation.  The only constraint is that the orientation changes
+        // from before.
+        let suspendFrame = 128;
+        context.suspend(suspendFrame / sampleRate)
+            .then(function() {
+              panner.orientationX.value = -100;
+              panner.orientationY.value = 2000;
+              panner.orientationZ.value = 8000;
+            })
+            .then(context.resume.bind(context));
+
+        context.startRendering()
+            .then(function(resultBuffer) {
+              verifyPannerOutputChanged(should, resultBuffer, {
+                message: 'panner.orientation{XYZ}',
+                suspendFrame: suspendFrame
+              });
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('forward setter', (task, should) => {
+        let {context, panner, source} = createGraph(2);
+
+        // For orientation to matter, we need to make the source directional,
+        // and also move away from the listener (because the default location is
+        // 0,0,0).
+        panner.setPosition(0, 0, 1);
+        panner.coneInnerAngle = 0;
+        panner.coneOuterAngle = 360;
+        panner.coneOuterGain = .001;
+
+        // After some (unimportant) time, change the panner orientation to a new
+        // orientation.  The only constraint is that the orientation changes
+        // from before.
+        let suspendFrame = 128;
+        context.suspend(suspendFrame / sampleRate)
+            .then(function() {
+              context.listener.forwardX.value = -100;
+              context.listener.forwardY.value = 2000;
+              context.listener.forwardZ.value = 8000;
+            })
+            .then(context.resume.bind(context));
+
+        context.startRendering()
+            .then(function(resultBuffer) {
+              verifyPannerOutputChanged(should, resultBuffer, {
+                message: 'listener.forward{XYZ}',
+                suspendFrame: suspendFrame
+              });
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('up setter', (task, should) => {
+        let {context, panner, source} = createGraph(2);
+
+        // For orientation to matter, we need to make the source directional,
+        // and also move away from the listener (because the default location is
+        // 0,0,0).
+        panner.setPosition(0, 0, 1);
+        panner.coneInnerAngle = 0;
+        panner.coneOuterAngle = 360;
+        panner.coneOuterGain = .001;
+        panner.setPosition(1, 0, 1);
+
+        // After some (unimportant) time, change the panner orientation to a new
+        // orientation.  The only constraint is that the orientation changes
+        // from before.
+        let suspendFrame = 128;
+        context.suspend(suspendFrame / sampleRate)
+            .then(function() {
+              context.listener.upX.value = 100;
+              context.listener.upY.value = 100;
+              context.listener.upZ.value = 100;
+              ;
+            })
+            .then(context.resume.bind(context));
+
+        context.startRendering()
+            .then(function(resultBuffer) {
+              verifyPannerOutputChanged(
+                  should, resultBuffer,
+                  {message: 'listener.up{XYZ}', suspendFrame: suspendFrame});
+            })
+            .then(() => task.done());
+      });
+
+      audit.run();
+
+      function createGraph(channelCount) {
+        let context = new OfflineAudioContext(2, renderFrames, sampleRate);
+        let panner = context.createPanner();
+        let source = context.createBufferSource();
+        source.buffer =
+            createConstantBuffer(context, 1, channelCount == 1 ? 1 : [1, 2]);
+        source.loop = true;
+
+        source.connect(panner);
+        panner.connect(context.destination);
+
+        source.start();
+        return {context: context, source: source, panner: panner};
+      }
+
+      function testPositionSetter(should, options) {
+        let {nodes, pannerSetter, message} = options;
+
+        let {context, source, panner} = nodes;
+
+        // Set panner x position. (Value doesn't matter);
+        pannerSetter.value = 1;
+
+        // Wait a bit and set a new position.  (Actual time and position doesn't
+        // matter).
+        let suspendFrame = 128;
+        context.suspend(suspendFrame / sampleRate)
+            .then(function() {
+              pannerSetter.value = 10000;
+            })
+            .then(context.resume.bind(context));
+
+        return context.startRendering().then(function(resultBuffer) {
+          verifyPannerOutputChanged(
+              should, resultBuffer,
+              {message: message, suspendFrame: suspendFrame});
+        });
+      }
+
+      function verifyPannerOutputChanged(should, resultBuffer, options) {
+        let {message, suspendFrame} = options;
+        // Verify that the first part of output is constant. (Doesn't matter
+        // what.)
+        let data0 = resultBuffer.getChannelData(0);
+        let data1 = resultBuffer.getChannelData(1);
+
+        let middle = '[0, ' + suspendFrame + ') ';
+        should(
+            data0.slice(0, suspendFrame),
+            message + '.value frame ' + middle + 'channel 0')
+            .beConstantValueOf(data0[0]);
+        should(
+            data1.slice(0, suspendFrame),
+            message + '.value frame ' + middle + 'channel 1')
+            .beConstantValueOf(data1[0]);
+
+        // The rest after suspendTime should be constant and different from the
+        // first part.
+        middle = '[' + suspendFrame + ', ' + renderFrames + ') ';
+        should(
+            data0.slice(suspendFrame),
+            message + '.value frame ' + middle + 'channel 0')
+            .beConstantValueOf(data0[suspendFrame]);
+        should(
+            data1.slice(suspendFrame),
+            message + '.value frame ' + middle + 'channel 1')
+            .beConstantValueOf(data1[suspendFrame]);
+        should(
+            data0[suspendFrame],
+            message + ': Output at frame ' + suspendFrame + ' channel 0')
+            .notBeEqualTo(data0[0]);
+        should(
+            data1[suspendFrame],
+            message + ': Output at frame ' + suspendFrame + ' channel 1')
+            .notBeEqualTo(data1[0]);
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/panner-automation-equalpower-stereo.html b/webaudio/the-audio-api/the-pannernode-interface/panner-automation-equalpower-stereo.html
new file mode 100644
index 0000000..7afc9c2
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/panner-automation-equalpower-stereo.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      panner-automation-equalpower-stereo.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/panner-model-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // To test the panner, we create a number of panner nodes
+      // equally spaced on a semicircle at unit distance.  The
+      // semicircle covers the azimuth range from -90 to 90 deg,
+      // covering full left to full right.  Each source is an impulse
+      // turning at a different time and we check that the rendered
+      // impulse has the expected gain.
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'Equal-power panner model of AudioPannerNode with stereo source',
+          },
+          (task, should) => {
+            // Create offline audio context.
+            context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            createTestAndRun(
+                context, should, nodesToCreate, 2,
+                function(panner, x, y, z) {
+                  panner.positionX.value = x;
+                  panner.positionY.value = y;
+                  panner.positionZ.value = z;
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/panner-automation-position.html b/webaudio/the-audio-api/the-pannernode-interface/panner-automation-position.html
new file mode 100644
index 0000000..8e09e86
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/panner-automation-position.html
@@ -0,0 +1,265 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Automation of PannerNode Positions
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/panner-formulas.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let sampleRate = 48000;
+      // These tests are quite slow, so don't run for many frames.  256 frames
+      // should be enough to demonstrate that automations are working.
+      let renderFrames = 256;
+      let renderDuration = renderFrames / sampleRate;
+
+      let context;
+      let panner;
+
+      let audit = Audit.createTaskRunner();
+
+      // Set of tests for the panner node with automations applied to the
+      // position of the source.
+      let testConfigs = [
+        {
+          // Distance model parameters for the panner
+          distanceModel: {model: 'inverse', rolloff: 1},
+          // Initial location of the source
+          startPosition: [0, 0, 1],
+          // Final position of the source.  For this test, we only want to move
+          // on the z axis which
+          // doesn't change the azimuth angle.
+          endPosition: [0, 0, 10000],
+        },
+        {
+          distanceModel: {model: 'inverse', rolloff: 1},
+          startPosition: [0, 0, 1],
+          // An essentially random end position, but it should be such that
+          // azimuth angle changes as
+          // we move from the start to the end.
+          endPosition: [20000, 30000, 10000],
+          errorThreshold: [
+            {
+              // Error threshold for 1-channel case
+              relativeThreshold: 4.8124e-7
+            },
+            {
+              // Error threshold for 2-channel case
+              relativeThreshold: 4.3267e-7
+            }
+          ],
+        },
+        {
+          distanceModel: {model: 'exponential', rolloff: 1.5},
+          startPosition: [0, 0, 1],
+          endPosition: [20000, 30000, 10000],
+          errorThreshold:
+              [{relativeThreshold: 5.0783e-7}, {relativeThreshold: 5.2180e-7}]
+        },
+        {
+          distanceModel: {model: 'linear', rolloff: 1},
+          startPosition: [0, 0, 1],
+          endPosition: [20000, 30000, 10000],
+          errorThreshold: [
+            {relativeThreshold: 6.5324e-6}, {relativeThreshold: 6.5756e-6}
+          ]
+        }
+      ];
+
+      for (let k = 0; k < testConfigs.length; ++k) {
+        let config = testConfigs[k];
+        let tester = function(c, channelCount) {
+          return (task, should) => {
+            runTest(should, c, channelCount).then(() => task.done());
+          }
+        };
+
+        let baseTestName = config.distanceModel.model +
+            ' rolloff: ' + config.distanceModel.rolloff;
+
+        // Define tasks for both 1-channel and 2-channel
+        audit.define(k + ': 1-channel ' + baseTestName, tester(config, 1));
+        audit.define(k + ': 2-channel ' + baseTestName, tester(config, 2));
+      }
+
+      audit.run();
+
+      function runTest(should, options, channelCount) {
+        // Output has 5 channels: channels 0 and 1 are for the stereo output of
+        // the panner node. Channels 2-5 are the for automation of the x,y,z
+        // coordinate so that we have actual coordinates used for the panner
+        // automation.
+        context = new OfflineAudioContext(5, renderFrames, sampleRate);
+
+        // Stereo source for the panner.
+        let source = context.createBufferSource();
+        source.buffer = createConstantBuffer(
+            context, renderFrames, channelCount == 1 ? 1 : [1, 2]);
+
+        panner = context.createPanner();
+        panner.distanceModel = options.distanceModel.model;
+        panner.rolloffFactor = options.distanceModel.rolloff;
+        panner.panningModel = 'equalpower';
+
+        // Source and gain node for the z-coordinate calculation.
+        let dist = context.createBufferSource();
+        dist.buffer = createConstantBuffer(context, 1, 1);
+        dist.loop = true;
+        let gainX = context.createGain();
+        let gainY = context.createGain();
+        let gainZ = context.createGain();
+        dist.connect(gainX);
+        dist.connect(gainY);
+        dist.connect(gainZ);
+
+        // Set the gain automation to match the z-coordinate automation of the
+        // panner.
+
+        // End the automation some time before the end of the rendering so we
+        // can verify that automation has the correct end time and value.
+        let endAutomationTime = 0.75 * renderDuration;
+
+        gainX.gain.setValueAtTime(options.startPosition[0], 0);
+        gainX.gain.linearRampToValueAtTime(
+            options.endPosition[0], endAutomationTime);
+        gainY.gain.setValueAtTime(options.startPosition[1], 0);
+        gainY.gain.linearRampToValueAtTime(
+            options.endPosition[1], endAutomationTime);
+        gainZ.gain.setValueAtTime(options.startPosition[2], 0);
+        gainZ.gain.linearRampToValueAtTime(
+            options.endPosition[2], endAutomationTime);
+
+        dist.start();
+
+        // Splitter and merger to map the panner output and the z-coordinate
+        // automation to the correct channels in the destination.
+        let splitter = context.createChannelSplitter(2);
+        let merger = context.createChannelMerger(5);
+
+        source.connect(panner);
+        // Split the output of the panner to separate channels
+        panner.connect(splitter);
+
+        // Merge the panner outputs and the z-coordinate output to the correct
+        // destination channels.
+        splitter.connect(merger, 0, 0);
+        splitter.connect(merger, 1, 1);
+        gainX.connect(merger, 0, 2);
+        gainY.connect(merger, 0, 3);
+        gainZ.connect(merger, 0, 4);
+
+        merger.connect(context.destination);
+
+        // Initialize starting point of the panner.
+        panner.positionX.setValueAtTime(options.startPosition[0], 0);
+        panner.positionY.setValueAtTime(options.startPosition[1], 0);
+        panner.positionZ.setValueAtTime(options.startPosition[2], 0);
+
+        // Automate z coordinate to move away from the listener
+        panner.positionX.linearRampToValueAtTime(
+            options.endPosition[0], 0.75 * renderDuration);
+        panner.positionY.linearRampToValueAtTime(
+            options.endPosition[1], 0.75 * renderDuration);
+        panner.positionZ.linearRampToValueAtTime(
+            options.endPosition[2], 0.75 * renderDuration);
+
+        source.start();
+
+        // Go!
+        return context.startRendering().then(function(renderedBuffer) {
+          // Get the panner outputs
+          let data0 = renderedBuffer.getChannelData(0);
+          let data1 = renderedBuffer.getChannelData(1);
+          let xcoord = renderedBuffer.getChannelData(2);
+          let ycoord = renderedBuffer.getChannelData(3);
+          let zcoord = renderedBuffer.getChannelData(4);
+
+          // We're doing a linear ramp on the Z axis with the equalpower panner,
+          // so the equalpower panning gain remains constant.  We only need to
+          // model the distance effect.
+
+          // Compute the distance gain
+          let distanceGain = new Float32Array(xcoord.length);
+          ;
+
+          if (panner.distanceModel === 'inverse') {
+            for (let k = 0; k < distanceGain.length; ++k) {
+              distanceGain[k] =
+                  inverseDistance(panner, xcoord[k], ycoord[k], zcoord[k])
+            }
+          } else if (panner.distanceModel === 'linear') {
+            for (let k = 0; k < distanceGain.length; ++k) {
+              distanceGain[k] =
+                  linearDistance(panner, xcoord[k], ycoord[k], zcoord[k])
+            }
+          } else if (panner.distanceModel === 'exponential') {
+            for (let k = 0; k < distanceGain.length; ++k) {
+              distanceGain[k] =
+                  exponentialDistance(panner, xcoord[k], ycoord[k], zcoord[k])
+            }
+          }
+
+          // Compute the expected result.  Since we're on the z-axis, the left
+          // and right channels pass through the equalpower panner unchanged.
+          // Only need to apply the distance gain.
+          let buffer0 = source.buffer.getChannelData(0);
+          let buffer1 =
+              channelCount == 2 ? source.buffer.getChannelData(1) : buffer0;
+
+          let azimuth = new Float32Array(buffer0.length);
+
+          for (let k = 0; k < data0.length; ++k) {
+            azimuth[k] = calculateAzimuth(
+                [xcoord[k], ycoord[k], zcoord[k]],
+                [
+                  context.listener.positionX.value,
+                  context.listener.positionY.value,
+                  context.listener.positionZ.value
+                ],
+                [
+                  context.listener.forwardX.value,
+                  context.listener.forwardY.value,
+                  context.listener.forwardZ.value
+                ],
+                [
+                  context.listener.upX.value, context.listener.upY.value,
+                  context.listener.upZ.value
+                ]);
+          }
+
+          let expected = applyPanner(azimuth, buffer0, buffer1, channelCount);
+          let expected0 = expected.left;
+          let expected1 = expected.right;
+
+          for (let k = 0; k < expected0.length; ++k) {
+            expected0[k] *= distanceGain[k];
+            expected1[k] *= distanceGain[k];
+          }
+
+          let info = options.distanceModel.model +
+              ', rolloff: ' + options.distanceModel.rolloff;
+          let prefix = channelCount + '-channel ' +
+              '[' + options.startPosition[0] + ', ' + options.startPosition[1] +
+              ', ' + options.startPosition[2] + '] -> [' +
+              options.endPosition[0] + ', ' + options.endPosition[1] + ', ' +
+              options.endPosition[2] + ']: ';
+
+          let errorThreshold = 0;
+
+          if (options.errorThreshold)
+            errorThreshold = options.errorThreshold[channelCount - 1]
+
+            should(data0, prefix + 'distanceModel: ' + info + ', left channel')
+                .beCloseToArray(expected0, {absoluteThreshold: errorThreshold});
+          should(data1, prefix + 'distanceModel: ' + info + ', right channel')
+              .beCloseToArray(expected1, {absoluteThreshold: errorThreshold});
+        });
+      }
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html b/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html
new file mode 100644
index 0000000..dae58c9
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/panner-distance-clamping.html
@@ -0,0 +1,233 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Clamping of Distance for PannerNode
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Arbitrary sample rate and render length.
+      let sampleRate = 48000;
+      let renderFrames = 128;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('ref-distance-error', (task, should) => {
+        testDistanceLimits(should, {name: 'refDistance', isZeroAllowed: true});
+        task.done();
+      });
+
+      audit.define('max-distance-error', (task, should) => {
+        testDistanceLimits(should, {name: 'maxDistance', isZeroAllowed: false});
+        task.done();
+      });
+
+      function testDistanceLimits(should, options) {
+        // Verify that exceptions are thrown for invalid values of refDistance.
+        let context = new OfflineAudioContext(1, renderFrames, sampleRate);
+
+        let attrName = options.name;
+        let prefix = 'new PannerNode(c, {' + attrName + ': ';
+
+        should(function() {
+          let nodeOptions = {};
+          nodeOptions[attrName] = -1;
+          new PannerNode(context, nodeOptions);
+        }, prefix + '-1})').throw('RangeError');
+
+        if (options.isZeroAllowed) {
+          should(function() {
+            let nodeOptions = {};
+            nodeOptions[attrName] = 0;
+            new PannerNode(context, nodeOptions);
+          }, prefix + '0})').notThrow();
+        } else {
+          should(function() {
+            let nodeOptions = {};
+            nodeOptions[attrName] = 0;
+            new PannerNode(context, nodeOptions);
+          }, prefix + '0})').throw('RangeError');
+        }
+
+        // The smallest representable positive single float.
+        let leastPositiveDoubleFloat = 4.9406564584124654e-324;
+
+        should(function() {
+          let nodeOptions = {};
+          nodeOptions[attrName] = leastPositiveDoubleFloat;
+          new PannerNode(context, nodeOptions);
+        }, prefix + leastPositiveDoubleFloat + '})').notThrow();
+
+        prefix = 'panner.' + attrName + ' = ';
+        panner = new PannerNode(context);
+        should(function() {
+          panner[attrName] = -1;
+        }, prefix + '-1').throw('RangeError');
+
+        if (options.isZeroAllowed) {
+          should(function() {
+            panner[attrName] = 0;
+          }, prefix + '0').notThrow();
+        } else {
+          should(function() {
+            panner[attrName] = 0;
+          }, prefix + '0').throw('RangeError');
+        }
+
+        should(function() {
+          panner[attrName] = leastPositiveDoubleFloat;
+        }, prefix + leastPositiveDoubleFloat).notThrow();
+      }
+
+      audit.define('min-distance', (task, should) => {
+        // Test clamping of panner distance to refDistance for all of the
+        // distance models.  The actual distance is arbitrary as long as it's
+        // less than refDistance.  We test default and non-default values for
+        // the panner's refDistance and maxDistance.
+        // correctly.
+        Promise
+            .all([
+              runTest(should, {
+                distance: 0.01,
+                distanceModel: 'linear',
+              }),
+              runTest(should, {
+                distance: 0.01,
+                distanceModel: 'exponential',
+              }),
+              runTest(should, {
+                distance: 0.01,
+                distanceModel: 'inverse',
+              }),
+              runTest(should, {
+                distance: 2,
+                distanceModel: 'linear',
+                maxDistance: 1000,
+                refDistance: 10,
+              }),
+              runTest(should, {
+                distance: 2,
+                distanceModel: 'exponential',
+                maxDistance: 1000,
+                refDistance: 10,
+              }),
+              runTest(should, {
+                distance: 2,
+                distanceModel: 'inverse',
+                maxDistance: 1000,
+                refDistance: 10,
+              }),
+            ])
+            .then(() => task.done());
+      });
+
+      audit.define('max-distance', (task, should) => {
+        // Like the "min-distance" task, but for clamping to the max
+        // distance. The actual distance is again arbitrary as long as it is
+        // greater than maxDistance.
+        Promise
+            .all([
+              runTest(should, {
+                distance: 20000,
+                distanceModel: 'linear',
+              }),
+              runTest(should, {
+                distance: 21000,
+                distanceModel: 'exponential',
+              }),
+              runTest(should, {
+                distance: 23000,
+                distanceModel: 'inverse',
+              }),
+              runTest(should, {
+                distance: 5000,
+                distanceModel: 'linear',
+                maxDistance: 1000,
+                refDistance: 10,
+              }),
+              runTest(should, {
+                distance: 5000,
+                distanceModel: 'exponential',
+                maxDistance: 1000,
+                refDistance: 10,
+              }),
+              runTest(should, {
+                distance: 5000,
+                distanceModel: 'inverse',
+                maxDistance: 1000,
+                refDistance: 10,
+              }),
+            ])
+            .then(() => task.done());
+      });
+
+      function runTest(should, options) {
+        let context = new OfflineAudioContext(2, renderFrames, sampleRate);
+        let src = new OscillatorNode(context, {
+          type: 'sawtooth',
+          frequency: 20 * 440,
+        });
+
+        // Set panner options.  Use a non-default rolloffFactor so that the
+        // various distance models look distinctly different.
+        let pannerOptions = {};
+        Object.assign(pannerOptions, options, {rolloffFactor: 0.5});
+
+        let pannerRef = new PannerNode(context, pannerOptions);
+        let pannerTest = new PannerNode(context, pannerOptions);
+
+        // Split the panner output so we can grab just one of the output
+        // channels.
+        let splitRef = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        let splitTest = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+
+        // Merge the panner outputs back into one stereo stream for the
+        // destination.
+        let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
+
+        src.connect(pannerTest).connect(splitTest).connect(merger, 0, 0);
+        src.connect(pannerRef).connect(splitRef).connect(merger, 0, 1);
+
+        merger.connect(context.destination);
+
+        // Move the panner some distance away. Arbitrarily select the x
+        // direction.  For the reference panner, manually clamp the distance.
+        // All models clamp the distance to a minimum of refDistance.  Only the
+        // linear model also clamps to a maximum of maxDistance.
+        let xRef = Math.max(options.distance, pannerRef.refDistance);
+
+        if (pannerRef.distanceModel === 'linear') {
+          xRef = Math.min(xRef, pannerRef.maxDistance);
+        }
+
+        let xTest = options.distance;
+
+        pannerRef.positionZ.setValueAtTime(xRef, 0);
+        pannerTest.positionZ.setValueAtTime(xTest, 0);
+
+        src.start();
+
+        return context.startRendering().then(function(resultBuffer) {
+          let actual = resultBuffer.getChannelData(0);
+          let expected = resultBuffer.getChannelData(1);
+
+          should(
+              xTest < pannerRef.refDistance || xTest > pannerRef.maxDistance,
+              'Model: ' + options.distanceModel + ': Distance (' + xTest +
+                  ') is outside the range [' + pannerRef.refDistance + ', ' +
+                  pannerRef.maxDistance + ']')
+              .beEqualTo(true);
+          should(actual, 'Test panner output ' + JSON.stringify(options))
+              .beEqualToArray(expected);
+        });
+      }
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/panner-equalpower-stereo.html b/webaudio/the-audio-api/the-pannernode-interface/panner-equalpower-stereo.html
new file mode 100644
index 0000000..2a0225b
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/panner-equalpower-stereo.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      panner-equalpower-stereo.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/panner-model-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // To test the panner, we create a number of panner nodes
+      // equally spaced on a semicircle at unit distance.  The
+      // semicircle covers the azimuth range from -90 to 90 deg,
+      // covering full left to full right.  Each source is an impulse
+      // turning at a different time and we check that the rendered
+      // impulse has the expected gain.
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'Equal-power panner model of AudioPannerNode with stereo source'
+          },
+          (task, should) => {
+            context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            createTestAndRun(
+                context, should, nodesToCreate, 2,
+                function(panner, x, y, z) {
+                  panner.setPosition(x, y, z);
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/panner-equalpower.html b/webaudio/the-audio-api/the-pannernode-interface/panner-equalpower.html
new file mode 100644
index 0000000..3ff21b6
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/panner-equalpower.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      panner-equalpower.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/panner-model-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      // To test the panner, we create a number of panner nodes
+      // equally spaced on a semicircle at unit distance.  The
+      // semicircle covers the azimuth range from -90 to 90 deg,
+      // covering full left to full right.  Each source is an impulse
+      // turning at a different time and we check that the rendered
+      // impulse has the expected gain.
+      audit.define(
+          {
+            label: 'test',
+            description: 'Equal-power panner model of AudioPannerNode',
+          },
+          (task, should) => {
+            // Create offline audio context.
+            context = new OfflineAudioContext(
+                2, sampleRate * renderLengthSeconds, sampleRate);
+
+            createTestAndRun(
+                context, should, nodesToCreate, 1,
+                function(panner, x, y, z) {
+                  panner.setPosition(x, y, z);
+                })
+                .then(() => task.done());
+            ;
+          });
+
+      // Test that a mono source plays out on both the left and right channels
+      // when the source and listener positions are the same.
+      audit.define(
+          {
+            label: 'mono source=listener',
+            description: 'Source and listener at the same position'
+          },
+          (task, should) => {
+            // Must be stereo to verify output and only need a short duration
+            let context =
+                new OfflineAudioContext(2, 0.25 * sampleRate, sampleRate);
+
+            // Arbitrary position for source and listener.  Just so we don't use
+            // defaults positions.
+            let x = 1;
+            let y = 2;
+            let z = 3;
+
+            context.listener.setPosition(x, y, z);
+
+            let src = new OscillatorNode(context);
+            let panner = new PannerNode(context, {
+              panningModel: 'equalpower',
+              positionX: x,
+              positionY: y,
+              positionZ: z
+            });
+
+            src.connect(panner).connect(context.destination);
+
+            src.start();
+
+            context.startRendering()
+                .then(renderedBuffer => {
+                  // Verify that both channels have the same data because they
+                  // should when the source and listener are at the same
+                  // position
+                  let c0 = renderedBuffer.getChannelData(0);
+                  let c1 = renderedBuffer.getChannelData(1);
+                  should(c0, 'Mono: Left and right channels').beEqualToArray(c1);
+                })
+                .then(() => task.done());
+          });
+
+      // Test that a stereo source plays out on both the left and right channels
+      // when the source and listener positions are the same.
+      audit.define(
+          {
+            label: 'stereo source=listener',
+            description: 'Source and listener at the same position'
+          },
+          (task, should) => {
+            // Must be stereo to verify output and only need a short duration.
+            let context =
+                new OfflineAudioContext(2, 0.25 * sampleRate, sampleRate);
+
+            // Arbitrary position for source and listener.  Just so we don't use
+            // defaults positions.
+            let x = 1;
+            let y = 2;
+            let z = 3;
+
+            context.listener.setPosition(x, y, z);
+
+            let src = new OscillatorNode(context);
+            let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
+            let panner = new PannerNode(context, {
+              panningModel: 'equalpower',
+              positionX: x,
+              positionY: y,
+              positionZ: z
+            });
+
+            // Make the oscillator a stereo signal (with identical signals on
+            // each channel).
+            src.connect(merger, 0, 0);
+            src.connect(merger, 0, 1);
+
+            merger.connect(panner).connect(context.destination);
+
+            src.start();
+
+            context.startRendering()
+                .then(renderedBuffer => {
+                  // Verify that both channels have the same data because they
+                  // should when the source and listener are at the same
+                  // position.
+                  let c0 = renderedBuffer.getChannelData(0);
+                  let c1 = renderedBuffer.getChannelData(1);
+                  should(c0, 'Stereo: Left and right channels').beEqualToArray(c1);
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/panner-rolloff-clamping.html b/webaudio/the-audio-api/the-pannernode-interface/panner-rolloff-clamping.html
new file mode 100644
index 0000000..e1519f8
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/panner-rolloff-clamping.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Clamping of PannerNode rolloffFactor
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Fairly arbitrary sample rate and render frames.
+      let sampleRate = 16000;
+      let renderFrames = 2048;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('linear-clamp-low', (task, should) => {
+        runTest(should, {
+          distanceModel: 'linear',
+          // Fairly arbitrary value outside the nominal range
+          rolloffFactor: -1,
+          clampedRolloff: 0
+        }).then(() => task.done());
+      });
+
+      audit.define('linear-clamp-high', (task, should) => {
+        runTest(should, {
+          distanceModel: 'linear',
+          // Fairly arbitrary value outside the nominal range
+          rolloffFactor: 2,
+          clampedRolloff: 1
+        }).then(() => task.done());
+      });
+
+      audit.define('inverse-clamp', (task, should) => {
+        runTest(should, {
+          distanceModel: 'inverse',
+          // Fairly arbitrary value outside the nominal range
+          rolloffFactor: -1,
+          clampedRolloff: 0
+        }).then(() => task.done());
+      });
+
+      audit.define('exponential-clamp', (task, should) => {
+        runTest(should, {
+          distanceModel: 'exponential',
+          // Fairly arbitrary value outside the nominal range
+          rolloffFactor: -2,
+          clampedRolloff: 0
+        }).then(() => task.done());
+      });
+
+      // Test clamping of the rolloffFactor.  The test is done by comparing the
+      // output of a panner with the rolloffFactor set outside the nominal range
+      // against the output of a panner with the rolloffFactor clamped to the
+      // nominal range.  The outputs should be the same.
+      //
+      // The |options| dictionary should contain the members
+      //   distanceModel  - The distance model to use for the panners
+      //   rolloffFactor  - The desired rolloffFactor.  Should be outside the
+      //                    nominal range of the distance model.
+      //   clampedRolloff - The rolloffFactor (above) clamped to the nominal
+      //                    range for the given distance model.
+      function runTest(should, options) {
+        // Offline context with two channels.  The first channel is the panner
+        // node under test.  The second channel is the reference panner node.
+        let context = new OfflineAudioContext(2, renderFrames, sampleRate);
+
+        // The source for the panner nodes.  This is fairly arbitrary.
+        let src = new OscillatorNode(context, {type: 'sawtooth'});
+
+        // Create the test panner with the specified rolloff factor.  The
+        // position is fairly arbitrary, but something that is not the default
+        // is good to show the distance model had some effect.
+        let pannerTest = new PannerNode(context, {
+          rolloffFactor: options.rolloffFactor,
+          distanceModel: options.distanceModel,
+          positionX: 5000
+        });
+
+        // Create the reference panner with the rolloff factor clamped to the
+        // appropriate limit.
+        let pannerRef = new PannerNode(context, {
+          rolloffFactor: options.clampedRolloff,
+          distanceModel: options.distanceModel,
+          positionX: 5000
+        });
+
+
+        // Connect the source to the panners to the destination appropriately.
+        let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
+
+
+        src.connect(pannerTest).connect(merger, 0, 0);
+        src.connect(pannerRef).connect(merger, 0, 1);
+
+        merger.connect(context.destination);
+
+        src.start();
+
+        return context.startRendering().then(function(resultBuffer) {
+          // The two channels should be the same due to the clamping.  Verify
+          // that they are the same.
+          let actual = resultBuffer.getChannelData(0);
+          let expected = resultBuffer.getChannelData(1);
+
+          let message = 'Panner distanceModel: "' + options.distanceModel +
+              '", rolloffFactor: ' + options.rolloffFactor;
+
+          should(actual, message).beEqualToArray(expected);
+        });
+      }
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-pannernode-interface/pannernode-basic.html b/webaudio/the-audio-api/the-pannernode-interface/pannernode-basic.html
new file mode 100644
index 0000000..32402f5
--- /dev/null
+++ b/webaudio/the-audio-api/the-pannernode-interface/pannernode-basic.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      pannernode-basic.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+      let panner;
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        should(() => {
+          context = new AudioContext();
+          panner = context.createPanner();
+        }, 'Initialize context and panner').notThrow();
+        task.done();
+      });
+
+      audit.define('basic', (task, should) => {
+        should(panner.numberOfInputs, 'panner.numberOfInputs').beEqualTo(1);
+        should(panner.numberOfOutputs, 'panner.numberOfOutputs').beEqualTo(1);
+        should(panner.refDistance, 'panner.refDistance').beEqualTo(1);
+        panner.refDistance = 270.5;
+        should(panner.refDistance, 'panner.refDistance = 270.5')
+            .beEqualTo(270.5);
+        should(panner.maxDistance, 'panner.maxDistance').beEqualTo(10000);
+        panner.maxDistance = 100.5;
+        should(panner.maxDistance, 'panner.maxDistance = 100.5')
+            .beEqualTo(100.5);
+        should(panner.rolloffFactor, 'panner.rolloffFactor').beEqualTo(1);
+        panner.rolloffFactor = 0.75;
+        should(panner.rolloffFactor, 'panner.rolloffFactor = 0.75')
+            .beEqualTo(0.75);
+        should(panner.coneInnerAngle, 'panner.coneInnerAngle').beEqualTo(360);
+        panner.coneInnerAngle = 240.5;
+        should(panner.coneInnerAngle, 'panner.coneInnerAngle = 240.5')
+            .beEqualTo(240.5);
+        should(panner.coneOuterAngle, 'panner.coneOuterAngle').beEqualTo(360);
+        panner.coneOuterAngle = 166.5;
+        should(panner.coneOuterAngle, 'panner.coneOuterAngle = 166.5')
+            .beEqualTo(166.5);
+        should(panner.coneOuterGain, 'panner.coneOuterGain').beEqualTo(0);
+        panner.coneOuterGain = 0.25;
+        should(panner.coneOuterGain, 'panner.coneOuterGain = 0.25')
+            .beEqualTo(0.25);
+        should(panner.panningModel, 'panner.panningModel')
+            .beEqualTo('equalpower');
+        should(panner.distanceModel)
+            .beEqualTo('inverse', 'panner.distanceModel');
+
+        should(panner.positionX.value, 'panner.positionX').beEqualTo(0);
+        should(panner.positionY.value, 'panner.positionY').beEqualTo(0);
+        should(panner.positionZ.value, 'panner.positionZ').beEqualTo(0);
+        should(panner.orientationX.value, 'panner.orientationX').beEqualTo(1);
+        should(panner.orientationY.value, 'panner.orientationY').beEqualTo(0);
+        should(panner.orientationZ.value, 'panner.orientationZ').beEqualTo(0);
+
+        task.done();
+      });
+
+      audit.define('listener', (task, should) => {
+        should(context.listener.positionX.value, 'listener.positionX')
+            .beEqualTo(0);
+        should(context.listener.positionY.value, 'listener.positionY')
+            .beEqualTo(0);
+        should(context.listener.positionZ.value, 'listener.positionZ')
+            .beEqualTo(0);
+        should(context.listener.forwardX.value, 'listener.forwardX')
+            .beEqualTo(0);
+        should(context.listener.forwardY.value, 'listener.forwardY')
+            .beEqualTo(0);
+        should(context.listener.forwardZ.value, 'listener.forwardZ')
+            .beEqualTo(-1);
+        should(context.listener.upX.value, 'listener.upX').beEqualTo(0);
+        should(context.listener.upY.value, 'listener.upY').beEqualTo(1);
+        should(context.listener.upZ.value, 'listener.upZ').beEqualTo(0);
+
+        task.done();
+      });
+
+      audit.define('panning models', (task, should) => {
+        // Check that the .panningModel attribute can be set to all legal
+        // values.
+        let panningModels = ['equalpower', 'HRTF'];
+
+        for (let i = 0; i < panningModels.length; ++i) {
+          should(function() {
+            panner.panningModel = panningModels[i];
+          }, 'Set panner.panningModel = "' + panningModels[i] + '"').notThrow();
+
+          should(
+              panner.panningModel,
+              'panner.panningModel = "' + panningModels[i] + '"')
+              .beEqualTo(panningModels[i]);
+        }
+
+        should(function() {
+          panner.panningModel = 'invalid';
+        }, 'panner.panningModel = "invalid"').notThrow();
+
+        should(panner.panningModel, 'panner.panningModel after invalid setter')
+            .beEqualTo('HRTF');
+
+        // Check that numerical values are no longer supported.  We shouldn't
+        // throw and the value shouldn't be changed.
+        panner.panningModel = 'HRTF';
+        should(function() {
+          panner.panningModel = 1;
+        }, 'panner.panningModel = 1').notThrow();
+
+        should(panner.panningModel, 'panner.panningModel').beEqualTo('HRTF');
+
+        task.done();
+      });
+
+      audit.define('distance models', (task, should) => {
+        // Check that the .panningModel attribute can be set to all legal
+        // values.
+        let distanceModels = ['linear', 'inverse', 'exponential'];
+
+        for (let i = 0; i < distanceModels.length; ++i) {
+          should(function() {
+            panner.distanceModel = distanceModels[i];
+          }, 'panner.distanceModel = "' + distanceModels[i] + '"').notThrow();
+
+          should(
+              panner.distanceModel,
+              'panner.distanceModel = "' + distanceModels[i] + '"')
+              .beEqualTo(distanceModels[i]);
+        }
+
+        should(function() {
+          panner.distanceModel = 'invalid';
+        }, 'panner.distanceModel = "invalid"').notThrow();
+
+        should(panner.distanceModel, 'panner.distanceModel')
+            .beEqualTo('exponential');
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.html b/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.html
new file mode 100644
index 0000000..9de58cf
--- /dev/null
+++ b/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: StereoPanner
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('invalid constructor', (task, should) => {
+        testInvalidConstructor(should, 'StereoPannerNode', context);
+        task.done();
+      });
+
+      audit.define('default constructor', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'StereoPannerNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'clamped-max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(should, node, prefix, [{name: 'pan', value: 0}]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        // Can't use testAudioNodeOptions because the constraints for this node
+        // are not supported there.
+        let node;
+
+        // An array of tests.
+        [{
+          // Test that we can set the channel count to 1 or 2 and that other
+          // channel counts throw an error.
+          attribute: 'channelCount',
+          tests: [
+            {value: 1}, {value: 2}, {value: 0, error: 'NotSupportedError'},
+            {value: 3, error: 'NotSupportedError'},
+            {value: 99, error: 'NotSupportedError'}
+          ]
+        },
+         {
+           // Test channelCountMode.  A mode of "max" is illegal, but others are
+           // ok.  But also throw an error of unknown values.
+           attribute: 'channelCountMode',
+           tests: [
+             {value: 'clamped-max'}, {value: 'explicit'},
+             {value: 'max', error: 'NotSupportedError'},
+             {value: 'foobar', error: 'TypeError'}
+           ]
+         },
+         {
+           // Test channelInterpretation can be set for valid values and an
+           // error is thrown for others.
+           attribute: 'channelInterpretation',
+           tests: [
+             {value: 'speakers'}, {value: 'discrete'},
+             {value: 'foobar', error: 'TypeError'}
+           ]
+         }].forEach(entry => {
+          entry.tests.forEach(testItem => {
+            let options = {};
+            options[entry.attribute] = testItem.value;
+            let method = testItem.error ? 'throw' : 'notThrow';
+
+            should(
+                () => {
+                  node = new StereoPannerNode(context, options);
+                },
+                `new StereoPannerNode(c, ${JSON.stringify(options)})`)[method](
+                testItem.error);
+            if (!testItem.error)
+              should(node[entry.attribute], `node.${entry.attribute}`)
+                  .beEqualTo(options[entry.attribute]);
+          });
+        });
+
+        task.done();
+      });
+
+      audit.define('constructor with options', (task, should) => {
+        let node;
+        let options = {
+          pan: 0.75,
+        };
+
+        should(
+            () => {
+              node = new StereoPannerNode(context, options);
+            },
+            'node1 = new StereoPannerNode(, ' + JSON.stringify(options) + ')')
+            .notThrow();
+        should(
+            node instanceof StereoPannerNode,
+            'node1 instanceof StereoPannerNode')
+            .beEqualTo(true);
+
+        should(node.pan.value, 'node1.pan.value').beEqualTo(options.pan);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-stereopanner-interface/no-dezippering.html b/webaudio/the-audio-api/the-stereopanner-interface/no-dezippering.html
new file mode 100644
index 0000000..a61f398
--- /dev/null
+++ b/webaudio/the-audio-api/the-stereopanner-interface/no-dezippering.html
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test StereoPannerNode Has No Dezippering
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Arbitrary sample rate except that it should be a power of two to
+      // eliminate any round-off in computing frame boundaries.
+      let sampleRate = 16384;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test mono input',
+            description: 'Test StereoPanner with mono input has no dezippering'
+          },
+          (task, should) => {
+            let context = new OfflineAudioContext(2, sampleRate, sampleRate);
+            let src = new ConstantSourceNode(context, {offset: 1});
+            let p = new StereoPannerNode(context, {pan: -1});
+
+            src.connect(p).connect(context.destination);
+            src.start();
+
+            // Frame at which to change pan value.
+            let panFrame = 256;
+            context.suspend(panFrame / context.sampleRate)
+                .then(() => p.pan.value = 1)
+                .then(() => context.resume());
+
+            context.startRendering()
+                .then(renderedBuffer => {
+                  let c0 = renderedBuffer.getChannelData(0);
+                  let c1 = renderedBuffer.getChannelData(1);
+
+                  // The first part should be full left.
+                  should(
+                      c0.slice(0, panFrame), 'Mono: Left channel, pan = -1: ')
+                      .beConstantValueOf(1);
+                  should(
+                      c1.slice(0, panFrame), 'Mono: Right channel, pan = -1:')
+                      .beConstantValueOf(0);
+
+                  // The second part should be full right, but due to roundoff,
+                  // the left channel won't be exactly zero.  Compare the left
+                  // channel against zero with a threshold instead.
+                  let tail = c0.slice(panFrame);
+                  let zero = new Float32Array(tail.length);
+
+                  should(c0.slice(panFrame), 'Mono: Left channel, pan = 1: ')
+                      .beCloseToArray(zero, {absoluteThreshold: 6.1233e-17});
+                  should(c1.slice(panFrame), 'Mono: Right channel, pan = 1:')
+                      .beConstantValueOf(1);
+                })
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'test stereo input',
+            description:
+                'Test StereoPanner with stereo input has no dezippering'
+          },
+          (task, should) => {
+            let context = new OfflineAudioContext(2, sampleRate, sampleRate);
+
+            // Create stereo source from two constant source nodes.
+            let s0 = new ConstantSourceNode(context, {offset: 1});
+            let s1 = new ConstantSourceNode(context, {offset: 2});
+            let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
+
+            s0.connect(merger, 0, 0);
+            s1.connect(merger, 0, 1);
+
+            let p = new StereoPannerNode(context, {pan: -1});
+
+            merger.connect(p).connect(context.destination);
+            s0.start();
+            s1.start();
+
+            // Frame at which to change pan value.
+            let panFrame = 256;
+            context.suspend(panFrame / context.sampleRate)
+                .then(() => p.pan.value = 1)
+                .then(() => context.resume());
+
+            context.startRendering()
+                .then(renderedBuffer => {
+                  let c0 = renderedBuffer.getChannelData(0);
+                  let c1 = renderedBuffer.getChannelData(1);
+
+                  // The first part should be full left.
+                  should(
+                      c0.slice(0, panFrame), 'Stereo: Left channel, pan = -1: ')
+                      .beConstantValueOf(3);
+                  should(
+                      c1.slice(0, panFrame), 'Stereo: Right channel, pan = -1:')
+                      .beConstantValueOf(0);
+
+                  // The second part should be full right, but due to roundoff,
+                  // the left channel won't be exactly zero.  Compare the left
+                  // channel against zero with a threshold instead.
+                  let tail = c0.slice(panFrame);
+                  let zero = new Float32Array(tail.length);
+
+                  should(c0.slice(panFrame), 'Stereo: Left channel, pan = 1: ')
+                      .beCloseToArray(zero, {absoluteThreshold: 6.1233e-17});
+                  should(c1.slice(panFrame), 'Stereo: Right channel, pan = 1:')
+                      .beConstantValueOf(3);
+                })
+                .then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'test mono input setValue',
+            description: 'Test StereoPanner with mono input value setter ' +
+                'vs setValueAtTime'
+          },
+          (task, should) => {
+            let context = new OfflineAudioContext(4, sampleRate, sampleRate);
+
+            let src = new OscillatorNode(context);
+
+            src.start();
+            testWithSetValue(context, src, should, {
+              prefix: 'Mono'
+            }).then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'test stereo input setValue',
+            description: 'Test StereoPanner with mono input value setter ' +
+                ' vs setValueAtTime'
+          },
+          (task, should) => {
+            let context = new OfflineAudioContext(4, sampleRate, sampleRate);
+
+            let src0 = new OscillatorNode(context, {frequency: 800});
+            let src1 = new OscillatorNode(context, {frequency: 250});
+            let merger = new ChannelMergerNode(context, {numberOfChannels: 2});
+
+            src0.connect(merger, 0, 0);
+            src1.connect(merger, 0, 1);
+
+            src0.start();
+            src1.start();
+
+            testWithSetValue(context, merger, should, {
+              prefix: 'Stereo'
+            }).then(() => task.done());
+          });
+
+      audit.define(
+          {
+            label: 'test mono input automation',
+            description: 'Test StereoPanner with mono input and automation'
+          },
+          (task, should) => {
+            let context = new OfflineAudioContext(4, sampleRate, sampleRate);
+
+            let src0 = new OscillatorNode(context, {frequency: 800});
+            let src1 = new OscillatorNode(context, {frequency: 250});
+            let merger = new ChannelMergerNode(context, {numberOfChannels: 2});
+
+            src0.connect(merger, 0, 0);
+            src1.connect(merger, 0, 1);
+
+            src0.start();
+            src1.start();
+
+            let mod = new OscillatorNode(context, {frequency: 100});
+            mod.start();
+
+            testWithSetValue(context, merger, should, {
+              prefix: 'Modulated Stereo',
+              modulator: (testNode, refNode) => {
+                mod.connect(testNode.pan);
+                mod.connect(refNode.pan);
+              }
+            }).then(() => task.done());
+          });
+
+
+      function testWithSetValue(context, src, should, options) {
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+        merger.connect(context.destination);
+
+        let pannerRef = new StereoPannerNode(context, {pan: -0.3});
+        let pannerTest =
+            new StereoPannerNode(context, {pan: pannerRef.pan.value});
+
+        let refSplitter =
+            new ChannelSplitterNode(context, {numberOfOutputs: 2});
+        let testSplitter =
+            new ChannelSplitterNode(context, {numberOfOutputs: 2});
+
+        pannerRef.connect(refSplitter);
+        pannerTest.connect(testSplitter);
+
+        testSplitter.connect(merger, 0, 0);
+        testSplitter.connect(merger, 1, 1);
+        refSplitter.connect(merger, 0, 2);
+        refSplitter.connect(merger, 1, 3);
+
+        src.connect(pannerRef);
+        src.connect(pannerTest);
+
+        let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;
+        // An arbitrary position, different from the default pan value.
+        let newPanPosition = .71;
+
+        pannerRef.pan.setValueAtTime(newPanPosition, changeTime);
+        context.suspend(changeTime)
+            .then(() => pannerTest.pan.value = newPanPosition)
+            .then(() => context.resume());
+
+        if (options.modulator) {
+          options.modulator(pannerTest, pannerRef);
+        }
+        return context.startRendering().then(renderedBuffer => {
+          let actual = new Array(2);
+          let expected = new Array(2);
+
+          actual[0] = renderedBuffer.getChannelData(0);
+          actual[1] = renderedBuffer.getChannelData(1);
+          expected[0] = renderedBuffer.getChannelData(2);
+          expected[1] = renderedBuffer.getChannelData(3);
+
+          let label = ['Left', 'Right'];
+
+          for (let k = 0; k < 2; ++k) {
+            let match =
+                should(
+                    actual[k],
+                    options.prefix + ' ' + label[k] + ' .value setter output')
+                    .beEqualToArray(expected[k]);
+            should(
+                match,
+                options.prefix + ' ' + label[k] +
+                    ' .value setter output matches setValueAtTime output')
+                .beTrue();
+          }
+
+        });
+      }
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-stereopanner-interface/stereopannernode-basic.html b/webaudio/the-audio-api/the-stereopanner-interface/stereopannernode-basic.html
new file mode 100644
index 0000000..48bacb0
--- /dev/null
+++ b/webaudio/the-audio-api/the-stereopanner-interface/stereopannernode-basic.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      stereopannernode-basic.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'Attributes and basic functionality of StereoPannerNode'
+          },
+          (task, should) => {
+
+            let context = new AudioContext();
+            let panner = context.createStereoPanner();
+
+            should(panner.numberOfInputs, 'panner.numberOfInputs').beEqualTo(1);
+            should(panner.numberOfOutputs, 'panner.numberOfOutputs')
+                .beEqualTo(1);
+            should(panner.pan.defaultValue, 'panner.pan.defaultValue')
+                .beEqualTo(0.0);
+            should(() => panner.pan.value = 1.0, 'panner.pan.value = 1.0')
+                .notThrow();
+            should(panner.pan.value, 'panner.pan.value').beEqualTo(1.0);
+
+            should(() => panner.channelCount = 1, 'panner.channelCount = 1')
+                .notThrow();
+            should(() => panner.channelCount = 3, 'panner.channelCount = 3')
+                .throw();
+            should(
+                () => panner.channelCountMode = 'explicit',
+                'panner.channelCountMode = "explicit"')
+                .notThrow();
+            should(
+                () => panner.channelCountMode = 'max',
+                'panner.channelCountMode = "max"')
+                .throw();
+
+            task.done();
+          });
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-stereopanner-interface/stereopannernode-panning.html b/webaudio/the-audio-api/the-stereopanner-interface/stereopannernode-panning.html
new file mode 100644
index 0000000..f683fd7
--- /dev/null
+++ b/webaudio/the-audio-api/the-stereopanner-interface/stereopannernode-panning.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      stereopannernode-panning.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/stereopanner-testing.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define('mono-test', (task, should) => {
+        StereoPannerTest
+            .create(should, {numberOfInputChannels: 1, prefix: 'Mono: '})
+            .run()
+            .then(() => task.done());
+      });
+
+      audit.define('stereo-test', (task, should) => {
+        StereoPannerTest
+            .create(should, {numberOfInputChannels: 2, prefix: 'Stereo: '})
+            .run()
+            .then(() => task.done());
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-waveshapernode-interface/ctor-waveshaper.html b/webaudio/the-audio-api/the-waveshapernode-interface/ctor-waveshaper.html
new file mode 100644
index 0000000..7aa33ca
--- /dev/null
+++ b/webaudio/the-audio-api/the-waveshapernode-interface/ctor-waveshaper.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test Constructor: WaveShaper
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audionodeoptions.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let context;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('initialize', (task, should) => {
+        context = initializeContext(should);
+        task.done();
+      });
+
+      audit.define('incorrect construction', (task, should) => {
+        testInvalidConstructor(should, 'WaveShaperNode', context);
+        task.done();
+      });
+
+      audit.define('valid default construction', (task, should) => {
+        let prefix = 'node0';
+        let node = testDefaultConstructor(should, 'WaveShaperNode', context, {
+          prefix: prefix,
+          numberOfInputs: 1,
+          numberOfOutputs: 1,
+          channelCount: 2,
+          channelCountMode: 'max',
+          channelInterpretation: 'speakers'
+        });
+
+        testDefaultAttributes(should, node, prefix, [
+          {name: 'curve', value: null}, {name: 'oversample', value: 'none'}
+        ]);
+
+        task.done();
+      });
+
+      audit.define('test AudioNodeOptions', (task, should) => {
+        testAudioNodeOptions(should, context, 'WaveShaperNode');
+        task.done();
+      });
+
+      audit.define('valid non-default', (task, should) => {
+        // Construct an WaveShaperNode with options
+        let options = {curve: Float32Array.from([1, 2, 3]), oversample: '4x'};
+        let node;
+
+        let message =
+            'node1 = new WaveShaperNode(, ' + JSON.stringify(options) + ')';
+        should(() => {
+          node = new WaveShaperNode(context, options);
+        }, message).notThrow();
+        should(node.curve, 'node1.curve').beEqualToArray(options.curve);
+        should(node.oversample, 'node1.oversample')
+            .beEqualTo(options.oversample);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-copy-curve.html b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-copy-curve.html
new file mode 100644
index 0000000..e897ac0
--- /dev/null
+++ b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-copy-curve.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Test WaveShaper Copies Curve Data
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      // Sample rate and number of frames are fairly arbitrary.  We need to
+      // render, however, at least 384 frames.  1024 is a nice small value.
+      let sampleRate = 16000;
+      let renderFrames = 1024;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {
+            label: 'test copying',
+            description: 'Modifying curve should not modify WaveShaper'
+          },
+          (task, should) => {
+            // Two-channel context; channel 0 contains the test data and channel
+            // 1 contains the expected result.  Channel 1 has the normal
+            // WaveShaper output and channel 0 has the WaveShaper output with a
+            // modified curve.
+            let context = new OfflineAudioContext(2, renderFrames, sampleRate);
+
+            // Just use a default oscillator as the source.  Doesn't really
+            // matter what we use.
+            let src = context.createOscillator();
+            src.type = 'sawtooth';
+
+            // Create the wave shapers: ws0 is the test shaper, and ws1 is the
+            // reference wave shaper.
+            let ws0 = context.createWaveShaper();
+            let ws1 = context.createWaveShaper();
+
+            // Wave shaper curves.  Doesn't really matter what we use as long as
+            // it modifies the input in some way.  Thus, keep it simple and just
+            // invert the input.
+            let desiredCurve = [1, 0, -1];
+            let curve0 = Float32Array.from(desiredCurve);
+            let curve1 = Float32Array.from(desiredCurve);
+
+            ws0.curve = curve0;
+            ws1.curve = curve1;
+
+            let merger = context.createChannelMerger(2);
+
+            // Connect the graph
+            src.connect(ws0);
+            src.connect(ws1);
+
+            ws0.connect(merger, 0, 0);
+            ws1.connect(merger, 0, 1);
+
+            merger.connect(context.destination);
+
+            // Let the context run for a bit and then modify the curve for ws0.
+            // Doesn't really matter what we modify the curve to as long as it's
+            // different.
+            context.suspend(256 / context.sampleRate)
+                .then(() => {
+                  should(
+                      () => {
+                        curve0[0] = -0.5;
+                        curve0[1] = 0.125;
+                        curve0[2] = 0.75;
+                      },
+                      `Modifying curve array at time ${context.currentTime}`)
+                      .notThrow();
+                })
+                .then(context.resume.bind(context));
+
+            src.start();
+
+            context.startRendering()
+                .then(function(renderedBuffer) {
+                  let actual = renderedBuffer.getChannelData(0);
+                  let expected = renderedBuffer.getChannelData(1);
+
+                  // Modifying the wave shaper curve should not modify the
+                  // output so the outputs from the two wave shaper nodes should
+                  // be exactly identical.
+                  should(actual, 'Output of WaveShaper with modified curve')
+                      .beEqualToArray(expected);
+
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-limits.html b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-limits.html
new file mode 100644
index 0000000..13e88be
--- /dev/null
+++ b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-limits.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      waveshaper-limits.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let context;
+      let bufferData;
+      let outputData;
+      let reference;
+
+      let sampleRate = 48000;
+      // Must be odd so we have an exact middle point.
+      let testFrames = 23;
+      let scale = 1 / ((testFrames - 1) / 2 - 1);
+      // Number of decimal digits to print
+      let decimals = 6;
+      // Required accuracy
+      let diffThreshold = Math.pow(10, -decimals);
+
+      // Generate reference data
+      function generateReference() {
+        // The curve data is 0, 1, 0, and the input data is a ramp from -1+eps
+        // to 1+eps.  Then the output is a ramp from 0 to 1 back to 0.
+        let ref = new Float32Array(testFrames);
+        let midPoint = (testFrames - 1) / 2;
+        // First sample is below -1 at -1-scale.
+        ref[0] = 0;
+        // Generate ramp up to the mid-point
+        for (let k = 0; k < midPoint; ++k) {
+          ref[k + 1] = k * scale;
+        }
+        // The value at the mid-point must be 1, from the curve
+        ref[midPoint] = 1;
+        // Generate a ramp from 1 down to 0
+        for (let k = midPoint; k < testFrames - 1; ++k) {
+          ref[k + 1] = 2 - k * scale;
+        }
+        // The last sample is out of range at 1+scale
+        ref[testFrames - 1] = 0;
+        return ref;
+      }
+
+      function checkResult(renderedBuffer, should) {
+        outputData = renderedBuffer.getChannelData(0);
+        reference = generateReference();
+        let success = true;
+        // Verify that every output value matches our expected reference value.
+        for (let k = 0; k < outputData.length; ++k) {
+          let diff = outputData[k] - reference[k];
+          should(
+              Math.abs(diff),
+              'Max error mapping ' + bufferData[k].toFixed(decimals) + ' to ' +
+                  outputData[k].toFixed(decimals))
+              .beLessThanOrEqualTo(diffThreshold);
+        }
+      }
+
+      audit.define(
+          {
+            label: 'test',
+            description:
+                'WaveShaperNode including values outside the range of [-1,1]'
+          },
+          function(task, should) {
+            context = new OfflineAudioContext(1, testFrames, sampleRate);
+            // Create input values between -1.1 and 1.1
+            let buffer =
+                context.createBuffer(1, testFrames, context.sampleRate);
+            bufferData = new Float32Array(testFrames);
+            let start = -1 - scale;
+            for (let k = 0; k < testFrames; ++k) {
+              bufferData[k] = k * scale + start;
+            }
+            buffer.copyToChannel(bufferData, 0);
+
+            let source = context.createBufferSource();
+            source.buffer = buffer;
+
+            // Create simple waveshaper. It should map -1 to 0, 0 to 1, and +1
+            // to 0 and interpolate all points in between using a simple linear
+            // interpolator.
+            let shaper = context.createWaveShaper();
+            let curve = new Float32Array(3);
+            curve[0] = 0;
+            curve[1] = 1;
+            curve[2] = 0;
+            shaper.curve = curve;
+            source.connect(shaper);
+            shaper.connect(context.destination);
+
+            source.start();
+            context.startRendering()
+                .then(buffer => checkResult(buffer, should))
+                .then(() => task.done());
+          });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-simple.html b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-simple.html
new file mode 100644
index 0000000..affd0c5
--- /dev/null
+++ b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper-simple.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      Simple Tests of WaveShaperNode
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      audit.define('simple', (task, should) => {
+        let context = new OfflineAudioContext(1, 1, 48000);
+        let shaper = context.createWaveShaper();
+
+        // Verify default values are correct.
+        should(shaper.curve, 'Initial WaveShaper.curve').beEqualTo(null);
+        should(shaper.oversample, 'Initial WaveShaper.oversample')
+            .beEqualTo('none');
+
+        // Set oversample and verify that it is set correctly.
+        should(() => shaper.oversample = '2x', 'Setting oversample to "2x"')
+            .notThrow();
+        should(shaper.oversample, 'Waveshaper.oversample = "2x"')
+            .beEqualTo('2x');
+
+        should(() => shaper.oversample = '4x', 'Setting oversample to "4x"')
+            .notThrow();
+        should(shaper.oversample, 'Waveshaper.oversample = "4x"')
+            .beEqualTo('4x');
+
+        should(
+            () => shaper.oversample = 'invalid',
+            'Setting oversample to "invalid"')
+            .notThrow();
+        should(shaper.oversample, 'Waveshaper.oversample = "invalid"')
+            .beEqualTo('4x');
+
+        // Set the curve and verify that the returned curve is the same as what
+        // it was set to.
+        let curve = Float32Array.from([-1, 0.25, .75]);
+        should(() => shaper.curve = curve, 'Setting curve to [' + curve + ']')
+            .notThrow();
+        should(shaper.curve, 'WaveShaper.curve').beEqualToArray(curve);
+
+        // Verify setting the curve to null works.
+        should(() => shaper.curve = null, 'Setting curve back to null')
+            .notThrow();
+        should(shaper.curve, 'Waveshaper.curve = null').beEqualTo(null);
+
+        task.done();
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper.html b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper.html
new file mode 100644
index 0000000..8bfa009
--- /dev/null
+++ b/webaudio/the-audio-api/the-waveshapernode-interface/waveshaper.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>
+      waveshaper.html
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="../../resources/audit-util.js"></script>
+    <script src="../../resources/audit.js"></script>
+    <script src="../../resources/buffer-loader.js"></script>
+  </head>
+  <body>
+    <script id="layout-test-code">
+      let audit = Audit.createTaskRunner();
+
+      let sampleRate = 44100;
+      let lengthInSeconds = 4;
+      let numberOfRenderFrames = sampleRate * lengthInSeconds;
+      let numberOfCurveFrames = 65536;
+      let inputBuffer;
+      let waveShapingCurve;
+
+      let context;
+
+      function generateInputBuffer() {
+        // Create mono input buffer.
+        let buffer =
+            context.createBuffer(1, numberOfRenderFrames, context.sampleRate);
+        let data = buffer.getChannelData(0);
+
+        // Generate an input vector with values from -1 -> +1 over a duration of
+        // lengthInSeconds. This exercises the full nominal input range and will
+        // touch every point of the shaping curve.
+        for (let i = 0; i < numberOfRenderFrames; ++i) {
+          let x = i / numberOfRenderFrames;  // 0 -> 1
+          x = 2 * x - 1;                     // -1 -> +1
+          data[i] = x;
+        }
+
+        return buffer;
+      }
+
+      // Generates a symmetric curve: Math.atan(5 * x) / (0.5 * Math.PI)
+      // (with x == 0 corresponding to the center of the array)
+      // This curve is arbitrary, but would be useful in the real-world.
+      // To some extent, the actual curve we choose is not important in this
+      // test, since the input vector walks through all possible curve values.
+      function generateWaveShapingCurve() {
+        let curve = new Float32Array(numberOfCurveFrames);
+
+        let n = numberOfCurveFrames;
+        let n2 = n / 2;
+
+        for (let i = 0; i < n; ++i) {
+          let x = (i - n2) / n2;
+          let y = Math.atan(5 * x) / (0.5 * Math.PI);
+        }
+
+        return curve;
+      }
+
+      function checkShapedCurve(buffer, should) {
+        let inputData = inputBuffer.getChannelData(0);
+        let outputData = buffer.getChannelData(0);
+
+        let success = true;
+
+        // Go through every sample and make sure it has been shaped exactly
+        // according to the shaping curve we gave it.
+        for (let i = 0; i < buffer.length; ++i) {
+          let input = inputData[i];
+
+          // Calculate an index based on input -1 -> +1 with 0 being at the
+          // center of the curve data.
+          let index = Math.floor(numberOfCurveFrames * 0.5 * (input + 1));
+
+          // Clip index to the input range of the curve.
+          // This takes care of input outside of nominal range -1 -> +1
+          index = index < 0 ? 0 : index;
+          index =
+              index > numberOfCurveFrames - 1 ? numberOfCurveFrames - 1 : index;
+
+          let expectedOutput = waveShapingCurve[index];
+
+          let output = outputData[i];
+
+          if (output != expectedOutput) {
+            success = false;
+            break;
+          }
+        }
+
+        should(
+            success, 'WaveShaperNode applied non-linear distortion correctly')
+            .beTrue();
+      }
+
+      audit.define('test', function(task, should) {
+        // Create offline audio context.
+        context = new OfflineAudioContext(1, numberOfRenderFrames, sampleRate);
+
+        // source -> waveshaper -> destination
+        let source = context.createBufferSource();
+        let waveshaper = context.createWaveShaper();
+        source.connect(waveshaper);
+        waveshaper.connect(context.destination);
+
+        // Create an input test vector.
+        inputBuffer = generateInputBuffer();
+        source.buffer = inputBuffer;
+
+        // We'll apply non-linear distortion according to this shaping curve.
+        waveShapingCurve = generateWaveShapingCurve();
+        waveshaper.curve = waveShapingCurve;
+
+        source.start(0);
+
+        context.startRendering()
+            .then(buffer => checkShapedCurve(buffer, should))
+            .then(task.done.bind(task));
+      });
+
+      audit.run();
+    </script>
+  </body>
+</html>
diff --git a/webauthn/OWNERS b/webauthn/OWNERS
index 8fc1758..1388899 100644
--- a/webauthn/OWNERS
+++ b/webauthn/OWNERS
@@ -1 +1,2 @@
 @apowers313
+@jcjones
diff --git a/webauthn/createcredential-badargs-attestation.https.html b/webauthn/createcredential-badargs-attestation.https.html
new file mode 100644
index 0000000..a56f4f0
--- /dev/null
+++ b/webauthn/createcredential-badargs-attestation.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() attestation parameter Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // attestation bad values
+    new CreateCredentialsTest("options.publicKey.attestation", {}).runTest("Bad attestation parameter: attestation is empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", []).runTest("Bad attestation parameter: attestation is empty array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", null).runTest("Bad attestation parameter: attestation is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", "noneofyourbusiness").runTest("Bad attestation parameter: attestation is \"noneofyourbusiness\"", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", "").runTest("Bad attestation parameter: attestation is empty string", new TypeError());
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-badargs-authnrselection.https.html b/webauthn/createcredential-badargs-authnrselection.https.html
new file mode 100644
index 0000000..2c42135
--- /dev/null
+++ b/webauthn/createcredential-badargs-authnrselection.https.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() authenticator selection Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var defaultAuthnrSel = {
+        authenticatorAttachment: "cross-platform",
+        requireResidentKey: false,
+        userVerification: "preferred"
+    };
+    // attachment
+    var authnrSelAttachPlatform = cloneObject(defaultAuthnrSel);
+    authnrSelAttachPlatform.authenticatorAttachment = "platform";
+    var authnrSelBadAttachEmptyStr = cloneObject(defaultAuthnrSel);
+    authnrSelBadAttachEmptyStr.authenticatorAttachment = "";
+    var authnrSelBadAttachEmptyObj = cloneObject(defaultAuthnrSel);
+    authnrSelBadAttachEmptyObj.authenticatorAttachment = {};
+    var authnrSelBadAttachNull = cloneObject(defaultAuthnrSel);
+    authnrSelBadAttachNull.authenticatorAttachment = null;
+    // resident key
+    var authnrSelRkTrue = cloneObject(defaultAuthnrSel);
+    authnrSelRkTrue.requireResidentKey = true;
+    var authnrSelRkBadString = cloneObject(defaultAuthnrSel);
+    authnrSelRkBadString.requireResidentKey = "foo";
+    // user verification
+    var authnrSelUvRequired = cloneObject(defaultAuthnrSel);
+    authnrSelUvRequired.userVerification = "required";
+    var authnrSelBadUvEmptyStr = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvEmptyStr.userVerification = "";
+    var authnrSelBadUvEmptyObj = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvEmptyObj.userVerification = {};
+    var authnrSelBadUvStr = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvStr.userVerification = "requiredshirtshoestshirt";
+    var authnrSelBadUvNull = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvNull.userVerification = null;
+
+    // authenticatorSelection bad values
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", []).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is empty array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", null).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", "").runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is empty string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", "none").runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is string", new TypeError());
+
+    // authenticatorSelection bad attachment values
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadAttachEmptyStr).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment is empty string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadAttachEmptyObj).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment is empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadAttachNull).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment is null", new TypeError());
+    // XXX: assumes authnr is behaving like most U2F authnrs; really depends on the authnr or mock configuration
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelAttachPlatform).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment platform", "NotAllowedError");
+
+    // authenticatorSelection bad requireResidentKey values
+   // XXX: assumes authnr is behaving like most U2F authnrs; really depends on the authnr or mock configuration
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkTrue).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection residentKey true", "NotAllowedError");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkBadString).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection residentKey is string", new TypeError());
+    // TODO: not sure if rk is "boolean" or "truthy"; add test cases if it should only accept boolean values
+
+    // authenticatorSelection bad userVerification values
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvEmptyStr).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification empty string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvEmptyObj).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvStr).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification bad value", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvNull).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification null", new TypeError());
+    // XXX: assumes this is a mock authenticator the properly reports that it is not doing userVerfication
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelUvRequired).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification required", "NotAllowedError");
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, cloneObject */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-badargs-challenge.https.html b/webauthn/createcredential-badargs-challenge.https.html
new file mode 100644
index 0000000..0ad67f2
--- /dev/null
+++ b/webauthn/createcredential-badargs-challenge.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() challenge Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // bad challenge values
+    new CreateCredentialsTest({path: "options.publicKey.challenge", value: undefined}).runTest("Bad challenge: challenge missing", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", "hi mom").runTest("Bad challenge: challenge is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", null).runTest("Bad challenge: challenge is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", {}).runTest("Bad challenge: challenge is empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", new Array()).runTest("Bad challenge: challenge is empty Array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", new ArrayBuffer(0)).runTest("Bad challenge: challenge is empty ArrayBuffer", new TypeError());
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-badargs-rp.https.html b/webauthn/createcredential-badargs-rp.https.html
new file mode 100644
index 0000000..9958b2c
--- /dev/null
+++ b/webauthn/createcredential-badargs-rp.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() rp Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // rp bad values
+    new CreateCredentialsTest({path: "options.publicKey.rp", value: undefined}).runTest("Bad rp: rp missing", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp", "hi mom").runTest("Bad rp: rp is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp", {}).runTest("Bad rp: rp is empty object", new TypeError());
+
+    // // rp.id
+    new CreateCredentialsTest("options.publicKey.rp.id", {}).runTest("Bad rp: id is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.id", null).runTest("Bad rp: id is null", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "").runTest("Bad rp: id is empty String", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "invalid domain.com").runTest("Bad rp: id is invalid domain (has space)", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "-invaliddomain.com").runTest("Bad rp: id is invalid domain (starts with dash)", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "0invaliddomain.com").runTest("Bad rp: id is invalid domain (starts with number)", "SecurityError");
+
+    // // rp.name
+    new CreateCredentialsTest({path: "options.publicKey.rp.name", value: undefined}).runTest("rp missing name", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.name", {}).runTest("Bad rp: name is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.name", null).runTest("Bad rp: name is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.name", "").runTest("Bad rp: name is empty String", new TypeError());
+
+    // // rp.icon
+    new CreateCredentialsTest("options.publicKey.rp.icon", {}).runTest("Bad rp: icon is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.icon", null).runTest("Bad rp: icon is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.icon", "").runTest("Bad rp: icon is empty String", new TypeError());
+    // // TODO: unicode tests for icon URL (see also: USVString)
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-badargs-user.https.html b/webauthn/createcredential-badargs-user.https.html
new file mode 100644
index 0000000..dd1870a
--- /dev/null
+++ b/webauthn/createcredential-badargs-user.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() user Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // user bad values
+    new CreateCredentialsTest({path: "options.publicKey.user", value: undefined}).runTest("Bad user: user missing", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user", "hi mom").runTest("Bad user: user is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user", {}).runTest("Bad user: user is empty object", new TypeError());
+
+    // // user.id
+    new CreateCredentialsTest({path: "options.publicKey.user.id", value: undefined}).runTest("Bad user: id is undefined", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", {}).runTest("Bad user: id is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", null).runTest("Bad user: id is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", "").runTest("Bad user: id is empty String", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Array()).runTest("Bad user: id is empty Array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(0)).runTest("Bad user: id is empty ArrayBuffer", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(65)).runTest("Bad user: ArrayBuffer id is too long (65 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Int16Array(33)).runTest("Bad user: Int16Array id is too long (66 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Int32Array(17)).runTest("Bad user: Int32Array id is too long (68 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Float32Array(17)).runTest("Bad user: Float32Array id is too long (68 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Float64Array(9)).runTest("Bad user: Float64Array id is too long (72 bytes)", new TypeError());
+    var buf = new ArrayBuffer(65);
+    new CreateCredentialsTest("options.publicKey.user.id", new DataView(buf)).runTest("Bad user: id is too long (65 bytes)", new TypeError());
+
+    // // user.name
+    new CreateCredentialsTest({path: "options.publicKey.user.name", value: undefined}).runTest("user missing name", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.name", {}).runTest("Bad user: name is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.name", null).runTest("Bad user: name is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.name", "").runTest("Bad user: name is empty String", new TypeError());
+
+    // // user.icon
+    new CreateCredentialsTest("options.publicKey.user.icon", {}).runTest("Bad user: icon is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.icon", null).runTest("Bad user: icon is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.icon", "").runTest("Bad user: icon is empty String", new TypeError());
+    // // TODO: unicode tests for icon URL (see also: USVString)
+
+    // // user.displayName
+    new CreateCredentialsTest({path: "options.publicKey.user.displayName", value: undefined}).runTest("Bad user: displayName is undefined", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.displayName", {}).runTest("Bad user: displayName is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.displayName", null).runTest("Bad user: displayName is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.displayName", "").runTest("Bad user: displayName is empty String", new TypeError());
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-excludecredentials.https.html b/webauthn/createcredential-excludecredentials.https.html
new file mode 100644
index 0000000..a4cfb0a
--- /dev/null
+++ b/webauthn/createcredential-excludecredentials.https.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() excludeCredentials Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // bad excludeCredentials values
+    new CreateCredentialsTest("options.publicKey.excludeCredentials", "hi mom").runTest("Bad excludeCredentials: string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.excludeCredentials", {}).runTest("Bad excludeCredentials: empty object", new TypeError());
+    // TODO: bad excludeCredentials with [{.type}] or [{.id}] or [{.transports}] wrong
+
+    // good excludeCredentials values
+    new CreateCredentialsTest({path: "options.publicKey.excludeCredentials", value: undefined}).runTest("excludeCredentials missing");
+    new CreateCredentialsTest("options.publicKey.excludeCredentials", []).runTest("excludeCredentials empty array");
+
+    // proper excludeCredentials behavior
+    // should error on excluding existing credential
+    promise_test((t) => {
+        var cred1;
+        return Promise.resolve()
+            .then(() => {
+                return createCredential();
+            })
+            .then((cred) => {
+                cred1 = cred;
+                var excludeCred = {
+                    id: cred.rawId,
+                    type: "public-key"
+                };
+                var args = {
+                    options: {
+                        publicKey: {
+                            excludeCredentials: [excludeCred]
+                        }
+                    }
+                };
+                var p = createCredential(args);
+                return promise_rejects (t, "NotAllowedError", p, "expected to fail on excluded credenetial");
+            });
+    }, "exclude existing credential");
+
+    // should not error on excluding random credential
+    promise_test(() => {
+        return Promise.resolve()
+            .then(() => {
+                return createCredential();
+            })
+            .then(() => {
+                var randomCredId = new Uint8Array(162);
+                window.crypto.getRandomValues(randomCredId);
+
+                var excludeCred = {
+                    id: randomCredId,
+                    type: "public-key"
+                };
+                var args = {
+                    options: {
+                        publicKey: {
+                            excludeCredentials: [excludeCred]
+                        }
+                    }
+                };
+                return createCredential(args);
+            });
+    }, "exclude random (non-existing) credential");
+
+    // TODO: exclude including transport type (USB, BLE, NFC)
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, createCredential, promise_test, promise_rejects */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-extensions.https.html b/webauthn/createcredential-extensions.https.html
new file mode 100644
index 0000000..6d0e3e3
--- /dev/null
+++ b/webauthn/createcredential-extensions.https.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() extensions Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var dummyExtension = {
+        foo: true,
+        bar: "yup"
+    };
+
+    // bad extension values
+    new CreateCredentialsTest("options.publicKey.extensions", "hi mom").runTest("Bad extensions: extensions is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", null).runTest("Bad extensions: extensions is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", []).runTest("Bad extensions: extensions is empty Array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", new ArrayBuffer(0)).runTest("Bad extensions: extensions is empty ArrayBuffer", new TypeError());
+    var badJson = '{"foo": true, "bar: "yup"}'; // missing quote after "bar"
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: badJson}).runTest("Bad extensions: malformatted JSON", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: dummyExtension}).runTest("Bad extensions: JavaScript object", new TypeError());
+    var badExtId = {};
+    badExtId[createRandomString(65)] = dummyExtension;
+    new CreateCredentialsTest("options.publicKey.extensions", {badExtId: dummyExtension}).runTest("Bad extensions: extension ID too long", new TypeError());
+
+    // phony extensions
+    // TODO: not sure if this should pass or fail
+    // should be clarified as part of https://github.com/w3c/webauthn/pull/765
+    var randomExtId = {};
+    randomExtId[createRandomString(64)] = dummyExtension;
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: JSON.stringify(randomExtId)}).runTest("extensions is a nonsensical JSON string");
+
+    // Defined extensions.
+
+    // appid
+    new CreateCredentialsTest("options.publicKey.extensions", {appid: ""}).runTest("empty appid in create request", "NotSupportedError");
+    new CreateCredentialsTest("options.publicKey.extensions", {appid: null}).runTest("null appid in create request", "NotSupportedError");
+    new CreateCredentialsTest("options.publicKey.extensions", {appid: "anything"}).runTest("appid in create request", "NotSupportedError");
+
+    // TODO
+    // defined extensions:
+    // * txAuthSimple
+    // * txAuthGeneric
+    // * authnSel
+    // * exts
+    // * uvi
+    // * loc
+    // * uvm
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, createRandomString */
+</script>
diff --git a/webauthn/createcredential-passing.https.html b/webauthn/createcredential-passing.https.html
new file mode 100644
index 0000000..25dba1d
--- /dev/null
+++ b/webauthn/createcredential-passing.https.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn credential.create() Passing Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // CreateCredentialTest passing tests
+
+    // default arguments
+    new CreateCredentialsTest().runTest("passing credentials.create() with default arguments");
+
+    // rp
+    new CreateCredentialsTest({path: "options.publicKey.rp.id", value: window.location.host}).runTest("passing credentials.create() with rpId (host and port)");
+    new CreateCredentialsTest({path: "options.publicKey.rp.id", value: window.location.hostname}).runTest("passing credentials.create() with rpId (hostname)");
+    new CreateCredentialsTest({path: "options.publicKey.rp.icon", value: undefined}).runTest("passing credentials.create() without rp.icon");
+
+    // user
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(1)).runTest("very short user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(64)).runTest("max length user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Uint8Array(64)).runTest("Uint8Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Int8Array(64)).runTest("Int8Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Int16Array(32)).runTest("Int16Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Int32Array(16)).runTest("Int32Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Float32Array(16)).runTest("Float32Array user id");
+    var dvBuf1 = new ArrayBuffer(16);
+    new CreateCredentialsTest("options.publicKey.user.id", new DataView(dvBuf1)).runTest("DataView user id");
+    new CreateCredentialsTest({path: "options.publicKey.user.icon", value: undefined}).runTest("passing credentials.create() without user.icon");
+
+    // good challenge values
+    // all these challenges are zero-filled buffers... think anyone will complain?
+    new CreateCredentialsTest("options.publicKey.challenge", new Int16Array(33)).runTest("Int16Array challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new Int32Array(17)).runTest("Int32Array challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new Float32Array(17)).runTest("Float32Array challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new Float64Array(9)).runTest("Float64Array challenge");
+    var dvBuf2 = new ArrayBuffer(65);
+    new CreateCredentialsTest("options.publicKey.challenge", new DataView(dvBuf2)).runTest("DataView challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new ArrayBuffer(8192)).runTest("Absurdly large challenge");
+
+    // good pubKeyCredParams values
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", []).runTest("Bad pubKeyCredParams: pubKeyCredParams is empty Array");
+    const pkParamEC256 = {
+        type: "public-key",
+        alg: cose_alg_ECDSA_w_SHA256
+    };
+    const pkParamEC512 = {
+        type: "public-key",
+        alg: cose_alg_ECDSA_w_SHA512
+    };
+    // XXX: presumes all mock authenticators support EC256
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC256]).runTest("EC256 pubKeyCredParams");
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC512, pkParamEC256])
+        .runTest("SelectEC256 pubKeyCredParams from a list");
+    // TODO: currently most browsers are mocking FIDO U2F, which is EC256 only
+    // new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC512]).runTest("EC512 pubKeyCredParams");
+
+    // NOTE: excludeCredentials parameter -- see also: createcredential-excludecredentials.https.html
+
+    // timeout
+    new CreateCredentialsTest({path: "options.publicKey.timeout", value: undefined}).runTest("passing credentials.create() with no timeout");
+
+    // valid authenticatorSelection values
+    var defaultAuthnrSel = {
+        authenticatorAttachment: "cross-platform",
+        requireResidentKey: false,
+        userVerification: "preferred"
+    };
+    // attachment
+    var authnrSelAttachUndef = cloneObject(defaultAuthnrSel);
+    authnrSelAttachUndef.authenticatorAttachment = undefined;
+    // resident key
+    var authnrSelRkUndef = cloneObject(defaultAuthnrSel);
+    authnrSelRkUndef.requireResidentKey = undefined;
+    var authnrSelRkFalse = cloneObject(defaultAuthnrSel);
+    authnrSelRkFalse.requireResidentKey = false;
+    // user verification
+    var authnrSelUvUndef = cloneObject(defaultAuthnrSel);
+    authnrSelUvUndef.userVerification = undefined;
+    var authnrSelUvDiscouraged = cloneObject(defaultAuthnrSel);
+    authnrSelUvDiscouraged.userVerification = "discouraged";
+    new CreateCredentialsTest({path: "options.publicKey.authenticatorSelection", value: undefined}).runTest("authenticatorSelection is undefined");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", {}).runTest("authenticatorSelection is empty object");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", cloneObject(defaultAuthnrSel)).runTest("authenticatorSelection default values");
+
+    // authnr selection attachment
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelAttachUndef).runTest("authenticatorSelection attachment undefined");
+
+    // authnr selection resident key
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkUndef).runTest("authenticatorSelection residentKey undefined");
+    // XXX: assumes authnr is behaving like most U2F authnrs; really depends on the authnr or mock configuration
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkFalse).runTest("authenticatorSelection residentKey false");
+
+    // authnr selection user verification
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelUvUndef).runTest("authenticatorSelection userVerification undefined");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelUvDiscouraged).runTest("authenticatorSelection userVerification discouraged");
+
+
+    // good attestation values
+    new CreateCredentialsTest("options.publicKey.attestation", "none").runTest("attestation parameter: attestation is \"none\"");
+    new CreateCredentialsTest("options.publicKey.attestation", "indirect").runTest("attestation parameter: attestation is \"indirect\"");
+    new CreateCredentialsTest("options.publicKey.attestation", "direct").runTest("attestation parameter: attestation is \"direct\"");
+    new CreateCredentialsTest({path: "options.publicKey.attestation", value: undefined}).runTest("attestation parameter: attestation is undefined");
+    // TODO: test this with multiple mock authenticators to make sure that the right options are chosen when available?
+
+    // good extension values
+    new CreateCredentialsTest({path: "options.publicKey.extensions", value: undefined}).runTest("extensions undefined");
+    new CreateCredentialsTest("options.publicKey.extensions", {}).runTest("extensions are empty object");
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: "", bar: "", bat: ""}).runTest("extensions are dict of empty strings");
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, cose_alg_ECDSA_w_SHA256, cose_alg_ECDSA_w_SHA512, cloneObject */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-pubkeycredparams.https.html b/webauthn/createcredential-pubkeycredparams.https.html
new file mode 100644
index 0000000..325191c
--- /dev/null
+++ b/webauthn/createcredential-pubkeycredparams.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() pubKeyCredParams Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var badType = {
+        type: "something-else",
+        alg: cose_alg_ECDSA_w_SHA512
+    };
+    var badTypeEmptyString = cloneObject(badType);
+    badTypeEmptyString.type = "";
+    var badTypeNull = cloneObject(badType);
+    badTypeNull.type = null;
+    var badTypeEmptyObj = cloneObject(badType);
+    badTypeEmptyObj.type = {};
+
+    var badAlg = {
+        type: "public-key",
+        alg: 42
+    };
+    var badAlgZero = cloneObject(badAlg);
+    badAlgZero.alg = 0;
+
+    // bad pubKeyCredParams values
+    new CreateCredentialsTest({path: "options.publicKey.pubKeyCredParams", value: undefined}).runTest("Bad pubKeyCredParams: pubKeyCredParams is undefined", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", "hi mom").runTest("Bad pubKeyCredParams: pubKeyCredParams is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", null).runTest("Bad pubKeyCredParams: pubKeyCredParams is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badType]).runTest("Bad pubKeyCredParams: first param has bad type (\"something-else\")", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badTypeEmptyString]).runTest("Bad pubKeyCredParams: first param has bad type (\"\")", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badTypeNull]).runTest("Bad pubKeyCredParams: first param has bad type (null)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badTypeEmptyObj]).runTest("Bad pubKeyCredParams: first param has bad type (empty object)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badAlg]).runTest("Bad pubKeyCredParams: first param has bad alg (42)", "NotSupportedError");
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badAlgZero]).runTest("Bad pubKeyCredParams: first param has bad alg (0)", "NotSupportedError");
+
+    // TODO: come back to this when mock authenticators support multiple cryptos so that we can test the preference ranking
+    // function verifyEC256(res) {
+    //     debug ("verifyEC256 got", res);
+    //     debug ("client data JSON", ab2str(res.response.clientDataJSON));
+    //     parseAuthenticatorData(res.response.attestationObject);
+    // }
+    // new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC256, pkParamEC512])
+    //     .afterTest(verifyEC256)
+    //     .runTest("EC256, EC512 pubKeyCredParams");
+    // function verifyEC512(res) {
+    //     debug ("verifyEC512 got", res);
+    //     debug ("client data JSON", ab2str(res.response.clientDataJSON));
+    //     // parseAuthenticatorData(res.response.attestationObject);
+    //     printHex ("clientDataJSON", res.response.clientDataJSON);
+    //     printHex ("attestationObject", res.response.attestationObject);
+    // }
+    // new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC512, pkParamEC256])
+    //     .afterTest(verifyEC512)
+    //     .runTest("EC512, EC256 pubKeyCredParams");
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, cose_alg_ECDSA_w_SHA512, cloneObject */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-timeout.https.html b/webauthn/createcredential-timeout.https.html
new file mode 100644
index 0000000..b94ae58
--- /dev/null
+++ b/webauthn/createcredential-timeout.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() timeout Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // bad timeout values
+    // TODO: there is some debate as to whether MAX_UNSIGNED_LONG + 1 and / or -1 should be disallowed since they get converted to valid values internally
+    // new CreateCredentialsTest({path: "options.publicKey.timeout", value: -1}).runTest("Bad timeout: negative", new TypeError());
+    // new CreateCredentialsTest({path: "options.publicKey.timeout", value: 4294967295 + 1}).runTest("Bad timeout: too big", new TypeError());
+
+    // timeout test
+    // XXX: this probably always passes with most mock authenticators unless
+    // some setup happens right here to make sure they don't return a credential
+    // right away. So... uhh... I guess test this with a real authenticator if you
+    // want to see if it really works.
+    promise_test(() => {
+        return new Promise((resolve, reject) => {
+            var args = {
+                options: {
+                    publicKey: {
+                        timeout: 1
+                    }
+                }
+            };
+
+            setTimeout(() => {
+                reject(new Error ("timed out"));
+            }, 1000);
+
+            createCredential(args).then((res) => {
+                resolve(res);
+            });
+        });
+    }, "ensure create credential times out");
+    // TODO: createCredential.timeout > 1s && setTimeout < 1s
+    // TODO: createCredential.timeout < 5s && setTimeout > 5s
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, createCredential, promise_test */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-badargs-rpid.https.html b/webauthn/getcredential-badargs-rpid.https.html
new file mode 100644
index 0000000..9e8da4d
--- /dev/null
+++ b/webauthn/getcredential-badargs-rpid.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn credential.get() rpId Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var credPromise = createCredential();
+
+    new GetCredentialsTest("options.publicKey.rpId", "")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: empty string", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", null)
+        .addCredential(credPromise)
+        .runTest("Bad rpId: null", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", "invalid domain.com")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: invalid domain (has space)", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", "-invaliddomain.com")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: invalid domain (starts with dash)", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", "0invaliddomain.com")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: invalid domain (starts with number)", "SecurityError");
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-badargs-userverification.https.html b/webauthn/getcredential-badargs-userverification.https.html
new file mode 100644
index 0000000..6101540
--- /dev/null
+++ b/webauthn/getcredential-badargs-userverification.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.get() user verification Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var credPromise = createCredential();
+
+    // authenticatorSelection bad userVerification values
+    new GetCredentialsTest("options.publicKey.userVerification", "")
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: empty string", new TypeError());
+    new GetCredentialsTest("options.publicKey.userVerification", {})
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: empty object", new TypeError());
+    new GetCredentialsTest("options.publicKey.userVerification", "requiredshirtshoestshirt")
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: bad value", new TypeError());
+    new GetCredentialsTest("options.publicKey.userVerification", null)
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: null", new TypeError());
+    // XXX: assumes this is a mock authenticator the properly reports that it is not doing userVerfication
+    new GetCredentialsTest("options.publicKey.userVerification", "required")
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: \"required\"", "NotAllowedError");
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-extensions.https.html b/webauthn/getcredential-extensions.https.html
new file mode 100644
index 0000000..b9f58ef
--- /dev/null
+++ b/webauthn/getcredential-extensions.https.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.get() extensions Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var dummyExtension = {
+        foo: true,
+        bar: "yup"
+    };
+    var credPromise = createCredential();
+
+    // bad extension values
+    new GetCredentialsTest("options.publicKey.extensions", "hi mom")
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is string", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", null)
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is null", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", [])
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is empty Array", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", new ArrayBuffer(0))
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is empty ArrayBuffer", new TypeError());
+    var badJson = '{"foo": true, "bar: "yup"}'; // missing quote after "bar"
+    new GetCredentialsTest("options.publicKey.extensions", {foo: badJson})
+        .addCredential(credPromise)
+        .runTest("Bad extensions: malformatted JSON", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", {foo: dummyExtension})
+        .addCredential(credPromise)
+        .runTest("Bad extensions: JavaScript object", new TypeError());
+    var badExtId = {};
+    badExtId[createRandomString(65)] = dummyExtension;
+    new GetCredentialsTest("options.publicKey.extensions", {badExtId: dummyExtension})
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extension ID too long", new TypeError());
+
+    // phony extensions
+    // TODO: not sure if this should pass or fail
+    // should be clarified as part of https://github.com/w3c/webauthn/pull/765
+    var randomExtId = {};
+    randomExtId[createRandomString(64)] = dummyExtension;
+    new GetCredentialsTest("options.publicKey.extensions", {foo: JSON.stringify(randomExtId)})
+        .addCredential(credPromise)
+        .runTest("extensions is a nonsensical JSON string");
+
+    // TODO
+    // defined extensions:
+    // * appid
+    // * txAuthSimple
+    // * txAuthGeneric
+    // * authnSel
+    // * exts
+    // * uvi
+    // * loc
+    // * uvm
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createRandomString, createCredential */
+</script>
diff --git a/webauthn/getcredential-passing.https.html b/webauthn/getcredential-passing.https.html
new file mode 100644
index 0000000..58b085f
--- /dev/null
+++ b/webauthn/getcredential-passing.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn credential.get() Passing Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var credPromise = createCredential();
+
+    // GetCredentialsTest with default args
+    new GetCredentialsTest()
+        .addCredential(credPromise)
+        .runTest("passing credentials.get() with default args");
+
+    // timeout
+    new GetCredentialsTest({path: "options.publicKey.timeout", value: undefined})
+        .addCredential(credPromise)
+        .runTest("passing credentials.create() with no timeout");
+
+    // rpId
+    new GetCredentialsTest({path: "options.publicKey.rpId", value: undefined})
+        .addCredential(credPromise)
+        .runTest("rpId undefined");
+    new GetCredentialsTest({path: "options.publicKey.rpId", value: window.location.host})
+        .addCredential(credPromise)
+        .runTest("passing credentials.get() with rpId (host and port)");
+    new GetCredentialsTest({path: "options.publicKey.rpId", value: window.location.hostname})
+        .addCredential(credPromise)
+        .runTest("passing credentials.get() with rpId (hostname)");
+
+    // allowCredentials
+    new GetCredentialsTest({path: "options.publicKey.allowCredentials", value: undefined})
+        .runTest("no credential specified");
+
+    // authnr selection user verification
+    new GetCredentialsTest({path: "options.publicKey.userVerification", value: undefined})
+        .addCredential(credPromise)
+        .runTest("authenticatorSelection userVerification undefined");
+    new GetCredentialsTest("options.publicKey.userVerification", "preferred")
+        .addCredential(credPromise)
+        .runTest("authenticatorSelection userVerification preferred");
+    new GetCredentialsTest("options.publicKey.userVerification", "discouraged")
+        .addCredential(credPromise)
+        .runTest("authenticatorSelection userVerification discouraged");
+
+    // good extension values
+    new GetCredentialsTest({path: "options.publicKey.extensions", value: undefined})
+        .addCredential(credPromise)
+        .runTest("extensions undefined");
+    new GetCredentialsTest("options.publicKey.extensions", {})
+        .addCredential(credPromise)
+        .runTest("extensions are empty object");
+    new GetCredentialsTest("options.publicKey.extensions", {foo: "", bar: "", bat: ""})
+        .addCredential(credPromise)
+        .runTest("extensions are dict of empty strings");
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-timeout.https.html b/webauthn/getcredential-timeout.https.html
new file mode 100644
index 0000000..8f5c2f3
--- /dev/null
+++ b/webauthn/getcredential-timeout.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.get() timeout Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var credPromise = createCredential();
+
+    // bad timeout values
+    // TODO: there is some debate as to whether MAX_UNSIGNED_LONG + 1 and / or -1 should be disallowed since they get converted to valid values internally
+    // new GetCredentialsTest({path: "options.publicKey.timeout", value: -1})
+    //     .addCredential(credPromise)
+    //     .runTest("Bad timeout: negative", new TypeError());
+    // new GetCredentialsTest({path: "options.publicKey.timeout", value: 4294967295 + 1})
+    //     .addCredential(credPromise)
+    //     .runTest("Bad timeout: too big", new TypeError());
+
+    // timeout test
+    // XXX: this probably always passes with most mock authenticators unless
+    // some setup happens right here to make sure they don't return a credential
+    // right away. So... uhh... I guess test this with a real authenticator if you
+    // want to see if it really works.
+    var timer;
+    function startTimer() {
+        timer = setTimeout(() => {
+            throw new Error("Timer went off before timeout");
+        }, 1000);
+    }
+    function stopTimer() {
+        clearTimeout(timer);
+    }
+    new GetCredentialsTest({path: "options.publicKey.timeout", value: 1})
+        .addCredential(credPromise)
+        .beforeTest(startTimer)
+        .afterTest(stopTimer)
+        .runTest("ensure create credential times out");
+    // TODO: createCredential.timeout > 1s && setTimeout < 1s
+    // TODO: createCredential.timeout < 5s && setTimeout > 5s
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/helpers.js b/webauthn/helpers.js
index e36cb6d..568d3e2 100644
--- a/webauthn/helpers.js
+++ b/webauthn/helpers.js
@@ -1,3 +1,125 @@
+// Useful constants for working with COSE key objects
+const cose_kty = 1;
+const cose_kty_ec2 = 2;
+const cose_alg = 3;
+const cose_alg_ECDSA_w_SHA256 = -7;
+const cose_alg_ECDSA_w_SHA512 = -36;
+const cose_crv = -1;
+const cose_crv_P256 = 1;
+const cose_crv_x = -2;
+const cose_crv_y = -3;
+
+/**
+ * These are the default arguments that will be passed to navigator.credentials.create()
+ * unless modified by a specific test case
+ */
+var createCredentialDefaultArgs = {
+    options: {
+        publicKey: {
+            // Relying Party:
+            rp: {
+                name: "Acme",
+                icon: "https://www.w3.org/StyleSheets/TR/2016/logos/W3C"
+            },
+
+            // User:
+            user: {
+                id: new Uint8Array(16), // Won't survive the copy, must be rebuilt
+                name: "john.p.smith@example.com",
+                displayName: "John P. Smith",
+                icon: "https://pics.acme.com/00/p/aBjjjpqPb.png"
+            },
+
+            pubKeyCredParams: [{
+                type: "public-key",
+                alg: cose_alg_ECDSA_w_SHA256,
+            }],
+
+            timeout: 60000, // 1 minute
+            excludeCredentials: [] // No excludeList
+        }
+    }
+};
+
+/**
+ * These are the default arguments that will be passed to navigator.credentials.get()
+ * unless modified by a specific test case
+ */
+var getCredentialDefaultArgs = {
+    options: {
+        publicKey: {
+            timeout: 60000
+            // allowCredentials: [newCredential]
+        }
+    }
+};
+
+function createCredential(opts) {
+    opts = opts || {};
+
+    // set the default options
+    var createArgs = cloneObject(createCredentialDefaultArgs);
+    let challengeBytes = new Uint8Array(16);
+    window.crypto.getRandomValues(challengeBytes);
+    createArgs.options.publicKey.challenge = challengeBytes;
+    createArgs.options.publicKey.user.id = new Uint8Array(16);
+
+    // change the defaults with any options that were passed in
+    extendObject (createArgs, opts);
+
+    // create the credential, return the Promise
+    return navigator.credentials.create(createArgs.options);
+}
+
+function createRandomString(len) {
+    var text = "";
+    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    for(var i = 0; i < len; i++) {
+        text += possible.charAt(Math.floor(Math.random() * possible.length));
+    }
+    return text;
+}
+
+
+function ab2str(buf) {
+    return String.fromCharCode.apply(null, new Uint8Array(buf));
+}
+
+// Useful constants for working with attestation data
+const authenticator_data_user_present = 0x01;
+const authenticator_data_user_verified = 0x04;
+const authenticator_data_attested_cred_data = 0x40;
+const authenticator_data_extension_data = 0x80;
+
+function parseAuthenticatorData(buf) {
+    if (buf.byteLength < 37) {
+        throw new TypeError ("parseAuthenticatorData: buffer must be at least 37 bytes");
+    }
+
+    printHex ("authnrData", buf);
+
+    var authnrData = new DataView(buf);
+    var authnrDataObj = {};
+    authnrDataObj.length = buf.byteLength;
+
+    authnrDataObj.rpIdHash = new Uint8Array (buf.slice (0,32));
+    authnrDataObj.rawFlags = authnrData.getUint8(32);
+    authnrDataObj.counter = authnrData.getUint32(33, false);
+    authnrDataObj.rawCounter = [];
+    authnrDataObj.rawCounter[0] = authnrData.getUint8(33);
+    authnrDataObj.rawCounter[1] = authnrData.getUint8(34);
+    authnrDataObj.rawCounter[2] = authnrData.getUint8(35);
+    authnrDataObj.rawCounter[3] = authnrData.getUint8(36);
+    authnrDataObj.flags = {};
+
+    authnrDataObj.flags.userPresent = (authnrDataObj.rawFlags&authenticator_data_user_present)?true:false;
+    authnrDataObj.flags.userVerified = (authnrDataObj.rawFlags&authenticator_data_user_verified)?true:false;
+    authnrDataObj.flags.attestedCredentialData = (authnrDataObj.rawFlags&authenticator_data_attested_cred_data)?true:false;
+    authnrDataObj.flags.extensionData = (authnrDataObj.rawFlags&authenticator_data_extension_data)?true:false;
+
+    return authnrDataObj;
+}
+
 /**
  * TestCase
  *
@@ -5,7 +127,7 @@
  * Is intended to be overloaded with subclasses that override testObject, testFunction and argOrder
  * The testObject is the default arguments for the testFunction
  * The default testObject can be modified with the modify() method, making it easy to create new tests based on the default
- * The testFunction is the target of the test and is called by the test() method. test() applies the testObject as arguments via toArgs()
+ * The testFunction is the target of the test and is called by the doIt() method. doIt() applies the testObject as arguments via toArgs()
  * toArgs() uses argOrder to make sure the resulting array is in the right order of the arguments for the testFunction
  */
 class TestCase {
@@ -15,6 +137,7 @@
         };
         this.testObject = {};
         this.argOrder = [];
+        this.ctx = null;
     }
 
     /**
@@ -70,7 +193,6 @@
         }
 
         // iterate through each of the desired modifications, and call recursiveSetObject on them
-        var obj = this.testObject;
         for (let idx in mods) {
             var mod = mods[idx];
             let paths = mod.path.split(".");
@@ -94,61 +216,164 @@
     }
 
     /**
-     * test
-     *
-     * run the test function with the top-level properties of the test object applied as arguments
+     * actually runs the test function with the supplied arguments
      */
-    test() {
-        return this.testFunction(...this.toArgs());
+    doIt() {
+        if (typeof this.testFunction !== "function") {
+            throw new Error("Test function not found");
+        }
+
+        return this.testFunction.call(this.ctx, ...this.toArgs());
     }
 
     /**
-     * testArgs
-     *
-     * calls test() with testObject() and expects it to fail with a TypeError()
+     * run the test function with the top-level properties of the test object applied as arguments
+     * expects the test to pass, and then validates the results
      */
-    testBadArgs(testDesc) {
-        promise_test(function(t) {
-            return promise_rejects(t, new TypeError(), this.test());
-        }.bind(this), testDesc);
+    testPasses(desc) {
+        return this.doIt()
+            .then((ret) => {
+                // check the result
+                this.validateRet(ret);
+                return ret;
+            });
+    }
+
+    /**
+     * run the test function with the top-level properties of the test object applied as arguments
+     * expects the test to fail
+     */
+    testFails(t, testDesc, expectedErr) {
+        return promise_rejects(t, expectedErr, this.doIt(), "Expected bad parameters to fail");
+    }
+
+    /**
+     * Runs the test that's implemented by the class by calling the doIt() function
+     * @param  {String} desc                A description of the test being run
+     * @param  [Error|String] expectedErr   A string matching an error type, such as "SecurityError" or an object with a .name value that is an error type string
+     */
+    runTest(desc, expectedErr) {
+        promise_test((t) => {
+            return Promise.resolve().then(() => {
+                return this.testSetup();
+            }).then(() => {
+                if (expectedErr === undefined) {
+                    return this.testPasses(desc);
+                } else {
+                    return this.testFails(t, desc, expectedErr);
+                }
+            }).then((res) => {
+                return this.testTeardown(res);
+            })
+        }, desc)
+    }
+
+    /**
+     * called before runTest
+     * virtual method expected to be overridden by child class if needed
+     */
+    testSetup() {
+        if (this.beforeTestFn) {
+            this.beforeTestFn.call(this);
+        }
+
+        return Promise.resolve();
+    }
+
+    /**
+     * Adds a callback function that gets called in the TestCase context
+     * and within the testing process.
+     */
+    beforeTest(fn) {
+        if (typeof fn !== "function") {
+            throw new Error ("Tried to call non-function before test");
+        }
+
+        this.beforeTestFn = fn;
+
+        return this;
+    }
+
+    /**
+     * called after runTest
+     * virtual method expected to be overridden by child class if needed
+     */
+    testTeardown(res) {
+        if (this.afterTestFn) {
+            this.afterTestFn.call(this, res);
+        }
+
+        return Promise.resolve();
+    }
+
+    /**
+     * Adds a callback function that gets called in the TestCase context
+     * and within the testing process. Good for validating results.
+     */
+    afterTest(fn) {
+        if (typeof fn !== "function") {
+            throw new Error ("Tried to call non-function after test");
+        }
+
+        this.afterTestFn = fn;
+
+        return this;
+    }
+
+    /**
+     * validates the value returned from the test function
+     * virtual method expected to be overridden by child class
+     */
+    validateRet() {
+        throw new Error("Not implemented");
     }
 }
 
+function cloneObject(o) {
+    return JSON.parse(JSON.stringify(o));
+}
+
+function extendObject(dst, src) {
+    Object.keys(src).forEach(function(key) {
+        if (isSimpleObject(src[key])) {
+            extendObject (dst[key], src[key]);
+        } else {
+            dst[key] = src[key];
+        }
+    });
+}
+
+function isSimpleObject(o) {
+    return (typeof o === "object" &&
+        !Array.isArray(o) &&
+        !(o instanceof ArrayBuffer));
+}
+
 /**
- * MakeCredentialTest
+ * CreateCredentialTest
  *
- * tests the WebAuthn makeCredential() interface
+ * tests the WebAuthn navigator.credentials.create() interface
  */
-class MakeCredentialTest extends TestCase {
+class CreateCredentialsTest extends TestCase {
     constructor() {
         // initialize the parent class
         super();
 
         // the function to be tested
-        this.testFunction = navigator.authentication.makeCredential;
+        this.testFunction = navigator.credentials.create;
+        // the context to call the test function with (i.e. - the 'this' object for the function)
+        this.ctx = navigator.credentials;
 
         // the default object to pass to makeCredential, to be modified with modify() for various tests
-        // var challenge = Uint8Array.from("Y2xpbWIgYSBtb3VudGFpbg");
-        this.testObject = {
-            accountInformation: {
-                rpDisplayName: "ACME",
-                displayName: "John P. Smith",
-                name: "johnpsmith@example.com",
-                id: "1098237235409872",
-                imageUri: "https://pics.acme.com/00/p/aBjjjpqPb.png"
-            },
-            cryptoParameters: [{
-                type: "ScopedCred",
-                algorithm: "RSASSA-PKCS1-v1_5",
-            }],
-            attestationChallenge: Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]).buffer
-        };
+        let challengeBytes = new Uint8Array(16);
+        window.crypto.getRandomValues(challengeBytes);
+        this.testObject = cloneObject(createCredentialDefaultArgs);
+        // cloneObject can't clone the BufferSource in user.id, so let's recreate it.
+        this.testObject.options.publicKey.user.id = new Uint8Array(16);
+        this.testObject.options.publicKey.challenge = challengeBytes;
 
         // how to order the properties of testObject when passing them to makeCredential
         this.argOrder = [
-            "accountInformation",
-            "cryptoParameters",
-            "attestationChallenge",
             "options"
         ];
 
@@ -156,43 +381,201 @@
         // would prefer to do this in the super class, but have to call super() before using `this.*`
         if (arguments.length) this.modify(...arguments);
     }
+
+    validateRet(ret) {
+        validatePublicKeyCredential(ret);
+        validateAuthenticatorAttestationResponse(ret.response);
+    }
 }
 
-//************* BEGIN DELETE AFTER 1/1/2017 *************** //
+/**
+ * GetCredentialsTest
+ *
+ * tests the WebAuthn navigator.credentials.get() interface
+ */
+class GetCredentialsTest extends TestCase {
+    constructor(...args) {
+        // initialize the parent class
+        super();
+
+        // the function to be tested
+        this.testFunction = navigator.credentials.get;
+        // the context to call the test function with (i.e. - the 'this' object for the function)
+        this.ctx = navigator.credentials;
+
+        // default arguments
+        let challengeBytes = new Uint8Array(16);
+        window.crypto.getRandomValues(challengeBytes);
+        this.testObject = cloneObject(getCredentialDefaultArgs);
+        this.testObject.options.publicKey.challenge = challengeBytes;
+
+        // how to order the properties of testObject when passing them to makeCredential
+        this.argOrder = [
+            "options"
+        ];
+
+        this.credentialPromiseList = [];
+
+        // enable the constructor to modify the default testObject
+        // would prefer to do this in the super class, but have to call super() before using `this.*`
+        if (arguments.length) {
+            if (args.cred instanceof Promise) this.credPromise = args.cred;
+            else if (typeof args.cred === "object") this.credPromise = Promise.resolve(args.cred);
+            delete args.cred;
+            this.modify(...arguments);
+        }
+    }
+
+    addCredential(arg) {
+        // if a Promise was passed in, add it to the list
+        if (arg instanceof Promise) {
+            this.credentialPromiseList.push(arg);
+            return this;
+        }
+
+        // if a credential object was passed in, convert it to a Promise for consistency
+        if (typeof arg === "object") {
+            this.credentialPromiseList.push(Promise.resolve(arg));
+            return this;
+        }
+
+        // if no credential specified then create one
+        var p = createCredential();
+        this.credentialPromiseList.push(p);
+
+        return this;
+    }
+
+    testSetup(desc) {
+        if (!this.credentialPromiseList.length) {
+            throw new Error("Attempting list without defining credential to test");
+        }
+
+        return Promise.all(this.credentialPromiseList)
+            .then((credList) => {
+                var idList = credList.map((cred) => {
+                    return {
+                        id: cred.rawId,
+                        transports: ["usb", "nfc", "ble"],
+                        type: "public-key"
+                    };
+                });
+                this.testObject.options.publicKey.allowCredentials = idList;
+                // return super.test(desc);
+            })
+            .catch((err) => {
+                throw Error(err);
+            });
+    }
+
+    validateRet(ret) {
+        validatePublicKeyCredential(ret);
+        validateAuthenticatorAssertionResponse(ret.response);
+    }
+}
+
+/**
+ * runs assertions against a PublicKeyCredential object to ensure it is properly formatted
+ */
+function validatePublicKeyCredential(cred) {
+    // class
+    assert_class_string(cred, "PublicKeyCredential", "Expected return to be instance of 'PublicKeyCredential' class");
+    // id
+    assert_idl_attribute(cred, "id", "should return PublicKeyCredential with id attribute");
+    assert_readonly(cred, "id", "should return PublicKeyCredential with readonly id attribute");
+    // rawId
+    assert_idl_attribute(cred, "rawId", "should return PublicKeyCredential with rawId attribute");
+    assert_readonly(cred, "rawId", "should return PublicKeyCredential with readonly rawId attribute");
+    // type
+    assert_idl_attribute(cred, "type", "should return PublicKeyCredential with type attribute");
+    assert_equals(cred.type, "public-key", "should return PublicKeyCredential with type 'public-key'");
+}
+
+/**
+ * runs assertions against a AuthenticatorAttestationResponse object to ensure it is properly formatted
+ */
+function validateAuthenticatorAttestationResponse(attr) {
+    // class
+    assert_class_string(attr, "AuthenticatorAttestationResponse", "Expected credentials.create() to return instance of 'AuthenticatorAttestationResponse' class");
+
+    // clientDataJSON
+    assert_idl_attribute(attr, "clientDataJSON", "credentials.create() should return AuthenticatorAttestationResponse with clientDataJSON attribute");
+    assert_readonly(attr, "clientDataJSON", "credentials.create() should return AuthenticatorAttestationResponse with readonly clientDataJSON attribute");
+    // TODO: clientDataJSON() and make sure fields are correct
+
+    // attestationObject
+    assert_idl_attribute(attr, "attestationObject", "credentials.create() should return AuthenticatorAttestationResponse with attestationObject attribute");
+    assert_readonly(attr, "attestationObject", "credentials.create() should return AuthenticatorAttestationResponse with readonly attestationObject attribute");
+    // TODO: parseAuthenticatorData() and make sure flags are correct
+}
+
+/**
+ * runs assertions against a AuthenticatorAssertionResponse object to ensure it is properly formatted
+ */
+function validateAuthenticatorAssertionResponse(assert) {
+    // class
+    assert_class_string(assert, "AuthenticatorAssertionResponse", "Expected credentials.create() to return instance of 'AuthenticatorAssertionResponse' class");
+
+    // clientDataJSON
+    assert_idl_attribute(assert, "clientDataJSON", "credentials.get() should return AuthenticatorAssertionResponse with clientDataJSON attribute");
+    assert_readonly(assert, "clientDataJSON", "credentials.get() should return AuthenticatorAssertionResponse with readonly clientDataJSON attribute");
+    // TODO: clientDataJSON() and make sure fields are correct
+
+    // signature
+    assert_idl_attribute(assert, "signature", "credentials.get() should return AuthenticatorAssertionResponse with signature attribute");
+    assert_readonly(assert, "signature", "credentials.get() should return AuthenticatorAssertionResponse with readonly signature attribute");
+
+    // authenticatorData
+    assert_idl_attribute(assert, "authenticatorData", "credentials.get() should return AuthenticatorAssertionResponse with authenticatorData attribute");
+    assert_readonly(assert, "authenticatorData", "credentials.get() should return AuthenticatorAssertionResponse with readonly authenticatorData attribute");
+    // TODO: parseAuthenticatorData() and make sure flags are correct
+}
+
+//************* BEGIN DELETE AFTER 1/1/2018 *************** //
 // XXX for development mode only!!
 // debug() for debugging purposes... we can drop this later if it is considered ugly
 // note that debug is currently an empty function (i.e. - prints no output)
 // and debug only prints output if the polyfill is loaded
 var debug = function() {};
 // if the WebAuthn API doesn't exist load a polyfill for testing
-// note that the polyfill only gets loaded if navigator.authentication doesn't exist
+// note that the polyfill only gets loaded if navigator.credentials create doesn't exist
 // AND if the polyfill script is found at the right path (i.e. - the polyfill is opt-in)
 function ensureInterface() {
-    return new Promise(function(resolve, reject) {
-        if (typeof navigator.authentication !== "object") {
-            debug = console.log;
+    if (typeof navigator.credentials === "object" && typeof navigator.credentials.create !== "function") {
+        // debug = onsole.log;
 
-            // dynamic loading of polyfill script by creating new <script> tag and seeing the src=
-            var scriptElem = document.createElement("script");
-            if (typeof scriptElem !== "object") {
-                debug("ensureInterface: Error creating script element while attempting loading polyfill");
-                return reject(new Error("ensureInterface: Error creating script element while loading polyfill"));
-            }
-            scriptElem.type = "application/javascript";
-            scriptElem.onload = function() {
-                debug("!!! XXX - LOADING POLYFILL FOR WEBAUTHN TESTING - XXX !!!");
-                return resolve();
-            };
-            scriptElem.onerror = function() {
-                return reject(new Error("navigator.authentication does not exist"));
-            };
-            scriptElem.src = "/webauthn/webauthn-polyfill/webauthn-polyfill.js";
-            if (document.body) {
-                document.body.appendChild(scriptElem);
-            } else {
-                debug("ensureInterface: DOM has no body");
-                return reject(new Error("ensureInterface: DOM has no body"));
-            }
+        return loadJavaScript("/webauthn/webauthn-polyfill/webauthn-polyfill.js")
+            .then(() => {
+                return loadJavaScript("/webauthn/webauthn-soft-authn/soft-authn.js");
+            });
+    } else {
+        return Promise.resolve();
+    }
+}
+
+function loadJavaScript(path) {
+    return new Promise((resolve, reject) => {
+        // dynamic loading of polyfill script by creating new <script> tag and seeing the src=
+        var scriptElem = document.createElement("script");
+        if (typeof scriptElem !== "object") {
+            debug("ensureInterface: Error creating script element while attempting loading polyfill");
+            return reject(new Error("ensureInterface: Error creating script element while loading polyfill"));
+        }
+        scriptElem.type = "application/javascript";
+        scriptElem.onload = function() {
+            debug("!!! Loaded " + path + " ...");
+            return resolve();
+        };
+        scriptElem.onerror = function() {
+            debug("navigator.credentials.create does not exist");
+            resolve();
+        };
+        scriptElem.src = path;
+        if (document.body) {
+            document.body.appendChild(scriptElem);
+        } else {
+            debug("ensureInterface: DOM has no body");
+            return reject(new Error("ensureInterface: DOM has no body"));
         }
     });
 }
@@ -201,9 +584,10 @@
     return ensureInterface()
         .then(() => {
             if (cb) return cb();
-        })
-        .catch((err) => {
-            return (err);
         });
 }
-//************* END DELETE AFTER 1/1/2017 *************** //
\ No newline at end of file
+//************* END DELETE AFTER 1/1/2018 *************** //
+
+/* JSHINT */
+/* globals promise_rejects, assert_class_string, assert_equals, assert_idl_attribute, assert_readonly, promise_test */
+/* exported standardSetup, CreateCredentialsTest, GetCredentialsTest */
\ No newline at end of file
diff --git a/webauthn/interfaces.https.any.js b/webauthn/interfaces.https.any.js
new file mode 100644
index 0000000..22766e6
--- /dev/null
+++ b/webauthn/interfaces.https.any.js
@@ -0,0 +1,31 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+if (self.importScripts) {
+  importScripts('/resources/testharness.js');
+  importScripts('/resources/WebIDLParser.js', '/resources/idlharness.js');
+}
+
+// https://w3c.github.io/webauthn/
+
+promise_test(async () => {
+  const webauthnIdl = await fetch('/interfaces/webauthn.idl').then(r => r.text());
+
+  const idlArray = new IdlArray();
+  idlArray.add_idls(webauthnIdl);
+
+  // static IDL tests
+  idlArray.add_untested_idls('interface CredentialCreationOptions {};');
+  idlArray.add_untested_idls('interface CredentialRequestOptions {};');
+  idlArray.add_untested_idls("interface Navigator { };");
+  idlArray.add_untested_idls("interface Credential { };");
+  // TODO: change to "tested" for real browsers?
+  idlArray.add_untested_idls("partial interface Navigator { readonly attribute WebAuthentication authentication; };");
+  idlArray.add_objects({
+    WebAuthentication: ["navigator.authentication"]
+  });
+  idlArray.test();
+  done();
+}, 'WebAuthn interfaces.');
diff --git a/webauthn/interfaces.https.html b/webauthn/interfaces.https.html
deleted file mode 100644
index a09296b..0000000
--- a/webauthn/interfaces.https.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WebAuthn WebIDL Tests</title>
-<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
-<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src=helpers.js></script>
-<!-- for testing WebIDL -->
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-<body></body>
-<script>
-standardSetup(function() {
-    "use strict";
-
-    // loads an IDL file from the webserver
-    function fetchIdl(idlUrl) {
-        return new Promise(function(resolve, reject) {
-            if (typeof idlUrl !== "string") {
-                return reject("fetchIdl: expected argument to be URL string");
-            }
-            var request = new XMLHttpRequest();
-            request.open("GET", idlUrl);
-            request.send();
-            request.onload = function() {
-                var idls = request.responseText;
-                return resolve(idls);
-            };
-        });
-    }
-
-    // this does the real work of running the IDL tests
-    function runIdlTests(idls) {
-        return new Promise(function(resolve, reject) {
-            var idlArray = new window.IdlArray();
-
-            // static IDL tests
-            idlArray.add_untested_idls("interface Navigator { };");
-            // TODO: change to "tested" for real browsers?
-            idlArray.add_untested_idls("partial interface Navigator { readonly attribute WebAuthentication authentication; };");
-            idlArray.add_objects({
-                WebAuthentication: ["navigator.authentication"]
-            });
-
-            // run test WebIDL tests loaded from the idls file
-            idlArray.add_idls(idls);
-            return resolve(idlArray.test());
-        });
-    }
-
-    // test harness function
-    window.promise_test(function() {
-        return fetchIdl("interfaces.idl") // load the IDL file ...
-            .then(function(idls) {
-                return runIdlTests(idls); // ... then run the tests.
-            });
-    }, "Validate WebAuthn IDL");
-});
-</script>
diff --git a/webauthn/interfaces.idl b/webauthn/interfaces.idl
deleted file mode 100644
index 173e515..0000000
--- a/webauthn/interfaces.idl
+++ /dev/null
@@ -1,96 +0,0 @@
-[SecureContext] interface WebAuthentication {
-    Promise < ScopedCredentialInfo > makeCredential (
-        Account                                 accountInformation,
-        sequence < ScopedCredentialParameters > cryptoParameters,
-        BufferSource                            attestationChallenge,
-        optional ScopedCredentialOptions        options
-    );
-
-    Promise < WebAuthnAssertion > getAssertion (
-        BufferSource               assertionChallenge,
-        optional AssertionOptions  options
-    );
-};
-
-[SecureContext]
-interface ScopedCredentialInfo {
-    readonly attribute ScopedCredential     credential;
-    readonly attribute CryptoKey            publicKey;
-    readonly attribute WebAuthnAttestation  attestation;
-};
-
-dictionary Account {
-    required DOMString rpDisplayName;
-    required DOMString displayName;
-    required DOMString id;
-    DOMString          name;
-    DOMString          imageURL;
-};
-
-dictionary ScopedCredentialParameters {
-    required ScopedCredentialType  type;
-    required AlgorithmIdentifier   algorithm;
-};
-
-dictionary ScopedCredentialOptions {
-    unsigned long                             timeoutSeconds;
-    USVString                                 rpId;
-    sequence < ScopedCredentialDescriptor >  excludeList;
-    WebAuthnExtensions                        extensions;
-};
-
-[SecureContext]
-interface WebAuthnAssertion {
-    readonly attribute ScopedCredential  credential;
-    readonly attribute ArrayBuffer       clientData;
-    readonly attribute ArrayBuffer       authenticatorData;
-    readonly attribute ArrayBuffer       signature;
-};
-
-dictionary AssertionOptions {
-    unsigned long                            timeoutSeconds;
-    USVString                                rpId;
-    sequence < ScopedCredentialDescriptor > allowList;
-    WebAuthnExtensions                       extensions;
-};
-
-dictionary WebAuthnExtensions {
-};
-
-[SecureContext]
-interface WebAuthnAttestation {
-    readonly    attribute USVString     format;
-    readonly    attribute ArrayBuffer   clientData;
-    readonly    attribute ArrayBuffer   authenticatorData;
-    readonly    attribute any           attestation;
-};
-
-dictionary ClientData {
-    required DOMString           challenge;
-    required DOMString           origin;
-    required AlgorithmIdentifier hashAlg;
-    DOMString                    tokenBinding;
-    WebAuthnExtensions           extensions;
-};
-
-enum ScopedCredentialType {
-    "ScopedCred"
-};
-
-[SecureContext]
-interface ScopedCredential {
-    readonly attribute ScopedCredentialType type;
-    readonly attribute ArrayBuffer          id;
-};
-
-dictionary ScopedCredentialDescriptor {
-    required ScopedCredentialType type;
-    required BufferSource   id;
-    sequence < Transport >  transports;
-};
-
-enum Transport {
-    "usb",
-    "nfc",
-    "ble"
-};
\ No newline at end of file
diff --git a/webauthn/makecredential-badargs-accountinformation.https.html b/webauthn/makecredential-badargs-accountinformation.https.html
deleted file mode 100644
index e0e95e5..0000000
--- a/webauthn/makecredential-badargs-accountinformation.https.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WebAuthn makeCredential accountInformation Tests</title>
-<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
-<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src=helpers.js></script>
-<body></body>
-<script>
-standardSetup(function() {
-    "use strict";
-
-    // accountInformation bad values
-    new MakeCredentialTest({path: "accountInformation", value: undefined}).testBadArgs("accountInformation missing");
-    new MakeCredentialTest("accountInformation", "hi mom").testBadArgs("accountInformation is string");
-    new MakeCredentialTest("accountInformation", {}).testBadArgs("accountInformation is empty object");
-    // accountInformation.rpDisplayName
-    new MakeCredentialTest({path: "accountInformation.rpDisplayName", value: undefined}).testBadArgs("accountInformation missing rpDisplayName");
-    new MakeCredentialTest("accountInformation.rpDisplayName", {}).testBadArgs("Bad accountInformation: rpDisplayName is object");
-    new MakeCredentialTest("accountInformation.rpDisplayName", null).testBadArgs("Bad accountInformation: rpDisplayName is null");
-    new MakeCredentialTest("accountInformation.rpDisplayName", "").testBadArgs("Bad accountInformation: rpDisplayName is empty String");
-    // accountInformation.displayName
-    new MakeCredentialTest({path: "accountInformation.displayName", value: undefined}).testBadArgs("accountInformation missing displayName");
-    new MakeCredentialTest("accountInformation.displayName", {}).testBadArgs("Bad accountInformation: displayName is object");
-    new MakeCredentialTest("accountInformation.displayName", null).testBadArgs("Bad accountInformation: displayName is null");
-    new MakeCredentialTest("accountInformation.displayName", "").testBadArgs("Bad accountInformation: displayName is empty String");
-    // accountInformation.id
-    new MakeCredentialTest({path: "accountInformation.id", value: undefined}).testBadArgs("accountInformation missing id");
-    new MakeCredentialTest("accountInformation.id", {}).testBadArgs("Bad accountInformation: id is object");
-    new MakeCredentialTest("accountInformation.id", null).testBadArgs("Bad accountInformation: id is null");
-    new MakeCredentialTest("accountInformation.id", "").testBadArgs("Bad accountInformation: id is empty String");
-});
-</script>
\ No newline at end of file
diff --git a/webauthn/makecredential-badargs-attestationchallenge.https.html b/webauthn/makecredential-badargs-attestationchallenge.https.html
deleted file mode 100644
index 441d1ad..0000000
--- a/webauthn/makecredential-badargs-attestationchallenge.https.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WebAuthn makeCredential attestationChallenge Tests</title>
-<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
-<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src=helpers.js></script>
-<body></body>
-<script>
-standardSetup(function() {
-    "use strict";
-
-    // attestationChallenge bad values
-    new MakeCredentialTest({path: "attestationChallenge", value: undefined}).testBadArgs("attestationChallenge missing");
-    new MakeCredentialTest("attestationChallenge", "hi mom").testBadArgs("accountInformation is string");
-    new MakeCredentialTest("attestationChallenge", {}).testBadArgs("accountInformation is empty object");
-    new MakeCredentialTest("attestationChallenge", Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])).testBadArgs("accountInformation is Uint8Array");
-});
-</script>
\ No newline at end of file
diff --git a/webauthn/makecredential-badargs-cryptoparameters.https.html b/webauthn/makecredential-badargs-cryptoparameters.https.html
deleted file mode 100644
index 7e17757..0000000
--- a/webauthn/makecredential-badargs-cryptoparameters.https.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WebAuthn makeCredential cryptoParameters Tests</title>
-<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
-<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src=helpers.js></script>
-<body></body>
-<script>
-standardSetup(function() {
-    "use strict";
-
-    // cryptoParameters bad values
-    new MakeCredentialTest({path: "cryptoParameters", value: undefined}).testBadArgs("cryptoParameters is missing");
-    new MakeCredentialTest("cryptoParameters", "hi mom").testBadArgs("cryptoParameters is String");
-    new MakeCredentialTest("cryptoParameters", []).testBadArgs("cryptoParameters is empty Array");
-    // cryptoParameters.type
-    new MakeCredentialTest({path: "cryptoParameters.0.type", value: undefined}).testBadArgs("cryptoParameters missing type");
-    new MakeCredentialTest("cryptoParameters.0.type", {}).testBadArgs("Bad cryptoParameters: type is object");
-    new MakeCredentialTest("cryptoParameters.0.type", null).testBadArgs("Bad cryptoParameters: type is null");
-    new MakeCredentialTest("cryptoParameters.0.type", "").testBadArgs("Bad cryptoParameters: type is empty String");
-    new MakeCredentialTest("cryptoParameters.0.type", "FIDO_2_0").testBadArgs("Bad cryptoParameters: type is not 'ScopedCred'");
-    // cryptoParameters.algorithm
-    new MakeCredentialTest({path: "cryptoParameters.0.algorithm", value: undefined}).testBadArgs("cryptoParameters missing algorithm");
-    new MakeCredentialTest("cryptoParameters.0.algorithm", {}).testBadArgs("Bad cryptoParameters: algorithm is object");
-    new MakeCredentialTest("cryptoParameters.0.algorithm", null).testBadArgs("Bad cryptoParameters: algorithm is null");
-    new MakeCredentialTest("cryptoParameters.0.algorithm", "").testBadArgs("Bad cryptoParameters: algorithm is empty String");
-    // multiple cryptoParameters
-    new MakeCredentialTest("cryptoParameters.1", {type: "FIDO_2_0", algorithm: "RSASSA-PKCS1-v1_5"}).testBadArgs("Bad cryptoParameters: second param has invalid type");
-    new MakeCredentialTest("cryptoParameters.1", {type: "ScopedCred", algorithm: ""}).testBadArgs("Bad cryptoParameters: second param has algorithm of empty String");
-});
-</script>
\ No newline at end of file
diff --git a/webauthn/securecontext.http.html b/webauthn/securecontext.http.html
new file mode 100644
index 0000000..5278695
--- /dev/null
+++ b/webauthn/securecontext.http.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn Secure Context Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // See https://www.w3.org/TR/secure-contexts/
+    // Section 1.1 - 1.4 for list of examples referenced below
+
+    // Example 1
+    // http://example.com/ opened in a top-level browsing context is not a secure context, as it was not delivered over an authenticated and encrypted channel.
+    test (() => {
+        assert_false (typeof navigator.credentials === "object" && typeof navigator.credentials.create === "function");
+    }, "no navigator.credentials.create in non-secure context");
+
+    // Example 4: TODO
+    // If a non-secure context opens https://example.com/ in a new window, then things are more complicated. The new window’s status depends on how it was opened. If the non-secure context can obtain a reference to the secure context, or vice-versa, then the new window is not a secure context.
+    //
+    // This means that the following will both produce non-secure contexts:
+    //<a href="https://example.com/" target="_blank">Link!</a>
+    // <script>
+    //     var w = window.open("https://example.com/");
+    // < /script>
+
+    // Example 6: TODO
+    // If https://example.com/ was somehow able to frame http://non-secure.example.com/ (perhaps the user has overridden mixed content checking?), the top-level frame would remain secure, but the framed content is not a secure context.
+
+    // Example 7: TODO
+    // If, on the other hand, https://example.com/ is framed inside of http://non-secure.example.com/, then it is not a secure context, as its ancestor is not delivered over an authenticated and encrypted channel.
+
+    // Example 9: TODO
+    // If http://non-secure.example.com/ in a top-level browsing context frames https://example.com/, which runs https://example.com/worker.js, then neither the framed document nor the worker are secure contexts.
+
+    // Example 12: TODO
+    // https://example.com/ nested in http://non-secure.example.com/ may not connect to the secure worker, as it is not a secure context.
+
+    // Example 13: TODO
+    // Likewise, if https://example.com/ nested in http://non-secure.example.com/ runs https://example.com/worker.js as a Shared Worker, then both the document and the worker are considered non-secure.
+
+});
+</script>
\ No newline at end of file
diff --git a/webauthn/securecontext.https.html b/webauthn/securecontext.https.html
new file mode 100644
index 0000000..6c9aabd
--- /dev/null
+++ b/webauthn/securecontext.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn Secure Context Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // See https://www.w3.org/TR/secure-contexts/
+    // Section 1.1 - 1.4 for list of examples referenced below
+
+    // Example 2
+    // https://example.com/ opened in a top-level browsing context is a secure context, as it was delivered over an authenticated and encrypted channel.
+    test (() => {
+        assert_true (typeof navigator.credentials === "object" && typeof navigator.credentials.create === "function");
+    }, "navigator.credentials.create exists in secure context");
+
+    // Example 3: TODO
+    // Example 5: TODO
+    // Example 8: TODO
+    // Example 10: TODO
+    // Example 11: TODO
+
+});
+</script>
\ No newline at end of file
diff --git a/webdriver/OWNERS b/webdriver/OWNERS
index 07296e3..d723512 100644
--- a/webdriver/OWNERS
+++ b/webdriver/OWNERS
@@ -1,6 +1,5 @@
 @AutomatedTester
-@JKereliuk
 @andreastt
-@lukeis
 @mjzffr
 @shs96c
+@whimboo
diff --git a/webdriver/interface/interface.html b/webdriver/interface/interface.html
deleted file mode 100644
index f3c149b..0000000
--- a/webdriver/interface/interface.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-<script type=text/plain class=untested>
-[Exposed=Window]
-interface Navigator {
-  // objects implementing this interface also implement the interfaces given below
-};
-</script>
-<script type=text/plain>
-Navigator implements NavigatorAutomationInformation;
-
-[NoInterfaceObject,
- Exposed=(Window)]
-interface NavigatorAutomationInformation {
-    readonly attribute boolean webdriver;
-    // always returns true
-};
-</script>
-<script>
-"use strict";
-
-if ("webdriver" in navigator) {
-  test(() => assert_true(navigator.webdriver), "navigator.webdriver is always true");
-  var idlArray = new IdlArray();
-  [].forEach.call(document.querySelectorAll("script[type=text\\/plain]"), function(node) {
-    if (node.className == "untested") {
-      idlArray.add_untested_idls(node.textContent);
-    } else {
-      idlArray.add_idls(node.textContent);
-    }
-  });
-  idlArray.test();
-} else {
-  done();
-}
-</script>
diff --git a/webdriver/tests/__init__.py b/webdriver/tests/__init__.py
index e69de29..0ba172f 100644
--- a/webdriver/tests/__init__.py
+++ b/webdriver/tests/__init__.py
@@ -0,0 +1,4 @@
+import pytest
+
+# Enable pytest assert introspection for assertion helper
+pytest.register_assert_rewrite('tests.support.asserts')
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/accept_alert/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/accept_alert/__init__.py
diff --git a/webdriver/tests/accept_alert/accept.py b/webdriver/tests/accept_alert/accept.py
new file mode 100644
index 0000000..efcbf3f
--- /dev/null
+++ b/webdriver/tests/accept_alert/accept.py
@@ -0,0 +1,47 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def accept_alert(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/alert/accept".format(**vars(session)))
+
+
+# 18.2 Accept Alert
+
+def test_no_browsing_context(session, create_window):
+    # 18.2 step 1
+    session.window_handle = create_window()
+    session.close()
+
+    response = accept_alert(session)
+    assert_error(response, "no such window")
+
+
+def test_no_user_prompt(session):
+    # 18.2 step 2
+    response = accept_alert(session)
+    assert_error(response, "no such alert")
+
+
+def test_accept_alert(session):
+    # 18.2 step 3
+    session.url = inline("<script>window.alert('Hello');</script>")
+    response = accept_alert(session)
+    assert_success(response)
+
+
+def test_accept_confirm(session):
+    # 18.2 step 3
+    session.url = inline("<script>window.result = window.confirm('Hello');</script>")
+    response = accept_alert(session)
+    assert_success(response)
+    assert session.execute_script("return window.result") is True
+
+
+def test_accept_prompt(session):
+    # 18.2 step 3
+    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Federer');</script>")
+    response = accept_alert(session)
+    assert_success(response)
+    assert session.execute_script("return window.result") == "Federer"
diff --git a/webdriver/tests/actions/modifier_click.py b/webdriver/tests/actions/modifier_click.py
index 73de161..85a23d4 100644
--- a/webdriver/tests/actions/modifier_click.py
+++ b/webdriver/tests/actions/modifier_click.py
@@ -1,5 +1,3 @@
-# META: timeout=long
-
 import pytest
 
 from tests.actions.support.refine import filter_dict, get_events
@@ -7,23 +5,16 @@
 
 
 @pytest.mark.parametrize("modifier, prop", [
-    (Keys.CONTROL, "ctrlKey"),
-    (Keys.ALT, "altKey"),
-    (Keys.META, "metaKey"),
-    (Keys.SHIFT, "shiftKey"),
-    (Keys.R_CONTROL, "ctrlKey"),
-    (Keys.R_ALT, "altKey"),
-    (Keys.R_META, "metaKey"),
-    (Keys.R_SHIFT, "shiftKey"),
+   (Keys.ALT, "altKey"),
+   (Keys.R_ALT, "altKey"),
+   (Keys.META, "metaKey"),
+   (Keys.R_META, "metaKey"),
+   (Keys.SHIFT, "shiftKey"),
+   (Keys.R_SHIFT, "shiftKey"),
 ])
-def test_modifier_click(session,
-                       test_actions_page,
-                       key_chain,
-                       mouse_chain,
-                       modifier,
-                       prop):
+def test_modifier_click(session, test_actions_page, key_chain, mouse_chain, modifier, prop):
     key_chain \
-        .pause(0) \
+        .pause(200) \
         .key_down(modifier) \
         .pause(200) \
         .key_up(modifier)
@@ -50,25 +41,43 @@
     assert expected == filtered_events
 
 
-def test_release_control_click(session, key_reporter, key_chain, mouse_chain):
+def test_many_modifiers_click(session, test_actions_page, key_chain, mouse_chain):
+    outer = session.find.css("#outer", all=False)
     key_chain \
         .pause(0) \
-        .key_down(Keys.CONTROL)
+        .key_down(Keys.CONTROL) \
+        .key_down(Keys.SHIFT) \
+        .pause(0) \
+        .key_up(Keys.CONTROL) \
+        .key_up(Keys.SHIFT)
     mouse_chain \
-        .pointer_move(0, 0, origin=key_reporter) \
+        .pointer_move(0, 0, origin=outer) \
+        .pause(0) \
+        .pointer_down() \
+        .pointer_up() \
+        .pause(0) \
+        .pause(0) \
         .pointer_down()
     session.actions.perform([key_chain.dict, mouse_chain.dict])
-    session.execute_script("""
-        var keyReporter = document.getElementById("keys");
-        ["mousedown", "mouseup"].forEach((e) => {
-            keyReporter.addEventListener(e, recordPointerEvent);
-          });
-        resetEvents();
-    """)
-    session.actions.release()
     expected = [
+        {"type": "mousemove"},
+        # shift and ctrl presses
+        {"type": "mousedown"},
         {"type": "mouseup"},
-        {"type": "keyup"},
+        {"type": "click"},
+        # no modifiers pressed
+        {"type": "mousedown"},
     ]
+    defaults = {
+        "altKey": False,
+        "metaKey": False,
+        "shiftKey": False,
+        "ctrlKey": False
+    }
+    for e in expected:
+        e.update(defaults)
+    for e in expected[1:4]:
+        e["shiftKey"] = True
+        e["ctrlKey"] = True
     events = [filter_dict(e, expected[0]) for e in get_events(session)]
     assert events == expected
diff --git a/webdriver/tests/actions/mouse.py b/webdriver/tests/actions/mouse.py
index 07f7809..f852653 100644
--- a/webdriver/tests/actions/mouse.py
+++ b/webdriver/tests/actions/mouse.py
@@ -1,7 +1,9 @@
 import pytest
 
-from tests.support.inline import inline
+from tests.actions.support.mouse import get_inview_center, get_viewport_rect
 from tests.actions.support.refine import get_events, filter_dict
+from tests.support.asserts import assert_move_to_coordinates
+from tests.support.inline import inline
 from tests.support.wait import wait
 
 
@@ -10,18 +12,6 @@
     return inline(content)
 
 
-def get_center(rect):
-    return {
-        "x": rect["width"] / 2 + rect["x"],
-        "y": rect["height"] / 2 + rect["y"],
-    }
-
-
-# TODO use pytest.approx once we upgrade to pytest > 3.0
-def approx(n, m, tolerance=1):
-    return abs(n - m) <= tolerance
-
-
 def test_click_at_coordinates(session, test_actions_page, mouse_chain):
     div_point = {
         "x": 82,
@@ -33,17 +23,14 @@
         .perform()
     events = get_events(session)
     assert len(events) == 4
+    assert_move_to_coordinates(div_point, "outer", events)
     for e in events:
-        if e["type"] != "mousemove":
-            assert e["pageX"] == div_point["x"]
-            assert e["pageY"] == div_point["y"]
-            assert e["target"] == "outer"
         if e["type"] != "mousedown":
             assert e["buttons"] == 0
         assert e["button"] == 0
     expected = [
         {"type": "mousedown", "buttons": 1},
-        {"type": "mouseup",  "buttons": 0},
+        {"type": "mouseup", "buttons": 0},
         {"type": "click", "buttons": 0},
     ]
     filtered_events = [filter_dict(e, expected[0]) for e in events]
@@ -63,7 +50,7 @@
     events = get_events(session)
     expected = [
         {"type": "mousedown", "button": 2},
-        {"type": "contextmenu",  "button": 2},
+        {"type": "contextmenu", "button": 2},
     ]
     assert len(events) == 4
     filtered_events = [filter_dict(e, expected[0]) for e in events]
@@ -76,7 +63,7 @@
 
 def test_click_element_center(session, test_actions_page, mouse_chain):
     outer = session.find.css("#outer", all=False)
-    center = get_center(outer.rect)
+    center = get_inview_center(outer.rect, get_viewport_rect(session))
     mouse_chain.click(element=outer).perform()
     events = get_events(session)
     assert len(events) == 4
@@ -84,12 +71,12 @@
     assert ["mousemove", "mousedown", "mouseup", "click"] == event_types
     for e in events:
         if e["type"] != "mousemove":
-            assert approx(e["pageX"], center["x"])
-            assert approx(e["pageY"], center["y"])
+            assert pytest.approx(e["pageX"], center["x"])
+            assert pytest.approx(e["pageY"], center["y"])
             assert e["target"] == "outer"
 
 
-def test_click_navigation(session, url):
+def test_click_navigation(session, url, release_actions):
     destination = url("/webdriver/tests/actions/support/test_actions_wdspec.html")
     start = link_doc(destination)
 
@@ -110,12 +97,18 @@
 
 
 @pytest.mark.parametrize("drag_duration", [0, 300, 800])
-@pytest.mark.parametrize("dx, dy",
-    [(20, 0), (0, 15), (10, 15), (-20, 0), (10, -15), (-10, -15)])
-def test_drag_and_drop(session, test_actions_page, mouse_chain, dx, dy, drag_duration):
+@pytest.mark.parametrize("dx, dy", [
+    (20, 0), (0, 15), (10, 15), (-20, 0), (10, -15), (-10, -15)
+])
+def test_drag_and_drop(session,
+                       test_actions_page,
+                       mouse_chain,
+                       dx,
+                       dy,
+                       drag_duration):
     drag_target = session.find.css("#dragTarget", all=False)
     initial_rect = drag_target.rect
-    initial_center = get_center(initial_rect)
+    initial_center = get_inview_center(initial_rect, get_viewport_rect(session))
     # Conclude chain with extra move to allow time for last queued
     # coordinate-update of drag_target and to test that drag_target is "dropped".
     mouse_chain \
@@ -128,8 +121,8 @@
     # mouseup that ends the drag is at the expected destination
     e = get_events(session)[1]
     assert e["type"] == "mouseup"
-    assert approx(e["pageX"], initial_center["x"] + dx)
-    assert approx(e["pageY"], initial_center["y"] + dy)
+    assert pytest.approx(e["pageX"], initial_center["x"] + dx)
+    assert pytest.approx(e["pageY"], initial_center["y"] + dy)
     # check resulting location of the dragged element
     final_rect = drag_target.rect
     assert initial_rect["x"] + dx == final_rect["x"]
diff --git a/webdriver/tests/actions/mouse_dblclick.py b/webdriver/tests/actions/mouse_dblclick.py
new file mode 100644
index 0000000..fc53a51
--- /dev/null
+++ b/webdriver/tests/actions/mouse_dblclick.py
@@ -0,0 +1,32 @@
+import pytest
+
+from tests.actions.support.refine import get_events, filter_dict
+from tests.support.asserts import assert_move_to_coordinates
+
+
+@pytest.mark.parametrize("click_pause", [0, 200])
+def test_dblclick_at_coordinates(session, test_actions_page, mouse_chain, click_pause):
+    div_point = {
+        "x": 82,
+        "y": 187,
+    }
+    mouse_chain \
+        .pointer_move(div_point["x"], div_point["y"]) \
+        .click() \
+        .pause(click_pause) \
+        .click() \
+        .perform()
+    events = get_events(session)
+    assert_move_to_coordinates(div_point, "outer", events)
+    expected = [
+        {"type": "mousedown", "button": 0},
+        {"type": "mouseup", "button": 0},
+        {"type": "click", "button": 0},
+        {"type": "mousedown", "button": 0},
+        {"type": "mouseup", "button": 0},
+        {"type": "click", "button": 0},
+        {"type": "dblclick", "button": 0},
+    ]
+    assert len(events) == 8
+    filtered_events = [filter_dict(e, expected[0]) for e in events]
+    assert expected == filtered_events[1:]
diff --git a/webdriver/tests/actions/mouse_pause_dblclick.py b/webdriver/tests/actions/mouse_pause_dblclick.py
new file mode 100644
index 0000000..ad17967
--- /dev/null
+++ b/webdriver/tests/actions/mouse_pause_dblclick.py
@@ -0,0 +1,52 @@
+from tests.actions.support.mouse import get_inview_center, get_viewport_rect
+from tests.actions.support.refine import get_events, filter_dict
+
+_DBLCLICK_INTERVAL = 640
+
+
+def test_dblclick_with_pause_after_second_pointerdown(session, test_actions_page, mouse_chain):
+        outer = session.find.css("#outer", all=False)
+        center = get_inview_center(outer.rect, get_viewport_rect(session))
+        mouse_chain \
+            .pointer_move(int(center["x"]), int(center["y"])) \
+            .click() \
+            .pointer_down() \
+            .pause(_DBLCLICK_INTERVAL + 10) \
+            .pointer_up() \
+            .perform()
+        events = get_events(session)
+        expected = [
+            {"type": "mousedown", "button": 0},
+            {"type": "mouseup", "button": 0},
+            {"type": "click", "button": 0},
+            {"type": "mousedown", "button": 0},
+            {"type": "mouseup", "button": 0},
+            {"type": "click", "button": 0},
+            {"type": "dblclick", "button": 0},
+        ]
+        assert len(events) == 8
+        filtered_events = [filter_dict(e, expected[0]) for e in events]
+        assert expected == filtered_events[1:]
+
+
+def test_no_dblclick(session, test_actions_page, mouse_chain):
+        outer = session.find.css("#outer", all=False)
+        center = get_inview_center(outer.rect, get_viewport_rect(session))
+        mouse_chain \
+            .pointer_move(int(center["x"]), int(center["y"])) \
+            .click() \
+            .pause(_DBLCLICK_INTERVAL + 10) \
+            .click() \
+            .perform()
+        events = get_events(session)
+        expected = [
+            {"type": "mousedown", "button": 0},
+            {"type": "mouseup", "button": 0},
+            {"type": "click", "button": 0},
+            {"type": "mousedown", "button": 0},
+            {"type": "mouseup", "button": 0},
+            {"type": "click", "button": 0},
+        ]
+        assert len(events) == 7
+        filtered_events = [filter_dict(e, expected[0]) for e in events]
+        assert expected == filtered_events[1:]
diff --git a/webdriver/tests/actions/pointer_origin.py b/webdriver/tests/actions/pointer_origin.py
new file mode 100644
index 0000000..cad59f0
--- /dev/null
+++ b/webdriver/tests/actions/pointer_origin.py
@@ -0,0 +1,129 @@
+import pytest
+
+from webdriver import MoveTargetOutOfBoundsException
+
+from tests.actions.support.mouse import get_inview_center, get_viewport_rect
+from tests.support.inline import inline
+
+
+def origin_doc(inner_style, outer_style=""):
+    return inline("""
+      <div id="outer" style="{1}"
+           onmousemove="window.coords = {{x: event.clientX, y: event.clientY}}">
+        <div id="inner" style="{0}"></div>
+      </div>
+    """.format(inner_style, outer_style))
+
+
+def get_click_coordinates(session):
+    return session.execute_script("return window.coords;")
+
+
+def test_viewport_inside(session, mouse_chain):
+    point = {"x": 50, "y": 50}
+
+    session.url = origin_doc("width: 100px; height: 50px; background: green;")
+    mouse_chain \
+        .pointer_move(point["x"], point["y"], origin="viewport") \
+        .perform()
+
+    click_coords = session.execute_script("return window.coords;")
+    assert pytest.approx(click_coords["x"], point["x"])
+    assert pytest.approx(click_coords["y"], point["y"])
+
+
+def test_viewport_outside(session, mouse_chain):
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        mouse_chain \
+            .pointer_move(-50, -50, origin="viewport") \
+            .perform()
+
+
+def test_pointer_inside(session, mouse_chain):
+    start_point = {"x": 50, "y": 50}
+    offset = {"x": 10, "y": 5}
+
+    session.url = origin_doc("width: 100px; height: 50px; background: green;")
+    mouse_chain \
+        .pointer_move(start_point["x"], start_point["y"]) \
+        .pointer_move(offset["x"], offset["y"], origin="pointer") \
+        .perform()
+
+    click_coords = session.execute_script("return window.coords;")
+    assert pytest.approx(click_coords["x"], start_point["x"] + offset["x"])
+    assert pytest.approx(click_coords["y"], start_point["y"] + offset["y"])
+
+
+def test_pointer_outside(session, mouse_chain):
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        mouse_chain \
+            .pointer_move(-50, -50, origin="pointer") \
+            .perform()
+
+
+def test_element_center_point(session, mouse_chain):
+    session.url = origin_doc("width: 100px; height: 50px; background: green;")
+    elem = session.find.css("#inner", all=False)
+    center = get_inview_center(elem.rect, get_viewport_rect(session))
+
+    mouse_chain \
+        .pointer_move(0, 0, origin=elem) \
+        .perform()
+
+    click_coords = get_click_coordinates(session)
+    assert pytest.approx(click_coords["x"], center["x"])
+    assert pytest.approx(click_coords["y"], center["y"])
+
+
+def test_element_center_point_with_offset(session, mouse_chain):
+    session.url = origin_doc("width: 100px; height: 50px; background: green;")
+    elem = session.find.css("#inner", all=False)
+    center = get_inview_center(elem.rect, get_viewport_rect(session))
+
+    mouse_chain \
+        .pointer_move(10, 15, origin=elem) \
+        .perform()
+
+    click_coords = get_click_coordinates(session)
+    assert pytest.approx(click_coords["x"], center["x"] + 10)
+    assert pytest.approx(click_coords["y"], center["y"] + 15)
+
+
+def test_element_in_view_center_point_partly_visible(session, mouse_chain):
+    session.url = origin_doc("""width: 100px; height: 50px; background: green;
+                                position: relative; left: -50px; top: -25px;""")
+    elem = session.find.css("#inner", all=False)
+    center = get_inview_center(elem.rect, get_viewport_rect(session))
+
+    mouse_chain \
+        .pointer_move(0, 0, origin=elem) \
+        .perform()
+
+    click_coords = get_click_coordinates(session)
+    assert pytest.approx(click_coords["x"], center["x"])
+    assert pytest.approx(click_coords["y"], center["y"])
+
+
+def test_element_larger_than_viewport(session, mouse_chain):
+    session.url = origin_doc("width: 300vw; height: 300vh; background: green;")
+    elem = session.find.css("#inner", all=False)
+    center = get_inview_center(elem.rect, get_viewport_rect(session))
+
+    mouse_chain \
+        .pointer_move(0, 0, origin=elem) \
+        .perform()
+
+    click_coords = get_click_coordinates(session)
+    assert pytest.approx(click_coords["x"], center["x"])
+    assert pytest.approx(click_coords["y"], center["y"])
+
+
+def test_element_outside_of_view_port(session, mouse_chain):
+    session.url = origin_doc("""width: 100px; height: 50px; background: green;
+                                position: relative; left: -200px; top: -100px;""")
+    elem = session.find.css("#inner", all=False)
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        mouse_chain \
+            .pointer_move(0, 0, origin=elem) \
+            .perform()
diff --git a/webdriver/tests/actions/sequence.py b/webdriver/tests/actions/sequence.py
index 3092034..426dbe8 100644
--- a/webdriver/tests/actions/sequence.py
+++ b/webdriver/tests/actions/sequence.py
@@ -37,45 +37,3 @@
     session.actions.release()
     assert len(get_keys(key_reporter)) == 0
     assert len(get_events(session)) == 0
-
-
-def test_many_modifiers_click(session, test_actions_page, key_chain, mouse_chain):
-    outer = session.find.css("#outer", all=False)
-    key_chain \
-        .pause(0) \
-        .key_down(Keys.CONTROL) \
-        .key_down(Keys.SHIFT) \
-        .pause(0) \
-        .key_up(Keys.CONTROL) \
-        .key_up(Keys.SHIFT)
-    mouse_chain \
-        .pointer_move(0, 0, origin=outer) \
-        .pause(0) \
-        .pointer_down() \
-        .pointer_up() \
-        .pause(0) \
-        .pause(0) \
-        .pointer_down()
-    session.actions.perform([key_chain.dict, mouse_chain.dict])
-    expected = [
-        {"type": "mousemove"},
-        # shift and ctrl presses
-        {"type": "mousedown"},
-        {"type": "mouseup"},
-        {"type": "click"},
-        # no modifiers pressed
-        {"type": "mousedown"},
-    ]
-    defaults = {
-        "altKey": False,
-        "metaKey": False,
-        "shiftKey": False,
-        "ctrlKey": False
-    }
-    for e in expected:
-        e.update(defaults)
-    for e in expected[1:4]:
-        e["shiftKey"] = True
-        e["ctrlKey"] = True
-    events = [filter_dict(e, expected[0]) for e in get_events(session)]
-    assert events == expected
diff --git a/webdriver/tests/actions/special_keys.py b/webdriver/tests/actions/special_keys.py
index f50bbc6..d2a4422 100644
--- a/webdriver/tests/actions/special_keys.py
+++ b/webdriver/tests/actions/special_keys.py
@@ -2,9 +2,10 @@
 
 import pytest
 import time
+from tests.support.fixtures import configuration
 from tests.actions.support.keys import ALL_EVENTS, Keys
 from tests.actions.support.refine import filter_dict, get_keys, get_events
-
+from webdriver import error
 
 @pytest.mark.parametrize("name,expected", ALL_EVENTS.items())
 def test_webdriver_special_key_sends_keydown(session,
@@ -20,6 +21,9 @@
             document.body.addEventListener("keydown",
                     function(e) { e.preventDefault() });
         """)
+    if (session.capabilities["browserName"] == 'internet explorer'):
+        key_reporter.click()
+        session.execute_script("resetEvents();")
     key_chain.key_down(getattr(Keys, name)).perform()
 
     # only interested in keydown
@@ -43,3 +47,37 @@
         assert entered_keys == expected["key"]
     else:
         assert len(entered_keys) == 0
+
+
+@pytest.mark.parametrize("value", [
+    (u"f"),
+    (u"\u0BA8\u0BBF"),
+    (u"\u1100\u1161\u11A8"),
+])
+def test_multiple_codepoint_keys_behave_correctly(session,
+                                                  key_reporter,
+                                                  key_chain,
+                                                  value):
+    key_chain \
+        .key_down(value) \
+        .key_up(value) \
+        .perform()
+
+    assert get_keys(key_reporter) == value
+
+
+@pytest.mark.parametrize("value", [
+    (u"fa"),
+    (u"\u0BA8\u0BBFb"),
+    (u"\u0BA8\u0BBF\u0BA8"),
+    (u"\u1100\u1161\u11A8c")
+])
+def test_invalid_multiple_codepoint_keys_fail(session,
+                                              key_reporter,
+                                              key_chain,
+                                              value):
+    with pytest.raises(error.InvalidArgumentException):
+        key_chain \
+            .key_down(value) \
+            .key_up(value) \
+            .perform()
\ No newline at end of file
diff --git a/webdriver/tests/actions/support/mouse.py b/webdriver/tests/actions/support/mouse.py
new file mode 100644
index 0000000..b3672eb
--- /dev/null
+++ b/webdriver/tests/actions/support/mouse.py
@@ -0,0 +1,26 @@
+def get_viewport_rect(session):
+    return session.execute_script("""
+        return {
+          height: window.innerHeight || document.documentElement.clientHeight,
+          width: window.innerWidth || document.documentElement.clientWidth,
+        };
+    """)
+
+
+def get_inview_center(elem_rect, viewport_rect):
+    x = {
+        "left": max(0, min(elem_rect["x"], elem_rect["x"] + elem_rect["width"])),
+        "right": min(viewport_rect["width"], max(elem_rect["x"],
+                                                 elem_rect["x"] + elem_rect["width"])),
+    }
+
+    y = {
+        "top": max(0, min(elem_rect["y"], elem_rect["y"] + elem_rect["height"])),
+        "bottom": min(viewport_rect["height"], max(elem_rect["y"],
+                                                   elem_rect["y"] + elem_rect["height"])),
+    }
+
+    return {
+        "x": (x["left"] + x["right"]) / 2,
+        "y": (y["top"] + y["bottom"]) / 2,
+    }
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/add_cookie/__init__.py
similarity index 100%
copy from webdriver/tests/state/__init__.py
copy to webdriver/tests/add_cookie/__init__.py
diff --git a/webdriver/tests/add_cookie/add.py b/webdriver/tests/add_cookie/add.py
new file mode 100644
index 0000000..f865a08
--- /dev/null
+++ b/webdriver/tests/add_cookie/add.py
@@ -0,0 +1,149 @@
+from datetime import datetime, timedelta
+
+from tests.support.asserts import assert_success
+from tests.support.fixtures import clear_all_cookies
+
+
+def add_cookie(session, cookie):
+    return session.transport.send(
+        "POST", "session/{session_id}/cookie".format(**vars(session)),
+        {"cookie": cookie})
+
+
+def test_add_domain_cookie(session, url, server_config):
+    new_cookie = {
+        "name": "hello",
+        "value": "world",
+        "domain": server_config["browser_host"],
+        "path": "/",
+        "httpOnly": False,
+        "secure": False
+    }
+
+    session.url = url("/common/blank.html")
+    clear_all_cookies(session)
+
+    result = add_cookie(session, new_cookie)
+    assert_success(result)
+
+    cookie = session.cookies("hello")
+    assert "domain" in cookie
+    assert isinstance(cookie["domain"], basestring)
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+
+    assert cookie["name"] == "hello"
+    assert cookie["value"] == "world"
+    assert cookie["domain"] == server_config["browser_host"] or \
+        cookie["domain"] == ".%s" % server_config["browser_host"]
+
+
+def test_add_cookie_for_ip(session, url, server_config, configuration):
+    new_cookie = {
+        "name": "hello",
+        "value": "world",
+        "domain": "127.0.0.1",
+        "path": "/",
+        "httpOnly": False,
+        "secure": False
+    }
+
+    session.url = "http://127.0.0.1:%s/common/blank.html" % (server_config["ports"]["http"][0])
+    clear_all_cookies(session)
+
+    result = add_cookie(session, new_cookie)
+    assert_success(result)
+
+    cookie = session.cookies("hello")
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+    assert "domain" in cookie
+    assert isinstance(cookie["domain"], basestring)
+
+    assert cookie["name"] == "hello"
+    assert cookie["value"] == "world"
+    assert cookie["domain"] == "127.0.0.1"
+
+
+def test_add_non_session_cookie(session, url):
+    a_year_from_now = int(
+        (datetime.utcnow() + timedelta(days=365) - datetime.utcfromtimestamp(0)).total_seconds())
+
+    new_cookie = {
+        "name": "hello",
+        "value": "world",
+        "expiry": a_year_from_now
+    }
+
+    session.url = url("/common/blank.html")
+    clear_all_cookies(session)
+
+    result = add_cookie(session, new_cookie)
+    assert_success(result)
+
+    cookie = session.cookies("hello")
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+    assert "expiry" in cookie
+    assert isinstance(cookie["expiry"], int)
+
+    assert cookie["name"] == "hello"
+    assert cookie["value"] == "world"
+    assert cookie["expiry"] == a_year_from_now
+
+
+def test_add_session_cookie(session, url):
+    new_cookie = {
+        "name": "hello",
+        "value": "world"
+    }
+
+    session.url = url("/common/blank.html")
+    clear_all_cookies(session)
+
+    result = add_cookie(session, new_cookie)
+    assert_success(result)
+
+    cookie = session.cookies("hello")
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+    if "expiry" in cookie:
+        assert cookie.get("expiry") is None
+
+    assert cookie["name"] == "hello"
+    assert cookie["value"] == "world"
+
+
+def test_add_session_cookie_with_leading_dot_character_in_domain(session, url, server_config):
+    new_cookie = {
+        "name": "hello",
+        "value": "world",
+        "domain": ".%s" % server_config["browser_host"]
+    }
+
+    session.url = url("/common/blank.html")
+    clear_all_cookies(session)
+
+    result = add_cookie(session, new_cookie)
+    assert_success(result)
+
+    cookie = session.cookies("hello")
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+    assert "domain" in cookie
+    assert isinstance(cookie["domain"], basestring)
+
+    assert cookie["name"] == "hello"
+    assert cookie["value"] == "world"
+    assert cookie["domain"] == server_config["browser_host"] or \
+        cookie["domain"] == ".%s" % server_config["browser_host"]
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/close_window/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/close_window/__init__.py
diff --git a/webdriver/tests/close_window/close.py b/webdriver/tests/close_window/close.py
new file mode 100644
index 0000000..ba41b34
--- /dev/null
+++ b/webdriver/tests/close_window/close.py
@@ -0,0 +1,39 @@
+from tests.support.asserts import assert_error, assert_success
+
+
+def close(session):
+    return session.transport.send(
+        "DELETE", "session/{session_id}/window".format(**vars(session)))
+
+
+def test_no_browsing_context(session, create_window):
+    new_handle = create_window()
+
+    session.window_handle = new_handle
+    session.close()
+    assert new_handle not in session.handles
+
+    response = close(session)
+    assert_error(response, "no such window")
+
+
+def test_close_browsing_context(session, create_window):
+    handles = session.handles
+
+    new_handle = create_window()
+    session.window_handle = new_handle
+
+    response = close(session)
+    value = assert_success(response, handles)
+    assert session.handles == handles
+    assert new_handle not in value
+
+
+def test_close_last_browsing_context(session):
+    assert len(session.handles) == 1
+    response = close(session)
+
+    assert_success(response, [])
+
+    # With no more open top-level browsing contexts, the session is closed.
+    session.session_id = None
diff --git a/webdriver/tests/close_window/user_prompts.py b/webdriver/tests/close_window/user_prompts.py
new file mode 100644
index 0000000..b68ef71
--- /dev/null
+++ b/webdriver/tests/close_window/user_prompts.py
@@ -0,0 +1,88 @@
+# META: timeout=long
+
+from tests.support.asserts import assert_error, assert_dialog_handled
+from tests.support.fixtures import create_dialog, create_window
+from tests.support.inline import inline
+
+
+def close(session):
+    return session.transport.send(
+        "DELETE", "session/{session_id}/window".format(**vars(session)))
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    original_handle = session.window_handle
+
+    session.window_handle = create_window(session)()
+    session.url = inline("<title>WD doc title</title>")
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+    response = close(session)
+    assert response.status == 200
+
+    # Asserting that the dialog was handled requires valid top-level browsing
+    # context, so we must switch to the original window.
+    session.window_handle = original_handle
+    assert_dialog_handled(session, "dismiss #1")
+
+    session.window_handle = create_window(session)()
+    session.url = inline("<title>WD doc title</title>")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+    response = close(session)
+    assert response.status == 200
+
+    # Asserting that the dialog was handled requires valid top-level browsing
+    # context, so we must switch to the original window.
+    session.window_handle = original_handle
+    assert_dialog_handled(session, "dismiss #2")
+
+    session.window_handle = create_window(session)()
+    session.url = inline("<title>WD doc title</title>")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+    response = close(session)
+    assert response.status == 200
+
+    # Asserting that the dialog was handled requires valid top-level browsing
+    # context, so we must switch to the original window.
+    session.window_handle = original_handle
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog, create_window):
+    session.window_handle = create_window()
+
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    response = close(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    response = close(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    response = close(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/contexts/json_serialize_windowproxy.py b/webdriver/tests/contexts/json_serialize_windowproxy.py
deleted file mode 100644
index 49507c8..0000000
--- a/webdriver/tests/contexts/json_serialize_windowproxy.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# META: timeout=long
-
-import json
-
-
-_window_id = "window-fcc6-11e5-b4f8-330a88ab9d7f"
-_frame_id = "frame-075b-4da1-b6ba-e579c2d3230a"
-
-def test_initial_window(session):
-    # non-auxiliary top-level browsing context
-    raw_json = session.execute_script("return window;")
-    obj = json.loads(raw_json)
-    assert len(obj) == 1
-    assert _window_id in obj
-    handle = obj[_window_id]
-    assert handle in session.window_handles
-
-
-def test_window_open(session):
-    # auxiliary browsing context
-    session.execute_script("window.foo = window.open()")
-    raw_json = session.execute_script("return window.foo;")
-    obj = json.loads(raw_json)
-    assert len(obj) == 1
-    assert _window_id in obj
-    handle = obj[_window_id]
-    assert handle in session.window_handles
-
-
-def test_frame(session):
-    # nested browsing context
-    append = """
-        window.frame = document.createElement('iframe');
-        document.body.appendChild(frame);
-    """
-    session.execute_script(append)
-    raw_json = session.execute_script("return frame.contentWindow;")
-    obj = json.loads(raw_json)
-    assert len(obj) == 1
-    assert _frame_id in obj
-    handle = obj[_frame_id]
-    assert handle not in session.window_handles
diff --git a/webdriver/tests/contexts/maximize_window.py b/webdriver/tests/contexts/maximize_window.py
deleted file mode 100644
index e9257e6..0000000
--- a/webdriver/tests/contexts/maximize_window.py
+++ /dev/null
@@ -1,277 +0,0 @@
-# META: timeout=long
-
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-
-alert_doc = inline("<script>window.alert()</script>")
-
-
-def maximize(session):
-    return session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
-
-
-# 10.7.3 Maximize Window
-
-
-def test_no_browsing_context(session, create_window):
-    """
-    2. If the current top-level browsing context is no longer open,
-    return error with error code no such window.
-
-    """
-    session.window_handle = create_window()
-    session.close()
-    response = maximize(session)
-    assert_error(response, "no such window")
-
-
-def test_handle_prompt_dismiss_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_accept_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_ignore():
-    """TODO"""
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    """
-    3. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - accept state
-           Accept the current user prompt.
-
-    """
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<title>WD doc title</title>")
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-    response = maximize(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-    response = maximize(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-    response = maximize(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session, create_dialog):
-    """
-    3. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - missing value default state
-           1. Dismiss the current user prompt.
-           2. Return error with error code unexpected alert open.
-
-    """
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    response = maximize(session)
-
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    response = maximize(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    response = maximize(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_fully_exit_fullscreen(session):
-    """
-    4. Fully exit fullscreen.
-
-    [...]
-
-    To fully exit fullscreen a document document, run these steps:
-
-      1. If document's fullscreen element is null, terminate these steps.
-
-      2. Unfullscreen elements whose fullscreen flag is set, within
-      document's top layer, except for document's fullscreen element.
-
-      3. Exit fullscreen document.
-
-    """
-    session.window.fullscreen()
-    assert session.execute_script("return window.fullScreen") is True
-
-    response = maximize(session)
-    assert_success(response)
-    assert session.execute_script("return window.fullScreen") is False
-
-
-def test_restore_the_window(session):
-    """
-    5. Restore the window.
-
-    [...]
-
-    To restore the window, given an operating system level window with
-    an associated top-level browsing context, run implementation-specific
-    steps to restore or unhide the window to the visible screen.  Do not
-    return from this operation until the visibility state of the top-level
-    browsing context's active document has reached the visible state,
-    or until the operation times out.
-
-    """
-    session.window.minimize()
-    assert session.execute_script("return document.hidden") is True
-
-    response = maximize(session)
-    assert_success(response)
-
-
-def test_maximize(session):
-    """
-    6. Maximize the window of the current browsing context.
-
-    [...]
-
-    To maximize the window, given an operating system level window with an
-    associated top-level browsing context, run the implementation-specific
-    steps to transition the operating system level window into the
-    maximized window state.  If the window manager supports window
-    resizing but does not have a concept of window maximation, the window
-    dimensions must be increased to the maximum available size permitted
-    by the window manager for the current screen.  Return when the window
-    has completed the transition, or within an implementation-defined
-    timeout.
-
-    """
-    before_size = session.window.size
-
-    response = maximize(session)
-    assert_success(response)
-
-    assert before_size != session.window.size
-
-
-def test_payload(session):
-    """
-    7. Return success with the JSON serialization of the current top-level
-    browsing context's window rect.
-
-    [...]
-
-    A top-level browsing context's window rect is defined as a
-    dictionary of the screenX, screenY, width and height attributes of
-    the WindowProxy. Its JSON representation is the following:
-
-    "x"
-        WindowProxy's screenX attribute.
-
-    "y"
-        WindowProxy's screenY attribute.
-
-    "width"
-        Width of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    "height"
-        Height of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    """
-    before_size = session.window.size
-
-    response = maximize(session)
-
-    # step 5
-    assert response.status == 200
-    assert isinstance(response.body["value"], dict)
-
-    value = response.body["value"]
-    assert "width" in value
-    assert "height" in value
-    assert "x" in value
-    assert "y" in value
-    assert isinstance(value["width"], int)
-    assert isinstance(value["height"], int)
-    assert isinstance(value["x"], int)
-    assert isinstance(value["y"], int)
-
-    assert before_size != session.window.size
-
-
-def test_maximize_twice_is_idempotent(session):
-    first_response = maximize(session)
-    assert_success(first_response)
-    max_size = session.window.size
-
-    second_response = maximize(session)
-    assert_success(second_response)
-    assert session.window.size == max_size
-
-
-"""
-TODO(ato): Implicit session start does not use configuration passed on
-from wptrunner.  This causes an exception.
-
-See https://bugzil.la/1398459.
-
-def test_maximize_when_resized_to_max_size(session):
-    # Determine the largest available window size by first maximising
-    # the window and getting the window rect dimensions.
-    #
-    # Then resize the window to the maximum available size.
-    session.end()
-    available = session.window.maximize()
-    session.end()
-
-    session.window.size = available
-
-    # In certain window managers a window extending to the full available
-    # dimensions of the screen may not imply that the window is maximised,
-    # since this is often a special state.  If a remote end expects a DOM
-    # resize event, this may not fire if the window has already reached
-    # its expected dimensions.
-    before = session.window.size
-    session.window.maximize()
-    assert session.window.size == before
-"""
diff --git a/webdriver/tests/contexts/resizing_and_positioning.py b/webdriver/tests/contexts/resizing_and_positioning.py
deleted file mode 100644
index 66331fa..0000000
--- a/webdriver/tests/contexts/resizing_and_positioning.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import json
-import pytest
-import webdriver
-
-
-def window_size_supported(session):
-    try:
-        session.window.size = ("a", "b")
-    except webdriver.UnsupportedOperationException:
-        return False
-    except webdriver.InvalidArgumentException:
-        return True
-
-def window_position_supported(session):
-    try:
-        session.window.position = ("a", "b")
-    except webdriver.UnsupportedOperationException:
-        return False
-    except webdriver.InvalidArgumentException:
-        return True
-
-
-def test_window_resize(session):
-    if not window_size_supported(session):
-        pytest.skip()
-
-    # setting the window size by webdriver is synchronous
-    # so we should see the results immediately
-
-    session.window.size = (400, 500)
-    assert session.window.size == (400, 500)
-
-    session.window.size = (500, 600)
-    assert session.window.size == (500, 600)
-
-
-"""
-TODO(ato):
-
-    Disable test because the while statements are wrong.
-    To fix this properly we need to write an explicit wait utility.
-
-def test_window_resize_by_script(session):
-    # setting the window size by JS is asynchronous
-    # so we poll waiting for the results
-
-    size0 = session.window.size
-
-    session.execute_script("window.resizeTo(700, 800)")
-    size1 = session.window.size
-    while size0 == size1:
-        size1 = session.window.size
-    assert size1 == (700, 800)
-
-    session.execute_script("window.resizeTo(800, 900)")
-    size2 = session.window.size
-    while size1 == size2:
-        size2 = session.window.size
-        assert size2 == (800, 900)
-    assert size2 == {"width": 200, "height": 100}
-"""
diff --git a/webdriver/tests/cookies/add_cookie.py b/webdriver/tests/cookies/add_cookie.py
deleted file mode 100644
index 86a5f86..0000000
--- a/webdriver/tests/cookies/add_cookie.py
+++ /dev/null
@@ -1,144 +0,0 @@
-from tests.support.fixtures import clear_all_cookies
-from tests.support.fixtures import server_config
-from datetime import datetime, timedelta
-
-def test_add_domain_cookie(session, url):
-    session.url = url("/common/blank.html")
-    clear_all_cookies(session)
-    create_cookie_request = {
-        "cookie": {
-            "name": "hello",
-            "value": "world",
-            "domain": "web-platform.test",
-            "path": "/",
-            "httpOnly": False,
-            "secure": False
-        }
-    }
-    result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
-
-    result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], list)
-    assert len(result.body["value"]) == 1
-    assert isinstance(result.body["value"][0], dict)
-
-    cookie = result.body["value"][0]
-    assert "name" in cookie
-    assert isinstance(cookie["name"], basestring)
-    assert "value" in cookie
-    assert isinstance(cookie["value"], basestring)
-    assert "domain" in cookie
-    assert isinstance(cookie["domain"], basestring)
-
-    assert cookie["name"] == "hello"
-    assert cookie["value"] == "world"
-    assert cookie["domain"] == ".web-platform.test"
-
-def test_add_cookie_for_ip(session, url, server_config):
-    session.url = "http://127.0.0.1:%s/404" % (server_config["ports"]["http"][0])
-    clear_all_cookies(session)
-    create_cookie_request = {
-        "cookie": {
-            "name": "hello",
-            "value": "world",
-            "domain": "127.0.0.1",
-            "path": "/",
-            "httpOnly": False,
-            "secure": False
-        }
-    }
-    result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
-
-    result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], list)
-    assert len(result.body["value"]) == 1
-    assert isinstance(result.body["value"][0], dict)
-
-    cookie = result.body["value"][0]
-    assert "name" in cookie
-    assert isinstance(cookie["name"], basestring)
-    assert "value" in cookie
-    assert isinstance(cookie["value"], basestring)
-    assert "domain" in cookie
-    assert isinstance(cookie["domain"], basestring)
-
-    assert cookie["name"] == "hello"
-    assert cookie["value"] == "world"
-    assert cookie["domain"] == "127.0.0.1"
-
-def test_add_non_session_cookie(session, url):
-    session.url = url("/common/blank.html")
-    clear_all_cookies(session)
-    a_year_from_now = int((datetime.utcnow() + timedelta(days=365)).strftime("%s"))
-    create_cookie_request = {
-        "cookie": {
-            "name": "hello",
-            "value": "world",
-            "expiry": a_year_from_now
-        }
-    }
-    result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
-
-    result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], list)
-    assert len(result.body["value"]) == 1
-    assert isinstance(result.body["value"][0], dict)
-
-    cookie = result.body["value"][0]
-    assert "name" in cookie
-    assert isinstance(cookie["name"], basestring)
-    assert "value" in cookie
-    assert isinstance(cookie["value"], basestring)
-    assert "expiry" in cookie
-    assert isinstance(cookie["expiry"], int)
-
-    assert cookie["name"] == "hello"
-    assert cookie["value"] == "world"
-    assert cookie["expiry"] == a_year_from_now
-
-def test_add_session_cookie(session, url):
-    session.url = url("/common/blank.html")
-    clear_all_cookies(session)
-    create_cookie_request = {
-        "cookie": {
-            "name": "hello",
-            "value": "world"
-        }
-    }
-    result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
-
-    result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], list)
-    assert len(result.body["value"]) == 1
-    assert isinstance(result.body["value"][0], dict)
-
-    cookie = result.body["value"][0]
-    assert "name" in cookie
-    assert isinstance(cookie["name"], basestring)
-    assert "value" in cookie
-    assert isinstance(cookie["value"], basestring)
-    assert "expiry" in cookie
-    assert cookie.get("expiry") is None
-
-    assert cookie["name"] == "hello"
-    assert cookie["value"] == "world"
diff --git a/webdriver/tests/cookies/delete_cookie.py b/webdriver/tests/cookies/delete_cookie.py
deleted file mode 100644
index 677fd94..0000000
--- a/webdriver/tests/cookies/delete_cookie.py
+++ /dev/null
@@ -1,123 +0,0 @@
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-
-def delete_cookie(session, name):
-    return session.transport.send("DELETE", "/session/%s/cookie/%s" % (session.session_id, name))
-
-
-# 16.4 Delete Cookie
-
-
-def test_no_browsing_context(session, create_window):
-    """
-    1. If the current top-level browsing context is no longer open,
-    return error with error code no such window.
-
-    """
-    session.window_handle = create_window()
-    session.close()
-    response = delete_cookie(session, "foo")
-    assert_error(response, "no such window")
-
-
-def test_handle_prompt_dismiss_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_accept_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_ignore():
-    """TODO"""
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - accept state
-           Accept the current user prompt.
-
-    """
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<title>WD doc title</title>")
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-    response = delete_cookie(session, "foo")
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-    response = delete_cookie(session, "foo")
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-    response = delete_cookie(session, "foo")
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session, create_dialog):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - missing value default state
-           1. Dismiss the current user prompt.
-           2. Return error with error code unexpected alert open.
-
-    """
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    response = delete_cookie(session, "foo")
-
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    response = delete_cookie(session, "foo")
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    response = delete_cookie(session, "foo")
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_unknown_cookie(session):
-    response = delete_cookie(session, "stilton")
-    assert response.status == 200
-    assert "value" in response.body
-    assert isinstance(response.body["value"], dict)
-    assert response.body["value"] == {}
diff --git a/webdriver/tests/cookies/get_named_cookie.py b/webdriver/tests/cookies/get_named_cookie.py
deleted file mode 100644
index 027859d..0000000
--- a/webdriver/tests/cookies/get_named_cookie.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from tests.support.inline import inline
-from tests.support.fixtures import clear_all_cookies
-from datetime import datetime, timedelta
-
-def test_get_named_session_cookie(session, url):
-    session.url = url("/common/blank.html")
-    clear_all_cookies(session)
-    session.execute_script("document.cookie = 'foo=bar'")
-
-    result = session.transport.send("GET", "session/%s/cookie/foo" % session.session_id)
-    assert result.status == 200
-    assert isinstance(result.body["value"], dict)
-
-    # table for cookie conversion
-    # https://w3c.github.io/webdriver/webdriver-spec.html#dfn-table-for-cookie-conversion
-    cookie = result.body["value"]
-    assert "name" in cookie
-    assert isinstance(cookie["name"], basestring)
-    assert "value" in cookie
-    assert isinstance(cookie["value"], basestring)
-    assert "path" in cookie
-    assert isinstance(cookie["path"], basestring)
-    assert "domain" in cookie
-    assert isinstance(cookie["domain"], basestring)
-    assert "secure" in cookie
-    assert isinstance(cookie["secure"], bool)
-    assert "httpOnly" in cookie
-    assert isinstance(cookie["httpOnly"], bool)
-    assert "expiry" in cookie
-    assert cookie.get("expiry") is None
-
-    assert cookie["name"] == "foo"
-    assert cookie["value"] == "bar"
-
-def test_get_named_cookie(session, url):
-    session.url = url("/common/blank.html")
-    clear_all_cookies(session)
-
-    # same formatting as Date.toUTCString() in javascript
-    utc_string_format = "%a, %d %b %Y %H:%M:%S"
-    a_year_from_now = (datetime.utcnow() + timedelta(days=365)).strftime(utc_string_format)
-    session.execute_script("document.cookie = 'foo=bar;expires=%s'" % a_year_from_now)
-
-    result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], list)
-    assert len(result.body["value"]) == 1
-    assert isinstance(result.body["value"][0], dict)
-
-    cookie = result.body["value"][0]
-    assert "name" in cookie
-    assert isinstance(cookie["name"], basestring)
-    assert "value" in cookie
-    assert isinstance(cookie["value"], basestring)
-    assert "expiry" in cookie
-    assert isinstance(cookie["expiry"], (int, long))
-
-    assert cookie["name"] == "foo"
-    assert cookie["value"] == "bar"
-    # convert from seconds since epoch
-    assert datetime.utcfromtimestamp(cookie["expiry"]).strftime(utc_string_format) == a_year_from_now
-
-def test_duplicated_cookie(session, url):
-    session.url = url("/common/blank.html")
-    clear_all_cookies(session)
-    create_cookie_request = {
-        "cookie": {
-            "name": "hello",
-            "value": "world",
-            "domain": "web-platform.test",
-            "path": "/",
-            "httpOnly": False,
-            "secure": False
-        }
-    }
-    result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
-
-    session.url = inline("<script>document.cookie = 'hello=newworld; domain=web-platform.test; path=/';</script>")
-    result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
-    assert result.status == 200
-    assert "value" in result.body
-    assert isinstance(result.body["value"], list)
-    assert len(result.body["value"]) == 1
-    assert isinstance(result.body["value"][0], dict)
-
-    cookie = result.body["value"][0]
-    assert "name" in cookie
-    assert isinstance(cookie["name"], basestring)
-    assert "value" in cookie
-    assert isinstance(cookie["value"], basestring)
-
-    assert cookie["name"] == "hello"
-    assert cookie["value"] == "newworld"
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/delete_cookie/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/delete_cookie/__init__.py
diff --git a/webdriver/tests/delete_cookie/delete.py b/webdriver/tests/delete_cookie/delete.py
new file mode 100644
index 0000000..ee53dc3
--- /dev/null
+++ b/webdriver/tests/delete_cookie/delete.py
@@ -0,0 +1,21 @@
+from tests.support.asserts import assert_error, assert_success
+
+
+def delete_cookie(session, name):
+    return session.transport.send(
+        "DELETE", "/session/{session_id}/cookie/{name}".format(
+            session_id=session.session_id,
+            name=name))
+
+
+def test_no_browsing_context(session, create_window):
+    session.window_handle = create_window()
+    session.close()
+
+    response = delete_cookie(session, "foo")
+    assert_error(response, "no such window")
+
+
+def test_unknown_cookie(session):
+    response = delete_cookie(session, "stilton")
+    assert_success(response)
diff --git a/webdriver/tests/delete_cookie/user_prompts.py b/webdriver/tests/delete_cookie/user_prompts.py
new file mode 100644
index 0000000..46bc375
--- /dev/null
+++ b/webdriver/tests/delete_cookie/user_prompts.py
@@ -0,0 +1,101 @@
+from tests.support.asserts import assert_error, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def delete_cookie(session, name):
+    return session.transport.send("DELETE", "/session/%s/cookie/%s" % (session.session_id, name))
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    """
+    2. Handle any user prompts and return its value if it is an error.
+
+    [...]
+
+    In order to handle any user prompts a remote end must take the
+    following steps:
+
+      [...]
+
+      2. Perform the following substeps based on the current session's
+      user prompt handler:
+
+        [...]
+
+        - accept state
+           Accept the current user prompt.
+
+    """
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<title>WD doc title</title>")
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+    response = delete_cookie(session, "foo")
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+    response = delete_cookie(session, "foo")
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+    response = delete_cookie(session, "foo")
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+    """
+    2. Handle any user prompts and return its value if it is an error.
+
+    [...]
+
+    In order to handle any user prompts a remote end must take the
+    following steps:
+
+      [...]
+
+      2. Perform the following substeps based on the current session's
+      user prompt handler:
+
+        [...]
+
+        - missing value default state
+           1. Dismiss the current user prompt.
+           2. Return error with error code unexpected alert open.
+
+    """
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    response = delete_cookie(session, "foo")
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    response = delete_cookie(session, "foo")
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    response = delete_cookie(session, "foo")
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/dismiss_alert/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/dismiss_alert/__init__.py
diff --git a/webdriver/tests/dismiss_alert/dismiss.py b/webdriver/tests/dismiss_alert/dismiss.py
new file mode 100644
index 0000000..6c06c43
--- /dev/null
+++ b/webdriver/tests/dismiss_alert/dismiss.py
@@ -0,0 +1,47 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def dismiss_alert(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/alert/dismiss".format(**vars(session)))
+
+
+# 18.1 Dismiss Alert
+
+def test_no_browsing_context(session, create_window):
+    # 18.1 step 1
+    session.window_handle = create_window()
+    session.close()
+
+    response = dismiss_alert(session)
+    assert_error(response, "no such window")
+
+
+def test_no_user_prompt(session):
+    # 18.1 step 2
+    response = dismiss_alert(session)
+    assert_error(response, "no such alert")
+
+
+def test_dismiss_alert(session):
+    # 18.1 step 3
+    session.url = inline("<script>window.alert('Hello');</script>")
+    response = dismiss_alert(session)
+    assert_success(response)
+
+
+def test_dismiss_confirm(session):
+    # 18.1 step 3
+    session.url = inline("<script>window.result = window.confirm('Hello');</script>")
+    response = dismiss_alert(session)
+    assert_success(response)
+    assert session.execute_script("return window.result;") is False
+
+
+def test_dismiss_prompt(session):
+    # 18.1 step 3
+    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Federer');</script>")
+    response = dismiss_alert(session)
+    assert_success(response)
+    assert session.execute_script("return window.result") is None
diff --git a/webdriver/tests/document_handling/page_source.py b/webdriver/tests/document_handling/page_source.py
deleted file mode 100644
index 04eae6f..0000000
--- a/webdriver/tests/document_handling/page_source.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import pytest
-
-from tests.support.inline import inline
-
-
-# 15.1.3 "Let source be the result returned from the outerHTML IDL attribute
-#         of the document element"
-def test_source_matches_outer_html(session):
-    session.url = inline("<html><head><title>Cheese</title><body>Peas")
-    expected_source = session.execute_script(
-        "return document.documentElement.outerHTML")
-
-    assert session.source == expected_source
-
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/element_clear/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/element_clear/__init__.py
diff --git a/webdriver/tests/element_clear/clear.py b/webdriver/tests/element_clear/clear.py
new file mode 100644
index 0000000..ddf37ee
--- /dev/null
+++ b/webdriver/tests/element_clear/clear.py
@@ -0,0 +1,400 @@
+# META: timeout=long
+
+import pytest
+
+from tests.support.asserts import (
+    assert_element_has_focus,
+    assert_error,
+    assert_success,
+)
+from tests.support.inline import inline
+
+
+def element_clear(session, element):
+    return session.transport.send(
+        "POST", "/session/{session_id}/element/{element_id}/clear".format(
+            session_id=session.session_id,
+            element_id=element.id))
+
+
+def add_event_listeners(element):
+    element.session.execute_script("""
+        var target = arguments[0];
+        window.events = [];
+        var expectedEvents = ["focus", "blur", "change"];
+        for (var i = 0; i < expectedEvents.length; i++) {
+          target.addEventListener(expectedEvents[i], function (eventObject) {
+            window.events.push(eventObject.type)
+          });
+        }
+        """, args=(element,))
+
+
+def get_events(session):
+    return session.execute_script("return window.events")
+
+
+@pytest.fixture(scope="session")
+def text_file(tmpdir_factory):
+    fh = tmpdir_factory.mktemp("tmp").join("hello.txt")
+    fh.write("hello")
+    return fh
+
+
+def test_closed_context(session, create_window):
+    new_window = create_window()
+    session.window_handle = new_window
+    session.url = inline("<input>")
+    element = session.find.css("input", all=False)
+    session.close()
+
+    response = element_clear(session, element)
+    assert_error(response, "no such window")
+
+
+def test_connected_element(session):
+    session.url = inline("<input>")
+    element = session.find.css("input", all=False)
+
+    session.url = inline("<input>")
+    response = element_clear(session, element)
+    assert_error(response, "stale element reference")
+
+
+def test_pointer_interactable(session):
+    session.url = inline("<input style='margin-left: -1000px' value=foobar>")
+    element = session.find.css("input", all=False)
+
+    response = element_clear(session, element)
+    assert_error(response, "element not interactable")
+
+
+def test_keyboard_interactable(session):
+    session.url = inline("""
+        <input value=foobar>
+        <div></div>
+
+        <style>
+        div {
+          position: absolute;
+          background: blue;
+          top: 0;
+        }
+        </style>
+        """)
+    element = session.find.css("input", all=False)
+    assert element.property("value") == "foobar"
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("value") == ""
+
+
+@pytest.mark.parametrize("type,value,default",
+                         [("number", "42", ""),
+                          ("range", "42", "50"),
+                          ("email", "foo@example.com", ""),
+                          ("password", "password", ""),
+                          ("search", "search", ""),
+                          ("tel", "999", ""),
+                          ("text", "text", ""),
+                          ("url", "https://example.com/", ""),
+                          ("color", "#ff0000", "#000000"),
+                          ("date", "2017-12-26", ""),
+                          ("datetime", "2017-12-26T19:48", ""),
+                          ("datetime-local", "2017-12-26T19:48", ""),
+                          ("time", "19:48", ""),
+                          ("month", "2017-11", ""),
+                          ("week", "2017-W52", "")])
+def test_input(session, type, value, default):
+    session.url = inline("<input type=%s value='%s'>" % (type, value))
+    element = session.find.css("input", all=False)
+    add_event_listeners(element)
+    assert element.property("value") == value
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("value") == default
+    events = get_events(session)
+    assert "focus" in events
+    assert "change" in events
+    assert "blur" in events
+    assert_element_has_focus(session.execute_script("return document.body"))
+
+
+@pytest.mark.parametrize("type",
+                         ["number",
+                          "range",
+                          "email",
+                          "password",
+                          "search",
+                          "tel",
+                          "text",
+                          "url",
+                          "color",
+                          "date",
+                          "datetime",
+                          "datetime-local",
+                          "time",
+                          "month",
+                          "week",
+                          "file"])
+def test_input_disabled(session, type):
+    session.url = inline("<input type=%s disabled>" % type)
+    element = session.find.css("input", all=False)
+
+    response = element_clear(session, element)
+    assert_error(response, "invalid element state")
+
+
+@pytest.mark.parametrize("type",
+                         ["number",
+                          "range",
+                          "email",
+                          "password",
+                          "search",
+                          "tel",
+                          "text",
+                          "url",
+                          "color",
+                          "date",
+                          "datetime",
+                          "datetime-local",
+                          "time",
+                          "month",
+                          "week",
+                          "file"])
+def test_input_readonly(session, type):
+    session.url = inline("<input type=%s readonly>" % type)
+    element = session.find.css("input", all=False)
+
+    response = element_clear(session, element)
+    assert_error(response, "invalid element state")
+
+
+def test_textarea(session):
+    session.url = inline("<textarea>foobar</textarea>")
+    element = session.find.css("textarea", all=False)
+    add_event_listeners(element)
+    assert element.property("value") == "foobar"
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("value") == ""
+    events = get_events(session)
+    assert "focus" in events
+    assert "change" in events
+    assert "blur" in events
+
+
+def test_textarea_disabled(session):
+    session.url = inline("<textarea disabled></textarea>")
+    element = session.find.css("textarea", all=False)
+
+    response = element_clear(session, element)
+    assert_error(response, "invalid element state")
+
+
+def test_textarea_readonly(session):
+    session.url = inline("<textarea readonly></textarea>")
+    element = session.find.css("textarea", all=False)
+
+    response = element_clear(session, element)
+    assert_error(response, "invalid element state")
+
+
+def test_input_file(session, text_file):
+    session.url = inline("<input type=file>")
+    element = session.find.css("input", all=False)
+    element.send_keys(str(text_file))
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("value") == ""
+
+
+def test_input_file_multiple(session, text_file):
+    session.url = inline("<input type=file multiple>")
+    element = session.find.css("input", all=False)
+    element.send_keys(str(text_file))
+    element.send_keys(str(text_file))
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("value") == ""
+
+
+def test_select(session):
+    session.url = inline("""
+        <select>
+          <option>foo
+        </select>
+        """)
+    select = session.find.css("select", all=False)
+    option = session.find.css("option", all=False)
+
+    response = element_clear(session, select)
+    assert_error(response, "invalid element state")
+    response = element_clear(session, option)
+    assert_error(response, "invalid element state")
+
+
+def test_button(session):
+    session.url = inline("<button></button>")
+    button = session.find.css("button", all=False)
+
+    response = element_clear(session, button)
+    assert_error(response, "invalid element state")
+
+
+def test_button_with_subtree(session):
+    """
+    Whilst an <input> is normally editable, the focusable area
+    where it is placed will default to the <button>.  I.e. if you
+    try to click <input> to focus it, you will hit the <button>.
+    """
+    session.url = inline("""
+        <button>
+          <input value=foobar>
+        </button>
+        """)
+    text_field = session.find.css("input", all=False)
+
+    response = element_clear(session, text_field)
+    assert_error(response, "element not interactable")
+
+
+def test_contenteditable(session):
+    session.url = inline("<p contenteditable>foobar</p>")
+    element = session.find.css("p", all=False)
+    add_event_listeners(element)
+    assert element.property("innerHTML") == "foobar"
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("innerHTML") == ""
+    assert get_events(session) == ["focus", "change", "blur"]
+    assert_element_has_focus(session.execute_script("return document.body"))
+
+
+def test_designmode(session):
+    session.url = inline("foobar")
+    element = session.find.css("body", all=False)
+    assert element.property("innerHTML") == "foobar"
+    session.execute_script("document.designMode = 'on'")
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("innerHTML") == "<br>"
+    assert_element_has_focus(session.execute_script("return document.body"))
+
+
+def test_resettable_element_focus_when_empty(session):
+    session.url = inline("<input>")
+    element = session.find.css("input", all=False)
+    add_event_listeners(element)
+    assert element.property("value") == ""
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("value") == ""
+    assert get_events(session) == []
+
+
+@pytest.mark.parametrize("type,invalid_value",
+                         [("number", "foo"),
+                          ("range", "foo"),
+                          ("email", "foo"),
+                          ("url", "foo"),
+                          ("color", "foo"),
+                          ("date", "foo"),
+                          ("datetime", "foo"),
+                          ("datetime-local", "foo"),
+                          ("time", "foo"),
+                          ("month", "foo"),
+                          ("week", "foo")])
+def test_resettable_element_does_not_satisfy_validation_constraints(session, type, invalid_value):
+    """
+    Some UAs allow invalid input to certain types of constrained
+    form controls.  For example, Gecko allows non-valid characters
+    to be typed into <input type=number> but Chrome does not.
+    Since we want to test that Element Clear works for clearing the
+    invalid characters in these UAs, it is fine to skip this test
+    where UAs do not allow the element to not satisfy its constraints.
+    """
+    session.url = inline("<input type=%s>" % type)
+    element = session.find.css("input", all=False)
+
+    def is_valid(element):
+        return session.execute_script("""
+            var input = arguments[0];
+            return input.validity.valid;
+            """, args=(element,))
+
+    # value property does not get updated if the input is invalid
+    element.send_keys(invalid_value)
+
+    # UA does not allow invalid input for this form control type
+    if is_valid(element):
+        return
+
+    response = element_clear(session, element)
+    assert_success(response)
+    assert is_valid(element)
+
+
+@pytest.mark.parametrize("type",
+                         ["checkbox",
+                          "radio",
+                          "hidden",
+                          "submit",
+                          "button",
+                          "image"])
+def test_non_editable_inputs(session, type):
+    session.url = inline("<input type=%s>" % type)
+    element = session.find.css("input", all=False)
+
+    response = element_clear(session, element)
+    assert_error(response, "invalid element state")
+
+
+def test_scroll_into_view(session):
+    session.url = inline("""
+        <input value=foobar>
+        <div style='height: 200vh; width: 5000vh'>
+        """)
+    element = session.find.css("input", all=False)
+    assert element.property("value") == "foobar"
+    assert session.execute_script("return window.pageYOffset") == 0
+
+    # scroll to the bottom right of the page
+    session.execute_script("""
+        var body = document.body;
+        window.scrollTo(body.scrollWidth, body.scrollHeight);
+        """)
+
+    # clear and scroll back to the top of the page
+    response = element_clear(session, element)
+    assert_success(response)
+    assert element.property("value") == ""
+
+    # check if element cleared is scrolled into view
+    rect = session.execute_script("""
+        var input = arguments[0];
+        var rect = input.getBoundingClientRect();
+        return {"top": rect.top,
+                "left": rect.left,
+                "height": rect.height,
+                "width": rect.width};
+        """, args=(element,))
+    window = session.execute_script("""
+        return {"innerHeight": window.innerHeight,
+                "innerWidth": window.innerWidth,
+                "pageXOffset": window.pageXOffset,
+                "pageYOffset": window.pageYOffset};
+        """)
+
+    assert rect["top"] < (window["innerHeight"] + window["pageYOffset"]) and \
+           rect["left"] < (window["innerWidth"] + window["pageXOffset"]) and \
+           (rect["top"] + element.rect["height"]) > window["pageYOffset"] and \
+           (rect["left"] + element.rect["width"]) > window["pageXOffset"]
diff --git a/webdriver/tests/element_click/bubbling.py b/webdriver/tests/element_click/bubbling.py
new file mode 100644
index 0000000..2b08be1
--- /dev/null
+++ b/webdriver/tests/element_click/bubbling.py
@@ -0,0 +1,158 @@
+from tests.support.asserts import assert_success
+from tests.support.inline import inline
+
+
+def element_click(session, element):
+    return session.transport.send(
+        "POST", "/session/{session_id}/element/{element_id}/click".format(
+            session_id=session.session_id,
+            element_id=element.id))
+
+
+def test_click_event_bubbles_to_parents(session):
+    session.url = inline("""
+        <style>
+        body * {
+          margin: 10px;
+          padding: 10px;
+          border: 1px solid blue;
+        }
+        </style>
+
+        <div id=three>THREE
+          <div id=two>TWO
+            <div id=one>ONE</div>
+          </div>
+        </div>
+
+        <script>
+        window.clicks = [];
+
+        var elements = document.querySelectorAll("div");
+        for (var level = 0; level < elements.length; level++) {
+          elements[level].addEventListener("click", function(clickEvent) {
+            window.clicks.push(clickEvent.currentTarget);
+          });
+        }
+        </script>
+        """)
+    three, two, one = session.find.css("div")
+    one.click()
+
+    clicks = session.execute_script("return window.clicks")
+    assert one in clicks
+    assert two in clicks
+    assert three in clicks
+
+
+def test_spin_event_loop(session):
+    """
+    Wait until the user agent event loop has spun enough times to
+    process the DOM events generated by clicking.
+    """
+    session.url = inline("""
+        <style>
+        body * {
+          margin: 10px;
+          padding: 10px;
+          border: 1px solid blue;
+        }
+        </style>
+
+        <div id=three>THREE
+          <div id=two>TWO
+            <div id=one>ONE</div>
+          </div>
+        </div>
+
+        <script>
+        window.delayedClicks = [];
+
+        var elements = document.querySelectorAll("div");
+        for (var level = 0; level < elements.length; level++) {
+          elements[level].addEventListener("click", function(clickEvent) {
+            var target = clickEvent.currentTarget;
+            setTimeout(function() { window.delayedClicks.push(target); }, 100);
+          });
+        }
+        </script>
+        """)
+    three, two, one = session.find.css("div")
+    one.click()
+
+    delayed_clicks = session.execute_script("return window.delayedClicks")
+    assert one in delayed_clicks
+    assert two in delayed_clicks
+    assert three in delayed_clicks
+
+
+def test_element_disappears_during_click(session):
+    """
+    When an element in the event bubbling order disappears (its CSS
+    display style is set to "none") during a click, Gecko and Blink
+    exhibit different behaviour.  Whilst Chrome fires a "click"
+    DOM event on <body>, Firefox does not.
+
+    A WebDriver implementation may choose to wait for this event to let
+    the event loops spin enough times to let click events propagate,
+    so this is a corner case test that Firefox does not hang indefinitely.
+    """
+    session.url = inline("""
+        <style>
+        #over,
+        #under {
+          position: absolute;
+          top: 8px;
+          left: 8px;
+          width: 100px;
+          height: 100px;
+        }
+
+        #over {
+          background: blue;
+          opacity: .5;
+        }
+        #under {
+          background: yellow;
+        }
+
+        #log {
+          margin-top: 120px;
+        }
+        </style>
+
+        <body id="body">
+          <div id=under></div>
+          <div id=over></div>
+
+           <div id=log></div>
+        </body>
+
+        <script>
+        let under = document.querySelector("#under");
+        let over = document.querySelector("#over");
+        let body = document.querySelector("body");
+        let log = document.querySelector("#log");
+
+        function logEvent({type, target, currentTarget}) {
+          log.innerHTML += "<p></p>";
+          log.lastElementChild.textContent =
+              `${type} in ${target.id} (handled by ${currentTarget.id})`;
+        }
+
+        for (let ev of ["click", "mousedown", "mouseup"]) {
+          under.addEventListener(ev, logEvent);
+          over.addEventListener(ev, logEvent);
+          body.addEventListener(ev, logEvent);
+        }
+
+        over.addEventListener("mousedown", function(mousedownEvent) {
+          over.style.display = "none";
+        });
+        </script>
+        """)
+    over = session.find.css("#over", all=False)
+
+    # should not time out
+    response = element_click(session, over)
+    assert_success(response)
diff --git a/webdriver/tests/element_click/select.py b/webdriver/tests/element_click/select.py
index 15c36fc..dcf92e6 100644
--- a/webdriver/tests/element_click/select.py
+++ b/webdriver/tests/element_click/select.py
@@ -211,3 +211,16 @@
     last_option = options[-1]
     last_option.click()
     assert last_option.selected
+
+
+def test_option_disabled(session):
+    session.url = inline("""
+        <select>
+          <option disabled>foo
+          <option>bar
+        </select>""")
+    option = session.find.css("option", all=False)
+    assert not option.selected
+
+    option.click()
+    assert not option.selected
diff --git a/webdriver/tests/element_click/stale.py b/webdriver/tests/element_click/stale.py
index d39e3a3..cd24dd7 100644
--- a/webdriver/tests/element_click/stale.py
+++ b/webdriver/tests/element_click/stale.py
@@ -1,16 +1,12 @@
-import pytest
-import webdriver
-
 from tests.support.asserts import assert_error
 from tests.support.inline import inline
 
 
-def click_element(session, element):
+def element_click(session, element):
     return session.transport.send(
-        "POST", "/session/{session_id}/element/{element_id}/click".format(**{
-            "session_id": session.session_id,
-            "element_id": element.id,
-        }))
+        "POST", "/session/{session_id}/element/{element_id}/click".format(
+            session_id=session.session_id,
+            element_id=element.id))
 
 
 def test_is_stale(session):
@@ -18,5 +14,5 @@
     button = session.find.css("button", all=False)
     session.url = inline("<button>bar</button>")
 
-    response = click_element(session, button)
+    response = element_click(session, button)
     assert_error(response, "stale element reference")
diff --git a/webdriver/tests/element_retrieval/get_active_element.py b/webdriver/tests/element_retrieval/get_active_element.py
deleted file mode 100644
index 9bc6f73..0000000
--- a/webdriver/tests/element_retrieval/get_active_element.py
+++ /dev/null
@@ -1,257 +0,0 @@
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_same_element
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-def read_global(session, name):
-    return session.execute_script("return %s;" % name)
-
-def get_active_element(session):
-    return session.transport.send("GET", "session/%s/element/active" % session.session_id)
-
-
-def assert_is_active_element(session, response):
-    """Ensure that the provided object is a successful WebDriver
-    response describing an element reference and that the referenced
-    element matches the element returned by the `activeElement`
-    attribute of the current browsing context's active document.
-
-    """
-    assert response.status == 200
-    assert "value" in response.body
-
-    from_js = session.execute_script("return document.activeElement")
-
-    if response.body["value"] is None:
-        assert from_js is None
-    else:
-        assert_same_element(session, response.body["value"], from_js)
-
-
-# > 1. If the current browsing context is no longer open, return error with
-# >    error code no such window.
-def test_closed_context(session, create_window):
-    new_window = create_window()
-    session.window_handle = new_window
-    session.close()
-
-    response = get_active_element(session)
-    assert_error(response, "no such window")
-
-
-# [...]
-# 2. Handle any user prompts and return its value if it is an error.
-# [...]
-# In order to handle any user prompts a remote end must take the following
-# steps:
-# 2. Run the substeps of the first matching user prompt handler:
-#
-#    [...]
-#    - dismiss state
-#      1. Dismiss the current user prompt.
-#    [...]
-#
-# 3. Return success.
-def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.url = inline("<body><p>Hello, World!</p></body>")
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-    assert_dialog_handled(session, "dismiss #1")
-    assert session.execute_script("return dismiss1") is None
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-    assert_dialog_handled(session, "dismiss #2")
-    assert read_global(session, "dismiss2") is None
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-    assert_dialog_handled(session, "dismiss #3")
-    assert read_global(session, "dismiss3") is None
-
-
-# [...]
-# 2. Handle any user prompts and return its value if it is an error.
-# [...]
-# In order to handle any user prompts a remote end must take the following
-# steps:
-# 2. Run the substeps of the first matching user prompt handler:
-#
-#    [...]
-#    - accept state
-#      1. Accept the current user prompt.
-#    [...]
-#
-# 3. Return success.
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<body><p>Hello, World!</p></body>")
-    create_dialog(session)("alert", text="accept #1", result_var="accept1")
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-    assert_dialog_handled(session, "accept #1")
-    assert read_global(session, "accept1") is None
-
-    create_dialog(session)("confirm", text="accept #2", result_var="accept2")
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-    assert_dialog_handled(session, "accept #2")
-    assert read_global(session, "accept2") is True
-
-    create_dialog(session)("prompt", text="accept #3", result_var="accept3")
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-    assert_dialog_handled(session, "accept #3")
-    assert read_global(session, "accept3") == "" or read_global(session, "accept3") == "undefined"
-
-
-# [...]
-# 2. Handle any user prompts and return its value if it is an error.
-# [...]
-# In order to handle any user prompts a remote end must take the following
-# steps:
-# 2. Run the substeps of the first matching user prompt handler:
-#
-#    [...]
-#    - missing value default state
-#    - not in the table of simple dialogs
-#      1. Dismiss the current user prompt.
-#      2. Return error with error code unexpected alert open.
-def test_handle_prompt_missing_value(session, create_dialog):
-    session.url = inline("<body><p>Hello, World!</p></body>")
-
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    response = get_active_element(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-    assert session.execute_script("return dismiss1") is None
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    response = get_active_element(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-    assert session.execute_script("return dismiss2") is False
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    response = get_active_element(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-    assert session.execute_script("return dismiss3") is None
-
-
-# > [...]
-# > 3. Let active element be the active element of the current browsing
-# >    context's document element.
-# > 4. Let active web element be the JSON Serialization of active element.
-# > 5. Return success with data active web element.
-def test_success_document(session):
-    session.url = inline("""
-        <body>
-            <h1>Heading</h1>
-            <input />
-            <input />
-            <input style="opacity: 0" />
-            <p>Another element</p>
-        </body>""")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-
-def test_sucess_input(session):
-    session.url = inline("""
-        <body>
-            <h1>Heading</h1>
-            <input autofocus />
-            <input style="opacity: 0" />
-            <p>Another element</p>
-        </body>""")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-
-def test_sucess_input_non_interactable(session):
-    session.url = inline("""
-        <body>
-            <h1>Heading</h1>
-            <input />
-            <input style="opacity: 0" autofocus />
-            <p>Another element</p>
-        </body>""")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-
-def test_success_explicit_focus(session):
-    session.url = inline("""
-        <body>
-            <h1>Heading</h1>
-            <input />
-            <iframe></iframe>
-        </body>""")
-
-    session.execute_script("document.body.getElementsByTagName('h1')[0].focus()")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-    session.execute_script("document.body.getElementsByTagName('input')[0].focus()")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-    session.execute_script("document.body.getElementsByTagName('iframe')[0].focus()")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-    session.execute_script("document.body.getElementsByTagName('iframe')[0].focus();")
-    session.execute_script("""
-        var iframe = document.body.getElementsByTagName('iframe')[0];
-        if (iframe.remove) {
-          iframe.remove();
-        } else {
-          iframe.removeNode(true);
-        }""")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-    session.execute_script("document.body.appendChild(document.createElement('textarea'))")
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-
-def test_success_iframe_content(session):
-    session.url = inline("<body></body>")
-    session.execute_script("""
-        let iframe = document.createElement('iframe');
-        document.body.appendChild(iframe);
-        let input = iframe.contentDocument.createElement('input');
-        iframe.contentDocument.body.appendChild(input);
-        input.focus();
-        """)
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
-
-
-def test_sucess_without_body(session):
-    session.url = inline("<body></body>")
-    session.execute_script("""
-        if (document.body.remove) {
-          document.body.remove();
-        } else {
-          document.body.removeNode(true);
-        }""")
-
-    response = get_active_element(session)
-    assert_is_active_element(session, response)
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/element_send_keys/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/element_send_keys/__init__.py
diff --git a/webdriver/tests/interaction/send_keys_content_editable.py b/webdriver/tests/element_send_keys/content_editable.py
similarity index 100%
rename from webdriver/tests/interaction/send_keys_content_editable.py
rename to webdriver/tests/element_send_keys/content_editable.py
diff --git a/webdriver/tests/element_send_keys/form_controls.py b/webdriver/tests/element_send_keys/form_controls.py
new file mode 100644
index 0000000..a3ff688
--- /dev/null
+++ b/webdriver/tests/element_send_keys/form_controls.py
@@ -0,0 +1,107 @@
+import pytest
+
+from tests.support.asserts import (
+    assert_element_has_focus,
+    assert_error,
+    assert_same_element,
+    assert_success,
+)
+from tests.support.inline import inline
+
+
+def send_keys_to_element(session, element, text):
+    return session.transport.send(
+        "POST", "/session/{session_id}/element/{element_id}/value".format(
+            session_id=session.session_id,
+            element_id=element.id),
+        {"text": text})
+
+
+def add_event_listeners(element):
+    element.session.execute_script("""
+        window.events = [];
+        var trackedEvents = ["focus", "change", "keypress", "keydown", "keyup", "input"];
+        for (var i = 0; i < trackedEvents.length; i++) {
+          arguments[0].addEventListener(trackedEvents[i], function(eventObject) { window.events.push(eventObject.type) });
+        }
+        """, args=(element,))
+
+
+def get_events(session):
+    return session.execute_script("return window.events")
+
+
+def test_input(session):
+    session.url = inline("<input>")
+    element = session.find.css("input", all=False)
+    assert element.property("value") == ""
+
+    send_keys_to_element(session, element, "foo")
+    assert element.property("value") == "foo"
+    assert_element_has_focus(element)
+
+
+def test_textarea(session):
+    session.url = inline("<textarea>")
+    element = session.find.css("textarea", all=False)
+    assert element.property("value") == ""
+
+    send_keys_to_element(session, element, "foo")
+    assert element.property("value") == "foo"
+    assert_element_has_focus(element)
+
+
+def test_input_append(session):
+    session.url = inline("<input value=a>")
+    element = session.find.css("input", all=False)
+    assert element.property("value") == "a"
+
+    send_keys_to_element(session, element, "b")
+    assert element.property("value") == "ab"
+
+    send_keys_to_element(session, element, "c")
+    assert element.property("value") == "abc"
+
+
+def test_textarea_append(session):
+    session.url = inline("<textarea>a</textarea>")
+    element = session.find.css("textarea", all=False)
+    assert element.property("value") == "a"
+
+    send_keys_to_element(session, element, "b")
+    assert element.property("value") == "ab"
+
+    send_keys_to_element(session, element, "c")
+    assert element.property("value") == "abc"
+
+
+@pytest.mark.parametrize("tag", ["input", "textarea"])
+def test_events(session, tag):
+    session.url = inline("<%s>" % tag)
+    element = session.find.css(tag, all=False)
+    add_event_listeners(element)
+
+    send_keys_to_element(session, element, "foo")
+    assert element.property("value") == "foo"
+    assert get_events(session) == ["focus",
+                                   "keydown",
+                                   "keypress",
+                                   "input",
+                                   "keyup",
+                                   "keydown",
+                                   "keypress",
+                                   "input",
+                                   "keyup",
+                                   "keydown",
+                                   "keypress",
+                                   "input",
+                                   "keyup"]
+
+
+@pytest.mark.parametrize("tag", ["input", "textarea"])
+def test_not_blurred(session, tag):
+    session.url = inline("<%s>" % tag)
+    element = session.find.css(tag, all=False)
+
+    send_keys_to_element(session, element, "")
+    assert_element_has_focus(element)
diff --git a/webdriver/tests/element_send_keys/interactability.py b/webdriver/tests/element_send_keys/interactability.py
new file mode 100644
index 0000000..3a809aa
--- /dev/null
+++ b/webdriver/tests/element_send_keys/interactability.py
@@ -0,0 +1,135 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import iframe, inline
+
+
+def element_send_keys(session, element, text):
+    return session.transport.send(
+        "POST", "/session/{session_id}/element/{element_id}/value".format(
+            session_id=session.session_id,
+            element_id=element.id),
+        {"text": text})
+
+
+def test_body_is_interactable(session):
+    session.url = inline("""
+        <body onkeypress="document.querySelector('input').value += event.key">
+          <input>
+        </body>
+    """)
+
+    element = session.find.css("body", all=False)
+    result = session.find.css("input", all=False)
+
+    # By default body is the active element
+    assert session.active_element is element
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+    assert session.active_element is element
+    assert result.property("value") == "foo"
+
+
+def test_document_element_is_interactable(session):
+    session.url = inline("""
+        <html onkeypress="document.querySelector('input').value += event.key">
+          <input>
+        </html>
+    """)
+
+    body = session.find.css("body", all=False)
+    element = session.find.css(":root", all=False)
+    result = session.find.css("input", all=False)
+
+    # By default body is the active element
+    assert session.active_element is body
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+    assert session.active_element is element
+    assert result.property("value") == "foo"
+
+
+def test_iframe_is_interactable(session):
+    session.url = inline(iframe("""
+        <body onkeypress="document.querySelector('input').value += event.key">
+          <input>
+        </body>
+    """))
+
+    body = session.find.css("body", all=False)
+    frame = session.find.css("iframe", all=False)
+
+    # By default the body has the focus
+    assert session.active_element is body
+
+    response = element_send_keys(session, frame, "foo")
+    assert_success(response)
+    assert session.active_element is frame
+
+    # Any key events are immediately routed to the nested
+    # browsing context's active document.
+    session.switch_frame(frame)
+    result = session.find.css("input", all=False)
+    assert result.property("value") == "foo"
+
+
+def test_transparent_element(session):
+    session.url = inline("""<input style="opacity: 0">""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+    assert element.property("value") == "foo"
+
+
+def test_readonly_element(session):
+    session.url = inline("<input readonly>")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+    assert element.property("value") == ""
+
+
+def test_obscured_element(session):
+    session.url = inline("""
+      <input>
+      <div style="position: relative; top: -3em; height: 5em; background: blue;"></div>
+    """)
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+    assert element.property("value") == "foo"
+
+
+def test_not_a_focusable_element(session):
+    session.url = inline("<div>foo</div>")
+    element = session.find.css("div", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_error(response, "element not interactable")
+
+
+def test_not_displayed_element(session):
+    session.url = inline("""<input style="display: none">""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_error(response, "element not interactable")
+
+
+def test_hidden_element(session):
+    session.url = inline("""<input style="visibility: hidden">""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_error(response, "element not interactable")
+
+
+def test_disabled_element(session):
+    session.url = inline("""<input disabled>""")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_error(response, "element not interactable")
diff --git a/webdriver/tests/element_send_keys/scroll_into_view.py b/webdriver/tests/element_send_keys/scroll_into_view.py
new file mode 100644
index 0000000..9ff0a9e
--- /dev/null
+++ b/webdriver/tests/element_send_keys/scroll_into_view.py
@@ -0,0 +1,77 @@
+from tests.support.asserts import assert_success
+from tests.support.fixtures import is_element_in_viewport
+from tests.support.inline import inline
+
+
+def element_send_keys(session, element, text):
+    return session.transport.send(
+        "POST", "/session/{session_id}/element/{element_id}/value".format(
+            session_id=session.session_id,
+            element_id=element.id),
+        {"text": text})
+
+
+def test_element_outside_of_not_scrollable_viewport(session):
+    session.url = inline("<input style=\"position: relative; left: -9999px;\">")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+
+    assert not is_element_in_viewport(session, element)
+
+
+def test_element_outside_of_scrollable_viewport(session):
+    session.url = inline("<input style=\"margin-top: 102vh;\">")
+    element = session.find.css("input", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+
+    assert is_element_in_viewport(session, element)
+
+
+def test_option_select_container_outside_of_scrollable_viewport(session):
+    session.url = inline("""
+        <select style="margin-top: 102vh;">
+          <option value="foo">foo</option>
+          <option value="bar" id="bar">bar</option>
+        </select>
+    """)
+    element = session.find.css("option#bar", all=False)
+    select = session.find.css("select", all=False)
+
+    response = element_send_keys(session, element, "bar")
+    assert_success(response)
+
+    assert is_element_in_viewport(session, select)
+    assert is_element_in_viewport(session, element)
+
+
+def test_option_stays_outside_of_scrollable_viewport(session):
+    session.url = inline("""
+        <select multiple style="height: 105vh; margin-top: 100vh;">
+          <option value="foo" id="foo" style="height: 100vh;">foo</option>
+          <option value="bar" id="bar" style="background-color: yellow;">bar</option>
+        </select>
+    """)
+    select = session.find.css("select", all=False)
+    option_foo = session.find.css("option#foo", all=False)
+    option_bar = session.find.css("option#bar", all=False)
+
+    response = element_send_keys(session, option_bar, "bar")
+    assert_success(response)
+
+    assert is_element_in_viewport(session, select)
+    assert is_element_in_viewport(session, option_foo)
+    assert not is_element_in_viewport(session, option_bar)
+
+
+def test_contenteditable_element_outside_of_scrollable_viewport(session):
+    session.url = inline("<div contenteditable style=\"margin-top: 102vh;\"></div>")
+    element = session.find.css("div", all=False)
+
+    response = element_send_keys(session, element, "foo")
+    assert_success(response)
+
+    assert is_element_in_viewport(session, element)
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/execute_async_script/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/execute_async_script/__init__.py
diff --git a/webdriver/tests/execute_async_script/collections.py b/webdriver/tests/execute_async_script/collections.py
new file mode 100644
index 0000000..b8602af
--- /dev/null
+++ b/webdriver/tests/execute_async_script/collections.py
@@ -0,0 +1,158 @@
+import os
+
+from tests.support.asserts import assert_same_element, assert_success
+from tests.support.inline import inline
+
+
+def execute_async_script(session, script, args=None):
+    if args is None:
+        args = []
+    body = {"script": script, "args": args}
+
+    return session.transport.send(
+        "POST", "/session/{session_id}/execute/async".format(**vars(session)),
+        body)
+
+
+def test_arguments(session):
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        function func() {
+            return arguments;
+        }
+        resolve(func("foo", "bar"));
+        """)
+    assert_success(response, [u"foo", u"bar"])
+
+
+def test_array(session):
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        resolve([1, 2]);
+        """)
+    assert_success(response, [1, 2])
+
+
+def test_file_list(session, tmpdir):
+    files = [tmpdir.join("foo.txt"), tmpdir.join("bar.txt")]
+
+    session.url = inline("<input type=file multiple>")
+    upload = session.find.css("input", all=False)
+    for file in files:
+        file.write("morn morn")
+        upload.send_keys(str(file))
+
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        resolve(document.querySelector('input').files);
+        """)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == len(files)
+    for expected, actual in zip(files, value):
+        assert isinstance(actual, dict)
+        assert "name" in actual
+        assert isinstance(actual["name"], basestring)
+        assert os.path.basename(str(expected)) == actual["name"]
+
+
+def test_html_all_collection(session):
+    session.url = inline("""
+        <p>foo
+        <p>bar
+        """)
+    html = session.find.css("html", all=False)
+    head = session.find.css("head", all=False)
+    body = session.find.css("body", all=False)
+    ps = session.find.css("p")
+
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        resolve(document.all);
+        """)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    # <html>, <head>, <body>, <p>, <p>
+    assert len(value) == 5
+
+    assert_same_element(session, html, value[0])
+    assert_same_element(session, head, value[1])
+    assert_same_element(session, body, value[2])
+    assert_same_element(session, ps[0], value[3])
+    assert_same_element(session, ps[1], value[4])
+
+
+def test_html_collection(session):
+    session.url = inline("""
+        <p>foo
+        <p>bar
+        """)
+    ps = session.find.css("p")
+
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        resolve(document.getElementsByTagName('p'));
+        """)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(ps, value):
+        assert_same_element(session, expected, actual)
+
+
+def test_html_form_controls_collection(session):
+    session.url = inline("""
+        <form>
+            <input>
+            <input>
+        </form>
+        """)
+    inputs = session.find.css("input")
+
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        resolve(document.forms[0].elements);
+        """)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(inputs, value):
+        assert_same_element(session, expected, actual)
+
+
+def test_html_options_collection(session):
+    session.url = inline("""
+        <select>
+            <option>
+            <option>
+        </select>
+        """)
+    options = session.find.css("option")
+
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        resolve(document.querySelector('select').options);
+        """)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(options, value):
+        assert_same_element(session, expected, actual)
+
+
+def test_node_list(session):
+    session.url = inline("""
+        <p>foo
+        <p>bar
+        """)
+    ps = session.find.css("p")
+
+    response = execute_async_script(session, """
+        let [resolve] = arguments;
+        resolve(document.querySelectorAll('p'));
+        """)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(ps, value):
+        assert_same_element(session, expected, actual)
diff --git a/webdriver/tests/execute_async_script/user_prompts.py b/webdriver/tests/execute_async_script/user_prompts.py
index 03e1762..7daf2a9 100644
--- a/webdriver/tests/execute_async_script/user_prompts.py
+++ b/webdriver/tests/execute_async_script/user_prompts.py
@@ -2,57 +2,95 @@
 
 from webdriver import error
 
+from tests.support.asserts import assert_success
 
-# 15.2 Executing Script
+
+def execute_async_script(session, script, args=None):
+    if args is None:
+        args = []
+    body = {"script": script, "args": args}
+
+    return session.transport.send(
+        "POST", "/session/{session_id}/execute/async".format(**vars(session)),
+        body)
+
 
 def test_handle_prompt_accept(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.execute_async_script("window.alert('Hello');")
+
+    response = execute_async_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
+    session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.accept()
 
 
 def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.execute_async_script("window.alert('Hello');")
+
+    response = execute_async_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
+    session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.dismiss()
 
 
 def test_handle_prompt_dismiss_and_notify(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss and notify"})}})
+
+    response = execute_async_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_async_script("window.alert('Hello');")
+        session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.dismiss()
 
 
 def test_handle_prompt_accept_and_notify(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept and notify"})}})
+
+    response = execute_async_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_async_script("window.alert('Hello');")
+        session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.accept()
 
 
 def test_handle_prompt_ignore(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "ignore"})}})
+
+    response = execute_async_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_async_script("window.alert('Hello');")
+        session.title
     session.alert.dismiss()
 
 
 def test_handle_prompt_default(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
+
+    response = execute_async_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_async_script("window.alert('Hello');")
+        session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.dismiss()
 
 
 def test_handle_prompt_twice(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.execute_async_script("window.alert('Hello');window.alert('Bye');")
+
+    response = execute_async_script(session, "window.alert('Hello');window.alert('Bye');")
+    assert_success(response, None)
+
+    session.alert.dismiss()
     # The first alert has been accepted by the user prompt handler, the second one remains.
     # FIXME: this is how browsers currently work, but the spec should clarify if this is the
     #        expected behavior, see https://github.com/w3c/webdriver/issues/1153.
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/execute_script/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/execute_script/__init__.py
diff --git a/webdriver/tests/execute_script/collections.py b/webdriver/tests/execute_script/collections.py
new file mode 100644
index 0000000..edee1e5
--- /dev/null
+++ b/webdriver/tests/execute_script/collections.py
@@ -0,0 +1,136 @@
+import os
+
+from tests.support.asserts import assert_same_element, assert_success
+from tests.support.inline import inline
+
+
+def execute_script(session, script, args=None):
+    if args is None:
+        args = []
+    body = {"script": script, "args": args}
+
+    return session.transport.send(
+        "POST", "/session/{session_id}/execute/sync".format(**vars(session)),
+        body)
+
+
+def test_arguments(session):
+    response = execute_script(session, """
+        function func() {
+            return arguments;
+        }
+        return func("foo", "bar");
+        """)
+    assert_success(response, [u"foo", u"bar"])
+
+
+def test_array(session):
+    response = execute_script(session, "return [1, 2]")
+    assert_success(response, [1, 2])
+
+
+def test_file_list(session, tmpdir):
+    files = [tmpdir.join("foo.txt"), tmpdir.join("bar.txt")]
+
+    session.url = inline("<input type=file multiple>")
+    upload = session.find.css("input", all=False)
+    for file in files:
+        file.write("morn morn")
+        upload.send_keys(str(file))
+
+    response = execute_script(session, "return document.querySelector('input').files")
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == len(files)
+    for expected, actual in zip(files, value):
+        assert isinstance(actual, dict)
+        assert "name" in actual
+        assert isinstance(actual["name"], basestring)
+        assert os.path.basename(str(expected)) == actual["name"]
+
+
+def test_html_all_collection(session):
+    session.url = inline("""
+        <p>foo
+        <p>bar
+        """)
+    html = session.find.css("html", all=False)
+    head = session.find.css("head", all=False)
+    body = session.find.css("body", all=False)
+    ps = session.find.css("p")
+
+    response = execute_script(session, "return document.all")
+    value = assert_success(response)
+    assert isinstance(value, list)
+    # <html>, <head>, <body>, <p>, <p>
+    assert len(value) == 5
+
+    assert_same_element(session, html, value[0])
+    assert_same_element(session, head, value[1])
+    assert_same_element(session, body, value[2])
+    assert_same_element(session, ps[0], value[3])
+    assert_same_element(session, ps[1], value[4])
+
+
+def test_html_collection(session):
+    session.url = inline("""
+        <p>foo
+        <p>bar
+        """)
+    ps = session.find.css("p")
+
+    response = execute_script(session, "return document.getElementsByTagName('p')")
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(ps, value):
+        assert_same_element(session, expected, actual)
+
+
+def test_html_form_controls_collection(session):
+    session.url = inline("""
+        <form>
+            <input>
+            <input>
+        </form>
+        """)
+    inputs = session.find.css("input")
+
+    response = execute_script(session, "return document.forms[0].elements")
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(inputs, value):
+        assert_same_element(session, expected, actual)
+
+
+def test_html_options_collection(session):
+    session.url = inline("""
+        <select>
+            <option>
+            <option>
+        </select>
+        """)
+    options = session.find.css("option")
+
+    response = execute_script(session, "return document.querySelector('select').options")
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(options, value):
+        assert_same_element(session, expected, actual)
+
+
+def test_node_list(session):
+    session.url = inline("""
+        <p>foo
+        <p>bar
+        """)
+    ps = session.find.css("p")
+
+    response = execute_script(session, "return document.querySelectorAll('p')")
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 2
+    for expected, actual in zip(ps, value):
+        assert_same_element(session, expected, actual)
diff --git a/webdriver/tests/execute_script/cyclic.py b/webdriver/tests/execute_script/cyclic.py
new file mode 100644
index 0000000..bc52b47
--- /dev/null
+++ b/webdriver/tests/execute_script/cyclic.py
@@ -0,0 +1,48 @@
+from tests.support.asserts import assert_error
+
+
+def execute_script(session, script, args=None):
+    if args is None:
+        args = []
+    body = {"script": script, "args": args}
+
+    return session.transport.send(
+        "POST", "/session/{session_id}/execute/sync".format(
+            session_id=session.session_id),
+        body)
+
+
+def test_array(session):
+    response = execute_script(session, """
+        let arr = [];
+        arr.push(arr);
+        return arr;
+        """)
+    assert_error(response, "javascript error")
+
+
+def test_object(session):
+    response = execute_script(session, """
+        let obj = {};
+        obj.reference = obj;
+        return obj;
+        """)
+    assert_error(response, "javascript error")
+
+
+def test_array_in_object(session):
+    response = execute_script(session, """
+        let arr = [];
+        arr.push(arr);
+        return {'arrayValue': arr};
+        """)
+    assert_error(response, "javascript error")
+
+
+def test_object_in_array(session):
+    response = execute_script(session, """
+        let obj = {};
+        obj.reference = obj;
+        return [obj];
+        """)
+    assert_error(response, "javascript error")
diff --git a/webdriver/tests/execute_script/json_serialize_windowproxy.py b/webdriver/tests/execute_script/json_serialize_windowproxy.py
new file mode 100644
index 0000000..9864227
--- /dev/null
+++ b/webdriver/tests/execute_script/json_serialize_windowproxy.py
@@ -0,0 +1,61 @@
+import json
+
+from tests.support.asserts import assert_success
+
+
+_window_id = "window-fcc6-11e5-b4f8-330a88ab9d7f"
+_frame_id = "frame-075b-4da1-b6ba-e579c2d3230a"
+
+
+def execute_script(session, script, args=None):
+    if args is None:
+        args = []
+    body = {"script": script, "args": args}
+
+    return session.transport.send(
+        "POST", "/session/{session_id}/execute/sync".format(**vars(session)),
+        body)
+
+
+def test_initial_window(session):
+    # non-auxiliary top-level browsing context
+    response = execute_script(session, "return window;")
+    raw_json = assert_success(response)
+
+    obj = json.loads(raw_json)
+    assert len(obj) == 1
+    assert _window_id in obj
+    handle = obj[_window_id]
+    assert handle in session.window_handles
+
+
+def test_window_open(session):
+    # auxiliary browsing context
+    session.execute_script("window.foo = window.open()")
+
+    response = execute_script(session, "return window.foo;")
+    raw_json = assert_success(response)
+
+    obj = json.loads(raw_json)
+    assert len(obj) == 1
+    assert _window_id in obj
+    handle = obj[_window_id]
+    assert handle in session.window_handles
+
+
+def test_frame(session):
+    # nested browsing context
+    append = """
+        window.frame = document.createElement('iframe');
+        document.body.appendChild(frame);
+    """
+    session.execute_script(append)
+
+    response = execute_script(session, "return frame.contentWindow;")
+    raw_json = assert_success(response)
+
+    obj = json.loads(raw_json)
+    assert len(obj) == 1
+    assert _frame_id in obj
+    handle = obj[_frame_id]
+    assert handle not in session.window_handles
diff --git a/webdriver/tests/execute_script/user_prompts.py b/webdriver/tests/execute_script/user_prompts.py
index 8d91bdd..a4cc680 100644
--- a/webdriver/tests/execute_script/user_prompts.py
+++ b/webdriver/tests/execute_script/user_prompts.py
@@ -2,57 +2,96 @@
 
 from webdriver import error
 
+from tests.support.asserts import assert_success
 
-# 15.2 Executing Script
+
+def execute_script(session, script, args=None):
+    if args is None:
+        args = []
+    body = {"script": script, "args": args}
+
+    return session.transport.send(
+        "POST", "/session/{session_id}/execute/sync".format(
+            session_id=session.session_id),
+        body)
+
 
 def test_handle_prompt_accept(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.execute_script("window.alert('Hello');")
+
+    response = execute_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
+    session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.accept()
 
 
 def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.execute_script("window.alert('Hello');")
+
+    response = execute_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
+    session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.dismiss()
 
 
 def test_handle_prompt_dismiss_and_notify(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss and notify"})}})
+
+    response = execute_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_script("window.alert('Hello');")
+        session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.dismiss()
 
 
 def test_handle_prompt_accept_and_notify(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept and notify"})}})
+
+    response = execute_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_script("window.alert('Hello');")
+        session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.accept()
 
 
 def test_handle_prompt_ignore(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "ignore"})}})
+
+    response = execute_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_script("window.alert('Hello');")
+        session.title
     session.alert.dismiss()
 
 
 def test_handle_prompt_default(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
+
+    response = execute_script(session, "window.alert('Hello');")
+    assert_success(response, None)
+
     with pytest.raises(error.UnexpectedAlertOpenException):
-        session.execute_script("window.alert('Hello');")
+        session.title
     with pytest.raises(error.NoSuchAlertException):
         session.alert.dismiss()
 
 
 def test_handle_prompt_twice(new_session, add_browser_capabilites):
     _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.execute_script("window.alert('Hello');window.alert('Bye');")
+
+    response = execute_script(session, "window.alert('Hello');window.alert('Bye');")
+    assert_success(response, None)
+
+    session.alert.dismiss()
     # The first alert has been accepted by the user prompt handler, the second one remains.
     # FIXME: this is how browsers currently work, but the spec should clarify if this is the
     #        expected behavior, see https://github.com/w3c/webdriver/issues/1153.
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/find_element/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/find_element/__init__.py
diff --git a/webdriver/tests/find_element/find.py b/webdriver/tests/find_element/find.py
new file mode 100644
index 0000000..7563179
--- /dev/null
+++ b/webdriver/tests/find_element/find.py
@@ -0,0 +1,117 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_same_element, assert_success
+from tests.support.inline import inline
+
+
+def find_element(session, using, value):
+    return session.transport.send(
+        "POST", "session/{session_id}/element".format(**vars(session)),
+        {"using": using, "value": value})
+
+
+# 12.2 Find Element
+
+@pytest.mark.parametrize("using", ["a", True, None, 1, [], {}])
+def test_invalid_using_argument(session, using):
+    # Step 1 - 2
+    response = find_element(session, using, "value")
+    assert_error(response, "invalid argument")
+
+
+@pytest.mark.parametrize("value", [None, [], {}])
+def test_invalid_selector_argument(session, value):
+    # Step 3 - 4
+    response = find_element(session, "css selector", value)
+    assert_error(response, "invalid argument")
+
+
+def test_closed_context(session, create_window):
+    # Step 5
+    new_window = create_window()
+    session.window_handle = new_window
+    session.close()
+
+    response = find_element(session, "css selector", "foo")
+
+    assert_error(response, "no such window")
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//a")])
+def test_find_element(session, using, value):
+    # Step 8 - 9
+    session.url = inline("<a href=# id=linkText>full link text</a>")
+
+    response = find_element(session, using, value)
+    assert_success(response)
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>link text</a>", "link text"),
+    ("<a href=#>&nbsp;link text&nbsp;</a>", "link text"),
+    ("<a href=#>link<br>text</a>", "link\ntext"),
+    ("<a href=#>link&amp;text</a>", "link&text"),
+    ("<a href=#>LINK TEXT</a>", "LINK TEXT"),
+    ("<a href=# style='text-transform: uppercase'>link text</a>", "LINK TEXT"),
+])
+def test_find_element_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline(document)
+
+    response = find_element(session, "link text", value)
+    assert_success(response)
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>partial link text</a>", "link"),
+    ("<a href=#>&nbsp;partial link text&nbsp;</a>", "link"),
+    ("<a href=#>partial link text</a>", "k t"),
+    ("<a href=#>partial link<br>text</a>", "k\nt"),
+    ("<a href=#>partial link&amp;text</a>", "k&t"),
+    ("<a href=#>PARTIAL LINK TEXT</a>", "LINK"),
+    ("<a href=# style='text-transform: uppercase'>partial link text</a>", "LINK"),
+])
+def test_find_element_partial_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline(document)
+
+    response = find_element(session, "partial link text", value)
+    assert_success(response)
+
+
+@pytest.mark.parametrize("using,value", [("css selector", "#wontExist")])
+def test_no_element(session, using, value):
+    # Step 8 - 9
+    response = find_element(session, using, value)
+    assert_error(response, "no such element")
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//*[name()='a']")])
+def test_xhtml_namespace(session, using, value):
+    session.url = inline("""<a href="#" id="linkText">full link text</a>""",
+                         doctype="xhtml")
+    expected = session.execute_script("return document.links[0]")
+
+    response = find_element(session, using, value)
+    value = assert_success(response)
+    assert_same_element(session, value, expected)
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", ":root"),
+                          ("tag name", "html"),
+                          ("xpath", "/html")])
+def test_htmldocument(session, using, value):
+    session.url = inline("")
+    response = find_element(session, using, value)
+    assert_success(response)
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/find_element_from_element/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/find_element_from_element/__init__.py
diff --git a/webdriver/tests/find_element_from_element/find.py b/webdriver/tests/find_element_from_element/find.py
new file mode 100644
index 0000000..d5713b3
--- /dev/null
+++ b/webdriver/tests/find_element_from_element/find.py
@@ -0,0 +1,129 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_same_element, assert_success
+from tests.support.inline import inline
+
+
+def find_element(session, element_id, using, value):
+    return session.transport.send(
+        "POST", "session/{session_id}/element/{element_id}/element".format(
+            session_id=session.session_id,
+            element_id=element_id),
+        {"using": using, "value": value})
+
+
+@pytest.mark.parametrize("using", ["a", True, None, 1, [], {}])
+def test_invalid_using_argument(session, using):
+    # Step 1 - 2
+    response = find_element(session, "notReal", using, "value")
+    assert_error(response, "invalid argument")
+
+
+@pytest.mark.parametrize("value", [None, [], {}])
+def test_invalid_selector_argument(session, value):
+    # Step 3 - 4
+    response = find_element(session, "notReal", "css selector", value)
+    assert_error(response, "invalid argument")
+
+
+def test_closed_context(session, create_window):
+    # Step 5
+    new_window = create_window()
+    session.window_handle = new_window
+    session.close()
+
+    response = find_element(session, "notReal", "css selector", "foo")
+    assert_error(response, "no such window")
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//a")])
+def test_find_element(session, using, value):
+    # Step 8 - 9
+    session.url = inline("<div><a href=# id=linkText>full link text</a></div>")
+    element = session.find.css("div", all=False)
+    response = find_element(session, element.id, using, value)
+    assert_success(response)
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>link text</a>", "link text"),
+    ("<a href=#>&nbsp;link text&nbsp;</a>", "link text"),
+    ("<a href=#>link<br>text</a>", "link\ntext"),
+    ("<a href=#>link&amp;text</a>", "link&text"),
+    ("<a href=#>LINK TEXT</a>", "LINK TEXT"),
+    ("<a href=# style='text-transform: uppercase'>link text</a>", "LINK TEXT"),
+])
+def test_find_element_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline("<div>{0}</div>".format(document))
+    element = session.find.css("div", all=False)
+
+    response = find_element(session, element.id, "link text", value)
+    assert_success(response)
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>partial link text</a>", "link"),
+    ("<a href=#>&nbsp;partial link text&nbsp;</a>", "link"),
+    ("<a href=#>partial link text</a>", "k t"),
+    ("<a href=#>partial link<br>text</a>", "k\nt"),
+    ("<a href=#>partial link&amp;text</a>", "k&t"),
+    ("<a href=#>PARTIAL LINK TEXT</a>", "LINK"),
+    ("<a href=# style='text-transform: uppercase'>partial link text</a>", "LINK"),
+])
+def test_find_element_partial_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline("<div>{0}</div>".format(document))
+    element = session.find.css("div", all=False)
+
+    response = find_element(session, element.id, "partial link text", value)
+    assert_success(response)
+
+
+@pytest.mark.parametrize("using,value",[("css selector", "#wontExist")])
+def test_no_element(session, using, value):
+    # Step 8 - 9
+    session.url = inline("<div></div>")
+    element = session.find.css("div", all=False)
+    response = find_element(session, element.id, using, value)
+    assert_error(response, "no such element")
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//*[name()='a']")])
+def test_xhtml_namespace(session, using, value):
+    session.url = inline("""<p><a href="#" id="linkText">full link text</a></p>""",
+                         doctype="xhtml")
+    from_element = session.execute_script("""return document.querySelector("p")""")
+    expected = session.execute_script("return document.links[0]")
+
+    response = find_element(session, from_element.id, using, value)
+    value = assert_success(response)
+    assert_same_element(session, value, expected)
+
+
+def test_parent_htmldocument(session):
+    session.url = inline("")
+    from_element = session.execute_script("""return document.querySelector("body")""")
+    expected = session.execute_script("return document.documentElement")
+
+    response = find_element(session, from_element.id, "xpath", "..")
+    value = assert_success(response)
+    assert_same_element(session, value, expected)
+
+
+def test_parent_of_document_node_errors(session):
+    session.url = inline("")
+    from_element = session.execute_script("return document.documentElement")
+
+    response = find_element(session, from_element.id, "xpath", "..")
+    assert_error(response, "invalid selector")
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/find_elements/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/find_elements/__init__.py
diff --git a/webdriver/tests/find_elements/find.py b/webdriver/tests/find_elements/find.py
new file mode 100644
index 0000000..1dac2a3
--- /dev/null
+++ b/webdriver/tests/find_elements/find.py
@@ -0,0 +1,134 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_same_element, assert_success
+from tests.support.inline import inline
+
+
+def find_elements(session, using, value):
+    return session.transport.send(
+        "POST", "session/{session_id}/elements".format(**vars(session)),
+        {"using": using, "value": value})
+
+
+@pytest.mark.parametrize("using", ["a", True, None, 1, [], {}])
+def test_invalid_using_argument(session, using):
+    # Step 1 - 2
+    response = find_elements(session, using, "value")
+    assert_error(response, "invalid argument")
+
+
+@pytest.mark.parametrize("value", [None, [], {}])
+def test_invalid_selector_argument(session, value):
+    # Step 3 - 4
+    response = find_elements(session, "css selector", value)
+    assert_error(response, "invalid argument")
+
+
+def test_closed_context(session, create_window):
+    # Step 5
+    new_window = create_window()
+    session.window_handle = new_window
+    session.close()
+
+    response = find_elements(session, "css selector", "foo")
+    assert_error(response, "no such window")
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//a")])
+def test_find_elements(session, using, value):
+    # Step 8 - 9
+    session.url = inline("<a href=# id=linkText>full link text</a>")
+
+    response = find_elements(session, using, value)
+    assert_success(response)
+    assert len(response.body["value"]) == 1
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>link text</a>", "link text"),
+    ("<a href=#>&nbsp;link text&nbsp;</a>", "link text"),
+    ("<a href=#>link<br>text</a>", "link\ntext"),
+    ("<a href=#>link&amp;text</a>", "link&text"),
+    ("<a href=#>LINK TEXT</a>", "LINK TEXT"),
+    ("<a href=# style='text-transform: uppercase'>link text</a>", "LINK TEXT"),
+])
+def test_find_elements_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline("<a href=#>not wanted</a><br/>{0}".format(document))
+    expected = session.execute_script("return document.links[1];")
+
+    response = find_elements(session, "link text", value)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
+
+    found_element = value[0]
+    assert_same_element(session, found_element, expected)
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>partial link text</a>", "link"),
+    ("<a href=#>&nbsp;partial link text&nbsp;</a>", "link"),
+    ("<a href=#>partial link text</a>", "k t"),
+    ("<a href=#>partial link<br>text</a>", "k\nt"),
+    ("<a href=#>partial link&amp;text</a>", "k&t"),
+    ("<a href=#>PARTIAL LINK TEXT</a>", "LINK"),
+    ("<a href=# style='text-transform: uppercase'>partial link text</a>", "LINK"),
+])
+def test_find_elements_partial_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline("<a href=#>not wanted</a><br/>{0}".format(document))
+    expected = session.execute_script("return document.links[1];")
+
+    response = find_elements(session, "partial link text", value)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
+
+    found_element = value[0]
+    assert_same_element(session, found_element, expected)
+
+
+@pytest.mark.parametrize("using,value", [("css selector", "#wontExist")])
+def test_no_element(session, using, value):
+    # Step 8 - 9
+    response = find_elements(session, using, value)
+    assert_success(response)
+    assert response.body["value"] == []
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//*[name()='a']")])
+def test_xhtml_namespace(session, using, value):
+    session.url = inline("""<a href="#" id="linkText">full link text</a>""",
+                         doctype="xhtml")
+    expected = session.execute_script("return document.links[0];")
+
+    response = find_elements(session, using, value)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
+
+    found_element = value[0]
+    assert_same_element(session, found_element, expected)
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", ":root"),
+                          ("tag name", "html"),
+                          ("xpath", "/html")])
+def test_htmldocument(session, using, value):
+    session.url = inline("")
+    response = find_elements(session, using, value)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/find_elements_from_element/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/find_elements_from_element/__init__.py
diff --git a/webdriver/tests/find_elements_from_element/find.py b/webdriver/tests/find_elements_from_element/find.py
new file mode 100644
index 0000000..d24d45a
--- /dev/null
+++ b/webdriver/tests/find_elements_from_element/find.py
@@ -0,0 +1,150 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_same_element, assert_success
+from tests.support.inline import inline
+
+
+def find_elements(session, element_id, using, value):
+    return session.transport.send(
+        "POST", "session/{session_id}/element/{element_id}/elements".format(
+            session_id=session.session_id,
+            element_id=element_id),
+        {"using": using, "value": value})
+
+
+@pytest.mark.parametrize("using", [("a"), (True), (None), (1), ([]), ({})])
+def test_invalid_using_argument(session, using):
+    # Step 1 - 2
+    response = find_elements(session, "notReal", using, "value")
+    assert_error(response, "invalid argument")
+
+
+@pytest.mark.parametrize("value", [None, [], {}])
+def test_invalid_selector_argument(session, value):
+    # Step 3 - 4
+    response = find_elements(session, "notReal", "css selector", value)
+    assert_error(response, "invalid argument")
+
+
+def test_closed_context(session, create_window):
+    # Step 5
+    new_window = create_window()
+    session.window_handle = new_window
+    session.close()
+
+    response = find_elements(session, "notReal", "css selector", "foo")
+
+    assert_error(response, "no such window")
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//a")])
+def test_find_elements(session, using, value):
+    # Step 8 - 9
+    session.url = inline("<div><a href=# id=linkText>full link text</a></div>")
+    element = session.find.css("div", all=False)
+    response = find_elements(session, element.id, using, value)
+    assert_success(response)
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>link text</a>", "link text"),
+    ("<a href=#>&nbsp;link text&nbsp;</a>", "link text"),
+    ("<a href=#>link<br>text</a>", "link\ntext"),
+    ("<a href=#>link&amp;text</a>", "link&text"),
+    ("<a href=#>LINK TEXT</a>", "LINK TEXT"),
+    ("<a href=# style='text-transform: uppercase'>link text</a>", "LINK TEXT"),
+])
+def test_find_elements_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline("<div><a href=#>not wanted</a><br/>{0}</div>".format(document))
+    element = session.find.css("div", all=False)
+    expected = session.execute_script("return document.links[1];")
+
+    response = find_elements(session, element.id, "link text", value)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
+
+    found_element = value[0]
+    assert_same_element(session, found_element, expected)
+
+
+@pytest.mark.parametrize("document,value", [
+    ("<a href=#>partial link text</a>", "link"),
+    ("<a href=#>&nbsp;partial link text&nbsp;</a>", "link"),
+    ("<a href=#>partial link text</a>", "k t"),
+    ("<a href=#>partial link<br>text</a>", "k\nt"),
+    ("<a href=#>partial link&amp;text</a>", "k&t"),
+    ("<a href=#>PARTIAL LINK TEXT</a>", "LINK"),
+    ("<a href=# style='text-transform: uppercase'>partial link text</a>", "LINK"),
+])
+def test_find_elements_partial_link_text(session, document, value):
+    # Step 8 - 9
+    session.url = inline("<div><a href=#>not wanted</a><br/>{0}</div>".format(document))
+    element = session.find.css("div", all=False)
+    expected = session.execute_script("return document.links[1];")
+
+    response = find_elements(session, element.id, "partial link text", value)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
+
+    found_element = value[0]
+    assert_same_element(session, found_element, expected)
+
+
+@pytest.mark.parametrize("using,value", [("css selector", "#wontExist")])
+def test_no_element(session, using, value):
+    # Step 8 - 9
+    session.url = inline("<div></div>")
+    element = session.find.css("div", all=False)
+    response = find_elements(session, element.id, using, value)
+    assert response.body["value"] == []
+
+
+@pytest.mark.parametrize("using,value",
+                         [("css selector", "#linkText"),
+                          ("link text", "full link text"),
+                          ("partial link text", "link text"),
+                          ("tag name", "a"),
+                          ("xpath", "//*[name()='a']")])
+def test_xhtml_namespace(session, using, value):
+    session.url = inline("""<p><a href="#" id="linkText">full link text</a></p>""",
+                         doctype="xhtml")
+    from_element = session.execute_script("""return document.querySelector("p")""")
+    expected = session.execute_script("return document.links[0]")
+
+    response = find_elements(session, from_element.id, using, value)
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
+
+    found_element = value[0]
+    assert_same_element(session, found_element, expected)
+
+
+def test_parent_htmldocument(session):
+    session.url = inline("")
+    from_element = session.execute_script("""return document.querySelector("body")""")
+    expected = session.execute_script("return document.documentElement")
+
+    response = find_elements(session, from_element.id, "xpath", "..")
+    value = assert_success(response)
+    assert isinstance(value, list)
+    assert len(value) == 1
+
+    found_element = value[0]
+    assert_same_element(session, found_element, expected)
+
+
+def test_parent_of_document_node_errors(session):
+    session.url = inline("")
+    from_element = session.execute_script("return document.documentElement")
+
+    response = find_elements(session, from_element.id, "xpath", "..")
+    assert_error(response, "invalid selector")
diff --git a/webdriver/tests/fullscreen_window.py b/webdriver/tests/fullscreen_window.py
deleted file mode 100644
index 9b42ad3..0000000
--- a/webdriver/tests/fullscreen_window.py
+++ /dev/null
@@ -1,208 +0,0 @@
-# META: timeout=long
-
-from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-
-alert_doc = inline("<script>window.alert()</script>")
-
-
-def read_global(session, name):
-    return session.execute_script("return %s;" % name)
-
-
-def fullscreen(session):
-    return session.transport.send("POST", "session/%s/window/fullscreen" % session.session_id)
-
-
-# 10.7.5 Fullscreen Window
-
-
-def test_no_browsing_context(session, create_window):
-    """
-    1. If the current top-level browsing context is no longer open,
-    return error with error code no such window.
-
-    """
-    session.window_handle = create_window()
-    session.close()
-    response = fullscreen(session)
-    assert_error(response, "no such window")
-
-
-def test_handle_prompt_dismiss_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_accept_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_ignore():
-    """TODO"""
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - accept state
-           Accept the current user prompt.
-
-    """
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog(session)("alert", text="accept #1", result_var="accept1")
-
-    expected_title = read_global(session, "document.title")
-    response = fullscreen(session)
-
-    assert_success(response, expected_title)
-    assert_dialog_handled(session, "accept #1")
-    assert read_global(session, "accept1") == None
-
-    expected_title = read_global(session, "document.title")
-    create_dialog(session)("confirm", text="accept #2", result_var="accept2")
-
-    response = fullscreen(session)
-
-    assert_success(response, expected_title)
-    assert_dialog_handled(session, "accept #2")
-    assert read_global(session, "accept2"), True
-
-    expected_title = read_global(session, "document.title")
-    create_dialog(session)("prompt", text="accept #3", result_var="accept3")
-
-    response = fullscreen(session)
-
-    assert_success(response, expected_title)
-    assert_dialog_handled(session, "accept #3")
-    assert read_global(session, "accept3") == ""
-
-
-def test_handle_prompt_missing_value(session, create_dialog):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - missing value default state
-           1. Dismiss the current user prompt.
-           2. Return error with error code unexpected alert open.
-
-    """
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    response = fullscreen(session)
-
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-    assert read_global(session, "accept1") == None
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    response = fullscreen(session)
-
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-    assert read_global(session, "dismiss2") == False
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    response = fullscreen(session)
-
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-    assert read_global(session, "dismiss3") == None
-
-
-def test_fullscreen(session):
-    """
-    4. Call fullscreen an element with the current top-level browsing
-    context's active document's document element.
-
-    """
-    response = fullscreen(session)
-    assert_success(response)
-    assert session.execute_script("return window.fullScreen") is True
-
-
-def test_payload(session):
-    """
-    5. Return success with the JSON serialization of the current top-level
-    browsing context's window rect.
-
-    [...]
-
-    A top-level browsing context's window rect is defined as a
-    dictionary of the screenX, screenY, width and height attributes of
-    the WindowProxy. Its JSON representation is the following:
-
-    "x"
-        WindowProxy's screenX attribute.
-
-    "y"
-        WindowProxy's screenY attribute.
-
-    "width"
-        Width of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    "height"
-        Height of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    """
-    response = fullscreen(session)
-
-    # step 5
-    assert response.status == 200
-    assert isinstance(response.body["value"], dict)
-
-    value = response.body["value"]
-    assert "width" in value
-    assert "height" in value
-    assert "x" in value
-    assert "y" in value
-    assert isinstance(value["width"], int)
-    assert isinstance(value["height"], int)
-    assert isinstance(value["x"], int)
-    assert isinstance(value["y"], int)
-
-
-def test_fullscreen_twice_is_idempotent(session):
-    assert session.execute_script("return window.fullScreen") is False
-
-    first_response = fullscreen(session)
-    assert_success(first_response)
-    assert session.execute_script("return window.fullScreen") is True
-
-    second_response = fullscreen(session)
-    assert_success(second_response)
-    assert session.execute_script("return window.fullScreen") is True
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/fullscreen_window/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/fullscreen_window/__init__.py
diff --git a/webdriver/tests/fullscreen_window/fullscreen.py b/webdriver/tests/fullscreen_window/fullscreen.py
new file mode 100644
index 0000000..de7d942
--- /dev/null
+++ b/webdriver/tests/fullscreen_window/fullscreen.py
@@ -0,0 +1,96 @@
+from tests.support.asserts import assert_error, assert_success
+
+
+def fullscreen(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/window/fullscreen".format(**vars(session)))
+
+
+def is_fullscreen(session):
+    # At the time of writing, WebKit does not conform to the Fullscreen API specification.
+    # Remove the prefixed fallback when https://bugs.webkit.org/show_bug.cgi?id=158125 is fixed.
+    return session.execute_script("return !!(window.fullScreen || document.webkitIsFullScreen)")
+
+
+# 10.7.5 Fullscreen Window
+
+
+def test_no_browsing_context(session, create_window):
+    """
+    1. If the current top-level browsing context is no longer open,
+    return error with error code no such window.
+
+    """
+    session.window_handle = create_window()
+    session.close()
+    response = fullscreen(session)
+    assert_error(response, "no such window")
+
+
+def test_fullscreen(session):
+    """
+    4. Call fullscreen an element with the current top-level browsing
+    context's active document's document element.
+
+    """
+    response = fullscreen(session)
+    assert_success(response)
+
+    assert is_fullscreen(session) is True
+
+
+def test_payload(session):
+    """
+    5. Return success with the JSON serialization of the current top-level
+    browsing context's window rect.
+
+    [...]
+
+    A top-level browsing context's window rect is defined as a
+    dictionary of the screenX, screenY, width and height attributes of
+    the WindowProxy. Its JSON representation is the following:
+
+    "x"
+        WindowProxy's screenX attribute.
+
+    "y"
+        WindowProxy's screenY attribute.
+
+    "width"
+        Width of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    "height"
+        Height of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    """
+    response = fullscreen(session)
+
+    # step 5
+    assert response.status == 200
+    assert isinstance(response.body["value"], dict)
+
+    value = response.body["value"]
+    assert "width" in value
+    assert "height" in value
+    assert "x" in value
+    assert "y" in value
+    assert isinstance(value["width"], int)
+    assert isinstance(value["height"], int)
+    assert isinstance(value["x"], int)
+    assert isinstance(value["y"], int)
+
+
+def test_fullscreen_twice_is_idempotent(session):
+    assert is_fullscreen(session) is False
+
+    first_response = fullscreen(session)
+    assert_success(first_response)
+    assert is_fullscreen(session) is True
+
+    second_response = fullscreen(session)
+    assert_success(second_response)
+    assert is_fullscreen(session) is True
diff --git a/webdriver/tests/fullscreen_window/user_prompts.py b/webdriver/tests/fullscreen_window/user_prompts.py
new file mode 100644
index 0000000..421ce81
--- /dev/null
+++ b/webdriver/tests/fullscreen_window/user_prompts.py
@@ -0,0 +1,116 @@
+from tests.support.asserts import assert_error, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def read_global(session, name):
+    return session.execute_script("return %s;" % name)
+
+
+def fullscreen(session):
+    return session.transport.send("POST", "session/%s/window/fullscreen" % session.session_id)
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    """
+    2. Handle any user prompts and return its value if it is an error.
+
+    [...]
+
+    In order to handle any user prompts a remote end must take the
+    following steps:
+
+      [...]
+
+      2. Perform the following substeps based on the current session's
+      user prompt handler:
+
+        [...]
+
+        - accept state
+           Accept the current user prompt.
+
+    """
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog(session)("alert", text="accept #1", result_var="accept1")
+
+    fullscreen(session)
+
+    assert_dialog_handled(session, "accept #1")
+    assert read_global(session, "accept1") is None
+
+    read_global(session, "document.title")
+    create_dialog(session)("confirm", text="accept #2", result_var="accept2")
+
+    fullscreen(session)
+
+    assert_dialog_handled(session, "accept #2")
+    assert read_global(session, "accept2"), True
+
+    create_dialog(session)("prompt", text="accept #3", result_var="accept3")
+
+    fullscreen(session)
+
+    assert_dialog_handled(session, "accept #3")
+    assert read_global(session, "accept3") == "" or read_global(session, "accept3") == "undefined"
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+    """
+    2. Handle any user prompts and return its value if it is an error.
+
+    [...]
+
+    In order to handle any user prompts a remote end must take the
+    following steps:
+
+      [...]
+
+      2. Perform the following substeps based on the current session's
+      user prompt handler:
+
+        [...]
+
+        - missing value default state
+           1. Dismiss the current user prompt.
+           2. Return error with error code unexpected alert open.
+
+    """
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    response = fullscreen(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+    assert read_global(session, "dismiss1") is None
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    response = fullscreen(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+    assert read_global(session, "dismiss2") is False
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    response = fullscreen(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
+    assert read_global(session, "dismiss3") is None
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/get_active_element/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/get_active_element/__init__.py
diff --git a/webdriver/tests/get_active_element/get.py b/webdriver/tests/get_active_element/get.py
new file mode 100644
index 0000000..d13effb
--- /dev/null
+++ b/webdriver/tests/get_active_element/get.py
@@ -0,0 +1,149 @@
+from tests.support.asserts import assert_error, assert_same_element
+from tests.support.inline import inline
+
+
+def read_global(session, name):
+    return session.execute_script("return %s;" % name)
+
+
+def get_active_element(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/active".format(**vars(session)))
+
+
+def assert_is_active_element(session, response):
+    """Ensure that the provided object is a successful WebDriver
+    response describing an element reference and that the referenced
+    element matches the element returned by the `activeElement`
+    attribute of the current browsing context's active document.
+
+    """
+    assert response.status == 200
+    assert "value" in response.body
+
+    from_js = session.execute_script("return document.activeElement")
+
+    if response.body["value"] is None:
+        assert from_js is None
+    else:
+        assert_same_element(session, response.body["value"], from_js)
+
+
+def test_closed_context(session, create_window):
+    """
+    > 1. If the current browsing context is no longer open, return error with
+    >    error code no such window.
+    """
+    new_window = create_window()
+    session.window_handle = new_window
+    session.close()
+
+    response = get_active_element(session)
+    assert_error(response, "no such window")
+
+
+def test_success_document(session):
+    """
+    > [...]
+    > 3. Let active element be the active element of the current browsing
+    >    context's document element.
+    > 4. Let active web element be the JSON Serialization of active element.
+    > 5. Return success with data active web element.
+    """
+    session.url = inline("""
+        <body>
+            <h1>Heading</h1>
+            <input />
+            <input />
+            <input style="opacity: 0" />
+            <p>Another element</p>
+        </body>""")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+
+def test_sucess_input(session):
+    session.url = inline("""
+        <body>
+            <h1>Heading</h1>
+            <input autofocus />
+            <input style="opacity: 0" />
+            <p>Another element</p>
+        </body>""")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+
+def test_sucess_input_non_interactable(session):
+    session.url = inline("""
+        <body>
+            <h1>Heading</h1>
+            <input />
+            <input style="opacity: 0" autofocus />
+            <p>Another element</p>
+        </body>""")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+
+def test_success_explicit_focus(session):
+    session.url = inline("""
+        <body>
+            <h1>Heading</h1>
+            <input />
+            <iframe></iframe>
+        </body>""")
+
+    session.execute_script("document.body.getElementsByTagName('h1')[0].focus()")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+    session.execute_script("document.body.getElementsByTagName('input')[0].focus()")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+    session.execute_script("document.body.getElementsByTagName('iframe')[0].focus()")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+    session.execute_script("document.body.getElementsByTagName('iframe')[0].focus();")
+    session.execute_script("""
+        var iframe = document.body.getElementsByTagName('iframe')[0];
+        if (iframe.remove) {
+          iframe.remove();
+        } else {
+          iframe.removeNode(true);
+        }""")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+    session.execute_script("document.body.appendChild(document.createElement('textarea'))")
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+
+def test_success_iframe_content(session):
+    session.url = inline("<body></body>")
+    session.execute_script("""
+        let iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        let input = iframe.contentDocument.createElement('input');
+        iframe.contentDocument.body.appendChild(input);
+        input.focus();
+        """)
+
+    response = get_active_element(session)
+    assert_is_active_element(session, response)
+
+
+def test_missing_document_element(session):
+    session.url = inline("<body></body>")
+    session.execute_script("""
+        if (document.body.remove) {
+          document.body.remove();
+        } else {
+          document.body.removeNode(true);
+        }""")
+
+    response = get_active_element(session)
+    assert_error(response, "no such element")
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/get_alert_text/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/get_alert_text/__init__.py
diff --git a/webdriver/tests/get_alert_text/get.py b/webdriver/tests/get_alert_text/get.py
new file mode 100644
index 0000000..28f8671
--- /dev/null
+++ b/webdriver/tests/get_alert_text/get.py
@@ -0,0 +1,60 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def get_alert_text(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/alert/text".format(**vars(session)))
+
+
+# 18.3 Get Alert Text
+
+def test_no_browsing_context(session, create_window):
+    # 18.3 step 1
+    session.window_handle = create_window()
+    session.close()
+
+    response = get_alert_text(session)
+    assert_error(response, "no such window")
+
+
+def test_no_user_prompt(session):
+    # 18.3 step 2
+    response = get_alert_text(session)
+    assert_error(response, "no such alert")
+
+
+def test_get_alert_text(session):
+    # 18.3 step 3
+    session.url = inline("<script>window.alert('Hello');</script>")
+    response = get_alert_text(session)
+    assert_success(response)
+    assert isinstance(response.body, dict)
+    assert "value" in response.body
+    alert_text = response.body["value"]
+    assert isinstance(alert_text, basestring)
+    assert alert_text == "Hello"
+
+
+def test_get_confirm_text(session):
+    # 18.3 step 3
+    session.url = inline("<script>window.confirm('Hello');</script>")
+    response = get_alert_text(session)
+    assert_success(response)
+    assert isinstance(response.body, dict)
+    assert "value" in response.body
+    confirm_text = response.body["value"]
+    assert isinstance(confirm_text, basestring)
+    assert confirm_text == "Hello"
+
+
+def test_get_prompt_text(session):
+    # 18.3 step 3
+    session.url = inline("<script>window.prompt('Enter Your Name: ', 'Federer');</script>")
+    response = get_alert_text(session)
+    assert_success(response)
+    assert isinstance(response.body, dict)
+    assert "value" in response.body
+    prompt_text = response.body["value"]
+    assert isinstance(prompt_text, basestring)
+    assert prompt_text == "Enter Your Name: "
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/get_current_url/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/get_current_url/__init__.py
diff --git a/webdriver/tests/get_current_url/get.py b/webdriver/tests/get_current_url/get.py
new file mode 100644
index 0000000..2b7965f
--- /dev/null
+++ b/webdriver/tests/get_current_url/get.py
@@ -0,0 +1,100 @@
+import json
+import pytest
+import types
+
+from tests.support.inline import inline
+from tests.support.asserts import assert_error, assert_success
+from tests.support.wait import wait
+
+alert_doc = inline("<script>window.alert()</script>")
+frame_doc = inline("<p>frame")
+one_frame_doc = inline("<iframe src='%s'></iframe>" % frame_doc)
+two_frames_doc = inline("<iframe src='%s'></iframe>" % one_frame_doc)
+
+
+def get_current_url(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/url".format(**vars(session)))
+
+
+# TODO(ato): 7.1 Get
+
+
+def test_get_current_url_no_browsing_context(session, create_window):
+    # 7.2 step 1
+    session.window_handle = create_window()
+    session.close()
+
+    result = get_current_url(session)
+    assert_error(result, "no such window")
+
+def test_get_current_url_matches_location(session):
+    # 7.2 step 3
+    url = session.execute_script("return window.location.href")
+
+    result = get_current_url(session)
+    assert_success(result, url)
+
+def test_get_current_url_payload(session):
+    # 7.2 step 4-5
+    session.start()
+
+    result = get_current_url(session)
+    assert result.status == 200
+    assert isinstance(result.body["value"], basestring)
+
+def test_get_current_url_special_pages(session):
+    session.url = "about:blank"
+
+    result = get_current_url(session)
+    assert_success(result, "about:blank")
+
+# TODO(ato): This test requires modification to pass on Windows
+def test_get_current_url_file_protocol(session):
+    # tests that the browsing context remains the same
+    # when navigated privileged documents
+    session.url = "file:///"
+
+    result = get_current_url(session)
+    assert_success(result, "file:///")
+
+# TODO(ato): Test for http:// and https:// protocols.
+# We need to expose a fixture for accessing
+# documents served by wptserve in order to test this.
+
+def test_set_malformed_url(session):
+    result = session.transport.send("POST",
+                                    "session/%s/url" % session.session_id,
+                                    {"url": "foo"})
+
+    assert_error(result, "invalid argument")
+
+def test_get_current_url_after_modified_location(session):
+    start = get_current_url(session)
+    session.execute_script("window.location.href = 'about:blank#wd_test_modification'")
+    wait(session,
+         lambda _: get_current_url(session).body["value"] != start.body["value"],
+         "URL did not change")
+
+    result = get_current_url(session)
+    assert_success(result, "about:blank#wd_test_modification")
+
+def test_get_current_url_nested_browsing_context(session, create_frame):
+    session.url = "about:blank#wd_from_within_frame"
+    session.switch_frame(create_frame())
+
+    result = get_current_url(session)
+    assert_success(result, "about:blank#wd_from_within_frame")
+
+def test_get_current_url_nested_browsing_contexts(session):
+    session.url = two_frames_doc
+    top_level_url = session.url
+
+    outer_frame = session.find.css("iframe", all=False)
+    session.switch_frame(outer_frame)
+
+    inner_frame = session.find.css("iframe", all=False)
+    session.switch_frame(inner_frame)
+
+    result = get_current_url(session)
+    assert_success(result, top_level_url)
diff --git a/webdriver/tests/get_current_url/user_prompts.py b/webdriver/tests/get_current_url/user_prompts.py
new file mode 100644
index 0000000..8bf2867
--- /dev/null
+++ b/webdriver/tests/get_current_url/user_prompts.py
@@ -0,0 +1,77 @@
+from tests.support.asserts import assert_error, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def read_global(session, name):
+    return session.execute_script("return %s;" % name)
+
+
+def get_current_url(session):
+    return session.transport.send("GET", "session/%s/url" % session.session_id)
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog(session)("alert", text="accept #1", result_var="accept1")
+
+    get_current_url(session)
+
+    assert_dialog_handled(session, "accept #1")
+    assert read_global(session, "accept1") is None
+
+    read_global(session, "document.title")
+    create_dialog(session)("confirm", text="accept #2", result_var="accept2")
+
+    get_current_url(session)
+
+    assert_dialog_handled(session, "accept #2")
+    assert read_global(session, "accept2"), True
+
+    create_dialog(session)("prompt", text="accept #3", result_var="accept3")
+
+    get_current_url(session)
+
+    assert_dialog_handled(session, "accept #3")
+    assert read_global(session, "accept3") == "" or read_global(session, "accept3") == "undefined"
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    response = get_current_url(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+    assert read_global(session, "dismiss1") is None
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    response = get_current_url(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+    assert read_global(session, "dismiss2") is False
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    response = get_current_url(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
+    assert read_global(session, "dismiss3") is None
diff --git a/webdriver/tests/get_element_attribute/get.py b/webdriver/tests/get_element_attribute/get.py
new file mode 100644
index 0000000..3fcc01a
--- /dev/null
+++ b/webdriver/tests/get_element_attribute/get.py
@@ -0,0 +1,101 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def get_element_attribute(session, element, attr):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/attribute/{attr}".format(
+            session_id=session.session_id,
+            element_id=element,
+            attr=attr))
+
+
+def test_no_browsing_context(session, create_window):
+    session.window_handle = create_window()
+    session.close()
+
+    result = get_element_attribute(session, "foo", "id")
+    assert_error(result, "no such window")
+
+def test_element_not_found(session):
+    # 13.2 Step 3
+    result = get_element_attribute(session, "foo", "id")
+    assert_error(result, "no such element")
+
+
+def test_element_stale(session):
+    session.url = inline("<input id=foo>")
+    element = session.find.css("input", all=False)
+    session.refresh()
+    result = get_element_attribute(session, element.id, "id")
+    assert_error(result, "stale element reference")
+
+
+def test_normal(session):
+    # 13.2 Step 5
+    session.url = inline("<input type=checkbox>")
+    element = session.find.css("input", all=False)
+    result = get_element_attribute(session, element.id, "input")
+    assert_success(result, None)
+
+    # Check we are not returning the property which will have a different value
+    assert session.execute_script("return document.querySelector('input').checked") is False
+    element.click()
+    assert True == session.execute_script("return document.querySelector('input').checked")
+    result = get_element_attribute(session, element.id, "input")
+    assert_success(result, None)
+
+
+@pytest.mark.parametrize("tag,attrs", [
+    ("audio", ["autoplay", "controls", "loop", "muted"]),
+    ("button", ["autofocus", "disabled", "formnovalidate"]),
+    ("details", ["open"]),
+    ("dialog", ["open"]),
+    ("fieldset", ["disabled"]),
+    ("form", ["novalidate"]),
+    ("iframe", ["allowfullscreen"]),
+    ("img", ["ismap"]),
+    ("input", ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"]),
+    ("menuitem", ["checked", "default", "disabled"]),
+    ("object", ["typemustmatch"]),
+    ("ol", ["reversed"]),
+    ("optgroup", ["disabled"]),
+    ("option", ["disabled", "selected"]),
+    ("script", ["async", "defer"]),
+    ("select", ["autofocus", "disabled", "multiple", "required"]),
+    ("textarea", ["autofocus", "disabled", "readonly", "required"]),
+    ("track", ["default"]),
+    ("video", ["autoplay", "controls", "loop", "muted"])
+])
+def test_boolean_attribute(session, tag, attrs):
+    for attr in attrs:
+        session.url = inline("<{0} {1}>".format(tag, attr))
+        element = session.find.css(tag, all=False)
+        result = result = get_element_attribute(session, element.id, attr)
+        assert_success(result, "true")
+
+
+def test_global_boolean_attributes(session):
+    session.url = inline("<p hidden>foo")
+    element = session.find.css("p", all=False)
+    result = result = get_element_attribute(session, element.id, "hidden")
+
+    assert_success(result, "true")
+
+    session.url = inline("<p>foo")
+    element = session.find.css("p", all=False)
+    result = result = get_element_attribute(session, element.id, "hidden")
+    assert_success(result, None)
+
+    session.url = inline("<p itemscope>foo")
+    element = session.find.css("p", all=False)
+    result = result = get_element_attribute(session, element.id, "itemscope")
+
+    assert_success(result, "true")
+
+    session.url = inline("<p>foo")
+    element = session.find.css("p", all=False)
+    result = result = get_element_attribute(session, element.id, "itemscope")
+    assert_success(result, None)
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/get_element_property/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/get_element_property/__init__.py
diff --git a/webdriver/tests/get_element_property/get.py b/webdriver/tests/get_element_property/get.py
new file mode 100644
index 0000000..f8df282
--- /dev/null
+++ b/webdriver/tests/get_element_property/get.py
@@ -0,0 +1,55 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+_input = inline("<input id=i1>")
+
+
+def get_element_property(session, element_id, prop):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/property/{prop}".format(
+            session_id=session.session_id,
+            element_id=element_id,
+            prop=prop))
+
+
+def test_no_browsing_context(session, create_window):
+    session.window_handle = create_window()
+    session.close()
+
+    result = get_element_property(session, "foo", "id")
+    assert_error(result, "no such window")
+
+
+def test_element_not_found(session):
+    # 13.3 Step 3
+    result = get_element_property(session, "foo", "id")
+    assert_error(result, "no such element")
+
+
+def test_element_stale(session):
+    session.url = _input
+    element = session.find.css("input", all=False)
+    session.refresh()
+
+    result = get_element_property(session, element.id, "id")
+    assert_error(result, "stale element reference")
+
+
+def test_property_non_existent(session):
+    session.url = _input
+    element = session.find.css("input", all=False)
+
+    result = get_element_property(session, element.id, "foo")
+    assert_success(result, None)
+
+    assert session.execute_script("return arguments[0].foo", args=[element]) is None
+
+
+def test_element(session):
+    session.url = inline("<input type=checkbox>")
+    element = session.find.css("input", all=False)
+    element.click()
+    assert session.execute_script("return arguments[0].hasAttribute('checked')", args=(element,)) is False
+
+    result = get_element_property(session, element.id, "checked")
+    assert_success(result, True)
diff --git a/webdriver/tests/get_element_property/user_prompts.py b/webdriver/tests/get_element_property/user_prompts.py
new file mode 100644
index 0000000..8d46d02
--- /dev/null
+++ b/webdriver/tests/get_element_property/user_prompts.py
@@ -0,0 +1,87 @@
+from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def read_global(session, name):
+    return session.execute_script("return %s;" % name)
+
+
+def get_property(session, element_id, name):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/property/{name}".format(
+            session_id=session.session_id, element_id=element_id, name=name))
+
+
+def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
+    # 13.3 step 2
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_property(session, element.id, "id")
+    assert_success(result, "foo")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_property(session, element.id, "id")
+    assert_success(result, "foo")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_property(session, element.id, "id")
+    assert_success(result, "foo")
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_property(session, element.id, "id")
+    assert_success(result, "foo")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_property(session, element.id, "id")
+    assert_success(result, "foo")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_property(session, element.id, "id")
+    assert_success(result, "foo")
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session):
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_property(session, element.id, "id")
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_property(session, element.id, "id")
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_property(session, element.id, "id")
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/get_element_tag_name/__init__.py
similarity index 100%
copy from webdriver/tests/state/__init__.py
copy to webdriver/tests/get_element_tag_name/__init__.py
diff --git a/webdriver/tests/get_element_tag_name/get.py b/webdriver/tests/get_element_tag_name/get.py
new file mode 100644
index 0000000..2b1663f
--- /dev/null
+++ b/webdriver/tests/get_element_tag_name/get.py
@@ -0,0 +1,43 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def get_element_tag_name(session, element_id):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/name".format(
+            session_id=session.session_id,
+            element_id=element_id))
+
+
+def test_no_browsing_context(session, create_window):
+    # 13.6 step 1
+    session.window_handle = create_window()
+    session.close()
+
+    result = get_element_tag_name(session, "foo")
+    assert_error(result, "no such window")
+
+
+def test_element_not_found(session):
+    # 13.6 Step 3
+    result = get_element_tag_name(session, "foo")
+    assert_error(result, "no such element")
+
+
+def test_element_stale(session):
+    # 13.6 step 4
+    session.url = inline("<input id=foo>")
+    element = session.find.css("input", all=False)
+    session.refresh()
+
+    result = get_element_tag_name(session, element.id)
+    assert_error(result, "stale element reference")
+
+
+def test_get_element_tag_name(session):
+    # 13.6 step 6
+    session.url = inline("<input id=foo>")
+    element = session.find.css("input", all=False)
+
+    result = get_element_tag_name(session, element.id)
+    assert_success(result, "input")
diff --git a/webdriver/tests/get_element_tag_name/user_prompts.py b/webdriver/tests/get_element_tag_name/user_prompts.py
new file mode 100644
index 0000000..71828c6
--- /dev/null
+++ b/webdriver/tests/get_element_tag_name/user_prompts.py
@@ -0,0 +1,85 @@
+from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def read_global(session, name):
+    return session.execute_script("return %s;" % name)
+
+
+def get_tag_name(session, element_id):
+    return session.transport.send("GET", "session/{session_id}/element/{element_id}/name".format(
+        session_id=session.session_id, element_id=element_id))
+
+
+def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_tag_name(session, element.id)
+    assert_success(result, "input")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_tag_name(session, element.id)
+    assert_success(result, "input")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_tag_name(session, element.id)
+    assert_success(result, "input")
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_tag_name(session, element.id)
+    assert_success(result, "input")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_tag_name(session, element.id)
+    assert_success(result, "input")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_tag_name(session, element.id)
+    assert_success(result, "input")
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session):
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_tag_name(session, element.id)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_tag_name(session, element.id)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_tag_name(session, element.id)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/get_element_text/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/get_element_text/__init__.py
diff --git a/webdriver/tests/get_element_text/get.py b/webdriver/tests/get_element_text/get.py
new file mode 100644
index 0000000..4d1eb18
--- /dev/null
+++ b/webdriver/tests/get_element_text/get.py
@@ -0,0 +1,33 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+# For failing tests, the Get Element Text end-point is used
+# directly. In all other cases, the Element.text() function is used.
+
+
+def get_element_text(session, element_id):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/text".format(
+            session_id=session.session_id,
+            element_id=element_id))
+
+
+def test_getting_text_of_a_non_existant_element_is_an_error(session):
+    session.url = inline("""<body>Hello world</body>""")
+
+    result = get_element_text(session, "foo")
+    assert_error(result, "no such element")
+
+
+def test_read_element_text(session):
+    session.url = inline("""
+        <body>
+          Noise before <span id='id'>This has an ID</span>. Noise after
+        </body>""")
+
+    element = session.find.css("#id", all=False)
+
+    result = get_element_text(session, element.id)
+    assert_success(result, "This has an ID")
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/get_named_cookie/__init__.py
similarity index 100%
copy from webdriver/tests/state/__init__.py
copy to webdriver/tests/get_named_cookie/__init__.py
diff --git a/webdriver/tests/get_named_cookie/get.py b/webdriver/tests/get_named_cookie/get.py
new file mode 100644
index 0000000..cbf7195
--- /dev/null
+++ b/webdriver/tests/get_named_cookie/get.py
@@ -0,0 +1,103 @@
+from datetime import datetime, timedelta
+
+from tests.support.asserts import assert_success
+from tests.support.fixtures import clear_all_cookies
+from tests.support.inline import inline
+
+
+def get_named_cookie(session, name):
+    return session.transport.send(
+        "GET", "session/{session_id}/cookie/{name}".format(
+            session_id=session.session_id,
+            name=name))
+
+
+def test_get_named_session_cookie(session, url):
+    session.url = url("/common/blank.html")
+    clear_all_cookies(session)
+    session.execute_script("document.cookie = 'foo=bar'")
+
+    result = get_named_cookie(session, "foo")
+    cookie = assert_success(result)
+    assert isinstance(cookie, dict)
+
+    # table for cookie conversion
+    # https://w3c.github.io/webdriver/webdriver-spec.html#dfn-table-for-cookie-conversion
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+    assert "path" in cookie
+    assert isinstance(cookie["path"], basestring)
+    assert "domain" in cookie
+    assert isinstance(cookie["domain"], basestring)
+    assert "secure" in cookie
+    assert isinstance(cookie["secure"], bool)
+    assert "httpOnly" in cookie
+    assert isinstance(cookie["httpOnly"], bool)
+    if "expiry" in cookie:
+        assert cookie.get("expiry") is None
+
+    assert cookie["name"] == "foo"
+    assert cookie["value"] == "bar"
+
+
+def test_get_named_cookie(session, url):
+    session.url = url("/common/blank.html")
+    clear_all_cookies(session)
+
+    # same formatting as Date.toUTCString() in javascript
+    utc_string_format = "%a, %d %b %Y %H:%M:%S"
+    a_year_from_now = (datetime.utcnow() + timedelta(days=365)).strftime(utc_string_format)
+    session.execute_script("document.cookie = 'foo=bar;expires=%s'" % a_year_from_now)
+
+    result = get_named_cookie(session, "foo")
+    cookie = assert_success(result)
+    assert isinstance(cookie, dict)
+
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+    assert "expiry" in cookie
+    assert isinstance(cookie["expiry"], (int, long))
+
+    assert cookie["name"] == "foo"
+    assert cookie["value"] == "bar"
+    # convert from seconds since epoch
+    assert datetime.utcfromtimestamp(
+        cookie["expiry"]).strftime(utc_string_format) == a_year_from_now
+
+
+def test_duplicated_cookie(session, url, server_config):
+    new_cookie = {
+        "name": "hello",
+        "value": "world",
+        "domain": server_config["browser_host"],
+        "path": "/",
+        "http_only": False,
+        "secure": False
+    }
+
+    session.url = url("/common/blank.html")
+    clear_all_cookies(session)
+
+    session.set_cookie(**new_cookie)
+    session.url = inline("""
+      <script>
+        document.cookie = '{name}=newworld; domain={domain}; path=/';
+      </script>""".format(
+        name=new_cookie["name"],
+        domain=server_config["browser_host"]))
+
+    result = get_named_cookie(session, new_cookie["name"])
+    cookie = assert_success(result)
+    assert isinstance(cookie, dict)
+
+    assert "name" in cookie
+    assert isinstance(cookie["name"], basestring)
+    assert "value" in cookie
+    assert isinstance(cookie["value"], basestring)
+
+    assert cookie["name"] == new_cookie["name"]
+    assert cookie["value"] == "newworld"
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/get_timeouts/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/get_timeouts/__init__.py
diff --git a/webdriver/tests/get_timeouts/get.py b/webdriver/tests/get_timeouts/get.py
new file mode 100644
index 0000000..17f8dfd
--- /dev/null
+++ b/webdriver/tests/get_timeouts/get.py
@@ -0,0 +1,44 @@
+from tests.support.asserts import assert_success
+
+
+def get_timeouts(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/timeouts".format(**vars(session)))
+
+
+def test_get_timeouts(session):
+    # 8.4 step 1
+    response = get_timeouts(session)
+
+    assert_success(response)
+    assert "value" in response.body
+    assert isinstance(response.body["value"], dict)
+
+    value = response.body["value"]
+    assert "script" in value
+    assert "implicit" in value
+    assert "pageLoad" in value
+
+    assert isinstance(value["script"], int)
+    assert isinstance(value["implicit"], int)
+    assert isinstance(value["pageLoad"], int)
+
+
+def test_get_default_timeouts(session):
+    response = get_timeouts(session)
+
+    assert_success(response)
+    assert response.body["value"]["script"] == 30000
+    assert response.body["value"]["implicit"] == 0
+    assert response.body["value"]["pageLoad"] == 300000
+
+
+def test_get_new_timeouts(session):
+    session.timeouts.script = 60
+    session.timeouts.implicit = 1
+    session.timeouts.page_load = 200
+    response = get_timeouts(session)
+    assert_success(response)
+    assert response.body["value"]["script"] == 60000
+    assert response.body["value"]["implicit"] == 1000
+    assert response.body["value"]["pageLoad"] == 200000
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/get_title/__init__.py
similarity index 100%
copy from webdriver/tests/state/__init__.py
copy to webdriver/tests/get_title/__init__.py
diff --git a/webdriver/tests/get_title/get.py b/webdriver/tests/get_title/get.py
new file mode 100644
index 0000000..3b1673c
--- /dev/null
+++ b/webdriver/tests/get_title/get.py
@@ -0,0 +1,69 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+from tests.support.wait import wait
+
+
+def read_global(session, name):
+    return session.execute_script("return %s;" % name)
+
+
+def get_title(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/title".format(**vars(session)))
+
+
+def test_no_browsing_context(session, create_window):
+    new_window = create_window()
+    session.window_handle = new_window
+    session.close()
+
+    result = get_title(session)
+    assert_error(result, "no such window")
+
+
+def test_title_from_top_context(session):
+    session.url = inline("<title>Foobar</title><h2>Hello</h2>")
+
+    result = get_title(session)
+    assert_success(result, read_global(session, "document.title"))
+
+
+def test_title_with_duplicate_element(session):
+    session.url = inline("<title>First</title><title>Second</title>")
+
+    result = get_title(session)
+    assert_success(result, read_global(session, "document.title"))
+
+
+def test_title_without_element(session):
+    session.url = inline("<h2>Hello</h2>")
+
+    result = get_title(session)
+    assert_success(result, read_global(session, "document.title"))
+
+
+def test_title_after_modification(session):
+    session.url = inline("<title>Initial</title><h2>Hello</h2>")
+    session.execute_script("document.title = 'Updated'")
+
+    wait(session,
+         lambda s: assert_success(get_title(s)) == read_global(session, "document.title"),
+         "Document title doesn't match '{}'".format(read_global(session, "document.title")))
+
+
+def test_title_strip_and_collapse(session):
+    document = "<title>   a b\tc\nd\t \n e\t\n </title><h2>Hello</h2>"
+    session.url = inline(document)
+
+    result = get_title(session)
+    assert_success(result, read_global(session, "document.title"))
+
+
+def test_title_from_frame(session, create_frame):
+    session.url = inline("<title>Parent</title>parent")
+
+    session.switch_frame(create_frame())
+    session.switch_frame(create_frame())
+
+    result = get_title(session)
+    assert_success(result, "Parent")
diff --git a/webdriver/tests/get_title/user_prompts.py b/webdriver/tests/get_title/user_prompts.py
new file mode 100644
index 0000000..39afeb5
--- /dev/null
+++ b/webdriver/tests/get_title/user_prompts.py
@@ -0,0 +1,118 @@
+from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def read_global(session, name):
+    return session.execute_script("return %s;" % name)
+
+
+def get_title(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/title".format(**vars(session)))
+
+
+def test_title_handle_prompt_dismiss(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
+    session.url = inline("<title>WD doc title</title>")
+
+    expected_title = read_global(session, "document.title")
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_title(session)
+    assert_success(result, expected_title)
+    assert_dialog_handled(session, "dismiss #1")
+    assert read_global(session, "dismiss1") is None
+
+    expected_title = read_global(session, "document.title")
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_title(session)
+    assert_success(result, expected_title)
+    assert_dialog_handled(session, "dismiss #2")
+    assert read_global(session, "dismiss2") is False
+
+    expected_title = read_global(session, "document.title")
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_title(session)
+    assert_success(result, expected_title)
+    assert_dialog_handled(session, "dismiss #3")
+    assert read_global(session, "dismiss3") is None
+
+
+def test_title_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {
+        "alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog(session)("alert", text="accept #1", result_var="accept1")
+
+    expected_title = read_global(session, "document.title")
+
+    result = get_title(session)
+    assert_success(result, expected_title)
+    assert_dialog_handled(session, "accept #1")
+    assert read_global(session, "accept1") is None
+
+    expected_title = read_global(session, "document.title")
+    create_dialog(session)("confirm", text="accept #2", result_var="accept2")
+
+    result = get_title(session)
+    assert_success(result, expected_title)
+    assert_dialog_handled(session, "accept #2")
+    assert read_global(session, "accept2") is True
+
+    expected_title = read_global(session, "document.title")
+    create_dialog(session)("prompt", text="accept #3", result_var="accept3")
+
+    result = get_title(session)
+    assert_success(result, expected_title)
+    assert_dialog_handled(session, "accept #3")
+    assert read_global(session, "accept3") == "" or read_global(session, "accept3") == "undefined"
+
+
+def test_title_handle_prompt_missing_value(session, create_dialog):
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = get_title(session)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+    assert read_global(session, "dismiss1") is None
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = get_title(session)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+    assert read_global(session, "dismiss2") is False
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = get_title(session)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
+    assert read_global(session, "dismiss3") is None
+
+
+# The behavior of the `window.print` function is platform-dependent and may not
+# trigger the creation of a dialog at all. Therefore, this test should only be
+# run in contexts that support the dialog (a condition that may not be
+# determined automatically).
+# def test_title_with_non_simple_dialog(session):
+#    document = "<title>With non-simple dialog</title><h2>Hello</h2>"
+#    spawn = """
+#        var done = arguments[0];
+#        setTimeout(function() {
+#            done();
+#        }, 0);
+#        setTimeout(function() {
+#            window['print']();
+#        }, 0);
+#    """
+#    session.url = inline(document)
+#    session.execute_async_script(spawn)
+#
+#    result = get_title(session)
+#    assert_error(result, "unexpected alert open")
diff --git a/webdriver/tests/get_window_rect.py b/webdriver/tests/get_window_rect.py
deleted file mode 100644
index b25ca28..0000000
--- a/webdriver/tests/get_window_rect.py
+++ /dev/null
@@ -1,161 +0,0 @@
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-
-alert_doc = inline("<script>window.alert()</script>")
-
-
-def get_window_rect(session):
-    return session.transport.send("GET", "session/%s/window/rect" % session.session_id)
-
-
-# 10.7.1 Get Window Rect
-
-
-def test_no_browsing_context(session, create_window):
-    """
-    1. If the current top-level browsing context is no longer open,
-    return error with error code no such window.
-
-    """
-    session.window_handle = create_window()
-    session.close()
-    response = get_window_rect(session)
-    assert_error(response, "no such window")
-
-
-def test_handle_prompt_dismiss_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_accept_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_ignore():
-    """TODO"""
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - accept state
-           Accept the current user prompt.
-
-    """
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<title>WD doc title</title>")
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-    response = get_window_rect(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-    response = get_window_rect(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-    response = get_window_rect(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session, create_dialog):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - missing value default state
-           1. Dismiss the current user prompt.
-           2. Return error with error code unexpected alert open.
-
-    """
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    response = get_window_rect(session)
-
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    response = get_window_rect(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    response = get_window_rect(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_payload(session):
-    """
-    3. Return success with the JSON serialization of the current top-level
-    browsing context's window rect.
-
-    [...]
-
-    A top-level browsing context's window rect is defined as a
-    dictionary of the screenX, screenY, width and height attributes of
-    the WindowProxy. Its JSON representation is the following:
-
-    "x"
-        WindowProxy's screenX attribute.
-
-    "y"
-        WindowProxy's screenY attribute.
-
-    "width"
-        Width of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    "height"
-        Height of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    """
-    response = get_window_rect(session)
-
-    assert response.status == 200
-    assert isinstance(response.body["value"], dict)
-    value = response.body["value"]
-    assert "width" in value
-    assert "height" in value
-    assert "x" in value
-    assert "y" in value
-    assert isinstance(value["width"], int)
-    assert isinstance(value["height"], int)
-    assert isinstance(value["x"], int)
-    assert isinstance(value["y"], int)
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/get_window_rect/__init__.py
similarity index 100%
copy from webdriver/tests/state/text/__init__.py
copy to webdriver/tests/get_window_rect/__init__.py
diff --git a/webdriver/tests/get_window_rect/get.py b/webdriver/tests/get_window_rect/get.py
new file mode 100644
index 0000000..ae493b7
--- /dev/null
+++ b/webdriver/tests/get_window_rect/get.py
@@ -0,0 +1,64 @@
+from tests.support.asserts import assert_error
+from tests.support.inline import inline
+
+
+alert_doc = inline("<script>window.alert()</script>")
+
+
+def get_window_rect(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/window/rect".format(**vars(session)))
+
+
+def test_no_browsing_context(session, create_window):
+    """
+    1. If the current top-level browsing context is no longer open,
+    return error with error code no such window.
+
+    """
+    session.window_handle = create_window()
+    session.close()
+    response = get_window_rect(session)
+    assert_error(response, "no such window")
+
+
+def test_payload(session):
+    """
+    3. Return success with the JSON serialization of the current top-level
+    browsing context's window rect.
+
+    [...]
+
+    A top-level browsing context's window rect is defined as a
+    dictionary of the screenX, screenY, width and height attributes of
+    the WindowProxy. Its JSON representation is the following:
+
+    "x"
+        WindowProxy's screenX attribute.
+
+    "y"
+        WindowProxy's screenY attribute.
+
+    "width"
+        Width of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    "height"
+        Height of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    """
+    response = get_window_rect(session)
+
+    assert response.status == 200
+    assert isinstance(response.body["value"], dict)
+    value = response.body["value"]
+    expected = session.execute_script("""return {
+         x: window.screenX,
+         y: window.screenY,
+         width: window.outerWidth,
+         height: window.outerHeight
+    }""")
+    assert expected == value
diff --git a/webdriver/tests/get_window_rect/user_prompts.py b/webdriver/tests/get_window_rect/user_prompts.py
new file mode 100644
index 0000000..f2e8ddd
--- /dev/null
+++ b/webdriver/tests/get_window_rect/user_prompts.py
@@ -0,0 +1,65 @@
+from tests.support.asserts import assert_error, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+alert_doc = inline("<script>window.alert()</script>")
+
+
+def get_window_rect(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/window/rect".format(**vars(session)))
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<title>WD doc title</title>")
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+    response = get_window_rect(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+    response = get_window_rect(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+    response = get_window_rect(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    response = get_window_rect(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    response = get_window_rect(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    response = get_window_rect(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/interaction/element_clear.py b/webdriver/tests/interaction/element_clear.py
deleted file mode 100644
index 0712290..0000000
--- a/webdriver/tests/interaction/element_clear.py
+++ /dev/null
@@ -1,185 +0,0 @@
-import pytest
-from tests.support.asserts import assert_error, assert_success
-from tests.support.inline import inline
-
-
-def clear(session, element):
-    return session.transport.send("POST", "session/{session_id}/element/{element_id}/clear"
-                                  .format(session_id=session.session_id,
-                                          element_id=element.id))
-
-
-# 14.2 Element Clear
-
-def test_no_browsing_context(session, create_window):
-    # 14.2 step 1
-    session.url = inline("<p>This is not an editable paragraph.")
-    element = session.find.css("p", all=False)
-
-    session.window_handle = create_window()
-    session.close()
-
-    response = clear(session, element)
-    assert_error(response, "no such window")
-
-
-def test_element_not_found(session):
-    # 14.2 Step 2
-    response = session.transport.send("POST", "session/{session_id}/element/{element_id}/clear"
-                                      .format(session_id=session.session_id,
-                                              element_id="box1"))
-
-    assert_error(response, "no such element")
-
-
-def test_element_not_editable(session):
-    # 14.2 Step 3
-    session.url = inline("<p>This is not an editable paragraph.")
-
-    element = session.find.css("p", all=False)
-    response = clear(session, element)
-    assert_error(response, "invalid element state")
-
-
-def test_button_element_not_resettable(session):
-    # 14.2 Step 3
-    session.url = inline("<input type=button value=Federer>")
-
-    element = session.find.css("input", all=False)
-    response = clear(session, element)
-    assert_error(response, "invalid element state")
-
-
-def test_disabled_element_not_resettable(session):
-    # 14.2 Step 3
-    session.url = inline("<input type=text value=Federer disabled>")
-
-    element = session.find.css("input", all=False)
-    response = clear(session, element)
-    assert_error(response, "invalid element state")
-
-
-def test_scroll_into_element_view(session):
-    # 14.2 Step 4
-    session.url = inline("<input type=text value=Federer><div style= \"height: 200vh; width: 5000vh\">")
-
-    # Scroll to the bottom right of the page
-    session.execute_script("window.scrollTo(document.body.scrollWidth, document.body.scrollHeight);")
-    element = session.find.css("input", all=False)
-    # Clear and scroll back to the top of the page
-    response = clear(session, element)
-    assert_success(response)
-
-    # Check if element cleared is scrolled into view
-    rect = session.execute_script("return document.getElementsByTagName(\"input\")[0].getBoundingClientRect()")
-
-    pageDict = {}
-
-    pageDict["innerHeight"] = session.execute_script("return window.innerHeight")
-    pageDict["innerWidth"] = session.execute_script("return window.innerWidth")
-    pageDict["pageXOffset"] = session.execute_script("return window.pageXOffset")
-    pageDict["pageYOffset"] = session.execute_script("return window.pageYOffset")
-
-    assert rect["top"] < (pageDict["innerHeight"] + pageDict["pageYOffset"]) and \
-           rect["left"] < (pageDict["innerWidth"] + pageDict["pageXOffset"]) and \
-           (rect["top"] + element.rect["height"]) > pageDict["pageYOffset"] and \
-           (rect["left"] + element.rect["width"]) > pageDict["pageXOffset"]
-
-
-# TODO
-# Any suggestions on implementation?
-# def test_session_implicit_wait_timeout(session):
-    # 14.2 Step 5
-
-# TODO
-# Any suggestions on implementation?
-# def test_element_not_interactable(session):
-#     # 14.2 Step 6
-#     assert_error(response, "element not interactable")
-
-
-def test_element_readonly(session):
-    # 14.2 Step 7
-    session.url = inline("<input type=text readonly value=Federer>")
-
-    element = session.find.css("input", all=False)
-    response = clear(session, element)
-    assert_error(response, "invalid element state")
-
-
-def test_element_disabled(session):
-    # 14.2 Step 7
-    session.url = inline("<input type=text disabled value=Federer>")
-
-    element = session.find.css("input", all=False)
-    response = clear(session, element)
-    assert_error(response, "invalid element state")
-
-
-def test_element_pointer_events_disabled(session):
-    # 14.2 Step 7
-    session.url = inline("<input type=text value=Federer style=\"pointer-events: none\">")
-
-    element = session.find.css("input", all=False)
-    response = clear(session, element)
-    assert_error(response, "invalid element state")
-
-
-@pytest.mark.parametrize("element", [["text", "<input id=text type=text value=\"Federer\"><input id=empty type=text value=\"\">"],
-                                    ["search", "<input id=search type=search value=\"Federer\"><input id=empty type=search value=\"\">"],
-                                    ["url", "<input id=url type=url value=\"www.hello.com\"><input id=empty type=url value=\"\">"],
-                                    ["tele", "<input id=tele type=telephone value=\"2061234567\"><input id=empty type=telephone value=\"\">"],
-                                    ["email", "<input id=email type=email value=\"hello@world.com\"><input id=empty type=email value=\"\">"],
-                                    ["password", "<input id=password type=password value=\"pass123\"><input id=empty type=password value=\"\">"],
-                                    ["date", "<input id=date type=date value=\"2017-12-25\"><input id=empty type=date value=\"\">"],
-                                    ["time", "<input id=time type=time value=\"11:11\"><input id=empty type=time value=\"\">"],
-                                    ["number", "<input id=number type=number value=\"19\"><input id=empty type=number value=\"\">"],
-                                    ["range", "<input id=range type=range min=\"0\" max=\"10\"><input id=empty type=range value=\"\">"],
-                                    ["color", "<input id=color type=color value=\"#ff0000\"><input id=empty type=color value=\"\">"],
-                                    ["file", "<input id=file type=file value=\"C:\\helloworld.txt\"><input id=empty type=file value=\"\">"],
-                                    ["textarea", "<textarea id=textarea>Hello World</textarea><textarea id=empty></textarea>"],
-                                    ["sel", "<select id=sel><option></option><option>a</option><option>b</option></select><select id=empty><option></option></select>"],
-                                    ["out", "<output id=out value=100></output><output id=empty></output>"],
-                                    ["para", "<p id=para contenteditable=true>This is an editable paragraph.</p><p id=empty contenteditable=true></p>"]])
-
-def test_clear_content_editable_resettable_element(session, element):
-    # 14.2 Step 8
-    url = element[1] + """<input id=focusCheck type=checkbox>
-                    <input id=blurCheck type=checkbox>
-                    <script>
-                    var id = %s
-                    document.getElementById("id").addEventListener("focus", checkFocus);
-                    document.getElementById("id").addEventListener("blur", checkBlur);
-                    document.getElementById("empty").addEventListener("focus", checkFocus);
-                    document.getElementById("empty").addEventListener("blur", checkBlur);
-
-                    function checkFocus() {
-                        document.getElementById("focusCheck").checked = true;
-                    }
-                    function checkBlur() {
-                        document.getElementById("blurCheck").checked = true;
-                    }
-                    </script>""" % element[0]
-    session.url = inline(url)
-    # Step 1
-    empty_element = session.find.css("#empty", all=False)
-    test_clear_element_helper(session, empty_element, False)
-    session.execute_script("document.getElementById(\"focusCheck\").checked = false;")
-    session.execute_script("document.getElementById(\"blurCheck\").checked = false;")
-    # Step 2 - 4
-    test_element = session.find.css("#" + element[0], all=False)
-    test_clear_element_helper(session, test_element, True)
-
-
-def test_clear_element_helper(session, element, value):
-    response = clear(session, element)
-    assert_success(response)
-    response = session.execute_script("return document.getElementById(\"focusCheck\").checked;")
-    assert response is value
-    response = session.execute_script("return document.getElementById(\"blurCheck\").checked;")
-    assert response is value
-    if element.name == "p":
-        response = session.execute_script("return document.getElementById(\"para\").innerHTML;")
-        assert response == ""
-    else:
-        assert element.property("value") == ""
diff --git a/webdriver/tests/interface.html b/webdriver/tests/interface.html
index 143a864..d54aaaf 100644
--- a/webdriver/tests/interface.html
+++ b/webdriver/tests/interface.html
@@ -1,15 +1,49 @@
 <!doctype html>
 <meta charset=utf-8>
-<title>WebDriver interface test</title>
+
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src=/resources/WebIDLParser.js></script>
 <script src=/resources/idlharness.js></script>
 
+<script type=text/plain class=untested>
+[Exposed=Window]
+interface Navigator {
+  // objects implementing this interface also implement the interfaces given below
+};
+</script>
+
+<script type=text/plain>
+Navigator includes NavigatorAutomationInformation;
+
+interface mixin NavigatorAutomationInformation {
+  readonly attribute boolean webdriver;
+};
+</script>
+
 <script>
-var t = new IdlArray();
-t.add_untested_idls("interface Navigator {};");
-t.add_idls("partial interface Navigator { readonly attribute boolean webdriver; };");
-t.add_objects({Navigator: ["navigator"]});
-t.test();
+"use strict";
+
+test(() => assert_idl_attribute(navigator, "webdriver"), "navigator.webdriver is present");
+
+// When test is run in automation navigator.webdriver is likely to
+// be true because WebDriver controls the browser instance.  To that
+// extent, this test is a bit special.  It should also be possible to
+// run the test manually, when WebDriver is not active, and so either
+// true/false outcome is OK.
+if (navigator.webdriver) {
+  test(() => assert_true(navigator.webdriver), "navigator.webdriver is true when webdriver-active is set");
+} else {
+  test(() => assert_false(navigator.webdriver), "navigator.webdriver is false when webdriver-active is not set");
+}
+
+var idls = new IdlArray();
+for (let node of [...document.scripts].filter(({type}) => type == "text/plain")) {
+  if (node.className == "untested") {
+    idls.add_untested_idls(node.textContent);
+  } else {
+    idls.add_idls(node.textContent);
+  }
+};
+idls.test();
 </script>
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/is_element_selected/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/is_element_selected/__init__.py
diff --git a/webdriver/tests/is_element_selected/selected.py b/webdriver/tests/is_element_selected/selected.py
new file mode 100644
index 0000000..16939b5
--- /dev/null
+++ b/webdriver/tests/is_element_selected/selected.py
@@ -0,0 +1,72 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+check_doc = inline("<input id=checked type=checkbox checked/><input id=notChecked type=checkbox/>")
+option_doc = inline("""<select>
+                        <option id=notSelected>r-</option>
+                        <option id=selected selected>r+</option>
+                       </select>
+                    """)
+
+
+def is_element_selected(session, element_id):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/selected".format(
+            session_id=session.session_id,
+            element_id=element_id))
+
+
+def test_no_browsing_context(session, create_window):
+    # 13.1 step 1
+    session.window_handle = create_window()
+    session.close()
+
+    result = is_element_selected(session, "foo")
+    assert_error(result, "no such window")
+
+
+def test_element_stale(session):
+    # 13.1 step 4
+    session.url = check_doc
+    element = session.find.css("#checked", all=False)
+    session.refresh()
+
+    result = is_element_selected(session, element.id)
+    assert_error(result, "stale element reference")
+
+
+def test_element_checked(session):
+    # 13.1 step 5
+    session.url = check_doc
+    element = session.find.css("#checked", all=False)
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, True)
+
+
+def test_checkbox_not_selected(session):
+    # 13.1 step 5
+    session.url = check_doc
+    element = session.find.css("#notChecked", all=False)
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
+
+
+def test_element_selected(session):
+    # 13.1 step 5
+    session.url = option_doc
+    element = session.find.css("#selected", all=False)
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, True)
+
+
+def test_element_not_selected(session):
+    # 13.1 step 5
+    session.url = option_doc
+    element = session.find.css("#notSelected", all=False)
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
diff --git a/webdriver/tests/is_element_selected/user_prompts.py b/webdriver/tests/is_element_selected/user_prompts.py
new file mode 100644
index 0000000..b1a181a
--- /dev/null
+++ b/webdriver/tests/is_element_selected/user_prompts.py
@@ -0,0 +1,84 @@
+from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
+from tests.support.inline import inline
+from tests.support.fixtures import create_dialog
+
+
+def is_element_selected(session, element_id):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/selected".format(
+            session_id=session.session_id,
+            element_id=element_id))
+
+
+def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
+    # 13.1 step 2
+    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    # 13.1 step 2
+    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = is_element_selected(session, element.id)
+    assert_success(result, False)
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session):
+    # 13.1 step 2
+    session.url = inline("<input id=foo>")
+    element = session.find.css("#foo", all=False)
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = is_element_selected(session, element.id)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = is_element_selected(session, element.id)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = is_element_selected(session, element.id)
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/maximize_window/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/maximize_window/__init__.py
diff --git a/webdriver/tests/maximize_window/maximize.py b/webdriver/tests/maximize_window/maximize.py
new file mode 100644
index 0000000..00d6bc9
--- /dev/null
+++ b/webdriver/tests/maximize_window/maximize.py
@@ -0,0 +1,185 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def maximize(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/window/maximize".format(**vars(session)))
+
+
+def is_fullscreen(session):
+    # At the time of writing, WebKit does not conform to the Fullscreen API specification.
+    # Remove the prefixed fallback when https://bugs.webkit.org/show_bug.cgi?id=158125 is fixed.
+    return session.execute_script("return !!(window.fullScreen || document.webkitIsFullScreen)")
+
+
+# 10.7.3 Maximize Window
+
+
+def test_no_browsing_context(session, create_window):
+    """
+    2. If the current top-level browsing context is no longer open,
+    return error with error code no such window.
+
+    """
+    session.window_handle = create_window()
+    session.close()
+    response = maximize(session)
+    assert_error(response, "no such window")
+
+
+def test_fully_exit_fullscreen(session):
+    """
+    4. Fully exit fullscreen.
+
+    [...]
+
+    To fully exit fullscreen a document document, run these steps:
+
+      1. If document's fullscreen element is null, terminate these steps.
+
+      2. Unfullscreen elements whose fullscreen flag is set, within
+      document's top layer, except for document's fullscreen element.
+
+      3. Exit fullscreen document.
+
+    """
+    session.window.fullscreen()
+    assert is_fullscreen(session) is True
+
+    response = maximize(session)
+    assert_success(response)
+    assert is_fullscreen(session) is False
+
+
+def test_restore_the_window(session):
+    """
+    5. Restore the window.
+
+    [...]
+
+    To restore the window, given an operating system level window with
+    an associated top-level browsing context, run implementation-specific
+    steps to restore or unhide the window to the visible screen.  Do not
+    return from this operation until the visibility state of the top-level
+    browsing context's active document has reached the visible state,
+    or until the operation times out.
+
+    """
+    session.window.minimize()
+    assert session.execute_script("return document.hidden") is True
+
+    response = maximize(session)
+    assert_success(response)
+
+
+def test_maximize(session):
+    """
+    6. Maximize the window of the current browsing context.
+
+    [...]
+
+    To maximize the window, given an operating system level window with an
+    associated top-level browsing context, run the implementation-specific
+    steps to transition the operating system level window into the
+    maximized window state.  If the window manager supports window
+    resizing but does not have a concept of window maximation, the window
+    dimensions must be increased to the maximum available size permitted
+    by the window manager for the current screen.  Return when the window
+    has completed the transition, or within an implementation-defined
+    timeout.
+
+    """
+    before_size = session.window.size
+
+    response = maximize(session)
+    assert_success(response)
+
+    assert before_size != session.window.size
+
+
+def test_payload(session):
+    """
+    7. Return success with the JSON serialization of the current top-level
+    browsing context's window rect.
+
+    [...]
+
+    A top-level browsing context's window rect is defined as a
+    dictionary of the screenX, screenY, width and height attributes of
+    the WindowProxy. Its JSON representation is the following:
+
+    "x"
+        WindowProxy's screenX attribute.
+
+    "y"
+        WindowProxy's screenY attribute.
+
+    "width"
+        Width of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    "height"
+        Height of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    """
+    before_size = session.window.size
+
+    response = maximize(session)
+
+    # step 5
+    assert response.status == 200
+    assert isinstance(response.body["value"], dict)
+
+    value = response.body["value"]
+    assert "width" in value
+    assert "height" in value
+    assert "x" in value
+    assert "y" in value
+    assert isinstance(value["width"], int)
+    assert isinstance(value["height"], int)
+    assert isinstance(value["x"], int)
+    assert isinstance(value["y"], int)
+
+    assert before_size != session.window.size
+
+
+def test_maximize_twice_is_idempotent(session):
+    first_response = maximize(session)
+    assert_success(first_response)
+    max_size = session.window.size
+
+    second_response = maximize(session)
+    assert_success(second_response)
+    assert session.window.size == max_size
+
+
+"""
+TODO(ato): Implicit session start does not use configuration passed on
+from wptrunner.  This causes an exception.
+
+See https://bugzil.la/1398459.
+
+def test_maximize_when_resized_to_max_size(session):
+    # Determine the largest available window size by first maximising
+    # the window and getting the window rect dimensions.
+    #
+    # Then resize the window to the maximum available size.
+    session.end()
+    available = session.window.maximize()
+    session.end()
+
+    session.window.size = available
+
+    # In certain window managers a window extending to the full available
+    # dimensions of the screen may not imply that the window is maximised,
+    # since this is often a special state.  If a remote end expects a DOM
+    # resize event, this may not fire if the window has already reached
+    # its expected dimensions.
+    before = session.window.size
+    session.window.maximize()
+    assert session.window.size == before
+"""
diff --git a/webdriver/tests/maximize_window/user_prompts.py b/webdriver/tests/maximize_window/user_prompts.py
new file mode 100644
index 0000000..41ef3be
--- /dev/null
+++ b/webdriver/tests/maximize_window/user_prompts.py
@@ -0,0 +1,62 @@
+from tests.support.asserts import assert_error, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def maximize(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/window/maximize".format(**vars(session)))
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<title>WD doc title</title>")
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+    response = maximize(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+    response = maximize(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+    response = maximize(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    response = maximize(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    response = maximize(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    response = maximize(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/minimize_window.py b/webdriver/tests/minimize_window.py
deleted file mode 100644
index 5c0ef72..0000000
--- a/webdriver/tests/minimize_window.py
+++ /dev/null
@@ -1,225 +0,0 @@
-from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-
-alert_doc = inline("<script>window.alert()</script>")
-
-
-def minimize(session):
-    return session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
-
-
-# 10.7.4 Minimize Window
-
-
-def test_no_browsing_context(session, create_window):
-    """
-    1. If the current top-level browsing context is no longer open,
-    return error with error code no such window.
-
-    """
-    session.window_handle = create_window()
-    session.close()
-    response = minimize(session)
-    assert_error(response, "no such window")
-
-
-def test_handle_prompt_dismiss_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_accept_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_ignore():
-    """TODO"""
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - accept state
-           Accept the current user prompt.
-
-    """
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<title>WD doc title</title>")
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-    response = minimize(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-    response = minimize(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-    response = minimize(session)
-    assert response.status == 200
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session, create_dialog):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - missing value default state
-           1. Dismiss the current user prompt.
-           2. Return error with error code unexpected alert open.
-
-    """
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    response = minimize(session)
-
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    response = minimize(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    response = minimize(session)
-    assert_error(response, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_fully_exit_fullscreen(session):
-    """
-    4. Fully exit fullscreen.
-
-    [...]
-
-    To fully exit fullscreen a document document, run these steps:
-
-      1. If document's fullscreen element is null, terminate these steps.
-
-      2. Unfullscreen elements whose fullscreen flag is set, within
-      document's top layer, except for document's fullscreen element.
-
-      3. Exit fullscreen document.
-
-    """
-    session.window.fullscreen()
-    assert session.execute_script("return window.fullScreen") is True
-
-    response = minimize(session)
-    assert_success(response)
-    assert session.execute_script("return window.fullScreen") is False
-    assert session.execute_script("return document.hidden") is True
-
-
-def test_minimize(session):
-    """
-    5. Iconify the window.
-
-    [...]
-
-    To iconify the window, given an operating system level window with an
-    associated top-level browsing context, run implementation-specific
-    steps to iconify, minimize, or hide the window from the visible
-    screen. Do not return from this operation until the visibility state
-    of the top-level browsing context's active document has reached the
-    hidden state, or until the operation times out.
-
-    """
-    assert not session.execute_script("return document.hidden")
-
-    response = minimize(session)
-    assert_success(response)
-
-    assert session.execute_script("return document.hidden")
-
-
-def test_payload(session):
-    """
-    6. Return success with the JSON serialization of the current top-level
-    browsing context's window rect.
-
-    [...]
-
-    A top-level browsing context's window rect is defined as a
-    dictionary of the screenX, screenY, width and height attributes of
-    the WindowProxy. Its JSON representation is the following:
-
-    "x"
-        WindowProxy's screenX attribute.
-
-    "y"
-        WindowProxy's screenY attribute.
-
-    "width"
-        Width of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    "height"
-        Height of the top-level browsing context's outer dimensions,
-        including any browser chrome and externally drawn window
-        decorations in CSS reference pixels.
-
-    """
-    assert not session.execute_script("return document.hidden")
-
-    response = minimize(session)
-
-    assert response.status == 200
-    assert isinstance(response.body["value"], dict)
-
-    value = response.body["value"]
-    assert "width" in value
-    assert "height" in value
-    assert "x" in value
-    assert "y" in value
-    assert isinstance(value["width"], int)
-    assert isinstance(value["height"], int)
-    assert isinstance(value["x"], int)
-    assert isinstance(value["y"], int)
-
-    assert session.execute_script("return document.hidden")
-
-
-def test_minimize_twice_is_idempotent(session):
-    assert not session.execute_script("return document.hidden")
-
-    first_response = minimize(session)
-    assert_success(first_response)
-    assert session.execute_script("return document.hidden")
-
-    second_response = minimize(session)
-    assert_success(second_response)
-    assert session.execute_script("return document.hidden")
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/minimize_window/__init__.py
similarity index 100%
copy from webdriver/tests/retrieval/__init__.py
copy to webdriver/tests/minimize_window/__init__.py
diff --git a/webdriver/tests/minimize_window/minimize.py b/webdriver/tests/minimize_window/minimize.py
new file mode 100644
index 0000000..b80b678
--- /dev/null
+++ b/webdriver/tests/minimize_window/minimize.py
@@ -0,0 +1,134 @@
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def minimize(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/window/minimize".format(**vars(session)))
+
+
+def is_fullscreen(session):
+    # At the time of writing, WebKit does not conform to the Fullscreen API specification.
+    # Remove the prefixed fallback when https://bugs.webkit.org/show_bug.cgi?id=158125 is fixed.
+    return session.execute_script("return !!(window.fullScreen || document.webkitIsFullScreen)")
+
+# 10.7.4 Minimize Window
+
+
+def test_no_browsing_context(session, create_window):
+    """
+    1. If the current top-level browsing context is no longer open,
+    return error with error code no such window.
+
+    """
+    session.window_handle = create_window()
+    session.close()
+    response = minimize(session)
+    assert_error(response, "no such window")
+
+
+def test_fully_exit_fullscreen(session):
+    """
+    4. Fully exit fullscreen.
+
+    [...]
+
+    To fully exit fullscreen a document document, run these steps:
+
+      1. If document's fullscreen element is null, terminate these steps.
+
+      2. Unfullscreen elements whose fullscreen flag is set, within
+      document's top layer, except for document's fullscreen element.
+
+      3. Exit fullscreen document.
+
+    """
+    session.window.fullscreen()
+    assert is_fullscreen(session) is True
+
+    response = minimize(session)
+    assert_success(response)
+    assert is_fullscreen(session) is False
+    assert session.execute_script("return document.hidden") is True
+
+
+def test_minimize(session):
+    """
+    5. Iconify the window.
+
+    [...]
+
+    To iconify the window, given an operating system level window with an
+    associated top-level browsing context, run implementation-specific
+    steps to iconify, minimize, or hide the window from the visible
+    screen. Do not return from this operation until the visibility state
+    of the top-level browsing context's active document has reached the
+    hidden state, or until the operation times out.
+
+    """
+    assert not session.execute_script("return document.hidden")
+
+    response = minimize(session)
+    assert_success(response)
+
+    assert session.execute_script("return document.hidden")
+
+
+def test_payload(session):
+    """
+    6. Return success with the JSON serialization of the current top-level
+    browsing context's window rect.
+
+    [...]
+
+    A top-level browsing context's window rect is defined as a
+    dictionary of the screenX, screenY, width and height attributes of
+    the WindowProxy. Its JSON representation is the following:
+
+    "x"
+        WindowProxy's screenX attribute.
+
+    "y"
+        WindowProxy's screenY attribute.
+
+    "width"
+        Width of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    "height"
+        Height of the top-level browsing context's outer dimensions,
+        including any browser chrome and externally drawn window
+        decorations in CSS reference pixels.
+
+    """
+    assert not session.execute_script("return document.hidden")
+
+    response = minimize(session)
+
+    assert response.status == 200
+    assert isinstance(response.body["value"], dict)
+
+    value = response.body["value"]
+    assert "width" in value
+    assert "height" in value
+    assert "x" in value
+    assert "y" in value
+    assert isinstance(value["width"], int)
+    assert isinstance(value["height"], int)
+    assert isinstance(value["x"], int)
+    assert isinstance(value["y"], int)
+
+    assert session.execute_script("return document.hidden")
+
+
+def test_minimize_twice_is_idempotent(session):
+    assert not session.execute_script("return document.hidden")
+
+    first_response = minimize(session)
+    assert_success(first_response)
+    assert session.execute_script("return document.hidden")
+
+    second_response = minimize(session)
+    assert_success(second_response)
+    assert session.execute_script("return document.hidden")
diff --git a/webdriver/tests/minimize_window/user_prompts.py b/webdriver/tests/minimize_window/user_prompts.py
new file mode 100644
index 0000000..406862b
--- /dev/null
+++ b/webdriver/tests/minimize_window/user_prompts.py
@@ -0,0 +1,62 @@
+from tests.support.asserts import assert_error, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def minimize(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/window/minimize".format(**vars(session)))
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    session.url = inline("<title>WD doc title</title>")
+
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+    response = minimize(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+    response = minimize(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+    response = minimize(session)
+    assert response.status == 200
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+    session.url = inline("<title>WD doc title</title>")
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    response = minimize(session)
+
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    response = minimize(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    response = minimize(session)
+    assert_error(response, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/navigation/current_url.py b/webdriver/tests/navigation/current_url.py
deleted file mode 100644
index 45e462f..0000000
--- a/webdriver/tests/navigation/current_url.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import json
-import pytest
-import types
-
-from tests.support.inline import inline
-from tests.support.asserts import assert_error, assert_success
-
-alert_doc = inline("<script>window.alert()</script>")
-frame_doc = inline("<p>frame")
-one_frame_doc = inline("<iframe src='%s'></iframe>" % frame_doc)
-two_frames_doc = inline("<iframe src='%s'></iframe>" % one_frame_doc)
-
-
-# TODO(ato): 7.1 Get
-
-
-def test_get_current_url_no_browsing_context(session, create_window):
-    # 7.2 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    result = session.transport.send("GET", "session/%s/url" % session.session_id)
-
-    assert_error(result, "no such window")
-
-
-def test_get_current_url_alert_prompt(session):
-    # 7.2 step 2
-    session.url = alert_doc
-
-    result = session.transport.send("GET", "session/%s/url" % session.session_id)
-
-    assert_error(result, "unexpected alert open")
-
-def test_get_current_url_matches_location(session):
-    # 7.2 step 3
-    url = session.execute_script("return window.location.href")
-    assert session.url == url
-
-def test_get_current_url_payload(session):
-    # 7.2 step 4-5
-    session.start()
-
-    result = session.transport.send("GET", "session/%s/url" % session.session_id)
-
-    assert result.status == 200
-    assert isinstance(result.body["value"], basestring)
-
-def test_get_current_url_special_pages(session):
-    session.url = "about:blank"
-
-    result = session.transport.send("GET", "session/%s/url" % session.session_id)
-
-    assert_success(result, "about:blank")
-
-# TODO(ato): This test requires modification to pass on Windows
-def test_get_current_url_file_protocol(session):
-    # tests that the browsing context remains the same
-    # when navigated privileged documents
-    session.url = "file:///"
-
-    result = session.transport.send("GET", "session/%s/url" % session.session_id)
-
-    assert_success(result, "file:///")
-
-# TODO(ato): Test for http:// and https:// protocols.
-# We need to expose a fixture for accessing
-# documents served by wptserve in order to test this.
-
-def test_set_malformed_url(session):
-    result = session.transport.send("POST",
-                                    "session/%s/url" % session.session_id,
-                                    {"url": "foo"})
-
-    assert_error(result, "invalid argument")
-
-def test_get_current_url_after_modified_location(session):
-    session.execute_script("window.location.href = 'about:blank#wd_test_modification'")
-
-    result = session.transport.send("GET", "session/%s/url" % session.session_id)
-
-    assert_success(result, "about:blank#wd_test_modification")
-
-def test_get_current_url_nested_browsing_context(session, create_frame):
-    session.url = "about:blank#wd_from_within_frame"
-    session.switch_frame(create_frame())
-
-    result = session.transport.send("GET", "session/%s/url" % session.session_id)
-
-    assert_success(result, "about:blank#wd_from_within_frame")
-
-def test_get_current_url_nested_browsing_contexts(session):
-    session.url = two_frames_doc
-    top_level_url = session.url
-
-    outer_frame = session.find.css("iframe", all=False)
-    session.switch_frame(outer_frame)
-
-    inner_frame = session.find.css("iframe", all=False)
-    session.switch_frame(inner_frame)
-
-    assert session.url == top_level_url
diff --git a/webdriver/tests/navigation/get_title.py b/webdriver/tests/navigation/get_title.py
deleted file mode 100644
index 3edee84..0000000
--- a/webdriver/tests/navigation/get_title.py
+++ /dev/null
@@ -1,282 +0,0 @@
-import pytest
-import time
-
-from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-def read_global(session, name):
-    return session.execute_script("return %s;" % name)
-
-# 1. If the current top-level browsing context is no longer open, return error
-#    with error code no such window.
-def test_title_from_closed_context(session, create_window):
-    new_window = create_window()
-    session.window_handle = new_window
-    session.close()
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_error(result, "no such window")
-
-# [...]
-# 2. Handle any user prompts and return its value if it is an error.
-# [...]
-# In order to handle any user prompts a remote end must take the following
-# steps:
-# 2. Run the substeps of the first matching user prompt handler:
-#
-#    [...]
-#    - dismiss state
-#      1. Dismiss the current user prompt.
-#    [...]
-#
-# 3. Return success.
-def test_title_handle_prompt_dismiss(new_session, add_browser_capabilites):
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.url = inline("<title>WD doc title</title>")
-
-    expected_title = read_global(session, "document.title")
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, expected_title)
-    assert_dialog_handled(session, "dismiss #1")
-    assert read_global(session, "dismiss1") == None
-
-    expected_title = read_global(session, "document.title")
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, expected_title)
-    assert_dialog_handled(session, "dismiss #2")
-    assert read_global(session, "dismiss2") == False
-
-    expected_title = read_global(session, "document.title")
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, expected_title)
-    assert_dialog_handled(session, "dismiss #3")
-    assert read_global(session, "dismiss3") == None
-
-# [...]
-# 2. Handle any user prompts and return its value if it is an error.
-# [...]
-# In order to handle any user prompts a remote end must take the following
-# steps:
-# 2. Run the substeps of the first matching user prompt handler:
-#
-#    [...]
-#    - accept state
-#      1. Accept the current user prompt.
-#    [...]
-#
-# 3. Return success.
-def test_title_handle_prompt_accept(new_session, add_browser_capabilites):
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog(session)("alert", text="accept #1", result_var="accept1")
-
-    expected_title = read_global(session, "document.title")
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, expected_title)
-    assert_dialog_handled(session, "accept #1")
-    assert read_global(session, "accept1") == None
-
-    expected_title = read_global(session, "document.title")
-    create_dialog(session)("confirm", text="accept #2", result_var="accept2")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, expected_title)
-    assert_dialog_handled(session, "accept #2")
-    assert read_global(session, "accept2") == True
-
-    expected_title = read_global(session, "document.title")
-    create_dialog(session)("prompt", text="accept #3", result_var="accept3")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, expected_title)
-    assert_dialog_handled(session, "accept #3")
-    assert read_global(session, "accept3") == "" or read_global(session, "accept3") == "undefined"
-
-# [...]
-# 2. Handle any user prompts and return its value if it is an error.
-# [...]
-# In order to handle any user prompts a remote end must take the following
-# steps:
-# 2. Run the substeps of the first matching user prompt handler:
-#
-#    [...]
-#    - missing value default state
-#    - not in the table of simple dialogs
-#      1. Dismiss the current user prompt.
-#      2. Return error with error code unexpected alert open.
-def test_title_handle_prompt_missing_value(session, create_dialog):
-    session.url = inline("<title>WD doc title</title>")
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-    assert read_global(session, "dismiss1") == None
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-    assert read_global(session, "dismiss2") == False
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-    assert read_global(session, "dismiss3") == None
-
-# The behavior of the `window.print` function is platform-dependent and may not
-# trigger the creation of a dialog at all. Therefore, this test should only be
-# run in contexts that support the dialog (a condition that may not be
-# determined automatically).
-#def test_title_with_non_simple_dialog(session):
-#    document = "<title>With non-simple dialog</title><h2>Hello</h2>"
-#    spawn = """
-#        var done = arguments[0];
-#        setTimeout(function() {
-#            done();
-#        }, 0);
-#        setTimeout(function() {
-#            window['print']();
-#        }, 0);
-#    """
-#    session.url = inline(document)
-#    session.execute_async_script(spawn)
-#
-#    result = session.transport.send("GET",
-#                                    "session/%s/title" % session.session_id)
-#    assert_error(result, "unexpected alert open")
-
-# [...]
-# 3. Let title be the initial value of the title IDL attribute of the current
-#    top-level browsing context's active document.
-# 4. Return success with data title.
-# [...]
-# The title attribute must, on getting, run the following algorithm:
-# [...]
-# 2. Otherwise, let value be the child text content of the title element [...]
-# [...]
-# 4. Return value.
-def test_title_from_top_context(session):
-    session.url = inline("<title>Foobar</title><h2>Hello</h2>")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-    assert_success(result, read_global(session, "document.title"))
-
-# [...]
-# 3. Let title be the initial value of the title IDL attribute of the current
-#    top-level browsing context's active document.
-# 4. Return success with data title.
-# [...]
-# The title attribute must, on getting, run the following algorithm:
-# [...]
-# 2. Otherwise, let value be the child text content of the title element [...]
-#
-#    The title element of a document is the first title element in the document
-#    (in tree order), if there is one, or null otherwise.
-#
-# [...]
-# 4. Return value.
-def test_title_with_duplicate_element(session):
-    session.url = inline("<title>First</title><title>Second</title>")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, read_global(session, "document.title"))
-
-# [...]
-# 3. Let title be the initial value of the title IDL attribute of the current
-#    top-level browsing context's active document.
-# 4. Return success with data title.
-# [...]
-# The title attribute must, on getting, run the following algorithm:
-# [...]
-# 2. Otherwise, let value be the child text content of the title element, or
-#    the empty string if the title element is null.
-# [...]
-# 4. Return value.
-def test_title_without_element(session):
-    session.url = inline("<h2>Hello</h2>")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, read_global(session, "document.title"))
-
-# [...]
-# 3. Let title be the initial value of the title IDL attribute of the current
-#    top-level browsing context's active document.
-# 4. Return success with data title.
-def test_title_after_modification(session):
-    session.url = inline("<title>Initial</title><h2>Hello</h2>")
-    session.execute_script("document.title = 'updated'")
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, read_global(session, "document.title"))
-
-# [...]
-# 3. Let title be the initial value of the title IDL attribute of the current
-#    top-level browsing context's active document.
-# 4. Return success with data title.
-# [...]
-# The title attribute must, on getting, run the following algorithm:
-# [...]
-# 2. Otherwise, let value be the child text content of the title element [...]
-# 3. Strip and collapse ASCII whitespace in value.
-# 4. Return value.
-def test_title_strip_and_collapse(session):
-    document = "<title>   a b\tc\nd\t \n e\t\n </title><h2>Hello</h2>"
-    session.url = inline(document)
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, read_global(session, "document.title"))
-
-# [...]
-# 3. Let title be the initial value of the title IDL attribute of the current
-#    top-level browsing context's active document.
-# 4. Return success with data title.
-def test_title_from_frame(session, create_frame):
-    session.url = inline("<title>Parent</title>parent")
-
-    session.switch_frame(create_frame())
-    session.switch_frame(create_frame())
-
-    result = session.transport.send("GET",
-                                    "session/%s/title" % session.session_id)
-
-    assert_success(result, "Parent")
diff --git a/webdriver/tests/retrieval/__init__.py b/webdriver/tests/new_session/__init__.py
similarity index 100%
rename from webdriver/tests/retrieval/__init__.py
rename to webdriver/tests/new_session/__init__.py
diff --git a/webdriver/tests/sessions/new_session/conftest.py b/webdriver/tests/new_session/conftest.py
similarity index 100%
rename from webdriver/tests/sessions/new_session/conftest.py
rename to webdriver/tests/new_session/conftest.py
diff --git a/webdriver/tests/sessions/new_session/create_alwaysMatch.py b/webdriver/tests/new_session/create_alwaysMatch.py
similarity index 100%
rename from webdriver/tests/sessions/new_session/create_alwaysMatch.py
rename to webdriver/tests/new_session/create_alwaysMatch.py
diff --git a/webdriver/tests/sessions/new_session/create_firstMatch.py b/webdriver/tests/new_session/create_firstMatch.py
similarity index 100%
rename from webdriver/tests/sessions/new_session/create_firstMatch.py
rename to webdriver/tests/new_session/create_firstMatch.py
diff --git a/webdriver/tests/sessions/new_session/default_values.py b/webdriver/tests/new_session/default_values.py
similarity index 100%
rename from webdriver/tests/sessions/new_session/default_values.py
rename to webdriver/tests/new_session/default_values.py
diff --git a/webdriver/tests/new_session/invalid_capabilities.py b/webdriver/tests/new_session/invalid_capabilities.py
new file mode 100644
index 0000000..52f2582
--- /dev/null
+++ b/webdriver/tests/new_session/invalid_capabilities.py
@@ -0,0 +1,98 @@
+#META: timeout=long
+
+import pytest
+from webdriver import error
+
+from conftest import product, flatten
+
+
+@pytest.mark.parametrize("value", [None, 1, "{}", []])
+def test_invalid_capabilites(new_session, value):
+    with pytest.raises(error.InvalidArgumentException):
+        new_session({"capabilities": value})
+
+
+@pytest.mark.parametrize("value", [None, 1, "{}", []])
+def test_invalid_always_match(new_session, add_browser_capabilites, value):
+    with pytest.raises(error.InvalidArgumentException):
+        new_session({"capabilities": {"alwaysMatch": value, "firstMatch": [add_browser_capabilites({})]}})
+
+
+@pytest.mark.parametrize("value", [None, 1, "[]", {}])
+def test_invalid_first_match(new_session, add_browser_capabilites, value):
+    with pytest.raises(error.InvalidArgumentException):
+        new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({}), "firstMatch": value}})
+
+
+invalid_data = [
+    ("acceptInsecureCerts", [1, [], {}, "false"]),
+    ("browserName", [1, [], {}, False]),
+    ("browserVersion", [1, [], {}, False]),
+    ("platformName", [1, [], {}, False]),
+    ("pageLoadStrategy", [1, [], {}, False, "invalid", "NONE", "Eager", "eagerblah", "interactive",
+                          " eager", "eager "]),
+    ("proxy", [1, [], "{}", {"proxyType": "SYSTEM"}, {"proxyType": "systemSomething"},
+               {"proxy type": "pac"}, {"proxy-Type": "system"}, {"proxy_type": "system"},
+               {"proxytype": "system"}, {"PROXYTYPE": "system"}, {"proxyType": None},
+               {"proxyType": 1}, {"proxyType": []}, {"proxyType": {"value": "system"}},
+               {" proxyType": "system"}, {"proxyType ": "system"}, {"proxyType ": " system"},
+               {"proxyType": "system "}]),
+    ("timeouts", [1, [], "{}", False, {"pageLOAD": 10}, {"page load": 10},
+                  {"page load": 10}, {"pageLoad": "10"}, {"pageLoad": {"value": 10}},
+                  {"invalid": 10}, {"pageLoad": -1}, {"pageLoad": 2**64},
+                  {"pageLoad": None}, {"pageLoad": 1.1}, {"pageLoad": 10, "invalid": 10},
+                  {" pageLoad": 10}, {"pageLoad ": 10}]),
+    ("unhandledPromptBehavior", [1, [], {}, False, "DISMISS", "dismissABC", "Accept",
+                                 " dismiss", "dismiss "])
+]
+
+@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
+                                  lambda key, value: {"firstMatch": [{key: value}]}])
+@pytest.mark.parametrize("key,value", flatten(product(*item) for item in invalid_data))
+def test_invalid_values(new_session, add_browser_capabilites, body, key, value):
+    capabilities = body(key, value)
+    if "alwaysMatch" in capabilities:
+        capabilities["alwaysMatch"] = add_browser_capabilites(capabilities["alwaysMatch"])
+    else:
+        capabilities["firstMatch"][0] = add_browser_capabilites(capabilities["firstMatch"][0])
+    with pytest.raises(error.InvalidArgumentException):
+        resp = new_session({"capabilities": capabilities})
+
+
+invalid_extensions = [
+    "firefox",
+    "firefox_binary",
+    "firefoxOptions",
+    "chromeOptions",
+    "automaticInspection",
+    "automaticProfiling",
+    "platform",
+    "version",
+    "browser",
+    "platformVersion",
+    "javascriptEnabled",
+    "nativeEvents",
+    "seleniumProtocol",
+    "profile",
+    "trustAllSSLCertificates",
+    "initialBrowserUrl",
+    "requireWindowFocus",
+    "logFile",
+    "logLevel",
+    "safari.options",
+    "ensureCleanSession",
+]
+
+
+@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
+                                  lambda key, value: {"firstMatch": [{key: value}]}])
+@pytest.mark.parametrize("key", invalid_extensions)
+def test_invalid_extensions(new_session, add_browser_capabilites, body, key):
+    capabilities = body(key, {})
+    if "alwaysMatch" in capabilities:
+        capabilities["alwaysMatch"] = add_browser_capabilites(capabilities["alwaysMatch"])
+    else:
+        capabilities["firstMatch"][0] = add_browser_capabilites(capabilities["firstMatch"][0])
+    with pytest.raises(error.InvalidArgumentException):
+        resp = new_session({"capabilities": capabilities})
+
diff --git a/webdriver/tests/sessions/new_session/merge.py b/webdriver/tests/new_session/merge.py
similarity index 100%
rename from webdriver/tests/sessions/new_session/merge.py
rename to webdriver/tests/new_session/merge.py
diff --git a/webdriver/tests/new_session/response.py b/webdriver/tests/new_session/response.py
new file mode 100644
index 0000000..c9a8c76
--- /dev/null
+++ b/webdriver/tests/new_session/response.py
@@ -0,0 +1,54 @@
+# META: timeout=long
+
+import uuid
+
+def test_resp_sessionid(new_session, add_browser_capabilites):
+    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
+    assert isinstance(resp["sessionId"], unicode)
+    uuid.UUID(hex=resp["sessionId"])
+
+
+def test_resp_capabilites(new_session, add_browser_capabilites):
+    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
+    assert isinstance(resp["sessionId"], unicode)
+    assert isinstance(resp["capabilities"], dict)
+    assert {"browserName",
+            "browserVersion",
+            "platformName",
+            "acceptInsecureCerts",
+            "setWindowRect",
+            "timeouts",
+            "proxy",
+            "pageLoadStrategy"}.issubset(
+                set(resp["capabilities"].keys()))
+
+
+def test_resp_data(new_session, add_browser_capabilites, platform_name):
+    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
+
+    assert isinstance(resp["capabilities"]["browserName"], unicode)
+    assert isinstance(resp["capabilities"]["browserVersion"], unicode)
+    if platform_name:
+        assert resp["capabilities"]["platformName"] == platform_name
+    else:
+        assert "platformName" in resp["capabilities"]
+    assert resp["capabilities"]["acceptInsecureCerts"] is False
+    assert isinstance(resp["capabilities"]["setWindowRect"], bool)
+    assert resp["capabilities"]["timeouts"]["implicit"] == 0
+    assert resp["capabilities"]["timeouts"]["pageLoad"] == 300000
+    assert resp["capabilities"]["timeouts"]["script"] == 30000
+    assert resp["capabilities"]["proxy"] == {}
+    assert resp["capabilities"]["pageLoadStrategy"] == "normal"
+
+
+def test_timeouts(new_session, add_browser_capabilites, platform_name):
+    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"timeouts": {"implicit": 1000}})}})
+    assert resp["capabilities"]["timeouts"] == {
+        "implicit": 1000,
+        "pageLoad": 300000,
+        "script": 30000
+    }
+
+def test_pageLoadStrategy(new_session, add_browser_capabilites, platform_name):
+    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"pageLoadStrategy": "eager"})}})
+    assert resp["capabilities"]["pageLoadStrategy"] == "eager"
diff --git a/webdriver/tests/sessions/new_session/support/__init__.py b/webdriver/tests/new_session/support/__init__.py
similarity index 100%
rename from webdriver/tests/sessions/new_session/support/__init__.py
rename to webdriver/tests/new_session/support/__init__.py
diff --git a/webdriver/tests/new_session/support/create.py b/webdriver/tests/new_session/support/create.py
new file mode 100644
index 0000000..85ae1cd
--- /dev/null
+++ b/webdriver/tests/new_session/support/create.py
@@ -0,0 +1,15 @@
+# Note that we can only test things here all implementations must support
+valid_data = [
+    ("acceptInsecureCerts", [False, None]),
+    ("browserName", [None]),
+    ("browserVersion", [None]),
+    ("platformName", [None]),
+    ("pageLoadStrategy", ["none", "eager", "normal", None]),
+    ("proxy", [None]),
+    ("timeouts", [{"script": 0, "pageLoad": 2.0, "implicit": 2**53 - 1},
+                  {"script": 50, "pageLoad": 25},
+                  {"script": 500},
+                  {}]),
+    ("unhandledPromptBehavior", ["dismiss", "accept", None]),
+    ("test:extension", [True, "abc", 123, [], {"key": "value"}, None]),
+]
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/page_source/__init__.py
similarity index 100%
copy from webdriver/tests/state/__init__.py
copy to webdriver/tests/page_source/__init__.py
diff --git a/webdriver/tests/page_source/source.py b/webdriver/tests/page_source/source.py
new file mode 100644
index 0000000..97e4e1e
--- /dev/null
+++ b/webdriver/tests/page_source/source.py
@@ -0,0 +1,16 @@
+from tests.support.asserts import assert_success
+from tests.support.inline import inline
+
+
+def get_page_source(session):
+    return session.transport.send(
+        "GET", "session/{session_id}/source".format(**vars(session)))
+
+
+def test_source_matches_outer_html(session):
+    session.url = inline("<html><head><title>Cheese</title><body>Peas")
+
+    expected = session.execute_script("return document.documentElement.outerHTML")
+
+    response = get_page_source(session)
+    assert_success(response, expected)
diff --git a/webdriver/tests/retrieval/find_element.py b/webdriver/tests/retrieval/find_element.py
deleted file mode 100644
index a0047eb..0000000
--- a/webdriver/tests/retrieval/find_element.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import pytest
-
-from tests.support.asserts import assert_error, assert_same_element, assert_success
-from tests.support.inline import inline
-
-
-def find_element(session, using, value):
-    return session.transport.send("POST",
-                                  "session/%s/element" % session.session_id,
-                                  {"using": using, "value": value})
-
-
-# 12.2 Find Element
-
-@pytest.mark.parametrize("using", ["a", True, None, 1, [], {}])
-def test_invalid_using_argument(session, using):
-    # Step 1 - 2
-    response = find_element(session, using, "value")
-    assert_error(response, "invalid argument")
-
-
-@pytest.mark.parametrize("value", [None, [], {}])
-def test_invalid_selector_argument(session, value):
-    # Step 3 - 4
-    response = find_element(session, "css selector", value)
-    assert_error(response, "invalid argument")
-
-
-def test_closed_context(session, create_window):
-    # Step 5
-    new_window = create_window()
-    session.window_handle = new_window
-    session.close()
-
-    response = find_element(session, "css selector", "foo")
-
-    assert_error(response, "no such window")
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//a")])
-def test_find_element(session, using, value):
-    # Step 8 - 9
-    session.url = inline("<a href=# id=linkText>full link text</a>")
-
-    response = find_element(session, using, value)
-    assert_success(response)
-
-
-@pytest.mark.parametrize("using,value", [("css selector", "#wontExist")])
-def test_no_element(session, using, value):
-    # Step 8 - 9
-    response = find_element(session, using, value)
-    assert_error(response, "no such element")
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//*[name()='a']")])
-def test_xhtml_namespace(session, using, value):
-    session.url = inline("""<a href="#" id="linkText">full link text</a>""", doctype="xhtml")
-    expected = session.execute_script("return document.links[0]")
-
-    response = find_element(session, using, value)
-    value = assert_success(response)
-    assert_same_element(session, value, expected)
diff --git a/webdriver/tests/retrieval/find_element_from_element.py b/webdriver/tests/retrieval/find_element_from_element.py
deleted file mode 100644
index 3bf1d86..0000000
--- a/webdriver/tests/retrieval/find_element_from_element.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import pytest
-
-from tests.support.asserts import assert_error, assert_same_element, assert_success
-from tests.support.inline import inline
-
-
-def find_element(session, element, using, value):
-    return session.transport.send("POST",
-                                  "session/%s/element/%s/element" % (session.session_id, element),
-                                  {"using": using, "value": value})
-
-
-@pytest.mark.parametrize("using", ["a", True, None, 1, [], {}])
-def test_invalid_using_argument(session, using):
-    # Step 1 - 2
-    response = find_element(session, "notReal", using, "value")
-    assert_error(response, "invalid argument")
-
-
-@pytest.mark.parametrize("value", [None, [], {}])
-def test_invalid_selector_argument(session, value):
-    # Step 3 - 4
-    response = find_element(session, "notReal", "css selector", value)
-    assert_error(response, "invalid argument")
-
-
-def test_closed_context(session, create_window):
-    # Step 5
-    new_window = create_window()
-    session.window_handle = new_window
-    session.close()
-
-    response = find_element(session, "notReal", "css selector", "foo")
-
-    assert_error(response, "no such window")
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//a")])
-def test_find_element(session, using, value):
-    # Step 8 - 9
-    session.url = inline("<div><a href=# id=linkText>full link text</a></div>")
-    element = session.find.css("div", all=False)
-    response = find_element(session, element.id, using, value)
-    assert_success(response)
-
-
-@pytest.mark.parametrize("using,value",[("css selector", "#wontExist")])
-def test_no_element(session, using, value):
-    # Step 8 - 9
-    session.url = inline("<div></div>")
-    element = session.find.css("div", all=False)
-    response = find_element(session, element.id, using, value)
-    assert_error(response, "no such element")
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//*[name()='a']")])
-def test_xhtml_namespace(session, using, value):
-    session.url = inline("""<p><a href="#" id="linkText">full link text</a></p>""", doctype="xhtml")
-    from_element = session.execute_script("""return document.querySelector("p")""")
-    expected = session.execute_script("return document.links[0]")
-
-    response = find_element(session, from_element.id, using, value)
-    value = assert_success(response)
-    assert_same_element(session, value, expected)
diff --git a/webdriver/tests/retrieval/find_element_from_elements.py b/webdriver/tests/retrieval/find_element_from_elements.py
deleted file mode 100644
index 933d69d..0000000
--- a/webdriver/tests/retrieval/find_element_from_elements.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import pytest
-
-from tests.support.asserts import assert_error, assert_same_element, assert_success
-from tests.support.inline import inline
-
-
-def find_elements(session, element, using, value):
-    return session.transport.send("POST",
-                                  "session/%s/element/%s/elements" % (session.session_id, element),
-                                  {"using": using, "value": value})
-
-@pytest.mark.parametrize("using", [("a"), (True), (None), (1), ([]), ({})])
-def test_invalid_using_argument(session, using):
-    # Step 1 - 2
-    response = find_elements(session, "notReal", using, "value")
-    assert_error(response, "invalid argument")
-
-
-@pytest.mark.parametrize("value", [None, [], {}])
-def test_invalid_selector_argument(session, value):
-    # Step 3 - 4
-    response = find_elements(session, "notReal", "css selector", value)
-    assert_error(response, "invalid argument")
-
-
-def test_closed_context(session, create_window):
-    # Step 5
-    new_window = create_window()
-    session.window_handle = new_window
-    session.close()
-
-    response = find_elements(session, "notReal", "css selector", "foo")
-
-    assert_error(response, "no such window")
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//a")])
-def test_find_elements(session, using, value):
-    # Step 8 - 9
-    session.url = inline("<div><a href=# id=linkText>full link text</a></div>")
-    element = session.find.css("div", all=False)
-    response = find_elements(session, element.id, using, value)
-    assert_success(response)
-
-
-@pytest.mark.parametrize("using,value", [("css selector", "#wontExist")])
-def test_no_element(session, using, value):
-    # Step 8 - 9
-    session.url = inline("<div></div>")
-    element = session.find.css("div", all=False)
-    response = find_elements(session, element.id, using, value)
-    assert response.body["value"] == []
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//*[name()='a']")])
-def test_xhtml_namespace(session, using, value):
-    session.url = inline("""<p><a href="#" id="linkText">full link text</a></p>""", doctype="xhtml")
-    from_element = session.execute_script("""return document.querySelector("p")""")
-    expected = session.execute_script("return document.links[0]")
-
-    response = find_elements(session, from_element.id, using, value)
-    value = assert_success(response)
-    assert isinstance(value, list)
-    assert len(value) == 1
-
-    found_element = value[0]
-    assert_same_element(session, found_element, expected)
diff --git a/webdriver/tests/retrieval/find_elements.py b/webdriver/tests/retrieval/find_elements.py
deleted file mode 100644
index 8bc2113..0000000
--- a/webdriver/tests/retrieval/find_elements.py
+++ /dev/null
@@ -1,79 +0,0 @@
-import pytest
-
-from tests.support.asserts import assert_error, assert_same_element, assert_success
-from tests.support.inline import inline
-
-
-def find_elements(session, using, value):
-    return session.transport.send("POST",
-                                  "session/%s/elements" % session.session_id,
-                                  {"using": using, "value": value})
-
-
-@pytest.mark.parametrize("using", ["a", True, None, 1, [], {}])
-def test_invalid_using_argument(session, using):
-    # Step 1 - 2
-    response = find_elements(session, using, "value")
-    assert_error(response, "invalid argument")
-
-
-@pytest.mark.parametrize("value", [None, [], {}])
-def test_invalid_selector_argument(session, value):
-    # Step 3 - 4
-    response = find_elements(session, "css selector", value)
-    assert_error(response, "invalid argument")
-
-
-def test_closed_context(session, create_window):
-    # Step 5
-    new_window = create_window()
-    session.window_handle = new_window
-    session.close()
-
-    response = session.transport.send("POST",
-                                      "session/%s/elements" % session.session_id,
-                                      {"using": "css selector", "value": "foo"})
-
-    assert_error(response, "no such window")
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//a")])
-def test_find_elements(session, using, value):
-    # Step 8 - 9
-    session.url = inline("<a href=# id=linkText>full link text</a>")
-
-    response = find_elements(session, using, value)
-    assert_success(response)
-    assert len(response.body["value"]) == 1
-
-
-@pytest.mark.parametrize("using,value", [("css selector", "#wontExist")])
-def test_no_element(session, using, value):
-    # Step 8 - 9
-    response = find_elements(session, using, value)
-    assert_success(response)
-    assert response.body["value"] == []
-
-
-@pytest.mark.parametrize("using,value",
-                         [("css selector", "#linkText"),
-                          ("link text", "full link text"),
-                          ("partial link text", "link text"),
-                          ("tag name", "a"),
-                          ("xpath", "//*[name()='a']")])
-def test_xhtml_namespace(session, using, value):
-    session.url = inline("""<p><a href="#" id="linkText">full link text</a></p>""", doctype="xhtml")
-    expected = session.execute_script("return document.links[0]")
-
-    response = find_elements(session, using, value)
-    value = assert_success(response)
-    assert isinstance(value, list)
-    assert len(value) == 1
-
-    found_element = value[0]
-    assert_same_element(session, found_element, expected)
diff --git a/webdriver/tests/state/text/__init__.py b/webdriver/tests/send_alert_text/__init__.py
similarity index 100%
rename from webdriver/tests/state/text/__init__.py
rename to webdriver/tests/send_alert_text/__init__.py
diff --git a/webdriver/tests/send_alert_text/send.py b/webdriver/tests/send_alert_text/send.py
new file mode 100644
index 0000000..5b38b6f
--- /dev/null
+++ b/webdriver/tests/send_alert_text/send.py
@@ -0,0 +1,76 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def send_alert_text(session, body=None):
+    return session.transport.send(
+        "POST", "session/{session_id}/alert/text".format(**vars(session)),
+        body)
+
+
+# 18.4 Send Alert Text
+
+@pytest.mark.parametrize("text", [None, {}, [], 42, True])
+def test_invalid_input(session, text):
+    # 18.4 step 2
+    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Name');</script>")
+    response = send_alert_text(session, {"text": text})
+    assert_error(response, "invalid argument")
+
+
+def test_no_browsing_context(session, create_window):
+    # 18.4 step 3
+    session.window_handle = create_window()
+    session.close()
+    body = {"text": "Federer"}
+    response = send_alert_text(session, body)
+    assert_error(response, "no such window")
+
+
+def test_no_user_prompt(session):
+    # 18.4 step 4
+    body = {"text": "Federer"}
+    response = send_alert_text(session, body)
+    assert_error(response, "no such alert")
+
+
+def test_alert_element_not_interactable(session):
+    # 18.4 step 5
+    session.url = inline("<script>window.alert('Hello');</script>")
+    body = {"text": "Federer"}
+    response = send_alert_text(session, body)
+    assert_error(response, "element not interactable")
+
+
+def test_confirm_element_not_interactable(session):
+    # 18.4 step 5
+    session.url = inline("<script>window.confirm('Hello');</script>")
+    body = {"text": "Federer"}
+    response = send_alert_text(session, body)
+    assert_error(response, "element not interactable")
+
+
+def test_send_alert_text(session):
+    # 18.4 step 6
+    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Name');</script>")
+    body = {"text": "Federer"}
+    send_response = send_alert_text(session, body)
+    assert_success(send_response)
+    accept_response = session.transport.send("POST", "session/{session_id}/alert/accept"
+                                             .format(session_id=session.session_id))
+    assert_success(accept_response)
+    assert session.execute_script("return window.result") == "Federer"
+
+
+def test_send_alert_text_with_whitespace(session):
+    # 18.4 step 6
+    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Name');</script>")
+    body = {"text": " Fed erer "}
+    send_response = send_alert_text(session, body)
+    assert_success(send_response)
+    accept_response = session.transport.send("POST", "session/{session_id}/alert/accept"
+                                             .format(session_id=session.session_id))
+    assert_success(accept_response)
+    assert session.execute_script("return window.result") == " Fed erer "
diff --git a/webdriver/tests/sessions/get_timeouts.py b/webdriver/tests/sessions/get_timeouts.py
deleted file mode 100644
index 96767e9..0000000
--- a/webdriver/tests/sessions/get_timeouts.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from tests.support.asserts import assert_success
-
-
-def get_timeouts(session):
-    return session.transport.send("GET", "session/{session_id}/timeouts"
-                                  .format(session_id=session.session_id))
-
-
-# 8.4 Get Timeouts
-
-def test_get_timeouts(session):
-    # 8.4 step 1
-    response = get_timeouts(session)
-
-    assert_success(response)
-    assert "value" in response.body
-    assert isinstance(response.body["value"], dict)
-
-    value = response.body["value"]
-    assert "script" in value
-    assert "implicit" in value
-    assert "pageLoad" in value
-
-    assert isinstance(value["script"], int)
-    assert isinstance(value["implicit"], int)
-    assert isinstance(value["pageLoad"], int)
-
-
-def test_get_default_timeouts(session):
-    response = get_timeouts(session)
-
-    assert_success(response)
-    assert response.body["value"]["script"] == 30000
-    assert response.body["value"]["implicit"] == 0
-    assert response.body["value"]["pageLoad"] == 300000
-
-
-def test_get_new_timeouts(session):
-    session.timeouts.script = 60
-    session.timeouts.implicit = 1
-    session.timeouts.page_load = 200
-    response = get_timeouts(session)
-    assert_success(response)
-    assert response.body["value"]["script"] == 60000
-    assert response.body["value"]["implicit"] == 1000
-    assert response.body["value"]["pageLoad"] == 200000
diff --git a/webdriver/tests/sessions/new_session/invalid_capabilities.py b/webdriver/tests/sessions/new_session/invalid_capabilities.py
deleted file mode 100644
index 88da4f1..0000000
--- a/webdriver/tests/sessions/new_session/invalid_capabilities.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#META: timeout=long
-
-import pytest
-from webdriver import error
-
-from conftest import product, flatten
-
-
-@pytest.mark.parametrize("value", [None, 1, "{}", []])
-def test_invalid_capabilites(new_session, value):
-    with pytest.raises(error.InvalidArgumentException):
-        new_session({"capabilities": value})
-
-
-@pytest.mark.parametrize("value", [None, 1, "{}", []])
-def test_invalid_always_match(new_session, add_browser_capabilites, value):
-    with pytest.raises(error.InvalidArgumentException):
-        new_session({"capabilities": {"alwaysMatch": value, "firstMatch": [add_browser_capabilites({})]}})
-
-
-@pytest.mark.parametrize("value", [None, 1, "[]", {}])
-def test_invalid_first_match(new_session, add_browser_capabilites, value):
-    with pytest.raises(error.InvalidArgumentException):
-        new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({}), "firstMatch": value}})
-
-
-invalid_data = [
-    ("acceptInsecureCerts", [1, [], {}, "false"]),
-    ("browserName", [1, [], {}, False]),
-    ("browserVersion", [1, [], {}, False]),
-    ("platformName", [1, [], {}, False]),
-    ("pageLoadStrategy", [1, [], {}, False, "invalid", "NONE", "Eager", "eagerblah", "interactive",
-                          " eager", "eager "]),
-    ("proxy", [1, [], "{}", {"proxyType": "SYSTEM"}, {"proxyType": "systemSomething"},
-               {"proxy type": "pac"}, {"proxy-Type": "system"}, {"proxy_type": "system"},
-               {"proxytype": "system"}, {"PROXYTYPE": "system"}, {"proxyType": None},
-               {"proxyType": 1}, {"proxyType": []}, {"proxyType": {"value": "system"}},
-               {" proxyType": "system"}, {"proxyType ": "system"}, {"proxyType ": " system"},
-               {"proxyType": "system "}]),
-    ("timeouts", [1, [], "{}", {}, False, {"pageLOAD": 10}, {"page load": 10},
-                  {"page load": 10}, {"pageLoad": "10"}, {"pageLoad": {"value": 10}},
-                  {"invalid": 10}, {"pageLoad": -1}, {"pageLoad": 2**64},
-                  {"pageLoad": None}, {"pageLoad": 1.1}, {"pageLoad": 10, "invalid": 10},
-                  {" pageLoad": 10}, {"pageLoad ": 10}]),
-    ("unhandledPromptBehavior", [1, [], {}, False, "DISMISS", "dismissABC", "Accept",
-                                 " dismiss", "dismiss "])
-]
-
-@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
-                                  lambda key, value: {"firstMatch": [{key: value}]}])
-@pytest.mark.parametrize("key,value", flatten(product(*item) for item in invalid_data))
-def test_invalid_values(new_session, add_browser_capabilites, body, key, value):
-    capabilities = body(key, value)
-    if "alwaysMatch" in capabilities:
-        capabilities["alwaysMatch"] = add_browser_capabilites(capabilities["alwaysMatch"])
-    else:
-        capabilities["firstMatch"][0] = add_browser_capabilites(capabilities["firstMatch"][0])
-    with pytest.raises(error.InvalidArgumentException):
-        resp = new_session({"capabilities": capabilities})
-
-
-invalid_extensions = [
-    "firefox",
-    "firefox_binary",
-    "firefoxOptions",
-    "chromeOptions",
-    "automaticInspection",
-    "automaticProfiling",
-    "platform",
-    "version",
-    "browser",
-    "platformVersion",
-    "javascriptEnabled",
-    "nativeEvents",
-    "seleniumProtocol",
-    "profile",
-    "trustAllSSLCertificates",
-    "initialBrowserUrl",
-    "requireWindowFocus",
-    "logFile",
-    "logLevel",
-    "safari.options",
-    "ensureCleanSession",
-]
-
-
-@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
-                                  lambda key, value: {"firstMatch": [{key: value}]}])
-@pytest.mark.parametrize("key", invalid_extensions)
-def test_invalid_extensions(new_session, add_browser_capabilites, body, key):
-    capabilities = body(key, {})
-    if "alwaysMatch" in capabilities:
-        capabilities["alwaysMatch"] = add_browser_capabilites(capabilities["alwaysMatch"])
-    else:
-        capabilities["firstMatch"][0] = add_browser_capabilites(capabilities["firstMatch"][0])
-    with pytest.raises(error.InvalidArgumentException):
-        resp = new_session({"capabilities": capabilities})
-
diff --git a/webdriver/tests/sessions/new_session/response.py b/webdriver/tests/sessions/new_session/response.py
deleted file mode 100644
index 0bec23f..0000000
--- a/webdriver/tests/sessions/new_session/response.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# META: timeout=long
-
-import uuid
-
-def test_resp_sessionid(new_session, add_browser_capabilites):
-    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
-    assert isinstance(resp["sessionId"], unicode)
-    uuid.UUID(hex=resp["sessionId"])
-
-
-def test_resp_capabilites(new_session, add_browser_capabilites):
-    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
-    assert isinstance(resp["sessionId"], unicode)
-    assert isinstance(resp["capabilities"], dict)
-    assert {"browserName",
-            "browserVersion",
-            "platformName",
-            "acceptInsecureCerts",
-            "setWindowRect",
-            "timeouts",
-            "proxy",
-            "pageLoadStrategy"}.issubset(
-                set(resp["capabilities"].keys()))
-
-
-def test_resp_data(new_session, add_browser_capabilites, platform_name):
-    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
-
-    assert isinstance(resp["capabilities"]["browserName"], unicode)
-    assert isinstance(resp["capabilities"]["browserVersion"], unicode)
-    if platform_name:
-        assert resp["capabilities"]["platformName"] == platform_name
-    else:
-        assert "platformName" in resp["capabilities"]
-    assert resp["capabilities"]["acceptInsecureCerts"] is False
-    assert isinstance(resp["capabilities"]["setWindowRect"], bool)
-    assert resp["capabilities"]["timeouts"]["implicit"] == 0
-    assert resp["capabilities"]["timeouts"]["pageLoad"] == 300000
-    assert resp["capabilities"]["timeouts"]["script"] == 30000
-    assert resp["capabilities"]["proxy"] == {}
-    assert resp["capabilities"]["pageLoadStrategy"] == "normal"
-
-
-def test_timeouts(new_session, add_browser_capabilites, platform_name):
-    resp, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"timeouts": {"implicit": 1000}})}})
-    assert resp["capabilities"]["timeouts"] == {
-        "implicit": 1000,
-        "pageLoad": 300000,
-        "script": 30000
-    }
-
-def test_pageLoadStrategy(new_session, add_browser_capabilites, platform_name):
-    resp, _ = new_session({"capabilities": add_browser_capabilites({"alwaysMatch": {"pageLoadStrategy": "eager"}})})
-    assert resp["capabilities"]["pageLoadStrategy"] == "eager"
diff --git a/webdriver/tests/sessions/new_session/support/create.py b/webdriver/tests/sessions/new_session/support/create.py
deleted file mode 100644
index 75ac3e8..0000000
--- a/webdriver/tests/sessions/new_session/support/create.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# Note that we can only test things here all implementations must support
-valid_data = [
-    ("acceptInsecureCerts", [False, None]),
-    ("browserName", [None]),
-    ("browserVersion", [None]),
-    ("platformName", [None]),
-    ("pageLoadStrategy", ["none", "eager", "normal", None]),
-    ("proxy", [None]),
-    ("unhandledPromptBehavior", ["dismiss", "accept", None]),
-    ("test:extension", [True, "abc", 123, [], {"key": "value"}, None]),
-]
diff --git a/webdriver/tests/set_window_rect.py b/webdriver/tests/set_window_rect.py
deleted file mode 100644
index f945f19..0000000
--- a/webdriver/tests/set_window_rect.py
+++ /dev/null
@@ -1,505 +0,0 @@
-# META: timeout=long
-
-import pytest
-
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-
-alert_doc = inline("<script>window.alert()</script>")
-
-
-def set_window_rect(session, rect):
-    return session.transport.send("POST", "session/%s/window/rect" % session.session_id, rect)
-
-
-# 10.7.2 Set Window Rect
-
-
-def test_current_top_level_browsing_context_no_longer_open(session, create_window):
-    """
-    1. If the current top-level browsing context is no longer open,
-    return error with error code no such window.
-
-    """
-    session.window_handle = create_window()
-    session.close()
-    response = set_window_rect(session, {})
-    assert_error(response, "no such window")
-
-
-def test_handle_prompt_dismiss():
-    """TODO"""
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - accept state
-           Accept the current user prompt.
-
-    """
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    original = session.window.rect
-
-    # step 2
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-    result = set_window_rect(session, {"x": original["x"],
-                                       "y": original["y"]})
-    assert result.status == 200
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-    result = set_window_rect(session, {"x": original["x"],
-                                       "y": original["y"]})
-    assert result.status == 200
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-    result = set_window_rect(session, {"x": original["x"],
-                                       "y": original["y"]})
-    assert_success(result)
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_dismiss_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_accept_and_notify():
-    """TODO"""
-
-
-def test_handle_prompt_ignore():
-    """TODO"""
-
-
-def test_handle_prompt_missing_value(session, create_dialog):
-    """
-    2. Handle any user prompts and return its value if it is an error.
-
-    [...]
-
-    In order to handle any user prompts a remote end must take the
-    following steps:
-
-      [...]
-
-      2. Perform the following substeps based on the current session's
-      user prompt handler:
-
-        [...]
-
-        - missing value default state
-           1. Dismiss the current user prompt.
-           2. Return error with error code unexpected alert open.
-
-    """
-
-    original = session.window.rect
-
-    # step 2
-    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = set_window_rect(session, {"x": original["x"],
-                                       "y": original["y"]})
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = set_window_rect(session, {"x": original["x"],
-                                       "y": original["y"]})
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = set_window_rect(session, {"x": original["x"],
-                                       "y": original["y"]})
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-@pytest.mark.parametrize("rect", [
-    {"width": "a"},
-    {"height": "b"},
-    {"width": "a", "height": "b"},
-    {"x": "a"},
-    {"y": "b"},
-    {"x": "a", "y": "b"},
-    {"width": "a", "height": "b", "x": "a", "y": "b"},
-
-    {"width": True},
-    {"height": False},
-    {"width": True, "height": False},
-    {"x": True},
-    {"y": False},
-    {"x": True, "y": False},
-    {"width": True, "height": False, "x": True, "y": False},
-
-    {"width": []},
-    {"height": []},
-    {"width": [], "height": []},
-    {"x": []},
-    {"y": []},
-    {"x": [], "y": []},
-    {"width": [], "height": [], "x": [], "y": []},
-
-    {"height": {}},
-    {"width": {}},
-    {"height": {}, "width": {}},
-    {"x": {}},
-    {"y": {}},
-    {"x": {}, "y": {}},
-    {"width": {}, "height": {}, "x": {}, "y": {}},
-])
-def test_invalid_types(session, rect):
-    """
-    8. If width or height is neither null nor a Number from 0 to 2^31 -
-    1, return error with error code invalid argument.
-
-    9. If x or y is neither null nor a Number from -(2^31) to 2^31 - 1,
-    return error with error code invalid argument.
-    """
-    response = set_window_rect(session, rect)
-    assert_error(response, "invalid argument")
-
-
-@pytest.mark.parametrize("rect", [
-    {"width": -1},
-    {"height": -2},
-    {"width": -1, "height": -2},
-])
-def test_out_of_bounds(session, rect):
-    """
-    8. If width or height is neither null nor a Number from 0 to 2^31 -
-    1, return error with error code invalid argument.
-
-    9. If x or y is neither null nor a Number from -(2^31) to 2^31 - 1,
-    return error with error code invalid argument.
-    """
-    response = set_window_rect(session, rect)
-    assert_error(response, "invalid argument")
-
-
-def test_width_height_floats(session):
-    """
-    8. If width or height is neither null nor a Number from 0 to 2^31 -
-    1, return error with error code invalid argument.
-    """
-
-    response = set_window_rect(session, {"width": 500.5, "height": 420})
-    value = assert_success(response)
-    assert value["width"] == 500
-    assert value["height"] == 420
-
-    response = set_window_rect(session, {"width": 500, "height": 450.5})
-    value = assert_success(response)
-    assert value["width"] == 500
-    assert value["height"] == 450
-
-
-def test_x_y_floats(session):
-    """
-    9. If x or y is neither null nor a Number from -(2^31) to 2^31 - 1,
-    return error with error code invalid argument.
-    """
-
-    response = set_window_rect(session, {"x": 0.5, "y": 420})
-    value = assert_success(response)
-    assert value["x"] == 0
-    assert value["y"] == 420
-
-    response = set_window_rect(session, {"x": 100, "y": 450.5})
-    value = assert_success(response)
-    assert value["x"] == 100
-    assert value["y"] == 450
-
-
-@pytest.mark.parametrize("rect", [
-    {},
-
-    {"width": None},
-    {"height": None},
-    {"width": None, "height": None},
-
-    {"x": None},
-    {"y": None},
-    {"x": None, "y": None},
-
-    {"width": None, "x": None},
-    {"width": None, "y": None},
-    {"height": None, "x": None},
-    {"height": None, "Y": None},
-
-    {"width": None, "height": None, "x": None, "y": None},
-
-    {"width": 200},
-    {"height": 200},
-    {"x": 200},
-    {"y": 200},
-    {"width": 200, "x": 200},
-    {"height": 200, "x": 200},
-    {"width": 200, "y": 200},
-    {"height": 200, "y": 200},
-])
-def test_no_change(session, rect):
-    """
-    13. If width and height are not null:
-
-    [...]
-
-    14. If x and y are not null:
-
-    [...]
-
-    15. Return success with the JSON serialization of the current
-    top-level browsing context's window rect.
-    """
-
-    original = session.window.rect
-    response = set_window_rect(session, rect)
-    assert_success(response, original)
-
-
-def test_fully_exit_fullscreen(session):
-    """
-    10. Fully exit fullscreen.
-
-    [...]
-
-    To fully exit fullscreen a document document, run these steps:
-
-      1. If document's fullscreen element is null, terminate these steps.
-
-      2. Unfullscreen elements whose fullscreen flag is set, within
-      document's top layer, except for document's fullscreen element.
-
-      3. Exit fullscreen document.
-    """
-    session.window.fullscreen()
-    assert session.execute_script("return window.fullScreen") is True
-
-    response = set_window_rect(session, {"width": 400, "height": 400})
-    value = assert_success(response)
-    assert value["width"] == 400
-    assert value["height"] == 400
-
-    assert session.execute_script("return window.fullScreen") is False
-
-
-def test_restore_from_minimized(session):
-    """
-    12. If the visibility state of the top-level browsing context's
-    active document is hidden, restore the window.
-
-    [...]
-
-    To restore the window, given an operating system level window with
-    an associated top-level browsing context, run implementation-specific
-    steps to restore or unhide the window to the visible screen. Do not
-    return from this operation until the visibility state of the top-level
-    browsing context's active document has reached the visible state,
-    or until the operation times out.
-    """
-
-    session.window.minimize()
-    assert session.execute_script("return document.hidden") is True
-
-    response = set_window_rect(session, {"width": 450, "height": 450})
-    value = assert_success(response)
-    assert value["width"] == 450
-    assert value["height"] == 450
-
-    assert session.execute_script("return document.hidden") is False
-
-
-def test_restore_from_maximized(session):
-    """
-    12. If the visibility state of the top-level browsing context's
-    active document is hidden, restore the window.
-
-    [...]
-
-    To restore the window, given an operating system level window with
-    an associated top-level browsing context, run implementation-specific
-    steps to restore or unhide the window to the visible screen. Do not
-    return from this operation until the visibility state of the top-level
-    browsing context's active document has reached the visible state,
-    or until the operation times out.
-    """
-
-    original_size = session.window.size
-    session.window.maximize()
-    assert session.window.size != original_size
-
-    response = set_window_rect(session, {"width": 400, "height": 400})
-    value = assert_success(response)
-    assert value["width"] == 400
-    assert value["height"] == 400
-
-
-def test_height_width(session):
-    original = session.window.rect
-    max = session.execute_script("""
-        return {
-          width: window.screen.availWidth,
-          height: window.screen.availHeight,
-        }""")
-
-    # step 12
-    response = set_window_rect(session, {"width": max["width"] - 100,
-                                         "height": max["height"] - 100})
-
-    # step 14
-    assert_success(response, {"x": original["x"],
-                              "y": original["y"],
-                              "width": max["width"] - 100,
-                              "height": max["height"] - 100})
-
-
-def test_height_width_larger_than_max(session):
-    max = session.execute_script("""
-        return {
-          width: window.screen.availWidth,
-          height: window.screen.availHeight,
-        }""")
-
-    # step 12
-    response = set_window_rect(session, {"width": max["width"] + 100,
-                                         "height": max["height"] + 100})
-
-    # step 14
-    rect = assert_success(response)
-    assert rect["width"] >= max["width"]
-    assert rect["height"] >= max["height"]
-
-
-def test_height_width_as_current(session):
-    original = session.window.rect
-
-    # step 12
-    response = set_window_rect(session, {"width": original["width"],
-                                         "height": original["height"]})
-
-    # step 14
-    assert_success(response, {"x": original["x"],
-                              "y": original["y"],
-                              "width": original["width"],
-                              "height": original["height"]})
-
-
-def test_x_y(session):
-    original = session.window.rect
-
-    # step 13
-    response = set_window_rect(session, {"x": original["x"] + 10,
-                                         "y": original["y"] + 10})
-
-    # step 14
-    assert_success(response, {"x": original["x"] + 10,
-                              "y": original["y"] + 10,
-                              "width": original["width"],
-                              "height": original["height"]})
-
-
-def test_negative_x_y(session):
-    original = session.window.rect
-
-    # step 13
-    response = set_window_rect(session, {"x": - 8, "y": - 8})
-
-    # step 14
-    os = session.capabilities["platformName"]
-    # certain WMs prohibit windows from being moved off-screen
-    if os == "linux":
-        rect = assert_success(response)
-        assert rect["x"] <= 0
-        assert rect["y"] <= 0
-        assert rect["width"] == original["width"]
-        assert rect["height"] == original["height"]
-
-    # On macOS, windows can only be moved off the screen on the
-    # horizontal axis.  The system menu bar also blocks windows from
-    # being moved to (0,0).
-    elif os == "darwin":
-        assert_success(response, {"x": -8,
-                                  "y": 23,
-                                  "width": original["width"],
-                                  "height": original["height"]})
-
-    # It turns out that Windows is the only platform on which the
-    # window can be reliably positioned off-screen.
-    elif os == "windows_nt":
-        assert_success(response, {"x": -8,
-                                  "y": -8,
-                                  "width": original["width"],
-                                  "height": original["height"]})
-
-
-def test_move_to_same_position(session):
-    original_position = session.window.position
-    position = session.window.position = original_position
-    assert position == original_position
-
-
-def test_move_to_same_x(session):
-    original_x = session.window.position[0]
-    position = session.window.position = (original_x, 345)
-    assert position == (original_x, 345)
-
-
-def test_move_to_same_y(session):
-    original_y = session.window.position[1]
-    position = session.window.position = (456, original_y)
-    assert position == (456, original_y)
-
-
-def test_resize_to_same_size(session):
-    original_size = session.window.size
-    size = session.window.size = original_size
-    assert size == original_size
-
-
-def test_resize_to_same_width(session):
-    original_width = session.window.size[0]
-    size = session.window.size = (original_width, 345)
-    assert size == (original_width, 345)
-
-
-def test_resize_to_same_height(session):
-    original_height = session.window.size[1]
-    size = session.window.size = (456, original_height)
-    assert size == (456, original_height)
-
-
-def test_payload(session):
-    # step 14
-    response = set_window_rect(session, {"x": 400, "y": 400})
-
-    assert response.status == 200
-    assert isinstance(response.body["value"], dict)
-    value = response.body["value"]
-    assert "width" in value
-    assert "height" in value
-    assert "x" in value
-    assert "y" in value
-    assert isinstance(value["width"], int)
-    assert isinstance(value["height"], int)
-    assert isinstance(value["x"], int)
-    assert isinstance(value["y"], int)
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/set_window_rect/__init__.py
similarity index 100%
copy from webdriver/tests/state/__init__.py
copy to webdriver/tests/set_window_rect/__init__.py
diff --git a/webdriver/tests/set_window_rect/set.py b/webdriver/tests/set_window_rect/set.py
new file mode 100644
index 0000000..ca5056f
--- /dev/null
+++ b/webdriver/tests/set_window_rect/set.py
@@ -0,0 +1,429 @@
+# META: timeout=long
+
+import pytest
+
+from tests.support.asserts import assert_error, assert_success
+
+
+def set_window_rect(session, rect):
+    return session.transport.send(
+        "POST", "session/{session_id}/window/rect".format(**vars(session)),
+        rect)
+
+
+def is_fullscreen(session):
+    # At the time of writing, WebKit does not conform to the Fullscreen API specification.
+    # Remove the prefixed fallback when https://bugs.webkit.org/show_bug.cgi?id=158125 is fixed.
+    return session.execute_script("return !!(window.fullScreen || document.webkitIsFullScreen)")
+
+
+# 10.7.2 Set Window Rect
+
+
+def test_current_top_level_browsing_context_no_longer_open(session, create_window):
+    """
+    1. If the current top-level browsing context is no longer open,
+    return error with error code no such window.
+
+    """
+    session.window_handle = create_window()
+    session.close()
+    response = set_window_rect(session, {})
+    assert_error(response, "no such window")
+
+
+@pytest.mark.parametrize("rect", [
+    {"width": "a"},
+    {"height": "b"},
+    {"width": "a", "height": "b"},
+    {"x": "a"},
+    {"y": "b"},
+    {"x": "a", "y": "b"},
+    {"width": "a", "height": "b", "x": "a", "y": "b"},
+
+    {"width": True},
+    {"height": False},
+    {"width": True, "height": False},
+    {"x": True},
+    {"y": False},
+    {"x": True, "y": False},
+    {"width": True, "height": False, "x": True, "y": False},
+
+    {"width": []},
+    {"height": []},
+    {"width": [], "height": []},
+    {"x": []},
+    {"y": []},
+    {"x": [], "y": []},
+    {"width": [], "height": [], "x": [], "y": []},
+
+    {"height": {}},
+    {"width": {}},
+    {"height": {}, "width": {}},
+    {"x": {}},
+    {"y": {}},
+    {"x": {}, "y": {}},
+    {"width": {}, "height": {}, "x": {}, "y": {}},
+])
+def test_invalid_types(session, rect):
+    """
+    8. If width or height is neither null nor a Number from 0 to 2^31 -
+    1, return error with error code invalid argument.
+
+    9. If x or y is neither null nor a Number from -(2^31) to 2^31 - 1,
+    return error with error code invalid argument.
+    """
+    response = set_window_rect(session, rect)
+    assert_error(response, "invalid argument")
+
+
+@pytest.mark.parametrize("rect", [
+    {"width": -1},
+    {"height": -2},
+    {"width": -1, "height": -2},
+])
+def test_out_of_bounds(session, rect):
+    """
+    8. If width or height is neither null nor a Number from 0 to 2^31 -
+    1, return error with error code invalid argument.
+
+    9. If x or y is neither null nor a Number from -(2^31) to 2^31 - 1,
+    return error with error code invalid argument.
+    """
+    response = set_window_rect(session, rect)
+    assert_error(response, "invalid argument")
+
+
+def test_width_height_floats(session):
+    """
+    8. If width or height is neither null nor a Number from 0 to 2^31 -
+    1, return error with error code invalid argument.
+    """
+
+    response = set_window_rect(session, {"width": 500.5, "height": 420})
+    value = assert_success(response)
+    assert value["width"] == 500
+    assert value["height"] == 420
+
+    response = set_window_rect(session, {"width": 500, "height": 450.5})
+    value = assert_success(response)
+    assert value["width"] == 500
+    assert value["height"] == 450
+
+
+def test_x_y_floats(session):
+    """
+    9. If x or y is neither null nor a Number from -(2^31) to 2^31 - 1,
+    return error with error code invalid argument.
+    """
+
+    response = set_window_rect(session, {"x": 0.5, "y": 420})
+    value = assert_success(response)
+    assert value["x"] == 0
+    assert value["y"] == 420
+
+    response = set_window_rect(session, {"x": 100, "y": 450.5})
+    value = assert_success(response)
+    assert value["x"] == 100
+    assert value["y"] == 450
+
+
+@pytest.mark.parametrize("rect", [
+    {},
+
+    {"width": None},
+    {"height": None},
+    {"width": None, "height": None},
+
+    {"x": None},
+    {"y": None},
+    {"x": None, "y": None},
+
+    {"width": None, "x": None},
+    {"width": None, "y": None},
+    {"height": None, "x": None},
+    {"height": None, "Y": None},
+
+    {"width": None, "height": None, "x": None, "y": None},
+
+    {"width": 200},
+    {"height": 200},
+    {"x": 200},
+    {"y": 200},
+    {"width": 200, "x": 200},
+    {"height": 200, "x": 200},
+    {"width": 200, "y": 200},
+    {"height": 200, "y": 200},
+])
+def test_no_change(session, rect):
+    """
+    13. If width and height are not null:
+
+    [...]
+
+    14. If x and y are not null:
+
+    [...]
+
+    15. Return success with the JSON serialization of the current
+    top-level browsing context's window rect.
+    """
+
+    original = session.window.rect
+    response = set_window_rect(session, rect)
+    assert_success(response, original)
+
+
+def test_fully_exit_fullscreen(session):
+    """
+    10. Fully exit fullscreen.
+
+    [...]
+
+    To fully exit fullscreen a document document, run these steps:
+
+      1. If document's fullscreen element is null, terminate these steps.
+
+      2. Unfullscreen elements whose fullscreen flag is set, within
+      document's top layer, except for document's fullscreen element.
+
+      3. Exit fullscreen document.
+    """
+    session.window.fullscreen()
+    assert is_fullscreen(session) is True
+
+    response = set_window_rect(session, {"width": 400, "height": 400})
+    value = assert_success(response)
+    assert value["width"] == 400
+    assert value["height"] == 400
+
+    assert is_fullscreen(session) is False
+
+
+def test_restore_from_minimized(session):
+    """
+    12. If the visibility state of the top-level browsing context's
+    active document is hidden, restore the window.
+
+    [...]
+
+    To restore the window, given an operating system level window with
+    an associated top-level browsing context, run implementation-specific
+    steps to restore or unhide the window to the visible screen. Do not
+    return from this operation until the visibility state of the top-level
+    browsing context's active document has reached the visible state,
+    or until the operation times out.
+    """
+
+    session.window.minimize()
+    assert session.execute_script("return document.hidden") is True
+
+    response = set_window_rect(session, {"width": 450, "height": 450})
+    value = assert_success(response)
+    assert value["width"] == 450
+    assert value["height"] == 450
+
+    assert session.execute_script("return document.hidden") is False
+
+
+def test_restore_from_maximized(session):
+    """
+    12. If the visibility state of the top-level browsing context's
+    active document is hidden, restore the window.
+
+    [...]
+
+    To restore the window, given an operating system level window with
+    an associated top-level browsing context, run implementation-specific
+    steps to restore or unhide the window to the visible screen. Do not
+    return from this operation until the visibility state of the top-level
+    browsing context's active document has reached the visible state,
+    or until the operation times out.
+    """
+
+    original_size = session.window.size
+    session.window.maximize()
+    assert session.window.size != original_size
+
+    response = set_window_rect(session, {"width": 400, "height": 400})
+    value = assert_success(response)
+    assert value["width"] == 400
+    assert value["height"] == 400
+
+
+def test_height_width(session):
+    original = session.window.rect
+    max = session.execute_script("""
+        return {
+          width: window.screen.availWidth,
+          height: window.screen.availHeight,
+        }""")
+
+    # step 12
+    response = set_window_rect(session, {"width": max["width"] - 100,
+                                         "height": max["height"] - 100})
+
+    # step 14
+    assert_success(response, {"x": original["x"],
+                              "y": original["y"],
+                              "width": max["width"] - 100,
+                              "height": max["height"] - 100})
+
+
+def test_height_width_larger_than_max(session):
+    max = session.execute_script("""
+        return {
+          width: window.screen.availWidth,
+          height: window.screen.availHeight,
+        }""")
+
+    # step 12
+    response = set_window_rect(session, {"width": max["width"] + 100,
+                                         "height": max["height"] + 100})
+
+    # step 14
+    rect = assert_success(response)
+    assert rect["width"] >= max["width"]
+    assert rect["height"] >= max["height"]
+
+
+def test_height_width_as_current(session):
+    original = session.window.rect
+
+    # step 12
+    response = set_window_rect(session, {"width": original["width"],
+                                         "height": original["height"]})
+
+    # step 14
+    assert_success(response, {"x": original["x"],
+                              "y": original["y"],
+                              "width": original["width"],
+                              "height": original["height"]})
+
+
+def test_x_y(session):
+    original = session.window.rect
+
+    # step 13
+    response = set_window_rect(session, {"x": original["x"] + 10,
+                                         "y": original["y"] + 10})
+
+    # step 14
+    assert_success(response, {"x": original["x"] + 10,
+                              "y": original["y"] + 10,
+                              "width": original["width"],
+                              "height": original["height"]})
+
+
+def test_negative_x_y(session):
+    original = session.window.rect
+
+    # step 13
+    response = set_window_rect(session, {"x": - 8, "y": - 8})
+
+    # step 14
+    os = session.capabilities["platformName"]
+    # certain WMs prohibit windows from being moved off-screen
+    if os == "linux":
+        rect = assert_success(response)
+        assert rect["x"] <= 0
+        assert rect["y"] <= 0
+        assert rect["width"] == original["width"]
+        assert rect["height"] == original["height"]
+
+    # On macOS, windows can only be moved off the screen on the
+    # horizontal axis.  The system menu bar also blocks windows from
+    # being moved to (0,0).
+    elif os == "darwin":
+        assert_success(response, {"x": -8,
+                                  "y": 23,
+                                  "width": original["width"],
+                                  "height": original["height"]})
+
+    # It turns out that Windows is the only platform on which the
+    # window can be reliably positioned off-screen.
+    elif os == "windows_nt":
+        assert_success(response, {"x": -8,
+                                  "y": -8,
+                                  "width": original["width"],
+                                  "height": original["height"]})
+
+
+def test_move_to_same_position(session):
+    original_position = session.window.position
+    position = session.window.position = original_position
+    assert position == original_position
+
+
+def test_move_to_same_x(session):
+    original_x = session.window.position[0]
+    position = session.window.position = (original_x, 345)
+    assert position == (original_x, 345)
+
+
+def test_move_to_same_y(session):
+    original_y = session.window.position[1]
+    position = session.window.position = (456, original_y)
+    assert position == (456, original_y)
+
+
+def test_resize_to_same_size(session):
+    original_size = session.window.size
+    size = session.window.size = original_size
+    assert size == original_size
+
+
+def test_resize_to_same_width(session):
+    original_width = session.window.size[0]
+    size = session.window.size = (original_width, 345)
+    assert size == (original_width, 345)
+
+
+def test_resize_to_same_height(session):
+    original_height = session.window.size[1]
+    size = session.window.size = (456, original_height)
+    assert size == (456, original_height)
+
+
+"""
+TODO(ato):
+
+    Disable test because the while statements are wrong.
+    To fix this properly we need to write an explicit wait utility.
+
+def test_resize_by_script(session):
+    # setting the window size by JS is asynchronous
+    # so we poll waiting for the results
+
+    size0 = session.window.size
+
+    session.execute_script("window.resizeTo(700, 800)")
+    size1 = session.window.size
+    while size0 == size1:
+        size1 = session.window.size
+    assert size1 == (700, 800)
+
+    session.execute_script("window.resizeTo(800, 900)")
+    size2 = session.window.size
+    while size1 == size2:
+        size2 = session.window.size
+        assert size2 == (800, 900)
+    assert size2 == {"width": 200, "height": 100}
+"""
+
+
+def test_payload(session):
+    # step 14
+    response = set_window_rect(session, {"x": 400, "y": 400})
+
+    assert response.status == 200
+    assert isinstance(response.body["value"], dict)
+    value = response.body["value"]
+    assert "width" in value
+    assert "height" in value
+    assert "x" in value
+    assert "y" in value
+    assert isinstance(value["width"], int)
+    assert isinstance(value["height"], int)
+    assert isinstance(value["x"], int)
+    assert isinstance(value["y"], int)
diff --git a/webdriver/tests/set_window_rect/user_prompts.py b/webdriver/tests/set_window_rect/user_prompts.py
new file mode 100644
index 0000000..85249d9
--- /dev/null
+++ b/webdriver/tests/set_window_rect/user_prompts.py
@@ -0,0 +1,73 @@
+from tests.support.asserts import assert_dialog_handled, assert_error, assert_success
+from tests.support.fixtures import create_dialog
+
+
+def set_window_rect(session, rect):
+    return session.transport.send(
+        "POST", "session/{session_id}/window/rect".format(**vars(session)),
+        rect)
+
+
+def test_handle_prompt_dismiss():
+    """TODO"""
+
+
+def test_handle_prompt_accept(new_session, add_browser_capabilites):
+    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
+    original = session.window.rect
+
+    # step 2
+    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+    result = set_window_rect(session, {"x": original["x"],
+                                       "y": original["y"]})
+    assert result.status == 200
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+    result = set_window_rect(session, {"x": original["x"],
+                                       "y": original["y"]})
+    assert result.status == 200
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+    result = set_window_rect(session, {"x": original["x"],
+                                       "y": original["y"]})
+    assert_success(result)
+    assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_dismiss_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_accept_and_notify():
+    """TODO"""
+
+
+def test_handle_prompt_ignore():
+    """TODO"""
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+    original = session.window.rect
+
+    create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+    result = set_window_rect(session, {"x": original["x"],
+                                       "y": original["y"]})
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #1")
+
+    create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+    result = set_window_rect(session, {"x": original["x"],
+                                       "y": original["y"]})
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #2")
+
+    create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+    result = set_window_rect(session, {"x": original["x"],
+                                       "y": original["y"]})
+    assert_error(result, "unexpected alert open")
+    assert_dialog_handled(session, "dismiss #3")
diff --git a/webdriver/tests/state/get_element_attribute.py b/webdriver/tests/state/get_element_attribute.py
deleted file mode 100644
index 1301a45..0000000
--- a/webdriver/tests/state/get_element_attribute.py
+++ /dev/null
@@ -1,195 +0,0 @@
-import pytest
-
-from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
-from tests.support.fixtures import create_dialog
-from tests.support.inline import inline
-
-
-def get_attribute(session, element, attr):
-    return session.transport.send("GET", "session/{session_id}/element/{element_id}/attribute/{attr}"
-                                  .format(session_id=session.session_id,
-                                          element_id=element,
-                                          attr=attr))
-
-
-# 13.2 Get Element Attribute
-
-def test_no_browsing_context(session, create_window):
-    # 13.2 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    result = get_attribute(session, "foo", "id")
-
-    assert_error(result, "no such window")
-
-
-def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
-    # 13.2 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    # 13.2 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session):
-    # 13.2 step 2
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = get_attribute(session, element.id, "id")
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_element_not_found(session):
-    # 13.2 Step 3
-    result = get_attribute(session, "foo", "id")
-
-    assert_error(result, "no such element")
-
-
-def test_element_stale(session):
-    # 13.2 step 4
-    session.url = inline("<input id=foo>")
-    element = session.find.css("input", all=False)
-    session.refresh()
-    result = get_attribute(session, element.id, "id")
-
-    assert_error(result, "stale element reference")
-
-
-def test_normal(session):
-    # 13.2 Step 5
-    session.url = inline("<input type=checkbox>")
-    element = session.find.css("input", all=False)
-    result = get_attribute(session, element.id, "input")
-    assert_success(result, None)
-    assert False == session.execute_script("return document.querySelector('input').checked")
-
-    # Check we are not returning the property which will have a different value
-    element.click()
-    assert True == session.execute_script("return document.querySelector('input').checked")
-    result = get_attribute(session, element.id, "input")
-    assert_success(result, None)
-
-
-@pytest.mark.parametrize("tag,attrs", [
-    ("audio", ["autoplay", "controls", "loop", "muted"]),
-    ("button", ["autofocus", "disabled", "formnovalidate"]),
-    ("details", ["open"]),
-    ("dialog", ["open"]),
-    ("fieldset", ["disabled"]),
-    ("form", ["novalidate"]),
-    ("iframe", ["allowfullscreen"]),
-    ("img", ["ismap"]),
-    ("input", ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"]),
-    ("menuitem", ["checked", "default", "disabled"]),
-    ("object", ["typemustmatch"]),
-    ("ol", ["reversed"]),
-    ("optgroup", ["disabled"]),
-    ("option", ["disabled", "selected"]),
-    ("script", ["async", "defer"]),
-    ("select", ["autofocus", "disabled", "multiple", "required"]),
-    ("textarea", ["autofocus", "disabled", "readonly", "required"]),
-    ("track", ["default"]),
-    ("video", ["autoplay", "controls", "loop", "muted"])
-])
-def test_boolean_attribute(session, tag, attrs):
-    # 13.2 Step 5
-    for attr in attrs:
-        session.url = inline("<{0} {1}>".format(tag, attr))
-
-        element = session.find.css(tag, all=False)
-        result = result = get_attribute(session, element.id, attr)
-        assert_success(result, "true")
-
-
-def test_global_boolean_attributes(session):
-    # 13.2 Step 5
-    session.url = inline("<p hidden>foo")
-    element = session.find.css("p", all=False)
-    result = result = get_attribute(session, element.id, "hidden")
-
-    assert_success(result, "true")
-
-    session.url = inline("<p>foo")
-    element = session.find.css("p", all=False)
-    result = result = get_attribute(session, element.id, "hidden")
-    assert_success(result, None)
-
-    session.url = inline("<p itemscope>foo")
-    element = session.find.css("p", all=False)
-    result = result = get_attribute(session, element.id, "itemscope")
-
-    assert_success(result, "true")
-
-    session.url = inline("<p>foo")
-    element = session.find.css("p", all=False)
-    result = result = get_attribute(session, element.id, "itemscope")
-    assert_success(result, None)
diff --git a/webdriver/tests/state/get_element_property.py b/webdriver/tests/state/get_element_property.py
deleted file mode 100644
index c76c561..0000000
--- a/webdriver/tests/state/get_element_property.py
+++ /dev/null
@@ -1,167 +0,0 @@
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
-from tests.support.inline import inline
-from tests.support.fixtures import create_dialog
-
-_input = inline("<input id=i1>")
-
-
-# 13.3 Get Element Property
-
-def test_no_browsing_context(session, create_window):
-    # 13.3 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id="foo"))
-
-    assert_error(result, "no such window")
-
-
-def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
-    # 13.3 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    # 13.3 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "foo")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session):
-    # 13.3 step 2
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-def test_element_not_found(session):
-    # 13.3 Step 3
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id="foo"))
-
-    assert_error(result, "no such element")
-
-
-def test_element_stale(session):
-    # 13.3 step 4
-    session.url = _input
-    element = session.find.css("input", all=False)
-    session.refresh()
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "stale element reference")
-
-
-def test_element_non_existent(session):
-    # 13.3 step 5-7
-    session.url = _input
-    element = session.find.css("input", all=False)
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/foo"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, None)
-    assert None == session.execute_script("return arguments[0].foo",
-                                          args=[element])
-
-
-def test_element(session):
-    # 13.3 step 5-7
-    session.url = inline("<input type=checkbox>")
-    element = session.find.css("input", all=False)
-    element.click()
-    assert None == session.execute_script("return arguments[0].hasAttribute('checked')",
-                                          args=[element])
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-    assert_success(result, True)
diff --git a/webdriver/tests/state/get_element_tag_name.py b/webdriver/tests/state/get_element_tag_name.py
deleted file mode 100644
index 2dd7528..0000000
--- a/webdriver/tests/state/get_element_tag_name.py
+++ /dev/null
@@ -1,149 +0,0 @@
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
-from tests.support.inline import inline
-from tests.support.fixtures import create_dialog
-
-
-# 13.6 Get Element Tag Name
-
-def test_no_browsing_context(session, create_window):
-    # 13.6 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id="foo"))
-
-    assert_error(result, "no such window")
-
-
-def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
-    # 13.6 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "input")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "input")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "input")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    # 13.6 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "input")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "input")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, "input")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session):
-    # 13.6 step 2
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_element_not_found(session):
-    # 13.6 Step 3
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id="foo"))
-
-    assert_error(result, "no such element")
-
-
-def test_element_stale(session):
-    # 13.6 step 4
-    session.url = inline("<input id=foo>")
-    element = session.find.css("input", all=False)
-    session.refresh()
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "stale element reference")
-
-
-def test_get_element_tag_name(session):
-    # 13.6 step 6
-    session.url = inline("<input id=foo>")
-    element = session.find.css("input", all=False)
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-    assert_success(result, "input")
diff --git a/webdriver/tests/state/is_element_selected.py b/webdriver/tests/state/is_element_selected.py
deleted file mode 100644
index 392929a..0000000
--- a/webdriver/tests/state/is_element_selected.py
+++ /dev/null
@@ -1,183 +0,0 @@
-from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
-from tests.support.inline import inline
-from tests.support.fixtures import create_dialog
-
-
-alert_doc = inline("<script>window.alert()</script>")
-check_doc = inline("<input id=checked type=checkbox checked/><input id=notChecked type=checkbox/>")
-option_doc = inline("""<select>
-                        <option id=notSelected>r-</option>
-                        <option id=selected selected>r+</option>
-                       </select>
-                    """)
-
-
-# 13.1 Is Element Selected
-
-def test_no_browsing_context(session, create_window):
-    # 13.1 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id="foo"))
-
-    assert_error(result, "no such window")
-
-
-def test_handle_prompt_dismiss(new_session, add_browser_capabilites):
-    # 13.1 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "dismiss"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_accept(new_session, add_browser_capabilites):
-    # 13.1 step 2
-    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({"unhandledPromptBehavior": "accept"})}})
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_handle_prompt_missing_value(session):
-    # 13.1 step 2
-    session.url = inline("<input id=foo>")
-    element = session.find.css("#foo", all=False)
-
-    create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #1")
-
-    create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #2")
-
-    create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
-
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "unexpected alert open")
-    assert_dialog_handled(session, "dismiss #3")
-
-
-def test_element_stale(session):
-    # 13.1 step 4
-    session.url = check_doc
-    element = session.find.css("#checked", all=False)
-    session.refresh()
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_error(result, "stale element reference")
-
-
-def test_element_checked(session):
-    # 13.1 step 5
-    session.url = check_doc
-    element = session.find.css("#checked", all=False)
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, True)
-
-
-def test_checkbox_not_selected(session):
-    # 13.1 step 5
-    session.url = check_doc
-    element = session.find.css("#notChecked", all=False)
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
-
-
-def test_element_selected(session):
-    # 13.1 step 5
-    session.url = option_doc
-    element = session.find.css("#selected", all=False)
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, True)
-
-
-def test_element_not_selected(session):
-    # 13.1 step 5
-    session.url = option_doc
-    element = session.find.css("#notSelected", all=False)
-    result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
-                                    .format(session_id=session.session_id,
-                                            element_id=element.id))
-
-    assert_success(result, False)
diff --git a/webdriver/tests/state/text/get_text.py b/webdriver/tests/state/text/get_text.py
deleted file mode 100644
index aa07b8d..0000000
--- a/webdriver/tests/state/text/get_text.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import pytest
-import uuid
-
-from tests.support.asserts import assert_error, assert_success
-from tests.support.inline import inline
-
-# For failing tests, the Get Element Text end-point is used
-# directly. In all other cases, the Element.text() function is used.
-
-def test_getting_text_of_a_non_existant_element_is_an_error(session):
-   session.url = inline("""<body>Hello world</body>""")
-   id = uuid.uuid4()
-
-   result = session.transport.send(
-       "GET",
-       "session/%s/element/%s/text" % (session.session_id, id))
-
-   assert_error(result, "no such element")
-
-
-def test_read_element_text(session):
-    session.url = inline("""
-        <body>
-          Noise before <span id='id'>This has an ID</span>. Noise after
-        </body>""")
-
-    element = session.find.css("#id", all=False)
-
-    assert element.text == "This has an ID"
diff --git a/webdriver/tests/status.py b/webdriver/tests/status.py
deleted file mode 100644
index 9472f8a..0000000
--- a/webdriver/tests/status.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import pytest
-import json
-
-
-def test_get_status_no_session(http):
-    with http.get("/status") as response:
-        # GET /status should never return an error
-        assert response.status == 200
-
-        # parse JSON response and unwrap 'value' property
-        parsed_obj = json.loads(response.read().decode('utf-8'))
-        value = parsed_obj["value"]
-
-        # Let body be a new JSON Object with the following properties:
-        # "ready"
-        #       The remote end's readiness state.
-        assert value["ready"] in [True, False]
-        # "message"
-        #       An implementation-defined string explaining the remote end's
-        #       readiness state.
-        assert isinstance(value["message"], basestring)
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/status/__init__.py
similarity index 100%
rename from webdriver/tests/state/__init__.py
rename to webdriver/tests/status/__init__.py
diff --git a/webdriver/tests/status/status.py b/webdriver/tests/status/status.py
new file mode 100644
index 0000000..95b2643
--- /dev/null
+++ b/webdriver/tests/status/status.py
@@ -0,0 +1,50 @@
+import json
+
+from tests.support.asserts import assert_success
+
+
+def get_status(session):
+    return session.transport.send("GET", "/status")
+
+
+def test_get_status_no_session(http):
+    with http.get("/status") as response:
+        # GET /status should never return an error
+        assert response.status == 200
+
+        # parse JSON response and unwrap 'value' property
+        parsed_obj = json.loads(response.read().decode('utf-8'))
+        value = parsed_obj["value"]
+
+        # Let body be a new JSON Object with the following properties:
+        # "ready"
+        #       The remote end's readiness state.
+        assert value["ready"] in [True, False]
+        # "message"
+        #       An implementation-defined string explaining the remote end's
+        #       readiness state.
+        assert isinstance(value["message"], basestring)
+
+
+def test_status_with_session_running_on_endpoint_node(new_session, add_browser_capabilites):
+    # For an endpoint node, the maximum number of active
+    # sessions is 1: https://www.w3.org/TR/webdriver/#dfn-maximum-active-sessions
+    # A session is open, so we expect `ready` to be False
+    # 8.3 step 1.
+
+    _, session = new_session({"capabilities": {"alwaysMatch": add_browser_capabilites({})}})
+
+    response = get_status(session)
+    value = assert_success(response)
+    assert value["ready"] is False
+    assert "message" in value
+
+    session.end()
+
+    # Active session count is 0, meaning that the
+    # readiness state of the server should be True
+    # 8.3 step 1. Again
+    response = get_status(session)
+    value = assert_success(response)
+    assert value["ready"] is True
+    assert "message" in value
diff --git a/webdriver/tests/support/asserts.py b/webdriver/tests/support/asserts.py
index 4174791..a1cd0f9 100644
--- a/webdriver/tests/support/asserts.py
+++ b/webdriver/tests/support/asserts.py
@@ -1,5 +1,6 @@
 from webdriver import Element, WebDriverException
 
+
 # WebDriver specification ID: dfn-error-response-data
 errors = {
     "element click intercepted": 400,
@@ -32,6 +33,7 @@
     "unsupported operation": 500,
 }
 
+
 # WebDriver specification ID: dfn-send-an-error
 #
 # > When required to send an error, with error code, a remote end must run the
@@ -98,7 +100,7 @@
     except:
         assert (result.status == 200 and
                 result.body["value"] != expected_text), (
-               "Dialog with text '%s' was not handled." % expected_text)
+            "Dialog with text '%s' was not handled." % expected_text)
 
 
 def assert_same_element(session, a, b):
@@ -123,7 +125,7 @@
         return
 
     message = ("Expected element references to describe the same element, " +
-        "but they did not.")
+               "but they did not.")
 
     # Attempt to provide more information, accounting for possible errors such
     # as stale element references or not visible elements.
@@ -135,3 +137,22 @@
         pass
 
     raise AssertionError(message)
+
+
+def assert_element_has_focus(target_element):
+    session = target_element.session
+
+    active_element = session.execute_script("return document.activeElement")
+    active_tag = active_element.property("localName")
+    target_tag = target_element.property("localName")
+
+    assert active_element == target_element, (
+        "Focussed element is <%s>, not <%s>" % (active_tag, target_tag))
+
+
+def assert_move_to_coordinates(point, target, events):
+    for e in events:
+        if e["type"] != "mousemove":
+            assert e["pageX"] == point["x"]
+            assert e["pageY"] == point["y"]
+            assert e["target"] == target
diff --git a/webdriver/tests/support/fixtures.py b/webdriver/tests/support/fixtures.py
index 778acd3..dbf9f1d 100644
--- a/webdriver/tests/support/fixtures.py
+++ b/webdriver/tests/support/fixtures.py
@@ -1,27 +1,26 @@
+from __future__ import print_function
+
 import json
 import os
 import urlparse
 import re
+import sys
 
 import webdriver
-import mozlog
 
-from tests.support.asserts import assert_error
 from tests.support.http_request import HTTPRequest
-from tests.support import merge_dictionaries
+from tests.support.wait import wait
 
 default_host = "http://127.0.0.1"
 default_port = "4444"
 
-logger = mozlog.get_default_logger()
-
 
 def ignore_exceptions(f):
     def inner(*args, **kwargs):
         try:
             return f(*args, **kwargs)
         except webdriver.error.WebDriverException as e:
-            logger.warning("Ignored exception %s" % e)
+            print("Ignored exception %s" % e, file=sys.stderr)
     inner.__name__ = f.__name__
     return inner
 
@@ -77,6 +76,7 @@
     session.window_handle = current_window
 
 
+@ignore_exceptions
 def _switch_to_top_level_browsing_context(session):
     """If the current browsing context selected by WebDriver is a
     `<frame>` or an `<iframe>`, switch it back to the top-level
@@ -181,7 +181,8 @@
         global _current_session
         if _current_session is not None and _current_session.session_id:
             _current_session.end()
-            _current_session = None
+
+        _current_session = None
 
     def create_session(body):
         global _current_session
@@ -214,7 +215,7 @@
 def url(server_config):
     def inner(path, protocol="http", query="", fragment=""):
         port = server_config["ports"][protocol][0]
-        host = "%s:%s" % (server_config["host"], port)
+        host = "%s:%s" % (server_config["browser_host"], port)
         return urlparse.urlunsplit((protocol, host, path, query, fragment))
 
     inner.__name__ = "url"
@@ -254,10 +255,31 @@
         session.send_session_command("POST",
                                      "execute/async",
                                      {"script": spawn, "args": []})
+        wait(session,
+             lambda s: s.send_session_command("GET", "alert/text") == text,
+             "modal has not appeared",
+             timeout=15,
+             ignored_exceptions=webdriver.NoSuchAlertException)
 
     return create_dialog
 
+
 def clear_all_cookies(session):
     """Removes all cookies associated with the current active document"""
     session.transport.send("DELETE", "session/%s/cookie" % session.session_id)
 
+
+def is_element_in_viewport(session, element):
+    """Check if element is outside of the viewport"""
+    return session.execute_script("""
+        let el = arguments[0];
+
+        let rect = el.getBoundingClientRect();
+        let viewport = {
+          height: window.innerHeight || document.documentElement.clientHeight,
+          width: window.innerWidth || document.documentElement.clientWidth,
+        };
+
+        return !(rect.right < 0 || rect.bottom < 0 ||
+            rect.left > viewport.width || rect.top > viewport.height)
+    """, args=(element,))
diff --git a/webdriver/tests/support/inline.py b/webdriver/tests/support/inline.py
index cdb6dc1..2f8fe9b 100644
--- a/webdriver/tests/support/inline.py
+++ b/webdriver/tests/support/inline.py
@@ -40,5 +40,9 @@
     if doc is None:
         rv = 404, [("Content-Type", "text/plain")], "Missing doc parameter in query"
     else:
-        rv = [("Content-Type", content_type)], doc
+        response.headers.update([
+          ("Content-Type", content_type),
+          ("X-XSS-Protection", "0")
+        ])
+        rv = doc
     return rv
diff --git a/webdriver/tests/support/wait.py b/webdriver/tests/support/wait.py
index f645abe..6f439ec 100644
--- a/webdriver/tests/support/wait.py
+++ b/webdriver/tests/support/wait.py
@@ -1,10 +1,13 @@
+import sys
 import time
 
+
 class TimeoutException(Exception):
     pass
 
 
-def wait(session, condition, message, interval=0.1, timeout=5):
+def wait(session, condition, message,
+         interval=0.1, timeout=5, ignored_exceptions=Exception):
     """ Poll a condition until it's true or the timeout ellapses.
 
     :param session: WebDriver session to use with `condition`
@@ -12,6 +15,8 @@
     :param message: failure description to display in case the timeout is reached
     :param interval: seconds between each call to `condition`. Default: 0.1
     :param timeout: seconds until we stop polling. Default: 5
+    :param ignored_exceptions: Exceptions that are expected and can be ignored.
+        Default: Exception
     """
 
     start = time.time()
@@ -19,10 +24,15 @@
 
     while not (time.time() >= end):
         next_step = time.time() + interval
-        success = condition(session)
+        try:
+            success = condition(session)
+        except ignored_exceptions:
+            last_exc = sys.exc_info()[0]
+            success = False
         next_interval = max(next_step - time.time(), 0)
         if not success:
             time.sleep(next_interval)
             continue
         return success
+
     raise TimeoutException("Timed out after %d seconds: %s" % (timeout, message))
diff --git a/webdriver/tests/switch_to_parent_frame.py b/webdriver/tests/switch_to_parent_frame.py
deleted file mode 100644
index 2e5c4e9..0000000
--- a/webdriver/tests/switch_to_parent_frame.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import pytest
-from webdriver import StaleElementReferenceException
-
-from tests.support.inline import inline, iframe
-
-
-def switch_to_parent_frame(session):
-    return session.transport.send("POST", "session/%s/frame/parent" % session.session_id)
-
-
-def test_stale_element_from_iframe(session):
-    session.url = inline(iframe("<p>foo"))
-    frame_element = session.find.css("iframe", all=False)
-    session.switch_frame(frame_element)
-    stale_element = session.find.css("p", all=False)
-    switch_to_parent_frame(session)
-    with pytest.raises(StaleElementReferenceException):
-        stale_element.text
diff --git a/webdriver/tests/state/__init__.py b/webdriver/tests/switch_to_parent_frame/__init__.py
similarity index 100%
copy from webdriver/tests/state/__init__.py
copy to webdriver/tests/switch_to_parent_frame/__init__.py
diff --git a/webdriver/tests/switch_to_parent_frame/switch.py b/webdriver/tests/switch_to_parent_frame/switch.py
new file mode 100644
index 0000000..215c2a4
--- /dev/null
+++ b/webdriver/tests/switch_to_parent_frame/switch.py
@@ -0,0 +1,23 @@
+import pytest
+from webdriver import StaleElementReferenceException
+
+from tests.support.asserts import assert_success
+from tests.support.inline import inline, iframe
+
+
+def switch_to_parent_frame(session):
+    return session.transport.send(
+        "POST", "session/{session_id}/frame/parent".format(**vars(session)))
+
+
+def test_stale_element_from_iframe(session):
+    session.url = inline(iframe("<p>foo"))
+    frame_element = session.find.css("iframe", all=False)
+    session.switch_frame(frame_element)
+    stale_element = session.find.css("p", all=False)
+
+    result = switch_to_parent_frame(session)
+    assert_success(result)
+
+    with pytest.raises(StaleElementReferenceException):
+        stale_element.text
diff --git a/webdriver/tests/user_prompts/accept_alert.py b/webdriver/tests/user_prompts/accept_alert.py
deleted file mode 100644
index d47ed1c..0000000
--- a/webdriver/tests/user_prompts/accept_alert.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from tests.support.asserts import assert_error, assert_success
-from tests.support.inline import inline
-
-
-def accept_alert(session):
-    return session.transport.send("POST", "session/{session_id}/alert/accept"
-                                  .format(session_id=session.session_id))
-
-
-# 18.2 Accept Alert
-
-def test_no_browsing_context(session, create_window):
-    # 18.2 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    response = accept_alert(session)
-    assert_error(response, "no such window")
-
-
-def test_no_user_prompt(session):
-    # 18.2 step 2
-    response = accept_alert(session)
-    assert_error(response, "no such alert")
-
-
-def test_accept_alert(session):
-    # 18.2 step 3
-    session.url = inline("<script>window.alert('Hello');</script>")
-    response = accept_alert(session)
-    assert_success(response)
-
-
-def test_accept_confirm(session):
-    # 18.2 step 3
-    session.url = inline("<script>window.result = window.confirm('Hello');</script>")
-    response = accept_alert(session)
-    assert_success(response)
-    assert session.execute_script("return window.result") is True
-
-
-def test_accept_prompt(session):
-    # 18.2 step 3
-    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Federer');</script>")
-    response = accept_alert(session)
-    assert_success(response)
-    assert session.execute_script("return window.result") == "Federer"
diff --git a/webdriver/tests/user_prompts/dismiss_alert.py b/webdriver/tests/user_prompts/dismiss_alert.py
deleted file mode 100644
index 6a20496..0000000
--- a/webdriver/tests/user_prompts/dismiss_alert.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from tests.support.asserts import assert_error, assert_success
-from tests.support.inline import inline
-
-
-def dismiss_alert(session):
-    return session.transport.send("POST", "session/{session_id}/alert/dismiss"
-                                  .format(session_id=session.session_id))
-
-
-# 18.1 Dismiss Alert
-
-def test_no_browsing_context(session, create_window):
-    # 18.1 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    response = dismiss_alert(session)
-    assert_error(response, "no such window")
-
-
-def test_no_user_prompt(session):
-    # 18.1 step 2
-    response = dismiss_alert(session)
-    assert_error(response, "no such alert")
-
-
-def test_dismiss_alert(session):
-    # 18.1 step 3
-    session.url = inline("<script>window.alert('Hello');</script>")
-    response = dismiss_alert(session)
-    assert_success(response)
-
-
-def test_dismiss_confirm(session):
-    # 18.1 step 3
-    session.url = inline("<script>window.result = window.confirm('Hello');</script>")
-    response = dismiss_alert(session)
-    assert_success(response)
-    assert session.execute_script("return window.result;") is False
-
-
-def test_dismiss_prompt(session):
-    # 18.1 step 3
-    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Federer');</script>")
-    response = dismiss_alert(session)
-    assert_success(response)
-    assert session.execute_script("return window.result") is None
diff --git a/webdriver/tests/user_prompts/get_alert_text.py b/webdriver/tests/user_prompts/get_alert_text.py
deleted file mode 100644
index 7b71f7f..0000000
--- a/webdriver/tests/user_prompts/get_alert_text.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from tests.support.asserts import assert_error, assert_success
-from tests.support.inline import inline
-
-
-def get_dialog_text(session):
-    return session.transport.send("GET", "session/{session_id}/alert/text"
-                                  .format(session_id=session.session_id))
-
-
-# 18.3 Get Alert Text
-
-def test_no_browsing_context(session, create_window):
-    # 18.3 step 1
-    session.window_handle = create_window()
-    session.close()
-
-    response = get_dialog_text(session)
-    assert_error(response, "no such window")
-
-
-def test_no_user_prompt(session):
-    # 18.3 step 2
-    response = get_dialog_text(session)
-    assert_error(response, "no such alert")
-
-
-def test_get_alert_text(session):
-    # 18.3 step 3
-    session.url = inline("<script>window.alert('Hello');</script>")
-    response = get_dialog_text(session)
-    assert_success(response)
-    assert isinstance(response.body, dict)
-    assert "value" in response.body
-    alert_text = response.body["value"]
-    assert isinstance(alert_text, basestring)
-    assert alert_text == "Hello"
-
-
-def test_get_confirm_text(session):
-    # 18.3 step 3
-    session.url = inline("<script>window.confirm('Hello');</script>")
-    response = get_dialog_text(session)
-    assert_success(response)
-    assert isinstance(response.body, dict)
-    assert "value" in response.body
-    confirm_text = response.body["value"]
-    assert isinstance(confirm_text, basestring)
-    assert confirm_text == "Hello"
-
-
-def test_get_prompt_text(session):
-    # 18.3 step 3
-    session.url = inline("<script>window.prompt('Enter Your Name: ', 'Federer');</script>")
-    response = get_dialog_text(session)
-    assert_success(response)
-    assert isinstance(response.body, dict)
-    assert "value" in response.body
-    prompt_text = response.body["value"]
-    assert isinstance(prompt_text, basestring)
-    assert prompt_text == "Enter Your Name: "
diff --git a/webdriver/tests/user_prompts/send_alert_text.py b/webdriver/tests/user_prompts/send_alert_text.py
deleted file mode 100644
index 6769b55..0000000
--- a/webdriver/tests/user_prompts/send_alert_text.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import pytest
-
-from tests.support.asserts import assert_error, assert_success
-from tests.support.inline import inline
-
-def send_alert_text(session, body=None):
-    return session.transport.send("POST", "session/{session_id}/alert/text"
-                                  .format(session_id=session.session_id), body)
-
-
-# 18.4 Send Alert Text
-
-@pytest.mark.parametrize("text", [None, {}, [], 42, True])
-def test_invalid_input(session, text):
-    # 18.4 step 2
-    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Name');</script>")
-    response = send_alert_text(session, {"text": text})
-    assert_error(response, "invalid argument")
-
-
-def test_no_browsing_context(session, create_window):
-    # 18.4 step 3
-    session.window_handle = create_window()
-    session.close()
-    body = {"text": "Federer"}
-    response = send_alert_text(session, body)
-    assert_error(response, "no such window")
-
-
-def test_no_user_prompt(session):
-    # 18.4 step 4
-    body = {"text": "Federer"}
-    response = send_alert_text(session, body)
-    assert_error(response, "no such alert")
-
-
-def test_alert_element_not_interactable(session):
-    # 18.4 step 5
-    session.url = inline("<script>window.alert('Hello');</script>")
-    body = {"text": "Federer"}
-    response = send_alert_text(session, body)
-    assert_error(response, "element not interactable")
-
-
-def test_confirm_element_not_interactable(session):
-    # 18.4 step 5
-    session.url = inline("<script>window.confirm('Hello');</script>")
-    body = {"text": "Federer"}
-    response = send_alert_text(session, body)
-    assert_error(response, "element not interactable")
-
-
-def test_send_alert_text(session):
-    # 18.4 step 6
-    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Name');</script>")
-    body = {"text": "Federer"}
-    send_response = send_alert_text(session, body)
-    assert_success(send_response)
-    accept_response = session.transport.send("POST", "session/{session_id}/alert/accept"
-                                             .format(session_id=session.session_id))
-    assert_success(accept_response)
-    assert session.execute_script("return window.result") == "Federer"
-
-
-def test_send_alert_text_with_whitespace(session):
-    # 18.4 step 6
-    session.url = inline("<script>window.result = window.prompt('Enter Your Name: ', 'Name');</script>")
-    body = {"text": " Fed erer "}
-    send_response = send_alert_text(session, body)
-    assert_success(send_response)
-    accept_response = session.transport.send("POST", "session/{session_id}/alert/accept"
-                                             .format(session_id=session.session_id))
-    assert_success(accept_response)
-    assert session.execute_script("return window.result") == " Fed erer "
diff --git a/webgl/OWNERS b/webgl/OWNERS
new file mode 100644
index 0000000..a02b1cb
--- /dev/null
+++ b/webgl/OWNERS
@@ -0,0 +1 @@
+@kenrussell
diff --git a/webmessaging/MessageEvent.html b/webmessaging/MessageEvent.html
index e95b3ef..4fb68b5 100644
--- a/webmessaging/MessageEvent.html
+++ b/webmessaging/MessageEvent.html
@@ -18,4 +18,11 @@
     assert_false(name in event);
   }, name + " on the instance");
 });
+
+test(function() {
+  var event = new MessageEvent("message");
+  assert_throws(new TypeError(), function() {
+    event.initMessageEvent();
+  }, "Not enough arguments to initMessageEvent");
+}, "initMessageEvent with no arguments");
 </script>
diff --git a/webmessaging/broadcastchannel/workers.html b/webmessaging/broadcastchannel/workers.html
index 7ccbd39..1d05feb 100644
--- a/webmessaging/broadcastchannel/workers.html
+++ b/webmessaging/broadcastchannel/workers.html
@@ -105,4 +105,37 @@
 
   }, 'Closing and re-opening a channel works.');
 
+async_test(t => {
+  function workerCode() {
+    close();
+    var bc = new BroadcastChannel('worker-test');
+    postMessage(true);
+  }
+
+  var workerBlob = new Blob([workerCode.toString() + ";workerCode();"], {type:"application/javascript"});
+
+  var w = new Worker(URL.createObjectURL(workerBlob));
+  w.onmessage = function(e) {
+    assert_true(e.data, "BroadcastChannel created on worker shutdown.");
+    t.done();
+  }
+}, 'BroadcastChannel created after a worker self.close()');
+
+async_test(t => {
+  function workerCode() {
+    close();
+    var bc = new BroadcastChannel('worker-test-after-close');
+    bc.postMessage(true);
+  }
+
+  var bc = new BroadcastChannel('worker-test-after-close');
+  bc.onmessage = function(e) {
+    assert_true(e.data, "BroadcastChannel created on worker shutdown.");
+    t.done();
+  }
+
+  var workerBlob = new Blob([workerCode.toString() + ";workerCode();"], {type:"application/javascript"});
+  new Worker(URL.createObjectURL(workerBlob));
+}, 'BroadcastChannel used after a worker self.close()');
+
 </script>
diff --git a/webmessaging/event.origin.sub.htm b/webmessaging/event.origin.sub.htm
index ea3c9e4..5b1ab3e 100644
--- a/webmessaging/event.origin.sub.htm
+++ b/webmessaging/event.origin.sub.htm
@@ -42,13 +42,23 @@
 
     window.onmessage = t.step_func(function(e)
     {
-        // testharness.js uses postMessage so we must check what data we want to receive
-        if (e.data.toString() === "#1" || e.data.toString() === "#2") {
-            ActualResult.push(e.data, e.origin);
-            if (ActualResult.length === ExpectedResult.length) {
-                assert_array_equals(ActualResult, ExpectedResult, "ActualResult");
-                t.done();
-            }
+        // Messages from TARGET1 and TARGET2 can come in any order
+        // (since one of them is cross-origin and can run in parallel).
+        // To make the tests immune to message reordering, always
+        // put the response from TARGET1 at the start of the list.
+        if (e.data.toString() === "#1")
+        {
+            ActualResult = [e.data, e.origin].concat(ActualResult);
+        }
+        else if (e.data.toString() === "#2")
+        {
+            ActualResult = ActualResult.concat([e.data, e.origin]);
+        }
+
+        if (ActualResult.length >= ExpectedResult.length)
+        {
+            assert_array_equals(ActualResult, ExpectedResult, "ActualResult");
+            t.done();
         }
     });
 </script>
diff --git a/webmessaging/message-channels/close.html b/webmessaging/message-channels/close.html
index 912aacd..d975ea7 100644
--- a/webmessaging/message-channels/close.html
+++ b/webmessaging/message-channels/close.html
@@ -38,18 +38,6 @@
 
 async_test(t => {
     const c = new MessageChannel();
-    c.port1.onmessage = t.unreached_func('Should not have delivered message');
-    c.port2.close();
-    const c2 = new MessageChannel();
-    c2.port1.onmessage = t.step_func(e => {
-        e.ports[0].postMessage('TESTMSG');
-        setTimeout(t.step_func_done(), time_to_wait_for_messages);
-      });
-    c2.port2.postMessage('TEST', [c.port2]);
-  }, 'Message sent from transferred closed port should not arrive.');
-
-async_test(t => {
-    const c = new MessageChannel();
     let isClosed = false;
     c.port1.onmessage = t.step_func_done(e => {
         assert_true(isClosed);
@@ -71,4 +59,10 @@
     c.port2.postMessage('DONE');
   }, 'Close in onmessage should not cancel inflight messages.');
 
+test(() => {
+    const c = new MessageChannel();
+    c.port1.close();
+    assert_throws("DataCloneError", () => self.postMessage(null, "*", [c.port1]));
+    self.postMessage(null, "*", [c.port2]);
+}, "close() detaches a MessagePort (but not the one its entangled with)");
 </script>
diff --git a/webmessaging/message-channels/worker.html b/webmessaging/message-channels/worker.html
new file mode 100644
index 0000000..0502021
--- /dev/null
+++ b/webmessaging/message-channels/worker.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/comms.html#messageevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+async_test(t => {
+  function workerCode() {
+    close();
+    var mc = new MessageChannel();
+    mc.port1.postMessage(42);
+    mc.port2.postMessage(42);
+    postMessage(true);
+  }
+
+  var workerBlob = new Blob([workerCode.toString() + ";workerCode();"], {type:"application/javascript"});
+
+  var w = new Worker(URL.createObjectURL(workerBlob));
+  w.onmessage = function(e) {
+    assert_true(e.data, "MessageChannel created on worker shutdown.");
+    t.done();
+  }
+}, 'MessageChannel/MessagePort created and used after a worker self.close()');
+
+</script>
diff --git a/webmessaging/postMessage_asterisk_xorigin.sub.htm b/webmessaging/postMessage_asterisk_xorigin.sub.htm
index a125246..532f282 100644
--- a/webmessaging/postMessage_asterisk_xorigin.sub.htm
+++ b/webmessaging/postMessage_asterisk_xorigin.sub.htm
@@ -42,7 +42,18 @@
 
     window.onmessage = t.step_func(function(e)
     {
-        ActualResult.push(e.data, e.origin);
+        // Messages from TARGET1 and TARGET2 can come in any order
+        // (since one of them is cross-origin and can run in parallel).
+        // To make the tests immune to message reordering, always
+        // put the response from TARGET1 at the start of the list.
+        if (e.data.toString() === "#1")
+        {
+            ActualResult = [e.data, e.origin].concat(ActualResult);
+        }
+        else if (e.data.toString() === "#2")
+        {
+            ActualResult = ActualResult.concat([e.data, e.origin]);
+        }
 
         if (ActualResult.length >= ExpectedResult.length)
         {
diff --git a/webrtc/RTCDTMFSender-helper.js b/webrtc/RTCDTMFSender-helper.js
index 97bb6f9..70fc691 100644
--- a/webrtc/RTCDTMFSender-helper.js
+++ b/webrtc/RTCDTMFSender-helper.js
@@ -8,17 +8,50 @@
 
 // The following helper functions are called from RTCPeerConnection-helper.js:
 //   getTrackFromUserMedia
+//   doSignalingHandshake
 
 // Create a RTCDTMFSender using getUserMedia()
+// Connect the PeerConnection to another PC and wait until it is
+// properly connected, so that DTMF can be sent.
 function createDtmfSender(pc = new RTCPeerConnection()) {
+  let dtmfSender;
   return getTrackFromUserMedia('audio')
   .then(([track, mediaStream]) => {
     const sender = pc.addTrack(track, mediaStream);
-    const dtmfSender = sender.dtmf;
-
+    dtmfSender = sender.dtmf;
     assert_true(dtmfSender instanceof RTCDTMFSender,
-      'Expect audio sender.dtmf to be set to a RTCDTMFSender');
-
+                'Expect audio sender.dtmf to be set to a RTCDTMFSender');
+    // Note: spec bug open - https://github.com/w3c/webrtc-pc/issues/1774
+    // on whether sending should be possible before negotiation.
+    const pc2 = new RTCPeerConnection();
+    Object.defineProperty(pc, 'otherPc', { value: pc2 });
+    exchangeIceCandidates(pc, pc2);
+    return doSignalingHandshake(pc, pc2);
+  }).then(() => {
+    if (!('canInsertDTMF' in dtmfSender)) {
+      return Promise.resolve();
+    }
+    // Wait until dtmfSender.canInsertDTMF becomes true.
+    // Up to 150 ms has been observed in test. Wait 1 second
+    // in steps of 10 ms.
+    // Note: Using a short timeout and rejected promise in order to
+    // make test return a clear error message on failure.
+    return new Promise((resolve, reject) => {
+      let counter = 0;
+      step_timeout(function checkCanInsertDTMF() {
+        if (dtmfSender.canInsertDTMF) {
+          resolve();
+        } else {
+          if (counter >= 100) {
+            reject('Waited too long for canInsertDTMF');
+            return;
+          }
+          ++counter;
+          step_timeout(checkCanInsertDTMF, 10);
+        }
+      }, 0);
+    });
+  }).then(() => {
     return dtmfSender;
   });
 }
@@ -89,12 +122,12 @@
             t.step_func(() => {
               t.done();
               pc.close();
+              pc.otherPc.close();
             }), expectedDuration + 100);
         }
       });
 
       dtmfSender.addEventListener('tonechange', onToneChange);
-
       testFunc(t, dtmfSender, pc);
     })
     .catch(t.step_func(err => {
diff --git a/webrtc/RTCDTMFSender-insertDTMF.https.html b/webrtc/RTCDTMFSender-insertDTMF.https.html
index 1f6a453..7a45e5d 100644
--- a/webrtc/RTCDTMFSender-insertDTMF.https.html
+++ b/webrtc/RTCDTMFSender-insertDTMF.https.html
@@ -55,7 +55,7 @@
    */
   promise_test(t => {
     return createDtmfSender()
-    .then(dtmfSender => {
+  .then(dtmfSender => {
       dtmfSender.insertDTMF('');
       dtmfSender.insertDTMF('012345689');
       dtmfSender.insertDTMF('ABCD');
@@ -148,7 +148,7 @@
 
       7.  Set the object's toneBuffer attribute to tones.
    */
-  promise_test(() => {
+  promise_test(t => {
     return createDtmfSender()
     .then(dtmfSender => {
       dtmfSender.insertDTMF('123');
@@ -162,4 +162,22 @@
     });
   }, 'insertDTMF() should set toneBuffer to provided tones normalized, with old tones overridden');
 
+  promise_test(t => {
+    let dtmfSender;
+    let sender;
+    let pc = new RTCPeerConnection();
+    return getTrackFromUserMedia('audio')
+      .then(([track, mediaStream]) => {
+        sender = pc.addTrack(track, mediaStream);
+        return pc.createOffer();
+      }).then(offer => {
+        pc.setLocalDescription(offer);
+        dtmfSender = sender.dtmf;
+        pc.removeTrack(sender);
+        pc.close();
+        assert_throws('InvalidStateError', () =>
+                      dtmfSender.insertDTMF('123'));
+      });
+  }, 'insertDTMF() after remove and close should reject');
+
 </script>
diff --git a/webrtc/RTCDTMFSender-ontonechange.https.html b/webrtc/RTCDTMFSender-ontonechange.https.html
index fcaa509..bfb8b8c 100644
--- a/webrtc/RTCDTMFSender-ontonechange.https.html
+++ b/webrtc/RTCDTMFSender-ontonechange.https.html
@@ -50,7 +50,7 @@
         7.  Fire an event named tonechange with a string consisting of tone at the
             RTCDTMFSender object.
    */
-  test_tone_change_events(dtmfSender => {
+  test_tone_change_events((t, dtmfSender) => {
     dtmfSender.insertDTMF('123');
   }, [
     ['1', '23', 0],
@@ -59,7 +59,7 @@
     ['', '', 170]
   ], 'insertDTMF() with default duration and intertoneGap should fire tonechange events at the expected time');
 
-  test_tone_change_events(dtmfSender => {
+  test_tone_change_events((t, dtmfSender) => {
     dtmfSender.insertDTMF('abc', 100, 70);
   }, [
     ['A', 'BC', 0],
@@ -282,4 +282,15 @@
 
   }, `Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing`);
 
+  /* Section 7.3 - Tone change event */
+  test(t => {
+    let ev = new RTCDTMFToneChangeEvent('tonechange', {'tone': '1'});
+    assert_equals(ev.type, 'tonechange');
+    assert_equals(ev.tone, '1');
+  }, 'Tone change event constructor works');
+
+  test(t => {
+    let ev = new RTCDTMFToneChangeEvent('worngname', {});
+  }, 'Tone change event with unexpected name should not crash');
+
 </script>
diff --git a/webrtc/RTCIceTransport.html b/webrtc/RTCIceTransport.html
index 57ecf07..9163285 100644
--- a/webrtc/RTCIceTransport.html
+++ b/webrtc/RTCIceTransport.html
@@ -118,7 +118,7 @@
     validateCandidateParameter(iceTransport.getRemoteParameters());
   }
 
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
 
@@ -163,7 +163,7 @@
     });
   }, 'Two connected iceTransports should has matching local/remote candidates returned');
 
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
     pc1.createDataChannel('');
diff --git a/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html b/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html
new file mode 100644
index 0000000..0bf58d9
--- /dev/null
+++ b/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection addTrack does not deadlock</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  // This test sets up two peer connections using a sequence of operations
+  // that triggered a deadlock in Chrome. See https://crbug.com/736725.
+  // If a deadlock is introduced again, this test times out.
+  promise_test(async t => {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    const stream = await navigator.mediaDevices.getUserMedia(
+      {audio: false, video: true});
+    const videoTrack = stream.getVideoTracks()[0];
+    t.add_cleanup(() => videoTrack.stop());
+    pc1.addTrack(videoTrack, stream);
+    const offer = await pc1.createOffer();
+    await pc1.setLocalDescription(offer);
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
+    const srdPromise = pc2.setRemoteDescription(offer);
+    pc2.addTrack(videoTrack, stream);
+    // The deadlock encountered in https://crbug.com/736725 occured here.
+    await srdPromise;
+    await pc2.createAnswer();
+  }, 'RTCPeerConnection addTrack does not deadlock.');
+</script>
diff --git a/webrtc/RTCPeerConnection-canTrickleIceCandidates.html b/webrtc/RTCPeerConnection-canTrickleIceCandidates.html
index 63dac8f..38c8b63 100644
--- a/webrtc/RTCPeerConnection-canTrickleIceCandidates.html
+++ b/webrtc/RTCPeerConnection-canTrickleIceCandidates.html
@@ -35,7 +35,7 @@
     assert_equals(pc.canTrickleIceCandidates, null, 'canTrickleIceCandidates property is null');
   }, 'canTrickleIceCandidates property is null prior to setRemoteDescription');
 
-  promise_test(function() {
+  promise_test(function(t) {
     var pc = new RTCPeerConnection();
 
     return pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp}))
@@ -44,7 +44,7 @@
     })
   }, 'canTrickleIceCandidates property is true after setRemoteDescription with a=ice-options:trickle');
 
-  promise_test(function() {
+  promise_test(function(t) {
     var pc = new RTCPeerConnection();
 
     return pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.replace('a=ice-options:trickle\r\n', '')}))
diff --git a/webrtc/RTCPeerConnection-createOffer-offerToReceive.html b/webrtc/RTCPeerConnection-createOffer-offerToReceive.html
new file mode 100644
index 0000000..c599b1c
--- /dev/null
+++ b/webrtc/RTCPeerConnection-createOffer-offerToReceive.html
@@ -0,0 +1,169 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test legacy offerToReceiveAudio/Video options</title>
+<link rel="help" href="https://w3c.github.io/webrtc-pc/#legacy-configuration-extensions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // Run some tests for both audio and video kinds
+  ['audio', 'video'].forEach((kind) => {
+    const capsKind = kind[0].toUpperCase() + kind.slice(1);
+
+    const offerToReceiveTrue = {};
+    offerToReceiveTrue[`offerToReceive${capsKind}`] = true;
+
+    const offerToReceiveFalse = {};
+    offerToReceiveFalse[`offerToReceive${capsKind}`] = false;
+
+    // Start testing
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+      const dummy = pc.createDataChannel('foo'); // Just to have something to offer
+
+      return pc.createOffer(offerToReceiveFalse)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 0,
+          'Expect pc to have no transceivers');
+      });
+    }, `createOffer() with offerToReceive${capsKind} set to false should not create a transceiver`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      return pc.createOffer(offerToReceiveTrue)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'recvonly',
+          'Expect transceiver to have "recvonly" direction');
+      });
+    }, `createOffer() with offerToReceive${capsKind} should create a "recvonly" transceiver`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      return pc.createOffer(offerToReceiveTrue)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'recvonly',
+          'Expect transceiver to have "recvonly" direction');
+      })
+      .then(() => pc.createOffer(offerToReceiveTrue))
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to still have only one transceiver');
+      })
+      ;
+    }, `offerToReceive${capsKind} option should be ignored if a non-stopped "recvonly" transceiver exists`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      return getTrackFromUserMedia(kind)
+      .then(([track, stream]) => {
+        pc.addTrack(track, stream);
+        return pc.createOffer();
+      })
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'sendrecv',
+          'Expect transceiver to have "sendrecv" direction');
+      })
+      .then(() => pc.createOffer(offerToReceiveTrue))
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to still have only one transceiver');
+      })
+      ;
+    }, `offerToReceive${capsKind} option should be ignored if a non-stopped "sendrecv" transceiver exists`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      return getTrackFromUserMedia(kind)
+      .then(([track, stream]) => {
+        pc.addTrack(track, stream);
+        return pc.createOffer(offerToReceiveFalse);
+      })
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'sendonly',
+          'Expect transceiver to have "sendonly" direction');
+      })
+      ;
+    }, `offerToReceive${capsKind} set to false with a track should create a "sendonly" transceiver`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      pc.addTransceiver(kind, {direction: 'recvonly'});
+
+      return pc.createOffer(offerToReceiveFalse)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'inactive',
+          'Expect transceiver to have "inactive" direction');
+      })
+      ;
+    }, `offerToReceive${capsKind} set to false with a "recvonly" transceiver should change the direction to "inactive"`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+      const pc2 = new RTCPeerConnection();
+
+      return getTrackFromUserMedia(kind)
+      .then(([track, stream]) => {
+        pc.addTrack(track, stream);
+        return pc.createOffer();
+      })
+      .then((offer) => pc.setLocalDescription(offer))
+      .then(() => pc2.setRemoteDescription(pc.localDescription))
+      .then(() => pc2.createAnswer())
+      .then((answer) => pc2.setLocalDescription(answer))
+      .then(() => pc.setRemoteDescription(pc2.localDescription))
+      .then(() => pc.createOffer(offerToReceiveFalse))
+      .then((offer) => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'sendonly',
+          'Expect transceiver to have "sendonly" direction');
+      })
+      ;
+    }, `subsequent offerToReceive${capsKind} set to false with a track should change the direction to "sendonly"`);
+  });
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: true })
+    .then(() => {
+      assert_equals(pc.getTransceivers().length, 2,
+        'Expect pc to have two transceivers');
+
+      assert_equals(pc.getTransceivers()[0].direction, 'recvonly',
+        'Expect first transceiver to have "recvonly" direction');
+      assert_equals(pc.getTransceivers()[1].direction, 'recvonly',
+        'Expect second transceiver to have "recvonly" direction');
+    });
+  }, 'offerToReceiveAudio and Video should create two "recvonly" transceivers');
+
+</script>
diff --git a/webrtc/RTCPeerConnection-getIdentityAssertion.html b/webrtc/RTCPeerConnection-getIdentityAssertion.html
index 2ecce83..d600816 100644
--- a/webrtc/RTCPeerConnection-getIdentityAssertion.html
+++ b/webrtc/RTCPeerConnection-getIdentityAssertion.html
@@ -36,7 +36,7 @@
         DOMString peerIdentity;
       };
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const port = window.location.port;
 
@@ -95,7 +95,7 @@
 
   // When generating assertion, the RTCPeerConnection doesn't care if the returned assertion
   // represents identity of different domain
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const port = window.location.port;
 
@@ -217,7 +217,7 @@
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
-    pc.setIdentityProvider('nonexistent-origin.web-platform.test', {
+    pc.setIdentityProvider('nonexistent.web-platform.test', {
       protocol: `non-existent`,
       usernameHint: `alice@example.org`,
     });
@@ -278,7 +278,7 @@
         value is provided for the peerIdentity member of RTCConfiguration, the value from
         RTCConfiguration is used.
   */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection({
       peerIdentity: 'bob@example.net'
     });
@@ -302,7 +302,7 @@
     9.6.  setIdentityProvider
       3.  If any identity provider value has changed, discard any stored identity assertion.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const port = window.location.port;
     const [idpDomain] = getIdpDomains();
diff --git a/webrtc/RTCPeerConnection-getStats.https.html b/webrtc/RTCPeerConnection-getStats.https.html
index a4df563..ac69ee5 100644
--- a/webrtc/RTCPeerConnection-getStats.https.html
+++ b/webrtc/RTCPeerConnection-getStats.https.html
@@ -10,8 +10,8 @@
   'use strict';
 
   // Test is based on the following editor draft:
-  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
-  // https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
+  // webrtc-pc 20171130
+  // webrtc-stats 20171122
 
   // The following helper function is called from RTCPeerConnection-helper.js
   //   getTrackFromUserMedia
@@ -20,54 +20,42 @@
   //   validateStatsReport
   //   assert_stats_report_has_stats
 
+  // The following helper function is called from RTCPeerConnection-helper.js
+  //   exchangeIceCandidates
+  //   doSignalingHandshake
+
   /*
-    8.2.  RTCPeerConnection Interface Extensions
-      partial interface RTCPeerConnection {
-        Promise<RTCStatsReport> getStats(optional MediaStreamTrack? selector = null);
-      };
-
-    8.3.  RTCStatsReport Object
-      interface RTCStatsReport {
-        readonly maplike<DOMString, object>;
-      };
-
-    8.4.  RTCStats Dictionary
-      dictionary RTCStats {
-        DOMHighResTimeStamp timestamp;
-        RTCStatsType        type;
-        DOMString           id;
-      };
-
-      id
-        Two RTCStats objects, extracted from two different RTCStatsReport objects, MUST
-        have the same id if they were produced by inspecting the same underlying object.
-
     8.2.  getStats
       1.  Let selectorArg be the method's first argument.
       2.  Let connection be the RTCPeerConnection object on which the method was invoked.
-      3.  If selectorArg is neither null nor a valid MediaStreamTrack, return a promise
-          rejected with a newly created TypeError.
+      3.  If selectorArg is null, let selector be null.
+      4.  If selectorArg is a MediaStreamTrack let selector be an RTCRtpSender
+          or RTCRtpReceiver on connection which track member matches selectorArg.
+          If no such sender or receiver exists, or if more than one sender or
+          receiver fit this criteria, return a promise rejected with a newly
+          created InvalidAccessError.
       5.  Let p be a new promise.
       6.  Run the following steps in parallel:
         1.  Gather the stats indicated by selector according to the stats selection algorithm.
         2.  Resolve p with the resulting RTCStatsReport object, containing the gathered stats.
    */
-
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     return pc.getStats();
   }, 'getStats() with no argument should succeed');
 
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     return pc.getStats(null);
   }, 'getStats(null) should succeed');
 
   /*
     8.2.  getStats
-      4.  Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track
-          member matches selectorArg. If no such sender or receiver exists, return a promise
-          rejected with a newly created InvalidAccessError.
+      4.  If selectorArg is a MediaStreamTrack let selector be an RTCRtpSender
+          or RTCRtpReceiver on connection which track member matches selectorArg.
+          If no such sender or receiver exists, or if more than one sender or
+          receiver fit this criteria, return a promise rejected with a newly
+          created InvalidAccessError.
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
@@ -94,12 +82,6 @@
     return pc.getStats(track);
   }, 'getStats() with track added via addTransceiver should succeed');
 
-  /*
-    8.2.  getStats
-      4.  Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track
-          member matches selectorArg. If more than one sender or receiver fit this criteria,
-          return a promise rejected with a newly created InvalidAccessError.
-   */
   promise_test(t => {
     const pc = new RTCPeerConnection();
     return getTrackFromUserMedia('audio')
@@ -140,7 +122,35 @@
       validateStatsReport(statsReport);
       assert_stats_report_has_stats(statsReport, ['peer-connection']);
     });
-  }, 'getStats() with no argument should return stats report containing peer-connection stats');
+  }, 'getStats() with no argument should return stats report containing peer-connection stats on an empty PC');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    return getTrackFromUserMedia('audio')
+    .then(([track, mediaStream]) => {
+      pc.addTrack(track, mediaStream);
+      return pc.getStats();
+    })
+    .then(statsReport => {
+      validateStatsReport(statsReport);
+      assert_stats_report_has_stats(statsReport, ['peer-connection']);
+      assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
+    });
+  }, 'getStats() with no argument should return stats report containing peer-connection stats and outbound-track-stats');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    return getTrackFromUserMedia('audio')
+    .then(([track, mediaStream]) => {
+      pc.addTrack(track);
+      return pc.getStats();
+    })
+    .then(statsReport => {
+      validateStatsReport(statsReport);
+      assert_stats_report_has_stats(statsReport, ['peer-connection']);
+      assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
+    });
+  }, 'getStats() with no argument should return stats for no-stream tracks');
 
   /*
     8.5.  The stats selection algorithm
@@ -150,7 +160,7 @@
         - All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
           objects added.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     return getTrackFromUserMedia('audio')
     .then(([track, mediaStream]) => {
@@ -173,7 +183,7 @@
         - All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
           added.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const transceiver = pc.addTransceiver('audio');
 
@@ -184,4 +194,150 @@
     });
   }, `getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats`);
 
+  /*
+    8.6   Mandatory To Implement Stats
+      An implementation MUST support generating statistics of the following types
+      when the corresponding objects exist on a PeerConnection, with the attributes
+      that are listed when they are valid for that object.
+   */
+
+  const mandatoryStats = [
+    "codec",
+    "inbound-rtp",
+    "outbound-rtp",
+    "remote-inbound-rtp",
+    "remote-outbound-rtp",
+    "peer-connection",
+    "data-channel",
+    "stream",
+    "track",
+    "transport",
+    "candidate-pair",
+    "local-candidate",
+    "remote-candidate",
+    "certificate"
+  ];
+
+  async_test(t => {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
+
+    const dataChannel = pc1.createDataChannel('test-channel');
+
+    return navigator.mediaDevices.getUserMedia({
+      audio: true,
+      video: true
+    })
+    .then(t.step_func(mediaStream => {
+      const tracks = mediaStream.getTracks();
+      assert_equals(tracks.length, 2,
+        'Expect media stream to have one audio and one video track');
+
+      let audioTrack;
+      let videoTrack;
+
+      for (const track of tracks) {
+        t.add_cleanup(() => track.stop());
+
+        pc1.addTrack(track, mediaStream);
+
+        if (track.kind === 'audio') {
+          audioTrack = track;
+        } else if (track.kind === 'video') {
+          videoTrack = track;
+        }
+      }
+
+      if (!audioTrack || ! videoTrack) {
+        assert_unreached('Expect mediaStream to have both audio and video streams');
+      }
+
+      const testStatsReport = (pc, statsReport) => {
+        validateStatsReport(statsReport);
+        assert_stats_report_has_stats(statsReport, mandatoryStats);
+
+        const dataChannelStats = findStatsFromReport(statsReport,
+          stats => {
+            return stats.type === 'data-channel' &&
+              stats.dataChannelIdentifier === dataChannel.id;
+          },
+          'Expect data channel stats to be found');
+
+        assert_equals(dataChannelStats.label, 'test-channel');
+
+        const audioTrackStats = findStatsFromReport(statsReport,
+          stats => {
+            return stats.type === 'track' &&
+              stats.trackIdentifier === audioTrack.id;
+          },
+          'Expect audio track stats to be found');
+
+        assert_equals(audioTrackStats.kind, 'audio');
+
+        const videoTrackStats = findStatsFromReport(statsReport,
+          stats => {
+            return stats.type === 'track' &&
+              stats.trackIdentifier === videoTrack.id;
+          },
+          'Expect video track stats to be found');
+
+        assert_equals(videoTrackStats.kind, 'video');
+
+        const mediaStreamStats = findStatsFromReport(statsReport,
+          stats => {
+            return stats.type === 'stream' &&
+              stats.streamIdentifier === mediaStream.id;
+          },
+          'Expect media stream stats to be found');
+
+        assert_true(mediaStreamStats.trackIds.include(audioTrackStats.id));
+        assert_true(mediaStreamStats.trackIds.include(videoTrackStats.id));
+      }
+
+      const onConnected = t.step_func(() => {
+        // Wait a while for the peer connections to collect stats
+        t.step_timeout(() => {
+          Promise.all([
+            pc1.getStats()
+            .then(statsReport => testStatsReport(pc1, statsReport)),
+
+            pc2.getStats()
+            .then(statsReport => testStatsReport(pc2, statsReport))
+          ])
+          .then(t.step_func_done())
+          .catch(t.step_func(err => {
+            assert_unreached(`test failed with error: ${err}`);
+          }));
+        }, 200)
+      })
+
+      let onTrackCount = 0
+      let onDataChannelCalled = false
+
+      pc2.addEventListener('track', t.step_func(() => {
+        onTrackCount++;
+        if (onTrackCount === 2 && onDataChannelCalled) {
+          onConnected();
+        }
+      }));
+
+      pc2.addEventListener('datachannel', t.step_func(() => {
+        onDataChannelCalled = true;
+        if (onTrackCount === 2) {
+          onConnected();
+        }
+      }));
+
+
+      exchangeIceCandidates(pc1, pc2);
+      doSignalingHandshake(pc1, pc2);
+    }))
+    .catch(t.step_func(err => {
+      assert_unreached(`test failed with error: ${err}`);
+    }));
+
+  }, `getStats() with connected peer connections having tracks and data channel should return all mandatory to implement stats`);
+
 </script>
diff --git a/webrtc/RTCPeerConnection-peerIdentity.html b/webrtc/RTCPeerConnection-peerIdentity.html
index d5f9db9..2d93880 100644
--- a/webrtc/RTCPeerConnection-peerIdentity.html
+++ b/webrtc/RTCPeerConnection-peerIdentity.html
@@ -54,7 +54,7 @@
       is, there is a current value for peerIdentity ), then this also establishes a
       target peer identity.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
 
diff --git a/webrtc/RTCPeerConnection-setDescription-transceiver.html b/webrtc/RTCPeerConnection-setDescription-transceiver.html
index ed41d66..d19b474 100644
--- a/webrtc/RTCPeerConnection-setDescription-transceiver.html
+++ b/webrtc/RTCPeerConnection-setDescription-transceiver.html
@@ -64,7 +64,7 @@
         2.  Set transceiver's mid value to the mid of the corresponding media
             description.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const transceiver = pc.addTransceiver('audio');
     assert_equals(transceiver.mid, null);
@@ -97,7 +97,7 @@
               transceiver be the result.
         3.  Set transceiver's mid value to the mid of the corresponding media description.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
 
@@ -109,7 +109,7 @@
     .then(offer => {
       return Promise.all([
         pc1.setLocalDescription(offer),
-        pc2.setRemoteDescrption(offer)
+        pc2.setRemoteDescription(offer)
       ])
       .then(() => {
         const transceivers = pc2.getTransceivers();
@@ -137,7 +137,7 @@
             the RTCSessionDescription that is being rolled back, set the mid value
             of that transceiver to null, as described by [JSEP] (section 4.1.8.2.).
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const transceiver = pc.addTransceiver('audio');
     assert_equals(transceiver.mid, null);
@@ -157,7 +157,7 @@
     });
   }, 'setLocalDescription(rollback) should unset transceiver.mid');
 
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const transceiver1 = pc.addTransceiver('audio');
     assert_equals(transceiver1.mid, null);
@@ -202,7 +202,7 @@
             addTrack, remove that transceiver from connection's set of transceivers,
             as described by [JSEP] (section 4.1.8.2.).
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
 
diff --git a/webrtc/RTCPeerConnection-setLocalDescription-offer.html b/webrtc/RTCPeerConnection-setLocalDescription-offer.html
index 5de04e9..76df63a 100644
--- a/webrtc/RTCPeerConnection-setLocalDescription-offer.html
+++ b/webrtc/RTCPeerConnection-setLocalDescription-offer.html
@@ -99,11 +99,12 @@
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
 
     return generateOffer({ pc, data: true })
     .then(offer =>
       promise_rejects(t, 'InvalidModificationError',
-        pc.setLocalDescription(offer)));
+        pc2.setLocalDescription(offer)));
   }, 'setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError');
 
   promise_test(t => {
diff --git a/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html b/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html
new file mode 100644
index 0000000..06408d0
--- /dev/null
+++ b/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html
@@ -0,0 +1,139 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.setRemoteDescription - replaceTrack</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // The following helper functions are called from RTCPeerConnection-helper.js:
+  //   getUserMediaTracksAndStreams
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      return sender.replaceTrack(tracks[1])
+      .then(t.step_func(() => {
+        assert_equals(sender.track, tracks[1]);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() sets the track attribute to a new track.');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      return sender.replaceTrack(null)
+      .then(t.step_func(() => {
+        assert_equals(sender.track, null);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() sets the track attribute to null.');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      assert_equals(sender.track, tracks[0]);
+      sender.replaceTrack(tracks[1]);
+      // replaceTrack() is asynchronous, there should be no synchronously
+      // observable effects.
+      assert_equals(sender.track, tracks[0]);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() does not set the track synchronously.');
+
+  async_test(t => {
+    const expectedException = 'InvalidStateError';
+    const caller = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      caller.close();
+      return sender.replaceTrack(tracks[1])
+      .then(t.step_func(() => {
+        assert_unreached('Expected replaceTrack() to be rejected with ' +
+                         expectedException + ' but the promise was resolved.');
+      }),
+      t.step_func(e => {
+        assert_equals(e.name, expectedException);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() rejects when the peer connection is closed.');
+
+  async_test(t => {
+    const expectedException = 'InvalidModificationError';
+    const caller = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      caller.removeTrack(sender);
+      // replaceTrack() should fail because the sender should be inactive after
+      // removeTrack().
+      return sender.replaceTrack(tracks[1])
+      .then(t.step_func(() => {
+        assert_unreached('Expected replaceTrack() to be rejected with ' +
+                         expectedException + ' but the promise was resolved.');
+      }),
+      t.step_func(e => {
+        assert_equals(e.name, expectedException);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() rejects when invoked after removeTrack().');
+
+  async_test(t => {
+    const expectedException = 'InvalidModificationError';
+    const caller = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      let p = sender.replaceTrack(tracks[1])
+      caller.removeTrack(sender);
+      // replaceTrack() should fail because it executes steps in parallel and
+      // queues a task to execute after removeTrack() has occurred. The sender
+      // should be inactive. If this can be racy, update or remove the test.
+      // https://github.com/w3c/webrtc-pc/issues/1728
+      return p.then(t.step_func(() => {
+        assert_unreached('Expected replaceTrack() to be rejected with ' +
+                         expectedException + ' but the promise was resolved.');
+      }),
+      t.step_func(e => {
+        assert_equals(e.name, expectedException);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() rejects after a subsequent removeTrack().');
+
+  // TODO(hbos): Verify that replaceTrack() changes what media is received on
+  // the remote end of two connected peer connections. For video tracks, this
+  // requires Chromium's video tag to update on receiving frames when running
+  // content_shell. https://crbug.com/793808
+
+</script>
diff --git a/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html b/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html
index f530e94..d4663e5 100644
--- a/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html
+++ b/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html
@@ -24,11 +24,11 @@
     const callee = new RTCPeerConnection();
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
-      let localTrack = tracks[0];
+      const localTrack = tracks[0];
       caller.addTrack(localTrack);
-      let offerPromise = performOffer(caller, callee);
+      const offerPromise = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
-        let remoteTrack = trackEvent.track;
+        const remoteTrack = trackEvent.track;
         assert_equals(remoteTrack.id, localTrack.id,
                       'Expected local and remote track IDs to match.');
         assert_equals(trackEvent.streams.length, 0,
@@ -47,15 +47,15 @@
     const callee = new RTCPeerConnection();
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
-      let localTrack = tracks[0];
-      let localStream = streams[0];
+      const localTrack = tracks[0];
+      const localStream = streams[0];
       caller.addTrack(localTrack, localStream);
-      let offerPromise = performOffer(caller, callee);
+      const offerPromise = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
         assert_equals(trackEvent.streams.length, 1,
                       'Expected track event to fire with a single stream.');
-        let remoteTrack = trackEvent.track;
-        let remoteStream = trackEvent.streams[0];
+        const remoteTrack = trackEvent.track;
+        const remoteStream = trackEvent.streams[0];
         assert_equals(remoteTrack.id, localTrack.id,
                       'Expected local and remote track IDs to match.');
         assert_equals(remoteStream.id, localStream.id,
@@ -74,19 +74,47 @@
   async_test(t => {
     const caller = new RTCPeerConnection();
     const callee = new RTCPeerConnection();
+    let eventSequence = '';
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      const ontrackResolver = new Resolver();
+      callee.ontrack = () => {
+        eventSequence += 'ontrack;';
+        ontrackResolver.resolve();
+      }
+      caller.addTrack(tracks[0]);
+      return Promise.all([
+        ontrackResolver.promise,
+        performOffer(caller, callee).then(() => {
+          eventSequence += 'setRemoteDescription;';
+        })
+      ]);
+    }))
+    .then(t.step_func(() => {
+      assert_equals(eventSequence, 'ontrack;setRemoteDescription;');
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'ontrack fires before setRemoteDescription resolves.');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
     return getUserMediaTracksAndStreams(2)
     .then(t.step_func(([tracks, streams]) => {
-      let localTrack1 = tracks[0];
-      let localTrack2 = tracks[1];
-      let localStream = streams[0];
+      const localTrack1 = tracks[0];
+      const localTrack2 = tracks[1];
+      const localStream = streams[0];
       caller.addTrack(localTrack1, localStream);
       caller.addTrack(localTrack2, localStream);
-      let offerPromise = performOffer(caller, callee);
+      const offerPromise = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
         assert_equals(trackEvent.streams.length, 1,
                       'Expected track event to fire with a single stream.');
-        let remoteTrack1 = trackEvent.track;
-        let remoteStream = trackEvent.streams[0];
+        const remoteTrack1 = trackEvent.track;
+        const remoteStream = trackEvent.streams[0];
         assert_equals(remoteTrack1.id, localTrack1.id,
                       'Expected first remote track ID to match first local track ID.');
         assert_equals(remoteStream.getTracks().length, 2,
@@ -94,7 +122,7 @@
         callee.ontrack = t.step_func(trackEvent => {
           assert_equals(trackEvent.streams.length, 1,
                         'Expected track event to fire with a single stream.');
-          let remoteTrack2 = trackEvent.track;
+          const remoteTrack2 = trackEvent.track;
           assert_equals(trackEvent.streams[0], remoteStream,
                         'Expected both track events to fire with the same remote stream.');
           assert_equals(remoteTrack2.id, localTrack2.id,
@@ -151,17 +179,53 @@
   async_test(t => {
     const caller = new RTCPeerConnection();
     const callee = new RTCPeerConnection();
+    let eventSequence = '';
     return getUserMediaTracksAndStreams(2)
     .then(t.step_func(([tracks, streams]) => {
-      let localTrack = tracks[0];
-      let localStreams = streams;
+      const localTracks = tracks;
+      const localStream = streams[0];
+      caller.addTrack(localTracks[0], localStream);
+      const offerPromise = performOffer(caller, callee);
+      callee.ontrack = t.step_func(trackEvent => {
+        callee.ontrack = null;
+        const remoteStream = trackEvent.streams[0];
+        const onaddtrackResolver = new Resolver();
+        remoteStream.onaddtrack = () => {
+          eventSequence += 'stream.onaddtrack;';
+          onaddtrackResolver.resolve();
+        }
+        caller.addTrack(localTracks[1], localStream);
+        Promise.all([
+          onaddtrackResolver.promise,
+          performOffer(caller, callee).then(() => {
+            eventSequence += 'setRemoteDescription;';
+          })
+        ]).then(t.step_func(() => {
+          assert_equals(eventSequence, 'stream.onaddtrack;setRemoteDescription;');
+          t.done();
+        }));
+      });
+      return offerPromise;
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'stream.onaddtrack fires before setRemoteDescription resolves.');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const localTrack = tracks[0];
+      const localStreams = streams;
       caller.addTrack(localTrack, localStreams[0], localStreams[1]);
-      let performOffer = performOffer(caller, callee);
+      const performOffer = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
         assert_equals(trackEvent.streams.length, 2,
                       'Expected the track event to fire with two streams.');
-        let remoteTrack = trackEvent.track;
-        let remoteStreams = trackEvent.streams;
+        const remoteTrack = trackEvent.track;
+        const remoteStreams = trackEvent.streams;
         assert_equals(remoteTrack.id, localTrack.id,
                       'Expected local and remote track IDs to match.');
         assert_equals(remoteStreams[0].id, localStreams[0].id,
@@ -184,38 +248,10 @@
   async_test(t => {
     const caller = new RTCPeerConnection();
     const callee = new RTCPeerConnection();
-    let eventSequence = '';
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
       caller.addTrack(tracks[0]);
-      let ontrackResolver = new Resolver();
-      callee.ontrack = () => {
-        eventSequence += 'ontrack;';
-        ontrackResolver.resolve();
-      }
-      return Promise.all([
-        ontrackResolver.promise,
-        performOffer(caller, callee).then(() => {
-          eventSequence += 'setRemoteDescription;';
-        })
-      ]);
-    }))
-    .then(t.step_func(() => {
-      assert_equals(eventSequence, 'ontrack;setRemoteDescription;');
-      t.done();
-    }))
-    .catch(t.step_func(reason => {
-      assert_unreached(reason);
-    }));
-  }, 'ontrack fires before setRemoteDescription resolves.');
-
-  async_test(t => {
-    const caller = new RTCPeerConnection();
-    const callee = new RTCPeerConnection();
-    return getUserMediaTracksAndStreams(1)
-    .then(t.step_func(([tracks, streams]) => {
-      caller.addTrack(tracks[0]);
-      let offerPromise = performOffer(caller, callee);
+      const offerPromise = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
         assert_array_equals(callee.getReceivers(), [trackEvent.receiver]);
         t.done();
@@ -232,11 +268,11 @@
     const callee = new RTCPeerConnection();
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
-      let sender = caller.addTrack(tracks[0]);
-      assert_true(sender != null);
-      let offerPromise = performOffer(caller, callee);
+      const sender = caller.addTrack(tracks[0]);
+      assert_not_equals(sender, null);
+      const offerPromise = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
-        let receivers = callee.getReceivers();
+        const receivers = callee.getReceivers();
         assert_equals(receivers.length, 1,
                       'Expected getReceivers() to be the track event\'s receiver.');
         caller.removeTrack(sender);
@@ -259,9 +295,9 @@
     const callee = new RTCPeerConnection();
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
-      let sender = caller.addTrack(tracks[0], streams[0]);
-      assert_true(sender != null);
-      let offerPromise = performOffer(caller, callee);
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      assert_not_equals(sender, null);
+      const offerPromise = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
         assert_not_equals(trackEvent.track, null);
         assert_equals(trackEvent.streams.length, 1);
@@ -279,21 +315,55 @@
     .catch(t.step_func(reason => {
       assert_unreached(reason);
     }));
-  }, 'removeTrack() causes onremovetrack and the track to be removed from the stream.');
+  }, 'removeTrack() makes stream.onremovetrack fire and the track to be removed from the stream.');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let eventSequence = '';
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      assert_not_equals(sender, null);
+      const offerPromise = performOffer(caller, callee);
+      callee.ontrack = t.step_func(trackEvent => {
+        const remoteStream = trackEvent.streams[0];
+        const onremovetrackResolver = new Resolver();
+        remoteStream.onremovetrack = t.step_func(removeEvent => {
+          eventSequence += 'stream.onremovetrack;';
+          onremovetrackResolver.resolve();
+        });
+        caller.removeTrack(sender);
+        return Promise.all([
+          onremovetrackResolver.promise,
+          performOffer(caller, callee).then(() => {
+            eventSequence += 'setRemoteDescription;';
+          })
+        ]).then(t.step_func(() => {
+          assert_equals(eventSequence, 'stream.onremovetrack;setRemoteDescription;');
+          t.done();
+        }));
+      });
+      return offerPromise;
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'stream.onremovetrack fires before setRemoteDescription resolves.');
 
   async_test(t => {
     const caller = new RTCPeerConnection();
     const callee = new RTCPeerConnection();
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
-      let sender = caller.addTrack(tracks[0]);
-      assert_true(sender != null);
-      let offerPromise = performOffer(caller, callee);
+      const sender = caller.addTrack(tracks[0]);
+      assert_not_equals(sender, null);
+      const offerPromise = performOffer(caller, callee);
       callee.ontrack = t.step_func(trackEvent => {
-        assert_not_equals(trackEvent.track, null);
+        const remoteTrack = trackEvent.track;
         caller.removeTrack(sender);
         performOffer(caller, callee);
-        trackEvent.track.onmute = t.step_func(() => {
+        remoteTrack.onmute = t.step_func(() => {
           assert_true(trackEvent.track.muted);
           t.done();
         });
@@ -303,6 +373,54 @@
     .catch(t.step_func(reason => {
       assert_unreached(reason);
     }));
-  }, 'removeTrack() causes onmute and the track to be muted.');
+  }, 'removeTrack() makes track.onmute fire and the track to be muted.');
 
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let eventSequence = '';
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0]);
+      assert_not_equals(sender, null);
+      const offerPromise = performOffer(caller, callee);
+      callee.ontrack = t.step_func(trackEvent => {
+        const remoteTrack = trackEvent.track;
+        const onmuteResolver = new Resolver();
+        remoteTrack.onmute = t.step_func(() => {
+          eventSequence += 'track.onmute;';
+          onmuteResolver.resolve();
+        });
+        caller.removeTrack(sender);
+        return Promise.all([
+          onmuteResolver.promise,
+          performOffer(caller, callee).then(() => {
+            eventSequence += 'setRemoteDescription;';
+          })
+        ]).then(t.step_func(() => {
+          assert_equals(eventSequence, 'track.onmute;setRemoteDescription;');
+          t.done();
+        }));
+      });
+      return offerPromise;
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'track.onmute fires before setRemoteDescription resolves.');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = pc.addTrack(tracks[0]);
+      assert_not_equals(sender, null);
+      pc.removeTrack(sender);
+      pc.removeTrack(sender);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'removeTrack() twice is safe.');
 </script>
diff --git a/webrtc/RTCPeerConnection-setRemoteDescription.html b/webrtc/RTCPeerConnection-setRemoteDescription.html
index 2efd301..3ceafe0 100644
--- a/webrtc/RTCPeerConnection-setRemoteDescription.html
+++ b/webrtc/RTCPeerConnection-setRemoteDescription.html
@@ -11,11 +11,8 @@
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
-  //   generateOffer()
-  //   generateAnswer()
   //   assert_session_desc_not_equals()
   //   assert_session_desc_equals()
-  //   test_state_change_event()
 
   /*
     4.3.2.  Interface Definition
@@ -48,76 +45,101 @@
   /*
     4.6.1.  enum RTCSdpType
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
     // SDP is validated after WebIDL validation
-    return promise_rejects(t, new TypeError(),
-      pc.setRemoteDescription({
-        type: 'bogus',
-        sdp: 'bogus'
-      }));
+    try {
+      await pc.setRemoteDescription({ type: 'bogus', sdp: 'bogus' });
+      t.unreached_func("Should have rejected.");
+    } catch (e) {
+      assert_throws(new TypeError(), () => { throw e });
+    }
   }, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
     // SDP is validated after validating type
-    return promise_rejects(t, 'InvalidStateError',
-      pc.setRemoteDescription({
-        type: 'answer',
-        sdp: 'invalid'
-      }));
+    try {
+      await pc.setRemoteDescription({ type: 'answer', sdp: 'invalid' });
+      t.unreached_func("Should have rejected.");
+    } catch (e) {
+      assert_throws('InvalidStateError', () => { throw e });
+    }
   }, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError');
 
+  /* Dedicated signalingstate events test. */
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    t.add_cleanup(() => pc2.close());
+
+    let eventCount = 0;
+    const states = [
+      'stable', 'have-local-offer', 'stable', 'have-remote-offer', 'closed'
+    ];
+    pc.onsignalingstatechange = () =>
+        assert_equals(pc.signalingState, states[++eventCount]);
+
+    const assert_state = state => {
+      assert_equals(state, pc.signalingState);
+      assert_equals(state, states[eventCount]);
+    };
+
+    const offer = await pc.createOffer({ offerToReceiveAudio: true });
+    assert_state('stable');
+    await pc.setLocalDescription(offer);
+    assert_state('have-local-offer');
+    await pc2.setRemoteDescription(offer);
+    await pc2.setLocalDescription(await pc2.createAnswer());
+    await pc.setRemoteDescription(pc2.localDescription);
+    assert_state('stable');
+    await pc.setRemoteDescription(await pc2.createOffer());
+    assert_state('have-remote-offer');
+    pc.close();
+    assert_state('closed');
+  }, 'Negotiation should fire signalingsstate events');
+
   /* Operations after returning to stable state */
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    t.add_cleanup(() => pc2.close());
 
-    test_state_change_event(t, pc,
-      ['have-remote-offer', 'stable', 'have-remote-offer']);
-
-    return pc2.createOffer({ offerToReceiveAudio: true })
-    .then(offer1 =>
-      pc.setRemoteDescription(offer1)
-      .then(() => pc.createAnswer())
-      .then(answer => pc.setLocalDescription(answer))
-      .then(() => pc2.createOffer({ offerToReceiveVideo: true }))
-      .then(offer2 => {
-        return pc.setRemoteDescription(offer2)
-        .then(() => {
-          assert_equals(pc.signalingState, 'have-remote-offer');
-          assert_session_desc_not_equals(offer1, offer2);
-          assert_session_desc_equals(pc.remoteDescription, offer2);
-          assert_session_desc_equals(pc.currentRemoteDescription, offer1);
-          assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
-        });
-      }));
+    const offer1 = await pc2.createOffer({ offerToReceiveAudio: true });
+    await pc.setRemoteDescription(offer1);
+    await pc.setLocalDescription(await pc.createAnswer());
+    const offer2 = await pc2.createOffer({ offerToReceiveVideo: true });
+    await pc.setRemoteDescription(offer2);
+    assert_session_desc_not_equals(offer1, offer2);
+    assert_session_desc_equals(pc.remoteDescription, offer2);
+    assert_session_desc_equals(pc.currentRemoteDescription, offer1);
+    assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
   }, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    t.add_cleanup(() => pc2.close());
 
-    test_state_change_event(t, pc,
-       ['have-local-offer', 'stable', 'have-remote-offer']);
-
-    return pc.createOffer({ offerToReceiveAudio: true })
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(answer =>
-      pc.setRemoteDescription(answer)
-      .then(() => generateOffer({ pc, data: true }))
-      .then(offer =>
-        pc.setRemoteDescription(offer)
-        .then(() => {
-          assert_equals(pc.signalingState, 'have-remote-offer');
-          assert_session_desc_equals(pc.remoteDescription, offer);
-          assert_session_desc_equals(pc.currentRemoteDescription, answer);
-          assert_session_desc_equals(pc.pendingRemoteDescription, offer);
-        })));
+    const offer = await pc.createOffer({ offerToReceiveAudio: true });
+    await pc.setLocalDescription(offer);
+    await pc2.setRemoteDescription(offer);
+    const answer = await pc2.createAnswer();
+    await pc2.setLocalDescription(answer);
+    await pc.setRemoteDescription(answer);
+    await pc.setRemoteDescription(await pc2.createOffer());
+    assert_equals(pc.remoteDescription.sdp, pc.pendingRemoteDescription.sdp);
+    assert_session_desc_equals(pc.remoteDescription, offer);
+    assert_session_desc_equals(pc.currentRemoteDescription, answer);
   }, 'Switching role from offerer to answerer after going back to stable state should succeed');
 
   /*
diff --git a/webrtc/RTCPeerConnection-track-stats.https.html b/webrtc/RTCPeerConnection-track-stats.https.html
new file mode 100644
index 0000000..c21bd82
--- /dev/null
+++ b/webrtc/RTCPeerConnection-track-stats.https.html
@@ -0,0 +1,628 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.getStats</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="dictionary-helper.js"></script>
+<script src="RTCStats-helper.js"></script>
+<script>
+  'use strict';
+
+  // The following helper functions are called from RTCPeerConnection-helper.js:
+  //   doSignalingHandshake
+  //   getUserMediaTracksAndStreams
+
+  // The following helper functions are called from RTCStats-helper.js
+  // (depends on dictionary-helper.js):
+  //   validateRtcStats
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    let track;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      pc.addTrack(track);
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      assert_true(trackStats != null, 'Has stats for track');
+      // TODO(hbos): Here and elsewhere, validateRtcStats() only tests id,
+      // timestamp and type is correct type. Should validate based on stats type
+      // but it expects both audio and video members.
+      // https://github.com/w3c/web-platform-tests/issues/9010
+      validateRtcStats(report, trackStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() without setLocalDescription() yields track stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      let track = tracks[0];
+      stream = streams[0];
+      pc.addTrack(track, stream);
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(streamStats != null, 'Has stats for stream');
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() without setLocalDescription() yields media stream stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    let track;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      pc.addTrack(track);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      assert_true(trackStats != null, 'Has stats for track');
+      validateRtcStats(report, trackStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() with setLocalDescription() yields track stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      let track = tracks[0];
+      stream = streams[0];
+      pc.addTrack(track, stream);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(streamStats != null, 'Has stats for stream');
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() with setLocalDescription() yields media stream stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    let track;
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      stream = streams[0];
+      pc.addTrack(track, stream);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(trackStats != null && streamStats != null,
+                  'Has stats for track and stream');
+      assert_array_equals(streamStats.trackIds, [ trackStats.id ],
+                          'streamStats.trackIds == [ trackStats.id ]');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack(): Media stream stats references track stats');
+
+  // TODO(hbos): addStream() is legacy API not in the spec. Based on discussion
+  // whether to standardize in legacy section, consider removing this test or
+  // keeping it until addTrack() has wide support.
+  // https://github.com/w3c/webrtc-pc/issues/1705
+  // https://github.com/w3c/webrtc-pc/issues/1125
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    let track;
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      stream = streams[0];
+      stream.addTrack(track);
+      pc.addStream(stream);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(trackStats != null && streamStats != null,
+                  'Has stats for track and stream');
+      assert_array_equals(streamStats.trackIds, [ trackStats.id ],
+                          'streamStats.trackIds == [ trackStats.id ]');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'Legacy addStream(): Media stream stats references track stats');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let sendingTrack;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack = tracks[0];
+      caller.addTrack(sendingTrack);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack.id);
+      assert_true(trackStats != null, 'Has stats for sending track');
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, outboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'O/A exchange yields outbound RTP stream stats for sending track');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let receivingTrack;
+    callee.ontrack = trackEvent => {
+      assert_true(receivingTrack == undefined, 'ontrack has not fired before');
+      receivingTrack = trackEvent.track;
+    };
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      caller.addTrack(tracks[0]);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return callee.getStats();
+    }))
+    .then(t.step_func(report => {
+      assert_true(receivingTrack != null, 'Has a receiving track');
+      let trackStats = findStatsByTypeAndId(report, 'track', receivingTrack.id);
+      assert_true(trackStats != null, 'Has stats for receiving track');
+      let inboundStats = findStatsByTypeAndMember(report, 'inbound-rtp',
+                                                  'trackId', trackStats.id);
+      assert_true(inboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, inboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'O/A exchange yields inbound RTP stream stats for receiving track');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id);
+      assert_true(trackStats != null, 'Has stats for replaced track');
+      validateRtcStats(report, trackStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() before offer: new track attachment stats present');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return performOffer(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id);
+      assert_true(trackStats != null, 'Has stats for replaced track');
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, outboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() after offer, before answer: new track attachment stats ' +
+     'present');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id);
+      assert_true(trackStats != null, 'Has stats for replaced track');
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, outboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() after answer: new track attachment stats present');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack1.id);
+      assert_true(trackStats != null, 'Has stats for original track');
+      assert_true(trackStats.objectDeleted);
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats == null,
+                  'The outbound RTP stream should no longer reference the ' +
+                  'original attachment');
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack(): original track attachment stats present after replacing');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+    let receiver = caller.getReceivers()[0];
+
+    // Obtain inbound and outbound RTP stream stats on a full stats report.
+    let fullReport = await caller.getStats();
+    let outboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', sender.track.id);
+    let outboundStats = findStatsByTypeAndMember(
+        fullReport, 'outbound-rtp', 'trackId', outboundTrackStats.id);
+    assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+    let inboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', receiver.track.id);
+    let inboundStats = findStatsByTypeAndMember(
+        fullReport, 'inbound-rtp', 'trackId', inboundTrackStats.id);
+    assert_true(inboundStats != null, 'Has stats for inbound RTP stream');
+
+    // Perform stats selection algorithm with sender selector. The result should
+    // contain the outbound-rtp but not the inbound-rtp.
+    let senderReport = await sender.getStats();
+    assert_true(senderReport.has(outboundStats.id));
+    assert_false(senderReport.has(inboundStats.id));
+
+    // Validate the stats graph, ensuring all stats objects are reachable and
+    // valid from the outbound-rtp stats.
+    validateStatsGraph(senderReport, senderReport.get(outboundStats.id));
+    // Ensure that the stats graph contains some expected dictionaries.
+    assert_equals(findStatsOfType(senderReport, 'track').length, 1,
+        'senderReport should contain track stats');
+    assert_equals(findStatsOfType(senderReport, 'transport').length, 1,
+        'senderReport should contain transport stats');
+    assert_equals(findStatsOfType(senderReport, 'candidate-pair').length, 1,
+        'senderReport should contain candidate-pair stats');
+    assert_equals(findStatsOfType(senderReport, 'local-candidate').length, 1,
+        'senderReport should contain local-candidate stats');
+    assert_equals(findStatsOfType(senderReport, 'remote-candidate').length, 1,
+        'senderReport should contain remote-candidate stats');
+  }, 'RTCRtpSender.getStats() contains only outbound-rtp and related stats');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+    let receiver = caller.getReceivers()[0];
+
+    // Obtain inbound and outbound RTP stream stats on a full stats report.
+    let fullReport = await caller.getStats();
+    let outboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', sender.track.id);
+    let outboundStats = findStatsByTypeAndMember(
+        fullReport, 'outbound-rtp', 'trackId', outboundTrackStats.id);
+    assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+    let inboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', receiver.track.id);
+    let inboundStats = findStatsByTypeAndMember(
+        fullReport, 'inbound-rtp', 'trackId', inboundTrackStats.id);
+    assert_true(inboundStats != null, 'Has stats for inbound RTP stream');
+
+    // Perform stats selection algorithm with receiver selector. The result
+    // should contain the inbound-rtp but not the outbound-rtp.
+    let receiverReport = await receiver.getStats();
+    assert_true(receiverReport.has(inboundStats.id));
+    assert_false(receiverReport.has(outboundStats.id));
+
+    // Validate the stats graph, ensuring all stats objects are reachable and
+    // valid from the outbound-rtp stats.
+    validateStatsGraph(receiverReport, receiverReport.get(inboundStats.id));
+    // Ensure that the stats graph contains some expected dictionaries.
+    assert_equals(findStatsOfType(receiverReport, 'track').length, 1,
+        'receiverReport should contain track stats');
+    assert_equals(findStatsOfType(receiverReport, 'transport').length, 1,
+        'receiverReport should contain transport stats');
+    assert_equals(findStatsOfType(receiverReport, 'candidate-pair').length, 1,
+        'receiverReport should contain candidate-pair stats');
+    assert_equals(findStatsOfType(receiverReport, 'local-candidate').length, 1,
+        'receiverReport should contain local-candidate stats');
+    assert_equals(findStatsOfType(receiverReport, 'remote-candidate').length, 1,
+        'receiverReport should contain remote-candidate stats');
+  }, 'RTCRtpReceiver.getStats() contains only inbound-rtp and related stats');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+
+    let senderReport = await sender.getStats();
+    let trackReport = await caller.getStats(sender.track);
+
+    // Verify the same stats objects are returned but don't compare each
+    // individual metric because timestamps and counters could have gone up
+    // between the two getStats() calls.
+    senderReport.forEach(senderReportStat => {
+      assert_true(trackReport.has(senderReportStat.id));
+    });
+    trackReport.forEach(trackReportStat => {
+      assert_true(senderReport.has(trackReportStat.id));
+    });
+  }, 'RTCPeerConnection.getStats(sendingTrack) is the same as ' +
+     'RTCRtpSender.getStats()');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+    let receiver = caller.getReceivers()[0];
+
+    let receiverReport = await receiver.getStats();
+    let trackReport = await caller.getStats(receiver.track);
+
+    // Verify the same stats objects are returned but don't compare each
+    // individual metric because timestamps and counters could have gone up
+    // between the two getStats() calls.
+    receiverReport.forEach(receiverReportStat => {
+      assert_true(trackReport.has(receiverReportStat.id));
+    });
+    trackReport.forEach(trackReportStat => {
+      assert_true(receiverReport.has(trackReportStat.id));
+    });
+  }, 'RTCPeerConnection.getStats(receivingTrack) is the same as ' +
+     'RTCRtpReceiver.getStats()');
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    let [tracks, streams] = await getUserMediaTracksAndStreams(1);
+    await promise_rejects(t, 'InvalidAccessError', pc.getStats(tracks[0]));
+  }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' +
+     'are zero senders or receivers for the track');
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender1 = pc.addTrack(tracks[0]);
+    let sender2 = pc.addTrack(tracks[1]);
+    await sender2.replaceTrack(sender1.track);
+    await promise_rejects(t, 'InvalidAccessError', pc.getStats(sender1.track));
+  }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' +
+     'are multiple senders for the track');
+
+  // Helpers.
+
+  function findStatsByTypeAndId(report, type, identifier) {
+    return findStats(report, stats => {
+      return stats.type == type && stats[type + 'Identifier'] == identifier;
+    });
+  }
+
+  function findStatsByTypeAndMember(report, type, member, value) {
+    return findStats(report, stats => {
+      return stats.type == type && stats[member] == value;
+    });
+  }
+
+  function findStats(report, findFunc) {
+    for (let it = report.values(), n = it.next(); !n.done; n = it.next()) {
+      if (findFunc(n.value))
+        return n.value;
+    }
+    return null;
+  }
+
+  function findStatsOfType(report, type) {
+    let stats = [];
+    for (let it = report.values(), n = it.next(); !n.done; n = it.next()) {
+      if (n.value.type == type)
+        stats.push(n.value);
+    }
+    return stats;
+  }
+
+  // Returns a promise that is resolved when pc.iceConnectionState reaches the
+  // 'connected' or 'completed' state. This is when transport stats can be
+  // expected to have its selectedCandidatePairId defined.
+  async function onIceConnectionStateCompleted(pc) {
+    if (pc.iceConnectionState == 'connected' ||
+        pc.iceConnectionState == 'completed') {
+      return Promise.resolve();
+    }
+    let resolver = new Resolver();
+    pc.oniceconnectionstatechange = e => {
+      if (pc.iceConnectionState == 'connected' ||
+          pc.iceConnectionState == 'completed') {
+        resolver.resolve();
+      }
+    };
+    return resolver.promise;
+  }
+
+  // Explores the stats graph starting from |stat|, validating each stat
+  // (validateRtcStats) and asserting that all stats of the report were visited.
+  function validateStatsGraph(report, stat) {
+    let visitedIds = new Set();
+    validateStatsGraphRecursively(report, stat.id, visitedIds);
+    assert_equals(visitedIds.size, report.size,
+                  'Entire stats graph should have been explored.')
+  }
+
+  function validateStatsGraphRecursively(report, currentId, visitedIds) {
+    if (visitedIds.has(currentId))
+      return;
+    visitedIds.add(currentId);
+    assert_true(report.has(currentId), 'Broken reference.');
+    let stat = report.get(currentId);
+    validateRtcStats(report, stat);
+    for (let member in stat) {
+      if (member.endsWith('Id')) {
+        validateStatsGraphRecursively(report, stat[member], visitedIds);
+      } else if (member.endsWith('Ids')) {
+        let ids = stat[member];
+        for (let i = 0; i < ids.length; ++i) {
+          validateStatsGraphRecursively(report, ids[i], visitedIds);
+        }
+      }
+    }
+  }
+
+  async function async_assert_throws(exceptionName, promise, description) {
+    try {
+      await promise;
+    } catch (e) {
+      assert_equals(e.name, exceptionName);
+      return;
+    }
+    assert_unreached('No exception was thrown.');
+  }
+
+</script>
diff --git a/webrtc/RTCRtpReceiver-getContributingSources.https.html b/webrtc/RTCRtpReceiver-getContributingSources.https.html
index de1e143..7ddc7e8 100644
--- a/webrtc/RTCRtpReceiver-getContributingSources.https.html
+++ b/webrtc/RTCRtpReceiver-getContributingSources.https.html
@@ -38,7 +38,7 @@
         possibly encode, and 127 represents silence.
    */
 
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
 
diff --git a/webrtc/RTCRtpReceiver-getStats.html b/webrtc/RTCRtpReceiver-getStats.html
deleted file mode 100644
index c30c961..0000000
--- a/webrtc/RTCRtpReceiver-getStats.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>RTCRtpReceiver.prototype.getStats</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="dictionary-helper.js"></script>
-<script src="RTCStats-helper.js"></script>
-<script>
-  'use strict';
-
-  // Test is based on the following editor draft:
-  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
-  // https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
-
-  // The following helper function is called from RTCStats-helper.js
-  //   validateStatsReport
-  //   assert_stats_report_has_stats
-
-  /*
-    5.3.  RTCRtpReceiver Interface
-      interface RTCRtpReceiver {
-         Promise<RTCStatsReport> getStats();
-          ...
-      };
-
-      getStats
-        1.  Let selector be the RTCRtpReceiver object on which the method was invoked.
-        2.  Let p be a new promise, and run the following steps in parallel:
-          1.  Gather the stats indicated by selector according to the stats selection
-              algorithm.
-          2.  Resolve p with the resulting RTCStatsReport object, containing the
-              gathered stats.
-        3.  Return p.
-
-    8.5. The stats selection algorithm
-      4.  If selector is an RTCRtpReceiver, gather stats for and add the following objects
-          to result:
-        - All RTCInboundRTPStreamStats objects corresponding to selector.
-        - All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
-          added.
-   */
-
-  promise_test(() => {
-    const pc = new RTCPeerConnection();
-    const { receiver } = pc.addTransceiver('audio');
-
-    return receiver.getStats()
-    .then(statsReport => {
-      validateStatsReport(statsReport);
-      assert_stats_report_has_stats(statsReport, ['inbound-rtp']);
-    });
-  }, 'receiver.getStats() should return stats report containing inbound-rtp stats');
-
-</script>
diff --git a/webrtc/RTCRtpReceiver-getStats.https.html b/webrtc/RTCRtpReceiver-getStats.https.html
new file mode 100644
index 0000000..4da0b0a
--- /dev/null
+++ b/webrtc/RTCRtpReceiver-getStats.https.html
@@ -0,0 +1,72 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCRtpReceiver.prototype.getStats</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="dictionary-helper.js"></script>
+<script src="RTCStats-helper.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+  // https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
+
+  // The following helper functions are called from RTCPeerConnection-helper.js:
+  //   doSignalingHandshake
+
+  // The following helper function is called from RTCStats-helper.js
+  //   validateStatsReport
+  //   assert_stats_report_has_stats
+
+  /*
+    5.3.  RTCRtpReceiver Interface
+      interface RTCRtpReceiver {
+         Promise<RTCStatsReport> getStats();
+          ...
+      };
+
+      getStats
+        1.  Let selector be the RTCRtpReceiver object on which the method was invoked.
+        2.  Let p be a new promise, and run the following steps in parallel:
+          1.  Gather the stats indicated by selector according to the stats selection
+              algorithm.
+          2.  Resolve p with the resulting RTCStatsReport object, containing the
+              gathered stats.
+        3.  Return p.
+
+    8.5. The stats selection algorithm
+      4.  If selector is an RTCRtpReceiver, gather stats for and add the following objects
+          to result:
+        - All RTCInboundRTPStreamStats objects corresponding to selector.
+        - All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
+          added.
+   */
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    const { receiver } = caller.addTransceiver('audio');
+
+    await doSignalingHandshake(caller, callee);
+    const statsReport = await receiver.getStats();
+    validateStatsReport(statsReport);
+    assert_stats_report_has_stats(statsReport, ['inbound-rtp']);
+  }, 'receiver.getStats() via addTransceiver should return stats report containing inbound-rtp stats');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    const stream = await navigator.mediaDevices.getUserMedia({audio:true});
+    const [track] = stream.getTracks();
+    caller.addTrack(track, stream);
+
+    await doSignalingHandshake(caller, callee);
+    const receiver = callee.getReceivers()[0];
+    const statsReport = await receiver.getStats();
+    validateStatsReport(statsReport);
+    assert_stats_report_has_stats(statsReport, ['inbound-rtp']);
+  }, 'receiver.getStats() via addTrack should return stats report containing inbound-rtp stats');
+
+</script>
diff --git a/webrtc/RTCRtpReceiver-getSynchronizationSources.https.html b/webrtc/RTCRtpReceiver-getSynchronizationSources.https.html
index 3494a91..236ec8b 100644
--- a/webrtc/RTCRtpReceiver-getSynchronizationSources.https.html
+++ b/webrtc/RTCRtpReceiver-getSynchronizationSources.https.html
@@ -25,7 +25,7 @@
       };
    */
 
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
 
diff --git a/webrtc/RTCRtpSender-getStats.html b/webrtc/RTCRtpSender-getStats.html
deleted file mode 100644
index 00aa680..0000000
--- a/webrtc/RTCRtpSender-getStats.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>RTCRtpSender.prototype.getStats</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="dictionary-helper.js"></script>
-<script src="RTCStats-helper.js"></script>
-<script>
-  'use strict';
-
-  // Test is based on the following editor draft:
-  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
-  // https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
-
-  // The following helper function is called from RTCStats-helper.js
-  //   validateStatsReport
-  //   assert_stats_report_has_stats
-
-  /*
-    5.2.  RTCRtpSender Interface
-      interface RTCRtpSender {
-         Promise<RTCStatsReport> getStats();
-          ...
-      };
-
-      getStats
-        1.  Let selector be the RTCRtpSender object on which the method was invoked.
-        2.  Let p be a new promise, and run the following steps in parallel:
-          1.  Gather the stats indicated by selector according to the stats selection
-              algorithm.
-          2.  Resolve p with the resulting RTCStatsReport object, containing the
-              gathered stats.
-        3.  Return p.
-
-    8.5. The stats selection algorithm
-      3.  If selector is an RTCRtpSender, gather stats for and add the following objects
-          to result:
-        - All RTCOutboundRTPStreamStats objects corresponding to selector.
-        - All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
-          objects added.
-   */
-
-  promise_test(() => {
-    const pc = new RTCPeerConnection();
-    const { sender } = pc.addTransceiver('audio');
-
-    return sender.getStats()
-    .then(statsReport => {
-      validateStatsReport(statsReport);
-      assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
-    });
-  }, 'sender.getStats() should return stats report containing outbound-rtp stats');
-
-</script>
diff --git a/webrtc/RTCRtpSender-getStats.https.html b/webrtc/RTCRtpSender-getStats.https.html
new file mode 100644
index 0000000..a20304d
--- /dev/null
+++ b/webrtc/RTCRtpSender-getStats.https.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCRtpSender.prototype.getStats</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="dictionary-helper.js"></script>
+<script src="RTCStats-helper.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // webrtc-pc 20171130
+  // webrtc-stats 20171122
+
+  // The following helper functions are called from RTCPeerConnection-helper.js:
+  //   doSignalingHandshake
+
+  // The following helper function is called from RTCStats-helper.js
+  //   validateStatsReport
+  //   assert_stats_report_has_stats
+
+  /*
+    5.2.  RTCRtpSender Interface
+      getStats
+        1.  Let selector be the RTCRtpSender object on which the method was invoked.
+        2.  Let p be a new promise, and run the following steps in parallel:
+          1.  Gather the stats indicated by selector according to the stats selection
+              algorithm.
+          2.  Resolve p with the resulting RTCStatsReport object, containing the
+              gathered stats.
+        3.  Return p.
+
+    8.5. The stats selection algorithm
+      3.  If selector is an RTCRtpSender, gather stats for and add the following objects
+          to result:
+        - All RTCOutboundRTPStreamStats objects corresponding to selector.
+        - All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
+          objects added.
+   */
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    const { sender } = caller.addTransceiver('audio');
+
+    await doSignalingHandshake(caller, callee);
+    const statsReport = await sender.getStats();
+    validateStatsReport(statsReport);
+    assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
+  }, 'sender.getStats() via addTransceiver should return stats report containing outbound-rtp stats');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    const callee = new RTCPeerConnection();
+    const stream = await navigator.mediaDevices.getUserMedia({audio:true});
+    const [track] = stream.getTracks();
+    const sender = caller.addTrack(track, stream);
+
+    await doSignalingHandshake(caller, callee);
+    const statsReport = await sender.getStats();
+    validateStatsReport(statsReport);
+    assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
+  }, 'sender.getStats() via addTrack should return stats report containing outbound-rtp stats');
+
+</script>
diff --git a/webrtc/RTCSctpTransport-maxMessageSize.html b/webrtc/RTCSctpTransport-maxMessageSize.html
new file mode 100644
index 0000000..d7f8362
--- /dev/null
+++ b/webrtc/RTCSctpTransport-maxMessageSize.html
@@ -0,0 +1,181 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCSctpTransport.prototype.maxMessageSize</title>
+<link rel="help" href="https://w3c.github.io/webrtc-pc/#rtcsctptransport-interface">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+// This test has an assert_unreached() that requires that the variable
+// canSendSize (initiated below) is greater than 2, if non-zero. The reason
+// is that we need two non-zero values that are less that that value for
+// testing with predictable results. This is a bit unfortunate but shouldn't
+// have any practical impact.
+
+// Helper class to read SDP attributes and generate SDPs with modified attribute values
+class SDPAttributeHelper {
+  constructor(attrName, valueRegExpStr) {
+    this.attrName = attrName;
+    this.re = new RegExp(`^a=${attrName}:(${valueRegExpStr})\\r\\n`, 'm');
+  }
+
+  getValue(sdp) {
+    const matches = sdp.match(this.re);
+    return matches ? matches[1] : null;
+  }
+
+  sdpWithValue(sdp, value) {
+    const matches = sdp.match(this.re);
+    const sdpParts = sdp.split(matches[0]);
+    const attributeLine = arguments.length > 1 ? `a=${this.attrName}:${value}\r\n` : '';
+    return `${sdpParts[0]}${attributeLine}${sdpParts[1]}`;
+  }
+
+  sdpWithoutAttribute(sdp) {
+    return this.sdpWithValue(sdp);
+  }
+}
+
+const mmsAttributeHelper = new SDPAttributeHelper('max-message-size', '\\d+');
+let canSendSize;
+const remoteValue1 = 1;
+const remoteValue2 = 2;
+
+promise_test(t => {
+  const pc = new RTCPeerConnection();
+  assert_equals(pc.sctp, null);
+  let maxMessageSize;
+
+  return generateOffer({ pc, data: true })
+  .then((offer) => {
+    assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null,
+      'SDP should have max-message-size attribute');
+
+    offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, 0) };
+    return pc.setRemoteDescription(offer);
+  })
+  .then(() => pc.createAnswer())
+  .then((answer) => pc.setLocalDescription(answer))
+  .then(() => {
+    assert_not_equals(pc.sctp, null);
+    canSendSize = pc.sctp.maxMessageSize == Number.POSITIVE_INFINITY ? 0 : pc.sctp.maxMessageSize;
+    if (canSendSize != 0 && canSendSize < remoteValue2) {
+      assert_unreached('This test needs two values that are less than canSendSize (unless it is zero)');
+    }
+  });
+}, 'Determine the local side send limitation (canSendSize) by offering a max-message-size of 0');
+
+promise_test(t => {
+  const pc = new RTCPeerConnection();
+  assert_equals(pc.sctp, null);
+
+  return generateOffer({ pc, data: true })
+  .then((offer) => {
+    assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null,
+      'SDP should have max-message-size attribute');
+
+    // Remove the max-message-size SDP attribute
+    offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithoutAttribute(offer.sdp) };
+    return pc.setRemoteDescription(offer)
+  })
+  .then(() => pc.createAnswer())
+  .then((answer) => pc.setLocalDescription(answer))
+  .then(() => {
+    assert_not_equals(pc.sctp, null);
+    // Test outcome depends on canSendSize value
+    if (canSendSize) {
+      assert_equals(pc.sctp.maxMessageSize, Math.min(65536, canSendSize),
+        'Missing SDP attribute and a non-zero canSendSize should give an maxMessageSize of min(65536, canSendSize)');
+    } else {
+      assert_equals(pc.sctp.maxMessageSize, 65536,
+        'Missing SDP attribute and a canSendSize of 0 should give an maxMessageSize of 65536');
+    }
+  });
+}, 'Remote offer SDP missing max-message-size attribute');
+
+promise_test(t => {
+  const pc = new RTCPeerConnection();
+  assert_equals(pc.sctp, null);
+
+  return generateOffer({ pc, data: true })
+  .then((offer) => {
+    assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null,
+      'SDP should have max-message-size attribute');
+
+    offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, remoteValue1) };
+    return pc.setRemoteDescription(offer);
+  })
+  .then(() => pc.createAnswer())
+  .then((answer) => pc.setLocalDescription(answer))
+  .then(() => {
+    assert_not_equals(pc.sctp, null);
+    assert_equals(pc.sctp.maxMessageSize, remoteValue1,
+      'maxMessageSize should be the value provided by the remote peer (as long as it is less than canSendSize)');
+  });
+}, 'max-message-size with a (non-zero) value provided by the remote peer');
+
+promise_test(t => {
+  const pc = new RTCPeerConnection();
+  assert_equals(pc.sctp, null);
+
+  return generateOffer({ pc, data: true })
+  .then((offer) => {
+    assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null,
+      'SDP should have max-message-size attribute');
+
+    offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, remoteValue1) };
+    return pc.setRemoteDescription(offer)
+  })
+  .then(() => pc.createAnswer())
+  .then((answer) => pc.setLocalDescription(answer))
+  .then(() => {
+    assert_not_equals(pc.sctp, null);
+    assert_equals(pc.sctp.maxMessageSize, remoteValue1,
+      'maxMessageSize should be the value provided by the remote peer (as long as it is less than canSendSize)');
+  })
+  .then(() => pc.createOffer()) // Start new O/A exchange that updates max-message-size
+  .then((offer) => {
+    offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, remoteValue2)};
+    return pc.setRemoteDescription(offer)
+  })
+  .then(() => pc.createAnswer())
+  .then((answer) => pc.setLocalDescription(answer))
+  .then(() => {
+    assert_not_equals(pc.sctp, null);
+    assert_equals(pc.sctp.maxMessageSize, remoteValue2,
+      'maxMessageSize should be the new value provided by the remote peer (as long as it is less than canSendSize)');
+  })
+  ;
+}, 'Renegotiate max-message-size with a (non-zero) value provided by the remote peer');
+
+promise_test(t => {
+  const pc = new RTCPeerConnection();
+  assert_equals(pc.sctp, null);
+  const largerThanCanSendSize = canSendSize + 1;
+
+  return generateOffer({ pc, data: true })
+  .then((offer) => {
+    assert_not_equals(mmsAttributeHelper.getValue(offer.sdp), null,
+      'SDP should have max-message-size attribute');
+
+    offer = { type: 'offer', sdp: mmsAttributeHelper.sdpWithValue(offer.sdp, largerThanCanSendSize) };
+    return pc.setRemoteDescription(offer)
+  })
+  .then(() => pc.createAnswer())
+  .then((answer) => pc.setLocalDescription(answer))
+  .then(() => {
+    assert_not_equals(pc.sctp, null);
+    // Test outcome depends on canSendSize value
+    if (canSendSize) {
+      assert_equals(pc.sctp.maxMessageSize, canSendSize,
+        'A remote value larger than a non-zero canSendSize should limit maxMessageSize to canSendSize');
+    } else {
+      assert_equals(pc.sctp.maxMessageSize, 65536,
+        'A canSendSize of zero should let the remote value set maxMessageSize');
+    }
+  });
+}, 'max-message-size with a (non-zero) value larger than canSendSize provided by the remote peer');
+
+</script>
diff --git a/webrtc/RTCStats-helper.js b/webrtc/RTCStats-helper.js
index 332ebb3..e91b40b 100644
--- a/webrtc/RTCStats-helper.js
+++ b/webrtc/RTCStats-helper.js
@@ -1,18 +1,12 @@
 'use strict';
 
 // Test is based on the following editor draft:
-// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
-// https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
-
+// webrtc-pc 20171130
+// webrtc-stats 20171122
 
 // This file depends on dictionary-helper.js which should
 // be loaded from the main HTML file.
 
-// To improve readability, the WebIDL definitions of the Stats
-// dictionaries are modified to annotate with required fields when
-// they are required by section 8.6 of webrtc-pc. ID fields are
-// also annotated with the stats type that they are linked to.
-
 /*
   [webrtc-stats]
   6.1.  RTCStatsType enum
@@ -82,6 +76,16 @@
   }
 }
 
+function findStatsFromReport(statsReport, predicate, message) {
+  for (const stats of statsReport.values()) {
+    if (predicate(stats)) {
+      return stats;
+    }
+  }
+
+  assert_unreached(message || 'none of stats in statsReport satisfy given condition')
+}
+
 // Get stats object of type that is expected to be
 // found in the statsReport
 function getRequiredStats(statsReport, type) {
@@ -132,7 +136,7 @@
     };
  */
 function validateRtcStats(statsReport, stats) {
-  assert_number_field(stats, 'timeStamp');
+  assert_number_field(stats, 'timestamp');
   assert_string_field(stats, 'type');
   assert_string_field(stats, 'id');
 }
@@ -141,35 +145,32 @@
   [webrtc-stats]
   7.1.  RTCRTPStreamStats dictionary
     dictionary RTCRTPStreamStats : RTCStats {
-      required  unsigned long      ssrc;
-      required  DOMString          mediaType;
-
-      [RTCMediaStreamTrackStats]
-      required  DOMString          trackId;
-
-      [RTCTransportStats]
-      required  DOMString          transportId;
-
-      [RTCCodecStats]
-      required  DOMString          codecId;
-
-                unsigned long      firCount;
-                unsigned long      pliCount;
-      required  unsigned long      nackCount;
-                unsigned long      sliCount;
-                unsigned long long qpSum;
+      unsigned long      ssrc;
+      DOMString          mediaType;
+      DOMString          trackId;
+      DOMString          transportId;
+      DOMString          codecId;
+      unsigned long      firCount;
+      unsigned long      pliCount;
+      unsigned long      nackCount;
+      unsigned long      sliCount;
+      unsigned long long qpSum;
     };
 
+    mediaType of type DOMString
+      Either "audio" or "video".
+
   [webrtc-pc]
   8.6.  Mandatory To Implement Stats
-    - RTCRTPStreamStats, with attributes ssrc, associateStatsId, isRemote, mediaType,
-      mediaTrackId, transportId, codecId, nackCount
+    - RTCRTPStreamStats, with attributes ssrc, mediaType, trackId,
+      transportId, codecId, nackCount
  */
 function validateRtpStreamStats(statsReport, stats) {
   validateRtcStats(statsReport, stats);
 
   assert_unsigned_int_field(stats, 'ssrc');
   assert_string_field(stats, 'mediaType');
+  assert_enum_field(stats, 'mediaType', ['audio', 'video'])
 
   validateIdField(statsReport, stats, 'trackId', 'track');
   validateIdField(statsReport, stats, 'transportId', 'transport');
@@ -177,7 +178,7 @@
 
   assert_optional_unsigned_int_field(stats, 'firCount');
   assert_optional_unsigned_int_field(stats, 'pliCount');
-  assert_unsigned_int_field(stats, 'nackCount');
+  assert_optional_unsigned_int_field(stats, 'nackCount');
   assert_optional_unsigned_int_field(stats, 'sliCount');
   assert_optional_unsigned_int_field(stats, 'qpSum');
 }
@@ -186,15 +187,12 @@
   [webrtc-stats]
   7.2.  RTCCodecStats dictionary
     dictionary RTCCodecStats : RTCStats {
-      required  unsigned long payloadType;
-      required  RTCCodecType  codecType;
-
-      [RTCTransportStats]
+      unsigned long payloadType;
+      RTCCodecType  codecType;
       DOMString     transportId;
-
       DOMString     mimeType;
-      required  unsigned long clockRate;
-      required  unsigned long channels;
+      unsigned long clockRate;
+      unsigned long channels;
       DOMString     sdpFmtpLine;
       DOMString     implementation;
     };
@@ -206,20 +204,20 @@
 
   [webrtc-pc]
   8.6.  Mandatory To Implement Stats
-    - RTCCodecStats, with attributes payloadType, codec, clockRate, channels, parameters
+    - RTCCodecStats, with attributes payloadType, codec, clockRate, channels, sdpFmtpLine
  */
 
 function validateCodecStats(statsReport, stats) {
   validateRtcStats(statsReport, stats);
 
   assert_unsigned_int_field(stats, 'payloadType');
-  assert_enum_field(stats, 'codecType', ['encode', 'decode']);
+  assert_optional_enum_field(stats, 'codecType', ['encode', 'decode']);
 
   validateOptionalIdField(statsReport, stats, 'transportId', 'transport');
 
   assert_optional_string_field(stats, 'mimeType');
   assert_unsigned_int_field(stats, 'clockRate');
-  assert_unsigned_int_field(stats, 'channels');
+  assert_optional_unsigned_int_field(stats, 'channels');
 
   assert_optional_string_field(stats, 'sdpFmtpLine');
   assert_optional_string_field(stats, 'implementation');
@@ -229,34 +227,42 @@
   [webrtc-stats]
   7.3.  RTCReceivedRTPStreamStats dictionary
     dictionary RTCReceivedRTPStreamStats : RTCRTPStreamStats {
-      unsigned long      packetsReceived;
-      unsigned long long bytesReceived;
-      unsigned long      packetsLost;
-      double             jitter;
-      double             fractionLost;
-      unsigned long      packetsDiscarded;
-      unsigned long      packetsRepaired;
-      unsigned long      burstPacketsLost;
-      unsigned long      burstPacketsDiscarded;
-      unsigned long      burstLossCount;
-      unsigned long      burstDiscardCount;
-      double             burstLossRate;
-      double             burstDiscardRate;
-      double             gapLossRate;
-      double             gapDiscardRate;
+        unsigned long      packetsReceived;
+        unsigned long long bytesReceived;
+        long               packetsLost;
+        double             jitter;
+        double             fractionLost;
+        unsigned long      packetsDiscarded;
+        unsigned long      packetsFailedDecryption;
+        unsigned long      packetsRepaired;
+        unsigned long      burstPacketsLost;
+        unsigned long      burstPacketsDiscarded;
+        unsigned long      burstLossCount;
+        unsigned long      burstDiscardCount;
+        double             burstLossRate;
+        double             burstDiscardRate;
+        double             gapLossRate;
+        double             gapDiscardRate;
     };
+
+    [webrtc-pc]
+    8.6.  Mandatory To Implement Stats
+      - RTCReceivedRTPStreamStats, with all required attributes from its
+        inherited dictionaries, and also attributes packetsReceived,
+        bytesReceived, packetsLost, jitter, packetsDiscarded
  */
 function validateReceivedRtpStreamStats(statsReport, stats) {
   validateRtpStreamStats(statsReport, stats);
 
-  assert_optional_unsigned_int_field(stats, 'packetsReceived');
-  assert_optional_unsigned_int_field(stats, 'bytesReceived');
-  assert_optional_unsigned_int_field(stats, 'packetsLost');
+  assert_unsigned_int_field(stats, 'packetsReceived');
+  assert_unsigned_int_field(stats, 'bytesReceived');
+  assert_unsigned_int_field(stats, 'packetsLost');
 
-  assert_optional_number_field(stats, 'jitter');
+  assert_number_field(stats, 'jitter');
   assert_optional_number_field(stats, 'fractionLost');
 
-  assert_optional_unsigned_int_field(stats, 'packetsDiscarded');
+  assert_unsigned_int_field(stats, 'packetsDiscarded');
+  assert_optional_unsigned_int_field(stats, 'packetsFailedDecryption');
   assert_optional_unsigned_int_field(stats, 'packetsRepaired');
   assert_optional_unsigned_int_field(stats, 'burstPacketsLost');
   assert_optional_unsigned_int_field(stats, 'burstPacketsDiscarded');
@@ -273,37 +279,21 @@
   [webrtc-stats]
   7.4.  RTCInboundRTPStreamStats dictionary
     dictionary RTCInboundRTPStreamStats : RTCReceivedRTPStreamStats {
-      required  unsigned long      packetsReceived;
-      required  unsigned long long bytesReceived;
-      required  unsigned long      packetsLost;
-      required  double             jitter;
-      required  unsigned long      packetsDiscarded;
-
-      [RTCRemoteOutboundRTPStreamStats]
       DOMString           remoteId;
-
       unsigned long       framesDecoded;
       DOMHighResTimeStamp lastPacketReceivedTimestamp;
     };
 
   [webrtc-pc]
   8.6.  Mandatory To Implement Stats
-    - RTCInboundRTPStreamStats, with all required attributes from RTCRTPStreamStats,
-      and also attributes packetsReceived, bytesReceived, packetsLost, jitter,
-      packetsDiscarded
+    - RTCInboundRTPStreamStats, with all required attributes from its inherited
+      dictionaries, and also attributes remoteId, framesDecoded
  */
 function validateInboundRtpStreamStats(statsReport, stats) {
   validateReceivedRtpStreamStats(statsReport, stats);
 
-  assert_unsigned_int_field(stats, 'packetsReceived');
-  assert_unsigned_int_field(stats, 'bytesReceived');
-  assert_unsigned_int_field(stats, 'packetsLost');
-  assert_number_field(stats, 'jitter');
-  assert_unsigned_int_field(stats, 'packetsDiscarded');
-
-  validateOptionalIdField(statsReport, stats, 'remoteId', 'remote-outbound-rtp');
-
-  assert_optional_unsigned_int_field(stats, 'framesDecoded');
+  validateIdField(statsReport, stats, 'remoteId', 'remote-outbound-rtp');
+  assert_unsigned_int_field(stats, 'framesDecoded');
   assert_optional_number_field(stats, 'lastPacketReceivedTimeStamp');
 }
 
@@ -311,18 +301,20 @@
   [webrtc-stats]
   7.5.  RTCRemoteInboundRTPStreamStats dictionary
     dictionary RTCRemoteInboundRTPStreamStats : RTCReceivedRTPStreamStats {
-      [RTCOutboundRTPStreamStats]
-      DOMString localId;
-
-      double    roundTripTime;
+        DOMString localId;
+        double    roundTripTime;
     };
- */
 
+  [webrtc-pc]
+  8.6.  Mandatory To Implement Stats
+    - RTCRemoteInboundRTPStreamStats, with all required attributes from its
+      inherited dictionaries, and also attributes localId, roundTripTime
+ */
 function validateRemoteInboundRtpStreamStats(statsReport, stats) {
   validateReceivedRtpStreamStats(statsReport, stats);
 
-  validateOptionalIdField(statsReport, stats, 'localId', 'outbound-rtp');
-  assert_optional_number_field(stats, 'roundTripTime');
+  validateIdField(statsReport, stats, 'localId', 'outbound-rtp');
+  assert_number_field(stats, 'roundTripTime');
 }
 
 /*
@@ -334,13 +326,18 @@
       unsigned long long bytesSent;
       unsigned long long bytesDiscardedOnSend;
     };
+
+    [webrtc-pc]
+    8.6.  Mandatory To Implement Stats
+      - RTCSentRTPStreamStats, with all required attributes from its inherited
+        dictionaries, and also attributes packetsSent, bytesSent
  */
 function validateSentRtpStreamStats(statsReport, stats) {
   validateRtpStreamStats(statsReport, stats);
 
-  assert_optional_unsigned_int_field(stats, 'packetsSent');
+  assert_unsigned_int_field(stats, 'packetsSent');
   assert_optional_unsigned_int_field(stats, 'packetsDiscardedOnSend');
-  assert_optional_unsigned_int_field(stats, 'bytesSent');
+  assert_unsigned_int_field(stats, 'bytesSent');
   assert_optional_unsigned_int_field(stats, 'bytesDiscardedOnSend');
 }
 
@@ -348,12 +345,7 @@
   [webrtc-stats]
   7.7.  RTCOutboundRTPStreamStats dictionary
     dictionary RTCOutboundRTPStreamStats : RTCSentRTPStreamStats {
-      required  unsigned long      packetsSent;
-      required  unsigned long long bytesSent;
-
-      [RTCRemoteInboundRTPStreamStats]
       DOMString           remoteId;
-
       DOMHighResTimeStamp lastPacketSentTimestamp;
       double              targetBitrate;
       unsigned long       framesEncoded;
@@ -361,20 +353,21 @@
       double              averageRTCPInterval;
     };
 
-  [webrtc-pc]
-  8.6.  Mandatory To Implement Stats
-    - RTCOutboundRTPStreamStats, with all required attributes from RTCRTPStreamStats,
-      and also attributes packetsSent, bytesSent, roundTripTime
+    [webrtc-pc]
+    8.6.  Mandatory To Implement Stats
+      - RTCOutboundRTPStreamStats, with all required attributes from its
+        inherited dictionaries, and also attributes remoteId, framesEncoded
  */
 function validateOutboundRtpStreamStats(statsReport, stats) {
-  validateOptionalIdField(statsReport, stats, 'remoteId', 'remote-inbound-rtp');
+  validateSentRtpStreamStats(statsReport, stats)
 
-  assert_unsigned_int_field(stats, 'packetsSent');
-  assert_unsigned_int_field(stats, 'bytesSent');
+  validateIdField(statsReport, stats, 'remoteId', 'remote-inbound-rtp');
 
   assert_optional_number_field(stats, 'lastPacketSentTimestamp');
   assert_optional_number_field(stats, 'targetBitrate');
-  assert_optional_unsigned_int_field(stats, 'framesEncoded');
+  if (stats['mediaType'] == 'video') {
+    assert_unsigned_int_field(stats, 'framesEncoded');
+  }
   assert_optional_number_field(stats, 'totalEncodeTime');
   assert_optional_number_field(stats, 'averageRTCPInterval');
 }
@@ -383,17 +376,20 @@
   [webrtc-stats]
   7.8.  RTCRemoteOutboundRTPStreamStats dictionary
     dictionary RTCRemoteOutboundRTPStreamStats : RTCSentRTPStreamStats {
-      [RTCInboundRTPStreamStats]
       DOMString           localId;
-
       DOMHighResTimeStamp remoteTimestamp;
     };
+
+  [webrtc-pc]
+  8.6.  Mandatory To Implement Stats
+    - RTCRemoteOutboundRTPStreamStats, with all required attributes from its
+      inherited dictionaries, and also attributes localId, remoteTimestamp
  */
 function validateRemoteOutboundRtpStreamStats(statsReport, stats) {
   validateSentRtpStreamStats(statsReport, stats);
 
-  validateOptionalIdField(statsReport, stats, 'localId', 'inbound-rtp');
-  assert_optional_number_field(stats, 'remoteTimeStamp');
+  validateIdField(statsReport, stats, 'localId', 'inbound-rtp');
+  assert_number_field(stats, 'remoteTimeStamp');
 }
 
 /*
@@ -401,10 +397,7 @@
   7.9.  RTCRTPContributingSourceStats
     dictionary RTCRTPContributingSourceStats : RTCStats {
       unsigned long contributorSsrc;
-
-      [RTCInboundRTPStreamStats]
       DOMString     inboundRtpStreamId;
-
       unsigned long packetsContributedTo;
       double        audioLevel;
     };
@@ -423,10 +416,10 @@
   [webrtc-stats]
   7.10. RTCPeerConnectionStats dictionary
     dictionary RTCPeerConnectionStats : RTCStats {
-      required  unsigned long dataChannelsOpened;
-      required  unsigned long dataChannelsClosed;
-                unsigned long dataChannelsRequested;
-                unsigned long dataChannelsAccepted;
+      unsigned long dataChannelsOpened;
+      unsigned long dataChannelsClosed;
+      unsigned long dataChannelsRequested;
+      unsigned long dataChannelsAccepted;
     };
 
   [webrtc-pc]
@@ -446,10 +439,8 @@
   [webrtc-stats]
   7.11. RTCMediaStreamStats dictionary
     dictionary RTCMediaStreamStats : RTCStats {
-      required  DOMString           streamIdentifier;
-
-      [RTCMediaStreamTrackStats]
-      required  sequence<DOMString> trackIds;
+      DOMString           streamIdentifier;
+      sequence<DOMString> trackIds;
     };
 
   [webrtc-pc]
@@ -479,35 +470,37 @@
   [webrtc-stats]
   7.12. RTCMediaStreamTrackStats dictionary
     dictionary RTCMediaStreamTrackStats : RTCStats {
-      required  DOMString           trackIdentifier;
-      required  boolean             remoteSource;
-      required  boolean             ended;
-      required  boolean             detached;
-                DOMString           kind;
-                DOMHighResTimeStamp estimatedPlayoutTimestamp;
-      required  unsigned long       frameWidth;
-      required  unsigned long       frameHeight;
-      required  double              framesPerSecond;
-                unsigned long       framesCaptured;
-      required  unsigned long       framesSent;
-      required  unsigned long       framesReceived;
-      required  unsigned long       framesDecoded;
-      required  unsigned long       framesDropped;
-      required  unsigned long       framesCorrupted;
-                unsigned long       partialFramesLost;
-                unsigned long       fullFramesLost;
-      required  double              audioLevel;
-                double              totalAudioEnergy;
-                boolean             voiceActivityFlag;
-                double              echoReturnLoss;
-                double              echoReturnLossEnhancement;
-                unsigned long long  totalSamplesSent;
-                unsigned long long  totalSamplesReceived;
-                double              totalSamplesDuration;
-                unsigned long long  concealedSamples;
-                unsigned long long  concealmentEvents;
-                double              jitterBufferDelay;
-                RTCPriorityType     priority;
+      DOMString           trackIdentifier;
+      boolean             remoteSource;
+      boolean             ended;
+      boolean             detached;
+      DOMString           kind;
+      DOMHighResTimeStamp estimatedPlayoutTimestamp;
+      unsigned long       frameWidth;
+      unsigned long       frameHeight;
+      double              framesPerSecond;
+      unsigned long       framesCaptured;
+      unsigned long       framesSent;
+      unsigned long       keyFramesSent;
+      unsigned long       framesReceived;
+      unsigned long       keyFramesReceived;
+      unsigned long       framesDecoded;
+      unsigned long       framesDropped;
+      unsigned long       framesCorrupted;
+      unsigned long       partialFramesLost;
+      unsigned long       fullFramesLost;
+      double              audioLevel;
+      double              totalAudioEnergy;
+      boolean             voiceActivityFlag;
+      double              echoReturnLoss;
+      double              echoReturnLossEnhancement;
+      unsigned long long  totalSamplesSent;
+      unsigned long long  totalSamplesReceived;
+      double              totalSamplesDuration;
+      unsigned long long  concealedSamples;
+      unsigned long long  concealmentEvents;
+      double              jitterBufferDelay;
+      RTCPriorityType     priority;
     };
 
   [webrtc-pc]
@@ -520,8 +513,8 @@
     };
 
   8.6.  Mandatory To Implement Stats
-    - RTCMediaStreamTrackStats, with attributes trackIdentifier, remoteSource, ended,
-      detached, ssrcIds, frameWidth, frameHeight, framesPerSecond, framesSent,
+    - RTCMediaStreamTrackStats, with attributes trackIdentifier, remoteSource,
+      ended, detached, frameWidth, frameHeight, framesPerSecond, framesSent,
       framesReceived, framesDecoded, framesDropped, framesCorrupted, audioLevel
  */
 
@@ -533,35 +526,40 @@
   assert_boolean_field(stats, 'ended');
   assert_boolean_field(stats, 'detached');
 
-  assert_optional_string_field(stats, 'kind');
+  assert_optional_enum_field(stats, 'kind', ['audio', 'video']);
   assert_optional_number_field(stats, 'estimatedPlayoutTimestamp');
+  if (stats['kind'] === 'video') {
+    assert_unsigned_int_field(stats, 'frameWidth');
+    assert_unsigned_int_field(stats, 'frameHeight');
+    assert_number_field(stats, 'framesPerSecond');
+    if (stats['framesSent']) {
+      assert_optional_unsigned_int_field(stats, 'framesCaptured');
+      assert_unsigned_int_field(stats, 'framesSent');
+      assert_optional_unsigned_int_field(stats, 'keyFramesSent');
+    } else {
+      assert_unsigned_int_field(stats, 'framesReceived');
+      assert_optional_unsigned_int_field(stats, 'keyFramesReceived');
+      assert_unsigned_int_field(stats, 'framesDecoded');
+      assert_unsigned_int_field(stats, 'framesDropped');
+      assert_unsigned_int_field(stats, 'framesCorrupted');
+    }
 
-  assert_unsigned_int_field(stats, 'frameWidth');
-  assert_unsigned_int_field(stats, 'frameHeight');
-  assert_number_field(stats, 'framesPerSecond');
+    assert_optional_unsigned_int_field(stats, 'partialFramesLost');
+    assert_optional_unsigned_int_field(stats, 'fullFramesLost');
+  } else {
+    assert_number_field(stats, 'audioLevel');
+    assert_optional_number_field(stats, 'totalAudioEnergy');
+    assert_optional_boolean_field(stats, 'voiceActivityFlag');
+    assert_optional_number_field(stats, 'echoReturnLoss');
+    assert_optional_number_field(stats, 'echoReturnLossEnhancement');
 
-  assert_optional_unsigned_int_field(stats, 'framesCaptured');
-  assert_unsigned_int_field(stats, 'frameSent');
-  assert_unsigned_int_field(stats, 'frameReceived');
-  assert_unsigned_int_field(stats, 'frameDecoded');
-  assert_unsigned_int_field(stats, 'frameDropped');
-  assert_unsigned_int_field(stats, 'frameCorrupted');
-
-  assert_optional_unsigned_int_field(stats, 'partialFramesLost');
-  assert_optional_unsigned_int_field(stats, 'fullFramesLost');
-
-  assert_number_field(stats, 'audioLevel');
-  assert_optional_number_field(stats, 'totalAudioEnergy');
-  assert_optional_boolean_field(stats, 'voiceActivityFlag');
-  assert_optional_number_field(stats, 'echoReturnLoss');
-  assert_optional_number_field(stats, 'echoReturnLossEnhancement');
-
-  assert_optional_unsigned_int_field(stats, 'totalSamplesSent');
-  assert_optional_unsigned_int_field(stats, 'totalSamplesReceived');
-  assert_optional_number_field(stats, 'totalSamplesDuration');
-  assert_optional_unsigned_int_field(stats, 'concealedSamples');
-  assert_optional_unsigned_int_field(stats, 'concealmentEvents');
-  assert_optional_number_field(stats, 'jitterBufferDelay');
+    assert_optional_unsigned_int_field(stats, 'totalSamplesSent');
+    assert_optional_unsigned_int_field(stats, 'totalSamplesReceived');
+    assert_optional_number_field(stats, 'totalSamplesDuration');
+    assert_optional_unsigned_int_field(stats, 'concealedSamples');
+    assert_optional_unsigned_int_field(stats, 'concealmentEvents');
+    assert_optional_number_field(stats, 'jitterBufferDelay');
+  }
 
   assert_optional_enum_field(stats, 'priority',
     ['very-low', 'low', 'medium', 'high']);
@@ -571,18 +569,15 @@
   [webrtc-stats]
   7.13. RTCDataChannelStats dictionary
     dictionary RTCDataChannelStats : RTCStats {
-      required  DOMString           label;
-      required  DOMString           protocol;
-      required  long                datachannelid;
-
-      [RTCTransportStats]
-                DOMString           transportId;
-
-      required  RTCDataChannelState state;
-      required  unsigned long       messagesSent;
-      required  unsigned long long  bytesSent;
-      required  unsigned long       messagesReceived;
-      required  unsigned long long  bytesReceived;
+      DOMString           label;
+      DOMString           protocol;
+      long                dataChannelIdentifier;
+      DOMString           transportId;
+      RTCDataChannelState state;
+      unsigned long       messagesSent;
+      unsigned long long  bytesSent;
+      unsigned long       messagesReceived;
+      unsigned long long  bytesReceived;
     };
 
   [webrtc-pc]
@@ -598,22 +593,19 @@
     - RTCDataChannelStats, with attributes label, protocol, datachannelId, state,
       messagesSent, bytesSent, messagesReceived, bytesReceived
  */
-
 function validateDataChannelStats(statsReport, stats) {
   validateRtcStats(statsReport, stats);
 
   assert_string_field(stats, 'label');
   assert_string_field(stats, 'protocol');
-  assert_int_field(stats, 'datachannelid');
+  assert_int_field(stats, 'dataChannelIdentifier');
 
   validateOptionalIdField(statsReport, stats, 'transportId', 'transport');
 
   assert_enum_field(stats, 'state',
     ['connecting', 'open', 'closing', 'closed']);
 
-  assert_unsigned_int_field(stats, 'messageSent');
-
-  assert_unsigned_int_field(stats, 'messageSent');
+  assert_unsigned_int_field(stats, 'messagesSent');
   assert_unsigned_int_field(stats, 'bytesSent');
   assert_unsigned_int_field(stats, 'messagesReceived');
   assert_unsigned_int_field(stats, 'bytesReceived');
@@ -623,25 +615,16 @@
   [webrtc-stats]
   7.14. RTCTransportStats dictionary
     dictionary RTCTransportStats : RTCStats {
-                unsigned long         packetsSent;
-                unsigned long         packetsReceived;
-      required  unsigned long long    bytesSent;
-      required  unsigned long long    bytesReceived;
-
-      [RTCTransportStats]
-      required  DOMString             rtcpTransportStatsId;
-
-                RTCIceRole            iceRole;
-                RTCDtlsTransportState dtlsState;
-
-      [RTCIceCandidatePairStats]
-      required  DOMString             selectedCandidatePairId;
-
-      [RTCCertificateStats]
-      required  DOMString             localCertificateId;
-
-      [RTCCertificateStats]
-      required  DOMString             remoteCertificateId;
+      unsigned long         packetsSent;
+      unsigned long         packetsReceived;
+      unsigned long long    bytesSent;
+      unsigned long long    bytesReceived;
+      DOMString             rtcpTransportStatsId;
+      RTCIceRole            iceRole;
+      RTCDtlsTransportState dtlsState;
+      DOMString             selectedCandidatePairId;
+      DOMString             localCertificateId;
+      DOMString             remoteCertificateId;
     };
 
   [webrtc-pc]
@@ -661,10 +644,10 @@
     };
 
   8.6.  Mandatory To Implement Stats
-    - RTCTransportStats, with attributes bytesSent, bytesReceived, rtcpTransportStatsId,
-      activeConnection, selectedCandidatePairId, localCertificateId, remoteCertificateId
+    - RTCTransportStats, with attributes bytesSent, bytesReceived,
+      rtcpTransportStatsId, selectedCandidatePairId, localCertificateId,
+      remoteCertificateId
  */
-
 function validateTransportStats(statsReport, stats) {
   validateRtcStats(statsReport, stats);
 
@@ -673,7 +656,8 @@
   assert_unsigned_int_field(stats, 'bytesSent');
   assert_unsigned_int_field(stats, 'bytesReceived');
 
-  validateIdField(statsReport, stats, 'rtcpTransportStatsId', 'transport');
+  validateOptionalIdField(statsReport, stats, 'rtcpTransportStatsId',
+                          'transport');
 
   assert_optional_enum_field(stats, 'iceRole',
     ['controlling', 'controlled']);
@@ -690,18 +674,27 @@
   [webrtc-stats]
   7.15. RTCIceCandidateStats dictionary
     dictionary RTCIceCandidateStats : RTCStats {
-      [RTCTransportStats]
-                DOMString           transportId;
+      DOMString           transportId;
+      boolean             isRemote;
+      RTCNetworkType      networkType;
+      DOMString           ip;
+      long                port;
+      DOMString           protocol;
+      RTCIceCandidateType candidateType;
+      long                priority;
+      DOMString           url;
+      DOMString           relayProtocol;
+      boolean             deleted = false;
+    };
 
-                boolean             isRemote;
-      required  DOMString           ip;
-      required  long                port;
-      required  DOMString           protocol;
-      required  RTCIceCandidateType candidateType;
-      required  long                priority;
-      required  DOMString           url;
-                DOMString           relayProtocol;
-                boolean             deleted = false;
+    enum RTCNetworkType {
+      "bluetooth",
+      "cellular",
+      "ethernet",
+      "wifi",
+      "wimax",
+      "vpn",
+      "unknown"
     };
 
   [webrtc-pc]
@@ -714,16 +707,18 @@
     };
 
   8.6.  Mandatory To Implement Stats
-    - RTCIceCandidateStats, with attributes ip, port, protocol, candidateType, priority,
-      url
+    - RTCIceCandidateStats, with attributes ip, port, protocol, candidateType,
+      priority, url
  */
-
 function validateIceCandidateStats(statsReport, stats) {
   validateRtcStats(statsReport, stats);
 
   validateOptionalIdField(statsReport, stats, 'transportId', 'transport');
   assert_optional_boolean_field(stats, 'isRemote');
 
+  assert_optional_enum_field(stats, 'networkType',
+    ['bluetooth', 'cellular', 'ethernet', 'wifi', 'wimax', 'vpn', 'unknown'])
+
   assert_string_field(stats, 'ip');
   assert_int_field(stats, 'port');
   assert_string_field(stats, 'protocol');
@@ -732,7 +727,7 @@
     ['host', 'srflx', 'prflx', 'relay']);
 
   assert_int_field(stats, 'priority');
-  assert_string_field(stats, 'url');
+  assert_optional_string_field(stats, 'url');
   assert_optional_string_field(stats, 'relayProtocol');
   assert_optional_boolean_field(stats, 'deleted');
 }
@@ -741,40 +736,34 @@
   [webrtc-stats]
   7.16. RTCIceCandidatePairStats dictionary
     dictionary RTCIceCandidatePairStats : RTCStats {
-      [RTCTransportStats]
-      required  DOMString                     transportId;
-
-      [RTCIceCandidateStats]
-      required  DOMString                     localCandidateId;
-
-      [RTCIceCandidateStats]
-      required  DOMString                     remoteCandidateId;
-
-      required  RTCStatsIceCandidatePairState state;
-      required  unsigned long long            priority;
-      required  boolean                       nominated;
-                unsigned long                 packetsSent;
-                unsigned long                 packetsReceived;
-      required  unsigned long long            bytesSent;
-      required  unsigned long long            bytesReceived;
-                DOMHighResTimeStamp           lastPacketSentTimestamp;
-                DOMHighResTimeStamp           lastPacketReceivedTimestamp;
-                DOMHighResTimeStamp           firstRequestTimestamp;
-                DOMHighResTimeStamp           lastRequestTimestamp;
-                DOMHighResTimeStamp           lastResponseTimestamp;
-      required  double                        totalRoundTripTime;
-      required  double                        currentRoundTripTime;
-                double                        availableOutgoingBitrate;
-                double                        availableIncomingBitrate;
-                unsigned long                 circuitBreakerTriggerCount;
-                unsigned long long            requestsReceived;
-                unsigned long long            requestsSent;
-                unsigned long long            responsesReceived;
-                unsigned long long            responsesSent;
-                unsigned long long            retransmissionsReceived;
-                unsigned long long            retransmissionsSent;
-                unsigned long long            consentRequestsSent;
-                DOMHighResTimeStamp           consentExpiredTimestamp;
+      DOMString                     transportId;
+      DOMString                     localCandidateId;
+      DOMString                     remoteCandidateId;
+      RTCStatsIceCandidatePairState state;
+      unsigned long long            priority;
+      boolean                       nominated;
+      unsigned long                 packetsSent;
+      unsigned long                 packetsReceived;
+      unsigned long long            bytesSent;
+      unsigned long long            bytesReceived;
+      DOMHighResTimeStamp           lastPacketSentTimestamp;
+      DOMHighResTimeStamp           lastPacketReceivedTimestamp;
+      DOMHighResTimeStamp           firstRequestTimestamp;
+      DOMHighResTimeStamp           lastRequestTimestamp;
+      DOMHighResTimeStamp           lastResponseTimestamp;
+      double                        totalRoundTripTime;
+      double                        currentRoundTripTime;
+      double                        availableOutgoingBitrate;
+      double                        availableIncomingBitrate;
+      unsigned long                 circuitBreakerTriggerCount;
+      unsigned long long            requestsReceived;
+      unsigned long long            requestsSent;
+      unsigned long long            responsesReceived;
+      unsigned long long            responsesSent;
+      unsigned long long            retransmissionsReceived;
+      unsigned long long            retransmissionsSent;
+      unsigned long long            consentRequestsSent;
+      DOMHighResTimeStamp           consentExpiredTimestamp;
     };
 
     enum RTCStatsIceCandidatePairState {
@@ -788,8 +777,7 @@
   [webrtc-pc]
   8.6.  Mandatory To Implement Stats
     - RTCIceCandidatePairStats, with attributes transportId, localCandidateId,
-      remoteCandidateId, state, priority, nominated, writable, readable, bytesSent,
-      bytesReceived, totalRtt, currentRtt
+      remoteCandidateId, state, priority, nominated, bytesSent, bytesReceived, totalRoundTripTime, currentRoundTripTime
  */
 function validateIceCandidatePairStats(statsReport, stats) {
   validateRtcStats(statsReport, stats);
@@ -806,7 +794,7 @@
   assert_optional_unsigned_int_field(stats, 'packetsSent');
   assert_optional_unsigned_int_field(stats, 'packetsReceived');
   assert_unsigned_int_field(stats, 'bytesSent');
-  assert_unsigned_int_field(stats, 'byteReceived');
+  assert_unsigned_int_field(stats, 'bytesReceived');
 
   assert_optional_number_field(stats, 'lastPacketSentTimestamp');
   assert_optional_number_field(stats, 'lastPacketReceivedTimestamp');
@@ -835,10 +823,10 @@
   [webrtc-stats]
   7.17. RTCCertificateStats dictionary
     dictionary RTCCertificateStats : RTCStats {
-      required  DOMString fingerprint;
-      required  DOMString fingerprintAlgorithm;
-      required  DOMString base64Certificate;
-      required  DOMString issuerCertificateId;
+      DOMString fingerprint;
+      DOMString fingerprintAlgorithm;
+      DOMString base64Certificate;
+      DOMString issuerCertificateId;
     };
 
   [webrtc-pc]
@@ -846,12 +834,11 @@
     - RTCCertificateStats, with attributes fingerprint, fingerprintAlgorithm,
       base64Certificate, issuerCertificateId
  */
-
 function validateCertificateStats(statsReport, stats) {
   validateRtcStats(statsReport, stats);
 
   assert_string_field(stats, 'fingerprint');
   assert_string_field(stats, 'fingerprintAlgorithm');
   assert_string_field(stats, 'base64Certificate');
-  assert_string_field(stats, 'issuerCertificateId');
+  assert_optional_string_field(stats, 'issuerCertificateId');
 }
diff --git a/webrtc/RTCTrackEvent-constructor.html b/webrtc/RTCTrackEvent-constructor.html
index d5c5f62..9579dd4 100644
--- a/webrtc/RTCTrackEvent-constructor.html
+++ b/webrtc/RTCTrackEvent-constructor.html
@@ -41,6 +41,7 @@
     assert_equals(trackEvent.receiver, receiver);
     assert_equals(trackEvent.track, track);
     assert_array_equals(trackEvent.streams, []);
+    assert_equals(trackEvent.streams, trackEvent.streams); // [SameObject]
     assert_equals(trackEvent.transceiver, transceiver);
 
     assert_equals(trackEvent.type, 'track');
diff --git a/webrtc/historical.html b/webrtc/historical.html
index c57a1ca..d49503e 100644
--- a/webrtc/historical.html
+++ b/webrtc/historical.html
@@ -25,6 +25,7 @@
 });
 
 [
+  "DataChannel",
   "mozRTCIceCandidate",
   "mozRTCPeerConnection",
   "mozRTCSessionDescription",
diff --git a/webrtc/identity-helper.js b/webrtc/identity-helper.js
index bd35d0f..16e5d35 100644
--- a/webrtc/identity-helper.js
+++ b/webrtc/identity-helper.js
@@ -8,7 +8,7 @@
     127.0.0.1   www2.web-platform.test
     127.0.0.1   xn--n8j6ds53lwwkrqhv28a.web-platform.test
     127.0.0.1   xn--lve-6lad.web-platform.test
-    0.0.0.0     nonexistent-origin.web-platform.test
+    0.0.0.0     nonexistent.web-platform.test
  */
 
 /*
diff --git a/webrtc/interfaces.https.html b/webrtc/interfaces.https.html
index e6e6846..2cb1ecf 100644
--- a/webrtc/interfaces.https.html
+++ b/webrtc/interfaces.https.html
@@ -107,13 +107,20 @@
       }));
   }
 
-  // Main function to do the IDL test, using fetched IDL text
-  function doIdlTest(idlText) {
+  promise_test(async t => {
+    await asyncInit();
+
     const idlArray = new IdlArray();
 
+    let webrtcIdl = fetch('/interfaces/webrtc-pc.idl').then(r => r.text());
+    let mediacaptureMainIdl = fetch('/interfaces/mediacapture-main.idl').then(r => r.text());
+
+    idlArray.add_untested_idls(mediacaptureMainIdl, { only: ['MediaStreamConstraints'] });
+    idlArray.add_idls(webrtcIdl);
+
     idlArray.add_untested_idls('interface EventHandler {};');
+    idlArray.add_idls('interface EventTarget {};');
     idlArray.add_idls('interface MediaStreamTrack : EventTarget {};');
-    idlArray.add_idls(idlText);
 
     idlArray.add_objects({
       'RTCPeerConnection': [`new RTCPeerConnection()`],
@@ -159,13 +166,6 @@
     });
 
     idlArray.test();
-  }
-
-  promise_test(t => {
-    return asyncInit()
-    .then(() => fetch('/interfaces/webrtc-pc.idl'))
-    .then(response => response.text())
-    .then(doIdlTest);
   }, 'Main test driver');
 
   /*
diff --git a/webrtc/protocol/README.txt b/webrtc/protocol/README.txt
new file mode 100644
index 0000000..062db85
--- /dev/null
+++ b/webrtc/protocol/README.txt
@@ -0,0 +1,21 @@
+This directory contains files that test for behavior relevant to webrtc,
+but which is specified in protocol specifications from the IETF, not in
+API recommendations from the W3C.
+
+The main specifications are given in the following internet-drafts:
+
+- draft-ietf-rtcweb-overview
+- draft-ietf-rtcweb-transports
+- draft-ietf-rtcweb-security-arch
+- draft-ietf-rtcweb-security
+- draft-ietf-rtcweb-rtp-usage
+- draft-ietf-rtcweb-jsep
+- draft-ietf-rtcweb-ip-handling
+- draft-ietf-rtcweb-fec
+- draft-ietf-rtcweb-data-protocol
+- draft-ietf-rtcweb-data-channel
+
+- RFC 7742, "WebRTC Video Processing and Codec Requirements"
+- RFC 7874, "WebRTC Audio Codec and Processing Requirements"
+
+An overview of the dependencies involved is in draft-jennings-rtcweb-deps
diff --git a/webrtc/protocol/video-codecs.html b/webrtc/protocol/video-codecs.html
new file mode 100644
index 0000000..cc15ced
--- /dev/null
+++ b/webrtc/protocol/video-codecs.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.createOffer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/webrtc/RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+// Tests for conformance to RFC 7742,
+// "WebRTC Video Processing and Codec Requirements"
+// The document was formerly known as draft-ietf-rtcweb-video-codecs.
+//
+// This tests that the browser is a WebRTC Browser as defined there.
+
+// TODO: Section 3.2: screen capture video MUST be prepared
+// to handle resolution changes.
+
+// TODO: Section 4: MUST support generating CVO (orientation)
+
+// Section 5: Browsers MUST implement VP8 and H.264 Constrained Baseline
+promise_test(async t => {
+  const pc = new RTCPeerConnection();
+  const offer = await pc.createOffer({offerToReceiveVideo: true});
+  let video_section_found = false;
+  for (let section of offer.sdp.split(/\r\nm=/)) {
+    if (section.search('video') != 0) {
+      continue;
+    }
+    video_section_found = true;
+    // RTPMAP lines have the format a=rtpmap:<pt> <codec>/<clock rate>
+    let rtpmap_regex = /\r\na=rtpmap:(\d+) (\S+)\/\d+\r\n/g;
+    let match = rtpmap_regex.exec(offer.sdp);
+    let payload_type_map = new Array();
+    while (match) {
+      payload_type_map[match[1]] = match[2];
+      match = rtpmap_regex.exec(offer.sdp);
+    }
+    assert_true(payload_type_map.indexOf('VP8') > -1,
+                'VP8 is supported');
+    assert_true(payload_type_map.indexOf('H264') > -1,
+                'H.264 is supported');
+    // TODO: Verify that one of the H.264 PTs supports constrained baseline
+  }
+  assert_true(video_section_found);
+}, 'H.264 and VP8 should be supported in initial offer');
+
+// TODO: Section 6: Recipients MUST be able to decode 320x240@20 fps
+// TODO: Section 6.1: VP8 MUST support RFC 7741 payload formats
+// TODO: Section 6.1: VP8 MUST respect max-fr/max-fs
+// TODO: Section 6.1: VP8 MUST encode and decode square pixels
+// TODO: Section 6.2: H.264 MUST support RFC 6184 payload formats
+// TODO: Section 6.2: MUST support Constrained Baseline level 1.2
+// TODO: Section 6.2: SHOULD support Constrained High level 1.3
+// TODO: Section 6.2: MUST support packetization mode 1.
+// TODO: Section 6.2: MUST include profile-level-id
+// TODO: Section 6.2: SHOULD interpret max-mbps, max-smbps, max-fs et al
+// TODO: Section 6.2: MUST NOT include sprop-parameter-sets
+// TODO: Section 6.2: MUST support SEI "filler payload"
+// TODO: Section 6.2: MUST support SEI "full frame freeze"
+// TODO: Section 6.2: MUST be prepared to receive User Data messages
+// TODO: Section 6.2: MUST encode and decode square pixels unless signaled
+</script>
diff --git a/webrtc/simplecall.html b/webrtc/simplecall.html
deleted file mode 100644
index f27c7ff..0000000
--- a/webrtc/simplecall.html
+++ /dev/null
@@ -1,116 +0,0 @@
-<!doctype html>
-<!--
-To run this test, you must have a webcam and a microphone or use
-fake devices by specifying
-  --use-fake-device-for-media-stream --use-fake-ui-for-media-stream
-for Chrome or by setting the
-  media.navigator.streams.fake
-property to true in Firefox.
--->
-
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-  <title>RTCPeerConnection Connection Test</title>
-</head>
-<body>
-  <div id="log"></div>
-  <div>
-    <video id="local-view" autoplay="autoplay"></video>
-    <video id="remote-view" autoplay="autoplay"/>
-    </video>
-  </div>
-
-  <!-- These files are in place when executing on W3C. -->
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-  <script type="text/javascript">
-  var test = async_test('Can set up a basic WebRTC call.', {timeout: 5000});
-
-  var gFirstConnection = null;
-  var gSecondConnection = null;
-
-  // if the remote video gets video data that implies the negotiation
-  // as well as the ICE and DTLS connection are up.
-  document.getElementById('remote-view')
-      .addEventListener('loadedmetadata', function() {
-    // Call negotiated: done.
-    test.done();
-  });
-
-  function getUserMediaOkCallback(localStream) {
-    gFirstConnection = new RTCPeerConnection(null);
-    gFirstConnection.onicecandidate = onIceCandidateToFirst;
-    localStream.getTracks().forEach(function(track) {
-      gFirstConnection.addTrack(track, localStream);
-    });
-    gFirstConnection.createOffer(onOfferCreated, failed('createOffer'));
-
-    var videoTag = document.getElementById('local-view');
-    videoTag.srcObject = localStream;
-  };
-
-  var onOfferCreated = test.step_func(function(offer) {
-    gFirstConnection.setLocalDescription(offer);
-
-    // This would normally go across the application's signaling solution.
-    // In our case, the "signaling" is to call this function.
-    receiveCall(offer.sdp);
-  });
-
-  function receiveCall(offerSdp) {
-    gSecondConnection = new RTCPeerConnection(null);
-    gSecondConnection.onicecandidate = onIceCandidateToSecond;
-    gSecondConnection.ontrack = onRemoteTrack;
-
-    var parsedOffer = new RTCSessionDescription({ type: 'offer',
-                                                  sdp: offerSdp });
-    gSecondConnection.setRemoteDescription(parsedOffer);
-
-    gSecondConnection.createAnswer(onAnswerCreated,
-                                   failed('createAnswer'));
-  };
-
-  var onAnswerCreated = test.step_func(function(answer) {
-    gSecondConnection.setLocalDescription(answer);
-
-    // Similarly, this would go over the application's signaling solution.
-    handleAnswer(answer.sdp);
-  });
-
-  function handleAnswer(answerSdp) {
-    var parsedAnswer = new RTCSessionDescription({ type: 'answer',
-                                                   sdp: answerSdp });
-    gFirstConnection.setRemoteDescription(parsedAnswer);
-  };
-
-  var onIceCandidateToFirst = test.step_func(function(event) {
-    // If event.candidate is null = no more candidates.
-    gSecondConnection.addIceCandidate(event.candidate);
-  });
-
-  var onIceCandidateToSecond = test.step_func(function(event) {
-    gFirstConnection.addIceCandidate(event.candidate);
-  });
-
-  var onRemoteTrack = test.step_func(function(event) {
-    var videoTag = document.getElementById('remote-view');
-    if (!videoTag.srcObject) {
-      videoTag.srcObject = event.streams[0];
-    }
-  });
-
-  // Returns a suitable error callback.
-  function failed(function_name) {
-    return test.unreached_func('WebRTC called error callback for ' + function_name);
-  }
-
-  // This function starts the test.
-  test.step(function() {
-    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
-      .then(test.step_func(getUserMediaOkCallback), failed('getUserMedia'));
-  });
-</script>
-
-</body>
-</html>
diff --git a/webrtc/simplecall.https.html b/webrtc/simplecall.https.html
new file mode 100644
index 0000000..4d948ee
--- /dev/null
+++ b/webrtc/simplecall.https.html
@@ -0,0 +1,120 @@
+<!doctype html>
+<!--
+To run this test, you must have a webcam and a microphone or use
+fake devices by specifying
+  --use-fake-device-for-media-stream --use-fake-ui-for-media-stream
+for Chrome or by setting the
+  media.navigator.streams.fake
+property to true in Firefox.
+-->
+
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>RTCPeerConnection Connection Test</title>
+</head>
+<body>
+  <div id="log"></div>
+  <div>
+    <video id="local-view" autoplay="autoplay"></video>
+    <video id="remote-view" autoplay="autoplay"/>
+    </video>
+  </div>
+
+  <!-- These files are in place when executing on W3C. -->
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script type="text/javascript">
+  var test = async_test('Can set up a basic WebRTC call.', {timeout: 5000});
+
+  var gFirstConnection = null;
+  var gSecondConnection = null;
+
+  // if the remote video gets video data that implies the negotiation
+  // as well as the ICE and DTLS connection are up.
+  document.getElementById('remote-view')
+      .addEventListener('loadedmetadata', function() {
+    // Call negotiated: done.
+    test.done();
+  });
+
+  function getUserMediaOkCallback(localStream) {
+    gFirstConnection = new RTCPeerConnection(null);
+    gFirstConnection.onicecandidate = onIceCandidateToFirst;
+    localStream.getTracks().forEach(function(track) {
+      gFirstConnection.addTrack(track, localStream);
+    });
+    gFirstConnection.createOffer(onOfferCreated, failed('createOffer'));
+
+    var videoTag = document.getElementById('local-view');
+    videoTag.srcObject = localStream;
+  };
+
+  var onOfferCreated = test.step_func(function(offer) {
+    gFirstConnection.setLocalDescription(offer);
+
+    // This would normally go across the application's signaling solution.
+    // In our case, the "signaling" is to call this function.
+    receiveCall(offer.sdp);
+  });
+
+  function receiveCall(offerSdp) {
+    gSecondConnection = new RTCPeerConnection(null);
+    gSecondConnection.onicecandidate = onIceCandidateToSecond;
+    gSecondConnection.ontrack = onRemoteTrack;
+
+    var parsedOffer = new RTCSessionDescription({ type: 'offer',
+                                                  sdp: offerSdp });
+    gSecondConnection.setRemoteDescription(parsedOffer);
+
+    gSecondConnection.createAnswer(onAnswerCreated,
+                                   failed('createAnswer'));
+  };
+
+  var onAnswerCreated = test.step_func(function(answer) {
+    gSecondConnection.setLocalDescription(answer);
+
+    // Similarly, this would go over the application's signaling solution.
+    handleAnswer(answer.sdp);
+  });
+
+  function handleAnswer(answerSdp) {
+    var parsedAnswer = new RTCSessionDescription({ type: 'answer',
+                                                   sdp: answerSdp });
+    gFirstConnection.setRemoteDescription(parsedAnswer);
+  };
+
+  var onIceCandidateToFirst = test.step_func(function(event) {
+    // If event.candidate is null = no more candidates.
+    if (event.candidate) {
+      gSecondConnection.addIceCandidate(event.candidate);
+    }
+  });
+
+  var onIceCandidateToSecond = test.step_func(function(event) {
+    if (event.candidate) {
+      gFirstConnection.addIceCandidate(event.candidate);
+    }
+  });
+
+  var onRemoteTrack = test.step_func(function(event) {
+    var videoTag = document.getElementById('remote-view');
+    if (!videoTag.srcObject) {
+      videoTag.srcObject = event.streams[0];
+    }
+  });
+
+  // Returns a suitable error callback.
+  function failed(function_name) {
+    return test.unreached_func('WebRTC called error callback for ' + function_name);
+  }
+
+  // This function starts the test.
+  test.step(function() {
+    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
+      .then(test.step_func(getUserMediaOkCallback), failed('getUserMedia'));
+  });
+</script>
+
+</body>
+</html>
diff --git a/webrtc/tools/.eslintrc.js b/webrtc/tools/.eslintrc.js
index 4c78b38..47bbb5d 100644
--- a/webrtc/tools/.eslintrc.js
+++ b/webrtc/tools/.eslintrc.js
@@ -129,6 +129,7 @@
     // RTCStats-helper.js
     validateStatsReport: true,
     assert_stats_report_has_stats: true,
+    findStatsFromReport: true,
     getRequiredStats: true,
     getStatsById: true,
     validateIdField: true,
diff --git a/websockets/Close-1000-reason.any.js b/websockets/Close-1000-reason.any.js
new file mode 100644
index 0000000..1517db2
--- /dev/null
+++ b/websockets/Close-1000-reason.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(1000, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(1000, "Clean Close");
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be opened");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Close-1000-reason.htm b/websockets/Close-1000-reason.htm
deleted file mode 100644
index 8424d78..0000000
--- a/websockets/Close-1000-reason.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close WebSocket - Code is 1000 and reason</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(1000, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(1000, "Clean Close");
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be opened");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Close-1000.any.js b/websockets/Close-1000.any.js
new file mode 100644
index 0000000..e052f23
--- /dev/null
+++ b/websockets/Close-1000.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create WebSocket - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(1000) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(1000);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be opened");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Close-1000.htm b/websockets/Close-1000.htm
deleted file mode 100644
index 1622109..0000000
--- a/websockets/Close-1000.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close WebSocket - Code is 1000</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create WebSocket - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(1000) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(1000);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be opened");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Close-Reason-124Bytes.any.js b/websockets/Close-Reason-124Bytes.any.js
new file mode 100644
index 0000000..97bc1bb
--- /dev/null
+++ b/websockets/Close-Reason-124Bytes.any.js
@@ -0,0 +1,15 @@
+// META: script=websocket.sub.js
+
+var test = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(code, 'reason more than 123 bytes') - SYNTAX_ERR is thrown");
+
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  var reason = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123";
+  assert_equals(reason.length, 124);
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket.close(1000, reason)
+  });
+  test.done();
+}), true);
diff --git a/websockets/Close-Reason-124Bytes.htm b/websockets/Close-Reason-124Bytes.htm
deleted file mode 100644
index 82a661e..0000000
--- a/websockets/Close-Reason-124Bytes.htm
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close WebSocket - Reason is more than 123 bytes long</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(code, 'reason more than 123 bytes') - SYNTAX_ERR is thrown");
-
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            var reason = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123";
-            assert_equals(reason.length, 124);
-            assert_throws("SYNTAX_ERR", function () { wsocket.close(1000, reason) });
-            test.done();
-        }), true);
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Close-reason-unpaired-surrogates.any.js b/websockets/Close-reason-unpaired-surrogates.any.js
new file mode 100644
index 0000000..119a32d
--- /dev/null
+++ b/websockets/Close-reason-unpaired-surrogates.any.js
@@ -0,0 +1,21 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get opened");
+var testClose = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get closed");
+
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+var replacementChar = "\uFFFD";
+var reason = "\uD807";
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(1000, reason);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be opened");
+  assert_equals(evt.reason, replacementChar, "reason replaced with replacement character");
+  testClose.done();
+}), true);
diff --git a/websockets/Close-reason-unpaired-surrogates.htm b/websockets/Close-reason-unpaired-surrogates.htm
deleted file mode 100644
index 9ad8d61..0000000
--- a/websockets/Close-reason-unpaired-surrogates.htm
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close WebSocket - Reason with unpaired surrogates</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get opened");
-        var testClose = async_test("W3C WebSocket API - Create WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get closed");
-
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-        var replacementChar = "\uFFFD";
-        var reason = "\uD807";
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(1000, reason);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be opened");
-            assert_equals(evt.reason, replacementChar, "reason replaced with replacement character");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Close-undefined.any.js b/websockets/Close-undefined.any.js
new file mode 100644
index 0000000..67bc9b2
--- /dev/null
+++ b/websockets/Close-undefined.any.js
@@ -0,0 +1,11 @@
+// META: script=websocket.sub.js
+
+var test = async_test();
+
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  wsocket.close(undefined);
+  test.done();
+}), true);
diff --git a/websockets/Close-undefined.htm b/websockets/Close-undefined.htm
deleted file mode 100644
index d6c89afb..0000000
--- a/websockets/Close-undefined.htm
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close WebSocket - Code is undefined</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test();
-
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            wsocket.close(undefined);
-            test.done();
-        }), true);
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Create-Secure-extensions-empty.any.js b/websockets/Create-Secure-extensions-empty.any.js
new file mode 100644
index 0000000..82b000d
--- /dev/null
+++ b/websockets/Create-Secure-extensions-empty.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.extensions should be set to '' after connection is established - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.extensions should be set to '' after connection is established - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.extensions, "", "extensions should be empty");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be closed");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-Secure-extensions-empty.htm b/websockets/Create-Secure-extensions-empty.htm
deleted file mode 100644
index 2f9f797..0000000
--- a/websockets/Create-Secure-extensions-empty.htm
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.extensions should be set to '' after connection is established - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.extensions should be set to '' after connection is established - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.extensions, "", "extensions should be empty");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be closed");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-Secure-url-with-space.any.js b/websockets/Create-Secure-url-with-space.any.js
new file mode 100644
index 0000000..94265c6
--- /dev/null
+++ b/websockets/Create-Secure-url-with-space.any.js
@@ -0,0 +1,9 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket;
+  var spaceUrl = "web platform.test";
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketWithSpaceInUrl(spaceUrl)
+  });
+}, "W3C WebSocket API - Create Secure WebSocket - Pass a URL with a space - SYNTAX_ERR should be thrown")
diff --git a/websockets/Create-Secure-url-with-space.htm b/websockets/Create-Secure-url-with-space.htm
deleted file mode 100644
index d2dfe1b..0000000
--- a/websockets/Create-Secure-url-with-space.htm
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket - url with space</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket;
-            var spaceUrl = "web platform.test";
-            assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketWithSpaceInUrl(spaceUrl) });
-        }, "W3C WebSocket API - Create Secure WebSocket - Pass a URL with a space - SYNTAX_ERR should be thrown")
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-Secure-valid-url-array-protocols.any.js b/websockets/Create-Secure-valid-url-array-protocols.any.js
new file mode 100644
index 0000000..fcaf8a3
--- /dev/null
+++ b/websockets/Create-Secure-valid-url-array-protocols.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and array of protocol strings - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and array of protocol strings - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, true);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-Secure-valid-url-array-protocols.htm b/websockets/Create-Secure-valid-url-array-protocols.htm
deleted file mode 100644
index d61a429..0000000
--- a/websockets/Create-Secure-valid-url-array-protocols.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket with valid url and array of protocols</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and array of protocol strings - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and array of protocol strings - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, true);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-Secure-valid-url-binaryType-blob.any.js b/websockets/Create-Secure-valid-url-binaryType-blob.any.js
new file mode 100644
index 0000000..fed88f5
--- /dev/null
+++ b/websockets/Create-Secure-valid-url-binaryType-blob.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.binaryType should be set to 'blob' after connection is established - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.binaryType should be set to 'blob' after connection is established - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.binaryType, "blob", "binaryType should be set to Blob");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-Secure-valid-url-binaryType-blob.htm b/websockets/Create-Secure-valid-url-binaryType-blob.htm
deleted file mode 100644
index 0261fb4..0000000
--- a/websockets/Create-Secure-valid-url-binaryType-blob.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket - binaryType set correctly</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.binaryType should be set to 'blob' after connection is established - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - wsocket.binaryType should be set to 'blob' after connection is established - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.binaryType, "blob", "binaryType should be set to Blob");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-Secure-valid-url-protocol-setCorrectly.any.js b/websockets/Create-Secure-valid-url-protocol-setCorrectly.any.js
new file mode 100644
index 0000000..7ecd295
--- /dev/null
+++ b/websockets/Create-Secure-valid-url-protocol-setCorrectly.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - protocol should be set correctly - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, true, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.protocol, "echo", "protocol should be set to echo");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-Secure-valid-url-protocol-setCorrectly.htm b/websockets/Create-Secure-valid-url-protocol-setCorrectly.htm
deleted file mode 100644
index a7f1510..0000000
--- a/websockets/Create-Secure-valid-url-protocol-setCorrectly.htm
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket and verify if protocol is set correctly after connection</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-            var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - protocol should be set correctly - Connection should be opened");
-            var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - Connection should be closed");
-
-            var wsocket = CreateWebSocket(true, true, false);
-            var isOpenCalled = false;
-
-            wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-                assert_equals(wsocket.protocol, "echo", "protocol should be set to echo");
-                wsocket.close();
-                isOpenCalled = true;
-                testOpen.done();
-            }), true);
-
-            wsocket.addEventListener('close', testClose.step_func(function (evt) {
-                assert_true(isOpenCalled, "WebSocket connection should be open");
-                assert_equals(evt.wasClean, true, "wasClean should be true");
-                    testClose.done();
-            }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-Secure-valid-url-protocol-string.any.js b/websockets/Create-Secure-valid-url-protocol-string.any.js
new file mode 100644
index 0000000..59c77c6
--- /dev/null
+++ b/websockets/Create-Secure-valid-url-protocol-string.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Check readyState is 1");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, true, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-Secure-valid-url-protocol-string.htm b/websockets/Create-Secure-valid-url-protocol-string.htm
deleted file mode 100644
index 7250b4e..0000000
--- a/websockets/Create-Secure-valid-url-protocol-string.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket with valid url and protocol string</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Check readyState is 1");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL and protocol string - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, true, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-Secure-valid-url.any.js b/websockets/Create-Secure-valid-url.any.js
new file mode 100644
index 0000000..6f1229e
--- /dev/null
+++ b/websockets/Create-Secure-valid-url.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL  - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-Secure-valid-url.htm b/websockets/Create-Secure-valid-url.htm
deleted file mode 100644
index 1fe6c0f..0000000
--- a/websockets/Create-Secure-valid-url.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket with valid url</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL  - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Pass a valid URL - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-Secure-verify-url-set-non-default-port.any.js b/websockets/Create-Secure-verify-url-set-non-default-port.any.js
new file mode 100644
index 0000000..755dbe2
--- /dev/null
+++ b/websockets/Create-Secure-verify-url-set-non-default-port.any.js
@@ -0,0 +1,7 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var urlNonDefaultPort = "wss://" + __SERVER__NAME + ":" + __NEW__SECURE__PORT + "/" + __PATH;
+  var wsocket = new WebSocket(urlNonDefaultPort);
+  assert_equals(wsocket.url, urlNonDefaultPort, "wsocket.url is set correctly");
+}, "W3C WebSocket API - Create Secure WebSocket - wsocket.url should be set correctly")
diff --git a/websockets/Create-Secure-verify-url-set-non-default-port.htm b/websockets/Create-Secure-verify-url-set-non-default-port.htm
deleted file mode 100644
index 63e49f4..0000000
--- a/websockets/Create-Secure-verify-url-set-non-default-port.htm
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket - wsocket.url is set correctly - non default port</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var urlNonDefaultPort = "wss://" + __SERVER__NAME + ":" + __NEW__SECURE__PORT + "/" + __PATH;
-            var wsocket = new WebSocket(urlNonDefaultPort);
-            assert_equals(wsocket.url, urlNonDefaultPort, "wsocket.url is set correctly");
-        }, "W3C WebSocket API - Create Secure WebSocket - wsocket.url should be set correctly")
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-asciiSep-protocol-string.any.js b/websockets/Create-asciiSep-protocol-string.any.js
new file mode 100644
index 0000000..cb3c3e4
--- /dev/null
+++ b/websockets/Create-asciiSep-protocol-string.any.js
@@ -0,0 +1,9 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var asciiWithSep = "/echo";
+  var wsocket;
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketWithAsciiSep(asciiWithSep)
+  });
+}, "W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string with an ascii separator character - SYNTAX_ERR is thrown")
diff --git a/websockets/Create-asciiSep-protocol-string.htm b/websockets/Create-asciiSep-protocol-string.htm
deleted file mode 100644
index 0d0480e..0000000
--- a/websockets/Create-asciiSep-protocol-string.htm
+++ /dev/null
@@ -1,21 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - ascii protocol string with separator</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        if(window.WebSocket) {
-            test(function () {
-                var asciiWithSep = "/echo";
-                var wsocket;
-                assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketWithAsciiSep(asciiWithSep) });
-            }, "W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string with an ascii separator character - SYNTAX_ERR is thrown")
-        }
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-blocked-port.any.js b/websockets/Create-blocked-port.any.js
new file mode 100644
index 0000000..f0dbc3f
--- /dev/null
+++ b/websockets/Create-blocked-port.any.js
@@ -0,0 +1,81 @@
+// META: script=websocket.sub.js
+
+async_test(t => {
+  const ws = CreateWebSocketWithBlockedPort(__PORT)
+  ws.onerror = t.unreached_func()
+  ws.onopen = t.step_func_done()
+}, 'Basic check');
+// list of bad ports according to
+// https://fetch.spec.whatwg.org/#port-blocking
+[
+  1, // tcpmux
+  7, // echo
+  9, // discard
+  11, // systat
+  13, // daytime
+  15, // netstat
+  17, // qotd
+  19, // chargen
+  20, // ftp-data
+  21, // ftp
+  22, // ssh
+  23, // telnet
+  25, // smtp
+  37, // time
+  42, // name
+  43, // nicname
+  53, // domain
+  77, // priv-rjs
+  79, // finger
+  87, // ttylink
+  95, // supdup
+  101, // hostriame
+  102, // iso-tsap
+  103, // gppitnp
+  104, // acr-nema
+  109, // pop2
+  110, // pop3
+  111, // sunrpc
+  113, // auth
+  115, // sftp
+  117, // uucp-path
+  119, // nntp
+  123, // ntp
+  135, // loc-srv / epmap
+  139, // netbios
+  143, // imap2
+  179, // bgp
+  389, // ldap
+  465, // smtp+ssl
+  512, // print / exec
+  513, // login
+  514, // shell
+  515, // printer
+  526, // tempo
+  530, // courier
+  531, // chat
+  532, // netnews
+  540, // uucp
+  556, // remotefs
+  563, // nntp+ssl
+  587, // smtp
+  601, // syslog-conn
+  636, // ldap+ssl
+  993, // imap+ssl
+  995, // pop3+ssl
+  2049, // nfs
+  3659, // apple-sasl
+  4045, // lockd
+  6000, // x11
+  6665, // irc (alternate)
+  6666, // irc (alternate)
+  6667, // irc (default)
+  6668, // irc (alternate)
+  6669, // irc (alternate)
+].forEach(blockedPort => {
+  async_test(t => {
+    const ws = CreateWebSocketWithBlockedPort(blockedPort)
+    ws.onerror = t.step_func_done()
+    ws.onopen = t.unreached_func()
+  }, "WebSocket blocked port test " + blockedPort)
+})
diff --git a/websockets/Create-blocked-port.htm b/websockets/Create-blocked-port.htm
deleted file mode 100644
index aafaee6..0000000
--- a/websockets/Create-blocked-port.htm
+++ /dev/null
@@ -1,93 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>WebSocket API - blocked port</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script>
-            async_test(t => {
-              const ws = CreateWebSocketWithBlockedPort(__PORT)
-              ws.onerror = t.unreached_func()
-              ws.onopen = t.step_func_done()
-            }, 'Basic check');
-            // list of bad ports according to
-            // https://fetch.spec.whatwg.org/#port-blocking
-            [
-                1,    // tcpmux
-                7,    // echo
-                9,    // discard
-                11,   // systat
-                13,   // daytime
-                15,   // netstat
-                17,   // qotd
-                19,   // chargen
-                20,   // ftp-data
-                21,   // ftp
-                22,   // ssh
-                23,   // telnet
-                25,   // smtp
-                37,   // time
-                42,   // name
-                43,   // nicname
-                53,   // domain
-                77,   // priv-rjs
-                79,   // finger
-                87,   // ttylink
-                95,   // supdup
-                101,  // hostriame
-                102,  // iso-tsap
-                103,  // gppitnp
-                104,  // acr-nema
-                109,  // pop2
-                110,  // pop3
-                111,  // sunrpc
-                113,  // auth
-                115,  // sftp
-                117,  // uucp-path
-                119,  // nntp
-                123,  // ntp
-                135,  // loc-srv / epmap
-                139,  // netbios
-                143,  // imap2
-                179,  // bgp
-                389,  // ldap
-                465,  // smtp+ssl
-                512,  // print / exec
-                513,  // login
-                514,  // shell
-                515,  // printer
-                526,  // tempo
-                530,  // courier
-                531,  // chat
-                532,  // netnews
-                540,  // uucp
-                556,  // remotefs
-                563,  // nntp+ssl
-                587,  // smtp
-                601,  // syslog-conn
-                636,  // ldap+ssl
-                993,  // imap+ssl
-                995,  // pop3+ssl
-                2049, // nfs
-                3659, // apple-sasl
-                4045, // lockd
-                6000, // x11
-                6665, // irc (alternate)
-                6666, // irc (alternate)
-                6667, // irc (default)
-                6668, // irc (alternate)
-                6669, // irc (alternate)
-            ].forEach(blockedPort => {
-              async_test(t => {
-                const ws = CreateWebSocketWithBlockedPort(blockedPort)
-                ws.onerror = t.step_func_done()
-                ws.onopen = t.unreached_func()
-              }, "WebSocket blocked port test " + blockedPort)
-            })
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-invalid-urls.any.js b/websockets/Create-invalid-urls.any.js
new file mode 100644
index 0000000..5ae25a2
--- /dev/null
+++ b/websockets/Create-invalid-urls.any.js
@@ -0,0 +1,32 @@
+// META: script=websocket.sub.js
+
+var wsocket;
+test(function() {
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = new WebSocket("/echo")
+  });
+}, "Url is /echo - should throw SYNTAX_ERR");
+
+test(function() {
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = new WebSocket("mailto:microsoft@microsoft.com")
+  });
+}, "Url is a mail address - should throw SYNTAX_ERR");
+
+test(function() {
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = new WebSocket("about:blank")
+  });
+}, "Url is about:blank - should throw SYNTAX_ERR");
+
+test(function() {
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = new WebSocket("?test")
+  });
+}, "Url is ?test - should throw SYNTAX_ERR");
+
+test(function() {
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = new WebSocket("#test")
+  });
+}, "Url is #test - should throw SYNTAX_ERR");
diff --git a/websockets/Create-invalid-urls.htm b/websockets/Create-invalid-urls.htm
deleted file mode 100644
index c4a4c79..0000000
--- a/websockets/Create-invalid-urls.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - pass in list of invalid urls</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        var wsocket;
-        test(function() {
-            assert_throws("SYNTAX_ERR", function () { wsocket = new WebSocket("/echo") });
-        }, "Url is /echo - should throw SYNTAX_ERR");
-
-        test(function () {
-            assert_throws("SYNTAX_ERR", function () { wsocket = new WebSocket("mailto:microsoft@microsoft.com") });
-        }, "Url is a mail address - should throw SYNTAX_ERR");
-
-        test(function () {
-            assert_throws("SYNTAX_ERR", function () { wsocket = new WebSocket("about:blank") });
-        }, "Url is about:blank - should throw SYNTAX_ERR");
-
-        test(function () {
-            assert_throws("SYNTAX_ERR", function () { wsocket = new WebSocket("?test") });
-        }, "Url is ?test - should throw SYNTAX_ERR");
-
-        test(function () {
-            assert_throws("SYNTAX_ERR", function () { wsocket = new WebSocket("#test") });
-        }, "Url is #test - should throw SYNTAX_ERR");
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-non-absolute-url.any.js b/websockets/Create-non-absolute-url.any.js
new file mode 100644
index 0000000..369557e
--- /dev/null
+++ b/websockets/Create-non-absolute-url.any.js
@@ -0,0 +1,8 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket;
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketNonAbsolute()
+  });
+}, "W3C WebSocket API - Create WebSocket - Pass a non absolute URL - SYNTAX_ERR is thrown")
diff --git a/websockets/Create-non-absolute-url.htm b/websockets/Create-non-absolute-url.htm
deleted file mode 100644
index 2ed8ece..0000000
--- a/websockets/Create-non-absolute-url.htm
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - non absolute url</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket;
-            assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketNonAbsolute() });
-        }, "W3C WebSocket API - Create WebSocket - Pass a non absolute URL - SYNTAX_ERR is thrown")
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-nonAscii-protocol-string.any.js b/websockets/Create-nonAscii-protocol-string.any.js
new file mode 100644
index 0000000..39be9f4
--- /dev/null
+++ b/websockets/Create-nonAscii-protocol-string.any.js
@@ -0,0 +1,9 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var nonAsciiProtocol = "\u0080echo";
+  var wsocket;
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketNonAsciiProtocol(nonAsciiProtocol)
+  });
+}, "W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string with non-ascii values - SYNTAX_ERR is thrown")
diff --git a/websockets/Create-nonAscii-protocol-string.htm b/websockets/Create-nonAscii-protocol-string.htm
deleted file mode 100644
index fd34018..0000000
--- a/websockets/Create-nonAscii-protocol-string.htm
+++ /dev/null
@@ -1,21 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - non ascii protocol string</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        if(window.WebSocket) {
-            test(function () {
-                var nonAsciiProtocol = "\u0080echo";
-                var wsocket;
-                assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketNonAsciiProtocol(nonAsciiProtocol) });
-            }, "W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string with non-ascii values - SYNTAX_ERR is thrown")
-        }
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-on-worker-shutdown.any.js b/websockets/Create-on-worker-shutdown.any.js
new file mode 100644
index 0000000..cb3eff8
--- /dev/null
+++ b/websockets/Create-on-worker-shutdown.any.js
@@ -0,0 +1,19 @@
+// META: script=websocket.sub.js
+
+async_test(t => {
+  function workerCode() {
+    close();
+    var ws = new WebSocket(self.location.origin.replace('http', 'ws'));
+    postMessage(ws.readyState == WebSocket.CONNECTING);
+  }
+
+  var workerBlob = new Blob([workerCode.toString() + ";workerCode();"], {
+    type: "application/javascript"
+  });
+
+  var w = new Worker(URL.createObjectURL(workerBlob));
+  w.onmessage = function(e) {
+    assert_true(e.data, "WebSocket created on worker shutdown.");
+    t.done();
+  }
+}, 'WebSocket created after a worker self.close()');
diff --git a/websockets/Create-protocol-with-space.any.js b/websockets/Create-protocol-with-space.any.js
new file mode 100644
index 0000000..b3c14d8
--- /dev/null
+++ b/websockets/Create-protocol-with-space.any.js
@@ -0,0 +1,8 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket;
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketWithSpaceInProtocol("ec ho")
+  });
+}, "W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string with a space in it - SYNTAX_ERR is thrown")
diff --git a/websockets/Create-protocol-with-space.htm b/websockets/Create-protocol-with-space.htm
deleted file mode 100644
index 023a498..0000000
--- a/websockets/Create-protocol-with-space.htm
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - protocol with space</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket;
-            assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketWithSpaceInProtocol("ec ho") });
-        }, "W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string with a space in it - SYNTAX_ERR is thrown")
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-protocols-repeated-case-insensitive.any.js b/websockets/Create-protocols-repeated-case-insensitive.any.js
new file mode 100644
index 0000000..16f9975
--- /dev/null
+++ b/websockets/Create-protocols-repeated-case-insensitive.any.js
@@ -0,0 +1,8 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket;
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketWithRepeatedProtocolsCaseInsensitive()
+  });
+}, "W3C WebSocket API - Create WebSocket - Pass a valid URL and an array of protocol strings with repeated values but different case - SYNTAX_ERR is thrown")
diff --git a/websockets/Create-protocols-repeated-case-insensitive.htm b/websockets/Create-protocols-repeated-case-insensitive.htm
deleted file mode 100644
index 47225ef..0000000
--- a/websockets/Create-protocols-repeated-case-insensitive.htm
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - repeated protocols with different case</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket;
-            assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketWithRepeatedProtocolsCaseInsensitive() });
-        }, "W3C WebSocket API - Create WebSocket - Pass a valid URL and an array of protocol strings with repeated values but different case - SYNTAX_ERR is thrown")
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-protocols-repeated.any.js b/websockets/Create-protocols-repeated.any.js
new file mode 100644
index 0000000..624d453
--- /dev/null
+++ b/websockets/Create-protocols-repeated.any.js
@@ -0,0 +1,8 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket;
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketWithRepeatedProtocols()
+  });
+}, "W3C WebSocket API - Create WebSocket - Pass a valid URL and an array of protocol strings with repeated values - SYNTAX_ERR is thrown")
diff --git a/websockets/Create-protocols-repeated.htm b/websockets/Create-protocols-repeated.htm
deleted file mode 100644
index 6a62bca..0000000
--- a/websockets/Create-protocols-repeated.htm
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - repeated protocols</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket;
-            assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketWithRepeatedProtocols() });
-        }, "W3C WebSocket API - Create WebSocket - Pass a valid URL and an array of protocol strings with repeated values - SYNTAX_ERR is thrown")
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-valid-url-array-protocols.any.js b/websockets/Create-valid-url-array-protocols.any.js
new file mode 100644
index 0000000..dde0303
--- /dev/null
+++ b/websockets/Create-valid-url-array-protocols.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and array of protocol strings - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and array of protocol strings - Connection should be closed");
+
+var wsocket = CreateWebSocket(false, false, true);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-valid-url-array-protocols.htm b/websockets/Create-valid-url-array-protocols.htm
deleted file mode 100644
index 3ebf3f5..0000000
--- a/websockets/Create-valid-url-array-protocols.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket with valid url and array of protocols</title>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and array of protocol strings - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and array of protocol strings - Connection should be closed");
-
-        var wsocket = CreateWebSocket(false, false, true);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Create-valid-url-protocol-empty.any.js b/websockets/Create-valid-url-protocol-empty.any.js
new file mode 100644
index 0000000..8682e4a
--- /dev/null
+++ b/websockets/Create-valid-url-protocol-empty.any.js
@@ -0,0 +1,7 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket = CreateWebSocket(false, true, false);
+  assert_equals(wsocket.protocol, "", "protocol should be empty");
+  wsocket.close();
+}, "W3C WebSocket API - Create WebSocket - wsocket.protocol should be empty before connection is established")
diff --git a/websockets/Create-valid-url-protocol-empty.htm b/websockets/Create-valid-url-protocol-empty.htm
deleted file mode 100644
index 9146937..0000000
--- a/websockets/Create-valid-url-protocol-empty.htm
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - wsocket.protocol is empty before connection is established</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket = CreateWebSocket(false, true, false);
-            assert_equals(wsocket.protocol, "", "protocol should be empty");
-            wsocket.close();
-        }, "W3C WebSocket API - Create WebSocket - wsocket.protocol should be empty before connection is established")
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-valid-url-protocol.any.js b/websockets/Create-valid-url-protocol.any.js
new file mode 100644
index 0000000..85e870f
--- /dev/null
+++ b/websockets/Create-valid-url-protocol.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string - Connection should be closed");
+
+var wsocket = CreateWebSocket(false, true, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-valid-url-protocol.htm b/websockets/Create-valid-url-protocol.htm
deleted file mode 100644
index 0a6dd97..0000000
--- a/websockets/Create-valid-url-protocol.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket with a valid url and protocol string</title>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL and a protocol string - Connection should be closed");
-
-        var wsocket = CreateWebSocket(false, true, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-valid-url.any.js b/websockets/Create-valid-url.any.js
new file mode 100644
index 0000000..9a43dcc
--- /dev/null
+++ b/websockets/Create-valid-url.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL - Connection should be closed");
+
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Create-valid-url.htm b/websockets/Create-valid-url.htm
deleted file mode 100644
index e74a7bf..0000000
--- a/websockets/Create-valid-url.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket with valid url</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create WebSocket - Pass a valid URL - Connection should be closed");
-
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.readyState, 1, "readyState should be 1(OPEN)");
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-verify-url-set-non-default-port.any.js b/websockets/Create-verify-url-set-non-default-port.any.js
new file mode 100644
index 0000000..5548fd1
--- /dev/null
+++ b/websockets/Create-verify-url-set-non-default-port.any.js
@@ -0,0 +1,7 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var urlNonDefaultPort = "ws://" + __SERVER__NAME + ":" + __NEW__PORT + "/" + __PATH;
+  var wsocket = new WebSocket(urlNonDefaultPort);
+  assert_equals(wsocket.url, urlNonDefaultPort, "wsocket.url is set correctly");
+}, "W3C WebSocket API - Create WebSocket - wsocket.url should be set correctly");
diff --git a/websockets/Create-verify-url-set-non-default-port.htm b/websockets/Create-verify-url-set-non-default-port.htm
deleted file mode 100644
index 181794d..0000000
--- a/websockets/Create-verify-url-set-non-default-port.htm
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - wsocket.url is set correctly - non default port</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var urlNonDefaultPort = "ws://" + __SERVER__NAME + ":" + __NEW__PORT + "/" + __PATH;
-            var wsocket = new WebSocket(urlNonDefaultPort);
-            assert_equals(wsocket.url, urlNonDefaultPort, "wsocket.url is set correctly");
-        }, "W3C WebSocket API - Create WebSocket - wsocket.url should be set correctly");
-    </script>
-</body>
-</html>
diff --git a/websockets/Create-wrong-scheme.any.js b/websockets/Create-wrong-scheme.any.js
new file mode 100644
index 0000000..506f81c
--- /dev/null
+++ b/websockets/Create-wrong-scheme.any.js
@@ -0,0 +1,8 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket;
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket = CreateWebSocketNonWsScheme()
+  });
+}, "W3C WebSocket API - Create WebSocket - Pass a URL with a non ws/wss scheme - SYNTAX_ERR is thrown")
diff --git a/websockets/Create-wrong-scheme.htm b/websockets/Create-wrong-scheme.htm
deleted file mode 100644
index a0a9820..0000000
--- a/websockets/Create-wrong-scheme.htm
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create WebSocket - non ws/wss scheme in url</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket;
-            assert_throws("SYNTAX_ERR", function () { wsocket = CreateWebSocketNonWsScheme() });
-        }, "W3C WebSocket API - Create WebSocket - Pass a URL with a non ws/wss scheme - SYNTAX_ERR is thrown")
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Close-1000-reason.any.js b/websockets/Secure-Close-1000-reason.any.js
new file mode 100644
index 0000000..4d3f67c
--- /dev/null
+++ b/websockets/Secure-Close-1000-reason.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(1000, "Clean Close");
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-1000-reason.htm b/websockets/Secure-Close-1000-reason.htm
deleted file mode 100644
index 3dead6b..0000000
--- a/websockets/Secure-Close-1000-reason.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 1000 and reason</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(1000, "Clean Close");
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-1000-verify-code.any.js b/websockets/Secure-Close-1000-verify-code.any.js
new file mode 100644
index 0000000..87ba407
--- /dev/null
+++ b/websockets/Secure-Close-1000-verify-code.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - event.code == 1000 and event.reason = 'Clean Close'");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(1000, "Clean Close");
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.code, 1000, "CloseEvent.code should be 1000");
+  assert_equals(evt.reason, "Clean Close", "CloseEvent.reason should be the same as the reason sent in close");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-1000-verify-code.htm b/websockets/Secure-Close-1000-verify-code.htm
deleted file mode 100644
index 0cac622..0000000
--- a/websockets/Secure-Close-1000-verify-code.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 1000 - verify code in CloseEvent is 1000</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000, reason) - event.code == 1000 and event.reason = 'Clean Close'");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(1000, "Clean Close");
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.code, 1000, "CloseEvent.code should be 1000");
-            assert_equals(evt.reason, "Clean Close", "CloseEvent.reason should be the same as the reason sent in close");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-1000.any.js b/websockets/Secure-Close-1000.any.js
new file mode 100644
index 0000000..67f4e05
--- /dev/null
+++ b/websockets/Secure-Close-1000.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000) - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(1000);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-1000.htm b/websockets/Secure-Close-1000.htm
deleted file mode 100644
index 3da8630..0000000
--- a/websockets/Secure-Close-1000.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 1000</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000) - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1000) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(1000);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-1005-verify-code.any.js b/websockets/Secure-Close-1005-verify-code.any.js
new file mode 100644
index 0000000..a7c72ea
--- /dev/null
+++ b/websockets/Secure-Close-1005-verify-code.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close() - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close() - return close code is 1005 - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.code, 1005, "CloseEvent.code should be 1005");
+  assert_equals(evt.reason, "", "CloseEvent.reason should be empty");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-1005-verify-code.htm b/websockets/Secure-Close-1005-verify-code.htm
deleted file mode 100644
index 6414b77..0000000
--- a/websockets/Secure-Close-1005-verify-code.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - verify return code is 1005</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close() - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close() - return close code is 1005 - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.code, 1005, "CloseEvent.code should be 1005");
-            assert_equals(evt.reason, "", "CloseEvent.reason should be empty");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-1005.any.js b/websockets/Secure-Close-1005.any.js
new file mode 100644
index 0000000..ddb2c18
--- /dev/null
+++ b/websockets/Secure-Close-1005.any.js
@@ -0,0 +1,13 @@
+// META: script=websocket.sub.js
+
+var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1005) - see '7.1.5.  The WebSocket Connection Close Code' in http://www.ietf.org/rfc/rfc6455.txt");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  assert_throws("INVALID_ACCESS_ERR", function() {
+    wsocket.close(1005, "1005 - reserved code")
+  });
+  test.done();
+}), true);
diff --git a/websockets/Secure-Close-1005.htm b/websockets/Secure-Close-1005.htm
deleted file mode 100644
index de8f51f..0000000
--- a/websockets/Secure-Close-1005.htm
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 1005</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(1005) - see '7.1.5.  The WebSocket Connection Close Code' in http://www.ietf.org/rfc/rfc6455.txt");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            assert_throws("INVALID_ACCESS_ERR", function () { wsocket.close(1005, "1005 - reserved code") });
-            test.done();
-        }), true);
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-2999-reason.any.js b/websockets/Secure-Close-2999-reason.any.js
new file mode 100644
index 0000000..0fa198e
--- /dev/null
+++ b/websockets/Secure-Close-2999-reason.any.js
@@ -0,0 +1,12 @@
+// META: script=websocket.sub.js
+
+var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(2999, reason) - INVALID_ACCESS_ERR is thrown");
+
+var wsocket = CreateWebSocket(true, false, false);
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  assert_throws("INVALID_ACCESS_ERR", function() {
+    wsocket.close(2999, "Close not in range 3000-4999")
+  });
+  test.done();
+}), true);
diff --git a/websockets/Secure-Close-2999-reason.htm b/websockets/Secure-Close-2999-reason.htm
deleted file mode 100644
index 223fc62..0000000
--- a/websockets/Secure-Close-2999-reason.htm
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 2999 and reason</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(2999, reason) - INVALID_ACCESS_ERR is thrown");
-
-        var wsocket = CreateWebSocket(true, false, false);
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            assert_throws("INVALID_ACCESS_ERR", function () { wsocket.close(2999, "Close not in range 3000-4999") });
-            test.done();
-        }), true);
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-3000-reason.any.js b/websockets/Secure-Close-3000-reason.any.js
new file mode 100644
index 0000000..6640ddc
--- /dev/null
+++ b/websockets/Secure-Close-3000-reason.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(3000, "Clean Close with code - 3000");
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-3000-reason.htm b/websockets/Secure-Close-3000-reason.htm
deleted file mode 100644
index 80a8be7..0000000
--- a/websockets/Secure-Close-3000-reason.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 3000 and reason</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(3000, "Clean Close with code - 3000");
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-3000-verify-code.any.js b/websockets/Secure-Close-3000-verify-code.any.js
new file mode 100644
index 0000000..5b122d4
--- /dev/null
+++ b/websockets/Secure-Close-3000-verify-code.any.js
@@ -0,0 +1,19 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - verify return code is 3000 - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(3000, "Clean Close");
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.code, 3000, "CloseEvent.code should be 3000");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-3000-verify-code.htm b/websockets/Secure-Close-3000-verify-code.htm
deleted file mode 100644
index 9d385d9..0000000
--- a/websockets/Secure-Close-3000-verify-code.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 3000 - verify code in CloseEvent is 3000</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(3000, reason) - verify return code is 3000 - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(3000, "Clean Close");
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.code, 3000, "CloseEvent.code should be 3000");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-4999-reason.any.js b/websockets/Secure-Close-4999-reason.any.js
new file mode 100644
index 0000000..d57899e
--- /dev/null
+++ b/websockets/Secure-Close-4999-reason.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(4999, reason) - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(4999, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(3000, "Clean Close with code - 4999");
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-4999-reason.htm b/websockets/Secure-Close-4999-reason.htm
deleted file mode 100644
index a6ea069..0000000
--- a/websockets/Secure-Close-4999-reason.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is 4999 and reason</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(4999, reason) - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(4999, reason) - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(3000, "Clean Close with code - 4999");
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-Reason-124Bytes.any.js b/websockets/Secure-Close-Reason-124Bytes.any.js
new file mode 100644
index 0000000..826cb6e
--- /dev/null
+++ b/websockets/Secure-Close-Reason-124Bytes.any.js
@@ -0,0 +1,15 @@
+// META: script=websocket.sub.js
+
+var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(code, 'reason more than 123 bytes') - SYNTAX_ERR is thrown");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  var reason = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123";
+  assert_equals(reason.length, 124);
+  assert_throws("SYNTAX_ERR", function() {
+    wsocket.close(1000, reason)
+  });
+  test.done();
+}), true);
diff --git a/websockets/Secure-Close-Reason-124Bytes.htm b/websockets/Secure-Close-Reason-124Bytes.htm
deleted file mode 100644
index 94feb0f..0000000
--- a/websockets/Secure-Close-Reason-124Bytes.htm
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Reason is more than 123 bytes long</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(code, 'reason more than 123 bytes') - SYNTAX_ERR is thrown");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            var reason = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123";
-            assert_equals(reason.length, 124);
-            assert_throws("SYNTAX_ERR", function () { wsocket.close(1000, reason) });
-            test.done();
-        }), true);
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-Reason-Unpaired-surrogates.any.js b/websockets/Secure-Close-Reason-Unpaired-surrogates.any.js
new file mode 100644
index 0000000..fdc62c4
--- /dev/null
+++ b/websockets/Secure-Close-Reason-Unpaired-surrogates.any.js
@@ -0,0 +1,21 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+var replacementChar = "\uFFFD";
+var reason = "\uD807";
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close(1000, reason);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be opened");
+  assert_equals(evt.reason, replacementChar, "reason replaced with replacement character");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-Reason-Unpaired-surrogates.htm b/websockets/Secure-Close-Reason-Unpaired-surrogates.htm
deleted file mode 100644
index 6947173..0000000
--- a/websockets/Secure-Close-Reason-Unpaired-surrogates.htm
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Reason with unpaired surrogates</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-        var replacementChar = "\uFFFD";
-        var reason = "\uD807";
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close(1000, reason);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be opened");
-            assert_equals(evt.reason, replacementChar, "reason replaced with replacement character");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
-
diff --git a/websockets/Secure-Close-onlyReason.any.js b/websockets/Secure-Close-onlyReason.any.js
new file mode 100644
index 0000000..79f79b5
--- /dev/null
+++ b/websockets/Secure-Close-onlyReason.any.js
@@ -0,0 +1,12 @@
+// META: script=websocket.sub.js
+
+var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(only reason) - INVALID_ACCESS_ERR is thrown");
+
+var wsocket = CreateWebSocket(true, false, false);
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  assert_throws("INVALID_ACCESS_ERR", function() {
+    wsocket.close("Close with only reason")
+  });
+  test.done();
+}), true);
diff --git a/websockets/Secure-Close-onlyReason.htm b/websockets/Secure-Close-onlyReason.htm
deleted file mode 100644
index 278d7aa..0000000
--- a/websockets/Secure-Close-onlyReason.htm
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Only reason</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - close(only reason) - INVALID_ACCESS_ERR is thrown");
-
-        var wsocket = CreateWebSocket(true, false, false);
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            assert_throws("INVALID_ACCESS_ERR", function () { wsocket.close("Close with only reason") });
-            test.done();
-        }), true);
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-readyState-Closed.any.js b/websockets/Secure-Close-readyState-Closed.any.js
new file mode 100644
index 0000000..3279744
--- /dev/null
+++ b/websockets/Secure-Close-readyState-Closed.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.close();
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-readyState-Closed.htm b/websockets/Secure-Close-readyState-Closed.htm
deleted file mode 100644
index 3ed38a8..0000000
--- a/websockets/Secure-Close-readyState-Closed.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - verify readyState is 3 when onclose is fired</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.close();
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-readyState-Closing.any.js b/websockets/Secure-Close-readyState-Closing.any.js
new file mode 100644
index 0000000..b183474
--- /dev/null
+++ b/websockets/Secure-Close-readyState-Closing.any.js
@@ -0,0 +1,12 @@
+// META: script=websocket.sub.js
+
+var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - readyState should be in CLOSING state just before onclose is called");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  wsocket.close();
+  assert_equals(wsocket.readyState, 2, "readyState should be 2(CLOSING)");
+  test.done();
+}), true);
diff --git a/websockets/Secure-Close-readyState-Closing.htm b/websockets/Secure-Close-readyState-Closing.htm
deleted file mode 100644
index 1048396..0000000
--- a/websockets/Secure-Close-readyState-Closing.htm
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - verify readyState is 2 before onclose is fired</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test("W3C WebSocket API - Create Secure WebSocket - Close the Connection - readyState should be in CLOSING state just before onclose is called");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            wsocket.close();
-            assert_equals(wsocket.readyState, 2, "readyState should be 2(CLOSING)");
-            test.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-server-initiated-close.any.js b/websockets/Secure-Close-server-initiated-close.any.js
new file mode 100644
index 0000000..8531b31
--- /dev/null
+++ b/websockets/Secure-Close-server-initiated-close.any.js
@@ -0,0 +1,20 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Server initiated Close - Client sends back a CLOSE - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Server initiated Close - Client sends back a CLOSE - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send("Goodbye");
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
+  assert_equals(evt.wasClean, true, "wasClean should be TRUE");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Close-server-initiated-close.htm b/websockets/Secure-Close-server-initiated-close.htm
deleted file mode 100644
index 4262754..0000000
--- a/websockets/Secure-Close-server-initiated-close.htm
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Server Initiated close</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create Secure WebSocket - Server initiated Close - Client sends back a CLOSE - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create Secure WebSocket - Server initiated Close - Client sends back a CLOSE - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send("Goodbye");
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(wsocket.readyState, 3, "readyState should be 3(CLOSED)");
-            assert_equals(evt.wasClean, true, "wasClean should be TRUE");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Close-undefined.any.js b/websockets/Secure-Close-undefined.any.js
new file mode 100644
index 0000000..31b36e4
--- /dev/null
+++ b/websockets/Secure-Close-undefined.any.js
@@ -0,0 +1,11 @@
+// META: script=websocket.sub.js
+
+var test = async_test();
+
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', test.step_func(function(evt) {
+  wsocket.close(undefined);
+  test.done();
+}), true);
diff --git a/websockets/Secure-Close-undefined.htm b/websockets/Secure-Close-undefined.htm
deleted file mode 100644
index 9bd4861..0000000
--- a/websockets/Secure-Close-undefined.htm
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Close Secure WebSocket - Code is undefined</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var test = async_test();
-
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', test.step_func(function (evt) {
-            wsocket.close(undefined);
-            test.done();
-        }), true);
-    </script>
-
-</body>
-</html>
diff --git a/websockets/Secure-Send-65K-data.any.js b/websockets/Secure-Send-65K-data.any.js
new file mode 100644
index 0000000..daa937a
--- /dev/null
+++ b/websockets/Secure-Send-65K-data.any.js
@@ -0,0 +1,31 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send  65K data on a Secure WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send 65K data on a Secure WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send 65K data on a Secure WebSocket - Connection should be closed");
+
+var data = "";
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  for (var i = 0; i < 65000; i++) {
+    data = data + "c";
+  }
+  wsocket.send(data);
+  assert_equals(data.length, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-65K-data.htm b/websockets/Secure-Send-65K-data.htm
deleted file mode 100644
index f9bbe37..0000000
--- a/websockets/Secure-Send-65K-data.htm
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send 65K data - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send  65K data on a Secure WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send 65K data on a Secure WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send 65K data on a Secure WebSocket - Connection should be closed");
-
-        var data = "";
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            for (var i = 0; i < 65000; i++) {
-                data = data + "c";
-            }
-            wsocket.send(data);
-            assert_equals(data.length, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-65K-arraybuffer.any.js b/websockets/Secure-Send-binary-65K-arraybuffer.any.js
new file mode 100644
index 0000000..17859e5
--- /dev/null
+++ b/websockets/Secure-Send-binary-65K-arraybuffer.any.js
@@ -0,0 +1,31 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send 65K binary data on a Secure WebSocket - ArrayBuffer - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send 65K binary data on a Secure WebSocket - ArrayBuffer - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send 65K binary data on a Secure WebSocket - ArrayBuffer - Connection should be closed");
+
+var data = "";
+var datasize = 65000;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  wsocket.send(data);
+  assert_equals(datasize, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data.byteLength, datasize);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-65K-arraybuffer.htm b/websockets/Secure-Send-binary-65K-arraybuffer.htm
deleted file mode 100644
index 64cfc02..0000000
--- a/websockets/Secure-Send-binary-65K-arraybuffer.htm
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send 65K binary data - ArrayBuffer - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send 65K binary data on a Secure WebSocket - ArrayBuffer - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send 65K binary data on a Secure WebSocket - ArrayBuffer - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send 65K binary data on a Secure WebSocket - ArrayBuffer - Connection should be closed");
-
-        var data = "";
-        var datasize = 65000;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            wsocket.send(data);
-            assert_equals(datasize, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data.byteLength, datasize);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybuffer.any.js b/websockets/Secure-Send-binary-arraybuffer.any.js
new file mode 100644
index 0000000..6e4c08d
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybuffer.any.js
@@ -0,0 +1,31 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - ArrayBuffer - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - ArrayBuffer - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - ArrayBuffer - Connection should be closed");
+
+var data = "";
+var datasize = 15;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  wsocket.send(data);
+  assert_equals(datasize, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data.byteLength, datasize);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybuffer.htm b/websockets/Secure-Send-binary-arraybuffer.htm
deleted file mode 100644
index 0e068ff..0000000
--- a/websockets/Secure-Send-binary-arraybuffer.htm
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBuffer - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - ArrayBuffer - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - ArrayBuffer - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - ArrayBuffer - Connection should be closed");
-
-        var data = "";
-        var datasize = 15;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            wsocket.send(data);
-            assert_equals(datasize, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data.byteLength, datasize);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybufferview-float32.any.js b/websockets/Secure-Send-binary-arraybufferview-float32.any.js
new file mode 100644
index 0000000..9825d34
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybufferview-float32.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float32Array - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float32Array - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float32Array - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Float32Array(data);
+  for (var i = 0; i < 2; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Float32Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybufferview-float32.htm b/websockets/Secure-Send-binary-arraybufferview-float32.htm
deleted file mode 100644
index 30a8010..0000000
--- a/websockets/Secure-Send-binary-arraybufferview-float32.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Float32Array - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float32Array - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float32Array - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float32Array - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var view;
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            view = new Float32Array(data);
-            for(var i = 0; i < 2; i++) {
-                view[i] = i;
-            }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Float32Array(evt.data);
-            for(var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybufferview-float64.any.js b/websockets/Secure-Send-binary-arraybufferview-float64.any.js
new file mode 100644
index 0000000..4dcac40
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybufferview-float64.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float64Array - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float64Array - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float64Array - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Float64Array(data);
+  for (var i = 0; i < 1; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Float64Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybufferview-float64.htm b/websockets/Secure-Send-binary-arraybufferview-float64.htm
deleted file mode 100644
index 1e121a8..0000000
--- a/websockets/Secure-Send-binary-arraybufferview-float64.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Float32Array - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float64Array - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float64Array - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Float64Array - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var view;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            view = new Float64Array(data);
-            for (var i = 0; i < 1; i++) {
-                view[i] = i;
-            }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Float64Array(evt.data);
-            for(var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybufferview-int32.any.js b/websockets/Secure-Send-binary-arraybufferview-int32.any.js
new file mode 100644
index 0000000..655af21
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybufferview-int32.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int32Array - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int32Array - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int32Array - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Int32Array(data);
+  for (var i = 0; i < 2; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Int32Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybufferview-int32.htm b/websockets/Secure-Send-binary-arraybufferview-int32.htm
deleted file mode 100644
index 316c62c..0000000
--- a/websockets/Secure-Send-binary-arraybufferview-int32.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Int32Array - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int32Array - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int32Array - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int32Array - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var view;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            view = new Int32Array(data);
-            for(var i = 0; i < 2; i++) {
-                view[i] = i;
-            }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Int32Array(evt.data);
-            for(var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint16-offset-length.any.js b/websockets/Secure-Send-binary-arraybufferview-uint16-offset-length.any.js
new file mode 100644
index 0000000..16f050f
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybufferview-uint16-offset-length.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint16Array with offset and length - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint16Array with offset and length - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint16Array with offset and length - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Uint16Array(data, 2, 2);
+  for (var i = 0; i < 4; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Uint16Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint16-offset-length.htm b/websockets/Secure-Send-binary-arraybufferview-uint16-offset-length.htm
deleted file mode 100644
index 1737d93..0000000
--- a/websockets/Secure-Send-binary-arraybufferview-uint16-offset-length.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Uint16Array with offset and length - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint16Array with offset and length - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint16Array with offset and length - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint16Array with offset and length - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var view;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            view = new Uint16Array(data, 2, 2);
-            for(var i = 0; i < 4; i++) {
-                view[i] = i;
-            }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Uint16Array(evt.data);
-            for(var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint32-offset.any.js b/websockets/Secure-Send-binary-arraybufferview-uint32-offset.any.js
new file mode 100644
index 0000000..8976b3d
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybufferview-uint32-offset.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint32Array with offset - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint32Array with offset - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint32Array with offset - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Uint32Array(data, 0);
+  for (var i = 0; i < 2; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Uint32Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint32-offset.htm b/websockets/Secure-Send-binary-arraybufferview-uint32-offset.htm
deleted file mode 100644
index 735bae5..0000000
--- a/websockets/Secure-Send-binary-arraybufferview-uint32-offset.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Uint32Array with offset - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint32Array with offset - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint32Array with offset - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint32Array with offset - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var view;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            view = new Uint32Array(data, 0);
-            for(var i = 0; i < 2; i++) {
-                view[i] = i;
-            }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Uint32Array(evt.data);
-            for(var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint8-offset-length.any.js b/websockets/Secure-Send-binary-arraybufferview-uint8-offset-length.any.js
new file mode 100644
index 0000000..9e9d1b5
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybufferview-uint8-offset-length.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset and length - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset and length - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset and length - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Uint8Array(data, 2, 4);
+  for (var i = 0; i < 8; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Uint8Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint8-offset-length.htm b/websockets/Secure-Send-binary-arraybufferview-uint8-offset-length.htm
deleted file mode 100644
index 83e1435..0000000
--- a/websockets/Secure-Send-binary-arraybufferview-uint8-offset-length.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Uint8Array with offset and length - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset and length - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset and length - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset and length - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var view;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            view = new Uint8Array(data, 2, 4);
-            for(var i = 0; i < 8; i++) {
-                view[i] = i;
-            }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Uint8Array(evt.data);
-            for(var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint8-offset.any.js b/websockets/Secure-Send-binary-arraybufferview-uint8-offset.any.js
new file mode 100644
index 0000000..f563cec
--- /dev/null
+++ b/websockets/Secure-Send-binary-arraybufferview-uint8-offset.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Uint8Array(data, 2);
+  for (var i = 0; i < 8; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Uint8Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-arraybufferview-uint8-offset.htm b/websockets/Secure-Send-binary-arraybufferview-uint8-offset.htm
deleted file mode 100644
index 413ec34..0000000
--- a/websockets/Secure-Send-binary-arraybufferview-uint8-offset.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Uint8Array with offset - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Uint8Array with offset - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-    var view;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-        view = new Uint8Array(data, 2);
-        for(var i = 0; i < 8; i++) {
-        view[i] = i;
-        }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Uint8Array(evt.data);
-        for(var i = 0; i < resultView.length; i++) {
-            assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-        }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-binary-blob.any.js b/websockets/Secure-Send-binary-blob.any.js
new file mode 100644
index 0000000..8bf0f12
--- /dev/null
+++ b/websockets/Secure-Send-binary-blob.any.js
@@ -0,0 +1,34 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - Blob - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - Blob - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - Blob - Connection should be closed");
+
+var data = "";
+var datasize = 65000;
+var isOpenCalled = false;
+
+var wsocket = CreateWebSocket(true, false, false);
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "blob";
+  for (var i = 0; i < datasize; i++)
+    data += String.fromCharCode(0);
+  data = new Blob([data]);
+  isOpenCalled = true;
+  wsocket.send(data);
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_true(evt.data instanceof Blob);
+  assert_equals(evt.data.size, datasize);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_true(evt.wasClean, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-binary-blob.htm b/websockets/Secure-Send-binary-blob.htm
deleted file mode 100644
index 6d52aa7..0000000
--- a/websockets/Secure-Send-binary-blob.htm
+++ /dev/null
@@ -1,48 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - Blob - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - Blob - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - Blob - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a Secure WebSocket - Blob - Connection should be closed");
-
-        var data = "";
-        var datasize = 65000;
-        var isOpenCalled = false;
-
-        var wsocket = CreateWebSocket(true, false, false);
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "blob";
-            for (var i = 0; i < datasize; i++)
-                data += String.fromCharCode(0);
-            data = new Blob([data]);
-            isOpenCalled = true;
-            wsocket.send(data);
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_true(evt.data instanceof Blob);
-            assert_equals(evt.data.size, datasize);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_true(evt.wasClean, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-data.any.js b/websockets/Secure-Send-data.any.js
new file mode 100644
index 0000000..04c720d
--- /dev/null
+++ b/websockets/Secure-Send-data.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send  data on a Secure WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send data on a Secure WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send data on a Secure WebSocket - Connection should be closed");
+
+var data = "Message to send";
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  assert_equals(data.length, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-data.htm b/websockets/Secure-Send-data.htm
deleted file mode 100644
index d7c1595..0000000
--- a/websockets/Secure-Send-data.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send data - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send  data on a Secure WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send data on a Secure WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send data on a Secure WebSocket - Connection should be closed");
-
-        var data = "Message to send";
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            assert_equals(data.length, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-null.any.js b/websockets/Secure-Send-null.any.js
new file mode 100644
index 0000000..7c374c2
--- /dev/null
+++ b/websockets/Secure-Send-null.any.js
@@ -0,0 +1,30 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send null data on a Secure WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send null data on a Secure WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send null data on a Secure WebSocket - Connection should be closed");
+
+var data = null;
+var nullReturned = false;
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  if ("null" == evt.data || "" == evt.data)
+    nullReturned = true;
+  assert_true(nullReturned);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-null.htm b/websockets/Secure-Send-null.htm
deleted file mode 100644
index 0a20335..0000000
--- a/websockets/Secure-Send-null.htm
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send null data - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send null data on a Secure WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send null data on a Secure WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send null data on a Secure WebSocket - Connection should be closed");
-
-        var data = null;
-        var nullReturned = false;
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            if ("null" == evt.data || "" == evt.data)
-                nullReturned = true;
-            assert_true(nullReturned);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-paired-surrogates.any.js b/websockets/Secure-Send-paired-surrogates.any.js
new file mode 100644
index 0000000..073f064
--- /dev/null
+++ b/websockets/Secure-Send-paired-surrogates.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send paired surrogates data on a Secure WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send paired surrogates data on a Secure WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send paired surrogates data on a Secure WebSocket - Connection should be closed");
+
+var data = "\uD801\uDC07";
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  assert_equals(data.length * 2, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-paired-surrogates.htm b/websockets/Secure-Send-paired-surrogates.htm
deleted file mode 100644
index 959e307..0000000
--- a/websockets/Secure-Send-paired-surrogates.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send (paired surrogates) data - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send paired surrogates data on a Secure WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send paired surrogates data on a Secure WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send paired surrogates data on a Secure WebSocket - Connection should be closed");
-
-        var data = "\uD801\uDC07";
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            assert_equals(data.length * 2, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-unicode-data.any.js b/websockets/Secure-Send-unicode-data.any.js
new file mode 100644
index 0000000..a3518c1
--- /dev/null
+++ b/websockets/Secure-Send-unicode-data.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send  unicode data on a Secure WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send unicode data on a Secure WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send unicode data on a Secure WebSocket - Connection should be closed");
+
+var data = "¥¥¥¥¥¥";
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  assert_equals(data.length * 2, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-unicode-data.htm b/websockets/Secure-Send-unicode-data.htm
deleted file mode 100644
index 42de641..0000000
--- a/websockets/Secure-Send-unicode-data.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send unicode data - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send  unicode data on a Secure WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send unicode data on a Secure WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send unicode data on a Secure WebSocket - Connection should be closed");
-
-        var data = "¥¥¥¥¥¥";
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            assert_equals(data.length * 2, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Secure-Send-unpaired-surrogates.any.js b/websockets/Secure-Send-unpaired-surrogates.any.js
new file mode 100644
index 0000000..83f3e7b
--- /dev/null
+++ b/websockets/Secure-Send-unpaired-surrogates.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send unpaired surrogates on a Secure WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send unpaired surrogates on a Secure WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send unpaired surrogates on a Secure WebSocket - Connection should be closed");
+
+var data = "\uD807";
+var replacementChar = "\uFFFD";
+var wsocket = CreateWebSocket(true, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, replacementChar);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Secure-Send-unpaired-surrogates.htm b/websockets/Secure-Send-unpaired-surrogates.htm
deleted file mode 100644
index 11df504..0000000
--- a/websockets/Secure-Send-unpaired-surrogates.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send unpaired surrogates - Secure WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send unpaired surrogates on a Secure WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send unpaired surrogates on a Secure WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send unpaired surrogates on a Secure WebSocket - Connection should be closed");
-
-        var data = "\uD807";
-        var replacementChar = "\uFFFD";
-        var wsocket = CreateWebSocket(true, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, replacementChar);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-0byte-data.any.js b/websockets/Send-0byte-data.any.js
new file mode 100644
index 0000000..131a19d
--- /dev/null
+++ b/websockets/Send-0byte-data.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send 0 byte data on a WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send 0 byte data on a WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send 0 byte data on a WebSocket - Connection should be closed");
+
+var data = "";
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  assert_equals(data.length, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-0byte-data.htm b/websockets/Send-0byte-data.htm
deleted file mode 100644
index 52ab7ca..0000000
--- a/websockets/Send-0byte-data.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send 0 byte data - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send 0 byte data on a WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send 0 byte data on a WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send 0 byte data on a WebSocket - Connection should be closed");
-
-        var data = "";
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            assert_equals(data.length, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-65K-data.any.js b/websockets/Send-65K-data.any.js
new file mode 100644
index 0000000..172e6ee
--- /dev/null
+++ b/websockets/Send-65K-data.any.js
@@ -0,0 +1,31 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send 65K data on a WebSocket -  Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send 65K data on a WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send 65K data on a WebSocket - Connection should be closed");
+
+var data = "";
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  for (var i = 0; i < 65000; i++) {
+    data = data + "c";
+  }
+  wsocket.send(data);
+  assert_equals(data.length, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-65K-data.htm b/websockets/Send-65K-data.htm
deleted file mode 100644
index 7d22340..0000000
--- a/websockets/Send-65K-data.htm
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send 65K data - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send 65K data on a WebSocket -  Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send 65K data on a WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send 65K data on a WebSocket - Connection should be closed");
-
-        var data = "";
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            for (var i = 0; i < 65000; i++) {
-                data = data + "c";
-            }
-            wsocket.send(data);
-            assert_equals(data.length, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-Unpaired-Surrogates.any.js b/websockets/Send-Unpaired-Surrogates.any.js
new file mode 100644
index 0000000..65bb2b1
--- /dev/null
+++ b/websockets/Send-Unpaired-Surrogates.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send unpaired surrogates on a WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send unpaired surrogates on a WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send unpaired surrogates on a WebSocket - Connection should be closed");
+
+var data = "\uD807";
+var replacementChar = "\uFFFD";
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, replacementChar);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-Unpaired-Surrogates.htm b/websockets/Send-Unpaired-Surrogates.htm
deleted file mode 100644
index 6697e68..0000000
--- a/websockets/Send-Unpaired-Surrogates.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send unpaired surrogates - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send unpaired surrogates on a WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send unpaired surrogates on a WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send unpaired surrogates on a WebSocket - Connection should be closed");
-
-        var data = "\uD807";
-        var replacementChar = "\uFFFD";
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, replacementChar);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-before-open.any.js b/websockets/Send-before-open.any.js
new file mode 100644
index 0000000..101a1a2
--- /dev/null
+++ b/websockets/Send-before-open.any.js
@@ -0,0 +1,8 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var wsocket = CreateWebSocket(false, false, false);
+  assert_throws("INVALID_STATE_ERR", function() {
+    wsocket.send("Message to send")
+  });
+}, "W3C WebSocket API - Send data on a WebSocket before connection is opened - INVALID_STATE_ERR is returned")
diff --git a/websockets/Send-before-open.htm b/websockets/Send-before-open.htm
deleted file mode 100644
index 1aace54..0000000
--- a/websockets/Send-before-open.htm
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send WebSocket - Send before connection in established</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-        test(function () {
-            var wsocket = CreateWebSocket(false, false, false);
-            assert_throws("INVALID_STATE_ERR", function () { wsocket.send("Message to send") });
-        }, "W3C WebSocket API - Send data on a WebSocket before connection is opened - INVALID_STATE_ERR is returned")
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-binary-65K-arraybuffer.any.js b/websockets/Send-binary-65K-arraybuffer.any.js
new file mode 100644
index 0000000..f446a25
--- /dev/null
+++ b/websockets/Send-binary-65K-arraybuffer.any.js
@@ -0,0 +1,31 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send 65K binary data on a WebSocket - ArrayBuffer - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send 65K binary data on a WebSocket - ArrayBuffer - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send 65K binary data on a WebSocket - ArrayBuffer - Connection should be closed");
+
+var data = "";
+var datasize = 65000;
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  wsocket.send(data);
+  assert_equals(datasize, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data.byteLength, datasize);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-binary-65K-arraybuffer.htm b/websockets/Send-binary-65K-arraybuffer.htm
deleted file mode 100644
index 95c12cf..0000000
--- a/websockets/Send-binary-65K-arraybuffer.htm
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send 65K binary data - ArrayBuffer - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send 65K binary data on a WebSocket - ArrayBuffer - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send 65K binary data on a WebSocket - ArrayBuffer - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send 65K binary data on a WebSocket - ArrayBuffer - Connection should be closed");
-
-        var data = "";
-        var datasize = 65000;
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            wsocket.send(data);
-            assert_equals(datasize, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data.byteLength, datasize);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-binary-arraybuffer.any.js b/websockets/Send-binary-arraybuffer.any.js
new file mode 100644
index 0000000..6205143
--- /dev/null
+++ b/websockets/Send-binary-arraybuffer.any.js
@@ -0,0 +1,31 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBuffer - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBuffer - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBuffer - Connection should be closed");
+
+var data = "";
+var datasize = 15;
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  wsocket.send(data);
+  assert_equals(datasize, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data.byteLength, datasize);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-binary-arraybuffer.htm b/websockets/Send-binary-arraybuffer.htm
deleted file mode 100644
index b960b92..0000000
--- a/websockets/Send-binary-arraybuffer.htm
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBuffer - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBuffer - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBuffer - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBuffer - Connection should be closed");
-
-        var data = "";
-        var datasize = 15;
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            wsocket.send(data);
-            assert_equals(datasize, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data.byteLength, datasize);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-binary-arraybufferview-int16-offset.any.js b/websockets/Send-binary-arraybufferview-int16-offset.any.js
new file mode 100644
index 0000000..7022668
--- /dev/null
+++ b/websockets/Send-binary-arraybufferview-int16-offset.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int16Array with offset - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int16Array with offset - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int16Array with offset - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var view;
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  view = new Int16Array(data, 2);
+  for (var i = 0; i < 4; i++) {
+    view[i] = i;
+  }
+  wsocket.send(view);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Int16Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-binary-arraybufferview-int16-offset.htm b/websockets/Send-binary-arraybufferview-int16-offset.htm
deleted file mode 100644
index 0ebd3ac..0000000
--- a/websockets/Send-binary-arraybufferview-int16-offset.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Int16Array with offset - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int16Array with offset - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int16Array with offset - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int16Array with offset - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var view;
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            view = new Int16Array(data, 2);
-            for(var i = 0; i < 4; i++) {
-                view[i] = i;
-            }
-            wsocket.send(view);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Int16Array(evt.data);
-            for(var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], view[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-binary-arraybufferview-int8.any.js b/websockets/Send-binary-arraybufferview-int8.any.js
new file mode 100644
index 0000000..242c8c6
--- /dev/null
+++ b/websockets/Send-binary-arraybufferview-int8.any.js
@@ -0,0 +1,38 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int8Array - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int8Array - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int8Array - Connection should be closed");
+
+var data = "";
+var datasize = 8;
+var int8View;
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "arraybuffer";
+  data = new ArrayBuffer(datasize);
+  int8View = new Int8Array(data);
+  for (var i = 0; i < 8; i++) {
+    int8View[i] = i;
+  }
+  wsocket.send(int8View);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  var resultView = new Int8Array(evt.data);
+  for (var i = 0; i < resultView.length; i++) {
+    assert_equals(resultView[i], int8View[i], "ArrayBufferView returned is the same");
+  }
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-binary-arraybufferview-int8.htm b/websockets/Send-binary-arraybufferview-int8.htm
deleted file mode 100644
index 5336a87..0000000
--- a/websockets/Send-binary-arraybufferview-int8.htm
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - ArrayBufferView - Int8Array - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int8Array - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int8Array - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - ArrayBufferView - Int8Array - Connection should be closed");
-
-        var data = "";
-        var datasize = 8;
-        var int8View;
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "arraybuffer";
-            data = new ArrayBuffer(datasize);
-            int8View = new Int8Array(data);
-            for (var i = 0; i < 8; i++) {
-                int8View[i] = i;
-            }
-            wsocket.send(int8View);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            var resultView = new Int8Array(evt.data);
-            for (var i = 0; i < resultView.length; i++) {
-                assert_equals(resultView[i], int8View[i], "ArrayBufferView returned is the same");
-            }
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-binary-blob.any.js b/websockets/Send-binary-blob.any.js
new file mode 100644
index 0000000..ee6486e
--- /dev/null
+++ b/websockets/Send-binary-blob.any.js
@@ -0,0 +1,34 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - Blob - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - Blob - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - Blob - Connection should be closed");
+
+var data = "";
+var datasize = 65000;
+var isOpenCalled = false;
+
+var wsocket = CreateWebSocket(false, false, false);
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.binaryType = "blob";
+  for (var i = 0; i < datasize; i++)
+    data += String.fromCharCode(0);
+  data = new Blob([data]);
+  isOpenCalled = true;
+  wsocket.send(data);
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_true(evt.data instanceof Blob);
+  assert_equals(evt.data.size, datasize);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_true(evt.wasClean, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-binary-blob.htm b/websockets/Send-binary-blob.htm
deleted file mode 100644
index fa14f4c..0000000
--- a/websockets/Send-binary-blob.htm
+++ /dev/null
@@ -1,48 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send binary data - Blob - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send binary data on a WebSocket - Blob - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send binary data on a WebSocket - Blob - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send binary data on a WebSocket - Blob - Connection should be closed");
-
-        var data = "";
-        var datasize = 65000;
-        var isOpenCalled = false;
-
-        var wsocket = CreateWebSocket(false, false, false);
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.binaryType = "blob";
-            for (var i = 0; i < datasize; i++)
-                data += String.fromCharCode(0);
-            data = new Blob([data]);
-            isOpenCalled = true;
-            wsocket.send(data);
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_true(evt.data instanceof Blob);
-            assert_equals(evt.data.size, datasize);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_true(evt.wasClean, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-data.any.js b/websockets/Send-data.any.js
new file mode 100644
index 0000000..487393b
--- /dev/null
+++ b/websockets/Send-data.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send data on a WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send data on a WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send data on a WebSocket - Connection should be closed");
+
+var data = "Message to send";
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  assert_equals(data.length, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-data.htm b/websockets/Send-data.htm
deleted file mode 100644
index 547a15f..0000000
--- a/websockets/Send-data.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send data - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send data on a WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send data on a WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send data on a WebSocket - Connection should be closed");
-
-        var data = "Message to send";
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            assert_equals(data.length, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-null.any.js b/websockets/Send-null.any.js
new file mode 100644
index 0000000..8d8d6b7
--- /dev/null
+++ b/websockets/Send-null.any.js
@@ -0,0 +1,30 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send null data on a WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send null data on a WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send null data on a WebSocket - Connection should be closed");
+
+var data = null;
+var nullReturned = false;
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  if ("null" == evt.data || "" == evt.data)
+    nullReturned = true;
+  assert_true(nullReturned);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-null.htm b/websockets/Send-null.htm
deleted file mode 100644
index 5b7a6f1..0000000
--- a/websockets/Send-null.htm
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send null data - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send null data on a WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send null data on a WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send null data on a WebSocket - Connection should be closed");
-
-        var data = null;
-        var nullReturned = false;
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            if ("null" == evt.data || "" == evt.data)
-                nullReturned = true;
-            assert_true(nullReturned);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-paired-surrogates.any.js b/websockets/Send-paired-surrogates.any.js
new file mode 100644
index 0000000..f12b001
--- /dev/null
+++ b/websockets/Send-paired-surrogates.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send (paired surrogates) data on a WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send (paired surrogates) data on a WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send (paired surrogates) data on a WebSocket - Connection should be closed");
+
+var data = "\uD801\uDC07";
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  assert_equals(data.length * 2, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-paired-surrogates.htm b/websockets/Send-paired-surrogates.htm
deleted file mode 100644
index 4413221..0000000
--- a/websockets/Send-paired-surrogates.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send (paired surrogates) data - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send (paired surrogates) data on a WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send (paired surrogates) data on a WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send (paired surrogates) data on a WebSocket - Connection should be closed");
-
-        var data = "\uD801\uDC07";
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            assert_equals(data.length * 2, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/Send-unicode-data.any.js b/websockets/Send-unicode-data.any.js
new file mode 100644
index 0000000..ce2b9a0
--- /dev/null
+++ b/websockets/Send-unicode-data.any.js
@@ -0,0 +1,28 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Send unicode data on a WebSocket - Connection should be opened");
+var testMessage = async_test("W3C WebSocket API - Send unicode data on a WebSocket - Message should be received");
+var testClose = async_test("W3C WebSocket API - Send unicode data on a WebSocket - Connection should be closed");
+
+var data = "¥¥¥¥¥¥";
+var wsocket = CreateWebSocket(false, false, false);
+var isOpenCalled = false;
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  wsocket.send(data);
+  assert_equals(data.length * 2, wsocket.bufferedAmount);
+  isOpenCalled = true;
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('message', testMessage.step_func(function(evt) {
+  assert_equals(evt.data, data);
+  wsocket.close();
+  testMessage.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(isOpenCalled, "WebSocket connection should be open");
+  assert_equals(evt.wasClean, true, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/Send-unicode-data.htm b/websockets/Send-unicode-data.htm
deleted file mode 100644
index aba7918..0000000
--- a/websockets/Send-unicode-data.htm
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Send unicode data - WebSocket</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Send unicode data on a WebSocket - Connection should be opened");
-        var testMessage = async_test("W3C WebSocket API - Send unicode data on a WebSocket - Message should be received");
-        var testClose = async_test("W3C WebSocket API - Send unicode data on a WebSocket - Connection should be closed");
-
-        var data = "¥¥¥¥¥¥";
-        var wsocket = CreateWebSocket(false, false, false);
-        var isOpenCalled = false;
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            wsocket.send(data);
-            assert_equals(data.length * 2, wsocket.bufferedAmount);
-            isOpenCalled = true;
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('message', testMessage.step_func(function (evt) {
-            assert_equals(evt.data, data);
-            wsocket.close();
-            testMessage.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(isOpenCalled, "WebSocket connection should be open");
-            assert_equals(evt.wasClean, true, "wasClean should be true");
-            testClose.done();
-        }), true);
-    </script>
-</body>
-</html>
diff --git a/websockets/binaryType-wrong-value.any.js b/websockets/binaryType-wrong-value.any.js
new file mode 100644
index 0000000..6030608
--- /dev/null
+++ b/websockets/binaryType-wrong-value.any.js
@@ -0,0 +1,19 @@
+// META: script=websocket.sub.js
+
+var testOpen = async_test("W3C WebSocket API - Create WebSocket - set binaryType to something other than blob or arraybuffer - SYNTAX_ERR is returned - Connection should be opened");
+var testClose = async_test("W3C WebSocket API - Create WebSocket - set binaryType to something other than blob or arraybuffer - SYNTAX_ERR is returned - Connection should be closed");
+
+var wsocket = CreateWebSocket(true, false, false);
+
+wsocket.addEventListener('open', testOpen.step_func(function(evt) {
+  assert_equals(wsocket.binaryType, "blob");
+  wsocket.binaryType = "notBlobOrArrayBuffer";
+  assert_equals(wsocket.binaryType, "blob");
+  wsocket.close();
+  testOpen.done();
+}), true);
+
+wsocket.addEventListener('close', testClose.step_func(function(evt) {
+  assert_true(evt.wasClean, "wasClean should be true");
+  testClose.done();
+}), true);
diff --git a/websockets/binaryType-wrong-value.htm b/websockets/binaryType-wrong-value.htm
deleted file mode 100644
index 2bab671..0000000
--- a/websockets/binaryType-wrong-value.htm
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>W3C WebSocket API - Create Secure WebSocket - binaryType is set to something other than blob/arraybuffer</title>
-    <script type="text/javascript" src="/resources/testharness.js"></script>
-    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
-    <script type="text/javascript" src="websocket.sub.js"></script>
-</head>
-<body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
-        var testOpen = async_test("W3C WebSocket API - Create WebSocket - set binaryType to something other than blob or arraybuffer - SYNTAX_ERR is returned - Connection should be opened");
-        var testClose = async_test("W3C WebSocket API - Create WebSocket - set binaryType to something other than blob or arraybuffer - SYNTAX_ERR is returned - Connection should be closed");
-
-        var wsocket = CreateWebSocket(true, false, false);
-
-        wsocket.addEventListener('open', testOpen.step_func(function (evt) {
-            assert_equals(wsocket.binaryType, "blob");
-            wsocket.binaryType = "notBlobOrArrayBuffer";
-            assert_equals(wsocket.binaryType, "blob");
-            wsocket.close();
-            testOpen.done();
-        }), true);
-
-        wsocket.addEventListener('close', testClose.step_func(function (evt) {
-            assert_true(evt.wasClean, "wasClean should be true");
-            testClose.done();
-        }), true);
-
-    </script>
-
-</body>
-</html>
diff --git a/websockets/close-invalid.any.js b/websockets/close-invalid.any.js
index a930495..8ac1da3 100644
--- a/websockets/close-invalid.any.js
+++ b/websockets/close-invalid.any.js
@@ -1,20 +1,20 @@
 // META: script=websocket.sub.js
 
 [
-    [0, "0"],
-    [500, "500"],
-    [NaN, "NaN"],
-    ["string", "String"],
-    [null, "null"],
-    [0x10000 + 1000, "2**16+1000"],
+  [0, "0"],
+  [500, "500"],
+  [NaN, "NaN"],
+  ["string", "String"],
+  [null, "null"],
+  [0x10000 + 1000, "2**16+1000"],
 ].forEach(function(t) {
-    [true, false].forEach(function(secure) {
-        test(function() {
-            var ws = CreateWebSocket(secure, false, false);
-            assert_throws("InvalidAccessError", function () {
-                ws.close(t[0]);
-            });
-            wsocket.onerror = this.unreached_func();
-        }, t[1] + " on a " + (secure ? "secure" : "insecure") + " websocket");
-    });
+  [true, false].forEach(function(secure) {
+    test(function() {
+      var ws = CreateWebSocket(secure, false, false);
+      assert_throws("InvalidAccessError", function() {
+        ws.close(t[0]);
+      });
+      wsocket.onerror = this.unreached_func();
+    }, t[1] + " on a " + (secure ? "secure" : "insecure") + " websocket");
+  });
 });
diff --git a/websockets/constants.js b/websockets/constants.js
index 8312cd2..cb17767 100644
--- a/websockets/constants.js
+++ b/websockets/constants.js
@@ -1,8 +1,7 @@
 //This file requires server-side substitutions and must be included as constants.js?pipe=sub
 
 var PORT = "{{ports[ws][0]}}";
-//FIXME: Add support for wss
-var PORT_SSL = "{{ports[ws][0]}}";
+var PORT_SSL = "{{ports[wss][0]}}";
 
 var SCHEME_DOMAIN_PORT;
 if (location.search == '?wss') {
diff --git a/websockets/constructor.any.js b/websockets/constructor.any.js
new file mode 100644
index 0000000..0605d5e
--- /dev/null
+++ b/websockets/constructor.any.js
@@ -0,0 +1,7 @@
+// META: script=websocket.sub.js
+
+test(function() {
+  var ws = new WebSocket("ws://" + __SERVER__NAME + ":" + __PORT + "/" + __PATH,
+    "echo", "Stray argument")
+  assert_true(ws instanceof WebSocket, "Expected a WebSocket instance.")
+}, "Calling the WebSocket constructor with too many arguments should not throw.")
diff --git a/websockets/constructor.html b/websockets/constructor.html
deleted file mode 100644
index c135b32..0000000
--- a/websockets/constructor.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<title>WebSocket constructor</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="websocket.sub.js"></script>
-<div id="log"></div>
-<script>
-test(function() {
-  var ws = new WebSocket("ws://" + __SERVER__NAME + ":" + __PORT + "/" + __PATH,
-                         "echo", "Stray argument")
-  assert_true(ws instanceof WebSocket, "Expected a WebSocket instance.")
-}, "Calling the WebSocket constructor with too many arguments should not throw.")
-</script>
diff --git a/websockets/eventhandlers.any.js b/websockets/eventhandlers.any.js
new file mode 100644
index 0000000..b30b0b6
--- /dev/null
+++ b/websockets/eventhandlers.any.js
@@ -0,0 +1,13 @@
+// META: script=websocket.sub.js
+
+function testEventHandler(name) {
+  test(function() {
+    var ws = new WebSocket("ws://" + __SERVER__NAME + ":" + __PORT + "/" + __PATH,
+      "echo")
+    assert_equals(ws["on" + name], null);
+    ws["on" + name] = function() {};
+    ws["on" + name] = 2;
+    assert_equals(ws["on" + name], null);
+  }, "Event handler for " + name + " should have [TreatNonCallableAsNull]")
+}
+["open", "error", "close", "message"].forEach(testEventHandler);
diff --git a/websockets/eventhandlers.html b/websockets/eventhandlers.html
deleted file mode 100644
index 95aeb0b..0000000
--- a/websockets/eventhandlers.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<title>WebSocket event handlers</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="websocket.sub.js"></script>
-<div id="log"></div>
-<script>
-function testEventHandler(name) {
-  test(function() {
-    var ws = new WebSocket("ws://" + __SERVER__NAME + ":" + __PORT + "/" + __PATH,
-                           "echo")
-    assert_equals(ws["on" + name], null);
-    ws["on" + name] = function() {};
-    ws["on" + name] = 2;
-    assert_equals(ws["on" + name], null);
-  }, "Event handler for " + name + " should have [TreatNonCallableAsNull]")
-}
-["open", "error", "close", "message"].forEach(testEventHandler);
-</script>
diff --git a/websockets/interfaces/WebSocket/bufferedAmount/bufferedAmount-defineProperty-getter.html b/websockets/interfaces/WebSocket/bufferedAmount/bufferedAmount-defineProperty-getter.html
index a6bd530..b564da2 100644
--- a/websockets/interfaces/WebSocket/bufferedAmount/bufferedAmount-defineProperty-getter.html
+++ b/websockets/interfaces/WebSocket/bufferedAmount/bufferedAmount-defineProperty-getter.html
@@ -3,7 +3,7 @@
 <title>WebSockets: defineProperty getter for bufferedAmount</title>
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
-<script src=../../../../constants.js?pipe=sub></script>
+<script src=../../../constants.js?pipe=sub></script>
 <meta name="variant" content="">
 <meta name="variant" content="?wss">
 <div id=log></div>
diff --git a/webstorage/eventTestHarness.js b/webstorage/eventTestHarness.js
index 7d9ed01..f39dfda 100644
--- a/webstorage/eventTestHarness.js
+++ b/webstorage/eventTestHarness.js
@@ -5,6 +5,10 @@
 
 storageEventList = new Array();
 iframe.contentWindow.onstorage = function(e) {
+    if (iframe.contentWindow.sessionStorage === e.storageArea)
+      e.storageAreaString = "sessionStorage";
+    else if (iframe.contentWindow.localStorage === e.storageArea)
+      e.storageAreaString = "localStorage";
     window.parent.storageEventList.push(e);
 };
 
diff --git a/webstorage/event_basic.js b/webstorage/event_basic.js
index fe8446c..9f0c11b 100644
--- a/webstorage/event_basic.js
+++ b/webstorage/event_basic.js
@@ -20,6 +20,8 @@
                 assert_unreached(msg);
             }
             assert_equals(storageEventList.length, 1);
+            assert_equals(storageEventList[0].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[0].key, "FOO");
             assert_equals(storageEventList[0].oldValue, null);
             assert_equals(storageEventList[0].newValue, "BAR");
@@ -38,18 +40,26 @@
                 assert_unreached(msg);
             }
             assert_equals(storageEventList.length, 5);
+            assert_equals(storageEventList[1].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[1].key, "FU");
             assert_equals(storageEventList[1].oldValue, null);
             assert_equals(storageEventList[1].newValue, "BAR");
 
+            assert_equals(storageEventList[2].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[2].key, "a");
             assert_equals(storageEventList[2].oldValue, null);
             assert_equals(storageEventList[2].newValue, "1");
 
+            assert_equals(storageEventList[3].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[3].key, "b");
             assert_equals(storageEventList[3].oldValue, null);
             assert_equals(storageEventList[3].newValue, "2");
 
+            assert_equals(storageEventList[4].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[4].key, "b");
             assert_equals(storageEventList[4].oldValue, "2");
             assert_equals(storageEventList[4].newValue, "3");
@@ -65,6 +75,8 @@
                 assert_unreached(msg);
             }
             assert_equals(storageEventList.length, 6);
+            assert_equals(storageEventList[5].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[5].key, "FOO");
             assert_equals(storageEventList[5].oldValue, "BAR");
             assert_equals(storageEventList[5].newValue, null);
@@ -80,6 +92,8 @@
                 assert_unreached(msg);
             }
             assert_equals(storageEventList.length, 7);
+            assert_equals(storageEventList[6].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[6].key, "FU");
             assert_equals(storageEventList[6].oldValue, "BAR");
             assert_equals(storageEventList[6].newValue, null);
@@ -95,6 +109,8 @@
                 assert_unreached(msg);
             }
             assert_equals(storageEventList.length, 8);
+            assert_equals(storageEventList[7].storageAreaString, storageString,
+                "Storage event came from wrong storage type.");
             assert_equals(storageEventList[7].key, null);
             assert_equals(storageEventList[7].oldValue, null);
             assert_equals(storageEventList[7].newValue, null);
diff --git a/webstorage/idlharness.html b/webstorage/idlharness.html
index 454e441..efff830 100644
--- a/webstorage/idlharness.html
+++ b/webstorage/idlharness.html
@@ -15,15 +15,9 @@
 <div id="log"></div>
 
 <pre id='untested_idl' style='display:none'>
-[PrimaryGlobal]
+[Global=Window, Exposed=Window]
 interface Window {
 };
-
-interface Event {
-};
-
-interface EventInit {
-};
 </pre>
 
 <pre id='idl'>
@@ -64,18 +58,26 @@
 </pre>
 
 <script>
-
-(function() {
+function do_test([html, dom]) {
   var idl_array = new IdlArray();
 
+  idl_array.add_untested_idls(dom, { only: ['Event', 'EventInit'] });
+  idl_array.add_untested_idls(html, { only: ['EventStorageInit'] });
   idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
   idl_array.add_idls(document.getElementById("idl").textContent);
 
   idl_array.add_objects({Storage: ["window.localStorage"]});
 
   idl_array.test();
-})();
+}
 
+function fetch_text(url) {
+  return fetch(url).then(response => response.text());
+}
+
+promise_test(function () {
+  return Promise.all(['/interfaces/html.idl', '/interfaces/dom.idl'].map(fetch_text)).then(do_test);
+}, 'webstorage interfaces');
 </script>
 </body>
 </html>
diff --git a/webusb/idlharness.https.html b/webusb/idlharness.https.html
index f69c53c..a256878 100644
--- a/webusb/idlharness.https.html
+++ b/webusb/idlharness.https.html
@@ -35,6 +35,7 @@
         idl_array.add_untested_idls('dictionary EventInit {};');
         idl_array.add_untested_idls('interface EventTarget {};');
         idl_array.add_untested_idls('interface Navigator {};');
+        idl_array.add_untested_idls('interface WorkerNavigator {};');
 
         let {device} = await getFakeDevice();
 
diff --git a/webusb/resources/idlharness.dedicatedworker.sharedworker.js b/webusb/resources/idlharness.dedicatedworker.sharedworker.js
new file mode 100644
index 0000000..99eb5a1
--- /dev/null
+++ b/webusb/resources/idlharness.dedicatedworker.sharedworker.js
@@ -0,0 +1,61 @@
+'use strict';
+importScripts('/resources/testharness.js');
+importScripts('/resources/WebIDLParser.js');
+importScripts('/resources/idlharness.js');
+importScripts('/webusb/resources/fake-devices.js');
+importScripts('/webusb/resources/usb-helpers.js');
+
+// Object instances used by the IDL test.
+var usbDevice;
+var usbConfiguration;
+var usbInterface;
+var usbAlternateInterface;
+var usbEndpoint;
+var usbConnectionEvent;
+
+usb_test(async () => {
+  let webUSBResponse = await fetch('/interfaces/webusb.idl');
+  let domResponse = await fetch('/interfaces/dom.idl');
+  let webusb_idl_text = await webUSBResponse.text();
+  let dom_idl_text = await domResponse.text();
+  let idl_array = new IdlArray();
+  idl_array.add_idls(webusb_idl_text);
+
+  // Untested IDL interfaces
+  idl_array.add_untested_idls(dom_idl_text, { only: ['Event', 'EventTarget'] });
+  idl_array.add_untested_idls('interface EventHandler {};');
+  idl_array.add_untested_idls('dictionary EventInit {};');
+  idl_array.add_untested_idls('interface Navigator {};');
+  idl_array.add_untested_idls('interface WorkerNavigator {};');
+
+  let {device} = await getFakeDevice();
+
+  usbDevice = device;
+  usbConfiguration = usbDevice.configurations[0];
+  usbInterface = usbConfiguration.interfaces[0];
+  usbAlternateInterface = usbInterface.alternates[0];
+  usbEndpoint = usbAlternateInterface.endpoints[0];
+  usbConnectionEvent =
+      new USBConnectionEvent('connect', { device: usbDevice })
+
+  idl_array.add_objects({
+    WorkerNavigator: ['navigator'],
+    USB: ['navigator.usb'],
+    USBAlternateInterface: ['usbAlternateInterface'],
+    USBConfiguration: ['usbConfiguration'],
+    USBConnectionEvent: ['usbConnectionEvent'],
+    USBDevice: ['usbDevice'],
+    USBEndpoint: ['usbEndpoint'],
+    USBInterface: ['usbInterface'],
+    USBInTransferResult: ['new USBInTransferResult("ok")'],
+    USBOutTransferResult: ['new USBOutTransferResult("ok")'],
+    USBIsochronousInTransferResult: ['new USBIsochronousInTransferResult([])'],
+    USBIsochronousOutTransferResult: ['new USBIsochronousOutTransferResult([])'],
+    USBIsochronousInTransferPacket: ['new USBIsochronousInTransferPacket("ok")'],
+    USBIsochronousOutTransferPacket: ['new USBIsochronousOutTransferPacket("ok")'],
+  });
+
+  idl_array.test();
+}, 'WebUSB on Workers IDL test');
+
+done();
\ No newline at end of file
diff --git a/webusb/resources/usb-helpers.js b/webusb/resources/usb-helpers.js
index d3b0501..81a712c 100644
--- a/webusb/resources/usb-helpers.js
+++ b/webusb/resources/usb-helpers.js
@@ -24,6 +24,11 @@
     '/resources/chromium/chooser_service.mojom.js',
     '/resources/chromium/webusb-test.js',
   ].forEach(path => {
+    // Use importScripts for workers.
+    if (typeof document === 'undefined') {
+      chain = chain.then(() => importScripts(path));
+      return;
+    }
     let script = document.createElement('script');
     script.src = path;
     script.async = false;
diff --git a/webusb/resources/usb.dedicatedworker.sharedworker.js b/webusb/resources/usb.dedicatedworker.sharedworker.js
new file mode 100644
index 0000000..b1b2a43
--- /dev/null
+++ b/webusb/resources/usb.dedicatedworker.sharedworker.js
@@ -0,0 +1,59 @@
+'use strict';
+importScripts('/resources/testharness.js');
+importScripts('/webusb/resources/fake-devices.js');
+importScripts('/webusb/resources/usb-helpers.js');
+
+let usbDevice, devicesFirstTime, fakeDevice, removedDevice;
+
+test(() => {
+  assert_true(navigator.usb instanceof USB,
+      'navigator.usb should be defined as a USB object');
+  assert_true(navigator.usb.getDevices instanceof Function,
+      'navigator.usb.getDevices should be defined as a function');
+  assert_equals(typeof navigator.usb.requestDevice, 'undefined',
+      'navigator.usb.requestDevice should not be defined');
+}, 'Web workers should have the WebUSB API exposed as defined in the spec.');
+
+usb_test(() => getFakeDevice()
+    .then(_ => usbDevice = _.device)
+    .then(() => navigator.usb.getDevices())
+    .then(devices => {
+      assert_equals(devices.length, 1);
+      assert_equals(usbDevice, devices[0]);
+      assertDeviceInfoEquals(devices[0], fakeDeviceInit);
+    }), 'getDevices returns devices that are connected');
+
+usb_test(() => getFakeDevice()
+    .then(() => navigator.usb.getDevices())
+    .then(_ => devicesFirstTime = _)
+    .then(() => assert_equals(devicesFirstTime.length, 1))
+    .then(() => navigator.usb.getDevices())
+    .then(devicesSecondTime => assert_array_equals(devicesSecondTime,
+        devicesFirstTime)),
+    'getDevices returns the same objects for each USB device');
+
+usb_test(() => getFakeDevice()
+    .then(_ => usbDevice = _.device)
+    .then(() => assertDeviceInfoEquals(usbDevice, fakeDeviceInit))
+    .then(() => usbDevice.open())
+    .then(() => usbDevice.close()),
+    'onconnect event is trigged by adding a device');
+
+usb_test(() => getFakeDevice()
+    .then(_ => {
+      usbDevice = _.device;
+      fakeDevice = _.fakeDevice;
+    })
+    .then(() => waitForDisconnect(fakeDevice))
+    .then(_ => removedDevice = _)
+    .then(() => {
+      assertDeviceInfoEquals(removedDevice, fakeDeviceInit);
+      assert_equals(removedDevice, usbDevice);
+    })
+    .then(() => removedDevice.open())
+    .then(() =>
+        assert_unreachable('should not be able to open a disconnected device'),
+        error => assert_equals(error.code, DOMException.NOT_FOUND_ERR)),
+    'ondisconnect event is triggered by removing a device');
+
+done();
\ No newline at end of file
diff --git a/webusb/resources/usb.serviceworker.js b/webusb/resources/usb.serviceworker.js
new file mode 100644
index 0000000..c509adf
--- /dev/null
+++ b/webusb/resources/usb.serviceworker.js
@@ -0,0 +1,9 @@
+'use strict';
+importScripts('/resources/testharness.js');
+
+test(() => {
+  assert_equals(typeof navigator.usb, 'undefined',
+      'navigator.usb should not be a USB object');
+}, 'Service workers should not have access to the WebUSB API.');
+
+done();
\ No newline at end of file
diff --git a/webusb/usb.https.html b/webusb/usb.https.html
index 9ead12a..0583079 100644
--- a/webusb/usb.https.html
+++ b/webusb/usb.https.html
@@ -130,4 +130,24 @@
     });
   });
 }, 'ondisconnect event is triggered by removing a device');
+
+usb_test(() => {
+  return getFakeDevice().then(({ device, fakeDevice }) => {
+    navigator.usb.test.onrequestdevice = event => {
+      event.respondWith(fakeDevice);
+    }
+    return callWithTrustedClick(() => {
+      let first = navigator.usb.requestDevice({ filters: [] });
+      let second = navigator.usb.requestDevice({ filters: [] });
+      return Promise.all([
+        first.then(chosenDevice => {
+          assert_equals(chosenDevice, device);
+        }),
+        second.then(chosenDevice => {
+          assert_equals(chosenDevice, device);
+        })
+      ]);
+    });
+  });
+}, 'multiple requestDevice calls are allowed per user activation');
 </script>
diff --git a/webusb/worker/idlharness.dedicatedworker.https.html b/webusb/worker/idlharness.dedicatedworker.https.html
new file mode 100644
index 0000000..9ff2183
--- /dev/null
+++ b/webusb/worker/idlharness.dedicatedworker.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+fetch_tests_from_worker(new Worker(
+    '/webusb/resources/idlharness.dedicatedworker.sharedworker.js'));
+
+</script>
diff --git a/webusb/worker/idlharness.sharedworker.https.html b/webusb/worker/idlharness.sharedworker.https.html
new file mode 100644
index 0000000..362e182
--- /dev/null
+++ b/webusb/worker/idlharness.sharedworker.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+fetch_tests_from_worker(new SharedWorker(
+    '/webusb/resources/idlharness.dedicatedworker.sharedworker.js'));
+
+</script>
\ No newline at end of file
diff --git a/webusb/worker/usb.dedicatedworker.https.html b/webusb/worker/usb.dedicatedworker.https.html
new file mode 100644
index 0000000..e3a0f98
--- /dev/null
+++ b/webusb/worker/usb.dedicatedworker.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+fetch_tests_from_worker(new Worker(
+    '/webusb/resources/usb.dedicatedworker.sharedworker.js'));
+
+</script>
diff --git a/webusb/worker/usb.serviceworker.https.html b/webusb/worker/usb.serviceworker.https.html
new file mode 100644
index 0000000..a5600e3
--- /dev/null
+++ b/webusb/worker/usb.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+service_worker_test('/webusb/resources/usb.serviceworker.js',
+    'Ensure that WebUSB is inaccessible from a service worker.');
+
+</script>
diff --git a/webusb/worker/usb.sharedworker.https.html b/webusb/worker/usb.sharedworker.https.html
new file mode 100644
index 0000000..5779b7f
--- /dev/null
+++ b/webusb/worker/usb.sharedworker.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+fetch_tests_from_worker(new SharedWorker(
+    '/webusb/resources/usb.dedicatedworker.sharedworker.js'));
+
+</script>
diff --git a/webvr/idlharness.html b/webvr/idlharness.html
deleted file mode 100644
index ecb89f3..0000000
--- a/webvr/idlharness.html
+++ /dev/null
@@ -1,240 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>WebVR IDL test</title>
-    <link rel="help" href="https://w3c.github.io/webvr/">
-
-    <script src=/resources/testharness.js></script>
-    <script src=/resources/testharnessreport.js></script>
-    <script src=/resources/WebIDLParser.js></script>
-    <script src=/resources/idlharness.js></script>
-<script id="webvr_idl" type=text/plain>
-// Archived version of the WebVR spec from
-// https://w3c.github.io/webvr/archive/prerelease/1.1/index.html
-
-interface VRDisplay : EventTarget {
-  readonly attribute boolean isPresenting;
-
-  /**
-   * Dictionary of capabilities describing the VRDisplay.
-   */
-  [SameObject] readonly attribute VRDisplayCapabilities capabilities;
-
-  /**
-   * If this VRDisplay supports room-scale experiences, the optional
-   * stage attribute contains details on the room-scale parameters.
-   * The stageParameters attribute can not change between null
-   * and non-null once the VRDisplay is enumerated; however,
-   * the values within VRStageParameters may change after
-   * any call to VRDisplay.submitFrame as the user may re-configure
-   * their environment at any time.
-   */
-  readonly attribute VRStageParameters? stageParameters;
-
-  /**
-   * Return the current VREyeParameters for the given eye.
-   */
-  VREyeParameters getEyeParameters(VREye whichEye);
-
-  /**
-   * An identifier for this distinct VRDisplay. Used as an
-   * association point in the Gamepad API.
-   */
-  readonly attribute unsigned long displayId;
-
-  /**
-   * A display name, a user-readable name identifying it.
-   */
-  readonly attribute DOMString displayName;
-
-  /**
-   * Populates the passed VRFrameData with the information required to render
-   * the current frame.
-   */
-  boolean getFrameData(VRFrameData frameData);
-
-  /**
-   * z-depth defining the near plane of the eye view frustum
-   * enables mapping of values in the render target depth
-   * attachment to scene coordinates. Initially set to 0.01.
-   */
-  attribute double depthNear;
-
-  /**
-   * z-depth defining the far plane of the eye view frustum
-   * enables mapping of values in the render target depth
-   * attachment to scene coordinates. Initially set to 10000.0.
-   */
-  attribute double depthFar;
-
-  /**
-   * The callback passed to `requestAnimationFrame` will be called
-   * any time a new frame should be rendered. When the VRDisplay is
-   * presenting the callback will be called at the native refresh
-   * rate of the HMD. When not presenting this function acts
-   * identically to how window.requestAnimationFrame acts. Content should
-   * make no assumptions of frame rate or vsync behavior as the HMD runs
-   * asynchronously from other displays and at differing refresh rates.
-   */
-  long requestAnimationFrame(FrameRequestCallback callback);
-
-  /**
-   * Passing the value returned by `requestAnimationFrame` to
-   * `cancelAnimationFrame` will unregister the callback.
-   */
-  void cancelAnimationFrame(long handle);
-
-  /**
-   * Begin presenting to the VRDisplay. Must be called in response to a user gesture.
-   * Repeat calls while already presenting will update the layers being displayed.
-   * If the number of values in the leftBounds/rightBounds arrays is not 0 or 4 for any of the passed layers the promise is rejected
-   * If the source of any of the layers is not present (null), the promise is rejected.
-   */
-  Promise<void> requestPresent(sequence<VRLayerInit> layers);
-
-  /**
-   * Stops presenting to the VRDisplay.
-   */
-  Promise<void> exitPresent();
-
-  /**
-   * Get the layers currently being presented.
-   */
-  sequence<VRLayerInit> getLayers();
-
-  /**
-   * The layer provided to the VRDisplay will be captured and presented
-   * in the HMD. Calling this function has the same effect on the source
-   * canvas as any other operation that uses its source image, and canvases
-   * created without preserveDrawingBuffer set to true will be cleared.
-   */
-  void submitFrame();
-};
-
-typedef (HTMLCanvasElement or
-         OffscreenCanvas) VRSource;
-
-dictionary VRLayerInit {
-  VRSource? source = null;
-
-  sequence<float> leftBounds = [];
-  sequence<float> rightBounds = [];
-};
-
-interface VRDisplayCapabilities {
-  readonly attribute boolean hasPosition;
-  readonly attribute boolean hasExternalDisplay;
-  readonly attribute boolean canPresent;
-  readonly attribute unsigned long maxLayers;
-};
-
-enum VREye {
-  "left",
-  "right"
-};
-
-interface VRPose {
-  readonly attribute Float32Array? position;
-  readonly attribute Float32Array? linearVelocity;
-  readonly attribute Float32Array? linearAcceleration;
-
-  readonly attribute Float32Array? orientation;
-  readonly attribute Float32Array? angularVelocity;
-  readonly attribute Float32Array? angularAcceleration;
-};
-
-[Constructor]
-interface VRFrameData {
-  readonly attribute Float32Array leftProjectionMatrix;
-  readonly attribute Float32Array leftViewMatrix;
-
-  readonly attribute Float32Array rightProjectionMatrix;
-  readonly attribute Float32Array rightViewMatrix;
-
-  readonly attribute VRPose pose;
-};
-
-interface VREyeParameters {
-  readonly attribute Float32Array offset;
-
-  readonly attribute unsigned long renderWidth;
-  readonly attribute unsigned long renderHeight;
-};
-
-interface VRStageParameters {
-  readonly attribute Float32Array sittingToStandingTransform;
-
-  readonly attribute float sizeX;
-  readonly attribute float sizeZ;
-};
-
-partial interface Navigator {
-  Promise<sequence<VRDisplay>> getVRDisplays();
-  readonly attribute FrozenArray<VRDisplay> activeVRDisplays;
-  readonly attribute boolean vrEnabled;
-};
-
-enum VRDisplayEventReason {
-  "mounted",
-  "navigation",
-  "requested",
-  "unmounted"
-};
-
-[Constructor(DOMString type, VRDisplayEventInit eventInitDict)]
-interface VRDisplayEvent : Event {
-  readonly attribute VRDisplay display;
-  readonly attribute VRDisplayEventReason? reason;
-};
-
-dictionary VRDisplayEventInit : EventInit {
-  required VRDisplay display;
-  VRDisplayEventReason reason;
-};
-
-partial interface Window {
-  attribute EventHandler onvrdisplayconnect;
-  attribute EventHandler onvrdisplaydisconnect;
-  attribute EventHandler onvrdisplayactivate;
-  attribute EventHandler onvrdisplaydeactivate;
-  attribute EventHandler onvrdisplayblur;
-  attribute EventHandler onvrdisplayfocus;
-  attribute EventHandler onvrdisplaypresentchange;
-};
-
-partial interface HTMLIFrameElement {
-  attribute boolean allowvr;
-};
-
-partial interface Gamepad {
-  readonly attribute unsigned long displayId;
-};
-</script>
-  </head>
-  <body>
-    <h1 class="instructions">Description</h1>
-    <p class="instructions">
-      This test verifies that implementations of the WebVR API match its WebIDL definition.
-    </p>
-
-    <div id='log'></div>
-
-    <script>
-      setup( () => {
-        var idl_array = new IdlArray();
-        idl_array.add_untested_idls("[PrimaryGlobal] interface Window {};");
-        idl_array.add_untested_idls("interface Navigator {};");
-        idl_array.add_untested_idls("interface Event {};");
-        idl_array.add_untested_idls("interface EventTarget {};");
-        idl_array.add_untested_idls("interface HTMLIFrameElement {};");
-        idl_array.add_untested_idls("interface Gamepad {};");
-
-        idl_array.add_idls(document.getElementById("webvr_idl").textContent);
-
-        idl_array.test();
-        done();
-      }, {explicit_done: true});
-    </script>
-  </body>
-</html>
diff --git a/webvr/idlharness.https.html b/webvr/idlharness.https.html
new file mode 100644
index 0000000..2a89c57
--- /dev/null
+++ b/webvr/idlharness.https.html
@@ -0,0 +1,242 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>WebVR IDL test</title>
+    <link rel="help" href="https://w3c.github.io/webvr/">
+
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+    <script src=/resources/WebIDLParser.js></script>
+    <script src=/resources/idlharness.js></script>
+<script id="webvr_idl" type=text/plain>
+// Archived version of the WebVR spec from
+// https://w3c.github.io/webvr/archive/prerelease/1.1/index.html
+
+interface VRDisplay : EventTarget {
+  readonly attribute boolean isPresenting;
+
+  /**
+   * Dictionary of capabilities describing the VRDisplay.
+   */
+  [SameObject] readonly attribute VRDisplayCapabilities capabilities;
+
+  /**
+   * If this VRDisplay supports room-scale experiences, the optional
+   * stage attribute contains details on the room-scale parameters.
+   * The stageParameters attribute can not change between null
+   * and non-null once the VRDisplay is enumerated; however,
+   * the values within VRStageParameters may change after
+   * any call to VRDisplay.submitFrame as the user may re-configure
+   * their environment at any time.
+   */
+  readonly attribute VRStageParameters? stageParameters;
+
+  /**
+   * Return the current VREyeParameters for the given eye.
+   */
+  VREyeParameters getEyeParameters(VREye whichEye);
+
+  /**
+   * An identifier for this distinct VRDisplay. Used as an
+   * association point in the Gamepad API.
+   */
+  readonly attribute unsigned long displayId;
+
+  /**
+   * A display name, a user-readable name identifying it.
+   */
+  readonly attribute DOMString displayName;
+
+  /**
+   * Populates the passed VRFrameData with the information required to render
+   * the current frame.
+   */
+  boolean getFrameData(VRFrameData frameData);
+
+  /**
+   * z-depth defining the near plane of the eye view frustum
+   * enables mapping of values in the render target depth
+   * attachment to scene coordinates. Initially set to 0.01.
+   */
+  attribute double depthNear;
+
+  /**
+   * z-depth defining the far plane of the eye view frustum
+   * enables mapping of values in the render target depth
+   * attachment to scene coordinates. Initially set to 10000.0.
+   */
+  attribute double depthFar;
+
+  /**
+   * The callback passed to `requestAnimationFrame` will be called
+   * any time a new frame should be rendered. When the VRDisplay is
+   * presenting the callback will be called at the native refresh
+   * rate of the HMD. When not presenting this function acts
+   * identically to how window.requestAnimationFrame acts. Content should
+   * make no assumptions of frame rate or vsync behavior as the HMD runs
+   * asynchronously from other displays and at differing refresh rates.
+   */
+  long requestAnimationFrame(FrameRequestCallback callback);
+
+  /**
+   * Passing the value returned by `requestAnimationFrame` to
+   * `cancelAnimationFrame` will unregister the callback.
+   */
+  void cancelAnimationFrame(long handle);
+
+  /**
+   * Begin presenting to the VRDisplay. Must be called in response to a user gesture.
+   * Repeat calls while already presenting will update the layers being displayed.
+   * If the number of values in the leftBounds/rightBounds arrays is not 0 or 4 for any of the passed layers the promise is rejected
+   * If the source of any of the layers is not present (null), the promise is rejected.
+   */
+  Promise<void> requestPresent(sequence<VRLayerInit> layers);
+
+  /**
+   * Stops presenting to the VRDisplay.
+   */
+  Promise<void> exitPresent();
+
+  /**
+   * Get the layers currently being presented.
+   */
+  sequence<VRLayerInit> getLayers();
+
+  /**
+   * The layer provided to the VRDisplay will be captured and presented
+   * in the HMD. Calling this function has the same effect on the source
+   * canvas as any other operation that uses its source image, and canvases
+   * created without preserveDrawingBuffer set to true will be cleared.
+   */
+  void submitFrame();
+};
+
+typedef (HTMLCanvasElement or
+         OffscreenCanvas) VRSource;
+
+dictionary VRLayerInit {
+  VRSource? source = null;
+
+  sequence<float> leftBounds = [];
+  sequence<float> rightBounds = [];
+};
+
+interface VRDisplayCapabilities {
+  readonly attribute boolean hasPosition;
+  readonly attribute boolean hasExternalDisplay;
+  readonly attribute boolean canPresent;
+  readonly attribute unsigned long maxLayers;
+};
+
+enum VREye {
+  "left",
+  "right"
+};
+
+interface VRPose {
+  readonly attribute Float32Array? position;
+  readonly attribute Float32Array? linearVelocity;
+  readonly attribute Float32Array? linearAcceleration;
+
+  readonly attribute Float32Array? orientation;
+  readonly attribute Float32Array? angularVelocity;
+  readonly attribute Float32Array? angularAcceleration;
+};
+
+[Constructor]
+interface VRFrameData {
+  readonly attribute Float32Array leftProjectionMatrix;
+  readonly attribute Float32Array leftViewMatrix;
+
+  readonly attribute Float32Array rightProjectionMatrix;
+  readonly attribute Float32Array rightViewMatrix;
+
+  readonly attribute VRPose pose;
+};
+
+interface VREyeParameters {
+  readonly attribute Float32Array offset;
+
+  readonly attribute unsigned long renderWidth;
+  readonly attribute unsigned long renderHeight;
+};
+
+interface VRStageParameters {
+  readonly attribute Float32Array sittingToStandingTransform;
+
+  readonly attribute float sizeX;
+  readonly attribute float sizeZ;
+};
+
+partial interface Navigator {
+  Promise<sequence<VRDisplay>> getVRDisplays();
+  readonly attribute FrozenArray<VRDisplay> activeVRDisplays;
+  readonly attribute boolean vrEnabled;
+};
+
+enum VRDisplayEventReason {
+  "mounted",
+  "navigation",
+  "requested",
+  "unmounted"
+};
+
+[Constructor(DOMString type, VRDisplayEventInit eventInitDict)]
+interface VRDisplayEvent : Event {
+  readonly attribute VRDisplay display;
+  readonly attribute VRDisplayEventReason? reason;
+};
+
+dictionary VRDisplayEventInit : EventInit {
+  required VRDisplay display;
+  VRDisplayEventReason reason;
+};
+
+partial interface Window {
+  attribute EventHandler onvrdisplayconnect;
+  attribute EventHandler onvrdisplaydisconnect;
+  attribute EventHandler onvrdisplayactivate;
+  attribute EventHandler onvrdisplaydeactivate;
+  attribute EventHandler onvrdisplayblur;
+  attribute EventHandler onvrdisplayfocus;
+  attribute EventHandler onvrdisplaypresentchange;
+};
+
+partial interface Gamepad {
+  readonly attribute unsigned long displayId;
+};
+</script>
+  </head>
+  <body>
+    <h1 class="instructions">Description</h1>
+    <p class="instructions">
+      This test verifies that implementations of the WebVR API match its WebIDL definition.
+    </p>
+
+    <div id='log'></div>
+
+    <script>
+      setup( () => {
+        var idl_array = new IdlArray();
+        idl_array.add_untested_idls("[Global=Window, Exposed=Window] interface Window {};");
+        idl_array.add_untested_idls("interface Navigator {};");
+        idl_array.add_untested_idls("interface Event {};");
+        idl_array.add_untested_idls("interface EventTarget {};");
+        idl_array.add_untested_idls("interface HTMLIFrameElement {};");
+        idl_array.add_untested_idls("interface Gamepad {};");
+
+        idl_array.add_untested_idls(`dictionary EventInit {
+          boolean bubbles = false;
+          boolean cancelable = false;
+          boolean composed = false;
+        };`);
+
+        idl_array.add_idls(document.getElementById("webvr_idl").textContent);
+
+        idl_array.test();
+        done();
+      }, {explicit_done: true});
+    </script>
+  </body>
+</html>
diff --git a/webvtt/api/VTTCue/constructor-exceptions.html b/webvtt/api/VTTCue/constructor-exceptions.html
new file mode 100644
index 0000000..4d051fb
--- /dev/null
+++ b/webvtt/api/VTTCue/constructor-exceptions.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>VTTCue constructor exceptions</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+    assert_throws(new TypeError, function() { new VTTCue(NaN, 0, 'foo'); });
+    assert_throws(new TypeError, function() { new VTTCue(Infinity, 0, 'foo'); });
+    assert_throws(new TypeError, function() { new VTTCue('tomorrow', 0, 'foo'); });
+}, document.title+', non-finite start time');
+test(function() {
+    assert_throws(new TypeError, function() { new VTTCue(0, NaN, 'foo'); });
+    assert_throws(new TypeError, function() { new VTTCue(0, Infinity, 'foo'); });
+    assert_throws(new TypeError, function() { new VTTCue(0, 'tomorrow', 'foo'); });
+}, document.title+', non-finite end time');
+test(function() {
+    var start = { valueOf: function() { return 42; } };
+    var end = { valueOf: function() { return 84; } };
+    var cue = new VTTCue(start, end, 'bar');
+    assert_equals(cue.startTime, 42);
+    assert_equals(cue.endTime, 84);
+    assert_equals(cue.text, 'bar');
+}, document.title+', valueOf');
+</script>
diff --git a/webvtt/api/VTTRegion/constructor.html b/webvtt/api/VTTRegion/constructor.html
index 53c1968..cddde0a 100644
--- a/webvtt/api/VTTRegion/constructor.html
+++ b/webvtt/api/VTTRegion/constructor.html
@@ -7,12 +7,52 @@
 <script>
 test(function() {
     var region = new VTTRegion();
-    assert_equals(region.width, 100);
-    assert_equals(region.lines, 3);
-    assert_equals(region.regionAnchorX, 0);
-    assert_equals(region.regionAnchorY, 100);
+    assert_true(region instanceof VTTRegion, "instanceof");
+
+    assert_equals(region.scroll, "");
     assert_equals(region.viewportAnchorX, 0);
     assert_equals(region.viewportAnchorY, 100);
-    assert_equals(region.scroll, '');
-}, document.title + ' initial values');
+    assert_equals(region.regionAnchorX, 0);
+    assert_equals(region.regionAnchorY, 100);
+    assert_equals(region.lines, 3);
+    assert_equals(region.width, 100);
+}, document.title + " initial values");
+
+test(function() {
+    var region = new VTTRegion();
+    region.scroll = "invalid-scroll-value";
+    assert_equals(region.scroll, "");
+
+    checkValues([-1, 101], "IndexSizeError");
+    checkValues([-Infinity, Infinity, NaN], new TypeError);
+    function checkValues(invalidValues, exception) {
+        for (var value of invalidValues) {
+            assert_throws(exception, function() { region.viewportAnchorX = value; });
+            assert_equals(region.viewportAnchorX, 0);
+            assert_throws(exception, function() { region.viewportAnchorY = value; });
+            assert_equals(region.viewportAnchorY, 100);
+            assert_throws(exception, function() { region.regionAnchorX = value; });
+            assert_equals(region.regionAnchorX, 0);
+            assert_throws(exception, function() { region.regionAnchorY = value; });
+            assert_equals(region.regionAnchorY, 100);
+            assert_throws(exception, function() { region.width = value; });
+            assert_equals(region.width, 100);
+        }
+    }
+
+    assert_equals(region.lines, 3);
+
+    region.lines = 130;
+    assert_equals(region.lines, 130);
+    region.viewportAnchorX = 64;
+    assert_equals(region.viewportAnchorX, 64);
+    region.viewportAnchorY = 32;
+    assert_equals(region.viewportAnchorY, 32);
+    region.regionAnchorX = 16;
+    assert_equals(region.regionAnchorX, 16);
+    region.regionAnchorY = 8;
+    assert_equals(region.regionAnchorY, 8);
+    region.width = 42;
+    assert_equals(region.width, 42);
+}, document.title + " mutations");
 </script>
diff --git a/webvtt/api/VTTRegion/lines.html b/webvtt/api/VTTRegion/lines.html
index a9be610..90e08b4 100644
--- a/webvtt/api/VTTRegion/lines.html
+++ b/webvtt/api/VTTRegion/lines.html
@@ -17,8 +17,12 @@
     // https://heycam.github.io/webidl/#abstract-opdef-converttoint
     [[0, 0],
      [-0, 0],
+     [-1, 4294967295],
+     [-100, 4294967196],
      [101, 101],
+     [-2147483648, 2147483648],
      [2147483647, 2147483647],
+     [2147483648, 2147483648],
      [NaN, 0],
      [Infinity, 0],
      [-Infinity, 0]].forEach(function (pair) {
@@ -28,10 +32,5 @@
         assert_equals(region.lines, expected);
     });
 
-    [-1, -100, -2147483648, 2147483648 /* wraps to -2147483648 */].forEach(function (invalid) {
-        assert_throws('IndexSizeError', function() {
-          region.lines = invalid;
-        }, invalid);
-    });
 }, document.title + ' script-created region');
 </script>
diff --git a/webvtt/api/VTTRegion/non-visible-cue-with-region.html b/webvtt/api/VTTRegion/non-visible-cue-with-region.html
new file mode 100644
index 0000000..5a48e46
--- /dev/null
+++ b/webvtt/api/VTTRegion/non-visible-cue-with-region.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Box-less VTTCue attached to VTTRegion</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video></video>
+<script>
+setup(function() {
+  window.video = document.querySelector('video');
+  video.src = getVideoURI('/media/test');
+});
+async_test(function(t) {
+  let track = video.addTextTrack('subtitles');
+  let cue = new VTTCue(0, 1, '');
+  cue.region = new VTTRegion();
+  cue.onexit = t.step_func_done(function() {
+    video.pause();
+  });
+  track.addCue(cue);
+  video.onloadedmetadata = t.step_func(function() {
+    video.currentTime = 0.8;
+    video.play();
+  });
+  video.onended = t.unreached_func('test ends before video');
+});
+</script>
diff --git a/webvtt/api/interfaces.html b/webvtt/api/interfaces.html
index 5e112da..5222256 100644
--- a/webvtt/api/interfaces.html
+++ b/webvtt/api/interfaces.html
@@ -116,53 +116,23 @@
 };
 </script>
 
-<script type=text/plain id=tested>
-enum AutoKeyword { "auto" };
-typedef (double or AutoKeyword) LineAndPositionSetting;
-enum DirectionSetting { "" /* horizontal */, "rl", "lr" };
-enum LineAlignSetting { "start", "center", "end" };
-enum PositionAlignSetting { "line-left", "center", "line-right", "auto" };
-enum AlignSetting { "start", "center", "end", "left", "right" };
-[Exposed=Window,
- Constructor(double startTime, double endTime, DOMString text)]
-interface VTTCue : TextTrackCue {
-  attribute VTTRegion? region;
-  attribute DirectionSetting vertical;
-  attribute boolean snapToLines;
-  attribute LineAndPositionSetting line;
-  attribute LineAlignSetting lineAlign;
-  attribute LineAndPositionSetting position;
-  attribute PositionAlignSetting positionAlign;
-  attribute double size;
-  attribute AlignSetting align;
-  attribute DOMString text;
-  DocumentFragment getCueAsHTML();
-};
-
-enum ScrollSetting { "" /* none */, "up" };
-[Exposed=Window,
- Constructor]
-interface VTTRegion {
-  attribute DOMString id;
-  attribute double width;
-  attribute long lines;
-  attribute double regionAnchorX;
-  attribute double regionAnchorY;
-  attribute double viewportAnchorX;
-  attribute double viewportAnchorY;
-  attribute ScrollSetting scroll;
-};
-</script>
 <script>
 "use strict";
-setup(function() {
+
+// https://w3c.github.io/webvtt/
+
+promise_test(async () => {
+  const webvttIDL = await fetch('/interfaces/webvtt.idl').then(response =>
+    response.text(),
+  );
   var idlArray = new IdlArray();
-  idlArray.add_untested_idls(document.getElementById("untested").textContent);
-  idlArray.add_idls(document.getElementById("tested").textContent);
+  idlArray.add_untested_idls(document.getElementById('untested').textContent);
+  idlArray.add_idls(webvttIDL);
   idlArray.add_objects({
     VTTCue: ['new VTTCue(0, 0, "")'],
     VTTRegion: ['new VTTRegion()'],
   });
   idlArray.test();
-});
+  done();
+}, 'webvtt interfaces.');
 </script>
diff --git a/webvtt/parsing/file-parsing/tests/header-regions.html b/webvtt/parsing/file-parsing/tests/header-regions.html
new file mode 100644
index 0000000..b10fcbc
--- /dev/null
+++ b/webvtt/parsing/file-parsing/tests/header-regions.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Tests proper parsing of various regions present in WebVTT header area.</title>
+<script src="/common/media.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const regionDefaults = {
+    width: 100,
+    lines: 3,
+    regionAnchorX: 0,
+    regionAnchorY: 100,
+    viewportAnchorX: 0,
+    viewportAnchorY: 100,
+    scroll: ''
+};
+
+function checkProperties(region, expected, i) {
+    for (var prop in regionDefaults) {
+        if (!(prop in expected))
+            expected[prop] = regionDefaults[prop];
+        assert_equals(region[prop], expected[prop], prop + ' (cue ' + ( i + 1 ) + ')');
+    }
+}
+
+function checkCueRegions(cues) {
+    for (let i = 0; i < cues.length; ++i) {
+        let cue = cues[i];
+        let expected = JSON.parse(cue.text);
+        if (cue.region)
+            checkProperties(cue.region, expected, i);
+        else
+            assert_equals(expected, 'no region');
+    }
+}
+
+async_test(function(t) {
+    var video = document.createElement('video');
+    video.src = getVideoURI('/media/test');
+    var testTrack = document.createElement('track');
+    testTrack.onload = t.step_func_done(function() {
+        var track = testTrack.track;
+        assert_equals(track.cues.length, 9);
+        checkCueRegions(track.cues);
+    });
+    testTrack.src = 'support/header-regions.vtt';
+    testTrack.kind = 'captions';
+    testTrack.default = true;
+    video.appendChild(testTrack);
+});
+</script>
diff --git a/webvtt/parsing/file-parsing/tests/regions-edge-case.html b/webvtt/parsing/file-parsing/tests/regions-edge-case.html
new file mode 100644
index 0000000..db1f27f
--- /dev/null
+++ b/webvtt/parsing/file-parsing/tests/regions-edge-case.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<!-- DO NOT EDIT! This file and support/regions-edge-case.vtt are generated. -->
+<!-- See /webvtt/parsing/file-parsing/README.md -->
+<meta charset=utf-8>
+<title>WebVTT parser test: multiple regions edge cases</title>
+<link rel="help" href="https://w3c.github.io/webvtt/#collect-webvtt-region-settings">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+var t = async_test('regions, lines');
+t.step(function(){
+    var video = document.createElement('video');
+    var track = document.createElement('track');
+    assert_true('src' in track, 'track element not supported');
+    track.src = 'support/regions-edge-case.vtt';
+    track['default'] = true;
+    track.kind = 'subtitles';
+    track.onload = this.step_func(trackLoaded);
+    track.onerror = this.step_func(trackError);
+    video.appendChild(track);
+    document.body.appendChild(video);
+});
+
+function trackLoaded(event) {
+    var track = event.target;
+    var video = track.parentNode;
+    var cues = video.textTracks[0].cues;
+    {
+assert_equals(cues.length, 4);
+
+var region1 = cues[0].region;
+assert_equals(region1.lines, 1);
+assert_equals(region1.id, "foo");
+
+var region2 = cues[1].region;
+assert_equals(region2.lines, 2);
+assert_equals(region2.id, "bill");
+
+var region3 = cues[2].region;
+assert_equals(region3.lines, 3);
+assert_equals(region3.id, "jill");
+
+var region4 = cues[3].region;
+assert_equals(region4.lines, 4);
+assert_equals(region4.id, "jack");
+
+    }
+    this.done();
+}
+
+function trackError(e) {
+    assert_unreached('got unexpected error event');
+}
+</script>
diff --git a/webvtt/parsing/file-parsing/tests/support/header-regions.vtt b/webvtt/parsing/file-parsing/tests/support/header-regions.vtt
new file mode 100644
index 0000000..81634c5
--- /dev/null
+++ b/webvtt/parsing/file-parsing/tests/support/header-regions.vtt
@@ -0,0 +1,52 @@
+WEBVTT FILE
+
+REGION
+id:region_without_settings
+
+REGION
+id:region_with_all_settings width:32%
+lines:5
+regionanchor:41%,20% viewportanchor:31%,84%
+scroll:up
+
+REGION
+id:region_floating_point_anchor
+regionanchor:41.125%,20.25% viewportanchor:32.75%,32.5%
+
+REGION
+id:not_unique_id width:42%
+
+REGION
+id:not_unique_id
+width:67%
+
+REGION
+invalid_settings values but region still created
+: Invalid Header
+
+00:00:00.000 --> 00:00:02.500 region:someregionattributeid
+"no region"
+
+00:00:00.000 --> 00:00:02.500 line:5 region:ignored_attribute_value
+"no region"
+
+00:00:00.000 --> 00:00:02.500 size:10% region:ignored_attribute_value
+"no region"
+
+00:00:00.000 --> 00:00:02.500 vertical:lr region:ignored_attribute_value
+"no region"
+
+00:00:03.000 --> 00:00:04.000 region:region_without_settings
+{}
+
+00:00:04.000 --> 00:00:05.000 region:region_with_all_settings
+{"width":32,"lines":5,"regionAnchorX":41,"regionAnchorY":20,"viewportAnchorX":31,"viewportAnchorY":84,"scroll":"up"}
+
+00:00:05.000 --> 00:00:06.000 region:region_floating_point_anchor
+{"regionAnchorX":41.125,"regionAnchorY":20.25,"viewportAnchorX":32.75,"viewportAnchorY":32.5}
+
+00:00:06.000 --> 00:00:07.000 region:not_unique_id
+{"width":67}
+
+00:00:07.000 --> 00:00:08.000 region:
+"no region"
diff --git a/webvtt/parsing/file-parsing/tests/support/regions-edge-case.vtt b/webvtt/parsing/file-parsing/tests/support/regions-edge-case.vtt
new file mode 100644
index 0000000..29debd5
--- /dev/null
+++ b/webvtt/parsing/file-parsing/tests/support/regions-edge-case.vtt
@@ -0,0 +1,44 @@
+WEBVTT
+
+NOTE valid
+
+REGION
+id:foo lines:1
+
+-->
+REGION
+id:foo
+lines:2
+-->
+
+REGION
+id:bill
+lines:2
+
+REGION
+REGION
+id:jill
+lines:3
+
+REGION
+--->
+id:jill lines:4
+
+REGION
+id:jack--> lines:5
+
+REGION
+id:jack lines:4
+
+00:00:00.000 --> 00:00:01.000 region:foo
+text
+
+00:00:00.000 --> 00:00:01.000 region:bill
+text
+
+00:00:00.000 --> 00:00:01.000 region:jill
+text
+
+00:00:00.000 --> 00:00:01.000 region:jack
+text
+
diff --git a/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused-ref.html b/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused-ref.html
index 66505b2..60f7bb0 100644
--- a/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused-ref.html
+++ b/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused-ref.html
@@ -1,14 +1,13 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <title>Reference for WebVTT rendering, cue text should be removed when removing them using the DOM APIs while paused</title>
+<script src="/common/reftest-wait.js"></script>
 <style>
-html { overflow:hidden }
-body { margin:0 }
-.video {
-    display: inline-block;
-    width: 320px;
-    height: 180px;
-    position: relative;
-    font-size: 9px;
-}
+html { overflow: hidden }
+body { margin: 0 }
 </style>
-<div class="video"></div>
+<video width="320" height="180" autoplay onplaying="this.onplaying = null; this.pause(); takeScreenshot();">
+  <source src="/media/white.webm" type="video/webm">
+  <source src="/media/white.mp4" type="video/mp4">
+</video>
+</html>
\ No newline at end of file
diff --git a/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused.html b/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused.html
index 708eba9..9233df8 100644
--- a/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused.html
+++ b/webvtt/rendering/cues-with-video/processing-model/dom_override_remove_cue_while_paused.html
@@ -2,15 +2,15 @@
 <html class="reftest-wait">
 <title>WebVTT rendering, cue text should be removed when removing them using the DOM APIs while paused</title>
 <link rel="match" href="dom_override_remove_cue_while_paused-ref.html">
+<script src="/common/reftest-wait.js"></script>
 <style>
-html { overflow:hidden }
-body { margin:0 }
+html { overflow: hidden }
+body { margin: 0 }
 ::cue {
     font-family: Ahem, sans-serif;
     color: green
 }
 </style>
-<script src="/common/reftest-wait.js"></script>
 <script>
     var i = 0;
     function updateCue() {
@@ -24,7 +24,7 @@
         takeScreenshotDelayed(1000);
     }
 </script>
-<video id="video" width="320" height="180" autoplay onplaying="this.onplaying = null; this.pause(); updateCue();">
+<video width="320" height="180" autoplay onplaying="this.onplaying = null; this.pause(); updateCue();">
     <source src="/media/white.webm" type="video/webm">
     <source src="/media/white.mp4" type="video/mp4">
     <track id="track" src="support/test.vtt" onload="updateCue();">
diff --git a/webxr/OWNERS b/webxr/OWNERS
new file mode 100644
index 0000000..8151e0d
--- /dev/null
+++ b/webxr/OWNERS
@@ -0,0 +1,3 @@
+@toji
+@paezagon
+@klausw
diff --git a/webxr/interfaces.https.html b/webxr/interfaces.https.html
new file mode 100644
index 0000000..385f835
--- /dev/null
+++ b/webxr/interfaces.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebXR Device API IDL Tests</title>
+<link rel="help" href="https://immersive-web.github.io/webxr/spec/latest/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+"use strict";
+
+promise_test(async () => {
+  const idl_array = new IdlArray();
+  const dom_idl = await fetch("/interfaces/dom.idl").then(r => r.text());
+  const webxr_idl = await fetch("/interfaces/webxr.idl").then(r => r.text());
+
+  idl_array.add_untested_idls(dom_idl);
+  idl_array.add_untested_idls("interface Navigator {};");
+  idl_array.add_idls(webxr_idl);
+  idl_array.add_idls("dictionary WebGLContextAttributes {};");
+  idl_array.add_objects({
+    Navigator:['navigator'],
+  });
+  idl_array.test();
+}, "Test IDL implementation of WebXR API");
+</script>
diff --git a/webxr/resources/webxr_check.html b/webxr/resources/webxr_check.html
new file mode 100644
index 0000000..2d8e5b3
--- /dev/null
+++ b/webxr/resources/webxr_check.html
@@ -0,0 +1,17 @@
+<script src=webxr_util.js></script>
+<script>
+'use strict';
+let definedObjects = [];
+let undefinedObjects = [];
+
+forEachWebxrObject((obj, name) => {
+    if(obj == undefined) {
+        undefinedObjects.push(name);
+    } else {
+        definedObjects.push(name);
+    }
+});
+
+window.parent.postMessage({ undefinedObjects, definedObjects}, '*');
+
+</script>
\ No newline at end of file
diff --git a/webxr/resources/webxr_util.js b/webxr/resources/webxr_util.js
new file mode 100644
index 0000000..388c757
--- /dev/null
+++ b/webxr/resources/webxr_util.js
@@ -0,0 +1,28 @@
+// This functions calls a callback with each API object as specified
+// by https://immersive-web.github.io/webxr/spec/latest/, allowing
+// checks to be made on all ojects.
+// Arguements:
+//      callback: A callback function with two arguements, the first
+//                being the API object, the second being the name of
+//                that API object.
+function forEachWebxrObject(callback) {
+  callback(window.navigator.xr, 'navigator.xr');
+  callback(window.XRDevice, 'XRDevice');
+  callback(window.XRSession, 'XRSession');
+  callback(window.XRSessionCreationOptions, 'XRSessionCreationOptions');
+  callback(window.XRFrameRequestCallback, 'XRFrameRequestCallback');
+  callback(window.XRPresentationContext, 'XRPresentationContext');
+  callback(window.XRPresentationFrame, 'XRPresentationFrame');
+  callback(window.XRView, 'XRView');
+  callback(window.XRViewport, 'XRViewport');
+  callback(window.XRDevicePose, 'XRDevicePose');
+  callback(window.XRLayer, 'XRLayer');
+  callback(window.XRWebGLLayer, 'XRWebGLLayer');
+  callback(window.XRWebGLLayerInit, 'XRWebGLLayerInit');
+  callback(window.XRCoordinateSystem, 'XRCoordinateSystem');
+  callback(window.XRFrameOfReference, 'XRFrameOfReference');
+  callback(window.XRStageBounds, 'XRStageBounds');
+  callback(window.XRStageBoundsPoint, 'XRStageBoundsPoint');
+  callback(window.XRSessionEvent, 'XRSessionEvent');
+  callback(window.XRCoordinateSystemEvent, 'XRCoordinateSystemEvent');
+}
\ No newline at end of file
diff --git a/webxr/webxr_availability.http.sub.html b/webxr/webxr_availability.http.sub.html
new file mode 100644
index 0000000..515b2ad
--- /dev/null
+++ b/webxr/webxr_availability.http.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/webxr/resources/webxr_util.js></script>
+  <script>
+    'use strict';
+
+    var same_origin_src = '/webxr/resources/';
+    var cross_origin_https_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+      same_origin_src;
+
+    test(t => {
+      forEachWebxrObject((obj, name) => {
+        assert_equals(obj, undefined, name + ' was defined in insecure context.');
+      });
+    }, 'Test webxr not available in insecure context');
+
+    async_test(t => {
+      let frame = document.createElement('iframe');
+      frame.src = cross_origin_https_src + 'webxr_check.html';
+
+      window.addEventListener('message', t.step_func(function handler(evt) {
+          if (evt.source === frame.contentWindow) {
+            document.body.removeChild(frame);
+            window.removeEventListener('message', handler);
+
+            assert_equals(evt.data.definedObjects.length, 0,
+              "Some objects were defined in insecure context: " +
+              evt.data.definedObjects.toString());
+            t.done();
+          }
+      }));
+
+      document.body.appendChild(frame);
+    }, 'Test webxr not available in secure context in insecure context');
+
+  </script>
+</body>
diff --git a/workers/SharedWorkerPerformanceNow.html b/workers/SharedWorkerPerformanceNow.html
new file mode 100644
index 0000000..a784293
--- /dev/null
+++ b/workers/SharedWorkerPerformanceNow.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>window.performance.now in shared workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+async_test(function(t) {
+    const worker = new SharedWorker('support/WorkerSendingPerformanceNow.js');
+    worker.port.onmessage = t.step_func(event => {
+        const results = event.data;
+        assert_true(results.length == 4);
+        assert_equals(results[0], 'undefined',
+          'workerStart not defined on the Worker object');
+        assert_equals(results[1], 'object', 'self.performance is defined');
+        assert_equals(results[2], 'function', 'self.performance.now is defined');
+        assert_greater_than(results[3], 0, 'Time in the worker should be positive');
+        assert_greater_than(window.performance.now(), results[3], 'Time in the worker should be before the current time in the main document');
+        setupIframe();
+    });
+    window.iframeStartTime = 0;
+    window.test_iframe = function(event) {
+        const workerTime = event.data[3];
+        assert_greater_than(workerTime, window.iframeStartTime,
+            'Time since origin time should be greater in the shared worker than the iframe');
+        t.done();
+    }
+    function setupIframe() {
+        const iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        const script = iframe.contentWindow.document.createElement('script');
+        script.innerHTML =
+            'window.top.iframeStartTime = window.performance.now();' +
+            'const worker = new SharedWorker("support/WorkerSendingPerformanceNow.js");' +
+            'worker.port.onmessage = function(event) {' +
+            '    window.top.test_iframe(event);' +
+            '};' +
+            'worker.port.postMessage("");';
+        iframe.contentWindow.document.body.appendChild(script);
+    }
+    worker.port.postMessage('');
+}, 'performance.now() exists in shared workers and reports reasonable times');
+</script>
+</body>
+</html>
diff --git a/workers/SharedWorker_dataUrl.html b/workers/SharedWorker_dataUrl.html
new file mode 100644
index 0000000..c1dec27
--- /dev/null
+++ b/workers/SharedWorker_dataUrl.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Shared Worker: Data URL cross-origin checks</title>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+</body>
+<script>
+
+function dirname(path) {
+  return path.replace(/\/[^\/]*$/, '/');
+}
+
+promise_test(t => {
+  return new Promise(function(resolve) {
+    let count = 0;
+    onmessage = e => {
+      assert_equals(e.data, 1);
+      if (++count == 2) {
+        resolve(true);
+      }
+    };
+
+    let iframeA = document.createElement('iframe');
+    document.body.appendChild(iframeA);
+    iframeA.src =  get_host_info().HTTP_REMOTE_ORIGIN +
+                   dirname(location.pathname) +
+                   "support/iframe_sw_dataUrl.html";
+
+    let iframeB = document.createElement('iframe');
+    document.body.appendChild(iframeB);
+    iframeB.src =  get_host_info().HTTPS_REMOTE_ORIGIN +
+                   dirname(location.pathname) +
+                   "support/iframe_sw_dataUrl.html";
+  });
+}, 'Data URL not shared by cross-origin SharedWorkers');
+
+promise_test(t => {
+  return new Promise(function(resolve) {
+    let count = 0;
+    onmessage = e => {
+      assert_equals(e.data, ++count);
+      if (count == 2) {
+        resolve(true);
+      }
+    };
+
+    let iframeA = document.createElement('iframe');
+    document.body.appendChild(iframeA);
+    iframeA.src =  get_host_info().HTTP_ORIGIN +
+                   dirname(location.pathname) +
+                   "support/iframe_sw_dataUrl.html";
+
+    let iframeB = document.createElement('iframe');
+    document.body.appendChild(iframeB);
+    iframeB.src =  get_host_info().HTTP_ORIGIN +
+                   dirname(location.pathname) +
+                   "support/iframe_sw_dataUrl.html";
+  });
+}, 'Data URLs shared by same-origin SharedWorkers');
+
+</script>
+</html>
diff --git a/workers/WorkerPerformanceNow.html b/workers/WorkerPerformanceNow.html
new file mode 100644
index 0000000..467dad4
--- /dev/null
+++ b/workers/WorkerPerformanceNow.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>performance.now in dedicated workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+async_test(function(t) {
+  const worker = new Worker('support/WorkerSendingPerformanceNow.js');
+  worker.onmessage = t.step_func_done(event => {
+    const results = event.data;
+    assert_true(results.length == 4);
+    assert_equals(results[0], 'undefined',
+      'workerStart not defined on the Worker object');
+    assert_equals(results[1], 'object', "self.performance is defined");
+    assert_equals(results[2], 'function', "self.performance.now is defined");
+    assert_greater_than(results[3], 0, "Time in the worker should be positive");
+    assert_greater_than(window.performance.now(), results[3], "Time in the worker should be before the current time in the main document");
+  });
+  worker.postMessage('');
+}, 'performance.now() exists in dedicated workers and reports reasonable times');
+</script>
+</body>
+</html>
diff --git a/workers/data-url-shared-window.html b/workers/data-url-shared-window.html
new file mode 100644
index 0000000..7459f59
--- /dev/null
+++ b/workers/data-url-shared-window.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<head>
+<title>data URL shared worker</title>
+</head>
+<body>
+<script>
+onmessage = event => {
+  const port = event.ports[0];
+  // This shared worker counts the total number of connected documents and
+  // notifies the connector of it.
+  const kScript =
+      "onconnect = e => {" +
+      "  if (self.count === undefined)" +
+      "    self.count = 0;" +
+      "  self.count++;" +
+      "  e.ports[0].postMessage(self.count);" +
+      "};";
+  const worker = new SharedWorker('data:application/javascript,' + kScript);
+  worker.port.onmessage = e => port.postMessage(e.data);
+};
+
+window.opener.postMessage('LOADED', '*');
+</script>
+</body>
+</html>
diff --git a/workers/data-url-shared.html b/workers/data-url-shared.html
index d95e639..aae76d4 100644
--- a/workers/data-url-shared.html
+++ b/workers/data-url-shared.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
-<title>data URL shared worker</title>
+<title>data URL shared workers</title>
+<script src="/common/get-host-info.sub.js"></script>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<div id=log></div>
 <script>
+
 function assert_worker_sends_pass(test_desc, mime_type, worker_code) {
   async_test(function(t) {
     var w = new SharedWorker(`data:${mime_type},onconnect = function(e) { port = e.ports[0]; ${worker_code}}`);
@@ -14,8 +15,8 @@
   }, test_desc);
 }
 
-function assert_worker_throws(test_desc, worker_code, before_connect_worker_code) {
-  assert_worker_sends_pass(test_desc, '', `try { ${worker_code}; port.postMessage("FAIL"); } catch (e) { port.postMessage("PASS"); }`, before_connect_worker_code);
+function assert_worker_throws(test_desc, worker_code) {
+  assert_worker_sends_pass(test_desc, '', `try { ${worker_code}; port.postMessage("FAIL"); } catch (e) { port.postMessage("PASS"); }`);
 }
 
 // Any MIME type allowed
@@ -27,12 +28,51 @@
 assert_worker_sends_pass('communication goes both ways', 'application/javascript', 'port.onmessage = function(e) { port.postMessage("PASS"); }');
 
 // test access to storage APIs
-// once https://github.com/w3c/IndexedDB/pull/150 lands, this is spec conforming
-assert_worker_throws('indexedDB inaccessible', 'self.indexedDB.open("someDBName")');
-assert_worker_throws('Web SQL Database inaccessible', 'self.openDatabase("someDBName", "1.0", "someDBName", 1);');
+
+// https://w3c.github.io/IndexedDB/#dom-idbfactory-open
+assert_worker_sends_pass('indexedDB is present', '', 'port.postMessage("indexedDB" in self ? "PASS" : "FAIL")');
+assert_worker_throws('indexedDB is inaccessible', 'self.indexedDB.open("someDBName")');
+// Other standardized storage APIs are either not exposed to workers
+// (e.g. window.localStorage, window.sessionStorage), or are [SecureContext]
+// (e.g. self.caches).
 
 // 'data:' workers are cross-origin
 assert_worker_sends_pass('cross-origin worker', '', 'fetch("/").then(() => port.postMessage("FAIL"), () => port.postMessage("PASS"))');
+
 // 'data:' workers have opaque origin
-assert_worker_sends_pass('worker has opaque origin', 'application/javascript', 'if (self.location.origin == "null") port.postMessage("PASS"); else { port.postMessage("FAIL"); }');
+assert_worker_sends_pass('worker has opaque origin', 'application/javascript', 'port.postMessage(self.location.origin == "null" ?  "PASS" : "FAIL")');
+
+function openWindow(url) {
+  return new Promise(resolve => {
+    const win = window.open(url, '_blank');
+    add_completion_callback(() => win.close());
+    window.onmessage = e => {
+      assert_equals(e.data, 'LOADED');
+      resolve(win);
+    };
+  });
+}
+
+promise_test(() => {
+  const kWindowURL = 'data-url-shared-window.html';
+  const kRemoteWindowURL = get_host_info().HTTP_REMOTE_ORIGIN +
+                           '/workers/data-url-shared-window.html';
+  return openWindow(kWindowURL)
+    .then(win => {
+        const channel = new MessageChannel;
+        win.postMessage(channel.port1, '*', [channel.port1]);
+        return new Promise(resolve => channel.port2.onmessage = resolve);
+      })
+    .then(msg_event => {
+        assert_equals(msg_event.data, 1);
+        return openWindow(kRemoteWindowURL);
+      })
+    .then(win => {
+        const channel = new MessageChannel;
+        win.postMessage(channel.port1, '*', [channel.port1]);
+        return new Promise(resolve => channel.port2.onmessage = resolve);
+      })
+    .then(msg_event => assert_equals(msg_event.data, 1));
+}, 'A data: URL shared worker should not be shared among origins.');
+
 </script>
diff --git a/workers/data-url.html b/workers/data-url.html
index 1308eba..f8fe65e 100644
--- a/workers/data-url.html
+++ b/workers/data-url.html
@@ -1,8 +1,7 @@
 <!DOCTYPE html>
-<title>Test workers can be started with a data URL</title>
+<title>data URL dedicated workers</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
 <script>
 // Helper assert functions -START-
 function assert_worker_sends_pass(test_desc, mime_type, worker_code) {
@@ -36,7 +35,7 @@
 
 // Actual tests -START-
 
-// Any MIME type
+// Any MIME type allowed
 assert_worker_sends_pass('application/javascript MIME allowed', 'application/javascript', 'self.postMessage("PASS")');
 assert_worker_sends_pass('text/plain MIME allowed', 'text/plain', 'self.postMessage("PASS")');
 assert_worker_sends_pass('empty MIME allowed', '', 'self.postMessage("PASS")');
@@ -45,9 +44,14 @@
 assert_worker_sends_pass('communication goes both ways', 'application/javascript', 'onmessage = function(e) { self.postMessage("PASS"); }');
 
 // test access to storage APIs
-// once https://github.com/w3c/IndexedDB/pull/150 lands, this is spec conforming
-assert_worker_throws('indexedDB inaccessible', 'self.indexedDB.open("someDBName")');
-assert_worker_throws('Web SQL Database inaccessible', 'self.openDatabase("someDBName", "1.0", "someDBName", 1);');
+
+// https://w3c.github.io/IndexedDB/#dom-idbfactory-open
+assert_worker_sends_pass('indexedDB is present', '', 'self.postMessage("indexedDB" in self ? "PASS" : "FAIL")');
+assert_worker_throws('indexedDB is inaccessible', 'self.indexedDB.open("someDBName")');
+
+// Other standardized storage APIs are either not exposed to workers
+// (e.g. window.localStorage, window.sessionStorage), or are [SecureContext]
+// (e.g. self.caches).
 
 // 'data:' workers are cross-origin
 assert_worker_sends_pass('cross-origin worker', '', 'fetch("/").then(() => self.postMessage("FAIL"), () => self.postMessage("PASS"))');
diff --git a/workers/interfaces/WorkerUtils/WindowTimers/005.html b/workers/interfaces/WorkerUtils/WindowTimers/005.html
new file mode 100644
index 0000000..b86eff1
--- /dev/null
+++ b/workers/interfaces/WorkerUtils/WindowTimers/005.html
@@ -0,0 +1,23 @@
+<!--
+self.close();
+var t = setInterval(function() {}, 10);
+postMessage(t);
+/*
+-->
+<!doctype html>
+<title>setInterval when closing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(function() {
+  var worker = new Worker('#');
+  worker.onmessage = this.step_func(function(e) {
+    assert_equals(e.data, 1);
+    this.done();
+  });
+});
+</script>
+<!--
+*/
+//-->
diff --git a/workers/interfaces/WorkerUtils/importScripts/1.headers b/workers/interfaces/WorkerUtils/importScripts/1.headers
new file mode 100644
index 0000000..a17a9a3
--- /dev/null
+++ b/workers/interfaces/WorkerUtils/importScripts/1.headers
@@ -0,0 +1 @@
+Content-Type: application/javascript
diff --git a/workers/interfaces/WorkerUtils/importScripts/null.headers b/workers/interfaces/WorkerUtils/importScripts/null.headers
new file mode 100644
index 0000000..a17a9a3
--- /dev/null
+++ b/workers/interfaces/WorkerUtils/importScripts/null.headers
@@ -0,0 +1 @@
+Content-Type: application/javascript
diff --git a/workers/interfaces/WorkerUtils/importScripts/undefined.headers b/workers/interfaces/WorkerUtils/importScripts/undefined.headers
new file mode 100644
index 0000000..a17a9a3
--- /dev/null
+++ b/workers/interfaces/WorkerUtils/importScripts/undefined.headers
@@ -0,0 +1 @@
+Content-Type: application/javascript
diff --git a/workers/modules/dedicated-worker-import-meta.html b/workers/modules/dedicated-worker-import-meta.html
new file mode 100644
index 0000000..6960cbe
--- /dev/null
+++ b/workers/modules/dedicated-worker-import-meta.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: import.meta</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(() => {
+  const script_url = 'resources/import-meta-url-worker.js';
+  const worker = new Worker(script_url, { type: 'module' });
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+}, 'Test import.meta.url on the top-level module script.');
+
+promise_test(() => {
+  const script_url = 'import-meta-url-worker.js';
+  const worker = new Worker('resources/dynamic-import-given-url-worker.js',
+                            { type: 'module' });
+  worker.postMessage('./' + script_url);
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+}, 'Test import.meta.url on the imported module script.');
+
+promise_test(() => {
+  const script_url = 'import-meta-url-worker.js';
+  const worker = new Worker('resources/dynamic-import-given-url-worker.js',
+                            { type: 'module' });
+  worker.postMessage('./' + script_url);
+
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_true(msg_event.data.endsWith(script_url)))
+      .then(() => {
+        worker.postMessage('./' + script_url + '#1');
+        return new Promise(resolve => worker.onmessage = resolve);
+      })
+      .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+}, 'Test import.meta.url on the imported module script with a fragment.');
+
+</script>
diff --git a/workers/modules/dedicated-worker-import.html b/workers/modules/dedicated-worker-import.html
new file mode 100644
index 0000000..9ca6f3c
--- /dev/null
+++ b/workers/modules/dedicated-worker-import.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: import</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Start a dedicated worker for |scriptURL| and wait until MessageEvents from
+// imported modules up to |expectedNumberOfImportedModules|.
+function RunImportTest(scriptURL, expectedNumberOfImportedModules) {
+  return new Promise(resolve => {
+    let numberOfImportedModules = 0;
+    const worker = new Worker(scriptURL, { type: 'module' });
+    worker.onmessage = e => {
+      if (e.data === 'LOADED')
+        ++numberOfImportedModules;
+      if (numberOfImportedModules === expectedNumberOfImportedModules)
+        resolve();
+    };
+  });
+}
+
+promise_test(() => {
+  return RunImportTest('resources/static-import-worker.js', 2);
+}, 'Test static import on DedicatedWorkerGlobalScope.');
+
+promise_test(() => {
+  return RunImportTest('resources/nested-static-import-worker.js', 3);
+}, 'Test nested static import on DedicatedWorkerGlobalScope.');
+
+promise_test(() => {
+  return RunImportTest(
+      'resources/static-import-and-then-dynamic-import-worker.js', 3);
+}, 'Test static import and then dynamic import on DedicatedWorkerGlobalScope.');
+
+promise_test(() => {
+  return RunImportTest('resources/dynamic-import-worker.js', 2);
+}, 'Test dynamic import on DedicatedWorkerGlobalScope.');
+
+promise_test(() => {
+  return RunImportTest('resources/nested-dynamic-import-worker.js', 3);
+}, 'Test nested dynamic import on DedicatedWorkerGlobalScope.');
+
+promise_test(() => {
+  return RunImportTest(
+      'resources/dynamic-import-and-then-static-import-worker.js', 3);
+}, 'Test dynamic import and then static import on DedicatedWorkerGlobalScope.');
+
+promise_test(() => {
+  const scriptURL = 'resources/import-scripts-worker.js';
+  const worker = new Worker(scriptURL, { type: 'module' });
+  return (new Promise(resolve => worker.onmessage = resolve))
+      .then(e => assert_equals(e.data, 'TypeError'));
+}, 'importScripts() on module worker should throw an exception.');
+
+promise_test(() => {
+  const scriptURL = 'resources/non-existent-worker.js';
+  const worker = new Worker(scriptURL, { type: 'module' });
+  return new Promise(resolve => worker.onerror = resolve);
+}, 'Worker construction for non-existent script should throw an exception.');
+
+promise_test(() => {
+  const scriptURL = 'resources/static-import-non-existent-script-worker.js';
+  const worker = new Worker(scriptURL, { type: 'module' });
+  return new Promise(resolve => worker.onerror = resolve);
+}, 'Static import for non-existent script should throw an exception.');
+
+promise_test(() => {
+  const script_url = './non-existent-worker.js';
+  const worker = new Worker('resources/dynamic-import-given-url-worker.js',
+                            { type: 'module' });
+  worker.postMessage(script_url);
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_equals(msg_event.data, 'ERROR'));
+}, 'Dynamic import for non-existent script should throw an exception.');
+
+</script>
diff --git a/workers/modules/dedicated-worker-options-credentials.html b/workers/modules/dedicated-worker-options-credentials.html
new file mode 100644
index 0000000..316b01d
--- /dev/null
+++ b/workers/modules/dedicated-worker-options-credentials.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: WorkerOptions 'credentials'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Determines the expected cookie value to be reported by a dedicated worker
+// based on the given option. The worker reports an empty string as the actual
+// cookie value if the cookie wasn't sent to the server. Otherwise, it's the
+// value set by the headers file:
+// "dedicated-worker-options-credentials.html.headers"
+function DetermineExpectedCookieValue(options) {
+  // Classic script loading should always send credentials regardless of the
+  // 'credentials' option because the spec says the option takes effect only
+  // for module script loading.
+  if (options.type == 'classic')
+    return 'COOKIE_VALUE';
+  assert_equals(options.type, 'module');
+
+  if (!options.credentials || options.credentials == 'omit')
+    return '';
+  if (options.credentials == 'same-origin' || options.credentials == 'include')
+    return 'COOKIE_VALUE';
+  assert_unreached('Invalid credentials option was specified: ' +
+                   options.credentials);
+}
+
+// Runs a credentials test with the given WorkerOptions.
+async function runCredentialsTest(options) {
+  const worker = new Worker('resources/credentials.py', options);
+
+  // Wait until the worker sends the actual cookie value.
+  const msg_event = await new Promise(resolve => worker.onmessage = resolve);
+
+  const expectedCookieValue = DetermineExpectedCookieValue(options);
+  assert_equals(msg_event.data, expectedCookieValue);
+}
+
+// Tests for module scripts.
+
+promise_test(() => runCredentialsTest({ type: 'module'}),
+    'new Worker() with the default credentials option should not send ' +
+    'the credentials');
+
+promise_test(() => runCredentialsTest({ credentials: 'omit',
+                                        type: 'module' }),
+    'new Worker() with credentials=omit should not send the credentials');
+
+promise_test(() => runCredentialsTest({ credentials: 'same-origin',
+                                        type: 'module' }),
+    'new Worker() with credentials=same-origin should send the credentials');
+
+promise_test(() => runCredentialsTest({ credentials: 'include',
+                                        type: 'module' }),
+    'new Worker() with credentials=include should send the credentials');
+
+// Tests for classic scripts.
+
+promise_test(() => runCredentialsTest({ type: 'classic' }),
+    'new Worker() with type=classic should always send the credentials ' +
+    'regardless of the credentials option (default).');
+
+promise_test(() => runCredentialsTest({ credentials: 'omit',
+                                        type: 'classic' }),
+    'new Worker() with type=classic should always send the credentials ' +
+    'regardless of the credentials option (omit).');
+
+promise_test(() => runCredentialsTest({ credentials: 'same-origin',
+                                        type: 'classic' }),
+    'new Worker() with type=classic should always send the credentials ' +
+    'regardless of the credentials option (same-origin).');
+
+promise_test(() => runCredentialsTest({ credentials: 'include',
+                                        type: 'classic' }),
+    'new Worker() with type=classic should always send the credentials ' +
+    'regardless of the credentials option (include).');
+
+</script>
diff --git a/workers/modules/dedicated-worker-options-credentials.html.headers b/workers/modules/dedicated-worker-options-credentials.html.headers
new file mode 100644
index 0000000..6f53744
--- /dev/null
+++ b/workers/modules/dedicated-worker-options-credentials.html.headers
@@ -0,0 +1,2 @@
+Set-Cookie: COOKIE_NAME=COOKIE_VALUE
+Access-Control-Allow-Credentials: true
diff --git a/workers/modules/dedicated-worker-options-type.html b/workers/modules/dedicated-worker-options-type.html
new file mode 100644
index 0000000..b7c96b1
--- /dev/null
+++ b/workers/modules/dedicated-worker-options-type.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: WorkerOptions 'type'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(() => {
+  const worker = new Worker('resources/post-message-on-load-worker.js');
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the default worker type.');
+
+promise_test(() => {
+  const worker = new Worker('resources/post-message-on-load-worker.js',
+                            { type: 'classic' });
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the "classic" worker type.');
+
+promise_test(() => {
+  const worker = new Worker('resources/post-message-on-load-worker.js',
+                            { type: 'module' });
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the "module" worker type.');
+
+test(() => {
+  try {
+    new Worker('resources/post-message-on-load-worker.js', { type: '' });
+    assert_unreached(
+        'Worker construction with an empty type should throw an exception');
+  } catch (e) {
+    assert_equals(e.name, 'TypeError');
+  }
+}, 'Test worker construction with an empty worker type.');
+
+test(() => {
+  try {
+    new Worker('resources/post-message-on-load-worker.js', { type: 'unknown' });
+    assert_unreached(
+        'Worker construction with an unknown type should throw an exception');
+  } catch (e) {
+    assert_equals(e.name, 'TypeError');
+  }
+}, 'Test worker construction with an unknown worker type.');
+
+</script>
diff --git a/workers/modules/resources/credentials.py b/workers/modules/resources/credentials.py
new file mode 100644
index 0000000..8f79563
--- /dev/null
+++ b/workers/modules/resources/credentials.py
@@ -0,0 +1,10 @@
+def main(request, response):
+    cookie = request.cookies.first("COOKIE_NAME", None)
+
+    response_headers = [("Content-Type", "text/javascript"),
+                        ("Access-Control-Allow-Credentials", "true")]
+
+    cookie_value = '';
+    if cookie:
+        cookie_value = cookie.value;
+    return (200, response_headers, "postMessage('"+cookie_value+"');")
diff --git a/workers/modules/resources/dynamic-import-and-then-static-import-worker.js b/workers/modules/resources/dynamic-import-and-then-static-import-worker.js
new file mode 100644
index 0000000..eaa3f1c
--- /dev/null
+++ b/workers/modules/resources/dynamic-import-and-then-static-import-worker.js
@@ -0,0 +1,2 @@
+import('./static-import-worker.js')
+  .then(module => postMessage('LOADED'));
diff --git a/workers/modules/resources/dynamic-import-given-url-worker.js b/workers/modules/resources/dynamic-import-given-url-worker.js
new file mode 100644
index 0000000..444badd
--- /dev/null
+++ b/workers/modules/resources/dynamic-import-given-url-worker.js
@@ -0,0 +1,4 @@
+// Dynamically import the script URL sent by postMessage().
+self.addEventListener('message', e => {
+  import(e.data).catch(error_event => postMessage('ERROR'));
+});
diff --git a/workers/modules/resources/dynamic-import-worker.js b/workers/modules/resources/dynamic-import-worker.js
new file mode 100644
index 0000000..98381b9
--- /dev/null
+++ b/workers/modules/resources/dynamic-import-worker.js
@@ -0,0 +1,2 @@
+import('./post-message-on-load-worker.js')
+  .then(module => postMessage('LOADED'));
diff --git a/workers/modules/resources/empty-worker.js b/workers/modules/resources/empty-worker.js
new file mode 100644
index 0000000..49ceb26
--- /dev/null
+++ b/workers/modules/resources/empty-worker.js
@@ -0,0 +1 @@
+// Do nothing.
diff --git a/workers/modules/resources/import-meta-url-worker.js b/workers/modules/resources/import-meta-url-worker.js
new file mode 100644
index 0000000..9d90977
--- /dev/null
+++ b/workers/modules/resources/import-meta-url-worker.js
@@ -0,0 +1 @@
+postMessage(import.meta.url);
diff --git a/workers/modules/resources/import-scripts-worker.js b/workers/modules/resources/import-scripts-worker.js
new file mode 100644
index 0000000..c2a2489
--- /dev/null
+++ b/workers/modules/resources/import-scripts-worker.js
@@ -0,0 +1,6 @@
+try {
+  importScripts('empty-worker.js');
+  postMessage('LOADED');
+} catch (e) {
+  postMessage(e.name);
+}
diff --git a/workers/modules/resources/nested-dynamic-import-worker.js b/workers/modules/resources/nested-dynamic-import-worker.js
new file mode 100644
index 0000000..8f56d15
--- /dev/null
+++ b/workers/modules/resources/nested-dynamic-import-worker.js
@@ -0,0 +1,2 @@
+import('./dynamic-import-worker.js')
+  .then(module => postMessage('LOADED'));
diff --git a/workers/modules/resources/nested-static-import-worker.js b/workers/modules/resources/nested-static-import-worker.js
new file mode 100644
index 0000000..f2ef520
--- /dev/null
+++ b/workers/modules/resources/nested-static-import-worker.js
@@ -0,0 +1,2 @@
+import './static-import-worker.js';
+postMessage('LOADED')
diff --git a/workers/modules/resources/post-message-on-load-worker.js b/workers/modules/resources/post-message-on-load-worker.js
new file mode 100644
index 0000000..93818cc
--- /dev/null
+++ b/workers/modules/resources/post-message-on-load-worker.js
@@ -0,0 +1 @@
+postMessage('LOADED');
diff --git a/workers/modules/resources/static-import-and-then-dynamic-import-worker.js b/workers/modules/resources/static-import-and-then-dynamic-import-worker.js
new file mode 100644
index 0000000..4e66d04
--- /dev/null
+++ b/workers/modules/resources/static-import-and-then-dynamic-import-worker.js
@@ -0,0 +1,2 @@
+import './dynamic-import-worker.js';
+postMessage('LOADED');
diff --git a/workers/modules/resources/static-import-non-existent-script-worker.js b/workers/modules/resources/static-import-non-existent-script-worker.js
new file mode 100644
index 0000000..16f70e9
--- /dev/null
+++ b/workers/modules/resources/static-import-non-existent-script-worker.js
@@ -0,0 +1 @@
+import './non-existent-script.js';
diff --git a/workers/modules/resources/static-import-worker.js b/workers/modules/resources/static-import-worker.js
new file mode 100644
index 0000000..4693169
--- /dev/null
+++ b/workers/modules/resources/static-import-worker.js
@@ -0,0 +1,2 @@
+import './post-message-on-load-worker.js';
+postMessage('LOADED');
diff --git a/workers/semantics/structured-clone/common.js b/workers/semantics/structured-clone/common.js
deleted file mode 100644
index 56c2a30..0000000
--- a/workers/semantics/structured-clone/common.js
+++ /dev/null
@@ -1,596 +0,0 @@
-function createWorker(msg) {
-  // `type` is defined in the test case itself
-  if (type == 'dedicated')
-    return new Worker('dedicated.js#'+encodeURIComponent(msg));
-  else if (type == 'shared')
-    return (new SharedWorker('shared.js#'+encodeURIComponent(msg))).port;
-  else
-    assert_unreached('invalid or missing `type`');
-}
-
-function check(msg, input, callback, test_obj) {
-  if (!test_obj)
-    test_obj = async_test(msg);
-  test_obj.step(function() {
-    var w = createWorker(msg);
-    if (typeof input === 'function')
-      input = this.step(input);
-    w.postMessage(input);
-    w.onmessage = this.step_func(function(ev) { callback(ev.data, input, this); });
-  });
-}
-
-function compare_primitive(actual, input, test_obj) {
-  assert_equals(actual, input);
-  if (test_obj)
-    test_obj.done();
-}
-function compare_Array(callback, callback_is_async) {
-  return function(actual, input, test_obj) {
-    if (typeof actual === 'string')
-      assert_unreached(actual);
-    assert_true(actual instanceof Array, 'instanceof Array');
-    assert_not_equals(actual, input);
-    assert_equals(actual.length, input.length, 'length');
-    callback(actual, input);
-    if (test_obj && !callback_is_async)
-      test_obj.done();
-  }
-}
-
-function compare_Object(callback, callback_is_async) {
-  return function(actual, input, test_obj) {
-    if (typeof actual === 'string')
-      assert_unreached(actual);
-    assert_true(actual instanceof Object, 'instanceof Object');
-    assert_false(actual instanceof Array, 'instanceof Array');
-    assert_not_equals(actual, input);
-    callback(actual, input);
-    if (test_obj && !callback_is_async)
-      test_obj.done();
-  }
-}
-
-function enumerate_props(compare_func, test_obj) {
-  return function(actual, input) {
-    for (var x in input) {
-      compare_func(actual[x], input[x], test_obj);
-    }
-  };
-}
-
-check('primitive undefined', undefined, compare_primitive);
-check('primitive null', null, compare_primitive);
-check('primitive true', true, compare_primitive);
-check('primitive false', false, compare_primitive);
-check('primitive string, empty string', '', compare_primitive);
-check('primitive string, lone high surrogate', '\uD800', compare_primitive);
-check('primitive string, lone low surrogate', '\uDC00', compare_primitive);
-check('primitive string, NUL', '\u0000', compare_primitive);
-check('primitive string, astral character', '\uDBFF\uDFFD', compare_primitive);
-check('primitive number, 0.2', 0.2, compare_primitive);
-check('primitive number, 0', 0, compare_primitive);
-check('primitive number, -0', -0, compare_primitive);
-check('primitive number, NaN', NaN, compare_primitive);
-check('primitive number, Infinity', Infinity, compare_primitive);
-check('primitive number, -Infinity', -Infinity, compare_primitive);
-check('primitive number, 9007199254740992', 9007199254740992, compare_primitive);
-check('primitive number, -9007199254740992', -9007199254740992, compare_primitive);
-check('primitive number, 9007199254740994', 9007199254740994, compare_primitive);
-check('primitive number, -9007199254740994', -9007199254740994, compare_primitive);
-
-check('Array primitives', [undefined,
-                           null,
-                           true,
-                           false,
-                           '',
-                           '\uD800',
-                           '\uDC00',
-                           '\u0000',
-                           '\uDBFF\uDFFD',
-                           0.2,
-                           0,
-                           -0,
-                           NaN,
-                           Infinity,
-                           -Infinity,
-                           9007199254740992,
-                           -9007199254740992,
-                           9007199254740994,
-                           -9007199254740994], compare_Array(enumerate_props(compare_primitive)));
-check('Object primitives', {'undefined':undefined,
-                           'null':null,
-                           'true':true,
-                           'false':false,
-                           'empty':'',
-                           'high surrogate':'\uD800',
-                           'low surrogate':'\uDC00',
-                           'nul':'\u0000',
-                           'astral':'\uDBFF\uDFFD',
-                           '0.2':0.2,
-                           '0':0,
-                           '-0':-0,
-                           'NaN':NaN,
-                           'Infinity':Infinity,
-                           '-Infinity':-Infinity,
-                           '9007199254740992':9007199254740992,
-                           '-9007199254740992':-9007199254740992,
-                           '9007199254740994':9007199254740994,
-                           '-9007199254740994':-9007199254740994}, compare_Object(enumerate_props(compare_primitive)));
-
-function compare_Boolean(actual, input, test_obj) {
-  if (typeof actual === 'string')
-    assert_unreached(actual);
-  assert_true(actual instanceof Boolean, 'instanceof Boolean');
-  assert_equals(String(actual), String(input), 'converted to primitive');
-  assert_not_equals(actual, input);
-  if (test_obj)
-    test_obj.done();
-}
-check('Boolean true', new Boolean(true), compare_Boolean);
-check('Boolean false', new Boolean(false), compare_Boolean);
-check('Array Boolean objects', [new Boolean(true), new Boolean(false)], compare_Array(enumerate_props(compare_Boolean)));
-check('Object Boolean objects', {'true':new Boolean(true), 'false':new Boolean(false)}, compare_Object(enumerate_props(compare_Boolean)));
-
-function compare_obj(what) {
-  var Type = window[what];
-  return function(actual, input, test_obj) {
-    if (typeof actual === 'string')
-      assert_unreached(actual);
-    assert_true(actual instanceof Type, 'instanceof '+what);
-    assert_equals(Type(actual), Type(input), 'converted to primitive');
-    assert_not_equals(actual, input);
-    if (test_obj)
-      test_obj.done();
-  };
-}
-check('String empty string', new String(''), compare_obj('String'));
-check('String lone high surrogate', new String('\uD800'), compare_obj('String'));
-check('String lone low surrogate', new String('\uDC00'), compare_obj('String'));
-check('String NUL', new String('\u0000'), compare_obj('String'));
-check('String astral character', new String('\uDBFF\uDFFD'), compare_obj('String'));
-check('Array String objects', [new String(''),
-                               new String('\uD800'),
-                               new String('\uDC00'),
-                               new String('\u0000'),
-                               new String('\uDBFF\uDFFD')], compare_Array(enumerate_props(compare_obj('String'))));
-check('Object String objects', {'empty':new String(''),
-                               'high surrogate':new String('\uD800'),
-                               'low surrogate':new String('\uDC00'),
-                               'nul':new String('\u0000'),
-                               'astral':new String('\uDBFF\uDFFD')}, compare_Object(enumerate_props(compare_obj('String'))));
-
-check('Number 0.2', new Number(0.2), compare_obj('Number'));
-check('Number 0', new Number(0), compare_obj('Number'));
-check('Number -0', new Number(-0), compare_obj('Number'));
-check('Number NaN', new Number(NaN), compare_obj('Number'));
-check('Number Infinity', new Number(Infinity), compare_obj('Number'));
-check('Number -Infinity', new Number(-Infinity), compare_obj('Number'));
-check('Number 9007199254740992', new Number(9007199254740992), compare_obj('Number'));
-check('Number -9007199254740992', new Number(-9007199254740992), compare_obj('Number'));
-check('Number 9007199254740994', new Number(9007199254740994), compare_obj('Number'));
-check('Number -9007199254740994', new Number(-9007199254740994), compare_obj('Number'));
-check('Array Number objects', [new Number(0.2),
-                               new Number(0),
-                               new Number(-0),
-                               new Number(NaN),
-                               new Number(Infinity),
-                               new Number(-Infinity),
-                               new Number(9007199254740992),
-                               new Number(-9007199254740992),
-                               new Number(9007199254740994),
-                               new Number(-9007199254740994)], compare_Array(enumerate_props(compare_obj('Number'))));
-check('Object Number objects', {'0.2':new Number(0.2),
-                               '0':new Number(0),
-                               '-0':new Number(-0),
-                               'NaN':new Number(NaN),
-                               'Infinity':new Number(Infinity),
-                               '-Infinity':new Number(-Infinity),
-                               '9007199254740992':new Number(9007199254740992),
-                               '-9007199254740992':new Number(-9007199254740992),
-                               '9007199254740994':new Number(9007199254740994),
-                               '-9007199254740994':new Number(-9007199254740994)}, compare_Object(enumerate_props(compare_obj('Number'))));
-
-function compare_Date(actual, input, test_obj) {
-  if (typeof actual === 'string')
-    assert_unreached(actual);
-  assert_true(actual instanceof Date, 'instanceof Date');
-  assert_equals(Number(actual), Number(input), 'converted to primitive');
-  assert_not_equals(actual, input);
-  if (test_obj)
-    test_obj.done();
-}
-check('Date 0', new Date(0), compare_Date);
-check('Date -0', new Date(-0), compare_Date);
-check('Date -8.64e15', new Date(-8.64e15), compare_Date);
-check('Date 8.64e15', new Date(8.64e15), compare_Date);
-check('Array Date objects', [new Date(0),
-                             new Date(-0),
-                             new Date(-8.64e15),
-                             new Date(8.64e15)], compare_Array(enumerate_props(compare_Date)));
-check('Object Date objects', {'0':new Date(0),
-                              '-0':new Date(-0),
-                              '-8.64e15':new Date(-8.64e15),
-                              '8.64e15':new Date(8.64e15)}, compare_Object(enumerate_props(compare_Date)));
-
-function compare_RegExp(expected_source) {
-  // XXX ES6 spec doesn't define exact serialization for `source` (it allows several ways to escape)
-  return function(actual, input, test_obj) {
-    if (typeof actual === 'string')
-      assert_unreached(actual);
-    assert_true(actual instanceof RegExp, 'instanceof RegExp');
-    assert_equals(actual.global, input.global, 'global');
-    assert_equals(actual.ignoreCase, input.ignoreCase, 'ignoreCase');
-    assert_equals(actual.multiline, input.multiline, 'multiline');
-    assert_equals(actual.source, expected_source, 'source');
-    assert_equals(actual.sticky, input.sticky, 'sticky');
-    assert_equals(actual.unicode, input.unicode, 'unicode');
-    assert_equals(actual.lastIndex, 0, 'lastIndex');
-    assert_not_equals(actual, input);
-    if (test_obj)
-      test_obj.done();
-  }
-}
-function func_RegExp_flags_lastIndex() {
-  var r = /foo/gim;
-  r.lastIndex = 2;
-  return r;
-}
-function func_RegExp_sticky() {
-  return new RegExp('foo', 'y');
-}
-function func_RegExp_unicode() {
-  return new RegExp('foo', 'u');
-}
-check('RegExp flags and lastIndex', func_RegExp_flags_lastIndex, compare_RegExp('foo'));
-check('RegExp sticky flag', func_RegExp_sticky, compare_RegExp('foo'));
-check('RegExp unicode flag', func_RegExp_unicode, compare_RegExp('foo'));
-check('RegExp empty', new RegExp(''), compare_RegExp('(?:)'));
-check('RegExp slash', new RegExp('/'), compare_RegExp('\\/'));
-check('RegExp new line', new RegExp('\n'), compare_RegExp('\\n'));
-check('Array RegExp object, RegExp flags and lastIndex', [func_RegExp_flags_lastIndex()], compare_Array(enumerate_props(compare_RegExp('foo'))));
-check('Array RegExp object, RegExp sticky flag', function() { return [func_RegExp_sticky()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
-check('Array RegExp object, RegExp unicode flag', function() { return [func_RegExp_unicode()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
-check('Array RegExp object, RegExp empty', [new RegExp('')], compare_Array(enumerate_props(compare_RegExp('(?:)'))));
-check('Array RegExp object, RegExp slash', [new RegExp('/')], compare_Array(enumerate_props(compare_RegExp('\\/'))));
-check('Array RegExp object, RegExp new line', [new RegExp('\n')], compare_Array(enumerate_props(compare_RegExp('\\n'))));
-check('Object RegExp object, RegExp flags and lastIndex', {'x':func_RegExp_flags_lastIndex()}, compare_Object(enumerate_props(compare_RegExp('foo'))));
-check('Object RegExp object, RegExp sticky flag', function() { return {'x':func_RegExp_sticky()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
-check('Object RegExp object, RegExp unicode flag', function() { return {'x':func_RegExp_unicode()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
-check('Object RegExp object, RegExp empty', {'x':new RegExp('')}, compare_Object(enumerate_props(compare_RegExp('(?:)'))));
-check('Object RegExp object, RegExp slash', {'x':new RegExp('/')}, compare_Object(enumerate_props(compare_RegExp('\\/'))));
-check('Object RegExp object, RegExp new line', {'x':new RegExp('\n')}, compare_Object(enumerate_props(compare_RegExp('\\n'))));
-
-function compare_Blob(actual, input, test_obj, expect_File) {
-  if (typeof actual === 'string')
-    assert_unreached(actual);
-  assert_true(actual instanceof Blob, 'instanceof Blob');
-  if (!expect_File)
-    assert_false(actual instanceof File, 'instanceof File');
-  assert_equals(actual.size, input.size, 'size');
-  assert_equals(actual.type, input.type, 'type');
-  assert_not_equals(actual, input);
-  var ev_reader = new FileReader();
-  var input_reader = new FileReader();
-  var read_count = 0;
-  var read_done = test_obj.step_func(function() {
-    read_count++;
-    if (read_count == 2) {
-      var ev_result = ev_reader.result;
-      var input_result = input_reader.result;
-      assert_equals(ev_result.byteLength, input_result.byteLength, 'byteLength');
-      var ev_view = new DataView(ev_result);
-      var input_view = new DataView(input_result);
-      for (var i = 0; i < ev_result.byteLength; ++i) {
-        assert_equals(ev_view.getUint8(i), input_view.getUint8(i), 'getUint8('+i+')');
-      }
-      if (test_obj)
-        test_obj.done();
-    }
-  });
-  var read_error = test_obj.step_func(function() { assert_unreached('FileReader error'); });
-  ev_reader.readAsArrayBuffer(actual);
-  ev_reader.onload = read_done;
-  ev_reader.onabort = ev_reader.onerror = read_error;
-  input_reader.readAsArrayBuffer(input);
-  input_reader.onload = read_done;
-  input_reader.onabort = input_reader.onerror = read_error;
-}
-function func_Blob_basic() {
-  return new Blob(['foo'], {type:'text/x-bar'});
-}
-check('Blob basic', func_Blob_basic, compare_Blob);
-
-function b(str) {
-  return parseInt(str, 2);
-}
-function encode_cesu8(codeunits) {
-  // http://www.unicode.org/reports/tr26/ section 2.2
-  // only the 3-byte form is supported
-  var rv = [];
-  codeunits.forEach(function(codeunit) {
-    rv.push(b('11100000') + ((codeunit & b('1111000000000000')) >> 12));
-    rv.push(b('10000000') + ((codeunit & b('0000111111000000')) >> 6));
-    rv.push(b('10000000') +  (codeunit & b('0000000000111111')));
-  });
-  return rv;
-}
-function func_Blob_bytes(arr) {
-  return function() {
-    var buffer = new ArrayBuffer(arr.length);
-    var view = new DataView(buffer);
-    for (var i = 0; i < arr.length; ++i) {
-      view.setUint8(i, arr[i]);
-    }
-    return new Blob([view]);
-  };
-}
-check('Blob unpaired high surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800])), compare_Blob);
-check('Blob unpaired low surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xDC00])), compare_Blob);
-check('Blob paired surrogates (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800, 0xDC00])), compare_Blob);
-
-function func_Blob_empty() {
-  return new Blob(['']);
-}
-check('Blob empty', func_Blob_empty , compare_Blob);
-function func_Blob_NUL() {
-  return new Blob(['\u0000']);
-}
-check('Blob NUL', func_Blob_NUL, compare_Blob);
-
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_Blob_basic)], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Array Blob object, Blob basic');
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_Blob_bytes([0xD800]))], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Array Blob object, Blob unpaired high surrogate (invalid utf-8)');
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_Blob_bytes([0xDC00]))], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Array Blob object, Blob unpaired low surrogate (invalid utf-8)');
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_Blob_bytes([0xD800, 0xDC00]))], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Array Blob object, Blob paired surrogates (invalid utf-8)');
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_Blob_empty)], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Array Blob object, Blob empty');
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_Blob_NUL)], compare_Array(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Array Blob object, Blob NUL');
-
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_Blob_basic)}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Object Blob object, Blob basic');
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xD800]))}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Object Blob object, Blob unpaired high surrogate (invalid utf-8)');
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xDC00]))}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Object Blob object, Blob unpaired low surrogate (invalid utf-8)');
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_Blob_bytes([0xD800, 0xDC00]))}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Object Blob object, Blob paired surrogates (invalid utf-8)');
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_Blob_empty)}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Object Blob object, Blob empty');
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_Blob_NUL)}, compare_Object(enumerate_props(compare_Blob, test_obj), true), test_obj);
-}, 'Object Blob object, Blob NUL');
-
-function compare_File(actual, input, test_obj) {
-  assert_true(actual instanceof File, 'instanceof File');
-  assert_equals(actual.name, input.name, 'name');
-  assert_equals(actual.lastModified, input.lastModified, 'lastModified');
-  compare_Blob(actual, input, test_obj, true);
-}
-function func_File_basic() {
-  return new File(['foo'], 'bar', {type:'text/x-bar', lastModified:42});
-}
-check('File basic', func_File_basic, compare_File);
-
-function compare_FileList(actual, input, test_obj) {
-  if (typeof actual === 'string')
-    assert_unreached(actual);
-  assert_true(actual instanceof FileList, 'instanceof FileList');
-  assert_equals(actual.length, input.length, 'length');
-  assert_not_equals(actual, input);
-  // XXX when there's a way to populate or construct a FileList,
-  // check the items in the FileList
-  if (test_obj)
-    test_obj.done();
-}
-function func_FileList_empty() {
-  var input = document.createElement('input');
-  input.type = 'file';
-  return input.files;
-}
-check('FileList empty', func_FileList_empty, compare_FileList);
-check('Array FileList object, FileList empty', [func_FileList_empty], compare_Array(enumerate_props(compare_FileList)));
-check('Object FileList object, FileList empty', {'x':func_FileList_empty}, compare_Object(enumerate_props(compare_FileList)));
-
-function compare_ArrayBufferView(view) {
-  var Type = window[view];
-  return function(actual, input, test_obj) {
-    if (typeof actual === 'string')
-      assert_unreached(actual);
-    assert_true(actual instanceof Type, 'instanceof '+view);
-    assert_equals(actual.length, input.length, 'length');
-    assert_not_equals(actual.buffer, input.buffer, 'buffer');
-    for (var i = 0; i < actual.length; ++i) {
-      assert_equals(actual[i], input[i], 'actual['+i+']');
-    }
-    if (test_obj)
-      test_obj.done();
-  };
-}
-function compare_ImageData(actual, input, test_obj) {
-  if (typeof actual === 'string')
-    assert_unreached(actual);
-  assert_equals(actual.width, input.width, 'width');
-  assert_equals(actual.height, input.height, 'height');
-  assert_not_equals(actual.data, input.data, 'data');
-  compare_ArrayBufferView('Uint8ClampedArray')(actual.data, input.data, null);
-  if (test_obj)
-    test_obj.done();
-}
-function func_ImageData_1x1_transparent_black() {
-  var canvas = document.createElement('canvas');
-  var ctx = canvas.getContext('2d');
-  return ctx.createImageData(1, 1);
-}
-check('ImageData 1x1 transparent black', func_ImageData_1x1_transparent_black, compare_ImageData);
-function func_ImageData_1x1_non_transparent_non_black() {
-  var canvas = document.createElement('canvas');
-  var ctx = canvas.getContext('2d');
-  var imagedata = ctx.createImageData(1, 1);
-  imagedata.data[0] = 100;
-  imagedata.data[1] = 101;
-  imagedata.data[2] = 102;
-  imagedata.data[3] = 103;
-  return imagedata;
-}
-check('ImageData 1x1 non-transparent non-black', func_ImageData_1x1_non_transparent_non_black, compare_ImageData);
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_ImageData_1x1_transparent_black)], compare_Array(enumerate_props(compare_ImageData)), test_obj);
-}, 'Array ImageData object, ImageData 1x1 transparent black');
-async_test(function(test_obj) {
-  check(test_obj.name, [test_obj.step(func_ImageData_1x1_non_transparent_non_black)], compare_Array(enumerate_props(compare_ImageData)), test_obj);
-}, 'Array ImageData object, ImageData 1x1 non-transparent non-black');
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_ImageData_1x1_transparent_black)}, compare_Object(enumerate_props(compare_ImageData)), test_obj);
-}, 'Object ImageData object, ImageData 1x1 transparent black');
-async_test(function(test_obj) {
-  check(test_obj.name, {'x':test_obj.step(func_ImageData_1x1_non_transparent_non_black)}, compare_Object(enumerate_props(compare_ImageData)), test_obj);
-}, 'Object ImageData object, ImageData 1x1 non-transparent non-black');
-
-function compare_ImageBitmap(actual, input, test_obj) {
-  if (typeof actual === 'string')
-    assert_unreached(actual);
-  assert_equals(actual instanceof ImageBitmap, 'instanceof ImageBitmap');
-  assert_not_equals(actual, input);
-  // XXX paint the ImageBitmap on a canvas and check the data
-  if (test_obj)
-    test_obj.done();
-}
-function get_canvas_1x1_transparent_black() {
-  var canvas = document.createElement('canvas');
-  canvas.width = 1;
-  canvas.height = 1;
-  return canvas;
-}
-async_test(function(test_obj) {
-  var canvas = get_canvas_1x1_transparent_black();
-  createImageBitmap(canvas, function(image) { check(test_obj.name, image, compare_ImageBitmap, test_obj); });
-}, 'ImageBitmap 1x1 transparent black');
-function get_canvas_1x1_non_transparent_non_black() {
-  var canvas = document.createElement('canvas');
-  canvas.width = 1;
-  canvas.height = 1;
-  var ctx = canvas.getContext('2d');
-  var imagedata = ctx.getImageData(0, 0, 1, 1);
-  imagedata.data[0] = 100;
-  imagedata.data[1] = 101;
-  imagedata.data[2] = 102;
-  imagedata.data[3] = 103;
-  return canvas;
-}
-async_test(function(test_obj) {
-  var canvas = get_canvas_1x1_non_transparent_non_black();
-  createImageBitmap(canvas, function(image) { check(test_obj.name, image, compare_ImageBitmap, test_obj); });
-}, 'ImageBitmap 1x1 non-transparent non-black');
-
-async_test(function(test_obj) {
-  var canvas = get_canvas_1x1_transparent_black();
-  createImageBitmap(canvas, function(image) { check(test_obj.name, [image], compare_Array(enumerate_props(compare_ImageBitmap)), test_obj); });
-}, 'Array ImageBitmap object, ImageBitmap 1x1 transparent black');
-async_test(function(test_obj) {
-  var canvas = get_canvas_1x1_non_transparent_non_black();
-  createImageBitmap(canvas, function(image) { check(test_obj.name, [image], compare_Array(enumerate_props(compare_ImageBitmap)), test_obj); });
-}, 'Array ImageBitmap object, ImageBitmap 1x1 non-transparent non-black');
-
-async_test(function(test_obj) {
-  var canvas = get_canvas_1x1_transparent_black();
-  createImageBitmap(canvas, function(image) { check(test_obj.name, {'x':image}, compare_Object(enumerate_props(compare_ImageBitmap)), test_obj); });
-}, 'Object ImageBitmap object, ImageBitmap 1x1 transparent black');
-async_test(function(test_obj) {
-  var canvas = get_canvas_1x1_non_transparent_non_black();
-  createImageBitmap(canvas, function(image) { check(test_obj.name, {'x':image}, compare_Object(enumerate_props(compare_ImageBitmap)), test_obj); });
-}, 'Object ImageBitmap object, ImageBitmap 1x1 non-transparent non-black');
-
-check('Array sparse', new Array(10), compare_Array(enumerate_props(compare_primitive)));
-check('Array with non-index property', function() {
-  var rv = [];
-  rv.foo = 'bar';
-  return rv;
-}, compare_Array(enumerate_props(compare_primitive)));
-check('Object with index property and length', {'0':'foo', 'length':1}, compare_Object(enumerate_props(compare_primitive)));
-function check_circular_property(prop) {
-  return function(actual) {
-    assert_equals(actual[prop], actual);
-  };
-}
-check('Array with circular reference', function() {
-  var rv = [];
-  rv[0] = rv;
-  return rv;
-}, compare_Array(check_circular_property('0')));
-check('Object with circular reference', function() {
-  var rv = {};
-  rv['x'] = rv;
-  return rv;
-}, compare_Object(check_circular_property('x')));
-function check_identical_property_values(prop1, prop2) {
-  return function(actual) {
-    assert_equals(actual[prop1], actual[prop2]);
-  };
-}
-check('Array with identical property values', function() {
-  var obj = {}
-  return [obj, obj];
-}, compare_Array(check_identical_property_values('0', '1')));
-check('Object with identical property values', function() {
-  var obj = {}
-  return {'x':obj, 'y':obj};
-}, compare_Object(check_identical_property_values('x', 'y')));
-
-function check_absent_property(prop) {
-  return function(actual) {
-    assert_false(prop in actual);
-  };
-}
-check('Object with property on prototype', function() {
-  var Foo = function() {};
-  Foo.prototype = {'foo':'bar'};
-  return new Foo();
-}, compare_Object(check_absent_property('foo')));
-
-check('Object with non-enumerable property', function() {
-  var rv = {};
-  Object.defineProperty(rv, 'foo', {value:'bar', enumerable:false, writable:true, configurable:true});
-  return rv;
-}, compare_Object(check_absent_property('foo')));
-
-function check_writable_property(prop) {
-  return function(actual, input) {
-    assert_equals(actual[prop], input[prop]);
-    actual[prop] += ' baz';
-    assert_equals(actual[prop], input[prop] + ' baz');
-  };
-}
-check('Object with non-writable property', function() {
-  var rv = {};
-  Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:false, configurable:true});
-  return rv;
-}, compare_Object(check_writable_property('foo')));
-
-function check_configurable_property(prop) {
-  return function(actual, input) {
-    assert_equals(actual[prop], input[prop]);
-    delete actual[prop];
-    assert_false('prop' in actual);
-  };
-}
-check('Object with non-configurable property', function() {
-  var rv = {};
-  Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:true, configurable:false});
-  return rv;
-}, compare_Object(check_configurable_property('foo')));
diff --git a/workers/semantics/structured-clone/dedicated.html b/workers/semantics/structured-clone/dedicated.html
index 5dd8c35..2f1732c 100644
--- a/workers/semantics/structured-clone/dedicated.html
+++ b/workers/semantics/structured-clone/dedicated.html
@@ -2,8 +2,31 @@
 <title>structured clone to dedicated worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js></script>
+<script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js></script>
+<script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js></script>
 <div id=log></div>
 <script>
-var type = 'dedicated';
+  runStructuredCloneBatteryOfTests({
+    setup() {
+      const blob = new Blob([`
+        onmessage = ev => postMessage(ev.data, ev.data.transfer);
+      `], {type: 'text/javascript'});
+      this.blobURL = URL.createObjectURL(blob);
+      this.worker = new Worker(this.blobURL);
+    },
+    structuredClone(data, transfer) {
+      return new Promise(resolve => {
+        this.worker.addEventListener('message', function f(ev) {
+          this.worker.removeEventListener('message', f);
+          resolve(ev.data.data);
+        }.bind(this));
+        this.worker.postMessage({data, transfer}, transfer);
+      });
+    },
+    teardown() {
+      this.worker.terminate();
+      URL.revokeObjectURL(this.blobURL);
+    }
+  });
 </script>
-<script src="common.js"></script>
diff --git a/workers/semantics/structured-clone/dedicated.js b/workers/semantics/structured-clone/dedicated.js
deleted file mode 100644
index 4744578..0000000
--- a/workers/semantics/structured-clone/dedicated.js
+++ /dev/null
@@ -1,4 +0,0 @@
-importScripts('worker-common.js');
-onmessage = function(ev) {
-  check(ev.data, self);
-};
diff --git a/workers/semantics/structured-clone/shared.html b/workers/semantics/structured-clone/shared.html
index 6f74354..793da8f 100644
--- a/workers/semantics/structured-clone/shared.html
+++ b/workers/semantics/structured-clone/shared.html
@@ -2,8 +2,35 @@
 <title>structured clone to shared worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js></script>
+<script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js></script>
+<script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js></script>
 <div id=log></div>
 <script>
-var type = 'shared';
+  runStructuredCloneBatteryOfTests({
+    setup() {
+      const blob = new Blob([`
+        onconnect = ev => {
+          const port = ev.ports[0];
+          port.onmessage = ev => port.postMessage(ev.data, ev.data.transfer);
+        };
+      `], {type: 'text/javascript'});
+      this.blobURL = URL.createObjectURL(blob);
+      this.worker = new SharedWorker(this.blobURL);
+      this.port = this.worker.port;
+    },
+    structuredClone(data, transfer) {
+      return new Promise(resolve => {
+        this.port.addEventListener('message', function f(ev) {
+          this.port.removeEventListener('message', f);
+          resolve(ev.data.data);
+        }.bind(this));
+        this.port.start();
+        this.port.postMessage({data, transfer}, transfer);
+      });
+    },
+    teardown() {
+      URL.revokeObjectURL(this.blobURL);
+    }
+  });
 </script>
-<script src="common.js"></script>
diff --git a/workers/semantics/structured-clone/shared.js b/workers/semantics/structured-clone/shared.js
deleted file mode 100644
index 372a9ec..0000000
--- a/workers/semantics/structured-clone/shared.js
+++ /dev/null
@@ -1,6 +0,0 @@
-importScripts('worker-common.js');
-onconnect = function(connect_ev) {
-  connect_ev.ports[0].onmessage = function(message_ev) {
-    check(message_ev.data, this);
-  };
-};
diff --git a/workers/semantics/structured-clone/worker-common.js b/workers/semantics/structured-clone/worker-common.js
deleted file mode 100644
index fd63ff5..0000000
--- a/workers/semantics/structured-clone/worker-common.js
+++ /dev/null
@@ -1,1018 +0,0 @@
-var msg = decodeURIComponent(location.hash.substr(1));
-
-var log = [];
-function check_true(actual, msg) {
-  if (actual !== true) {
-    log.push(msg);
-    return false;
-  }
-  return true;
-}
-
-function check_Blob(msg, input, port, expect_File, orig_input) {
-  expect_File = !!expect_File;
-  orig_input = orig_input || input;
-  try {
-    var expected;
-    switch (msg) {
-      case 'Blob basic':
-      case 'File basic':
-        expected = [0x66, 0x6F, 0x6F];
-        expected.type = 'text/x-bar';
-        if (expect_File) {
-          expected.name = 'bar';
-          expected.lastModified = 42;
-        }
-        break;
-      case 'Blob unpaired high surrogate (invalid utf-8)':
-        expected = [0xED, 0xA0, 0x80];
-        expected.type = '';
-        break;
-      case 'Blob unpaired low surrogate (invalid utf-8)':
-        expected = [0xED, 0xB0, 0x80];
-        expected.type = '';
-        break;
-      case 'Blob paired surrogates (invalid utf-8)':
-        expected = [0xED, 0xA0, 0x80, 0xED, 0xB0, 0x80];
-        expected.type = '';
-        break;
-      case 'Blob empty':
-        expected = [];
-        expected.type = '';
-        break;
-      case 'Blob NUL':
-        var expected = [0x00];
-        expected.type = '';
-        break;
-      default:
-        check_true(false, 'check_Blob: unknown test');
-        return;
-        break;
-    }
-    if (check_true(input instanceof Blob, 'input instanceof Blob') &&
-        check_true((input instanceof File) == expect_File, '(input instanceof File) == expect_File') &&
-        check_true(input.size === expected.length, 'input.size === expected.length') &&
-        check_true(input.type === expected.type, 'input.type === expected.type')) {
-      if (!expect_File || (check_true(input.name === expected.name, 'input.name === expected.name') &&
-                           check_true(input.lastModified === expected.lastModified))) {
-        var reader = new FileReader();
-        var read_done = function() {
-          try {
-            var result = reader.result;
-            check_true(result.byteLength === expected.length, 'result.byteLength === expected.length')
-            var view = new DataView(result);
-            for (var i = 0; i < result.byteLength; ++i) {
-              check_true(view.getUint8(i) === expected[i], 'view.getUint8('+i+') === expected['+i+']')
-            }
-            if (log.length === 0) {
-              port.postMessage(orig_input);
-            } else {
-              port.postMessage('FAIL '+log);
-            }
-            close();
-          } catch(ex) {
-            postMessage('FAIL '+ex);
-            close();
-          }
-        }
-        var read_error = function() { port.postMessage('FAIL (got FileReader error)'); close(); };
-        reader.readAsArrayBuffer(input);
-        reader.onload = read_done;
-        reader.onabort = reader.onerror = read_error;
-      }
-    } else {
-      port.postMessage('FAIL '+log);
-      close();
-    }
-  } catch(ex) {
-    postMessage('FAIL '+ex);
-    close();
-  }
-}
-
-function check_ImageData(input, expected) {
-  if (check_true(input instanceof ImageData, 'input instanceof ImageData') &&
-      check_true(input.width === expected.width, 'input.width === '+expected.width) &&
-      check_true(input.height === expected.height, 'input.height === '+expected.height) &&
-      check_true(input.data instanceof Uint8ClampedArray, 'input.data instanceof Uint8ClampedArray') &&
-      check_true(input.data.length === expected.data.length, 'input.data.length === '+expected.data.length) &&
-      check_true(!('CanvasPixelArray' in self), "!('CanvasPixelArray' in self)")) {
-    for (var i = 0; i < input.length; ++i) {
-      if (!(check_true(input.data[i] === expected.data[i], 'input.data['+i+'] === '+expected.data[i]))) {
-        return false;
-      }
-    }
-    return true;
-  }
-  return false;
-}
-
-function check_ImageBitmap(input, expected) {
-  return check_true(input instanceof ImageBitmap, 'input instanceof ImageBitmap');
-  // XXX paint it on a proxy canvas and check the data
-}
-
-function check_RegExp(msg, input) {
-  // XXX ES6 spec doesn't define exact serialization for `source` (it allows several ways to escape)
-  switch (msg) {
-    case 'RegExp flags and lastIndex':
-      return check_true(input instanceof RegExp, "input instanceof RegExp") &&
-             check_true(input.source === 'foo', "input.source === 'foo'") &&
-             check_true(input.global === true, "input.global === true") &&
-             check_true(input.ignoreCase === true, "input.ignoreCase === true") &&
-             check_true(input.multiline === true, "input.multiline === true") &&
-             check_true(input.lastIndex === 0, "input.lastIndex === 0");
-      break;
-    case 'RegExp sticky flag':
-      return check_true(input instanceof RegExp, "input instanceof RegExp") &&
-             check_true(input.source === 'foo', "input.source === 'foo'") &&
-             check_true(input.global === false, "input.global === false") &&
-             check_true(input.ignoreCase === false, "input.ignoreCase === false") &&
-             check_true(input.multiline === false, "input.multiline === false") &&
-             check_true(input.sticky === true, "input.sticky === true") &&
-             check_true(input.unicode === false, "input.unicode === false") &&
-             check_true(input.lastIndex === 0, "input.lastIndex === 0");
-      break;
-    case 'RegExp unicode flag':
-      return check_true(input instanceof RegExp, "input instanceof RegExp") &&
-             check_true(input.source === 'foo', "input.source === 'foo'") &&
-             check_true(input.global === false, "input.global === false") &&
-             check_true(input.ignoreCase === false, "input.ignoreCase === false") &&
-             check_true(input.multiline === false, "input.multiline === false") &&
-             check_true(input.sticky === false, "input.sticky === false") &&
-             check_true(input.unicode === true, "input.unicode === true") &&
-             check_true(input.lastIndex === 0, "input.lastIndex === 0");
-      break;
-    case 'RegExp empty':
-      return check_true(input instanceof RegExp, "input instanceof RegExp") &&
-             check_true(input.source === '(?:)', "input.source === '(?:)'") &&
-             check_true(input.global === false, "input.global === false") &&
-             check_true(input.ignoreCase === false, "input.ignoreCase === false") &&
-             check_true(input.multiline === false, "input.multiline === false") &&
-             check_true(input.lastIndex === 0, "input.lastIndex === 0");
-      break;
-    case 'RegExp slash':
-      return check_true(input instanceof RegExp, "input instanceof RegExp") &&
-             check_true(input.source === '\\/', "input.source === '\\\\/'") &&
-             check_true(input.global === false, "input.global === false") &&
-             check_true(input.ignoreCase === false, "input.ignoreCase === false") &&
-             check_true(input.multiline === false, "input.multiline === false") &&
-             check_true(input.lastIndex === 0, "input.lastIndex === 0");
-      break;
-    case 'RegExp new line':
-      return check_true(input instanceof RegExp, "input instanceof RegExp") &&
-             check_true(input.source === '\\n', "input.source === '\\\\n'") &&
-             check_true(input.global === false, "input.global === false") &&
-             check_true(input.ignoreCase === false, "input.ignoreCase === false") &&
-             check_true(input.multiline === false, "input.multiline === false") &&
-             check_true(input.lastIndex === 0, "input.lastIndex === 0");
-      break;
-    default:
-      check_true(false, 'check_RegExp: unknown test');
-      return false;
-      break;
-  }
-}
-
-function check_FileList(msg, input) {
-  try {
-    return check_true(input instanceof FileList, 'input instanceof FileList') &&
-           check_true(input.length === 0, 'input.length === 0');
-  } catch(ex) {
-    return check_true(false, ex);
-  }
-}
-
-function check(input, port) {
-  try {
-    switch (msg) {
-      case 'primitive undefined':
-        if (check_true(input === undefined, 'input === undefined')) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive null':
-        if (check_true(input === null, 'input === null')) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive true':
-        if (check_true(input === true, 'input === true')) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive false':
-        if (check_true(input === false, 'input === false')) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive string, empty string':
-        if (check_true(input === '', "input === ''")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive string, lone high surrogate':
-        if (check_true(input === '\uD800', "input === '\uD800'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive string, lone low surrogate':
-        if (check_true(input === '\uDC00', "input === '\uDC00'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive string, NUL':
-        if (check_true(input === '\u0000', "input === '\u0000'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive string, astral character':
-        if (check_true(input === '\uDBFF\uDFFD', "input === '\uDBFF\uDFFD'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, 0.2':
-        if (check_true(input === 0.2, "input === 0.2")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, 0':
-        if (check_true(input === 0, "input === 0") &&
-            check_true(1/input === Infinity, "1/input === Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, -0':
-        if (check_true(input === 0, "input === 0") &&
-            check_true(1/input === -Infinity, "1/input === -Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, NaN':
-        if (check_true(input !== input, "input !== input")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, Infinity':
-        if (check_true(input === Infinity, "input === Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, -Infinity':
-        if (check_true(input === -Infinity, "input === -Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, 9007199254740992':
-        if (check_true(input === 9007199254740992, "input === 9007199254740992")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, -9007199254740992':
-        if (check_true(input === -9007199254740992, "input === -9007199254740992")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, 9007199254740994':
-        if (check_true(input === 9007199254740994, "input === 9007199254740994")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'primitive number, -9007199254740994':
-        if (check_true(input === -9007199254740994, "input === -9007199254740994")) {
-          port.postMessage(input);
-          close();
-          break;
-        }
-      case 'Array primitives':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 19, 'input.length === 19') &&
-            check_true(input[0] === undefined, 'input[0] === undefined') &&
-            check_true(input[1] === null, 'input[1] === null') &&
-            check_true(input[2] === true, 'input[2] === true') &&
-            check_true(input[3] === false, 'input[3] === false') &&
-            check_true(input[4] === '', "input[4] === ''") &&
-            check_true(input[5] === '\uD800', "input[5] === '\\uD800'") &&
-            check_true(input[6] === '\uDC00', "input[6] === '\\uDC00'") &&
-            check_true(input[7] === '\u0000', "input[7] === '\\u0000'") &&
-            check_true(input[8] === '\uDBFF\uDFFD', "input[8] === '\\uDBFF\\uDFFD'") &&
-            check_true(input[9] === 0.2, "input[9] === 0.2") &&
-            check_true(1/input[10] === Infinity, "1/input[10] === Infinity") &&
-            check_true(1/input[11] === -Infinity, "1/input[11] === -Infinity") &&
-            check_true(input[12] !== input[11], "input[12] !== input[11]") &&
-            check_true(input[13] === Infinity, "input[13] === Infinity") &&
-            check_true(input[14] === -Infinity, "input[14] === -Infinity") &&
-            check_true(input[15] === 9007199254740992, "input[15] === 9007199254740992") &&
-            check_true(input[16] === -9007199254740992, "input[16] === -9007199254740992") &&
-            check_true(input[17] === 9007199254740994, "input[17] === 9007199254740994") &&
-            check_true(input[18] === -9007199254740994, "input[18] === -9007199254740994")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object primitives':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_true(input['undefined'] === undefined, "input['undefined'] === undefined") &&
-              check_true(input['null'] === null, "input['null'] === null") &&
-              check_true(input['true'] === true, "input['true'] === true") &&
-              check_true(input['false'] === false, "input['false'] === false") &&
-              check_true(input['empty'] === '', "input['empty'] === ''") &&
-              check_true(input['high surrogate'] === '\uD800', "input['high surrogate'] === '\uD800'") &&
-              check_true(input['low surrogate'] === '\uDC00', "input['low surrogate'] === '\uDC00'") &&
-              check_true(input['nul'] === '\u0000', "input['nul'] === '\u0000'") &&
-              check_true(input['astral'] === '\uDBFF\uDFFD', "input['astral'] === '\uDBFF\uDFFD'") &&
-              check_true(input['0.2'] === 0.2, "input['0.2'] === 0.2") &&
-              check_true(1/input['0'] === Infinity, "1/input['0'] === Infinity") &&
-              check_true(1/input['-0'] === -Infinity, "1/input['-0'] === -Infinity") &&
-              check_true(input['NaN'] !== input['NaN'], "input['NaN'] !== input['NaN']") &&
-              check_true(input['Infinity'] === Infinity, "input['Infinity'] === Infinity") &&
-              check_true(input['-Infinity'] === -Infinity, "input['-Infinity'] === -Infinity") &&
-              check_true(input['9007199254740992'] === 9007199254740992, "input['9007199254740992'] === 9007199254740992") &&
-              check_true(input['-9007199254740992'] === -9007199254740992, "input['-9007199254740992'] === -9007199254740992") &&
-              check_true(input['9007199254740994'] === 9007199254740994, "input['9007199254740994'] === 9007199254740994") &&
-              check_true(input['-9007199254740994'] === -9007199254740994, "input['9007199254740994'] === -9007199254740994")) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 19, 'i === 19')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'Boolean true':
-        if (check_true(input instanceof Boolean, "input instanceof Boolean") &&
-            check_true(String(input) === 'true', "String(input) === 'true'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Boolean false':
-        if (check_true(input instanceof Boolean, "input instanceof Boolean") &&
-            check_true(String(input) === 'false', "String(input) === 'false'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array Boolean objects':
-        (function() {
-          if (check_true(input instanceof Array, 'input instanceof Array') &&
-              check_true(input.length === 2, 'input.length === 2') &&
-              check_true(String(input[0]) === 'true', "String(input[0]) === 'true'") &&
-              check_true(String(input[1]) === 'false', "String(input[1]) === 'false'")) {
-            for (var i = 0; i < input.length; ++i) {
-              if (!check_true(input[i] instanceof Boolean, 'input['+i+'] instanceof Boolean'))
-                return;
-            }
-            port.postMessage(input);
-            close();
-          }
-        })();
-        break;
-      case 'Object Boolean objects':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_true(String(input['true']) === 'true', "String(input['true']) === 'true'") &&
-              check_true(String(input['false']) === 'false', "String(input['false']) === 'false'")) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-              if (!check_true(input[x] instanceof Boolean, 'input['+x+'] instanceof Boolean'))
-                return;
-            }
-            if (check_true(i === 2, 'i === 2')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'String empty string':
-        if (check_true(input instanceof String, "input instanceof String") &&
-            check_true(String(input) === '', "String(input) === ''")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'String lone high surrogate':
-        if (check_true(input instanceof String, "input instanceof String") &&
-            check_true(String(input) === '\uD800', "String(input) === '\\uD800'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'String lone low surrogate':
-        if (check_true(input instanceof String, "input instanceof String") &&
-            check_true(String(input) === '\uDC00', "String(input) === '\\uDC00'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'String NUL':
-        if (check_true(input instanceof String, "input instanceof String") &&
-            check_true(String(input) === '\u0000', "String(input) === '\\u0000'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'String astral character':
-        if (check_true(input instanceof String, "input instanceof String") &&
-            check_true(String(input) === '\uDBFF\uDFFD', "String(input) === '\\uDBFF\\uDFFD'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array String objects':
-        (function() {
-          if (check_true(input instanceof Array, 'input instanceof Array') &&
-              check_true(input.length === 5, 'input.length === 5') &&
-              check_true(String(input[0]) === '', "String(input[0]) === ''") &&
-              check_true(String(input[1]) === '\uD800', "String(input[1]) === '\\uD800'") &&
-              check_true(String(input[2]) === '\uDC00', "String(input[1]) === '\\uDC00'") &&
-              check_true(String(input[3]) === '\u0000', "String(input[2]) === '\\u0000'") &&
-              check_true(String(input[4]) === '\uDBFF\uDFFD', "String(input[3]) === '\\uDBFF\\uDFFD'")) {
-            for (var i = 0; i < input.length; ++i) {
-              if (!check_true(input[i] instanceof String, 'input['+i+'] instanceof String'))
-                return;
-            }
-            port.postMessage(input);
-            close();
-          }
-        })();
-        break;
-      case 'Object String objects':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_true(String(input['empty']) === '', "String(input['empty']) === ''") &&
-              check_true(String(input['high surrogate']) === '\uD800', "String(input['high surrogate']) === '\\uD800'") &&
-              check_true(String(input['low surrogate']) === '\uDC00', "String(input['low surrogate']) === '\\uDC00'") &&
-              check_true(String(input['nul']) === '\u0000', "String(input['nul']) === '\\u0000'") &&
-              check_true(String(input['astral']) === '\uDBFF\uDFFD', "String(input['astral']) === '\\uDBFF\\uDFFD'")) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-              if (!check_true(input[x] instanceof String, 'input['+x+'] instanceof Boolean'))
-                return;
-            }
-            if (check_true(i === 5, 'i === 5')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'Number 0.2':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(Number(input) === 0.2, "Number(input) === 0.2")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number 0':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(1/Number(input) === Infinity, "1/Number(input) === Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number -0':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(1/Number(input) === -Infinity, "1/Number(input) === -Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number NaN':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(Number(input) !== Number(input), "Number(input) !== Number(input)")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number Infinity':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(Number(input) === Infinity, "Number(input) === Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number -Infinity':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(Number(input) === -Infinity, "Number(input) === -Infinity")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number 9007199254740992':
-        if (check_true(input instanceof Number) &&
-            check_true(Number(input) === 9007199254740992, "Number(input) === 9007199254740992")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number -9007199254740992':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(Number(input) === -9007199254740992, "Number(input) === -9007199254740992")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number 9007199254740994':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(Number(input) === 9007199254740994, "Number(input) === 9007199254740994")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Number -9007199254740994':
-        if (check_true(input instanceof Number, "input instanceof Number") &&
-            check_true(Number(input) === -9007199254740994, "Number(input) === -9007199254740994")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array Number objects':
-        (function() {
-          if (check_true(input instanceof Array, 'input instanceof Array') &&
-              check_true(input.length === 10, 'input.length === 10') &&
-              check_true(Number(input[0]) === 0.2, "Number(input[0]) === 0.2") &&
-              check_true(1/Number(input[1]) === Infinity, "1/Number(input[1]) === Infinity") &&
-              check_true(1/Number(input[2]) === -Infinity, "1/Number(input[2]) === -Infinity") &&
-              check_true(Number(input[3]) !== Number(input[3]), "Number(input[3]) !== Number(input[3])") &&
-              check_true(Number(input[4]) === Infinity, "Number(input[4]) === Infinity") &&
-              check_true(Number(input[5]) === -Infinity, "Number(input[5]) === -Infinity") &&
-              check_true(Number(input[6]) === 9007199254740992, "Number(input[6]) === 9007199254740992") &&
-              check_true(Number(input[7]) === -9007199254740992, "Number(input[7]) === -9007199254740992") &&
-              check_true(Number(input[8]) === 9007199254740994, "Number(input[8]) === 9007199254740994") &&
-              check_true(Number(input[9]) === -9007199254740994, "Number(input[9]) === -9007199254740994")) {
-            for (var i = 0; i < input.length; ++i) {
-              if (!check_true(input[i] instanceof Number, 'input['+i+'] instanceof Number'))
-                return;
-            }
-            port.postMessage(input);
-            close();
-          }
-        })();
-        break;
-      case 'Object Number objects':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_true(Number(input['0.2']) === 0.2, "Number(input['0.2']) === 0.2") &&
-              check_true(1/Number(input['0']) === Infinity, "1/Number(input['0']) === Infinity") &&
-              check_true(1/Number(input['-0']) === -Infinity, "1/Number(input['-0']) === -Infinity") &&
-              check_true(Number(input['NaN']) !== Number(input['NaN']), "Number(input['NaN']) !== Number(input['NaN'])") &&
-              check_true(Number(input['Infinity']) === Infinity, "Number(input['Infinity']) === Infinity") &&
-              check_true(Number(input['-Infinity']) === -Infinity, "Number(input['-Infinity']) === -Infinity") &&
-              check_true(Number(input['9007199254740992']) === 9007199254740992, "Number(input['9007199254740992']) === 9007199254740992") &&
-              check_true(Number(input['-9007199254740992']) === -9007199254740992, "Number(input['-9007199254740992']) === -9007199254740992") &&
-              check_true(Number(input['9007199254740994']) === 9007199254740994, "Number(input['9007199254740994']) === 9007199254740994") &&
-              check_true(Number(input['-9007199254740994']) === -9007199254740994, "Number(input['-9007199254740994']) === -9007199254740994")) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-              if (!check_true(input[x] instanceof Number, 'input['+x+'] instanceof Number'))
-                return;
-            }
-            if (check_true(i === 10, 'i === 10')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'Date 0':
-        if (check_true(input instanceof Date, "input instanceof Date") &&
-            check_true(1/Number(input) === 1/Number(new Date(0)), "1/Number(input) === 1/Number(new Date(0))")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Date -0':
-        if (check_true(input instanceof Date, "input instanceof Date") &&
-            check_true(1/Number(input) === 1/Number(new Date(-0)), "1/Number(input) === 1/Number(new Date(-0))")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Date -8.64e15':
-        if (check_true(input instanceof Date, "input instanceof Date") &&
-            check_true(Number(input) === -8.64e15, "Number(input) === -8.64e15")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Date 8.64e15':
-        if (check_true(input instanceof Date, "input instanceof Date") &&
-            check_true(Number(input) === 8.64e15, "Number(input) === 8.64e15")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array Date objects':
-        (function() {
-          if (check_true(input instanceof Array, 'input instanceof Array') &&
-              check_true(input.length === 4, 'input.length === 4') &&
-              check_true(1/Number(input[0]) === 1/new Date(0), '1/Number(input[0]) === 1/new Date(0)') &&
-              check_true(1/Number(input[1]) === 1/new Date(-0), '1/Number(input[1]) === 1/new Date(-0)') &&
-              check_true(Number(input[2]) === -8.64e15, 'Number(input[2]) === -8.64e15') &&
-              check_true(Number(input[3]) === 8.64e15, 'Number(input[3]) === 8.64e15')) {
-            for (var i = 0; i < input.length; ++i) {
-              if (!check_true(input[i] instanceof Date, 'input['+i+'] instanceof Date'))
-                return;
-            }
-            port.postMessage(input);
-            close();
-          }
-        })();
-        break;
-      case 'Object Date objects':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_true(1/Number(input['0']) === 1/new Date(0), "1/Number(input['0']) === 1/new Date(0)") &&
-              check_true(1/Number(input['-0']) === 1/new Date(-0), "1/Number(input[1]) === 1/new Date(-0)") &&
-              check_true(Number(input['-8.64e15']) === -8.64e15, "Number(input['-8.64e15']) === -8.64e15") &&
-              check_true(Number(input['8.64e15']) === 8.64e15, "Number(input['8.64e15']) === 8.64e15")) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-              if (!check_true(input[x] instanceof Date, 'input['+x+'] instanceof Date'))
-                return;
-            }
-            port.postMessage(input);
-            close();
-          }
-        })();
-        break;
-      case 'RegExp flags and lastIndex':
-      case 'RegExp empty':
-      case 'RegExp slash':
-      case 'RegExp new line':
-        if (check_RegExp(msg, input)) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array RegExp object, RegExp flags and lastIndex':
-      case 'Array RegExp object, RegExp empty':
-      case 'Array RegExp object, RegExp slash':
-      case 'Array RegExp object, RegExp new line':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1') &&
-            check_RegExp(msg.substr('Array RegExp object, '.length), input[0])) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object RegExp object, RegExp flags and lastIndex':
-      case 'Object RegExp object, RegExp empty':
-      case 'Object RegExp object, RegExp slash':
-      case 'Object RegExp object, RegExp new line':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_RegExp(msg.substr('Object RegExp object, '.length), input['x'])) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 1, 'i === 1')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'Blob basic':
-      case 'Blob unpaired high surrogate (invalid utf-8)':
-      case 'Blob unpaired low surrogate (invalid utf-8)':
-      case 'Blob paired surrogates (invalid utf-8)':
-      case 'Blob empty':
-      case 'Blob NUL':
-        check_Blob(msg, input, port);
-        // no postMessage or close here, check_Blob takes care of that
-        break;
-      case 'Array Blob object, Blob basic':
-      case 'Array Blob object, Blob unpaired high surrogate (invalid utf-8)':
-      case 'Array Blob object, Blob unpaired low surrogate (invalid utf-8)':
-      case 'Array Blob object, Blob paired surrogates (invalid utf-8)':
-      case 'Array Blob object, Blob empty':
-      case 'Array Blob object, Blob NUL':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1')) {
-          check_Blob(msg.substr('Array Blob object, '.length), input[0], port, false, input);
-          // no postMessage or close here, check_Blob takes care of that
-        }
-        break;
-      case 'Object Blob object, Blob basic':
-      case 'Object Blob object, Blob unpaired high surrogate (invalid utf-8)':
-      case 'Object Blob object, Blob unpaired low surrogate (invalid utf-8)':
-      case 'Object Blob object, Blob paired surrogates (invalid utf-8)':
-      case 'Object Blob object, Blob empty':
-      case 'Object Blob object, Blob NUL':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)')) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 1, 'i === 1')) {
-              check_Blob(msg.substr('Object Blob object, '.length), input['x'], port, false, input);
-              // no postMessage or close here, check_Blob takes care of that
-            }
-          }
-        })();
-        break;
-      case 'File basic':
-        check_Blob(msg, input, port, true);
-        // no postMessage or close here, check_Blob takes care of that
-        break;
-      case 'FileList empty':
-        if (check_FileList(msg, input)) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array FileList object, FileList empty':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1') &&
-            check_FileList(msg.substr('Array FileList object, '.length), input[0])) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object FileList object, FileList empty':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_FileList(msg.substr('Array FileList object, '.length), input['x'])) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 1, 'i === 1')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'ImageData 1x1 transparent black':
-        if (check_ImageData(input, {width:1, height:1, data:[0,0,0,0]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'ImageData 1x1 non-transparent non-black':
-        if (check_ImageData(input, {width:1, height:1, data:[100, 101, 102, 103]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array ImageData object, ImageData 1x1 transparent black':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1') &&
-            check_ImageData(input[0], {width:1, height:1, data:[0,0,0,0]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array ImageData object, ImageData 1x1 non-transparent non-black':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1') &&
-            check_ImageData(input[0], {width:1, height:1, data:[100, 101, 102, 103]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object ImageData object, ImageData 1x1 transparent black':
-        (function(){
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_ImageData(input['x'], {width:1, height:1, data:[0,0,0,0]})) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 1, 'i === 1')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'Object ImageData object, ImageData 1x1 non-transparent non-black':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_ImageData(input['x'], {width:1, height:1, data:[100, 101, 102, 103]})) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 1, 'i === 1')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'ImageBitmap 1x1 transparent black':
-        if (check_ImageBitmap(input, {width:1, height:1, data:[0, 0, 0, 0]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'ImageBitmap 1x1 non-transparent non-black':
-        if (check_ImageBitmap(input, {width:1, height:1, data:[100, 101, 102, 103]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array ImageBitmap object, ImageBitmap 1x1 transparent black':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1') &&
-            check_ImageBitmap(input[0], {width:1, height:1, data:[0, 0, 0, 0]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array ImageBitmap object, ImageBitmap 1x1 non-transparent non-black':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1') &&
-            check_ImageBitmap(input[0], {width:1, height:1, data:[100, 101, 102, 103]})) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object ImageBitmap object, ImageBitmap 1x1 transparent black':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_ImageBitmap(input['x'], {width:1, height:1, data:[0, 0, 0, 0]})) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 1, 'i === 1')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'Object ImageBitmap object, ImageBitmap 1x1 non-transparent non-black':
-        (function() {
-          if (check_true(input instanceof Object, 'input instanceof Object') &&
-              check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-              check_ImageBitmap(input['x'], {width:1, height:1, data:[100, 101, 102, 103]})) {
-            var i = 0;
-            for (var x in input) {
-              i++;
-            }
-            if (check_true(i === 1, 'i === 1')) {
-              port.postMessage(input);
-              close();
-            }
-          }
-        })();
-        break;
-      case 'Array sparse':
-        (function() {
-          if (check_true(input instanceof Array, 'input instanceof Array') &&
-              check_true(input.length === 10, 'input.length === 10')) {
-            for (var x in input) {
-              check_true(false, 'unexpected enumerable property '+x);
-              return;
-            }
-            port.postMessage(input);
-            close();
-          }
-        })();
-        break;
-      case 'Array with non-index property':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 0, 'input.length === 0') &&
-            check_true(input.foo === 'bar', "input.foo === 'bar'")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object with index property and length':
-        if (check_true(input instanceof Object, 'input instanceof Object') &&
-            check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-            check_true(input[0] === 'foo', "input[0] === 'foo'") &&
-            check_true(input.length === 1, 'input.length === 1')) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array with circular reference':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 1, 'input.length === 1') &&
-            check_true(input[0] === input, "input[0] === input")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object with circular reference':
-        if (check_true(input instanceof Object, 'input instanceof Object') &&
-            check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-            check_true(input['x'] === input, "input['x'] === input")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Array with identical property values':
-        if (check_true(input instanceof Array, 'input instanceof Array') &&
-            check_true(input.length === 2, 'input.length === 2') &&
-            check_true(input[0] === input[1], "input[0] === input[1]")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object with identical property values':
-        if (check_true(input instanceof Object, 'input instanceof Object') &&
-            check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-            check_true(input['x'] === input['y'], "input['x'] === input['y']")) {
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object with property on prototype':
-      case 'Object with non-enumerable property':
-        if (check_true(input instanceof Object, 'input instanceof Object') &&
-            check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-            check_true(!('foo' in input), "!('foo' in input)")) {
-          input = {};
-          Object.defineProperty(input, 'foo', {value:'bar', enumerable:false, writable:true, configurable:true});
-          port.postMessage(input);
-          close();
-        }
-        break;
-      case 'Object with non-writable property':
-        if (check_true(input instanceof Object, 'input instanceof Object') &&
-            check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-            check_true(input.foo === 'bar', "input.foo === bar")) {
-          input.foo += ' baz';
-          if (check_true(input.foo === 'bar baz', "input.foo === 'bar baz'")) {
-            input = {};
-            Object.defineProperty(input, 'foo', {value:'bar', enumerable:true, writable:false, configurable:true});
-            port.postMessage(input);
-            close();
-          }
-        }
-        break;
-      case 'Object with non-configurable property':
-        if (check_true(input instanceof Object, 'input instanceof Object') &&
-            check_true(!(input instanceof Array), '!(input instanceof Array)') &&
-            check_true(input.foo === 'bar', "input.foo === bar")) {
-          delete input.foo;
-          if (check_true(!('foo' in input), "!('foo' in input)")) {
-            input = {};
-            Object.defineProperty(input, 'foo', {value:'bar', enumerable:true, writable:true, configurable:false});
-            port.postMessage(input);
-            close();
-          }
-        }
-        break;
-
-      default:
-        port.postMessage('FAIL: unknown test');
-        close();
-    }
-    if (log.length > 0) {
-      port.postMessage('FAIL '+log);
-      close();
-    }
-  } catch (ex) {
-    port.postMessage('FAIL '+ex);
-    close();
-  }
-}
diff --git a/workers/support/WorkerSendingPerformanceNow.js b/workers/support/WorkerSendingPerformanceNow.js
new file mode 100644
index 0000000..ac12190
--- /dev/null
+++ b/workers/support/WorkerSendingPerformanceNow.js
@@ -0,0 +1,22 @@
+function calcResponse() {
+  const response = [
+    typeof(workerStart),
+    typeof(performance),
+    typeof(performance.now),
+    performance.now()
+  ];
+  return response;
+}
+
+self.onmessage = function(event) {
+  postMessage(calcResponse());
+  self.close();
+}
+
+self.addEventListener("connect", function(event) {
+  const port = event.ports[0];
+  port.onmessage = function(event) {
+    port.postMessage(calcResponse());
+    port.close();
+  };
+});
diff --git a/workers/support/iframe_sw_dataUrl.html b/workers/support/iframe_sw_dataUrl.html
new file mode 100644
index 0000000..0fb0ec2
--- /dev/null
+++ b/workers/support/iframe_sw_dataUrl.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Iframe for Shared Worker: Data URL cross-origin checks</title>
+<body>
+<script>
+
+let worker = new SharedWorker('data:text/javascript,let conns=0; onconnect = e => { e.ports[0].postMessage(++conns); }');
+worker.port.onmessage = e => {
+  parent.postMessage(e.data, '*');
+}
+
+</script>
+</body>
+</html>
diff --git a/workers/support/name-as-accidental-global.js b/workers/support/name-as-accidental-global.js
index 8783a38..f2c39ea 100644
--- a/workers/support/name-as-accidental-global.js
+++ b/workers/support/name-as-accidental-global.js
@@ -1,8 +1,9 @@
 "use strict";
+importScripts("/resources/testharness.js");
 
 var name = "something else";
 
 // This just makes the test name not "Untitled"
-test(() => { }, "Declaring name as an accidental global must not cause a harness error");
+test(() => { }, `Declaring name as an accidental global must not cause a harness error for ${self.constructor.name}`);
 
 done();
diff --git a/workers/worker-performance.worker.js b/workers/worker-performance.worker.js
index c4e562b..c913b2e 100644
--- a/workers/worker-performance.worker.js
+++ b/workers/worker-performance.worker.js
@@ -16,6 +16,7 @@
 }, "Can use performance.now in workers");
 
 test(function testPerformanceMark () {
+    while (performance.now() == start) { }
     performance.mark("mark1");
      // Stall the minimum amount of time to ensure the marks are separate
     var now = performance.now();
@@ -110,9 +111,9 @@
     assert_equals(typeof(performance.navigation), "undefined", "performance.navigation is undefined");
 }, "performance.navigation is not available in workers");
 
-test(function testPerformanceHasNoToJSON () {
-    assert_equals(typeof(performance.toJSON), "undefined", "performance.toJSON is undefined");
-}, "performance.toJSON is not available in workers");
+test(function testPerformanceHasToJSON () {
+    assert_equals(typeof(performance.toJSON), "function", "performance.toJSON is a function");
+}, "performance.toJSON is available in workers");
 
 test(function testPerformanceNoNavigationEntries () {
     assert_equals(performance.getEntriesByType("navigation").length, 0, "getEntriesByType(\"navigation\") returns nothing");
diff --git a/worklets/OWNERS b/worklets/OWNERS
new file mode 100644
index 0000000..ddff133
--- /dev/null
+++ b/worklets/OWNERS
@@ -0,0 +1 @@
+@bfgeek
diff --git a/worklets/layout-worklet-credentials.https.html b/worklets/layout-worklet-credentials.https.html
new file mode 100644
index 0000000..9468d2d
--- /dev/null
+++ b/worklets/layout-worklet-credentials.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/worklet-test-utils.js"></script>
+    <script src="resources/credentials-tests.js"></script>
+</head>
+<body>
+<script>
+    runCredentialsTests("layout");
+</script>
+</body>
+</html>
diff --git a/worklets/layout-worklet-csp.https.html b/worklets/layout-worklet-csp.https.html
new file mode 100644
index 0000000..854df8c
--- /dev/null
+++ b/worklets/layout-worklet-csp.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/worklet-test-utils.js"></script>
+    <script src="resources/csp-tests.js"></script>
+</head>
+<body>
+<script>
+    runContentSecurityPolicyTests("layout");
+</script>
+</body>
+</html>
diff --git a/worklets/layout-worklet-import.https.html b/worklets/layout-worklet-import.https.html
new file mode 100644
index 0000000..a2f57c2
--- /dev/null
+++ b/worklets/layout-worklet-import.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/worklet-test-utils.js"></script>
+    <script src="resources/import-tests.js"></script>
+</head>
+<body>
+<script>
+    runImportTests("layout");
+</script>
+</body>
+</html>
diff --git a/worklets/layout-worklet-referrer.https.html b/worklets/layout-worklet-referrer.https.html
new file mode 100644
index 0000000..cb383a9
--- /dev/null
+++ b/worklets/layout-worklet-referrer.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/worklet-test-utils.js"></script>
+    <script src="resources/referrer-tests.js"></script>
+</head>
+<body>
+<script>
+    runReferrerTests("layout");
+</script>
+</body>
+</html>
diff --git a/worklets/layout-worklet-service-worker-interception.https.html b/worklets/layout-worklet-service-worker-interception.https.html
new file mode 100644
index 0000000..146dff9
--- /dev/null
+++ b/worklets/layout-worklet-service-worker-interception.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/worklet-test-utils.js"></script>
+    <script src="resources/service-worker-interception-tests.js"></script>
+    <script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+</head>
+<body>
+<script>
+    runServiceWorkerInterceptionTests("layout");
+</script>
+</body>
+</html>
diff --git a/worklets/resources/credentials-tests.js b/worklets/resources/credentials-tests.js
index 46d2311..ec0b701 100644
--- a/worklets/resources/credentials-tests.js
+++ b/worklets/resources/credentials-tests.js
@@ -1,106 +1,130 @@
+function createCookieValue(settings) {
+  return settings.credentials + '-' + settings.origin;
+}
+
+function createSetCookieURL(settings) {
+  const params = new URLSearchParams;
+  params.append('name', 'cookieName');
+  params.append('value', createCookieValue(settings));
+  if (settings.origin == 'same') {
+    return get_host_info().HTTPS_ORIGIN +
+           '/worklets/resources/set-cookie.py?' + params;
+  }
+  if (settings.origin == 'remote') {
+    return get_host_info().HTTPS_REMOTE_ORIGIN +
+           '/worklets/resources/set-cookie.py?' + params;
+  }
+  assert_unreached('settings.origin has an invalid value.');
+}
+
+function createScriptURL(settings) {
+  const params = new URLSearchParams;
+  if (settings.expectCredentialsSent)
+    params.append('value', createCookieValue(settings));
+  if (settings.origin == 'same') {
+    return get_host_info().HTTPS_ORIGIN +
+           '/worklets/resources/credentials.py?' + params;
+  }
+  if (settings.origin == 'remote') {
+    return get_host_info().HTTPS_REMOTE_ORIGIN +
+           '/worklets/resources/credentials.py?' + params;
+  }
+  assert_unreached('settings.origin has an invalid value.');
+}
+
+function createWorkletOptions(settings) {
+  if (settings.credentials == '')
+    return {};
+  return { credentials: settings.credentials };
+}
+
+// Run a credentials test with the given settings.
+//
+// Example:
+// settings = {
+//   workletType: 'paint',
+//   credentials: 'include',
+//   origin: 'same',  // 'same' or 'remote'
+//   expectCredentialsSent: true
+// };
+function runCredentialsTest(settings) {
+  const worklet = get_worklet(settings.workletType);
+  const setCookieURL = createSetCookieURL(settings);
+  const scriptURL = createScriptURL(settings);
+  const options = createWorkletOptions(settings);
+
+  // { credentials: 'include' } is necessary for configuring document's cookies
+  // with the Set-Cookie: header of the response.
+  return fetch(setCookieURL, { mode: 'cors', credentials: 'include' })
+      .then(response => worklet.addModule(scriptURL, options));
+}
+
 // Runs a series of tests related to credentials on a worklet.
 //
 // Usage:
 // runCredentialsTests("paint");
 function runCredentialsTests(worklet_type) {
-  const worklet = get_worklet(worklet_type);
-
   promise_test(() => {
-      document.cookie = 'cookieName=default';
-      const kScriptURL = 'resources/credentials.py?mode=default';
-      return worklet.addModule(kScriptURL).then(undefined_arg => {
-        assert_equals(undefined_arg, undefined);
-      });
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: '',
+                                origin: 'same',
+                                expectCredentialsSent: true });
   }, 'Importing a same-origin script with the default WorkletOptions should ' +
-     'omit the credentials');
+     'send the credentials');
 
   promise_test(() => {
-      const kSetCookieURL =
-          get_host_info().HTTPS_REMOTE_ORIGIN +
-          '/worklets/resources/set-cookie.py?name=cookieName';
-      const kScriptURL = get_host_info().HTTPS_REMOTE_ORIGIN +
-                         '/worklets/resources/credentials.py?mode=default';
-      const kOptions = { credentials: 'same-origin' };
-
-      // Set a cookie in the remote origin and then start a worklet.
-      return fetch(kSetCookieURL, { mode: 'cors' })
-        .then(() => worklet.addModule(kScriptURL, kOptions))
-        .then(undefined_arg => assert_equals(undefined_arg, undefined));
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: '',
+                                origin: 'remote',
+                                expectCredentialsSent: false });
   }, 'Importing a remote-origin script with the default WorkletOptions ' +
-     'should not include the credentials');
+     'should not send the credentials');
 
   promise_test(() => {
-      document.cookie = 'cookieName=omit';
-      const kScriptURL = 'resources/credentials.py?mode=omit';
-      const kOptions = { credentials: 'omit' };
-      return worklet.addModule(kScriptURL, kOptions).then(undefined_arg => {
-        assert_equals(undefined_arg, undefined);
-      });
-  }, 'Importing a same-origin script with credentials=omit should omit the ' +
-     'credentials');
-
-  promise_test(() => {
-      const kSetCookieURL =
-          get_host_info().HTTPS_REMOTE_ORIGIN +
-          '/worklets/resources/set-cookie.py?name=cookieName';
-      const kScriptURL = get_host_info().HTTPS_REMOTE_ORIGIN +
-                         '/worklets/resources/credentials.py?mode=omit';
-      const kOptions = { credentials: 'omit' };
-
-      // Set a cookie in the remote origin and then start a worklet.
-      return fetch(kSetCookieURL, { mode: 'cors' })
-        .then(() => worklet.addModule(kScriptURL, kOptions))
-        .then(undefined_arg => assert_equals(undefined_arg, undefined));
-  }, 'Importing a remote-origin script with credentials=omit should omit the ' +
-     'credentials');
-
-  promise_test(() => {
-      document.cookie = 'cookieName=same-origin';
-      const kScriptURL = 'resources/credentials.py?mode=same-origin';
-      const kOptions = { credentials: 'same-origin' };
-      return worklet.addModule(kScriptURL, kOptions).then(undefined_arg => {
-        assert_equals(undefined_arg, undefined);
-      });
-  }, 'Importing a same-origin script with credentials=same-origin should ' +
-     'include the credentials');
-
-  promise_test(() => {
-      const kSetCookieURL =
-          get_host_info().HTTPS_REMOTE_ORIGIN +
-          '/worklets/resources/set-cookie.py?name=cookieName';
-      const kScriptURL = get_host_info().HTTPS_REMOTE_ORIGIN +
-                         '/worklets/resources/credentials.py?mode=same-origin';
-      const kOptions = { credentials: 'same-origin' };
-
-      // Set a cookie in the remote origin and then start a worklet.
-      return fetch(kSetCookieURL, { mode: 'cors' })
-        .then(() => worklet.addModule(kScriptURL, kOptions))
-        .then(undefined_arg => assert_equals(undefined_arg, undefined));
-  }, 'Importing a remote-origin script with credentials=same-origin should ' +
-     'not include the credentials');
-
-  promise_test(() => {
-      document.cookie = 'cookieName=include';
-      const kScriptURL = 'resources/credentials.py?mode=include';
-      const kOptions = { credentials: 'include' };
-      return worklet.addModule(kScriptURL, kOptions).then(undefined_arg => {
-          assert_equals(undefined_arg, undefined);
-      });
-  }, 'Importing a same-origin script with credentials=include should include ' +
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: 'omit',
+                                origin: 'same',
+                                expectCredentialsSent: false });
+  }, 'Importing a same-origin script with credentials=omit should not send ' +
      'the credentials');
 
   promise_test(() => {
-      const kSetCookieURL =
-          get_host_info().HTTPS_REMOTE_ORIGIN +
-          '/worklets/resources/set-cookie.py?name=cookieName';
-      const kScriptURL = get_host_info().HTTPS_REMOTE_ORIGIN +
-                         '/worklets/resources/credentials.py?mode=include';
-      const kOptions = { credentials: 'include' };
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: 'omit',
+                                origin: 'remote',
+                                expectCredentialsSent: false });
+  }, 'Importing a remote-origin script with credentials=omit should not send ' +
+     'the credentials');
 
-      // Set a cookie in the remote origin and then start a worklet.
-      return fetch(kSetCookieURL, { mode: 'cors' })
-        .then(() => worklet.addModule(kScriptURL, kOptions))
-        .then(undefined_arg => assert_equals(undefined_arg, undefined));
+  promise_test(() => {
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: 'same-origin',
+                                origin: 'same',
+                                expectCredentialsSent: true });
+  }, 'Importing a same-origin script with credentials=same-origin should ' +
+     'send the credentials');
+
+  promise_test(() => {
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: 'same-origin',
+                                origin: 'remote',
+                                expectCredentialsSent: false });
+  }, 'Importing a remote-origin script with credentials=same-origin should ' +
+     'not send the credentials');
+
+  promise_test(() => {
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: 'include',
+                                origin: 'same',
+                                expectCredentialsSent: true });
+  }, 'Importing a same-origin script with credentials=include should send ' +
+     'the credentials');
+
+  promise_test(() => {
+    return runCredentialsTest({ workletType: worklet_type,
+                                credentials: 'include',
+                                origin: 'remote',
+                                expectCredentialsSent: true });
   }, 'Importing a remote-origin script with credentials=include should ' +
-     'include the credentials');
+     'send the credentials');
 }
diff --git a/worklets/resources/credentials.py b/worklets/resources/credentials.py
index 98b9e73..8709896 100644
--- a/worklets/resources/credentials.py
+++ b/worklets/resources/credentials.py
@@ -1,32 +1,14 @@
 # Returns a valid response when a request has appropriate credentials.
 def main(request, response):
-    credentials_mode = request.GET.first("mode")
     cookie = request.cookies.first("cookieName", None)
-    source_origin = request.headers.get("origin", None);
-    is_cross_origin = request.GET.first("is_cross_origin", False)
+    expected_value = request.GET.first("value", None)
+    source_origin = request.headers.get("origin", None)
 
-    # The request with the default WorkletOptions should not include the cookie.
-    if credentials_mode is "default" and cookie is not None:
-        return (404)
+    response_headers = [("Content-Type", "text/javascript"),
+                        ("Access-Control-Allow-Origin", source_origin),
+                        ("Access-Control-Allow-Credentials", "true")]
 
-    # The request with "credentials=omit" should not include the cookie.
-    if credentials_mode is "omit" and cookie is not None:
-        return (404)
+    if cookie == expected_value:
+        return (200, response_headers, "")
 
-    if credentials_mode is "same-origin":
-        # The cross-origin request with "credentials=same-origin" should not
-        # include the cookie.
-        if is_cross_origin and cookie is not None:
-          return (404)
-        # The same-origin request with "credentials=same-origin" should include
-        # the cookie.
-        if not is_cross_origin and cookie is None:
-          return (404)
-
-    # The request with "credentials=include" should include the cookie.
-    if credentials_mode is "include" and cookie is None:
-        return (404)
-
-    return (200, [("Content-Type", "text/javascript"),
-                  ("Access-Control-Allow-Origin", source_origin),
-                  ("Access-Control-Allow-Credentials", "true")], "")
+    return (404, response_headers)
diff --git a/worklets/resources/csp-tests.js b/worklets/resources/csp-tests.js
index c56a128..0de1daa 100644
--- a/worklets/resources/csp-tests.js
+++ b/worklets/resources/csp-tests.js
@@ -22,16 +22,13 @@
 // Usage:
 // runContentSecurityPolicyTests("paint");
 function runContentSecurityPolicyTests(workletType) {
-  const worklet = get_worklet(workletType);
-
   promise_test(t => {
     const kWindowURL =
         'resources/addmodule-window.html?pipe=header(' +
         'Content-Security-Policy, script-src \'self\' \'unsafe-inline\')';
     const kScriptURL =
         get_host_info().HTTPS_REMOTE_ORIGIN +
-        '/worklets/resources/import-empty-worklet-script.js' +
-        '?pipe=header(Access-Control-Allow-Origin, *)';
+        '/worklets/resources/import-empty-worklet-script-with-cors-header.js';
     return openWindowAndExpectResult(
         kWindowURL, kScriptURL, workletType, 'REJECTED');
   }, 'Importing a remote-origin worklet script should be blocked by the ' +
@@ -51,9 +48,9 @@
     const kWindowURL =
         'resources/addmodule-window.html?pipe=header(' +
         'Content-Security-Policy, script-src * \'unsafe-inline\')';
-    const kScriptURL = get_host_info().HTTPS_REMOTE_ORIGIN +
-                       '/worklets/resources/empty-worklet-script.js' +
-                       '?pipe=header(Access-Control-Allow-Origin, *)';
+    const kScriptURL =
+        get_host_info().HTTPS_REMOTE_ORIGIN +
+        '/worklets/resources/empty-worklet-script-with-cors-header.js';
     return openWindowAndExpectResult(
         kWindowURL, kScriptURL, workletType, 'RESOLVED');
   }, 'Importing a remote-origin worklet script should not be blocked ' +
@@ -65,9 +62,9 @@
         'Content-Security-Policy, script-src * \'unsafe-inline\')';
     // A worklet on HTTPS_REMOTE_ORIGIN will import a child script on
     // HTTPS_REMOTE_ORIGIN.
-    const kScriptURL = get_host_info().HTTPS_REMOTE_ORIGIN +
-                       '/worklets/resources/import-empty-worklet-script.js' +
-                       '?pipe=header(Access-Control-Allow-Origin, *)';
+    const kScriptURL =
+        get_host_info().HTTPS_REMOTE_ORIGIN +
+        '/worklets/resources/import-empty-worklet-script-with-cors-header.js';
     return openWindowAndExpectResult(
         kWindowURL, kScriptURL, workletType, 'RESOLVED');
   }, 'Importing a remote-origin script from a remote-origin worklet script '+
@@ -77,12 +74,57 @@
     const kWindowURL =
         'resources/addmodule-window.html?pipe=header(' +
         'Content-Security-Policy, worker-src \'self\' \'unsafe-inline\')';
-    const kScriptURL = get_host_info().HTTPS_REMOTE_ORIGIN +
-                       '/worklets/resources/empty-worklet-script.js' +
-                       '?pipe=header(Access-Control-Allow-Origin, *)';
+    const kScriptURL =
+        get_host_info().HTTPS_REMOTE_ORIGIN +
+        '/worklets/resources/empty-worklet-script-with-cors-header.js';
     return openWindowAndExpectResult(
         kWindowURL, kScriptURL, workletType, 'RESOLVED');
   }, 'Importing a remote-origin worklet script should not be blocked by ' +
      'the worker-src directive because worklets obey the script-src ' +
      'directive.');
+
+  promise_test(t => {
+    const kWindowURL = 'resources/addmodule-window.html';
+    const kScriptURL =
+        get_host_info().HTTP_ORIGIN +
+        '/worklets/resources/empty-worklet-script.js';
+    return openWindowAndExpectResult(
+        kWindowURL, kScriptURL, workletType, 'REJECTED');
+  }, 'Importing an insecure-origin worklet script should be blocked because ' +
+     'of mixed contents.');
+
+  promise_test(t => {
+    const kWindowURL = 'resources/addmodule-window.html?pipe=header(' +
+                       'Content-Security-Policy, upgrade-insecure-requests)';
+    // This test relies on some unintuitive cleverness due to WPT's test setup:
+    // 'Upgrade-Insecure-Requests' does not upgrade the port number, so we use
+    // URLs in the form `http://[host]:[https-port]`. If the upgrade fails, the
+    // load will fail, as we don't serve HTTP over the secure port.
+    const kHost = get_host_info().ORIGINAL_HOST;
+    const kPort = get_host_info().HTTPS_PORT;
+    const kScriptURL =
+        `http://${kHost}:${kPort}/worklets/resources/empty-worklet-script.js`;
+    return openWindowAndExpectResult(
+        kWindowURL, kScriptURL, workletType, 'RESOLVED');
+  }, 'Importing an insecure-origin worklet script should not be blocked ' +
+     'because the upgrade-insecure-requests directive translates it as the ' +
+     'secure origin.');
+
+  promise_test(t => {
+    const kWindowURL = 'resources/addmodule-window.html';
+    const kScriptURL = 'import-insecure-origin-empty-worklet-script.sub.js';
+    return openWindowAndExpectResult(
+        kWindowURL, kScriptURL, workletType, 'REJECTED');
+  }, 'Importing an insecure-origin script from a secure-origin worklet ' +
+     'script should be blocked because of mixed contents.');
+
+  promise_test(t => {
+    const kWindowURL = 'resources/addmodule-window.html?pipe=header(' +
+                       'Content-Security-Policy, upgrade-insecure-requests)';
+    const kScriptURL = 'import-insecure-origin-empty-worklet-script.sub.js';
+    return openWindowAndExpectResult(
+        kWindowURL, kScriptURL, workletType, 'RESOLVED');
+  }, 'Importing an insecure-origin script from a secure-origin worklet ' +
+     'script should not be blocked because the upgrade-insecure-requests ' +
+     'directive translates it as the secure origin.');
 }
diff --git a/worklets/resources/empty-worklet-script-with-cors-header.js b/worklets/resources/empty-worklet-script-with-cors-header.js
new file mode 100644
index 0000000..9513071
--- /dev/null
+++ b/worklets/resources/empty-worklet-script-with-cors-header.js
@@ -0,0 +1 @@
+// This file is served with "Access-Control-Allow-Origin" header.
diff --git a/worklets/resources/empty-worklet-script-with-cors-header.js.headers b/worklets/resources/empty-worklet-script-with-cors-header.js.headers
new file mode 100644
index 0000000..cb762ef
--- /dev/null
+++ b/worklets/resources/empty-worklet-script-with-cors-header.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/worklets/resources/import-empty-worklet-script-with-cors-header.js b/worklets/resources/import-empty-worklet-script-with-cors-header.js
new file mode 100644
index 0000000..bd04a18
--- /dev/null
+++ b/worklets/resources/import-empty-worklet-script-with-cors-header.js
@@ -0,0 +1,3 @@
+// This file and descendant files are served with "Access-Control-Allow-Origin"
+// header.
+import './empty-worklet-script-with-cors-header.js';
diff --git a/worklets/resources/import-empty-worklet-script-with-cors-header.js.headers b/worklets/resources/import-empty-worklet-script-with-cors-header.js.headers
new file mode 100644
index 0000000..cb762ef
--- /dev/null
+++ b/worklets/resources/import-empty-worklet-script-with-cors-header.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/worklets/resources/import-empty-worklet-script.js b/worklets/resources/import-empty-worklet-script.js
deleted file mode 100644
index 339fbe9..0000000
--- a/worklets/resources/import-empty-worklet-script.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// This script can be imported as a remote-origin script, so the
-// Access-Control-Allow-Origin is specified here.
-import './empty-worklet-script.js?pipe=header(Access-Control-Allow-Origin, *)';
diff --git a/worklets/resources/import-insecure-origin-empty-worklet-script.sub.js b/worklets/resources/import-insecure-origin-empty-worklet-script.sub.js
new file mode 100644
index 0000000..cc7f5f3
--- /dev/null
+++ b/worklets/resources/import-insecure-origin-empty-worklet-script.sub.js
@@ -0,0 +1,5 @@
+// Some tests rely on some unintuitive cleverness due to WPT's test setup:
+// 'Upgrade-Insecure-Requests' does not upgrade the port number, so we use URLs
+// in the form `http://[host]:[https-port]`. If the upgrade fails, the load will
+// fail, as we don't serve HTTP over the secure port.
+import 'http://{{host}}:{{ports[https][0]}}/worklets/resources/empty-worklet-script.js';
diff --git a/worklets/resources/import-referrer-checker-worklet-script.sub.js b/worklets/resources/import-referrer-checker-worklet-script.sub.js
new file mode 100644
index 0000000..1dcb6f3
--- /dev/null
+++ b/worklets/resources/import-referrer-checker-worklet-script.sub.js
@@ -0,0 +1,2 @@
+// Forward GET parameters to the server.
+import './referrer-checker.py?referrer_policy={{GET[referrer_policy]}}&expected_referrer={{GET[expected_referrer]}}';
diff --git a/worklets/resources/import-referrer-checker-worklet-script.sub.js.headers b/worklets/resources/import-referrer-checker-worklet-script.sub.js.headers
new file mode 100644
index 0000000..cb762ef
--- /dev/null
+++ b/worklets/resources/import-referrer-checker-worklet-script.sub.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/worklets/resources/import-remote-origin-referrer-checker-worklet-script.sub.js b/worklets/resources/import-remote-origin-referrer-checker-worklet-script.sub.js
new file mode 100644
index 0000000..f301701
--- /dev/null
+++ b/worklets/resources/import-remote-origin-referrer-checker-worklet-script.sub.js
@@ -0,0 +1,2 @@
+// Forward GET parameters to the server.
+import 'https://{{domains[www1]}}:{{ports[https][0]}}/worklets/resources/referrer-checker.py?referrer_policy={{GET[referrer_policy]}}&expected_referrer={{GET[expected_referrer]}}';
diff --git a/worklets/resources/import-remote-origin-referrer-checker-worklet-script.sub.js.headers b/worklets/resources/import-remote-origin-referrer-checker-worklet-script.sub.js.headers
new file mode 100644
index 0000000..cb762ef
--- /dev/null
+++ b/worklets/resources/import-remote-origin-referrer-checker-worklet-script.sub.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/worklets/resources/referrer-checker.py b/worklets/resources/referrer-checker.py
new file mode 100644
index 0000000..184d547
--- /dev/null
+++ b/worklets/resources/referrer-checker.py
@@ -0,0 +1,26 @@
+# Returns a valid response when request's |referrer| matches
+# |expected_referrer|.
+def main(request, response):
+    referrer = request.headers.get("referer", "")
+    referrer_policy = request.GET.first("referrer_policy")
+    expected_referrer = request.GET.first("expected_referrer", "")
+
+    response_headers = [("Content-Type", "text/javascript"),
+                        ("Access-Control-Allow-Origin", "*")]
+
+    if referrer_policy == "no-referrer" or referrer_policy == "origin":
+        if referrer == expected_referrer:
+            return (200, response_headers, "")
+        return (404, response_headers)
+
+    if referrer_policy == "same-origin":
+        if referrer == expected_referrer:
+            return (200, response_headers, "")
+        # The expected referrer doesn't contain query params for simplification,
+        # so we check the referrer by startswith() here.
+        if (expected_referrer != "" and
+            referrer.startswith(expected_referrer + "?")):
+            return (200, response_headers, "")
+        return (404, response_headers)
+
+    return (404, response_headers)
diff --git a/worklets/resources/referrer-tests.js b/worklets/resources/referrer-tests.js
index 2fee016..01b8e2a 100644
--- a/worklets/resources/referrer-tests.js
+++ b/worklets/resources/referrer-tests.js
@@ -14,8 +14,9 @@
 // Example:
 // settings = {
 //   workletType: 'paint',
+//   fetchType: 'top-level' or 'descendant',
 //   referrerPolicy: 'no-referrer',
-//   isCrossOrigin: false
+//   scriptsOrigins: { topLevel: 'same', descendant: 'remote' }
 // };
 function runReferrerTest(settings) {
   const kWindowURL =
@@ -35,45 +36,136 @@
 function runReferrerTests(workletType) {
   const worklet = get_worklet(workletType);
 
+  // Tests for top-level script fetch -----------------------------------------
+
   promise_test(() => {
-      return runReferrerTest({ workletType: workletType,
-                               referrerPolicy: 'no-referrer',
-                               isCrossOrigin: false });
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'top-level',
+                             referrerPolicy: 'no-referrer',
+                             scriptOrigins: { topLevel: 'same' } });
   }, 'Importing a same-origin script from a page that has "no-referrer" ' +
      'referrer policy should not send referrer.');
 
   promise_test(() => {
-      return runReferrerTest({ workletType: workletType,
-                               referrerPolicy: 'no-referrer',
-                               isCrossOrigin: true });
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'top-level',
+                             referrerPolicy: 'no-referrer',
+                             scriptOrigins: { topLevel: 'remote' } });
   }, 'Importing a remote-origin script from a page that has "no-referrer" ' +
      'referrer policy should not send referrer.');
 
   promise_test(() => {
-      return runReferrerTest({ workletType: workletType,
-                               referrerPolicy: 'origin',
-                               isCrossOrigin: false });
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'top-level',
+                             referrerPolicy: 'origin',
+                             scriptOrigins: { topLevel: 'same' } });
   }, 'Importing a same-origin script from a page that has "origin" ' +
      'referrer policy should send only an origin as referrer.');
 
   promise_test(() => {
-      return runReferrerTest({ workletType: workletType,
-                               referrerPolicy: 'origin',
-                               isCrossOrigin: true });
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'top-level',
+                             referrerPolicy: 'origin',
+                             scriptOrigins: { topLevel: 'remote' } });
   }, 'Importing a remote-origin script from a page that has "origin" ' +
      'referrer policy should send only an origin as referrer.');
 
   promise_test(() => {
-      return runReferrerTest({ workletType: workletType,
-                               referrerPolicy: 'same-origin',
-                               isCrossOrigin: false });
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'top-level',
+                             referrerPolicy: 'same-origin',
+                             scriptOrigins: { topLevel: 'same' } });
   }, 'Importing a same-origin script from a page that has "same-origin" ' +
      'referrer policy should send referrer.');
 
   promise_test(() => {
-      return runReferrerTest({ workletType: workletType,
-                               referrerPolicy: 'same-origin',
-                               isCrossOrigin: true });
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'top-level',
+                             referrerPolicy: 'same-origin',
+                             scriptOrigins: { topLevel: 'remote' } });
   }, 'Importing a remote-origin script from a page that has "same-origin" ' +
      'referrer policy should not send referrer.');
+
+  // Tests for descendant script fetch -----------------------------------------
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'no-referrer',
+                             scriptOrigins: { topLevel: 'same',
+                                              descendant: 'same' } });
+  }, 'Importing a same-origin script from a same-origin worklet script that ' +
+     'has "no-referrer" referrer policy should not send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'no-referrer',
+                             scriptOrigins: { topLevel: 'same',
+                                              descendant: 'remote' } });
+  }, 'Importing a remote-origin script from a same-origin worklet script ' +
+     'that has "no-referrer" referrer policy should not send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'no-referrer',
+                             scriptOrigins: { topLevel: 'remote',
+                                              descendant: 'remote' } });
+  }, 'Importing a remote-origin script from a remote-origin worklet script ' +
+     'that has "no-referrer" referrer policy should not send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'origin',
+                             scriptOrigins: { topLevel: 'same',
+                                              descendant: 'same' } });
+  }, 'Importing a same-origin script from a same-origin worklet script that ' +
+     'has "origin" referrer policy should send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'origin',
+                             scriptOrigins: { topLevel: 'same',
+                                              descendant: 'remote' } });
+  }, 'Importing a remote-origin script from a same-origin worklet script ' +
+     'that has "origin" referrer policy should send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'origin',
+                             scriptOrigins: { topLevel: 'remote',
+                                              descendant: 'remote' } });
+  }, 'Importing a remote-origin script from a remote-origin worklet script ' +
+     'that has "origin" referrer policy should send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'same-origin',
+                             scriptOrigins: { topLevel: 'same',
+                                              descendant: 'same' } });
+  }, 'Importing a same-origin script from a same-origin worklet script that ' +
+     'has "same-origin" referrer policy should send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'same-origin',
+                             scriptOrigins: { topLevel: 'same',
+                                              descendant: 'remote' } });
+  }, 'Importing a remote-origin script from a same-origin worklet script ' +
+     'that has "same-origin" referrer policy should not send referrer.');
+
+  promise_test(() => {
+    return runReferrerTest({ workletType: workletType,
+                             fetchType: 'descendant',
+                             referrerPolicy: 'same-origin',
+                             scriptOrigins: { topLevel: 'remote',
+                                              descendant: 'remote' } });
+  }, 'Importing a remote-origin script from a remote-origin worklet script ' +
+     'that has "same-origin" referrer policy should not send referrer.');
 }
diff --git a/worklets/resources/referrer-window.html b/worklets/resources/referrer-window.html
index 773bb3b..4817f04 100644
--- a/worklets/resources/referrer-window.html
+++ b/worklets/resources/referrer-window.html
@@ -9,24 +9,89 @@
 </head>
 <body>
 <script>
-function createScriptURL(isCrossOrigin, params) {
-  if (isCrossOrigin) {
-    return get_host_info().HTTPS_REMOTE_ORIGIN +
-           '/worklets/resources/referrer.py?' + params;
+function createScriptURLForTopLevel(scriptOrigin) {
+  if (scriptOrigin === 'same')
+    return new URL('referrer-checker.py', location.href);
+  if (scriptOrigin === 'remote') {
+    return new URL(get_host_info().HTTPS_REMOTE_ORIGIN +
+                   '/worklets/resources/referrer-checker.py');
   }
-  return 'referrer.py?' + params;
+  assert_unreached('scriptOrigin should be \'same\' or \'remote\'');
+}
+
+function createScriptURLForDecendant(scriptOrigins) {
+  if (scriptOrigins.topLevel === 'same' &&
+      scriptOrigins.descendant === 'same') {
+    return new URL('import-referrer-checker-worklet-script.sub.js',
+                   location.href);
+  }
+  if (scriptOrigins.topLevel === 'same' &&
+      scriptOrigins.descendant === 'remote') {
+    return new URL(
+        'import-remote-origin-referrer-checker-worklet-script.sub.js',
+        location.href);
+  }
+  if (scriptOrigins.topLevel === 'remote' &&
+      scriptOrigins.descendant === 'remote') {
+    return new URL(
+        get_host_info().HTTPS_REMOTE_ORIGIN +
+        '/worklets/resources/import-referrer-checker-worklet-script.sub.js');
+  }
+  assert_unreached('scriptOrigins have an invalid origin combination.');
+}
+
+function isDestinationCrossOrigin(fetchType, scriptOrigins) {
+  if (fetchType === 'top-level')
+    return scriptOrigins.topLevel === 'remote';
+  if (fetchType === 'descendant')
+    return scriptOrigins.descendant === 'remote';
+  assert_unreached('fetchType has an invalid value.');
+}
+
+function createExpectedReferrer(
+    importerURL, fetchType, referrerPolicy, scriptOrigins) {
+  if (referrerPolicy === 'no-referrer')
+    return "";
+  if (referrerPolicy === 'same-origin') {
+    if (isDestinationCrossOrigin(fetchType, scriptOrigins))
+      return "";
+    // Delete query params to make it easier to match with an actual referrer in
+    // the referrer-checker.py.
+    const expectedReferrer = new URL(importerURL);
+    for (var key of expectedReferrer.searchParams.keys())
+      expectedReferrer.searchParams.delete(key);
+    return expectedReferrer;
+  }
+  if (referrerPolicy === 'origin')
+    return (new URL(importerURL)).origin + '/';
+  assert_unreached('referrerPolicy has an invalid value.');
 }
 
 window.onmessage = e => {
-  const isCrossOrigin = e.data.isCrossOrigin;
+  const workletType = e.data.workletType;
+  const fetchType = e.data.fetchType;
+  const referrerPolicy = e.data.referrerPolicy;
+  const scriptOrigins = e.data.scriptOrigins;
+
+  let scriptURL;
+  let expectedReferrer;
+  if (fetchType === 'top-level') {
+    scriptURL = createScriptURLForTopLevel(scriptOrigins.topLevel);
+    expectedReferrer = createExpectedReferrer(
+        location.href, fetchType, referrerPolicy, scriptOrigins);
+  } else if (fetchType === 'descendant') {
+    scriptURL = createScriptURLForDecendant(scriptOrigins);
+    expectedReferrer = createExpectedReferrer(
+        scriptURL, fetchType, referrerPolicy, scriptOrigins);
+  } else {
+    assert_unreached('fetchType should be \'top-level\' or \'descendant\'');
+  }
 
   const params = new URLSearchParams;
-  params.append('referrer_policy', e.data.referrerPolicy)
-  params.append('source_origin', get_host_info().HTTPS_ORIGIN);
-  params.append('is_cross_origin', isCrossOrigin ? 'true' : 'false')
+  params.append('referrer_policy', referrerPolicy);
+  params.append('expected_referrer', expectedReferrer);
 
-  const scriptURL = createScriptURL(isCrossOrigin, params);
-  get_worklet(e.data.workletType).addModule(scriptURL)
+  get_worklet(workletType).addModule(scriptURL + '?' + params)
       .then(() => window.opener.postMessage('RESOLVED', '*'))
       .catch(e => window.opener.postMessage(e.message, '*'));
 };
diff --git a/worklets/resources/referrer.py b/worklets/resources/referrer.py
deleted file mode 100644
index 475845c..0000000
--- a/worklets/resources/referrer.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Returns a valid response when request's |referrer| matches |referrer_policy|.
-def main(request, response):
-    referrer = request.headers.get("referer", None)
-    referrer_policy = request.GET.first("referrer_policy")
-    source_origin = request.GET.first("source_origin")
-    is_cross_origin = request.GET.first("is_cross_origin")
-
-    response_headers = [("Content-Type", "text/javascript"),
-                        ("Access-Control-Allow-Origin", source_origin)];
-
-    # When the referrer policy is "no-referrer", the referrer header shouldn't
-    # be sent.
-    if referrer_policy == "no-referrer" and not referrer:
-        return (200, response_headers, "")
-
-    # When the referrer policy is "origin", the referrer header should contain
-    # only the origin. Note that |referrer| contains a trailing slash, while
-    # |source_origin| doesn't.
-    if referrer_policy == "origin" and referrer == source_origin + "/":
-        return (200, response_headers, "")
-
-    # When the referrer policy is "same-origin", the referrer header should be
-    # sent only for a same-origin request.
-    if referrer_policy == "same-origin":
-        if is_cross_origin == "true" and not referrer:
-            return (200, response_headers, "")
-        if is_cross_origin == "false" and referrer:
-            return (200, response_headers, "")
-
-    return (404)
diff --git a/worklets/resources/service-worker-interception-tests.js b/worklets/resources/service-worker-interception-tests.js
index 310b26d..16d2987 100644
--- a/worklets/resources/service-worker-interception-tests.js
+++ b/worklets/resources/service-worker-interception-tests.js
@@ -120,6 +120,4 @@
             })
           .then(msg_event => assert_equals(msg_event.data, 'RESOLVED'));
     }, 'Static import should be intercepted by a service worker.');
-
-    // TODO(nhiroki): Add tests for dynamic import.
 }
diff --git a/worklets/resources/set-cookie.py b/worklets/resources/set-cookie.py
index 28f947a..6da37be 100644
--- a/worklets/resources/set-cookie.py
+++ b/worklets/resources/set-cookie.py
@@ -1,6 +1,9 @@
 def main(request, response):
     name = request.GET.first("name")
-    source_origin = request.headers.get("origin", None);
-    response.headers.set("Set-Cookie", name + "=value")
-    response.headers.set("Access-Control-Allow-Origin", source_origin)
-    response.headers.set("Access-Control-Allow-Credentials", "true")
+    value = request.GET.first("value")
+    source_origin = request.headers.get("origin", None)
+
+    response_headers = [("Set-Cookie", name + "=" + value),
+                        ("Access-Control-Allow-Origin", source_origin),
+                        ("Access-Control-Allow-Credentials", "true")]
+    return (200, response_headers, "")
diff --git a/worklets/resources/worklet-test-utils.js b/worklets/resources/worklet-test-utils.js
index 4912dad..f7f28b4 100644
--- a/worklets/resources/worklet-test-utils.js
+++ b/worklets/resources/worklet-test-utils.js
@@ -1,8 +1,10 @@
 // Returns a reference to a worklet object corresponding to a given type.
 function get_worklet(type) {
-  if (type == 'paint')
-    return CSS.paintWorklet;
   if (type == 'animation')
     return window.animationWorklet;
+  if (type == 'layout')
+    return CSS.layoutWorklet;
+  if (type == 'paint')
+    return CSS.paintWorklet;
   return undefined;
 }
diff --git a/x-frame-options/OWNERS b/x-frame-options/OWNERS
new file mode 100644
index 0000000..e9cd727
--- /dev/null
+++ b/x-frame-options/OWNERS
@@ -0,0 +1,2 @@
+@annevk
+@mikewest
diff --git a/x-frame-options/README.md b/x-frame-options/README.md
new file mode 100644
index 0000000..7b35f0f
--- /dev/null
+++ b/x-frame-options/README.md
@@ -0,0 +1,2 @@
+This directory contains tests for
+[HTTP Header Field X-Frame-Options](https://tools.ietf.org/html/rfc7034).
diff --git a/x-frame-options/deny.sub.html b/x-frame-options/deny.sub.html
index 8ef5831..626f204 100644
--- a/x-frame-options/deny.sub.html
+++ b/x-frame-options/deny.sub.html
@@ -11,7 +11,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
@@ -25,7 +25,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
diff --git a/x-frame-options/multiple.sub.html b/x-frame-options/multiple.sub.html
index 6cf3d46..f7a28e4 100644
--- a/x-frame-options/multiple.sub.html
+++ b/x-frame-options/multiple.sub.html
@@ -24,7 +24,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
@@ -38,7 +38,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
@@ -52,7 +52,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
@@ -66,7 +66,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
@@ -80,7 +80,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
diff --git a/x-frame-options/redirect.sub.html b/x-frame-options/redirect.sub.html
new file mode 100644
index 0000000..0bc708b
--- /dev/null
+++ b/x-frame-options/redirect.sub.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./support/helper.js"></script>
+<body>
+<script>
+  async_test(t => {
+    var i = document.createElement('iframe');
+    i.src = "./support/redirect.py?value=DENY&url=/x-frame-options/support/xfo.py%3Fvalue%3DALLOWALL";
+
+    wait_for_message_from(i, t)
+      .then(t.step_func_done(e => {
+        assert_equals(e.data, "Loaded");
+        i.remove();
+      }));
+
+    document.body.appendChild(i);
+  }, "XFO on redirect responses is ignored.");
+</script>
diff --git a/x-frame-options/sameorigin.sub.html b/x-frame-options/sameorigin.sub.html
index 9687426..9efbc32 100644
--- a/x-frame-options/sameorigin.sub.html
+++ b/x-frame-options/sameorigin.sub.html
@@ -37,7 +37,7 @@
     assert_no_message_from(i, t);
 
     i.onload = t.step_func_done(_ => {
-      assert_throws("SecurityError", function () { return i.contentDocument; });
+      assert_equals(i.contentDocument, null);
       i.remove();
     });
 
diff --git a/x-frame-options/support/redirect.py b/x-frame-options/support/redirect.py
new file mode 100644
index 0000000..0addf20
--- /dev/null
+++ b/x-frame-options/support/redirect.py
@@ -0,0 +1,4 @@
+def main(request, response):
+    response.status = 302
+    response.headers.set("X-Frame-Options", request.GET.first("value"))
+    response.headers.set("Location", request.GET.first("url"))
diff --git a/XMLHttpRequest/FormData-append.html b/xhr/FormData-append.html
similarity index 100%
rename from XMLHttpRequest/FormData-append.html
rename to xhr/FormData-append.html
diff --git a/XMLHttpRequest/OWNERS b/xhr/OWNERS
similarity index 100%
rename from XMLHttpRequest/OWNERS
rename to xhr/OWNERS
diff --git a/XMLHttpRequest/README.md b/xhr/README.md
similarity index 100%
rename from XMLHttpRequest/README.md
rename to xhr/README.md
diff --git a/XMLHttpRequest/XMLHttpRequest-withCredentials.any.js b/xhr/XMLHttpRequest-withCredentials.any.js
similarity index 100%
rename from XMLHttpRequest/XMLHttpRequest-withCredentials.any.js
rename to xhr/XMLHttpRequest-withCredentials.any.js
diff --git a/XMLHttpRequest/abort-after-receive.htm b/xhr/abort-after-receive.htm
similarity index 100%
rename from XMLHttpRequest/abort-after-receive.htm
rename to xhr/abort-after-receive.htm
diff --git a/XMLHttpRequest/abort-after-send.htm b/xhr/abort-after-send.htm
similarity index 100%
rename from XMLHttpRequest/abort-after-send.htm
rename to xhr/abort-after-send.htm
diff --git a/xhr/abort-after-stop.htm b/xhr/abort-after-stop.htm
new file mode 100644
index 0000000..7c5060f
--- /dev/null
+++ b/xhr/abort-after-stop.htm
@@ -0,0 +1,30 @@
+<!doctype html>
+<html>
+  <head>
+    <title>XMLHttpRequest: abort event should fire when stop() method is used</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[3] following::dt[3]/following::dd[1]/p"/>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      var test = async_test();
+      window.onload = test.step_func(function() {
+        var client = new XMLHttpRequest();
+        var abortFired = false;
+        client.onabort = test.step_func(function (e) {
+          assert_equals(e.type, 'abort');
+          abortFired = true;
+        });
+        client.open("GET", "resources/delay.py?ms=3000", true);
+        client.send(null);
+        test.step_timeout(() => {
+          assert_equals(abortFired, true);
+          test.done();
+        }, 200);
+        window.stop();
+      });
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/abort-after-timeout.htm b/xhr/abort-after-timeout.htm
similarity index 100%
rename from XMLHttpRequest/abort-after-timeout.htm
rename to xhr/abort-after-timeout.htm
diff --git a/XMLHttpRequest/abort-during-done.htm b/xhr/abort-during-done.htm
similarity index 100%
rename from XMLHttpRequest/abort-during-done.htm
rename to xhr/abort-during-done.htm
diff --git a/XMLHttpRequest/abort-during-headers-received.htm b/xhr/abort-during-headers-received.htm
similarity index 100%
rename from XMLHttpRequest/abort-during-headers-received.htm
rename to xhr/abort-during-headers-received.htm
diff --git a/XMLHttpRequest/abort-during-loading.htm b/xhr/abort-during-loading.htm
similarity index 100%
rename from XMLHttpRequest/abort-during-loading.htm
rename to xhr/abort-during-loading.htm
diff --git a/xhr/abort-during-open.any.js b/xhr/abort-during-open.any.js
new file mode 100644
index 0000000..42a1bce
--- /dev/null
+++ b/xhr/abort-during-open.any.js
@@ -0,0 +1,18 @@
+var test = async_test("XMLHttpRequest: abort() during OPEN");
+test.step(function() {
+  var client = new XMLHttpRequest()
+  client.open("GET", "...")
+  client.onreadystatechange = function() {
+    test.step(function() {
+      assert_unreached()
+    })
+  }
+  assert_equals(client.readyState, 1, "before abort()")
+  assert_equals(client.status, 0)
+  assert_equals(client.statusText, "")
+  client.abort()
+  assert_equals(client.readyState, 1, "after abort()")
+  assert_equals(client.status, 0)
+  assert_equals(client.statusText, "")
+})
+test.done()
diff --git a/XMLHttpRequest/abort-during-unsent.htm b/xhr/abort-during-unsent.htm
similarity index 100%
rename from XMLHttpRequest/abort-during-unsent.htm
rename to xhr/abort-during-unsent.htm
diff --git a/XMLHttpRequest/abort-during-upload.htm b/xhr/abort-during-upload.htm
similarity index 100%
rename from XMLHttpRequest/abort-during-upload.htm
rename to xhr/abort-during-upload.htm
diff --git a/XMLHttpRequest/abort-event-abort.htm b/xhr/abort-event-abort.htm
similarity index 100%
rename from XMLHttpRequest/abort-event-abort.htm
rename to xhr/abort-event-abort.htm
diff --git a/XMLHttpRequest/abort-event-listeners.htm b/xhr/abort-event-listeners.htm
similarity index 100%
rename from XMLHttpRequest/abort-event-listeners.htm
rename to xhr/abort-event-listeners.htm
diff --git a/XMLHttpRequest/abort-event-loadend.htm b/xhr/abort-event-loadend.htm
similarity index 100%
rename from XMLHttpRequest/abort-event-loadend.htm
rename to xhr/abort-event-loadend.htm
diff --git a/XMLHttpRequest/abort-event-order.htm b/xhr/abort-event-order.htm
similarity index 100%
rename from XMLHttpRequest/abort-event-order.htm
rename to xhr/abort-event-order.htm
diff --git a/XMLHttpRequest/abort-upload-event-abort.htm b/xhr/abort-upload-event-abort.htm
similarity index 100%
rename from XMLHttpRequest/abort-upload-event-abort.htm
rename to xhr/abort-upload-event-abort.htm
diff --git a/XMLHttpRequest/abort-upload-event-loadend.htm b/xhr/abort-upload-event-loadend.htm
similarity index 100%
rename from XMLHttpRequest/abort-upload-event-loadend.htm
rename to xhr/abort-upload-event-loadend.htm
diff --git a/xhr/access-control-and-redirects-async-same-origin.htm b/xhr/access-control-and-redirects-async-same-origin.htm
new file mode 100644
index 0000000..14e999c
--- /dev/null
+++ b/xhr/access-control-and-redirects-async-same-origin.htm
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that asynchronous XMLHttpRequests handle redirects according to the CORS standard.</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script>
+    function runTest(test, path, credentials, expectSuccess) {
+      const xhr = new XMLHttpRequest();
+      xhr.withCredentials = credentials;
+      xhr.open("GET", "resources/redirect.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN + path, true);
+
+      xhr.onload = test.step_func_done(function() {
+        assert_true(expectSuccess);
+        assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
+      });
+      xhr.onerror = test.step_func_done(function() {
+        assert_false(expectSuccess);
+        assert_equals(xhr.status, 0);
+      });
+      xhr.send(null);
+    }
+
+    const withoutCredentials = false;
+    const withCredentials = true;
+    const succeeds = true;
+    const fails = false;
+
+    // Test simple same origin requests that receive cross origin redirects.
+
+    // The redirect response passes the access check.
+    async_test(t => {
+      runTest(t, "/xhr/resources/access-control-basic-allow-star.py",
+          withoutCredentials, succeeds)
+    }, "Request without credentials is redirected to a cross-origin response with Access-Control-Allow-Origin=* (with star)");
+
+    // The redirect response fails the access check because credentials were sent.
+    async_test(t => {
+      runTest(t, "/xhr/resources/access-control-basic-allow-star.py",
+          withCredentials, fails)
+    }, "Request with credentials is redirected to a cross-origin response with Access-Control-Allow-Origin=* (with star)");
+
+    // The redirect response passes the access check.
+    async_test(t => {
+      runTest(t, "/xhr/resources/access-control-basic-allow.py",
+          withoutCredentials, succeeds)
+    }, "Request without credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin");
+
+    // The redirect response passes the access check.
+    async_test(t => {
+      runTest(t, "/xhr/resources/access-control-basic-allow.py",
+          withCredentials, succeeds)
+    }, "Request with credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin");
+
+    // forbidding credentials. The redirect response passes the access check.
+    async_test(t => {
+      runTest(t, "/xhr/resources/access-control-basic-allow-no-credentials.py",
+          withoutCredentials, succeeds)
+    }, "Request without credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin (no credentials)");
+
+    // forbidding credentials. The redirect response fails the access check.
+    async_test(t => {
+      runTest(t, "/xhr/resources/access-control-basic-allow-no-credentials.py",
+          withCredentials, fails)
+    }, "Request with credentials is redirected to a cross-origin response with a specific Access-Control-Allow-Origin (no credentials)");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-and-redirects-async.htm b/xhr/access-control-and-redirects-async.htm
new file mode 100644
index 0000000..a77846f
--- /dev/null
+++ b/xhr/access-control-and-redirects-async.htm
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that asynchronous XMLHttpRequests handle redirects according to the CORS standard.</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script>
+    function runTest(test, destination, parameters, customHeader, local, expectSuccess) {
+      const xhr = new XMLHttpRequest();
+      const url = (local ? get_host_info().HTTP_ORIGIN : get_host_info().HTTP_REMOTE_ORIGIN) +
+        "/xhr/resources/redirect-cors.py?location=" + destination + "&" +  parameters;
+
+      xhr.open("GET", url, true);
+
+      if (customHeader)
+        xhr.setRequestHeader("x-test", "test");
+
+      xhr.onload = test.step_func_done(function() {
+        assert_true(expectSuccess);
+        assert_true(xhr.responseText.startsWith("PASS"));
+      });
+      xhr.onerror = test.step_func_done(function() {
+        assert_false(expectSuccess);
+        assert_equals(xhr.status, 0);
+      });
+      xhr.send();
+    }
+
+    const withCustomHeader = true;
+    const withoutCustomHeader = false;
+    const local = true;
+    const remote = false;
+    const succeeds = true;
+    const fails = false;
+
+    // Test simple cross origin requests that receive redirects.
+
+    // The redirect response fails the access check because the redirect lacks a CORS header.
+    async_test(t => {
+      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow-star.py", "",
+          withoutCustomHeader, remote, fails)
+    }, "Request is redirected without CORS headers to a response with Access-Control-Allow-Origin=*");
+
+    // The redirect response passes the access check.
+    async_test(t => {
+      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow-star.py", "allow_origin=true",
+          withoutCustomHeader, remote, succeeds)
+    }, "Request is redirected to a response with Access-Control-Allow-Origin=*");
+
+    // The redirect response fails the access check because user info was sent.
+    async_test(t => {
+      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN.replace("http://", "http://username:password@") +
+          "/xhr/resources/access-control-basic-allow-star.py", "allow_origin=true",
+          withoutCustomHeader, remote, fails)
+    }, "Request with user info is redirected to a response with Access-Control-Allow-Origin=*");
+
+    // The redirect response fails the access check because the URL scheme is unsupported.
+    async_test(t => {
+      runTest(t, "foo://bar.cgi", "allow_origin=true", withoutCustomHeader, remote, fails)
+    }, "Request is redirect to a bad URL");
+
+    // The preflighted redirect response fails the access check because of preflighting.
+    async_test(t => {
+      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow-star.py",
+          "allow_origin=true&redirect_preflight=true", withCustomHeader, remote, fails)
+    }, "Preflighted request is redirected to a response with Access-Control-Allow-Origin=*");
+
+    // The preflighted redirect response fails the access check after successful preflighting.
+    async_test(t => {
+      runTest(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow-star.py",
+          "allow_origin=true&allow_header=x-test&redirect_preflight=true",
+          withCustomHeader, remote, fails)
+    }, "Preflighted request is redirected to a response with Access-Control-Allow-Origin=* and header allowed");
+
+    // The same-origin redirect response passes the access check.
+    async_test(t => {
+      runTest(t, get_host_info().HTTP_ORIGIN + "/xhr/resources/pass.txt",
+          "", withCustomHeader, local, succeeds)
+    }, "Request is redirected to a same-origin resource file");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-and-redirects.htm b/xhr/access-control-and-redirects.htm
new file mode 100644
index 0000000..a10bc1f
--- /dev/null
+++ b/xhr/access-control-and-redirects.htm
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that redirects between origins are allowed when access control is involved.</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script>
+    function runSync(test, url)
+    {
+      const xhr = new XMLHttpRequest();
+      xhr.open("GET", url, false);
+      xhr.send();
+      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
+      test.done();
+    }
+    function runAsync(test, url)
+    {
+      const xhr = new XMLHttpRequest();
+      xhr.open("GET", url, true);
+      xhr.onload = test.step_func_done(function() {
+        assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
+      });
+      xhr.onerror = test.unreached_func("Network error");
+      xhr.send();
+      test.done();
+    }
+    test(t => {
+      runSync(t, "resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow.py")
+    }, "Local sync redirect to remote origin");
+    async_test(t => {
+      runAsync(t, "resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow.py")
+    }, "Local async redirect to remote origin");
+    test(t => {
+      runSync(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/redirect-cors.py?location=" + get_host_info().HTTP_ORIGIN +
+          "/xhr/resources/access-control-basic-allow.py&allow_origin=true")
+    }, "Remote sync redirect to local origin");
+    async_test(t => {
+      runAsync(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/redirect-cors.py?location=" + get_host_info().HTTP_ORIGIN +
+          "/xhr/resources/access-control-basic-allow.py&allow_origin=true")
+    }, "Remote async redirect to local origin");
+    test(t => {
+      runSync(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow.py&allow_origin=true")
+    }, "Remote sync redirect to same remote origin");
+    async_test(t => {
+      runAsync(t, get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/redirect-cors.py?location=" + get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow.py&allow_origin=true")
+    }, "Remote async redirect to same remote origin");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-access-control-origin-header-data-url.htm b/xhr/access-control-basic-allow-access-control-origin-header-data-url.htm
new file mode 100644
index 0000000..0d66ad7
--- /dev/null
+++ b/xhr/access-control-basic-allow-access-control-origin-header-data-url.htm
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that cross-origin access is granted to null-origin embedded iframe</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+const url = get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-origin-header.py";
+async_test(function(test) {
+  window.addEventListener("message", test.step_func(function(evt) {
+    if (evt.data == "ready") {
+      document.getElementById("frame").contentWindow.postMessage(url, "*");
+    } else {
+      assert_equals(evt.data, "PASS: Cross-domain access allowed.\nHTTP_ORIGIN: null");
+      test.done();
+    }
+  }), false);
+}, "Access granted to null-origin iframe");
+    </script>
+    <iframe id="frame" src='data:text/html,
+    <script>
+(function() {
+  parent.postMessage("ready", "*");
+  window.addEventListener("message", function(evt) {
+    try {
+      const url = evt.data;
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", url, false);
+      xhr.send();
+
+      parent.postMessage(xhr.responseText, "*");
+    } catch(e) {
+      parent.postMessage(e.message, "*");
+    }
+  });
+})();
+    </script>'>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-access-control-origin-header.htm b/xhr/access-control-basic-allow-access-control-origin-header.htm
new file mode 100644
index 0000000..aedd207
--- /dev/null
+++ b/xhr/access-control-basic-allow-access-control-origin-header.htm
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test(function(test) {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-origin-header.py", false);
+      xhr.send();
+
+      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\n" +
+          "HTTP_ORIGIN: " + get_host_info().HTTP_ORIGIN);
+      test.done();
+    }, "Access control test with origin header");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-async.htm b/xhr/access-control-basic-allow-async.htm
new file mode 100644
index 0000000..142181e
--- /dev/null
+++ b/xhr/access-control-basic-allow-async.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Testing a basic asynchronous CORS XHR request</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test(function(test) {
+      const xhr = new XMLHttpRequest;
+
+      xhr.onreadystatechange = test.step_func(function() {
+        if (xhr.readyState == xhr.DONE) {
+          assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
+          test.done();
+        }
+      });
+
+      xhr.onerror = test.unreached_func("FAIL: Network error.");
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-allow.py", true);
+      xhr.send();
+    }, "Basic async cross-origin XHR request");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-non-cors-safelisted-method-async.htm b/xhr/access-control-basic-allow-non-cors-safelisted-method-async.htm
new file mode 100644
index 0000000..e468c82
--- /dev/null
+++ b/xhr/access-control-basic-allow-non-cors-safelisted-method-async.htm
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests cross-origin async request with non-CORS-safelisted method</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test((test) => {
+      const xhr = new XMLHttpRequest;
+
+      xhr.onload = test.step_func_done(() => {
+        assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
+      });
+
+      xhr.onerror = test.unreached_func("Unexpected error.");
+
+      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-put-allow.py");
+      xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
+      xhr.send("PASS: PUT data received");
+    }, "Allow async PUT request");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-non-cors-safelisted-method.htm b/xhr/access-control-basic-allow-non-cors-safelisted-method.htm
new file mode 100644
index 0000000..2612265
--- /dev/null
+++ b/xhr/access-control-basic-allow-non-cors-safelisted-method.htm
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests cross-origin request with non-CORS-safelisted method</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-put-allow.py", false);
+
+      xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
+
+      xhr.send("PASS: PUT data received");
+
+      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
+    }, "Allow PUT request");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-preflight-cache-invalidation-by-header.htm b/xhr/access-control-basic-allow-preflight-cache-invalidation-by-header.htm
new file mode 100644
index 0000000..306eb02
--- /dev/null
+++ b/xhr/access-control-basic-allow-preflight-cache-invalidation-by-header.htm
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Preflight cache should be invalidated in presence of custom header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const uuid = token();
+    const xhr = new XMLHttpRequest;
+
+    async_test(function(test) {
+      xhr.onerror = test.unreached_func("FAIL: Network error.");
+      xhr.onload = test.step_func(function() {
+        // Token reset.  We can start the test now.
+        assert_equals(xhr.responseText, "PASS");
+        firstRequest();
+      });
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/reset-token.py?token=" + uuid, true);
+      xhr.send();
+
+      function firstRequest() {
+        xhr.onload = test.step_func(function() {
+          assert_equals(xhr.responseText, "PASS: First PUT request.");
+          secondRequest();
+        });
+        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
+        xhr.send();
+      }
+
+      function secondRequest() {
+        xhr.onload = test.step_func(function() {
+          assert_equals(xhr.responseText, "PASS: Second OPTIONS request was sent.");
+          test.done();
+        });
+        // Send a header not included in the inital cache.
+        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
+        xhr.setRequestHeader("x-test", "headerValue");
+        xhr.send();
+      }
+    }, "Preflight cache should be invalidated in presence of custom header");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-preflight-cache-invalidation-by-method.htm b/xhr/access-control-basic-allow-preflight-cache-invalidation-by-method.htm
new file mode 100644
index 0000000..f8a99a2
--- /dev/null
+++ b/xhr/access-control-basic-allow-preflight-cache-invalidation-by-method.htm
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Preflight cache should be invalidated by changed method</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const uuid = token();
+    const xhr = new XMLHttpRequest;
+
+    async_test(function(test) {
+      xhr.onerror = test.unreached_func("FAIL: Network error.");
+      xhr.onload = test.step_func(function() {
+        // Token reset.  We can start the test now.
+        assert_equals(xhr.responseText, "PASS");
+        firstRequest();
+      });
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/reset-token.py?token=" + uuid, true);
+      xhr.send();
+
+      function firstRequest() {
+        xhr.onload = test.step_func(function() {
+          assert_equals(xhr.responseText, "PASS: First PUT request.");
+          secondRequest();
+        });
+        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
+        xhr.send();
+      }
+
+      function secondRequest() {
+        xhr.onload = test.step_func(function() {
+          assert_equals(xhr.responseText, "PASS: Second OPTIONS request was sent.");
+          test.done();
+        });
+        // Send a header not included in the inital cache.
+        xhr.open("XMETHOD", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache-invalidation.py?token=" + uuid, true);
+        xhr.send();
+      }
+    }, "Preflight cache should be invalidated by changed method");
+
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-preflight-cache-timeout.htm b/xhr/access-control-basic-allow-preflight-cache-timeout.htm
new file mode 100644
index 0000000..ccd961f
--- /dev/null
+++ b/xhr/access-control-basic-allow-preflight-cache-timeout.htm
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Preflight cache should be invalidated on timeout</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const uuid = token();
+    let xhr = new XMLHttpRequest;
+
+    async_test(function(test) {
+      xhr.onerror = test.unreached_func("FAIL: Network error.");
+      xhr.onload = test.step_func(function() {
+        // Token reset.  We can start the test now.
+        assert_equals(xhr.responseText, "PASS");
+        firstRequest();
+      });
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/reset-token.py?token=" + uuid, true);
+      xhr.send();
+
+      function firstRequest() {
+        xhr.onload = test.step_func(function() {
+          assert_equals(xhr.responseText, "PASS: First PUT request.");
+          step_timeout(secondRequest, 3000); // 3 seconds
+        });
+        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache-timeout.py?token=" + uuid, true);
+        xhr.send();
+      }
+
+      function secondRequest() {
+        xhr.onload = test.step_func(function() {
+          assert_equals(xhr.responseText, "PASS: Second OPTIONS request was sent.");
+          test.done();
+        });
+        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache-timeout.py?token=" + uuid, true);
+        xhr.send();
+      }
+    }, "Preflight cache should be invalidated on timeout");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-preflight-cache.htm b/xhr/access-control-basic-allow-preflight-cache.htm
new file mode 100644
index 0000000..b8deda4
--- /dev/null
+++ b/xhr/access-control-basic-allow-preflight-cache.htm
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Preflight cache should allow second request without preflight OPTIONS request</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const uuid = token();
+
+    async_test(function(test) {
+      const xhr = new XMLHttpRequest;
+      xhr.onerror = test.unreached_func("FAIL: Network error.");
+      xhr.onload = test.step_func(function() {
+        // Token reset.  We can start the test now.
+        assert_equals(xhr.responseText, "PASS");
+        firstRequest();
+      });
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/reset-token.py?token=" + uuid, true);
+      xhr.send();
+
+      function firstRequest() {
+        xhr.onload = test.step_func(function() {
+          assert_equals(xhr.responseText, "PASS: First PUT request.");
+          secondRequest();
+        });
+        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache.py?token=" + uuid, true);
+        xhr.send();
+      }
+
+      function secondRequest() {
+        xhr.onload = test.step_func_done(function() {
+          assert_equals(xhr.responseText, "PASS: Second PUT request. Preflight worked.");
+        });
+        xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-preflight-cache.py?token=" + uuid, true);
+        xhr.send();
+      }
+    }, "Preflight cache should allow second request");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow-star.htm b/xhr/access-control-basic-allow-star.htm
new file mode 100644
index 0000000..d05222f
--- /dev/null
+++ b/xhr/access-control-basic-allow-star.htm
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests "*" setting for Access-Control-Allow-Origin header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const xhr = new XMLHttpRequest;
+
+    test(function(test) {
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-allow-star.py", false);
+
+      xhr.send();
+
+      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
+    }, "Allow star");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-allow.htm b/xhr/access-control-basic-allow.htm
new file mode 100644
index 0000000..61c02ed
--- /dev/null
+++ b/xhr/access-control-basic-allow.htm
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests CORS with Access-Control-Allow-Origin header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-allow.py", false);
+
+      xhr.send();
+
+      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.");
+    }, "Allow basic");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-cors-safelisted-request-headers.htm b/xhr/access-control-basic-cors-safelisted-request-headers.htm
new file mode 100644
index 0000000..20b875b
--- /dev/null
+++ b/xhr/access-control-basic-cors-safelisted-request-headers.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that CORS-safelisted request headers are permitted in cross-origin request</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("POST", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-cors-safelisted-request-headers.py", false);
+
+      xhr.setRequestHeader("Accept", "*");
+      xhr.setRequestHeader("Accept-Language", "ru");
+      xhr.setRequestHeader("Content-Language", "ru");
+      xhr.setRequestHeader("Content-Type", "text/plain");
+      xhr.setRequestHeader("Save-Data", "on");
+
+      xhr.send();
+
+      assert_equals(xhr.responseText,
+          "Accept: *\n" +
+          "Accept-Language: ru\n" +
+          "Content-Language: ru\n" +
+          "Content-Type: text/plain\n");
+    }, "Request with CORS-safelisted headers");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-cors-safelisted-response-headers.htm b/xhr/access-control-basic-cors-safelisted-response-headers.htm
new file mode 100644
index 0000000..a5c470b
--- /dev/null
+++ b/xhr/access-control-basic-cors-safelisted-response-headers.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that CORS-safelisted response headers are permitted in cross-origin request</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-whitelist-response-headers.py", false);
+      xhr.send();
+
+      assert_not_equals(xhr.getResponseHeader("cache-control"), null);
+      assert_not_equals(xhr.getResponseHeader("content-language"), null);
+      assert_not_equals(xhr.getResponseHeader("content-type"), null);
+      assert_not_equals(xhr.getResponseHeader("expires"), null);
+      assert_not_equals(xhr.getResponseHeader("last-modified"), null);
+      assert_not_equals(xhr.getResponseHeader("pragma"), null);
+      assert_equals(xhr.getResponseHeader("x-webkit"), null);
+
+      assert_not_equals(xhr.getAllResponseHeaders().match("en"), null);
+      assert_equals(xhr.getAllResponseHeaders().match("foobar"), null);
+    }, "Response with CORS-safelisted headers");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-denied.htm b/xhr/access-control-basic-denied.htm
new file mode 100644
index 0000000..535762d
--- /dev/null
+++ b/xhr/access-control-basic-denied.htm
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests CORS denying resource without Access-Control-Allow-Origin header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const path = "/xhr/resources/access-control-basic-denied.py";
+
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_ORIGIN + path, false);
+      xhr.send();
+      assert_equals(xhr.status, 200);
+    }, "Same-origin request accepted");
+
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + path, false);
+      assert_throws("NetworkError", () => xhr.send());
+      assert_equals(xhr.status, 0);
+    }, "Cross-origin request denied");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-get-fail-non-simple.htm b/xhr/access-control-basic-get-fail-non-simple.htm
new file mode 100644
index 0000000..a4fe234
--- /dev/null
+++ b/xhr/access-control-basic-get-fail-non-simple.htm
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests CORS denying preflighted request to resource without CORS headers for OPTIONS</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-options-not-supported.py", false);
+
+      // Non-CORS-safelisted header
+      xhr.setRequestHeader("x-test", "foobar");
+
+      // This fails because the server-side script is not prepared for an OPTIONS request
+      assert_throws("NetworkError", () => xhr.send());
+      assert_equals(xhr.status, 0);
+    }, "Preflighted cross-origin request denied");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-non-cors-safelisted-content-type.htm b/xhr/access-control-basic-non-cors-safelisted-content-type.htm
new file mode 100644
index 0000000..062f8b0
--- /dev/null
+++ b/xhr/access-control-basic-non-cors-safelisted-content-type.htm
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests cross-origin request with non-CORS-safelisted content type</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(() => {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-put-allow.py", false);
+      xhr.setRequestHeader("Content-Type", "text/plain");
+      xhr.send("PASS: PUT data received");
+
+      assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
+
+      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-put-allow.py", false);
+      xhr.setRequestHeader("Content-Type", "application/xml");
+
+      assert_throws("NetworkError", () => xhr.send("FAIL: PUT data received"));
+      assert_equals(xhr.status, 0, "Cross-domain access was denied in 'send'.");
+    }, "Deny cross-origin request with non-CORS-safelisted content type");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-post-success-no-content-type.htm b/xhr/access-control-basic-post-success-no-content-type.htm
new file mode 100644
index 0000000..7e7a7d3
--- /dev/null
+++ b/xhr/access-control-basic-post-success-no-content-type.htm
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that POST requests with text content and no content-type set explicitly don't generate a preflight request.</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test(function(test) {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("POST", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-basic-options-not-supported.py");
+
+      xhr.onerror = test.unreached_func("Network error.");
+
+      xhr.onload = test.step_func_done(function() {
+        assert_equals(xhr.status, 200);
+      });
+
+      xhr.send("Test");
+    }, "POST request with text content and no Content-Type header");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-post-with-non-cors-safelisted-content-type.htm b/xhr/access-control-basic-post-with-non-cors-safelisted-content-type.htm
new file mode 100644
index 0000000..0a6df93
--- /dev/null
+++ b/xhr/access-control-basic-post-with-non-cors-safelisted-content-type.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Non-CORS-safelisted value in the Content-Type header results in a request preflight</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("POST", get_host_info().HTTP_ORIGIN +
+          "/xhr/resources/access-control-basic-options-not-supported.py", false);
+
+      xhr.setRequestHeader("Content-Type", "application/xml");
+
+      xhr.send();
+
+      assert_equals(xhr.status, 200, "Same-origin access doesn't issue preflight; not denied.");
+    }, "Same-origin request with non-safelisted content type succeeds");
+
+    test(function() {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("POST", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-options-not-supported.py", false);
+
+      xhr.setRequestHeader("Content-Type", "application/xml");
+
+      assert_throws("NetworkError", () => xhr.send());
+      assert_equals(xhr.status, 0, "Cross-domain access was denied in 'send'.");
+    }, "CORS request with non-safelisted content type sends preflight and fails");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-basic-preflight-denied.htm b/xhr/access-control-basic-preflight-denied.htm
new file mode 100644
index 0000000..6475186
--- /dev/null
+++ b/xhr/access-control-basic-preflight-denied.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests async XHR preflight denial due to lack of CORS headers</title>
+    <!--The original test addressed a more specific issue involving caching,
+        but that issue has since been resolved.
+        We maintain this test as a basic test of invalid preflight denial.
+        Please refer to the comment in the following link for more information:
+        https://chromium-review.googlesource.com/c/chromium/src/+/630338#message-0280542b95c9b0f82b121dc373320c04fcaece31
+    -->
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test((test) => {
+      const xhr = new XMLHttpRequest;
+      xhr.onerror = test.step_func_done(() => {
+        assert_equals(xhr.status, 0);
+      });
+
+      xhr.onload = test.unreached_func("Request succeeded unexpectedly");
+
+      xhr.open("FOO", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-basic-denied.py");
+      xhr.send();
+    });
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-async-header-denied.htm b/xhr/access-control-preflight-async-header-denied.htm
new file mode 100644
index 0000000..a00cc58
--- /dev/null
+++ b/xhr/access-control-preflight-async-header-denied.htm
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Async request denied at preflight because of non-CORS-safelisted header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const uuid = token();
+    const url = get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-preflight-denied.py?token=" + uuid;
+
+    async_test((test) => {
+      let xhr = new XMLHttpRequest;
+      xhr.open("GET", url + "&command=reset", false);
+      xhr.send();
+
+      xhr = new XMLHttpRequest;
+      xhr.open("GET", url + "&command=header", true);
+      xhr.setRequestHeader("x-test", "foo");
+
+      xhr.onload = test.unreached_func(
+          "Cross-domain access with custom header allowed without throwing exception");
+
+      xhr.onerror = test.step_func_done(() => {
+        xhr = new XMLHttpRequest;
+        xhr.open("GET", url + "&command=complete", false);
+        xhr.send();
+        assert_equals(xhr.responseText, "Request successfully blocked.");
+      });
+
+      xhr.send();
+    }, "Async request denied at preflight");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-async-method-denied.htm b/xhr/access-control-preflight-async-method-denied.htm
new file mode 100644
index 0000000..a0425a4
--- /dev/null
+++ b/xhr/access-control-preflight-async-method-denied.htm
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Async request denied at preflight because of non-CORS-safelisted method</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    const uuid = token();
+    const url = get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-preflight-denied.py?token=" + uuid;
+
+    async_test((test) => {
+      let xhr = new XMLHttpRequest;
+      xhr.open("GET", url + "&command=reset", false);
+      xhr.send();
+
+      xhr = new XMLHttpRequest;
+      xhr.open("DELETE", url + "&command=method", true);
+
+      xhr.onload = test.unreached_func(
+          "Cross-domain access with non-CORS-safelisted method allowed without throwing exception");
+
+      xhr.onerror = test.step_func_done(() => {
+        xhr = new XMLHttpRequest;
+        xhr.open("GET", url + "&command=complete", false);
+        xhr.send();
+        assert_equals(xhr.responseText, "Request successfully blocked.");
+      });
+
+      xhr.send();
+    }, "Async request denied at preflight");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-async-not-supported.htm b/xhr/access-control-preflight-async-not-supported.htm
new file mode 100644
index 0000000..a4dc06d
--- /dev/null
+++ b/xhr/access-control-preflight-async-not-supported.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Async PUT request denied at preflight</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+const uuid = token();
+const url = get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/access-control-preflight-denied.py?token=" + uuid;
+
+async_test((test) => {
+  let xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=reset", false);
+  xhr.send();
+
+  xhr = new XMLHttpRequest;
+  xhr.open("PUT", url, true);
+
+  xhr.onload = test.unreached_func("Cross-domain access allowed unexpectedly.");
+
+  xhr.onerror = test.step_func_done(() => {
+    xhr = new XMLHttpRequest;
+    xhr.open("GET", url + "&command=complete", false);
+    xhr.send();
+    assert_equals(xhr.responseText, "Request successfully blocked.");
+  });
+
+  xhr.send();
+});
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-credential-async.htm b/xhr/access-control-preflight-credential-async.htm
new file mode 100644
index 0000000..ad7117f
--- /dev/null
+++ b/xhr/access-control-preflight-credential-async.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests proper handling of cross-origin async request with credentials</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test((test) => {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-auth-basic.py?uid=fooUser",
+          true, "fooUser", "barPass");
+      xhr.withCredentials = true;
+
+      xhr.onerror = test.unreached_func("Unexpected error.");
+
+      xhr.onload = test.step_func_done(() => {
+        assert_equals(xhr.status, 401, "Request raises HTTP 401: Unauthorized error.");
+      });
+
+      xhr.send();
+    }, "CORS async request with URL credentials");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-credential-sync.htm b/xhr/access-control-preflight-credential-sync.htm
new file mode 100644
index 0000000..3844d02
--- /dev/null
+++ b/xhr/access-control-preflight-credential-sync.htm
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests proper handling of cross-origin sync request with credentials</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(() => {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-auth-basic.py?uid=fooUser", false, "fooUser", "barPass");
+
+      xhr.withCredentials = true;
+
+      xhr.send();
+
+      assert_equals(xhr.status, 401, "Request raises HTTP 401: Unauthorized error.");
+    }, "CORS sync request with URL credentials");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-headers-async.htm b/xhr/access-control-preflight-headers-async.htm
new file mode 100644
index 0000000..ffea72a
--- /dev/null
+++ b/xhr/access-control-preflight-headers-async.htm
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Test that async CORS requests with custom headers are sent with OPTIONS preflight</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+async_test((test) => {
+  let xhr = new XMLHttpRequest;
+  const uuid = token();
+
+  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/reset-token.py?token=" + uuid, false);
+  xhr.send();
+
+  xhr = new XMLHttpRequest;
+  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/no-custom-header-on-preflight.py?token=" + uuid);
+  xhr.setRequestHeader("x-test", "foobar");
+
+  xhr.onerror = test.unreached_func("Unexpected error");
+
+  xhr.onload = test.step_func_done(() => {
+    assert_equals(xhr.responseText, "PASS");
+  });
+
+  xhr.send();
+}, "Preflighted async request with custom header");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-headers-sync.htm b/xhr/access-control-preflight-headers-sync.htm
new file mode 100644
index 0000000..2ae9fe8
--- /dev/null
+++ b/xhr/access-control-preflight-headers-sync.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Test that sync CORS requests with custom headers are not sent with OPTIONS preflight</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    test(function() {
+      let xhr = new XMLHttpRequest;
+      const uuid = token();
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/reset-token.py?token=" + uuid, false);
+      xhr.send();
+
+      xhr = new XMLHttpRequest;
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/no-custom-header-on-preflight.py?token=" + uuid, false);
+      xhr.setRequestHeader("x-test", "foobar");
+      xhr.send();
+      assert_equals(xhr.responseText, "PASS");
+    }, "Preflighted sync request with custom header");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-request-header-lowercase.htm b/xhr/access-control-preflight-request-header-lowercase.htm
new file mode 100644
index 0000000..7dc4608
--- /dev/null
+++ b/xhr/access-control-preflight-request-header-lowercase.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Access-Control-Request-Headers values should be lowercase</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test(function(test) {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-preflight-request-header-lowercase.py");
+
+      xhr.setRequestHeader("X-Test", "foobar");
+
+      xhr.onerror = test.unreached_func("Error occurred.");
+
+      xhr.onload = test.step_func_done(function() {
+        assert_equals(xhr.status, 200);
+        assert_equals(xhr.responseText, "PASS");
+      });
+
+      xhr.send();
+    }, "Request with uppercase header set");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-request-header-sorted.htm b/xhr/access-control-preflight-request-header-sorted.htm
new file mode 100644
index 0000000..830e0fb
--- /dev/null
+++ b/xhr/access-control-preflight-request-header-sorted.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Tests that Access-Control-Request-Headers are sorted.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script>
+async_test((test) => {
+  const xhr = new XMLHttpRequest();
+  const url = get_host_info().HTTP_REMOTE_ORIGIN + '/xhr/resources/access-control-preflight-request-header-sorted.py';
+  xhr.open('GET', url);
+  xhr.setRequestHeader("X-Custom-Test", "foobar");
+  xhr.setRequestHeader("X-Custom-ua", "foobar");
+  xhr.setRequestHeader("X-Custom-V", "foobar");
+  xhr.setRequestHeader("X-Custom-s", "foobar");
+  xhr.setRequestHeader("X-Custom-U", "foobar");
+  xhr.onerror = test.unreached_func('xhr failure');
+  xhr.onload = test.step_func_done(() => {
+    assert_equals(xhr.responseText, 'PASS');
+  });
+  xhr.send();
+});
+</script>
+</body>
+</html>
diff --git a/xhr/access-control-preflight-request-headers-origin.htm b/xhr/access-control-preflight-request-headers-origin.htm
new file mode 100644
index 0000000..fc11abc
--- /dev/null
+++ b/xhr/access-control-preflight-request-headers-origin.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Test that 'Origin' is not included in Access-Control-Request-Headers in a preflight request</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+async_test((test) => {
+  const xhr = new XMLHttpRequest;
+  const url = get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/access-control-preflight-request-headers-origin.py";
+
+  xhr.open("GET", url);
+  xhr.setRequestHeader("x-pass", "PASS");
+
+  xhr.onerror = test.unreached_func("Unexpected error");
+
+  xhr.onload = test.step_func_done(() => {
+    assert_equals(xhr.responseText, "PASS");
+  });
+
+  xhr.send();
+}, "'Origin' should not be included in CORS Request-Headers");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-request-invalid-status-301.htm b/xhr/access-control-preflight-request-invalid-status-301.htm
new file mode 100644
index 0000000..62fc480
--- /dev/null
+++ b/xhr/access-control-preflight-request-invalid-status-301.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that preflight requests returning invalid 301 status code result in error.</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test((test) => {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-preflight-request-invalid-status.py?code=301");
+
+      xhr.setRequestHeader("x-pass", "pass");
+
+      xhr.onerror = test.step_func_done(function() {
+        assert_equals(xhr.status, 0);
+      });
+
+      xhr.onload = test.unreached_func("Invalid 301 response to preflight should result in error.");
+
+      xhr.send();
+    }, "Request with 301 preflight response");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-request-invalid-status-400.htm b/xhr/access-control-preflight-request-invalid-status-400.htm
new file mode 100644
index 0000000..9e76d9e
--- /dev/null
+++ b/xhr/access-control-preflight-request-invalid-status-400.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that preflight requests returning invalid 400 status code result in error.</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test((test) => {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-preflight-request-invalid-status.py?code=400");
+
+      xhr.setRequestHeader("x-pass", "pass");
+
+      xhr.onerror = test.step_func_done(function() {
+        assert_equals(xhr.status, 0);
+      });
+
+      xhr.onload = test.unreached_func("Invalid 400 response to preflight should result in error.");
+
+      xhr.send();
+    }, "Request with 400 preflight response");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-request-invalid-status-501.htm b/xhr/access-control-preflight-request-invalid-status-501.htm
new file mode 100644
index 0000000..f2ed85b
--- /dev/null
+++ b/xhr/access-control-preflight-request-invalid-status-501.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that preflight requests returning invalid 501 status code result in error.</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test((test) => {
+      const xhr = new XMLHttpRequest;
+
+      xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/access-control-preflight-request-invalid-status.py?code=501");
+
+      xhr.setRequestHeader("x-pass", "pass");
+
+      xhr.onerror = test.step_func_done(function() {
+        assert_equals(xhr.status, 0);
+      });
+
+      xhr.onload = test.unreached_func("Invalid 501 response to preflight should result in error.");
+
+      xhr.send();
+    }, "Request with 501 preflight response");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-request-must-not-contain-cookie.htm b/xhr/access-control-preflight-request-must-not-contain-cookie.htm
new file mode 100644
index 0000000..6dd8e6d
--- /dev/null
+++ b/xhr/access-control-preflight-request-must-not-contain-cookie.htm
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Preflight request must not contain any cookie header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+    async_test((test) => {
+      function setupCookie() {
+        const xhr = new XMLHttpRequest;
+        // Delete all preexisting cookies and set a cookie named "foo"
+        xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+            "/xhr/resources/access-control-cookie.py?cookie_name=foo");
+        xhr.withCredentials = true;
+        xhr.send();
+        xhr.onerror = test.unreached_func("Unexpected error.");
+        xhr.onload = test.step_func(() => {
+          assert_equals(xhr.status, 200);
+          sendPreflightedRequest();
+        });
+      }
+
+      function sendPreflightedRequest() {
+        const xhr = new XMLHttpRequest;
+        // Request to server-side file fails if cookie is included in preflight
+        xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+          "/xhr/resources/access-control-preflight-request-must-not-contain-cookie.py");
+        xhr.withCredentials = true;
+        xhr.setRequestHeader("X-Proprietary-Header", "foo");
+        xhr.onerror = test.unreached_func("Unexpected error.");
+        xhr.onload = test.step_func(() => {
+          assert_equals(xhr.status, 200);
+          assert_equals(xhr.responseText, "COOKIE");
+          cleanupCookies();
+        });
+        xhr.send();
+      }
+
+      function cleanupCookies() {
+        const xhr = new XMLHttpRequest;
+        // Delete all cookies
+        xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+            "/xhr/resources/access-control-cookie.py");
+        xhr.withCredentials = true;
+        xhr.send();
+        xhr.onerror = test.unreached_func("Unexpected error.");
+        xhr.onload = test.step_func_done(() => {});
+      }
+
+      setupCookie();
+    });
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-sync-header-denied.htm b/xhr/access-control-preflight-sync-header-denied.htm
new file mode 100644
index 0000000..7948c18
--- /dev/null
+++ b/xhr/access-control-preflight-sync-header-denied.htm
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Sync request denied at preflight because of non-CORS-safelisted header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+const uuid = token();
+const url = get_host_info().HTTP_REMOTE_ORIGIN +
+    "/xhr/resources/access-control-preflight-denied.py?token=" + uuid;
+
+test(() => {
+  let xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=reset", false);
+  xhr.send();
+
+  xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=header", false);
+  xhr.setRequestHeader("x-test", "foo");
+
+  assert_throws("NetworkError", () => xhr.send());
+
+  xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=complete", false);
+  xhr.send();
+  assert_equals(xhr.responseText, "Request successfully blocked.");
+}, "Sync request denied at preflight");
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-sync-method-denied.htm b/xhr/access-control-preflight-sync-method-denied.htm
new file mode 100644
index 0000000..a54c308
--- /dev/null
+++ b/xhr/access-control-preflight-sync-method-denied.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Sync request denied at preflight because of non-CORS-safelisted method</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+const uuid = token();
+const url = get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/access-control-preflight-denied.py?token=" + uuid;
+
+test(() => {
+  let xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=reset", false);
+  xhr.send();
+
+  xhr = new XMLHttpRequest;
+  xhr.open("DELETE", url + "&command=method", false);
+
+  assert_throws("NetworkError", () => xhr.send());
+
+  xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=complete", false);
+  xhr.send();
+  assert_equals(xhr.responseText, "Request successfully blocked.");
+});
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-preflight-sync-not-supported.htm b/xhr/access-control-preflight-sync-not-supported.htm
new file mode 100644
index 0000000..bb08296
--- /dev/null
+++ b/xhr/access-control-preflight-sync-not-supported.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Sync PUT request denied at preflight</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+const uuid = token();
+const url = get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/access-control-preflight-denied.py?token=" + uuid;
+
+test(() => {
+  let xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=reset", false);
+  xhr.send();
+
+  xhr = new XMLHttpRequest;
+  xhr.open("PUT", url, false);
+
+  assert_throws("NetworkError", () => xhr.send(""));
+
+  xhr = new XMLHttpRequest;
+  xhr.open("GET", url + "&command=complete", false);
+  xhr.send();
+  assert_equals(xhr.responseText, "Request successfully blocked.");
+});
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-recursive-failed-request.htm b/xhr/access-control-recursive-failed-request.htm
new file mode 100644
index 0000000..2c2bcef
--- /dev/null
+++ b/xhr/access-control-recursive-failed-request.htm
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Recursively repeated CORS requests with failed preflights should never result in unexpected behavior</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+let requestCount = 0;
+const requestMax = 10;
+
+async_test((test) => {
+  function preflightRequest() {
+    const xhr = new XMLHttpRequest;
+
+    xhr.onload = test.unreached_func("Request succeeded unexpectedly.");
+
+    xhr.onerror = test.step_func(() => {
+      assert_equals(xhr.status, 0);
+      if (++requestCount >= requestMax) {
+        test.done();
+        return;
+      }
+      preflightRequest();
+    });
+
+    xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+        "/xhr/resources/access-control-basic-denied.py");
+    xhr.send();
+  }
+
+  preflightRequest();
+});
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-response-with-body-sync.htm b/xhr/access-control-response-with-body-sync.htm
new file mode 100644
index 0000000..d4c90ae
--- /dev/null
+++ b/xhr/access-control-response-with-body-sync.htm
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests body from CORS preflight response and actual response with sync request</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+test(() => {
+  const xhr = new XMLHttpRequest;
+
+  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN+
+      "/xhr/resources/access-control-allow-with-body.py", false);
+  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+  xhr.send();
+
+  assert_equals(xhr.status, 200);
+  assert_equals(xhr.responseText, "PASS");
+});
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-response-with-body.htm b/xhr/access-control-response-with-body.htm
new file mode 100644
index 0000000..3ab0521
--- /dev/null
+++ b/xhr/access-control-response-with-body.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that XHR doesn't prepend the body from CORS preflight response to the actual response</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+async_test((test) => {
+  const xhr = new XMLHttpRequest;
+
+  xhr.onerror = test.unreached_func("Unexpected error.");
+
+  xhr.onload = test.step_func_done(() => {
+    assert_equals(xhr.status, 200);
+    assert_equals(xhr.responseText, "PASS");
+  });
+
+  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/access-control-allow-with-body.py");
+  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+  xhr.send();
+});
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-response-with-exposed-headers.htm b/xhr/access-control-response-with-exposed-headers.htm
new file mode 100644
index 0000000..c6f7bf5
--- /dev/null
+++ b/xhr/access-control-response-with-exposed-headers.htm
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Test CORS response with 'Access-Control-Expose-Headers' header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+async_test((test) => {
+  const xhr = new XMLHttpRequest;
+
+  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN +
+      "/xhr/resources/pass.txt?pipe=" +
+      "header(Cache-Control,no-cache)|" +
+      "header(Access-Control-Max-Age,0)|" +
+      "header(Access-Control-Allow-Origin,*)|" +
+      "header(X-foo,BAR)|" +
+      "header(x-test,TEST)|" +
+      "header(Access-Control-Expose-Headers,x-Foo)|",
+      "header(Content-Type,text/html)");
+
+  xhr.onerror = test.unreached_func("Unexpected error");
+
+  xhr.onload = test.step_func_done(() => {
+    assert_equals(xhr.status, 200);
+    assert_equals(xhr.getResponseHeader("X-FOO"), "BAR");
+    assert_equals(xhr.getResponseHeader("x-foo"), "BAR");
+    assert_equals(xhr.getResponseHeader("x-test"), null);
+    assert_equals(xhr.responseText, "PASS\n");
+  });
+
+  xhr.send();
+});
+    </script>
+  </body>
+</html>
diff --git a/xhr/access-control-sandboxed-iframe-allow-origin-null.htm b/xhr/access-control-sandboxed-iframe-allow-origin-null.htm
new file mode 100644
index 0000000..ae96668
--- /dev/null
+++ b/xhr/access-control-sandboxed-iframe-allow-origin-null.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that sandboxed iframe has CORS XHR access to a server that accepts null domain</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+async_test((test) => {
+  window.addEventListener("message", test.step_func((evt) => {
+    if (evt.data === "ready") {
+      document.getElementById("frame").contentWindow.postMessage(
+          get_host_info().HTTP_ORIGIN +
+          "/xhr/resources/pass.txt?pipe=" +
+          "header(Cache-Control,no-store)|" +
+          "header(Content-Type,text/plain)|" +
+          "header(Access-Control-Allow-Credentials,true)|" +
+          "header(Access-Control-Allow-External,true)|" +
+          "header(Access-Control-Allow-Origin,null)", "*");
+    } else {
+      assert_equals(evt.data.trim(), "PASS");
+      test.done();
+    }
+  }), false);
+});
+    </script>
+    <iframe id="frame" sandbox="allow-scripts" src="/xhr/resources/access-control-sandboxed-iframe.html">
+    </iframe>
+  </body>
+</html>
diff --git a/xhr/access-control-sandboxed-iframe-allow.htm b/xhr/access-control-sandboxed-iframe-allow.htm
new file mode 100644
index 0000000..a3dbed4
--- /dev/null
+++ b/xhr/access-control-sandboxed-iframe-allow.htm
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that sandboxed iframe has CORS XHR access to a server that accepts all domains</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+async_test((test) => {
+  window.addEventListener("message", test.step_func((evt) => {
+    if (evt.data === "ready") {
+      document.getElementById("frame").contentWindow.postMessage(
+          get_host_info().HTTP_ORIGIN +
+          "/xhr/resources/pass.txt?pipe=" +
+          "header(Cache-Control,no-store)|" +
+          "header(Content-Type,text/plain)|" +
+          "header(Access-Control-Allow-Credentials,true)|" +
+          "header(Access-Control-Allow-External,true)|" +
+          "header(Access-Control-Allow-Origin,*)", "*");
+    } else {
+      assert_equals(evt.data.trim(), "PASS");
+      test.done();
+    }
+  }), false);
+});
+    </script>
+    <iframe id="frame" sandbox="allow-scripts" src="/xhr/resources/access-control-sandboxed-iframe.html">
+    </iframe>
+  </body>
+</html>
diff --git a/xhr/access-control-sandboxed-iframe-denied-without-wildcard.htm b/xhr/access-control-sandboxed-iframe-denied-without-wildcard.htm
new file mode 100644
index 0000000..a703a92
--- /dev/null
+++ b/xhr/access-control-sandboxed-iframe-denied-without-wildcard.htm
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that sandboxed iframe does not have CORS XHR access to server with "Access-Control-Allow-Origin" set to the original origin</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+const path = "/xhr/resources/pass.txt?pipe=" +
+    "header(Cache-Control,no-store)|" +
+    "header(Content-Type,text/plain)" +
+    "header(Access-Control-Allow-Credentials,true)|" +
+    "header(Access-Control-Allow-Origin," + get_host_info().HTTP_ORIGIN + ")";
+
+async_test((test) => {
+  const xhr = new XMLHttpRequest;
+  xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + path);
+  xhr.send();
+  xhr.onerror = test.unreached_func("Unexpected error");
+  xhr.onload = test.step_func_done(() => {
+    assert_equals(xhr.status, 200);
+    assert_equals(xhr.responseText.trim(), "PASS");
+  });
+}, "Check that path exists and is accessible via CORS XHR request");
+
+async_test((test) => {
+  window.addEventListener("message", test.step_func((evt) => {
+    if (evt.data === "ready") {
+      document.getElementById("frame").contentWindow.postMessage(
+          get_host_info().HTTP_REMOTE_ORIGIN + path, "*");
+    } else {
+      assert_equals(evt.data, "Exception thrown. Sandboxed iframe XHR access was denied in 'send'.");
+      test.done();
+    }
+  }), false);
+}, "Sandboxed iframe is denied CORS access to server that allows parent origin");
+    </script>
+    <iframe id="frame" sandbox="allow-scripts" src="/xhr/resources/access-control-sandboxed-iframe.html">
+    </iframe>
+  </body>
+</html>
diff --git a/xhr/access-control-sandboxed-iframe-denied.htm b/xhr/access-control-sandboxed-iframe-denied.htm
new file mode 100644
index 0000000..5b62991
--- /dev/null
+++ b/xhr/access-control-sandboxed-iframe-denied.htm
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tests that sandboxed iframe does not have CORS XHR access to its server</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+    <script type="text/javascript">
+const path = "/xhr/resources/pass.txt?pipe=" +
+    "header(Cache-Control,no-store)|" +
+    "header(Content-Type,text/plain)";
+
+async_test((test) => {
+  const xhr = new XMLHttpRequest;
+  xhr.open("GET", get_host_info().HTTP_ORIGIN + path);
+  xhr.send();
+  xhr.onerror = test.unreached_func("Unexpected error");
+  xhr.onload = test.step_func_done(() => {
+    assert_equals(xhr.status, 200);
+    assert_equals(xhr.responseText.trim(), "PASS");
+  });
+}, "Check that path exists and is accessible via local XHR request");
+
+async_test((test) => {
+  window.addEventListener("message", test.step_func((evt) => {
+    if (evt.data === "ready") {
+      document.getElementById("frame").contentWindow.postMessage(
+          get_host_info().HTTP_ORIGIN + path, "*");
+    } else {
+      assert_equals(evt.data, "Exception thrown. Sandboxed iframe XHR access was denied in 'send'.");
+      test.done();
+    }
+  }), false);
+}, "Sandboxed iframe is denied access to path");
+    </script>
+    <iframe id="frame" sandbox="allow-scripts" src="/xhr/resources/access-control-sandboxed-iframe.html">
+    </iframe>
+  </body>
+</html>
diff --git a/xhr/allow-lists-starting-with-comma.htm b/xhr/allow-lists-starting-with-comma.htm
new file mode 100644
index 0000000..ece699b
--- /dev/null
+++ b/xhr/allow-lists-starting-with-comma.htm
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Allow lists starting with a comma should be parsed correctly</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+  </head>
+  <body>
+  <script type="text/javascript">
+    async_test(function(test) {
+      const client = new XMLHttpRequest();
+      let url = "xhr/resources/access-control-allow-lists.py?headers=,y-lol,x-print,%20,,,y-print&origin=" +
+          get_host_info().HTTP_ORIGIN;
+      client.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + '/' + url, false);
+      client.setRequestHeader('x-print', 'unicorn')
+      client.setRequestHeader('y-print', 'narwhal')
+      // Sending GET request with custom headers
+      assert_equals(client.send(null), undefined);
+      const response = JSON.parse(client.response);
+      assert_equals(response['x-print'], "unicorn");
+      assert_equals(response['y-print'], "narwhal");
+
+      url = "xhr/resources/access-control-allow-lists.py?methods=,,PUT,GET&origin=" +
+          get_host_info().HTTP_ORIGIN;
+      client.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + '/' + url, false);
+      // Sending PUT request
+      assert_equals(client.send(null), undefined);
+      test.done();
+    }, "Allow lists starting with a comma should be parsed correctly");
+  </script>
+  </body>
+  </html>
diff --git a/XMLHttpRequest/anonymous-mode-unsupported.htm b/xhr/anonymous-mode-unsupported.htm
similarity index 100%
rename from XMLHttpRequest/anonymous-mode-unsupported.htm
rename to xhr/anonymous-mode-unsupported.htm
diff --git a/xhr/close-worker-with-xhr-in-progress.html b/xhr/close-worker-with-xhr-in-progress.html
new file mode 100644
index 0000000..4d03bea
--- /dev/null
+++ b/xhr/close-worker-with-xhr-in-progress.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+async_test(t => {
+  function workerCode(origin) {
+    const xhr = new XMLHttpRequest();
+    xhr.open('GET', origin + '/xhr/resources/image.gif?pipe=trickle(100:d2)', true);
+    xhr.responseType = 'blob';
+    xhr.send();
+    postMessage('sent');
+  }
+
+  const workerBlob = new Blob([workerCode.toString() + ";workerCode('" + location.origin + "');"], {type:"application/javascript"});
+  const w = new Worker(URL.createObjectURL(workerBlob));
+  w.onmessage = t.step_func(e => {
+    assert_equals(e.data, 'sent');
+    t.step_timeout(t.step_func(() => {
+      w.terminate();
+      t.step_timeout(t.step_func_done(() => {}), 500);
+    }, 100));
+  });
+}, 'Terminating a worker with a XHR in progress doesn\'t crash');
+</script>
diff --git a/XMLHttpRequest/data-uri.htm b/xhr/data-uri.htm
similarity index 100%
rename from XMLHttpRequest/data-uri.htm
rename to xhr/data-uri.htm
diff --git a/XMLHttpRequest/event-abort.htm b/xhr/event-abort.htm
similarity index 100%
rename from XMLHttpRequest/event-abort.htm
rename to xhr/event-abort.htm
diff --git a/xhr/event-error-order.sub.html b/xhr/event-error-order.sub.html
new file mode 100644
index 0000000..252a90b
--- /dev/null
+++ b/xhr/event-error-order.sub.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="assert" content="Check the order of events fired when the request has failed.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/xmlhttprequest-event-order.js"></script>
+    <title>XMLHttpRequest: event - error (order of events)</title>
+</head>
+
+<body>
+    <div id="log"></div>
+
+    <script type="text/javascript">
+        var test = async_test();
+
+        test.step(function()
+        {
+            var xhr = new XMLHttpRequest();
+            prepare_xhr_for_event_order_test(xhr);
+
+            xhr.addEventListener("loadend", function() {
+                test.step(function() {
+                    // no progress events due to CORS failure
+                    assert_xhr_event_order_matches([1, "loadstart(0,0,false)", "upload.loadstart(0,12,true)", 2, 4, "upload.error(0,0,false)", "upload.loadend(0,0,false)", "error(0,0,false)", "loadend(0,0,false)"]);
+                    test.done();
+                });
+            });
+
+            xhr.open("POST", "http://nonexistent.{{host}}:{{ports[http][0]}}", true);
+            xhr.send("Test Message");
+        });
+    </script>
+</body>
+</html>
diff --git a/xhr/event-error.sub.html b/xhr/event-error.sub.html
new file mode 100644
index 0000000..5f27560
--- /dev/null
+++ b/xhr/event-error.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>XMLHttpRequest Test: event - error</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="Check if event onerror is fired When the request has failed.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script>
+
+async_test(function (t) {
+  var client = new XMLHttpRequest();
+  client.onerror = t.step_func(function(e) {
+    assert_true(e instanceof ProgressEvent);
+    assert_equals(e.type, "error");
+    t.done();
+  });
+
+  client.open("GET", "http://nonexistent.{{host}}:{{ports[http][0]}}");
+  client.send("null");
+}, document.title);
+
+</script>
diff --git a/XMLHttpRequest/event-load.htm b/xhr/event-load.htm
similarity index 100%
rename from XMLHttpRequest/event-load.htm
rename to xhr/event-load.htm
diff --git a/XMLHttpRequest/event-loadend.htm b/xhr/event-loadend.htm
similarity index 100%
rename from XMLHttpRequest/event-loadend.htm
rename to xhr/event-loadend.htm
diff --git a/XMLHttpRequest/event-loadstart-upload.htm b/xhr/event-loadstart-upload.htm
similarity index 100%
rename from XMLHttpRequest/event-loadstart-upload.htm
rename to xhr/event-loadstart-upload.htm
diff --git a/XMLHttpRequest/event-loadstart.htm b/xhr/event-loadstart.htm
similarity index 100%
rename from XMLHttpRequest/event-loadstart.htm
rename to xhr/event-loadstart.htm
diff --git a/XMLHttpRequest/event-progress.htm b/xhr/event-progress.htm
similarity index 100%
rename from XMLHttpRequest/event-progress.htm
rename to xhr/event-progress.htm
diff --git a/XMLHttpRequest/event-readystate-sync-open.htm b/xhr/event-readystate-sync-open.htm
similarity index 100%
rename from XMLHttpRequest/event-readystate-sync-open.htm
rename to xhr/event-readystate-sync-open.htm
diff --git a/XMLHttpRequest/event-readystatechange-loaded.htm b/xhr/event-readystatechange-loaded.htm
similarity index 100%
rename from XMLHttpRequest/event-readystatechange-loaded.htm
rename to xhr/event-readystatechange-loaded.htm
diff --git a/XMLHttpRequest/event-timeout-order.htm b/xhr/event-timeout-order.htm
similarity index 100%
rename from XMLHttpRequest/event-timeout-order.htm
rename to xhr/event-timeout-order.htm
diff --git a/XMLHttpRequest/event-timeout.htm b/xhr/event-timeout.htm
similarity index 100%
rename from XMLHttpRequest/event-timeout.htm
rename to xhr/event-timeout.htm
diff --git a/xhr/event-upload-progress-crossorigin.htm b/xhr/event-upload-progress-crossorigin.htm
new file mode 100644
index 0000000..5e558a27
--- /dev/null
+++ b/xhr/event-upload-progress-crossorigin.htm
@@ -0,0 +1,33 @@
+<!doctype html>
+<html lang=en>
+<meta charset=utf-8>
+<title>XMLHttpRequest: upload progress event for cross-origin requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id="log"></div>
+<script>
+const remote = get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/corsenabled.py",
+      redirect = "resources/redirect.py?code=307&location=" + remote;
+
+[remote, redirect].forEach(url => {
+  async_test(test => {
+    const client = new XMLHttpRequest();
+    client.upload.onprogress = test.step_func_done()
+    client.onload = test.unreached_func()
+    client.open("POST", url)
+    client.send("On time: " + url)
+  }, "Upload events registered on time (" + url + ")");
+});
+
+[remote, redirect].forEach(url => {
+  async_test(test => {
+    const client = new XMLHttpRequest();
+    client.onload = test.step_func_done();
+    client.open("POST", url);
+    client.send("Too late: " + url);
+    client.upload.onloadstart = test.unreached_func(); // registered too late
+    client.upload.onprogress = test.unreached_func(); // registered too late
+  }, "Upload events registered too late (" + url + ")");
+});
+</script>
diff --git a/XMLHttpRequest/event-upload-progress.htm b/xhr/event-upload-progress.htm
similarity index 100%
rename from XMLHttpRequest/event-upload-progress.htm
rename to xhr/event-upload-progress.htm
diff --git a/xhr/firing-events-http-content-length.html b/xhr/firing-events-http-content-length.html
new file mode 100644
index 0000000..4748ce3
--- /dev/null
+++ b/xhr/firing-events-http-content-length.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+  <head>
+    <title>ProgressEvent: firing events for HTTP with Content-Length</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      async_test(t => {
+        const xhr = new XMLHttpRequest();
+        let progressHappened = false;
+
+        xhr.onprogress = t.step_func(pe => {
+          assert_equals(pe.type, "progress");
+          assert_greater_than_equal(pe.loaded, 0, "loaded");
+          assert_true(pe.lengthComputable, "lengthComputable");
+          assert_equals(pe.total, 1300, "total");
+          progressHappened = true;
+        });
+
+        xhr.onloadend = t.step_func_done(() => {
+          assert_true(progressHappened);
+        });
+
+        xhr.open("GET", "resources/trickle.py?ms=0&count=100&specifylength=1", true);
+        xhr.send(null);
+      });
+    </script>
+  </body>
+</html>
diff --git a/xhr/firing-events-http-no-content-length.html b/xhr/firing-events-http-no-content-length.html
new file mode 100644
index 0000000..ddf7dd8
--- /dev/null
+++ b/xhr/firing-events-http-no-content-length.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+  <head>
+    <title>ProgressEvent: firing events for HTTP with no Content-Length</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      async_test(t => {
+        const xhr = new XMLHttpRequest();
+        let progressHappened = false;
+
+        xhr.onprogress = t.step_func(pe => {
+          assert_equals(pe.type, "progress");
+          assert_greater_than_equal(pe.loaded, 0, "loaded");
+          assert_false(pe.lengthComputable, "lengthComputable");
+          assert_equals(pe.total, 0, "total");
+          progressHappened = true;
+        });
+
+        // "loadstart", "error", "abort", "load" tests are out of scope.
+        // They SHOULD be tested in each spec that implement ProgressEvent.
+
+        xhr.onloadend = t.step_func_done(() => {
+          assert_true(progressHappened);
+        });
+
+        xhr.open("GET", "resources/trickle.py?ms=0&count=100", true);
+        xhr.send(null);
+      });
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/folder.txt b/xhr/folder.txt
similarity index 100%
rename from XMLHttpRequest/folder.txt
rename to xhr/folder.txt
diff --git a/XMLHttpRequest/formdata-blob.htm b/xhr/formdata-blob.htm
similarity index 100%
rename from XMLHttpRequest/formdata-blob.htm
rename to xhr/formdata-blob.htm
diff --git a/XMLHttpRequest/formdata-delete.htm b/xhr/formdata-delete.htm
similarity index 100%
rename from XMLHttpRequest/formdata-delete.htm
rename to xhr/formdata-delete.htm
diff --git a/XMLHttpRequest/formdata-foreach.html b/xhr/formdata-foreach.html
similarity index 100%
rename from XMLHttpRequest/formdata-foreach.html
rename to xhr/formdata-foreach.html
diff --git a/XMLHttpRequest/formdata-get.htm b/xhr/formdata-get.htm
similarity index 100%
rename from XMLHttpRequest/formdata-get.htm
rename to xhr/formdata-get.htm
diff --git a/XMLHttpRequest/formdata-has.htm b/xhr/formdata-has.htm
similarity index 100%
rename from XMLHttpRequest/formdata-has.htm
rename to xhr/formdata-has.htm
diff --git a/XMLHttpRequest/formdata-set.htm b/xhr/formdata-set.htm
similarity index 100%
rename from XMLHttpRequest/formdata-set.htm
rename to xhr/formdata-set.htm
diff --git a/XMLHttpRequest/formdata.htm b/xhr/formdata.htm
similarity index 100%
rename from XMLHttpRequest/formdata.htm
rename to xhr/formdata.htm
diff --git a/XMLHttpRequest/getallresponseheaders-cookies.htm b/xhr/getallresponseheaders-cookies.htm
similarity index 100%
rename from XMLHttpRequest/getallresponseheaders-cookies.htm
rename to xhr/getallresponseheaders-cookies.htm
diff --git a/XMLHttpRequest/getallresponseheaders-status.htm b/xhr/getallresponseheaders-status.htm
similarity index 100%
rename from XMLHttpRequest/getallresponseheaders-status.htm
rename to xhr/getallresponseheaders-status.htm
diff --git a/XMLHttpRequest/getallresponseheaders.htm b/xhr/getallresponseheaders.htm
similarity index 100%
rename from XMLHttpRequest/getallresponseheaders.htm
rename to xhr/getallresponseheaders.htm
diff --git a/XMLHttpRequest/getresponseheader-case-insensitive.htm b/xhr/getresponseheader-case-insensitive.htm
similarity index 100%
rename from XMLHttpRequest/getresponseheader-case-insensitive.htm
rename to xhr/getresponseheader-case-insensitive.htm
diff --git a/XMLHttpRequest/getresponseheader-chunked-trailer.htm b/xhr/getresponseheader-chunked-trailer.htm
similarity index 100%
rename from XMLHttpRequest/getresponseheader-chunked-trailer.htm
rename to xhr/getresponseheader-chunked-trailer.htm
diff --git a/XMLHttpRequest/getresponseheader-cookies-and-more.htm b/xhr/getresponseheader-cookies-and-more.htm
similarity index 100%
rename from XMLHttpRequest/getresponseheader-cookies-and-more.htm
rename to xhr/getresponseheader-cookies-and-more.htm
diff --git a/XMLHttpRequest/getresponseheader-error-state.htm b/xhr/getresponseheader-error-state.htm
similarity index 100%
rename from XMLHttpRequest/getresponseheader-error-state.htm
rename to xhr/getresponseheader-error-state.htm
diff --git a/XMLHttpRequest/getresponseheader-server-date.htm b/xhr/getresponseheader-server-date.htm
similarity index 100%
rename from XMLHttpRequest/getresponseheader-server-date.htm
rename to xhr/getresponseheader-server-date.htm
diff --git a/XMLHttpRequest/getresponseheader-special-characters.htm b/xhr/getresponseheader-special-characters.htm
similarity index 100%
rename from XMLHttpRequest/getresponseheader-special-characters.htm
rename to xhr/getresponseheader-special-characters.htm
diff --git a/XMLHttpRequest/getresponseheader-unsent-opened-state.htm b/xhr/getresponseheader-unsent-opened-state.htm
similarity index 100%
rename from XMLHttpRequest/getresponseheader-unsent-opened-state.htm
rename to xhr/getresponseheader-unsent-opened-state.htm
diff --git a/xhr/header-user-agent-async.htm b/xhr/header-user-agent-async.htm
new file mode 100644
index 0000000..8c1d0b6
--- /dev/null
+++ b/xhr/header-user-agent-async.htm
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Test that async requests (both OPTIONS preflight and regular) are sent with the User-Agent header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script type="text/javascript">
+  async_test((test) => {
+    let xhr = new XMLHttpRequest;
+    xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/header-user-agent.py");
+    xhr.setRequestHeader("x-test", "foobar");
+
+    xhr.onerror = test.unreached_func("Unexpected error");
+
+    xhr.onload = test.step_func_done(() => {
+      assert_equals(xhr.responseText, "PASS");
+    });
+
+    xhr.send();
+  }, "Async request has User-Agent header");
+</script>
+</body>
+</html>
diff --git a/xhr/header-user-agent-sync.htm b/xhr/header-user-agent-sync.htm
new file mode 100644
index 0000000..d88aac2
--- /dev/null
+++ b/xhr/header-user-agent-sync.htm
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Test that sync requests (both OPTIONS preflight and regular) are sent with the User-Agent header</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<script type="text/javascript">
+  test(function() {
+    let xhr = new XMLHttpRequest;
+    xhr.open("post", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/header-user-agent.py", false);
+    xhr.setRequestHeader("x-test", "foobar");
+    xhr.send();
+    assert_equals(xhr.responseText, "PASS");
+  }, "Sync request has User-Agent header");
+</script>
+</body>
+</html>
diff --git a/XMLHttpRequest/headers-normalize-response.htm b/xhr/headers-normalize-response.htm
similarity index 100%
rename from XMLHttpRequest/headers-normalize-response.htm
rename to xhr/headers-normalize-response.htm
diff --git a/XMLHttpRequest/historical.html b/xhr/historical.html
similarity index 100%
rename from XMLHttpRequest/historical.html
rename to xhr/historical.html
diff --git a/xhr/interfaces.html b/xhr/interfaces.html
new file mode 100644
index 0000000..cc98075
--- /dev/null
+++ b/xhr/interfaces.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>XMLHttpRequest IDL tests</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/WebIDLParser.js></script>
+<script src=/resources/idlharness.js></script>
+
+<h1>XMLHttpRequest IDL tests</h1>
+<div id=log></div>
+
+<script type=text/plain class=untested>
+[TreatNonCallableAsNull]
+callback EventHandlerNonNull = any (Event event);
+typedef EventHandlerNonNull? EventHandler;
+</script>
+<script>
+"use strict";
+var form = document.createElement("form");
+var idlArray = new IdlArray();
+
+function doTest([domIdl, xhrIdl]) {
+  idlArray.add_untested_idls(domIdl);
+  var untestedIdl = document.querySelector("script.untested").textContent;
+  idlArray.add_untested_idls(untestedIdl);
+  idlArray.add_idls(xhrIdl);
+  idlArray.add_objects({
+    XMLHttpRequest: ['new XMLHttpRequest()'],
+    XMLHttpRequestUpload: ['(new XMLHttpRequest()).upload'],
+    FormData: ['new FormData()', 'new FormData(form)']
+  });
+  idlArray.test();
+}
+
+function fetchText(url) {
+  return fetch(url).then(response => response.text());
+}
+
+promise_test(() => {
+  return Promise.all(["/interfaces/dom.idl",
+                      "/interfaces/xhr.idl"].map(fetchText))
+    .then(doTest);
+}, "Test driver");
+</script>
diff --git a/XMLHttpRequest/loadstart-and-state.html b/xhr/loadstart-and-state.html
similarity index 100%
rename from XMLHttpRequest/loadstart-and-state.html
rename to xhr/loadstart-and-state.html
diff --git a/XMLHttpRequest/no-utf16-json.htm b/xhr/no-utf16-json.htm
similarity index 100%
rename from XMLHttpRequest/no-utf16-json.htm
rename to xhr/no-utf16-json.htm
diff --git a/XMLHttpRequest/open-after-abort.htm b/xhr/open-after-abort.htm
similarity index 100%
rename from XMLHttpRequest/open-after-abort.htm
rename to xhr/open-after-abort.htm
diff --git a/XMLHttpRequest/open-after-setrequestheader.htm b/xhr/open-after-setrequestheader.htm
similarity index 100%
rename from XMLHttpRequest/open-after-setrequestheader.htm
rename to xhr/open-after-setrequestheader.htm
diff --git a/XMLHttpRequest/open-during-abort-event.htm b/xhr/open-during-abort-event.htm
similarity index 100%
rename from XMLHttpRequest/open-during-abort-event.htm
rename to xhr/open-during-abort-event.htm
diff --git a/XMLHttpRequest/open-during-abort-processing.htm b/xhr/open-during-abort-processing.htm
similarity index 100%
rename from XMLHttpRequest/open-during-abort-processing.htm
rename to xhr/open-during-abort-processing.htm
diff --git a/XMLHttpRequest/open-during-abort.htm b/xhr/open-during-abort.htm
similarity index 100%
rename from XMLHttpRequest/open-during-abort.htm
rename to xhr/open-during-abort.htm
diff --git a/XMLHttpRequest/open-method-bogus.htm b/xhr/open-method-bogus.htm
similarity index 100%
rename from XMLHttpRequest/open-method-bogus.htm
rename to xhr/open-method-bogus.htm
diff --git a/XMLHttpRequest/open-method-case-insensitive.htm b/xhr/open-method-case-insensitive.htm
similarity index 100%
rename from XMLHttpRequest/open-method-case-insensitive.htm
rename to xhr/open-method-case-insensitive.htm
diff --git a/XMLHttpRequest/open-method-case-sensitive.htm b/xhr/open-method-case-sensitive.htm
similarity index 100%
rename from XMLHttpRequest/open-method-case-sensitive.htm
rename to xhr/open-method-case-sensitive.htm
diff --git a/XMLHttpRequest/open-method-insecure.htm b/xhr/open-method-insecure.htm
similarity index 100%
rename from XMLHttpRequest/open-method-insecure.htm
rename to xhr/open-method-insecure.htm
diff --git a/XMLHttpRequest/open-method-responsetype-set-sync.htm b/xhr/open-method-responsetype-set-sync.htm
similarity index 100%
rename from XMLHttpRequest/open-method-responsetype-set-sync.htm
rename to xhr/open-method-responsetype-set-sync.htm
diff --git a/XMLHttpRequest/open-open-send.htm b/xhr/open-open-send.htm
similarity index 100%
rename from XMLHttpRequest/open-open-send.htm
rename to xhr/open-open-send.htm
diff --git a/XMLHttpRequest/open-open-sync-send.htm b/xhr/open-open-sync-send.htm
similarity index 100%
rename from XMLHttpRequest/open-open-sync-send.htm
rename to xhr/open-open-sync-send.htm
diff --git a/xhr/open-parameters-toString.htm b/xhr/open-parameters-toString.htm
new file mode 100644
index 0000000..c059482
--- /dev/null
+++ b/xhr/open-parameters-toString.htm
@@ -0,0 +1,54 @@
+<!doctype html>
+<title>XMLHttpRequest: open() attempts to toString its string parameters</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(() => {
+  let log = [];
+  let expected = [
+    'method',
+    'url',
+    // NOTE: 'async' intentionally missing
+    'username',
+    'password',
+  ];
+
+  let xhr = new XMLHttpRequest;
+  xhr.open(
+    {
+      toString() {
+        log.push('method');
+        return 'get';
+      },
+    },
+    {
+      toString() {
+        log.push('url');
+        return location.href;
+      },
+    },
+    // NOTE: ToBoolean should not invoke valueOf
+    {
+      valueOf() {
+        log.push('async');
+        return true;
+      },
+    },
+    {
+      toString() {
+        log.push('username');
+        return 'username';
+      },
+    },
+    {
+      toString() {
+        log.push('password');
+        return 'password';
+      },
+    }
+  );
+
+  assert_array_equals(log, expected);
+});
+</script>
diff --git a/XMLHttpRequest/open-referer.htm b/xhr/open-referer.htm
similarity index 100%
rename from XMLHttpRequest/open-referer.htm
rename to xhr/open-referer.htm
diff --git a/XMLHttpRequest/open-send-during-abort.htm b/xhr/open-send-during-abort.htm
similarity index 100%
rename from XMLHttpRequest/open-send-during-abort.htm
rename to xhr/open-send-during-abort.htm
diff --git a/XMLHttpRequest/open-send-open.htm b/xhr/open-send-open.htm
similarity index 100%
rename from XMLHttpRequest/open-send-open.htm
rename to xhr/open-send-open.htm
diff --git a/XMLHttpRequest/open-sync-open-send.htm b/xhr/open-sync-open-send.htm
similarity index 100%
rename from XMLHttpRequest/open-sync-open-send.htm
rename to xhr/open-sync-open-send.htm
diff --git a/XMLHttpRequest/open-url-about-blank-window.htm b/xhr/open-url-about-blank-window.htm
similarity index 100%
rename from XMLHttpRequest/open-url-about-blank-window.htm
rename to xhr/open-url-about-blank-window.htm
diff --git a/XMLHttpRequest/open-url-base-inserted-after-open.htm b/xhr/open-url-base-inserted-after-open.htm
similarity index 100%
rename from XMLHttpRequest/open-url-base-inserted-after-open.htm
rename to xhr/open-url-base-inserted-after-open.htm
diff --git a/XMLHttpRequest/open-url-base-inserted.htm b/xhr/open-url-base-inserted.htm
similarity index 100%
rename from XMLHttpRequest/open-url-base-inserted.htm
rename to xhr/open-url-base-inserted.htm
diff --git a/XMLHttpRequest/open-url-base.htm b/xhr/open-url-base.htm
similarity index 100%
rename from XMLHttpRequest/open-url-base.htm
rename to xhr/open-url-base.htm
diff --git a/XMLHttpRequest/open-url-encoding.htm b/xhr/open-url-encoding.htm
similarity index 100%
rename from XMLHttpRequest/open-url-encoding.htm
rename to xhr/open-url-encoding.htm
diff --git a/xhr/open-url-fragment.htm b/xhr/open-url-fragment.htm
new file mode 100644
index 0000000..03f4016
--- /dev/null
+++ b/xhr/open-url-fragment.htm
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>XMLHttpRequest: open() resolving URLs - fragment identifier</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[7]" />
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      test(function() {
+        var client = new XMLHttpRequest()
+        client.open("GET", "folder.txt#foobar", false)
+        client.send(null)
+        assert_equals(client.responseText, "top\n")
+      })
+      test(function() {
+        var client = new XMLHttpRequest()
+        client.open("GET", "resources/requri.py#foobar", false)
+        client.send(null)
+        assert_regexp_match(client.responseText, /xhr\/resources\/requri\.py$/)
+      }, 'make sure fragment is removed from URL before request')
+      test(function() {
+        var client = new XMLHttpRequest()
+        client.open("GET", "resources/requri.py?help=#foobar", false)
+        client.send(null)
+        assert_regexp_match(client.responseText, /xhr\/resources\/requri\.py\?help=$/)
+      }, 'make sure fragment is removed from URL before request (with query string)')
+      test(function() {
+        var client = new XMLHttpRequest()
+        client.open("GET", "resources/requri.py?" +encodeURIComponent("#foobar"), false)
+        client.send(null)
+        assert_regexp_match(client.responseText, /xhr\/resources\/requri\.py\?%23foobar$/)
+      }, 'make sure escaped # is not removed')
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/open-url-javascript-window-2.htm b/xhr/open-url-javascript-window-2.htm
similarity index 100%
rename from XMLHttpRequest/open-url-javascript-window-2.htm
rename to xhr/open-url-javascript-window-2.htm
diff --git a/XMLHttpRequest/open-url-javascript-window.htm b/xhr/open-url-javascript-window.htm
similarity index 100%
rename from XMLHttpRequest/open-url-javascript-window.htm
rename to xhr/open-url-javascript-window.htm
diff --git a/XMLHttpRequest/open-url-multi-window-2.htm b/xhr/open-url-multi-window-2.htm
similarity index 100%
rename from XMLHttpRequest/open-url-multi-window-2.htm
rename to xhr/open-url-multi-window-2.htm
diff --git a/XMLHttpRequest/open-url-multi-window-3.htm b/xhr/open-url-multi-window-3.htm
similarity index 100%
rename from XMLHttpRequest/open-url-multi-window-3.htm
rename to xhr/open-url-multi-window-3.htm
diff --git a/XMLHttpRequest/open-url-multi-window-4.htm b/xhr/open-url-multi-window-4.htm
similarity index 100%
rename from XMLHttpRequest/open-url-multi-window-4.htm
rename to xhr/open-url-multi-window-4.htm
diff --git a/XMLHttpRequest/open-url-multi-window-5.htm b/xhr/open-url-multi-window-5.htm
similarity index 100%
rename from XMLHttpRequest/open-url-multi-window-5.htm
rename to xhr/open-url-multi-window-5.htm
diff --git a/XMLHttpRequest/open-url-multi-window-6.htm b/xhr/open-url-multi-window-6.htm
similarity index 100%
rename from XMLHttpRequest/open-url-multi-window-6.htm
rename to xhr/open-url-multi-window-6.htm
diff --git a/XMLHttpRequest/open-url-multi-window.htm b/xhr/open-url-multi-window.htm
similarity index 100%
rename from XMLHttpRequest/open-url-multi-window.htm
rename to xhr/open-url-multi-window.htm
diff --git a/XMLHttpRequest/open-url-redirected-worker-origin.htm b/xhr/open-url-redirected-worker-origin.htm
similarity index 100%
rename from XMLHttpRequest/open-url-redirected-worker-origin.htm
rename to xhr/open-url-redirected-worker-origin.htm
diff --git a/XMLHttpRequest/open-url-worker-origin.htm b/xhr/open-url-worker-origin.htm
similarity index 100%
rename from XMLHttpRequest/open-url-worker-origin.htm
rename to xhr/open-url-worker-origin.htm
diff --git a/XMLHttpRequest/open-url-worker-simple.htm b/xhr/open-url-worker-simple.htm
similarity index 100%
rename from XMLHttpRequest/open-url-worker-simple.htm
rename to xhr/open-url-worker-simple.htm
diff --git a/XMLHttpRequest/open-user-password-non-same-origin.htm b/xhr/open-user-password-non-same-origin.htm
similarity index 100%
rename from XMLHttpRequest/open-user-password-non-same-origin.htm
rename to xhr/open-user-password-non-same-origin.htm
diff --git a/xhr/overridemimetype-blob.html b/xhr/overridemimetype-blob.html
new file mode 100644
index 0000000..db4b880
--- /dev/null
+++ b/xhr/overridemimetype-blob.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<title>XMLHttpRequest: overrideMimeType() and responseType = "blob"</title>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+  const client = new XMLHttpRequest();
+  client.onload = t.step_func_done(() => {
+    assert_equals(client.getResponseHeader("Content-Type"), "");
+    assert_equals(client.response.type, "text/xml");
+  });
+  client.open("GET", "resources/status.py");
+  client.responseType = "blob";
+  client.send();
+}, "Use text/xml as fallback MIME type");
+
+async_test(t => {
+  const client = new XMLHttpRequest();
+  client.onload = t.step_func_done(() => {
+    assert_equals(client.getResponseHeader("Content-Type"), "");
+    assert_equals(client.response.type, "text/xml");
+  })
+  client.open("GET", "resources/status.py?content=thisshouldnotmakeadifferencebutdoes");
+  client.responseType = "blob";
+  client.send();
+}, "Use text/xml as fallback MIME type, 2");
+
+promise_test(() => {
+  // Don't load generated-mime-types.json as sending them all over the network would be prohibitive
+  return fetch("../mimesniff/mime-types/resources/mime-types.json").then(res => res.json()).then(runTests);
+}, "Loading data…");
+
+function runTests(tests) {
+  let index = 0;
+  tests.forEach((val) => {
+    if(typeof val === "string") {
+      return;
+    }
+    index++;
+    async_test(t => {
+      const client = new XMLHttpRequest(),
+            expectedOutput = val.output !== null ? val.output : "application/octet-stream";
+      client.onload = t.step_func_done(() => {
+        assert_equals(client.getResponseHeader("Content-Type"), "");
+        assert_equals(client.response.type, expectedOutput);
+      });
+      client.open("GET", "resources/status.py");
+      client.responseType = "blob";
+      client.overrideMimeType(val.input);
+      client.send();
+    }, index + ") MIME types need to be parsed and serialized: " + val.input);
+  });
+}
+</script>
diff --git a/XMLHttpRequest/overridemimetype-done-state.htm b/xhr/overridemimetype-done-state.htm
similarity index 100%
rename from XMLHttpRequest/overridemimetype-done-state.htm
rename to xhr/overridemimetype-done-state.htm
diff --git a/xhr/overridemimetype-edge-cases.window.js b/xhr/overridemimetype-edge-cases.window.js
new file mode 100644
index 0000000..6dfe755
--- /dev/null
+++ b/xhr/overridemimetype-edge-cases.window.js
@@ -0,0 +1,40 @@
+const testURL = "resources/status.py?type=" + encodeURIComponent("text/plain;charset=windows-1252") + "&content=%C2%F0";
+
+async_test(t => {
+  const client = new XMLHttpRequest();
+  let secondTime = false;
+  client.onload = t.step_func(() => {
+    if(!secondTime) {
+      assert_equals(client.responseText, "\uFFFD");
+      secondTime = true;
+      client.open("GET", testURL);
+      client.send();
+    } else {
+      assert_equals(client.responseText, "Âð");
+      t.done();
+    }
+  });
+  client.open("GET", testURL);
+  client.overrideMimeType("text/plain;charset=UTF-8")
+  client.send();
+}, "overrideMimeType() state needs to be reset across requests");
+
+async_test(t => {
+  const client = new XMLHttpRequest();
+  client.onload = t.step_func_done(() => {
+    assert_equals(client.responseText, "Âð")
+  });
+  client.open("GET", testURL);
+  client.overrideMimeType("text/xml");
+  client.send();
+}, "If charset is not overridden by overrideMimeType() the original continues to be used");
+
+async_test(t => {
+  const client = new XMLHttpRequest();
+  client.onload = t.step_func_done(() => {
+    assert_equals(client.responseText, "\uFFFD")
+  });
+  client.open("GET", testURL);
+  client.overrideMimeType("text/plain;charset=342");
+  client.send();
+}, "Charset can be overridden by overrideMimeType() with a bogus charset");
diff --git a/XMLHttpRequest/overridemimetype-headers-received-state-force-shiftjis.htm b/xhr/overridemimetype-headers-received-state-force-shiftjis.htm
similarity index 100%
rename from XMLHttpRequest/overridemimetype-headers-received-state-force-shiftjis.htm
rename to xhr/overridemimetype-headers-received-state-force-shiftjis.htm
diff --git a/XMLHttpRequest/overridemimetype-invalid-mime-type.htm b/xhr/overridemimetype-invalid-mime-type.htm
similarity index 100%
rename from XMLHttpRequest/overridemimetype-invalid-mime-type.htm
rename to xhr/overridemimetype-invalid-mime-type.htm
diff --git a/XMLHttpRequest/overridemimetype-loading-state.htm b/xhr/overridemimetype-loading-state.htm
similarity index 100%
rename from XMLHttpRequest/overridemimetype-loading-state.htm
rename to xhr/overridemimetype-loading-state.htm
diff --git a/XMLHttpRequest/overridemimetype-open-state-force-utf-8.htm b/xhr/overridemimetype-open-state-force-utf-8.htm
similarity index 100%
rename from XMLHttpRequest/overridemimetype-open-state-force-utf-8.htm
rename to xhr/overridemimetype-open-state-force-utf-8.htm
diff --git a/XMLHttpRequest/overridemimetype-open-state-force-xml.htm b/xhr/overridemimetype-open-state-force-xml.htm
similarity index 100%
rename from XMLHttpRequest/overridemimetype-open-state-force-xml.htm
rename to xhr/overridemimetype-open-state-force-xml.htm
diff --git a/XMLHttpRequest/overridemimetype-unsent-state-force-shiftjis.htm b/xhr/overridemimetype-unsent-state-force-shiftjis.htm
similarity index 100%
rename from XMLHttpRequest/overridemimetype-unsent-state-force-shiftjis.htm
rename to xhr/overridemimetype-unsent-state-force-shiftjis.htm
diff --git a/XMLHttpRequest/preserve-ua-header-on-redirect.htm b/xhr/preserve-ua-header-on-redirect.htm
similarity index 100%
rename from XMLHttpRequest/preserve-ua-header-on-redirect.htm
rename to xhr/preserve-ua-header-on-redirect.htm
diff --git a/XMLHttpRequest/progress-events-response-data-gzip.htm b/xhr/progress-events-response-data-gzip.htm
similarity index 100%
rename from XMLHttpRequest/progress-events-response-data-gzip.htm
rename to xhr/progress-events-response-data-gzip.htm
diff --git a/XMLHttpRequest/progressevent-constructor.html b/xhr/progressevent-constructor.html
similarity index 100%
rename from XMLHttpRequest/progressevent-constructor.html
rename to xhr/progressevent-constructor.html
diff --git a/XMLHttpRequest/progressevent-interface.html b/xhr/progressevent-interface.html
similarity index 100%
rename from XMLHttpRequest/progressevent-interface.html
rename to xhr/progressevent-interface.html
diff --git a/XMLHttpRequest/resources/accept-language.py b/xhr/resources/accept-language.py
similarity index 100%
rename from XMLHttpRequest/resources/accept-language.py
rename to xhr/resources/accept-language.py
diff --git a/XMLHttpRequest/resources/accept.py b/xhr/resources/accept.py
similarity index 100%
rename from XMLHttpRequest/resources/accept.py
rename to xhr/resources/accept.py
diff --git a/XMLHttpRequest/resources/access-control-allow-lists.py b/xhr/resources/access-control-allow-lists.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-allow-lists.py
rename to xhr/resources/access-control-allow-lists.py
diff --git a/XMLHttpRequest/resources/access-control-allow-with-body.py b/xhr/resources/access-control-allow-with-body.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-allow-with-body.py
rename to xhr/resources/access-control-allow-with-body.py
diff --git a/XMLHttpRequest/resources/access-control-auth-basic.py b/xhr/resources/access-control-auth-basic.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-auth-basic.py
rename to xhr/resources/access-control-auth-basic.py
diff --git a/XMLHttpRequest/resources/access-control-basic-allow-no-credentials.py b/xhr/resources/access-control-basic-allow-no-credentials.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-allow-no-credentials.py
rename to xhr/resources/access-control-basic-allow-no-credentials.py
diff --git a/XMLHttpRequest/resources/access-control-basic-allow-star.py b/xhr/resources/access-control-basic-allow-star.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-allow-star.py
rename to xhr/resources/access-control-basic-allow-star.py
diff --git a/XMLHttpRequest/resources/access-control-basic-allow.py b/xhr/resources/access-control-basic-allow.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-allow.py
rename to xhr/resources/access-control-basic-allow.py
diff --git a/XMLHttpRequest/resources/access-control-basic-cors-safelisted-request-headers.py b/xhr/resources/access-control-basic-cors-safelisted-request-headers.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-cors-safelisted-request-headers.py
rename to xhr/resources/access-control-basic-cors-safelisted-request-headers.py
diff --git a/XMLHttpRequest/resources/access-control-basic-denied.py b/xhr/resources/access-control-basic-denied.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-denied.py
rename to xhr/resources/access-control-basic-denied.py
diff --git a/XMLHttpRequest/resources/access-control-basic-options-not-supported.py b/xhr/resources/access-control-basic-options-not-supported.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-options-not-supported.py
rename to xhr/resources/access-control-basic-options-not-supported.py
diff --git a/XMLHttpRequest/resources/access-control-basic-preflight-cache-invalidation.py b/xhr/resources/access-control-basic-preflight-cache-invalidation.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-preflight-cache-invalidation.py
rename to xhr/resources/access-control-basic-preflight-cache-invalidation.py
diff --git a/XMLHttpRequest/resources/access-control-basic-preflight-cache-timeout.py b/xhr/resources/access-control-basic-preflight-cache-timeout.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-preflight-cache-timeout.py
rename to xhr/resources/access-control-basic-preflight-cache-timeout.py
diff --git a/XMLHttpRequest/resources/access-control-basic-preflight-cache.py b/xhr/resources/access-control-basic-preflight-cache.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-preflight-cache.py
rename to xhr/resources/access-control-basic-preflight-cache.py
diff --git a/XMLHttpRequest/resources/access-control-basic-put-allow.py b/xhr/resources/access-control-basic-put-allow.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-put-allow.py
rename to xhr/resources/access-control-basic-put-allow.py
diff --git a/XMLHttpRequest/resources/access-control-basic-whitelist-response-headers.py b/xhr/resources/access-control-basic-whitelist-response-headers.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-basic-whitelist-response-headers.py
rename to xhr/resources/access-control-basic-whitelist-response-headers.py
diff --git a/XMLHttpRequest/resources/access-control-cookie.py b/xhr/resources/access-control-cookie.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-cookie.py
rename to xhr/resources/access-control-cookie.py
diff --git a/XMLHttpRequest/resources/access-control-origin-header.py b/xhr/resources/access-control-origin-header.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-origin-header.py
rename to xhr/resources/access-control-origin-header.py
diff --git a/XMLHttpRequest/resources/access-control-preflight-denied.py b/xhr/resources/access-control-preflight-denied.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-preflight-denied.py
rename to xhr/resources/access-control-preflight-denied.py
diff --git a/XMLHttpRequest/resources/access-control-preflight-request-header-lowercase.py b/xhr/resources/access-control-preflight-request-header-lowercase.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-preflight-request-header-lowercase.py
rename to xhr/resources/access-control-preflight-request-header-lowercase.py
diff --git a/XMLHttpRequest/resources/access-control-preflight-request-header-sorted.py b/xhr/resources/access-control-preflight-request-header-sorted.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-preflight-request-header-sorted.py
rename to xhr/resources/access-control-preflight-request-header-sorted.py
diff --git a/XMLHttpRequest/resources/access-control-preflight-request-headers-origin.py b/xhr/resources/access-control-preflight-request-headers-origin.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-preflight-request-headers-origin.py
rename to xhr/resources/access-control-preflight-request-headers-origin.py
diff --git a/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py b/xhr/resources/access-control-preflight-request-invalid-status.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py
rename to xhr/resources/access-control-preflight-request-invalid-status.py
diff --git a/XMLHttpRequest/resources/access-control-preflight-request-must-not-contain-cookie.py b/xhr/resources/access-control-preflight-request-must-not-contain-cookie.py
similarity index 100%
rename from XMLHttpRequest/resources/access-control-preflight-request-must-not-contain-cookie.py
rename to xhr/resources/access-control-preflight-request-must-not-contain-cookie.py
diff --git a/XMLHttpRequest/resources/access-control-sandboxed-iframe.html b/xhr/resources/access-control-sandboxed-iframe.html
similarity index 100%
rename from XMLHttpRequest/resources/access-control-sandboxed-iframe.html
rename to xhr/resources/access-control-sandboxed-iframe.html
diff --git a/XMLHttpRequest/resources/auth1/auth.py b/xhr/resources/auth1/auth.py
similarity index 100%
rename from XMLHttpRequest/resources/auth1/auth.py
rename to xhr/resources/auth1/auth.py
diff --git a/XMLHttpRequest/resources/auth2/auth.py b/xhr/resources/auth2/auth.py
similarity index 100%
rename from XMLHttpRequest/resources/auth2/auth.py
rename to xhr/resources/auth2/auth.py
diff --git a/XMLHttpRequest/resources/auth2/corsenabled.py b/xhr/resources/auth2/corsenabled.py
similarity index 100%
rename from XMLHttpRequest/resources/auth2/corsenabled.py
rename to xhr/resources/auth2/corsenabled.py
diff --git a/XMLHttpRequest/resources/auth3/auth.py b/xhr/resources/auth3/auth.py
similarity index 100%
rename from XMLHttpRequest/resources/auth3/auth.py
rename to xhr/resources/auth3/auth.py
diff --git a/XMLHttpRequest/resources/auth4/auth.py b/xhr/resources/auth4/auth.py
similarity index 100%
rename from XMLHttpRequest/resources/auth4/auth.py
rename to xhr/resources/auth4/auth.py
diff --git a/XMLHttpRequest/resources/auth5/auth.py b/xhr/resources/auth5/auth.py
similarity index 100%
rename from XMLHttpRequest/resources/auth5/auth.py
rename to xhr/resources/auth5/auth.py
diff --git a/XMLHttpRequest/resources/auth6/auth.py b/xhr/resources/auth6/auth.py
similarity index 100%
rename from XMLHttpRequest/resources/auth6/auth.py
rename to xhr/resources/auth6/auth.py
diff --git a/XMLHttpRequest/resources/auth7/corsenabled.py b/xhr/resources/auth7/corsenabled.py
similarity index 100%
rename from XMLHttpRequest/resources/auth7/corsenabled.py
rename to xhr/resources/auth7/corsenabled.py
diff --git a/XMLHttpRequest/resources/auth8/corsenabled-no-authorize.py b/xhr/resources/auth8/corsenabled-no-authorize.py
similarity index 100%
rename from XMLHttpRequest/resources/auth8/corsenabled-no-authorize.py
rename to xhr/resources/auth8/corsenabled-no-authorize.py
diff --git a/XMLHttpRequest/resources/auth9/auth.py b/xhr/resources/auth9/auth.py
similarity index 100%
rename from XMLHttpRequest/resources/auth9/auth.py
rename to xhr/resources/auth9/auth.py
diff --git a/xhr/resources/authentication.py b/xhr/resources/authentication.py
new file mode 100644
index 0000000..369a49a
--- /dev/null
+++ b/xhr/resources/authentication.py
@@ -0,0 +1,27 @@
+def main(request, response):
+    session_user = request.auth.username
+    session_pass = request.auth.password
+    expected_user_name = request.headers.get("X-User", None)
+
+    token = expected_user_name
+    if session_user is None and session_pass is None:
+        if token is not None and request.server.stash.take(token) is not None:
+            return 'FAIL (did not authorize)'
+        else:
+            if token is not None:
+                request.server.stash.put(token, "1")
+            status = (401, 'Unauthorized')
+            headers = [('WWW-Authenticate', 'Basic realm="test"'),
+                       ('XHR-USER', expected_user_name),
+                       ('SES-USER', session_user)]
+            return status, headers, 'FAIL (should be transparent)'
+    else:
+        if request.server.stash.take(token) == "1":
+            challenge = "DID"
+        else:
+            challenge = "DID-NOT"
+        headers = [('XHR-USER', expected_user_name),
+                   ('SES-USER', session_user),
+                   ("X-challenge", challenge)]
+        return headers, session_user + "\n" + session_pass;
+
diff --git a/XMLHttpRequest/resources/base.xml b/xhr/resources/base.xml
similarity index 100%
rename from XMLHttpRequest/resources/base.xml
rename to xhr/resources/base.xml
diff --git a/XMLHttpRequest/resources/chunked.py b/xhr/resources/chunked.py
similarity index 100%
rename from XMLHttpRequest/resources/chunked.py
rename to xhr/resources/chunked.py
diff --git a/XMLHttpRequest/resources/conditional.py b/xhr/resources/conditional.py
similarity index 100%
rename from XMLHttpRequest/resources/conditional.py
rename to xhr/resources/conditional.py
diff --git a/XMLHttpRequest/resources/content.py b/xhr/resources/content.py
similarity index 100%
rename from XMLHttpRequest/resources/content.py
rename to xhr/resources/content.py
diff --git a/XMLHttpRequest/resources/corsenabled.py b/xhr/resources/corsenabled.py
similarity index 100%
rename from XMLHttpRequest/resources/corsenabled.py
rename to xhr/resources/corsenabled.py
diff --git a/XMLHttpRequest/resources/delay.py b/xhr/resources/delay.py
similarity index 100%
rename from XMLHttpRequest/resources/delay.py
rename to xhr/resources/delay.py
diff --git a/XMLHttpRequest/resources/echo-headers.py b/xhr/resources/echo-headers.py
similarity index 100%
rename from XMLHttpRequest/resources/echo-headers.py
rename to xhr/resources/echo-headers.py
diff --git a/XMLHttpRequest/resources/echo-method.py b/xhr/resources/echo-method.py
similarity index 100%
rename from XMLHttpRequest/resources/echo-method.py
rename to xhr/resources/echo-method.py
diff --git a/XMLHttpRequest/resources/empty-div-utf8-html.py b/xhr/resources/empty-div-utf8-html.py
similarity index 100%
rename from XMLHttpRequest/resources/empty-div-utf8-html.py
rename to xhr/resources/empty-div-utf8-html.py
diff --git a/XMLHttpRequest/resources/folder.txt b/xhr/resources/folder.txt
similarity index 100%
rename from XMLHttpRequest/resources/folder.txt
rename to xhr/resources/folder.txt
diff --git a/XMLHttpRequest/resources/form.py b/xhr/resources/form.py
similarity index 100%
rename from XMLHttpRequest/resources/form.py
rename to xhr/resources/form.py
diff --git a/XMLHttpRequest/resources/gzip.py b/xhr/resources/gzip.py
similarity index 100%
rename from XMLHttpRequest/resources/gzip.py
rename to xhr/resources/gzip.py
diff --git a/XMLHttpRequest/resources/header-content-length.asis b/xhr/resources/header-content-length.asis
similarity index 100%
rename from XMLHttpRequest/resources/header-content-length.asis
rename to xhr/resources/header-content-length.asis
diff --git a/xhr/resources/header-user-agent.py b/xhr/resources/header-user-agent.py
new file mode 100644
index 0000000..4778de4
--- /dev/null
+++ b/xhr/resources/header-user-agent.py
@@ -0,0 +1,15 @@
+def main(request, response):
+    response.headers.set("Access-Control-Allow-Origin", "*")
+    response.headers.set("Access-Control-Max-Age", 0)
+    response.headers.set('Access-Control-Allow-Headers', "x-test")
+
+    if request.method == "OPTIONS":
+        if not request.headers.get("User-Agent"):
+            response.content = "FAIL: User-Agent header missing in preflight request."
+            response.status = 400
+    else:
+        if request.headers.get("User-Agent"):
+            response.content = "PASS"
+        else:
+            response.content = "FAIL: User-Agent header missing in request"
+            response.status = 400
diff --git a/XMLHttpRequest/resources/headers-basic.asis b/xhr/resources/headers-basic.asis
similarity index 100%
rename from XMLHttpRequest/resources/headers-basic.asis
rename to xhr/resources/headers-basic.asis
diff --git a/XMLHttpRequest/resources/headers.asis b/xhr/resources/headers.asis
similarity index 100%
rename from XMLHttpRequest/resources/headers.asis
rename to xhr/resources/headers.asis
diff --git a/XMLHttpRequest/resources/headers.py b/xhr/resources/headers.py
similarity index 100%
rename from XMLHttpRequest/resources/headers.py
rename to xhr/resources/headers.py
diff --git a/XMLHttpRequest/resources/image.gif b/xhr/resources/image.gif
similarity index 100%
rename from XMLHttpRequest/resources/image.gif
rename to xhr/resources/image.gif
Binary files differ
diff --git a/XMLHttpRequest/resources/img-utf8-html.py b/xhr/resources/img-utf8-html.py
similarity index 100%
rename from XMLHttpRequest/resources/img-utf8-html.py
rename to xhr/resources/img-utf8-html.py
diff --git a/XMLHttpRequest/resources/img.jpg b/xhr/resources/img.jpg
similarity index 100%
rename from XMLHttpRequest/resources/img.jpg
rename to xhr/resources/img.jpg
Binary files differ
diff --git a/XMLHttpRequest/resources/infinite-redirects.py b/xhr/resources/infinite-redirects.py
similarity index 100%
rename from XMLHttpRequest/resources/infinite-redirects.py
rename to xhr/resources/infinite-redirects.py
diff --git a/XMLHttpRequest/resources/init.htm b/xhr/resources/init.htm
similarity index 100%
rename from XMLHttpRequest/resources/init.htm
rename to xhr/resources/init.htm
diff --git a/XMLHttpRequest/resources/inspect-headers.py b/xhr/resources/inspect-headers.py
similarity index 100%
rename from XMLHttpRequest/resources/inspect-headers.py
rename to xhr/resources/inspect-headers.py
diff --git a/XMLHttpRequest/resources/invalid-utf8-html.py b/xhr/resources/invalid-utf8-html.py
similarity index 100%
rename from XMLHttpRequest/resources/invalid-utf8-html.py
rename to xhr/resources/invalid-utf8-html.py
diff --git a/XMLHttpRequest/resources/last-modified.py b/xhr/resources/last-modified.py
similarity index 100%
rename from XMLHttpRequest/resources/last-modified.py
rename to xhr/resources/last-modified.py
diff --git a/XMLHttpRequest/resources/no-custom-header-on-preflight.py b/xhr/resources/no-custom-header-on-preflight.py
similarity index 100%
rename from XMLHttpRequest/resources/no-custom-header-on-preflight.py
rename to xhr/resources/no-custom-header-on-preflight.py
diff --git a/XMLHttpRequest/resources/nocors/folder.txt b/xhr/resources/nocors/folder.txt
similarity index 100%
rename from XMLHttpRequest/resources/nocors/folder.txt
rename to xhr/resources/nocors/folder.txt
diff --git a/XMLHttpRequest/resources/parse-headers.py b/xhr/resources/parse-headers.py
similarity index 100%
rename from XMLHttpRequest/resources/parse-headers.py
rename to xhr/resources/parse-headers.py
diff --git a/XMLHttpRequest/resources/pass.txt b/xhr/resources/pass.txt
similarity index 100%
rename from XMLHttpRequest/resources/pass.txt
rename to xhr/resources/pass.txt
diff --git a/XMLHttpRequest/resources/redirect-cors.py b/xhr/resources/redirect-cors.py
similarity index 100%
rename from XMLHttpRequest/resources/redirect-cors.py
rename to xhr/resources/redirect-cors.py
diff --git a/XMLHttpRequest/resources/redirect.py b/xhr/resources/redirect.py
similarity index 100%
rename from XMLHttpRequest/resources/redirect.py
rename to xhr/resources/redirect.py
diff --git a/XMLHttpRequest/resources/requri.py b/xhr/resources/requri.py
similarity index 100%
rename from XMLHttpRequest/resources/requri.py
rename to xhr/resources/requri.py
diff --git a/XMLHttpRequest/resources/reset-token.py b/xhr/resources/reset-token.py
similarity index 100%
rename from XMLHttpRequest/resources/reset-token.py
rename to xhr/resources/reset-token.py
diff --git a/XMLHttpRequest/resources/responseType-document-in-worker.js b/xhr/resources/responseType-document-in-worker.js
similarity index 100%
rename from XMLHttpRequest/resources/responseType-document-in-worker.js
rename to xhr/resources/responseType-document-in-worker.js
diff --git a/XMLHttpRequest/resources/responseXML-unavailable-in-worker.js b/xhr/resources/responseXML-unavailable-in-worker.js
similarity index 100%
rename from XMLHttpRequest/resources/responseXML-unavailable-in-worker.js
rename to xhr/resources/responseXML-unavailable-in-worker.js
diff --git a/XMLHttpRequest/resources/send-after-setting-document-domain-window-1.htm b/xhr/resources/send-after-setting-document-domain-window-1.htm
similarity index 100%
rename from XMLHttpRequest/resources/send-after-setting-document-domain-window-1.htm
rename to xhr/resources/send-after-setting-document-domain-window-1.htm
diff --git a/XMLHttpRequest/resources/send-after-setting-document-domain-window-2.htm b/xhr/resources/send-after-setting-document-domain-window-2.htm
similarity index 100%
rename from XMLHttpRequest/resources/send-after-setting-document-domain-window-2.htm
rename to xhr/resources/send-after-setting-document-domain-window-2.htm
diff --git a/XMLHttpRequest/resources/send-after-setting-document-domain-window-helper.js b/xhr/resources/send-after-setting-document-domain-window-helper.js
similarity index 100%
rename from XMLHttpRequest/resources/send-after-setting-document-domain-window-helper.js
rename to xhr/resources/send-after-setting-document-domain-window-helper.js
diff --git a/XMLHttpRequest/resources/shift-jis-html.py b/xhr/resources/shift-jis-html.py
similarity index 100%
rename from XMLHttpRequest/resources/shift-jis-html.py
rename to xhr/resources/shift-jis-html.py
diff --git a/XMLHttpRequest/resources/status.py b/xhr/resources/status.py
similarity index 100%
rename from XMLHttpRequest/resources/status.py
rename to xhr/resources/status.py
diff --git a/XMLHttpRequest/resources/trickle.py b/xhr/resources/trickle.py
similarity index 100%
rename from XMLHttpRequest/resources/trickle.py
rename to xhr/resources/trickle.py
diff --git a/XMLHttpRequest/resources/upload.py b/xhr/resources/upload.py
similarity index 100%
rename from XMLHttpRequest/resources/upload.py
rename to xhr/resources/upload.py
diff --git a/XMLHttpRequest/resources/utf16-bom.json b/xhr/resources/utf16-bom.json
similarity index 100%
rename from XMLHttpRequest/resources/utf16-bom.json
rename to xhr/resources/utf16-bom.json
Binary files differ
diff --git a/XMLHttpRequest/resources/utf16.txt b/xhr/resources/utf16.txt
similarity index 100%
rename from XMLHttpRequest/resources/utf16.txt
rename to xhr/resources/utf16.txt
Binary files differ
diff --git a/XMLHttpRequest/resources/well-formed.xml b/xhr/resources/well-formed.xml
similarity index 100%
rename from XMLHttpRequest/resources/well-formed.xml
rename to xhr/resources/well-formed.xml
diff --git a/XMLHttpRequest/resources/win-1252-html.py b/xhr/resources/win-1252-html.py
similarity index 100%
rename from XMLHttpRequest/resources/win-1252-html.py
rename to xhr/resources/win-1252-html.py
diff --git a/XMLHttpRequest/resources/win-1252-xml.py b/xhr/resources/win-1252-xml.py
similarity index 100%
rename from XMLHttpRequest/resources/win-1252-xml.py
rename to xhr/resources/win-1252-xml.py
diff --git a/XMLHttpRequest/resources/workerxhr-origin-referrer.js b/xhr/resources/workerxhr-origin-referrer.js
similarity index 100%
rename from XMLHttpRequest/resources/workerxhr-origin-referrer.js
rename to xhr/resources/workerxhr-origin-referrer.js
diff --git a/XMLHttpRequest/resources/workerxhr-simple.js b/xhr/resources/workerxhr-simple.js
similarity index 100%
rename from XMLHttpRequest/resources/workerxhr-simple.js
rename to xhr/resources/workerxhr-simple.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-event-order.js b/xhr/resources/xmlhttprequest-event-order.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-event-order.js
rename to xhr/resources/xmlhttprequest-event-order.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-aborted.js b/xhr/resources/xmlhttprequest-timeout-aborted.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-aborted.js
rename to xhr/resources/xmlhttprequest-timeout-aborted.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-abortedonmain.js b/xhr/resources/xmlhttprequest-timeout-abortedonmain.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-abortedonmain.js
rename to xhr/resources/xmlhttprequest-timeout-abortedonmain.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-overrides.js b/xhr/resources/xmlhttprequest-timeout-overrides.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-overrides.js
rename to xhr/resources/xmlhttprequest-timeout-overrides.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-overridesexpires.js b/xhr/resources/xmlhttprequest-timeout-overridesexpires.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-overridesexpires.js
rename to xhr/resources/xmlhttprequest-timeout-overridesexpires.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-runner.js b/xhr/resources/xmlhttprequest-timeout-runner.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-runner.js
rename to xhr/resources/xmlhttprequest-timeout-runner.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-simple.js b/xhr/resources/xmlhttprequest-timeout-simple.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-simple.js
rename to xhr/resources/xmlhttprequest-timeout-simple.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-synconmain.js b/xhr/resources/xmlhttprequest-timeout-synconmain.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-synconmain.js
rename to xhr/resources/xmlhttprequest-timeout-synconmain.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-synconworker.js b/xhr/resources/xmlhttprequest-timeout-synconworker.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-synconworker.js
rename to xhr/resources/xmlhttprequest-timeout-synconworker.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout-twice.js b/xhr/resources/xmlhttprequest-timeout-twice.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout-twice.js
rename to xhr/resources/xmlhttprequest-timeout-twice.js
diff --git a/XMLHttpRequest/resources/xmlhttprequest-timeout.js b/xhr/resources/xmlhttprequest-timeout.js
similarity index 100%
rename from XMLHttpRequest/resources/xmlhttprequest-timeout.js
rename to xhr/resources/xmlhttprequest-timeout.js
diff --git a/XMLHttpRequest/resources/zlib.py b/xhr/resources/zlib.py
similarity index 100%
rename from XMLHttpRequest/resources/zlib.py
rename to xhr/resources/zlib.py
diff --git a/XMLHttpRequest/response-data-arraybuffer.htm b/xhr/response-data-arraybuffer.htm
similarity index 100%
rename from XMLHttpRequest/response-data-arraybuffer.htm
rename to xhr/response-data-arraybuffer.htm
diff --git a/XMLHttpRequest/response-data-blob.htm b/xhr/response-data-blob.htm
similarity index 100%
rename from XMLHttpRequest/response-data-blob.htm
rename to xhr/response-data-blob.htm
diff --git a/XMLHttpRequest/response-data-deflate.htm b/xhr/response-data-deflate.htm
similarity index 100%
rename from XMLHttpRequest/response-data-deflate.htm
rename to xhr/response-data-deflate.htm
diff --git a/XMLHttpRequest/response-data-gzip.htm b/xhr/response-data-gzip.htm
similarity index 100%
rename from XMLHttpRequest/response-data-gzip.htm
rename to xhr/response-data-gzip.htm
diff --git a/XMLHttpRequest/response-data-progress.htm b/xhr/response-data-progress.htm
similarity index 100%
rename from XMLHttpRequest/response-data-progress.htm
rename to xhr/response-data-progress.htm
diff --git a/XMLHttpRequest/response-invalid-responsetype.htm b/xhr/response-invalid-responsetype.htm
similarity index 100%
rename from XMLHttpRequest/response-invalid-responsetype.htm
rename to xhr/response-invalid-responsetype.htm
diff --git a/XMLHttpRequest/response-json.htm b/xhr/response-json.htm
similarity index 100%
rename from XMLHttpRequest/response-json.htm
rename to xhr/response-json.htm
diff --git a/XMLHttpRequest/response-method.htm b/xhr/response-method.htm
similarity index 100%
rename from XMLHttpRequest/response-method.htm
rename to xhr/response-method.htm
diff --git a/XMLHttpRequest/responseText-status.html b/xhr/responseText-status.html
similarity index 100%
rename from XMLHttpRequest/responseText-status.html
rename to xhr/responseText-status.html
diff --git a/XMLHttpRequest/responseType-document-in-worker.html b/xhr/responseType-document-in-worker.html
similarity index 100%
rename from XMLHttpRequest/responseType-document-in-worker.html
rename to xhr/responseType-document-in-worker.html
diff --git a/XMLHttpRequest/responseXML-unavailable-in-worker.html b/xhr/responseXML-unavailable-in-worker.html
similarity index 100%
rename from XMLHttpRequest/responseXML-unavailable-in-worker.html
rename to xhr/responseXML-unavailable-in-worker.html
diff --git a/XMLHttpRequest/responsedocument-decoding.htm b/xhr/responsedocument-decoding.htm
similarity index 100%
rename from XMLHttpRequest/responsedocument-decoding.htm
rename to xhr/responsedocument-decoding.htm
diff --git a/xhr/responsetext-decoding.htm b/xhr/responsetext-decoding.htm
new file mode 100644
index 0000000..fae0104
--- /dev/null
+++ b/xhr/responsetext-decoding.htm
@@ -0,0 +1,93 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>XMLHttpRequest: responseText decoding</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      function create_html(content) {
+        return "<!doctype html><meta charset=windows-1252><x>" + content + "</x>";
+      }
+      function create_encoded_html(encoded_content) {
+        return encodeURIComponent("<!doctype html><meta charset=windows-1252><x>") + encoded_content + encodeURIComponent("<\/x>");
+      }
+      function create_xml(content) {
+        return "<?xml version='1.0' encoding='windows-1252'?><x>" + content + "</x>";
+      }
+      function create_encoded_xml(encoded_content) {
+        return encodeURIComponent("<?xml version='1.0' encoding='windows-1252'?><x>") + encoded_content + encodeURIComponent("<\/x>");
+      }
+      function request(type, input, output, responseType) {
+        async_test((test) => {
+          const client = new XMLHttpRequest();
+          if (responseType !== undefined) {
+            client.responseType = responseType;
+          }
+          client.open("GET", "resources/status.py?content=" + input + "&type=" + encodeURIComponent(type), true);
+          client.onload = test.step_func_done(() => {
+            assert_equals(client.responseText, output);
+          })
+          client.send(null);
+        }, document.title + " (" + type + " " + input + " " + (responseType ? " " + responseType : "empty") + ")");
+      }
+
+      const encoded_content = "%e6%a9%9f";
+      const decoded_as_windows_1252 = "\u00e6\u00a9\u0178";
+      const decoded_as_utf_8 = "\u6a5f";
+      const encoded_xml = create_encoded_xml(encoded_content);
+      const encoded_html = create_encoded_html(encoded_content);
+      const xml_decoded_as_windows_1252 = create_xml(decoded_as_windows_1252);
+      const xml_decoded_as_utf_8 = create_xml(decoded_as_utf_8);
+      const html_decoded_as_windows_1252 = create_html(decoded_as_windows_1252);
+      const html_decoded_as_utf_8 = create_html(decoded_as_utf_8);
+
+      // "default" response type
+      // An XML-ish response is sniffed.
+      request("application/xml", encoded_xml, xml_decoded_as_windows_1252);
+      // An HTML-ish response isn't sniffed.
+      request("text/html", encoded_html, html_decoded_as_utf_8);
+      request("application/xml;charset=utf-8", encoded_xml, xml_decoded_as_utf_8);
+      request("application/xml;charset=windows-1252", encoded_xml, xml_decoded_as_windows_1252);
+      request("text/html;charset=utf-8", encoded_html, html_decoded_as_utf_8);
+      request("text/html;charset=windows-1252", encoded_html, html_decoded_as_windows_1252);
+      request("text/plain;charset=windows-1252", "%FF", "\u00FF");
+      request("text/plain", "%FF", "\uFFFD");
+      request("text/plain", "%FE%FF", "");
+      request("text/plain", "%FE%FF%FE%FF", "\uFEFF");
+      request("text/plain", "%EF%BB%BF", "");
+      request("text/plain", "%EF%BB%BF%EF%BB%BF", "\uFEFF");
+      request("text/plain", "%C2", "\uFFFD");
+      request("text/xml", "%FE%FF", "");
+      request("text/xml", "%FE%FF%FE%FF", "\uFEFF");
+      request("text/xml", "%EF%BB%BF", "");
+      request("text/xml", "%EF%BB%BF%EF%BB%BF", "\uFEFF");
+      request("text/plain", "%E3%81%B2", "\u3072");
+
+      // "text" response type
+      // An XML-ish response isn't sniffed.
+      request("application/xml", encoded_xml, xml_decoded_as_utf_8, "text");
+      // An HTML-ish response isn't sniffed.
+      request("text/html", encoded_html, html_decoded_as_utf_8, "text");
+      request("application/xml;charset=utf-8", encoded_xml, xml_decoded_as_utf_8, "text");
+      request("application/xml;charset=windows-1252", encoded_xml, xml_decoded_as_windows_1252, "text");
+      request("text/html;charset=utf-8", encoded_html, html_decoded_as_utf_8, "text");
+      request("text/html;charset=windows-1252", encoded_html, html_decoded_as_windows_1252, "text");
+      request("text/plain;charset=windows-1252", "%FF", "\u00FF", "text");
+      request("text/plain", "%FF", "\uFFFD", "text");
+      request("text/plain", "%FE%FF", "", "text");
+      request("text/plain", "%FE%FF%FE%FF", "\uFEFF", "text");
+      request("text/plain", "%EF%BB%BF", "", "text");
+      request("text/plain", "%EF%BB%BF%EF%BB%BF", "\uFEFF", "text");
+      request("text/plain", "%C2", "\uFFFD", "text");
+      request("text/plain;charset=bogus", "%C2", "\uFFFD", "text");
+      request("text/xml", "%FE%FF", "", "text");
+      request("text/xml", "%FE%FF%FE%FF", "\uFEFF", "text");
+      request("text/xml", "%EF%BB%BF", "", "text");
+      request("text/xml", "%EF%BB%BF%EF%BB%BF", "\uFEFF", "text");
+      request("text/plain", "%E3%81%B2", "\u3072", "text");
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/responsetype.html b/xhr/responsetype.html
similarity index 100%
rename from XMLHttpRequest/responsetype.html
rename to xhr/responsetype.html
diff --git a/XMLHttpRequest/responseurl.html b/xhr/responseurl.html
similarity index 100%
rename from XMLHttpRequest/responseurl.html
rename to xhr/responseurl.html
diff --git a/XMLHttpRequest/responsexml-basic.htm b/xhr/responsexml-basic.htm
similarity index 100%
rename from XMLHttpRequest/responsexml-basic.htm
rename to xhr/responsexml-basic.htm
diff --git a/xhr/responsexml-document-properties.htm b/xhr/responsexml-document-properties.htm
new file mode 100644
index 0000000..b008348
--- /dev/null
+++ b/xhr/responsexml-document-properties.htm
@@ -0,0 +1,109 @@
+<!doctype html>
+<html>
+  <head>
+    <title>XMLHttpRequest: responseXML document properties</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      var timePreXHR = Math.floor(new Date().getTime(new Date().getTime() - 3000) / 1000); // three seconds ago, in case there's clock drift
+      var client = new XMLHttpRequest()
+      client.open("GET", "resources/well-formed.xml", false)
+      client.send(null)
+      var responseURL = new URL('resources/well-formed.xml', location.href).href
+      var expected = {
+        domain:undefined,
+        URL:responseURL,
+        documentURI:responseURL,
+        baseURI:responseURL,
+        referrer:'',
+        title:'',
+        contentType:'application/xml',
+        readyState:'complete',
+        location:null,
+        defaultView:null,
+        body:null,
+        doctype:null,
+        all:undefined,
+        cookie:''
+      }
+
+      for (var name in expected) {
+        runTest(name, expected[name])
+      }
+
+      function runTest(name, value){
+        test(function(){
+          assert_equals(client.responseXML[name], value)
+        }, name)
+      }
+
+      async_test(t => {
+        const client = new XMLHttpRequest();
+        client.open("GET", "resources/redirect.py?location=well-formed.xml");
+        client.send();
+        client.onload = t.step_func_done(() => {
+          assert_equals(client.responseXML.URL, responseURL);
+          assert_equals(client.responseXML.baseURI, responseURL);
+        });
+      }, "Test document URL properties after redirect");
+
+      async_test(t => {
+        const client = new XMLHttpRequest();
+        client.open("GET", "resources/redirect.py?location=base.xml");
+        client.send();
+        client.onload = t.step_func_done(() => {
+          const localResponseURL = new URL('resources/base.xml', location.href).href;
+          assert_equals(client.responseXML.URL, localResponseURL);
+          assert_equals(client.responseXML.baseURI, 'https://example.com/');
+          client.responseXML.documentElement.remove();
+          assert_equals(client.responseXML.baseURI, localResponseURL);
+          const newBase = document.createElement("base"),
+                newBaseURL = "https://elsewhere.example/";
+          newBase.href = "https://elsewhere.example/";
+          client.responseXML.appendChild(newBase);
+          assert_equals(client.responseXML.baseURI, newBaseURL);
+          newBase.remove();
+          document.head.appendChild(newBase);
+          assert_equals(client.responseXML.baseURI, localResponseURL);
+          newBase.remove();
+        });
+      }, "Test document URL properties of document with <base> after redirect");
+
+      test(function() {
+        var lastModified = Math.floor(new Date(client.responseXML.lastModified).getTime() / 1000);
+        var now = Math.floor(new Date().getTime(new Date().getTime() + 3000) / 1000); // three seconds from now, in case there's clock drift
+        assert_greater_than_equal(lastModified, timePreXHR);
+        assert_less_than_equal(lastModified, now);
+      }, 'lastModified set to time of response if no HTTP header provided')
+
+      test(function() {
+        var client2 = new XMLHttpRequest()
+        client2.open("GET", "resources/last-modified.py", false)
+        client2.send(null)
+        assert_equals((new Date(client2.getResponseHeader('Last-Modified'))).getTime(), (new Date(client2.responseXML.lastModified)).getTime())
+      }, 'lastModified set to related HTTP header if provided')
+
+      test(function() {
+        client.responseXML.cookie = "thisshouldbeignored"
+        assert_equals(client.responseXML.cookie, "")
+      }, 'cookie (after setting it)')
+
+      var objectProps = [
+        "styleSheets",
+        "implementation",
+        "images",
+        "forms",
+        "links",
+      ];
+
+      for (let prop of objectProps) {
+        test(function() {
+          assert_equals(typeof(client.responseXML[prop]), "object")
+        }, prop + " should be an object")
+      }
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/responsexml-get-twice.htm b/xhr/responsexml-get-twice.htm
similarity index 100%
rename from XMLHttpRequest/responsexml-get-twice.htm
rename to xhr/responsexml-get-twice.htm
diff --git a/XMLHttpRequest/responsexml-media-type.htm b/xhr/responsexml-media-type.htm
similarity index 100%
rename from XMLHttpRequest/responsexml-media-type.htm
rename to xhr/responsexml-media-type.htm
diff --git a/XMLHttpRequest/responsexml-non-document-types.htm b/xhr/responsexml-non-document-types.htm
similarity index 100%
rename from XMLHttpRequest/responsexml-non-document-types.htm
rename to xhr/responsexml-non-document-types.htm
diff --git a/XMLHttpRequest/responsexml-non-well-formed.htm b/xhr/responsexml-non-well-formed.htm
similarity index 100%
rename from XMLHttpRequest/responsexml-non-well-formed.htm
rename to xhr/responsexml-non-well-formed.htm
diff --git a/xhr/security-consideration.sub.html b/xhr/security-consideration.sub.html
new file mode 100644
index 0000000..a364e2c
--- /dev/null
+++ b/xhr/security-consideration.sub.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+  <head>
+    <title>ProgressEvent: security consideration</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#security-considerations" data-tested-assertations="/following-sibling::p" />
+    <link rel="help" href="https://fetch.spec.whatwg.org/#http-fetch" data-tested-assertations="/following-sibling::ol[1]/li[3]/ol[1]/li[6]" />
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      async_test(function() {
+        var xhr = new XMLHttpRequest();
+
+        xhr.onprogress = this.unreached_func("MUST NOT dispatch progress event.");
+        xhr.onload = this.unreached_func("MUST NOT dispatch load event.");
+        xhr.onerror = this.step_func(function(pe) {
+          assert_equals(pe.type, "error");
+          assert_equals(pe.loaded, 0, "loaded is zero.");
+          assert_false(pe.lengthComputable, "lengthComputable is false.");
+          assert_equals(pe.total, 0, "total is zero.");
+        });
+        xhr.onloadend = this.step_func(function(pe) {
+          assert_equals(pe.type, "loadend");
+          assert_equals(pe.loaded, 0, "loaded is zero.");
+          assert_false(pe.lengthComputable, "lengthComputable is false.");
+          assert_equals(pe.total, 0, "total is zero.");
+          this.done();
+        });
+        xhr.open("GET", "http://{{host}}:{{ports[http][1]}}/xhr/resources/img.jpg", true);
+        xhr.send(null);
+      })
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/send-accept-language.htm b/xhr/send-accept-language.htm
similarity index 100%
rename from XMLHttpRequest/send-accept-language.htm
rename to xhr/send-accept-language.htm
diff --git a/XMLHttpRequest/send-accept.htm b/xhr/send-accept.htm
similarity index 100%
rename from XMLHttpRequest/send-accept.htm
rename to xhr/send-accept.htm
diff --git a/xhr/send-after-setting-document-domain.htm b/xhr/send-after-setting-document-domain.htm
new file mode 100644
index 0000000..49eeb95
--- /dev/null
+++ b/xhr/send-after-setting-document-domain.htm
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+  <head>
+    <title>XMLHttpRequest: send() with document.domain set</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[2]/ol[1]/li[3]" />
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      var test_base_url = location.protocol+'//www2.'+location.host+"/xhr/resources/",
+          test_windows = [
+            window.open(test_base_url + "send-after-setting-document-domain-window-1.htm"),
+            window.open(test_base_url + "send-after-setting-document-domain-window-2.htm"),
+          ],
+          num_tests_left = test_windows.length;
+
+      async_test(function(wrapper_test) {
+        window.addEventListener("message", function(evt) {
+          // run a shadow test that just forwards the results
+          async_test(function(test) {
+            assert_true(evt.data.passed, evt.data.message);
+            test.done();
+          }, evt.data.name);
+
+          // after last result comes in, close all test
+          // windows and complete the wrapper test.
+          if (--num_tests_left == 0) {
+            for (var i=0; i<test_windows.length; ++i) {
+              test_windows[i].close();
+            }
+            wrapper_test.done();
+          }
+        }, false);
+      }, "All tests ran");
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/send-authentication-basic-cors-not-enabled.htm b/xhr/send-authentication-basic-cors-not-enabled.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-basic-cors-not-enabled.htm
rename to xhr/send-authentication-basic-cors-not-enabled.htm
diff --git a/XMLHttpRequest/send-authentication-basic-cors.htm b/xhr/send-authentication-basic-cors.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-basic-cors.htm
rename to xhr/send-authentication-basic-cors.htm
diff --git a/XMLHttpRequest/send-authentication-basic-repeat-no-args.htm b/xhr/send-authentication-basic-repeat-no-args.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-basic-repeat-no-args.htm
rename to xhr/send-authentication-basic-repeat-no-args.htm
diff --git a/XMLHttpRequest/send-authentication-basic-setrequestheader-and-arguments.htm b/xhr/send-authentication-basic-setrequestheader-and-arguments.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-basic-setrequestheader-and-arguments.htm
rename to xhr/send-authentication-basic-setrequestheader-and-arguments.htm
diff --git a/XMLHttpRequest/send-authentication-basic-setrequestheader-existing-session.htm b/xhr/send-authentication-basic-setrequestheader-existing-session.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-basic-setrequestheader-existing-session.htm
rename to xhr/send-authentication-basic-setrequestheader-existing-session.htm
diff --git a/XMLHttpRequest/send-authentication-basic-setrequestheader.htm b/xhr/send-authentication-basic-setrequestheader.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-basic-setrequestheader.htm
rename to xhr/send-authentication-basic-setrequestheader.htm
diff --git a/XMLHttpRequest/send-authentication-basic.htm b/xhr/send-authentication-basic.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-basic.htm
rename to xhr/send-authentication-basic.htm
diff --git a/xhr/send-authentication-competing-names-passwords.htm b/xhr/send-authentication-competing-names-passwords.htm
new file mode 100644
index 0000000..bc6755c
--- /dev/null
+++ b/xhr/send-authentication-competing-names-passwords.htm
@@ -0,0 +1,50 @@
+<!doctype html>
+<html>
+  <head>
+    <title>XMLHttpRequest: send() - "Basic" authenticated requests with competing user name/password options</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/common/utils.js"></script>
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      function request(user1, pass1, user2, pass2, name) {
+        test(function() {
+          const client = new XMLHttpRequest(),
+                userwin = user2 || user1,
+                passwin = pass2 || pass1;
+          let urlstart = "";
+          if (user1 || pass1) {
+            urlstart = "http://";
+            if (user1) {
+              urlstart += user1;
+            }
+            if (pass1) {
+              urlstart += ":" + pass1;
+            }
+            urlstart += "@" + location.host + location.pathname.replace(/\/[^\/]*$/, '/');
+          }
+          client.open("GET", urlstart + "resources/authentication.py", false, user2, pass2);
+          client.setRequestHeader("x-user", userwin);
+          client.send(null);
+          assert_equals(client.responseText, ((userwin||'') + "\n" + (passwin||'')), 'responseText should contain the right user and password');
+        }, "XMLHttpRequest user/pass options: " + name);
+      }
+      // Cannot have just a password
+      request(null, null, token(), null, "user in open()");
+      request(null, null, token(), token(), "user/pass in open()");
+      request(null, null, token(), token(), "another user/pass in open(); must override cached credentials from previous test");
+      request(null, token(), token(), null, "pass in URL, user in open()");
+      request(null, token(), token(), token(), "pass in URL, user/pass in open()");
+      request(token(), null, null, null, "user in URL");
+      request(token(), null, null, token(), "user in URL, pass in open()");
+      request(token(), token(), null, null, "user/pass in URL");
+      request(token(), null, token(), null, "user in URL and open()");
+      request(token(), null, token(), token(), "user in URL; user/pass in open()");
+      request(token(), token(), token(), null, "user/pass in URL; user in open()");
+      request(token(), token(), null, token(), "user/pass in URL; pass in open()");
+      request(token(), token(), token(), token(), "user/pass in URL and open()");
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/send-authentication-cors-basic-setrequestheader.htm b/xhr/send-authentication-cors-basic-setrequestheader.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-cors-basic-setrequestheader.htm
rename to xhr/send-authentication-cors-basic-setrequestheader.htm
diff --git a/XMLHttpRequest/send-authentication-cors-setrequestheader-no-cred.htm b/xhr/send-authentication-cors-setrequestheader-no-cred.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-cors-setrequestheader-no-cred.htm
rename to xhr/send-authentication-cors-setrequestheader-no-cred.htm
diff --git a/XMLHttpRequest/send-authentication-existing-session-manual.htm b/xhr/send-authentication-existing-session-manual.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-existing-session-manual.htm
rename to xhr/send-authentication-existing-session-manual.htm
diff --git a/XMLHttpRequest/send-authentication-prompt-2-manual.htm b/xhr/send-authentication-prompt-2-manual.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-prompt-2-manual.htm
rename to xhr/send-authentication-prompt-2-manual.htm
diff --git a/XMLHttpRequest/send-authentication-prompt-manual.htm b/xhr/send-authentication-prompt-manual.htm
similarity index 100%
rename from XMLHttpRequest/send-authentication-prompt-manual.htm
rename to xhr/send-authentication-prompt-manual.htm
diff --git a/XMLHttpRequest/send-blob-with-no-mime-type.html b/xhr/send-blob-with-no-mime-type.html
similarity index 100%
rename from XMLHttpRequest/send-blob-with-no-mime-type.html
rename to xhr/send-blob-with-no-mime-type.html
diff --git a/XMLHttpRequest/send-conditional-cors.htm b/xhr/send-conditional-cors.htm
similarity index 100%
rename from XMLHttpRequest/send-conditional-cors.htm
rename to xhr/send-conditional-cors.htm
diff --git a/XMLHttpRequest/send-conditional.htm b/xhr/send-conditional.htm
similarity index 100%
rename from XMLHttpRequest/send-conditional.htm
rename to xhr/send-conditional.htm
diff --git a/xhr/send-content-type-charset.htm b/xhr/send-content-type-charset.htm
new file mode 100644
index 0000000..4e75df2
--- /dev/null
+++ b/xhr/send-content-type-charset.htm
@@ -0,0 +1,113 @@
+<!doctype html>
+<html>
+  <head>
+    <title>XMLHttpRequest: send() - charset parameter of Content-Type</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::ol[1]/li[4]/p/code[contains(text(),'Content-Type')]/.. following::ol[1]/li[4]/p/code[contains(text(),'Content-Type')]/../following-sibling::p" />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#dom-XMLHttpRequest-send-a-string" data-tested-assertations="following::p[2]" />
+  </head>
+  <body>
+    <div id="log"></div>
+    <script>
+      function request(input, output, title) {
+        title = title || document.title + ' - ' + input;
+        test(function() {
+        var client = new XMLHttpRequest()
+        client.open("POST", "resources/content.py", false)
+        if(input !== null)
+          client.setRequestHeader("Content-Type", input)
+        client.send("TEST")
+        assert_equals(client.responseText, "TEST")
+        assert_equals(client.getResponseHeader("x-request-content-type"), output)
+        }, title)
+      }
+
+      request(
+        "text; charset=ascii",
+        "text; charset=ascii",
+        "header with invalid MIME type is not changed"
+      )
+      request(
+        "",
+        "",
+        "header with invalid MIME type (empty string) is not changed"
+      )
+      request(
+        "charset=ascii",
+        "charset=ascii",
+        "known charset but bogus header - missing MIME type"
+      )
+      request(
+        "charset=bogus",
+        "charset=bogus",
+        "bogus charset and bogus header - missing MIME type"
+      )
+      request(
+        "text/plain;charset=utf-8",
+        "text/plain;charset=UTF-8",
+        "Correct text/plain MIME with charset"
+      )
+      request(
+        "text/x-pink-unicorn",
+        "text/x-pink-unicorn",
+        "If no charset= param is given, implementation should not add one - unknown MIME"
+      )
+      request(
+        "text/plain",
+        "text/plain",
+        "If no charset= param is given, implementation should not add one - known MIME"
+      )
+      request(
+        "text/plain;  hi=bye",
+        "text/plain;  hi=bye",
+        "If no charset= param is given, implementation should not add one - known MIME, unknown param, two spaces"
+      )
+      request(
+        "text/x-thepiano;charset= waddup",
+        "text/x-thepiano;charset=UTF-8",
+        "charset given but wrong, fix it (unknown MIME, bogus charset)"
+      )
+      request(
+        "text/plain;charset=utf-8;charset=waddup",
+        "text/plain;charset=UTF-8",
+        "charset given but wrong, fix it (known MIME, bogus charset)"
+      )
+      request(
+        "text/plain;charset=shift-jis",
+        "text/plain;charset=UTF-8",
+        "charset given but wrong, fix it (known MIME, actual charset)"
+      )
+      request(
+        "text/x-pink-unicorn; charset=windows-1252; charset=bogus; notrelated; charset=ascii",
+        "text/x-pink-unicorn;charset=UTF-8",
+        "Multiple charset parameters deduplicate, bogus parameter dropped"
+      )
+      request(
+        null,
+        "text/plain;charset=UTF-8",
+        "No content type set, give MIME and charset"
+      )
+      request(
+        "text/plain;charset= utf-8",
+        "text/plain;charset=UTF-8",
+        "charset with space")
+      request(
+        "text/plain;charset=\"utf-8\"",
+        "text/plain;charset=UTF-8",
+        "charset in double quotes")
+      request(
+        "text/plain;charset=\" utf-8\"",
+        "text/plain;charset=UTF-8",
+        "charset in double quotes with space")
+      request(
+        "text/plain;charset=\"u\\t\\f-8\"",
+        "text/plain;charset=UTF-8",
+        "charset in double quotes with backslashes")
+      request(
+        "YO/yo;charset=x;yo=YO; X=y",
+        "yo/yo;charset=UTF-8;yo=YO;x=y",
+        "unknown parameters need to be preserved")
+    </script>
+  </body>
+</html>
diff --git a/XMLHttpRequest/send-content-type-string.htm b/xhr/send-content-type-string.htm
similarity index 100%
rename from XMLHttpRequest/send-content-type-string.htm
rename to xhr/send-content-type-string.htm
diff --git a/XMLHttpRequest/send-data-arraybuffer.htm b/xhr/send-data-arraybuffer.htm
similarity index 100%
rename from XMLHttpRequest/send-data-arraybuffer.htm
rename to xhr/send-data-arraybuffer.htm
diff --git a/XMLHttpRequest/send-data-arraybufferview.htm b/xhr/send-data-arraybufferview.htm
similarity index 100%
rename from XMLHttpRequest/send-data-arraybufferview.htm
rename to xhr/send-data-arraybufferview.htm
diff --git a/XMLHttpRequest/send-data-blob.htm b/xhr/send-data-blob.htm
similarity index 100%
rename from XMLHttpRequest/send-data-blob.htm
rename to xhr/send-data-blob.htm
diff --git a/XMLHttpRequest/send-data-es-object.htm b/xhr/send-data-es-object.htm
similarity index 100%
rename from XMLHttpRequest/send-data-es-object.htm
rename to xhr/send-data-es-object.htm
diff --git a/XMLHttpRequest/send-data-formdata.htm b/xhr/send-data-formdata.htm
similarity index 100%
rename from XMLHttpRequest/send-data-formdata.htm
rename to xhr/send-data-formdata.htm
diff --git a/XMLHttpRequest/send-data-unexpected-tostring.htm b/xhr/send-data-unexpected-tostring.htm
similarity index 100%
rename from XMLHttpRequest/send-data-unexpected-tostring.htm
rename to xhr/send-data-unexpected-tostring.htm
diff --git a/XMLHttpRequest/send-entity-body-basic.htm b/xhr/send-entity-body-basic.htm
similarity index 100%
rename from XMLHttpRequest/send-entity-body-basic.htm
rename to xhr/send-entity-body-basic.htm
diff --git a/XMLHttpRequest/send-entity-body-document-bogus.htm b/xhr/send-entity-body-document-bogus.htm
similarity index 100%
rename from XMLHttpRequest/send-entity-body-document-bogus.htm
rename to xhr/send-entity-body-document-bogus.htm
diff --git a/XMLHttpRequest/send-entity-body-document.htm b/xhr/send-entity-body-document.htm
similarity index 100%
rename from XMLHttpRequest/send-entity-body-document.htm
rename to xhr/send-entity-body-document.htm
diff --git a/XMLHttpRequest/send-entity-body-empty.htm b/xhr/send-entity-body-empty.htm
similarity index 100%
rename from XMLHttpRequest/send-entity-body-empty.htm
rename to xhr/send-entity-body-empty.htm
diff --git a/XMLHttpRequest/send-entity-body-get-head-async.htm b/xhr/send-entity-body-get-head-async.htm
similarity index 100%
rename from XMLHttpRequest/send-entity-body-get-head-async.htm
rename to xhr/send-entity-body-get-head-async.htm
diff --git a/XMLHttpRequest/send-entity-body-get-head.htm b/xhr/send-entity-body-get-head.htm
similarity index 100%
rename from XMLHttpRequest/send-entity-body-get-head.htm
rename to xhr/send-entity-body-get-head.htm
diff --git a/XMLHttpRequest/send-entity-body-none.htm b/xhr/send-entity-body-none.htm
similarity index 100%
rename from XMLHttpRequest/send-entity-body-none.htm
rename to xhr/send-entity-body-none.htm
diff --git a/xhr/send-network-error-async-events.sub.htm b/xhr/send-network-error-async-events.sub.htm
new file mode 100644
index 0000000..c51a05c
--- /dev/null
+++ b/xhr/send-network-error-async-events.sub.htm
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onerror" data-tested-assertations="../.." />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::ol[1]/li[9]/ol/li[2] following::ol[1]/li[9]/ol/li[3]" />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[4] following::dd[4]/p" />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#network-error" data-tested-assertations=".." />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#request-error" data-tested-assertations="following::ol[1]/li[4] following::ol[1]/li[6] following::ol[1]/li[7] following::ol[1]/li[7]/ol/li[3] following::ol[1]/li[7]/ol/li[4] following::ol[1]/li[9] following::ol[1]/li[10]" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>XMLHttpRequest: The send() method: Fire a progress event named error when Network error happens (synchronous flag is unset)</title>
+</head>
+
+<body>
+    <div id="log"></div>
+
+    <script type="text/javascript">
+        var test = async_test();
+
+        test.step(function(){
+            var xhr = new XMLHttpRequest();
+            var expect =  ["loadstart", "upload.loadstart", 4, "upload.error", "upload.loadend", "error", "loadend"];
+            var actual = [];
+
+            xhr.onreadystatechange = test.step_func(() => {
+                if (xhr.readyState == 4) {
+                    actual.push(xhr.readyState);
+                }
+            });
+
+            xhr.onloadstart        = test.step_func(e => { actual.push(e.type); })
+            xhr.onloadend          = test.step_func_done(e => {
+                actual.push(e.type);
+                assert_array_equals(actual, expect);
+            })
+            xhr.onerror            = test.step_func(e => { actual.push(e.type); })
+
+            xhr.upload.onloadstart = test.step_func(e => { actual.push("upload." + e.type); })
+            xhr.upload.onloadend   = test.step_func(e => { actual.push("upload." + e.type); })
+            xhr.upload.onerror     = test.step_func(e => { actual.push("upload." + e.type); })
+
+            xhr.open("POST", "http://nonexistent.{{host}}:{{ports[http][0]}}", true);
+            xhr.send("Test Message");
+        });
+    </script>
+</body>
+</html>
diff --git a/xhr/send-network-error-sync-events.sub.htm b/xhr/send-network-error-sync-events.sub.htm
new file mode 100644
index 0000000..b9f4fdf
--- /dev/null
+++ b/xhr/send-network-error-sync-events.sub.htm
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[4] following::dd[4]/p" />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#network-error" data-tested-assertations=".." />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#request-error" data-tested-assertations="following::ol[1]/li[4] following::ol[1]/li[5]" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>XMLHttpRequest: The send() method: Throw a "throw an "NetworkError" exception when Network error happens (synchronous flag is set)</title>
+</head>
+
+<body>
+    <div id="log"></div>
+
+    <script type="text/javascript">
+        test(function()
+        {
+            var xhr = new XMLHttpRequest();
+
+            xhr.open("POST", "http://nonexistent.{{host}}:{{ports[http][0]}}", false);
+
+            assert_throws("NetworkError", function()
+            {
+                xhr.send("Test Message");
+            });
+            assert_equals(xhr.readyState, 4)
+
+        }, "http URL");
+
+        test(function()
+        {
+            var xhr = new XMLHttpRequest();
+
+            xhr.open("GET", "data:text/html;charset=utf-8;base64,PT0NUWVBFIGh0bWw%2BDQo8", false);
+
+            assert_throws("NetworkError", function()
+            {
+                xhr.send("Test Message");
+            });
+            assert_equals(xhr.readyState, 4)
+
+        }, "data URL");
+    </script>
+</body>
+</html>
diff --git a/XMLHttpRequest/send-no-response-event-loadend.htm b/xhr/send-no-response-event-loadend.htm
similarity index 100%
rename from XMLHttpRequest/send-no-response-event-loadend.htm
rename to xhr/send-no-response-event-loadend.htm
diff --git a/XMLHttpRequest/send-no-response-event-loadstart.htm b/xhr/send-no-response-event-loadstart.htm
similarity index 100%
rename from XMLHttpRequest/send-no-response-event-loadstart.htm
rename to xhr/send-no-response-event-loadstart.htm
diff --git a/XMLHttpRequest/send-no-response-event-order.htm b/xhr/send-no-response-event-order.htm
similarity index 100%
rename from XMLHttpRequest/send-no-response-event-order.htm
rename to xhr/send-no-response-event-order.htm
diff --git a/XMLHttpRequest/send-non-same-origin.htm b/xhr/send-non-same-origin.htm
similarity index 100%
rename from XMLHttpRequest/send-non-same-origin.htm
rename to xhr/send-non-same-origin.htm
diff --git a/XMLHttpRequest/send-receive-utf16.htm b/xhr/send-receive-utf16.htm
similarity index 100%
rename from XMLHttpRequest/send-receive-utf16.htm
rename to xhr/send-receive-utf16.htm
diff --git a/XMLHttpRequest/send-redirect-bogus-sync.htm b/xhr/send-redirect-bogus-sync.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect-bogus-sync.htm
rename to xhr/send-redirect-bogus-sync.htm
diff --git a/XMLHttpRequest/send-redirect-bogus.htm b/xhr/send-redirect-bogus.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect-bogus.htm
rename to xhr/send-redirect-bogus.htm
diff --git a/XMLHttpRequest/send-redirect-infinite-sync.htm b/xhr/send-redirect-infinite-sync.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect-infinite-sync.htm
rename to xhr/send-redirect-infinite-sync.htm
diff --git a/XMLHttpRequest/send-redirect-infinite.htm b/xhr/send-redirect-infinite.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect-infinite.htm
rename to xhr/send-redirect-infinite.htm
diff --git a/XMLHttpRequest/send-redirect-no-location.htm b/xhr/send-redirect-no-location.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect-no-location.htm
rename to xhr/send-redirect-no-location.htm
diff --git a/xhr/send-redirect-post-upload.htm b/xhr/send-redirect-post-upload.htm
new file mode 100644
index 0000000..5c1c638
--- /dev/null
+++ b/xhr/send-redirect-post-upload.htm
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onprogress" data-tested-assertations="../.." />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-progress" data-tested-assertations="../.." />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following::dt[@id="dom-xmlhttprequest-send-bodyinit"]/following::dd[1]/p[2] following::ol[1]/li[9]//li[1] following::ol[1]/li[9]//li[2]" />
+    <link rel="help" href="https://fetch.spec.whatwg.org/#http-fetch" data-tested-assertations="following::ol[1]/li[6]/dl/dd[1]//dd[3]" />
+    <link rel="help" href="https://fetch.spec.whatwg.org/#concept-http-redirect-fetch" data-tested-assertations="following::li[16]" />
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>XMLHttpRequest: The send() method: POSTing to URL that redirects</title>
+</head>
+
+<body>
+    <div id="log"></div>
+
+    <script type="text/javascript">
+    function testRedirectPost(params) {
+        var test = async_test(document.title + " (" + params.name + ")");
+        var actual = [];
+        // We check upload.onprogress with a boolean because it *might* fire more than once
+        var progressFiredReadyState1 = false;
+
+        var expectedHeaders, expectedEvents;
+
+        // 307 redirects should resend the POST data, and events and headers will be a little different..
+        if(params.expectResendPost) {
+            expectedHeaders = {
+                "X-Request-Content-Length": "12000",
+                "X-Request-Content-Type": "text/plain;charset=UTF-8",
+                "X-Request-Method": "POST",
+                "X-Request-Query": "NO",
+                "Content-Length": "12000"
+            }
+            expectedEvents = [
+                "xhr onreadystatechange 1",
+                "xhr loadstart 1",
+                "upload loadstart 1",
+                "upload loadend 1",
+                "xhr onreadystatechange 2",
+                "xhr onreadystatechange 3",
+                "xhr onreadystatechange 4",
+                "xhr load 4",
+                "xhr loadend 4"
+            ];
+        } else {
+            // setting the right expectations for POST resent as GET without request body
+            expectedHeaders = {
+                "X-Request-Content-Length": "NO",
+                "X-Request-Content-Type": "NO",
+                "X-Request-Method": "GET",
+                "X-Request-Query": "NO"
+            }
+            expectedEvents = [
+                "xhr onreadystatechange 1",
+                "xhr loadstart 1",
+                "upload loadstart 1",
+                "upload loadend 1",
+                "xhr onreadystatechange 2",
+                /* we expect no onreadystatechange readyState=3 event because there is no loading content */
+                "xhr onreadystatechange 4",
+                "xhr load 4",
+                "xhr loadend 4"
+            ];
+        }
+        // Override expectations if provided.
+        if(params.expectedContentType)
+          expectedHeaders["X-Request-Content-Type"] = params.expectedContentType;
+
+        test.step(function()
+        {
+            var xhr = new XMLHttpRequest();
+
+            xhr.upload.onloadstart = test.step_func(function(e) {
+                actual.push("upload loadstart " + xhr.readyState);
+            });
+            xhr.upload.onprogress = test.step_func(function(e) {
+                // events every 50ms, one final when uploading is done
+                if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
+                    assert_equals(xhr.status, 200, "JS never gets to see the 30x status code");
+                }
+                progressFiredReadyState1 = xhr.readyState === xhr.OPENED;
+            });
+            xhr.upload.onloadend = test.step_func(function() {
+                actual.push("upload loadend " + xhr.readyState);
+            });
+            xhr.onloadstart = test.step_func(function() {
+                actual.push("xhr loadstart " + xhr.readyState);
+            });
+            xhr.onreadystatechange = test.step_func(function() {
+                if(xhr.readyState >= xhr.HEADERS_RECEIVED) {
+                    assert_equals(xhr.status, 200, "JS never gets to see the 30x status code");
+                }
+                actual.push("xhr onreadystatechange " + xhr.readyState);
+            });
+            xhr.onload = test.step_func(function(e)
+            {
+                actual.push("xhr load " + xhr.readyState);
+            });
+            xhr.onloadend = test.step_func(function(e)
+            {
+                actual.push("xhr loadend " + xhr.readyState);
+
+                assert_true(progressFiredReadyState1, "One progress event should fire on xhr.upload when readyState is 1");
+
+                // Headers will tell us if data was sent when expected
+                for(var header in expectedHeaders) {
+                    assert_equals(xhr.getResponseHeader(header), expectedHeaders[header], header);
+                }
+
+                assert_array_equals(actual, expectedEvents, "events firing in expected order and states");
+                if (params.expectedBody)
+                  assert_equals(xhr.response, params.expectedBody, 'request body was resent');
+                test.done();
+            });
+
+            xhr.open("POST", "./resources/redirect.py?location=content.py&code=" + params.code, true);
+            xhr.send(params.body);
+        });
+    }
+
+    const stringBody = "Test Message".repeat(1000);
+    const blobBody = new Blob(new Array(1000).fill("Test Message"));
+
+    testRedirectPost({name: "301", code: 301, expectResendPost: false, body: stringBody});
+    testRedirectPost({name: "302", code: 302, expectResendPost: false, body: stringBody});
+    testRedirectPost({name: "303", code: 303, expectResendPost: false, body: stringBody});
+    testRedirectPost({name: "307 (string)", code: 307, expectResendPost: true,  body: stringBody, expectedBody: stringBody });
+    testRedirectPost({name: "307 (blob)", code: 307, expectResendPost: true, body: blobBody, expectedBody: stringBody, expectedContentType: "NO" });
+    </script>
+</body>
+</html>
diff --git a/XMLHttpRequest/send-redirect-to-cors.htm b/xhr/send-redirect-to-cors.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect-to-cors.htm
rename to xhr/send-redirect-to-cors.htm
diff --git a/XMLHttpRequest/send-redirect-to-non-cors.htm b/xhr/send-redirect-to-non-cors.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect-to-non-cors.htm
rename to xhr/send-redirect-to-non-cors.htm
diff --git a/XMLHttpRequest/send-redirect.htm b/xhr/send-redirect.htm
similarity index 100%
rename from XMLHttpRequest/send-redirect.htm
rename to xhr/send-redirect.htm
diff --git a/XMLHttpRequest/send-response-event-order.htm b/xhr/send-response-event-order.htm
similarity index 100%
rename from XMLHttpRequest/send-response-event-order.htm
rename to xhr/send-response-event-order.htm
diff --git a/XMLHttpRequest/send-response-upload-event-loadend.htm b/xhr/send-response-upload-event-loadend.htm
similarity index 100%
rename from XMLHttpRequest/send-response-upload-event-loadend.htm
rename to xhr/send-response-upload-event-loadend.htm
diff --git a/XMLHttpRequest/send-response-upload-event-loadstart.htm b/xhr/send-response-upload-event-loadstart.htm
similarity index 100%
rename from XMLHttpRequest/send-response-upload-event-loadstart.htm
rename to xhr/send-response-upload-event-loadstart.htm
diff --git a/XMLHttpRequest/send-response-upload-event-progress.htm b/xhr/send-response-upload-event-progress.htm
similarity index 100%
rename from XMLHttpRequest/send-response-upload-event-progress.htm
rename to xhr/send-response-upload-event-progress.htm
diff --git a/xhr/send-send.any.js b/xhr/send-send.any.js
new file mode 100644
index 0000000..2bfe404
--- /dev/null
+++ b/xhr/send-send.any.js
@@ -0,0 +1,7 @@
+test(function() {
+  var client = new XMLHttpRequest()
+  client.open("GET", "resources/well-formed.xml")
+  client.send(null)
+  assert_throws("InvalidStateError", function() { client.send(null) })
+  client.abort()
+}, "XMLHttpRequest: send() - send()");
diff --git a/XMLHttpRequest/send-sync-blocks-async.htm b/xhr/send-sync-blocks-async.htm
similarity index 100%
rename from XMLHttpRequest/send-sync-blocks-async.htm
rename to xhr/send-sync-blocks-async.htm
diff --git a/XMLHttpRequest/send-sync-no-response-event-load.htm b/xhr/send-sync-no-response-event-load.htm
similarity index 100%
rename from XMLHttpRequest/send-sync-no-response-event-load.htm
rename to xhr/send-sync-no-response-event-load.htm
diff --git a/XMLHttpRequest/send-sync-no-response-event-loadend.htm b/xhr/send-sync-no-response-event-loadend.htm
similarity index 100%
rename from XMLHttpRequest/send-sync-no-response-event-loadend.htm
rename to xhr/send-sync-no-response-event-loadend.htm
diff --git a/XMLHttpRequest/send-sync-no-response-event-order.htm b/xhr/send-sync-no-response-event-order.htm
similarity index 100%
rename from XMLHttpRequest/send-sync-no-response-event-order.htm
rename to xhr/send-sync-no-response-event-order.htm
diff --git a/XMLHttpRequest/send-sync-response-event-order.htm b/xhr/send-sync-response-event-order.htm
similarity index 100%
rename from XMLHttpRequest/send-sync-response-event-order.htm
rename to xhr/send-sync-response-event-order.htm
diff --git a/XMLHttpRequest/send-sync-timeout.htm b/xhr/send-sync-timeout.htm
similarity index 100%
rename from XMLHttpRequest/send-sync-timeout.htm
rename to xhr/send-sync-timeout.htm
diff --git a/xhr/send-timeout-events.htm b/xhr/send-timeout-events.htm
new file mode 100644
index 0000000..eae2568
--- /dev/null
+++ b/xhr/send-timeout-events.htm
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <title>XMLHttpRequest: The send() method: timeout is not 0 </title>
+</head>
+
+<body>
+    <div id="log"></div>
+
+    <script type="text/javascript">
+        async_test(t => {
+            const xhr = new XMLHttpRequest(),
+                  expect = [4, "", "upload.timeout", "upload.loadend", "timeout", "loadend"];
+            let actual = [];
+
+            xhr.onreadystatechange = t.step_func(() => {
+                if (xhr.readyState == 4) {
+                    actual.push(xhr.readyState, xhr.response);
+                }
+            });
+
+            xhr.onloadend = t.step_func_done(e => {
+                assert_equals(e.loaded, 0);
+                assert_equals(e.total, 0);
+                actual.push(e.type);
+                assert_array_equals(actual, expect);
+            });
+
+            xhr.ontimeout = t.step_func(e => {
+                assert_equals(e.loaded, 0);
+                assert_equals(e.total, 0);
+                actual.push(e.type);
+            });
+
+
+            xhr.upload.onloadend = t.step_func(e => {
+                assert_equals(e.loaded, 0);
+                assert_equals(e.total, 0);
+                actual.push("upload." + e.type);
+            });
+
+            xhr.upload.ontimeout = t.step_func(e => {
+                assert_equals(e.loaded, 0);
+                assert_equals(e.total, 0);
+                actual.push("upload." + e.type);
+            });
+
+
+            let content = "";
+            for (var i = 0; i < 121026; i++) {
+                content += "[" + i + "]";
+            }
+
+            xhr.open("POST", "./resources/trickle.py", true);
+            xhr.timeout = 1;
+            xhr.send(content);
+        });
+    </script>
+</body>
+</html>
diff --git a/XMLHttpRequest/send-usp.any.js b/xhr/send-usp.any.js
similarity index 100%
rename from XMLHttpRequest/send-usp.any.js
rename to xhr/send-usp.any.js
diff --git a/XMLHttpRequest/setrequestheader-after-send.htm b/xhr/setrequestheader-after-send.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-after-send.htm
rename to xhr/setrequestheader-after-send.htm
diff --git a/XMLHttpRequest/setrequestheader-allow-empty-value.htm b/xhr/setrequestheader-allow-empty-value.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-allow-empty-value.htm
rename to xhr/setrequestheader-allow-empty-value.htm
diff --git a/XMLHttpRequest/setrequestheader-allow-whitespace-in-value.htm b/xhr/setrequestheader-allow-whitespace-in-value.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-allow-whitespace-in-value.htm
rename to xhr/setrequestheader-allow-whitespace-in-value.htm
diff --git a/XMLHttpRequest/setrequestheader-before-open.htm b/xhr/setrequestheader-before-open.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-before-open.htm
rename to xhr/setrequestheader-before-open.htm
diff --git a/XMLHttpRequest/setrequestheader-bogus-name.htm b/xhr/setrequestheader-bogus-name.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-bogus-name.htm
rename to xhr/setrequestheader-bogus-name.htm
diff --git a/XMLHttpRequest/setrequestheader-bogus-value.htm b/xhr/setrequestheader-bogus-value.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-bogus-value.htm
rename to xhr/setrequestheader-bogus-value.htm
diff --git a/XMLHttpRequest/setrequestheader-case-insensitive.htm b/xhr/setrequestheader-case-insensitive.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-case-insensitive.htm
rename to xhr/setrequestheader-case-insensitive.htm
diff --git a/XMLHttpRequest/setrequestheader-content-type.htm b/xhr/setrequestheader-content-type.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-content-type.htm
rename to xhr/setrequestheader-content-type.htm
diff --git a/XMLHttpRequest/setrequestheader-header-allowed.htm b/xhr/setrequestheader-header-allowed.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-header-allowed.htm
rename to xhr/setrequestheader-header-allowed.htm
diff --git a/XMLHttpRequest/setrequestheader-header-forbidden.htm b/xhr/setrequestheader-header-forbidden.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-header-forbidden.htm
rename to xhr/setrequestheader-header-forbidden.htm
diff --git a/XMLHttpRequest/setrequestheader-open-setrequestheader.htm b/xhr/setrequestheader-open-setrequestheader.htm
similarity index 100%
rename from XMLHttpRequest/setrequestheader-open-setrequestheader.htm
rename to xhr/setrequestheader-open-setrequestheader.htm
diff --git a/XMLHttpRequest/status-async.htm b/xhr/status-async.htm
similarity index 100%
rename from XMLHttpRequest/status-async.htm
rename to xhr/status-async.htm
diff --git a/XMLHttpRequest/status-basic.htm b/xhr/status-basic.htm
similarity index 100%
rename from XMLHttpRequest/status-basic.htm
rename to xhr/status-basic.htm
diff --git a/XMLHttpRequest/status-error.htm b/xhr/status-error.htm
similarity index 100%
rename from XMLHttpRequest/status-error.htm
rename to xhr/status-error.htm
diff --git a/xhr/sync-no-progress.any.js b/xhr/sync-no-progress.any.js
new file mode 100644
index 0000000..a915e4d
--- /dev/null
+++ b/xhr/sync-no-progress.any.js
@@ -0,0 +1,12 @@
+test(t => {
+  let xhr = new XMLHttpRequest();
+  let loadEventFired = false;
+  xhr.onprogress = t.unreached_func('progress event should not be fired');
+  xhr.onload = () => {
+    loadEventFired = true;
+  };
+  xhr.open('GET', 'resources/trickle.py?count=4&delay=150', false);
+  xhr.send();
+  // Check the load event as a sanity check that the test is working.
+  assert_true(loadEventFired, 'load event should have fired');
+}, 'progress event should not be fired by sync XHR');
diff --git a/XMLHttpRequest/template-element.html b/xhr/template-element.html
similarity index 100%
rename from XMLHttpRequest/template-element.html
rename to xhr/template-element.html
diff --git a/XMLHttpRequest/timeout-cors-async.htm b/xhr/timeout-cors-async.htm
similarity index 100%
rename from XMLHttpRequest/timeout-cors-async.htm
rename to xhr/timeout-cors-async.htm
diff --git a/xhr/timeout-multiple-fetches.html b/xhr/timeout-multiple-fetches.html
new file mode 100644
index 0000000..30d6b73
--- /dev/null
+++ b/xhr/timeout-multiple-fetches.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<title>XMLHttpRequest: timeout, redirects, and CORS preflights</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/get-host-info.sub.js></script>
+<div id=log></div>
+<script>
+async_test(t => {
+  const client = new XMLHttpRequest
+  client.open("GET", "resources/redirect.py?delay=500&location=delay.py") // 500 + 500 = 1000
+  client.timeout = 1000
+  client.send()
+  client.ontimeout = t.step_func_done(() => {
+    assert_equals(client.readyState, 4)
+  })
+  client.onload = t.unreached_func("load event fired")
+}, "Redirects should not reset the timer")
+
+async_test(t => {
+  const client = new XMLHttpRequest
+  client.open("YO", get_host_info().HTTP_REMOTE_ORIGIN + "/xhr/resources/delay.py")
+  client.timeout = 1000
+  client.send()
+  client.ontimeout = t.step_func_done(() => {
+    assert_equals(client.readyState, 4)
+  })
+  client.onload = t.unreached_func("load event fired")
+}, "CORS preflights should not reset the timer")
+</script>
diff --git a/XMLHttpRequest/timeout-sync.htm b/xhr/timeout-sync.htm
similarity index 100%
rename from XMLHttpRequest/timeout-sync.htm
rename to xhr/timeout-sync.htm
diff --git a/XMLHttpRequest/xmlhttprequest-basic.htm b/xhr/xmlhttprequest-basic.htm
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-basic.htm
rename to xhr/xmlhttprequest-basic.htm
diff --git a/XMLHttpRequest/xmlhttprequest-eventtarget.htm b/xhr/xmlhttprequest-eventtarget.htm
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-eventtarget.htm
rename to xhr/xmlhttprequest-eventtarget.htm
diff --git a/XMLHttpRequest/xmlhttprequest-network-error-sync.htm b/xhr/xmlhttprequest-network-error-sync.htm
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-network-error-sync.htm
rename to xhr/xmlhttprequest-network-error-sync.htm
diff --git a/XMLHttpRequest/xmlhttprequest-network-error.htm b/xhr/xmlhttprequest-network-error.htm
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-network-error.htm
rename to xhr/xmlhttprequest-network-error.htm
diff --git a/XMLHttpRequest/xmlhttprequest-sync-block-defer-scripts-subframe.html b/xhr/xmlhttprequest-sync-block-defer-scripts-subframe.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-sync-block-defer-scripts-subframe.html
rename to xhr/xmlhttprequest-sync-block-defer-scripts-subframe.html
diff --git a/XMLHttpRequest/xmlhttprequest-sync-block-defer-scripts.html b/xhr/xmlhttprequest-sync-block-defer-scripts.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-sync-block-defer-scripts.html
rename to xhr/xmlhttprequest-sync-block-defer-scripts.html
diff --git a/XMLHttpRequest/xmlhttprequest-sync-block-scripts.html b/xhr/xmlhttprequest-sync-block-scripts.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-sync-block-scripts.html
rename to xhr/xmlhttprequest-sync-block-scripts.html
diff --git a/xhr/xmlhttprequest-sync-default-feature-policy.sub.html b/xhr/xmlhttprequest-sync-default-feature-policy.sub.html
new file mode 100644
index 0000000..5ad5557
--- /dev/null
+++ b/xhr/xmlhttprequest-sync-default-feature-policy.sub.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<body>
+  <meta charset="utf-8">
+  <title>Synchronous XMLHttpRequest Feature Policy Test</title>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script src=util/utils.js></script>
+  <script>
+  'use strict';
+  run_all_fp_tests_allow_all(
+      'http://{{domains[www]}}:{{ports[http][0]}}',
+      'sync-xhr',
+      'NetworkError',
+      () => {
+        return new Promise((resolve, reject) => {
+          try {
+            var xhr = new XMLHttpRequest();
+            xhr.open("GET", "data:,", false);
+            try {
+              xhr.send();
+            } catch(e) {
+              reject(e);
+            }
+          } catch(e) {
+            reject({"name": "UnexpectedException:" + e.name});
+          }
+          resolve();
+       });
+      });
+  </script>
+</body>
diff --git a/XMLHttpRequest/xmlhttprequest-sync-not-hang-scriptloader-subframe.html b/xhr/xmlhttprequest-sync-not-hang-scriptloader-subframe.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-sync-not-hang-scriptloader-subframe.html
rename to xhr/xmlhttprequest-sync-not-hang-scriptloader-subframe.html
diff --git a/XMLHttpRequest/xmlhttprequest-sync-not-hang-scriptloader.html b/xhr/xmlhttprequest-sync-not-hang-scriptloader.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-sync-not-hang-scriptloader.html
rename to xhr/xmlhttprequest-sync-not-hang-scriptloader.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-aborted.html b/xhr/xmlhttprequest-timeout-aborted.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-aborted.html
rename to xhr/xmlhttprequest-timeout-aborted.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-abortedonmain.html b/xhr/xmlhttprequest-timeout-abortedonmain.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-abortedonmain.html
rename to xhr/xmlhttprequest-timeout-abortedonmain.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-overrides.html b/xhr/xmlhttprequest-timeout-overrides.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-overrides.html
rename to xhr/xmlhttprequest-timeout-overrides.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-overridesexpires.html b/xhr/xmlhttprequest-timeout-overridesexpires.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-overridesexpires.html
rename to xhr/xmlhttprequest-timeout-overridesexpires.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-reused.html b/xhr/xmlhttprequest-timeout-reused.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-reused.html
rename to xhr/xmlhttprequest-timeout-reused.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-simple.html b/xhr/xmlhttprequest-timeout-simple.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-simple.html
rename to xhr/xmlhttprequest-timeout-simple.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-synconmain.html b/xhr/xmlhttprequest-timeout-synconmain.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-synconmain.html
rename to xhr/xmlhttprequest-timeout-synconmain.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-twice.html b/xhr/xmlhttprequest-timeout-twice.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-twice.html
rename to xhr/xmlhttprequest-timeout-twice.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-worker-aborted.html b/xhr/xmlhttprequest-timeout-worker-aborted.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-worker-aborted.html
rename to xhr/xmlhttprequest-timeout-worker-aborted.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-worker-overrides.html b/xhr/xmlhttprequest-timeout-worker-overrides.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-worker-overrides.html
rename to xhr/xmlhttprequest-timeout-worker-overrides.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-worker-overridesexpires.html b/xhr/xmlhttprequest-timeout-worker-overridesexpires.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-worker-overridesexpires.html
rename to xhr/xmlhttprequest-timeout-worker-overridesexpires.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-worker-simple.html b/xhr/xmlhttprequest-timeout-worker-simple.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-worker-simple.html
rename to xhr/xmlhttprequest-timeout-worker-simple.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-worker-synconworker.html b/xhr/xmlhttprequest-timeout-worker-synconworker.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-worker-synconworker.html
rename to xhr/xmlhttprequest-timeout-worker-synconworker.html
diff --git a/XMLHttpRequest/xmlhttprequest-timeout-worker-twice.html b/xhr/xmlhttprequest-timeout-worker-twice.html
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-timeout-worker-twice.html
rename to xhr/xmlhttprequest-timeout-worker-twice.html
diff --git a/XMLHttpRequest/xmlhttprequest-unsent.htm b/xhr/xmlhttprequest-unsent.htm
similarity index 100%
rename from XMLHttpRequest/xmlhttprequest-unsent.htm
rename to xhr/xmlhttprequest-unsent.htm